两个名字,一件事
Verilog 中的每个信号都有类型。你最先遇到的两种类型是 wire 和 reg。两者都能保存值,都可以是单位或多位。区别不在于信号是什么,而在于由谁驱动它。
wire信号在过程块之外被驱动:由assign语句、子模块的输出或模块的输入端口驱动。reg信号在过程块之内被驱动:initial或always。
规则就这么多。这两个关键字的名字有些别扭,因为它们比现代的 Verilog 写法出现得更早。reg 在硬件中并不总会变成寄存器,原因请往下读。
wire:连续的连接
wire 就是一根导线。它持续传送驱动源产生的任何值。驱动 wire 的方式有两种:
有三点值得注意:
y和z在模块中声明为wire,在 testbench 中也再次声明为wire。- 它们由
assign驱动,这是连续赋值的形式。只要=右边的任何信号发生变化,右边就会重新计算。 - 在保持
y为wire的情况下,我们不能在always块中写y = a & b。编译器会拒绝。
如果你忘了写 wire 关键字,Verilog 会自动把这个信号隐式声明为 1 位的 wire。这有时很方便,有时却是一个悄无声息的 bug。大多数团队会打开工具选项,让隐式 wire 直接报错。写清楚,就能避开这个坑。
reg:过程块中的存储
reg 是你在 initial 或 always 中赋值的信号。这个名字是语言早期留下的遗迹,那时“reg”听起来就像“register”(寄存器);在现代用法中,reg 只是过程代码所写入的信号的类型。
count 在模块内是 reg(因为 always 块写入它),在 testbench 中是 wire(因为 DUT 的输出端口驱动它)。同一个信号,在不同作用域中扮演不同角色,类型也不同。
为什么“reg”不一定是“寄存器”
这是初学者最常掉进去的坑。下面这个模块把 y 声明为 reg,但综合出来的硬件里并没有触发器:
always @(*) 块对任何输入的变化都敏感,它是组合逻辑。综合工具看到这种写法,会生成一个与门:没有触发器,没有时钟,只有逻辑。这里的 reg 关键字纯粹是语法上的要求,因为 y 是在 always 中被赋值的。
要得到真正的触发器,always 块必须对时钟边沿敏感:
always @(posedge clk) begin
q <= d;
end
这就是“请综合工具生成一个触发器”的写法:带时钟的敏感列表,加上非阻塞赋值。同样是 reg 关键字,硬件却完全不同。我们会在 Always 块和阻塞与非阻塞赋值中讲解两者的区别。
实际中如何选择
每次声明信号时,问自己:我要怎样驱动它?
- 用
assign驱动?→wire。 - 连接到子模块的输出端口?→
wire。 - 模块的输入端口?→
wire。(输入总是 wire。) - 在
initial或always中驱动?→reg。 - 由过程代码驱动的模块输出端口?→
output reg。 - 由
assign驱动的模块输出端口?→output wire。(或者只写output,只写方向时默认就是 wire。)
在普通 Verilog 中,这棵决策树涵盖了你会遇到的所有情况。
驱动冲突
理论上,wire 信号可以有多个驱动源,三态总线就是这样工作的:多个模块可以驱动同一根 wire,不活动的模块输出高阻态(z)。对于普通逻辑,两条 assign 语句写同一根 wire 会产生未定义的行为:
assign y = a;
assign y = b; // BAD - y has two drivers
仿真器可能选其中一个,也可能把信号变成 X,这取决于工具。无论哪种情况,这都是 bug。除非你明确是在搭建总线,否则每根 wire 只能有一个驱动源。
reg 信号只能由一个过程块驱动。两个 always 块给同一个 reg 赋值,在综合时会报错,在仿真中的表现也会很诡异。规则相同:只有一个驱动源。
SystemVerilog 的改进:logic
SystemVerilog(Verilog 演进而成的超集)增加了一个可以同时取代两者的关键字:logic。logic 信号既可以由 assign 驱动,也可以由过程块驱动,但不能两者兼有,而且编译器会阻止你无意中造成多驱动的 bug。
module modern(input logic a, input logic b, output logic y);
assign y = a ^ b;
endmodule
如果你从零开始一个项目,并且工具支持 SystemVerilog(本页的编辑器就支持,使用 -g2012),到处使用 logic 能让规则更简单。普通的 .v 文件仍然需要区分 wire 和 reg,而且在实际项目中,你会一直看到两种风格并存。
接下来
下一篇文档向量与数组会以同样的两种类型为基础,介绍如何让它们变成多位信号:总线位宽、范围、packed 维度,以及位向量和向量数组之间的区别。在搭建比 1 位加法器更大的东西之前,这些都是必备知识。
常见问题
Verilog 中 wire 和 reg 有什么区别?
wire 和 reg 在仿真的每个时刻都保存着一个信号值,选择哪一个取决于由谁来驱动这个信号。wire 在过程块之外被驱动:由 assign 或子模块的输出驱动。reg 在 initial 或 always 块内部被驱动。这两个名字是历史遗留下来的,容易让人误解:reg 并不总是意味着“寄存器”。
Verilog 中的 reg 就是触发器吗?
不一定。只有当 reg 所在的 always 块对时钟边沿敏感并使用非阻塞赋值时,它才会被综合成触发器。在组合逻辑的 always @(*) 块中赋值的 reg,会被综合成普通的组合逻辑。这个关键字选择的是 Verilog 数据类型,而不是硬件。
什么时候用 wire,什么时候用 reg?
经验法则:如果你要用 assign 驱动信号,或者把它连到子模块的输出端口,就用 wire。如果你要在 always 或 initial 块中给它赋值,就用 reg。模块的输入总是 wire。输出如果由 assign 驱动就是 wire,由过程块驱动就是 reg。
可以在 always 块中给 wire 赋值吗?
不可以,那是语法错误。wire 信号只能由连续赋值(assign)驱动,或者作为子模块实例的输出被连接驱动。initial 或 always 里的任何赋值,目标都必须是 reg(在 SystemVerilog 中也可以是 logic)。编译器会发现这个问题,并报告“左侧类型不匹配”的错误。
SystemVerilog 中的 logic 是什么?
logic 是 SystemVerilog 把 wire 和 reg 统一起来的类型。它既可以由连续赋值驱动,也可以由过程块驱动(但不能同时由两者驱动)。现代代码越来越多地到处使用 logic,不再区分 wire 和 reg。普通的 Verilog 文件仍然需要做出选择。