關鍵詞: defparam,參數,例化,ram

當一個模塊被另一個模塊引用例化時,高層模塊可以對低層模塊的參數值進行改寫。這樣就允許在編譯時將不同的參數傳遞給多個相同名字的模塊,而不用單獨為隻有參數不同的多個模塊再新建文件。

參數覆蓋有 2 種方式:1)使用關鍵字 defparam,2)帶參數值模塊例化。

defparam 語句

可以用關鍵字 defparam 通過模塊層次調用的方法,來改寫低層次模塊的參數值。

例如對一個單口地址線和數據線都是 4bit 寬度的 ram 模塊的 MASK 參數進行改寫:

實例

//instantiation
defparam     u_ram_4x4.MASK = 7 ;
ram_4x4    u_ram_4x4
    (
        .CLK    (clk),
        .A      (a[4-1:0]),
        .D      (d),
        .EN     (en),
        .WR     (wr),    //1 for write and 0 for read
        .Q      (q)    );

ram_4x4 的模型如下:

實例

module  ram_4x4
    (
     input               CLK ,
     input [4-1:0]       A ,
     input [4-1:0]       D ,
     input               EN ,
     input               WR ,    //1 for write and 0 for read
     output reg [4-1:0]  Q    );
 
    parameter        MASK = 3 ;
 
    reg [4-1:0]     mem [0:(1<<4)-1] ;
    always @(posedge CLK) begin
        if (EN && WR) begin
            mem[A]  <= D & MASK;
        end
        else if (EN && !WR) begin
            Q       <= mem[A] & MASK;
        end
    end
 
endmodule

對此進行一個簡單的仿真,testbench 編寫如下:

實例

`timescale 1ns/1ns
 
module test ;
    parameter    AW = 4 ;
    parameter    DW = 4 ;
 
    reg                  clk ;
    reg [AW:0]           a ;
    reg [DW-1:0]         d ;
    reg                  en ;
    reg                  wr ;
    wire [DW-1:0]        q ;
 
    //clock generating
    always begin
        #15 ;     clk = 0 ;
        #15 ;     clk = 1 ;
    end
 
    initial begin
        a         = 10 ;
        d         = 2 ;
        en        = 'b0 ;
        wr        = 'b0 ;
        repeat(10) begin
            @(negedge clk) ;
            en     = 1'b1;
            a      = a + 1 ;
            wr     = 1'b1 ;  //write command
            d      = d + 1 ;
        end
        a         = 10 ;
        repeat(10) begin
            @(negedge clk) ;
            a      = a + 1 ;
            wr     = 1'b0 ;  //read command
        end
    end // initial begin
 
    //instantiation
    defparam     u_ram_4x4.MASK = 7 ;
    ram_4x4    u_ram_4x4
    (
        .CLK    (clk),
        .A      (a[AW-1:0]),
        .D      (d),
        .EN     (en),
        .WR     (wr),    //1 for write and 0 for read
        .Q      (q)
     );
 
    //stop simulation
    initial begin
        forever begin
            #100;
            if ($time >= 1000)  $finish ;
        end
    end
 
endmodule // test

仿真結果如下:

圖中黃色部分,當地址第一次為 c 時寫入數據 4, 當第二次地址為 c 時讀出數據為 4;可知此時 ram 行為正確,且 MASK 不為 3。 因為 ram 的 Q 端 bit2 沒有被屏蔽。

當第一次地址為 1 時寫入數據為 9,第二次地址為 1 時讀出的數據卻是 1,因為此時 MASK 為 7,ram 的 Q 端信號 bit3 被屏蔽。由此可知,MASK 參數被正確改寫。

帶參數模塊例化

第二種方法就是例化模塊時,將新的參數值寫入模塊例化語句,以此來改寫原有 module 的參數值。

例如對一個地址和數據位寬都可變的 ram 模塊進行帶參數的模塊例化:

實例

ram #(.AW(4), .DW(4))
    u_ram
    (
        .CLK    (clk),
        .A      (a[AW-1:0]),
        .D      (d),
        .EN     (en),
        .WR     (wr),    //1 for write and 0 for read
        .Q      (q)
     );

ram 模型如下:

實例

module  ram
    #(  parameter       AW = 2 ,
        parameter       DW = 3 )
    (
        input                   CLK ,
        input [AW-1:0]          A ,
        input [DW-1:0]          D ,
        input                   EN ,
        input                   WR ,    //1 for write and 0 for read
        output reg [DW-1:0]     Q
     );
 
    reg [DW-1:0]         mem [0:(1<<AW)-1] ;
    always @(posedge CLK) begin
        if (EN && WR) begin
            mem[A]  <= D ;
        end
        else if (EN && !WR) begin
            Q       <= mem[A] ;
        end
    end
 
endmodule

仿真時,隻需在上一例的 testbench 中,將本次例化的模塊 u_ram 覆蓋掉 u_ram_4x4, 或重新添加之即可。

仿真結果如下。由圖可知,ram 模塊的參數 AW 與 DW 均被改寫為 4, 且 ram 行為正確。

區別與建議

(1) 和模塊端口實例化一樣,帶參數例化時,也可以不指定原有參數名字,按順序進行參數例化,例如 u_ram 的例化可以描述為:

ram #(4, 4)   u_ram (......) ;

(2) 當然,利用 defparam 也可以改寫模塊在端口聲明時聲明的參數,利用帶參數例化也可以改寫模塊實體中聲明的參數。例如 u_ram 和 u_ram_4x4 的例化分別可以描述為:

實例

defparam     u_ram.AW = 4 ;
defparam     u_ram.DW = 4 ;
ram   u_ram(......);
ram_4x4  #(.MASK(7))    u_ram_4x4(......);

(3) 那能不能混合使用這兩種模塊參數改寫的方式呢?當然能!前提是所有參數都是模塊在端口聲明時聲明的參數或參數都是模塊實體中聲明的參數,例如 u_ram 的聲明還可以表示為(模塊實體中參數可自行實驗驗證):

實例

defparam     u_ram.AW = 4 ;
ram #(.DW(4)) u_ram (......);  //也隻有我這麼無聊才會實驗這種寫法

(4) 那如果一個模塊中既有在模塊在端口聲明時聲明的參數,又有在模塊實體中聲明的參數,那這兩種參數還能同時改寫麼?例如在 ram 模塊中加入 MASK 參數,模型如下:

實例

module  ram
    #(  parameter       AW = 2 ,
        parameter       DW = 3 )
    (
        input                   CLK ,
        input [AW-1:0]          A ,
        input [DW-1:0]          D ,
        input                   EN ,
        input                   WR ,    //1 for write and 0 for read
        output reg [DW-1:0]     Q    );
 
    parameter            MASK = 3 ;
 
    reg [DW-1:0]         mem [0:(1<<AW)-1] ;
    always @(posedge CLK) begin
        if (EN && WR) begin
            mem[A]  <= D ;
        end
        else if (EN && !WR) begin
            Q       <= mem[A] ;
        end
    end
 
endmodule

此時再用 defparam 改寫參數 MASK 值時,編譯報 Error:

實例

//都采用defparam時會報Error
defparam     u_ram.AW = 4 ;
defparam     u_ram.DW = 4 ;
defparam     u_ram.MASK = 7 ;
ram   u_ram  (......);
 
//模塊實體中parameter用defparam改寫也會報Error
defparam     u_ram.MASK = 7 ;
ram #(.AW(4), .DW(4))   u_ram (......);

重點來了!!!如果你用帶參數模塊例化的方法去改寫參數 MASK 的值,編譯不會報錯,MASK 也將被成功改寫!

ram #(.AW(4), .DW(4), .MASK(7)) u_ram (......);

可能的解釋為,在編譯器看來,如果有模塊在端口聲明時的參數,那麼實體中的參數將視為 localparam 類型,使用 defparam 將不能改寫模塊實體中聲明的參數。

也可能和編譯器有關係,大家也可以在其他編譯器上實驗。

(5)建議,對已有模塊進行例化並將其相關參數進行改寫時,不要采用 defparam 的方法。除了上述缺點外,defparam 一般也不可綜合。

(6)而且建議,模塊在編寫時,如果預知將被例化且有需要改寫的參數,都將這些參數寫入到模塊端口聲明之前的地方(用關鍵字井號 # 表示)。這樣的代碼格式不僅有很好的可讀性,而且方便調試。

源碼下載

Download

階段總結

其實,介紹到這裏,大家完全可以用前麵學習到的 Verilog 語言知識,去搭建硬件電路的小茅草屋。對,是小茅草屋。因為硬件語言對應實際硬件電路的這種特殊性,在用 Verilog 建立各種模型時必須考慮實際生成的電路是什麼樣子的,是否符合實際要求。有時候 rtl 仿真能通過,但是最後生成的實際電路可能會工作異常。

所以,要為你的小茅草屋添磚蓋瓦,還需要再學習下進階部分。當然,進階部分也隻能讓你的小茅草屋變成硬朗的磚瓦房,能抵擋風雪交加,可能遇到地震還是會垮塌。

如果你想鞏固下你的磚瓦房,去建一套別墅,那你需要再學習下 Verilog 高級篇知識,例如 PLI(編程語言接口)、UDP(用戶自定義原語),時序約束和時序分析等,還需要多參與項目工程積累經驗,特別注意一些設計技巧,例如低功耗設計、異步設計等。當然學會用 SystemVerilog 去全麵驗證,又會讓你的建築增加一層防護盾。

但是如果你想把數字電路、Verilog 所有的知識學完,去築一套防炮彈的總統府,那真的是愛莫能助。因為,學海無涯,回頭沒岸哪。

限於篇幅,這裏隻介紹下進階篇。有機會,高級篇,技巧篇,也一並補上。