Osnove jezika Verilog

Verilog je standardni strojno-opisni jezik (Hardware Description Language) za načrtovanje simulacijo in sintezo digitalnih vezij. To je eden izmed dveh standardnih jezikov (drugi je VHDL) za razvoj vezij na nivoju registrov. Prva standardna različica jezika je Verilog-95, danes najbolj razširjena pa Verilog 2001 (IEEE Standard 1364). SystemVerilog je nadgradnja jezika Verilog, ki omogoča še bolj učinkovito modeliranje in verifikacijo digitalnih sistemov.

Sintaksa Veriloga je nekoliko podobna jeziku C. Stavki se zaključijo s podpičjem. V izrazih uporablja enake simbole za zapis logičnih operatorjev, indeksov in komentarjev. Zaviti oklepaji imajo v Verilogu poseben pomen (operator združevanja), blok stavkov pa zapišemo med begin in end. Najpomembnejša razlika pa je v paralelnem načinu izvajanja stavkov med simulacijo, ki predstavljajo dele digitalnega vezja.

Verilog modul, signali in konstante

Modul je v jeziku Verilog osnovna enota, ki predstavlja strojno komponento z zunanjimi priključki ali pa testno komponento brez priključkov. Modul lahko uporabimo znotraj drugega modula za hierarhično opisovanje večjih vezij. Verilog dovoljuje opis več modulov v posamezni datoteki, pravila dobre prakse pa priporočajo opis enega modula, ki ima ime enako imenu datoteke.

module <ime>(<seznam_priključkov>);
<deklaracije_priključkov>;

// opis modula

endmodule

V oklepaju navedemo seznam priključkov, ki jih v deklaraciji podrobneje določimo. Ime modula in priključnih signalov so identifikatorji za katere veljajo podobna pravila pisanja kot v jeziku C. Jezik Verilog razlikuje velike in male črke. Ime je lahko sestavljeno iz črk angleške abecede, številk, podčrtaja in znaka $, prvi znak pa mora biti črka ali podčrtaj.

Priključke deklariramo tako, da zapišemo smer signala (input, inout, output) in podatkovni tip. Privzeti podatkovni tip priključkov je povezava (wire), če potrebujemo spremenljivke moramo zapisati reg. Vhodni priključki so lahko le povezave, izhodni pa so povezave ali spremenljivke.

module vezje(x, y, z);
  input [3:0] x;
  input [3:0] y;
  output reg z;
Verilog 2001 omogoča tudi krajši zapis, ker naredimo deklaracijo kar v seznamu priključkov, ki ga zaradi preglednosti zapišemo v več vrsticah. Elementi seznama so ločeni z vejico, zaključimo pa z zaklepajem in podpičjem:
module vezje2(
   input [3:0] x,
   input [3:0] y,
   output reg z
   );

Sitaksa jezika Verilog ne zahteva zamikanja, vendar pravilno zamikanje zelo poveča preglednost in berljivost kode. Podobno je s komentarji, ki olajšajo razumevanje in kasnejše popravljanje kode. Komentar naj dodatno razlaga kodo in ne opisuje očitnega. Primer slabega in dobrega komentiranja:

// SLAB komentar:
// vsota vrednosti a in b 
c = a + b;

// DOBER komentar:
// 8-bitni nepredznačen seštevalnik
c = a + b;

Signali: povezave in spremenljivke

Signale v digitalnem vezju delimo na povezave in spremenljivke. Signali imajo v strojno-opisnem jeziku podobno vlogo kot spremenljivke v programskih jezikih. Povezave določa podatkovni tip wire ali tri. Povezave so signali med izhodom in vhodom nekega gradnika in imajo vedno vrednost, ki jo določa logični izhod (driver). Večbitne povezave oz. vodila določimo z navedbo območja indeksov v oglatem oklepaju. Najprej navedemo indeks najpomembnejšega (MSB), nato dvopičje in indeks najmanj pomembnega bita (LSB).

Spremenljivke v Verilogu deklariramo s podatkovnim tipom reg ali integer, ki predstavlja 32-bitna predznačena števila. Spremenljivke imajo lastnost, da shranjujejo stanje, podobno kot delajo spremenljivke v običajnih programskih jezikih. V prvi izvedbi jezika Verilog so se spremenljivke imenovale registri (zato oznaka reg), vendar lahko z njimi opišemo tako izhode pomnilnih elementov, kot tudi kombinacijske logike. Večbitne spremenljivke reg so privzeto nepredznačene, z deklaracijo reg signed pa jih določimo kot predznačene v dvojiškem komplementu. Primer deklaracij povezav in spremenljivk:

wire x;             // enobitna povezava
wire [7:0] y;       // 8-bitno vodilo
reg [3:0] z;        // 4-bitna spremenljivka
reg signed [9:0] s; // 10-bitna spremenljivka predznačene vrednosti
integer d;          // 32-bitna predznačena spremenljivka

Verilog dovoljuje, da imajo povezave vrste wire ali tri več logičnih izhodov (driverjev). Vrednost takšne povezave se določa po pravilu: končna vrednost je x, če je vsaj en izhod x ali če so skupaj vezani izhodi vrednosti 0 in 1. Kadar so vsi izhodi 0 ali vsi 1, bo končna vrednost 0 oz. 1. Če je posamezen izhod z, ne bo vplival na vrednost, če pa so vsi z, bo tudi končna vrednost z.

Pri večbitnih vektorjih zapišemo ob deklaraciji območje indeksov pred imenom signala. Zaporedje podatkov oz. zbirko pa deklariramo tako, da določimo območje indeksov za imenom signala. Zbirko večbitnih vektorjev definiramo takole: reg [7:0] m[0:255]. Takšen podatkovni tip pride v poštev za pomnilnike ali vpogledne tabele, npr. m predstavlja pomnilnik 256 8-bitnih vrednosti. Zapis y[0] je prvi element, ki predstavlja 8-bitni vektor, drugi bit tega vektorja pa dobimo: y[0][1].

Konstante

Celoštevilske konstante so Verilogu zapisane v obliki: <velikost>'<osnova><vrednost> Velikost določa število bitov, osnova je b (binarna), d (desetiška), h (šestnajstiška) ali o (osmiška), vrednost pa sestavljajo števke v izbrani osnovi. Osnovo smemo zapisati tudi z velikimi črkami B, D, H ali O. Če opustimo velikost, je privzeta 32 bitov. Verilog po potrebi razširi podano vrednost na predpisano velikost ali pa skrči, tako da odstrani najvišje bite. Med števkami smemo vriniti podčrtaj za boljšo preglednost. Dvojiške vrednosti so:

1'b0            // enobitna logična 0
4'b0101, 4'b101 // 4-bitna vrednost 0101
15, 'hf         // 32-bitna vrednost 15

Osmiške števke predstavljajo tri, šestnajstiške pa štiri bite. Če je med njimi zapisana z ali x, se razširi na ustrezno število bitov. Celoštevilske konstante predstavljajo nepredznačene vrednosti ali pa predznačene (dvojiški komplement), če pred osnovo dodamo s ali S.

Izrazi in operatorji

Osnovni operatorji v Verilogu imajo enako sintakso kot v jeziku C. To so aritmetični, logični in relacijski operatorji, operatorji pomika in pogojni operator ?:. Na kratko jih bomo opisali in razložili še nekatere posebne operatorje.

Nad celoštevilskimi vrednostmi delamo binarne aritmetične operacije: +, -, *, /, % in unarni + in -. Rezultat seštevanje n-bitnih vektorjev ima n+1 bitov, množenja pa 2n bitov. Če rezultat priredimo n-bitnemu vektorju, bodo najvišji biti izgubljeni. Kadar imamo operanda različnih velikosti, se pred operacijo najmanjši razširi na velikost večjega. Operatorja deljenja in ostanka zahtevata kompleksno logiko in običajno nista primerna za sintezo vezja.

Binarne operacije nad biti: & and, | or, ^ xor, ~^ xnor in unarno ~ not predstavljajo logične operacije nad posameznimi biti.

Rezultat logičnih operatorjev !, && in || je enobitni signal: 0=narobe (false), 1=pravilno (true). Delujejo tudi nad večbitnimi vektorji, ki imajo pravilno vrednost, če je vsaj en bit na 1.

Relacijski operatorji: >, <, >=, <=, ==, != delujejo podobno kot v jeziku C. Rezultat je enobitna vrednost 0 ali 1, če so vsi vhodni biti na 0 ali 1. Če je katerikoli bit x ali z, bo rezultat operacije nedefinirana vrednost x. Posebna operatorja === in !== pa znata primerjati tudi vrednosti, ki vsebujejo x ali z.

Verilog pozna logično pomikanje: <<, >> in aritmetično pomikanje vrednosti: <<<, >>>. Aritmetični pomik v levo daje enak rezultat kot logični, pri pomiku v desno pa pride do razlike kadar pomikamo predznačene vrednosti.

Operatorji reduciranja: &, ~&, |, ~|, ^, ~^ so unarni operatorji, ki izvajajo logično operacijo nad vsemi biti vektorja in določijo enobitni rezultat. Npr. &x naredi konjukcijo vseh bitov vektorja.

Indeksiranje opišemo z oglatimi oklepaji, da pridemo do posameznega bita vektorja ali elementa niza. Če zapišemo v oklepajih območje indeksov, je rezultat ustrezno velik podvektor. Z zavitimi oklepaji pa opišemo združevanje, ko iz posameznih signalov ali podvektorjev naredimo večji vektor. Z operatorjem repliciranja pa opišemo večkratno združevanje signalov. Npr.

  wire [7:0] x;
  wire [3:0] y;

  {x[2:0], y}              // x[2], x[1], x[0], y[3], y[2], y[1], y[0]
  {x[3], x[2], x[1]}       // enako kot x[3:1]
  {y[0], y[1], y[2], y[3]} // obrne signal y 
  {1'b0, x}                // doda vodilno 0 signalu x
  {4{x[0]}}                // repliciranje bita, enako kot {x[0], x[0], x[0], x[0]}
  {2{y[1:0]}}              // repliciranje podvektorja, kot {y[1:0], y[1:0]}