Salta al contenuto
Note per Studenti Registri a scorrimento e contatori

Registri a scorrimento e contatori

In questa pagina 5

Dopo aver visto i registri semplici e il controllo del caricamento (Registri e microoperazioniUn registro a $n$ bit è formato da $n$ flip-flop (più porte) con clock comune; il caricamento si controlla con clock gating (OR con il clock: risparmia potenza ma introduce ritardi e clock skew) oppure, meglio, con load enable (MUX + D FF: $Q\leftarrow D$ se EN=1, $Q\leftarrow Q$ altrimenti). Un sistema digitale complesso si divide in datapath (registri e blocchi aritmetico-logici) e unità di controllo (FSM che invia i segnali di controllo e riceve i segnali di stato). Le microoperazioni, scritte in RTL (es. $K_1:R_2\leftarrow R_1$), sono di trasferimento, aritmetiche, logiche (con maschere: AND azzera, OR pone a 1, XOR complementa) e di scorrimento.Registri e microoperazioni →), si descrivono un esempio di trasferimento da più sorgenti e due registri molto usati: registri a scorrimento e contatori. (Versione sintetica: Registri e contatoriRegistro parallelo a n bit con caricamento abilitato; banco dei registri con due porte di lettura e una di scrittura; registri a scorrimento; contatori sincroni e asincroni (ripple), contatore modulo N con esempio svolto.Registri e contatori →.)

Trasferimento tra registri selezionati da un MUX

Un registro di destinazione può ricevere il contenuto da più sorgenti (altri registri, uscite di logica combinatoria). Con un trasferimento condizionato scritto in RTL come un if-then-else controllato da due segnali K1,K2K_1,K_2: K1: R0←R1,K1‾K2: R0←R2.K_1:\ R_0\leftarrow R_1,\qquad \overline{K_1}K_2:\ R_0\leftarrow R_2 .

  • se K1=1K_1=1: R1R_1 è trasferito in R0R_0, qualunque sia K2K_2;
  • se K1=0K_1=0 e K2=1K_2=1: R2R_2 è trasferito in R0R_0;
  • se K1=0K_1=0 e K2=0K_2=0: R0R_0 mantiene il valore (hold).

Un multiplexer (Multiplexer e funzioni logiche realizzate con decoder e multiplexerIl multiplexer (MUX) $2^n$-to-1 ha $2^n$ ingressi dati, $n$ ingressi di selezione e un'uscita che copia l'ingresso selezionato: $Y=\sum_i m_i(S),I_i$. Si realizza con decoder + AND di enable + OR (costo 22 per il 4-to-1) o direttamente (costo 18). Un MUX $2^n$-to-1 realizza qualunque funzione di $n$ variabili (ingressi dati = colonna della tabella di verità); con un MUX $2^{n-1}$-to-1 si usano le $n-1$ variabili come selezione e gli ingressi dati valgono $0$, $1$, $X$ o $\overline X$ (l'ultima variabile). I MUX a vettori selezionano gruppi di bit.Multiplexer e funzioni logiche realizzate con decoder e multiplexer →) sceglie il registro sorgente; il segnale LoadLoad (=K1+K2=K_1+K_2) abilita il caricamento. Con nn sorgenti si usa un MUX nn-to-1: se i segnali di controllo sono nn linee 1-hot (una per sorgente), un encoder li trasforma nel codice binario di selezione (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 →); alcune sorgenti possono essere uscite di logica dedicata.

Registri a scorrimento (shift register)

Un registro a scorrimento è una catena di flip-flop D collegati in cascata (l'uscita di ciascuno è l'ingresso del successivo), tutti con lo stesso clock; a ogni fronte attivo i bit scorrono lateralmente di una posizione (a destra, a sinistra o in entrambe le direzioni).

Registro a 4 bit più semplice (solo FF): ingresso seriale SI = ingresso del primo FF; uscita seriale SO = uscita dell'ultimo; scorrimento a ogni fronte di salita.

Esempio. Shift register a 4 bit con SISI che porta la sequenza 1,0,1,1,0,0,0,1,1,01,0,1,1,0,0,0,1,1,0 (un bit per ciclo, partenza da 00000000); il bit entra in Q0Q_0 e avanza verso Q3Q_3:

fronte 1 2 3 4 5 6 7 8 9 10
SISI 1 0 1 1 0 0 0 1 1 0
Q3Q2Q1Q0Q_3Q_2Q_1Q_0 0001 0010 0101 1011 0110 1100 1000 0001 0011 0110
SO=Q3SO=Q_3 0 0 0 1 0 1 1 0 0 0

Dopo 4 fronti il primo bit (11) arriva a SOSO: l'uscita seriale riproduce l'ingresso con un ritardo di 4 cicli di clock (una linea di ritardo).

Shift enable. Si può abilitare lo scorrimento passando il clock attraverso una porta controllata da ShiftShift (come per il LoadLoad), ma con gli stessi problemi di clock skew: è preferibile un controllo sull'ingresso dei FF (multiplexer con hold).

Caricamento parallelo

Se si ha accesso all'ingresso di ogni FF (non solo del primo) si può caricare in parallelo il registro, e se si accede a tutte le uscite si possono leggere in parallelo i dati (anche inseriti in serie). Con questo si converte parallelo →\to serie e serie →\to parallelo.

Ogni stadio: tre AND, una OR e un D FF; due segnali di controllo, ShiftShift e LoadLoad (il clock va direttamente ai FF):

ShiftShift LoadLoad operazione
0 0 nessun cambiamento (hold)
0 1 caricamento parallelo dei DiD_i
1 X scorrimento (da Q0Q_0 verso Q3Q_3)

In RTL: Shift: Q←sl QShift:\ Q\leftarrow sl\,Q (SISI entra in Q0Q_0, ogni Qi→Qi+1Q_i\to Q_{i+1}, cioè verso il MSB) e Shift‾⋅Load: Q←D\overline{Shift}\cdot Load:\ Q\leftarrow D; l'hold è implicito quando nessuna condizione è soddisfatta. Lo scorrimento ha priorità sul caricamento (vale con Shift=1Shift=1 indipendentemente da LoadLoad).

Per lo scorrimento nella direzione opposta (dal MSB verso il LSB) il circuito è identico con gli indici di DiD_i e QiQ_i in ordine inverso: Shift: Q←sr QShift:\ Q\leftarrow sr\,Q.

Convenzione. slsl = shift a sinistra = i bit vanno verso il MSB (con Q3Q_3 disegnato a sinistra, dal LSB Q0Q_0 "da destra a sinistra"); srsr = shift a destra = i bit vanno verso il LSB ("da sinistra a destra", dal MSB al LSB). Se il comando è Shift: Q←sr QShift:\ Q\leftarrow sr\,Q i bit scorrono da sinistra a destra a prescindere dal valore di LoadLoad.

Shift register bidirezionale (universale)

Si aggiunge a ogni stadio un multiplexer 4-to-1 con selezione S1S0S_1S_0:

S1S_1 S0S_0 operazione
0 0 nessun cambiamento (hold)
0 1 shift left
1 0 shift right
1 1 caricamento parallelo

Ingressi seriali: LSI (left serial input, entra in Q0Q_0 nello shift left) e RSI (right serial input, entra in Q3Q_3 nello shift right); uscite seriali: Q3Q_3 per lo shift left, Q0Q_0 per lo shift right. In RTL: S1S0‾: Q←sr QS_1\overline{S_0}:\ Q\leftarrow sr\,Q; S1‾S0: Q←sl Q\overline{S_1}S_0:\ Q\leftarrow sl\,Q; S1S0: Q←DS_1S_0:\ Q\leftarrow D; l'hold per S1S0=00S_1S_0=00 è implicito.

Usi

Contatori

Un contatore è un registro che, in risposta a una serie di ingressi (il clock), attraversa una sequenza predefinita di stati (numeri binari, sequenza casuale...), fornendo in uscita un conteggio. Un contatore binario a nn bit conta da 00 a 2n−12^n-1. Due categorie: ripple (asincrono) e sincrono.

Contatore ripple

Una catena di FF D le cui uscite Q0,Q1,Q2,Q3Q_0,Q_1,Q_2,Q_3 danno il numero binario. Il FF del LSB riceve il clock di sistema; ogni FF successivo riceve come clock l'uscita negata del precedente; l'ingresso DD di ogni FF è collegato alla sua uscita negata Q‾\overline Q: al fronte di salita del proprio clock ogni FF cambia stato (toggle). Un reset asincrono azzera il registro.

Funzionamento: Q0Q_0 commuta a ogni fronte del clock di sistema; Q1Q_1 commuta ogni volta che Q0Q_0 passa da 1 a 0 (cioè quando Q0‾\overline{Q_0} sale); Q2Q_2 ogni volta che Q1Q_1 scende 1→01\to0; e così via. Partendo da 00000000: dopo il primo fronte Q0→1Q_0\to1 (00010001); dopo il secondo Q0→0Q_0\to0 e Q1→1Q_1\to1 (00100010); la sequenza è 0000,0001,0010,…,1111,0000,…0000,0001,0010,\dots,1111,0000,\dots.

Il tempo di transizione tra due conteggi dipende dal numero di FF: quando il conteggio passa da 01110111 a 10001000 i flip-flop cambiano uno alla volta in cascata, e per un tempo pari a 4 tp4\,t_p le uscite attraversano valori intermedi: 0111→0110→0100→0000→10000111\to0110\to0100\to0000\to1000. Letture fatte in questo intervallo danno valori sbagliati.

Contatore all'indietro: se il clock di ogni FF è l'uscita diretta (non negata) del precedente, si ottiene un conteggio verso il basso (Q1Q_1 commuta quando Q0Q_0 sale 0→10\to1).

Pro e contro. Struttura semplicissima; ma è un circuito asincrono: i bit di uscita hanno ritardi diversi rispetto al clock di ingresso, tanto più quanti più bit ci sono (problemi nella tempistica di lettura). Si usa dove non ci sono vincoli stringenti di velocità: clock a bassa frequenza o eventi rari come clock.

Contatore sincrono binario

Tutti i flip-flop sono sensibili allo stesso clock: commutano insieme, non in cascata.

Contatore sincrono binario seriale. Ingresso ENEN (abilita il conteggio) e uscita COCO (carry output, permette di costruire contatori a più stadi). Con EN=1EN=1, a ogni fronte il valore memorizzato è incrementato di 1. Ogni XOR somma 0 o 1 al bit memorizzato (il bit ii commuta quando tutti i bit inferiori valgono 1), ogni AND propaga il segnale ENEN allo stadio successivo. Il ritardo di propagazione è quello di 4 AND per COCO e di 3 AND più una XOR per Q3Q_3.

Contatore sincrono parallelo. Sostituendo la catena di AND a 2 ingressi con AND in parallelo a più ingressi si ottiene un contatore più veloce. Collegando COCO del primo di due contatori a 4 bit a ENEN del secondo si ottiene un contatore a 8 bit.

Rispetto al ripple, il contatore sincrono ha una struttura più complessa ma è più veloce e meno soggetto a problemi di temporizzazione.

VHDL

Registro a nn bit con load sincrono e clear asincrono attivo basso (parametro generic):

vhdl
library IEEE;
use IEEE.std_logic_1164.all;

entity register_wLoad is
  generic ( n : integer := 8 );
  port ( clk, clear_n, load : in  std_logic;
         D : in  std_logic_vector(n-1 downto 0);
         Q : out std_logic_vector(n-1 downto 0) );
end register_wLoad;

architecture beh of register_wLoad is
begin
  process (clk, clear_n)
  begin
    if clear_n = '0' then
      Q <= (others => '0');
    elsif clk'event and clk = '1' then
      if load = '1' then Q <= D; end if;       -- load = '0': hold
    end if;
  end process;
end beh;

Shift register a 4 bit con reset asincrono (ingresso seriale SI in Q0Q_0, uscita seriale SO =Q3=Q_3):

vhdl
entity ShiftRegister_4_r is
  port ( CLK, RST, SI : in  std_logic;
         SO           : out std_logic;
         Q            : out std_logic_vector(3 downto 0) );
end ShiftRegister_4_r;

architecture beh of ShiftRegister_4_r is
  signal shift : std_logic_vector(3 downto 0);
begin
  process (RST, CLK)
  begin
    if RST = '1' then
      shift <= "0000";
    elsif CLK'event and CLK = '1' then
      shift <= shift(2 downto 0) & SI;         -- scorrimento verso il MSB
    end if;
  end process;
  Q  <= shift;
  SO <= shift(3);
end beh;

Contatore a 4 bit con enable, reset asincrono e carry-out:

vhdl
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;

entity Counter_4_r is
  port ( CLK, RST, EN : in  std_logic;
         Q  : out std_logic_vector(3 downto 0);
         CO : out std_logic );
end Counter_4_r;

architecture beh of Counter_4_r is
  signal count : std_logic_vector(3 downto 0);
begin
  process (RST, CLK)
  begin
    if RST = '1' then
      count <= "0000";
    elsif CLK'event and CLK = '1' then
      if EN = '1' then
        count <= std_logic_vector(unsigned(count) + 1);
      end if;
    end if;
  end process;
  Q  <= count;
  CO <= '1' when (EN = '1' and count = "1111") else '0';
end beh;

Con EN=1EN=1 il conteggio avanza di uno a ogni fronte di salita e COCO diventa 1 quando il conteggio vale 11111111; con EN=0EN=0 il conteggio è fermo. Nel testbench si applica il reset iniziale, poi EN=1EN=1 per 17 cicli (per vedere il ritorno a 00000000), poi EN=0EN=0.

Errori comuni

  • Confondere ritardo del ripple (cresce con nn) con quello del sincrono (cresce molto meno).
  • Scambiare le direzioni slsl/srsr: slsl va verso il MSB.
  • Credere che nello shift register con Shift=1Shift=1 e Load=1Load=1 vinca il caricamento: vince lo scorrimento.
  • Dimenticare che nel ripple le uscite sono errate durante la propagazione.
  • Usare le uscite di un ripple counter come se fossero tutte sincrone.

Versione ripasso

Esercizi su questo argomento

Teoria collegata