當一個模塊被另一個模塊引用例化時,高層模塊可以對低層模塊的參數值進行改寫。這樣就允許在編譯時將不同的參數傳遞給多個相同名字的模塊,而不用單獨為只有參數不同的多個模塊再新建文件。
參數覆蓋有 2 種方式:1)使用關鍵字 defparam,2)帶參數值模塊例化。
defparam 語句
可以用關鍵字 defparam 通過模塊層次調用的方法,來改寫低層次模塊的參數值。
例如對一個單口地址線和數據線都是 4bit 寬度的 ram 模塊的 MASK 參數進行改寫:
實例
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 的模型如下:
實例
(
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 編寫如下:
實例
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 模塊進行帶參數的模塊例化:
實例
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 模型如下:
實例
#( 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.DW = 4 ;
ram u_ram(......);
ram_4x4 #(.MASK(7)) u_ram_4x4(......);
(3) 那能不能混合使用這兩種模塊參數改寫的方式呢?當然能!前提是所有參數都是模塊在端口聲明時聲明的參數或參數都是模塊實體中聲明的參數,例如 u_ram 的聲明還可以表示為(模塊實體中參數可自行實驗驗證):
實例
ram #(.DW(4)) u_ram (......); //也只有我這么無聊才會實驗這種寫法
(4) 那如果一個模塊中既有在模塊在端口聲明時聲明的參數,又有在模塊實體中聲明的參數,那這兩種參數還能同時改寫么?例如在 ram 模塊中加入 MASK 參數,模型如下:
實例
#( 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 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)而且建議,模塊在編寫時,如果預知將被例化且有需要改寫的參數,都將這些參數寫入到模塊端口聲明之前的地方(用關鍵字井號 # 表示)。這樣的代碼格式不僅有很好的可讀性,而且方便調試。


