RF Halfband Interpolator (prog)
Complex I/Q halfband FIR interpolator (fixed 2:1 rate change) whose coefficient set is driven at run time through a top-level COEFS bus instead of being baked into the netlist. Same zero-stuff-and-filter datapath as the fixed variant, with reloadable taps.
Introduction
The Halfband Interpolator (prog) block is the run-time programmable
sibling of halfband_interp. It doubles the sample rate of a complex
baseband stream by zero-stuffing and low-pass filtering, but its NumTaps
coefficients are supplied live on a top-level COEFS pin rather than
computed by the plugin and frozen into the VHDL.
The anti-image filter is a halfband — cutoff at Fs/4, every second tap zero (except the centre), symmetric — which is the cheapest FIR for a 2:1 interpolation. See halfband_interp for the full zero-stuff-and-filter theory.
slow rate fast (output) rate
IN_I ─┐ ┌── OUT_I
IN_Q ─┤ [ 0-stuff + ├── OUT_Q
COEFS ─┘ halfband ] └── SAMPLE_IN
What “programmable” means here
In the fixed variant the plugin computes the halfband taps, packs the
quantised values into a constant and wires it to the internal
coefs_packed port. In this prog variant that port is instead exposed
as a top-level input pin COEFS (NumTaps * CoefSize bits, packed
little-tap-first). You drive it from fabric — a register file, BRAM, an
AXI-lite word, a DMA, or another block — and change the anti-image
response at run time without re-synthesising.
You compute the taps yourself: quantise a unity-DC-gain, Fs/4-cutoff
low-pass to signed CoefSize-bit words and concatenate them so that
COEFS[(k+1)*CoefSize-1 : k*CoefSize] = signed h[k], for
k = 0 .. NumTaps-1.
Pin Description
k lives in
bits [(k+1)*CoefSize-1 : k*CoefSize] as a signed CoefSize-bit value.
Treated as slowly-varying (ap_stable); drive it from a register file,
BRAM or DMA. Supply Fs/4-cutoff halfband anti-image taps.
Properties
Bit width of each I/Q input sample (signed).
Bit width of each signed I / Q input sample. Range 4 to 32, default 16. Changing it triggers a redesign.Default: 16
Options: 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
Bit width of each coefficient (signed). Sets the COEFS bus width with NumTaps.
Bit width of each signed coefficient. Choices 10, 12, 14, 16, 18, 20, 24; default 16. Sets the COEFS bus width together with NumTaps. Changing it triggers a redesign (COEFS width changes).Default: 16
Options: 10 12 14 16 18 20 24
Number of taps.
Number of filter taps (filter order =NumTaps - 1). Choices
7, 11, 15, 19, 23, 27, 31, 39, 47, 63; default 15. Sets the
COEFS bus width together with CoefSize. Changing it triggers a
redesign. Use a canonical halfband length of the form 4k+3.
Default: 15
Options: 7 11 15 19 23 27 31 39 47 63
Usage
Coefficient bus (COEFS)
COEFS is a single wide std_logic_vector of width NumTaps * CoefSize.
Tap k occupies bits [(k+1)*CoefSize-1 : k*CoefSize] as a signed
two’s-complement CoefSize-bit value. Inside the HLS core the bus is
ap_stable (sampled every clock, assumed slowly varying), so update it
only when the datapath is quiescent (or accept a transient during reload).
Because the taps are unknown at synthesis time, Vivado cannot prune
the zero taps: the programmable variant instantiates the full NumTaps
multipliers per channel. Prefer the fixed variant if you never retune.
Zero-stuff-and-filter model
For each channel c in {I, Q}, the block inserts a zero between input
samples to form a fast-rate stream and filters it:
u_c[2m] = x_c[m], u_c[2m+1] = 0
y_c[n] = sum_{k=0..NumTaps-1} h[k] * u_c[n-k]
Passband gain note
Supply coefficients with the gain you want. If you quantise a unity-DC-gain
halfband, the interpolated envelope comes out at roughly half amplitude
because half the fast-rate samples are inserted zeros; scale the taps by
2 (or gain downstream) to preserve level — the usual factor-of-L
convention for an interpolate-by-L filter.
Bit widths
Data is treated as signed two’s complement.
IN_I,IN_Q: signedInputSizebits (slow-rate samples).COEFS:NumTaps * CoefSizebits (packed signed taps).- internal product :
InputSize + CoefSizebits. OUT_I,OUT_Q: signedInputSize + CoefSize + 8bits (fast rate).
The extra 8 accumulator bits (ACC_GROWTH = 8) prevent overflow.
SAMPLE_IN / strobe behaviour
Identical to the fixed variant. The block runs at the fast (output)
clock and produces a valid OUT_I/OUT_Q every clock. SAMPLE_IN is an
output asserted on the clocks where a new IN_I/IN_Q sample is
consumed (a zero is inserted on the others); use it as the “advance now”
enable for the slow-rate producer. The internal consume phase starts
true after reset.
Latency and reset
#pragma HLS PIPELINE II=1: one output pair per clock (fast rate).- 1-clock reported latency.
- Data ports use
ap_none;COEFSusesap_stable. RESET(ap_rst) clears the delay lines and re-arms the consume phase; it does not clearCOEFS, which is driven externally.
Typical applications
- DUC 2:1 up-sampling stages whose anti-image response must be re-tuned at run time.
- Adaptive / calibrated interpolation with taps loaded from software.
- One bitstream serving products that need different responses.
Resources & Timing
-
Latency: 1 clock cycle (reported)
-
Throughput: Runs at the fast (output) rate: 1 output pair per clock (II=1). A new input sample is consumed on every second clock (input rate = output rate / 2), flagged by SAMPLE_IN.
Implemented with Vitis HLS. Because the coefficients arrive at run time on COEFS, Vivado cannot prune zero taps: the full NumTaps multipliers per channel are instantiated (more DSPs than the fixed variant). COEFS is ap_stable — update it only when the pipeline is idle or tolerate a brief transient. Output level depends on the taps you load (a unity-DC-gain halfband yields ~half amplitude on zero-stuffed data; scale by 2 to compensate). Fixed 2:1 rate change is built into the block.