Tunable IIR - I Order
First-order IIR filter with user-programmable coefficients. Allows runtime coefficient updates for adaptive filtering applications.
Introduction
The Tunable IIR - I Order block implements a first-order IIR filter with user-programmable coefficients operating in real time on FPGA. Unlike fixed-coefficient filters (Butterworth, Bessel, etc.), this block allows the user to provide custom filter coefficients that can be changed at runtime.
This component is ideal for adaptive filtering applications where the filter response must be adjusted dynamically based on system conditions.
Pin Description
Properties
Select input data type
Input data format selection. Available values: Unsigned 16 bit, Signed 17 bit, default Unsigned 16 bit.Default: UINT16
Options: UINT16 INT17
Usage
First-Order IIR Filter Theory
A standard first-order IIR filter is described by the difference equation:
$$ y[n] = b_0 \cdot x[n] + b_1 \cdot x[n-1] - a_1 \cdot y[n-1] $$
In Z-domain, the transfer function is:
$$ H(z) = \frac{b_0 + b_1 z^{-1}}{1 + a_1 z^{-1}} $$
The recursive dependency on $y[n-1]$ creates a feedback loop that limits the maximum clock rate on FPGA implementations.
Clustered Lookahead Transformation
To enable high-speed FPGA operation, the filter uses the Clustered Lookahead technique with a lookahead factor of 3. This transforms the original filter into an equivalent form where the feedback dependency spans 3 samples instead of 1.
Mathematical Derivation
Starting from the original first-order filter:
$$ y[n] = b_0 x[n] + b_1 x[n-1] - a_1 y[n-1] $$
We can write the next two outputs:
$$ y[n+1] = b_0 x[n+1] + b_1 x[n] - a_1 y[n] $$
$$ y[n+2] = b_0 x[n+2] + b_1 x[n+1] - a_1 y[n+1] $$
Substituting recursively to eliminate intermediate $y$ terms:
$$ y[n+1] = b_0 x[n+1] + b_1 x[n] - a_1 (b_0 x[n] + b_1 x[n-1] - a_1 y[n-1]) $$
After three recursive substitutions, we obtain the clustered lookahead form:
$$ y[n] = b’_0 x[n] + b’_1 x[n-1] + b’_2 x[n-2] + b’_3 x[n-3] - a’_3 y[n-3] $$
where the transformed coefficients are:
$$ a’_3 = a_1^3 $$
The numerator transformation uses convolution. Define intermediate coefficients:
$$ d[n] = [1, -a_1, a_1^2] $$
Then:
$$ b’[n] = d[n] * b[n] = \text{conv}([1, -a_1, a_1^2], [b_0, b_1]) $$
Resulting in:
$$ b’_0 = b_0 $$ $$ b’_1 = b_1 - a_1 b_0 $$ $$ b’_2 = a_1^2 b_0 - a_1 b_1 $$ $$ b’_3 = a_1^2 b_1 $$
Stability Analysis
The stability of the transformed filter depends on the pole location of the original filter:
Original Filter Stability
The original filter is stable if and only if:
$$ |a_1| < 1 $$
This ensures the pole lies inside the unit circle in the Z-plane.
Transformed Filter Stability
The clustered lookahead transformation preserves stability. Since:
$$ a’_3 = a_1^3 $$
If $|a_1| < 1$, then:
$$ |a’_3| = |a_1|^3 < |a_1| < 1 $$
The transformation actually improves the stability margin by cubing a coefficient that is already less than 1 in magnitude.
Important: The transformation assumes the original filter is stable. If $|a_1| \geq 1$, both the original and transformed filters will be unstable.
Fixed-Point Coefficient Format
The FPGA implementation uses 32-bit signed fixed-point coefficients with 30 fractional bits (Q2.30 format):
$$ \text{coefficient}{fixed} = \text{round}(\text{coefficient}{float} \times 2^{30}) $$
This provides:
- Range: approximately $\pm 2$
- Precision: approximately $9.3 \times 10^{-10}$
Python Reference Implementation
The following Python code calculates the transformed coefficients for any first-order IIR filter:
python
import numpy as np
from scipy import signal
# Example: Design a first-order Bessel high-pass filter
N = 1 # Order of filter
fs = 250e6 # Sampling frequency (Hz)
fc = 5e6 # Cutoff frequency (Hz)
Wn = fc / (fs / 2) # Normalized frequency
# Get original filter coefficients
b_z, a_z = signal.bessel(N, Wn, btype='high')
print("Original coefficients:")
print(f" b = {b_z}")
print(f" a = {a_z}")
# Clustered lookahead transformation (factor of 3)
# Transform denominator: a'[3] = a[1]^3
an = [1, a_z[1]**3]
# Transform numerator using convolution
# d[n] = [1, -a[1], a[1]^2]
bn = [1, -a_z[1], a_z[1]**2]
bq = np.convolve(bn, b_z)
print("\nTransformed coefficients (clustered lookahead):")
print(f" b' = {bq}")
print(f" a' = {an}")
# Verify frequency response equivalence
w_orig, h_orig = signal.freqz(b_z, a_z, worN=1024)
w_trans, h_trans = signal.freqz(bq, an, worN=1024)
print("\nFrequency response verification:")
print(f" Max magnitude difference: {np.max(np.abs(np.abs(h_orig) - np.abs(h_trans))):.2e}")
# Convert to fixed-point (Q2.30 format)
SCALE = 1 << 30
b_fixed = [int(round(c * SCALE)) for c in bq]
a_fixed = [int(round(c * SCALE)) for c in an]
print("\nFixed-point coefficients (Q2.30):")
print(f" b0 = {b_fixed[0]}")
print(f" b1 = {b_fixed[1]}")
print(f" b2 = {b_fixed[2]}")
print(f" b3 = {b_fixed[3]}")
print(f" a3 = {a_fixed[1]}")
# Stability check
print(f"\nStability analysis:")
print(f" Original pole: a1 = {a_z[1]:.6f}")
print(f" |a1| = {abs(a_z[1]):.6f} {'< 1 (STABLE)' if abs(a_z[1]) < 1 else '>= 1 (UNSTABLE)'}")
print(f" Transformed pole: a'3 = {an[1]:.6f}")
print(f" |a'3| = {abs(an[1]):.6f} {'< 1 (STABLE)' if abs(an[1]) < 1 else '>= 1 (UNSTABLE)'}")
Filter Equation Summary
The FPGA implements the following equation:
$$ y[j] = b’_0 x[j] + b’_1 x[j-1] + b’_2 x[j-2] + b’_3 x[j-3] - a’_3 y[j-3] $$
This form allows 3 clock cycles between feedback samples, enabling high-speed pipelined implementation.
Typical Applications
- Adaptive filtering with runtime coefficient updates
- Custom filter responses not available in standard blocks
- System identification and modeling
- Real-time filter tuning based on environmental conditions
- Research and prototyping of custom filter designs
Resources & Timing
- Latency: 6 clock cycles