在Verilog程序设计中,关于模块,always过程,并行赋值语句,函数及task调用等等。如何声明,调用,嵌套及赋值等一系列问题,初学者往往无所适从。关于wire,reg类型的声明,初始化及赋值等也有一系列的问题需要注意。本文对上述提到的相关问题进行深入讨论。希望读者在理解之后举一反三,做到正确使用Verilog语法规则,使得代码清晰,简洁,准确。
1. 模块(module)的组织与正确使用
模块(module)是verilog工程的基础,所有的verilog程序都是在module中进行编写的,少量的宏定义可以在模块之外。一个文件可以只包含一个模块,也可以是一个文件包含多个模块。 一个模块不能分离存储到不同的文件中。
1)通常情况下一个文件(*.v)中包含一个module。但也可以在 同一个*.v 文件中,同时存在2个或者多个 module 模块。module 的格式请参考:Verilog 模块与端口。
2)在文件中,module 级别是最高一级的,其他的如always,initial,并发赋值等只能在module 模块下进行编写和使用。 和module 同一级别 的verilog语法声明也包括`timescale, `include, `define 等宏定义,如例1所示。
例1:
// MACRO can be defined outside module
`timescale 1ns/1ps
`include "test.v"
`define PPP
module aaa
(
input clk,
input xxxx,
output kkk
);
reg [4:0] ttt;
wire [2:0] uuu;
// MACRO can be also defined inside module
`include "debug.v"
`define BBB
awlays @ (posedge clk)
begin
....
end
always @ (*)
begin
....
end
assign kkk = ...;
assign uuu = ...;
endmodule
module bbb
(
...
);
....
endmodule
3)module 和 module是同一级别的。 Verilog 语法中module 模块中不能再嵌套定义module了, 虽然Systemverilog 可以嵌套定义,但也有规则限制。
例2:
module aaa
(
....
);
.....
//ERROR: module nested define is not allowed in Verilog
module bbb
(
...
);
...
endmodule
ccc ccc_inst
(
.d (),
.c ()
);
endmodule
从例1,例2的使用可以看出:
-
- Verilog 语法中module 下不能在定义 其他module了,但是systemverilog 可以(nested module declarations)。但是verilog 和 systemverilog 在module中都可以例化其他模块(module)。
- module 和 `include 是同一级别的, 即 `include可以写在module 模块之外。 当然, `include也可以写在模块内部。
- module 和 `define 是同一级别的, 即 `define可以写在module 模块之外。 当然, `define也可以写在模块内部。
- module 和 `timescale是同一级别的, 即 `timescale可以写在module 模块之外。 当然, `timescale也可以写在模块内部。但是很少将timescale写在module 内部声明。
4)module嵌套时的端口传递
-
- 被例化的module 输入端口(input)传递关系,能否将变量传递给被例化模块的输入端口(input),关键看驱动模型。
- 符合驱动模型传递如下:
- 本级模块的input 类型端口
- 本级模块的output 类型端口
- 本级模块的定义的reg 类型变量
- 本级模块中的wire 类型变量
- 不符合驱动模型传递如下:
- 无
- 符合驱动模型传递如下:
- 被例化的module 输出端口(output)传递关系
- 符合驱动模型传递如下:
- 本级模块的output,但是有如下限制
- 本级模块的output不能连接到多个被例化模块中的output端口上
- 本级模块的output连接到1个被例化模块中的output端口上,不能在本级模块其他逻辑中赋值
- 本机模块的wire,但是有如下限制
- 本级模块的wire不能连接到多个被例化模块中的output端口上
- 本级模块的wire连接到1个被例化模块中的output端口上,不能在本级模块其他逻辑中赋值
- 本级模块的output,但是有如下限制
- 不符合驱动模型传递如下:
- 本级模块的reg 类型不能连接到被例化模块中的output 端口上
- 本级模块中的output reg 类型端口,不能连接到被例化模块中的output端口上
- 符合驱动模型传递如下:
- 被例化的module 输入端口(input)传递关系,能否将变量传递给被例化模块的输入端口(input),关键看驱动模型。
2. 仿真文件编写
在仿真文件中, module 内部可以编写
- reg声明
- wire声明
- task声明
- function 声明,
- module 例化,
- generate,
- `define,
- `include,
- `timescale,
- parameter,
- localparam,
- always,
- assign
- initial
- IP, primitive 例化等 。
也就是说这些声明,例化是同一级别的。
举例
`timescale 1ns/1ps
`include "test.v"
`define KKK 3
module tb();
reg clk;
reg clk_50 = 0;
reg rst = 1;
localparam PERIOD = 10;
always # clk_50 = ~clk_50;
initial
begin
clk = 0;
forever #(PERIOD/2) clk = ~ clk;
end
reg [3:0] cnt;
always @ (posedge clk or posedge rst)
if(rst) cnt <= 0;
else
begin
if(cnt == PEROID) cnt <= 0;
else cnt <= cnt + 1;
end
wire ppp = (cnt == `KKK) ? 1'b1 : 1'b0;
initial
begin
#2000;
$stop;
end
endmodule
在模块中可以使用wire,reg等声明,但要注意reg 声明时不能使用变量赋初值,如果需要,只能使用wire声明。
例1:
reg b = 0;
reg c = 0;
reg a = b | c;
initial
begin
b = 1;
c = 0;
#10;
b = 0;
c = 1;
end
wire k = b | c;
这里 reg a = b | c ; 是不正确的。 只能使用 wire k = b |c ; 或者是 wire k; assign = b | c; 来替代。
例2:always 多处赋值
`timescale 1ns / 1ps
module altera_sim #
(
parameter ADD_SUB = 0
)
(
input clk,
output [7:0] led
);
reg [3:0] cnt = 0;
always @ (posedge clk)
begin
cnt <= cnt + 1;
end
reg [7:0] led_r = 0;
always @ (posedge clk)
if(cnt[3:0] == 0) led_r <= ADD_SUB ? led_r + 1 : led_r - 1;
//always @ (posedge clk)
//if(cnt[3:0] == 1) led_r <= led_r + 2;
assign led = led_r;
endmodule
`timescale 1ns / 1ps
module tb(
);
reg clk = 0;
localparam CLK_PERIOD = 10;
initial
begin
clk = 0;
#200;
forever
#(CLK_PERIOD/2) clk = ~clk;
end
reg [3:0] b = 5;
initial
begin
b = 0;
end
always @ (posedge clk)
b <= ~b;
always @ (posedge clk)
b <= b + 1;
initial
begin
clk = 0;
#2000;
$stop;
end
wire [7:0] led;
altera_sim #(.ADD_SUB (1))
top_inst
(
.clk (clk),
.led (led)
);
assign led = 8'h80;
endmodule
b 有4个地方赋值:
- 声明的时候reg [3:0] b = 0;
- 初始化的时候赋值 initial
- always b <= ~b;
- always b <= b + 1;

这样的设计,在仿真的时候,可以正常使用。 但是相同的逻辑不能在综合的时候使用。原因是: 2 次 always 进行不同的赋值综合的时候会报错。调用模块alter_sim中的led (output 端口) 和 assign led = 8’h80; 赋值相冲突,最终输出为led = xx;
例3:initial 中嵌套always, 不能正常编译。
reg [3:0] b = 5;
initial
begin
b = 0;
always @ (posedge clk)
b <= ~b;
end
initial 中使用always 编译的时候会报错。但可以使用其他方式实现:
reg [3:0] b = 5;
initial
begin
b = 0;
while(1)
begin
@(posedge clk);
b <= ~b;
end
end
或者
reg [3:0] b = 5;
initial
begin
b = 0;
while(1)
begin
#10;
b <= ~b;
end
end
例4:always 中使用 initial 不能正常编译:
always @ (posedge clk)
begin
initial
begin
b = 3;
end
b <= ~b;
end
例5:2 个模块的输出(output) 连接到一起了。
wire [7:0] led; altera_sim # (.ADD_SUB(1)) top_inst ( .clk (clk), .led (led) ); //assign led = 8'h80; altera_sim # (.ADD_SUB(0)) top1_inst ( .clk (clk), .led (led) );

例6:always 中不能使用assign
always @ (posedge clk)
begin
assign c = a + b;
end
例7:always 中不能使用generate , 但是generate 中可以使用always。
`timescale 1ns/1ps
module tb();
genvar i;
always @ (posedge clk)
generate
for (i = 0; i < 4; i = i + 1)
begin:test_gen
b[i] <= b[i] + 1;
end
endgenerate
endmodule
例8:forever, while, if, for, case 等必须在initial, always 下使用,不能独立存在。即使有些综合器没有报错。
`timescale 1ns/1ps module tb(); reg clk = 0; forever # clk = ~clk; reg a = 0; reg b = 1; localparam kkk = 1; if(kkk) wire c = a & b; else wire c = a | b; endmodule
例9:`define parameter可以在端口定义中使用,define 可以定义端口的种类和变量宽度,parameter 只能定义端口的宽度。举例:
`define KKK 4
module aaa #
(
parameter D = 8
)
(
input clk,
input [`KKK - 1:0]a,
input [D - 1:0]b,
`ifdef KKK
ouput [D - 1:0] led
`else
ouput [D - 1:0] testpin
`endif
);
.....
endmodule
例10:always 使用
always @ (posedge clk);
begin
a <= a + 1;
end
; 代表当前的always 结束了, 比如在仿真中使用@(posedge clk); 所以之后的begin …. end 和 always 再也没有任何关系了。在always 中一定会有一个真实的执行体,不会只有always ,说得直白一些就是always 下一定能找到一个“;”。 举例:
always @ (posedge clk) if(reset) st <= 0; else case (st) 0: st <= 1; default : st <= 0; endcase
| 第一级 | 第二级 | 第三级 | |
| module | |||
| wire声明 | |||
| reg声明 | |||
| task 声明 | |||
| function 声明 | 不能使用 #10; 不能使用always@(posedge)等时序逻辑 | ||
| generate | |||
| `define | 可以定义端口数量,改变端口声明 | ||
| `include | |||
| `timescale | |||
| parameter | 可以定义端口宽度 | ||
| localparam | |||
| always | 不能使用使用initial,assign, 模块声明,例化 ,不能声明function, 但可以调用function | ||
| initial | 不能使用使用always, assign, 模块声明,例化,不能声明function, 但可以调用function | ||
| assign | 不能包括begin …. end, 只能使用逻辑,关系,等式,位操作等运算符 | ||
| IP, primitive 例化 | |||
| module 例化 | |||
| `timescale | |||
| `define | |||
| `include |