A UVM sequence response is an item the driver sends back to the sequence that issued a request, usually to return read data or a status. The sequence collects it with get_response(), or receives it automatically in response_handler(). The driver returns it with item_done(rsp), put_response(rsp) or its rsp_port, after copying the request's IDs into it with set_id_info().
Many simple testbenches never use responses, because the driver can write results straight into the request object. Responses become necessary when the result arrives after the driver has already released the request, when one sequence keeps several requests in flight, or when the response is a different type from the request.
8 min read | Intermediate
What You'll Learn
- Return a response from the driver with the correct sequence and transaction IDs
- Collect responses in a sequence with
get_response() - Handle responses asynchronously with
response_handler() - Avoid response queue overflow and stuck sequences
Syntax
// In the sequence (uvm_sequence #(REQ, RSP), RSP defaults to REQ)
virtual task get_response(output RSP response, input int transaction_id = -1);
function void use_response_handler(bit enable);
virtual function void response_handler(uvm_sequence_item response);
function void set_response_queue_depth(int value); // default 8, -1 = unlimited
function void set_response_queue_error_report_disabled(bit value);
// In the driver
rsp.set_id_info(req); // copy sequence ID and transaction ID
seq_item_port.item_done(rsp); // finish the request and return rsp
seq_item_port.put_response(rsp); // return rsp without finishing anything
rsp_port.write(rsp); // via the driver's analysis port
When You Don't Need a Response
With the usual get_next_item() and item_done() handshake, finish_item() in the sequence does not return until the driver calls item_done(). So if the driver writes the result into the request object before calling item_done(), the sequence can read it straight from req:
// Driver
seq_item_port.get_next_item(m_req);
drive_read(m_req); // waits for the bus to return data
m_req.rdata = vif.rdata; // fill in the result
seq_item_port.item_done(); // no response object
// Sequence
start_item(req);
if (!req.randomize() with { write == 0; addr == 8'h20; }) `uvm_error("SEQ", "Randomization failed")
finish_item(req); // returns after item_done()
`uvm_info("SEQ", $sformatf("Read 0x%0h", req.rdata), UVM_MEDIUM)
This is fine for simple, one-at-a-time drivers. It breaks down as soon as the driver calls item_done() before the transfer is really over, which is exactly what a driver that overlaps several transfers does. Then the data has to travel back separately, as a response.
How a Response Finds Its Sequence
Several sequences can share one sequencer, so the sequencer needs to know which sequence a response belongs to. Every request carries a sequence ID and a transaction ID, set when the sequence sends it. rsp.set_id_info(req) copies both into the response. The sequencer then delivers the response to the right sequence, where it waits in that sequence's response queue.
Forget set_id_info() on a freshly created response and the sequencer cannot route it. It rejects the response with a fatal error about a missing sequence ID, and the simulation stops.
Example: Returning Read Data with get_response()
bus_item has random addr, write and data fields, plus rdata and ok fields that only the driver fills in. The driver builds a separate response for every request:
class bus_driver extends uvm_driver #(bus_item);
`uvm_component_utils(bus_driver)
// Rest of the code (constructor, build_phase getting vif)
virtual task run_phase(uvm_phase phase);
bus_item m_req, m_rsp;
forever begin
seq_item_port.get_next_item(m_req);
drive(m_req); // perform the bus transfer
$cast(m_rsp, m_req.clone()); // response starts as a copy
m_rsp.set_id_info(m_req); // route it to the right sequence
m_rsp.rdata = vif.rdata;
m_rsp.ok = !vif.err;
seq_item_port.item_done(m_rsp); // finish request, send response
end
endtask
endclass
The sequence collects one response for each request it sends:
class read_seq extends uvm_sequence #(bus_item);
`uvm_object_utils(read_seq)
// Rest of the code (constructor)
virtual task body();
bus_item m_rsp;
for (int i = 0; i < 4; i++) begin
req = bus_item::type_id::create("req");
start_item(req);
if (!req.randomize() with { write == 0; addr == 8'h20 + i; }) `uvm_error("SEQ", "Randomization failed")
finish_item(req);
get_response(m_rsp); // blocks until the response arrives
if (!m_rsp.ok) `uvm_error("SEQ", $sformatf("Read of 0x%0h failed", m_rsp.addr))
else `uvm_info("SEQ", $sformatf("0x%0h = 0x%0h", m_rsp.addr, m_rsp.rdata), UVM_MEDIUM)
end
endtask
endclass
get_response() blocks until a response is in the queue, then returns the oldest one. Because this sequence collects each response straight after its request, the queue never holds more than one.
Matching a specific request
When several requests are outstanding and responses may come back in a different order, pass the transaction ID of the request you are waiting for:
get_response(m_rsp, req.get_transaction_id()); // waits for this request's response only
Use this only for a response that is guaranteed to arrive. If the driver has already returned that response and it was collected by an earlier call, get_response() waits forever.
Quick Check
Q: The driver creates m_rsp with bus_rsp::type_id::create(), fills in the data and calls item_done(m_rsp), but leaves out m_rsp.set_id_info(m_req). What happens?
Show Answer
The sequencer cannot tell which sequence the response belongs to, because a new object has no sequence ID. It stops the simulation with a fatal error instead of delivering the response. The fix is one line: m_rsp.set_id_info(m_req) before returning it.
Handling Responses Asynchronously
Waiting for each response before sending the next request serializes everything. A sequence that wants to send requests back to back and process results as they arrive can switch to the response handler instead. After use_response_handler(1), the sequencer calls the sequence's response_handler() function for every response, and nothing goes into the response queue.
class stream_seq extends uvm_sequence #(bus_item);
`uvm_object_utils(stream_seq)
int unsigned n_rsp, n_err;
// Rest of the code (constructor)
virtual function void response_handler(uvm_sequence_item response);
bus_item r;
if (!$cast(r, response)) `uvm_fatal("SEQ", "Unexpected response type")
n_rsp++;
if (!r.ok) n_err++;
endfunction
virtual task body();
use_response_handler(1); // before the first request
repeat (50) begin
req = bus_item::type_id::create("req");
start_item(req);
if (!req.randomize()) `uvm_error("SEQ", "Randomization failed")
finish_item(req); // no get_response() here
end
wait (n_rsp == 50); // all responses handled
`uvm_info("SEQ", $sformatf("%0d responses, %0d errors", n_rsp, n_err), UVM_LOW)
endtask
endclass
response_handler() is a function, so it must not wait or consume time. Keep it to bookkeeping such as counting, storing or flagging, and do anything time-consuming in body(). Enable the handler at the top of body() rather than in pre_body(), because pre_body() is skipped when a parent starts this sequence with a `uvm_do macro.
The Response Queue and Overflow
Each sequence's response queue holds 8 responses by default. If the driver returns responses faster than the sequence calls get_response(), or the sequence never calls it at all, the ninth response overflows the queue. UVM drops it and reports an error.
That is the most common response problem: a driver that always returns a response, combined with a sequence written for the no-response style. There are three clean fixes:
- Collect every response with
get_response(), as read_seq does. - Use
response_handler(), as stream_seq does. - If the sequence genuinely doesn't care about responses, call
set_response_queue_depth(-1)for an unlimited queue, orset_response_queue_error_report_disabled(1)to drop overflow silently. Use these deliberately, because they also hide real bugs.
Different Request and Response Types
The response doesn't have to be the same class as the request. A read request may only need an address, while the response carries data and a status. Give the second parameter to the sequence, the sequencer and the driver:
class rd_seq extends uvm_sequence #(bus_req, bus_rsp); // get_response() returns bus_rsp
// ...
typedef uvm_sequencer #(bus_req, bus_rsp) bus_sequencer;
// ...
class bus_driver extends uvm_driver #(bus_req, bus_rsp);
// inside run_phase:
// m_rsp = bus_rsp::type_id::create("m_rsp");
// m_rsp.set_id_info(m_req);
// seq_item_port.item_done(m_rsp);
All three must agree on both types, otherwise the connection between driver and sequencer does not compile.
Which Driver Call to Use
item_done(rsp)finishes the current request and returns the response in one call. It suits drivers that complete one request at a time.put_response(rsp)returns a response without finishing a request. A driver that callsitem_done()early and returns results later uses this. With theget()style of handshake,put(rsp)plays the same role, as described in Using get() and put().rsp_port.write(rsp)sends the response through the driver's analysis port. It needsm_driver.rsp_port.connect(m_sequencer.rsp_export)in the agent, and is mostly found in drivers that already separate request and response handling into different threads.
Common Mistakes
Mistake #1: Forgetting set_id_info()
Without the request's IDs, the sequencer cannot route the response and the test ends in a fatal error. Call rsp.set_id_info(req) on every response, even when the response is a clone of the request, so the code keeps working if the response type changes later.
Mistake #2: Returning responses nobody collects
A driver that always calls item_done(rsp) overflows the queue of any sequence that doesn't call get_response(), after eight items. Make the driver and its sequences agree on whether responses are used.
Mistake #3: Calling get_response() with the handler enabled
Once use_response_handler(1) is set, responses go only to response_handler(). A get_response() call in the same sequence waits forever.
Mistake #4: Doing time-consuming work in response_handler()
response_handler() is a function called by the sequencer, so it cannot wait on events or clock edges. Record the response there and act on it in body().
Summary
In this article, you learned:
- A driver returns a response with
item_done(rsp),put_response(rsp)orrsp_port, always afterrsp.set_id_info(req) - A sequence collects responses in order with
get_response(), or by transaction ID use_response_handler(1)sends responses toresponse_handler()instead of the queue- The response queue holds 8 responses by default, and overflow is reported as an error
- Request and response can be different classes if the sequence, sequencer and driver all agree