Frame Synchronizer, 16-Channel Folded (fs_x16)
16-channel time-shared frame synchronizer; correlates an incoming soft-decision
stream against a 24-bit sync word, runs a HUNT/VERIFY/LOCKED flywheel per
channel, and forwards only payload symbols — channel-tagged and marked with
frame_start — to the back-end Viterbi decoder.
Tested platforms include xsim.
Design
fs_x16 is the fourth and final stage of the folded MSK front-end chain
(cr_x16 → ted_x16 → dsd_x16 → fs_x16). It implements the
time-shared equivalent of 16 parallel frame_sync blocks, consuming the
tagged soft-decision stream from dsd_x16 and producing a payload-only
tagged stream for msk_decode_x16.
Sync-word correlation. Each input sample carries a wide 16-bit soft
value in its high 16 bits (produced by dsd_x16). A 24-symbol shift
register is maintained per channel. On each sample beat the shift register
is updated and correlated against the 24-bit OPV sync word 0x02B8DB
(each bit contributing +soft or -soft to a 22-bit signed
accumulator).
HUNT/VERIFY/LOCKED state machine. The FSM logic is ported verbatim
from frame_sync.vhd — the proven single-channel reference that decodes
end-to-end in hardware:
HUNT — peak-descent detection. While the correlation is above
hunt_thresh, the running value is tracked. The FSM transitions to VERIFY as soon as the correlation starts falling (peak descent) or drops below threshold. The current symbol at transition time is the first payload symbol;frame_startis asserted here and the frame counter is seeded toSYNC_W + 1 = 25.VERIFY — speculative payload output. Payload symbols (
frame_ctrin[24, 2167]) are forwarded immediately.frame_startis re-asserted atframe_ctr = 24on the second and subsequent frames. Atframe_ctr = 23the correlation is checked againstlock_thresh; if it passes the FSM moves to LOCKED, otherwise it drops back to HUNT.LOCKED — flywheel. Identical payload forwarding and frame-counter wrap. Consecutive sync misses are counted; after
miss_limitmisses the FSM returns to HUNT.
The snap/re-anchor mechanism that was previously in an earlier revision of
this file has been removed. It caused variable frame_start offsets
by re-seeding the frame counter at arbitrary correlation peaks within the
payload window. The frame_start marker is now anchored solely to the
FSM transitions described above, matching frame_sync.vhd exactly.
Per-channel state. All state is held in 16-entry signal arrays, read and written atomically within the single clocked process that handles each sample beat. Because the input is symbol-rate sparse (one symbol every many clocks per channel), there is no collision between channels and no additional pipeline staging is required. State arrays:
sr— 24-deep shift register of 16-bit signed soft values (16 x 24entries).state— 2-bit FSM state (00= HUNT,01= VERIFY,10= LOCKED).frame_ctr— 12-bit frame position counter (0–2167).miss_count— 4-bit consecutive miss counter.hunt_above— 1-bit flag: correlation was above threshold on the previous symbol.hunt_prev— 22-bit signed previous correlation value (peak tracking).
Threshold tuning. The correct threshold values depend on the amplitude
of the soft decisions produced by dsd_x16. For nominal signal levels the
true 24-symbol sync correlation is approximately 660–678, while the worst
noise/false peak is around 442. The defaults hunt_thresh=550 /
lock_thresh=500 sit between these to ensure HUNT only transitions on a
genuine sync, and VERIFY/LOCKED reject false peaks. These values must both
be moved together if the signal level changes.
Output format. Payload symbols are forwarded as tagged messages. Each output sample word carries:
Bit
[16]—frame_start: 1 on the first payload symbol of each frame.Bits
[15:0]— 3-bit quantized soft decision sign-extended to 16 bits (low 16 bits of the input sample, passed through verbatim fromdsd_x16).
Sync-word symbols are dropped; the downstream msk_decode_x16 receives
exactly 2144 payload symbols per frame per channel.
Interface
<?xml version='1.0' encoding='utf-8'?>
<!-- fs_x16: FOLDED 16-channel frame synchronizer, the time-shared equivalent of
16 parallel frame_sync blocks from msk_demod_channel/frontend. Finds each
channel's frame boundary (sliding 24-bit sync-word correlator + HUNT/VERIFY/
LOCKED flywheel) and forwards only the PAYLOAD soft decisions, tagged and
marked with frame_start, to the back-end decoder (msk_decode_x16).
INPUT (tagged, from dsd_x16): per symbol, metadata(value=ch) then a 1-sample
message with HIGH 16 = wide 16-bit soft (correlated here) and LOW 16 = the
3-bit quantized soft (forwarded to the decoder).
OUTPUT (tagged payload soft): per PAYLOAD symbol, metadata(value=ch) then a
1-sample message: bit 16 = frame_start (1 on the first payload symbol of a
frame), low 16 = the 3-bit quantized soft sign-extended. Sync-word symbols
are dropped. The downstream decoder collects 2144 payload symbols/frame
per channel into BRAM and Viterbi-decodes.
FEED-FORWARD-ish: per-channel state = the 24-deep soft shift register, the
HUNT/VERIFY/LOCKED FSM state, frame counter, miss counter, and HUNT
peak-descent registers, all in 16-entry RAMs. One shared correlator/FSM
datapath. Symbol-rate input (sparse) -> each channel's update is atomic in
one clock, same fold validity as cr_x16/ted_x16/dsd_x16. -->
<ComponentSpec>
<property name='init' type='bool' writable='true' default='false'
description="Pulse high to reset all 16 per-channel frame-sync states"/>
<!-- Frame-sync tuning (shared). NOTE: unlike the monolith frame_sync.vhd
(hunt=20000/lock=16000, sized for full-scale soft ~+/-800), the folded
chain's diff-detector soft_n is small (~+/-27 for a nominal signal):
true 24-symbol sync correlation ~660-678, worst false/noise peak ~442.
Defaults sit between those so HUNT only locks on a true sync AND VERIFY/
LOCKED reject an off-by-N false lock (forcing re-hunt). BOTH must move
together with signal level. Robust fix = normalize corr by soft energy. -->
<property name='hunt_thresh' type='long' writable='true' default='550'
description="Correlation threshold to leave HUNT (signed)"/>
<property name='lock_thresh' type='long' writable='true' default='500'
description="Correlation threshold to confirm sync in VERIFY/LOCKED (signed)"/>
<property name='miss_limit' type='uchar' writable='true' default='16'
description="Consecutive sync misses before dropping LOCKED -> HUNT. Default 16: holds the lock through marginal re-checks so fs doesn't drop/re-hunt mid-stream (the proven-good value; 3 was too low and dropped frames)."/>
<property name='frame_offset' type='long' writable='true' default='0'
description="Symbols added to the HUNT-fire frame_ctr seed (fine-tune acquisition phase). Default 0. The seed is SYNC_W+frame_offset; fs_v fires on the VERIFY first-tick at v_ctr=SYNC_W, not at HUNT-fire."/>
<property name='settle_syms' type='ulong' writable='true' default='0'
description="Per-channel symbols to ignore after reset before HUNT may lock (skip front-end startup transient). Default 0 = disabled (the preamble provides the acquisition runway). Leave at 0."/>
<!-- Per-channel status -->
<property name='locked' type='bool' volatile='true' arraylength='16'
description="Frame-sync LOCKED state per channel"/>
<property name='frame_count' type='ulong' volatile='true' arraylength='16'
description="frame_start pulses (frames found) per channel since reset"/>
<port name="input" producer="false" protocol="complex_short_timed_sample-prot"/>
<port name="output" producer="true" protocol="complex_short_timed_sample-prot"/>
</ComponentSpec>
Ports
Inputs:
input: Tagged soft-decision input from dsd_x16 (metadata + sample per symbol; HIGH 16 = wide soft for correlation, LOW 16 = 3-bit quantized soft).Protocol:
complex_short_timed_sample-protOptional:
False
Outputs:
output: Tagged payload-soft output to msk_decode_x16 (metadata + sample per payload symbol; bit 16 = frame_start, low 16 = 3-bit quantized soft).Protocol:
complex_short_timed_sample-protOptional:
False
Opcode Handling
fs_x16 consumes complex_short_timed_sample metadata and sample
opcodes on its input. Metadata words are parsed to extract the channel index
from word 2 bits [3:0]; sample words drive the correlator and FSM.
On output, the same two opcodes are produced: four metadata words followed by one sample word for each payload symbol that is forwarded. Sync-word symbols are silently discarded and produce no output.
An input EOF propagates to the output after the internal FIFO is drained.
Properties
init: Pulse high to reset all 16 per-channel frame-sync states.Type:
boolAccess:
Parameter:
FalseWritable:
TrueInitial:
FalseVolatile:
False
Default value:
false
hunt_thresh: Correlation threshold to leave HUNT (signed).Type:
longAccess:
Parameter:
FalseWritable:
TrueInitial:
FalseVolatile:
False
Default value:
550
lock_thresh: Correlation threshold to confirm sync in VERIFY/LOCKED (signed).Type:
longAccess:
Parameter:
FalseWritable:
TrueInitial:
FalseVolatile:
False
Default value:
500
miss_limit: Consecutive sync misses before dropping LOCKED -> HUNT. Default 16: holds the lock through marginal re-checks so fs doesn’t drop/re-hunt mid-stream (the proven-good value; 3 was too low and dropped frames).Type:
ucharAccess:
Parameter:
FalseWritable:
TrueInitial:
FalseVolatile:
False
Default value:
16
frame_offset: Symbols added to the HUNT-fire frame_ctr seed (fine-tune acquisition phase). Default 0. The seed is SYNC_W+frame_offset; fs_v fires on the VERIFY first-tick at v_ctr=SYNC_W, not at HUNT-fire.Type:
longAccess:
Parameter:
FalseWritable:
TrueInitial:
FalseVolatile:
False
Default value:
0
settle_syms: Per-channel symbols to ignore after reset before HUNT may lock (skip front-end startup transient). Default 0 = disabled (the preamble provides the acquisition runway). Leave at 0.Type:
ulongAccess:
Parameter:
FalseWritable:
TrueInitial:
FalseVolatile:
False
Default value:
0
locked: Frame-sync LOCKED state per channel.Type:
boolArray dimensions:
['16']
Access:
Parameter:
FalseWritable:
FalseInitial:
FalseVolatile:
True
Default value:
None
frame_count: frame_start pulses (frames found) per channel since reset.Type:
ulongArray dimensions:
['16']
Access:
Parameter:
FalseWritable:
FalseInitial:
FalseVolatile:
True
Default value:
None
Implementations
fs_x16(HDL)16-channel time-shared frame synchronizer; sliding 24-bit sync-word correlator and HUNT/VERIFY/LOCKED flywheel per channel, forwarding only payload symbols tagged with channel id and a
frame_startmarker.
Dependencies
The fs_x16 HDL worker depends on:
ieee.std_logic_1164
ieee.numeric_std
ocpi.types(OpenCPI generated types, including opcode enumerations forcomplex_short_timed_sample-prot)
No external primitive libraries or generated ROM files are required; the correlator, FSM, and output formatting are all inlined in the worker architecture.
Limitations
Limitations of fs_x16 are:
The number of channels is fixed at 16 by the
N_CHconstant; it is not runtime configurable.The sync word (
0x02B8DB, 24 bits) and frame length (2168 symbols, 24-symbol sync + 2144 payload) are compile-time constants matched to the OPV waveform; they cannot be changed without re-synthesis.The
hunt_threshandlock_threshproperties must be tuned relative to signal amplitude. The defaults are calibrated for the soft amplitude produced bydsd_x16at nominal signal levels; they are not self-normalizing. A robust fix would normalize the correlation by the estimated soft energy (deferred).The
frame_offsetproperty adjusts the frame counter seed at HUNT transition but does not shift the payload window relative to an already acquired frame in LOCKED state.
Testing
The unit test applies a sequence of tagged soft-decision messages including
valid sync-word patterns and payload symbols, verifies that frame_start
is asserted exactly at the first payload symbol of each frame, that
locked status is asserted after VERIFY passes, and that the quantized
soft values forwarded in the output match the low 16 bits of the input.
The frame_count property is checked to confirm all active channels
advance monotonically.
Tested platforms: None