1. 实验内容
- 基于“FPGA实验1:DDS IP 数字波形合成DAC ”和 “FPGA实验2: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端口的变化。
- 用VIO配置频率字,分别生成1MHz和3MHz的DDS正弦波形,用system ILA抓取DAC的输入数据,用Matlab分析频谱,验证频率正确。
- 本实验是一个典型的带反向流控的跨时钟域传输信号的例子
2. 实验背景知识
2.1. FIFO
FIFO即First In First Out,是一种先进先出数据存储、缓冲器,我们知道一般的存储器是用外部的读写地址来进行读写,而FIFO这种存储器的结构并不需要外部的读写地址而是通过自动的加一操作来控制读写,这也就决定了FIFO只能顺序的读写数据。
FIFO 的典型结构如下,主要分为读和写两部分,另外就是状态信号,空和满信号,同时还有数据的数量状态信号,与RAM 最大的不同是 FIFO 没有地址线,不能进行随机地址读取数据,什么是随机读取数据呢,也就是可以任意读取某个地址的数据。而 FIFO 则不同,不能进行随机读取,这样的好处是不用频繁地控制地址
线。
虽然用户看不到地址线,但是在 FIFO 内部还是有地址的操作的,用来控制 RAM 的读写接口。其地址在读写操作时如下图所示,其中==深度值也就是一个 FIFO 里最大可以存放多少个数据。==初始状态下,读写地址都为 0,在向 FIFO 中写入一个数据后,写地址加 1,从 FIFO 中读出一个数据后,读地址加 1。此时 FIFO 的状态即为空,因为写了一个数据,又读出了一个数据。
可以把 FIFO 想象成一个水池,写通道即为加水,读通道即为放水,假如不间断的加水和放水,如果加水速度比放水速度快,那么 FIFO 就会有满的时候,如果满了还继续加水就会溢出overflow,如果放水速度比加水速度快,那么 FIFO 就会有空的时候,所以把握好加水与放水的时机和速度,保证水池一直有水是一项很艰巨的任务。也就是判断空与满的状态,择机写数据或读数据。
2.2. 同步FIFO和异步FIFO
根据读写时钟,可以分为同步 FIFO(读写时钟相同)和异步 FIFO(读写时钟不同)。
同步FIFO,读和写应用同一个时钟。它的作用一般是做交互数据的一个缓冲,也就是说它的主要作用就是一个buffer。
异步FIFO,读写应用不同的时钟,它有两个主要的作用,一个是实现数据在不同时钟域进行传递,另一个作用就是实现不同数据宽度的数据接口。
3. 实验步骤
1. 本实验基于“FPGA实验1:DDS IP 数字波形合成DAC ” 和“FPGA实验2:ADDA测试”做进一步实验,使用vivado的IP Catalog工具,配置IP参数,其中VIO参数配置与实验2相同。
DDS参数配置:

Clocking wizard参数配置:
其余参数配置与前两个实验相同。
AXI4-Stream Data FIFO参数配置:
FIFO depth
FIFO的深度,可以在16到32768之间变化,具体情况视情况而定,但要是2的n次幂。
Enable packet mode
使能包模式:此项设定需要TLAST信号被使能。FIFO的操作在包模式下被修改为存储传送的数据,直到TLAST信号被响应。当TLAST信号被响应或者FIFO满了,存储的数据将被送至AXI4-Stream master interface。
Asynchronous Clocks
异步时钟:启用后S_AXIS_ACLK和M_AXIS_ACLK将会是异步时钟。
Synchronization Stages across Cross Clock Domain Logic
当启用异步时钟后,才会有该选项,其作用相当于跨时钟域时的打拍操作。一般默认即可。
ACLKEN Conversion Mode
此选项用来选择ACLKEN信号的转换模式。
None:没有和这个IP相关的ACLKEN 信号相关;
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信号。
Signal Properties
信号特性:可以看到,软件可以自动计算,当然我们也可以手动修改。
TDATA width (bytes)
参数指定axi4流上TData信号的宽度(以字节为单位接口。此参数是一个整数,可以从0到512不等。设置为0以忽略TDATA信号。如果省略了tdata信号,则tkeep和tstb信号也会省略。如图设置为1则可以看到位宽为8bit。
Enable TSTRB
是否使能TSTRB信号,只有当TData width(bytes)参数大于0时,才能启用此选项。
Enable TKEEP
是否使能TKEEP信号,只有当TData width(bytes)参数大于0时,才能启用此选项。
Enable TLAST
是否使能TKEEP信号,只有当TData width(bytes)参数大于0时,才能启用此选项。
TID width (bits)
用来指定TID信号的位宽,0为忽略,1~32为相应的位宽。
TDEST width (bits)
用来指定TDEST 信号的位宽,0为忽略,1~32为相应的位宽。
TUSER Width (bits)
用来指定TUSER 信号的位宽,0为忽略,1~32为相应的位宽。
ILA参数配置:
ILA观察的波形:FIFO输出波形,DAC接收到的数据,DDS生成的数据,FIFO data count信号。

2. 设计顶层(top)模块,DA数据发送(da_wave_send)模块和Fword_set(频率控制字)模块,在前两个实验基础上例化AXI4-Stream Data FIFO,并将接口对应连接起来。
程序结构
顶层(top)设计
`timescale 1ns / 1ps
module top(
input sys_clk ,//系统时钟 50MHz T=20ns
input sys_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、VIO按键控制频率控制字(key_PINC)
wire [1:0] key_PINC;
wire [7:0] ping_0x80;
vio_0 vio_0 (
.clk(sys_clk), // input wire clk
.probe_out0(key_PINC), // output wire [1 : 0] probe_out0
.probe_out1(ping_0x80)
);
//---------------1、MMCM模块--------------//
//output
wire [0:0] clk_out100M;
wire [0:0] clk_out25M;
//---------------2、信号频率控制模块--------------//
wire [23:0] Fword ; //频率字
Fword_set Fword_set(
//input
.clk (clk_out100M ),
.rst_n (sys_rst_n ),
.key_PINC (key_PINC ),
//output
.Fword (Fword )
);
//---------------3、DDS模块--------------//
//input
wire [0:0] fre_ctrl_word_en ;
wire dds_m_axis_data_tready;
wire dds_m_axis_phase_tready;
//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 [23:0] dds_m_axis_phase_tdata ;
wire [0:0] dds_s_axis_config_tready ;
assign fre_ctrl_word_en=1'b1;
//---------------4、axis_data_fifo模块--------------//
//output
wire [31:0] fifo_axis_wr_data_count;
wire [31:0] fifo_axis_rd_data_count;
wire [31:0] fifo_axis_data_count;
wire [7:0] fifo_m_axis_tdata;
wire fifo_m_axis_tvalid;
wire fifo_s_axis_tready;
wire da_m_axis_tready;
//例化MMCM IP
clk_wiz_0 clk_wiz_0
(
// Clock out ports
.clk_out1(clk_out100M), // output clk_out1
.clk_out2(clk_out25M), // output clk_out2
// Status and control signals
.reset(~sys_rst_n), // input reset
// Clock in ports
.clk_in1(sys_clk)); // input clk_in1
//例化DDS IP
dds_compiler_0 dds_compiler_0 (
.aclk (clk_out100M ), // 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 [23: 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 [15 : 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 [23 : 0] m_axis_phase_tdata
);
//例化axis_data_fifo_0 IP
axis_data_fifo_0 axis_data_fifo_0 (
.s_axis_aresetn (sys_rst_n), // input wire s_axis_aresetn 低电平有效
.m_axis_aresetn (sys_rst_n), // input wire m_axis_aresetn 低电平有效
.s_axis_aclk (clk_out100M), // input wire s_axis_aclk 连接DDS时钟
.s_axis_tvalid (dds_m_axis_data_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 连接DDS输出
.m_axis_aclk (sys_clk), // input wire m_axis_aclk 连接DAC时钟
.m_axis_tvalid (fifo_m_axis_tvalid), // output wire m_axis_tvalid
.m_axis_tready (dds_s_axis_config_tready), // input wire m_axis_tready 连接DDS的tready
.m_axis_tdata (fifo_m_axis_tdata), // output wire [7 : 0] m_axis_tdata
.axis_data_count (fifo_axis_data_count), // output wire [31 : 0] axis_data_count
.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_wave_send da_wave_send(
.clk (sys_clk),
.rst_n (sys_rst_n),
.ping_0x80 (ping_0x80),
.rd_data (fifo_m_axis_tdata),
.da_m_axis_tready (da_m_axis_tready),
.da_clk (da_clk),
.da_data (da_data)
);
//例化ILA IP
ila_0 ila_0 (
.clk(sys_clk), // input wire clk
.probe0(fifo_m_axis_tdata), // input wire [7:0]
.probe1(da_data), // input wire [7:0]
.probe2(dds_m_axis_data_tdata), // input wire [7:0]
.probe3(fifo_axis_data_count) // input wire [31:0]
);
endmodule
频率控制字(Fword_set)模块
`timescale 1ns / 1ps
//
// 通过按键来选择对应的频率控制字,进而选择对应的信号频率
//
module Fword_set(
input clk ,
input rst_n ,
input [1:0] key_PINC ,
output reg [23:0] Fword
);
//always@(posedge clk or negedge rst_n)
//begin
// if(!rst_n)
// key_sel <= 4'd0;
// else
// key_sel <= key_sel;
//end
// 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=20bits Frequency per channel=100MHz
// 输出频率的计算公式f_out=f_clk*deta_theta/(2^B)=100M* 104857/(2^20 )= 10M
always@(*)
begin
case(key_PINC)
0: Fword <= 'h28f5; //1Mhz 10485 每次相位增加的值 deta_theta
1: Fword <= 'h51eb; //2Mhz 20971
2: Fword <= 'h7ae1; //3Mhz 31457
3: Fword <= 'ha3d7; //4Mhz 41943
endcase
end
endmodule
明明已经改变了DDS的工作时钟为100Mhz,为什么这里的相位增量还是与前两个实验保持一致,我理解的原因是板载固定50Mhz,所以即使DDS的工作时钟为100Mhz,它实际还是只能达到50Mhz。
DA数据发送(da_wave_send)模块
`timescale 1ns / 1ps
module da_wave_send(
input clk , //时钟
input rst_n , //复位信号,低电平有效
input [7:0] ping_0x80, //将有符号DDS数据+128使其变为无符号数符合DAC输入范围
input [7:0] rd_data, //读出的数据
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
//*****************************************************
//数据rd_data是在clk的上升沿更新的,所以DA芯片在clk的下降沿锁存数据是稳定的时刻
//而DA实际上在da_clk的上升沿锁存数据,所以时钟取反,这样clk的下降沿相当于da_clk的上升沿
assign da_clk = ~clk;
assign da_data = rd_data^ping_0x80; //将读到的数据变成无符号数据赋值给DA数据端口
assign da_m_axis_tready = 1'b1;
endmodule
3. 设计约束文件
这里约束文件与前两个实验相同。
set_property PACKAGE_PIN U18 [get_ports sys_clk]
set_property IOSTANDARD LVCMOS33 [get_ports sys_clk]
set_property PACKAGE_PIN N15 [get_ports sys_rst_n]
set_property IOSTANDARD LVCMOS33 [get_ports sys_rst_n]
set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN F19} [get_ports {da_data[7]}]
set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN G20} [get_ports {da_data[6]}]
set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN G19} [get_ports {da_data[5]}]
set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN H18} [get_ports {da_data[4]}]
set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN J18} [get_ports {da_data[3]}]
set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN L20} [get_ports {da_data[2]}]
set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN L19} [get_ports {da_data[1]}]
set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN M20} [get_ports {da_data[0]}]
set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN F20} [get_ports da_clk]
set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN E18} [get_ports {ad_data[7]}]
set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN E19} [get_ports {ad_data[6]}]
set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN D19} [get_ports {ad_data[5]}]
set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN D20} [get_ports {ad_data[4]}]
set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN M17} [get_ports {ad_data[3]}]
set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN M18} [get_ports {ad_data[2]}]
set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN L16} [get_ports {ad_data[1]}]
set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN L17} [get_ports {ad_data[0]}]
set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN G18} [get_ports ad_clk]
#时序约束
create_clock -period 20.000 -name sys_clk -waveform {0.000 10.000} [get_ports sys_clk]
4. RTL图
5. 生成bitstream,连接开发板测试,利用VIO控制频率控制字,利用ILA观察DAC的输入数据。
ILA观察的波形:FIFO输出波形,DAC接收到的数据,DDS生成的数据,FIFO data count信号。
DDS和FIFO_data波形要将 Waveform Style 设置为 Analog(模拟),Radix 设置为 Signed Decimal(有符号十进制)。
DA波形要将 Waveform Style 设置为 Analog(模拟),Radix 设置为 Unsigned Decimal(无符号十进制)。
1Mhz正弦波:
key_PINC=0
3Mhz正弦波:
key_PINC=2
从ila观察到的波形看出,FIFO和DDS的输出波形的相位并不相同,原因可以从AXI握手时序图理解。
数据只有在主设备的VALID和从设备的READY信号都有效时才开始传输,所以产生了主设备和从设备之间数据的相位差。![图片源自参考资料[1]](https://img-blog.csdnimg.cn/7b4dfb5fcae04f258ec42af3125a3442.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAbTBfNDcxMjExMTI=,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center)
6. 把ILA波形导出到CSV文件,在Matlab里分析波形的频谱,验证生成波形的正确性。
导出csv文件,并将导出的csv文件拖入matlab工作区中导入数据
matlab代码:
da_data_OBUF70 = iladatafifo1Mhz{:,5}; %取出数据
SampleinWindow = iladatafifo1Mhz{:,2};
fs=50000000; %采样频率50Mhz
N=4096; %采样点数
n=0:N-1;
t=n/fs; %时间序列
f=n*fs/N; %频率序列
figure;
subplot(2,1,1)
plot(t,da_data_OBUF70); %时域波形
title('时域波形');xlabel('时间/s');ylabel('幅值');
y=abs(fft(da_data_OBUF70,N));
subplot(2,1,2)
plot(f,y); %频域波形
title('频域波形');xlabel('频率/hz');ylabel('幅值');
1Mhz正弦波:
key_PINC=0
频域波形中有两个明显的尖峰,第一个尖峰位于1Mhz,第二个尖峰位于49Mhz(共轭对称特性)。从波形中可验证输出波形频率为1Mhz。
3Mhz正弦波:
key_PINC=2
从波形中可验证输出波形频率为3Mhz。
参考文献
[1] https://www.cnblogs.com/xuqing125/p/8337586.html
[2] course_s1_ZYNQ那些事儿-FPGA实验篇V1.06.pdf
[3] CSDN博客:https://blog.csdn.net/QQ286615275/article/details/90297888
[4] 官方文档:pg085-axi4stream-infrastructure.pdf
https://china.xilinx.com/content/dam/xilinx/support/documents/ip_documentation/axis_infrastructure_ip_suite/v1_1/pg085-axi4stream-infrastructure.pdf
[5] CSDN博客:https://blog.csdn.net/szm1234/article/details/123454871