基于Zynq的数据采集系统设计与调试(三) —— FIFO的使用
前言:
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模块的实现
- `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