AMBA APB · Module 9
Protection Violations
How PPROT[2:0] carries the privilege, security, and data/instruction context of an access, and how a peripheral enforces its access policy by returning PSLVERR on a permission mismatch. The security stakes: a silent permit is a leak, a silent block is a functional bug — and the bit-polarity trap behind both.
An APB access is not just an address and data — it carries who is asking. The PPROT[2:0] sidebands stamp every transfer with its privilege level, its security level, and whether it is a data or instruction fetch. A subordinate that owns a protected resource compares that context against the resource's required permission, and when the context is insufficient it completes the access with PSLVERR. The single idea to carry: a protection violation is an access-control decision delivered through the ordinary error channel — PPROT is the question, the resource's policy is the rule, and PSLVERR is the "denied." Getting the bit polarity wrong does not crash anything; it silently inverts permit and deny, and on a secure resource that is a data leak.
1. Problem statement
The problem is enforcing an access-control policy on a bus that has no access-control channel — using the same single error bit that signals every other failure.
APB transports PPROT[2:0] alongside PADDR/PWDATA, and those three bits are the only thing the bus tells a subordinate about the requester's privilege and security context. There is no separate "permission denied" response, no protection fault line, no secure/non-secure handshake. So when a subordinate guards a resource that not every requester is allowed to touch — a privileged-only control register, a secure key store, a configuration block — it has exactly one mechanism to refuse: complete the access and drive PSLVERR high. That creates three obligations that "PSLVERR means error" does not capture:
- The verdict depends on
PPROT, not on the address alone. The same register at the same address is grantable to one requester and a violation to another, decided entirely by the incomingPPROTagainst the resource's required permission. The error source is a comparison, not a fixed property of the address. - A denial must not leak. When the policy says no, the subordinate must complete the transfer, assert
PSLVERR, and return no protected data and apply no side effect. A "soft" denial that returns zero but still reads the secret, or that errors but still wrote the register, defeats the entire point of the check. - The bit polarity is the security boundary. Whether
PPROT[1]being1means secure or non-secure is not cosmetic — read it backwards and a non-secure requester is granted a secure resource. The protocol gives you a context; the correctness of how you interpret it is the access-control guarantee.
So the job is not "report an error" — it is "evaluate the requester's privilege/security context against this resource's policy, and on a mismatch deny the access cleanly through PSLVERR, leaking nothing and changing nothing."
2. Why previous knowledge is insufficient
Chapter 9.1 established what PSLVERR is — a single-cycle pass/fail verdict sampled only on the completion edge, not an abort. Chapter 9.2 pinned the timing — that the verdict must be produced coincident with PREADY across any wait count. Chapter 9.3 gave the first source: the address decoder finding no mapped slave. Those chapters tell you everything about the error mechanism. This chapter supplies a different source — one that does not come from the address at all — and that exposes gaps the earlier view never touched:
- The error is a function of context, not address. Chapter 9.3's illegal-address error is decided by
PADDRalone: an address either maps or it doesn't, the same for everyone. A protection violation is decided byPPROTagainst a mapped, perfectly legal address — the resource exists, the requester just isn't allowed. You cannot detect it by looking at the address; you must look at the sidebands. - The error mechanism is now an access-control boundary. Earlier sources (no map, internal fault) were functional failures. A protection violation is a security decision. That changes the cost of a bug: a wrong illegal-address decode is a functional miss; a wrong protection decode is either a denial-of-service (legitimate firmware blocked) or a confidentiality breach (secure data handed to a non-secure requester). The same
PSLVERRbit now carries a security guarantee. - You must reason about polarity and "no leak," not just timing. The prior chapters made you precise about when
PSLVERRis valid. This one makes you precise about what it must be given thePPROTencoding, and about the second obligation that has no analogue in a timing bug: on deny, return nothing. A protection check that errors at the right time but returns the secret anyway is still a hole.
So the model to add is the policy model: PPROT carries the access context, the resource carries a required permission, and PSLVERR is the enforcement verb — with the bit polarity and the no-leak rule as load-bearing as the timing. (Tracing a reported protection PSLVERR back to its origin is its own discipline, covered in chapter 9.6.)
3. Mental model
The model: PPROT is the requester's badge, the resource has a clearance requirement, and PSLVERR is the locked door. Every access walks up wearing a badge that states three things — am I privileged, am I secure, am I fetching code. The resource checks the badge against what it requires. If the badge clears, the door opens (normal completion). If it doesn't, the door stays shut and a "denied" light comes on (PSLVERR) — and crucially, nothing behind the door is shown or moved.
Three refinements make it precise:
- The badge has exact polarities — read them right.
PPROT[0]:0= normal/unprivileged,1= privileged.PPROT[1]:0= secure,1= non-secure.PPROT[2]:0= data,1= instruction. Note the security bit is inverted from intuition —1is the less trusted (non-secure) state, matching AMBA'sAxPROT[1]. Misreading any bit flips the decision. - The resource owns the requirement, not the bus. The bus only carries the badge; it does not decide. The subordinate (or a protection unit in front of it) holds the policy — "this register requires privileged and secure" — and performs the comparison. Two resources behind the same bus can have entirely different requirements.
- A denial completes, leaks nothing, and changes nothing. The "denied" verdict rides the ordinary completion edge —
PREADYhigh, bus returns toIDLEexactly as on a grant. But the subordinate must return no protected data (drivePRDATAto zero/safe) and apply no side effect on a write. The transfer happened on the bus; the resource did not.
4. Real SoC implementation
In silicon, a protection-checking subordinate (or a thin wrapper in front of a plain one) decodes PPROT[2:0] for the access that is completing, compares it against the addressed resource's required permission, and produces PSLVERR on a mismatch — on the same registered edge as PREADY, exactly as chapter 9.2 requires. The check sits either per-peripheral (each slave knows its own policy) or in a central protection unit that fronts a region; the logic is the same comparison either way.
// Protection-checking APB subordinate.
// PPROT bit meanings (AMBA polarity — get these EXACTLY right):
// PPROT[0] : 0 = normal (unprivileged) access, 1 = privileged access
// PPROT[1] : 0 = secure access, 1 = non-secure access
// PPROT[2] : 0 = data access, 1 = instruction access
//
// This block guards a control register that is privileged-only AND secure-only.
// The violation is purely a function of PPROT vs the resource policy — the
// address is valid; the requester simply lacks the required context.
logic priv_ok; // requester is privileged
logic secure_ok; // requester is secure
logic prot_violation;
assign priv_ok = pprot[0]; // 1 => privileged -> required here
assign secure_ok = ~pprot[1]; // pprot[1]==0 => secure -> required here
// NB: secure_ok inverts pprot[1] because 1 means NON-secure. Writing
// (pprot[1]) here would be the classic polarity bug -> grants non-secure.
// Resource policy for the addressed register: needs BOTH privileged and secure.
assign prot_violation = sel && (!priv_ok || !secure_ok);
always_ff @(posedge pclk or negedge presetn) begin
if (!presetn) begin
pready <= 1'b0;
pslverr <= 1'b0;
prdata <= '0;
end else if (sel && penable && access_done) begin
pready <= 1'b1; // complete the access THIS cycle...
pslverr <= prot_violation; // ...stamp DENY on the same edge as PREADY
if (prot_violation) begin
prdata <= '0; // DENY: leak nothing on a read
// and apply NO side effect on a write (do not update the register)
end else if (!pwrite) begin
prdata <= reg_q; // GRANT read: return the real data
end else begin
reg_q <= pwdata; // GRANT write: apply the side effect
end
end else begin
pready <= 1'b0;
pslverr <= 1'b0; // not completing -> defined 0, never float/X
end
end
// Two guarantees: (1) on deny, PSLVERR is the verdict AND prdata is zero AND the
// register is untouched — the door stays shut. (2) the verdict is coincident with
// PREADY by construction, so it is sampled correctly at the completion edge.Two facts drive the real design. First, the comparison must be against the access that is completing, with the same edge alignment chapter 9.2 demands — the PPROT decode and PREADY are produced together, or the verdict and completion disagree. Second, deny is two obligations, not one: assert PSLVERR and withhold the resource (no data out, no side effect). A subordinate that errors but still returns reg_q, or errors but still writes pwdata, has reported a violation while committing it — the worst possible outcome on a secure resource. The central-protection-unit variant moves this comparison upstream (one policy block for a whole region instead of per-slave), but the contract — decode PPROT, compare to policy, deny cleanly through PSLVERR — is identical.
5. Engineering tradeoffs
The PPROT[2:0] encoding and the policy choices around it are a set of consequential decisions about what context means and where the check lives.
| PPROT bit / decision | Meaning when 0 | Meaning when 1 | Example violation that yields PSLVERR |
|---|---|---|---|
PPROT[0] — privilege | normal (unprivileged) access | privileged access | unprivileged access (PPROT[0]=0) to a privileged-only control register |
PPROT[1] — security | secure access | non-secure access | non-secure access (PPROT[1]=1) to a secure-only key/config register |
PPROT[2] — access type | data access | instruction access | instruction fetch (PPROT[2]=1) from a data-only / execute-never region |
| Check per-peripheral | — | — | each slave owns its policy; flexible but the polarity logic is duplicated and must be reviewed N times |
| Central protection unit | — | — | one policy block fronts a region; single review surface, but it must know every resource's requirement |
| Enforce vs ignore PPROT | tie-off / advisory only | actively compared | a slave that ignores PPROT permits everything — fine for a truly public peripheral, a hole for a protected one |
The throughline: PPROT is three independent context bits with fixed, non-obvious polarities (especially the inverted security bit), and the policy is a per-resource requirement compared against them. The cheap decisions — which bit, where the check lives — are dominated by one expensive one: the comparison's polarity and the no-leak behaviour, because that is the actual security boundary. Centralising the check shrinks the surface you must get exactly right; ignoring PPROT on a resource that needs it is a silent permit.
6. Common RTL mistakes
7. Debugging scenario
The signature protection bug is a PPROT polarity error: the check looks present and reasonable, the access completes, and the wrong requesters are granted — invisible until something reads data it should never have seen, or legitimate firmware is mysteriously blocked.
- Observed symptom: during security bring-up, a non-secure bus master reads a secure-only key register and gets back the real key material instead of a fault — confidentiality is broken. (The inverse also appears: a secure-only register also rejecting a legitimate secure access, blocking privileged firmware.) No exception fires; the read "succeeds."
- Waveform clue: on the offending access,
PPROT[1]=1(a non-secure requester), the address is the legal secure register,PREADYrises normally — andPSLVERRstays low at the completion edge whilePRDATAcarries the secret. The protection logic produced a grant for a non-secure access to a secure resource: the verdict is exactly backwards. - Root cause: the secure check was written
secure_ok = pprot[1];— readingPPROT[1]=1as "secure" — when AMBA definesPPROT[1]=1as non-secure. The polarity is inverted, so every non-secure access is treated as cleared and every secure access as denied. The error logic is structurally correct; one bit's sense is wrong. - Correct RTL: invert the security comparison to match the spec —
assign secure_ok = ~pprot[1]; // pprot[1]==0 => secure— and gate the data path on the verdict:if (prot_violation) prdata <= '0;so a denial returns nothing even if the rest of the read path is wrong. Make the polarity self-documenting with a comment stating the AMBA meaning at the point of use. - Verification assertion: assert the security property directly — a non-secure access to a secure resource must error and must not return data:
assert property (@(posedge pclk) disable iff(!presetn) (sel && penable && pready && is_secure_resource && pprot[1]) |-> pslverr);and the no-leak half:assert property (@(posedge pclk) disable iff(!presetn) (sel && penable && pready && pslverr) |-> (prdata == '0));. - Debug habit: when a protected resource grants the wrong requester, do not start in the data path — line up
PPROTagainst the resource's required policy at the completion edge and check the sense of each comparison. Write the AMBA polarity next to the bit every time (pprot[1]==0 => secure), and prove permit and deny with directed tests; a polarity bug passes every "legal access" test and only fails the negative one — which is exactly the test most likely to be missing.
8. Verification perspective
A protection check is a small comparison with an enormous failure cost, so verification must prove both that the policy is right and that a denial leaks nothing — and a directed suite that only ever runs the legal access proves neither.
- Cross
PPROTagainst resource permission. Functional coverage must cross the access context — privileged/unprivileged, secure/non-secure, data/instruction — against each protected resource's required policy, so every (context × resource) cell is exercised. The diagonal cases (legal access) are easy; the off-diagonal cases (the violations) are the entire point and are exactly where polarity bugs hide. Without the cross, a flipped bit passes silently because no test ever drove the denying context. - Assert permit and deny, not just one. For each protected resource, bind two properties: a permit assertion that a sufficiently-cleared access completes with
PSLVERR=0(legitimate firmware is not blocked), and a deny assertion that an insufficiently-cleared access completes withPSLVERR=1. A correct design needs both — testing only permit lets a deny-everything bug through; testing only deny lets a grant-everything bug through. The polarity bug fails exactly one of them. - Prove the security property: no secure data on a non-secure access. This is the load-bearing assertion. For any secure resource, a non-secure access (
pprot[1]==1) must producePSLVERRandPRDATA == 0(no leak). Make it a formal property if possible — it is small enough to prove exhaustively, and "secure data never crosses to a non-secure access" is the kind of guarantee you want proven, not sampled. Pair it with negative testing: explicitly drive every denyingPPROTcombination at every protected register and check the response is error-with-no-data, never grant-with-data.
The point: protection-violation verification is a cross-coverage problem (every context against every policy), a bidirectional assertion problem (permit and deny both), and above all a security-property problem (no leak on a denied access) — and the negative tests, the ones a happy-path suite omits, are the ones that catch the polarity bug.
9. Interview discussion
"How does an APB peripheral enforce that only secure or only privileged software can touch a register?" separates engineers who have built protected SoCs from those who have only read the timing diagrams. The wrong answer is "the bus blocks it" or "it returns zero."
Frame it as PPROT is the context, the resource is the policy, PSLVERR is the enforcement: every access carries PPROT[2:0] — [0] privilege (1=privileged), [1] security (0=secure, 1=non-secure), [2] data/instruction (1=instruction) — and the subordinate (or a protection unit in front of it) compares that context against the resource's required permission, completing the access with PSLVERR high on a mismatch. State the polarities explicitly, especially the inverted security bit, because that is the detail that signals you have actually written the check. Then deliver the depth: a denial is two obligations — assert PSLVERR and leak nothing (no protected data on the read, no side effect on the write), since the transfer still completes on the bus and APB does not roll back; protection is the slave's job, not the bus's — APB only transports PPROT, the comparison and policy live in the design; and the polarity is the security boundary — read PPROT[1] backwards and you grant every non-secure requester, a hole that passes every legal-access test and only fails the negative one. Closing with "and it is otherwise an ordinary completion-edge error — same timing contract as any other PSLVERR, just sourced from a permission check instead of an address decode" shows you see it as one instance of the error mechanism, not a special case.
10. Practice
- Decode the badge. Given
PPROT = 3'b011, state each bit's meaning (privilege, security, access type) using the correct polarities, and say whether this access clears a privileged-and-secure-only register. - Write the check. From memory, write the
priv_ok/secure_ok/prot_violationlogic for a register that requires privileged and secure access, and explain whysecure_okinvertsPPROT[1]. - Deny cleanly. On a denied read of a secure register, state the two things the subordinate must do beyond asserting
PSLVERR, and what breaks if it skips either. - Find the leak. Given a waveform where a non-secure access (
PPROT[1]=1) to a secure register completes withPSLVERR=0and real data onPRDATA, identify the one-bit RTL error and the fix. - Cross-cover it. List the (context × resource) cells you must cover for one register that requires secure only (privilege don't-care), and identify which cell is the security-critical negative test.
11. Q&A
12. Key takeaways
PPROT[2:0]carries the access context, with fixed AMBA polarities:PPROT[0]0=normal/1=privileged;PPROT[1]0=secure/1=non-secure (inverted from intuition);PPROT[2]0=data/1=instruction.- A protection violation is a comparison, not an address property. The subordinate (or a protection unit) compares the incoming
PPROTagainst the resource's required permission and completes the access withPSLVERRon a mismatch — the same register can be a grant to one requester and a violation to another. - Enforcement is the slave's job, not the bus's. APB only transports
PPROT; the policy and the comparison live in the design. A resource that ignoresPPROTpermits everything. - Deny is two obligations: assert
PSLVERRand leak nothing — return no protected data and apply no side effect — because the transfer completes on the bus and APB does not roll back. - The bit polarity is the security boundary. Reading
PPROT[1]backwards silently grants non-secure requesters secure resources; it passes every legal-access test and only fails the negative one. Comment the polarity, and prove both permit and deny. - It is otherwise an ordinary completion-edge error — same timing contract as any other
PSLVERR(chapters 9.1/9.2), just sourced from a permission check. Verify the (context × resource) cross, both permit and deny, and the security property "no secure data on a non-secure access."