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>'<osnova><vrednost>

   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 <ime_modula>(<lista_portova>);
<deklaracije_portova>;

// 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:

<ime_modula> <ime_instance>(<signali_koji_se_povezuju_sa_portovima>);

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(<lista_portova>);

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(<lista_portova>);

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
 <naredba>

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:

<promenljiva> = <izraz>;

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:

<promenljiva> <= <izraz>;

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:

#<broj>

koji odlaze izvrsavanje naredbe ili procesa za <broj> 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>) <naredba_ili_blok>

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(<uslov>)
 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(<izraz>)
<lista_vrednosti>: <naredba>
<lista_vrednosti>: <naredba>
...
default: <naredba>
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(<uslov>) <naredba>

for(<init>; <uslov> ; <inc>) <naredba>

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(<konstanta>) <naredba>

gde je <konstanta> 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(<izraz>) 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(<izraz>) 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 @<hex_broj>, 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.