VHDL - istruzioni concorrenti, process e testbench
In questa pagina 6
Dopo aver visto la struttura di un blocco (Introduzione al VHDL - entity, architecture, tipi e livelli di descrizioneVHDL è un linguaggio di descrizione dell'hardware (HDL), non di programmazione: le dichiarazioni sono concorrenti e descrivono blocchi di circuito; serve per documentare, simulare e sintetizzare. Un blocco ha una entity (nome e porte in/out) e almeno una architecture (cosa c'è dentro: structural con component e port map, dataflow con equazioni booleane, behavioral con comportamento). Tipi: std_logic (da 1164: '0','1','X','Z','U','-',...), std_logic_vector, bit, boolean, integer. Signal per i collegamenti interni, constant per i valori fissi.Introduzione al VHDL - entity, architecture, tipi e livelli di descrizione →), qui si descrive come scrivere il comportamento dei circuiti combinatori e come verificarli in simulazione con un testbench. Gli stessi costrutti, con il clock, servono per i circuiti sequenziali (VHDL - latch, flip-flop e reset sincrono e asincronoIn VHDL un elemento di memoria si descrive con un process il cui ramo non assegna sempre il segnale: latch D = process(D,C) con if C='1' then Q<=D; flip-flop positive-edge-triggered = process(CLK) con if CLK'event and CLK='1'. Reset asincrono: RST nella sensitivity list e testato per primo (if RST='1' ... elsif fronte); reset sincrono: solo CLK in lista e RST testato dentro al ramo del fronte (serve un fronte per averne effetto). Il testbench di un circuito sincrono ha un process che genera il clock e uno che applica gli ingressi lontano dal fronte attivo.VHDL - latch, flip-flop e reset sincrono e asincrono →).
Istruzioni concorrenti
Nel corpo di un'architecture (tra begin ed end) le istruzioni sono concorrenti: tutte attive in parallelo.
Assegnazione semplice
y <= (a and b) or c; è un'equazione booleana. Ogni volta che cambia un segnale a destra, y viene rivalutato. Con più assegnazioni allo stesso segnale in istruzioni concorrenti si hanno più driver: in simulazione, se i valori sono diversi, il segnale diventa 'X' (conflitto); è un errore di progetto.
when ... else (logica prioritaria)
Y <= I0 when S = "00" else
I1 when S = "01" else
I2 when S = "10" else
I3;Le condizioni sono valutate in ordine: se la prima è vera si sceglie il primo valore e le successive non contano. Serve per le priorità e permette decisioni su segnali diversi in condizioni diverse.
with ... select (logica parallela)
with S select
Y <= I0 when "00",
I1 when "01",
I2 when "10",
I3 when others;Si valuta una sola espressione di scelta (S); tutte le scelte hanno la stessa priorità, tutti i valori possibili devono essere coperti da una e una sola scelta e when others è obbligatorio per i valori non elencati. È più semplice del when-else per i circuiti a selezione (multiplexer, decoder, tabelle di verità). Più valori si elencano con |: '1' when "0100" | "1000".
library IEEE;
use IEEE.std_logic_1164.all;
entity decoder2to4 is
port ( A : in std_logic_vector(1 downto 0);
EN : in std_logic;
D : out std_logic_vector(3 downto 0) );
end decoder2to4;
architecture dataflow of decoder2to4 is
begin
D(0) <= EN and not A(1) and not A(0);
D(1) <= EN and not A(1) and A(0);
D(2) <= EN and A(1) and not A(0);
D(3) <= EN and A(1) and A(0);
end dataflow;Con EN = '0' tutte le uscite sono '0'.
Il process
Un process è un'istruzione concorrente (parallela alle altre dell'architecture) dentro la quale le istruzioni sono sequenziali: eseguite una dopo l'altra, nell'ordine in cui sono scritte, in un tempo infinitesimo (se non c'è un wait). Tutti i process di un'architecture lavorano simultaneamente.
etichetta : process ( lista_di_sensibilità )
-- dichiarazioni locali
begin
-- istruzioni sequenziali: assegnazioni, if, case, for ...
end process;Lista di sensibilità (sensitivity list): i segnali al cui cambiamento il process si attiva. Per descrivere un circuito combinatorio deve contenere tutti gli ingressi (in VHDL-2008 si può scrivere process (all)). Può contenere solo segnali leggibili (ingressi o signal interni, non uscite). Un process senza lista parte subito all'inizio della simulazione e va avanti ripetendosi, a meno di un wait: è lo schema dei testbench.
Aggiornamento dei segnali. Il valore assegnato a un segnale dentro un process viene aggiornato solo quando il process si sospende (finite le istruzioni o a un wait). Se lo stesso segnale è assegnato più volte nello stesso passaggio, vale l'ultima assegnazione. Quindi
process (b)
begin
a <= b;
a <= not b;
end process;descrive : un semplice inverter, cioè logica combinatoria, e non un flip-flop né un segnale che alterna.
if e case sequenziali
L'if è l'analogo sequenziale del when-else (valuta le condizioni in ordine, crea una priorità); il case è l'analogo sequenziale del with-select (scelte tutte coperte, non sovrapposte, when others obbligatorio; null per "nessuna azione"):
-- priority encoder a 4 ingressi con if
process (D)
begin
if D(3) = '1' then A <= "11"; V <= '1';
elsif D(2) = '1' then A <= "10"; V <= '1';
elsif D(1) = '1' then A <= "01"; V <= '1';
elsif D(0) = '1' then A <= "00"; V <= '1';
else A <= "--"; V <= '0'; -- nessun ingresso a 1: A indifferente
end if;
end process;-- numero primo (o 1) a 4 bit con case
process (input)
begin
case input is
when x"1" | x"2" | x"3" | x"5" | x"7" | x"b" | x"d" => isPrime <= '1';
when others => isPrime <= '0';
end case;
end process;(I valori sono i primi minori di , più 1.)
Memoria non voluta
Se in un if (o in un case) non si specificano tutte le condizioni, il segnale mantiene il valore precedente per quelle non coperte: il sintetizzatore deve introdurre un elemento di memoria (latch). È lo stesso meccanismo con cui si descrive di proposito un latch o un flip-flop; ma in un circuito che deve essere combinatorio è un errore.
Esempio. Numeri primi a 3 bit ():
-- CORRETTO: tutte le condizioni (else)
process (input)
begin
if input = "001" or input = "010" or input = "011" or input = "101" or input = "111" then
isPrime <= '1';
else
isPrime <= '0';
end if;
end process;
-- ERRATO: manca il caso per "000" e "011"
process (input)
begin
if input = "001" or input = "010" or input = "101" or input = "111" then
isPrime <= '1';
elsif input = "100" or input = "110" then
isPrime <= '0';
end if; -- per "000" e "011" isPrime conserva il valore di prima: memoria
end process;Nel secondo codice, se arriva "011" dopo "100" l'uscita resta '0', se dopo "101" resta '1': il circuito ha memoria. Altro esempio tipico: process(A) begin if A = '1' then Q <= B; end if; end process; descrive un latch (memoria), non una porta.
Regola pratica: per la logica combinatoria ogni uscita deve essere assegnata in ogni ramo di if e case, e tutti gli ingressi stanno nella lista di sensibilità.
Testbench
Il testbench è codice VHDL che simula un circuito: applica segnali in ingresso al circuito da verificare (DUT, device under test) e osserva le uscite. Non si sintetizza: serve solo per la simulazione.
Struttura:
- entity vuota (nessuna porta, il testbench non ha ingressi né uscite);
- nell'architecture: segnali per pilotare gli ingressi del DUT e leggerne le uscite;
- istanza del DUT (con la port map);
- uno o più process che applicano gli stimoli, in modo sequenziale, con
wait forper far passare il tempo (wait;senza argomenti sospende il process per sempre).
Esempio: funzione maggioranza ( se almeno due ingressi sono 1):
library IEEE;
use IEEE.std_logic_1164.all;
entity majority is
port ( a, b, c : in std_logic;
y : out std_logic );
end majority;
architecture maj_dataflow of majority is
begin
y <= (a and b) or (a and c) or (b and c);
end maj_dataflow;library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
entity tb_majority is -- entity vuota
end tb_majority;
architecture test of tb_majority is
signal inp : std_logic_vector(2 downto 0) := "000";
signal y : std_logic;
begin
DUT : entity work.majority(maj_dataflow)
port map (a => inp(2), b => inp(1), c => inp(0), y => y);
stimuli : process
begin
for i in 0 to 7 loop -- tutte le 8 combinazioni
inp <= std_logic_vector(to_unsigned(i, 3));
wait for 10 ns; -- tempo perché l'uscita si stabilizzi
report "in = " & integer'image(i) & " -> y = " & std_logic'image(y);
end loop;
wait; -- fine degli stimoli
end process;
end test;Risultato atteso del report: per e per (cioè ).
Punti da ricordare:
- Dopo aver cambiato gli ingressi si aspetta (
wait for 10 ns) prima di leggere l'uscita: il valore appena assegnato a un segnale è visibile solo dopo che il process si sospende. - In un process di stimoli le istruzioni sono sequenziali (a differenza del resto dell'architecture).
- Per verificare automaticamente si usa
assert:
assert (y = '1') = (i >= 3 and i /= 4)
report "uscita errata per i = " & integer'image(i)
severity error;- Il testbench di un circuito sequenziale ha anche un process che genera il clock (vedi VHDL - macchine a stati finiti e testbench sequenzialeUna macchina a stati finiti (FSM) si descrive in VHDL, in stile behavioral, con un tipo enumerato per gli stati e tre process: (1) registro di stato con reset (asincrono) e aggiornamento state<=next_state sul fronte; (2) stato futuro come funzione combinatoria di stato e ingresso (case state, default next_state<=state); (3) uscita come funzione combinatoria di stato (Moore) o di stato e ingresso (Mealy). Il testbench ha il process del clock e il process degli ingressi (reset iniziale, sequenza di test che percorre tutti gli stati).VHDL - macchine a stati finiti e testbench sequenziale →).
- Per un testbench che esplora tutte le combinazioni di ingressi bastano passi di un ciclo
for; per i circuiti sequenziali non si può in genere (serve scegliere le sequenze significative).
Tabella riassuntiva
| istruzione concorrente | istruzione sequenziale (dentro un process) | |
|---|---|---|
| condizione a priorità | when ... else |
if ... elsif ... else |
| scelta parallela | with ... select |
case ... when ... |
| assegnazione | y <= expr; |
y <= expr; (vince l'ultima) |
Errori comuni
- Lista di sensibilità incompleta in un process combinatorio: in simulazione l'uscita non si aggiorna quando cambia l'ingresso mancante.
ifsenzaelse(ocasesenzaothers): memoria non voluta.- Più assegnazioni concorrenti allo stesso segnale (driver multipli,
'X'). - Leggere un'uscita subito dopo l'assegnazione senza
wait. - Usare
with selectsenzawhen others. - Ritenere che
a <= b; a <= not b;in un process produca due valori in successione: vale l'ultimo.
Versione ripasso
- Concorrenti:
y <= expr;;when-else(priorità, valutato in ordine);with-select(parallelo, tutti i casi,when others,|per più valori). Più driver sullo stesso segnale'X'. - Process: istruzione concorrente a corpo sequenziale;
sensitivity list(combinatorio: tutti gli ingressi;process(all)); senza lista parte subito e si ferma conwait. Segnali aggiornati alla sospensione; vince l'ultima assegnazione ( inverter). if/elsif/elsewhen-else;casewith-select(nullper nessuna azione).- Memoria non voluta:
ifsenzaelseocaseincompleto latch; regola: assegnare ogni uscita in ogni ramo.process(A) if A='1' then Q<=B;= latch. - Testbench: entity vuota; segnali; DUT (
entity work.nome(arch) port map(...)); process di stimoli conwait for,wait;finale;report,assert. Cicloforsu tutte le combinazioni conto_unsigned. - Esempi: maggioranza per ; decoder 2-to-4 con enable (Decoder, encoder e priority encoderUn decoder $n$-to-$m$ ($m\le2^n$) converte un ingresso binario a $n$ bit in un'uscita 1-hot (un solo 1, nella posizione indicata): le sue uscite sono i mintermini degli ingressi, realizzati con $m$ AND; per decoder grandi si usa l'approccio gerarchico (costo in ingressi: 3-to-8 = 27, 6-to-64 = 182) e un enable. Ogni funzione = decoder + OR dei suoi mintermini. L'encoder fa l'operazione inversa (1-hot $\to$ binario) ma sbaglia con più ingressi a 1 o tutti a 0: il priority encoder risolve con una priorità e un'uscita V (valid).Decoder, encoder e priority encoder →).
- Errori: sensitivity list incompleta;
ifsenzaelse; lettura senzawait.