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
  • Combinational Design25
  • Sequential Design27
  • FSM & Control27
  • Timing & Clocking39
  • Reset & CDC34
  • Memory & Buses57
  • Advanced RTL28
    • Parameterization and Generate Hub
    • Top-Level Integration Hub
    • Synthesis Intent Hub
    • Style and Static Checks Hub
    • Assertions in RTL Hub
    • Formal Verification Basics Hub
    • Reusable SystemVerilog for RTL
      • Packages and Compile Order
      • Packed Structs and Unions
      • Parameter Types and Guards
      • Functions and Tasks
      • Interfaces, Modports, and Portability
      • Compile-Order and Package Errors, Decoded
      • Mini Project: a Reusable FIFO IP
  • Senior & Interview76
  • Glossary and Notation
Loading lesson

‹Reusable SystemVerilog for RTL
  1. Topics
  2. RTL Design
  3. Advanced RTL
  4. Reusable SystemVerilog for RTL
  5. Parameter Types and Guards

Part 8 · Advanced · Advanced

Parameter Types and Guards

Parameterize data type as well as dimensions, derive widths once, and fail illegal configurations at elaboration.

On this page

    Why this matters

    A parameterised module with no checked legal range quietly accepts bad values: STAGES = 0 gives a [0:-1] array whose output is never written. One simulator shows X, another 0, and neither warns.

    After this lesson you can: guard illegal parameter values with an elaboration check, pass a type as a parameter, and choose which configurations to test.

    The problem: a reuse at STAGES = 0

    A delay pipeline is written for STAGES = 2. Later, a block that needs no delay reuses it with STAGES = 0. Nothing in the module rejects that value, so the storage range becomes [0:-1], which is two entries, not none. The output reads entry -1, which nothing ever writes.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    // snippet: the unguarded body, instantiated with STAGES = 0
    T stage_q [0:STAGES-1];            // [0:-1]: two entries
    always_ff @(posedge clk) begin
      stage_q[0] <= data_i;
      for (int i = 1; i < STAGES; i++) stage_q[i] <= stage_q[i-1];
    end
    assign data_o = stage_q[STAGES-1]; // stage_q[-1]: never written

    Simulated with an 8-bit type and data_i = 5a for four cycles, the two simulators disagree, and neither warns:

    Diagram
    Icarus:    STAGES=0: data_i=5a data_o=xx
    Verilator: STAGES=0: data_i=5a data_o=00

    The bug surfaces far from its cause: X in one simulator, zeros in the other, and whatever the synthesis tool decides in silicon.

    The guard

    The fix checks the parameter before any storage is declared. An illegal value now stops the build with a message that names the module and the rule.

    pipeline.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    module pipeline #(
      parameter type T = logic [31:0],
      parameter int STAGES = 2
    ) (
      input  logic clk,
      input  T     data_i,
      output T     data_o
    );
      if (STAGES < 1) begin : g_bad_stages
        $error("pipeline: STAGES must be at least 1");  // elaboration-time check
        assign data_o = 'x;                              // keeps the bad build well-formed
      end else begin : g_pipeline
        T stage_q [0:STAGES-1];
        always_ff @(posedge clk) begin
          stage_q[0] <= data_i;
          for (int i = 1; i < STAGES; i++) stage_q[i] <= stage_q[i-1];
        end
        assign data_o = stage_q[STAGES-1];
      end
    endmodule
    • parameter type T passes a whole data type: width, signedness and any packed struct layout travel together.

    • if (STAGES < 1) at module scope is a generate-if: it is decided while the design is built, not while it runs.

    • $error inside that branch is an elaboration system task, so the build stops before any simulation or synthesis.

    • assign data_o = 'x gives the illegal branch a driver, so the only message is the one you wrote.

    • g_pipeline holds the real storage, declared only when STAGES is legal.

    With STAGES = 0 the same instance now fails to build:

    Diagram
    Icarus:
    ERROR: pipeline.sv:10: pipeline: STAGES must be at least 1
           During elaboration  Scope: g_bad_stages
    
    Verilator:
    %Warning-USERERROR: pipeline.sv:10:5: pipeline: STAGES must be at least 1

    Legal and illegal configurations

    Configuration

    Result

    Why

    STAGES = 0 or negative

    Build stops with the guard message

    No storage range exists; unguarded it was [0:-1]

    STAGES = 1

    Legal: one register, one cycle of latency

    Minimum value; the copy loop runs zero times

    STAGES = 2 (default)

    Legal: two cycles of latency

    The configuration the module was written for

    STAGES = 3

    Legal: three cycles of latency

    A non-default value; test it as well

    T = logic [7:0] or a packed struct

    Legal

    Any packed type with a fixed width

    Test at least the minimum, a normal value and one more. Each row above is one elaboration of the same source.

    Rules

    • Use localparam for derived values callers must not override.

    • Guard zero/one values before $clog2 or part-select expressions.

    • Test minimum, non-power-of-two, and normal configurations.

    • Do not use defparam; named parameter overrides keep hierarchy ownership visible.

    Warning

    Limits: a module-scope $error inside a generate-if is diagnosed at elaboration in tools that support elaboration system tasks. A procedural initial $fatal instead runs at simulation time zero, and whether a synthesis tool diagnoses it must be checked in that flow. FPGA synthesis can support register or memory initialisation, whereas ASIC initialisation usually needs an explicit reset; initialisation support does not imply support for procedural diagnostics.

    Common pitfall

    A parameterized module is not reusable until its legal range is documented and checked. An intended zero width can instead produce a legal but unintended range: [-1:0] contains two bits. Invalid dimensions often fail far from the real cause.

    Check yourself Why use a type parameter instead of separate width and signed parameters?

    The type carries width, signedness, and packed structure together, preventing callers from choosing inconsistent combinations.

    Check yourself The unguarded pipeline is built with STAGES = 0. Why does one simulator show xx and another 00?

    The output reads stage_q[-1], an entry nothing writes. Its value is the simulator's default for an unwritten variable: X in a four-state simulator, 0 in a two-state one. Neither is a design decision, which is why the guard rejects the configuration.

    Remember

    • Guard zero and one values before $clog2, ranges or part-selects, and use a localparam for derived values.

    • A module-scope $error inside a generate-if stops the build at elaboration; an initial $fatal runs only at simulation time zero.

    • Test the minimum, a normal value and one more configuration.

    Rule of thumb: a parameterised module is not reusable until its legal range is documented and checked.

    How this was checked

    Unguarded STAGES = 0 run for four cycles with data_i = 5a: Icarus Verilog 13.0 printed data_o = xx and Verilator 5.048 printed 00 (2026-10-17); Verilator lint with -Wall reported nothing about the range. The guarded module stopped at elaboration in both tools with the messages shown.

    Keep going

    • Interview: run a mock interview on this topic

    Related topics

    • Parameterization and generate

    • Functions and tasks

    Previous

    269 / 350

    Next: Functions and Tasks

    Up next

    Up nextFunctions and Tasks

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Packages and Compile Order
    • Interfaces, Modports, and Portability
    • Compile-Order and Package Errors, Decoded