← All posts
Devlog · Sergej Klementinovski

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.

The audio path, as it plays now

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 = 1 the PS offers dp_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_REF is 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

The runs

RunWhat changedPictureSoundINTR_STATUS bit 12
0First version: synchronous link clock (MISC0 0x21) with M/N and MAUD/NAUD written, audio soft reset pulsed, all eight info frame words written to 0x308yesnoneset again at once after the clear
1A/V buffer takes the live input, the TX’s audio offyesnone (expected)clear
4Info frame into its eight registers, 0x308 + 4nyesnoneset
5The A/V buffer’s audio test pattern instead of Ariayesnoneset
6Test pattern on the PS audio clock (24.242 MHz)yesnoneset
7Audio soft reset only cleared, never pulsedyesnoneset
8Run 7 and an asynchronous link clock (MISC0 0x20, no M/N writes)yesnoneset
9Run 8 with the test pattern; Aria not on the DPyesa 2 kHz toneclear
10Run 8 with Aria live; the flag cleared again 20 ms later; AV_BUF_AUDIO_CH_CONFIG 0 to 3 triedyesnoneclear from then on
11Run 10 with the TX set to one channelyesnoneclear
12Run 10 with the DP setting Aria’s paceyesnoneclear
FinalNew bitstream with TID per channel; the firmware’s settings of runs 8 to 10yesthe melodyset 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_STATUS bit 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. tready took 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.

One frame, two words

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:

  1. Bench. Our Verilator bench of the audio unit now checks the TID of every subframe.
  2. 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.
  3. 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.
  4. Timing. The bitstream met timing: WNS +0.596 ns, WHS +0.010 ns.
  5. Silent board check. Nobody listening: the picture’s frame checksum unchanged (0x057C4590), Aria 5,780,890 frames in 120 s, 0 underruns.
  6. 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 (MISC0 0x20, MAUD and NAUD read 0), the mixer at unity (0x20002000), AUD_SOFT_RST 0.

The settings that play, with register names as in AMD’s embeddedsw drivers (xdppsu, xavbuf):

RegisterAddressValue
BUF_OUTPUT_AUD_VID_SELECT0xFD4AB0700x0C: 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_SOURCE0xFD4AB120audio clock source bit (mask 0x2) = 0: the PL
AUD_SOFT_RST0xFD4ACC000 only, never 1
AUD_MIXER_VOLUME_CONTROL0xFD4AC0000x20002000: both gains 0x2000, unity (the reset value 0 mutes)
MAIN_STREAM_MISC00xFD4A01A40x20: bit 0 clear, an asynchronous clock; RGB, 8 bpc. M_VID, N_VID, TX_AUDIO_MAUD and TX_AUDIO_NAUD not written
TX_AUDIO_CONTROL0xFD4A03000 first (mute), 1 last (enable)
TX_AUDIO_INFO_DATA0-70xFD4A0308 + 4n0x441B8400 (type 0x84, length 0x1B, version 0x11), 0x00000001 (two channels; coding, rate and size from the stream header), then six zeros
TX_AUDIO_CHANNELS0xFD4A03041: two channels
INTR_STATUS0xFD4A03A0write 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

  1. Enable the PS’s live audio input (PSU__USE__AUDIO = 1).
  2. Send the channel in TID: 0 with subframe 1 (left), 1 with subframe 2 (right). The PS accepts a constant TID and plays nothing.
  3. Use a 24.576 MHz audio clock. If the PS’s DP_AUDIO_REF cannot be 24.576 MHz, make the clock in the PL, feed it to dp_s_axis_audio_clk and select the PL as the A/V buffer’s audio clock source.
  4. Run the link with an asynchronous clock (MAIN_STREAM_MISC0 bit 0 clear) and do not write M/N or MAUD/NAUD.
  5. Never pulse the audio soft reset; write it 0 only.
  6. Set both mixer gains to 0x2000. The register resets to 0, which mutes.
  7. 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.
  8. Prove the PS path with the A/V buffer’s audio test pattern first. Do not read INTR_STATUS bit 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) and zynqmp_disp_regs.h (ZYNQMP_DISP_AUD_MIXER_VOLUME_NO_SCALE, 0x2000).
  • AMD embeddedsw, the xdppsu and xavbuf drivers: register names and offsets; INTR_STATUS clears by writing ones.
  • AMD community forums: a thread on DisplayPort audio on the KV260 that played only once DP_AUDIO_REF was 24.576 MHz.
Older post Textured triangles at full frame rate
Follow Kromi One

We'll email you when the developer edition is public and when the crowdfunding campaign opens.

You'll get one email to confirm your address. Unsubscribe any time. See our privacy policy.

Thanks for signing up