• 同步FIFO的verilog实现(2)——高位扩展法


    一、前言

            在之前的文章中,我们介绍了同步FIFO的verilog的一种实现方法:计数法。其核心在于:在同步FIFO中,我们可以很容易的使用计数来判断FIFO中还剩下多少可读的数据,从而可以判断空、满。

            关于计数法实现同步FIFO的详细内容,请参考:同步FIFO的verilog实现(1)——计数法

    二、高位扩展法原理

            我们知道对于FIFO的设计来说,其核心在于设计读写指针,并且生成可靠的空、满信号。

            当读/写地址指针在复位操作期间被置为零时,或者当读指针在从FIFO中读取了最后一个字之后追上了写指针,此时读指针和写指针相等代表着FIFO为空状态。而当写指针再次追上读指针时,此时读指针和写指针相等代表着FIFO为写满。也就是说当读写指针相等时,FIFO要么为空,要么为满。
       因此我们可以将地址位扩展一位,用最高位来判断空满,其余低位还是正常用于读写地址索引。当写指针递增超过FIFO的最大地址时,写指针的MSB位将置为1,同时将其余低位设置回零。读指针也是如此。如果读指针和写指针的MSB不同,则意味着写指针比读指针多绕了一次,表示FIFO写满。如果两个指针的MSB相同,则表示两个指针的回绕次数相同,表示FIFO读空。如下图所示:

            当最高位不同,且其他位相同,则表示读指针或者写指针多跑了一圈,这显然不可能发生,情况只能是写指针多跑了一圈,与就意味着FIFO被写满了。
            当最高位相同,且其他位相同,则表示读指针追到了写指针或者写指针追到了读指针,而显然不会让写指针追读指针(这种情况只能是写指针超过读指针一圈),所以可能出现的情况只能是读指针追到了写指针,也就意味着FIFO被读空了。

    三、同步FIFO的verilog实现

            理解了原理,我们就能很快设计出相应的verilog代码:

    1. //------------------------<高位扩展法设计同步FIFO>----------------------------
    2. module sync_fifo1#(
    3. //-----------------------------<参数定义>---------------------------------
    4. parameter FIFO_WIDTH = 16, //FIFO宽度
    5. parameter FIFO_DEPTH = 16 //FIFO深度
    6. )(
    7. //-----------------------------<接口定义>---------------------------------
    8. input clk, //时钟信号
    9. input rst, //复位信号
    10. input [FIFO_WIDTH-1:0] din, //FIFO输入数据(写数据)
    11. input rd_en, //读使能信号
    12. input wr_en, //写使能信号
    13. output reg [FIFO_WIDTH-1:0] dout, //FIFO输出数据(读数据)
    14. output empty, //FIFO空标志
    15. output full //FIFO满标志
    16. );
    17. //-----------------------------<reg定义>---------------------------------
    18. reg [FIFO_WIDTH-1:0] fifo_buffer[FIFO_DEPTH-1:0]; //用二维数组实现RAM
    19. reg [$clog2(FIFO_DEPTH):0] wr_addr; //写地址(写指针),位宽要多出一位
    20. reg [$clog2(FIFO_DEPTH):0] rd_addr; //读地址(读指针),位宽要多出一位
    21. //-----------------------------<wire定义>---------------------------------
    22. wire [$clog2(FIFO_DEPTH) - 1 : 0] wr_addr_true; //真实写地址指针
    23. wire [$clog2(FIFO_DEPTH) - 1 : 0] rd_addr_true; //真实读地址指针
    24. wire wr_addr_msb; //写地址指针地址最高位
    25. wire rd_addr_msb; //读地址指针地址最高位
    26. assign {wr_addr_msb,wr_addr_true} = wr_addr; //将最高位与其他位拼接
    27. assign {rd_addr_msb,rd_addr_true} = rd_addr; //将最高位与其他位拼接
    28. //-----------------------------<读操作>-----------------------------------
    29. always@(posedge clk or posedge rst)begin
    30. if(rst)
    31. rd_addr <= 0;
    32. else if(rd_en && !empty)begin //读使能有效且FIFO非空
    33. rd_addr <= rd_addr + 1'd1; //读指针递增
    34. dout <= fifo_buffer[rd_addr_true]; //fifo读出数据
    35. end
    36. else begin
    37. rd_addr <= rd_addr;
    38. dout <= dout;
    39. end
    40. end
    41. //-----------------------------<写操作>-----------------------------------
    42. always@(posedge clk or posedge rst)begin
    43. if(rst)
    44. wr_addr <= 0;
    45. else if(wr_en && !full)begin //写使能有效且FIFO非满
    46. wr_addr <= wr_addr + 1'd1; //读指针递增
    47. fifo_buffer[wr_addr_true] <= din; //数据写入fifo
    48. end
    49. else begin
    50. wr_addr <= wr_addr;
    51. end
    52. end
    53. //-----------------------------<通过地址扩展位更新空/满信号>-----------------------------------
    54. assign empty = ( wr_addr == rd_addr ) ? 1'b1 : 1'b0; //当所有位相等时,读指针追到了写指针,FIFO被读空
    55. assign full = ((wr_addr_msb != rd_addr_msb ) && ( wr_addr_true == rd_addr_true )) ? 1'b1 : 1'b0; //当最高位不同但是其他位相等时,写指针超过读指针一圈,FIFO被写满
    56. endmodule

    四、测试代码

            给出如下的测试代码:

    1. `timescale 1ns/1ns
    2. //-----------------------------<高位扩展法同步FIFO测试>---------------------------------
    3. module tb_sync_fifo1();
    4. parameter WIDTH = 8;
    5. parameter DEPTH = 8;
    6. reg clk ;
    7. reg rst ;
    8. reg [WIDTH-1:0] din ;
    9. reg wr_en ;
    10. reg rd_en ;
    11. wire [WIDTH-1:0] dout ;
    12. wire full ;
    13. wire empty ;
    14. //-----------------------------<测试模块例化>---------------------------------
    15. sync_fifo1 #(
    16. .FIFO_WIDTH (WIDTH), //FIFO宽度
    17. .FIFO_DEPTH (DEPTH) //FIFO深度
    18. )
    19. sync_fifo_u1(
    20. .clk (clk ),
    21. .rst (rst ),
    22. .din (din ),
    23. .rd_en (rd_en ),
    24. .wr_en (wr_en ),
    25. .dout (dout ),
    26. .empty (empty ),
    27. .full (full )
    28. );
    29. //-----------------------------<模块测试>---------------------------------
    30. initial begin
    31. clk = 1'b0; //初始时钟为0
    32. rst <= 1'b0; //初始复位
    33. din <= 'd0;
    34. wr_en <= 1'b0;
    35. rd_en <= 1'b0;
    36. #10
    37. rst <= 1'b1;
    38. #10
    39. rst <= 1'b0;
    40. repeat(10)
    41. #10 begin
    42. wr_en <= 1'b1;
    43. rd_en <= 1'b0;
    44. din <= $random; //生成8位的随机数
    45. end
    46. repeat(10)
    47. #10 begin
    48. wr_en <= 1'b0;
    49. rd_en <= 1'b1;
    50. end
    51. $finish;
    52. end
    53. //------------------------------<设置时钟>----------------------------------------
    54. always #5 clk = ~clk;
    55. endmodule

    五、结果

     

  • 相关阅读:
    C++智能指针shared_ptr用法
    【第八章 死锁、lock锁、synchronized和lock的异同、线程通信、sleep()和wait()的异同】
    charCodeAt() 方法了解和用法&unicode编码表
    基于Springboot实现校园新闻网站管理系统演示【项目源码+论文说明】
    如何制定关于AI伦理的国际标准?
    Java 泛型(类,接口,方法)的概述
    NBlog个人博客部署维护过程记录 -- 后端springboot + 前端vue
    unicode汉字编码转化
    OpenInventor/Coin3D 学习指南
    一个在使用react时突然遇到的问题
  • 原文地址:https://blog.csdn.net/apple_53311083/article/details/132740196