Osnove Verilog HDL jezika: ========================== LEKSICKA SVOJSTVA VERILOGA: =========================== * Komentari su kao u C/C++-u * Beline se preskacu i ignorisu (kao i u C-u). * Jezik razlikuje mala i velika slova (case-sensitive) * Identifikatori: kao u C-u, s tim sto i karakter $ moze da ucestvuje u nazivu, ali ne kao pocetni karakter. Ugradjene specijalne funkcije obicno pocinju znakom $. * Celobrojne konstante se mogu zadavati u dekadnom, binarnom, oktalnom i heksadekadnom formatu. Sintaksa je: ' Velicina je broj bitova, osnova je b, d, h ili o (mogu i velika slova B,D,H,O, po ukusu), dok je vrednost sam broj zapisan u datoj osnovi. Ako se velicina izostavi, podrazumeva se 32. Ako se i osnova izostavi, podrazumeva se dekadni zapis. Heksadekadne cifre a, b, c, d, e, f se mogu pisati i kao velika i kao mala slova. Ako je velicina veca nego sto je neophodno, zapis se prosiruje vodecim nulama. Ako je manja, skracuju se visi bitovi. Dozvoljeno je koristiti znak _ izmedju cifara, za povecanje citljivosti. Vrednost cifre u svim osnovama (osim dekadne) takodje moze biti z ili x. Prva vrednost znaci "visoka impedansa" (eng. floating), sto u stvari predstavlja "otkacenu zicu", dok x predstavlja nedefinisanu vrednost. Pri prosirivanju zapisa, ako je vodeca cifra z ili x, tada se zapis prosiruje tom istom cifrom. U oktalnom zapisu jedna z cifra znaci tri z bita, dok u heksadekadnom zapisu jedna z cifra znaci cetiri z bita. Sve celobrojne konstante su podrazumevano neoznacene. Ako se ispred oznake osnove (d, h, b, o) navede s (ili S), tada se ta vrednost smatra oznacenom, sa zapisom u potpunom komplementu (npr. 'sd54 je 32-bitni oznacen ceo broj zapisan dekadno, a 16'shffaa je 16-bitni oznaceni ceo broj zapisan heksadekadno). Oznaka znaka s ne utice na internu bitovsku reprezentaciju vrednosti, vec samo na to kako ce se ona tumaciti (tj. da li cemo je tumaciti kao neoznaceni ceo broj ili kao oznaceni ceo broj u potpunom komplementu). PRIMERI: 54, 12, 65, 464 // dekadne neoznacene konstante (sirine 32 bita) 'h5f23, 'o4317, 'b01101101, 'd593 // konstante sa eksplicitno // zadatom osnovom (sirine 32 bita) // zapis se do 32 bita // prosiruje nulama 16'hffff, 12'd59, 3'b101 // konstante sa ekplicitno zadatom // sirinom (u bitovima) 'sd53, 'sd12, 16'sh54ff // oznacene konstante 16'h5czz, 'bz011, 'o32z // primeri konstanti sa 'z' bitovima * Stringovi se zapisuju izmedju dvostrukih navodnika kao i u C-u. Tipicno se koriste prilikom ispisa poruka, na slican nacin kao i u C-ovoj funkciji printf(). * Jezik takodje sadrzi i izvestan broj operatora i kljucnih reci o kojima ce biti vise reci kasnije. OSNOVNI TIPOVI PODATAKA U VERILOGU ================================== Podaci u verilogu predstavljaju signale koji se prenose kroz kolo koje se dizajnira. Svaki signal moze biti jednobitni ili visebitni. Visebitni signali se zovu VEKTORI. Svaki bit signala moze uzimati sledece vrednosti: 0: logicka nula 1: logicka jedinica x: nedefinisana vrednost z: vrednost visoke impedanse Vrednosti 0 i 1 su najcesce rezultat toga sto je signal povezan sa odgovarajucim izvorom niskog, odnosno visokog napona. Nedefinisana vrednost obicno znaci da signal ili jos uvek nije inicijalizovan (poput neinicijalizovane varijable u C-u) ili postoji neka logicka nekonzistentnost u kolu koja onemogucava da se jasno definise vrednost u toj tacki (npr. na isti signal povezemo i nulu i jedinicu) Vrednost visoke impedanse mozemo razumeti kao "otkacenu zicu", tj. u pitanju je najcesce signal koji se dobija na izlazu nekog "iskljucenog" kola (tj. kola ciji je "Enable" ulaz iskljucen, pa na izlazu ne daju nista). Dva osnovna tipa podataka (signala) u verilogu su: -- zice (eng. "net types" ili "wires") -- registri (eng. registers) Prvi tip konceptualno predstavlja mehanizam za povezivanje kola i logickih sklopova. Na svaku zicu se moze povezati izlaz nekog kola A, kao i ulaz nekog kola B, cime se izlaz A povezuje na ulaz kola B. Svaka zica samim tim predstavlja tacku u kolu u kojoj se moze izmeriti vrednost signala. Ono sto je karakteristicno za zice je da one same po sebi nemaju mogucnost da cuvaju neku vrednost, tj. nemaju memorijsku sposobnost. Vrednost zice je uvek odredjena vrednoscu signala koji se na nju dovodi iz nekog drugog izvora (koji zovemo vodeci signal, eng. "driver"). Svaki zica moze imati i vise drajvera, ali je bitno da oni ne budu u koliziji. Najjednostavniji zicani tip je wire tip. Na primer: wire x; // deklarise jednobitni podatak tipa wire wire [7:0] y; // deklarise 8-bitni podatak tipa wire wire [31:0] z; // deklarise 32-bitni podatak tipa wire wire [0:7] x; Dakle, kada deklarisemo visebitne signale, treba u zagradama navesti (dvotackom razdvojene) indeks bita najvece tezine i indeks bita najmanje tezine. U nastavku se sa signalima moze pristupati celovito (x, y, z), ali i pojedinacnim bitovima (y[5], z[12], i sl.), kao i grupama bitova (y[4:2] daje trobitni signal koji se sastoji iz bitova y[4], y[3] i y[2]). Alternativno ime za wire tip je tri. Nema nikakve razlike izmedju ova dva tipa, tj. u pitanju su sinonimi. Obicno se upotreba ova dva imena primenjuju u cilju citljivijeg koda: wire se obicno koristi kada imamo signal sa samo jednim drajverom, dok se tri koristi kada imamo signal sa vise drajvera (kada do izrazaja dolazi mogucnost slaganja signala, kao sto opisujemo u nastavku). Tip wire ima osobinu da moze da ima vise drajvera, u kom slucaju se vrednost signala dobija na sledeci nacin: ako je bar jedan od drajvera nedefinisan (x) takva je vrednost i wire signala. Ako drajveri imaju definisanu vrednost (0 ili 1), ali su im vrednosti razlicite, tada je rezultat opet nedefinisan. Ako su svi drajveri 0 ili svi 1, tada je takav i rezultat. Najzad, ako je neki od drajvera jednak z, tada on ne utice na vrednost signala. Ako su svi drajveri z, tada je i rezultat z. Pored ovog najcesceg zicanog tipa, postoje jos neki tipovi od kojih pominjemo sledece: -- wand (ili triand): ponasa se potpuno isto, jedino sto favorizuje nulu, tj. ako je bar jedan od drajvera 0, tada je rezultat 0, bez obzira na ostale drajvere. -- wor (ili trior): slicno kao i prethodno, samo favorizuje 1, tj. ako je bar jedan od drajvera 1, tada je rezultat 1, bez obzira na ostale drajvere. Sa druge strane, registarski tipovi konceptualno predstavljaju signal koji ima memorijsko svojstvo, tj. u njega se moze "upisati" vrednost koju on onda cuva dokle god mu se ta vrednost ne promeni. Obicno ove signale zamisljamo kao izlaze iz nekih registara (otud i ime registarski tipovi), mada treba napomenuti da u praksi prilikom sinteze kola ne mora zaista postojati registar koji cuva vrednost ovog signala. Iz programerskog ugla, registarski podaci imaju mnogo slicnosti sa klasicnim promenljivama iz proceduralnih programskih jezika (moze im se dodeliti vrednost koja se kasnije moze procitati, i sl.). Najjednostavniji registarski tip je reg. Na primer: reg x; // deklarise jednobitni podatak tipa reg reg [7:0] y; // deklarise 8-bitni podatak tipa reg Deklarisanje visebitnih (vektorskih) registarskih podataka je isto kao i kod zicanih tipova. Pored ovog tipa, postoje jos i tipovi: integer -- predstavlja 32-bitni reg tip, s tim sto se vrednost ovog tipa tumaci kao oznacen broj u potpunom komplementu. Obicno se koristi za brojace i slicne "meta" promenljive, a redje za delove koda koje treba sintetizovati u stvarno kolo. time -- predstavlja 64-bitni reg tip koji moze cuvati informaciju o proteklom vremenu, tj. broju vremenskih jedinica koje su protekle od nekog trenutka. Ova vrednost se tumaci kao neoznacena. Tipovi wire i reg su podrazumevano neoznaceni, ali se to moze promeniti dodavanjem signed kljucne reci: wire signed [7:0] x; // oznacena 8-bitna vrednost wire tipa reg signed [31:0] y; // oznacena 32-bitna vrednost reg tipa Verilog omogucava i kreiranje nizova podataka. Pritom, treba napraviti razliku izmedju vektora i niza. Vektor je jedan podatak koji se sastoji iz vise bitova. Niz je sekvenca vise podataka (potencijalno visebitnih). Na primer: wire x[0:99]; // definise niz od 100 jednobitnih signala. Indeksi // u nizu su od 0 do 99. reg [7:0] y[0:255] // definise niz od 256 8-bitnih podataka. Zapis y[0] predstavlja element u nizu sa indeksom 0. Zapis y[0][2] predstavlja bit na indeksu 2 u elementu niza sa indeksom 0. Nizovi su narocito korisni za predstavljanje memorija (npr. gore deklarisani podatak y je u stvari memorija koja se sastoji od 256 bajtova). Moguce je kreirati i visedimenzione nizove, npr: reg [7:0] x[0:99][0:255]; // dvodimenzioni niz 100x256 ciji su // elementi 8-bitne vrednosti registarskog tipa. Elementu se sada moze pristupati pomocu x[i][j], gde je i prvi indeks (od 0 do 99) a j drugi indeks (od 0 do 255). Iz ovog podatka se dalje mogu izdvajati bitovi po zelji (npr. x[2][3][5] ili x[2][3][5:2]). IZRAZI I OPERATORI U VERILOGU ============================= Verilog podrzava bogat skup operatora. U slucaju binarnih operatora, ako operandi nisu iste sirine, tada se uzi tip prosiruje na broj bitova sireg tipa. Vecina operatora se jednostavno moze sintetisati, tj. napraviti konkretno hardversko kolo koje definise njihovo ponasanje. Izuzetak su slozeni aritmeticki operatori, poput deljenja i ostatka po modulu. * Operator indeksiranja ([]): ovaj operator smo vec pominjali, koristi se za pristup bitovima u vektorima, tj. izdvajanje podsignala u okviru vektorskog signala, kao i za pristup elementima niza. * Operator za pristup ugnjezdenim imenima (.): slicno kao u C-u, kada se pristupa clanovima struktura. Vise o ovome kasnije. * Aritmeticki operatori: +, -, *, /, %, kao i unarni + i -. Ovi operatori su slicni kao i u C-u. Medjutim, treba imati u vidu da je rezultat sabiranja dva n-bitna podatka zapravo n+1-bitni podatak (najvisi bit je bit prekoracenja). Proizvod dva n-bitna podatka je 2n-bitni podatak. Zato ako rezultat ovakvih operacija dodelimo podatku koji takodje ima samo n bitova, visi bitovi ce biti izgubljeni. Dalje, treba naglasiti da se operatori / i % tesko mogu sintetizovati (jer zahtevaju veoma slozenu logiku), tako da se vise koriste u testiranju i simulaciji, dok ih treba izbegavati u kolima koja zelimo da u praksi sintetizujemo, tj. preslikamo na stvarni hardver. * Logicki operatori: !, &&, ||. Ovi operatori rade isto kao i u C-u. Visebitni signal se smatra logicki tacnim akko je bar jedan njegov bit jednak 1. Rezultat ovih operatora je jednobitni signal sa odgovarajucom vrednoscu. * Relacioni operatori: >, <, >=, <=, ==, !=, ===, !==. Ovi operatori funkcionisu kao u C-u, a daju za rezultat jednobitnu odgovarajucu vrednost. Poslednja dva operatora su zanimljiva: uobicajeni relacioni operatori daju rezultat x kad god je bar neki od bitova u bilo kom od operanda jednak x ili z (tj. rade samo za potpuno definisane vrednosti). Operatori === i !== mogu da uporedjuju i bitove sa ovim specijalnim vrednostima, npr. 3'b01z == 3'b01z daje x, dok 3'b01z === 3'b01z daje 1. * Bitovski operatori: &, |, ^, ~, ~^. Ovi operatori rade nad pojedinacnim bitovima, isto kao i u C-u. operator ~^ je zapravo negacija ekskluzivne disjunkcije (ekvivalentno sa ~(x ^ y)). * Operatori pomeranja: <<, >>, <<<, >>>. Operatori su slicni kao u C-u, s tim sto su prva dva logicko, a druga dva aritmeticko pomeranje (naravno, aritmeticko pomeranje u levo je isto kao i logicko, tj. << i <<< rade identicno). Pritom, aritmetiko pomeranje u desno >>> ima efekta samo ako je podatak koji se pomera oznacenog tipa (deklarisan kao signed). Ako nije, tada se >>> ponasa identicno kao >>. * Operatori redukcije (&, ~&, |, ~|, ^, ~^): ovo su unarni operatori koji obavljaju odgovarajucu logicku operaciju nad svim bitovima jednog signala i kao rezultat daju jednobitni signal sa odgovarajucom vrednoscu. Na primer &x daje jedan bit koji nastaje konjunkcijom svih bitova u signalu x. Operatori sa ~ ispred predstavljaju odgovarajucu negaciju (tj. NAND, NOR, NXOR). * Operatori grupisanja ({}, {{}}): u pitanju su dva operatora koji omogucavaju kreiranje novih signala grupisanjem postojecih (na neki nacin, ovi operatori su inverz indeksnog operatora koji izdvaja podsignale iz visebitnih signala). Prvi operator omogucava grupisanje signala, npr: wire [7:0] x; wire [3:0] y; { x[2:0], y } // dobijamo 7-bitni signal koji se sastoji iz // bitova x[2], x[1], x[0], y[3], y[2], y[1], y[0] { x[3], x[2], x[1] } // isto sto i x[3:1] { y[0], y[1], y[2], y[3] } // obrce signal y u ogledalu { 1'b0, x } // dopisuje jednu vodecu nulu na signal x Drugi operator je operator replikacije, koji omogucava da se isti signal ponovi vise puta, npr: {4{x[0]}} // isto sto i {x[0], x[0], x[0], x[0]} {2{y[1:0]}} // isto sto i {y[1:0], y[1:0]} Broj ponavljanja mora biti konstanta. * Uslovni operator (?:): Isto kao i u C-u. KONCEPT MODULA: =============== Modul u verilog-u predstavlja osnovnu jedinicu dizajna i tipicno predstavlja hardversku komponentu koju zelimo da napravimo. Svaki modul moze imati ulaze i izlaze koji predstavljaju signale koji respektivno, ulaze i izlaze iz komponente koju dizajniramo (ulaze i izlaze drugacije zovemo portovi). Manji moduli se mogu instancirati u okviru vecih modula (kao sto se jednostavnija logicka kola koriste u izgradnji slozenijih). Takodje, u verilog-u postoje i moduli za koje nije cilj da se sintetizuju, vec se koriste iskljucivo za testiranje drugih modula. Ovi moduli najcesce nemaju portove, a sintaksa veriloga koja se u njima koristi je obicno znatno slobodnija, jer ne moramo da vodimo racuna o tome da li se nesto moze ili ne moze sintetizovati u stvarnom hardveru. Sintaksa kreiranja modula je sledeca: module (); ; // kod endmodule // kraj modula U istom fajlu veriloga se obicno moze definisati veci broj modula, mada je cesto dobra praksa da se za svaki modul kreira poseban fajl sa kodom koji se zove isto kao i modul (sa ekstenzijom .v). Portovi modula se navode svojim identifikatorima koji su razdvojeni zarezima. Nakon zaglavlja slede deklaracije portova. Za svaki port treba definisati da li je ulazni, izlazni, ili ulazno-izlazni. Na primer: module my_module(x, y, z); input [3:0] x; output y; inout z; Za portove se podrazumeva da su tipa wire. Ovo se moze i eksplicitno naglasiti: input wire [3:0] x; output wire y; inout wire z; ali za tim nema potrebe. Medjutim, ako zelimo da ne budu tipa wire, onda to moramo eksplicitno naglasiti, npr: output reg y; Samo izlazni portovi mogu biti deklarisani kao reg. Ulazni i ulazno-izlazni portovi mogu biti samo wire. Modul se moze instancirati u okviru drugog modula. Tom prilikom se navodi sledeca deklaracija: (); Signali koji se povezuju sa portovima su zapravo neki signali koji postoje u modulu u okviru koga instanciramo nas modul. Pri navodjenju, oni se razdvajaju zarezima (kao argumenti funkcija u C-u). Medjutim, ponekad je moguce da se na neki port ne dovede ni jedan signal (npr. neki izlaz nas ne zanima, neki ulaz nije bitan, i sl.). U tom slucaju se prosto ostavi prazan prostor u listi signala, npr: moj_modul _mm1(x, y, , z, ); // ovde su treci i peti port ostali nepovezani Drugi nacin da se ovo uradi je eksplicitno navodjenje signala: moj_modul _mm1(.x(x), .y(y), .z(z), .u(), .w()); Ovde se iza tacke navodi naziv porta u modulu moj_modul, a u zagradama se navodi naziv signala iz spoljnog modula koji se povezuje na ovaj port (ne moraju se zvati isto, ali cesto to jeste slucaj). Za one portove koji ostaju nepovezani prosto se stave prazne zagrade. U ovom slucaju nije bitan redosled navodjenja portova. Prilikom povezivanja, na ulazni port se moze povezati signal tipa wire ili reg (odgovarajuce sirine), dok se na izlazni (i ulazno-izlazni) port moze povezati samo wire signal. Kada se neki modul instancira u okviru drugog modula, moguce je direktno pristupati signalima u okviru instanciranog modula pomocu operatora '.' kao i u C-u. Npr. _mm1.x predstavljace port x u okviru _mm1 instance moj_modul modula. Slicno, ako je unutar moj_modul-a definisan podatak d tipa wire (ili reg), tada se pomocu _mm1.d iz spoljnog modula moze pristupiti ovom signalu. Ova mogucnost se obicno koristi samo za debagovanje, dok je u ostalim situacijama treba izbegavati. U okviru modula, moguce je definisati i parametre. Parametar je konstanta (celbrojnog tipa) koja se koristi kasnije u kodu. Npr: parameter BIT_WIDTH = 8; Ono sto je zgodno je to sto se ova konstanta moze promeniti pri instanciranju modula i time se promeniti odgovarajuci parametar (npr. sirina magistrale i sl.). Na primer, ako je gornji parametar definisan u okviru modula moj_modul, tada se prilikom instanciranja moze navesti sledece: moj_modul #(16) _mm1(); Ovim ce se parametar BIT_WIDTH postaviti na 16 umesto na podrazumevanih 8. Ukoliko u modulu postoji deklaracija vise parametara, tada se oni navode istim redom prilikom instanciranja: moj_modul #(5, 6, 7) _mm1(); U ovom primeru, 5 ce biti vrednost prvog deklarisanog parametra u modulu moj_modul, 6 ce biti vrednost drugog, a 7 vrednost treceg. U kodu modula se ocekuje da se odgovarajucim izlazima pridruze neke vrednosti. Postoji vise nacina da se to uradi, o cemu govorimo u nastavku. MODELOVANJE NA NIVOU GEJTOVA (GATE-LEVEL): ========================================== Ovo je najnizi nivo modelovanja, a sastoji se u dizajnu kola na nivou osnovnih logickih kola koja se onda eksplicitno povezuju u slozenije kolo. Na primer: wire x, y; wire z; and(z, x, y); // ovim se kreira AND kolo ciji je izlaz z, a ulazi su // x i y not(z, x); // ovim se kreira NOT kolo ciji je izlaz z, a ulaz x Generalno, uvek je prvi argument izlaz, a ostalo su ulazi (kojih u slucaju and, or, nor, xor, nand, xnor kola moze biti dva ili vise). Svi ulazi (kao i izlaz) moraju biti jednobitni. Pored pomenutih standardnih kola, definisana su i sledeca kola: -- buf(x, y) -- uzima vrednost y i salje na izlaz x. Baferi se tipicno koriste za simulaciju kasnjenja, jer inace ne menjaju vrednost ulaznog signala, vec ga samo prosledjuju na izlaz. -- bufif0(x, y, e) -- vrednost ulaza y se prosledjuje na izlaz x akko je ulaz e jednak 0. U suprotnom, vrednost izlaza je z. -- bufif1(x, y, e) -- slicno, samo e treba da bude 1 -- notif0(x, y, e) -- isto, samo sto ce x biti negacija od y -- notif1(x, y, e) -- slicno Napomenimo da postoje i jos prostije logicke primitive (poput cmos tranzistora, nmos tranzistora, otpornika i sl.) ali se ovde time necemo baviti. Logickim kolima se moze zadati kasnjenje po zelji. Na primer: and #(5) and(z, x, y); // rezultat se propagira na izlaz sa kasnjenjem // koje iznosi 5 vremenskih jedinica Sintaksa zadavanja kasnjenja je nesto od sledeceg: #n #(n) #(n,m) #(n,m,k) Prva dva slucaja su ekvivalentna -- uvodi se kasnjenje od n vremenskih jedinica. U trecem slucaju, kasnjenje n se odnosi na prelaz sa 0 na 1, dok se kasnjenje m odnosi na prelaz sa 1 na 0. U cetvrtom slucaju, kasnjenje n se odnosi na prelaz sa 0 na 1, m na prelaz sa 1 na 0, dok se k odnosi na kasnjenje prilikom prelaska u stanje visoke impedanse (z). Ako se navede samo n, tada se ono koristi kod svih prelaza, ako se navedu m i n, tada se za prelaz u z koristi min(m, n). Za prelaz u stanje x (ako tako nesto kolo dozvoljava) uvek se koristi najmanje od navedenih kasnjenja. Napomenimo da su kasnjenja korisna pri simulaciji, medjutim, ona se ni na koji nacin ne mogu sintetizovati. Prosto, hardverska kasnjenja zavise od konkretne tehnologije izrade i to je nesto sto ne mozemo mi da "isprogramiramo". U fazi sinteze se najcesce vrsi ponovno testiranje, s tim sto alati za sintezu najcesce poznaju konkretne vrednosti kasnjenja za tip tehnologije za koji se sinteza vrsi. MODELOVANJE NA NIVOU PROTOKA PODATAKA (DATAFLOW): ================================================= Na ovom nivou, modelovanje se sastoji u formiranju izraza (pomocu ranije opisanih operatora) i pridruzivanja tih izraza odgovarajucim signalima. Ovi izrazi ce u fazi sinteze biti zamenjeni odgovarajucim kombinatornim kolom koje se sastoji iz odgovarajucih osnovnih logickih kola, ali se na ovaj nacin sam programer/dizajner oslobadja mukotrpnog posla rucnog povezivanja odgovarajucih primitiva. Osnovni mehanizam modelovanja na nivou protoka podataka je assign naredba: wire [3:0] x; wire y, z; wire [2:0] p; assign x = { y | ~z, { y, {2{z}} } ^ ~p }; Sa desne strane se formira 4-bitni signal. Ovaj signal se pridruzuje podatku x. Ova naredba se zove naredba KONTINUIRANE DODELE. Ova dodela se prilicno razlikuje od koncepta dodele u proceduralnim programskim jezicima. Kontinuirana dodela znaci da se signal (zica) x trajno povezuje na izlaz kola koje izracunava signal na desnoj strani. Ovim signal na desnoj strani postaje drajver za zicu x. Svaki put kada se bilo koji od signala koji ucestvuju u izrazu promeni, vrsi se ponovno izracunavanje vrednosti izraza, cime se odmah menja i vrednost signala x (u proceduralnim jezicima poput C-a, dodela se izvrsi u jednom trenutku, nakon cega promenljiva sa leve strane vise nije ni na koji nacin povezana sa izrazom na desnoj strani; ako zelimo da joj ponovo promenimo vrednost, moramo ponovo da izvrsimo dodelu). Modelovanje na nivou protoka podataka je znacajno jednostavnije, jer sada mozemo na intuitivniji nacin da zapisemo zeljeno izracunavanje. Na primer, gornji izraz bi se na nivou gejtova morao opisati sledecim kodom: wire z1; wire [2:0] p1; not(z1, z); or(x[3], y, z1); not(p1[2], p[2]); xor(x[2], y, p1[2]); not(p1[1], p[1]); xor(x[1], z, p1[1]); not(p1[0], p[0]); xor(x[0], z, p1[0]); Naredba assign takodje dozvoljava definisanje kasnjenja, radi vernije simulacije. Kasnjenje se navodi iza assign kljucne reci: assign #5 x = y; Ova naredba je ekvivalentna sa: buf #5 (x, y); na nivou gejtova. Najzad, umesto naredbe assign, moguce je koristiti inicijalizaciju prilikom deklaracije wire podatka: wire x = y | z; Ovo je ekvivalentno sa: wire x; assign x = y | z; MODELOVANJE PONASANJA (BEHAVIOURAL MODELING) ============================================ Modelovanje ponasanja je najapstraktniji (i najmocniji) nacin za opis hardvera u Verilog-u. Ovaj nacin najvise lici na klasicno programiranje. Ipak, postoje i znacajne razlike, jer se odredjeni verilog kod izvrsava dosta drugacije nego sto bi se izvrsavao analogni kod u nekom proceduralnom jeziku. Takodje, na ovom nivou treba voditi racuna da dobijene konstrukcije ne budu previse komplikovane, jer se tada ne bi mogle sintetizovati u hardveru. Tipicno, programi za sintezu podrzavaju samo jedan podskup svih verilog konstrukcija, a potrebno je izvesno iskustvo u verilogu da bi se znalo sta se i kako moze sintetizovati. Sa druge strane, moduli koji sluze iskljucivo za simulaciju i ne treba da se sintetizuju mogu sadrzati proizvoljan verilog kod. * PROCESI --------- U Verilog-u, proces je aktivnost koja se izvrsava u nekom trenutku. Obicno se sastoji iz niza izracunavanja koja treba obaviti. Postoje dva osnovna tipa procesa: initial i always. Verilog procesi se izvrsavaju u nekom vremenu ciji protok diktira rad simulatora. Osnovna vremenska jedinica se odredjuje direktivom `timescale (u verilog-u postoje direktive, nalik C-u. One pocinju znakom `. Pored `timescale, postoje i `define, `ifdef, `include i sl. cija je semantika slicna onoj u C-u). Na primer, ako zelimo da nam protok vremena bude izrazen u nanosekundama, treba da (na pocetku fajla, pre definicija modula) stavimo: `timescale 1ns/1ns Prvo vreme oznacava osnovnu vremensku jedinicu. Drugo vreme oznacava preciznost sa kojom se vreme meri (ako se stavi nesto manje, npr 1ps, tada je moguce zadavati i razlomljene delove nanosekunde u specifikacijama kasnjenja i slicnim situacijama, npr. #5.1). Verilog simulator se izvrsava tako sto broji vremenske jedinice i u svakoj vremenskoj jedinici izvrsava operacije (dogadjaje) koje su rasporedjene u toj vremenskoj jedinici. Rasporedjivanje operacija je prilicno kompleksan proces (videti dole detaljnije), ali u osnovi postoji nekoliko redova u koje se dogadjaji smestaju, medju kojima je najznacajniji tzv. red aktivnih dogadjaja. U ovom redu se u svakom trenutku nalaze operacije koje su spremne za izvrsavanje u tekucoj vremenskoj jedinici. Ove operacije simulator izvrsava odmah i to u proizvoljnom redosledu -- ovo znaci da mozemo smatrati da je njihovo izvrsavanje konkurentno i da se ne moze pretpostaviti krajnji efekat operacija, u slucaju da one jedna od druge zavise. Ovaj model dobro oslikava situaciju u stvarnom hardveru, gde se u svakom trenutku mnogi signali paralelno prenose i uticu na neke druge signale, pa se prilikom dizajna ne mozemo osloniti na sekvencijalnost prenosenja signala. Ukoliko se za neku operaciju definise kasnjenje, tada se ta operacija smesta u red buducih dogadjaja koji ce se aktivirati (tj. preci u aktivan red) u trenutku kada simulator izbroji odgovarajuci broj vremenskih jedinica. Verilog simulator u svakom trenutku barata sa vecim brojem procesa koji su definisani i koji se paralelno izvrsavaju. Dogadjaji, tj. operacije koje treba izvrsiti, samim tim, dolaze nezavisno iz razlicitih procesa. Pored procesa, dogadjaje generise i assign naredba kontinuirane dodele (koja nije deo ni jednog procesa, vec je naredba, tj. neka vrsta procesa sama za sebe) -- svaki put kada se promeni vrednost nekog od signala na desnoj strani ove naredbe izaziva dogadjaj izracunavanja desne strane (evaluation event) a zatim i dogadjaj azuriranja vrednosti leve strane (update event). Svako azuriranje vrednosti moze izazvati nove dogadjaje evaluacije koji se odmah dodaju u aktivni red, i tako dok se taj red ne isprazni. Sve ovo se desava u istoj vremenskoj jedinici, osim ako nije specificirano kasnjenje u nekoj od naredbi. Najzad, postoji i red dogadjaja "neblokirajucih dodela". Dogadjaji iz ovog reda se izvrsavaju na kraju odgovarajuce vremenske jedinice, nakon sto se izvrse svi dogadjaji iz reda aktivnih dogadjaja. Ovaj red sluzi za podrsku semantici "neblokirajucih dodela" (vidi dole). Initial proces je proces koji se izvrsava samo jednom, pocev od nulte vremenske jedinice u radu simulatora (tj. odmah po pokretanju simulacije). Sintaksa ovog procesa je: initial Ako postoji vise od jedne naredbe (sto je tipican slucaj), tada se koriste begin i end: initial begin // naredbe end Kasnije cemo videti koje sve naredbe tu mogu da stoje. Vreme potrebno za izvrsenje ovog procesa zavisi od naredbi -- ukoliko neke od njih sadrze definiciju kasnjenja, tada to produzava rad initial bloka. U suprotnom, isti se izvrsava u 0-toj vremenskoj jedinici. Na primer: initial begin x <= 0; y <= 1; end Ovaj proces se pokrece samo jednom (u 0toj vremenskoj jedinici) i postavlja signale x i y (koji moraju biti registarskog tipa) na date vrednosti. Sa druge strane: initial begin x <= 0; #20 y <= 1; end ovaj proces nakon sto upise 0 u x, mora da saceka 20 vremenskih jedinica, pa tek onda da u y upise 1. Zato ce ceo proces trajati 20 vremenskih jedinica (od pocetka simulacije). Drugi tip procesa u verilog-u je always. Ovaj proces se, za razliku od initial procesa, iznova pokrece cim se zavrsi i tako dokle god traje simulacija. Sintaksa mu je ista kao kod initial procesa, samo sto se koristi kljucna rec always. Naravno, ako bi se ovaj proces izvrsio trenutno, tj. u jednoj vremenskoj jedinici, tada bismo ga odmah pokretali ponovo, sto bi dovelo do beskonacne petlje u aktivnom redu. Zbog toga se kod always procesa uvek na neki nacin kontrolise njegovo pokretanje. To se radi na dva nacina: -- dodavanjem kasnjenja u naredbe u procesu, npr: initial clk <= 0; always #20 clk <= ~clk; Prvi (initial) proces inicijalizuje (odmah) clk signal na 0. Drugi (always) proces se pokrece takodje odmah, ali pre izvrsenja naredbe mora da saceka 20 vremenskih jedinica, nakon cega se invertuje clk signal. Odmah zatim se ponovo pokrece always proces koji opet ceka jos 20 vremenskih jedinica pre nego sto invertuje clk signal i td. -- specifikacijom dogadjaja koji aktivira proces, npr: always @(p or q) begin p <= q; q <= p; end Ovaj proces se aktivira svaki put kada se promeni vrednost bilo p bilo q signala. Efekat procesa je zamena vrednosti p i q (videcemo kasnije zasto to radi ispravno). Sa druge strane: always @(posedge p or negedge q) begin p <= q; q <= p; end aktivira proces ili kada se p promeni sa 0 na 1 (na pozitivnoj ivici) ili kada se q promeni sa 1 na 0 (na negativnoj ivici). Ovo je zgodno kod sekvencijalnih kola, gde npr. zelimo da se nesto desava kao reakcija na pozitivnu ivicu signala sata. Naglasimo da se naredbe u okviru begin-end slozene naredbe uvek izvrsavaju sekvencijalno, tj. po redu kako su navedene. Alternativno, umesto begin-end moze se koristiti fork-join blok, u kome se sve naredbe izvrsavaju konkurentno. Samim tim specifikacija kasnjenja se u begin-end bloku uvek racuna od zavrsetka prethodne naredbe, dok se u fork-join bloku uvek racuna od pocetka izvrsavanja bloka. Konkurentnost se u verilog-u, naravno, ispoljava i u tome sto se naredbe iz razlicitih procesa konkurentno izvrsavaju. * NAREDBA PROCEDURALNE DODELE ----------------------------- Postoje dve vrste naredbe proceduralne dodele: blokirajuce i neblokirajuce. Blokirajuce se izvrsavaju tako sto se izracuna izraz na desnoj strani, a zatim se azurira vrednost promenljive na levoj strani. Ovo ponasanje je najslicnije naredbi dodele u proceduralnim programskim jezicima. Sintaksa ove naredbe je: = ; Naziv blokirajuca potice od toga sto se naredbe koje slede u bloku naredbi iza te naredbe nece izvrsavati pre nego sto se azuriranje vrednosti leve strane ne zavrsi (upravo ovako radi i dodela u proceduralnim programskim jezicima). Na primer: initial begin p = q; q = p; end Ovde ce se vrednost q upisati u p, pa ce se tek onda zapoceti izvrsavanje druge dodele kojom se vrednost p upisuje u q. Efekat je da se u q upisuje nova vrednost podatka p, a to je upravo stara vrednost q. Neblokirajuca naredba proceduralne dodele ima sintaksu: <= ; Ova naredba se izvrsava u dve etape: najpre se izracuna izraz na desnoj strani, ali se izracunata vrednost ne upisuje odmah u promenljivu na levoj strani. Umesto toga, vrednost izraza se zapamti, a nastavlja se dalje sa sledecom naredbom u bloku. Na kraju tekuce vremenske jedinice, kada se aktivni red isprazni, vrsi se azuriranje promenljive sa leve strane. Dakle, ova naredba ne blokira izvrsavanje narednih naredbi u bloku, jer se one izvrsavaju pre nego sto se izvrsi upis u promenljivu. Na primer: initial begin p <= q; q <= p; end Sada se najpre izracuna desna strana prve naredbe (q), ali se ta vrednost ne upisuje u p. Zatim se izracuna desna strana druge naredbe (to je vrednost p pre promene, jer nova vrednost jos nije upisana), i ta vrednost se takodje zapamti interno za kasnije. Na kraju vremenske jedinice, kada se svi ostali dogadjaji obrade (ukljucujuci i one iz drugih procesa), vrsi se upisivanje zapamcenih vrednosti u p odnosno q. Efekat ce biti da ce q dobiti staru vrednost od p, a p ce dobiti staru vrednost od q, tj. imacemo zamenu vrednosti ovih promenljivih. VAZNA NAPOMENA: promenljiva na levoj strani proceduralne dodele (i blokirajuce i neblokirajuce) mora biti registarskog tipa, za razliku od ranije uvedene naredbe kontinuirane dodele kod koje promenljiva na levoj strani mora biti zicanog (wire) tipa. Dobra preporuka je da se blokirajuce dodele koriste pri dizajnu kombinatornih kola (jer se izlazi nekih kola moraju najpre izracunati pre nego sto se dovedu na ulaze narednih kola u lancu), dok se pri dizajnu sekvencijalnih kola koriste ne-blokirajuce dodele (jer se tipicno na uzlaznoj putanji sata uzimaju zatecene vrednosti i one se koriste za izracunavanje novih vrednosti, tj. novog stanja). Takodje, preporuka je da se u istom bloku naredbi ne mesaju blokirajuce i neblokirajuce naredbe dodele -- iako sam jezik to ne zabranjuje, ovakva praksa obicno dovodi do loseg dizajna kola. * VREMENSKA KONTROLA NAREDBI I PROCESA --------------------------------------- Prvi tip vremenske kontrole je oblika: # koji odlaze izvrsavanje naredbe ili procesa za vremenskih jedinica u simulatoru. Na primer: initial #20 begin #10 x <= y; end zapocinje proces tek u 20-toj vremenskoj jedinici od pocetka simulacije. Nakon sto proces zapocne, ceka se jos 10 vremenskih jedinica (sto je ukupno 30 jedinica) da bi se izvrsila naredba dodele. Drugi tip vremenske kontrole je zasnovan na promeni vrednosti signala po zelji. Ovde se koristi sintaksa @(). Na primer: always @(posedge clk) begin q <= ~q; end Ovaj proces simulira T flip flop koji reaguje na prelazak signala sata sa 0 na 1. Slicno, naredni proces ima identicni efekat: always begin @(posedge clk) q <= ~q; end Puna sintaksa @() omogucava da se prati vise signala istovremeno (npr. @(x or y or negedge z)). Ako ispred nekog signala stoji posedge, tada se reaguje samo prilikom promene sa 0 na 1, ako stoji negedge, tada se reaguje samo prilikom promene sa 1 na 0. Ako ne stoji nista, tada se reaguje na svaku promenu tog signala. Specijalno, sintaksa @* ili @(*) je skraceni zapis za @(x1 or x2 or ... or xn), gde su x1,...,xn svi signali cije se vrednosti ocitavaju u naredbi koja se kontrolise @* konstruktom. Na primer: always @* begin x <= a + b; y <= c + d; end Ovde ce @* biti ekvivalentno sa @(a or b or c or d). Signali x i y se ne ukljucuju, zato sto se njihova vrednost ne ocitava u ovoj naredbi, vec se samo vrsi upis. Treci tip vremenske kontrole je wait() naredba. Njena sintaksa je: wait() Uslov moze biti bilo koji logicki izraz. Izvrsavanje naredbe se odlaze dok se ne ispuni uslov. Na primer: integer x; initial begin x <= 0; wait(clk) x <= x + 1; end Najpre se x postavlja na nulu, a onda se ceka da se signal sata postavi na 1 (ukoliko vec nije jednak 1). Tada se izvrsava naredba uvecanja vrednosti. Dakle, @() sintaksa nam omogucava da registrujemo promenu signala, dok wait() omogucava da cekamo odredjenu vrednost ili ispunjenje odredjenog uslova (koji je mozda vec ispunjen, u kom slucaju ne cekamo nista, vec odmah izvrsavamo naredbu). Posmatrajmo i sledeci blok: integer x; reg clk; initial begin x <= 0; clk <= 0; end always #10 clk <= ~clk; always wait(clk) x <= x + 1; Ovde imamo problem sa beskonacnom petljom. Naime, signal sata se menja svakih 10 vremenskih jedinica. Kada se jednom postavi na 1, tada je uslov wait-a ispunjen i poslednji always proces je, dokle god vazi clk == 1 (a to je narednih 10 vremenskih jedinica) ekvivalentan sa: always x <= x + 1; Ovaj proces nema nikakvih vremenskih ogranicenja i iznova i iznova se ponovo pokrece u tekucoj vremenskoj jedinici, blokirajuci aktivni red dodadjaja. Resenje je da se doda kasnjenje: always wait(clk) #1 x <= x + 1; Sada se kada clk postane jedan ovaj blok svodi na: always #1 x <= x + 1; sto za efekat ima da se na svakih #1 vremenskih jedinica uvecava vrednost x za 1. * KONTROLA VREMENA UNUTAR DODELE -------------------------------- Vremenska kontrola se moze zadati i unutar naredbe dodele: x <= #10 y; Efekat ovoga je da se desna strana izracunava odmah, ali se upis u levu stranu odlaze za 10 vremenskih jedinica. Suprotno tome, #10 x < = y; odlaze kompletnu naredbu, tj. i izracunavanje desne strane. Slicno, ako imamo: x <= #10 y; y <= z; obe desne strane ce se izracunati odmah, zatim ce se odmah upisati z u y, dok ce se upis u x odloziti za 10 vremenskih jedinica. Suprotno tome, #10 x <= y; y <= z; odlaze obe naredbe kompletno, dok kod: x <= y; #10 y <= z; odlaze drugu naredbu kompletno, dok se prva izracunava u potpunosti u tekucoj vremenskoj jedinici. * NAREDBE GRANANJA ------------------- Naredba grananja je if-else naredba, cija je sintaksa: if() naredba else naredba pri cemu se deo sa else moze izostaviti. Ako zelimo vise naredbi, stavljamo ih u begin-end blok (ili fork-join, ako zelimo da se konkurentno izvrsavaju). Naredba case je analogna switch naredbi u C-u i ima sledecu sintaksu: case() : : ... default: endcase Izraz moze biti proizvoljnog tipa i broja bitova. Lista vrednosti se navodi razdvojena zarezima nakon cega sledi naredba (koja moze biti i begin-end blok) koja se izvrsava ako je izraz jednak nekoj od datih vrednosti. default opcija se moze izostaviti, a izvrsava se ako izraz nije jednak ni jednoj od ponudjenih vrednosti. Za razliku od C-a, ne navodi se nista nalik break naredbi nakon svake od opcija. * PETLJE U VERILOGU ------------------- Petlje while i for imaju analognu sintaksu i semantiku kao i u C-u: while() for(; ; ) Petlja forever izvrsava neku naredbu beskonacno mnogo puta. Neophodno je navesti odgovarajucu vremensku kontrolu, inace bi se uslo u beskonacnu petlju. Na primer: initial forever @(posedge clk) q <= ~q; je identicno kao i: always @(posedge clk) q <= ~q; Petlja repeat ponavlja neku naredbu konacno mnogo puta: repeat() gde je ceo broj ponavljanja naredbe, npr: repeat(5) @(posedge clk) ; U ovom slucaju imamo praznu naredbu (;) koja se izvrsava kada nastupi pozitivna ivica casovnika. Ovo se ponavlja 5 puta. Efekat ove naredbe je da se saceka 5 ciklusa casovnika pre nego sto se nastavi izvrsavanje bloka. * NEKE UGRADJENE NAREDBE ------------------------ Ugradjene naredbe (ili ugradjeni poslovi) u verilog-u pocinji simbolom $, po cemu se razlikuju od korisnicki definisanih identifikatora. Neke najvaznije su: $time: vraca tekuce vreme (izrazeno u broju vremenskih jedinica od pocetka simulacije) $display(): nalik printf-u, ispisuje poruku na ekran. Na primer: $display($time, ": x: %b, y: %b", x, y); prikazuje poruku oblika: 10: x: 1, y: 0 Specifikatori koji se koriste su %b (za binarni ispis), %o (oktalni), %d (dekadni), %h (heksadekadni) i sl. $monitor(): slicno kao i $display, jedino sto se time registruje da zelimo da se poruka prikazuje svaki put kada se promeni neka od vrednosti koje se prikazuju. Obicno se to uradi u nekom initial bloku na pocetku. U svakom trenutku moze biti aktivan samo jedan monitor. On se moze ukljucivati i iskljucivati naredbama $monitoron i $monitoroff. $finish: ovom naredbom se zaustavlja simulacija. Zgodno ako imamo neki beskonacni proces, onda se u nekom initial procesu moze staviti #100 $finish; tako da se nakon 100 vremenskih jedinica ipak zaustavi simulacija. $dumpfile(): ovim se specificira naziv fajla u koji ce biti smestene informacije koje su pogodne za graficki prikaz simulacije (u verilogovom VCD (value-change-dump) formatu). Na primer: $dumpfile("data.vcd"); $dumpvars(): ovom naredbom se definise koje varijable zelimo da nam budu prikazane u VCD fajlu. Na primer: $dumpvars(1, my_module); kaze da se prikazuju sve varijable deklarisane u okviru modula my_module. Da je prvi argument bio 0, tada bi se prikazivale i sve varijable unutar podmodula koji su instancirani u modulu my_module. Moze se navesti i lista vise modula, kao i konkretnih varijabli, npr: $dumpvars(1, my_module1, my_module2, my_var1, my_var2); $signed() vrsi konverziju u oznaceni tip, tj. omogucava da se izraz tumaci kao oznacena vrednost u potpunom komplementu. Sam binarni sadrzaj vrednosti izraza se ne menja. Analogno, $unsigned() vrsi obrnutu konverziju. $readmemh funkcija vrsi ucitavanje sadrzaja iz tekstualnog fajla u memoriju. Na primer: reg [31:0] mem[0:255]; $readmemh("sadrzaj_memorije.txt", mem); ucitava podatke iz fajla vrednosti_registara.txt i smesta ih u memoriju r. Format ulaznog fajla se sastoji iz podataka navedenih u heksadekadnom formatu (otud sufiks 'h' u nazivu funkcije). Heksadekadni brojevi u fajlu treba da budu razdvojeni belinama. Svaka od navedenih vrednosti u fajlu se ucitava u sledecu lokaciju u memoriji, pocev od najnize adrese (u nasem primeru, pocev od adrese 0 ka adresi 255). Dodatno, u fajlu je moguce navesti specifikaciju adrese oblika @, cime se govori readmemh funkciji da brojeve u nastavku fajla ucitava pocev od zadate adrese. Na ovaj nacin se moze kontrolisati sta se gde ucitava. Analogno, postoji i funkcija $readmemb, kod koje se ocekuje da se brojevi (i adrese) zadaju u binarnom formatu. * DIREKTIVE PRETPROCESORA -------------------------- Direktive pretprocesora pocinju simbolom `. Pored ranije opisane `timescale direktive, tu su jos i `define, `include, `ifdef, `else `endif i sl. koje su potpuno analogne odgovarajucim direktivama u C-u. Konstante uvedene `define direktivom se nakon toga koriste navodjenjem karaktera ` ispred naziva konstante. Na primer: `define MOJA_KONSTANTA 32'h80000000 /* kasnije u kodu */ x = `MOJA_KONSTANTA; // obratiti paznju na ` ispred naziva konstante * DETALJNIJE O SEMANTICI VERILOG SIMULATORA ------------------------------------------- Izvrsavanje Verllog simulatora je nedeterministicko, a sastoji se od veceg broja PROCESA koji se izvrsavaju paralelno, u proizvoljnom redosledu. Pod procesom podrazumevamo sledece: *) na nivou gejtova: svaki gejt (not, and, or,...) je proces. *) na nivou protoka podataka: svaka assign naredba kontinuirane dodele je proces *) na nivou modelovanja ponasanja: initial i always su procesi Za svaki proces vezujemo dva tipa DOGADJAJA: dogadjaji evaluacije procesa (evaluation event) i dogadjaji azuriranja vrednosti signala koji se u tom procesu izracunavaju (update event). Dogadjaj evaluacije podrazumeva izracunavanje koje se obavlja u tom procesu. Npr. kod gejtova to znaci izracunavanje odgovarajuce logicke funkcije, kod assign naredbe izracunavanje desne strane. Kod initial i always procesa, dogadjaj evaluacije podrazumeva pokretanje naredbe koja se nalazi u okviru procesa (od tipa naredbe zavisi kako ce se ona tacno izvrsavati). Dogadjaj azuriranja podrazumeva promenu vrednosti nekog signala. Npr. kod gejtova se nakon izracunavanja logicke funkcije odgovarajuci izlazni signal gejta postavlja na dobijenu vrednost. Kod assign naredbe se signal na levoj strani dodele postavlja na izracunatu vrednost. U slucaju initial i always procesa, dogadjaji azuriranja nastaju kao rezultat proceduralnih dodela. Dogadjaji azuriranja kreiraju nove dogadjaje evaluacije: svi procesi koji su osetljivi na promenu nekog signala se ponovo izracunavaju. Npr. gejtovi su osetljivi na svaku promenu ulaznih signala, assign naredbe su osetljive na svaku promenu nekog od signala koji se pojavljuje u izrazu na levoj strani. Dogadjaji evaluacije za initial i always procese se kreiraju odmah po pokretanju simulatora. Za initial procese ce to biti i jedina evaluacija u toku rada simulatora, dok ce kod always procesa nakon zavrsetka te prve evaluacije odmah biti kreiran novi dogadjaj evaluacije za taj proces. Dogadjaji evaluacije kreiraju dogadjaje azuriranja ako i samo ako se evaluacijom izracuna neka vrednost koja je razlicita od tekuce vrednosti signala kome se ta vrednost dodeljuje. Npr. ako na ulazu and() kola imamo 0 i 1, i ako se drugi ulaz u nekom trenutku promeni u 0, to ce izazvati dogadjaj evaluacije (izracunava se nova vrednost), ali nece doci do kreiranja dogadjaja azuriranja (jer je izracunata vrednost ostala nepromenjena). Svaki dogadjaj je rasporedjen da se obradi u odredjenoj VREMENSKOJ JEDINICI. Simulator prilikom pokretanja inicijalizuje brojac vremenskih jedinica na 0. Nakon sto obradi sve dogadjaje koji su rasporedjeni u toj vremenskoj jedinici, brojac se uvecava i prelazi se na sledecu vremensku jedinicu u kojoj se obradjuju dogadjaji rasporedjeni u toj vremenskoj jedinici, i td. Simulator prestaje sa radom kada vise ne postoji ni jedan dogadjaj koji je rasporedjen za obradu u nekoj od sledecih vremenskih jedinica. Podrazumevano se na pocetku simulacije za svaki od procesa (u svim modulima dizajna) dodaje dogadjaj evaluacije u nultoj vremenskoj jedinici. Ukoliko se prilikom evaluacije utvrdi da je potrebno promeniti vrednost nekog signala, tada se dogadjaj azuriranja rasporedjuje u istoj toj vremenskoj jedinici. Takodje, nakon sto se izvrsi azuriranje signala, dogadjaji evaluacije za procese osetljive na promenu tog signala se rasporedjuju u istoj toj vremenskoj jedinici. Jedini nacin da se rasporedjivanje nekog dogadjaja odlozi za neku od sledecih vremenskih jedinica je da se definise KASNJENJE: *) na nivou gejtova: specifikacijom kasnjenja se odlaze dogadjaj azuriranja nakon evaluacije za neku od sledecih vremenskih jedinica (time se dobija efekat kasnjenja propagacije izracunate vrednosti kroz gejt). *) za assign naredbu: specifikacijom kasnjenja se odlaze dogadjaj azuriranja nakon evaluacije za neku od sledecih vremenskih jedinica (time se dobija efekat kasnjenja izracunavanja kod kombinatornog kola koje izracunava dati izraz). *) za naredbe u initial i always procesima: kasnjenje moze odloziti celu naredbu ili samo neki njen deo. Na primer: initial #10 // odlaze se cela begin-end naredba za 10 vremenskih jedinica begin x <= y; y <= x; end initial begin x <= y; #10 y <= x; // odlaze se samo druga naredba dodele end U drugom primeru se u 0-toj vremenskoj jedinici rasporedjuje dogadjaj evaluacije za initial proces, ali se nakon izvrsavanja prve naredbe dodele zaustavlja dalja evaluacija i kreira se novi dogadjaj koji podrazumeva evaluaciju ostatka naredbe i koji se rasporedjuje sa kasnjenjem od 10 vremenskih jedinica. Naglasimo da se kod slozenih begin-end naredbi, cak i kada nema nikakvog kasnjenja, moze dogoditi da simulator svojevoljno prekine evaluaciju nakon neke od naredbi i da ostatak slozene naredbe rasporedi za kasniju evaluaciju u istoj vremenskoj jedinici (u vidu dogadjaja evaluacije ostatka naredbe). Ovim se simulira paralelno izvrsavanje vise procesa. U slucaju initial i always procesa, pored kasnjenja moguce je takodje navoditi i @() i wait() konstrukcije za kontrolu izvrsavanja koje odlazu evaluaciju naredbe (ili dela naredbe) do ispunjenja odredjenih uslova. Kada se dati uslov ispuni, dogadjaj evaluacije naredbe (ili ostatka naredbe) se rasporedjuje za obradu u toj istoj vremenskoj jedinici. Na primer: initial // evaluacija prve naredbe dodele se obavlja u 0-toj jednici begin x <= y; @(posedge z) y <= x; // evaluacija druge naredbe dodele se odlaze // do prvog prelaska signala z sa 0 na 1 end Verilog simulator dogadjaje postavlja u REDOVE, pri cemu ima nekoliko razlicitih redova koji se razmatraju: *) red aktivnih dogadjaja Qa: ovde se nalaze dogadjaji koji su rasporedjeni za obradu u tekucoj vremenskoj jedinici. Oni se obradjuju u proizvoljnom poretku (s tim sto se naredbe iz istog begin-end bloka moraju evaluirati u poretku koji je naveden u tom bloku; slicno, dogadjaji azuriranja koji poticu iz istog begin-end bloka moraju se obraditi u poretku u kome su navedeni). *) red neaktivnih dogadjaja Qi: ovde se nalaze dogadjaji koji su rasporedjeni u tekucoj vremenskoj jedinici sa kasnjenjem #0. Ovaj jezicki mehanizam se retko koristi prilikom testiranja, kako bi se naterao simulator da neke aktivne dogadjaje ipak rasporedi onako kako korisnik zeli. Ovo narusava prirodni nedeterminizam koji postoji u hardveru koji se dizajnira, pa zato ovakve konstrukcije treba izbegavati. *) red neblokirajucih dodela Qn: ovde se nalaze dogadjaji azuriranja koji su nastali iz neblokirajucih dodela rasporedjenih za izvrsavanje u tekucoj vremenskoj jedinici. Ovi dogadjaji obradjuju se nakon sto se isprazne prethodna dva reda. Ovim se obezbedjuje ranije opisana semantika neblokirajucih dodela. *) red monitorskih dogadjaja Qm: ovaj red sadrzi $monitor dogadjaje koji se kreiraju u svakoj vremenskoj jedinici. Obrada ovih dogadjaja podrazumeva ispis odgovarajuce poruke na izlazu na osnovu vrednosti signala koji se prate (samo ako je bar jedan od signala promenjen u toj vremenskoj jedinici). *) red buducih dogadjaja Qf: ovaj red sadrzi dogadjaje koji su rasporedjeni za izvrsavanje u nekoj od sledecih vremenskih jedinica. Ovi dogadjaji bice prebaceni u aktivni red cim se ispune odgovarajuci uslovi (npr. nastupi odgovarajuca vremenska jedinica u slucaju da je zadato kasnjenje, ili se ispuni odgovarajuci uslov u slucaju @() i wait() konstrukcija). Medju ovim dogadjajima postoje i dogadjaji azuriranja nastali iz neblokirajucih dodela koji su odlozeni kasnjenjem unutar naredbe dodele, npr: x <= 10# y; // dogadjaj evaluacije se izvrsava odmah, a dogadjaj // azuriranja se odlaze za 10 vremenskih jedinica i // dodaje u red buducih dogadjaja Ovakvi dogadjaji ce prilikom aktivacije biti rasporedjeni u red neblokirajucih dodela, a ne u aktivni red. Algoritam rasporedjivanja je dat sledecim pseudokodom: while(neki od redova nije prazan) { if(Qa prazan) { if(Qi nije prazan) prebaci sve dogadjaje iz Qi u Qa else if(Qn nije prazan) prebaci sve dogadjaje iz Qn u Qa else if(Qm nije prazan) prebaci sve dogadjaje iz Qm u Qa else { uvecaj brojac vremenskih jedinica; aktiviraj sve dogadjaje iz Qf koji su rasporedjeni za tu vremensku jedinicu; } } else { E = proizvoljan dogadjaj uzet iz Qa if(E je dogadjaj azuriranja) { azuriramo odgovarajuci signal; dodajemo dogadjaje evaluacije za sve procese (ili naredbe) koji su osetljivi na tu promenu u red aktivnih dogadjaja; } else // dogadjaj evaluacije { izvrsi evaluaciju procesa (ili naredbe) dodaj dogadjaje azuriranja u aktivni red (ili red neblokirajucih dodela, u slucaju naredbe neblokirajuce dodele) } } } * SINTEZA VERILOG DIZAJNA -------------------------- Sinteza verilog dizajna podrazumeva generisanje konkretnog logickog kola koje ima istu semantiku kao dati dizajn, kao i njegovu implementaciju u konkretnoj tehnologiji. Za sintezu se obicno koristi poseban alat koji ne mora biti isti kao i alat koji je koriscen za simulaciju i testiranje (mada neka komercijalna okruzenja sadrze sve u sebi: editor, simulator, kao i alat za sintezu). Sinteza se obicno sastoji u kreiranju tzv. mreze (engl. netlist) koja opisuje kolo na nivou gejtova (pri cemu se koriste samo oni logicki veznici koji su podrzani u toj tehnologiji). Nakon dodatnog testiranja na ovom nivou, vrsi se implementacija u konkretnoj tehnologiji (npr. rasporedjivanje gejtova na silicijumskom cipu, kreiranje odgovarajucih maski za fabrikaciju, i sl.). Vazno je napomenuti da nisu sve verilog kontrukcije takve da ih je moguce sintetizovati, tj. zaista implementirati u hardveru. To naravno pre svega zavisi od alata za sintezu. Generalno, vaze sledece smernice: -- na nivou gejtova je sve moguce sintetizovati, jedino sto ce se ignorisati kasnjenja koja su definisana za pojedinacne gejtove. -- na nivou protoka podataka je takodje moguce sintetizovati vecinu stvari. Problem su opet kasnjenja u assign naredbama, kao i pojedini operatori, poput operatora ===, i !==. Neki alati za sintezu ne dopustaju ni sintezu operatora / i %, zbog slozenosti odgovarajucih logickih kola. Sinteza se obavlja tako sto se izrazi na desnoj strani assign naredbe predstave odgovarajucim kombinatornim kolom na cijem se ulazu nalaze signali koji ucestvuju u izrazu, a na izlazu se nalazi signal koji je na levoj strani dodele. Operator ? se obicno sintetizuje pomocu multipleksera. -- na nivou modelovanja ponasanja, samo odredjeni podskup svih konstrukcija je moguce sintetizovati. Pre svega, nije moguce sintetizovati initial procese, oni se koriste samo za inicijalnu postavku podataka prilikom simulacije. Sa druge strane, always procesi se mogu sintetizovati, pod uslovom da se njihova evaluacija kontrolise @() konstrukcijom (evaluacija kontrolisana zadavanjem kasnjenja se ne moze sintetizovati). Pritom, razlikujemo sledece varijante: *) ako je lista signala u @() oblika @(x1 or x2 or ... or xn), gde su x1,...,xn signali cije se vrednosti ocitavaju u odgovarajucoj naredbi (ulazni signali), i ako se u telu always procesa izlaznim signalima (tj. signalima kojima se dodeljuje vrednost) dodeljije vrednost u SVIM GRANAMA naredbe, tada tada se sintetizuje KOMBINATORNO KOLO ciji su ulazi upravo ovi signali koji se ocitavaju, a izlazi su signali kojima se dodeljuje vrednost u naredbi. Iako su ovi izlazni signali deklarisani kao reg promenljive, nece se zaista kreirati registri koji cuvaju njihove vrednosti, jer su one direktne funkcije ulaza, pa nemaju sekvencijalnu prirodu. Umesto @(x1 or ... or xn) moze se koristiti i @*. *) ako je lista signala u @() oblika @(x1 or x2 or ... or xn) ali se izlaznim signalima ne dodeljuje vrednost u svim granama naredbe, tada je jasno da ce izlazi u nekim situacijama morati da zadrze svoje prethodne vrednosti (tj. moraju da imaju mogucnost cuvanja prethodnog stanja). U tom slucaju se sintetizuje ASINHRONO SEKVENCIJALNO KOLO. Za izlazne signale se sintetizuju reze koje cuvaju njihove vrednosti i menjaju vrednost pod uticajem promene ulaznih signala. *) ako je lista oblika @(posedge clk) ili @(negedge clk), tada se sintetizuje SINHRONO SEKVENCIJALNO KOLO, pri cemu se pomocu flip-flop-ova cuva vrednost registarskih promenljivih kojima se menja stanje u naredbi (izrazi koji se dodeljuju ovim promenljivama se sintetizuju kao odgovarajuce logicke mreze koje racunaju novo stanje, kao sto je uobicajeno u slucaju dizajna konacnih automata). Slozenije verilog konstrukcije, poput petlji najcesce ne mogu da se sintetizuju. Zato se petlje obicno koriste samo u modulima za testiranje, a ne u modulima koje je potrebno sintetizovati. U najkracem, ono sto je moguce sintetizovati je nesto izmedju nivoa protoka podataka i nivoa modelovanja ponasanja. Ovaj "medjunivo" se obicno oznacava terminom RTL (register-transfer-level) modelovanje.