ALU - FIR FILTER
Configurable FIR (Finite Impulse Response) filter implemented via Vivado HLS. Supports up to 64 taps with user-defined coefficients entered as comma-separated values. Full fixed-point configuration for input, output, and coefficients with independent integer and fractional bit widths.
Introduction
The FIR Filter block implements a general-purpose finite impulse response filter:
$$ y[n] = \sum_{k=0}^{N-1} h[k] \cdot x[n-k] $$
where:
h[k]are the filter coefficients (taps)Nis the number of tapsx[n-k]are delayed input samples
Coefficients are entered directly in the properties as a comma-separated list, supporting both decimal values (e.g., 0.25, 0.5, 0.25) and integer values. The filter automatically converts decimal coefficients to fixed-point format based on the configured coefficient fractional bits.
Pin Description
Properties
Total bit width of input data (8-32 bits)
Total bit width of input data X. Range: 8-32 bits. Includes both integer and fractional parts.Default: 16
Options: 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
Number of fractional bits in input data (0-16)
Number of fractional bits in input data. Range: 0-16 bits. Integer bits = InDataBits - InFractBits.Default: 0
Options: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Total bit width of output data (8-64 bits)
Total bit width of output data Y. Range: 8-64 bits. Should be large enough to avoid overflow. Recommended: InDataBits + CoefDataBits + ceil(log2(NumTaps)).Default: 32
Options: 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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
Number of fractional bits in output data (0-32)
Number of fractional bits in output data. Range: 0-32 bits. Should match: InFractBits + CoefFractBits for no precision loss.Default: 16
Options: 0 1 2 3 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
Total bit width of coefficients (8-32 bits)
Total bit width of filter coefficients. Range: 8-32 bits. Higher values improve coefficient precision.Default: 16
Options: 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
Number of fractional bits in coefficients (0-31)
Number of fractional bits in coefficients. Range: 0-31 bits. For normalized coefficients (|h| < 1), use CoefFractBits = CoefDataBits - 1. Example: 16-bit coefficients with 15 fractional bits -> Q1.15 format.Default: 15
Options: 0 1 2 3 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
Comma-separated list of FIR coefficients (e.g., 0.25, 0.5, 0.25 or as integers: 8192, 16384, 8192)
Comma-separated list of FIR filter coefficients. Supports 1 to 64 coefficients. Values can be:
- Decimal (e.g., 0.25, 0.5, -0.125): Automatically converted to fixed-point
- Integer (e.g., 8192, 16384): Used directly as fixed-point values
The coefficients define the filter impulse response. For symmetric coefficients, the filter has linear phase.
Examples:
- Moving average (4 taps):
0.25, 0.25, 0.25, 0.25 - Simple low-pass:
0.25, 0.5, 0.25 - Differentiator:
1, -1
Default: 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125
Functional description
The FIR filter implements discrete convolution of the input signal with the impulse response defined by the coefficients:
$$ y[n] = h_0 \cdot x[n] + h_1 \cdot x[n-1] + h_2 \cdot x[n-2] + \ldots + h_{N-1} \cdot x[n-N+1] $$
where:
x[n]->X(input signal)y[n]->Y(filtered output)h[k]-> coefficients from the FIR Coefficients property
Transfer function
In the z-domain, the FIR filter has the transfer function:
$$ H(z) = \sum_{k=0}^{N-1} h[k] \cdot z^{-k} $$
FIR filters have only zeros (no poles), guaranteeing:
- Unconditional stability
- Linear phase (if coefficients are symmetric)
- Finite impulse response (output settles after N samples)
Coefficient entry
Coefficients are entered as a comma-separated list in the FIR Coefficients property:
| Input format | Example | Description |
|---|---|---|
| Decimal | 0.25, 0.5, 0.25 | Normalized coefficients |
| Integer | 8192, 16384, 8192 | Pre-scaled fixed-point values |
Decimal coefficients are automatically converted to fixed-point using:
$$ \text{fixed_value} = \text{round}(\text{decimal} \times 2^{\text{CoefFractBits}}) $$
Example with CoefFractBits = 15:
- 0.5 -> 16384
- 0.25 -> 8192
- 1.0 -> 32768
Fixed-point format
All data paths use configurable fixed-point arithmetic:
| Parameter | Format | Description |
|---|---|---|
| Input | Q(I-F).F | I = InDataBits, F = InFractBits |
| Output | Q(O-F).F | O = OutDataBits, F = OutFractBits |
| Coefficients | Q(C-F).F | C = CoefDataBits, F = CoefFractBits |
The output uses rounding (AP_RND) and saturation (AP_SAT) to prevent overflow.
Filter design examples
Low-pass filter (8 taps, moving average):
0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125
High-pass filter (3 taps, differentiator):
-0.5, 1.0, -0.5
Band-pass filter (symmetric, 7 taps):
-0.05, -0.1, 0.25, 0.6, 0.25, -0.1, -0.05
Resource usage
The filter uses:
- N delay registers (one per tap)
- N multipliers (fully parallel for II=1)
- Adder tree for accumulation
Resources scale linearly with the number of taps.
Latency
Fixed latency of 4 clock cycles (HLS pipeline) plus delay line fill time. The DV output goes high after N samples have been processed.
Typical use cases
- Low-pass filtering (noise reduction, anti-aliasing)
- High-pass filtering (DC removal, edge detection)
- Band-pass filtering (frequency selection)
- Matched filtering (pulse detection)
- Interpolation and decimation filters
- Custom frequency response shaping
Python examples for coefficient generation
The following Python examples show how to design FIR filters and generate quantized coefficients ready to paste into the FIR Coefficients property.
Low-pass filter using scipy.signal
python
import numpy as np
from scipy import signal
# Filter parameters
order = 16 # Number of taps - 1
cutoff = 0.2 # Normalized cutoff frequency (0 to 1, where 1 = Nyquist)
coef_fract_bits = 15 # Fractional bits for quantization
# Design filter using window method
coefficients = signal.firwin(order + 1, cutoff, window='hamming')
# Quantize to fixed-point
scale = 2 ** coef_fract_bits
quantized = np.round(coefficients * scale).astype(int)
# Print as comma-separated list (for SCI-Compiler)
print("Decimal coefficients:")
print(", ".join(f"{c:.6f}" for c in coefficients))
print("\nQuantized coefficients (integer):")
print(", ".join(str(c) for c in quantized))
High-pass filter
python
import numpy as np
from scipy import signal
order = 32
cutoff = 0.3 # Normalized cutoff
coef_fract_bits = 15
# High-pass: pass_zero=False
coefficients = signal.firwin(order + 1, cutoff, window='hamming', pass_zero=False)
scale = 2 ** coef_fract_bits
quantized = np.round(coefficients * scale).astype(int)
print("High-pass coefficients:")
print(", ".join(f"{c:.6f}" for c in coefficients))
Band-pass filter
python
import numpy as np
from scipy import signal
order = 64
low_cutoff = 0.1 # Lower normalized frequency
high_cutoff = 0.4 # Upper normalized frequency
coef_fract_bits = 15
# Band-pass filter
coefficients = signal.firwin(order + 1, [low_cutoff, high_cutoff],
window='hamming', pass_zero=False)
scale = 2 ** coef_fract_bits
quantized = np.round(coefficients * scale).astype(int)
print("Band-pass coefficients:")
print(", ".join(f"{c:.6f}" for c in coefficients))
Custom frequency response using firwin2
python
import numpy as np
from scipy import signal
order = 48
coef_fract_bits = 15
# Define arbitrary frequency response
# freq: normalized frequencies (0 to 1)
# gain: desired gain at each frequency
freq = [0, 0.1, 0.2, 0.3, 0.5, 1.0]
gain = [1, 1, 0.5, 0.1, 0, 0]
coefficients = signal.firwin2(order + 1, freq, gain)
scale = 2 ** coef_fract_bits
quantized = np.round(coefficients * scale).astype(int)
print("Custom response coefficients:")
print(", ".join(f"{c:.6f}" for c in coefficients))
Raised cosine filter (for communications)
python
import numpy as np
def raised_cosine(num_taps, beta, sps):
"""
Raised cosine filter for pulse shaping.
beta: roll-off factor (0 to 1)
sps: samples per symbol
"""
t = np.arange(num_taps) - (num_taps - 1) / 2
t = t / sps
coefficients = np.zeros(num_taps)
for i, ti in enumerate(t):
if ti == 0:
coefficients[i] = 1
elif abs(ti) == 1 / (2 * beta) and beta != 0:
coefficients[i] = np.pi / 4 * np.sinc(1 / (2 * beta))
else:
num = np.sinc(ti) * np.cos(np.pi * beta * ti)
den = 1 - (2 * beta * ti) ** 2
coefficients[i] = num / den
# Normalize
coefficients /= np.sum(coefficients)
return coefficients
# Parameters
num_taps = 33
beta = 0.35 # Roll-off factor
sps = 4 # Samples per symbol
coef_fract_bits = 15
coefficients = raised_cosine(num_taps, beta, sps)
scale = 2 ** coef_fract_bits
quantized = np.round(coefficients * scale).astype(int)
print("Raised cosine coefficients:")
print(", ".join(f"{c:.6f}" for c in coefficients))
Gaussian filter (for smoothing)
python
import numpy as np
from scipy.ndimage import gaussian_filter1d
def gaussian_fir(num_taps, sigma):
"""Generate Gaussian FIR filter coefficients."""
# Create impulse
impulse = np.zeros(num_taps * 10)
impulse[len(impulse) // 2] = 1
# Apply Gaussian filter to get impulse response
response = gaussian_filter1d(impulse, sigma)
# Extract center portion
center = len(response) // 2
half = num_taps // 2
coefficients = response[center - half : center + half + 1]
# Normalize
coefficients /= np.sum(coefficients)
return coefficients
num_taps = 17
sigma = 2.0 # Standard deviation in samples
coef_fract_bits = 15
coefficients = gaussian_fir(num_taps, sigma)
scale = 2 ** coef_fract_bits
quantized = np.round(coefficients * scale).astype(int)
print("Gaussian coefficients:")
print(", ".join(f"{c:.6f}" for c in coefficients))
Differentiator filter
python
import numpy as np
from scipy import signal
order = 16
coef_fract_bits = 15
# Differentiator (type III FIR)
coefficients = signal.remez(order + 1, [0.05, 0.95], [1],
type='differentiator')
# Normalize to max = 1
coefficients /= np.max(np.abs(coefficients))
scale = 2 ** coef_fract_bits
quantized = np.round(coefficients * scale).astype(int)
print("Differentiator coefficients:")
print(", ".join(f"{c:.6f}" for c in coefficients))
Hilbert transformer (90-degree phase shift)
python
import numpy as np
from scipy import signal
order = 32 # Must be even for Hilbert
coef_fract_bits = 15
coefficients = signal.remez(order + 1, [0.05, 0.95], [1],
type='hilbert')
scale = 2 ** coef_fract_bits
quantized = np.round(coefficients * scale).astype(int)
print("Hilbert transformer coefficients:")
print(", ".join(f"{c:.6f}" for c in coefficients))
Quantization analysis helper
python
import numpy as np
def analyze_quantization(coefficients, coef_fract_bits):
"""Analyze quantization error for FIR coefficients."""
scale = 2 ** coef_fract_bits
quantized = np.round(coefficients * scale).astype(int)
reconstructed = quantized / scale
# Errors
abs_error = np.abs(coefficients - reconstructed)
rel_error = abs_error / (np.abs(coefficients) + 1e-10)
print(f"Coefficient Fractional Bits: {coef_fract_bits}")
print(f"Max absolute error: {np.max(abs_error):.2e}")
print(f"Mean absolute error: {np.mean(abs_error):.2e}")
print(f"Max relative error: {np.max(rel_error) * 100:.2f}%")
print(f"Sum of original: {np.sum(coefficients):.6f}")
print(f"Sum of quantized: {np.sum(reconstructed):.6f}")
return quantized
# Example usage
from scipy import signal
coefficients = signal.firwin(17, 0.3, window='hamming')
quantized = analyze_quantization(coefficients, coef_fract_bits=15)
print("\nQuantized coefficients:")
print(", ".join(str(c) for c in quantized))