Bit and secondary-code synchronisation
Until the navigation-bit boundary (for a data signal) or the secondary-code phase (for a pilot) is known, a signal can only be integrated over one primary code period. The BitBuffer collects the prompts of those short integrations, and a per-signal detector (detect_bit_or_secondary_code_sync) searches them for the boundary. Once has_bit_or_secondary_code_been_found returns true, longer coherent integrations become available (see calc_num_code_blocks_to_integrate) and the buffer accumulates the decoded soft bits, which get_soft_bits returns. For a pilot, the secondary-code sync also gives the prompt's sign, which a record's carrier_phase_polarity hands to the four-quadrant carrier discriminators (see LoopRecord).
Detectors exist for every GPS, Galileo and BeiDou signal that GNSSSignals.jl models. How a signal is synchronised is a method of detect_bit_or_secondary_code_sync and of the small trait functions below (uses_soft_bit_edge_detection, get_bit_edge_or_secondary_code_tolerance, …), so a new signal type can be supported by adding methods for it.
TrackingLoops.BitBuffer — Type
Bit sync state and decoded soft bits of one signal.
code_block_buffer is the pre-sync window of prompt signs, B bits wide (get_code_block_buffer_type), unused after sync. soft_bits collects the decoded bits (see get_soft_bits), unbounded between resets. phase_acc holds the soft CFAR detector's PhaseAccumulators, empty on the hard path.
TrackingLoops.PhaseAccumulators — Type
Per-hypothesis bin statistics for the soft CFAR sync detectors, one entry per timing hypothesis: bit-edge phase with one-bit bins (_detect_bit_edge_cfar, _update_phase_accumulators!) or overlay rotation with one overlay-wiped secondary period per bin (_detect_secondary_code_cfar, _update_secondary_accumulators!). O(hypotheses) per block, no growing history. period is blocks_per_bit or the secondary-code length N.
The vectors are mutated in place across BitBuffer rebuilds: an immutable form would be copied (~660 B) or boxed every block, allocating pre-sync.
Fields (all length period once seeded; empty before the first block):
open_bin_sum: coherent sum of the open bin (overlay-wiped for secondary).mean_bin_energy/bin_energy_sum_of_squared_deviations: running mean andM₂ = Σ(energyᵢ − mean)²of the completed-bin energies (Welford;M₂forgets beyondBIN_ENERGY_VARIANCE_MEMORYbins). The bin count isdiv(num_blocks - hypothesis, period).last_bin_polarity: sign of the last completed bin's real part (0before).
TrackingLoops.SyncResult — Type
SyncResultOutcome of a per-signal bit-sync / secondary-code-sync detector call.
Fields:
found::Bool: whether the detector locked on this update.phase::Int: the secondary-code chip the upcoming integration aligns to (0:N-1, from the hard_secondary_code_search); zero for the soft CFAR detectors and signals without a secondary code, which fire on a boundary.polarity::Int8:±1, the locked match orientation, applied to the decoded bits so a negative-polarity lock does not invert them.
TrackingLoops.get_bit_edge_detection_confidence — Method
get_bit_edge_detection_confidence(_)
Target confidence (1 − P(false lock)) of the soft CFAR sync detectors, default 0.999: they integrate until the best hypothesis beats its competitor with this confidence (two bins for a clean signal, longer in noise). Lower it to lock faster at the cost of more false locks.
Overriding
TrackingLoops.get_bit_edge_detection_confidence(::GPSL1CA) = 0.9999Takes effect at the next detector call.
TrackingLoops.get_bit_edge_or_secondary_code_tolerance — Method
get_bit_edge_or_secondary_code_tolerance(_)
Hamming tolerance of the hard-decision _secondary_code_search: the largest fraction of chip errors accepted, max_errors = floor(Int, tolerance × N). Default 0.025 (45 of 1800 chips). Only hard-path signals (GPS L1C-P, BeiDou B1C-P) read it; the soft detectors use get_bit_edge_detection_confidence.
Overriding
E.g. to loosen it for low-C/N₀ work:
TrackingLoops.get_bit_edge_or_secondary_code_tolerance(::GPSL1C_P) = 0.05Takes effect at the next detector call.
TrackingLoops.get_code_block_buffer_type — Method
get_code_block_buffer_type(_)
Concrete Unsigned width B of BitBuffer.code_block_buffer for signal (default UInt64), a type parameter so construction is type-stable.
Hard-path signals need at least N bits (the 1800-chip overlay pilots use UInt1800); soft secondary-code signals keep one period so their hard path stays available; GPS L1 C/A's holds the pre-sync bits recovered at sync; signals with nothing to search use UInt8. Per-signal values: Code-block buffer widths.
TrackingLoops.get_soft_bits — Method
get_soft_bits(bit_buffer::BitBuffer)
get_soft_bits(state::SignalLoopState)The soft bits decoded so far: per navigation bit, the polarity-corrected real part of the summed de-rotated prompts. The sign is the hard decision, the magnitude the reliability, ready for soft-decision decoding (e.g. Viterbi for Galileo E1B, LDPC for GPS L1C-D). Bits recovered from the pre-sync window are sign votes scaled by the sync-time prompt magnitude, to stay comparable.
The buffer's own vector, not a copy: drain it (empty!) after reading. A SignalLoopState's holds 64 bits before it allocates.
TrackingLoops.has_bit_or_secondary_code_been_found — Method
has_bit_or_secondary_code_been_found(bit_buffer::BitBuffer)
has_bit_or_secondary_code_been_found(state::SignalLoopState)Whether the navigation-bit boundary or secondary-code phase has been found; from then on the signal may integrate over several code periods and bits are collected for get_soft_bits.
TrackingLoops.uses_soft_bit_edge_detection — Method
uses_soft_bit_edge_detection(signal)
Whether signal's bit edge is found by the soft CFAR detector _detect_bit_edge_cfar instead of the hard detect_bit_or_secondary_code_sync.
By default true when a bit spans more than one code period and there is no secondary code (currently only GPS L1 C/A). BeiDou B1I/B3I do not qualify even on GEO PRNs, since their signal type reports a 20-chip secondary code (see BeiDou GEO satellites). Override per signal type, e.g.:
TrackingLoops.uses_soft_bit_edge_detection(::SomeSignal) = falseConstant-folded per signal type.
TrackingLoops.uses_soft_secondary_code_detection — Method
uses_soft_secondary_code_detection(signal)
Whether signal's secondary code is found by the soft CFAR detector _detect_secondary_code_cfar instead of the hard _secondary_code_search.
Each bin integrates one secondary period coherently, so the default enables it only for short codes, 1 < N ≤ 100; the 1800-chip overlays (18 s) are too long to integrate and too long to false-lock, and keep the hard sweep. Mutually exclusive with uses_soft_bit_edge_detection. Override per signal type, e.g.:
TrackingLoops.uses_soft_secondary_code_detection(::GPSL5I) = falseConstant-folded per signal type.
TrackingLoops.get_default_correlator — Function
get_default_correlator(gpsl1)
get_default_correlator(gpsl1, num_ants)
Default correlator for a signal: EarlyPromptLateCorrelator for BPSK signals such as GPS L1 C/A, VeryEarlyPromptLateCorrelator for the BOC(1,1)-class ones (GPS L1C, Galileo E1, BeiDou B1C). Their autocorrelation has ±0.5-chip side peaks that can capture a plain early-late discriminator; the very-early/very-late taps feed the VEML dll_disc that mitigates them.
TrackingLoops.detect_bit_or_secondary_code_sync — Method
detect_bit_or_secondary_code_sync(
signal,
prn,
code_block_bits,
num_code_blocks
)
Symbol-sync detector for GPS L1C-D: one CNAV-2 symbol per 10 ms primary code period (100 sps; IS-GPS-800G §3.2.3), so it locks immediately — see _detect_symbol_is_code_block_sync.
TrackingLoops.detect_bit_or_secondary_code_sync — Method
detect_bit_or_secondary_code_sync(
signal,
prn,
code_block_bits,
num_code_blocks
)
Secondary-code sync detector for GPS L1C-P: the hard _detect_secondary_code_sync over the per-PRN 1800-chip overlay (IS-GPS-800G §3.2.2.1.2) on the 10 ms primary code, an 18 s cycle. Needs 1800 buffered blocks; accepts up to 45 errors (2.5 %).
TrackingLoops.detect_bit_or_secondary_code_sync — Method
detect_bit_or_secondary_code_sync(_, _, _, _)
"Sync" detector for GPS L2CL — a no-op that never reports found. L2CL is a dataless pilot without overlay whose 767250-chip (1.5 s) code period already is the coherent integration, so there is nothing to lock.
TrackingLoops.detect_bit_or_secondary_code_sync — Method
detect_bit_or_secondary_code_sync(
signal,
prn,
code_block_bits,
num_code_blocks
)
Symbol-sync detector for GPS L2CM: one CNAV symbol per 20 ms code period (50 sps; IS-GPS-200N §3.2.2), so it locks immediately — see _detect_symbol_is_code_block_sync.
TrackingLoops.detect_bit_or_secondary_code_sync — Method
detect_bit_or_secondary_code_sync(
signal,
prn,
code_block_bits,
num_code_blocks
)
Hard-path secondary-code sync detector for GPS L5I over the shared NH10 code — see _detect_secondary_code_sync. The default detector is the soft one (uses_soft_secondary_code_detection).
TrackingLoops.detect_bit_or_secondary_code_sync — Method
detect_bit_or_secondary_code_sync(
signal,
prn,
code_block_bits,
num_code_blocks
)
Hard-path secondary-code sync detector for the GPS L5Q pilot over the shared NH20 code — see _detect_secondary_code_sync. The default detector is the soft one (uses_soft_secondary_code_detection).
TrackingLoops.detect_bit_or_secondary_code_sync — Method
detect_bit_or_secondary_code_sync(
signal,
prn,
code_block_bits,
num_code_blocks
)
Symbol-sync detector for Galileo E1B (both GalileoE1B and the BOC(1,1) approximation GalileoE1B_BOC11): one I/NAV symbol per 4 ms primary code period (250 sym/s; Galileo OS SIS ICD Tables 11, 15), so it locks immediately — see _detect_symbol_is_code_block_sync.
TrackingLoops.detect_bit_or_secondary_code_sync — Method
detect_bit_or_secondary_code_sync(
signal,
prn,
code_block_bits,
num_code_blocks
)
Hard-path secondary-code sync detector for the Galileo E1C pilot (both GalileoE1C and the BOC(1,1) approximation GalileoE1C_BOC11) over the shared CS25 code (Galileo OS SIS ICD Table 4; 100 ms cycle) — see _detect_secondary_code_sync. The default detector is the soft one (uses_soft_secondary_code_detection).
TrackingLoops.detect_bit_or_secondary_code_sync — Method
detect_bit_or_secondary_code_sync(
signal,
prn,
code_block_bits,
num_code_blocks
)
Hard-path secondary-code sync detector for Galileo E5a-I over the shared CS20 code (Galileo OS SIS ICD Table 19) — see _detect_secondary_code_sync. One CS20 period is exactly one 50 sps F/NAV symbol. The default detector is the soft one (uses_soft_secondary_code_detection).
TrackingLoops.detect_bit_or_secondary_code_sync — Method
detect_bit_or_secondary_code_sync(
signal,
prn,
code_block_bits,
num_code_blocks
)
Hard-path secondary-code sync detector for the Galileo E5a-Q pilot over the per-PRN CS100 code (Galileo OS SIS ICD Table 20; 100 ms cycle) — see _detect_secondary_code_sync. The default detector is the soft one (uses_soft_secondary_code_detection).
TrackingLoops.detect_bit_or_secondary_code_sync — Method
detect_bit_or_secondary_code_sync(
signal,
prn,
code_block_bits,
num_code_blocks
)
"Sync" detector for Galileo E5a-QP — reports found = true from the very first integration via _detect_symbol_is_code_block_sync.
With no data and no overlay every block boundary is equivalent; the lock only gates the switch from single-block pre-sync integration to the whole-cycle one (max_num_code_blocks_to_integrate).
TrackingLoops.detect_bit_or_secondary_code_sync — Method
detect_bit_or_secondary_code_sync(
signal,
prn,
code_block_bits,
num_code_blocks
)
Hard-path secondary-code sync detector for Galileo E5b-I over the shared CS4 code (1110; OS SIS ICD v2.2 §3.5.1) — see _detect_secondary_code_sync. One CS4 period is exactly one 250 sym/s I/NAV symbol. The default detector is the soft one (uses_soft_secondary_code_detection).
TrackingLoops.detect_bit_or_secondary_code_sync — Method
detect_bit_or_secondary_code_sync(
signal,
prn,
code_block_bits,
num_code_blocks
)
Hard-path secondary-code sync detector for the Galileo E5b-Q pilot over the per-SVID CS100 code (OS SIS ICD v2.2 §3.5.2: CS100₍ₙ₊₅₀₎ for SVID n; 100 ms cycle) — see _detect_secondary_code_sync. The default detector is the soft one (uses_soft_secondary_code_detection).
TrackingLoops.detect_bit_or_secondary_code_sync — Method
detect_bit_or_secondary_code_sync(
signal,
prn,
code_block_bits,
num_code_blocks
)
Symbol-sync detector for Galileo E6-B: one C/NAV symbol per 1 ms primary code period (1000 sym/s; OS SIS ICD v2.2 Table 5), so it locks immediately — see _detect_symbol_is_code_block_sync.
TrackingLoops.detect_bit_or_secondary_code_sync — Method
detect_bit_or_secondary_code_sync(
signal,
prn,
code_block_bits,
num_code_blocks
)
Hard-path secondary-code sync detector for the Galileo E6-C pilot over the per-SVID CS100 code (E6-B/C Codes Technical Note §2.4: CS100ₙ for SVID n, the same half of the table as GalileoE5aQ; 100 ms cycle) — see _detect_secondary_code_sync. The default detector is the soft one (uses_soft_secondary_code_detection).
TrackingLoops.detect_bit_or_secondary_code_sync — Method
detect_bit_or_secondary_code_sync(
signal,
prn,
code_block_bits,
num_code_blocks
)
Hard-path secondary-code sync detector for BeiDou B1I over the per-PRN NH20 overlay (20 ms tiered code) — see _detect_secondary_code_sync. GEO PRNs never sync (see BeiDou GEO satellites). The default detector is the soft one (uses_soft_secondary_code_detection).
TrackingLoops.detect_bit_or_secondary_code_sync — Method
detect_bit_or_secondary_code_sync(
signal,
prn,
code_block_bits,
num_code_blocks
)
Hard-path secondary-code sync detector for BeiDou B3I over the per-PRN NH20 overlay — see _detect_secondary_code_sync; behaves as BeiDouB1I's. The default detector is the soft one (uses_soft_secondary_code_detection).
TrackingLoops.detect_bit_or_secondary_code_sync — Method
detect_bit_or_secondary_code_sync(
signal,
prn,
code_block_bits,
num_code_blocks
)
Hard-path secondary-code sync detector for the BeiDou B2a data component over the shared 5-chip code (00010; BDS-SIS-ICD-B2a-1.0 §5.2.1) — see _detect_secondary_code_sync. One period is exactly one 200 sym/s B-CNAV2 symbol. The default detector is the soft one (uses_soft_secondary_code_detection).
TrackingLoops.detect_bit_or_secondary_code_sync — Method
detect_bit_or_secondary_code_sync(
signal,
prn,
code_block_bits,
num_code_blocks
)
Hard-path secondary-code sync detector for the BeiDou B2a pilot over the per-PRN 100-chip code (BDS-SIS-ICD-B2a-1.0 §5.2.1 Table 5-4; 100 ms cycle) — see _detect_secondary_code_sync. The default detector is the soft one (uses_soft_secondary_code_detection).
TrackingLoops.detect_bit_or_secondary_code_sync — Method
detect_bit_or_secondary_code_sync(
signal,
prn,
code_block_bits,
num_code_blocks
)
Symbol-sync detector for BeiDou B2bI: one B-CNAV3 symbol per 1 ms primary code period (1000 sym/s; BDS-SIS-ICD-B2b-1.0 §5), so it locks immediately — see `detectsymboliscodeblock_sync`.
TrackingLoops.detect_bit_or_secondary_code_sync — Method
detect_bit_or_secondary_code_sync(
signal,
prn,
code_block_bits,
num_code_blocks
)
Symbol-sync detector for the BeiDou B1C data component: one B-CNAV1 symbol per 10 ms primary code period (100 sym/s; BDS-SIS-ICD-B1C-1.0 Table 5-1), so it locks immediately — see _detect_symbol_is_code_block_sync.
TrackingLoops.detect_bit_or_secondary_code_sync — Method
detect_bit_or_secondary_code_sync(
signal,
prn,
code_block_bits,
num_code_blocks
)
Secondary-code sync detector for the BeiDou B1C pilot: the hard _detect_secondary_code_sync over the per-PRN 1800-chip overlay (BDS-SIS-ICD-B1C-1.0 §5.2.2) on the 10 ms primary code, an 18 s cycle — the same shape as GPS L1C-P. Needs 1800 buffered blocks; accepts up to 45 errors (2.5 %).
Code-block buffer widths
get_code_block_buffer_type per signal, and what the packed buffer of that width is for:
| Signal | Returns | Detector / buffer role |
|---|---|---|
| GPS L1 C/A | UInt64 | soft bit-edge CFAR — holds up to 3 pre-sync bits |
| Galileo E1B | UInt8 | symbol = primary period, buffer unused |
| GPS L5I | UInt32 | soft secondary CFAR — packed buffer vestigial |
| GPS L5Q | UInt32 | soft secondary CFAR — packed buffer vestigial |
| GPS L1C-D | UInt8 | symbol = primary period, buffer unused |
| GPS L1C-P | UInt1800 | hard rotation sweep — 1800-chip overlay horizon |
| GPS L2CM | UInt8 | symbol = primary period, buffer unused |
| GPS L2CL | UInt8 | dataless pilot, no sync, buffer unused |
| Galileo E1C | UInt32 | soft secondary CFAR — packed buffer vestigial |
| Galileo E5a-I | UInt32 | soft secondary CFAR — packed buffer vestigial |
| Galileo E5a-Q | UInt128 | soft secondary CFAR — packed buffer vestigial |
| Galileo E5a-QP | UInt8 | no data, no overlay, buffer unused |
| Galileo E5b-I | UInt32 | soft secondary CFAR — packed buffer vestigial |
| Galileo E5b-Q | UInt128 | soft secondary CFAR — packed buffer vestigial |
| Galileo E6-B | UInt8 | symbol = primary period, buffer unused |
| Galileo E6-C | UInt128 | soft secondary CFAR — packed buffer vestigial |
| BeiDou B1I | UInt32 | soft secondary CFAR — packed buffer vestigial |
| BeiDou B3I | UInt32 | soft secondary CFAR — packed buffer vestigial |
| BeiDou B2a-I | UInt32 | soft secondary CFAR — packed buffer vestigial |
| BeiDou B2a-Q | UInt128 | soft secondary CFAR — packed buffer vestigial |
| BeiDou B2b-I | UInt8 | symbol = primary period, buffer unused |
| BeiDou B1C-D | UInt8 | symbol = primary period, buffer unused |
| BeiDou B1C-P | UInt1800 | hard rotation sweep — 1800-chip overlay horizon |
The soft detectors read the incremental PhaseAccumulators instead of the packed buffer. GPS L1 C/A still recovers the pre-sync bits it holds once sync is found; the secondary-coded signals do not, as their pre-sync signs carry the overlay, not data.
BeiDou GEO satellites
BDS-SIS-ICD-B1I-3.0 §5.2.1 applies the 20-bit Neuman-Hoffman overlay only on the MEO/IGSO satellites (PRN 6-58), which broadcast the D1 message at 50 sym/s: one NH20 period is one data symbol. The GEO satellites (PRN 1-5 and 59-63) broadcast D2 at 500 sym/s and carry no overlay; GNSSSignals models that with all-ones columns of a per-PRN secondary code, so B1I and B3I report a 20-chip secondary code for every PRN and use the secondary-code detector, not the bit-edge one.
On a GEO PRN that search never completes: an all-ones reference is rotation-invariant, so the 20 rotation hypotheses differ only in how their 20-block bins straddle the data, and with 2-block D2 symbols every bin averages about ten random symbols and none stands out. The soft CFAR detector needs a peak that beats its runner-up, so it declines to lock. A GEO satellite therefore tracks and can be ranged on, but stays in the one-block pre-sync integration and decodes no bits. It takes both the missing overlay and the D2 rate: without an overlay at the D1 rate the bins would reduce to GPS L1 C/A's bit-edge search and lock on the symbol boundary (test/signals.jl tests both cases). Decoding a GEO satellite needs a per-PRN data rate upstream: get_data_frequency is per signal type and reports the D1 50 Hz for every PRN.