FIFO的FPGA实现

FIFO队列是一种数据缓冲器,用于数据的缓存。他是一种先入先出的存储器,即最先写入的数据,最先读。FIFO的参数有数据深度和数据宽度。数据宽度是指存储数据的宽度。深度是指存储器可以存储多少个数据。

FIFO队列有两个标志位。一个满和一个空标志位。分别表示FIFO是数据写满,还是数据读空。在数据写满状态下,数据写入是不允许的,因此在这个状态下,写入的数据无效。而数据读空状态下,数据读取是不允许的,因此在这个状态下,读取的数据无效。

FIFO队列有两个位置指示指针。一个是写指针,指向队列的第一个存储单元。一个读指针,指向队列的最后一个存储单元。当有写命令的时候,数据写入写指针指向的存储单元,然后指针加一。当有读命令的时候,读指针加一,在读出读指针指向的存储单元的数据。这里读命令,指针要加一,是定义读数据,是读出读指针的下一个存储单元的数据。

当写指针和读指针的指向存储单元一样时,这时候根据之前是读命令还是写命令来判断队列是空,还是满。在读命令,两个指针值一样时候,则队列空。在写命令,两个指针值一样,则队列满。

以后就开始写代码实现上诉FIFO队列,并进行仿真。

以下,是实现数据宽度为8.深度为2^4的深度的FIFO。。读/写时钟是同一个。

module fifo_cus

  #(

           parameter N = 8,      //数据宽度

          parameter M = 4       //fifo的地址宽度

   )      

 //对队列的参数设置。建议这样写,便于以后代码的移植。

     //如果以后要实现数据宽度为16,深度为2^8的FIFO。只需改N =16 M=8即可

   (

                     input                          clk,             //输入时钟

                     input                          rst_n,           //输入复位信号

                     input                          wr,             //输入写使能

                     input[N-1:0]                  w_data,         //输入输入

                     input                          rd,             //输入读使能

                                    

                     output                         empty,          //输出fifo空标志

                     output                         full,            //输出fifo满标志

                     output[N-1:0]                  r_data          //输出读取的数据  

    );

  

    //寄存器组,用来充当FIFO队列

         reg [N-1:0] array_reg [2**M - 1:0];

   

    //定义写指针,指示当前写的位置,下一个状态写的位置,写位置的下一个位置

         reg [M-1:0]  w_ptr_reg, w_ptr_next,w_ptr_succ;

        

    //定义读指针,指示当前读的位置,下一个状态读的位置,读位置的下一个位置

         reg [M-1:0] r_ptr_reg, r_ptr_next,r_ptr_succ;

        

         //定义FIFO满和空的信号

         reg full_reg,  full_next;

         reg empty_reg, empty_next;

    wire wr_en;

 

   //数据的写入,在数据的上升沿的时候,有写使能信号,将数据写入。而

 always@( posedge clk ) begin

       if( wr_en )

               array_reg[w_ptr_reg] <= w_data;

           else

               array_reg[w_ptr_reg] <= array_reg[w_ptr_reg];

    end 

  

  // 数据的读取。数据读取是一直在读取的,不过读取的是之前的值。

    assign r_data = array_reg[r_ptr_reg];

    assign wr_en = wr & ~full_reg;

/*状态跳转

  在复位信号有效,读/写指针都指向0地址。此时队列状态为空。

  在复位不有效,且在时钟的上升沿,读/写指针的值,队列空,满状态的值又下一状态决定。否则保持  */

         always@( posedge clk ) begin

           if( !rst_n )

              begin

                        w_ptr_reg <= 0;

                             r_ptr_reg <= 0;

                             full_reg <= 1'b0;

                             empty_reg <= 1'b1;

                     end

           else

              begin

                        w_ptr_reg <= w_ptr_next;

                             r_ptr_reg <= r_ptr_next;

                             full_reg <= full_next;

                             empty_reg <= empty_next;

                     end

         end

//下一个状态的判定

   always@ * begin

      w_ptr_next = w_ptr_reg;

          r_ptr_next = r_ptr_reg;

          full_next  = full_reg;

          empty_next = empty;

          w_ptr_succ = w_ptr_reg + 1'b1;

          r_ptr_succ = r_ptr_reg + 1'b1;

          case( {wr,rd} )

       /*读命令:在读命令下,如果队列不为空,讲当前读指针的下一个指针赋值给读指针的下一个状态,同时将队列的满标志置0。

         然后判断读指针的下一个指针是否和写指针的值一样。一样的话,说明,队列为空。否则不为空。     */

           2'b01:             

              begin

                        if( ~empty_reg )         

                                begin

                                        r_ptr_next = r_ptr_succ;

                                        full_next = 0;

                                        if( r_ptr_succ == w_ptr_reg )

                                            empty_next = 1'b1;

                                        else

                                            empty_next = 1'b0;

                                     end

                     end

/*写命令:在写命令下,如果队列不为满,将当前写指针的下一个指针赋值给读指针的下一个状态,同时将队列的空标志置0。

         然后判断写指针的下一个指针是否和读指针的值一样。一样的话,说明,队列为满。否则不为满。

      */

           2'b10:

              begin

                        if( ~full_reg )

                                 begin

                                         w_ptr_next = w_ptr_succ;

                                               empty_next= 0;

                                         if( w_ptr_succ == r_ptr_reg )

                                                   full_next = 1'b1;

                                         else

                                                   full_next = 1'b0;

                                      end

                     end

/*读写命令:在读写命令下, 直接改变对应指针的下一个状态值。

      */

           2'b11:

              begin

                                      w_ptr_next = w_ptr_succ;

                                      r_ptr_next = r_ptr_succ;

           endcase

   end

// 满/空输出信号的赋值。

   assign full = full_reg;

   assign empty = empty_reg;

endmodule

 

  好了,终于搞定FIFO的代码了。下面来仿真看看结果。

以下分析仿真的结果:

写数据:

从下图仿真,可看出。在最开始的时候,队列是空的状态。读指针和写指针都是0。在写使能情况下,在每个时钟的上升沿(蓝色线),数据写入队列array_reg中。同时,写指针加一。而读指针是不变的。

从下图发现,在队列满状态下,即使写使能,FIFO也不接受写数据。依旧保持原来的值。

读数据

从下图中看出,最开始,数据读出是有值的。为初始化的读指针指向的存储单元的值。这里为4。

当有读命令时候,在时钟的上升沿(蓝色线),读指针加一。读取的数据随之改变。

在数据读完后,即队列为空状态下。此时对数据的读取是无效的。从图中可看出,读完后,读指针为0.回到存储器的第一个地址。而此时读出的值是无效的。


读写命令:

在同时读同时写的时候。从下图,可看出,结果有问题了。在队列为空的状态下,此时读取的值,应为此时写的数据才对了。但是从图中,可看出,读取的值不是当前写的数据的值。而是之前存储在FIFO中的值。这样的话,读取的值就不是正确的值了。

从上图仿真结果,可知。程序在读写命令时候,编写得不正确。造成结果不对。

返回程序分析。程序不对的地方在于读写命令的时候,处理 得不正确。在空的状态下,数据写入是先写入,然后写指针加一。而读取命令是,指针先加一,然后再读取。而读和写指针的值一样的。这样造成,读取的FIFO的存储单元的值,为写的存储单元的下一个存储单元的值。因此造成读取不正确。

改正的程序如下:

2'b11:

              begin

                        if( ~full_reg && ~empty_reg )

                               begin

                                      w_ptr_next = w_ptr_succ;

                                      r_ptr_next = r_ptr_succ;

                               end

                             else if( full_reg )          //在满的状态,不允许写

                               begin

                                 r_ptr_next = r_ptr_succ;

                                      full_next = 0;

                               end

                             else if( empty_reg )         //在空的状态,不允许写

               begin                      

                                 w_ptr_next = w_ptr_succ;

                                      empty_next = 0;

                               end

                     end

只需要规定以下:在满的状态,不允许写,在空的状态下,不允许读。这样就可以了。

然后再进行仿真:

这里只看读写命令的图。从下图中,可看出,此时读取的数据,为刚刚写的数据。这样就正确了。

这样,就完成了FPGA的FIFO了。通过这样一个简单的练习,可看出,仿真,是很重要的,能发现程序中的问题。

以上仿真没有覆盖到所有情况,有兴趣的,可以自己仿真看看仿真图,验证程序写得是否正确。


  • 14
    点赞
  • 76
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FPGA 同步 FIFO 是一种用于在 FPGA 设备中实现数据缓冲和数据转移的组件。它由一个读取指针和一个写入指针组成,可以实现读写操作的同步和互斥。 使用 FPGA 同步 FIFO 的一个常见场景是在不同频率的数据传输之间进行数据缓冲和同步。当输入以不同频率产生数据时,为了保证数据的可靠传输和处理,可以使用同步 FIFO 来缓冲输入数据,并在输出端以相同或不同的频率读取数据。 FPGA 同步 FIFO实现可以采用 FPGA 内部的存储单元,如 Block RAM 或 Distributed RAM。写入操作将数据写入 FIFO 的写入指针所指向的位置,并将写入指针前移。读取操作将数据从 FIFO 的读取指针所指向的位置读取出来,并将读取指针前移。读写指针的移动是同步的,以保证数据的正确性和完整性。 FPGA 同步 FIFO 的大小通常取决于数据传输的需求和 FPGA 设备的资源限制。可以根据数据产生和处理的速度来确定 FIFO 的大小,并且需要根据需要调整读写指针的顺序和移动策略,以满足数据的传输需求。 尽管 FPGA 同步 FIFO 在数据传输中起到了重要的作用,但同时也会增加设计的复杂性和资源消耗。在使用 FPGA 同步 FIFO 时,需要注意处理数据的同步和互斥问题,以及避免出现数据丢失、溢出等异常情况。 总之,FPGA 同步 FIFO 是一种用于实现数据缓冲和转移的组件,在不同频率的数据传输中发挥了关键作用。它可以通过读写指针的同步移动来保证数据的可靠性和完整性,并且可根据需求和硬件资源进行灵活调整。但同时也需要注意处理同步和互斥问题,以确保数据的正确传输。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值