feat(audio): implement nominal Direct Sound playback and FIFO DMA
This commit is contained in:
parent
894f524cda
commit
5888c4d292
21 changed files with 933 additions and 129 deletions
|
|
@ -1,49 +1,112 @@
|
|||
# Sound initialization subset
|
||||
# Direct Sound subset
|
||||
|
||||
Sound synthesis and host audio output are not implemented.
|
||||
The core now models disabled sound-register state so firmware can perform a functional sound reset.
|
||||
This is not a silent-audio compatibility mode. Enabling sound returns an explicit diagnostic.
|
||||
The core models timer-driven Direct Sound A/B playback and a bounded sound-DMA configuration.
|
||||
It does not produce desktop audio. Programmable sound generator (PSG) synthesis remains unsupported.
|
||||
`Memory::audio_level()` exposes the current digital stereo level for deterministic inspection, not a continuous sample stream.
|
||||
|
||||
## Supported state
|
||||
## Registers and inactive PSG state
|
||||
|
||||
| Address | Behavior |
|
||||
| --- | --- |
|
||||
| `0x04000060..0x04000081` | Programmable sound generator (PSG) registers remain zero; writes have no effect while master sound is disabled |
|
||||
| `0x04000082` (`SOUNDCNT_H`) | Retains mixing/routing/timer configuration with readback mask `0x770f` |
|
||||
| `0x04000084` (`SOUNDCNT_X`) | Reads zero; status flags are read-only; a write setting bit 7 fails |
|
||||
| `0x04000088` (`SOUNDBIAS`) | Retains bias/resolution configuration with mask `0xc3fe`; no analog output is generated |
|
||||
| `0x04000090..0x0400009f` | Read/write wave RAM in the CPU-accessible inactive bank |
|
||||
| `0x04000060..0x04000081` | Idle PSG configuration and documented read masks; writes are ignored while master sound is disabled |
|
||||
| `0x04000082` (`SOUNDCNT_H`) | Mixing, routing, timer selection, and FIFO reset strobes; readback mask `0x770f` |
|
||||
| `0x04000084` (`SOUNDCNT_X`) | Master enable in bit 7; read-only PSG activity flags remain zero |
|
||||
| `0x04000088` (`SOUNDBIAS`) | Bias/resolution configuration with mask `0xc3fe` |
|
||||
| `0x04000090..0x0400009f` | CPU wave RAM window, opposite the bank selected by `SOUND3CNT_L` bit 6 |
|
||||
| `0x040000a0..0x040000a7` | FIFO A/B writes with byte, halfword, and word access boundaries preserved |
|
||||
|
||||
Unused mapped bytes through `0x0400008b` read zero and ignore writes.
|
||||
The FIFO reset strobes in `SOUNDCNT_H` do not latch. No FIFO data can be queued in this subset.
|
||||
FIFO addresses and the remaining sound-region holes stay unmapped.
|
||||
All sound configuration starts at zero in raw memory. RegisterRamReset bit 6 sets the functional bias default to `0x0200`.
|
||||
Unused mapped register bytes read zero and ignore writes.
|
||||
FIFO reads and the `0x0400008c..0x0400008f` gap remain explicit unmapped-access errors; no FIFO open-bus value is invented.
|
||||
Raw memory starts with zero sound state. RegisterRamReset bit 6 sets the functional bias default to `0x0200`.
|
||||
|
||||
With master sound disabled, `SOUND3CNT_L` stays zero. The CPU accesses wave bank 1.
|
||||
Bank 0 cannot be selected or played and remains zero. Disabling master sound does not erase wave RAM.
|
||||
The BIOS reset explicitly clears the accessible wave RAM through ordinary stores.
|
||||
Future bank switching and channel playback need a complete sound engine, not a larger register array.
|
||||
Master disable clears PSG configuration and the FIFO queues, but retains wave RAM and Direct Sound routing/bias.
|
||||
The CPU then accesses wave bank 1. Idle bank selection can expose bank 0 after master enable.
|
||||
The original BIOS reset clears bank 1 and both FIFO queues. It retains bank 0 and any separate in-flight playback word.
|
||||
This is a functional reset subset, not a claim of complete firmware ordering or sound-reset equivalence.
|
||||
|
||||
## Diagnostics and atomicity
|
||||
An enabled PSG channel trigger returns `MemoryError::UnsupportedIo` with the exact address and value.
|
||||
This applies even when the configured channel would be inaudible. Disabled PSG trigger writes are ignored.
|
||||
Length, envelope, sweep, frequency, and wave-bank playback clocks are not implemented.
|
||||
Do not interpret successful idle configuration writes as PSG synthesis.
|
||||
|
||||
A write setting `SOUNDCNT_X` bit 7 returns `MemoryError::UnsupportedIo`, including the address, byte value, and operation.
|
||||
Status bits and unused bits alone do not enable sound.
|
||||
Validation occurs before any bytes in the access are written.
|
||||
Block stores validate every register value before committing earlier writes. Failed CPU and DMA steps remain retryable.
|
||||
## FIFO and timer behavior
|
||||
|
||||
Original tests cover disabled-register writes, masks, byte lanes, wave retention, and BIOS reset selection.
|
||||
They also check rejected sound activation through byte/word accesses, ARM block stores, and DMA without partial state changes.
|
||||
Each channel has a seven-word queue, a separate four-byte playback word, and a held signed sample.
|
||||
Timer 0 or timer 1 overflow clocks the selected channel while master sound is enabled.
|
||||
Timer 1 cascade overflows work. Timer IRQ enable and stereo routing do not gate sample consumption.
|
||||
Timers 2 and 3 cannot clock Direct Sound.
|
||||
|
||||
## Remaining work
|
||||
On each selected overflow:
|
||||
|
||||
Implement channel clocks, length/envelope/sweep state, wave banking, FIFO consumption, timer-triggered sound DMA, and mixing.
|
||||
Add a host-independent sample interface before connecting a desktop audio backend.
|
||||
Do not acknowledge active sound requests without implementing their device behavior.
|
||||
1. Request sound DMA if the queue contains at most three words, before loading a playback word.
|
||||
2. Load a queued word if the playback word is empty.
|
||||
3. Latch the next signed eight-bit sample in little-endian order.
|
||||
|
||||
The held sample remains unchanged between overflows. An empty playback word supplies zero on the next overflow.
|
||||
This underflow policy follows both consulted emulator implementations; the cited hardware report leaves it uncertain.
|
||||
A bulk advance consumes all available samples and then reaches zero without looping over every empty overflow.
|
||||
|
||||
A partial FIFO write updates the addressed lanes of the next queue slot and enqueues that whole word.
|
||||
Other lanes retain that slot's previous bytes. A write to a full queue clears the queue.
|
||||
Reset strobes clear queues without changing the held sample or remaining playback bytes.
|
||||
Queue clearing also zeros stored queue slots in this nominal model.
|
||||
Exact stale-lane behavior across overflow/reset remains unverified on hardware.
|
||||
|
||||
HALT continues timer/audio progress. STOP freezes it.
|
||||
Master disable stops sample consumption; route disable only mutes the mixer path.
|
||||
|
||||
## Sound DMA
|
||||
|
||||
Special timing supports DMA1 to FIFO A and DMA2 to FIFO B.
|
||||
Each request transfers four 32-bit words with a fixed destination, regardless of programmed count, width, or destination mode.
|
||||
Source address control, channel priority, repeat enable, completion IRQ, and retained DMA data still apply.
|
||||
Without repeat, the channel disables after the fourth word. With repeat, it waits for another timer request.
|
||||
|
||||
Other channel/FIFO pairings and non-FIFO destinations remain explicit `DmaError::UnsupportedControl` cases.
|
||||
The references disagree about request routing outside the supported pairing; hardware reports permit non-FIFO destinations.
|
||||
The core does not silently guess those configurations. Video capture and Game Pak DRQ remain unsupported.
|
||||
|
||||
Requests become visible at successful machine-step boundaries. Active DMA blocks ignore further requests.
|
||||
Device-only bulk advances coalesce unserviced requests; they do not execute or replay DMA blocks.
|
||||
Sub-instruction DMA arbitration and exact refill latency are not modeled.
|
||||
|
||||
## Mixing and timing ownership
|
||||
|
||||
Each routed sample contributes `sample * 2` at 50% volume or `sample * 4` at 100% volume.
|
||||
The mixer adds the ten-bit bias, clips each side to `0..1023`, and subtracts 512.
|
||||
`StereoLevel` therefore contains signed levels in `-512..511`. Master disable returns zero.
|
||||
Bias resolution bits retain their values but do not yet drive pulse-width modulation (PWM) sampling or quantization.
|
||||
There is no resampling, analog filtering, sample queue, or host output backend.
|
||||
Polling once per video frame will not reconstruct audio.
|
||||
|
||||
Timer and sound state share the staged CPU/DMA clock.
|
||||
Reads and writes observe their nominal bus-completion phase, after earlier timer overflows.
|
||||
Successful steps commit once. Failed steps discard FIFO consumption, held samples, register writes, and refill requests.
|
||||
Block-store validation simulates sound enable/disable in a temporary state before committing any RAM or I/O writes.
|
||||
Other devices retain their existing scheduling limits.
|
||||
|
||||
## Validation and remaining work
|
||||
|
||||
Original tests cover signed samples, byte order, queue capacity, partial writes, resets, underflow, stereo routing, and clipping.
|
||||
They also cover cascaded timers, DMA block/repeat behavior, source progress, completion IRQs, HALT/STOP, and clock batching.
|
||||
CPU/DMA failure tests verify transactional rollback and sound writes at bus completion.
|
||||
BIOS tests cover disabled FIFO reset and the supported wave-bank policy.
|
||||
|
||||
These tests validate the documented nominal model, not hardware audio fidelity.
|
||||
Next work: PSG clocks and synthesis, a timestamped or fixed-rate output stream, PWM/mixer sampling, and a Darwin host backend.
|
||||
The local Emerald startup now passes master enable and stops at a PSG channel 1 trigger.
|
||||
See [the local runtime result](../research/emerald-reset.md).
|
||||
|
||||
## References
|
||||
|
||||
- [GBATEK sound control registers](https://problemkaputt.de/gbatek-gba-sound-control-registers.htm): disabled PSG behavior, master enable, masks, FIFO reset strobes, and bias.
|
||||
- [GBATEK](https://mgba-emu.github.io/gbatek/): channel 3 wave-bank access and register layout.
|
||||
- [mGBA BIOS service implementation at 3a5e34be](https://github.com/mgba-emu/mgba/blob/3a5e34be33dc7f8f707e5bc9db69e8a430046f21/src/gba/bios.c): functional reset defaults. No upstream implementation was copied.
|
||||
- [GBATEK Direct Sound](https://problemkaputt.de/gbatek-gba-sound-channel-a-and-b-dma-sound.htm): registers, timer selection, signed samples, and DMA blocks.
|
||||
- [GBATEK sound control](https://problemkaputt.de/gbatek-gba-sound-control-registers.htm): master enable, read masks, reset strobes, routing, and bias.
|
||||
- [GBATEK mirror](https://mgba-emu.github.io/gbatek/): wave-bank access and register layout.
|
||||
- [gbadev Direct Sound overview](https://gbadev.net/gbadoc/audio/directsound.html): introductory FIFO/timer/DMA behavior; not authority for queue edge cases.
|
||||
- [Gericom's hardware findings, mGBA issue 1847](https://github.com/mgba-emu/mgba/issues/1847): seven-word queue, separate playback buffer, request order, partial writes, and overflow observations.
|
||||
- [mGBA audio at 3a5e34be](https://github.com/mgba-emu/mgba/blob/3a5e34be33dc7f8f707e5bc9db69e8a430046f21/src/gba/audio.c): playback, reset, mixing, and destination-based request routing comparison.
|
||||
- [NanoBoyAdvance APU at 55b5cf0a](https://github.com/nba-emu/NanoBoyAdvance/blob/55b5cf0ae3d929582ac5bfd486558173502b8354/src/nba/src/hw/apu/apu.cc): master gating, playback word, underflow, and mixing.
|
||||
- [NanoBoyAdvance FIFO](https://github.com/nba-emu/NanoBoyAdvance/blob/55b5cf0ae3d929582ac5bfd486558173502b8354/src/nba/src/hw/apu/channel/fifo.hh), [registers](https://github.com/nba-emu/NanoBoyAdvance/blob/55b5cf0ae3d929582ac5bfd486558173502b8354/src/nba/src/hw/apu/registers.cc), and [DMA](https://github.com/nba-emu/NanoBoyAdvance/blob/55b5cf0ae3d929582ac5bfd486558173502b8354/src/nba/src/hw/dma/dma.cc): queue/reset policy and channel-based request routing comparison.
|
||||
- [mGBA BIOS at 3a5e34be](https://github.com/mgba-emu/mgba/blob/3a5e34be33dc7f8f707e5bc9db69e8a430046f21/src/gba/bios.c): functional reset defaults.
|
||||
|
||||
Reset values are source-backed. Exact Nintendo firmware ordering, timing, and analog behavior are not established.
|
||||
The Rust implementation and regression programs are original. No upstream implementation or external test ROM was copied into the repository.
|
||||
|
|
|
|||
|
|
@ -216,11 +216,12 @@ Supported flags can be combined:
|
|||
| 3 | Clear all 96 KiB of video RAM: `0x06000000..0x06017fff` |
|
||||
| 4 | Clear sprite attribute memory (OAM): `0x07000000..0x070003ff` |
|
||||
| 5 | Reset supported idle serial state and select general-purpose inputs |
|
||||
| 6 | Reset disabled sound state, mixing control, bias, and accessible wave RAM |
|
||||
| 6 | Disable sound, reset FIFO queues and mixing control, set bias, and clear accessible wave bank 1 |
|
||||
| 7 | Reset supported display, DMA, timer, and interrupt registers as described below |
|
||||
|
||||
All low-byte flag combinations, including `r0=0xff`, are accepted within the supported device subset.
|
||||
This is not full audio or serial support. [Sound activation](audio.md) and [serial transfers](serial.md) still fail explicitly.
|
||||
This is not full audio or serial support. [PSG playback](audio.md) and [serial transfers](serial.md) still fail explicitly.
|
||||
Sound reset retains wave bank 0 and the separate in-flight playback word; exact firmware sound-reset equivalence is not established.
|
||||
The firmware always clears the low halfword of SIODATA32, even when bit 5 is clear; its upper halfword is preserved.
|
||||
|
||||
Every supported request first writes `DISPCNT=0x0080`, including requests with no selected flags.
|
||||
|
|
@ -509,5 +510,5 @@ 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.
|
||||
Active sound, serial transfers, HardReset, and serial/Game Pak STOP wake-up remain unimplemented.
|
||||
PSG playback, host audio output, serial transfers, HardReset, and serial/Game Pak STOP wake-up remain unimplemented.
|
||||
This subset is not sufficient for Pokémon Emerald compatibility.
|
||||
|
|
|
|||
|
|
@ -59,7 +59,9 @@ Exact startup/completion placement remains nominal, not independently verified p
|
|||
See [CPU resume timing](cpu.md#nominal-cpu-resume-after-dma) for failure, idle, and refill rules.
|
||||
Startup scheduling delays, channel-resumption costs, a full CPU fetch pipeline, and display-bus contention remain unmodeled.
|
||||
|
||||
Special timing modes (sound FIFO/video capture), Game Pak DRQ, and prohibited source mode 3 return `DmaError::UnsupportedControl`.
|
||||
Special timing supports DMA1/FIFO A and DMA2/FIFO B with four-word blocks, forced word width, and fixed destinations.
|
||||
Timer-driven requests, repeat, and completion IRQs are described in [Direct Sound](audio.md).
|
||||
Other special-mode pairings, video capture, Game Pak DRQ, and prohibited source mode 3 return `DmaError::UnsupportedControl`.
|
||||
DMA reads below work RAM reuse known channel data as described below. They never expose BIOS bytes.
|
||||
DMA writes to DMA registers return `DmaError::RegisterDestination`; self-modifying transfers are not supported.
|
||||
Other accesses use the existing memory map, alignment checks, mirrors, and I/O write rules.
|
||||
|
|
|
|||
|
|
@ -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 and timer clocks by its nominal cost.
|
||||
Each successful step advances display, timer, and Direct Sound 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 accesses use ordered bus-completion phases. Other device writes still take effect before their bulk clock update.
|
||||
Timer and sound 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`.
|
||||
|
|
@ -34,10 +34,11 @@ The transaction also owns timer IF bits. Reads see overflows through the current
|
|||
A later internal cycle can raise an acknowledged timer request again. Non-timer IF bits 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, and timer IF bits.
|
||||
Failed steps discard staged counters, divider progress, reload/control writes, and requests.
|
||||
Successful steps commit timers, the shared divider phase, timer IF bits, and Direct Sound state.
|
||||
Failed steps discard staged counters, divider progress, timer/audio writes, FIFO consumption, and requests.
|
||||
Display and other device clocks then advance once, without advancing timers again.
|
||||
Those devices still use the bulk scheduler. Mid-instruction display events, general bus arbitration, and IRQ synchronization delays remain unmodeled.
|
||||
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.
|
||||
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.
|
||||
|
|
@ -92,7 +93,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.
|
||||
Audio events and interrupt sources other than timers, display events, DMA completion, and keypad input are not implemented.
|
||||
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.
|
||||
|
||||
`--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`.
|
||||
|
|
@ -228,7 +230,7 @@ The current desktop demos do not enter STOP; the frontend does not yet provide a
|
|||
|
||||
Only keypad wake-up is implemented. General-purpose serial and Game Pak wake sources require their missing external devices.
|
||||
Oscillator restart delays, exact entry/wake edges, and hardware-specific IF behavior remain unverified.
|
||||
There is no host-thread sleep, wall-clock accounting, or audio clock implementation.
|
||||
There is no host-thread sleep or wall-clock accounting. STOP freezes the emulated timer-driven audio clock.
|
||||
|
||||
The graphics demo now uses the optional original BIOS replacement's VBlankIntrWait service.
|
||||
Its IRQ callback acknowledges VBlank and updates the BIOS RAM flag before the service returns.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue