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

от автора

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

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

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

И мой взгляд упал на платку с CPLD (рублей за 200, вроде) на которой были и индикаторы и кнопки. Когда-то стоит начинать работать с такой штукой, подумал я и

Выбор на чем писать VHDL или Verilog не стоял — хотя и пишу все на С, но люблю все же Ada — так что VHDL однозначно. К тому же, почитав введение в 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 — ну конечно, без управления…

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


ссылка на оригинал статьи https://habr.com/ru/post/461621/


Комментарии

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

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