code wiki / (root) / nx_chirp_ss.nx

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}