Занимаясь поиском причины некорректно работающего проекта на ПЛИС, несколько раз приходилось погружаться в топологию схемы на уровне логических ячеек. Ошибка проявляла себя любопытным образом - машина состояний не переключалась по заведомо свершившемуся событию. Подключение сигналов машины к отладочным выводам микросхемы приводило к тому, что ошибка пропадала и проект начинал работать правильно. Для поиска несоответствий пришлось сравнивать конфигурацию регистров в нетлистах и проблема была обнаружена - в процессе трассировки программа неверно инициализировала регистр.
Копание в ПЛИС на уровне схемы соединений натолкнуло меня на идею сделать небольшой тестовый проект в нетлист-редакторе. В данной статье рассмотрен способ конфигурирования ПЛИС на уровне логики микросхемы. С помощью программы, позволяющей работать с архитектурой кристалла, показана методика создания проектов на этапе трассировки и переноса схемы соединений дизайна в чип.
В качестве примера я выбрал два простых “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), в основном окне программы отобразится схема устройства, представленная на рисунке ниже.
В окне показаны имеющиеся в чипе компоненты и соединения. В мелком масштабе узнаётся схема устройства, представленная в документации к этой ПЛИС.
На схеме можно видеть набор логических элементов PFU (Programmable Function Units). Они расположены почти по всей площади микросхемы и сгруппированы в столбцы и строки.
Блок PFU состоит из четырёх программируемых ячеек (Slices). Каждая ячейка может быть сконфигурирована в одном из четырёх режимов - Logic, Ripple, RAM и ROM. В Logic-режиме ячейку можно использовать для создания комбинаторной логики, настроив таблицу истинности. Ripple-режим позволяет реализовывать элементарные арифметические функции - двуxбитное сложение, вычитание и прочее. С помощью нескольких ячеек в RAM-режиме можно реализовывать блок перезаписываемой памяти, а в режиме ROM - программируемой памяти.
В каждой ячейке Slice имеется две таблицы истинности с четырьмя входами, одним выходом (LUT look-up table) и битами переноса, два регистра и мультиплексоры. Ниже на рисунке показаны элементы ячеек из документации (слева) и в редакторе EPIC (справа).
Каждый из блоков PFU имеет буквенно-цифровую адресацию, соответствующую строке и столбцу, в котором он расположен (к примеру, R9C18), а каждая ячейка в блоке индексируется с помощью букв A, B, C и D.
Порты вводов-выводов расположены по периметру устройства и сгруппированы в банки. Каждый отдельный банк может быть сконфигурирован на работу вывода микросхемы с определённым стандартным типом сигналов - LVCMOS, LVTTL, LVDS, …
Коммутация компонентов производится с помощью буферов и переключателей. Рядом с каждым блоком PFU расположен блок переключателей, входы-выходы которого подключены к логическим ячейкам и к общей шине, соединяющей остальные переключатели в одну общую сеть.
Эти буферы позволяют соединять выводы ячеек друг с другом. Такую операцию можно делать вручную в графическом редакторе, выбирая соответствующие входы-выходы компонент и создавая соединения. Но это слишком трудоёмкий процесс, поэтому значительно удобнее пользоваться 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 }
Параметры проще всего скопировать из атрибутов вручную созданной компоненты в графическом редакторе:
В данном примере созданы два основных типа: вход и выход ПЛИС. Далее их параметры копируем на остальные порты:
# 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, который можно открыть в графической оболочке и посмотреть топологию получившейся прошивки.
Далее файл соединений можно конвертировать в бинарный файл, загружаемый в плис. Для этого в консоли программы fpgac вводится команда
% bitgen -jedec -w "hello_world.ncd"
Выполнив её, получим файл прошивки hello_world.jed. Прошиваем программатором устройство и проверяем его работу.
20 последовательно соединённых регистров делит входную частоту 12 МГц:
Замеряем частоту выходного сигнала на ножке В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
Размещая 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. И, конечно же, подобные примеры могут быть полезным методическим материалом при изучении ПЛИС.