FIFO 简介
FIFO 是 First In First Out 的缩写,先进先出,顺序写入数据,顺序读出数据,没有指定的读写地址线,读地址和写地址都从0开始,每读或写一次,指针加一,指向下一个存储单元,读写相互独立。
FIFO 可用于同步或异步时钟域内的数据传输,可以从快时钟域到慢时钟域,也可以从慢时钟域到快时钟域。
FIFO 工作原理
-
写指针
用写时钟,写地址从0开始,每写一次地址指针加一,指向下一个存储单元。
若 FIFO 为满状态,则不可再写。
-
读指针
用读时钟,读地址从0开始,每读一次地址指针加一,指向下一个存储单元。
若 FIFO 为空状态,则不可再读。
-
读空状态
复位时为空状态,写入数据后空状态清除。
当读地址追赶上写地址,两者相等时,FIFO 为读空状态,此时不可再读。
-
写满状态
复位时写满状态无效。
当写地址超过读地址一个 FIFO 深度,两者再次相等时,FIFO 为写满状态,此时不可再写入数据。
-
宽度
一次读写操作的数据位数。
-
深度
一个 FIFO 可以存储数据的个数。
FIFO 空状态和满状态
FIFO 为空或者为满时,写地址和读地址都相等,为了区分空状态和满状态,需要再加一个额外的地址位(MSB 位),表示写指针是否越过最后一个 FIFO 地址,若越过,MSB 为 1。
若读写地址与扩展位都相同时,表明读写数据的数量是一致的,FIFO 此时为空状态;若读写地址相同,扩展位相反时,表明写数据的数量越过了一个 FIFO 深度,FIFO 此时为满状态。
FIFO 指针编码
在不同时钟域下,用二进制计数可能会出现亚稳态,因为二进制计数时所有位可能同时发生变化,所以我们采用一次只会改变一位的格雷码。
DEC | BIN | Gray | DEC | BIN | Gray |
---|---|---|---|---|---|
0 | 0000 | 0000 | 8 | 1000 | 1100 |
1 | 0001 | 0001 | 9 | 1001 | 1101 |
2 | 0010 | 0011 | 10 | 1010 | 1111 |
3 | 0011 | 0010 | 11 | 1011 | 1110 |
4 | 0100 | 0110 | 12 | 1100 | 1010 |
5 | 0101 | 0111 | 13 | 1101 | 1011 |
6 | 0110 | 0101 | 14 | 1110 | 1001 |
7 | 0111 | 0100 | 15 | 1111 | 1000 |
需要一个二进制转换Gray码的转换电路。
// convert bin code to gray code
assign wrAddrGray = (wrAddrPtr >> 1) ^ wrAddrPtr;
assign rdAddrGray = (rdAddrPtr >> 1) ^ rdAddrPtr;
然后问题就转变成了如何通过格雷码判断 FIFO 的空和满状态
-
空状态
空状态依旧是读写指针的格雷码相同
assign emptyFlag = (rdAddrGray == wrAddrGray);
-
满状态
满状态要满足三个条件:
- 写指针 wPtr 和读指针 rPtr 的 MSB 位相反。因为 wPtr 越过了一个 FIFO 深度。
- 写指针 wPtr 和读指针 rPtr 的次高位相反。因为 gray 码具有镜像对称的特点。
- 写指针 wPtr 和读指针 rPtr 的其余位相同。
assign fullFlag = (wrAddrGray == {~rdAddrGray[ADDRWIDTH:ADDRWIDTH-1], rdAddrGray[ADDRWIDTH-2:0]});
FIFO 实现代码
`define __FIFO_V__
module Fifo #(
parameter DATAWIDTH = 4,
parameter DATADEPTH = 8,
parameter ADDRWIDTH = 3
)(
input wire wrClk,
input wire rdClk,
input wire rst_n,
input wire wrEn,
input wire rdEn,
input wire [DATAWIDTH-1:0] din,
output reg [DATAWIDTH-1:0] dout,
output reg wrValid,
output reg rdValid,
output wire fullFlag,
output wire emptyFlag
);
reg [ADDRWIDTH:0] wrAddrPtr; // width = ADDRWIDTH + 1: MSB + addr
reg [ADDRWIDTH:0] rdAddrPtr;
wire [ADDRWIDTH-1:0] wrAddr;
wire [ADDRWIDTH-1:0] rdAddr;
wire [ADDRWIDTH:0] wrAddrGray; // gray code
wire [ADDRWIDTH:0] rdAddrGray;
reg [DATAWIDTH-1:0] fifoRam[DATADEPTH-1:0];
assign wrAddr = wrAddrPtr[ADDRWIDTH-1:0];
assign rdAddr = rdAddrPtr[ADDRWIDTH-1:0];
assign wrAddrGray = (wrAddrPtr >> 1) ^ wrAddrPtr; // convert bin code to gray code
assign rdAddrGray = (rdAddrPtr >> 1) ^ rdAddrPtr;
// this expression is valid under sync mode
// if under async mode, need to add two step register to sync first
// assign emptyFlag = (rdAddrGray == wrAddrGray);
// assign fullFlag = (wrAddrGray == {~rdAddrGray[ADDRWIDTH:ADDRWIDTH-1], rdAddrGray[ADDRWIDTH-2:0]});
genvar i;
generate
for(i = 0; i < DATADEPTH; i = i + 1)
begin: fifoInit
always @(posedge wrClk or negedge rst_n) begin
if(!rst_n) begin
fifoRam[i] <= {DATAWIDTH{1'b0}};
wrValid <= 1'b0;
end
else if(wrEn & !fullFlag) begin
fifoRam[wrAddr] <= din;
wrValid <= 1'b1;
end
else begin
wrValid <= 1'b0;
end
end
end
endgenerate
always @(posedge rdClk or negedge rst_n) begin
if(!rst_n) begin
dout <= {DATAWIDTH{1'b0}};
rdValid <= 1'b0;
end
else if(rdEn & !emptyFlag) begin
dout <= fifoRam[rdAddr];
rdValid <= 1'b1;
end
else begin
rdValid <= 1'b0;
end
end
always @(posedge wrClk or negedge rst_n) begin
if(!rst_n) begin
wrAddrPtr <= {(ADDRWIDTH + 1){1'b0}};
end
else if(wrEn & !fullFlag) begin
wrAddrPtr <= (wrAddrPtr == {(ADDRWIDTH + 1){1'b1}}) ? {(ADDRWIDTH + 1){1'b0}} : wrAddrPtr + 1;
end
end
always @(posedge rdClk or negedge rst_n) begin
if(!rst_n) begin
rdAddrPtr <= {(ADDRWIDTH + 1){1'b0}};
end
else if(rdEn & !emptyFlag) begin
rdAddrPtr <= (rdAddrPtr == {(ADDRWIDTH + 1){1'b1}}) ? {(ADDRWIDTH + 1){1'b0}} : rdAddrPtr + 1;
end
end
// if async mode
reg [ADDRWIDTH:0] wrAddrGrayD1;
reg [ADDRWIDTH:0] wrAddrGrayD2;
reg [ADDRWIDTH:0] rdAddrGrayD1;
reg [ADDRWIDTH:0] rdAddrGrayD2;
always @(posedge wrClk or negedge rst_n) begin
if (!rst_n) begin
wrAddrGrayD1 <= {(ADDRWIDTH + 1){1'b0}};
wrAddrGrayD2 <= {(ADDRWIDTH + 1){1'b0}};
end
else begin
wrAddrGrayD1 <= wrAddrGray;
wrAddrGrayD2 <= wrAddrGrayD1;
end
end
always @(posedge rdClk or negedge rst_n) begin
if (!rst_n) begin
rdAddrGrayD1 <= {(ADDRWIDTH + 1){1'b0}};
rdAddrGrayD2 <= {(ADDRWIDTH + 1){1'b0}};
end
else begin
rdAddrGrayD1 <= rdAddrGray;
rdAddrGrayD2 <= rdAddrGrayD1;
end
end
assign emptyFlag = rdAddrGray == wrAddrGrayD2;
assign fullFlag = wrAddrGray == {~rdAddrGrayD2[ADDRWIDTH:ADDRWIDTH-1], rdAddrGrayD2[ADDRWIDTH-2:0]};
endmodule
Conclusion
2022 一开年就太忙了,2021 年终总结可以变成牛年总结了。
这周接受了一个观点,和一个和你思考方式不同的人生活,比和另一个你一起生活更有趣。
2022 年想把联合国儿童基金会的月捐金额提高,提高多少就要看老板给我涨多少工资了。觉得慈善不光是在帮助孩子,更是在救赎我自己,回馈每一个在阴影之下给过我光的人。在物竞天择适者生存的丛林之下,希望每个孩子有一次尝试走出困境的机会。