AMBA APB · Module 15
APB Corner Cases
The legal-but-extreme corners of the APB state space that directed positive tests miss and random under-hits — reset-during-transfer, back-to-back, max wait states / PREADY stuck low, mixed latency, and read-after-write to the same address. Enumerate them as a corner list, stimulate each deliberately, and check each with an assertion, a scoreboard, and a coverage bin. The capstone of the APB verification module.
The worst APB silicon bugs do not live in illegal traffic — they live in the legal traffic at the edges of the state space, where directed positive tests rarely go and constrained-random under-hits. A corner case is a transfer that is fully compliant with the spec yet sits at a boundary so extreme that the RTL author never pictured it: a reset asserted in the exact middle of an ACCESS phase, a new transfer that begins on the same edge the previous one completes, a slave that holds PREADY low for far longer than anyone expected. The single idea to carry: corner cases are the boundary states of the APB state space — reset-during-transfer, back-to-back, max-wait, mixed-latency, read-after-write-same-address — that random and directed-positive tests under-hit but that produce the highest-impact escapes, so they must be explicitly enumerated as a corner list, deliberately stimulated with directed sequences, and individually checked with an assertion, a scoreboard expectation, and a coverage bin. These are not illegal injections — every corner here is spec-legal. They are the legal extremes, and they are where chips die in the field.
1. Problem statement
The problem is enumerating, deliberately stimulating, and individually checking the low-probability, high-impact, legal-but-extreme boundary scenarios of the APB protocol — the corners of the state space that a verification environment built only from directed positive tests and seeded constrained-random will under-hit, yet that cause the worst escapes when the RTL mishandles them.
A directed positive test exercises the protocol's centre: a clean SETUP, one or two ACCESS cycles, completion, idle, repeat. That centre is necessary and it is easy. The danger is at the edges, where the protocol is still legal but the conditions are extreme — and three things make those edges genuinely hard:
- The corner is legal, so no rule-checker flags it. Reset arriving mid-ACCESS is permitted; a transfer following another with no idle cycle is permitted; a slave stretching
PREADYfor hundreds of cycles is permitted. None of these violate the rules catalogue, so the protocol assertions stay silent — the question is not "did the bus break a rule?" but "did the DUT survive a legal extreme?" - The corner is low-probability, so random under-hits it. A boundary state — reset landing in the one cycle that is ACCESS, a new SETUP on the exact completion edge — occupies a vanishingly small slice of the input space. Constrained-random will reach the centre millions of times and the boundary almost never, so leaving corners to chance means they ship un-exercised.
- The corner is high-impact, so mishandling it is catastrophic. These are the scenarios where a write half-commits, where a slave latches stale state across reset, where the bus hangs forever because
PREADYnever came. The impact is inversely proportional to the probability — the rarer the corner, the more likely the RTL author never handled it, and the worse the field failure.
So the job is to convert "we hope the random hit the edges" into "we enumerated every boundary, drove each one on purpose, and proved each one is handled" — a named corner list, a directed sequence per corner, and a triad of checks (assertion + scoreboard + coverage bin) per corner.
2. Why previous knowledge is insufficient
Everything earlier in this module gives you the machinery to check APB and to measure what you exercised — but none of it makes the extreme boundary states actually occur, and that is exactly the gap corner cases fill.
- The coverage model measures the space; it does not enumerate the corners. Chapter 15.5 (functional coverage) builds a covergroup over direction, region, response, and wait-count — the general model. But a general wait-count coverpoint with a
[2:$]"many" bin tells you some long wait happened; it does not have a bin for "PREADYstuck low past the watchdog threshold," nor for "back-to-back with zero idle," nor for "reset asserted during ACCESS." Corner cases demand corner-specific bins — named boundary cells the general model would never carve out on its own. Coverage is the instrument; the corner list is the thing you point it at. - The catalogue tells you what is legal; it does not rank legal scenarios by danger. Chapter 15.1 (rules catalogue) enumerated every rule — but a rule list treats all legal traffic as equal. The catalogue does not say "reset-during-transfer is legal and it is the scenario your RTL is most likely to get wrong." Corner cases are precisely the subset of legal-and-extreme scenarios that the catalogue permits but does not flag as high-risk; selecting and prioritising them is a separate engineering judgement the catalogue does not make for you.
- Corners are legal-but-extreme — not illegal. This is the critical distinction from Chapter 15.7 (negative testing). Negative testing injects out-of-spec stimulus —
PADDRchanging mid-transfer,PSELdropping during ACCESS, X on a control line where the spec forbids it — to confirm the checkers catch the violation. Corner cases drive fully compliant stimulus at its extremes to confirm the DUT survives it. The two are complementary and must not be conflated: negative testing asks "does my monitor scream when the protocol is broken?"; corner-case testing asks "does my design behave when the protocol is obeyed but pushed to its boundary?" An illegal case must be rejected; a corner case must be handled.
So the model to add is the corner list as a first-class artifact: a deliberately-enumerated set of legal boundary states, each paired with a directed sequence that forces it and a triad of checks that confirms the DUT handled it — sitting beside the coverage model, drawing on the catalogue, and sharply distinct from negative testing.
3. Mental model
The model: the APB state space is a wide flat plain with a few sheer cliffs at its edges, and corner cases are the cliffs. Directed positive tests walk the plain — the well-travelled centre where SETUP leads to ACCESS leads to completion. Constrained-random scatters footprints across the plain, dense in the middle and exponentially sparser toward the edges. The cliffs — reset landing in ACCESS, a transfer beginning on a completion edge, PREADY never arriving — are at the very rim, where almost no random footprint reaches and where a misstep is fatal. To verify the cliffs you cannot wander; you must walk to each edge on purpose, push off it deliberately, and have an instrument that records you reached it and a checker that confirms you survived.
Three refinements make the model precise and interview-ready:
- Each corner is a named boundary state, not a vague "edge case." The discipline is to enumerate them: (1) reset-during-transfer —
PRESETnasserted while a transfer is in ACCESS; all bus signals must go to their reset state, no partial write commits, the slave returns to clean idle; (2) back-to-back — a new SETUP on the cycle immediately after a completion, withPSELstaying high across the boundary whilePENABLEdrops to 0 for the new SETUP then rises again; (3) max-wait /PREADYstuck low — the slave never completes, so a watchdog must fire rather than the bus hanging silently; (4) mixed-latency — a zero-wait transfer immediately followed by a max-wait one (and the reverse), stressing latency transitions; (5) read-after-write to the same address — ordering and coherency at the bus boundary. Naming each one is what lets you write a sequence and a bin for it. - A corner needs a directed sequence, because random will not reach it. You do not configure random and hope; you write a sequence that constructs the boundary: issue a transfer, count into its ACCESS phase, assert
PRESETnon that exact cycle; or complete a transfer and drive the next SETUP on the very next edge with no idle. The corner is built, not stumbled upon. - A corner is verified only when all three checks agree it happened and was handled. The assertion proves the DUT obeyed the boundary rule (reset forced signals to idle; back-to-back re-sequenced
PENABLEcorrectly). The scoreboard proves the data outcome was right (no half-committed write; read-after-write returned the just-written value). The coverage bin proves the corner was reached — a named bin per corner, because the general coverage model would report closure without ever carving these cells. Assertion = obeyed the rule; scoreboard = produced the right data; coverage = the corner was exercised. All three, per corner.
4. Real SoC implementation
In a real APB environment the corners are driven by directed sequences — one per corner — running on the same agent as the random traffic, and each is backed by a corner assertion in the checker and a corner bin in the coverage model. The sequence below constructs two of the highest-risk corners — reset-during-ACCESS and a zero-idle back-to-back pair — and the assertions that confirm the DUT handled them:
// apb_corner_seq.sv — directed corner-case sequences. These do NOT rely on
// random reaching the boundary; they CONSTRUCT the boundary on purpose.
// Each corner is paired with an assertion (below) and a coverage bin.
class apb_corner_seq extends uvm_sequence #(apb_seq_item);
`uvm_object_utils(apb_corner_seq)
virtual apb_if vif; // direct interface handle to time PRESETn precisely
function new(string name = "apb_corner_seq");
super.new(name);
endfunction
// CORNER 1: RESET-DURING-TRANSFER — assert PRESETn while a write is in ACCESS.
// Legal-but-extreme: the slave must abort cleanly, commit NOTHING, idle.
task reset_during_access(bit [31:0] addr, bit [31:0] data);
apb_seq_item req = apb_seq_item::type_id::create("req");
fork
// drive a normal write that intends to take a couple of wait states
begin
start_item(req);
req.pwrite = 1; req.paddr = addr; req.pwdata = data;
req.expect_waits = 2; // slave will hold PREADY low a while
finish_item(req);
end
// race a reset pulse into the ACCESS phase of that same write
begin
@(posedge vif.pclk iff vif.psel && !vif.penable); // SETUP seen
@(posedge vif.pclk); // now in ACCESS
vif.presetn = 1'b0; // <-- PRESETn asserted mid-ACCESS
repeat (2) @(posedge vif.pclk);
vif.presetn = 1'b1; // release; bus must restart from idle
end
join
endtask
// CORNER 2: BACK-TO-BACK — a new SETUP on the cycle AFTER completion, no idle.
// PSEL must stay high across the boundary; PENABLE drops to 0 for the new
// SETUP, then rises for its ACCESS. The most commonly mis-handled corner.
task back_to_back(bit [31:0] a0, bit [31:0] d0, bit [31:0] a1, bit [31:0] d1);
apb_seq_item t0 = apb_seq_item::type_id::create("t0");
apb_seq_item t1 = apb_seq_item::type_id::create("t1");
start_item(t0);
t0.pwrite = 1; t0.paddr = a0; t0.pwdata = d0;
t0.no_idle_after = 1; // driver keeps PSEL high into the next item
finish_item(t0);
start_item(t1); // issued with ZERO idle cycles between
t1.pwrite = 1; t1.paddr = a1; t1.pwdata = d1;
finish_item(t1);
endtask
task body();
reset_during_access(32'h0000_0010, 32'hCAFE_F00D);
back_to_back(32'h0000_0020, 32'hAAAA_1111,
32'h0000_0024, 32'hBBBB_2222);
endtask
endclass
// apb_corner_checks.sv — assertions that confirm each corner was HANDLED.
// Bound to the APB interface, non-intrusive, alongside the protocol checker.
module apb_corner_checks (
input logic pclk,
input logic presetn,
input logic psel,
input logic penable,
input logic pready,
input logic pwrite,
input logic slave_busy, // slave-internal: a transfer is committing
input logic slave_idle // slave-internal: FSM is in its IDLE state
);
// CORNER 1 check (a): reset forces all bus control to idle the next cycle.
// While PRESETn is low, PSEL and PENABLE must be deasserted — no live transfer.
property p_reset_forces_idle;
@(posedge pclk) (!presetn) |-> (!psel && !penable);
endproperty
a_reset_forces_idle: assert property (p_reset_forces_idle);
// CORNER 1 check (b): NO PARTIAL COMMIT — a write that was mid-ACCESS when
// reset hit must not commit. The slave must not still be "busy committing"
// one cycle after reset asserts; it must drop to not-busy.
property p_no_partial_commit;
@(posedge pclk) $fell(presetn) |=> !slave_busy;
endproperty
a_no_partial_commit: assert property (p_no_partial_commit);
// CORNER 1 check (c): clean idle after reset releases — the slave FSM is in
// IDLE the cycle reset deasserts, not stuck in a stale mid-transfer state.
property p_clean_idle_on_release;
@(posedge pclk) $rose(presetn) |-> slave_idle;
endproperty
a_clean_idle_on_release: assert property (p_clean_idle_on_release);
// CORNER 2 check: BACK-TO-BACK re-sequencing — on the completion edge
// (PSEL & PENABLE & PREADY) if PSEL stays high into the next cycle (no idle),
// then PENABLE MUST drop to 0 next cycle (the new SETUP), never stay high.
// If PENABLE failed to deassert, the slave would miss the new transfer.
property p_back_to_back_resequence;
@(posedge pclk) disable iff (!presetn)
(psel && penable && pready) ##1 psel |-> !penable;
endproperty
a_back_to_back_resequence: assert property (p_back_to_back_resequence);
// MAX-WAIT WATCHDOG: once in ACCESS, PREADY must arrive within MAX_WAIT
// cycles, else the bus is hung — fail loudly rather than stall forever.
localparam int MAX_WAIT = 1024;
property p_pready_watchdog;
@(posedge pclk) disable iff (!presetn)
(psel && penable && !pready) |-> ##[1:MAX_WAIT] pready;
endproperty
a_pready_watchdog: assert property (p_pready_watchdog);
// CORNER COVERAGE: named bins so the general model can't report closure
// without these corners actually having been reached.
c_reset_in_access: cover property (@(posedge pclk) $fell(presetn) && penable);
c_zero_idle_b2b: cover property (@(posedge pclk)
(psel && penable && pready) ##1 (psel && !penable));
c_max_wait_tail: cover property (@(posedge pclk)
(psel && penable && !pready) [*32]); // long stretch reached
endmoduleTwo facts make this the right shape. First, the corner is constructed, not awaited — reset_during_access uses a direct interface handle to drop PRESETn on the precise cycle the transfer is in ACCESS, and back_to_back keeps PSEL high across the boundary so the new SETUP abuts the old completion with zero idle. Neither corner is left to the seed. Second, each corner carries its own triad — the reset corner has three assertions (forces-idle, no-partial-commit, clean-idle-on-release) plus the c_reset_in_access bin; the back-to-back corner has its re-sequencing assertion plus the c_zero_idle_b2b bin; and the scoreboard supplies the data check (the aborted write committed nothing; both back-to-back writes landed in order). The wait-state challenges page goes deeper on the watchdog and on PREADY timing; here it is one named corner among the list.
5. Engineering tradeoffs
The corner-case map is the engineering artifact: each row names a corner, says what makes it a corner, what breaks if the RTL mishandles it, and how the triad checks it. Building this table — and keeping it complete — is the work.
| Corner scenario | What makes it a corner (legal-but-extreme) | What breaks if mishandled | How to check it (assertion · scoreboard · coverage) |
|---|---|---|---|
| Reset-during-transfer | PRESETn asserted mid-ACCESS — legal at any time, but lands in the one cycle the slave is actively transferring | A write half-commits (partial data latched); the slave stays in a stale mid-transfer FSM state; corrupt latched control survives reset | Assert: reset ⇒ !psel && !penable; `$fell(presetn) |
| Back-to-back (zero idle) | A new SETUP on the cycle immediately after completion — legal, but PSEL never drops, so the SETUP/ACCESS boundary is invisible without watching PENABLE | PENABLE not deasserted for the new SETUP ⇒ slave misses the new transfer; or the slave merges the two transfers / re-uses stale PADDR | Assert: on completion, if PSEL stays high then next cycle !penable. Scoreboard: both transfers land, in order, distinct addresses. Coverage: completion ##1 (psel && !penable) bin |
Max-wait / PREADY stuck low | The slave legally holds PREADY low — but for far longer than expected, or forever | The bus hangs silently; the manager stalls with no timeout; a deadlock ships as an intermittent "hang" | Assert: watchdog — in ACCESS, PREADY within MAX_WAIT else fail. Scoreboard: no completion recorded ⇒ flagged. Coverage: long-stretch / watchdog-threshold bin |
| Mixed-latency (0-wait abutting max-wait) | A zero-wait transfer immediately followed by a max-wait one (and the reverse) — legal, but stresses the latency transition, not steady state | The manager mis-pipelines: it assumes the next transfer is also fast, samples too early, or drops the held bus on the slow one | Assert: stability holds across the slow transfer regardless of the prior fast one. Scoreboard: both data values correct. Coverage: cross(wait_prev, wait_curr) cell {0} × {many} |
| Read-after-write same address | A read of an address on the cycle-boundary right after a write to it — legal, but tests ordering/coherency at the bus boundary | The read returns stale (pre-write) data — the write had not yet committed when the read sampled; a coherency hole at the bus edge | Assert: no overlap/illegal interleave on the shared address. Scoreboard: read returns the just-written value (write-before-read ordering). Coverage: cross(op, same_addr) cell write→read |
The throughline: a corner is only "done" when its row is fully populated — what makes it extreme, what breaks, and a check in all three columns. The tradeoff is which corners to enumerate and how hard to push each: more corners and deeper stress cost stimulus-authoring and runtime, but each row left empty is a legal extreme shipping un-exercised. The discipline mirrors the coverage model's "every meaningful bin explained" — here, every corner row must have an assertion, a scoreboard expectation, and a named bin, or it is not verified.
6. Common RTL mistakes
7. Debugging scenario
The signature corner-case escape is a half-committed write surviving reset: a peripheral that, when reset lands in the middle of an ACCESS write, latches the partial write and comes out of reset holding a value that was never supposed to commit — a legal scenario the RTL author never pictured.
- Observed symptom: a peripheral intermittently boots with one configuration register holding a garbage value — not its reset default, not a value firmware ever wrote. It happens only on warm resets that occur under bus traffic, perhaps once in thousands of resets, and never in the clean directed tests. In silicon it presents as a rare, unreproducible boot-state corruption.
- Waveform clue: on the captured trace,
PRESETnfalls whilePSEL,PENABLEare high andPWRITEis 1 — a write is in ACCESS. The slave's internal write-commit strobe pulses on the same edgePRESETnfalls, latchingPWDATAinto the register. The register then holds that mid-flight value across reset;slave_busywas still high one cycle afterPRESETnasserted. - Root cause: the slave's write-commit logic was gated only on
psel && penable && preadyand was not qualified bypresetn. When reset asserted in ACCESS, the commit condition was still momentarily true, so the register captured the in-flightPWDATAinstead of aborting. The FSM, meanwhile, did not force itself to idle on reset, so it carried stale state. The transfer half-committed: a write that the manager considered aborted left a value in the slave. - Correct RTL: qualify every commit and every state update by reset, and force the FSM to a clean idle. The register must take its reset value, never a mid-flight capture:
// FIX: gate the write commit on reset and force a clean idle FSM.
// A transfer in flight when PRESETn asserts commits NOTHING.
always_ff @(posedge pclk or negedge presetn) begin
if (!presetn) begin
state <= S_IDLE; // clean idle, no stale mid-transfer state
cfg_reg <= CFG_RESET_VAL; // reset default — never a mid-flight value
busy <= 1'b0; // not committing
end else begin
case (state)
S_IDLE : if (psel && !penable) state <= S_ACCESS;
S_ACCESS : begin
busy <= 1'b1;
if (pready) begin
if (pwrite) cfg_reg <= pwdata; // commit ONLY when not in reset
busy <= 1'b0;
state <= (psel && !penable) ? S_ACCESS : S_IDLE; // back-to-back
end
end
endcase
end
end- Verification assertion: prove no partial commit and a clean idle, and cover that the corner was actually reached so the proof is non-vacuous:
// reset in the middle of a transfer commits nothing and idles cleanly
a_no_partial_commit: assert property (@(posedge pclk)
$fell(presetn) |=> !busy);
a_clean_idle: assert property (@(posedge pclk)
$rose(presetn) |-> (state == S_IDLE));
c_reset_in_access: cover property (@(posedge pclk)
$fell(presetn) && penable && pwrite);- Debug habit: when a peripheral boots with state that is neither its reset default nor anything firmware wrote, suspect a legal corner the RTL never handled — and reach first for reset-during-transfer. Check whether
PRESETncan fall while a transfer is in ACCESS, whether every commit and state update is qualified by reset, and whether a directed sequence ever drove that race. If the corner has no directed test and nocover, it almost certainly shipped un-exercised, and the field bug is the first time the boundary was ever hit.
8. Verification perspective
Corner cases are a verification deliverable in their own right — a named list with a directed sequence and a check triad per corner — and they exist precisely because the two instruments you already have (constrained-random and the general coverage model) systematically under-serve the boundary.
- Constrained-random under-hits corners by construction, so you force them with directed sequences. A boundary state is a tiny, sharply-conditioned slice of the input space — reset landing in the exact ACCESS cycle, a SETUP on the exact completion edge — and a random distribution spends its mass in the centre. No reasonable seed count makes the rim dense. The response is not "run more seeds"; it is to construct the corner: a directed sequence that drives a transfer into ACCESS and asserts
PRESETnon that cycle, or that holdsPSELhigh to abut two transfers with zero idle. Random covers the plain; directed sequences walk to each cliff. - Corner coverage is explicit named bins, not a side-effect of the general model. The general coverage model measures the space, but its bins (
zero/one/manywaits, region, response) do not carve out the corners — a[2:$]bin fills on ordinary slow transfers while thePREADY-stuck tail goes unhit. So corner verification adds corner-specific bins:$fell(presetn) && penable(reset-in-ACCESS),completion ##1 (psel && !penable)(zero-idle back-to-back), a long-stretchPREADY-low tail, and cross bins for the combinations —wait_prev × wait_currcell{0}×{many}(mixed latency),op × same_addrcellwrite→read(read-after-write). The cross is what forces the transition corners the marginals never hit; a named bin per corner is what stops "closed" from being silent about them. - Hang corners need a watchdog, because their failure mode is the absence of an event. Most checks fire when something wrong happens; the
PREADY-stuck corner fails when something right never happens — completion never arrives. A property like(psel && penable && !pready) |-> ##[1:MAX_WAIT] preadyconverts "the bus hung" into a concrete, bounded assertion failure with a cycle and a signal, instead of a simulation that runs to timeout with no diagnosis. Every hang-class corner gets a bounded-liveness watchdog so silence becomes a loud failure. - The triad makes each corner auditable. Per corner: an assertion (the boundary rule was obeyed), a scoreboard expectation (the data outcome was right — no half-commit, ordered back-to-back, fresh read-after-write), and a coverage bin (the corner was reached). Sign-off walks the corner list and confirms all three are green for each — assertion and scoreboard prove handled, the bin proves exercised, and an empty bin means the directed sequence never drove the corner, which is a hole, not a pass.
The point: corners are under-served by random and by general coverage by their very nature, so verification owns them as an explicit list — directed sequences to construct each boundary, named bins (including crosses) to prove each was reached, watchdogs to turn hangs into failures, and a per-corner assertion-plus-scoreboard-plus-bin triad that makes the list auditable at sign-off.
9. Interview discussion
"What are APB corner cases, and how are they different from negative testing?" is a critical senior-DV question, and the answer that signals real silicon experience opens on the legal-versus-illegal distinction and then shows the enumerate-stimulate-check discipline.
Lead with the distinction: a corner case is legal-but-extreme — fully spec-compliant traffic at a boundary the RTL author rarely pictured, and the DUT must handle it — whereas negative testing injects illegal, out-of-spec stimulus to confirm the checkers reject it. Reset-during-transfer is legal and must work; PADDR changing mid-transfer is illegal and must be flagged. Then enumerate the canonical corners so the interviewer hears a list, not a vibe: reset-during-transfer (PRESETn mid-ACCESS — no partial write commit, clean idle on release), back-to-back (a new SETUP on the cycle after completion — PSEL stays high, PENABLE re-sequences; the most mis-handled corner), max-wait / PREADY stuck low (a watchdog must fire, not a silent hang), mixed-latency (zero-wait abutting max-wait), and read-after-write to the same address (ordering at the bus boundary). The depth flourishes are three: why random under-hits corners — boundary states are a vanishing slice of the input space, so they must be directed, constructed on purpose, not left to seeds; the check triad — each corner needs an assertion (rule obeyed), a scoreboard expectation (data correct), and an explicit named coverage bin (corner reached), because the general coverage model reports closure without ever carving these cells; and the watchdog — a hang corner fails by the absence of completion, so you convert it into a bounded-liveness assertion. Closing with "and this is the capstone of the verification flow — the catalogue defines legal, assertions check it continuously, the monitor and scoreboard reconstruct and check data, coverage proves exercise, the agent packages it, negative testing breaks the rules to test the checkers, and corner cases push legal traffic to its extremes" demonstrates you see corner cases as the closing piece of a complete methodology, not an afterthought.
10. Practice
- Enumerate the corner list. Without looking back, list the five canonical APB corners, and for each state in one sentence what makes it legal-but-extreme and what breaks if the RTL mishandles it.
- Construct the reset corner. Write a directed sequence that drives a write into its ACCESS phase and asserts
PRESETnon that exact cycle. Then write the two assertions that prove no partial commit and a clean idle on release, plus thecoverthat proves the corner was reached. - Re-sequence the back-to-back. Draw the waveform for two writes with zero idle between them —
PCLK,PSEL,PENABLE,PADDR— and write the assertion that catches a slave which fails to deassertPENABLEfor the new SETUP. - Corner vs illegal. For each, say whether it is a corner case (DUT must handle) or an illegal case (checker must reject), and why: (a)
PRESETnasserted mid-ACCESS; (b)PADDRchanging during a held transfer; (c) a slave holdingPREADYlow for 500 cycles; (d)PENABLEhigh whilePSELis low. - Close the hang corner. Write the watchdog property for
PREADYstuck low, name theMAX_WAITbound and where it comes from, and explain why a hang corner needs a bounded-liveness assertion rather than waiting for the simulation timeout.
11. Q&A
12. Key takeaways
- A corner case is legal-but-extreme — fully spec-compliant traffic at a boundary the RTL rarely handles — and the DUT must survive it; this is sharply distinct from negative testing, where illegal stimulus must be rejected by the checkers.
- The canonical APB corners are a named list: reset-during-transfer (no partial commit, clean idle), back-to-back (zero idle,
PSELhigh,PENABLEre-sequences), max-wait /PREADYstuck low (watchdog, not a silent hang), mixed-latency (0-wait abutting max-wait), and read-after-write to the same address (ordering at the bus boundary). - Constrained-random under-hits corners by construction — boundary states are a vanishing slice of the input space — so each corner must be directed, constructed on purpose by a sequence, not left to seeds.
- Each corner is verified by a triad: an assertion (the boundary rule was obeyed), a scoreboard expectation (the data outcome was right), and an explicit named coverage bin (the corner was reached) — because the general coverage model reports closure without ever carving these cells, and crosses are what force the transition corners.
- A hang corner fails by the absence of an event, so the
PREADY-stuck case needs a bounded-liveness watchdog (in ACCESS |-> ##[1:MAX_WAIT] pready) that converts a silent bus hang into a loud, located assertion failure. - Corner cases are the capstone of the M15 verification methodology: the rules catalogue defines what is legal, assertions check it continuously, the monitor reconstructs transactions, the scoreboard checks data, functional coverage proves the space was exercised, the UVM agent packages it for reuse, negative testing breaks the rules to confirm the checkers fire, and corner cases push legal traffic to its extremes — together, the complete APB verification flow.