天天看點

Verilog 中的參數化模組化

什麼是參數化模組化

和寫軟體程式一樣,我們也希望 Verilog 的子產品也可以重利用。要使子產品可以重複利用,關鍵就在于避免寫死(hard literal),使子產品參數化。

參數化模組化的好處是可以使代碼清晰,便于後續維護和修改。

Verilog 的參數化模組化是有一定限制的,它的參數值是編譯時計算的,不會引入任何實際的硬體電路。參數必須在編譯時确定值。也就是說隻能達到動态編譯,固态運作,而非軟體的動态編譯,動态運作。

這主要是因為它是描述(Description)硬體的語言,而非軟體設計(Design)語言。

比如一個計數器,我們可以設定一個參數來指定它的計數周期(動态編譯),但是這個計數周期在綜合之後就是固定值了(固态運作),不能在運作的時候動态地改為另外一個值(除非電路綜合時同時産生了多個計數器,這種情況不算真正意義上的動态運作,而且也達不到真正意義上的動态運作,因為不可能把所有可能的計數器都實作了備用,耗費資源而且沒有實際意義)。

參數化模組化的主要目的是:

提高子產品的通用性,隻需要修改參數,不用修改其他代碼就可以适用于不同的環境中。

總結一下我找到的資料,具體的參數化模組化方法一共就 3 種:

  1. `define 宏定義
  2. parameter 子產品參數化
  3. `ifdef 等 條件編譯

Define Macro Substitution

`define 是編譯器指令,功能是全局宏定義的文本代替。它類似于 C 語言中的#define,用法如下:

// define
`define     WORD_REG    reg     [31:0]
// using
`WORD_REG   reg32;
           

Problem

`define 定義的宏的作用域是全局的,這種機制會導緻兩個問題

  1. 可能會有在不同檔案中發生重定義的問題
  2. 編譯順序有要求 file-order dependent,必須確定使用前,宏定義有效,是以每個使用到宏定義的源檔案必須包含這個頭檔案,這會導緻多重包含的問題。

Solution

  1. 對于第一個問題,盡可能把所有的宏定義放在同一個頭檔案中,比如 “global_define.vh”
  2. 對于第二個問題,和 C++ 類似,頭檔案應該使用頭檔案保護符。
// global_define.vh head file
`ifndef GLOBAL_DEFINE_VH
    `define     MAX = 8
    `define     SIZE = 4
    // ...
`enif
           

Guideline

  1. 隻有那些要求有全局作用域、并且在其他地方不會被修改的常量才用 define 來定義
  2. 對于那些隻限于子產品内的常量,不要使用 define
  3. 盡可能将所有的 define 都放在同一個檔案中,然後在編譯時先讀取這個檔案
  4. 不要使用 `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 中隻有兩種方法實作參數重定義:

  1. 使用 # 符号,順序清單重定義
  2. 使用 defparam

逐個讨論

  1. 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 中,出現了第三種方法,并且牆裂推薦使用這種新方法。

  1. 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

  1. 隻有那些要求有全局作用域、并且在其他地方不會被修改的常量才用 define 來定義
  2. 對于那些隻限于子產品内的常量,不要使用 define
  3. 盡可能将所有的 define 都放在同一個檔案中,然後在編譯時先讀取這個檔案
  4. 不要使用 `undef
  5. 不要使用 defparam,應該使用 named parameter redefinition。
  6. 需要時使用條件編譯

Reference

  1. IEEE Std 1364-1995
  2. IEEE Std 1364-2001
  3. New Verilog-2001 Techniques for Creating Parameterized Models
  4. (原創) 如何使用參數式模組? (SOC) (Verilog) (C/C++) (template)
  5. 艾米電子 - 參數與常量,Verilog
  6. Verilog代碼可移植性設計