文章目录
前言
本实验是DAC FIFO实验,具体的要求、注意事项以及开发流程如下。
DAC FIFO实验流程 |
1.基于“DDS IP 数字波形合成DAC ” “ ADDA测试” 实验方案。
2.用MMCM把合成出100MHz的时钟,让DDS工作在100MHz时钟。
3.让DAC和DAC的接口电路工作在50MHz,此时DAC的采样率为50MHz。
4.在DDS和DAC接口电路之间,放置一个带独立时钟的AXI-Stream-Data FIFO,FIFO两端的时钟分别为DDS的工作时钟100MHz和DAC的工作时钟50MHz。
5.生成FIFO需要带data count信号。(本实验仅用于观察,以后的实验中这些信号有用)
6.DDS的数据输出接口需要有TREADY信号。
7.DAC接口电路需要将FIFO输出端的AXI-S接口转换成DAC的接口格式,自行编写RTL代码完成该功能。另外由于DAC的工作频率小于DDS工作频率,所以DAC接口控制器给FIFO的RDY信号应该一直为高。
8.以上结构的意义在于,把接口电路和信号处理电路分离在不同的时钟域,从而使得各部分保持独立。
9.本实验添加2个system ILA,分别观察FIFO两端接口的信号时序,注意观察 data count端口的变化。
10.用VIO配置频率字,分别生成1MHz和3MHz的DDS正弦波形,用system ILA抓取DAC的输入数据,用Matlab分析频谱,验证频率正确。
11.本实验是一个典型的带反向流控的跨时钟域传输信号的例子。
本实验是基于“DDS IP 数字波形合成DAC ”和 “ ADDA测试” 实验方案的,本实验中与之相关的内容可以参考博文:ZYNQ FPGA实验——DDS IP数字波形合成和ZYNQ FPGA实验——AD/DA测试。
一、添加AXI4-Stream Data FIFO IP核
有关FIFO的简介可以参考:ZYNQ之FPGA 片内FIFO读写测试实验。
AXI4-Stream Data FIFO是一种先入先出形式的数据缓存队列,其输入与输出接口均为AXIS接口的数据缓存器,可用在数据缓存、跨时钟域传输等各类场景,支持数据的分割和数据拼接。
下面在Vivado中来添加AXI4-Stream Data FIFO IP核。
先按照下图中顺序找到AXI4-Stream Data FIFO双击打开。
出现如下图所示的界面。
General页卡下出现的参数有:
Component Name:IP名称,可以修改,一般保持默认即可。
FIFO depth:FIFO的深度,可以自己设定,选择范围16至32768。当FIFO的深度为16或者32时利用基于FIFO的LUTRAM,其他深度的FIFO将利用BLOCK RAM实现。
Memory type:实现FIFO的RAM类型,一般选择自动Auto。
Independent clocks:独立时钟,选择No即同步FIFO,选择Yes即异步FIFO,这里选择Yes。
CDC sync stages:跨时钟域处理的同步阶数,由于我们选择的是异步时钟,这里设置数量为2。
Enable packet mode: 使能包模式,设置为Yes将使能包模式,此项设定需要TLAST信号被使能,FIFO的操作在包模式下被修改为存储传送的数据,直到TLAST信号被置位,当TLAST信号被置位或者FIFO满了,存储的传送数据将被送至AXI4-Stream master interface,这里选择No。
ACLKEN conversion mode :ACLKEN的转换模式,当ACLKEN转换执行时会消耗额外的延迟和逻辑,这里可供选择的转换模式如下:
None ——没有ACLKEN信号关联于IP。
S AXIS Only —— 一个S_AXIS_ACLKEN信号关联到S_AXIS_ACLK时钟信号且没有M_AXIS_ACLKEN信号。
M AXIS Only ——一个M_AXIS_ACLKEN信号关联到M_AXIS_ACLK时钟信号且没有S_AXIS_ACLKEN信号。
S AXIS M AXIS ——两边时钟都有ACLKEN信号被关联。
这里选择None。
AXIS是一种半双工的总线,数据传输永远是从MASTER发送给SLAVE,所以M_AXIS是发送接口,其用来发送FIFO中的数据,即FIFO读取端;S_AXIS是接收接口,用来将数据写入FIFO中,即FIFO写入端。
Enable ECC:ECC(Error Correction Checking) ,即错误纠正检查,这里不使用,选择No。
Include ECC error injection:包含错误纠正检查注入,这里也选择No。
TDATA width (bytes): TDATA位宽,以字节为单位, 该参数指定了所有AXI4-Stream interfaces中的TDATA信号的位宽。该参数为整数,范围在0到256之间,将其设为0将省略TDATA信号,如果TDATA信号被省略,TKEEP和TSTRB信号也会被省略。接口数据的位宽按bits计算,需要乘以8,这里设置为1。
Enable TSTRB :使能TSTRB信号,如果设定为Yes,这个参数指定是否在所有AXI4-Stream interfaces使用可选的TSTRB信号,这个选项只能在参数TDATA Width (bytes)大于0时才可以使能,这个信号和TKEEP一起构成了对TDATA的描述,表明当前传输的数据是有效、无效还是占位符。这里选择No。
Enable TKEEP :使能TKEEP信号,如果设定为Yes,这个参数指定是否在所有AXI4-Stream interfaces使用可选的TKEEP信号,这个选项只能在参数TDATA Width (bytes)大于0时才可以使能,这个信号和TSTRB一起构成了对TDATA的描述,表明当前传输的数据是是有效、无效还是占位符。这里选择No。
Enable TLAST: 使能TLAST信号,如果设定为Yes,这个参数指定是否在所有AXI4-Stream interfaces使用可选的TLAST信号,在使用STREAM FIFO时,TLAST信号的作用是指示一次传输数据流的最后一个数据,也指示着该数据流的结束,它会记录下TLAST信号的位置及当其SLAVE接口(SFIFO的数据写入接口)的某一个数据写入的同时TLASET信号为高的话,当MASTER接口(SFIFO的数据读出接口)读出该数据时也会将TLAST信号拉高,也就是说,写入数据时有TLAST,读出数据时也会有TLAST信号。这里选择No。
TID width (bits):TID位宽,以比特为单位,如果该参数大于0,这个参数指定是否在所有的AXI4-Stream interfaces中使用TID信号,值等于0则省略这个信号。适用于多个AXIS接口构成的系统通信,用于区别源信号。这里置为0。
TDEST width (bits):TDEST位宽,以比特为单位,如果该参数大于0,这个参数指定是否在所有的AXI4-Stream interfaces中使用TDEST信号,值等于0则省略这个信号。适用于多个AXIS接口构成的系统通信,用于区别目标信号。这里置为0。
TUSER width (bits):TUSER 位宽,以比特为单位,如果该参数大于0,这个参数指定是否在所有的AXI4-Stream interfaces中使用TDEST信号,值等于0则省略这个信号。传输用于的定制信息。这里置为0。
设置完成后切换到Flags页卡下如下图所示。
Flags页卡下出现的参数有:
Enable write data count:使能写入数据计数,同步于写时钟。这里置为Yes。
Enable almost full:使能写入几乎满,同步于写时钟。这里置为No。
Enable programmable full:使能可编程满,同步于写时钟。可以自己设置阈值来提醒当前FIFO的写入状态。这里置为No。
Programmable full threshold:可编程的满阈值。这里用默认数值。
Enable read data count:使能读取数据计数,同步于读时钟。这里置为Yes。
Enable almost empty:使能读取几乎空,同步于读时钟。这里置为No。
Enable programmable empty:使能可编程空,同步于读时钟。可以自己设置阈值来提醒当前FIFO的读取状态。这里置为No。
Programmable empty threshold:可编程的空阈值。这里用默认数值。
上面的参数设置完成后点击OK,弹出下面的窗口,点击Generate生成即可。
这是AXI4-Stream Data FIFO的所有端口信息,其中灰色的是未被使用的,我使用的Vivado是2018.3版本,没有data_count[31:0]这个端口,因此在后续ILA抓取数据时,使用axis_rd_data_count[31:0]端口代替。
二、添加PLL IP核
这里的输入时钟设置为50MHz。
由于本实验中的DDS工作在100MHz,故这里的输出时钟设置为100MHz,如下图所示。
以上参数设置完成后点击OK,然后再点击Generate生成相应的 IP 核即可。
三、添加DDS IP核
Configuration页卡参数设置如下图所示。
Implementation页卡参数设置如下图所示。
在Detailed Implementation页卡下勾选Output TREADY。
设置完成后的Summary页卡如下图所示。
以上参数设置完成后点击OK,然后再点击Generate生成相应的 IP 核即可。
四、添加VIO IP核
在General Options页卡下的设置如下图所示。
在PROBE_OUT Ports页卡下的设置如下图所示。
以上参数设置完成后点击OK,然后再点击Generate生成相应的 IP 核即可。
五、添加ILA IP核
本实验添加2个system ILA,分别观察FIFO两端接口的信号时序。
第一个ILA设置4个探针,分别用来监测频率字控制位(2位)、fifo的输入和输出(8位)和data count端口(32位)。
第一个ILA各个探针位数的设置如下图所示。
第二个ILA设置2个探针,分别用来监测频率字控制位(2位)和da数据(8位)。
第二个ILA各个探针位数的设置如下图所示。
以上参数设置完成后点击OK,然后再点击Generate生成相应的 IP 核即可。
六、编写测试程序
本实验的文件目录结构如下图所示。
在文件dac_fifo.v中写入的代码如下。
//该代码参考自博客:vivado VIO IP的用法、DAC FIFO实验,以及正点原子官方文件,见总结
`timescale 1ns / 1ps
module dac_fifo(
input sys_clk, //系统时钟 50MHz T=20ns
input rst_n, //系统复位
//DA芯片接口
output da_clk, //DA(AD9708)驱动时钟,最大支持125Mhz时钟
output [7:0] da_data, //输出给DA的数据
//AD芯片接口
input [7:0] ad_data, //AD输入数据
//模拟输入电压超出量程标志(本次试验未用到)
//input ad_otr , //0:在量程范围 1:超出量程
output ad_clk //AD(AD9280)驱动时钟,最大支持32Mhz时钟
);
//----------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)
);
//---------------PLL模块--------------//
wire clk_100M;
clk_wiz_0 pll_inst
(
// Clock out ports
.clk_out1(clk_100M), // output clk_out1
// Status and control signals
.reset(~rst_n), // input reset
.locked(locked), // output locked
// Clock in ports
.clk_in1( sys_clk) // input clk_in1
);
//---------------DDS模块--------------//
//input
wire [0:0] fre_ctrl_word_en;
wire m_axis_data_tready;
wire m_axis_phase_tready;
//output
wire [0:0] m_axis_data_tvalid;
wire [7:0] m_axis_data_tdata;
wire [0:0] m_axis_phase_tvalid;
wire [23:0] m_axis_phase_tdata;
wire [0:0] s_axis_config_tready;
assign fre_ctrl_word_en=1'b1;
//---------------AXIS模块--------------//
//output
wire fifo_s_axis_tready;
wire fifo_m_axis_tvalid;
wire da_m_axis_tready;
wire [7:0] fifo_m_axis_tdata;
wire [31:0] fifo_axis_wr_data_count;
wire [31:0] fifo_axis_rd_data_count;
assign s_axis_aresetn = 1'b1;
//例化DDS
dds_compiler_0 dds_compiler_0_inst (
.aclk(clk_100M), // input wire aclk 给DDS的工作频率为100MHz
.s_axis_config_tvalid(fre_ctrl_word_en), // input wire s_axis_config_tvalid
.s_axis_config_tready(s_axis_config_tready), // output wire s_axis_config_tready
.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_tready(fifo_s_axis_tready), // input wire m_axis_data_tready
.m_axis_data_tdata(m_axis_data_tdata), // output wire [15 : 0] m_axis_data_tdata
.m_axis_phase_tvalid(m_axis_phase_tvalid), // output wire m_axis_phase_tvalid
.m_axis_phase_tready(fifo_s_axis_tready), // input wire m_axis_phase_tready
.m_axis_phase_tdata(m_axis_phase_tdata) // output wire [23 : 0] m_axis_phase_tdata
);
//例化AXIS
axis_data_fifo_0 axis_data_fifo_inst (
.s_axis_aresetn(s_axis_aresetn), // input wire s_axis_aresetn
.s_axis_aclk(clk_100M), // input wire s_axis_aclk 给100MHz
.s_axis_tvalid(m_axis_data_tvalid), // input wire s_axis_tvalid
.s_axis_tready(fifo_s_axis_tready), // output wire s_axis_tready
.s_axis_tdata(m_axis_data_tdata), // input wire [7 : 0] s_axis_tdata
.m_axis_aclk(sys_clk), // input wire m_axis_aclk 给系统时钟频率50MHz
.m_axis_tvalid(fifo_m_axis_tvalid), // output wire m_axis_tvalid
.m_axis_tready(s_axis_config_tready), // input wire m_axis_tready
.m_axis_tdata(fifo_m_axis_tdata), // output wire [7 : 0] m_axis_tdata
.axis_wr_data_count(fifo_axis_wr_data_count), // output wire [31 : 0] axis_wr_data_count
.axis_rd_data_count(fifo_axis_rd_data_count) // output wire [31 : 0] axis_rd_data_count
);
//DA数据发送
da_wave_send u_da_wave_send(
.clk (sys_clk),
.rst_n (rst_n),
.rd_data (fifo_m_axis_tdata),
.da_clk (da_clk),
.da_data (da_data),
.da_m_axis_tready (da_m_axis_tready)
);
//ILA采集数据
ila_0 ila_0_inst (
.clk(sys_clk), // input wire clk
.probe0(key_PINC), // input wire [1:0] probe0
.probe1(m_axis_data_tdata), // input wire [7:0] probe1
.probe2(fifo_m_axis_tdata), // input wire [7:0] probe2
.probe3(fifo_axis_rd_data_count) // input wire [31:0] probe3
);
ila_1 ila_1_inst (
.clk(sys_clk), // input wire clk
.probe0(key_PINC), // input wire [1:0] probe0
.probe1(da_data) // input wire [7:0] probe1
);
endmodule
文件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
文件da_wave_send.v中写入的代码如下。
//本代码来自正点原子
module da_wave_send(
input clk , //时钟
input rst_n , //复位信号,低电平有效
input [7:0] rd_data, //读出的数据
//DA芯片接口
output da_clk , //DA(AD9708)驱动时钟,最大支持125Mhz时钟
output [7:0] da_data, //输出给DA的数据
output da_m_axis_tready // 由于DAC的工作频率小于DDS工作频率,所以DAC接口控制器给FIFO的RDY信号应该一直为高
);
//parameter 频率调节控制
parameter FREQ_ADJ = 8'd5; //频率调节,FREQ_ADJ的值越大,最终输出的频率越低,范围0~255
//reg define
reg [7:0] freq_cnt ; //频率调节计数器
//数据rd_data是在clk的上升沿更新的,所以DA芯片在clk的下降沿锁存数据是稳定的时刻
//而DA实际上在da_clk的上升沿锁存数据,所以时钟取反,这样clk的下降沿相当于da_clk的上升沿
assign da_clk = ~clk;
assign da_data = rd_data; //将读到的数据赋值给DA数据端口
assign da_m_axis_tready = 1'b1;
//频率调节计数器
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
freq_cnt <= 8'd0;
else if(freq_cnt == FREQ_ADJ)
freq_cnt <= 8'd0;
else
freq_cnt <= freq_cnt + 8'd1;
end
endmodule
七、管脚分配
本实验中的管脚分配与上一个实验ZYNQ FPGA实验——AD/DA测试相同,因此这里直接将其约束文件粘贴过来即可。
先在Constraints下新建一个与工程同名的dac_fifo.xdc文件。
然后在其中输入如下内容。
set_property PACKAGE_PIN U18 [get_ports sys_clk]
set_property PACKAGE_PIN N15 [get_ports rst_n]
set_property IOSTANDARD LVCMOS33 [get_ports sys_clk]
set_property IOSTANDARD LVCMOS33 [get_ports rst_n]
set_property PACKAGE_PIN F20 [get_ports da_clk]
set_property IOSTANDARD LVCMOS33 [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 IOSTANDARD LVCMOS33 [get_ports {
da_data[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {
da_data[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {
da_data[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {
da_data[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {
da_data[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {
da_data[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {
da_data[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports {
da_data[6]}]
set_property PACKAGE_PIN G18 [get_ports ad_clk]
set_property IOSTANDARD LVCMOS33 [get_ports ad_clk]
set_property IOSTANDARD LVCMOS33 [get_ports {
ad_data[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports {
ad_data[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {
ad_data[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {
ad_data[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {
ad_data[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {
ad_data[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {
ad_data[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {
ad_data[0]}]
set_property PACKAGE_PIN E18 [get_ports {
ad_data[7]}]
set_property PACKAGE_PIN E19 [get_ports {
ad_data[6]}]
set_property PACKAGE_PIN D19 [get_ports {
ad_data[5]}]
set_property PACKAGE_PIN D20 [get_ports {
ad_data[4]}]
set_property PACKAGE_PIN M17 [get_ports {
ad_data[3]}]
set_property PACKAGE_PIN M18 [get_ports {
ad_data[2]}]
set_property PACKAGE_PIN L16 [get_ports {
ad_data[1]}]
set_property PACKAGE_PIN L17 [get_ports {
ad_data[0]}]
保存该文件之后,管脚就算是分配完成了。
八、连接开发板测试
连接开发板,点击Generate Bitstream生成比特流文件,将其下载到开发板上。
这时候会出现hw_ila_1、hw_ila_2和hw_vios三个窗口,其中hw_vios窗口是用来调节频率字的,其中00对应1MHz,01对应2MHz,10对应3MHz,hw_ila_1窗口是第一个ILA下参数的波形,hw_ila_2窗口是第二个ILA下参数的波形,该窗口下的波形文件将会在Matlab中进行频率验证。
1MHz输出频率在第一个ILA下观察到的输出波形如下图所示。
1MHz输出频率在第二个ILA下观察到的输出波形如下图所示。
2MHz输出频率在第一个ILA下观察到的输出波形如下图所示。
2MHz输出频率在第二个ILA下观察到的输出波形如下图所示。
3MHz输出频率在第一个ILA下观察到的输出波形如下图所示。
3MHz输出频率在第二个ILA下观察到的输出波形如下图所示。
通过观察上面在不同频率下的输出波形,信号m_axis_data_tdata[7:0])和信号fifo_m_axis_tdata[7:0]的时序总是有点差别,这是因为在主设备向从设备传输数据时,只有主从双方都准备好时,主设备才能成功发出信息到从设备,从设备才能准确的接收到来自主设备的信息,也就是代码中的tvaild信号和tdata信号都有效时,信息才能成功的传输。
九、Matlab中分析输出波形
将上面第二个ILA下的不同输出频率对应的正弦波导出为CSV文件,然后在Matlab中依次进行分析。
新建脚本文件并写入如下代码。
%这是1MHz的代码,2MHz和3MHz的代码稍加改动即可
csv_row5 = iladata1{
:,5}; %取出csv文件中的第五列数据
fs=50000000; %设置采样频率为50MHz
N=4096; %采样点
n=0:N-1;
t=n/fs;
f=n*fs/N; %频率序列
subplot(2,1,1);plot(t,csv_row5);grid on;title('1MHz-时域波形');
y=abs(fft(csv_row5,N));
subplot(2,1,2);plot(f,y,'r');grid on;title('1MHz-频谱');
输出频率为1MHz的运行结果如下图所示。
输出频率为2MHz的运行结果如下图所示。
输出频率为3MHz的运行结果如下图所示。
由上面Matlab运行结果可知,各波形所对应的频率都是正确的。
总结
以上就是ZYNQ FPGA实验——DAC FIFO实验的所有内容了,本实验是在ZYNQ FPGA实验——DDS IP数字波形合成和ZYNQ FPGA实验——AD/DA测试这两个实验的基础上进行的,AXI4-Stream Data FIFO IP核的添加、DDS和AXIS的例化代码参考了许多资料和博文,文中所参考的文章都在下面进行了罗列。
本文参考资料:
正点原子–course_s1_ZYNQ那些事儿-FPGA实验篇V1.06.pdf
ALINX黑金AX7020开发板用户手册V2.2.pdf
领航者ZYNQ之FPGA开发指南_V2.0 .pdf
参考博文:
vivado VIO IP的用法
详解XILINX IP AXI4 STREAM DATA FIFO
AXI4 STREAM DATA FIFO
DAC FIFO实验(AXI-stream FIFO IP核配置)
AXI总线协议时序