ZYNQ从入门到秃头09番外 DDS增强版实验(基于ALINX 7020 && AN108)

Posted “逛丢一只鞋”

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ZYNQ从入门到秃头09番外 DDS增强版实验(基于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

因此,可以得出我们的结论,完全准确的实现了设计目标

导出数据


close all;clc;clear all;
wave_data = xlsread('iladata.csv');    
Y = fft(wave_data(:,6)); 
% A = 20*log10(abs(Y)); 
A = abs(Y);
figure
plot(A);title('FFT');xlabel('N');ylabel('Amplitude');

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_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[1ZYNQ从入门到秃头09 DDS IP 数字波形合成(基于ALINX 7020 && AN108)

ZYNQ从入门到秃头[番外] Vivado VSCode现代化编辑工具配置

ZYNQ从入门到秃头05 LED闪烁实验 && 按键控制LED实验Verilog(PL)

ZYNQ从入门到秃头10 DAC FIFO实验(AXI-stream FIFO IP核配置)

ZYNQ从入门到秃头07 FPGA 片内 RAM && ROM 读写测试实验

ZYNQ从入门到秃头07 FPGA 片内 RAM && ROM 读写测试实验