Fix GPS acquisition visibility and pull-in edge cases
This commit is contained in:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user