Для программирования 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 может быть адаптирован под любую ПЛИС, если у вас есть тех документация, описывающая внутреннюю структуру микросхемы.

Возможно, российским компаниям, которые разрабатывают ПЛИС этот проект может дать быстрый старт в разработке своих микросхем.

Комментарии (3)


  1. nerudo
    17.04.2024 07:36

    Они оттрасированный дизайн в json хранят? Интересно посмотреть на внутренности, можете выложить (output/out-pnr.json )?


    1. nckma Автор
      17.04.2024 07:36
      +3

      Вот тут на диске положил файлы

      https://disk.yandex.ru/d/7hp8c2sLz34X5Q


    1. nckma Автор
      17.04.2024 07:36
      +3

      Там интересно то, что можно свою произвольную архитектуру FPGA описать и использовать netxpnr-generic для трассировки.