Verilog语法基础讲解之参数化设计

 

在Verilog语法中,可以实现参数化设计。所谓参数化设计,就是在一个功能模块中,对于一个常量,其值在不同的应用场合需要设置为不同的置,则将此值在设计时使用parameter 关键字声明,那么在上层模块例化使用该功能模块时,可以根据具体需求重新配置该常量的值,从而实现不同应用场合对对应常量的灵活调整。

 

以下为使用Verilog设计的一个控制LED闪烁灯的模块代码:

 

01    module counter(Clk,Rst_n,led);

02

03        input Clk;    //系统时钟,50M

04        input Rst_n;    //全局复位,低电平复位

05        

06        output
reg led;    //led输出

07        

08        reg
[24:0]cnt;    //定义计数器寄存器

09

10    //计数器计数进程    

11        always@(posedge Clk or
negedge Rst_n)

12        if(Rst_n ==
1'b0)

13            cnt <=
25'd0;

14        else
if(cnt ==
25'd24_999_999)

15            cnt <=
25'd0;

16        else

17            cnt <= cnt +
1'b1;

18

19    //led输出控制进程

20        always@(posedge Clk or
negedge Rst_n)

21        if(Rst_n ==
1'b0)

22            led <=
1'b1;

23        else
if(cnt ==
25'd24_999_999)

24            led <=
~led;

25        else

26            led <= led;

27

28    endmodule

 

在此设计中,定义了一个名为cnt的计数器,该cnt在输入时钟"Clk"的驱动下,每个时钟的上升沿执行一次自加操作,当计数到达24999999后,计数器清零,同时led驱动状态发生翻转。由于系统时钟为50M,即周期为20ns,cnt每计数25000000次,即500ms,驱动led状态翻转一次,从而可以实现控制led以1s为周期进行亮灭闪烁。在这个系统中,我们可以看到,如果我们希望将闪烁速度进行更改,例如改为每50ms让led翻转一次,则需要在代码中将"cnt ==
25'd24_999_999"中25'd24_999_999这个常量修改为25'd24_999_99。

 

现在考虑另外一种情况,假如某个系统中有三个LED灯需要通过这种方式来进行驱动闪烁,每个LED灯的闪烁频率还不一样。现假设LED1闪烁频率为1s,LED2为0.1s,LED3为0.01秒。那么这三个LED驱动模块中,"25'd24_999_999"这个常量就需要分别修改为

 

LED1:"25'd24_999_999"

LED2:"25'd24_999_99"

LED3:"25'd24_999_9"

 

因此,必须独立的设计三个这样的模块,然后在上层模块中分别例化这三个模块,以实现需求的功能。

 

独立设计这样三个模块并分别例化,确实能够完全实现上述假设系统需求的功能,但是通过这种方式设计出来的模块,不具备通用性,如果要求发生变化,则还需要回到底层模块中去修改对应的常量,而底层模块中可能不止一个地方使用到了这个常量,例如上述代码中,第14行和第23行就分别用到了这个常量。如果模块中对此常量的使用次数较多,在修改的过程即增加了工作量,又极容易发生遗漏,从而导致错误。

 

而参数化设计的使用,则可以非常完美的解决这一问题。这里首先放上该设计使用参数化设计之后的该模块代码:

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有更加快捷的方式,这种方式不需要我们针对每一个需求分别复制设计代码并修改对应参数,只需要在上层模块例化该模块时,直接在例化的过程中修改该值即可。具体实现方法,这里以实例代码的形式进行讲解。

 

在上层模块中,例化子模块时直接修改其参数有两种语法结构,这里我们首先看第一种:

 

行和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日于芯航线电子工作室

Verilog语法基础讲解之参数化设计的更多相关文章

  1. FPGA学习笔记(一)Verilog语法基础

    一.变量类型 ①数值 数值表示采用 <二进制位数>'<数值表示的进制><数值>的结构. 其中进制可以为b.o.d.h分别代表二.八.十.十六进制. 例如22'd0代 ...

  2. Verilog HDL基础语法讲解之模块代码基本结构

    Verilog HDL基础语法讲解之模块代码基本结构   本章主要讲解Verilog基础语法的内容,文章以一个最简单的例子"二选一多路器"来引入一个最简单的Verilog设计文件的 ...

  3. JAVA 入门第一章(语法基础)

    本人初学java 博客分享记录一下自己的学习历程 java我的初步学习分为六章,有c和c++的基础学起来也简便了很多. 第一章 语法基础 第二章 面向对象 第三章 常用工具类 第四章 文件操纵 第五章 ...

  4. python基础入门一(语法基础)

    作为自己正式接触并应用的第一门编程语言,在Alex和武sir两位大王的要求下,开始了写博客总结的日子.学习编程语言是很有趣的一件事情,但有2点请一定要谨记:1.做人靠自己,码代码也必须靠自己.能不能成 ...

  5. PHP语法基础

    1.PHP语法基础 PHP标记符 <?php ?> 常亮与变量 $a = 10; 变量 可以在运行过程中修改 $a = 10; $a = 20; $b = 5; echo $a+$b; c ...

  6. C#语法基础和面向对象编程

    1.C#语法基础 http://www.cnblogs.com/tonney/archive/2011/03/16/1986456.html 2.C#与面向对象基础 很棒的资源,简明扼要,介绍的非常清 ...

  7. Lua脚本之语法基础快速入门

    要 1.基本数据类型 2.Lua中的常用语句结构以及函数 3.Lua中的常用语句结构介绍 4.Lua中的库函数 目录[-] 一.基本数据类型 二.Lua中的常用语句结构以及函数 1.Lua中的常用语句 ...

  8. Javascript语法基础

    Javascript语法基础   一.基本数据类型   JavaScript中支持数字.字符串和布尔值三种基本数据类型: 1.数字 数字型是JavaScript中的基本数据类型.在JavaScript ...

  9. LinQ 语法基础

    LINQ (Language-Integrated Query,语言集成查询). LINQ to Objects.LINQ to SQL.LINQ to DataSet和LINQ to XML,它们分 ...

随机推荐

  1. APICloud上有关iOS证书的一些问题

    1. 苹果开发者账号及其区别: 苹果的开发者账号分为个人.公司和企业三类. 个人是99$一年,只能个人使用,可以提交应用到AppStore: 公司的也是99$,但是可以邀请其它成员一起使用,可以提交应 ...

  2. iOS - (调用系统本机打电话功能)

    如下图所示,点击订单里的打电话 button 后,调用系统的打电话功能. 这个调用系统打电话功能有点简单,不需要遵守协议和代理什么的,直接在点击方法里写上几句代码就可以了. 下面来看看代码吧: 接下来 ...

  3. Jquery判断离开页面时,通过Ajax更新数据(兼容IE,Chrome,FF浏览器)

    现在很多项目都有客户离开网页时,处理一些业务的需求.所以焦点就聚集在了如何获取页面离开事件. 以下是本人在一个项目中需要记录页面浏览时长的处理办法,测试兼容IE,Chrome,FF浏览器 代码如下: ...

  4. 到底UDP和TCP是什么个概念?

    今天在论坛看到一牛人对tcp和udp的解释和区分,突然间恍然大悟. 以下全为拷贝. 在现实生活中,“要想富,先修路”:同时人总要“居有定所”,于是盖起了N多的房子.但是当你和同事商量好去做客的时候却发 ...

  5. Java基础之一组有用的类——Observable和Observer对象(Horrific)

    控制台程序. Obserable类提供了一个有趣的机制,可以把类对象中发生的改变通知给许多其他类对象. 对于可以观察的对象来说,类定义中需要使用java.util.Observable类.只需要简单地 ...

  6. Greenplum:学习资料

    Greenplum技术浅析:http://www.cnblogs.com/end/archive/2012/08/17/2644290.html Greenplum 数据库架构分析:http://ww ...

  7. iptables使用

    iptables规则的查看.添加.删除和修改 1.查看 iptables -nvL --line-number (这个命令跟/etc/init.d/iptables status 输出差不多) -L ...

  8. 实验十四_访问CMOS RAM

    编程:以"年/月/日 时:分:秒"的格式,显示当前的日期,时间. 注意:CMOS RAM中存储着系统的配置信息,除了保存时间信息的单元外,不要向其他的单元写入内容,否则将引起一些系 ...

  9. swift语言实战晋级-第9章 游戏实战-跑酷熊猫-9-10 移除平台与视差滚动

    9.9 移除场景之外的平台 用为平台是源源不断的产生的,如果不注意销毁,平台就将越积越多,虽然在游戏场景中看不到.几十个还看不出问题,那几万个呢?几百万个呢? 所以我们来看看怎么移除平台,那什么样的平 ...

  10. java类的定义以及参数传递

    class类(类似结构体)的定义 import java.util.Scanner; import java.io.*; class student{//类的名称 public String name ...