#!/usr/bin/env python3
"""
Synthetic Radar Scene Generator for AERIS-10 FPGA Co-simulation.

Generates test vectors (ADC samples + reference chirps) for multi-target
radar scenes with configurable:
  - Target range, velocity, RCS
  - Noise floor and clutter
  - ADC quantization (8-bit, 400 MSPS)

Output formats:
  - Hex files for Verilog $readmemh
  - CSV for analysis
  - Python arrays for direct use with fpga_model.py

The scene generator models the complete RF path:
  TX chirp -> propagation delay -> Doppler shift -> RX IF signal -> ADC

Author: Phase 0.5 co-simulation suite for PLFM_RADAR
"""

import math
import os


# =============================================================================
# AERIS-10 System Parameters
# =============================================================================

# RF parameters
F_CARRIER = 10.5e9        # 10.5 GHz carrier
C_LIGHT = 3.0e8           # Speed of light (m/s)
WAVELENGTH = C_LIGHT / F_CARRIER  # ~0.02857 m

# Chirp parameters
F_IF = 120e6              # IF frequency (120 MHz)
CHIRP_BW = 20e6           # Chirp bandwidth (30 MHz -> 10 MHz = 20 MHz sweep)
F_CHIRP_START = 30e6      # Chirp start frequency (relative to IF)
F_CHIRP_END = 10e6        # Chirp end frequency (relative to IF)

# Sampling
FS_ADC = 400e6            # ADC sample rate (400 MSPS)
FS_SYS = 100e6            # System clock (100 MHz)
ADC_BITS = 8              # ADC resolution

# Chirp timing
T_LONG_CHIRP = 30e-6      # 30 us long chirp duration
T_SHORT_CHIRP = 0.5e-6    # 0.5 us short chirp
T_LISTEN_LONG = 137e-6    # 137 us listening window
T_PRI_LONG = 167e-6       # 30 us chirp + 137 us listen
T_PRI_SHORT = 175e-6      # staggered short-PRI sub-frame
N_SAMPLES_LISTEN = int(T_LISTEN_LONG * FS_ADC)  # 54800 samples

# Processing chain
CIC_DECIMATION = 4
FFT_SIZE = 1024
RANGE_BINS = 64
DOPPLER_FFT_SIZE = 16      # Per sub-frame
DOPPLER_TOTAL_BINS = 32    # Total output bins (2 sub-frames x 16)
CHIRPS_PER_SUBFRAME = 16
CHIRPS_PER_FRAME = 32

# Derived
RANGE_RESOLUTION = C_LIGHT / (2 * CHIRP_BW)  # 7.5 m
MAX_UNAMBIGUOUS_RANGE = C_LIGHT * T_LISTEN_LONG / 2  # ~20.55 km
VELOCITY_RESOLUTION_LONG = WAVELENGTH / (2 * CHIRPS_PER_SUBFRAME * T_PRI_LONG)
VELOCITY_RESOLUTION_SHORT = WAVELENGTH / (2 * CHIRPS_PER_SUBFRAME * T_PRI_SHORT)

# Short chirp LUT (60 entries, 8-bit unsigned)
SHORT_CHIRP_LUT = [
    255, 237, 187, 118, 49, 6, 7, 54, 132, 210, 253, 237, 167, 75, 10, 10,
    80, 180, 248, 237, 150, 45, 1, 54, 167, 249, 228, 118, 15, 18, 127, 238,
    235, 118, 10, 34, 167, 254, 187, 45, 8, 129, 248, 201, 49, 10, 145, 254,
    167, 17, 46, 210, 235, 75, 7, 155, 253, 118, 1, 129,
]


# =============================================================================
# Target definition
# =============================================================================

class Target:
    """Represents a radar target."""

    def __init__(self, range_m, velocity_mps=0.0, rcs_dbsm=0.0, phase_deg=0.0):
        """
        Args:
            range_m: Target range in meters
            velocity_mps: Target radial velocity in m/s (positive = approaching)
            rcs_dbsm: Radar cross-section in dBsm
            phase_deg: Initial phase in degrees
        """
        self.range_m = range_m
        self.velocity_mps = velocity_mps
        self.rcs_dbsm = rcs_dbsm
        self.phase_deg = phase_deg

    @property
    def delay_s(self):
        """Round-trip delay in seconds."""
        return 2 * self.range_m / C_LIGHT

    @property
    def delay_samples(self):
        """Round-trip delay in ADC samples at 400 MSPS."""
        return self.delay_s * FS_ADC

    @property
    def doppler_hz(self):
        """Doppler frequency shift in Hz."""
        return 2 * self.velocity_mps * F_CARRIER / C_LIGHT

    @property
    def amplitude(self):
        """Linear amplitude from RCS (arbitrary scaling for ADC range)."""
        # Simple model: amplitude proportional to sqrt(RCS) / R^2
        # Normalized so 0 dBsm at 100m gives roughly 50% ADC scale
        rcs_linear = 10 ** (self.rcs_dbsm / 10.0)
        if self.range_m <= 0:
            return 0.0
        amp = math.sqrt(rcs_linear) / (self.range_m ** 2)
        # Scale to ADC range: 100m/0dBsm -> ~64 counts (half of 128 peak-to-peak)
        return amp * (100.0 ** 2) * 64.0

    def __repr__(self):
        return (f"Target(range={self.range_m:.1f}m, vel={self.velocity_mps:.1f}m/s, "
                f"RCS={self.rcs_dbsm:.1f}dBsm, delay={self.delay_samples:.1f}samp)")


# =============================================================================
# IF chirp signal generation
# =============================================================================

def generate_if_chirp(n_samples, chirp_bw=CHIRP_BW, f_if=F_IF, fs=FS_ADC):
    """
    Generate an IF chirp signal (the transmitted waveform as seen at IF).

    This models the PLFM chirp as a linear frequency sweep around the IF.
    The ADC sees this chirp after mixing with the LO.

    Args:
        n_samples: number of samples to generate
        chirp_bw: chirp bandwidth in Hz
        f_if: IF center frequency in Hz
        fs: sample rate in Hz

    Returns:
        (chirp_i, chirp_q): lists of float I/Q samples (normalized to [-1, 1])
    """
    chirp_i = []
    chirp_q = []
    chirp_rate = chirp_bw / (n_samples / fs)  # Hz/s

    for n in range(n_samples):
        t = n / fs
        # Instantaneous frequency: f_if - chirp_bw/2 + chirp_rate * t
        # Phase: integral of 2*pi*f(t)*dt
        _f_inst = f_if - chirp_bw / 2 + chirp_rate * t
        phase = 2 * math.pi * (f_if - chirp_bw / 2) * t + math.pi * chirp_rate * t * t
        chirp_i.append(math.cos(phase))
        chirp_q.append(math.sin(phase))

    return chirp_i, chirp_q


def generate_reference_chirp_q15(n_fft=FFT_SIZE, chirp_bw=CHIRP_BW, _f_if=F_IF, _fs=FS_ADC):
    """
    Generate a reference chirp in Q15 format for the matched filter.

    The reference chirp is the expected received signal (zero-delay, zero-Doppler).
    Padded with zeros to FFT_SIZE.

    Returns:
        (ref_re, ref_im): lists of N_FFT signed 16-bit integers
    """
    # Generate chirp for a reasonable number of samples
    # The chirp duration determines how many samples of the reference are non-zero
    # For 30 us chirp at 100 MHz (after decimation): 3000 samples
    # But FFT is 1024, so we use 1024 samples of the chirp
    chirp_samples = min(n_fft, int(T_LONG_CHIRP * FS_SYS))

    ref_re = [0] * n_fft
    ref_im = [0] * n_fft

    chirp_rate = chirp_bw / T_LONG_CHIRP

    for n in range(chirp_samples):
        t = n / FS_SYS
        # After DDC, the chirp is at baseband
        # The beat frequency from a target at delay tau is: f_beat = chirp_rate * tau
        # Reference chirp is the TX chirp at baseband (zero delay)
        phase = math.pi * chirp_rate * t * t
        re_val = round(32767 * 0.9 * math.cos(phase))
        im_val = round(32767 * 0.9 * math.sin(phase))
        ref_re[n] = max(-32768, min(32767, re_val))
        ref_im[n] = max(-32768, min(32767, im_val))

    return ref_re, ref_im


# =============================================================================
# ADC sample generation with targets
# =============================================================================

def generate_adc_samples(targets, n_samples, noise_stddev=3.0,
                         clutter_amplitude=0.0, seed=42):
    """
    Generate synthetic ADC samples for a radar scene.

    Models:
      - Multiple targets at different ranges (delays)
      - Each target produces a delayed, attenuated copy of the TX chirp at IF
      - Doppler shift applied as phase rotation
      - Additive white Gaussian noise
      - Optional clutter

    Args:
        targets: list of Target objects
        n_samples: number of ADC samples at 400 MSPS
        noise_stddev: noise standard deviation in ADC LSBs
        clutter_amplitude: clutter amplitude in ADC LSBs
        seed: random seed for reproducibility

    Returns:
        list of n_samples 8-bit unsigned integers (0-255)
    """
    # Simple LCG random number generator (no numpy dependency)
    rng_state = seed
    def next_rand():
        nonlocal rng_state
        rng_state = (rng_state * 1103515245 + 12345) & 0x7FFFFFFF
        return rng_state

    def rand_gaussian():
        """Box-Muller transform using LCG."""
        while True:
            u1 = (next_rand() / 0x7FFFFFFF)
            u2 = (next_rand() / 0x7FFFFFFF)
            if u1 > 1e-10:
                break
        return math.sqrt(-2.0 * math.log(u1)) * math.cos(2.0 * math.pi * u2)

    # Generate TX chirp (at IF) - this is what the ADC would see from a target
    chirp_rate = CHIRP_BW / T_LONG_CHIRP
    chirp_samples = int(T_LONG_CHIRP * FS_ADC)  # 12000 samples at 400 MSPS

    adc_float = [0.0] * n_samples

    for target in targets:
        delay_samp = target.delay_samples
        amp = target.amplitude
        doppler_hz = target.doppler_hz
        phase0 = target.phase_deg * math.pi / 180.0

        for n in range(n_samples):
            # Check if this sample falls within the delayed chirp
            n_delayed = n - delay_samp
            if n_delayed < 0 or n_delayed >= chirp_samples:
                continue

            t = n / FS_ADC
            t_delayed = n_delayed / FS_ADC

            # Signal at IF: cos(2*pi*f_if*t + pi*chirp_rate*t_delayed^2 + doppler + phase)
            phase = (2 * math.pi * F_IF * t
                     + math.pi * chirp_rate * t_delayed * t_delayed
                     + 2 * math.pi * doppler_hz * t
                     + phase0)

            adc_float[n] += amp * math.cos(phase)

    # Add noise
    for n in range(n_samples):
        adc_float[n] += noise_stddev * rand_gaussian()

    # Add clutter (slow-varying, correlated noise)
    if clutter_amplitude > 0:
        clutter_phase = 0.0
        clutter_freq = 0.001  # Very slow variation
        for n in range(n_samples):
            clutter_phase += 2 * math.pi * clutter_freq
            adc_float[n] += clutter_amplitude * math.sin(clutter_phase + rand_gaussian() * 0.1)

    # Quantize to 8-bit unsigned (0-255), centered at 128
    adc_samples = []
    for val in adc_float:
        quantized = round(val + 128)
        quantized = max(0, min(255, quantized))
        adc_samples.append(quantized)

    return adc_samples


def generate_baseband_samples(targets, n_samples_baseband, noise_stddev=0.5,
                              seed=42):
    """
    Generate synthetic baseband I/Q samples AFTER DDC.

    This bypasses the DDC entirely, generating what the DDC output should look
    like for given targets. Useful for testing matched filter and downstream
    processing without running through NCO/mixer/CIC/FIR.

    Each target produces a beat frequency: f_beat = chirp_rate * delay
    After DDC, the signal is at baseband with this beat frequency.

    Args:
        targets: list of Target objects
        n_samples_baseband: number of baseband samples (at 100 MHz)
        noise_stddev: noise in Q15 LSBs
        seed: random seed

    Returns:
        (bb_i, bb_q): lists of signed 16-bit integers (Q15)
    """
    rng_state = seed
    def next_rand():
        nonlocal rng_state
        rng_state = (rng_state * 1103515245 + 12345) & 0x7FFFFFFF
        return rng_state

    def rand_gaussian():
        while True:
            u1 = (next_rand() / 0x7FFFFFFF)
            u2 = (next_rand() / 0x7FFFFFFF)
            if u1 > 1e-10:
                break
        return math.sqrt(-2.0 * math.log(u1)) * math.cos(2.0 * math.pi * u2)

    chirp_rate = CHIRP_BW / T_LONG_CHIRP
    bb_i_float = [0.0] * n_samples_baseband
    bb_q_float = [0.0] * n_samples_baseband

    for target in targets:
        f_beat = chirp_rate * target.delay_s  # Beat frequency
        amp = target.amplitude / 4.0  # Scale down for baseband (DDC gain ~ 1/4)
        doppler_hz = target.doppler_hz
        phase0 = target.phase_deg * math.pi / 180.0

        for n in range(n_samples_baseband):
            t = n / FS_SYS
            phase = 2 * math.pi * (f_beat + doppler_hz) * t + phase0
            bb_i_float[n] += amp * math.cos(phase)
            bb_q_float[n] += amp * math.sin(phase)

    # Add noise and quantize to Q15
    bb_i = []
    bb_q = []
    for n in range(n_samples_baseband):
        i_val = round(bb_i_float[n] + noise_stddev * rand_gaussian())
        q_val = round(bb_q_float[n] + noise_stddev * rand_gaussian())
        bb_i.append(max(-32768, min(32767, i_val)))
        bb_q.append(max(-32768, min(32767, q_val)))

    return bb_i, bb_q


# =============================================================================
# Multi-chirp frame generation (for Doppler processing)
# =============================================================================

def generate_doppler_frame(targets, n_chirps=CHIRPS_PER_FRAME,
                           n_range_bins=RANGE_BINS, noise_stddev=0.5, seed=42):
    """
    Generate a complete Doppler frame (32 chirps x 64 range bins).

    Each chirp sees a phase rotation due to target velocity:
      phase_shift_per_chirp = 2*pi * doppler_hz * T_chirp_repeat

    Args:
        targets: list of Target objects
        n_chirps: chirps per frame (32)
        n_range_bins: range bins per chirp (64)

    Returns:
        (frame_i, frame_q): [n_chirps][n_range_bins] arrays of signed 16-bit
    """
    rng_state = seed
    def next_rand():
        nonlocal rng_state
        rng_state = (rng_state * 1103515245 + 12345) & 0x7FFFFFFF
        return rng_state

    def rand_gaussian():
        while True:
            u1 = (next_rand() / 0x7FFFFFFF)
            u2 = (next_rand() / 0x7FFFFFFF)
            if u1 > 1e-10:
                break
        return math.sqrt(-2.0 * math.log(u1)) * math.cos(2.0 * math.pi * u2)

    frame_i = []
    frame_q = []

    for chirp_idx in range(n_chirps):
        chirp_i = [0.0] * n_range_bins
        chirp_q = [0.0] * n_range_bins

        for target in targets:
            # Which range bin does this target fall in?
            # After matched filter + range decimation:
            delay_baseband_samples = target.delay_s * FS_SYS
            range_bin_float = delay_baseband_samples * n_range_bins / FFT_SIZE
            range_bin = round(range_bin_float)

            if range_bin < 0 or range_bin >= n_range_bins:
                continue

            amp = target.amplitude / 4.0

            # Doppler phase for this chirp.
            # The frame uses staggered PRF: chirps 0-15 use the long PRI,
            # chirps 16-31 use the short PRI.
            if chirp_idx < CHIRPS_PER_SUBFRAME:
                slow_time_s = chirp_idx * T_PRI_LONG
            else:
                slow_time_s = (CHIRPS_PER_SUBFRAME * T_PRI_LONG) + \
                              ((chirp_idx - CHIRPS_PER_SUBFRAME) * T_PRI_SHORT)

            doppler_phase = 2 * math.pi * target.doppler_hz * slow_time_s
            total_phase = doppler_phase + target.phase_deg * math.pi / 180.0

            # Spread across a few bins (sinc-like response from matched filter)
            for delta in range(-2, 3):
                rb = range_bin + delta
                if 0 <= rb < n_range_bins:
                    # sinc-like weighting
                    weight = 1.0 if delta == 0 else 0.2 / abs(delta)
                    chirp_i[rb] += amp * weight * math.cos(total_phase)
                    chirp_q[rb] += amp * weight * math.sin(total_phase)

        # Add noise and quantize
        row_i = []
        row_q = []
        for rb in range(n_range_bins):
            i_val = round(chirp_i[rb] + noise_stddev * rand_gaussian())
            q_val = round(chirp_q[rb] + noise_stddev * rand_gaussian())
            row_i.append(max(-32768, min(32767, i_val)))
            row_q.append(max(-32768, min(32767, q_val)))

        frame_i.append(row_i)
        frame_q.append(row_q)

    return frame_i, frame_q


# =============================================================================
# Output file generators
# =============================================================================

def write_hex_file(filepath, samples, bits=8):
    """
    Write samples to hex file for Verilog $readmemh.

    Args:
        filepath: output file path
        samples: list of integer samples
        bits: bit width per sample (8 for ADC, 16 for baseband)
    """
    hex_digits = (bits + 3) // 4
    fmt = f"{{:0{hex_digits}X}}"

    with open(filepath, 'w') as f:
        f.write(f"// {len(samples)} samples, {bits}-bit, hex format for $readmemh\n")
        for _i, s in enumerate(samples):
            if bits <= 8:
                val = s & 0xFF
            elif bits <= 16:
                val = s & 0xFFFF
            elif bits <= 32:
                val = s & 0xFFFFFFFF
            else:
                val = s & ((1 << bits) - 1)
            f.write(fmt.format(val) + "\n")



def write_csv_file(filepath, columns, headers=None):
    """
    Write multi-column data to CSV.

    Args:
        filepath: output file path
        columns: list of lists (each list is a column)
        headers: list of column header strings
    """
    n_rows = len(columns[0])
    with open(filepath, 'w') as f:
        if headers:
            f.write(",".join(headers) + "\n")
        for i in range(n_rows):
            row = [str(col[i]) for col in columns]
            f.write(",".join(row) + "\n")



# =============================================================================
# Pre-built test scenarios
# =============================================================================

def scenario_single_target(range_m=500, velocity=0, rcs=0, n_adc_samples=16384):
    """
    Single stationary target at specified range.
    Good for validating matched filter range response.
    """
    target = Target(range_m=range_m, velocity_mps=velocity, rcs_dbsm=rcs)

    adc = generate_adc_samples([target], n_adc_samples, noise_stddev=2.0)
    return adc, [target]


def scenario_two_targets(n_adc_samples=16384):
    """
    Two targets at different ranges — tests range resolution.
    Separation: ~2x range resolution (15m).
    """
    targets = [
        Target(range_m=300, velocity_mps=0, rcs_dbsm=10, phase_deg=0),
        Target(range_m=315, velocity_mps=0, rcs_dbsm=10, phase_deg=45),
    ]
    for _t in targets:
        pass

    adc = generate_adc_samples(targets, n_adc_samples, noise_stddev=2.0)
    return adc, targets


def scenario_multi_target(n_adc_samples=16384):
    """
    Five targets at various ranges and velocities — comprehensive test.
    """
    targets = [
        Target(range_m=100, velocity_mps=0, rcs_dbsm=20, phase_deg=0),
        Target(range_m=500, velocity_mps=30, rcs_dbsm=10, phase_deg=90),
        Target(range_m=1000, velocity_mps=-15, rcs_dbsm=5, phase_deg=180),
        Target(range_m=2000, velocity_mps=50, rcs_dbsm=0, phase_deg=45),
        Target(range_m=5000, velocity_mps=-5, rcs_dbsm=-5, phase_deg=270),
    ]
    for _t in targets:
        pass

    adc = generate_adc_samples(targets, n_adc_samples, noise_stddev=3.0)
    return adc, targets


def scenario_noise_only(n_adc_samples=16384, noise_stddev=5.0):
    """
    Noise-only scene — baseline for false alarm characterization.
    """
    adc = generate_adc_samples([], n_adc_samples, noise_stddev=noise_stddev)
    return adc, []


def scenario_dc_tone(n_adc_samples=16384, adc_value=128):
    """
    DC input — validates CIC decimation and DC response.
    """
    return [adc_value] * n_adc_samples, []


def scenario_sine_wave(n_adc_samples=16384, freq_hz=1e6, amplitude=50):
    """
    Pure sine wave at ADC input — validates NCO/mixer frequency response.
    """
    adc = []
    for n in range(n_adc_samples):
        t = n / FS_ADC
        val = round(128 + amplitude * math.sin(2 * math.pi * freq_hz * t))
        adc.append(max(0, min(255, val)))
    return adc, []


# =============================================================================
# Main: Generate all test vectors
# =============================================================================

def generate_all_test_vectors(output_dir=None):
    """
    Generate a complete set of test vectors for co-simulation.

    Creates:
      - adc_single_target.hex: ADC samples for single target
      - adc_multi_target.hex: ADC samples for 5 targets
      - adc_noise_only.hex: Noise-only ADC samples
      - adc_dc.hex: DC input
      - adc_sine_1mhz.hex: 1 MHz sine wave
      - ref_chirp_i.hex / ref_chirp_q.hex: Reference chirp for matched filter
      - bb_single_target_i.hex / _q.hex: Baseband I/Q for matched filter test
      - scenario_info.csv: Target parameters for each scenario
    """
    if output_dir is None:
        output_dir = os.path.dirname(os.path.abspath(__file__))


    n_adc = 16384  # ~41 us of ADC data

    # --- Scenario 1: Single target ---
    adc1, targets1 = scenario_single_target(range_m=500, n_adc_samples=n_adc)
    write_hex_file(os.path.join(output_dir, "adc_single_target.hex"), adc1, bits=8)

    # --- Scenario 2: Multi-target ---
    adc2, targets2 = scenario_multi_target(n_adc_samples=n_adc)
    write_hex_file(os.path.join(output_dir, "adc_multi_target.hex"), adc2, bits=8)

    # --- Scenario 3: Noise only ---
    adc3, _ = scenario_noise_only(n_adc_samples=n_adc)
    write_hex_file(os.path.join(output_dir, "adc_noise_only.hex"), adc3, bits=8)

    # --- Scenario 4: DC ---
    adc4, _ = scenario_dc_tone(n_adc_samples=n_adc)
    write_hex_file(os.path.join(output_dir, "adc_dc.hex"), adc4, bits=8)

    # --- Scenario 5: Sine wave ---
    adc5, _ = scenario_sine_wave(n_adc_samples=n_adc, freq_hz=1e6, amplitude=50)
    write_hex_file(os.path.join(output_dir, "adc_sine_1mhz.hex"), adc5, bits=8)

    # --- Reference chirp for matched filter ---
    ref_re, ref_im = generate_reference_chirp_q15()
    write_hex_file(os.path.join(output_dir, "ref_chirp_i.hex"), ref_re, bits=16)
    write_hex_file(os.path.join(output_dir, "ref_chirp_q.hex"), ref_im, bits=16)

    # --- Baseband samples for matched filter test (bypass DDC) ---
    bb_targets = [
        Target(range_m=500, velocity_mps=0, rcs_dbsm=10),
        Target(range_m=1500, velocity_mps=20, rcs_dbsm=5),
    ]
    bb_i, bb_q = generate_baseband_samples(bb_targets, FFT_SIZE, noise_stddev=1.0)
    write_hex_file(os.path.join(output_dir, "bb_mf_test_i.hex"), bb_i, bits=16)
    write_hex_file(os.path.join(output_dir, "bb_mf_test_q.hex"), bb_q, bits=16)

    # --- Scenario info CSV ---
    with open(os.path.join(output_dir, "scenario_info.txt"), 'w') as f:
        f.write("AERIS-10 Test Vector Scenarios\n")
        f.write("=" * 60 + "\n\n")

        f.write("System Parameters:\n")
        f.write(f"  Carrier: {F_CARRIER/1e9:.1f} GHz\n")
        f.write(f"  IF: {F_IF/1e6:.0f} MHz\n")
        f.write(f"  Chirp BW: {CHIRP_BW/1e6:.0f} MHz\n")
        f.write(f"  ADC: {FS_ADC/1e6:.0f} MSPS, {ADC_BITS}-bit\n")
        f.write(f"  Range resolution: {RANGE_RESOLUTION:.1f} m\n")
        f.write(f"  Wavelength: {WAVELENGTH*1000:.2f} mm\n")
        f.write("\n")

        f.write("Scenario 1: Single target\n")
        for t in targets1:
            f.write(f"  {t}\n")

        f.write("\nScenario 2: Multi-target (5 targets)\n")
        for t in targets2:
            f.write(f"  {t}\n")

        f.write("\nScenario 3: Noise only (stddev=5.0 LSB)\n")
        f.write("\nScenario 4: DC input (value=128)\n")
        f.write("\nScenario 5: 1 MHz sine wave (amplitude=50 LSB)\n")

        f.write("\nBaseband MF test targets:\n")
        for t in bb_targets:
            f.write(f"  {t}\n")



    return {
        'adc_single': adc1,
        'adc_multi': adc2,
        'adc_noise': adc3,
        'adc_dc': adc4,
        'adc_sine': adc5,
        'ref_chirp_re': ref_re,
        'ref_chirp_im': ref_im,
        'bb_i': bb_i,
        'bb_q': bb_q,
    }


if __name__ == '__main__':
    generate_all_test_vectors()
