Est-il possible de créer un filtre IIR dans un FPGA cadencé à la fréquence d'échantillonnage?

9

Cette question concerne l'implémentation d'un filtre IIR dans un FPGA avec des tranches DSP, avec des critères très spécifiques.

Disons que vous faites un filtre sans taps avant et seulement 1 tap inversé, avec cette équation:

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

(voir image)

Prenez la tranche DSP48A1 de Xilinx comme exemple - la plupart des tranches DSP IP dures sont similaires.

Disons que vous avez des données analogiques entrant à 1 échantillon par horloge. Je voudrais concevoir un filtre IIR qui fonctionne de manière synchrone à l'horloge d'échantillonnage.

Le problème est que pour exécuter la tranche DSP au taux maximum, vous ne pouvez pas multiplier ET ajouter sur le même cycle. Vous devez avoir un registre de pipeline entre ces composants.

Donc, si vous avez 1 nouvel échantillon à chaque horloge, vous devrez produire 1 sortie par horloge. Cependant, vous avez besoin des horloges de sortie précédentes 2 avant de pouvoir en produire une nouvelle dans cette conception.

La solution évidente consiste soit à traiter les données à une fréquence d'horloge double, soit à désactiver le registre de pipeline afin de pouvoir multiplier et ajouter dans le même cycle.

Malheureusement, si vous échantillonnez à la fréquence d'horloge maximale de la tranche DSP entièrement pipelinée, aucune de ces solutions n'est possible. Y a-t-il un autre moyen de construire cela?

(Points bonus si vous pouvez concevoir un filtre IIR qui fonctionne à la moitié du taux d'échantillonnage, en utilisant n'importe quel nombre de tranches DSP)

L'objectif serait d'exécuter un filtre de compensation pour un ADC 1 GSPS dans un FPGA Xilinx Artix. Leurs tranches DSP peuvent fonctionner un peu plus de 500 MHz lorsqu'elles sont entièrement pipelinées. S'il existe une solution pour 1 échantillon par horloge, je voudrais essayer de mettre à l'échelle la solution pour 2 échantillons par horloge. Tout cela est très facile avec un filtre FIR.

exemple de filtre IIR à rétroaction simple

Marcus10110
la source
1
Juste pour clarifier, il n'y a aucune raison pour laquelle vous n'auriez pas une sortie par cycle d'horloge avec la méthode du pipeline, non? Vous essayez de minimiser la latence jusqu'à un cycle d'horloge au lieu de deux, non? Selon votre situation, si vous utilisez un entier pour b1, vous pouvez convertir la multiplication en une addition géante incluant x [n].
horta
à droite - puisqu'il y a une entrée par horloge, il doit y avoir une sortie par horloge. la latence n'est pas un problème non plus. la tranche DSP n'a qu'un additionneur à 2 entrées, et les taps sont généralement de très gros nombres, donc vous ne pouvez pas ajouter b1 fois en 1 cycle d'horloge. la principale limite est que la sortie doit être réinjectée dans 1 horloge, mais il faut 2 horloges pour produire.
Marcus10110
1
Je pense que vous ne comprenez toujours pas comment fonctionne un pipeline. Un pipeline augmente potentiellement la latence, mais vous permet d'obtenir 1 sortie pour chaque entrée à chaque cycle d'horloge. C'est juste que le résultat est maintenant 2 horloges après plutôt que l'horloge idéale 1 après. L'entrée serait la séquence comme ceci: x [0], x [1], x [2], x [3], x [4] tandis que la sortie serait à ce même intervalle de temps y [-2], y [-1], y [0], y [1], y [2]. Vous ne perdez aucun échantillon. De plus, vous êtes sur un FPGA, donc si vous voulez faire plus de travail que ce pour quoi les pipelines DSP sont conçus, utilisez le fpga pour paralléliser la charge de travail.
horta
Ce DSP est capable de faire une multiplication fusionnée accumulée dans un cycle. Cependant, je ne sais pas si la sortie d'une tranche DSP peut être connectée à sa propre entrée avec rétroaction en un seul cycle.
jbarlow
horta - vous avez raison sur le pipelining en général, mais le problème est que l'onglet b1 dans ce cas a un retour - ce qui signifie qu'une étape dans le pipeline dépend de la sortie de la valeur précédente. s'il faut toujours 2 horloges pour produire la sortie suivante à partir de la sortie précédente, il n'y a aucun moyen de produire 1 sortie par horloge, quelle que soit la latence ajoutée. jbarlow - vous avez raison, la tranche DSP a une option fusionnée à 1 cycle. Cependant, il ne peut pas fonctionner assez rapidement dans ce cas. en ajoutant le registre M (voir fiche technique), vous pouvez atteindre 500 MHz, mais vous ne pouvez pas multiplier et ajouter le même clk.
Marcus10110

Réponses:

3

Je n'ai pas encore travaillé avec les filtres IIR, mais si vous avez seulement besoin de calculer l'équation donnée

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

une fois par cycle CPU, vous pouvez utiliser le pipelining.

Dans un cycle, vous effectuez la multiplication et dans un cycle, vous devez faire la sommation pour chaque échantillon d'entrée. Cela signifie que votre FPGA doit être capable de faire la multiplication en un cycle lorsqu'il est cadencé à la fréquence d'échantillonnage donnée! Ensuite, vous aurez seulement besoin de faire la multiplication de l'échantillon actuel ET la somme du résultat de multiplication du dernier échantillon en parallèle. Cela entraînera un décalage de traitement constant de 2 cycles.

Ok, regardons la formule et concevons un pipeline:

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

Votre code de pipeline pourrait ressembler à ceci:

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

Notez que les trois commandes doivent être exécutées en parallèle et que la "sortie" sur la deuxième ligne utilise donc la sortie du dernier cycle d'horloge!

Je n'ai pas beaucoup travaillé avec Verilog, donc la syntaxe de ce code est très probablement erronée (par exemple, largeur de bit manquante des signaux d'entrée / sortie; syntaxe d'exécution pour la multiplication). Cependant, vous devriez avoir l'idée:

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: Peut-être qu'un programmeur Verilog expérimenté pourrait modifier ce code et supprimer ce commentaire et le commentaire au-dessus du code par la suite. Merci!

PPS: dans le cas où votre facteur "b1" est une constante fixe, vous pourriez être en mesure d'optimiser la conception en implémentant un multiplicateur spécial qui ne prend qu'une seule entrée scalaire et calcule les "temps b1" uniquement.

Réponse à: "Malheureusement, cela équivaut en fait à y [n] = y [n-2] * b1 + x [n]. Cela est dû à l'étape supplémentaire du pipeline." comme commentaire à l'ancienne version de la réponse

Oui, c'était en fait correct pour l'ancienne version (INCORRECTE !!!) suivante:

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

J'espère avoir corrigé ce bug maintenant en retardant les valeurs d'entrée, également dans un deuxième registre:

  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

Pour nous assurer que cela fonctionne correctement cette fois, regardons ce qui se passe lors des premiers cycles. Notez que les 2 premiers cycles produisent plus ou moins de déchets (définis), car aucune valeur de sortie précédente (par exemple y [-1] == ??) n'est disponible. Le registre y est initialisé à 0, ce qui revient à supposer que y [-1] == 0.

Premier cycle (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

Deuxième cycle (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]

Troisième cycle (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]

Quatrième cycle (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]

Nous pouvons voir qu'à partir de cylce n = 2, nous obtenons la sortie suivante:

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

ce qui équivaut à

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

Comme mentionné ci-dessus, nous introduisons un décalage supplémentaire de l = 1 cycle. Cela signifie que votre sortie y [n] est retardée de retard l = 1. Cela signifie que les données de sortie sont équivalentes mais sont retardées d'un "index". Pour être plus clair: les données de sortie retardées sont de 2 cycles, car un cycle d'horloge (normal) est nécessaire et 1 cycle d'horloge supplémentaire (décalage l = 1) est ajouté pour l'étage intermédiaire.

Voici un croquis pour décrire graphiquement comment les données circulent:

croquis du flux de données

PS: Merci d'avoir regardé de près mon code. J'ai donc aussi appris quelque chose! ;-) Faites-moi savoir si cette version est correcte ou si vous voyez d'autres problèmes.

SDwarfs
la source
Bon travail! Malheureusement, y [n] = y [n-2] * b + x [n-1] n'est pas réellement fonctionnellement équivalent à y [n] = y [n-1] * b + x [n] avec latence. La forme d'une fonction de transfert IIR ressemble en fait à ceci: y [n] = x [n] * b0 + x [n-1] * b1 - y [n-1] * a1 - y [n-2] * a2 etc. Votre formulaire définit b0 et a1 à 0 et utilise à la place b1 et a2. Cependant, cette transformation produit en fait un filtre très différent. S'il existait un moyen de calculer un filtre avec le premier dénominateur (a1) réglé à zéro, vos deux solutions fonctionneraient parfaitement.
Marcus10110
Eh bien, vous devez comprendre correctement le problème du "décalage introduit". Par exemple, un filtre de "traitement de flux de données" devrait simplement transmettre son entrée car y [n] = x [n] fonctionnerait correctement s'il produisait y [n] = x [n-1] en sortie. La sortie est juste retardée d'un cycle (par exemple, l'indice de sortie est compensé par une valeur fixe par rapport à tous les indices d'entrée)! Dans notre exemple, cela signifie que votre fonction a y[n+l] = y[n-1] * b + x[n]une valeur fixe pour le décalage lqui peut être réécrit y[n] = y[n-1-l] * b + x[n-l]et pour l = 1, c'est-à-dire y[n] = y[n-2] * b + x[n-1].
SDwarfs
Pour votre filtre IIR plus complexe, vous devrez faire de même: 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. En supposant que vous pouvez faire les trois multiplications en parallèle (1. étape / 1 cycle) et que vous devez faire pour ajouter les produits ensemble, vous avez besoin de 2 cycles (1 cycle: ajouter / sous deux premiers résultats de produit, 1 cycle: ajouter / sous le résultat de ces deux add / subs), vous aurez besoin de 2 cycles supplémentaires. Donc l = (3-1) = 2 vous donnant 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
Bien sûr, pour que cela fonctionne, votre FPGA doit être capable de faire en parallèle: 4 multiplications et 3 additions / soustractions. Cela signifie que vous avez besoin de ressources pour 4 multiplicateurs et 3 additionneurs.
SDwarfs
0

Oui, vous pouvez cadencer à la fréquence d'échantillonnage.

Une solution à ce problème consiste à manipuler l'expression d'origine afin que les registres de pipeline puissent être insérés, tout en conservant la séquence de sortie souhaitée.

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

cela peut être manipulé en: y [n] = y [n-2] * b1 * b1 + x [n-1] * b1 + x [n].

Pour vérifier qu'il s'agit de la même séquence, considérez ce qui arrive aux premiers échantillons x [0], x [1], x [2] etc., où avant x [0] tous les échantillons x, y étaient nuls.

Pour l'expression originale, la séquence est:

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

Il est clair qu'il faut que b1 <1, sinon cela croîtra sans limite.

Considérons maintenant l'expression manipulée:

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

C'est la même séquence.

Une solution matérielle dans les primitives de bibliothèque Xilinx aurait besoin de deux DSP48E en cascade. Reportez-vous à la figure 1-1 dans UG193 v3.6 pour les noms de port et de registre ci-dessous. La première primitive multiplie par b1 et ajoute une horloge plus tard; la seconde multiplie par b1 * b1 et ajoute une horloge plus tard. Il existe une latence de pipeline de 4 horloges pour cette logique.

- DSP48E # 1

a_port1: = b1; - coefficient constant, régler AREG = 1

b_port1: = x; - définir l'attribut BREG = 1

c_port1: = x; - définir CREG = 1

- interne au 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; - sortie du 1er DSP48E

- fin du DSP48E # 1

- DSP48E # 2

a_port2: = reg_p2; - définir l'attribut AREG = 0

                -- this means the output of register reg_p2

                -- directly feeds back to the multiplier

b_port2: = b1 * b1; - constante, définissez BREG = 1

c_port2: = reg_p1; - définir CREG = 1

- interne au DSP48E # 2

reg_b2 <= b_port2;

reg_c2 <= c_port2;

reg_m2 <= a_port2 * reg_b2;

reg_p2 <= reg_m2 + reg_c2;

- fin du DSP48E # 2

La séquence à reg_p1:

x [0],

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

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

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

etc.

La séquence à reg_p2 est le résultat souhaité. En interne au 2ème DSP48E, le registre reg_m2 a une séquence:

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

Il y a une belle élégance à ce résultat. De toute évidence, le DSP48E ne se multiplie pas et n'ajoute pas la même horloge, mais c'est ce que l'équation de différence nécessite. L'équation de différence manipulée nous permet de tolérer les registres M et P dans le DSP48E et l'horloge à pleine vitesse.

Dave Brown
la source