#!/usr/bin/env python3 """Model-level checks for acquisition search edge cases.""" from pathlib import Path import re TEST_DIR = Path(__file__).resolve().parent REPO_ROOT = TEST_DIR.parents[3] DEFINITIONS = REPO_ROOT / "hardware/external/gps/rtl/common/definitions.sv" ACQ_TOP = REPO_ROOT / "hardware/external/gps/rtl/acq_src/acq_top.sv" def read_parameters(path): text = path.read_text(encoding="utf-8") params = {} parameter_re = ( r"parameter\s+([A-Za-z_][A-Za-z0-9_]*)\s*=\s*([^;]+);" ) for match in re.finditer(parameter_re, text): value = match.group(2).strip() if re.fullmatch(r"\d+", value): params[match.group(1)] = int(value) elif re.fullmatch(r"\d+\.\d+", value): params[match.group(1)] = float(value) return params def check(condition, message): if not condition: raise AssertionError(message) def main(): params = read_parameters(DEFINITIONS) acq_top = ACQ_TOP.read_text(encoding="utf-8") samples_per_code = params["SAMPLES_PER_CODE"] num_freq_bin = params["NUM_FREQ_BIN"] freq_bin_wl = params["FREQ_BIN_WL"] dopp_step = params["DOPP_STEP"] pullin_freq_step = params["PULLIN_FREQ_STEP"] num_pullin = params["NUM_PULLIN"] acq_base_freq = params["ACQ_BASE_FREQ"] check(samples_per_code == 16368, "unexpected sample count model") check(num_freq_bin == 41, "unexpected coarse Doppler bin count") check(num_pullin == 11, "unexpected pull-in bin count") check(dopp_step == 500, "unexpected coarse Doppler step") check(pullin_freq_step == 100, "unexpected pull-in Doppler step") check(acq_base_freq == 4082000, "unexpected acquisition base frequency") check( "freq_bin_cnt <= (max_freq_bin_idx == 0) ? 0 : " "(max_freq_bin_idx - 1);" in acq_top, "RTL must saturate pull-in search at coarse bin zero", ) check( "rf_wr_addr == 2*SAMPLES_PER_CODE - 1" in acq_top, "RTL must capture exactly two 1 ms code windows", ) max_encoded_bin = (1 << freq_bin_wl) - 1 legacy_bin_zero_start = (0 - 1) & max_encoded_bin check( legacy_bin_zero_start >= num_freq_bin, "legacy bin-zero decrement no longer demonstrates an out-of-range wrap", ) covered_frequencies = set() for coarse_bin in range(num_freq_bin): pullin_start = 0 if coarse_bin == 0 else coarse_bin - 1 check(0 <= pullin_start < num_freq_bin, "pull-in start out of coarse range") coarse_frequency = acq_base_freq + dopp_step * coarse_bin for pullin_bin in range(num_pullin): pullin_frequency = ( acq_base_freq + dopp_step * pullin_start + pullin_freq_step * pullin_bin ) covered_frequencies.add(pullin_frequency) if coarse_bin == 0: check( pullin_frequency >= acq_base_freq, "coarse bin zero pull-in searched below the table", ) else: check( coarse_frequency - dopp_step <= pullin_frequency <= coarse_frequency + dopp_step, "pull-in search does not cover the expected +/-500 Hz window", ) lowest_expected = acq_base_freq highest_expected = ( acq_base_freq + dopp_step * (num_freq_bin - 2) + pullin_freq_step * (num_pullin - 1) ) check(min(covered_frequencies) == lowest_expected, "unexpected low search edge") check(max(covered_frequencies) == highest_expected, "unexpected high search edge") check(len(covered_frequencies) >= num_freq_bin * 5, "pull-in coverage collapsed") last_sample_written = (2 * samples_per_code) - 1 check(last_sample_written == 32735, "unexpected two-code capture fencepost") print( "ACQ search model PASS: " f"coarse_bins={num_freq_bin} pullin_bins={num_pullin} " f"freq_range={lowest_expected}..{highest_expected}Hz " f"last_sample={last_sample_written}" ) if __name__ == "__main__": main()