work / aurascope / progress

Where things stand

// Phase 1 · foundation · updated

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.

done & verified planned, not yet started

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.

✓ DONE — v0.1

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

✓ DONE — v0.1

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 honest lesson from this phase: the tool that's officially recommended isn't always the one that fits the job. Twice in Phase 1 — once catching an unverified hardware assumption, once recognizing an overbuilt dependency chain — the right move was stopping to check rather than pushing forward on momentum.

The wireless audio source

● DONE

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 DIAGRAM · AURASCOPE SYSTEM I2S Mic 48kHz PCM nRF5340 Gateway Zephyr / NCS 3.2.99 I2S driver LC3 encoder BIS source · broadcast_source audio_datapath (LOCKED) MCUboot + FOTA (QSPI) Auracast (BIS) connectionless · no pairing LC3 · 48kHz · 96kbps nRF5340 Headset Zephyr / NCS 3.2.99 BIS sink · broadcast_sink LC3 decoder USB UAC2 broadcast_sink app CONFIG_AUDIO_SINK_USB=y (custom) MCUboot + FOTA (QSPI) Linux Host Arty Z7-20 snd-usb-audio ALSA /dev/audiomon char driver eBPF probes kprobes dashboard USB --- = WIP audio / I2S / LC3 BLE / Auracast USB planned (WIP)

Component overview — dashed boxes are planned (WIP).

Signal flow

SEQUENCE DIAGRAM · SIGNAL FLOW I2S Mic nRF5340 GW Auracast nRF5340 HS USB Linux I2S PCM 48kHz 1. LC3 enc 2. BIS · 2× ISO channels 3. 0x1 (L) + 0x2 (R) · 96kbps passive sync 0x0003 4. no pairing · no ACKs LC3 dec 5. USB Audio Class 2 6. snd-usb-audio 7. card 1: nRF5340 USB Audio ALSA / arecord 8. audio / LC3 BLE / Auracast USB

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:

Gateway log showing two LC3 broadcast streams started at 48kHz 96kbps
GW log — two broadcast streams started (Chan alloc 0x1 and 0x2), LC3 @ 48kHz / 96kbps, drift comp state reaching CALIB

The headset syncs to both BIS streams (bitfield 0x0003) and the ISO RX SDU counters show zero bad packets:

Headset log showing sync to broadcast stream 0x0003 and zero bad packets
HS log — syncing to broadcast stream index 0x0003, both channels, ISO RX SDUs Bad: 0

The nRF5340 enumerates on the Linux host as a standard USB audio card:

aplay -l showing nRF5340 USB Audio enumerated as card 1
aplay -l — nRF5340 USB Audio enumerated as card 1, device 0

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:

Audacity waveform showing 1kHz sine received over Auracast USB
1 kHz sine — I2S mic → gateway → Auracast BIS → headset USB → Audacity. The path works.

What's still ahead

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.