API Reference

Driver API

HardwareLoopCore.AbstractLoopDriver — Type
AbstractLoopDriver

Supertype of a hardware correlator's driver as the loop core sees it. Required:

  • read_records!(driver, records) — append every record the device has produced since the last call to records (a Vector{DeviceRecord} sized once), returning how many. Never blocks.
  • write_word!(driver, channel, carrier_hz, code_hz) — commit a carrier and code NCO word on channel, effective on the next sample.
  • arm!(driver, channel, spec::ArmSpec) — load a replica and start correlating; returns ArmOutcome.
  • release!(driver, channel).
  • assignment_start(driver, channel) — the device sample the channel's current assignment took effect at, typemax(Int64) while a scheduled arm has not been confirmed, or typemin(Int64) once the device has given up on it (the core then rejects the arm and frees the channel).
  • sample_count(driver, band) — the device's free-running sample counter on band's counter.
  • driver_capabilities(driver) — the fixed limits.

Optional: wait_records(driver, timeout_ms) — block in the kernel until records may be available (default: return immediately), and overflowed_channels!(driver) — the device's own record-loss report, cleared on read (default 0).

source
HardwareLoopCore.read_records! — Function
read_records!(driver, records::Vector{DeviceRecord}) -> Int

Append every record the device has produced since the last call — correlator dumps and epoch strobes, in the order the device produced them — to records and return how many were appended. Must never block, and should not allocate: the core passes the same vector, emptied and with its capacity reserved, on every pass. Required for every AbstractLoopDriver.

source
HardwareLoopCore.write_word! — Function
write_word!(driver, channel, carrier_hz::Float64, code_hz::Float64) -> Bool

Commit a carrier and a code NCO word (the Dopplers, in Hz) on channel, effective on the device's next sample. Return false if the device refused the word (e.g. the channel is not running), which the core counts as rejected. Required for every AbstractLoopDriver.

source
HardwareLoopCore.assignment_start — Function
assignment_start(driver, channel) -> Int64

The device sample (on the channel's band counter) the channel's current assignment took effect at. While an accepted arm is still waiting to take effect, typemax(Int64); once the device has given up on it, typemin(Int64), and the core then rejects the arm and releases the channel. Records whose integration began before this sample are dropped as stale. Required for every AbstractLoopDriver.

source
HardwareLoopCore.wait_records — Function
wait_records(driver, timeout_ms::Integer)

Block until the device may have records, for at most timeout_ms milliseconds — e.g. on a DMA interrupt. Optional: the default returns at once, so the service loop polls.

source
HardwareLoopCore.overflowed_channels! — Function
overflowed_channels!(driver) -> Integer

The channels the device reports having lost records on since the last call (a bitmap or a count, as the device keeps it), cleared on read. Optional: the default reports none (0).

source

Arming

HardwareLoopCore.ArmSpec — Type
ArmSpec

What the core asks a driver to program when arming a channel: the signal (as the concrete GNSSSignals object), the PRN, the handover Dopplers and code phase valid at valid_at_sample (on the channel's band counter), the quantised tap offsets, the band and RF routing, and the amplitude declarations the record scaling depends on.

source
HardwareLoopCore.arm_rejected — Function
arm_rejected(reason) -> ArmOutcome

The ArmOutcome of a refused arm. reason is one of HardwareLoopProtocol's REJECT_* codes (e.g. REJECT_UNSUPPORTED_SIGNAL, REJECT_BAD_CONFIG); the core forwards it to the receiver in a STATUS_ARM_REJECTED status event and frees the channel.

source

Records

HardwareLoopCore.DeviceRecord — Type
DeviceRecord(channel, prn, sample_index, integrated_samples, taps, num_taps;
             band = 1, num_ants = 1, code_phase = NaN)

One correlator dump, or one epoch strobe, as the driver hands it to the core. isbits, so the ingest buffer is one flat vector.

  • channel — hardware channel (1-based), or 0 for an epoch strobe.
  • band — the band the record's sample_index is counted on (1-based index into the core's band table).
  • prn — the PRN the channel was correlating, for stale-record detection.
  • sample_index — the device counter at the end of the integration.
  • integrated_samples — samples integrated.
  • code_phase — the replica's code phase in chips at sample_index, NaN when the device does not report it.
  • num_taps, num_ants — how many of taps are meaningful: latest tap first, antenna-major (taps[(ant - 1) * num_taps + tap]), raw accumulator sums.
source
HardwareLoopCore.pack_taps — Function
pack_taps(values::AbstractVector{<:Complex}) -> NTuple{MAX_RECORD_TAPS,ComplexF64}

Pack tap values into the fixed tuple a DeviceRecord carries, in the order given (latest tap first, antenna-major), zero beyond length(values). Throws an ArgumentError for more than MAX_RECORD_TAPS values.

source
HardwareLoopCore.strobe_record — Function
strobe_record(sample_index; band = 1) -> DeviceRecord

An epoch strobe: a timebase marker at sample_index on band's counter. A driver hands one to the core wherever the device strobes its epoch clock; the core uses it to advance its epoch clock and folds nothing from it.

source

Core and service pass

HardwareLoopCore.LoopCore — Type
LoopCore(driver, signals, segment; estimator = NCOReferencedPLLAndDLL(),
         config = nothing, max_pending_records = 65536, num_ants = NumAnts(1))

The loop process's whole state: the driver, the signal banks, the channel table, the bands, the protocol segment it publishes into, the epoch clock and the counters. signals is the tuple of signal objects this loop can track (fixed at construction, so a trimmed binary knows every type it needs); segment is the HardwareLoopProtocol.Segment the receiver attaches to, with one event ring per hardware channel of the driver.

  • estimator — the delay-aware TrackingLoops estimator every satellite channel steps.
  • config — a LoopConfig; by default one whose epoch is a primary code period of the first signal on the reference band.
  • max_pending_records — the capacity of the ingest buffers; records past it are dropped and counted.
  • num_ants — the antenna blocks per record as a static NumAnts; must match driver_capabilities(driver).num_ants.

Drive it with service_pass! or run!.

source
HardwareLoopCore.service_pass! — Function
service_pass!(core; wait_ms = 1)

One pass of the loop process's service loop. Blocks in the driver's wait for at most wait_ms when nothing is pending, and never otherwise.

source
HardwareLoopCore.run! — Function
run!(core; max_wait_ms = 1)

The service loop: passes until a shutdown command arrives (or core.running is cleared). Marks the loop running in the segment's header on entry and stopped on exit.

source
HardwareLoopCore.take_records! — Function
take_records!(core) -> Int

Read every record the driver has, move the plausible ones into the pending buffer and advance the epoch clock. Returns how many were taken.

source
HardwareLoopCore.fold_closed_epochs! — Function
fold_closed_epochs!(core, now_reference) -> Int

Fold every epoch that has closed — every record before the boundary is ingested, each channel's completed records step the loop, the code phases are advanced to the boundary, the noise references close their epoch, the epoch states are published and the words are scheduled — and return how many. now_reference is the device counter on the reference band at the start of this pass; an epoch more than max_backlog_epochs behind it is folded observation-only.

source
HardwareLoopCore.commit_words! — Function
commit_words!(core) -> Int

Write every word the fold scheduled, right now, and return how many were committed. The device applies a word on the sample after the write, so the sample counter read straight after it is where the word landed; the channel's timeline is corrected from the predicted landing to that sample, and a word landing more than an epoch past its prediction is counted late.

source
HardwareLoopCore.confirm_arms! — Function
confirm_arms!(core)

Publish a STATUS_ARMED status event, stamped with the device sample the assignment took effect at, for every arm the device has confirmed (assignment_start) since the last pass. From then on the channel's records are believed. An arm the device gave up on (typemin(Int64)) is answered with STATUS_ARM_REJECTED (REJECT_DEVICE_ERROR) and the channel is released.

source
HardwareLoopCore.rearm_noise_references! — Function
rearm_noise_references!(core)

Re-arm every band's noise reference that has served LoopConfig.noise_rearm_epochs epochs onto a fresh decoy: the next PRN of its signal, a pseudo-random code phase and a carrier offset within ±5 kHz, so a chance alignment with a live satellite spoils one observation in the noise window rather than the window.

source

Configuration

HardwareLoopCore.LoopConfig — Type
LoopConfig(; kwargs...)

Loop-wide configuration, changed at run time only through a ConfigureCommand:

  • epoch_length — the fold epoch in reference-band samples (default: one primary code period of the first supported signal).
  • commit_lead_samples — how far past the device counter read at the start of a pass the words that pass computes are predicted to land (0). A word is written in the very pass that computes it; the timeline is corrected to the sample it really landed at.
  • coherent_code_blocks — the coherent accumulation ceiling in primary-code blocks once bit/secondary sync is found; 0 for one whole symbol. The default is 1: the loops are tuned for one step per primary code period (an 18 Hz PLL at 1 ms), and stepping them once per 20 ms symbol instead puts the bandwidth–time product at 0.36, where the delay-aware loop limit-cycles or drifts (measured on the board and reproduced with the simulated device: a 48 dBHz satellite's Doppler ran 30 Hz off in six seconds after bit sync). The bit buffer accumulates the symbol itself.
  • max_integration_time — the longest record, in seconds (20 ms).
  • max_backlog_epochs — how many epochs behind the device the fold may run before older records are folded observation-only (4).
  • noise_rearm_epochs — how often a band's noise reference is re-armed onto a fresh decoy (1000 epochs, one second at a 1 ms epoch).
  • publish_taps — whether every armed channel publishes TapsEvents.
  • max_epoch_clock_advance — the furthest a single record may move the epoch clock, in reference samples (one second).
source

Simulated device

HardwareLoopCore.SimulatedDevice — Type
SimulatedDevice(signals::Tuple; sampling_freq, num_channels = 6, epoch_length,
                dump_interval_samples = 0, handover_code_phase_error = 0.0,
                record_delay_samples = 0,
                band_id = get_band_id(get_band(first(signals))))
SimulatedDevice(signal; kwargs...)

A simulated hardware correlator for any of signals (one band, one antenna) at sampling_freq (Hz), fed raw samples through correlate_chunk! and strobing the epoch clock every epoch_length samples (default: one primary code period of the first signal).

dump_interval_samples makes it dump inside a primary code period as well as on the wrap. handover_code_phase_error is a deliberate error, in chips, added to every arm's code phase: real handovers are never exact, and it is what makes the code loop's sign observable over a short run. record_delay_samples holds every record back until the counter is that far past its end — the DMA latency of a real device, and the knob the delay-tolerance tests turn.

source
HardwareLoopCore.correlate_chunk! — Function
correlate_chunk!(dev::SimulatedDevice, samples) -> Int

Correlate one chunk of raw samples with every active channel's replica, queue the records it completed (and the epoch strobes) for read_records!, and return how many were queued.

source