Model the receiver chain

Add thermal noise, oscillator phase noise, LNA gain, IQ imbalance, AGC, ADC quantization and TX-to-RX leakage to a Radar, and know the order they run in.

A radar with no receiver fields set is an ideal receiver: the cube is the noiseless echo in sqrt(W) at the antenna port. This page shows how to add each receiver stage as a Radar field and what each one does.

The fields#

Radar fieldRecordEffect
noiseNoise(...)Thermal noise from a noise figure, and optional oscillator phase noise
lna_gainfloat, dBLow-noise amplifier voltage gain
iqIq(gain_db, phase_deg)Quadrature gain and phase imbalance
agcAgc(target_rms, ...)Automatic gain control to a target RMS
adcAdc(bits, full_scale)Uniform quantization with clipping
leakageLeakage(isolation_db, delay_s)Direct TX-to-RX coupling (FMCW only)
impedancefloat, ohm, default 50.0Port impedance for the sqrt(W) to volt conversion
seedint, default 0Base seed for every random stage
from witwin.radar import Adc, Agc, Iq, Leakage, Noise
 
radar = radar.replace(
    noise=Noise(figure=10.0),
    lna_gain=20.0,
    iq=Iq(gain_db=0.5, phase_deg=3.0),
    agc=Agc(target_rms=0.1),
    adc=Adc(bits=12, full_scale=1.0),
    leakage=Leakage(isolation_db=40.0, delay_s=0.2e-9),
    seed=7,
)

Order#

The stages always run in this order, whatever order you set the fields in:

  1. Leakage is added during synthesis as one more path row, so the rest of the chain sees it.
  2. Conversion from sqrt(W) to volts: v = sqrt(W) · sqrt(impedance).
  3. Thermal noise, phase noise and the LNA gain, in one fused native stage.
  4. IQ imbalance.
  5. AGC.
  6. ADC.

The chain exists only when at least one of noise, lna_gain, agc or adc is set. With a chain, the cube is in volts; without one, it is in sqrt(W).

IQ imbalance alone has no effect

In 0.4.1, a radar with iq set and none of noise, lna_gain, agc or adc builds no receiver chain, so the IQ imbalance is silently skipped and the cube is unchanged. Set another stage as well, for example lna_gain=0.0, to apply it.

For FMCW with the default spectrum output, the chain runs on time-domain beat samples and the result is then transformed to range. You get a range spectrum of a noisy, quantized signal, as a real receiver would produce.

Thermal noise#

Noise(figure=10.0)

The noise is circularly symmetric complex Gaussian with total power k · T_sys · B, where:

  • T_sys = antenna_temperature + 290 · (F - 1) and F = 10^(figure / 10). antenna_temperature defaults to 290 K, so T_sys = 290 · F.
  • B is bandwidth in Hz. None (the default) uses the waveform's bandwidth: the sample rate for FMCW, the occupied band num_subcarriers · subcarrier_spacing for OFDM, and the declared pulse bandwidth for pulsed.

Noise() with the default figure=0.0 still adds thermal noise at 290 K. noise.noise_power_watts and noise.thermal_sigma_volts(impedance) give the resulting levels.

Oscillator phase noise#

Noise(figure=10.0, phase_density=-90.0, phase_offset=1e6)

phase_density is the single-sideband phase-noise density in dBc/Hz measured at phase_offset Hz. The model is a free-running (Wiener) oscillator with a -20 dB/decade slope. It does not model the close-in 1/f³ region or a far-out floor.

In a scene simulation, one oscillator is evaluated at the receive time and at the delayed transmit time of every path, so phase noise correlates across paths, antennas and frames as it would in hardware. This needs ADC-time observations, so:

  • Phase noise works with FMCW only.
  • Motion.auto() resolves to per-ADC sampling when phase noise is set. Motion.static() and Motion.chirp() are refused with phase noise.

LNA, AGC and ADC#

  • lna_gain is a voltage gain in dB. The noise is referred to the receiver input, so the LNA scales signal and noise together.
  • Agc(target_rms, mode="per_rx", min_gain=-60.0, max_gain=60.0) scales the signal toward target_rms volts. mode="per_rx" measures each receiver separately and "global" uses one gain. The gain is limited to [min_gain, max_gain] dB. AGC makes the receiver nonlinear in the signal.
  • Adc(bits, full_scale) quantizes each component uniformly (mid-tread) with step 2 · full_scale / (2^bits - 1) and clips values beyond ±full_scale volts. bits is 1 to 30.

IQ imbalance#

Iq(gain_db, phase_deg) models a demodulator whose two branches differ by a gain ratio and a phase error. It leaks a conjugate image: y = alpha · x + beta · conj(x). On a range-Doppler map the image appears at the mirrored Doppler bin, iq.image_rejection_db below the true return. Use it to train or test a classifier that must not rely on a clean Doppler sign.

Leakage#

Leakage(isolation_db, delay_s) adds the part of the transmitted chirp that reaches the receiver directly. After de-chirping it is a strong tone at a range of centimetres. Its amplitude is sqrt(P_tx) · 10^(-isolation_db / 20) and its delay is delay_s. It spends the receiver's dynamic range and puts window sidelobes over the near range bins. Leakage is FMCW only: an OFDM or pulsed radar with leakage raises NotImplementedError at synthesis.

Seeds and repeatability#

All random stages derive their own streams from seed with a counter-based generator. The same seed gives a bitwise identical cube, and turning one stage on does not change another stage's random draws. Use radar.replace(seed=s) to draw a new noise realization.

Gradients through the chain#

Noise, LNA and AGC are differentiable with respect to the signal. ADC quantization refuses a differentiable input, and every receiver setting (noise figure, gain, AGC limits, ADC bits) must be a host float, not a tensor. See Differentiability.