XiangShan 跨 Cache Hierarchy 预取 (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 是否直接接通 | L1Prefetcher 和 Berti 内部都有 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 | L1 local req -> l1_pf_arb -> L1 DCache pipeline |
真正连到 L2 的 sideband 是:
1 | L1 PrefetcherWrapper.io.l1_pf_to_l2 |
PrefetchRecv 只携带物理地址和来源:
1 | addr_valid: 这拍有 L1 下放的预取地址 |
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_arb,l1_req.ready 和 l3_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 | l1D_pf_train_on_hit = 1: hit/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 | pf_enableSMS(Constantin) |
这里把 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 | first-issue load |
它只有在观察到稳定 stride 时才发预取:
1 | 新 stride 非 0/1 block |
当前 stride confidence 是 3 bit,CONF_THRESHOLD = 3,MAX_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 | DeltaStatus.L1_PREF -> PrefetchTarget.L1 |
进入 Berti 的训练要求是:
1 | first-issue load |
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 | region tag |
真正发出 L2/L3 hint 前必须满足:
1 | entry valid |
如果同一个 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 | SMS -> Prefetch2L2SMS |
L2 何时接收 L1 hint
L2 只有在配置了 PrefetchReceiverParams 时才有 pf_recv_node。默认 L2 参数包含:
1 | PrefetchReceiver + BOP + TP |
L2 receiver 接收 L1 hint 的门控是:
1 | csr.l2_pf_enable |
其中 l2_pf_enable 是 L2 prefetch master enable,l2_pf_recv_enable 只控制“接收 L1 下放地址”这一路。收到地址后,PrefetchReceiver 不训练、不查 TLB,因为 L1 已经送来 PA;它只做格式转换:
1 | PrefetchRecv(addr, pf_source) |
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 | L2 PrefetchReq |
所以 L2 访问 LLC/L3 的必要条件是:
1 | L2 prefetch request 成功进入对应 slice |
从架构角度看,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_hint。configs/common/CacheConfig.py 随后执行:
1 | dcache.prefetcher.add_pf_downstream(l2_prefetcher) |
因此默认链路是:
1 | L1D XSCompositePrefetcher |
这条链路只传递 prefetcher 内部的 DeferredPacket / hint,不是 cache coherence request,也不是立即填入下层 cache 的数据通路。真正发包仍发生在目标 host 层级的 PFQ 出队阶段。
pf-ahead 的统一数据结构
src/mem/cache/prefetch/queued.hh 中的 Queued::AddrPriority 扩展了普通预取候选:
1 | int pfahead_host = 0; |
Queued::insert() 会把这些字段复制到 DeferredPacket:
1 | dpp.pfahead = addr_prio.pfahead; |
约定如下:
| 字段 | 含义 |
|---|---|
pfahead=false |
普通本层预取候选 |
pfahead=true, pfahead_host=2 |
这笔预取应该由 L2 host |
pfahead=true, pfahead_host=3 |
这笔预取应该由 L3 host |
pfSource |
预测来源,例如 SStream、SPht、SStride、HWP_BOP |
depth |
预取深度/距离元数据,随 request metadata 继续保留 |
这里的 host 是“最终在哪一级 cache 发出 prefetch packet”,不是“从哪一级发起预测”。L1 可以预测一个 host=3 的地址,但它不会自己向 L3 发 packet,而是把候选逐级交给 L2,再由 L2 交给 L3。
逐级转发规则
核心分流点是 src/mem/cache/prefetch/queued.cc 的 Queued::addToQueue():
1 | if (hasHintDownStream() && dpp.pfahead && |
| 当前层 | 候选 | 有 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 | addresses.back().pfahead_host = ahead_level; |
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 | L1 miss |
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 默认是 False;kmh_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 默认是 False,kmh_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 路径里,largeBOP、smallBOP、learnedBOP 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_l1、berti、sms_pfFilter、cmc、BOP(level=1)、spp、ipcp、Opt |
| 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=2。GetPFAddrL3() 同理只取 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 | L1 预测 host=3 |
最终效果是“数据提前进入 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 / L2CompositeWithWorkerPrefetcher 的 rxHint() |
需要注意两点差异:
| 差异 | 说明 |
|---|---|
| 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 | L1 XSCompositePrefetcher 生成 host=3 pfahead |