Lab 4.2 — Fixed-Point Digital Mixer¶
Goal¶
Convert the floating-point digital mixer from Block 3 into a fixed-point model and evaluate spectral artifacts, quantization error and HDL readiness.
The lab answers the practical question:
How many bits are needed in the NCO and complex multiplier to shift an IQ signal without unacceptable spurs or EVM degradation?
Executable files¶
| Environment | File | Output |
|---|---|---|
| Python | blocks/block_04_simulink_and_fixed_point/python/lab_4_2_fixed_point_digital_mixer.py |
metrics + PNG figures in docs/assets |
| MATLAB | blocks/block_04_simulink_and_fixed_point/matlab/lab_4_2_fixed_point_digital_mixer.m |
metrics + PNG figures in docs/assets |
Run from the repository root:
python blocks/block_04_simulink_and_fixed_point/python/lab_4_2_fixed_point_digital_mixer.py
MATLAB:
matlab -batch "run('blocks/block_04_simulink_and_fixed_point/matlab/lab_4_2_fixed_point_digital_mixer.m')"
Generated Python figures:
docs/assets/lab42_fixed_point_mixer_spectrum.png
docs/assets/lab42_fixed_point_mixer_error.png
docs/assets/lab42_nco_frequency_resolution.png
Generated MATLAB figures:
docs/assets/lab42_fixed_point_mixer_spectrum_matlab.png
docs/assets/lab42_fixed_point_mixer_error_matlab.png
docs/assets/lab42_nco_frequency_resolution_matlab.png
Engineering context¶
A digital mixer shifts a complex baseband signal by multiplying it by a complex oscillator:
y[n] = x[n] * exp(j * 2*pi*f_shift*n/Fs)
In hardware, this becomes:
NCO / DDS -> sin/cos LUT or CORDIC -> complex multiplier -> rounding/saturation
Processing chain¶
flowchart LR
X[Input IQ] --> CMUL[Complex multiplier]
NCO[NCO sin/cos] --> CMUL
CMUL --> ROUND[Rounding / scaling]
ROUND --> SAT[Saturation]
SAT --> Y[Shifted IQ]
Y --> FFT[Spectrum and spur analysis]
Recommended starting formats¶
| Signal | Start format | Notes |
|---|---|---|
| Input IQ | Q1.15 | normalized complex input |
| NCO sin/cos | Q1.15 | simple LUT-compatible format |
| Product terms | Q2.30 | each real multiplication result |
| Sum/difference | Q3.30 | complex multiply accumulation |
| Output IQ | Q1.15 | after rounding and saturation |
| Phase accumulator | 24–32 bits | depends on frequency resolution and spur target |
Complex multiplication¶
For:
x = I + jQ
w = cos(phi) + j sin(phi)
The mixer output is:
y_i = I*cos(phi) - Q*sin(phi)
y_q = I*sin(phi) + Q*cos(phi)
Reference implementations¶
The executable Python and MATLAB scripts implement the same experiment:
- generate a complex IQ tone with additive noise;
- generate an NCO/DDS oscillator using a phase accumulator;
- run a floating-point mixer;
- run an educational Q1.15 fixed-point complex multiplier;
- compare floating-point and fixed-point spectra;
- compute frequency shift error, RMS error, EVM, spur estimate and saturation count;
- save comparison figures.
NCO / DDS discussion¶
For HDL, the oscillator is usually generated by an NCO:
phase[n+1] = phase[n] + phase_increment
phase_increment = round(f_shift / Fs * 2^P)
where P is the phase accumulator width.
Frequency resolution:
delta_f = Fs / 2^P
| Phase width | Frequency resolution at Fs = 2.4 MS/s | Practical note |
|---|---|---|
| 16 bits | 36.6 Hz | enough for simple demos |
| 24 bits | 0.143 Hz | good for most labs |
| 32 bits | 0.00056 Hz | common robust choice |
Required plots¶
Produce at least:
- input spectrum;
- floating-point mixer output spectrum;
- fixed-point mixer output spectrum;
- float vs fixed error magnitude;
- optional spur zoom near carrier;
- optional constellation before/after for modulated signals.
Metrics¶
| Metric | How to compute | Engineering meaning |
|---|---|---|
| Frequency shift error | measured peak minus expected peak | NCO phase increment accuracy |
| RMS error | rms(y_float - y_fixed) |
average implementation error |
| EVM | RMS error normalized by signal RMS | modulation quality impact |
| Spur level | largest unwanted spectral component | NCO and quantization artifacts |
| Saturation count | clipped output samples | scaling quality |
HDL mapping¶
Suggested streaming interface:
input wire clk
input wire rst
input wire in_valid
input wire signed [15:0] in_i
input wire signed [15:0] in_q
output wire out_valid
output wire signed [15:0] out_i
output wire signed [15:0] out_q
Internal blocks:
flowchart LR
PHASE[Phase accumulator] --> LUT[sin/cos LUT or CORDIC]
LUT --> CMUL[Complex multiplier]
IN[Input IQ] --> CMUL
CMUL --> SCALE[Round / scale]
SCALE --> OUT[Output IQ]
What to expect¶
Input tone: 120.0 kHz
Requested shift: -120.0 kHz
Actual NCO shift: -120.000029 kHz
Phase bits: 24
NCO frequency resolution: 0.143051 Hz
Measured output peak: 0.000 Hz
Frequency shift error: 0.000 Hz
RMS error: 1.086899e-03
EVM: 0.1417 %
Largest spur estimate: -70.12 dBc
Saturation count: 0
Two of these numbers do not mean what they seem to mean:
- The 0.14 % EVM is not Q1.15 rounding. The 24-bit phase increment gives -120 000.0286 Hz instead of -120 000 Hz. Over 32 768 samples (13.7 ms) that 0.0286 Hz error accumulates about 0.0025 rad of phase against the ideal floating-point reference, and that drift is the EVM. With
phase_bits = 32the EVM drops to 0.0027 %. The spectrum cannot show this: 0.0286 Hz is far inside one 73 Hz FFT bin, which is why "Frequency shift error: 0.000 Hz". - The -70.12 dBc "spur" is the highest noise bin, not a mixer spur. The input carries
noise_rms = 0.01. Withnoise_rms = 0the largest spur is -98.2 dBc, the real Q1.15 limit of this mixer.
Exercises¶
- Set
phase_bits = 16. The frequency error becomes -7.32 Hz and the EVM 35.9 %, while the largest spur is still -69.1 dBc and the peak still lands in the 0 Hz bin. Explain why a spectrum-only check would accept this NCO, and what an EVM against a reference measures that a spectrum does not. - Set
noise_rms = 0andq_fractional_bits = 11. The largest spur rises from -98.2 to -79.6 dBc. Is that close to 6 dB per bit? Which part of the mixer (data words or the sine/cosine words) would you widen first? - For a shift that must stay phase-coherent over a 100 ms burst, how many phase bits keep the accumulated phase error below 0.01 rad at 2.4 MS/s?
Answers (checked by running the lab script)
- A 7.32 Hz frequency error moves the output peak by far less than one FFT bin (73 Hz here), and the spurs come from amplitude quantization, which is unchanged, so the spectrum looks perfect. But the error accumulates as a phase ramp: over 32 768 samples at 2.4 MS/s it reaches
2π·7.32·0.0137 ≈ 0.63 rad, which is the 35.9 % EVM. An EVM against a reference compares sample by sample, so it sees phase and timing, which a magnitude spectrum discards. - From −98.2 to −79.6 dBc is 18.6 dB for 4 bits, about 4.7 dB per bit: close to, but below, 6 dB. Widen the data words first: with only the data at 11 bits the worst spur is −78.7 dBc, with only the sine/cosine words at 11 bits it is −86.4 dBc.
- Rounding the phase increment leaves a frequency error of at most
Fs / 2^(B+1). Keeping2π · Δf · 0.1 sbelow 0.01 rad needsΔf < 0.0159 Hz, so2^(B+1) > 2.4 MHz / 0.0159 Hz ≈ 1.5·10⁸: 27 bits (error 0.0089 Hz, 0.0056 rad). 26 bits gives 0.0179 Hz and 0.011 rad, just too much. A 32-bit accumulator has a wide margin.
Report checklist¶
- State sample rate, input tone frequency and shift frequency.
- State input, NCO, product and output formats.
- Compute phase increment and frequency resolution.
- Compare float and fixed spectra.
- Estimate frequency shift error.
- Compute RMS error and EVM.
- Estimate spur level.
- Count saturation events.
- Explain whether LUT, CORDIC or direct DDS is preferred.
Engineering conclusion template¶
The selected mixer format ______ provides EVM = ____ % and saturation count = ____.
The phase accumulator width ____ gives frequency resolution ____ Hz at Fs = ____ Hz.
The largest observed spur is approximately ____ dBc.
This configuration is / is not ready for HDL because ______.