The HAL2 (High-bandwidth Audio/Video Link 2) is the audio ASIC in the SGI Indy.
It is controlled entirely through indirect registers: values are staged in one or
more Indirect Data Registers (IDR0–IDR3) and then committed by writing the target
register address to the Indirect Address Register (IAR).
| Offset | Name | Description |
|---|---|---|
0x10 |
ISR |
Interrupt / global control register |
0x20 |
REV |
Chip revision (read-only, → 0x4010) |
0x30 |
IAR |
Indirect address register |
0x40 |
IDR0 |
Indirect data register 0 |
0x50 |
IDR1 |
Indirect data register 1 |
0x60 |
IDR2 |
Indirect data register 2 |
0x70 |
IDR3 |
Indirect data register 3 |
Base physical address: 0x1FBD8000 (HPC3 PBUS PIO Channel 0). All accesses are 32-bit or 16-bit.
| PBUS PIO Ch | Physical Base | Description |
|---|---|---|
| Channel 0 | 0x1FBD8000 |
HAL2 ASIC direct registers (ISR, REV, IAR, IDR0–IDR3) |
| Channel 1 | 0x1FBD8400 |
Crystal CS8411 AES/EBU Receiver (0x1FBD8400–0x1FBD847C) and Crystal CS8401A AES/EBU Transmitter (0x1FBD8480–0x1FBD84FC) |
| Channel 2 | 0x1FBD8800 |
8-Bit Multiplying DAC (MDAC) headphone/speaker volume (0x1FBD8800 = VOLUME_RIGHT, 0x1FBD8804 = VOLUME_LEFT) |
| Channel 3 | 0x1FBD8C00 |
Synth Reset / Page Select (0x1FBD8C00, Indigo2 MAUI synthesizer option) |
See cs4216.md for the Crystal CS4216 stereo audio codec, Serial Mode 3 frame format, and the two-stage output volume architecture.
| Bit | Name | Active | Effect |
|---|---|---|---|
| 2 | CODEC_MODE |
high | 0 = Indigo mode (default); 1 = Quad mode (two independent stereo pairs) |
| 3 | GLOBAL_RESET_N |
low | Writing 0 resets the entire HAL2 chip |
| 4 | CODEC_RESET_N |
low | Writing 0 resets only the codec/synth section |
Reset sequence (from SetupHAL2 in a2diags.c):
ISR ← 0x00 (assert global reset)
~1 ms delay
ISR ← 0x18 (release global + codec reset)
~70 µs delay
The IAR word encodes both the target register group and the read/write direction.
Bit 7: 1 = read from indirect register → IDR, 0 = write IDR → indirect register
Bits 15:12 + 11:8: register group and index (see table below)
Bits 3:2: parameter selector within the group (1 = control word 1, 2 = control word 2)
| IAR value | Symbol | IDR words used | Description |
|---|---|---|---|
0x9104 |
HAL2_DMA_ENABLE_W |
IDR0 | DMA enable bitmask (write) |
0x9184 |
HAL2_DMA_ENABLE_R |
IDR0 | DMA enable bitmask (read-back) |
0x910C |
HAL2_DMA_DRIVE_W |
IDR0 | DMA drive bitmask (write) |
0x918C |
HAL2_DMA_DRIVE_R |
IDR0 | DMA drive bitmask (read-back) |
0x9108 |
HAL2_DMA_ENDIAN_W |
IDR0 | DMA endian (0 = big-endian) |
0x1404 |
HAL2_CODECA_CTRL1_W |
IDR0 | Codec A channel / clock / mode (write) |
0x1484 |
HAL2_CODECA_CTRL1_R |
IDR0 | Codec A channel / clock / mode (read) |
0x1408 |
HAL2_CODECA_CTRL2_W |
IDR0,IDR1 | Codec A gain / attenuation / mute (write) |
0x1488 |
HAL2_CODECA_CTRL2_R0 |
IDR0 | Codec A CTRL2 word 0 read-back into IDR0 |
0x1489 |
HAL2_CODECA_CTRL2_R1 |
IDR0 | Codec A CTRL2 word 1 read-back into IDR0 |
0x1504 |
HAL2_CODECB_CTRL1_W |
IDR0 | Codec B channel / clock / nonaudio (write) |
0x1584 |
HAL2_CODECB_CTRL1_R |
IDR0 | Codec B channel / clock / nonaudio (read) |
0x1508 |
HAL2_CODECB_CTRL2_W |
IDR0,IDR1 | Codec B gain / attenuation / mute (write) |
0x1588 |
HAL2_CODECB_CTRL2_R0 |
IDR0 | Codec B CTRL2 word 0 read-back into IDR0 |
0x1589 |
HAL2_CODECB_CTRL2_R1 |
IDR0 | Codec B CTRL2 word 1 read-back into IDR0 |
0x1304 |
HAL2_AESTX_CTRL_W |
IDR0 | AES TX channel / clock / mode |
0x1204 |
HAL2_AESRX_CTRL_W |
IDR0 | AES RX channel / clock |
0x2104 |
HAL2_BRES1_CTRL1_W |
IDR0 | BRES 1 master clock select |
0x2108 |
HAL2_BRES1_CTRL2_W |
IDR0,IDR1 | BRES 1 inc / modctrl (write) |
0x2188 |
HAL2_BRES1_CTRL2_R0 |
IDR0 | BRES 1 inc read-back into IDR0 |
0x2189 |
HAL2_BRES1_CTRL2_R1 |
IDR0 | BRES 1 modctrl read-back into IDR0 |
0x2204 |
HAL2_BRES2_CTRL1_W |
IDR0 | BRES 2 master clock select |
0x2208 |
HAL2_BRES2_CTRL2_W |
IDR0,IDR1 | BRES 2 inc / modctrl (write) |
0x2288 |
HAL2_BRES2_CTRL2_R0 |
IDR0 | BRES 2 inc read-back into IDR0 |
0x2289 |
HAL2_BRES2_CTRL2_R1 |
IDR0 | BRES 2 modctrl read-back into IDR0 |
0x2304 |
HAL2_BRES3_CTRL1_W |
IDR0 | BRES 3 master clock select |
0x2308 |
HAL2_BRES3_CTRL2_W |
IDR0,IDR1 | BRES 3 inc / modctrl (write) |
0x2388 |
HAL2_BRES3_CTRL2_R0 |
IDR0 | BRES 3 inc read-back into IDR0 |
0x2389 |
HAL2_BRES3_CTRL2_R1 |
IDR0 | BRES 3 modctrl read-back into IDR0 |
0x9100 |
HAL2_RELAY_CONTROL_W |
IDR0 | Headphone relay (1 = headphone out) |
0x9180 |
HAL2_RELAY_CONTROL_R |
IDR0 | Headphone relay read-back |
HAL2 has three identical Bresenham clock generators (BRES1, BRES2, BRES3).
Each produces an audio sample-rate clock from a fixed master oscillator.
| Value | Master clock |
|---|---|
0 |
48 000 Hz |
1 |
44 100 Hz |
2 |
AES RX recovered clock (used for digital sync) |
Output frequency = master_clock × inc / mod
modctrl = (inc − mod − 1) & 0xFFFF ← what is actually written to IDR1
Common settings (48 kHz master):
| Rate | inc | mod | modctrl |
|---|---|---|---|
| 48000 | 4 | 4 | 0xFFFF (−1) |
| 32000 | 4 | 6 | 0xFFF9 |
| 16000 | 4 | 12 | 0xFFF3 |
| 8000 | 4 | 24 | 0xFFE7 |
Common setting (44.1 kHz master):
| Rate | inc | mod | modctrl |
|---|---|---|---|
| 44100 | 1 | 1 | 0xFFFF (−1) |
Note: the diags use inc=4, mod=4 (not inc=1, mod=1) as their 48 kHz default,
giving modctrl = 4 − 4 − 1 = −1 = 0xFFFF. Both produce the same ratio.
One-bit-per-engine enable mask written to IDR0:
| Bit | Engine |
|---|---|
| 1 | AES RX |
| 2 | AES TX (⚠ 0x04, not 0x02; AESTX is bit 2) |
| 3 | Codec A (0x08) |
| 4 | Codec B (0x10) |
The diags read-modify-write this register (HAL2_DMA_ENABLE_R then HAL2_DMA_ENABLE_W)
so bits for other engines are preserved. The emulator must store this register and
return it on IAR reads.
One-bit-per-HPC3-channel mask written to IDR0. Bit N = drive enable for HPC3 PDMA channel N. Also read-modified-written by the driver.
Written to IDR0 then strobed via HAL2_CODECA_CTRL1_W / HAL2_CODECB_CTRL1_W.
Bits 2:0 — HPC3 PDMA channel number (0–7) that feeds this codec
Bits 4:3 — Bresenham clock ID: 1=BRES1, 2=BRES2, 3=BRES3 (0 = none)
Bits 9:8 — Channel mode (see below)
Bit 10 — Timestamp enable (used by clock-test diagnostics)
| Value | Mode | Channels consumed per sample period | Notes |
|---|---|---|---|
0 |
— | (undefined / reset state) | |
1 |
Mono | 1 × 32-bit word | HAL2 duplicates it to both L and R outputs |
2 |
Stereo | 2 × 32-bit words: [L, R] | Most common mode |
3 |
Quad | Codec A only; see below |
Quad mode (CHANNEL_MODE = 3) — two flavours:
-
Single-stream quad (
ISR.CODEC_MODE = 0): Codec A receives one DMA stream of 4-word frames[FL, FR, BL, BR]. HAL2 sends FL/FR to Codec A DACs and BL/BR to Codec B DACs internally. Only one HPC3 DMA channel is needed. -
Dual-stream quad (
ISR.CODEC_MODE = 1): Codec A and Codec B are independent stereo devices each with their own DMA channel (set via CTRL1 on each codec separately). This is whatTestLine(QUAD)/thru -quse. In this mode theSetupDMAChannelcode sets Codec A to stereo on channel N and Codec B to stereo on channel N+1; both codecs run simultaneously.
HAL2 DMA transfers 32-bit words. The audio data is 24-bit signed PCM, left-justified:
31 8 7 0
[ 24-bit PCM | 00000000 ]
In the diagnostics this is constructed as sintab_value << 8.
The HPC3 PDMA layer on the Indy delivers 16-bit values to the HAL2 (it strips the
low 8 padding bits in 16-bit PBUS mode), so the emulator's DmaClient::read()
returns (word >> 8) as u16, ready for use as i16. In 8-bit PBUS mode it
returns the word's top byte.
Word: [ M0 ] [ M1 ] [ M2 ] ...
Each 32-bit word is a single mono sample. HAL2 feeds it to both Left and Right DACs.
Word: [ L0 ] [ R0 ] [ L1 ] [ R1 ] ...
Interleaved left/right pairs. Most programs and the default thru mode use this.
Word: [ FL0 ] [ FR0 ] [ BL0 ] [ BR0 ] [ FL1 ] ...
HAL2 routes FL/FR to Codec A and BL/BR to Codec B.
Codec A DMA: [ L0 ] [ R0 ] [ L1 ] [ R1 ] ... (stereo, mode=2)
Codec B DMA: [ L0 ] [ R0 ] [ L1 ] [ R1 ] ... (stereo, mode=2)
Two completely independent DMA streams, one per codec.
Written as IDR0 = ctrl2_word_0, IDR1 = ctrl2_word_1 to HAL2_CODECA_CTRL2_W
(0x1408) or HAL2_CODECB_CTRL2_W (0x1508). These 28 bits are transmitted
in-band to the Crystal CS4216 codec over its Serial Mode 3 TDM frame (see
cs4216.md):
IDR0 bit 10 — MUTE (1 = mute DAC outputs, 0 = unmuted)
IDR0 bits 9:8 — Input source select (0 = line1, 1 = synth, 3 = mic)
IDR0 bits 7:4 — Left channel ADC input gain (0–15, 1.5 dB/step)
IDR0 bits 3:0 — Right channel ADC input gain (0–15, 1.5 dB/step)
IDR1 bits 11:7 — Left DAC output attenuation (0–31, -1.5 dB/step, 0 = 0 dB)
IDR1 bits 6:2 — Right DAC output attenuation (0–31, -1.5 dB/step, 0 = 0 dB)
IDR1 bits 1:0 — Digital output pins DO2:DO1 (mic preamp & phantom bias)
0x03 = line input (1x preamp, mic bias off)
0x01 = mic input (1x preamp, mic bias on)
0x00 = mic input with 10x (+20 dB) preamp boost
On the Indy and Indigo2, speaker and headphone output volume is controlled by an
external 8-bit Multiplying DAC (MDAC) on HPC3 PBUS PIO Channel 2 at
0x1FBD8800 (VOLUME_RIGHT) and 0x1FBD8804 (VOLUME_LEFT), where 0x00 is
muted and 0xFF is full volume. The IRIX audio driver (a2_dd.o), the PROM
startup tune, and NetBSD (haltwo) adjust speaker/headphone volume by writing
to VOLUME_LEFT and VOLUME_RIGHT, while Linux (hal2) and ARCS diagnostics
also use the CS4216 CTRL2 LA/RA attenuators and MUTE bit.
The emulator combines the CS4216 MUTE bit, the 5-bit LA/RA DAC
attenuators (VOLUME_LEFT /
VOLUME_RIGHT MDAC registers when scaling Codec A output samples.
Written as a single IDR0 value, same bitfield layout as Codec CTRL1:
Bits 2:0 — HPC3 DMA channel
Bits 4:3 — Bresenham clock ID
Bits 9:8 — Channel mode (2 = stereo, always used in practice)
Example from Test_Rx_Clock: IDR0 = 0x213 → channel 3, BRES clock 2, stereo.
src/dev/hal2.rs uses the shared high-precision timer manager. Codec A is
paced from host elapsed time in 250 µs wakeups with bounded catch-up, rather
than one fixed DMA burst per wakeup. DMA demand tracks the guest's polls to
avoid advancing into descriptors the guest has not prepared.
- ISR reset and IAR decoding cover Codec A/B, AES TX/RX, BRES 1–3, and global
DMA controls. Enable/drive/endian/relay values and both words of two-word
CTRL2registers (_R0at0x..88and_R1at0x..89) read back viaIDR0. - Output volume combines the external 8-bit MDAC registers at
0x1FBD8800(VOLUME_RIGHT) and0x1FBD8804(VOLUME_LEFT) with the CS4216 Codec AMUTEbit (IDR0bit 10) and 5-bitLA/RADAC attenuators (IDR1bits 11:7 and 6:2,$-1.5,\text{dB}$ per step). - BRES rate is derived from its selected master, increment, and modulus. Codec CLKID is the generator number 1–3, not a zero-based index.
- Codec A keeps a host cpal stream open across enable/disable cycles and
resamples guest-rate stereo samples to the host output rate.
[audio] hostpicks the host audio system (on Linux ALSA by default, or cpal's own PulseAudio client, which PipeWire answers too), andcpal_buffer_framesthe host buffer size; PulseAudio, whose default target is about two seconds, gets 512 frames unless told otherwise. - Mono is duplicated to L/R. Stereo consumes interleaved pairs. Single-stream quad consumes all four samples but discards the rear pair.
- Codec B is an input DMA silence writer, not a second host output stream.
- AES TX drains DMA into an internal queue; AES RX writes that loopback data (or silence) to DMA. This is not host digital audio I/O.
- Host capture and rear-channel/dual-stream quad output are not implemented.
- DMA endian is stored/read back; the current sample path assumes big-endian.
- Recovered AES master-clock locking is not modeled;
update_ratesuses the 48 kHz/44.1 kHz masters and defaults other selectors to 48 kHz. - Timestamp mode does not interleave clock-test timestamps into DMA.
- End-to-end rate changes, underruns, reboot/stop/start, and host-device changes need recorded audio checks on supported platforms; register readback alone does not prove playback fidelity. See TODO.md.