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. local, protected & Encapsulation

Part 2 · OOP for Verification · Intermediate

local, protected & Encapsulation

Who may see each member, hidden queues with getters, and base classes meant to be extended.

On this page

    Why this matters

    A test reset a checker's internal count to make a failing run pass. Nothing stopped it, because every field was public.

    After this lesson you can: mark members local or protected, expose state through getters, and design a base class with a deliberate extension surface.

    Three visibility levels

    Every class member is public by default: any code with a handle can read it, write it or call it. The local qualifier restricts a member to the declaring class, and protected admits the declaring class plus its subclasses.

    The compiler enforces the boundary at compile time. Violations are errors, not warnings.

    Member qualifier

    Same class

    Subclass

    Outside code

    None (public)

    yes

    yes

    yes

    protected

    yes

    yes

    no

    local

    yes

    no

    no

    Encapsulation is a contract: public members are the supported interface and the rest stays free to change. Hidden state also keeps invariants enforceable, since only the class methods can disturb them.

    Base classes meant to be extended

    In a VIP base class, protected marks the intended extension surface: handles and hooks that subclasses are expected to use. Local marks internals that even subclasses must reach through methods.

    SystemVerilog has no friend declaration, so privileged access always goes through an explicit method. A debug accessor with a policy argument replaces backdoor field access.

    A base class with a protected setting its subclasses may tune and a local count only its own methods may change:

    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
    49
    50
    51
    class link_base;
      protected int unsigned timeout = 100;   // subclasses may tune this
      local int unsigned in_flight;           // only link_base's own methods change it
    
      function void start();
        in_flight++;
      endfunction
    
      function void finish();
        if (in_flight > 0) in_flight--;
      endfunction
    
      function int unsigned pending();
        return in_flight;
      endfunction
    endclass
    
    class slow_link extends link_base;
      function new();
        timeout = 500;                        // OK: protected is visible to subclasses
      endfunction
    
      function void send_one();
        start();                              // OK: the base's public method
        // in_flight++;                       // COMPILE ERROR: local to link_base
        finish();
      endfunction
    
      function int unsigned get_timeout();
        return timeout;
      endfunction
    endclass
    
    module tb_visibility;
      slow_link l;
      int errors = 0;
      initial begin
        l = new();
        l.start();
        if (l.pending() != 1) errors++;
        l.send_one();
        if (l.pending() != 1) errors++;       // send_one started and finished its own
        l.finish();
        if (l.pending() != 0) errors++;
        if (l.get_timeout() != 500) errors++;
        // l.timeout = 0;                     // COMPILE ERROR: protected, outside code
        if (errors == 0) $display("PASS: the subclass tuned the protected timeout and moved the local count only through methods");
        else $fatal(1, "FAIL: %0d visibility checks failed", errors);
        $finish;
      end
    endmodule
    Terminal
    PASS: the subclass tuned the protected timeout and moved the local count only through methods

    Testbench

    Predict first The checker below expects e0 then e1. It then sees g0 (equal to e0), g1 (wrong data) and one more packet when its queue is already empty. What do get_hits() and get_miss_count() return?

    1 and 2. g0 matches; g1 is a mismatch; the last packet has nothing to compare against, which the checker also counts as a miss.

    A hidden queue and hidden tallies that only move through methods, with getters as the read path.

    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
    class mini_txn;
      local int id;            // local: only mini_txn code may touch these
      local int unsigned data;
    
      function new(int i, int unsigned d);
        id = i;
        data = d;
      endfunction
    
      // Same-class access: this method may read rhs's locals, another handle of mini_txn.
      function bit compare(mini_txn rhs);
        return (id == rhs.id) && (data == rhs.data);
      endfunction
    endclass
    
    class in_order_checker;
      // Hidden: only this class touches the queue and tallies,
      // so hits plus misses always equals the number of checks.
      local mini_txn expected_q[$];
      local int hits;
      local int misses;
    
      function void add_expected(mini_txn t);
        expected_q.push_back(t);
      endfunction
    
      function void check_actual(mini_txn t);
        mini_txn exp;
        if (expected_q.size() == 0) begin
          misses++;
          return;
        end
        exp = expected_q.pop_front();
        if (exp.compare(t)) hits++;
        else misses++;
      endfunction
    
      // Read-only view of hidden state; no setter is part of this API.
      function int get_hits();
        return hits;
      endfunction
    
      function int get_miss_count();
        return misses;
      endfunction
    endclass
    
    module top;
      in_order_checker chk;
      mini_txn e0;
      mini_txn e1;
      mini_txn g0;
      mini_txn g1;
      mini_txn surprise;
      int errors = 0;
      initial begin
        chk = new();
        e0 = new(0, 32'hA1);
        e1 = new(1, 32'hB2);
        g0 = new(0, 32'hA1);   // matches e0
        g1 = new(1, 32'h00);   // wrong data for e1
        surprise = new(7, 32'hFF);
        chk.add_expected(e0);
        chk.add_expected(e1);
        chk.check_actual(g0);
        chk.check_actual(g1);
        chk.check_actual(surprise);   // queue is empty: counts as a miss
        // chk.hits = 0;  // COMPILE ERROR: hits is local to in_order_checker
        if (chk.get_hits() !== 1) errors++;
        if (chk.get_miss_count() !== 2) errors++;
        if (chk.get_hits() + chk.get_miss_count() !== 3) errors++;
        if (errors == 0) $display("PASS: the checker tallied 1 hit and 2 misses over 3 checks");
        else $fatal(1, "FAIL: hit and miss tallies differ from 1 and 2");
        $finish;
      end
    endmodule
    Terminal
    PASS: the checker tallied 1 hit and 2 misses over 3 checks

    Common mistakes

    Leaving everything public turns every field into API and freezes refactoring. Writing setters without validation keeps the name of encapsulation with none of its effect.

    Local in a base class blocks subclasses that genuinely need the state. Prefer protected for the extension surface and local for invariant-carrying internals.

    Common pitfall

    Local is per class, not per object. A method may read locals through another handle of the same class, as compare() reads rhs above, so local never isolates one object from its siblings.

    Interview

    Likely question: a VIP update broke every user subclass. What visibility mistake caused it?

    • Implementation details were left public or protected.

    • Users built on them, so the details became de-facto API.

    • The fix is a small protected extension surface with the rest local.

    Check yourself A subclass tries to increment its base counter declared local. What happens?

    Compilation fails. Local members are visible only inside the declaring class, so the subclass must call a base method instead.

    Check yourself Outside code needs the miss count but must never reset it. What is the safe shape?

    A getter such as get_miss_count() with no matching setter. Reads stay possible while writes stay impossible.

    Project step

    Write PacketFIFO's checker as a class: a local queue of expected pkt_txns, wrote() when a packet enters the FIFO, read() when one leaves, and getters for matched, mismatched and still-pending counts. No outside code can reach the queue.

    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
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    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;
      bit [3:0]  len;
      bit [7:0]  payload[$];
    
      function new(kind_e k, bit [1:0] d, bit [7:0] p[$]);
        kind = k; dest = d; payload = p; len = 4'(p.size());
      endfunction
    
      function bit compare(pkt_txn other);
        if (kind != other.kind || dest != other.dest || len != other.len) return 0;
        foreach (payload[i]) if (payload[i] != other.payload[i]) return 0;
        return 1;
      endfunction
    endclass
    
    // PacketFIFO's checker: the expected queue and the tallies are hidden.
    class pkt_checker;
      local pkt_txn expected_q[$];
      local int     matched, mismatched;
    
      function void wrote(pkt_txn p);         // a packet went into the FIFO
        expected_q.push_back(p);
      endfunction
    
      function void read(pkt_txn p);          // a packet came out
        pkt_txn exp;
        if (expected_q.size() == 0) begin
          mismatched++;                       // nothing was expected
          return;
        end
        exp = expected_q.pop_front();
        if (exp.compare(p)) matched++;
        else mismatched++;
      endfunction
    
      function int get_matched();    return matched;           endfunction
      function int get_mismatched(); return mismatched;        endfunction
      function int get_pending();    return expected_q.size(); endfunction
    endclass
    
    module tb_pkt_checker;
      pkt_checker chk;
      pkt_txn     p;
      int errors = 0;
      initial begin
        chk = new();
        p = new(DATA, 2, '{8'hA5, 8'h3C}); chk.wrote(p);
        p = new(CTRL, 1, '{8'h07});        chk.wrote(p);
        p = new(DATA, 2, '{8'hA5, 8'h3C}); chk.read(p);    // matches
        p = new(CTRL, 1, '{8'h08});        chk.read(p);    // one payload byte wrong
        p = new(DATA, 0, '{8'h11});        chk.wrote(p);   // still inside at the end
        // chk.expected_q.delete();                    // COMPILE ERROR: local
        $display("matched=%0d wrong=%0d pending=%0d", chk.get_matched(), chk.get_mismatched(), chk.get_pending());
        if (chk.get_matched() != 1 || chk.get_mismatched() != 1 || chk.get_pending() != 1) errors++;
        if (errors == 0) $display("PASS: pkt_checker counted 1 match, 1 wrong packet and 1 packet still expected");
        else $fatal(1, "FAIL: checker tallies wrong");
        $finish;
      end
    endmodule
    Terminal
    matched=1 wrong=1 pending=1
    PASS: pkt_checker counted 1 match, 1 wrong packet and 1 packet still expected

    Remember

    • Public is the default; protected admits subclasses; local admits only the class.

    • local is per class, not per object: a method may read another object's locals.

    • Read hidden state through getters; leave out setters you do not want used.

    Rule of thumb: hide what must stay consistent; expose what others may 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: Parameterized Classes

    Previous

    45 / 259

    Next: Parameterized Classes

    Up next

    Up nextParameterized Classes

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Constants & Enums in Classes
    • extern Methods & Class File Organization