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. SystemVerilog RTL Cheat Sheet

Part 1 · Foundations · Beginner

SystemVerilog RTL Cheat Sheet

One page to keep open while coding RTL: the five constructs that prevent most bugs, then every everyday construct with its meaning, its trap and the lesson that teaches it.

On this page

    Why this matters

    A signed -8 compared with an unsigned 200 comes out not smaller, and 200 + 100 in eight bits is 44, with no warning. Most RTL bugs are a construct doing exactly what the language says, which a one-page reference catches before simulation does.

    After this lesson you can: pick the right SystemVerilog construct for each RTL job, name the trap in each one, and find the lesson that teaches it in depth.

    Five constructs that prevent most RTL bugs

    Two lines from the run further down this page: a signed -8 compared with an unsigned 200 is reported as not smaller, and 200 + 100 stored in eight bits is 44. Neither is a tool bug. The language did exactly what the code said, and the code said something the writer did not mean.

    This page is a reference you can keep open while you write RTL. Every row gives the construct, what it means, the trap, and the lesson that teaches it properly. Start with the five constructs below: used together they turn the most common RTL mistakes into compile errors, lint reports or visible x values.

    Construct

    What it prevents

    Lesson

    always_ff @(posedge clk ...)

    A block that was meant to be a flop but is not, and a second block writing the same register.

    always_ff and always_comb

    always_comb with defaults first

    Accidental latches and stale sensitivity lists.

    always_ff and always_comb

    logic

    Mixed continuous and procedural drivers; an unreset flop shows x, not a quiet 0.

    The logic type

    typedef enum logic [N-1:0]

    Raw numbers reaching a state register; states show by name in the waveform.

    Enums and robust FSMs

    unique case or priority case with default

    Unstated assumptions about overlapping or missing branches, and latches from a missing value.

    Latch avoidance and case semantics

    Here are all five in one small block. Use it as the starting shape for a control block: storage in always_ff, logic in always_comb with defaults at the top, typed states, and a default branch for the encoding nobody uses.

    five_habits.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
    25
    26
    27
    28
    29
    30
    31
    // yosys: ok
    `default_nettype none
    module five_habits (
      input  logic       clk, rst_n,
      input  logic       start, done_in,
      input  logic [7:0] din,
      output logic       busy,
      output logic [7:0] dout
    );
      typedef enum logic [1:0] {IDLE, RUN, HOLD} state_e;   // 2'b11 unused
      state_e state, next;
    
      always_ff @(posedge clk or negedge rst_n)              // storage: <= only
        if (!rst_n) state <= IDLE;
        else        state <= next;
    
      always_ff @(posedge clk)                               // datapath: no reset needed
        if (state == RUN) dout <= din;
    
      always_comb begin                                      // logic: defaults first
        next = state;
        busy = 1'b0;
        unique case (state)
          IDLE:    if (start) next = RUN;
          RUN:     begin busy = 1'b1; if (done_in) next = HOLD; end
          HOLD:    next = IDLE;
          default: next = IDLE;                              // 2'b11: recover
        endcase
      end
    endmodule
    `default_nettype wire

    Read it top to bottom. The default_nettype none line makes a misspelt signal name a compile error instead of a new one-bit wire. The enum gives the register a type, so state <= 2 will not compile. The first always_ff is the only writer of state. The second has no reset, because dout is written before anyone reads it; What a reset costs measures what that saves. In always_comb, next = state and busy = 1'b0 come before the case, so every path assigns both and no latch can appear.

    Declarations and types

    Construct

    Meaning

    Trap

    Lesson

    logic [7:0] a;

    A 4-state vector with one writer.

    bit, int and byte are 2-state: a missing reset reads as 0, not x.

    The logic type

    logic [3:0][7:0] w;

    Packed: four bytes forming one 32-bit vector; w[0] is the low byte.

    logic [7:0] m [4] is unpacked: an array of four bytes, not a number.

    Bit vectors and slicing

    typedef ... name_t;

    Names a type once.

    Two modules that each declare the same typedef get two types; share it from a package.

    Packages and compile order

    typedef enum logic [1:0] {...}

    Named, typed values.

    Without the base type the enum is a 2-state 32-bit int. Unused encodings still exist in the flops.

    Enums and robust FSMs

    typedef struct packed {...}

    Named fields over one vector.

    The first field listed is the most significant.

    Packed structs and unions

    parameter / localparam

    Set per instance / derived inside.

    A derived width declared as parameter can be overridden to a wrong value.

    Parameters vs localparams

    $clog2(N)

    Bits needed to index N items.

    $clog2(1) is 0, a zero-width vector. Guard it: (N > 1) ? $clog2(N) : 1.

    Parameterized, type-safe design

    '0, '1, 'x

    Fill every bit of the target.

    A fixed 8'hFF stays 8 bits when the register grows.

    Why SystemVerilog improves RTL

    Processes and assignments

    Construct

    Meaning

    Trap

    Lesson

    always_ff

    Edge-triggered storage.

    Put the reset in the event list only for an asynchronous reset.

    always_ff and always_comb

    always_comb

    Combinational logic; sensitivity is automatic.

    A path that does not assign an output still means storage. Defaults first.

    always_ff and always_comb

    always_latch

    A latch on purpose.

    Almost never wanted in synchronous RTL; comment why it is there.

    Intentional latches

    <= in always_ff, = in always_comb

    Flops update together; logic computes in order.

    = in a clocked block lets a later block read the new value: a race.

    Blocking vs non-blocking

    assign y = ...;

    A continuous driver.

    A second driver of the same logic is a compile error, which is the point.

    assign vs always

    The blocking rule fits on one line: flops get <=, logic gets =, and one block owns each variable. Most race and latch bugs break one of those three.

    Operators that bite

    Four operator rules cause most arithmetic surprises: result width, signedness, shift direction and the unknown value. The bench below prints one result for each. It is a single module with no design under test, so you can paste it into any simulator.

    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
    25
    26
    27
    28
    29
    30
    31
    32
    module tb_ops_bite;
      logic        [7:0] u8 = 8'd200;
      logic signed [7:0] s8 = -8'sd8;            // bit pattern 1111_1000
      logic        [7:0] a  = 8'd200, b = 8'd100;
      logic        [7:0] sum8;
      logic        [8:0] sum9;
      logic              x1 = 1'bx;
      int errors = 0;
      initial begin
        sum8 = a + b;                            // 8-bit target keeps 8 bits
        sum9 = a + b;                            // 9-bit target keeps the carry
        $display("1  s8 < u8        -> %b", s8 < u8);   // mixed: compare is unsigned
        $display("2  s8 >> 1        -> %0d", s8 >> 1);  // logical: fills with 0
        $display("3  s8 >>> 1       -> %0d", s8 >>> 1); // arithmetic: copies the sign
        $display("4  sum8 = a + b   -> %0d", sum8);
        $display("5  sum9 = a + b   -> %0d", sum9);
        $display("6  x1 == 1'b1     -> %b", x1 == 1'b1);
        $display("7  x1 === 1'bx    -> %b", x1 === 1'bx);
        if (x1 == 1'b1) $display("8  if (x1 == 1)   -> then branch");
        else            $display("8  if (x1 == 1)   -> else branch");
        if ((s8 < u8) !== 1'b0)      errors++;
        if ((s8 >> 1) !== 8'sd124)   errors++;
        if ((s8 >>> 1) !== -8'sd4)   errors++;
        if (sum8 !== 8'd44)          errors++;
        if (sum9 !== 9'd300)         errors++;
        if ((x1 == 1'b1) !== 1'bx)   errors++;
        if ((x1 === 1'bx) !== 1'b1)  errors++;
        if (errors == 0) $display("PASS: all eight results as described");
        else $fatal(1, "FAIL: %0d unexpected results", errors);
        $finish;
      end
    endmodule
    Diagram
    1  s8 < u8        -> 0
    2  s8 >> 1        -> 124
    3  s8 >>> 1       -> -4
    4  sum8 = a + b   -> 44
    5  sum9 = a + b   -> 300
    6  x1 == 1'b1     -> x
    7  x1 === 1'bx    -> 1
    8  if (x1 == 1)   -> else branch
    PASS: all eight results as described

    Read the output one line at a time.

    • Line 1. An expression is signed only when every operand is signed. u8 is unsigned, so s8 is read as the unsigned pattern 248, and 248 < 200 is false.

    • Lines 2 and 3. >> always fills with zeros, so the pattern 1111_1000 becomes 0111_1100, which is 124. >>> copies the sign bit, giving -4, but only because s8 is declared signed; on an unsigned operand >>> also fills with zeros.

    • Lines 4 and 5. The addition is as wide as its widest operand or target. An 8-bit target drops the carry: 300 - 256 = 44. A 9-bit target keeps it.

    • Lines 6 to 8. == with an x operand gives x. An if treats x as false and takes the else branch, so simulation shows a clean value that silicon need not produce. === compares x literally; it belongs in testbenches, not RTL.

    Operator

    Meaning

    Trap

    Lesson

    a + b, a * b

    Width of the widest operand or target.

    A narrow target drops the carry (line 4).

    Width discipline

    signed, $signed(), $unsigned()

    How the bits are read.

    One unsigned operand makes the whole expression unsigned (line 1).

    Signed arithmetic pattern

    >> / >>>

    Logical / arithmetic right shift.

    >>> keeps the sign only on a signed operand (lines 2 and 3).

    Shift and arithmetic intent

    &a, |a, ^a

    Reduce a vector to one bit.

    !a is logical (is it zero?); ~a inverts every bit.

    Reduction and logical operators

    {a, b}, {4{a}}

    Concatenate, replicate.

    The result is unsigned and self-determined: {a + b} adds at the operands' width and loses the carry.

    Concatenation and replication

    == / ===

    Logical equality / 4-state identity.

    === is simulation only; synthesis may reject it or treat it as == (lines 6 to 8).

    Reduction and logical operators

    Sign extension

    Widening a signed value.

    Concatenating a sign bit gives an unsigned result; cast it back.

    Sign extension and casting

    Case forms

    Construct

    Meaning

    Trap

    Lesson

    case

    First matching item wins.

    No default and a value not listed: the output holds, which is a latch in always_comb.

    Latch avoidance and case semantics

    unique case

    Exactly one item matches.

    The check runs only in a simulator that implements it; Icarus 13 says it ignores the keyword.

    Latch avoidance and case semantics

    priority case

    First match wins, and at least one matches.

    Synthesis may remove the logic for the no-match case; keep the promise exact.

    Latch avoidance and case semantics

    default

    Every value not listed.

    In a unique case it also switches off the no-match report.

    always_ff and always_comb

    case (x) inside

    Items may be ranges [0:3] or contain ? wildcards.

    Only the item's ?, x and z bits are wildcards, never the selector's. Prefer it to casez.

    Mux theory and patterns

    casez

    z or ? bits are don't-care.

    A z in the selector also matches, so a floating input picks a branch.

    Mux theory and patterns

    The Icarus message is worth knowing. Compiling the five_habits block above prints vvp.tgt sorry: Case unique/unique0 qualities are ignored. The design still simulates, but no overlap or no-match check runs. If your flow depends on those checks, run the block in a simulator or lint tool that implements them.

    Generate, packages and interfaces

    Construct

    Meaning

    Trap

    Lesson

    for (genvar i = 0; i < N; i++) begin : g_lane

    Builds N copies at elaboration.

    Name every block. Unnamed ones get tool-made names such as genblk1 in hierarchy paths and constraints.

    Generate loops and conditionals

    if (P) begin : g_opt ... end

    Builds one option or the other.

    The condition must be a constant, normally a parameter.

    Generate loops and conditionals

    package p; ... endpackage and import p::*;

    Shared types, parameters and functions.

    The package must compile before every file that imports it.

    Packages and compile order

    interface with modport

    A bundle of signals with directions per side.

    Support differs across synthesis, lint and CDC tools; try the whole flow first.

    Interfaces and modports

    Do not use in RTL

    Each of these simulates in one way and builds in another, or hides a bug the tools would otherwise report.

    Construct

    Why not

    Use instead

    Lesson

    initial in synthesisable code

    An ASIC has no power-up values; most ASIC flows ignore or reject it.

    A reset. The exception is FPGA register or ROM contents, which the bitstream loads.

    ROM inference

    #5 delays

    Synthesis ignores them, so simulation timing differs from the hardware.

    Clocked logic; delays only in testbenches.

    Zero-delay simulation

    casex

    An x in the selector matches any item, so an unknown picks a branch.

    case inside, or casez with care.

    Mux theory and patterns

    Implicit nets

    A misspelt name becomes a new one-bit wire.

    default_nettype none at the top of each file, wire at the end.

    Modules and ports

    full_case / parallel_case pragmas

    Synthesis obeys them, simulation ignores them.

    unique or priority with a default.

    Latch avoidance and case semantics

    === and !==

    Hardware has no x to compare.

    == in RTL; === in testbenches.

    Reduction and logical operators

    Common mistakes

    Common pitfall

    Mixing one unsigned operand into a signed expression. $signed(a) < b with b unsigned is an unsigned compare, exactly as in line 1 of the run, and nothing warns. Declare both operands signed, or cast both, and test with a negative value.

    Check yourself logic signed [7:0] s = -8'sd8; What do s >> 2 and s >>> 2 give, read as signed decimals?

    s >> 2 fills with zeros: 1111_1000 becomes 0011_1110, which is 62. s >>> 2 copies the sign bit: 1111_1110, which is -2. The second answer holds only because s is declared signed.

    Check yourself A reviewer finds casex (opcode) and if (mode === 2'b01) in a synthesisable block. What do you change, and why?

    Replace casex with case (opcode) inside, so an unknown opcode does not match a wildcard item. Replace === with ==: hardware has no x to compare, and synthesis may reject === or treat it as ==, which hides the difference from simulation.

    Remember

    • always_ff for storage with <=, always_comb with defaults first and =, logic, typed enums, and unique or priority case with a default.

    • One unsigned operand makes the expression unsigned, a narrow target drops the carry, and >>> keeps the sign only on a signed operand.

    • Keep initial, # delays, casex, implicit nets, full_case pragmas and === out of synthesisable code.

    Rule of thumb: if the code depends on a width, a sign or an unknown, write it explicitly and test it with a value that would expose it.

    How this was checked

    Tool: Icarus Verilog 13.0, run on 2026-10-17. Command: iverilog -g2012 -o ops_bite.vvp ops_bite.sv && vvp -n ops_bite.vvp, where ops_bite.sv is the tb_ops_bite module on the page. Result: the eight result lines and the PASS line shown on the page, copied unedited.

    The five_habits block was compiled with iverilog -g2012 (which prints: vvp.tgt sorry: Case unique/unique0 qualities are ignored.), synthesised with Yosys 0.69+post (synth -top five_habits) with no warnings and no latch, and linted with Verilator 5.048 --lint-only -Wall (only a file-name warning). The shift answer in the first check-yourself (62 and -2) was confirmed with a one-line Icarus run.

    Scope and limits: the table rows summarise the linked lessons and IEEE 1800 rules; they were not each run here. Synthesis support for interfaces, case inside and === differs between tools.

    Keep going

    • Practice: FSM design with enums and unique/priority case

    • Practice: Incomplete case and unique0: simulation versus synthesis

    • Code lab: Build a signed comparator

    • Interview: run a mock interview on this topic

    Related topics

    • always_ff and always_comb

    • X-propagation discipline

    Previous

    30 / 350

    Next: Simulation and Testbenches

    Up next

    Up nextSimulation and Testbenches

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Why SystemVerilog Improves RTL Quality
    • The `logic` Type and Assignment Model
    • always_ff and always_comb