← Design Verification Interviews

Interview Question · Code Reading

One Item, Two Subscribers

L1 JuniorL2 Mid-LevelL3 Senioruvmtlmanalysis

The Question

The interviewer asks

A monitor's analysis port feeds a coverage subscriber and a scoreboard. What does the scoreboard see, and why?

systemverilog
class nibble_cov extends uvm_subscriber #(bus_item);   // coverage needs the low nibble
  function void write(bus_item t);
    t.data &= 8'h0f;
  endfunction
endclass
// In the env:
mon.ap.connect(sb.analysis_export);      // scoreboard expects 3c, a5, f0
mon.ap.connect(cov.analysis_export);
// The monitor writes three items: 3c, a5, f0. What does the scoreboard see?
Reveal the Model Answer

It sees 0c, 05 and 00, so every compare fails. write() passes the same handle to each subscriber in turn, in zero time. The coverage subscriber masks the data in place. In uvm-core it runs before the scoreboard, because subscribers are called in name order. Subscribers must treat the item as read-only. One that needs to change it edits a clone().

One Object, Two SubscribersArrows from the monitor are write calls, numbered in the order uvm-core makes them. Arrows into the object are handles: nothing is copied.
One object, two subscribersArrows from the monitor are `write` calls, numbered in the order uvm-core makes them. Arrows into the object are handles: nothing is copied. publisher mon.ap.write(t) Writes 3c, then a5,then f0 subscriber cov: nibble_cov Called first: t.data&= 8'h0f subscriber sb: scoreboard Called second: sees0c, expects 3c shared object One bus_itemobject data 3c becomes 0cbefore thescoreboard reads it 1: write(t)2: write(t)edits in placereads the edited data
Read the diagram as text
  • mon.ap.write(t): Writes 3c, then a5, then f0
  • cov: nibble_cov: Called first: t.data &= 8'h0f
  • sb: scoreboard: Called second: sees 0c, expects 3c
  • One bus_item object: data 3c becomes 0c before the scoreboard reads it
  • mon.ap.write(t) → cov: nibble_cov: 1: write(t)
  • mon.ap.write(t) → sb: scoreboard: 2: write(t)
  • cov: nibble_cov → One bus_item object: edits in place
  • sb: scoreboard → One bus_item object: reads the edited data

Connect order does not decide who runs first. Any subscriber that changes the item must change a clone() instead.

Self-Check

Tick each key point you covered in your answer.

What Interviewers Listen For (Junior)

A good junior answer says "same handle" before running anything. It spots the place where the coverage subscriber changes the item.

Cross-Questions

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

WhyWhat IfProve ItCompareScale It

  1. One Item, Two Subscribers
  2. What IfA Rename That Hid the BugL2 Mid-Level

    Asked if they mention call order

    What if someone renames cov to zcov, and the mismatches disappear?

    Reveal the Model Answer

    The bug is still there; only the order changed. Subscribers run in name order in this library, so the scoreboard now reads the item before coverage edits it. Any later subscriber still sees corrupted data. Treat a fix that depends on names or order as a symptom, and remove the in-place edit.

    Self-Check

    Tick each key point you covered in your answer.

    Misconception

    "The rename fixed it."

    The edit still happens on the shared object. Only the scoreboard stopped seeing it, by luck of ordering.

    Source uvm-core 2020.3.1 (IEEE 1800.2 library) source, base/uvm_port_base.svh, m_imp_list[string]: connections keyed by name

  3. WhyWhy Analysis Write Is a FunctionL1 Junior

    Why is the analysis write() a function and not a task?

    Reveal the Model Answer

    A monitor must never be held up by its listeners, or it would miss the next transfer on the bus. A function cannot wait, so every subscriber finishes in zero time. A subscriber that needs time puts the item in an analysis FIFO and works on it in its own thread.

    One Write, Three ListenersArrows leaving in_mon are calls of write, numbered in the order this library makes them. The FIFO's arrow to the checker is a returned handle.
    call: who calls whomdata: where the transaction goes
    One write, three listenersArrows leaving `in_mon` are calls of `write`, numbered in the order this library makes them. The FIFO's arrow to the checker is a returned handle. publisher in_mon ap.write(t) at 10 and20 ns queue chk.fifo Analysis FIFO,unbounded subscriber cov uvm_subscriber:write(t) subscriber sb write_in(t) andwrite_out(r) component chk run_phase fifo.get(t), then 25ns of work publisher out_mon ap.write(r) at 15 and25 ns 1: write(t)2: write(t)3: write_in(t)t returns to get(t)write_out(r)
    1. Publish10 ns (illustrative)

      At 10 ns in_mon calls ap.write(#1) once. The port calls write on each of its three resolved imps, in turn, inside that one call.

      Follow the calls, values and waits
      1. call write(#1): in_mon → chk.fifo
      2. call write(#1): in_mon → cov
      3. call write(#1): in_mon → sb
    2. Return10 ns (illustrative)

      Every write is a function, so all three finish at 10 ns and in_mon goes on at once. The FIFO stored the handle; cov and sb did their work.

    3. Consume later10 ns (illustrative)

      The checker's own thread takes #1 from the FIFO at 10 ns and works on it for 25 ns. The monitor never waited for it.

      Follow the calls, values and waits
      1. return #1: chk.fifo → chk run_phase
      2. Waiting: chk run_phase — working on #1 until 35 ns
    4. Second input15 ns (illustrative)

      At 15 ns out_mon writes the result to the scoreboard's second imp, which calls write_out.

      Follow the calls, values and waits
      1. call write_out(#1): out_mon → sb
    5. Publish #2 into a busy checker20 ns (illustrative)

      At 20 ns in_mon writes #2. The FIFO stores it although the checker is still busy with #1.

      Follow the calls, values and waits
      1. call write(#2): in_mon → chk.fifo
      2. call write(#2): in_mon → cov
      3. call write(#2): in_mon → sb
    6. Take #235 ns (illustrative)

      At 35 ns the checker finishes #1 and takes #2, which has waited in the FIFO since 20 ns.

      Follow the calls, values and waits
      1. return #2: chk.fifo → chk run_phase
      2. Waiting: chk run_phase — working on #2 until 60 ns
      3. Released: chk run_phase
    7. Finish60 ns (illustrative)

      The checker finishes #2 at 60 ns: it took 2 and finished 2.

      Follow the calls, values and waits
      1. Released: chk run_phase
    Read the diagram as text
    • in_mon: ap.write(t) at 10 and 20 ns
    • chk.fifo: Analysis FIFO, unbounded
    • cov: uvm_subscriber: write(t)
    • sb: write_in(t) and write_out(r)
    • chk run_phase: fifo.get(t), then 25 ns of work
    • out_mon: ap.write(r) at 15 and 25 ns
    • in_mon → chk.fifo: 1: write(t)
    • in_mon → cov: 2: write(t)
    • in_mon → sb: 3: write_in(t)
    • chk.fifo → chk run_phase: t returns to get(t)
    • out_mon → sb: write_out(r)

    The publisher's code does not change with the number of listeners, zero included.

    Self-Check

    Tick each key point you covered in your answer.

    Misconception

    "Add a @(posedge clk) inside write() to wait for the DUT."

    A function cannot contain timing controls, so this does not compile.

    Source IEEE 1800.2-2020 §12.2.10.1.4 (write is a function)

  4. What IfA Subscriber That Changes the ItemL1 JuniorL2 Mid-Level

    What if one subscriber changes the item inside its write()?

    Reveal the Model Answer

    Every subscriber gets the same object, not a copy. So the change is seen by every subscriber called after it, and by the monitor too. Delivery order is not the connect order, so the effect can move when a component is renamed. Clone the item before changing it.

    Self-Check

    Tick each key point you covered in your answer.

    Misconception

    "Each subscriber gets its own copy of the item."

    write passes one handle to all subscribers. A change in one is visible to the others.

    Source Lesson U3.5 key points

  5. CompareAnalysis Port vs Put PortL2 Mid-Level

    Compare an analysis port with a put port for sending monitored items.

    Reveal the Model Answer

    An analysis port goes to any number of subscribers, including none, and never blocks. A put port goes to exactly one receiver and may block until it accepts. Monitors use analysis ports, so adding a coverage collector never changes the monitor's timing.

    Self-Check

    Tick each key point you covered in your answer.

    Misconception

    "An analysis port needs at least one subscriber."

    An analysis port with no connections is legal; write() then simply does nothing.

    Source Accellera, UVM 1.1 User's Guide, §2.5.1 Analysis Ports (PDF p24)

  6. Scale ItThirty Subscribers on One PortL3 Senior

    At SoC level one monitor feeds thirty subscribers: scoreboards, coverage, loggers. What risks do you manage?

    Reveal the Model Answer

    All thirty run inside the monitor's time step, so heavy work there slows every cycle. One careless subscriber that edits the item corrupts the rest. Keep write() short, move slow work into FIFOs read by separate threads, and make items read-only by rule. Loggers that print every item should be off by default.

    Self-Check

    Tick each key point you covered in your answer.

    Missed Corner

    "Fan-out is free because it takes zero simulation time."

    Zero simulated time is not zero wall-clock time. Thirty writes per item add real run time.

    Source Verification Academy, UVM Cookbook, Analysis Components: analysis port write behaviour (PDF p152-160)

  7. Prove ItSolve U7.2.1: A Scoreboard Among Careless ListenersSimulator-graded

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

    Open Problem U7.2.1

What People Get Wrong

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

Misconception

"write() gives each subscriber its own copy."

It hands the same object to every subscriber. The twin's coverage edit reaches the scoreboard.

Run It

The interview's bench: a monitor, a nibble coverage subscriber and a scoreboard.

RUNVerilator + UVM 2020.3.1 · simulator service
Edit the code, then run it
Expected among the UVM messages: sb got 0c, expected 3c sb got 05, expected a5 sb got 00, expected f0 FAIL: write() passes one handle to every subscriber, so the coverage edit reached the scoreboard
See the Fix
`timescale 1ns/1ns
`include "uvm_macros.svh"
import uvm_pkg::*;
class bus_item extends uvm_sequence_item;
  `uvm_object_utils(bus_item)
  rand bit [7:0] data;
  function new(string name = "bus_item"); super.new(name); endfunction
endclass
class bus_monitor extends uvm_component;
  `uvm_component_utils(bus_monitor)
  uvm_analysis_port #(bus_item) ap;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void build_phase(uvm_phase phase); ap = new("ap", this); endfunction
  task run_phase(uvm_phase phase);
    bit [7:0] seen[3] = '{8'h3c, 8'ha5, 8'hf0};
    phase.raise_objection(this);
    foreach (seen[i]) begin
      bus_item t = bus_item::type_id::create("t");
      t.data = seen[i];
      #10 ap.write(t);
    end
    phase.drop_objection(this);
  endtask
endclass
// Coverage only cares about the low nibble.
class nibble_cov extends uvm_subscriber #(bus_item);
  `uvm_component_utils(nibble_cov)
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void write(bus_item t);
    bus_item mine;
    $cast(mine, t.clone());          // the fix: edit a private copy
    mine.data &= 8'h0f;
  endfunction
endclass
class bus_sb extends uvm_subscriber #(bus_item);
  `uvm_component_utils(bus_sb)
  bit [7:0] expected[$] = '{8'h3c, 8'ha5, 8'hf0};
  int errors;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void write(bus_item t);
    bit [7:0] want = expected.pop_front();
    $display("sb got %h, expected %h", t.data, want);
    if (t.data != want) errors++;
  endfunction
endclass
class bus_env extends uvm_env;
  `uvm_component_utils(bus_env)
  bus_monitor mon; nibble_cov cov; bus_sb sb;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void build_phase(uvm_phase phase);
    mon = bus_monitor::type_id::create("mon", this);
    sb  = bus_sb::type_id::create("sb", this);
    cov = nibble_cov::type_id::create("cov", this);
  endfunction
  function void connect_phase(uvm_phase phase);
    mon.ap.connect(sb.analysis_export);
    mon.ap.connect(cov.analysis_export);
  endfunction
  function void check_phase(uvm_phase phase);
    if (sb.errors) $display("FAIL: write() passes one handle to every subscriber, so the coverage edit reached the scoreboard");
    else $display("PASS: the scoreboard saw what the monitor saw");
  endfunction
endclass
class fan_test extends uvm_test;
  `uvm_component_utils(fan_test)
  bus_env env;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void build_phase(uvm_phase phase); env = bus_env::type_id::create("env", this); endfunction
endclass
module tb;
  initial run_test("fan_test");
endmodule

Prints: sb got 3c, expected 3c sb got a5, expected a5 sb got f0, expected f0 PASS: the scoreboard saw what the monitor saw

Tool as Truth

"Subscribers are called in the order you connected them."

The scoreboard was connected first and still ran second. uvm-core keeps the connections in a table keyed by name.

Source uvm-core 2020.3.1 (IEEE 1800.2 library) source, base/uvm_port_base.svh, m_imp_list[string]

Half-Truth

"Let the monitor clone the item for each subscriber."

It works, but it pays for a copy per subscriber per item. The usual rule is read-only subscribers and copy only on write.

Source Verification Academy, UVM Cookbook, Coding Guidelines 10.1, manual copy on write (PDF p585)

Learn It, Then Prove It