# Ch267 closeout — `0xA000A8C8` is NOT the polled gate. The chain just clears it; nothing reads it. **Status:** Closed. Phase 1 passive observation **rules out** `0xA000A8C8` as a polled gate. **Verdict:** `gate_only_cleared_never_polled`. **Headline counts** across the entire BIOS-long run (93 accesses to phys `0x000A8C8`, all kseg1 alias): | Role | Count | |--------------------|-------| | clearer(dispatcher) | 69 (3 SWs × 23 dispatcher invocations) | | clearer(other) | 24 (1 init-time + 23 helper-frame writes) | | writer(non-zero) | **0** | | poller(read) | **0** | **Action per Codex's gate:** Do **NOT** proceed to Phase 2 (`0xA000A8C8` poke). The address is a *write target*, not a polled value. The treadmill must be gating on something else. ## Codex Ch267 Phase 1 acceptance — line-by-line | Codex requirement | Status | Where | |-------------------------------------------------------------------------------------|--------|-------| | Key on phys 0x0000A8C8, accept all three kseg/kuseg aliases | ✅ | `CH267_PHYS_TARGET = 29'h000_A8C8` (matches low 29 bits of EA) | | Capture every EE map access to that word | ✅ | `ch267_*` arrays, cap=1024 | | Classify each as clearer / writer / poller | ✅ | `ch267_role_name` task | | Distinguish dispatcher clearer (PC in 0xBFC4F320..F520) vs other | ✅ | `ch267_in_disp` field | | Log PC, access type, value, pass index, pre/post-clear | ✅ | full stream output | | Suppress dispatcher clears beyond first-per-pass | ✅ | `dc_per_pass[]` filter (kept the first, counted+suppressed the rest) | | 5-way verdict labels | ✅ | gate_alias_mismatch / gate_nonzero_writer_found / gate_polled_zero_no_writer / gate_only_cleared_never_polled / gate_no_traffic_at_all | | Regression unaffected | ✅ | 157 / 157 with target off-by-default | ## What the stream actually showed ### One previously-unknown init-time clearer The very first access to `0xA000A8C8` happens at **cyc=54566** (deep BIOS init, pre-treadmill) from **PC=0xBFC4B83C**: ``` [0] cyc=54566 pass=0 CLEARER(other) pc=0xbfc4b83c ea=0xa000a8c8(kseg1) data=0x00000000 post_clear=0 ``` This is the *first* zeroing of `0xA000A8C8` — before the Ch266 dispatcher ever runs. The PC is far from the dispatcher chain; it's somewhere in early kernel init. Not a smoking gun because it writes zero like the dispatcher does, but worth naming so future autopsies don't think it's mysterious. ### The "other" clearer pattern in the helper 24 captures at **PC=0xBFC4D388** (inside the Ch265 helper, the instruction right after the helper's JAL out to the dispatcher) also write zero to `0xA000A8C8`. This is a **trace-timing artefact**, not a separate writer. The Ch266 dispatcher's JAL `0xBFC4F334 → jr $ra` has a delay slot at `0xBFC4F338`; if the delay slot is `sw $0, OFF($base)`, that write retires while `core_pc` is *one cycle ahead*, already showing `0xBFC4D388` (the helper's post-JAL instruction). So Ch266 attributed three writes to PCs F32C/F330/F334 inside the dispatcher, but the third write was actually F338 (the JR delay slot), reported with PC=0xBFC4D388 because `core_pc` sampling is one cycle late on memory events. Confirmation: every "other" clearer at 0xBFC4D388 fires *immediately after* a `CLEARER(disp)` from `0xBFC4F32C` (see cyc=67019→67034, 67131, 68243 — 15-cycle gap between the dispatcher write and the "helper" write, matching the JR + delay-slot + pipeline-bubble timing). Three writes per dispatcher call, distributed across what looks like two PCs because of the same one-cycle skew the Ch266 closeout noted. (Same skew explanation applies to PC=0xBFC4F334 in Ch266's output — it was actually the JR delay slot's write at F338, not a write from the JR itself.) **Net:** there's still one writer (the dispatcher), three SWs per call. The autopsy just gave us a clearer picture of which PCs the writes are really attributed to. ### Zero pollers, zero non-zero writers — the gate is elsewhere The crucial counts: ``` writer(non-zero) = 0 poller(read) = 0 ``` **No read of `0xA000A8C8` happens anywhere in the model during the BIOS-long run.** Combined with the disassembly of the Ch217 outer-caller post-chain: ``` 0xbfc52378: lui $v0, 0x1f80 ; <- clobbers $v0=0xA000A8C8 0xbfc5237c: ori $v0, $v0, 0x1070 ; $v0 now = 0x1F801070 0xbfc52380: sw $0, 4($v0) ; write 0 to I_MASK 0xbfc52384: jal 0xbfc52388: sw $0, 0($v0) ; write 0 to I_STAT (W1C ack) ``` …the outer caller **discards** `$v0=0xA000A8C8` immediately after the chain returns and rebuilds it as `0x1F801070` (IOP INTC I_STAT). The `0xA000A8C8` pointer is never used as a polled value, never used as a data pointer, never used at all by the outer caller. The chain's job appears to be **pure side-effect** — clearing the kernel struct at `0xA000A8C8` and updating internal selector-keyed state via the helper (`$v1` return values were selector-dependent). The chain's `$v0` is computed but discarded. ## What this means for the search **The polled gate is not at `0xA000A8C8`.** Ch263–Ch266 narrowed the search to "the longjmp-return chain's effect," and Ch267 shows that effect is *not* a polled value at 0xA000A8C8 itself. Possible relocations for "where the gate actually lives": 1. **One of the INTC writes the outer caller does immediately after the chain.** `0xBFC52380: sw $0, 4($v0)` writes 0 to I_MASK; `0xBFC52388: sw $0, 0($v0)` does W1C on I_STAT. Both happen *every* Ch217 pass. Could the treadmill be gated on the I_STAT value AFTER the W1C? If a "ready bit" needed to remain set across the W1C, our INTC model might be eating it. 2. **Elsewhere in the loop body the autopsies haven't covered.** The Ch217 caller dump only shows PCs 0xBFC52340..0xBFC5238C — the area *immediately* around the JAL. The treadmill itself is longer; the polled state might be read further along (post-W1C, post-RFE) before the exception loops back. 3. **A COP0 register, not memory.** The treadmill involves an RFE; COP0 Status/Cause/EPC reads aren't in EE_MAP and wouldn't show up in our existing autopsies. A re-poll of Status.IE or Cause.IP between passes could be the gate. ## Recommendation for Ch268 **Pivot away from `0xA000A8C8` entirely.** Three concrete follow-ups, in order of cheapest-first: **(A) Widen Ch267 to scan ALL read EAs in the treadmill window.** Instead of keying on one EA, capture every non-fetch READ across a wider PC window — say the Ch217 caller body `0xBFC52340..0xBFC52400`. Bucket reads by EA and diff pass 1 vs pass 8. Any EA that BIOS reads every pass and whose value is "the same" deserves the polled-gate label. Cheap to implement — copy the Ch266 capture, widen the PC, drop the write capture, add per-pass diff bookkeeping. **(B) Capture the immediate post-chain INTC writes.** Profile the W1C cadence at I_STAT (0x1F801070) and I_MASK (0x1F801074) across passes. If our INTC stub's behavior on those writes differs from what BIOS expects, the treadmill could be gating on I_STAT's residual after W1C. **(C) Observe COP0 reads.** Add a minimal COP0 access logger to ee_core_stub. Look for any read of Status/Cause/EPC that returns the same value every pass — that's a candidate for a "this would have changed on a real PS2" gate. (A) is the highest-EV next step — it directly searches for the gate without committing to a guess. (B) is the second-highest-EV because we have a smoking gun pointing at INTC (selector 0x05 → `$v1=0x1F801070`). (C) is the fallback if (A) and (B) both come up empty. **Do NOT proceed to Phase 2** (TB-poke of 0xA000A8C8). The Ch267 result rules out 0xA000A8C8 as the gate, so poking it would just confirm that — and possibly confuse the dispatcher's internal selector-state tracking. ## Files changed - `sim/tb/integration/tb_ee_core_bios_smoke.sv` — added `\`ifdef CH267_GATE_OBSERVER` block. Single capture (R+W for any EA matching phys 0x000A8C8 across aliases), with per-event PC/value/role/post-clear tags. Stream-suppression for dispatcher clears beyond first-per-pass. SUMMARY block with alias breakdown + role counts. 5-way verdict logic with alias-mismatch detection. Two call sites (`ch267_print_observer()`) in halt + timeout exits. - `sim/Makefile` — new `tb_ee_core_bios_long_gate_observer` target (only `-DCH267_GATE_OBSERVER`). ## iverilog 12 quirks hit None new. Wrote with the Ch264/265/266 patterns in mind (no `return` from task; no bit-select on parenthesized expr; `trace_pkg::` namespace). Clean first-try compile. ## Regression Full regression: 157 / 157 with the new target off by default (`CH267_GATE_OBSERVER` undefined for routine builds). Standing by for Codex's Ch268 call. Recommendation: (A) — wider PC-window read autopsy across the Ch217 caller body, to find what EA the treadmill actually polls. The Ch266 infrastructure is reusable; just widen the PC window and drop the write capture.