1. 程式人生 > 實用技巧 >自己動手寫處理器之第二階段(3)——Verilog HDL行為語句

自己動手寫處理器之第二階段(3)——Verilog HDL行為語句

2.6 Verilog HDL行為語句

2.6.1 過程語句

Verilog定義的模組一般包括有過程語句,過程語句有兩種:initial、always。其中initial常用於模擬中的初始化,其中的語句只執行一次,而always中語句則是不斷重複執行的。此外,always過程語句是可綜合的,initial過程語句是不可綜合的。

1、always過程語句

always過程語句的格式如圖2-10所示。

always過程語句通常是帶有觸發條件的,觸發條件寫在敏感訊號表示式中,敏感訊號表示式又稱為事件表示式或敏感訊號列表,當該表示式中變數的值改變時,就會引發其中語句序列的執行。因此,敏感訊號表示式中應列出影響塊內取值的所有訊號。

(1)敏感訊號表示式的格式

如果有兩個或兩個以上的敏感訊號時,它們之間使用“or”連線,此處還是以32位加法器為例,2.4節是使用assign直接賦值的,其實也可以使用always過程語句實現,如下。只要被加數in1、加數in2中的任何一個改變,都會觸發always過程語句,在其中進行加法運算。這裡有兩個敏感訊號in1、in2,使用“or”連線。

  1. module add32(input wire[31:0] in1,
  2. input wire[31:0] in2,
  3. output reg[31:0] out);
  4. always @ (in1 or in2) //使用always過程語句實現加法
  5. begin
  6. out = in1 + in2;
  7. end
  8. endmodule


敏感訊號列表中的多個訊號也可以使用逗號隔開,上面的32位加法器可以修改為如下形式。

  1. module add32(input wire[31:0] in1,
  2. input wire[31:0] in2,
  3. output reg[31:0] out);
  4. always @ (in1, in2) //多個敏感訊號使用逗號分隔
  5. begin
  6. out = in1 + in2;
  7. end
  8. endmodule


敏感訊號列表也可以使用萬用字元“*”,表示在該過程語句中的所有輸入訊號變數,上面的32位加法器可以修改為如下形式。

  1. module add32(input wire[31:0] in1,
  2. input wire[31:0] in2,
  3. output reg[31:0] out);
  4. always @ (*) //使用萬用字元表示過程語句中的所有輸入訊號變數
  5. begin
  6. out = in1 + in2;
  7. end
  8. endmodule

(2)組合電路與時序電路

敏感訊號可以分為兩種型別:一種為電平敏感型,一種為邊沿敏感型。前一種一般對應組合電路,如上面給出的加法器的例子,後一種一般對應時序電路。對於時序電路,敏感訊號通常是時鐘訊號,Verilog HDL提供了posedge、negedge兩個關鍵字來描述時鐘訊號。posedge表示以時鐘訊號的上升沿作為觸發條件,negedge表示以時鐘訊號的下降沿作為觸發條件。還是以32位加法器為例,可以為其新增一個時鐘同步訊號,如下。

  1. module add32(input wire clk, //增加了一個時鐘輸入訊號
  2. input wire[31:0] in1,
  3. input wire[31:0] in2,
  4. output reg[31:0] out);
  5. always @ (posedge clk) //在時鐘訊號的上升沿會觸發always中的語句
  6. begin
  7. out = in1 + in2;
  8. end
  9. endmodule

在時鐘訊號的上升沿,才會進行加法運算,這一點與前面的加法器不同,也就是當被加數in1、加數in2變化時,並不會立即改變輸出out,而是要等待時鐘訊號的上升沿。

2、initial過程語句

initial過程語句的格式如圖2-11所示。

initial過程語句不帶觸發條件,並且其中的語句序列只執行一次。initial過程語句通常用於模擬模組中對激勵向量的描述,或用於給暫存器賦初值,它是面向模擬模擬的過程語句,通常不能被綜合。如下是initial過程語句的一個例子,用於給儲存器mem賦初值。

  1. initial
  2. begin
  3. for(addr = 0; addr < size; addr = addr+1) // for是一種迴圈語句,下文會介紹
  4. mem[addr] = 0;
  5. end


2.6.2 賦值語句

賦值語句有兩種:持續賦值語句、過程賦值語句。

1、持續賦值語句

assign為持續賦值語句,主要用於對wire型變數的賦值。如上文中加法器的例子。

2、過程賦值語句

在always、initial過程中的賦值語句稱為過程賦值語句,多用於對reg型變數進行賦值,分為非阻塞賦值和阻塞賦值兩種方式。

(1)非阻塞賦值(Non-Blocking)

賦值符號為“<=”,例如。

b <= a

非阻塞賦值在整個過程語句結束時才會完成賦值操作,即b的值並不是立刻改變的。

(2)阻塞賦值(Blocking)

賦值符號為“=”,例如。

b = a

阻塞賦值在該語句結束時就立即完成賦值操作,即b的值在這條語句結束後立刻改變。如果在一個塊語句中,有多條阻塞賦值語句,那麼在前面的賦值語句沒有完成之前,後面的語句就不能被執行,彷彿被阻塞了一樣,因此稱為阻塞賦值方式。

在always過程塊中,阻塞賦值可以理解為賦值語句是順序執行的,而非阻塞賦值可以理解為賦值語句是併發執行的。如圖2-12所示。在一個過程塊中,阻塞式賦值與非阻塞式賦值只能使用其中一種。

2.6.3 條件語句

條件語句有if-else、case兩種,應放在always塊內。分別介紹如下。

1、if-else語句

if-else語句的格式有如下三種。

  1. (1) if(表示式) 語句序列1; // 非完整性IF語句
  2. (2) if(表示式) 語句序列1; // 二重選擇的IF語句
  3. else 語句序列2;
  4. (3) if(表示式1) 語句序列1; // 多重選擇的IF語句
  5. else if(表示式2) 語句序列2;
  6. else if(表示式3) 語句序列3;
  7. ......
  8. else if(表示式n) 語句序列n;
  9. else 語句序列n+1;

上述格式中的“表示式”一般為邏輯表示式或關係表示式,也可能是1位的變數。系統對錶達式的值進行判斷,如果為0、X、Z,則按“假”處理,如果為1,則按“真”處理。語句序列可以是單句,也可以是多句,多句時需使用begin-end塊語句括起來。

還是以32位加法器為例,為其新增一個復位訊號rst,如果rst為高電平,那麼復位訊號有效,輸出out為0,反之,復位訊號無效,輸出out為兩個輸入訊號之和。

  1. module add32(input wire rst, // 增加了一個復位訊號
  2. input wire[31:0] in1,
  3. input wire[31:0] in2,
  4. output reg[31:0] out);
  5. always @ (*)
  6. begin
  7. if(rst == 1'b1)
  8. out <= 32'h0; // 如果復位訊號有效,那麼輸出out為0
  9. else
  10. out <= in1 + in2; // 反之,輸出out為兩個輸入訊號之和
  11. end
  12. endmodule

2、case語句

相對於if-else語句只有兩個分支而言,case語句是一種多分支語句,所以case語句多用於多條件譯碼電路,如譯碼器、資料選擇器、狀態機及微處理器的指令譯碼等。其格式如下。

  1. case(敏感表示式)
  2. 1: 語句序列1;
  3. 2: 語句序列2;
  4. ......
  5. 值n: 語句序列n;
  6. default: 語句序列n+1;
  7. endcase


當敏感表示式的值等於“值1”時,執行語句序列1;當等於“值2”時,執行語句序列2;依次類推。如果敏感表示式的值與上面列出的值都不符,那麼執行default後面的語句序列。如下程式碼是一個簡單的運算單元,可執行加法或減法運算,如果輸入變數type的值為1,那麼執行加法運算,如果type的值為0,那麼執行減法運算。

  1. module add_sub32(input wire type, // type決定運算型別
  2. input wire[31:0] in1,
  3. input wire[31:0] in2,
  4. output reg[31:0] out);
  5. always @ (*)
  6. begin
  7. case(type)
  8. 1'b1 : out <= in1 + in2; // type為1,執行加法運算
  9. 1'b0 : out <= in1 - in2; // type為0,執行減法運算
  10. default : out <= 32'h0;
  11. endcase
  12. end
  13. endmodule


case語句中,敏感表示式與值1-n之間的比較是一種全等比較,必須保證兩者的對應位全等。casez、casex語句是case語句的兩種擴充套件。

  • 在casez語句中,如果比較的雙方某些位的值為高阻Z,那麼對這些位的比較結果就不予考慮,只需考慮其它位的比較結果。
  • 在casex語句中,如果比較的雙方某些位的值為Z或X,那麼對這些位的比較結果就不予考慮,只需考慮其它位的比較結果。

此外,還有一種表示X或Z的方式,即用表示無關值的符號“?”來表示,例如。

  1. case(a)
  2. 2'b1x : out <= 1; //只有a等於2'b1x時,out才等於1
  3. casez(a)
  4. 2'b1x : out <= 1; //a等於2'b1x、2'b1z時,out等於1
  5. casex(a)
  6. 2'b1x : out <= 1; //a等於2'b10、2'b11、2'b1x、2'b1z時,out等於1
  7. case(a)
  8. 2'b1? : out <= 1; //a等於2'b10、2'b11、2'b1x、2'b1z時,out等於1


2.6.4 迴圈語句

Verilog HDL中存在四種類型的迴圈語句:for、forever、repeat、while,用來控制語句的執行次數,分別介紹如下。

1、for語句

for語句的格式如下。這與C語言是相似的。

  1. for(迴圈變數賦初值; 迴圈結束條件; 修改迴圈變數)
  2. 執行語句序列;


一個使用for語句實現7人表決器的例子如下。通過for迴圈統計贊成的人數,若超過4人(含4人)贊成則通過,其中vote[7:1]表示7個人的投票情況,vote[i]為1,表示第i個人投的是贊成票,反之是反對票,pass是輸出,超過4個人贊成,pass為1,反之為0。

  1. module vote7(vote, pass);
  2. input wire[7:1] vote;
  3. output reg pass;
  4. reg[2:0] sum;
  5. integer i;
  6. always @ (vote)
  7. begin
  8. sum = 0;
  9. for(i=1; i<7; i=i+1)
  10. if(vote[i])
  11. sum = sum+1; //如果vote[i]為1,那麼sum加1,注意此處使用阻塞賦值
  12. if(sum[2] == 1'b1) //如果sum大於等於4,那麼輸出pass為1
  13. pass = 1;
  14. else
  15. pass = 0;
  16. end
  17. endmodule

2、forever語句

forever語句的格式如下。

  1. forever begin
  2. 語句序列
  3. end


forever迴圈語句連續不斷的執行其中的語句序列,常用來產生週期性的波形。在2.8節編寫模擬用的Test Bench檔案時,會給出forever語句的例子。

3、repeat語句

repeat語句的格式如下。

  1. repeat(迴圈次數表示式) begin
  2. 語句序列
  3. end


4、while語句

while語句的格式如下。

  1. while(迴圈執行條件表示式) begin
  2. 語句序列
  3. end


while語句在執行時,首先判斷迴圈執行條件表示式是否為真,若為真,則執行其中的語句序列,然後再次判斷迴圈執行條件表示式是否為真,若為真,則再次執行其中的語句序列,如此反覆,直到迴圈執行條件表示式不為真。

2.6.5 編譯指示語句

Verilog HDL和C語言一樣提供了編譯指示功能,允許在程式中使用編譯指示(Compiler Directives)語句,在編譯時,通常先對這些指示語句進行預處理,然後再將預處理的 結果和源程式一起進行編譯。

編譯指示語句以“`”開始,以區別其它語句。常用的編譯指示語句有:`define、`include、`ifdef、`else、`endif,分別介紹如下。

1、巨集替換`define

`define可以用一個簡單的名字或有意義的標識(也稱為巨集名)代替一個複雜的名字或變數,其格式如下。

`define 巨集名 變數或名字


例如:一般在時序電路中會有一個復位訊號,當該復位訊號為高電平時表示復位訊號有效,當該復位訊號為低電平時,表示復位訊號無效。分別執行不同的程式碼,如下。

  1. always @ (clk)
  2. begin
  3. if(rst == 1'b1)
  4. //復位有效
  5. else
  6. //復位無效
  7. end


一種更為友好的書寫方式,是使用巨集定義,如下。

  1. // 定義巨集RstEnable表示復位訊號有效,這個名字對讀者而言更有意義
  2. `define RstEnable 1'b1
  3. ......
  4. always @ (clk)
  5. begin
  6. if(rst == `RstEnable) // 在編譯的時候會自動將`RstEnable替換成1'b1
  7. //復位有效
  8. else
  9. //復位無效
  10. end

2、`include語句

`include是檔案包含語句,它可將一個檔案全部包含到另一個檔案中,使用格式如下。

`include "檔名"


在OpenMIPS處理器的實現過程中,我們定義了很多巨集,這些巨集都集中在檔案defines.v中,如果某一程式需要使用其中的巨集定義,就可以在程式檔案的開始使用`include語句將defines.v檔案包含進來即可,如下。

`include "defines.v"

3、條件編譯語句`ifdef、`else、`endif

條件編譯語句`ifdef、`else、`endif可以指定僅對程式中的部分內容進行編譯,有兩種使用形式。

第一種使用形式如下。當指定的巨集在程式中已定義,那麼其中的語句序列參與原始檔的編譯,否則,其中的語句序列不參與原始檔的編譯。

  1. `ifdef 巨集名
  2. 語句序列
  3. `endif


第二種使用形式如下。當指定的巨集在程式中已定義,那麼其中的語句序列1參與原始檔的編譯,否則,其中的語句序列2參與原始檔的編譯。

  1. `ifdef 巨集名
  2. 語句序列1
  3. `else
  4. 語句序列2
  5. `endif

2.6.6 行為語句的可綜合性

前面幾小節介紹了Verilog HDL中的多種行為語句,包括過程語句、賦值語句、條件語句、迴圈語句、編譯指示語句,所有的語句都能在模擬中使用,但是有些語句是不可綜合的。也就是說綜合器無法將這些語句轉變為對應的硬體電路。Verilog HDL行為語句可綜合性的情況如表2-4所示。