空满边界与同周期读写
返回 项目总览。
FIFO 最容易出错的地方不是普通写入或读取,而是 empty/full 边界与同一周期同时请求读写。判断时必须区分 request 和 accepted operation。
四种请求组合
对正常的非空非满状态:
| wr_en | rd_en | write_ok | read_ok | occupancy 变化 |
|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 |
| 1 | 0 | 1 | 0 | +1 |
| 0 | 1 | 0 | 1 | -1 |
| 1 | 1 | 1 | 1 | 0 |
同时读写时,读出旧队首、写入新队尾,逻辑顺序继续前进。
empty 时的语义
empty 时 read_ok=0。即使 rd_en=1,也不会更新 dout、rd_ptr 或 count。
如果同拍 wr_en=1、rd_en=1:
read_ok=0;write_ok=1,因为 FIFO 不满;- 新数据进入 FIFO,count 从 0 变 1;
dout不在这一拍返回新写数据。
当前设计没有 bypass
这不是 fall-through FIFO。空状态同拍读写不会让新数据直接穿过到
dout,下一拍再次有效读取才能得到它。
full 时的语义
只有写请求时,read_ok=0、write_ok=0,memory、写指针和 count 全部保持,已有数据不会被覆盖。
full 时同拍读写:
read_ok=1,因为 FIFO 非空;write_ok=1,因为!full || read_ok成立;dout取得当前队首旧数据;- 新
din写入被释放的位置; - 两个指针都前进,count 仍等于 DEPTH。
full 时通常 wr_ptr==rd_ptr。同一时钟沿对同一 memory 地址既读又写时,nonblocking assignment 的 RHS 读取更新前的 memory,因此 dout 得到旧队首,写入则在时钟沿后保存新数据。这与 reference model “先取期望读值,再 pop,最后 append”一致。
almost-full 的边界
当前阈值为 14:
- count 13:
almost_full=0; - 写入后 count 14:
almost_full=1; - count 14 同拍读写:保持 14,flag 保持 1;
- count 14 只读:降到 13,flag 清零。
flag 是 count 的组合比较结果,会跟随时序 count 更新。
dout 的保持行为
reset 把 dout 清零;只有 read_ok 时才赋新值。空读或空闲周期保持最近一次有效读出的数据。使用者必须结合自己的 valid/enable 规则判断数据是否有效,不能仅看总线值变化。
常见 bug 注入
- 把
write_ok写成wr_en && !full:full 同拍读写会产生气泡。 - empty 时仍移动
rd_ptr:后续读取顺序错位。 - full 时普通写仍更新 memory:最旧未读数据被覆盖。
- 同拍读写让 count 加一或减一:flags 会逐步漂移。
- 依赖指针自然溢出:非 2 的幂深度会访问无效地址。
自测
- empty 时同时读写 X,本拍
dout和下一状态分别是什么? - full 时同时读写,为什么 count 不变但数据集合发生变化?
- full 时只写,哪些内部状态必须保持?
答案要点:dout 保持旧值、X 入队且 count=1;一个旧元素出队、一个新元素入队;memory、wr_ptr、rd_ptr、count 均不因该写请求改变。
面试复述要点
我先用
read_ok/write_ok把请求转换成真正接受的操作。full 时如果同拍存在有效读,就允许写入替换被读走的位置;empty 时同拍读写只接受写,没有 bypass。count 只在单独读或单独写时变化。
上一篇:参数化 RTL。下一篇:Cocotb 验证架构。