01 module counter(Clk,Rst_n,led);
02
03 input Clk; //系统时钟
04 input Rst_n; //全局复位,低电平复位
05
06 output reg led; //led输出
07
08 reg [24:0]cnt; //定义计数器寄存器
09
10 parameter CNT_MAX =
25'd24_999_999;
11
12 //计数器计数进程
13 always@(posedge Clk or negedge Rst_n)
14 if(Rst_n ==
1'b0)
15 cnt <=
25'd0;
16 else if(cnt == CNT_MAX)
17 cnt <=
25'd0;
18 else
19 cnt <= cnt +
1'b1;
20
21 //led输出控制进程
22 always@(posedge Clk or negedge Rst_n)
23 if(Rst_n ==
1'b0)
24 led <=
1'b1;
25 else if(cnt == CNT_MAX)
26 led <= ~led;
27 else
28 led <= led;
29
行声明了一个参数化常量"CNT_MAX",在第16行和第25行进行条件判断时,直接判断cnt的值是否与该参数值相等,而不再是直接写具体数字的方式。到这里大家就可以看到,通过这样一种方式,首先就能避免一个模块中多次使用该常量而对修改设计带来的隐患。因为当我们需要修改该常量的值以适应不同的应用要求时,直接修改这个parameter的值即可,从而可以避免因为该参数使用的地方过多而在修改时容易出现的遗漏的问题。
另外,在我们的系统中,假如也需要三个这样的模块来对应驱动三个LED,大家可能首先想到的就是将该代码复制得到三个文件,并将其中的parameter值修改为对应需要的值即可。此种方式虽然能够避免一个常量在模块中被多次使用,修改时容易发生的遗漏问题,但仍然还是需要存在三个独立的子模块,并未大大减轻设计工作量。其实,修改parameter有更加快捷的方式,这种方式不需要我们针对每一个需求分别复制设计代码并修改对应参数,只需要在上层模块例化该模块时,直接在例化的过程中修改该值即可。具体实现方法,这里以实例代码的形式进行讲解。
在上层模块中,例化子模块时直接修改其参数有两种语法结构,这里我们首先看第一种:
01 module LED_flicker(
02 Clk,
03 Rst_n,
04 LED
05 );
06
07 input Clk;
08 input Rst_n;
09 output [1:0]LED;
10
11 counter counter0(
12 .Clk(Clk),
13 .Rst_n(Rst_n),
14 .led(LED[0])
15 );
16
17 counter counter1(
18 .Clk(Clk),
19 .Rst_n(Rst_n),
20 .led(LED[1])
21 );
22
23 defparam counter0.CNT_MAX =
24_999_99;
24 defparam counter1.CNT_MAX =
24_999_9;
25
26 endmodule
|
行和24行分别使用"defparam"来重新定义CNT_MAX的值。这里以第23行修改counter0中的CNT_MAX的值来具体讲解此种语法的结构。
首先,使用关键字"defparam"来声明这里需要对某个参数的值进行重新定义,空格之后紧跟着的counter0为counter模块被例化的名字,counter0后紧随一个".","."后紧随着的就是counter模块中需要修改的参数的名字,然后使用"="将新的值赋给改参数即可。这里使用模块名+"."+参数名的方式,与C语言结构体中使用结构体成员的形式很类似,大家可以对比学习。通过这种方式,大家可以在不修改原有设计文件的情况下,例化后,在上层模块直接修改参数。虽然模块中设定了有默认值,但是使用defparam修改的值比原始设计文件中的值拥有更高的编译优先级。当使用defparam修改了原始文件中的参数值后,原始文件中的默认参数值即被忽略。
的计数值进行计数,则需要花费太多的仿真时间,严重影响设计效率,因此也可以在仿真时直接在testbench中使用defparam来对设计文件中的参数进行修改,从而降低仿真复杂度。上述LED_flicker系统的testbench代码如下所示:
01 `timescale
1ns/1ps
02 `define clk_period 20
03
04 module LED_flicker_tb;
05
06 //source define
07 reg Clk;
08 reg Rst_n;
09
10 //probe define
11 wire [1:0]LED;
12
13 //instant user module
14 LED_flicker LED_flicker0(
15 .Clk(Clk),
16 .Rst_n(Rst_n),
17 .LED(LED)
18 );
19
20 defparam LED_flicker0.counter0.CNT_MAX =
24;
21 defparam LED_flicker0.counter1.CNT_MAX =
24;
22
23 //generater clock
24 initial Clk =
1;
25 always #(`clk_period/2)Clk = ~Clk;
26
27 initial begin
28 Rst_n =
1'b0;
29 #(`clk_period *
20 +
1);
30 Rst_n =
1'b1;
31 #(`clk_period *
2000);
32 $stop;
33 end
34
35 endmodule
|
行和第21行,使用defparam对设计模块中的CNT_MAX进行了重新定义,我们希望通过这种方式,来在仿真的时候,让计数值非常小,这样仿真便能很快的得出结果,通过这两句话大家也可以看到,结构体的形式能够实现多级调用。然而,当使用此testbench文件直接仿真LED_flicker模块时,却并不能实现预期的效果。在modelsim中仿真时,报告如下信息:
虽然并不影响仿真的执行,然而从仿真执行的结果来看,这种在testbench中再次修改设计模块中参数的方式无法与实体模块中例化子模块后进行参数定义同时存在,将实体模块中使用defparam修改参数的部分屏蔽,则仿真文件中的defparam生效,实现了预期效果。下图分别为不屏蔽LED_flicker中defparam内容和屏蔽LED_flicker中defparam内容后的仿真结果,从图中可知,不屏蔽,LED[0]和LED[1]的翻转频率分别为100ms和10ms,结果与LED_flicker中defparam定义的值一致,屏蔽掉后,LED[0]和LED[1]的翻转频率都是0.001ms,仿真结果与testench文件LED_flicker_tb中defparam定义的值一致。
不屏蔽LED_flicker中defparam内容时仿真结果
屏蔽LED_flicker中defparam内容时仿真结果
在实际使用中,我们往往希望实体设计模块中能够设定某参数值为符合实际板级运行要求的值,而在仿真中,为了节约仿真时间,我们又希望修改该参数值以配合仿真通过上面的例子我们发现,使用defparam语句难以同时满足实体设计和仿真的需求。那么有没有更好的方法来实现实体设计与仿真验证中对参数的修改能够共存且互不干扰呢?
为了实现此功能,我们调整在实体模块中例化子模块时对参数的修改方式,上面我们介绍的是使用defparam语句重新定义子模块参数内容。接下来我们使用在模块例化时,直接对参数进行例化的方式来修改子模块中的参数值。使用参数例化方式修改参数值的LED闪烁灯模块代码如下所示:
01 module LED_flicker_inst(
02 Clk,
03 Rst_n,
04 LED
05 );
06
07 input Clk;
08 input Rst_n;
09 output [1:0]LED;
10
11 counter
12 #(
13 .CNT_MAX(24_999_99)
14 )
15 counter0(
16 .Clk(Clk),
17 .Rst_n(Rst_n),
18 .led(LED[0])
19 );
20
21 counter
22 #(
23 .CNT_MAX(24_999_9)
24 )
25 counter1(
26 .Clk(Clk),
27 .Rst_n(Rst_n),
28 .led(LED[1])
29 );
30
行到19行为例化counter0模块的代码。这里对于子模块中的参数,使用了和端口相类似的形式进行修改。通过这种方式,在例化每个功能模块的时候,直接将所需修改参数的值也同时通过例化的方式修改了。
下面对这种例化方式的代码结构进行简单介绍:
行为原始设计模块的名字,第12行和14行为一对小括号,对参数例化的内容就在此括号中进行。注意11号,即括号前面需要使用"#"符号来声明这是对参数进行例化。括号内,参数例化形式与端口例化形式一致,使用"."+原始参数名+新值的方式来修改新的值。通过这种方式,能够避开对于同一个参数多次使用defparam来重定义值而引发的冲突。使用上述testench文件仿真LED_flicker_inst模块的仿真结果如下图所示:
使用defparam重新修改CNT_MAX值的仿真结果
可以看到,LED[0]和LED[1]的翻转频率都是0.001ms,为通过testbench修改后的结果。
为了验证使用这种例化的方式是否能够达到预期的效果,这里将testbench中defparam内容屏蔽,再来观察仿真结果即可,屏蔽掉testbench文件中defparam内容后仿真结果如下图所示:
屏蔽testbench文件中defparam内容后仿真
可以看到,屏蔽掉testbench中队CNT_MAX值的修改内容后,LED[0]和LED[1]的翻转频率分别为100ms和10ms,仿真结果就与实际设计值一致。但是在我们实际板级使用的时候,testbench中的值并不会对实际设计内容产生影响,因此,即使不屏蔽testbench中对参数值的修改,也不会影响板级设计结果。
因此我们可以总结得出,为了同时保证实际逻辑设计和仿真验证时对参数的修改能够共存,互不影响,在实体模块设计中,使用参数例化的方式修改被例化模块中的参数。在仿真验证用的testbench中,使用defparam语句来修改对应的参数。
通过本教程内容,讲解了在Verilog中使用参数化设计的方法,展示了参数化设计在实际使用时候的巨大便利。介绍了两种修改参数值的方法,并通过实际的例子展示了两种修改方式的特点,并最终给出了能够同时兼容实际逻辑设计和仿真验证的参数修改方法。希望大家以后。在设计自己的代码的时候,多使用这种参数化的设计方式。
如有更多问题,欢迎加入芯航线FPGA技术支持群:472607506
小梅哥
2015年11月7日于芯航线电子工作室
|
|