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/READYrule — 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
VALIDmust not wait forREADYis 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 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 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.
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.
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
endclassThe 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.
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
endclassdrive_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.
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;
endtaskclass 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
endclassThe 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 cyclesThe 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
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.
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:
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 READYAXI'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.
The tell is a hang with a channel's VALID and READY both stuck low. Localize it at the source-side handshake:
- Check the order of assertion versus wait in the driver. The source must assert
VALIDthen pollREADY. If the code pollsREADYbefore assertingVALID, that is the bug. This is the first thing to inspect on any AXI handshake hang. - Confirm both sides are low.
VALIDlow andREADYlow together, forever, is the signature of a circular wait — distinct from a slow slave (whereVALIDis high, waiting onREADY). - 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
READYwait onVALID, so the master must not also wait.
Encode the handshake rule once and reuse it on every channel:
- Always assert
VALIDbefore pollingREADY. Write every source channel as: drive payload, raiseVALID, thendo @(posedge clk); while (!READY);. Never gateVALIDonREADY. - Keep
VALIDand payload stable until the transfer. Once asserted,VALIDmust stay high and the payload unchanged until the cycleREADYis also high — do not drop or change them mid-handshake. - Use a protocol-checking slave or assertions. A reference slave or AXI handshake assertions (
VALIDstable until handshake, noVALID-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
VALIDonREADY. A source must assertVALIDwithout waiting forREADY; waiting first deadlocks against a slave that waits forVALID— 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(orRLAST). The final data beat must be flagged; withoutWLASTthe slave waits for more beats and the burst never completes. - Sizing the burst wrong. The data array must hold
AxLEN + 1beats; an off-by-one againstlendrives too few or too many beats. Constraindata.size() == len + 1. - Matching responses by order instead of ID. Responses can return out of order; the scoreboard must match
BID/RIDto the request's ID, not assume FIFO order. - Changing payload or dropping
VALIDmid-handshake. OnceVALIDis asserted, the payload andVALIDmust 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
- Size the burst. For an
INCRwrite withlen = 3andsize = 2on a 32-bit bus, state how many W beats are driven, which one assertsWLAST, and how many bytes the transaction moves. - Spot the deadlock. Given a driver that polls
AWREADYbefore assertingAWVALID, explain why it hangs against a compliant slave and give the one-line reorder that fixes it. - 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. - 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.
- Extend to outstanding. Outline what changes in the driver to allow two writes in flight at once, and why the transaction's
idfield 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/READYhandshake, 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 withfork … join, assertingWLAST/RLASTon the final beat. - The handshake rule is non-negotiable: a source asserts
VALIDindependently ofREADY; conditioningVALIDonREADYdeadlocks 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
VALIDbefore pollingREADY, flag the last beat withWLAST/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 thatVALIDnever waits forREADY.
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.