在之前的文章中,我们介绍了同步FIFO的verilog的一种实现方法:计数法。其核心在于:在同步FIFO中,我们可以很容易的使用计数来判断FIFO中还剩下多少可读的数据,从而可以判断空、满。
关于计数法实现同步FIFO的详细内容,请参考:同步FIFO的verilog实现(1)——计数法
我们知道对于FIFO的设计来说,其核心在于设计读写指针,并且生成可靠的空、满信号。
当读/写地址指针在复位操作期间被置为零时,或者当读指针在从FIFO中读取了最后一个字之后追上了写指针,此时读指针和写指针相等代表着FIFO为空状态。而当写指针再次追上读指针时,此时读指针和写指针相等代表着FIFO为写满。也就是说当读写指针相等时,FIFO要么为空,要么为满。
因此我们可以将地址位扩展一位,用最高位来判断空满,其余低位还是正常用于读写地址索引。当写指针递增超过FIFO的最大地址时,写指针的MSB位将置为1,同时将其余低位设置回零。读指针也是如此。如果读指针和写指针的MSB不同,则意味着写指针比读指针多绕了一次,表示FIFO写满。如果两个指针的MSB相同,则表示两个指针的回绕次数相同,表示FIFO读空。如下图所示:

当最高位不同,且其他位相同,则表示读指针或者写指针多跑了一圈,这显然不可能发生,情况只能是写指针多跑了一圈,与就意味着FIFO被写满了。
当最高位相同,且其他位相同,则表示读指针追到了写指针或者写指针追到了读指针,而显然不会让写指针追读指针(这种情况只能是写指针超过读指针一圈),所以可能出现的情况只能是读指针追到了写指针,也就意味着FIFO被读空了。
理解了原理,我们就能很快设计出相应的verilog代码:
- //------------------------<高位扩展法设计同步FIFO>----------------------------
- module sync_fifo1#(
- //-----------------------------<参数定义>---------------------------------
- parameter FIFO_WIDTH = 16, //FIFO宽度
- parameter FIFO_DEPTH = 16 //FIFO深度
- )(
- //-----------------------------<接口定义>---------------------------------
- input clk, //时钟信号
- input rst, //复位信号
-
- input [FIFO_WIDTH-1:0] din, //FIFO输入数据(写数据)
- input rd_en, //读使能信号
- input wr_en, //写使能信号
-
- output reg [FIFO_WIDTH-1:0] dout, //FIFO输出数据(读数据)
- output empty, //FIFO空标志
- output full //FIFO满标志
- );
-
- //-----------------------------<reg定义>---------------------------------
- reg [FIFO_WIDTH-1:0] fifo_buffer[FIFO_DEPTH-1:0]; //用二维数组实现RAM
- reg [$clog2(FIFO_DEPTH):0] wr_addr; //写地址(写指针),位宽要多出一位
- reg [$clog2(FIFO_DEPTH):0] rd_addr; //读地址(读指针),位宽要多出一位
-
- //-----------------------------<wire定义>---------------------------------
- wire [$clog2(FIFO_DEPTH) - 1 : 0] wr_addr_true; //真实写地址指针
- wire [$clog2(FIFO_DEPTH) - 1 : 0] rd_addr_true; //真实读地址指针
- wire wr_addr_msb; //写地址指针地址最高位
- wire rd_addr_msb; //读地址指针地址最高位
-
- assign {wr_addr_msb,wr_addr_true} = wr_addr; //将最高位与其他位拼接
- assign {rd_addr_msb,rd_addr_true} = rd_addr; //将最高位与其他位拼接
-
- //-----------------------------<读操作>-----------------------------------
- always@(posedge clk or posedge rst)begin
- if(rst)
- rd_addr <= 0;
- else if(rd_en && !empty)begin //读使能有效且FIFO非空
- rd_addr <= rd_addr + 1'd1; //读指针递增
- dout <= fifo_buffer[rd_addr_true]; //fifo读出数据
- end
- else begin
- rd_addr <= rd_addr;
- dout <= dout;
- end
- end
- //-----------------------------<写操作>-----------------------------------
- always@(posedge clk or posedge rst)begin
- if(rst)
- wr_addr <= 0;
- else if(wr_en && !full)begin //写使能有效且FIFO非满
- wr_addr <= wr_addr + 1'd1; //读指针递增
- fifo_buffer[wr_addr_true] <= din; //数据写入fifo
- end
- else begin
- wr_addr <= wr_addr;
- end
- end
-
- //-----------------------------<通过地址扩展位更新空/满信号>-----------------------------------
- assign empty = ( wr_addr == rd_addr ) ? 1'b1 : 1'b0; //当所有位相等时,读指针追到了写指针,FIFO被读空
- assign full = ((wr_addr_msb != rd_addr_msb ) && ( wr_addr_true == rd_addr_true )) ? 1'b1 : 1'b0; //当最高位不同但是其他位相等时,写指针超过读指针一圈,FIFO被写满
-
- endmodule
给出如下的测试代码:
- `timescale 1ns/1ns
- //-----------------------------<高位扩展法同步FIFO测试>---------------------------------
- module tb_sync_fifo1();
- parameter WIDTH = 8;
- parameter DEPTH = 8;
-
- reg clk ;
- reg rst ;
-
- reg [WIDTH-1:0] din ;
- reg wr_en ;
- reg rd_en ;
- wire [WIDTH-1:0] dout ;
- wire full ;
- wire empty ;
-
- //-----------------------------<测试模块例化>---------------------------------
- sync_fifo1 #(
- .FIFO_WIDTH (WIDTH), //FIFO宽度
- .FIFO_DEPTH (DEPTH) //FIFO深度
- )
- sync_fifo_u1(
- .clk (clk ),
- .rst (rst ),
- .din (din ),
- .rd_en (rd_en ),
- .wr_en (wr_en ),
-
- .dout (dout ),
- .empty (empty ),
- .full (full )
- );
-
- //-----------------------------<模块测试>---------------------------------
- initial begin
- clk = 1'b0; //初始时钟为0
- rst <= 1'b0; //初始复位
- din <= 'd0;
- wr_en <= 1'b0;
- rd_en <= 1'b0;
- #10
- rst <= 1'b1;
- #10
- rst <= 1'b0;
- repeat(10)
- #10 begin
- wr_en <= 1'b1;
- rd_en <= 1'b0;
- din <= $random; //生成8位的随机数
- end
- repeat(10)
- #10 begin
- wr_en <= 1'b0;
- rd_en <= 1'b1;
- end
- $finish;
- end
- //------------------------------<设置时钟>----------------------------------------
- always #5 clk = ~clk;
- endmodule