È possibile creare un filtro IIR in un FPGA con frequenza di campionamento?


9

Questa domanda riguarda l'implementazione di un filtro IIR in un FPGA con sezioni DSP, con criteri molto specifici.

Supponiamo che tu stia creando un filtro senza tocchi in avanti e solo 1 tocco inverso, con questa equazione:

y[n]=y[n-1]B1+X[n]

(vedi immagine)

Prendiamo ad esempio la slice DSP48A1 di Xilinx: la maggior parte delle slice DSP IP hard sono simili.

Supponiamo che tu abbia dati analogici in arrivo a 1 campione per clock. Vorrei progettare un filtro IIR che funziona in modo sincrono sull'orologio di esempio.

Il problema è che per eseguire la sezione DSP alla massima velocità, non è possibile moltiplicare E aggiungere nello stesso ciclo. Devi avere un registro della pipeline tra questi componenti.

Quindi, se hai 1 nuovo campione ogni clock, dovrai produrre 1 output per clock. Tuttavia, prima di poterne produrre uno nuovo in questo design, sono necessari i clock dell'uscita 2 precedente.

La soluzione ovvia è elaborare i dati a doppia frequenza o disabilitare il registro della pipeline in modo da poter moltiplicare e aggiungere nello stesso ciclo.

Sfortunatamente, se diciamo che state campionando alla frequenza di clock massima della porzione DSP con pipeline completa, nessuna di queste soluzioni è possibile. C'è un altro modo per costruirlo?

(Punti bonus se è possibile progettare un filtro IIR che funziona a metà della frequenza di campionamento, utilizzando un numero qualsiasi di sezioni DSP)

L'obiettivo sarebbe quello di eseguire un filtro di compensazione per un ADC 1 GSPS in un FPGA Xilinx Artix. Le loro sezioni DSP possono funzionare a poco più di 500 MHz quando sono completamente pipeline. Se esiste una soluzione per 1 campione per orologio, vorrei provare a ridimensionare la soluzione per 2 campioni per orologio. Tutto questo è molto semplice con un filtro FIR.

esempio di filtro IIR a feedback singolo


1
Giusto per chiarire, non c'è motivo per cui non avresti un output per ciclo di clock con il metodo pipeline, giusto? Stai cercando di ridurre al minimo la latenza fino a un ciclo di clock invece di due, giusto? A seconda della situazione, se si utilizza un numero intero per b1, è possibile convertire il moltiplicare in un'aggiunta gigante che include x [n].
horta,

giusto - poiché esiste un input per clock, deve esserci un output per clock. la latenza non è neanche un problema. la sezione DSP ha solo un sommatore a 2 input e i tocchi sono in genere numeri piuttosto grandi, quindi non è possibile aggiungere b1 volte in 1 ciclo di clock. il limite principale è che l'uscita deve retrocedere in 1 clock, ma per produrre sono necessari 2 clock.
Marcus10110,

1
Penso che tu stia ancora fraintendendo il funzionamento di una pipeline. Una pipeline potenzialmente aumenta la latenza, ma consente di ottenere 1 output per ciascun input ad ogni ciclo di clock. È solo che il risultato è ora 2 orologi dopo piuttosto che l'ideale 1 orologio dopo. L'ingresso sarebbe la sequenza in questo modo: x [0], x [1], x [2], x [3], x [4] mentre l'uscita sarebbe nello stesso intervallo di tempo y [-2], y [-1], y [0], y [1], y [2]. Non stai perdendo alcun campione. Inoltre, sei su un FPGA, quindi se vuoi fare più lavoro di quello per cui sono progettate le pipeline DSP, usa il fpga per parallelizzare il carico di lavoro.
horta,

Quel DSP è in grado di fare un accumulo moltiplicato fuso in un ciclo. Non mi è chiaro se l'output di una slice DSP possa essere collegato al proprio input con feedback in un singolo ciclo.
jbarlow,

horta - hai ragione sulla pipeline in generale, ma il problema è che la scheda b1 in questo caso ha un feedback - il che significa che uno stadio nella pipeline dipende dall'output del valore precedente. se sono sempre necessari 2 clock per produrre l'output successivo dall'output precedente, non è possibile produrre 1 output per clock, indipendentemente dalla latenza aggiunta. jbarlow - hai ragione, la sezione DSP ha un'opzione fusa a 1 ciclo. Tuttavia, in questo caso non può essere eseguito abbastanza velocemente. aggiungendo il registro M (vedi scheda tecnica) puoi raggiungere i 500 MHz. Tuttavia, non puoi moltiplicare e aggiungere lo stesso clk.
Marcus10110,

Risposte:


3

Non ho ancora lavorato con i filtri IIR, ma se hai solo bisogno di calcolare l'equazione data

y[n] = y[n-1]*b1 + x[n]

una volta per ciclo della CPU, è possibile utilizzare il pipelining.

In un ciclo si esegue la moltiplicazione e in un ciclo è necessario eseguire la somma per ciascun campione di input. Ciò significa che il tuo FPGA deve essere in grado di eseguire la moltiplicazione in un ciclo quando è sincronizzato alla frequenza di campionamento indicata! Quindi dovrai solo eseguire la moltiplicazione del campione corrente E la somma del risultato della moltiplicazione dell'ultimo campione in parallelo. Ciò provoca un ritardo di elaborazione costante di 2 cicli.

Ok, diamo un'occhiata alla formula e progettiamo una pipeline:

y[n] = y[n-1]*b1 + x[n]

Il codice della pipeline potrebbe essere simile al seguente:

output <= last_output_times_b1 + last_input
last_output_times_b1 <= output * b1;
last_input <= input

Nota che tutti e tre i comandi devono essere eseguiti in parallelo e che "output" nella seconda riga utilizza quindi l'output dell'ultimo ciclo di clock!

Non ho lavorato molto con Verilog, quindi la sintassi di questo codice è probabilmente errata (ad es. Larghezza di bit mancante dei segnali di input / output; sintassi di esecuzione per la moltiplicazione). Tuttavia dovresti avere l'idea:

module IIRFilter( clk, reset, x, b, y );
  input clk, reset, x, b;
  output y;

  reg y, t, t2;
  wire clk, reset, x, b;

  always @ (posedge clk or posedge reset)
  if (reset) begin
    y <= 0;
    t <= 0;
    t2 <= 0;
  end else begin
    y <= t + t2;
    t <= mult(y, b);
    t2 <= x
  end

endmodule

PS: Forse un programmatore esperto di Verilog potrebbe modificare questo codice e rimuovere questo commento e il commento sopra il codice in seguito. Grazie!

PPS: nel caso in cui il fattore "b1" sia una costante fissa, potresti essere in grado di ottimizzare il progetto implementando uno speciale moltiplicatore che accetta solo un input scalare e calcola solo "volte b1".

Risposta a: "Sfortunatamente, questo è effettivamente equivalente a y [n] = y [n-2] * b1 + x [n]. Ciò è dovuto allo stadio aggiuntivo della pipeline." come commento alla vecchia versione della risposta

Sì, quello era effettivamente giusto per la seguente vecchia versione (INCORRETTA !!!):

  always @ (posedge clk or posedge reset)
  if (reset) begin
    t <= 0;
  end else begin
    y <= t + x;
    t <= mult(y, b);
  end

Spero di aver corretto questo errore ora ritardando anche i valori di input in un secondo registro:

  always @ (posedge clk or posedge reset)
  if (reset) begin
    y <= 0;
    t <= 0;
    t2 <= 0;
  end else begin
    y <= t + t2;
    t <= mult(y, b);
    t2 <= x
  end

Per assicurarci che funzioni correttamente questa volta diamo un'occhiata a ciò che accade nei primi cicli. Si noti che i primi 2 cicli producono più o meno immondizia (definita), poiché non sono disponibili valori di output precedenti (ad es. Y [-1] == ??). Il registro y è inizializzato con 0, che equivale ad assumere y [-1] == 0.

Primo ciclo (n = 0):

BEFORE: INPUT (x=x[0], b); REGISTERS (t=0, t2=0, y=0)

y <= t + t2;      == 0
t <= mult(y, b);  == y[-1] * b  = 0
t2 <= x           == x[0]

AFTERWARDS: REGISTERS (t=0, t2=x[0], y=0), OUTPUT: y[0]=0

Secondo ciclo (n = 1):

BEFORE: INPUT (x=x[1], b); REGISTERS (t=0, t2=x[0], y=y[0])

y <= t + t2;      ==     0  +  x[0]
t <= mult(y, b);  ==  y[0]  *  b
t2 <= x           ==  x[1]

AFTERWARDS: REGISTERS (t=y[0]*b, t2=x[1], y=x[0]), OUTPUT: y[1]=x[0]

Terzo ciclo (n = 2):

BEFORE: INPUT (x=x[2], b); REGISTERS (t=y[0]*b, t2=x[1], y=y[1])

y <= t + t2;      ==  y[0]*b +  x[1]
t <= mult(y, b);  ==  y[1]   *  b
t2 <= x           ==  x[2]

AFTERWARDS: REGISTERS (t=y[1]*b, t2=x[2], y=y[0]*b+x[1]), OUTPUT: y[2]=y[0]*b+x[1]

Quarto ciclo (n = 3):

BEFORE: INPUT (x=x[3], b); REGISTERS (t=y[1]*b, t2=x[2], y=y[2])

y <= t + t2;      ==  y[1]*b +  x[2]
t <= mult(y, b);  ==  y[2]   *  b
t2 <= x           ==  x[3]

AFTERWARDS: REGISTERS (t=y[2]*b, t2=x[3], y=y[1]*b+x[2]), OUTPUT: y[3]=y[1]*b+x[2]

Possiamo vedere che a partire dal cilindro n = 2 otteniamo il seguente output:

y[2]=y[0]*b+x[1]
y[3]=y[1]*b+x[2]

che equivale a

y[n]=y[n-2]*b + x[n-1]
y[n]=y[n-1-l]*b1 + x[n-l],  where l = 1
y[n+l]=y[n-1]*b1 + x[n],  where l = 1

Come accennato in precedenza, introduciamo un ritardo aggiuntivo di l = 1 cicli. Ciò significa che l'output y [n] è ritardato di ritardo l = 1. Ciò significa che i dati di output sono equivalenti ma sono ritardati di un "indice". Per essere più chiari: i dati di uscita ritardati di 2 cicli, poiché è necessario un ciclo di clock (normale) e 1 ciclo di clock aggiuntivo (ritardo l = 1) viene aggiunto per lo stadio intermedio.

Ecco uno schizzo per rappresentare graficamente il flusso dei dati:

schizzo del flusso di dati

PS: Grazie per aver esaminato da vicino il mio codice. Quindi ho imparato anche qualcosa! ;-) Fammi sapere se questa versione è corretta o se vedi altri problemi.


Bel lavoro! Sfortunatamente, y [n] = y [n-2] * b + x [n-1] non è effettivamente funzionalmente equivalente a y [n] = y [n-1] * b + x [n] con latenza. La forma di una funzione di trasferimento IIR si presenta in questo modo: y [n] = x [n] * b0 + x [n-1] * b1 - y [n-1] * a1 - y [n-2] * a2 e così via. Il modulo imposta b0 e a1 su 0 e utilizza invece b1 e a2. Tuttavia, quella trasformazione in realtà produce un filtro molto diverso. Se esistesse un modo per calcolare un filtro con il primo denominatore (a1) impostato su zero, entrambe le soluzioni funzionerebbero perfettamente.
Marcus10110,

Bene, devi capire correttamente il problema del "ritardo introdotto". Ad esempio, un filtro di "elaborazione del flusso di dati" dovrebbe semplicemente inoltrare il suo input poiché y [n] = x [n] funzionerebbe correttamente se producesse y [n] = x [n-1] come output. L'uscita è appena ritardata di 1 ciclo (ad es. L'indice di uscita è compensato da un valore fisso relativo a tutti gli indici di ingresso)! Nel nostro esempio questo significa che la tua funzione ha y[n+l] = y[n-1] * b + x[n]un valore fisso per il ritardo lche può essere riscritto y[n] = y[n-1-l] * b + x[n-l]e per l = 1 questo è y[n] = y[n-2] * b + x[n-1].
SDwarfs,

Per il tuo filtro IIR più complesso dovresti fare lo stesso: y[n+l] = x[n] * b0 + x[n-1] * b1 - y[n-1] * a1 - y[n-2] * a2=> y[n] = x[n-l]*b0 + x[n-1-l] * b1 - y[n-1-l] * a1 - y[n-2-l]*a2. Supponendo che sia possibile eseguire tutte e tre le moltiplicazioni in parallelo (1. fase / 1 ciclo) e che sia necessario eseguire le operazioni per aggiungere i prodotti insieme, sono necessari 2 cicli (1 ciclo: aggiungi / sub risultati primi due prodotti, 1 ciclo: aggiungi / sub il risultato di questi due add / subs), avrai bisogno di 2 cicli aggiuntivi. Quindi l = (3-1) = 2 ti dà y[n]=x[n-2]*b0+x[n-1-2]*b1-y[n-1-2]*a1-y[n-2-2]*a2=>y[n]=x[n-2]*b0+x[n-3]*b1-y[n-3]*a1-y[n-4]*a2
SDwarfs il

Naturalmente per far funzionare tutto ciò, il tuo FPGA deve essere in grado di fare in parallelo: 4 moltiplicazioni e 3 aggiunte / sottrazioni. Significa che hai bisogno di risorse per 4 moltiplicatori e 3 adder.
SDwarfs,

0

Sì, è possibile eseguire il clock alla frequenza di campionamento.

Una soluzione a questo problema è quella di manipolare l'espressione originale in modo da poter inserire i registri della pipeline, mantenendo la sequenza di output desiderata.

Dato: y [n] = y [n-1] * b1 + x [n];

questo può essere manipolato in: y [n] = y [n-2] * b1 * b1 + x [n-1] * b1 + x [n].

Per verificare che questa sia la stessa sequenza, considera cosa succede ai primi numerosi campioni x [0], x [1], x [2] ecc., Dove prima di x [0] tutti i campioni x, y erano zero.

Per l'espressione originale la sequenza è:

y = x[0],

x[1] +x[0]*b1,

x[2] +x[1]*b1 +x[0]*b1*b1,

x[3] +x[2]*b1 +x[1]*b1*b1 +x[0]*b1*b1*b1, ...

È chiaro che è necessario che b1 <1, altrimenti questo crescerà senza limiti.

Ora considera l'espressione manipolata:

y = x[0],

x[0]*b1 +x[1],

x[0]*b1*b1 +x[1]*b1 +x[2],

x[0]*b1*b1*b1 +x[1]*b1*b1 +x[2]*b1 +x[3], ...

Questa è la stessa sequenza.

Una soluzione hardware nelle primitive della libreria Xilinx richiederebbe due DSP48E in cascata. Consultare la figura 1-1 in UG193 v3.6 per i nomi delle porte e dei registri di seguito. La prima primitiva si sta moltiplicando per b1 e aggiungendo un orologio dopo; il secondo si sta moltiplicando per b1 * b1 e aggiungendo un orologio più tardi. Esiste una latenza della pipeline di 4 clock per questa logica.

- DSP48E # 1

a_port1: = b1; - coefficiente costante, impostare AREG = 1

b_port1: = x; - imposta l'attributo BREG = 1

c_port1: = x; - imposta CREG = 1

- interno a DSP48E # 1

reg_a1 <= a_port1;

reg_b1 <= b_port1;

reg_c1 ​​<= c_port1;

reg_m1 <= reg_a1 * reg_b1;

reg_p1 <= reg_m1 + reg_c1; - uscita del 1 ° DSP48E

- fine di DSP48E # 1

- DSP48E # 2

a_port2: = reg_p2; - imposta l'attributo AREG = 0

                -- this means the output of register reg_p2

                -- directly feeds back to the multiplier

b_port2: = b1 * b1; - costante, impostare BREG = 1

c_port2: = reg_p1; - imposta CREG = 1

- interno a DSP48E # 2

reg_b2 <= b_port2;

reg_c2 <= c_port2;

reg_m2 <= a_port2 * reg_b2;

reg_p2 <= reg_m2 + reg_c2;

- fine di DSP48E # 2

La sequenza su reg_p1:

x [0],

x [1] + x [0] * b1,

x [2] + x [1] * b1,

x [3] + x [2] * b1,

eccetera.

La sequenza su reg_p2 è il risultato desiderato. Interno al 2 ° DSP48E, il registro reg_m2 ha una sequenza:

x [0] * * b1 b1,

x [1] * b1 * b1 + x [0] * b1 * b1 * b1,

x [2] * b1 * b1 + x [1] * b1 * b1 * b1 + x [0] * b1 * b1 * b1 * b1

C'è una bella eleganza in questo risultato. Chiaramente il DSP48E non si moltiplica e non si aggiunge nello stesso clock, ma questo è ciò che richiede l'equazione della differenza. L'equazione della differenza manipolata ci consente di tollerare i registri M e P nel DSP48E e il clock alla massima velocità.

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.