Progettare il filtro Butterworth in Matlab e ottenere coefficienti di filtro [ab] come numeri interi per il generatore di codice HDL Verilog online


15

Ho progettato un filtro Butterworth passa-basso molto semplice usando Matlab. Il seguente frammento di codice dimostra ciò che ho fatto.

fs = 2.1e6;
flow = 44 * 1000;
fNorm =  flow / (fs / 2);
[b,a] = butter(10, fNorm, 'low');

In [b, a] sono memorizzati i coefficienti di filtro. Vorrei ottenere [b, a] come numeri interi in modo da poter utilizzare un generatore di codice HDL online per generare codice in Verilog.

I valori di Matlab [b, a] sembrano essere troppo piccoli per essere utilizzati con il generatore di codice online (lo script Perl sul lato server rifiuta di generare codice con i coefficienti) e mi chiedo se sarebbe possibile ottenere [b, a] in una forma che può essere utilizzata come input corretto.

I coefficienti a che ottengo in Matlab sono:

1.0000
-9.1585
37.7780
-92.4225
148.5066
-163.7596
125.5009
-66.0030
22.7969
-4.6694
0.4307

I coefficienti b che ottengo in Matlab sono:

1.0167e-012
1.0167e-011
4.5752e-011
1.2201e-010
2.1351e-010
2.5621e-010
2.1351e-010
1.2201e-010
4.5752e-011
1.0167e-011
1.0167e-012

Usando il generatore online, vorrei progettare un filtro con una larghezza di bit a 12 bit e una forma di filtro I o II. Non so cosa si intenda per "bit frazionari" al link sopra.

Eseguendo il generatore di codice (http://www.spiral.net/hardware/filter.html) con i coefficienti [b, a] sopra elencati, con bit frazionari impostati su 20 e una larghezza di bit di 12, viene visualizzato il seguente errore di esecuzione :

Integer A constants: 1048576 -9603383 39613104 -96912015 155720456 -171714386 131597231 -69209161 23904282 -4896220 451621
Integer B constants: 0 0 0 0 0 0 0 0 0 0 0

Error: constants wider than 26 bits are not allowed, offending constant = -69209161, effective bitwidth = 7 mantissa + 20 fractional = 27 total.

An error has occurred - please revise the input parameters. 

Come posso cambiare il mio design in modo che questo errore non si verifichi?

AGGIORNAMENTO: Usando Matlab per generare un filtro Butterworth del 6 ° ordine, ottengo i seguenti coefficienti:

Per un:

1.0000
-5.4914
12.5848
-15.4051
10.6225
-3.9118
0.6010 

per b:

0.0064e-005
0.0382e-005
0.0954e-005
0.1272e-005
0.0954e-005
0.0382e-005
0.0064e-005

Eseguendo il generatore di codice online (http://www.spiral.net/hardware/filter.html), ora ricevo il seguente errore (con bit frazionari come 8 e larghezza di bit di 20):

./iirGen.pl -A 256  '-1405' '3221' '-3943' '2719' '-1001' '153' -B  '0' '0' '0' '0' '0' '0' '0' -moduleName acm_filter -fractionalBits 8 -bitWidth 20 -inData inData  -inReg   -outReg  -outData outData -clk clk -reset reset -reset_edge negedge -filterForm 1  -debug  -outFile ../outputs/filter_1330617505.v 2>&1 
At least 1 non-zero-valued constant is required.  Please check the inputs and try again.

Forse i coefficienti b sono troppo piccoli, o forse il generatore di codice (http://www.spiral.net/hardware/filter.html) vuole che [b, a] in un altro formato?

AGGIORNARE:

Forse quello che devo fare è ridimensionare i coefficienti [b, a] del numero di bit frazionari per ottenere i coefficienti come numeri interi.

a .* 2^12
b .* 2^12

Tuttavia, penso ancora che i coefficienti b siano estremamente piccoli. Cosa sto facendo di sbagliato qui?

Forse un altro tipo di filtro (o metodo di progettazione del filtro) sarebbe più adatto? Qualcuno potrebbe dare un suggerimento?

AGGIORNAMENTO: Come suggerito da Jason R e Christopher Felton nei commenti qui sotto, un filtro SOS sarebbe più adatto. Ora ho scritto del codice Matlab per ottenere un filtro SOS.

fs = 2.1e6;
flow = 44 * 1000;      
fNorm =  flow / (fs / 2);
[A,B,C,D] = butter(10, fNorm, 'low');
[sos,g] = ss2sos(A,B,C,D);

La matrice SOS che ottengo è:

1.0000    3.4724    3.1253    1.0000   -1.7551    0.7705
1.0000    2.5057    1.9919    1.0000   -1.7751    0.7906
1.0000    1.6873    1.0267    1.0000   -1.8143    0.8301
1.0000    1.2550    0.5137    1.0000   -1.8712    0.8875
1.0000    1.0795    0.3046    1.0000   -1.9428    0.9598

È ancora possibile utilizzare lo strumento di generazione del codice Verilog (http://www.spiral.net/hardware/filter.html) per implementare questo filtro SOS o devo semplicemente scrivere Verilog a mano? È disponibile un buon riferimento?

Mi chiederei se sarebbe meglio usare un filtro FIR in questa situazione.

INOLTRE: I filtri ricorsivi IIR possono essere implementati usando la matematica intera esprimendo i coefficienti come frazioni. (Vedi l'eccellente libro di elaborazione del segnale DSP di Smith per ulteriori dettagli: http://www.dspguide.com/ch19/5.htm )

Il seguente programma Matlab converte i coefficienti del filtro Butterworth in parti frazionarie usando la funzione Matlab rat (). Quindi, come menzionato nei commenti, le sezioni del secondo ordine possono essere utilizzate per implementare numericamente il filtro (http://en.wikipedia.org/wiki/Digital_biquad_filter).

% variables
% variables
fs = 2.1e6;                     % sampling frequency           
flow = 44 * 1000;               % lowpass filter


% pre-calculations
fNorm =  flow / (fs / 2);       % normalized freq for lowpass filter

% uncomment this to look at the coefficients in fvtool
% compute [b,a] coefficients
% [b,a] = butter(7, fNorm, 'low');
% fvtool(b,a)  

% compute SOS coefficients (7th order filter)
[z,p,k] = butter(7, fNorm, 'low');

% NOTE that we might have to scale things to make sure
% that everything works out well (see zp2sos help for 'up' and 'inf' options)
sos = zp2sos(z,p,k, 'up', 'inf'); 
[n,d] = rat(sos); 
sos_check = n ./ d;  % this should be the same as SOS matrix

% by here, n is the numerator and d is the denominator coefficients
% as an example, write the the coefficients into a C code header file
% for prototyping the implementation

 % write the numerator and denominator matices into a file
[rownum, colnum] = size(n);  % d should be the same
sections = rownum;           % the number of sections is the same as the number of rows
fid = fopen('IIR_coeff.h', 'w');

fprintf(fid, '#ifndef IIR_COEFF_H\n');
fprintf(fid, '#define IIR_COEFF_H\n\n\n');
for i = 1:rownum
   for j = 1:colnum

       if(j <= 3)  % b coefficients
            bn = ['b' num2str(j-1) num2str(i) 'n' ' = ' num2str(n(i,j))];
            bd = ['b' num2str(j-1) num2str(i) 'd' ' = ' num2str(d(i,j))];
            fprintf(fid, 'const int32_t %s;\n', bn);
            fprintf(fid, 'const int32_t %s;\n', bd);

       end
       if(j >= 5)  % a coefficients
            if(j == 5) 
                colstr = '1'; 
            end
            if(j == 6) 
                colstr = '2'; 
            end
            an = ['a' colstr num2str(i) 'n' ' = ' num2str(n(i,j))];
            ad = ['a' colstr num2str(i) 'd' ' = ' num2str(d(i,j))];
            fprintf(fid, 'const int32_t %s;\n', an);
            fprintf(fid, 'const int32_t %s;\n', ad);
       end
   end
end

% write the end of the file
fprintf(fid, '\n\n\n#endif');
fclose(fid);

4
I filtri IIR di ordine superiore come questo sono generalmente implementati usando sezioni del secondo ordine ; ottieni il filtro che desideri collegando in cascata più fasi del secondo ordine (con una singola fase del primo ordine se l'ordine desiderato è dispari). È in genere un'implementazione più solida rispetto all'implementazione diretta del filtro di ordine superiore.
Jason R

3
Se non fai ciò che suggerisce @JasonR avrai parole di dimensioni molto grandi. Filtri come questo possono fallire in virgola mobile a precisione singola se implementati con una struttura IIR di base, è necessario il SOS.
Christopher Felton

@JasonR: Grazie per avermi suggerito questo. Ho aggiornato dalla risposta sopra.
Nicholas Kinar

@ChristopherFelton: Grazie per l'aiuto a rafforzare questo.
Nicholas Kinar

Sì, con la tua nuova matrice SOS puoi creare 3 filtri dal sito. Oppure puoi usare il mio codice qui . Funzionerà allo stesso modo del sito Web. Sarò lieto di aggiornare lo script ad eccezione della matrice SOS.
Christopher Felton

Risposte:


5

Come discusso, è meglio usare la somma delle sezioni, ovvero suddividere il filtro di ordine superiore in filtri a cascata del 2 ° ordine. La domanda aggiornata ha la matrice SOS. Usando questo codice e un esempio qui, l'oggetto Python può essere usato per generare le singole sezioni.

In matlab

save SOS

In Python

import shutil
import numpy
from scipy.io import loadmat
from siir import SIIR

matfile = loadmat('SOS.mat')  
SOS = matfile['SOS']
b = numpy.zeros((3,3))
a = numpy.zeros((3,3))
section = [None for ii in range(3)]
for ii in xrange(3):
    b[ii] = SOS[ii,0:3]
    a[ii] = SOS[ii,3:6]

    section[ii] = SIIR(b=b[ii], a=a[ii], W=(24,0))
    section[ii].Convert()  # Create the Verilog for the section
    shutil.copyfile('siir_hdl.v', 'iir_sos_section%d.v'%(ii))

Ulteriori informazioni sul punto fisso sono disponibili qui


Grazie mille per tutti i collegamenti perspicaci e per il codice Python; Spero che la tua risposta (e le altre risposte pubblicate qui) fungano da buoni riferimenti per molti altri. Vorrei poter contrassegnare tutte le risposte qui come accettate.
Nicholas Kinar

1
Se hai problemi fammi sapere e aggiornerò / riparerò il codice se non funziona per te. Lo modificherò (relativamente presto, doh) per accettare direttamente una matrice SOS.
Christopher Felton

1
Ho provato a implementare la mia versione dal tuo esempio. Sul mio sistema, ho dovuto usare "from numpy import zeros" e cambiare loatmat in loadmat (). La matrice SOS fornita da Matlab ( mathworks.com/help/toolbox/signal/ref/ss2sos.html ) è nello stesso formato previsto? Ricevo il seguente errore quando provo ad accedere alla matrice SOS: "TypeError: unhashable type" quando l'interprete raggiunge la riga "b [ii] = SOS [0: 3, ii]"
Nicholas Kinar

1
Ciò dipende dal formato del file SOS.mat. Se semplicemente >>> stampa (matfile) ti mostrerà le chiavi nel file .mat caricato. Scipy.io.loadmat si carica sempre come dizionario (BOMK).
Christopher Felton

1
Sì, è corretto, l'output di 0 è l'input su 1 e così via. Un piccolo pensiero deve essere inserito nella larghezza della parola. Il valore predefinito è una frazione di 24 bit a due usi (0 intero, 23 frazione, 1 segno). Credo che inizialmente volessi usare una larghezza di parola più piccola.
Christopher Felton

10

I 'bit frazionari' sono il numero di bit in un bus che hai dedicato a rappresentare la parte frazionaria di un numero (ad es., 75 in 3,75).

Supponi di avere un bus digitale largo 4 bit, quale numero 1001rappresenta? Potrebbe significare "9" se lo tratti come un numero intero positivo (2 ^ 3 + 2 ^ 0 = 8 + 1 = 9). Oppure potrebbe significare -7 nella notazione del complemento a due: (-2 ^ 3 + 2 ^ 0 = -8 + 1 = -7).

Che dire dei numeri con alcune frazioni in essi, cioè numeri "reali"? I numeri reali possono essere rappresentati nell'hardware come "punto fisso" o "virgola mobile". Sembra che quei generatori di filtri utilizzino un punto fisso.

Torna al nostro bus a 4 bit ( 1001). Introduciamo un punto binario in modo da ottenere 1.001. Ciò significa che ora utilizzavano i bit sull'RHS del punto per costruire numeri interi e i bit sull'LHS per creare una frazione. Il numero rappresentato da un bus digitale impostato su 1.001è 1,125 ( 1* 2 ^ 0 + 0* 2 ^ -1 + 0* 2 ^ -2 + 1* 2 ^ -3 = 1 + 0.125 = 1.125). In questo caso, tra i 4 bit nel bus, ne stiamo usando 3 per rappresentare la parte frazionaria di un numero. Oppure, abbiamo 3 bit frazionari.

Quindi, se hai un elenco di numeri reali come quello che hai sopra, ora devi decidere quanti bit frazionari vuoi rappresentarli. Ed ecco il compromesso: più bit frazionari usi, più vicino puoi rappresentare il numero che desideri, ma più grande sarà il tuo circuito. Inoltre, meno bit frazionari utilizzi, più la risposta in frequenza effettiva del filtro si discosterà da quella progettata all'inizio!

E a peggiorare le cose, stai cercando di costruire un filtro Infinite Impulse Response (IIR). Questi possono effettivamente diventare instabili se non hai abbastanza bit frazionari e interi!


Grazie per aver fornito questa risposta perspicace. Ho provato a far funzionare il generatore di codice usando i coefficienti b di cui sopra e continuo a ricevere alcuni errori. Potresti suggerire qualcosa che potrei fare per far funzionare correttamente il generatore? Aggiornerò la risposta sopra per mostrare cosa ho fatto.

10

Quindi Marty ha ben curato la questione dei pezzi. Sul filtro stesso, penso che probabilmente stai ricevendo un avviso o un reclamo da matlab sui coefficienti scarsamente dimensionati? Quando tracciamo il filtro, da scipy non a matlab ma probabilmente è molto simile.

Risposta

Che è di 100 dB verso il basso nella banda passante! Quindi, potresti voler assicurarti di volere un filtro per ordini più piccoli, che ti aiuterà comunque nella tua implementazione. Quando arrivo a un filtro del 6 ° ordine, smetto di ricevere lamentele su coefficienti errati. Forse prova a ridurre l'ordine e vedi se soddisfa ancora le tue esigenze.


Grazie per avermelo suggerito! Penso che un filtro del 6 ° ordine funzionerebbe altrettanto bene. Utilizzando fvtool di matlab, penso che la risposta sia buona per la mia applicazione. Ora ho aggiornato la mia risposta sopra. Tuttavia, c'è ancora qualcosa che non va nel generatore di codice HDL Verilog ( spiral.net/hardware/filter.html ). Forse vuole la [b, a] in un altro formato. Inoltre, +1 per l'uso di SciPy.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.