和写软件程序一样,我们也希望 Verilog 的模块也可以重利用。要使模块可以重复利用,关键就在于避免硬编码(hard literal),使模块参数化。
Verilog 的参数化建模是有一定限制的,它的参数值是编译时计算的,不会引入任何实际的硬件电路。参数必须在编译时确定值。也就是说只能达到动态编译,固态运行,而非软件的动态编译,动态运行。
这主要是因为它是描述(Description)硬件的语言,而非软件设计(Design)语言。
比如一个计数器,我们可以设置一个参数来指定它的计数周期(动态编译),但是这个计数周期在综合之后就是固定值了(固态运行),不能在运行的时候动态地改为另外一个值(除非电路综合时同时产生了多个计数器,这种情况不算真正意义上的动态运行,而且也达不到真正意义上的动态运行,因为不可能把所有可能的计数器都实现了备用,耗费资源而且没有实际意义)。
提高模块的通用性,只需要修改参数,不用修改其他代码就可以适用于不同的环境中。
总结一下我找到的资料,具体的参数化建模方法一共就 3 种:
`define 是编译器指令,功能是全局宏定义的文本代替。它类似于 C 语言中的#define,用法如下:
// define
`define WORD_REG reg [31:0]
// using
`WORD_REG reg32;
`define 定义的宏的作用域是全局的,这种机制会导致两个问题
// global_define.vh head file
`ifndef GLOBAL_DEFINE_VH
`define MAX = 8
`define SIZE = 4
// ...
`enif
应该避免硬编码设计 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;
Verilog-2001 中添加了一个新的关键字 localparam,用来定义模块内部的、不能被其他模块修改的局部常量,概念类似于 C++ 中 class 的 protect 成员。
虽然 localparam 不能被外部模块修改,但是它可以用 parameter 来初始化。
parameter N = 8;
localparam N1 = N - 1;
在 Verilog-2001 出现之前,Verilog-1995 中只有两种方法实现参数重定义:
逐个讨论
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
虽然每次例化都要说明所有的参数值,但是比第二种方法好
每次例化都要说明所有的参数值。
2. Using defparam
Syntax
defparam path.name = value;
比如在上面的例子中
defparam r1.SIZE = 8;
可以放在任何文件的任何地方,不用再重复没有修改的参数值
因为 defparam 有这么 “强” 的功能,反而会导致一系列的问题
Hierarchical deparam.
因为 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 中,出现了第三种方法,并且墙裂推荐使用这种新方法。
myreg #(.SIZE(8)) r1(.q(q), .d(d), .clk(clk), .rst(rst) );
结合了前两种方法的有点,既显示说明了哪个参数值改变了,也将参数传递放在了实例化的语句中。这种方法是最干净的 (cleanest) 方法,不依赖于任何综合工具。
貌似没有~
Guideline
不要使用 defparam,应该使用 named parameter redefinition。
Example
clock cycle definition
因为时钟是一个设计中最基本的常量,它不会在随着模块变化,所以应该用 `define 来定义,并且将它放在顶层的头文件中。
FSM
在一个设计中可能有不止一个 FSM,而通常 FSM 有一些共同的状态名字,比如 IDLE、READY、READ、WRITE、ERROR、DONE 等,所以应该用 localparam 来定义这些常量。
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 的模块中,针对不同的应用环境,我们要实现不同的模块,这时候也可以使用条件编译选择具体综合哪段代码。
总结一下,就是一下几点
只有那些要求有全局作用域、并且在其他地方不会被修改的常量才用 define 来定义
对于那些只限于模块内的常量,不要使用 define
尽可能将所有的 define 都放在同一个文件中,然后在编译时先读取这个文件
不要使用 `undef
不要使用 defparam,应该使用 named parameter redefinition。
需要时使用条件编译