Model obnašanja vezja v jeziku Verilog

Visokonivojsko načrtovanje vezja predstavlja model obnašanja (behavioral), ki je najbolj podoben pisanju kode v nekem programskem jeziku. Kljub temu pa so pomembne razlike med strojno-opisnim jezikom, ki opisuje paralelne strukture vezja in običajnim programskim jezikom, ki določa zaporedje izvajanja ukazov.

Model obnašanja opisujejo proceduralni bloki v katerih zapisujemo stavke med begin in end. Ločimo dve vrsti blokov:

Primer generatorja periodičnega signala:

 reg c;

 initial begin
   c = 1’b0;
 end

 always begin
  #50 c = ~c;
 end

Blok initial poskrbi, da dobi spremenljivka c ob začetku simulacije vrednost 0. Hkrati se izvede tudi blok always, ki pa ima pred prireditvenim stavkom zapisano zakasnitev, zato se negacija vrednosti izvede šele po 50 časovnih enotah.

Prireditev z operatorjem = (blocking assignment) znotraj proceduralnega bloka deluje tako, da je izvajanje stavkov blokirano, dokler se ne izvede. S takšnimi stavki lahko opišemo zaporedje dogodkov. Stavek always se izvaja v zanki, tako da dobimo na izhodu vezja periodičen signal.

Opisan primer proceduralnih blokov je lahko del simulacijske testne strukture, ni pa primeren za sintezo vezja zaradi uporabe zakasnitev. V nadaljevanju bomo prestavili nekaj primerov kode, ki omogoča sintezo kombinacijskih in sekvenčni vezij.

Opis kombinacijskih vezij

Kombinacijska vezja opišemo z blokom always@ in seznamom signalov, ki predstavlja pogoj za izvedbo stavkov v bloku. Pogoj za kombinacijska vezja so vsi vhodni signali, saj v splošnem lahko pride do spremembe izhoda ob spremembi kateregakoli vhoda. Verilog'95 zahteva seznam signalov v obliki (a or b or c), Verilog 2001 pa omogoča naštevanje signalov z vejico. Primer opisa polnega seštevalnika:

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

  always @(a, b, cin)
  begin
    s = a ^ b ^ cin;
    cout = (a & b) | (cin & (a ^ b));     
  end

endmodule

Za opis kombinacijskih vezij vedno uporabljamo prireditvene stavke z operatorjem =. Izhodi prireditvenih stavkov morajo biti deklarirani kot spremenljivke. Namesto naštevanja vseh vhodnih signalov smemo v opisu kombinacijskih vezij uporabiti krajši zapis z zvezdico: always @*.

Pogojni stavek

Pogojni stavek ima obliko: if (<pogoj>) stavek1; else stavek2;, pri čemer lahko else izpustimo. Stavek je lahko tudi nov pogojni stavek, kar je uporabno za opis zaporednih primerjav. Če naj se ob pogoju izvede več stavkov, jih zapišemo med begin in end.

Primer opisa vezja, ki da na izhod maksimalno vrednost:

module max( 
    input [3:0] a,
    input [3:0] b,
    output reg [3:0] m
    );

  always @* begin
    if (a>b) m = a;
    else m = b;
  end

endmodule

V opisu kombinacijskih vezij je potrebno izhod določiti ob vseh pogojih, kar pomeni za if in za else. Če izpustimo stavek else, moramo predhodno določiti privzeto vrednost signalov, sicer se bo vrednost ohranjala in bi dobili sekvenčni zapah:

always @* begin
    y  = 1'b0;  // privzeta vrednost kadar pogoj ni izpolnjen
    yn = 1'b1;
    if (trig) begin
      y = x;
      yn = ~x;
    end
  end

Stavek case uporabljamo za pogojno izvedbo stavkov ob naštetih vrednostih signala ali izraza. Uporaben je za opis dekodirnikov, naprimer dekodirnika iz 4-bitne bcd v sedem segmentno kodo:

always @* begin
 case (bcd)
    4'h0: seg = 8'b11111100;
    4'h1: seg = 8'b01100000;
    4'h2: seg = 8'b11011010;
    4'h3: seg = 8'b11110010;
    4'h4: seg = 8'b01100110;
    4'h5: seg = 8'b10110110;
    4'h6: seg = 8'b10111110;
    4'h7: seg = 8'b11100000;
    4'h8: seg = 8'b11111110;
    4'h9: seg = 8'b11110110;
    default: seg = 8'b00000000; // ugasni segmente če ni BCD koda
 endcase
end

Naštejemo lahko tudi več vrednosti, ki jih med seboj ločimo z vejico. Primer vezja za detekcijo, ali je BCD vrednost praštevilo:

always @* begin
 case (bcd)
    1,2,3,5,7: prime = 1'b1; // BCD je praštevilo
    default  : prime = 1'b0;
 endcase
end

Opis sekvenčnih vezij

Sekvenčna vezja opišemo z blokom always@ in pogojem za fronto ure (posedge ali gegedge) v seznamu signalov. Pogoj za naraščajočo (prvo) fronto ure posedge clk je edini zapis v seznamu, kadar opisujemo popolnoma sinhrono vezje.

V prireditvenih stavkih za opis sekvenčnih vezij uporabimo operator <= (nonblocking assignment), ki ne blokira izvajanja ostalih stavkov. Primer opisa sinhronega podatkovnega flop-flopa:

always @(posedge clk)
 if (reset) 
   q <= 1’b0;
 else 
   q <= d;

Podatkovni-flip flop z asinhronim reset signalom opišemo tako, da dodamo še fronto signala reset v seznam signalov. Če je signal reset aktiven ob logični 1, zapišemo pozitivno fronto, če ja aktiven ob 0 pa negativno fronto:

// D flip-flop z asinhronim resetom (pozitivna logika)
always @(posedge clk or posedge reset)
 if (reset)  // reset == 1
   q <= 1’b0;
 else 
   q <= d;

// D flip-flop z asinhronim resetom (negativna logika)
always @(posedge clk or negedge reset)
 if (~reset)   // reset == 0
   q <= 1’b0;
 else 
   q <= d;

Zaporedni prireditveni stavki z operatorjem <= predstavljajo v vezju registre ali flip-flope. Primer 4-bitnega zaporednega pomikalnega registra (SISO):

module siso(
 input clk;
 input en;
 input din;
 output reg dout
);

reg d1; // notranji flip-flopi
reg d2;
reg d3;

always @(posedge clk)
 if (en) begin  // pomikanje ob fronti ure in en==1
  d1 <= din;
  d2 <= d1;
  d3 <= d2;
  dout <= d3;
 end

endmodule

Sekvenčni avtomat

Delovanje sekvenčnega avtomata predstavimo z diagramom prehajanja stanj. Poglejmo si avtomat za detekcijo pritiska tipke.Ob pritisku na tipko, gre avtomat iz mirovnega stanja v stanje impulz, ob naslednjem urinem ciklu pa v stanje čakaj, kjer ostane tako dolgo, dokler je tipka pritisnjena. Avtomat v stanju impulz generira izhodni signal, ki je dolg natanko eno urino periodo.

Za zapis stanj deklariramo 2-bitno spremenljivko, ki ji priredimo začetno stanje. Stanja bomo v opisu avtomata označevali s parametri (konstantami) v obliki zaporednih vrednosti.

module fsm(clk, t, izh); 
 input clk, t; 
 output izh;

parameter mir=0, impulz=1, cakaj=2;

reg [1:0] st = mir;

always @(posedge clk) begin
 case (st)
   mir: 
      if (t==1) st <= impulz;
   impulz: 
      st <= cakaj;
   cakaj: 
      if (t==0) st <= mir;
   default: 
      st <= mir;
 endcase
end

assign izh = (st==impulz) ? 1 : 0;

endmodule