本文檔中通過verilog實例來學習verilog語法。Verilog是一種硬件描述語言,它具有並發性和時序性。並發性是指不同硬件模塊的同時操作,時序性是指信號的賦值或操作在時鍾的邊沿進行。由於作者本身也是一個初學者,所以盡量用簡單明了的例子介紹Verilog語法。
Verilog中的注釋
Verilog代碼中的注釋和c++語言相同,分為短注釋(//)和長注釋(/* … */)。短注釋通常放在每行代碼的后面或上面,用來注釋這行代碼的功能。長注釋一般在module的開始處,用來說明模塊的功能。比如下面四位全加器代碼中的注釋。
/* 通過實例化全加器模塊實現四位加法的功能。 輸入:cin,進位 x, y 被加數和加數 s 和 cout 進位 */ module adder4(cin, x, y,s,cout); input cin; input [3:0] x; input [3:0] y; output [3:0] s; output cout; wire [3:1] c; //內部線網類型信號c,用來存儲串行進位 fulladd stage0(.cin(cin),.x(x[0]),.y(y[0]),.s(s[0]),.cout(c[1])); fulladd stage1(.cin(c[1]),.x(x[1]),.y(y[1]),.s(s[1]),.cout(c[2])); fulladd stage2(.cin(c[2]),.x(x[2]),.y(y[2]),.s(s[2]),.cout(c[3])); fulladd stage3(.cin(c[3]),.x(x[3]),.y(y[3]),.s(s[3]),.cout(cout)); endmodule
Verilog中的信號
Verilog中,電路里面的一個信號就代表一個特定類型的線網(net)或變量。這里線網指的兩個或更多電路結點的相互連接。
一個線網或變量的聲明格式如下:
type [range] signal_name{,signal_name};
方括號中range(范圍)是可選的,如果沒有指定范圍,默認情況下表示該信號是標量,是只有一位的單位信號。大括號中表示允許加入的條目,也就是說一個在一行里面可以聲明多個線網或變量。
范圍表示為[Ra:Rb]的形式,它定義了矢量信號的范圍。范圍[Ra:Rb]可以增大或減小。在任何情況下,Ra都表示一個矢量信號的最高有效位(最左邊),Rb表示一個矢量信號的最低有效位(最右邊)。Ra和Rb可以是正整數和負整數。
比如:
wire [7:0] x1,x2;
wire [3:1] Array;
reg y1,y2; //y1和y2沒有指定范圍,只有一位。
標識符
在verilog中,線網或變量名字是通過標識符來表示的。標識符是由一些字母,數字,下划線或美元符號組成。但必須注意兩點:標識符不能以數字開頭,也不能使用verilog中的關鍵字。Verilog標識符中也可以出現轉義字符,比如 \abc,如果用轉義字符,則可以用特殊字符,比如\*cd,但是通常寫代碼過程中,不要用轉義字符來定義變量,這種轉義字符通常是其它語言用工具翻譯成verilog語言時自動插入的。另外注意的一點就是verilog是區分大小寫的,m和M是兩個不同的變量。
合法的標識符:
f, x, x1, x_y, Byte
非法的標識符:
1x, 100, x*y,default(verilog關鍵字)
Verilog中一些系統任務或函數以$開頭,比如$display, $fopen, $monitor等等。Verilog中還定義一些編譯指示,以`開頭,比如:`timescale 1ns/1ns,定義時間單位和時間精度。
信號的值
Verilog支持單位信號表示的標量(一位信號)或者矢量(多位信號),每個單位信號有四種可能的值:0,1,z(或者Z),x(或者X)。
0, 邏輯值0
1, 邏輯值1,
在正邏輯系統中,邏輯值0表示低電平,邏輯值1表示高電平。負邏輯系統中正好相反。在教程中,我們總是用正邏輯系統。
我們用閾值電壓來表示電平的邏輯值。 在上圖中,最低電壓為 Vss,表示接地。VDD表示最高電壓,表示接電源,通常電源在5v到1v之間。VDD~V1,min之間的范圍對應邏輯值1,V0,max~Vss之間的范圍對應邏輯值0。典型的V1,min 是60%Vdd,V0,max是40%Vdd
X, 通常表示多驅動值的沖突,為未初始化的值或者兩個反相信號(如0和1)的短接。Verilog中線網和變量的初始值都為X。
Z, 通常表示未驅動的高阻值,此時在電源和地之間沒有電流流動。比如在三態門中,E=0,則輸出為Z。
Verilog中的數字
信號值如果為標量,則為一位的0,1,x或z。例如:
a = 1’b0; b = 1’b1;
c = x; d = z;
對於矢量,通常表示為:[size]’[radix]constant, 標量也可以當作size=1的矢量。其中size表示常數的位數,radix為常數的基數。如果沒有指定基數,則默認為十進制。Verilog支持的基數有:d(十進制),b(二進制),h(十六進制),o(八進制)。如果定義的位數大於所需要的位數時,通常在前面補0,但是如果最高位是x或z時候,就用x和z來填充。在常量表示中間,可以任意插入下划線,把數字隔開,以方便閱讀。可以在基數前面加s(或者S)以表示這是一個有符號數,若未指定一個整數的進制類型,則默認是有符號數,若指定了進制類型,僅當基數前有s,則其為有符號數,否則為無符號數。對於有符號數,如果定義的位數大於所需要的位數時,前面補最高有效位(MSB),如下表中的8’sha9。
下面是一些實例:
數字表示 |
二進制 |
注釋 |
reg [31:0] a = 8'sha9 |
‘11111111111111111111111110101001 |
8位有符號數,所以進行MSB位擴展, |
reg [31:0] b = 4'd5 |
’00000000000000000000000000000101 |
4位無符號數,高位補0 |
reg [31:0] c = 12'h5b_3 |
‘00000000000000000000010110110011 |
忽略下划線,12位的無符號數,高位補0 |
reg [31:0] d = -8'b101 |
’11111111111111111111111111111011 |
負數轉為補碼表示,MSB位擴展到32位 |
reg [31:0] e= 10'o752 |
‘00000000000000000000000111101010 |
8進制無符號數,高位補0 |
reg [31:0] f = 8'hf |
’00000000000000000000000000001111 |
16進制無符號數,高位補0 |
reg [31:0] g = 12'hxa |
‘00000000000000000000xxxxxxxx1010 |
左邊補x直到12位,然后高位補0 |
reg [31:0] h = -8'sha9 |
’00000000000000000000000001010111 |
補碼表示負數,擴展MSB位, |
reg [31:0] i = 8'ha9 |
‘00000000000000000000000010101001 |
無符號數,高位補0 |
reg [31:0] j = -4'sha |
’00000000000000000000000000000110 |
補碼表示負數,擴展MSB位。 |
實現這些實例的verilog代碼為:
`timescale 1ns/1ns module verilogdisnum; reg [31:0] a = 8'sha9; initial $displayb("a=",a); reg [31:0] b = 4'd5; initial $displayb("b=",b); reg [31:0] c = 4'h5b_3; initial $displayb("c=",c); reg [31:0] d = -8'b101; initial $displayb("d=",d); reg [31:0] e= 10'o752; initial $displayb("e=",e); reg [31:0] f = 8'hf; initial $displayb("f=",f); reg [31:0] g = 12'hxa; initial $displayb("g=",g); reg [31:0] h = -8'sha9; initial $displayb("h=",h); reg [31:0] i = 8'ha9; initial $displayb("i=",i); reg [31:0] j = -4'sha; initial $displayb("j=",j); endmodule
Verilog中的參數
參數由一個標識符和一個常數組成。比如:
parameter n=4;
parameter S0=2’b00; S1=2’b01;S2=2’b10;S3=2’b11;
標識符n可以在代碼中替換表示數字4的地方,S0則可以替換數值2’b00。參數的主要作用是指定參數化的子電路。比如下面的代碼中,可以看到代碼通過parameter定義了一個常量n,實例化時候通過addern #(.n(32)) addern_0(…)的形式把32傳入到參數n中,從而實現32位的加法操作。另外一種調用參數的方法是用defparam,比如下面的例子:
addern addern_0(…)
defparam addern_0.n=32;
但這種方法是不可綜合的,通常只是用在testbench當中。
module addern(x, y, s, cout);
parameter n=8;
input [n-1:0] x;
input [n-1:0] y;
output reg[n-1:0] s;
output reg cout;
reg [n:0] c;
integer k;
always @(x,y) begin
c[0] = 1'b0;
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
cout = c[n];
end
endmodule
`timescale 1ns/1ns `define clock_period 20 module addern_tb; reg [7:0] x,y; wire cout; wire [7:0] s; reg clk; addern #(.n(32)) addern_0( .x(x), .y(y), .s(s), .cout(cout) ); initial clk = 0; always #(`clock_period/2) clk = ~clk; initial begin x = 0; repeat(20) #(`clock_period) x = $random; end initial begin y = 0; repeat(20) #(`clock_period) y = $random; end initial begin #(`clock_period*20) $stop; end endmodule