← Design Verification Interviews

Interview Question · Code Writing

A Copy That Shares Its Payload

L1 JuniorL2 Mid-Levelsystemverilogoopcopying

The Question

The interviewer asks

A packet class holds a handle to a payload object. The scoreboard saves a copy of each packet, then the driver reuses its packet for the next beat. Write a copy() method that keeps the saved copy safe.

Reveal the Model Answer

new src makes a shallow copy. It copies every property, but for a handle property it copies only the handle. So both packets still share one payload. A deep copy also constructs a new payload and copies its contents. Each class writes its own copy(), and pkt::copy() calls payload::copy() for its handle members, so the chain grows with the class tree.

Who Sees the Monitor's New BeatsThree copies of one transaction: a second handle, a shallow copy and a deep copy. Arrows are references, from a handle to its object and from a transaction to its payload through body. The arrow from P1 to P2 is the beats being copied.
Who sees the monitor's new beatsThree copies of one transaction: a second handle, a shallow copy and a deep copy. Arrows are references, from a handle to its object and from a transaction to its payload through `body`. The arrow from P1 to P2 is the beats being copied. handle mon and h two handles, oneobject handle s s = new mon handle d d = mon.copy() object txn 1 id 3, later 4 object txn 2 shallow copy, id 3 object txn 3 deep copy, id 3 object payload P1 beats 11, 22 object payload P2 built by copy() bodybodybodybeats copied
  1. The shallow copy shares P1

    new mon copied id and addr into txn 2, and copied the body handle as a handle. Both transactions now reach payload P1.

  2. The deep copy builds P2

    copy() called new(id, addr), which built a fresh payload, P2. Then it copied the beats into P2 by value.

    Follow the calls, values and waits
    1. data beats 11, 22: payload P1 → payload P2
  3. The monitor reuses its object

    The monitor writes ID 4 into txn 1 and beats 77, 88 into P1, the payload txn 1 refers to.

    Follow the calls, values and waits
    1. data id = 4: mon and h → txn 1
    2. data beats 77, 88: txn 1 → payload P1
  4. Reading the copies

    h shows ID 4 and beats 77, 88. s keeps ID 3 but reads 77, 88 through the shared P1. Only d still shows ID 3 with beats 11, 22.

    Follow the calls, values and waits
    1. data 77, 88: payload P1 → txn 2
    2. data 11, 22: payload P2 → txn 3
Read the diagram as text
  • mon and h: two handles, one object
  • s: s = new mon
  • d: d = mon.copy()
  • txn 1: id 3, later 4
  • txn 2: shallow copy, id 3
  • txn 3: deep copy, id 3
  • payload P1: beats 11, 22
  • payload P2: built by copy()
  • mon and h → txn 1
  • s → txn 2
  • d → txn 3
  • txn 1 → payload P1: body
  • txn 2 → payload P1: body
  • txn 3 → payload P2: body
  • payload P1 → payload P2: beats copied

A shallow copy owns its values but borrows its nested objects.

Shallow copy first, then replace every handle member with a copy of its own.
class payload;
  byte data[$];
  function payload copy();
    copy = new;
    copy.data = data;
  endfunction
endclass

class pkt;
  int     id;
  payload pl;
  function pkt copy();
    copy    = new this;        // copies id and the handle pl
    copy.pl = pl.copy();       // replaces the shared handle with a new payload
  endfunction
endclass

saved = new sent;

saved.id  // its own int
saved.pl  // the same object as sent.pl

Shallow: the payload is shared

saved = sent.copy();

saved.id  // its own int
saved.pl  // a new payload, same bytes

Deep: nothing is shared

Self-Check

Tick each key point you covered in your answer.

What Interviewers Listen For (Junior)

A good junior answer writes a copy() that builds the nested object and calls its copy. In one sentence, it explains why new src is not enough.

Cross-Questions

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

WhyWhat IfProve ItBreak ItCompare

  1. A Copy That Shares Its Payload
  2. WhyTwo Handles, One ObjectL0 FresherL1 Junior

    Why is a copy needed at all? What does this print?

    systemverilog
    class item; int v; endclass
    module tb;
      item h1, h2;
      initial begin
        h1 = new; h1.v = 1;
        h2 = h1;                    // copies the handle
        h2.v = 2;
        $display("h1.v=%0d", h1.v);
      end
    endmodule
    
    Reveal the Model Answer

    It prints h1.v=2. A class variable holds a handle, a reference to an object, and h2 = h1 copies only the handle. Both names now refer to the one object new created, so a write through either is seen through both.

    RUNVerilator · simulator service
    Edit the code, then run it
    Expected: h1.v=2

    Self-Check

    Tick each key point you covered in your answer.

    Misconception

    "Assigning one class variable to another copies the object, like a struct."

    Structs are values; classes are references. Only new creates an object, and assignment never calls it.

    Source IEEE 1800-2017 §8.4

  3. What IfA Generator That Reused One ObjectL1 Junior

    Asked if they write a generator

    A generator creates one txn before its loop, sets a new address each pass and puts it in a mailbox. The driver receives three transactions, all with the last address. Why?

    log
    gen: put addr=100
    gen: put addr=200
    gen: put addr=300
    drv: got addr=300
    drv: got addr=300
    drv: got addr=300
    Reveal the Model Answer

    A mailbox stores what it is given, and for a class that is a handle. All three messages refer to the same object, which holds the address written last. Construct a new object inside the loop for every transaction.

    Four Handles to One Object, or to FourA generator pushes four transactions, first with one object built before the loop, then with one new() per pass. Arrows from tr are writes and pushes; arrows from the queue are the references its entries hold.
    Four handles to one object, or to fourA generator pushes four transactions, first with one object built before the loop, then with one `new()` per pass. Arrows from `tr` are writes and pushes; arrows from the queue are the references its entries hold. process Generator uses handle tr queue exp_q a queue of handles object One object built before the loop object Four objects one new() per pass tr.id = ipush_back(tr)refers totr = new(i)refers to
    Read the diagram as text
    • Generator: uses handle tr
    • exp_q: a queue of handles
    • One object: built before the loop
    • Four objects: one new() per pass
    • Generator → One object: tr.id = i
    • Generator → exp_q: push_back(tr)
    • exp_q → One object: refers to
    • Generator → Four objects: tr = new(i)
    • exp_q → Four objects: refers to

    A push stores a handle. Only new makes a second object.

    Self-Check

    Tick each key point you covered in your answer.

    Misconception

    "put() stores a copy of the object."

    Nothing in the mailbox calls new or copy(). The twin reads back three entries that all show the last address.

    Run It

    One object, three puts, three gets.

    RUNVerilator · simulator service
    Edit the code, then run it
    Expected: 300 300 300 FAIL: the mailbox holds three handles to one object, so every message shows the last address
    See the Fix
    class txn; int addr; endclass
    module tb;
      mailbox #(txn) mb = new;
      txn t, got;
      int first;
      initial begin
        for (int i = 0; i < 3; i++) begin
          t = new;                // the fix: a new object for every transaction
          t.addr = 'h100 * (i + 1);
          mb.put(t);
        end
        for (int i = 0; i < 3; i++) begin
          mb.get(got);
          if (i == 0) first = got.addr;
          $write("%h ", got.addr[11:0]);
        end
        $display("");
        if (first == 'h100) $display("PASS: each message refers to its own object");
        else $display("FAIL: the mailbox holds three handles to one object, so every message shows the last address");
      end
    endmodule
    

    Prints: 100 200 300 PASS: each message refers to its own object

  4. Break ItThe Copy That Forgot the Subclass FieldsL2 Mid-Level

    A crc_pkt extends pkt and adds a crc field, but does not override the virtual copy_from(pkt rhs). The scoreboard copies packets through pkt handles. What breaks?

    Reveal the Model Answer

    The call is virtual, but crc_pkt has no override, so the base body runs and copies only id. The copy looks right in every base field while crc keeps whatever it held before. Every class that adds data must override the copy method. It calls super for the inherited fields and casts rhs to reach its own.

    Self-Check

    Tick each key point you covered in your answer.

    Misconception

    "copy_from() is virtual, so it copies whatever the object really is."

    Virtual only chooses which body runs. Without an override the base body runs, and it knows nothing about crc.

    Run It

    Two crc_pkt objects copied through a pkt handle with only the base copy_from().

    RUNVerilator · simulator service
    Edit the code, then run it
    Expected: dst.id=4 dst.crc=0 FAIL: the inherited copy_from() copied only the base fields, so crc was lost
    See the Fix
    class pkt;
      int id;
      virtual function void copy_from(pkt rhs);
        id = rhs.id;
      endfunction
    endclass
    class crc_pkt extends pkt;
      int crc;
      virtual function void copy_from(pkt rhs);
        crc_pkt r;
        super.copy_from(rhs);       // the fix: copy the base fields, then this class's own
        if ($cast(r, rhs)) crc = r.crc;
      endfunction
    endclass
    module tb;
      crc_pkt src, dst;
      pkt h;
      initial begin
        src = new; src.id = 4; src.crc = 'h5a;
        dst = new;
        h = dst;
        h.copy_from(src);           // the scoreboard copies through base handles
        $display("dst.id=%0d dst.crc=%0h", dst.id, dst.crc);
        if (dst.crc == 'h5a) $display("PASS: the derived copy_from() copied crc too");
        else $display("FAIL: the inherited copy_from() copied only the base fields, so crc was lost");
      end
    endmodule
    

    Prints: dst.id=4 dst.crc=5a PASS: the derived copy_from() copied crc too

  5. CompareFilling an Object vs Cloning OneL2 Mid-Level

    Compare a copy(rhs) method that fills an existing object with a clone() method that returns a new one.

    Reveal the Model Answer

    copy(rhs) writes into an object the caller already owns. Each class copies its own fields and calls super.copy(rhs) for the rest. It needs a $cast of rhs to reach the derived fields. clone() constructs the right derived type, usually by calling copy() on a fresh object, and returns it through a base handle. Methodologies such as UVM provide both on the same pattern.

    Self-Check

    Tick each key point you covered in your answer.

    Half-Truth

    "clone() can be written once in the base class."

    Only the derived class can construct an object of its own type. So each class must override clone(), or a macro must generate it.

  6. Prove ItSolve 4.2.6: The Copy That Shares Its PayloadSimulator-graded

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

    Open Problem 4.2.6

What People Get Wrong

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

Misconception

"saved = new sent; gives the scoreboard its own packet."

The built-in copy stops at the first level: handle members are copied as handles. The twin changes the driver's payload and the saved packet changes with it.

Run It

The scoreboard saves new sent, then the driver rewrites its payload.

RUNVerilator · simulator service
Edit the code, then run it
Expected: saved.data=99 FAIL: new copied the payload handle, so the saved packet changed with the driver's
See the Fix
class payload;
  byte data[$];
  function payload copy();
    copy = new;
    copy.data = data;          // a queue assignment copies the elements
  endfunction
endclass
class pkt;
  int id;
  payload pl;
  function new(); pl = new; endfunction
  function pkt copy();
    copy = new this;           // shallow: copies id and the payload handle
    copy.pl = pl.copy();       // deep: a payload of its own
  endfunction
endclass
module tb;
  pkt sent, saved;
  initial begin
    sent = new; sent.id = 1; sent.pl.data = '{8'h11};
    saved = sent.copy();      // the fix: copy() also copies the payload
    sent.pl.data[0] = 8'h99;   // the driver reuses its packet for the next beat
    $display("saved.data=%h", saved.pl.data[0]);
    if (saved.pl.data[0] == 8'h99) $display("FAIL: new copied the payload handle, so the saved packet changed with the driver's");
    else $display("PASS: the saved packet kept its own payload");
  end
endmodule

Prints: saved.data=11 PASS: the saved packet kept its own payload

Missed Corner

"For a queue of payload handles, copy.list = list; copies the payloads."

Assigning a queue copies its elements, and the elements are handles. Each entry still points at the original payload; the copy must construct one per entry.

Source IEEE 1800-2017 §8.12

Half-Truth

"Always deep-copy everything to be safe."

Shared read-only objects, such as a configuration, are meant to be shared. Copying them wastes memory and can split one setting into stale copies.

Buzzword Without Mechanism

Says "deep copy" but the code is only copy = new this;.

Naming the idea is not the work: the deep part is the line that replaces each handle member with a new object.

Learn It, Then Prove It