Exemple VHDL: Alu
Aquest exemple treballa amb una ALU senzilla:
- La descriu en VHDL
- Refina un pèl la descripció
- Comprova la sintaxi amb ghdl
- Prepara un test i l'executa amb ghdl
- Observa els resultats amb gtkview
ghdl és un compilador lliure de VHDL que permet compilar i simular circuïts descrits amb VHDL. El resultat final d'usar aquest compilador és un programa executable que, quan l'executem, simula el circuit.
L'ALU
Primera aproximació
La ALU té aquesta descripció en VHDL:
1 library ieee;
2 use ieee.std_logic_1164.all;
3
4 entity alu is
5 port
6 (A, B : in std_logic_vector (3 downto 0);
7 opcode: in std_logic_vector (3 downto 0);
8 result: out std_logic_vector (3 downto 0) );
9 end alu;
10
11 architecture comportament of alu is
12 begin
13 process (opcode, A, B)
14 begin
15 case opcode is
16 when "0000" => result <= not A;
17 when "0001" => result <= not (A and B);
18 when "0010" => result <= not A or not B;
19 when "0011" => result <= "1111";
20 when "0100" => result <= not A or not B;
21 when "0101" => result <= not B;
22 when "0110" => result <= not A xor not B;
23 when "0111" => result <= A or not B;
24 when "1000" => result <= not A and B;
25 when "1001" => result <= A xor B;
26 when "1010" => result <= B;
27 when "1011" => result <= A or B;
28 when "1100" => result <= "0000";
29 when "1101" => result <= A and not B;
30 when "1110" => result <= A and B;
31 when "1111" => result <= A;
32 when others => result <= "----";
33 end case;
34 end process;
35 end comportament;
Noteu que cal una sentència «when others» ja que VHDL requereix que una sentència «case» sigui completa i abasti tot el domini. Com que les variables de tipus std_logic_vector no són binàries sinó que poden tenir altres valors com ara Z,X, etc., les combinacions possibles de quatre bits són moltes més de les que semblen. S'ha de dir que algunes eines poc respectuoses amb l'estàndart no obliguen a usar «when others».
Per comprovar que la sintaxi és correcta, simplement ens cal usar ghdl amb la opció -s fent:
1 $ ghdl -s alu.vhdl
Si troba errors sintàctics es queixarà amb els corresponents missatges d'error similars a aquest:
Fixeu-vos que en aquest missatge es diu:
Que l'error és al fitxer alu.vhdl
- Que és a la línia 10
- Que és a la columna 47 4: Que el problema és que manca un ';' al final d'una llista
Una vegada hem comprovat que la nostra descripció VHDL és sintàcticament correcta cal demanar al compilador que l'analitzi. A tal efecte usem la opció -a del compilador ghdl:
1 $ ghdl -a alu.vhdl
El resultat, si tot va bé, serà un fitxer de nom alu.o.
Finalment, la darrera fase l'anomenem elaboració i es fa usant ghdl amb l'opció -e fent:
1 $ ghdl -e alu
Fixeu-vos que ara només usem el nom de l'entitat (declarat al bloc entity com a alu) i no el nom del fitxer que la conté (alu.vhdl). El resultat és un programa executable anomenat simplement alu. El podeu executar fent simplement:
1 $ ./alu
Naturalment no veureu res! La descripció de l'alu no té cap tipus de senyal d'entrada que la faci funcionar, per tant quan la simulem... simplement no fa res. Més endavant ja veurem com subministrar-li senyals d'entrada.
Petites millores
La descripció que tenim de l'ALU té un element empipador: el tipus de dades que descriu els busos es repeteix aquí i allí de forma sistemàtica. Seria interessant poder definir un nou tipus de dades «bus» que permetés simplificar la descripció.
A tal efecte, definim un nou package en un nou fitxer que anomenarem tipus.vhdl i que té el següent contingut:
Aqui es defineix el tipus «bus_4» com un vector de 4 bits. Ara podem usar aquest bus en la definició de l'ALU. Només cal fer els següents canvis:
1 library ieee;
2 library work;
3 use ieee.std_logic_1164.all;
4 use work.tipus.all;
5
6
7 entity alu is
8 port
9 (A, B, opcode : in bus_4;
10 result : out bus_4 );
11 end alu;
12
13 architecture comportament of alu is
14 begin
15 process (opcode, A, B)
16 begin
17 case opcode is
18 when "0000" => result <= not A;
19 when "0001" => result <= not (A and B);
20 when "0010" => result <= not A or not B;
21 when "0011" => result <= "1111";
22 when "0100" => result <= not A or not B;
23 when "0101" => result <= not B;
24 when "0110" => result <= not A xor not B;
25 when "0111" => result <= A or not B;
26 when "1000" => result <= not A and B;
27 when "1001" => result <= A xor B;
28 when "1010" => result <= B;
29 when "1011" => result <= A or B;
30 when "1100" => result <= "0000";
31 when "1101" => result <= A and not B;
32 when "1110" => result <= A and B;
33 when "1111" => result <= A;
34 when others => result <= "----";
35 end case;
36 end process;
37 end comportament;
La capçalera d'aquest fitxer es pot simplificar atès que la llibreria work, que correspon amb els packages que tens en el mateix directori, no cal importar-la. Per tant la capçalera pot ser:
Per processar això, ara cal analitzar primer tipus.vhdl (no farem test sintàctic atès que suposarem que és correcte). Fem-ho així:
Fixeu-vos que cal analitzar cada package per separat i cal respectar l'ordre natural: com que alu usa tipus, primer cal analitzar tipus i després alu. Una vegada feta la fase d'anàlisi, podem passar a la fase d'elaboració del simulador:
1 $ ghdl -e alu
Fixeu-vos que només cal elaborar l'entitat que ens interessa simular, en aquest cas l'alu. El resultat, com abans, és un programa executable de nom alu que, de nou, si l'executeu no fa res en especial per que no s'han definit senyals d'entrada.
Comprovant el funcionament
Per comprovar que l'alu especificada es comporta com cal, podem definir un "test bench" que és quelcom similar al que en programació seria un "unit test". Això ho podem fer usant el mateix VDHL.
La idea és molt senzilla: només cal definir una nova entitat que és una composició de diverses components. En el nostre cas, serà la composició d'una sola component que és una alu. En la implementació d'aquesta entitat (architecture), generarem els senyals que volguem i els dirigirem a la alu comprovant després que el resultat és correcte.
Aquesta nova entitat l'anomenarem alu_tb1 i la definirem en el fitxer alu_tb1.vhdl de la següent forma:
1 library ieee;
2 use ieee.std_logic_1164.all;
3 use work.tipus.all; -- inclou el subtipus bus_4
4
5 entity alu_tb1 is
6 end alu_tb1;
7
8 architecture behavior of alu_tb1 is
9
10 component alu
11 port (
12 A : in bus_4;
13 B : in bus_4;
14 opcode : in bus_4;
15 result : out bus_4);
16 end component;
17
18 signal T_A, T_B, T_opcode, T_result : bus_4;
19
20 begin -- behavior
21
22 alu_UT : alu port map (
23 A => T_A,
24 B => T_B,
25 opcode => T_opcode,
26 result => T_result);
27
28 process
29 begin
30 T_A <= "0000";
31 T_B <= "1001";
32 T_opcode <= "1110";
33 wait for 10 ns;
34 assert (T_result = "0000") report "AND incorrect" severity failure;
35
36 T_A <= "0001";
37 T_B <= "1001";
38 T_opcode <= "1110";
39 wait for 10 ns;
40 assert (T_result = "0001") report "AND incorrect" severity failure;
41
42 wait;
43 end process;
44
45 end behavior;
Fixeu-vos en alguns detalls de com és la entitat de test. Només té arquitectura. Amb la sentència component definim quines components fa servir aquesta entitat. En aquest cas, només una alu. Definim també alguns signals, que juguen el paper de "cables" i usarem per fer el "cablejat" d'aquesta entitat.
A continuació instanciem una alu, que anomenem alu_UT, i connectem als signals que hem definit anteriorment. Finalment, una vegada definit el montatge per fer els tests, comencem a fer una seqüència de tests sota la sentència process. La idea és senzilla, injectem en cada cable el senyal que ens interessa, esperem un temps prudencial i comprovem mitçant una sentència assert que comprova el senyal de sortida.
Si processeu el test bench fent:
i obteniu un executable anomenat alu_tb1. Si l'executeu fent:
1 $ ./alu_tb1
i no obteniu cap sortida és que l'alu està fent l'operació AND com esperaveu. Altrament les sentències assert haurien aturat el procés tot indicant l'error.
També podeu "espiar" què està passant amb els senyals dins del circuit. Només cal que, en fer la simulació, indiqueu que s'enregistrin els senyals. Ho podeu fer així:
1 $ ./alu_tb1 --vcd=alu_tb1.vcd
Això genera un fitxer en format vcd que conté l'evolució dels senyals. Si els voleu veure en forma de cronograma només heu d'usar l'eina gtkwave (que potser haureu d'instal·lar abans usant aptitude) fent:
1 $ gtkwave alu_tb1.vcd
L'aplicació té una interfície gràfica senzilla que permet explorar els senyals: