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 Source Bus

The OAM bus is one side of DMA; the other is the source bus DMA reads from — the external bus (cart ROM/RAM, WRAM) or the VRAM bus. The question here is what the CPU observes when its own read or write targets the same physical bus DMA is driving.

At a glance

  • The address pads are MUXed to DMA; the data pads and CPU strobes are not gated at all — every conflict behaviour falls out of that one asymmetry.
  • CPU read on the DMA bus: the CPU latches source[dma_a] — the byte DMA is fetching that M-cycle.
  • CPU write on the DMA bus: OAM gets the CPU's byte, and the cell at DMA's source address takes a spurious write of it.
  • HRAM is on-chip, on neither bus: CPU access during DMA is uncontended.
  • Sources $FE/$FF fold into WRAM: work_ram[(src − $E000) & $1FFF] — the echo region extended one page.

Source-side bus arbitration

One asymmetry drives every conflict on this page: during DMA the address pads are MUXed to DMA, but the data pads and the CPU's read/write strobes are not gated at all.

GateRoleTypeLogic
LOGODMA bus-read windownot_x1NOT(RORU) — per-T-cycle DMA address window
MORYDMA-vs-CPU address MUX select sourcenand2NAND2(dma_run, LOGO)
LUMAdma_addr_ext drivernot_x1dma_addr_ext = AND2(dma_run, LOGO)
per-bit address MUXesaddress pad sourcemuxsel = dma_addr_ext: DMA address vs CPU address
CEDEinverted dma_addr_extnot_x2gates CPU-side address-pad drivers off during DMA

So whatever the CPU's instruction intended, the address reaching the bus during DMA is DMA's:

CPU targetdma_addr_ext during the CPU strobeAddress pads carry
external bus, DMA source on external bus1DMA's address — the CPU's intended address never reaches the pads
VRAM bus, DMA source on VRAM1DMA's address
HRAM ($FF80–$FFFE)irrelevantHRAM is on-chip, on neither bus; CPU access proceeds normally

Pitfall: there is no address collision to detect

DMA's address replaces the CPU's at the pads, so the two never meet and there is no collision to detect.

CPU read conflict

The CPU's read strobe asserts on its normal cadence; the address pads carry dma_a; the memory drives source[dma_a] onto the data pads; the CPU latches it. The CPU's register receives the same byte DMA is fetching this M-cycle — equivalently OAM[dma_dest] after this M-cycle's commit.

Anchored end to end (dmg-sim measurement, gambatte oamdma_src0000_busyread0000_1 ROM): at the conflict M-cycle the pads show DMA's address $009E, and the CPU's sample edge latches 0x04 = source[$009E]. The DMA OAM commit of the same byte fires co-temporally on a distinct strobe chain. The result is A = 0x04, matching the hardware-verified out4.

CPU write conflict

The CPU's write strobe and data drivers assert on their normal cadence, overriding DMA's read-direction pad state — so when DMA's write arm (POWU → WYJA) latches the data pad into OAM, the pad carries the CPU's data. Three things follow:

  • OAM[dma_dest] receives the CPU's byte.
  • the off-chip cell at DMA's source address takes a spurious write of the same byte (the strobe asserts with DMA's address on the pads);
  • the CPU's intended target never sees the write.

Anchored: the pad sequence shows the CPU's 0x00 winning the data bus mid-settle, the OAM commit strobe latching it, and OAM[$9E] = 0x00 after the transfer — matching the hardware-verified out0 (dmg-sim measurement, gambatte oamdma_src8000_busywrite8000 ROM).

Both conflicts resolve deterministically — CPU and DMA share the master clock, so the CPU's data-phase and DMA's write-arm sub-window are co-aligned the same way every M-cycle. There is no sub-dot race here.

HRAM exemption. The HRAM chip-select chain derives from the CPU bus phase and the $FFxx decode; dma_run is not an input. CPU access to HRAM during DMA is uncontended (120 successful HRAM reads inside one DMA window in the measurement above) — the basis of every HRAM-stub DMA routine.

Stack accesses are ordinary bus cycles at SP; the rules above apply unchanged. Stack in HRAM is unaffected; stack in WRAM during a same-bus DMA redirects identically.

DMA sources $FE and $FF

For source pages $FE/$FF the address MUX behaves as for any other page: dma_a goes onto the cart address pads. The difference is the cart chip-select MUX (TYHO, sel = dma_addr_ext): during DMA it takes DMA's a15 directly, bypassing the CPU-side decode (TOZA) that would normally de-assert /CS for $FE–$FF. So the cart bus is driven with $FE00–$FE9F / $FF00–$FF9F addresses — even though on the CPU side those map to on-die OAM/IO/HRAM.

The package bus is now driven with an OAM/IO-range address; one device on it answers:

CandidateResponds?Reason
On-die OAM SRAMNoOAM uses its own internal address bus, not the external pads; its output enable stays 0 throughout (measured)
On-die HRAMNoThe HRAM enable chain rides the CPU-internal address bus, not the external pads
On-package WRAM dieYesSelects on cs_n_pad + a14=1 alone (see below)
Cart devicesMootWRAM has already driven the bus; MBC RAM /CS requires a14=0 anyway

The WRAM die in the DMG-CPU-B package selects on cs_n_pad and a14=1 alone — no a13 qualifier and no $FE/$FF exclusion, because those exclusions live in the CPU die's internal decode, which TYHO has bypassed. It drives the data pads with work_ram[a[12:0]], so the address wraps: $FExx → $1Exx, $FFxx → $1Fxx.

The behaviour is the documented echo region extended one page: DMA from $FE/$FF fetches work_ram[(src − $E000) & $1FFF] — hardware-verified as work_ram[$1E00+i] / work_ram[$1F00+i] per byte index.

A pre-filled-WRAM ROM family confirms 8-of-8 byte matches at the fold offsets (dmg-sim measurement, gambatte oamdma_src{FE,FF}00_busypop* ROMs), and both bank commit strobes fire exactly 80 times per transfer — every byte index commits, with no per-index suppression. $FE and $FF sources differ only in offset — work_ram[$1Exx] vs work_ram[$1Fxx] — through the identical mechanism; there is no per-page behavioural difference between them.

For implementors

Treat $FE/$FF sources as the echo region extended one page — the single mechanism covers the read-conflict and pop-conflict ROM families alike.