STANAG 4539: the high-rate waveform
hfmodem.bateleur.s4539 sends and receives the waveform STANAG 4539 uses for 3200 to 9600 bit/s, and 12800 bit/s uncoded. It was written from MIL-STD-188-110B appendix C, which specifies the same waveform; the STANAG's own text was not to hand. No foreign 4539 implementation has been tested against this one: every result below is this implementation against itself, through bateleur.sim's channels.
The waveform (appendix C)
| Part | Where | Paragraphs |
|---|---|---|
| 2400 symbols/s on 1800 Hz; data as QPSK (scrambled to look like 8-PSK), 8-PSK, 16-, 32- and 64-QAM; the tables C-V to C-VII | s4539.TABLES, data_frame | C.5.1 |
| Scrambling: 9 stages, x^9 + x^4 + 1, reset every frame; added to 8-PSK symbol numbers, exclusive-or'd with QAM ones | scramble_values | C.5.1.3, figure C-5 |
| Frames: 256 data symbols and a 31-symbol mini-probe; after every 72, the last 72 symbols of the preamble again | symbols, frames_plan | C.5.2 |
| Preamble: 0-7 blocks of 184 for AGC, 184 fixed symbols, and 103 carrying the rate and interleaver as three Barker codes' phases | preamble, known103 | C.5.2.1 |
| Mini-probe signs carrying rate, interleaver and set count | probe_signs | C.5.2.2 |
| Rate 1/2 K=7 code made a block code by full tail-biting, punctured to 3/4 | encode_block, fec.encode_tailbiting | C.5.3.2 |
| Block interleaver, 1 to 72 frames, loaded by increment and read in order, aligned to the reinserted preamble | encode_block | C.5.3.3 |
| End of message 4B65A5B2, and zero fill | find_eom | C.5.4.3 |
The implemented scrambler puts the newest bit at index 0, shifts right and feeds index 0 from index 4 XOR index 8: the fifth and ninth stages, or s[n-5] XOR s[n-9]. The seed is eight zeros followed by one. The last 3–6 stages form each output symbol before those shifts. This reading is unverified against a foreign 4539 implementation. Its recurrence agrees with the independently cross-checked 4285 generator, but that does not establish the 4539 seed, output grouping or reset convention.
Tail-biting seeds from the first six input bits and encodes the remaining bits followed by those six. A known-payload foreign recording spanning two interleaver blocks, including the preamble and mini-probes, would settle these readings. Local round trips and the table C-XVI interleaver example establish only the checks they exercise.
The receiver
Acquisition finds the preamble's 184 fixed symbols, then reads the rate and interleaver from the Barker codes: a symbol-spaced channel is fitted to the known symbols around them, and each of the 64 sets of three phases is tried against what arrived. Read this way, the mode came out right in every case tried on the 2 ms / 1 Hz channel down to 5 dB.
A preamble found under the threshold (to 0.8 of it) is taken if the first frames' mini-probes are where it says, with the signs it says: a fade can take a preamble down that far, and on the 2 ms / 1 Hz channel at 15 dB one did.
Far off frequency. Appendix C asks for 75 Hz (C.6.3, C.6.4). The search correlates the preamble in chunks of 23 symbols, and 75 Hz turns such a chunk 260 degrees, which leaves a third of the score, under the threshold. So the search runs three times, shifted to 0 and to 50 Hz either way, and a preamble found by more than one is taken from the search whose shift was nearest what it measured: the others may have locked to a later path of it.
Late entry. A receiver that missed the preamble (a fade can cover all 77 ms of it) joins at the next reinserted preamble, which is how appendix C asks that a receiver be able to deduce the mode "from the subsequent data portion": it looks for the reinserted preamble's closing "-" probe, checks for the "+" probe 71 symbols before it (an ordinary probe has data there), reads the Barker codes between, and decodes from the interleaver block that starts there. The two known ends of a reinserted preamble, 71 symbols apart, give the carrier offset to a fraction of a hertz; the search alone left it several hertz out, and the gains then turned too far between probes to interpolate. The synchronisation preamble ends with the same 103 symbols, so a preamble whose first part was lost in a fade is joined at its end, and nothing after it is lost.
The equalizer (blockeq.BlockEqualizer) is the path model of the 188-110 receiver (patheq), with one difference that decides everything at 64-QAM: the paths' gains come from the probes, not from the equalizer's own decisions. A ninth of this waveform's symbols are known, and the data is dense; tracking the gains from decisions, a fade's burst of wrong decisions turned the gains a quarter round and the equalizer stayed there, probes and all (9600 bit/s on 2 ms / 1 Hz lost most of its symbols at 40 dB). So each probe's gains are fitted from the samples only known symbols reach, the gains between two probes are interpolated, and a data block is equalized when the probe after it has come. The path search runs again every frame over the last eight probes, where the decisions cannot mislead it.
Then, for each block:
- it is equalized backwards as well as forwards, its feedback from the probe after it, and each symbol is taken from whichever direction's decisions sit nearer their points: a wrong decision's errors run on after it one way and before it the other;
- the gains are fitted again inside it, every 64 symbols, to what it decided, and it is equalized again, both ways;
- the soft values are scaled by the error seen around each symbol (over 32 symbols, and at least what the probes either side made), not by the filter's own estimate. That estimate came from the probe fits: 10 dB pessimistic where a block was clean, and blind to a block lost in a fade, which it then reported as sure. Measured instead, 9600 bit/s at 33 dB went from 2e-2 to 8e-4 on this alone.
Additional receiver requirements:
- The whole pulse. The transmitter's trim (
phy.TRIM_HZ, the rig's 3 kHz top edge) rings for some 10 ms, and a model of the pulse's lobe alone left a floor at -33 dB whatever the SNR: below 8-PSK's needs, above 64-QAM's. The model now reads 128 samples of a 160-sample table, and the floor is below -55 dB. A rig sent the untrimmed pulse (bandwidth = "3200") never meets this. - Nothing learned from the data in the first search. The path search uses only chunks wholly known, which at the start is the preamble.
- The pulse as it arrives off frequency. 75 Hz off, the signal meets the matched filter off centre: one edge of its spectrum is cut and the other passed into the filter's skirt, and what comes out, derotated, is no longer the pulse sent. 9600 bit/s on the poor channel lost 1 bit in 100 to that at 30 dB. The pulse is modelled as it leaves the filter at the carrier offset (to 5 Hz), and chosen again as the frequency loop moves.
The frequency loop is second order: from the gains' common turn between probes it keeps the offset and its rate of change, so a steady Doppler sweep is followed without lag, to 100 Hz either way. Each change of frequency turns the gains already fitted to match, and a block's gains are interpolated between its probes with their common turn taken evenly, not straight across, which at a quarter turn loses 3 dB midway.
Simulation measurements
The recorded short harness was rerun in simulation on 2026-09-30 with corrected Gaussian-spectrum Rayleigh fading. Each row uses one trial, one second of requested payload (minimum 16 bytes), message seed 1000 and channel seed 0. Preambles, interleaver fill and flush extend the waveform beyond the requested payload duration. SNR is measured in 3 kHz; the carrier offset is +23 Hz unless shown otherwise. Two-path channels have independent equal-power taps. Spread is twice the Gaussian spectral standard deviation. Every missing bit counts as an error.
Bounds below are one-sided 95% binomial upper bounds assuming independent bit errors. Fading errors are correlated, so these are conditional bounds. The short trials do not establish a standards performance target.
The earlier long-run figures lacked per-trial records and an exact source revision; their fading figures are withdrawn. The corrected short runs below retain zero errors and unchanged conditional bounds in all fifteen rows. The C6 checks request only one second of payload: they do not measure a five-minute synchronization hold or a full traversal of the Doppler sweep.
C-XVII: short run
| Mode | Channel: delay / spread | SNR dB | Bits | Errors | Upper 95% |
|---|---|---|---|---|---|
| 3200VL | AWGN | 9 | 3,200 | 0 | 0.000935728 |
| 3200VL | 2 paths, 2 ms / 1 Hz | 15 | 3,200 | 0 | 0.000935728 |
| 4800VL | AWGN | 13 | 4,800 | 0 | 0.000623916 |
| 4800VL | 2 paths, 2 ms / 1 Hz | 20 | 4,800 | 0 | 0.000623916 |
| 6400VL | AWGN | 16 | 6,400 | 0 | 0.000467974 |
| 6400VL | 2 paths, 2 ms / 1 Hz | 24 | 6,400 | 0 | 0.000467974 |
| 8000VL | AWGN | 19 | 8,000 | 0 | 0.000374396 |
| 8000VL | 2 paths, 2 ms / 1 Hz | 28 | 8,000 | 0 | 0.000374396 |
| 9600VL | AWGN | 21 | 9,600 | 0 | 0.000312007 |
| 9600VL | 2 paths, 2 ms / 1 Hz | 33 | 9,600 | 0 | 0.000312007 |
| 12800US | AWGN | 27 | 12,800 | 0 | 0.000234014 |
C6.3: short run
| Mode | Channel: delay / spread | SNR dB | Bits | Errors | Upper 95% | Offset / sweep |
|---|---|---|---|---|---|---|
| 9600L | 2 paths, 2 ms / 1 Hz | 30 | 9,600 | 0 | 0.000312007 | -75 Hz / +0 Hz/s |
| 9600L | 2 paths, 2 ms / 1 Hz | 30 | 9,600 | 0 | 0.000312007 | +75 Hz / +0 Hz/s |
C6.4: short run
| Mode | Channel: delay / spread | SNR dB | Bits | Errors | Upper 95% | Offset / sweep |
|---|---|---|---|---|---|---|
| 9600L | AWGN | 24 | 9,600 | 0 | 0.000312007 | +0 Hz / -3.5 Hz/s |
| 9600L | AWGN | 24 | 9,600 | 0 | 0.000312007 | +0 Hz / +3.5 Hz/s |
Reproduce these simulation rows serially:
python -m hfmodem.bateleur.measure --preset c-xvii --trials 1 --seconds 1 --output c-xvii-short.json
python -m hfmodem.bateleur.measure --preset c6-3 --trials 1 --seconds 1 --output c6-3-short.json
python -m hfmodem.bateleur.measure --preset c6-4 --trials 1 --seconds 1 --output c6-4-short.json
Omitting the short-run overrides requests the longer preset. See bateleur's simulation measurements for the channel correction and the recorded fields.
Not implemented
- The high-speed asynchronous and Ethernet user interfaces (C.5.4.1.2-3): the host dialect's data port carries bytes, and the modem frames them as a message with the EOM.
- A configurable maximum message length in blocks (C.5.4.5.3): reception ends at the EOM or when the signal is lost.
- Optional leading AGC blocks are not transmitted.