Sound through DisplayPort, after a night of silence
Our audio unit streamed into the KV260's DisplayPort controller, and for a night the monitor stayed silent. Notes for engineers: the runs, the signals that misled us, and the one sideband signal that fixed it.
Notes for engineers: what we tried, in what order, and what finally made the monitor play.

Summary
Aria, the audio unit of our chipset, streams live audio from the PL of the KV260’s Zynq UltraScale+ into the PS’s DisplayPort controller. For a night the monitor stayed silent, although the picture was fine, the controller took every word of the stream and its registers read back as written. The cause was one sideband signal: Aria sent TID 0 with both subframes of a frame, and UG1085 Table 33-6 calls TID the “audio channel ID”. With TID 0 on the left subframe and 1 on the right, the monitor played a melody the next morning.
The setup
- Aria, in the PL. 48 kHz stereo, 16-bit samples, one frame stream for two outputs: I2S and the DP’s live audio input. On the KV260 the I2S output drives no pins yet; it sets the pace, one frame every 512 audio clocks, and the DP stream mirrors every frame, silence included.
- The stream. With
PSU__USE__AUDIO = 1the PS offersdp_s_axis_audio_{clk, tdata[31:0], tid, tvalid, tready}. A frame is two 32-bit words in the format of UG1085 Table 33-6: subframe 1 carries the left sample, subframe 2 the right. - The clocks. Aria’s audio clock is 24.576 MHz (512 × 48 kHz) from a PL MMCM; we measured 24,576,025 Hz. It clocks the stream port and is the A/V buffer’s audio clock. The PS’s own
DP_AUDIO_REFis 24.242 MHz on our board (RPLL / 88), and the RPLL also clocks the PL’s reference clocks, the R5s, SDIO0 and SPI0, so we cannot retune it. - The PS path. A/V buffer, audio mixer, DP TX; the link trains at 2.7 Gb/s. KROM, our bare-metal firmware, sets every register itself.
- The listener. A 4K DisplayPort monitor that plays DP audio from other machines every day: speakers on its headphone jack, later headphones.
What we saw
Unattended board checks came first: the link trained, the A/V buffer took audio from the live input, Aria streamed with 0 underruns, and the picture’s frame checksum stayed the same throughout. Two registers stood out. The audio mixer read 0, muted, until we wrote its gains. And the TX’s live-audio-buffer underflow flag, INTR_STATUS bit 12 (LIV_ABUF_UNDRFLW), was set again at once after we cleared it.
Then came the listening runs, one of us at the monitor. Each run loaded the bitstream and firmware over JTAG, ran the firmware for 120 s with Aria playing a test tone (750 Hz left, 375 Hz right, triangle waves of amplitude 8,192), and read the TX’s and the A/V buffer’s registers over JTAG. The picture was there every time. The sound was not.
How we narrowed it down

| Run | What changed | Picture | Sound | INTR_STATUS bit 12 |
|---|---|---|---|---|
| 0 | First version: synchronous link clock (MISC0 0x21) with M/N and MAUD/NAUD written, audio soft reset pulsed, all eight info frame words written to 0x308 | yes | none | set again at once after the clear |
| 1 | A/V buffer takes the live input, the TX’s audio off | yes | none (expected) | clear |
| 4 | Info frame into its eight registers, 0x308 + 4n | yes | none | set |
| 5 | The A/V buffer’s audio test pattern instead of Aria | yes | none | set |
| 6 | Test pattern on the PS audio clock (24.242 MHz) | yes | none | set |
| 7 | Audio soft reset only cleared, never pulsed | yes | none | set |
| 8 | Run 7 and an asynchronous link clock (MISC0 0x20, no M/N writes) | yes | none | set |
| 9 | Run 8 with the test pattern; Aria not on the DP | yes | a 2 kHz tone | clear |
| 10 | Run 8 with Aria live; the flag cleared again 20 ms later; AV_BUF_AUDIO_CH_CONFIG 0 to 3 tried | yes | none | clear from then on |
| 11 | Run 10 with the TX set to one channel | yes | none | clear |
| 12 | Run 10 with the DP setting Aria’s pace | yes | none | clear |
| Final | New bitstream with TID per channel; the firmware’s settings of runs 8 to 10 | yes | the melody | set again by the end of the run |
Run labels follow our log; runs 2 and 3 were never run.
Runs 4, 7 and 8 came from reading UG1085 and Linux’s ZynqMP DisplayPort audio driver (zynqmp_dp_audio.c). The info frame has eight registers, and our first version had written all eight words to the first. Linux never asserts the audio soft reset (“Reset doesn’t work”, says its comment), runs the link asynchronously and writes no info frame; UG1085 has software write MAUD and NAUD only with synchronous clocking. Runs 5 and 6 replaced Aria with the A/V buffer’s own test pattern, which stayed silent on either clock.
Run 9 was the turning point: with the soft reset never pulsed, the link asynchronous and the PL’s audio clock, the PS’s own test pattern came through the monitor. We recorded what the monitor played. The recording of run 9 holds a single peak at 2,000 Hz (Goertzel magnitude 185, neighbouring frequencies at most 3.3), and its RMS level is 336 against 27 in the recording of run 4. So the PS’s path to the monitor works. We changed the reset and the link clock together, so we cannot say which of the two was needed; Linux does both, and we kept both.
One of us found a thread on AMD’s community forums about a KV260 whose DP audio played only once DP_AUDIO_REF was exactly 24.576 MHz. Ours is 24.242 MHz, which is why the PS clock cannot serve 48 kHz on our board and the PL’s 24.576 MHz must, as in run 9. The thread pointed us at the clocks. It did not explain the silence that followed.
Runs 10 to 12 put Aria back on the DP with everything that made the pattern play, and the monitor stayed silent. Run 12 is the decisive one. We let the DP’s tready set Aria’s pace: the PS took every frame at 48 kHz (5,771,901 in the run), with the flag clear, and played none of them. The TX’s settings read back as written: two channels, AUDIO_CONTROL 1, the mixer at 0x20002000, AV_BUF_OUTPUT 0x0C (audio stream 1 from the live input), the audio clock from the PL.
The misleading signals
- The flag.
INTR_STATUSbit 12 latches while the TX’s audio is enabled and its input does not yet run. Cleared too early, it is set again. In runs 10 to 12 it was clear and nothing played; at the end of the final run, the one that played, it was set again. Neither state tells whether anything is heard. (Why it latched again in the final run we have not looked into: Aria counted 0 underruns.) - Accepted words and read-back.
treadytook Aria’s words at the right rate, nothing stalled, and every register held what we wrote. The PS accepts words it will not play. - The clock. The right audio clock was part of the answer, and the forum thread confirmed which one to use. It did not make our stream play.
- A false “no picture”. Our firmware starts about 2 min 35 s after the board session’s firmware step begins (the loader’s clean phase and staging), not after 20 to 30 s. In one attended run we called “no picture” before our firmware had even started. From then on we read the start time from the log and told the listener the exact time. When someone has to listen or look, give them the time from the log, not an estimate.
The cause
After run 12 the only difference between what played (the PS’s own pattern) and what did not (Aria’s stream) was the stream’s content. Aria built each word as Table 33-6 lays it out: preamble, sample, V, U, C and P. But it tied TID to 0 for both words. Table 33-6 calls TID the “audio channel ID”. With TID 0 on both subframes, the PS took every word and played nothing.

What the TX does inside with two words marked channel 0, whether it drops them or mutes them, we do not know.
The fix and how we verified it
The fix is in the RTL: TID 0 with subframe 1 (left), TID 1 with subframe 2 (right). Before it reached the monitor:
- Bench. Our Verilator bench of the audio unit now checks the TID of every subframe.
- Mutant. A deliberate fault, the right sample marked as channel 0, fails the bench. All 13 of the unit’s mutants are caught; lint passes.
- Netlist. Vivado’s synthesized netlist of the audio unit, replayed against the bench’s recorded ports: 14.4 million clock edges, 0 mismatches, in three of the bench’s modes. The unit has two clocks (core and audio), so we extended our replay tool: it records every rising edge of any input clock with the clock’s index and replays the edges in that order.
- Timing. The bitstream met timing: WNS +0.596 ns, WHS +0.010 ns.
- Silent board check. Nobody listening: the picture’s frame checksum unchanged (0x057C4590), Aria 5,780,890 frames in 120 s, 0 underruns.
- Listening. On the morning of 16 October, with the firmware’s settings of runs 8 to 10, Aria played a phrase of “Ode to Joy” as triangle waves, and the monitor played it. Aria: 5,778,992 frames in the 120-second run, 0 underruns. The TX asynchronous (
MISC00x20, MAUD and NAUD read 0), the mixer at unity (0x20002000),AUD_SOFT_RST0.
The settings that play, with register names as in AMD’s embeddedsw drivers (xdppsu, xavbuf):
| Register | Address | Value |
|---|---|---|
BUF_OUTPUT_AUD_VID_SELECT | 0xFD4AB070 | 0x0C: video 1 live, video 2 none, audio stream 1 from the live input (field mask 0x30; 0x20 the test pattern, 0x30 off) |
BUF_AUD_VID_CLK_SOURCE | 0xFD4AB120 | audio clock source bit (mask 0x2) = 0: the PL |
AUD_SOFT_RST | 0xFD4ACC00 | 0 only, never 1 |
AUD_MIXER_VOLUME_CONTROL | 0xFD4AC000 | 0x20002000: both gains 0x2000, unity (the reset value 0 mutes) |
MAIN_STREAM_MISC0 | 0xFD4A01A4 | 0x20: bit 0 clear, an asynchronous clock; RGB, 8 bpc. M_VID, N_VID, TX_AUDIO_MAUD and TX_AUDIO_NAUD not written |
TX_AUDIO_CONTROL | 0xFD4A0300 | 0 first (mute), 1 last (enable) |
TX_AUDIO_INFO_DATA0-7 | 0xFD4A0308 + 4n | 0x441B8400 (type 0x84, length 0x1B, version 0x11), 0x00000001 (two channels; coding, rate and size from the stream header), then six zeros |
TX_AUDIO_CHANNELS | 0xFD4A0304 | 1: two channels |
INTR_STATUS | 0xFD4A03A0 | write 0x1000 once the stream runs, to clear bit 12 (a diagnostic only) |
The order: the A/V buffer’s output and clock source, the audio soft reset written 0, the mixer’s gains; then the main stream; then the TX’s audio in UG1085’s order (mute, the info frame, the channels, enable); then the PL stream.
Checklist for others
- Enable the PS’s live audio input (
PSU__USE__AUDIO = 1). - Send the channel in TID: 0 with subframe 1 (left), 1 with subframe 2 (right). The PS accepts a constant TID and plays nothing.
- Use a 24.576 MHz audio clock. If the PS’s
DP_AUDIO_REFcannot be 24.576 MHz, make the clock in the PL, feed it todp_s_axis_audio_clkand select the PL as the A/V buffer’s audio clock source. - Run the link with an asynchronous clock (
MAIN_STREAM_MISC0bit 0 clear) and do not write M/N or MAUD/NAUD. - Never pulse the audio soft reset; write it 0 only.
- Set both mixer gains to 0x2000. The register resets to 0, which mutes.
- If you write the audio info frame, give each of its eight words its own register (0x308 + 4n). UG1085 lists “Information frame is not supported”, Linux writes none, and we have not tested without it.
- Prove the PS path with the A/V buffer’s audio test pattern first. Do not read
INTR_STATUSbit 12 as sound or silence, and record what the monitor plays.
References
- AMD UG1085, Zynq UltraScale+ Device Technical Reference Manual, chapter 33, the DisplayPort controller: Table 33-6 (the live audio subframes; TID, the “audio channel ID”), the order of its audio management, and its list of limits (“Information frame is not supported”).
- Linux, the ZynqMP DisplayPort driver:
zynqmp_dp_audio.c(the soft reset, the asynchronous link, no info frame) andzynqmp_disp_regs.h(ZYNQMP_DISP_AUD_MIXER_VOLUME_NO_SCALE, 0x2000). - AMD embeddedsw, the xdppsu and xavbuf drivers: register names and offsets;
INTR_STATUSclears by writing ones. - AMD community forums: a thread on DisplayPort audio on the KV260 that played only once
DP_AUDIO_REFwas 24.576 MHz.