nx_chirp_ss.nx source
↩ module page · 181 lines · 7104 B
1// nx_chirp_ss.nx -- Chirp Spread Spectrum modulation, re-derived
2// from the public chirp-z transform (Cooley 1969, IEEE T-AU). This
3// is the modulation layer that gives the Nishi physical layer the
4// long-link low-SNR tolerance of LoRa-class radio WITHOUT touching
5// LoRa's patented demodulator tricks.
6//
7// Substrate v1 parameters:
8// - M = 4 symbols per chirp (2 bits/symbol, "SF=2")
9// - N = 64 samples per chirp
10// - inc_min .. inc_max in phase-units per sample define the swept
11// band; the chirp ramps linearly across [inc_min, inc_max).
12//
13// For symbol s, the chirp's instantaneous chip index is
14// (i + s * N/M) mod N. This is the cyclic-shift property that gives
15// CSS its orthogonality across the M-ary alphabet. Decoding is by
16// correlation against the M reference chirps; arg-max picks the
17// symbol. Substrate-friendly: integer-only, square-wave reference,
18// no FFT required at SF=2.
19//
20// Patent stance: linear-FM chirp modulation predates LoRa by decades
21// (Klauder 1960 radar; Cooley 1969 chirp-z math; Costas 1984 chirp
22// sequences). We re-derive from those public sources, not from LoRa.
23// Per cardinal feedback-nishilang-mission-displace-cuda-directx-via-
24// patent-clean-absorption: never copy reference code; absorb the math.
25//
26// genealogy_id: cooley_1969_chirp_z + klauder_1960_radar_chirp +
27// costas_1984_frequency_hopping + nx_bfsk_q10
28// lineage_id: nishi_chirp_ss_q10
29//
30// nx_safety_envelope:
31// intended_use: "Chirp spread-spectrum comms primitive (Costas
32// 1984 + LoRa-class linear FM chirp) -- low-
33// power telemetry, mesh networking, recovery
34// beacons"
35// sil_target: SIL2 (comms reliability; chirp recovery
36// in noisy channels gates safety
37// telemetry transport)
38// asil_target: QM
39// dal_target: DAL C
40// evidence: [Q10_fixed_point_deterministic,
41// Costas_1984_FH_pattern,
42// sealed_chirp_verdict_enum]
43// hazard_register: [bug-tape-chirp-collision-no-CSMA,
44// bug-tape-spread-factor-mismatch,
45// bug-tape-noise-floor-too-low-false-detect]
46// residual_risk: "Single-channel only; multi-channel collision
47// avoidance is upstream. SNR threshold
48// hardcoded per the spread factor; substrate
49// recommendation: dynamic SF queued."
50// verdict: NOT_YET_EVALUATED
51
52import "nx_syscalls_x86_64.nx"
53
54// Sealed verdict per modulate/demodulate call.
55const NX_CSS_VERDICT_UNKNOWN: i64 = 0
56const NX_CSS_VERDICT_OK: i64 = 1
57const NX_CSS_VERDICT_BUF_TOO_SMALL: i64 = 2
58const NX_CSS_VERDICT_BAD_PARAMS: i64 = 3
59const NX_CSS_VERDICT_SNR_LOW: i64 = 4
60const NX_CSS_VERDICT_N: i64 = 5
61
62const NX_CSS_PHASE_PERIOD: i64 = 1024
63const NX_CSS_PHASE_HALF: i64 = 512
64const NX_CSS_AMPLITUDE: i64 = 32767
65
66// Compute the chirp's chip phase-increment at sample i for symbol s.
67// chip_idx = (i + s * (N/M)) mod N
68// chip_inc = inc_min + chip_idx * (inc_max - inc_min) / N
69func _chip_inc(i: i64, s: i64, N: i64, M: i64,
70 inc_min: i64, inc_max: i64) -> i64 {
71 var chip_idx: i64 = i + s * (N / M)
72 while chip_idx >= N { chip_idx = chip_idx - N }
73 return inc_min + chip_idx * (inc_max - inc_min) / N
74}
75
76// Modulate `n_symbols` symbols (each in 0..M-1 packed as one byte)
77// into `n_symbols * N` int16-range samples. Returns sealed verdict;
78// writes total samples produced to *out_n.
79func nx_chirp_ss_modulate(
80 symbols: *u8, n_symbols: i64,
81 N: i64, M: i64, inc_min: i64, inc_max: i64,
82 out: *i64, out_cap: i64, out_n: *i64
83) -> i64 {
84 if n_symbols < 0 { return NX_CSS_VERDICT_BAD_PARAMS }
85 if N < 4 { return NX_CSS_VERDICT_BAD_PARAMS }
86 if M < 2 { return NX_CSS_VERDICT_BAD_PARAMS }
87 if (N / M) * M != N { return NX_CSS_VERDICT_BAD_PARAMS }
88 if inc_min < 1 { return NX_CSS_VERDICT_BAD_PARAMS }
89 if inc_max <= inc_min { return NX_CSS_VERDICT_BAD_PARAMS }
90 let total: i64 = n_symbols * N
91 if total > out_cap { return NX_CSS_VERDICT_BUF_TOO_SMALL }
92
93 var sym_idx: i64 = 0
94 while sym_idx < n_symbols {
95 let s: i64 = symbols[sym_idx] & 0xff
96 if s >= M { return NX_CSS_VERDICT_BAD_PARAMS }
97 var phase: i64 = 0
98 var i: i64 = 0
99 while i < N {
100 let inc: i64 = _chip_inc(i, s, N, M, inc_min, inc_max)
101 phase = (phase + inc) & (NX_CSS_PHASE_PERIOD - 1)
102 var sign: i64 = 1
103 if phase >= NX_CSS_PHASE_HALF { sign = -1 }
104 out[sym_idx * N + i] = sign * NX_CSS_AMPLITUDE
105 i = i + 1
106 }
107 sym_idx = sym_idx + 1
108 }
109 *out_n = total
110 return NX_CSS_VERDICT_OK
111}
112
113// Demodulate received samples by correlating each N-sample window
114// against the M reference chirps; pick arg-max |correlation|.
115// Reports min margin (= best - second-best) across the message.
116func nx_chirp_ss_demodulate(
117 samples: *i64, n_samples: i64,
118 N: i64, M: i64, inc_min: i64, inc_max: i64,
119 out_symbols: *u8, out_cap: i64,
120 out_n: *i64, out_min_margin: *i64
121) -> i64 {
122 if N < 4 { return NX_CSS_VERDICT_BAD_PARAMS }
123 if M < 2 { return NX_CSS_VERDICT_BAD_PARAMS }
124 if M > 32 { return NX_CSS_VERDICT_BAD_PARAMS }
125 let n_symbols: i64 = n_samples / N
126 if n_symbols > out_cap { return NX_CSS_VERDICT_BUF_TOO_SMALL }
127
128 var min_margin: i64 = 0x7fffffffffffffff
129 var sym_idx: i64 = 0
130 while sym_idx < n_symbols {
131 var best_corr: i64 = -1
132 var second_corr: i64 = -1
133 var best_s: i64 = 0
134 var s: i64 = 0
135 while s < M {
136 var phase: i64 = 0
137 var corr: i64 = 0
138 var i: i64 = 0
139 while i < N {
140 let inc: i64 = _chip_inc(i, s, N, M, inc_min, inc_max)
141 phase = (phase + inc) & (NX_CSS_PHASE_PERIOD - 1)
142 var sign: i64 = 1
143 if phase >= NX_CSS_PHASE_HALF { sign = -1 }
144 corr = corr + samples[sym_idx * N + i] * sign
145 i = i + 1
146 }
147 var a: i64 = corr
148 if a < 0 { a = -a }
149 if a > best_corr {
150 second_corr = best_corr
151 best_corr = a
152 best_s = s
153 } else {
154 if a > second_corr { second_corr = a }
155 }
156 s = s + 1
157 }
158 out_symbols[sym_idx] = best_s & 0xff
159
160 var margin: i64 = best_corr - second_corr
161 if margin < 0 { margin = -margin }
162 if margin < min_margin { min_margin = margin }
163
164 sym_idx = sym_idx + 1
165 }
166 *out_n = n_symbols
167 *out_min_margin = min_margin
168
169 let peak: i64 = N * NX_CSS_AMPLITUDE
170 if min_margin * 16 < peak {
171 return NX_CSS_VERDICT_SNR_LOW
172 }
173 return NX_CSS_VERDICT_OK
174}
175
176// Sealed-enum validity gate.
177func nx_css_verdict_is_valid(v: i64) -> i64 {
178 if v < 0 { return 0 }
179 if v >= NX_CSS_VERDICT_N { return 0 }
180 return 1
181}