Les filtres CIC (Cascaded Integrator-Comb), ou filtres à Intégrateurs-Peignes en Cascade, sont des composants fondamentaux dans le traitement numérique du signal, particulièrement adaptés aux systèmes multi-cadences et aux implémentations FPGA grâce à leur structure simple, leur faible consommation de ressources et l'absence de multiplicateurs. Cet article explore l'implémentation d'un filtre CIC de décimation en Verilog, accompagné de méthodes de vérification avec Matlab et Simulink.
Conception Verilog du Filtre CIC
Un filtre CIC est constitué de N étages d'intégrateurs suivis d'un décimateur, puis de N étages de peignes (souvent des filtres différenciateurs). Le code Verilog suivant présente un cœur de filtre CIC à N étages, avec un facteur de décimation paramétrable.
module filtre_cic_decim #(
parameter NOMBRE_ETAGES = 3, // Nombre d'étages intégrateur/peigne
parameter LARGEUR_ENTREE_BITS = 16, // Largeur de données d'entrée
parameter FACTEUR_DECIMATION = 1 // Facteur R pour les peignes (1-z^-R)
)(
input horloge, // Horloge système
input reinitialiser, // Signal de réinitialisation asynchrone
input signed [LARGEUR_ENTREE_BITS-1:0] entree_data, // Données d'entrée signées
output reg signed [LARGEUR_ENTREE_BITS + NOMBRE_ETAGES*3 - 1:0] sortie_data // Données de sortie signées
);
// Calcul de la largeur de bits interne pour éviter les débordements.
// Une augmentation empirique de 3 bits par étage est souvent utilisée pour les CIC.
localparam LARGEUR_INTERNE_BITS = LARGEUR_ENTREE_BITS + NOMBRE_ETAGES * 3;
// Registres pour la chaîne d'intégrateurs
reg signed [LARGEUR_INTERNE_BITS-1:0] registres_integrateurs [0:NOMBRE_ETAGES];
// Bloc d'intégrateurs
always @(posedge horloge) begin
if (reinitialiser) begin
for (integer k = 0; k <= NOMBRE_ETAGES; k = k + 1)
registres_integrateurs[k] <= 0;
end else begin
// Premier intégrateur reçoit l'entrée
registres_integrateurs[0] <= entree_data + registres_integrateurs[0];
// Intégrateurs en cascade
for (integer k = 1; k <= NOMBRE_ETAGES; k = k + 1)
registres_integrateurs[k] <= registres_integrateurs[k-1] + registres_integrateurs[k];
end
end
// Registres et lignes à retard pour la chaîne de peignes
// Chaque étage de peigne implémente H(z) = 1 - z^(-R)
reg signed [LARGEUR_INTERNE_BITS-1:0] registres_flux_peigne [0:NOMBRE_ETAGES];
reg signed [LARGEUR_INTERNE_BITS-1:0] lignes_retard_peigne [0:NOMBRE_ETAGES][0:FACTEUR_DECIMATION-1];
// Bloc de peignes
always @(posedge horloge) begin
if (reinitialiser) begin
for (integer k = 0; k <= NOMBRE_ETAGES; k = k + 1) begin
registres_flux_peigne[k] <= 0;
for (integer d = 0; d < FACTEUR_DECIMATION; d = d + 1)
lignes_retard_peigne[k][d] <= 0;
end
end else begin
// L'entrée de la section peigne est la sortie du dernier intégrateur
registres_flux_peigne[0] <= registres_integrateurs[NOMBRE_ETAGES];
for (integer k = 0; k < NOMBRE_ETAGES; k = k + 1) begin
// Mise à jour de la ligne à retard pour l'étage actuel
for (integer d = 0; d < FACTEUR_DECIMATION-1; d = d + 1)
lignes_retard_peigne[k][d+1] <= lignes_retard_peigne[k][d];
lignes_retard_peigne[k][0] <= registres_flux_peigne[k]; // Stocke l'entrée actuelle de l'étage
// Calcul de la sortie de l'étage de peigne: Courant - Retardé (par FACTEUR_DECIMATION cycles)
registres_flux_peigne[k+1] <= registres_flux_peigne[k] - lignes_retard_peigne[k][FACTEUR_DECIMATION-1];
end
// La sortie finale est celle du dernier étage de peigne
sortie_data <= registres_flux_peigne[NOMBRE_ETAGES];
end
end
endmodule
Un aspect crucial lors de l'implémentation est la gestion de la largeur de bits. Les intégrateurs accumulent des valeurs, entraînant une croissance de la largeur de bits. Un dimensionnement insuffisant peut provoquer des débordements, qui se traduisent par des erreurs numériques inattendues en simulation. Pour une entrée de 16 bits et un filtre CIC à trois étages, une largeur de sortie d'au moins 16 + 3*3 = 25 bits est généralement requise, comme indiqué par l'expérience.
Vérification avec Matlab et Simulink
La validation du comportement du filtre CIC en Matlab est une étape essentielle. Lors de la génération des coefficients d'un filtre de compensation CIC (souvent nécessaire pour aplatir la bande passante après décimation) à l'aide d'outils comme fdatool, il est impératif de cocher l'option "Normaliser les coefficients du filtre pour éviter les débordements". Oublier cette étape peut conduire à des différences d'ordres de grandeur entre la simulation FPGA et les résultats Matlab.
Pour la modélisation sous Simulink, il est recommandé de décomposer l'équation différentielle du filtre CIC. Construire manuellement les structures d'intégrateur et de peigne à l'aide de blocs 'Unit Delay' permet de visualiser clairement le flux de données à chaque nœud du filtre. Lorsque l'on compare les formes d'onde de la simulation Vivado ou Quartus avec celles de Simulink, il est crucial d'aligner les axes temporels sur les fronts d'horloge post-décimation pour éviter les illusions de décalage temporel.
Lors de la création de fichiers de coefficients (.coe) pour des filtres FIR de compensation, l'utilisation de scripts Python est fortement conseillée pour éviter les erreurs manuelles, surtout pour des ordres de filtre élevés. Voici un exemple simple pour des coefficients d'un filtre passe-bas à trois étages (corresponds à (1-z^-1)^3) :
import numpy as np
# Coefficients pour un filtre de compensation de 3ème ordre (dérivé de (1-z^-1)^3)
# Ces coefficients sont généralement utilisés pour un filtre FIR de compensation,
# pas directement pour la partie peigne du CIC.
coefficients_compens = np.array([-1, 3, -3, 1])
chemin_fichier = 'coefficients_compensation_cic.coe'
with open(chemin_fichier, 'w') as f:
f.write('Radix=10;\n')
f.write('Coefficients=' + ','.join(map(str, coefficients_compens)) + ';\n')
print(f"Fichier COE généré : {chemin_fichier}")
Optimisation et Vérification en FPGA (Vivado/Quartus)
Dans les phases de déploiement sur FPGA, notamment avec des outils comme Vivado ou Quartus, une attention particulière doit être portée à la convergence temporelle, en se concentrant sur les chemins critiques, en particulier ceux liés aux logiques de peigne. Un piège courant est la configuration incorrecte des contraintes d'horloge de décimation, ce qui génère des rapports de timing remplis de fausses erreurs. L'ajout de contraintes spécifiques telles que set_multicycle_path -hold 2 dans le fichier SDC (Synopsys Design Constraints) peut souvent résoudre ces problèmes.
Pour la vérification des résultats de simulation, plutôt que de comparer visuellement les formes d'onde, il est bien plus efficace d'utiliser un script d'automatisation. Un script comme celui-ci peut détecter des erreurs sutbiles qui échapperaient à l'œil humain :
# Exemple de script Tcl pour comparer des données de simulation
proc lire_csv {chemin_fichier} {
set fichier_id [open $chemin_fichier r]
set donnees_liste {}
while {[gets $fichier_id ligne] >= 0} {
# Ignorer l'en-tête si nécessaire, ou parser les données numériques
if {[string is integer -strict [string trim $ligne]]} {
lappend donnees_liste [string trim $ligne]
}
}
close $fichier_id
return $donnees_liste
}
proc calculer_difference_vecteur {vecteur1 vecteur2} {
set max_diff 0.0
if {[llength $vecteur1] != [llength $vecteur2]} {
puts "Erreur: Les vecteurs ont des longueurs différentes."
return -1
}
for {set i 0} {$i < [llength $vecteur1]} {incr i} {
set val1 [lindex $vecteur1 $i]
set val2 [lindex $vecteur2 $i]
set diff [expr {abs(double($val1) - double($val2))}]
if {$diff > $max_diff} {
set max_diff $diff
}
}
return $max_diff
}
set donnees_fpga [lire_csv "sortie_fpga.csv"]
set donnees_matlab_ref [lire_csv "reference_matlab.csv"]
set ecart_max [calculer_difference_vecteur $donnees_fpga $donnees_matlab_ref]
if {$ecart_max > 1e-6} {
puts "La simulation a échoué ! Écart maximal détecté : $ecart_max"
} else {
puts "Les formes d'onde correspondent. Validation réussie !"
}