什麼是參數化模組化
和寫軟體程式一樣,我們也希望 Verilog 的子產品也可以重利用。要使子產品可以重複利用,關鍵就在于避免寫死(hard literal),使子產品參數化。
參數化模組化的好處是可以使代碼清晰,便于後續維護和修改。
Verilog 的參數化模組化是有一定限制的,它的參數值是編譯時計算的,不會引入任何實際的硬體電路。參數必須在編譯時确定值。也就是說隻能達到動态編譯,固态運作,而非軟體的動态編譯,動态運作。
這主要是因為它是描述(Description)硬體的語言,而非軟體設計(Design)語言。
比如一個計數器,我們可以設定一個參數來指定它的計數周期(動态編譯),但是這個計數周期在綜合之後就是固定值了(固态運作),不能在運作的時候動态地改為另外一個值(除非電路綜合時同時産生了多個計數器,這種情況不算真正意義上的動态運作,而且也達不到真正意義上的動态運作,因為不可能把所有可能的計數器都實作了備用,耗費資源而且沒有實際意義)。
參數化模組化的主要目的是:
提高子產品的通用性,隻需要修改參數,不用修改其他代碼就可以适用于不同的環境中。
總結一下我找到的資料,具體的參數化模組化方法一共就 3 種:
- `define 宏定義
- parameter 子產品參數化
- `ifdef 等 條件編譯
Define Macro Substitution
`define 是編譯器指令,功能是全局宏定義的文本代替。它類似于 C 語言中的#define,用法如下:
// define
`define WORD_REG reg [31:0]
// using
`WORD_REG reg32;
Problem
`define 定義的宏的作用域是全局的,這種機制會導緻兩個問題
- 可能會有在不同檔案中發生重定義的問題
- 編譯順序有要求 file-order dependent,必須確定使用前,宏定義有效,是以每個使用到宏定義的源檔案必須包含這個頭檔案,這會導緻多重包含的問題。
Solution
- 對于第一個問題,盡可能把所有的宏定義放在同一個頭檔案中,比如 “global_define.vh”
- 對于第二個問題,和 C++ 類似,頭檔案應該使用頭檔案保護符。
// global_define.vh head file
`ifndef GLOBAL_DEFINE_VH
`define MAX = 8
`define SIZE = 4
// ...
`enif
Guideline
- 隻有那些要求有全局作用域、并且在其他地方不會被修改的常量才用 define 來定義
- 對于那些隻限于子產品内的常量,不要使用 define
- 盡可能将所有的 define 都放在同一個檔案中,然後在編譯時先讀取這個檔案
- 不要使用 `undef
Parameter
應該避免寫死設計 hard literal,使用參數 parameter 來代替。舉個例子
// use parameter
parameter SIZE = 8,
MAX = 10;
reg [SIZE - 1 : 0] din_r;
// DO NOT use hard literal
reg [7 : 0] din_r;
Localparam
Verilog-2001 中添加了一個新的關鍵字 localparam,用來定義子產品内部的、不能被其他子產品修改的局部常量,概念類似于 C++ 中 class 的 protect 成員。
雖然 localparam 不能被外部子產品修改,但是它可以用 parameter 來初始化。
parameter N = 8;
localparam N1 = N - 1;
Parameter Redefinition
在 Verilog-2001 出現之前,Verilog-1995 中隻有兩種方法實作參數重定義:
- 使用 # 符号,順序清單重定義
- 使用 defparam
逐個讨論
-
Uisng #
Syntax
舉個栗子,子產品 myreg
module myreg (q, d, clk, rst);
parameter Trst = 1,
Tclk = 1,
SIZE = 4;
// ...
endmodule
在上一層的子產品中傳遞參數例化這個子產品
module bad_warpper (q, d, clk, rst)
// legal parameter passing
myreg #(1, 1, 8) r1(.q(q), .d(d), .clk(clk), .rst(rst) );
// illegal parameter passing
// myreg #(,,8) r1(.q(q), .d(d), .clk(clk), .rst(rst) );
endmodule
Pro
雖然每次例化都要說明所有的參數值,但是比第二種方法好
Con
每次例化都要說明所有的參數值。
2. Using defparam
Syntax
defparam path.name = value;
比如在上面的例子中
defparam r1.SIZE = 8;
Pro
可以放在任何檔案的任何地方,不用再重複沒有修改的參數值
Con
因為 defparam 有這麼 “強” 的功能,反而會導緻一系列的問題
Hierarchical deparam.
- 比如頂層子產品使用 defparam 修改子子產品的參數,子子產品中又使用 defparam 修改頂層子產品要傳遞進來的參數,形成一個環,這樣子可能導緻綜合時不提示錯誤,但是結果與預期不符。
-
Multiple defparams
在 單個檔案 / 多個檔案 中重複定義 defparam,會有微妙的問題,Verilog-1995 中沒有定義這種現象,實際結果依賴于使用的綜合工具。
因為 defparam 有這麼多缺點,是以在 2001 年之前,Synopsys 是不支援 defparam 的,網上很多轉載的部落格都說 defparam 是不可綜合的,實際上在後來,Synopsys 在壓力之下添加了對其的支援。而我用 XST 也證明是支援 defparam 可綜合。
綜上原因,Verilog Standards Group (VSG) 倡議大家抵制使用 defparam,大神 Clifford E. Cummings 在論文中建議綜合工具如果使用者堅持使用 defparam 語句,必須添加以一個參數 +Iamstupid…
“The Verilog compiler found a defparam statement in the source code at (file_name/line#). To use defparam statements in the Verilog source code, you must include the switch +Iamstupid on the command line which will degrade compiler performance and introduce potential problems but is bug-compatible with Verilog-1995 implementations. Defparam statements can be replaced with named parameter redefinition as define by the IEEE Verilog-2001 standard.”
總結一下,可以發現 Verilog-1995 中的兩種方法都不怎麼好,顯然 VSG 也發現了這個問題,是以在 Verilog-2001 中,出現了第三種方法,并且牆裂推薦使用這種新方法。
-
Using named parameter redefinition
Syntax
類似于子產品例化時端口連接配接的方式,比如上例中隻想改變 SIZE 的值.
myreg #(.SIZE(8)) r1(.q(q), .d(d), .clk(clk), .rst(rst) );
Pro
結合了前兩種方法的有點,既顯示說明了哪個參數值改變了,也将參數傳遞放在了執行個體化的語句中。這種方法是最幹淨的 (cleanest) 方法,不依賴于任何綜合工具。
Con
貌似沒有~
Guideline
-
不要使用 defparam,應該使用 named parameter redefinition。
Example
-
clock cycle definition
因為時鐘是一個設計中最基本的常量,它不會在随着子產品變化,是以應該用 `define 來定義,并且将它放在頂層的頭檔案中。
-
FSM
在一個設計中可能有不止一個 FSM,而通常 FSM 有一些共同的狀态名字,比如 IDLE、READY、READ、WRITE、ERROR、DONE 等,是以應該用 localparam 來定義這些常量。
Conditional Compilation
Verilog 的條件編譯和 C 也十分類似。前面介紹 define 時,已經用到了條件編譯中的 `ifdef。條件編譯一共有 5 個關鍵字,分别是:
`ifdef `else `elsif `endif `ifndef
條件編譯一般在以下情況中使用
- 選擇一個子產品的不同部分
- 選擇不同的時序和結構
- 選擇不同的仿真激勵
Syntax
// example1
`ifdef text_macro
// do something
`endif
// example2
`ifdef text_macro
// do something
`else
// do something
`endif
// example3
`ifdef text_macro
// do something
`elsif
// do something
`else
// do something
`endif
// example4
`ifndef text_macro
// do something
`else
// do something
`endif
條件編譯是一個非常好的技術,它可以幫助我們更好的管理代碼。
舉個栗子,比如我們寫了一個程式,在 debug 階段,在程式中添加了很多顯示中間變量的語句,到最後 release 時,當然要去掉這些語句。最差的方法當然是删掉這些代碼,但是如果以後我們還想 debug 時,又得手動寫,而且時間長了,我們自己都記不清該加哪些語句了。稍微好點的方法是把它們注釋起來,但是同樣,時間長了,哪些該注釋,那些不該注釋又混淆了。最好的方法就是用條件編譯。我們可以定義一個宏 DEBUG
`define DEBUG
// conditional compilation
`ifdef DEBUG
// debug
`else
// release
`endif
這樣,我們隻需要選擇是否注釋第一行的宏定義就可快速在 debug 和 release 之間切換。
再比如在 Verilog 的子產品中,針對不同的應用環境,我們要實作不同的子產品,這時候也可以使用條件編譯選擇具體綜合哪段代碼。
Summary
總結一下,就是一下幾點
Guideline
- 隻有那些要求有全局作用域、并且在其他地方不會被修改的常量才用 define 來定義
- 對于那些隻限于子產品内的常量,不要使用 define
- 盡可能将所有的 define 都放在同一個檔案中,然後在編譯時先讀取這個檔案
- 不要使用 `undef
- 不要使用 defparam,應該使用 named parameter redefinition。
- 需要時使用條件編譯
Reference
- IEEE Std 1364-1995
- IEEE Std 1364-2001
- New Verilog-2001 Techniques for Creating Parameterized Models
- (原創) 如何使用參數式模組? (SOC) (Verilog) (C/C++) (template)
- 艾米電子 - 參數與常量,Verilog
- Verilog代碼可移植性設計