基于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信号。

基于Zynq的数据采集系统设计与调试(三) —— FIFO的使用_第1张图片

    左边红线可看出,本设计中设置的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

        基于Zynq的数据采集系统设计与调试(三) —— FIFO的使用_第2张图片

       

      

        2) 设置数据深度:因ad7989采样率为100KSPS,每隔10us转换得到1个数据,DAM时钟是200M,相对而言AD速度较慢,因此深度设为64就满足设计要求,但考虑到资源使用情况都是1个18K BRAM,所以设置为128

基于Zynq的数据采集系统设计与调试(三) —— FIFO的使用_第3张图片

     

          3) 其他为默认设置


    2. FIFO2的配置如下:

        1) 选型: Independent Clock Block RAM

基于Zynq的数据采集系统设计与调试(三) —— FIFO的使用_第4张图片


        2) 数据深度设置: 此处设为1个package的数据深度(可能优化的时候会更改,但1024足够满足本设计要求的)

基于Zynq的数据采集系统设计与调试(三) —— FIFO的使用_第5张图片


        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个数据,这样就不会每次占用总线太长时间。

        基于Zynq的数据采集系统设计与调试(三) —— FIFO的使用_第6张图片

        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


  

你可能感兴趣的:(FPGA/Verilog,ARM/Zynq7020)