Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here4
  • UVM Foundations14
  • UVM Phases & Simulation Lifecycle26
  • Factory & Configuration15
  • Transaction-Level Modeling (TLM)13
  • UVM Sequences & Stimulus21
  • UVM Agents & Protocol VIP21
    • Agent Architecture
      • What an Agent Owns, and What It Shows the World
      • Agent Configuration: Knobs, Config Objects and Who Owns Them
      • Mode-Gated Build: One Agent Class, Active or Passive
      • The Driver: Owning the Pins, One Transfer at a Time
      • The Monitor: Watch the Pins, Publish a Fresh Transaction
      • The Sequencer: The Agent's Stimulus Socket
      • Monitor Implementation Detail: Sampling, Races and X
      • Interface Abstraction: Virtual Interfaces vs Abstract BFMs
    • Reusable VIP: Active, Passive and Protocol Agents
    • Error and Fault Injection
    • Agent Patterns: Slaves, Reset, Interrupts and Pipelines
  • Environment & Test Classes11
  • Checking & Coverage in UVM21
  • UVM Register Abstraction Layer (RAL)22
  • Advanced UVM in Practice13
  • Debug & Regression10
  • Coding Practice47
  • Verification in Industry Practice10
  • Senior Prep: Architecture, Leadership and Interviews32
Loading lesson

‹Agent Architecture
  1. Topics
  2. UVM Verification
  3. UVM Agents & Protocol VIP
  4. Agent Architecture
  5. Monitor Implementation Detail: Sampling, Races and X

Part 6 · Agents & Protocol IP · Intermediate

Monitor Implementation Detail: Sampling, Races and X

Clocking blocks and #1step input skew, why the bench drives with NBAs after posedge and samples through mon_cb, SVA versus class checks, X policy, and what a monitor must never do.

On this page

    A random DMA test failed on two jobs out of three. Each time the DMA set STATUS.ERR: the packet had arrived one word short. The sequence had generated every item. The stream driver had called item_done for every one, and its own debug message said it had seen TREADY for the first beat. The DUT never took that beat. Driver and DUT had looked at the same signal on the same clock edge and disagreed about its value.

    How can two parts of one simulation see different values at the same instant, and how do you build a monitor and driver that never do?

    The idea: a photo finish, not a referee's eye

    A simulator processes each instant of time in ordered regions. When a clock edge happens, every process waiting on it wakes in the Active region, in an order the standard leaves undefined. Flip-flops evaluate their inputs there, and their outputs change later, in the NBA (nonblocking assignment) region. A testbench thread that reads a signal right after @(posedge clk) is racing every other process that woke on the same edge: if one of them changes that signal with a blocking assignment, the reader may see either value.

    A clocking block removes the race for sampling. Its input skew #1step means: sample in the Preponed region of the edge's time step, before anything in that instant has run. Like a photo-finish camera that fires just before the line, it records the value every signal had before the edge, which is exactly the value the DUT's flip-flops capture on that edge. Whatever happens during the edge cannot change the picture.

    The picture: one time step, in order

    Ordered regions of a time step: Preponed, where a #1step clocking block samples; Active, where processes woken by the clock edge run; Inactive; NBA, where nonblocking assignments, including the drivers' pin updates, take effect; Observed, where concurrent assertions evaluate; Reactive; and Postponed. Changes in NBA can wake more Active processes.
    The regions of one simulation time step. A clocking block with #1step samples in Preponed, before the edge's activity. The bench's drivers drive with nonblocking assignments, which take effect in NBA, after every process that woke on the edge has read the old values.

    Put the bench's two rules on this picture. The APB and stream monitors sample in Preponed, through mon_cb. (The interrupt monitor reads its one level line directly at the clock edge: irq changes only on a clock edge and the monitor looks for a change with !==, so a one-cycle-late view costs nothing. A fast multi-bit bus gets a clocking block.) Drivers change pins in NBA, by waiting on @(posedge clk) and assigning with <=. Every read a driver makes right after the edge happens in Active, before NBA, so it too sees pre-edge values. No reader is ever racing a writer.

    Build it

    The clocking block lives in the interface

    apb_if.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // snippet: from the course DMA bench
    interface apb_if (input logic clk, input logic rst_n);
      logic [7:0]  paddr;
      logic        psel, penable, pwrite;
      logic [31:0] pwdata, prdata;
      logic        pready, pslverr;
    
      // Monitors sample through mon_cb: #1step reads each signal just before the clock
      // edge, which is exactly the value the DUT samples on that edge. Drivers drive with
      // nonblocking assignments right after @(posedge clk) (see apb_driver).
      clocking mon_cb @(posedge clk);
        default input #1step;
        input paddr, psel, penable, pwrite, pwdata, prdata, pready, pslverr;
      endclocking
    endinterface

    mon_cb is declared in the interface, never in a class (a clocking block in a class does not compile). default input #1step sets the sampling point for every input, and the signal list makes each one readable as vif.mon_cb.psel. The monitor waits with @(vif.mon_cb), which fires on the clocking event, after the samples for that edge have been taken. There are no outputs in mon_cb: the bench's drivers do not drive through a clocking block, for a reason the war story explains.

    The driver side: one event, nonblocking drives

    Every wait in apb_driver and axis_driver is @(posedge vif.clk), and every pin assignment is <=. Two consequences follow. A driver's if (!vif.pready) right after the edge reads the pre-edge value, which matches what the monitor's mon_cb sampled and what the DUT saw. And every thread in the bench, in every agent, wakes on the same event object, so a hand-off from one thread to another (the APB driver finishing the GO write, the stream driver starting its first beat) cannot slip by an edge.

    Proof you can run

    The model below has a driver that changes a signal with a blocking write on the clock edge (the risky style), a monitor that reads the raw signal right after @(posedge clk), and one that samples through a #1step clocking block. The reference is the value the signal held mid-cycle.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    // A blocking write on the clock edge races a raw read; a #1step clocking
    // block always returns the value from before the edge.
    module tb_sample;
      logic clk = 0;
      always #5 clk = ~clk;
    
      logic v = 0;                 // driven with a BLOCKING write on the edge: risky style
      logic pre_edge = 0;          // the value v held in the middle of the cycle
      int   raw_saw_new = 0, cb_ok = 0, errors = 0;
    
      clocking mon_cb @(posedge clk);
        default input #1step;
        input v;
      endclocking
    
      always @(negedge clk) pre_edge = v;          // reference: the settled value
    
      initial begin                                 // the "driver"
        repeat (8) begin
          @(posedge clk);
          v = ~v;                                   // changes v in the same time step as the edge
        end
      end
    
      initial begin                                 // a monitor that reads the raw signal
        repeat (8) begin
          @(posedge clk);
          if (v !== pre_edge) raw_saw_new++;        // order-dependent: may see the new value
        end
      end
    
      initial begin                                 // a monitor that samples through mon_cb
        repeat (8) begin
          @(mon_cb);
          if (mon_cb.v === pre_edge) cb_ok++;
          else errors++;
        end
        #1;
        if (errors == 0 && cb_ok == 8)
          $display("PASS: mon_cb returned the pre-edge value 8 of 8 times (the raw read saw the new value %0d times on this simulator)", raw_saw_new);
        else
          $display("FAIL: mon_cb disagreed with the pre-edge value %0d times", errors);
        $finish;
      end
    endmodule

    On Verilator the raw read sees the new value all eight times; on another simulator, or after an unrelated edit changes process order, it may see the old one. That number is not something to rely on, which is the point. The clocking block's answer is the same everywhere.

    Protocol checks: SVA in the interface, or code in the class?

    A monitor can check protocol rules in its run_phase, but pin-level rules are better written as concurrent assertions in the interface (or in a checker module bound to it). They sample with the same preponed semantics, run every cycle whether or not a monitor exists, travel with the interface into passive chip-level benches, and are reused unchanged by formal tools and emulators. A sketch for the bench's APB interface:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // snippet: assertions that could live in apb_if
    property p_setup_then_access;      // a new transfer starts with a setup cycle, then access
      @(posedge clk) disable iff (!rst_n)
        (psel && !penable) |=> (psel && penable);
    endproperty
    property p_hold_until_ready;       // control and data stay stable through the access phase
      @(posedge clk) disable iff (!rst_n)
        (psel && penable && !pready) |=> (psel && penable && $stable({paddr, pwrite, pwdata}));
    endproperty
    property p_no_x_on_strobes;        // out of reset the strobes are always 0 or 1
      @(posedge clk) disable iff (!rst_n) !$isunknown({psel, penable});
    endproperty
    a_setup:  assert property (p_setup_then_access);
    a_hold:   assert property (p_hold_until_ready);
    a_no_x:   assert property (p_no_x_on_strobes);

    Check

    Best home

    Why

    Handshake order, signal stability, no X on strobes

    SVA in the interface or a bound checker

    Cycle-accurate, always on, reusable in formal and emulation, works with no UVM monitor

    Address in the register map, legal burst for this configuration

    Monitor or scoreboard class

    Needs configuration and transaction context that assertions do not have

    Turning checks off for an error-injection test

    Both: disable iff on a control bit, or a config field

    The test, not the checker, decides when a violation is expected

    X and Z: decide the policy, do not inherit one

    Look at the monitor's completion test: if (vif.mon_cb.psel && vif.mon_cb.penable && vif.mon_cb.pready). If PSEL is X, the condition is not true, so the monitor silently skips the cycle. A DUT bug that puts X on a strobe turns into missing transactions, which the scoreboard may blame on something else entirely. Decide the policy explicitly. On control lines (strobes, valid, ready), X out of reset is a bug: report it, from an assertion like a_no_x or with an uvm_error in the monitor using $isunknown. On data lines, X is legal when not qualified (PWDATA during a read, TDATA while TVALID is low) and a bug when qualified. During reset, X is expected; gate checks with disable iff (!rst_n). Use the 4-state operators (===, !==, $isunknown) for these checks; == against X yields X, and an if treats X as false.

    What a monitor must never do

    • Raise objections. A monitor sees traffic, not intent; a passive monitor on a bus that never goes idle would hold the test open forever. End of test belongs to the test, or to a scoreboard that uses phase_ready_to_end to wait for owed items; see End-of-test strategy.

    • Change an item after publishing it, or reuse one object for several publishes (see The monitor).

    • Drive or force any signal, even "just to help" with a default value. A monitor that drives is not reusable as a passive observer.

    Watch it run

    The random test from the opening is dma_rand_test. With the drivers on @(posedge clk) and nonblocking drives, and the monitors on mon_cb, all three jobs complete and every word is checked (the random keys and lengths differ by simulator and seed):

    Diagram
    UVM_INFO dma_tb.sv(731) @ 0: uvm_test_top [JOB] key=90fdd8fd len=10 irq_en=1
    UVM_INFO dma_tb.sv(731) @ 645000: uvm_test_top [JOB] key=0e564b57 len=1 irq_en=1
    UVM_INFO dma_tb.sv(731) @ 885000: uvm_test_top [JOB] key=5a1793f4 len=3 irq_en=1
    UVM_INFO dma_tb.sv(411) @ 1305000: uvm_test_top.env.sb [SB] 14 words matched, 0 mismatched
    UVM_ERROR :    0

    10 + 1 + 3 = 14 words: no beat lost at a job boundary, which is exactly where the bug used to strike.

    What breaks: two clocking blocks, one edge

    This happened while building the course bench. The first version gave each interface a driver clocking block, and each driver waited on its own: the APB driver on @(apb_vif.drv_cb), the stream driver on @(axis_vif.drv_cb). Both blocks were clocked by the same posedge clk.

    • Symptom: dma_rand_test failed on 2 of 3 jobs with STATUS.ERR; each packet arrived one word short, although the sequence generated N items and the driver called item_done N times.

    • Root cause: the APB driver finished the GO write on an edge. In the same time step, the stream driver received its first item and executed @(axis_vif.drv_cb). The two clocking blocks' events fire in an undefined order within the time step, and this time the stream block's event had not fired yet, so the stream driver caught the current edge instead of the next one. From then on its clocking-block drives and samples were offset from what the DUT saw: it "saw" TREADY for the first beat, but the DUT never took that beat.

    • Fix: every driver waits on @(posedge clk), one shared event, and drives with nonblocking assignments; monitors keep sampling through mon_cb with #1step. The rerun above is the result.

    The lesson is not "clocking blocks are bad". It is that every thread that hands work to another must wait on the same event. Separate clocking blocks are separate events, even on one clock. The bench keeps clocking blocks where they remove a race (sampling) and plain @(posedge clk) with NBAs where threads must stay in step (driving).

    Interview

    "What is a clocking block for, and what does #1step mean?"

    A weak answer: "It avoids race conditions between the testbench and the DUT." True, and it cannot answer the next question.

    A strong answer: "A clocking block fixes when the testbench samples and drives relative to a clock. Input skew #1step samples in the Preponed region of the edge's time step, so I get the value from just before the edge, which is what the DUT's flops captured, whatever else wakes on that edge. In our bench monitors sample through mon_cb with #1step, and drivers drive with nonblocking assignments right after posedge clk, so pin changes land in NBA after every reader has run. We learned not to give each driver its own clocking block: two blocks on one clock are two events with undefined relative order, and a thread handing off between them lost a beat."

    Expect the follow-ups: Where do protocol checks go? (pin-level rules as SVA in the interface or a bound checker; transaction rules in the monitor or scoreboard) and What does your monitor do with X on PSEL? (report it out of reset; an if treats X as false and would silently skip the transfer).

    Check yourself A monitor samples with @(posedge vif.clk) and reads vif.tready directly. It works today. What change elsewhere could break it without touching the monitor?

    Anything that makes TREADY change in the Active region of the edge: a testbench responder that drives TREADY with a blocking assignment, a TREADY that is combinational from a signal driven that way, or a simulator or compile-order change that reorders processes. The raw read then races the writer. Sampling through a #1step clocking block makes the monitor immune to all of them.

    Check yourself An error-injection test deliberately drives PENABLE without a setup cycle. The interface assertion a_setup fires and fails the test. What is the right fix?

    Make the check controllable, not weaker: guard it with a control bit (disable iff (!rst_n || !chk_en)) that the test clears for the window in which it injects, or have the test demote the expected failure. The test owns the decision that a violation is expected; the assertion stays on for every other test.

    Check yourself Why is X on TDATA while TVALID is low not an error, while X on TVALID is?

    TDATA carries meaning only when TVALID is high; while it is low, the protocol does not care what TDATA holds, and many designs leave it undriven. TVALID decides whether a transfer happens at all, so an unknown TVALID makes the bus state undefined, and a monitor's if would silently treat it as no transfer.

    Next: every class in this topic reached the pins through a virtual interface. In Interface abstraction you will see where that handle comes from, why parameterized interfaces make it painful, and the abstract-BFM pattern that emulation-friendly benches use instead.

    Related topics

    • Interface abstraction

    • The monitor

    • The driver handshake

    • End-of-test strategy

    Previous

    100 / 280

    Next: Interface Abstraction: Virtual Interfaces vs Abstract BFMs

    Up next

    Up nextInterface Abstraction: Virtual Interfaces vs Abstract BFMs

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • What an Agent Owns, and What It Shows the World
    • Agent Configuration: Knobs, Config Objects and Who Owns Them
    • Mode-Gated Build: One Agent Class, Active or Passive