Skip to content

Latest commit

 

History

History
335 lines (258 loc) · 14.9 KB

hal2.md

File metadata and controls

335 lines (258 loc) · 14.9 KB

HAL2 Audio ASIC — Register Reference and Emulation Notes

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).


Physical Register Map

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.

Related Audio Devices on HPC3 PBUS PIO Channels 0–3

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.


ISR — Interrupt Status Register (0x10)

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

IAR — Indirect Address Register (0x30)

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)

Indirect Register Addresses

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

Bresenham Clock Generators

HAL2 has three identical Bresenham clock generators (BRES1, BRES2, BRES3). Each produces an audio sample-rate clock from a fixed master oscillator.

CTRL1 — Master clock select (IDR0)

Value Master clock
0 48 000 Hz
1 44 100 Hz
2 AES RX recovered clock (used for digital sync)

CTRL2 — Frequency divider (IDR0 = inc, IDR1 = modctrl)

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.


DMA Enable / Drive Registers

HAL2_DMA_ENABLE_W (0x9104)

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.

HAL2_DMA_DRIVE_W (0x910C)

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.


Codec Control Register 1 — Channel, Clock, Mode

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)

Channel mode field (bits 9:8)

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:

  1. 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.

  2. 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 what TestLine(QUAD) / thru -q use. In this mode the SetupDMAChannel code sets Codec A to stereo on channel N and Codec B to stereo on channel N+1; both codecs run simultaneously.


Audio Sample Format

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.


Memory Layout by Channel Mode

Mono (mode = 1)

Word:  [  M0  ]  [  M1  ]  [  M2  ]  ...

Each 32-bit word is a single mono sample. HAL2 feeds it to both Left and Right DACs.

Stereo (mode = 2)

Word:  [  L0  ]  [  R0  ]  [  L1  ]  [  R1  ]  ...

Interleaved left/right pairs. Most programs and the default thru mode use this.

Quad — single-stream (Codec A, mode = 3, ISR.CODEC_MODE = 0)

Word:  [ FL0 ]  [ FR0 ]  [ BL0 ]  [ BR0 ]  [ FL1 ]  ...

HAL2 routes FL/FR to Codec A and BL/BR to Codec B.

Dual-stream quad (ISR.CODEC_MODE = 1)

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.


Codec Control Register 2 and External MDAC Volume

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 ($-1.5,\text{dB}$/step), and the 8-bit VOLUME_LEFT / VOLUME_RIGHT MDAC registers when scaling Codec A output samples.


AES TX Control Register

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.


Emulator implementation (October 2026)

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 CTRL2 registers (_R0 at 0x..88 and _R1 at 0x..89) read back via IDR0.
  • Output volume combines the external 8-bit MDAC registers at 0x1FBD8800 (VOLUME_RIGHT) and 0x1FBD8804 (VOLUME_LEFT) with the CS4216 Codec A MUTE bit (IDR0 bit 10) and 5-bit LA/RA DAC attenuators (IDR1 bits 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] host picks the host audio system (on Linux ALSA by default, or cpal's own PulseAudio client, which PipeWire answers too), and cpal_buffer_frames the 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.

Remaining fidelity and validation

  • 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_rates uses 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.