SystemVerilog Basics

Chapter 4 of 16

Data types: the difference between logic, reg, and wire

Understand how SystemVerilog's logic type unifies and simplifies Verilog's reg/wire split, across both combinational and sequential logic, plus where each type's boundaries still apply.

In Verilog, signals must be strictly split into two data types:

  • wire: represents a connection driven by a continuous assignment (assign) or a module port; it cannot be assigned inside an always block.
  • reg: represents a variable assigned inside a procedural block (always/initial). Despite the name "reg", it does not necessarily synthesize to a register — combinational always blocks use reg too.

This distinction often confuses beginners: reg doesn't mean a hardware register, it just means "a variable assigned by a procedural block."

Where this split comes from: continuous vs. procedural assignment

The wire/reg split is really a split by assignment style, not by whether a signal is a register:

  • Continuous assignment (an assign statement) describes a wire that continuously tracks the expression on its right-hand side; its target must be declared wire.
  • Procedural assignment (an assignment inside an initial/always block) describes "store this value into a variable when some event happens"; its target must be declared reg.

The two can't be mixed — the compiler will flag it directly:

wire w;
initial begin
  w = 1'b1;  // ✗ compile error: wire cannot be assigned inside a procedural block
end
 
reg r;
assign r = 1'b1;  // ✗ compile error: reg cannot be the target of a continuous assignment

In other words, plain Verilog forces you to decide upfront whether you'll describe a signal with assign or with a procedural block, and declare it wire or reg accordingly — a purely syntactic burden that has nothing to do with whether the signal ends up as a real register.

SystemVerilog's logic type

SystemVerilog introduces the logic type to simplify this. logic can be assigned inside a procedural block just like reg, and can also be connected to a port just like wire, as long as it isn't driven by multiple drivers at once (it can't be used where multiple drivers are needed, such as a tri-state bus — that still requires wire). Like wire and reg, logic is also a 4-state type (it can represent 0, 1, x, and z) — the next chapter goes deeper into the difference between 2-state and 4-state data types.

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

In the example above, y is of type logic — it's both a module output port and is assigned inside an always_comb block. That combination isn't allowed in plain Verilog (ports can only be declared wire, unless explicitly overridden with reg, and combinational outputs need extra care).

logic works for sequential logic too

The example above is combinational logic (always_comb). logic works just as well in sequential logic (always_ff), playing the role reg used to — here's a simple D flip-flop with an asynchronous reset:

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

Here q is logic too: it's an output port, and it's also assigned inside always_ff using <= (a non-blocking assignment) — a later chapter on procedural blocks goes into the difference between blocking (=) and non-blocking (<=) assignment in detail; for now, just remember sequential logic conventionally uses <=. Together with mux2 above, this shows that whether you're writing combinational or sequential logic, declaring things logic covers both — no more picking between wire and reg.

The short version, if you're curious now: <= schedules its update to happen only after every block has finished evaluating with this time step's starting values, so it doesn't matter which always_ff block the simulator happens to run first — everyone reads the same old values, matching how real hardware updates all its registers "at once" on a clock edge. Using = (blocking) here could make the result depend on simulator execution order, a mismatch between simulation and real hardware. Chapter 8 covers exactly how that scheduling works.

When you still need wire

When a signal needs to be driven by multiple drivers (for example a tri-state bus driven by several modules), you must use wire (or a more explicit wire variant such as tri), because logic only allows a single driver — multiple drivers will cause a compile or simulation error. A typical case is several devices sharing the same bus lines, where at most one device is "enabled" to drive them at any given time and the rest output high-impedance z:

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

If several instances of tristate_buf have their data_out tied to the same wire, as long as the surrounding logic/protocol guarantees at most one enable is high at a time, the shared wire never gets driven to conflicting values at once — exactly the scenario wire allows and logic doesn't.

Comparing the three types

Propertywirereg (plain Verilog)logic (SystemVerilog)
Assignable inside a procedural block (always/initial)?NoYesYes
Can be the target of a continuous assignment (assign)?YesNoYes (single driver only)
Can be used directly as a module output port type?YesNot in plain VerilogYes
Supports multiple drivers (e.g. a tri-state bus)?YesN/ANo — falls back to wire
4-state (0/1/x/z)?YesYesYes

Summary

  • The wire/reg split is really "continuous vs. procedural assignment," unrelated to whether a signal is a real register.
  • logic can replace reg and, in most cases, wire — for both combinational (always_comb) and sequential (always_ff) logic.
  • You only need wire when you genuinely need multiple drivers (such as a tri-state bus).
  • Using always_comb and always_ff instead of plain always lets tools catch common mistakes like unintended latches at compile time.

Which statement accurately describes the logic type in SystemVerilog?

In plain Verilog, which of the following causes a compile error?

Which data type should you use when multiple modules need to drive the same tri-state bus? (lowercase English word)