verilog語法實例學習(4)


Verilog模塊

Verilog中代碼描述的電路叫模塊,模塊具有以下的結構:

module module_name[ (portname {, portname})]; //端口列表

[parameter declarations] //參數定義

[input declarations] // I/O定義

[output declarations]

[inout declarations]

[wire or tri declarations] //內部信號定義

[reg or integer declartions]

[function or task declarations] //功能定義

[assign continuous assignments]

[initial block]

[always block]

[gate instantiations]

[module instantiations]

endmodule

      模塊通常以module開始,endmodule結束,並具有模塊名,模塊名可以是任何有效的標識符。名字后面跟隨的是端口列表,端口都有一個相關聯的類型。端口類型可以是input, ouput或者inout(雙向),可以是標量,也可以是矢量。下面是全加器模塊和4位加法模塊的代碼。

module fulladd(cin,x,y,s,cout);
  input cin,x,y;
  output s,cout;
  assign {cout,s}=x+y+cin;
endmodule
module fulladd4(cin,x,y,s,cout);
  input [3:0] x,y;
  input cin;
  output [3:0] s;
output cout;
  assign {cout,s}=x+y+cin;
endmodule

      端口在默認狀態下是線網(wire,tri等)類型。信號可以分為端口信號和內部信號。出現在端口列表中的信號是端口信號,其它的信號為內部信號。對於端口信號,輸入端口只能是線網類型。輸出端口可以是線網類型,也可以是reg類型若輸出端口在過程塊中賦值則為reg類型;若在過程塊外賦值(包括實例化語句),則為線網類型。內部信號類型與輸出端口相同,可以是線網或reg類型。判斷方法也與輸出端口相同。若在過程塊中賦值,則為reg類型;若在過程塊外賦值,則為線網類型。

內部信號定義時候我們可以定義一些在模塊內部使用的信號。比如下面n位加法器的代碼中:我們定義了integer 類型k以及內部信號C。

module addern(carryin,X,Y,S,carryout);
parameter n=32;
input carryin;
input [n-1:0] X, Y;
output reg [n-1:0] S;
output reg carryout;
reg [n:0] C;
integer k;
always @(X,Y, carryin)
begin
	C[0]=carryin;
	for(k=0;k<n;k=k+1)
	begin
		S[k]=X[k]^Y[k]^C[k];
		C[k+1]=(X[k]&Y[k])|(X[k]&C[k])|(Y[k]&C[k]);
	end
	carryout=C[n];
end
endmodule


在功能定義中,我們通過always過程語句實現n位加法操作,當然我們也可以用assign連續賦值語句實現同樣的功能,比如上面fulladd4的代碼。


並行語句

在硬件描述語言中,並行語句是代表着電路的一部分,這些語句是並行執行的,它們的順序並不重要。並行語句包括連續賦值語句和門實例化語句。

連續賦值語句

      連續賦值語句用來描述組合邏輯電路,用來給線網賦值,賦值時用阻塞賦值。但不同連續賦值語句之間是並行執行的,因為它們都代表電路的一部分,這些電路在物理上可以並行執行。

連續賦值語句的格式為:assign net_assginment[,net assignment];

下面是一些連續賦值語句的例子:

assign cout = (x&y)|(y&cin)|(x&cin);

assign s = x^y^z;

門實例化語句

verilog中包括預定義的基本邏輯門。這些邏輯門允許通過門實例化語句來調用。門實例化語句結構為:

gate_name instance_name(output_port, input_port{,input_port});

實例名甚至可以省略,直接調用gate_name實現邏輯功能。

下面是門實例化實現全加器代碼:

module fulladd(cin,x,y,s,cout);
  input cin,x,y;
  ouput s,cout;
  wire z1,z2,z3,z4;
  and and1(z1,x,y);
  and and2(z2,x,cin);
  and and3(z3,y,cin);
  or or1(cout,z1,z2,z3);
  xor xor1(z4,x,y);
  xor xor2(s,z4,cin);
endmodule
module fulladd(cin,x,y,s,cout);
  input cin,x,y;
  ouput s,cout;
  wire z1,z2,z3,z4;
  and(z1,x,y);
  and(z2,x,cin);
  and(z3,y,cin);
  or(cout,z1,z2,z3);
  xor(z4,x,y);
  xor(s,z4,cin);
endmodule


      我們還可以在門電路中設置一個延時參數,例如:and #(20) and1(z,x1,x2,x3), 表示這個與門延時20時間單位。但這種參數只用在testbench中,電路中是不能綜合的。如果電路中用到這些延時參數,綜合工具通常會忽略它們。Verilog允許邏輯門有任意的輸入,但是實際上受CAD系統限或工藝等等限制,邏輯門的扇入和扇出都是有限制的。扇入:邏輯門的輸入數量。扇出:某個門驅動其它門的數量。

Verilog中支持的邏輯門主要由以下幾種。

名稱

說明

用法

and

f = a&b&…

and(f,a,b,…)

nand

f=~(a&b&…)

nand(f,a,b,…)

or

f=a|b|…

or(f,a,b,…)

nor

f=~(a|b|…)

nor(f,a,b,…)

xor

f=a^b^…

xor(f,a,b,…)

xnor

f=~(a^b^…)

xnor(f,a,b,…)

not

f=~a

not(f,a)

buf

f=a;

buf(f,a)

notif0

f=!e?~a:'bz 三態門

notif0(f,a,e)

notif1

f=e?~a:'bz 三態門

notif1(f,a,e)

bufif0

f=!e?a:'bz 三態門

bufif0(f,a,e)

bufif1

f=e?a:'bz 三態門

bufif1(f,a,e)


過程語句

除了並行語句,verilog中還提供了過程語句。並行語句是並行執行,而過程語句則是按照代碼的順序執行,verilog語法要求過程語句包含在一個always塊內部:

always塊

always塊包含一個或多個過程語句的結構,它的形式如下:

always @(sensitivity_list) //敏感信號列表

[begin] //當一個always塊中包含多條語句時,就必須用begin…end

[procedural assignment statement]

[if-else statement]

[case statement]

[while, repeat, and for loops]

[task and function calls]

[end]

相比於連續賦值語句和門實例化,上面的這些語句提供了更為強大的行為級電路描述方式。

  敏感信號列表是一個直接影響always塊信號輸出的信號列表。敏感信號之間用逗號(,)或者 or分開。當敏感信號列表中任何一個信號發生改變時,always塊中的過程語句即被順序執行。我們可以用always @(*),表示所有的輸入信號多包含在敏感信號列表中。下面是一個簡單always塊例子:

always @(x,y)

begin

s=x^y;

c=x&y;

end

敏感信息列表也可以在信號的邊沿觸發,posedge 信號的升沿觸發,negedge信號的下降沿觸發,例如下面的代碼:

always @(posedge clk,negedge Rst_n)
begin
   if(Rst_n==0)
        Q<=0;
     else
        Q<=D;
end

一個verilog module可以包含多個always塊,它們都代表電路的一部分,不同的always塊之間是並行執行的。

過程賦值語句

always塊中賦值的信號都是reg或integer等變量類型,不能是wire類型。給一個信號賦值用過程賦值語句。過程賦值語句有兩種,阻塞賦值和非阻塞賦值。

阻塞賦值:

s = x + y; //先執行第一句

p = s[0]; //再執行第二句

非阻塞賦值:

s <= x + y; //兩條語句同時執行

p<= s[0]; //p此時更新的s[0]仍是之前的值

在一個always塊中,一般不建議混用阻塞和非阻塞賦值語句。

      前面提到連續賦值語句中,我們采用阻塞賦值,是不是組合電路都不能采用非阻塞賦值?其實在很多情況下是可以使用的,但是如果分支語句的賦值取決於之前的結果,非阻塞賦值可能產生無意義的電路。比如下面bit_count模塊代碼,用來統計四位數中1的數目,綜合后是3個加法器。如果把for循環中的賦值改為非阻塞賦值,則循環過程為:

count <= count +x[0];

count <= count +x[1];

count <= count +x[2];

此時,count的初始值都為0,則for循環退化為

count<=x[0];

count<=x[1];

count<=x[2];

當always塊當中存在多條給同一個變量賦值的語句時,相當於多源激勵輸入。

module bit_count(x,count);
  parameter n=4;
  parameter logn=2;
  input [n-1:0] x;
  output reg[logn:0] count;
  integer k;

  always @(x)
  begin
     count=0;
	 for(k=0;k<n;k=k+1)
	   count = count + x[k];
  end

endmodule
module bit_count(x,count);
  parameter n=4;
  parameter logn=2;
  input [n-1:0] x;
  output reg[logn:0] count;
  integer k;

  always @(x)
  begin
     count<=0; #10
	 for(k=0;k<n;k=k+1)
	   count <= count + x[k];
  end

endmodule

用阻塞賦值,用下面的testbench,得到結果:

# .main_pane.objects.interior.cs.body.tree

# run -all

# x=10101010,count= 4

# x=11111010,count= 6

`timescale 1ns/1ns
module bit_count_tb;
  reg [7:0] x;
  wire [3:0] count;
  bit_count #(.n(8),.logn(3)) bit_count0(.x(x),.count(count));

  initial
  begin
    x = 8'b10101010;
	 #20
	 $display("x=%b,count=%d",x,count);
	 x = 8'b11111010;
	 #20
	 $display("x=%b,count=%d",x,count);

	 $stop;
  end
endmodule

如果用非阻塞賦值的代碼,則得到下面的結果:這是此時相當於多源激勵輸入,如果有的0,有的1,最終的值為x

# .main_pane.objects.interior.cs.body.tree
# run -all
# x=10101010,count= x
# x=11111010,count= x
# ** Note: $stop    : D:/fpga/veriloglearn/bitcount/testbench/bit_count_tb.v(17)

if else 語句

和c語言的if else語句語法一樣,if else語句必須包含在always塊中。if else語法結構為:


if( 表達式1)
begin
   statement;
end
else if(表達式2)
begin
    statement;
end
else
begin
   statement;
end

下面的if else語句,定義了一個二選一電路。

module vif1(a,b,sel,r);
  input a;
  input b;
  input sel;
  output reg r;

  always @(*)
  begin
     if(sel==0)
	    r = a;
	  else
	    r = b;
  end
endmodule

image

下面的代碼綜合后是兩個比較器和兩個級聯的二路選擇器。

module vif1(a,b,c,sel,r);
  input a;
  input b;
  input c;
  input [1:0] sel;
  output reg r;

  always @(*)
  begin
     if(sel==2'b00)
	    r = a;
	  else if(sel==2'b01)
	    r = b;
	  else
	    r = c;
  end
endmodule

image



下面的代碼綜合后是三個比較器和三個級聯的二路選擇器。也就是說elseif會被綜合成級聯的二路選擇器,而不是多路選擇器。

module vif1(a,b,c,d,sel,r);
  input a;
  input b;
  input c;
  input d;
  input [1:0] sel;
  output reg r;

  always @(*)
  begin
     if(sel==2'b00)
	    r = a;
	  else if(sel==2'b01)
	    r = b;
	  else if(sel==2'b10)
	    r = c;
	  else
	    r = d;
  end
endmodule


image



case 語句

case語句是一種多分支選擇語句,可以直接處理多分支語句。

1)case(表達式) <case分支項> endcase

2)casex(表達式) <case分支項> endcase

3)casez(表達式) <case分支項> endcase

case分支項的一般格式如下:

分支表達式: 語句;

……

默認項(default) 語句;

1)case后括號內的表達式稱為控制表達式,分支項后的表達式稱作分支表達式,又稱作常量表達式。控制表達式通常表示為控制信號的某些位,分支表達式則用這些控制信號的具體狀態值來表示。

2)當控制表達式和分支表達式的值相等時,就執行分支表達式后的語句。

3)default項可有可無,一個case語句里只准有一個default項。(為了防止程序自動生成鎖存器,一般都要設置default項

4)每一個case的表達是必須各不相同,執行完case分支項的語句后,跳出case塊。

5)case語句的所有表達式的值的位寬必須相等。

case casex casez 的真值 (行0,1,x,z是控制表達式,列0/1/x/z是分支表達式)

case

0   1   x   z

casez

0    1   x   z

casex

0   1   x   z

0

1   0   0   0

0

1   0   0   1

0

1   0   1   1

1

0   1   0   0

1

0   1   0   1

1

0   1   1   1

x

0   0   1   0

x

0   0   1   1

x

1   1   1   1

z

0   0   0   1

z

1   1   1   1

z

1   1   1   1

casex和casez是case的特殊情況,用來處理過程中不必考慮的情況。casez用來處理不用考慮高阻值,casex表示不用考慮高阻值和不定值

上述表格說明casez中,可以將z任意匹配,casex中可以將x任意匹配。在case的分支語句中,從上到下開始匹配,輸出第一個匹配成功的值。

下面代碼中我們用case語句實現上面ifelse中的電路功能。綜合工具通常只考慮x=0,x=1的情況,所以在case中,我們不考慮x,z的情況。

從rtl viewer中,可以看到綜合后是一個多路選擇器,這個要比上面ifelse語句綜合后的電路要好。

module vif1(a,b,c,d,sel,r);
  input a;
  input b;
  input c;
  input d;
  input [1:0] sel;
  output reg r;

  always @(*)
  begin
     if(sel==2'b00)
	    r = a;
	  else if(sel==2'b01)
	    r = b;
	  else if(sel==2'b10)
	    r = c;
	  else
	    r = d;
  end
endmodule


image


循環語句

Verilog包括四種循環語句,for, while, repeat和forever,綜合工具通常僅支持for語句。其它幾種語句主要用在testbench當中。

     在C語言中,經常用到for循環語句,但在硬件描述語言中for語句的使用和C語言等軟件描述語言有較大的區別。

     在Testbench中for語句在生成激勵信號等方面使用較普遍,但在RTL級編碼中卻很少使用for循環語句。主要原因就是for循環會被綜合器展開為所有變量情況的執行語句,每個變量獨立占用寄存器資源,每條執行語句並不能有效地復用硬件邏輯資源,造成巨大的資源浪費。簡單的說就是:for語句循環幾次,就是將相同的電路復制幾次,因此循環次數越多,占用面積越大,綜合就越慢

     在RTL硬件描述中,遇到類似的算法,推薦的方法是先搞清楚設計的時序要求,做一個reg型計數器。在每個時鍾沿累加,並在每個時鍾沿判斷計數器情況,做相應的處理,能復用的處理模塊盡量復用,即使所有的操作不能復用,也采用case語句展開處理。

對於下面的for循環語句: 

for(i=0;i<16;i++)
  DoSomething();

可以采用如下代碼實現:

reg [3:0] counter;
always @(posedge clk)
  if(syn_rst)
    counter<=4'b0;
  else
    counter<=counter+1;
always @(posedge clk)
  begin
    case(counter)
        4'b0000:
        4'b0001:
        ......
    default:
    endcase
  end
     另外,有幾個語法的細節需要注意一下。for(i=0;i<16;i=i+1)中的i既可以是reg型的變量也可以是integer類型的變量,但是當i是reg型的變量時,需要注意因為判斷語句i<16的緣故,i應定義為reg[4:0] i而不是reg[3:0] i 。由於verilog中沒有自增運算符,文中提到的for語句不能寫成for(i=0;i<16; i++)的形式。

下面簡單的列舉幾個用for實現的程序代碼:
示例一:

module vfor1(clk,Rst_n,data, num);

  input clk; //時鍾信號
  input Rst_n;  //復位信號
  input [3:0] data;

  output reg [2:0] num;
  integer i;

  always @(posedge clk)
  begin
    if(Rst_n==0)
	   num = 0;
	else
	begin
	  for(i=0;i < 4; i=i+1)
	   if(data[i])  num = num + 1;

   end
  end

endmodule


      綜合后,用rtl vieer,可以看到這兒有四個相同的電路,而且效率很高,但所消耗的邏輯資源較大。在對速度(時鍾周期數)要求不是很高的情況下,可以多用幾個時鍾周期完成任務,而沒有必要用for循環來做。

image

while, repeat, forever都用在testbench當中,下面是它們的語法:

while(condition)

begin

  statement;

end

repeat(constanat_value)

begin

statement;

end

forever

begin

  statement;

end

我們用下面的代碼說明它們的用法:

`timescale 1ns/1ns
`define clock_period 20

module addern_tb;
  reg [7:0] x,y;

  wire cout;
  wire [7:0] s;
  reg clk;
  integer i,j;

  addern #(.n(8)) addern_0(
						.x(x),
						.y(y),
						.s(s),
						.cout(cout)
                  );

  initial clk = 0;
  always #(`clock_period/2) clk = ~clk;

  initial begin
     x = 0;
     repeat(10)
	    #(`clock_period) x = $random;
     i = 10;
	  while(i>0)
	  begin
	     #(`clock_period) x = $random;
		  i = i -1;
	  end
	  forever
	    #(`clock_period) x = $random;
  end

  initial begin
     y = 0;
     repeat(10)
	    #(`clock_period) y = $random;
	  j = 10;
	  while(j>0)
	  begin
	     #(`clock_period) y = $random;
		  j = j -1;
	  end
	  forever
	    #(`clock_period) y = $random;

  end


  initial begin
     #(`clock_period*200)
	  $stop;
  end


endmodule



initial語句

      initial和always具有相同的結構,但initial塊內的內容只在仿真開始的時候執行一次。initial語句用在testbench中,對綜合來說毫無意義。











免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM