VHDL - logica sequenziale e registri
In questa pagina 9
Un circuito sequenziale ha uno stato memorizzato in registri (Latch, registri e parametri temporaliUn circuito sequenziale ha uno stato memorizzato in due modi: retroazione positiva (due inverter in anello, bistabile, i latch statici) o immagazzinamento di carica (circuiti dinamici). Un latch è sensibile al livello del clock (trasparente quando il clock è attivo, memorizza quando non lo è), un registro (flip-flop) è sensibile al fronte e si realizza con due latch sulle fasi opposte (master-slave). Il latch SR a porte NOR/NAND non ammette la combinazione S = R = 1 perché passando da lì alla memoria lo stato finale è imprevedibile. Parametri temporali del registro: ritardo clock-uscita t_c-q, tempo di setup t_su (dato stabile prima del fronte), tempo di hold t_hold (dato stabile dopo il fronte). Periodo minimo: T_min = t_c-q + t_p,logic(max) + t_su; vincolo di hold: t_cd,reg + t_cd,logic > t_hold. La pipeline spezza la logica con registri per aumentare la frequenza (a spese della latenza).Latch, registri e parametri temporali →). In VHDL (VHDL - struttura, tipi di dato e processiVHDL è un linguaggio di descrizione dell'hardware: un listato non è una sequenza di istruzioni eseguite da un processore ma la descrizione di un circuito, che la sintesi traduce in uno schema (LUT, flip-flop, multiplexer). Ogni modulo ha una entity (interfaccia: port con modo in, out, inout e tipo; eventuali generic) e una architecture (parte dichiarativa: segnali, costanti, componenti; parte assertiva dopo begin: assegnazioni concorrenti e processi). Le istruzioni nell'architecture sono concorrenti: l'ordine in cui sono scritte non conta. Un process esegue le proprie istruzioni in modo sequenziale quando un segnale della lista di sensibilità cambia. Segnali (<=, aggiornati alla fine del delta cycle, definiti nella parte dichiarativa) e variabili (:=, immediate, solo dentro il processo) hanno semantica diversa. Tipi: bit, boolean, integer, std_logic (a 9 valori, tra cui 'Z', 'X', 'U'), std_logic_vector, unsigned e signed (numeric_std).VHDL - struttura, tipi di dato e processi →) si descrive con processi sensibili al clock. L'hardware inferito dipende dalla forma del codice: si seguono schemi standard.
Latch e flip-flop D
Latch (sensibile al livello, trasparente per ): il processo ha sensibilità su e e non assegna quando (il valore precedente è mantenuto):
process (d, clk)
begin
if clk = '1' then q <= d; end if;
end process;Flip-flop D (sul fronte di salita): la sensibilità è solo clk e l'assegnazione è dentro rising_edge(clk):
process (clk)
begin
if rising_edge(clk) then q <= d; end if;
end process;(equivalente: if clk'event and clk = '1'). Flip-flop T: if rising_edge(clk) then if t = '1' then qi <= not qi; end if; end if; con un segnale interno qi (un'uscita out non si legge) e q <= qi;.
La differenza tra latch e registro, dal punto di vista del codice, è la presenza di rising_edge: un if sul livello descrive un latch (sensibile al livello), un if su rising_edge(clk) un registro (sensibile al fronte).
Reset sincrono e asincrono
- Sincrono: il reset è valutato dentro il ramo del fronte di clock, quindi ha effetto solo al fronte successivo; la lista di sensibilità contiene solo
clk:
process (clk)
begin
if rising_edge(clk) then
if rst = '1' then q <= '0';
else q <= d;
end if;
end if;
end process;- Asincrono: il reset ha priorità ed è valutato fuori dal fronte: agisce subito, a prescindere dal clock;
rstè nella lista di sensibilità:
process (rst, clk)
begin
if rst = '1' then q <= '0';
elsif rising_edge(clk) then q <= d;
end if;
end process;Il primo codice della lista implementa un reset sincrono, il secondo asincrono (per esempio A<='0' dentro if rising_edge(CLK) è un reset sincrono). Nelle FPGA i flip-flop hanno entrambi gli ingressi (set/reset sincrono o asincrono secondo la configurazione). Il reset asincrono non è sincronizzato con il clock: il suo rilascio vicino a un fronte può violare setup/hold (si sincronizza il rilascio con un flip-flop).
Registro a bit
entity regn is
generic (n : integer := 16);
port (d : in std_logic_vector(n-1 downto 0);
res, clk : in std_logic;
q : out std_logic_vector(n-1 downto 0));
end regn;
architecture bhv of regn is
begin
process (res, clk)
begin
if res = '1' then q <= (others => '0');
elsif rising_edge(clk) then q <= d;
end if;
end process;
end bhv;Con un enable (ce) si aggiorna solo quando serve: elsif rising_edge(clk) then if ce = '1' then q <= d; end if; end if; (nelle FPGA il flip-flop ha un ingresso di enable dedicato).
Registro a scorrimento
process (clk)
begin
if rising_edge(clk) then
if load = '1' then
qi <= r; -- caricamento parallelo
else
qi(qi'right) <= w; -- ingresso seriale
for i in 1 to qi'left loop
qi(i) <= qi(i-1); -- ogni bit prende il precedente
end loop;
end if;
end if;
end process;
q <= qi;Poiché qi è un segnale, le assegnazioni nel ciclo for leggono i valori precedenti al fronte: tutti i bit scorrono insieme di una sola posizione (Registri dinamici, C2MOS, TSPC e registri a scorrimentoI registri dinamici memorizzano il bit come carica su un nodo capacitivo in alta impedenza: meno transistor e commutazioni più rapide dei registri statici, ma il dato si perde (serve un clock minimo) e il master-slave con gate di trasmissione è sensibile alla sovrapposizione dei clock (race). Il registro C2MOS (clocked CMOS) usa inverter con transistor di clock in serie: è insensibile alla sovrapposizione dei clock (0-0 e 1-1) se i fronti sono ripidi, perché durante la sovrapposizione ciascuno stadio può solo caricare o solo scaricare il nodo, mai propagare. Il TSPC usa una sola fase di clock (nessun clock invertito) e permette di inglobare logica nei latch. Un registro a scorrimento è una cascata di registri D: a ogni fronte il dato avanza di una posizione (conversione serie-parallelo, ritardi, FIFO); il collegamento diretto richiede t_c-q > t_hold.Registri dinamici, C2MOS, TSPC e registri a scorrimento →). Con una variabile il dato attraverserebbe tutto il registro in un colpo.
Contatore (laboratorio)
Contatore a 4 bit crescente o decrescente secondo un ingresso, con reset sincrono:
signal cnt : unsigned(3 downto 0);
...
process (clk)
begin
if rising_edge(clk) then
if rst = '1' then
cnt <= (others => '0');
elsif up = '1' then
cnt <= cnt + 1; -- avvolge da 15 a 0
else
cnt <= cnt - 1;
end if;
end if;
end process;
led <= std_logic_vector(cnt);Per rendere visibile il conteggio sui LED (con clock a ) serve rallentarlo: non si crea un secondo clock con la logica, ma una abilitazione (clock enable) generata da un contatore divisore: if tick = '1' then cnt <= cnt + 1; end if;, con tick a per un solo ciclo ogni periodi (circa ).
Segnali asincroni: debouncing
La pressione di un pulsante produce rimbalzi (bouncing) di durata tipica minore di : più transizioni ravvicinate che un circuito veloce conta come più pressioni. Si risolve con il debouncing: il segnale del pulsante viene campionato con un opportuno segnale di clock e uno o più flip-flop, con un clock lento (per esempio un campionamento ogni circa –) o con un contatore che accetta il nuovo valore solo se rimane stabile per un tempo maggiore dei rimbalzi. Prima di usare un ingresso asincrono nel dominio del clock lo si fa passare per due flip-flop in serie (sincronizzatore) per ridurre la probabilità di metastabilità (Latch, registri e parametri temporaliUn circuito sequenziale ha uno stato memorizzato in due modi: retroazione positiva (due inverter in anello, bistabile, i latch statici) o immagazzinamento di carica (circuiti dinamici). Un latch è sensibile al livello del clock (trasparente quando il clock è attivo, memorizza quando non lo è), un registro (flip-flop) è sensibile al fronte e si realizza con due latch sulle fasi opposte (master-slave). Il latch SR a porte NOR/NAND non ammette la combinazione S = R = 1 perché passando da lì alla memoria lo stato finale è imprevedibile. Parametri temporali del registro: ritardo clock-uscita t_c-q, tempo di setup t_su (dato stabile prima del fronte), tempo di hold t_hold (dato stabile dopo il fronte). Periodo minimo: T_min = t_c-q + t_p,logic(max) + t_su; vincolo di hold: t_cd,reg + t_cd,logic > t_hold. La pipeline spezza la logica con registri per aumentare la frequenza (a spese della latenza).Latch, registri e parametri temporali →).
Display a 7 segmenti multiplexati
Gli otto display della scheda condividono le stesse linee dei segmenti (catodi) e hanno anodi distinti: due display diversi non possono visualizzare cifre diverse nello stesso istante. Si usa la tecnica stroboscopica: si accende un display alla volta, ciclicamente, con la cifra corretta, abbastanza in fretta da apparire accesi tutti insieme. Un contatore a bit (con i bit più significativi che pilotano un decoder per gli anodi e i bit più bassi che contano il tempo) gestisce la rotazione. Il tempo di accensione non deve essere né troppo lungo (sfarfallio, flicker) né troppo corto (luminosità bassa): con l'intero ciclo vale , e si sceglie tra e : ( per , per ); con ogni display resta acceso . Un valore binario da mostrare in decimale si converte in BCD con il double dabble (Rappresentazione dei numeri e dell'informazioneUn'informazione digitale è una stringa di bit. Numeri senza segno: N bit rappresentano 0…2^N − 1; in esadecimale ogni cifra raggruppa 4 bit. Numeri con segno: complemento a due (range −2^(N−1)…2^(N−1) − 1; si nega invertendo i bit e sommando 1; il bit più significativo è il segno e l'estensione di segno lo replica). Il BCD codifica ogni cifra decimale con 4 bit (0…9) e si ottiene dal binario con l'algoritmo double dabble (shift a sinistra, e +3 a ogni gruppo maggiore di 4 prima dello shift). Il codice Gray cambia un solo bit tra valori consecutivi. I numeri frazionari si trattano in virgola fissa (4 bit di parte frazionaria = multipli di 1/16), i caratteri in ASCII, gli errori con un bit di parità.Rappresentazione dei numeri e dell'informazione →).
Regole per un progetto sincrono
- Un solo clock (o clock con abilitazioni) per tutti i registri; non si usano segnali prodotti da logica combinatoria come clock.
- Ogni processo sequenziale ha
rising_edge(clk); ogni processo combinatorio assegna le uscite in tutti i rami (VHDL - logica combinatoriaUna logica combinatoria in VHDL si descrive con assegnazioni concorrenti (semplici, condizionate when-else con priorità, selezionate with-select senza priorità) o con processi sensibili a tutti gli ingressi (if/elsif, case). Nel processo ogni uscita va assegnata in tutti i rami e la lista di sensibilità deve contenere tutti i segnali letti: altrimenti la sintesi inferisce un latch (memoria). L'aritmetica si fa con unsigned/signed di numeric_std (to_unsigned, to_integer, resize, conversioni di tipo) e con un bit in più per il riporto. Cicli for e istruzioni generate creano hardware replicato (non cicli nel tempo). La descrizione strutturale istanzia componenti con component e port map (per posizione o per nome, anche con generic map), e permette di costruire sistemi gerarchici.VHDL - logica combinatoria →). - Le uscite dei registri vanno lette solo tramite segnali interni.
- Le temporizzazioni (setup, hold, frequenza massima) si verificano dopo l'implementazione (Simulazione, testbench, vincoli di temporizzazione e flusso su FPGAUn progetto VHDL si verifica per simulazione con un testbench: una entity senza porte che istanzia il circuito sotto test (UUT), genera il clock (process con wait for e inversione), applica gli stimoli e controlla le uscite con assert/report/severity (note, warning, error, failure) o leggendo vettori da file (textio). Il flusso su FPGA è: descrizione VHDL, simulazione funzionale, sintesi (rete di LUT e flip-flop), implementazione (piazzamento e instradamento), analisi di temporizzazione, generazione del bitstream e programmazione della scheda. I vincoli (file XDC) assegnano i pin e definiscono il clock (periodo); l'analisi di temporizzazione verifica per ogni cammino registro-registro lo slack di setup, T − (t_c-q + t_logica + t_routing + t_su), e di hold; con slack negativo la frequenza è troppo alta: f_max = 1/(T − WNS).Simulazione, testbench, vincoli di temporizzazione e flusso su FPGA →).
Errori comuni
- Scrivere
if clk = '1'invece dirising_edge(clk): si ottiene un latch, non un registro. - Mettere
rstnella lista di sensibilità ma testarlo dentrorising_edge(non è asincrono) o viceversa. - Usare un segnale come riporto o come stato intermedio nello stesso ciclo aspettandosi il valore appena assegnato.
- Dimenticare che in VHDL
cnt + 1richiedeunsigned(nonstd_logic_vector). - Generare un clock lento con un contatore e usarlo come clock: meglio un clock enable.
Versione ripasso
- Latch:
process(d,clk) if clk='1' then q<=d(livello); FF D:process(clk) if rising_edge(clk) then q<=d(fronte); FF T con segnale interno. - Reset sincrono:
rstdentrorising_edge(clk), sensibilità soloclk; asincrono:if rst='1' ... elsif rising_edge(clk), sensibilitàrst, clk, priorità e effetto immediato; rilascio da sincronizzare. - Registro bit:
generic(n),(others=>'0'), enablece. - Shift register: segnale
qi,for i in 1 to qi'left loop qi(i)<=qi(i-1),loadparallelo; con segnale scorre di una posizione. - Contatore:
unsigned,cnt<=cnt+1/-1, avvolge; rallentare con clock enable (tickogni cicli). - Debouncing: rimbalzi ms; campionare con clock lento e uno o più FF; sincronizzatore a 2 FF per ingressi asincroni.
- Display multiplexati: un display alla volta, ciclicamente; ms a MHz (); BCD con double dabble.
- Regole: un solo clock,
rising_edgein ogni processo sequenziale, uscite assegnate in tutti i rami nei combinatori. - Errori:
if clk='1'= latch; reset mal posizionato; segnale immediato;+sustd_logic_vector; clock da logica.