Skip to content

FPGA/hardware-correlator injection seam: feed external CorrelatorOutputs to the estimator #203

Description

@zsoerenm

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions