Verilog 语言支持宏定义(`define),参数 parameter,局域参数(localparam)以及`include等内容。这些数据常量的支持极大方便数字系统设计、仿真与验证。这些参数是预编译处理的。
1. 预编译
所谓预编译就是在系统编译之前,编译器先将定义的宏常量,参数等先对系统文件扫描一遍,将文件中引用的宏和参数以实际值替代。对`include 文件的引用,将实际文件复制到对应位置,然后才对系统进行编译,这一点和具有编译运行的软件编译处理是一致的,如C语言,C++语言等。
2. 宏定义
- `define 关键字
宏定义的关键字是`define。在预编译阶段,`define 用于文本替换,类似于 C 语言中的 #define。一旦 `define 指令被使用,其在整个翻译过程中都会有效。例如,在一个文件中定义:
`define DATA_DW 32 ,其含义是 DATA_DW=32, 在编写文件时使用`DATA_DW表示常数32, 在系统编译时,首先将所有`DATA_DW 出现的地方都替换成32,然后再编译。
一个文件定义的宏常量也可以在另一个文件中使用,比如在另一个文件中可以直接使用 `DATA_DW。当然这和编译工具的设定有关,建议一个文件中定义的宏只在该文件中使用。
使用宏的好处是,在全局中使用宏定义的常量,将来如果该常量需要改变变化,直接修改宏定义就可以改动所有使用宏的地方。
宏定义也可以是一个带有参数的表达式方式,像`define low_pos(w,b) ((w)*64 + (b)*8) ,该宏调用时需要传递常量参数,如:
integer i=`low_pos(10,4); i 的结果为 i=10*64+4*8=672。
例1:利用宏参数定义模块输入输出位宽。
设计文件 mul8.v
`define PW 8
module mul8
(
input [`PW-1:0] a,
input [`PW-1:0] b,
output [`PW*2-1:0] p
);
assign p = a * b;
endmodule
仿真文件 tb.v
`timescale 1 ns/1 ps
`define PW 8
module tb
(
);
parameter PERIOD = 10 ;
reg CLK;
initial
begin
CLK = 1'b0;
#(PERIOD/2);
forever
#(PERIOD/2) CLK = ~CLK;
end
reg [`PW-1:0] a, b;
wire [`PW*2-1:0] p;
initial
begin
a = `PW'b0;
b = `PW'b0;
end
always @(posedge CLK)
begin
a = a + 1;
if(a == 2**(`PW)-1 ) //对于求幂运算符**,只能是2的幂,指数部 //分必须是常量
b = b + 1;
end
mul8 mul8_dut
(
.a (a),
.b (b),
.p (p)
);
endmodule
- `undef 关键字
利用`undef 关键字可以中止当前宏常量的定义。
如: `undef PW
文件在这条语句之后 就不能再以PW 替代8 使用。如果想继续使用`PW宏,则需要重新声明,一般在需要修改该宏的值的情况下应用。
- `ifdef, `ifndef, `elsif, `else, `endif
`elsif, `else 编译指令对于 `ifdef 指令是可选的,即可以只使用 `ifdef 和 `endif 组成一次条件编译指令块。
例:缺省对32位数据处理,如果定义宏,则按宏定义处理
`ifdef DATA_DW
reg [`DATA_DW-1:0] data_a;
reg [`DATA_DW-1:0] data_b;
reg [`DATA_DW-1:0] data_c;
`else
reg [31:0] data_a;
reg [31:0] data_b;
reg [31:0] data_c;
`endif
注: 宏一般在定义时大小写字符都可以使用,而且大小写是区分的,也就是大写字符定义的宏与小写字符定义宏虽然只有大小写之分,却代表了不同的宏。
如:
`define data_w 8 与`define DATA_W 32 定义的宏,可以分别使用,互不冲突。一般习惯宏都是用大写字母。
- 参数 parameter与局部参数localparam
parameter 与localparam都可以定义参数常量,但使用范围不同:
localparam定义的参数仅限于本module内部使用,模块例化不可调用,相当于局部常量。状态机状态常量定义,而且只能在定义的位置之后使用。
parameter定义的参数不仅能在本文件中使用,还能利用module 例化后起到参数传递的作用。parameter经常在module接口,以及在设计文件中多处使用特定常数的地方使用。
举例:
设计文件 para_fadder.v
module para_fadder
#(
parameter WDTH = 4
)
(
input ci,
input [WDTH-1:0] a,
input [WDTH-1:0] b,
output [WDTH-1:0] sum,
output co
);
assign {co, sum} = a + b + ci;
endmodule
Testbench 文件 tb.v
`timescale 1ns/1ps
module tb
(
);
parameter WDTH = 16;
reg ci;
reg [WDTH-1:0] a, b;
wire [WDTH-1:0] sum;
wire co;
initial
begin
a ='b0;
b ='b0;
ci = 0;
#10
a ='d100;
b ='d33;
ci = 0;
#10
a ='d101;
b ='d37;
ci = 1;
end
para_fadder
#(
.WDTH(WDTH)
)
para_fadder_dut
(
.ci (ci),
.a (a),
.b (b),
.sum (sum),
.co (co)
);
endmodule
Modelsim仿真波形如图1所示,
图1
说明: 在设计文件中定义了 参数 WDTH=4,而在例化中传递的参数为16,那么最终在设计文件中的参数的具体数值由传递值决定,本例中WDTH的最终为16,最终例化了16位全加器。如果在例化中没有给参数传递值,则WDTH=4变成缺省值,即例化4位全加器。如例化如下:
para_fadder para_fadder_dut
(
.ci (ci),
.a (a),
.b (b),
.sum (sum),
.co (co)
);
上例中在例化没有给parameter声明的常量传递任何值,模块内的参数将使用缺省值,即例化成4位全加器。
module中 parameter 的格式如下:
设计文件
module para_fadder
#(
parameter WDTH = 4,
parameter WDTH1 = 4 //最后一个参数没有分隔符
)
多个参数采用逗号 “,”隔开,最后一个参数没有分隔符 。
模块例化时有类似的格式:
para_fadder
#(
.WDTH (WDTH),
.WDTH1 (WDTH1)
)
如果只有一个参数,则为
para_fadder
#(
.WDTH(WDTH)
)
可见合理使用参数(parameter)及参数传递机制,可以使Verilog代码的灵活性和重用性得到较大提高。在后续学习和开发中应学会合理使用参数化设计提高代码的重用性。
对应视频:

学习本篇文章后有几点收获,1、宏定义可以带参数,例如:`define low_pos(w,b) ((w)*64 + (b)*8) ;2、利用`undef 关键字可以中止当前宏常量的定义;3、localparam定义的参数仅限于本module内部使用,模块例化不可调用,相当于局部常量;4、parameter定义的参数不仅能在本文件中使用,还能利用module 例化后起到参数传递的作用。
本篇文章对宏定义(`define),参数 parameter、局域参数localparam,做了非常详细的讲解,其中列举了大量验证实例,而且每一个实例都是简单易懂,是非常棒的教学文章!