← Design Verification Interviews

Interview Question · Design

A Layered Testbench Before a Methodology

L3 Seniortestbencharchitecturemethodology

The Question

The interviewer asks

A team wants to move a block from pin-level directed tests to constrained random. Would you build a layered testbench in plain SystemVerilog first, or go straight to UVM? Defend the choice.

Reveal the Model Answer

Either way the bench needs the same layers. A driver and a monitor own the pins through an interface, a generator makes transactions, and a monitor feeds the scoreboard. Plain SystemVerilog first suits a small block or a team new to classes. Every handshake stays visible, and each layer maps onto a UVM agent later. For reusable IP, or a team that already has UVM in place, go straight to UVM. Otherwise you rebuild its factory, configuration and phasing by hand.

One Operation Through Every LayerA layered SystemVerilog bench for a FIFO, with one operation going through it. Arrows show handles and pin values moving between layers; a mailbox or the interface sits on every path.
One operation through every layerA layered SystemVerilog bench for a FIFO, with one operation going through it. Arrows show handles and pin values moving between layers; a mailbox or the interface sits on every path. process fifo_generator directed, then seededrandom queue req mailbox mailbox #(fifo_op),bound 1 process fifo_driver drives throughdrv_cb design fifo the design under test process fifo_scoreboard predicts with exp_q queue obs mailbox mailbox #(fifo_obs) process fifo_monitor samples throughmon_cb coverage fifo_scenarios counts the plannedcases put(op)get(op)pinspinsput(o)check(o)sample(o)
Read the diagram as text
  • fifo_generator: directed, then seeded random
  • req mailbox: mailbox #(fifo_op), bound 1
  • fifo_driver: drives through drv_cb
  • fifo: the design under test
  • fifo_scoreboard: predicts with exp_q
  • obs mailbox: mailbox #(fifo_obs)
  • fifo_monitor: samples through mon_cb
  • fifo_scenarios: counts the planned cases
  • fifo_generator → req mailbox: put(op)
  • req mailbox → fifo_driver: get(op)
  • fifo_driver → fifo: pins
  • fifo → fifo_monitor: pins
  • fifo_monitor → obs mailbox: put(o)
  • obs mailbox → fifo_scoreboard: check(o)
  • obs mailbox → fifo_scenarios: sample(o)

Each layer talks to the next only through a mailbox or the interface, so any one of them can be replaced on its own.

Self-Check

Tick each key point you covered in your answer.

What Interviewers Listen For (Mid-Level)

A good mid-level answer names the layers and what crosses each boundary: pins below the driver, transactions above it. It keeps checking out of the driver.

Cross-Questions

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

WhyProve ItBreak ItCompareScale It

  1. A Layered Testbench Before a Methodology
  2. WhyWhy the Monitor Feeds the ScoreboardL2 Mid-Level

    Why should the scoreboard get its transactions from a monitor instead of from the driver that sent them?

    Reveal the Model Answer

    The driver reports what it meant to send; the monitor reports what actually appeared on the pins. A driver bug that drops or corrupts a beat is invisible if the scoreboard trusts the driver. A monitor also works in a passive agent, where no driver exists, so the same checking reuses at subsystem level.

    Self-Check

    Tick each key point you covered in your answer.

    Misconception

    "It is the same data, so taking it from the driver saves a component."

    It is the same data only when the driver is correct, which is one of the things the bench should not assume.

    Source Spear & Tumbush, SystemVerilog for Verification (3rd ed.), ch. 1.10 (PDF p58-62)

  3. Break ItA Monitor That Reads at the EdgeL2 Mid-Level

    Break it: the monitor reads DUT outputs directly at @(posedge clk), with no clocking block. What can it get wrong?

    Reveal the Model Answer

    The monitor and the DUT flops then run at the same edge. The monitor may read a value before or after another process writes it, so it can log the wrong cycle's data. A clocking block samples its inputs at a fixed point before the edge. Then every read is the value from before the edge.

    Self-Check

    Tick each key point you covered in your answer.

    Misconception

    "The monitor only reads, so it cannot race."

    A read races with a write in another process at the same time. Their order is not defined, so the read can see either value.

    Source IEEE 1800-2017 §4.7

  4. CompareWhat UVM Adds to a Layered BenchL2 Mid-LevelL3 Senior

    Compare a hand-built layered bench with a UVM one. What do you get from UVM that you would otherwise write yourself?

    Reveal the Model Answer

    UVM adds a factory, so tests can swap classes without editing the bench. It adds a configuration database and phases with objections for start-up and end of test. It also gives standard TLM ports and one reporting system. The cost is a large library, a learning curve and slower compiles. A plain bench has none of that cost and none of that reuse.

    Self-Check

    Tick each key point you covered in your answer.

    Half-Truth

    "UVM makes the testbench find bugs faster."

    It organises the bench; the checking, coverage and stimulus quality still decide what is found.

  5. Prove ItSolve 7.7.3: Premature Test CompletionSimulator-graded

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

    Open Problem 7.7.3

  6. Scale ItReusing a Block Bench at Subsystem LevelL3 SeniorL4 Staff

    Two blocks you verified separately are now connected in a subsystem. How much of each block bench do you reuse, and how?

    Reveal the Model Answer

    At the internal boundary the drivers go away, because the other block drives those pins, so those agents become passive monitors. Their scoreboards and protocol checks keep running unchanged on real traffic. External interfaces keep active agents, and a subsystem scoreboard checks end-to-end behaviour from the outer monitors. Reuse is cheap only if the block benches kept checking out of the drivers.

    The Same Block Environment, Twice, in a Subsystem BenchBlock B's input is now block A's output. Arrows into a bus are driving; arrows out of a bus are observing.
    The same block environment, twice, in a subsystem benchBlock B's input is now block A's output. Arrows into a bus are driving; arrows out of a bus are observing. test sub_test_base Builds sub_env_cfgonce env sub env Passes each block itscfg; compares Aoutputs with B inputs block env blk_a env(active) drv, sqr, mon, pred,cmp block env blk_b env(passive) mon, pred, cmp; nodriver interface ext bus tb.ext, at thesubsystem pins design Block A RTL Shows y = x + 1 interface mid bus tb.dut.mid, internal cfgcfg.blk_acfg.blk_bdrivesxresult is B inputobserved
    1. Configure

      The test fills one sub_env_cfg. The sub env hands cfg.blk_a to blk_a and cfg.blk_b to blk_b; block B's agent config says UVM_PASSIVE.

      More context

      The first two lines of the run show the result: "blk_a UVM_ACTIVE agent builds: mon sqr drv" and "blk_b UVM_PASSIVE agent builds: mon".

      Follow the calls, values and waits
      1. data sub_env_cfg: sub_test_base → sub env
      2. data cfg.blk_a: sub env → blk_a env (active)
      3. data cfg.blk_b (passive agent): sub env → blk_b env (passive)
    2. Drive A15 ns (illustrative)

      Only block A's driver touches pins. It puts 16 on ext and the design takes it at 15 ns.

      More context

      Block B has no driver, so nothing else drives the subsystem. Left active, block B builds a driver that takes nothing, and the LINK check reports three results that never arrived.

      Follow the calls, values and waits
      1. data x = 16: blk_a env (active) → ext bus
      2. data x = 16: ext bus → Block A RTL
    3. Cross35 ns (illustrative)

      Block A shows 17 on its result pins. Inside the subsystem that result is block B's input, taken at 35 ns.

      More context

      At the same edge block A's own comparator logs "35 ns blk_a y=17 matches". One value is A's output and B's input at once.

      Follow the calls, values and waits
      1. data A result 17 = B input: Block A RTL → mid bus
    4. Observe B's input35 ns (illustrative)

      Block B's monitor watches mid and feeds its own predictor and comparator, unchanged from the block bench.

      More context

      B's monitor samples 17 at 35 ns, and its predictor expects 18. Nothing in block B's environment knows that its input came from block A.

      Follow the calls, values and waits
      1. data A result 17 = B input: mid bus → blk_b env (passive)
      2. Waiting: blk_b env (passive) — expects 18 from block B
    5. Check B55 ns (illustrative)

      B's result, 18, is checked at 55 ns.

      More context

      The log reads "55 ns blk_b y=18 matches". Each B result arrives 20 ns after the A result that fed it.

      Follow the calls, values and waits
      1. Released: blk_b env (passive)
    Read the diagram as text
    • sub_test_base: Builds sub_env_cfg once
    • sub env: Passes each block its cfg; compares A outputs with B inputs
    • blk_a env (active): drv, sqr, mon, pred, cmp
    • blk_b env (passive): mon, pred, cmp; no driver
    • ext bus: tb.ext, at the subsystem pins
    • Block A RTL: Shows y = x + 1
    • mid bus: tb.dut.mid, internal
    • sub_test_base → sub env: cfg
    • sub env → blk_a env (active): cfg.blk_a
    • sub env → blk_b env (passive): cfg.blk_b
    • blk_a env (active) → ext bus: drives
    • ext bus → Block A RTL: x
    • Block A RTL → mid bus: result is B input
    • mid bus → blk_b env (passive): observed

    Two copies of one environment class. What changed is one config field and where the virtual interface points.

    Self-Check

    Tick each key point you covered in your answer.

    What Interviewers Listen For (Staff)

    A good staff answer plans block benches for subsystem reuse from the start. It names the rule that makes reuse cheap: checking lives in monitors, and agents can be active or passive.

    Wrong Altitude

    Proposes writing a fresh subsystem bench and dropping the block checks.

    Block-level checks are the cheapest way to locate a failure at subsystem level. Without them, every mismatch becomes an end-to-end search.

What People Get Wrong

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

Wrong Altitude

"Just use UVM."

At this level the interviewer wants the layers and the trade-off, not a library name. Saying what each layer owns is the answer; UVM is one way to package it.

Half-Truth

"The driver already sees the pins, so it should check the responses too."

The driver may check its own handshake, but data checks belong in a monitor and scoreboard. There they still run when the agent is passive, and they do not share the driver's mistakes.

Buzzword Without Mechanism

Lists "generator, driver, monitor, scoreboard" but cannot say what each one passes to the next.

The point of layering is the boundaries: transactions above the driver, pins below it, observed transactions into the scoreboard.

Missed Corner

Ends the test when the generator has sent its last transaction.

Transactions still inside the DUT are never compared. The twin ends early and leaves three unchecked.

Run It

Eight transactions through a three-stage DUT; the test ends when the generator finishes.

RUNVerilator · simulator service
Edit the code, then run it
Expected: checked 5 of 8, 3 still in the scoreboard FAIL: the test ended with transactions still inside the DUT, so they were never checked
See the Fix
module tb;
  logic clk = 0;
  always #5 clk = ~clk;
  // A three-stage DUT that adds 1.
  logic [7:0] in_d = 0, p1, p2, out_d;
  logic in_v = 0, v1 = 0, v2 = 0, out_v = 0;
  always @(posedge clk) begin
    v1 <= in_v;  p1 <= in_d;
    v2 <= v1;    p2 <= p1;
    out_v <= v2; out_d <= p2 + 1;
  end
  // Scoreboard: expected values, in order.
  int exp_q[$];
  int checked = 0, errors = 0, e;
  always @(posedge clk) if (out_v) begin
    e = exp_q.pop_front();
    if (out_d != e) errors++;
    checked++;
  end
  initial begin
    for (int i = 0; i < 8; i++) begin @(negedge clk); in_v = 1; in_d = i; exp_q.push_back(i + 1); end
    @(negedge clk); in_v = 0;
    // the fix: drain the scoreboard, with a bound so a hang still ends
    for (int t = 0; t < 20 && exp_q.size() != 0; t++) @(posedge clk);
    #1 $display("checked %0d of 8, %0d still in the scoreboard", checked, exp_q.size());
    if (exp_q.size() != 0) $display("FAIL: the test ended with transactions still inside the DUT, so they were never checked");
    else $display("PASS: every transaction was checked before the end");
    $finish;
  end
endmodule

Prints: checked 8 of 8, 0 still in the scoreboard PASS: every transaction was checked before the end

Learn It, Then Prove It