DDS直接數字式頻率合成器(Direct Digital Synthesizer),相信所有人看到這個名字就覺得不會陌生。有些資料講述的方式太高大上,不少人一時半會接受不了。本篇文章從雙口RAM入手,由淺入深脫掉DDS高大上的外衣。
基本原理框圖:
? ??
兩個關鍵術語:
a. 相位累加器:Phase = Phase + freq_ctrl,可以暫且理解為i = i + 1一樣的東西。
b. 頻率控制字:freq_ctrl,這個東西的值直接影響輸出信號的頻率。
假設系統工作時鐘(查表時鐘)為150MHz,ROM表深度為4096,存儲波形為1個周期(如正弦波每周期抽樣量化為4096個點),也就是一個周期的波形由4096個采樣點組成,意味著輸出波形一個周期最多4096個采樣點。比如Data輸出10M的正弦波,輸出的正弦波每周期只有15個采樣點;而輸出1M的正弦波,每周期將有150個采樣點;我們也可以知道當輸出頻率小于等于36.621KHz時,輸出波形每周期由4096個點構成。輸出信號的每周期點越多,階梯效過越不明顯,經過低通濾波器后波形越好看。
如果freq_ctrl為1時,那么輸出信號為150MHz/4096=36.621KHz,如果freq_ctrl為2時,那么輸出信號為150MHz*2/4096=73.242KHz。因此當需要輸出正弦波頻率為fout MHz時,
Fout = 150MHz*freq_ctrl/4096,所以freq_ctrl = Fout*4096/150MHz。
如果上面的大家都理解了,那么恭喜你已經完全理解了DDS的核心部分。至于其他DDS相關的內如,比如頻率分辨率(因為rom地址必須是整數,所以freq_ctrl必須是整數,所以上例的頻率分辨率為),旁瓣抑制比(量化多1bit,多6db = 20lg2)……(╯-_-)╯╧╧
好了理解了上面,下面來一個FPGA工程實現一個DDS。
工程目標:通過SPI可以配置RAM的值,通過SPI配置頻率控制字,輸出數據給DAC,同時提供隨路時鐘。此工程可外接MCU通過任何你想得到的方式配置用戶想輸出的周期信號(不僅僅只是sin、cos、方波、鋸齒、三角,可以是任何波形哦)。
軟件環境vivado2014.2
1. 新建工程
2. 例化IP
a) 使用MMCM將時鐘3倍頻
?
b) 例化真雙口RAM 16bitsx4096
?
3. 直接貼代碼
module dds_top(
//global
input wire i_clk,//50M
input wire i_rst,
//spi
input wire i_spi_sclk,
input wire i_spi_miso,
input wire i_spi_nss,
//dac
output wire o_dac_clk,
output wire [15:0]o_dac_data
);
wire clk;//main clk,150M
wire mmcm_locked;
wire asy_rst_wire;
reg [7:0]asy_rst_reg;
wire asy_rst;
reg [7:0]spi_sclk_reg;
reg [7:0]spi_miso_reg;
reg [7:0]spi_nss_reg;
wire spi_sclk_pos;
wire spi_nss_neg;
wire spi_nss_pos;
reg [14:0]spi_addr;
reg [15:0]spi_data;
reg [7:0]spi_cnt;
reg [31:0]spi_miso_data;
reg [15:0]spi_cnt_reg;
reg spi_wen;
reg [11:0]phase;//phase: 0-4095
///////////////////clk and reset///////////////////
clk_wiz u_clk_wiz
(
// Clock in ports
.clk_in1 ( i_clk ),//50M
// Clock out ports
.clk_out1 ( clk ), //150M
// Status and control signals
.reset ( i_rst ),
.locked ( mmcm_locked )
);
assign asy_rst_wire = i_rst | (~mmcm_locked);
always@(posedge clk or posedge i_rst)
if(i_rst)
asy_rst_reg <= 8'd0;
else
asy_rst_reg <= {asy_rst_reg[6:0],asy_rst_wire};
assign asy_rst = asy_rst_reg[7];
/////////////////// spi input ///////////////////
//spi frame 32bit
// wen addr data
// [31] [30:16] [15:0]
always@(posedge clk or posedge asy_rst)
if(asy_rst)
begin
spi_sclk_reg <= 8'd0;
spi_miso_reg <= 8'd0;
spi_nss_reg <= 8'd0;
end
else
begin
spi_sclk_reg <= {spi_sclk_reg[6:0],i_spi_sclk};
spi_miso_reg <= {spi_miso_reg[6:0],i_spi_miso};
spi_nss_reg <= {spi_nss_reg[6:0],i_spi_nss};
end
//get sclk posedge
assign spi_sclk_pos = ~spi_sclk_reg[7] & spi_sclk_reg[6] ;
//get nss negedge & posedge
assign spi_nss_neg = spi_nss_reg[6] & ~spi_nss_reg[5] ;
assign spi_nss_pos = ~spi_nss_reg[6] & spi_nss_reg[5] ;
always@(posedge clk or posedge asy_rst)
if(asy_rst)
spi_cnt <= 8'd0;
else if(spi_nss_neg)
spi_cnt <= 8'd0;
else if(spi_sclk_pos)
spi_cnt <= spi_cnt + 1'b1;
else
spi_cnt <= spi_cnt;
always@(posedge clk or posedge asy_rst)
if(asy_rst)
spi_miso_data <= 32'd0;
else if(spi_nss_neg)
spi_miso_data <= 32'd0;
else if(spi_sclk_pos)
spi_miso_data <= {spi_miso_data[30:0],spi_miso_reg[6]};
else
spi_miso_data <= spi_miso_data;
always@(posedge clk or posedge asy_rst)
if(asy_rst)
begin
spi_addr <= 15'd0;
spi_data <= 16'd0;
end
else
if(spi_cnt == 8'd32)
begin
spi_addr <= spi_miso_data[30:16];
spi_data <= spi_miso_data[15:0];
end
else
begin
spi_addr <= 15'd0;
spi_data <= 16'd0;
end
always@(posedge clk or posedge asy_rst)
if(asy_rst)
spi_cnt_reg <= 16'd0;
else
spi_cnt_reg <= {spi_cnt_reg[7:0],spi_cnt[7:0]};
//address 0-4095 for dds_ram
always@(posedge clk or posedge asy_rst)
if(asy_rst)
spi_wen <= 1'b0;
else if(spi_cnt_reg == {8'd31,8'd32} && spi_miso_data[31:28] == 4'b1000)
spi_wen <= 1'b1;
else
spi_wen <= 1'b0;
//address 4096-16383,16385-32767 reserved
//address 16384 for freq control,low 12bits
reg [15:0]freq_ctrl_reg;
always@(posedge clk or posedge asy_rst)
if(asy_rst)
freq_ctrl_reg <= 16'd0;
else if(spi_cnt_reg == {8'd31,8'd32} && spi_miso_data[31:16] == {1'b1,15'd16384})
freq_ctrl_reg <= spi_miso_data[15:0];
else
freq_ctrl_reg <= freq_ctrl_reg;
////////////////////////dpram////////////////////////
//16*4096
dds_ram u_dds_ram (
.clka ( clk ),
.ena ( 1'b1 ),
.wea ( spi_wen ),
.addra ( spi_addr[11:0] ),//4096-12bit
.dina ( spi_data ),
.douta ( ),
.clkb ( clk ),
.enb ( 1'b1 ),
.web ( 1'b0 ),
.addrb ( phase ),//4096-12bit
.dinb ( 16'd0 ),
.doutb ( o_dac_data )
);
always@(posedge clk or posedge asy_rst)
if(asy_rst)
phase <= 12'd0;
else
phase <= phase + freq_ctrl_reg[11:0];
ODDR #(
.DDR_CLK_EDGE("SAME_EDGE"), // "OPPOSITE_EDGE" or "SAME_EDGE"
.INIT(1'b0), // Initial value of Q: 1'b0 or 1'b1
.SRTYPE("SYNC") // Set/Reset type: "SYNC" or "ASYNC"
) ODDR_inst (
.Q(o_dac_clk), // 1-bit DDR output
.C(clk), // 1-bit clock input
.CE(1'b1), // 1-bit clock enable input
.D1(1'b1), // 1-bit data input (positive edge)
.D2(1'b0), // 1-bit data input (negative edge)
.R(asy_rst), // 1-bit reset
.S(1'b0) // 1-bit set
);
endmodule
4. 時序和管腳約束
create_clock -period 6.667 -name clk150 -waveform {0.000 3.333} -add [get_nets clk]
set_property PACKAGE_PIN V4 [get_ports i_clk]
set_property PACKAGE_PIN V3 [get_ports i_rst]
set_property PACKAGE_PIN V2 [get_ports i_spi_miso]
set_property PACKAGE_PIN W2 [get_ports i_spi_nss]
set_property PACKAGE_PIN W1 [get_ports i_spi_sclk]
set_property PACKAGE_PIN W4 [get_ports o_dac_clk]
set_property PACKAGE_PIN AB1 [get_ports {o_dac_data[15]}]
set_property PACKAGE_PIN AB2 [get_ports {o_dac_data[14]}]
set_property PACKAGE_PIN AB3 [get_ports {o_dac_data[13]}]
set_property PACKAGE_PIN AA1 [get_ports {o_dac_data[12]}]
set_property PACKAGE_PIN AA3 [get_ports {o_dac_data[11]}]
set_property PACKAGE_PIN Y1 [get_ports {o_dac_data[10]}]
set_property PACKAGE_PIN Y2 [get_ports {o_dac_data[9]}]
set_property PACKAGE_PIN Y3 [get_ports {o_dac_data[8]}]
set_property PACKAGE_PIN AA4 [get_ports {o_dac_data[7]}]
set_property PACKAGE_PIN U1 [get_ports {o_dac_data[6]}]
set_property PACKAGE_PIN U2 [get_ports {o_dac_data[5]}]
set_property PACKAGE_PIN T1 [get_ports {o_dac_data[4]}]
set_property PACKAGE_PIN R1 [get_ports {o_dac_data[3]}]
set_property PACKAGE_PIN R2 [get_ports {o_dac_data[2]}]
set_property PACKAGE_PIN P1 [get_ports {o_dac_data[1]}]
set_property PACKAGE_PIN P2 [get_ports {o_dac_data[0]}]
5. 實現報告
?
一個可配波形的DDS就這么簡單!我們可以將多個簡單的程序放在一起,比如增加幅度控制、正交調制、載波相乘、觸發控制等配合依托MCU的交互界面和功放,就變成了一個復雜的信號源了。
6. vivado仿真
?
參照SPI時序,編寫testbench,本例中通過SPI接口配置了兩次頻率控制字,第一次配置為1(頻率為36.621KHz),第二次配置為8(頻率為4.687MHz)。通過仿真結果可以觀察到SPI時序正確,頻率控制字配置正確,輸出波形頻率滿足期望。
?
此圖中輸入50MHz時鐘對應一周期為10ns,倍頻后150MHz時鐘對應一周期為3.333ns,
圖中(31974-18321)/3.333 = 4096,因此頻率與預期一致。
PS:vivado simulator 的仿真效果不錯,就是速度與專業仿真軟件modelsim/questasim相比沒那么給力。當然,如果是用第三方軟件仿真需要編譯庫才可使用。
編譯庫使用compile_simlib命令:
例 compile_simlib -simulator modelsim -family virtex7 -library unisim -library simprim -language vhdl
我喜歡 compile_simlib -simulator modelsim -family virtex7 -library all -language all
編譯好的庫保存在當前工程目錄內,可以拷出來通過修改modelsim.ini 文件,使modelsim直接支持對應的庫,免得以后再編譯。
評論