vivado FPGA操作乒乓FIFO

本文详细阐述了如何通过Vivado中的标准FIFO IP核,精确控制wr_en和rd_en使能信号,以及data_in和data_out数据通道,解决写入与读取时序问题,确保在乒乓传输中无数据丢失。关键在于预判几乎满和空信号,以同步切换通道并避免数据交错。

        本文调用vivado中ip核的两个standard fifo,通过调整使能信号(wr_en,rd_en)的时序和data_in(与in_1,in_2),data_out(与out_1,out_2)的接通,实现乒乓fifo传输数据流的无缝输入输出,不丢失任何一位数据。

        本项目的难点:

        1、写入:必须在fifo1停止写入数据之后,下一个时钟上升沿马上把数据写入fifo2,但是由于standard fifo的运行模式,若我们把使能信号简单地分配给统一的状态机,会出现读/写不全、重复读/写等问题。

        在almost_full_1信号拉高之后,下一个时钟上升沿fifo1将写入最后一位数据,但state仍旧未切换,等state切换再拉高wr_en_2会使得至少一位数据发生丢失因此我们需要在almost_full_1信号拉高之后,将wr_en_2提前拉高,并将wr_en_1拉低,保证对data_in数据的完整采集,且不会出现一个数据写入到两个fifo的现象。如下图所示:fifo_1最后一位写入的是“0”,此后将data_in新输入的数据“1”直接写入fifo_2。

        特别声明:wr_ack信号拉高前一个周期的数据能够写入,而拉低前的最后一位数据是无法写入fifo(因为该信号判断流程为:尝试写入——写入成功/不成功——拉高/拉低,属于“滞后”标记位)

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAampfX2JveQ==,size_20,color_FFFFFF,t_70,g_se,x_16       

       既然读使能信号wr_en需要提前打开,那么data_in所进入的通道(in_1\in_2)也应该随着使能信号wr_en同时切换(注意:是同时切换,wr_en一变化,通道同时就切换,是同时操作,并不是滞后一个时期),如下图所示:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAampfX2JveQ==,size_15,color_FFFFFF,t_70,g_se,x_16

 

2、读出:

        读出操作相对而言就比较简单,在对写入操作进行上述处理后,rd_en只需在n_state切换的时刻将rd_en进行切换,即可正常读出数据。

        data_out根据rd_en的切换来切换即可,时序不需要多做要求,自然顺着读就行,不会丢数据,仅会滞后几个周期。

        特别声明:只要数据在valid信号拉高的“框内"即可成功读出(因为该信号判断流程为:valid拉高/拉低——能够/不能成功读出——成功写入/没写成功,属于“提前”标记位)

3、ip核的配置

        看图:        

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAampfX2JveQ==,size_20,color_FFFFFF,t_70,g_se,x_16watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAampfX2JveQ==,size_20,color_FFFFFF,t_70,g_se,x_16watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAampfX2JveQ==,size_20,color_FFFFFF,t_70,g_se,x_16 

源代码:

`timescale 1ns / 1ps
module fifo_pp_myself(
    input clk,
    input _rst,
    input wr_en,
    input       [9:0]data_in,
    output  reg [9:0]data_out
    );
  reg [3:0]c_state;
  reg [3:0]n_state;
  reg  wr_en_1;
  reg  rd_en_1;
  reg  wr_en_2;
  reg  rd_en_2;
  reg  [9:0]in_1;
  reg  [9:0]in_2;
  wire [9:0]out_1;
  wire [9:0]out_2;
  wire full_1;
  wire full_2;
  wire empty_1;
  wire empty_2;
  wire [9:0]data_out_r;
  wire wr_ack_1,wr_ack_2;
  wire valid_1,valid_2;
  wire almost_full_1,almost_full_2;
  wire almost_empty_1,almost_empty_2;
   parameter
        idle    = 4'b0000,
        start   = 4'b0010,
        w1_r2   = 4'b1000,
        r1_w2   = 4'b0100;
  /********例话两个标准FIFO*********/              
        fifo_generator_0 fifo_1(
        .clk(clk),
        .srst(~_rst),
        .din(in_1),
        .full(full_1),
        .almost_full(almost_full_1),
        .wr_en(wr_en_1),
        .empty(empty_1),
        .almost_empty(almost_empty_1),
        .dout(out_1),
        .rd_en(rd_en_1),
        .wr_ack(wr_ack_1),
        .valid(valid_1)
 );
fifo_generator_0 fifo_2(
    .clk(clk),
    .srst(~_rst),
    .din(in_2),
    .full(full_2),
    .almost_full(almost_full_2),
    .wr_en(wr_en_2),
    .empty(empty_2),
    .almost_empty(almost_empty_2),
    .dout(out_2),
    .rd_en(rd_en_2),
     .wr_ack(wr_ack_2),
     .valid(valid_2)
  );
   /*******状态连续*********/
   always@(posedge clk or negedge _rst)
   if(!_rst)
        c_state <= idle;
   else
        c_state <= n_state;
  /********状态机**********/
  always@(posedge clk or negedge _rst)
    case(c_state)
        idle:
            if(wr_en)
                n_state <= start;
            else
                n_state <= idle;
        start:
            if(full_1)
                n_state <= r1_w2;
            else
                n_state <= start;
        r1_w2:
            if(empty_1 | full_2)
                n_state <= w1_r2;
            else
                n_state <= r1_w2;
        w1_r2:
            if(empty_2 | full_1)
                n_state <= r1_w2;
            else
                n_state <= w1_r2;
        default:n_state <= idle;
        endcase
   /******使能信号*******/
    always@(posedge clk or negedge _rst)
    begin
        case(n_state)
            idle:
                    begin
                        wr_en_1 <= 1'b0;
                        rd_en_1 <= 1'b0;
                        wr_en_2 <= 1'b0;              
                        rd_en_2 <= 1'b0;
                    end
            start:
                    begin
                        if(almost_full_1)//提前打开下一个写入通道的开关,防止丢交接时候的两个数据
                            begin
                                 wr_en_1 <= 1'b0;
                                 wr_en_2 <= 1'b1;
                            end 
                        else
                            begin
                                 wr_en_1 <= 1'b1;
                                 rd_en_1 <= 1'b0;
                                 wr_en_2 <= 1'b0;              
                                 rd_en_2 <= 1'b0;
                            end 
                   end
            r1_w2:
                   begin
                           if(almost_full_2)
                              begin
                                  wr_en_1 <= 1'b1;
                                  wr_en_2 <= 1'b0;
                              end
                           
                           else
                              begin
                                  wr_en_1 <= 1'b0;
                                  rd_en_1 <= 1'b1;
                                  wr_en_2 <= 1'b1;              
                                  rd_en_2 <= 1'b0;
                              end
                   end
           w1_r2:
                 begin
                           if(almost_full_1)
                              begin
                                  wr_en_1 <= 1'b0;
                                  wr_en_2 <= 1'b1;
                              end
                           
                           else
                              begin
                                  wr_en_1 <= 1'b1;
                                  rd_en_1 <= 1'b0;
                                  wr_en_2 <= 1'b0;              
                                  rd_en_2 <= 1'b1;
                              end
                   end
     endcase
     end
    
   
  /***写数据的交互****/
  always@(posedge clk or negedge _rst)
  case(n_state)
    start: in_1 <= data_in; 
  endcase
  always@(posedge clk or negedge _rst)
  case(c_state)
    start:begin
            if(almost_full_1)begin
                in_2 <= data_in;
                in_1 <= in_1; end
            else
                in_1 <= data_in;
          end
    r1_w2:begin//4
                if(almost_full_2)begin                
                        in_1 <= data_in;
                        in_2 <= in_2;end
                else
                    begin 
                        in_2 <= data_in; 
                        //data_out <= out_1;
                     end
          end
    w1_r2://8
            begin
                if(almost_full_1)begin
                    in_2 <= data_in;
                    in_1 <= in_1;end 
                else
                    begin 
                        in_1 <= data_in; 
                       // data_out <= out_2;
                     end
          end
    endcase
  /*******data读出只需要跟读使能匹配就行*************/
   always@(posedge clk or negedge _rst)
   if(!_rst)
        data_out <= 0;
   else if(rd_en_1)
        data_out <= out_1;
   else 
        data_out <= out_2;             
endmodule

tb文件:

`timescale 1ns / 1ps
module fifo_pp_myself_tb();
    reg     clk;         
    reg     _rst;        
    reg     wr_en;       
    reg     [9:0]data_in;
    wire    [9:0]data_out;
fifo_pp_myself fifo_pp_myself(
     .clk       (clk     ),
     ._rst      (_rst    ),
     .wr_en     (wr_en   ),
     .data_in   (data_in ),
     .data_out  (data_out)
    );
    initial clk = 1;
    always#10 clk = ~clk;
    always#20 data_in = data_in + 1'b1;
    
    initial begin
        _rst = 1'b1;
        #20;
        _rst = 1'b0;
//        wr_en = 1'b1;
        #200;
        wr_en = 1'b1;//开启读使能之后再拉高复位,这样第一个输入的数据也能读出来
        #20;
        _rst = 1'b1;
        //#500;      
        data_in = 10'b0;
        #200000;
        $stop;
    end
endmodule

仿真图:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAampfX2JveQ==,size_20,color_FFFFFF,t_70,g_se,x_16

 watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAampfX2JveQ==,size_20,color_FFFFFF,t_70,g_se,x_16

         根据细节图,在状态交接处,数据仍是连续不间断,且不会出现重复读写同一数据的情况。

         根据全局图,可以看出每次写入fifo时的最后一位都是0,说明每个周期读取次数也并没有出现问题(多读或者少读,长长的那一位都不会是0,可能是3,5,7)

        结论:乒乓fifo操作基本实现,目前并未发现有bug。

 

### 乒乓 FIFO 的实现原理 乒乓 FIFO 是一种基于“乒乓”架构设计的缓冲机制,其核心思想是利用两个独立的 FIFO 缓冲区交替工作来提高系统的效率和灵活性。具体来说,在乒乓 FIFO 中: - **双缓冲结构**:采用两个独立的 FIFO 缓冲区(通常称为 `FIFO1` 和 `FIFO2`)。每个缓冲区拥有各自的读指针和写指针[^1]。 - **数据写入切换逻辑**:当其中一个 FIFO 缓冲区被写满时,后续的数据会自动切换到另一个 FIFO 缓冲区继续写入。这种切换过程由状态机控制,确保不会发生数据冲突或丢失[^2]。 - **数据读取同步**:在写操作切换的同时,读操作可以从已经完成写入的第一个 FIFO 缓冲区中提取数据。由于采用了先进先出的原则,读指针始终指向当前缓冲区中最旧的数据位置。 - **硬件资源优化**:理论上,乒乓 FIFO 可以通过单口 RAM 来实现,但在 FPGA 平台上更常用的是真双口 RAM,因为后者支持同时进行读写操作而无需额外等待时间[^3]。 ### 乒乓 FIFO 的应用场景 乒乓 FIFO 被广泛应用于需要高效处理连续流式数据的各种领域,尤其是在嵌入式系统、通信设备以及图像处理等领域中有重要地位。以下是几个典型的应用场景: #### 图像采集与传输 在视频帧捕获过程中,摄像头模块可能会持续生成大量像素数据。为了防止因处理器速度不足而导致的画面撕裂现象,可以使用乒乓 FIFO 将一整帧存储在一个缓冲区内后再传递给下一级单元处理;与此同时另一侧则接收新来的下一帧信息直到完全填充满为止再互换角色重复上述动作。 #### 高速串行通讯协议桥接 对于某些高速串行总线标准而言(比如USB3.0,SATA),它们内部往往存在不同速率之间的转换需求,此时就可以借助于此类特殊形式队列来进行临时性的流量调节作用从而达到平稳过渡的效果. ```verilog // Verilog 示例代码片段展示简单的乒乓 FIFO 控制部分 always @(posedge clk or negedge reset_n) begin : proc_state_machine if (~reset_n) begin write_to_fifo1 <= 1'b1; read_from_fifo1 <= 1'b0; end else case (current_state) IDLE: ; WRITE_FIFO1: begin if(fifo1_full && !fifo2_full)begin current_state<=WRITE_FIFO2; write_to_fifo1 <= 1'b0; write_to_fifo2 <= 1'b1; end end READ_FIFO1: ; // 类似定义其他状态转移条件... end ```
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值