Module 9: SystemVerilog for Design & Verification · 9.1 SystemVerilog Design Enhancements

The logic Type

Module 8 closed the classic Verilog material. This module starts on SystemVerilog, and the first thing to fix is the one Section 4.2 complained about: wire and reg are the most confusing part of classic Verilog, the names are misleading and the rules are counterintuitive. SystemVerilog’s fix is not a clearer rulebook. It is a new type, logic, that makes the choice mostly disappear.

What logic is

logic is a single data type that can be driven by either a continuous assignment or a procedural block, the two contexts that used to force you to choose between wire and reg and to remember which one each required.

// Classic Verilog: you have to pick the right type for the right context
wire combinational_result;
assign combinational_result = a & b;

reg sequential_result;
always @(posedge clk) sequential_result <= d;
// SystemVerilog: logic works in BOTH contexts
logic combinational_result;
assign combinational_result = a & b;

logic sequential_result;
always_ff @(posedge clk) sequential_result <= d;

sequential_result no longer needs to be reg and combinational_result no longer needs to be wire. Both are logic, and the tool decides from how the signal is driven whether it becomes combinational logic or a flip-flop, by the same inference rules from Module 5 that never depended on the keyword in the first place. reg never meant “register”, as Section 4.2 said, and logic finally removes the name that made people think it did.

The one case that still needs wire

logic has one restriction that wire does not: it can be driven by only one source. So logic cannot be used for a real multi-driver net, the legitimate tri-state bus case from the multi-driver article in Section 8.4, where more than one source deliberately drives the same net and z states arbitrate which one is active.

// Tri-state bus: still needs wire, because logic forbids multiple drivers
wire [7:0] shared_bus;
assign shared_bus = (driver_a_en) ? data_a : 8'bz;
assign shared_bus = (driver_b_en) ? data_b : 8'bz;

The restriction is a feature. It means a logic signal structurally cannot become the accidental multi-driver conflict from Section 8.4. That bug becomes a compile error, caught automatically rather than by careful review or a lint pass.

Converting existing RTL

Migrating classic Verilog to logic is close to mechanical for the large majority of signals:

module counter (
    input  logic       clk, rst_n, en,
    output logic [7:0] count   // was: output reg [7:0] count
);
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) count <= 8'b0;
        else if (en) count <= count + 1'b1;
    end
endmodule

Every port and every internal signal that is not a genuine multi-driver net becomes logic. The only signals that stay wire are the ones with a deliberate structural reason for multiple drivers, which in most designs is a small minority, if any exist outside specific bus-sharing structures. Nearly every SystemVerilog style guide in use makes logic the default for everything and reserves wire for the multi-driver case, for exactly the safety reason above.

Practice Exercise

Convert the following module to logic throughout, keeping exactly one signal as wire because it is genuinely tri-stated.

module bus_driver (
    input  wire       clk, rst_n, drive_en,
    input  wire [7:0] data_in,
    output wire [7:0] shared_bus,
    output reg  [7:0] status_reg
);
    assign shared_bus = drive_en ? data_in : 8'bz;
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) status_reg <= 8'b0;
        else        status_reg <= data_in;
    end
endmodule
Solution
module bus_driver (
    input  logic       clk, rst_n, drive_en,
    input  logic [7:0] data_in,
    output wire  [7:0] shared_bus,     // must stay wire: genuinely tri-stated
    output logic [7:0] status_reg      // was reg, now logic
);
    assign shared_bus = drive_en ? data_in : 8'bz;
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) status_reg <= 8'b0;
        else        status_reg <= data_in;
    end
endmodule

shared_bus stays wire because it is driven with z as part of its normal tri-state behaviour, which is the case logic’s single-driver rule excludes. Everything else, including status_reg (which was reg only because of the old naming confusion), becomes logic.

logic replaces both wire and reg for nearly every signal, ending the confusion from Section 4.2 by making the choice unnecessary. wire still exists for the deliberate multi-driver case, and logic’s single-driver rule means it cannot fall into the accidental multi-driver bug from Section 8.4. In the next article we look at always_ff, always_comb and always_latch, the blocks that make your intent explicit enough for the tool to check it.

always_ff, always_comb and always_latch →