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

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

В качестве примера я выбрал два простых “Hello World!” проекта.

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

Во втором примере реализован генератор периодического сигнала на комбинаторной логике и имитации линии задержки.

Для экспериментов была выбрана ПЛИС MachXO3LF фирмы Lattice Semiconductor, установленная на отладочной плате MachXO3LStarterKit. Это недорогая микросхема с 6900 логическими ячейками и FLASH-памятью для хранения бит-файла конфигурации. В качестве среды разработки используется IDE Lattice Diamond, состоящей из отдельных приложений для синтеза, трансляции, трассировки топологии, прошивки и отладки. Приложения могут работать как в интерактивном, так и в консольном режимах. 

В данном эксперименте простой дизайн создан исключительно с помощью программы для редактирования/просмотра конфигурации ПЛИС. Фирма Lattice Semiconductor поставляет для этих целей EPIC Device Editor (Editor for Programmable Integrated Circuits). С её помощью можно анализировать расположение логических блоков, вносить незначительные изменения в схему без дополнительных временных затрат на компиляцию проекта. А также вручную "рисовать" поведение схемы в ПЛИС.

Метод, описанный в этой статье, имеет исключительно экспериментально-обучающий характер, и может оказаться полезным для инженеров, начинающих погружаться мир HDL-разработки. Возможно некоторые аспекты помогут лучше разобраться в особенностях временных ограничений и оптимизации проектов.

Итак, запустив приложение EPIC, создав новый дизайн (File -> New -> New Design, или Ctrl+N) и выбрав нужную микросхему (в нашем случае LCMXO3LF-6900C), в основном окне программы отобразится схема устройства, представленная на рисунке ниже.

Схема ПЛИС LCMXO3LF-6900C в окне программы EPIC
Схема ПЛИС LCMXO3LF-6900C в окне программы EPIC

В окне показаны имеющиеся в чипе компоненты и соединения. В мелком масштабе узнаётся схема устройства, представленная в документации к этой ПЛИС.

Схема ПЛИС в документации
Схема ПЛИС в документации

На схеме можно видеть набор логических элементов PFU (Programmable Function Units). Они расположены почти по всей площади микросхемы и сгруппированы в столбцы и строки.

Блок PFU состоит из четырёх программируемых ячеек (Slices). Каждая ячейка может быть сконфигурирована в одном из четырёх режимов -  Logic, Ripple, RAM и ROM. В Logic-режиме ячейку можно использовать  для создания комбинаторной логики, настроив таблицу истинности. Ripple-режим позволяет реализовывать элементарные арифметические функции - двуxбитное сложение, вычитание и прочее. С помощью нескольких ячеек в RAM-режиме можно реализовывать блок перезаписываемой памяти, а в режиме ROM - программируемой памяти.

Схема блока PFU (Programmable Function Units)
Схема блока PFU (Programmable Function Units)

В каждой ячейке Slice имеется две таблицы истинности с четырьмя входами, одним выходом (LUT look-up table) и битами переноса, два регистра и мультиплексоры. Ниже на рисунке показаны элементы ячеек из документации (слева) и в редакторе EPIC (справа).

Схема Slice в документации и в окне программы EPIC
Схема Slice в документации и в окне программы EPIC

Каждый из блоков PFU имеет буквенно-цифровую адресацию, соответствующую строке и столбцу, в котором он расположен (к примеру, R9C18), а каждая ячейка в блоке индексируется с помощью букв A, B, C и D. 

Адресация блоков в микросхеме
Адресация блоков в микросхеме

Порты вводов-выводов расположены по периметру устройства и сгруппированы в банки. Каждый отдельный банк может быть сконфигурирован на работу вывода микросхемы с определённым стандартным типом сигналов - LVCMOS, LVTTL, LVDS, …

Окно конфигурации портов
Окно конфигурации портов

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

Общий вид блока коммутации в окне программы EPIC
Общий вид блока коммутации в окне программы EPIC

Эти буферы позволяют соединять выводы ячеек друг с другом. Такую операцию можно делать вручную в графическом редакторе, выбирая соответствующие входы-выходы компонент и создавая соединения. Но это слишком трудоёмкий процесс, поэтому значительно удобнее пользоваться tcl-командами или скриптом. Tcl-функции приложения EPIC немногочисленны, но позволяют автоматизировать процесс и выполнять все операции, доступные в графической оболочке - создавать, размещать и параметризировать компоненты, создавать сети и соединения. С помощью tcl-скрипта выполнены описываемые в статье примеры.

Пример 1

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

Создадим файл make.tcl и в него будем писать скрипт с командами для EPIC. Для начала выберем используемую на отладочной плате ПЛИС:

# Create new design
eco_design new -arch {MachXO3LF} -device {LCMXO3LF-6900C} -package {CABGA256} -speed {5} -ncd {hello_world.ncd}

В нашем проекте используется ПЛИС семейства MachXO3 в корпусе с 256 выводами и "быстродействием" №5.

Далее опишем физические выводы. Они проименованы в соответствии со схемой устройства:

# add components to input pins
eco_add comp -name {CLK_12MHZ} -site {C8}
eco_add comp -name {DIP_SW[1]} -site {N2}
eco_add comp -name {DIP_SW[2]} -site {P1}
eco_add comp -name {DIP_SW[3]} -site {M3}
eco_add comp -name {DIP_SW[4]} -site {N1}

# add components to output pins
eco_add comp -name {LED[0]} -site {H11}
eco_add comp -name {LED[1]} -site {J13}
eco_add comp -name {LED[2]} -site {J11}
eco_add comp -name {LED[3]} -site {L12}
eco_add comp -name {LED[4]} -site {K11}
eco_add comp -name {LED[5]} -site {L13}
eco_add comp -name {LED[6]} -site {N15}
eco_add comp -name {LED[7]} -site {P16}
eco_add comp -name {CLK_OUT_DIV} -site {B5}
eco_add comp -name {CLK_OUT} -site {B4}
Вводы-выводы отладочной платы
Вводы-выводы отладочной платы

Два вывода микросхемы подключим к сигналам CLK_OUT (коммутация входной частоты с ножкой микросхемы) и CLK_OUT_DIV (коммутация делённой частоты с ножкой микросхемы) для отладки с помощью осциллографа.

Далее конфигурируем два вывода - указываем тип (In/Out), ток, напряжение и прочие параметры:

# assign parameters to output pins
eco_config comp -comp {LED[0]} {PADDI:#OFF TRIMUX:PADDT:::PADDT=#SIG IOBUF:::PULLMODE=DOWN,IO_TYPE=LVCMOS33,DRIVE=4,SLEWRATE=SLOW,CLAMP=OFF,OPENDRAIN=OFF,DIFFRESISTOR=OFF,DIFFDRIVE=NA,BANK_VCCIO=3.3,HYSTERESIS=NA,PERSISTENT=OFF DATAMUX:PADDO:::PADDO=#SIG VREF:OFF PGMUX:#OFF ODMUX:TRIMUX PGBUF:#OFF INRDBUF:#OFF LVDSBUF:#OFF INRDMUX:#OFF LVDSMUX:DATAMUX}
# assign parameters to input pins
eco_config comp -comp {DIP_SW[1]} {PADDI:PADDI TRIMUX:#OFF IOBUF:::PULLMODE=UP,IO_TYPE=LVCMOS33,DRIVE=NA,SLEWRATE=NA,CLAMP=OFF,OPENDRAIN=OFF,DIFFRESISTOR=OFF,DIFFDRIVE=NA,BANK_VCCIO=2.5,HYSTERESIS=SMALL,PERSISTENT=OFF DATAMUX:#OFF VREF:#OFF PGMUX:INBUF ODMUX:#OFF PGBUF:#OFF INRDBUF:#OFF LVDSBUF:#OFF INRDMUX:PGMUX LVDSMUX:#OFF }

Параметры проще всего скопировать из атрибутов вручную созданной компоненты в графическом редакторе:

Параметры порта в окне программы EPIC
Параметры порта в окне программы EPIC

В данном примере созданы два основных типа: вход и выход ПЛИС. Далее их параметры копируем на остальные порты:

# copy parameters to output pins
eco_clone comp {LED[0]} -comp {LED[1]}
eco_clone comp {LED[0]} -comp {LED[2]}
eco_clone comp {LED[0]} -comp {LED[3]}
eco_clone comp {LED[0]} -comp {LED[4]}
eco_clone comp {LED[0]} -comp {LED[5]}
eco_clone comp {LED[0]} -comp {LED[6]}
eco_clone comp {LED[0]} -comp {LED[7]}
eco_clone comp {LED[0]} -comp {CLK_OUT}
eco_clone comp {LED[0]} -comp {CLK_OUT_DIV}

# copy parameters to input pins
eco_clone comp {DIP_SW[1]} -comp {CLK_12MHZ}
eco_clone comp {DIP_SW[1]} -comp {DIP_SW[2]}
eco_clone comp {DIP_SW[1]} -comp {DIP_SW[3]}
eco_clone comp {DIP_SW[1]} -comp {DIP_SW[4]}

Создадим логическую ячейку с именем SLICE0 для демонстрации работы комбинаторной логики, и расположим её в шеснадцатом столбце, тридцать второй колонке в блоке D. Адрес расположения выбран случайно.

# LUT Demonstration
# add component to slice
eco_add comp -name {SLICE0} -site {R16C32D}

Сконфигурируем её в виде CCU2 - carry-chain unit. 

CCU имеет 9 входов (4х2 логических входов и один вход для бита переноса) и три выхода (2х1 логических выходов, один выход для бита переноса). В качестве инициализирующего параметра CCU-блока принимается шеснадцатибитное значение, которое формирует таблицу истинности для каждого отдельного выхода. К примеру, значение INIT=0xF444 формирует следующую таблицу истинности:

D C B A : Z
0 0 0 0 : 0
0 0 0 1 : 0
0 0 1 0 : 1
0 0 1 1 : 0
0 1 0 0 : 0
0 1 0 1 : 0
0 1 1 0 : 1
0 1 1 1 : 0
1 0 0 0 : 0
1 0 0 1 : 0
1 0 1 0 : 1
1 0 1 1 : 0
1 1 0 0 : 1
1 1 0 1 : 1
1 1 1 0 : 1
1 1 1 1 : 1

Параметры ячейки SLICE0:

# set parameters to LUT
eco_config comp -comp SLICE0 {MODE:CCU2 CCU2::S0=0xF444 CCU2:::INJECT1_0=YES F0:F}

Соединим четыре входа переключателей с четырьмя входами созданной логической ячейки. А выход SLICE0 подключим непосредственно к выводу ПЛИС со светодиодом.

# net from slice to led
eco_add net -name led0 -netpin H11.PADDO -netpin R16C32D.F0
# nets from pin to slice
eco_add net -name in1 -netpin N2.PADDI -netpin R16C32D.A0
eco_add net -name in2 -netpin P1.PADDI -netpin R16C32D.B0
eco_add net -name in3 -netpin M3.PADDI -netpin R16C32D.C0
eco_add net -name in4 -netpin N1.PADDI -netpin R16C32D.D0

Для экономии электричества отлючим шесть светодиодов, подав логическую единицу на выход ПЛИС, поскольку катод диода подключен к выводу микросхемы, а анод подтянут к питанию 3.3В. Логическую единицу в виде константы можно задать с помощью следующей ячейки:

# SLICE with High level output
eco_add comp -name {SLICE1} -site {R18C37D}
eco_config comp -comp {SLICE1} {MODE:LOGIC K1:#OFF K0::H0=1 REG1:#OFF REG0:#OFF Q1:#OFF Q0:#OFF F1:#OFF F0:F GSR:ENABLED }
Ячейка, сконфигурированная константой
Ячейка, сконфигурированная константой

В данном случае выход F0 будет подключен к выходу 4-input look up table K0, которая сконфигурирована константой “1”.

Светодиоды LED1-LED6  подключаем к выходу этой логической ячейки

# Connecting LEDs to HIGH signal
eco_add net -name vcc -netpin J13.PADDO -netpin R18C37D.F0
eco_add net -name vcc -netpin J11.PADDO -netpin R18C37D.F0
eco_add net -name vcc -netpin L12.PADDO -netpin R18C37D.F0
eco_add net -name vcc -netpin K11.PADDO -netpin R18C37D.F0
eco_add net -name vcc -netpin L13.PADDO -netpin R18C37D.F0
eco_add net -name vcc -netpin N15.PADDO -netpin R18C37D.F0

В итоге первый светодиод будет загораться только при определённом состоянии переключателей, следующие шесть будут выключены.

Поморгаем заключительным, восьмым светодиодом. Для этого поделим входную частоту 12 МГц с ввода ПЛИС, воспользовавшись элементарным делителем на триггерах, схема которого показана ниже.

Схема делителя частоты
Схема делителя частоты

Каждый следующий элемент такой схемы делит частоту на 2. Чтобы вручную не создавать и не описывать десяток компонент, напишем цикл:

# Behavioral of clock divider

# Counter width
set BITS 20
# Address
set ROW 10
set LINE 3
for {set i 0} {$i < $BITS} {incr i} {
	# Place components in one line
	eco_add comp -name CNT_TEST$i -site R${LINE}C[expr $i+$ROW]D
	# Config component with Register output
	eco_config comp -comp CNT_TEST$i {MODE:LOGIC K1:#OFF K0::H0=~A REG1:#OFF REG0:::REGSET=RESET,CLKDELAY=DEL0:SD=1 Q1:#OFF Q0:Q F1:#OFF F0:F GSR:ENABLED CLKMUX:CLK:::0=0,CLK=#SIG REGMODE:FF}
	if {$i == 0} {
		# First component with clock input
		eco_add net -name clk12mhz -netpin C8.PADDI -netpin R${LINE}C[expr $i+$ROW]D.CLK
	} else {
		# Connecting clk signal of component to register output of previous component
		eco_add net -name clkdiv[expr $i-1]_i -netpin R${LINE}C[expr {$i - 1 +$ROW}]D.Q0 -netpin R${LINE}C[expr $i+$ROW]D.CLK
	}
	eco_add net -name clkdiv${i}   -netpin R${LINE}C[expr $i+$ROW]D.Q0 -netpin R${LINE}C[expr $i+$ROW]D.A0
	eco_add net -name clkdiv${i}_i -netpin R${LINE}C[expr $i+$ROW]D.F0 -netpin R${LINE}C[expr $i+$ROW]D.DI0
	
	# connecting last component register output to pin
	if {$i == [expr $BITS - 1]} {
		eco_add net -name clkdiv${i}_i -netpin B5.PADDO -netpin R${LINE}C[expr $i+$ROW]D.Q0
		eco_add net -name clkdiv${i}_i -netpin P16.PADDO -netpin R${LINE}C[expr $i+$ROW]D.Q0
	}
}

С помощью констант ROW и LINE задаётся адрес первой ячейки делителя, остальные будут располагаться правее от неё. Константа BITS определяет количество ячеек и, соответственно, разрядность делителя. Вход первой ячейки подключен ко входу тактирующего сигнала, выход последней - к восьмому светодиоду и свободному выходу микросхемы для отображения на осциллографе. В каждой ячейке один из доступных LUT сконфигурирован на инвертирование входного сигнала (K0::H0=~A) а его выход F0 подключен ко входу D1 регистра. Регистр тактируется выходом Q0 предыдущей ячейки, а выход регистра подаётся на тактирующий сигнал регистра следующей ячейки. Так выглядит схема одного элемента в графическом редакторе:

Сконфигурированная ячейка делителя частоты
Сконфигурированная ячейка делителя частоты

В заключении проект сохраняется командой:

eco_design save

Итого готовый код выглядит так:

make.tcl
# bitgen -jedec -w "hello_world.ncd"

# Create new design
eco_design new -arch {MachXO3LF} -device {LCMXO3LF-6900C} -package {CABGA256} -speed {5} -ncd {hello_world.ncd}

# add components to input pins
eco_add comp -name {CLK_12MHZ} -site {C8}
eco_add comp -name {DIP_SW[1]} -site {N2}
eco_add comp -name {DIP_SW[2]} -site {P1}
eco_add comp -name {DIP_SW[3]} -site {M3}
eco_add comp -name {DIP_SW[4]} -site {N1}

# add components to output pins
eco_add comp -name {LED[0]} -site {H11}
eco_add comp -name {LED[1]} -site {J13}
eco_add comp -name {LED[2]} -site {J11}
eco_add comp -name {LED[3]} -site {L12}
eco_add comp -name {LED[4]} -site {K11}
eco_add comp -name {LED[5]} -site {L13}
eco_add comp -name {LED[6]} -site {N15}
eco_add comp -name {LED[7]} -site {P16}
eco_add comp -name {CLK_OUT_DIV} -site {B5}
eco_add comp -name {CLK_OUT} -site {B4}

# assign parameters to output pins
eco_config comp -comp {LED[0]} {PADDI:#OFF TRIMUX:PADDT:::PADDT=#SIG IOBUF:::PULLMODE=DOWN,IO_TYPE=LVCMOS33,DRIVE=4,SLEWRATE=SLOW,CLAMP=OFF,OPENDRAIN=OFF,DIFFRESISTOR=OFF,DIFFDRIVE=NA,BANK_VCCIO=3.3,HYSTERESIS=NA,PERSISTENT=OFF DATAMUX:PADDO:::PADDO=#SIG VREF:OFF PGMUX:#OFF ODMUX:TRIMUX PGBUF:#OFF INRDBUF:#OFF LVDSBUF:#OFF INRDMUX:#OFF LVDSMUX:DATAMUX}
# assign parameters to input pins
eco_config comp -comp {DIP_SW[1]} {PADDI:PADDI TRIMUX:#OFF IOBUF:::PULLMODE=UP,IO_TYPE=LVCMOS33,DRIVE=NA,SLEWRATE=NA,CLAMP=OFF,OPENDRAIN=OFF,DIFFRESISTOR=OFF,DIFFDRIVE=NA,BANK_VCCIO=2.5,HYSTERESIS=SMALL,PERSISTENT=OFF DATAMUX:#OFF VREF:#OFF PGMUX:INBUF ODMUX:#OFF PGBUF:#OFF INRDBUF:#OFF LVDSBUF:#OFF INRDMUX:PGMUX LVDSMUX:#OFF }

# copy parameters to output pins
eco_clone comp {LED[0]} -comp {LED[1]}
eco_clone comp {LED[0]} -comp {LED[2]}
eco_clone comp {LED[0]} -comp {LED[3]}
eco_clone comp {LED[0]} -comp {LED[4]}
eco_clone comp {LED[0]} -comp {LED[5]}
eco_clone comp {LED[0]} -comp {LED[6]}
eco_clone comp {LED[0]} -comp {LED[7]}
eco_clone comp {LED[0]} -comp {CLK_OUT}
eco_clone comp {LED[0]} -comp {CLK_OUT_DIV}

# copy parameters to input pins
eco_clone comp {DIP_SW[1]} -comp {CLK_12MHZ}
eco_clone comp {DIP_SW[1]} -comp {DIP_SW[2]}
eco_clone comp {DIP_SW[1]} -comp {DIP_SW[3]}
eco_clone comp {DIP_SW[1]} -comp {DIP_SW[4]}

# LUT Demonstration
# add component to slice
eco_add comp -name {SLICE0} -site {R16C32D}
# set parameters to LUT
eco_config comp -comp SLICE0 {MODE:CCU2 CCU2::S0=0xF444 CCU2:::INJECT1_0=YES F0:F}

# nets from pin to slice
eco_add net -name in1 -netpin N2.PADDI -netpin R16C32D.A0
eco_add net -name in2 -netpin P1.PADDI -netpin R16C32D.B0
eco_add net -name in3 -netpin M3.PADDI -netpin R16C32D.C0
eco_add net -name in4 -netpin N1.PADDI -netpin R16C32D.D0

# net from slice to led
eco_add net -name led0 -netpin H11.PADDO -netpin R16C32D.F0

# SLICE with High level output
eco_add comp -name {SLICE1} -site {R18C37D}
eco_config comp -comp {SLICE1} {MODE:LOGIC K1:#OFF K0::H0=1 REG1:#OFF REG0:#OFF Q1:#OFF Q0:#OFF F1:#OFF F0:F GSR:ENABLED }
# Connecting LEDs to HIGH signal
eco_add net -name vcc -netpin J13.PADDO -netpin R18C37D.F0
eco_add net -name vcc -netpin J11.PADDO -netpin R18C37D.F0
eco_add net -name vcc -netpin L12.PADDO -netpin R18C37D.F0
eco_add net -name vcc -netpin K11.PADDO -netpin R18C37D.F0
eco_add net -name vcc -netpin L13.PADDO -netpin R18C37D.F0
eco_add net -name vcc -netpin N15.PADDO -netpin R18C37D.F0
#eco_add net -name vcc -netpin P16.PADDO -netpin R18C37D.F0

# Net for clock input
eco_add net -name clk12mhz -netpin B4.PADDO -netpin C8.PADDI

# Generating of Clock divider

# Counter width
set BITS 20
# 
set ROW 10
set LINE 3
for {set i 0} {$i < $BITS} {incr i} {
	# Place components in one line
	eco_add comp -name CNT_TEST$i -site R${LINE}C[expr $i+$ROW]D
	# Config component with Register output
	eco_config comp -comp CNT_TEST$i {MODE:LOGIC K1:#OFF K0::H0=~A REG1:#OFF REG0:::REGSET=RESET,CLKDELAY=DEL0:SD=1 Q1:#OFF Q0:Q F1:#OFF F0:F GSR:ENABLED CLKMUX:CLK:::0=0,CLK=#SIG REGMODE:FF}
	if {$i == 0} {
		# First component with clock input
		eco_add net -name clk12mhz -netpin C8.PADDI -netpin R${LINE}C[expr $i+$ROW]D.CLK
	} else {
		# Connecting clk signal of component to register output of previous component
		eco_add net -name clkdiv[expr $i-1]_i -netpin R${LINE}C[expr {$i - 1 +$ROW}]D.Q0 -netpin R${LINE}C[expr $i+$ROW]D.CLK
	}
	eco_add net -name clkdiv${i}   -netpin R${LINE}C[expr $i+$ROW]D.Q0 -netpin R${LINE}C[expr $i+$ROW]D.A0
	eco_add net -name clkdiv${i}_i -netpin R${LINE}C[expr $i+$ROW]D.F0 -netpin R${LINE}C[expr $i+$ROW]D.DI0
	
	# connecting last component register output to pin
	if {$i == [expr $BITS - 1]} {
		eco_add net -name clkdiv${i}_i -netpin B5.PADDO -netpin R${LINE}C[expr $i+$ROW]D.Q0
		eco_add net -name clkdiv${i}_i -netpin P16.PADDO -netpin R${LINE}C[expr $i+$ROW]D.Q0
	}
}

eco_design save

Запустим готовый tcl-скрипт с помощью консольного режима EPIC. Запускаем утилиту fpgac и вводим команду source make.tcl

λ fpgac
--- Lattice Diamond EPIC Version 3.11.3.469
--- Copyright (C) 1992-2020 Lattice Semiconductor Corporation.
--- All Rights Reserved.
--- Lattice Diamond install path: C:/lscc/diamond/3.11_x64
--- Start Time: Wed 19. Jul 09:17:40 2023
% source make.tcl

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

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

Вид готового проекта в EPIC
Вид готового проекта в EPIC

Далее файл соединений можно конвертировать в бинарный файл, загружаемый в плис. Для этого в консоли программы fpgac вводится команда

% bitgen -jedec -w "hello_world.ncd"

Выполнив её, получим файл прошивки hello_world.jed. Прошиваем программатором устройство и проверяем его работу.

20 последовательно соединённых регистров делит входную частоту 12 МГц:

2^{20} = 1048576,  \frac{12 МГц} { 1048576} = 11.452 Гц

Замеряем частоту выходного сигнала на ножке В5 (CLK_DIV) отладочной платы. Измерение даёт такой же результат с небольшой погрешностью.

Измеренный осциллографом сигнал
Измеренный осциллографом сигнал

Проверим работу переключателей. Перебрав все 16 вариантов видим, что диод зажигается только при заданных в LUT значениях ячейки SLICE0 (0хF444).

Демонстрация работы комбинаторной логики
Демонстрация работы комбинаторной логики

Пример 2

С помощью следующего проекта можно продемонстрировать задержку в распространении сигналов внутри ПЛИС. Воспользуемся ей для создания примитивного генератора частоты. Реализуем следующую схему в логике, заменив конденсаторы линией задержки.

Схема генератора
Схема генератора

Компоненты NAND опишем в SLICE0 и SLICE1 и расположим их в левом верхнем углу ПЛИС (колонка 2, столбец 2) и соединим их друг с другом через блок SLICE2, расположенный в правом нижнем углу (колонка 25, столбец 40). Выход CLK_OUT соединим с выходом NAND-ячейки. Следующий tcl-скрипт генерирует вышеописанную схему.

eco_design new -arch {MachXO3LF} -device {LCMXO3LF-6900C} -package {CABGA256} -speed {5} -ncd {gen.ncd}

#80 MHz
set SLICE2 R25C40D
#100 - 125 MHz
#set SLICE2 R14C21D 

eco_add comp -name {SLICE0} -site {R2C2D}
eco_add comp -name {SLICE1} -site {R2C2C}
eco_add comp -name SLICE2 -site $SLICE2

eco_config comp -comp SLICE2 {MODE:LOGIC K1::H1=A K0::H0=A REG1:#OFF REG0:#OFF Q1:#OFF Q0:#OFF F1:F F0:F GSR:ENABLED}
eco_config comp -comp SLICE0 {MODE:LOGIC K0::H0=~(A*B) REG1:#OFF REG0:#OFF Q1:#OFF Q0:#OFF F1:#OFF F0:F GSR:ENABLED}
eco_config comp -comp SLICE1 {MODE:LOGIC K0::H0=~(A*B) REG1:#OFF REG0:#OFF Q1:#OFF Q0:#OFF F1:#OFF F0:F GSR:ENABLED}

eco_add comp -name {CLK_OUT} -site {B4}
eco_config comp -comp {CLK_OUT} {PADDI:#OFF TRIMUX:PADDT:::PADDT=#SIG IOBUF:::PULLMODE=DOWN,IO_TYPE=LVCMOS33,DRIVE=4,SLEWRATE=SLOW,CLAMP=OFF,OPENDRAIN=OFF,DIFFRESISTOR=OFF,DIFFDRIVE=NA,BANK_VCCIO=3.3,HYSTERESIS=NA,PERSISTENT=OFF DATAMUX:PADDO:::PADDO=#SIG VREF:OFF PGMUX:#OFF ODMUX:TRIMUX PGBUF:#OFF INRDBUF:#OFF LVDSBUF:#OFF INRDMUX:#OFF LVDSMUX:DATAMUX}

eco_add net -name del00 -netpin R2C2D.F0 -netpin $SLICE2.A0 -netpin B4.PADDO
eco_add net -name del10 -netpin R2C2C.F0 -netpin $SLICE2.A1
eco_add net -name del01 -netpin $SLICE2.F0 -netpin R2C2D.A0
eco_add net -name del01 -netpin $SLICE2.F0 -netpin R2C2D.B0
eco_add net -name del11 -netpin $SLICE2.F1 -netpin R2C2C.A0
eco_add net -name del11 -netpin $SLICE2.F1 -netpin R2C2C.B0

eco_design save
Общий вид проекта в программе EPIC
Общий вид проекта в программе EPIC

Размещая SLICE2 ближе или дальше от SLICE0 и SLICE1 можно увеличивать или уменьшать частоту генерируемого сигнала.

В приложении можно оценить приблизительное время задержки каждого сигнала. Для созданных цепей получаем следующие значения:

Net "del00":
driver - Pin "R2C2D.F0"
3.775ns - Pin "R25C40D.A0"
2.015ns - Pin "B4.PADDO"

Net "del01":
driver - Pin "R25C40D.F0"
4.140ns - Pin "R2C2D.A0"
4.076ns - Pin "R2C2D.B0"

Net "del10":
driver - Pin "R2C2C.F0"
3.900ns - Pin "R25C40D.A1"

Net "del11":
driver - Pin "R25C40D.F1"
3.479ns - Pin "R2C2C.A0"
3.510ns - Pin "R2C2C.B0"

Полученные значения задержки можно использовать для симуляции проекта:

library IEEE;
use IEEE.std_logic_1164.all;

entity gen_test is 
end gen_test;

architecture arch of gen_test is
	signal del00 : std_logic := '0';
	signal del01 : std_logic := '0';
	signal del10 : std_logic := '0';
	signal del11 : std_logic := '0';
	
	signal s2a : std_logic :='0';
	signal s2b : std_logic :='0';
	signal s0a : std_logic :='0';
	signal s0b : std_logic :='0';
	signal s1a : std_logic :='0';
	signal s1b : std_logic :='0';
begin
	s2a <= del00 after 3.775 ns;
	s2b <= del10 after 3.9 ns;
	
	del01 <= s2a;
	del11 <= s2b;
	
	s0a <= del01 after 4.959 ns;
	s0b <= del01 after 3.963 ns;
	
	del00 <= not(s0a and s0b);
	
	s1a <= del11 after 3.479 ns;
	s1b <= del11 after 3.510 ns;
	
	del10 <= not(s1a and s1b);
end architecture;

Схема в симуляторе начинает генерировать периодический сигнал с частотою около 65 МГц.

Симуляция работы генератора
Симуляция работы генератора

Создадим файл прошивки данного проекта как в предыдущем примере и загрузим его в ПЛИС.Измеренная с помощью осциллографа частота на выводе CLK_OUT получилась приблизительно 83 МГц.

Генерируемый сигнал
Генерируемый сигнал

Значительная разница между измеренной и рассчитанной частотой обусловлена тем, что программа завышает расчитанное время задержки распространения сигнала, предоставляя "худший сценарий".  Это объясняет тот факт, что прошивка ПЛИС может работать корректно, даже если в результате трассировки программа сообщает о многочисленных нарушениях временных ограничений. 

Данная статья и описанные примеры демонстрируют возможности работы с ПЛИС на уровне элементарных блоков и схемы. Несмотря на практическую бесполезность этих тестов, некоторые из рассмотренных методов возможно применять в разработке. К примеру вносить изменения в готовый нетлист для отладки больших проектов, которые требуют продолжительной компиляции. Так можно соединить интересующий сигнал с выводом микросхемы для измерений без правки кода и дальнейшего синтеза. Можно изменить настройки входов-выходов или проинициализировать Block Ram Memory новыми значениями. Работая с редактором схемы возможно реализовать устройство типа time-to-digital converter. И, конечно же, подобные примеры могут быть полезным методическим материалом при изучении ПЛИС.

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


  1. Nahrenako
    24.07.2023 06:44

    Пример того как не нужно использовать FPGA.

    Если мы говорим об обучении, то с самого начала нужно учить использовать синхронный дизайн и рассказывать что такое Global Clock ресурсы и почему нельзя строить делители так как это сделал автор.


    1. Abwindzentrierer Автор
      24.07.2023 06:44
      +2

      Автор не показывает пример того, как нужно использовать FPGA и не утверждает, что так необходимо делать делители или что-либо ещё.
      Автор демонстрирует некоторые из возможностей программы.
      На мой взгляд этот подход может быть вполне наглядным примером работы как синхронной, так и асинхронной логики.


    1. LAutour
      24.07.2023 06:44
      +1

      Синхронные делители нужны не всегда.


  1. KeisN13
    24.07.2023 06:44
    +1

    Отличное проектирование на FPGA из разряда "Ненормальное программирование". Я что-то подобное делал на стриме : ECO Flow - извращенное проектирование на FPGA . Тот еще зашквар конечно xD. Разумеется, Eco flow не для этого, это просто был эксперимент :)

    Вот про то , зачем нужен ECO режим: ECO Flow в Vivado или работа в режиме редактирования нетлиста

    Ну и ссылочку на стрим оставлю.

    PS: такие делители, разве что для семисегментников подходят, так делать не стоит конечно, лучше все триггеры питать одним клоком, а выход использовать как clock-enable для дальнейшей схемы. Так и по таймингам проще и проект более устойчивый к всяким там температурам. Ну и статья бы не сильно изменилась, если схему делителя сразу задать правильно. А так спасибо за статью и картинки, было интересно посмотреть как ECO выглядит в латиссовском софте.


  1. GarryC
    24.07.2023 06:44

    Больше всего понравилась фраза:

    Это объясняет тот факт, что прошивка ПЛИС может работать корректно, даже если в результате трассировки программа сообщает о многочисленных нарушениях временных ограничений. 


    А что, так можно было ?