FIFO almost-full 阈值设计:提前量预算公式与反压气泡的阈值扫描实测
专题导航:异步FIFO设计全景。前置要求:已读支柱页、异步FIFO 满空信号与FIFO FWFT 与标准读模式,知道满空标志本身带三拍保守欠账、FWFT 下 almost-empty 要按弹出后语义理解。 上一篇:FIFO FWFT 与标准读模式 | 下一篇:FIFO 非对称位宽设计(./fifo-asymmetric-width.md)。 读序说明:满空语义篇证明了「标志必然迟到、误差只能偏保守」,本篇回答它留下的工程问题——既然迟到不可避免,almost-full 应该提前多少拍拉起才恰好够用。配套实验在工程包 derekzhuo/async-fifo-mini 的
tb/tb_almost_threshold.v。
本文要回答的问题
- 为什么等
full拉起再通知上游停写必然溢出?从标志拉起到停写生效,中间隔着哪些拍? - FIFO almost-full 阈值的预算公式怎么列,同步拍、skid 反应拍、同拍读写与突发长度各扣多少?
- 阈值设大设小的代价分别是什么?「写气泡」能不能用仿真量化出来?
- almost-empty 在读端对称地解决什么问题,它和 FWFT 的弹出语义有什么牵连?
- 深度不够用的时候,应该加深 FIFO 还是把反压再提前?阈值做成可配还是写死?
这张漏斗卡是全文的账本:深度 16 不是阈值可以自由支配的 16 格,反压路径上的每一级延迟都要先扣走自己的份额,末层红色条带才是 almost-full 比较器允许指向的最高水位。下面逐级拆账,再用扫描实验验证这条减法的边界。
一、为什么 full 再停写必然溢出:停写令在路上时写入不会停
设想最朴素的实现:FIFO 里一项空位都没有时拉起 full,上游看到后停止写入。问题在于「看到」这两个字——在异步 FIFO 里,占用比较发生在写时钟域(满判定用本地写指针加同步过来的旧读指针),但反压通知往往还要在系统里走一段路:跨时钟域两级触发器、上游总线的 valid/ready 排队、skid buffer 里已经握手成功的在途数据。这段路上的每一拍,写端都还在忠实地写入,因为停写令尚未到达。
这与异步FIFO 满空信号实测的三拍欠账是同一笔时间账:标志描述的永远是过去的占用,而写入消耗的是现在的槽位。等到 full 真实生效,FIFO 早已被在途写入顶破。full 的正确角色是「最后一道闸门、正常工况下永远不该碰到」,真正承担日常反压的是提前拉起的 almost-full。
读这张波形盯住两条竖线之间的距离:占用在第 2 拍达到阈值 14,比较器当拍拉起 afull;但经过两级同步,上游第 4 拍才看到它,于是第 2、3 拍又各写入一项,占用从 14 冲到 16——正好顶满,一项不多。如果阈值设成 15(只留 1 格提前量),同样两拍在途写入会把占用推到 17,第 17 项无处安放,就是溢出。阈值的本质是给停写令的旅途预留槽位:路上还要进几项,就必须在剩几项空槽时提前发令。
二、阈值预算公式:把反压路径上的延迟逐项扣成空槽
把上节的直觉写成不等式。设 FIFO 深度 DEPTH,afull 在占用达到 afull_thresh 时拉起,从拉起到写入真正停止期间最坏还会进入 N 项数据,则必须满足:
afull_thresh ≤ DEPTH − N,其中 N = N_sync + N_resp + N_burst| 延迟项 | 含义 | 本组取值(组A / 组B) | 数量来源 |
|---|---|---|---|
| N_sync | afull 同步到写决策点的打拍数 | 2 / 2 | 两级触发器,多电平同步器的标准配置 |
| N_resp | 上游收到反压到停写的反应拍 | 0 / 1 | 组合 ready 门控为 0;一级 skid buffer 为 1,每级在途数据加 1 |
| N_burst | 突发与同拍读写的最坏预留 | 0 / 0 | 本组实验读端停顿窗口内完全不读,已是最坏,不再另加;实板按总线在途突发长度取值 |
| 阈值上限 | DEPTH − N_sync − N_resp − N_burst | 14 / 13 | 预算结果,占用阈值只许更小不许更大 |
跨时钟域路径的结构如下图,颜色区分信号角色:蓝色是写端数据流,紫色比较器产出 afull,灰色两级同步器是延迟大头,橙色 skid 是上游自己的反应延迟,红色节点才是写入真正被掐断的地方。
读图抓住那条红色回边:从存储水位变化到写端停写,绕紫色、灰色、橙色三级节点走完整整一圈,圈上延迟之和就是 N。公式里最容易漏算的是两项。第一是时钟域折算:N_sync 名义上是两级、两拍,但当 afull 服务的决策时钟与产生水位的时钟不同频时,要按「这段时间内写端还能写几项」折算,写快读慢时 N 大于拍数本身,折算方法与异步FIFO 满空信号第五节给出的 N ≥ (2+1) × T_r/T_w 同口径。第二是同拍读写:占用顶满的那一拍如果读端同时取走一项,写入可以合法进行,深度瞬间腾出一格——但这格能否计入预算,取决于读停窗口内是否真的有读,保守做法是假设没有,本组实验的读端停顿窗口就专门制造这种最坏情况。
保守方向必须明确:阈值宁可早,不可晚。早了,代价只是部分槽位在临界期内被冻结(容量气泡,数据完好);晚了,代价是第 DEPTH+1 项写入覆盖未读数据,不可恢复。这与满空标志「虚报只损带宽、漏报才毁数据」的原则严格同构,格雷码跨时钟域原理保证的是方向,阈值预算保证的是幅度,两者共同守住零溢出。
三、almost-empty 的对称账:读端用提前量换预取时间
almost-empty 在读侧做的是同一件事的镜像:占用降到阈值以下时提前拉起,通知消费者「存货不多,该发起预取了」。DMA 描述符预取、报文缓冲的排空中断、AXI-Stream 上游补发使能,都依赖这个提前量。延迟预算对称成立:aempty_thresh ≥ N_sync_r + N_resp_r + N_prefetch,即标志必须在「预取数据到达前读完最后一项」之前拉起,给读端到写端的请求往返留出数据项数。
读这张波形:占用第 2 拍跌破 4,aempty 同步两拍后第 4 拍到达消费侧,预取请求同拍发出;在新数据跨越两个时钟域回来之前,读端以剩余 2 项为粮继续输出而不断流——这 2 项就是阈值买来的预取窗口。需要特别强调 FIFO FWFT 与标准读模式对照表里埋的那个语义点:FWFT 下读使能是「弹出当前队头」,almost-empty 必须按弹出之后的占用理解,阈值 1 等价于「窗口里只剩当前这一份」,照搬标准模式的计数会让预取请求晚发一拍。
四、阈值扫描实测:9 档阈值下的气泡、最大占用与溢出
道理到此为止,上实验。深度 16 的单时钟行为模型,写端始终背靠背有数据;读端按 16 拍周期运行——前 8 拍连续读、后 8 拍完全停顿,每个周期都人为制造一次「反压窗口内读端彻底消失」的最坏情况。afull 经两级同步延迟后才到达写端;扫描分两组:组 A 上游组合 ready(N_resp=0,预算 14),组 B 上游带一级 skid(N_resp=1,预算 13)。每档跑 400 拍,统计接受写入数、写气泡(想写却被反压挡住且当时确有空槽的拍数)、最大占用与溢出次数。
终端卡里 9 行扫描结果整理如下,数据全部来自 run1.log:
| 组 | N_resp | 占用阈值 | 提前量(空槽) | 接受写入 | 写气泡 | 最大占用 | 溢出 |
|---|---|---|---|---|---|---|---|
| A | 0 | 4 | 12 | 205 | 195 | 6 | 0 |
| A | 0 | 8 | 8 | 209 | 191 | 10 | 0 |
| A | 0 | 12 | 4 | 213 | 187 | 14 | 0 |
| A | 0 | 14 | 2 | 215 | 92 | 16 | 0 |
| A | 0 | 15 | 1 | 215 | 69 | 16 | 24 |
| B | 1 | 8 | 8 | 210 | 190 | 11 | 0 |
| B | 1 | 12 | 4 | 214 | 186 | 15 | 0 |
| B | 1 | 13 | 3 | 215 | 138 | 16 | 0 |
| B | 1 | 14 | 2 | 215 | 115 | 16 | 24 |
这张表可以逐列读出预算公式的全部预言。最大占用列在安全档位严格等于「阈值 + N」:组 A 各档为 6/10/14/16(thresh+2),组 B 为 11/15/16(thresh+3)——停写令到达前进入的数据一项不差,预算模型精确成立。溢出列精确地在突破预算线的档位翻红:组 A 阈值 15 > 14,首次溢出发生在第 32 拍(第一个长读停窗口),之后每个周期重犯一次,共 24 次;组 B 加了一级 skid,阈值 14 也越线,同样 24 次溢出。这两行就是「晚一档毁数据」的直接复现,而不是理论推断。
气泡列讲的是另一半代价:提前量从 1 加到 12,写气泡从 69 单调涨到 195——阈值越早(占用阈值越小),FIFO 在大量时间里被挡在远未满的状态,等效深度严重缩水:阈值 4 时最大占用只有 6,一块深度 16 的 RAM 只用了不到四成。注意临界档(组 A 的 14、组 B 的 13)气泡反而明显低于 12 档,因为它把水位放到 16 才动作,FIFO 真正被用满;零裕量省下的气泡就是「贴边飞行」的租金,时钟频率漂移或激励变化时没有任何缓冲。安全档位的写入数从 205 爬到 215 后封顶,上限由读端 200 个读拍的吞吐决定,再保守的阈值也换不回超过消费能力的带宽——气泡损失集中在排空阶段的瞬时响应,而非宏观吞吐,这与满空语义篇「气泡税只在边界附近征收」的结论一致。
五、设计与思考:阈值的四个工程取舍
阈值过大与过小,买的是两种不同性质的保险。 过小(提前量过多)的账单是容量气泡和等效深度缩水,扫描里 thresh=4 的 FIFO 实际最大占用只有 6;过大(提前量不足)的账单是溢出,24 次/400 拍的速率在真实流量里意味着稳定的数据损坏。两者不对称,所以初版设计建议从「预算上限再留 1~2 拍裕量」起步,即 thresh = DEPTH − N − margin,用后仿或实测的气泡计数回校,而不是一上来就折半深度。
固定阈值还是运行时可配,取决于延迟是否恒定。 N_sync 在定型后是常数,但 N_resp 和 N_burst 常随系统配置变化:同一 FIFO 接到不同上游时 skid 级数不同,AXI 突发长度可配,跨时钟域频率比在多模式时钟方案里会变。这类场景把阈值做成寄存器字段(Xilinx FIFO Generator 的 programmable almost-full/full 就是这个接口),复位后按当前模式写入;代价是比较器从常量比较变成寄存器比较,时序稍差且要防软件配错——建议加一道硬件钳位:写入值超过预算上限时截到上限并可上报告警,把「保守方向」从约定变成结构保证。
与 FWFT 读延迟的关系。 FWFT 模式下读端零延迟取数,aempty 的预取窗口里没有读延迟那一项,阈值可以更贴近 1;标准模式下消费者从决定读到拿到数据有 1 拍结构性延迟,预取窗口必须把这一 beat 的需求算进去。选错模式沿用旧阈值,表现为 FWFT 下多发一次无用预取(浪费带宽),或标准模式下偶发读空断流——排查时容易误判为时序问题,根子在阈值口径。
加深 FIFO 还是提前反压? 预算超支时有两条路:加大 DEPTH 让 thresh 上限抬高,或减小 N(减同步级、把 skid 移出反压环、改组合 ready)。判据是气泡来源:若扫描显示常态占用远离阈值、溢出只在最坏突发出现,加深 FIFO 买的是突发吸收能力,直接有效,深度与突发长度的工程算法见 FIFO 深度计算实测;若常态就顶在阈值附近、气泡数高企,说明两端速率长期失衡,加深 FIFO 只是把拥塞延后,真正该动的是反压环延迟或上游限速。可综合的完整实现(含 almost 标志位)照抄异步 FIFO Verilog 模板。
代码获取:实验平台在独立公开仓库 derekzhuo/async-fifo-mini 的 tb 目录,本篇对应文件为 tb_almost_threshold.v(自包含占用模型 + 两组响应延迟 × 多档阈值扫描,不依赖 rtl 目录)。克隆后执行 iverilog -g2012 -s tb_almost_threshold -o build/x.vvp tb/tb_almost_threshold.v && vvp build/x.vvp 即可复现本文全部 29 行输出;改 N_RESP 与阈值列表可扫描自己系统的预算边界,本机需安装 Icarus Verilog。
实验环境
- 机器/系统:Mac(Intel)/ macOS,本机原生执行(未使用容器)
- 工具版本:Icarus Verilog 13.0(stable)
- 日期:2026-11-03
- 完整命令:
iverilog -g2012 -s tb_almost_threshold -o build/tb_almost_threshold.vvp tb/tb_almost_threshold.v && vvp build/tb_almost_threshold.vvp - 原始记录:本文终端卡内嵌的 29 行输出即完整原始记录(run1.log),实验源码见上述工程包
这一篇在知识体系里的位置
本篇是异步FIFO 专题原理段第六篇,在异步FIFO 满空信号给出的「三拍保守欠账」之上,回答欠账如何用提前量预先付清:满空语义篇管标志误差的方向,本篇管 almost 标志的幅度;格雷码跨时钟域原理与两级触发器同步器模板解释了预算表里 N_sync 为什么固定是两拍,FIFO 指针绕回与扩展位给出占用计数的状态空间,FIFO FWFT 与标准读模式决定读端 aempty 阈值的计数口径。支柱页异步FIFO设计全景串起整条路径。
总结
FIFO almost-full 阈值的本质是给停写令的旅途预留槽位:afull 拉起后要经过两级同步(N_sync=2)、上游 skid 反应(N_resp)与突发预留(N_burst)写入才真正停止,故占用阈值必须满足 afull_thresh ≤ DEPTH−N_sync−N_resp−N_burst,且只许更早不许更晚——早了只产生容量气泡,晚了必然溢出。深度 16、读端周期性完全停顿的 iverilog 扫描实测逐拍坐实预算式:最大占用在安全档恒等于「阈值+N」,N_resp=0 时阈值 14 临界零溢出(最大占用顶到 16)、15 即溢出 24 次,加一级 skid 后预算降到 13、14 同样溢出 24 次;提前量从 1 加大到 12 则写气泡从 69 涨到 195、等效深度缩到 6。almost-empty 是读端的镜像预算,FWFT 下按弹出后语义计数;工程上从预算上限留 1~2 拍裕量起步,延迟随配置变化时用可配阈值加硬件钳位,常态拥塞则优先缩短反压环而非盲目加深 FIFO。