FIFO深度怎么定?用突发流量和背压算清缓存预算

FIFO深度怎么定?用突发流量和背压算清缓存预算

给FIFO填一个"看起来够用"的深度,最容易漏掉两件事:下游连续不读的时间,以及发出停写通知之后仍会到达的数据。本文用一个可复现的占用量模型,把突发峰值、背压尾流和实现约束分开计算。

一 先把"一个数据"说清楚

本文统一以等宽FIFO的一个存储字为单位,每拍最多写一个字、读一个字。若一个字是32位,12字对应48字节有效数据;包尾、通道号等随路信息也需要存储,但不能混入"有效载荷字节数"后直接当深度。

混合位宽时,写一次和读一次未必抵消。例如64位写、16位读,应先按位数或统一的小字单位记账,再换算为IP要求的写深度。不同频率的读写口则要放到同一时间轴,枚举时钟事件或采用有依据的到达上界、服务下界,不能直接减两个时钟域的"拍数"。

记第t拍开始时占用为qt,本拍真正接受的写入为wt,真正完成的出队为rt,则:

qt+1 = qt + wt - rt

注意这里是成功操作。空FIFO遇到读请求,没有数据可出;受背压的写请求,也不能直接记成已经入队。估算无损容量时,先用不受容量限制的模型观察所有应接收数据的峰值,再用候选深度重跑,检查是否拒写。

二 平均带宽够用 仍可能丢突发

考虑32拍观察窗口,初始为空:

  • 第0至15拍每拍到达1字,之后不再到达
  • 第0至7拍下游暂停
  • 第8至15拍只在偶数拍读1字
  • 第16拍起每拍可读1字

平均到达量只有0.5字/拍,窗口内有20次读机会。但前8拍已经堆积8字,接着8拍又净增4字,峰值达到12字。窗口后面的空闲读机会无法提前借来使用。

在本文的读写语义下,深度12无丢失,深度11丢1字。12只是这条流量轨迹的逻辑容量结果,实际IP可能要求更大或特定粒度的配置值,还要确认配置深度对应的可用容量。

如果同样的突发会重复,必须把突发间隔纳入模型,验证下一次开始前能否排空;若不能,初始占用就不再是零。一次突发通过,不等于持续业务通过。

三 停写通知之后 还会来多少字

假设占用到8字时触发停写,而从触发之后算起,数据通路还会送来3字;这期间不能保证下游读取。那么至少要留3字空位,即总容量至少11字。

一般地,若触发瞬间实际占用最多为T+δ,触发之后尚未计入占用、且无法撤回的数据最多为I字,最保守的条件是:

D >= T + δ + I

δ用于覆盖阈值越过或检测延迟造成的额外占用;I覆盖后续反馈延迟和已在途数据。两者要画出计数边界,避免把同一段流水线算两次。只有已知每拍最多到达W字、尾流持续至多L个完整计数拍时,才可用W×L估计对应部分。若控制是"整包接收后才允许停止",剩余包长也必须计入。

可以减去这段时间内有保证的出队量,但必须验证每个前缀时刻的峰值,不能拿整段总读取量抵扣更早出现的峰值。按本文的满时拒写语义,还必须保证每个有写入的拍开始时有空位;同拍读取不能抵扣该拍所需的接收空间。消费者随时可能停住时,安全预算先按零读取处理。

四 almost_full不等于立即停住

阈值信号的名字和时序要查具体IP。例如AMD的XPM_FIFO_ASYNC文档分别列出标准读模式、FWFT模式,以及读操作、写操作引起的标志更新延迟,不能统一套成"两个同步拍"。AMD UG1726 XPM_FIFO_ASYNC

Intel/Altera FIFO手册同样按模式与配置列延迟,并要求DCFIFO写侧依据wrfull、读侧依据rdempty决定操作。异步两侧的计数和状态不能当作一个即时共享计数器。FIFO状态与延迟;FIFO信号定义

因此,跨域状态可见延迟、阈值产生延迟和上游响应延迟,应按实际路径分别分析。某些延迟造成保守停写,另一些决定尾流预算,不应全部机械相加成"通用公式"。异步FIFO的CDC可靠性也不能靠增加容量代替。

五 用可运行模型验证预算

下面代码保存为demo.py,用Python 3执行即可,无第三方依赖。模型约定:空时不直通;满状态在拍开始时判断,因此满时即使同时读,也拒绝当拍写入。普通非空非满状态下,同时读写占用不变。其他实现可能支持满时替换,必须相应修改接收条件。

service表示读取机会,不是已完成读数;lost表示无法暂停的数据源所提供、但未被缓存接收的字数。模型不追踪数据内容和顺序。

python 复制代码
"""Deterministic occupancy model only; not an RTL/IP simulation."""

def run(arrivals, service, depth=None, initial=0):
    assert len(arrivals) == len(service)
    assert initial >= 0 and (depth is None or depth >= initial)
    q, peak, lost, trace = initial, initial, 0, []
    for offered, budget in zip(arrivals, service):
        assert offered >= 0 and budget >= 0
        # No empty bypass; full-at-start cannot accept a write.
        read = min(q, budget)
        write = offered if depth is None else min(offered, depth - q)
        lost += offered - write
        q += write - read
        peak = max(peak, q)
        trace.append(q)
    return peak, lost, q, trace


def stopped_source(threshold, tail, cycles=20):
    # No reads. Trigger after reaching threshold, then tail more writes.
    q, trigger, arrivals = 0, None, []
    for t in range(cycles):
        write = int(trigger is None or t <= trigger + tail)
        arrivals.append(write)
        q += write
        if trigger is None and q >= threshold:
            trigger = t
    return arrivals


a = [int(t < 16) for t in range(32)]
s = [int(t >= 8 and (t >= 16 or t % 2 == 0)) for t in range(32)]
p, lost, end, trace = run(a, s, depth=12)
assert (p, lost, end) == (12, 0, 0)
assert run(a, s, depth=11)[1] == 1
print(f"burst: peak={p}, lost={lost}, end={end}; depth11_lost=1")
print("burst trace:", trace)

p, lost, end, _ = run([1]*100, [int(t % 2 == 0) for t in range(100)], 16)
assert (p, lost, end) == (16, 35, 16)
print(f"sustained: peak={p}, lost={lost}, end={end}")

a = stopped_source(threshold=8, tail=3)
assert run(a, [0]*20, 11)[:3] == (11, 0, 11)
assert run(a, [0]*20, 10)[1] == 1
assert run(stopped_source(8, 0), [0]*20, 8)[1] == 0
print("delayed stop: threshold=8, tail=3, peak=11; D11_lost=0, D10_lost=1")

assert run([], [], 0)[:3] == (0, 0, 0)
assert run([0], [1], 0)[:3] == (0, 0, 0)
assert run([1], [0], 0)[:3] == (0, 1, 0)
assert run([1], [1], 1)[:3] == (1, 0, 1)  # empty: no bypass
assert run([1], [1], 1, initial=1)[:3] == (1, 1, 0)  # full
assert run([1], [1], 2, initial=1)[:3] == (1, 0, 1)  # middle
print("boundaries: zero capacity, empty/full/middle simultaneous, zero tail PASS")
print("ALL CHECKS PASS (Python occupancy model only)")

实际运行输出:

text 复制代码
burst: peak=12, lost=0, end=0; depth11_lost=1
burst trace: [1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0]
sustained: peak=16, lost=35, end=16
delayed stop: threshold=8, tail=3, peak=11; D11_lost=0, D10_lost=1
boundaries: zero capacity, empty/full/middle simultaneous, zero tail PASS
ALL CHECKS PASS (Python occupancy model only)

持续超速负例中,上游100拍连续写,下游隔拍读,深度16仍丢35字。若这种正的长期速率差无限持续,且上游不能暂停、不能丢弃,也没有其他存储出口,任何有限FIFO最终都会装满。加深只能推迟失败时间。

零深度测试只是模型边界检查,不表示厂商IP支持深度零。空、满、中间占用的同时读写测试,则用于防止把"读写抵消"误用到边界。

六 落地时交付四个结果

先给出到达与最差服务的假设,再给出占用峰值;随后给出背压触发点与尾流预算,最后对照目标IP确认可用深度、阈值范围和空满语义。这里的突发案例与停写案例是两个独立例子,不能把12和3直接相加作为最终深度。

若预算已经包含反馈尾流,就不要再重复加一遍。把最长停顿、连续突发、最慢响应、复位后首包和实际时钟比带入目标IP仿真,并检查最终握手与溢出标志。这样得到的深度才有可解释的边界,而不只是一个经验数字。

相关推荐
kyle~1 小时前
软件缓存一致性
缓存
仍然.10 小时前
Redis---缓存
数据库·redis·缓存
骇客野人12 小时前
微服务多级缓存架构设计
缓存·微服务·架构
禾小西17 小时前
05丨Redis 内存快照:宕机后,如何实现快速恢复?
数据库·redis·缓存
一口一口吃成大V18 小时前
Fpga中常用器件
fpga开发
李纲明19 小时前
WordPress 站点变慢:按 TTFB → 缓存 → 查询 → 前端 分层排查(含配置注意)
前端·缓存·性能优化·wordpress·后端开发
我不会插花弄玉20 小时前
4.string类型【由浅入深-redis】
数据库·redis·缓存
Riwuarua20 小时前
从近似0基础开始FPGA开发——Part.23:AXI/AXIS 数据搬运、DMA 与 DDR 缓存架构设计
缓存·fpga开发