Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

FPGA-Based DDS Signal Generator: Design and Implementation

Tech May 15 1

Implementation Environment

  • Software: Quartus II 13.0
  • Hardware: MP801

Fundamentals of DDS

DDS (Direct Digital Synthesizer) is a digital synthesis technique offering wide relative bandwidth, fast frequency switching, high resolution, and continuous phase. It enables easy digital modulation of frequency, phase, and amplitude, making it widely used in communications.

How DDS Works

  1. Store the waveform data (e.g., sine, square, triangle, sawtooth) in ROM. The method to synthesize the desired signal is described in reference: https://www.cnblogs.com/qidaiymm/p/5692253.html

  2. The dds_control module extracts data from ROM at specific intervals. This is achieved using:

    • Phase accumulator: Typically N-bit wide (24–32 bits). The phase resolution is 1/2^N. It determines the starting point of the waveform.
    • Frequency accumulator: Controls how many data points are skipped per clock cycle. This sets the output frequency.
  3. Given a DDS clock frequency F_clk, if the frequency control word fword = 1, the output frequency is F_out = F_clk / 2^N (the base frequency). If fword = B, then F_out = B * F_clk / 2^N. Thus, any output frequency can be generated by adjusting B, and the frequency resolution depends on the clock frequency and the accumulator width. Higher clock and wider acumulator yield better resolution.

  4. The DDS output is digital. A DAC chip (e.g., AD9709) converts it to analog, which can be observed on an oscilloscope.

Example of Frequency and Phase Control

Suppose a sine wave consists of 33 points stored in ROM. The x-axis (ROM address) is generated by dds_control, and the y-axis (ROM data) is the waveform amplitude.

  • Frequency control: If fword = 1, the frequency accumulator fre_acc increments by 1 each cycle, reading all 33 points sequentially. If fword = 2, it reads every other point (1, 3, 5, ...), effectively doubling the output frequency.
  • Phase control: Adding a phase word pword shifts the starting address, thereby shifting the phase of the output waveform.

Project Implementation

This design implements a single-channel DDS signal generator. DIP switches select the waveform type (sine, square, sawtooth, triangle). Push buttons adjust frequency and phase.

RTL View

The RTL structure consists of three main modules: select, dds_control, and ad9709_driver.

Module Description

(1) select Module

This module handles user input:

  • sw0: select sine wave
  • sw1: select square wave
  • sw2: select sawtooth
  • sw3: select triangle wave
  • key1: cycle through four frequancy values
  • key2: cycle through four phase values

Outputs:

  • rom_select[3:0]: selects which ROM to read
  • fword[31:0]: frequency control word
  • pword[31:0]: phase control word
module select(
    input                sclk                    ,
    input                s_rst_n                ,
        
    input                sw0                    ,
    input                sw1                    ,
    input                sw2                    ,
    input                sw3                    ,
        
    input                key1                    ,
    input                key2                    ,

    output    reg[ 3:0]    rom_select                    ,
    output    reg[31:0]    fword                    ,
    output    reg[31:0]    pword                
    );

reg [3:0] cnt1;
reg [3:0] cnt2;

always @(posedge sclk or negedge s_rst_n) begin
    if(!s_rst_n)
        cnt1 <= 0;
    else if(cnt1 == 3)
        cnt1 <= 0;
    else if(key1 == 1'b0)
        cnt1 <= cnt1 + 1'b1;
end

always @(posedge sclk or negedge s_rst_n) begin
    if(!s_rst_n)
        cnt2 <= 0;
    else if(cnt2 == 3)
        cnt2 <= 0;
    else if(key2 == 1'b0)
        cnt2 <= cnt2 + 1'b1;
end

always @(*) begin
    case({sw3,sw2,sw1,sw0})
        4'b0001: rom_select = 4'd0; // sine
        4'b0010: rom_select = 4'd1; // square
        4'b0100: rom_select = 4'd2; // sawtooth
        4'b1000: rom_select = 4'd3; // triangle
        default: rom_select = 4'd0;
    endcase
end

always @(*) begin
    case(cnt1)
        0: fword = 2000;
        1: fword = 3000;
        2: fword = 4000;
        3: fword = 5000;
        default: fword = 5000;
    endcase
end

always @(*) begin
    case(cnt2)
        0: pword = 0;
        1: pword = 64;
        2: pword = 128;
        3: pword = 192;
        default: pword = 0;
    endcase
end

endmodule

(2) dds_control Module

This module generates ROM addresses based on the frequency and phase control words. It accumulates the frequency word and adds the phase word to produce the address. The output is then selected from one of four waveform ROMs.

Key points:

  • The ROM address is derived from the high-order bits of the frequency accumulator: fre_acc[31:24] + pword.
  • The module outputs a clock signal da_clk that is synchronous with the data output.
module dds_control(
    input                    sclk            ,
    input                    s_rst_n        ,
    input                    en                ,

    input        [31:0]    fword           ,
    input        [11:0]    pword           ,
    input        [ 3:0]    rom_select    ,

    output                da_clk        ,
    output reg[ 7:0]  rom_data    
);

reg        [31:0]        fre_acc        ;
reg        [ 7:0]        rom_addr        ;

wire        [ 7:0]        q1                ;
wire        [ 7:0]        q2                ;
wire        [ 7:0]        q3                ;
wire        [ 7:0]        q4                ;

always @(posedge sclk or negedge s_rst_n) begin
    if(!s_rst_n)
        fre_acc <= 0;
    else if(en == 1'b0)
        fre_acc <= 0;
    else
        fre_acc <= fre_acc + fword;
end

always @(posedge sclk or negedge s_rst_n) begin
    if(!s_rst_n)
        rom_addr <= 0;
    else if(en == 1'b0)
        rom_addr <= 0;
    else
        rom_addr <= fre_acc[31:24] + pword;
end

assign da_clk = en ? sclk : 1'b1 ;

always @(*) begin
    case(rom_select)
        0: rom_data = q1;
        1: rom_data = q2;
        2: rom_data = q3;
        3: rom_data = q4;
        default: rom_data = q1;
    endcase
end

rom u1 (.address(rom_addr), .clock(sclk), .q(q1));
square_rom u2 (.address(rom_addr), .clock(sclk), .q(q2));
sawtooth_rom u3 (.address(rom_addr), .clock(sclk), .q(q3));
triangle_rom u4 (.address(rom_addr), .clock(sclk), .q(q4));

endmodule

Note: When generating .mif files with Mif_Maker2010, ensure the bit width is 8 and the depth is 256. Verify the .mif file contains valid data (not all zero) and place it in the project directory.

(3) ad9709_driver Module

This module drives the AD9709 DAC chip (single-channel mode, channel A). It passes the ROM data to the DAC at the synchronized clock rate.

module ad9709_driver(
    input                  da_clk        ,
    input                  rst_n            ,
    input       [7:0]       rom_data        ,
   
    output               dac_mode        ,
    output               dac_clka        ,
    output reg[7:0]   dac_da        ,
    output               dac_wra        ,
    output               dac_sleep
    );

always @ (posedge da_clk or negedge rst_n) begin
    if(rst_n == 1'b0)
        dac_da <= 0;
    else    
        dac_da <= rom_data;
end

assign dac_sleep = 0 ;
assign dac_wra   = dac_clka ;
assign dac_clka  = ~da_clk;
assign dac_mode  = 1;

endmodule

(4) Top-Level Module

The top-level module instantiates all submodules and connects the hardware peripherals (switches, buttons, DAC outputs).

module dds(
    input                    sclk                ,
    input                    s_rst_n            ,
    input                    en                    ,
    
    input                    sw0                ,
    input                    sw1                ,
    input                    sw2                ,
    input                    sw3                ,
    input                    key1                ,
    input             key2                ,
                            
    output                dac_mode            ,
    output                dac_clka            ,
    output                dac_wra            ,
    output                dac_sleep        ,

    output    [7:0]        dac_da
);

wire                da_clk                    ;
wire    [7:0]        rom_data                   ;

wire    [31:0]    fword                        ;
wire    [31:0]    pword                        ;
wire    [3:0]        rom_select                ;

dds_control u0(
    .sclk             (sclk        ),
    .s_rst_n         (s_rst_n    ),
    .en              (en          ),
    .fword           (fword       ),
    .pword           (pword       ),
    .rom_select      (rom_select  ),
    .da_clk          (da_clk      ),
    .rom_data        (rom_data    )
);

ad9709_driver u1(
    .da_clk        (da_clk        ),
    .rst_n         (s_rst_n       ),
    .rom_data      (rom_data      ),
    .dac_mode      (dac_mode      ),
    .dac_clka      (dac_clka      ),
    .dac_da        (dac_da        ),
    .dac_wra       (dac_wra       ),
    .dac_sleep     (dac_sleep     )
);

select u2(
    .sclk            ( sclk        ),
    .s_rst_n        ( s_rst_n    ),
    .sw0            ( sw0        ),
    .sw1            ( sw1        ),
    .sw2            ( sw2        ),
    .sw3            ( sw3        ),    
    .key1           ( key1       ),
    .key2           ( key2       ),
    .rom_select     ( rom_select ),
    .fword          ( fword      ),
    .pword          ( pword      )
);

endmodule

Board Demonstration

The design was implemented on the MP801 board. The DIP switches select the waveform, and buttons adjust frequency/phase. The output was observed on an oscilloscope.

Known Limitations

  1. Output frequency calculation formula not derived for this specific test.
  2. No debouncing for push buttons; fword and pword values may need tuning.
  3. No output filtering implemented; some glitches may be present.
  4. No amplitude control module.
  5. Only single-channel DAC mode used; the second channel is not driven.

References

  1. "Practical Guide to FPGA System Design and Verification Based on ac620" (Xiao Meige)
  2. "FPGA Simplest Design Principles and Applications" (Ming Deyang)

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.