É possível criar um filtro IIR em um FPGA com clock na frequência de amostra?


9

Esta pergunta é sobre a implementação de um filtro IIR em um FPGA com fatias DSP, com critérios muito específicos.

Digamos que você esteja criando um filtro sem toques para a frente e apenas um toque para trás, com esta equação:

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

(veja a imagem)

Tome a fatia DSP48A1 do Xilinx como exemplo - a maioria das fatias IP DSP rígidas são semelhantes.

Digamos que você receba dados analógicos em 1 amostra por relógio. Gostaria de criar um filtro IIR que seja executado de forma síncrona no relógio de amostra.

O problema é que, para executar a fatia DSP na taxa máxima, você não pode multiplicar E adicionar no mesmo ciclo. Você precisa ter um registro de pipeline entre esses componentes.

Portanto, se você tiver 1 nova amostra a cada relógio, precisará produzir 1 saída por relógio. No entanto, você precisa dos relógios de saída 2 anteriores para poder produzir um novo neste design.

A solução óbvia é processar os dados a uma taxa de clock dupla ou desativar o registro do pipeline para que você possa multiplicar e adicionar no mesmo ciclo.

Infelizmente, se você estiver amostrando com a taxa de clock máxima da fatia DSP totalmente em pipeline, nenhuma dessas soluções será possível. Existe alguma outra maneira de construir isso?

(Pontos de bônus se você pode criar um filtro IIR que opere na metade da taxa de amostragem, usando qualquer número de fatias DSP)

O objetivo seria executar um filtro de compensação para um 1 GSPS ADC em um Xilinx Artix FPGA. Suas fatias DSP podem rodar pouco mais de 500 MHz quando totalmente em pipeline. Se houver uma solução para 1 amostra por relógio, eu gostaria de tentar escalar a solução para 2 amostras por relógio. Tudo isso é muito fácil com um filtro FIR.

exemplo de filtro IIR com feedback único


11
Só para esclarecer, não há razão para que você não tenha uma saída por ciclo de clock com o método de pipeline, certo? Você está tentando minimizar a latência para um ciclo de relógio em vez de dois, certo? Dependendo da sua situação, se você estiver usando um número inteiro para b1, poderá converter a multiplicação em um complemento gigante, incluindo x [n].
Horta #

certo - como existe uma entrada por relógio, é preciso haver uma saída por relógio. latência também não é um problema. a fatia DSP possui apenas um somador de 2 entradas e os toques geralmente são números muito grandes; portanto, você não pode adicionar tempos b1 em 1 ciclo de relógio. o limite principal é que a saída precisa ser realimentada em 1 relógio, mas são necessários 2 relógios para produzir.
Marcus10110 /

11
Acho que você ainda está entendendo mal como funciona um pipeline. Um pipeline potencialmente aumenta a latência, mas permite que você obtenha 1 saída para cada entrada em cada ciclo de clock. Agora, o resultado é agora 2 relógios depois, e não o ideal 1 relógio depois. A entrada seria a seguinte sequência: x [0], x [1], x [2], x [3], x [4] enquanto a saída seria no mesmo intervalo de tempo y [-2], y [-1], y [0], y [1], y [2]. Você não está perdendo nenhuma amostra. Além disso, você está em um FPGA; portanto, se você quiser fazer mais trabalho do que os pipelines DSP foram projetados, utilize o fpga para paralelizar a carga de trabalho.
horta #

Esse DSP é capaz de fazer uma multiplicação por fusão acumulada em um ciclo. Não está claro para mim, no entanto, se a saída de uma fatia DSP pode ser conectada à sua própria entrada com feedback em um único ciclo.
jbarlow

horta - você está certo sobre pipelining em geral, mas o problema é que a guia b1, neste caso, tem feedback - o que significa que um estágio no pipeline depende da saída do valor anterior. se sempre são necessários dois relógios para produzir a próxima saída da saída anterior, não há como produzir 1 saída por relógio, independentemente da latência adicionada. jbarlow - você está certo, a fatia DSP tem uma opção de fusão de 1 ciclo. No entanto, não pode ser executado rápido o suficiente neste caso. adicionando o registro M (consulte a folha de dados), você pode alcançar 500 MHz. No entanto, não é possível multiplicar e adicionar no mesmo clk.
Marcus10110 12/12

Respostas:


3

Ainda não trabalhei com filtros IIR, mas se você precisar apenas calcular a equação fornecida

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

uma vez por ciclo de CPU, você pode usar pipelining.

Em um ciclo, você faz a multiplicação e, em um ciclo, precisa fazer a soma para cada amostra de entrada. Isso significa que seu FPGA deve ser capaz de fazer a multiplicação em um ciclo quando cronometrado na taxa de amostragem especificada! Então você só precisará fazer a multiplicação da amostra atual E o somatório do resultado da multiplicação da última amostra em paralelo. Isso causa um atraso de processamento constante de 2 ciclos.

Ok, vamos dar uma olhada na fórmula e criar um pipeline:

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

O código do seu pipeline pode ficar assim:

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

Observe que todos os três comandos precisam ser executados em paralelo e que a "saída" na segunda linha usa a saída do último ciclo do relógio!

Como não trabalhei muito com o Verilog, a sintaxe desse código pode estar errada (por exemplo, falta de largura de bit dos sinais de entrada / saída; sintaxe de execução para multiplicação). No entanto, você deve ter a ideia:

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: Talvez algum programador experiente da Verilog possa editar esse código e remover esse comentário e o comentário acima do código posteriormente. Obrigado!

PPS: Caso seu fator "b1" seja uma constante fixa, você poderá otimizar o design implementando um multiplicador especial que apenas recebe uma entrada escalar e calcula apenas "tempos b1".

Resposta a: "Infelizmente, isso é realmente equivalente a y [n] = y [n-2] * b1 + x [n]. Isso ocorre devido ao estágio extra do pipeline." como comentário para a versão antiga da resposta

Sim, isso foi realmente adequado para a seguinte versão antiga (INCORRETA !!!):

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

Espero corrigir esse bug agora, atrasando os valores de entrada também em um segundo 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

Para garantir que funcione corretamente dessa vez, vejamos o que acontece nos primeiros ciclos. Observe que os 2 primeiros ciclos produzem mais ou menos lixo (definido), pois nenhum valor de saída anterior (por exemplo, y [-1] == ??) está disponível. O registro y é inicializado com 0, o que equivale a assumir que y [-1] == 0.

Primeiro 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

Segundo 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]

Terceiro 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]

Podemos ver que, começando com cylce n = 2, obtemos a seguinte saída:

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

que é equivalente 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

Como mencionado acima, introduzimos um atraso adicional de l = 1 ciclos. Isso significa que sua saída y [n] está atrasada por lag l = 1. Isso significa que os dados de saída são equivalentes, mas estão atrasados ​​em um "índice". Para ser mais claro: os dados de saída atrasados ​​são 2 ciclos, pois é necessário um ciclo de relógio (normal) e 1 ciclo de relógio adicional (atraso l = 1) é adicionado para o estágio intermediário.

Aqui está um esboço para representar graficamente como os dados fluem:

esboço do fluxo de dados

PS: Obrigado por dar uma olhada no meu código. Então eu aprendi algo também! ;-) Deixe-me saber se esta versão está correta ou se você encontrar mais problemas.


Bom trabalho! Infelizmente, y [n] = y [n-2] * b + x [n-1] não é funcionalmente equivalente a y [n] = y [n-1] * b + x [n] com latência. A forma de uma função de transferência IIR é realmente assim: y [n] = x [n] * b0 + x [n-1] * b1 - y [n-1] * a1 - y [n-2] * a2 e assim por diante. Seu formulário define b0 e a1 como 0 e, em vez disso, usa b1 e a2. No entanto, essa transformação realmente produz um filtro muito diferente. Se houvesse uma maneira de calcular um filtro com o primeiro denominador (a1) definido como zero, as duas soluções funcionariam perfeitamente.
Marcus10110

Bem, você precisa entender o problema "atraso introduzido" corretamente. Como exemplo, um filtro "processamento de fluxo de dados" deve apenas encaminhar sua entrada, pois y [n] = x [n] funcionaria corretamente se produzir y [n] = x [n-1] como saída. A saída é apenas atrasada em 1 ciclo (por exemplo, o índice de saída é compensado por um valor fixo em relação a todos os índices de entrada)! No nosso exemplo, isso significa que sua função está y[n+l] = y[n-1] * b + x[n]com um valor fixo para o atraso lque pode ser reescrito para y[n] = y[n-1-l] * b + x[n-l]e para l = 1 é esse y[n] = y[n-2] * b + x[n-1].
SDwarfs 30/10

Para o seu filtro IIR mais complexo, você precisaria fazer o mesmo: 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. Supondo que você possa fazer todas as três multiplicações em paralelo (1. estágio / 1 ciclo) e precisar adicionar os produtos, você precisa de 2 ciclos (1 ciclo: adicionar / sub primeiros dois resultados do produto, 1 ciclo: adicionar / sub o resultado desses dois add / subs), você precisará de 2 ciclos adicionais. Então l = (3-1) = 2 dando a você 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

Obviamente, para que isso funcione, seu FPGA deve ser capaz de fazer paralelamente: 4 multiplicações e 3 adições / subtrações. Isso significa que você precisa de recursos para 4 multiplicadores e 3 somadores.
SDwarfs 30/10

0

Sim, você pode cronometrar na frequência da amostra.

Uma solução para esse problema é manipular a expressão original para que os registros de pipeline possam ser inseridos, mantendo a sequência de saída desejada.

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

isso pode ser manipulado em: y [n] = y [n-2] * b1 * b1 + x [n-1] * b1 + x [n].

Para verificar se essa é a mesma sequência, considere o que acontece com as primeiras amostras x [0], x [1], x [2] etc., onde antes de x [0] todas as amostras x, y eram zero.

Para a expressão original, a sequência é:

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, ...

É claro que é necessário que b1 <1, caso contrário, este irá crescer sem limites.

Agora considere a expressão manipulada:

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], ...

Esta é a mesma sequência.

Uma solução de hardware nas primitivas da biblioteca Xilinx precisaria de dois DSP48E em cascata. Consulte a figura 1-1 no UG193 v3.6 para obter os nomes de porta e registro abaixo. O primeiro primitivo está multiplicando por b1 e adicionando um relógio mais tarde; o segundo é multiplicado por b1 * b1 e adicionando um relógio mais tarde. Há uma latência de pipeline de 4 relógios para essa lógica.

- DSP48E # 1

a_port1: = b1; - coeficiente constante, defina AREG = 1

b_port1: = x; - definir atributo BREG = 1

c_port1: = x; - defina CREG = 1

- interno ao 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; - saída do 1º DSP48E

- fim do DSP48E # 1

- DSP48E # 2

a_port2: = reg_p2; - definir atributo AREG = 0

                -- this means the output of register reg_p2

                -- directly feeds back to the multiplier

b_port2: = b1 * b1; - constante, defina BREG = 1

c_port2: = reg_p1; - defina CREG = 1

- interno ao DSP48E # 2

reg_b2 <= b_port2;

reg_c2 <= c_port2;

reg_m2 <= a_port2 * reg_b2;

reg_p2 <= reg_m2 + reg_c2;

- fim do DSP48E # 2

A sequência em reg_p1:

x [0],

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

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

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

etc.

A sequência em reg_p2 é o resultado desejado. Interno ao 2º DSP48E, o registro reg_m2 possui uma sequência:

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

Há uma boa elegância nesse resultado. Claramente, o DSP48E não se multiplica e adiciona no mesmo relógio, mas é isso que a equação da diferença está exigindo. A equação da diferença manipulada nos permite tolerar os registros M e P no DSP48E e o relógio em velocidade máxima.

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.