¿Es difícil escribir tu primer programa VHDL?

¿Es difícil escribir tu primer programa VHDL? Es difícil de decir, pero lo principal aquí es la motivación ...


Tal vez podría haber logrado retrasar este momento, pero un vecino me pidió que hiciera un generador de pulso rectangular para que pudiera mostrarse claramente y sería posible controlar la frecuencia y la duración del pulso.

Y con una precisión de 0.1 microsegundos ...

Y mi opinión cayó en una bufanda con CPLD (200 rublos, más o menos) en la que había indicadores y botones. Una vez que deberías comenzar a trabajar con tal cosa, pensé, y ...

La elección sobre qué escribir VHDL o Verilog no fue, aunque escribo todo en C, pero todavía amo a Ada, por lo que VHDL es único. Además, después de leer la introducción a FPGA, me di cuenta de que nada sería complicado (bueno, al menos para una tarea tan simple).

Entonces, al principio, la palabra era hacernos un generador. La frecuencia de la trituración nativa es de 50 MHz, es decir, la reduciremos a 10, de modo que la conmutación de la línea del reloj estará en el medio y al final. Esto es lo que pasó.

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

Entonces necesita mostrar y administrar de alguna manera. Tenemos dos valores: la duración del período y la duración del impulso, de modo que para la duración del período asignamos 3 familiaridades (teniendo en cuenta las décimas), y para la duración del 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"; 

Bueno, para el control, las señales de los botones son adecuadas, que son visibles en la parte inferior del tablero; solo hay 4 de ellas,
así que deje que dos controlen el cambio en el período y el momento, respectivamente, uno establece el signo del cambio y otro enciende y apaga la salida del generador ...

Aquí está la gerencia
 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; 


En gestión hay procedimientos inc / dec, aquí están
 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; 


Un poco largo y complicado (por eso está plegado), pero es bastante comprensible.

Bueno, tenemos que mostrarlo de alguna manera: tenemos un indicador de siete segmentos y hay 6 de ellos (en realidad 8). Mostraremos el tiempo y, para no sufrir con un punto, en décimas de microsegundo.

Déjelos recorrer y mostrar el dígito actual:

 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, saqué parte del código de las fuentes que venían con una bufanda: ¡es genial y está claramente escrito! en_xhdl: esta señal controlará qué indicador está encendido en el ciclo de conmutación, dataout_xhdl1: esta señal enciende los LED, bueno, data4 es un registro temporal y almacena un dígito.

Queda por escribir un corazón que considere todo: el generador mismo. Aquí tactX es el generador de pantalla, y cnt es el contador de posición del pulso. Bueno, lin es la señal del generador en sí.

 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; 

Bueno, queda por dar salida a los datos, esto se hace constantemente, por lo que debe ubicarse en el bloque de ejecución paralela.

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

Al final, oculte todos los procesos juntos: el archivo Quarus Prime resultante se recibió, compiló e informó favorablemente 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 % ) 

La etapa más tediosa permanece, aunque es completamente gráfica: asignar pines específicos a las señales. Y eso es todo: ¡queda por completar todo en el dispositivo y verificar! Curiosamente, logramos mantener dentro de 229 celdas, por lo que ya quedaban 11, pero en realidad, casi todo fue engullido por la interfaz: botones y pantalla. En realidad, el generador se puede apilar en varias celdas: Intel tiene un documento donde describen cómo apilar en 1 LUT, bueno, por supuesto, sin control ...

Entonces, respondiendo a la pregunta del titular: no, no es difícil si conoces a C o Ada y entiendes cómo funciona la electrónica digital, y sí, es difícil si no tienes una idea de las cosas básicas ... Al menos, me llevó un día escribir, y yo ¡Obtuve mucho placer tanto del proceso de desarrollo como de un dispositivo que funciona! Y el vecino está feliz :)

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


All Articles