ZYNQ从入门到秃头10 DDS增强版实验ADDA测试(基于ALINX 7020 && AN108)
Posted “逛丢一只鞋”
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ZYNQ从入门到秃头10 DDS增强版实验ADDA测试(基于ALINX 7020 && AN108)相关的知识,希望对你有一定的参考价值。
前言
首先要对ADDA的相关实验进行学习和分析,考虑到黑金和正点原子的两套阵营,结合二者的优点,特意整理了这么一篇从零开始的ADDA实验文章
硬件平台基于ALINX 7020 && AN108,也是ZYNQ入门的组合套餐
【ZYNQ】从入门到秃头09 DDS IP 数字波形合成(基于ALINX 7020 && AN108)
上述文章在ALINX的板子上实现了正点原子教程中的代码,主要就是IO约束的改动,完成了一些基本的功能,对于一些实践中常用的功能和属性,在本文的番外中进行详细的阐述
DDS IP 数字波形合成
首先把全部的任务要求贴出来,之前任务中已经完成的部分在这里就不详细阐述了,主要是介绍还新增的实验任务
实验任务
- 使用 Vivado的IPI工具,例化DDS IP
- DDS需要能够配置频率字(相位增量)
- DDS工作时钟使用PL的板载50MHz时钟
- 使用ILA工具观察波形, 使用VIO设定频率字
- 在ILA的波形窗口里,观察你设定的波形的周期,验证你频率字设定的正确性
- 把ILA波形导出到CSV文件,波形样点长度不小于2048点,在Matlab里分析波形的频谱,验证你生成波形的正确性。
- 使用VIO更改频率字,分别生成1MHz和3MHz的正弦波形。使用以上流程,验证你输出波形的正确性。
DDS IP核介绍
此前的教程中DDS实验通过导入COE波形文件到ROM中,然后通过DA输出
本次实验通过Vivado中DDS IP核进行实验
首先对DDS IP核进行介绍
参考原文链接:
https://blog.csdn.net/keilzc/article/details/104146629
DDS IP核简介
图中我们看到DDS IP核主要包括5部分组成,其中DDS核心为相位累加器(标记1所示)和LUT查找表(标记2所示)。相位累加器实现查找表地址的产生,LUT查找表用来存储输出波形。标记3部分为抖动产生器和泰勒级数矫正产生模块,主要用来改善SFDR,两者改善的效果、使用的逻辑资源存在差异。标记4部分则为AXI4接口,实现相位累加字配置,多通道配置,相位累加器输出和波形数据输出。标记5部分则在多通道DDS输出时使用。
DDS IP核可以配置为三种模式:相位产生器、SIN/COS LUT或者相位累加器和SIN/COS LUT(即DDS)。
参数解释
定制相位位宽(或频率分辨率)
根据数据手册对频率分辨率的描述:
频率分辨率:以赫兹为单位指定,指定最小频率分辨率,用于确定相位累加器使用的相位宽度及其相关的相位增量(PINC)和相位偏移(POFF)值。
较小的值可提供较高的频率分辨率,并且需要较大的累加器。较大的值会减少硬件资源。根据噪声整形的选择,可以增加相位宽度,并且频率分辨率高于指定的分辨率。对于光栅化模式(rasterized mode),频率分辨率由系统时钟、通道数和所选模数固定。 从这段描述,我们得出信息,频率分辨率可以用来控制相位位宽。 如果操作模式选择标准模式,如下IP 核定制页面:
相位累加器位宽phase width
phase width是相位累加器的位宽,Frequency Resolution是频率分辨率,channels是IP核设置的通道数,默认通道数为1时,二者对应关系(对应dds频率分辨率公式):
只不过在VIVADO的DDS IP核使用中,频率分辨率与通道数有关。
output width是输出波形的数据位宽,一般与DA转换器匹配,SFDR(Spurious Free Dynamic Range(dB))与之有关,具体换算关系见表格4-3。
参数配置测试
- System Parameters模式
output width与sfdr设置测试
sfdr = 42时,output width = 7:
sfdr = 43时,output width = 8:(注意此时图形界面中data位宽为16)
sfdr = 49时,output width = 9:(注意此时图形界面中data位宽为32)
- Hardware Parameters模式
除了上面通过计算的配置方式,还有另外一种配置方式,直接输入位宽,然后软件自动计算
输出正余弦选择以及数据格式
可以在IP核定制页面选择输出正弦还是余弦还或者是都输出:
因为引入了VIO虚拟按键控制频率的输出,所以Phase Offset Programmability选择Programmable
由于输出采用的是axi总线,因此输出数据位于M_AXIS_DATA_TDATA中,那么正余弦输出结果是如何组合成M_AXIS_DATA_TDATA的呢?
数据手册给出解释:
输出DATA通道TDATA结构将正弦和余弦输出字段符号扩展到下一个字节边界,然后以最低有效部分的余弦进行连接,以创建m_axis_data_tdata。如果仅选择正弦或余弦之一,则将其符号扩展并放入m_axis_data_tdata的最低有效部分。
下图显示了这三种配置的TDATA的内部结构。正交输出,仅余弦和仅正弦。例如,在图中显示了11位输出,符号扩展到16位。 <<<表示符号扩展名:
因此我们可以这么认为,由于存在扩展符号位的关系,我们可以提取低一半的数据为COS,高一半的数据为SIN。
例化模块
例化DDS
基于DDS IP核的介绍,我们可以针对我们的应用场景进行配置
首先是使用板载PL 50MHz时钟,需要进行修改
然后AN108型号的ADDA模块是一款高速8位ADDA,所以生成的信号只要比adda模块精度高就可以了
为了方便,仅输出8位宽的sine信号就可以了
例化ILA
例化了一个 ILA的 IP核,用于捕获 ad_data的数据。
需要注意的是, ILA的采样时钟必须使用 ad_clk,否则数据可能采集错误。
ILA IP核的配置如下图所示:
我们把探针数量设置为1,并且把采样深度设置为 4096。探针宽度的设置如下图所示:
我们将两个探针的位宽设置成 8,对应ad_data的位宽,设置完成后点击“ OK”按钮即可。
例化VIO
使用场景:在使用In system debug时需要使用按键触发查看相关信号,但不想用板子上的按键。
VIO:Virtual input output,即虚拟IO。
主要用作虚拟IO使用;VIO的输出可以控制模块的输入,VIO的输入可以显示模块的输出值。
假设有一个模块的复位信号,需要由自己控制,则可以使用VIO核进行相关控制;
必须要通过ILA才能模拟VIO,testbench不能模拟VIO
可以看到vio可以作为输入输出两种功能来使用,可以实时监测和驱动fpga内部信号。
简而言之,VIO可以通过配置得到一个0~256组探针,每一组位宽范围为1到256的虚拟信号,每一位都是可配置的。它的功能主要是在没有实体器件的环境下作为一种模拟的输入输出,验证自己代码的正确性。
程序设计
顶层模块
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2022/03/02 20:24:22
// Design Name:
// Module Name: dds_ip
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module dds_ip(
input sys_clk, // 系统时钟, 50 MHz T=20ns
input rst_n // 系统复位
);
// VIO按键控制频率
wire [1 : 0] key_PINC;
vio_0 u_vio_0 (
.clk(sys_clk), // input wire clk
.probe_out0(key_PINC) // output wire [1 : 0] probe_out0
);
// 信号频率控制模块
wire [15 : 0] Fword; // 频率字
Fword_set Fword_set_instance (
.clk(sys_clk),
.rst_n(rst_n),
.key_PINC(key_PINC),
.Fword(Fword)
);
// DDS 模块
//input
wire [0:0] fre_ctrl_word_en;
//output
wire [0 : 0] m_axis_data_tvalid;
wire [31 : 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_sine u_dds_sine (
.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 [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 [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 [15 : 0] m_axis_phase_tdata
);
// ILA
ila_0 u_ila (
.clk(sys_clk), // input wire clk
.probe0(key_PINC), // input wire [1:0] probe0
.probe1(Fword), // input wire [15:0] probe1
.probe2(m_axis_data_tdata) // input wire [31:0] probe2
);
endmodule
频率控制模块
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2022/03/09 21:46:35
// Design Name:
// Module Name: Fword_set
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module Fword_set(
input clk,
input rst_n,
input [1 : 0] key_PINC,
output reg [15 : 0] Fword
);
// The output frequency(f_out ) , of the DDS waveform is a function of the system clock frequency(f_clk ) .
// the phase width, that is, number of bits (B ) in the phase accumulator
// and the phase increment value (deta_theta) .
// The output frequency in Hertz is defined by:f_out=f_clk*deta_theta/(2^B)
// fre_ctrl_word是如何确定的?
// 根据IP核的summery, phase width=16bits Frequency per channel=50MHz
// 输出频率的计算公式f_out=f_clk*deta_theta/(2^B)=50M * 13107.2/(2^16 )= 10M
always@(*)
begin
case(key_PINC)
0: Fword <= 'h51e; //1Mhz 1310.72 每次相位增加的值 deta_theta
1: Fword <= 'ha3d; //2Mhz 2621.44
2: Fword <= 'h147b; //4Mhz 5242.88
3: Fword <= 'h3333; //10Mhz 13107.2
endcase
end
endmodule
这里的计算一定要注意,根据选择的核心频率来进行计算
IO约束
############## clock and reset define##################
create_clock -period 20.000 [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 K16 [get_ports rst_n]
这个就很常规,IO约束一下复位和时钟就可以了
RTL
实验结果
将工程生成的比特流文件下载到 ZYNQ中 后, 然后 使用 ila测量 DA输出 通道的波形。
我们可以看到下面的结果,现实的是十六进制的数值
我们希望看到的是波形的形状,所以通过右键进行修改,可以看到模拟波形
但是看到的不是预期的sine函数,找了一通问题,发现问题在于十六进制默认不会进行补码的计算,所以全都是正数,需要先转换成有符号的十进制捕获,然后再进行波形的模拟
这样就可以看到我们预想的波形
修改VIO,key = 00,对应1 MHz
1 MHz对应的周期是 0.000001 s,也就是 1 微秒(μs),因为ila时钟50MHz,所以一个单位是 0.02 微秒(μs),20 纳秒(ns),这里我们可以看到ila捕获的50个时钟单位,所以这里周期是 50 * 20ns = 1000 ns = 1 μs
修改VIO,key = 01,对应 2 MHz
2 MHz对应的周期是 0.0000005 s,也就是 0.5 微秒(μs),这里我们可以看到ila捕获的 25 个时钟单位,所以这里周期是 25 * 20ns = 500 ns = 0.5 μs
修改VIO,key = 10,对应 4 MHz
4 MHz对应的周期是 0.00000025 s,也就是 0.25 微秒(μs),这里我们可以看到ila捕获的 12 个时钟单位,所以这里周期是 12 * 20ns = 240 ns = 0.24 μs
修改VIO,key = 11,对应 10 MHz
10 MHz对应的周期是 0.0000001 s,也就是 0.1 微秒(μs),这里我们可以看到ila捕获的 5 个时钟单位,所以这里周期是 5 * 20ns = 100 ns = 0.1 μs
因此,可以得出我们的结论,实现了设计目标
但是为了更加的科学,通过Matlab在进行分析一下,首先就是导出数据
导出数据
Maltab频率分析
最靠谱的还是要看Matlab 的分析结果
close all;clc;clear all;
fs = 50000000; %设置采样频率为50MHz
wave_data = xlsread('iladata.csv');
N = length(wave_data);
n = 0:N-1;
f = n*fs/N; %频率序列
Y = fft(wave_data(:,6));
% A = 20*log10(abs(Y));
A = abs(Y);
figure
plot(f,A);title('FFT');xlabel('Hz');ylabel('Amplitude');
可以看到,1MHz频率的波形通过FFT计算后,与理论值一致
工程下载
https://download.csdn.net/download/szm1234/85072327
ADDA测试
有了DDS的生成,我们现在的任务是如何把生成的正弦波通过DA送出去,然后再通过AD收进来
之前我们已经实现了ROM中存储的正弦波进行ADDA的操作,现在我们照猫画虎,在上面DDS IP实验的基础上继续进行
实验任务
- 注意,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数据验证频谱正确。
例化模块
例化clk wizard
在顶层模块代码例化了 clk_wiz的 ip核,用于生成 100M时钟作为读 rom地址的时钟。
clk_wiz IP核的配置如下图所示:
接下来切换至 Output Clocks”选项卡,在 Output Clock 选项卡 中,将其“ Output Freq(MHz)”分别设置为25 100,其他设置保持默认即可,如 下图 所示。
其余的设置保持默认,最后直接点击“ OK”按钮即可 。
程序设计
问题报错
在进行程序的移植过程中,出现了一个错误,先贴一下,具体如下
dds_ip文件应该是我们的顶层文件,但是这里我们看到没有识别到,并且还报错
起初是认为顶层模块没有识别,通过设置发现是模块本身没有识别,模块编写出现错误
通过检查发现是一个非常简单的问题引起的,这里可能是2017版本的一个bug,漏加“ ,” 编辑器竟然不会提示错误
添加 逗号 之后,顶层模块正确识别,一切都恢复正常
顶层模块
经过了小插曲,开始程序设计
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2022/03/10 23:07:24
// Design Name:
// Module Name: dds_adda
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module dds_ip(
input sys_clk, // 系统时钟, 50 MHz T=20ns
input sys_rst_n, // 系统复位
// DA芯片接口
output da_clk_100M, // DA(AD9708)驱动时钟,最大支持125Mhz时钟
output [7 : 0] da_data, // 输出给DA的数据
// AD芯片接口
input [7 : 0] ad_data, // AD输入数据
output ad_clk_25M // AD(AD9280)驱动时钟,最大支持32Mhz时钟
);
// VIO按键控制频率
wire [1 : 0] key_PINC;
vio_0 u_vio_0 (
.clk(sys_clk), // input wire clk
.probe_out0(key_PINC) // output wire [1 : 0] probe_out0
);
// 信号频率控制模块
wire [15 : 0] Fword; // 频率字
Fword_set Fword_set_instance (
.clk(sys_clk),
.rst_n(sys_rst_n),
.key_PINC(key_PINC),
.Fword(Fword)
);
// DDS 模块
//input
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_sine u_dds_sine (
.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 [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
);
// CLK_WIZ PLL
wire clk_100M; // da芯片的驱动时钟
clk_wiz_0 u_clk_wiz_0
(
// Clock out ports
.clk_out1(ad_clk_25M), // output clk_out1
.clk_out2(clk_100M), // output clk_out2
// Status and control signals
.reset(~sys_rst_n), // input reset
.locked(locked), // output locked
// Clock in ports
.clk_in1(sys_clk)); // input clk_in1
// DA数据发送
da_wave_send u_da_wave_send(
.clk (clk_100M),
.rst_n (sys_rst_n),
.rd_data (m_axis_data_tdata),
.da_clk (da_clk_100M),
.da_data (da_data)
);
// ILA
ila_0 u_ila (
.clk(ad_clk_25M), // input wire clk
.probe0(key_PINC), // input wire [1:0] probe0
.probe1(Fword), // input wire [15:0] probe1
.probe2(m_axis_data_tdata), // input wire [7:0] probe2
.probe3(ad_data), // input wire [7:0] probe3
.probe4(da_data) // input wire [7:0] probe4
);
endmodule
频率控制模块
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2022/03/09 21:46:35
// Design Name:
// Module Name: Fword_set
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module Fword_set(
input clk,
input rst_n,
input [1 : 0] key_PINC,
output reg [15 : 0] Fword
);
// The output frequency(f_out ) , of the DDS waveform is a function of the system clock frequency(f_clk ) .
// the phase width, that is, number of bits (B ) in the phase accumulator
// and the phase increment value (deta_theta) .
// The output frequency in Hertz is defined by:f_out=f_clk*deta_theta/(2^B)
// fre_ctrl_word是如何确定的?
// 根据IP核的summery, phase width=16bits Frequency per channel=50MHz
// 输出频率的计算公式f_out=f_clk*deta_theta/(2^B)=50M * 13107.2/(2^16 )= 10M
always@(*)
begin
case(key_PINC)
0: Fword <= 'h51e; //1Mhz 1310.72 每次相位增加的值 deta_theta
1: Fword <= 'ha3d; //2Mhz 2621.44
2: Fword <= 'h147b; //4Mhz 5242.88
3: Fword <= 'h3333; //10Mhz 13107.2
endcase
end
endmodule
DA模块
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2022/02/27 16:01:20
// Design Name:
// Module Name: da_wave_send
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module da_wave_send(
input clk, // 时钟
input rst_n, // 复位信号,低电平有效
input [7 : 0] rd_data, // DDS读出数据
// DA 芯片接口
output da_clk, // DA(AD9708)驱动时钟,最大支持125MHz时钟
output [7 : 0] da_data // 输出给DA的数据
);
// *****************************
// ** main code
// *****************************
// 数据rd_data是在clk的上升沿更新的,所以DA芯片在clk的下降沿锁频存数据是最稳定的时刻
// 而DA实际上在da_clk的上升沿锁存数据,所以时钟取反,这样clk的下降沿相当于da_clk的上升沿
assign da_clk = ~clk;
assign da_data = rd_data; // 将读到的ROM数据幅值给DA数据端口
endmodule
IO约束
############## clock and reset define##################
create_clock -period 20.000 [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 sys_rst_n]
set_property PACKAGE_PIN K16 [get_ports sys_rst_n]
########AN108 ON AX7020 and AX7010 J11##################
set_property PACKAGE_PIN F20 [get_ports da_clk_100M]
set_property PACKAGE_PIN F19 [get_ports da_data[7]]
set_property PACKAGE_PIN G20 [get_ports da_dataZYNQ从入门到秃头09 DDS IP 数字波形合成(基于ALINX 7020 && AN108)
ZYNQ从入门到秃头10 DAC FIFO实验(AXI-stream FIFO IP核配置)
ZYNQ从入门到秃头07 FPGA 片内 RAM && ROM 读写测试实验
ZYNQ从入门到秃头07 FPGA 片内 RAM && ROM 读写测试实验