Who owns an analysis transaction
Decide who copies an analysis transaction, when and at what cost: the publisher that creates it, and the listener that keeps it.
- Lesson
- U7.2
- Time
- 25 min
- Runs on
- Verilator with UVM
- Check
- 5 questions
The pins carried 16, 32 and 48, but a slow checker reports 32, 48 and 48. The monitor refilled one object while the checker was still reading it. A listener that cloned each transaction to protect itself kept 0, 0 and 0.
The checker finishes its first item at 65 ns and reads 32, because the monitor refilled object A at 45 ns.
Two sides of one rule
U3.5 showed that one write reaches every listener, that write cannot wait, and that a listener must not change what it receives. This lesson takes the two sides that rule leaves open: the publisher that creates the object, and the listener that keeps it.
- First transfer15 ns (illustrative)
The monitor fills object A with x = 16 and writes it. The recorder saves the handle; the checker’s get returns the same handle and reads x = 16 to start its 50 ns of work.
More context
Neither listener receives a copy. Both handles name the one object the monitor created.
Follow the calls, values and waits
- data handle to A: Object A → Recorder entry
- data handle to A: Object A → Checker variable
- data x = 16: Object A → Checker variable
- Waiting: Checker variable — working for 50 ns
- Refill and write again45 ns (illustrative)
With reuse enabled, the monitor refills the same object A with x = 32 and writes it again. The FIFO queues a handle to A, the object the recorder and the busy checker still hold.
More context
The refill is one assignment to A. No holder is told, and UVM does not prevent it.
Follow the calls, values and waits
- data handle to A: Object A → FIFO entry
- The checker reads x again65 ns (illustrative)
The checker finishes its first item and reads x through its handle: 32, not the 16 the pins carried at 15 ns.
More context
The example logs
done x=32at 65 ns. A fresh object per transfer, or a copy made inside write, keeps 16.Follow the calls, values and waits
- data x = 32: Object A → Checker variable
- Released: Checker variable
Read the diagram as text
- Recorder entry: Saved during the first transfer
- FIFO entry: Queued by the second transfer
- Checker variable: Still processing the first transfer
- Object A: x = 32; previously x = 16
- Recorder entry → Object A: handle
- FIFO entry → Object A: handle
- Checker variable → Object A: handle
These arrows are references, not copies or calls. When the checker reads x at 65 ns, the same object now holds 32.
A handle is a reference to an object. Saving it in a FIFO or a queue does not create another object, so every holder sees later changes to its fields. A transaction that nobody changes after write is an immutable snapshot; UVM does not enforce that for you.
Refills one object
shared.x = vif.x;
in_ap.write(shared);
Every saved handle names the one object, shared. The next assignment changes earlier entries too.
Creates one per transfer
incr_item t = incr_item::type_id::create("t");
t.x = vif.x;
in_ap.write(t);
Each observation has its own object. Read-only listeners can keep it safely.
Who copies, when, at what cost
| Who | Copies when | Why | Cost |
|---|---|---|---|
| Publisher (your monitor) | Always: a fresh object per transfer, never changed after write | It is the only writer; each observation needs its own snapshot | One allocation per transfer, the cheap default |
Listener that reads only inside write | Never | It has finished before the publisher can change anything | None |
| Listener that keeps handles for later | Inside write, unless the publisher never changes a published object | A refill or later change would alter what it kept | One allocation per kept transfer; the copy must cover its fields |
| Listener that transforms data | A local variable for a derived value, or a private clone to change a whole transaction | The received object belongs to every listener | A clone is one allocation per transfer |
With a publisher that never changes a published object, only a listener that transforms whole transactions needs a copy. Each clone is one more allocation; how much stays live depends on what is kept (U2.1 S5).
Copy inside write, at the moment you receive the handle. A listener that copies later, after a FIFO get or in report_phase, copies whatever the publisher has written since.
A clone is only as good as do_copy
clone() creates an object of the same type and fills it through copy, which calls do_copy (U2.1 S2). The example's incr_item has no do_copy and no field macros, so the copy covers no user field: the clone keeps x at its initial value, 0. Nothing reports it.
Clone without do_copy
class incr_item extends uvm_sequence_item;
`uvm_object_utils(incr_item)
bit [7:0] x;
...
endclass
if (!$cast(c, t.clone())) `uvm_fatal("CLONE", "not an incr_item")
// c.x == 0
The example's clone keeper keeps 0, 0 and 0 while the pins carried 16, 32 and 48.
Clone with do_copy
function void do_copy(uvm_object rhs);
incr_item r;
super.do_copy(rhs);
if (!$cast(r, rhs)) `uvm_fatal("COPY", "not an incr_item")
x = r.x;
endfunction
Now clone keeps 16, 32 and 48, the same as copying the field by hand.
A copy is also only as deep as do_copy makes it. Assigning a queue member copies the queue; assigning a class-handle member copies the handle, so both objects share one header unless do_copy creates a separate one (U2.1 S4).
An empty FIFO with a busy checker
The slow checker receives through an analysis FIFO and works on each item for 50 ns, as U3.5 S2 described. Its FIFO empties when get returns, which is before the work is done.
The last item leaves the FIFO at 115 ns, but its 50 ns of work finishes at 165 ns.
So the example ends on completed work, wait (chk.done == 3), never on an empty FIFO. U7.3 gives the full rule for ending a test.
Try it
The monitor refills one object (reuse = 1), as a third-party monitor might. The handle keeper ends with three references to it, all 48. The clone keeper copies at the right time, but clone cannot copy a field that do_copy does not know, so it keeps zeros. Only the field-copy keeper has the pins' values.
Add the do_copy from the right-hand compare to incr_item and rerun: the clone keeper keeps 16 32 48. Then set reuse = 0. The handle keeper is now correct too, and the checker finishes 16, 32 and 48.
15 ns monitor write x=16 15 ns checker get x=16 45 ns monitor write x=32 65 ns checker done x=32 65 ns checker get x=32 75 ns monitor write x=48 115 ns checker done x=48 115 ns checker get x=48 165 ns checker done x=48 handle keeper kept 48 48 48 clone keeper kept 0 0 0 field-copy keeper kept 16 32 48 UVM_ERROR : 0Check yourself
Remember
- The publisher creates a fresh object for every transfer and never changes it after
write; that is the cheap default. - A listener that keeps handles copies inside
write, unless the publisher never changes a published object. - A listener that transforms data uses a local variable or a private clone, never the received object.
clone()copies only the fields that field macros ordo_copycover; a nested handle thatdo_copyassigns stays shared.- An empty analysis FIFO says nothing about work a consumer has already taken out; end on completed work.
Practice
- A scoreboard among careless listeners Verify · Medium