Esempio di codice per i filtri FIR / IIR in VHDL?


11

Sto cercando di iniziare con DSP nella mia scheda Spartan-3. Ho realizzato una scheda AC97 con un chip da una vecchia scheda madre e finora ho ottenuto ADC, moltiplicando i campioni per un numero <1 (riduzione del volume) e quindi DAC.

Ora vorrei fare alcune cose DSP di base, come un filtro passa-basso, passa-alto ecc. Ma sono davvero confuso sulla rappresentazione numerica (numeri interi? Punto fisso? Q0.15? Overflow o saturazione?).

Voglio solo un po 'di codice di esempio di un vero e proprio semplice filtro per farmi iniziare. Niente ad alta efficienza, veloce o niente del genere. Solo il filtro teorico implementato in VHDL.

Ho cercato ma trovo solo formule teoriche - lo capisco, quello che non capisco è come elaborare i campioni audio firmati a 16 bit, 48KHz che sto ricevendo dall'ADC. Ho usato queste librerie: http://www.vhdl.org/fphdl/ . Se moltiplico i miei campioni per 0,5, 0,25, ecc., Posso sentire la differenza. Ma un filtro più grande mi dà solo rumore.

Grazie.


2
Mentre sono tutto per usare tutto ciò che hai a portata di mano per imparare cose, vorrei sottolineare che fare filtri audio in un FPGA non è un modo molto efficiente o economico per farlo. Quindi, se fai un vero progetto, ti consiglio di utilizzare un DSP a basso costo. Eccezioni: quando stai facendo un numero empio di canali audio allo stesso tempo, o stai facendo FIR con un numero assurdo di tocchi.

Risposte:


8

Sembra che tu debba prima capire gli aspetti DSP, quindi realizzare un'implementazione in FPGA.

  • Ordina il DSP in C, Matlab, Excel o in qualsiasi altro luogo
  • Prova a pensare a come trasferirai ciò che hai imparato da questo in FPGA-land
  • Scopri di aver fatto alcune ipotesi sull'implementazione che non funziona bene (come ad esempio l'uso del virgola mobile)
  • Torna indietro e aggiorna i tuoi contenuti DSP offline per tenerne conto.
  • Iterate n volte :)

Per quanto riguarda i tipi di dati, puoi usare numeri interi bene.

ecco un po 'di codice di esempio per iniziare. Tieni presente che mancano molti problemi del mondo reale (ad esempio ripristino, gestione degli overflow), ma si spera sia istruttivo:

library ieee;
use ieee.std_logic_1164.all;
entity simple_fir is
    generic (taps : integer_vector); 
    port (
        clk      : in  std_logic;
        sample   : in  integer;
        filtered : out integer := 0);
end entity simple_fir;
----------------------------------------------------------------------------------------------------------------------------------
architecture a1 of simple_fir is
begin  -- architecture a1
    process (clk) is
        variable delay_line : integer_vector(0 to taps'length-1) := (others => 0);
        variable sum : integer;
    begin  -- process
        if rising_edge(clk) then  -- rising clock edge
            delay_line := sample & delay_line(0 to taps'length-2);
            sum := 0;
            for i in 0 to taps'length-1 loop
                sum := sum + delay_line(i)*taps(taps'high-i);
            end loop;
            filtered <= sum;
        end if;
    end process;
end architecture a1;
----------------------------------------------------------------------------------------------------------------------------------
-- testbench
----------------------------------------------------------------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
entity tb_simple_fir is
end entity tb_simple_fir;
architecture test of tb_simple_fir is
    -- component generics
    constant lp_taps : integer_vector := ( 1, 1, 1, 1, 1);
    constant hp_taps : integer_vector := (-1, 0, 1);

    constant samples : integer_vector := (0,0,0,0,1,1,1,1,1);

    signal sample   : integer;
    signal filtered : integer;
    signal Clk : std_logic := '1';
    signal finished : std_logic;
begin  -- architecture test
    DUT: entity work.simple_fir
        generic map (taps => lp_taps)  -- try other taps in here
        port map (
            clk      => clk,
            sample   => sample,
            filtered => filtered);

    -- waveform generation
    WaveGen_Proc: process
    begin
        finished <= '0';
        for i in samples'range loop
            sample <= samples(i);
            wait until rising_edge(clk);
        end loop;
        -- allow pipeline to empty - input will stay constant
        for i in 0 to 5 loop
            wait until rising_edge(clk);
        end loop;
        finished <= '1';
        report (time'image(now) & " Finished");
        wait;
    end process WaveGen_Proc;

    -- clock generation
    Clk <= not Clk after 10 ns when finished /= '1' else '0';
end architecture test;

Grazie per la tua risposta. È più o meno quello che ho fatto, ma sto riscontrando alcuni problemi con la rappresentazione numerica. Il mio ADC mi fornisce valori compresi tra -32k e + 32k (con segno a 16 bit). Ho anche il problema della costante di filtro - come posso rappresentarlo? E il risultato di moltiplicare tra il campione e la costante? Questo è ciò che mi confonde di più.
hjf

@hjf - sono solo numeri interi. Fintanto che tutto rimane entro 32 bit, va tutto bene. Se hai bisogno di una larghezza maggiore rispetto a quella, puoi utilizzare i vettori UNSIGNED o SIGNED più larghi che desideri. Oppure usa i tipi fixed_point da VHDL2008 (vedi qui: vhdl.org/fphdl )
Martin Thompson,

5

Il filtro FIR passa basso più semplice che puoi provare è y (n) = x (n) + x (n-1). Puoi implementarlo abbastanza facilmente in VHDL. Di seguito è riportato uno schema a blocchi molto semplice dell'hardware che si desidera implementare.

Schema a blocchi per un filtro passa basso semplice

Secondo la formula, sono necessari i campioni ADC attuali e precedenti per ottenere l'output appropriato. Quello che dovresti fare è agganciare i campioni ADC in arrivo sul fronte di discesa del clock ed eseguire i calcoli appropriati sul fronte di salita per ottenere l'output appropriato. Dato che stai sommando due valori a 16 bit, è possibile che tu abbia una risposta a 17 bit. È necessario memorizzare l'input in registri a 17 bit e utilizzare un sommatore a 17 bit. Il tuo output, tuttavia, saranno i 16 bit inferiori della risposta. Il codice potrebbe assomigliare a questo, ma non posso garantire che funzionerà completamente poiché non l'ho testato, e tanto meno sintetizzato.

IEEE.numeric_std.all;
...
    signal x_prev, x_curr, y_n: signed(16 downto 0);
    signal filter_out: std_logic_vector(15 downto 0);
...
process (clk) is
begin
    if falling_edge(clk) then
        --Latch Data
        x_prev <= x_curr;
        x_curr <= signed('0' & ADC_output); --since ADC is 16 bits
    end if;
end process;

process (clk) is
begin
    if rising_edge(clk) then
        --Calculate y(n)
        y_n <= x_curr + x_prev;
    end if;
end process;

filter_out <= std_logic_vector(y_n(15 downto 0));  --only use the lower 16 bits of answer

Come puoi vedere, puoi usare questa idea generale per aggiungere formule più complicate, come quelle con coefficienti. Formule più complicate, come i filtri IIR, potrebbero richiedere l'uso di variabili per ottenere la logica dell'algoritmo corretta. Infine, un modo semplice per aggirare i filtri che hanno numeri reali come coefficienti è quello di trovare un fattore di scala in modo che tutti i numeri finiscano per essere il più vicino possibile ai numeri interi. Il risultato finale dovrà essere ridimensionato dello stesso fattore per ottenere il risultato corretto.

Spero che questo possa esserti utile e aiutarti a far rotolare la palla.

* Questo è stato modificato in modo che il latching dei dati e il latching dell'output siano in processi separati. Utilizza anche tipi firmati anziché std_logic_vector. Suppongo che il tuo input ADC sarà un segnale std_logic_vector.


2
È molto improbabile che i processi che si innescano su entrambi i bordi (come hai descritto) possano essere sintetizzati
Martin Thompson,

@Martin Sto assumendo che tu sappia molto di più sugli FPGA di me, ma ho bloccato i dati in entrata sul fronte di discesa e l'output bloccato sul fronte di salita per un compito di classe, quindi ho pensato che avrebbe funzionato. Puoi spiegare perché tali processi non funzionano?
dhsieh2,

3
Funzionerà bene in un simulatore. I sintetizzatori la soffoceranno però (nella mia esperienza) poiché le infradito nel dispositivo possono solo cronometrare su un bordo.
Martin Thompson,

@ dhsieh2 Grazie, questo è il tipo di risposta che stavo cercando. Un'altra domanda, come farei se usassi i numeri firmati (il mio ADC mi dà valori compresi tra -32k e +32k).
hjf,

4
@Martin Su Xilinx FPGA, ogni volta che suono su entrambi i bordi dell'orologio, non c'è problema. Non è possibile sincronizzare lo stesso FF su entrambi i bordi. Quando si guarda all'output dell'analizzatore di temporizzazione, in realtà risulta molto chiaro che si stanno facendo bordi opposti e si adegua di conseguenza il budget di temporizzazione.

5

Un altro semplice frammento di codice (solo il coraggio). Nota Non ho scritto direttamente il VHDL, ho usato MyHDL per generare il VHDL.

-- VHDL code snip
architecture MyHDL of sflt is

type t_array_taps is array(0 to 6-1) of signed (15 downto 0);
signal taps: t_array_taps;

begin

SFLT_RTL_FILTER: process (clk) is
    variable sum: integer;
begin
    if rising_edge(clk) then
        sum := to_integer(x * 5580);
        sum := to_integer(sum + (taps(0) * 5750));
        sum := to_integer(sum + (taps(1) * 6936));
        sum := to_integer(sum + (taps(2) * 6936));
        sum := to_integer(sum + (taps(3) * 5750));
        sum := to_integer(sum + (taps(4) * 5580));
        taps(0) <= x;
        for ii in 1 to 5-1 loop
            taps(ii) <= taps((ii - 1));
        end loop;
        y <= to_signed(sum, 16);
    end if;
end process SFLT_RTL_FILTER;

end architecture MyHDL;

circuito sintetizzato

Questa è un'implementazione diretta. Richiederà moltiplicatori. La sintesi di questo circuito, mirata ad un Altera Cyclone III, non utilizzava moltiplicatori espliciti ma richiedeva 350 elementi logici.

Questo è un piccolo filtro FIR e avrà la seguente risposta (non eccezionale) ma dovrebbe essere utile come esempio.

risposta del filtro

Inoltre ho un paio di esempi, qui e qui , che potrebbero essere utili.

Inoltre, la tua domanda sembra porsi: "qual è la rappresentazione in virgola fissa appropriata?" Spesso quando si implementano le funzioni DSP, viene utilizzata la rappresentazione in virgola fissa, poiché semplifica l'analisi dei filtri. Come accennato, il punto fisso è solo arthimetic intero. L'implementazione effettiva funziona semplicemente con numeri interi ma la nostra rappresentazione precedente è frazionaria.
In genere si verificano problemi durante la conversione da numeri interi di implementazione (punto fisso) a virgola mobile di progettazione.

Non so quanto siano supportati i tipi VHDL a virgola fissa e virgola mobile. Funzioneranno bene nella simulazione ma non so se si sintetizzeranno con la maggior parte degli strumenti di sintesi. Ho creato una domanda separata per questo.


3

OpenCores ha un numero di esempi DSP, IIR e FIR, incluso BiQuad. Dovrai registrarti per scaricare i file.

modifica
Comprendo il commento di Kortuk sui link non funzionanti e, in effetti, se il link a OpenCores muore la risposta diventerà inutile. Sono abbastanza fiducioso che ciò non accadrà; il mio link è generico e morirà solo se il dominio OpenCores completo scomparisse.
Ho cercato di cercare alcuni esempi che potrei usare per questa risposta, ma sono tutti troppo lunghi per essere rappresentati qui. Quindi mi atterrò al mio consiglio di registrarmi per il sito (dovevo trasferirmi a New York, perché la mia città natale non era accettata) e dare un'occhiata al codice presentato lì.


Come per tutte le cose, i collegamenti si interrompono. Abbiamo discusso in precedenza che un collegamento da solo non fornisce una risposta. Puoi riportare un po 'di quello che c'è e fare una risposta carnosa che ha quel link come riferimento per saperne di più?
Kortuk,

@Kortuk - Volevo farlo ieri. Mi sono registrato ieri con gli opencores per ottenere alcuni dettagli, ma hanno bisogno di qualche giorno per pensare se mi avranno
Stevenvh,

felice di sentirlo, mi chiedevo onestamente se qualcosa fosse arrivato sulla tua strada. Non vedo l'ora di saperne di più.
Kortuk,

1

Ho cercato di implementare script per l'implementazione automatica dei filtri IIR, in cui è possibile definire se il progetto deve essere il più veloce possibile (quindi ogni moltiplicazione viene eseguita con un moltiplicatore dedicato) o il più piccolo possibile (quindi ogni moltiplicatore viene riutilizzato).

Le fonti sono state pubblicate su alt.sources come "Implementazione comportamentale ma sintetizzabile di filtri IIR in VHDL" (puoi anche trovarlo nell'archivio google: https://groups.google.com/group/alt.sources/msg/c8cf038b9b8ceeec ? dmode = source )

I post in alt.sources sono in formato "shar", quindi è necessario salvare il messaggio come testo e decomprimerlo (con l'utilità "unshar") per ottenere le fonti.


0

Cosa ne pensi di questo? https://github.com/MauererM/VIIRF

Implementa un filtro IIR basato su biquad (SOS, sezioni di secondo ordine) che si occupa dell'implementazione in virgola fissa. Dispone inoltre di script Python per la progettazione e la verifica del filtro. Non utilizza costrutti FPGA specifici del fornitore ed è possibile scegliere il compromesso tra utilizzo ad alta velocità e area bassa.

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.