Digital audio frontend for the classic Burr-Brown PCM56 R-2R DAC, implemented in SystemVerilog on Lattice ECP5. Provides a complete clock-domain-isolated DSP pipeline with async FIFO jitter isolation, 2x FIR oversampling with inverse sinc compensation, TPDF dither, and 1st-order noise shaping.
- I2S / Left-Justified input with input synchronizers
- Async BRAM FIFO (1024 depth) for clock domain crossing — decouples jittery I2S BCK from clean local master clock
- Write domain: incoming BCK (jittery)
- Read domain: local MCK (clean, via Amanero)
- Automatic fill-level recentering: inserts/drops samples at zero-crossing when FIFO approaches full/empty, preventing long-term drift with asynchronous clock sources
- 31-tap FIR 2x oversampler with embedded inverse sinc compensation, running on a gated clock shifted away from BCK edges
- Audio DSP pipeline: 24-bit → 16-bit with TPDF dither + 1st-order error-feedback noise shaping
- PCM56 serial transmitter with synchronous clock-enable architecture
- Output reclocking: BCK outputs driven via ODDRX1F + DCCA global clock buffer for low-jitter DAC interface
- Dual-mode: Oversampling (OS) / Non-Oversampling (NOS) via compile-time defines
┌─────────────────────────────────────────────────────────────────────────────┐
│ I2S Source (Amanero) │
│ BCK (jittery) ──────┬──────────┐ │
│ LRCK ───── Sync ────┤ I2S │ │
│ DATA ───── Sync ────┤ Receiver │ │
└───────────────────────┴────┬─────┘ │
│ 24-bit L/R │
▼ │
┌──────────────────────────────────────────────────────────────────────────┐ │
│ Dirty Domain (BCK) Async FIFO (1024×48bit, dual-clock BRAM)│ │
│ wr_clk = bck_i ──────────► ┌──────────────────────────────┐ │ │
│ │ Gray-code pointers │ │ │
│ │ Dual 2-flop CDC sync │ │ │
│ │ Registered almost_full/ │ │ │
│ │ almost_empty watermarks │ │ │
│ └──────────┬───────────────────┘ │ │
└──────────────────────────────────────────────┼────────────────────────────┘ │
│ 48-bit dout │
▼ │
┌──────────────────────────────────────────────────────────────────────────┐ │
│ Clean Domain (MCK) MCK via DCCA (global clock buffer) │ │
│ │ │
│ Timing Generator ──► clean BCK (MCK/4) │ │
│ ──► clean LRCK │ │
│ ──► bit counter (64-step frame) │ │
│ │ │
│ FIFO Recentering: │ │
│ almost_full ──► drop one FIFO read (decrease fill) │ │
│ almost_empty ──► repeat one FIFO word (increase fill) │ │
│ │ │
│ FIR Filter (2x OS, 31-tap polyphase HBF + inv-sinc) │ │
│ on gated fir_clk (DCCA CE, shifted off BCK edges) │ │
│ │ │
│ Audio DSP: TPDF dither + 1st-order error-feedback noise shaper │ │
│ │ │
│ PCM56 Transmitter (posedge MCK with CE, serializer) │ │
│ │ │
│ Output IO: BCK via ODDRX1F + DCCA │ │
│ SDO/LE via IOBUF (SLEWRATE=FAST) │ │
└───────────────────────────────────┬──────────────────────────────────────┘ │
│ │
▼ │
PCM56 DAC (CH2/CH3) │
└─────────────────────────────────────────────────────────────────────────────┘
rtl/i2s_top.sv— Top-level: clock management, FIFO, module integrationrtl/async_fifo.sv— Dual-clock async FIFO with registered watermarksrtl/i2s_receiver.sv— I2S / Left-Justified serial audio capturertl/input_sync.sv— Double-flop input synchronizersrtl/timing_gen.sv— Clean BCK/LRCK generation from MCKrtl/oversampler_fir.sv— 31-tap polyphase FIR with inverse sincrtl/audio_dsp.sv— TPDF dither + 1st-order noise shaperrtl/pcm56_transmitter.sv— PCM56 serial interface driverrtl/reset_sync.sv— Power-on reset with domain-specific deassertionpinmap.lpf— ECP5 pin mapping and timing constraintssim/i2s_tb.sv— Testbench for behavioral simulationdocs/pcm56.pdf— Hardware schematic (10 pages)
The design separates the I2S source domain from the DAC output domain at the async FIFO boundary:
| Domain | Clock | Role | Jitter handling |
|---|---|---|---|
| Dirty | incoming BCK | I2S receiver, FIFO write | Jitter present on BCK, but only affects capture flops; CDC isolation prevents propagation |
| Clean | local MCK (Amanero) | timing gen, FIR, DSP, transmitter, output BCK | MCK is a clean dedicated clock; jitter-free output to DAC |
Amanero provides both the I2S signal and the local MCK, ensuring clean clock quality without additional PLL cleanup.
The FIFO bridges the two domains via Gray-code pointer CDC. Registered watermarks (almost_full/almost_empty) drive automatic recentering: when fill approaches overflow/underflow, a single sample is dropped or repeated, preventing audio dropout with asynchronous source clocks.
- Synthesis: Yosys
- Place & Route: nextpnr-ecp5
- Bitstream: Project Trellis (ecppack)
- Simulation: Icarus Verilog
- Programming: openFPGALoader
Target device: Lattice ECP5-25K (CABGA256, speed grade 6)
makemake nosmake simmake flash # flash (non-volatile), OS mode
make load # SRAM (volatile), OS mode
make flash_nos # flash, NOS mode
make load_nos # SRAM, NOS modemake cleanMIT. See LICENSE file.