MSK Decode x16 Back-End (msk_decode_x16)
Decodes 16 channels of tagged OPV soft decisions into frames using a single time-shared Viterbi decoder, with no DSP front-end.
Tested platforms include xsim.
Design
msk_decode_x16 is the shared back-end decoder of the folded 16-channel OPV
demodulator chain. It receives tagged soft decisions produced by the upstream
front-end fold workers (cr_x16 → ted_x16 → dsd_x16 → fs_x16),
collects them into per-channel double-buffered BRAM soft stores, and
time-multiplexes a single opv_frame_decoder (Viterbi, rate-1/2 k=7) across
all 16 channels at 16:1.
The input stream uses complex_short_timed_sample metadata/sample framing.
Per payload symbol, fs_x16 emits a metadata message (4 words; the local
channel index 0..15 is carried in byte 2 of the value field), followed by a
one-sample message whose bit 16 is frame_start and whose low 3 bits are the
3-bit signed quantized soft decision. Sync symbols have already been discarded
by fs_x16; every forwarded symbol is a payload symbol.
The BRAM soft store consists of 32 banks arranged as a flat 1-D array
(bank = ch*2 + buf_sel, flat_addr = bank*4096 + symbol_index).
Each bank holds 2144 nibbles (4-bit cells: 3-bit signed soft + sign-extension
bit). The power-of-2 depth (4096 entries, only 0..2143 used) is required for
Vivado to infer BRAM18 primitives; a non-power-of-2 depth of 2144 triggers
Synth 8-7186 and causes the array to fall back to flip-flops. The flat 1-D
organisation with a registered bank index is likewise required — an
array-of-arrays with a dynamic outer index is seen as a non-memory variable and
also falls back to flip-flops. 32 banks of 4K x 4-bit each map to 32 BRAM18
primitives (one per bank), fitting easily within ZC706 or ZCU102 resources.
The double-buffer scheme decouples collection from decode: while the scheduler
reads and re-drives one bank to the Viterbi, the next frame for that channel
collects into the other bank. A sample beat that would write into a bank still
awaiting decode is stalled via backpressure on input_out.take. Metadata
beats are never stalled.
The scheduler scans the 32 bank-ready flags in priority order (lowest bank index
first), picks the first ready bank, and re-drives all 2144 symbols to the shared
opv_frame_decoder at one symbol per clock. It accounts for the 2-cycle BRAM
read latency through the SCHED_RD0 / SCHED_RD1 / SCHED_LAST1 /
SCHED_LAST2 pipeline drain states.
Each decoded frame produces one output frame sequence: a 16-byte metadata
message (uchar_timed_sample metadata opcode, carrying abs_bin =
port_index*16 + local_ch in value byte 0), followed by a 134-byte sample
message containing the decoded payload. The output FSM waits until a complete
frame is buffered in the 256-entry output FIFO before beginning the sample
message, ensuring EOM is asserted exactly on byte 133.
Four instances of this worker (port_index 0..3) cover all 64 channels of
a 64-bin channelizer. The frame_merge4 worker merges their four output
streams into a single DMA transfer to the host.
Latency at 100 MHz: re-drive burst is 2146 clocks per channel; Viterbi decode is approximately 70,881 clocks (~0.71 ms); 16 channels x 0.71 ms = 11.4 ms against a 40 ms frame period (28.5% utilisation).
Interface
<?xml version='1.0' encoding='utf-8'?>
<!-- msk_decode_x16: 16-channel OPV back-end decoder. This is msk_demod_x16 with
the entire per-channel DSP front-end removed (carrier recovery, matched
filter / timing, diff detector, soft quantizer, frame_sync are now the
folded workers cr_x16 -> ted_x16 -> dsd_x16 -> fs_x16). It keeps the SHARED
back-end: per-channel double-buffered soft BRAM collection, the 16:1
scheduler, the single opv_frame_decoder (Viterbi), and the output framing.
INPUT (tagged payload soft, from fs_x16): per payload symbol, metadata(value
= local channel 0..15) then a 1-sample message with bit 16 = frame_start
and the low 3 bits = the signed quantized soft. Sync symbols were already
dropped by fs_x16; every forwarded symbol is a payload symbol.
OUTPUT (decoded frames): uchar_timed_sample, per frame metadata(value =
abs_bin = port_index*16 + local ch) then 134 payload bytes.
Instantiate 4x (port_index 0..3), one per channelizer output port, for 64
channels. -->
<ComponentSpec>
<property name='init' type='bool' writable='true' default='false'
description="Pulse high to reset all collect buffers and decoder state"/>
<property name='port_index' type='uchar' writable='true' default='0'
description="Which group of 16 channels (0..3); abs_bin = port_index*16 + local ch"/>
<!-- Per-channel volatile status (index = local slot 0..15) -->
<property name='path_metric' type='ushort' volatile='true' arraylength='16'
description="Viterbi path metric for the most-recent decoded frame per channel"/>
<property name='dropped_frames' type='ulong' volatile='true' arraylength='16'
description="Frames dropped because the channel collect buffer was still full (should stay 0)"/>
<port name="input" producer="false" protocol="complex_short_timed_sample-prot"/>
<port name="output" producer="true" protocol="uchar_timed_sample-prot"/>
</ComponentSpec>
Ports
Inputs:
input: Tagged soft-decision stream fromfs_x16(complex_short_timed_sampleprotocol); carries per-symbol metadata + 3-bit signed soft decisions for 16 channels.Protocol:
complex_short_timed_sample-protOptional:
False
Outputs:
output: Decoded OPV frames (uchar_timed_sampleprotocol); each frame is preceded by a metadata message carryingabs_binand followed by 134 payload bytes.Protocol:
uchar_timed_sample-protOptional:
False
Opcode Handling
On the input port, metadata opcodes are parsed to extract the local
channel index from the value field. sample opcodes carry the soft decision
and frame_start flag.
On the output port, metadata opcodes carry the absolute bin tag; sample
opcodes carry the decoded payload bytes.
All other input opcodes are not forwarded and do not affect decoder state.
Properties
init: Pulse high to reset all collect buffers and decoder state.Type:
boolAccess:
Parameter:
FalseWritable:
TrueInitial:
FalseVolatile:
False
Default value:
false
port_index: Which group of 16 channels (0..3); abs_bin = port_index*16 + local ch.Type:
ucharAccess:
Parameter:
FalseWritable:
TrueInitial:
FalseVolatile:
False
Default value:
0
path_metric: Viterbi path metric for the most-recent decoded frame per channel.Type:
ushortArray dimensions:
['16']
Access:
Parameter:
FalseWritable:
FalseInitial:
FalseVolatile:
True
Default value:
None
dropped_frames: Frames dropped because the channel collect buffer was still full (should stay 0).Type:
ulongArray dimensions:
['16']
Access:
Parameter:
FalseWritable:
FalseInitial:
FalseVolatile:
True
Default value:
None
Implementations
msk_decode_x16(HDL)HDL implementation of the 16-channel OPV back-end decoder: per-channel double-buffered BRAM soft collection and a single time-shared Viterbi decoder.
Dependencies
The dependencies for this worker are:
ieee.std_logic_1164
ieee.numeric_std
viterbi_decoder_k7_soft.vhd(shared source frommsk_demod.hdl)
opv_frame_decoder.vhd(shared source frommsk_demod.hdl; wraps the Viterbi and output framing)32 BRAM18 primitives (inferred from the flat
soft_bramarray withram_style="block")
Limitations
Limitations of msk_decode_x16 are:
The worker handles exactly 16 channels. Four instances are required for 64 channels; a single instance cannot be reconfigured for a different channel count without modifying
N_CHand rebuilding.The
initproperty resets all collect buffers and the shared Viterbi simultaneously; any frame currently being decoded is lost.The tag FIFO depth is 4 entries. More than 4 frames in simultaneous in-flight decode (which is impossible at 16:1 scheduling) would cause tag corruption.
BRAM inference requires Vivado and the
ram_style="block"attribute; XSim will simulate correctly but uses LUTRAM, so BRAM resource counts are only meaningful in synthesis targets.
Testing
This component test suite uses the OpenCPI unit test framework. The unit test
drives the tagged soft-decision input format produced by fs_x16 and
verifies that all decoded frames appear on the output with correct metadata
and payload contents.
Tested platforms: None
Component testing not completed.