Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 109 additions & 13 deletions AI/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -786,18 +786,112 @@ Integrate DMG OAM DMA as a real transfer mechanism inside the system architectur
- the infrastructure can already represent future block or windowed transfers without requiring a later scheduler redesign, even though GDMA and HDMA remain out of scope here
- the system can trace DMA over time

Status note (`2026-03-19`): the current repo closes `Phase 3.5` and therefore
closes `Phase 3` as a whole. The DMA subsystem now exposes one common transfer
contract with explicit lifecycle/status queries independent of `FF46` readback,
published bus-impact state, and future-family hooks such as `block_size`,
`transfer_family`, and `advance_condition`. OAM DMA runs on the shared T-cycle
timeline as a real `160`-byte bus-routed transfer, and traces carry
start/progress/completion plus the published DMA bus-impact metadata from the
same cycle.

Timing refinement note (`2026-03-19`): source-analysis cross-check against
SameBoy commit `208ba4afabffab9edde416f2dbb8ae459e34adb8` (`Core/memory.c`,
`GB_IO_DMA` setup, `GB_dma_run`, and `is_addr_in_dma_use`) is now reflected in
the repo's DMG OAM-DMA model. The transfer keeps the `640`-T-cycle DMG burst
duration, but exposes an explicit `2`-T-cycle start-up seam before the first
byte commit. The first byte becomes visible at elapsed T-cycle `2`, later bytes
continue every `4` T-cycles, the last byte lands at elapsed T-cycle `638`, and
the final `Completed` transition remains visible after the remaining
`2`-T-cycle tail.

Oracle-validation note (`2026-03-19`): the same SameBoy cross-check also shows
that CPU-side non-HRAM conflict handling is not published during the internal
warm-up markers. The repo therefore now keeps the DMA transfer `in flight`
through the start-up seam while leaving the published CPU bus state
`Unrestricted` until that seam ends, and only then switches to the DMG
`HRAM-only` restriction. This keeps bus-impact onset explicit in the DMA timing
contract instead of inferring it from "transfer armed" alone.

#### Recommended sequencing inside Phase 3

1. Extract OAM DMA into the common controller.
Acceptance criteria: OAM DMA no longer lives on an ad hoc path, visible DMG behavior does not regress, CPU still remains HRAM-only during active OAM DMA, and the PPU still sees the same OAM-conflict state.
2. Separate transfer mechanics from arbitration policy.
Acceptance criteria: the bus consults one common DMA constraint API, the DMA subsystem does not reimplement bus decode, and the PPU can react to common OAM or VRAM impact state rather than to transfer-specific register knowledge.
3. Add common lifecycle and status visibility.
Acceptance criteria: OAM DMA uses `Idle -> Starting -> Active -> Completed`, the common API can already represent future cancellation, and code can query active-versus-finished state without depending on one specific origin register.
4. Prepare block or windowed progression hooks.
Acceptance criteria: fields such as `block_size` and `advance_condition` exist in the controller contract, including room for future `0x10`-byte HDMA-style blocks, they are not yet wired to real HDMA registers, and Phase 3 does not need another scheduler redesign when CGB transfer work starts.
5. Lock the infrastructure with focused tests.
Acceptance criteria: tests cover OAM DMA on the new controller, published bus constraints, lifecycle visibility, completion, and at least one simulated `0x10`-byte block-style transfer shape that is not yet mapped to real CGB MMIO.
Phase `3` should be executed as narrow subphases. No subphase counts as closed
unless its local acceptance criteria land together with focused automated
coverage and move the phase-level DMA/bus/scheduler contract forward without
reintroducing CPU-local blocking logic, instant-copy shortcuts, or DMA-owned
bus decode.

1. `Phase 3.1` - Common DMA transfer contract and `FF46`-owned OAM descriptor.
Acceptance criteria: the DMA subsystem replaces the ad hoc
`OamStartRequested`-style placeholder with one typed active-transfer shape,
`FF46` writes still latch the visible source page immediately, OAM DMA start
normalization derives the effective `XX00-XX9F` source range plus fixed
`FE00-FE9F` destination inside DMA-owned code, and the transfer record
already carries explicit DMG properties such as total length, timing policy,
CPU-impact policy, and memory-region impact.
Validation gate: focused unit tests cover `FF46` readback, source-page
normalization, fixed OAM destination, `160`-byte length, DMG timing-policy
metadata, CPU-impact metadata, and direct-boot startup state without yet
requiring whole-machine copy progression.
2. `Phase 3.2` - Scheduler-driven DMA timeline and current-cycle state
publication.
Acceptance criteria: DMA gains an autonomous-peripheral `tick` path on the
shared scheduler timeline, transfer progression becomes explicit per T-cycle
rather than implicit in one later bulk copy, `Starting -> Active ->
Completed` becomes observable on that timeline, current-cycle DMA state is
published before bus arbitration for the same T-cycle, and DMG OAM DMA
duration is modeled as `640` dots with byte-phase visibility.
Validation gate: focused unit and integration tests cover state progression
across `Idle`, `Starting`, `Active`, and `Completed`, `1` byte every `4`
dots progression metadata, `640`-dot total duration, deterministic stepping,
and trace visibility for start and completion points.
3. `Phase 3.3` - Central arbitration closure and DMG CPU HRAM-only behavior.
Acceptance criteria: the bus consumes one common DMA constraint view instead
of peeking at `FF46` or transfer internals, CPU-versus-DMA precedence stays
centralized in arbitration rather than in CPU-local special cases, live DMG
OAM DMA leaves the CPU with ordinary HRAM access only while active, and the
PPU can consume one common OAM-impact signal rather than transfer-specific
register knowledge.
Validation gate: focused arbitration tests cover CPU HRAM-only access during
active DMA, non-HRAM CPU blocked-read and ignored-write behavior, unrestricted
DMA requester access through the same arbitration path, DMA precedence over
ordinary PPU region-policy checks, and same-cycle coherence between published
DMA state and the bus decision the CPU observes.
4. `Phase 3.4` - Real OAM data movement through the shared bus model.
Acceptance criteria: DMA source reads and OAM destination writes happen
through the same central bus/arbitration model used by the rest of the
machine, OAM DMA copies the full `160` bytes from the latched source page to
OAM over time instead of by side effect, transfer-progress state and copied
bytes remain separately observable on the timeline, and completion clears the
in-flight transfer state without bypassing lifecycle visibility.
Validation gate: integration tests cover source-page selection, correct
`160`-byte copy contents, partial-progress snapshots before completion,
OAM contents after completion, and completion ordering relative to the last
transfer T-cycle.
5. `Phase 3.5` - Future transfer-family hooks, observability, and phase
closure.
Acceptance criteria: the common DMA API exposes lifecycle and status queries
without depending on one origin register, the transfer contract already
carries fields such as `block_size` and `advance_condition` for future
block/windowed DMA families, traces expose DMA start/progress/completion plus
published bus-impact state, and the phase closes with explicit TODOs only if
a concrete remaining gap still blocks full Phase `3` done criteria.
Validation gate: focused tests cover lifecycle/status visibility, current
bus-impact publication, and at least one simulated `0x10`-byte block-style
transfer shape that is not yet wired to real CGB MMIO; before closing the
phase, the resulting DMA ordering is cross-checked against SameBoy at the
source-analysis level when a timing-sensitive question remains.

#### Subphase exit rule

Every Phase `3` subphase should end with:

- targeted unit and integration coverage for the newly closed DMA contract
- updated traces or snapshots when observable DMA ordering changes
- `cargo test -q` passing locally at minimum, and `make check` when the
subphase changes repo tooling or shared workflow-critical infrastructure
- a roadmap TODO recorded immediately if the subphase ships with a concrete
uncovered gap

#### Risks if delayed too much

Expand Down Expand Up @@ -1641,9 +1735,11 @@ Suggested entry style:

### Phase 2 — CPU and real temporal control

- [CPU][BOOT] Full DMG boot ROM execution beyond the Phase `2.4` synthetic handoff baseline still needs MMIO-facing `LDH` / `(C)` transfers, `[hli]` / `[hld]` address-update transfers, and the remaining subtract plus non-CB accumulator-rotate families used by the production DMG firmware.
- [CPU] Phase `2.6` still needs explicit verification and, if necessary, refinement for the `EI ; HALT` pending-IRQ edge case before the HALT-control path can be considered fully hardened.
- [TIMER] Phase `2.7` still needs exact same-cycle `TIMA` / `TMA` write semantics on the reload T-cycle itself; the current baseline closes overflow delay, pre-reload writes, and CPU-visible IRQ ordering, but not yet the finest reload-cycle write arbitration cases.
- [CPU][BOOT] Full DMG boot ROM execution beyond the Phase `2.4` synthetic handoff baseline still needs MMIO-facing `LDH` / `(C)` transfers, `[hli]` / `[hld]` address-update transfers, and the remaining subtract plus non-CB accumulator-rotate families used by the production DMG firmware. Phase dependency: the full boot-facing group does not block general Phase `4` PPU bring-up, but the shared `[hli]` / `[hld]` subset is also consumed by the Phase `4` OAM-corruption event model.
- [CPU][OPCODES] General CPU opcode coverage beyond the narrow Phase `2.4` synthetic boot target is still intentionally partial. Before commercial-ROM execution can be treated as a supported goal, the remaining MMIO-visible transfers, implicit-address transfer forms, wider ALU families (`SUB` / `SBC`, `AND`, `XOR`, `OR`, and the remaining `CP` variants), and the broader CB-prefixed rotate / shift / bit-manipulation matrix still need closure through the same T-cycle execution model. Phase dependency: this does not reopen Phase `2` as a prerequisite for entering Phase `4`, but the micro-event-relevant subset already feeds Phase `4` OAM-corruption closure, especially implicit address-update forms plus the common event model for stack/control-flow, interrupt service, and observable address-bearing `inc/dec` paths.
- [CPU][DIAGNOSTICS] Unsupported decoded opcodes currently fall into a non-retiring execute loop instead of surfacing a first-class diagnostic. Replace that bring-up placeholder with an explicit unsupported-opcode reporting or trap policy that preserves scheduler visibility and avoids silent hangs during ROM and test investigation. Phase dependency: this is not a hardware-model blocker for Phase `4`, but it is strongly recommended before deeper Phase `4` ROM or trace-based validation so unsupported opcodes do not surface as silent hangs.
- [CPU] Phase `2.6` still needs explicit verification and, if necessary, refinement for the `EI ; HALT` pending-IRQ edge case before the HALT-control path can be considered fully hardened. Phase dependency: no current downstream phase is blocked on this edge case, but Phase `9` hardening should not claim the HALT path as fully closed while it remains unverified.
- [TIMER] Phase `2.7` still needs exact same-cycle `TIMA` / `TMA` write semantics on the reload T-cycle itself; the current baseline closes overflow delay, pre-reload writes, and CPU-visible IRQ ordering, but not yet the finest reload-cycle write arbitration cases. Phase dependency: no current downstream phase is blocked on this timer corner case, but later Phase `9` timer/oracle closure should not claim exact reload-cycle behavior until this arbitration is finished.

### Phase 3 — Base DMA

Expand Down
2 changes: 1 addition & 1 deletion AI/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ Include contract-level tests for in-memory and disk save backends, format versio
- Full-emulator save-state tests validate whole-machine snapshot ownership, hidden temporal-state restore, and save/load continuation determinism under the recorded execution mode and overrides.

Keep hardware-style cartridge persistence tests separate from full-emulator save-state tests; the former must not require CPU, PPU, APU, WRAM, or other console-state serialization.
For DMA behavior, include `FF46` source-page selection, full `160`-byte copy correctness, DMG total duration of `640` dots, transfer-progress timing, CPU blocking outside HRAM, HRAM accessibility during DMA, and OAM/LCD interaction whenever suitable tests exist.
For DMA behavior, include `FF46` source-page selection, full `160`-byte copy correctness, DMG total duration of `640` dots, the `2`-T-cycle OAM-DMA start-up seam before the first byte commit, the separate onset of published CPU bus restriction after that seam, transfer-progress timing, CPU blocking outside HRAM, HRAM accessibility during DMA, and OAM/LCD interaction whenever suitable tests exist.
For APU behavior, include tests that `NR52` power-off clears ordinary audio registers, preserves wave RAM accessibility, and does not reset the `DIV-APU` source relationship whenever suitable tests exist.
Include tests that `DIV-APU` advances from the falling edge of `DIV` bit `4`, including `DIV`-write-induced extra ticks when the edge is produced.
Include tests that the frame sequencer clocks length, envelope, and CH1 sweep without becoming the waveform timer for the channels themselves.
Expand Down
9 changes: 7 additions & 2 deletions AI/hardware/DMA.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,12 @@ Do not flatten DMA into a generic `memcpy_async(src, dst, len)` helper. OAM DMA,
- OAM DMA should be modeled as a `160`-byte transfer whose observable effects unfold over time rather than as a single commit.
- For the current DMG-family target, OAM DMA lasts `160` M-cycles = `640` dots at normal speed.
- Treat those `640` dots as the current hard requirement for DMG-family work; future CGB speed differences should extend the model later rather than weaken the DMG baseline.
- On the shared T-cycle timeline, DMG OAM DMA should conceptually progress at `1` byte every `4` dots.
- On the shared T-cycle timeline, DMG OAM DMA should expose the documented internal start-up seam before the first byte commit rather than pretending the transfer begins directly on the first regular `4`-dot cadence boundary.
- For the current DMG-family target, model OAM DMA with a `2`-T-cycle delay before the first byte commit, then `1` committed byte every `4` dots thereafter while still keeping the full burst at `640` dots.
- The resulting DMG OAM timeline should therefore make the first byte observable on elapsed T-cycle `2`, the last byte on elapsed T-cycle `638`, and leave the remaining `2` T-cycles visible before the transfer reaches `Completed`.
- Interactions between DMA source access, CPU-visible blocking, and OAM visibility should remain explicit and testable.
- CPU execution should continue during OAM DMA, but on DMG the CPU should only retain normal HRAM access while the transfer is active.
- CPU execution should continue during OAM DMA, but on DMG the CPU should only retain normal HRAM access once the transfer has exited its start-up seam and published its live bus-impact state.
- Keep the start-up seam and the CPU-visible bus-impact onset as separate timing edges in the common DMA model, even when the current DMG OAM transfer happens to align both at elapsed T-cycle `2`.
- DMA destination writes into OAM must still flow through the same central access-arbitration model used elsewhere; do not create a magical OAM backdoor.
- On the scheduler timeline, DMA progress should be advanced before current-cycle bus arbitration and CPU access decisions so the bus sees the live DMA state for that same T-cycle.
- DMA owns transfer progress and source/destination stepping; the bus owns the resulting blocked-access policy observed by CPU and other requesters.
Expand Down Expand Up @@ -193,6 +196,7 @@ Priority order:
- lifecycle-visibility tests covering `Idle`, active start, completion, and future-compatible cancellation hooks
- tests that bus-constraint publication is observable separately from actual byte-copy work on a T-cycle
- tests for common DMA-controller state such as `kind`, `remaining_bytes`, and current CPU-impact policy
- warm-up-seam tests covering the `2`-T-cycle delay before the first DMG OAM byte commit and the remaining tail before `Completed`
- unit tests for a simulated `0x10`-byte block or windowed transfer shape that is not yet wired to CGB MMIO

## Implementation notes for this repo
Expand All @@ -201,6 +205,7 @@ Priority order:
- Keep DMG OAM DMA and future CGB HDMA conceptually separated.
- Prefer designs where DMA consumes bus activity over time so CPU-visible restrictions arise naturally from arbitration rather than a one-shot special case.
- Keep bus arbitration centralized: DMA should request transfer work, while the bus should expose the resulting blocked-access semantics.
- Keep the first-byte start-up seam explicit in the DMA-owned progress model rather than burying it in bus code or by inflating the overall burst duration.
- A scheduler shape where `cpu.tick()`, `dma.tick()`, `ppu.tick()`, `timer.tick()`, and `apu.tick()` all advance on the same T-cycle timeline is the intended baseline, even if orchestration details differ internally.
- Keep `FF46` as the MMIO trigger that configures DMA state; do not bury the whole transfer inside a bus write handler.
- Even if the first implementation only supports `DmaKind::Oam`, structure the subsystem so later `Gdma` and `Hdma` kinds fit without redesigning the contract.
Expand Down
Loading