SystemVerilog 基础

第 4 章 · 共 16 章

数据类型:logic、reg、wire 的区别

理解 SystemVerilog 中 logic 类型如何统一并简化 Verilog 的 reg/wire 划分,覆盖组合逻辑与时序逻辑两种场景,以及三种类型各自的适用边界。

在 Verilog 中,信号必须严格区分为两类数据类型:

  • wire:表示由连续赋值语句(assign)或模块端口驱动的连线,不能在 always 块中被赋值。
  • reg:表示在过程块(always/initial)中被赋值的变量,尽管名字带有 "reg",它不一定综合成寄存器——组合逻辑的 always 块同样使用 reg

这个区分经常让初学者困惑:reg 不代表硬件寄存器,只是"由过程块赋值的变量"。

这个区分从哪来:连续赋值 vs. 过程赋值

wire/reg 的区分本质上是赋值方式的区分,而不是"这个信号是不是寄存器":

  • 连续赋值assign 语句)描述的是一根线随时跟随右边表达式变化,目标必须声明为 wire
  • 过程赋值initial/always 块内的赋值)描述的是"在某个事件发生时,把某个值存进变量",目标必须声明为 reg

两者不能混用,编译器会直接报错:

wire w;
initial begin
  w = 1'b1;  // ✗ 编译错误:wire 不能在过程块中被赋值
end
 
reg r;
assign r = 1'b1;  // ✗ 编译错误:reg 不能作为连续赋值的目标

也就是说,在纯 Verilog 里,你得先想清楚"我要用 assign 还是过程块来描述这个信号",再决定它该声明成 wire 还是 reg——这是一种语法层面的心智负担,和这个信号最终会不会变成真实寄存器其实没有必然联系。

SystemVerilog 的 logic 类型

SystemVerilog 引入了 logic 类型来简化这一点。logic 可以像 reg 一样在过程块中赋值,也可以像 wire 一样被端口连接,只要它不是被多个驱动源同时驱动(即不能用于需要多重驱动的场景,比如三态总线,那种情况仍需使用 wire)。和 wire/reg 一样,logic 也是 4 态类型(可以表示 01xz)——下一章会更深入讨论 2 态与 4 态数据类型的区别。

module mux2 (
  input  logic sel,
  input  logic a,
  input  logic b,
  output logic y
);
 
  always_comb begin
    y = sel ? b : a;
  end
 
endmodule

上面的例子中,ylogic 类型,既是模块输出端口,又在 always_comb 块中被赋值——这在纯 Verilog 中是不允许的(端口只能用 wire 声明,除非显式用 reg 覆盖,且组合逻辑输出还需格外小心)。

logic 同样适用于时序逻辑

上面的例子是组合逻辑(always_comb)。logic 同样可以用在时序逻辑(always_ff)里,扮演过去 reg 的角色——一个简单的带异步复位的 D 触发器:

module dff_async_rst (
  input  logic clk,
  input  logic rst_n,
  input  logic d,
  output logic q
);
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n)
      q <= 1'b0;
    else
      q <= d;
  end
 
endmodule

这里 q 同样是 logic:既是输出端口,又在 always_ff 块中通过 <=(非阻塞赋值)被赋值——后面"过程块"一章会详细展开阻塞赋值 = 和非阻塞赋值 <= 的区别,这里先记住时序逻辑习惯用 <= 即可。这个例子和前面的 mux2 一起说明:不管是组合逻辑还是时序逻辑,声明成 logic 都能满足需求,不用再纠结该选 wire 还是 reg

如果现在就好奇,这里给一个简短版本<= 会把更新推迟到所有过程块都用这个时间步刚开始时的值算完之后才真正生效,所以仿真器先跑哪个 always_ff 块并不重要——大家读到的都是同一份旧值,这和真实硬件里所有寄存器在时钟边沿"同时"更新是一致的。如果这里用 =(阻塞赋值),结果就可能跟仿真器碰巧先算哪个块有关——这正是仿真和真实硬件行为对不上的一类经典问题。第 8 章会讲清楚这套调度到底是怎么运作的。

什么时候仍然需要 wire

当一个信号需要被多个驱动源驱动(例如三态总线,多个模块共同驱动同一根线),必须使用 wire(或更明确的 wire 变体,如 tri),因为 logic 类型只允许单一驱动源,多重驱动会导致编译或仿真报错。典型场景是总线上多个设备共享同一组信号线,同一时刻只有一个设备被"使能"去驱动它,其余设备则输出高阻态 z

module tristate_buf (
  input  wire data_in,
  input  wire enable,
  output wire data_out
);
 
  assign data_out = enable ? data_in : 1'bz;
 
endmodule

如果有多个这样的 tristate_buf 实例的 data_out 接到同一根线上,只要软件/协议保证任意时刻至多一个 enable 为高,这根共享线就不会被同时驱动成冲突的值——这正是 wire 允许而 logic 不允许的场景。

三种类型的对比

特性wirereg(纯 Verilog)logic(SystemVerilog)
可否在过程块(always/initial)中赋值
可否作为连续赋值(assign)的目标是(仅限单一驱动源)
可否直接作为模块输出端口类型纯 Verilog 中不行
是否支持多重驱动(如三态总线)不适用否,需要退回 wire
是否 4 态(0/1/x/z

小结

  • wire/reg 的区分本质是"连续赋值 vs. 过程赋值",和信号是否为真实寄存器无关。
  • logic 几乎可以替代 reg 和大多数场景下的 wire,无论组合逻辑(always_comb)还是时序逻辑(always_ff)都能用。
  • 只有在真正需要多重驱动(如三态总线)时,才需要使用 wire
  • 使用 always_combalways_ff 代替普通 always,可以让工具在编译期检查出组合逻辑闭锁(latch)等常见错误。

以下哪种说法准确描述了 SystemVerilog 中的 logic 类型?

在纯 Verilog 中,下面哪种写法会导致编译错误?

在需要多个模块共同驱动同一根三态总线时,应该使用哪种数据类型?(英文小写)