把前面看過的夏宇聞老師的那本書上面的verilog的常用語法進行了一下梳理,并且為了熟悉語法在HDLBits刷了刷題,一些例子就直接從那個里面引用了。參考過的內(nèi)容列在下面:
·verilog數(shù)字系統(tǒng)設(shè)計教程(夏宇聞):學(xué)習(xí)verilog的必讀書。第四版的第三章到第七章,夏宇聞老師這本書上講的比較詳細。
·HDLBits:類似于CS的LeetCode,題目倒是沒有LeetCode多,適合入門語法。
·Verilog Tutorial : 這個里面對verilog語法講的很細,而且有大量的代碼示例,不過代碼不能直接復(fù)制下來運行,但是用一些小trick還是可以把代碼搞下來的。
01verilog模塊的結(jié)構(gòu)
verilog語法中最基本的元素就是模塊了,主要包括模塊聲明以及模塊內(nèi)容。首先是模塊的聲明,具體的聲明結(jié)構(gòu)如下
module Hello_World(<端口信號列表>); //注意這里要有分號
<邏輯代碼>
endmodule
接下來是模塊內(nèi)容,主要包括I/O口的說明,內(nèi)部信號的說明,功能的定義三個結(jié)構(gòu)。
(1)I/O口的說明
輸入端口:
input[信號位寬-1:0] 端口1;
input[信號位寬-1:0] 端口2;
...
輸出端口:
output[信號位寬-1:0] 端口1;
output[信號位寬-1:0] 端口2;
...
輸入/輸出端口:
inout[信號位寬-1:0] 端口1;
inout[信號位寬-1:0] 端口2;
...
(2)內(nèi)部信號的說明
reg [width-1:0] 變量1,變量2,...;
wire[width-1:0]變量1,變量2,...;
(3)功能定義
主要有三種最基本的功能定義方法,分別是always,assign,initial。一個module里面可以寫多個always,assign,initial,這些功能在電路通電之后也是同時開始執(zhí)行的。
·initial用在仿真中。主要有以下兩種功能,一方面可以在仿真開始時對各變量進行初始化,這個初始化過程不需要任何仿真時間,即在 0 ns 時間內(nèi),便可以完成初始化工作;另一方面可以用來生成激勵波形作為電路的測試仿真信號。
// 完成初始化
initial begin
areg = 0; //初始化一個寄存器
for(index=0;indexindex =index+1)
memory[index] = 0; //初始化一個memory
end
// 完成激勵波形的生成
initial begin
inputs = 8'b0000_0000;
#10 inputs = 8'b0110_0101;
#10 inputs = 8'b1111_0101;
end
·assign用于實現(xiàn)組合邏輯,直接互聯(lián)不同的信號,或者變量值。
assign = <變量或者常量>;
// 右邊也可以使用 (判斷條件)?(判斷條件為真時的邏輯處理):(判斷條件為假時的邏輯處理)
// 完成更加簡潔的if...else語句
·always語句可以實現(xiàn)組合邏輯,也可以實現(xiàn)時序邏輯。它是一直運行的,但是后面跟著的過程塊是否執(zhí)行,就要看它的觸發(fā)條件是否滿足了,這個觸發(fā)條件就寫在括號中??梢杂尚盘柕倪呇赜|發(fā),也可以由信號的變化觸發(fā)。
// 所有信號變換都可以觸發(fā),是組合邏輯
always@(*) begin
...
end
// 信號a與信號b的變化都可以觸發(fā),是組合邏輯
always@(a or b) begin
...
end
// 時鐘的上升沿或者復(fù)位信號的下降沿可以觸發(fā),是時序邏輯
always@(posedge clk or negedge rst_n) begin
...
end
(4)引用模塊
在引用時既可以按照模塊定義的端口順序來連接,不用標(biāo)明原模塊定義時規(guī)定的端口名,也可以在引用時采用“.”符號,這樣就不用完全按照模塊定義的端口順序來連接了,一般還是使用“.”來引用比較好,這樣邏輯比較清晰。
...
MyDesign M1(.sin(in),.pout(out));
...
MyDesign 是已經(jīng)定義的另一個模塊,M1是自己取的一個名字,sin與pout是MyDesign模塊里面端口的名字,in與out是在正在寫的模塊中定義的兩個信號,這兩個信號分別連接到sin與pout端口。
02數(shù)據(jù)類型
數(shù)據(jù)主要可以分為兩種類型,一種是常量類型的數(shù)據(jù),另一種是變量類型的數(shù)據(jù)。
(1)常量類型的數(shù)據(jù)
·整數(shù):二進制(b),十進制(d),十六進制(h)。
·x和z值:
·x代表未定值:不確定狀態(tài)為高還是為低,旨在告訴綜合工具設(shè)計者不關(guān)心它的電平是多少,是0是1都可以。
·z代表高阻值:輸出引腳與內(nèi)部電路斷開,不導(dǎo)通,其電平隨外部電平高低而定,表示設(shè)計者不驅(qū)動這個信號。
·一個x可以用來定義十六進制數(shù)的4位二進制數(shù)的狀態(tài)或者二進制數(shù)的1位。建議在寫case語句中使用這些值。
·負數(shù):在位寬表達式前加一個減號就可以定義一個負數(shù)。
·下劃線:主要用來分隔開數(shù)的表達式以提高程序的可讀性,它只能用在具體的數(shù)字之間。
<位寬><進制><數(shù)字>
8'b10101100 // 位寬為8的數(shù)的二進制表示,'b表示二進制
8'ha2 // 位寬為8的數(shù)的十六進制表示,'h表示十六進制
<數(shù)字>
10 //默認采用十進制,并且數(shù)字的位寬采用默認位寬(與機器系統(tǒng)相關(guān),最少32位)
4'b10x0 //位寬為4的二進制數(shù),從低位數(shù)起第2位是不定值
8'h4x //位寬為8的二進制數(shù),其低四位值為不定值
-8'd5//這個表達式代表5的補數(shù)
16'b1010_1011_1111_1010
·參數(shù):使用parameter來定義常量,注意在引用實例的時候可以通過參數(shù)的傳遞來改變定義時規(guī)定的參數(shù)。
//parameter可以直接定義一系列常量參數(shù)
parameter a=2,b=2.3,c=a-1;
//可以通過傳參修改module中定義的parameter
module Decode(A,B);
parameter width=1,polarity=1;
...
endmodule
module Decode_top;
wire[3:0] A1;
wire[3:0] B1;
wire[3:0] A2;
wire[3:0] B2;
Decode #(4,0) D1(A1,B1); // D1實例引用的width=4,polarity=0
Decode #(5) D2(A2,B2); // D2實例引用的width=5,polarity=1
endmodule
(2)變量類型的數(shù)據(jù)
·wire型:常用來表示用以assign關(guān)鍵字指定的組合邏輯信號。verilog程序模塊中輸入、輸出信號類型默認時自動定義為wire型。wire型信號可以用做任何方程式的輸入,也可以用作assign語句的輸出。
·reg型:常用來表示always模塊內(nèi)的指定信號,在always模塊內(nèi)被賦值的每一個信號都必須定義成reg型。reg的默認初始值是不定值。
·memory型:從編程角度可以理解成一個多維數(shù)組,從物理角度可以理解成RAM型存儲器或者ROM存儲器,從實現(xiàn)角度可以理解成是reg型數(shù)據(jù)的擴展。
wire [3:0] a,b; //定義了兩個4位的wire型數(shù)據(jù)
reg [3:0] a,b; //定義了兩個4位的reg型數(shù)據(jù)
reg [n-1:0] memory [m-1:0] //[n-1:0]定義存儲器中每一個存儲單元的大小,[m-1:0]定義了
//存儲器中存儲單元的個數(shù)也可以理解成各存儲單元的地址
memory[3] = 0; //注意對memory中的存儲單元進行讀寫操作的時候必須指定該單元在存儲器中的地址
03運算符及表達式
·算術(shù)運算符
+ //加
- //減
* //乘
/ //除
% //求模
進行整數(shù)除法運算時結(jié)果值略去小數(shù)部分,只留下整數(shù)部分,取模運算要求%兩側(cè)均為整數(shù)。
·位運算符
~ //取反
& //按位與
| //按位或
^ //按位異或
~^ //按位同或
不同長度的數(shù)據(jù)在進行位運算的時候,系統(tǒng)會自動地將兩者按右端對齊,位數(shù)少的操作數(shù)會在相應(yīng)的高位用0填滿。
·邏輯運算符
&& //邏輯與
|| //邏輯或
! //邏輯非
·關(guān)系運算符
a//小于
a>b //大于
a<=b //小于等于
a>=b //大于等于
·等式運算符
== //等于
!= //不等于
·移位運算符
a>>n //a右移n位
a<//a左移n位
兩種移位運算都用0來填補移出的空位。
·位拼接運算符
{信號1的某幾位,信號2的某幾位,...}
{a,b[3:0],w,3'b101}
{4{w}} //位拼接可以用來簡化表達式,這樣寫等同于{w,w,w,w}
{b,{3{a,b}}} //這樣寫等同于 {b,a,b,a,b,a,b}
位拼接表達式不允許存在沒有指明位數(shù)的信號。
·縮減運算符
reg [3:0] B;
reg C;
C = &B; // 相當(dāng)于 C=((B[0] & B[1])&B[2])&B[3]
與之前的位運算不同,縮減運算是對單個操作數(shù)各位的邏輯操作,最終的運算結(jié)果是1位二進制數(shù)。
04條件語句及循環(huán)語句
(1)條件語句
條件語句主要有兩種寫法,一種是if else的寫法,另一種是case的寫法。
// if else 的寫法
if(a==2'b00) begin
<具體邏輯>
end
else if(a==2'b01) begin
<具體邏輯>
end
else begin
<具體邏輯>
end
// case 的寫法
case(a)
2'b00:<具體邏輯>
2'b01:<具體邏輯>
default:<具體邏輯>
endcase
還有用法與case類似的casex與casez,這兩者可以用來處理比較過程中不必考慮的情況。其中casez語句用來處理不必考慮高阻值z的比較過程,casex語句則將高阻值z和不定值都視為不必關(guān)心的情況。所謂不必關(guān)心的情況就是說,在表達式進行比較時不將該位的狀態(tài)考慮在內(nèi)。
(2)循環(huán)語句
總體來說循環(huán)語句用到的不是很多,這里就列舉兩個最常用的forever和for循環(huán)。
·forever循環(huán)語句常用來產(chǎn)生周期性的波形,用來作為仿真信號,必須要寫在initial塊中。
·for循環(huán)語句的一般形式是for(<變量名>=<初值>;<判斷表達式>;<變量名>=<新值>)。for可以綜合的,for幾次就相當(dāng)于把你的電路復(fù)制幾次。
initial begin
clk = 0;
forever begin
clk = ~clk;
#5;
end
end
initial begin
counter2 = 'b0 ;
for (i=0; i<=10; i=i+1) begin
#10 ;
counter2 = counter2 + 1'b1 ;
end
end
05塊語句
·順序塊:采用begin end語句,塊內(nèi)的語句是按照順序執(zhí)行的,前面的語句執(zhí)行完才輪到后面的語句執(zhí)行,每條語句的延遲時間是相對于前一條語句的仿真時間定的。
·并行塊:采用fork join語句,塊內(nèi)的語句是并行執(zhí)行的,每條語句的延遲時間是相對于程序進入塊內(nèi)的時間。并行塊是不可綜合的,只能用在仿真中。
module begin_tb();
reg [7:0] r;
regclk;
initial begin:begin_tb //這里可以將塊名寫上
clk = 0;
#50 r = 8'h35;
#30 r = 8'h12;
#40 r = 8'h41;
end
always #5 clk = ~clk;
endmodule
module fork_tb();
reg [7:0] r;
reg clk;
initial fork:fork_tb //這里可以將塊名寫上
clk = 0;
#50 r = 8'h35;
#30 r = 8'h12;
#40 r = 8'h41;
join
always #5 clk = ~clk;
endmodule
上面是begin end的仿真時序圖,下面是fork join的仿真時序圖。從仿真時序圖中可以明顯的看到,begin end是串行的時序而fork join是并行的時序。
·生成塊:采用generate endgenerate語句,這一聲明語句方便參數(shù)化模塊的生成。當(dāng)對矢量中的多個位進行重復(fù)操作時,或者當(dāng)進行多個模塊的實例引用的重復(fù)操作時,或者在根據(jù)參數(shù)的定義來確定程序中是都應(yīng)該包括某段verilog代碼的時候,使用生成語句能夠大大簡化程序的編寫過程。
1、for:在generate中的應(yīng)用主要是用來減少重復(fù)操作。
//-------------module-------------//
頂層模塊包含N個半加器
module top(a,b,sum,cout);
parameter N=2;
input [N-1:0] a, b;
output [N-1:0] sum, cout;
聲明一個暫時的循環(huán)變量
genvar i;
生成N次
generate
for (i = 0; i < N; i = i + 1) begin
half_add u0(a[i], b[i], sum[i], cout[i]);
end
endgenerate
endmodule
定義一個半加器
module half_add(a, b, sum, cout);
input a,b;
output sum,cout;
assign sum = a ^ b;
assign cout = a & b;
endmodule
//-------------testbench-------------//
1ns / 1ns
module top_tb;
parameter N = 2;
reg [N-1:0] a, b;
wire [N-1:0] sum, cout;
top instance1( .a(a), .b(b), .sum(sum), .cout(cout));
initial begin
a <= 0;
0;
= #10 a <= 'h2;
b <= 'h3;
#20 b <= 'h4;
#10 a <= 'h5;
end
endmodule
具體生成的RTL圖以及仿真結(jié)果如上圖所示。
2、if:在generate中主要用來根據(jù)參數(shù)的定義來確定程序。
//-------------module-------------//
module top(a, b, sel, out);
input a,b,sel;
output out;
parameter USE_CASE = 0;
// 使用 generate 塊選擇使用 mux_case 或者 mux_assign
generate
if (USE_CASE)
mux_case m1 (.a(a), .b(b), .sel(sel), .out(out));
else
mux_assign m2 (.a(a), .b(b), .sel(sel), .out(out));
endgenerate
endmodule
// Design #1: 使用assign
module mux_assign ( input a, b, sel,
output out);
assign out = sel ? a : b;
// 這個display可以在仿真時標(biāo)明在用哪一個design
initial
$display ("mux_assign is instantiated");
endmodule
// Design #2: 使用case
module mux_case (input a, b, sel,
output reg out);
always @ (a or b or sel) begin
case (sel)
0 : out = a;
1 : out = b;
endcase
end
// 這個display可以在仿真時標(biāo)明在用哪一個design
initial
$display ("mux_case is instantiated");
endmodule
//-------------testbench-------------//
`timescale 1ns / 1ns
module top_tb;
reg a, b, sel;
wire out;
integer i;
// 使用 USE_CASE 選擇用哪個 design
initial
$display("USE_CASE = %0d",0);
top
initial begin
a <= 0;
b <= 0;
sel <= 0;
for (i = 0; i <= 2; i = i + 1) begin
b <= $random;
sel <= $random;
$display ("i=%0d a=0x%0h b=0x%0h sel=0x%0h out=0x%0h", i, a, b, sel, out);
end
end
endmodule
上面是仿真結(jié)果在tcl的輸出,可以看到使用不同的USE_CASE可以選取不同的design。
3、case:在generate中主要用來根據(jù)參數(shù)的定義來確定程序。
//-------------module-------------//
module top (a, b, cin, sum, cout);
input a,b,cin;
output sum,cout;
parameter ADDER_TYPE = 1;
generate
case(ADDER_TYPE)
0 : ha u0 (.a(a), .b(b), .sum(sum), .cout(cout));
1 : fa u1 (.a(a), .b(b), .cin(cin), .sum(sum), .cout(cout));
endcase
endgenerate
endmodule
Design #1: 半加器
module ha (input a, b,
output reg sum, cout);
always @ (a or b)
sum} = a + b;
initial
("Half adder instantiation");
endmodule
Design #2: 全加器
module fa (input a, b, cin,
output reg sum, cout);
always @ (a or b or cin)
sum} = a + b + cin;
initial
("Full adder instantiation");
endmodule
//-------------testbench-------------//
1ns / 1ns
module top_tb;
reg a, b, cin;
wire sum, cout;
initial
%0d",0);
= top #(.ADDER_TYPE(0)) u0 (.a(a), .b(b), .cin(cin), .sum(sum), .cout(cout));
initial begin
a <= 0;
b <= 0;
cin <= 0;
0x%0h b=0x%0h cin=0x%0h cout=0%0h sum=0x%0h",
= b, cin, cout, sum);
for (int i = 0; i<=2; i = i + 1) begin
#10 a <= $random;
b <= $random;
cin <= $random;
end
end
endmodule
上面是仿真結(jié)果在tcl的輸出,可以看到使用不同的ADDER_TYPE可以選取不同的design。
06任務(wù)
任務(wù)要寫在task endtask中,有點像matlab中函數(shù)這個概念,其中可以有input、output、inout端口作為出入口參數(shù)。注意任務(wù)中不能出現(xiàn)initial語句和always語句, 但任務(wù)調(diào)用語句可以在initial語句和always語句中使用(這些都是對static task來說的,automatic task略有不同,一般默認是static task)。
module top_tb;
reg [7:0] x, y, z;
task sum;
input [7:0] a, b;
output [7:0] c;
begin
c = a + b;
end
endtask
initial begin:test
{8'd2,8'd3};
= sum (x, y, z);
#10 {x,y} = {8'd5,8'd1};
sum (x, y, z);
end
endmodule
x 和 y 是輸入值,最后計算結(jié)果存儲在 z 中。
07常用的系統(tǒng)任務(wù)
(1)格式化輸出函數(shù)
·$display
reg [3:0] rval;
initial begin
rval = 3;
$display("rval = %h hex, rval = %d decimal, rval = %b binary", rval, rval, rval);
$display("current scope is %m"); // 輸出等級層次的名字
$display("%s", "Hello World");
//在%和表示輸出格式的字符之間插入一個0自動調(diào)整顯示輸出的數(shù)據(jù)寬度
$display("Simulation time is %0d", $time);
end
·$strobe
與display不同,它可以確保所有在同一時鐘沿賦值的其它語句在執(zhí)行完畢之后才顯示數(shù)據(jù)。
reg [3:0] a,b;
wire [4:0] y = a + b;
// 采用display的方式輸出
initial begin
a = 3;
b = 2;
$display("$display: time =%0d, a= %d, b=%d, y= %d ",$time ,a, b, y);
#10
a = 4;
$display("$display: time =%0d, a= %d, b=%d, y= %d ",$time,a, b, y);
b = 5;
$display("$display: time =%0d, a= %d, b=%d, y= %d ",$time ,a, b, y);
#10;
end
// 采用strobe的方式輸出
initial begin
a = 3;
b = 2;
$strobe("$strobe: time =%0d, a= %d, b=%d, y= %d ",$time ,a, b, y);
#10
a = 4;
$strobe("$strobe: time =%0d, a= %d, b=%d, y= %d ",$time,a, b, y);
b = 5;
$strobe("$strobe: time =%0d, a= %d, b=%d, y= %d ",$time ,a, b, y);
#10;
end
從上面演示的例子可以看出,當(dāng)打印輸出連續(xù)賦值語句或module例化的輸出結(jié)果時,在當(dāng)前的時間點內(nèi),$display無法輸出當(dāng)前運算結(jié)果,但 $strobe卻可以顯示組合邏輯結(jié)果。因此, 如果希望在當(dāng)前時間點內(nèi)打印連續(xù)賦值語句或module例化的輸出結(jié)果推薦使用$strobe, 而其他情況下使用$display可以顯示更多的細節(jié)。
·$monitor
一般在initial塊中調(diào)用,可以不間斷地對所設(shè)定的信號進行監(jiān)視。一般與時間度量系統(tǒng)函數(shù)$time一起使用,具體的應(yīng)用實例在下面時間度量系統(tǒng)函數(shù)給出。
(2)時間度量系統(tǒng)函數(shù)
·$time
該函數(shù)可以返回一個64位整數(shù)來表示當(dāng)前的仿真時刻,該時刻受時間尺度比例的影響,例如在下面的例子中,時間尺度是10ns,16ns與32ns就要變成1.6與3.2,同時因為$time的輸出要是整數(shù),因此實際的輸出為2和3。
`timescale 10ns / 1ns
module top_tb ();
reg set;
initial begin
$monitor("time=%0d",$time," ","set=",set);
#1.6 set = 0;
#1.6 set = 1;
end
endmodule
(3)仿真暫停與退出函數(shù)
·$finish
該系統(tǒng)函數(shù)可以退出仿真器,返回主操作系統(tǒng)結(jié)束仿真過程,并且可以輸出當(dāng)前的仿真時刻和位置。
·$stop
該系統(tǒng)函數(shù)暫停模擬并將模擬器置于交互模式。
(4)文件讀取到寄存器函數(shù)
·readmemh
$readmemh("<數(shù)據(jù)文件名>",<存儲器名>);
$readmemh("<數(shù)據(jù)文件名>",<存儲器名>,<起始地址>);
$readmemh("<數(shù)據(jù)文件名>",<存儲器名>,<起始地址>,<終止地址>);
·<數(shù)據(jù)文件名> 是指向一個文本文件,用來保存仿真的數(shù)據(jù)。每一行代表一個十六進制的數(shù)據(jù)。
·<存儲器名> 為仿真文件中例化的存儲器的名稱。
·<起始地址>,<終止地址> 指示將文本文件中的數(shù)據(jù)存儲到存儲器的位置段。
`timescale 1ns / 1ps
module top_tb();
reg clk = 0;
always clk = #10 ~clk;
reg [7:0] ram[0:127];
localparam FILE_NAME = "../../../led_sim.sim";
initial begin
$readmemh (FILE_NAME, ram, 2, 8); //從第二個16進制數(shù)據(jù)讀起,但是由于ram是8位的因此每次存入8位
end
integer i;
initial
begin
#20;
for(i = 0; i < 16; i = i + 1)
$display("ram[%02d] = 0x%h ", i, ram[ i ] );
#8000;
$stop;
end
endmodule
上圖左邊是文件中的數(shù)據(jù),右邊是仿真中的輸出值,可以看到ram[00],ram[01],ram[09]-ram[15]的值都是0xxx(未初始化的值),真正初始化的只有ram[02]- ram[08]。
(5)隨機數(shù)生成函數(shù)
·$random
一般用法是$random%b,其中b>0,它給出了一個范圍在(-b+1):(b-1)中的隨機數(shù)。也可以通過位拼接操作{$random}%b,生成0:(b-1)之間的隨機數(shù)。
reg [23:0] rand;
rand = $random%60; // 生成一個范圍在 [-59,59] 之間的隨機數(shù)
rand = {$random}%60;// 生成一個范圍在 [0,59] 之間的隨機數(shù)
08編譯預(yù)處理
·宏定義 `define
`define WORDSIZE 8
module
reg [`WORDSIZE-1:0] data; //相當(dāng)于定義[7:0],注意在引用時也要加`
...
endmodule
·文件包含 `include
`include相當(dāng)于將被引用的文件全部囊括進引用文件中??梢詫⒁恍┏S玫暮甓x命令或任務(wù)(task)組成一個文件,然后用`include命令將這些宏定義包含到自己所寫的源文件中。
·時間尺度 `timescale
該命令的格式如下:`timescale<時間單位>/<時間精度>,時間單位參量用來定義模塊中仿真時間和延遲時間的基準(zhǔn)單位,時間精度參量用來定義該模塊仿真時間的精確程度。
`timescale 10ns/1ns
parameter d = 1.55;
#d實際上是延時16ns d //時間單位為10ns,時間精度為1ns,1.55*10=15.5,再根據(jù)精度得到16,因此
審核編輯 :李倩
-
模塊
+關(guān)注
關(guān)注
7文章
2695瀏覽量
47431 -
仿真
+關(guān)注
關(guān)注
50文章
4070瀏覽量
133552 -
Verilog
+關(guān)注
關(guān)注
28文章
1351瀏覽量
110074
原文標(biāo)題:Verilog常用基礎(chǔ)語法全梳理
文章出處:【微信號:ZYNQ,微信公眾號:ZYNQ】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論