top of page
Search
  • Writer's pictureDinesh Thogulua

IIR overflow analysis app

When implementing an FIR filter we need to worry about how finite word length effects. But we need not worry about its stability as an FIR filter, by design, is always stable. We also need not worry about overflows as it is possible to theoretically calculate the biggest value the output of an FIR can assume for a given set of coefficients and the bit resolution of the input. However for an IIR filter, stability and overflow are both problems: Quantizing coefficients could make an IIR unstable especially if is has poles close to the unit circle like in resonators. It is also very difficult to find out how big the output of an IIR can get, or how quantisation errors of coefficients behave at the output - All these are due to the fact that IIR uses feedback.


There is abundant literature on how to check IIR stability after coefficient quantization and different input scaling methods that could prevent or lessen the chance of, an overflow. I am not going to repeat them in this post. However, if you read through existing literature, you will realise that the best way to prevent an overflow (using sum of absolute values of the impulse response) is often impossible (when there is no closed form) or too conservative, in that, you will end up losing quite a bit of resolution if you used it, while overflow situations it tries to prevent are very rare. Besides that, sometimes you are designing a generic IIR hardware (say, a series of biquads) whose coefficients can be programmed. In that case, it isn't possible to calculate a fixed scalar anyway. An alternative is to actually simulate potential filter candidates* with potential inputs* and try different scaling values - You could start with the most conservative and then get more and more adventurous until an actual overflow occurs - You can then use the midpoint as the actual scalar.


I recently had some time on my hands and decided to write an app to do the aforementioned simulation. I want to present to you in this post. As usual, the code is free and is available here.


How to use the App

Download the code from github and run the main.py file with python3 main.py command. It will print out a print like Dash is running on http://127.0.0.1:8050/. Open that link (the port could be different in your computer) and you will see the homepage of the app as shown below.



The code uses a butterworth low pass filter to demonstrate the functionalities in this app. The impulse response of this filter will appear in the top right graph right away when you open the app. You could change the following lines near the very top of main.py to your own filter that you want to play with using the app.

# Design filter 
sos = signal.butter(btype='lowpass', N=order, Wn=cutoff_freq/np.pi, output='sos')
zpk = signal.butter(btype='lowpass', N=order, Wn=cutoff_freq/np.pi, output='zpk')   

At the left top of the app, you can enter the audio file (only wav format allowed) that you want to pass as input to the filter. Unfortunately the Dash library I am using doesn't allow opening of a file dialog box. So you have to type in the filename in its entirely. But as a consolation, when you type in the path, every time you type in a backslash, the app will check if the path entered so far is valid and display a check mark (or a cross mark if it isn't valid). This is shown below



Once you enter the entire filename, make sure you hit the enter key - This time, if the file is valid, the green check mark would appear again. You can now start playing with the input parameters below the filename input.


You can change the bit-width of filter coefficients, the input signal after scaling is applied, the resolution of the multiplier (useful when you are planning a HW implementation) and the scale value itself. For the scale value, you get two choices: 1. Use sum of absolute of impulse response samples or, 2. Use a custom scale value. If you select the first choice, you can use the slider you see on the right side graph to select how many samples of the impulse response you want to use in the scale value calculation. If you choose the custom scale value, you need to enter the scale value in the text box provided for it.


Once you are happy with the choice of parameters, hit the Run button. The python code will then put the wav file you have given through two filters: A fixed point filter using the parameters you have chosen, and a full resolution floating point filter that will create a baseline against which to check our fixed point filter's performance. The code achieves this processing using a piecemeal method - It processes 1 second worth of audio data at once and then moves to the next second of audio data and so on. Unfortunately the Dash library doesn't give me an easy way to show the progress. So, for now, you have to head to the python command line (where you ran the code) to see the progress bar. Or you can just sit tight and wait until the "Updating..." text on the browser tab disappears and you see a graph in the bottom graph area in the app as shown below:


The bottom graph shows relative error: Basically I compare, sample by sample, the output of the floating point filter (y[n]) and the fixed point filter (y_fp[n]), and normalise each comparison by the output of the floating point filter, i.e, (y[n] - y_fp[n])/y[n]. Then for a given one second chunk of audio data, I take the mean value of the relative errors for all samples. I do this for every 1 second chunk of audio and eventually graph the distribution of the means. One nuance to note is that it is normal to find end of audio files, or their very beginning, may contain silent or near silent seconds where the samples are very small. In these cases, you will see a large relative error. For example, if the floating point filter output is 0.00001 and the fixed point filter output is 0.0003, the relative error is 2! But in terms of audibility, this error is acceptable. I have added a threshold of 10^-5 for the floating point filter's output samples - I ignore samples below this value while calculating relative error. You may want to change this threshold to your liking.


To check if an overflow occurred during the processing for the choice of scaling value, you need to head over to the command line again, where, after the processing of the file is over, where you will see a "Saturation array" displayed. Every element of that array corresponds to the number of times the fixed point code had to saturate the output because an overflow occurred. So if the array is all zeroes, then it means no overflow occurred at all. Oh BTW, if overflow did occur, it will of course be reflected in the form of relative error and you will see it in the graph


Overview of the code

This app was written to make it easy for DSP engineers to test their own filter designs. It is highly likely that you have to change the code - at the least change the filter to your filter of interest, or go deeper, like changing the rounding method used in the fixed point implementation of the filter or even improve the code to run not one wav file, but several wav files stored in a database. So this is more of an app starter than an app. My intention is to save you considerable amount of time by building all the necessary basics including the GUI, fixed point implementation etc.


The code has a ton of comments in it as is usually my style. With the help of this post and those comments, you should be able to understand the code fully. I just want to point out that the actual core functionality of passing data to the filters is situated at the end of the file in def run(n_clicks): function. It uses some utility functions above it. And everything above those utility functions are merely code required to run the Dash html UI with the exception of the code at the very top just below library imports - This part of the code is where I design the filters as I mentioned in the last sub section above.


I have used globals to communicate values between various callbacks. I hate globals. And Dash apps are supposed to run on servers where multiple users may be interacting with it simultaneously. So globals will be a complete disaster in an actual deployment. But the reason why I have used them in the code is that I couldn't quickly figure out an easy alternative within the Dash framework and I just didn't have the time to look deeper. I apologise for it. If you can figure out the alternative way, please let me know.


Please note that isn't exactly of production quality. I have done some testing, but they are by no means comprehensive. So if you find any errors, please do let me know. Have fun playing with the app. I hope you like it.


* Even if you are designing a generic hardware, usually you will have some idea about what kind of filters are going to be implemented in it - an LPF or a notch filter etc. and also what kind of inputs to expect - audio or an OFDM signal of a certain bandwidth etc.


29 views0 comments

Recent Posts

See All

Works

Comments


bottom of page