Skip to content

Latest commit

 

History

7 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

sdr-fm-ddc

A complete software-defined-radio receive chain in SystemVerilog: NCO + mixer, CIC and FIR decimation, and FM demodulation - verified bit-exactly against a golden Python model, end to end.

CI License Language

Architecture

The CORDIC trick, twice

The chain reuses the pipelined CORDIC from cordic-core at both ends, and both times it replaces a textbook block with something smaller:

  • Mixer: rotation-mode CORDIC rotating each sample by −φ is the complex multiply by e^−jφ - NCO sin/cos table and four mixer multipliers collapse into one shift-add pipeline. The 32-bit phase accumulator gives fs/2^32 tuning resolution and advances only on accepted samples, so stream gaps never slip the LO.
  • Demodulator: vectoring-mode CORDIC returns atan2(Q,I) in BAM units, where ±π wraps in plain 16-bit arithmetic - so FM demodulation (the phase derivative) is a single subtract with no unwrap logic. The magnitude output comes free and is exposed as RSSI.

Between them: an N=3 Hogenauer CIC (÷16, 28-bit wraparound arithmetic per the width bound, truncation of the exact 2^12 gain) and a 31-tap transposed FIR channel filter (÷2, round-half-up, saturating).

What the hardware computes

Everything below is produced by the bit-exact model - i.e. this is the RTL output, not a floating-point idealization:

FM demodulation demo

python tools/gen_demo_plot.py
# -> group delay 8 output samples, demod-vs-sent rms 311.5 BAM
#    (3.0% of the 10240 BAM deviation)

Verification

ddc_mixer     400 random IQ samples, randomly tuned NCO - bit-exact,
              including the phase-accumulator track through stream gaps
fir_decim     600 random samples with full-scale bursts - bit-exact
              through the elastic pipeline, saturation and drain
cic_decim     640 full-scale samples with bursts at the rails - bit-exact
              through the wraparound arithmetic
fm_demod      wandering-frequency phasor across every phase wrap -
              freq_out and mag_out bit-exact
fm_receiver   4096-sample FM signal through the whole chain - 128
              outputs bit-exact, spectral peak on the message tone,
              deviation amplitude within 15%, DC ~0 after exact tuning

TESTS=5 PASS=5 x three seeds  (icarus, cocotb 2.0)
lint: verilator -Wall clean

All streams run under random input gaps and output backpressure with a per-cycle stability watcher; every stage is valid/ready and drains without new input.

Synthesis

Vivado out-of-context, Zynq-7020, 156.25 MHz, whole receiver:

LUTs FFs DSPs BRAM WNS @156.25 MHz
4422 4343 30 0 +0.446 ns

The first synthesis pass failed timing at −3.5 ns and the failure is worth reading about: an unregistered x * coef + t cone that passes OOC timing in isolation (where the input arrives from an untimed port) does not survive being fed from a real upstream register. The FIR here registers its products (DSP MREG) and folds the rounding constant into the accumulation chain - see the header of rtl/fir_decim.sv.

Usage

python tb/run.py                 # full regression (or: tb/run.py cic_decim)
python tools/gen_coeffs.py       # regenerate taps -> svh + json + figure
python tools/gen_demo_plot.py    # regenerate the demo figure

f_lo = phase_inc / 2^32 · fs; freq_out is instantaneous frequency in BAM per output sample: f = freq_out / 65536 · fs_out.

License

MIT

About

SDR FM receiver chain in SystemVerilog - CORDIC NCO+mixer, CIC and FIR decimation, FM discriminator; bit-exact golden model end to end

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages