API Reference
Driver API
HardwareLoopCore.AbstractLoopDriver — Type
AbstractLoopDriverSupertype 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 torecords(aVector{DeviceRecord}sized once), returning how many. Never blocks.write_word!(driver, channel, carrier_hz, code_hz)— commit a carrier and code NCO word onchannel, effective on the next sample.arm!(driver, channel, spec::ArmSpec)— load a replica and start correlating; returnsArmOutcome.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, ortypemin(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 onband'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).
HardwareLoopCore.DriverCapabilities — Type
DriverCapabilitiesThe device's fixed limits as the core needs them: channels, the widest tap layout its records carry, antennas, and the band table.
HardwareLoopCore.driver_capabilities — Function
driver_capabilities(driver) -> DriverCapabilitiesThe device's fixed limits (DriverCapabilities), read once when the LoopCore is built. Required for every AbstractLoopDriver.
HardwareLoopCore.read_records! — Function
read_records!(driver, records::Vector{DeviceRecord}) -> IntAppend 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.
HardwareLoopCore.write_word! — Function
write_word!(driver, channel, carrier_hz::Float64, code_hz::Float64) -> BoolCommit 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.
HardwareLoopCore.arm! — Function
arm!(driver, channel, spec::ArmSpec) -> ArmOutcomeLoad the replica ArmSpec describes onto channel and start correlating, replacing whatever the channel ran before. Return ARM_ACCEPTED, and report the sample the assignment took effect at through assignment_start once it has; or return arm_rejected(reason) if the device cannot serve it. Required for every AbstractLoopDriver.
HardwareLoopCore.release! — Function
release!(driver, channel)Stop channel correlating. Records the device has already produced for it may still arrive; the core drops them. Required for every AbstractLoopDriver.
HardwareLoopCore.assignment_start — Function
assignment_start(driver, channel) -> Int64The 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.
HardwareLoopCore.sample_count — Function
sample_count(driver, band::Integer) -> Int64The device's free-running sample counter for band (an index into DriverCapabilities.bands), read now. Band 1 is the reference band: its counter is the receiver timebase the epoch clock runs on. Required for every AbstractLoopDriver.
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.
HardwareLoopCore.overflowed_channels! — Function
overflowed_channels!(driver) -> IntegerThe 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).
Arming
HardwareLoopCore.ArmSpec — Type
ArmSpecWhat 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.
HardwareLoopCore.ArmOutcome — Type
The outcome of arm!: accepted (confirmation follows through assignment_start) or rejected with a protocol reason code.
HardwareLoopCore.ARM_ACCEPTED — Constant
ARM_ACCEPTEDThe ArmOutcome a driver's arm! returns when it accepted the arm. The core then waits for assignment_start to confirm it.
HardwareLoopCore.arm_rejected — Function
arm_rejected(reason) -> ArmOutcomeThe 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.
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), or0for an epoch strobe.band— the band the record'ssample_indexis 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 atsample_index,NaNwhen the device does not report it.num_taps,num_ants— how many oftapsare meaningful: latest tap first, antenna-major (taps[(ant - 1) * num_taps + tap]), raw accumulator sums.
HardwareLoopCore.MAX_RECORD_TAPS — Constant
How many tap × antenna values one record can carry.
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.
HardwareLoopCore.strobe_record — Function
strobe_record(sample_index; band = 1) -> DeviceRecordAn 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.
HardwareLoopCore.is_strobe — Function
is_strobe(record::DeviceRecord) -> BoolWhether record is an epoch strobe (strobe_record) rather than a correlator dump.
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-awareTrackingLoopsestimator every satellite channel steps.config— aLoopConfig; 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 staticNumAnts; must matchdriver_capabilities(driver).num_ants.
Drive it with service_pass! or run!.
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.
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.
HardwareLoopCore.take_records! — Function
take_records!(core) -> IntRead every record the driver has, move the plausible ones into the pending buffer and advance the epoch clock. Returns how many were taken.
HardwareLoopCore.fold_closed_epochs! — Function
fold_closed_epochs!(core, now_reference) -> IntFold 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.
HardwareLoopCore.commit_words! — Function
commit_words!(core) -> IntWrite 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.
HardwareLoopCore.handle_commands! — Function
handle_commands!(core) -> IntDrain the command ring, executing and acknowledging every command. Returns how many were handled.
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.
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.
HardwareLoopCore.LATENCY_EDGES_US — Constant
Record-age histogram edges in microseconds; ages past the last edge land in the overflow bin.
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;0for 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 publishesTapsEvents.max_epoch_clock_advance— the furthest a single record may move the epoch clock, in reference samples (one second).
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.
HardwareLoopCore.correlate_chunk! — Function
correlate_chunk!(dev::SimulatedDevice, samples) -> IntCorrelate 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.