Part 5 · Sequences · Intermediate

Copy, Clone & Scoreboard Queue Safety

type_id::create, copy/clone before queueing, nested objects, and the monitor reuse trap.

Why copy before queue

Monitors and drivers reuse a single transaction object for performance. The monitor fills fields on object t, calls ap.write(t), then on the next clock overwrites t's fields. If the scoreboard queues the handle instead of a snapshot, every queued entry points to the same object — and that object's fields always reflect the last sample. Compare then passes or fails randomly. Clone before queue is non-negotiable.

The same rule applies on the expected path: ref models often mutate a working copy. Queue exp snapshots with copy/clone, never the live handle from predict().

diagram
[ITEM] handle queue vs copy queue

  Monitor reuses txn object M:

  Cycle 1: M.addr=0x100  ap.write(M)  scb queues HANDLE to M
  Cycle 2: M.addr=0x200  ap.write(M)  M.addr overwritten!
  Cycle 3: scoreboard pops queue[0]  M.addr is 0x200, not 0x100  ← BUG

  FIX: snap = create(); snap.copy(M); queue.push_back(snap);

type_id::create — always, not new()

UVM factory integration requires type_id::create() for items. new() bypasses factory overrides — tests cannot swap item types without editing sequences.

systemverilog
// CORRECT — factory-aware
apb_item req = apb_item::type_id::create("req");

// WRONG — bypasses factory
apb_item req = new("req");

// Override in test:
set_type_override_by_type(apb_item::get_type(), apb_item_errata::get_type());
// Only affects type_id::create calls
diagram
[UVM] factory path for [ITEM]

  [SEQ] req = apb_item::type_id::create("req")
              │
              ▼
         UVM factory lookup  apb_item or override type
              │
              ▼
         [ITEM] randomized and driven

  new("req") skips factory — override silently ignored
  • Every create in sequences, monitors, scoreboards — consistent factory path.

  • Named create string appears in debug — use descriptive names.

  • Factory override per test — errata items without VIP edits.


copy vs clone — API reference

Both duplicate registered fields. copy(dest) fills an existing object; clone(dest) is equivalent convenience. Always create dest first.

systemverilog
function void write_exp(apb_item t);
  apb_item snap;
  snap = apb_item::type_id::create("exp_snap");
  snap.copy(t);                    // deep copy registered fields
  exp_queue.push_back(snap);
  `uvm_info("SCB", $sformatf("queued exp addr=0x%08x", snap.addr), UVM_HIGH)
endfunction

function void write_act(apb_item t);
  apb_item exp;
  if (exp_queue.size() == 0) begin
    `uvm_error("SCB", "unexpected actual — no expected in queue")
    return;
  end
  exp = exp_queue.pop_front();
  if (!t.compare(exp))
    `uvm_error("SCB", $sformatf("mismatch exp=%s act=%s",
                                  exp.sprint(), t.sprint()))
endfunction
  • copy requires pre-allocated destination — type_id::create first.

  • clone(dest) same effect — stylistic preference in UVM codebases.

  • Only UVM_FIELD-registered members copy — verify registration.


APB scoreboard walkthrough

Follow one APB write through ref model predict and monitor sample — both paths must clone before queue.

diagram
[CHECK] APB write scoreboard — copy at both streams

  Step 1: [SEQ] drives write addr=0x4000 data=0xAA
  Step 2: ref_model.predict(req)
          exp = create(); exp.copy(predicted_rsp);
          scb.write_exp(exp);           ← snapshot queued

  Step 3: [UVM] monitor samples pins (reuses mon_txn)
          mon_txn.addr=0x4000 mon_txn.data=0xAA
          scb.write_act(mon_txn);
          compare uses queued exp snapshot — NOT live mon_txn handle

  Step 4: monitor reuses mon_txn for next beat — queue unaffected
systemverilog
class apb_scoreboard extends uvm_scoreboard;
  apb_item exp_q[$];

  function void write_exp(apb_item t);
    apb_item snap = apb_item::type_id::create("exp_snap");
    snap.copy(t);
    exp_q.push_back(snap);
  endfunction

  function void write_act(apb_item t);
    if (exp_q.size() == 0) begin
      `uvm_error("SCB", "unexpected act")
      return;
    end
    apb_item exp = exp_q.pop_front();
    if (!t.compare(exp))
      `uvm_error("SCB", "APB mismatch")
  endfunction
endclass

Nested objects — uvm_field_object

Items containing nested uvm_objects need uvm_field_object for deep copy. Shallow copy duplicates the handle — nested object still shared.

systemverilog
class burst_beat extends uvm_object;
  rand bit [31:0] data;
  `uvm_object_utils(burst_beat)
endclass

class axi_burst_item extends uvm_sequence_item;
  rand burst_beat beats[];

  `uvm_object_utils_begin(axi_burst_item)
    `uvm_field_array_object(beats, UVM_ALL_ON)
  `uvm_object_utils_end
endclass

// copy now deep-copies beats array and each element
diagram
[ITEM] shallow vs deep copy trap

  beats[$] without field_array_object:
    snap.copy(orig)  snap.beats[i] SAME HANDLE as orig.beats[i]
    mutate orig.beats[0]  snap.beats[0] changes too

  With uvm_field_array_object: independent nested copies

Driver item reuse — finish_item boundary

After finish_item returns, the driver has called item_done — safe to reuse or re-randomize req. Before finish_item returns, the driver may still read req fields during drive_apb. Do not randomize mid-flight.

diagram
[SEQ] item lifetime during handshake

  start_item(req)
  randomize(req)        ← [SEQ] owns req content
  finish_item(req)      ← BLOCKS — [DRV] reading req
       │
       ├── get_next_item(req)
       ├── drive_apb(req)     ← reads addr, data, write
       └── item_done()
  finish_item returns   ← NOW safe to re-randomize req

Key takeaways

  • type_id::create for every item — factory overrides depend on it.

  • copy/clone before ANY queue — monitor and ref model reuse objects.

  • Register nested objects with uvm_field_object for deep copy.

Common pitfalls

  • Queueing handle not copy — #1 scoreboard silent corruption bug.

  • new() instead of type_id::create — factory override ignored.

  • Unregistered field not copied — compare misses mismatches on that field.

  • Randomizing req before finish_item returns — race with driver reads.