![](https://habrastorage.org/getpro/habr/upload_files/11f/ac4/34d/11fac434d5bc659b9d85455ac652b311.jpg)
Для программирования FPGA нужна среда проектирования. Например, при использовании микросхем ПЛИС Altera/Intel мы используем САПР Intel Quartus Prime Software. Возможно, основная ценность технологии ПЛИС даже не сами микросхемы, а программное обеспечение, которое позволяет разместить Verilog HDL / VHDL проект по логическим блокам и развести связи между ними используя заданные временные ограничения.
Возможно ли использование open source инструментов для FPGA проектирования? Это было бы очень интересно посмотреть.
Я расскажу о своем опыте использования Yosys oss-cad-suite для платы Марсоход3GW-2 на базе ПЛИС китайской компании Gowin GW1NR-LV9QN88PC6/I5. Фото платы показано выше в начале статьи.
Сама микросхема довольно интересная и содержит множество встроенных блоков:
до 71 User IO pin
8640 LUT
6480 FF
17280 bit SSRAM
468kb BSRAM
608Kb User Flash
5 DSP Blocks
2 PLL
встроенный генератор OSC
встроенные блоки сериализаторов OSER/ISER, ELVDS/TLVDS, ODDR/IDDR
встроенная PSRAM 8Mbytes
На плате Марсоход3GW-2 имеются:
двухканальный программатор MBFTDI построенный на чипе FT2232H
8ми битная АЦП ADC1175 20MHz
кварцевый генератор 100МГц
2 пользовательские кнопки
8 светодиодов
выход HDMI
разъем для плат расширения, например, для подключения шилда Ethernet или 7ми сегментного индикатор
Попробую использовать вчерашний релиз open source oss-cad-suite-linux-x64-20240416.tgz в Ubuntu 22.04. Скачиваю с https://github.com/YosysHQ/oss-cad-suite-build/releases/tag/2024-04-16
Распаковываю. Перехожу в папку oss-cad-suite и делаю:
> source environment
По идее окружение для работы с FPGA готово к работе!
Откомпилирую некоторые демо проекты для платы Марсоход3GW-2 этим пакетом.
Демонстрационные проекты для платы можно взять с гитхаба: https://github.com/marsohod4you/Marsohod3GW/tree/Marsohod3GW2_GW1NR-LV9QN88PC6I5
Используем бранч Marsohod3GW2_GW1NR-LV9QN88PC6I5
Самый простой проект здесь _clk_counter. Это просто двоичный счетчик, выход которого идет на 8 светодиодов.
Последовательность команд будет такая:
> cd _clk_counter/src
> mkdir output
> yosys -p "read_verilog top.v; synth_gowin -json output/out-synth.json"
> nextpnr-gowin --json output/out-synth.json --write output/out-pnr.json --device GW1NR-LV9QN88PC6/I5 --family GW1N-9C --cst board.cst
> gowin_pack -d GW1N-9C -o output/pack.fs output/out-pnr.json
Это команды, которые синтезируют (yosys) из Verilog исходника, делают размещение и трассировку (place & route) соединений в ПЛИС (nextpnr) и затем упаковывают результат в битстрим используемый для загрузки в ПЛИС (gowin_pack).
Загрузить в ПЛИС можно другой командой:
> openFPGALoader output/pack.fs
Это всё в теории. На самом деле это всё у меня не сработало!
Было несколько проблем.
Первая проблема связаны с тем, что оригинальный проект был сделан в САПР GowinEDA и видимо синтаксис файла назначений board.cst не понравился трассировщику nextpnr-gowin. Чтобы nextpnr-gowin скушал файл назначений мне пришлось его слегка исправить. Например, убрать назначения типов IO и назначить каждый из сигналов TMDS отдельно, а не парами. В результате получился вот такой немного модифицированный файл board_.cst :
//Copyright (C)2014-2022 Gowin Semiconductor Corporation.
//All rights reserved.
//File Title: Physical Constraints file
//GOWIN Version: 1.9.8.05
//Part Number: GW1NR-LV9QN88PC6/I5
//Device: GW1NR-9C
//Created Time: Wed 03 15 13:45:13 2023
//IO_LOC "TMDS_D_P[0]" 49,48;
//IO_PORT "TMDS_D_P[0]" IO_TYPE=LVCMOS18D PULL_MODE=NONE DRIVE=8;
//IO_LOC "TMDS_D_P[1]" 52,51;
//IO_PORT "TMDS_D_P[1]" IO_TYPE=LVCMOS18D PULL_MODE=NONE DRIVE=8;
//IO_LOC "TMDS_D_P[2]" 54,53;
//IO_PORT "TMDS_D_P[2]" IO_TYPE=LVCMOS18D PULL_MODE=NONE DRIVE=8;
//IO_LOC "TMDS_CLK_P" 41,42;
//IO_PORT "TMDS_CLK_P" IO_TYPE=LVCMOS18D PULL_MODE=NONE DRIVE=8;
IO_LOC "TMDS_D_P[0]" 49;
IO_LOC "TMDS_D_N[0]" 48;
IO_LOC "TMDS_D_P[1]" 52;
IO_LOC "TMDS_D_N[1]" 51;
IO_LOC "TMDS_D_P[2]" 54;
IO_LOC "TMDS_D_N[2]" 53;
IO_LOC "TMDS_CLK_P" 41;
IO_LOC "TMDS_CLK_N" 42;
//IO_PORT "TMDS_D_N[0]" PULL_MODE=NONE DRIVE=8;
//IO_PORT "TMDS_D_P[0]" PULL_MODE=NONE DRIVE=8;
//IO_PORT "TMDS_D_N[1]" PULL_MODE=NONE DRIVE=8;
//IO_PORT "TMDS_D_P[1]" PULL_MODE=NONE DRIVE=8;
//IO_PORT "TMDS_D_N[2]" PULL_MODE=NONE DRIVE=8;
//IO_PORT "TMDS_D_P[2]" PULL_MODE=NONE DRIVE=8;
//IO_PORT "TMDS_CLK_P" PULL_MODE=NONE DRIVE=8;
//IO_PORT "TMDS_CLK_N" PULL_MODE=NONE DRIVE=8;
IO_LOC "IO[19]" 40;
IO_LOC "IO[18]" 39;
IO_LOC "IO[17]" 38;
IO_LOC "IO[16]" 37;
IO_LOC "IO[15]" 36;
IO_LOC "IO[14]" 35;
IO_LOC "IO[13]" 34;
IO_LOC "IO[12]" 33;
IO_LOC "IO[11]" 32;
IO_LOC "IO[10]" 31;
IO_LOC "IO[9]" 30;
IO_LOC "IO[8]" 29;
IO_LOC "IO[7]" 28;
IO_LOC "IO[6]" 27;
IO_LOC "IO[5]" 26;
IO_LOC "IO[4]" 25;
IO_LOC "IO[3]" 20;
IO_LOC "IO[2]" 19;
IO_LOC "IO[1]" 18;
IO_LOC "IO[0]" 17;
//IO_PORT "IO[18]" IO_TYPE=LVCMOS18 PULL_MODE=NONE;
//IO_PORT "IO[17]" IO_TYPE=LVCMOS18 PULL_MODE=NONE;
//IO_PORT "IO[16]" IO_TYPE=LVCMOS18 PULL_MODE=NONE;
//IO_PORT "IO[15]" IO_TYPE=LVCMOS18 PULL_MODE=NONE;
//IO_PORT "IO[14]" IO_TYPE=LVCMOS18 PULL_MODE=NONE;
//IO_PORT "IO[13]" IO_TYPE=LVCMOS18 PULL_MODE=NONE;
//IO_PORT "IO[12]" IO_TYPE=LVCMOS18 PULL_MODE=NONE;
//IO_PORT "IO[11]" IO_TYPE=LVCMOS18 PULL_MODE=NONE;
//IO_PORT "IO[10]" IO_TYPE=LVCMOS18 PULL_MODE=NONE;
//IO_PORT "IO[9]" IO_TYPE=LVCMOS18 PULL_MODE=NONE;
//IO_PORT "IO[8]" IO_TYPE=LVCMOS18 PULL_MODE=NONE;
//IO_PORT "IO[7]" IO_TYPE=LVCMOS18 PULL_MODE=NONE;
//IO_PORT "IO[6]" IO_TYPE=LVCMOS18 PULL_MODE=NONE;
//IO_PORT "IO[5]" IO_TYPE=LVCMOS18 PULL_MODE=NONE;
//IO_PORT "IO[4]" IO_TYPE=LVCMOS18 PULL_MODE=NONE;
//IO_PORT "IO[3]" IO_TYPE=LVCMOS18 PULL_MODE=NONE;
//IO_PORT "IO[2]" IO_TYPE=LVCMOS18 PULL_MODE=NONE;
//IO_PORT "IO[1]" IO_TYPE=LVCMOS18 PULL_MODE=NONE;
//IO_PORT "IO[0]" IO_TYPE=LVCMOS18 PULL_MODE=NONE;
IO_LOC "LED[7]" 87;
IO_LOC "LED[6]" 88;
IO_LOC "LED[5]" 10;
IO_LOC "LED[4]" 11;
IO_LOC "LED[3]" 13;
IO_LOC "LED[2]" 14;
IO_LOC "LED[1]" 15;
IO_LOC "LED[0]" 16;
IO_LOC "FTB0" 60;
IO_LOC "FTB1" 59;
//IO_LOC "FTB2" 10;
//IO_LOC "FTB3" 9;
//IO_PORT "FTB0" PULL_MODE=NONE;
//IO_PORT "FTB1" PULL_MODE=NONE;
//IO_PORT "FTB2" IO_TYPE=LVCMOS33 PULL_MODE=NONE;
//IO_PORT "FTB3" IO_TYPE=LVCMOS33 PULL_MODE=NONE;
IO_LOC "FTC[7]" 61;
IO_LOC "FTC[6]" 62;
IO_LOC "FTC[5]" 63;
IO_LOC "FTC[4]" 68;
IO_LOC "FTC[3]" 69;
IO_LOC "FTC[2]" 70;
IO_LOC "FTC[1]" 71;
IO_LOC "FTC[0]" 72;
//IO_PORT "FTC[7]" PULL_MODE=NONE;
//IO_PORT "FTC[6]" PULL_MODE=NONE;
//IO_PORT "FTC[5]" PULL_MODE=NONE;
//IO_PORT "FTC[4]" PULL_MODE=NONE;
//IO_PORT "FTC[3]" PULL_MODE=NONE;
//IO_PORT "FTC[2]" PULL_MODE=NONE;
//IO_PORT "FTC[1]" PULL_MODE=NONE;
//IO_PORT "FTC[0]" PULL_MODE=NONE;
IO_LOC "FTD[7]" 73;
IO_LOC "FTD[6]" 74;
IO_LOC "FTD[5]" 75;
IO_LOC "FTD[4]" 76;
IO_LOC "FTD[3]" 56;
IO_LOC "FTD[2]" 55;
IO_LOC "FTD[1]" 57;
IO_LOC "FTD[0]" 50;
//IO_PORT "FTD[7]" PULL_MODE=NONE;
//IO_PORT "FTD[6]" PULL_MODE=NONE;
//IO_PORT "FTD[5]" PULL_MODE=NONE;
//IO_PORT "FTD[4]" PULL_MODE=NONE;
//IO_PORT "FTD[3]" PULL_MODE=NONE;
//IO_PORT "FTD[2]" PULL_MODE=NONE;
//IO_PORT "FTD[1]" PULL_MODE=NONE;
//IO_PORT "FTD[0]" PULL_MODE=NONE;
IO_LOC "ADC_D[7]" 81;
IO_LOC "ADC_D[6]" 82;
IO_LOC "ADC_D[5]" 83;
IO_LOC "ADC_D[4]" 84;
IO_LOC "ADC_D[3]" 85;
IO_LOC "ADC_D[2]" 86;
IO_LOC "ADC_D[1]" 3;
IO_LOC "ADC_D[0]" 4;
//IO_PORT "ADC_D[7]" PULL_MODE=NONE;
//IO_PORT "ADC_D[6]" PULL_MODE=NONE;
//IO_PORT "ADC_D[5]" PULL_MODE=NONE;
//IO_PORT "ADC_D[4]" PULL_MODE=NONE;
//IO_PORT "ADC_D[3]" PULL_MODE=NONE;
//IO_PORT "ADC_D[2]" PULL_MODE=NONE;
//IO_PORT "ADC_D[1]" PULL_MODE=NONE;
//IO_PORT "ADC_D[0]" PULL_MODE=NONE;
IO_LOC "ADC_CLK" 77;
//IO_PORT "ADC_CLK" PULL_MODE=NONE;
IO_LOC "KEY1" 79;
IO_LOC "KEY0" 80;
IO_PORT "KEY1" PULL_MODE=UP;
IO_PORT "KEY0" PULL_MODE=UP;
Вторая серьёзная проблема с которой я встретился - не работает openFPGAloader
При попытке загрузить битстрим в FPGA получаю ошибку unable to open ftdi device: -4 (usb_open() failed)
Оказывается, что пользователи по умолчанию не имеют доступа к USB FTDI программатору. Разрешения можно задать в udev rules. Для этого нужно создать файл 99-openfpgaloader.rules следующего содержания:
Затем положить этот файл в /etc/udev/rules.d/, содержимое файла вот:
# Copy this file to /etc/udev/rules.d/
ACTION!="add|change", GOTO="openfpgaloader_rules_end"
# gpiochip subsystem
SUBSYSTEM=="gpio", MODE="0664", GROUP="plugdev", TAG+="uaccess"
SUBSYSTEM!="usb|tty|hidraw", GOTO="openfpgaloader_rules_end"
# Original FT232/FT245 VID:PID
ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", MODE="664", GROUP="plugdev", TAG+="uaccess"
# Original FT2232 VID:PID
ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6010", MODE="664", GROUP="plugdev", TAG+="uaccess"
# Original FT4232 VID:PID
ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6011", MODE="664", GROUP="plugdev", TAG+="uaccess"
# Original FT232H VID:PID
ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6014", MODE="664", GROUP="plugdev", TAG+="uaccess"
# Original FT231X VID:PID
ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6015", MODE="664", GROUP="plugdev", TAG+="uaccess"
# anlogic cable
ATTRS{idVendor}=="0547", ATTRS{idProduct}=="1002", MODE="664", GROUP="plugdev", TAG+="uaccess"
# altera usb-blaster
ATTRS{idVendor}=="09fb", ATTRS{idProduct}=="6001", MODE="664", GROUP="plugdev", TAG+="uaccess"
ATTRS{idVendor}=="09fb", ATTRS{idProduct}=="6002", MODE="664", GROUP="plugdev", TAG+="uaccess"
ATTRS{idVendor}=="09fb", ATTRS{idProduct}=="6003", MODE="664", GROUP="plugdev", TAG+="uaccess"
# altera usb-blasterII - uninitialized
ATTRS{idVendor}=="09fb", ATTRS{idProduct}=="6810", MODE="664", GROUP="plugdev", TAG+="uaccess"
# altera usb-blasterII - initialized
ATTRS{idVendor}=="09fb", ATTRS{idProduct}=="6010", MODE="664", GROUP="plugdev", TAG+="uaccess"
# dirtyJTAG
ATTRS{idVendor}=="1209", ATTRS{idProduct}=="c0ca", MODE="664", GROUP="plugdev", TAG+="uaccess"
# Jlink
ATTRS{idVendor}=="1366", ATTRS{idProduct}=="0105", MODE="664", GROUP="plugdev", TAG+="uaccess"
# NXP LPC-Link2
ATTRS{idVendor}=="1fc9", ATTRS{idProduct}=="0090", MODE="664", GROUP="plugdev", TAG+="uaccess"
# NXP ARM mbed
ATTRS{idVendor}=="0d28", ATTRS{idProduct}=="0204", MODE="664", GROUP="plugdev", TAG+="uaccess"
# icebreaker bitsy
ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="6146", MODE="664", GROUP="plugdev", TAG+="uaccess"
# orbtrace-mini dfu
ATTRS{idVendor}=="1209", ATTRS{idProduct}=="3442", MODE="664", GROUP="plugdev", TAG+="uaccess"
# QinHeng Electronics USB To UART+JTAG (ch347)
ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="55dd", MODE="664", GROUP="plugdev", TAG+="uaccess"
LABEL="openfpgaloader_rules_end"
Так же нужно добавить себя как пользователя в группу plugdev.
Выполняем такие команды:
>sudo cp 99-openfpgaloader.rules /etc/udev/rules.d/
> sudo udevadm control --reload-rules && sudo udevadm trigger # force udev to take new rule
> sudo usermod -a $USER -G plugdev # add user to plugdev group
Теперь отключить и подключить заново плату Марсоход3GW-2 к USB порту компьютера и вот openFPGAloader успешно загружает битстрим в плату.
Загружает, но проект не работает!!!
Большое разочарование, но что делать? Надежда всё еще есть...
Проект nextpnr довольно активно развивается, и есть в нем как бы новая ветвь разработки под кодовым названием himbaechel. Не знаю, что это значит, но можно попробовать её. Для этого соберем из исходников этот вариант nextpnr.
Я ставил всё на рабочий компьютер c убунтой, поэтому cmake, gcc, python3 у меня уже стоят по умолчанию. Что потребовалось дополнительно поставить это
> sudo apt install python3-pip
> pip install apycula
> sudo apt install libboost-dev libboost-filesystem-dev libboost-thread-dev libboost-program-options-dev libboost-iostreams-dev libboost-dev
> sudo apt install libeigen3-dev
После этого можно взять исходники nextpnr с гитхаба и скомпилировать их:
> git clone https://github.com/YosysHQ/nextpnr.git
> cd nextpnr
> mkdir build
> cd build
> cmake .. -DARCH="himbaechel" -DHIMBAECHEL_GOWIN_DEVICES="all"
> make -j20
При успешной компиляции у меня получился файл nextpnr-himbaechel. Попробую использовать для place&route его.
Заранее скажу - всё получилось!
Теперь я успешно скомпилировал и загрузил в плату Марсоход3GW-2 три разных простых демо проекта: _clk_counter, _clk_pll_counter, _adc_led.
Проект _clk_counter это просто двоичный счетчик с отображением на светодиоды.
Проект _clk_pll_counter это почти такой же счетчик, но еще используется встроенная в FPGA PLL. Кроме того, по нажатию кнопок платы режим отображения на светодиоды меняется.
Проект _adc_led получает с АЦП платы 8-ми битные данные и отображает их на светодиодах.
Приведу подробнее команды используемые для сборки проекта и вывод:
Синтез:
>yosys -p "read_verilog top.v gowin_rpll/gowin_rpll.v; synth_gowin -json output/out-synth.json"
Хотел приложить к статье вывод этой команды, но, к сожалению, с современеным хабровским редактором мне оказалось это не под силу. Какая-то невыполнимая задача. Текст сам абы как маркируется, меняет шрифт и всё такое. Посмотреть на вывод команды весьма любопытно.
Размещение (Place&Route):
> ~/fpga/nextpnr/build/nextpnr-himbaechel --json output/out-synth.json --write output/out-pnr.json --device GW1NR-LV9QN88PC6/I5 --vopt family=GW1N-9C --vopt cst=board_.cst
Упаковка:
> gowin_pack -d GW1N-9C -o output/pack.fs output/out-pnr.json
Загрузка в ПЛИС платы:
> openFPGALoader output/pack.fs
Работает! Ниже показана демонстрация работы проекта _adc_led при подключенном к АЦП переменном резисторе:
Выводы..
Я считаю, что появление open source компиляторов для FPGA это огромный шаг вперед. Это полезно и технологическим компаниям и студентам, изучающим технологию FPGA. Проект nextpnr может быть адаптирован под любую ПЛИС, если у вас есть тех документация, описывающая внутреннюю структуру микросхемы.
Возможно, российским компаниям, которые разрабатывают ПЛИС этот проект может дать быстрый старт в разработке своих микросхем.