Verilog基础语法——parameter、localparam与`define
- 写在前面
- 一、localparam
- 二、parameter
- 三、`define
- 写在最后
写在前面
在使用Verilog编写RTL代码时,如果需要定义一个常量,可以使用`define、parameter和localparam三种进行定义与赋值。
一、localparam
localparam是一种局部常量,只在声明该常量的模块中有效,不可用于模块与模块之间的参数传递。一般在定义仅用于模块内部的参数时使用localparam,比如状态机状态的定义声明。例如:
// FSM Sate
localparam IDLE = 4'b0001;
localparam INPUT = 4'b0010;
localparam DECODE = 4'b0100;
localparam COMPLETE = 4'b1000;
// FSM
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
curr_state <= IDLE;
//...
end
else begin
case(curr_state)
IDLE:
begin
if(start)
curr_state <= INPUT;
else
curr_state <= IDLE;
//...
end
INPUT :
begin
curr_state <= DECODE;
//...
end
DECODE :
begin
curr_state <= COMPLETE;
//...
end
ALL_COMPLETE:
begin
curr_state <= IDLE;
end
default :;
endcase
end
end
二、parameter
parameter与localparam相同的是,其作用范围仅仅是声明该参数的模块内部,而不同的是,parameter可以用于模块之间的参数传递,一般用于参数化设计。参数化设计是指对于所设计的功能子模块,通过修改其内部参数值即可使得该模块适用于其他场景。
这里,parameter可以分为在模块头部中声明与在模块内部定义声明,两种定义方式需要不同的方式来进行参数传递。
(1)在模块头部中定义
在模块头部中定义参数是一种常用的做法,其格式如下:
module counter
#(
parameter CNT_NUM = 8'd128 ,
parameter DIN_WIDTH = 4'd8 ,
parameter DOUT_WIDTH = 5'd16
)
(
input wire [DIN_WIDTH-1:0] din ,
output wire [DOUT_WIDTH-1:0] dout
);
// 模块内部代码
// ...
endmodule
而在模块例化时,需要与例化模块输入输出端口一样,给参数接入不同的数值即可,其格式如下:
module top
(
// 输出输入声明
// ...
);
localparam CNT_NUM = 8'd100;
localparam DIN_WIDTH = 4'd6;
localparam DOUT_WIDTH = 5'd12;
wire [DIN_WIDTH-1:0] counter_din ;
wire [DOUT_WIDTH-1:0] counter_dout;
// 模块例化
counter
#(
.CNT_NUM (CNT_NUM ), // 参数传递
.DIN_WIDTH (DIN_WIDTH ), // 参数传递
.DOUT_WIDTH(DOUT_WIDTH) // 参数传递
)
counter_inst
(
.din (counter_din ),
.dout(counter_dout)
)
// ...
endmodule
(2)在模块内部定义
在模块内部定义的paramter其格式如下:
module counter
(
input wire [15:0] din ,
output wire [7:0] dout
);
parameter CNT_NUM = 8'd128 ;
parameter DIN_WIDTH = 4'd8 ;
parameter DOUT_WIDTH = 5'd16 ;
// 模块内部代码
// ...
endmodule
在上层模块的例化中可以通过defparam进行修改所例化模块中定义参数的值,其格式如下:
module top
(
// 输出输入声明
// ...
) ;
wire [15:0] counter_din ;
wire [7:0] counter_dout;
// 模块例化
counter counter_inst
(
.din (counter_din ),
.dout(counter_dout)
);
// 格式:
// defparam 模块例化名称 参数名称 = 重新设定的参数值
// 如果是多层嵌套子模块,在模块3中例化模块2,在模块2中例化模块1,则格式为:
// defparam 模块3例化名称 模块2例化名称 模块1例化名称 参数名称 = 重新设定的参数值
defparam counter_inst CNT_NUM = 8'd100;
defparam counter_inst DIN_WIDTH = 4'd6;
defparam counter_inst DOUT_WIDTH = 5'd12;
endmodule
这种方式的缺点在于:该方式声明的parameter无法用于模块输入输出信号位宽的控制,因为参数定义声明在模块的内部。
三、`define
通过`define定义的参数作用范围是整个设计工程文件,遇到`undef则失效,其格式如下:
`define CNT_NUM 8'd128
module counter
(
// 输出输入声明
// ...
)
// 模块内部代码
// ...
endmodule
也可以将所以`define定义声明的参数放在一个单独文件中,并在每个模块中使用`include包含声明文件,以作用于整个工程项目。其格式如下:
// para_def.vh
// 独立参数声明文件
`define CNT_NUM 8'd128
`define DIN_WIDTH 4'd6;
`define DOUT_WIDTH 5'd12;
然后再每个模块的前面使用`include包含该参数声明文件即可使用,格式如下:
// `include "路径/参数声明文件名"
`include "F/xxx/RTL/para_def.vh"
module counter
(
// 输出输入声明
// ...
)
// 模块内部代码
// ...
// 使用格式:`参数名
always @(posedge clk) begin
if(cnt == `CNT_NUM)
else
end
endmodule
写在最后
在本文中,我们学习了Verilog基础语法中三种不同的参数定义方式——localparam、parameter与`define,其中,`define定义的参数作用范围最广,且支持用于模块之间的参数传递;localparam作用范围仅为模块内部,且不支持参数传递;而parameter是两者的折中,作用范围为模块内部,但是支持参数传递。在实际代码编写过程中,应选择合适的方式对不同参数进行声明,使用时可以参考下表。
关键字 | 适用场景 |
---|---|
localparam | 仅用于模块内部的参数,且在不同场景下无需进行修改 |
parameter | 仅用于模块内部的参数,但是在不同场景下需要进行修改 |
`define | 整个工程文件中都会用到的参数 |
本文到此结束,欢迎评论区交流探讨。