DSP

基于Zynq的数据采集系统设计与调试(三) —— FIFO的使用

2019-07-13 20:55发布

前言:     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