HardwareLoopCore.jl

HardwareLoopCore — Module
HardwareLoopCore

The engine of a hardware correlator's loop process: the dedicated, allocation-free process that closes the tracking loops of an FPGA correlator. It reads correlator records from a device, folds them epoch by epoch into TrackingLoops' loop arithmetic, writes the resulting NCO words back to the device, and publishes everything a receiver needs into a HardwareLoopProtocol segment.

  • AbstractLoopDriver — the driver API a device implements.
  • LoopCore and service_pass! — the per-channel state and the epoch fold that turn device records into loop steps, the commands the core executes and the events it publishes.
  • SimulatedDevice — a software correlator behind the driver API, for tests and for trying the core without hardware.

The per-record arithmetic — discriminators, loop filters, the bit buffer, the C/N₀ estimators, the delay-aware estimator and its NCO timelines — is TrackingLoops'. Warm service passes allocate nothing.

source

Where it sits

A receiver built on a hardware correlator runs two processes. The receiver process acquires satellites, decides which channel tracks which signal, decodes the navigation data and computes positions. The loop process sits next to the device: it reads every correlator record the device produces, closes the carrier and code loops, and writes the new NCO words back within a fraction of a millisecond. This package is the engine of that loop process.

  • TrackingLoops.jl provides the per-record arithmetic: discriminators, loop filters, bit synchronisation, C/N₀ estimation and the delay-aware estimator.
  • HardwareLoopProtocol.jl is the wire contract between the two processes: a shared-memory segment with a command ring (receiver → loop) and per channel an event ring and an epoch state snapshot (loop → receiver).
  • A device plugs in through an AbstractLoopDriver. The package ships one, SimulatedDevice, a software correlator for tests and for trying the core without hardware. See Writing a driver for the contract a real device implements.

The core is built so that the loop process can be compiled with juliac --trim into a small executable: everything is sized at construction, the driver and the signals are type parameters, and a warm service_pass! allocates nothing.

Why it is not part of HardwareLoopProtocol

HardwareLoopProtocol.jl is the contract both processes link against; this package is one implementation of the loop side of it. Keeping them apart keeps the contract small and stable:

  • The receiver process needs the protocol but not the engine. The protocol depends on Base only; this package pulls in TrackingLoops, GNSSSignals, StaticArrays and Unitful, none of which a receiver needs just to read a segment.
  • The segment layout is versioned and checked by a layout hash on attach, so both processes must agree on it. Changes to the loop arithmetic, the fold or a driver should not force a new protocol release, and a new protocol release should not be mixed up with loop changes.
  • The protocol does not prescribe how the loop is closed. Another loop process — for another device, or not written in Julia at all — can speak the same protocol without this package.

Signals and limitations

The core tracks any signal type that GNSSSignals defines and TrackingLoops has a default correlator and bit/secondary-code synchronisation for (GPS L1 C/A, L1C, L2C, L5; Galileo E1, E5a, E5b, E6; BeiDou B1I, B1C, B2a, B2b, B3I). A loop serves the signal types passed to LoopCore at construction, mixed freely across channels. Overlay (secondary) codes are removed once their phase is known, code periods longer than max_integration_time are stepped in partial records, and a data component can ride on its pilot's NCO words as a passenger.

The test suite closes the loop through SimulatedDevice on GPS L1 C/A and checks GPS L5 (pilot with overlay, data passenger) and Galileo E1B (partial records) with scripted records. The other signals go through the same code paths but have not been run against a device.

Current limitations:

  • The signal types, the antenna count and the hardware channel count are fixed when the core is constructed.
  • One estimator: TrackingLoops' delay-aware NCOReferencedPLLAndDLL with its default loop filters. Its bandwidths are set for the whole loop when the core is built (the estimator keyword of LoopCore); the per-arm carrier_loop_bandwidth_hz and code_loop_bandwidth_hz of an ArmCommand are not applied yet.
  • A record carries at most five taps and ten tap × antenna values (MAX_RECORD_TAPS).
  • With several antennas, the core does no beamforming. The noise reference measures one density, and the antennas are taken to see equal, uncorrelated noise.
  • C/N₀ needs a noise reference: one hardware channel per band armed on a decoy PRN. Without one, no C/N₀ is published.
  • overflowed_channels! is part of the driver API but the core does not read it yet. Lost records are detected from gaps in each channel's records instead.
  • SimulatedDevice correlates sample by sample in Julia, on one band with one antenna. It is meant for tests, not for real-time use.

Installation

using Pkg
Pkg.add("HardwareLoopCore")

The core publishes into a HardwareLoopProtocol segment and its signals are GNSSSignals objects, so a loop process usually loads both as well:

Pkg.add(["HardwareLoopProtocol", "GNSSSignals"])

The segment is mapped with POSIX mmap, so the loop process runs on Linux and macOS. A heap-backed segment (as on the Usage page) works anywhere.

Versioning

The public API is every exported symbol, listed in the API Reference. It follows semantic versioning: breaking changes to these symbols bump the major version. The fields of LoopCore and the counters on it are readable for diagnostics but are not part of that promise, and neither are unexported names.