Exemple de code pour les filtres FIR / IIR en VHDL?

11

J'essaie de commencer avec DSP dans ma carte Spartan-3. J'ai fait une carte AC97 avec une puce d'une ancienne carte mère, et jusqu'à présent, je l'ai fait pour faire de l'ADC, multiplier les échantillons pour un nombre <1 (diminuer le volume) puis DAC.

Maintenant, j'aimerais faire quelques trucs DSP de base, comme un filtre passe-bas, passe-haut, etc. Mais je suis vraiment confus à propos de la représentation numérique (entiers? Point fixe? Q0.15? Débordement ou saturation?).

Je veux juste un exemple de code d'un filtre simple réel pour me lancer. Pas de haute efficacité, rapide ou quelque chose comme ça. Juste le filtre théorique implémenté en VHDL.

J'ai cherché mais je trouve juste des formules théoriques - je comprends, ce que je ne comprends pas, c'est comment traiter les échantillons audio 16 bits, 48 ​​kHz signés que je reçois de l'ADC. J'utilise ces bibliothèques: http://www.vhdl.org/fphdl/ . Si je multiplie mes échantillons par 0,5, 0,25, etc., je peux entendre la différence. Mais un filtre plus grand ne me donne que du bruit.

Merci.

hjf
la source
2
Bien que je sois tout à fait prêt à utiliser tout ce que vous avez sous la main pour apprendre des choses, je voudrais souligner que la création de filtres audio dans un FPGA n'est pas un moyen très efficace ou rentable de le faire. Donc, si vous faites un vrai projet, je recommanderais plutôt d'utiliser un DSP à faible coût. Exceptions: lorsque vous faites un nombre impie de canaux audio en même temps, ou que vous faites des FIR avec un nombre absurde de tapotements.

Réponses:

8

Il semble que vous devez d'abord comprendre les aspects DSP, puis effectuer une implémentation dans FPGA.

  • Triez le DSP en C, Matlab, Excel ou n'importe où ailleurs
  • Essayez de penser comment vous allez transférer ce que vous avez appris de cela dans FPGA-land
  • Découvrez que vous avez émis des hypothèses sur l'implémentation qui ne fonctionne pas bien (comme l'utilisation de virgule flottante par exemple)
  • Revenez en arrière et mettez à jour votre contenu DSP hors ligne pour en tenir compte.
  • Itérer n fois :)

Concernant les types de données, vous pouvez très bien utiliser des entiers.

voici un exemple de code pour vous aider à démarrer. Notez qu'il manque beaucoup de problèmes réels (par exemple, réinitialisation, gestion des débordements) - mais j'espère que c'est instructif:

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;
Martin Thompson
la source
Merci pour votre réponse. C'est plus ou moins ce que j'ai fait, mais j'ai des problèmes avec la représentation numérique. Mon ADC me donne des valeurs de -32k à + 32k (signé 16 bits). J'ai aussi le problème de la constante du filtre - comment puis-je représenter cela? Et le résultat de la multiplication entre l'échantillon et la constante? C'est ce qui me déroute le plus.
hjf
@hjf - ce ne sont que des entiers. Tant que tout reste dans les 32 bits, vous êtes OK. SI vous avez besoin de plus de largeur que cela, vous pouvez utiliser des vecteurs UNSIGNED ou SIGNED aussi larges que vous le souhaitez. Ou utilisez les types fixed_point de VHDL2008 (voir ici: vhdl.org/fphdl )
Martin Thompson
5

Le filtre FIR passe-bas le plus simple que vous pouvez essayer est y (n) = x (n) + x (n-1). Vous pouvez l'implémenter assez facilement en VHDL. Vous trouverez ci-dessous un schéma fonctionnel très simple du matériel que vous souhaitez implémenter.

Schéma fonctionnel d'un filtre passe-bas simple

Selon la formule, vous avez besoin des échantillons ADC actuels et précédents afin d'obtenir la sortie appropriée. Ce que vous devez faire est de verrouiller les échantillons ADC entrants sur le front descendant de l'horloge et d'effectuer les calculs appropriés sur le front montant afin d'obtenir la sortie appropriée. Étant donné que vous ajoutez deux valeurs 16 bits ensemble, il est possible que vous vous retrouviez avec une réponse 17 bits. Vous devez stocker l'entrée dans des registres 17 bits et utiliser un additionneur 17 bits. Votre sortie, cependant, sera les 16 bits inférieurs de la réponse. Le code pourrait ressembler à ceci mais je ne peux pas garantir qu'il fonctionnera complètement puisque je ne l'ai pas testé, et encore moins synthétisé.

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

Comme vous pouvez le voir, vous pouvez utiliser cette idée générale pour ajouter des formules plus compliquées, telles que celles avec des coefficients. Des formules plus compliquées, comme les filtres IIR, peuvent nécessiter l'utilisation de variables pour obtenir une logique d'algorithme correcte. Enfin, un moyen facile de contourner les filtres qui ont des nombres réels comme coefficients est de trouver un facteur d'échelle afin que tous les nombres finissent par être aussi proches que possible des nombres entiers. Votre résultat final devra être revu à la baisse par le même facteur pour obtenir le résultat correct.

J'espère que cela peut vous être utile et vous aider à faire bouger les choses.

* Ceci a été modifié afin que le verrouillage des données et le verrouillage de sortie soient dans des processus distincts. Utilisation également de types signés au lieu de std_logic_vector. Je suppose que votre entrée ADC va être un signal std_logic_vector.

dhsieh2
la source
2
Les processus qui déclenchent les deux bords (comme vous l'avez décrit) sont très peu susceptibles de synthétiser
Martin Thompson
@Martin Je suppose que vous en savez beaucoup plus sur les FPGA que moi, mais j'ai verrouillé les données entrantes sur le front descendant et verrouillé la sortie sur le front montant pour une affectation de classe, donc j'ai pensé que cela aurait fonctionné. Pouvez-vous expliquer pourquoi de tels processus ne fonctionnent pas?
dhsieh2
3
Cela fonctionnera bien dans un simulateur. Les synthétiseurs s'étoufferont cependant (selon mon expérience) car les bascules de l'appareil ne peuvent cadencer que sur un bord.
Martin Thompson
@ dhsieh2 Merci, c'est le genre de réponse que je cherchais. Une autre question, comment pourrais-je le faire si j'utilisais des nombres signés (mon ADC me donne des valeurs entre -32k et + 32k).
hjf
4
@Martin J'horloge tout le temps des deux fronts d'horloge dans les FPGA Xilinx, pas de problème. Vous ne pouvez pas synchroniser le même FF sur les deux bords. Lorsque vous regardez la sortie de l'analyseur de synchronisation, cela montre clairement que vous faites des bords opposés et ajuste le budget de synchronisation en conséquence.
5

Un autre extrait de code simple (juste les tripes). Remarque Je n'ai pas écrit le VHDL directement, j'ai utilisé MyHDL pour générer le 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;

circuit synthétisé

Il s'agit d'une mise en œuvre directe. Il faudra des multiplicateurs. La synthèse de ce circuit, ciblée pour un Altera Cyclone III, n'a pas utilisé de multiplicateurs explicites mais a nécessité 350 éléments logiques.

Ceci est un petit filtre FIR et aura la réponse suivante (pas si grande) mais devrait être utile comme exemple.

réponse du filtre

De plus, j'ai quelques exemples, ici et ici , qui pourraient être utiles.

En outre, votre question semble demander: "quelle est la représentation appropriée en virgule fixe?" Souvent, lors de la mise en œuvre des fonctions DSP, la représentation en virgule fixe est utilisée, car elle simplifie l'analyse des filtres. Comme mentionné, le point fixe est juste un arthimétique entier. L'implémentation réelle fonctionne simplement avec des entiers, mais notre représentation reçue est fractionnée.
Des problèmes surviennent généralement lors de la conversion de l'implémentation de l'implémentation (virgule fixe) en / en conception flottante.

Je ne sais pas dans quelle mesure les types VHDL à virgule fixe et à virgule flottante sont pris en charge. Ils fonctionneront bien en simulation mais je ne sais pas s'ils vont synthétiser avec la plupart des outils de synthèse. J'ai créé une question distincte pour cela.

Christopher Felton
la source
3

OpenCores a un certain nombre d'exemples DSP, IIR et FIR, y compris BiQuad. Vous devrez vous inscrire pour télécharger les fichiers.

edit
Je comprends le commentaire de Kortuk sur les liens morts, et en effet, si le lien vers OpenCores meurt, la réponse deviendra inutile. Je suis convaincu que cela ne se produira pas; mon lien est générique, et il ne mourra que si le domaine OpenCores complet disparaît.
J'ai essayé de chercher des exemples que je pourrais utiliser pour cette réponse, mais ils sont tous trop longs pour être représentés ici. Je vais donc m'en tenir à mes conseils pour vous inscrire au site vous-même (j'ai dû déménager à New York, parce que ma ville natale n'était pas acceptée) et jeter un œil au code qui y est présenté.

stevenvh
la source
Comme pour tout, les liens se brisent. Nous avons discuté auparavant qu'un lien à lui seul ne répond pas. Pouvez-vous apporter une partie de ce qui existe et faire une réponse charnue qui a ce lien comme référence pour en savoir plus?
Kortuk
@Kortuk - Je voulais le faire hier. Je me suis inscrit hier avec des opencores pour obtenir des détails, mais ils ont besoin de quelques jours pour penser s'ils
m'auront
content de l'entendre, je me demandais honnêtement si quelque chose vous avait gêné. Au plaisir d'en entendre plus.
Kortuk
1

J'ai essayé d'implémenter des scripts pour l'implémentation authomatique des filtres IIR, où vous pouvez définir si la conception doit être aussi rapide que possible (pour que chaque multiplication soit effectuée avec un multiplicateur dédié) ou aussi petite que possible (pour que chaque multiplicateur soit réutilisé).

Les sources ont été publiées sur alt.sources comme "Implémentation comportementale mais synthétisable des filtres IIR en VHDL" (vous pouvez également la trouver dans les archives google: https://groups.google.com/group/alt.sources/msg/c8cf038b9b8ceeec ? dmode = source )

Les publications sur alt.sources sont au format "shar", vous devez donc enregistrer le message sous forme de texte et le dés-partager (avec l'utilitaire "unshar") pour obtenir les sources.

WZab
la source
0

Que dis-tu de ça? https://github.com/MauererM/VIIRF

Il implémente un filtre IIR basé sur biquad (SOS, sections de second ordre) qui prend en charge l'implémentation en virgule fixe. Il comprend également des scripts Python pour la conception et la vérification du filtre. Il n'utilise pas de constructions FPGA spécifiques au fournisseur et vous pouvez choisir le compromis entre une utilisation rapide et faible.

KeyNuts
la source