FPGA的DDS部分学习
这边是32个采样点的正弦波信号,通过DAC输出,也就是数模转换出来的。
如果每1ms输出一个信号,也就是DAC以1000HZ输出,那么下面这样一个完整的正弦信号需要32个点。也就是32ms
所以输出一个完整周期正弦波信号的频率为1000/32HZ(f=1/T,这边完整周期信号的时间周期是32ms,1/32ms就是1000/32HZ)
如果用同样的一组DAC数据来输出一个2*(1000/32)HZ的正弦信号,也就频率变高,时间缩小,也就是原来32ms现在16ms就要输出一个完整的正弦信号。
时间缩短一半,频率提高一倍。
在1ms的DAC输出下,如果取点间隔为1
FCLK = 1000hz (周期1ms),数据输出信号频率
Fo = 1000/32 (N=5),正弦信号输出频率
Fo = FCLK /(2^N)
如果取点间隔为2
Fo = 1000/(32/2)
也就是Fo= 2*1000/32
那么这个2也就可以单独拉出来,频率控制字Fword=2也写成B=2
N是相位累加器中的位数,其实就是精度,通俗的说就是将模拟量分成多少份。分辨率为1/2^N
最终:Fo=FCLK/(2^N/B)=FCLK*B/2^N
dds的时钟频率为FCLK,频率控制字为Fword=1,则输出的频率为Fout=FCLK/2^N,这个频率是“基频”。若Fword=B,则输出频率为Fout=B*FCLK/2^N
1、频率控制字就是上面的B,或者Fword,数值越大,输出信号的频率越高
2、相位控制字,控制输出信号的相位偏移,主要用于相位的信号调制
3、相位累加器=N位加法器+N位相位累加寄存器,每一个时钟脉冲输入时,相位累加器便把频率控制字累加1
也就是说,相位累加器的作用是帮频率控制字进行相位增量,设位宽N,把之前的输出公式改一下
Fout=B*FCLK/2^N改成 B=Fout*2^N/FCLK
也就是通过相位累加寄存器寄存累加的值
4、波形数据表ROM,里面有完成周期的信号,代码中我们设置了正弦,三角,方波三种周期信号。
假设波形数据表ROM的地址位宽为12位,存储数据位宽8位,也就是ROM有2^12=4096个存储空间,每个空间存储1个字节的数据(一个字节8bit)
将一个正弦周期信号,沿横轴等间隔采样4096次,每次采样的幅值用1字节数据表示,最大255,最小值0(8bit,2^8)。将4096次采样结果顺序写入ROM的4096个存储单元。
波形数据表ROM以相位调制器传入的相位码作为ROM读地址,将地址对应存储单元中的电压幅值数字量输出。
(4096个幅值数据是顺序写入ROM的,也就是在ROM中存了一个完整的周期正弦波信号,在完整的周期T内,每个幅值对应的相位不同)
所以可以通过相位调制器输出的相位码,作为读ROM的地址,将存储的幅值数字量读出来。
5、最后给DAC数字转模拟量输出正弦波信号。
module dds_ad9767(
clk,
reset_n,
mode_sel, //选择模式,波形选择
Fword, //频率控制字
Pword, //相位控制字
data
);
input clk;
input reset_n;
input [1:0] mode_sel; //00 01 10 11
input [31:0] Fword;
input [11:0] Pword;
output reg [13:0] data; //AD9767是14位精度
//频率控制字同步寄存器,同步寄存器就是打拍同步
reg [31:0] Fword_r;
always @(posedge clk)
Fword_r <= Fword;
//相位控制字同步寄存器
reg [11:0] Pword_r;
always @(posedge clk)
Pword_r <= Pword;
//相位累加器
reg [31:0] Freq_ACC;
always @(posedge clk or negedge reset_n)
if(!reset_n)
Freq_ACC <= 0;
else
Freq_ACC <= Fword_r + Freq_ACC;
//波形数据表地址
//截取32位累加器的高12位作为ROM的查询地址,这样查询地址也是4096个点。
//和ROM表的单周期的离散点存储位深度一致
wire [11:0] Rom_addr;
assign Rom_addr = Freq_ACC[31:20] + Pword_r;
wire [13:0] data_sine,data_square,data_triangular;
rom_sine rom_sine (
.clka(clk), // input wire clka
.addra(Rom_addr), // input wire [11 : 0] addra
.douta(data_sine) // output wire [13 : 0] douta
);
rom_square rom_square (
.clka(clk), // input wire clka
.addra(Rom_addr), // input wire [11 : 0] addra
.douta(data_square) // output wire [13 : 0] douta
);
rom_triangular rom_triangular (
.clka(clk), // input wire clka
.addra(Rom_addr), // input wire [11 : 0] addra
.douta(data_triangular) // output wire [13 : 0] douta
);
always @(*)
case(mode_sel)
0: data = data_sine;
1: data = data_square;
2: data = data_triangular;
3: data = 8192;
endcase
endmodule
上面这是AD9767芯片的驱动代码,就是根据上面的流程图换算的代码。
下面的顶层模块,通过vio虚拟几个端口出来,因为AD模块一插没啥硬件按键了。
顶层代码
`timescale 1ns / 1ps
`define sim
module dds_top(
Clk,
Reset_n,
DataA,
ClkA,
WRTA,
DataB,
WRTB,
ClkB
);
input Clk;
input Reset_n;
output [13:0]DataA;
output ClkA;
output WRTA;
output [13:0]DataB;
output ClkB;
output WRTB;
wire CLK125M;
assign ClkA = CLK125M;
assign ClkB = CLK125M;
assign WRTA = ClkA;
assign WRTB = ClkB;
reg [31:0]FwordA,FwordB;
reg [11:0]PwordA,PwordB;
reg [1:0]Mode_SelA;
reg [1:0]Mode_SelB;
reg [2:0]CHA_Fword_Sel;
reg [2:0]CHB_Fword_Sel;
reg [2:0]CHA_Pword_Sel;
reg [2:0]CHB_Pword_Sel;
MMCM MMCM(
.clk_out1(CLK125M),
.resetn(Reset_n),
.locked(),
.clk_in1(Clk)
);
dds_ad9767 dds_A(
.clk(CLK125M),
.reset_n(Reset_n),
.mode_sel(Mode_SelA),
.Fword(FwordA),
.Pword(PwordA),
.data(DataA)
);
dds_ad9767 dds_B(
.clk(CLK125M),
.reset_n(Reset_n),
.mode_sel(Mode_SelB),
.Fword(FwordB),
.Pword(PwordB),
.data(DataB)
);
wire CHA_Fword_flag;
wire CHB_Fword_flag;
wire CHA_Pword_flag;
wire CHB_Pword_flag;
wire Mode_SelA_flag;
wire Mode_SelB_flag;
vio_0 vio (
.clk(CLK125M), // input wire clk
.probe_out0(CHA_Fword_flag), // output wire [0 : 0] probe_out0
.probe_out1(CHB_Fword_flag), // output wire [0 : 0] probe_out1
.probe_out2(CHA_Pword_flag), // output wire [0 : 0] probe_out2
.probe_out3(CHB_Pword_flag), // output wire [0 : 0] probe_out3
.probe_out4(Mode_SelA_flag), // output wire [0 : 0] probe_out4
.probe_out5(Mode_SelB_flag) // output wire [0 : 0] probe_out5
);
//对flag信号打两拍,用于上升沿检测
reg CHA_Fword_flag_reg0,CHA_Fword_flag_reg1;
reg CHB_Fword_flag_reg0,CHB_Fword_flag_reg1;
reg CHA_Pword_flag_reg0,CHA_Pword_flag_reg1;
reg CHB_Pword_flag_reg0,CHB_Pword_flag_reg1;
reg Mode_SelA_flag_reg0,Mode_SelA_flag_reg1;
reg Mode_SelB_flag_reg0,Mode_SelB_flag_reg1;
always@(posedge CLK125M or negedge Reset_n)
if(!Reset_n)begin
CHA_Fword_flag_reg0 <= 0;
CHA_Fword_flag_reg1 <= 0;
end
else begin
CHA_Fword_flag_reg0 <= CHA_Fword_flag;
CHA_Fword_flag_reg1 <= CHA_Fword_flag_reg0;
end
always@(posedge CLK125M or negedge Reset_n)
if(!Reset_n)begin
CHB_Fword_flag_reg0 <= 0;
CHB_Fword_flag_reg1 <= 0;
end
else begin
CHB_Fword_flag_reg0 <= CHB_Fword_flag;
CHB_Fword_flag_reg1 <= CHB_Fword_flag_reg0;
end
always@(posedge CLK125M or negedge Reset_n)
if(!Reset_n)begin
CHA_Pword_flag_reg0 <= 0;
CHA_Pword_flag_reg1 <= 0;
end
else begin
CHA_Pword_flag_reg0 <= CHA_Pword_flag;
CHA_Pword_flag_reg1 <= CHA_Pword_flag_reg0;
end
always@(posedge CLK125M or negedge Reset_n)
if(!Reset_n)begin
CHB_Pword_flag_reg0 <= 0;
CHB_Pword_flag_reg1 <= 0;
end
else begin
CHB_Pword_flag_reg0 <= CHB_Pword_flag;
CHB_Pword_flag_reg1 <= CHB_Pword_flag_reg0;
end
always@(posedge CLK125M or negedge Reset_n)
if(!Reset_n)begin
Mode_SelA_flag_reg0 <= 0;
Mode_SelA_flag_reg1 <= 0;
end
else begin
Mode_SelA_flag_reg0 <= Mode_SelA_flag;
Mode_SelA_flag_reg1 <= Mode_SelA_flag_reg0;
end
always@(posedge CLK125M or negedge Reset_n)
if(!Reset_n)begin
Mode_SelB_flag_reg0 <= 0;
Mode_SelB_flag_reg1 <= 0;
end
else begin
Mode_SelB_flag_reg0 <= Mode_SelB_flag;
Mode_SelB_flag_reg1 <= Mode_SelB_flag_reg0;
end
wire CHA_Fword_posedge;
wire CHB_Fword_posedge;
wire CHA_Pword_posedge;
wire CHB_Pword_posedge;
wire Mode_SelA_posedge;
wire Mode_SelB_posedge;
//上升沿检测
assign CHA_Fword_posedge = (!CHA_Fword_flag_reg1) & CHA_Fword_flag_reg0;
assign CHB_Fword_posedge = (!CHB_Fword_flag_reg1) & CHB_Fword_flag_reg0;
assign CHA_Pword_posedge = (!CHA_Pword_flag_reg1) & CHA_Pword_flag_reg0;
assign CHB_Pword_posedge = (!CHB_Pword_flag_reg1) & CHB_Pword_flag_reg0;
assign Mode_SelA_posedge = (!Mode_SelA_flag_reg1) & Mode_SelA_flag_reg0;
assign Mode_SelB_posedge = (!Mode_SelB_flag_reg1) & Mode_SelB_flag_reg0;
always@(posedge CLK125M or negedge Reset_n)
if(!Reset_n)
`ifdef sim
CHA_Fword_Sel <= 4;//仿真时启用
`else
CHA_Fword_Sel <= 0; //板级验证时启用
`endif
else if(CHA_Fword_posedge)
CHA_Fword_Sel <= CHA_Fword_Sel + 1'd1;
else
CHA_Fword_Sel <= CHA_Fword_Sel;
always@(posedge CLK125M or negedge Reset_n)
if(!Reset_n)
`ifdef sim
CHB_Fword_Sel <= 4;//仿真时启用
`else
CHB_Fword_Sel <= 0; //板级验证时启用
`endif
else if(CHB_Fword_posedge)
CHB_Fword_Sel <= CHB_Fword_Sel + 1'd1;
else
CHB_Fword_Sel <= CHB_Fword_Sel;
always@(posedge CLK125M or negedge Reset_n)
if(!Reset_n)
`ifdef sim
CHA_Pword_Sel <= 3;//仿真时使用
`else
CHA_Pword_Sel <= 0;//板级验证时使用
`endif
else if(CHA_Pword_posedge)
CHA_Pword_Sel <= CHA_Pword_Sel + 1'd1;
else
CHA_Pword_Sel <= CHA_Pword_Sel;
always@(posedge CLK125M or negedge Reset_n)
if(!Reset_n)
CHB_Pword_Sel <= 0;//板级验证时使用
else if(CHB_Pword_posedge)
CHB_Pword_Sel <= CHB_Pword_Sel + 1'd1;
else
CHB_Pword_Sel <= CHB_Pword_Sel;
always@(posedge CLK125M or negedge Reset_n)
if(!Reset_n)
Mode_SelA <= 0;
else if(Mode_SelA_posedge)
Mode_SelA <= Mode_SelA + 1'd1;
else
Mode_SelA <= Mode_SelA;
always@(posedge CLK125M or negedge Reset_n)
if(!Reset_n)
`ifdef sim
Mode_SelB <= 1;
`else
Mode_SelB <= 0;
`endif
else if(Mode_SelB_posedge)
Mode_SelB <= Mode_SelB + 1'd1;
else
Mode_SelB <= Mode_SelB;
//频率控制字
//如果把周期完整的一个波形等分成2的32次方份,在时钟频率为125M次/秒的条件下,
//如果希望1秒钟输出一个完成的周期,那么每一拍递进多少份?
always@(*)
case(CHA_Fword_Sel)
0:FwordA = 34;//2**32 / 125000000; 34.35
1:FwordA = 344;//2**32 / 12500000;
2:FwordA = 3436;//2**32 / 1250000;
3:FwordA = 34360;//2**32 / 125000;
4:FwordA = 343597;//2**32 / 12500;
5:FwordA = 3435974;//2**32 / 1250;
6:FwordA = 34359738;//2**32 / 125;
7:FwordA = 343597384;//2**32 / 12.5;
endcase
always@(*)
case(CHB_Fword_Sel)
0:FwordB = 34;//2**32 / 125000000; 34.35
1:FwordB = 344;//2**32 / 12500000;
2:FwordB = 3436;//2**32 / 1250000;
3:FwordB = 34360;//2**32 / 125000;
4:FwordB = 343597;//2**32 / 12500;
5:FwordB = 3435974;//2**32 / 1250;
6:FwordB = 34359738;//2**32 / 125;
7:FwordB = 343597384;//2**32 / 12.5;
endcase
always@(*)
case(CHA_Pword_Sel)
0:PwordA = 0; //0
1:PwordA = 341; //30
2:PwordA = 683; //60
3:PwordA = 1024; //90
4:PwordA = 1707; //150
5:PwordA = 2048; //180
6:PwordA = 3072; //270
7:PwordA = 3641; //320
endcase
always@(*)
case(CHB_Pword_Sel)
0:PwordB = 0; //0
1:PwordB = 341; //30
2:PwordB = 683; //60
3:PwordB = 1024; //90
4:PwordB = 1707; //150
5:PwordB = 2048; //180
6:PwordB = 3072; //270
7:PwordB = 3641; //320
endcase
endmodule
最后板级验证的时候,可以通过vio端口控制电平触发,上一时刻0,这一时刻1,下面解释了,Q是上一时刻的寄存的,D是这一次的。
最后,看到示波器波形输出正弦波和方波,同时通过vio改变观察波形的周期频率变化。
最后算一下,确实是差不多的,比如频率控制字这边的Fword=34(这个是算出来的,2^32/125000000,在125MHz的时钟频率下,把一个正弦波分成2^32份,频率为f=1/T,一个信号发出就是1/125M,2^32次份信号就是2^32/125M)
用上面的公式Fout=B*FCLK/2^N
343597*125M/2^32=10000hz,10KHz(大概约等于,9,999.98883344233036041259765625)
本文来自博客园,作者:祈愿树下,转载请注明原文链接:https://www.cnblogs.com/cjl520/p/18172351