8. Работа с ПЛИС

Наш курс в основном сосредоточен на написании собственной логики на языках описания аппаратуры и последующего её тестирования (верификации), поэтому до этого мы учились пользоваться именно симулятором, а не работали с реальной ПЛИС. Тем не менее знания по работе с ПЛИС тоже важны, так что в этой части мы расскажем о работе с ПЛИС.

Сразу скажем, что современные ПЛИС достаточно сложные устройства и про их внутреннее устройство можно узнать в лекции Школы синтеза цифровых схем.

Процесс генерации прошивки

К сожалению, для генерации прошивки необходимо использовать проприетарные САПР от производителей ПЛИС, поэтому процесс работы для разных ПЛИС отличается[1], но общий процесс един для всех.

Первый шаг — синтез (synthesis) высокоуровневого кода на HDL в примитивы: LUT-ы, регистры, ALU, доступные на конкретной ПЛИС. Следующий шаг — раскладка (place and route) примитивов в ячейки конкретной ПЛИС. Последний шаг — генерация прошивки (bitstream).

Это крупные шаги, на самом деле каждый из них включает в себя более мелкие этапы, например анализ таймингов.

Наши ПЛИС

В доступе у авторов курса имеются две ПЛИС: Sipeed Tang Primer 25K и Sipeed Tang Mega 138K Pro Dock. Они используют чипы от компании Gowin, поэтому для дальнейшей работы предлагается установить САПР Gowin. Однако стандартное средство записи прошивок под Linux ведёт себя не очень хорошо, поэтому для прошивки будем использовать openFPGAloader.

Распознаватель числа, заданного кодом Хаффмана

Как и всегда будем строить наше повествование на примере. Весь код можно найти в репозитории, мы же покажем только верхнеуровневый модуль, и другие важные для работы с ПЛИС файлы. В качестве примера у нас будет распознаватель кода Хаффмана для чисел от 1 до 6, который будет подсвечивать светодиод с соответствующим номером, в соответствии с таблицей.

Число Код Хаффмана

1

000

2

001

3

01

4

10

5

110

6

111

src/08-fpga/huffman_top.sv
module huffman_top (  (1)
    input logic clk_i,

    // inputs
    input logic btn_0_i,   (2)
    input logic btn_1_i,
    input logic rst_btn_i,

    // outputs
    output logic [5:0] led_o  (3)
);

  logic btn_0_db, btn_1_db;

  debouncer btn_0_debouncer (  (4)
      .clk_i(clk_i),
      .rst_ni(rst_btn_i),
      .btn_i(btn_0_i),
      .btn_db_o(btn_0_db)
  );

  debouncer inv_btn_1_debouncer (
      .clk_i(clk_i),
      .rst_ni(rst_btn_i),
      .btn_i(btn_1_i),
      .btn_db_o(btn_1_db)
  );

  logic btn_0, btn_1;

  re_detector btn_0_re_detector (  (5)
      .clk_i (clk_i),
      .rst_ni(rst_btn_i),
      .in_i  (btn_0_db),
      .out_o (btn_0)
  );

  re_detector btn_1_re_detector (
      .clk_i (clk_i),
      .rst_ni(rst_btn_i),
      .in_i  (btn_1_db),
      .out_o (btn_1)
  );

  logic [2:0] number;

  huffman_led led_rsm (  (6)
      .btn_0_i(btn_0),
      .btn_1_i(btn_1),
      .number_o(number),
      .clk_i(clk_i),
      .rst_ni(rst_btn_i)
  );

  logic [5:0] inv_select;

  demux6 demux_led (  (7)
      .number_i(number),
      .select_o(inv_select)
  );

  assign led_o = ~inv_select;

endmodule
1 Объявляем наш верхнеуровневый модуль, именно к нему мы будем подключать все внешние сигналы.
2 Обратите внимание, кнопки на наших ПЛИС — нормально замкнутые, то есть в нажатом состоянии сигнал — логический ноль.
3 Светодиоды так же включаются на логический ноль.
4 Первый модуль на пути сигнала — подавитель дребезга. Физические кнопки не идеальны и на самом деле генерируют падение и повышение сигнала с большой частотой при нажатии[2]. Поэтому для работы с сигналом дребезг нужно подавить.
5 Следующий модуль заставляет систему регистрировать нажатие только один раз, вместо непрерывного сигнала.
6 Конечный автомат превщающий код Хаффмана в число.
7 Дешифратор для выбора светодиода.

Работа с САПР

У нас есть код, теперь его нужно превратить в прошивку для ПЛИС. Будем использовать Tang Primer 25K, аналогичные файлы для Mega 138K Pro Dock есть в репозитории.

Все действия можно производить из графического интерфейса САПР, однако такой подход менее переносим, поэтому для управления САПР чаще всего будет применяться язык скриптовый язык Tcl[3].

Кроме кода нам нужно описать, как называются в нашем коде внешние сигналы, а также какую частоту управляющего сигнала мы хотим получить. К сожалению, у каждого производителя свой формат описания.

Разберем формат описания сигналов на примере одного сигнала.

src/08-fpga/primer25k_pin_constraints.cst
IO_LOC "led_o[5]" B10; (1)
IO_PORT "led_o[5]" PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3; (2)
1 Директива IO_LOC указывает, что сигнал led_o[5] нужно подключить к пину B10;
2 Директива IO_PORT указывает на настройки подключения, при написании вручную, можно опускать.

Для генерации файлов описания сигналов можно использовать инструмент Gowin FloorPlanner. Он автоматически умеет генерировать директивы IO_LOC и IO_PORT с настройками по умолчанию здорово экономя время. Побольше о нём можно найти в руководстве SUG935, которое находится в директории $FPGA_TOOLS/doc/EN.

src/08-fpga/timing_constraints.sdc
create_clock -name clk -period 10 [get_ports {clk_i}] (1)
1 create_clock указывает на то, что хотим создать управляющий сигнал, -name clk указывает имя, -period указывает период в наносекундах, [get_ports {clk_i}] указывает к какому порту подключается сигнал.

Для генерации файлов описания управляющих сигналов можно использовать Gowin Timing Constraint Editor. Он обладает схожими с FloorPlanner удобствами. Код руководства: SUG940.

Теперь мы можем создать проект. Здесь будем предполагать, что мы находимся в директории src/08-fpga

Мы показываем примеры для платы Primer 25k, поэтому они имеют префикс primer25k. В случае если вы используете Mega 138k, то используйте файлы с префиксом mega138k.

Для генерации проекта запустим скрипт на языке Tcl из консольного обработчика команд САПР.

$ gw_sh primer25k.tcl

После чего проект из директории можно будет открыть с помощью GUI интерфейса gw_ide.

Рассмотрим что же написано в скрипте primer25k.tcl.

src/08-fpga/primer25k.tcl
create_project -name primer25k_huffman -pn GW5A-LV25MG121NES -device_version A -force ; (1)
set_option -output_base_name primer25k_huffman ; (2)
add_file -type cst ../primer25k_pin_constraints.cst ; (3)
source ../common.tcl ; (4)
1 Создаем проект, указываем его название, модель ПЛИС, а так же указываем, что проект нужно создать, даже если такая директория уже существует. Точки с запятой не обязательны, но необходимы для комментариев в конце строки.
2 Указываем имя файла прошивки.
3 Добавляем файл описания сигналов, так как он специфичен для конкретной модели ПЛИС. Обратите внимание, здесь мы находимся уже в директории проекта, поэтому нам надо спуститься на уровень ниже.
4 Вызываем другой файл с настройками для нескольких ПЛИС.
src/08-fpga/common.tcl
set_option -use_cpu_as_gpio 1 ; (1) (2)
set_option -synthesis_tool gowinsynthesis
set_option -top_module huffman_top ; (3)
set_option -verilog_std sysv2017 ; (4)

add_file -type verilog ../debouncer.sv ; (5)
add_file -type verilog ../re_detector.sv
add_file -type verilog ../huffman_led/huffman_led.sv
add_file -type verilog ../demux6.sv
add_file -type verilog ../huffman_top.sv
add_file -type sdc ../timing_constraints.sdc
1 Опишем только важные опции.
2 Укажем, что управляющий сигнал не должен быть зарезервирован для некоего модуля CPU.
3 Укажем верхнеуровневый модуль.
4 Укажем версию Verilog, иначе наш код на SystemVerilog не синтезируется.
5 Добавим файлы с кодом и с описанием управляющего сигнала.
Подробнее об этих командах можно узнать в руководстве SUG100.

После открытия проекта primer25k_huffman в GUI, для генерации прошивки нужно нажать на зеленые стрелочки на панели инструментов и дождаться завершения процесса.

gw ide

Теперь можно подключить ПЛИС.

Для Primer 25k
primer25k
Для Mega 138k
mega138k

Осталось прошить ПЛИС, для этого необходимо выполнить

$ openFPGALoader primer25k_huffman/impl/pnr/primer25k_huffman.fs

Теперь можно пробовать вводить числа в коде Хаффмана.

Не забывайте про упражнения.

1. Попытки сделать open-source решения существуют, но часто требуют реверс-инженеринга: https://github.com/YosysHQ/nextpnr
3. Внезапно статья на русской Википедии достаточно хороша: https://ru.wikipedia.org/wiki/Tcl