AMBA AHB · Module 7
ERROR Response
The AHB ERROR response — what it means, what triggers it (unmapped address, permission violation, slave fault, unsupported access), its mandatory two-cycle handshake, and why ERROR reports a failure without automatically undoing a write.
Chapter 7.1 covered OKAY — the silent default success. This chapter covers the response that matters when things go wrong: ERROR. ERROR is the HRESP value a subordinate drives when it cannot or should not complete a transfer — an unmapped address, a permission violation, an internal fault, an unsupported access.
This chapter owns the behaviour of ERROR: what qualifies as an error and what does not, what the errored transfer is and is not allowed to do to the subordinate's state, and how the failure becomes something software can see and act on. The exact cycle-by-cycle timing of the response has its own chapter — The Two-Cycle ERROR Response — and is treated here only as far as needed. If what you came for is the handshake, go there.
The single idea to carry through: ERROR reports a failure; it does not repair one. It tells the manager "this didn't work." What the transfer left behind, and what anyone does about it, are separate questions with separate answers — and both are answered below.
1. What Is It?
ERROR is the HRESP value meaning the subordinate could not complete the transfer successfully — it is the failure outcome, the counterpart to OKAY's success.
A subordinate drives ERROR when something prevents a successful transfer. The common triggers:
- Unmapped address — no real subordinate exists at that address; the default subordinate (chapter 3.10) returns ERROR.
- Permission / security violation — the access is not allowed by privilege (HPROT) or security (HNONSEC) rules.
- Subordinate-internal fault — the subordinate itself cannot complete (e.g., an ECC failure, a hardware fault).
- Unsupported / illegal access — a size, alignment, or burst the subordinate does not support, or a locked/protected region.
ERROR is fundamentally informational: it reports that the transfer failed. It is not an automatic recovery mechanism — it does not undo a write, retry the transfer, or roll back state. It simply signals the failure to the manager, which then decides what to do (raise an exception, abort a burst, log a fault, etc., chapter 7.7). ERROR's job is to report, not to fix — a distinction that decides how both the subordinate and the recovering software must be written.
2. Why Does It Exist?
ERROR exists because transfers can fail, and the manager must be told when one does — without a failure signal, a manager would treat a failed transfer as if it succeeded (consuming garbage read data, assuming a rejected write happened). ERROR is the explicit "this didn't work" that prevents that.
Consider a read of an unmapped address. Without an error mechanism, what would happen? The transfer would have to complete somehow (the bus can't hang, chapter 6.7), returning some data — which the manager would consume as if valid, silently using garbage. That is a dangerous silent failure. ERROR exists to make the failure explicit: the default subordinate returns ERROR, so the manager knows the read failed and can handle it (raise a fault) rather than trusting garbage. ERROR exists to turn silent failures into reported ones, so the manager learns when a transfer did not do what it was asked. That is a correctness requirement and a security requirement at once: an unmapped or unauthorised access must not appear to succeed.
ERROR is signalled across two cycles rather than one because the bus is pipelined: by the time a subordinate signals an error in a transfer's data phase, the manager has already issued the next transfer's address phase (chapter 5.1). Holding HREADY low for one cycle leaves that address phase uncaptured, which gives the manager one edge of margin in which it may still cancel the transfer by driving HTRANS to IDLE. Cancelling is a permission the specification grants the manager, not an obligation it imposes — a manager is equally entitled to complete the remaining transfers of a burst. The derivation, the exact edges, and the checkers belong to The Two-Cycle ERROR Response; nothing further about the handshake is needed here.
The reason ERROR reports rather than fixes (doesn't auto-undo a write) is that recovery is a policy decision the manager owns, not something the bus can do generically. What should happen on a failed write? Retry? Abort the burst? Raise an exception? Log and continue? The answer depends on the system and the situation — only the manager (and its software) can decide. The bus's job is to report the failure accurately; responding to it is the manager's policy. An auto-undo would impose one recovery policy on every system that uses the bus, and no single policy fits them all — so the mechanism (report the failure) and the policy (decide what to do) are kept apart.
3. Mental Model
Model ERROR as a transaction being declined at a register — the clerk says "this won't go through" (and gives you a heads-up first), but declining doesn't reverse anything else; you decide what to do next.
At a checkout (the bus), you try to pay (a transfer). Sometimes it's declined (ERROR) — the card's not valid here (unmapped address), it's over-limit or not authorized (permission violation), the terminal faults (internal error), or the payment type isn't supported (unsupported access). The clerk gives you a heads-up — "this is going to decline" (the first warning cycle) — and then it declines (the completing cycle). Crucially, declining doesn't undo your other actions — it doesn't refund a previous purchase or fix anything; it just tells you this payment failed. What you do next — try another card, cancel the order, call the bank — is your decision (the manager's recovery policy). The clerk reports; you decide.
This captures ERROR: the decline = the failure outcome; the heads-up then decline = the two-cycle warning-then-completion; declining doesn't undo other things = ERROR reports without auto-rollback; you decide what's next = the manager owns recovery. The decline is informational — it tells you the payment didn't work, no more.
Watch the two-cycle ERROR response:
The mandatory two-cycle ERROR response
4 cyclesThe model's lesson: ERROR is a decline with a heads-up — the warning cycle (HREADY low) lets the manager react before the decline completes (HREADY high). In the waveform, HRESP=ERROR spans two cycles; the first (HREADY low) is the warning, the second (HREADY high) completes the failed transfer. The decline reports; recovery is the manager's.
4. Real Hardware Perspective
In hardware, ERROR is driven by the data-phase subordinate on HRESP (aggregated like HREADY/OKAY, chapter 6.6), and the two-cycle behaviour is a specific HREADY/HRESP sequence the subordinate produces.
Two distinct pieces of hardware are involved, and keeping them apart is what makes error behaviour designable. The first is the qualification logic: the block that decides whether this access is an error at all — region match, size and alignment support, privilege and security permission, internal fault status. It produces a single qualified decision. The second is the response logic: the small state machine that, given that decision, drives HRESP and HREADYOUT in the required two-cycle shape. Section 6b builds the first; the second is the subject of HRESP Generation, and its timing is derived in The Two-Cycle ERROR Response.
The reason the split matters is that almost every interesting error bug lives in the qualification stage or in what happens around it, not in the response FSM. A subordinate that drives a textbook-perfect two-cycle ERROR while having already committed the write it is rejecting has a correct response FSM and a broken design.
The default subordinate is the hardware that handles the most common ERROR trigger — unmapped addresses (chapter 3.10). When the decoder finds no real subordinate for an address, it selects the default subordinate, whose entire job is to return the two-cycle ERROR (and, for the bus's sake, to always complete so the bus doesn't hang). So the default subordinate is, in effect, a dedicated ERROR generator for unmapped space. This is why an unmapped access produces a clean ERROR rather than a hang: the default subordinate is there to respond. In hardware, every AHB system has this default-subordinate ERROR path.
The HRESP encoding depends on version (chapter 7.1): in full AHB, HRESP is 2-bit (ERROR is one of OKAY/ERROR/RETRY/SPLIT); in AHB-Lite/AHB5, HRESP is effectively 1-bit (OKAY/ERROR only). So in the simplified versions, ERROR is the failure response (no RETRY/SPLIT). This chapter's ERROR behaviour — the two-cycle response, the triggers — is the same across versions; only the set of other responses differs. ERROR is universal; RETRY/SPLIT are the full-AHB-only legacy additions (chapters 7.3/7.4).
A crucial hardware/semantic point: ERROR does not define what happens to a write's side effects. Whether a write that errored actually modified any state is implementation-specific — the protocol only says the transfer failed, not whether it was atomic or partially applied. So a manager (and software) must not assume an errored write either definitely happened or definitely didn't — it must treat the state as uncertain and recover according to policy. In hardware, the subordinate may or may not have applied a partial effect before erroring; ERROR doesn't specify. This is why ERROR is "report, not fix" — it makes no guarantee about rollback, so recovery must be explicit.
5. System Architecture Perspective
At the system level, ERROR is the foundation of fault detection and security enforcement — it is how the bus surfaces illegal, unauthorized, and failed accesses to the manager and ultimately to software, enabling exceptions, protection, and robust error handling.
ERROR underpins memory protection and security: when an access violates privilege (HPROT) or security (HNONSEC) rules, the subordinate (or a protection unit) returns ERROR, which the manager turns into a fault/exception (chapter 7.7). So ERROR is the mechanism by which unauthorized accesses are blocked and reported rather than silently allowed — a security-critical function. A TrustZone-style secure system relies on ERROR to deny non-secure accesses to secure regions (chapter on HNONSEC). At the system level, then, ERROR is not merely "a failed transfer" — it is the enforcement signal for the system's protection and security policies. Without it, illegal accesses could not be cleanly denied and reported.
ERROR also underpins fault detection and reporting: an unmapped access (software bug — a wild pointer), a hardware fault (ECC failure), or an unsupported access all surface as ERROR, which the manager escalates to a processor exception or fault handler. ERROR is therefore how the system detects and responds to faults at the bus level — the bus's contribution to the system's fault-handling architecture. A robust system uses ERROR to catch and contain faults (a wild access faults rather than silently corrupting); the processor's exception handler then logs, recovers, or terminates the offending software. ERROR is thus the bus-level entry point to the whole fault-handling stack.
The report-not-fix design is architecturally important because it keeps recovery policy where it belongs — in the manager and software — while the bus provides only the mechanism (accurate failure reporting). This separation lets different systems implement different recovery policies (retry, abort, fault, ignore) on the same bus mechanism. So the architecture cleanly divides: the bus reports failures (ERROR), and the system decides recovery (in the manager/software). This is the right separation — the bus can't know the right recovery for every situation, so it reports accurately and lets the policy layer decide. It is the same mechanism-vs-policy separation that good architectures use throughout.
A subtlety for bursts: an ERROR in the middle of a burst leaves the manager with a genuine choice, and the specification lets it make either one. It may cancel the remaining beats, and the two-cycle response is what makes that implementable. It may also complete them, which is explicitly acceptable and is what a simple DMA engine reporting aggregate status typically does. The system-level consequence is that a burst is not an atomic unit under failure: some beats may have taken effect, one failed, and the rest may or may not have been attempted. Software recovering from a bus fault after a burst cannot assume it knows which — it must re-establish the state it needs. Designing a subordinate on the assumption that the burst will stop is a real and common bug, worked through in The Two-Cycle ERROR Response.
6. Engineering Tradeoffs
ERROR's design reflects AHB's report-the-failure, manager-decides choice.
- Two-cycle ERROR vs single-cycle. The two-cycle response gives the manager a guaranteed reaction cycle (to cancel the pipelined next transfer) at the cost of one extra cycle on the rare error. A single-cycle error would be faster but leave the manager no clean window to stop the next transfer in the pipeline. AHB chooses two-cycle — the reaction window is worth one cycle on a rare event.
- Report vs auto-recover. ERROR reports the failure and leaves recovery to the manager, keeping the bus simple and recovery-policy flexible, at the cost that the manager must implement handling. Auto-recovery (e.g., automatic retry) would offload the manager but impose one policy. AHB reports and delegates — mechanism vs policy separation.
- No write-rollback guarantee vs atomic errors. ERROR makes no guarantee about an errored write's side effects (implementation-specific), keeping subordinates simple, at the cost that recovery must treat state as uncertain. Guaranteeing atomicity (full rollback) would burden every subordinate. AHB leaves it unspecified — simplicity over a strong atomicity guarantee.
- Universal ERROR vs richer failure set. ERROR is the universal failure response (all versions); full AHB adds RETRY/SPLIT for specific cases (chapters 7.3/7.4), but AHB-Lite/AHB5 drop those, keeping just OKAY/ERROR. The trade is expressiveness vs simplicity; the modern trend (AHB-Lite/AHB5) favors the simple OKAY/ERROR set, with ERROR as the sole failure.
The throughline: ERROR is a two-cycle, report-not-fix failure response that surfaces faults and enforces protection, leaving recovery to the manager. The costs (an extra cycle on errors, manager-implemented recovery, uncertain write state) are accepted because they keep the bus simple, the recovery policy flexible, and the reaction window available — the right trade-offs for a failure mechanism that must be accurate but not prescriptive.
6b. Qualifying an Error, and Refusing It Cleanly
Two questions decide whether a subordinate handles errors correctly, and neither is about HRESP timing. Which accesses should be errors? And what is an errored access allowed to leave behind?
Which accesses should be errors
An error is an access the subordinate cannot or should not perform. That is a narrower category than it first appears, and the boundary is worth stating explicitly because both sides of it are commonly crossed.
| Condition | ERROR? | Why |
|---|---|---|
| Address in no subordinate's region | Yes | Nothing exists to service it. The default subordinate answers so the bus cannot hang. |
| Address inside the region, but an unimplemented hole | Yes | The subordinate owns the region and knows there is nothing there. |
| Write to a read-only register, read of a write-only register | Yes | The access is defined but not permitted on this location. |
| Privilege or security check fails (HPROT / HNONSEC) | Yes | The access is denied. This is the response that enforces the protection. |
| Size or alignment the subordinate does not support | Yes | It cannot perform the access as asked. |
| Uncorrectable ECC on a read | Yes | It cannot return valid data. Returning corrupt data silently is the failure mode being avoided. |
| Subordinate is busy and needs more time | No | Insert wait states with HREADYOUT. ERROR is not a back-pressure mechanism. |
| Write of a value the register considers invalid | Usually no | Ignoring or clamping the value is a design choice; making it a bus error turns a data problem into a processor exception. Decide deliberately and document it. |
| FIFO full, buffer full | No | Wait-state it, or define a status bit software can poll. |
The two "no" rows are where designs go wrong in the field. Returning ERROR for congestion converts a latency problem into a fault handler, and the resulting bug is reported by software as a random crash under load.
What an errored access may leave behind
Here the protocol is deliberately silent, and that silence is the most important thing on this page. AMBA does not define whether an errored write took effect. It reports that the transfer failed; it makes no statement about side effects.
Silence in the specification is not permission to be careless — it is a requirement that you decide, write it down, and check it. The convention essentially every project adopts, and the one this chapter recommends:
An access that receives ERROR leaves the subordinate's architectural state unchanged. An errored write updates no register and no memory. An errored read triggers no side effect — no FIFO pop, no read-to-clear, no counter advance — and its data is not to be used.
This is engineering practice, not an AMBA requirement, and it must be stated in the subordinate's specification for anyone to be entitled to rely on it. It matters most where it is most easily lost: the security path. A subordinate that returns ERROR for a denied write but has already latched the data has reported the denial while performing the access, which is the one outcome the whole mechanism exists to prevent.
The structural rule that makes it true is simple. Qualify first, commit second — in the same cycle, in that order. The write enable must be gated by the qualified decision, not raced against it.
// ahb_error_qualify.sv
//
// The decision half of a subordinate's error handling: WHICH accesses are
// errors, and the gating that guarantees a rejected access changes nothing.
// The response half - driving HRESP/HREADYOUT in the two-cycle shape - is a
// separate block; see the HRESP generation and two-cycle ERROR chapters.
//
// SCOPE: AHB-Lite / AHB5. HRESP is one bit. Address phase values are
// registered into the data phase, which is where the decision is applied.
module ahb_error_qualify #(
parameter int ADDR_W = 32,
parameter int REGION_BITS = 12, // bytes of implemented space
parameter bit SECURE_ONLY = 1'b1 // region rejects non-secure access
) (
input logic HCLK,
input logic HRESETn,
// ---- address phase
input logic HSEL,
input logic [1:0] HTRANS,
input logic HWRITE,
input logic [ADDR_W-1:0] HADDR,
input logic [2:0] HSIZE,
input logic [3:0] HPROT,
input logic HNONSEC,
input logic HREADY, // global: address phase captured
// ---- from the register file / memory
input logic addr_unimplemented, // hole inside the region
input logic write_to_ro,
input logic read_of_wo,
input logic ecc_uncorrectable, // read data phase
// ---- data phase results
output logic access_qualified, // 1 = perform the access
output logic access_error, // 1 = respond ERROR
output logic [3:0] error_reason, // for the fault-status reg
output logic wr_en // THE gated write enable
);
localparam logic [1:0] NONSEQ = 2'b10, SEQ = 2'b11;
// Reason codes, mirrored in the fault-status register below.
localparam logic [3:0] R_NONE = 4'd0, R_HOLE = 4'd1, R_RO = 4'd2,
R_WO = 4'd3, R_SEC = 4'd4, R_SIZE = 4'd5,
R_ECC = 4'd6;
// ---- capture the address phase into the data phase. Only a captured
// address phase (HREADY high) may become a data phase; this is the
// same rule that makes the two-cycle ERROR window work.
logic dp_valid, dp_write, dp_nonsec;
logic [2:0] dp_size;
logic [ADDR_W-1:0] dp_addr;
always_ff @(posedge HCLK or negedge HRESETn) begin
if (!HRESETn) begin
dp_valid <= 1'b0; dp_write <= 1'b0; dp_nonsec <= 1'b0;
dp_size <= 3'd0; dp_addr <= '0;
end else if (HREADY) begin
dp_valid <= HSEL && (HTRANS == NONSEQ || HTRANS == SEQ);
dp_write <= HWRITE;
dp_nonsec <= HNONSEC;
dp_size <= HSIZE;
dp_addr <= HADDR;
end
end
// ---- qualification. Evaluated in the data phase, one decision, one place.
//
// Alignment: an access of 2**HSIZE bytes must be aligned to that size.
// HSIZE 0 (byte) is always aligned; the mask below is 0 for that case.
logic misaligned, out_of_region, security_violation;
always_comb begin
misaligned = |(dp_addr[2:0] & ((3'd1 << dp_size) - 3'd1));
out_of_region = |dp_addr[ADDR_W-1:REGION_BITS];
security_violation = SECURE_ONLY && dp_nonsec;
end
always_comb begin
error_reason = R_NONE;
if (out_of_region || addr_unimplemented) error_reason = R_HOLE;
else if (security_violation) error_reason = R_SEC;
else if (misaligned) error_reason = R_SIZE;
else if (dp_write && write_to_ro) error_reason = R_RO;
else if (!dp_write && read_of_wo) error_reason = R_WO;
else if (!dp_write && ecc_uncorrectable) error_reason = R_ECC;
end
assign access_error = dp_valid && (error_reason != R_NONE);
assign access_qualified = dp_valid && (error_reason == R_NONE);
// ---- THE line that matters. The write enable is derived from the
// qualified decision, never from dp_valid alone. Every reason code
// above therefore suppresses the write by construction, and adding a
// new reason code cannot forget to.
assign wr_en = access_qualified && dp_write;
// ---- Guards. The first is the invariant this whole block exists to hold.
// synthesis translate_off
a_no_write_on_error: assert property (@(posedge HCLK) disable iff (!HRESETn)
access_error |-> !wr_en)
else $error("errored access asserted the write enable");
// A denied non-secure write must change nothing. Stated separately because
// it is the case a security review will ask about by name.
a_secure_region_denies: assert property (@(posedge HCLK) disable iff (!HRESETn)
(dp_valid && dp_write && SECURE_ONLY && dp_nonsec) |-> (access_error && !wr_en))
else $error("non-secure write to a secure region was not fully denied");
// synthesis translate_on
endmodulewr_en = access_qualified && dp_write is the entire safety property, and it is worth writing that way rather than as a chain of if (!error) guards scattered through the register file. A reason code added later inherits the suppression automatically; a scattered guard has to be remembered.
How software finds out
An ERROR that reaches the manager becomes a bus fault, and a bus fault with no context is close to useless — firmware learns that something failed. The subordinate side of making it diagnosable is a fault-status register that captures the first failing access and stops, so that the storm of secondary faults that usually follows cannot overwrite the one that matters.
// Fault status capture - PROJECT PRACTICE, not an AMBA requirement.
// First-fault semantics: latch the first error, hold it, set an overrun
// flag if others follow. Cleared by a software write to fault_clear.
//
// Latching the LAST fault instead is the classic mistake: a wild pointer
// usually produces a burst of faults, and by the time the handler reads
// the register it holds the least interesting one.
always_ff @(posedge HCLK or negedge HRESETn) begin
if (!HRESETn) begin
fault_valid <= 1'b0; fault_ovf <= 1'b0;
fault_addr <= '0; fault_info <= '0;
end else if (fault_clear) begin
fault_valid <= 1'b0; fault_ovf <= 1'b0;
end else if (access_error) begin
if (!fault_valid) begin
fault_valid <= 1'b1;
fault_addr <= dp_addr;
fault_info <= {dp_write, dp_nonsec, dp_size, error_reason};
end else begin
fault_ovf <= 1'b1; // a fault was lost - the handler must know
end
end
endThe security test passed, and the secure register changed anyway
REJECTED-WRITE-TOOK-EFFECTA TrustZone-style subsystem was in security signoff. The test that mattered drove a non-secure write at a secure configuration register and checked the response. It passed, and had passed on every regression for months:
SEC-047 non-secure write to secure CFG region
expected HRESP = ERROR ... observed HRESP = ERROR PASSAn independent reviewer added one line to the test — read the register back through the secure port afterwards — and it failed:
SEC-047b non-secure write to secure CFG @ 0x4001_0020
wrote 0xDEAD_BEEF (denied, HRESP = ERROR)
readback 0xDEAD_BEEF <-- expected 0x0000_0001, the pre-access valueThe access had been correctly refused on the bus and correctly performed in the register file. Every protocol assertion in the environment passed, on that test and on every other.
The security check and the write commit were parallel, not sequential.
The register file's write enable was derived from the decoded access alone:
// The shipping code.
assign wr_en = dp_valid && dp_write && addr_hit; // <-- no permission term
// The security check lived somewhere else entirely, feeding only the response.
assign sec_fail = dp_valid && dp_nonsec && region_is_secure;
assign hresp_d = sec_fail || decode_fail || size_fail;Both statements were correct in isolation. sec_fail produced a perfectly conformant two-cycle ERROR. wr_en performed the write. Nothing connected them, so the subordinate reported the denial and carried it out in the same cycle.
Three things kept it hidden for months, and each is worth recognising in its own right.
No protocol assertion could see it. Every AMBA rule was satisfied: HRESP held ERROR for both cycles, HREADYOUT went low then high, the bus recovered cleanly. The invariant that was violated — a rejected access changes nothing — is not an AMBA requirement. The specification declines to define an errored write's side effects at all. Nothing in the protocol layer was ever going to catch this, which is exactly why it has to be asserted as a project invariant instead.
The test asked the wrong question. "Did it return ERROR?" and "did it leave the register alone?" are two independent properties, and the test checked only the first. That framing is the general lesson: for a rejection, the response is the smaller half of the requirement.
The functional path was untouched. Secure writes worked. Non-secure reads were denied and returned nothing useful. Only the denied write had a side effect, and no functional test had reason to read back a register it had just been told it could not write.
Derive the commit from the qualification instead of running the two in parallel, so that suppression is structural rather than remembered.
// FIX: one qualified decision, and the write enable derived from it.
// Adding a new rejection reason now suppresses the write automatically -
// which is the property being bought here, not just this one bug.
always_comb begin
error_reason = R_NONE;
if (decode_fail) error_reason = R_HOLE;
else if (dp_nonsec && region_is_secure) error_reason = R_SEC;
else if (size_fail) error_reason = R_SIZE;
else if (dp_write && write_to_ro) error_reason = R_RO;
end
assign access_error = dp_valid && (error_reason != R_NONE);
assign access_qualified = dp_valid && (error_reason == R_NONE);
assign wr_en = access_qualified && dp_write; // gated by construction
assign hresp_d = access_error;
// The guard that makes a regression of this bug impossible to ship.
a_no_write_on_error: assert property (@(posedge HCLK) disable iff (!HRESETn)
access_error |-> !wr_en)
else $error("errored access asserted the write enable");The check that finds this class of bug generally is a rejection-invariance check, and it is worth adding to any subordinate that enforces permissions: for every access that receives ERROR, the subordinate's architectural state before and after must be identical. In a UVM environment that is a scoreboard rule — snapshot the shadow model on an errored transfer, compare after — and it costs almost nothing to run on every error the suite already generates. Applied to this design it also found a read-to-clear status register that was cleared by a denied read, a second instance of the same mistake in a different block.
Two habits follow from this lab, and they generalise well beyond AHB:
- Test a rejection twice. Once for the response, once for the absence of effect. A test that only checks the response is testing the easier half.
- Assert your own invariants. The protocol is silent on errored side effects, so protocol conformance can never establish them. Whatever your subordinate promises about rejected accesses, write it in the specification and assert it in RTL, because nothing else in the flow will.
7. Industry Example
Trace ERROR across several failure scenarios in a system.
A processor with a memory-protection scheme accesses a system with mapped memory, peripherals, and unmapped regions.
- Wild pointer — unmapped address ERROR. Buggy software dereferences a wild pointer into unmapped address space. The decoder finds no subordinate; the default subordinate returns the two-cycle ERROR. The processor receives ERROR, raises a bus-fault exception, and its handler logs the fault and terminates the offending task — rather than silently reading garbage. ERROR caught the bug.
- Unauthorized access — permission ERROR. Non-secure software tries to read a secure peripheral. The protection logic detects the violation (HNONSEC/HPROT) and returns ERROR. The processor faults; the security monitor handles it (logs the violation, denies access). ERROR enforced the security boundary — the secure data was never returned.
- ECC failure — subordinate-internal ERROR. A read hits a memory location with an uncorrectable ECC error. The memory subordinate, unable to return valid data, returns ERROR. The processor faults; the handler may retry, mark the page bad, or escalate. ERROR surfaced the hardware fault instead of returning corrupt data.
- Unsupported access — access ERROR. Software issues an unaligned access to a subordinate that requires alignment. The subordinate returns ERROR. The processor faults; the handler corrects or reports the misuse. ERROR caught the illegal access.
- The reaction window in a burst. A burst write hits an error on its third beat. The two-cycle ERROR gives the processor a cycle to react: it drives the next (already-pipelined) beat to IDLE, aborting the rest of the burst cleanly, then handles the error — rather than blindly writing the remaining beats into a failing region. The reaction window enabled the clean abort.
- Recovery is the processor's policy. In every case, ERROR reported the failure; the recovery (fault handler action — log, retry, terminate, deny) was the processor/software's decision. The bus did its job (accurate reporting); the system did its job (policy-based recovery). And after the errored write, software treated the target state as uncertain, re-establishing known state where needed.
The example shows ERROR as the system's fault-and-security backbone: it catches wild pointers, enforces protection, surfaces hardware faults, and flags illegal accesses — each reported accurately via the two-cycle response, each handled by the manager's policy. The reaction window enabled clean burst abort, and recovery remained the manager's decision throughout. ERROR is how failures become handled rather than silent.
8. Common Mistakes
9. Interview Insight
ERROR is a frequent, Critical-rated interview topic — it tests the two-cycle handshake and the report-not-fix semantics.
The answer that lands covers meaning, timing, and semantics: "ERROR is the HRESP value meaning the subordinate couldn't complete the transfer — from an unmapped address (the default subordinate responds), a permission or security violation, an internal fault, or an unsupported access. It's a mandatory two-cycle response: the first cycle drives ERROR with HREADY low — a warning — and the second drives ERROR with HREADY high to complete. The two cycles exist because the next transfer is already pipelined, so the manager needs a guaranteed cycle to react — for instance, to drive the next transfer to IDLE and cancel it. And ERROR reports the failure; it doesn't automatically undo a write — whether an errored write partially applied is unspecified, so the manager treats the state as uncertain and recovery is its policy decision." The two-cycle rationale and the report-not-fix point are the senior signals.
10. Practice Challenge
Reason from ERROR's meaning, timing, and semantics.
- List the triggers. Name the four common causes of an ERROR response.
- Read the waveform. From Figure 2, identify the warning cycle and the completion cycle, and what HREADY does in each.
- Explain the two cycles. Why does ERROR need two cycles? Tie it to the pipeline.
- Report not fix. Explain why ERROR doesn't undo a write and how a manager should treat the state afterward.
- System role. Explain how ERROR underpins fault detection and security enforcement.
11. Key Takeaways
- ERROR is the HRESP failure outcome — the subordinate couldn't complete the transfer (unmapped address, permission/security violation, internal fault, or unsupported access).
- ERROR is a mandatory two-cycle response: cycle 1 = ERROR with HREADY low (warning), cycle 2 = ERROR with HREADY high (completion). The two cycles give the manager a guaranteed reaction window.
- The two cycles exist because of the pipeline — the next transfer is already issued, so the manager needs a cycle to react (e.g., drive the next transfer to IDLE) before the error completes.
- ERROR reports, it doesn't fix — it makes no guarantee about a write's side effects (implementation-specific), so the manager treats the state as uncertain and owns recovery policy.
- The default subordinate returns ERROR for unmapped addresses — the most common ERROR source — preventing silent garbage reads and bus hangs.
- ERROR is the foundation of fault detection and security enforcement — illegal, unauthorized, and failed accesses surface as ERROR and become exceptions; the bus provides the mechanism, the system the policy.
12. What Comes Next
The AHB error cluster divides the subject deliberately. Knowing which page owns what saves reading four chapters to answer one question:
- This page — what counts as an error, what the errored transfer may and may not do, and how software sees it.
- The Two-Cycle ERROR Response — the exact temporal mechanism: the capturing edge, why one warning cycle is enough, and the forward and backward assertions.
- HRESP Generation — how a subordinate produces the response: the response FSM and its RTL.
- Error Response Testing — how to verify all of it: injection sequences, scoreboard, coverage model.
- Master Response Handling — how a manager reacts across the response types.
For the success case, see OKAY Response. For the default subordinate that returns ERROR on unmapped addresses, see Default Slave, Address Decoding and HSEL. For the permission and security inputs the qualification logic checks, see HPROT and HNONSEC & HEXCL. For the legacy full-AHB responses, see RETRY Response and SPLIT Response; AHB-Lite Response Simplification explains why they were dropped. For the pipeline behind the response window, see The Two-Phase Pipeline. For the broader protocol map, see the AMBA family overview.
13. References
- Arm AMBA 5 AHB Protocol Specification (ARM IHI 0033) — the normative definition of the ERROR response: the required two-cycle sequence from the subordinate, and the manager's permission to cancel the remaining transfers of a burst, which the specification states alongside the explicit allowance to complete them.
- Arm AMBA 3 AHB-Lite Protocol Specification (ARM IHI 0033A) — the single-bit HRESP carrying OKAY and ERROR, which is the encoding assumed throughout this chapter.
- Arm AMBA 2 Specification — the two-bit HRESP with RETRY and SPLIT, for legacy full-AHB systems only.
- IEEE 1800 (SystemVerilog) — the assertion and coverage constructs used in section 6b.
Two things this chapter states are engineering practice rather than protocol requirement, and are labelled as such where they appear: that an errored access should leave the subordinate's state unchanged, and the structure of a fault-status register. AMBA does not specify either — it declines to define an errored write's side effects at all, which is precisely why a project must decide, write it down, and check it.