Initial Galileo receiver and SignalSim integration

This commit is contained in:
2026-07-30 23:26:03 +03:00
commit 2a6ccfc23d
153 changed files with 8080 additions and 0 deletions
+119
View File
@@ -0,0 +1,119 @@
# Algorithms and conventions
## Signal and downconversion
The analytic sampled signal uses
\[
x[n]=A\,c(\tau_n)b(\tau_n)s_{BOC}(\tau_n)
e^{j(\phi_0+2\pi(f_{IF}+f_D)n/f_s+\pi\dot f_D(n/f_s)^2)}+w[n].
\]
The generator defines a positive Doppler as positive complex phase rotation.
Digital downconversion and a candidate Doppler wipeoff therefore use
\[
y[n]=x[n]e^{-j2\pi(f_{IF}+f_{bin})n/f_s}.
\]
The phase-accumulator NCO has modulus \(2^P\), frequency-control word
\(FCW=\operatorname{round}(f_o2^P/f_s)\), and update
\(phase_{n+1}=(phase_n+FCW)\bmod 2^P\).
## Code and FFT acquisition
Fractional code phase advances by \(f_c/f_s\) chips per sample and indexes the
chip with `floor(phase) mod code_length`. The BOC(1,1) sine-phased square wave
is +1 for the first half of a chip and -1 for the second half. This convention
is shared by generation, acquisition, and tracking.
The circular correlation is
\[
R_f[k]=\operatorname{IFFT}\{\operatorname{FFT}(y_f[n])
\operatorname{FFT}(c[n])^*\}[k].
\]
For coherent interval \(T_c\), the complex correlator output is retained before
power formation. Noncoherent accumulation is explicitly
\[
P_f[k]=\sum_{m=0}^{M-1}|R_{f,m}[k]|^2,
\quad T_{obs}=M T_c.
\]
Differential combining uses adjacent complex results before magnitude. E1B
hypothesis mode enumerates primary-epoch signs (limited to eight periods to
bound exponential work). E1C search enumerates secondary phase; known-phase
wipeoff uses phase zero at the first observation epoch. Delay results are
reported over one 4 ms primary period even for longer coherent integrations.
Zero-padding can provide a convenient FFT size or a denser interpolation of a
particular numerical representation, but it does not increase signal
bandwidth, physical information, or fundamental delay resolution.
## Detection
After circularly excluding a guard interval around the main peak, the model
computes
\[
M_2=P_{max}/P_{second},\qquad M_\mu=P_{max}/\bar P_N,
\]
plus `(peak-noise_mean)/(sidelobe-noise_mean)` and a CFAR-style
`(peak-noise_median)/noise_std`. The acquisition C/N0 diagnostic is
\[
\widehat{C/N_0}=10\log_{10}\left((P_{max}-P_N)/(P_NT_c)\right).
\]
It is an estimator, not a calibrated RF measurement. Half-chip competitive BOC
neighbors set a side-peak flag. `flag` is the implemented mitigation; an
unambiguous BOC/CBOC strategy plugs into the detector without changing results.
Parabolic interpolation preserves raw Doppler and sample bins.
## Tracking
Each component channel owns carrier/code NCO phase, loop states, correlation
history, lock counters, C/N0 history, and state-machine state. Phases advance at
every accepted sample and never reset on Python block boundaries.
Carrier discriminators are
\[
e_{PLL,2}=\tan^{-1}(Q/I),\qquad e_{PLL,4}=\operatorname{atan2}(Q,I)
\]
in radians. The cross/phase-difference FLL is
\[
e_{FLL}=\angle(P_kP_{k-1}^*)/(2\pi T)\quad[Hz].
\]
The normalized envelope DLL reports a locally calibrated chip error
\[
e_{DLL}=d\frac{|E|-|L|}{|E|+|L|}\quad[chips],
\]
with power and dot-product alternatives. A BOC false-lock flag compares VEVL
and prompt magnitudes.
For a second-order filter, noise bandwidth and damping generate
\[
\omega_n=\frac{8\zeta B_n}{4\zeta^2+1},\quad
k_p=2\zeta\omega_n,\quad k_i=\omega_n^2.
\]
The third-order starting model places three repeated stable poles derived from
the same \(\omega_n\): correction
\(3\omega_ne+3\omega_n^2\int e+\omega_n^3\iint e\). These are physical
starting coefficients, not final optimized receiver-loop values.
Carrier NCO frequency is corrected in hertz; code NCO frequency is nominal code
rate plus DLL correction and optional carrier aiding
\(f_{code,aid}=f_{carrier}f_c/f_{RF}\). Prompt C/N0 uses coherent versus total
prompt moments over a sliding window and divides the ratio by integration time.
+28
View File
@@ -0,0 +1,28 @@
# Configuration
`ReceiverConfig` owns RF, acquisition, tracking, front-end, and fixed-point
dataclasses. YAML loading is strict: a misspelled field is an error.
Important validation rules include positive sampling/loop rates, a real IF
strictly below Nyquist, integer sample counts per integration interval,
ordered correlator tap spacing, explicit FFT length when requested, E1B
bit-transition protection beyond 4 ms, and E1C secondary-code handling beyond
4 ms. E1B tracking is conservatively limited to 4 ms until navigation-symbol
synchronization is implemented.
`intermediate_frequency_hz` may be nonzero for either real or analytic input.
The front end always outputs complex baseband and divides the effective sample
rate by the integer decimation factor. Acquisition/tracking must use that output
rate.
Acquisition parallelism fields expose future hardware/software scheduling but
do not change numerical results. `zero_padding_mode: none` provides the exact
N-sample circular correlation. A longer FFT changes the sampled numerical
correlation/edge convention; it is not additional physical delay information.
Default YAML files:
- `e1_default.yaml`: requested 16.368 MHz nominal defaults.
- `acquisition_example.yaml`: faster 4.092 MHz executable search.
- `tracking_example.yaml`: 4.092 MHz tracking defaults.
+50
View File
@@ -0,0 +1,50 @@
# Phase gates and reproducible commands
The project was implemented in six gates. These are the exact command shapes
used to validate each gate; the final regression reruns their union.
## Phase 1
```bash
python -m pytest tests/unit/test_config.py tests/unit/test_signals.py
python examples/generate_e1_signal.py
```
## Phase 2
```bash
python -m pytest tests/unit/test_frontend.py tests/unit/test_acquisition_metrics.py tests/unit/test_acquisition_parallelism.py tests/integration/test_acquisition.py tests/integration/test_long_integration.py
python examples/run_acquisition.py
```
## Phases 3 and 4
```bash
python -m pytest tests/unit/test_discriminators.py tests/unit/test_loop_filters.py tests/unit/test_correlator.py tests/integration/test_tracking.py
python examples/run_tracking.py
python examples/run_complete_receiver.py
```
## Phase 5
```bash
python -m pytest tests/unit/test_fixed_point.py tests/unit/test_fixed_fft.py tests/integration/test_fixed_acquisition.py tests/regression/test_complete_receiver.py
python -m galileo_rx.cli compare-fixed --config configs/acquisition_example.yaml --input samples.bin
```
## Phase 6 and final regression
```bash
python -m pytest tests/unit/test_rtl_model.py tests/unit/test_vector_export.py
python -m pytest
```
The CLI smoke sequence is:
```bash
python -m galileo_rx.cli generate --config configs/acquisition_example.yaml --duration-ms 60 --prn 1 --output samples.bin
python -m galileo_rx.cli acquire --config configs/acquisition_example.yaml --input samples.bin --output acquisition.json
python -m galileo_rx.cli track --config configs/tracking_example.yaml --input samples.bin --acquisition-result acquisition.json --output tracking.csv
python -m galileo_rx.cli run --config configs/acquisition_example.yaml --input samples.bin
python -m galileo_rx.cli compare-fixed --config configs/acquisition_example.yaml --input samples.bin
```
+45
View File
@@ -0,0 +1,45 @@
# Fixed-point plan
The fixed framework represents signed two's-complement integers with declared
total/fractional widths. Every quantizer selects saturation or wrap and nearest,
truncate, or convergent ties-to-even rounding. Quantizers count overflow and
accumulate RMS/peak quantization error.
Proposed starting points (not final widths):
| Stage | Width | Fraction | Policy |
|---|---:|---:|---|
| input sample | 8 | 7 | saturate |
| sine/cosine | 16 | 15 | saturate |
| mixer output | 18 | 15 | saturate |
| FFT input | 18 | 15 | saturate |
| FFT twiddle | 16 | 15 | convergent |
| FFT stage | 20 | 15 | configured scaling |
| correlation product | 36 | 30 | full precision then requantize |
| IFFT output | 28 | 15 | saturate |
| coherent accumulator | 40 | 15 | saturate |
| noncoherent accumulator | unsigned 56 | 20 | saturate |
| carrier phase | unsigned 32 | 0 | natural wrap |
| code phase | unsigned 48 | 32 | natural wrap |
| loop state | 32 | 20 | saturate |
| FCW | signed 32 | 0 | wrap in phase accumulator |
| discriminator | 24 | 18 | saturate |
`FixedNco` and `FixedCorrelator` are integer bit-true models. Fixed PCPS models
declared quantization at input, normalized FFT output, pointwise product, IFFT
output/power, and accumulation. Exact butterfly-level identity depends on the
chosen streaming FFT IP scaling schedule and is explicitly unresolved until an
RTL FFT architecture is selected.
`FixedRadix2Fft` supplies a fully stage-quantized power-of-two oracle with
quantized twiddles, convergent butterfly rounding, saturation counters, and one
bit of scaling per stage. It is used for standalone RTL FFT vectors. The
runtime PCPS boundary model remains able to use arbitrary transform lengths;
matching a vendor FFT at those lengths requires configuring that core's exact
schedule.
The sweep utility varies input/FFT widths and reports mean absolute/relative,
RMS, peak error, acquisition decision, Doppler bin, code phase, and overflows.
Tracking qualification additionally compares convergence, steady-state jitter,
and loss-of-lock decisions; these metrics must become release gates once loop
state formats are frozen.
+104
View File
@@ -0,0 +1,104 @@
# RTL mapping and architecture tradeoffs
All streaming datapaths use `data`, `valid`, `ready`, optional `first/last`,
`channel_id`, and sideband metadata. State advances only on `valid && ready`.
Backpressure must propagate or terminate in a sized sample FIFO. Reset clears
valid/state/overflow counters and sets NCO phases to software-programmed start
values; it must not occur between arbitrary host blocks.
## Acquisition formulas
Let \(N_s=\operatorname{round}(f_sT_c)\), FFT size \(N\ge N_s\), PRNs \(P\),
Doppler bins \(D\), noncoherent intervals \(M\), Doppler lanes \(L\), FFT
latency/interval \(C_{FFT}\), and clock \(f_{clk}\).
- observation buffer: \(MN_s\) complex samples;
- transforms per PRN: \(2DM\) when code FFTs are precomputed;
- code FFT store: \(PN\) complex words (or \(N\) for on-demand PRN loading);
- pointwise complex multiplications: \(PDMN\);
- approximate single-engine search cycles:
\(P\lceil D/L\rceil M(2C_{FFT}+N)\);
- acquisition time is those cycles divided by \(f_{clk}\), plus fill and control
overhead.
### A — buffered FFT PCPS
One coherent block is buffered. A mixer iterates Doppler bins, a forward FFT is
multiplied by conjugated code FFT memory, the same or a second engine performs
IFFT, magnitude squared accumulates in Doppler/code RAM, and peak search scans
the result. It minimizes mixers/FFT engines/DSPs and maximizes search latency.
Ping-pong buffers hide capture behind search. One FFT engine needs arbitration
and doubles transform serialization; two remove that conflict at extra DSP/BRAM.
### B — parallel Doppler lanes
`NUM_DOPPLER_LANES=L` replicates carrier mixers and either feeds batched FFTs or
replicates FFT engines. With one reused FFT, lanes improve mixing but not the FFT
bottleneck. With L FFT/IFFT paths, ideal Doppler latency approaches 1/L while
DSP, twiddle ROM, transpose memory, and noncoherent RAM ports grow roughly with
L. Intermediate choices use L mixers, a smaller number of FFT engines, and
FIFOs. Resource budgets and required reacquisition time select L; neither
architecture is universally preferred.
## Tracking
Fully parallel mode instantiates carrier/code NCOs and VE/E/P/L/VL complex
MACs per channel. It provides one-sample-per-clock latency independence and
uses the most DSPs and accumulators.
Time-multiplexed mode stores phase words, FCWs, five complex accumulators, loop
states, counters, and secondary phase per channel in `channel_state_ram`.
`NUM_CORRELATOR_LANES` engines visit all active channels for every input sample.
The no-loss condition is
\[
\left\lceil\frac{NUM\_TRACKING\_CHANNELS}{NUM\_CORRELATOR\_LANES}\right\rceil
C_{channel}\le f_{clk}/f_s.
\]
If it fails, more lanes, a faster clock, a lower post-decimation sample rate, or
fewer active channels are required. A sample FIFO cannot repair a sustained
rate violation.
Parameters are `NUM_TRACKING_CHANNELS`, `NUM_CORRELATOR_LANES`, `SAMPLE_WIDTH`,
`NCO_PHASE_WIDTH`, `CODE_PHASE_WIDTH`, `ACCUMULATOR_WIDTH`, and
`MAX_INTEGRATION_SAMPLES`. The Python `TrackingScheduler` evaluates this exact
condition.
The detailed per-module signal, width, latency, storage, reset, saturation, and
verification contract is in `src/galileo_rx/rtl_model/architecture.md`.
## SignalSim gate before Acquisition IP / Tracking IP implementation
The validated external stimulus is complex IQ8 at 8.184 MHz with +2.046 MHz
IF. `validate-signalsim` exports the 20 ms acquisition observation as separate
two's-complement I/Q hex files, a per-sample timestamp CSV, and symbolic
register values under `reports/signalsim_validation/rtl_vectors/`.
The fixed acquisition boundary model agrees with the float64/complex128
reference on Doppler bin, code-phase sample, and acquisition decision with zero
overflow. Its normalized correlation-map RMS error is 0.00219 and peak error is
0.01996 for the proposed comparison formats. This is an input/FFT/product/power
boundary result; it does not yet include a selected vendor FFT butterfly shift
schedule.
Measured requirements feeding the RTL plan are:
- complex IQ input and `IQ` ordering must be a first-class sample-input mode;
- code ROM import must discard one padding bit per 1024-bit SignalSim E1 table
sector—no unexplained chip offset is permitted;
- acquisition-to-tracking handoff carries IF center separately from Doppler so
code aiding never uses the IF frequency;
- E1B pull-in supports 2 ms dumps and data-insensitive FLL, followed by paired
4 ms raw-symbol outputs;
- E1C channel state includes secondary phase (measured phase 10 for this
vector) and supports wipeoff before PLL/discriminator processing;
- tracking measurement/APB visibility includes prompt I/Q, carrier/code NCOs,
state, lock metrics, secondary phase, and symbol-valid/data;
- APB addresses are still unassigned. The exported metadata uses symbolic
names and therefore blocks a bus-level RTL run until the register map exists.
Native real-IF SignalSim stimulus is unavailable, so the real-input DDC path
remains covered only by internal Python vectors. Full CBOC, IQ2, full I/NAV
decoding, nominal/weak C/N0, signed-Doppler, and multi-PRN external regressions
remain gates before claiming broad Galileo/RTL compatibility.
+241
View File
@@ -0,0 +1,241 @@
# SignalSim integration and measured compatibility
## Repository identity and immutable baseline
The detected project is the GNSS simulator by Jun Mo (`globsky/SignalSim`), not
another repository with a similar name.
| Item | Detected value |
|---|---|
| Local path | `/home/onur/Desktop/galileo/signalsim/SignalSim` |
| Remote | `https://github.com/globsky/SignalSim` |
| Commit | `80b262113e77c4482ca9f5cbfef9bcada402ed28` |
| Branch/status before integration | `main...origin/main`, clean |
| License evidence | No LICENSE/COPYING file; source headers say “All rights reserved”; README identifies some commercially licensed features |
SignalSim itself was not modified. The receiver parses its code tables from the
external checkout at run time, so the SignalSim commit remains part of the test
vector provenance and its copyrighted tables are not copied into this project.
The identity commands were:
```bash
git -C signalsim/SignalSim remote -v
git -C signalsim/SignalSim rev-parse HEAD
git -C signalsim/SignalSim status --short --branch
```
## Reproducible build
```bash
tools/build_signalsim.sh /home/onur/Desktop/galileo/signalsim/SignalSim
ctest --test-dir build/signalsim --output-on-failure
```
The build used CMake 3.31.9, GCC 11.4.0, Ninja 1.11.1, C++20, Release mode,
OpenMP, and `USE_NATIVE_OPT=OFF`. It produced `build/signalsim/IFdataGen`.
CTest reported `No tests were found!!!`; the repository does not define
IFdataGen CTest tests at this commit. The documented `-v`/`--version` options do
not print a version and instead fall through to configuration handling.
## Source-to-sample trace
The executed Galileo E1 path is:
```text
JSON output.systemSelect "Galileo"/"E1"
-> IFdataGen frequency selection
-> PrnGenerate(GalileoSystem, SIGNAL_INDEX_E1, SVID)
E1B = E1MemoryCode[(SVID-1)*128]
E1C = E1MemoryCode[(SVID+49)*128]
-> SatelliteSignal
E1B = -I/NAV symbol / sqrt(2)
E1C = 25-symbol pilot sign / sqrt(2)
-> SatIfSignal BOC half-chip sign and carrier rotation
-> complex Gaussian noise + AGC
-> IQ8/IQ16/IQ4/IQ2 quantizer
-> binary file, I before Q
```
`GetMemorySequence()` reads four storage sectors per E1 code. Within each
32-word/1024-bit sector it consumes 1023 bits MSB first and discards the last
padding bit. The receiver adapter mirrors that exact behavior. Treating the
table as 4092 contiguous bits was experimentally shown to reduce peak/second
from about 7 to about 1; the sector-aware implementation restores acquisition.
SignalSim models E1 as equal-amplitude E1B and E1C on the same real baseband
axis. It flips every second 2.046-Mchip/s half-chip, which is BOC(1,1). It does
not synthesize the BOC(6,1) term or full Galileo E1 OS CBOC in this path. E1B
and E1C cannot be selected separately by the JSON/CLI; `E1` always generates
the combined signal.
Source-inspected Galileo selection (only E1 was generated in this validation):
| Band selector | Components implemented in open source path | Data layer / pilot overlay | Validation status |
|---|---|---|---|
| E1 | E1B data + E1C pilot, inseparable | I/NAV / common CS25 | Generated and received |
| E5a | data I + pilot Q codes | F/NAV / PRN-specific CS100 | Source inspected only |
| E5b | data I + pilot Q codes | I/NAV / PRN-specific CS100 | Source inspected only |
| E6 | pilot output (data amplitude set to zero) | no open data path / CS100 | Source inspected only |
| E5 AltBOC | Listed as commercially licensed | N/A | Not available/tested |
### Navigation and secondary data
E1B is not a fixed or random placeholder bit stream. `INavBit::GetFrameData()`
constructs Galileo I/NAV words from the selected RINEX ephemeris/almanac/UTC
data, adds CRC-24Q, convolutionally encodes, interleaves, inserts the 10-symbol
sync pattern, and emits 500 encoded symbols per 2-second page. Word types above
20 are forced to dummy word 63; reserved/SAR/spare fields include zero-filled
placeholders. Reed-Solomon ephemeris parity words 17–20 are implemented.
E1C uses a common 25-symbol value held in `PilotBit.cpp` as `0x9b501c`, addressed
LSB first. This work verifies the sequence SignalSim actually emits; it does
not independently certify that constant against a particular Galileo ICD
revision.
See [the bit-layer report](../reports/signalsim_bit_generation_report.md) for a
stage-by-stage distinction between navigation fields, channel symbols,
spreading chips, and samples.
## Output formats and controls
`sampleFreq` and `centerFreq` are configurable JSON values in MHz and are
converted internally to integer kHz. A signal's complex IF is
`RF carrier - centerFreq`; out-of-band signals are disabled. Native output is
complex IQ only—there is no real-only IF binary mode.
| Format | Exact packing | Quantization at AGC gain `G` | Status |
|---|---|---|---|
| IQ16 | little-endian signed I16, signed Q16 | truncate `3277*G*x`, saturate −32768…32767 | usable |
| IQ8 | signed I8, signed Q8 | truncate `25*G*x`, saturate −128…127 | validated |
| IQ4 | one byte; I high nibble, Q low; sign-magnitude | truncate `3*G*abs(x)`, magnitude saturates at 7 | adapter tested |
| IQ2 | two complex samples/byte, low then high nibble; sign/magnitude | threshold levels | broken in this commit: computed byte is never assigned to output |
The adapter in `galileo_rx.io.signalsim_reader` validates file length, signed
conversion, byte order, IQ order, offsets, counts, memory mapping, and streaming.
All decoded samples are promoted to `numpy.complex128`; quantized integer widths
remain explicit input boundaries.
## Generated vector and executable results
The deterministic vector configuration is
`verification/vectors/signalsim/high_cn0_single_prn/config.json`. It requests
one visible Galileo PRN 1, combined E1B/E1C, 55 dB-Hz configured C/N0, 8.184
MHz complex IQ8, center 1573.374 MHz (IF +2.046 MHz), static receiver, bundled
RINEX navigation data, and 2.2 seconds duration.
```bash
build/signalsim/IFdataGen \
-c verification/vectors/signalsim/high_cn0_single_prn/config.json \
-o verification/vectors/signalsim/high_cn0_single_prn/high_cn0_single_prn_iq8.bin \
-t -st
MPLBACKEND=Agg python -m galileo_rx.cli validate-signalsim \
--signalsim-path signalsim/SignalSim \
--config configs/signalsim_validation.yaml \
--output-dir reports/signalsim_validation \
--mode quick
```
The sample file is 36,009,600 bytes (18,004,800 complex samples) with SHA-256
`3e72a5ad2d35ee68273b77587a66847b0fb328650b39c9e39272f0dc54e39e3c`.
SignalSim's validation summary reports PRN 1 Doppler `+1828 Hz`; this is rounded
to integer hertz. SignalSim does not emit exact sample-side code-delay truth.
Measured quick-mode results:
| Quantity | E1B | E1C |
|---|---:|---:|
| PCPS detected PRN | 1 | 1 |
| Raw Doppler bin | +1750 Hz | +1750 Hz |
| Interpolated Doppler | +1766.36 Hz | +1776.64 Hz |
| Code phase | 18309.410 samples | 18309.256 samples |
| Peak/second metric | 7.511 | 6.903 |
| Final tracking Doppler | +1828.251 Hz | +1828.284 Hz |
| Tracking Doppler jitter | 1.326 Hz RMS | 1.082 Hz RMS |
| DLL error | 0.0963 chips RMS | 0.0849 chips RMS |
| Final state | PLL_TRACK | PLL_TRACK |
| Reacquisition epochs | 0 | 0 |
E1B uses 2 ms pull-in integrations. A 4 ms BPSK-squared FLL has an unambiguous
range of only ±62.5 Hz, almost exactly the residual between the interpolated
estimate and the +1828 Hz truth. Two milliseconds expands that range to ±125
Hz, after which the channel converges and 2 ms prompt pairs form one 4 ms
symbol. Two exact I/NAV sync patterns were recovered 250 symbols (one second)
apart after a 300 ms tracking transient. This proves raw symbol recovery and
page-boundary synchronization, not FEC/page/CRC decoding.
E1C synchronization jointly searched secondary phase and residual carrier
frequency. It found phase 10 and +52.0 Hz residual, correlation 0.9830, and
0/100 sign errors. Wiping the secondary code yielded 13.72 dB of 25-period
coherent gain versus a 4 ms period, and 14.58 dB improvement relative to the
unwiped 25-period sum.
Full mode adds three generated vectors. The 50 dB-Hz configured nominal vector
passes E1C acquisition (metric 12.49), secondary synchronization (correlation
0.9947, 0/100 errors), and pilot tracking at +1828.672 Hz with 0.817 Hz jitter.
The negative-Doppler PRN 27 vector passes
acquisition at −2750 Hz versus −2637 Hz truth and, after pull-in, tracks at
−2637.776 Hz with 0.531 Hz jitter and 0.0615-chip DLL RMS. Five-PRN acquisition
detects PRNs 1, 4, 19, 21, and 27; every raw Doppler result is within one 250 Hz
bin. The negative channel raised five reacquisition flags during initial pull-in
but none after epoch 500, and finished in `PLL_TRACK`; that transient is retained
in the report rather than hidden.
Detailed machine results and plots are in
`reports/signalsim_validation/`, with acquisition-only results in
`reports/signalsim_acquisition_report.{json,md}`.
## Direct answers and limitations
1. Galileo generation: **yes**.
2. Galileo bands: E1, E5a, E5b, E6; E5 AltBOC is not in the open path.
3. E1B: **yes, only as part of combined E1**.
4. E1C: **yes, only as part of combined E1**.
5. Modulation: BOC(1,1) approximation, not CBOC.
6. Navigation data: complete encoded/interleaved I/NAV symbols are generated,
with the field limitations described above.
7. E1C secondary symbols: **yes**, 25 symbols.
8. Configurable IF: **yes**, through center frequency, kHz granularity.
9. Configurable sample rate: **yes**, kHz granularity.
10. Output: complex IQ only; no native real IF.
11. Binary formats: exact table above; IQ8 is the validated integration format.
12. PCPS acquisition: **pass** for E1B and E1C.
13. Carrier/code tracking: **pass** for E1B and E1C over the available vector.
14. E1B symbols: **raw symbols and sync recovered**; navigation decoder absent.
15. E1C secondary: **phase synchronized and long-coherent gain measured**.
16. Failures: no component-only E1, no CBOC, no real IF, broken IQ2 writer,
no exported exact delay/symbol truth, no receiver I/NAV FEC/page decoder,
and the remaining signal-format/component limitations listed above.
## Compatibility and RTL readiness
“Ready” below means the external stimulus, convention adapter, Python golden
result, and automated check exist. It does not claim the RTL IP itself is built.
| Capability | SignalSim support | Python receiver result | RTL readiness |
|---|---:|---:|---:|
| Galileo E1B generation | Partial (combined E1 only) | Pass | Ready |
| Galileo E1C generation | Partial (combined E1 only) | Pass | Ready |
| Real IF | No | Not tested | Blocked |
| Complex IQ | Yes | Pass | Ready |
| Configurable sample rate | Yes | Pass at 8.184 MHz | Ready |
| Configurable IF | Yes | Pass at +2.046 MHz | Ready |
| PCPS acquisition | N/A | Pass | Ready |
| Carrier tracking | N/A | Pass | Ready |
| Code tracking | N/A | Pass | Ready |
| E1B symbol recovery | Generated | Pass (raw + sync) | Ready |
| Full I/NAV decoding | Generated | Not implemented | Blocked |
| E1C secondary-code synchronization | Generated | Pass | Ready |
## Regression commands
```bash
pytest -m signalsim -v
pytest tests/integration/test_signalsim_acquisition.py -v
pytest tests/integration/test_signalsim_tracking.py -v
```
`--mode full` additionally regenerates/validates nominal-C/N0 acquisition,
negative-Doppler acquisition and tracking, and five-PRN acquisition. E1B-only
and E1C-only cases remain blocked because SignalSim cannot generate them.