fix(pps): align firmware TOW with current RTL

This commit is contained in:
2026-07-28 21:53:33 +03:00
parent 988f9686e7
commit a697284bff
18 changed files with 1002 additions and 960 deletions
@@ -49,9 +49,9 @@ The default command executes the diagnostic matrix:
```text
scenario bits correction reference error DUT - trusted PPS
current-rtl/180ms 9 180 ms -0.000032 ms +0.000032 ms
current-rtl/160ms 9 160 ms -20.000032 ms +20.000032 ms
current-bit/160ms 8 160 ms -0.000032 ms +0.000032 ms
current-rtl/180ms 9 180 ms -0.000032 ms +0.000032 ms
```
Positive `DUT - trusted PPS` means the project output is late. The default
@@ -110,7 +110,8 @@ jitter assertion or synthetic run cannot certify it.
The sweep includes the current coefficients, quieter/faster precision loops,
a robust-noise guard, a larger Doppler-drift gain, and a quality-scheduled
candidate. The generated report contains the current numerical comparison.
No production constants are changed by the analysis.
The stability analysis does not alter loop constants. Production firmware now
uses the current-RTL-compatible 180 ms epoch correction described below.
## 20 ms diagnosis
@@ -124,15 +125,18 @@ The implemented interface is consequently:
```text
current RTL delivery 9 * 20 ms = 180 ms
firmware POSTPONE_TOW 8 * 20 ms = 160 ms
signed epoch mismatch 20 ms
production POSTPONE_TOW 9 * 20 ms = 180 ms
signed epoch mismatch 0 ms
former firmware value 8 * 20 ms = 160 ms
former signed mismatch 20 ms
```
With the current bitstream, `POSTPONE_TOW=0.180` is a firmware-only workaround
and must be used consistently by both satellite-state and PPS calculations.
It is 180 ms, not 190 ms. The preferred permanent correction is to validate a
candidate window containing the current eighth bit and retain the intended
160 ms firmware compensation.
Production firmware now uses `POSTPONE_TOW=0.180` as the current-RTL-compatible
correction, consistently in both satellite-state and PPS calculations. It is
180 ms, not 190 ms. If RTL is later changed to validate a candidate window
containing the current eighth bit, firmware must return to 160 ms in the same
coordinated release.
The validation intentionally does not modify production RTL or firmware.
@@ -7,7 +7,7 @@ This report is generated by `stability_analysis.py`. The absolute LNAV epoch pat
- Current RTL delivered the validated subframe after 9 following bits.
- With 180 ms postponement, reference error was -0.000032 ms.
- DUT minus trusted PPS was 32.01 ns. That remainder is the model's configured four-PCLK capture pipeline, not a measured board delay.
- The production source still defines `POSTPONE_TOW` as 160 ms; this analysis uses the requested 180 ms candidate configuration.
- Production firmware and this analysis both define `POSTPONE_TOW` as 180 ms for the current RTL delivery semantics.
## Repository hardware data
@@ -589,7 +589,7 @@ def simulate_pps_core(
@dataclass(frozen=True)
class EndToEndConfig:
decoder_mode: DecoderMode = DecoderMode.CURRENT_RTL
postpone_tow_s: float = 0.160
postpone_tow_s: float = 0.180
nav_solve_period_bits: int = 10
propagation_s: float = 0.070
pps_capture_pipeline_ticks: int = 4
@@ -790,14 +790,14 @@ def default_comparison() -> tuple[EndToEndResult, ...]:
run_end_to_end(EndToEndConfig()),
run_end_to_end(
EndToEndConfig(
decoder_mode=DecoderMode.CURRENT_BIT,
decoder_mode=DecoderMode.CURRENT_RTL,
postpone_tow_s=0.160,
)
),
run_end_to_end(
EndToEndConfig(
decoder_mode=DecoderMode.CURRENT_RTL,
postpone_tow_s=0.180,
decoder_mode=DecoderMode.CURRENT_BIT,
postpone_tow_s=0.160,
)
),
)
@@ -838,7 +838,7 @@ def main(argv: Sequence[str] | None = None) -> int:
default="matrix",
help="run the three-way diagnosis matrix or one decoder mode",
)
parser.add_argument("--postpone-ms", type=float, default=160.0)
parser.add_argument("--postpone-ms", type=float, default=180.0)
parser.add_argument("--propagation-ms", type=float, default=70.0)
parser.add_argument("--capture-pipeline-ticks", type=int, default=4)
parser.add_argument("--compute-delay-ms", type=float, default=10.0)
@@ -597,8 +597,8 @@ def write_report(
f"{epoch_result.pps.edges[0].dut_minus_trusted_s * 1.0e9:.2f} ns. "
"That remainder is the model's configured four-PCLK capture pipeline, "
"not a measured board delay.",
"- The production source still defines `POSTPONE_TOW` as 160 ms; this "
"analysis uses the requested 180 ms candidate configuration.",
"- Production firmware and this analysis both define `POSTPONE_TOW` "
"as 180 ms for the current RTL delivery semantics.",
"",
"## Repository hardware data",
"",
@@ -1,5 +1,7 @@
from __future__ import annotations
from pathlib import Path
import re
import unittest
from pps_end_to_end_validation.pps_end_to_end import (
@@ -50,6 +52,22 @@ class LnavDecoderTests(unittest.TestCase):
class PpsEndToEndTests(unittest.TestCase):
def test_model_default_matches_production_firmware_postpone_tow(self):
header = (
Path(__file__).resolve().parents[1]
/ "gps_system/header/gps/gps_ip_defines.h"
).read_text(encoding="utf-8")
match = re.search(
r"#define\s+POSTPONE_TOW\s+\(double\)\(([0-9.]+)\)",
header,
)
self.assertIsNotNone(match)
self.assertAlmostEqual(float(match.group(1)), 0.180)
self.assertAlmostEqual(
EndToEndConfig().postpone_tow_s,
float(match.group(1)),
)
def test_bit_generator_recreates_50bps_stream_and_registered_sample_lag(self):
result = run_end_to_end(EndToEndConfig())
self.assertEqual(result.bit_valid_events, 2 + 3 * 300)
@@ -76,7 +94,7 @@ class PpsEndToEndTests(unittest.TestCase):
self.assertGreater(result.firmware_rejected_nav_solves, 0)
def test_current_rtl_and_160ms_correction_make_pps_20ms_late(self):
result = run_end_to_end(EndToEndConfig())
result = run_end_to_end(EndToEndConfig(postpone_tow_s=0.160))
offset_s = result.pps.edges[0].dut_minus_trusted_s
self.assertAlmostEqual(result.expected_semantic_offset_ms, 20.0, places=9)
self.assertGreater(offset_s, 0.019999)
@@ -86,17 +104,18 @@ class PpsEndToEndTests(unittest.TestCase):
def test_current_bit_validation_and_160ms_remove_lnav_bit_error(self):
result = run_end_to_end(
EndToEndConfig(decoder_mode=DecoderMode.CURRENT_BIT)
EndToEndConfig(
decoder_mode=DecoderMode.CURRENT_BIT,
postpone_tow_s=0.160,
)
)
self.assertEqual(result.first_subframe_readout_bits, 8)
self.assertEqual(result.first_nav_solve_bit, 18)
self.assertAlmostEqual(result.expected_semantic_offset_ms, 0.0, places=9)
self.assertLess(abs(result.pps.edges[0].dut_minus_trusted_s), 100.0e-9)
def test_180ms_firmware_workaround_matches_current_rtl(self):
result = run_end_to_end(
EndToEndConfig(postpone_tow_s=0.180)
)
def test_180ms_production_firmware_matches_current_rtl(self):
result = run_end_to_end(EndToEndConfig())
self.assertEqual(result.first_subframe_readout_bits, 9)
self.assertAlmostEqual(result.expected_semantic_offset_ms, 0.0, places=9)
self.assertLess(abs(result.pps.edges[0].dut_minus_trusted_s), 100.0e-9)
@@ -119,12 +138,16 @@ class PpsEndToEndTests(unittest.TestCase):
result = run_end_to_end(
EndToEndConfig(
decoder_mode=DecoderMode.CURRENT_BIT,
postpone_tow_s=0.160,
dut_output_delay_s=12.0e-9,
trusted_output_delay_s=35.0e-9,
)
)
baseline = run_end_to_end(
EndToEndConfig(decoder_mode=DecoderMode.CURRENT_BIT)
EndToEndConfig(
decoder_mode=DecoderMode.CURRENT_BIT,
postpone_tow_s=0.160,
)
)
delta = (
result.pps.edges[0].dut_minus_trusted_s