Est-il difficile d'écrire votre premier programme VHDL? C'est difficile à dire, mais l'essentiel ici est la motivation ...
J'aurais peut-être pu retarder ce moment, mais un voisin m'a demandé de fabriquer un générateur d'impulsions rectangulaire afin qu'il soit clairement affiché et qu'il soit possible de contrôler la fréquence et la durée de l'impulsion.
Et avec une précision de 0,1 microsecondes ...
Et mon avis est tombé sur un foulard avec CPLD (200 roubles, en quelque sorte) sur lequel il y avait des indicateurs et des boutons. Une fois que vous devriez commencer à travailler avec une telle chose, je pensais, et ...
Le choix sur quoi écrire VHDL ou Verilog n'était pas - bien que j'écrive tout en C, mais j'aime toujours Ada - donc VHDL est unique. De plus, après avoir lu l'introduction au FPGA, j'ai réalisé que rien ne serait compliqué (enfin, au moins pour une tâche aussi simple).
Donc, au
début était le mot, faisons-nous un générateur. La fréquence du shred natif est de 50 MHz, c'est-à-dire que nous la réduirons à 10, afin que la commutation de la ligne d'horloge se fasse au milieu et à la fin. Voici ce qui s'est passé.
Ensuite, vous devez en quelque sorte afficher et gérer. Nous avons deux valeurs - la durée de la période et la durée de l'impulsion, de sorte que pour la durée de la période nous attribuons 3 familiarités (en tenant compte des dixièmes), et pour la durée de la période - 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";
Eh bien, pour les signaux de contrôle des boutons conviennent, qui sont visibles en bas de la carte - il n'y a que 4
alors laissez deux contrôler le changement de période et d'élan, respectivement, l'un définit le signe du changement, et un autre active et désactive la sortie du générateur ...
Voici la gestion 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;
Dans la gestion, il existe des procédures inc / dec, les voici
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 peu long et compliqué (c'est pourquoi il est replié), mais c'est tout à fait compréhensible.
Eh bien, nous devons l'afficher d'une manière ou d'une autre - nous avons un indicateur à sept segments, et il y en a 6 (en fait 8). Nous afficherons l'heure et, pour ne pas souffrir avec un point, en dixièmes de microseconde.
Laissez-les parcourir et afficher le chiffre actuel:
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;
J'avoue honnêtement que j'ai tiré une partie du code des sources fournies avec un foulard - c'est très cool et clairement écrit! en_xhdl - ce signal contrôlera quel indicateur est allumé dans le cycle de commutation, dataout_xhdl1 - ce signal allume les LED, eh bien, data4 est un registre temporaire et stocke un chiffre.
Il reste à écrire un cœur qui considère tout - le générateur lui-même. Ici, tactX est le générateur d'affichage et cnt est le compteur de position d'impulsion. Eh bien, lin est le signal du générateur lui-même.
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;
Eh bien, il reste à sortir les données - cela se fait en permanence, donc elles doivent être situées dans le bloc d'exécution parallèle.
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';
À la fin, aveugle tous les processus ensemble - le fichier Quarus Prime résultant a été favorablement reçu, compilé et signalé 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 % )
L'étape la plus fastidieuse reste, bien qu'elle soit complètement graphique - pour affecter des broches spécifiques aux signaux. Et c'est tout - il reste à tout remplir dans l'appareil et à vérifier! Fait intéressant, nous avons réussi à rester dans les 229 cellules, donc il en restait déjà 11 - mais en réalité, presque tout était englouti par l'interface - les boutons et l'affichage. En fait, le générateur peut être empilé dans plusieurs cellules - Intel a un document où ils décrivent comment empiler en 1 LUT - bien sûr, sans contrôle ...
Donc, répondre à la question du titre - non, ce n'est pas difficile si vous connaissez C ou Ada et vous comprenez comment fonctionne l'électronique numérique, et oui, c'est difficile si vous n'avez pas une idée des choses de base ... Au moins, cela m'a pris un jour pour écrire, et je J'ai eu beaucoup de plaisir à la fois du processus de développement et d'un appareil qui fonctionne! Et le voisin est content :)