Documente Academic
Documente Profesional
Documente Cultură
A: Tutorial de Verilog.
B: Introducere in FPGA-uri. Plăci Altera DE2
1. Introducere
1.1. Ce este Verilog-ul ?
1.2. Cateva caracteristici
1.3. Modelarea unui system digital cu Verilog
1.3.1. Modelul flux de date
1.3.2. Modelarea comportamentala
1.3.3. Modelarea structurala
1.3.4. Modelarea mixtă
Obiective:
dobândirea unor cunostinţe de bază prinvind modelarea sistemelor digitale
folosind limbajul de descriere hardware Verilog. În cele ce urmează, prezentul
material oferă o descriere succintă a principalelor noţiuni (subset Verilog) în
vederea modelării şi sintezei unor sisteme digitale de complexitate medie.
1. Introducere
Verilog-ul este un limbaj de descriere hardware (HDL) care permite modelarea unui
sistem digital la diferite niveluri de abstractizare: algoritmic, comportamental, portă
logică, nivelul comutator (switch level). Acesta se bucură de o mare popularitate în
rândurile designerilor de hardware şi a producătorilor de tool-uri CAD. Din acest
considerent, Verilog HDL a fost transformat într-un standard IEEE, ultima acţiune de
acest tip concretizându-se prin standardul IEEE 1364-2001.
Există mai multe stiluri de a modela un sistem în Verilog. Elementul central in Verilog
este constituit de noţiunea de modul. Acesta poate fi folosit pentru a modela atât elemente
simple, cât şi sisteme complexe. În esenţă un design Verilog complex poate fi asimilat cu
o multitudine de module interconectate, reprezentând subansamblele sale. În cadrul unui
modul se pot întalni următoarele stiluri de modelare:
• Modelarea sub formă de flux de date;
• Modelarea comportamentală;
• Modelarea structurală;
• Modelarea mixtă;
Acestea vor fi succint prezentate prin nişte exemple în cele ce urmează.
`timescale 2ns/1ns
assign #2 a=b;
Figura 1: Celulă sumator pe 1 bit. Cod Verilog HDL pentru modelul de tip flux de
date
Trebuie insistat asupra faptului că aceste instrucţiuni se execută concurent (nu contează
ordinea în care au fost scrise, ele bucurându-se de calitatea de execuţie paralelă, - aspect
similar cu execuţia portilor logice dintr-un design hardware).
Pentru a exemplifica acet tip de modelare vom folosi acelaşi dipozitiv – celula de
însumare completă.
Figura 2: Celulă sumator pe 1 bit. Cod Verilog HDL pentru modelarea comportamentală
O clauză always trebuie să fie prevăzută cu un fel de control pentru timp. Acesta poate fi
reprezentat fie de întârzieri (wait – aşteaptă un interval de timp), fie de producerea unui
eveniment (wait – aşteaptă modificarea unui semnal din lista de senzitivităţi). În lipsa
acestora, codul este executat la infinit.
Foarte important este şi cuvântul cheie reg. Acesta nu face referire la regiştri întâlniţi în
calculul digital. Aceştia din urmă îşi modifică valoarea sincron cu un semnal de tact.
În Verilog HDL, cuvântul cheie reg denotă o variabilă care poate memora o valoare la
un moment dat.
Acestea sunt instanţiate în componentele unui design, şi sunt conectate prin net-uri.
Acestea se declară folosind cuvântul cheie wire. Reprezintă analogul firelor/traseelor din
design-urile fizice.
Figura 4: Celulă sumator pe 1 bit. Cod Verilog HDL pentru modelarea mixtă
Lista de porturi defineşte interfaţa unui modul – maniera prin care comunică cu
exteriorul. Porturile pot fi de trei tipuri:
• Intrare – input
• Ieşire – output
• Bidirecţionale – inout
Un port este în mod implicit de tip net (reamintim că tipul net este asimilat unei
conexiuni fizice – vezi secţiunea 2.3.1). Opţional (dacă este folosit în cadrul unei
construcţii always) porturile de ieşire pot si declarate de tip reg.
Declaraţiile de porturi se pot face în cadrul listei de porturi, fir pot constitui parte a
modulului (module port declaration style versus module port list style). În cele ce
urmează cele două stiluri vor fi exemplificate pentru a se evidenţia diferenţele.
În Verilog HDL, pentru claritate şi lizibilitate este proferabil să sie folosit module port
declaration style, întrucât atât porturile cât şi tipurile de date sunt specificate într-un
singur loc.
Din punct de vedere al corectitudinii codului, cele două tipuri de realizare a declaraţiilor
sunt echivalente.
module nume_modul
# (parameter param1=valoare1, param2=valoare2, ...
parameter param3=valoare3, ...)
(listă_porturi);
declaraţii_şi_instrucţiuni
endmodule
nume_modul nume_instanţă(listă_asocieri_porturi);
Asocierea porturilor din listă se poate face implicit, prin poziţia semnalelor asociate, sau
explicit printr-o construcţie de forma (prin nume):
Această expresie poate fi o variabilă sau un net, un câmp sau sub-şir dintr-un şir, o
concatenare a celor anterior precizate, sau o expresie (numai pentru porturile care sunt de
intrare). Trebuie avut în vedere, faptul că nu este admisă folosirea simultană în cadrul
aceleiaşi instanţe a asocierii poziţionale cu cea bazată pe nume. De asemenea, asocierea
prin poziţie reclamă respectarea întocmai a ordinii şi tipului expresiilor asociate
porturilor. Atribuirea prin poziţie, admite schimbarea ordinii expresiilor asociate
porturilor în raport cu declaraţia de modul.
În continuare este prezentat un exemplu pentru un sumator de tip ripple carry pe 4 biţi.
Acesta foloseşte celula de însumare descrisă în secţiunea 1, modificată astfel încât să
permită un parametru de timp – întârzierea pentru o poartă logică.
a)
b)
b)
Figura 8: Sumator cu propagarea serială a transportului (ripple carry adder) a- modul care
descrie celula de însumare; b- modul care descrie sumator cu propagarea serială a
întârzierii. Exemplu pentru diferitele modalităţi de realizare a instanţierii.
Ca şi ultimă remarcă, Verilog HDL este foarte flexibil în ceea ce priveşte declararea şi
instanţierea unui modul, oferind fiecărui programator posibilitatea de a-şi organiza codul
astfel încât să fie cât mai accesibil cu putinţă. Totuşi, este recomandat ca declaraţiile să
always
[control_timp] construcţie_procedurală
O clauză always trebuie să fie prevăzută cu un fel de control pentru timp. Acesta poate fi
reprezentat fie de întârzieri (wait – aşteaptă un interval de timp), fie de producerea unui
eveniment (wait – aşteaptă modificarea unui semnal din lista de senzitivităţi). În lipsa
acestora, codul este executat la infinit. Cea mai folosită construcţie procedurală este
blocul secvenţial de tip - begin … end.
Lista de senzitivităţi defineşte semnalele care determină execuţia unui always. Aceasta
poate fi sensibil la front (crescător sau descrescător), sau la palierul unui semnal (pozitiv
sau negativ). Este important de precizat că o listă de sensitivităţi nu permite atât semnale
care sunt sensibile la front cât şi semnale sensibile la palier. Această diferenţă este
deosebit de importantă în contextul circuitelor secvenţiale. Pentru logica combinaţională
regula este simplă – şi anume – se vor trece în această listă toate semnalele care produc o
modificare a ieşirilor.
Regiştri sunt o colecţie de FF. Cu toate că latch-urile sunt folosite pentru a construi
regiştri (configuraţie master-slave), ele apar rareori ca elemente de sine stătătoare în
cadrul unui design digital.
Atribuirea de tip:
• Blocking
– uzitează de operatorul “=” şi presupune evaluarea şi actualizarea
imediată (instantanee) a valorii variabilei din stânga egalului;
• Non-blocking
– uzitează de operatorul “<=” şi presupune evaluarea expresiei din
dreapta şi actualizarea ei la sfârşitul pasului de simulare (la
terminarea blocului always);
– toate atribuirile de acest tip din cadrul unui bloc always sunt
evaluate cu valorile variabilelor dinainte de execuţia blocului. Cu
alte cuvinte, dacă avem o variabilă reg care îşi modifică la un
moment dat valoarea în cadrul blocului always, ca urmare a
evaluării expresiei din dreapta, această modificare nu este una
imediată, proximele evaluări folosind valoarea de la intrarea în
blocul always (valoarea veche);
Sunt situaţii în care, în ciuda acestor diferenţe se ajunge la acelaşi rezultat. În continuare
câteva exemple folosind declaraţii de tip always.
Un exemplu în acest sens prezintă codul Verilog HDL pentru un codificator binar 4 la 2
(4-to-2 binary encoder), şi rezultatul obţinut în urma sintezei (interpretarea pentru codul
respectiv).
În Verilog HDL lista de sensitivităţi aferentă unei construcţii always nu poate conţine
atât semnale care sunt active pe front cât şi semnale active pe palier, ele sunt active fie
pe front, fie pe palier.
b)
Figura 13: Exemplu de logică secvenţială – descriere bistabilului de tip D cu reset - a)
sincron b) asincron
Foarte mare atenţie trebuie acordată diferenţei dintre atribuirea de tip blocking şi cea de
tip non-blocking. În acest sens este prezentat un exemplu simplu cu două flip-flop-uri de
tip, pentru care este prezentat rezultatul obţinut în urma sintezei.
Pentru atribuirea de tip blocking, comportamentul pentru codul prezentat mai jos este
următorul – valorile folosite pentru toate evaluările sunt cele calculate în interiorul
blocului, şi pot fi diferite de cele de la intrarea în blocul always. De asemenea, atribuirile
se fac in-order şi valoarea nou calculată este actualizată imediat. În exemplul de mai jos,
valoarea pentru g este calculată folosind noua valoare a lui f.
Ultimele două exemple vin să confirme diferenţele majore care pot să apară la rezultatele
de sinteză, ca urmare a unor modificări aparent minore ale sintaxei.
2.2.1. Constante
Valoare Comentariu
Verilog HDL
0 0 logic sau FALS
1 1 logic sau ADEVĂRAT
X sau x necunoscută
Z sau z impedanţă ridicată
Tabel 1: Set de valori Verilog HDL
Simbolul “_” din interiorul unei constante reale sau întregi este ignorat. El se foloseşte de
regulă pentru îmbunătăţirea lizibilităţii.
-32
15
• bază de numeraţie
⎧ 2 ' b11
Constanta 3 exprimată ⎨ - în binar pe 2 biţi
⎩ 2 ' b11
⎧ 9 ' so72
⎪12 ' so772
⎪
Constanta -6 exprimată ⎨ -în octal pe 9, respectiv 12 biţi
⎪ 12 ' so 7 _ 72
⎪⎩12 ' so72
⎧ 3' d − 7
⎪
Incorect exprimate ⎨3' so772
⎪(1 + 2) ' so7 _ 72
⎩
⎧3' oz
Şir extins de ⎨ echivalent cu zzz, respectiv xxx
⎩3' bx
• notaţie ştiinţifică
Constantele de tip şiruri sunt definite ca secvenţe de caractere între ghilimele duble. Nu
pot fi scrise pe mai multe rânduri. Pot conţine caractere speciale definite cu ajutorul lui
backslash:
”Constanta SIR”
Tipul net este folosit pentru a descrie o conexiune fizică între mai multe elemente de
structură (componente ale unui design). Valoarea net-ului este determinată de sursele
pentru semnalul pe care îl transportă. Valoarea default în cazul în acre nu există o sursă
este z.
Există mai multe tipuri de net-uri, care în ultima variantă a standardului au fost multe
dintre ele echivalate:
• Wire şi tri
Wire este cel mai folosit în design-urile Verilog HDL. Tradiţional s-a folosit
pentru reprezentarea conexiunilor cu o singură sursă de semnal (signal driver).
Ulterior a devenit identic ca şi semnificaţie cu tri – semnal multisursă (mai multe
declaraţii de tip assign atribuie valoarea aceluiaşi semnal). Valoarea semnalului în
acest caz este decisă cu ajutorul tabelului.
• Wor şi trior
Wor sau trior este echivalentul unui or-cablat între toate sursele unui semnal.
Comportamentul este descris în tabelul ce urmează.
• Trireg
Modelează noduri capacitive – când toate sursele de semnal sunt impedanţă ridicată,
net-ul reţine ultima valoare.
• Tri0 şi tri1
Sunt folosite pentru a modela funcţii logice cablate, având un tabel de funţionare
asemănător cu cel prezentat pentru wire (sau tri). Diferenţa survine pentru situaţia în
care semnalul sursă nu are driver pe net, caz în care valoarea este 0 logic (pentru tri0),
respectiv 1 logic (pentru tri1).
• Supply0 şi supply1
Supply0 este folosit pentru a modela legătura la masă, iar supply1 modelează legătura
la alimentare.
• Net-uri nedeclarate
Verilog HDL permite nedeclararea tipului de net, situaţie în care în mod automat
(dacă nu se specifică explicit altceva), ea este de tip wire pe un singur bit. Schimbarea
acestui default, se face prin directiva de compilator default_nettype:
`default_nettype none
Ex.:
reg [3:0] counter_reg, buf_reg; //ieşirea unui numărator, respectiv registru
reg test0; //variabilă pe 1 bit
2.2.4. Diferenţa între reg şi wire
parameter MSB=31, LSB=1; // declararea de parametri în cadrul
//modulului
reg signed [MSB:LSB] adr_bus;
reg signed [MSB:LSB] operand1,operand2;
Un reg reprezintă o variabilă care poate revendica unul sau mai mulţi biţi. Nu trebuie
confundat şirul de biţi cu un array de biţi care reprezintă o memorie.
rom.dat conţine
11011111
11000000
10101010
01010101
Variabile întregi
Acestea sunt uzual folosite pentru a realiza descrieri de nivel înalt folosind Verilog HDL.
Sintaxa pentru declararea unei variabile întreg este:
În baza notei de mai sus, următoarele atribuiri (care sunt însoţite de conversii implicite)
sunt corecte:
integer i;
reg [7:0] reg1;
…
i=7;
reg1=i; // reg1 ia valoarea 0000…0111 exprimată pe 32 de biţi
//conversia se realizează implicit
…
O variabilă real este identică cu o variabilă de tip realtime. Sintaxa pentru declararea ei
este:
real real1, real2, …, realn[MSB:LSB];
//msb, lsb specifică indexii unui array de numere reale
Valoarea default pentru variabila real este 0. De asemenea daca i se atribuie unei
variabile real un şir de biţi care conţine x şi z, aceştia din urmă vor fi transformaţi în 0
logic.
Verilog HDL oferă suport pentru lucrul cu semnale pe mai mulţi biţi. Astfel două din
facilităţile cele mai importante vizează operaţia de concatenare, precum şi adresarea unor
sub-şiruri binare. Ele sunt prezentate prin două atribuiri continue în exemplul următor:
assign {a[7:0],a[15:8]}={a[15:8],a[7:0]};
assign {cout,s}=op1+op2+cin;
//rezulatatul este extins la nr. de biţi din partea stângă
Operatorii Verilog HDL se pot clasifica astfel: operatori aritmetici, operatori relaţionali,
operatori bit-cu-bit (bit-wise), operatori logici, operatori de reducere, operatori de
comparare, operatori de şiftare, operatori de concatenare şi replicare, şi operatorul
Operator Comentariu
Aritmetici
Shiftare
Logici
&& Şi logic
|| Sau logic
! Negare logică
Bit cu bit
De reducere
De egalitate
Ex.:
my_reg1=8`b00101100;
my_reg2=8`b1111110;
| my_reg1 // este 1
& my_reg2 //este 0
my_r1=4`b11xz;
my_r2=4`b11xz;
initial
[control_timp] construcţie_procedurală
De regulă aceasta este folosită pentru a realiza o iniţializare, sau generarea unor forme de
undă în cadrul unor entităţi de test.
Ex.:
Un exemplu referitor la folosirea construcţiei initial este oferit în secţiunea 3.2 – sub
forma unui testbench.
if (condiţie_1)
construcţie_procedurală_1
[else
construcţie_procedurală_2]
endcase
endcase
module ALU_unit
(input wire [31:0] op_a,
input wire [31:0] op_b,
input wire [3:0] opcode_instr,
output reg rezultat);
endmodule
Forever-loop execută secvenţa de cod la infinit. Din aceste considerente este necesar un
fel de control de timp (de exemplu introducerea unei întârzieri). Pentru instrucţiunea
forever sintaxa este de forma:
forever
construcţie_procedurală
Acesta poate fi folosit pentru generarea unui semnal de tact cu perioada de 10 unităţi de
timp.
initial
begin
semnal_tact=1`b0;
Repeat-loop execută secvenţa de cod de un număr specificat de ori. Dacă contorul este x
sau z el este interpretat ca 0. Pentru instrucţiunea repeat sintaxa este de forma:
repeat (contor_nr_iteraţii)
construcţie_procedurală
repeat (contor)
@ (posedge tact) sum= sum+1;
//aşteaptă contor fronturi crescătoare ale semnalului de tact
//pe frontul crescător al tactului incrementează sum
While-loop execută secvenţa de cod cât timp este indeplinită condiţia. Dacă contorul este
x sau z el este interpretat ca 0 (FALSE). Pentru instrucţiunea while sintaxa este de forma:
while (condiţie)
construcţie_procedurală
For-loop execută secvenţa de cod un număr finit de ori. Pentru instrucţiunea for sintaxa
este de forma:
for (atribuire_iniţializare ; condiţie ; adunare_pas)
construcţie_procedurală
integer i;
for (i=0; i<nr_pos; i=i+1)
acc=acc<<1;
Un bloc poate primi o etichetă, caz in care el poate conţine declaraţii de variabile.
Acestea din urmă sunt statice (au valori valide pe tot parcursul simulării).
begin
[:identificator_bloc [declaraţii_locale_bloc]]
instrucţiuni_procedurale
end
begin
out1= x & y;
//se execută după atribuirea anterioară, pe frontul crescător al semnalului de tact
@ (posedge semnal_tact)
out2=x | y;
end
Foarte utile pentru design-urile care necesită replicarea unor subcomponente sunt
instrucţiunile de tip generate. Acestea permit selecţia sau replicarea unor secvenţe de cod
în faza premergătoare simulării (faza de elaborare / elaboration time) în care toate
modulele din design sunt conectate şi referinţele ierarhice sunt soluţionate. Un bloc
generate este înrămat de cuvintele cheie generate … endgenerate.
module xor_w
( input wire [7:0] in1,in2,
output wire [7:0] x
);
//generate-loop-ul
generate
for (i=0; i<8; i=i+1)
begin: multiplicare_xor
xor my_xor (x[i], in1[i],in2[i]);
end
endgenerate
endmodule
În faza de elaborare a ierarhiei de module corpul for-ului este replicat pentru fiecare
iteraţie, astfel se obţine:
Există şi posibilitatea realizării unei selecţii ăn faza de elaborare, prin testarea unei
condiţii statice (depinde exclusiv de evaluarea unor constante şi/sau parametri). Similar
se poate folosi şi o construcţie de tip case pentru a realiza o selecţie condiţionată
multiplă.
Dacă modificăm exemplul anterior, astfel încât primii 4 biţi sunt rezultatul unui xor, iar
următorii ai unui and logic, codul Verilog HDL se modifică astfel :
instrucţiuni_procedurale;
endtask
Foarte important este cuvântul cheie automatic. Dacă acesta este prezent, variabilele
locale task-ului sunt alocate dinamic (fiecare apel de task are propriul set de variabile
locale). În caz contrar, ele sunt statice. Trebuie ţinut cont de caracterul concurent al
execuţiei componentelor/blocurilor în Verilog HDL. Acest fapt, poate conduce la situaţia
în care un task este concurent din mai multe părţi ale codului. Fiecare task beneficiază de
Funcţiile Verilog HDL se declară în cadrul unui modul şi pot fi apelate din diferite părţi
ale codului. Prezintă următoarele deosebiri faţă de anterior prezentatele task-uri:
• pot returna o valoare, de tip real, integer, time, real şi realtime;
• nu pot conţine întârzieri;
• pot apela alte funcţii, dar nu pot apela alte task-uri;
• trebuie să aibă cel puţin un argument;
instrucţiuni_procedurale;
endfunction
module exemplu
( input wire [7:0] my_byte,
output wire [8:0] paritate_ext_byte);
function automatic paritate;
input [7:0] data; //input declaration
reg p_bit; //local declaration
2.8.integer i; cu fişiere
Lucrul //local declaration
begin //begin … end block statement
p_bit=1’b0;
for (i=0; i<8; i=i+1)
if (data[i])
p_bit=p_bit ^ data[i];
paritate=p_bit;
end
endfunction
assign paritate_ext_byte={my_byte, paritate(my_byte)};
endmodule
În exemplul anterior este folosită funcţia sistem $time, care returnează timpul de
simulare.
• Pentru monotorizare:
$monitor – monotorizează lista de argumente continuu şi afişează mesajul
redactat la sfârşitul pasului de timp în care unul sau mai multe semnale din listă
sau modificat. Prin sfârşitul pasului de timp se înţelege momentul de timp la care
toate evenimentele aferente pasului respectiv s-au finalizat.
initial
$monitor (“Timp: %t, semnal tact: %b valoare ieşire: %b”, $time,clk,out1);
$fopen – deschide un fişier şi returnează o valoare de tip întreg care este handler-
ul fişierului respectiv ;
Un fişier poate fi deschis pentru: citire (mod este “r”, “rb”) , scriere (mod este
“w”, “wb”), adăugare (mod este “a”, “ab”), scriere şi citire (mod este “w+”,
“w+b”, “wb+”, “r+”, “r+b”, “rb+”), adăugare şi citire (“a+”, “a+b”, “ab+”).
“b” se referă la fişiere binare.
$readmemb (“nume_fişier”,nume_memorie,adr1,adr2);
• De conversie şi formatare:
Conversie:
$rtoi(număr_real) – converteşte un număr real într-un întreg prin trunchiere;
• Altele:
Există mai multe modalităţi de specificare a întârzierilor în Verilog HDL, ele sunt
exemplificate în cele ce urmează:
întârziere prin declaraţia semnalului
Indică faptul că orice modificare a driver-ului semnalului (în acest caz expresia din partea
dreaptă care este evaluată continuu) este întârziată cu 15 unităţi de timp.
control de tip întârziere pentru descrierea comportamentală
out1= #(2+3) a ^ b ^ cin ; //expresia din dreapta este evaluată, apoi este introdusă
//întârzierea, urmată apoi de atribuirea propriuzisă
Comportamentul este identic cu:
begin
aux= a ^ b ^ cin ;
#(2+3) out1=aux ;
end
Un testbench este în esenţă un modul folosit pentru verificarea funcţionării corecte a unui
design. Acesta are trei sarcini mai importante:
• generarea sau încărcarea dintr-un fişier a stimulilor de test (valorilor de intrare
pentru verificare);
Generarea stimulilor
Generarea unei secvenţe de valori de intrare poate fi realizată cu ajutorul unei construcţii
de tip initial.
initial
begin
//prima variantă de stimuli pentru a testa o celulă de însumare
a=1’b0;
b=1’b1;
cin=1’b1;
//după 20 unităţi de timp modifică valoarea lui a
#20 a=1’b1;
//după 20 unităţi de timp modifică valoarea lui cin şi a lui b
#20 cin=1’b0;
b=1’b0;
end
Pattern-urile repetitive se pot obţine prin iniţializare, urmată de o atribuire continuă, sau o
construcţie de tip always.
//initializare
initial
tact=1’b0;
//modificare continua
assign #(PERIOADA/2) tact=~tact; // PERIOADA- constanta sau parametru modul
Sau:
//initializare
initial
tact=1’b0;
//modificare continua
always
#(PERIOADA/2) tact=~tact; // PERIOADA- constanta sau parametru modul
//generare stimuli
initial
begin
//citeşte stimuli în memorie
$readmemb(“mem.dat”,memorie);
adder_cell_struct uadder_cell(a,b,cin,s,cout);
endmodule
4. Bibliografie
1. IEEE Standard Verilog Hardware Description Language – IEEE Std 1364-2001
2. S. Kilts – Advanced FPGA Design: Architecture, Implementation and
Optimization – John Willey&Sons, 2007
3. D. Thomas, P. Moorby – The Verilog Hardware Description Language, Fifth
Edition – Kluver Academics, 2002
4. J. Bhasker – A Verilog HDL Primer, Third Edition – Star Galaxy Press, 2005
5. S. Brown, Z. Vranesic – Fundemantels of Digital Logic with Verilog Design –
McGraw-Hill, 2007
6. MIT Introductory Digital Systems Laboratory -
http://ocw.mit.edu/OcwWeb/Electrical-Engineering-and-Computer-Science/6-
111Spring-2006/CourseHome/index.htm