Skip to content

File hbdecim_q15_core.h

FileList > hbdecim_q15 > hbdecim_q15_core.h

Go to the source code of this file

Fixed-point halfband 2:1 decimator for interleaved IQ int16 samples. More...

  • #include <stddef.h>
  • #include <stdint.h>
  • #include "dp_state.h"

Classes

Type Name
struct hbdecim_q15_state_t

Public Functions

Type Name
hbdecim_q15_state_t * hbdecim_q15_create (size_t num_taps, const float * h)
Allocate and initialise a fixed-point halfband 2:1 decimator. The FIR branch coefficients are supplied as float and converted internally to Q15 with a x0.5 polyphase rate scaling. The full halfband prototype is sparse (every other tap is zero); supply only the non-zero FIR branch taps, not the full sparse prototype.
void hbdecim_q15_destroy (hbdecim_q15_state_t * r)
Free all heap resources owned by the decimator state. Releases the Q15 coefficient buffer, all four delay rings, and the state struct itself. Passing NULL is a no-op. The Python wrapper calls this in del andexit ; call it explicitly only for deterministic release before GC reclaims the object.
size_t hbdecim_q15_execute (hbdecim_q15_state_t * r, const int16_t * in, size_t n_in, int16_t * out, size_t max_out)
Decimate a block of interleaved IQ int16 samples by 2. Input must be interleaved int16_t IQ pairs (I₀ Q₀ I₁ Q₁ …); pass a 1-D array of 2*n_complex elements. Each pair of complex input samples produces one complex output sample, so an array of length 2N yields at most N output pairs (2N int16 output values). If n_in is odd the trailing IQ pair is buffered and consumed on the next call.
size_t hbdecim_q15_execute_max_out (hbdecim_q15_state_t * r)
Maximum output samples for a given input length.
size_t hbdecim_q15_get_num_taps (const hbdecim_q15_state_t * r)
FIR branch length as supplied to the constructor. This is the count of non-zero symmetric taps in the FIR branch, not the full sparse halfband prototype length. Useful for introspection when chaining multiple stages with programmatically computed filter banks.
double hbdecim_q15_get_rate (const hbdecim_q15_state_t * r)
The sample-rate reduction factor; always 0.5 for 2:1 decimation. Exposed as a read-only property so pipelines can query the rate of each stage programmatically without hard-coding the 2:1 assumption.
void hbdecim_q15_get_state (const hbdecim_q15_state_t * state, void * blob)
void hbdecim_q15_reset (hbdecim_q15_state_t * r)
Zero all delay rings and clear the pending-sample flag. After a reset the decimator behaves identically to a freshly constructed instance: the four dual-write delay rings are zeroed and has_pending is cleared, so no partial IQ pair carries over. Call this between unrelated signal segments to prevent inter-segment leakage.
int hbdecim_q15_set_state (hbdecim_q15_state_t * state, const void * blob)
size_t hbdecim_q15_state_bytes (const hbdecim_q15_state_t * state)

Macros

Type Name
define HBDECIM_Q15_STATE_MAGIC [**DP\_FOURCC**](dp__state_8h.md#define-dp_fourcc) ('H','B','1','5')
define HBDECIM_Q15_STATE_VERSION 1u

Detailed Description

Input: interleaved int16_t pairs (I₀ Q₀ I₁ Q₁ …) as produced by ADCIQ. Output: decimated 2:1 interleaved int16_t IQ pairs.

Algorithm

The halfband filter has the polyphase property: one branch is a pure delay (every other sample passes through unchanged) and the other branch carries the FIR computation. Caller supplies the FIR branch coefficients as float; they are converted to Q15 internally with the standard x0.5 rate scaling.

The symmetric-fold optimisation halves the number of multiplications: instead of computing Sum h[k]*x[n-k] for all k, the filter computes Sum h[k]*(x[n-k] + x[n-(N-1-k)]) for k = 0..N/2-1, exploiting h[k] = h[N-1-k]. The center tap is a single unconditional right-shift (x0.5, baked in as the polyphase rate identity).

On AVX2 the inner loop uses _mm256_madd_epi16 to multiply 16 int16_t coefficient values against 16 int16_t folded delay-line samples in a single instruction, accumulating into 8 int32_t lanes. I and Q run as two independent madd chains on the same coefficient vector — free ILP on any superscalar core. The fold uses saturating add (_mm256_adds_epi16) which clips at +-32767; for signals at or below -1 dBFS the saturation never fires. The int32_t accumulator is reduced to int64_t before the final round-and-shift to Q15 output.

Delay-line layout

Even- and odd-indexed input samples are demultiplexed into separate I and Q rings (four int16_t dual-write rings total). The dual-write trick stores each value at position p and p+cap so the FIR inner loop reads a contiguous slice — no modulo arithmetic in the hot path.

Lifecycle

hbdecim_q15_state_t *r = hbdecim_q15_create(num_taps, h_fir);
// in:  interleaved int16_t IQ, 2*n_in elements
// out: interleaved int16_t IQ, 2*n_out elements (n_out <= n_in/2)
size_t n = hbdecim_q15_execute(r, in, n_in, out, max_out);
hbdecim_q15_destroy(r);

Public Functions Documentation

function hbdecim_q15_create

Allocate and initialise a fixed-point halfband 2:1 decimator. The FIR branch coefficients are supplied as float and converted internally to Q15 with a x0.5 polyphase rate scaling. The full halfband prototype is sparse (every other tap is zero); supply only the non-zero FIR branch taps, not the full sparse prototype.

hbdecim_q15_state_t * hbdecim_q15_create (
    size_t num_taps,
    const float * h
) 

Parameters:

  • num_taps Number of FIR branch coefficients in h (>= 1).
  • h Float FIR branch coefficients of length num_taps. Must be symmetric (h[k] == h[num_taps-1-k]).

Returns:

HBDecimQ15 instance.

>>> import numpy as np
>>> from doppler.filter import HBDecimQ15
>>> h = np.array([0.25, 0.5, 0.25], dtype=np.float32)
>>> dec = HBDecimQ15(h)
>>> dec.num_taps
3
>>> dec.rate
0.5


function hbdecim_q15_destroy

Free all heap resources owned by the decimator state. Releases the Q15 coefficient buffer, all four delay rings, and the state struct itself. Passing NULL is a no-op. The Python wrapper calls this in del andexit ; call it explicitly only for deterministic release before GC reclaims the object.

void hbdecim_q15_destroy (
    hbdecim_q15_state_t * r
) 

>>> import numpy as np
>>> from doppler.filter import HBDecimQ15
>>> h = np.array([0.25, 0.5, 0.25], dtype=np.float32)
>>> with HBDecimQ15(h) as dec:
...     y = dec.execute(
...         np.array([1000, 0, 1000, 0, 1000, 0, 1000, 0],
...                  dtype=np.int16))
...     y.dtype
dtype('int16')

function hbdecim_q15_execute

Decimate a block of interleaved IQ int16 samples by 2. Input must be interleaved int16_t IQ pairs (I₀ Q₀ I₁ Q₁ …); pass a 1-D array of 2*n_complex elements. Each pair of complex input samples produces one complex output sample, so an array of length 2N yields at most N output pairs (2N int16 output values). If n_in is odd the trailing IQ pair is buffered and consumed on the next call.

size_t hbdecim_q15_execute (
    hbdecim_q15_state_t * r,
    const int16_t * in,
    size_t n_in,
    int16_t * out,
    size_t max_out
) 

Parameters:

  • r Decimator state.
  • in Interleaved int16_t IQ input array of 2*n_in elements (I₀ Q₀ I₁ Q₁ …).
  • n_in Number of complex input pairs (half the int16 element count).
  • out Output buffer; caller must provide space for max_out int16_t values.
  • max_out Capacity of out in int16_t elements (>= n_in).

Returns:

Number of int16_t values written to out.

>>> import numpy as np
>>> from doppler.filter import HBDecimQ15
>>> h = np.array([0.25, 0.5, 0.25], dtype=np.float32)
>>> dec = HBDecimQ15(h)
>>> x = np.array([1000, 0, 1000, 0, 1000, 0, 1000, 0], dtype=np.int16)
>>> y = dec.execute(x)
>>> y.dtype
dtype('int16')
>>> y.shape
(4,)
>>> y.tolist()
[0, 0, 625, 0]


function hbdecim_q15_execute_max_out

Maximum output samples for a given input length.

size_t hbdecim_q15_execute_max_out (
    hbdecim_q15_state_t * r
) 

Returns 0 to trigger the lazy-alloc path in the Python glue: the output buffer is sized to n_in on first call (always sufficient for 2:1).


function hbdecim_q15_get_num_taps

FIR branch length as supplied to the constructor. This is the count of non-zero symmetric taps in the FIR branch, not the full sparse halfband prototype length. Useful for introspection when chaining multiple stages with programmatically computed filter banks.

size_t hbdecim_q15_get_num_taps (
    const hbdecim_q15_state_t * r
) 

>>> import numpy as np
>>> from doppler.filter import HBDecimQ15
>>> h = np.array([0.25, 0.5, 0.25], dtype=np.float32)
>>> HBDecimQ15(h).num_taps
3

function hbdecim_q15_get_rate

The sample-rate reduction factor; always 0.5 for 2:1 decimation. Exposed as a read-only property so pipelines can query the rate of each stage programmatically without hard-coding the 2:1 assumption.

double hbdecim_q15_get_rate (
    const hbdecim_q15_state_t * r
) 

>>> import numpy as np
>>> from doppler.filter import HBDecimQ15
>>> h = np.array([0.25, 0.5, 0.25], dtype=np.float32)
>>> HBDecimQ15(h).rate
0.5

function hbdecim_q15_get_state

void hbdecim_q15_get_state (
    const hbdecim_q15_state_t * state,
    void * blob
) 

function hbdecim_q15_reset

Zero all delay rings and clear the pending-sample flag. After a reset the decimator behaves identically to a freshly constructed instance: the four dual-write delay rings are zeroed and has_pending is cleared, so no partial IQ pair carries over. Call this between unrelated signal segments to prevent inter-segment leakage.

void hbdecim_q15_reset (
    hbdecim_q15_state_t * r
) 

>>> import numpy as np
>>> from doppler.filter import HBDecimQ15
>>> h = np.array([0.25, 0.5, 0.25], dtype=np.float32)
>>> dec = HBDecimQ15(h)
>>> x = np.array([1000, 0, 1000, 0, 1000, 0, 1000, 0], dtype=np.int16)
>>> _ = dec.execute(x)
>>> dec.reset()
>>> y = dec.execute(x)
>>> y.tolist()
[0, 0, 625, 0]

function hbdecim_q15_set_state

int hbdecim_q15_set_state (
    hbdecim_q15_state_t * state,
    const void * blob
) 

function hbdecim_q15_state_bytes

size_t hbdecim_q15_state_bytes (
    const hbdecim_q15_state_t * state
) 

Macro Definition Documentation

define HBDECIM_Q15_STATE_MAGIC

#define HBDECIM_Q15_STATE_MAGIC `DP_FOURCC ('H','B','1','5')`

define HBDECIM_Q15_STATE_VERSION

#define HBDECIM_Q15_STATE_VERSION `1u`


The documentation for this class was generated from the following file native/inc/hbdecim_q15/hbdecim_q15_core.h