Motivation
We want to offload downconversion + correlation to an FPGA (starting with the LiteX
M2SDR) and run only the tracking loop filters on the host: the FPGA streams correlator
dumps, the host folds them through the Doppler estimator and streams NCO updates back.
The DMA transport, wire format and NCO marshaling live in GNSSReceiver.jl
(JuliaGNSS/GNSSReceiver.jl#107). This issue tracks the remaining Tracking.jl-side changes.
What #205 already provides
PR #205 (feat(track): per-chunk Doppler update with collected correlator outputs) landed
the core mechanism this offload needs:
CorrelatorOutput{C} = (correlator, integrated_samples, sample_index) — a single
completed integration, sample-index tagged. Exported. This is exactly the per-dump
payload the FPGA produces (the receiver adds channel/PRN routing on top).
TrackedSignal.correlator_outputs::Vector{CorrelatorOutput{C}} — a per-signal buffer
collecting the 0/1/2+ outputs completed in a chunk, read via the exported
get_correlator_outputs(t) (returns the live buffer).
doppler_update_interval — the fixed processing epoch: within a chunk the NCO is
held fixed and every completed output is collected, then the estimator folds over them
in order and updates every satellite's NCO once at a common epoch (synchronized
update — the foundation for future vector/Kalman tracking).
- Per-record
integration_time = integrated_samples / sampling_frequency is preserved, so
the loop-filter math is unchanged; the interval is only the scheduling grid.
is_integration_completed was removed (superseded by the correlator_outputs
collection) — the old per-completion flag is gone.
So the estimate phase already consumes a batch of sample-indexed CorrelatorOutputs. What
remains is to let an external producer (the FPGA) supply those outputs and to run the
estimator without the sample buffer.
Remaining work
1. Decouple the estimator from BandMeasurements
estimate_dopplers_and_filter_prompt! still reads sampling_frequency out of
BandMeasurements (_est_one_group!; #205 did not change this). The FPGA path has no
sample buffer, so add a method taking a per-band sampling frequency source instead
(NamedTuple/Dict keyed by band_key, or store the rate on the SignalGroup/
TrackState so the argument disappears). Keep the existing (track_state, measurements)
method as a delegating overload — non-breaking; the CPU track! path is untouched. Must be
per-band, not a scalar (the estimator walks groups that may be on different bands).
2. Sanction an external-producer path into correlator_outputs
Today correlator_outputs is populated by the software correlate phase and consumed +
cleared by the estimator. For the FPGA path we need a blessed way to:
- append externally-built
CorrelatorOutputs to a signal's buffer (currently only
reachable by mutating the vector get_correlator_outputs returns — an explicit
append/ingest API would be cleaner and documents intent), and
- run
estimate_dopplers_and_filter_prompt! standalone over an externally-filled
buffer (skipping downconvert_and_correlate!), with the "estimate consumes and clears
the buffer" invariant documented as part of the public contract for external producers.
3. sample_index convention for a free-running counter
#205's sample_index is buffer-relative (end sample within the current measurement).
The FPGA supplies a free-running global sample counter. Define/document how an external
producer maps its global timestamp onto the per-chunk sample_index the estimator/vector
tracking expects (e.g. relative to the chunk/epoch origin), so downstream vector tracking
reads a consistent time grid.
Caller contract (for GNSSReceiver.jl)
CorrelatorOutput.correlator filled with accumulator scaling matching normalize
(raw sum-of-products over integrated_samples) — satisfied by construction if the
receiver reuses EarlyPromptLateCorrelator / update_accumulator.
integrated_samples = the FPGA output's true sample count for that integration.
- Outputs appended per-signal in
sample_index order; the estimator folds them in
order and applies one NCO update per chunk.
Note on transport delay (documentation)
dll_disc uses the sat's pre-update code_doppler as the value in effect during the
integration — correct for a one-epoch feedback pipeline. Deeper feedback delay is the
caller's responsibility (epoch-tagged outputs + scheduling NCO updates to a known future
epoch); document it alongside the new fs-based method.
Out of scope
DMA/FIFO ingest, PipeChannel transports, wire format, NCO marshaling, hardware channel
management — all in GNSSReceiver.jl#107.
Motivation
We want to offload downconversion + correlation to an FPGA (starting with the LiteX
M2SDR) and run only the tracking loop filters on the host: the FPGA streams correlator
dumps, the host folds them through the Doppler estimator and streams NCO updates back.
The DMA transport, wire format and NCO marshaling live in GNSSReceiver.jl
(JuliaGNSS/GNSSReceiver.jl#107). This issue tracks the remaining Tracking.jl-side changes.
What #205 already provides
PR #205 (
feat(track): per-chunk Doppler update with collected correlator outputs) landedthe core mechanism this offload needs:
CorrelatorOutput{C}=(correlator, integrated_samples, sample_index)— a singlecompleted integration, sample-index tagged. Exported. This is exactly the per-dump
payload the FPGA produces (the receiver adds channel/PRN routing on top).
TrackedSignal.correlator_outputs::Vector{CorrelatorOutput{C}}— a per-signal buffercollecting the 0/1/2+ outputs completed in a chunk, read via the exported
get_correlator_outputs(t)(returns the live buffer).doppler_update_interval— the fixed processing epoch: within a chunk the NCO isheld fixed and every completed output is collected, then the estimator folds over them
in order and updates every satellite's NCO once at a common epoch (synchronized
update — the foundation for future vector/Kalman tracking).
integration_time = integrated_samples / sampling_frequencyis preserved, sothe loop-filter math is unchanged; the interval is only the scheduling grid.
is_integration_completedwas removed (superseded by thecorrelator_outputscollection) — the old per-completion flag is gone.
So the estimate phase already consumes a batch of sample-indexed
CorrelatorOutputs. Whatremains is to let an external producer (the FPGA) supply those outputs and to run the
estimator without the sample buffer.
Remaining work
1. Decouple the estimator from
BandMeasurementsestimate_dopplers_and_filter_prompt!still readssampling_frequencyout ofBandMeasurements(_est_one_group!; #205 did not change this). The FPGA path has nosample buffer, so add a method taking a per-band sampling frequency source instead
(
NamedTuple/Dictkeyed byband_key, or store the rate on theSignalGroup/TrackStateso the argument disappears). Keep the existing(track_state, measurements)method as a delegating overload — non-breaking; the CPU
track!path is untouched. Must beper-band, not a scalar (the estimator walks groups that may be on different bands).
2. Sanction an external-producer path into
correlator_outputsToday
correlator_outputsis populated by the software correlate phase and consumed +cleared by the estimator. For the FPGA path we need a blessed way to:
CorrelatorOutputs to a signal's buffer (currently onlyreachable by mutating the vector
get_correlator_outputsreturns — an explicitappend/ingest API would be cleaner and documents intent), and
estimate_dopplers_and_filter_prompt!standalone over an externally-filledbuffer (skipping
downconvert_and_correlate!), with the "estimate consumes and clearsthe buffer" invariant documented as part of the public contract for external producers.
3.
sample_indexconvention for a free-running counter#205's
sample_indexis buffer-relative (end sample within the current measurement).The FPGA supplies a free-running global sample counter. Define/document how an external
producer maps its global timestamp onto the per-chunk
sample_indexthe estimator/vectortracking expects (e.g. relative to the chunk/epoch origin), so downstream vector tracking
reads a consistent time grid.
Caller contract (for GNSSReceiver.jl)
CorrelatorOutput.correlatorfilled with accumulator scaling matchingnormalize(raw sum-of-products over
integrated_samples) — satisfied by construction if thereceiver reuses
EarlyPromptLateCorrelator/update_accumulator.integrated_samples= the FPGA output's true sample count for that integration.sample_indexorder; the estimator folds them inorder and applies one NCO update per chunk.
Note on transport delay (documentation)
dll_discuses the sat's pre-updatecode_doppleras the value in effect during theintegration — correct for a one-epoch feedback pipeline. Deeper feedback delay is the
caller's responsibility (epoch-tagged outputs + scheduling NCO updates to a known future
epoch); document it alongside the new fs-based method.
Out of scope
DMA/FIFO ingest,
PipeChanneltransports, wire format, NCO marshaling, hardware channelmanagement — all in GNSSReceiver.jl#107.