xs-gem5 的 SMT 实现

commit: b367bd0875849935c3cd0652ec01781e2f98e996

当前 xs-gem5 的 SMT 是面向解耦前端的细粒度多线程模型

  • 每个线程拥有独立的架构状态、rename map、前端取指状态、FTQ 队列实例、LSQ 实例和 ROB 链
  • 物理寄存器文件/free list、ROB、IQ/scheduler、执行单元、cache 端口、流水线带宽及预测器表结构是 CPU 级共享资源

实现的核心策略:各级先为每个 tid 计算阻塞原因和资源压力,再由 SmtActiveThreadArbiter 在同一周期只放行一个可推进的线程

  • 所以严格意义上属于细粒度多线程,不属于同时多线程 SMT
  • 其优先级会偏向资源占用较少的线程
  • 在另一个线程发生可让出的 ROB 头阻塞或高 LSQ 压力时冻结后者

因此同时建模了共享瓶颈、反压传播和一种后端阻塞时的资源借用机制

configs/example/smt_idealkmhv3.py 进一步启用了共享 LSQ、DynamicBorrowing ROB 和共享 FTQ(但 FTQ 按活动线程分区)

配置

Enable Config

configs/common/xiangshan.pyargs.smt 为真时设置 system.multi_thread = True,并将每个 CPU 的 numThreads 固定为 2

O3 的编译期常量 MaxThreads = 4 是硬上限;Fetch 也会在运行时拒绝超过该上限的配置

FS 模式下,O3 为 numThreads 个硬件上下文建立 ThreadState;共享同一个 workload/system image,但 CPU 必须提供每个硬件线程各自的架构状态

参数默认值与 Ideal SMT 配置覆盖值

项目 BaseO3 默认值 smt_idealkmhv3.py 覆盖 语义
每周期 fetch 的线程数 1 未覆盖 smtNumFetchingThreads 次调用 fetch;默认只对一个线程取指
fetch policy RoundRobin 未覆盖 当前解耦前端的实际选择使用 decodeScheduler
ROB policy Partitioned DynamicBorrowing 默认均分;Ideal 配置支持带 donor/base 状态的动态借用
LSQ mode / policy Independent / Partitioned Shared / Dynamic 默认每线程独立容量;Ideal 配置让所有活动线程竞争一个总容量池
IQ policy Partitioned 未覆盖 参数声明存在;资源实际由共享 scheduler 管理
commit policy RoundRobin 未覆盖 枚举可切为 OldestReady,但当前 commitInsts() 未调用该策略选择函数
FTQ mode / policy Independent / Partitioned Shared / Partitioned 默认每线程各有 ftq_size 上限;Ideal 配置改为总容量池并给线程分区上限

状态和资源划分

类别 线程独立状态 共享状态/结构
BPU threads[tid] 前端流水状态、history manager Decoupled BPU 、BTB/RAS/TAGE/ITTAGE 等预测表
FTQ FTQ 的每 tid queue ftq_size 总容量
Fetch/Decode PC、decoder、fetch buffer、fetch queue、thread status、redirect/throttle 计数 I-cache port、fetch/decode 宽度、Fetch/Decode 输出带宽
Rename ThreadStateThreadContext、ISA 指针、rename/commit rename map PhysRegFileUnifiedFreeListScoreboard
Rename / IEW / IQ 每线程 fixed buffer、stall reason、MemDepUnit scheduler/IQ、依赖图、执行/写回带宽
ROB 每个 tid 的 ROB entry list、head/tail/已用量 numROBEntries 的总存储,及共享的 policy
LSQ tid 一个 LSQUnit,各自的 LQ/SQ/RARQ/RAWQ 队列语义 Shared 模式下的总配额、store buffer/缓存接口
Commit commit 状态、每线程 ROB head / trap / squash commit width、全局序号、commit 仲裁

Frontend:预测、取指与 Decode 选择

Decoupled BPU

每个线程独立状态:

  1. threads[tid] 前端流水状态
  2. history manager 等各自线程的分支历史状态 (GHR hash 等)

所有线程共享状态/结构:
BPU 内部的 BTB/RAS/TAGE/ITTAGE 等预测表

BPU 预测仲裁逻辑:选择性 RoundRobin, 每个周期只对单个线程预测 (in scheduleThread()):

  1. nextPredictTid 开始轮询,跳过 Inactive、正在 squash/redirect、已有有效预测或 FTQ 已满的线程
  2. 成功选择后推进轮询指针, 建立预测/FTQ 项

FTQ

所有线程共享状态/结构:FTQ 总容量,每个 queue entry 按 tid 标识区分开

FTQ 按线程 tid 保存 queue entry, 但所有线程共享 FTQ 的总容量, 只是每个线程各自有自己的容量限制

Fetch

Fetch 按 tid 为每个线程分配 decoder 与 buffer

Fetch 每周期执行 smtNumFetchingThreads 次 fetch(默认值为 1)

  • 把 fetch queue entry 送入 Decode 时,selectUnstalledThread() 会采集各线程的 ldstqCountiqCountrobCount
  • 占用计数多的线程被调度的优先级最低
  • 被限流的线程占用计数视为最大
    • 限流条件
      1. ROB/LQ/SQ 头部出现可作为 donation 信号的后端阻塞
      2. 该线程 LSQ 占用达到高水位, 默认高水位为 (LQEntries + SQEntries) × 75%,保持窗口默认为 8 周期
    • 若所有候选线程都被限流,则撤销该限流设置,避免完全无进展
  • Fetch 为被调度的线程维护 PC、buffer 和 fetch queue

Decode

Decode 一次只从被选线程的 fetchQueue[tid] 向下游输出最多 decodeWidth

Backend:一次选择一个可推进线程

  • CPU 为每个 tid 初始化一张 rename map,但二者指向同一物理寄存器文件和 free list
    • 物理寄存器容量必须至少覆盖 numThreads × 架构寄存器数
  • LSQ 始终为每线程构造一个 unit;
    • Independent 时各线程可使用完整配置容量
    • Shared 时 policy 控制总容量
  • IQ 为每个线程初始化 memDepUnit[tid],但只安装一个 scheduler 及依赖图

Rename、IEW 和 Commit 都采用相同的仲裁逻辑:

  1. 遍历全部 tid,计算每线程是否被上游/本级资源阻塞,以及 ROB/LSQ/IQ 使用量与头部 stall reason
  2. 将可推进线程交给 SmtActiveThreadArbiter,选取最低 smtBorrowPriority() 的线程,并将同周期活跃候选线程标为 OtherFragStall (对上级反压, 只有一个活跃线程时不反压)

SmtActiveThreadArbiter 仲裁策略:

  1. 候选资格由调用级决定
    • 只有本级未被 block 且其 fixedbuffer[tid] 非空的线程才调用 observe()
    • 已因 ROB、LSQ、squash 或上游反压而不能推进的线程不参加本轮仲裁
  2. 分数越小优先级越高
    • 基础分数为 robCount + 2 × iqCount + 4 × ldstqCount
      • 若 ROB/LQ/SQ 头部出现可 donation 的阻塞,额外加 $2^{48}$
      • 若出现内存压力,再加 $2^{49}$
      • 这两个远大于正常队列计数的惩罚使“可让出资源”或内存压力线程通常输给可继续前进的线程
    • 优先选择共享后端占用较少的线程
    • 分数相等时仅在严格小于时更新,故按 tid 遍历顺序较早者获胜
  3. 对上游的反压
    • 只有一个活跃候选线程时不产生反压
    • 第二个活跃候选现场出现时,仲裁器同时对每个线程返回冻结请求,并锁定 freezeActive
    • 这些冻结请求是对上游的 OtherFragStall 逐级反压,限制后续周期继续向该级注入,但不撤销本周期已在本级 buffer 中的工作
    • 对于超过两个的活跃候选,后续候选也会收到冻结请求,但仍参与最低分比较
  4. 当前周期实际处理 selected() 返回的最低分线程
    • observe() 在产生冻结请求前就更新最低分记录
    • 所以两线程时第二个线程即使被写入上游反压,仍可因分数更低而成为本周期的处理线程;其 buffer 中已有指令照常推进

因此总体效果是“只允许单一线程消耗本级带宽, 对并发输入线程施加即时反压”,而不是传统 SMT 那种把同一周期宽度按多个线程分配。两个线程持续都有工作时,优先级会随 ROB/IQ/LSQ 占用和后端 stall 动态改变;长期公平性还取决于这些计数的变化及 activeThreads 的轮转,而非该仲裁器自身维护的 round-robin 指针

ROB/Commit: DynamicBorrowing

  • Partitioned 等策略设置每线程上限
  • Dynamic 给每线程总容量
  • DynamicBorrowing 初始均分并允许条件性借用

Commit 依据 IEW 的阻塞信息维护 donor 状态,并保持 smtBorrowDonorHoldCycles(默认 8)个周期

ROB 根据 donor/base 的保留条目参数决定可否分配;与 Fetch 的限流共同避免一个已被内存或头部依赖卡住的线程继续占满共享窗口

smtCommitPolicy 目前未配置项:

  • getCommittingThread() 实现了 RoundRobinOldestReady
  • 实际 commitInsts() 直接遍历 activeThreads,并未调用 getCommittingThread()