xs-pf-ahead

XiangShan RTL 实现

RTL 里的 pf-ahead 是由 目标层级 sink + 旁路接收器 + L2 Hint task 组合出来的机制。L1 预取器在预测时决定这批地址应该落在 L1、L2 还是更远层级;真正跨层时,当前这棵 RTL 主要实现的是 L1 → L2 的 sideband hint,L2 接收后再在 L2 内部发起普通 prefetch Hint 访问。

当前 RTL 的关键结论

问题 结论
L1 用什么表示 ahead 目标 SINK_L1 / SINK_L2 / SINK_L3,保存在 L1 多级预取过滤表 entry 里
L1 真正导出的跨层通道 l1_pf_to_l2: PrefetchRecv,字段是 addr_valid / addr / pf_source
L1 → L3 是否直接接通 L1PrefetcherBerti 内部都有 l3_req 端口,但当前 MemBlock 没有把 l3_pf_req 导出到 LLC;wrapper 中 L3 arbiter 的输出被流水线消费并置 ready,因此当前 RTL 不能按“L1 直达 L3 sideband”理解
L2 是否把 L1 hint 再转成 L3 hint 没有看到独立的 L2→L3 PrefetchRecv 链路;L2 是把收到的 L1 hint host 在 L2,若该 Hint 在 L2 miss,则通过正常 MSHR/下行 cache 访问把数据取到 L2,途中会访问 LLC/L3
L3 / LLC 的角色 当前 openLLC 配置没有单独的 prefetch receiver;它被 L2 miss/request 驱动,不像 XS-GEM5 那样默认有一个 L3 worker prefetcher

因此 RTL 和 XS-GEM5 的抽象不同:RTL 当前更像“L1 可以提前告诉 L2 取某个 PA;L2 预取 miss 自然访问 LLC”,而 XS-GEM5 是“L1/L2/L3 prefetcher 对象逐级转交一个 host=2/3 的候选”。

跨层数据通路

L1 侧预取器统一被 PrefetcherWrapper 包起来。每个子预取器最多有三类输出:

1
2
3
L1 local req -> l1_pf_arb -> L1 DCache pipeline
L2 hint -> l2_pf_arb -> l1_pf_to_l2(PrefetchRecv) -> L2 PrefetchReceiver
L3 hint -> l3_pf_arb -> 当前 RTL 未导出到 LLC

真正连到 L2 的 sideband 是:

1
2
3
4
5
L1 PrefetcherWrapper.io.l1_pf_to_l2
-> MemBlock.l2_pf_sender_opt(BundleBridgeSource)
-> XSTile: l2.pf_recv_node := memBlock.l2_pf_sender_opt
-> CoupledL2.prefetcher.io.recv_addr
-> L2 PrefetchReceiver

PrefetchRecv 只携带物理地址和来源:

1
2
3
addr_valid: 这拍有 L1 下放的预取地址
addr: 已翻译、已通过 PMP/PBMT/pmem 过滤的 PA
pf_source: MemReqSource.Prefetch2L2SMS/Stream/Stride/Berti

L2 接收后不会把它当作 TileLink A 通道上游请求;PrefetchReceiver 会把它转成 L2 内部 PrefetchReq,再进入 L2 prefetch queue,最后由 SinkA 改写成 opcode=Hint 的 L2 task。

L1 哪些预取器会下放到 L2/L3

默认 core 参数里 L1D 预取器序列是:

L1 预取器 L2 hint L3 hint 说明
SMSPrefetcher 会发 不发 wrapper 把 SMS 只接到 l2_pf_arbl1_req.readyl3_req.ready 都被钉住
L1Prefetcher 内的 Stream 会发 内部可生成,但默认关闭且当前未导出到 LLC Stream 同时生成 L1/L2/L3 三个 sink;L3 还受 enableL3StreamPrefetch=false 默认门控
L1Prefetcher 内的 Stride 会发 不发 Stride 只生成 L1 和 L2 两个深度
BertiPrefetcher 会发 内部可生成,但当前未导出到 LLC Berti 的 learned delta status 可映射到 L1/L2/L3 target;实际跨层导出仍只有 L2

SMS:只作为 L1 → L2 hint 来源

SMS 的训练输入来自 load/store 训练流,但会过滤掉硬件预取本身:

1
train valid = source.valid && !source.bits.isHwPrefetch

是否只在 miss 上训练由 l1D_pf_train_on_hit 控制:

1
2
l1D_pf_train_on_hit = 1: hit/miss 都可训练
l1D_pf_train_on_hit = 0: 只接受 first-issue miss

SMS 产生候选的核心来源包括 AGT、PHT 和内部 stride 路径;只要 pf_filter 给出已翻译的 l2_pf_addr,并且 SMS 处于 enable 状态,就发到 L2:

1
SMS candidate -> SMS pf_filter -> io.l2_req(valid, addr, source=Prefetch2L2SMS)

SMS 的使能条件更严格:

1
2
3
4
pf_enableSMS(Constantin)
&& csr.l1D_pf_enable
&& csr.l2_pf_recv_enable
&& (AGT/PHT 对应子开关)

这里把 l2_pf_recv_enable 放进 SMS enable 是一个架构选择:SMS 本身就是 L2 ahead 来源,如果 L2 不接收 L1 hint,SMS 没有本地 L1 fallback。

Stream:active region 后向 L2 发更远 region,L3 默认不生效

Stream 使用 region bit-vector 判断连续/空间局部性。训练入口接受 first-issue 且非硬件预取的 load;真正触发 stream 更新/预取的访问是:L1 miss 或命中 Stream 曾经预取进来的 cache line

一个 region 变 active 的依据是:同一 region 访问计数达到 ACTIVE_THRESHOLD 或相邻 region 已经 active

当前参数下 REGION_SIZE=1024B,64B line 时一个 region 有 16 个 block,ACTIVE_THRESHOLD = 16 - 4 = 12。当访问进入 active region 且当前 block 是新访问位时,Stream 同时构造多个目标:

目标 sink 默认距离 默认宽度 是否跨层有效
L1 SINK_L1 64 个 cache block 2 line 进入 L1 DCache
L2 SINK_L2 640 个 cache block 4 line 通过 l1_pf_to_l2 发给 L2
L3 SINK_L3 640 个 cache block 8 line enableL3StreamPrefetch 默认 false,且当前未导出到 LLC

Stream 的 L2/L3 请求和 Stride 共用 MutiLevelPrefetchFilter 的 L2/L3 bank。它不会直接把一串地址无条件打出去,而是先写入 region bit-vector,由过滤表按“尚未发送的 bit”逐个发出。

Stride:稳定 stride 后向 L1/L2 双深度预取

Stride 按 PC hash 建表,训练条件是:

1
2
3
first-issue load
&& !isHwPrefetch
&& (L1 miss || hit on Stride-prefetched line)

它只有在观察到稳定 stride 时才发预取:

1
2
3
新 stride 非 0/1 block
&& 新 stride 与表中 stride 方向和大小一致
&& confidence >= CONF_THRESHOLD

当前 stride confidence 是 3 bit,CONF_THRESHOLD = 3MAX_CONF = 7。满足条件后生成两个目标:

目标 sink 距离 说明
L1 SINK_L1 stride << l1_stride_ratio,默认 stride * 4 近距离
L2 SINK_L2 stride << l2_stride_ratio,默认 stride * 32 远距离 pf-ahead

Stride 不生成 SINK_L3。如果同一 region 里 Stream 已经覆盖,代码保留了 LOOK_UP_STREAM 过滤接口,但当前常量默认关闭。

Berti:有 L1/L2/L3 target,但当前跨层只接通到 L2

Berti 根据 PC 关联的 delta 学习结果决定 target:

1
2
3
DeltaStatus.L1_PREF -> PrefetchTarget.L1
DeltaStatus.L2_PREF / L2_PREF_REPL -> PrefetchTarget.L2
其他可预取状态 -> PrefetchTarget.L3

进入 Berti 的训练要求是:

1
2
3
4
first-issue load
&& !isHwPrefetch
&& (L1 miss || hit on Berti-prefetched line)
&& csr.berti_enable

Berti buffer 会做 TLB/PMP/pmem 检查,得到 PA 后按 target 发到 L1/L2/L3 端口。需要注意的是,PrefetcherWrapper 虽然接了 Berti 的 l3_req,但当前顶层没有 L1→LLC 的 sender,所以 Berti 的 L3 target 在这棵 RTL 中不是一个已接通的跨层 hint。

L1 过滤、翻译和仲裁决策

Stream/Stride 的候选不会直接发到 L2,而是先进入 MutiLevelPrefetchFilter

这个过滤器有两个资源池:

资源池 容量 处理目标
L1 pool 16 entries SINK_L1
L2/L3 pool 16 entries SINK_L2 / SINK_L3

每个 entry 以 region 为粒度保存:

1
2
3
4
5
6
region tag
bit_vec: 这个 region 内哪些 line 值得预取
sent_vec: 哪些 line 已经发过
sink: 目标层级
source: Stream / Stride
is_vaddr: 是否还停留在 VA 形式

真正发出 L2/L3 hint 前必须满足:

1
2
3
4
5
6
7
8
9
entry valid
&& 已经完成 VA -> PA 翻译
&& TLB 未 miss
&& 无 page/access fault
&& 非 uncache/MMIO
&& PMP 允许 load
&& PA 落在 PmemRanges
&& bit_vec 里还有未发送的 line
&& L1D prefetcher enable

如果同一个 region 同时被不同目标层级请求,过滤器采用“更近层级优先”:

1
SINK_L1 < SINK_L2 < SINK_L3

也就是说,已经准备下放到 L3 的 region,如果后续出现 L2/L1 目标,会被改写成更近目标;反过来,已经归类为 L2 的 region 不会因为后来出现 L3 请求而升级到 L3。这避免了同一 region 被更远层级过早拿走。

L1 wrapper 里 L2 输出的仲裁来自所有 L1 子预取器:

1
SMS / StreamStride / Berti -> l2_pf_arb -> Pipeline(L2_PF_REG_CNT) -> l1_pf_to_l2

输出的 pf_source 会保留来源:

1
2
3
4
SMS    -> Prefetch2L2SMS
Stream -> Prefetch2L2Stream
Stride -> Prefetch2L2Stride
Berti -> Prefetch2L2Berti

L2 何时接收 L1 hint

L2 只有在配置了 PrefetchReceiverParams 时才有 pf_recv_node。默认 L2 参数包含:

1
PrefetchReceiver + BOP + TP

L2 receiver 接收 L1 hint 的门控是:

1
2
3
csr.l2_pf_enable
&& csr.l2_pf_recv_enable
&& Constantin("pfRcv_enable")

其中 l2_pf_enable 是 L2 prefetch master enable,l2_pf_recv_enable 只控制“接收 L1 下放地址”这一路。收到地址后,PrefetchReceiver 不训练、不查 TLB,因为 L1 已经送来 PA;它只做格式转换:

1
2
3
PrefetchRecv(addr, pf_source)
-> PrefetchReq(tag, set, pfSource, needT=false)
-> L2 prefetch queue

L2 prefetcher 内部的仲裁优先级是:

1
Receiver(L1 hint) > NL > VBOP > PBOP > TP

因此,当 L1 下放 hint 和 L2 本地 BOP/TP 同时产生请求时,receiver 路径优先进入 L2 prefetch queue。

L2 何时向 L3/LLC 取数

当前 RTL 中没有独立的 L2→L3 PrefetchRecv sideband。L2 的“向 L3 下放”发生在更低一层的正常 cache 访问路径上:

1
2
3
4
5
6
7
L2 PrefetchReq
-> SinkA 转成 Hint task
-> MainPipe 查 L2 directory
-> L2 hit: 不需要向下
-> L2 miss: Hint 被当作 acquire_on_miss,分配 MSHR
-> MSHR 通过 L2 下行端口访问 LLC/L3/内存
-> 返回后以 prefetch metadata 写入 L2

所以 L2 访问 LLC/L3 的必要条件是:

1
2
3
4
L2 prefetch request 成功进入对应 slice
&& 没有被 RequestBuffer/MSHR 同地址去重丢弃
&& L2 directory miss,或需要更高权限/别名处理导致需要 MSHR
&& MSHR/下行通道有资源

从架构角度看,RTL 的 L2 不会检查“这笔 L1 hint 的目标是 L3”再继续转发;L1 送到 L2 的 Prefetch2L2* 来源都被 L2 host。只有当这笔 L2 prefetch 在 L2 miss 时,才自然访问 LLC/L3,并最终把数据填回 L2。

L2 本地预取器也遵循同样路径:

来源 触发条件概括 向 LLC/L3 的条件
BOP/PBOP L2 训练请求,不接受 L1DataPrefetch 作为 BOP/PBOP 训练来源 产生的 L2 Hint 在 L2 miss
TP 基于 L2 miss/prefetch-hit 序列训练,受 l2_tp_en 控制 TP 重放请求在 L2 miss
NL 可选,受配置是否包含 NLParameters 控制 NL 请求在 L2 miss
Receiver L1 sideband hint,受 l2_pf_recv_enable 控制 L1 hint 在 L2 miss

和 XS-GEM5 的对应关系

RTL XS-GEM5
SINK_L2 pfahead_host=2
SINK_L3 pfahead_host=3,但 RTL 当前没有接通 L1/L2 到 L3 worker 的等价 receiver
L1 PrefetchRecv sideband prefetcher object 的 rxHint()
L2 PrefetchReceiver host L1 hint L2 worker 收到 L1 hint 后 host
L2 prefetch miss 访问 LLC 目标层 PFQ 发包后访问下层 cache

最容易混淆的是 L3:XS-GEM5 文档里可以说 L1 生成 host=3,L2 再转给 L3 worker;但当前 RTL 源码里没有这个逐级转发对象链。RTL 的已接通主路径是 L1 让 L2 提前取,L2 miss 时由正常 cache 层级把请求送到 LLC。

XS-GEM5 实现

XS-GEM5 里的 pf-ahead 不是 RTL 中的 BundleBridge,而是在 gem5 预取器对象之间建立一条 C++ 层面的 hint downstream:上层预取器把一个已经构造好的 prefetch candidate 交给下层预取器的 rxHint(),下层再决定继续转发还是在本层 PFQ 中发包。

默认层级和连接

默认选型来自 configs/common/Options.py

层级 默认 prefetcher 角色
L1D XSCompositePrefetcher 生成 L1 本地预取,也生成 host=2/3 的 pf-ahead 候选
L2 wrapper L2CompositeWithWorkerPrefetcher 接收 L1 hint,host L2 ahead,继续下传 L3 ahead,同时运行 L2 本地算法
L3 WorkerPrefetcher 默认只接收并 host 下传 hint,不主动生成复杂预取

configs/common/xiangshan.py 在常规 XiangShan 配置中会打开 l1_to_l2_pf_hint;如果没有关闭 L3,也会打开 l2_to_l3_pf_hintconfigs/common/CacheConfig.py 随后执行:

1
2
dcache.prefetcher.add_pf_downstream(l2_prefetcher)
l2_prefetcher.add_pf_downstream(system.l3.prefetcher)

因此默认链路是:

1
2
3
L1D XSCompositePrefetcher
-> L2CompositeWithWorkerPrefetcher
-> L3 WorkerPrefetcher

这条链路只传递 prefetcher 内部的 DeferredPacket / hint,不是 cache coherence request,也不是立即填入下层 cache 的数据通路。真正发包仍发生在目标 host 层级的 PFQ 出队阶段。

pf-ahead 的统一数据结构

src/mem/cache/prefetch/queued.hh 中的 Queued::AddrPriority 扩展了普通预取候选:

1
2
3
4
int pfahead_host = 0;
bool pfahead = false;
int depth = 0;
PrefetchSourceType pfSource;

Queued::insert() 会把这些字段复制到 DeferredPacket

1
2
dpp.pfahead = addr_prio.pfahead;
dpp.pfahead_host = addr_prio.pfahead_host;

约定如下:

字段 含义
pfahead=false 普通本层预取候选
pfahead=true, pfahead_host=2 这笔预取应该由 L2 host
pfahead=true, pfahead_host=3 这笔预取应该由 L3 host
pfSource 预测来源,例如 SStreamSPhtSStrideHWP_BOP
depth 预取深度/距离元数据,随 request metadata 继续保留

这里的 host 是“最终在哪一级 cache 发出 prefetch packet”,不是“从哪一级发起预测”。L1 可以预测一个 host=3 的地址,但它不会自己向 L3 发 packet,而是把候选逐级交给 L2,再由 L2 交给 L3。

逐级转发规则

核心分流点是 src/mem/cache/prefetch/queued.ccQueued::addToQueue()

1
2
3
4
5
6
if (hasHintDownStream() && dpp.pfahead &&
(dpp.pfahead_host > cache->level())) {
hintDownStream->rxHint(&dpp);
prefetchStats.pfaheadOffloaded++;
return;
}
当前层 候选 有 downstream 时的行为 没有 downstream 时的行为
L1 host=2 交给 L2 rxHint() 留在 L1 PFQ,本层处理
L1 host=3 先交给 L2 rxHint() 留在 L1 PFQ,本层处理
L2 host=2 host <= level,进入 L2 PFQ 进入 L2 PFQ
L2 host=3 交给 L3 rxHint() 留在 L2 PFQ,本层处理
L3 host=3 host <= level,进入 L3 PFQ 进入 L3 PFQ

pf-ahead 是逐级接力,不是跨级直达。L1 产生 pfahead_host=3 时,必须先经过 L2 worker;只有 L2 也接了 L3 downstream,才会继续到 L3。代码没有强制“host=3 不能被 L2/L1 本地化”的硬检查,链路缺失时会退化为当前层 PFQ 处理。

当目标层真正 host 该候选并从 PFQ 出队时,Queued::getPacket() 会统计 pfaheadProcess,之后按普通硬件预取一样发 packet。

Worker 如何接收 hint

WorkerPrefetcher::rxHint() 把上游传来的 DeferredPacket 复制进本地 localBuffer,再由 transfer() 每次最多搬运 depth 个到本层 PFQ。默认 depth=4

对 stream 来源还有一个关键过滤规则:如果来源是 SStream,只有在“这笔请求已经到达目标 host 层”时才查本层 pfLRUFilter

1
(ptr->pfahead ? (ptr->pfahead_host <= cache->level()) : true)

因此 L2 收到 host=3 的 stream ahead 时,不会因为 L2 的本地 stream LRU 过滤提前丢掉它;它进入 L2 localBuffer 后,transfer() 再调用 addToQueue(),随后按统一规则继续下传到 L3。

L2CompositeWithWorkerPrefetcher::rxHint() 还带一个和 pf-ahead 不完全相同的自适应下推逻辑:当 offloadLowAccuracy 打开、CDP 占比高于 0.5、且该来源准确率低于 0.5 时,它可以把 hint 直接转给 L3 downstream。这是低准确率 offload 策略,复用同一个 rxHint() 通道,但不是由 pfahead_host 决定的默认 pf-ahead 规则。

L1 什么时候 ahead 到 L2 / L3

L1D 默认的 XSCompositePrefetcher 是主入口。它只对“非写请求且带 PC”的访问训练,之后不同子策略决定目标层级。最终只要调用 sendPFWithFilter(..., ahead_level)ahead_level > 1,就会设置:

1
2
addresses.back().pfahead_host = ahead_level;
addresses.back().pfahead = true;

ahead_level=1 是 L1 本地预取;ahead_level=2 是 L1 ahead 到 L2;ahead_level=3 是 L1 ahead 到 L3。

Active-page stream:默认 L1 同时生成 L2/L3 ahead

相关参数默认打开:

参数 默认值 含义
enable_activepage True 启用 active-page stream
stream_pf_ahead True active page 进入新 region 时向更低层提前预取
region_size 64 * 16 默认一个 region 是 16 个 64B block

触发条件来自 XSCompositePrefetcher::calculatePrefetch()

条件 作用
ACT 命中或相邻 active page 命中 识别当前 PC/region 处于 active stream
is_active_page && enableActivepage 先产生 L1 本地 stream 预取
enter_new_region && streamPFAhead 再额外产生 L2/L3 ahead

地址策略是按方向 decr 前后对称的:

目标 代码中的目标基址 host 结果
L1 block_addr ± 16 * blkSize 1 预取较近的下一段 stream region
L2 L1_target ± 48 * blkSize 2 约等于当前块前/后 64 个 block 的 region
L3 L1_target ± 256 * blkSize 3 约等于当前块前/后 272 个 block 的 region

sendStreamPF() 会对目标 region 内的 regionBlks 个 block 逐个调用 sendPFWithFilter()。在默认 region_size=64*16 时,一次 active-page stream 目标 region 最多覆盖 16 个 cache line。L2/L3 目标还会分别进入 pfPageLRUFilterL2 / pfPageLRUFilterL3,避免同一远端 page/region 被反复 ahead。

结论:默认配置下,L1 ahead 到 L3 主要来自 active-page stream,而且只在 active stream 跨入新 region 时生成;普通非 active stream 访问不会直接产生 L3 ahead。

SMS/PHT:默认 ahead 到 L2,可配置到 L3

XSCompositePrefetcher 的 SMS PHT 默认启用:

参数 默认值 含义
enable_pht True 启用 SMS pattern history table
pht_pf_level 2 PHT 预测默认 host 到 L2
pht_pf_ahead True 如果有 stride lookahead 地址,用 lookahead 地址查 PHT

PHT 查表条件是:

1
2
3
4
L1 miss
或 eligible prefetch-first-hit,来源属于 SStride / HWP_BOP / SPht / IPCP_CPLX / SPP / Berti 等
并且不是 store
并且 enable_pht=True

PHT 命中后会根据当前 region、递增 region、递减 region 的历史 bit pattern 生成候选。由于默认 pht_pf_level=2,这些候选会被 sendPFWithFilter(..., SPht, 2) 或后续 sms_pfFilter.GetPFAddrL2() 标成:

1
pfahead=true, pfahead_host=2, pfSource=SPht

如果通过 --pht-pf-level=3 或参数覆盖把 pht_pf_level 改为 3,PHT 候选会变成 L3 ahead。默认不是这样。

XSStride:默认未启用;启用后主要 ahead 到 L2

XSCompositePrefetcher 里有 sstride 子预取器,但 enable_sstride 默认是 Falsekmh_align 配置会打开它。

启用且 use_xs_depth=True 时,XSStridePrefetcher 在 stride 置信度达到阈值后同时生成两个深度:

目标 地址策略 host
L1 lookupAddr + (stride << 2) 1
L2 lookupAddr + (stride << 5) 2

也就是 L1 取较近的 4 * stride,L2 取更远的 32 * stride。当前 XSStridePrefetcher 路径没有默认生成 L3 stride ahead;虽然 sendPFWithFilter() 能编码 ahead_level=3,但常规 stride 生成点只使用 1/2 两级。

XsStream 子预取器:默认未启用;L3 还需要额外开关

xsstream 是另一个可选 stream 子预取器,enable_xsstream 默认是 Falsekmh_align 会打开它。它自己的 active 判定是:region 访问计数超过 ACTIVETHRESHOLD=12,或相邻 region 已经 active。

启用后,它的策略是:

目标 地址策略 一次发射数量
L1 block_addr ± depth * blkSize L1BLKDEGREE=2
L2 block_addr ± (depth << 2) * blkSize L2BLKDEGREE=4
L3 block_addr ± (depth << 3) * blkSize L3BLKDEGREE=8

enable_l3_stream_pre 默认是 False,所以这个子预取器默认只产生 L1/L2 两级;只有显式打开 enable_l3_stream_pre 后才产生 host=3 的 L3 stream ahead。

BOP:L1 默认关闭;PF buffer 模式下可 ahead 到 L2

PrefetcherConfig.py 在创建 XSCompositePrefetcher 时把 enable_bop=False,所以 L1 BOP 不是默认 ahead 来源。

如果重新打开 L1 BOP,并且 bop_pf_level=2,则在 PF buffer 路径里,largeBOPsmallBOPlearnedBOP buffer 中取出的候选会被改写为:

1
pfahead=true, pfahead_host=2

这表示 L1 BOP 的候选由 L2 host。代码中没有默认把 L1 BOP 候选 host 到 L3。

PF buffer 模式下的分层发射

默认 enable_pf_buffer=False,候选会在 calculatePrefetch() 后直接进入 insert() / addToQueue()

打开 --enable-pf-buffer 后,XS-GEM5 更像一个分层发射队列。XSCompositePrefetcher::GetPFRequestsFromBuffer() 每次最多取出:

类别 每次最多 来源优先级
L1 候选 1 个 stridestream_pfFilter_l1bertisms_pfFiltercmc、BOP(level=1)、sppipcpOpt
L2 候选 1 个 stridestream_pfFilter_l2l3.GetPFAddrL2()sms_pfFilter.GetPFAddrL2()、BOP(level=2)
L3 候选 1 个 stridestream_pfFilter_l2l3.GetPFAddrL3()sms_pfFilter.GetPFAddrL3()

PrefetchFilter::GetPFAddrL2() 只取 PFlevel == 2 且物理地址有效的 entry,并设置 pfahead_host=2GetPFAddrL3() 同理只取 PFlevel == 3,并设置 pfahead_host=3

这个模式的意义是限制 L1 一次训练生成的大量远距离候选:每轮最多放出一个 L1、一个 L2、一个 L3,避免远端 ahead 把本地 PFQ 或下游 hint 通道瞬间灌满。

L2 如何处理来自 L1 的 ahead

L2 wrapper 的 L2CompositeWithWorkerPrefetcher 同时有两种身份:

身份 行为
worker 接收 L1 的 rxHint(),把 host=2 的候选放入 L2 PFQ,把 host=3 的候选继续转给 L3
L2 本地预取器 基于 L2 看到的访问训练 CDP、BOP、CMC、DespacitoStream 等算法

L1 传来的 host=2 ahead 到达 L2 后不再下传,因为 pfahead_host > cache->level() 为假。它进入 L2 PFQ 后由 L2 cache 发 packet,命中/缺失处理和普通 L2 预取一致。

L1 传来的 host=3 ahead 到达 L2 后,如果 L2 接了 L3 downstream,会被 addToQueue() 继续转发给 L3;如果没有接 L3 downstream,就会退化为 L2 本地 PFQ 处理。

L2 自身算法生成的普通预取通常不是 L1 pf-ahead 的一部分。它们可以在 L2 本层发包,也可以通过 CDP 或低准确率 offload 逻辑下推,但那属于 L2 自身策略,不是 L1 通过 pfahead_host 指定的跨层 host。

L3 的角色

默认 --l3-hwp-type=WorkerPrefetcher。因此 L3 默认不做复杂模式识别,只做三件事:

步骤 行为
接收 rxHint() 从 L2 收到 DeferredPacket
缓冲 放入 localBuffer,再搬到 L3 PFQ
发包 从 L3 PFQ 出队,按普通 prefetch packet 访问 L3/下游

如果改成 L3CompositeWithWorkerPrefetcher 或其他 L3 prefetcher,L3 可以有本地算法;但这不是当前默认 XiangShan 配置。

地址转换、过滤和发包语义

pf-ahead 只改变“候选由哪一级 cache host”,不改变地址合法性、TLB、队列和发包规则。

Queued::insert() 仍然执行普通预取检查:

检查 行为
queue filter 已在 PFQ / translation queue 中的地址会去重或更新优先级
cache snoop 如果目标 PA 已在 cache/MSHR 中,可丢弃冗余预取
system address 非内存地址会丢弃
同页预取 用原请求 PA 加减 stride 得到目标 PA
跨页预取 需要 TLB 和 context;否则丢弃或进入 translation queue

到达目标 host 层级后,pfahead packet 仍从该层 PFQ 正常出队。它不会绕过 cache 忙闲、MSHR、slice 路由、tag port 或队列容量限制。

因此:

1
2
3
4
5
6
L1 预测 host=3
-> L2 rxHint / localBuffer
-> L2 addToQueue 发现 host=3 > level=2
-> L3 rxHint / localBuffer
-> L3 PFQ
-> L3 发普通 prefetch packet

最终效果是“数据提前进入 L3”,不是“L1 直接把数据拉到 L1”。如果后续 demand 访问继续向下 miss,才会从更近的下层 cache 受益。

和 RTL 描述的对应关系

RTL XiangShan XS-GEM5
L1 通过 sideband 向 L2/L3 发送预取 hint L1 prefetcher 通过 hintDownStream->rxHint() 发送 DeferredPacket
SINK_L2 / SINK_L3 pfahead_host=2/3
L1 Stream 远距离 L3 预取 默认 active-page stream 在 enter-new-region 时生成 host=3
L1 Stride L1/L2 双深度 XSStridePrefetcher 启用时生成 stride<<2 到 L1、stride<<5 到 L2
SMS 默认偏 L2 PHT 默认 pht_pf_level=2,host 到 L2;可配置为 3
下层接收器 WorkerPrefetcher / L2CompositeWithWorkerPrefetcherrxHint()

需要注意两点差异:

差异 说明
L3 来源标记 XS-GEM5 的 pfSource 会随 DeferredPacket 保留;RTL L3 receiver 曾有来源硬编码/缺失的简化
直达性 RTL 里可以描述成 L1 sideband 到 L3;XS-GEM5 实际是 L1 -> L2 -> L3 的 prefetcher object 链路

观察和调试指标

排查 pf-ahead 链路时,优先看这些统计:

统计 含义
pfaheadOffloaded 当前层因为 host > level 把候选交给 downstream 的次数
pfaheadProcess 当前层实际 host 并从 PFQ 发出 pfahead packet 的次数
hintsReceived worker 收到上游 hint 的次数
pfIssued_srcs[...] 各来源最终发出的预取数
PrefetchFilter.l2Issued/l3Issued PF buffer/filter 中 L2/L3 ahead 候选实际取出的次数

健康的默认 L1 -> L3 active-page stream ahead 通路应该表现为:

1
2
3
4
5
6
L1 XSCompositePrefetcher 生成 host=3 pfahead
-> L1 pfaheadOffloaded 增加
-> L2 worker hintsReceived 增加
-> L2 pfaheadOffloaded 增加
-> L3 worker hintsReceived 增加
-> L3 pfaheadProcess / pfIssued 增加