Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • RTL Foundations36
    • Digital Logic for RTL Engineers
    • Number Systems for RTL Engineers
    • Verilog Fundamentals
    • Verilog Operators and Types
    • SystemVerilog for RTL
      • Why SystemVerilog Improves RTL Quality
      • The `logic` Type and Assignment Model
      • always_ff and always_comb
      • typedef, enum and Robust FSM Coding
      • Interfaces and Modports
      • SystemVerilog RTL Cheat Sheet
    • Simulation and Testbenches
  • Combinational Design25
  • Sequential Design27
  • FSM & Control27
  • Timing & Clocking39
  • Reset & CDC34
  • Memory & Buses57
  • Advanced RTL28
  • Senior & Interview76
  • Glossary and Notation
Loading lesson

‹SystemVerilog for RTL
  1. Topics
  2. RTL Design
  3. RTL Foundations
  4. SystemVerilog for RTL
  5. The `logic` Type and Assignment Model

Part 1 · Foundations · Beginner

The `logic` Type and Assignment Model

The logic type, the single-driver rule, and how X and Z behave in simulation.

On this page

    Why this matters

    A 2-state bit hides a missing reset by reading 0 where logic would show x. And the conditional operator with an x select merges the two values bit by bit, while an if with x quietly takes the else branch.

    After this lesson you can: choose logic or a net for a signal, and predict how x and z behave in expressions, if conditions and conditional operators.

    What logic is

    logic is a 4-state data type: each bit holds 0, 1, x (unknown) or z (high impedance). A variable declared with it may be written by one continuous assignment or port, or by procedural assignments. Ordinary procedural processes can write the same variable; logic alone does not enforce one procedural writer. Keep one writer per RTL variable, and use always_ff/always_comb plus lint to check ownership.

    Type

    States

    Driven by

    Use for

    logic variable

    0 1 x z

    One assign/port, or procedural assignments; keep one RTL writer

    Almost every RTL variable

    wire

    0 1 x z

    Continuous drivers, possibly several

    Multi-driver nets: tri-state buses

    bit

    0 1

    Procedural

    Testbench counters and flags (never for RTL storage you need x checks on)

    int

    0 1 (2-state, 32-bit signed)

    Procedural

    Loop indices, testbench counters

    integer

    0 1 x z (4-state, 32-bit signed)

    Procedural

    Legacy Verilog code; prefer logic with an explicit width in RTL

    Warning

    2-state types such as bit and bit [31:0] cannot represent x or z. Using them for RTL signals hides uninitialized and unreset values: a missing reset reads as 0 instead of x, and a bug that simulation would have exposed passes silently. int, byte, shortint and longint are also 2-state. Use them for loop indices and testbench bookkeeping, not RTL storage; integer is the 4-state 32-bit signed type.

    Assignment restrictions and writer ownership

    two_drivers.v
    1
    2
    3
    4
    5
    // bad-example: two drivers on one logic signal
    module two_drivers (input logic a, b, sel, output logic y);
      assign y = a;                       // driver 1
      always_comb if (sel) y = b;         // illegal mixing; always_comb also forbids other writers
    endmodule

    The example is illegal because it mixes continuous and procedural assignments to the same variable, and violates always_comb writer ownership. That is different from two plain procedural processes assigning a variable: the language permits those, so simultaneous writes can race unless lint or a stronger process construct catches the mistake.

    The following standalone testbench uses two ordinary initial writers at distinct times. Its result is deterministic and demonstrates that the data type itself does not reject multiple procedural writers. This is a simulation example, not a synthesizable RTL pattern. Resolved shared buses instead need a net such as wire (or explicitly wire logic); logic describes the stored bit values, while the net kind supplies resolution.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    module tb_logic_writers;
      logic q;
      int errors = 0;
    
      initial begin
        q = 1'b0;
        #5 q = 1'b1;
      end
      initial begin
        #1 q = 1'b1;
        #2 q = 1'b0;
      end
      initial begin
        #2;
        assert (q === 1'b1) else errors++;
        #2;
        assert (q === 1'b0) else errors++;
        #2;
        assert (q === 1'b1) else errors++;
        if (errors == 0) $display("PASS: plain procedural writers update logic at separate times");
        else $display("FAIL: procedural-writer errors=%0d", errors);
        $finish;
      end
    endmodule

    X and Z in simulation

    • Every logic bit starts as `x` until assigned. An un-reset flop shows x in the waveform instead of a misleading 0.

    • x propagates through logic: x & 0 is 0, but x & 1 is x. An x in an if condition is treated as false, so simulation deterministically takes the else branch, which can hide the bug.

    • The conditional operator behaves differently. With c = 1'bx, c ? a : b does not choose a branch: it merges the two values bit by bit, keeping bits where they agree and giving x where they differ. So c ? 4'b1010 : 4'b1000 gives 10x0 (iverilog), while the same x in an if silently takes else.

    • Simulators differ on x handling (optimistic or pessimistic modes, and x-propagation options). An x condition in an if is optimistic and can hide a bug; a two-state tool such as Verilator, by default, never shows the x at all. That is why explicit x checks and assertions matter.

    • z means nothing is driving. On an internal signal it usually indicates an unconnected port.

    Tip

    Compare with === / !== in testbenches so that x and z count as differences. Self-checking and observability covers this in the testbench topic.

    Check yourself A flop has no reset and the testbench never writes it. What does logic show in simulation, and what would a bit show?

    logic shows x, flagging the missing initialization. A bit would show 0, which looks valid and hides the bug.

    Testbench

    A small testbench that checks uninitialized logic and the basic x identities.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    module tb_logic_x;
      logic a;                 // never assigned: starts as x
      logic [3:0] v = 4'b1x0z;
      int errors = 0;
      initial begin
        #1;
        if (a !== 1'bx)       errors++;      // uninitialized logic is x
        if ((1'bx & 1'b0) !== 1'b0) errors++;  // x AND 0 is known: 0
        if ((1'bx & 1'b1) !== 1'bx) errors++;  // x AND 1 stays unknown
        if (^v !== 1'bx)       errors++;      // reduction over an x bit is x
        if (errors == 0) $display("PASS: x behaves as described");
        $finish;
      end
    endmodule

    Remember

    • logic is 4-state and starts as x, so an un-reset flop shows x in the waveform.

    • logic alone does not enforce one writer. Use always_ff and always_comb with lint to check ownership.

    • An x in an if condition is false. Compare with === and !== in testbenches.

    Rule of thumb: one writer per RTL variable, and a net for anything shared.

    Keep going

    • Interview: run a mock interview on this topic

    Related topics

    • Wire vs reg

    Previous

    26 / 350

    Next: always_ff and always_comb

    Up next

    Up nextalways_ff and always_comb

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • typedef, enum and Robust FSM Coding
    • Interfaces and Modports
    • SystemVerilog RTL Cheat Sheet