FilterBuilder v0.1

As I said in other posts, I am FPGA designer in a company. My functions are, between others, implement the signal acquisition and apply different digital signal processing algorithms, to obtain a signal that the FPGA can use to control the power electronic device. My regular workflow when I have to design a new algorithm has several steps, and one of them is the design of the filter. In this step I have to design the filter using MATLAB or Python, then as I have several parametrizable filters, I only have to re-parametrize the filter and then I have to simulate it using a signal as similar as possible to the real signal, so I generate a signal or, in some cases I can obtain the real signal from an scope. When I have all the steps completed, I have spent few hours designing, modifying and specially testing the filter. Few month ago, I started to looking for an application to design and implement filters in Verilog automatically. An application that implement a filter by me with only introduce the type of the filter, the order, and the cut frequency. Internet is large, and obviously there are application that can do this, not much, but there are. Some of them does not reach my expectations, and the other ones are expensive. In short, the application I wanted must meet the next requirements:

  1. Command line application, in order to allow the integration with other tools.
  2. Allow different filters architectures (FIR and IIR), and also different architectures (Direct-Form, Transposed…)
  3. Auto generate its own test benches and execute that in the application itself.
  4. Generate documentation that could be integrated in other tools like Sphinx or Latex.
  5. Open source in order to allow the community to expand its features.

With all of these requirements, my chances to find an application are almost zero.

Few month ago, I started to integrate all my tests for Verilog using Python, and I am very happy with the results. Also, in my GitHub you can find an automatic documentation tool that parses a Verilog file and generate a Markdown file with the documentation, so last week I though … What if I develop my own filter implement application? It would not be too complicated as I am lucky to have a Python developer at home, my wife Sara Martínez. Joining her experience with Python and my experience with Verilog and DSP, we have developed FilterBuilder. A tool based on Python that allows the user to design a filter, and also implement that filter. The tool is in development process yet, but I wanted to show you the first results and start to receive some feedback. Let’s see how it works.

The tool is available in the Github repository of the blog. First of all we can take a look to the help menu see what we can do with the tool.

~/git/filter_builder$ python3 ./filter_builder_main.py --help
usage: filter_builder_main.py [-h] [--name NAME] [--order ORDER] [--fsample FSAMPLE] [--fcut FCUT [FCUT ...]]
                              [--window WINDOW] [--beta BETA] [--response RESPONSE] [--io_nbits IO_NBITS]
                              [--io_nbits_decimal IO_NBITS_DECIMAL] [--coeff_nbits COEFF_NBITS]
                              [--coeff_nbits_decimal COEFF_NBITS_DECIMAL] [--resetn] [--verbose] [--testbench]
                              [--axi_stream] [--force] [--version]

Filter builder utility.

optional arguments:
  -h, --help            show this help message and exit
  --name NAME, -n NAME  Module name. Default = filter
  --order ORDER, -o ORDER
                        Set the filter order. Default = 8
  --fsample FSAMPLE, -fs FSAMPLE
                        Set filter's sampling frequency. Default = 10000
  --fcut FCUT [FCUT ...], -fc FCUT [FCUT ...]
                        Set filter's cut frequency. It is possible to add more than one fcut ie: -fc 100 200.
                        Default = 1000
  --window WINDOW, -w WINDOW
                        FIR filter window. If the window requires no parameters, then window can be a string.
                        If the window requires parameters, then window must be a tuple with the first argument
                        the string name of the window, and the next arguments the needed parameters. Default =
                        hamming
  --beta BETA, -b BETA  Beta argument for kaiser window
  --response RESPONSE, -r RESPONSE
                        Filter type: lowpass, highpass, bandpass or bandstop. Default = lowpass
  --io_nbits IO_NBITS   Number of bits for input/output quantization. Default = 16
  --io_nbits_decimal IO_NBITS_DECIMAL
                        Number of bits for input/output quantization. Default = IO nBIts -1
  --coeff_nbits COEFF_NBITS
                        Number of bits for coefficients quantization. Default = 16
  --coeff_nbits_decimal COEFF_NBITS_DECIMAL
                        Number of bits for coefficients quantization. Default = Coefficient nBits -1
  --resetn              Active low reset in the filter module
  --verbose, -v         Enable print messages with design steps. Three different verbose levels (-v -vv -vvv)
  --testbench, -tb      Generates a testbench for the filter.
  --axi_stream, -axis   Generate AXI4 Stream interfaces for input and output
  --force, -f           Force deletion of the existing folder.
  --version             show program's version number and exit

Filterbuilder is under development, and the version released is the v0.1, so there is a number of “must” that are no available yet. This version offers full support to FIR filters, either design and implementation in Verilog. To test the tool, it can be executed without arguments and a 8th order lowpass filter, with in/out ports and coefficients with a width of 16 bits, and 15 bits for the fractional part.

~/git/filter_builder$ python3 ./filter_builder_main.py

In this case, the tool will create a folder named _output with a folder inside named filter. In this folder you will find the verilog module, and a svg file with the bode of the filter. To change the name of the module, and also the folder that the tool will create, we can use the argument –name. in this example I will generate a low pass FIR filter, with a sampling frequency of 25 ksps, a cutoff frequency of 3 kHz, and we will set the name as FIR_filter.

~/git/filter_builder$ python3 ./filter_builder_main.py --fcut 3000 --fsample 25000 --response lowpass --name FIR_filter

This command will generate a folder with the Verilog module inside named FIR_filter.

/**
	File autogenerated by FilterBuilder tool. https://www.controlpaths.com    
     
	Module name:	FIR_filter    
	Author:	Filter Builder tool.    
	Filter type:	fir
	FIR Window: 	hamming
	Filter order: 	8    
	Response:	lowpass    
	Fcut:	[0.24]    
	Input/Output width:	16 bits    
	Input/Output fractional bits:	15 bits    
	Coefficients width:	16 bits    
	Coefficients fractional bits:	15 bits
**/

module FIR_filter (
	input aclk,
	input reset,

	input ce,
	input signed [15:0] input_data, /* Input filter data */
	output signed [15:0] output_data /* Output filter data */
);

In order to change the width of the ports and the coefficients we can use the arguments coeff_nbits y io_nbits for the entire width and coeff_nbits_decimal and io_nbits_decimal for the fractional part. Also the module can be implemented using an AXI4 Stream interface. This can be done using the argument –axi_stream. In the next example I have implemented a bandpass filter with AXI4 Stream interface and the coefficients widths changed.

~/git/filter_builder$ python3 ./filter_builder_main.py --fcut 3000 5000 --fsample 25000 --response lowpass --name axis_fir -r bandpass --coeff_nbits 24 --coeff_nbits_decimal 20 --axi_stream 

In this case, the code generated is the next.

/**
	File autogenerated by FilterBuilder tool. https://www.controlpaths.com    
     
	Module name:	axis_fir    
	Author:	Filter Builder tool.    
	Filter type:	fir
	FIR Window: 	hamming
	Filter order: 	8    
	Response:	bandpass    
	Fcut:	[0.24, 0.4]    
	Input/Output width:	16 bits    
	Input/Output fractional bits:	15 bits    
	Coefficients width:	24 bits    
	Coefficients fractional bits:	20 bits
	Interface:	AXI4 Stream
**/

module axis_fir (
	input aclk,
	input reset,

	input signed [15:0] s_axis_tdata, /* Input filter data */
	input s_axis_tvalid,
	output s_axis_tready,

	output signed [15:0] m_axis_tdata, /* Output filter data */
	output reg m_axis_tvalid,
	input m_axis_tready
);

	/* Coefficients parameters */
	localparam signed coeff0 = -24'd21207;
	localparam signed coeff1 = -24'd95773;
	localparam signed coeff2 = -24'd109113;
	localparam signed coeff3 = 24'd227161;
	localparam signed coeff4 = 24'd495153;
	localparam signed coeff5 = 24'd227161;
	localparam signed coeff6 = -24'd109113;
	localparam signed coeff7 = -24'd95773;
	localparam signed coeff8 = -24'd21207;

	/* Register declaration */
	reg signed [23:0] pipe_reg_0; /* Input pipeline registers declaration */
	reg signed [23:0] pipe_reg_1; /* Input pipeline registers declaration */
	reg signed [23:0] pipe_reg_2; /* Input pipeline registers declaration */
	reg signed [23:0] pipe_reg_3; /* Input pipeline registers declaration */
	reg signed [23:0] pipe_reg_4; /* Input pipeline registers declaration */
	reg signed [23:0] pipe_reg_5; /* Input pipeline registers declaration */
	reg signed [23:0] pipe_reg_6; /* Input pipeline registers declaration */
	reg signed [23:0] pipe_reg_7; /* Input pipeline registers declaration */
	reg signed [23:0] pipe_reg_8; /* Input pipeline registers declaration */

	/* Wires declaration */
	wire signed [23:0] input_data_internal; /* Input resized to internal width */
	wire signed [23:0] output_data_internal; /* Output resized to internal width */
	wire signed [(2*24)-1:0] pipe_reg_coeff_0; /* product result of coeffs x input pipe registers */
	wire signed [(2*24)-1:0] pipe_reg_coeff_1; /* product result of coeffs x input pipe registers */
	wire signed [(2*24)-1:0] pipe_reg_coeff_2; /* product result of coeffs x input pipe registers */
	wire signed [(2*24)-1:0] pipe_reg_coeff_3; /* product result of coeffs x input pipe registers */
	wire signed [(2*24)-1:0] pipe_reg_coeff_4; /* product result of coeffs x input pipe registers */
	wire signed [(2*24)-1:0] pipe_reg_coeff_5; /* product result of coeffs x input pipe registers */
	wire signed [(2*24)-1:0] pipe_reg_coeff_6; /* product result of coeffs x input pipe registers */
	wire signed [(2*24)-1:0] pipe_reg_coeff_7; /* product result of coeffs x input pipe registers */
	wire signed [(2*24)-1:0] pipe_reg_coeff_8; /* product result of coeffs x input pipe registers */

	/* Input data resize to coefficient width */
	assign input_data_internal = {{(3){s_axis_tdata[15]}}, s_axis_tdata, 5'b0};

	/* Output tvalid management */
	always @(posedge aclk)
		if (reset)
			m_axis_tvalid <= 1'b0;
		else
			m_axis_tvalid <= s_axis_tvalid;

	/* 8 level pipeline definition */
	always@(posedge aclk)
		if (reset) begin
			pipe_reg_0 <= 24'd0;
			pipe_reg_1 <= 24'd0;
			pipe_reg_2 <= 24'd0;
			pipe_reg_3 <= 24'd0;
			pipe_reg_4 <= 24'd0;
			pipe_reg_5 <= 24'd0;
			pipe_reg_6 <= 24'd0;
			pipe_reg_7 <= 24'd0;
			pipe_reg_8 <= 24'd0;
		end
		else
			if (s_axis_tvalid) begin
				pipe_reg_0 = input_data_internal;
				pipe_reg_1 <= pipe_reg_0;
				pipe_reg_2 <= pipe_reg_1;
				pipe_reg_3 <= pipe_reg_2;
				pipe_reg_4 <= pipe_reg_3;
				pipe_reg_5 <= pipe_reg_4;
				pipe_reg_6 <= pipe_reg_5;
				pipe_reg_7 <= pipe_reg_6;
				pipe_reg_8 <= pipe_reg_7;
			end

	/* MACC Structure */
	assign pipe_reg_coeff_0 = pipe_reg_0 * coeff0;
	assign pipe_reg_coeff_1 = (pipe_reg_1 * coeff1) + pipe_reg_coeff_0;
	assign pipe_reg_coeff_2 = (pipe_reg_2 * coeff2) + pipe_reg_coeff_1;
	assign pipe_reg_coeff_3 = (pipe_reg_3 * coeff3) + pipe_reg_coeff_2;
	assign pipe_reg_coeff_4 = (pipe_reg_4 * coeff4) + pipe_reg_coeff_3;
	assign pipe_reg_coeff_5 = (pipe_reg_5 * coeff5) + pipe_reg_coeff_4;
	assign pipe_reg_coeff_6 = (pipe_reg_6 * coeff6) + pipe_reg_coeff_5;
	assign pipe_reg_coeff_7 = (pipe_reg_7 * coeff7) + pipe_reg_coeff_6;
	assign pipe_reg_coeff_8 = (pipe_reg_8 * coeff8) + pipe_reg_coeff_7;

	assign output_data_internal = pipe_reg_coeff_8 >>> 20;
	assign m_axis_tdata = output_data_internal >>> 5;

endmodule

I have tried to keep the code clean and easy to understand in order to allow the user make the changes that they need.

Filterbuilder is far to be a finished tool, but I am very exited with its potential since it has all I need, and I think that my needs are the same of any other FPGA designer could have. For now the tool only allow to design and implement FIR filters, but you can design a low pass, high pass, band pass and band stop filters, of any order and any signals widths with only one command. In this version you have to be careful with the widths you choose, because it does not consider the overflows when the filter is quantized. For the most applications, you can select one or two bits for the integer part and it will be enough, but for other applications you will need to test the filter. The tool not allow yet the auto generation of the test bench, but I am working of this to have it ready at short time. Enjoy the tool!

2 Comments

  1. Hi,
    Have you tried pyfda?
    https://github.com/chipmuenk/pyfda

    Do you have any observation on it?

    Thanks
    Mark

    • hi Mark, I haven’t tried it but some users have told me that is a interesting tool. I will check it and see if using pyfda can I develop a filter without the user interface. Thanks for your recommendation 🙂

Leave a Reply to pabloCancel Reply