The Fetch State Machine
A sprite fetch begins with a one-shot trigger chain and ends 6 dots later, when WUTY clears the TAKA latch. In between, a 3-bit counter runs 0–5 on the opposite clock edge from the BG fetcher. TAKA's lifetime extends well beyond the fetch itself: it is high for most of every scanline.
- Trigger: TEKY → SOBU (DFF) → RYCE → TAKA, a one-shot closed by SUDA.
- The counter runs 0–5 on ALET rising — the opposite edge from the BG fetcher.
- TEKY's AND4 requires FEPO (X match), TUKU (no window activation), LYRY (BG fetch complete), and SOWO (no fetch already running).
- WUTY ends the fetch at +6.010 dots from TEKY↑, clears TAKA, and gates the shifter load.
- The BG drain cascade is pinned throughout — no NYXU fires during a sprite fetch.
- TAKA is high for most of every scanline: ATEJ re-asserts it at each line end, and the startup TAVE clears the carry-over.
The gates
| Gate | Role | Type | Clock / Trigger | Notes |
|---|---|---|---|---|
| TOXE / TULY / TESE | Fetch counter bits 0–2 | dffr | SABE, then ~Q ripple | Reset by TAKA↑ |
| SABE | Gated fetch clock | nand2 | LAPE, TAME | Advances on ALET rising — opposite edge from the BG fetcher |
| TAME | Self-stop | nand2 | TESE, TOXE | Freezes SABE at count 5 |
| TEKY | X-match trigger | and4 | FEPO, TUKU, LYRY, SOWO | TUKU = NOT(RYDY) blocks during window activation; LYRY requires BG fetch complete; SOWO = NOT(TAKA) blocks re-trigger |
| SOBU | Fetch request | dffr | TAVA (clk5 = ALET rising) | Captures TEKY — the one DFF in the trigger chain. No reset domain (r_n tied high) |
| SUDA | Trigger one-shot partner | dffr | LAPE (clk4 = ALET falling) | Captures SOBU ~1 dot later; suda_n closes RYCE. No reset domain |
| RYCE | One-shot fire | and2 | SOBU, suda_n | High from SOBU's capture until SUDA mirrors it |
| SECA | TAKA set net | nor3 | RYCE, ROSY, ATEJ | Three set arms: Mode-3 trigger, LCD-off, and the line-end pulse |
| TAKA | Fetch-running latch | nand_latch | Set: SECA; Reset: VEKU = NOR2(WUTY, TAVE) | Freezes SACU via FEPO's persistence; WUTY = normal end-of-fetch, TAVE = startup carry-over clear |
| WUTY | Fetch done | not | VUSA | Clears TAKA; clocks the per-slot fetched-flag DFFs |
| VUSA | Fetch-done aggregate | or2 | TYNO, tyfo_n | Two parallel decode branches |
| TYNO | Done decode A | nand3 | SEBA, TOXE, VONU | |
| TYFO | Done decode B | dffr | LAPE | Captures TOXE; no reset domain |
| TOBU / VONU / SEBA | TULY capture chain | dffr | clk5 / clk5 / clk4 | Mode-3-only (r_n = mode3); feed TYNO |
The trigger chain
TEKY → SOBU (DFF) → RYCE → TAKA, with SUDA closing the one-shot
Measured with zero variance (dmg-sim measurement, purpose-built
sprite_steady ROM —
one sprite at SPRITEX 80, 64 firing scanlines):
| Stage | Δ from TEKY↑ (dots) |
|---|---|
| TEKY rises (ALET falling edge) | 0.000 |
| SOBU captures on the next ALET rising | +0.493 |
| RYCE fires (a gate delay from SOBU) | +0.493 |
| TAKA fires | +0.496 |
| Counter resets to 0 | ~+0.50 |
| SUDA captures, closing RYCE | +0.997 |
The counter then counts 0→5 over five dots, one tick per ALET rising via SABE. At count 5 WUTY fires and clears TAKA at +6.010 dots from TEKY↑.
The net penalty is 6 dots: six suppressed SACU edges, with the TEKY↑ → TAKA↓ freeze measuring 6.010 dots gate-resolved. SACU makes zero transitions inside that freeze. SACU resumes 1.024 dots after TAKA falls, on the next ALET falling edge. The penalty model develops this 6-dot penalty together with its alignment overhead.
A brief TEKY glitch pulse (~0.016 dots ≈ 3.9 ns) appears as TAKA clears and FEPO deasserts, a combinational race. The pulse is too narrow for SOBU's capture, and it has no pipeline effect.
The BG cascade freezes too
Throughout each TAKA-high window, the BG drain-detect cascade is pinned.
SEGU stuck at 1 gives RYFA no clock edges, so RYFA stays frozen at 1. That
pre-freeze capture of PANY=1 is guaranteed, because TEKY required LYRY=1.
RENE keeps re-capturing RYFA=1, SEKO stays 0, and TEVO and NYXU never fire.
The BG counter holds at 5 by the ordinary MOCE self-stop, and no
BG-shifter parallel load occurs during a sprite fetch. Verified across 30
TAKA-high windows (dmg-sim measurement, the gambatte 10spritesPrLine
stacked-sprite ROM): zero NYXU/RENE/RYFA/SEGU/SEKO transitions inside any of
them. The same scanlines show the normal 22-pulse NYXU cadence outside the
stacked-sprite cluster.
OAM byte capture during the fetch
The fetcher has no dedicated tile/attribute DFFs. It reuses the two-stage capture chain that Mode 2 uses for (Y, X), and the byte roles follow the address:
| Stage | Cells | Mode 2 (scan) | Mode 3 (fetch) |
|---|---|---|---|
| 1, bank B | YDYV…ZECA | Y byte (OAM byte 0) | tile index (byte 2) |
| 1, bank A | XYKY…ECED | X byte (byte 1) | attribute (byte 3) |
| 2, Y-side | XUSO, XEGU, YJEX, XYJU, YBOG, WYSO, XOTE, YZAB | Y-compare operand B | sprite tile VRAM row bits |
| 2, X-side | YLOR, ZYTY, ZYVE, ZEZY, GOMO, BAXO, YZOS, DEPO | X-store fan-out | attribute bits: GOMO = palette, BAXO = X-flip, YZOS = Y-flip, DEPO = priority (bits 0–3 unused on DMG) |
In Mode 3 the capture enable reduces to oam_data_latch = WEFY =
AND2(TUVO, tyfo_n), with TUVO high while the fetch counter ∈ {0, 1}. That
gives one pulse per fetch, near TAKA-rise. The pulse captures the byte-pair
at the fetcher-driven address (sprite index × 4 + 2, + 3). Stage 2 is
transparent through most of Mode 3, so the bytes flow straight through.
Under DMA, the fetcher's bus driver is tri-stated, but neither capture
enable is gated. Each fetch's pulse therefore captures the byte-pair at
DMA's current address instead: a garbage tile index to the VRAM path,
and attribute bits taken from a DMA source byte. Measured across one
DMA + Mode 3 overlap (dmg-sim measurement, Hacktix strikethrough ROM): 10
capture pulses, each at TAKA=1/counter=0. Each pulse overwrites Stage 1 with
the byte-pair at the then-current dma_a.
TAKA outside Mode 3
TAKA's set net SECA = NOR3(RYCE, ROSY, ATEJ) includes the line-end pulse, ATEJ. ATEJ fires at every scanline boundary, including VBlank lines, and re-asserts TAKA during H-Blank. TAKA is therefore high for most of every scanline. It is low only between the startup carry-over clear and the line's first real fetch, and between fetch completions. The startup carry-over clear runs TAVE↑ at AVAP+5.996 and TAKA↓ at +5.996. The carry-over is cleared each scanline by TAVE through VEKU's second reset arm.
Measured per-scanline waveform (zero variance, six scanlines): ATEJ↑ at AVAP+376.000 dots sets TAKA. That instant is the scanline boundary, 456 − 80 dots of Mode 2. The ATEJ pulse is 1.001 dots wide, one half-XUPY cycle. TAKA then holds through H-Blank until the next line's TAVE.
The carry-over clear has a side effect. SOWO = NOT(TAKA) rises at +5.999 and briefly satisfies TEKY's AND4. The resulting glitch pulse is too narrow for SOBU to capture.