OpenCores
URL https://opencores.org/ocsvn/viirf/viirf/trunk

Subversion Repositories viirf

[/] [viirf/] [trunk/] [src/] [config/] [IIR_Config.py] - Blame information for rev 2

Details | Compare with Previous | View Log

Line No. Rev Author Line
1 2 Muraer
"""
2
    MIT License
3
 
4
    Copyright (c) 2017 Mario Mauerer
5
 
6
    Permission is hereby granted, free of charge, to any person obtaining a copy
7
    of this software and associated documentation files (the "Software"), to deal
8
    in the Software without restriction, including without limitation the rights
9
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
    copies of the Software, and to permit persons to whom the Software is
11
    furnished to do so, subject to the following conditions:
12
 
13
    The above copyright notice and this permission notice shall be included in all
14
    copies or substantial portions of the Software.
15
 
16
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
    SOFTWARE.
23
 
24
    VIIRF - Versatile IIR Filter
25
 
26
    This stand-alone script is used to configure the cascaded IIR filter hardware.
27
    It checks the provided data for errors and generates multiple files used for configuring the filter's generics
28
    and the testbenches.It also calculates the filter's step-response (both floating-point and quantized filter)
29
 
30
    Python: 3.5.2
31
    NOTE: The behavior of e.g., integers, longs or integer divisions differs from python 2 and 3!
32
    Python 3 is preferred/required due to the infinite integer-precision provided by python 3.
33
    Do NOT use NUMPY-datatypes for filter calculations. They are limited to 64 bit and this is quickly not enough
34
    for high-precision / low-noise filters.
35
 
36
"""
37
import numpy as np
38
import math
39
import datetime
40
import os
41
import matplotlib.pyplot as plt
42
from IIR_SOS_Filt_df1 import SOS_Casc_df1_Float, SOS_Casc_df1_Quantized
43
 
44
 
45
def main():
46
    """ Implementation of the filter-configuration script.
47
    """
48
 
49
    """ USER CONFIGURATION: """
50
 
51
    """ SOS filter coefficients of direct-form 1 filter implementation
52
        Deliver as list of: [b0, b1, b2, 1, a1, a2]
53
    """
54
    """ This example-filter is an over-the-top lowpass filter to illustrate the numerical stability
55
        of the cascaded biquads.
56
        Designed with Matlab's fdatool.
57
        Chebyshev Type II, fs = 8MHz, fpass = 100kHz, fstop = 103kHz, Astop=100dB, Apass=1dB
58
        27 sections, order = 53
59
    """
60
    SOS = [
61
        [0.995479543133467093, -1.984442343028223199, 0.995479543133467093, 1.000000000000000000, -1.992735837405045007,
62
         0.998943837906379417],
63
        [0.990160401678707558, -1.973793179424183863, 0.990160401678707558, 1.000000000000000000, -1.990574581081460126,
64
         0.996817448646951321],
65
        [0.989994808108325630, -1.973370399921241303, 0.989994808108325630, 1.000000000000000000, -1.988322781675001094,
66
         0.994642785115447126],
67
        [0.991233302433756958, -1.975696584512673271, 0.991233302433756958, 1.000000000000000000, -1.985941378398403678,
68
         0.992382744424644914],
69
        [0.991659186917251634, -1.976348896290843538, 0.991659186917251634, 1.000000000000000000, -1.983386802441603702,
70
         0.989996937324842396],
71
        [0.991088443582708867, -1.974955022041059438, 0.991088443582708867, 1.000000000000000000, -1.980609068243228243,
72
         0.987439951339094235],
73
        [0.989791457625747717, -1.972046311400692975, 0.989791457625747717, 1.000000000000000000, -1.977549417505724660,
74
         0.984659251857910700],
75
        [0.987954582977658147, -1.967983929921569342, 0.987954582977658147, 1.000000000000000000, -1.974137317922536994,
76
         0.981592546952532841],
77
        [0.985631182144554474, -1.962860596839200111, 0.985631182144554474, 1.000000000000000000, -1.970286545701848580,
78
         0.978164380415427415],
79
        [0.982774011953296389, -1.956564178822880251, 0.982774011953296389, 1.000000000000000000, -1.965889972689622844,
80
         0.974281625675177509],
81
        [0.979253170496530601, -1.948812091805058877, 0.979253170496530601, 1.000000000000000000, -1.960812519064707216,
82
         0.969827417018972904],
83
        [0.974849525369247516, -1.939136275833626355, 0.974849525369247516, 1.000000000000000000, -1.954881497685421854,
84
         0.964652853916498465],
85
        [0.969223223534541378, -1.926817757162398781, 0.969223223534541378, 1.000000000000000000, -1.947873233632636314,
86
         0.958565521324582837],
87
        [0.961850833163988006, -1.910757730499921481, 0.961850833163988006, 1.000000000000000000, -1.939494351783139114,
88
         0.951313448987238353],
89
        [0.951915030351768388, -1.889252974781179262, 0.951915030351768388, 1.000000000000000000, -1.929355449638937792,
90
         0.942562554513742157],
91
        [0.938119438257709160, -1.859621313051272651, 0.938119438257709160, 1.000000000000000000, -1.916934024503464062,
92
         0.931864889025346255],
93
        [0.918395732636982709, -1.817613163905800633, 0.918395732636982709, 1.000000000000000000, -1.901522707459767370,
94
         0.918614305102019579],
95
        [0.889505718582964788, -1.756619076035403682, 0.889505718582964788, 1.000000000000000000, -1.882158880178752547,
96
         0.901986187040051623],
97
        [0.846725210100101044, -1.667053994501234815, 0.846725210100101044, 1.000000000000000000, -1.857535164039942233,
98
         0.880860806020991594],
99
        [0.784291731375497903, -1.537281285666471931, 0.784291731375497903, 1.000000000000000000, -1.825904322530537804,
100
         0.853741893377616523],
101
        [0.697754992932913987, -1.358310063925080913, 0.697754992932913987, 1.000000000000000000, -1.785035725009338492,
102
         0.818719360505246518],
103
        [0.587659395799238982, -1.131033533640340982, 0.587659395799238982, 1.000000000000000000, -1.732396994259293388,
104
         0.773624816388282732],
105
        [0.459692699491257184, -0.866373131097798344, 0.459692699491257184, 1.000000000000000000, -1.665995235127884611,
106
         0.716751802598463050],
107
        [0.320384043209753555, -0.577024918342495030, 0.320384043209753555, 1.000000000000000000, -1.586704538299201772,
108
         0.648848540201372170],
109
        [0.180337233564054289, -0.285068622336177968, 0.180337233564054289, 1.000000000000000000, -1.502750870671594852,
110
         0.576957777166854702],
111
        [0.067223579650870141, -0.048886921409928924, 0.067223579650870141, 1.000000000000000000, -1.433955574106285269,
112
         0.518049962948276210],
113
        [0.149602974506854058, 0.149602974506854058, 0.000000000000000000, 1.000000000000000000, -0.703314433339987333,
114
         0.000000000000000000]]
115
 
116
    """ Filter section gain-values and overall output gain.
117
        This array can have different dimensions/lengths:
118
        If SOSGAIN_EN is true: It must be at least as long as the number of sections, as each section
119
            requires an individual gain.
120
        If FINALGAIN_EN is true: It must be at least one element long, the final output gain of the filter.
121
        If neither of these two parameters is true: It must be an empty list.
122
        If both parameters are true: The list must be the number of sections plus 1 (section-gains + output gain)
123
    """
124
    G = [0.158364443548321743]
125
 
126
    """ Defines if section-output-gains in hardware will be enabled.
127
        The length of G must correspond with this setting (see above).
128
    """
129
    SOSGAIN_EN = False
130
 
131
    """ Defines if a final filter output gain will be used.
132
        The length of G must correspond with this setting (see above).
133
    """
134
    FINALGAIN_EN = True
135
 
136
    """ Number of bits of the unfiltered input data vector.
137
        The filter operates on signed vectors.
138
    """
139
    W_DAT_INPUT = 16
140
 
141
    """ Total number of bits of the filter coefficients.
142
        The coefficients are signed vectors.
143
        See W_FRAC below for explanation of Q-notation.
144
    """
145
    W_COEF = 18
146
 
147
    """ Number of bits used for the fraction of the quantized coefficients. Must be <= W_COEF.
148
        Integer arithmetic: Q-Notation: The difference between W_COE and W_FRAC are the integer bits.
149
        E.g., for Q1.15, it would require W_COEF = 16 and W_FRAC = 15.
150
    """
151
    W_FRAC = 16
152
 
153
    """ Filter-input gain: Must be a power of two.
154
        The filter gain can be used to extend the number of bits of the filtered data,
155
        in order to increase the precision/filter resolution.
156
        If not used/desired: Set to 1.
157
        W_SECT_DAT (filter-internal vector width) will depend on this value.
158
    """
159
    GAIN_INPUT = 4
160
 
161
    """ Number of bits of (signed) filter output vector
162
        Filtered result will be saturated to the range of this (signed) vector.
163
        If filter overshoots (to full-scale inputs) should be covered correctly, use at least 1 bit more
164
        than at filter input (W_DAT_INPUT).
165
    """
166
    W_DAT_OUTPUT = 32
167
 
168
    """ This script can only take the required bit-extension caused by the final gain into account.
169
        If the SOS-gains are used, it could be necessary to manually extend the filter-internal vector width.
170
        This can be done with this parameter. It directly affects W_SECT_DAT.
171
        Adjust if the sections saturate or the filter does not achieve the desired response due to vector length limits.
172
        Set to 0 if not used/required.
173
    """
174
    NUM_BITS_SECT_DAT_EXT_MANUAL = 0
175
 
176
    """ Simulation-specific user-configuration. This does not affect the filter parameters.
177
    """
178
    STEP_AMPLITUDE = 1.0  # Amplitude of the step for the step-response. Range: 0-1
179
    NUM_STEP_SIM = 800  # Number of filter iterations for the simulation of the step-response
180
 
181
    """ File-Names and directories for Data Storage.
182
        This script generates various text-files containing filter configurations and testbench stimuli.
183
    """
184
    FILENAME_METADATA = 'FilterMetaData.txt'  # Stores information for the VHDL generic header
185
    DIRNAME_STIMULIDATA = 'FilterStimuliData'  # Directory where testbench-related data is stored.
186
 
187
    """ END OF USER INPUT: Implementation follows. """
188
 
189
    # Convert to floating-point; to homogenize the input
190
    SOS = [[float(y) for y in x] for x in SOS]  # SOS is a list of lists.
191
    G = [float(x) for x in G]
192
    W_DAT_INPUT = int(W_DAT_INPUT)
193
    W_COEF = int(W_COEF)
194
    W_FRAC = int(W_FRAC)
195
    # GAIN_INPUT = int(GAIN_INPUT) Don't do this - check for exact power of two and integer-ness later.
196
    W_DAT_OUTPUT = int(W_DAT_OUTPUT)
197
 
198
    # Check the viability of the user-supplied data. This function may raise errors and create console output.
199
    check_user_inputdata(SOS, G, SOSGAIN_EN, FINALGAIN_EN, W_COEF, W_FRAC, GAIN_INPUT)
200
 
201
    # Calculate the required internal filter vector length (W_SECT_DAT):
202
    w_sect_dat = get_internal_bit_width(W_DAT_INPUT, G, GAIN_INPUT, W_DAT_OUTPUT, FINALGAIN_EN)
203
    w_sect_dat = w_sect_dat + NUM_BITS_SECT_DAT_EXT_MANUAL
204
    s = 'Filter-internal bit-width (W_SECT_DAT): ' + repr(w_sect_dat)
205
    print(s)
206
 
207
    # Quantize the coefficients:
208
    SOSd, Gd = quantize_filter_coefs(SOS, G, W_FRAC)
209
 
210
    # Write the required VHDL-generics into a file for storage:
211
    write_filter_metadata(FILENAME_METADATA, SOS, W_DAT_INPUT, W_DAT_OUTPUT, GAIN_INPUT, w_sect_dat, W_COEF, W_FRAC,
212
                          FINALGAIN_EN, SOSGAIN_EN)
213
 
214
    # Write the coefficients into different files for the testbench:
215
    # First, create the directory for all testbench-related data, if necessary:
216
    if not os.path.exists(DIRNAME_STIMULIDATA):
217
        os.makedirs(DIRNAME_STIMULIDATA)
218
    write_filter_coe_testbench(SOSd, Gd, SOSGAIN_EN, FINALGAIN_EN, DIRNAME_STIMULIDATA)
219
 
220
    # Create a floating-point step-input vector. A vector would not be needed (it's all the same value),
221
    # but this allows an easy change to other inputs if so desired (e.g., noise).
222
    floatstepin = np.ones(NUM_STEP_SIM, dtype=float) * STEP_AMPLITUDE
223
 
224
    # Simulate the filter's step response with the floating-point implementation:
225
    filt_float_inst = SOS_Casc_df1_Float(SOS, G, GAIN_INPUT, SOSGAIN_EN, FINALGAIN_EN)  # Create the filter instance
226
    filt_float_response = []
227
    for i in range(0, NUM_STEP_SIM):
228
        filt_float_response.append(filt_float_inst.update_filter(floatstepin[i]))
229
 
230
    # Create the quantized step-input vector.
231
    max_in = int(2 ** (W_DAT_INPUT - 1) - 1)  # Maximum (signed) filter input value
232
    quantstepin = np.round(np.ones(NUM_STEP_SIM) * max_in * STEP_AMPLITUDE)
233
    quantstepin = [int(x) for x in quantstepin]  # Convert to python integer
234
 
235
    # Simulate the quantized filter's step response with the VHDL-reference model:
236
    filt_quant_inst = SOS_Casc_df1_Quantized(SOSd, Gd, GAIN_INPUT, SOSGAIN_EN, FINALGAIN_EN, W_DAT_INPUT, w_sect_dat,
237
                                             W_DAT_OUTPUT, W_COEF, W_FRAC)
238
    filt_quant_response = []
239
    for i in range(0, NUM_STEP_SIM):
240
        filt_quant_response.append(filt_quant_inst.update_filter(quantstepin[i]))
241
 
242
    # Convert the quantized output to float and rescale back such that the plots can be compared
243
    filt_quant_response_float = [float(x) / max_in / STEP_AMPLITUDE for x in filt_quant_response]
244
 
245
    # Write the digital filter input (stimuli) and reference output into two files for testbench-use.
246
    filename_stims = os.path.join(DIRNAME_STIMULIDATA, 'InputDat_Stimuli.txt')
247
    write_list_file(filename_stims, quantstepin)
248
    filename_stims = os.path.join(DIRNAME_STIMULIDATA, 'OutputDat_GoldenReference.txt')
249
    write_list_file(filename_stims, filt_quant_response)
250
 
251
    print('Done. Displaying the plot.')
252
 
253
    # Plot the filter's step-response:
254
    fig = plt.figure()
255
    ax = fig.add_subplot(111)
256
    x = np.arange(1, NUM_STEP_SIM + 1, 1)
257
    plt.step(x, filt_float_response, label='Floating-Point', marker='o')
258
    plt.step(x, filt_quant_response_float, label='Quantized (Rescaled)', marker='*')
259
    ax.set_xlabel('Sample Nr.')
260
    ax.set_ylabel('Filter Response')
261
    ax.set_title('Filter Step Responses')
262
    plt.legend()
263
    plt.grid()
264
    plt.show()  # This will block until the plotted window is closed.
265
 
266
 
267
def write_list_file(filename, dat):
268
    """ Writes the content of the list "dat" into a text-file.
269
        Existing files will be cleared / overwritten.
270
    """
271
    clear_txt_file(filename)
272
    [write_string_append(filename, repr(x)) for x in dat]
273
 
274
 
275
def write_filter_coe_testbench(sosd, gd, sosgain_en, finalgain_en, dirpath):
276
    """ Writes the coefficients in multiple files needed for the filter testbench
277
        dirpath is a string pointing to the desired directory where the files will be created in.
278
    """
279
    sosd_np = np.array(sosd)
280
    numsec, _ = sosd_np.shape
281
    filename_b0 = os.path.join(dirpath, 'CoeffStimuli_b0.txt')
282
    filename_b1 = os.path.join(dirpath, 'CoeffStimuli_b1.txt')
283
    filename_b2 = os.path.join(dirpath, 'CoeffStimuli_b2.txt')
284
    filename_a1 = os.path.join(dirpath, 'CoeffStimuli_a1.txt')
285
    filename_a2 = os.path.join(dirpath, 'CoeffStimuli_a2.txt')
286
    filename_g = os.path.join(dirpath, 'CoeffStimuli_g.txt')
287
    filename_finalgain = os.path.join(dirpath, 'CoeffStimuli_finalgain.txt')
288
 
289
    clear_txt_file(filename_b0)
290
    clear_txt_file(filename_b1)
291
    clear_txt_file(filename_b2)
292
    clear_txt_file(filename_a1)
293
    clear_txt_file(filename_a2)
294
    # Note, these values are cleared and it will always have to be written something to the gain-files,
295
    # since the testbench always reads them.
296
    clear_txt_file(filename_g)
297
    clear_txt_file(filename_finalgain)
298
 
299
    # Note that the SOS-coeffs are given as list of: [b0, b1, b2, 1, a1, a2]
300
    # b0, b1, b2, a1, a2 are always required:
301
    b0 = sosd_np[:, 0]
302
    b1 = sosd_np[:, 1]
303
    b2 = sosd_np[:, 2]
304
    a1 = sosd_np[:, 4]
305
    a2 = sosd_np[:, 5]
306
 
307
    if sosgain_en is True and finalgain_en is True:
308
        g = gd[:-1]  # The section gains: all but the last element, which is the output gain
309
        outgain = gd[-1]  # Last element is the final output gain
310
    elif sosgain_en is True and finalgain_en is False:
311
        g = gd  # There is no output-gain
312
        outgain = 0  # Write bogus-value; the filter will not use it, but the testbench will want to read it.
313
    elif sosgain_en is False and finalgain_en is True:
314
        outgain = gd[-1]  # G contains only the output gain. Get it out of the list.
315
        g = np.zeros(numsec, dtype=int)  # Write zero; to allow testbench-read
316
    elif sosgain_en is False and finalgain_en is False:
317
        outgain = 0  # Write zero; to allow testbench-read
318
        g = np.zeros(numsec, dtype=int)  # Write zero; to allow testbench-read
319
 
320
    # Write the coefficients of the sections into their files:
321
    [write_string_append(filename_b0, repr(x)) for x in b0]
322
    [write_string_append(filename_b1, repr(x)) for x in b1]
323
    [write_string_append(filename_b2, repr(x)) for x in b2]
324
    [write_string_append(filename_a1, repr(x)) for x in a1]
325
    [write_string_append(filename_a2, repr(x)) for x in a2]
326
    [write_string_append(filename_g, repr(x)) for x in g]
327
    write_string_append(filename_finalgain, repr(outgain))
328
 
329
 
330
def write_filter_metadata(filename, sos, w_dat_input, w_dat_output, gain_input, w_sect_dat, w_coef, w_frac,
331
                          finalgain_en, sosgain_en):
332
    """ Writes the filter's meta-data (for VHDL generic) into a text-file for storage."""
333
    clear_txt_file(filename)
334
    t = datetime.datetime.now().strftime("%d.%m.%Y - %H:%M:%S")
335
    sos_np = np.array(sos)
336
    numsec, _ = sos_np.shape  # Nr. of filter sections
337
    write_string_append(filename, t + '\n')
338
    write_string_append(filename, 'generic(')
339
    write_string_append(filename, 'NUM_SEC : integer := ' + repr(numsec) + ';')
340
    write_string_append(filename, 'W_DAT_INPUT : integer := ' + repr(w_dat_input) + ';')
341
    write_string_append(filename, 'GAIN_INPUT : integer := ' + repr(gain_input) + ';')
342
    write_string_append(filename, 'W_SECT_DAT : integer := ' + repr(w_sect_dat) + ';')
343
    write_string_append(filename, 'W_COEF : integer := ' + repr(w_coef) + ';')
344
    write_string_append(filename, 'W_FRAC : integer := ' + repr(w_frac) + ';')
345
    if sosgain_en is True:
346
        write_string_append(filename, 'SOSGAIN_EN : boolean := true;')
347
    else:
348
        write_string_append(filename, 'SOSGAIN_EN : boolean := false;')
349
    if finalgain_en is True:
350
        write_string_append(filename, 'FINALGAIN_EN : boolean := true;')
351
    else:
352
        write_string_append(filename, 'FINALGAIN_EN : boolean := false;')
353
    write_string_append(filename, 'W_DAT_OUTPUT : integer := ' + repr(w_dat_output) + ';')
354
    write_string_append(filename, 'W_DAT_INTF : integer := SET_SOMETHING;')
355
    write_string_append(filename, 'USE_PIPELINE_CORE : boolean := SET_SOMETHING')  # No semicolon - last entry
356
    write_string_append(filename, ');')
357
 
358
 
359
def clear_txt_file(filename):
360
    """ Clears the content of a text-file.
361
        If the file does not exist, a new empty file with this name is created.
362
    """
363
    with open(filename, "w") as file:
364
        pass
365
 
366
 
367
def write_string_append(filename, string):
368
    """ Writes a string to a file.
369
        If file does not exist, it will create it.
370
        If file exists, content will be overwritten.
371
        newline is done automatically.
372
    """
373
    with open(filename, "a") as file:
374
        print(string, file=file)
375
 
376
 
377
def quantize_filter_coefs(sos, g, w_frac):
378
    """ Quantizes the filter coefficients; from floating to fixed-point
379
        Note: Python 3's integers are arbitrary precision.
380
    """
381
    sos_d = np.round(np.array(sos) * np.power(2.0, w_frac))
382
    g_d = np.round(np.array(g) * np.power(2.0, w_frac))
383
    sos_d = sos_d.tolist()  # Convert back to python-3 list instead of numpy-array due to better precision.
384
    sos_d = [[int(y) for y in x] for x in sos_d]  # Python-3 integer.
385
    g_d = g_d.tolist()
386
    g_d = [int(x) for x in g_d]
387
    return (sos_d, g_d)
388
 
389
 
390
def get_internal_bit_width(w_dat_input, g, gain_input, w_dat_output, finalgain_en):
391
    """ Calculates the required internal filter widths (W_SECT_DAT).
392
        This only works perfectly, if only finalgain_en is true. If there are SOS-gains that are smaller than one,
393
        it is not accounted for by this script. W_SECT_DAT then has to be manually increased, if the filter performance
394
        is unsatisfactory.
395
    """
396
 
397
    # Add one bit for the filter-internal bit-width to accommodate filter overshoots up to twice the input value.
398
    # This is relevant for (near-) full-scale inputs, that would otherwise saturate the sections prematurely.
399
    w_sect_dat = w_dat_input + 1
400
 
401
    # Add the bits required due to the filter input gain
402
    # Note: gain_input has to be a power of two. This script checked this earlier (see "check_user_inputdata").
403
    w_sect_dat += int(math.ceil(math.log(gain_input, 2)))
404
 
405
    if finalgain_en is True:
406
        # If the final filter output gain is enabled and less than one, the internal bit-width has to be increased
407
        # such that precision is not lost (see below)
408
        # The filter output gain is the last element in G:
409
        gain_out = g[-1]
410
 
411
        """ If the final output gain is smaller than 1, it means that additional bits are needed before the final gain,
412
            as the values are larger than what will be output after the output gain and our data vectors need to be able
413
            to cover this extended range. (e.g., if the final output gain is 0.1 and our filter settles to 1, it have to
414
            be able to store a value of 10 inside our filter.
415
            If the overall output gain is larger than 1, no additional bits are needed inside our filter, since it
416
            "creates" the bits/information with the output gain. No check is needed if the extended vector fits into the
417
            output, as it will just be saturated.
418
        """
419
        if gain_out < 1:
420
            """ Figure out how many bits are required to accommodate the (larger) value before the gain.
421
                This is done by solving the (signed-value) maximum of the two (pre/after gain) vectors for the number of
422
                bits required; like so: 1 / gain_out = 2^(x-1)-1  ==> Solve for x, which is the number of additional
423
                bits required due to the output gain. Solution: x = ln((2(gain_out+1))/gain_out)/ln(2)
424
                ==> use ceil of this value.
425
            """
426
            numbits_outgain_addtl = int(math.ceil(math.log((2.0 * (gain_out + 1)) / gain_out) / math.log(2.0)))
427
            w_sect_dat += numbits_outgain_addtl
428
 
429
    return w_sect_dat
430
 
431
 
432
def check_user_inputdata(sos, g, sosgain_en, finalgain_en, w_coef, w_frac, gain_input):
433
    """ This function checks the user-supplied filter configuration for errors or inconsistencies.
434
        This makes sure the hardware/filter is not wrongly configured.
435
        The function may raise runtime errors and/or produces console outputs.
436
    """
437
    sos_np = np.array(sos)  # Convert to numpy-array to get the shape (and further functions, see below)
438
    sos_x, sos_y = sos_np.shape
439
    g_np = np.array(g)
440
    len_g, = g_np.shape
441
    # Check correct format of coefficients. They have to be supplied as list of [b0, b1, b2, 1, a1, a2].
442
    if sos_y != 6:
443
        raise RuntimeError('Number of SOS coefficients per section is incorrect.'
444
                           'Deliver section-coefficients as list of: [b0, b1, b2, 1, a1, a2]')
445
 
446
    # Check if coefficient a0 of sos is fixed to 1:
447
    # Careful with float comparison.
448
    for i in range(0, sos_x):
449
        if np.isclose(sos_np[i, 3], 1.0, rtol=1e-8, atol=1e-9, equal_nan=False) is False:
450
            raise RuntimeError('SOS-coefficients at index 3 of SOS-array must all be equal to 1. '
451
                               'Deliver section-coefficients as list of: [b0, b1, b2, 1, a1, a2]')
452
 
453
    # Check, if the length of the output-gain vector corresponds to the selection settings (sosgain_en, finalgain_en)
454
    if sosgain_en is False and finalgain_en is False:
455
        if len_g != 0:
456
            raise RuntimeError('If both SOSGAIN_EN and FINALGAIN_EN are False, G must be an empty list.')
457
    elif sosgain_en is True and finalgain_en is False:
458
        if len_g != sos_x:
459
            raise RuntimeError('If SOSGAIN_EN is True and FINALGAIN_EN is False, G must be as long as  '
460
                               'there are sections.')
461
    elif sosgain_en is True and finalgain_en is True:
462
        if len_g != sos_x + 1:
463
            raise RuntimeError('If SOSGAIN_EN and FINALGAIN_EN are True, G must be as long as  '
464
                               'the number of sections plus 1. The last entry in G is the final output gain.')
465
    elif sosgain_en is False and finalgain_en is True:
466
        if len_g != 1:
467
            raise RuntimeError('If SOSGAIN_EN is False and FINALGAIN_EN is True, G must be a list of length 1,  '
468
                               'containing the filter output gain.')
469
 
470
    # Check, if the filter is not too long. Maximum nr. of sections: 255 (hardcoded in VHDL)
471
    if sos_x > 255:
472
        raise RuntimeError('Too many filter sections desired. Max. nr. of sections is 255 (Hardcoded in VHDL).')
473
 
474
    s = 'Number of SOS this filter comprises: ' + repr(sos_x)
475
    print(s)
476
 
477
    # Check, if the section-gains/output gain could be disabled:
478
    if sosgain_en is True:
479
        if finalgain_en is True:
480
            len_iter = len_g - 1
481
            if np.isclose(g_np[len_g - 1], 1.0, rtol=1e-8, atol=1e-9, equal_nan=False) is True:
482
                print('Detected that the final filter gain is unity. FINALGAIN_EN could be set to False.')
483
        else:
484
            len_iter = len_g
485
        gain_notunity = 0
486
        for i in range(0, len_iter):
487
            if np.isclose(g_np[i], 1.0, rtol=1e-8, atol=1e-9, equal_nan=False) is False:
488
                gain_notunity = 1
489
        if gain_notunity < 0.5:
490
            print('Detected that all section-gains are unity. SOSGAIN_EN could be set to False '
491
                  '(Disables output-gains of section); Less hardware-resources used.')
492
 
493
    # Check if the delivered coefficients are within the range of the number-space covered by the
494
    # coefficients/fractions.
495
    # Q-Notation: Signed Qm.n format.
496
    m = int(w_coef - w_frac)  # Nr. of integer bits
497
    if m < 1:
498
        raise RuntimeError('W_COEF - W_FRAC must be >= 1. Terminating.')
499
    n = int(w_frac)  # Nr. of fraction bits
500
    coef_min = -1.0 * float((2.0 ** (m - 1)))
501
    coef_max = float(2.0 ** (m - 1) - 2.0 ** (-1 * n))
502
    # Check, if all coefficients are in range:
503
    for i in range(0, sos_x):
504
        for k in range(0, sos_y):
505
            if sos[i][k] > coef_max or sos[i][k] < coef_min:
506
                s = 'Coefficient SOS[' + repr(i) + ',' + repr(k) + '] out of range with value ' + repr(sos[i][k])
507
                print(s)
508
                s = 'Specified Q-format: Q' + repr(m) + '.' + repr(n)
509
                print(s)
510
                s = 'Allowed range: ' + repr(coef_min) + ' < Coeff. < ' + repr(coef_max)
511
                print(s)
512
                raise RuntimeError('Coefficient out of range for specified Q-format. See console-output for details')
513
 
514
    # Check the gain-coefficients for conformity:
515
    for i in range(0, len_g):
516
        if g[i] > coef_max or g[i] < coef_min:
517
            s = 'Coefficient G[' + repr(i) + '] out of range with value ' + repr(g[i])
518
            print(s)
519
            s = 'Specified Q-format: Q' + repr(m) + '.' + repr(n)
520
            print(s)
521
            s = 'Allowed range: ' + repr(coef_min) + ' < Coeff. < ' + repr(coef_max)
522
            print(s)
523
            raise RuntimeError('Coefficient out of range for specified Q-format. See console-output for details')
524
 
525
    # If there is only a single section, check that finalgain is used, and not sos-gain.
526
    # Otherwise, the section-width calculation is not correct / the filter saturates.
527
    if sos_x == 1:
528
        if sosgain_en is True and finalgain_en is False:
529
            raise RuntimeError('When only using one section, use the final gain and not the SOS-gain. '
530
                               'Otherwise, section-data widths not calculated correctly.')
531
 
532
    # Check, if the filter-gain is specified as a power of two and as integer:
533
    if isinstance(gain_input, int) is False:
534
        raise RuntimeError('GAIN_INPUT must be an integer (and power of two)')
535
    if gain_input < 1:
536
        raise RuntimeError('GAIN_INPUT must be >= 1')
537
    if ((gain_input & (gain_input - 1)) == 0) is False:
538
        raise RuntimeError('GAIN_INPUT must be a power of two')
539
 
540
    # Check the value of the filter's overall output gain:
541
    if finalgain_en is True:
542
        gain_out = g[-1]
543
        if gain_out > 1:
544
            print('NOTE: final filter output gain is > 1. This could be covered/supported by the input gain'
545
                  '(and maybe an output gain < 1) for increased filter precision (but higher resource utilization'
546
                  'due to longer filter-internal vector widths)')
547
 
548
 
549
if __name__ == "__main__":
550
    main()  # Simply execute the main script.

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.