SlopScore
00 crowd

xls32-fpga-synth (github.com/kazunori279/xls32-fpga-synth)

XLS32 — a 32-voice polyphonic HLS/FPGA synthesizer in Google XLS (DSLX) on a Basys 3, built end-to-end with AI coding agents and verified headlessly over USB.
Python · ★ 11 · 2 forks · Apache-2.0 · paperwork by the Cap'mmostly ai (inferred)light human (inferred)works-on-my-machine (inferred)other
listed 3 hours ago by kazunori279 · last checked 3 hours ago · demo
The owner didn't write this. This repo never submitted itself. The Cap'm found it on a truffle trawl and wrote its paperwork from what GitHub already shows. Picked by hand by the Cap'm on 2026-09-11: A 32-voice polyphonic MIDI synthesizer running in FPGA fabric; "not a single line of it was written by hand" as "the whole design was built by Claude Code (Opus 4.8)". 11 stars; Apache-2.0 license. The owner did not submit this. Votes count; awards don't until the owner claims it.

I'm not calling your project slop! Geeze, it's a joke... Do you own this repo?

Log in with GitHub as kazunori279. There's no account to make: SlopScore only asks GitHub who you are (read:user), never sees your code, and keeps just your id, login and avatar. Then you can:

  • Keep it, on your terms. Commit your own slopscore.md (spec) and press Refresh. Your paperwork replaces the Cap'm's, and you can submit it for Slop of the Day.
  • Take it down. One click on Remove. It stays gone; the trawl never brings it back.

Log in with GitHub

Can't log in as the owner? Request a takedown. No login needed, and a trawled listing comes down right away.

GitHub says
XLS32 — a 32-voice polyphonic HLS/FPGA synthesizer in Google XLS (DSLX) on a Basys 3, built end-to-end with AI coding agents and verified headlessly over USB.
website
https://kazunori279.github.io/xls32-fpga-synth/
created
2026-07-13 · pushed 2 weeks ago · 350 commits · 1 contributor
languages
Python 67%JavaScript 9%Verilog 8%HTML 6%Shell 4%RPC 2%
paperwork
licensereadme 42% health
dependencies
no dependency graph (no manifest, or disabled) · OSV.dev, checked 3 hours ago

Disclosures, inferred by the Cap'm

slopbucket
vibe-coded
category
other
ai_generated
mostly
human_touch
light
status
works-on-my-machine
language (detected)
csshtmljavascriptpythonrpcshelltclverilog
license (detected)
apache-2.0

The Cap'm's log

The Cap'm wrote this paperwork, not the owner. This repo never submitted itself to SlopScore. The Cap'm picked it by hand: A 32-voice polyphonic MIDI synthesizer running in FPGA fabric; "not a single line of it was written by hand" as "the whole design was built by Claude Code (Opus 4.8)". It carries the Apache-2.0 license. The disclosures above are his best guess from what GitHub shows.

Is this yours? Commit a real slopscore.md and press Refresh to replace this, or remove the listing in one click. There's no account to make: you log in with GitHub.

README (read the rest on GitHub)

XLS32 — a polyphonic HLS/FPGA synthesizer, built end-to-end with AI coding agents

A polyphonic MIDI synthesizer built entirely in FPGA fabric — the oscillators, filters, envelopes, and effects all run as digital logic on the chip, not as software on a CPU. It's written in Google XLS (DSLX), not hand-written Verilog, and the same engine runs on two very different boards: a Basys 3 (Xilinx Artix-7) and an apf.audio Tiliqua Eurorack module (Lattice ECP5).

Because it was developed headlessly over a network, every feature is verified automatically over USB. And not a single line of it was written by hand: the whole design was built by Claude Code (Opus 4.8), the AI coding agent, through loop engineering — prompts in, a self-verifying build → measure → revise loop out.

The XLS32 browser front-end — an analog-style panel driving the FPGA synth live over USB

The browser front-end (webui/): a Serum/Vital-style panel that drives the FPGA synth live over USB — oscillators, filter, envelopes, LFO, unison, cross-mod, and effects, plus a 4-part multitimbral selector, preset browser, and demo player.

Watch the XLS32 demo video

▶️ Demo video — the panel played live over the Tiliqua module's four-part Prelude in C, with the synth's own audio (click to watch on YouTube). For sound alone: Saint-Saëns · Le Cygne, 2:18, off a Basys 3; Bach · Goldberg Aria, 1:45, and Vivaldi · Winter, Largo, 1:45, both off a Tiliqua. 4 parts each, recorded over USB — no room mic, no software instruments, and the picture is a spectrogram of that same signal. For three modules driven from the one panel: multi-board & multipart demo.

At a glance

  • What it is — a 32-voice polyphonic, 4-part multitimbral subtractive synth: oscillators → per-voice resonant filter → VCA, with 2× ADSR, LFO, unison, cross-osc FM/ring-mod, and stereo effects.
  • Hardware — one engine, two boards: a Basys 3 (Xilinx Artix-7 xc7a35t, audio over USB) and a Tiliqua Eurorack module (Lattice ECP5 LFE5U-25F, analog jacks + a DVI visualiser). The synth is a literal circuit that computes one audio sample per tick — see Two boards, one synth.
  • Written inGoogle XLS (DSLX) compiled to Verilog, plus a per-board shell (Verilog on Basys 3, Amaranth on Tiliqua) for I/O and the block-RAM effects. No hand-written datapath.
  • Play itthe panel is live at kazunori279.github.io/xls32-fpga-synth: a browser analog-style panel that drives either board over USB with nothing installed (or drive it from Python). MIDI in, 16-bit stereo audio out. The page needs Chrome — see What you need.
  • Built by AI — every line written by Claude Code (Opus 4.8) through loop engineering: a self-verifying edit → build → measure loop, with 175 scored end-to-end tests over USB, run against both boards.
  • Start here — the Quick start is the ten-minute path and the User guide is the same ground at length, for either board. Both boards ship a prebuilt bitstream, so neither needs a toolchain. Everything after that is Part II: the Builder's guide builds from source, and Architecture is how it works.

Contents

Part I · For synth users — a board, a cable, and Chrome.

Part II · For developers — how it is built, and how to build it.

Four companion documents go deeper, split the same way the code is — core + Basys 3 in one pair, Tiliqua in the other:

per-block deep-dive build history & friction logs
core engine + Basys 3 shell ARCHITECTURE.md DEVELOPMENT.md
Tiliqua / ECP5 shell ARCHITECTURE_tiliqua.md DEVELOPMENT_tiliqua.md

The 50-minute talk that covers all of the above — with playable audio clips from each milestone — lives in docs/slides/ (English · 日本語).


Part I · For synth users

A board, a cable and Chrome. Everything in this part runs from the bitstream committed in this repo and a web page that needs no install — no toolchain, no terminal, and no clone.

▶ Quick start — play it

Three steps to a Tiliqua making sound: flash it once, plug a keyboard in, and — if you want the full panel — open a web page. Doing this on a Basys 3 instead? That route is §2 · A, and it is the one that wants a terminal.

What you need: a Tiliqua R5 in a powered Eurorack case, a USB-C cable, and something to listen on — out0/out1 are Eurorack line level, so go through a mixer, an audio interface or an output module. A display is optional. A computer or an Android tablet running Chrome is needed for step 1, and again for step 3 if you want the panel.

1 · Flash it — once, from Chrome

  1. Connect and open the flasher — USB-C into the module's dbg port, then tiliqua-webflash in Chrome. Pick the module over WebUSB.
  2. Choose XLS24 from the Community list and write it to slot 7. There is nothing to download: the module's maker merged XLS32 into the flasher on 2026-08-22, so it sits in the same list as the factory bitstreams. Power-cycle the case; the bootloader counts down for five seconds — pick slot 7 from the menu once, and every cold boot after that goes straight there.

The list entry is deliberately one netlist behind this repo. XLS24 in the flasher is the archive that closes at 54.30 MHz, and it is the copy the maker tested across several of his own Tiliquas before merging. xls24-r5.tar.gz here (408 KB) is newer and closes at 56.63 MHz; since 2026-08-25 it has been graded on all three modules on this desk rather than one. They grade identically and sound identical, so take the list unless you specifically want the newer routing — in which case download that file and use the flasher's upload button instead.

This is the only step that needs a computer at all, so borrow one if you have to. Once slot 7 is written the module never asks again.

There is a second archive beside it, xls32-r5.tar.gz, which is the same synth with 32 voices instead of 24 and belongs in slot 6. It fills 98.3 % of the FPGA where 24 voices fill 93.5 %, and this repo labels it experimental for exactly that reason: it runs here, and it did not run on one of the two modules the module's maker tried it on. Start with slot 7. boards/tiliqua/firmware/README.md is the long answer.

2 · Play it — no computer at all

Move the cable to usb2 and plug in a USB-MIDI keyboard, or put a keyboard straight into the TRS MIDI-In jack and leave the computer out of it entirely. It is 24 voices across 4 parts, on MIDI channels 1–4, and a TRS keyboard's own transmit channel picks which part it plays.

Sound comes out of out0/out1 — the stereo pair; the other two jacks are silent by design — and, whenever usb2 is connected, back up that same cable as a 48 kHz USB audio input at the same time. If you fitted a screen, it shows one tile per voice.

3 · Control it — the panel

Connect usb2 to a computer or an Android tablet, open the panel in Chrome, press POWER, and allow MIDI and audio input when the browser asks. That is the full instrument: every parameter, a preset browser, and four demo songs the board plays to itself.

Then read the User guide. Which part the TRS jack plays and how to change it, what to do when a note hangs, what each jack and LED means, how to drive the synth from a DAW or a hardware controller instead of the panel, and the handful of things that reliably catch people out.


1. Overview — the instrument

What it is

A 32-voice polyphonic, 4-part multitimbral subtractive synthesizer: two detuned oscillators and a sub per voice, into a resonant multimode filter and a VCA, with two ADSR envelopes, an LFO, unison, cross-oscillator FM and ring modulation, and a stereo chorus / echo / reverb chain on the end. That is a conventional analog-style layout, and it plays like one — a keyboard, a DAW, or the browser panel over MIDI, and sound out of the jacks.

What is not conventional is where it runs. There is no CPU and no audio thread: the whole instrument is a circuit on an FPGA, computing one sample per tick, so nothing an operating system does can jitter its timing. The circuit is written in Google XLS (DSLX) rather than hand-written Verilog, and the same engine is compiled unchanged for both boards — which is §4 and §5, and none of it changes how you play the thing.

Synth spec — board-independent; everything here is the engine itself.

Spec Value
Polyphony time-multiplexed — one voice enters the pipeline every ~24 engine cycles. 32 voices on Basys 3, 24 on the Tiliqua bitstream this repo ships (32 there too, as an experimental build)
Multitimbral 4 parts — MIDI channels 1–4, each an independent patch
Synthesis subtractive: oscillators → per-voice resonant filter → VCA, with 2× ADSR + LFO
Oscillators 2 per voice (detuned dual) + sub-osc → up to 64 oscillators across a 32-voice build, 48 across a 24; 5 waveforms (sine/saw/square/triangle/noise), PWM, cross-osc ring/FM/FM+ (8 ratios)
Filter per-voice state-variable, resonant — LP / HP / BP / notch
Envelopes 2× ADSR per voice (amplitude + filter)
Modulation per-part LFO (vibrato + tremolo), pitch bend (±2 st), portamento/glide
Effects stereo — chorus, ping-pong delay/echo, 8-comb Freeverb reverb (in the shell, not the engine)
Sample format 16-bit signed PCM, stereo out
Verification 175 scored end-to-end cases over USB (FFT / spectrogram), on both boards

Two boards, one synth

The same instrument ships on two very different pieces of hardware. Both run the identical engine with the identical feature set — down to the same core/synth.x; what differs is how you hear it, how you play it, what it takes to get going, and one number, how many notes sound at once before the engine steals a voice.

Basys 3 Tiliqua
What it is an entry-level FPGA development board — Digilent Basys 3, Xilinx Artix-7 xc7a35t a Eurorack module — apf.audio Tiliqua R5, Lattice ECP5 LFE5U-25F
How you hear it 16-bit audio back up the USB cable, through the panel or the host/ tools Eurorack jacks out0/out1and the same audio up the USB cable at once
How you play it over USB: the panel, or any MIDI source the host can reach USB-MIDI, or a keyboard straight into the TRS MIDI-In jack with no computer at all
Sample rate 32 kHz 48 kHz
Polyphony 32 voices 24 voices, or 32 from the experimental build — see below
You can also watch 16 LEDs as a voice-activity comet, and the 7-segment display a 720×720p60 DVI visualiser — one tile per voice — plus 8 level LEDs and an encoder
To flash it a clone of this repo and openFPGALoader — one command a Chrome tab, and nothing installed
Prebuilt bitstream boards/basys3/firmware/top.bit xls24-r5.tar.gz (slot 7) and xls32-r5.tar.gz (slot 6)

Neither needs an FPGA toolchain — the bitstream is committed for both, and building from source is optional (§3). Tiliqua is the gentler start: flashing it is a web page, and once flashed it is a standalone instrument. Basys 3 is the cheaper one, and everything reaches it over the single USB cable it is already using for power.

Both boards are driven by the same panel, the same host/ tools and the same 175-case test suite. The engineering differences between the two shells — clocking, transports, area — are §5 · The two boards.


2. User guide

Everything the Quick start skipped: the long form for either board, what each cable and jack does, how the panel behaves, and the failure modes — with the reasons, so a symptom tells you what to change.

Neither board needs an FPGA toolchain: both ship a prebuilt bitstream in the repo — a bare top.bit for Basys 3, a bitstream archive for Tiliqua. Building from source is §3 and is optional.

What you need

To play the synth: Chrome, and that is all. The web UI owns the hardware directly from the page — it uses Web MIDI and Web Serial, and neither ships in Firefox or Safari. In those browsers POWER reports the board as unsupported and there is nothing to configure — use Chrome. On Tiliqua the flashing is a browser page too (tiliqua-webflash), so nothing at all has to be installed; on Basys 3 you need openFPGALoader, one line below.

Phones and tablets can be the UI too. The panel is touch-native — pointer events throughout, no mouse-only interactions anywhere, and a layout that folds to a single narrow column below 900 px — so an Android tablet running Chrome drives a Tiliqua the same way a laptop does: plug usb2 into the tablet, open the panel, press POWER. The module takes its power from the Eurorack case, not from the tablet, so a phone or tablet is a genuinely practical host. A 10-inch screen is the comfortable size; phone-sized ones fit, but the knobs get tight.

Which host can do what

Host Flash a board Send notes The panel
Mac, Windows or Linux computer + Chrome
Android tablet or phone + Chrome ✅ Tiliqua only ✅ — audio path untested
iPhone / iPad ✅ via a Core MIDI app † ✗ — but CC from that app does the same job
A keyboard alone — USB-MIDI, or DIN/TRS into the jack ✗ — only the knobs the keyboard itself sends
Same computer, but Firefox or Safari

The crosses are all the same cross: Firefox and Safari ship neither Web MIDI nor Web Serial, and Apple requires every iOS browser to use WebKit, so an iPhone's Chrome is Safari underneath. That is a limit of those browsers, not of the synth — which only ever speaks standard MIDI, and so will talk to almost anything that does.

† Untested with this board. iOS handles class-compliant USB-MIDI and USB audio natively and the module draws its power from the Eurorack case rather than from the phone, so it ought to work; no one has actually tried it.

Two of those crosses are worth a sentence more before you rely on a particular host:

  • Basys 3 needs a Mac, Windows or Linux machine. It talks over Web Serial, which only reached Android in 2026 on a limited set of devices, and its 2 Mbaud link has never been tried over one.
  • The Android audio path is untested on hardware. It should work, but two things could bite: Android may label the board's audio input differently than the panel expects, and if its audio layer downmixes all four USB channels instead of taking the first two, the clock counter carried on ch2/3 will come through as noise. If you try it, that noise is the symptom to listen for.

For the command-line tools, the demos and the test suite — none of which is needed to play:

  • uv (Python env + deps): curl -LsSf https://astral.sh/uv/install.sh | sh
  • openFPGALoader (flash over USB-JTAG): brew install openfpgaloader — required for Basys 3, and for Tiliqua only if you SRAM-load your own build instead of using the web flasher (≥ 0.12.1 for the Tiliqua's dirtyJtag probe).
  • uhubctl (read USB port state): brew install uhubctl — only for host/usb_watch.py, the background witness the test suite's diagnosis rules lean on (test/README.md). Worth having before the first odd capture, not after.

Then, once per checkout:

git clone <repo-url> && cd <repo-dir>
uv sync                     # runtime deps only (all have prebuilt wheels — works on any Mac)

Command-line examples below are shown from that project root, and the Python ones run under uv (pyproject.toml pins the deps).

uv sync installs the host tools and the test suite. The web UI needs none of it — it is a static page. Two extras are opt-in: --extra localmidi adds python-rtmidi, which the host Tiliqua transport needs to send MIDI (it builds from C++ source, so skip it on locked-down machines — e.g. Santa on corp Macs blocks the compiler; the Basys 3 sends MIDI down its UART and the browser has its own Web MIDI). --extra presetgen adds the preset-generation toolchain (dawdreamer etc.), only for dev work; --extra deepfit adds the learned audio distances on top of it, and --extra docs the diagram renderer.

uv sync syncs the environment to exactly what you name, so --extra localmidi on its own uninstalls any other extra you had. Name every extra you want each time — e.g. uv sync --extra localmidi --extra deepfit — rather than adding them one at a time.

A · Basys 3 — flash and go

You need a Basys 3 board (Xilinx xc7a35t) and a USB cable. macOS ships the FTDI serial driver, so the board enumerates as /dev/cu.usbserial-* automatically — nothing else to install.

A prebuilt bitstream ships in the repo at boards/basys3/firmware/top.bit, so you can flash without building (no Vivado / F4PGA — just openFPGALoader). Plug the board in over USB, then:

# A) Persistent — write the onboard SPI flash (survives power cycles, boots standalone):
openFPGALoader -b basys3 -f boards/basys3/firmware/top.bit
#    then set the Basys 3 mode jumper JP1 to QSPI so it loads from flash on power-up.

# B) Volatile — load SRAM directly (quicker, but lost on power-off / unplug):
openFPGALoader -b basys3 boards/basys3/firmware/top.bit

Verify it's alive (should print the Artix-7 IDCODE):

openFPGALoader -b basys3 --detect     # idcode 0x362d093 / xc7a35

Notes:

  • SRAM is volatile. After any power-cycle or USB re-enumeration the SRAM config is wiped and the board goes silent (the web UI shows frames: 0) — just re-run option B, or use option A so it reloads itself from flash. For a demo machine, prefer A + JP1=QSPI: then it needs only uv + this repo, no openFPGALoader and no rebuild.
  • Power: the board runs off USB. Some laptops (e.g. a MacBook Air over a single USB-C hub) don't supply enough current — if the board's power LED stays dark, use a powered USB hub or the board's external supply.
  • JTAG vs UART share the FTDI. Free the serial port before flashing — close the web UI tab (it holds the port through Web Serial) — then reopen it after: the audio stream (UART) and JTAG programming use the same USB chip.

The committed bitstream is the Vivado/DSP48 build: 32 kHz, STAGES=48. To regenerate it see §3 · Basys 3, then cp build/top.bit boards/basys3/firmware/top.bit.

Rebuilt and verified 2026-08-22 for M37, which put this board back in step with the two DC fixes the Tiliqua had been carrying alone. It closed timing with zero failing endpoints (100 MHz, worst slack +1.322 ns) and then played: A major 7 came back at 438 / 554 / 658 / 830 Hz, all four inside the bin the check can resolve. It took an RTL fix to get there — the engine's audio-ready handshake had become a genuine 10 ns path. uv run --no-project python scripts/check_artefacts.py holds its provenance and will say so when it drifts again; the full record is top.bit.md.

Then jump to Run the web UI to play it.

B · Tiliqua — flash and go

You need a Tiliqua R5 in a Eurorack case with power and one USB-C cable to the dbg port (JTAG + the bootloader's serial log). That is enough to flash it and hear it. A second cable to usb2 carries the UAC2 audio and USB-MIDI link — add it when you want to play from the browser or from the host/ tools, which is Run the web UI below.

boards/tiliqua/firmware/ holds two committed bitstream archives — each the bitstream plus the manifest the bootloader needs — so you can run the synth without building. They are the same engine and differ in polyphony:

voices die clk post-route slot
xls24-r5.tar.gz (raw, 408 KB) 24 93.5 % 56.63 MHz 7 formal — what this repo stands behind
xls32-r5.tar.gz (raw, 429 KB) 32 98.3 % 48.37 MHz 6 experimental

Take the 24-voice one. Neither closes the 60 MHz clk constraint — that is issue #3, and it is why this repo exists — so both are a bet that the silicon beats nextpnr's model. At 32 voices the bet is 29 % and it has already failed on one of the two modules the module's maker tried. At 24 it is 6 %, for eight fewer simultaneous notes. Both archives are graded on hardware here; boards/tiliqua/firmware/README.md has the numbers and the history.

Then write it to a slot.

A · The web flasher — the default, and nothing to install. Open tiliqua-webflash in Chrome, pick the module over WebUSB, and choose slot 7. XLS24 is in the Community list, so for the 24-voice build there is nothing to download — but that entry is the 54.30 MHz archive, one netlist behind this repo (see §1 above). To flash what is committed here, or to flash the 32-voice build at all, upload the .tar.gz instead.

B · pdm flash, if you already have the vendor SDK checked out (see §3 · Tiliqua for what pdm needs). openFPGALoader --scan-usb should print 0x1209:0xc0ca dirtyJtag apf.audio Tiliqua R5 first — if it does not, the module is not talking and nothing below will work:

cd ~/Documents/GitHub/tiliqua/gateware
pdm flash archive ~/Documents/GitHub/xls32-fpga-synth/boards/tiliqua/firmware/xls24-r5.tar.gz \
    --slot 7

Any slot 0–7 works, and the archive does not care which. Slot 7 for 24 voices and slot 6 for 32 is what this repo's docs and tooling assume — keeping them apart means you can A/B the two without reflashing, and slot 6 is where the vendor DSP-MDIFF example used to live. Catch the five-second countdown, pick the slot from the menu once, and every cold boot from then on loads it directly.

Flashing to a slot is also how you avoid the clock trap — take the archive path if you can. The audio domain is the SI5351's clk0 wired straight into the fabric, with no FPGA PLL, and only the bootloader programs that chip, from the manifest of whichever slot it last booted. This archive's manifest carries clk0_hz: 12288000 and pins clk1_hz: 39070000, so booting it from a slot always clocks the module correctly.

Scan report · 2026-09-11
  • Prohibited terms or links
  • Repository eligibility
  • slopscore.md paperwork
  • Content policy
  • Risk review

0 comments

log in to comment.

report this listinglog in to report