Designing IIR (Infinite Impulse Response) [filters](https://www.onzuu.com/category/filters) is powerful but fraught with subtle pitfalls that can lead to unstable, inaccurate, or completely non-functional filters. ![2020-06-08-eclypsez7-petalinux-and-signal-processing-at-the-edge_img0](https://hackmd.io/_uploads/B1q-TjFKlg.png) Here are the common pitfalls, their causes, and how to fix them. **1. Instability** The Pitfall: The filter's output grows uncontrollably and oscillates until it saturates, even with a small input. This is the most critical failure mode. **Why it Happens:** * Cause: The poles of the filter's transfer function lie outside the unit circle in the z-plane. * Design Source: During the transformation from a prototype analog filter (e.g., Butterworth, Chebyshev) to a digital filter (using methods like Bilinear Transform), the stability of the analog filter is preserved. However, if you design the filter by directly placing poles and zeros, it's easy to place a pole outside the stable region (|z| < 1). * Quantization: Even if the theoretical poles are inside the unit circle, coefficient quantization (storing the a and b coefficients with limited precision, e.g., 16-bit fixed-point) can move the poles outside the unit circle, causing instability. **How to Fix It:** 1. Theoretical Guarantee: Use proven design methods like the Bilinear Transform. A stable analog filter will always yield a stable digital IIR filter. 2. Check Pole Locations: Always calculate or plot the pole-zero plot of your filter. Ensure all poles have a magnitude (|z|) strictly less than 1. 3. Use Higher Precision: Implement the filter using floating-point arithmetic (e.g., float or double in C/C++) to minimize the impact of coefficient quantization. This is often sufficient for applications on PCs, modern [microcontrollers](https://www.ampheo.com/c/microcontrollers), and [DSPs](https://www.ampheo.com/c/dsp-digital-signal-processors). 4. Fixed-Point Mitigation: If you must use fixed-point (for speed or lack of an FPU): * Use a cascade structure (see below) which is less sensitive to quantization. * Use a high enough bit-width (e.g., 32-bit) for coefficients and internal states. * Scale coefficients and signals carefully to avoid overflow while maintaining precision. **2. Overflow and Limit Cycles** The Pitfall: The internal calculations of the filter (the sum of products) exceed the numerical range of the data type, causing wrapping (overflow) or saturation. This introduces severe distortion. Related to this are limit cycles – small, persistent oscillations at the output even when the input is zero, caused by quantization errors in the feedback loop. **Why it Happens:** * The feedback nature of IIR filters means quantization errors can recirculate indefinitely. * In fixed-point implementations, the range of values is limited (e.g., -1.0 to ~1.0 for Q15 format). Large signals or high-Q filters can easily produce intermediate sums outside this range. **How to Fix It:** 1. Scaling: Analyze the gain at each stage of your filter (especially in a cascaded design). Scale the input and the results between stages to ensure the signals remain within the valid range. A common technique is to scale by 1/sum(|impulse_response|) or similar norms. 2. Saturation Arithmetic: If overflow occurs, use saturation arithmetic instead of simple wrapping. This means clamping the value to the maximum or minimum representable value, which is less destructive than wrapping from positive to negative. 3. Use Floating-Point: The simplest solution for many applications is to use floating-point, which has a huge dynamic range, making overflow a non-issue for most audio and control signals. **3. High Sensitivity to Coefficient Quantization** The Pitfall: The filter's frequency response (especially the stopband attenuation and the precise cutoff frequency) changes drastically when the ideal coefficients are rounded to the finite precision of the hardware. **Why it Happens:** This is especially problematic for high-order filters with poles close to the unit circle (i.e., high-Q filters with sharp cutoffs). A small change in the coefficient value can move a pole significantly, changing the filter's behavior. **How to Fix It:** * Avoid Direct Form I/II: The standard Direct Form I and II implementations are highly sensitive to coefficient quantization for high-order filters. * Use Cascade (or Parallel) Form: This is the most important fix. Instead of implementing one big high-order filter, break it down into a cascade of smaller, manageable 2nd-order sections (biquads). * A 10th-order filter becomes 5x 2nd-order sections in series. * Each biquad has its own pair of poles and zeros. Quantization only affects that specific section, preventing the error from propagating and ruining the entire response. * How to do it: Design your filter and then use zp2sos (Zero-Pole to Second-Order Sections) in MATLAB/Python (scipy.signal) to convert it to biquads. **Example of a Biquad Section (Direct Form II Transposed is common):** ``` c // A single biquad section typedef struct { float b0, b1, b2, a1, a2; // Coefficients float w1, w2; // Delay elements (state) } Biquad; float processBiquad(Biquad *bq, float input) { float w0 = input - bq->a1 * bq->w1 - bq->a2 * bq->w2; float output = bq->b0 * w0 + bq->b1 * bq->w1 + bq->b2 * bq->w2; // Update state for next sample bq->w2 = bq->w1; bq->w1 = w0; return output; } // For a cascade, just pass the output of one biquad to the input of the next. ``` **4. Nonlinear Phase Response** The Pitfall: Different frequencies experience different time delays through the filter. This distorts the shape of a transient signal (e.g., a pulse or musical attack) even if the frequency content is correct. This is not a "bug" but an inherent property of IIR filters. **Why it Happens:** IIR filters are designed to be memory-efficient for a given frequency response. This efficiency comes at the cost of not having a linear phase response. **How to Fix (or mitigate) It:** 1. Use an FIR Filter: If linear phase is an absolute requirement (e.g., in audio crossovers, data analysis), use a Finite Impulse Response (FIR) filter. They can be designed to have perfectly linear phase but require more computation. 2. Accept It: For many applications (e.g., control systems, simple tone shaping), the phase nonlinearity is not a critical issue. 3. Forward-Backward Filtering (Zero-Phase): Filter the signal forward in time, then reverse the result and filter it again. This cancels the phase shift but is non-causal (requires the entire signal to be in memory) and introduces a processing delay, making it unsuitable for real-time applications. It's great for offline processing (filtfilt in MATLAB/Python). **5. Computational Precision and Round-off Error** The Pitfall: Even with a stable filter in theory, the accumulated rounding errors in the calculations can cause noise, distortion, or eventually lead to instability. **Why it Happens:** Every multiplication and addition in the recursive feedback loop can introduce a small rounding error. These errors accumulate over time. **How to Fix It:** * Use Double Precision: For the internal state variables (w0, w1, w2 in the biquad example) and calculations, use a higher precision than the input/output. For example, use double for the state even if the I/O is float. * Dithering: In advanced audio applications, adding a very small amount of dither noise can help de-correlate rounding errors, making them sound like white noise instead of distortion. **Summary: Best Practices Checklist** To avoid these pitfalls, follow this process: 1. Design: Use a proven method (Bilinear Transform) from a trusted software tool (SciPy, MATLAB, Octave). 2. Structure: Always implement high-order IIR filters as a cascade of 2nd-order biquad sections. 3. Arithmetic: Use floating-point unless you have a compelling reason (speed, cost, power) not to. If you need fixed-point, plan carefully. 4. Stability Check: Always analyze the pole locations of your final quantized coefficients. 5. Scaling: For fixed-point, rigorously analyze and apply scaling to prevent overflow. 6. Phase: Be aware of the phase response and choose IIR only if linear phase is not required.