0、同步fifo的基本概念
同步fifo其实就是在同一个时钟域下,实现先进先出的buffer;相比较异步fifo来说整个设计是比较好理解的。
1、同步fifo设计的关键问题
和异步fifo一样,也要考虑空满判断条件,但因为是同一个时钟域,所以不用考虑同步的问题;
满:写指针增加追上读指针;
空:读指针增加追上写指针。
示意图:
上述示意图的意思是为了寄存输出full和empty,所以是以次态来判断指针是否相等,在下1T即可得到full和empty的信号;
不过上述是示意图是以写时不读或者读时不写的情况说明的,以写满为例说明,当T,写指针指向fifo最后的位置,读指针指向fifo起始位置,此时判断写指针次态即+1的指针是否与当T的读指针相等,相等,当写使能的时候,下1T即写满。判空同理;
如果当T同发生读写,除非一开始写1个同时读1个,存在判空的可能即(wptr=rptr=0),其他情况因为同时读写,之前的状态就不会改变,即非空非满,注意满的时候不能写了,所以不存在上1T是满,下1T能够同时读写;
假设深度是4为例(注意这里指针即地址不是扩展1位的)
以画波形图说明:
从上图我们就可以看到右边红框是只读不写,然后读指针+1等于写指针,判空;特别是左边红框,可以看到描述就是前面描述判满的那个图,此时读写同时发生,所以不会得出判满的结论。
上面的设计相对来说考虑的要多些,如果只是为了寄存输出full和empty还有种简单点的设计:
读写指针是地址扩展1位的;
取读写指针的次态判断空满;
然后直接打拍次态判断结果。
示意图如下:
同样以深度为4为例,通过时序图来分析:
2、设计电路框架图
3、描述电路代码
确定好输入输出信号,理解了full和empty的设计,剩下的就是描述这个电路的过程:
方法一:
module sync_fifo
#(parameter DATA_WIDTH = 8,
parameter PTR_WIDTH = 4,
parameter DEPTH = 2 ** PTR_WIDTH
)
(input clk,
input rst_n,
input wen,
input ren,
input [DATA_WIDTH-1:0] wdata,
output logic [DATA_WIDTH-1:0] rdata,
output logic full,
output logic empty
);
logic [PTR_WIDTH-1:0] wptr;
logic [PTR_WIDTH-1:0]rptr;
logic [DATA_WIDTH-1:0] mem [DEPTH-1:0];
// point increment
always_ff @(posedge clk or negedge rst_n)begin
if(!rst_n) begin
wptr <= 'h0;
end else if(wen && !full)
wptr <= wptr +1'b1;
end
always_ff @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rptr <= 'h0;
end if(ren && !empty)
rptr <= rptr + 1'b1;
end
//full
always_ff @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
full <= 'h0;
end else
full <= ((wen&&!ren) && (rptr == wptr + 1'b1));
end
//empty
always_ff @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
empty <= 'h1;
end else if(!wen) //only read not write
empty <= (ren && (wptr == rptr + 1'b1));
else // write and read
empty <= (ren && (wptr==rptr));
end
//memory
always_ff @(posedge clk)begin
if(wen && !full)
mem[wptr] <= wdata;
end
always_ff @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rdata <= 'h0;
end if(ren && !full)
rdata <= mem[rptr];
end
endmodule // sync_fifo
方法二:相比较方法一的设计,方法二使用扩展1位的指针,同样为了寄存输出,需要通过指针次态来判断空满。这里可以联想下前面异步FIFO的关于空满判断的思路。
module sync_fifo
#(parameter DATA_WIDTH = 8,
parameter PTR_WIDTH = 4,
parameter DEPTH = 2 ** PTR_WIDTH
)
(input clk,
input rst_n,
input wen,
input ren,
input [DATA_WIDTH-1:0] wdata,
output logic [DATA_WIDTH-1:0] rdata,
output logic full,
output logic empty
);
logic [DATA_WIDTH-1:0] mem [DEPTH-1:0];
logic [PTR_WIDTH-1:0] wptr;
logic [PTR_WIDTH-1:0] rptr;
logic [PTR_WIDTH-1:0] wptr_nxt;
logic [PTR_WIDTH-1:0] rptr_nxt;
logic full_s;
logic empty_s;
//next ptr
assign wptr_nxt = wptr + (wen && (!full));
assign rptr_nxt = rptr + (ren && (!empty));
//current ptr
always_ff @(posedge clk or negedge rst_n)begin
if(!rst_n)
wptr <= 'h0;
else
wptr <= wptr_nxt;
end
always_ff @(posedge clk or negedge rst_n)begin
if(!rst_n)
rptr <= 'h0;
else
rptr <= rptr_nxt;
end
//judge full and empty
assign full_s = (rptr_nxt==({~wptr_nxt[PTR_WIDTH-1],wptr_nxt[PTR_WIDTH-2:0]}));
assign empty_s = (wptr_nxt==rptr_nxt);
always_ff @(posedge clk or negedge rst_n)begin
if(!rst_n)
full <= 'h0;
else
full <= full_s;
end
always_ff @(posedge clk or negedge rst_n)begin
if(!rst_n)
empty <= 'h1;
else
empty <= empty_s;
end
//RAM
logic [PTR_WIDTH-2:0] waddr;
logic [PTR_WIDTH-2:0] raddr;
assign waddr = wptr[PTR_WIDTH-1:0];
assign raddr = rptr[PTR_WIDTH-1:0];
always_ff @(posedge clk)begin
if(wen && !full)
mem[waddr] <= wdata;
end
always_ff @(posedge clk or negedge rst_n)begin
if(!rst_n)
rdata <= 'h0;
else if(ren && !empty)
rdata <= mem[raddr];
end
endmodule //sync_fifo
4、波形图分析
相同的tb下,两种方法的波形图:
方法1:
方法2:
这个tb写的不好,但也能验证设计功能的正确性;两种方法都实现正确读空。
5、关于full和empty的组合逻辑输出和时序逻辑输出比较
上述是寄存输出full和empty,还有一种就是组合逻辑输出full和empty信号的,这两种写法,要根据实际的需求来设计,比如考虑面积,比如这个full信号被其他模块采用,那就最好寄存输出。关于组合逻辑输出full和empt信号的设计已经有很多了,这里不再赘述。
后言
其实这个同步FiFO,没有那么复杂,只是在实际设计中要灵活变通,什么时候需要寄存输出还是组合逻辑输出需要考量;我自认为上面也算写的不错了,但肯定还有不足,作为芯片设计的新人,希望更多人的看到,并提出意见,互相进步吧!