一、前言
在之前的文章中,我们介绍了同步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代码:
//------------------------<高位扩展法设计同步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