(по материалам компании Lushay Labs)
Отладочные платы, вместо микроконтроллеров содержащие программируемые логические интегральные схемы -- ПЛИС, или FPGA (Field-Programmable Gate Array), уже сегодня могут стать не менее популярными, чем платы Arduino. В значительной мере этому способствует наличие на рынке доступных плат китайского производства наряду с программными средствами разработки с открытым исходным кодом (Open Source Software -- OSS).
Одной из таких плат является недорогая (на момент написания статьи цена составляла порядка 2 тыс. р) плата Tang Nano 9K, разработанная на основе ПЛИС GW1NR-9 компании Gowin Semiconductor. Помимо коннекторов для подключения разнообразных дисплеев и встроенного программатора, эта плата оснащена флеш-памятью, двумя кнопками и шестью светодиодами, конфигурируемыми пользователем. Сама ПЛИС содержит 8640 логических блоков LUT4, что вполне отвечает потребностям начинающих пользователей.
Что касается набора инструментов с открытым исходным кодом, то на сегодняшний день он включает в себя все компоненты, необходимые для каждого из этапов разработки: ПО Yosys для синтеза, NextPnR для размещения и маршрутизации цепей и Apicula, реконструирующее архитектуру и предоставляющее инструменты генерации битового потока (битстрима) для программирования ПЛИС компании Gowin, которое в свою очередь осуществляется посредством ПО openFPGALoader.
Данный набор инструментов легко конфигурируется, для чего по адресу https://github.com/YosysHQ/oss-cad-suite-build/releases?ref=learn.lushaylabs.com достаточно выбрать и скачать архив OSS-CAD-Suite для соответствующей платформы (MacOSX, Linux, Windows), распаковать его и добавить к списку значений переменной окружения PATH абсолютный путь к находящейся в корне распакованного архива папке bin, например, так:
export PATH="/home/имя_пользователя/oss-cad-suite/bin:$PATH"
Удобнее это можно сделать, добавив к редактору Visual Studio Code расширение Lushay Code, которое позволяет задать вышеуказанный путь и запускать любой из инструментов OSS-CAD-Suite. Чтобы начать настройку данного плагина кликните на значок FPGA Toolchain в правом нижнем углу окна VS Code.
Ниже показано, как с их помощью можно быстро научиться создавать проекты на языке Verilog.
Считаем до… 63
Начнем с простого примера двоичного счетчика, разряды которого отображаются с помощью светодиодов.
1. На плате Tang Nano 9K имеется 6 светодиодов, и если каждый будет представлять в нашем счетчике один двоичный разряд, то мы сможем отображать числа от 0 до 111111 (63 в десятичном формате).
2. В качестве входных сигналов используем тактовые импульсы, получаемые от встроенного в плату генератора.
3. Частота последнего составляет 27 МГц, поэтому если фиксировать каждый такт, то счетчик будет заполняться примерно 421 тыс. раз каждую секунду, --слишком быстро для визуализации процесса. Чтобы заставить его считать не так быстро, скажем, раз в полсекунды, нужно пропустить 13 500 тыс. тактов прежде чем увеличить значение счетчика на единицу.
Теперь, когда у нас есть общий план, давайте создадим файл под названием counter.v и запишем в него то, что нам известно.
module top //объявление модуля
(
input clk, //вход - тактовый сигнал
output [5:0] led //выход - 6 светодиодов
);
localparam WAIT_TIME = 13500000; //"замедляющий" параметр WAIT_TIME
reg [5:0] ledCounter = 0; //создаем и обнуляем шестиразрядный счетчик
endmodule //конец модуля
Приведенный выше код, написанный на языке Verilog, определяет модуль под названием top с одним входом clk для тактового сигнала и шестью выходами на светодиоды led. Эти обозначения будут позднее сопоставлены с физическими выводами ПЛИС в другом файле -- файле ограничений (привязок).
Формат определения выхода led имеет вид [MSB:LSB], благодаря чему группа из шести светодиодов может быть представлена не как led5, led4, ...led0, а как битовый массив размером [5:0]. Здесь крайний левый бит (с индексом 5) называется старшим (most significant bit, MSB) , а крайний правый бит (с индексом 0) -- младшим значащим битом (least significant bit, LSB).
Следующая строка определяет локальную константу WAIT_TIME, выше мы уже подсчитали, что нам потребуется 13,5 млн тактов на каждый шаг счетчика, чтобы достичь желаемой задержки в полсекунды.
Последняя строка -- это и есть наш фактический счетчик, и мы снова используем тот же формат [5:0], чтобы задать переменную (регистр) шириной в 6 бит.
Но это еще не все. На самом деле нам понадобится еще один счетчик тактов. Двоичное представление числа 13 500 000 содержит 24 разряда, поэтому нам нужно создать счетчик размером [23:0].
reg [23:0] clockCounter = 0; //создаем и обнуляем 24-разрядный счетчик
Итак, мы описали все компоненты проекта. Теперь следует задать правила увеличения значений наших счетчиков. Это можно сделать с помощью блока always:
always @(posedge clk) begin
clockCounter <= clockCounter + 1;
if (clockCounter == WAIT_TIME) begin
clockCounter <= 0;
ledCounter <= ledCounter + 1;
end
end
Блок always начинается со списка чувствительности. По сути, это условия для того, чтобы сработала описываемая им схема. В нашем случае всякий раз, когда на линии тактовой частоты появляется положительный импульс, должно произойти следующее.
Прежде всего значение счетчика тактов увеличим на единицу, а затем проверим, достигло ли оно заданного выше значения WAIT_TIME. Если так, тогда сбрасываем счетчик тактов на ноль, а к значению шестиразрядного счетчика прибавляем единицу. При этом нет необходимости перезагружать шестиразрядный счетчик, поскольку он автоматически вернется к 0 сразу, как только увеличится до 63.
Отметим, что оператор "<=" не похож на стандартный оператор присваивания в большинстве языков программирования. Данный оператор устанавливает значение для регистра clockCounter, которое будет передаваться ему только при следующем тактовом сигнале. Это означает, что если мы увеличим его от 0 до 1 в первой строке блока, значение clockCounter для оставшейся части текущего блока по-прежнему будет равно 0 и увеличится на единицу только на следующем такте. То же самое происходит, когда мы увеличиваем значение шестиразрядного счетчика, изменение которого будет замечено только при следующем тактовом сигнале.
Есть способ немедленно присвоить значение, используя вместо этого блокирующий оператор "=", но при работе с регистрами принято использовать неблокирующие операторы присваивания.
Последнее, что нужно сделать для завершения нашего модуля, -- это подключить регистр к светодиодам.
assign led = ledCounter;
За пределами блока always мы используем assign и = для определения значения wires. Провода в отличие от регистров не могут хранить значения, поэтому нужно указать, к чему они подключены.
Окончательный код должен выглядеть следующим образом:
module top
(
input clk,
output [5:0] led
);
localparam WAIT_TIME = 13500000;
reg [5:0] ledCounter = 0;
reg [23:0] clockCounter = 0;
always @(posedge clk) begin
clockCounter <= clockCounter + 1;
if (clockCounter == WAIT_TIME) begin
clockCounter <= 0;
ledCounter <= ledCounter + 1;
end
end
assign led = ledCounter;
endmodule
Следующий файл, который нам нужно создать, -- это файл counter.cst, в котором нам нужно будет привязать физические контакты микросхемы к цепям clk и led. Чтобы сделать это, нам нужно иметь некоторую информацию о портах ввода/вывода (General Purpose Input/Output, GPIO), а именно их номера. Для платы это довольно просто, все номера GPIO перечислены на следующем рисунке:
Но в нашем примере мы не используем GPIO-порты платы, мы используем контакты самой ПЛИС, которые подключены к встроенным компонентам (тактовому генератору и светодиодам).
Как видим, встроенные светодиоды подключены к контактам микросхемы 10,11,13,14,15 и 16. Аналогично для привязки цепи clk мы находим контакт 52 в секции xtal (кварцевый генератор).
По завершении нашего мини-исследования мы можем создать cst-файл, в котором мы определим привязки следующим образом:
IO_LOC "clk" 52;
IO_PORT "clk" PULL_MODE=UP;
IO_LOC "led[0]" 10;
IO_LOC "led[1]" 11;
IO_LOC "led[2]" 13;
IO_LOC "led[3]" 14;
IO_LOC "led[4]" 15;
IO_LOC "led[5]" 16;
В этом файле мы назначаем проводу clk вывод 52 с подтягивающим резистором, а также определяем 6 разрядов нашего светодиодного выхода, используя найденные нами выше номера выводов ПЛИС. Заметим, что добавив к каждому разряду индекс бита в квадратных скобках, мы сократили наш код verilog, задав присваивание значений сигналов светодиодам одной строкой вместо шести.
Мастер Lushay Code позволяет легко создать файл ограничений (Constraints) из шаблона, для этого достаточно выбрать нужную плату из выпадающего списка, а затем выбрать нужные шаблоны и при необходимости отредактировать их.
Теперь все готово для запуска нашего проекта. Кликаем на уже знакомый нам значок FPGA Toolchain в правом нижнем углу и в меню команд VS Code выбираем Build Only.
Для загрузки битстрима в подключенную через USB-C плату с ПЛИС можно аналогичным образом выбрать команду Build and Program, либо Program Only если битстрим уже сгенерирован.
Если путь к папке oss-cad-suite/bin добавлен к переменной PATH, можно воспользоваться утилитой openFPGALoader, запустив ее в окне терминала, при необходимости предварив командой sudo.
Битстрим загружен и мы видим, что наш проект работает, правда, не совсем так как хотелось бы. Исправим логическую ошибку, заключающуюся в том, что мы не учли уровень активного сигнала для светодиодов -- по умолчанию он высокий! Поэтому добавим инверсию ~ (логическое НЕ) в предпоследнюю строку модуля top.
assign led = ~ledCounter;
Источник: