msk_demod_x16 HDL Worker
HDL implementation of the 16-channel OPV MSK demodulator: 16 parallel
msk_demod_frontend instances and one time-shared Viterbi decoder at 16:1.
Detail
This worker integrates a complete OPV demodulator chain — carrier recovery, timing recovery, soft-quantization, frame synchronisation, and Viterbi decoding — for 16 channels, sharing the computationally expensive Viterbi across all channels via time-multiplexing.
Input demux. Each channelizer output port delivers a 16-sample sample
message per frame period: SOM on word 0 (bin 0), EOM on word 15 (bin 15), with
Q in bits 31:16 and I in bits 15:0. A 4-bit position counter (pos) resets
to 0 on SOM and increments on each subsequent word. On each accepted word,
fe_data_valid(slot) is pulsed for exactly one clock and fe_i_in /
fe_q_in are driven from the current word. The demux only processes words
when all_buf_clear = '1' (no channel has a pending frame awaiting decode),
implementing coarse input backpressure to prevent BRAM overrun.
16 parallel front-ends. The gen_fe generate statement instantiates 16
msk_demod_frontend entities. Each front-end contains:
carrier_correction_4p: a fourth-power Costas loop with 32-bit NCO and PI loop filter. Proportional and integral gains are shared across all 16 front-ends via thecc_lpf_p_gain/cc_lpf_i_gain/cc_lpf_p_shift/cc_lpf_i_shiftproperties. To avoid a VRFC 10-3093 error in XSim (“neither a static name nor a globally static expression”), the resized gain values are pre-computed intocc_p_gain_slv/cc_i_gain_slvsignals before being mapped to all 16 port maps. The default shifts (p_shift=16,i_shift=22) are tuned for Charlie-to-Charlie over-the-air operation and achieve lock over approximately ±8 kHz.burst_detect(from thedspprimitive library): energy accumulator with exponential decay;burst_detand 24-bitenergy_outare readable via the volatile property arrays.msk_demod_channel: matched-filter correlator with Gardner timing-error detector and PI loop filter. Outputs a 16-bitsoft_outandomega_current(timing NCO step, readable per channel).soft_quantizer: maps the 16-bit soft value to a 3-bit signedsoft_q.frame_sync: preamble correlator and state machine (HUNT → VERIFY → LOCKED) that assertsframe_starton the first payload symbol of each detected frame andpayload_validfor all payload symbols in VERIFY or LOCKED state.lockedmirrors the LOCKED state.
dec_soft_valid(ch) is the AND of fe_soft_q_val_d(ch) and
fe_payload_valid(ch), gating the collect process to payload symbols only.
BRAM soft collect and 16:1 scheduler. The back-end is architecturally
identical to msk_decode_x16. 32 banks (bank = ch*2 + buf_sel) are
laid out as a flat 1-D signal soft_bram of 131,072 4-bit entries
(32 * 4096) with ram_style="block", inferred as 32 BRAM18 primitives.
The flat 1-D layout and registered bank index (wr_bank_r, rd_bank_r)
are required for reliable block RAM inference in Vivado (see Limitations).
The write process tracks a per-channel buffer selector (wr_buf_sel) and
write index (wr_idx). At frame_start, the write index resets;
at index 2143, buf_ready_set(ch) is pulsed, the selector toggles, and
rd_buf_sel(ch) records which buffer was just filled for the scheduler.
When any buf_ready flag is set, all_buf_clear goes low and
input_out.take is de-asserted, stalling the input for the duration of the
decode.
The 7-state scheduler (SCHED_IDLE / SCHED_RD0 / SCHED_RD1 /
SCHED_DRIVE / SCHED_LAST1 / SCHED_LAST2 / SCHED_WAIT) scans
the 16 buf_ready flags in fixed priority order. For the winning channel it
issues reads via rd_bank_r / rd_addr_r, accounting for the 2-cycle BRAM
read latency (two extra states drain the pipeline after the last issued
address). On the first symbol (sym_count = 0), dec_frame_str is
asserted and abs_bin = port_index*16 + decode_ch is enqueued into the 4-entry
tag FIFO. After all 2144 symbols are delivered, the scheduler waits in
SCHED_WAIT for dec_byte_last from opv_frame_decoder, captures the
path metric into ch_path_metric(decode_ch), and returns to SCHED_IDLE.
Output FSM and FIFOs. opv_frame_decoder emits up to 134 bytes per
frame into a 256-entry output FIFO. The output FSM begins a frame only when
the FIFO holds at least 134 bytes, guaranteeing that EOM is asserted on the
134th byte without stalling. A 4-entry tag FIFO carries one abs_bin byte
per in-flight decode. The metadata message layout matches
struct.pack("<LLQ", id=0, pad=0, value=abs_bin): 16 bytes total (4 id +
4 pad + 8 value), with abs_bin placed in value byte 0.
Worker Properties
ocpi_buffer_size_outputType:
ushortAccess:
Parameter:
FalseWritable:
FalseInitial:
TrueVolatile:
FalseRead back:
False
Default value:
0
ocpi_blocked_outputType:
ulongAccess:
Parameter:
FalseWritable:
FalseInitial:
FalseVolatile:
TrueRead back:
False
Default value:
None
ocpi_max_latency_outputType:
ushortAccess:
Parameter:
TrueWritable:
FalseInitial:
FalseVolatile:
FalseRead back:
False
Default value:
None
ocpi_latency_outputType:
ushortAccess:
Parameter:
FalseWritable:
FalseInitial:
FalseVolatile:
TrueRead back:
False
Default value:
None
ocpi_messages_outputType:
ulongAccess:
Parameter:
FalseWritable:
FalseInitial:
FalseVolatile:
TrueRead back:
False
Default value:
None
ocpi_bytes_outputType:
ulongAccess:
Parameter:
FalseWritable:
FalseInitial:
FalseVolatile:
TrueRead back:
False
Default value:
None
Worker Ports
inputProtocol:
complex_short_timed_sampleOptional:
False
outputProtocol:
uchar_timed_sampleOptional:
False
Inputs:
Outputs:
Worker Ports
Inputs:
input:Type:
StreamInterfaceProtocol:
complex_short_timed_sample-protClockDirection:
inData width:
32
Outputs:
output:Type:
StreamInterfaceProtocol:
uchar_timed_sample-protWorker EOF:
trueClock:
inputData width:
8
Utilisation
Utilization reporting is not currently implemented.