Azotirovanie.ru

Инженерные системы и решения
4 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Записки программиста

Записки программиста

ПЛИС (Программируемая Логическая Интегральная Схема) — это интегральная схема, предназначенная для построения цифровых цепей из описания на специальном языке программирования. Другими словами, ПЛИС представляет собой чип, как бы содержащий в себе кучу элементов наподобие 74HCxx. Какие именно это будут логические элементы, какие между ними будут связи, и какие связи будет иметь получившаяся схема с внешним миром, определяется на этапе программирования ПЛИС.

Примечание: Насколько я смог выяснить, в русском языке на сегодняшний день термины ПЛИС и FPGA (Field-Programmable Gate Array, Программируемая Пользователем Вентильная Матрица), принято считать взаимозаменяемыми, что будет использовано далее по тексту. Однако стоит знать о существовании и альтернативной точки зрения, согласно которой FPGA (ППВМ) является одной из разновидностей ПЛИС (PLD, Programmable Logic Device).

Основные сведения об FPGA

Для программирования FPGA используются языки описания аппаратуры (HDL, Hardware Description Language). Среди них наибольшей популярностью пользуются Verilog (и его диалекты, в частности SystemVerilog), а также VHDL. Языки во многом похожи, но имеют разный синтаксис и различаются в некоторых деталях. Если Verilog — это такой C мира описания аппаратуры, то VHDL — соответственно, Pascal. Насколько мне известно, VHDL несколько менее популярен, в частности, из-за его многословности по сравнению с Verilog. Из преимуществ VHDL (или недостатков, кому как) можно назвать строгую статическую типизацию. Verilog же иногда допускает неявное приведение типов. Если продолжать аналогию с C и Pascal, языки различаются не настолько сильно, чтобы не выучить их оба.

На данный момент лидирующими производителями FPGA являются компании Altera (сейчас принадлежит Intel) и Xilinx. По информации из разных источников, вместе они контролируют не менее 80% рынка. Из других игроков стоит отметить Actel (куплена Microsemi), Lattice Semiconductor, Quicklogic и SiliconBlue. С железом от Xilinx можно работать только из среды разработки от Xilinx (называется Vivado), а среда разработки от Altra (называетя Quartus) понимает только железо от Altera. То есть, полный вендор лок, и выбирая конкретную FPGA для своего проекта, вы автоматически выбираете и инструменты разработки соответствующего производителя, их техническую поддержку, документацию, условия лицензирования софта, политику касаемо прекращения поддержки железа, и так далее.

FPGA часто используются в задачах, где некие вычисления хочется существенно ускорить, реализовав их прямо в железе. Например, FPGA нашли широкое применение в области обработки сигналов, скажем, в осциллографах, анализаторах спектра, логических анализаторах, генераторах сигналов, Software Defined Radio и даже некоторых мониторах. В частности, в LimeSDR используется Altera Cyclone IV, а в осциллографе Rigol DS1054Z стоит Xilinx Spartan-6, а также ProASIC 3 от компании Actel. Еще из применений, о которых я слышал, могу назвать компьютерное зрение, распознавание речи и биоинформатику. Есть и другие проекты, в частности по разработке веб-серверов и СУБД, работающих на FPGA [PDF]. Но, насколько мне известно, это направление все еще остается сильно экспериментальным.

Xilinx или Altera?

Как говорится, лучший Linux тот, который использует ваш знакомый гуру по Linux.

Мой знакомый гуру по FPGA в лице Дмитрия Олексюка посоветовал начать с девборды Arty Artix-7 от компании Digilent. Используемой в ней FPGA является Artix-7 от Xilinx. Сам Digilent не производит доставку в Россию, но устройство доступно и на AliExpress, хотя и с заметной наценкой (официальная цена составляет 99$). Также его продают на eBay. Это довольно мощная плата, которая, тем не менее, стоит вполне адекватных денег.

Fun fact! Если вам просто хочется попрограммировать на Verilog или VHDL, строго говоря, покупать какую-либо плату с FPGA не требуется. Первое время можно ограничиться симулятором, работа с которым будет рассмотрена далее.

Arty Artix-7, девборда с FPGA

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

По доставке. Я слышал, что устройства с FPGA на борту имеют большие шансы не пройти таможню. Магазин на AliExpress, ссылку на который я привел выше, доставляет платы в Россию через курьерскую службу СПСР. Для прохождения таможни потребовалось заполнить онлайн-форму с паспортными данными (только данные, без фото) и контактным телефоном, как этого требует текущее российское законодательство. После этого плата была доставлена курьером прямо до двери без каких-либо вопросов.

Установка Vivado

Среда разработки Vivado доступна для скачивания на сайте Xilinx. Будьте морально готовы к тому, что перед скачиванием вам придется пройти регистрацию и заполнить довольно подробную форму о себе. Скачиваем архив под названием «Vivado HLx 2017.2: All OS installer Single-File Download». Не перепутайте случайно с каким-нибудь «Vivado Lab Solutions», это совершенно не то, что нужно. Архив весит более 20 Гб, поэтому запасаемся терпением.

Распаковываем архив, запускаем инсталлятор. Ставим Vivado HL System Edition. Полная его версия займет на диске 47 Гб. Лично я снял галочку напротив Software Development Kit и оставил поддержку только 7 Series устройств, что уменьшило размер до 12 Гб. Забегая немного вперед отмечу, что такой конфигурации оказалось вполне достаточно.

Перед запуском Vivado нужно добавить в него поддержку Arty Artix-7, так как из коробки он ничего об этой плате не знает. Делается это как-то так:

/ opt / xilinx / Vivado / 2017.2 / data / boards / board_files
wget https: // github.com / Digilent / vivado-boards / archive / master.zip
unzip master.zip
mv vivado-boards-master / new / board_files /* . /
rm -r vivado-boards-master
rm master.zip

Читайте так же:
Чего можно сделать со старым счетчиком

Также скачиваем отсюда и сохраняем куда-нибудь файл Arty_Master.xdc. Он понадобится нам далее. Файл содержит описание находящихся на плате светодиодов, переключателей и так далее. Без него поморгать светодиодами на Verilog будет непросто.

Первый проект на SystemVerilog

В Vivado говорим File → New Project… В качестве типа проекта выбираем RTL Project, ставим галочку Do not specify sources at this time. В диалоге выбора типа платы находим в списке Arty.

Первым делом добавляем к проекту скачанный ранее XDC файл. Копируем его в каталог с проектом. Затем говорим File → Add Sources… → Add or create constraints → Add Files, находим копию файла, жмем Finish. В дереве файлов проекта (Sources) в группе Constraints появится файл Arty_Master.xdc, или как вы там назвали копию. Открываем его и раскомментируем все строчки в группах Clock signal, Switches и LEDs.

Далее говорим File → Add Sources… → Add or create design sources → Create File. В типе файла выбираем SystemVerilog, в имени файла вводим что-нибудь вроде hello. Говорим Finish. Далее появится диалог Define Module, который предложит накликать интерфейс модуля. Диалог довольно бесполезный, потому что то же самое удобнее сделать прямо в коде, так что жмем Cancel.

В дереве исходников находим новый файл hello.sv, он будет в группе Design Sources. Открываем и пишем следующий код:

`timescale 1ns / 1ps

module hello (
input logic CLK100MHZ ,
input logic [ 3 : 0 ] sw ,
output logic [ 3 : 0 ] led
) ;

always @ ( posedge CLK100MHZ )
begin
if ( sw [ 0 ] == 0 )
begin
led <= 4’b0001 ;
end
else
begin
led <= 4’b0000 ;
end
end

Если все было сделано правильно, на этом этапе Vivado у вас будет выглядеть как-то так (кликабельно, PNG, 71 Кб):

Vivado, среда разработки под FPGA от Xilinx

Компиляция программы осуществляется в два этапа — синтез (synthesis) и имплементация (implementation). На этапе синтеза программа транслируется в абстрактную цепь из логических вентилей и прочих элементов. На этапе имплементации принимается решение о том, как прошить эту цепь в конкретную железку.

Запустим синтез, сказав Flow → Run Synthesis, или просто нажав F11. В правом верхнем углу вы увидите индикацию того, что процесс идет. Он может занимать довольно много времени, в зависимости от вашего компьютера и сложности программы. На моем ноутбуке синтез приведенной выше программы выполнился где-то секунд за 10. Если теперь сказать Flow → Open Synthesized Design, то можно увидеть красивую картинку вроде такой:

Схема цепи, полученная после этапа синтеза

Настало время прошить нашу плату. Говорим Flow → Run Imlementation, затем Flow → Generate Bitstream. Подключаем плату к компьютеру по USB, в Vivado говорим Flow → Open Hardware Manager → Open target → Auto Connect → Program device. Потребуется указать путь к bit-файлу. У меня он был следующим:

Говорим Program. Теперь на плате горит светодиод LD4, если переключатель SW0 опущен (см приведенную выше фотографию платы). Если же переключатель поднят, светодиод не горит. Простенько, конечно, но это же «hello, world», чего вы ожидали? 🙂

Симуляция

Симуляция — это виртуальное выполнение кода на Verilog или VHDL прямо на вашем компьютере, безо всяких там ПЛИС’ов. Это одновременно и отладочный инструмент, и своего рода фреймворк для покрытия кода тестами.

При знакомстве с симуляцией первое, что я обнаружил, было то, что она у меня не работает. В логах было просто:

Google по этой ошибке находил только всякую ерунду в стиле «попробуйте отключить антивирус». В итоге решить проблему помогло добавление флага -v 2 в скрипт

/opt/xilinx/Vivado/2017.2/bin/xelab. С его помощью я выяснил, что Clang, бинарник которого Vivado таскает за собой, падает со следующей ошибкой:

А эта ошибка и ее решение уже описаны на Arch Wiki. Лично я просто скопировал уже существующий файл из каталога Vivado_HLS:

… после чего все заработало. Итак, а теперь, собственно, пример симуляции.

По аналогии с тем, как ранее мы создавали hello.sv, создаем новый файл hello_sim.sv в группе Simulation Sources. В файле пишем следующий код:

`timescale 1ns / 1ps

module hello_sim ( ) ;
logic clck_t ;
logic [ 3 : 0 ] sw_t ;
logic [ 3 : 0 ] led_t ;

hello hello_t ( clck_t , sw_t , led_t ) ;

initial begin
clck_t <= 0 ;
sw_t <= 4’b0000 ; # 1 ; clck_t <= 1 ; # 1 ; clck_t <= 0 ; # 1 ;
assert ( led_t === 4’b0001 ) ;

sw_t <= 4’b0001 ; # 1 ; clck_t <= 1 ; # 1 ; clck_t <= 0 ; # 1 ;
assert ( led_t === 4’b0000 ) ;
end

В дереве исходников делаем правый клик по файлу, выбираем Source Node Properties. В секции Used In снимаем галочки Synthesis и Implementation. Мы же не хотим, чтобы какие-то там тесты засоряли нашу далеко не резиновую FPGA?

Теперь говорим Flow → Run Simulation → Run Behavioral Simulation. В итоге вы увидите что-то примерно такого плана:

Симуляция в Vivado

Можно видеть, что когда sw[0] равен нулю, led[0] равен единице, и наоборот. При этом все изменения происходят по фронту тактового сигнала. Похоже, что программа работает корректно. Ну и на ассертах ничего не свалилось, что как бы намекает.

Заключение

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

  • Если вас интересуют подробности по железу, обратите внимание на даташит Artix-7 [PDF], reference manual к Arty Artix-7 [PDF], а также схему платы [PDF];
  • Книга Цифровая схемотехника и архитектура компьютера (Digital Design and Computer Architecture) — прекрасная книга, в которой, помимо прочего, подробно объясняются языки SystemVerilog и VHDL, а также внутреннее устройство FPGA; — каталог всевозможных открытых проектов под FPGA. Для скачивания исходного кода требуется регистрация. Активация аккаунта производится модераторами вручную, поэтому может занимать несколько дней;
Читайте так же:
Модуль для джумла счетчик

А пишите ли вы под FPGA? Если да, то на каком языке и какого производителя предпочитаете?

DDS Синтезатор на Verilog

В этом посте я поделюсь тем, как разбирался с написанием DDS синтезатора на Verilog. Он будет использован для генерации синусоидального колебания, частоту и начальную фазу которого можно регулировать и рассчитан для использования с 8-битным однополярным ЦАП. О том, как работает синтезатор хорошо написано в статье журнала Компоненты и Технологии. Для сокращения объема использованной памяти таблицы синуса использована симметрия.

Для компиляции под Линуксом я использовал Iverilog, а для отображения GTKWave. Для удобства был написан простенький Makefile, возможно кому-нибудь он пригодится. Изначально при помощи компилятора iverilog мы получаем файл tb.out, а затем отправляем его в симулятор vvp, который устанавливается вместе с iverilog. В результате vvp сгенерирует out.vcd, в котором содержатся все переменные (сигналы), используемые в проекте. Цель display помимо вышесказанного запустит GTKWave с файлом переменных и можно будет увидеть временные диаграммы сигналов.

В первую очередь необходимо разместить в памяти таблицу будущего синуса, для я написал простенький скрипт на Python, разбивающий четверть периода синуса на 64 точки и генерирующий в формате, который потом можно скопировать в исходный код. Так как я задумывал реализацию DDS для внешнего однополярного ЦАП с разрядностью не более 8 бит, то амплитуда синуса должна быть в интервале от 0 до 256, где в диапазоне 0. 127 лежит отрицательный полупериод, а в 128. 255 — положительный. В связи с этим полученные значения синуса (от 0 до pi/4) умножаются на 127, а затем ещё к ним прибавляется 127. В результате получается значения первой четверти периода, амплитуда которых 128. 256.

Обращу внимание на то, что при таком формировании синус на выходе ЦАП будет иметь постоянную составляющую. Для того чтобы её убрать необходимо пропустить его через конденсатор.

Т.к синус функция симметричная (нечётная), то можно обнаружить первую симметрию sin(x)=-sin(pi+x). Вторая симметрия характерна тем, что имея таблицу на четверть периода, вторую четверть можно получить, проходя таблицу в обратном порядке (т.к. синус на полупериоде сначала возрастает, потом убывает).

Формируем синус

Основная часть DDS синтезатора — фазовый аккумулятор. По сути он является индексом элемента из Look Up Table (LUT). За каждый период тактового сигнала значение в нём увеличивается на некоторое значение, в результате на выходе получается синус. От значения приращения аккумулятора фазы будет зависеть частота сигнала на выходе — чем оно больше, тем выше частота. Однако, по критерию Котельникова частота дискретизации должна быть как минимум в 2 раза больше частоты сигнала (для избежания эффекта наложения спектра), отсюда ограничение на максимальное приращение — половина фазового аккумулятора. Вообще инженерный критерий это частота дискретизации = 2.2 частоты сигнала, поэтому, решив не доводить до крайности, я убрал ещё один разряд, оставив 6 бит на инкремент при разрядности фазового аккумулятора 8 бит (хотя уже при этом синус шакалит).

Из-за используемой симметрии непосредственно для выборки по индексу будут использоваться только младшие 6 бит 2^6=64. Старшие 2 бита используются для выявления четвертьпериода генерирования синуса и соответственно изменения направления обхода таблицы. Должно получиться что-то похожее на:

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

Немного пояснений о том, как работает симметрия. На каждом такте проверяется (по 2-м старшим битам), в какой четверти находится в данный момент фазовый аккумулятор. Если старшие = 00, то на выходе в старшем разряде 1 (отвечает за положительную полуволну), в младших — значение из LUT в соответствии с индексом. После того как значение фазового аккумулятора превысит 63 (пройдет первая четверть), в старших битах появится 01, а младшие снова заполнятся нулями.

Для прохождения LUT в обратном порядке достаточно инвертировать младшие биты фазового аккумулятора (он продолжит увеличиваться за каждый такт, а его инвертированное значение будет уменьшаться).

Для формирования отрицательной полуволны в старший разряд выходных данных запишем 0. А инвертировать теперь необходимо само значение из таблицы синуса. Тут смысл состоит в том, что необходимо получить зеркальную копию четверти синуса, а если этого не сделать, то получится тот же рисунок, что и в первой четверти, но опущенный на 127 вниз. Можете проверить это, убрав инверсию в коде.

Меняем частоту и начальную фазу

Как уже было описано выше для изменения частоты необходимо поменять значение инкремента фазы. Появятся новые входы:

Для изменения значения инкремента фазы будем просто защелкивать его на каждом такте:

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

Читайте так же:
Расшифровка клейма поверки счетчика
Testbench

Код тестбенча для Iverilog и GTKWave обладает некоторыми конструкциями (со знаком доллара) которые не используются в привычных ISE Design Suite или Quartus. Смысл их сводится к тому, чтобы выбрать отслеживаемые сигналы и загрузить их в файл, чтобы затем передать симулятору. Сама по себе работа тестбенча тривиальна — делаем сброс, устанавливаем частоту/начальную фазу и ждем некоторое время.

Временные диаграммы

На выходе получим что-то похожее на синус с меняющейся частотой и начальной фазой в установленные в тестбенче моменты времени. Стоит отметить, что с ростом частоты падает разрешение по ней (количество отсчетов на период), соответственно частота тактирования синтезатора и размер его LUT играет решающую роль в воспроизведении чистого синуса (чем больше его форма приближается к идеальному, тем меньше побочных составляющих будет в спектре результирующего сигнала и тем уже будет пик на генерируемой частоте).

Тут видно, что сигнал со второй частотой имеет уже не такой гладкий синус, как другие. Рассмотрим его поближе.

Видно, что на синус это все же немного похоже, результат станет ещё лучше после того как такой сигнал будет пропущен через антиалиасинговый фильтр (Фильтр Нижних Частот).

Преобразователь SPI в UART на Verilog

Рассмотрим проект преобразователя интерфейсов из SPI в UART, реализованный на языке Verilog.

1 Схема преобразователя SPI в UART

Для работы преобразователя нам понадобятся следующие функциональные блоки:

2 Модуль ФАПЧ (PLL)

Модуль ФАПЧ Модуль ФАПЧ

3 Модуль синтезатора частот

Исходный код модуля синтезатора частот на Verilog (разворачивается)

Напишем тестбенч для проверки нашего модуля синтезатора.

Исходный код тестбенча синтезатора частот (разворачивается)

Временная диаграмма работы модуля нормальная:

Временная диаграмма работы модуля синтезатора частоты Временная диаграмма работы модуля синтезатора частоты

4 Модуль передатчика UART

Напишем тестбенч для модуля передатчика UART.

Код тестбенча для модуля передатчика UART (разворачивается)

Видно, что модуль работает так, как нужно:

Временная диаграмма работы модуля передатчика UART Временная диаграмма работы модуля передатчика UART

5 Модуль приёмника SPI

Теперь проверим работу нашего модуля, написав тест.

Код тестбенча приёмника SPI (разворачивается)

На линии MOSI выставим 16’b1001100001100011, на линии data_out то же число:

Временная диаграмма работы модуля приёмника SPI Временная диаграмма работы модуля приёмника SPI

6 Соединяем модули преобразователя SPI в UART

Объединим все модули вот таким образом:

RTL диаграмма проекта SPI в UART RTL диаграмма проекта SPI в UART

Назовём этот модуль top_spi_uart.v.

Исходный код модуля верхнего уровня на Verilog (разворачивается)

Напишем тестбенч для проверки модуля.

Тестбенч для преобразователя SPI в UART (разворачивается)

Убедимся, что всё работает исправно:

Временная диаграмма работы преобразователя SPI в UART Временная диаграмма работы преобразователя SPI в UART

По SPI (линия MOSI) мы передаём 1 байт 8’b01101001. Видно, что на передатчике UART (линия TxD) то же значение.

7 Демонстрация работыпреобразователя SPI в UART

Для демонстрации работы преобразователя SPI в UART соберём небольшой стенд. В роли передатчика SPI будет выступать отладочная плата с микросхемой FTDI FT2232HL, подключённая к ноутбуку по USB. В роли приёмника UART – модуль USB-UART, подключённый к USB-порту компьютера. Между ними в роли преобразователя SPI-UART выступает ПЛИС Cyclone II (EP2C5T144C8) на отладочной плате A-C2FB. Такую плату сейчас сложно найти в продаже, но есть другие на 2-ом Циклоне.

Стенд для демонстрации работы преобразователя SPI в UART Стенд для демонстрации работы преобразователя SPI в UART Стенд для демонстрации работы преобразователя SPI в UART – плата с FT2232HL и плата A-C2FB с ПЛИС Cyclone ii Стенд для демонстрации работы преобразователя SPI в UART – плата с FT2232HL и плата A-C2FB с ПЛИС Cyclone ii

Посылать данные с компьютера-источника будем с помощью программы SPI via FTDI, о которой я уже писал. Она переводит подключённую микросхему FTDI в режим SPI с заданными настройками и позволяет осуществлять запись/чтение по интерфейсу SPI.

Не забудем, что наш проект работает со скоростями SPI до 750 кбит/с. Это ограничение вызвано тем, что наш UART работает на пределе возможностей (921600 kbps), и больший поток он просто не успеет обработать.

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

Демонстрация работы преобразователя SPI в UART: приём данных на компьютере Демонстрация работы преобразователя SPI в UART: приём данных на компьютере

Как видно, данные безошибочно передаются с компьютера на компьютер. Преобразователь интерфейсов работает.

Счетчик по модулю verilog

Верилог имеет три вида оператора присваивания: непрерывное, блокирующее и неблокирующее. Если с непрерывным, или постоянным присваиванием все более-менее понятно, то разница между блокирующим и неблокирующим присваиваниями не столь отчетлива и во многих руководствах она остается за кадром. К сожалению, нередко встречаются утверждения о том, что блокирующие присваивания «выполняются последовательно». Некоторые же идут настолько далеко, что дают советы использовать неблокирующие присваивания тем, кто хочет, чтобы их код исполнялся побыстрее. Цель этой статьи — развеять туман и помочь начинающим составить представление о том, что же именно представляют из себя различные виды присваиваний в синтезируемом подмножестве Верилога.

Непрерывное присваивание

То, что в Верилоге именуется «постоянным», или «непрерывным» присваиванием на самом деле всегда развертывается в комбинаторную схему. Непрерывное присваивание может встречаться в операторе assign, либо же прямо в декларации сигнала wire. Левой частью всегда является сигнал, правой — выражение, использующее любые другие сигналы. Значения регистровых переменных тоже являются сигналами.

// регистр, содержащий семплированное значение входа strobe
reg strobe_sampled;

// декларация сигнала strobe_negedge
wire strobe_negedge;
// непрерывное присваивание выражения сигналу strobe_negedge
assign strobe_negedge =

strobe & strobe_sampled;

// декларация и присваивание совмещенные в одном операторе
wire strobe_posedge = strobe &

Неблокирующее присваивание

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

Читайте так же:
Epson p50 как сбросить счетчик чернил
Пример

reg reg_A;
reg reg_B;
wire swap_en;

always @(posedge clk) begin
if (swap_en) begin
reg_A <= reg_B;
reg_B <= reg_A;
end
end

Человеку непосвященному скорее всего покажется, что по фронту сигнала clk, если swap_en равен «1», регистры reg_A и reg_B примут значение, которое reg_B имел до свершения события. В действительности же эта запись соединяет выход регистра reg_A со входом reg_B, а выход reg_B со входом reg_A. Таким образом, если в момент положительного перепада clk сигнал swap_en установлен в «1», в каждый из регистров записывается предварительно установившееся на его входе значение. Для reg_A это значение reg_B, а для reg_B — это значение reg_A. Два регистра обменялись значениями одновременно!

Пример 2

input strobe;
reg strobe_sampled;

reg[7:0] count;

always @(posedge clk) begin
strobe_sampled <= strobe;
if (strobe &

strobe_sampled) begin
// событие: положительный перепад на входе «strobe»
count <= count + 1;
end
end

По фронту clk происходит запись текущего значения strobe в регистр strobe_sampled. Параллельно происходит проверка,
а не единице ли равно текущее значение strobe и не ноль ли при этом значение strobe_sampled.

Пишем «демку» для LESO2 на Verilog

Схема, синтезируемая из условия if использует выход регистра strobe_sampled. То есть, условие внутри if можно понимать как «strobe равно единице и предыдущее значение strobe равно нулю».

При этом не будет лишним повторить, что эта запись на самом деле не так проста, как кажется. Например, условие внутри if — это выход комбинаторной схемы, которая не связана с тактирующим сигналом и поэтому может быть описана извне:

Но это еще не все. Новичок скорее всего прочитает этот код примерно так: «если обнаружен положительный перепад сигнала strobe, взять содержимое count, увеличить его на 1 и записать обратно в count». В действительности же следует читать это как: «в регистр count записывается значение выражения, которое к моменту обнаружения положительного перепада сигнала strobe имеет установившееся значение count + 1». Вариант записи, иллюстрирующий такое прочтение:

strobe_sampled;
wire [7:0] count_incr = count + 1;
always @(posedge clk) begin
strobe_sampled <= strobe;
if (strobe_posedge)
count <= count_incr;
end

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

always @(posedge clk) begin
count <= count + 1;
if (count == 10) count <= 0;
end

Этот счетчик имеет период счета равный 11.

Выражение count == 10 выполняется на один такт позже после того, как в регистр count было записано значение 10. Один из способов исправить положение — употребить в if то же выражение, что и в правой части присваивания:

Иногда удобно выносить выражения типа count + 1 из блоков always, это позволяет уменьшить вероятность ошибок в случае их многократного использования.

Блокирующее присваивание

Блокирующее присваивание, заклеймленное некорыми как «медленное», в действительности во многих случаях синтезируется в совершенно ту же схему, что и неблокирующее. Так, например фрагменты:

always @(posedge clk) begin
x = x + 1;
y = y + 1;
end

дадут один и тот же результат. Оба выражения выполнятся одновременно, в обоих случаях «время выполнения» будет равно времени записи в соответствующий регистр значения на его входе. Пока выражения не зависят друг от друга, никакой разницы между блокирующими и неблокирующими присваиваниями нет.

В то же время, следующая запись являет собой что-то новое:

Здесь x увеличится на 1, а y примет значение x + 1. Чтобы записать это выражение неблокирующими присваиваниями, потребовалась бы такая запись:

Цепочку блокирующих присваиваний можно рассматривать как одно большое выражение. Еще пример:

y = input_value >> 4;
y = y + center_offset;
y = 3 * y;

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

Заключительный пример, использующий оба вида присваиваний, заодно напоминающий о неродстве оператора for с одноименными операторами в алгоритмических языках программирования:

// Эквивалентная запись:
// history[3] <= history[2]; history[2] <= history[1]; history[1] <= history[0]; history[0] <= current;
always @(posedge clk) begin
for (i = 1; i < 4; i = i + 1) history[i] <= history[i-1];

history[0] <= current;
end

// Эквивалентная запись:
// avg <= (history[3]+history[2]+history[1]+history[0])/4;
always @(posedge clk) begin
sum = 0;
for (i = 0; i < 4; i = i + 1) sum = sum + history[i];

avg = sum/4;
end

Этот пример выдает скользящее среднее с запаздыванием на два такта.

Читайте так же:
Выявили счетчик с пультом
Регистры и провода

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

Пример. Шинный мультиплексор.

wire [7:0] data_in = cpu_memr ? ram_data :
cpu_inport? io_data :
interrupt ? 8’hFF : 8’hZZ;

Тернарные выражения удобны для описания подобных конструкций. Однако следует проявлять осторожность, потому что в действительности то, что описано в этом выражении является приоритетным шифратором. Кроме приоритетности, побочным эффектом может являться неравное время установки результата в зависимости от входных значений. «Честный» же мультиплексор можно описать только с помощью оператора case. Но оператор case может быть только внутри always блока, а нужно, чтобы шина data_in имела установившееся значение к очередному фронту тактового сигнала, то есть присваивание должно быть асинхронным.

Выручит конструкция такого вида:

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

Выводы

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

Адрес этой статьи: http://sensi.org/

Просьба указывать ссылку на первоисточник.

Copyright © 2009 Viacheslav Slavinsky svofski on gmail

Updated: Fri Feb 27 00:01:57 UTC 2009

Железное противостояние: VHDL против Verilog

Verilog is a type of Hardware Description Language (HDL). Verilog is one of the two languages used by education and business to design FPGAs and ASICs. If you are unfamilliar with how FPGAs and ASICs work you should read this page for an introduction to FPGAs and ASICs. Verilog and VHDL are the two most popular HDLs used. Compared to traditional software languages such as Java or C, Verilog works very differently. Let’s get started by looking at a simple example.

First we will create a Verilog file that describes an And Gate. As a refresher, a simple And Gate has two inputs and one output. The output is equal to 1 only when both of the inputs are equal to 1. Below is a picture of the And Gate that we will be describing with Verilog.

Let’s get to it! One fundamental unit of Verilog is called a wire. For now let’s assume that a wire can only be a 0 or a 1. Here is some basic wire logic:

wire and_temp; assign and_temp = input_1 & input_2;

We are creating a wire called and_temp on the first line of code. On the second line of the code, we are taking the wire that we created and we are assigning the wire. To assign it, we are using the Boolean AND function which in Verilog is the Ampersand (&). If you were to describe the code shown above, you might say, «The signal and_temp gets input_1 AND-ed with input_2.»

Input_1 and Input_2 are inputs to this piece of Verilog Code. Let’s show the complete list of inputs and outputs. This is done in the module definition. Module is a reserved keyword in Verilog which shows the creation of a block of code with defined inputs and outputs.

module example_and_gate ( input_1, input_2, and_result); input input_1; input input_2; output and_result;

This is your basic module. It defines our module called example_and_gate and 3 signals, 2 inputs and 1 output. Let’s put everything together to finish the file. The only thing we are missing is the assignment of the output and_result. One other note, // in Verilog is used for a comment.

/////////////////////////////////////////////////////////////////////////////// // File Downloaded from http://www.nandland.com /////////////////////////////////////////////////////////////////////////////// module example_and_gate ( input_1, input_2, and_result); input input_1; input input_2; output and_result; wire and_temp; assign and_temp = input_1 & input_2; assign and_result = and_temp; endmodule // example_and_gate

Congratulations! You have created your first Verilog file.

Does it seem like you had to write a lot of code just to create a stupid and gate? First of all, and gates aren’t stupid. Secondly, you are correct, HDLs take a lot of code to do relatively simple tasks. You can take some comfort in the fact that Verilog is at least less verbose than VHDL. Get used to the fact that doing something that was very easy in software will take you significantly longer in an HDL such as Verilog or VHDL. But just ask some software guy to try to generate an image to a VGA monitor that displays Conway’s Game of Life and watch their head spin in amazement! By the way, that video is created with an FPGA. You will be able to do that soon enough!

голоса
Рейтинг статьи
Ссылка на основную публикацию
Adblock
detector