基于实验一DDS IP 数字波形合成设计完成ADDA测试,关于实验一可参考上一篇文章DDS的理解及IP核的使用_Laid-back guy的博客
实验内容参考Zynq FPGA实验_DUWT实验的博客
目录
实验内容:
- 注意,AN108是34针的插头,注意其插装位置,1脚和zynq底板对齐,不要插错
- 黑金AN108的低通滤波器通带为0-20MHz左右
- 基于“DDS IP 数字波形合成DAC ” 实验方案,使用50MHz时钟频率,使用DAC输出正弦波。
- 把DAC输出模拟信号自环给ADC的输入
- 使用MMCM分频,给ADC提供25MHz采样时钟
- 使用ILA捕获ADC的输出数据,不少于2048样点。
- 使用Matlab分析ADC数据频谱
- 用VIO更改频率字,生成1MHz和3MHz的正弦信号,用Matlab分析ILA数据验证频谱正确。
ADDA模块
ADDA,包含ADC与DAC(Analog to Digital Converter/ Digital to Analog Converter,即模数转换器/数模转换器),能实现模拟到数字再到模拟信号的转换过程。
本实验采用的是ALINX的AN108,包含一块高速DA转换芯片AD9708,转换位数为8位,最大转换速度为125MSPS以及一块高速AD转换芯片,转换位数为8位,最大转换速度为32MSPS
AN108的实物图及结构图如下
其中7阶巴特沃斯低通滤波器的带宽为40Mhz,幅度调节的输出范围为-5~5V(10Vpp) ,其中实物图中右侧黑色旋钮为滑动变阻器节输出的电压范围,推荐通过调节滑动变阻器,使输出的电压范围在-5V至+5V之间
衰减电路的转换公式为,将-5~5V的输入电压转换为0~2V的输出电压送入高速AD芯片
程序设计
根据实验二的要求,利用实验一设计的DDS模块输出1Mhz/3Mhz正弦波并将其送入DAC进行DA转换得模拟信号,将模拟信号自环给ADC进行AD转换得到该程序输出的数字信号。
根据上述要求,可以设计出如下ADDA测试的系统框图
顶层模块的RTL图如下
代码如下:
`timescale 1ns / 1ps
module adda_test(
input sys_clk,
input rst_n,
//DA芯片接口
output da_clk, //DA(AD9708)驱动时钟,最大支持125Mhz时钟
output [7:0] da_data, //输出给DA的数据
//AD芯片接口
//input ad_otr , //0:在量程范围 1:超出量程
input [7:0] ad_data, //AD输入数据
output ad_clk //AD(AD9280)驱动时钟,最大支持32Mhz时钟
);
// -----------0、时钟控制模块-----------//
//PLL IP核例化
wire clk_out1;
wire clk_out2;
clk_wiz_0 clk_wiz_0_inst(
// Clock out ports
.clk_out1(clk_out1), // 给DAC提供50MHz采样时钟
.clk_out2(clk_out2), // 给ADC提供25MHz采样时钟
// Status and control signals
.reset(~rst_n), // input reset
// Clock in ports
.clk_in1(sys_clk)); // input clk_in1
// -----------1、DDS模块-----------//
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 [15:0] Fword;
Fword_set Fword_set_inst(
.clk (sys_clk),
.rst_n (rst_n),
.key_PINC (key_PINC),
.Fword (Fword)
);
//DDS IP核例化
wire [0:0] fre_ctrl_word_en ;
//output
wire [0:0] m_axis_data_tvalid ;
wire [7:0] m_axis_data_tdata ;
wire [0:0] m_axis_phase_tvalid ;
wire [15:0] m_axis_phase_tdata ;
assign fre_ctrl_word_en=1'b1;
dds_compiler_0 dds_compiler_0_inst (
.aclk(clk_out1), // input wire aclk
.s_axis_config_tvalid(fre_ctrl_word_en), // input wire s_axis_config_tvalid
.s_axis_config_tdata(Fword), // input wire [15 : 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 [7 : 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 [15 : 0] m_axis_phase_tdata
);
// -----------2、AD9708-----------//
AD9708 AD9708_inst(
.clk (clk_out1),
.rst_n (rst_n),
.data_in (m_axis_data_tdata),
.da_clk (da_clk),
.da_data (da_data)
);
// -----------3、AD9708-----------//
AD9280 AD9280_inst(
.clk (clk_out2),
.rst_n (rst_n),
.ad_clk (ad_clk),
.ad_data (ad_data)
);
// -----------4、ILA模块---------//
//ILA IP核例化
ila_0 ila_0_inst (
.clk(clk_out1), // input wire clk
.probe0(m_axis_data_tdata), // input wire [7:0] probe0
.probe1(da_data), // input wire [7:0] probe1
.probe2(ad_data) // input wire [7:0] probe2
);
endmodule
顶层模块adda_test对5个模块进行了例化,包括
时钟控制模块,利用MMCM分频,给DDS模块以及DAC提供50MHz采样时钟,给ADC提供25MHz采样时钟。
DDS模块,利用实验一设计的DDS IP 数字波形合成DAC,生成ADDA测试用的正弦波。
AD9708,DA数据发送模块,接受来自DDS模块的数字信号并发送至DA转换芯片的数据端口。
AD9280,AD数据接收模块,输出AD转换芯片的驱动时钟并且接收AD转换完成的数据。
ILA模块,观察送入DA芯片以及AD芯片输出的数字信号
DDS模块中Fword_set代码如下:
`timescale 1ns / 1ps
//通过按键来选择对应的频率控制字,进而选择对应的信号频率
module Fword_set(
input clk,
input rst_n,
input [1:0] key_PINC,
output reg [15:0] Fword
);
// 根据IP核的summery, phase width=16bits Frequency per channel=50MHz
// 输出频率的计算公式f_out=f_clk*deta_theta/(2^B)=50M* 3,932/(2^16 )= 3M
always @(*)begin
case(key_PINC)
0: Fword <= 'h51e; //1Mhz 1310 每次相位增加的值 deta_theta
1: Fword <= 'hf5c; //3Mhz 3,932
endcase
end
endmodule
DA数据发送模块AD9708代码如下:
`timescale 1ns / 1ps
module AD9708(
input clk , //时钟
input rst_n , //复位信号,低电平有效
input [7:0] data_in, //DDS读出的数据
//DA芯片接口
output da_clk , //DA(AD9708)驱动时钟,最大支持125Mhz时钟
output [7:0] da_data //输出给DA的数据
);
//*****************************************************
//** main code
//*****************************************************
//数据data_in是在clk的上升沿更新的,所以DA芯片在clk的下降沿
//而DA实际上在da_clk的上升沿锁存数据,所以时钟取反,这样clk的下降沿相当于da_clk的上升沿
assign da_clk = ~clk;
assign da_data = data_in^'h0x80; //将读到的DDS数据与0x80取异或赋值给DA数据端口
endmodule
AD数据接收模块AD9280的代码如下:
`timescale 1ns / 1ps
module AD9280(
input clk , //时钟
input rst_n , //复位信号,低电平有效
input [7:0] ad_data , //AD输入数据
output ad_clk //AD(AD9280)驱动时钟,最大支持32Mhz时钟
);
assign ad_clk = ~clk;
endmodule
约束文件:
############## clock and reset define##################
create_clock -period 20 [get_ports sys_clk]
set_property IOSTANDARD LVCMOS33 [get_ports {sys_clk}]
set_property PACKAGE_PIN U18 [get_ports {sys_clk}]
set_property IOSTANDARD LVCMOS33 [get_ports {rst_n}]
set_property PACKAGE_PIN N15 [get_ports {rst_n}]
################# AN108 ON AX7020 #################
set_property PACKAGE_PIN F20 [get_ports {da_clk}]
set_property PACKAGE_PIN F19 [get_ports {da_data[7]}]
set_property PACKAGE_PIN G20 [get_ports {da_data[6]}]
set_property PACKAGE_PIN G19 [get_ports {da_data[5]}]
set_property PACKAGE_PIN H18 [get_ports {da_data[4]}]
set_property PACKAGE_PIN J18 [get_ports {da_data[3]}]
set_property PACKAGE_PIN L20 [get_ports {da_data[2]}]
set_property PACKAGE_PIN L19 [get_ports {da_data[1]}]
set_property PACKAGE_PIN M20 [get_ports {da_data[0]}]
set_property PACKAGE_PIN L17 [get_ports {ad_data[0]}]
set_property PACKAGE_PIN L16 [get_ports {ad_data[1]}]
set_property PACKAGE_PIN M18 [get_ports {ad_data[2]}]
set_property PACKAGE_PIN M17 [get_ports {ad_data[3]}]
set_property PACKAGE_PIN D20 [get_ports {ad_data[4]}]
set_property PACKAGE_PIN D19 [get_ports {ad_data[5]}]
set_property PACKAGE_PIN E19 [get_ports {ad_data[6]}]
set_property PACKAGE_PIN E18 [get_ports {ad_data[7]}]
set_property PACKAGE_PIN G18 [get_ports {ad_clk}]
set_property IOSTANDARD LVCMOS33 [get_ports {da_clk}]
set_property IOSTANDARD LVCMOS33 [get_ports {da_data[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports {da_data[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {da_data[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {da_data[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {da_data[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {da_data[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {da_data[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {da_data[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {ad_data[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {ad_data[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {ad_data[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {ad_data[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {ad_data[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {ad_data[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {ad_data[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {ad_data[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports {ad_clk}]
下载验证
key_PINC=0,1Mhz正弦波
key_PINC=1,3Mhz正弦波
Matlab分析验证
导出ILA抓取的波形,利用matlab分析验证频率,代码如下:
filename = 'C:\zynq\adda_test\adda_test.srcs\csv\iladata.csv';
M = readmatrix(filename,'Range','E3:E4098'); %读取4096个数据
fs = 50000000; %采样率
N = length(M); %采样点数
n = 0:N-1;
t = n/fs; %时间序列
f = n*fs/N; %频率序列
Y = fft(M); %对M进行FFT变换
fshift = (-N/2:N/2-1)*fs/N;
y = fftshift(Y); %以0为中心循环平移
y_abs = abs(y);
figure;
subplot(2,1,1);
plot(t,M); %时域波形
title('时域波形');xlabel('时间/s');ylabel('幅值');
subplot(2,1,2);
plot(fshift,y_abs); %频域波形
title('频域波形');xlabel('频率/s');ylabel('幅值');
key_PINC=0,iladata.csv
key_PINC=0,iladata1.csv
由图可知,ADC的输出波形并不光滑且含其他频率成分,但主频率为1Mhz/3Mhz
可能是由于DA与AD之间的连接并非理想环境
问题总结
由于之前的实验对DDS IP以及AD9708的了解较少,所以导致了输出的波形不理想,有毛刺。
经过查询相关资料得知,DDS IP输出数据为有符号数,而AD9708的输入数据为无符号数,具体如下图所示
所以该实验需要在两个模块之间进行数据转换,由于数据位宽为8bit,具体操作为将DDS输出的数据与十六进制数0x80异或送给DAC模块
assign da_data = data_in^'h0x80; //将读到的DDS数据与0x80取异或赋值给DA数据端口
参考资料
[1]黑金ALINX高速ADDA使用指南REV1.3.pdf