实验要求
DAC FIFO实验
- 基于“DDS IP 数字波形合成DAC ” “ ADDA测试” 实验方案
- 用MMCM 把 合成出100MHz的时钟,让DDS工作在100MHz时钟
- 让DAC和DAC的接口电路工作在50MHz,此时DAC的采样率为50MHz
- 在DDS和DAC接口电路之间,放置一个带独立时钟的AXI-Stream-Data FIFO,FIFO两端的时钟分别为DDS的工作时钟100MHz和DAC的工作时钟50MHz
- 生成FIFO需要带data count信号(本实验仅用于观察,以后的实验中这些信号有用。)
- DDS的数据输出接口需要有TREADY信号
- DAC接口电路需要将FIFO输出端的AXI-S接口转换成DAC的接口格式,自行编写RTL代码完成该功能。另外由于DAC的工作频率小于DDS工作频率,所以DAC接口控制器给FIFO的RDY信号应该一直为高。
- 以上结构的意义在于,把接口电路和信号处理电路分离在不同的时钟域,从而使得各部分保持独立
- 本实验添加2个system ILA,分别观察FIFO两端接口的信号时序,注意观察 data count端口的变化。
10.用VIO配置频率字,分别生成1MHz和3MHz的DDS正弦波形,用system ILA抓取DAC的输入数据,用Matlab分析频谱,验证频率正确。 - 本实验是一个典型的带反向流控的跨时钟域传输信号的例子
带FIFO的ADDA实验
- 本实验在DAC FIFO实验的基础上完成
- 把DAC输出模拟信号自环给ADC的模拟输入
- ADC使用25MHz的时钟信号采样
- ADC的输出的数据信号,用ILA抓取观察波形
- 用VIO配置频率字,分别生成1MHz和3MHz的DDS正弦波形,用Matlab分析频谱,验证频率的正确性。
由于ADDA实验相较于DAC仅多出一个AD数据接收模块,故将两个实验在一篇文章中进行总结
AXI4-STREAM DATA FIFO
AXI4-Stream总线
关于AXI4-Stream总线的认识,可以参考带你快速入门AXI4总线--AXI4-Stream篇(1)----AXI4-Stream总线这篇文章
AXI4-Stream协议是AXI4三个协议中最简单的一个部分,面向流的传输,不涉及内存地址,主要用于往FIFO等没有地址的数据缓冲区传送大量数据时使用。AXIS的通信过程主要基于握手机制。
握手机制,即主从设备之间借助tvalid、tready信号的状态太判断双方是否能发送或接受数据。
tvalid:该信号拉高时,表示主机可以传输数据给从机。
tready:该信号拉高时,表示从机可以接收来自主机的数据。
AXI4-STREAM DATA FIFO IP核
FIFO模块能够为AXI4-Stream数据流提供临时存储(缓冲区),多用于一下两种情况:
需要比寄存器更多的缓存单元
存储和转发:主机上积累一定数量的字节后,再转发给从机(包模式)。
FIFO,first in,first out,即先入先出的数据缓存队列,其结构如下如所示
如图所示,FIFO IP核的主要操作是对数据进行写入,读出。虽然AXI4-Stream数据流不涉及内存地址,且FIFO没有地址线,但FIFO的内部含地址操作,其读写过程如下图所示:
其中深度值为FIFO内部可以存储的最大数据量,根据读写始终,FIFO可分为同步FIFO与异步FIFO,本实验中所用到的是异步FIFO。
在IP catalog搜索,AXI4-STREAM DATA FIFO,双击出现其配置界面:
首先我们知道的是,在AXI协议中,数据通过写通道实现master到slave的传输,读通道实现slave到master的传输。因此,在FIFO IP核中,接收数据的端口S_AXIS用来将数据写入IP核,而发送数据的端口M_AXIS用来将数据读出IP核。
关于IP核中各个信号的详解,可翻阅官方手册pg085-axi4stream-infrastructure.pdf第43页,以下对该实验中所需信号进行解释。
General
FIFO depth:FIFO深度,根据需要设定,可选范围16-32768,必须为2的整数次幂
Memory type:存储资源的使用类型–不同模式下可支持的功能不同,手册有具体介绍,自动即可,需要注意的是UltraRAM模式不支持异步时钟。
Independent clocks:独立时钟设置,该实验需要使用异步时钟
CDC sync stages:跨时钟域处理的同步阶数,自动即可。
Enable Packet Mode:设置为Yes将启动FIFO的数据包模式,且启用TLAST信号(该信号表示一个数据包的结尾)。FIFO的操作在包模式下被修改为存储传送的数据,直到TLAST信号被置位,我们这里先不使用。
ACLKEN Conversion Mode:此选项用来选择ACLKEN信号的转换模式,这里暂不使用。
Signal properties
TDATA Width(bytes):此参数指定AXI4上TDATA信号的宽度(以字节为单位)。整数,可以在0到512之间变化。端口位宽*8得到以bit为单位的数据位宽,这里设置为1.
Enable TSTRB, Enable TKEEP:TDATA WITH(字节)参数大于0时,才能使用这两项。改两项用来区分AXI协议中的三种流数据字节,包括数据字节(Data Byte),占位字节 (Position byte),空字节(Null byte),本次实验从简不使用。
Enable TLAST:TLAST信号,当FIFO启用数据包模式时,该信号指示一次传输数据流的最后一个数据,标志着该数据流的结束,本次实验从简不使用。
Flags
Enable write data count:写入数据(S_AXIS)计数输出端口,同步于写时钟。
Enable almost full:写入将满使能输出端口,同步于写时钟,Packet Mode模式下不允许使用,本实验从简不使用。
Enable programmable full:使能可编程满,同步于写时钟。可以自己设置阈值来提醒当前FIFO的写入状态。
Programmable full threshold:可编程满阈值。
Enable read data count:读出数据(M_AXIS)计数输出端口,同步于读时钟。
Enable almost empty:读出将空,同步于读时钟,Packet Mode模式下不允许使用,本实验从简不使用。
Enable programmable empty:使能可编程空,同步于读时钟。可以自己设置阈值来提醒当前FIFO的读取状态
Programmable empty threshold:可编程空阈值
此时的FIFO IP核框图如下
例化所用代码如下,包含了上述的各个信号。
//----------- Begin Cut here for INSTANTIATION Template ---// INST_TAG
axis_data_fifo_0 your_instance_name (
.s_axis_aresetn(s_axis_aresetn), // input wire s_axis_aresetn
.s_axis_aclk(s_axis_aclk), // input wire s_axis_aclk
.s_axis_tvalid(s_axis_tvalid), // input wire s_axis_tvalid
.s_axis_tready(s_axis_tready), // output wire s_axis_tready
.s_axis_tdata(s_axis_tdata), // input wire [7 : 0] s_axis_tdata
.m_axis_aclk(m_axis_aclk), // input wire m_axis_aclk
.m_axis_tvalid(m_axis_tvalid), // output wire m_axis_tvalid
.m_axis_tready(m_axis_tready), // input wire m_axis_tready
.m_axis_tdata(m_axis_tdata), // output wire [7 : 0] m_axis_tdata
.axis_wr_data_count(axis_wr_data_count), // output wire [31 : 0] axis_wr_data_count
.axis_rd_data_count(axis_rd_data_count) // output wire [31 : 0] axis_rd_data_count
);
// INST_TAG_END ------ End INSTANTIATION Template ---------
DAC FIFO实验模块
时钟控制模块MMCM
按照实验要求,DDS的工作时钟以及FIFO写时钟为100MHz,DAC的工作时钟以及FIFO读时钟为50MHz。
DDS模块
关于DDS模块的详情可参考第一次实验记录DDS的理解及IP核的使用
DDS IP核的设置如下
FIFO模块
ILA模块
主要观察的信号为FIFO 两端的数据端口以及读写计数端口。
顶层模块
代码如下:
`timescale 1ns / 1ps
module dac_fifo(
input sys_clk,
input rst_n,
//DA芯片接口
output da_clk, //DA(AD9708)驱动时钟,最大支持125Mhz时钟
output [7:0] da_data, //输出给DA的数据
//AD芯片接口
input [7:0] ad_data, //AD输入数据
output ad_clk //AD(AD9280)驱动时钟,最大支持32Mhz时钟
);
// -----------0、时钟控制模块-----------//
//PLL IP核例化
wire clk_50Mhz;
wire clk_100Mhz;
wire clk_25Mhz;
clk_wiz_0 clk_wiz_0_inst(
// Clock out ports
.clk_out1(clk_50Mhz), // 给DAC与FIFO读数据端提供50MHz采样时钟
.clk_out2(clk_100Mhz), // 给DDS与FIFO写数据段提供100MHz采样时钟
.clk_out3(clk_25Mhz), // output clk_out3
// 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 (clk_100Mhz),
.rst_n (rst_n),
.key_PINC (key_PINC),
.Fword (Fword)
);
//DDS IP核例化
wire [0:0] fre_ctrl_word_en ;
//output
wire [0:0] dds_m_axis_data_tvalid ;
wire [7:0] dds_m_axis_data_tdata ;
wire [0:0] dds_m_axis_phase_tvalid ;
wire [15:0] dds_m_axis_phase_tdata ;
wire [0:0] dds_s_axis_config_tready ;
assign fre_ctrl_word_en=1'b1;
dds_compiler_0 dds_compiler_0_inst (
.aclk(clk_100Mhz), // input wire aclk
.s_axis_config_tvalid(fre_ctrl_word_en), // input wire s_axis_config_tvalid
.s_axis_config_tready(dds_s_axis_config_tready ), // output wire s_axis_config_tready
.s_axis_config_tdata(Fword), // input wire [15 : 0] s_axis_config_tdata
.m_axis_data_tvalid(dds_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(dds_m_axis_data_tdata), // output wire [7 : 0] m_axis_data_tdata
.m_axis_phase_tvalid(dds_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(dds_m_axis_phase_tdata) // output wire [15 : 0] m_axis_phase_tdata
);
// -----------2、fifo模块----------//
// 写FIFO端口
wire [31 : 0] fifo_axis_wr_data_count;
wire fifo_s_axis_tready; // FIFO tready
// 读FIFO端口
wire da_m_axis_tready;
wire fifo_m_axis_tvalid;
wire [7 : 0] fifo_m_axis_tdata;
wire [31 : 0] fifo_axis_rd_data_count;
axis_data_fifo_0 axis_data_fifo_0_inst (
.s_axis_aresetn(rst_n), // input wire s_axis_aresetn
.s_axis_aclk(clk_100Mhz), // input wire s_axis_aclk
.m_axis_aclk(clk_50Mhz), // input wire m_axis_aclk
.s_axis_tvalid(dds_m_axis_phase_tvalid), // input wire s_axis_tvalid
.s_axis_tready(fifo_s_axis_tready), // output wire s_axis_tready
.s_axis_tdata(dds_m_axis_data_tdata), // input wire [7 : 0] s_axis_tdata
.m_axis_tvalid(fifo_m_axis_tvalid), // output wire m_axis_tvalid
.m_axis_tready(da_m_axis_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
);
// -----------3、AD9708-----------//
AD9708 AD9708_inst(
.clk (clk_50Mhz),
.rst_n (rst_n),
.data_in (fifo_m_axis_tdata),
.da_clk (da_clk),
.da_data (da_data),
.da_m_axis_tready(da_m_axis_tready)
);
// -----------4、ILA模块---------//
//ILA IP核例化
ila_0 ila_0_inst (
.clk(clk_50Mhz), // input wire clk
.probe0(dds_m_axis_data_tdata), // input wire [7:0] probe0
.probe1(fifo_m_axis_tdata), // input wire [7:0] probe1
.probe3(fifo_axis_wr_data_count), // input wire [31:0] probe3
.probe4(fifo_axis_rd_data_count) // input wire [31:0] probe4
);
endmodule
DDS模块中的频率控制代码如下:
`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=100MHz
// 输出频率的计算公式f_out=f_clk*deta_theta/(2^B)=100M* 1966/(2^16 )= 3M
always @(*)begin
case(key_PINC)
0: Fword <= 'h51e; //1Mhz 1310 每次相位增加的值 deta_theta
1: Fword <= 'hf5c; //3Mhz 3932
endcase
end
endmodule
DA数据发送模块AD9708代码如下:
`timescale 1ns / 1ps
module AD9708(
input clk , //时钟
input rst_n , //复位信号,低电平有效
//AXI-S接口
input [7:0] data_in, //DDS读出的数据
output da_m_axis_tready, // 由于DAC的工作频率小于DDS工作频率,所以DAC接口控制器给FIFO的RDY信号应该一直为高。
//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数据端口
assign da_m_axis_tready = 1'b1;
endmodule
由于该实验效果与带FIFO的ADDA实验相差不多,所以放在下部分一并讨论
带FIFO的ADDA实验
相较于DAC FIFO实验,该实验仅需要在顶层模块中例化AD数据接收模块AD9280,并且ILA需要添加观察信号ad_data。
// -----------4、AD9280-----------//
AD9280 AD9280_inst(
.clk (clk_25Mhz),
.rst_n (rst_n),
.ad_clk (ad_clk),
.ad_data (ad_data)
);
`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
实验结果
RTL图
由RTL图可知 带FIFO的ADDA实验相较于DAC FIFO实验多出了AD数据接受模块FIFO两端数据的写入与读出分别接到了DDS的输出以及DA数据发送模块AD9708,MMCM为DDS以及FIFO写端口提供了100MHz,DAC以及FIFO读端口提供了50MHz。
ILA波形图
下载验证,观察ILA波形,1Mhz正弦波如下:
3Mhz正弦波如下:
Matlab分析验证
filename = 'C:\zynq\dac_fifo\dac_fifo.srcs\csv1\iladata.csv';
M = readmatrix(filename,'Range','D3:D4098'); %读取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('幅值');
参考资料
[1]AXI总线介绍_theboynoName的博客-CSDN博客
[2]带你快速入门AXI4总线--AXI4-Stream篇(1)----AXI4-Stream总线_孤独的单刀的博客-CSDN博客_axi stream
[3]pg085-axi4stream-infrastructure.pdf