Principe de la DDS
La technologie DDS (synthèse numérique directe) est une méthode qui, en se fondant sur le théorème d'échantillonnage de Nyquist et sur les techniques de traitement numérique, effectue un échantillonnage sans distorsion d'une série de signaux analogiques, stocke les signaux numériques obtenus en mémoire et, sous le contrôle d'une horloge, convertit les quantités numériques en signaux analogiques par conversion numérique-analogique.
Le module DDS se compose principalement d'un accumulateur de phase, d'une table de correspondance, d'un convertisseur DAC et d'un filtre passe-bas. La structure de base est la suivante.

L'accumulateur de phase est le composant central du DDS, utilisé pour mettre en œuvre l'accumulation de phase et produire l'amplitude correspondante. L'accumulateur de phase est constitué d'un additionneur de largeurMbits et d'un registre de largeurMbits. Sous contrôle de l'horloge, le résultat d'accumulation précédent est renvoyé à l'entrée de l'additionneur pour mettre en œuvre la fonction d'accumulation, de sorte que l'incrément de phase à chaque cycle d'horloge estK, et le résultat d'accumulation de phase est utilisé comme adresse envoyée à la section de table de correspondance ROM.
La table de correspondance d'amplitude stocke l'amplitude numérique binaire correspondant à chaque phase. À chaque cycle d'horloge, la table de correspondance adresse les informations d'adresse de phase fournies par l'accumulateur de phase, puis délivre la valeur numérique d'amplitude binaire discrète correspondante. Supposons que l'adresse de la table de correspondance estMbits, et les données de sortie sont deNbits, alors la capacité de la table de correspondance est
. Il est facile de voir que la résolution de phase du signal de sortie est :

Le convertisseur DAC convertit les signaux numériques en signaux analogiques. En fait, le signal délivré par le DAC n'est pas continu. Au lieu de cela, selon le poids de chaque bit de code, il somme la valeur numérique de chaque bit d'entrée, puis effectue une sortie analogique en unités de sa résolution. Le signal de sortie réel est un signal analogique linéaire en escalier, il doit donc être lissé, ce qui est généralement fait à l'aide d'un filtre.
Filtre passe-bas : comme le signal analogique délivré par le convertisseur DAC présente des défauts en escalier, il doit être lissé pour éliminer la plupart des signaux parasites, ce qui rend le signal de sortie relativement idéal sur le plan analogique.
Lorsque le DDS fonctionne, le mot de commande de fréquenceKetMest ajouté à l'accumulateur de phase de la largeur de bits correspondante, et le résultat est utilisé comme valeur de phase. À chaque cycle d'horloge, il est envoyé à la table de correspondance ROM sous forme binaire, convertissant l'information de phase en une valeur d'amplitude sinusoïdale numérisée, qui est ensuite convertie en un signal analogique en forme de marches par conversion numérique-analogique. Après que le signal a traversé le filtrage système pour éliminer la plupart des signaux parasites, une onde sinusoïdale relativement pure peut être obtenue.
Du point de vue de la décomposition en fréquence, la table de correspondance ROM divise la fréquence d'entrée
into
parties. Le nombre de parties occupées par la fréquence de sortie
est exactement le mot de commande de fréquence de pasK. Par conséquent, la fréquence de sortie du DDS peut être exprimée comme :

Du point de vue de la phase, dans le temps
l'incrément de phase de la sortie contrôlé par le mot de commande de fréquenceKest :

En considérant la vitesse angulaire de la fréquence de sortie à ce moment
, dans le temps
l'incrément de phase de la fréquence de sortie peut également s'exprimer comme :

À partir des deux équations ci-dessus, la relation entre la fréquence de sortie du DDS et la fréquence d'entrée peut également être dérivée.
Conception du DDS
Description de la conception
Dans ce qui suit, seul le circuit DDS avant le DAC est conçu.
Le DDS conçu présente les caractéristiques suivantes :
- 1) Fréquence contrôlable ;
- 2) Phase de départ contrôlable ;
- 3) Amplitude contrôlable ;
- 4) Sortie sélectionnable d'onde sinusoïdale, d'onde triangulaire et d'onde carrée ;
- 5) Optimisation des ressources : le fichier de stockage de forme d'onde n'utilise qu'un quart des données de l'onde sinusoïdale.
Génération de la ROM
Pour le module ROM, il est préférable d'utiliser un cœur IP personnalisé, car le timing et la surface seront mieux optimisés. Une ROM personnalisée nécessite également de spécifier un fichier de données. Par exemple, le suffixe du fichier de données ROM dans ISE est.coe, et le suffixe du fichier de données ROM dans Quartus II est.mif。
Pour faciliter la simulation, le module ROM est écrit en code ici, avec une largeur d'adresse de 8 bits et une largeur de données de 10 bits.
Pour économiser de l'espace, seul un quart de la forme d'onde sinusoïdale est stocké, puis elle est décalée selon la symétrie pour obtenir une période complète de la forme d'onde des données sinusoïdales.
Pour obtenir divers modes DDS, des programmes ROM pour l'onde triangulaire et l'onde carrée sont également ajoutés.
Le code d'implémentation est le suivant (tout est inclus dans le fichier mem.v).
Exemple
input clk, //horloge de référence
input rstn , //resetn, actif bas
input en , //démarrage de la génération des ondes
input [1:0] sel , //sélection des ondes
input [7:0] addr ,
output dout_en ,
output [9:0] dout); //données en sortie, largeur 10bit
//données en sortie depuis les ROMs
wire [9:0] q_tri ;
wire [9:0] q_square ;
wire [9:0] q_cos ;
//adresse ROM
reg [1:0] en_r ;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
en_r <= 2'b0 ;
end
else begin
en_r <= {en_r[0], en} ; //retarder d'un cycle pour en
end
end
assign dout = en_r[1] ? (q_tri | q_square | q_cos) : 10'b0 ;
assign dout_en = en_r[1] ;
//instanciation ROM
cos_ROM u_cos_ROM (
.clk (clk),
.en (en_r[0] & (sel == 2'b0)), //sel = 0, onde cos
.addr (addr[7:0]),
.q (q_cos[9:0]));
square_ROM u_square_ROM (
.clk (clk),
.en (en_r[0] & sel == 2'b01), //sel = 1, onde carrée
.addr (addr[7:0]),
.q (q_square[9:0]));
tri_ROM u_tri_ROM (
.clk (clk),
.en (en_r[0] & sel == 2'b10), //sel = 2, onde triangulaire
.addr (addr[7:0]),
.q (q_tri[9:0]));
endmodule
//ROM d'ondes carrées
module square_ROM (
input clk,
input en,
input [7:0] addr,
output reg [9:0] q);
//1 dans la première moitié du cycle, et 0 dans la seconde moitié du cycle
always @(posedge clk) begin
if (en) begin
q <= { 10{(addr < 128)} };
end
else begin
q <= 'b0 ;
end
end
endmodule
//ROM d'ondes triangulaires
module tri_ROM (
input clk,
input en,
input [7:0] addr,
output reg [9:0] q);
//front montant, addr -> 0x0, 0x3f
always @(posedge clk) begin
if (en) begin
if (addr < 128) begin
q <= {addr[6:0], 3'b0}; //front montant
end
else begin //front descendant
q <= 10'h3ff - {addr[6:0], 3'b0} ;
end
end
else begin
q <= 'b0 ;
end
end
endmodule
//Mieux vaut utiliser mem ip.
//Ce format est facile pour la simulation
module cos_ROM (
input clk,
input en,
input [7:0] addr,
output reg [9:0] q);
wire [8:0] ROM_t [0 : 64] ;
//en raison de la symétrie de la fonction cos, il suffit de stocker 1/4 des données d'un cycle
assign ROM_t[0:64] = {
511, 510, 510, 509, 508, 507, 505, 503,
501, 498, 495, 492, 488, 485, 481, 476,
472, 467, 461, 456, 450, 444, 438, 431,
424, 417, 410, 402, 395, 386, 378, 370,
361, 352, 343, 333, 324, 314, 304, 294,
283, 273, 262, 251, 240, 229, 218, 207,
195, 183, 172, 160, 148, 136, 124, 111,
99 , 87 , 74 , 62 , 50 , 37 , 25 , 12 ,
0 } ;
always @(posedge clk) begin
if (en) begin
if (addr[7:6] == 2'b00 ) begin //quadrant 1, addr[0, 63]
q <= ROM_t[addr[5:0]] + 10'd512 ; //décaler vers le haut
end
else if (addr[7:6] == 2'b01 ) begin //2e, addr[64, 127]
q <= 10'd512 - ROM_t[64-addr[5:0]] ; //inverser deux fois
end
else if (addr[7:6] == 2'b10 ) begin //3e, addr[128, 192]
q <= 10'd512 - ROM_t[addr[5:0]]; //inverser et décaler à droite
end
else begin //4e quadrant, addr [193, 256]
q <= 10'd512 + ROM_t[64-addr[5:0]]; //inverser et décaler vers le haut
end
end
else begin
q <= 'b0 ;
end
end
endmodule
Module de contrôle DDS
Exemple
input clk, //horloge de référence
input rstn , //resetn, actif bas
input wave_en , //commencer à générer des ondes
input [1:0] wave_sel , //sélection des ondes
input [1:0] wave_amp , //commande d'amplitude des ondes
input [7:0] phase_init, //phase initiale
input [7:0] f_word , //mot de commande de fréquence
output [9:0] dout, //données en sortie, largeur 10bit
output dout_en);
//accumulateur de phase
reg [7:0] phase_acc_r ;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
phase_acc_r <= 'b0 ;
end
else if (wave_en) begin
phase_acc_r <= phase_acc_r + f_word ;
end
else begin
phase_acc_r <= 'b0 ;
end
end
//adresse ROM
reg [7:0] mem_addr_r ;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
mem_addr_r <= 'b0 ;
end
else if (wave_en) begin
mem_addr_r <= phase_acc_r + phase_init ;
end
else begin
mem_addr_r <= 'b0 ;
end
end
//instanciation ROM
wire [9:0] dout_temp ;
mem u_mem_wave(
.clk (clk), //horloge de référence
.rstn (rstn), //resetn, actif bas
.en (wave_en), //démarrer la génération des ondes
.sel (wave_sel[1:0]), //sélection des ondes
.addr (mem_addr_r[7:0]),
.dout_en (dout_en),
.dout (dout_temp[9:0])); //sortie de données, largeur 10 bits
//amplitude
//0 -> dout/1 //1 -> dout/2 //2 -> dout/4 //3 -> dout/8
assign dout = dout_temp >> wave_amp ;
endmodule
testbench
Exemple
module test ;
reg clk ;
reg rstn ;
reg wave_en ;
reg [1:0] wave_sel ;
reg [1:0] wave_amp ;
reg [7:0] phase_init ;
reg [7:0] f_word ;
wire [9:0] dout ;
wire dout_en ;
//(1)clk, reset et autres registres constants
initial begin
clk = 1'b0 ;
rstn = 1'b0 ;
#100 ;
rstn = 1'b1 ;
#10 ;
forever begin
#5 ; clk = ~clk ; //horloge système, 100MHz
end
end
//(2)configuration du signal ;
parameter clk_freq = 100000000 ; //100MHz
integer freq_dst = 2000000 ; //2MHz
integer phase_coe = 2; //1/4 de cycle, soit pi/2
initial begin
wave_en = 1'b0 ;
//(a)onde cos, phase pi/2
wave_amp = 2'd1 ;
wave_sel = 2'd0 ;
phase_init = 256/phase_coe ; //phase d'initialisation pi/8
f_word = (1<<8) * freq_dst / clk_freq; //obtenir le mot de commande de fréquence
#500 ;
@ (negedge clk) ;
wave_en = 1'b1 ; //démarrer la génération d'ondes
# 2000 ;
//(b)onde triangulaire, phase d'initialisation pi/4
wave_en = 1'b0 ;
wave_sel = 2'd2 ;
phase_init = 256/4 ;
wave_amp = 2'd2 ;
# 50 ;
wave_en = 1'b1 ;
end
//(3) instanciation de module
dds u_dds(
.clk (clk),
.rstn (rstn),
.wave_en (wave_en),
.wave_sel (wave_sel[1:0]),
.wave_amp (wave_amp[1:0]),
.phase_init (phase_init[7:0]),
.f_word (f_word[7:0]),
.dout (dout[9:0]),
.dout_en (dout_en));
//(4) terminer la simulation
always begin
#100;
if ($time >= 100000) $finish ;
end
endmodule
Résultats de simulation
Comme le montre la figure ci-dessous, le signal de sortie est réglé sur un affichage analogique.
- 1) On peut voir que la fréquence de l'onde sinusoïdale est de 2 MHz, correspondant au mot de commande de fréquence ;
- 2) La phase initiale de l'onde sinusoïdale est de 1/2 période, et la phase initiale de l'onde triangulaire est de 1/4 période, conformément aux réglages ;
- 3) L'onde triangulaire se voit attribuer la moitié de l'amplitude de l'onde sinusoïdale, et l'amplitude est également contrôlable ;
- 4) Les formes d'onde de sortie sont l'onde sinusoïdale et l'onde triangulaire, et elles peuvent être commutées normalement ;
- 5) La forme d'onde sinusoïdale ne présente aucune anomalie ; seules les données d'onde sinusoïdale sur 1/4 de période sont utilisées pour compléter la sortie sinusoïdale complète.
En raison de contraintes d'espace, la simulation n'a testé que certaines fonctionnalités. Les lecteurs peuvent modifier les paramètres pour tester d'autres fonctionnalités, telles que d'autres fréquences, la sortie d'onde carrée, etc.
Annexe : Utilisation de Matlab
Génération de données d'onde sinusoïdale sur 1/4 de période
L'utilisation de Matlab pour générer des données d'onde sinusoïdale sur 1/4 de période est décrite comme suit, et le processus de reconstitution de l'onde sinusoïdale complète est également simulé.
Exemple
%=======================================================
% génération 1/4 de données d'onde cos avec format hex txt
%=======================================================
N = 64 ; %communs256points de données, prendre1/4
n = 0:N ;
w = n/N *pi/2 ; %Quantifier à pi/2à l'intérieur
st = (2^10 /2 -1)*cos(w) ; %Les données d'onde sinusoïdale occupent 10 bits
st = floor(st) ;
%%Assembler le premier quadrant
st1 = st+512 ;
figure(5) ;plot(n, st1) ;
hold on ;
%%Assembler le deuxième quadrant
n2 = 64 + n ;
st2 = 512 - st(64-n+1);
plot(n2, st2);
hold on
%%Assembler le troisième quadrant
n3 = 128 + n ;
st3 = 512 - st ;
plot(n3, st3) ;
hold on ;
%%Assembler le quatrième quadrant
n4 = 192 + n ;
st4 = 512 + st(64-n+1) ;
plot(n4, st4) ;
hold on ;
Téléchargement du code source
Téléchargement
