Menu

Verilog wire 和 reg 的区别:何时用哪个(附示例)

Verilog 中两种主要的数据类型:用于连续连接的 wire 和用于过程存储的 reg,以及每次在两者之间做选择的规则。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

两个名字,一件事

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 文件仍然需要做出选择。

Coddy 编程语言插图

和 Coddy 一起学编程

开始