AXI is widely used for communication between IP blocks in modern SoCs, but implementing an AXI interface correctly is more than connecting the right signals.
Many AXI problems come from relatively small RTL design decisions: making VALID depend incorrectly on READY, losing information when backpressure occurs, mishandling burst boundaries, assuming read and write channels always progress together, or failing to account for multiple outstanding transactions.
These issues can be particularly difficult to debug because an AXI interface may work perfectly in a simple simulation and fail when the slave applies backpressure, transactions overlap, or a burst reaches an address boundary.
This guide explains the common AXI design mistakes VLSI engineers encounter in RTL, why they happen, how to avoid them, and what to check during simulation and verification.
The examples focus on AXI4 concepts, but many of the underlying lessons apply to other ready/valid-based interfaces as well.
AXI is not a simple single-channel bus.
AXI4 separates communication into five independent channels:
Each channel has its own VALID and READY handshake.
A transfer occurs when both sides of the handshake are asserted at the active clock edge:
VALID && READY
This means an AXI design has to handle not only the data itself, but also when that data is accepted, when it must remain stable, how transactions are associated, and what happens when the receiving side is not ready. Arm’s AXI documentation defines the channel structure and transaction rules in detail.
That is where many implementation mistakes begin.
One of the most common mistakes is designing the source so that it waits for READY before asserting VALID.
For example, conceptually:
if (READY)
VALID = 1;
This may appear convenient, but it creates an undesirable dependency between the sender and receiver.
A source should be able to indicate that it has a valid transfer without waiting for the destination to assert READY.
If both sides wait for each other, the interface can stop making progress.
A ready/valid interface is intended to allow the source to assert VALID when data is available while the destination independently controls READY.
Think of the signals this way:
Source controls:
Destination controls:
The source says:
“I have something to transfer.”
The destination says:
“I can accept it.”
The transfer happens when:
VALID = 1 AND READY = 1
This principle should be applied independently to each AXI channel.
Another common error occurs when the source changes the address, data, or control information while VALID is asserted but READY is low.
Consider:
Cycle 1:
VALID = 1
READY = 0
DATA = A
Cycle 2:
VALID = 1
READY = 0
DATA = B
The receiver has not accepted the first transfer.
Changing the payload without a valid transfer can therefore violate the intended handshake behavior.
Once the source asserts VALID, it should keep the relevant transfer information stable until the handshake occurs.
Conceptually:
VALID = 1
READY = 0
DATA = A
VALID = 1
READY = 0
DATA = A
VALID = 1
READY = 1
DATA = A
The transfer happens on the final cycle.
This is one of the first waveform checks you should perform when debugging an AXI interface.
AXI has independent channels.
A common design mistake is assuming that the write address and write data must always arrive together.
They do not have to behave as a single combined channel.
For writes, AXI separates:
Write Address → AW channel
from
Write Data → W channel
and the completion response uses:
Write Response → B channel
This means an RTL implementation must be able to handle legal differences in timing between these channels.
Suppose the write address arrives first but the data arrives several cycles later.
Your slave must be able to retain the required information and associate the pieces correctly.
A design that assumes:
AWVALID && WVALID
must always occur together can work in a simple testbench and fail with a more realistic master.
Design and verify each channel according to its own handshake rules.
Then explicitly handle the relationships between channels where the protocol requires them.
Backpressure is normal in AXI.
A slave may temporarily deassert READY because:
A common mistake is testing an interface only when:
READY = 1
all the time.
That does not adequately test the handshake logic.
Arm’s current AXI learning material emphasizes understanding how transactions behave in real systems, including data flow, correctness and performance.
Randomly or deliberately introduce periods where:
READY = 0
Then verify that:
Backpressure testing is one of the simplest ways to expose weak AXI RTL.
The opposite mistake is designing or verifying an AXI interface under the assumption that READY is permanently asserted.
For example:
assign ARREADY = 1’b1;
may be valid for a very simple interface if the architecture genuinely supports accepting every request immediately.
But a more realistic slave may need to apply backpressure.
If the RTL cannot tolerate READY going low, it has not been tested against an important part of the protocol behavior.
Design the interface so that it works correctly when:
READY = 1
and when:
READY = 0
for multiple cycles.
Then verify both cases.
A subtle but important problem occurs when the source’s VALID depends on the destination’s READY, while the destination’s READY depends on the source’s VALID.
For example:
Master VALID → Slave READY
↑ ↓
└──────────────┘
This can create a combinational loop.
Ready/valid interfaces are particularly vulnerable to this type of design problem because both signals control whether a transfer occurs.
A common solution is to derive the control from registered state or otherwise ensure that the interface does not create an unintended combinational path between source and destination.
When reviewing an AXI interface, ask:
Can VALID change combinationally because of READY?
and:
Can READY change combinationally because of VALID?
If the answer produces a loop across connected IP blocks, the architecture needs to be reconsidered.
AXI is burst-based, so burst parameters need careful handling.
Important fields include:
The AXI specification defines the burst length and size rules, and AXI4 allows INCR bursts of up to 256 transfers.
A common RTL mistake is interpreting AxLEN directly as the number of transfers.
For AXI4:
Burst length = AxLEN + 1
So:
AxLEN = 0 → 1 transfer
AxLEN = 3 → 4 transfers
AxLEN = 7 → 8 transfers
Create a clearly defined internal representation such as:
beats_remaining = AxLEN + 1
and decrement it only when an actual data transfer occurs.
This makes the RTL and verification environment easier to reason about.
This is a particularly important AXI design error.
An AXI burst must not cross a 4 KB address boundary. The rule exists in part to prevent a burst from crossing between different slaves and to limit the address range a subordinate needs to handle.
For example, a burst beginning near the end of one 4 KB region may need to be split rather than allowed to continue into the next region.
If an address generator simply calculates:
next_address = current_address + transfer_size;
without checking the boundary, it can generate an illegal burst.
Before issuing a burst, calculate:
Then verify that the burst remains within the same 4 KB region.
This check belongs in the design or transaction-generation logic where appropriate, rather than relying only on the testbench to catch it.
AXI supports different burst types.
The main types are:
An INCR burst increments the address according to the transfer size.
A FIXED burst keeps the address constant.
A WRAP burst follows wrapping-address rules.
The AXI specification defines these behaviors explicitly.
Designers sometimes assume every burst behaves like:
address = address + data_width
That is not generally correct.
The address increment is determined by the transfer size, not simply by the physical width of the bus.
Calculate the number of bytes per beat from AxSIZE.
For example:
bytes_per_beat = 2 ^ AxSIZE
Then use the correct address-generation algorithm for the selected burst type.
WSTRB identifies which byte lanes of write data are valid.
A slave should not simply assume that every byte of WDATA is meaningful on every transfer.
For example, on a 32-bit data bus:
WDATA = 32 bits
WSTRB = 4 bits
Each strobe bit corresponds to a byte lane.
Conceptually:
WSTRB[0] → byte 0
WSTRB[1] → byte 1
WSTRB[2] → byte 2
WSTRB[3] → byte 3
If only some strobes are asserted, the design must preserve the unaffected bytes according to the memory/register semantics.
A register or memory implementation writes all 32 bits regardless of WSTRB.
That can silently corrupt existing data during partial writes.
Make byte-enable behavior explicit in the RTL and include partial-write cases in the testbench.
For AXI write and read data channels, the LAST signal identifies the final data transfer of a burst.
A common mistake is generating LAST based on a cycle counter rather than on actual accepted transfers.
For example, if:
WVALID = 1
WREADY = 0
the write data has not transferred.
The burst counter should not advance simply because a clock cycle passed.
Update transaction state when the handshake occurs:
WVALID && WREADY
not merely because:
WVALID
is high.
This distinction is critical when backpressure is present.
This mistake appears in many AXI implementations.
Suppose an RTL block does:
if (VALID)
counter <= counter + 1;
But the actual transfer occurs only when:
VALID && READY
The counter can therefore move ahead of the transaction.
For a channel where transfer completion is defined by the handshake:
if (VALID && READY)
update_transaction_state();
This applies to:
The important question is always:
Did the transfer actually happen?
AXI interfaces contain many state-holding elements.
Reset may affect:
A common mistake is resetting only the visible AXI signals while leaving internal transaction state inconsistent.
For example, the interface may come out of reset with:
AWVALID = 0
but an internal state machine still believes a write transaction is active.
That can produce difficult-to-debug failures later.
For every AXI channel, document:
Then verify reset both during initialization and in appropriate reset/recovery scenarios.
A slave should generate responses based on completed transactions, not merely because a request was observed.
For writes, the B channel carries the write response.
For reads, the R channel carries read data and response information.
A common design error is generating a response too early or losing the relationship between the response and the transaction that caused it.
This becomes more complicated when multiple transactions can be outstanding.
For every transaction, verify:
Request → Processing → Response
and ensure that:
AXI can support multiple transactions in flight.
This is useful for performance, but it introduces additional design complexity.
For example, a master may issue several read requests before receiving all the read data.
If the design assumes:
one request → one response → next request
it may accidentally create a much more restrictive interface than intended or mishandle legal transaction behavior.
Arm’s AXI material discusses outstanding transactions and the role they play in system performance and traffic management.
Decide explicitly whether your IP supports:
Then make the RTL and verification environment consistent with that architectural decision.
When multiple transactions are active, transaction IDs become important.
A design that simply stores one request in one register may not be sufficient if the interface allows multiple transactions to remain outstanding.
AXI’s ordering model contains rules around transaction IDs and ordering requirements. Arm has also published updates to the AXI specification addressing ordering and unique-ID behavior.
Do not implement ID handling as an afterthought.
At the architecture stage, determine:
This is especially important for high-performance AXI masters, slaves and interconnect-related logic.
A basic test might look like:
VALID = 1
READY = 1
every cycle.
Everything works.
But real bugs often appear when the interface is stressed.
A useful AXI testbench should introduce conditions such as:
The goal is not to create random complexity for its own sake.
The goal is to test the situations in which the RTL’s assumptions are most likely to break.
AXI behavior is often well suited to assertion-based verification.
Instead of discovering every protocol violation manually from waveforms, assertions can continuously check important properties.
Examples include checking that:
A simple ready/valid stability property can conceptually look like:
property p_data_stable;
@(posedge clk)
VALID && !READY |=> VALID && $stable(DATA);
endproperty
The exact assertion should be adapted to the channel and protocol property being checked.
The important point is that assertions should express protocol intent, not simply duplicate implementation details.
AXI waveforms can contain dozens of signals.
A common debugging mistake is looking immediately at:
without first identifying where the handshakes occurred.
Start with:
VALID
READY
for the channel you are debugging.
Then identify the exact clock edges where:
VALID && READY
is true.
Only after identifying the accepted transfers should you examine:
This makes waveform debugging considerably easier.
Mistake | Possible consequence | Better practice |
VALID waits for READY | Deadlock | Generate VALID independently |
Payload changes while stalled | Incorrect transfer | Hold payload stable |
Treating channels as one | Lost/blocked transactions | Handle channels independently |
Ignoring backpressure | Data loss or deadlock | Test READY deassertion |
READY assumed always high | Hidden bugs | Stress with stalls |
Combinational VALID/READY loop | Timing/functional problems | Break dependency appropriately |
Incorrect AxLEN interpretation | Wrong burst count | Use AxLEN + 1 |
Crossing 4 KB boundary | Protocol violation | Check burst boundary |
Wrong address increment | Incorrect burst addressing | Use AxSIZE/AxBURST rules |
Ignoring WSTRB | Partial-write corruption | Implement byte enables |
Wrong LAST generation | Broken burst termination | Count accepted beats |
State advances without handshake | Lost transactions | Update on VALID & READY |
Incomplete reset state | Post-reset failures | Reset internal transaction state |
Poor ID handling | Ordering errors | Define ID/outstanding policy |
Only happy-path testing | Bugs escape simulation | Add backpressure/stress cases |
Most AXI bugs are not caused by forgetting a signal name. They come from misunderstanding when a transfer actually occurs and what the design must do when the transfer cannot occur immediately.
The most important principles are:
AXI becomes much easier to design once you stop thinking of it as a collection of signals and start thinking in terms of transactions, handshakes, state, buffering and backpressure.
For VLSI engineers, that shift in thinking is what turns AXI knowledge from interview-level theory into something useful for real RTL and SoC work.