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
下面的代碼綜合后是兩個比較器和兩個級聯的二路選擇器。
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
下面的代碼綜合后是三個比較器和三個級聯的二路選擇器。也就是說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
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
循環語句
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實現的程序代碼:
示例一:
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循環來做。
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中,對綜合來說毫無意義。