Skip to content

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 PREADY for 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 PREADY never 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 "PREADY stuck 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 — PADDR changing mid-transfer, PSEL dropping 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-transferPRESETn asserted 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, with PSEL staying high across the boundary while PENABLE drops to 0 for the new SETUP then rises again; (3) max-wait / PREADY stuck 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 PRESETn on 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 PENABLE correctly). 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.
A map of the APB state space showing a dense central region walked by directed and random tests, surrounded by five labelled corner cells at the rim — reset-during-transfer, back-to-back, max-wait PREADY-stuck, mixed-latency, and read-after-write-same-address — each annotated with what breaks if mishandled and its assertion, scoreboard, and coverage-bin check.
Figure 1 — the APB state space with its boundary corners enumerated and each tagged with what breaks and how it is checked. The wide centre band is the well-travelled region directed positive tests walk and where random stimulus lands densely; the five labelled cells around the rim are the corners random under-hits: reset-during-transfer (PRESETn mid-ACCESS — a write must not half-commit, the slave must return to clean idle), back-to-back (no idle between transfers — PSEL stays high while PENABLE re-sequences), max-wait / PREADY-stuck (the slave never completes — a watchdog must fire, not a silent hang), mixed-latency (zero-wait abutting max-wait), and read-after-write-same-address (ordering at the bus boundary). Each corner carries what breaks if mishandled and the check triad that confirms it — assertion (rule obeyed), scoreboard (data correct), coverage bin (corner reached). The figure teaches that corners are legal boundary states, sparse under random, that must be enumerated, deliberately stimulated, and individually checked.

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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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
 
endmodule

Two facts make this the right shape. First, the corner is constructed, not awaitedreset_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 scenarioWhat makes it a corner (legal-but-extreme)What breaks if mishandledHow to check it (assertion · scoreboard · coverage)
Reset-during-transferPRESETn asserted mid-ACCESS — legal at any time, but lands in the one cycle the slave is actively transferringA write half-commits (partial data latched); the slave stays in a stale mid-transfer FSM state; corrupt latched control survives resetAssert: 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 PENABLEPENABLE not deasserted for the new SETUP ⇒ slave misses the new transfer; or the slave merges the two transfers / re-uses stale PADDRAssert: 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 lowThe slave legally holds PREADY low — but for far longer than expected, or foreverThe 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 stateThe manager mis-pipelines: it assumes the next transfer is also fast, samples too early, or drops the held bus on the slow oneAssert: 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 addressA read of an address on the cycle-boundary right after a write to it — legal, but tests ordering/coherency at the bus boundaryThe read returns stale (pre-write) data — the write had not yet committed when the read sampled; a coherency hole at the bus edgeAssert: 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, PRESETn falls while PSEL, PENABLE are high and PWRITE is 1 — a write is in ACCESS. The slave's internal write-commit strobe pulses on the same edge PRESETn falls, latching PWDATA into the register. The register then holds that mid-flight value across reset; slave_busy was still high one cycle after PRESETn asserted.
  • Root cause: the slave's write-commit logic was gated only on psel && penable && pready and was not qualified by presetn. When reset asserted in ACCESS, the commit condition was still momentarily true, so the register captured the in-flight PWDATA instead 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:
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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:
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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 PRESETn can 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 no cover, it almost certainly shipped un-exercised, and the field bug is the first time the boundary was ever hit.
A two-panel waveform contrast of reset asserted mid-ACCESS: the green correct panel shows PRESETn falling during ACCESS forcing PSEL and PENABLE to idle, no PWDATA committed, and the slave FSM dropping to a clean IDLE; the red bug panel shows the in-flight PWDATA latched as a half-committed write and the FSM stuck in a stale ACCESS state so the peripheral boots with a value never written.
Figure 2 — reset asserted mid-ACCESS, handled correctly versus mishandled. Both panels show PCLK, PRESETn, PSEL, PENABLE, PWDATA and the slave FSM state as PRESETn falls during the ACCESS phase of a write. In the correct (green) panel the write commit is qualified by reset, so when PRESETn falls the slave commits nothing, drives PSEL and PENABLE to idle, and the FSM drops to a clean IDLE — the register keeps its reset default. In the bug (red) panel the commit logic is not qualified by reset, so the in-flight PWDATA is latched on the same edge PRESETn falls (a half-committed write), and the FSM is left stuck in a stale ACCESS state, so the peripheral boots holding a value firmware never wrote. The figure teaches that reset-during-transfer is a legal corner the RTL must abort cleanly — no partial commit, no latched state, a clean idle — and that the assertion gate is $fell(presetn) implies no commit and clean idle.

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 PRESETn on that cycle, or that holds PSEL high 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/many waits, region, response) do not carve out the corners — a [2:$] bin fills on ordinary slow transfers while the PREADY-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-stretch PREADY-low tail, and cross bins for the combinations — wait_prev × wait_curr cell {0}×{many} (mixed latency), op × same_addr cell write→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] pready converts "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

  1. 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.
  2. Construct the reset corner. Write a directed sequence that drives a write into its ACCESS phase and asserts PRESETn on that exact cycle. Then write the two assertions that prove no partial commit and a clean idle on release, plus the cover that proves the corner was reached.
  3. 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 deassert PENABLE for the new SETUP.
  4. 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) PRESETn asserted mid-ACCESS; (b) PADDR changing during a held transfer; (c) a slave holding PREADY low for 500 cycles; (d) PENABLE high while PSEL is low.
  5. Close the hang corner. Write the watchdog property for PREADY stuck low, name the MAX_WAIT bound 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, PSEL high, PENABLE re-sequences), max-wait / PREADY stuck 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.