问题根源:位宽错误!
工程目标:设计5个灯,分别以2s,3s,4s,5s,6s的频率闪烁(针对计数器、参数化设计练习)
正确的设计代码:(注意里面的参数化设计)
子模块:
//设计5个灯,分别以2s,3s,4s,5s,6s的频率闪烁
//先设计子模块,底层模块,一个灯以2s的频率闪烁
module led_flash_1led (clk,rst_n,led);
input clk;
input rst_n;
output reg led;
reg [27:0] cnt;
parameter CNTE=26'd50000000;
//计数1s(5000_0000)
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cnt<=0;
else if(cnt==CNTE-1'd1)
cnt<=0;
else
cnt<=cnt+1;
end
//LED灯输出
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
led<=0;
else if(cnt==CNTE-1'd1)
led<=~led;
end
endmodule
顶层模块:
//设计5个灯,分别以2s,3s,4s,5s,6s的频率闪烁
//这里是顶层模块
module led_flash_5led (clk,rst_n,led5);
input clk;
input rst_n;
output [4:0] led5;
led_flash_1led u0(.clk(clk),.rst_n(rst_n),.led(led5[0]));
led_flash_1led #(.CNTE(27'd7500_0000)) u1(.clk(clk),.rst_n(rst_n),.led(led5[1]));
led_flash_1led #(.CNTE(27'd10000_0000)) u2(.clk(clk),.rst_n(rst_n),.led(led5[2]));
led_flash_1led #(.CNTE(27'd1_2500_0000)) u3(.clk(clk),.rst_n(rst_n),.led(led5[3]));
led_flash_1led #(.CNTE(28'd1_5000_0000)) u4(.clk(clk),.rst_n(rst_n),.led(led5[4]));
endmodule
正确的测试代码:(注意里面的参数化设计)
`timescale 1ns/1ns
module led_flash_5led_tb();
reg clk;
reg rst_n;
wire [4:0] led5;
led_flash_5led led_inst (.clk(clk),.rst_n(rst_n),.led5(led5));
defparam led_inst.u0.CNTE=50;
defparam led_inst.u1.CNTE=75;
defparam led_inst.u2.CNTE=100;
defparam led_inst.u3.CNTE=125;
defparam led_inst.u4.CNTE=150;
initial clk=1;
always #10 clk=~clk;
initial begin
rst_n=0;
#2;
rst_n=1;
#3000;
$stop;
end
endmodule
引脚分配:
仿真波形:
错误原因记录:
在错误的一版子模块设计中,考虑到计时1s后灯翻转,所以计时变量cnt(即需要计时间到1s/20ns=26'd50000000)的位宽设为26位,reg [25:0] cnt;然而,这没有考虑到顶层模块通过参数化设计对子模块进行例化时,里面的cnt已经超过了1s对应的计时次数,所以位宽也要改变!!!最大的是计时3s后灯翻转对应的次数(3s/20ns=28'd1_5000_0000),所以cnt的位宽设为28位,reg [27:0] cnt。否则就会出现,后面的4个灯无法亮的情况,也就是上板后只有第1个灯闪烁,而后4个灯没动静的错误情况。
那么问题来了,为什么在仿真中没有检查出来呢?这是因为在仿真中,为了节省仿真时间,提高效率,仿真代码中对计时进行了参数化设计。所以仿真中没有看出问题。
这个问题还是值得考虑的!
注意:位宽!!!以后设计中对于每一个数字都用标准的<位宽>’<进制><数字>形式来写,当对数字进行修改时,也要关注位宽的变化。当进行模块例化时,也要考虑模块中参数和变量的位宽大小。比如:5个led分别以1s,2s,3s,4s,5s的频率闪烁,用1s的模块作为子模块,顶层模块对子模块例化的时候。
附加如何进行参数化设计:(下面的模块是另一个工程里的,只是为了说明参数化设计这个问题而已)
方法一:使用defparam语句重新定义子模块参数内容
子模块counter中
parameter CNT=25’d2499_9999;
上层模块LED_flicker
counter counter0(.clk(clk),.rst_n(rst_n),.led(led[0]));
counter counter1(.clk(clk),.rst_n(rst_n),.led(led[1]));
defparam couter0.CNT=22’d249_9999;
defparam counter1.CNT=18’d24_9999;
虽然模块中设定了有默认值,但是使用defparam修改的值比原始设计文件中的值拥有更高的编译优先级。当使用defparam修改了原始文件中的参数值后,原始文件中的默认参数值即被忽略。
方法二:使用参数例化方式修改参数值
子模块counter中
parameter CNT=25’d2499_9999;
上层模块LED_flicker_inst
counter #(.CNT(22’d249_9999)) counter0(.clk(clk),.rst_n(rst_n),.led(led[0]));
counter #(.CNT(18’d24_9999)) counter1(.clk(clk),.rst_n(rst_n),.led(led[1]));//参数例化+端口例化
能够避开对于同一个参数多次使用defparam来重定义值而引发的冲突。
测试代码中:
仿真中,为了节约仿真时间,我们希望仿真中再次修改参数值。
总结,为了同时保证实际逻辑设计和仿真验证时对参数的修改能够共存,互不影响,在实体模块设计中,使用参数例化的方式修改被例化模块中的参数(设计中用方法二)。在仿真验证用的testbench中,使用defparam语句来修改对应的参数(设计中用方法二时,测试中用方法一)。