Where things stand
I'm building AuraScope in public, step by step, and this page is where I'll keep an honest running account of what's actually done versus what's still ahead. Green means built, verified, and tagged. Grey means planned but not started. Nothing here is aspirational — if it's marked done, I've run it myself and checked the output.
The hardware, first
AuraScope's audio path doesn't actually need any FPGA fabric — that part runs over USB. But the very first thing I wanted working on the board was an LED that reflects whether audio is playing, and the Arty Z7-20's LEDs are wired to the fabric, not directly to the processor. So before Linux, before any driver code, I needed a real hardware design.
Defining the hardware in Vivado
I built the block design — the Zynq processor plus GPIO peripherals for the LED and buttons — generated the HDL wrapper, and wrote the constraints. When I went to export the hardware, Vivado refused: "cannot write hardware definition file — no generated IPI blocks." My first instinct was that I'd typed the export command wrong. I hadn't. Checking the project's actual run status told the real story — nothing had ever been synthesized. The wrapper and constraints existed, the project looked finished, and none of it had actually been built yet. I launched synthesis and implementation properly and got a real bitstream.
Before writing any device-tree code, I had a plan in my head for how the LEDs were wired: through EMIO, the standard PS-to-fabric GPIO route. Reasonable assumption. Instead of building on it, I unzipped the exported hardware file and checked. Vivado's own automation hadn't used EMIO at all — it had used two separate AXI GPIO cores, a different, equally valid approach, sitting at addresses I could read directly out of the file. Nothing was broken. My assumption just wasn't what got built, and I caught it before it cost me anything downstream.
What I ended up with: a verified .xsa — a real
bitstream, real hardware handoff data, real processor-init data, all
confirmed present rather than assumed.
Getting Yocto to consume it
Yocto setup — and a real dependency maze
This is the part that took longest, and it wasn't a single bug — it was a wrong turn at the tooling level. I matched my Yocto release to a Xilinx layer branch (confirmed against what each layer actually declares, not just what a README claims), and started registering layers. Each one kept surfacing a dependency I hadn't added yet, which is normal for a real board support package — until I followed a deprecated layer's own advice toward what's now AMD's recommended migration path, and watched it cascade four layers deep into virtualization, TPM, and security infrastructure that has nothing to do with getting Linux to boot on a small board with two GPIO cores.
I stepped back. Rather than keep pulling in unrelated infrastructure
because a deprecated README pointed there, I checked whether the
older, simpler tool for consuming a .xsa directly was
still around. It was — deprecated, but fully functional. After
tracking down one missing dependency (a submodule that needed
initializing) and one confusing tool-path issue (an argument that
wanted an installation root, not the binary path I'd correctly
located), it worked: a real Yocto machine, generated directly from
my own verified hardware.
The wireless audio source
nRF5340 Auracast bring-up
Before any of the Zynq work makes sense, there needs to be something to receive. I adapted Nordic's official nRF Audio application to a custom nRF5340 board — gateway broadcasting LC3 over Auracast (BIS), headset receiving and presenting the decoded audio as a USB sound card. Nine real bugs fixed along the way, from toolchain mismatches to a mono hardcode in the decode path that silently dropped one stereo channel. Custom board files at github.com/a-teresa/broadcast.
System architecture
Component overview — dashed boxes are planned (WIP).
Signal flow
Step-by-step signal path from I2S microphone to ALSA on the Linux host.
The gateway starts broadcasting two LC3 streams at 48kHz, 96kbps — one per stereo channel:
The headset syncs to both BIS streams (bitfield 0x0003) and the ISO RX SDU counters show zero bad packets:
The nRF5340 enumerates on the Linux host as a standard USB audio card:
To verify the full path end-to-end, I fed a 1 kHz sine wave into the gateway's I2S microphone input and recorded the output from the USB audio card in Audacity. Recognisable and periodic — residual jitter is a known open item (clock-domain mismatch on the USB sink path, no drift compensation yet), but the wireless → USB path is confirmed working:
Audio into Linux, and eyes on it
USB host + ALSA on the Zynq
Device tree for the ChipIdea USB host port plus committed kernel config fragments for the host stack and snd-usb-audio. With the board on its 12 V supply, the nRF5340 receiver enumerates and /proc/asound/cards lists it as card 0, attached at usb-ci_hdrc.0-1.
Streaming verified, and a live dashboard
Audio confirmed flowing mic → Auracast → Zynq, and aurascope-dash built to watch it: ALSA capture, FFT, and a Canvas dashboard over Server-Sent Events showing a waveform per stereo channel and a spectrum, plus 30 s of level history.
eBPF tracing in the same dashboard
kprobes on snd_pcm_period_elapsed and __snd_pcm_xrun via ply, feeding a period-interval latency histogram and an xrun counter into the same live feed. 9350 period events observed, all in the 16–32 ms bucket, zero xruns.
The whole chain, running
Every stage above is live at the same time in this clip. Sound goes into a microphone wired to one nRF5340, crosses the room as radio, and comes out the other end as numbers a Linux kernel is being measured on:
- Capture. An I2S microphone feeds the nRF5340 gateway on the left.
- Encode and broadcast. The gateway encodes to LC3 and broadcasts it as Auracast — two Broadcast Isochronous Streams, one per stereo channel.
- Receive. The second nRF5340 syncs to both streams, decodes the LC3, and presents itself over USB as a standard USB Audio Class device.
- Into Linux. The Arty Z7-20's USB host port enumerates it,
snd-usb-audiobinds it, and it becomes ALSA card 0 on the Zynq. - Observe.
aurascope-dashcaptures from that card and serves the live scope, while eBPF kprobes onsnd_pcm_period_elapsedand__snd_pcm_xrunreport how punctually the kernel is delivering it.
- What to watch for — the green LED on the gateway (left) and on the receiver (top right) pulsing together. On Nordic's Auracast firmware that is the broadcast-active indicator, so both blinking at once means the stream is up at the transmitting and receiving end, not just being sent into the void.
- The steady red LED on the Arty is its power indicator, confirming the board is running off the barrel jack rather than USB — the condition that lets its USB host port supply VBUS to the receiver.
The blinking is the honest part: it is the two radios reporting an active broadcast, not a rendering of something recorded earlier. Nothing here is stubbed — the microphone, the radio link, the USB enumeration and the kernel tracing are all running at once.
What's still ahead
Audio-status LED
Exposing the AXI GPIO core from the hardware step as a Linux LED, driven by a small daemon — blinking while audio is arriving, stopping when the stream goes silent.
The other half of observability
A kernel character driver exposing the same statistics from inside the kernel, to set against what eBPF already reports from outside — then full integration, and the audio-status LED last.
I'll update this page as each step lands. Full technical write-ups for the completed steps are linked from the AuraScope project page. The raw, unedited notes behind this page live in the repo: docs/notes/phase1-log.md.