├── .gitignore ├── logger ├── __pycache__ │ ├── log.cpython-38.pyc │ └── log_resouce.cpython-38.pyc └── log_resouce.py ├── filter_design ├── __pycache__ │ └── filter_design.cpython-38.pyc └── filter_design.py ├── code_generator ├── __pycache__ │ └── v_code_generator.cpython-38.pyc └── v_code_generator.py ├── README.md └── filter_builder_main.py /.gitignore: -------------------------------------------------------------------------------- 1 | _output/* -------------------------------------------------------------------------------- /logger/__pycache__/log.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/controlpaths/filterbuilder/HEAD/logger/__pycache__/log.cpython-38.pyc -------------------------------------------------------------------------------- /logger/__pycache__/log_resouce.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/controlpaths/filterbuilder/HEAD/logger/__pycache__/log_resouce.cpython-38.pyc -------------------------------------------------------------------------------- /filter_design/__pycache__/filter_design.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/controlpaths/filterbuilder/HEAD/filter_design/__pycache__/filter_design.cpython-38.pyc -------------------------------------------------------------------------------- /code_generator/__pycache__/v_code_generator.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/controlpaths/filterbuilder/HEAD/code_generator/__pycache__/v_code_generator.cpython-38.pyc -------------------------------------------------------------------------------- /logger/log_resouce.py: -------------------------------------------------------------------------------- 1 | 2 | def log (text, severity, verbose): 3 | if severity == 'info' and verbose >= 2: 4 | print("[INFO] " + text) 5 | 6 | if severity == "warning" and verbose >= 1: 7 | print("[WARNING] " + text) 8 | 9 | if severity == "error": 10 | print("[ERROR] " + text) 11 | exit() -------------------------------------------------------------------------------- /filter_design/filter_design.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy import signal 3 | import matplotlib.pyplot as plt 4 | 5 | from logger.log_resouce import log 6 | 7 | # FIR design function according arguments 8 | def fir_design (wc, response, order, conf_window, conf_beta, verbose): 9 | 10 | log(f'Designing {order}th orderFIR filter', 'info', verbose) 11 | 12 | if conf_window == 'kaiser': 13 | taps = signal.firwin(order+1,tuple(wc),window = (conf_window, conf_beta), pass_zero = response) 14 | else: 15 | taps = signal.firwin(order+1,tuple(wc),window = conf_window, pass_zero = response) 16 | 17 | log(f'Coefficients: {"".join(str(taps))}', 'info', verbose) 18 | 19 | return taps 20 | 21 | def fir_images (output_path, taps, module_name, order, wc, verbose): 22 | 23 | # bode magnitude 24 | w1,h1 = signal.freqz(taps) 25 | plt.plot(w1/np.pi, 20 * np.log10(abs(h1)), 'k') 26 | plt.title(f'{order}th FIR, wc = {wc}') 27 | plt.grid() 28 | plt.savefig(f'{output_path}/{module_name}_mag_bode.svg') 29 | 30 | # Quantize coefficients according arguments 31 | def quantize_coeffs (coeffs, bits, verbose): 32 | 33 | log("Quantifying coefficients...", 'info', verbose) 34 | 35 | coeffs_quantized = [] 36 | for coeff in coeffs: 37 | if np.floor(coeff * 2**bits) == 0: 38 | log(f'Coefficient {coeff} is zero after quantification', 'warning', verbose) 39 | 40 | coeffs_quantized.append(int(np.floor(coeff * 2**bits))) 41 | 42 | log(f'Coefficients quantized: {"".join(str(coeffs_quantized))}', 'info', verbose) 43 | 44 | return coeffs_quantized -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # filterbuilder tool 2 | 3 | Filterbuilder is a tool that allow the designer design different kind of digital filter. Filterbuilder designs and also implement the filter in verilog. 4 | 5 | ## Usage 6 | 7 | In order to use the tool, you must call the filter_builder_main.py script with the argument --help. 8 | 9 | ``` 10 | filter_builder$ python3 filter_builder_main.py --help 11 | 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] [--sim] [--axi_stream] [--force] [--version] 12 | 13 | Filter builder utility. 14 | 15 | optional arguments: 16 | -h, --help show this help message and exit 17 | --name NAME, -n NAME Module name. Default = filter 18 | --order ORDER, -o ORDER 19 | Set the filter order. Default = 8 20 | --fsample FSAMPLE, -fs FSAMPLE 21 | Set filter's sampling frequency. Default = 10000 22 | --fcut FCUT [FCUT ...], -fc FCUT [FCUT ...] 23 | Set filter's cut frequency. It is possible to add more than one fcut ie: -fc 100 200. Default = 1000 24 | --window WINDOW, -w WINDOW 25 | 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 26 | string name of the window, and the next arguments the needed parameters. Default = hamming 27 | --beta BETA, -b BETA Beta argument for kaiser window 28 | --response RESPONSE, -r RESPONSE 29 | Filter type: lowpass, highpass, bandpass or bandstop. Default = lowpass 30 | --io_nbits IO_NBITS Number of bits for input/output quantization. Default = 16 31 | --io_nbits_decimal IO_NBITS_DECIMAL 32 | Number of bits for input/output quantization. Default = IO nBIts -1 33 | --coeff_nbits COEFF_NBITS 34 | Number of bits for coefficients quantization. Default = 16 35 | --coeff_nbits_decimal COEFF_NBITS_DECIMAL 36 | Number of bits for coefficients quantization. Default = Coefficient nBits -1 37 | --resetn Number of bits for coefficients quantization. Default = Coefficient nBits -1 38 | --verbose, -v Enable print messages with design steps. Three different verbose levels (-v -vv -vvv) 39 | --testbench, -tb Generates a testbench for the filter. 40 | --sim, -s Generates a testbench for the filter and execute it using Icarus verilog. Needs Icarus Verilog installed and added to the PATH. Simulation results are stored in an vcd file 41 | --axi_stream, -axis Generate AXI4 Stream interfaces for input and output 42 | --force, -f Force deletion of the existing folder. 43 | --version show program's version number and exit 44 | 45 | ``` 46 | Maintainers: 47 | 48 | **Pablo Trujillo**. FPGA designer (https://www.controlpaths.com) 49 | **Sara Martínez**. Python developer and QA analyst. 50 | -------------------------------------------------------------------------------- /filter_builder_main.py: -------------------------------------------------------------------------------- 1 | from genericpath import exists 2 | from matplotlib.pyplot import plot as plt 3 | 4 | import os 5 | import shutil 6 | import argparse 7 | 8 | from logger.log_resouce import * 9 | from code_generator.v_code_generator import * 10 | from filter_design.filter_design import * 11 | 12 | def filter_builder_args(parser): 13 | parser.add_argument("--name", "-n", type = str, default = "filter", help="Module name. Default = filter") 14 | parser.add_argument("--order", "-o", type = int, default = 8, help="Set the filter order. Default = 8") 15 | parser.add_argument("--fsample", "-fs", type = int, default = 10000, help="Set filter's sampling frequency. Default = 10000") 16 | parser.add_argument("--fcut", "-fc", type = float, nargs="+", default = [1000], help="Set filter's cut frequency. It is possible to add more than one fcut ie: -fc 100 200. Default = 1000") 17 | parser.add_argument("--window", "-w", type = str, default = "hamming", help="FIR filter window. If the window requires no parameters, then window can be a string.\ 18 | 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") 19 | parser.add_argument("--beta", "-b", type = float, default = '0.1', help="Beta argument for kaiser window") 20 | parser.add_argument("--response", "-r", type = str, default = 'lowpass', help="Filter type: lowpass, highpass, bandpass or bandstop. Default = lowpass") 21 | 22 | parser.add_argument("--io_nbits", type = int, default = 16, help="Number of bits for input/output quantization. Default = 16") 23 | parser.add_argument("--io_nbits_decimal", type = int, help="Number of bits for input/output quantization. Default = IO nBIts -1") 24 | parser.add_argument("--coeff_nbits", type = int, default = 16, help="Number of bits for coefficients quantization. Default = 16") 25 | parser.add_argument("--coeff_nbits_decimal", type = int, help="Number of bits for coefficients quantization. Default = Coefficient nBits -1") 26 | parser.add_argument("--resetn", action = "store_true", default = False, help="Active low reset in the filter module") 27 | parser.add_argument("--verbose", "-v", action = "count", default = 0, help="Enable print messages with design steps. Three different verbose levels (-v -vv -vvv)") 28 | parser.add_argument("--testbench", "-tb", action = "store_true", default = False, help="Generates a testbench for the filter.") 29 | # parser.add_argument("--sim", "-s", action = "store_true", default = False, help="Generates a testbench for the filter and execute it using Icarus verilog. \ 30 | # Needs Icarus Verilog installed and added to the PATH. Simulation results \ 31 | # are stored in an vcd file") 32 | parser.add_argument("--axi_stream", "-axis", action = "store_true", default = False, help="Generate AXI4 Stream interfaces for input and output") 33 | parser.add_argument("--force", "-f", action = "store_true", help="Force deletion of the existing folder.") 34 | parser.add_argument('--version', action='version', version='v0.1 (April 2022)') 35 | 36 | def filter_builder_main(args): 37 | # create filter file path 38 | output_module_path = f'./_output/{args.name}' 39 | log(f'Creating "{args.name}.v" file into "{output_module_path}"', 'info', args.verbose) 40 | 41 | if not(os.path.exists(output_module_path)): 42 | os.makedirs(output_module_path) 43 | 44 | filepath = f'{output_module_path}/{args.name}.v' 45 | 46 | # check arguments 47 | io_nbits_decimal = (args.io_nbits - 1) if not(args.io_nbits_decimal) else args.io_nbits_decimal 48 | if not(args.io_nbits_decimal): 49 | log(f'IN/OUT fractional bits are not defined. Defined in {args.io_nbits - 1} bits.', 'warning', args.verbose) 50 | 51 | coeff_nbits_decimal = (args.coeff_nbits - 1) if not(args.coeff_nbits_decimal) else args.coeff_nbits_decimal 52 | if not(args.coeff_nbits_decimal): 53 | log(f'Coefficients fractional bits are not defined. Defined in {args.io_nbits - 1} bits.', 'warning', args.verbose) 54 | 55 | wc = [] 56 | for cutoff in args.fcut: 57 | if cutoff > args.fsample/2: 58 | log(f'Cut frequency {args.fcut} is greater than the Nyquist frequency {args.fsample/2}. Filter cannot be designed.', 'error', args.verbose) 59 | wc.append(cutoff / (args.fsample/2)) 60 | 61 | coeffs = fir_design(wc, args.response, args.order, args.window, args.beta, args.verbose) 62 | 63 | # documentation 64 | fir_images(output_module_path, coeffs, args.name, args.order, wc, args.verbose) 65 | 66 | quantized_coeffs = quantize_coeffs(coeffs, coeff_nbits_decimal, args.verbose) 67 | 68 | write_verilog_fir_code(filepath, args.name,'fir', args.response, args.order, wc, args.window, args.beta, args.coeff_nbits, coeff_nbits_decimal, args.io_nbits, io_nbits_decimal, quantized_coeffs, args.resetn, args.axi_stream, args.verbose) 69 | 70 | if __name__ == "__main__": 71 | parser = argparse.ArgumentParser(description="Filter builder utility.") 72 | filter_builder_args(parser) 73 | args = parser.parse_args() 74 | filter_builder_main(args) -------------------------------------------------------------------------------- /code_generator/v_code_generator.py: -------------------------------------------------------------------------------- 1 | from logger.log_resouce import * 2 | 3 | # Write header of verilog file 4 | def write_file_header (module_name, filter_type, filter_response, order, wc, window, beta, coeffs_bits, coeffs_decimal_bits, inout_bits, inout_decimal_bits, axi_stream, verbose): 5 | 6 | log("Writing header.", 'info', verbose) 7 | 8 | header_text = f'/**\n\tFile autogenerated by FilterBuilder tool. https://www.controlpaths.com\ 9 | \n \ 10 | \n\tModule name:\t{module_name}\ 11 | \n\tAuthor:\tFilter Builder tool.\ 12 | \n\tFilter type:\t{filter_type}' 13 | 14 | if filter_type == 'fir': 15 | header_text += f'\n\tFIR Window: \t{window}' 16 | 17 | if filter_type == 'fir' and window == 'kaiser': 18 | header_text += f'\n\tKaiser beta: \t{beta}' 19 | 20 | header_text += f'\n\tFilter order: \t{order}\ 21 | \n\tResponse:\t{filter_response}\ 22 | \n\tFcut:\t{wc}\ 23 | \n\tInput/Output width:\t{inout_bits} bits\ 24 | \n\tInput/Output fractional bits:\t{inout_decimal_bits} bits\ 25 | \n\tCoefficients width:\t{coeffs_bits} bits\ 26 | \n\tCoefficients fractional bits:\t{coeffs_decimal_bits} bits\n' 27 | 28 | if axi_stream: 29 | header_text += '\tInterface:\tAXI4 Stream\n' 30 | 31 | header_text += '**/\n\n' 32 | 33 | return header_text 34 | 35 | # write module and port declaration 36 | def write_port_module (module_name, text_reset, inout_bits, axi_stream, verbose): 37 | log("Writing module declaration.", 'info', verbose) 38 | 39 | port_text = f'module {module_name} (\n\tinput aclk,\n\tinput {text_reset},' 40 | 41 | if not(axi_stream): 42 | port_text += f'\n\n\tinput ce,\n\tinput signed [{inout_bits-1}:0] input_data, /* Input filter data */\n\toutput signed [{inout_bits-1}:0] output_data /* Output filter data */\n);' 43 | else: 44 | port_text += f'\n\n\tinput signed [{inout_bits-1}:0] s_axis_tdata, /* Input filter data */\n\tinput s_axis_tvalid,\n\toutput s_axis_tready,\n\n\toutput signed [{inout_bits-1}:0] m_axis_tdata, /* Output filter data */\n\toutput reg m_axis_tvalid,\n\tinput m_axis_tready\n);' 45 | 46 | return port_text 47 | 48 | # write coefficient in local parameters 49 | def write_coefficient_parameters (coeffs_bits, quantized_coeffs, verbose): 50 | log("Writing coefficients.", 'info', verbose) 51 | 52 | # coefficients declared as parameters 53 | coeffs_text = "\n\n\t/* Coefficients parameters */\n" 54 | coeff_num = 0 55 | for param_coeff in quantized_coeffs: 56 | if param_coeff >= 0: 57 | coeffs_text = f'{coeffs_text}\tlocalparam signed coeff{coeff_num} = {coeffs_bits}\'d{param_coeff};\n' 58 | else: 59 | coeffs_text = f'{coeffs_text}\tlocalparam signed coeff{coeff_num} = -{coeffs_bits}\'d{-param_coeff};\n' 60 | coeff_num += 1 61 | 62 | return coeffs_text 63 | 64 | # write wires and regs of the module 65 | def write_wires_regs (order, coeffs_bits, verbose): 66 | log("Writing wires and registers declaration.", 'info', verbose) 67 | 68 | # pipeline regs declaration 69 | wire_and_reg_text = f'\n\t/* Register declaration */' 70 | 71 | for current_index in range (0,order+1): 72 | wire_and_reg_text += f'\n\treg signed [{coeffs_bits-1}:0] pipe_reg_{current_index}; /* Input pipeline registers declaration */' 73 | 74 | # input and output with internal widths wire declaration 75 | wire_and_reg_text += f'\n\n\t/* Wires declaration */\n\twire signed [{coeffs_bits-1}:0] input_data_internal; /* Input resized to internal width */\n\twire signed [{coeffs_bits-1}:0] output_data_internal; /* Output resized to internal width */\n' 76 | 77 | for current_index in range (0,order+1): 78 | wire_and_reg_text += f'\twire signed [(2*{coeffs_bits})-1:0] pipe_reg_coeff_{current_index}; /* product result of coeffs x input pipe registers */\n' 79 | 80 | return wire_and_reg_text 81 | 82 | # write input resize 83 | def write_input_resize (input_data_text, inout_bits, inout_decimal_bits, coeffs_bits, coeffs_decimal_bits, verbose): 84 | log("Writing input resizing.", 'info', verbose) 85 | 86 | coeffs_integer_bits = coeffs_bits - coeffs_decimal_bits 87 | inout_integer_bits = inout_bits - inout_decimal_bits 88 | 89 | difference_integer_bits = coeffs_integer_bits - inout_integer_bits 90 | 91 | difference_fractional_bits = coeffs_decimal_bits - inout_decimal_bits 92 | 93 | if difference_integer_bits < 0: 94 | log(f'Number of integer bits of the input/output are greater than integer bits of the coefficient, this result in a negative width. Filter cannot be implemented.', 'error', verbose) 95 | 96 | if difference_fractional_bits < 0: 97 | log(f'Number of fractional bits of the input/output are greater than fractional bits of the coefficient, this result in a negative width. Filter cannot be implemented.', 'error', verbose) 98 | 99 | resize_text = f'\n\t/* Input data resize to coefficient width */\n\tassign input_data_internal = {{' 100 | if difference_integer_bits > 0: 101 | resize_text += f'{{({difference_integer_bits}){{{input_data_text}[{inout_bits-1}]}}}}, ' 102 | 103 | resize_text += input_data_text 104 | 105 | if difference_fractional_bits > 0: 106 | resize_text += f', {difference_fractional_bits}\'b0' 107 | 108 | resize_text += '};\n' 109 | 110 | return resize_text 111 | 112 | # write the register pipeline 113 | def write_pipeline (order, coeffs_bits, ce_text, edge_reset, verbose): 114 | log("Writing pipeline.", 'info', verbose) 115 | 116 | pipeline_text = f'\n\t/* {order} level pipeline definition */\n\talways@(posedge aclk)\n\t\tif ({edge_reset}) begin' 117 | 118 | for pipe_level in range (0, order+1): 119 | pipeline_text += f'\n\t\t\tpipe_reg_{pipe_level} <= {coeffs_bits}\'d0;' 120 | 121 | pipeline_text += f'\n\t\tend\n\t\telse\n\t\t\tif ({ce_text}) begin\n\t\t\t\tpipe_reg_0 = input_data_internal;' 122 | 123 | for pipe_level in range (1, order+1): 124 | pipeline_text += f'\n\t\t\t\tpipe_reg_{pipe_level} <= pipe_reg_{pipe_level-1};' 125 | 126 | pipeline_text += '\n\t\t\tend\n' 127 | 128 | return pipeline_text 129 | 130 | # write data valid management 131 | def write_management_output_ce (reset_text, axi_stream, verbose): 132 | 133 | if axi_stream: 134 | log('Creating output data valid management', 'info', verbose) 135 | 136 | managements_output_ce_text = f'\n\t/* Output tvalid management */\n\talways @(posedge aclk)\n\t\tif ({reset_text})\n\t\t\tm_axis_tvalid <= 1\'b0;\n\t\telse\n\t\t\tm_axis_tvalid <= s_axis_tvalid;\n' 137 | else: 138 | managements_output_ce_text = '' 139 | 140 | return managements_output_ce_text 141 | 142 | # write MACC structure 143 | def write_macc_structure (order, verbose): 144 | log("Writing combinational MACC structure.", 'info', verbose) 145 | 146 | macc_text = '\n\t/* MACC Structure */' 147 | 148 | macc_text += f'\n\tassign pipe_reg_coeff_0 = pipe_reg_0 * coeff0;' 149 | 150 | for current_level in range (1, order+1): 151 | macc_text += f'\n\tassign pipe_reg_coeff_{current_level} = (pipe_reg_{current_level} * coeff{current_level}) + pipe_reg_coeff_{current_level-1};' 152 | 153 | return macc_text 154 | 155 | # write output resize 156 | def write_output_resize (order, coeffs_decimal_bits, inout_decimal_bits, output_data_text, verbose): 157 | log("Writing output resizing.", 'info', verbose) 158 | 159 | output_resize = f'\n\n\tassign output_data_internal = pipe_reg_coeff_{order} >>> {coeffs_decimal_bits};' 160 | 161 | output_resize += f'\n\tassign {output_data_text} = output_data_internal >>> {coeffs_decimal_bits - inout_decimal_bits};' 162 | 163 | return output_resize 164 | 165 | # verilog fir file creation 166 | def write_verilog_fir_code(file_path, module_name, filter_type, filter_response, order, wc, window, beta, coeffs_bits, coeffs_decimal_bits, inout_bits, inout_decimal_bits, quantized_coeffs, resetn, axi_stream, verbose): 167 | 168 | f = open(f'{file_path}', "w") 169 | 170 | log("Generating filter code.",'info', verbose) 171 | 172 | # reset text according reset type 173 | if resetn: 174 | text_reset = "resetn" 175 | else: 176 | text_reset = "reset" 177 | 178 | # edge reset text according reset type 179 | if resetn: 180 | edge_reset = "!resetn" 181 | else: 182 | edge_reset = "reset" 183 | 184 | # auxiliar definitions 185 | if not(axi_stream): 186 | input_data_text = 'input_data' # save this for later 187 | output_data_text = 'output_data' # save this for later 188 | ce_text = 'ce' 189 | else: 190 | input_data_text = 's_axis_tdata' # save this for later 191 | output_data_text = 'm_axis_tdata' # save this for later 192 | ce_text = 's_axis_tvalid' 193 | 194 | # header 195 | ######## 196 | header_text = write_file_header (module_name, filter_type, filter_response, order, wc, window, beta, coeffs_bits, coeffs_decimal_bits, inout_bits, inout_decimal_bits, axi_stream, verbose) 197 | 198 | # module and port text 199 | ####################### 200 | port_text = write_port_module (module_name, text_reset, inout_bits, axi_stream, verbose) 201 | 202 | # Coefficient parameters 203 | ######################## 204 | coeffs_text = write_coefficient_parameters (coeffs_bits, quantized_coeffs, verbose) 205 | 206 | # output ce management 207 | ###################### 208 | output_ce_text = write_management_output_ce (edge_reset, axi_stream, verbose) 209 | 210 | # pipeline text 211 | ############### 212 | pipeline_text = write_pipeline (order, coeffs_bits, ce_text, edge_reset, verbose) 213 | 214 | # wires and regs 215 | ################ 216 | wire_and_reg_text = write_wires_regs (order, coeffs_bits, verbose) 217 | 218 | # input resize 219 | ############## 220 | resize_text = write_input_resize (input_data_text, inout_bits, inout_decimal_bits, coeffs_bits, coeffs_decimal_bits, verbose) 221 | 222 | # macc text 223 | ########### 224 | macc_text = write_macc_structure (order, verbose) 225 | 226 | # output resize 227 | ############### 228 | output_resize_text = write_output_resize (order, coeffs_decimal_bits, inout_decimal_bits, output_data_text, verbose) 229 | 230 | end_text = '\n\nendmodule' 231 | 232 | log("File creation complete.", 'info', verbose) 233 | 234 | f.write(header_text + port_text + coeffs_text + wire_and_reg_text + resize_text + output_ce_text + pipeline_text + macc_text + output_resize_text + end_text) 235 | f.close() --------------------------------------------------------------------------------