PL2PS2PL v3.0 数据输入的实现2

本文详细描述了一个项目中如何在不同时钟速率下实现FIFO,涉及音频信号的31.25kHz采样、40MHz写入、120MHz读取以及与DMA的交互,包括采样率调整、时序同步、中断管理和数据一致性处理等关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

米联客给出的数据输入仅仅是在相同时钟域下的实现,fifo的输入输出时钟都为100MHz,然而在我的项目中,需要的是对音频信号以31.25KHz进行采样,然后将采样得到的数据以40MHz的速率送到fifo中,并以120MHz的速率进行读取。

在读取到DDR中之后,在PS端进行滤波处理,滤波结束后DMA以120Mhz传输到fifo_out,并以8Mhz进行读取输出。

这篇将记录异步时钟域fifo的实现。

BD工程结构

版本1

版本2

测试数据产生

已知实际数据为31.25kHz频率采样,且每个数据会伴随一个valid信号,故采用下列代码产生31.25kHz的递增数列数据。



module test(
    input clk,
    input rst,
    input en,

    output reg valid,
    output reg [63:0] data_out
    );



    reg [10:0]   cnt;
    // reg [9:0]   cnt1;
    always @(posedge clk or negedge rst) begin
        if (!rst || !en) begin
            cnt = 1'b0;
        end
        else begin
            if (cnt >= 11'd1279) begin
                cnt = 1'b0;
            end
            else begin
                cnt = cnt + 1'b1;
            end
        end

    end

    always @(posedge clk or negedge rst) begin
        if (!rst) begin
            valid = 1'b0;
        end
        else begin
            if (cnt == 11'd1) begin
                valid = 1'b1;
            end
            else begin
                valid = 1'b0;
            end
        end
    end

    always @(posedge clk or negedge rst) begin
        if (!rst || !en) begin
            data_out = 64'b0;
        end
        else begin
            // if (data_out == 64'd1023 && cnt == 5'd9) begin
            //     data_out = 64'b0;
            // end
            // else 
            if (cnt == 5'd9) begin
                data_out = data_out + 64'd1;
            end
            else begin
                data_out = data_out;
            end

        end
    end




endmodule

LAST信号产生

为了实现axis stream fifo的正确读写和时序同步,每帧数据末尾产生一个last信号。

module datainput(
    input   wire clk40M,
    input   wire s_axis_aresetn,
    input   wire valid,
    input   wire [63:0] data_in,
    input   wire S_AXIS_tready,
    input   en,
    input   wire s_axis_aclk,


    output  reg  S_AXIS_tvalid,
    output  reg  S_AXIS_tlast,
    output  wire [7:0]S_AXIS_tkeep,
    output  wire [63:0] S_AXIS_tdata,
    output  reg  [10:0] cnt_r
    );



    reg [10:0]  cnt ;
    wire flag;
    reg  first = 1'b1;
  
    assign S_AXIS_tkeep = 8'b11111111;
    assign S_AXIS_tdata = data_in;
  


   always @(posedge clk40M or negedge s_axis_aresetn) begin   //计数器
        if(!s_axis_aresetn || !en) begin
             cnt <= 11'd0;
             cnt_r <= 11'd0;
        end
        else if(valid && S_AXIS_tready) begin
            // if (first) begin
            //     if(cnt < 11'd515) begin
            //         cnt <= cnt + 1'b1;
            //     end
            //     else begin
            //         cnt <= 11'd0;  
            //     end
            //     cnt_r <= cnt;
            // end
            // else begin
                if(cnt < 11'd511) begin
                    cnt <= cnt + 1'b1;
                end
                else begin
                    cnt <= 11'd0;  
                end
                cnt_r <= cnt;
            // end
        end
        else begin
            cnt <= cnt; 
            cnt_r <= cnt;
        end
   end



//    always @(posedge clk40M ) begin
//         cnt_r <= cnt;
//    end  


//    always @(posedge clk40M or negedge s_axis_aresetn) begin
//         if(!s_axis_aresetn)begin
//             S_AXIS_tvalid <= 1'b0;
//             S_AXIS_tlast <= 1'b0;
//             flag <= 1'b0;
//         end
//         else if (cnt == 11'd0&& flag) begin
//             flag <= 1'b0;
//             S_AXIS_tlast <= 1'b1;
//             S_AXIS_tvalid <= valid;
//         end
//         else if (cnt == 11'd1023) begin
//             flag <= 1'b1;
//             S_AXIS_tvalid <= valid;
//         end
//         else
//             S_AXIS_tlast <= 1'b0;
//             S_AXIS_tvalid <= valid;
//    end

//    always @(posedge clk40M or negedge s_axis_aresetn) begin
//     if(!s_axis_aresetn)begin
//         S_AXIS_tvalid <= 1'b0;
//         S_AXIS_tlast <= 1'b0;
//         // flag <= 1'b0;
//     end
//     else if (valid && S_AXIS_tready) begin
//         S_AXIS_tvalid <= 1'b1;
//         if (cnt_r == 11'd1023) begin
//             S_AXIS_tlast <= 1'b1;
//         end
//     end
//     else if(!S_AXIS_tready) begin
//         S_AXIS_tvalid <= S_AXIS_tvalid;
//         S_AXIS_tlast <= S_AXIS_tlast;
//     end
//     else begin
//         S_AXIS_tvalid <= 1'b0;
//         S_AXIS_tlast <= 1'b0;
//     end
// end

    reg [1:0] state;
    assign flag = (cnt != cnt_r )? 1'b1:1'b0;


   always@(posedge clk40M)
   begin
       if(!s_axis_aresetn) begin
           S_AXIS_tvalid <= 1'b0;
           S_AXIS_tlast <= 1'b0;
           state <=0;
       end
       else begin
          case(state)
            0: begin
                if(en && S_AXIS_tready) begin
                    if (flag) begin
                        S_AXIS_tvalid <= 1'b1;
                        state <= 1;
                    end
                    else begin
                        S_AXIS_tvalid <= 1'b0;
                        state <= 0;
                    end
                end
                else begin
                   S_AXIS_tvalid <= 1'b0;
                   state <= 0;
                end
              end
            1:begin
                 if(en && S_AXIS_tready) begin
                    if (flag) begin
                        S_AXIS_tvalid <= 1'b1;
                        // if (first) begin
                        //     if (cnt_r == 11'd515) begin
                        //         S_AXIS_tlast <= 1'b1;
                        //         first <= 1'b0;
                        //         state <= 2;
                        //     end
                        //     else begin
                        //         S_AXIS_tlast <= 1'b0;
                        //         state <= 1;
                        //      end
                        // end
                        // else begin
                            if(cnt_r == 11'd511) begin
                            S_AXIS_tlast <= 1'b1;
                            state <= 2;
                            end
                            else begin 
                            S_AXIS_tlast <= 1'b0;
                            state <= 1;
                            end
                        // end
                    end
                    else begin
                        S_AXIS_tvalid <= 1'b0;
                        S_AXIS_tlast <= 1'b0;
                        state <= 1;
                    end
                 end
                 else begin               
                    state <= 1;
                 end
              end       
            2:begin
                 if(!S_AXIS_tready) begin
                    S_AXIS_tvalid <= S_AXIS_tvalid;
                    S_AXIS_tlast <= S_AXIS_tlast;
                    state <= 2;
                 end
                 else begin
                    S_AXIS_tvalid <= 1'b0;
                    S_AXIS_tlast <= 1'b0;
                    state <= 0;
                 end
              end
           default: state <=0;
           endcase
       end              
   end  

   





endmodule

 异步读写实现

以axis data fifo in为例

写入速度小于读入速度

使能读写数据计数

添加一个判断模块fifoin_full_0,每当fifo中写入512个数据时,就输出一个中断信号full到ps端,ps端进入中断处理函数,使能Rxgo信号,当Rxgo为1且Rxready为1接收就绪时,执行一次DMA传输

由于DMA读写速度为120MHz,而数据的采样速率为31.25Khz,且fifo写入速度为40Mhz,所以当DMA读取512个数据时,fifo里面数据甚至还没写入下一个数据,且fifo深度设为1024,一定不会产生溢出。

以axis data fifo out为例

写入速度大于读入速度

添加一个判断模块fifoout_empty_0,每当fifo为空,就输出一个中断信号empty,ps端进入中断处理函Txgo信号,当Txgo=1且Txready=1发送就绪时,执行一次DMA传输,传输512个32位数据到FIFO out。

由于DMA读写速度为120MHz,而数据的输出速率位31.25kHz,且fifo输出速度为8Mhz,所以一定要等到fifo慢慢读空之后再往里面写入512个数据,由于DMA写入速度很快,所以不会出现输出缺失数据的现象。

数据输出

输出数据照样以31.25kHz输出,因为FIFOout为8MHz,所以设计一个计数器,每256拉高一次M axis ready信号,读取一个数据输出。

module audio_Left_out
(
    // Inputs
     input           clk_i  //8M hZ
    ,input           rst_i
    ,input           audio_valid_i
    ,input  [ 31:0]  inport_tdata_i

    // Outputs
    
    ,output          i2s_sck_o
    ,output          i2s_sdata_o
    ,output          i2s_ws_o
    //ready
    ,output          M_AXIS_TREADY
    
    //debug
    //,output[4:0]     bit_count_q
    //,output[4:0]     cnt

);


//-----------------------------------------------------------------
// Registers
//-----------------------------------------------------------------
//reg         sck_q;
reg         sdata_q;
reg         ws_q;
//reg [4:0]   bit_count_q;
reg [5:0]   cnt;

//reg bit_clock_w;

//-----------------------------------------------------------------
// Buffer
//-----------------------------------------------------------------
reg [31:0] buffer_q;

reg [8:0]   cnt1;
//计数器
always @ (posedge rst_i or posedge clk_i)
if (rst_i)
    begin
        cnt1 <= 9'd0;
    end
else
    begin
        if (cnt1 >= 255) begin
            cnt1 <= 0;
        end
        else begin
            cnt1 <= cnt1 + 1;
        end
        
    end

//M_AXIS_TREADY 31.25kHz
assign M_AXIS_TREADY = (cnt1 >= 255) ? 1'b1 : 1'b0;



always @ (posedge rst_i or posedge clk_i or posedge audio_valid_i)
if (rst_i || audio_valid_i)
    begin
        cnt <= 6'd63;
        buffer_q <= inport_tdata_i;
    end
else
    begin
        cnt <= cnt - 6'd1;
        buffer_q <= buffer_q;
    end


always @ (posedge rst_i or negedge clk_i )
if(rst_i)
    begin
        sdata_q <= 1'b0;
        ws_q    <= 1'b0;
    end
else if( cnt > 31)
    begin
       sdata_q  <= buffer_q[cnt-32];
       ws_q     <= 1'b1; 
    end
else 
    begin
       sdata_q  <= buffer_q[cnt];
       ws_q     <= 1'b0; 
    end    



assign i2s_sck_o   = clk_i;
assign i2s_sdata_o = sdata_q;
assign i2s_ws_o    = ws_q;


endmodule

坑:

1.status = 15

2.fifo last问题

3.fifo 内部有四个缓存

现象分析:

FIFO在写入数据时,前几个数据虽然已经写入fifo,但是wr_data_count并不会增加,如图中所示,在输入的数据已经到511时,wr_data_count只有507。同样对于rd_data_count,在第二张图可以看到,前几个写入的数据直接被放在了输出的总线上,所以rd_data_count会短暂的变成1后又重新回到0,一直输入四个数据后,rd_data_count才开始正常增加。

原因猜测:

好像没什么影响,就是个现象

可能1

FIFO First-word-Fall-Through模式的仿真 - 知乎 (zhihu.com)

FWFT与Standard模式的区别,在axis stream fifo默认是fwft模式,会先将得到的数据放在输出总线上,以减少延迟。

可能2

【FPGA基础篇】Xilinx FIFO详细解析_mrVillain的博客-优快云博客

 Read Data Count 和 Write Data Count 都是采用的保守估计的方式。什么是保守估计,意思就是假设FIFO中至少有8个数据可读,但FIFO只跟你说我这里只有8个数据可读,那么你就会认为只能读8个数据,实际上可能能读9个或者10个,这么做的原因时为了保证FIFO读操作的安全性,后面我们分析异步FIFO的时候就能深刻理解这么做的精妙之处,这就是保守估计。类似的,当FIFO中还可以写至少8个数据时,FIFO会说只能写8个数据了,这样子你也只会写8个数据,实际上FIFO可以写9-10个左右。
 

 

4.中断冲突问题,不能在一个中断没有结束的时候产生另一个中断

现象分析:

在一开始,我在vitis中初始化中断后,将DMA传输函数放在了fifo的空/满中断处理函数中,

Status = XAxiDma_SimpleTransfer(&AxiDma,(u32)(TxBufferPtr),
						(u32)(MAX_PKT_LEN_TX), XAXIDMA_DMA_TO_DEVICE);

 但是DMA传输函数会产生中断信号来告诉ps端传输结果,这样就导致了在其中一个中断运行过程中触发了另一个中断。(理论上是不是可以设置中断优先级来处理?没试验过

解决方案:

在fifo的空/满中断处理函数就改变一个全局参数,之后在主函数中根据该参数判断是否运行传输函数,这样子fifo的空/满中断结束很快,不会影响到DMA传输函数中断。

7. DMA输入输出之间的最大延迟和最小延迟

现象分析:

DMA的两次传输函数应用的间隔有最大延迟时间和最小延迟时间的要求,必须在某个时间区间内。

产生延迟的主要原因是ps端的处理函数,比如滤波过程产生的延迟。

若延迟过大,则会导致FIFO in两次读取的间隔过大,FIFO in溢出,FIFO out两次写入的间隔过大,FIFO out长时间等待数据。

若延迟过小,则会导致FIFO in两次读取的间隔过小,DMA不能读取到完整的一帧数据,导致数据处理紊乱,FIFO out两次写入间隔过短,数据来不及传输出去,导致溢出。

解决方案:

采用了根据wr/rd data count计数的中断来触发DMA传输函数,可以有效避免延迟过小的问题。比如FIFO in 写入512个数据就DMA读取一次,FIFO out为空就传输一次。

延迟过大的问题无法直接解决,主要根据计算FIFO写入和读取一帧数据的时间差来计算最大延迟,并保证PS的处理函数所需时间小于最大延迟。或者采用乒乓缓存的方式。

8. 810 1800 输出中断没响应

现象分析:

由于输出中断是在FIFO out的rd data count为0时触发,所以在一开始产生了输出中断,但是此时DDR中仍未传入有效数据,所以输出的也是随机乱码。

解决方案:

采用了方案2

1、可以让FIFO out的rd data count为1时触发

2、添加了一个GPIO模块作为FIFO out的使能模块,并在第一次传输完成后将gpio拉高。

9.输出dma的fifo读取很快 810 1900-1952

10.DcacheFlushRange和DcacheInvalidateRange

用DMA进行数据传输时,需要解决DCache(Data Cache)和DDR Memory之间的数据一致性问题。

PS通过DMA向PL写数据

1)调用:Xil_DCacheFlushRange(INTPTR adr, u32 len)

2)DMA写PL:XAXIDMA_DMA_TO_DEVICE

PS通过DMA从PL读数据

1)DMA读PL:XAXIDMA_DEVICE_TO_DMA

2)调用:Xil_DCacheInvalidateRange(INTPTR adr, u32 len)

解析:

Xil_DCacheFlushRange(INTPTR adr, u32 len);的作用时将Cache中的数据刷新到DDR中去,也就是说当我们对得到的数据进行处理,并想将这些数据输出到PL端时,我们处理后的数据都是先放在Cache当中,需要先刷新到DDR中,再由DMA将数据从DDR中传输到PL端。

Xil_DCacheInvalidateRange(INTPTR adr, u32 len);的作用时将Cache中数据无效化,并将DDR中的数据刷新到Cache中,以得到最新的数据。当我们从PL端读取数据后,新的数据若要显示在Cache中就要调用Xil_DCacheInvalidateRange(INTPTR adr, u32 len)。

11. 811 jtag速度过快

现象分析:

ERROR: [Labtools 27-3412] Mismatch between the design programmed into the device 'xc7a100t' (JTAG device index = '0'
 and the probes file(s) 'E:/0_WORK/FQPB_Prj/par/FQPB_Prj_0126.runs/impl_1/IDCU_FPGA_TOP.ltx'.
 The hw_probe 'u0_sys_clk_gen/async_rst' in the probes file has port index '1'. This port location for the ILA core at location (uuid_44686827098D55E5A4639609BA1F5BBF), does not support a data probe.
.
Resolution: 
1) Ensure that the clock signal connected to the debug core and/or debug hub is clean and free-running.
2) Ensure that the clock connected to the debug core and/or debug hub meets all timing constraints.
3) Ensure that the JTAG clock frequency is 2.5x times slower than the frequency of the clock connected to your debug hub.

jtag的速度必须小于等于ILA最小时钟频率的1/2.5,因为本次项目中最小ILA的频率为8M,所以jtag的传输速度必须小于3.2M。

解决方案:

修改Jtag速度:xilinx usb下载器 速度高速极限设置 JTAG-SMT2 JTAG-HS2 JTAG-HS3和Platform Cable USB DLC9 DLC10速度测试-优快云博客

12.wr_data_count和rd_data_count不一样

【FPGA基础篇】Xilinx FIFO详细解析_mrVillain的博客-优快云博客

由于wr_data_cnt同步于wr_clk,wr_en也同步于wr_clk,所以wr_en使能后,每写入一个data,都可以在下个时钟沿反馈到wr_data_cnt上,写入一个数据,wr_data_cnt就加一;相应的,读出一个数据,wr_data_cnt也需要减一,才能正确反应FIFO中的数据量。但对于rd_en,由于rd_en是同步于rd_clk的,所以rd_en的使能需要通过一系列的时钟同步到wr_clk中才能反映到wr_data_cnt上,通常采用的方式是格雷码和打拍子的方式,如下图所示,可以看到需要将Read Counter通过格雷码转换(格雷码转换可以消除很多中间不定态,大大增强了稳定性),再进行跨时钟域传输,这里跨时钟域传输一般可以选择打拍子的方式,由于已经转化成了格雷码,此时多bit传输就可以变成单bit传输,因为相邻state只有其中一个bit发生了变化,单bit跨时钟域传输就可以采用打拍子的方式进行时钟域转换,这样子就造成了的结果就是传送到wr_clk时钟域时的Read Counter的数值是过去的Read Counter,比最新的Read Counter要小,因此Write Counter-Read Counter偏大,从而认为FIFO中有更多的数据存储(实际上并没有那么多数据),因此wr_data_cnt输出的信息就是FIFO中最多有多少个数据,至少还能写入多少个数据,而我们能知道的就是我们只要写入的数据不超过它的最小值,就能保证FIFO永远不会写满,这样子安全性就提高了。
 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值