Nintendo DS Sound FIFO and Capture: ARM7 Streaming and DMA
Separate Nintendo DS PCM channels from ARM7 sound FIFOs, then trace FIFO requests, capture routing, DMA writes, and audio-clock edge cases.
Nintendo DS audio combines two paths that are easy to conflate. The sound hardware provides sixteen programmable channels, while the ARM7 side also has two PCM FIFO streams, commonly called FIFO A and FIFO B. The FIFOs are for software-fed audio data; they are not just two more ordinary channels. The hardware also includes capture units that can write selected mixer or channel output back to memory. These paths interact, but they have different controls, clocks, and DMA behavior.
For emulator work, the key question is ownership: which processor writes samples, which device consumes them, which timer sets the rate, what requests DMA, and where a capture result goes. If an implementation converts all of this to two host audio queues, it can miss the sixteen-channel mixer, capture routing, FIFO underflow, ARM7 timer interaction, and the guest-visible DMA request boundary.
Programmable channels are not PCM FIFOs
The sixteen channels have per-channel control and source state. Depending on the selected format, a channel can play PCM or compressed/sample-based data and has settings for volume, pan, repeat behavior, and a timer-derived playback rate. The sound register set is distinct from FIFO A/B’s write ports and control bits. A trace should identify channel number or FIFO stream explicitly.
Channel sample playback is an address-and-length operation. The start/source address, repeat point or length, format, and timer determine how the engine advances. A channel can end, loop, or hold according to its mode. Implementing a channel as a host decoder that reads an entire asset once will not preserve register changes made while it is running or the memory visibility rules at each fetch boundary.
FIFO A and FIFO B instead accept a stream of data written by the ARM7. Each FIFO has a small hardware queue, and a programmable sound control register selects its volume and output routing. When the queue reaches a low-water condition, a sound DMA request can refill it. This special request mechanism is the operational reason the FIFO is useful: CPU software can maintain a stream without polling each individual audio sample.
Underflow should be observable in the model even if the final audible symptom is a click or silence. A FIFO that is empty at a sample deadline cannot consume data that the host has already buffered but the guest has not yet written. Conversely, host audio callbacks should not change the FIFO’s guest-visible occupancy merely because they requested a larger output block.
DMA request and FIFO refill
The ARM7 configures a DMA channel for the FIFO destination and the sound-specific timing mode. The request cadence is driven by the device’s FIFO state, not by a general transfer-per-frame timer. A correct emulator ties FIFO threshold events to DMA eligibility, then transfers the programmed burst in the documented unit and decrements its programmed count according to DMA semantics.
Keep the following states separate: FIFO occupancy, current sound request, DMA channel enable and remaining count, source address, transfer progress, and output sample deadline. A missed refill can arise because DMA was not armed, the source pointer is wrong, the request was not raised, or the mixer consumed the FIFO too early. One generic “audio underrun” bit cannot distinguish these causes.
A software stream may mix with output from the sixteen channels. Final gain and routing follow the sound control registers and system path, so tests should observe the FIFO signal before mixing and the final master output after mixing. Do not add a host-side gain correction that conceals a wrong channel-volume field or route selection.
Capture is a path back into memory
The DS has two sound capture units. Their control registers choose a source such as a mixer output or an associated sound channel, select sample format and repeat/one-shot behavior, and connect the capture destination to memory through DMA. Capture frequency is coupled to the associated channel timer rather than provided by a wholly independent arbitrary-rate sampler. This coupling is important: changing a channel timer can change both playback and capture timing.
Capture should be modeled as a timed consumer of the selected mixer signal and a producer of memory writes. If the capture source is a channel rather than the full mixer, the tap point matters; downstream pan or master mix changes may not affect the captured data in the same way. A capture implementation that records the host speaker output cannot necessarily reproduce the hardware’s internal capture source.
The control bits that associate an extra channel with capture output also affect channel mixing behavior. Validate the combination of capture-enable and add-to-channel settings from the hardware reference; do not infer a valid mode by toggling one bit at a time in a host mixer. Tests should cover capture from each source, PCM8 and PCM16 output, one-shot completion, repeated capture, and DMA to an aligned buffer.
A FIFO ledger for repeatable tests
This Python model shows a bounded producer-consumer invariant. It is not a register-level DS implementation:
from collections import deque
class SoundFifo:
def __init__(self, capacity):
if capacity <= 0:
raise ValueError("capacity must be positive")
self.capacity = capacity
self.words = deque()
def write_word(self, value):
if len(self.words) == self.capacity:
return False
self.words.append(value & 0xFFFFFFFF)
return True
def consume_word(self):
if not self.words:
return None
return self.words.popleft()
The device’s actual threshold, burst, and overflow behavior must replace this simple capacity rule. Use it to test that a host callback cannot consume a sample twice or let the FIFO exceed its configured capacity.
Debugging by first divergence
Record ARM7 register writes, FIFO A/B occupancy, refill-request transitions, DMA source and count, each transfer completion, channel timer phase, capture enable/source/format, capture destination, and final mixer output. Use cycle timestamps. If audio becomes silent, determine whether the channel stopped, the FIFO emptied, the DMA request was masked, capture altered a route, or the host resampler lost phase.
Regression tests should include a FIFO held empty, one-word writes at the request boundary, simultaneous A/B refill, DMA disabled during a request, DMA source crossing an aligned boundary, capture started while playback runs, capture one-shot completion, and save-state restore with both data queued and a request pending. Verify that the same guest program produces the same FIFO request sequence at different host audio output rates.
For a capture integration test, feed a deterministic synthetic waveform through a known sound channel, capture it to memory, and compare the guest buffer bytes before host resampling. Then repeat with mixer capture and changed pan/volume settings to map the actual tap point. If exact hardware measurements are unavailable, label results as GBATEK conformance or emulator cross-check rather than hardware verification.
Acceptance criteria
A robust DS implementation separates programmable channels, FIFO A/B, special sound DMA requests, capture units, memory writes, and the final mixer. Each state transition is timestamped and testable. Save states preserve FIFO content, active DMA count and source, per-channel phase, capture position, and mixer history where necessary.
The most useful mental model is not “the DS has sixteen channels plus two queues.” It is a set of devices with distinct ownership and paths that converge at specific mixer points. Preserving those boundaries turns an audio symptom into a concrete queue, timer, route, or capture defect.
Clock domains and channel special cases
The DS sound registers expose timing through per-channel timer values, but the ARM7 FIFO streams and capture units are configured through their own control paths. Do not assume that a shared numeric timer value means all of these subsystems advance in the same phase. Keep a central emulated sound clock and explicit divider state for each channel/FIFO path, then schedule events at the granularity required by the chosen accuracy target.
The capture unit’s association with sound channels 1 and 3 is a hardware coupling that can affect the final mix. Capture-control bits can choose whether a corresponding channel is output as itself or added to a paired channel, and the manual warns that addition behavior depends on the relevant enable bits together. Encode such conditions directly in the mixer state machine and test all bit combinations, including invalid-looking combinations, rather than assuming a single “capture on” mode.
FIFO burst size and DMA programming also affect latency. A large refill burst reduces CPU overhead but may make a guest’s source pointer and remaining count visible at different times than many small writes. Test DMA completion and FIFO drain under simultaneous channel playback, and assert that no host sample is consumed before its guest-side word was transferred.
Capture checks beyond audio listening
Capture data offers a better low-level test than listening alone. With a deterministic channel or mixer signal, compare the memory bytes the capture engine writes, including PCM8 versus PCM16 sign and alignment behavior, destination progression, and one-shot stop. Then inspect whether the captured stream corresponds to the pre-pan or post-pan source selected by the control register. A successful sound recording at the host output can still hide a capture tap-point bug.
For evidence quality, retain the source-control value, associated channel timer, start/stop cycles, DMA buffer address, byte count, and raw captured bytes with each regression result. This metadata allows an independent reviewer to distinguish an incorrect register model from a bad waveform fixture or host audio configuration.
Related:
- Nintendo DS IPC FIFO and IPCSYNC: Reliable ARM7/ARM9 Messaging
- Game Boy Advance DMA: Channel Priority, Trigger Timing, and Sound FIFOs
Sources: