Основы VHDL
Сайт: | НИЦ СФ |
Курс: | НИЦ СФ |
Книга: | Основы VHDL |
Напечатано:: | |
Дата: | пятница, 18 апреля 2025, 13:02 |
1. Предисловие
Данный конспект собран в качестве сопровождающего материала к курсам по ПЛИС. Поскольку курсы регулярно проводятся на базе ПЛИС различных производителей (как правило либо AMD(prev.Xilinx) либо IntelFPGA (prev.Altera)), то иллюстрации с логическими схемами, полученными в САПР, будут приведены в смешанном формате (некоторые из САПР Vivado, другие из САПР Quartus).
Базовым средством моделирования является ModelSim (либо QuestaSim), однако и для Xsim даются комментарии.
Все предложения по модернизациям и исправлению ошибок просьба направлять в Telegram: https://t.me/suggestmenews_bot
2. Основные сведения о VHDL
Использование графического представления схем из логических элементов может быстро перегрузиться и усложнить восприятие даже с использованием обозначений некоторых абстрактных элементов с заранее известным поведением. Для улучшения эффективности разработки логических схем были созданы системы автоматического проектирования (САПР), которые позволили использовать языки описания аппаратуры (англ. Hardware Description Language, HDL). То есть, были разработаны инструменты, благодаря которым описания поведения схем стало превращаться в схемы. Этот процесс называется синтезом. Синтез является одним из этапов компиляции проектов под ПЛИС; ступени компиляции проектов мы рассмотрим немного позднее.
Итак, для получения схемы теперь достаточно её описать. Для описания схем в рамках данного конспекта будет рассматриваться язык VHDL, а именно его наиболее актуальную версию VHDL 2008. \
!NB Важный факт о VHDL: язык является нечувствительным к регистру, то есть объекты с именами Var1
и VAR1
— это один и тот же объект.
Структура описания схемы на VHDL выглядит следующим образом:
- Подключаемые библиотеки
- Описание сущности (англ. entity)
- Описание архитектуры
Рассмотрим каждый из элементов отдельно на примере описания элемента И, представленного ниже. Если текст начинается с двойного дефиса — это комментарий к коду.
Библиотека IEEE — это набор стандартных объектов языка описания, которые утверждены международным институтом электроинженерии. STD_LOGIC_1164 подключается для того, чтобы воспользоваться необходимыми типами данных и возможными операциями над ними. В VHDL один из наиболее распространенных является тип std_logic. Его название буквально означает "стандартная логика". Этот тип определяет возможные логические значения, такие как логический ноль и логическую единицу. Подробнее об этом типе будет описано в следующих разделах.
Поле entity предназначено для описания программу как черного ящика, как ее мог бы видеть пользователь этой схемы. В ней содержится поле port, в нем определяются входы, выходы и их параметры. В данном примере система имеет два однобитных входа и один выход, все инициализированы типом std_logic.Обратите внимание - так как запись по сути является перечислением в скобках, то после последней записи отсутствует точка с запятой - это частая орфографическая ошибка у начинающих.
Блок архитектура (architecture) своим названием отражает свое назначение: в нем производится описание архитектуры программы. Внутри архитектуры между ключевыми слова begin и end описывается поведение схемы. В представленном коде демонстрируется простое описание путем присвоения в выход логической схемы результата логической операции И над двумя входами схемы. В языке VHDL символы "<=" подразумевают операцию присвоения, называемой неблокирующее присваивание (Nonblocking Assignment).
В результате анализа данного описания САПР Vivado превратит его в следующую схему:Результат компиляции кода
В языке VHDL существует набор зарезервированных слов, которые обозначают логические операции, вот некоторые из них:
конструкция в VHDL | описание |
---|---|
not
|
НЕ |
and |
И |
or |
ИЛИ |
xor |
Исключающее ИЛИ |
Используя их можно буквально описывать уравнениями необходимые логические схемы. При их использовании необходимо учитывать порядок выполнения операций, который задается с помощью скобок. Например:
... 1 architecture mux_struct of mux1 is 2 begin 3 y <= x0 and x1; 4 y2 <= (x0 and not(a)) or (x1 and a); 5 end mux_struct;
2.1. Список зарезервированных слов в VHDL
Каждый объект, создаваемый при описании схемы на VHDL, имеет свое имя, которое ему придумывает разработчик. В этой задаче основным правилом является запрет на использование зарезервированных слов, то есть слов, которые являются частью языка VHDL, а, следовательно, использование этих же слов для именования объектов может вызвать неоднозначное поведение инструментов анализа и синтеза схем вплоть до ошибок компиляции, из текстовых описаний которых очень трудно понять в чем же дело. НЕ ИСПОЛЬЗУЙТЕ эти слова для именования объектов в описании схемы.
Согласно стандарту языка VHDL STD 1076-2008 зарезервированы следующие слова:
abs | access | after | alias | all |
and | architecture | array | assert | assume |
assume_guarantee | attribute | begin | block | body |
buffer | bus | case | component | configuration |
constant | context | cover | default | disconnect |
downto | else | elsif | end | entity |
exit | fairness | file | for | force |
function | generate | generic | group | guarded |
if | impure | in | inertial | inout |
is | label | library | linkage | literal |
loop | map | mod | nand | new |
next | nor | not | null | of |
on | open | or | others | out |
package | parameter | port | postponed | procedure |
process | property | protected | pure | range |
record | register | reject | release | rem |
report | restrict | restrict_guarantee | return | rol |
ror | select | sequence | severity | signal |
shared | sla | sll | sra | srl |
strong | subtype | then | to | transport |
type | unaffected | units | until | use |
variable | vmode | vprop | vunit | wait |
when | while | with | xnor | xor |
3. Signal и неблокирующее присвоение
Помимо непосредственного описания взаимодействия между портами существуют тип объектов под названием signal. Данные объекты инициализируются в архитектуре до начала описания самой архитектуры. Пример применения объектов типа сигнал приведен ниже:
Сигнал обычно называют аналогией "провода" — при помощи таких объектов как при помощи проводов можно соединять между собой операции над входами и другие компоненты или выходы.
!NB на примере кода выше можно заметить, что сначала "провод" подключается к выходу, а затем только к операции логического И между входными портами. Результат синтеза этого кода не будет ничем отличаться от предыдущего примера: это демонстрирует главный нюанс, который часто путает начинающих: VHDL — это язык описания аппаратуры, а не язык программирования; порядок строк в этом примере не влияет на результат. Об этом говорит само название вида присвоения которое используется при "передаче" данных справа от выражения в объект слева: процедура "передачи" не блокирует выполнение строк ниже, а значит они выполняются "одновременно".
4. Типы данных VHDL
- std_logic
- std_logic_vector
1 my_bus_out1(0) <= '1';
2 my_bus_out1(3 downto 1) <= (others => '0');
3 my_bus_out1 (6 downto 4) <= "010";
Типы данных
Тип данных std_logic_vector необходим прежде всего для интерпретации физического сигнала. Такой тип не подходит для математических операций. Внутри самого блока мы можем оперировать исключительно двоичными значения, для этого в VHDL используются различные типы данных, в том числе и из других библиотек ieee:
- integer
- другие (natural, positive, bit,...)
- unsigned (библиотека numeric_std)
- signed (библиотека numeric_std)
Рассмотрим основные типы данных.
unsigned, signed, integer
Эти типы данных предназначены для возможности проведения математических операций. Подключаются эти типы данных с использованием библиотеки numeric_std, в которой определены результаты математических операций для данных типов.
Типы unsigned и signed - это двоичные типы данных, которые по способу инициализации схожи с std_logic_vector. Unsigned является беззнаковым типом данных, в то время как signed - знаковым.
<...> signal sum1 : unsigned(7 downto 0) :="10110100"; --8bit unsigned signal sum2 : signed(3 downto 0) :=4x"F"; --4bit signed <...>
При инициализации сигнала данного типа его битность четко определена в скобках; в примере сигналы sum1 и sum2 однозначно определены как шины на 8 и 4 бит соответственно. После указания типа данных и его битности появилась еще одна запись с так называемым "моржовым" оператором (двоеточие равно). Данная запись указывает значение сигнала в нулевой момент времени. В верхней строке показан пример инициализации в двоичном виде, а инициализация сигнала sum2 показана с использованием шестнадцатеричного числа F.
Тип данных integer является целочисленным типом данных, однако, при инициализации его битность по умолчанию 32, если только не указаны ограничения при создании сигнала:
<...> signal count1 : integer; --32bit signal count2 : integer range 0 to 3; --2bit <...>Запись range 0 to 3 сообщает, что создаваемый сигнал не может принимать значения вне указанного диапазона [0;3]. Исходя из этих данных синтезатор определит, что для данного диапазона достаточно двух бит.
Другие типы данных
В языке VHDL определены и другие, менее популярные типы данных. К примеру, positive — это подвид типа integer, который может принимать только положительные значения от 1 до 232. Тип natural — подтип integer, который может принимать неотрицательные значения: от 0 до 232.
Константы
При описании различных схем существуют ситуации, где используются конкретные числа, с которыми выполняются математические или логические операции. Так как использование так называемых "магических чисел" является плохой практикой, существуют константы. Пример использования константы:
1 library ieee; 2 use ieee.std_logic_1164.all; 3 use ieee.numeric_std.all; 4 5 entity const_use is 6 port( 7 s1 : in std_logic; 8 s2 : in std_logic_vector(7 downto 0); 9 s3 : out std_logic 10 ); 11 end; 12 13 architecture rtl of const_use is ; 14 constant s2_check : std_logic_vector(7 downto 0) :="11001010"; 15 16 begin 17 18 process(all) 19 begin 20 if s1 = '1' then 21 s2 <= s2_check; 22 else 23 s2 <= (others =>'0'); 24 end if; 25 end process; 26 end rtl;
В строке 14 продемонстрирован пример создания константы и инициализация её значения. Аналогично сигналам константы могут быть любого типа данных
Массивы
При работе с данными зачастую неудобно создавать множество сигналов или констант с индексом в имени. На помощь приходит возможность создания массивов. Для создания массива необходимо создать тип данных, а затем создать сигнал или константу с этим типом данных. Для этого используется команда type:
type <type_name> is array (range) of <type>;
В данной команде <type_name> — это название типа данных, range - это размер массива, а <type> — это указание типа данных каждого из элементов массива. Рассмотрим пример:
<..> type my_array is array (0 to 5) of unsigned(7 downto 0); --creating array signal const_arr : my_array; -- creating array signal
Здесь создается тип my_array, этот тип подразумевает, что это массив из 6 значений (от 0 до 5) данных типа unsigned(7 downto 0).
Литература:
Signed and Unsigned in VHDL, Nandland: https://www.nandland.com/vhdl/examples/example-signed-unsigned.html
Data type conversion in VHDL, Doulos: https://www.doulos.com/knowhow/vhdl/vhdl-vector-arithmetic-using-numeric_std/
VHDL Arrays, Nandland: https://www.nandland.com/vhdl/examples/example-array-type-vhdl.html
5. Операторы присвоения по условию
Помимо простого присвоения значения сигналам существуют еще два вида:
- Conditional signal assignment (присвоение сигналу значения по условию)
- Selected signal assignment (присвоение сигналу значения по выбору)
Conditional signal assignment
Присвоение значения по условию аналогично оператору if-else
в высокоуровневых языках программирования. Синтаксис выглядит следующим образом:
1 library ieee; 2 use ieee.std_logic_1164.all; 3 entity mux_mod is 4 port( 5 a_data : in std_logic_vector(3 downto 0); 6 b_data : in std_logic_vector(3 downto 0); 7 sel : in std_logic; 8 q_data : out std_logic_vector(3 downto 0) 9 ); 10 end entity; 11 architecture rtl of mux_mod is 12 begin 13 q_data <= a_data when sel = '0' else b_data; 14 end rtl;
Запись в строке 13 является присвоением по условию: на выход q_data
подаются значения со входа a_data
только при условии нулевого значения входа sel
, во всех других случаях на выход поступят значения со входа b_data
. Как можно догадаться такое описание соответствует поведению схемы мультиплексора:
Инструменты компиляции Quartus отображают несколько нюансов схемы: во-первых, т.к. сигналы многобитные, то на каждый бит информации свой мультиплексор, поэтому отрисована "пачка" мультиплексоров друг за другом. Во-вторых, сигнал sel
инвертирован, что подчеркивает пустая окружность на мультиплексоре. И, наконец, в-третьих, однобитный sel
управляет четырьмя мультиплексорами, поэтому он "размножен" на все из них.
Данная конструкция может каскадироваться, т.е. возможность множественного выбора как по бинарному дереву. Дополним схему выше еще одним входом условия и еще одним входом данных, например:
1 library ieee; 2 use ieee.std_logic_1164.all; 3 entity mux_mod2 is 4 port( 5 a_data : in std_logic_vector(3 downto 0); 6 b_data : in std_logic_vector(3 downto 0); 6 c_data : in std_logic_vector(3 downto 0); 7 sel0 : in std_logic; 7 sel1 : in std_logic; 8 q_data : out std_logic_vector(3 downto 0) 9 ); 10 end entity; 11 architecture rtl of mux_mod is 12 signal sels : std_logic_vector(1 downto 0); 13 begin 14 q_data <= a_data when sels = "00" else b_data 15 when sels = "01" else c_data; 16 sels <= sel1 & sel0; -- операция конкатенации 17 end rtl;
Данное описание синтезируется в следующую схему:
Теперь на схеме видно, что выход q_data определяется сразу двумя мультиплексорами
!NB Обратите внимание: каскадирование происходит путем подключения выхода мультиплексора ко входу другого. Получается, что при различных комбинациях управляющих сигналов данные будут проходить разные по количеству элементов пути. Принято считать, что у сигнала a_data выше приоритет, отсюда и название этого вида мультиплексирования — с приоритетом.
Selected signal assignment
Присвоение значения по выбору аналогично оператору switch-case
в высокоуровневых языках программирования. Синтаксис выглядит следующим образом:
1 library ieee; 2 use ieee.std_logic_1164.all; 3 entity mux_mod is 4 port( 5 a_data : in std_logic_vector(3 downto 0); 6 b_data : in std_logic_vector(3 downto 0); 7 sel : in std_logic; 8 q_data : out std_logic_vector(3 downto 0) 9 ); 10 end entity; 11 architecture rtl of mux_mod is 12 begin 13 with sel select 14 q_data <= a_data when '0', 15 b_data when others; 16 end rtl;
Запись в строках 14-15 является присвоением в порт q_data
по выбору sel
. Результат компиляции будет аналогичным первому варианту в начале страницы.
В случае же увеличения событий управления количество "выборов" увеличивается, запись выглядит следующим образом:
1 library ieee; 2 use ieee.std_logic_1164.all; 3 entity mux_mod2 is 4 port( 5 a_data : in std_logic_vector(3 downto 0); 6 b_data : in std_logic_vector(3 downto 0); 6 c_data : in std_logic_vector(3 downto 0); 7 sel0 : in std_logic; 7 sel1 : in std_logic; 8 q_data : out std_logic_vector(3 downto 0) 9 ); 10 end entity; 11 architecture rtl of mux_mod is 12 signal sels : std_logic_vector(1 downto 0); 13 begin 14 with (sels) select 15 q_data <= a_data when "00", 16 b_data when "01", 17 c_data when others; 18 sels <= sel1 & sel0; -- операция конкатенации 19 end rtl;
В таком описании схема уже не имеет приоритетов между состояниями селектора, поэтому данное описание синтезируется в следующую схему:
!NB Обратите внимание: теперь мультиплексоры уже не упакованы в "пачки", а сигнал управления подключен не сверху, а слева. Такой мультиплексор выбирает номер бита из входа
DATA
по порядковому номеру SEL
. Полезные ссылки:
Surf VHDL: concurrent conditional assignments https://surf-vhdl.com/vhdl-syntax-web-course-surf-vhdl/vhdl-concurrent-conditional-assignment/
6. Моделирование в Modelsim
Перед тем как двинуться далее стоит рассмотреть инструмент, используемый для моделирования описываемой схемы с целью проверки и отладки перед тем как запустить ее на отладочной плате.
Для проведения моделирования одним из наиболее популярных инструментов является Modelsim. В версиях Quartus после 18.0 доступен его аналог — Questasim, который хотя и называется по другому, имеет абсолютно тот же внешний вид и набор команд, дополненный набором продвинутых возможностей.
Графический интерфейс инструмента позволяет произвести моделирование с использованием всего нескольких кнопок. Рассмотрим этот инструмент подробнее.
Начальное окно Modelsim
Запустить инструмент можно прямо из меню Пуск.
Важно! По умолчанию бесплатная версия называется Modelsim Starter Edition. Однако, с инструментов Quartus устанавливается автоматически еще и платная версия и ею воспользоваться уже не получится.
Графический интерфейс представляет из себя следующие поля:
- Панель Transcript (консоль снизу)
- Список всех библиотек (панель Library)
- Панель меню управления моделированием в верхней части окна
Консоль имеет два назначения:
- Вывод промежуточных сообщений, связанных с выполняемыми операциями
- Возможность выполнения всех операций при помощи скриптов
Консоль поддерживает выполнение команд с использованиям языка TCL (этот язык часто встречается и в других инструментах разработки).
Для начала стоит достаточно знать, что строки, начинающиеся со знака #, являются комментариями. С этого знака как правило начинается вывод информационных сообщений (информация об ошибках, предупреждениях о проблемах и статусе моделирования и компиляции).
Панель библиотек (library) содержит наборы компонентов, в том числе vendor-specific, которые могут пригодиться при моделировании. Пользователи могут создавать свои библиотеки со своими модулями, ссылаясь на них на этапе подготовки к моделированию.
Панель меню и управления процессом моделирования содержит все инструменты, которые пригодятся при анализе процесса моделирования.
Порядок проведения моделирования HDL-кода.
Инструмент Modelsim имеет следующую идеологию проведения моделирования
- Создание рабочей (work) библиотеки, в которой будут находиться объекты моделирования
- Компиляция HDL-файлов
- Переход в режим моделирования модуля
При использовании графического интерфейса начать лучше всего со смены рабочей директории, чтобы временные файлы моделирования хранились в том же месте, где и исходные файлы. Для этого необходимо нажать File -> Change directory и выбрать директорию, в которой находятся файлы, с которыми в дальнейшем будет вестись работа.
Смена директории через графический интерфейс
Работа через графический интерфейс позволяет сразу перейти к компиляции файлов через меню Compile выбираем Compile... При компиляции первого исходного файла Modelsim предложит автоматически создать библиотеку work, в которую будут компилироваться моделируемые исходные файлы. Если данный вопрос не возник, то, скорее всего, в этой директории уже ранее создавалась рабочая библиотека и компилируемый файл будет в нее сразу добавлен.
Элементы графического интерфейса при компиляции исходных файлов
При компиляции в консоли появится информация о статусе компиляции и результатах анализа файла:
- В случае нахождения ошибки в коде инструмент укажет имя файла и номер строки с ошибкой, а также описание вида ошибки (на приведенном на изображении примере указана ошибка
- При успешной компиляции указывается количество ошибок 0 ( 0 Errors), а в списке библиотек появится work, внутри которой по имени entity указано скомпилированное hdl-описание схемы, которое можно раскрыть и увидеть имя архитектуры скомпилированного модуля
Пример вывода информации в консоль после компиляции исходного файла
После успешной компиляции исходного кода необходимо найти файл в списке библиотеки work, нажать по нему правой клавишей мыши и выбрать Simulate. После этого инструмент моделирования перейдет в режим моделирования, перестроив порядок некоторых инструментов.
Меню перехода в моделирование
Графический интерфейс в режиме моделирования
В режиме моделирования графический интерфейс оставляет консоль, но теперь раскрывает следующие панели:
- Панель sim с информацией о моделируемом объекте и всех элементах, которые в него включены
- Панель objects с информацией об объектах в выделенном в панели sim объекте (сигналы, константы, порты...)
- Панель processes перечисляет объекты типа process в выделенном объекте.
Главная задача моделирования — сымитировать необходимые воздействия на входы схемы (подать логические нули или единицы), запустить временной промежуток моделирования и проанализировать поведение выходов/внутренних сигналов на предмет ошибок.
Один из способов выполнить данную задачу — представить все объекты в графическом виде как в логическом анализаторе/осциллографе и визуально проинспектировать поведение.
Для этого необходимо выделить все объекты, нажать правой кнопкой мыши и выбрать Add Wave. После этого шага должно появиться окно или вкладка Wave.
Добавление на временную диаграмму объектов в графическом интерфейсе Modelsim
Обновленное состояние окна моделирования с вкладкой Wave
На этом этапе можно начать управление моделированием и обозначение воздействий на входы схемы:
- Для запуска моделирования на определенные участки времени в верхней панели есть элементы Run: указывается временной промежуток, который необходимо промоделировать за один шаг (на изображении это 100 ps), для выполнения одного шага моделирования выполняется команда Run (кнопка правее поля 100 ps), доступны также запуски моделирования на неопределенный промежуток времени (с остановкой по требованию/по событию в коде)
- Для проверки схемы необходимо создать воздействия на входы: для этого правой кнопкой мыши нажимаем по порту типа "вход" (у них зеленая стрелка слева от ромба), выбираем Force ... и в появившемся окне в двоичном виде задаем значение, которое будет дано на этот вход на ближайшие шаги моделирования
Этапы установления значения 10 на входе А
На изображении ниже приведен результат запуска нескольких шагов моделирования, изменения значения на одном из входов согласно изображениям выше и запуску еще нескольких шагов моделирования.
Временная диаграмма Modelsim после нескольких шагов и воздействий на входы
Последовательное изменение значений на входах при помощи Force и дальнейшее выполнение моделирования через команду Run позволит проследить за поведением сигналов и сделать выводы о работоспособности hdl-описания схемы.
*Использование скрипта для автоматизации моделирования
Стоит обратить внимание, что каждое взаимодействие через графический интерфейс повторено командой в консоли. Таким образом, можно не зная языка TCL скопировать все эти строки в один текстовый файл и всего лишь вызовом одного файла сразу выполнять те шаги, которые приходится постоянно друг за другом выполнять мышью или клавиатурой. Например:
Чтобы выполнить такой файл необходимо в консоли Modelsim вызвать команду: do do_compile.do
7. Конструкция process
Конструкция process — это дополнительная процедура, внутри которой можно использовать высокоуровневые операторы для описания логики.
Эта конструкция используется в коде следующим образом:
!NB: использование конструкции process не обязательно означает, что код реализует последовательную логику. Ниже будут рассмотрены примеры
Условное ветвление
В рамках конструкции process доступно использование привычной для высокоуровневых языков программирования — условного ветвления
if-else конструкция по своей логике близка к мультиплексору, ко входу адреса которого подключено условие ветвления (равенство cmd='1'), а в обоих ветках оператора описаны нулевое значение для выхода и первое значение. На схеме это будет выглядеть следующим образом:
Схема получилась комбинационная: внутри конструкции process покрыты все варианты комбинаций входов и выходов. Условия ветвления можно каскадировать используя последовательность if-elsif-else
Подобная вложенность ветвления может преобразиться как в мультиплексор с четырьмя входами данных, так и в каскад мультиплексоров.
7.1. Защелка на VHDL
!NB: Хотя защелка распространена в электронике, её использование в ПЛИС стараются избегать, а некоторые инструменты синтеза даже считают использование защелок ошибкой
Когда в конструкции process будут описаны не все ветви условного оператора, возникает вопрос: поведение какого логического элемента данное описание имитирует?
Warning (10631): VHDL Process Statement warning at simple_process.vhd(13): inferring latch(es) for signal or variable "q", which holds its previous value in one or more paths through the process
7.2. Триггер на VHDL
По аналогии с защелкой в условии ветвления можно использовать функцию rising_edge(clk) — эта функция возвращает состояние истины только в момент положительного перепада фронта сигнала clk. Простое описание триггера может выглядеть следующим образом:
В рамках этой конструкции можно добавить функционал комбинационных схем и начать реализовывать описание более сложных систем: счетчиков, конвейерных вычислений, детекторов событий и многих других. Ниже приведены примеры в обратном порядке: сначала задача и схема, которую может выполнить данную задачу, а затем код, с помощью которого можно синтезировать данную схему.
Сдвиговый регистр
Одна из наиболее стандартных визуализаций последовательной логики — это сдвиговый регистр: цепь последовательно соединенных друг за другом триггеров, фактически задерживающие распространение изменяющегося сигнала на входе первого триггера на время, равное длительности периода тактовой частоты умноженной на количество регистров в цепи. Подробнее можно поиграться со схемой на сайте: https://www.falstad.com/circuit/, выбрал Схемы ->Последовательные схемы -> Сдвиговые регистры -> SIPO. Пример такой схемы показан ниже ( можно не обращать внимания на буферы входов и выходов — это просто компоненты представления схемы после Technology map)
Поведение компонентов схемы можно описать временной диаграммой:
С каждым тактом генератора изменение на входе распространяется к выходу схемы, пока, наконец, через 5 тактов не проявится.
Простая версия исходного кода может выглядеть следующим образом:
В данном случае передача данных из "провода" а1 в "провод" а2 происходит только положительному фронту, что архитектурно подразумевает наличие триггера между этими проводами.
Выше приведена схема потому что визуальное представление на RTL Viewer имеет менее визуально очевидные межсоединения:
Здесь уже изображена "пачка" триггеров, назначенных на каждый "слой" из изображения выше. Эту же запись можно проинтерпретировать как попросту многобитное слово, внутри которого производится сдвиг каждого бита вправо или влево, а крайние значения отвечают за вход и выход. Пример такого решения с возможностью параметризации:
8. Несинтезируемые конструкции VHDL
Сами по себе языки описания аппаратуры имеют в себе как подмножество синтезируемых конструкций, т.е. тех, которые превращаются в схемы, так и набор не синтезируемых конструкций: эти конструкции могут быть использованы для описания поведения, которое будет выполняться при моделировании, но оно не имеет возможности превратиться в схему из логических элементов.
Тестбенч
Тестбенч (testbench) - это код, написанный на языке описания аппаратуры (как правило), используемый для создания воздействий на тестируемый код каких-либо воздействий и анализа результатов этих воздействий. По изначальной идее тестбенч - это стол, на котором стоит проверочное оборудование (генератор, блок питания, осциллограф, вольтметр и др.), которое настраивается под конкретную задачу, и на этот стол разработчик располагает элемент изделия, скажем, электрическую плату питания. Эту плату разработчик подключает проводами с одной стороны к оборудованию с другой стороны в соответствующие разъемы платы. После этого оборудование по очереди запускается с целью выявить потенциально возможные огрехи работы платы (плата не запускается, плата запускается и ничего не делает и др.).
картинка тестбенчка из презы елисея
Аналогично необходимо подготовить "стол" для проведения тестирования разрабатываемого кода.
Инициализация тестбенча
Текст кода инициализации тестбенча концептуально требует следующего: в архитектуре рядом с сигналами объявить о подключаемом компоненте (тестируемом коде), создать сигналы-"проводники", которыми будет произведено "подключение" ко входам и выходам тестируемого кода и в теле архитектуры произвести подключение. После этих операций можно писать сам код воздействий на блок.
Напишем тестбенч для следующего кода:
Как и у любого другого кода, у тестбенча неизменными остаются главные компоненты: объявление библиотек, описание entity и описание архитектуры.
library ieee;
use ieee.std_logic_1164.all;
entity no_sig_tb is
end entity;
architecture tb of no_sig_tb is
begin
end tb;
Однако, поскольку тестбенч подразумевает собой обертку вокруг тестируемого кода, входов и выходов у тестбенча может не быть, поэтому блок сущности можно оставить пустым. Добавим компонент и сигналы в архитектуру
library ieee;
use ieee.std_logic_1164.all;
entity no_sig_tb is
end entity;
architecture tb of no_sig_tb is
component no_sig is
port(
a1 : in std_logic;
a2 : in std_logic;
o1 : out std_logic
);
end component;
signal a1_sig : std_logic :='0';
signal a2_sig : std_logic:='0';
signal o1_sig : std_logic:='0';
begin
end tb;
конструкцией component производится объявление о том, какой внешний вид схемы, которую в данном тестбенче планируется подключать для работы, по сути это entity кода выше; сигналов понадобится столько же сколько и портов. Подключение компонента производится через создание уникальной копии компонента, instance, к которой описывается подключение сигналов.
Так же хорошее объяснение построения тестового окружения можно посмотреть по ссылке: https://fpgatutorial.com/how-to-write-a-basic-testbench-using-vhdl/
9. Конечные автоматы
Конечный автомат (так же встречается машина с конечным набором состояний, англ. finite state machine, fsm) — это концепция описания поведения последовательной логики с целью абстрагирования от структуры последовательных схем.
Термин исходит из теории автоматов (раздела дискретной математики), в которой существует понятие абстрактный автомат (abstract machine) — это теоретическая модель функционирования компьютерных систем. По аналогии с математическими функциями, абстрактный автомат также имеет набор входов, выходов, состояний и правил взаимодействия между входами, выходами и переходами:
Математическая функция \( y=f(x) \) | Абстрактный автомат |
---|---|
Аргументы (\( x \)) | множество входов |
функция (\( y \)) | множество выходов |
уравнение зависимости (\( f(x) \)) | модель поведения |
В литературе часто можно встретить следующее обозначение автомата:
\( A=(X,Y,S,F_y,F_s) \),
где:
\( X \) — множество входных сигналов автомата,
\( Y \) — множество выходных сигналов автомата,
\( S\) — множество состояний автомата,
\( F_y \) — функции, описывающие значения выходов,
\( F_s \) — функции, описывающие условия переходов между состояниями.
Когда множества \( X \), \( Y \) и\( S \) являются конечными, такие автоматы называют конечным автоматом (КА, англ. finite state machine, fsm). Здесь и далее мы будем оперировать этим термином.
Функции описания переходов реализуют так называемое бинарное отношение между множеством входных сигналов автомата и множеством состояний. Такое отношение определяет в какое состояние автомат перейдет в следующий (t+1) момент времени:
\( s(t+1) = f_s(x(t),s(t)) \),
где \( s\) — это состояние из множества \( S\).
Функции описания значений выходов характеризуют состояние выходных сигналов относительно множества входов и текущего состояния КА:
\( y(t) = f_y(x(t),s(t)) \),
где \( y\) — это выход из множества \( Y\).
Можно заметить, что описание переходов КА напоминает поведение последовательной логики, а описание выходов схемы — комбинационной логики. Именно эта связь и используется при абстракции описания последовательных схем при помощи КА. Исторически сам автомат представляется в виде черной коробки, на входах которой в каждый момент времени t имеется некоторое значение. Это значение преобразуется через функции переходов в значение следующего состояния КА и сигналов для выходных портов Y, определяемых также через функции.
Рассмотрим способы составления и представления КА на примере следующей задачи:
На четырехбитный вход схемы последовательно вводят числа для ввода пароля 4х-значного пароля. При правильной последовательности чисел необходимо разблокировать дверь и зажечь зеленый светодиод, после чего выключить светодиод и закрыть дверь. При неправильной последовательности зажечь красный светодиод.
Назовем автомат, который будем описывать далее, \( FSM \). Примем, что
В множество входов \( X \) конечного автомата, описывающего такое поведение, входит только четырехбитных вход чисел. Обозначим его dataIn.
Множество выходов \( Y \) — это зеленый светодиод (ledg), красный светодиод (ledr), и сигнал открытия двери (doorOpen).
Множество состояний автомата \( S \) перечислить сложнее. Для начала точно известно, что есть начальное состояние (\(\(S_0\)), в котором принимается первое значение пароля. Так же можно сделать вывод, что должно быть состояние открытия двери ( \( S_{open}\)) и состояние ошибки ( \( S_{err}\)). В состояние \( S_{open}\) можно попасть только если все четыре значения введены верно, в состояние ошибки нужно попасть если хотя бы одно из значений было введено неверно, но только после ввода всей последовательности.
Определив часть состояний уже можно описать функции для выходов уравнениями булевой алгебры:
\( ledg = (FSM == S_{open}) \)— зеленый светодиод загорится только когда КА будет в состоянии открытия двери, во всех других случаях будет выключен;
\( doorOpen = (FSM == S_{open}) \) — дверь откроется только когда КА будет в состоянии открытия двери;
\( ledr = (FSM = S_{err}) \) — красный светодиод загорится только когда КА будет в состоянии ошибки.
При составлении конечных автоматов существует несколько способов описания, одним из удобных и наглядных способов представления считается граф переходов. Пример такого графа для перечисленных выше состояний можно увидеть ниже:
Приведенный на рисунке граф можно интерпретировать следующим образом: "конечный автомат начинается в \( S_0\), если на входе dataIn символ a — КА переходит в состояние \( S_{open}\), из которого безусловно возвращается обратно в \( S_0\). Если на входе был любой другой символ, КА переходит в \( S_{err}\), из которого безусловно возвращается обратно в \( S_0\)".
Исходя из этого описания можно понять, что полный функционал задачи не реализован: в данном графе обрабатывается только первый символ пароля, после которого дверь либо открыта либо нет. Для обработки следующих символов можно добавить дополнительные состояния проверки следующих символов, а также состояний ожидания ввода оставшихся символов когда первый уже оказался неверным.
9.1. КА на ПЛИС
Концепция конечных автоматов широко применяется в программировании, отлично зарекомендовав себя как модель абстракции над последовательной логикой. Общее представление конечного автомата выглядит следующим образом:
[здесь картинка из понга/харрисов где состояние регистр выходы]
Самостоятельное составление подобной структуры по абстрактному описанию вызывает большие проблемы, поэтому в языках описания аппаратуры продумана последовательность использования конструкций, которые позволяют инструментам компиляции и моделирования не только представить в виде последовательной логики, но и извлечь информацию в виде графа конечного автомата и предоставить его пользователю для визуальной верификации корректности описанного поведения.
Конструкции VHDL
Для описания КА в языке VHDL используется конструкция case, собственный тип данных и rising_edge(clk): каждый "случай" по сути своей подразумевает наличие "состояния", в котором может находиться схема, а благодаря тактовому сигналу реализуется дискретность времени. Конструкция case использует инициализированный сигнал любого типа в качестве входного аргумента ветвления и в зависимости от его текущего значения выполняет одну из ветвей. Хотя использование этого оператора соответствует логике поведения КА, у него имеется ряд ограничений:
- Использование номеров в качестве состояний в случае типов данных integer/unsigned/... легко может запутать
- Такой способ описания не распознается инструментами компиляции и моделирования как конечный автомат (а это важно)
Описание конечного автомата на VHDL принято использовать путем создания собственного типа данных, относящегося к перечисляемым (аналог enum), и затем создание сигнала этого типа для дальнейшего использования в конструкции case. Полученная реализация для описанного ранее конечного автомата будет выглядеть следующим образом:
9.2. Возможности визуализации КА
Отображение графов в Modelsim/Questasim/Quartus
Многие современные инструменты работы с HDL умеют распознавать правильно описанные конечные автоматы и изображать по ним графы переходов. Далее будут рассмотрены следующие варианты, которые несложно использовать:
- VS Code + плагин Teros HDL
- Modelsim/Questasim с дополнительными настройками
- инструмент State Machine Viewer, встроенный в Quartus
VS Code + Plugin
Самый простой и быстрый инструмент — это редактор VS Code с установленным в нем расширением Teros HDL. Для визуализации конечного автомата необходимо нажать на соответствующую клавишу
Modelsim/Questasim
Распознавание конечного автомата в инструментах моделирования — это дополнительная возможность, поэтому требует использование скриптовых команд
- При компиляции файла необходимо добавить флаг +cover=f:
vcom -work work passwd_fsm.vhd +cover=f
В отчете компиляции появится следующее дополнительное сообщение:
# ** Note: (vcom-143) Recognized 1 FSM in architecture body "passwd_fsm(rtl)".
- При запуске моделирования необходимо использовать флаг -fsmdebug:
vsim -fsmdebug work.passwd_fsm
В результате после запуска моделирования необходимо включить вкладку FSM через меню View, перейти на нее и лицезреть КА, найденные в коде. Нажав на один из них (в текущем случае на единственный) справа откроется диаграмма переходов