feat(serial): clock disconnected normal transfers and completion IRQs

This commit is contained in:
0xrsydn 2026-10-07 04:17:46 +07:00
commit 0b36ef1e24
17 changed files with 860 additions and 196 deletions

View file

@ -544,5 +544,6 @@ Unsupported SWIs, unsupported exception vectors, and null/misaligned IRQ callbac
The CPU reports `CpuError::UnsupportedInstruction` with `bios::UNSUPPORTED_TRAP`, rather than silently treating a service as a no-op.
Invalid arithmetic/decompression arguments use the same CPU error type with the distinct `bios::INVALID_ARGUMENT_TRAP` instruction.
Prior boot/service steps remain committed on failure.
PSG wave channel 3, host audio output, serial transfers, HardReset, and serial/Game Pak STOP wake-up remain unimplemented.
PSG wave channel 3, host audio output, connected serial links, HardReset, and serial/Game Pak STOP wake-up remain unimplemented.
Disconnected normal serial transfers use the [bounded serial model](serial.md); cancel active transfers before BIOS serial reset.
This subset is not sufficient for Pokémon Emerald compatibility.

View file

@ -1,17 +1,19 @@
# Disconnected serial subset
The core supports normal-mode data registers, disconnected external-clock waiting, and general-purpose pins.
It does not support internally clocked transfers, external clock edges, multiplayer, UART, Joybus communication, or external serial interrupts.
The core supports disconnected normal-mode transfers, external-clock waiting, and general-purpose pins.
Internal clocks shift pulled-high input at nominal GBA rates. No link partner is emulated.
External clock edges, multiplayer, UART, Joybus communication, and GPIO interrupts remain unsupported.
Unsupported activity remains diagnostic rather than reporting a fabricated completion.
## Registers
- `SIODATA32` at `0x04000120` is a four-byte latch in the supported normal-mode subset.
- `SIODATA8` at `0x0400012a` retains its low byte; its upper byte reads zero.
- `SIODATA32` at `0x04000120` holds the 32-bit shift register. Byte lanes and halfword accesses are supported.
- `SIODATA8` at `0x0400012a` holds the 8-bit shift register; its upper byte reads zero.
Each transfer shifts only its selected data register. The other register retains its value.
- `SIOCNT` at `0x04000128` retains normal 8/32-bit mode, IRQ configuration, clock selection, and the output-data bit.
The retained mask is `0x508b`. The disconnected serial input is high, except when a general-purpose output drives it low.
Start bit 7 can latch when clock-source bit 0 is clear. Software clears bit 7 to cancel the request.
Setting both bits 7 and 0 returns an internally-clocked-transfer diagnostic. Multiplayer/UART mode bit 13 remains unsupported.
Start bit 7 requests a transfer. Bit 0 selects external or internal clock; bit 1 selects the internal rate.
Completion or software cancellation clears start. Multiplayer/UART mode bit 13 remains unsupported.
- `RCNT` at `0x04000134` retains mode bits, interrupt configuration, output latches, and pin directions with mask `0xc1ff`.
With bit 15 clear, bits 8 and 14 are writable latches without GPIO interrupt or Joybus effects.
With bit 15 set, bit 14 selects Joybus; otherwise bit 8 enables GPIO interrupts. Both active configurations remain diagnostic.
@ -23,7 +25,7 @@ Unsupported activity remains diagnostic rather than reporting a fabricated compl
Nonzero writes return an explicit error. Joybus status and data-transfer side effects are not implemented.
GPIO means general-purpose input/output. UART means universal asynchronous receiver/transmitter.
No external serial device is connected. Elapsed cycles alone do not change serial data or generate an IRQ.
No external serial device is connected. Only an enabled internal serial clock can shift data or generate a completion IRQ.
Configuration that hardware accepts in other modes can still be rejected by this intentionally limited subset.
## External-clock waiting
@ -36,12 +38,63 @@ Repeated start writes do not complete the request. Software can cancel it by cle
General-purpose mode does not shift serial data. A retained external-clock start bit has no link-port effect there.
Returning to normal mode still cannot complete a transfer without external clock edges.
Selecting internal clock while start is set remains diagnostic, including after an external request.
Selecting internal clock while an unshifted request is pending starts a fresh nominal bit period.
This is a disconnected waiting model, not an implementation of a connected link or an external clock-input API.
Original tests cover both widths, rate settings, control lanes, cancellation, GPIO selection, HALT/STOP, and ARM/Thumb stores.
DMA tests distinguish its own completion IRQ from a serial IRQ. Invalid word/block stores cannot leave a partial start request.
## Internal clocks and completion
Normal mode shifts most-significant bit first. At each nominal sampling edge, the selected register shifts left and receives a one.
The disconnected SI input is high. Completion therefore leaves `0xff` or `0xffffffff`, without inventing peer data.
Reads between edges expose the partially shifted value. SO waveforms and normal-mode RCNT pin samples remain unmodeled.
At the nominal 16,777,216 Hz system clock:
| SIOCNT bit 1 | Cycles per bit | 8-bit transfer | 32-bit transfer |
| --- | --- | --- | --- |
| 0 | 64 | 512 cycles | 2,048 cycles |
| 1 | 8 | 64 cycles | 256 cycles |
The first sample occurs one full bit period after activation. Selecting internal clock for an unshifted external request uses this same rule.
A repeated start write with unchanged width/clock fields preserves the current phase and remaining bit count.
A width/clock change before any bit has shifted starts a fresh period. Data writes before that first bit preserve the phase.
Software cancellation clears progress but retains already shifted data.
Whole-access writes merge control and data lanes before applying start/cancel behavior; byte writes remain separate accesses.
Completion clears start and latches IF bit 7 when SIOCNT bit 14 is enabled at the final edge.
IE, IME, and CPSR.I gate wake-up or delivery, not request latching. Clearing IF acknowledges the request.
A completed request does not reassert IF without another transfer.
HALT continues internal shifting and bounds its idle batch at serial completion. STOP freezes the bit phase and remaining count.
Only keypad wake is implemented for STOP; serial GPIO wake still requires its missing external-input model.
### Bus timing and validation
Serial state shares the timer/audio transaction during CPU instructions, IRQ entry, and DMA units.
Each access observes all elapsed source/data cycles through its bus-completion phase.
A start write excludes its preceding cycles. A load excludes its trailing internal cycle.
IF reads and acknowledgements include serial completion through that access, alongside timer requests.
Successful steps commit serial state and requests once. Failed steps discard speculative shifts, writes, and completion IRQs.
The display/capture scheduler does not clock serial again after that commit.
CPU-only stepping can change registers but does not advance serial clocks.
Block-store preflight simulates ordered access phases on temporary timing/device copies before committing any write.
Validation can therefore accept reconfiguration when an earlier request completes before the write reaches the bus.
The actual transfers then execute once from the original state. Other IF sources and DMA completion flags remain independent.
### Bounded behavior
After any bit has shifted, changes to width, clock selection/rate, or the selected data register remain diagnostic while start stays set.
Selecting GPIO during an internally clocked request also remains diagnostic. Clear start before these operations.
IRQ-enable and idle SO configuration writes remain supported without restarting progress.
These limits also apply to BIOS services that write serial registers; cancel active transfers before requesting serial reset.
Unverified live reconfiguration is not silently approximated.
The period arithmetic follows documented GBA rates. Activation phase, mid-transfer register visibility, and exact pin edges lack physical-hardware validation.
No external pin waveform, cable, connected partner, or serial-device protocol is implemented.
Original tests cover individual shifts, batching, cancellation, IRQ gating, HALT/STOP, bus phases, DMA, capture independence, and rollback.
## BIOS reset
RegisterRamReset always clears the low halfword of SIODATA32, even without bit 5.
@ -52,15 +105,16 @@ Clearing SIOCNT cancels a pending external-clock request. Unselected serial rese
Original tests cover independent byte lanes, input pull-ups, output directions, reset flag selection, and explicit unsupported operations.
Whole-access and block-store validation prevent a rejected control value from committing preceding bytes or registers.
RCNT selection bits share the high byte. Each high-byte write replaces the complete selection, so validation needs no retained-mode shadow.
Static RCNT mode rejection uses the complete high byte. Live-transfer validation additionally uses staged serial state.
Mode-gating tests cover every high-byte value, previous modes, low-byte preservation, CPU stores, DMA retries, and unchanged interrupt state.
Inactive configuration bits do not wake HALT. RCNT word accesses still reject the unmapped padding at `0x04000136`.
## References
- [GBATEK reset functions](https://problemkaputt.de/gbatek-bios-reset-functions.htm): reset flags, general-purpose selection, and the unconditional SIODATA32 side effect.
- [GBATEK normal serial mode](https://problemkaputt.de/gbatek-sio-normal-mode.htm): data widths, input status, writable unused RCNT bits 8/14, and external-clock slave start/wait/timeout behavior.
- [mGBA serial implementation at 3a5e34be](https://github.com/mgba-emu/mgba/blob/3a5e34be33dc7f8f707e5bc9db69e8a430046f21/src/gba/sio.c): compared normal-mode control and completion paths. Its no-driver scheduled completion does not establish disconnected external-clock hardware behavior; this core does not copy that fallback.
- [GBATEK normal serial mode](https://problemkaputt.de/gbatek-sio-normal-mode.htm): data widths, MSB-first shifting, rates, pulled-high SI, start/completion, IRQ selection, and external-clock waiting.
- [GBATEK mode summary](https://problemkaputt.de/gbatek-sio-control-registers-summary.htm): RCNT/SIOCNT mode selection.
- [mGBA serial implementation at 3a5e34be](https://github.com/mgba-emu/mgba/blob/3a5e34be33dc7f8f707e5bc9db69e8a430046f21/src/gba/sio.c): cross-checked normal transfer-duration arithmetic and completion IRQ handling. Its no-driver scheduled completion and zero receive data are not copied; they do not establish disconnected hardware behavior.
- [GBATEK](https://mgba-emu.github.io/gbatek/): GBA general-purpose pin directions, internal pull-ups, SI falling-edge interrupts, and the SIO mode-selection table.
- [mGBA BIOS implementation at 3a5e34be](https://github.com/mgba-emu/mgba/blob/3a5e34be33dc7f8f707e5bc9db69e8a430046f21/src/gba/bios.c): functional serial reset defaults, not copied source.

View file

@ -7,7 +7,7 @@
Otherwise it services one ready DMA data unit, keeping the CPU paused.
If HALT is waiting and no DMA is ready, it advances device clocks to the next event without executing CPU code.
Otherwise, it samples the IRQ line, including the CPU's interrupt mask, then enters IRQ mode or executes one instruction.
Each successful step advances display, timer, and audio clocks by its nominal cost.
Each successful step advances display, timer, audio, and enabled internal serial clocks by its nominal cost.
`Machine::last_timing()` reports separate code, data, internal, and idle cycle totals for the last successful step.
`StepTiming::idle_cycles` counts device-clock cycles during HALT; it does not count CPU work.
IRQ entry is a separate step; the vector instruction runs on the following step.
@ -15,7 +15,7 @@ Entry charges the discarded incoming-state PC+8/PC+4 fetch and the ARM vector pa
DMA resume can make the discarded fetch non-sequential. Missing discarded bytes do not prevent IRQ entry.
See [refill timing](../research/refill-fetch-timing.md) for history effects and remaining limits.
A device event during a step can trigger IRQ entry once no DMA is ready and CPU masks allow delivery.
Timer and sound accesses use ordered bus-completion phases. Other device writes still take effect before their bulk clock update.
Timer, sound, and serial accesses use ordered bus-completion phases. Other device writes still take effect before their bulk clock update.
`StepKind::Dma { channel }` identifies a DMA unit. `StepKind::HaltIdle` identifies one bounded HALT clock advance.
`StepKind::StopIdle` identifies a stopped system with no clock progress.
The other step kinds remain `Instruction` and `IrqEntry`.
@ -31,15 +31,18 @@ Timer-start writes do not count preceding cycles. Timer-stop writes count throug
Block transfers preserve distinct word phases; combined reload/control writes merge at one phase, with reload bytes first.
The transaction also owns timer IF bits. Reads see overflows through the current access; acknowledgements clear earlier timer requests.
A later internal cycle can raise an acknowledged timer request again. Non-timer IF bits retain their existing behavior.
A later internal cycle can raise an acknowledged timer request again.
The same transaction owns serial IF bit 7; serial completion and acknowledgement follow the same access boundaries.
Other IF sources retain their existing behavior.
IRQ delivery still waits until the next machine step. A committed timer request can wake HALT, but not STOP.
Successful steps commit timers, the shared divider phase, timer IF bits, and audio state.
Failed steps discard staged counters, divider progress, timer/audio writes, FIFO consumption, and requests.
Successful steps commit timers, the shared divider phase, audio/serial state, and timer/serial IF bits.
Failed steps discard staged counters, divider progress, device writes, FIFO consumption, serial shifts, and requests.
Display and other device clocks then advance once, without advancing timers again.
Display and remaining devices still use the bulk scheduler. Mid-instruction display events, general bus arbitration, and IRQ synchronization delays remain unmodeled.
[Direct Sound](audio.md) consumes selected timer overflows in the timer transaction; sound DMA requests commit at step boundaries.
The same transaction advances both PSG pulse oscillators, the noise counter, and their shared 512 Hz sequencer independently of the general-purpose timers.
It also advances [normal serial transfers](serial.md) with nominal internal clocks. External-clock requests cannot progress without a clock source.
Host clock advances and HALT idle batches retain ordinary bulk timer advancement; STOP idle does not advance timers.
The unchanged [prefetch cancellation probe](../research/prefetch-cancellation.md) now matches both published interval totals and actual timer samples.
@ -95,7 +98,8 @@ See [prescaler evidence and original tests](../research/timer-prescaler.md).
Hardware startup and register-write delays are still not modeled. Writes retain the immediate bus-completion policy.
The shared-phase tests validate this bounded model, not absolute enable/reload edges on hardware.
Timers 0 and 1 can clock Direct Sound playback and request FIFO DMA independently of timer IRQ enable.
Interrupt sources other than timers, display events, DMA completion, and keypad input are not implemented.
Implemented interrupt sources are timers, display events, DMA completion, keypad input, and normal serial completion.
Other serial modes and external serial interrupts remain unimplemented.
`--timer-demo` executes original ARM code that configures Timer 0, IE, and IME.
The timer overflows after 16 supplied cycles and enters the original handler through vector `0x18`.
@ -173,7 +177,7 @@ Wake-up does not acknowledge IF. Clearing IF after wake-up does not put the CPU
On the next machine step, ready DMA still takes priority.
The CPU then enters an unmasked IRQ handler or resumes the next instruction.
Each `HaltIdle` step advances to the earliest display edge or independently clocked timer overflow.
Each `HaltIdle` step advances to the earliest display edge, independently clocked timer overflow, or internal serial completion.
Display edges include every HBlank entry and scanline start. Cascaded timers receive pulses at their predecessor's overflow.
These bounds prevent idle advances from skipping DMA triggers or wake-up events.
They also keep frame execution bounded when no interrupt can wake the CPU.
@ -209,7 +213,7 @@ The same selected-button OR/AND logic applies, including the empty-mask behavior
IME and CPSR.I do not gate wake-up in this functional model.
A matching condition already present at entry prevents a sustained stop.
Stale IF flags alone cannot wake STOP, including an old keypad request.
Timer, display, and DMA requests cannot wake STOP.
Timer, display, DMA, and internal serial requests cannot wake STOP.
An input sample supplied while stopped updates KEYINPUT and the wake condition without setting IF.
It does not consume the normal running-state keypad polling history.