Salta al contenuto
Note per Studenti VHDL - istruzioni concorrenti, process e testbench

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)

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

vhdl
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".

Esempio: 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 →), in dataflow:

vhdl
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.

vhdl
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

vhdl
process (b)
begin
  a <= b;
  a <= not b;
end process;

descrive a=b‾a=\overline b: 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"):

vhdl
-- 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;
vhdl
-- 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 1,2,3,5,7,11,131,2,3,5,7,11,13 sono i primi minori di 1616, 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 (1,2,3,5,71,2,3,5,7):

vhdl
-- 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 for per far passare il tempo (wait; senza argomenti sospende il process per sempre).

Esempio: funzione maggioranza (y=1y=1 se almeno due ingressi sono 1):

vhdl
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;
vhdl
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: y=0y=0 per i=0,1,2,4i=0,1,2,4 e y=1y=1 per i=3,5,6,7i=3,5,6,7 (cioè 011,101,110,111011,101,110,111).

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:
vhdl
assert (y = '1') = (i >= 3 and i /= 4)
  report "uscita errata per i = " & integer'image(i)
  severity error;

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.
  • if senza else (o case senza others): memoria non voluta.
  • Più assegnazioni concorrenti allo stesso segnale (driver multipli, 'X').
  • Leggere un'uscita subito dopo l'assegnazione senza wait.
  • Usare with select senza when others.
  • Ritenere che a <= b; a <= not b; in un process produca due valori in successione: vale l'ultimo.

Versione ripasso

Esercizi su questo argomento

Teoria collegata