文章目录
前言
本实验是DDS IP数字波形合成,具体的要求以及开发流程如下。
DDS IP数字波形合成实验流程:
1.使用 Vivado的IPI工具,例化DDS IP。
2.DDS需要能够配置频率字(相位增量)。
3.DDS工作时钟使用PL的板载50MHz时钟。
4.使用ILA工具观察波形,使用VIO设定频率字。
5.在ILA的波形窗口里,观察你设定的波形的周期,验证你频率字设定的正确性。
6.把ILA波形导出到CSV文件,波形样点长度不小于2048点,在Matlab里分析波形的频谱,验证你生成波形的正确性。
7.使用VIO更改频率字,分别生成1MHz和3MHz的正弦波形。
使用以上流程,验证你输出波形的正确性。
一、DDS简介
DDS(Direct Digital Synthesizer)是直接数字式频率合成器的英文缩写,它同DSP(Digital Signal Processing,数字信号处理)一样,也是一项关键的数字化技术,与传统的频率合成器相比,DDS具有低成本、低功耗、高分辨率和快速转换时间等优点,广泛使用在电信与电子仪器领域,是实现设备全数字化的一个关键技术。
DDS是一种把数字信号通过数模(D/A)转换器转换成模拟信号的数字合成技术,其特点是从相位概念出发直接合成所需要的波形。DDS的具体工作过程是由N位相位累加器、N位加法器和N位累加寄存器组成,每来一个时钟脉冲,N位加法器将频率控制字K与N位累加寄存器输出的累加相位数据相加,并把相加后的结果送至累加寄存器的输入端,累加寄存器一方面将上一时钟周期作用后所产生的新的相位数据反馈到加法器的输入端,使加法器在下一时钟的作用下继续与频率控制字K相加;另一方面将这个值作为取样地址送入幅度/相位转换电路,幅度/相位转换电路根据这个地址输出相应的波形数据,最后经D/A转换器和LPF(低通滤波器)将波形数据转换成所需要的模拟波形。
二、添加DDS IP核
首先创建一个名为dds_test的工程,新工程到下图所示的界面后点击Finish即可完成工程的创建。
然后按照下图中序号找到DDS Compiler双击打开。
在Configuration栏目下的设置如下图所示。
Phase Generator and SIN COS LUT:DDS由Phase Generator和SIN/COS组合提供,并且带有可选的相位抖动。(LUT,Look Up Table,即查找表)
Phase Generator only:仅仅提供相位输出。
SIN COS LUT only:只提供了带有可选泰勒级数校正电路的SIN/COS LUT。
Frequency Resolution(频率分辨率):这个参数与输出相位数据的宽度和系统时钟频率相关,如果想要得到n bit的输出位宽,且系统输入时钟频率为 f Hz,则:
F
r
e
q
u
e
n
c
y
R
e
s
o
l
u
t
i
o
n
=
f
2
n
Frequency Resolution =\frac{f}{2^n}
FrequencyResolution=2nf
这里的输出位宽可以参考总结栏目。
SFDR(Spurious Free Dynamic Range)与输出宽度的关系如下表所示。
在Implementation栏目下的Phase Increment Programmability(相位增量可编程性)下选择Programmable。
在Output Frequencies下的设置如下图所示。
以上未提及的栏目保持默认设置即可,点击OK,在弹出的框中选择Generate即可生成DDS IP。
三、添加VIO IP核
在IP Catalog搜索栏输入vio并找到VIO(Virtual Input/Output)双击打开。
在General Options栏目下的设置如下图所示。
切换到PROBE_OUT Ports栏目下的设置如下图。
点击OK,在弹出的框中选择Generate即可生成VIO IP。
四、添加ILA IP核
在IP Catalog搜索栏输入ila并找到ILA(Integrated Logic Analyzer)双击打开。
在General Options栏目下的设置如下图所示。
根据代码给每个探针设置位数如下图所示。
点击OK,在弹出的框中选择Generate即可生成ILA IP。
五、编写测试程序
新建名为dds_test的Verilog文件,依次按照下图中标注的序号进行即可。
在新建好的dds_test.v文件中写入如下代码。
//该代码来自博客:vivado VIO IP的用法,见总结
`timescale 1ns / 1ps
module dds_test(
input sys_clk, //系统时钟 50MHz T=20ns
input rst_n //系统复位
);
//----------VIO按键控制频率控制字(key_PINC)--------------//
wire [1:0] key_PINC;
vio_0 vio_0_inst (
.clk(sys_clk), // input wire clk
.probe_out0(key_PINC) // output wire [1 : 0] probe_out0
);
//---------------信号频率控制模块--------------//
wire [23:0] Fword ; //频率字
Fword_set Fword_set_inst(
//input
.clk (sys_clk ),
.rst_n (rst_n ),
.key_PINC (key_PINC ),
//output
.Fword (Fword )
);
//---------------DDS模块--------------//
//input
wire [0:0] fre_ctrl_word_en ;
//output
wire [0:0] m_axis_data_tvalid ;
wire [47:0] m_axis_data_tdata ;
wire [0:0] m_axis_phase_tvalid ;
wire [23:0] m_axis_phase_tdata ;
assign fre_ctrl_word_en=1'b1;
//例化DDS IP
dds_compiler_0 dds_compiler_0_inst (
.aclk (sys_clk ), // input wire aclk
.s_axis_config_tvalid (fre_ctrl_word_en ), // input wire s_axis_config_tvalid
.s_axis_config_tdata (Fword ), // 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 [47 : 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
);
ila_0 ila_0_inst (
.clk(sys_clk), // input wire clk
.probe0(key_PINC), // input wire [1:0] probe0
.probe1(Fword), // input wire [23:0] probe1
.probe2(m_axis_data_tdata) // input wire [47:0] probe2
);
endmodule
其中例化dds部分的代码来自dds_compiler_0中的dds_compiler_0.veo文件,不过需要将括号内的参数做一修改。
例化vio部分的代码来自vio_0中的vio_0.veo文件,同样需要将括号内的参数做一修改。
例化ila逻辑分析仪部分的代码来自ila_0中的ila_0.veo文件,同样需要将括号内的参数做一修改。
按下图中序号先右击Fword_set_init选择Add Sources,然后新建Fword_set.v文件。
在Fword_set.v文件中写入如下代码。
//该代码来自博客:vivado VIO IP的用法,见总结
`timescale 1ns / 1ps
module Fword_set(
input clk ,
input rst_n ,
input [1:0] key_PINC ,
output reg [23:0] Fword
);
always@(*)
begin
case(key_PINC)
0: Fword <= 'h51eb; //1Mhz 20971.52 取整20971
1: Fword <= 'ha3d7; //2Mhz 41943.04 取整41943
2: Fword <= 'hf5c2; //3Mhz 62914.56 取整62914
//3: Fword <= 'h33333; //10Mhz 209715.2 取整209715
endcase
end
endmodule
上面代码中有关相位增量Δθ的计算如下。
输
出
频
率
=
系
统
时
钟
频
率
×
相
位
增
量
查
找
表
的
深
度
输出频率 =\frac{系统时钟频率×相位增量}{查找表的深度}
输出频率=查找表的深度系统时钟频率×相位增量
转化为对应的公式为:
从而得到相位增量的计算式子如下:
在本实验中,已知系统时钟频率为50MHz,相位宽度为20,若要得到1MHz的输出频率,相位增量的计算如下:
Δ
θ
=
1
M
H
z
×
2
20
50
M
H
z
=
20971.52
Δθ =\frac{1MHz×2^{20}}{50MHz}=20971.52
Δθ=50MHz1MHz×220=20971.52
其他的输出频率也可以根据上式计算得到对应的相位增量,并将其取整转化为十六进制即可。
至此,该Vivado工程的目录结构如下图所示。
六、分配管脚
本实验中需要分配管脚的只有时钟信号sys_clk(管脚为U18)和复位信号rst_n(管脚为N15),按照下图中的数字顺序即可完成管脚的分配。
管脚分配完成后Ctrl+S保存,名称与工程名保持一致。
管脚分配的信息在dds_test.xdc文件中。
七、连接开发板测试
连接开发板,点击Generate Bitstream生成比特流文件,将其下载到开发板上。
这时候会出现hw_ila_1和hw_vios两个窗口,先切换到hw_vios窗口,按照下图中标注的顺序依次操作添加key_PINC[1:0]信号。
先设置为key_PINC[1]=0,key_PINC[0]=0,如下图所示。
按照代码中的设置,输出应当是频率为1MHz的正弦波,但是输出的并不是波形,即模拟量,而是数字量,如下图所示。
再按照下图中的序号设置信号m_axis_data_tdata[47:0]的输出模式为Analog而非Digital。
设置完成后信号m_axis_data_tdata[47:0]的输出如下图所示,但是输出的是方波,并不是想象中的正弦波,那么问题出在哪里呢?
其实是基数的进制设置问题,按照下图中标注的顺序依次设置输出信号的基数为Signed Decimal,即有符号的十进制数。
设置完成后输出如下图所示,此时信号m_axis_data_tdata[47:0]的输出看起来才有点正弦波的样子,但幅度有点小。
将鼠标放到波形附近按住鼠标左键向上移动鼠标为缩小波形的幅度,向下移动鼠标为放大波形的幅度,调整波形的幅度至适合为止。
如下图所示,这时候输出的正弦波就比较规整了。
按照代码中频率字的设置,下图就是1MHz输出频率所对应的正弦波,通过放置光标测得其一个周期大概是50个单位长度(这里并不知道横坐标的单位,故无法知道波形的周期)。
再设置key_PINC[1]=0,key_PINC[0]=1,下图就是1MHz输出频率所对应的正弦波,通过放置光标测得其一个周期大概是25个单位长度。
再设置key_PINC[1]=1,key_PINC[0]=0,下图就是3MHz输出频率所对应的正弦波,通过放置光标测得其一个周期大概是17个单位长度。
单从不同输出频率对应的正弦波周期来看,输出的正弦波大致是正确的,但是要进一步验证波形的正确性,需要通过观察其周期是否为相应输出频率的倒数。
八、Simulator仿真
通过连接开发板测试已经知道了在不同的输出频率下,一个周期的正弦波在ILA下对应的单位长度是有关系的。比如上面硬件测试中输出频率为1MHz的正弦波,一个周期大概是50个单位长度;输出频率为2MHz的正弦波,一个周期大概是25个单位长度;而输出频率为3MHz的正弦波,一个周期大概是17个单位长度。根据输出频率之间的倍数关系可以判断其周期的对应关系是正确的,但是要知道某一输出频率所对应的正弦波周期,通过ILA是无法知道的,因为没有时间刻度,这时候我们可以通过写一个testbench文件来进行仿真。
首先右击Simulation Sources选择Add Source按照下图中标注的顺序新建testbench文件。
在tb_dds_test.v文件中写入如下代码。
`timescale 1ns / 1ps
module tb_dds_test();
// Inputs
reg sys_clk;
reg rst_n;
// Instantiate the Unit Under Test (UUT)
dds_test uut (
.sys_clk (sys_clk),
.rst_n (rst_n)
);
initial
begin
// Initialize Inputs
sys_clk = 0;
rst_n = 1;
end
always #10 sys_clk = ~ sys_clk; //20ns一个周期,产生50MHz时钟源
endmodule
保存代码后选择SIMULATION下的Run Simulation,选择第一个行为仿真。
将需要观察的信号key_PINC[1:0]、Fword[23:0]以及m_axis_data_tdata[47:0]拖入到波形仿真窗口,设置仿真时间为10us,仿真启动后会自动运行1000ns,所以先按Restart键消除掉启动时仿真的1000ns波形,再按Run键运行10us,这时key_PINC[1:0]的值为00,我这里是以二进制显示的,key_PINC[1:0]=00对应的输出频率为1MHz,其周期理论上应当为1000ns。
再设置key_PINC[1:0]的值为01,但是在仿真的界面怎么修改呢?这里我反复尝试了好几次才成功。
首先展开key_PINC[1:0],右击[0],在弹出的下拉框中选择Force Constant…,因为key_PINC[1:0]的默认值是00,所以要给其一个强制常量。
点击Force Constant…后弹出如下对话框,将其数值基数改为二进制,强制常量设置为1,点击OK即可。
修改完成后再按Run键运行10us,这时key_PINC[1:0]的值为01,key_PINC[1:0]=01对应的输出频率为2MHz,其周期理论上应当为500ns。
再设置key_PINC[1:0]的值为10,按Run键运行10us,key_PINC[1:0]=10对应的输出频率为3MHz,其周期理论上应当为333.33ns。
信号key_PINC[1:0]的不同值所对应的频率字代码如下。
0: Fword <= 'h51eb; //1Mhz 20971.52 取整20971
1: Fword <= 'ha3d7; //2Mhz 41943.04 取整41943
2: Fword <= 'hf5c2; //3Mhz 62914.56 取整62914
我们先来看1MHz、2MHz、3MHz总体的输出图,如下图所示。
这里仍然要先把信号m_axis_data_tdata[47:0]设置成模拟,并且把数值基数设置为有符号的十进制,同时纵向调节输出波形大小才能得到如上图所示的正弦波形。然后再逐个将不同输出频率对应的正弦波放大通过添加标记来查看其周期。
首先是1MHz输出频率对应的正弦波形,如下图所示,通过添加标记可知其周期为1000ns,正好是输出频率的倒数,因此输出的正弦波是正确的。
接着是2MHz输出频率对应的正弦波形,如下图所示,通过添加标记可知其周期为500ns,正好是输出频率的倒数,因此输出的正弦波也是正确的。
再接着是3MHz输出频率对应的正弦波形,如下图所示,通过添加标记可知其周期约为333.33ns,正好是输出频率的倒数,因此输出的正弦波也是正确的。
以上就是Simulator仿真的所有过程以及内容了,我们通过仿真验证了ILA下无法验证的问题,这让我们对输出正弦波的正确性有了把握。
九、Matlab中分析输出波形
1、导出CSV文件
通过Matlab怎么判断ILA下各输出频率所对应的正弦波是否正确呢?
先通过Vivado软件ILA仿真导出CSV文件,在导出前先将FWord[23:0]的基数形式改为无符号的十进制,否则会在Matlab读取该文件时出错。导出CSV文件需要先点击下图中圈中的按钮。
接着会弹出如下对话框,修改格式为CSV,文件保存的位置最好是在该工程下,当然也可以保存到其他文件夹下,由于我这里有三个波形文件需要导出,因此在导出时我会给文件做好重命名,便于区分。
再依次将输出频率为2MHz和3MHz的波形CSV文件导出到同一文件夹下,如下图所示。
CSV文件打开后的部分内容如下图所示,里面存放着六列数据,总共有4096行,其中在Matlab中需要用到的只有最后一列,即m_axis_data_tdata[47:0]这一列数据。
接下来就是在Matlab软件中的具体操作了。
2、使用信号分析仪分析波形
先新建一个脚本文件,输入如下代码并保存。
filename1 = 'G:\ZYNQfiles\dds_test\iladata_1MHz.csv'; %这里的文件位置以自己保存的位置为准
filename2 = 'G:\ZYNQfiles\dds_test\iladata_2MHz.csv';
filename3 = 'G:\ZYNQfiles\dds_test\iladata_3MHz.csv';
M1 = csvread(filename1,1,0); %这里的1,0代表从该文件的第1行第0列开始读数直到文件末,因为上图中CSV文件的第0行为各列的名称,类型不是数据,因此无法读取,故从第1行开始读取数据
M2 = csvread(filename2,1,0);
M3 = csvread(filename3,1,0);
R2019a版本之后的Matlab使用readmatrix()函数可代替csvread()函数,且更加方便,我的Matlab版本较低,只能使用csvread()函数读取CSV文件中的数值。
关于csvread()函数的具体使用可以在Matlab的命令行中输入如下命令查看。
doc csvread
输入上面的命令后打开的界面如下图所示。
运行该脚本以后,如果没有什么错误,其工作区的内容就会如下图所示。
接着点击signal analyzer打开信号分析仪。
信号分析仪的界面如下图所示。
将界面左下方的M1、M2、M3分别拖入右侧坐标图中查看其频谱图。
按照下图中的序号依次设置可以得到下图,通过光标定位纵坐标最大处,读出此时横坐标的值为0.04,横坐标就是归一化频率。
归一化频率与输出频率以及系统时钟频率的关系如下式。
归
一
化
频
率
=
输
出
频
率
系
统
时
钟
频
率
的
一
半
归一化频率 =\frac{输出频率}{系统时钟频率的一半}
归一化频率=系统时钟频率的一半输出频率
1
M
H
z
25
M
H
z
=
0.04
\frac{1MHz}{25MHz}=0.04
25MHz1MHz=0.04
计算所得的归一化频率0.04与上图中读到的横坐标相同,因此1MHz输出频率的正弦波是按照频率字的设定输出的。
同理可得M2的频谱图如下图所示。
2
M
H
z
25
M
H
z
=
0.08
\frac{2MHz}{25MHz}=0.08
25MHz2MHz=0.08
计算所得的归一化频率0.08与上图中读到的横坐标相同,因此2MHz输出频率的正弦波是按照频率字的设定输出的。
M3的频谱图如下图所示。
3
M
H
z
25
M
H
z
=
0.12
\frac{3MHz}{25MHz}=0.12
25MHz3MHz=0.12
计算所得的归一化频率0.12与上图中读到的横坐标相同,因此3MHz输出频率的正弦波是按照频率字的设定输出的。
通过Matlab分析输出波形,各输出频率对应的正弦波也是正确的。
3、写脚本文件做FFT
将上面从Vivado文件中导出的三个CSV文件导入到Matlab软件中,具体的操作按照下图中的序号进行,注意导入数据的文件夹路径为你的CSV文件所在的位置,下图中只是以我的CSV文件存放的位置为例。
按照上图中的步骤点击打开后,再点击下图中圈中的导入所选内容按钮,若成功则会显示以下变量已导入,同理将其余两个文件的数据都导入进来。
然后再新建脚本文件dds_test_fft.m,写入如下代码。
csv1_row6 = iladata1MHz{:,6}; %iladata_1MHz.csv文件中的第六列数据
csv2_row6 = iladata2MHz{:,6}; %iladata_2MHz.csv文件中的第六列数据
csv3_row6 = iladata3MHz{:,6}; %iladata_3MHz.csv文件中的第六列数据
fs=50000000; %设置采样频率为50MHz
N=4096; %采样点
n=0:N-1;
t=n/fs;
f=n*fs/N; %频率序列
figure;
subplot(2,1,1);plot(t,csv1_row6);grid on;title('1MHz输出频率对应的正弦波');
y1=abs(fft(csv1_row6,N));
subplot(2,1,2);plot(f,y1,'r');grid on;title('1MHz输出频率对应的频谱');
figure;
subplot(2,1,1);plot(t,csv2_row6);grid on;title('2MHz输出频率对应的正弦波');
y2=abs(fft(csv2_row6,N));
subplot(2,1,2);plot(f,y2,'r');grid on;title('2MHz输出频率对应的频谱');
figure;
subplot(2,1,1);plot(t,csv3_row6);grid on;title('3MHz输出频率对应的正弦波');
y3=abs(fft(csv3_row6,N));
subplot(2,1,2);plot(f,y3,'r');grid on;title('3MHz输出频率对应的频谱');
保存后运行该脚本文件,得到相应的输出图如下。
输出频率为1MHz的输出图如下。
可以清楚地看到频谱图中有两个尖峰,通过光标定位,一个位于1MHz处,一个位于49MHz处,可知该正弦波确实为1MHz输出频率对应的输出,因此输出结果正确。
输出频率为2MHz的输出图如下。
该频谱图中同样有两个尖峰,通过光标定位,一个位于2MHz处,一个位于48MHz处,可知该正弦波确实为2MHz输出频率对应的输出,因此输出结果也是正确的。
输出频率为3MHz的输出图如下。
该频谱图中同样也有两个尖峰,通过光标定位,一个位于3MHz处,一个位于47MHz处,可知该正弦波确实为3MHz输出频率对应的输出,因此输出结果也是正确的。
总结
以上就是ZYNQ FPGA实验——DDS IP数字波形合成的内容了,整个实验的过程还是很考验各项基础的,因此要熟练的掌握Vivado软件的各项操作,尤其是例化IP核、Simulator仿真以及硬件调试等操作,这是在实验中经常用到的。此外,通过Vivado软件导出CSV文件并在Matlab中查看其频谱图也是相当重要的实验手段。
本文参考博文:
vivado VIO IP的用法
FPGA数字信号处理基础----Xilinx DDS IP使用