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_start is asserted here and the frame counter is seeded to SYNC_W + 1 = 25.

  • VERIFY — speculative payload output. Payload symbols (frame_ctr in [24, 2167]) are forwarded immediately. frame_start is re-asserted at frame_ctr = 24 on the second and subsequent frames. At frame_ctr = 23 the correlation is checked against lock_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_limit misses 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 24 entries).

  • 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 from dsd_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-prot

    • Optional: 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-prot

    • Optional: 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: bool

    • Access:

      • Parameter: False

      • Writable: True

      • Initial: False

      • Volatile: False

    • Default value: false

  • hunt_thresh: Correlation threshold to leave HUNT (signed).

    • Type: long

    • Access:

      • Parameter: False

      • Writable: True

      • Initial: False

      • Volatile: False

    • Default value: 550

  • lock_thresh: Correlation threshold to confirm sync in VERIFY/LOCKED (signed).

    • Type: long

    • Access:

      • Parameter: False

      • Writable: True

      • Initial: False

      • Volatile: 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: uchar

    • Access:

      • Parameter: False

      • Writable: True

      • Initial: False

      • Volatile: 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: long

    • Access:

      • Parameter: False

      • Writable: True

      • Initial: False

      • Volatile: 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: ulong

    • Access:

      • Parameter: False

      • Writable: True

      • Initial: False

      • Volatile: False

    • Default value: 0

  • locked: Frame-sync LOCKED state per channel.

    • Type: bool

      • Array dimensions: ['16']

    • Access:

      • Parameter: False

      • Writable: False

      • Initial: False

      • Volatile: True

    • Default value: None

  • frame_count: frame_start pulses (frames found) per channel since reset.

    • Type: ulong

      • Array dimensions: ['16']

    • Access:

      • Parameter: False

      • Writable: False

      • Initial: False

      • Volatile: 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_start marker.

Dependencies

The fs_x16 HDL worker depends on:

  • ieee.std_logic_1164

  • ieee.numeric_std

  • ocpi.types (OpenCPI generated types, including opcode enumerations for complex_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_CH constant; 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_thresh and lock_thresh properties must be tuned relative to signal amplitude. The defaults are calibrated for the soft amplitude produced by dsd_x16 at nominal signal levels; they are not self-normalizing. A robust fix would normalize the correlation by the estimated soft energy (deferred).

  • The frame_offset property 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