Un blocco hardware con input e output si chiama modulo, ne esistono di 2 tipi:
- Comportamentale: Descrive cosa fa un modulo
- Strutturale: Descrive come è costruito un modulo, partendo da moduli più semplici
Esempio
module nomemodulo(input logic a, b, c, output logic y);
assign y = ~a & ~b & ~c | a & ~b & ~c | a & ~b & c;
endmodule
Con la sintassi module - endmodule iniziamo e chiudiamo un modulo; Input e output indicano rispettivamente gli input e output del blocco hardware e con assign definiamo l’espressione booleana da assegnare all’output.
Operatori Utilizzati:
- ~ not
- | or
- & and
Esempio
module nuovomodule(input logic a, b, c, output logic y);
assign y = ~b & ~c | a & ~b
endmodule
I linguaggi di descrizione hardware (HDL) hanno due principali obiettivi: Sintesi e Simulazione.
- Nella Simulazione forniamo valori di ingresso al modulo e controlliamo se il modulo fornisce le giuste uscite.
- Nella sintesi la descrizione del modulo viene tradotta in rete di porte logiche.
Esempio di Sintesi Modulo originale:
module funzione(input logic a, b, c, output logic y);
assign y = ~a & ~b & ~c | a & ~b & ~c | a & ~b & c;
endmodule
Il circuito sintetizzato sarà ![[es sistemi - page 4.png|]]
Definizione Una classe di componenti logiche prende il nome di idioma.
Logica Combinatoria
Gli operatori a singolo bit (bitwise) agiscono su segnali costituiti da bit singoli o su bus multibit.
Esempio di not collegato a bus 4 bit:
module neg(input logic [3:0] a, output logic [3:0] y);
assign y = ~a
endmodule
In questo caso la variabile a rappresenta un bus a 4 bit infatti indicano i bit dal più significativo al meno significativo infatti potevamo rappresentarlo anche con o Come output produciamo il complemento di ogni bit del bus.
Esempio
module porte(input logic [3:0] a,b,
output logic [3:0] y1, y2, y3, y4, y5)
assign y1 = a & b;
assign y2 = a | b;
assign y3 = a ^ b; //XOR
assign y4 = ~(a & b); //NAND
assign y5 = ~(a | b); //NOR
endmodule
Ogni y output sarà un altro bus da 4 bit
Sintesi del Circuito Gli operatori di riduzione sono costituiti da porte logiche a tanti ingressi che producono un’unica uscita.
Esempio AND a 8 ingressi
module and8(input logic [7:0] a, output logic y);
assign y = &a;
//&a equivale a scrivere a[7] & a[6] & a[5] ecc...
endmodule
Assegnamento Condizionale (MUX)
Un MUX seleziona l’uscita da generare tra varie alternative sulla base di un ingresso chiamato condizione. Esempio MUX 2:1 a 4 bit di ingresso che fa uso di assegnamento condizionale
module mux2(input logic [3:0] d0, d1, input logic s,
output logic [3:0] y);
assign y = s ? d1 : d0;
endmodule
L’operatore condizionale ? seleziona sulla base della prima espressione, la seconda o la terza espressione. In questo caso se s = 1 scegliamo d1 se s = 0 scegliamo d0.
Esempio MUX 4:1
module mux4(input logic [3:0] d0, d1, d2, d3
input logic [1:0] s,
output logic [3:0] y);
assign y = s[1] ? (s[0] ? d3 : d2) : (s[0] ? d1 : d0)
endmodule
In questo caso abbiamo due bit di scelta quindi 4 combinazioni (00, 01, 10, 11), quindi con s[1] scegliamo se andare in 0x o 1x e poi con s[0] scegliamo se andare in x0 o x1.
Variabili Locali
Le variabili locali sono utilizzate solo all’interno del modulo
Esempio
module sommatore(input logic a, b, rin,
output logic s, rout);
logic p, g;
assign p = a ^ b;
assign g = a & b;
assign s = p ^ rin;
assign rout = g | (p & rin);
endmodule
p, g non sono valori accessibili fuori dal sommatore e vengono usati solo all’interno per effettuare calcoli.
NOTAZIONI
- E’ importante utilizzare le parentesi per specificare le precedenze delle operazioni.
- I numeri possono essere spezzati con i _ 111_232323_424 ha lo stesso valore di 111232323424
- In Verilog 4’bxxxx è una notazione per rappresentare un bus di 4 bit con tutti i valori impostati al valore x corrispondente.
- Concatenazione di bit: Dobbiamo assegnare al bus y il valore a 9 bit
c2c1d0d0d0c0101:
assign y = {c[2:1], {3{d[0]}}, c[0], 3'b101}
- Con _{3{d\[0\]}}_ indico 3 volte il valore _d\[0\]_
- assign {ab, bb, cb} = ~{a, b, c} significa che:
- ab = ~a
- bb = ~b
- cb = ~c
---
## Porte Logiche con Ritardi
E' possibile assegnare dei ritardi alle istruzioni da svolgere:
```verilog
`time scale 1ns/1ps
module esempio(input logic a, b, c, output logic y);
logic ab, bb, cb, n1, n2, n3;
assign #1 {ab, bb, cb} = ~{a, b, c};
assign #2 n1 = ab & bb & cb;
assign #2 n2 = a & bb & cb;
assign #2 n3 = a & bb & c;
assign #4 y = n1 | n2 | n3;
endmodule
In questo caso come unità di misura abbiamo 1 nanosecondo con precisione 1 picosecondo, con assignx indichiamo dopo quante volte l’unità di misura viene svolta l’operazione. Quindi rispettivamente le righe verranno svolte dopo:
- 1ns
- 2ns
- 2ns
- 2ns
- 4ns
Modellazione Strutturale
Costruiamo un MUX 4:1 usando tre MUX 2:1
Definiamo un modulo per il MUX 2:1
module mux2(input logic [3:0] d0, d1, input logic s,
output logic [3:0] y);
assign s ? d1 : d0;
endmodule
Il MUX 4:1 sarà formato da un MUX 2:1 che sceglie fra altri due MUX 2:1, utilizziamo quindi il modulo mux2 3 volte passando ogni volta parametri diversi.
module mux4(input logic [3:0] d0, d1, d2, d3,
input logic [1:0] s, output logic [3:0] y);
logic [3:0] basso, alto;
mux2 muxbasso(d0, d1, s[0], basso)
mux2 muxalto(d2, d3, s[0], alto)
mux2 muxuscita(basso, alto, s[1], y)
endmodule
Quindi se s1 = 1 scelgo l’output del MUX_BASSO mentre se s1 = 0 scelgo il MUX_ALTO e loro faranno la stessa cosa con s0 e i loro input dx.
Logica Sequenziale
Flip Flop D
module flop(input logic clk, input logic [3:0] d,
output logic [3:0] q);
always_ff @(posedge clk)
q <= d;
endmodule
always_ff indica il flip flop, posedge clk indica su che fronte d’onda si attiva l’istruzione q ⇐ d, in questo caso quando l’onda è positiva.
In generale un’istruzione always ha la forma:
always @(sensitivity list)
istruzione
Abbiamo anche varie istruzione always: always_ff, always_latch, always_comb.
Le istruzioni always possono essere usate quando abbiamo bisogno di una memorizzazione, mentre istruzioni assign vengono ogni volta rieseguite da capo.
Registro resettabile in modo asincrono/sincrono
module flopr(input logic clk, input logic reset,
input logic [3:0] d, output logic [3:0] q);
always_ff @(posedge clk or posedge reset)
if (reset) q <= 4'b0;
else q <= d;
endmodule
module flopr(input logic clk, input logic reset,
input logic [3:0] d, output logic [3:0] q);
always_ff @(posedge clk)
if (reset) q <= 4'b0;
else q <= d;
endmodule
Il primo registro (Asincrono) può essere resettato anche quando il clock è a 0, il secondo (Sincrono) controlla il reset soltanto quando il clock = 1.
Registri con abilitazione (enable) Questi reagiscono al clock soltanto se la linea enable è attiva
module flopen(input logic clk,
input logic reset,
input logic en,
input logic [3:0] d,
output logic [3:0] q);
always_ff @(posedge clk, posedge reset)
if (reset) q <= 4'b0;
else if (en) q <= d;
endmodule
Registri multipli: sincronizzatore
module sync(input logic clk,
input logic d,
output logic q);
logic n1;
always_ff @(posedge clk)
begin
n1 <= d;
q <= n1;
end
endmodule
Il costrutto begin / end è necessario perché ci sono più istruzioni che vanno eseguite con l’always. Negli esempi precedenti il costrutto if / else corrisponde ad una sola operazione quindi il costrutto begin / end non era necessario.
Latch D Quando il clock è alto non interferisce e fa passare gli input, mentre quando il clock è basso mantiene lo stato ottenuto in precedenza.
module latch(input logic clk, input logic [3:0] d,
output logic [3:0] q);
always_latch
if (clk) q <= d;
endmodule
Con always_latch descriviamo un latch. Questo viene valutato ogni volta che clk o d cambiano. Se clk è alto, d attraversa i latch e arriva a q altrimenti q mantiene il valore precedente.
Negatore di always
module neg(input logic [3:0] a, output logic [3:0] y);
always_comb
y = ~a;
endmodule
always_comb rivaluta le istruzioni all’interno dell’istruzione always ogni volta che i segnali a destra di ⇐ / = cambiano. Quindi ogni volta che cambia y neghiamo a.
Il simbolo = nell’istruzione always e’ chiamato assegnamento bloccante, rispetto all’assegnamento non bloccante ⇐. E’ buona norma usare assegnamenti bloccanti in logica combinatoria e assegnamenti non bloccanti in logica sequenziale.
Sommatore Completo
module sommatore(input logic a, b, rin,
output logic s, rout);
logic p, g;
always_comb
begin
p = a ^ b;
g = a & b;
s = p ^ rin;
rout = g | (p & rin);
end
endmodule
Istruzione case
module sette_segmenti(input logic [3:0] dati,
output logic [6:0] segmenti);
always_comb
case(dati)
0: segmenti = 7'b111_1110;
1: segmenti = 7'b011_0000;
2: segmenti = 7'b110_1101;
3: segmenti = 7'b111_1001;
4: segmenti = 7'b011_0011;
//ecc
5: segmenti = 7'b111_1110;
6: segmenti = 7'b111_1110;
7: segmenti = 7'b111_1110;
8: segmenti = 7'b111_1110;
9: segmenti = 7'b111_1110;
default: segmenti = 7'b000_0000;
endcase
endmodule
Il caso appena visto non è il vero e proprio metodo, vediamo un caso reale
Decoder 3:8
module decoder3_8(input logic [2:0] a,
output logic [7:0] y);
always_comb
case(a)
3'b000: y = 8'b00000001;
3'b001: y = 8'b00000010;
3'b010: y = 8'b00000100;
3'b011: y = 8'b00001000;
3'b100: y = 8'b00010000;
3'b101: y = 8'b00100000;
3'b110: y = 8'b01000000;
3'b111: y = 8'b10000000;
default: y = 8'bxxxxxxxx;
endcase
endmodule
Quindi se a vale 000 y diventa 00000001
Se ho bisogno di utilizzare il valore don’t care posso usare il costrutto casez e ? come don’t care
module circprio_indiff(input logic [3:0] a,
output logic [3:0] y);
always_comb
casez(a)
4'b1???: y = 4'b1000;
4'b01??: y = 4'b0100;
4'b001?: y = 4'b0010;
4'b0001: y = 4'b0001;
default: y = 4'b0000;
endcase
endmodule
Il default è il campo che viene eseguito se non rientriamo in nessuno dei casi precedenti.
Assegnamenti bloccanti e non bloccanti
Usare always_ff @(posedge clk) e assegnamenti non bloccanti per modellizzare logica sequenziale sincrona
always_ff @(posedge clk)
begin
n1 <= d;
q <= n1;
end
Usare assegnamenti continui per logica combinatoria semplice
assign y = s ? d1 : d0
Usare always_comb e assegnamenti bloccanti per logica combinatoria complessa
always_comb
begin
p = a ^ b;
g = a & b;
s = p & rin;
rout = g | (p & rin);
end
Non fare assegnamenti allo stesso segnale in più di un’istruzione always o più di un’istruzione di assegnamento continuo.
Sommatore completo con assegnamenti non bloccanti
module sommatore(input logic a, b, rin,
output logic s, rout);
logic p, g;
always_comb
begin
p <= a ^ b;
g <= a & b;
s <= p ^ rin;
rout <= g | (p & rin);
end
endmodule
Contatore modulo 11, conta fino a 10 Mi servono 4 bit per implementarlo:
module contatore(input logic [3:0] count, input logic clk
input logic reset,
output logic next_count);
always_ff @(posedge clk or posedge reset)
if (reset) count <= 4'b0000;
else if (count == 4'b1010) count <= 4'b0000;
else next_count <= count + 1
endmodule
Macchine a stati finiti Implementazione di una macchina a stati finiti che riconosce una sequenza, è previsto un segnale di reset asincrono. MOORE
module sequenzeMoore(input logic clk,
input logic reset,
input logic a,
output logic y);
typedef enum logic [1:0] {S0, S1, S2} tipostato;
tipostato stato, statopross;
always_ff @(posedge clk, posedge reset)
if (reset) stato <= S0;
else stato <= statopross;
always_comb
case (stato)
S0: if (a) statopross = S0;
else statopross = S1;
S1: if (a) statopross = S2;
else statopross = S1;
S2: if (a) statopross = S0;
else statopross = S1;
default: statopross = S0;
endcase
assign y = (stato == S2);
endmodule
- L’istruzione typedef definisce tipostato come valore di tipo logico a due bit con tre possibilità: S0, S1, S2. stato e statopross sono due segnali di tipo tipostato. Quindi stato è di tipo tipostato che è un enum formato da due bit e può valere S0 S1 o S2.
- Le codifiche enumerative seguono l’ordine numerico quindi S0 = 00, S1 = 01, S2 = 10.
- Dal momento che la logica di stato prossimo deve essere combinatoria è necessaria la clausola default
- Manda 1 come output quando riconosce la sequenza S2.
- MOORE: uscita dipende solo dallo stato presente. MEALY: uscita dipende da ingresso e stato presente.
Esempio Mealy
module sequenzeMealy(input logic clk,
input logic reset,
input logic a,
output logic y);
typedef enum logic [1:0] {S0, S1, S2} tipostato;
tipostato stato, statopross;
always_ff @(posedge clk, posedge reset)
if (reset) stato <= S0;
else stato <= statopross;
always_comb
case (stato)
S0: if (a) statopross = S0;
else statopross = S1;
S1: if (a) statopross = S2;
else statopross = S1;
S2: if (a) statopross = S0;
else statopross = S1
default: statopross = S0;
endcase
assign y = (a & stato == S1);
endmodule
ESEMPI
module es(input logic clk, input enum logic [1:0] {A, L, F, F} x,
output logic [1:0] z);
type def [1:0] {SIN, SA, SAL, SALF} tipostato;
tipostato stato, statopross;
always_ff @(posedge clk)
stato <= statopross
always_comb
case (stato)
SIN: if (x == A) statoprossimo = SA
else statoprossimo = SIN
SA: if (x==A) statoprossimo = SA
else if (x==L) statoprossimo = SAL
else statoprossimo = SIN
SAL: if(x==A) statoprossimo = SA
else if (x==L) statoprossimo = SIN
else statoprossimo = SALF
SALF: if (x==A) statoprossimo = SA
else statoprossimo = SIN
default: statoprossimo = SIN
endcase
assign z[1] = (stato == SAL & x == A)
assign z[0] = (stato == SALF & x == A)
endmodule
module mux2(input d0, d1, s, output logic y);
assign y = s ? d1 : d0
module mux4(input logic d0, d1, d2, d3, s1, s0, output logic y);
logic muxalto, muxbasso
mux2(d0, d1, s1, muxalto)
mux2(d1, d2, s1, muxbasso)
mux2(muxalto, muxbasso, s0, y)
endmodule