В качестве примера приведем проекты на VHDL, реализующие при помощи различных форм VHDL-описаний простейший одноразрядный сумматор с учетом переноса (см. рисунок), а также сумматор с произвольной разрядностью.
Двоичный одноразрядный сумматор с переносом
Определим необходимые элементы для реализации одноразрядного сумматора с переносом — это элементы and, or и xor. Реализуем данные элементы в виде библиотеки gates:
library IEEE;
use IEEE.std_logic_1164.all;package gates is
component and_2
generic (tpd_h1 : time := 1 ns;
tpd_lh : time :=1 ns);
port (in1, in2 : std_ulogic;
out1 : out std_ulogic);
end component;component or_2
generic (tpd_h1 : time := 1 ns;
tpd_lh : time := 1 ns);
port (in1, in2 : std_logic;
out1 : out std_logic);
end component;component xor_2
generic (tpd_hl : time := 1 ns;
tpd_lh : time := 1 ns);
port (in1, in2 : std_logic;VHDL Basics
out1 : out std_logic);
end component;
end gates;library IEEE;
use IEEE.Std_Logic_1164.all;
—————————
— Элемент «И»
—————————
entity and_2 is
generic (tpd_hl : time := 1 ns;
tpd_lh : time := 1 ns);
port (in1, in2 : std_ulogic;
out1 : out std_ulogic);
end and_2;architecture only of and_2 is
begin
p1: process (in1, in2)
variable val : std_ulogic;
begin
val := in1 and in2;
case val is
when ‘0’ => out1 out1 out1 out1 out1 out1 out1 out1 a, in2 => b, out1 => xor1_out);
xor2: xor_2 port map (in1 => xor1_out, in2 => cin, out1 => sum);
and1: and_2 port map (in1 => a, in2 => b, out1 => and1_out);
or1: or_2 port map (in1 => a, in2 => b, out1 => or1_out);
and2: and_2 port map (in1 => cin, in2 => or1_out, out1 => and2_out);
or2: or_2 port map (in1 => and1_out, in2 => and2_out, out1 => cout);
end structural;
В качестве примера использования настройки приведем реализацию сумматора с последовательным переносом с произвольной (определяемой пользователем) разрядностью, в котором в структурном описании используется разработанный ранее простейший сумматор:
- Разработка модели двоичного одноразрядного сумматора с переносом — Active-HDL
- Проект и структура описания объектов в VHDL
- Задачи, решаемые применением языка VHDL для анализа и синтеза дискретных систем
- Моделирование цифрового автомата управления лифтом — Active-HDL
- Принципы моделирования на языке VHDL
- Применение пакета прикладных программ Aldec Active-HDL 5.1 для моделирования на VHDL
- Скалярные типы данных в VHDL
- Функции и процедуры, пакеты в VHDL
- Введение в VHDL
- Схемы сумматоров (с разными переносами на 2-ом ярусе)
- Составные (композитные) типы: массивы и записи в VHDL
- Математические основы анализа и синтеза дискретных систем
- Процесс моделирования и краткий обзор видов моделирования
- Графический редактор схем в Aldec Active-HDL
- Общие принципы расчета надежности структурных схем электростанций
- Типы данных в VHDL
- Операторы управления в VHDL
Источник: all4study.ru
How to create your first VHDL program: Hello World!
Введение в язык описания аппаратуры: начало работы с VHDL для проектирования цифровых схем
В данной статье рассматривается VHDL, язык описания аппаратного обеспечения, и то, как он структурируется при описании цифровых схем. Мы также рассмотрим некоторые вводные примеры описаний схем и коснемся различий между типами данных » std_logic » и » bit «.
VHDL является одним из широко используемых языков описания аппаратуры (HDL, Hardware Description Language). VHDL означает язык описания аппаратуры VHSIC. В свою очередь, VHSIC означает высокоскоростная интегральная микросхема (Very-High-Speed Integrated Circuit).
Создание VHDL было инициировано Министерством обороны США в 1981 году. Сотрудничество таких компаний, как IBM и Texas Instruments, привело к выпуску первой версии VHDL в 1985 году. Xilinx, которая выпустила первую микросхему FPGA в 1984 году, вскоре поддержала VHDL в своих продуктах. С тех пор VHDL превратился в развитый язык в области проектирования, моделирования и синтеза цифровых схем.
В данной статье мы вкратце обсудим общую структуру кода VHDL при описании заданной схемы. С помощью некоторых вводных примеров мы также познакомимся с некоторыми широко используемыми типами данных, операторами и так далее.
Общая структура кода VHDL
Рассмотрим простую цифровую схему, показанную на рисунке 1.
На этом рисунке показано, что есть два входных порта (a и b) и один выходной порт (out1). На рисунке показано, что входные и выходные порты имеют ширину один бит. Функциональность схемы – это выполнение логической операции И двух входов и вывод результата на выходной порт.
VHDL использует похожее описание; однако он имеет свой собственный синтаксис. Например, для описания входных и выходных портов этой схемы используются следующие строки кода:
entity circuit_1 is Port ( a : in STD_LOGIC; b : in STD_LOGIC; out1 : out STD_LOGIC); end circuit_1;
Давайте по очереди разберемся, что это значит.
Строка 1. Первая строка указывает произвольное название для описываемой схемы. Слово « circuit_1 », которое находится между ключевыми словами « entity » и « is », определяет имя этого модуля.
Строки с 2 по 4. Эти строки определяют входные и выходные порты схемы. Сравнивая эти строки со схемой, изображенной на рисунке 1, мы видим, что порты схемы вместе со своими функциями перечислены после ключевого слова « port ». Например, в строке 3 говорится, что у нас есть порт под названием « b ». Этот порт является входом, как указано ключевым словом « in » после двоеточия.
Что указывает ключевое слово « std_logic »? Как мы обсудим далее в этой статье, std_logic является в VHDL широко используемым типом данных. Его можно использовать для описания однобитового цифрового сигнала. Поскольку все порты входов/выходов на рисунке 1 передают единицу или ноль, мы можем использовать для этих портов тип данных std_logic .
Строка 5. Эта строка определяет конец оператора « entity ».
Следовательно, фрагмент кода entity определяет 1) название описываемой схемы и 2) порты схемы вместе со своими характеристиками, а именно: вход/выход и тип данных, которые должны передаваться этими портами. Фактически фрагмент кода entity описывает интерфейс модуля с окружающей его средой. Функции схемы, указанные в обсуждаемом операторе « entity », показаны на рисунке 1 зеленым цветом.
В дополнение к интерфейсу схемы с окружающей средой нам необходимо описать работу схемы. На рисунке 1 функциональность схемы – это выполнение логической операции И двух входов и вывод результата на выходной порт. Чтобы описать работу схемы, VHDL добавляет секцию « architecture » и связывает её с circuit_1 , определенной оператором entity . Код VHDL, описывающий архитектуру этой схемы, будет следующим.
architecture Behavioral of circuit_1 is begin out1
Строка 6. Эта строка дает название, « Behavioral », для архитектуры, которая будет описана в следующих строках. Это имя встречается между ключевыми словами « architecture » и « of ». Данная строка также связывает эту архитектуру с « circuit_1 ». Другими словами, эта архитектура будет описывать работу « circuit_1 ».
Строка 8. Указывает начало описания архитектуры.
Строка 9. Строка 9 использует синтаксис VHDL для описания работы схемы. Логическая операция И (AND) двух входов находится в круглых скобках, а результат присваивается выходному порту с помощью оператора присваивания «
Строка 10. Указывает конец описания архитектуры. Как упоминалось выше, эти строки кода описывают внутреннюю работу схемы, которая здесь представляет простой логический элемент И (на рисунке 1 показан синим цветом).
Объединив то, что мы обсудили ранее, мы закончим описание « Circuit_1 » в VHDL. Мы получим следующий код:
entity circuit_1 is Port ( a : in STD_LOGIC; b : in STD_LOGIC; out1 : out STD_LOGIC); end circuit_1; —————————————————— architecture Behavioral of circuit_1 is begin out1
Однако нам еще нужно добавить еще несколько строк кода. Эти строки добавят библиотеку, содержащую некоторые важные определения, включая определения типов данных и операторов. Библиотека может состоять из нескольких пакетов (смотрите рисунок 2 ниже). Нам нужно сделать необходимый пакет(ы) данной библиотеки видимыми для проекта.
Поскольку в приведенном выше примере используется тип данных « std_logic », нам нужно добавить в код пакет « std_logic_1164 » из библиотеки « ieee ». Обратите внимание, что логические операторы для типа данных std_logic также определены в пакете « std_logic_1164 », иначе нам пришлось бы сделать соответствующий пакет видимым для кода. Окончательный код будет следующим:
library ieee; use ieee.std_logic_1164.all entity circuit_1 is Port ( a : in STD_LOGIC; b : in STD_LOGIC; out1 : out STD_LOGIC); end circuit_1; architecture Behavioral of circuit_1 is begin out1
Здесь мы добавляем две новые строки перед тем, что уже написали. Первая строка добавляет библиотеку « ieee », а вторая строка указывает, что требуется пакет « std_logic_1164 » из данной библиотеки. Поскольку « std_logic » является широко используемым типом данных, нам почти всегда нужно добавлять в VHDL код библиотеку « ieee » и пакет « std_logic_1164 ».
Для проверки приведенного выше VHDL кода мы можем использовать симулятор Xilinx ISE.
Теперь, когда мы познакомились с основными блоками кода VHDL, давайте рассмотрим один из самых важных типов данных VHDL, т.е. тип данных « std_logic ».
Тип данных « std_logic » (против « bit »)
Как упоминалось выше, тип данных « std_logic » может использоваться для представления однобитового сигнала. Интересно, что существует еще один тип данных VHDL, « bit », который может принимать логическую единицу или логический ноль.
Итак, зачем нам нужен тип данных std_logic , если тип данных « bit » уже охватывает высокое и низкое логические состояния цифрового сигнала? Ну, цифровой сигнал на самом деле не ограничивается высоким логическим уровнем и низким логическим уровнем. Рассмотрим инвертор с тремя состояниями, показанный на рисунке 3.
Когда на «включен» высокий логический уровень, «выход_данных» подключается либо к Vdd, либо к земле; однако, когда на «включен» низкий логический уровень, «выход данных» висит в воздухе, то есть он не имеет низкоимпедансного соединения с Vdd или землей, а вместо этого представляет для внешней схемы «высокий импеданс». Тип данных « std_logic » позволяет нам описывать цифровой сигнал в режиме с высоким импедансом, присвоив ему значение ‘ Z ‘.
Существует еще одно состояние, то есть к дополнение в высокому логическому уровню, низкому логическому уровню и высокому импедансу, которое может быть использовано при проектировании цифровых схем. Иногда нам неважно значение на конкретном входе. В этом случае представление значения сигнала с «неважным значением» может привести к более эффективному проектированию. Тип данных « std_logic » поддерживает состояние «неважное значение». Это позволяет улучшить аппаратную оптимизацию для таблиц поиска.
Тип данных « std_logic » также позволяет нам представить неинициализированный сигнал, присвоив ему значение ‘ U ‘. Это может быть полезно при моделировании фрагмента VHDL кода. Оказывается, что тип данных « std_logic » фактически может принимать девять значений:
- ‘ U ‘: не инициализировано;
- ‘ 1 ‘: обычный индикатор высокого логического уровня, также известный как «принудительный высокий логический уровень»;
- ‘ 0 ‘: обычный индикатор низкого логического уровня, также известный как «принудительный низкий логический уровень»;
- ‘ Z ‘: высокий импеданс;
- ‘ — ‘: неважное значение;
- ‘ W ‘: слабое неизвестное;
- ‘ X ‘: неизвестное;
- ‘ H ‘: слабая ‘ 1 ‘ (имеется в виду слабый ток);
- ‘ L ‘: слабый ‘ 0 ‘ (имеется в виду слабый ток).
Среди этих значений мы обычно используем ‘ 0 ’, ‘ 1 ’, ‘ Z ’ и ‘ — ’.
Давайте посмотрим пример.
Пример 1
Напишем VHDL код для схемы на рисунке 4.
Основная процедура почти такая же, как в предыдущем примере. Код будет выглядеть следующим образом:
library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity circuit_2 is Port ( a : in STD_LOGIC; b : in STD_LOGIC; c : in STD_LOGIC; d : in STD_LOGIC; out1 : out STD_LOGIC; out2 : out STD_LOGIC); end circuit_2; architecture Behavioral of circuit_2 is signal sig1: std_logic; begin sig1
Строки 1 и 2. Эти строки добавляют в код необходимые библиотеку и пакет. Поскольку используется тип данных « std_logic », мы должны добавить пакет « std_logic_1164 ».
Строки 3-10. Эти строки определяют имя модуля вместе с его входными/выходными портами. Этот фрагмент кода соответствует частям зеленого цвета на рисунке 4.
Строки 11-17. Эта часть кода описывает работу схемы (т.е. части синего цвета на рисунке 4). Как вы могли заметить, на рисунке 4 есть один внутренний узел; он обозначен как « sig1 ». Мы используем оператор « port » из « entity » для определения входных/выходных портов, но как мы можем определить внутренние узлы схемы? Для этого мы используем ключевое слово « signal ».
В строке 12 вышеприведенного кода ключевое слово « signal » сообщает программному обеспечению, что в схеме есть узел, помеченный как « sig1 ». Подобно определению портов, мы используем ключевое слово « std_logic » после двоеточия, чтобы указать требуемый тип данных. Теперь мы можем присвоить значение этому узлу (строка 14) или использовать его значение (строка 15).
Пример 2
Напишем VHDL код для схемы на рисунке 5.
Данная схема представляет собой мультиплексор два-к-одному. Когда на « sel » высокий логический уровень, выход нижнего логического элемента И будет в низком логическом состоянии независимо от значения на « b ». Мы можем сказать, что логический элемент И предотвращает распространение « b » на « sig2 ». С другой стороны, поскольку на « sel » высокий логический уровень, выход верхнего логического элемента И будет соответствовать « a ». Или, что то же самое, « a » достигнет « sig3 ». Поскольку в этом случае « sig2 » равен логическому нулю, то на выходе логического элемента ИЛИ будет такое же состояние, как у « sig3 ». Следовательно, когда на « sel » высокий логический уровень, « out1 » будет таким же, как « a ».
Аналогичное обсуждение покажет, что, когда на « sel » низкий логический уровень, « out1 » будет принимать значение « b ». Следовательно, исходя из значения « sel », мы можем позволить значению на одном или другом входе достичь выхода. Это называется мультиплексированием, а схема называется мультиплексором.
VHDL для начинающих. Brainfuck
Как известно, VHDL – высокоуровневый язык описания аппаратуры (если это вызывает сомнения, можно посмотреть здесь и здесь). Из всего разнообразия задач мне приглянулся именно brainfuck благодаря лёгкости в реализации с одной стороны и волшебству создания программируемого (пусть и весьма ограниченно) вычислителя с другой.
В рамках данной статьи я не буду углубляться в дебри синтаксиса и настройки среды, сконцентрировавшись на реализации конкретной задачи.
Испытательным стендом будет Altera Cyclone II Starter Kit (EP2C20F484C7)
Любителей мигающих лампочек прошу под кат.
Техническое задание
- Память команд — 64 команды, ячейки памяти — 32 ячейки по 8 бит каждая;
- Устройство должно поддерживать два режима: занесения программы и выполнения; смена режима должна осуществляться при помощи переключателя SW9;
- В режиме занесения программы переключатели SW8 – SW3 определяют адрес в памяти программ, SW2 – SW0 — код команды; запись в память осуществлятся при нажатии кнопки KEY3; содержимое текущей ячейки памяти отображается на свтодиодах LEDR2 – LEDR0;
- В режиме выполнения программы значения ячеек памяти должны отображаться на семисегментных индикаторах HEX1 – HEX0; адрес отображаемой ячейки должен задаваться при помощи переключателей SW4 – SW0;
- В любом режиме работы по нажатию кнопки KEY3 значения всех ячеек памяти должны обнуляться.
Разметка фронта работ
Проект в Quartus II создан, самое время определиться с набором entity. Я решил не выделяться и, пусть это и не очень красиво, реализовать всё в одной сущности. Для вывода на семисегментные индикаторы понадобится специальный дешифратор, который выделим в отдельный entity.
Реализация
Дешифратор можно реализовать сразу, «в лоб». Он выполняет простейшее преобразование, задаваемое таблично, поэтому портов всего два:
entity dc7x is port( i: in std_logic_vector(3 downto 0); z: out std_logic_vector(6 downto 0) ); end dc7x;
Модель поведения задаётся просто:
with i select z
Перейдём непосредственно к интерпретатору.
Порты
Задействованные внешние устройства ввода и вывода показаны на рисунке:
Как видно, нужен доступ к тумблерам SW, кнопкам KEY, светодиодам LED и семисегментникам HEX. Сигнал синхронизации будет вырабатывать внутренний генератор 50Mhz.
entity brainfuck is port( RUN: in std_logic; SW: in std_logic_vector(8 downto 0); LED: out std_logic_vector(2 downto 0); HEX1: out std_logic_vector(6 downto 0); HEX2: out std_logic_vector(6 downto 0); clk: in std_logic; RESET: in boolean ); end brainfuck;
RUN — тот самый переключатель режима работы SW9, RESET — кнопка KEY3.
Архитектура
Нам понадобится несколько внутренних элементов: массивы памяти команд и данных, а также указатели на конкретные ячейки в них.
Так как на индикаторах требуется показывать не только вывод программы, но и содержимое каждой конкретной ячейки памяти, использовано два вектора: out_result содержит вывод программы, а final_out_result подключён к дешифраторам семисегментных индикаторов.
type t_memory is array (31 downto 0) of std_logic_vector (7 downto 0); — command memory signal cell_memory: t_memory := (others => x»00″); type d_memory is array (63 downto 0) of std_logic_vector (2 downto 0); — cells memory signal comm_memory: d_memory := (others => «000»); signal comm_number: std_logic_vector(6 downto 0) := (others => ‘0’); signal cell_number: std_logic_vector(5 downto 0) := (others => ‘0’); signal out_result: std_logic_vector(7 downto 0) := (others => ‘0’); signal final_out_result: std_logic_vector(7 downto 0) := (others => ‘0’);
Process (clk, RESET)
Наконец подобрались к самому главному — модели поведения интерпретатора. Для начала объявим переменную-счётчик открытых скобок.
variable u: integer := 0;
Для нормальной работы с циклами это должна быть именно переменная, а не сигнал. Главное отличие первого от второго в том, что значение в сигнал записывается по окончании выполнения процесса, а в переменную — непосредственно в момент присваивания.
begin if rising_edge(clk) then if (not RESET) then cell_memory x»00″); out_result ‘0’); final_out_result
По сигналу сброса (кнопки в Cyclone II инверсные, поэтому и условие инверсное) обнуляем значения ячеек памяти и выходной вектор, а если при этом ещё и идёт запись программы, заполняем соответствующую ячейку памяти команд.
else if (RUN = ‘0’) then running_led ‘0’); cell_number ‘0’); cell_memory x»00″);
В любом случае при выходе из режима выполнения необходимо «забывать» о предыдущих результатах, чтобы каждый следующий запуск происходил «с нуля».
else — executing running_led ‘0’); if (SW(5) = ‘1’) then final_out_result
Выбр вывода: на дешифраторы подаётся либо выход программы, либо значение из текущей ячейки. Выбор осуществляется с помощью тумблера SW5.
case comm_memory(conv_integer(unsigned(comm_number))) is when «000» => — next if (u = 0) then cell_number — [ if ((cell_memory(conv_integer(unsigned(cell_number))) = x»00″) or (u /= 0)) then u := u + 1; end if; if (u < 0 )then comm_number — ] if ((cell_memory(conv_integer(unsigned(cell_number))) /= x»00″) or (u /= 0)) then u := u — 1; end if; if (u < 0 )then comm_number — stop if (u = 0) then null; end if;
Программа на Brainfuck представляется как автомат: есть набор фиксированных состояний, перемещение между которыми осуществляется (за исключением циклов) линейно. Такая модель на VHDL (да и не только) реализуется switch-case конструкцией.
Как уже говорилось, u — счётчик открытых скобок. Команды выполняются только при (u = 0), в остальных случаях происходит поиск парной скобки. В нормальном режиме и при поиске закрывающей скобки указатель команд движется вперёд, иначе — назад. Здесь ясно видно, что если бы u была сигналом, при первой реакции на закрывающую скобку счётчик команд увеличился бы, только на следующем такте указатель пошёл бы назад, наткнулся на закрывающую скобку второй раз (u = -2), а такого количества парных открывающих скобок нет — программа никогда бы не выполнилась.
Условие (u /= 0) сделано для реализации вложенных циклов.
Testbench
Код готов и компилируется,
но перед прошивкой устройства надо протестировать алгоритм на адекватность. Текст тестбенча приводить не буду, он есть в прикреплённых файлах. Отмечу лишь, что тупой последовательный прогон всех значений здесь не подойдёт, поэтому проверяется корректность выполнения конкретной программы. Я использовал сложение двух чисел:
+++>++<[->+.x
В качестве среды моделирования использовалась ModelSim-Altera.
Разводка платы
Последний этап перед прошивкой — задание соответствий сигналов модели реальным портам платы. Координаты выводов есть в приложении «Документация Cyclone II», ну а кому лень — вот готовая распиновка:
Заключение
- Полностью готовый к прошивке (скомпилированный и разведённый по плате) проект
- Документация к Altera Cyclone II (с обозначением всех портов на координатной сетке)
P.S.
Приведённый код можно, конечно, использовать и для реализации на другом железе, для этого надо создать проект под конкретную плату и прикрепить к нему файлы исходников.
Источник: habr.com