Первое сентября традиционно ассоциируется с началом учебного года и возвращением в отдохнувшее за время летних каникул здание школы или иного учебного заведения за новыми знаниями. Как человек, давно закончивший школу, и по случайному стечению обстоятельств студент-заочник, волнующих переживаний в связи с началом календарной осени я не испытываю. Тем не менее, я тоже решил заняться изучением чего-нибудь нового, интересного и полезного. Тема была выбрана не самая простая – проектирование цифровых устройств, ПЛИС и языки описания аппаратуры (в частности, VHDL).
Интересно, что многие материалы по проектированию для ПЛИС, понятные абсолютному новичку вроде меня и доступные в русскоязычном сегменте интернета, связаны с проектом Марсоход, который развивается силами небольшой команды инженеров из Таганрога. Марсоход это не просто плата-конструктор и её старший и более серьёзный родственник в лице второй модели. Это сообщество, в котором найдётся место и опытным профессионалам, и "чайникам", только начавшим постигать все тонкости искусства проектирования цифровых схем.
Наверное, поэтому я захотел поделиться своим опытом изучения ПЛИС с аудиторией блога Марсоход. С программируемой логикой ранее мне сталкиваться не приходилось, а идея познакомиться с ПЛИС поближе возникла после изучения курса схемотехники – появилось желание почувствовать процесс разработки изнутри, а отсутствие практических навыков заставляет действовать именно в таком формате. Свои заметки – эту, и, надеюсь, будущие – я бы хотел представлять в виде "докладов" и "лабораторных работ", нарочно уделяя повышенное внимание возможно очевидным аспектам затронутой темы. Разумеется, любое описываемое устройство будет протестировано в плате Марсоход2, а соответствующие проекты для Quartus II будут доступны для загрузки.
Начинать знакомство с ПЛИС следует с "минимального проекта", который нужен, скорее, для соблюдения правил приличного тона. В конце семидесятых годов в прошлого века в учебнике по языку программирования Си "The C Programming Language" была предложена минимальная программа, выводящая на экран сообщение "Hello, world!". Что характерно, фраза эта стала традиционной и впоследствии получила распространение и в других учебниках, став первым шагом на тернистом пути начинающих программистов. Разумеется, от ПЛИС тоже можно добиться вывода хрестоматийного приветствия, причём не одним единственным способом. Между тем, такая задача оказывается более сложной и несколько выходит за рамки первого знакомства с программируемой логикой. Простейшим проектом для ПЛИС – своеобразным аналогом "Hello, world!" – станет мигающий светодиод, которых на плате Марсоход2 установлено четыре штуки. Есть на ней и две кнопки – их можно приспособить для управления огоньками.
Достаточно соединить при помощи инвертора вход, который будет соответствовать кнопке, с выходом, обозначающем, соответственно, светодиод. Если эту простейшую конструкцию прошить в Марсоход 2, то светодиод будет загораться по нажатию кнопки.
Попробуем усложнить схему и научим нашу плату считать. В нашем распоряжении четыре светодиода, а значит мы можем считать от нуля до 24-1, то есть, до 15. На выходе число будет представлено в двоичном коде, где горящий светодиод обозначает единицу, а не горящий – ноль. Счётчик определяет количество поступивших на его вход импульсов – фронтов тактовой частоты или, в нашем частном случае, нажатий кнопки. Реализовать двоичный счётчик можно как схемотехнически с использованием триггеров, благо, информации по этой теме и готовых схем в интернете можно найти предостаточно, так и алгоритмически, используя язык описания аппаратуры (Hardware Description Language, HDL). Поскольку моей целью является изучение не только схемотехники, но и VHDL, я попытаюсь описать требуемый счётчик при помощи этого языка.
Как упоминалось выше, счётчик будет чувствителен к нажатию кнопки на плате – это действие будет увеличивать его значение на один. Нажатие второй кнопки сбрасывает счётчик. Вот так это выглядит на языке VHDL:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity counter is
port (key1 : in std_logic;
key2 : in std_logic;
leds : out unsigned (3 downto 0));
end counter;
architecture behavior_counter of counter is
begin
process (key1, key2)
variable cnt : integer range 0 to 15;
if (falling_edge(key1) then
cnt:=cnt+1;
end if;
if (key2='0') then
cnt:=0;
end if;
leds <=to_unsigned(cnt, 4);
end process;
end behavior_counter;
Код VHDL превращается компилятором в цифровую схему, которую можно посмотреть при помощи инструмента RTL Viewer, расположенного в меню Tools – Netlist Viewers синтезатора Quartus II. Вот в такую схему превращается вышеописанный счётчик:
Здесь для выделения изменения уровня сигнала на входе счётчика используется функция falling_edge(X), возвращающая значение True при "обнаружении" отрицательного фронта. Также существует функция rising_edge(X), реагирующая на положительный фронт, но гораздо чаще в VHDL можно встретить конструкции вроде (X'event and X='1'), дающие ровно тот же эффект. Надо полагать, различия в поведении этих двух способов описания имеются, но достоверной информацией на эту тему я не располагаю.
Также можно обратить внимание, что используется функция to_unsigned(), позволяющая преобразовать переменную типа integer в "беззнаковый" вектор заданной длины. Обратная функция to_integer() позволит преобразовать вектор в числовую переменную. Эти функции наряду со многими другими входят в пакет numeric_std, который был специально разработан для согласования арифметических и логических типов в VHDL. Подробнее о нём можно почитать в этой интересной статье.
Оказалось, что написать обычный счётчик на VHDL не составит труда даже для человека, впервые познакомившегося с языком. Можно немного усложнить задачу и сделать четырёхразрядный счётчик в рефлексивном двоичном коде Грея – в таком счётчике каждое следующее значение может отличаться от предыдущего только одним разрядом. В целом, код Грея находит в различных отраслях математической науки, также он необходим для реализации асинхронной очереди FIFO – на страницах блога эта тема уже затрагивалась, причём было предложено несколько реализаций счётчика в кодах Грея на языке Verilog. Таким образом, можно легко написать аналогичный модуль на VHDL.
За основу счётчика взят модуль, который предлагает сама компания Altera. В счётчике используется мнимый бит, который помещается в младший разряд вектора q. Сигнал сброса устанавливает значение мнимого бита на "1", а прохождение каждого тактового импульса меняет его значение на противоположное. Решение об изменении значения любого не воображаемого бита зависит исключительно от значений битов более низкого порядка (включая воображаемый). Значение бита меняется, если за ним идёт последовательность 10..0 (любое количество нулей в зависимости от разрядности самого счётчика). Старшему биту значение присваивается по другому правилу, иначе счётчик будет останавливаться на значении 10..0.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity gray_counter is
port
(
clk : in std_logic;
enable : in std_logic;
reset : in std_logic;
gray_out : out std_logic_vector (3 downto 0)
);
end entity;
architecture behavior of gray_counter is
signal q : std_logic_vector (4 downto 0); --вектор, включающий все значения счётчика и мнимый бит q(0)
signal n : std_logic_vector (4 downto 0); --специальный компонент n(x), указывающий, что младшие разряды
--относительно q(x) не имеют единичных значений
signal m : std_logic;
begin
m <= q(3) or q(4);
process (clk, reset, enable)
begin
if (reset = '1') then --сброс счётчика: воображаемый бит меняет значение на "1", остальные – на "0"
q(0) <= '1';
q(4 downto 1) <= (others => '0');
elsif (rising_edge(clk) and enable='1') then
q(0) <= not q(0); --каждый такт воображаемый бит инвертируется
for i in 1 to 4 loop
q(i) <= q(i) xor (q(i-1) and n(i-1));
--изменяем значение бита q(i) только тогда, когда за ним идёт последовательность 10..0
end loop;
q(4) <= q(4) xor (m and n(3)); --для определения значения старшего бита требуется специальный
--сигнал-модификатор, значение которого зависит непосредственно
--от старшего бита и бита, стоящего за ним
end if;
end process;
n(0) <= '1';
process (q, n) --в этом процессе определяется значение разрядов вектора компонентов,
--за исключением n(0), который всегда равен "1" (см. выше)
begin
for j in 1 to 4 loop
n(j) <= n(j-1) and not q(j-1);
end loop;
end process;
gray_out <= q(4 downto 1); --вывод значения счётчика (воображаемый разряд отбрасывается)
end behavior;
Предложенный код счётчика может быть легко модернизирован с целью увеличения разрядности. Кстати, в проекте с счётчиком в коде Грея появился делитель частоты, который уменьшает опорную частоту 100 МГц до 1 Гц. В основе делителя тоже лежит счётчик, правда, обычный.
Такую схему счётчика в коде Грея синтезирует Quartus II:
Существуют и другие, более специфичные варианты счётчиков, однако их рассмотрение уже выходит за рамки "первого свидания" с ПЛИС и языками описания аппаратуры. Каждый из описанных счётчиков можно скачать в виде отдельного проекта для Quartus II и убедиться в его работоспособности. Конечно, оба проекта просты до неприличия, но и это всего лишь первая страница блога новичка. В будущем же я бы хотел уделять больше внимания изучению микропроцессоров и смежных областей в сфере вычислительной техники. Этому благотвортствует и университетская нагрузка, и личный интерес. Так же, я надеюсь на обратную связь – принимается и критика, и идеи для будущих проектов, которые вы бы хотели реализовать, но до сих пор этого не сделали.
Подробнее...