什么是参数化建模
和写软件程序一样,我们也希望 Verilog 的模块也可以重利用。要使模块可以重复利用,关键就在于避免硬编码(hard literal),使模块参数化。
参数化建模的好处是可以使代码清晰,便于后续维护和修改。
Verilog 的参数化建模是有一定限制的,它的参数值是编译时计算的,不会引入任何实际的硬件电路。参数必须在编译时确定值。也就是说只能达到动态编译,固态运行,而非软件的动态编译,动态运行。
这主要是因为它是描述(Description)硬件的语言,而非软件设计(Design)语言。
比如一个计数器,我们可以设置一个参数来指定它的计数周期(动态编译),但是这个计数周期在综合之后就是固定值了(固态运行),不能在运行的时候动态地改为另外一个值(除非电路综合时同时产生了多个计数器,这种情况不算真正意义上的动态运行,而且也达不到真正意义上的动态运行,因为不可能把所有可能的计数器都实现了备用,耗费资源而且没有实际意义)。
参数化建模的主要目的是:
提高模块的通用性,只需要修改参数,不用修改其他代码就可以适用于不同的环境中。
总结一下我找到的资料,具体的参数化建模方法一共就 3 种:
- `define 宏定义
- parameter 模块参数化
- `ifdef 等 条件编译
Define Macro Substitution
`define 是编译器指令,功能是全局宏定义的文本代替。它类似于 C 语言中的#define,用法如下:
// define
`define WORD_REG reg [31:0]
// using
`WORD_REG reg32;
Problem
`define 定义的宏的作用域是全局的,这种机制会导致两个问题
- 可能会有在不同文件中发生重定义的问题
- 编译顺序有要求 file-order dependent,必须确保使用前,宏定义有效,所以每个使用到宏定义的源文件必须包含这个头文件,这会导致多重包含的问题。
Solution
- 对于第一个问题,尽可能把所有的宏定义放在同一个头文件中,比如 “global_define.vh”
- 对于第二个问题,和 C++ 类似,头文件应该使用头文件保护符。
// global_define.vh head file
`ifndef GLOBAL_DEFINE_VH
`define MAX = 8
`define SIZE = 4
// ...
`enif
Guideline
- 只有那些要求有全局作用域、并且在其他地方不会被修改的常量才用 define 来定义
- 对于那些只限于模块内的常量,不要使用 define
- 尽可能将所有的 define 都放在同一个文件中,然后在编译时先读取这个文件
- 不要使用 `undef
Parameter
应该避免硬编码设计 hard literal,使用参数 parameter 来代替。举个例子
// use parameter
parameter SIZE = 8,
MAX = 10;
reg [SIZE - 1 : 0] din_r;
// DO NOT use hard literal
reg [7 : 0] din_r;
Localparam
Verilog-2001 中添加了一个新的关键字 localparam,用来定义模块内部的、不能被其他模块修改的局部常量,概念类似于 C++ 中 class 的 protect 成员。
虽然 localparam 不能被外部模块修改,但是它可以用 parameter 来初始化。
parameter N = 8;
localparam N1 = N - 1;
Parameter Redefinition
在 Verilog-2001 出现之前,Verilog-1995 中只有两种方法实现参数重定义:
- 使用 # 符号,顺序列表重定义
- 使用 defparam
逐个讨论
- Uisng #
Syntax
举个栗子,模块 myreg
module myreg (q, d, clk, rst);
parameter Trst = 1,
Tclk = 1,
SIZE = 4;
// ...
endmodule
在上一层的模块中传递参数例化这个模块
module bad_warpper (q, d, clk, rst)
// legal parameter passing
myreg #(1, 1, 8) r1(.q(q), .d(d), .clk(clk), .rst(rst) );
// illegal parameter passing
// myreg #(,,8) r1(.q(q), .d(d), .clk(clk), .rst(rst) );
endmodule
Pro
虽然每次例化都要说明所有的参数值,但是比第二种方法好
Con
每次例化都要说明所有的参数值。
2. Using defparam
Syntax
defparam path.name = value;
比如在上面的例子中
defparam r1.SIZE = 8;
Pro
可以放在任何文件的任何地方,不用再重复没有修改的参数值
Con
因为 defparam 有这么 “强” 的功能,反而会导致一系列的问题
Hierarchical deparam.
- 比如顶层模块使用 defparam 修改子模块的参数,子模块中又使用 defparam 修改顶层模块要传递进来的参数,形成一个环,这样子可能导致综合时不提示错误,但是结果与预期不符。
- Multiple defparams
在 单个文件 / 多个文件 中重复定义 defparam,会有微妙的问题,Verilog-1995 中没有定义这种现象,实际结果依赖于使用的综合工具。
因为 defparam 有这么多缺点,所以在 2001 年之前,Synopsys 是不支持 defparam 的,网上很多转载的博客都说 defparam 是不可综合的,实际上在后来,Synopsys 在压力之下添加了对其的支持。而我用 XST 也证明是支持 defparam 可综合。
综上原因,Verilog Standards Group (VSG) 倡议大家抵制使用 defparam,大神 Clifford E. Cummings 在论文中建议综合工具如果用户坚持使用 defparam 语句,必须添加以一个参数 +Iamstupid…
“The Verilog compiler found a defparam statement in the source code at (file_name/line#). To use defparam statements in the Verilog source code, you must include the switch +Iamstupid on the command line which will degrade compiler performance and introduce potential problems but is bug-compatible with Verilog-1995 implementations. Defparam statements can be replaced with named parameter redefinition as define by the IEEE Verilog-2001 standard.”
总结一下,可以发现 Verilog-1995 中的两种方法都不怎么好,显然 VSG 也发现了这个问题,所以在 Verilog-2001 中,出现了第三种方法,并且墙裂推荐使用这种新方法。
- Using named parameter redefinition
Syntax
类似于模块例化时端口连接的方式,比如上例中只想改变 SIZE 的值.
myreg #(.SIZE(8)) r1(.q(q), .d(d), .clk(clk), .rst(rst) );
Pro
结合了前两种方法的有点,既显示说明了哪个参数值改变了,也将参数传递放在了实例化的语句中。这种方法是最干净的 (cleanest) 方法,不依赖于任何综合工具。
Con
貌似没有~
Guideline
-
不要使用 defparam,应该使用 named parameter redefinition。
Example -
clock cycle definition
因为时钟是一个设计中最基本的常量,它不会在随着模块变化,所以应该用 `define 来定义,并且将它放在顶层的头文件中。 -
FSM
在一个设计中可能有不止一个 FSM,而通常 FSM 有一些共同的状态名字,比如 IDLE、READY、READ、WRITE、ERROR、DONE 等,所以应该用 localparam 来定义这些常量。
Conditional Compilation
Verilog 的条件编译和 C 也十分类似。前面介绍 define 时,已经用到了条件编译中的 `ifdef。条件编译一共有 5 个关键字,分别是:
`ifdef `else `elsif `endif `ifndef
条件编译一般在以下情况中使用
- 选择一个模块的不同部分
- 选择不同的时序和结构
- 选择不同的仿真激励
Syntax
// example1
`ifdef text_macro
// do something
`endif
// example2
`ifdef text_macro
// do something
`else
// do something
`endif
// example3
`ifdef text_macro
// do something
`elsif
// do something
`else
// do something
`endif
// example4
`ifndef text_macro
// do something
`else
// do something
`endif
条件编译是一个非常好的技术,它可以帮助我们更好的管理代码。
举个栗子,比如我们写了一个程序,在 debug 阶段,在程序中添加了很多显示中间变量的语句,到最后 release 时,当然要去掉这些语句。最差的方法当然是删掉这些代码,但是如果以后我们还想 debug 时,又得手动写,而且时间长了,我们自己都记不清该加哪些语句了。稍微好点的方法是把它们注释起来,但是同样,时间长了,哪些该注释,那些不该注释又混淆了。最好的方法就是用条件编译。我们可以定义一个宏 DEBUG
`define DEBUG
// conditional compilation
`ifdef DEBUG
// debug
`else
// release
`endif
这样,我们只需要选择是否注释第一行的宏定义就可快速在 debug 和 release 之间切换。
再比如在 Verilog 的模块中,针对不同的应用环境,我们要实现不同的模块,这时候也可以使用条件编译选择具体综合哪段代码。
Summary
总结一下,就是一下几点
Guideline
-
只有那些要求有全局作用域、并且在其他地方不会被修改的常量才用 define 来定义
-
对于那些只限于模块内的常量,不要使用 define
-
尽可能将所有的 define 都放在同一个文件中,然后在编译时先读取这个文件
-
不要使用 `undef
-
不要使用 defparam,应该使用 named parameter redefinition。
-
需要时使用条件编译