É difícil escrever seu primeiro programa VHDL?

É difícil escrever seu primeiro programa VHDL? É difícil dizer, mas o principal aqui é a motivação ...


Talvez eu pudesse ter atrasado esse momento, mas um vizinho me pediu para criar um gerador de pulsos retangular, para que ele fosse claramente exibido e que fosse possível controlar a frequência e a duração do pulso.

E com uma precisão de 0,1 microssegundos ...

E minha opinião caiu em um lenço com CPLD (200 rublos, como) no qual havia indicadores e botões. Uma vez que você deveria começar a trabalhar com uma coisa dessas, pensei, e ...

A escolha sobre o que escrever VHDL ou Verilog não foi - embora eu escreva tudo em C, mas ainda amo Ada -, então o VHDL é único. Além disso, depois de ler a introdução ao FPGA, percebi que nada seria complicado (bem, pelo menos para uma tarefa tão simples).

Então, no começo era a palavra, vamos nos tornar um gerador. A frequência do shred nativo é de 50 MHz, ou seja, vamos reduzi-lo para 10, para que a mudança da linha do relógio fique no meio e no final. Aqui está o que aconteceu.

-- 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; 

Então você precisa exibir e gerenciar de alguma forma. Temos dois valores - a duração do período e a duração do impulso, de modo que, para a duração do período, atribuímos 3 familiaridades (considerando os décimos) e para a duração do período - 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"; 

Bem, para controle, os sinais dos botões são adequados, visíveis na parte inferior do quadro - existem apenas 4 deles,
então, dois controlem a mudança no período e no momento, respectivamente, um define o sinal da mudança e outro liga e desliga a saída do gerador ...

Aqui está a gerência
 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; 


Na administração, existem procedimentos inc / dec, aqui estão eles
 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; 


Um pouco longo e complicado (é por isso que está dobrado), mas é bastante compreensível.

Bem, precisamos exibi-lo de alguma forma - temos um indicador de sete segmentos e existem 6 deles (na verdade 8). Mostraremos o tempo e, para não sofrer com um ponto, em décimos de microssegundo.

Deixe-os percorrer e exibir o dígito atual:

 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; 

Admito honestamente que arrastei parte do código das fontes que vieram com um lenço - é muito legal e claramente escrito! en_xhdl - este sinal controla qual indicador está aceso no ciclo de comutação, dataout_xhdl1 - esse sinal acende os LEDs, bem, data4 é um registro temporário e armazena um dígito.

Resta escrever um coração que considere tudo - o próprio gerador. Aqui, tactX é o gerador de exibição e cnt é o contador de posição de pulso. Bem, lin é o sinal do próprio gerador.

 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; 

Bem, resta produzir os dados - isso é feito constantemente, portanto deve estar localizado no bloco de execução paralelo.

  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'; 

No final, oculte todos os processos juntos - o arquivo Quarus Prime resultante foi recebido, compilado e relatado favoravelmente que

 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 % ) 

O estágio mais tedioso permanece, embora seja completamente gráfico - atribuir pinos específicos aos sinais. E isso é tudo - resta preencher tudo no dispositivo e verificar! Curiosamente, conseguimos manter dentro de 229 células, então ainda restavam 11 - mas, na realidade, quase todos devoraram a interface - botões e tela. Na verdade, o gerador pode ser empilhado em várias células - a Intel possui um documento onde descreve como empilhar 1 LUT - bem, é claro, sem controle ...

Então, ao responder à pergunta do título - não, não é difícil se você conhece C ou Ada e entende como a eletrônica digital funciona, e sim, é difícil se você não tem uma idéia das coisas básicas ... Pelo menos, levei um dia para escrever e eu Fiquei muito satisfeito com o processo de desenvolvimento e com um dispositivo em funcionamento! E o vizinho está feliz :)

Source: https://habr.com/ru/post/pt461621/


All Articles