4. FIFO写操作握手:写请求与写确认、写指针更新机制、写满条件判断
写操作握手,说白了就是数据怎么安全地送进FIFO里。我刚开始做FIFO设计时,总觉得写操作很简单——不就是把数据写进去嘛。结果第一次仿真就出了大问题,数据写丢了,还覆盖了旧数据。嗯,从那以后我再也不敢小看这个握手过程了。
4.1 写请求与写确认:谁说了算?
写操作的核心,是写使能(wr_en)和写确认(wr_ack)的配合。我个人习惯把写使能看作“敲门”,写确认看作“开门”。
- 写使能(wr_en):外部模块告诉FIFO:“我要写数据了!”
- 写确认(wr_ack):FIFO回应:“收到,数据已存入。”
你想想看,如果没有写确认,外部模块怎么知道数据真的写进去了?我在项目中遇到过这样的情况:外部模块连续发写请求,但FIFO其实已经满了,结果数据全丢了。所以,写确认信号必须和写指针更新同步。
核心原则:写使能有效且FIFO未满时,数据才被真正写入。写确认在下一个时钟周期拉高。
4.2 写指针更新机制:数据该放哪儿?
写指针(wr_ptr)就是FIFO的“收件员”,它告诉数据该放在哪个地址。每次成功写入一个数据,写指针就加1。
我记得有一次调试,发现写指针跳过了某个地址,数据直接写到了错误的位置。查了半天,原来是写指针更新条件写错了。这里我给大家一个避坑指南:
我曾经踩过的坑:写指针更新不能只看wr_en,还要检查FIFO是否满。如果FIFO满了还更新指针,数据就会覆盖还没读走的内容。
写指针更新的标准写法:
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
wr_ptr <= 0;
else if (wr_en && !full)
wr_ptr <= wr_ptr + 1;
end
这里有个细节:wr_en && !full 这个条件,缺一不可。我见过有人只写wr_en,结果FIFO满了还在写,数据全乱套了。
4.3 写满条件判断:什么时候该停?
写满(full)判断,是FIFO设计中最容易出错的地方之一。为什么?因为判断条件不是简单的“写指针追上读指针”。
我给大家画个图,你就明白了:
看到了吗?写满的判断,不是简单的 wr_ptr == rd_ptr。因为空状态也是这个条件。那怎么区分?
我的经验:用格雷码(Gray Code)或者加一个额外的标志位。我个人偏爱加一个 full_flag 寄存器,在写操作时判断是否即将满。
写满判断的标准写法:
assign full = (wr_ptr_next == rd_ptr) && wr_en;
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
full_reg <= 0;
else if (wr_en && (wr_ptr_next == rd_ptr))
full_reg <= 1;
else if (rd_en)
full_reg <= 0;
end
这里有个关键点:wr_ptr_next 是写指针的下一个值。如果下一个值等于读指针,说明再写一次就满了。这叫“提前判断”,能避免溢出。
4.4 写操作握手完整流程
把上面三个部分串起来,写操作的完整流程是这样的:
- 外部模块拉高 wr_en,同时把数据放到 wr_data 总线上
- FIFO检查 full 信号:如果未满,接收数据,更新写指针
- 下一个时钟周期,拉高 wr_ack,告诉外部模块“数据已收”
- 如果FIFO满了,wr_ack 保持低电平,数据被忽略
重要提醒:写确认信号不能和写使能同时有效。我见过有人把这两个信号连在一起,结果每个时钟周期都写两次数据,FIFO直接爆炸。
4.5 常见问题与避坑
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 数据写丢 | 写使能有效时FIFO已满 | 检查full信号后再写 |
| 写指针乱跳 | 更新条件没加满判断 | wr_en && !full |
| 满状态误判 | wr_ptr == rd_ptr 无法区分空/满 | 加额外标志位或用格雷码 |
| 写确认延迟 | 组合逻辑路径太长 | 用寄存器打一拍 |
嗯,写操作握手这部分,说白了就是三个字:稳、准、狠。稳——数据不能丢;准——指针不能偏;狠——满了一定要停。我在项目中吃过亏,所以特别强调这些细节。你设计的时候,多想想边界情况,基本就不会出大问题。
小技巧:仿真时故意让写操作连续不停,看看FIFO满了之后数据会不会丢失。这是检验写操作握手是否正确的“试金石”。
可可精品资料库