文章目录
- 前言
- 一、多bit数据流跨时钟域传输——FIFO
- 1、FIFO分类
- 2、常见参数
- 3、与普通存储器的区别
- 4、FIFO优缺点
- 二、同步FIFO
- 1、计数器法
- 2、高位扩展法
- 3、单端口和双端口RAM
- 3.1 单端口RAM
- 3.2 双端口RAM
- 4、例化双端口RAM实现同步FIFO
- 三、异步FIFO
- 1、格雷码
- 1.1 二进制和格雷码之间的转换
- 1.2 使用格雷码判断空满
- 1.3 当深度不是2次幂
- 1.4 异步FIFO能否消除掉亚稳态
- 1.5 读写判断是存在漏洞,不是真空或者真满
- 1.6 格雷码产生传输错误
- 1.7 极端读写时钟域情况
- 2、例化双端口RAM实现异步FIFO
- 四、计算FIFO最小深度
- 1、FIFO写时钟100MHz,读时钟80MHz,每100个写时钟,写入80个数据;每一个读时钟读走一个数据,求最小深度不会溢出
- 2、一个8bit宽的AFIFO,输入时钟为100MHz,输出时钟为95MHz,设一个package为4Kbit,且两个package之间的发送间距足够大,问AFIFO的深度。
- 3、A/D采样率50MHz,DSP读A/D速率40MHz,要不丢失地将10万个采样数据送入DSP,在A/D在和DSP之间至少加多大容量(深度)的FIFO才行?
- 4、异步FIFO要考虑格雷码同步的时间
- 5、真题
- 五、读写频率、读写有无空闲周期下最小深度计算
- 1、fA>fB,读写之间无空闲周期
- 2、fA>fB,两次连续读写之间有一个周期的延迟
- 3、fA>fB,读写都有空闲周期/读写使能百分比
- 4、fA<fB,读写之间无空闲周期
- 5、fA<fB,读写都有空闲周期/读写使能百分比
- 6、fA=fB,读写之间无空闲周期
- 7、fA=fB,读写都有空闲周期/读写使能百分比
- 8、读写速率相等每100个时钟写入80个数据每10个时钟读取8个数据
- 六、简答题
- 1、什么是异步FIFO?画出异步FIFO的结构图。
- 2、如何判断读空或者写满信号?
- 3、为什么要用格雷码?在FIFO中使用格雷码的优点
- 4、异步FIFO设计核心
- 5、如何验证FIFO?
前言
2023.4.6 把之前学习的内容汇总整理
一、多bit数据流跨时钟域传输——FIFO
大多数数据具有连续性,背靠背传输;需要较快的速度传输
两种解决方法:SRAM(两个时钟域通过其缓冲,这里不讲)、FIFO
1、FIFO分类
同步FIFO
:读写时钟为同一时钟,时钟沿同时进行读写,内部所有逻辑为同步逻辑,常用于交互数据缓冲
异步FIFO
:读写时钟不同,相互独立
2、常见参数
FIFO宽度
:一次读写数据的数据位
FIFO深度
:可以存储多少个N位的数据
空标志
:FIFO已空或将要空时由FIFO的状态电路送出的一个信号,以阻止FIFO的读操作继续从FIFO中读出数据而造成无效数据的读出(underflow
)。读写指针相等时,表示空,发生在复位或者读指针读完最后一个数追上写指针的时候。
满标志
:FIFO已满或将要满时由FIFO的状态电路送出的一个信号,以阻止FIFO的写操作继续向FIFO中写数据而造成溢出(overflow
)。当读写指针再次相等,表示满。
读时钟
:读操作所遵循的时钟,在每个时钟沿来临时读数据。
写时钟
:写操作所遵循的时钟,在每个时钟沿来临时写数据。
写指针
:总是指向下一个将要被写入的单元,复位时,指向0
读指针
:总是指向当前要被读出的数据,复位时,指向第 1 个单元(编号为 0)
3、与普通存储器的区别
- 区别:没有外部读写地址线,使用简单
4、FIFO优缺点
- 优点:适用性强,可以用来同步没有任何关系的时钟域间的数据交换,相比于握手协议数据传输速率更快,不同宽度的数据接口也可以用FIFO。
- 缺点:只能顺序读写,且面积大
二、同步FIFO
1、计数器法
计数器复位时初始化为0,写操作计数器+1,读操作计数器-1,计数器为0表示空,计数器为深度时表示满。
缺点:读写不能同时操作,且计数器会占用额外的资源,当FIFO较大时,可能会降低读写的速度。
关键: 读写指针的变化、空满信号的产生
module sync_fifo
#(parameter DEPTH = 'd16, parameter WIDTH = 'd8)(
input clk,
input rst_n,
input wr_en,
input rd_en,
input [WIDTH-1:0] data_in,
output [WIDTH-1:0] data_out,
output empty,
output full
);
reg [$clog2(DEPTH):0] cnt; //计数器范围为0-16
reg [WIDTH-1:0] mem [DEPTH-1:0];
reg [$clog2(DEPTH)-1:0] waddr, raddr;
//写操作
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
waddr <= 0;
else if(wr_en & !full)begin
mem[waddr] <= data_in;
waddr <= waddr+1;
end
end
//读操作
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
raddr <= 0;
else if(rd_en & !empty)begin
data_out <= mem[raddr];
raddr <= raddr+1; //注意这里读地址也是+1
end
end
//更新计数器
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt <= 0;
else begin
case({wr_en, rd_en})
2'b00: cnt <= cnt;
2'b01: if(cnt!=0) cnt <= cnt - 1;
2'b10: if(cnt!=DEPTH) cnt <= cnt + 1;
2'b11: cnt <= cnt;
endcase
end
end
assign full = (cnt==DEPTH);
assign empty = (cnt==0);
endmodule
2、高位扩展法
没有扩展的时候,空和满的时候,读写指针是完全相同的,无法进行判断
- 地址位扩展一位,当写指针增加并越过最后一个FIFO地址时,就将写指针这个未用的MSB加1,其它位回零。对读指针也进行同样的操作。
- 对于深度为
2^n
的FIFO,需要的读/写指针位宽为(n+1)
位,如对于深度为8的FIFO,需要采用4bit的计数器,0000 ~1000、1001~1111,MSB作为折回标志位,而低3位作为地址指针。 - 如果两个指针的MSB不同,说明写指针比读指针多折回了一次;如r_addr=0000,而w_addr = 1000,为满。
- 如果两个指针的MSB相同,则说明两个指针折回的次数相等。其余位相等,说明FIFO为空。
module sync_fifo
#(parameter DEPTH = 'd16, parameter WIDTH = 'd8)(
input clk,
input rst_n,
input wr_en,
input rd_en,
input [WIDTH-1:0] data_in,
output [WIDTH-1:0] data_out,
output empty,
output full
);
reg [WIDTH-1:0] mem [DEPTH-1:0];
reg [$clog2(DEPTH):0] waddr, raddr;
//写操作
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
waddr <= 0;
else if(wr_en & !full)begin
mem[waddr[$clog2(DEPTH)-1:0]] <= data_in;
waddr <= waddr+1;
end
end
//读操作
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
raddr <= 0;
else if(rd_en & !empty)begin
data_out <= mem[raddr[$clog2(DEPTH)-1:0]]; //这里也可以用组合逻辑输出,实现read和data out同步输出
raddr <= raddr+1; //注意这里读地址也是+1
end
end
assign empty = (raddr == waddr);
assign full = (raddr == {~waddr[$clog2(DEPTH)], waddr[$clog2(DEPTH)-1:0]}); //最高位相反
endmodule
3、单端口和双端口RAM
3.1 单端口RAM
module ram_mod(
input clk,
input rst_n,
input write_en,
input [7:0]write_addr,
input [3:0]write_data,
input read_en,
input [7:0]read_addr,
output reg [3:0]read_data
);
reg [3:0] mem [7:0];
integer i;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
for(i=0;i<8;i=i+1)
mem[i]<=0;
end
else if(write_en)
mem[write_addr]<=write_data;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
read_data<=0;
end
else if(read_en)
read_data<=mem[read_addr];
end
endmodule
3.2 双端口RAM
module dual_port_RAM #(parameter DEPTH = 16,
parameter WIDTH = 8)(
input wclk
,input wenc
,input [$clog2(DEPTH)-1:0] waddr //深度对2取对数,得到地址的位宽。
,input [WIDTH-1:0] wdata //数据写入
,input rclk
,input renc
,input [$clog2(DEPTH)-1:0] raddr //深度对2取对数,得到地址的位宽。
,output reg [WIDTH-1:0] rdata //数据输出
);
reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];
always @(posedge wclk) begin
if(wenc)
RAM_MEM[waddr] <= wdata;
end
always @(posedge rclk) begin
if(renc)
rdata <= RAM_MEM[raddr];
end
endmodule
4、例化双端口RAM实现同步FIFO
//这里用的是高位扩展法
module sfifo#(
parameter WIDTH = 8,
parameter DEPTH = 16
)(
input clk ,
input rst_n ,
input winc ,
input rinc ,
input [WIDTH-1:0] wdata ,
output reg wfull ,
output reg rempty ,
output wire [WIDTH-1:0] rdata
);
wire wenc,renc;
assign wenc = winc && !wfull;
assign renc = rinc && !rempty;
reg [$clog2(DEPTH):0] waddr;
reg [$clog2(DEPTH):0] raddr;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
wfull<=0;
rempty<=0;
end
else begin
rempty<=(raddr==waddr);
wfull<=(waddr==raddr+DEPTH);
end
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
raddr<=0;
end
else if(renc)
raddr<=raddr+1;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
waddr<=0;
end
else if(wenc)
waddr<=waddr+1;
end
//例化双端口RAM
dual_port_RAM #(.DEPTH(DEPTH),.WIDTH(WIDTH))
ram(
.wclk(clk),
.wenc(wenc),
.waddr(waddr[DEPTH-1:0]), //注意这里地址位宽,最高位不是有效地址位
.wdata(wdata),
.rclk(clk),
.renc(renc),
.raddr(raddr[DEPTH-1:0]),
.rdata(rdata)
);
endmodule
三、异步FIFO
1、格雷码
格雷码:循环码,相邻两个数值之间只有一位发生改变,发生亚稳态的概率大大降低
二进制码 | 格雷码 |
---|---|
000 | 000 |
001 | 001 |
010 | 011 |
011 | 010 |
100 | 110 |
101 | 111 |
110 | 101 |
111 | 100 |
1.1 二进制和格雷码之间的转换
二进制码转化为格雷码:从最右边第一位开始,依次将每一位与左邻一位异或(XOR),作为对应格雷码该位的值,最左边一位不变;
gray = bin ^ (bin>>1);
格雷码转化为二进制码:从左边第二位起,将每位与左边一位解码后的值异或(XOR),作为该位解码后的值(最左边一位依然不变)。
reg [3:0] gray, bin;
bin[3] = gray[3];
bin[2] = bin[3] ^ gray[2];
bin[1] = bin[2] ^ gray[1];
bin[0] = bin[1] ^ gray[0];
1.2 使用格雷码判断空满
- 空:两者指针完全相同
- 满:最高位和次高位不同,其他位完全相同
- 7(0111)和15(1111)对应到二进制码的话就只有最高位不高。7(0100)和15(1000)对应到格雷码的话最高位和次高位相反,其他位相同
1.3 当深度不是2次幂
- 并不是一定要用格雷码做读写指针,而是当深度为2次幂的时候,刚好格雷码满足消除亚稳态的需求;
- 在非2次幂深度情况下,格雷码已经不再适用,此时的解决方法通常有:
若深度为偶数,可采用最接近的2次幂的格雷码编码,在此基础上修改;
深度为一般数值时,可自行设计一种逻辑电路,或者查找表,以实现指针每次只跳变一次的功能; - 以上方法通常在设计层面较为复杂,若无特定需求,可将FIFO深度设置为2次幂,浪费一些存储空间,来化简控制电路的复杂度。
或者在偶数个的时候,去掉头和尾的数字,中间部分也是满足要求
异步FIFO在2N个数据类就可以循环
1.4 异步FIFO能否消除掉亚稳态
- 亚稳态不能从根本上消除,只能采取一定的措施使其对电路的影响降低
- 打两拍只能大幅降低亚稳态发生的概率,但是理论上是不能完全消除的
1.5 读写判断是存在漏洞,不是真空或者真满
- 写指针通过两级寄存器同步到读时钟域,再和读指针比较进行FIFO空状态判断,因为在两级同步寄存器需要时间,而在这个同步的时间内有可能还会写入新的数据,因此同步后的写指针一定是小于或者等于当前实际的写指针,所以此时判断 FIFO为空不一定是真空,这样更加保守,一共不会出现空读的情况,虽然会影响FIFO的性能,但是并不会出错。
- 读指针通过两级寄存器同步到写时钟域,再和写指针比较进行FIFO满状态判断,同步后的读指针一定是小于或者等于当前的读指针,所以此时判断FIFO为满不一定是真满。
1.6 格雷码产生传输错误
- 保证的是同步后的读写地址即使在出错的情形下,依然能够保证FIFO功能的正确性,不会出现
空读满写
的错误状态,当然同步后的读写地址出错总是存在的。 地址总线bus skew
一定不能超过一个周期,否则可能出现gray码多位数据跳变的情况,这个时候gray码就失去了作用,因为这时候同步后的地址已经不能保证只有1位跳变了。
写指针000 -> 001,假设同步过去变成了000 -> 000,这样会判断为空,但实际上并不为空,此时不能读操作,还是可以写。不会对FIFO的功能造成影响。
1.7 极端读写时钟域情况
- 写时钟域慢,读时钟域快:读时钟域总是能采集到信号(慢到快),判断空不成问题
假如读指针从1-9,同步到写时钟域的是7,写指针也在7,那么会判断“满”,会停止写入,其实没满,还差点
- 写时钟域快,读时钟域慢:会出现漏采(只能采集到135)。将读指针传到写时钟域是可以的(慢到快),判断满不成问题
假设写指针从1-9,同步到读时钟域是7,读指针也在7,会产生“空信号”,会停止读,但实际上没有空
- 当在极端状况下,写很快,读很慢,那一开始就被写满;相反则永远是读空
总结:写得快,会错误判断产生虚空;读得快,会错误判断产生虚满
2、例化双端口RAM实现异步FIFO
module asyn_fifo#(
parameter WIDTH = 8,
parameter DEPTH = 16
)(
input wclk ,
input rclk ,
input wrstn ,
input rrstn ,
input winc ,
input rinc ,
input [WIDTH-1:0] wdata ,
output wire wfull ,
output wire rempty ,
output wire [WIDTH-1:0] rdata
);
//双端口RAM的读写使能信号
wire wenc = winc && !wfull;
wire renc = rinc && !rempty;
parameter ADD_WIDTH = $clog2(DEPTH); //地址线宽度
//二进制地址加减
reg [ADD_WIDTH:0] wptr_bin,rptr_bin;
wire [ADD_WIDTH:0] wptr_bin_next,rptr_bin_next;
assign wptr_bin_next = wptr_bin + (winc&!wfull);
assign rptr_bin_next = rptr_bin + (rinc&!rempty);
//二进制码转换为格雷码
reg [ADD_WIDTH:0] wptr_gray,rptr_gray;
wire [ADD_WIDTH:0] wptr_gray_next,rptr_gray_next;
assign wptr_gray_next = wptr_bin ^ (wptr_bin>>1);
assign rptr_gray_next = rptr_bin ^ (rptr_bin>>1);
//更新二进制码和格雷码
always@(posedge wclk or negedge wrstn)begin
if(!wrstn)
{wptr_bin,wptr_gray} <=0;
else
{wptr_bin,wptr_gray}<={wptr_bin_next,wptr_gray_next};
end
always @ (posedge rclk or negedge rrstn) begin
if (!rrstn) begin
{rptr_bin,rptr_gray} <= 'd0;
end
else
{rptr_bin,rptr_gray} <= {rptr_bin_next, rptr_gray_next};
end
//同步格雷码
reg [ADD_WIDTH:0] wptr_gray_r,wptr_gray_rr;
always@(posedge rclk or negedge rrstn)begin //读时钟域同步写格雷码
if(!rrstn)
{wptr_gray_r,wptr_gray_rr} <=0;
else
{wptr_gray_rr,wptr_gray_r}<={wptr_gray_r,wptr_gray};
end
reg [ADD_WIDTH:0] rptr_gray_r,rptr_gray_rr;
always@(posedge wclk or negedge wrstn)begin //写时钟域同步读格雷码
if(!wrstn)
{rptr_gray_rr,rptr_gray_r} <=0;
else
{rptr_gray_rr,rptr_gray_r}<={rptr_gray_r,rptr_gray};
end
//空满标志的产生,利用格雷码判断
assign rempty = rptr_gray==wptr_gray_rr;
assign wfull = wptr_gray=={~rptr_gray_rr[ADD_WIDTH:ADD_WIDTH-1],rptr_gray_rr[ADD_WIDTH-2:0]};
//例化双端口RAM
dual_port_RAM #(.DEPTH(DEPTH),.WIDTH(WIDTH))
one(
.wclk(wclk),
.wenc(wenc),
.waddr(wptr_bin[ADD_WIDTH-1:0]), //注意这里地址
.wdata(wdata),
.rclk(rclk),
.renc(renc),
.raddr(rptr_bin[ADD_WIDTH-1:0]),
.rdata(rdata)
);
endmodule
二进制码和格雷码更新这段也可以按照下面这样写,但是不知道和分开定义一个wptr_gray_next,再寄存到wptr_gray有什么区别。这样写的话看上去简单一些。
//二进制地址加减
always@(posedge wclk or negedge wrstn)begin
if(!wrstn)
wptr_bin <= 0;
else if(wenc)
wptr_bin <= wptr + 1;
end
always@(posedge rclk or negedge wrstn)begin
if(!rrstn)
rptr_bin <= 0;
else if(renc)
rptr_bin <= rptr + 1;
end
//二进制码转格雷码
always@(posedge wclk or negedge wrstn)begin
if(!wrstn)
wptr_gray <= 0;
else
wptr_gray <= wptr_bin ^ (wptr_bin>>1);
end
always@(posedge rclk or negedge wrstn)begin
if(!wrstn)
rptr_bin <= 0;
else if(renc)
rptr_bin <= rptr_bin ^ (rptr_bin>>1);
end
四、计算FIFO最小深度
-
一般来说,在不连续传输的写入情况下,才考虑FIFO深度问题,因为假设是连续写入,FIFO总是会满的
-
在正常情况下,应该是这样的:空闲—Burst突发—空闲—Burst突发—空闲—Burst突发
-
背靠背传输:空闲—Burst突发—Burst突发—空闲—Burst突发—空闲。
如果接收方没法接收所有数据,那么剩余的数据可以被存储在FIFO内部且不会溢出,也就是我们要求的FIFO深度的意思。 -
在SDRAM的应用中,我们通常使用的读写FIFO是突发长度的2倍,比如突发长度为256,那FIFO的深度设置为512,使得FIFO始终保持半满的状态。可以保证数据的传输。
burst_length
:表示这段时间写入的数据量
burst_length/wclk
:写入数据的时间
(X/Y)*rclk
:每Y个时钟读出X个数据,说明读出效率不是100%,要在rclk基础上打折扣,速度没有rclk
depth
:写入和读出两者之差为FIFO中残留的数据,这个也就是理论上的FIFO的最小深度。
depth = burst_length - (burst_length/wclk) * ((X/Y)*rclk)
1、FIFO写时钟100MHz,读时钟80MHz,每100个写时钟,写入80个数据;每一个读时钟读走一个数据,求最小深度不会溢出
2、一个8bit宽的AFIFO,输入时钟为100MHz,输出时钟为95MHz,设一个package为4Kbit,且两个package之间的发送间距足够大,问AFIFO的深度。
传输数据个数 = 时间 * 速度(频率)
深度 = (读写速率差) * (写入数据需要的时间)
传输数据个数:4Kbit/8bit =500 word
写入500个word所需时间:500/100
该时间内读取的个数:500/100*95
(1)深度 = 写个数 - 读个数 = 500 - 500/100 * 95 = 25
(2)深度 = (100-95) * (500/100) = 25
3、A/D采样率50MHz,DSP读A/D速率40MHz,要不丢失地将10万个采样数据送入DSP,在A/D在和DSP之间至少加多大容量(深度)的FIFO才行?
FIFO用于缓冲块数据流,一般用在快时钟域写慢时钟域读
FIFO深度 /(写入速率 - 读出速率)= FIFO被填满时间 > 数据包传送时间= 数据量 / 写入速率
FIFO写满的时间 > 数据包传输的时间
FIFO深度 = (写入速率 - 读出速率)* 数据包传送时间
100000 / 50MHz = 1/ 500 s = 2ms
(50MHz - 40MHz) * 1/500 = 20k
4、异步FIFO要考虑格雷码同步的时间
5、真题
五、读写频率、读写有无空闲周期下最小深度计算
1、fA>fB,读写之间无空闲周期
写速率fA=80MHz,读速率fB=50MHz,突发长度Burst Length = 120,读写之间没有空闲周期,是连续读写一个突发长度。
120-120/80*50 = 120 - 75 = 45
2、fA>fB,两次连续读写之间有一个周期的延迟
同case1,两次连续读写之间通常有延迟
3、fA>fB,读写都有空闲周期/读写使能百分比
写速率fA=80MHz,读速率fB=50MHz,突发长度Burst Length = 120
两个连续写入之间的空闲周期为=1,两个连续读取之间的空闲周期为=3
写使能占得百分比为=50%=1/2,读使能占得百分比为=25%=1/4
答: 两个连续写入之间的空闲周期为1的意思是,每写入一个数据,要等待一个周期,再写入下一个数据。这也可以理解为每两个周期,写入一个数据。同理每四个周期,读取一个数据。相当于读写频率降低了。
120-120/40*12.5=82.5=83
4、fA<fB,读写之间无空闲周期
写进去的数据总是被读走了,所以FIFO深度为1即可
5、fA<fB,读写都有空闲周期/读写使能百分比
写速率fA=30MHz,读速率fB=50MHz,突发长度Burst Length = 120
两个连续写入之间的空闲周期为=1,两个连续读取之间的空闲周期为=3
120-120/30*12.5=20(实际上这道题目写速率比读快)
6、fA=fB,读写之间无空闲周期
读写为同一时钟,可以不需要FIFO,直接写入的值给输出。如果读写时钟存在相位差,那么FIFO深度为1即可。
7、fA=fB,读写都有空闲周期/读写使能百分比
写速率fA= 50MHz,读速率fB=50MHz,突发长度Burst Length = 120
两个连续写入之间的空闲周期为=1,两个连续读取之间的空闲周期为=3
120-120/25*12.5=60(实际上这道题目写速率比读快)
8、读写速率相等每100个时钟写入80个数据每10个时钟读取8个数据
考虑背靠背情况:160-160*8/10=32
六、简答题
1、什么是异步FIFO?画出异步FIFO的结构图。
- 读写时钟域不同。在写时钟域下将数据放入缓存区,读时钟域下将数据读取出来
- 没有外部读写地址线,只能按照顺序读写
- 写指针指向下一个要写入的位置,读指针指向当前要读取的位置。复位时,都指向零,是写指针下一个要写入的位置,读指针当前读取的位置,所以是“读空/empty”
2、如何判断读空或者写满信号?
直接根据地址是否相等不能判断空或者满。
对指针增加一位最高有效位MSB,n位指针,n-1位表示访问FIFO所需的地址位数。n位完全相同表示“空”;MSB不同,n-1位完全相同表示“满”
3、为什么要用格雷码?在FIFO中使用格雷码的优点
- 二进制码在传输时可能会发生多bit位同时变化得情况,如0111->1000,这样容易发生亚稳态。格雷码是循环码,相邻两位之间只有1bit发生改变,降低亚稳态发生的概率
- 多bit信号变化时会产生竞争冒险,消除竞争冒险
- 多bit传输时,格雷码跳变较少,功耗降低,有利于低功耗设计
- 格雷码出错只是一位发生错误,降低了错误对系统的影响,保证FIFO功能的正确性,只会出现假空或者假满
4、异步FIFO设计核心
- 格雷码+指针同步
5、如何验证FIFO?