Fix GPS acquisition visibility and pull-in edge cases

This commit is contained in:
Codex
2026-08-04 13:48:48 +03:00
parent 30179df8e6
commit 28b55fdfb6
7 changed files with 333 additions and 19 deletions
+6 -1
View File
@@ -1,5 +1,6 @@
CC ?= gcc
CFLAGS ?= -O2 -std=c11 -Wall -Wextra -Werror
PYTHON ?= python3
ROOT := ..
TARGETS := test_imu_calibration test_gps_nav_epoch \
@@ -18,7 +19,7 @@ TARGETS += test_gps_flash_journal
TARGETS += test_gps_assistance_data
TARGETS += test_gps_assistance_store
.PHONY: all run sanitize clean
.PHONY: all run python-models sanitize clean
all: $(TARGETS)
@@ -128,6 +129,10 @@ run: $(TARGETS)
./test_gps_pps_quality
./test_gps_pps_adaptive_loop
./test_gps_visibility
$(PYTHON) test_acq_search_model.py
python-models:
$(PYTHON) test_acq_search_model.py
sanitize:
$(CC) -O1 -g -std=c11 -Wall -Wextra -Werror \
@@ -0,0 +1,117 @@
#!/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()
@@ -21,6 +21,7 @@ typedef struct {
bool visible[GPS_ACQ_MANAGER_PRN_COUNT];
bool tracked[GPS_ACQ_MANAGER_PRN_COUNT];
bool hardware_busy[GPS_ACQ_MANAGER_PRN_COUNT];
bool measurement_alive[GPS_ACQ_MANAGER_PRN_COUNT];
uint64_t invisible_since[GPS_ACQ_MANAGER_PRN_COUNT];
uint64_t nav_ready_tick[GPS_ACQ_MANAGER_PRN_COUNT];
unsigned int not_busy_polls[GPS_ACQ_MANAGER_PRN_COUNT];
@@ -63,7 +64,9 @@ static unsigned int nav_ready_count(const receiver_sim_t* sim)
return count;
}
static void sim_init(receiver_sim_t* sim)
static void sim_init_with_visibility_probe(
receiver_sim_t* sim,
uint64_t visibility_probe_ticks)
{
memset(sim, 0, sizeof(*sim));
const gps_acq_manager_config_t config = {
@@ -71,7 +74,7 @@ static void sim_init(receiver_sim_t* sim)
.hot_retry_delay_ticks = 30u,
.hot_window_ticks = 7200u,
.unknown_probe_interval_ticks = 2u,
.visibility_probe_interval_ticks = 60u,
.visibility_probe_interval_ticks = visibility_probe_ticks,
.max_hot_streak = 4u
};
gps_acq_manager_init(&sim->manager, &config);
@@ -87,6 +90,11 @@ static void sim_init(receiver_sim_t* sim)
sim->engine_prn = GPS_ACQ_MANAGER_INVALID_PRN;
}
static void sim_init(receiver_sim_t* sim)
{
sim_init_with_visibility_probe(sim, 60u);
}
static void cache_ephemeris(receiver_sim_t* sim, unsigned int prn)
{
gps_ephemeris_t ephemeris;
@@ -141,6 +149,7 @@ static void sim_complete_engine(receiver_sim_t* sim)
gps_acq_manager_mark_tracking(&sim->manager, prn);
sim->tracked[prn] = true;
sim->hardware_busy[prn] = true;
sim->measurement_alive[prn] = true;
sim->not_busy_polls[prn] = 0u;
sim->invisible_since[prn] = 0u;
gps_ephemeris_t restored;
@@ -173,6 +182,7 @@ static void sim_complete_engine(receiver_sim_t* sim)
static void sim_mark_tracking_lost(receiver_sim_t* sim, unsigned int prn)
{
sim->tracked[prn] = false;
sim->measurement_alive[prn] = false;
sim->nav_ready_tick[prn] = 0u;
sim->not_busy_polls[prn] = 0u;
gps_acq_manager_mark_lost(
@@ -201,6 +211,7 @@ static void sim_update_tracking(receiver_sim_t* sim)
SIM_UNLOCK_TICKS)
{
sim->hardware_busy[prn] = false;
sim->measurement_alive[prn] = false;
if(!sim->drop_loss_irq)
{
sim_mark_tracking_lost(sim, prn);
@@ -223,6 +234,29 @@ static void sim_update_tracking(receiver_sim_t* sim)
}
}
static unsigned int manager_count_state(
const receiver_sim_t* sim,
gps_acq_prn_state_t state)
{
return gps_acq_manager_count_state(&sim->manager, state);
}
static void sim_reconcile_stale_locked_measurements(receiver_sim_t* sim)
{
for(unsigned int prn = 0u;
prn < GPS_ACQ_MANAGER_PRN_COUNT;
prn++)
{
if((gps_acq_manager_state(&sim->manager, prn) ==
GPS_ACQ_PRN_STALE) &&
sim->hardware_busy[prn] &&
sim->measurement_alive[prn])
{
gps_acq_manager_mark_tracking(&sim->manager, prn);
}
}
}
static void sim_step(receiver_sim_t* sim)
{
sim->tick++;
@@ -566,6 +600,79 @@ static void test_incomplete_almanac_keeps_full_round_robin(void)
sim.search_attempts[5u]);
}
static void test_wrong_sky_model_safety_probe_finds_real_satellites(void)
{
receiver_sim_t sim;
sim_init_with_visibility_probe(&sim, 5u);
for(unsigned int prn = 0u;
prn < GPS_ACQ_MANAGER_PRN_COUNT;
prn++)
{
assert(gps_acq_manager_set_visibility(
&sim.manager, prn, true, false, -900));
}
for(unsigned int prn = 0u ; prn < 4u ; prn++)
{
assert(gps_acq_manager_set_visibility(
&sim.manager, prn, true, true, 500 - (int)prn));
}
for(unsigned int prn = 10u ; prn < 14u ; prn++)
{
sim.visible[prn] = true;
}
gps_acq_manager_set_visibility_mode(&sim.manager, true, sim.tick);
sim_start_engine(&sim);
uint64_t start_tick = sim.tick;
while((tracked_count(&sim) < 4u) &&
((sim.tick - start_tick) < 120u))
{
sim_step(&sim);
}
assert(tracked_count(&sim) == 4u);
for(unsigned int prn = 10u ; prn < 14u ; prn++)
{
assert(sim.tracked[prn]);
assert(sim.search_attempts[prn] != 0u);
}
printf("wrong-sky-model recovery telemetry: ticks=%lu attempts=[%u,%u,%u,%u]\n",
(unsigned long)(sim.tick - start_tick),
sim.search_attempts[10u], sim.search_attempts[11u],
sim.search_attempts[12u], sim.search_attempts[13u]);
}
static void test_stale_locked_measurements_recover_tracking_state(void)
{
receiver_sim_t sim;
sim_init(&sim);
for(unsigned int prn = 0u ; prn < GPS_TRCK_IP_COUNT ; prn++)
{
sim.visible[prn] = true;
}
sim_start_engine(&sim);
while(tracked_count(&sim) < GPS_TRCK_IP_COUNT)
{
sim_step(&sim);
}
gps_acq_manager_mark_stale(&sim.manager, 7u);
gps_acq_manager_mark_stale(&sim.manager, 8u);
assert(manager_count_state(&sim, GPS_ACQ_PRN_TRACKING) ==
GPS_TRCK_IP_COUNT - 2u);
assert(manager_count_state(&sim, GPS_ACQ_PRN_STALE) == 2u);
sim_reconcile_stale_locked_measurements(&sim);
assert(manager_count_state(&sim, GPS_ACQ_PRN_TRACKING) ==
GPS_TRCK_IP_COUNT);
assert(manager_count_state(&sim, GPS_ACQ_PRN_STALE) == 0u);
gps_acq_manager_mark_stale(&sim.manager, 8u);
sim.measurement_alive[8u] = false;
sim_reconcile_stale_locked_measurements(&sim);
assert(gps_acq_manager_state(&sim.manager, 8u) ==
GPS_ACQ_PRN_STALE);
}
static void test_missed_completion_interrupt_is_polled(void)
{
receiver_sim_t sim;
@@ -620,6 +727,8 @@ int main(void)
test_partial_loss_short_and_15_second_gaps();
test_hot_failures_do_not_starve_cold_search();
test_incomplete_almanac_keeps_full_round_robin();
test_wrong_sky_model_safety_probe_finds_real_satellites();
test_stale_locked_measurements_recover_tracking_state();
test_missed_completion_interrupt_is_polled();
test_missed_tracking_loss_interrupt_is_reconciled();
test_stale_cache_forces_ephemeris_decode();