Skip to content

UVM

Case Study — An AXI Agent

Build a UVM agent for AMBA AXI, the step up from APB — the five independent channels, the burst-aware transaction, a driver that runs the write address, write data, and write response channels concurrently, the VALID/READY rule that VALID must never wait for READY, outstanding and out-of-order transactions matched by ID, and the handshake deadlock that breaking the rule causes.

Industry Case Studies · Module 30 · Page 30.2

The Engineering Problem

The APB agent taught the pattern — item, driver, monitor, active/passive — on the simplest protocol: one transfer at a time, one address-then-data handshake. AXI is the same pattern scaled to the protocol that carries the high-bandwidth traffic on a chip, and the scaling changes the agent's shape. AXI has five independent channels, supports bursts of many beats per transaction, allows many outstanding transactions in flight at once, and lets responses return out of order. A single sequential driver no longer fits: the agent must run channels concurrently and match responses to requests by ID.

The four facts that reshape the agent. (1) Five channels: write address (AW), write data (W), write response (B), read address (AR), read data (R) — each with its own VALID/READY handshake, operating independently. (2) Bursts: one transaction is AxLEN+1 beats; the final write-data beat is flagged with WLAST. (3) Concurrency: because the channels are independent, the driver runs them as concurrent processes, not a sequence. (4) The handshake rule: a source asserts VALID without waiting for the destination's READY — breaking this rule deadlocks the bus, the single most important thing to get right.

How do you build a UVM agent for AXI — the five independent channels, a burst-aware transaction, a driver that runs the write address, data, and response channels concurrently, and the VALID/READY rule — so the agent handles outstanding, out-of-order bursts the way a single-channel APB agent never has to?

Motivation — why AXI is the real step up

AXI is where a verification engineer's agent-building skill is actually tested, because the protocol's concurrency breaks the simple sequential driver:

  • Concurrency is the new idea. APB is sequential — one transfer, fully complete, then the next. AXI's five channels run at once: write data can flow while the next write address is issued, and reads proceed independently of writes. The driver has to model this concurrency, which is a genuinely different design from APB's single loop.
  • Bursts make the transaction richer. One AXI transaction is many beats, so the item carries a data array and a length, and the driver loops beats with WLAST — a step up from APB's single data word.
  • Outstanding and out-of-order are why IDs exist. AXI lets multiple transactions be in flight and lets responses come back in a different order, identified by ID — the feature that gives AXI its bandwidth and the reason the scoreboard must match by ID, not by order.
  • The handshake rule is a real, famous bug. The rule that VALID must not wait for READY is exactly the kind of protocol subtlety that, broken, causes a silent deadlock — and it is one of the most common AXI bring-up bugs, so building it in correctly is the lesson.

The motivation, in one line: AXI scales the APB agent pattern with concurrency, bursts, and outstanding out-of-order transactions, so building it is where you learn to model independent concurrent channels, ID-matched responses, and the VALID/READY discipline that keeps the bus from deadlocking.

Mental Model

Hold the AXI agent as a busy seaport with five independent docks, not APB's single service counter:

APB is a single service counter: one customer at a time, hand over the form, wait for it to be processed, take the receipt, next customer. AXI is a container seaport, and the difference is everything. The port has five separate docks operating independently and simultaneously: an inbound-manifest dock where ships declare what they are delivering (write address), a cargo-unloading dock where the containers actually come off (write data), an outbound-receipt dock where delivery confirmations are issued (write response), and two more for the pickup side (read address and read data). These docks do not take turns — while one ship's cargo is being unloaded, the next ship is already filing its manifest, and confirmations for earlier ships go out on their own dock meanwhile. Every ship carries an ID painted on its hull, which is what makes the chaos manageable: the port can accept a dozen manifests before any cargo is unloaded (outstanding), and it can issue confirmations in whatever order the unloading finishes, not the order the manifests arrived (out of order), because the ID on each confirmation says which ship it belongs to. And there is one ironclad rule at every dock that keeps the port from gridlocking: a ship raises its "ready to transfer" flag whenever it has cargo to move, and it does not first wait to see the dock's "ready to receive" flag — both sides raise their flags independently, and the transfer happens the moment both are up. If a ship refused to raise its flag until it saw the dock's flag, and the dock refused to raise its flag until it saw the ship's, both would wait forever, staring at each other, and the whole port would freeze. APB is a single service counter — one at a time, sequential. AXI is a container seaport with five independent docks operating simultaneously: a manifest dock (write address), a cargo-unloading dock (write data), a confirmation dock (write response), and two for pickup (read address, read data). The docks do not take turns. Every ship carries an ID, which is what lets the port accept many manifests before unloading (outstanding) and issue confirmations out of order (matched by ID). And one ironclad rule prevents gridlock: a ship raises its ready flag without first waiting to see the dock's flag — both sides raise independently, and transfer happens when both are up. If each waited to see the other's flag first, both would wait forever and the port would freeze.

So building the AXI agent is running the seaport: the transaction is a ship with an ID and a hold full of containers (the burst beats); the driver works the five docks concurrently (concurrent processes per channel), flagging the last container with WLAST; the handshake at every dock follows the rule — assert VALID independent of READY — and the IDs let responses come back out of order, matched to their requests. The deadlock you must never write is the two-sided wait where VALID is conditioned on READY.

The Five Channels in One Figure

AXI's five channels split into a write group and a read group. Each is an independent VALID/READY handshake; the groups operate concurrently.

The five AXI channels: AW, W, B for writes and AR, R for reads, each an independent VALID/READY handshakeAW — Write Addressid, addr, len (beats−1), size, burst type; one per write transactionid, addr, len (beats−1), size, burst type; one per write transactionW — Write Dataone transfer per burst beat; WLAST flags the final beatone transfer per burst beat; WLAST flags the final beatB — Write Responseone bresp per transaction, returned with its BID (may be out of order)one bresp per transaction, returned with its BID (may be out of order)AR — Read Addressid, addr, len, size, burst type; one per read transactionid, addr, len, size, burst type; one per read transactionR — Read Dataone transfer per beat with a per-beat rresp; RLAST flags the final beatone transfer per beat with a per-beat rresp; RLAST flags the final beat
Figure 1 — the five AXI channels. Write uses three: AW (write address — id, addr, len, size, burst), W (write data — one transfer per burst beat, WLAST on the final beat), and B (write response — one bresp per transaction, returned with its BID). Read uses two: AR (read address, like AW) and R (read data — one transfer per beat with a per-beat rresp, RLAST on the final beat). Every channel is an independent VALID/READY handshake, and the channels run concurrently — write data can flow while the next write address is issued, and reads proceed independently of writes. IDs on every channel let many transactions be outstanding and responses return out of order.

The figure is the protocol's structure. Writes use three channels: AW carries the address and burst parameters once per transaction; W carries the data, one transfer per beat, with WLAST on the final beat; B returns a single response per transaction, tagged with BID. Reads use two: AR mirrors AW, and R returns the read data, one beat at a time with a per-beat response and RLAST on the last. The defining property is independence: the five handshakes are separate, so the channels operate concurrently — and that concurrency is exactly what the driver must model. The IDs threaded through every channel are what make outstanding, out-of-order traffic possible: a B response carries the BID that says which AW it answers, so responses need not come back in request order.

The Independent-Handshake Rule

Every AXI channel uses the same VALID/READY handshake, and it has one rule that, broken, deadlocks the bus: a source asserts VALID without waiting for READY.

The sequence-to-sequencer-to-driver handshake feeding the AXI channel drivers, each channel using an independent VALID/READY handshake onto the bussequencesequencerdriverDUTstart_item(req)get_next_item(req)drive vif signalsresponseitem_done()finish_item()
Figure 2 — the AXI VALID/READY handshake and its rule. The source (master, for an address or write-data channel) drives the payload and asserts VALID as soon as it has something to send — it must NOT wait to see READY first. The destination asserts READY when it can accept. The transfer occurs on the rising edge where both VALID and READY are high. Once asserted, VALID must remain high (and the payload stable) until the transfer completes. The forbidden pattern is conditioning VALID on READY: if the master waits for READY before asserting VALID and the slave waits for VALID before asserting READY, both wait forever — a handshake deadlock.

The rule is precise and asymmetric. A source (the master on AW, W, AR; the slave on B, R) asserts VALID the moment it has a payload to send, and the payload plus VALID must stay stable until the transfer completes. A destination asserts READY when it can accept. The transfer happens on the cycle both are high. The forbidden pattern is making VALID depend on READY: the protocol explicitly forbids a source from waiting to observe READY before asserting VALID. The reason is the deadlock in the caption — if both sides wait to see the other's flag first, neither ever raises its own, and the channel hangs. (The reverse is allowed: a destination may wait for VALID before asserting READY.) This single rule is the difference between an AXI driver that works and one that hangs at bring-up — and it is the DebugLab below.

The Agent Hierarchy

The AXI agent has the same outer shape as APB — sequencer, driver, monitor under an agent, with a scoreboard at env level — but the driver internally runs concurrent per-channel processes rather than one loop.

AXI agent hierarchy: axi_test contains env, which contains axi_agent and axi_scoreboard; the agent contains axi_sequencer, axi_driver, and axi_monitorseqrseqraxi_sequenceraxi_sequencerdrvdrvaxi_driveraxi_drivermonmonaxi_monitoraxi_monitoragentagentaxi_agentaxi_agentsbsbaxi_scoreboardaxi_scoreboardenvenvaxi_envaxi_envaxi_testaxi_testuvm_testuvm_test
Figure 3 — the AXI agent in the testbench tree. The axi_test builds an env containing the axi_agent and an axi_scoreboard. The agent always builds the axi_monitor, and — when active — the axi_sequencer and axi_driver. The outer shape matches APB; what differs is inside the driver, which runs the AW, W, and B (and AR, R) channels as concurrent processes, and inside the monitor, which watches all five channels concurrently and reconstructs whole burst transactions matched by ID.

The hierarchy is deliberately the same agent shape you learned for APB — the reusable-agent pattern does not change with the protocol. The monitor is unconditional, the sequencer and driver are built only when active, and the driver connects to the sequencer in connect_phase. What is new lives inside the components: the driver's run_phase forks concurrent channel processes instead of running a single sequential transfer, and the monitor watches all five channels at once, assembling each multi-beat burst and matching responses to requests by ID before it broadcasts a completed transaction. The lesson is that the architecture scales without changing while the channel handling grows richer.

The Transaction

The AXI transaction is burst-aware: it carries the ID, the address, the burst parameters, an array of data beats, and a per-beat response — far richer than APB's single word.

axi_seq_item — a burst-aware AXI transaction
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
typedef enum bit [1:0] { FIXED = 2'b00, INCR = 2'b01, WRAP = 2'b10 } axi_burst_e;
 
class axi_seq_item extends uvm_sequence_item;
  rand bit [3:0]    id;
  rand bit [31:0]   addr;
  rand bit [7:0]    len;       // AxLEN: number of beats minus 1
  rand bit [2:0]    size;      // AxSIZE: bytes per beat = 2**size
  rand axi_burst_e  burst;     // FIXED / INCR / WRAP
  rand bit          write;     // 1 = write, 0 = read
  rand bit [31:0]   data[];    // one entry per beat (len+1 entries)
  bit [1:0]         resp[];    // response per beat (R) or one entry (B)
 
  constraint c_beats { data.size() == len + 1; }
  constraint c_size  { size <= 2; }            // up to 4 bytes/beat on a 32-bit bus
 
  `uvm_object_utils_begin(axi_seq_item)
    `uvm_field_int(id,    UVM_ALL_ON)
    `uvm_field_int(addr,  UVM_ALL_ON)
    `uvm_field_int(len,   UVM_ALL_ON)
    `uvm_field_int(write, UVM_ALL_ON)
    `uvm_field_array_int(data, UVM_ALL_ON)
  `uvm_object_utils_end
 
  function new(string name = "axi_seq_item");
    super.new(name);
  endfunction
endclass

The item models the whole burst as one transaction. len is AxLEN — beats minus one — and the c_beats constraint keeps the data array sized to match (len + 1 entries). size is the bytes-per-beat exponent, burst selects the address pattern (INCR is the common one), and id is the transaction identifier that lets this transaction be outstanding and its response come back out of order. The per-beat resp array holds the read responses (one per R beat) or the single write response (B). Registering data with `uvm_field_array_int gives the dynamic array automatic copy, compare, and print. One item, one burst — the driver expands it into multiple channel transfers.

The Write Path — running AW, W, and B concurrently

A write spans three channels. The driver drives the address on AW, streams the data beats on W (flagging WLAST), and collects the response on B — and because the channels are independent, it runs them as concurrent processes, each obeying the VALID/READY rule.

axi_driver — the write path: concurrent AW / W / B, VALID never waits for READY
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class axi_driver extends uvm_driver #(axi_seq_item);
  `uvm_component_utils(axi_driver)
  virtual axi_if vif;
  // ... new(), build_phase get vif (guarded with `uvm_fatal) ...
 
  task run_phase(uvm_phase phase);
    reset_outputs();
    forever begin
      axi_seq_item tr;
      seq_item_port.get_next_item(tr);
      if (tr.write) drive_write(tr);
      else          drive_read(tr);
      seq_item_port.item_done();
    end
  endtask
 
  task drive_write(axi_seq_item tr);
    // The three write channels are independent → run them concurrently.
    fork
      drive_aw(tr);          // write address
      drive_w(tr);           // write data beats
      collect_b(tr);         // write response
    join                     // transaction done when all three complete
  endtask
 
  task drive_aw(axi_seq_item tr);
    @(posedge vif.clk);
    vif.awid    <= tr.id;
    vif.awaddr  <= tr.addr;
    vif.awlen   <= tr.len;
    vif.awsize  <= tr.size;
    vif.awburst <= tr.burst;
    vif.awvalid <= 1'b1;                          // assert VALID — do NOT wait for AWREADY
    do @(posedge vif.clk); while (!vif.awready);  // transfer when both high
    vif.awvalid <= 1'b0;
  endtask
 
  task drive_w(axi_seq_item tr);
    foreach (tr.data[i]) begin
      @(posedge vif.clk);
      vif.wdata  <= tr.data[i];
      vif.wlast  <= (i == tr.len);                // WLAST on the final beat
      vif.wvalid <= 1'b1;                          // assert VALID — do NOT wait for WREADY
      do @(posedge vif.clk); while (!vif.wready);  // beat transfers when both high
    end
    vif.wvalid <= 1'b0; vif.wlast <= 1'b0;
  endtask
 
  task collect_b(axi_seq_item tr);
    vif.bready <= 1'b1;                            // ready to accept the response
    do @(posedge vif.clk); while (!vif.bvalid);   // wait for the slave's B
    tr.resp = new[1]; tr.resp[0] = vif.bresp;      // BID == tr.id identifies it
    vif.bready <= 1'b0;
  endtask
endclass

drive_write is the heart of the difference from APB: a fork … join runs the AW, W, and B channels concurrently, because they are independent and may overlap — the slave can accept write data before, during, or after it accepts the address, and the response comes whenever the slave is done. Each channel task is a VALID/READY handshake that asserts VALID first and then waits for READY (the do … while polls READY after VALID is up) — never the reverse, which is the deadlock. drive_w loops the data array, asserting WLAST on the final beat (i == tr.len) so the slave knows the burst is complete. collect_b raises BREADY and waits for BVALID, capturing the response whose BID matches this transaction's id. Phase placement is unchanged: vif in build_phase, all driving in run_phase.

The Read Path and the Monitor

A read uses AR and R: drive the address, then collect len + 1 data beats until RLAST. The monitor watches every channel concurrently and reconstructs whole bursts, matching by ID.

axi_driver read path + axi_monitor sketch
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  task drive_read(axi_seq_item tr);
    fork
      drive_ar(tr);                                // read address (like AW)
      collect_r(tr);                               // read data beats
    join
  endtask
 
  task collect_r(axi_seq_item tr);
    int i = 0;
    tr.data = new[tr.len + 1]; tr.resp = new[tr.len + 1];
    vif.rready <= 1'b1;
    forever begin
      do @(posedge vif.clk); while (!vif.rvalid);  // each beat: wait for RVALID
      tr.data[i] = vif.rdata;
      tr.resp[i] = vif.rresp;                       // per-beat response
      if (vif.rlast) break;                         // RLAST ends the burst
      i++;
    end
    vif.rready <= 1'b0;
  endtask
axi_monitor — watch all channels, assemble bursts, broadcast by ID
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class axi_monitor extends uvm_monitor;
  `uvm_component_utils(axi_monitor)
  virtual axi_if vif;
  uvm_analysis_port #(axi_seq_item) ap;
  // ... new() creates ap; build_phase gets vif ...
 
  task run_phase(uvm_phase phase);
    fork
      monitor_writes();   // observe AW + W (+ B), assemble each write burst, ap.write(tr)
      monitor_reads();    // observe AR + R, assemble each read burst, ap.write(tr)
    join                  // all channels watched concurrently
  endtask
endclass

The read path mirrors the write but with two channels: drive_ar issues the address and collect_r loops beats, sizing the data and response arrays to the burst and ending on RLAST. The monitor's run_phase forks concurrent observers — one assembling write bursts from AW/W/B, one assembling read bursts from AR/R — because, like the driver, it must watch independent channels at once. Each observer reconstructs a whole multi-beat transaction from the pins and broadcasts it on the analysis port, matched to its request by ID, so the scoreboard receives complete, identified transactions to check.

Outstanding and Out-of-Order, by ID

Because every channel carries an ID, a master can issue several addresses before any response returns (outstanding), and the slave can return responses in a different order than the requests (out of order) — each response routed to its request by matching BID/RID to the original AWID/ARID. A production driver therefore does not block one transaction's fork … join before starting the next; it launches transactions with fork … join_none and tracks them by ID, and the scoreboard keys its expected-versus-observed matching on ID rather than on arrival order. The agent shown here drives one transaction at a time for clarity, but the transaction's id field and the by-ID monitor reconstruction are exactly what a multi-outstanding extension builds on — the structure is already ID-aware.

Waveform Perspective — an AXI write burst

A two-beat write burst shows the channels operating independently: the address handshakes once on AW, the two data beats handshake on W with WLAST on the second, and the response returns on B — each on its own VALID/READY pair.

AXI write burst: AW (one transfer) + W (two beats, WLAST on the last) + B (response)

6 cycles
AXI write burst: AW (one transfer) + W (two beats, WLAST on the last) + B (response)AW: AWVALID & AWREADY high → address transfers (id, addr, len=1)AW: AWVALID & AWREADY …W beat 0: WVALID & WREADY high → first data beatW beat 0: WVALID & WRE…W beat 1: WLAST asserted → final data beat transfersW beat 1: WLAST assert…B: BVALID & BREADY high → write response (BID == AWID)B: BVALID & BREADY hig…clkawvalidawreadywvalidwreadywlastwdata----D0D1----bvalidbreadyt0t1t2t3t4t5
Figure 4 — an AXI write burst of two beats. AW handshakes once in cycle 1 (AWVALID & AWREADY both high) carrying the address and len=1. W transfers two beats: beat 0 in cycle 2 and beat 1 in cycle 3 with WLAST asserted, each on its own WVALID & WREADY handshake. B returns the response in cycle 4 (BVALID & BREADY). The channels are independent handshakes — here AW completes before the data, but the protocol allows them to overlap — and in every case VALID is asserted by the source without waiting for READY.

The waveform makes the channel independence concrete. The AW handshake completes once, in cycle 1, carrying the burst's address and len. The W channel then transfers two beats — D0 in cycle 2, D1 in cycle 3 with WLAST high to mark the end of the burst — each beat its own WVALID/WREADY handshake. The B response returns in cycle 4 with its BID matching the AWID. Each VALID rises from its source independently of READY, and each transfer lands on the cycle both flags are high. This is the seaport running: the manifest filed once, the containers unloaded one per cycle with the last one flagged, the confirmation issued when the slave is done — three docks, three independent handshakes, one transaction.

DebugLab — the handshake that deadlocked the bus

The simulation hangs with no transactions completing — VALID was conditioned on READY

Symptom

The AXI agent hung at bring-up: the test started, the first write sequence began, and then nothing — no AW transfer, no data, no response, the simulation advancing clocks forever with every channel idle. Waveforms showed AWVALID low the entire time and AWREADY also low, both sides apparently waiting. No error, no UVM_FATAL — just a test that never finished and eventually hit the timeout.

Root cause

The driver asserted AWVALID only after observing AWREADY, and the slave asserted AWREADY only after observing AWVALID — a circular wait that violates the AXI handshake rule:

the forbidden VALID-waits-for-READY pattern
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
buggy driver:  do @(posedge clk); while (!vif.awready);   // wait for READY first ✗
               vif.awvalid <= 1'b1;                        // assert VALID only after
slave:         asserts AWREADY only after it sees AWVALID
result:        driver waits for AWREADY, slave waits for AWVALID → neither moves → deadlock
fix:           vif.awvalid <= 1'b1;                        // assert VALID first ✓
               do @(posedge clk); while (!vif.awready);    // THEN wait for READY

AXI's rule is asymmetric and deliberate: a source must assert VALID independently of READY — it may not wait to see READY before driving VALID. A destination is allowed to wait for VALID before asserting READY. The buggy driver inverted the source side: it waited for AWREADY before raising AWVALID. Paired with a perfectly legal slave that waits for AWVALID before raising AWREADY, the two form a circular dependency where neither ever asserts its signal. The transfer never happens, and because it is the very first one, the whole agent is wedged.

Diagnosis

The tell is a hang with a channel's VALID and READY both stuck low. Localize it at the source-side handshake:

  1. Check the order of assertion versus wait in the driver. The source must assert VALID then poll READY. If the code polls READY before asserting VALID, that is the bug. This is the first thing to inspect on any AXI handshake hang.
  2. Confirm both sides are low. VALID low and READY low together, forever, is the signature of a circular wait — distinct from a slow slave (where VALID is high, waiting on READY).
  3. Reproduce against a known-good slave. If the agent hangs even against a compliant reference slave, the bug is on the master side; a compliant slave is legal to make READY wait on VALID, so the master must not also wait.
Prevention

Encode the handshake rule once and reuse it on every channel:

  1. Always assert VALID before polling READY. Write every source channel as: drive payload, raise VALID, then do @(posedge clk); while (!READY);. Never gate VALID on READY.
  2. Keep VALID and payload stable until the transfer. Once asserted, VALID must stay high and the payload unchanged until the cycle READY is also high — do not drop or change them mid-handshake.
  3. Use a protocol-checking slave or assertions. A reference slave or AXI handshake assertions (VALID stable until handshake, no VALID-waits-for-READY) catch the violation immediately instead of as a silent hang.

The one-sentence lesson: in AXI, a source must assert VALID independently of READY — asserting VALID only after seeing READY creates a circular wait with a slave that waits for VALID, deadlocking the channel; always raise VALID first, then wait for READY.

Common Mistakes

  • Conditioning VALID on READY. A source must assert VALID without waiting for READY; waiting first deadlocks against a slave that waits for VALID — the DebugLab hang.
  • Driving the channels sequentially instead of concurrently. AW, W, and B are independent and may overlap; a driver that fully finishes AW before starting W mismodels the protocol and serializes what should be concurrent. Use fork … join.
  • Forgetting WLAST (or RLAST). The final data beat must be flagged; without WLAST the slave waits for more beats and the burst never completes.
  • Sizing the burst wrong. The data array must hold AxLEN + 1 beats; an off-by-one against len drives too few or too many beats. Constrain data.size() == len + 1.
  • Matching responses by order instead of ID. Responses can return out of order; the scoreboard must match BID/RID to the request's ID, not assume FIFO order.
  • Changing payload or dropping VALID mid-handshake. Once VALID is asserted, the payload and VALID must stay stable until the transfer completes.

Senior Design Review Notes

Interview Insights

AXI has five independent channels: three for writes — write address (AW), write data (W), and write response (B) — and two for reads — read address (AR) and read data (R). For a write, the master sends the address and burst parameters once on AW, streams the data beats on W with WLAST marking the final beat, and the slave returns a single response on B. For a read, the master sends the address on AR, and the slave returns the data beats on R, one per beat with a per-beat response and RLAST on the last. The defining property is that the channels are independent — each has its own VALID/READY handshake and they operate concurrently, so write data can flow while the next write address is issued, and reads proceed independently of writes. They relate through IDs: each transaction carries an ID on its address channel (AWID, ARID), and the response carries the matching ID (BID, RID), which is what lets responses be associated with their requests even when many are outstanding and responses come back out of order. So the channels are separate concurrent pipes, coordinated not by ordering but by the IDs threaded through them. This independence and concurrency is the central difference from a single-channel protocol like APB, where there is one sequential transfer at a time. The understanding to convey is the three-write, two-read split, the role of each channel including WLAST/RLAST and the response channels, and that the channels are independent and ID-coordinated — which is the structure the whole agent is built around.

The rule is that a source must assert VALID independently of READY — it may not wait to observe READY before asserting VALID — while a destination is allowed to wait for VALID before asserting READY; the transfer occurs on the cycle both are high, and once asserted VALID and its payload must stay stable until then. The asymmetry is deliberate. The source side — the master on the address and write-data channels, the slave on the response channels — has to raise VALID as soon as it has something to send, regardless of READY. The destination side may legally hold READY until it sees VALID. If you break the rule by making the source wait for READY before asserting VALID, you create the classic deadlock: the master waits for READY before raising VALID, and a perfectly compliant slave waits for VALID before raising READY, so neither ever asserts its signal and the channel hangs forever. The symptom is a simulation that hangs with both VALID and READY stuck low, no error, just no progress until a timeout. Because it is usually the first transfer that wedges, the whole agent locks at bring-up. The fix is to always assert VALID first and then poll READY, and the prevention is handshake assertions or a protocol-checking slave that flags the violation immediately. This is one of the most common AXI bring-up bugs, so recognizing the both-low-forever signature and knowing the assert-VALID-first rule is exactly what an interviewer is probing. The understanding to convey is the asymmetric rule, the circular-wait deadlock that breaking it causes, and the both-signals-low signature — which shows you have actually brought up an AXI interface.

A write burst uses the three write channels concurrently: the master issues the address once on AW, streams the data beats on W with WLAST on the final beat, and the slave returns one response on B. On AW, the master drives the ID, address, length (AxLEN, which is beats minus one), size, and burst type, asserts AWVALID, and the transfer completes when AWREADY is also high — this happens once per transaction. On W, the master drives each data beat in turn: for a burst of N beats it sends N transfers, each a WVALID/WREADY handshake, and it asserts WLAST on the final beat so the slave knows the burst is done. Crucially the W channel is independent of AW — the data can be accepted before, during, or after the address, so a real driver runs them concurrently rather than finishing AW before starting W. On B, after it has the address and all the data, the slave returns a single response carrying BRESP and a BID equal to the AWID, and the master accepts it by having BREADY high when BVALID asserts. So the transaction is: address once on AW, N data beats on W with the last flagged, response once on B — three handshakes on three channels, run concurrently. The length field and WLAST together define the burst boundary, and the ID ties the B response back to this write. A driver models this with a fork that runs the AW, W, and B tasks together, joining when all three complete. The understanding to convey is the AW-once, W-per-beat-with-WLAST, B-once structure, the concurrency of the channels, and the role of length and WLAST in bounding the burst — which shows you understand an AXI write as concurrent channel activity, not a sequential transfer.

Outstanding means the master can issue multiple transaction addresses before earlier ones have completed, and out-of-order means the slave can return responses in a different order than the requests arrived — and IDs are what make both possible by tagging every transaction so a response can be matched to its request regardless of order. Without IDs, responses would have to come back in request order and only one transaction could be safely in flight at a time, which would throttle the bus. With IDs, the master issues, say, three reads with IDs 0, 1, and 2 on AR without waiting, and the slave can return their R data in whatever order it finishes them — maybe ID 2 first because that data was already in a fast cache, then ID 0, then ID 1 — each R burst carrying its RID so the master knows which request it answers. This is what gives AXI its bandwidth: a fast responder need not wait behind a slow one. The consequence for verification is that the agent must be ID-aware: the transaction carries an ID, the monitor reconstructs bursts and tags them by ID, and the scoreboard matches expected against observed by ID, not by arrival order — matching by order would mismatch the moment responses come back out of order. A subtlety is that transactions with the same ID must stay ordered relative to each other, while different IDs may reorder, so the matching is per-ID. The understanding to convey is that IDs decouple response order from request order, enabling outstanding and out-of-order traffic for bandwidth, and that the agent and scoreboard must therefore key everything on ID — which is a core AXI concept and a frequent source of scoreboard bugs when matched by order instead.

The outer UVM structure is the same — item, sequencer, driver, monitor, agent, with active/passive assembly — but the internals differ because AXI's channels are concurrent: the driver runs multiple per-channel processes instead of one sequential loop, the transaction models a whole burst instead of a single word, and the monitor and scoreboard must match by ID instead of by order. In APB the driver is a single loop: get an item, drive setup then access, done, next — one transfer at a time. In AXI the driver, for each transaction, forks concurrent tasks for the independent channels — AW, W, B for a write — because they may overlap and a sequential driver would mismodel the protocol; and a fully featured driver issues transactions without blocking so several are outstanding. The transaction grows from a single address and word to a burst: an ID, length, size, burst type, and a data array of beats, with per-beat responses. The monitor, instead of watching one handshake, watches all five channels concurrently and assembles multi-beat bursts, tagging them by ID. And the scoreboard matches by ID rather than assuming responses arrive in order. What does not change is the agent pattern itself — the reusable, active/passive, factory-built, config-driven shell — which is the point: the architecture you learned on APB scales to AXI without changing, while the channel handling inside grows to match the protocol's concurrency. The understanding to convey is same-pattern, richer-internals — concurrent channel processes, a burst transaction, and ID-based matching — which shows you see the agent pattern as protocol-independent and understand exactly what AXI's concurrency adds.

Exercises

  1. Size the burst. For an INCR write with len = 3 and size = 2 on a 32-bit bus, state how many W beats are driven, which one asserts WLAST, and how many bytes the transaction moves.
  2. Spot the deadlock. Given a driver that polls AWREADY before asserting AWVALID, explain why it hangs against a compliant slave and give the one-line reorder that fixes it.
  3. Make it concurrent. Rewrite a sequential drive_aw(); drive_w(); collect_b(); as the correct concurrent form, and explain what overlap it now allows that the sequential version forbade.
  4. Match by ID. Describe how a scoreboard would handle two outstanding reads whose responses return out of order, and what would break if it matched by arrival order instead.
  5. Extend to outstanding. Outline what changes in the driver to allow two writes in flight at once, and why the transaction's id field is the prerequisite.

Summary

  • An AXI agent is the APB pattern scaled by concurrency: five independent channels (AW, W, B for writes; AR, R for reads), each an independent VALID/READY handshake, operating at once.
  • The transaction is burst-aware — id, address, length (AxLEN, beats − 1), size, burst type, a data array, and per-beat responses — and the driver runs the channels concurrently with fork … join, asserting WLAST/RLAST on the final beat.
  • The handshake rule is non-negotiable: a source asserts VALID independently of READY; conditioning VALID on READY deadlocks the channel against a compliant slave — the both-signals-low-forever hang.
  • IDs enable outstanding and out-of-order traffic: many transactions in flight, responses returned in any order and matched back by BID/RID, so the monitor and scoreboard must key on ID, not arrival order.
  • The agent's outer shape is unchanged from APB — item, sequencer, driver, monitor, active/passive, factory-built, config-driven — which is the lesson: the reusable-agent architecture is protocol-independent; only the channel handling grows.
  • The durable rule of thumb: build the AXI agent as the same agent pattern with concurrent per-channel processes, a burst transaction, and ID-based matching — drive every channel by asserting VALID before polling READY, flag the last beat with WLAST/RLAST, and match responses by ID — because AXI's bandwidth comes from concurrent, outstanding, out-of-order channels, and the one rule that keeps them from deadlocking is that VALID never waits for READY.

Next — UART Agent: APB was single-channel and synchronous; AXI added concurrent channels. The next case study takes on a different axis — a serial, asynchronous interface — where the agent must recover timing from a start bit, sample in the middle of each bit at a configured baud rate, and handle framing and parity, showing how the same agent pattern adapts to a protocol with no shared clock at all.