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. Constants & Enums in Classes

Part 2 · OOP for Verification · Intermediate

Constants & Enums in Classes

Frozen per-object values, enum kind fields and one shared per-kind lookup table.

On this page

    Why this matters

    A transaction's kind field held 3, a value no protocol defined, and every check passed it along. An enum field would have refused it at compile time.

    After this lesson you can: freeze per-object and class-wide values with const, type kind fields as enums, and keep per-kind facts in one table.

    Two flavors of const

    A const property is write-once. A static const is a class-level constant initialized at its declaration, identical for every object with no per-object storage.

    An instance const is instead assigned once in the constructor. Each object carries its own frozen value, which models attributes fixed at birth such as a port number.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    // bad-example: instance const has exactly one legal assignment site
    const int port_id;
    function new(int port);
      port_id = port;   // legal: the one allowed assignment
    endfunction
    function void set_port(int x);
      // port_id = x;   // compile error: const after construction
    endfunction

    Enum kind fields

    A kind field as raw bits accepts encodings that no protocol defines. An enum field rejects bare integers at compile time, randomizes only over named values and logs them with name().

    Define the shared enum once in a package so drivers, monitors and scoreboards agree. Prefixed literals such as BUS_READ keep the package namespace clean.

    Raw bits

    Enum field

    kind = 3 compiles silently

    kind = 3 is a compile error without a cast

    kind = 1 needs a comment

    kind = BUS_WRITE documents itself

    Display prints 1

    kind.name() prints BUS_WRITE

    An enum field also keeps randomization honest: the solver picks only named values. Weighting them with dist is taught in dist: Weighted Distributions.

    Testbench

    Predict first t0 is a BUS_WRITE and t1 a BUS_READ. What do t0.expected_latency() and bus_txn::latency_of(BUS_IDLE) return?

    2 and 1. Both read the same per-kind facts: the table maps BUS_WRITE to 2, and the function's case maps BUS_IDLE to 1.

    Frozen port ids, a class-wide burst limit, directed enum values, and the per-kind latency written two ways: a static function and a static table.

    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
    package bus_pkg;
      typedef enum bit [1:0] { BUS_READ, BUS_WRITE, BUS_IDLE } kind_e;
      parameter int MAX_BURST = 8;
    endpackage
    
    class bus_txn;
      // No import inside the class: IEEE 1800 (26.3) makes an import directly
      // in a class scope illegal, so package names use bus_pkg:: instead.
      const int port_id;   // per-object: set once in new(), then frozen
      bus_pkg::kind_e kind;
      bit [31:0] addr;
    
      function new(int port, bus_pkg::kind_e k, bit [31:0] a);
        port_id = port;
        kind = k;
        addr = a;
      endfunction
    
      // One shared table as data: enum value to expected-latency cycles.
      static const int latency_tbl[bus_pkg::kind_e] = '{
        bus_pkg::BUS_READ  : 4,
        bus_pkg::BUS_WRITE : 2,
        bus_pkg::BUS_IDLE  : 1
      };
    
      function int expected_latency();
        return latency_tbl[kind];   // replaces a case statement
      endfunction
    
      // The same table as code: one function instead of a case in every method.
      static function int latency_of(bus_pkg::kind_e k);
        case (k)
          bus_pkg::BUS_READ:  return 4;
          bus_pkg::BUS_WRITE: return 2;
          bus_pkg::BUS_IDLE:  return 1;
          default:   return 1;
        endcase
      endfunction
    endclass
    
    module top;
      import bus_pkg::*;
      bus_txn t0;
      bus_txn t1;
      int errors = 0;
      initial begin
        t0 = new(0, BUS_WRITE, 32'h1000);
        t1 = new(1, BUS_READ, 32'h2003);
        if (t0.port_id !== 0) errors++;
        if (t1.port_id !== 1) errors++;
        // t0.port_id = 9;  // COMPILE ERROR: const after construction
        if (bus_pkg::MAX_BURST !== 8) errors++;
        if ({1'b0, t1.addr[3:0]} >= bus_pkg::MAX_BURST) errors++;   // 3 < 8: inside limit
        // t0.kind = 3;     // COMPILE ERROR: enum takes literals or kind_e'(3)
        if (bus_txn::latency_of(t0.kind) !== 2) errors++;
        if (bus_txn::latency_of(BUS_READ) !== 4) errors++;
        if (bus_txn::latency_of(BUS_IDLE) !== 1) errors++;
        if (t0.expected_latency() !== 2) errors++;
        if (t1.expected_latency() !== 4) errors++;
        if (t1.kind.name() != "BUS_READ") errors++;
        if (errors == 0) $display("PASS: ports froze at 0 and 1 with latencies 2, 4 and 1 by kind");
        else $fatal(1, "FAIL: frozen ports, burst limit or kind latencies differ from plan");
        $finish;
      end
    endmodule
    Terminal
    PASS: ports froze at 0 and 1 with latencies 2, 4 and 1 by kind

    Common mistakes

    Assigning an instance const anywhere but the constructor is a compile error. Initializing a non-static const at its declaration freezes one value pattern for shapes that needed per-object values.

    Declaring the enum inside one class forks the definition across components. SystemVerilog also has no const-qualified methods, so read-only behavior stays a team convention: query methods must not mutate state.

    Common pitfall

    Raw bit vectors for kind fields randomize illegal encodings in and pass them silently. The enum pays for itself the first time a bare 3 would have become a transaction.

    Interview

    Likely question: where do protocol timing constants shared by driver and monitor live?

    • One static table or package parameter referenced by both.

    • Never duplicated literals, and never instance fields.

    • The values are class-level facts, not per-object state.

    Check yourself Two objects need different frozen port ids. Which const flavor applies?

    Instance const assigned once in new(). A static const holds one value for every object and cannot vary per port.

    Check yourself What stops kind from holding the value 3?

    The enum type itself. Bare-integer assignment to an enum variable is a compile error unless written as an explicit cast.

    Remember

    • An instance const is set once in new(); a static const once at its declaration.

    • An enum field rejects bare integers and prints its name.

    • Per-kind facts live in one static table or function, not in every method.

    Rule of thumb: if a value must not change, let the compiler enforce it.

    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.

    Imports cannot appear directly inside a class (IEEE 1800 26.3), which is why the bench writes bus_pkg:: inside bus_txn.

    Related topics

    • Next: extern Methods & Class File Organization

    Previous

    47 / 259

    Next: extern Methods & Class File Organization

    Up next

    Up nextextern Methods & Class File Organization

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Static Properties & Methods
    • local, protected & Encapsulation