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éeintoparties. Le nombre de parties occupées par la fréquence de sortieest 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 tempsl'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 tempsl'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

module mem(
    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

module dds(
    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

`timescale 1ns/1ns

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

clear all;close all;clc;
%=======================================================
% 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