SH-4 ↔ ARM: interrupts & mailboxes
The two processors can talk in two ways: by raising interrupts in each other through the AICA's interrupt controller, or by reading and writing shared sound RAM. The hardware supports both. Manatee uses interrupts only for its own clock and for MIDI input, and talks to the game entirely through a polled mailbox.
The interrupt hardware
The AICA's interrupt controller has a set of sources: external pins, MIDI input, MIDI output, the DMA engine, the three timers, a once-per-sample tick, and one special source meaning "the other CPU wants your attention". For each target CPU every source has three bits: enable (may this source interrupt me?), pending (has it fired?), and reset (acknowledge it).
Levels and FIQ
On the ARM side each source is given a priority level from 0 to 7 by three bit-plane registers. When any enabled source is pending, the AICA asserts the ARM's FIQ and reports the level of the winning source in a status register. The ARM's normal IRQ input is never used by the AICA, so FIQ is the only interrupt the sound CPU ever sees.
Anatomy of Manatee's FIQ handler
- EntryThe CPU switches to FIQ mode: it gets private copies of registers r8-r14, saves the
interrupted status, and jumps to the vector at address
0x1C, which branches to the handler. - Own stackThe handler points its stack at a 96-byte area inside the driver image itself and saves its return address there. Everything it needs fits in the banked registers, so timer interrupts save nothing else.
- Dispatch by levelIt reads the level register and jumps through an 8-entry table. Only level 2 (timer) and level 5 (MIDI) do anything; any other level is treated as spurious.
- Timer pathReload Timer A, acknowledge it, raise the 1 ms flag, and every fourth (corrected, see below) tick raise the 4 ms flag and bump the "execute counter" the SH-4 can read.
- MIDI pathSave r0-r7 (the parser is ordinary code), then drain the MIDI FIFO through a running-status parser and push complete messages into the driver's event ring as "port 7".
- ExitWrite the interrupt-clear register four times (the chip needs repeated writes to reliably drop the line) and return, restoring the interrupted mode.
All real work happens in the main loop. The timer interrupt only sets two bytes; the main loop sees them and runs the 1 ms or 4 ms work. This keeps the interrupt short and means almost no data is shared between interrupt and main code. The one exception is the MIDI input path, which appends to the same event ring the main loop's producers use, without locking; harmless on a retail console, where nothing is plugged into the MIDI port.
The heartbeat: an exact 4 ms clock
Timer A counts output samples at 44.1 kHz. Manatee reloads it so that it overflows every 44 samples: 0.9977 ms, slightly shorter than a millisecond. Musical timing is built on a 4 ms tick, so the error would accumulate. The fix is neat: one interrupt in every 441 is not counted towards the 4 ms clock.
The mailbox: how the game talks to the driver
Although the hardware offers a doorbell interrupt, Sega's library and Manatee communicate purely through shared sound RAM, polled from both sides. There are four channels:
Commands
32 slots of 16 bytes plus a "batch pending" flag byte. Play, stop, volume, pitch, fades, effects…
Bank table
Address and size of every downloaded bank. Writing an entry is the download notification.
Status records
32 bytes per port: playing/paused/fading flags, volume, pan, pitch, speed, position, errors. Rewritten every 4 ms.
System block
The execute counter, the MIDI timing counter, the driver error word, the current effect program.
Details worth knowing
- Batches, not a stream. The library keeps a 256-entry queue on the SH-4 side and moves up to 32 commands per frame. The driver always scans all 32 slots and runs every non-empty one in slot order; there is no count field.
- The pending flag is the handshake. The SH-4 sets it; only the ARM clears it. If the ARM has not yet consumed a batch, the SH-4 reports "driver busy" and tries again next frame.
- Command codes are regular. The high nibble picks the family (MIDI ports, one-shot ports, stream ports, CD audio, global) and the low nibble the operation (play, stop, pause, continue, volume, pan, speed, pitch, effect send, direct level, reset…), so the same operation has the same low nibble in every family.
- Fades are driver-side. A volume command carries a target and a fade time; the driver converts that into a step and an interval and runs the fade on its 4 ms tick.
- A debug log. Every executed command is copied into a 128-entry history ring in sound RAM, together with the number of ticks since the previous one. Nothing on the SH-4 reads it; it was presumably for Sega's tools.
Boot handshake
- Hold the ARMThe SH-4 asserts the ARM's reset.
- Prepare sound RAMIt fills all 2 MB with the word "SEGA" and copies the driver image (vectors, code, built-in data) to address 0.
- ReleaseIt releases reset and simply waits a fixed time; there is no explicit acknowledgement. It then reads the driver's version bytes and its table of work-area addresses.
- Driver start-upThe ARM writes 0xFF to its status word, silences all 64 channels, clears its work RAM, installs its built-in banks, starts Timer A, writes "SEGA" to the status word and enables FIQ.
To give the driver a bank, the SH-4 copies it into free sound RAM (by DMA or CPU) and writes its address and size into the bank table. The driver only looks at the table when a command uses that bank, and validates the bank then (tag, version and end marker). The SH-4 is entirely responsible for sound-RAM allocation above the driver's own work area.