基于Zynq的数据采集系统设计与调试(三) —— FIFO的使用
2019-07-13 20:24 发布
生成海报
前言:
FIFO是数据采集系统中必不可少的环节,AD采回来的数据要送至ARM/DSP处理,或将采回来的数据写到本地,都需要解决读写速度匹配问题,解决这类问题,首选FIFO。
在我们的设计当中,使用的是ADI公司的AD7989,18bit,100KSPS,采用三线SPI数据传输模式。采用两级FIFO,第一级FIFO用于缓存AD采样点数据,第二级FIFO用于DMA数据传输。
一. FIFO的使用
在本设计中,将数据这样打包:一个package包含1024个字(4096Byte)其中1020个AD采样数据点(AD采样数据扩展成32bit),4个字的数据包信息:通道号(有两个通道)、块号(package编号)、触发信号位置、CRC校验码。
第一级FIFO(FIFO1)缓存AD采样点,时钟与AD时钟相同,都是50MHz,第二级FIFO(FIFO2)从第一级FIFO取数据,当取到1020个数据时,就往FIFO2中写4个字的数据包信息,第二级FIFO用于DMA将数据流送至内存DDR3的数据缓存。FIFO2采用异步时钟,因为触发信号(Trig)是一个脉冲,触发信号模块使用的时钟也是50MHz,因此写时钟仍然是50MHz,读时钟采用DMA时钟(200MHz)。FIFO都是Xilinx
Vivado下自带的IP core。
FIFO1的写使能信号是ad模块的ad_data_rdy,当FIFO1中有数据时,读使能信号就有效(rd_en = !fifo_empty); FIFO2的写使能信号在FIFO1数出数据有效后一个时钟有效, 读使能是FIFO2的prog_empty来控制的。关于prog_empty信号,一开始的时候对Empty Threshold Assert Value和Empty Threshold Negate Value理解有误,后来查看了Xilinx
FIFO ipCore手册pg057-fifo-generator.pdf的pg105-106得知:
Forbuilt-in FIFOs, the number of entries in the FIFO must be greater than thethreshold value + 1 before prog_empty is deasserted. For non built-inFIFOs(block RAM, distributed RAM, and so forth), if the number
of entries inthe FIFO is greater than threshold value, prog_empty is deasserted. 本设计使用的是Block RAM,对于Block RAM FIFO而言,当FIFO中的数据个数大于Negate Value时,prog_empty=0 ,当FIFO中数据个数小于等于Assert Value时,prog_empty=1 。本设计中,FIFO_RD_EN
= prog_empty,下图是第二级FIFO读写数据时序图,wr_fifo2_count表示写入到FIFO中数据个数,read_pointer表示从FIFO中读出的数据个数,FIFO_ALMOST_EMPTY即prog_empty信号。
左边红线可看出,本设计中设置的Empty Threshold Negate Value=16,当FIFO中有17(>= Negate Value)个数据时,prog_empty=0。本设计中设置的Empty Threshold Assert Value=2,所以当FIFO中数据个数小于等于2时,prog_empty要变为1。对应到右边红线,在这个时钟边沿,FIFO中正好还有2个数据,因此prog_empty由0变为1,而FIFO_RD_EN =
!prog_empty,此时仍有效,因此会继续从FIFO中读出一个数据。
二. FIFO的配置
1. FIFO1的配置如下:
1) 选型: Common Clock Block RAM
2) 设置数据深度:因ad7989采样率为100KSPS,每隔10us转换得到1个数据,DAM时钟是200M,相对而言AD速度较慢,因此深度设为64就满足设计要求,但考虑到资源使用情况都是1个18K BRAM,所以设置为128
3) 其他为默认设置
2. FIFO2的配置如下:
1) 选型: Independent Clock Block RAM
2) 数据深度设置: 此处设为1个package的数据深度(可能优化的时候会更改,但1024足够满足本设计要求的)
3) 设置prog_empty(很关键):Empty Threshold Assert Value=2,Empty Threshold Negate Value=16
为什么要用prog_empty信号来作为FIFO2的读使能控制信号(rd_en = !prog_empty)呢? 因为DMA的MAX Burst Length = 16,关于DMA的Burst传输我是这样理解的,(如果有误,还请指点哈!) 对于Write Channel(write channel --- Receive packet --- S2MM),dma端先送TREADY信号, 等待M_AXIS_S2MM主机的TVALID和TDATA,每接收一个数据内部计数器加一,当计数器达到16时,TREADY拉低,dma申请一次总线,然后把接收到的数据写入到DDR3(或者内存)中,即每接收burst
length个数据,申请一次总线,这样就不会太占用系统资源。使用prog_empty作为FIFO读使能控制,可保证连续读出Burst Length个数据,这样就不会每次占用总线太长时间。
4) 其他采用默认设置
三. ad7989_fifo模块的实现
[plain] view
plain copy
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2016/06/20 11:03:04
// Design Name:
// Module Name: ad7989_fifo
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module ad7989_fifo(
input ad_clk,rst_n,ad_start,//ad_start: ad_start是由PS部分发送过来的ad采样控制信号
// input trig_in,//trig_in由DA部分(DDS)传过来脉冲信号,启动ad转换
// AD SPI port
input ad_sdo, //ad转换串型数据
output ad_cnv,
output ad_sclk,
// FIFO2 port
input rd_clk,
input rd_en,
output empty,
output full,
output almost_empty, //Empty Threshold Assert Valie = 16,Empty Threshold Negate Valie = 17,
output [31:0] fifo_dout
);
// intern signal
wire ad_data_rdy;
wire [17:0] ad_data;
wire [31:0] sample_data;
wire [31:0] fifo1_dout;
wire [31:0] fifo2_din;
wire fifo1_empty;
wire wr_fifo1_en;
wire rd_fifo1_en;
wire wr_fifo2_en;
wire trigger;
wire trig_in;
// reg variable
reg [31:0] block_num = 0; //块序列号
reg [31:0] trig_position = 32'd0; //触发信号来时对应的AD数据位置信息
reg [31:0] channel_num = 32'd1; //通道号: 1表示1倍频通道采集的数据,2表示2倍频采集的数据
reg [31:0] crc_code = 32'd0; //crc校验码
// trig_in detect
reg trig_reg1 = 0;
reg trig_reg2 = 0;
// FIFO_1
reg [9:0] wr_fifo1_count = 0; //Sample data 计数器 4096Byte数据(1020次)一次循环
// reg rd_fifo1_en_tmp;
// FIFO_2
reg wr_fifo2_en_tmp1;
reg wr_fifo2_en_tmp2;
reg [10:0] wr_fifo2_count = 0;
reg [31:0] fifo2_din_reg;
assign sample_data = ({{15{ad_data[17]}},ad_data[16:0]}); //
// always @(posedge ad_clk) begin
// rd_fifo1_en_tmp <= wr_fifo1_en;
// end
// assign rd_fifo1_en = rd_fifo1_en_tmp;
assign wr_fifo1_en = ad_data_rdy;
assign rd_fifo1_en = !fifo1_empty;
//=========== fifo1_data count ===========
always @(posedge ad_clk) begin
if(wr_fifo1_count == 11'd1020) wr_fifo1_count <= 11'd0; //一个packet包含1020个ADC数据
else if(wr_fifo1_en) wr_fifo1_count <= wr_fifo1_count + 1'b1;
else wr_fifo1_count <= wr_fifo1_count;
end
//========== trig_position ==========
always @(posedge ad_clk) begin
trig_reg1 <= trig_in;
trig_reg2 <= trig_reg1;
end
assign trigger = trig_reg1 & (!trig_reg2);
always @(posedge ad_clk) begin // trig_in是边沿信号
if(trigger == 1) trig_position <= wr_fifo1_count + 1; //trig_position
else if(wr_fifo2_count == 11'd1024) trig_position <= 0; //wr_fifo2_count = 1022时,将trig_position写入fifo中
else trig_position <= trig_position;
end
// ========== block_num ==========
always @(posedge ad_clk) begin
if(wr_fifo2_count == 11'd1024 && wr_fifo2_en == 1) block_num <= block_num + 1'b1;
else block_num <= block_num;
end
// ========== crc_code ==========
always @(posedge ad_clk) begin
if(!rst_n) crc_code <= 0;
else if(wr_fifo2_count < 11'd1020 && wr_fifo2_en == 1) crc_code <= crc_code ^ fifo2_din_reg;
else case(wr_fifo2_count)
11'd1020: crc_code <= crc_code ^ block_num;
11'd1021: crc_code <= crc_code ^ trig_position;
11'd1022: crc_code <= crc_code ^ channel_num;
11'd1023: crc_code <= crc_code;
11'd1024: crc_code <= 0;
endcase
end
// ========== wr_fifo2_en ==========
always @(posedge ad_clk) begin
wr_fifo2_en_tmp1 <= rd_fifo1_en;
wr_fifo2_en_tmp2 <= wr_fifo2_en_tmp1;
end
assign wr_fifo2_en = (wr_fifo2_count <= 11'd1020) ? wr_fifo2_en_tmp2 : 1'b1; //连续写4个字的控制信息: 块序列号、crc校验码、触发信号来时对应的AD数据位置信息、通道号
// ========== fifo2 data count ==========
always @(posedge ad_clk) begin
if(wr_fifo2_count == 11'd1024) wr_fifo2_count <= 0;
else if(wr_fifo2_en == 1) wr_fifo2_count <= wr_fifo2_count + 1'b1;
else if(wr_fifo2_count>= 11'd1020) wr_fifo2_count <= wr_fifo2_count + 1'b1;
else wr_fifo2_count <= wr_fifo2_count;
end
// ========== fifo2_din ==========
assign fifo2_din = fifo2_din_reg;
always @(posedge ad_clk) begin
case(wr_fifo2_count)
11'd1020: fifo2_din_reg <= block_num;
11'd1021: fifo2_din_reg <= trig_position;
11'd1022: fifo2_din_reg <= channel_num;
11'd1023: fifo2_din_reg <= crc_code;
default: fifo2_din_reg <= fifo1_dout;
endcase
end
ad7989_dev_if U_ad(
.ad_clk(ad_clk),
.rst_n(rst_n),
.ad_start(ad_start),//ad_start: ad_start是由PS部分发送过来的ad采样控制信号
.ad_sdo(ad_sdo), //ad转换串型数据
.ad_cnv(ad_cnv),
.ad_sclk(ad_sclk),
.ad_data(ad_data),
.ad_data_rdy(ad_data_rdy) // data is available
);
trig_generator U_trig(
.ad_clk(ad_clk),
.rst_n(rst_n),
.ad_data_rdy(ad_data_rdy),
.trig_signal(trig_in)
);
fifo_sample_data U_ad_samle_fifo (
.clk(ad_clk), // input wire clk
.srst(!rst_n), // input wire srst
.din(sample_data), // input wire [31 : 0] din
.wr_en(wr_fifo1_en), // input wire wr_en
.rd_en(rd_fifo1_en), // input wire rd_en
.dout(fifo1_dout), // output wire [31 : 0] dout
.full(), // output wire full
.empty(fifo1_empty) // output wire empty
);
fifo_dma_stream_0 U_dma_fifo (
.rst(!rst_n), // input wire rst
.wr_clk(ad_clk), // input wire wr_clk
.rd_clk(rd_clk), // input wire rd_clk
.din(fifo2_din), // input wire [31 : 0] din
.wr_en(wr_fifo2_en), // input wire wr_en
.rd_en(rd_en), // input wire rd_en
.dout(fifo_dout), // output wire [31 : 0] dout
.full(full), // output wire full
.empty(empty), // output wire empty
.prog_empty(almost_empty)
);
endmodule
打开微信“扫一扫”,打开网页后点击屏幕右上角分享按钮