FPGA-Based DDS Signal Generator: Design and Implementation
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
-
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
-
The
dds_controlmodule 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.
-
Given a DDS clock frequency
F_clk, if the frequency control wordfword = 1, the output frequency isF_out = F_clk / 2^N(the base frequency). Iffword = B, thenF_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. -
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 accumulatorfre_accincrements by 1 each cycle, reading all 33 points sequentially. Iffword = 2, it reads every other point (1, 3, 5, ...), effectively doubling the output frequency. - Phase control: Adding a phase word
pwordshifts 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 wavesw1: select square wavesw2: select sawtoothsw3: select triangle wavekey1: cycle through four frequancy valueskey2: cycle through four phase values
Outputs:
rom_select[3:0]: selects which ROM to readfword[31:0]: frequency control wordpword[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_clkthat 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
- Output frequency calculation formula not derived for this specific test.
- No debouncing for push buttons;
fwordandpwordvalues may need tuning. - No output filtering implemented; some glitches may be present.
- No amplitude control module.
- Only single-channel DAC mode used; the second channel is not driven.
References
- "Practical Guide to FPGA System Design and Verification Based on ac620" (Xiao Meige)
- "FPGA Simplest Design Principles and Applications" (Ming Deyang)