Skip to content

Commit 52c3e63

Browse files
committed
docs: define global t-cycle scheduler contract
1 parent 7d0660d commit 52c3e63

13 files changed

Lines changed: 169 additions & 0 deletions

File tree

AI/ARCHITECTURE.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,32 @@ For an early-stage repo, a simplified equivalent is acceptable as long as these
6666
- For the PPU, that shared T-cycle timeline is also the dot timeline; dot-by-dot behavior is the intended baseline.
6767
- Long-running hardware operations triggered by MMIO writes, such as OAM DMA, should become explicit in-flight subsystem state on that shared timeline rather than immediate bulk side effects.
6868

69+
## Global scheduler policy
70+
71+
- The DMG core must have one global scheduler that advances the whole machine on one deterministic T-cycle timeline.
72+
- A `GlobalScheduler` plus `step_t_cycle()`-style entry point, or an equally explicit equivalent, is the preferred architectural shape.
73+
- Do not run PPU, timer, APU, serial, joypad, DMA, or interrupt production as unsynchronized threads or independently clocked loops inside the core.
74+
- The recommended per-T-cycle phase order is:
75+
1. external event ingress
76+
2. master clock / shared system-counter tick
77+
3. resolution of free-running counter-derived edges
78+
4. autonomous peripheral ticks
79+
5. bus arbitration for the current T-cycle
80+
6. CPU micro-operation
81+
7. MMIO side-effect commit
82+
8. interrupt aggregation into `IF`
83+
9. CPU wake / interrupt-accept evaluation
84+
- This phase order is an architectural contract for observable behavior, not a claim that Nintendo published one canonical internal scheduler.
85+
- Another internal decomposition is acceptable only if it preserves the same observable dependencies for PPU mode visibility, DMA blocking, timer overflow delay, serial completion timing, joypad visible-edge timing, MMIO visibility, and CPU interrupt acceptance.
86+
- Phase `3` is for events derived from the free-running shared clock. Device-local MMIO semantics such as `DIV` reset effects, `FF46` DMA start, `SC.7` transfer start, or `LCDC.7` LCD transitions still belong to the device that owns the register when the access commits in phase `7`.
87+
- The scheduler should keep one cycle-local context object or equivalent that can carry at least:
88+
- the global T-cycle index
89+
- current-cycle external events
90+
- already-derived counter-edge signals
91+
- current ownership or arbitration facts
92+
- queued side effects and interrupt requests
93+
- The scheduler coordinates ordering and synchronization points; it must not reimplement timer, PPU, DMA, serial, joypad, APU, cartridge, or CPU-local quirks internally.
94+
6995
## Console model policy
7096

7197
- The core must expose an explicit console model concept.
@@ -125,6 +151,10 @@ to immediately materialize as a separate directory.
125151
- stable per-T-cycle subsystem stepping order
126152
- explicit global synchronization points
127153
- orchestration between CPU, PPU, DMA, timer, APU, and peripherals
154+
- ingress of timestamped external events such as host input changes or external serial clocks
155+
- explicit separation between free-running device ticks, bus arbitration, MMIO commit, interrupt aggregation, and CPU wake / accept points
156+
- cycle-local context and tracing support so phase order remains visible in code and logs
157+
- subsystem-facing APIs that keep stage boundaries explicit, for example free-running tick, CPU step, MMIO commit, and interrupt-request aggregation steps, even if final names differ
128158

129159
### `bus/`
130160

@@ -134,6 +164,7 @@ to immediately materialize as a separate directory.
134164
- access arbitration
135165
- integration of cartridge, VRAM, WRAM, OAM, I/O, HRAM, IE, and boot ROM mapping
136166
- modeling of access restrictions and conflicts when hardware makes them visible
167+
- two-layer arbitration made of decode / nominal ownership followed by requester-aware access policy
137168
- routing of OAM and `FEA0-FEFF` access attempts and CPU-provided address-bearing micro-events into the DMG-family OAM corruption path when applicable
138169
- MMIO routing to the subsystem-owned register contract for each mapped address
139170
- one source of truth for MMIO ownership, model availability, access class, and read/write side-effect policy
@@ -154,6 +185,7 @@ to immediately materialize as a separate directory.
154185
- fixed-priority pending selection
155186
- MMIO exposure of `FF0F` and `FFFF`
156187
- separation between controller-owned request state and CPU-owned `IME` / acceptance flow
188+
- aggregation of source requests into `IF` without collapsing that step into CPU dispatch
157189

158190
### `boot/`
159191

@@ -200,6 +232,7 @@ to immediately materialize as a separate directory.
200232
- DIV / TIMA / TMA / TAC
201233
- edge-sensitive timer timing
202234
- timer interrupt request generation
235+
- ownership of the timer overflow pipeline, including the delayed `IF` request relative to logical overflow
203236

204237
### `joypad/`
205238

@@ -210,6 +243,7 @@ to immediately materialize as a separate directory.
210243
- joypad interrupt generation through the shared interrupt-controller path
211244
- input-driven wake signaling for CPU `STOP` integration
212245
- separation between frontend input collection and emulated joypad semantics
246+
- distinction between hardware-facing button changes, visible `JOYP` changes, joypad IRQ requests, and `STOP` wake signaling
213247

214248
### `serial/`
215249

@@ -219,6 +253,7 @@ to immediately materialize as a separate directory.
219253
- peer or link-endpoint boundary
220254
- serial interrupt generation through the shared interrupt-controller path
221255
- separation between emulated serial hardware and any host transport implementation
256+
- ownership of transfer-complete detection, `SC.7` clear timing, and completion-triggered serial IRQ requests
222257

223258
### `cartridge/`
224259

@@ -287,6 +322,7 @@ to immediately materialize as a separate directory.
287322

288323
## Ownership boundary notes
289324

325+
- The scheduler owns phase order, cycle context, and subsystem call order; it must not become a second implementation of timer, PPU, DMA, serial, joypad, or CPU rules.
290326
- The boot subsystem owns firmware assets, model-aware boot configuration, and boot-ROM enable/disable state.
291327
- The boot subsystem also owns the source-of-truth startup snapshot for direct-boot entry, while the target subsystems still own the live semantics of their registers once execution begins.
292328
- The DMA subsystem owns transfer state and transfer requests over time.
@@ -298,8 +334,10 @@ to immediately materialize as a separate directory.
298334
- Frontends, test harnesses, and tooling should provide serial peers, scripted bits, loopback, or external clock pulses through a serial-endpoint boundary rather than by writing received bytes directly into `SB`.
299335
- The serial subsystem owns the translation from MMIO-visible `SB` / `SC` plus peer-provided bits and clocks into live transfer progress, `SB` intermediate state, and serial interrupt requests.
300336
- The timer owns the shared divider/system-counter state and visible `DIV`, while the APU owns `DIV-APU`, frame-sequencer state, channel-active state, DAC state, mixer state, and HPF state derived from that shared timing source.
337+
- The bus owns central decode, requester arbitration, and blocked-access policy; CPU, DMA, and future transfer engines must not bypass that one policy path with caller-specific memory shortcuts.
301338
- The bus applies boot mapping, DMA contention, and blocked-access semantics using that subsystem state; CPU code should not embed those rules directly.
302339
- The bus owns address decode and MMIO dispatch, but the device that owns a register must own its read, write, and side-effect semantics.
340+
- The interrupt controller owns `IF` / `IE` state and fixed-priority pending selection, but it does not decide when the CPU actually accepts an interrupt or wakes from `HALT` / `STOP`.
303341
- The cartridge owns the meaning of persistent RAM and RTC content, while the save backend owns durable storage mechanics such as file format, paths, versioning, and atomic replacement.
304342
- Save-state machinery must not be smuggled into the cartridge persistence boundary; CPU, PPU, APU, WRAM, and other console-owned state belong to a different system.
305343
- MMIO metadata should be centralized enough that readable bits, writable bits, dynamic bits, reserved bits, and model-specific availability are not re-declared ad hoc in several modules.

AI/EXECUTION.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@
3333
- Prefer reproducible validation over intuition.
3434
- Preserve the project's T-cycle timing foundation; do not introduce M-cycle-first scheduling shortcuts as a local convenience.
3535

36+
## When touching the global scheduler or cross-subsystem ordering
37+
38+
- Preserve the fixed per-T-cycle phase contract defined by `AI/ARCHITECTURE.md` and `AI/TIMING-AND-ACCURACY.md`.
39+
- Update the matching hardware docs together when the observable ordering of DMA, PPU mode visibility, MMIO side effects, interrupt visibility, or CPU acceptance changes.
40+
- Keep requester arbitration, MMIO commit, and interrupt aggregation explicit; do not replace them with ad hoc subsystem-to-subsystem calls just because one local test passes.
41+
- Add or update focused cross-subsystem tests such as DMA-vs-CPU, delayed timer `IF`, serial completion plus IRQ, joypad visible-edge IRQ, `HALT` / IRQ priority, and `STAT`-versus-bus coherence.
42+
- Prefer cycle traces over aggregate instruction summaries when validating scheduler work.
43+
3644
## Definition of done
3745

3846
- Behavior is implemented consistently with hardware semantics and project architecture.

AI/ROADMAP.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,30 @@ For the authoritative structure and boundaries, see
148148

149149
---
150150

151+
## Cross-cutting scheduler foundation workstream
152+
153+
This workstream spans the early roadmap because the scheduler contract must be
154+
fixed before later CPU, DMA, PPU, timer, serial, joypad, and APU work can stay
155+
coherent without refactoring.
156+
157+
1. **Explicit scheduler phases** (`Phase 0`)
158+
Goal: refactor stepping around one visible `step_t_cycle()`-style entry point with fixed per-T-cycle phases.
159+
Acceptance criteria: the phase order is explicit and stable, there are no hidden cross-calls that bypass it, the scheduler owns ordering rather than reimplementing subsystem rules, and one `CycleContext`-style object or equivalent carries current-cycle events, derived signals, ownership facts, and queued side effects or IRQ requests.
160+
2. **Central arbitration** (`Phase 1`)
161+
Goal: unify decode, ownership, and access policy behind one requester-aware bus path.
162+
Acceptance criteria: CPU and DMA use the same arbitration route, decode/ownership and access-policy layers stay distinct, and DMG OAM DMA correctly leaves CPU with HRAM only.
163+
3. **IRQ aggregation layer** (`Phase 2`)
164+
Goal: separate source request, `IF` visibility, and CPU acceptance.
165+
Acceptance criteria: PPU, timer, serial, and joypad only request; the CPU accepts by `IME/IE/IF` and fixed priority; timer keeps its delayed one-M-cycle request timing.
166+
4. **Cycle logging** (`Phase 0`, expanded later)
167+
Goal: make the actual ordering visible per T-cycle.
168+
Acceptance criteria: traces can expose phase, bus owner, CPU micro-op, PPU mode, DMA activity, timer or serial events, and `IF/IE/IME`.
169+
5. **Global-order regression tests** (`Phase 2` onward)
170+
Goal: lock down the scheduler invariants at cross-subsystem boundaries.
171+
Acceptance criteria: focused tests cover DMA versus CPU, delayed timer `IF`, serial completion plus IRQ, joypad visible `High -> Low` plus IRQ, `HALT` / IRQ priority, and `STAT`-versus-bus coherence.
172+
173+
---
174+
151175
## Recommended implementation order
152176

153177
### Phase 0 — Verification, debugging, and base architecture infrastructure
@@ -200,8 +224,10 @@ This phase must define:
200224

201225
- base hardware types in `model/`
202226
- T-cycle scheduler skeleton
227+
- explicit per-T-cycle phase order with one `step_t_cycle()`-style top-level entry point
203228
- definition of responsibilities by module
204229
- initial interfaces between CPU, bus, PPU, DMA, timer, cartridge, and debugger
230+
- a cycle-local context shape carrying external events, derived signals, ownership facts, and queued side effects or IRQ requests
205231
- conventions to avoid mixing frontend logic with core logic
206232

207233
#### Done criteria
@@ -210,7 +236,9 @@ This phase must define:
210236
- the project can run base tests against `gb-core`
211237
- there is a reusable minimum tracing infrastructure
212238
- the scheduler has a defined notion of T-cycle advancement
239+
- the scheduler phase order is explicit in code and docs rather than implicit in subsystem call chains
213240
- the responsibility split between modules is fixed
241+
- cycle traces can expose enough per-T-cycle state to debug scheduler ordering issues
214242
- the core does not depend on `gb-cli`, `gb-desktop`, or `gb-web` to function
215243
- the architecture is prepared to incorporate CGB without contaminating DMG behavior yet
216244

@@ -253,6 +281,7 @@ Build the real foundation of the emulated system on top of which CPU, timer, DMA
253281
- global stepping by T-cycle
254282
- explicit notion of dot compatible with the PPU
255283
- stable stepping order for subsystems inside the scheduler
284+
- explicit ordering between external-event ingress, shared-counter advance, derived edges, free-running peripherals, bus arbitration, CPU micro-ops, MMIO commit, IRQ aggregation, and CPU wake / accept
256285

257286
##### Bus and memory map
258287

@@ -261,6 +290,7 @@ Build the real foundation of the emulated system on top of which CPU, timer, DMA
261290
- modeling of echo RAM and unusable areas
262291
- access arbitration infrastructure
263292
- one central decode path across `0x0000-0xFFFF` with explicit owner and access policy per region
293+
- distinct decode / ownership and access-policy layers in that arbitration path
264294
- centralized MMIO register routing instead of a generic RAM-like `FF00-FF7F` block
265295

266296
##### Base cartridge
@@ -293,9 +323,11 @@ Build the real foundation of the emulated system on top of which CPU, timer, DMA
293323
#### Done criteria
294324

295325
- the system can advance by T-cycles consistently
326+
- the scheduler phase order is stable, explicit, and shared across subsystems instead of encoded through call-site accidents
296327
- all memory accesses go through `bus/`
297328
- the memory map is modeled completely for DMG
298329
- every DMG address region has an explicit owner, read behavior, write behavior, and blocked-access policy where applicable
330+
- bus arbitration resolves decode/ownership before applying requester-specific restrictions, and DMA-versus-CPU precedence is centralized instead of duplicated
299331
- every MMIO address in `0xFF00-0xFF7F` and `0xFFFF` has an explicit routed owner and contract rather than accidental default byte-storage behavior
300332
- mixed MMIO registers are represented as per-field contracts rather than as plain read/write bytes plus a coarse mask
301333
- the cartridge subsystem parses `0x0100-0x014F` into a typed header structure and preserves CGB/SGB compatibility flags for later work
@@ -423,6 +455,7 @@ Build a truly temporal CPU core, where observable behavior emerges from internal
423455
- real temporal effect of EI and DI
424456
- interrupt priority
425457
- interrupt acceptance timing
458+
- explicit separation between source request, `IF` aggregation, CPU wake, and CPU interrupt acceptance
426459
- HALT
427460
- STOP
428461
- HALT bug
@@ -433,6 +466,7 @@ Build a truly temporal CPU core, where observable behavior emerges from internal
433466
- instructions generate their real bus accesses
434467
- the timer advances with the global scheduler
435468
- interrupts and HALT are integrated into the real execution flow
469+
- source requests become visible in `IF` before the CPU accepts them, and timer keeps its delayed request timing instead of being flattened into same-cycle overflow service
436470
- direct-boot timer state does not fake `DIV` or related registers through disconnected visible-only initialization
437471
- real boot executes through the same CPU fetch/decode/execute engine used for the rest of the machine
438472
- real boot reaches cartridge code only through an executed `FF50` write and next-fetch handoff
@@ -470,11 +504,13 @@ Integrate OAM DMA as a real transfer mechanism inside the system architecture, c
470504
- real temporal copy progression
471505
- integration with bus arbitration
472506
- observability of DMA start, progress, and completion
507+
- scheduler-visible DMA state that bus arbitration can use on the same T-cycle
473508

474509
#### Done criteria
475510

476511
- the transfer is not implemented as an instantaneous memory copy
477512
- arbitration correctly reflects DMA effects on concurrent accesses
513+
- CPU-versus-DMA precedence is decided centrally through bus arbitration instead of CPU-local blocking logic
478514
- the system can trace DMA over time
479515

480516
#### Risks if delayed too much
@@ -697,6 +733,7 @@ Complete basic system peripherals on top of an already consolidated bus, schedul
697733
- serial interrupt generation driven by real transfer completion rather than by transfer start
698734
- serial port functional at the emulated hardware level
699735
- trace tools to observe both peripherals
736+
- scheduler-visible regression coverage for their IRQ timing and CPU wake interactions
700737

701738
#### Done criteria
702739

@@ -716,6 +753,7 @@ Complete basic system peripherals on top of an already consolidated bus, schedul
716753
- serial interrupt requests occur only at transfer completion, when the eighth shift clears `SC.7`
717754
- both are integrated through the bus and scheduler
718755
- their interrupts and states are observable and testable
756+
- their event timing does not depend on frame callbacks or host timers bypassing the T-cycle scheduler
719757

720758
#### Joypad implementation breakdown
721759

AI/TIMING-AND-ACCURACY.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,25 @@ When implementing timing:
5252
- When external documentation expresses a timing rule in M-cycles or microseconds, restate the corresponding T-cycle value in project docs and code whenever that rule becomes behaviorally relevant or is recorded as deferred validation work.
5353
- Keep the timing model clean enough that future CGB double-speed behavior can be expressed as an extension of the same temporal foundation rather than a separate clocking design.
5454

55+
## Global scheduler rule
56+
57+
- The core should advance through one fixed per-T-cycle scheduler contract, not through ad hoc call chains between subsystems.
58+
- The recommended ordering is:
59+
1. external event ingress
60+
2. master clock / shared system-counter tick
61+
3. free-running counter-derived edge resolution
62+
4. autonomous peripheral ticks
63+
5. bus arbitration
64+
6. CPU micro-operation
65+
7. MMIO side-effect commit
66+
8. interrupt aggregation into `IF`
67+
9. CPU wake / interrupt-accept evaluation
68+
- This is a project-level deterministic ordering rule chosen to preserve documented dependencies; it is not presented as the one true internal Nintendo implementation.
69+
- Free-running divider-derived events such as timer input edges and `DIV-APU` edge detection belong to step `3`, after the shared counter advances and before autonomous peripherals consume those edges.
70+
- Immediate MMIO effects produced by a write on the access T-cycle, such as `DIV` reset behavior, `FF46` DMA start, `SC.7` transfer start, or `LCDC.7` LCD transitions, still belong to the owning device when the access commits in step `7`.
71+
- `IF` updates from hardware sources belong to step `8`; CPU acceptance is a later CPU-owned decision and must not be collapsed into the producer path.
72+
- Another internal implementation shape is acceptable only if these same observable dependencies remain true.
73+
5574
## Practical timing rule
5675

5776
- CPU, PPU, timer, APU, DMA, and bus-visible effects should be expressible on a shared T-cycle timeline.
@@ -80,3 +99,5 @@ When implementing timing:
8099
- Slow APU control clocks such as length, envelope, and CH1 sweep must remain distinct from each channel's own fast waveform timer and from the host sample or resampler cadence.
81100
- Host-rate sample production should observe already-stepped hardware state; it must not become the clock that drives the APU core.
82101
- When cartridge hardware uses wall-clock-like progression outside powered-on execution, such as battery-backed `MBC3` RTC advance between sessions, model that through an explicit elapsed-time source at the persistence boundary and restate any powered-on bus-visible timing rule in T-cycles when it becomes behaviorally relevant.
102+
- Timer, joypad, serial, and the APU frame sequencer must not be tied to video-frame or VBlank callbacks; they live on the shared machine timeline even when their visible effects are unrelated to the LCD.
103+
- Bus restrictions and MMIO-visible state must tell one coherent story on that timeline. For example, `STAT.mode`, VRAM/OAM accessibility, DMA blocking, `SC.7`, `TIMA/TMA/IF`, and visible `JOYP` state must align with the same current-cycle machine state the scheduler uses internally.

0 commit comments

Comments
 (0)