文章目錄
前言
本文節選自《FPGA之道》,來一起學習下作者對於並行與串行的講解。
Verilog的並行語句
在Verilog的基本程序框架一節中,我們了解到,module模塊實現中的語句部分里面的語句都是並行的。那么到底Verilog里面有哪些並行語句可以供我們使用呢?請看如下描述:
module <module_name>(<port_list>);
<Verilog連續賦值語句>;
<Verilog程序塊語句>;
<Verilog實例化語句>;
<Verilog生成語句>;
<Verilog函數調用語句>;
<Verilog模塊說明語句>;
endmodule;
以上這些並行語句,沒有哪一類是不可或缺的,但是一個module中怎么着也得至少有一條,否則雖然從語法上來講沒什么問題,但是這個module就不具有任何功能了。一般來說,只要有模塊實例化語句、程序段語句和連續賦值語句這三類語句就足夠描述FPGA的功能了,這也正是我們在【Verilog的基本程序框架】一節中所介紹的。下面詳細介紹一下這些並行語句。
Verilog連續賦值語句
Verilog中共有兩種連續賦值語句,即普通連續賦值語句和條件連續賦值語句。它們都只能給線網類型的變量賦值,前面的章節已經對於這兩種語句有過介紹,這里簡要總結如下:
普通連續賦值語句
該語句的顯式語法為:
assign <wire_name> = ;
也可以有隱式的寫法,即在聲明線網變量時同時賦值,例如:
wire a;
assign a = 1’b1;
可以簡寫成:
wire a = 1’b1;
條件連續賦值語句
該語句的顯式語法為:
assign = <1-bit_select> ? <input_for_1> : <input_for_0>;
也可以有隱式的寫法,即在聲明線網變量時同時賦值,例如:
wire a, b, sel, c;
assign c = sel ? a : b;
可以簡寫成:
wire c = sel ? a : b;
Verilog程序塊語句
Verilog中共包含兩種程序塊語句——initial與always,它們的本質區別是initial程序塊僅在程序的最開始執行一次,而always程序塊會不斷地、循環地得到執行。因此,initial程序塊主要負責模塊的初始化功能,而always程序塊才主要負責模塊的功能描述。下面,我們將針對always程序塊,進行詳細講解。always的語法結構如下:
always@(<sensitive_list>)
begin : <lable> <statements>;
end
其中,是可選的,它是程序塊的標號,主要起到提高代碼可讀性的作用。注意,如果always中只包含一條子語句,那么begin-end關鍵字可省略,不過必須緊跟在begin關鍵字的后面,因此有的程序塊不能省略begin-end關鍵字,其它含有begin-end關鍵字語法的語句也是類似。
之前已經介紹過,按照<sensitive_list>的形式來分,always共有三個“純種”的基本類型:純組合always、純同步時序邏輯always、具有異步復位的同步時序邏輯always。不過如果從always程序塊的結構入手,我們的可以將always的語法划分可以更細一些,即:純組合always、純時序always、具有同步復位的always、具有異步復位的always以及具有混合復位的always。可見,基於結構的划分比基於<sensitive_list>的划分更細致一些、面更廣一些。基於結構的划分其實就是基於時鍾描述結構的划分,因此,首先來介紹一下在always中表示時鍾事件的方法。
沿事件
時鍾描述實際上就是基於時鍾信號跳變沿的描述。如果在有一個變量a,那么表示a變化的時候會觸發always的執行,但是如果要描述時序邏輯,我們顯然需要更精確的描述方法,於是Verilog提供了沿事件。沿事件的描述有兩種語法:
posedge ,
negedge ,
分別對應敏感變量的上升沿事件或者下降沿事件。我們可以利用這兩句語法來方便的描述時鍾等事件。
切記!在一個always的敏感量表中,只能出現一個時鍾信號的一種邊沿事件。這是由寄存器的結構決定的,因為一個寄存器只有一個時鍾端口,並且只敏感這個端口的某一個邊沿,因此凡是不尊重這個事實的代碼都是不可綜合的。不過,異步復位信號有時候也做邊沿事件放入到敏感量表中,但是請注意,異步復位其實是電平敏感事件,之所以作為邊沿事件放入到敏感量表中,很大程度上是為了方便仿真。
下面根據時鍾描述結構的不同,分別介紹五種基本always代碼結構如下:
純組合always
純組合always語法如下:
always@(<sensitive_list>)begin
;
end
參考例子如下:
// b must be register data types
always@(a)begin
b = not a;
end
上述例子描述了一個非門的結構,關於純組合always程序塊,有三點需要注意:
一、純組合always程序塊中的語句強烈推薦只使用阻塞賦值符號,而時序always程序塊中推薦只使用非阻塞賦值符號,否則會帶來非常多的隱患。
二、雖然從字面上理解,always是在變量a出現變化的情況下才觸發執行,但是不可自作聰明將上例寫成:
// It is wrong!!
always@(posedge a or negedge a)begin
b = not a;
end
注意,只有時序邏輯才能用posedge和negedge關鍵字,雖然從代碼事件解釋來看上述兩例好像功能相似,但是若出現沿事件關鍵字,則編譯器會將程序塊綜合為時序邏輯,而這個世界上目前還沒有既能夠敏感一個信號上升沿又能夠敏感這個信號下降沿的觸發器,所以綜合會報錯。
三、若<sensitive_list>中有多個變量,則可以用逗號“,”或者關鍵字or分隔開來。如果<sensitive_list>中的變量確實太多,Verilog給大家提供了一個偷懶的方法,那就是使用匹配符號“”,此時編譯器將會完成<sensitive_list>中的元素推斷。例如:
always@()
或
always@*
純時序always
純時序always的語法如下:
always@(<edge_type> clk) // only clk in the sensitive list
begin
;
end
參考例子如下:
// a must be register data types
always@(posedge clk)
begin
a <= b;
end
具有同步復位的always
具有同步復位的always的語法如下:
always@(<edge_type> clk)
begin
if(rst) //or if(!rst)
begin
;
end
else
begin
;
end
end
參考例子如下:
//a must be register data types
always@(negedge clk)
if(!rst)
a <= 1’b0;
else
a <= b & c;
具有異步復位的always
具有異步復位的always語法如下:
always@(<edge_type> clk, <edge_type> aRst)
begin
if(aRst) //or if(!aRst)
begin
;
end
else
begin
;
end
end
參考例子如下:
//n must be register data types
always @(posedge clk or negedge aRst)
if (!aRst)
n <= 8’b0;
else
n <= m;
注意,在Verilog中必須通過敏感量表中的一個沿事件再加上代碼中的電平判斷來實現一個電平敏感的異步復位。以下寫法雖然道理上也說的通,但是卻是Verilog不支持的:
always @(posedge clk or aRst) //will cause compile error
if (!aRst)
n <= 8’b0;
else
n <= m;
這是由於always敏感量表中一旦出現了沿事件,就不允許再出現描述組合邏輯的信號事件了。
具有混合復位的always
具有混合復位的always的語法如下:
always@(<edge_type> clk, <edge_type> aRst)
begin
if(aRst) //or if(!aRst)
begin
;
end
else
begin
if(rst) //or if(!rst)
begin
;
end
else
begin
;
end
end
end
也可以寫成如下形式:
always@(<edge_type> clk, <edge_type> aRst)
begin
if(aRst) //or if(!aRst)
begin
;
end
else if(rst) //or else if(!rst)
begin
;
end
else
begin
;
end
end
參考例子如下:
//a must be register data types
always@(posedge clk, negedge aRst)
if(!aRst)
a <= 4’h0;
else
if(rst) //or if(!rst)
a <= 4’hF;
else
a <= b;
Verilog實例化語句
實例化語句是非常重要的一種語句,有了它,我們才可以化繁為簡、聚簡成繁!在之前的Verilog基本程序框架小節中我們對實例化語句進行了一些簡單了解,而在這里我們將詳細介紹一下Verilog中的實例化語句。Verilog語言中支持兩種模塊實例化方式——單獨實例化與數組實例化,分別介紹如下:
單獨實例化
單獨實例化的語法如下:
<module_name> <instance_name> (.<port_name_0> (),
.<port_name_1> (),
…
.<port_name_N> ());
其中,<module_name>是一個已經完成模塊的名字,<instance_name>是我們給它實例化對象起的一個名字,這兩者之間的對應關系很像C++中類和對象之間的關系。<port_name_X>對應被實例化模塊中的具體端口名稱,其后的為與端口連接的父模塊內部的變量。例如:
wire a, b, c;
myAnd insAnd (.in0 (a), .in1(b), .out©);
注意,實例化的時候,實例的輸出端口只能連接線網類型的變量,而輸入端口可以連接線網或者寄存器類型的變量。
數組實例化
有些情況下,我們可能需要同時實例化一個模塊多次,這個時候如果使用單獨實例化語句會使代碼顯得非常的臃腫,也不利於閱讀和修改,於是Verilog提供了數組實例化語句,語法如下:
<module_name> <instance_name> <instance_array_range>
(.<port_name_0> (variable0),
.<port_name_1> (variable1),
…
.<port_name_N> (variableN));
可以看出,相比於單獨實例化語句,它主要多了一個<instance_array_range>參數,利用這個參數,我們就可以控制實例的數量。例如:
wire [3:0] a, b, c;
myAnd insAnd[3:0] (.in0 (a), .in1(b), .out©);
上述數組實例化語句的功能相當於
myAnd insAnd3 (.in0 (a[3]), .in1(b[3]), .out(c[3]));
myAnd insAnd2 (.in0 (a[2]), .in1(b[2]), .out(c[2]));
myAnd insAnd1 (.in0 (a[1]), .in1(b[1]), .out(c[1]));
myAnd insAnd0 (.in0 (a[0]), .in1(b[0]), .out(c[0]));
有些時候,眾多實例中的有些端口是需要共用信號的,例如使能信號,此時可以寫成這樣:
wire en;
wire [3:0] a, b, c;
myEnAnd insEnAnd[3:0] (.in0 (a), .in1(b), .inEn (en), .out©);
此時的數組實例化語句的功能相當於
myAnd insAnd3 (.in0 (a[3]), .in1(b[3]), .inEn (en), .out(c[3]));
myAnd insAnd2 (.in0 (a[2]), .in1(b[2]), .inEn (en), .out(c[2]));
myAnd insAnd1 (.in0 (a[1]), .in1(b[1]), .inEn (en), .out(c[1]));
myAnd insAnd0 (.in0 (a[0]), .in1(b[0]), .inEn (en), .out(c[0]));
注意,數組實例化時,對輸入的變量位寬是有一定要求的:
一、等於所有實例對應端口的位寬之和。例如對於上例的變量a來說,它的位寬等於4個實例中in0端口的位寬和:1bit*4 = 4bits。這樣變量的位寬將會被均分到各個實例的對應端口上;
二、等於模塊對應端口的位寬。例如對於上例的變量en來說,它的位寬就等於模塊只能夠inEn端口的位寬,為1bit,此時該變量就會被連接至所有的實例對應的端口上。
對於其他情況的位寬輸入Verilog的數組實例化語句都是不支持的,請不要在這個地方進行錯誤的發明創造。
實例參數重載
參數重載也是實例化語句的一個重要組成部分。在Verilog基本程序框架中,我們提到過,為了增強模塊的重用性,Verilog會在模塊中定義一些參數,從而通過再例化的時候對參數進行重載來適應不同的需求。按照例化時對參數的重新賦值方式,我們可以把參數重載分為內部重載與外部重載,分別介紹如下:
內部重載
內部重載使用”#(.<parameter_name>(new_value), …)”語法,例如:
cellAnd #(.WIDTH(4)) m (a,b,c);
這是我們在【Verilog基本程序模板】小節中給出的例子。
外部重載
相比於在模塊實例化的時候來修改參數的值,外部重載允許在編譯的時候再修改參數的值。外部重載需要用到defparam關鍵字,舉例如下:
cellAnd m (a,b,c);
defparam m.WIDTH = 4;
不過在使用defparam的時候需謹慎,因為有些綜合工具或者它們的早期版本並不支持該語法。
端口賦值形式
實例化時實例端口的賦值形式有多種,當然,最常用,最典型也是最推薦的就是映射賦值,不過除此以外,端口賦值還有多種形式,列舉如下供大家了解:
一、映射賦值。例如:
wire a, b, c;
myAnd insAnd (.in0 (a), .in1(b), .out©);
二、位置賦值。例如:
wire a, b, c;
myAnd insAnd (a, b, c);
三、部分賦值。這是由於有些模塊在使用時並不是所有端口都需要的,若上例中的端口b是可以不使用的,那么按照映射賦值可以寫成:
myAnd insAnd (.in0 (a), .out©);
而按照位置賦值必須寫成:
myAnd insAnd (a, , c);
注意其中的多余的那個逗號,是用來占位用的,有了它,后面的c變量來能正確對應到out端口。
四、常數賦值。例如:
wire a, c;
myAnd insAnd (.in0 (a), .in1(1’b1), .out©);
注意,常數只能用於實例的輸入端口。
五、表達式賦值。例如:
wire a, b, c, d;
myAnd insAnd (.in0 (a&d), .in1(~b), .out©);
不過不建議這樣做,因為不太符合規范。
Verilog生成語句
Verilog中的生成語句主要使用generate語法關鍵字,按照形式主要分為循環生成與條件生成,分別介紹如下:
循環生成
循環生成的主要目的是簡化我們的代碼書寫,利用循環生成語句我們可以將之前需要寫很多條比較相似的語句才能實現的功能用很簡短的循環生成語句來代替。基本語法如下:
genvar ;
generate
for (=0; < ; =+1)
begin:
end
endgenerate
關於以上語法有四點注意:
1、循環生成中for語句使用的變量必須用genvar關鍵字定義,genvar關鍵字可以寫在generate語句外面,也可以寫在generate語句里面,只要先於for語句聲明即可;
2、必須給循環段起一個名字。這是一個強制規定,並且也是利用循環生成語句生成多個實例的時候分配名字所必須的;
3、for語句的內容必須加begin-end,即使只有一條語句也不能省略。這也是一個強制規定,而且給循環起名字也離不開begin關鍵字;
4、可以是實例化語句也可以是連續賦值語句。
關於循環生成,舉例如下:
input [3:0] a,b;
output [3:0] c,d;
generate
genvar i;
for (i=0; i < 4; i=i+1)
begin : genExample
myAnd insAnd (.a(a[i]), .b(b[i]), .c(c[i]));
assign d[i] = a[i];
end
endgenerate
注意,利用循環生成語句生成的實例名稱不能像數組例化那樣用方括號表示,否則會報錯。那么,你可能會疑惑上例中實例的名字,其實,上述實例化展開來類似:
myAnd genExample(0).insAnd (.a(a[0]), .b(b[0]), .c(c[0]));
myAnd genExample(1).insAnd (.a(a[1]), .b(b[1]), .c(c[1]));
myAnd genExample(2).insAnd (.a(a[2]), .b(b[2]), .c(c[2]));
myAnd genExample(3).insAnd (.a(a[3]), .b(b[3]), .c(c[3]));
這也是為什么循環生成語句必須要有個名字。從上例我們還可以看出,當循環語句用作實例化時,所表述的功能跟數組實例化語句其實是類似的。
最后,循環生成語句是支持嵌套的,例如二重循環生成語法如下:
genvar <var1>, <var2>;
generate for (<var1>=0; <var1> < <limit>; <var1>=<var1>+1) begin: <label_1> for (<var2>=0; <var2> < <limit>; <var2>=<var2>+1) begin: <label_2> <code> end end
endgenerate
條件生成
條件生成的目的是為了左右編譯器的行為,類似於C語言中的條件選擇宏定義,根據一些初始參數來決定載入哪部分代碼來進行編譯。Verilog中共提供了兩種條件生成語句,一種是generate-if語句,一種是generate-case語句,兩者的功能幾乎相同,只是書寫形式不一樣而已,分別介紹如下:
generate-if語句
該語句的語法如下:
generate
if (<condition>) begin: <label_1>
<code>;
end else if (<condition>) begin: <label_2>
<code>;
end else begin: <label_3>
<code>;
end
endgenerate
關於該語法有三點注意:
1、必須是常量比較,例如一些參數,這樣編譯器才可以在編譯前確定需要使用的代碼;
2、if語句的內容中,begin-end只有在有多條語句時才是必須的;
3、每一個條件分支的名稱是可選的,這點不像循環生成語句那么嚴格。
關於generate-if語句,舉例如下:
wire c, d0, d1, d2;
parameter sel = 1;
generate
if (sel == 0)
assign c = d0;
else if (sel == 1)
assign c = d1;
else
assign c = d2;
endgenerate
該例子表示編譯器會根據參數sel的值,來確定到底是讓d0~d2中哪個變量和c連通。但是注意,一旦連通,那么要想更改必須修改參數后重新編譯,如果需要動態選擇,可以寫成如下這樣,但是資源上卻都需要一個多路復用器來實現。
assign c = (sel == 0) ? d0 : (sel == 1) ? d1 : d2;
generate-case語句
該語句的語法如下:
generate
case (<constant_expression>) <value>: begin: <label_1> <code> end <value>: begin: <label_2>
<code> end …… default: begin: <label_N>
<code>
end endcase endgenerate
關於該語法也有三點注意,和generate-if類似:
1、<constant_expression>必須是常量比較,例如一些參數,這樣編譯器才可以在編譯前確定需要使用的代碼;
2、case語句的內容中,begin-end只有在有多條語句時才是必須的;
3、每一個條件分支的名稱是可選的,這點不像循環生成語句那么嚴格。
關於generate-case語句,舉例如下:
wire c, d0, d1, d2;
parameter sel = 1;
generate
case (sel)
0 : assign c = d0;
1: assign c = d1;
default: assign c = d2;
endcase
endgenerate
該例所描述的功能和generate-if小節的例子是一摸一樣的。
Verilog函數調用語句
函數的定義可以放在模塊實現部分中的聲明部分,使用function語法定義如下:
function [<lower>:<upper>] <output_name> ;
input <name>;
<other inputs>
<variable declarations>
begin
<statements>
end
endfunction
關於函數的定義有以下幾點說明:
1、<output_name>既是輸出的變量名也是函數調用名,它的位寬由function關鍵字后面的范圍指定;
2、中,只能夠聲明寄存器類型的變量;
3、函數體中語句只能使用阻塞賦值符號;
4、函數調用的時候只能使用位置賦值,因此需要嚴格按照input的順序羅列變量;
5、函數調用可以用在並行語句中也可以用在串行語句中;
6、函數中可以調用別的函數;
7、函數支持遞歸,不過此情況一般用於仿真;
8、函數不能調用任務,這主要是由於任務中可以有定時相關語句,而函數中不能夠有。
舉例如下:
示例一:幾種常用函數調用方法;
module tft(input clk, a, b, c, output reg d,e);
function andFunc ;
input a;
input b;
begin
andFunc = a & b;
end
endfunction
always@(posedge clk)
begin
e <= andFunc(a, b); // called by no-blocking assignment
end
always@*
begin
d = andFunc(a, b); // called by blocking assignment
end
assign c = andFunc(a, b); // called by continuous assignment
endmodule
示例二:函數調用函數
module(input a,b, output c);
function bufFunc ;
input a;
begin bufFunc = a;
end
endfunction
function andFunc ;
input a;
input b;
reg t;
begin andFunc = bufFunc(a) & b;
end
endfunction
assign c = andFunc(a,b);
endmodule
示例三:函數遞歸
function [3:0] addFunc ;
input [3:0] a;
reg [3:0] t;
begin
if(a == 4'b0) addFunc = 1'b0;
else begin t = a - 1'b1; addFunc = a + addFunc(t);
end
end
endfunction
最后,需要提醒大家注意的是,函數的抽象級別比較高,它的編程思路更像是軟件而不是硬件,因此一般多用於仿真時使用,具體設計FPGA時,如果需要重復使用某一個功能,完全可以通過模塊實例化的方式來實現。
Verilog模塊說明語句
模塊說明語句的關鍵字是specify,它主要用來說明模塊的一些時延信息。它的語法如下:
specify
<specparam_declarations> //一些參數定義
<timing_constraint_checks> //設置一些時序檢查選項
<simple_pin-to-pin_path_delay> //設置模塊中組合邏輯管腳到管腳的時間延遲
<edge-sensitive_pin-to-pin_path_delay> //設置模塊中時序邏輯時鍾相關的時間延遲
<state-dependent_pin-to-pin_path_delay> //條件延遲語句,類似條件生成語句
endspecify
一個簡單的例子如下:
specify
specparam d_to_q =9;
specparam clk_to_q =11;
(d=>q) = d_to_q;
(clk=>q) = clk_to_q;
endspecify
一般來說,各個FPGA廠商一般會針對自己的根據硬件相關的一些原語編寫specify,這樣我們才能夠對我們的設計進行時序仿真或者時序分析,因此基本上我們不需要在自己設計的模塊中編寫specify。所以本小節僅對模塊說明語句進行一些簡單介紹,讓大家對specify有個概念,做個了解即可。
Verilog的串行語句
串行語句的執行思路是順序執行的,一般高級編程語言中的語句執行方式都是順序執行的,例如c語言,由此可見,順序執行的語句更容易幫助我們來表達我們的設計思想,尤其是使描述時序邏輯變得容易。所以,雖然FPGA的設計思路都是並行的,module中僅支持並行語句的調用,但是為了方便設計者表達自己的思想,尤其是表達時序邏輯的思想,Verilog中的一些並行語句中的子語句體允許是順序執行的,例如always。那么到底Verilog語言里面有哪些串行語句可以供我們使用呢?以always為例描述如下:
always@(...)
begin
<Verilog阻塞賦值語句>;
<Verilog非阻塞賦值語句>;
<Verilog條件語句>;
<Verilog循環語句>;
<Verilog等待語句>;
<Verilog函數調用語句>;
<Verilog任務調用語句>;
end
Verilog阻塞賦值語句
使用阻塞賦值操作符對變量進行賦值的語句叫阻塞賦值語句。一般來說,如果你認為你描述的這個變量在FPGA硬件中對應連線,那么你就應該使用阻塞賦值語句。使用阻塞賦值符號的賦值語句,一定要等到賦值行為結束之后才會開始執行下一條程序,因此阻塞賦值語句的書寫順序改變會引起綜合或者仿真的問題。舉例如下:
always@(c, d) begin
b = c & d;
a = ~ b;
end
若賦值語句順序顛倒會引起仿真的問題。
Verilog非阻塞賦值語句
使用非阻塞賦值操作符對變量進行賦值的語句叫非阻塞賦值語句。一般來說,如果你認為你描述的這個變量在FPGA硬件中對應寄存器等存儲記憶單元,那么你就應該使用非阻塞賦值語句。使用非阻塞賦值符號的賦值語句,在賦值行為未完成之前就會開始執行下一條程序,也正是因為如此,所以非阻塞賦值語句的書寫順序是無所謂的。舉例如下:
always@(posedge clk) begin
b <= c & d;
a <= ~ b;
end
賦值語句順序顛倒無所謂。
如果無視變量對應的硬件結構而亂用賦值符號的話,會造成非常大的隱患。
Verilog條件語句
條件語句是一種典型的串行語句。Verilog中有兩類條件語句——帶優先級條件語句和無優先級條件語句。其中優先級條件語中的各個條件分支是具有優先級的,且分支優先級按照書寫順序從高至低,代表為if條件語句;而無優先級條件語句中,各個分支間的地位是等同的,代表為case條件語句。除了if和case語句外,Verilog還支持casex和casez兩種衍生的無優先級條件語句,分別介紹如下:
if條件語句
if條件語句的完全語法如下:
if (<condition>) begin
<statement>;
end
else if (<condition>) begin
<statement>;
end
else begin
<statement>;
end
其中的 else if和else分支都不是必須的,可以根據具體情況來確定。以求A、B、C三者中的最大值為例描述如下:
if (A >= B and A >= C)
max = A;
else if (B >= C)
max<= B;
else
max <= C;
為什么說if條件語句是具有優先級的條件語句呢?需要從兩個方面來說:
第一,從語句的功能描述來分析。如果要描述上述求最大值的例子,我們可以這樣翻譯代碼:首先,判斷數A是不是大於等於B和C,如果成立,則最大值是A,結束判斷;否則說明A不是最大值,那么這時候只需判斷數B是不是大於等於C,如果成立,則最大值是B,判斷結束;否則,由於之前已經得出A、B兩數都不是最大值,那么最大值只能是C了。由此可見,每一個分支的判斷都是建立在寫在它之前的所有分支的基礎上的。
第二,從硬件實現上來說。上述求最大值的例子,對應到門級電路上,肯定是從A到max之間的路徑最短,即所經過的邏輯門最少,而從B到max之間的路徑次之,從C到max之間的路徑最長。關於門級實現可以參考如下示意圖:
由此可見,基於優先級條件語句的特點,如果我們知道A、B、C三個數中最大值的概率是B大於C大於A,那么我們應該把對B的判斷放在第一個分支,然后C放在第二個分支,而A放在最后一個分支。這樣,今后的仿真效率會更高,且對於具體的FPGA實現,也能保證最短路徑得到最充分的利用,這樣芯片即使工作在比較惡劣的環境下,也能保證故障率達到最低。
case條件語句
case條件語句的完全語法如下:
case (<expression>)
<constant-value1> :
begin <statements>;
end
<constant-value2> :
begin <statements>;
end
<other branchs>
default :
begin <statements>;
end
endcase
其中,<constant-value>的值必須互相不同,以四選一多路選擇器為例描述如下:
case (sel)
2’b00 : data = channel0;
2’b01 : data = channel1;
2’b10 : data = channel2;
2’b11 : data = channel3;
default : data = channel0;
endcase
上述例子中的分支已經覆蓋完全,但是還是有一個default分支,這雖然有些畫蛇添足,但確是一個編程的好習慣,請大家注意!
為什么說case條件語句是無優先級的條件語句呢?也需要從兩方面來說:
第一,從語句的功能描述來分析。如果要描述上述多路選擇器的例子,我們可以這樣翻譯代碼:如果sel等於2’b00,那么選擇第一路輸出;如果sel等於2’b01,那么選擇第二路輸出;如果sel等於2’b10,那么選擇第三路輸出;如果sel等於2’b11那么選擇第四路輸出。可見這四個分支的判斷之間沒有任何相互關系,也互不為前提。
第二,從硬件實現上來說。上述多路復用器的例子,對應到門級電路上,無論是channel0~3中的任何一個,到data的路徑都是等長的。關於門級實現可以參考如下示意圖:
由此可見,在使用無優先級條件語句時,分支的順序是無關的,不會影響電路的最終實現。
if與case的對比
為了進一步說明優先級條件語句與非優先級條件語句之間的區別,我們用if條件語句重寫上節中四選一多路選擇器的例子如下;
if(sel == 2'b00)
data = channel0;
else if(sel == 2'b01)
data = channel1;
else if (sel == 2'b10)
data = channel2;
else
data = channel3;
關於其門級實現可參考如下電路圖:
可見,此時,channel0~3到data的路徑長度就不一致了,最短的為一個兩輸入復用器延遲,最大的為3個兩輸入復用器延遲。當然,由於上圖並不是最簡形式,所以此處我們沒必要深究它與【case條件語句】小節中的例子到底孰優孰劣,但是請注意,由於目前的編譯器都會對我們的代碼有一定優化作用,因此有時候if和case也可能會綜合成為一樣的電路。
case語句中的判斷表達式有可能出現的情況比較多,但是分支卻有可能沒有那么多,因此下面介紹一些case的變形寫法,能夠更加方便我們去描述電路。
case語句的一些變形
首先,利用特殊的“或”符號——“,”來簡化代碼,例如,要構建一個三輸入的多路復用器,可以描述如下(當然,這並不是最優描述形式):
case (sel)
2’b00 : data = channel0;
2’b01 : data = channel1;
2’b10 ,
2’b11 : data = channel2;
default : data = channel0;
endcase
其次,case的常量和表達式還可以互換位置,例如
reg sel;
case (2'b00)
sel : data = channel0;
default : data = channel1;
endcase
case、casex與casez
在Verilog語法中,case的比較是十分高效的,但它的匹配成功要求所有位上的邏輯值必須精確相等。於是,Verilog又提供了casex與casez兩種語法結構作為補充,它們和case的語法結構相同,只不過分別以casex和casez開頭而已。這樣,在比較的時候就可以引入不關心位,從而能夠達到簡化代碼的效果。在【本篇->編程語法->Verilog基本語法->Verilog數據類型->Verilog四值邏輯系統】小節,我們介紹了Verilog中的四種邏輯形式:0、1、X、Z,那么,對於casex來說,它會將X、Z視為“不關心位”;而對於casez來說,它會將Z視為“不關心位”。
在Verilog中,我們可以用“?”來表示“不關心位”,討論如下:
條件表達式中有“不關心位”
舉例說明如下:
reg a;
case (a)
1’b0 : statement1;
1’b1 : statement2;
1’bx : statement3;
1’bz : statement4;
endcase
上例中,若a = 1’b0或1’b1,那么statement1或statement2將會執行;若我們令a = ?,那么statement4將會執行,因為語法認為“?”等於Z狀態。
reg a;
casez (a)
1’b0 : statement1;
1’b1 : statement2;
1’bx : statement3;
1’bz : statement4;
endcase
上例中,若a = 1’b0、1’b1或1’bx,那么statement1、statement2或statement3將會執行;但是若a = ?或者1’bz,那么statement1會執行,因為此時casez將這兩種值視為無關狀態,會直接執行第一條語句,所以statement4永遠得不到執行。
reg a;
casex (a)
1’b0 : statement1;
1’b1 : statement2;
1’bx : statement3;
1’bz : statement4;
endcase
上例中,若a = 1’b0或1’b1,那么statement1或statement2將會執行;但是若a = ?或者1’bx、1’bz,那么statement1會執行,因為此時casex將這三種值視為無關狀態,會直接執行第一條語句,所以statement3、statement4永遠得不到執行。
常數項中有“不關心位”
舉例如下:
case (sel)
3’b1?? : data0 = channel0;
3’b01? : data0 = channel1;
default : data0 = channel2;
endcase
由於case要求精確的匹配,所以無論當sel是什么情況,都只能執行default語句,因此data0只能取到channel2的值。
casex (sel)
3'b1z? : data1 = channel0;
3'b01x : data1 = channel1;
default : data1 = channel2;
endcase
由於casex將?、X、Z均視為“不關心位”,因此,sel從3’b1003’b111都能匹配3’b1z?,而sel從3’b0103’b011都能匹配3’b01x,而3’b000、3’b001什么都不匹配,因此data1可以取到channel0、channel1和channel2的所有值。
casez (sel)
3'b1z? : data2 = channel0;
3'b01x : data2 = channel1;
default : data2 = channel2;
endcase
由於casez將?、Z均視為“不關心位”,因此,sel從3’b1003’b111都能匹配3’b1z?,而sel從3’b0103’b011卻不能匹配3’b01x,再加上3’b000、3’b001也什么都不匹配,因此data2可以取到channel0和channel2的值,卻沒有辦法通過匹配獲得channel1的值。
上述幾個例子如果寫在一個module中,我們可以通過其綜合后的電路來更加形象的理解:
要想用case來實現上例casex實現的優先級譯碼功能,最優的情況下可以寫成這樣:
case (sel)
3’b001 : data0 = channel2;
3’b010,
3’b011 : data0 = channel1;
default : data0 = channel0;
endcase
最后,需要說明的一點是,casex和casez中,可以通過使用不關心位來實現代碼的簡化或一些特殊的邏輯功能,例如優先級譯碼器。但是在其他情況下請避免使用,因為casex和casez的很多用法都只能停留在仿真階段。
Verilog循環語句
Verilog中的循環語句有很多種,包括for循環、while循環、repeat循環以及forever循環等。這些循環語法中除了for循環有時候可以用來幫助我們簡化一些代碼的編寫外,基本都是主要用於仿真激勵的設計,因此本小節主要介紹一下Verilog中的for循環,剩下的將會在【功能仿真篇->仿真語法->Verilog Test Fixture】章節中介紹。
for循環的語法為:
integer ; //遞減
for ( = <initial_value>; >= <final_value>; =-1) begin
;
end
或者
integer ; //遞增
for ( = <initial_value>; <= <final_value>; =+1) begin
;
end
例如,如果我們要將一個矢量信號高低位顛倒的賦給另一個矢量信號,可以用for循環簡便的表述如下:
integer i;
for (i = 7; i >= 0; i=i+1) begin
a[i] <= b[7-i];
end
注意,在描述設計時,for循環一般不應該進行功能描述,而應該只進行結構描述。否則,由於for循環抽象級別比較高,編譯器不一定能夠正確給對應的實現電路,而且有時候很可能就不存在能夠對應該功能的電路。
Verilog等待語句
Verilog中有三種等待語句,分別介紹如下:
事件等待
事件等待的語法如下:
@( or or … )
每個always程序塊中都必有一個事件等待語法,除此以外,事件等待語法還可以位於always程序塊中,此時的always程序塊主要是用於仿真。
直接時間等待
直接時間等待的語法如下:
#
直接時間等待只能用於仿真。
表達式等待語句
表達式等待語句的語法如下:
wait ();
當為真的時候程序才往下執行,它也主要用於仿真。
由於等待語句主要用於仿真結構中,所以詳情請參閱【功能仿真篇->仿真語法->Verilog Test Fixture】小節。
Verilog函數調用語句
函數調用語句即可以用在並行語句中,也可以用在串行語句中。
Verilog任務調用語句
任務即是task,它的語法如下:
task <task_name>;
input <input_name>;
<more_inputs>
output <output_name>;
<more_outputs>
begin
;
end
endtask
關於任務調用有以下幾點說明:
1、任務中有輸入端口也有輸出端口,所以它的調用是通過輸入端口將數據傳入任務中,然后從輸出端口得到結果,所以任務可以同時有多個輸出,這點與函數不同;
2、中,只能夠聲明寄存器類型的變量;
3、任務中可以使用阻塞賦值也可以使用非阻塞賦值,具體要看調用任務的always是在描述時序邏輯還是組合邏輯;
4、任務調用的時候只能使用位置賦值,因此需要嚴格按照端口的順序羅列變量;
5、任務調用只能用在串行語句中;
6、任務中可以調用別的任務;
7、任務支持遞歸,不過此情況一般用於仿真;
8、任務中可以調用函數。
舉例如下:
示例一:任務中的阻塞賦值與非阻塞賦值
task bufTask;
input a;
output b;
begin
b = a;
end
endtask
task regTask;
input a;
output reg b;
begin
b <= a;
end
endtask
always@*
bufTask(a,b);
always@(posedge clk)
regTask(a,b);
示例二:任務調用任務
task andTask;
input a,b;
output c;
reg t;
begin
t = bufFunc(a);
c = t & b;
end
endtask
示例三:任務遞歸
task addTask ;
input [3:0] a;
output [3:0] b;
reg [3:0] t,h;
begin
if(a == 4’b0)
b = 4’b0;
else begin
t = a - 1’b1;
addTask(t, h);
b = h + 1’b1;
end
end
endtask
最后,需要提醒大家注意的是,任務和一般也多用於仿真時使用,雖然任務的描述跟模塊有些類似,但是具體在設計FPGA時,如果需要重復使用某一個功能,完全可以通過模塊實例化的方式來實現。
文章來源: reborn.blog.csdn.net,作者:李銳博恩,版權歸原作者所有,如需轉載,請聯系作者。
原文鏈接:reborn.blog.csdn.net/article/details/104304347