Opis vezja v jeziku Verilog

Strojno-opisni jeziki ponujajo različne načine opisovanja oz. modeliranja logičnega vezja. V jeziku Verilog ločimo:

Prvi trije modeli predstavljajo jezikovni opis sheme vezja, ki ga sestavljajo logična vrata, gradniki z digitalnimi operatorji ter hierarhično sestavljena vezja. Zadnji opis uporabljamo za visokonivojsko načrtovanje vezij s procesi, ki je podobno pisanju kode v programskem jeziku. Model obnašanja vezja bomo podrobneje predstavili v naslednjem poglavju: Model obnašanja.

Opis logičnih vrat

Verilog ima vgrajene modele najbolj osnovnih sestavnih elementov logičnih vezij: logičnih vrat, ojačevalnikov, tranzistorjev ipd., s katerimi sestavimo opis vezja. To je najnižji način modeliranja vezij, kjer eksplicitno med seboj povezujemo osnovne elemente. Primer:

wire x, y;
wire z;

and(z, x, y);  // vrata IN, izhod je z, vhoda sta x in y
not(z, x);     // negator z izhodom z in vhodom x

Modeli logičnih vrat predstavljajo Boolove funkcije, pri katerih je prvi argument izhod, ostali pa so vhodi. Standardna logična vrata and, or, nor, xor, nand, xnor imajo dva ali več vhodov. Poleg teh so na voljo še modeli različnih vrst ojačevalnikov (buf, buif0, bufif1), ki le prenašajo vrednost iz vhoda na izhod. Gradnikom je mogoče določiti tudi časovne zakasnitve. Modeli vezja na ravni logičnih vrat z zakasnitvami niso primerni za sintezo vezja, zato jih ne bomo podrobneje razlagali.

Opis pretoka podatkov

Model pretoka podatkov obravnava digitalno vezje kot skupek operacij nad signali in prenosov podatkov med vhodi in izhodi vezja. Operacije opišemo s prireditvenimi stavki (assign), ki so precej bolj kompaktni kot opis vezja na nivoju logičnih vrat.

Primer: polni seštevalnik opišemo z dvema stavkoma, enega potrebujemo za vsoto, drugega pa za izhodni prenos.

assign s    = a ^ b ^ cin;
assign cout = (a & b) | (cin & (a ^ b));

Stavek assign imenujemo kontinuirani prireditveni stavek. Simulator izvede ta stavek vsakokrat, ko se spremeni katerikoli signal na desni strani prireditvenega operatorja. Izraz na desni strani prireditve postane logični izhod (driver) za signal na levi strani. Prireditveni stavek lahko združimo z deklaracijo signala in v tem primeru ne potrebujemo stavka assign:

wire s = a ^ b ^ c;

Opis polnega seštevalnika z logičnimi vrati bi zahteval pet stavkov:

xor(x, a, b);
xor(s, x, cin);
and(c1, a, b);
and(c2, cin, x);
or(cout, c1, c2);

Operatorji jezika Verilog so zelo uporabni za modeliranje Boolovih funkcij in transformacij podatkov s kombinacijskimi vezji. Poglejmo še nekaj praktičnih primerov vezij, ki jih opišemo s kontinuiranim prireditvenim stavkom in operatorji.

Izbiralnik (multiplexer)

Izbiralnik ima en izhodni signal, na katerega poveže izbrani podatkovni vhod, ki ga določa vrednost na izbirnem vhodu sel. Najmanjši izbiralnik 2-1 ima dva podatkovna vhoda: in0 in in1. Logično funkcijo izbiralnika opišemo s stavkom: assign out = (in1 & sel) | (in0 & ~sel);

Bolj enostaven in razumljiv je opis izbiralnika 2-1 s pogojni operatorjem ?:. V tem primeru so podatkovni vhodi in izhod lahko tudi večbitne vrednosti. Primer izbiralnika 4-bitnih vrednosti:

module mux2_1
(
 input sel,
 input [3:0] in0,
 input [3:0] in1,
 output [3:0] out
 );

assign out = sel ? in1 : in0;

endmodule

Še bolj učinkovit pa je opis izbiralnika z operatorjem indeksiranja. Podatkovni vhod deklariramo kot vektorski signal, vrednost izbirnega vhoda pa uporabimo kot indeks za izbiro vhodnega bita, ki ga priredimo izhodu. Primer izbiralnika 8-1:

module mux8_1
(
 input [2:0] sel,
 input [7:0] in,
 output out
 );

assign out = in[sel];

endmodule

Razdeljevalnik (demultiplekser)

Razdeljevalniki izvajajo inverzno funkcijo: en vhod razdelijo na dva ali več izhodov. Razdeljevalnik s pozitivno logiko deluje tako, da je na izbranem izhodu vrednost vhoda, ostali pa so na 0. Narejeni so iz logičnih vrat AND in negatorjev. Za vsak izhodni signal je potrebno določiti logično funkcijo v obliki kontinuirane prireditve, lahko pa izhode združimo v vektor in zapišemo le en prireditveni stavek:

module demux1_4  // razdeljevalnik s štirimi izhodi
(
 input        in,
 input  [1:0] s,
 output [3:0] out
);

assign out = {in & ~s[0] & ~s[1],
              in &  s[0] & ~s[1],
              in & ~s[0] &  s[1],
              in &  s[0] &  s[1]};
endmodule

Še bolj učinkovito pa opišemo razdeljevalnik z operatorjem pomikanja: assign out = in << sel;

8-bitni seštevalnik s prenosom

module adder
( 
  input [7:0] a,
  input [7:0] b,
  output [7:0] sum,
  output carry 
);

assign {carry, sum} = a + b;
  
endmodule

Strukturni opis vezja

Osnovna enota opisa vezja je modul. Modul lahko vsebuje druge enote (module), ki jih vključimo in povežemo v zapisu:

<ime_modula> <ime_instance>(<povezave_signalov>);

Vključenemu modulu pravimo inštanca. Vsaki inštanci določimo ime za identifikacijo in razlikovanje med več enotami, saj lahko vključimo več modulov enake vrste. V oklepaju navedemo povezovalne signale, ki so signali deklarirani na ravni modula v katerega vključujemo drug modul.

Primer opisa štiribitnega seštevalnika, ki je sestavljen iz štirih polnih seštevalnikov (za lažje razumevanje dodajamo tudi celoten opis polnega seštevalnika full_adder, ki je sicer v svoji datoteki):

module full_adder(
    input a,
    input b,
    input cin,
    output s,
    output cout
    );

   assign s    = a ^ b ^ cin;
   assign cout = (a & b) | (cin & (a ^ b));
endmodule

module adder(
   input [3:0] a,
   input [3:0] b,
   output [3:0] sum
   );

   wire c0, c1, c2;

   full_adder u0 (a[0], b[0], 1'b0, sum[0], c0 ); 
   full_adder u1 (a[1], b[1], c0,   sum[1], c1 );
   full_adder u2 (a[2], b[2], c1,   sum[2], c2 );
   full_adder u3 (a[3], b[3], c2,   sum[1],    );
endmodule

Polni seštevalnik vključimo v strukturni opis vezja tako, da navedemo ime modula full_adder, enolično oznako, npr. u1 in povezave signalov v oklepaju. Povezave signalov so v obliki seznama, kjer je povezava določena z zaporednim mestom signala v oklepaju. Ta oblika opisa povezav se imenuje pozicijski zapis, ki je krajši, vendar postane nepregleden pri večjem številu priključkov in lahko vodi do napak. Če nek signal ni povezan, kot v primeru izhodnega prenosa zadnjega seštevalnika, moramo vseeno narediti vejico.

Verilog pozna tudi daljši (imenski) zapis, pri katerem eksplicitno določimo kateri priključek vključenega modula je povezan z določenim signalom vezja. Primer takšnega zapisa za prvi polni seštevalnik:

full_adder u0 ( 
    .a(a[0]),
    .b(b[0]),   
    .s(sum[0]),
    .cin(1'b0),
    .cout(c0)
   );

Eksplicitno povezovanje zahteva več pisanja, vendar je manj podvrženo napakam in ne zahteva določitve vseh povezav, ki so lahko zapisane v poljubnem vrstnem redu.