The Monitor is fed from the Observables, which since ed5a75e31 fill
channels without a valid time reference with their latest tracking
data. That never worked in Galileo E6-only configurations: the GPS L1
C/A, GPS L5, Galileo, BeiDou D1/D2 and BeiDou B1C telemetry decoders
produced no output until they decoded the time of week, so the
Observables never received E6 tracking data. The GLONASS, GPS L2C,
BeiDou B2a and SBAS decoders already forwarded every symbol with
Flag_valid_word unset; now all telemetry decoders do. Dumps,
telemetry_to_trk and nav-data monitor messages are still issued only
with a valid time of week.
The tracking-only entries also had fs set to 0, which gnss-sdr-monitor
takes as an empty channel, hiding SBAS channels. fs is now kept, with
the validity flags still unset; the PVT only uses observations with
Flag_valid_pseudorange.
As a side effect, the secondary-band acquisition assistance can use a
primary-band channel as soon as its tracking locks, instead of after
its time of week is decoded. The Galileo F/NAV test sink now records
the first item with a valid word.
Alternative to PR #1122, without new options or changes to tracking
and to the PVT time-of-week reset handling. Related to #1018.
A one-code-period PCPS window starts at an arbitrary code phase, so a
secondary-code or data sign transition at the code epoch inside it
splits the correlation peak up to +-0.74/T away from the true Doppler.
For GPS L5 (the I5 NH10 code changes sign at 6 of every 10 epochs),
about 40 % of the acquisitions started tracking 250-900 Hz off. The
1 ms Costas loop then either held a false lock at exactly +-500 Hz
(lock test 0.97, C/N0 3.9 dB low, secondary-code sync impossible)
until bit_synchronization_time_limit_s, or never locked and lost lock
a few seconds after pull-in.
After a positive decision, refine_doppler() despreads the dwells with
the replica aligned to the detected code epoch, scores the two sides
of the epoch under both sign hypotheses (summed non-coherently over
dwells), and re-estimates the Doppler over +-1/T around the grid peak.
It only runs on positive acquisitions, so its cost is negligible, also
with the CUDA grid. It applies to every pcps_acquisition search over
one code period without bit_transition_flag. The new
Acquisition_XX.refine_doppler parameter (default true) disables it.
With -DENABLE_CUDA=ON, band<n>_cuda=true computes that band's
frequency-translating FIR (complex composite taps applied to the real
capture) on an NVIDIA GPU through the new CudaDdcEngine, instead of on
a CPU worker thread. As with the CUDA acquisition engine, the public
interface has no CUDA types and hides the implementation behind a
PIMPL. Each band falls back to the CPU, with a warning in the log, if
its engine cannot be created or a GPU call fails. Builds without CUDA,
and bands without the flag, are unchanged.
Taken from PR #1144, plus:
- Report the CUDA status per band with LOG instead of std::cout/cerr
- Check cudaGetLastError() after the kernel launch, so a failed launch
is not mistaken for valid output
- Select the engine's device in set_taps(), compute() and the
destructor, since compute() runs on the band worker threads
- Build the engine only with both ENABLE_CUDA and ENABLE_EVK1029
With enable_freq_xlating=true, Evk1029Source translates, filters and
decimates each band itself and exposes one gr_complex output port per
band, so Bypass Signal Conditioners can connect the channels straight
to the source. Each band's FIR runs on its own worker thread, and the
next chunk of the capture is decoded while the current one is filtered.
Per-band filter design: band<n>_IF, _decimation_factor, _bw, _tw,
_window, _attenuation_dB and _kaiser_beta.
Taken from PR #1143 without its SignalConditioner Bypass commits (next
already provides Bypass), plus:
- Center shorter band filters on the longest one, so every band has the
same group delay (mismatched tap counts biased pseudoranges between
bands by up to km)
- Fix source dump in fused mode: one gr_complex file per band (_ch<n>)
- Reject unequal or non-positive band decimation factors
- Build with GNU Radio < 3.9 (firdes window type) and VOLK < 2.5
(rotator), use volk_gnsssdr::vector
- Link Gnuradio::filter privately and only with ENABLE_EVK1029
- Start worker threads after the capture file is opened
With blocking=false and max_dwells > 1, copy the whole integration window
into a per-block buffer, so that general_work() keeps consuming input while
the worker integrates. The shared conditioner buffer no longer holds back
every channel during a search.
- set_history() keeps the last window readable, so a search starts as soon
as one new dwell arrives. Dwell offsets in the window follow the
dwell-residual skip, and the sample stamp is the end of the window (the
end of the first dwell while the window starts at stream index 0).
- The buffer is allocated at the first search and freed on a positive
acquisition or when the channel is deactivated.
- Deactivation stops the worker and drops the search, so the next
activation starts a new one. stop() replaces the destructor wait.
- Idle input is consumed in one call per wakeup, keeping the history.
Based on #1108 by Vladislav P.
Replace the fixed 20 ms limit on SignalConditioner.batch_size_ms and
max_batch_latency_ms. A configured batch is accepted if it is no longer
than the automatic one (min(10 ms, observable_interval_ms)), or if the
buffer it needs (whole batches, with room for one on top of the largest
reader window) fits the alignment budget: the signal time channels can
trail the receiver clock before the observables miss epochs. Batches
are also limited to 4 MiB, since GNU Radio allocates two of them on
each side of the batching block.
At observable_interval_ms=20 and 4 MSps gr_complex this allows about
90 ms instead of 20 ms. At 5 ms it now rejects batches that the old
limit let through. In automatic mode, an explicit max_batch_latency_ms
sets the batch size, so that batch gets the same check. Defaults are
unchanged.
add_obs_sat_record_line() formatted every observable group (pseudorange,
carrier phase, Doppler, CN0) unconditionally, even for the placeholder
Gnss_Synchro{} slot get_constellation_observables_map() creates for a
signal with no valid observation for a given satellite (e.g. untracked,
or simply a frequency that satellite doesn't transmit, such as a GPS
PRN5/PRN7 which has an L5 slot but only tracks/transmits L1). That
placeholder is all-zero, so it printed as a formatted "0.000" value with
a Signal Strength Indicator floored to its minimum class (1), which
reads as a real but degraded observation rather than "no data".
Per RINEX 3/4, an unavailable observation must be a fully blank field,
not a zero value. Gate the formatter on Flag_valid_pseudorange, which
already reliably distinguishes a real observable (set true by the
Observables block) from the placeholder (defaults false) -- write 64
spaces instead when false.
Select batch targets from sample rate, latency and memory limits, and
consumer buffer requirements. Replace eligible pass-through stages before
channel fan-out without machine-specific profiles or configuration edits.
Preserve samples and tags across partial batches, deadlines, and EOF.
Handle conditioned output ports consistently and clean up stream and
message connections on disconnect or connection failure.
If the multi-channel fixture setup fails on thread 0 (no CUDA device,
or out of device memory at high channel counts), thread 0 skips and
returns before the timed loop. Google Benchmark then releases the other
threads from the start barrier, and they call compute_grid() on engines
that were never created, which crashes the whole benchmark binary.
Add a ready flag that setup() sets only on success. Every thread checks
it at the top of the timed loop, which is the first point where threads
1..N-1 can see the outcome of the setup, and skips the run instead.
Also add the missing <string> include for the fixture's error member.
The existing cases time a single engine, but each acquisition block owns
its own engine and CUDA stream, so the figure that bounds how many
channels can search at once is the aggregate rate with several engines
resident on the device.
Add bm_pcps_grid_cuda_channels, which gives every benchmark thread its
own engine, wipe-off grid and output buffers, and sweeps 1 to 8 threads.
Rate counters are summed across threads and are fed the iteration count
already totaled over threads, so kAvgThreads yields the aggregate device
rate and a 1/channels scale yields the per-channel rate; both were
checked against the reported per-thread time.
On a Jetson Orin Nano at 4000 samples and 41 Doppler bins the aggregate
rate is 2757, 3839, 5943 and 5307 dwells/s for 1, 2, 4 and 8 channels,
so the device saturates at about four concurrent channels.
Signed-off-by: Phillip Vu <36169227+phillipvu@users.noreply.github.com>