前面已經記錄了一些組成Verilog的基本組成,可以用這些基本組成來構成表達式。這一節,就來記錄一下把這些表達式構成一個文件的各種行為描述語句。 ①這里用Verilog基本要素進行的行為描述主要是針對綜合來的,也就是可以設計出實際電路來的(行為描述語句有兩大子集,一個是面向綜合,一個是面向仿真)。②行為描述語句一般指放在always語句中。內容提綱如下所示:
·觸發事件控制
·條件語句(if與case語句)
·循環語句
·任務和函數
·編譯預處理
一、觸發事件控制
①電平敏感事件是指 指定信號的電平發生變化時發生指定的行為。
②邊沿觸發事件(信號跳變沿)是指 指定信號的邊沿信號跳變時發生指定的行為,分為信號的上升沿(x→1或者z→1或者0→1)和下降沿x→0或者z→0或者1→0)。
③信號跳變沿觸發電路對信號的某一跳變沿敏感名字一個時鍾周期內,只有一個上升沿和一個下降沿,因此計算結果在一個周期內保持不變,而電平觸發電路則可能會引起數據在一個時鍾周期內變化一次或多次。
其他敏感列表的事項請查看這篇博文:http://www.cnblogs.com/IClearner/p/7253002.html。
二、條件語句
Verilog的條件語句包括if語句和case語句。
(1)if語句
①if語句中的條件判斷表達式(括號中的那個)一般為邏輯表達式或者關系表達式或者就一個變量。如果表達式的值是0、X或者Z,則全部按照“假”處理;若為1,則按照“真”處理。
②在應用中,else if 分支的語句數目由實際情況決定;else分支可以省略,但在描述組合邏輯中,會綜合得到鎖存器。
(2)case語句
①case語句,case語句是一個多路條件分支的形式,常用於多路譯碼、狀態機以及微處理器的指令譯碼等場合,有case分支 、casez分支、casex分支這三種形式。
②case語句首先對條件表達式求值,然后同時並行對各分支項求值並進行比較;當case語句跳轉到某一分支后,控制指針將轉移到endcase。
③case分支,case分支語句在執行時,條件表達式和分支之間進行的比較是一種按位進行的全等比較,也就是說,只有在分支項表達式和條件表達式的每一位都彼此相等的情況下,才會認為二者是相等的;此外x、z這兩種邏輯狀態也作為合法的狀態參與比較(1 ==1,,0==0,x==x,z==z)。
④當分支的結果以常數出現時,如果沒有指定位寬,則Verilog編譯器默認其具有與PC字長相等的位寬。
⑤在casez分支中,如果分支取值的某些位為高阻z,則這些位的比較就不予以考慮,只關注其他位的比較結果。
⑥在casex分支中,則x、z都不予以考慮。
(3)if與case的區別
①if語句指定了一個有優先級的編碼邏輯,而case語句生成的邏輯是並行的,不具有優先級。
②通常if結構得出的電路速度較慢,但是占用面積較小,由於速度慢,因此不適合構建特別長的if語句。
③case結構較if結構的速度快,但是占用面積大。
三、循環語句
循環語句有四種:for循環、repeat循環、while循環、forever循環。但是forever循環不能進行綜合,而其他三種在一定情況下可以進行綜合,因此這里記錄可以綜合的這三種。一般在電路設計中,不是經常用到循環語句,因為循環語句不好進行優化,占用的資源多,即使用到也是用到for循環。
(1)for循環
①for循環中,在循環次數確定的情況下,也就是循環結束條件是個常量下,才可以進行綜合。主體注意要用begin...end來進行包括。
②在Verilog中,不支持++,--這些運算,需要用完整的i=i+1來完成。
(2)repeat循環
①repeat語句執行指定的循環次數,如果循環次數是x或者z,那么循環次數將會按照0次處理。主體注意要用begin...end來進行包括。
②當循環次數在程序編譯過程中保持不變時,可以進行綜合。
(3)while循環
①只有在指定的循環條件為真(0、x、z都是假)時才會重復執行循環體,注意循環體要用begin...end來包括。
②在執行語句中,必須有改變循環執行條件表達式的值的語句,否則循環可能進入死循環。
循環語句使用注意事項:
①循環語句中出現的變量都采用阻塞賦值(時序中,即沿觸發中),這是因為在always塊中,使用非阻塞賦值時,只有在always結束后才會把右端的值賦給左邊的寄存器,如果采用非阻塞賦值,則會造成循環語句只執行一次。
②在沿觸發中,語句采用阻塞賦值,這是由於循環語句本質是由組合邏輯實現的,雖然整體是基於時序邏輯,但是循環部分確是組合邏輯。
③基於循環語句的Verilog顯得相對精簡,但是面向硬件設計的關注點是時序、面積、功耗等,在使用循環語句進行電路設計時要慎重。
四、任務和函數
(1)任務語句(task)
任務(task)就是一段封裝在“task-endtask”之間的程序。任務是通過調用來執行的,而且只有在調用時才執行,如果定義了任務,但是在整個過程中都沒有調用它,則該任務不會被執行的。調用某個任務時可能需要它處理某些數據並返回操作結果,所以Verilog 中的task 存在輸入端和輸出端。
任務定義語法格式如下所示:
1 task<任務名>; // <= task task_id;
2 <端口及數據類型聲明語句> // <= [declaration]
3 <語句1> // <= procedural_statement
4 <語句2> // <= procedural_statement
5 ..... // <=
6 <語句n> // <= procedural_statement
7 endtask // <= endtask
上升的格式中,關鍵詞 task 和 endtask 將它們之間的內容標志成一個任務定義,task 標志着一個任務定義結構的開始;task_id 是任務名;可選項 declaration 是端口聲明語句和變量聲明語句,任務接收輸入值和返回輸出值就是通過此處聲明的端口進行的;procedural_statement 是一段用來完成這個任務操作的過程語句,如果過程語句多於一條,應將其放在語句塊內;endtask 為任務定義結構體結束標志。一個任務的例子如下所示:
1 task find_max; //任務定義結構開頭,命名為 find_max
2 input [15:0] x,y; //輸入端口說明
3 output [15:0] tmp; //輸出端口說明
4 if(x>y) //給出任務定義的描述語句
5 tmp = x; 6 else
7 tmp = y; 8 endtask
編寫任務時,需要注意:
①調用某個任務時,可能需要它處理某些數據並返回操作結果,因此任務應當有接收數據的輸入端和返回數據的輸出端。
②任務的輸入、輸出和雙向端口的數量不受限制,甚至可以沒有輸入、輸出和雙向端口。
③在任務定義的描述語句中,可以出現不可綜合的語句,但是這樣會引起任務不可綜合。
④任務的定義結構定義不能在initial或者always語句中,在任務定義結構內不能出現 initial 和 always 過程塊;但是任務的調用必須在這兩個語句塊中。
⑤任務定義中可以出現“disable”終止語句,但是這是不可綜合的;在仿真中,如果出現的該終止語句,將中斷正在執行的任務;任務被中斷后,程序流程將返回到調用任務的地方繼續向下執行。
⑥在第一行“task”語句中不能列出端口名稱。
⑦可以使用出現不可綜合操作符合語句(使用最為頻繁的就是延遲控制語句,例如#10ns),但這樣會造成該任務不可綜合。
任務的使用(調用):
task_id (端口1,端口2,...,端口n);
task_id 是要調用的任務名,端口 1、端口 2,…是參數列表。參數列表給出傳入任務的數據(進入任務的輸入端)和接收返回結果的變量(從任務的輸出端接收返回結果)。
任務調用的注意事項:
①任務調用中接收返回數據的變量必須是寄存器類型。
②任務調用語句和一條普通的行為描述語句的處理方法一致。
③可綜合任務只能實現組合邏輯;也就是說調用可綜合任務的時間為0。而在面向仿真的任務中可以帶有時序控制,如多個時鍾周期或時延,因此面向仿真的任務調用時間不為0。
④在任務中可以調用其他任務或者函數,也可以調用自身。
⑤當調用輸入、輸出和雙向端口時,任務調用語句必須包含端口名列表,且信號端口順序和類型必須和任務定義結構中的順序和類型一致。任務的輸出端口必須和寄存器類型的數據變量相對應。
(2)函數(function)
函數與任務類似,函數(function)是一段封裝在“function-endfunction”之間的程序。函數的定義格式如下所示:
1 function<返回值的類型或范圍>(函數名); 2 <端口說明語句>// input XXX;
3 <變量類型說明語句>// reg YYY;
4 begin
5 <語句>
6 ........ 7 函數名= ZZZ; //函數名就相當於輸出變量;
8 end
9 endfunction
函數編寫的注意事項如下:
①定義函數時至少要有一個輸入參量;可以按照ANSI 和module 形式直接定義輸入端口;例如:
function[63:0] alu (input[63:0] a, b,input[7:0] opcode );
此外,函數至少有一個輸入端口,可以有多個輸入端口;不允許輸出聲明,也不允許雙向端口,因為函數名本身充當一個返回值。
②和任務一樣,函數的定義只能在模塊中完成,不能出現在過程塊中。
③函數結果中,不能出現任何形式的時間控制語句(如#,wait),也不能使用disable。
④函數內部可以調用函數,但是不能調用任務。
⑤如果描述語句是可綜合的,則必須所有分支均賦值,不允許存在不賦值情況,只能按照組合邏輯方式描述。
函數編寫的一個例子如下所示,這是一個計算有符號數絕對值的函數:
1 function[width-1 : 0] DWF_absval; 2 input [width-1 : 0] A; 3 begin
4 DWF_absval = ((^(A ^ A) !== 1'b0)) ? {width{1'bx}} :(A[width-1] == 1'b0) ? A : (-A);
5 end
6 endfunction
關於函數調用的注意事項:
①函數調用可以在過程塊中完成,也可以在assign這樣的連續賦值語句中出現。
②函數調用語句不能單獨作為一條語句出現,只能作為賦值語句的右端操作數。
使用函數的一個完整實例:一個乘法電路——輸入操作數a和b,輸出他們的乘積;這里使用移位相加的算法來實現乘法。使用函數的實現的代碼如下:
1 module MAC #(parameter N=8)( 2 input clk, 3 input reset, 4 input [N-1:0] opa, 5 input [N-1:0] opb, 6 output reg[2*N-1:0] out 7 ); 8
9 function[2*N-1:0] mult;//函數定義,mult 函數完成乘法操作
10 input[N-1:0] opa; //函數只能定義輸入端
11 input[N-1:0] opb; //輸出端口為函數名本身
12 reg[2*N-1:0] result; 13 integer i; 14 begin
15 result = opa[0]? opb : 0; 16 for(i= 1; i <= N-1; i = i+1) 17 begin
18 if(opa[i]==1) result=result+(opb<<i); 19 end
20 mult=result; 21 end
22 endfunction
23
24 wire[2*N-1:0] sum; 25 assign sum = mult(opa,opb) + out; 26 always@(posedge clk or negedge reset) 27 if(!reset) 28 out<=0; 29 else
30 out<=sum; 31
32 endmodule
(3)關於任務和函數的對比和總結
①task語句和function語句都是可以綜合的,但是只能實現組合邏輯,具備組合邏輯的優點和缺點。
②task和function都必須在模塊內部定義,除參數個數不同外,還可以定義內部變量,包括寄存器、時間變量、整型等,但是不能定義線網變量。二者只能出現在行為描述中。
③區別:
比較點 |
任務 |
函數 |
輸入、輸出 |
可以有任意多個種類類型的參數 |
至少有一個輸入,不能有輸入和雙向端口 |
調用 |
只能在過程語句中調用 |
可以在過程語句中調用,也可以在作為賦值操作的表達式用在assign賦值語句中 |
觸發事件控制 |
任務可以包含延遲控制語句等,但是只能面向仿真,不可綜合。 |
不能有時間控制語句 |
調用其他函數和任務 |
可以調用其他函數和任務 |
只能調用函數 |
返回值 |
任務沒有返回值 |
函數向調用它的表達式返回一個值 |
其他 |
任務調用語句可以作為一條完整的語句出現 |
函數語句只能作為賦值操作的表達式,不能作為一條獨立的語句出現 |
五、編譯預處理語句
①編譯預處理是Verilog編譯系統的一個組成部分,指編譯系統會對一些特殊命令進行預處理,然后將預處理結果和源程序一起在進行通常的編譯處理。編譯預處理是可以綜合的。
②在Verilog語言編譯時,特定的編譯器指令在整個編譯過程中有效(編譯過程可跨越多個文件),直到遇到其他的不同的編譯程序指令。
③常用的編譯預處理語句有:`define、`undef ; `ifdef、`elsif 、`else、`endif ; `include ; `timescale。
(1)`define,`undef
格式:`define 宏的名稱 宏的正文
①宏定義的名稱可以是大寫,也可以是小寫,但是主要不要和變量名重復,此外宏定義的使用要注意帶上`。
②和所有的編譯器偽指令一樣,宏定義在超過單個文件邊界時仍然有效(對工程中的其他源文件),除非被后面的`define或者`undef偽指令覆蓋,否則`define不受范圍的限制。
③當用變量定義宏時,變量可以在宏正文中使用,並且在使用宏時可以用實際的變量表達式代替。
④通過用反斜杠\轉移中間的換行符,宏定義可以跨越幾行,新的行是宏正文的一部分。
⑤宏定義的行末不需要添加分號或者逗號表示結束
⑥宏正文不能分離的語言記號包括注釋、數字、字符串、保留的關鍵字、運算符。
⑦編譯器偽指令不允許作為宏的名字,此外紅的正文可以是一個表達式,並不僅英語變量名的替換。
⑧`define和parameter的區別:
區別點 |
`define |
parameter |
作用域 |
①`define從編譯器讀到這條指令開始到編譯結束都有效(除非遇到上面的提到的情況),可以應用於整個工程。 ②`define可以寫在代碼的任何位置。 |
①parameter作用於聲明的當前文件,如果要讓它作用與整個項目,可以將這些聲明單獨列在一個文件中,然后用`include進行包含。 ②parameter必須放在應用之前,也就是你要用到某個parameter參數了,你必須先parameter它。 |
傳遞功能 |
`define不能實現參數傳遞,但是不局限與定義變量,還可以定義 |
Parameter可以用作模塊例化時的參數傳遞,實現參數化調用,但是僅局限於定義變量,而不能定義表達式。 |
(2)條件編譯命令`if語句
①條件編譯指令包括`ifdef 、`else 、`endif,其中`ifdef 、`endif必不可少,`else可選,且條件編譯語句可以放在程序的任何地方調用。
②舉例:
`ifdef AAAA Parameter X1 = 1; `elsif BBBB Parameter X2 = 1; `elsif CCCC Parameter X3 = 1; `else Parameter X4 = 1; `endif
意思是:如果用`define 定義的ABCD,那么就會執行第一個模塊(Parameter X1 = 1);否則執行第二個模塊( Parameter X2 = 1)。
(3)文件包含`include語句
①當某個模塊需要調用某一個文件時,但是這個文件不在當前目錄下,那么就需要使用`include語句進行包含,這樣調用才不會出現錯誤。如 `include “../../xxxx.v”
②一個`include指令只能指定一個被包含的文件。如果要完成N個文件的包含,需要N個`include指令。
③可以將多個`include指令寫在同一行,在`include命令行只能出現空格和注釋。
④如果文件A同時包含了文件B和C,那么文件C可以直接利用B的內容,而不需要在對B文件進行包括,同理,B也可以直接利用C的內容。
(4)時間尺度`timescale語句
①`timescale用於定義延時的單位和延時的精度,如`timescale 1ns/100ps那么時間單位就是1ns,精度就是100ps。
②時間單位,表示了仿真時測量的單位,比如延時1,1ns;精度則表示仿真器只識別的范圍,比如精度是100ps,那么如果你1.3ns,編譯器是識別,但是如果寫1.32,那么由於精度達不到那么細,所以0.02被四舍五入掉。
③`timescale影響着全部模塊,知道遇到另外的`timescale。