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

  • Start Here6
  • SystemVerilog Language Foundations28
  • Object-Oriented SystemVerilog for Verification30
    • Classes, Objects & Handles
    • Inheritance and Polymorphism
    • Static, Encapsulation & Parameterized Classes
      • Static Properties & Methods
      • local, protected & Encapsulation
      • Parameterized Classes
      • Constants & Enums in Classes
      • extern Methods & Class File Organization
    • Virtual Interfaces: the Class-to-Pins Bridge
    • Events, Semaphores, Mailboxes and Processes
    • OOP Patterns for Testbenches
  • Constraint Randomization — Interview-Critical Deep Dive36
  • SystemVerilog Assertions — Interview-Critical Deep Dive40
  • Functional Coverage in SystemVerilog22
  • Testbench Architecture in SystemVerilog23
  • Advanced Language & Integration25
  • Senior & Interview Preparation21
  • Principal-Level Verification28
Loading lesson

‹Static, Encapsulation & Parameterized Classes
  1. Topics
  2. SystemVerilog
  3. Object-Oriented SystemVerilog for Verification
  4. Static, Encapsulation & Parameterized Classes
  5. Parameterized Classes

Part 2 · OOP for Verification · Intermediate

Parameterized Classes

One class template stamped into distinct checked types, each with its own statics.

On this page

    Why this matters

    A counter meant to total every item read 4 when 7 were made. Each specialization of the class had its own copy of the static.

    After this lesson you can: write classes with value and type parameters, name specializations with typedefs, and predict which statics they share.

    Value and type parameters

    A parameterized class is one source definition stamped into concrete classes at each use site. Value parameters set sizes and constants, while type parameters let one container hold any payload type with full checking.

    Parameter kind

    Example

    Use

    Value parameter

    #(int DEPTH = 8)

    Queue depth, width, retry limit

    Type parameter

    #(type T = int)

    Payload type of a container or scoreboard

    Parameterized classes are not full C++ templates: there are no template functions and no specialization metaprogramming. Likewise there is no signature overloading, so sibling APIs get distinct names such as write_int and write_str.

    Specializations are distinct types

    Fifo of byte and fifo of int share source code and nothing else. Handles never cross-assign, and each specialization owns a separate copy of every static member.

    Every specialization is a class of its own, with its own statics.

    Name each specialization once with a typedef in a shared package. Every use site then agrees by construction, and width changes become one-line edits.

    Testbench

    Predict first The bench puts 4 items into a fifo #(byte, 4) and 3 into a fifo #(int, 4). What do byte_fifo_t::put_total and int_fifo_t::put_total read?

    4 and 3. The two specializations are separate classes, so each has its own static counter.

    A generic fifo exercised as two types, with per-specialization put tallies proving the statics are separate.

    top.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
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    class fifo #(type T = int, int DEPTH = 8);
      local T q[$];
      static int put_total = 0;
    
      function bit try_put(T item);
        if (q.size() >= DEPTH) return 0;
        q.push_back(item);
        put_total++;
        return 1;
      endfunction
    
      function bit try_get(output T item);
        if (q.size() == 0) return 0;
        item = q.pop_front();
        return 1;
      endfunction
    
      function int level();
        return q.size();
      endfunction
    endclass
    
    typedef fifo #(byte, 4) byte_fifo_t;
    typedef fifo #(int, 4) int_fifo_t;
    
    module top;
      byte_fifo_t bf;
      byte_fifo_t bf_alias;
      int_fifo_t wf;
      byte bb;
      int ww;
      int errors = 0;
      initial begin
        bf = new();
        wf = new();
        bf_alias = bf;   // same typedef: legal handle assignment
        for (int i = 0; i < 4; i++) begin
          if (!bf.try_put(8'h10 * (i + 1))) errors++;   // 10, 20, 30, 40
        end
        if (bf.try_put(8'hFF) !== 0) errors++;         // full: rejected
        for (int i = 0; i < 4; i++) begin
          if (!bf.try_get(bb)) errors++;
          if (bb !== 8'h10 * (i + 1)) errors++;        // order kept
        end
        if (!wf.try_put(1)) errors++;
        if (!wf.try_put(2)) errors++;
        if (!wf.try_put(3)) errors++;
        for (int i = 1; i <= 3; i++) begin
          if (!wf.try_get(ww)) errors++;
          if (ww !== i) errors++;
        end
        if (bf_alias.level() !== 0) errors++;          // alias sees the same drained queue
        if (byte_fifo_t::put_total !== 4) errors++;    // byte statics count byte puts
        if (int_fifo_t::put_total !== 3) errors++;     // int statics count int puts
        if (errors == 0) $display("PASS: byte fifo kept 4 in order and each type counted its own puts");
        else $fatal(1, "FAIL: fifo order or per-type put tallies differ from plan");
        $finish;
      end
    endmodule
    Terminal
    PASS: byte fifo kept 4 in order and each type counted its own puts

    With the typedefs from the listing above, handles of the two specializations never mix:

    design.sv
    1
    2
    3
    4
    // bad-example: distinct specializations are unrelated types (typedefs from the listing above)
    byte_fifo_t bf = new();
    int_fifo_t wf = new();
    bf = wf;   // compile error: a byte fifo is never an int fifo

    One scoreboard serves every protocol when the payload contract is explicit: the type must provide compare(). The built-in mailbox follows the same idea, with mailbox of a transaction type rejecting wrong-type puts at compile time.

    top.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
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    class pkt;
      int id;
      int unsigned data;
      function new(int i, int unsigned d);
        id = i;
        data = d;
      endfunction
      function bit compare(pkt rhs);
        return (id == rhs.id) && (data == rhs.data);
      endfunction
    endclass
    
    class frag;
      int tag;
      function new(int t);
        tag = t;
      endfunction
      function bit compare(frag rhs);
        return tag == rhs.tag;
      endfunction
    endclass
    
    class scoreboard #(type T = pkt);
      local T expected_q[$];
      local int bad;
    
      function void add_expected(T t);
        expected_q.push_back(t);
      endfunction
    
      function void check_got(T t);
        T exp;
        if (expected_q.size() == 0) begin
          bad++;
          return;
        end
        exp = expected_q.pop_front();
        if (!exp.compare(t)) bad++;   // contract: T provides compare()
      endfunction
    
      function int get_bad();
        return bad;
      endfunction
    endclass
    
    typedef scoreboard #(pkt) pkt_sb_t;
    typedef scoreboard #(frag) frag_sb_t;
    
    module top;
      pkt_sb_t psb;
      frag_sb_t fsb;
      pkt p0;
      pkt p1;
      pkt q0;
      pkt q1;
      frag f0;
      frag f_wrong;
      int errors = 0;
      initial begin
        psb = new();
        fsb = new();
        p0 = new(0, 32'hA1);
        p1 = new(1, 32'hB2);
        q0 = new(0, 32'hA1);
        q1 = new(1, 32'hB2);
        f0 = new(5);
        f_wrong = new(6);
        psb.add_expected(p0);
        psb.add_expected(p1);
        psb.check_got(q0);
        psb.check_got(q1);
        fsb.add_expected(f0);
        fsb.check_got(f_wrong);   // wrong tag: one bad check
        if (psb.get_bad() !== 0) errors++;
        if (fsb.get_bad() !== 1) errors++;
        if (errors == 0) $display("PASS: one scoreboard served two payload types with 0 and 1 bad checks");
        else $fatal(1, "FAIL: per-protocol bad tallies differ from 0 and 1");
        $finish;
      end
    endmodule
    Terminal
    PASS: one scoreboard served two payload types with 0 and 1 bad checks

    Common mistakes

    Expecting one static counter across specializations reads the wrong variable. Spelling a specialization slightly differently at two sites silently creates two types.

    Calling a method on T that some payload lacks breaks the implicit contract. Document the required methods next to the parameter list.

    Common pitfall

    A typedef is an alias, not a new type. Two identical typedefs name one specialization, which is exactly why the recipe works: agreement by construction instead of by spelling.

    Interview

    Likely questions: why parameterize a mailbox, and where does a shared counter across widths live?

    • A typed mailbox rejects wrong-type puts at compile time.

    • No static inside the template spans specializations.

    • A cross-width total lives outside the class, owned by the environment.

    Check yourself How many static put_total copies exist after using fifo of byte and fifo of int?

    Two. Each specialization owns its own statics, so the byte count and the int count advance independently.

    Check yourself Two sites write fifo of byte with different default arguments. Are the handles compatible?

    Yes when the effective parameters match. Fifo of byte with an omitted depth is the same type as fifo of byte with depth 8, but the typedef recipe removes the guessing.

    Project step

    Use the generic fifo as PacketFIFO's reference model: fifo #(pkt_txn, 4) holds packets the way the RTL holds bytes, refuses a fifth like a full FIFO, and returns them in order.

    One way to write it:

    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
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    typedef enum bit [1:0] {DATA = 2'd0, CTRL = 2'd1, ERR = 2'd2} kind_e;
    
    class pkt_txn;
      kind_e    kind;
      bit [1:0] dest;
      function new(kind_e k, bit [1:0] d);
        kind = k; dest = d;
      endfunction
    endclass
    
    class fifo #(type T = int, int DEPTH = 8);
      local T q[$];
      function bit try_put(T item);
        if (q.size() >= DEPTH) return 0;
        q.push_back(item);
        return 1;
      endfunction
      function bit try_get(output T item);
        if (q.size() == 0) return 0;
        item = q.pop_front();
        return 1;
      endfunction
    endclass
    
    // PacketFIFO's reference model: the same container, holding packets, as deep as the RTL.
    typedef fifo #(pkt_txn, 4) pkt_model_t;
    
    module tb_pkt_model;
      pkt_model_t model;
      pkt_txn     p;
      int errors = 0;
      initial begin
        model = new();
        for (int i = 0; i < 4; i++) begin
          p = new(DATA, 2'(i));
          if (!model.try_put(p)) errors++;
        end
        p = new(CTRL, 0);
        if (model.try_put(p)) errors++;                   // fifth packet: full, rejected
        for (int i = 0; i < 4; i++) begin
          if (!model.try_get(p) || p.dest != 2'(i)) errors++;
        end
        if (model.try_get(p)) errors++;                   // empty again
        if (errors == 0) $display("PASS: fifo #(pkt_txn, 4) took 4 packets, refused a fifth, and returned them in order");
        else $fatal(1, "FAIL: %0d model checks failed", errors);
        $finish;
      end
    endmodule
    Terminal
    PASS: fifo #(pkt_txn, 4) took 4 packets, refused a fifth, and returned them in order

    Remember

    • Each specialization is a separate class with its own statics.

    • Handles of different specializations never assign to each other.

    • Name each specialization once with a typedef so every site agrees.

    Rule of thumb: one template, many types: name the ones you use.

    How this was checked

    Run with Verilator 5.048 (--binary --timing) on 2026-10-18; every bench printed the PASS line shown, with no warnings. These class benches were run on Verilator only.

    Related topics

    • Next: Constants & Enums in Classes

    Previous

    46 / 259

    Next: Constants & Enums in Classes

    Up next

    Up nextConstants & Enums in Classes

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Static Properties & Methods
    • extern Methods & Class File Organization