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 Foundations35
  • Object-Oriented SystemVerilog for Verification35
  • Constraint Randomization — Interview-Critical Deep Dive66
  • SystemVerilog Assertions — Interview-Critical Deep Dive63
  • Functional Coverage in SystemVerilog28
  • Testbench Architecture in SystemVerilog38
  • Advanced Language & Integration30
  • Senior & Interview Preparation34
    • Senior Debug Playbook
    • Reviewing Verification Code
    • Verification Strategy: Spec to Sign-Off
    • SystemVerilog Interview Q&A Bank
      • Q&A: Language Core
      • Q&A: Arrays & Queues
      • Q&A: Testbench Plumbing
      • Q&A: Classes and Interfaces
      • Q&A: OOP
      • Q&A: Constraint and Assertion Fire-Drill
      • Q&A: Interfaces & Virtual Interfaces
      • Q&A: Processes & IPC
      • Q&A: Scenario and Design Questions
      • Q&A: Constraints Quick-Fire
      • Q&A: SVA Quick-Fire
    • Capstone: Verify a Parameterized FIFO
  • Principal-Level Verification28
Loading lesson

‹SystemVerilog Interview Q&A Bank
  1. Topics
  2. SystemVerilog
  3. Senior & Interview Preparation
  4. SystemVerilog Interview Q&A Bank
  5. Q&A: Classes and Interfaces

Part 8 · Senior & Interview Prep · Intermediate

Q&A: Classes and Interfaces

Copy depth, virtual dispatch, checked casts, interfaces, clocking and virtual interfaces.

On this page

    Classes model behavior that changes at runtime; modules and interfaces model structure fixed at elaboration. The virtual interface is the one bridge between the two worlds.

    Behavior in classes, structure in modules

    Q: Class versus module, why are testbenches built from classes

    A module is a static hardware container, fixed at elaboration with no inheritance. A class is a dynamic software object created with new, supporting inheritance, polymorphism and randomization.

    Testbenches need runtime flexibility: thousands of transactions created and destroyed, behavior swapped by extension without touching the driver. DUTs stay modules because hardware is static structure with timing.

    Q: Shallow copy versus deep copy, what does new-from-object copy

    Level

    What happens

    When it bites

    Handle assignment (t2 = t1)

    One object, two names

    Editing through either name changes both

    Shallow copy (new t1)

    Two objects sharing nested ones

    Editing the copy payload corrupts the original

    Deep copy (hand-written copy)

    Fully independent objects

    Monitors must use it before reusing a transaction

    The listing below is class declarations only, with no top module, so this course's checks do not run it. It shows the shared-payload bug and the recursive fix.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // not-run: fragment: class declarations only, no top module
    class payload;
      bit [7:0] len;
      function payload copy();
        payload c = new();
        c.len = len;
        return c;
      endfunction
    endclass
    
    class packet;
      bit [7:0] hdr;
      payload pl;
      function packet copy();           // deep copy: written by hand
        packet c = new();
        c.hdr = hdr;
        c.pl = (pl == null) ? null : pl.copy();
        return c;
      endfunction
    endclass
    // p2 = new p1;  shallow: p2.pl and p1.pl are the same object

    Without classes, plain assignment already copies by value. The testbench below proves it on packed headers, which is exactly the guarantee class handles do not give.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    typedef struct packed { logic [7:0] hdr; logic [7:0] len; } hdr_t;
    
    module tb_copy_plain;
      hdr_t a, b;
      int errors = 0;
      initial begin
        a.hdr = 8'hA5; a.len = 8'd16;
        b = a;
        b.len = 8'd99;
        if (a.len !== 8'd16) errors++;   // original untouched
        if (b.hdr !== 8'hA5) errors++;   // header carried over
        if (errors == 0) $display("PASS: plain assignment copies and the original stays intact");
        else $fatal(1, "FAIL: copy semantics wrong");
        $finish;
      end
    endmodule

    Q: What do virtual methods buy a driver

    A virtual method dispatches on the object type, not the handle type. Components hold base handles while tests inject derived objects with overridden behavior, and the driver is never edited.

    Without virtual, the base version runs even when the object is derived. The override is silently ignored, which is the worst kind of stimulus bug.

    The listing below is class declarations only, with no top module, so this course's checks do not run it.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // not-run: fragment: class declarations only, no top module
    class base_txn;
      rand bit [7:0] data;
      virtual function void apply();
        // normal drive would go here
      endfunction
    endclass
    
    class corrupt_txn extends base_txn;
      function void apply();            // override: corrupt on purpose
        data = data ^ 8'hFF;
      endfunction
    endclass
    // driver code, never edited:
    //   base_txn t; gen2drv.get(t); t.apply();

    Checked casts and shared state

    Q: Checked casts, what are the two uses

    $cast is a checked downcast from a base handle to a derived handle. As a function it returns 0 on failure so the code can branch; as a task it raises a run-time error on failure, which is not fatal.

    Its second use converts an integral value to an enum, where direct assignment is a compile error. Upcasts never need a cast since every derived object is already a base object.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    // snippet
    // base_txn b; err_txn e;
    // function form: branch on the actual type
    // if ($cast(e, b)) $display("error txn");
    // task form: failure is a testbench bug, fail loudly
    // $cast(e, b);
    // enum conversion: op_e op; $cast(op, rdata);

    Q: Static members, where do they belong in a testbench

    A static property has one copy shared by all instances, alive from time zero. The canonical uses are transaction ID stampers and debug counters callable without an instance.

    Static state is global state in disguise: it couples tests invisibly and goes stale across restarts. Use it for IDs and counters, never as a configuration back-channel.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    // snippet
    // class fifo_txn;
    //   static int next_id = 0;   // one copy for every instance
    //   int id;
    //   function new();
    //     id = next_id++;         // free unique ID per transaction
    //   endfunction
    // endclass

    Q: Parameterized classes, what problem do they solve

    A class with type or value parameters is written once and specialized per transaction type. Each specialization is a distinct, incompatible type.

    The standard trick for a common handle is a non-parameterized base class that the parameterized class extends. The built-in mailbox with a type argument is the example everyone already uses.

    The interface as contract

    Q: Why do interfaces exist

    An interface bundles a signal group into one named port. A 40-signal bus becomes one connection line, and protocol assets travel with it: clocking blocks, modports, assertions and tasks.

    Q: What do modports actually enforce

    Modports are compile-time direction contracts per connection point. The compiler rejects a module driving a signal its modport declares as input, which kills contention bugs before simulation.

    They are compile-time only. Code holding the raw interface bypasses the contract entirely.

    Q: Clocking skews, what do input and output delays mean

    input #1step samples in the Preponed region, the value just before the edge, exactly what a flop captures. output with a small delay drives past the hold window.

    #1step differs from #0: #0 samples after the edge updates may have landed, so it can read post-edge values. Together the skews remove sampling and drive races for every signal accessed through the clocking block.

    Diagram
    CLOCKING BLOCK TIMING
    
            setup     edge      hold
      ────────────────┬──┬──┬────────────────────► time
                      │  ▲  │
       input #1step ──┘  │  └── output #2ns drives here
       samples here      │      (after hold window)
       (Preponed: value  clk
        before the edge) rises

    The listing below is an interface with no module or testbench, so this course's checks do not run it.

    fifo_if.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // not-run: fragment: an interface only, no top module
    interface fifo_if (input logic clk, input logic rst_n);
      logic push, pop, full, empty;
      logic [7:0] wdata, rdata;
      clocking cb @(posedge clk);
        default input #1step output #2ns;
        output push, pop, wdata;
        input full, empty, rdata;
      endclocking
      modport DUT (input push, pop, wdata, clk, rst_n,
                   output full, empty, rdata);
      modport TB (clocking cb, input rst_n);
    endinterface

    Pre-edge sampling needs no clocking block to prove. In the testbench below, active-region reads always precede NBA updates, so the sampled value matches what a flop would capture.

    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
    module tb_preedge;
      timeunit 1ns; timeprecision 1ps;
      logic clk = 0;
      always #5 clk = ~clk;
      logic sig = 0;
      logic pre_val, post_val;
      int errors = 0;
      always_ff @(posedge clk) sig <= ~sig;
      initial begin
        @(posedge clk);
        pre_val = sig;                  // active region: pre-edge value
        #1;
        post_val = sig;                 // NBA settled: post-edge value
        if (pre_val !== 1'b0) errors++;
        if (post_val !== 1'b1) errors++;
        @(posedge clk);
        pre_val = sig; #1; post_val = sig;
        if (pre_val !== 1'b1) errors++;
        if (post_val !== 1'b0) errors++;
        if (errors == 0) $display("PASS: edge sampling sees pre-edge values like a flop");
        else $fatal(1, "FAIL: sampling order wrong");
        $finish;
      end
    endmodule

    Getting the virtual interface to the driver

    Q: Why do classes need virtual interfaces at all

    Classes have no elaboration-time existence, so they cannot have ports. A virtual interface is a runtime handle to a real interface instance, and the one sanctioned bridge into the static module world.

    The standard chain is constructor injection: top builds the real interface and passes the handle down through environment, agent, driver and monitor. UVM uses config-db lookup for the same journey.

    Q: Many agents, one interface array, what goes wrong

    Each agent needs its own handle bound to its own instance. Binding every agent to instance zero drives one physical port from all agents while the other ports sit idle.

    The symptom is triple traffic on port zero and silence elsewhere. Per-instance keys in UVM, or per-agent constructor arguments outside it, prevent it.

    The listing below is a class that needs the fifo_if interface above and has no top module, so this course's checks do not run it. It names the null-handle defense every driver needs.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // not-run: fragment: needs the fifo_if interface above and has no top module
    class driver;
      virtual fifo_if vif;              // null until assigned
      function new(virtual fifo_if vif);
        this.vif = vif;
      endfunction
      task automatic run();
        if (vif == null) $display("vif never assigned");
        else $display("vif bound");
      endtask
    endclass

    Common pitfall

    Driving DUT pins from class code at the raw edge instead of through the clocking block brings races back. A null virtual interface dies at first use, so assert the handle before the run starts.

    Interview

    Likely follow-up: why #1step and not #0. #1step is the previous-timestep value, guaranteed pre-edge like silicon. #0 can read post-edge updates.

    Check yourself Without virtual, a base handle holds a derived object and calls the overridden method. Which version runs?

    The base version. Dispatch follows the declared handle type, silently ignoring the override with no warning.

    Check yourself Every agent received instance zero of the interface array. What does the scoreboard see?

    Triple traffic on port zero and nothing on the other ports. Each agent needs its own handle bound to its own instance.

    Related topics

    • OOP: classes and handles

    • OOP: inheritance and polymorphism

    • OOP: virtual interfaces

    • Foundations: interfaces

    • Testbench: bridge to UVM

    Previous

    321 / 363

    Next: Q&A: OOP

    Up next

    Up nextQ&A: OOP

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Q&A: Language Core
    • Q&A: Arrays & Queues
    • Q&A: Constraint and Assertion Fire-Drill