文章目录
- 一、DDS介绍
- 二、DDS原理
- 2.1 频率计算
- 2.2 相位改变
- 2.3 波形切换
- 三、Matlab生成波形文件
- 四、FPGA实现DDS
- 4.1 Verilog代码
- 4.2 仿真验证
- 4.2.1 改变频率
- 4.2.2 切换波形
- 4.2.3 相位调节
- 4.2.4 幅度调节
- 五、Xilinx DDS Compiler的使用
- 5.1 功能框图
- 5.1.1 相位累加器
- 5.1.2 SIN/COS LUT
- 5.2 端口说明
- 5.3 工作原理
- 5.3.1 光栅化
- 5.3.2 输出频率计算
- 5.3.3 相位增量Δθ的计算
- 5.3.4 累加器位宽计算
- 5.4 配置通道
- 5.4.1 CONFIG 通道 TDATA 结构
- 5.4.2 输入相位通道TDATA结构
- 5.4.3 输出数据通道TDATA结构
- 5.5 IP配置
- 5.6 仿真验证IP输出
- 5.7 仿真验证IP动态改变频率
一、DDS介绍
DDS(Direct Digital Synthesizer)也叫直接数字式频率合成器,用于生成精确的模拟信号波形。它通过数字方式直接合成信号,而不是通过模拟信号生成技术。DDS通常由相位累加器、波形存储器(如ROM或RAM)、DAC和低通滤波器组成,DDS结构图如下所示:
二、DDS原理
如上图所示,DDS输出给DAC的波形来源于ROM表,相位累加器作为ROM表的输入地址,然后读出数据。这个ROM表里面存放的就是完整的波形数据,例如:正弦波、方波、三角波、锯齿波等等。
2.1 频率计算
假设ROM的深度的位宽为N,所以ROM一共有
2
N
2^N
2N个数据。读取ROM的时钟为
f
c
l
k
f_{clk}
fclk,如果一个时钟周期读地址+1,那么读出一个完整的波形需要的时间为:
t
i
m
e
=
2
N
∗
1
f
c
l
k
time = 2^N * \frac{1}{f_{clk}}
time=2N∗fclk1
那么读出的波形频率 f o u t f_{out} fout:
f o u t = 1 t i m e = f c l k 2 N f_{out} =\frac{1}{time} = \frac{f_{clk}}{2^N } fout=time1=2Nfclk
如果每一个时钟周期读地址+2,那么读出一个完整的波形需要的时间为:
t
i
m
e
=
2
N
∗
1
f
c
l
k
∗
1
2
time = 2^N * \frac{1}{f_{clk}}*\frac{1}{2}
time=2N∗fclk1∗21
那么读出的波形频率
f
o
u
t
f_{out}
fout:
f
o
u
t
=
1
t
i
m
e
=
f
c
l
k
2
N
∗
2
f_{out} =\frac{1}{time} = \frac{f_{clk}}{2^N } * 2
fout=time1=2Nfclk∗2
我们可以看出,只要控制读rom地址的时间,就能得到不同频率的波形,如果一个时钟周期读地址+B,那么我们可以得到
f
o
u
t
f_{out}
fout:
f
o
u
t
=
f
c
l
k
2
N
∗
B
f_{out} = \frac{f_{clk}}{2^N } * B
fout=2Nfclk∗B
这个B就是DDS结构图中的频率控制字,那么对上式进行变形可以得到:
B
=
f
o
u
t
∗
2
N
f
c
l
k
B= \frac{f_{out} * 2^N}{f_{clk}}
B=fclkfout∗2N
2.2 相位改变
已知我们整个波形的采样点是 2 N 2^N 2N个,我们改变读rom的起始地址,就能改变读出来的波形相位,假设每次相位变换为1°,则相位控制字p_word:
p w o r d = 2 N 360 p_{word} = \frac{ 2^N}{360 } pword=3602N
假设每次相位变换为N°,则相位控制字p_word:
p
w
o
r
d
=
N
∗
2
N
360
p_{word} =N*\frac{ 2^N}{360 }
pword=N∗3602N
2.3 波形切换
我们通过波形切换信号来选择波形输出的是正弦波还是三角波还是其他波。
三、Matlab生成波形文件
从上一章我们可以知道,实现DDS最关键的就是ROM,所以我们需要提前在rom里存放一个完整的波形文件,我们就用Matlabl来生成四个声正弦波、方波、三角波、锯齿波的coe文件。深度都是2048、数据位宽根据DA芯片的位宽来选择,我们这里生成的数据位宽是14位无符号数据。三角波matlab代码如下:
% 参数设置
bitWidth = 14; % 位宽
depth = 2048; % 深度
maxValue = 2^bitWidth - 1; % 无符号数的最大值
% 生成三角波
t = linspace(0, 1, depth);
triangleWave = round(maxValue * (abs(2 * (t - floor(t + 0.5)))));
% 将三角波限制在无符号范围内
triangleWave = uint16(triangleWave);
% 打开文件以写入
fileID = fopen('triangle_wave.coe', 'w');
% 写入COE文件头
fprintf(fileID, 'memory_initialization_radix=10;\n');
fprintf(fileID, 'memory_initialization_vector=\n');
% 写入数据
for i = 1:depth
fprintf(fileID, '%d', triangleWave(i));
if i < depth
fprintf(fileID, ',\n');
else
fprintf(fileID, ';\n');
end
end
% 关闭文件
fclose(fileID);
disp('COE文件生成成功!');
其它波形也同样生成。
四、FPGA实现DDS
4.1 Verilog代码
本次实验实现从10Hz 到10Mhz的DDS,频率分别为 10hz、50hz、100hz、500hz、
1khz、5khz、10khz、50khz、100khz、500khz、1mhz、2mhz、3mhz、4mhz、5mhz、10mhz、
我们来算一下各种频率控制字:我们频率控制字的位宽设置为32位(越大精度越高),给dds读取的时钟频率用50M系统倍频到125M,根据上面的频率控制字的公式,我们算出10hz的频率控制字为:
B
=
f
o
u
t
∗
2
N
f
c
l
k
=
10
∗
2
32
125
∗
1
0
6
=
344
B= \frac{f_{out} * 2^N}{f_{clk}}= \frac{10 * 2^{32}}{125*10^{6}}=344
B=fclkfout∗2N=125∗10610∗232=344
以此类推可以算出其它频率的频率控制字,整个dds代码如下:
module dds (
input sys_clk , //输入125M时钟
input rst , //锁相环lock信号
input wave_change , //波形切换信号
input pha_change , //相位改变信号
input fre_change , //频率改变信号
input ran_change , //幅度改变信号
output reg [13:0] wave_data
);
/***************function**************/
/***************parameter*************/
/***************port******************/
/***************mechine***************/
/***************reg*******************/
reg [31:0] fre_word ; //32位宽的频率控制字
reg [31:0] fre_add ; //32位宽的相位累加器
reg [3:0] fword_sel ; //频率选择
reg [10:0] p_word ; //11位宽的相位控制字
reg [1:0] wave_sel ; //波形选择
reg [1:0] ran_sel ; //幅度选择
reg [13:0] r_wave_data ; //输出波形数据存储器
/***************wire******************/
wire [13:0] triangular_wave ;
wire [13:0] square_wave ;
wire [13:0] sin_wave ;
wire [13:0] sawtooth_wave ;
wire [10:0] addr ; //一共2048个采样点
/***************component*************/
sawtooth_rom u0_sawtooth_rom (
.clka (sys_clk ),
.addra(addr ),
.douta(sawtooth_wave )
);
sin_rom u0_sin_rom (
.clka (sys_clk ),
.addra(addr ),
.douta(sin_wave )
);
square_rom u0_square_rom (
.clka (sys_clk ),
.addra(addr ),
.douta(square_wave )
);
triangular_rom u0_triangular_rom (
.clka (sys_clk ),
.addra(addr ),
.douta(triangular_wave )
);
/***************assign****************/
assign addr = fre_add[31 : 21] + p_word;
/***************always****************/
//每当改变频率信号拉高时,切换频率控制字
always @(posedge sys_clk) begin
if(rst == 1'b0)
fword_sel <= 4'd0;
else if(fre_change == 1'b1)
fword_sel <= fword_sel + 1;
else
fword_sel <= fword_sel;
end
always @(posedge sys_clk) begin
if(rst == 1'b0)
fre_word <= 'd0;
else
case (fword_sel)
4'd0: fre_word <= 32'd344; //10hz
4'd1: fre_word <= 32'd1718; //50hz
4'd2: fre_word <= 32'd3436; //100hz
4'd3: fre_word <= 32'd17180; //500hz
4'd4: fre_word <= 32'd34360; //1khz
4'd5: fre_word <= 32'd171799; //5khz
4'd6: fre_word <= 32'd343597; //10khz
4'd7: fre_word <= 32'd1717987; //50khz
4'd8: fre_word <= 32'd3435974; //100khz
4'd9: fre_word <= 32'd17179869; //500khz
4'd10: fre_word <= 32'd34359738; //1MHZ
4'd11: fre_word <= 32'd68719477; //2MHZ
4'd12: fre_word <= 32'd103079215;//3MHZ
4'd13: fre_word <= 32'd137438953;//4MHZ
4'd14: fre_word <= 32'd171798692;//5MHZ
4'd15: fre_word <= 32'd343597384;//10MHZ
default:fre_word <= 32'd344;
endcase
end
always @(posedge sys_clk) begin
if(rst == 1'b0)
fre_add <= 'd0;
else
fre_add <= fre_add + fre_word;
end
always @(posedge sys_clk) begin
if(rst == 1'b0)
p_word <= 'd0;
else if(pha_change == 1'b1)
p_word <= p_word + 'd57; //每次偏移10°
else
p_word <= p_word;
end
//波形切换
always @(posedge sys_clk) begin
if(rst == 1'b0)
wave_sel <= 'd0;
else if(wave_change == 1'b1)
wave_sel <= wave_sel + 1'b1;
else
wave_sel <= wave_sel;
end
//幅度切换
always @(posedge sys_clk) begin
if(rst == 1'b0)
ran_sel <= 'd0;
else if(ran_change == 1'b1)
ran_sel <= ran_sel + 1'b1;
else
ran_sel <= ran_sel;
end
always @(posedge sys_clk) begin
if(rst == 1'b0)
r_wave_data <= 'd0;
else
case (wave_sel)
2'b00: r_wave_data <= sin_wave;
2'b01: r_wave_data <= square_wave;
2'b10: r_wave_data <= sawtooth_wave;
2'b11: r_wave_data <= triangular_wave;
default: r_wave_data <= sin_wave;
endcase
end
always @(posedge sys_clk) begin
if(rst == 1'b0)
wave_data <= 'd0;
else
case (ran_sel)
2'd0: wave_data <= r_wave_data;
2'd1: wave_data <= {1'b0,r_wave_data[13:1]}; //幅度的1/2
2'd2: wave_data <= {2'b00,r_wave_data[13:2]}; //幅度的1/4
2'd3: wave_data <= {3'b000,r_wave_data[13:3]}; //幅度的1/8
default: wave_data <= r_wave_data;
endcase
end
endmodule
4.2 仿真验证
4.2.1 改变频率
我们一开始不改变频率,就用默认的频率控制字344 来跑一下,观察波形和频率是否正确,仿真代码如下:
`timescale 1ns / 1ps
module tb_dds();
reg sys_clk ;
reg wave_change ;
reg pha_change ;
reg fre_change ;
reg ran_change ;
wire [13:0] wave_data ;
initial begin
sys_clk =0;
wave_change =0;
pha_change =0;
fre_change =0;
ran_change =0;
end
always #10 sys_clk = ~sys_clk;
clk_wiz_0 u_dds_clk
(
.clk_out1 (dds_clk ),
.locked (rst ),
.clk_in1 (sys_clk )
);
dds u_dds(
.sys_clk ( dds_clk ),
.rst ( rst ),
.wave_change ( wave_change ),
.pha_change ( pha_change ),
.fre_change ( fre_change ),
.ran_change ( ran_change ),
.wave_data ( wave_data )
);
endmodule
打开仿真
我们可以看到四种波形可以正常输出,我们选取一个周期时间看为 99.882960ms,打开计算器
可以算出波形频率≈10hz,我们改变一下 fword_sel为10,这样输出的频率应该为1Mhz,代码修改如下:
//每当改变频率信号拉高时,切换频率控制字
always @(posedge dds_clk) begin
if(rst == 1'b0)
fword_sel <= 4'd10;
else if(fre_change == 1'b1)
fword_sel <= fword_sel + 1;
else
fword_sel <= fword_sel;
end
打开仿真
看一个波形周期为1000ns,可以算出波形频率为1Mhz
由此可以看频率切换没问题,我们来观察一些波形切换。
4.2.2 切换波形
我们修改仿真,只观察输出端口的波形数据,让仿真运行一段时间然后给一个切换波形的信号,以此切换四次可以回到初始的sin波形,为了让仿真快一点,我们频率控制字还是1Mhz,仿真代码如下:
`timescale 1ns / 1ps
module tb_dds();
reg sys_clk ;
reg wave_change ;
reg pha_change ;
reg fre_change ;
reg ran_change ;
wire [13:0] wave_data ;
initial begin
sys_clk =0;
wave_change =0;
pha_change =0;
fre_change =0;
ran_change =0;
#10000;
@(posedge dds_clk)
wave_change = 1;
@(posedge dds_clk)
wave_change = 0;
#10000;
@(posedge dds_clk)
wave_change = 1;
@(posedge dds_clk)
wave_change = 0;
#10000;
@(posedge dds_clk)
wave_change = 1;
@(posedge dds_clk)
wave_change = 0;
#10000;
@(posedge dds_clk)
wave_change = 1;
@(posedge dds_clk)
wave_change = 0;
#10000;
$stop;
end
always #10 sys_clk = ~sys_clk;
clk_wiz_0 u_dds_clk
(
.clk_out1 (dds_clk ),
.locked (rst ),
.clk_in1 (sys_clk )
);
dds u_dds(
.sys_clk ( dds_clk ),
.rst ( rst ),
.wave_change ( wave_change ),
.pha_change ( pha_change ),
.fre_change ( fre_change ),
.ran_change ( ran_change ),
.wave_data ( wave_data )
);
endmodule
打开波形观察:
我们在一定的时间给一个wave_change脉冲信号,我们可以看到波形也跟着切换了。切换顺序为sin、方波、锯齿波、三角波和我们代码一致。
4.2.3 相位调节
我们先修改代码,再添加一个sin_rom,让其读地址没有加频率控制字,波形为sin_wave1:
/***************component*************/
sawtooth_rom u0_sawtooth_rom (
.clka (sys_clk ),
.addra(addr ),
.douta(sawtooth_wave )
);
sin_rom u0_sin_rom (
.clka (sys_clk ),
.addra(addr ),
.douta(sin_wave )
);
sin_rom u1_sin_rom (
.clka (sys_clk ),
.addra(fre_add[31 : 21] ),
.douta(sin_wave1 )
);
square_rom u0_square_rom (
.clka (sys_clk ),
.addra(addr ),
.douta(square_wave )
);
triangular_rom u0_triangular_rom (
.clka (sys_clk ),
.addra(addr ),
.douta(triangular_wave )
);
/***************assign****************/
assign addr = fre_add[31 : 21] + p_word;
修改仿真代码,在某个时间给重复给18个周期的相位切换信号,由于代码里我们每一个切换信号,相位偏移10°,所以18个切换信号,相位应该会偏移180°,仿真代码如下:
`timescale 1ns / 1ps
module tb_dds();
reg sys_clk ;
reg wave_change ;
reg pha_change ;
reg fre_change ;
reg ran_change ;
wire [13:0] wave_data ;
initial begin
sys_clk =0;
wave_change =0;
pha_change =0;
fre_change =0;
ran_change =0;
#10000;
repeat(18) begin
@(posedge dds_clk)
pha_change = 1;
end
pha_change = 0;
#10000;
$stop;
end
always #10 sys_clk = ~sys_clk;
clk_wiz_0 u_dds_clk
(
.clk_out1 (dds_clk ),
.locked (rst ),
.clk_in1 (sys_clk )
);
dds u_dds(
.sys_clk ( dds_clk ),
.rst ( rst ),
.wave_change ( wave_change ),
.pha_change ( pha_change ),
.fre_change ( fre_change ),
.ran_change ( ran_change ),
.wave_data ( wave_data )
);
endmodule
打开波形观察:
由上图可以看到,相位调节也没问题。
4.2.4 幅度调节
修改仿真代码,在某个时间给一个给幅度切换信号,持续一段时间后再给一个幅度调节信号,仿真代码如下:
`timescale 1ns / 1ps
module tb_dds();
reg sys_clk ;
reg wave_change ;
reg pha_change ;
reg fre_change ;
reg ran_change ;
wire [13:0] wave_data ;
initial begin
sys_clk =0;
wave_change =0;
pha_change =0;
fre_change =0;
ran_change =0;
#10000;
@(posedge dds_clk)
ran_change = 1;
@(posedge dds_clk)
ran_change = 0;
#10000;
@(posedge dds_clk)
ran_change = 1;
@(posedge dds_clk)
ran_change = 0;
#10000;
$stop;
end
always #10 sys_clk = ~sys_clk;
clk_wiz_0 u_dds_clk
(
.clk_out1 (dds_clk ),
.locked (rst ),
.clk_in1 (sys_clk )
);
dds u_dds(
.sys_clk ( dds_clk ),
.rst ( rst ),
.wave_change ( wave_change ),
.pha_change ( pha_change ),
.fre_change ( fre_change ),
.ran_change ( ran_change ),
.wave_data ( wave_data )
);
endmodule
打开波形观察:
由上图可以看到,幅度调节也没问题。
五、Xilinx DDS Compiler的使用
5.1 功能框图
该核心由两个主要部分组成:相位发生器和 SIN/COS LUT,它们可以单独使用,也可以与可选的抖动发生器一起使用。
5.1.1 相位累加器
相位发生器由一个累加器和一个可选加法器组成),用于添加相位偏移(如上图1所示。当核心定制时,相位增量 (PINC) 和相位偏移 (POFF) 可以独立配置为固定、可编程(使用 CONFIG 通道)或流式(使用输入 PHASE 通道),意思为相位增量和相位偏移有两种路径到相位累加器,如下图路径1和路径2所示:
- 当设置为固定时,DDS输出频率就是在设置IP时设置的,生成后无法更改。
- 当设置为可编程时,CONFIG 通道 TDATA 字段将包含一个用于相关输入(PINC 或 POFF)的子字段,如果已选择两个输入都可编程,则两个输入都包含一个子字段。如果 PINC 和 POFF 均未设置为可编程,则不存在 CONFIG 通道
- 当设置为流式传输时,输入相位通道 TDATA 字段将有一个子字段用于相关输入(PINC 或 POFF),如果两者都被选择为流式传输,则将同时包含这两个子字段。如果 PINC 和 POFF 均未设置为流式传输,并且核心配置为具有相位发生器,则没有输入相位通道
5.1.2 SIN/COS LUT
仅配置为 SIN/COS LUT 时,相位发生器未实现,PHASE_IN 信号使用输入 PHASE 通道输入,并使用查找表转换为正弦和余弦输出。内核可以配置为仅正弦输出、仅余弦输出或两者(正交)输出。每个输出都可以独立配置为取反。可以使用可选的泰勒级数校正来提高精度
5.2 端口说明
DDS IP所有的端口说明如下,实际上根据不同配置,端口数量不一样:
端口名称 | 输入方向 | 是否可编程 | 端口说明 |
aclk | input | no | 上升沿有效 |
aclken | input | yes | 时钟使能信号 |
aresetn | in | yes | 低电平有效同步清除,至少持续两个时钟周期 |
s_axis_config_tvalid | input | yes | CONFIG 通道的 TVALID |
s_axis_config_tready | out | yes | CONFIG 通道的 TREAD |
s_axis_config_tdata | input | yes | CONFIG 通道的 TDATA |
s_axis_config_tlast | input | yes | CONFIG 通道的最后一个数据指示信号 |
s_axis_phase_tvalid | input | yes | 输入相位通道的 TVALID |
s_axis_phase_tready | output | yes | 输入相位通道的 TREADY |
s_axis_phase_tuser | input | yes | 输入相位通道的 TUSER |
s_axis_phase_tlast | input | yes | 输入相位通道的 TLAST |
端口名称 | 输入方向 | 是否可编程 | 端口说明 |
m_axis_phase_tvalid | output | yes | 输出相位的TVALID |
m_axis_phase_tready | input | yes | 输出相位的TREADY |
m_axis_phase_tdata | output | yes | 输出相位通道的 TDATA |
m_axis_phase_tuser | output | yes | TUSER 用于输出PHASE通道 |
m_axis_phase_tlast | output | yes | TLAST 用于输出相位通道 |
m_axis_data_tvalid | output | yes | 输出数据的TVALID |
m_axis_data_tready | input | yes | 输出数据的TREADY |
m_axis_data_tdata | output | yes | 输出数据通道的 TDATA |
m_axis_data_tuser | output | yes | TUSER 用于输出数据通道 |
m_axis_data_tlast | output | yes | TLAST 用于输出数据通道 |
5.3 工作原理
相位发生器与 SIN/COS LUT 配合使用,可提供相位截断 DDS 或泰勒级数校正 DDS。可在两个模块之间添加可选抖动发生器,以提供相位抖动 DDS。工作原理如下:
- 输入寄存器:由于输入的相位增量可以设置为可编程,因此这个寄存器就是将外部输入的相位增量打一拍进来。
- 相位累加器:相位增量进行累加,和前文DDS原理的相位累加器一样
- 量化器:截断相位累加器从而给后面的查找表,例如前文fpga实现dds,相位累加器的位宽是32位,但是ROM的地址位宽只有11位,所以需要截断相位累加器,使其高11位作为ROM的读地址。
- 查找表:可以是ROM,或者RAM也可以是LUT,深度就是 2 B θ 2^{Bθ} 2Bθ。查找表的深度和宽度分别影响信号的相位角分辨率和幅度分辨率
5.3.1 光栅化
DDS 的光栅化操作模式不会截断累积相位。 光栅化操作适用于所需频率为系统时钟的有理分数的配置(输出频率 = 系统频率 * N/M,其中 0 < N < M)。 支持从 9 到 16384 的 M 值。SIN/COS LUT 相应地配置为从 0 到 M-1 的值,这些值描述了一个完整的圆。由于光栅化操作模式下没有相位截断,因此不需要抖动或泰勒校正,因为它们可以减轻相位截断的影响。在光栅化操作中,相位噪声显著降低。因此,输出相位角分辨率和幅度分辨率仅由 LUT 表输出宽度决定。在光栅化模式下,在适用的情况下利用象限对称性来减少内存使用
5.3.2 输出频率计算
- 标准模式下的输出频率:
f o u t = f c l k 2 B θ ∗ Δ θ f_{out} = \frac{f_{clk}}{2^{B_θ} } * {Δθ} fout=2Bθfclk∗Δθ
例如: f c l k f_{clk} fclk = 120MHz, B θ B_θ Bθ=10, Δ θ Δθ Δθ=12,那么输出的频率:
f o u t = f c l k 2 B θ ∗ Δ θ = 120 × 1 0 6 × 12 2 10 = 1.406250 M H z f_{out} = \frac{f_{clk}}{2^{B_θ} } * {Δθ}=\frac{120 × 10^6 ×12}{2^{10}}=1.406250MHz fout=2Bθfclk∗Δθ=210120×106×12=1.406250MHz
精度就是 精度 = f c l k 2 B θ = 0.117187 M H z 精度 = \frac{f_{clk}}{2^{B_θ} } =0.117187MHz 精度=2Bθfclk=0.117187MHz
产生输出频率fout Hz所需的相位增量值Δθ为:
Δ
θ
=
f
o
u
t
∗
2
B
θ
f
c
l
k
Δθ= \frac{f_{out} * 2^{B_θ}}{f_{clk}}
Δθ=fclkfout∗2Bθ
如果 DDS 核心采用时分复用方式来处理多个通道,则每个通道的有效时钟频率会降低。对于 C 通道,所需的相位增量为:
Δ θ = C ∗ f o u t ∗ 2 B θ f c l k Δθ= \frac{C*f_{out} * 2^{B_θ}}{f_{clk}} Δθ=fclkC∗fout∗2Bθ
- 光栅化操作模式下的输出频率:
单通道配置的 DDS 波形的输出频率 fout 是系统时钟频率 fclk、模数 M 和相位增量值 ΔΘ 的函数。输出频率定义为:
f
o
u
t
=
f
c
l
k
∗
Δ
θ
M
f_{out} = \frac{f_{clk}*Δθ}{M }
fout=Mfclk∗Δθ
例如:
f
c
l
k
f_{clk}
fclk = 120MHz,
M
M
M=1000,
Δ
θ
Δθ
Δθ=12,那么输出的频率:
f
o
u
t
=
120
×
1
0
6
×
12
1000
=
1.44
M
H
z
f_{out} = \frac{120 × 10^6 ×12}{1000}=1.44MHz
fout=1000120×106×12=1.44MHz
精度就是
精度
=
f
c
l
k
M
=
0.12
M
H
z
精度 = \frac{f_{clk}}{M} =0.12MHz
精度=Mfclk=0.12MHz
5.3.3 相位增量Δθ的计算
对于标准模式,0 到 2 N − 1 2^{N-1} 2N−1 范围内的相位增量值描述范围 [0,360]°(其中 N 是相位累加器中的位数)。对于光栅化模式,由于内部实现,相位增量值必须被视为无符号。相位增量值 [0 到 Modulus-1] 描述范围 [0,360]°
- 标准化操作
例如 f c l k f_{clk} fclk=100MHz, B θ B_θ Bθ=18,想要生成 f o u t f_{out} fout=19MHz的波形,Δθ:
Δ θ = f o u t ∗ 2 B θ f c l k = 19 × 1 0 6 × 2 18 100 × 1 0 6 = 49807.36 Δθ= \frac{f_{out} * 2^{B_θ}}{f_{clk}} = \frac{19 × 10^6×2^{18}}{100×10^6} =49807.36 Δθ=fclkfout∗2Bθ=100×10619×106×218=49807.36
该值必须取整,因此实际上的
Δ
θ
Δθ
Δθ=49807,带入到输出频率计算公式里:
f
o
u
t
=
f
c
l
k
2
B
θ
∗
Δ
θ
=
100
×
1
0
6
×
49807
2
18
=
18.9998627
M
H
z
f_{out} = \frac{f_{clk}}{2^{B_θ} } * {Δθ}=\frac{100 × 10^6 ×49807}{2^{18}}=18.9998627MHz
fout=2Bθfclk∗Δθ=218100×106×49807=18.9998627MHz
- 光栅化操作
例如 f c l k f_{clk} fclk=100MHz, M M M=1536,想要生成 f o u t f_{out} fout=19MHz的波形,Δθ:
Δ θ = f o u t ∗ M f c l k = 19 × 1 0 6 × 1536 100 × 1 0 6 = 291.84 Δθ= \frac{f_{out} * M}{f_{clk}} = \frac{19 × 10^6×1536}{100×10^6} =291.84 Δθ=fclkfout∗M=100×10619×106×1536=291.84
该值必须取整,因此实际上的 Δ θ Δθ Δθ=292,带入到输出频率计算公式里:
f o u t = 100 × 1 0 6 × 292 1536 = 19.0104167 M H z f_{out} = \frac{100 × 10^6 ×292}{1536}=19.0104167MHz fout=1536100×106×292=19.0104167MHz
5.3.4 累加器位宽计算
相位宽度与系统时钟频率一起决定了 DDS 的频率分辨率。累加器必须具有足够的场宽度才能达到所需的频率分辨率。例如:如果所需分辨率为 1 Hz,时钟频率为 100 MHz,则累加器所需的宽度为:
B θ = l o g 2 ( f c l k Δ f ) = l o g 2 ( 100 × 1 0 6 1 ) = 26.5754 = 27 b i t B_{θ} = log_2(\frac{f_{clk}}{Δf})=log_2(\frac{100 ×10^6}{1})=26.5754=27 bit Bθ=log2(Δffclk)=log2(1100×106)=26.5754=27bit
5.4 配置通道
CONFIG 通道是非阻塞的,这意味着 DDS Compiler 的其他通道不会等待来自 CONFIG 通道的数据。要对 CONFIG 通道进行编程,必须进行 N 次传输,其中 N 是通道数。每次传输都包含从通道 0 开始按顺序排列的每个通道的 PINC 和/或 POFF 值。对于通道 (索引 N-1),只有最后一次传输必须使 TLAST 置位。否则会导致 event_s_config_tlast_missing 或 event_s_config_tlast_unexpected 输出在一个周期内置位。
在光栅化模式下,相位发生器的相位信号可以精确地表示为整数,这是由于相位发生器与系统时钟之间存在有理分数关系,并且相位向量不会发生截断。因此,正弦/余弦信号的保真度仅取决于正弦/余弦表条目的准确性和精度。与标准模式不同,没有时间基准抖动。标准操作模式中描述的时间基准抖动的影响不会影响光栅化配置。 由于相位累加器没有相位向量截断,并且所有可能的相位值在正弦/余弦表中都有相应的条目,因此无需补偿或减轻相位误差,因为没有相位误差。抖动和泰勒级数校正是两种减少相位误差影响的技术。由于它们不是必需的,因此在光栅化模式下不提供
5.4.1 CONFIG 通道 TDATA 结构
当 CONFIG 通道配置为为每个 TDM 通道提供 PINC 和 POFF 值时,每个字段都会进行符号扩展以适应字节边界,然后这些面向字节的字段将在最低有效位置与 PINC 连接起来。例如,对于 11 位的相位宽度,PINC 将占用位 10:0,而 POFF 将占用位 26:16。因此,s_axis_config_tdata 总体上将是 31:0。
- PINC 和 POFF 都设置为可编程:
- PINC 仅设置为可编程
3. POFF 仅设置为可编程
5.4.2 输入相位通道TDATA结构
- PINC 和 POFF 均设置为 Streaming
- PINC 仅设置为 Streaming
- POFF 仅设置为 Streaming
- DDS 配置为仅 SIN/COS LUT
5.4.3 输出数据通道TDATA结构
5.5 IP配置
我们这里算一下相位增量是否正确,根据上面的公式:
Δ θ = f o u t ∗ 2 B θ f c l k = 2 × 1 0 6 × 2 24 100 × 1 0 6 = 335544.32 Δθ= \frac{f_{out} * 2^{B_θ}}{f_{clk}} = \frac{2 × 10^6×2^{24}}{100×10^6} =335544.32 Δθ=fclkfout∗2Bθ=100×1062×106×224=335544.32
和配置界面的增量一样。
5.6 仿真验证IP输出
我们先试一下最简单功能,因此仿真只需要给IP一个时钟和复位即可,仿真代码如下:
`timescale 1ns / 1ps
module tb_dds_ip();
reg aclk ;
reg aresetn ;
wire m_axis_data_tvalid ;
wire [31:0] m_axis_data_tdata ;
wire m_axis_phase_tvalid ;
wire [23:0] m_axis_phase_tdata ;
wire [15:0] sin ;
wire [15:0] cos ;
assign sin = m_axis_data_tdata[31:16];
assign cos = m_axis_data_tdata[15:0];
initial begin
aclk =0;
aresetn =0;
#1000;
aresetn =1;
end
always #5 aclk = ~aclk;
dds_compiler_0 u_dds_compiler_0 (
.aclk(aclk), // input wire aclk
.aresetn(aresetn), // input wire aresetn
.m_axis_data_tvalid(m_axis_data_tvalid), // output wire m_axis_data_tvalid
.m_axis_data_tdata(m_axis_data_tdata), // output wire [31 : 0] m_axis_data_tdata
.m_axis_phase_tvalid(m_axis_phase_tvalid), // output wire m_axis_phase_tvalid
.m_axis_phase_tdata(m_axis_phase_tdata) // output wire [23 : 0] m_axis_phase_tdata
);
endmodule
允许并打开仿真
我们可以看到频率= 1s/500ns=2Mhz
5.7 仿真验证IP动态改变频率
接下来我们试一下动态更改频率,打开IP修改配置如下:
仿真代码如下:
`timescale 1ns / 1ps
module tb_dds_ip();
reg aclk ;
reg aresetn ;
reg s_axis_config_tvalid;
reg [23:0] s_axis_config_tdata =24'hA3D70 ;
wire m_axis_data_tvalid ;
wire [31:0] m_axis_data_tdata ;
wire m_axis_phase_tvalid ;
wire [23:0] m_axis_phase_tdata ;
wire [15:0] sin ;
wire [15:0] cos ;
assign sin = m_axis_data_tdata[31:16];
assign cos = m_axis_data_tdata[15:0];
initial begin
aclk =0;
aresetn =0;
s_axis_config_tvalid = 0;
#10000;
aresetn =1;
#10000;
@(posedge aclk)
s_axis_config_tvalid = 1;
s_axis_config_tdata =24'hA3D70; //4MHZ
@(posedge aclk)
s_axis_config_tvalid = 0;
s_axis_config_tdata =24'hA3D70 + 24'h51EB8; //5mhz
#20000;
@(posedge aclk)
s_axis_config_tvalid = 1;
@(posedge aclk)
s_axis_config_tvalid = 0;
end
always #5 aclk = ~aclk;
dds_compiler_0 u_dds_compiler_0 (
.aclk(aclk), // input wire aclk
.aresetn(aresetn), // input wire aresetn
.s_axis_config_tvalid(s_axis_config_tvalid), // input wire s_axis_config_tvalid
.s_axis_config_tdata(s_axis_config_tdata), // input wire [23 : 0] s_axis_config_tdata
.m_axis_data_tvalid(m_axis_data_tvalid), // output wire m_axis_data_tvalid
.m_axis_data_tdata(m_axis_data_tdata), // output wire [31 : 0] m_axis_data_tdata
.m_axis_phase_tvalid(m_axis_phase_tvalid), // output wire m_axis_phase_tvalid
.m_axis_phase_tdata(m_axis_phase_tdata) // output wire [23 : 0] m_axis_phase_tdata
);
endmodule
运行并打开仿真:
我们放大看一下频率是多少:
我们可以看到,在相位增量改变之前,我们的频率还是2Mhz。
我们可以看到,在相位增量改变之后,我们的频率变成了4Mhz,符合我们的相位增量24’HA3D70。