空满边界与同周期读写

返回 项目总览。

FIFO 最容易出错的地方不是普通写入或读取,而是 empty/full 边界与同一周期同时请求读写。判断时必须区分 request 和 accepted operation。

四种请求组合

对正常的非空非满状态:

wr_enrd_enwrite_okread_okoccupancy 变化
00000
1010+1
0101-1
11110

同时读写时,读出旧队首、写入新队尾,逻辑顺序继续前进。

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 的幂深度会访问无效地址。

自测

  1. empty 时同时读写 X,本拍 dout 和下一状态分别是什么?
  2. full 时同时读写,为什么 count 不变但数据集合发生变化?
  3. full 时只写,哪些内部状态必须保持?

答案要点:dout 保持旧值、X 入队且 count=1;一个旧元素出队、一个新元素入队;memory、wr_ptr、rd_ptr、count 均不因该写请求改变。

面试复述要点

我先用 read_ok/write_ok 把请求转换成真正接受的操作。full 时如果同拍存在有效读,就允许写入替换被读走的位置;empty 时同拍读写只接受写,没有 bypass。count 只在单独读或单独写时变化。

上一篇:参数化 RTL。下一篇:Cocotb 验证架构。