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

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).

* 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 x[3:0];
output y;
inout z;

Za portove se podrazumeva da su tipa wire. Ovo se moze i eksplicitno
navesti nakon toga:

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

ali za tim nema potrebe. Medjutim, ako zelimo da ne budu tipa wire,
onda to moramo eksplicitno navesti u odgovarajucoj redekleraciji, npr:

reg x[3:0];

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.

GATE-LEVEL MODELOVANJE:
=======================

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.


DATA-FLOW MODELOVANJE:
======================

Na data-flow 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
data-flow modelovanja 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 DATA-FLOW nivou je znacajno jednostavnije, jer sada
mozemo na intuitivniji nacin da zapisemo zeljeno izracunavanje.
Na primer, gornji izraz bi se na gate-level nivou morao opisati
sledecim kodom:

wire z1;
wire p1[2:0];
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 gate-level nivou.

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

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, 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. 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

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

* 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.

* SINTEZA VERILOG DIZAJNA
--------------------------

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 koji je obicno poseban alat
nezavisan od simulatora (mada neka komercijalna okruzenja sadrze sve
u sebi: editor, simulator, kao i alat za sintezu i programiranje
FPGA ili VLSI komponente). Generalno, vaze sledece smernice:

-- na gate-level nivou je sve moguce sintetizovati, jedino sto ce se
ignorisati kasnjenja koja su definisana za pojedinacne primitive.

-- na dataflow nivou je takodje moguce sintetizovati vecinu stvari.
Problem su opet kasnjenja u assign naredbama, kao i pojedini
operatori, poput deljenja i ostatka po modulu.

-- na nivou dizajna ponasanja (behavioural modeling), samo odredjeni
podskup svih konstrukcija je moguce sintetizovati. Npr. petlje ciji
broj iteracija nije poznat u fazi prevodjenja najcesce ne mogu da se
sintetizuju. Medjutim, za tim obicno i nema potrebe, jer se petlje
najcesce koriste samo za pocetnu inicijalizaciju hardvera (npr.
zelimo da povezemo 32 flip-flopa u registar, a ne zelimo da pisemo
32 puta istu naredbu sa promenjenim indeksima). 

