Хабрахабр

Сложно ли написать свою первую программу на VHDL?

Сложно ли написать свою первую программу на VHDL? Трудно сказать, но главное тут — мотивация…

Может, мне и удалось бы оттянуть этот момент, но сосед попросил сделать генератор прямоугольных импульсов, чтобы наглядно отображались и можно было бы управлять частотой и длительностью импульса.

1 микросекунды… И с точностью в 0.

Когда-то стоит начинать работать с такой штукой, подумал я и…
Выбор на чем писать VHDL или Verilog не стоял — хотя и пишу все на С, но люблю все же Ada — так что VHDL однозначно. И мой взгляд упал на платку с CPLD (рублей за 200, вроде) на которой были и индикаторы и кнопки. К тому же, почитав введение в FPGA, понял, что ничего сложного и не будет (ну по крайней мере для такой простой задачи).

Частота родного клока 50 Мгц, то есть низведем его до 10, так что переключение линии тактирования будет в середине и конце. Итак, вначале было слово сделаем себе генератор. Вот что получилось.

-- 100 ns signal generator
process(clk) variable t:integer range 0 to 5 := 0;
begin if rising_edge(clk) then t := t + 1; if t = 5 then t := 0; tact <= not tact; end if; if t = 2 then tact <= not tact; end if; end if;
end process;

Затем нужно как-то отображать и управлять. У нас две величины — длина периода и длина импульса, так что на длину периода отведем 3 знакоместа (с учетом десятых), и на длину периода — 3.

shared variable period : integer range 0 to 1000 := 500;
shared variable duty : integer range 0 to 1000 := 250; shared variable dig1:std_logic_vector(3 downto 0):="0000";
shared variable dig2:std_logic_vector(3 downto 0):="0101";
shared variable dig3:std_logic_vector(3 downto 0):="0010"; shared variable di1:std_logic_vector(3 downto 0):="0000";
shared variable di2:std_logic_vector(3 downto 0):="0000";
shared variable di3:std_logic_vector(3 downto 0):="0101";

Ну для управления подойдут сигналы от кнопок, что видны внизу платы — их всего 4,
так что пусть две управляют изменением периода и импульса соответственно, одна задает знак изменения, а еще одна включает и отключает вывод генератора…

Вот управление

process(key1)
begin if rising_edge(key1) then ready <= not ready; end if;
end process; process(key3)
begin if rising_edge(key3) then if key4 = '1' then inc_duty; else dec_duty; end if; end if;
end process; process(key2)
begin if rising_edge(key2) then if key4 = '1' then inc_period; else dec_period; end if; end if;
end process;

В управлении есть процедуры inc/dec, вот они

procedure inc_duty is
begin if duty < period then duty := duty + 1; if dig1 = "1001" then dig1 := "0000"; if dig2 = "1001" then dig2 := "0000"; if dig3 = "1001" then dig3 := "0000"; else dig3 := dig3 + 1; end if; else dig2 := dig2 + 1; end if; else dig1 := dig1 + 1; end if; end if;
end procedure; procedure dec_duty is
begin if duty > 1 then duty := duty - 1; if dig1 = "0000" then dig1 := "1001"; if dig2 = "0000" then dig2 := "1001"; dig3 := dig3 - 1; else dig2 := dig2 - 1; end if; else dig1 := dig1 - 1; end if; end if;
end procedure; procedure inc_period is
begin if period < 1000 then period := period + 1; if di1 = "1001" then di1 := "0000"; if di2 = "1001" then di2 := "0000"; if di3 = "1001" then di3 := "0000"; else di3 := di3 + 1; end if; else di2 := di2 + 1; end if; else di1 := di1 + 1; end if; end if;
end procedure; procedure dec_period is
begin if period > 1 then period := period - 1; if di1 = "0000" then di1 := "1001"; if di2 = "0000" then di2 := "1001"; if di3 = "0000" then di3 := "1001"; else di3 := di3 - 1; end if; else di2 := di2 - 1; end if; else di1 := di1 - 1; end if; end if;
end procedure;

Немного длинно и сложно (потому и свернуто), но вполне понятно.

Отображать будем время и, чтобы не мучится с точкой, в десятых микросекунды. Ну и надо как-то отображать — у нас семисегментный индикатор, и их 6 штук (вообще-то 8).

Пусть они по циклу переключаются и отображается текущая цифра:

process(tactX)
begin case tactX is when"000"=> en_xhdl<="11111110"; when"001"=> en_xhdl<="11111101"; when"010"=> en_xhdl<="11111011"; when"011"=> en_xhdl<="11110111"; when"100"=> en_xhdl<="11101111"; when"101"=> en_xhdl<="11011111"; when"110"=> en_xhdl<="10111111"; when"111"=> en_xhdl<="01111111"; when others => en_xhdl<="01111111"; end case;
end process; process(en_xhdl)
begin case en_xhdl is when "11111110"=> data4<=dig1; when "11111101"=> data4<=dig2; when "11111011"=> data4<=dig3; when "11110111"=> data4<="1111"; when "11101111"=> data4<=di1; when "11011111"=> data4<=di2; when "10111111"=> data4<=di3; when "01111111"=> data4<="0000"; when others => data4<="1111"; end case;
end process; process(data4)
begin case data4 is WHEN "0000" => dataout_xhdl1 <= "11000000"; WHEN "0001" => dataout_xhdl1 <= "11111001"; WHEN "0010" => dataout_xhdl1 <= "10100100"; WHEN "0011" => dataout_xhdl1 <= "10110000"; WHEN "0100" => dataout_xhdl1 <= "10011001"; WHEN "0101" => dataout_xhdl1 <= "10010010"; WHEN "0110" => dataout_xhdl1 <= "10000010"; WHEN "0111" => dataout_xhdl1 <= "11111000"; WHEN "1000" => dataout_xhdl1 <= "10000000"; WHEN "1001" => dataout_xhdl1 <= "10010000"; WHEN OTHERS => dataout_xhdl1 <= "11111111"; END CASE; END PROCESS;

Признаюсь честно часть кода утащил от исходников, что шли с платкой — уж очень здорово и понятно написано! en_xhdl — этот сигнал будет управлять какой индикатор включен в такте переключения, dataout_xhdl1 — этот сигнал включает светодиоды, ну а data4 временный регистр и хранит цифру.

Тут tactX — генератор отображения, а cnt — счетчик положения в импульсе. Осталось написать сердце, которое все и считает — собственно генератор. Ну и lin — сигнал собственно генератора.

process(tact) variable cntX : integer range 0 to 1000 := 0; variable cnt : integer range 0 to 1000 := 0; begin if rising_edge(tact) then if cntX = 0 then tactX <= tactX + 1; end if; cntX := cntX + 1; if cnt > period then cnt := 0; else cnt := cnt + 1; end if; if cnt = 0 then lin <= '0'; elsif cnt = duty then lin <= '1'; end if; end if;
end process;

Ну вот, осталось вывести данные — это делается постоянно, так что должно располагатся в блоке параллельного выполнения.

cat_led <= dataout_xhdl1; en_led <= en_xhdl; led1 <= not ready; out1 <= lin when ready = '1' else '0'; out2 <= not lin when ready = '1' else '0';

В конце слепить все процессы вместе — полученный файл Quarus Prime благосклонно принял, скомпилил, сообщил, что

Top-level Entity Name v12
Family MAX II
Device EPM240T100C5
Timing Models Final
Total logic elements 229 / 240 ( 95 % )
Total pins 29 / 80 ( 36 % )
Total virtual pins 0
UFM blocks 0 / 1 ( 0 % )

Остался самый нудный этап, хотя и полностью графический — назначить сигналам конкретные пины. И все — осталось залить все в устройство и проверить! Что интересно, удалось все же уложиться в 229 ячеек, так что осталось еще аж 11 штук — но в реальности почти все сожрал интерфейс — кнопки и отображение. Собственно генератор может быть уложен в несколько ячеек — у интела есть документ, где они описывают, как уложить в 1 LUT — ну конечно, без управления…

И сосед доволен 🙂 Так что отвечая на вопрос заголовка — нет, не сложно, если знаешь С или Ада и понимаешь, как работает цифровая электроника, и да, сложно, если нет представления о базовых вещах… По крайней мере, у меня процесс написания занял день, и я получил массу удовольствия как от процесса разработки, так и от функционирующего устройства!

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть