Exemplo de código para filtros FIR / IIR em VHDL?


11

Estou tentando começar com o DSP na minha placa Spartan-3. Fiz uma placa AC97 com um chip de uma placa-mãe antiga e, até agora, consegui fazer o ADC, multiplicar as amostras por um número <1 (diminuir o volume) e depois o DAC.

Agora eu gostaria de fazer algumas coisas básicas de DSP, como um filtro passa-baixo, passa-alto etc. Mas estou realmente confuso sobre a representação numérica (números inteiros? Ponto fixo? Q0.15? Estouro ou saturação?).

Eu só quero um código de exemplo de um filtro simples real para começar. Sem alta eficiência, rápido ou algo assim. Apenas o filtro teórico implementado em VHDL.

Estive pesquisando, mas apenas encontro fórmulas teóricas - entendo isso, o que não entendo é como processar as amostras de áudio assinadas de 16 bits e 48KHz que estou recebendo do ADC. Eu tenho usado essas bibliotecas: http://www.vhdl.org/fphdl/ . Se eu multiplicar minhas amostras por 0,5, 0,25, etc., posso ouvir a diferença. Mas um filtro maior me dá apenas ruído.

Obrigado.


2
Embora eu seja a favor de usar o que você tem em mãos para aprender coisas, gostaria de ressaltar que a criação de filtros de áudio em um FPGA não é uma maneira muito eficiente ou econômica de fazer isso. Portanto, se você faz um projeto real, recomendo usar um DSP de baixo custo. Exceções: quando você está fazendo um número ímpio de canais de áudio ao mesmo tempo ou está fazendo FIRs com um número absurdo de toques.

Respostas:


8

Parece que você precisa descobrir os aspectos do DSP primeiro e, em seguida, fazer uma implementação no FPGA.

  • Classifique o DSP em C, Matlab, Excel ou em qualquer outro lugar
  • Experimente e pense em como transferir o que aprendeu disso para o FPGA-land
  • Descubra que você fez alguma suposição sobre a implementação que não funciona bem (como o uso de ponto flutuante, por exemplo)
  • Volte e atualize seu material DSP offline para levar isso em consideração.
  • Iterar n vezes :)

Em relação aos tipos de dados, você pode usar números inteiros muito bem.

Aqui está um código de exemplo para você começar. Observe que faltam muitos problemas do mundo real (por exemplo, redefinição, gerenciamento de estouro) - mas espero que seja instrutivo:

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;

Obrigado pela sua resposta. Foi mais ou menos o que fiz, mas estou tendo alguns problemas com a representação numérica. Meu ADC me fornece valores entre -32k e + 32k (assinado com 16 bits). Eu também tenho o problema da constante de filtro - como eu represento isso? E o resultado da multiplicação entre a amostra e a constante? Isso é o que mais me confunde.
Hjf

@ hjf - é tudo apenas números inteiros. Enquanto tudo permanecer dentro de 32 bits, você estará bem. Se você precisar de mais largura do que isso, poderá usar vetores UNSIGNED ou SIGNED da largura que desejar. Ou use os tipos fixed_point de VHDL2008 (ver aqui: vhdl.org/fphdl )
Martin Thompson

5

O filtro FIR passa-baixo mais simples que você pode experimentar é y (n) = x (n) + x (n-1). Você pode implementar isso facilmente no VHDL. Abaixo está um diagrama de blocos muito simples do hardware que você deseja implementar.

Diagrama de blocos para um filtro passa-baixo simples

De acordo com a fórmula, você precisa das amostras atuais e anteriores do ADC para obter a saída apropriada. O que você deve fazer é travar as amostras ADC recebidas na borda descendente do relógio e executar os cálculos apropriados na borda ascendente para obter a saída apropriada. Como você está adicionando dois valores de 16 bits, é possível que você tenha uma resposta de 17 bits. Você deve armazenar a entrada em registradores de 17 bits e usar um somador de 17 bits. Sua saída, no entanto, serão os 16 bits inferiores da resposta. O código pode parecer algo assim, mas não posso garantir que funcione completamente, pois ainda não o testei e muito menos o sintetizei.

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

Como você pode ver, você pode usar essa ideia geral para adicionar fórmulas mais complicadas, como aquelas com coeficientes. Fórmulas mais complicadas, como os filtros IIR, podem exigir o uso de variáveis ​​para corrigir a lógica do algoritmo. Finalmente, uma maneira fácil de contornar os filtros que têm números reais como coeficientes é encontrar um fator de escala para que todos os números acabem o mais próximo possível de números inteiros. Seu resultado final terá que ser reduzido novamente pelo mesmo fator para obter o resultado correto.

Espero que isso possa ser útil para você e ajudá-lo a fazer a bola rolar.

* Isso foi editado para que a trava de dados e a saída saiam em processos separados. Também usando tipos assinados em vez de std_logic_vector. Estou assumindo que sua entrada ADC será um sinal std_logic_vector.


2
Processos que desencadeiam off ambas as bordas (como você descreveu) são muito improvável de sintetizar
Martin Thompson

@ Martin Suponho que você saiba muito mais sobre FPGAs do que eu, mas prendi os dados recebidos na borda descendente e a saída na borda ascendente para uma aula, então pensei que isso teria funcionado. Você pode explicar por que esses processos não funcionam?
dhsieh2

3
Vai funcionar bem em um simulador. Os sintetizadores vão se engasgar com isso (na minha experiência), pois os chinelos no dispositivo só podem funcionar em uma borda.
Martin Thompson

@ dhsieh2 Obrigado, este é o tipo de resposta que eu estava procurando. Outra pergunta, como eu faria isso se estivesse usando números assinados (meu ADC me fornece valores entre -32k e + 32k).
Hjf

4
@ Martin Eu relógio coisas fora de ambas as bordas do relógio o tempo todo nos FPGAs da Xilinx, não há problema. Você simplesmente não pode marcar o mesmo FF nas duas extremidades. Quando você olha para a saída do analisador de temporização, na verdade fica muito claro que você está fazendo arestas opostas e ajusta o orçamento de temporização de acordo.

5

Outro trecho de código simples (apenas a coragem). Observe que não escrevi o VHDL diretamente, usei o MyHDL para gerar o 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 sintetizado

Esta é uma implementação direta. Exigirá multiplicadores. A síntese deste circuito, direcionada para um Altera Cyclone III, não usou multiplicadores explícitos, mas exigiu 350 elementos lógicos.

Este é um filtro FIR pequeno e terá a seguinte resposta (não tão boa), mas deve ser útil como exemplo.

resposta do filtro

Além disso, tenho alguns exemplos, aqui e aqui , que podem ser úteis.

Além disso, sua pergunta parece perguntar: "o que é a representação de ponto fixo apropriada?" Freqüentemente, ao implementar funções DSP, a representação de ponto fixo é usada, porque simplifica a análise dos filtros. Como mencionado, o ponto fixo é apenas arthimético inteiro. A implementação real está simplesmente trabalhando com números inteiros, mas nossa representação recebida é fracionária.
Geralmente, surgem problemas ao converter de número inteiro de implementação (ponto fixo) para / para o ponto flutuante de design.

Não sei até que ponto os tipos de ponto fixo e ponto flutuante VHDL são suportados. Eles funcionarão bem em simulação, mas não sei se sintetizarão com a maioria das ferramentas de síntese. Eu criei uma pergunta separada para isso.


3

O OpenCores possui vários exemplos de DSP, IIR e FIR, incluindo o BiQuad. Você precisará se registrar para baixar os arquivos.

editar
Entendo o comentário de Kortuk sobre links mortos e, de fato, se o link para o OpenCores morrer, a resposta se tornará inútil. Estou bastante confiante de que isso não vai acontecer; meu link é genérico e só morrerá se o domínio completo do OpenCores desaparecer.
Tentei procurar alguns exemplos que eu poderia usar para esta resposta, mas eles são muito longos para serem representados aqui. Portanto, seguirei meu conselho de me registrar no site (tive que me mudar para Nova York, porque minha cidade natal não foi aceita) e dar uma olhada no código apresentado lá.


Como em todas as coisas, os links quebram. Discutimos anteriormente que um link por si só não dá uma resposta. Você pode trazer um pouco do que existe e fazer uma resposta equilibrada que tenha esse link como referência para saber mais?
Kortuk

@ Kortuk - eu queria fazer isso ontem. Registei-me ontem com OpenCores para obter alguns detalhes, mas eles precisam de alguns dias para pensar se eles têm me
stevenvh

feliz em ouvi-lo, eu estava sinceramente me perguntando se algo estava no seu caminho. Ansiosos para ouvir mais sobre isso.
Kortuk

1

Tentei implementar scripts para implementação automática de filtros IIR, onde é possível definir se o design deve ser o mais rápido possível (para que cada multiplicação seja realizada com multiplicador dedicado) ou o menor possível (para que cada multiplicador seja reutilizado).

As fontes foram publicadas em alt.sources como "Implementação comportamental, mas sintetizável, de filtros IIR em VHDL" (você também pode encontrá-lo no arquivo do google: https://groups.google.com/group/alt.sources/msg/c8cf038b9b8ceeec ? dmode = origem )

As postagens no alt.sources estão no formato "shar", então você precisa salvar a mensagem como texto e descompactá-la (com o utilitário "unshar") para obter as fontes.


0

Que tal agora? https://github.com/MauererM/VIIRF

Ele implementa um filtro IIR baseado em biquad (SOS, seções de segunda ordem) que cuida da implementação de ponto fixo. Ele também possui scripts Python para design e verificação do filtro. Ele não usa construções FPGA específicas do fornecedor e você pode escolher a troca entre o uso em alta e a baixa área.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.