# GPS PPS end-to-end Linux validation This package is a deterministic, causal validation of the project timing path from synthetic GPS LNAV data to the physical PPS edge. It exists separately from `pps_python`, whose models concentrate on WLS noise, oscillator behavior, and adaptive-loop performance. The validation does not use Linux wall-clock scheduling. The 1 kHz, 50 bit/s, RF-sample, PCLK, and 125 MHz events advance from synthetic timestamps, making the signed absolute-time result repeatable on a normal Linux host. ## What is modeled The model follows the current firmware and RTL conventions: 1. It generates deterministic GPS LNAV subframes with D29*/D30* data inversion and valid parity for all ten 30-bit words. 2. Each navigation bit is expanded to twenty 1 kHz prompt-sign samples. 3. The `bit_gen.sv` 40 ms `1 -> 0` transition acquisition, majority decision, 20 ms `bit_valid` cadence, and registered ABS_SAMPLE behavior are applied. 4. The tracking wrapper's chronological `2 + 300 + 8 = 310` bit window, preamble search, parity check, `load_sigs`, and subframe interrupt ordering are applied. 5. `nav_solve_timer` is initialized from HOW Z-count and counts later bit-valid events. Navigation-solve interrupt, PPS counter capture, capture sequence, and a 32-bit RF ABS_SAMPLE wrap are exercised. 6. Firmware reconstructs periodic TOW from the HOW/sample anchor and evaluates `TOW + POSTPONE_TOW + STARTOFFSET - b/c`. 7. The timestamped 125 MHz PPS peripheral performs coarse/normal mapping, ignores synthetic CPU/APB delay as phase, acquires lock after five valid updates, and generates integer-second edges. 8. DUT edges are compared with an independent trusted PPS. Optional DUT and trusted-output delays make board, cable, and vendor delay sign explicit. This is a digital epoch-path validation, not an RF waveform simulator. The existing `pps_python` tests continue to cover WLS, oscillator, PI, adaptive quality, and holdover behavior. ## Run From `soc/software`: ```bash python3 -m pps_end_to_end_validation.pps_end_to_end python3 -m unittest pps_end_to_end_validation/test_pps_end_to_end.py ``` 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 ``` Positive `DUT - trusted PPS` means the project output is late. The default 32 ns remainder is the explicitly configured four-tick PCLK capture pipeline; it is not part of the LNAV-bit mismatch. Run one scenario or obtain machine-readable output: ```bash python3 -m pps_end_to_end_validation.pps_end_to_end \ --mode current-rtl --postpone-ms 160 --json python3 -m pps_end_to_end_validation.pps_end_to_end \ --mode current-bit \ --dut-output-delay-ns 12 \ --trusted-output-delay-ns 35 ``` ## Closed-loop stability and dynamic tuning The bit-level command above isolates the absolute epoch convention. The companion stability analysis connects that result to the fixed-point firmware/RTL PI model, replays the real GDOP and tracked-satellite history in `fusion/logs/gps_live_5min_20260713.csv`, and synthesizes only the telemetry that the repository did not capture: ```bash python3 -m pps_end_to_end_validation.stability_analysis python3 -m unittest \ pps_end_to_end_validation/test_stability_analysis.py ``` Generated results are in `output/stability/`: - `stability_report.md`: real-data inventory, discrete poles, absolute phase wander, period jitter, dynamic parameter sweep, and recommendations; - `parameter_sweep.csv`: machine-readable results for every scenario/tuning; - `synthetic_telemetry.csv`: 5 Hz ABS_SAMPLE, REM_CODE_PHASE, corrected 125 MHz capture, HDOP/VDOP/TDOP/GDOP, C/N0, pseudorange noise, clock sigma/drift, oscillator and timing-error inputs. The older fusion capture used by `stability_analysis.py` contains 989 GDOP values and 990 tracked-satellite values (eight throughout), but no PPS phase/rate, ABS_SAMPLE, C/N0, TDOP/HDOP, pseudorange residual, oscillator temperature, or trusted-edge/TIC samples. The newer live-board workflow below supplies many of those PPS status fields, while ABS_SAMPLE, REM_CODE_PHASE, temperature, and trusted-edge timing remain unavailable. The stability CSV labels replacements as synthetic; it must not be read as board telemetry. The current acquire, precision, and robust fixed-point poles are inside the unit circle at the 200 ms update rate, and the stress analysis observes no unlock. It also exposes an important metrics issue: cycle-to-cycle PPS period jitter can remain below 20 ns while absolute phase moves slowly through tens of nanoseconds. A physical 20 ns maximum therefore requires a common-clock time-interval-counter capture and cable/output-delay calibration; a p95 period 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. The stability analysis does not alter loop constants. Production firmware now uses the current-RTL-compatible 180 ms epoch correction described below. ## Live board telemetry replay `hardware_replay.py` accepts the host-timestamped UART CSV produced by the fusion capture utility. It extracts the firmware fields that actually drive or describe PPS: raw/filtered WLS clock bias, innovation, Doppler datum drift, TDOP/GDOP/HDOP/VDOP, clock sigma, C/N0, tracked/used satellites, velocity/FDE, reference SVID, fractional capture correction, RTL phase residual, rate word, integrator, lock/profile, pulse count, and commit sequence. Capture and replay from `soc/software`: ```bash python3 fusion/serial_capture.py \ --port /dev/ttyUSB2 --baud 460800 --duration 300 \ --output /tmp/genesys2_pps.csv python3 -m pps_end_to_end_validation.hardware_replay \ --input /tmp/genesys2_pps.csv \ --output-dir pps_end_to_end_validation/output/hardware_replay python3 -m unittest \ pps_end_to_end_validation.test_hardware_replay ``` Raw UART files are ignored by Git because `[LOCATION]` records contain exact coordinates. The replay output is normalized to PPS fields and deliberately omits latitude/longitude. For an already-running board, replay imports the first logged locked filter/PI snapshot. Starting the model from reset would erase the observed integrator and create a false acquisition transient. When status is decimated, commit sequence numbers expand the trace back to the nominal 200 ms cadence; intermediate values are explicitly marked `interpolated=true`. The report separates three quantities that should not be conflated: - direct `hw_phase` statistics from the board; - fixed-point PI reproduction and a telemetry-conditioned model replay; and - inferred active phase obtained by integrating `rate_cmd` under a constant oscillator assumption. Only the first two are directly constrained by firmware/RTL telemetry. The third is useful for diagnosing slow wander but is not a trusted-generator measurement. `hw_phase` itself is a 5 Hz commit residual, not the physical PPS pin offset. A calibrated, common-clock TIC/scope capture is still required for a maximum-20 ns output claim. ## 20 ms diagnosis The current tracking wrapper shifts the current bit into `shift_reg_n`, while its preamble/parity predicate reads the preceding `shift_reg_p`. On the eighth following preamble bit, the validator therefore sees only the previous seven bits. The register contains all eight after the edge, but the bit-valid edge condition has ended. Validation occurs on the ninth bit, 20 ms later. The implemented interface is consequently: ```text current RTL delivery 9 * 20 ms = 180 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 ``` 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. ## Other regression coverage The unit tests verify: - parity-valid LNAV round-trip and rejection after one corrupted data bit; - 1 kHz prompt-sign to 50 bit/s generation and the registered 1 ms sample lag; - ninth-bit delivery in current RTL and eighth-bit delivery in the candidate implementation; - constant signed PPS error over successive physical edges; - 180 ms firmware compatibility with current RTL; - invariance to synthetic CPU/APB stalls when capture timestamps are used; - DUT-versus-trusted output-delay sign; and - rejection behavior for the firmware-permitted `NAV_SOLVE_TIMER=40` case. The `N=40` case divides the GPS week but not a 300-bit subframe. When firmware refreshes the HOW/sample anchor after six seconds, the timer phase is half a solve period away from that anchor and the current `N * 20 ms` snap rejects otherwise valid solves. Production `N=10` divides 300 and is unaffected. If general timer periods are required, reconstruct elapsed time on the fundamental 20 ms navigation-bit grid. A narrower compatibility restriction is to accept only periods for which `300 % N == 0`. ## Hardware confirmation Before selecting a production correction: 1. Probe `bit_valid`, the final preamble bits, `preamble_ok`, parity result, and `load_sigs` with ILA. 2. Count whether delivery is on following bit 8 or 9 in the flashed bitstream. 3. Measure DUT and trusted PPS with a common time-interval counter and record the sign. The diagnosis predicts a DUT edge approximately 20 ms late. 4. Apply only nanosecond-scale output/cable calibration after the epoch mismatch is removed.