Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.

At a glance

  • 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

GateRoleTypeClock / TriggerNotes
TOXE / TULY / TESEFetch counter bits 0–2dffrSABE, then ~Q rippleReset by TAKA↑
SABEGated fetch clocknand2LAPE, TAMEAdvances on ALET rising — opposite edge from the BG fetcher
TAMESelf-stopnand2TESE, TOXEFreezes SABE at count 5
TEKYX-match triggerand4FEPO, TUKU, LYRY, SOWOTUKU = NOT(RYDY) blocks during window activation; LYRY requires BG fetch complete; SOWO = NOT(TAKA) blocks re-trigger
SOBUFetch requestdffrTAVA (clk5 = ALET rising)Captures TEKY — the one DFF in the trigger chain. No reset domain (r_n tied high)
SUDATrigger one-shot partnerdffrLAPE (clk4 = ALET falling)Captures SOBU ~1 dot later; suda_n closes RYCE. No reset domain
RYCEOne-shot fireand2SOBU, suda_nHigh from SOBU's capture until SUDA mirrors it
SECATAKA set netnor3RYCE, ROSY, ATEJThree set arms: Mode-3 trigger, LCD-off, and the line-end pulse
TAKAFetch-running latchnand_latchSet: SECA; Reset: VEKU = NOR2(WUTY, TAVE)Freezes SACU via FEPO's persistence; WUTY = normal end-of-fetch, TAVE = startup carry-over clear
WUTYFetch donenotVUSAClears TAKA; clocks the per-slot fetched-flag DFFs
VUSAFetch-done aggregateor2TYNO, tyfo_nTwo parallel decode branches
TYNODone decode Anand3SEBA, TOXE, VONU
TYFODone decode BdffrLAPECaptures TOXE; no reset domain
TOBU / VONU / SEBATULY capture chaindffrclk5 / clk5 / clk4Mode-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:

StageCellsMode 2 (scan)Mode 3 (fetch)
1, bank BYDYV…ZECAY byte (OAM byte 0)tile index (byte 2)
1, bank AXYKY…ECEDX byte (byte 1)attribute (byte 3)
2, Y-sideXUSO, XEGU, YJEX, XYJU, YBOG, WYSO, XOTE, YZABY-compare operand Bsprite tile VRAM row bits
2, X-sideYLOR, ZYTY, ZYVE, ZEZY, GOMO, BAXO, YZOS, DEPOX-store fan-outattribute 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.

Pitfall: the capture is not gated off under DMA

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.