← Design Verification Interviews

Interview Question · Concept

Static Members of a Parameterized Class

L2 Mid-Levelsystemverilogoopparameterized classes

The Question

The interviewer asks

class box #(type T = int); has a static int created; that its constructor increments. You construct two box#(int) and one box#(byte). What do box#(int)::created and box#(byte)::created read, and why?

Reveal the Model Answer

They read 2 and 1. Each distinct set of parameter values makes a separate class type, called a specialization. Each specialization has its own copy of every static member. So a parameterized class cannot hold one counter for all its specializations. Put that state in a non-parameterized base class or a package.

One Static Counter per Class, and per SpecializationA base class axi_txn with a static counter, and a parameterized id_pool used with two widths. Dashed badges are calls or reads of a static; solid badges are values that come back or counts that change.
One static counter per class, and per specializationA base class `axi_txn` with a static counter, and a parameterized `id_pool` used with two widths. Dashed badges are calls or reads of a static; solid badges are values that come back or counts that change. caller Three reads axi_txn objects caller Two err_txns derived objects static axi_txn::created static, in the baseclass static id_pool#(4)::next_id wraps after 15 static id_pool#(8)::next_id a separate class caller Test code 14 more alloc() calls alloc()alloc()created++created++ insuper.newalloc(), next_id
  1. Three reads

    Each read takes its ID from the 4-bit pool: 0, 1 and 2. Each constructor also increments created.

    Follow the calls, values and waits
    1. call alloc(), 3 times: Three reads → id_pool #(4)::next_id
    2. data IDs 0, 1, 2: id_pool #(4)::next_id → Three reads
    3. data created: 1, 2, 3: Three reads → axi_txn::created
  2. Two error transactions

    They draw IDs 0 and 1 from the 8-bit pool, a separate counter. Their super.new increments the same created, which reaches 5.

    Follow the calls, values and waits
    1. call alloc(), 2 times: Two err_txns → id_pool #(8)::next_id
    2. data IDs 0, 1: id_pool #(8)::next_id → Two err_txns
    3. data created: 4, 5: Two err_txns → axi_txn::created
  3. Fourteen more from the 4-bit pool

    The 4-bit pool hands out 3 to 15, wraps, and hands out 0. Its next_id is now 1, while the 8-bit pool still holds 2.

    Follow the calls, values and waits
    1. call alloc(), 14 times: Test code → id_pool #(4)::next_id
    2. data 3 to 15, then 0: id_pool #(4)::next_id → Test code
  4. The default specialization

    id_pool #() takes the default WIDTH of 4, so it is the same class as id_pool #(4) and reads the same counter: 1.

    Follow the calls, values and waits
    1. call id_pool #()::next_id: Test code → id_pool #(4)::next_id
    2. data 1: id_pool #(4)::next_id → Test code
Read the diagram as text
  • Three reads: axi_txn objects
  • Two err_txns: derived objects
  • axi_txn::created: static, in the base class
  • id_pool #(4)::next_id: wraps after 15
  • id_pool #(8)::next_id: a separate class
  • Test code: 14 more alloc() calls
  • Three reads → id_pool #(4)::next_id: alloc()
  • Two err_txns → id_pool #(8)::next_id: alloc()
  • Three reads → axi_txn::created: created++
  • Two err_txns → axi_txn::created: created++ in super.new
  • Test code → id_pool #(4)::next_id: alloc(), next_id

A static exists once per class. Each specialization of id_pool is its own class; err_txn is not.

Self-Check

Tick each key point you covered in your answer.

What Interviewers Listen For (Mid-Level)

A good mid-level answer predicts 2 and 1. It moves shared state into a base class that has no parameters.

Cross-Questions

Where the interviewer goes next. Open a node to practise that follow-up.

WhyWhat IfProve ItCompareScale It

  1. Static Members of a Parameterized Class
  2. WhyWhy Each Specialization Is Its Own ClassL2 Mid-Level

    Why doesn't the language share statics across all specializations of a parameterized class?

    Reveal the Model Answer

    A type parameter can change the type and size of members, including statics declared with T. So box#(int) and box#(string) may not even have compatible statics. The language therefore treats every specialization as a separate class, generated from one template, and the statics follow.

    Self-Check

    Tick each key point you covered in your answer.

    Tool as Truth

    "It is a simulator optimisation and tools differ."

    The rule is in the standard: a specialization is a distinct type with its own static members.

    Source IEEE 1800-2017 §8.25

  3. What IfOne Counter for Every SpecializationL2 Mid-Level

    What if you want one counter shared by every specialization, such as a global transaction ID?

    Reveal the Model Answer

    Put the static in a base class without parameters, and derive the parameterized class from it. Derived classes share the static properties of their base class. So every specialization then updates the same counter. Reach it through the base class name, such as item_base::next_id.

    Self-Check

    Tick each key point you covered in your answer.

    Misconception

    "Declare it static in the parameterized class; static means one copy."

    Each specialization is its own class with its own statics. box#(int) and box#(string) then count separately.

    Source IEEE 1800-2017 §8.25

  4. What IfTwo Agents, One Static SettingL1 JuniorL2 Mid-Level

    Asked if they use statics for configuration

    Port 0's agent writes to 2004 although it was configured with base address 1000. Port 1 was configured with 2000. The agent stores its base address in a static int. What happened?

    log
    cfg: port0 base_addr=1000
    cfg: port1 base_addr=2000
    port0: write addr=2004   <-- expected 1004
    Reveal the Model Answer

    A static member is one variable per class, shared by every instance. Configuring port 1 overwrote the value port 0 had set. Make the setting an instance member, or pass a per-instance configuration object.

    Self-Check

    Tick each key point you covered in your answer.

    Misconception

    "Each agent object keeps its own static value."

    There is only one, no matter how many objects exist. The twin configures two agents and port 0 ends up with port 1's address.

    Run It

    Two agents configure a static int base_addr in turn.

    RUNVerilator · simulator service
    Edit the code, then run it
    Expected: port0 writes to 00002004 FAIL: a static is shared by every instance, so port1 overwrote port0's address
    See the Fix
    class agent;
      int base_addr;                // the fix: per-instance configuration
      function void configure(int a); base_addr = a; endfunction
      function int target(int off); return base_addr + off; endfunction
    endclass
    module tb;
      agent port0, port1;
      initial begin
        port0 = new; port0.configure('h1000);
        port1 = new; port1.configure('h2000);
        $display("port0 writes to %h", port0.target(4));
        if (port0.target(4) == 'h1004) $display("PASS: each agent kept its own address");
        else $display("FAIL: a static is shared by every instance, so port1 overwrote port0's address");
      end
    endmodule
    

    Prints: port0 writes to 00001004 PASS: each agent kept its own address

  5. Prove ItSolve 6.4.3: The Monitors That Watched One BusSimulator-graded

    Show it, don't just say it: this Silicon Lab problem is graded by the simulator.

    Open Problem 6.4.3

  6. CompareForward Class DeclarationsL1 JuniorL2 Mid-Level

    A driver class holds a handle to a sequencer, and the sequencer holds a handle back to the driver. Compare typedef class with reordering the file to make this compile.

    Reveal the Model Answer

    Reordering cannot work, because each class names the other before it is declared. typedef class sequencer; declares the name first, so driver can hold a sequencer handle. The full class can follow later in the same scope. Reordering is enough only when the dependency runs one way.

    Self-Check

    Tick each key point you covered in your answer.

    Misconception

    "typedef class copies the class definition earlier in the file."

    It only declares the name as a class type; the definition must still appear later in the same scope.

    Source IEEE 1800-2017 §6.18

  7. Scale ItType Parameters vs a Common Base ClassL3 Senior

    You are designing a reusable scoreboard. Do you parameterize it by transaction type, or write it against a common base transaction class?

    Reveal the Model Answer

    A type parameter gives full type safety and needs no casts. But scoreboard#(axi_txn) and scoreboard#(apb_txn) share no type, so they cannot sit in one array. A base-class design can hold any transaction and swap types at run time. The price is a cast wherever code needs derived fields. Many teams combine them: a parameterized class that extends a non-parameterized base holding the common interface.

    Self-Check

    Tick each key point you covered in your answer.

    What Interviewers Listen For (Senior)

    A good senior answer names what each choice makes impossible. It picks the mixed design only after saying what must be shared while the test runs.

    Half-Truth

    "Parameterize everything; it is more reusable."

    Unrelated specializations cannot be stored or handled together, which hurts reuse when an environment needs to treat many scoreboards alike.

  8. Prove ItSolve 4.6.1: Typed Value BoxSimulator-graded

    Show it, don't just say it: this Silicon Lab problem is graded by the simulator.

    Open Problem 4.6.1

What People Get Wrong

Common wrong answers and why they fail. Run It shows the wrong belief failing on a real simulator.

Misconception

"A static member is shared by every object of box."

It is shared by every object of one specialization. The twin counts three boxes and no single counter reaches 3.

Run It

Two box#(int) and one box#(byte), each constructor incrementing a static counter.

RUNVerilator · simulator service
Edit the code, then run it
Expected: box#(int)::created=2 box#(byte)::created=1 FAIL: each specialization has its own static, so no counter saw all three boxes
See the Fix
class box_base;
  static int created;           // the fix: one counter in a non-parameterized base
endclass
class box #(type T = int) extends box_base;
  function new(); created++; endfunction
endclass
module tb;
  box #(int)  a, b;
  box #(byte) c;
  initial begin
    a = new; b = new; c = new;
    $display("boxes created: %0d", box_base::created);
    if (box_base::created == 3) $display("PASS: the base class counted every specialization");
  end
endmodule

Prints: boxes created: 3 PASS: the base class counted every specialization

Spec Misquote

"box and box#() are two different types."

The bare name and #() both mean the default specialization, so they share statics.

Source IEEE 1800-2017 §8.25

Half-Truth

"Statics are the easy way to share configuration between components."

They are shared with every instance of the class, including a second agent that needs a different setting.

Learn It, Then Prove It