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
    • Sequence Items and the Driver Handshake
    • Sequence Mechanics: Lifecycle, Sequencer, Responses and Control
    • Virtual Sequences and Coordination
      • The Coordination Problem
      • Virtual Sequencer Wiring
      • Declaring p_sequencer
      • Scenario Patterns and Placement
      • Sub-sequences and Sequence Libraries
      • Virtual Sequences Without a Virtual Sequencer
      • lock vs grab: Who Gets the Sequencer
      • Protocol Layering with Translator Sequences
      • Reactive (Slave) Sequences
  • UVM Agents & Protocol VIP21
  • 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

‹Virtual Sequences and Coordination
  1. Topics
  2. UVM Verification
  3. UVM Sequences & Stimulus
  4. Virtual Sequences and Coordination
  5. Reactive (Slave) Sequences

Part 5 · Sequences · Intermediate

Reactive (Slave) Sequences

Responder sequences that run forever and answer requests a monitor observed: an analysis FIFO on the sequencer, a TREADY backpressure responder for the DMA's output stream, the driver-waits-for-request pattern, and a memory-model slave.

On this page

    The base bench's output stream accepts every beat (initial m_ax.tready = 1'b1 in the top): the DMA can always send. Real downstream logic stalls, and the DMA's output register and its s_tready logic exist precisely to cope with that. No test has ever exercised it. To make TREADY random, the bench must react to the DUT: wait until it offers a beat, then decide how long to make it wait. Who decides, a driver or a sequence, and how does the sequence even know a beat is being offered?

    The idea: the shop counter

    So far, every sequence has been the active side: it decides what happens and the DUT responds. On a slave (or reactive) interface the roles flip. The DUT starts each transaction, and the bench answers: with TREADY after some delay, with read data, with an error response. A responder sequence is the sequence that answers. It runs forever, waits for a request, decides on a response, and sends it to the driver as an item.

    Think of a shop counter. The customer (the DUT) walks up; the shop assistant (the driver) handles the physical exchange; the manager in the back (the responder sequence) decides the policy: serve at once, or make them wait two minutes. The manager needs someone to say a customer has arrived, and that is the monitor's job. Keeping the decision in a sequence, not the driver, means a test can change the policy (no stalls, random stalls, long stalls, errors) by starting a different sequence or overriding one, with the same agent.

    Note

    This lesson is the sequence side only. Reactive slave agents, in the Agents section, builds the complete reactive AXI-Stream slave agent with configuration, reset handling and coverage.

    The picture: one beat on the output stream

    Sequence diagram: the DUT asserts TVALID, the monitor writes the beat into a FIFO on the sequencer, the responder sequence gets it and sends a delay item, the driver raises TREADY after the delay.
    A reactive loop: the monitor reports that the DUT is offering a beat, the responder sequence chooses a delay, and the driver applies it.

    Build it: random TREADY for the DMA's output

    The response item and a sequencer with a FIFO

    The item is the response: how many clocks to hold TREADY low. The sequencer gets an analysis FIFO, so a monitor can push requests into it and a sequence running on it can reach them through p_sequencer.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // snippet
    class axis_ready_item extends uvm_sequence_item;
      rand int unsigned delay;                   // clocks to hold TREADY low
      bit  [31:0]       data;                    // the beat that was offered
      constraint delay_c { delay dist { 0 := 4, [1:3] := 4, [4:6] := 2 }; }
      `uvm_object_utils(axis_ready_item)
      function new(string name = "axis_ready_item");
        super.new(name);
      endfunction
    endclass
    
    class axis_slave_sequencer extends uvm_sequencer #(axis_ready_item);
      `uvm_component_utils(axis_slave_sequencer)
      uvm_tlm_analysis_fifo #(axis_item) offer_fifo;
      function new(string name, uvm_component parent);
        super.new(name, parent);
        offer_fifo = new("offer_fifo", this);
      endfunction
    endclass

    The request monitor

    This monitor reports offers, not completed transfers: the first clock on which TVALID is high for a beat that has not yet been taken. It samples through the mon_cb clocking block like every monitor on the bench, and creates a fresh object for each offer.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // snippet: run_phase of axis_req_monitor
    task run_phase(uvm_phase phase);
      bit pending;
      forever begin
        @(vif.mon_cb);
        if (vif.mon_cb.tvalid && vif.mon_cb.tready) pending = 0;          // beat taken
        else if (vif.mon_cb.tvalid && !pending) begin                     // a new offer
          axis_item r = axis_item::type_id::create("r");
          r.data = vif.mon_cb.tdata;
          r.last = vif.mon_cb.tlast;
          pending = 1;
          req_ap.write(r);
        end
      end
    endtask

    The responder sequence

    The responder blocks on the FIFO, then does an ordinary start_item / randomize / finish_item. The finish_item returns when the driver calls item_done, which the driver does after the handshake, so the loop answers exactly one offer at a time.

    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
    // snippet
    class axis_ready_responder_seq extends uvm_sequence #(axis_ready_item);
      `uvm_object_utils(axis_ready_responder_seq)
      `uvm_declare_p_sequencer(axis_slave_sequencer)
      int unsigned n_served, stall_clocks;
      function new(string name = "axis_ready_responder_seq");
        super.new(name);
      endfunction
      task body();
        forever begin
          axis_item       offered;
          axis_ready_item rsp;
          p_sequencer.offer_fifo.get(offered);   // block until the DUT offers a beat
          rsp = axis_ready_item::type_id::create("rsp");
          start_item(rsp);
          if (!rsp.randomize()) `uvm_fatal("RAND", "axis_ready_item randomize failed")
          rsp.data = offered.data;
          finish_item(rsp);                      // returns after the handshake
          n_served++;
          stall_clocks += rsp.delay;
        end
      endtask
    endclass

    The driver and the wiring

    The driver is the bench's usual shape: wait on @(posedge clk), drive with nonblocking assignments. It holds TREADY low for delay clocks, raises it, and waits for the clock edge where TVALID was also high. In the agent's connect_phase, the monitor's port goes to the FIFO and the driver to the sequencer.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // snippet
    // axis_ready_driver::run_phase
    vif.tready <= 1'b0;
    forever begin
      seq_item_port.get_next_item(req);
      repeat (req.delay) @(posedge vif.clk);
      vif.tready <= 1'b1;
      do @(posedge vif.clk); while (!vif.tvalid);   // the beat moves on TVALID && TREADY
      vif.tready <= 1'b0;
      seq_item_port.item_done();
    end
    
    // axis_slave_agent::connect_phase
    mon.req_ap.connect(sqr.offer_fifo.analysis_export);
    drv.seq_item_port.connect(sqr.seq_item_export);
    
    // the test's run_phase: the responder lives as long as the test
    fork
      resp.start(slv.sqr);
    join_none

    For this run, the slave's driver takes over TREADY from the top's initial m_ax.tready = 1'b1 default, and its vif is the output interface. The bench now ships this pattern as axis_sink_agent (see Reactive slave agents). The bench's passive output agent and scoreboard are untouched: they keep checking every word, now with stalls.

    The other shape: the driver waits for the request

    Without a monitor, the driver itself can watch for the request and then ask the sequence what to do. The responder sequence calls start_item early, the driver's get_next_item returns an empty item, the driver waits on the pins for the request, fills the request fields into the item and calls item_done; the sequence reads the request after finish_item and sends a second item with the response. It needs no FIFO, but every item is a two-step exchange and the driver must understand requests. The monitor-and-FIFO shape keeps the driver dumb and lets the same monitor feed coverage.

    A memory-model slave is the same loop with state. For a bus where the DUT reads and writes memory, the responder keeps bit [31:0] mem[bit [31:0]]: a write request updates it, a read request is answered with mem[addr] (or a random value for an address never written). The sequence is the memory; the driver only moves bits.

    Watch it run

    A scratch run of the bench with the slave agent added (dma_backpressure_test, one 8-word job with key 0x12345678). The stall clocks are random:

    Diagram
    UVM_INFO dma_tb_scratch.sv(1307) @ 765000: uvm_test_top [RESP] answered 8 beats, 15 stall clocks, STATUS=00000002
    UVM_INFO dma_tb.sv(411) @ 765000: uvm_test_top.env.sb [SB] 8 words matched, 0 mismatched
    UVM_ERROR :    0
    • 8 beats answered for 8 words: one response per offer, no duplicates from the monitor and none missed.

    • 15 stall clocks of backpressure, and still 8 words matched: the DMA held each output word stable while TREADY was low and lost nothing.

    • STATUS=00000002: DONE alone. Backpressure on the output did not cause an error.

    What breaks

    The responder that held the test hostage

    A responder runs forever, so resp.start(slv.sqr) never returns. Call it directly in the test's run_phase, and nothing after it runs: no job is programmed, the objection never drops, and the test ends only at the timeout. A scratch test that does exactly that, with a 20 us timeout:

    Diagram
    UVM_FATAL uvm_phase_hopper.svh(598) @ 20000000: reporter [PH_TIMEOUT] Explicit timeout of 20000000 hit, indicating a probable testbench issue

    Start responders in fork ... join_none (or from the agent or env, so tests never forget). Do not raise an objection for them: a responder has no end, and end of test belongs to the test and the scoreboard.

    The FIFO name that was already taken

    The first version of the slave sequencer named its FIFO req_fifo. The run died at time 0:

    Diagram
    UVM_FATAL uvm_component.svh(1761) @ 0: req_fifo [CLDEXT] Cannot set 'req_fifo' as a child of 'uvm_test_top.slv.sqr', which already has a child by that name.

    uvm_sequencer already has a child component called req_fifo (its internal request FIFO), and component names must be unique among siblings. Renaming the member's instance name to offer_fifo fixed it. It is a good reminder that a class you extend comes with children you did not write.

    Interview

    "How do you model a slave interface in UVM?"

    Weak: "Put the response logic in the driver." It works, but every new response policy means a new driver.

    Strong: "A reactive agent: a monitor detects requests and writes them to an analysis FIFO on the sequencer; a responder sequence runs forever, gets each request through p_sequencer, randomizes a response item and sends it; the driver only applies it on the pins. Response policy then lives in sequences: zero-wait, random backpressure, error injection, a memory model. The responder is forked with join_none and never raises an objection. On our DMA I used it to drive random TREADY on the output stream, and the existing scoreboard kept checking every word."

    Follow-ups: Why a FIFO and not a direct call into the sequence? (sequences are objects without ports; the FIFO decouples the monitor's timing from the sequence), and How does a test change the policy? (start a different responder, or override the response item or sequence through the factory).

    Check yourself The request monitor reports an offer on every clock where TVALID is high, not just the first. What goes wrong?

    Every stalled clock produces another request, so the responder answers the same beat several times. The driver then raises TREADY for beats that do not exist yet, and the delays no longer mean what the sequence chose. The pending flag makes one request per beat.

    Check yourself A test wants no backpressure for its first job and heavy backpressure for its second. How, without touching the agent?

    Kill the first responder after the first job and start a second one with a different delay constraint (for example a derived item type through a factory override, or a sequence with a randomize() with on the delay). The monitor, FIFO and driver are unchanged.

    Next: that completes coordination across agents. Return to the topic map to review, or continue to TLM and connectivity, where the analysis FIFO used here is explained in full.

    Related topics

    • TLM FIFOs

    • Agent architecture

    • lock vs grab

    Previous

    93 / 280

    Next: UVM Agents & Protocol VIP

    Up next

    Up nextUVM Agents & Protocol VIP

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • The Coordination Problem
    • Virtual Sequencer Wiring
    • Declaring p_sequencer