Skip to content

UVM

Request Management

How the sequencer pairs requests with responses — routing each response by its ID into the originating sequence's bounded response queue, retrieved with get_response, and the overflow that follows when responses aren't consumed.

Sequencers · Module 11 · Page 11.3

The Engineering Problem

Module 11.2 showed how the sequencer's engine grants requests from its arbitration queue. But a request is only half the story when the driver answers — a read returns data, a transfer returns an ack. Those responses have to find their way back to the exact sequence (and the exact request) that originated them, across a sequencer that may be juggling many sequences and many outstanding requests. And there's a subtler problem: responses arrive on the driver's schedule, but the sequence consumes them on its own — so the sequencer must buffer them, and a buffer is bounded. Get the bookkeeping wrong and a response goes to the wrong sequence; get the buffering wrong and responses overflow and are lost.

This chapter is the sequencer's request/response management: how each response is paired with its request and routed to the right place, and how responses are buffered until consumed. The driver stamps a response with the request's IDs (set_id_info, Module 8.2) and returns it; the sequencer routes it — by sequence_id — into the originating sequence's response queue (a per-sequence, bounded buffer); and the sequence retrieves it with get_response (optionally matching by transaction_id). The catch is the bound: if responses arrive faster than get_response drains them — or the sequence never consumes them — the response queue overflows. This chapter is that machinery: the pairing, the routing, the per-sequence response queue, and why an unconsumed response queue overflows.

How does the sequencer pair each request with its response and route it back — the IDs, the per-sequence response queue, get_response — and why does an unconsumed (or slowly-consumed) response queue overflow?

Motivation — why responses need per-sequence buffered routing

The response side needs routing and buffering because of how producers and consumers are decoupled across the sequencer:

  • Many sequences share the sequencer, so responses must be routed. A response could belong to any of several running sequences. To deliver it to the right one, it must carry the originating sequence's identity (sequence_id) — which the driver stamps with set_id_info — and the sequencer routes by that ID. Without routing, a response could reach the wrong sequence.
  • A sequence may have many outstanding requests, so responses need a per-request ID. A single sequence can issue several requests before any responds. To match a response to the specific request it answers, the transaction_id distinguishes them, so get_response can fetch the response for a given request.
  • Producer and consumer run on different schedules, so responses are buffered. The driver returns responses when it finishes driving; the sequence consumes them when it gets around to it. These rates differ, so the sequencer buffers responses per sequence — a response queue — bridging the schedules.
  • Buffers are bounded, so consumption must keep up. A response queue can't be infinite, so it has a bounded depth. If responses arrive faster than they're consumed, the queue fills and overflows — so the sequence must drain it (or a handler must process responses as they arrive).
  • Not every flow needs responses, so the response path is optional. A fire-and-forget sequence neither expects nor consumes responses. If the driver returns them anyway, they pile up — so the response path must be matched: return-and-consume, or neither.

The motivation, in one line: responses must reach the exact originating sequence and request across a shared sequencer, on a different schedule than they're consumed — so the sequencer routes them by ID into per-sequence bounded buffers that the sequence drains, and the bound means consumption must keep up or the queue overflows.

Mental Model

Hold request management as a mailroom with a bounded inbox per recipient:

The sequencer is a mailroom: every response is a labeled letter, sorted into the recipient's inbox, and each inbox holds only so much — so a recipient who never picks up their mail overflows it. You send out a request (place an order). When the answer comes back, the sender of the answer (the driver) writes the original sender's address on it (set_id_info — the sequence_id) plus a reference number for which order it answers (transaction_id). The mailroom (the sequencer) sorts the letter by address into the recipient's inbox (the originating sequence's response queue). The recipient collects their mail (get_response) — and can ask for the letter matching a specific reference number (get_response by transaction_id) if they have several outstanding. But each inbox is bounded — it holds only a handful of letters. If letters arrive faster than the recipient collects them, or the recipient never collects, the inbox overflows and new letters are dropped (with a "mailbox full" notice). The remedies are the familiar ones: collect your mail (call get_response), set up forwarding/auto-processing (a response handler), get a bigger box (raise the queue depth), or — if you don't want mail at all — tell the sender to stop replying.

So request management is address, sort, collect, within a bounded box: the driver addresses the response by ID, the sequencer sorts it into the originating sequence's response queue, the sequence collects it — and an uncollected box overflows, which you fix by collecting, forwarding, enlarging, or stopping the replies.

Visual Explanation — the request/response round trip with the response queue

The defining picture is the round trip: a request goes out, the driver answers, and the response is routed by ID into the originating sequence's response queue, which the sequence drains.

A request is answered, the response stamped with IDs and routed into the originating sequence's bounded response queuesend (IDs)drive + buildresponseset_id_info(req)route by sequence_idget_response (drain)Sequencesends requests, drainsresponsesRequest itemsequence_id + transaction_idDriverdrives + builds responseResponse (stamped)IDs copied via set_id_infoResponse queue (per-sequence,bounded)routed by sequence_id12
Figure 1 — the request/response round trip through the response queue. A sequence sends a request (carrying its sequence_id/transaction_id). The driver drives it, builds a response, stamps it with the request's IDs (set_id_info), and returns it. The sequencer routes the response by sequence_id into the originating sequence's response queue — a per-sequence, bounded buffer. The sequence drains it with get_response (optionally matching by transaction_id). The response queue bridges the driver's schedule (when it returns responses) and the sequence's (when it consumes them).

The figure shows the full round trip and where the response queue sits. The sequence sends a request carrying its sequence_id and transaction_id (Module 8.2). The driver drives it, builds a response, stamps it with the request's IDs (set_id_info(req) — copying the identity so the response can be routed), and returns it. The sequencer routes the response by sequence_id into the originating sequence's response queue — and this is the piece this chapter adds: a per-sequence, bounded buffer that holds responses for that sequence. The sequence then drains the queue with get_response (optionally get_response(rsp, transaction_id) to fetch the response for a specific request). The crucial structural fact is the queue in the middle: it bridges two schedules — the driver returns responses when it finishes driving, and the sequence consumes them when it calls get_response. Because these schedules differ, the queue buffers; because the buffer is bounded (the warning coloring), the sequence must keep up draining it. So the round trip is send → drive → stamp → route-into-queue → drain, and the response queue is the per-sequence holding area that makes the decoupled timing work — and that overflows if the draining lags (the DebugLab). Everything about request management is getting the response into the right queue (routing by ID) and getting it out in time (draining before overflow).

RTL / Simulation Perspective — stamping, routing, and draining

Request management surfaces in three places: the driver stamps and returns the response, the sequencer routes it (automatically, by ID), and the sequence drains it with get_response. The code shows the driver and sequence ends.

returning, routing, and draining responses
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── DRIVER: build a response, STAMP it with the request's IDs, return it ──
task my_driver::run_phase(uvm_phase phase);
  forever begin
    seq_item_port.get_next_item(req);
    drive_on_pins(req);
    rsp = bus_item::type_id::create("rsp");
    rsp.set_id_info(req);              // ← stamp sequence_id + transaction_id (so it routes back)
    rsp.rdata = sampled_data;
    seq_item_port.item_done(rsp);      // return the response (routed into req's sequence's queue)
  end
endtask
//   the SEQUENCER routes rsp by sequence_id into the originating sequence's RESPONSE QUEUE
//   (per-sequence, bounded — default depth ~8)
 
// ── SEQUENCE: DRAIN the response queue — one get_response per request ──
task my_seq::body();
  repeat (N) begin
    req = bus_item::type_id::create("req");
    start_item(req); assert(req.randomize()); finish_item(req);
    get_response(rsp);                 // ← drain ONE response (else the queue fills → overflow)
    // get_response(rsp, req.get_transaction_id());  // match a SPECIFIC request's response
  end
endtask
 
// ── alternatives if you can't drain in lockstep ──
//   use_response_handler(1);  // process responses via a callback as they arrive (no polling)
//   set_response_queue_depth(64);                     // size the queue larger
//   set_response_queue_error_report_disabled(1);      // (if responses are intentionally dropped)

The code shows the three roles. On the driver side: it builds a response, calls rsp.set_id_info(req) to stamp it with the request's IDs (Module 8.2 — this is what lets the sequencer route it), and returns it with item_done(rsp). The sequencer then automatically routes the response by sequence_id into the originating sequence's response queue — a per-sequence, bounded buffer (default depth ~8). On the sequence side: after each finish_item, it calls get_response(rsp) to drain one response from its queue — and crucially, this should keep pace with the requests, one get_response per request that's answered, or the queue fills (the DebugLab). get_response(rsp, transaction_id) fetches the response for a specific request (when several are outstanding). The alternatives (the comments) handle cases where lockstep draining isn't practical: use_response_handler(1) processes responses via a callback as they arrive (no polling — the queue doesn't fill); set_response_queue_depth(64) enlarges the buffer; set_response_queue_error_report_disabled(1) silences the overflow warning if responses are intentionally dropped. The shape to carry: driver stamps and returns (set_id_info + item_done(rsp)), sequencer routes by ID into the per-sequence response queue, and sequence drains (get_response) — keeping up or the bounded queue overflows.

Verification Perspective — pairing a response to its request by ID

The routing depends entirely on IDs: sequence_id gets the response to the right sequence, and transaction_id matches it to the right request. Understanding the two-level pairing is understanding how responses find home.

Two-level pairing: sequence_id routes to the sequence's queue, transaction_id matches the specific requestset_id_info → route by sequence_id → drain by (optionally) transaction_idset_id_info → route by sequence_id → drain by (optionally) transaction_id1Driver: set_id_info(req)copy req's sequence_id (which sequence) + transaction_id (whichrequest) onto the response.2Sequencer: route by sequence_iddeliver the response into the originating sequence's responsequeue.3Sequence: get_response(rsp)drain the next response from this sequence's queue.4Or: get_response(rsp, transaction_id)fetch the response matching a SPECIFIC outstanding request, bytransaction_id.
Figure 2 — two-level pairing routes a response to its request. The driver's set_id_info copies the request's sequence_id (which sequence) and transaction_id (which request) onto the response. The sequencer uses sequence_id to route the response into the originating sequence's response queue. Within the sequence, get_response(rsp) drains the next response, or get_response(rsp, transaction_id) fetches the response matching a specific outstanding request. So sequence_id selects the queue; transaction_id selects the request within it. Both come from set_id_info.

The pairing is two-level, and both levels come from the same set_id_info. Level one — sequence_id (which sequence): the driver's set_id_info copies the request's sequence_id onto the response, and the sequencer uses it to route the response into the originating sequence's response queue. This is what gets the response to the right sequence among the many sharing the sequencer — without it, the response is an orphan (Module 8.2's hang). Level two — transaction_id (which request): set_id_info also copies the transaction_id, which distinguishes which of a sequence's (possibly several) outstanding requests this response answers. The sequence then drains its queue: get_response(rsp) fetches the next response (fine when there's one outstanding, or order doesn't matter), while get_response(rsp, transaction_id) fetches the response matching a specific request — essential when a sequence has multiple outstanding requests and must pair each answer to its request (a pipelined sequence sending several reads, then collecting each read's data). So sequence_id selects the queue, transaction_id selects the request within it — a two-level address, both halves stamped by set_id_info. The verification value is precision: in a pipelined flow with many outstanding requests, plain get_response returns them in arrival order, which may not match the request order — so get_response by transaction_id is how you reliably pair this answer to this request. The IDs aren't just for routing to the sequence; they're for matching within it.

Runtime / Execution Flow — the response queue and overflow

At run time, the response queue fills as the driver returns responses and drains as the sequence consumes them. When the fill rate exceeds the drain rate, the bounded queue overflows — the central runtime concern of request management.

The response queue fills as the driver returns responses and drains as the sequence consumes; overflow when fill exceeds draindriver returns (fill) vs sequence get_response (drain) → overflow if fill > draindriver returns (fill) vs sequence get_response (drain) → overflow if fill > drain1Driver returns responses (fill)each item_done(rsp) routes a response into the sequence's boundedqueue.2Sequence get_response (drain)each get_response removes one response from the queue.3Drain keeps pace → healthyif the sequence consumes as fast as responses arrive, the boundedqueue stays within depth.4Fill > drain → overflowresponses arrive faster than consumed (or never consumed) → queuefills → overflow, responses lost.
Figure 3 — the response queue fills and drains; overflow when fill outpaces drain. The driver returns responses (fill) on its schedule; the sequence consumes them with get_response (drain) on its own. If the sequence drains as fast as responses arrive, the bounded queue stays healthy. If responses arrive faster than get_response drains them — or the sequence never calls get_response — the queue fills to its depth and overflows: the sequencer warns and excess responses are lost. Remedies: drain in pace, use a response handler, enlarge the depth, or don't return responses you won't consume.

The runtime behavior is a producer/consumer balance on a bounded buffer. Fill (step 1): the driver returns responses on its schedule — each item_done(rsp) routes a response into the sequence's bounded response queue. Drain (step 2): the sequence consumes them on its schedule — each get_response removes one. Healthy (step 3): if the sequence drains as fast as responses arrive — one get_response per answered request, in pace — the bounded queue stays within depth, and everything flows. Overflow (step 4): if responses arrive faster than they're consumed — the sequence sends many requests but drains slowly, or never calls get_response at all — the queue fills to its depth and overflows: the sequencer issues a "response queue overflow" warning and excess responses are lost. The remedies map to rebalancing: drain in pace (call get_response per answered request — the simplest fix), use a response handler (use_response_handler(1) — responses are processed by a callback as they arrive, so the queue doesn't accumulate), enlarge the depth (set_response_queue_depth — more headroom if bursts are temporary), or — if responses are genuinely not needed — have the driver not return them (or disable the warning, accepting the drop). The key runtime insight is that the response queue is a bounded buffer between mismatched schedules, so request management is fundamentally a flow-control problem: the sequence must consume responses at the rate they're produced, or the buffer overflows — which is exactly the DebugLab.

Waveform Perspective — draining vs overflowing

The response queue's health is visible on a timeline: responses arriving (fill) against get_response calls (drain), with the queue occupancy rising toward overflow when draining lags.

Response queue: healthy draining vs overflow when get_response lags

12 cycles
Response queue: healthy draining vs overflow when get_response lagshealthy: drains (get_rsp) keep pace with fills (rsp_in) → q_depth lowhealthy: drains (get_r…sequence stops draining — responses keep arrivingsequence stops drainin…q_depth climbs toward the boundq_depth climbs toward …overflow (ovf): queue full → excess responses lostoverflow (ovf): queue …clkrsp_inget_rspq_depth000012345666ovft0t1t2t3t4t5t6t7t8t9t10t11
Figure 4 — the response queue occupancy over time. rsp_in pulses when the driver returns a response (fill); get_rsp pulses when the sequence drains one. q_depth shows occupancy. In the HEALTHY region, drains keep pace with fills, so q_depth stays low. In the OVERFLOW region, responses keep arriving (rsp_in) but the sequence stops draining (no get_rsp), so q_depth climbs to the bound and overflow fires (ovf pulse) — excess responses lost. The queue is a bounded buffer: consume at the fill rate, or it overflows.

The waveform shows the buffer balance and its failure. In the HEALTHY region, rsp_in (a response returned by the driver — fill) is matched by get_rsp (the sequence draining — drain), so the queue occupancy q_depth stays low — responses arrive and are consumed in pace. Then the sequence stops draining (no more get_rsp pulses) while the driver keeps returning responses (rsp_in continues): now fill with no drain, so q_depth climbs — 1, 2, 3… toward the bound. When it reaches the bound (here ~6), the OVERFLOW fires (ovf pulse): the queue is full, the sequencer warns, and excess responses are lost. The visual story is a bounded buffer filling: as long as drains keep pace with fills, occupancy stays low; the moment draining lags (or stops), occupancy rises until it hits the bound and overflows. The picture makes the flow-control reality concrete — the response queue is finite, so the sequence must consume responses at the rate they're produced. The ovf pulse is the symptom the DebugLab dissects: it's the queue saying "I'm full, and I'm dropping responses," which happens precisely when the sequence sends-and-doesn't-drain. The remedy is visible too: resume the get_rsp pulses (drain in pace), and q_depth falls back — consume the responses, and the queue stays healthy.

DebugLab — the response queue that overflowed

A 'response queue overflow' flood because the sequence sent requests but never consumed responses

Symptom

A sequence sent a long stream of requests, and the driver returned a response for each (item_done(rsp)). The log flooded with "response queue overflow" warnings, and downstream the responses seemed to go missing — a scoreboard that checked responses saw fewer than expected, as if the driver had dropped them. The requests all drove correctly; only the responses were warned about and lost. The sequence worked (it sent all its requests) but never seemed to use the responses.

Root cause

The driver returned a response for every request, but the sequence never called get_response — so the responses accumulated in the bounded response queue until it overflowed:

why responses piled up and overflowed
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
driver:  item_done(rsp);   // returns a response for EVERY request (fill)
 
sequence:  repeat (N) begin
             start_item(req); ... finish_item(req);
             // ✗ MISSING: get_response(rsp);   ← never drains the response queue
           end
 
→ responses fill the bounded response queue (default depth ~8) with NO draining
  → queue reaches depth → "response queue overflow" → excess responses DROPPED
  → the scoreboard sees fewer responses than requests (the dropped ones)
 
fix (pick one, by intent):
  (a) DRAIN: consume one response per request →   get_response(rsp);   after each finish_item
  (b) HANDLER: process responses as they arrive →  use_response_handler(1); + response_handler()
  (c) DON'T RETURN: if responses aren't needed, have the driver NOT return them (item_done; no rsp)
  (d) SIZE/SILENCE: set_response_queue_depth(...) / set_response_queue_error_report_disabled(1)

This is the unconsumed-response-queue overflow, the central request-management bug — and the opposite of Module 8.2's missing-response hang. There, the driver forgot set_id_info, so a response never arrived and get_response hung. Here, the driver correctly returns responses, but the sequence never consumes them: it sent its requests and never called get_response, so the per-sequence response queue — a bounded buffer (default depth ~8) — filled and overflowed, the sequencer warned, and excess responses were dropped (which is why the scoreboard saw fewer than it expected). The root issue is a producer/consumer mismatch: the driver produces responses (fill) but the sequence doesn't consume them (no drain), and the bounded queue can't hold an unbounded backlog. The fix depends on intent: if the sequence needs the responses, it must drain them — call get_response once per answered request (option a), or register a response handler so they're processed as they arrive (option b, which avoids polling); if the sequence doesn't need responses, the driver shouldn't return them (option c — item_done with no response), so they never pile up; and if dropping is acceptable, you can enlarge the queue or disable the warning (option d). The general lesson, and the request-management point: every response the driver returns must be consumed — by get_response or a handler — because the response queue is bounded, so a sequence that sends-and-doesn't-drain overflows it and loses responses. Match the response path: return-and-consume, or neither.

Diagnosis

The tell is a flood of "response queue overflow" with responses going missing. Diagnose response-queue bugs:

  1. Confirm the driver returns responses. If the driver calls item_done(rsp) (returns a response) but the sequence doesn't consume, responses accumulate — the overflow setup.
  2. Check whether the sequence calls get_response. A sequence that sends requests but never (or rarely) calls get_response doesn't drain the queue — the overflow cause.
  3. Correlate dropped responses with the warning. A scoreboard seeing fewer responses than requests, alongside the overflow warning, confirms responses are being dropped by the full queue.
  4. Decide intent. Determine whether the sequence needs responses (then drain or handle them) or doesn't (then the driver shouldn't return them) — the fix follows from the intent.
Prevention

Match the response path — return-and-consume, or neither:

  1. Drain every response the driver returns. Call get_response once per answered request (in pace), so the bounded queue stays healthy.
  2. Use a response handler for high-rate or async responses. use_response_handler(1) + response_handler() processes responses as they arrive, so the queue doesn't accumulate while the sequence does other work.
  3. Don't return responses the sequence won't use. For fire-and-forget flows, have the driver call item_done with no response — so nothing piles up.
  4. Size or silence deliberately, not as a crutch. set_response_queue_depth/set_response_queue_error_report_disabled are for genuine bursts or intentional drops — not a substitute for matching the response path.

The one-sentence lesson: the sequencer buffers each returned response in a bounded per-sequence response queue, so a sequence that sends requests but never consumes the responses (no get_response) overflows the queue and loses responses — match the response path by draining each response (get_response), handling them (use_response_handler), or not returning responses the sequence won't use.

Common Mistakes

  • Sending requests but never calling get_response. Returned responses fill the bounded queue and overflow; drain each (or use a handler), or don't return responses you won't use.
  • Returning responses a fire-and-forget sequence won't consume. They pile up and overflow; for flows that don't need responses, the driver should item_done with no response.
  • Using plain get_response with multiple outstanding requests. It returns responses in arrival order, which may not match request order; use get_response(rsp, transaction_id) to pair a specific request.
  • Treating the response queue as unbounded. It has a bounded depth (default ~8); consumption must keep pace with production, or it overflows.
  • Silencing the overflow warning instead of fixing the flow. Disabling the report hides a real producer/consumer mismatch; fix the draining unless drops are intentional.
  • Forgetting set_id_info on the response. Without it the response isn't routed (Module 8.2's hang) — the opposite failure from overflow, but also a request-management break.

Senior Design Review Notes

Interview Insights

Through a two-level identity, both halves stamped by the driver with set_id_info. When a sequence sends a request, the item carries a sequence_id, which identifies the originating sequence, and a transaction_id, which identifies the specific request within that sequence. When the driver builds a response, it calls set_id_info on the response, copying both IDs from the request. The sequencer then uses the sequence_id to route the response into the originating sequence's response queue — a per-sequence buffer — so the response reaches the right sequence among the many that may share the sequencer. Within the sequence, get_response drains the next response from its queue, or get_response with a transaction_id argument fetches the response matching a specific outstanding request. So the pairing is two-level: sequence_id selects which sequence's queue the response goes into, and transaction_id selects which request the response answers within that sequence. This matters most in pipelined flows where a sequence has several outstanding requests at once: plain get_response returns responses in arrival order, which may not match the order the requests were issued, so to reliably pair each answer with its request you use get_response by transaction_id. The whole mechanism depends on set_id_info being called on the response — without it, the response has no identity, so the sequencer can't route it to the originating sequence and get_response hangs waiting for a response that never arrives. So the request-response pairing is the IDs carried on the request, copied to the response by the driver, used by the sequencer to route and by the sequence to match.

The response queue is a per-sequence, bounded buffer inside the sequencer that holds responses routed to a sequence until the sequence consumes them. It exists because the driver returns responses on its own schedule — when it finishes driving each request — while the sequence consumes them on its schedule, when it calls get_response, and those rates differ, so the sequencer buffers responses to bridge the gap. The queue has a bounded depth, around 8 by default. It overflows when responses arrive faster than the sequence drains them, or when the sequence never calls get_response at all: the driver keeps returning responses that fill the queue, and with no draining the occupancy climbs to the depth limit, at which point the sequencer issues a response queue overflow warning and excess responses are dropped. The classic scenario is a sequence that sends a stream of requests and never consumes the responses, so a downstream check sees fewer responses than requests because the dropped ones never made it. The remedies follow from the cause: drain the queue by calling get_response once per answered request so consumption keeps pace; or register a response handler with use_response_handler so responses are processed by a callback as they arrive, which avoids accumulation; or, if the sequence doesn't need responses at all, have the driver not return them so nothing piles up; or, for genuine temporary bursts, enlarge the queue depth. The key point is that the response queue is a bounded buffer between mismatched producer and consumer schedules, so request management is a flow-control problem — consume responses at the rate they're produced, or the buffer overflows.

Exercises

  1. Trace the round trip. Describe a response's path from the driver building it to the sequence consuming it, naming set_id_info, the response queue, and get_response.
  2. Fix the overflow. A sequence sends requests, the driver returns responses, and the log floods with overflow warnings. Give two fixes for when the sequence needs the responses and one for when it doesn't.
  3. Pair the pipelined responses. A sequence has three outstanding read requests. Explain how to pair each returned response to its request, and why plain get_response may not suffice.
  4. Hang vs overflow. Contrast a get_response hang with a response-queue overflow — the cause and the fix of each.

Summary

  • Request management is how the sequencer pairs each request with its response and buffers it: the driver stamps the response with the request's IDs (set_id_infosequence_id, transaction_id) and returns it, the sequencer routes it by sequence_id into the originating sequence's response queue, and the sequence drains it with get_response.
  • The pairing is two-level: sequence_id selects the queue (which sequence), transaction_id selects the request within it — so get_response(rsp, transaction_id) pairs a specific answer to a specific request (essential with multiple outstanding requests).
  • The response queue is a per-sequence, bounded buffer (default depth ~8) bridging the driver's fill schedule and the sequence's drain schedule — so consumption must keep pace with production.
  • An unconsumed (or slowly-drained) response queue overflows: a sequence that sends requests but never calls get_response fills the queue → "response queue overflow" → responses lost — the opposite of Module 8.2's missing-response hang.
  • The durable rule of thumb: match the response path — every response the driver returns must be consumed (get_response, or a use_response_handler callback) or not returned at all — and pair multiple outstanding requests by transaction_id, because the per-sequence response queue is bounded, so sending-and-not-draining overflows it and loses responses.

Next — Sequence Scheduling: the engine grants and manages requests one at a time; the next chapter steps back to scheduling — how the sequencer interleaves multiple sequences over time, the order their items reach the driver, and the scheduling behavior that emerges from arbitration, locks, and relevance together.