Skip to content

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 channelPPROT 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 incoming PPROT against 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] being 1 means 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 PADDR alone: an address either maps or it doesn't, the same for everyone. A protection violation is decided by PPROT against 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 PSLVERR bit now carries a security guarantee.
  • You must reason about polarity and "no leak," not just timing. The prior chapters made you precise about when PSLVERR is valid. This one makes you precise about what it must be given the PPROT encoding, 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 intuition1 is the less trusted (non-secure) state, matching AMBA's AxPROT[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 — PREADY high, bus returns to IDLE exactly as on a grant. But the subordinate must return no protected data (drive PRDATA to zero/safe) and apply no side effect on a write. The transfer happened on the bus; the resource did not.
A decision diagram showing incoming PPROT[2:0] bits with their polarities feeding a comparison against a resource policy requiring privileged and secure access, branching to a green GRANT box on sufficient context and a red PSLVERR box on an unprivileged or non-secure access.
Figure 1 — the PPROT[2:0] permission check. The incoming access carries PPROT[0] (0 = normal/unprivileged, 1 = privileged), PPROT[1] (0 = secure, 1 = non-secure), and PPROT[2] (0 = data, 1 = instruction). The subordinate holds a per-resource policy — here a register that requires privileged AND secure access. The check compares the incoming context against the requirement: if the access is sufficiently privileged and secure it takes the green GRANT branch and completes normally (PREADY=1, PSLVERR=0, data returned); if it is unprivileged or non-secure it takes the red branch and completes with PSLVERR=1, returning no data and applying no side effect — a protection violation expressed through the ordinary error mechanism. The figure stresses that each bit's polarity must be read exactly, because a flipped bit silently inverts permit and deny.

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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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 / decisionMeaning when 0Meaning when 1Example violation that yields PSLVERR
PPROT[0] — privilegenormal (unprivileged) accessprivileged accessunprivileged access (PPROT[0]=0) to a privileged-only control register
PPROT[1] — securitysecure accessnon-secure accessnon-secure access (PPROT[1]=1) to a secure-only key/config register
PPROT[2] — access typedata accessinstruction accessinstruction fetch (PPROT[2]=1) from a data-only / execute-never region
Check per-peripheraleach slave owns its policy; flexible but the polarity logic is duplicated and must be reviewed N times
Central protection unitone policy block fronts a region; single review surface, but it must know every resource's requirement
Enforce vs ignore PPROTtie-off / advisory onlyactively compareda 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, PREADY rises normally — and PSLVERR stays low at the completion edge while PRDATA carries 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]; — reading PPROT[1]=1 as "secure" — when AMBA defines PPROT[1]=1 as 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 PPROT against 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.
Two side-by-side cases of a non-secure access to a secure-only register: the correct case in green completes with PSLVERR high and PRDATA zero, while the buggy flipped-polarity case in red completes with PSLVERR low and leaks the real secret on PRDATA.
Figure 2 — the wrong-polarity security bug, same non-secure access (PPROT[1]=1) to a secure-only register. Left (correct, green): the check tests PPROT[1] for the non-secure value, recognises the access as non-secure against a secure-only policy, completes with PSLVERR high, and returns zero — the secret stays inside the boundary and the manager raises a fault. Right (bug, red): the RTL inverted the polarity and read PPROT[1]=1 as 'secure', so it grants the access, drives PSLVERR low, and returns the real secure data on PRDATA — a silent permit that leaks the secret with no fault raised. Both complete the transfer identically on the bus; only the verdict and the returned data differ, so the leak is invisible without a negative test.

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 PPROT against 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 with PSLVERR=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 produce PSLVERR and PRDATA == 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 denying PPROT combination 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

  1. 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.
  2. Write the check. From memory, write the priv_ok / secure_ok / prot_violation logic for a register that requires privileged and secure access, and explain why secure_ok inverts PPROT[1].
  3. 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.
  4. Find the leak. Given a waveform where a non-secure access (PPROT[1]=1) to a secure register completes with PSLVERR=0 and real data on PRDATA, identify the one-bit RTL error and the fix.
  5. 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 PPROT against the resource's required permission and completes the access with PSLVERR on 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 ignores PPROT permits everything.
  • Deny is two obligations: assert PSLVERR and 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."