13 KiB
GPS PPS peripheral
pps_wrapper is an APB-controlled, 125 MHz disciplined GPS timebase. Its raw
64-bit counter is immutable after reset. Tracking-channel measurement events
capture that counter into a per-channel bank; software reads the channel used
as the WLS timing datum and commits this atomic mailbox:
{GPS_TIME_AT_CAPTURE, EPOCH_COUNTER, EPOCH_SEQUENCE}
The core extrapolates the GPS reference to the commit instant from the counter
age. Before coarse alignment it uses nominal rate; afterwards it uses the
current disciplined rate estimate, preventing oscillator_ppm * compute_delay
from leaking into phase. CPU/WLS/APB latency is therefore not interpreted as PPS phase. A coarse
commit aligns the separate disciplined accumulator. Later commits update a
signed fixed-point PI rate word; holdover freezes the last rate word. The raw
counter is never slewed or reset by a navigation solution.
GPS_FRACTION_Q32 is a Q0.32 fraction of one GPS second. KP and KI are
signed 48-bit Q2.46 discrete coefficients. Q2.46 is required because the
125 MHz clock makes the low-bandwidth integral coefficient smaller than one
Q2.30 LSB. Phase, integrator, and rate diagnostics
are signed Q32 counter-tick values. MAX_SLEW_Q32 is the absolute rate limit
in Q32 ticks per 125 MHz clock; the reset value is approximately 20 ppm.
The PI controller is the hardware loop filter: Kp supplies proportional
phase correction and Ki is the retained frequency/integral state. There is
no additional phase IIR and no derivative term. An extra phase IIR was
modelled and rejected because it duplicated the small navigation pre-filter
and added acquisition/profile-transition delay. Software may change Kp,
Ki, maximum slew and lock policy while running; the core captures the whole
mailbox coherently at the next commit and preserves the integrator across a
normal profile transition.
Each rising PPS edge is held high for 100 ms (12,500,000 tracking-clock cycles
at 125 MHz), making the physical output easy to observe without changing the
disciplined edge timing. The width is derived from CLK_FREQ_HZ and the shared
package constant, so reduced-clock unit tests retain the same 10% duty cycle.
All interface, state, coefficient, intermediate-product and default widths are
defined once in pps_pkg.sv. PPS_PRODUCT_WIDTH is derived as
PPS_STATE_WIDTH + PPS_COEFF_WIDTH (64 + 48 = 112). It is the full-precision
combinational multiply result, not a 112-bit APB field or retained time state.
The hardware does not add POSTPONE_TOW. Baremetal must commit an already
epoch-aligned GPS reference, so the verified 160 ms subframe readout correction
is owned exactly once by the navigation software.
Tracking C/N0 and Doppler are not inputs to the PPS RTL. The tracking wrapper
publishes those measurements through its separate 20 ms APB mailbox. Firmware
uses C/N0, geometry, WLS clock sigma, integrity and Doppler velocity residual
to select acquire, precision, robust or holdover, then writes the
corresponding PI coefficients, slew limit and lock thresholds through this
PPS mailbox. Doppler also predicts the WLS clock-bias datum between commits.
This keeps quality policy and the small navigation pre-filter in software
while the deterministic PI loop, fixed-point rate state and physical edge
generation remain in hardware.
In a conventional absolute-pseudorange model, receiver clock bias and
receiver clock drift are the level and slope of the same physical clock:
b_r = c*delta_t_r and bdot_r = c*d(delta_t_r)/dt. The position-WLS bias
in this project is different: subtracting the common capture time and adding
STARTOFFSET makes it a reference-satellite-relative datum, approximately
c*STARTOFFSET - reference_range, rather than a pure receiver oscillator
offset.
The Doppler WLS still solves a fourth, common receiver clock/frequency-drift state. The PPS pre-filter does not use that state directly as the derivative of the position-WLS datum. Instead, it predicts that datum with:
position_datum_drift =
-reference_geometric_range_rate
+ c * reference_satellite_clock_drift
The geometric term uses the Doppler-WLS receiver velocity plus the selected reference satellite position and velocity. The PPS RTL sees only the resulting timestamped reference and loop configuration.
The APB register offsets live in pps_pkg.sv. Multiword input fields are
shadow registers and must be followed by one COMMIT write. A new mailbox
must not be written while STATUS.COMMIT_BUSY is set.
The capture_i vector is synchronous to PCLK. Each pulse stores the
Gray-synchronized 125 MHz counter and increments that channel's capture
sequence. The two-stage crossing adds a small fixed timestamp pipeline offset;
the SoC-level connection must use the exact PCLK event that updates
ABS_SAMPLE/REM_CODE_PHASE. CAPTURE_COUNTER initially refers to the integer
RF-sample event. Before writing EPOCH_COUNTER, navigation software subtracts
round((REM_CODE_PHASE / 1.023e6) * (125e6 / RF_HZ))
counter ticks, so the counter and corrected WLS observation refer to the same fractional code boundary. This removes an otherwise variable 0--61 ns term. The remaining PCLK/Gray pipeline delay is fixed and must be measured once on hardware.
APB register map
| Offset | Register | Access | Meaning |
|---|---|---|---|
0x00 |
CONTROL |
RW | bit 0 enable output, bit 1 track enable, bit 2 holdover, bit 3 clear sticky status (pulse) |
0x04 |
STATUS |
RO | aligned/valid/busy/stale/saturation/overrun state; bit 11 is PPS loop lock |
0x08/0x0c |
RAW_COUNTER |
RO | immutable 125 MHz counter; reading low latches high |
0x10 |
CAPTURE_SELECT |
RW | tracking-channel capture bank index |
0x14/0x18 |
CAPTURE_COUNTER |
RO | selected channel's captured counter |
0x1c |
CAPTURE_SEQUENCE |
RO | selected channel's capture count |
0x20/0x24 |
EPOCH_COUNTER |
shadow | counter paired with the WLS reference |
0x28/0x2c |
GPS_SECONDS |
shadow | continuous GPS seconds at the captured epoch |
0x30 |
GPS_FRACTION_Q32 |
shadow | Q0.32 fraction of that GPS second |
0x34 |
EPOCH_SEQUENCE |
shadow | modular freshness tag |
0x38/0x80 |
KP_Q46_LO/HI |
RW shadow | signed 48-bit Q2.46 proportional coefficient |
0x3c/0x84 |
KI_Q46_LO/HI |
RW shadow | signed 48-bit Q2.46 integral coefficient |
0x40 |
MAX_SLEW_Q32 |
RW shadow | absolute Q32 ticks/clock rate clamp |
0x44 |
COMMIT |
WO | bit 0 commit, bit 1 coarse-align commit |
0x48..0x74 |
diagnostics | RO | sequence, GPS time, phase, integrator, rate and pulse count |
0x78 |
DIAG_SNAPSHOT |
WO | bit 0 atomically snapshots all multiword diagnostics |
0x7c |
VERSION |
RO | RTL interface version (0x00020200) |
0x88 |
LOOP_CONFIG |
RW shadow | bits 15:8 acquire ticks, 23:16 release ticks, 31:24 consecutive acquire updates; bits 7:0 reserved |
Coarse alignment does not assert lock or drive the pin. The reset policy
requires five consecutive raw PI residuals within eight tracking-clock ticks
(64 ns at 125 MHz); a residual above 32 ticks (256 ns) releases lock. Runtime
profiles may widen the release threshold or require more good updates without
filtering the phase seen by the controller. Holdover retains the internal
frequency/lock state but deasserts STATUS.VALID; a newly committed bad
reference drops lock immediately. STATUS.LOCKED, STATUS.VALID, and the
physical PPS output therefore distinguish a held/aligned timebase from a
currently qualified one.
Firmware uses a two-second no-reference watchdog to enter holdover but keeps
the Doppler-predicted bias and PI state for bounded same-datum gaps. On
recovery it fills all mailbox shadow words, releases holdover immediately
before the normal commit toggle, and therefore applies the first coherent
post-gap reference (v2.2 rejects normal PI commits that arrive while holdover
is still asserted). The physical PPS already runs from the held rate word and
does not phase-step; STATUS.VALID may precede the PI apply only by the
bounded synchronizer/commit-pipeline latency. A datum change or an observation
gap beyond the prediction horizon uses a coarse commit and remains invalid
until lock is reacquired.
Diagnostic words do not change until another DIAG_SNAPSHOT request completes;
software polls STATUS.DIAG_BUSY before reading the group. The disciplined
timebase continues in holdover and while the physical output is disabled;
CONTROL.ENABLE gates pps_out and validity, not timekeeping.
This also applies to the single-word ACTIVE_SEQUENCE and PULSE_COUNT
registers: they belong to the same frozen diagnostic group. A live debugger
must halt the CPU briefly, request a new snapshot, wait for status bit 10 to
clear, and only then read 0x48..0x74. For example, with OpenOCD's GDB monitor:
monitor halt
monitor mww 0x1002a078 1
monitor sleep 10
x/wx 0x1002a004
x/wx 0x1002a048
x/wx 0x1002a05c
x/wx 0x1002a060
x/wx 0x1002a074
monitor resume
Reading ACTIVE_SEQUENCE or PULSE_COUNT without this request can return the
previous snapshot, including reset zero, even while the core is accepting
commits and generating pulses.
Run lint plus all five Questa tests from this directory:
make -C tb check
pps_wrapper_tb uses the same ordered MMIO writes as gps_system/src/gps_pps.c and
injects CPU/APB latency; it separately verifies that a held normal commit is
not applied and that the firmware's shadow-first release sequence applies the
first recovery update. pps_core_tb independently stresses signed PI math,
slew/anti-windup saturation, stale/future epochs, phase saturation, sequence
wrap, holdover and output disable. pps_convergence_tb supplies an independent
GPS truth clock, a +15 ppm oscillator error followed by a +8 ppm temperature-like
step, deterministic measurement noise, varying 3--27 cycle software latency,
and 5 Hz navigation commits. It checks phase and rate convergence numerically.
pps_adaptive_tb changes Kp, Ki, maximum slew and lock policy while the
loop is live, checks integrator preservation, validates raw-residual lock
acquisition/release, and verifies that holdover suppresses validity.
CLK_FREQ_HZ is reduced only to shorten simulation; tick/Q-format behavior is
unchanged.
The convergence waveform also exports real-valued views: phase/mapping/PPS
errors in counter ticks, rate and target rate in ppm, plus fixed-scale
normalized errors. wave.do opens these as analog plots automatically. Its
test clock period is 1 second / CLK_FREQ_HZ, so adjacent PPS pulses are one
second apart on Questa's displayed time axis even though the event count is
reduced for simulation speed.
For an interactive waveform in a second terminal:
cd hardware/external/gps/rtl/pps/tb
./run.sh # closed-loop convergence (default)
./run.sh converge # same as the default
./run.sh precision # fixed-point/quantization sweep
./run.sh core # arithmetic/limit corner cases
./run.sh wrapper # APB and CDC mailbox view
SoC integration and physical accuracy
The peripheral is mapped at 0x1002a000; it has no PLIC interrupt. Tracking
channels drive capture_i only from their nav-solve event, and the generated
Rocket/VCU118 and Genesys2 tops expose gps_pps as an output. VCU118 routes it
to the otherwise-unused PMOD1/J53.8 signal (R29, FPGA-side LVCMOS12 through
the board's 3.3 V level shifter). Genesys2 routes it to the otherwise-unused
PMOD JC10 signal (AK28, LVCMOS33). Both use 8 mA drive and FAST slew.
At 125 MHz a PPS edge can occur only on an 8 ns grid. The fractional accumulator provides much finer long-term frequency resolution, but it cannot place one edge between clock edges. Absolute accuracy also contains WLS, atmosphere/multipath, clock-distribution and the fixed PCLK/Gray-capture offset; the latter must be measured and calibrated on the board. Thus this design is nanosecond-order hardware, not a claim of 1 ns absolute accuracy.
Functional precision boundary
pps_precision_tb.sv runs the same 5 Hz discrete PI scaling with a +15 ppm to
+8 ppm oscillator step and reports the steady fixed-point limit cycle. It
separates range width from fractional precision and writes pps_precision.csv.
For this nominal loop:
- coefficient Q46 and Q40 both give about 0.57 ns RMS; Q39 gives 1.74 ns,
Q38 gives 4.06 ns, Q36 gives 13.61 ns, and Q32 loses
Kientirely; - keeping Q46 while reducing the coefficient word to signed 19 bits is bit-identical for the nominal gains; 18 bits saturates Kp and raises the floor to about 21 ns. A 24-bit word leaves about 37x Kp tuning headroom;
- GPS fraction Q30 is below 1 ns resolution, Q28 produces about 1.74 ns RMS, and Q26 about 6.90 ns RMS in this deterministic case;
- phase storage needs only four fractional tick bits for a sub-ns loop floor here, but rate word Q32 remains important: Q31 gives about 14 ns and Q30 about 42 ns of steady phase offset due to accumulated rate quantization;
- the independent 125 MHz output grid contributes
8/sqrt(12) = 2.31 nsRMS for uniformly distributed edge quantization, even if the control arithmetic is finer.
These numbers are an arithmetic quantization floor, not total PPS accuracy; WLS, RF clock, atmosphere, multipath and board I/O delay remain separate.