Covers the exact configuration the fix is for: GNSS-SDR.internal_fs_sps
not a multiple of 1 kHz (4000123 Hz), coherent_integration_time_ms (4)
spanning more than one code period (GPS L1 C/A's own code period is
1 ms), and max_dwells (5) > 1. Asserts a successful acquisition and
checks the reported Doppler/delay against the known synthetic signal's
truth, with tolerances the dwell-boundary drift this fix addresses
would clearly exceed.
Registering it in acq_test (tests/CMakeLists.txt) surfaced that the
target had apparently never actually linked successfully in this
environment before: it links FirFilter/SignalGenerator/GenSignalSource
but was missing input_filter_adapters/signal_generator_adapters/
signal_source_adapters, and separately hit a Linux-only undefined
libboost_thread symbol inside the system's packaged
libgnuradio-runtime.so, fixed with the standard
--copy-dt-needed-entries linker workaround (guarded to Linux only).
Neither is specific to this new test; fixing them was necessary to
build and run the suite at all, and the rest of acq_test (20 tests
total) now passes too.
Preserve the candidate anchor until its expected confirmation point.
Replace it only after confirmation is missed, preventing payload
correlations from repeatedly displacing a valid preamble.
test_main.cc #included every unit test into a single translation unit, which made GCC exceed its variable tracking limit and required several GB of RAM to compile on small machines.
Use radian angles and the correct GEO inclination for BeiDou almanacs,
and convert GPST to BDT before propagating BeiDou navigation data.
Require a sample-clock-anchored PVT fix no older than one second for
Doppler prediction. Clear previous assistance when retrying acquisition
after tracking loss.
With set_doppler_num_bins(), acq_grid holds the Doppler candidate columns
followed by 0, 1 or 2 noise-reference columns placed at doppler_center
+/- the configured doppler_max, outside the linear Doppler axis. Nothing
in the dump said how many columns were candidates, so the readers either
rejected the file (Acquisition_Dump_Reader) or plotted the reference
columns on a wrong Doppler axis (plot_acq_grid.py, plot_acq_grid.m).
Write a new doppler_num_candidates variable to the dump and use it in
the readers to keep only the candidate columns. Dumps without it are
read as before, except that the reference column of older narrowed
dumps is also skipped. The Python and MATLAB scripts plot a
single-candidate (narrowed) grid as a code-delay line instead of an
empty surface.
Add a regression test that reads back a full-grid dump with two
reference columns and a narrowed N = 5 dump.
A narrowed search of N < d_num_doppler_bins candidates adds up to two
noise-reference rows, so N = d_num_doppler_bins - 1 needs
d_num_doppler_bins + 1 rows. The step-1 grid buffers were sized to the
full grid's own row count, which has no reference rows when the
peak-ratio statistic is used or the CFAR wraparound distance suffices,
so that request wrote one row past the end of d_grid_doppler_wipeoffs
and d_magnitude_grid.
Size the step-1 buffers (and the CUDA engine) for d_num_doppler_bins + 2
rows, which covers every row count set_doppler_num_bins() can produce,
and add a regression test.
Conflict resolution: keep next's CUDA engine hooks, sample-buffer renames
(d_buffer_sample_count) and full_grid_search option; adopt the PR's
set_doppler_num_bins() members. CUDA capacity now uses
d_num_doppler_bins_full_grid_active and update_grid_doppler_wipeoffs()
uploads the unified grid to the GPU.
- Order code and carrier uploads on the compute stream and wait before
returning so callers can safely reuse host buffers.
- Verify CUDA readiness and completed grid counts in adapter tests,
rejecting unexpected CPU fallback.
- Exercise host-buffer reuse in engine parity tests.
- Correct the Jetson helper's test executable path.
- Use ArgsProduct for compatibility with Google Benchmark 1.9.2–1.9.5.
Build system:
- Drop hardcoded sm_30 (Kepler) nvcc flags rejected by CUDA >= 11; honour
CMAKE_CUDA_ARCHITECTURES, auto-detect Jetson SoCs from the device tree
(Orin 87, Xavier 72, TX2 62, Nano 53) or use "native" with CMake >= 3.24.
- CUDA standard follows the host C++ standard (C++17).
- find_package(CUDAToolkit) and explicit CUDA::cudart / CUDA::cufft links.
- Define CUDA_GPU_ACCEL project-wide; keep -Wno-psabi away from nvcc.
Acquisition:
- New CudaPcpsEngine (acquisition/libs/cuda_pcps_engine.{h,cu}): batched
cuFFT evaluation of the Doppler x code-phase grid, one stream per block.
- pcps_acquisition::doppler_grid() dispatches to the engine when
Acquisition_XX.use_cuda=true (or GNSS-SDR.use_cuda_acquisition=true),
falling back to the CPU loop on any failure. Peak search and statistics
are unchanged.
Tracking (GPS_L1_CA_DLL_PLL_Tracking_GPU):
- Split the racy single-launch wipe-off + correlator kernel into two
launches on the same stream.
- cudaHostAllocMapped || cudaHostAllocWriteCombined -> cudaHostAllocMapped.
- No cudaDeviceReset() in the per-channel teardown; do not cudaFree()
device aliases of host-mapped buffers; const-correct GPU_Complex.
Tests/docs:
- CudaPcpsEngineTest (GPU grid vs CPU reference incl. accumulation,
bit-transition mode, 4 ms integration) and GpsL1CaPcpsAcquisitionCudaTest
(full adapter on a real capture, CPU vs GPU estimates).
- benchmark_pcps_grid: CPU baseline vs GPU sweep.
- docs/JETSON.md build/verify/benchmark guide; README, changelog, sample
config updates.
Signed-off-by: Phillip Vu <36169227+phillipvu@users.noreply.github.com>
Hybrid_Observables:
- Interpolate only between consecutive tracking samples whose TOW advanced
as much as the receiver time: after a gap in the valid words of a
channel, the last sample before it and the first one after it were used
together, giving a meaningless carrier phase and, if the TOW had stepped,
a first pseudorange off by up to hundreds of seconds. Fix the TOW
rollover in the interpolation and in the pseudorange computation, which
added a week to any travel time longer than 302.4 s.
- Set the receiver time from the largest group of channels agreeing on the
TOW, and set it again when such a group contradicts it, instead of
trusting the latest TOW of the first valid epoch. Channels contradicting
the receiver time do not deliver valid pseudoranges. Make the clock
correction from the PVT block safe at the TOW rollover.
- Size the tracking history from the receiver clock buffer and the
observables interval instead of a fixed count, which was too short for
1 ms symbols (Galileo E6) at long intervals and 4x too large at the
default one. Find the nearest sample with a binary search.
- Use the mean Doppler of the epoch in the cycle-slip detector: the final
value raised false slips with high Doppler rates and long intervals.
- Make the static helpers private and remove their unit test.
RTKLIB_PVT: do not count the epochs without enough satellites as solver
errors, and send the reset to the observables once per outage: each reset
emptied the tracking history of every channel every two seconds.
set_doppler_num_bins(1) (an exactly-known Doppler, e.g. from a
dual-frequency assist) previously honored a widening exception: if
Acquisition_<Sig>.enable_assisted_doppler_narrowing was set, a
literal N == 1 request could get silently expanded into
doppler_narrowing_num_bins candidate bins instead of searched as
requested. That was never asked for and contradicts the whole point
of this unification -- N is supposed to be authoritative, full stop.
A caller that actually wants a safety margin around its own estimate
should just request that many bins directly (set_doppler_num_bins(3),
not set_doppler_num_bins(1) plus a config flag that silently changes
what "1" means).
Removes both config parameters entirely (enable_assisted_doppler_narrowing,
doppler_narrowing_num_bins) along with the widening branch in
set_doppler_num_bins(). Dual-frequency-assisted secondary acquisition
now always searches exactly 1 bin, unconditionally -- no config knob
changes that. Does not reintroduce next's original enable_doppler_narrowing
name/semantics either (a master on/off switch that could fall back an
assisted request to the full grid): under the unified API, the caller's
N is never second-guessed by a flag, matching this PR's whole premise.
Test file updated to match: init() drops both removed parameters, and
the two tests that specifically exercised the widening path
(NarrowingWithMultipleCandidateBinsRecoversOffsetEstimate,
NarrowedDumpMetadataMultipleCandidateBins) now request their multi-bin
coverage directly (set_doppler_num_bins(5)) instead of relying on
config-driven expansion from N == 1 -- same coverage, no removed
feature required to exercise it.
Verified: full local test suite (105 tests) passes against a
from-scratch build, isolated from other in-progress work.
set_doppler_uncertainty() only ever supported two states: the full
configured grid, or a single exactly-known bin (assisted narrowing's
2-bin variant). Regular searches, primary-frequency-assisted secondary
signals, and any future partial-uncertainty search (e.g. a visibility-
aware Doppler prediction with residual error) all had to be shoehorned
into one of those two cases instead of directly stating how many
candidate bins they actually need.
Replaces it with set_doppler_num_bins(uint32_t): 0 is a sentinel for
"the full configured range" (the only value a caller can't supply
directly, since that count is otherwise private to this class); any
other value is the literal candidate bin count, from 1 (exactly known,
as when assisted by an already-tracked primary frequency) up to the
full grid. Regular and narrowed searches now go through the exact same
code path -- update_grid_doppler_wipeoffs(), compute_statistics(), the
two statistic functions -- differing only in candidate_count.
CFAR reference-bin placement is generalized the same way:
needs_extra_reference_row(candidate_bins) reproduces both today's
full-grid conditional extra-row behavior (only when the in-grid
wraparound distance can't clear reference_bin_min_sidelobes) and
narrowed mode's unconditional extra-row assumption, for any candidate
count. d_min_reference_separation_hz is derived automatically from the
new reference_bin_min_sidelobes config parameter (default 4) and
coherent_integration_time_ms, rather than needing to be set directly.
Also fixes a latent asymmetry: whenever a dedicated reference row is
needed for more than one real candidate, two rows are now computed --
at +doppler_max and -doppler_max -- and the statistic picks whichever
sits opposite the winning candidate's side of center. Previously the
reference was always fixed at +doppler_max regardless of which side
the signal landed on; a candidate near the edge of the search range
could end up right next to its own reference sample. With exactly one
real candidate (always at offset 0, no side to mirror), a single fixed
row is kept, matching prior behavior.
The detection threshold (d_threshold_active) is recalibrated by
set_doppler_num_bins() whenever the active candidate count changes,
since compute_threshold() folds the bin count into the false-alarm
probability.
Propagates the rename through AcquisitionInterface,
PcpsAcquisitionAdapter, ChannelInterface, Channel, and
GNSSFlowgraph's two assist_acquisition_doppler() call sites (0 ->
literal full-grid sentinel for the regular/unassisted case, 1 for the
dual-frequency-assisted case -- an exact inversion of the old
uncertainty-flag convention).
Verified: full local test suite (acq_test, trk_test, gnss_block_test,
flowgraph_test -- 105 tests) passes against a from-scratch build off
upstream/next, isolated from other in-progress work in this tree.
Follow-ups to the frequency-error-reduction merge (tracking state 5):
- Anchor Flag_PLL_180_deg_phase_locked to the sign of the last Prompt
symbol. The differential bit/secondary-code detector reconstructs bits
relative to an arbitrary first bit, so the previous corr_value sign rule
was independent of the actual carrier polarity.
- Do not feed the deliberate Doppler bin hops of the scan into the
high_dyn carrier/code phase-rate smoothers: clear the histories and
zero the rate steps while scanning.
- Start the pull_in_time_s and bit_synchronization_time_limit_s budgets
at the first sample after the scan instead of at acquisition, so long
scans no longer consume the FLL pull-in or the sync time limit. Scan
completion re-arms pull-in and resets the lock-fail counters. The
non-scan path keeps the acquisition time origin.
- Clamp f_error_accumulation to at least 1, both when parsed from the
configuration and in the block constructor, to avoid an out-of-bounds
write and a scan that never advances.
- Add unit tests for polarity, rate suppression, timers, and a full L2C
scan, and document the new options in the CHANGELOG.
Follow-up to the galileo-navtype-fallback merge. The original fallback let
E5a/E5b/E6 observations use the other service's ephemeris, but the BGD index
for those signals is fixed by frequency, so an E5a range modeled with an
I/NAV clock would be off by BGD(E1,E5a) - BGD(E1,E5b). Only E1 selects its
BGD from the ephemeris clock model, so only E1 may fall back.
Drop the per-PRN locked set: I/NAV and F/NAV carry the same IODnav/toe,
so they expire together, and an E1 switch between services costs only BGD
quantization. select_galileo_ephemeris() is const again and simply prefers
the primary service while its ephemeris is fresh.
Also normalize a legacy Unknown provenance to the primary service on the
returned ephemeris, and allow E1 Reduced CED in F/NAV-primary receivers.
Introduce the 5D signal chain: ICD BPSK(10) replica, PCPS acquisition,
DLL-PLL tracking (1 ms data component), and a first-cut B-CNAV2 decoder
(preamble + CRC-24Q, MT10/MT11/MT30, systematic 288 bits, no 64-ary
LDPC). GEO and BDS-2 PRNs are not assigned B2a channels. B2a-only SPP
remaps 5D onto RTKLIB slot 0 with lam[0]=c/FREQ5.
Signed-off-by: huangchuhan <huangchh37@mail2.sysu.edu.cn>
Covers Dll_Pll_Conf::SetFromConfiguration() parsing of f_error_step_num
(default disabled, odd values pass through, even values round up to
odd, zero stays disabled) and f_error_accumulation/f_error_doppler_step
pass-through, plus dll_pll_veml_tracking::f_error_bin_multiplier()'s
bin-index-to-Doppler-offset mapping, including that step_num=5
reproduces the original fixed 5-bin scan order.
f_error_bin_multiplier() is made public so it can be exercised
directly from the test.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: joebre <joel.brenner@saphyrion.ch>
- Rename doppler_uncertanty -> doppler_uncertainty throughout (interface,
adapter, block, comments, log message).
- Fix the out-of-bounds write and mis-detection in narrowed-mode
activation: track it with an explicit d_doppler_search_narrowed flag,
set directly in set_doppler_uncertainty() and guarded on
d_num_doppler_bins > 1, instead of inferring it from
"d_num_doppler_bins_active < d_num_doppler_bins". That inferred check
broke two ways: for a 1-bin full grid (a valid, already-accepted
config), it still forced 2 active bins, writing past the grid/wipeoff
allocations sized to d_num_doppler_bins; for a 2-bin full grid, an
actually-narrowed request computes the same active count as the full
grid (2 < 2 is false), so it silently searched the full grid's own
bin layout instead of the assisted {center, center + doppler_max}
pair. Reproduced the out-of-bounds write directly (temporarily
reverting to the inferred check crashes with "corrupted double-linked
list" under the new NarrowingEnabledOneBinGrid test) and the
mis-detection (NarrowingEnabledTwoBinGrid).
- Keep the noise-reference bin out of candidate selection: both
max_to_input_power_statistic() and first_vs_second_peak_statistic()
now take a separate candidate_count, used only for the peak-search
loop bound; the CFAR path's own num_doppler_bins (used for the
"opposite bin" reference lookup) is unchanged. In narrowed mode,
candidate_count is 1 -- the noise-reference bin can no longer win and
be reported as the acquisition result, regardless of its own power
(verified against a live signal deliberately placed in the reference
bin instead of the known/candidate bin: reverting this fix, both the
CFAR and peak-ratio statistic mistakenly report the reference bin's
Doppler as if it were the assisted center).
- Recalibrate the acceptance threshold for narrowed acquisition: adds
d_threshold_narrowed, computed like d_threshold but with
num_doppler_bins = 1 (the true candidate count in narrowed mode)
instead of the full grid's bin count, since compute_threshold() folds
the candidate count into the false-alarm probability and reusing the
full-grid threshold in narrowed mode was effectively far stricter
than the requested PFA. get_threshold() returns it whenever
d_doppler_search_narrowed is true.
- Fix narrowed dump layout/metadata: ensure_dump_grid_allocated() now
sizes acq_grid to d_num_doppler_bins_active (not the full grid width),
matching what copy_magnitude_grid_to_dump_grid() actually writes each
cycle -- this also makes narrow<->full transitions between dumps
self-correcting. dump_results() writes accurate, mode-appropriate
doppler_max/doppler_step (the same {0, doppler_max} encoding
compute_statistics() uses internally when narrowed), plus new
doppler_center and doppler_narrowed fields so offline post-processing
can tell which encoding is in effect. Verified end to end against a
live dump (NarrowedDumpMetadata).
- Strengthen Acq_Conf::enable_doppler_narrowing's doc comment (default,
which implementations it applies to, its dependency on the caller
passing doppler_uncertainty == 0) and add a commented usage example
next to GNSS-SDR.assist_dual_frequency_acq in a dual-frequency
Galileo E1B+E5a example config.
- Add tests/.../pcps_acquisition_doppler_narrowing_test.cc: 8 focused
tests (narrowing enabled/disabled, 1-bin and 2-bin full-grid
configurations, CFAR and peak-ratio statistics, a full-narrow-full
transition, two-step acquisition with step-1 narrowing, and narrowed
dump metadata), built on the same real-capture/known-ground-truth
pattern as gps_l1_ca_pcps_acquisition_test.cc. Confirmed each targeted
regression test actually fails without its corresponding fix before
finalizing them.
Signed-off-by: joebre <joel.brenner@saphyrion.ch>