GEM5 XS-GEM5:IEW阶段深度解析与分布式调度器架构
1. 引言:IEW在乱序流水线中的核心作用
IEW(Issue/Execute/Writeback)阶段是GEM5 XS-GEM5中乱序执行的心脏,位于重命名(Rename)阶段之后,提交(Commit)阶段之前。作为香山V3处理器的关键模拟组件,IEW不仅实现了传统的乱序执行逻辑,更引入了现代超标量处理器的分布式调度器架构。
1.1 IEW的四大子阶段
- 分发 (Dispatch): 从重命名阶段接收指令,通过智能分类将其分发到多个专用队列中
- 发射 (Issue): 采用分布式调度器,并行监控多个指令队列,实现高效的指令选择和发射
- 执行 (Execute): 指令在功能单元中执行,支持推测执行和多级流水线
- 写回 (Writeback): 结果写回与依赖唤醒,支持推测性唤醒优化
1.2 现代化的分布式设计特点
- 多队列架构: 根据指令类型分为整数、浮点、向量、内存等专用队列
- 负载均衡: 智能的指令分发策略避免队列拥塞
- 跨队列协作: 灵活的推测唤醒网络支持跨队列依赖处理
- 资源仲裁: 精确的寄存器文件端口管理和冲突仲裁
2. 核心数据结构与架构组件
2.1 IEW 类:顶层协调者(src/cpu/o3/iew.hh, iew.cc)
IEW类作为整个乱序执行阶段的总指挥,协调着分发、调度、执行等各个环节。
2.1.1 关键成员变量
class IEW {
// 分布式调度器 - 现代化的核心组件
Scheduler* scheduler; // 分布式调度器指针
InstructionQueue instQueue; // 指令队列管理器
LSQ ldstQueue; // 加载/存储队列
// 分发队列 - 两阶段分发缓冲
std::deque<DynInstPtr> dispQue[3]; // 分发队列:[IntDQ, FVDQ, MemDQ]
std::vector<uint32_t> dqSize; // 各分发队列容量
std::vector<uint32_t> dispWidth; // 各队列分发带宽
// 流水级间通信
TimeBuffer<TimeStruct> *timeBuffer; // 主时间缓冲
TimeBuffer<RenameStruct> *renameQueue; // 来自重命名阶段
TimeBuffer<IEWStruct> *iewQueue; // 发送到提交阶段
TimeBuffer<IssueStruct> issueToExecQueue; // 内部发射到执行
// 阻塞控制与缓冲
std::deque<DynInstPtr> skidBuffer[MaxThreads]; // 阻塞缓冲区
std::deque<DynInstPtr> insts[MaxThreads]; // 输入指令队列
// 状态管理
Status _status; // 整体状态
StageStatus dispatchStatus[MaxThreads]; // 各线程分发状态
StageStatus exeStatus, wbStatus; // 执行和写回状态
};
2.1.2 分发队列架构 (Dispatch Queue)
XS-GEM5引入了两阶段分发机制,通过分发队列(dispQue)实现更灵活的指令缓冲:
enum DQType {
IntDQ, // 整数指令队列
FVDQ, // 浮点/向量指令队列
MemDQ, // 内存指令队列
NumDQ
};
// 指令分类逻辑 (src/cpu/o3/iew.cc:2157)
IEW::DQType IEW::getInstDQType(const DynInstPtr &inst) {
if (inst->isMemRef() || inst->isReadBarrier() ||
inst->isWriteBarrier() || inst->isNonSpeculative()) {
return MemDQ; // 内存相关指令
}
if (inst->isFloating() || inst->isVector()) {
return FVDQ; // 浮点/向量指令
}
return IntDQ; // 整数指令
}
2.2 分布式调度器架构 (Scheduler & IssueQue)
2.2.1 Scheduler:中央协调者
class Scheduler {
// 发射队列集合
std::vector<IssueQue*> issueQues;
// 推测唤醒网络
std::vector<SpecWakeupChannel> specWakeupNetwork;
std::map<DynInstPtr, std::vector<Event*>> specWakeEvents;
// 寄存器文件端口管理
std::vector<std::pair<DynInstPtr, int>> rfPortOccupancy;
std::vector<DynInstPtr> arbFailedInsts;
// 三级记分板系统
std::vector<bool> scoreboard; // 正式记分板
std::vector<bool> bypassScoreboard; // 旁路记分板
std::vector<bool> earlyScoreboard; // 早期记分板
// 性能计数器
struct SchedulerStats {
statistics::Scalar arbFailed; // 仲裁失败次数
statistics::Vector portissued; // 端口发射统计
statistics::Vector portBusy; // 端口占用统计
} schedulerStats;
};
2.2.2 IssueQue:分布式指令队列
每个IssueQue是一个独立的小型调度器,专门处理特定类型的指令:
class IssueQue {
// 指令存储与管理
std::list<DynInstPtr> instList; // 主指令链表
std::vector<std::queue<DynInstPtr>> readyQs; // 就绪队列(按端口)
std::vector<DynInstPtr> selectQ; // 选择队列
std::queue<DynInstPtr> replayQ; // 重试队列
// 依赖关系管理
std::vector<std::vector<std::pair<uint8_t, DynInstPtr>>> subDepGraph;
// subDepGraph[physRegId] = [(srcOpIdx, dependentInst), ...]
// 流水线控制
std::vector<std::vector<DynInstPtr>> inflightIssues; // 多级发射流水线
int issueStages; // 发射流水线级数
// 选择器策略
BaseSelector* selector; // 指令选择策略
// 端口配置
std::vector<IssuePort> oports; // 输出端口
int inports; // 输入端口数
// 统计信息
struct IssueQueStats {
statistics::Histogram exec_stall_cycle; // 执行停顿周期
statistics::Vector mem_stall_cycle; // 内存停顿(按级别)
statistics::Scalar issued; // 发射指令数
statistics::Scalar cancelled; // 取消指令数
} issueQueStats;
};
2.3 推测唤醒网络 (SpecWakeupChannel)
推测唤醒是现代超标量处理器的关键优化技术:
class SpecWakeupChannel {
std::vector<std::string> srcIQs; // 源发射队列列表
std::vector<std::string> dstIQs; // 目标发射队列列表
int wakeupDelay; // 唤醒延迟
bool crossCluster; // 是否跨集群唤醒
};
// 推测唤醒事件类
class SpecWakeupCompletion : public Event {
DynInstPtr inst; // 触发唤醒的指令
IssueQue* targetQueue; // 目标队列
std::map<DynInstPtr, std::vector<Event*>>* eventMap;
public:
void process() override {
targetQueue->wakeUpDependents(inst, true); // 执行推测唤醒
// 清理事件映射
(*eventMap)[inst].erase(
std::remove((*eventMap)[inst].begin(),
(*eventMap)[inst].end(), this),
(*eventMap)[inst].end());
}
};
2.4 LSQ:内存子系统接口
LSQ专门处理内存访问的复杂性:
class LSQ {
// 加载队列
std::vector<LSQUnit> LQs; // 每线程的加载队列
// 存储队列
std::vector<LSQUnit> SQs; // 每线程的存储队列
// 内存依赖跟踪
MemDepUnit memDepUnit; // 内存依赖单元
// 性能优化
bool enableStoreSetTrain; // 存储集训练
// 统计信息
struct LSQStats {
statistics::Scalar loadTLBMisses; // Load TLB缺失
statistics::Scalar storeTLBMisses; // Store TLB缺失
statistics::Scalar loadCacheMisses; // Load Cache缺失
statistics::Scalar storeCacheMisses; // Store Cache缺失
} lsqStats;
};
3. 分布式调度器架构深度解析
3.1 香山V3调度器配置实例
基于代码分析,香山V3处理器采用了高度分布式的调度器架构,具体配置如下:
3.1.1 KMHV3Scheduler配置
# 整数执行队列 - 6个独立队列
intIQ0-5: size=16, 支持ALU/Branch/Mul/Div等操作
# 内存执行队列 - 7个专门队列
load0-2: size=16, 专门处理Load操作
store0-1: size=16, 处理Store地址计算
std0-1: size=16, 处理Store数据操作
# 浮点/向量队列 - 5个队列
fpIQ0-3: size=16, 浮点运算
vecIQ0: size=16, 向量运算
这种配置能够: - 最大化并行度: 18个独立队列并行工作 - 避免资源冲突: 不同类型操作使用独立资源 - 优化关键路径: Load/Store分离减少内存访问延迟
3.1.2 推测唤醒网络拓扑
// 关键的跨队列唤醒通道
SpecWakeupChannel examples[] = {
// 整数间唤醒
{.srcIQs={"intIQ0"}, .dstIQs={"intIQ1", "intIQ2"}, .delay=1},
// 整数到内存唤醒
{.srcIQs={"intIQ0", "intIQ1"}, .dstIQs={"load0", "store0"}, .delay=2},
// 浮点内部唤醒
{.srcIQs={"fpIQ0"}, .dstIQs={"fpIQ1", "fpIQ2", "fpIQ3"}, .delay=1},
// 跨域唤醒(整数->浮点)
{.srcIQs={"intIQ0"}, .dstIQs={"fpIQ0"}, .delay=3},
};
3.2 指令选择器策略详解
3.2.1 基础选择器 (BaseSelector)
class BaseSelector {
public:
std::vector<DynInstPtr> select(std::queue<DynInstPtr>& readyQ,
int numSelect) {
std::vector<DynInstPtr> selected;
for (int i = 0; i < numSelect && !readyQ.empty(); i++) {
selected.push_back(readyQ.front());
readyQ.pop();
}
return selected;
}
};
3.2.2 伪年龄选择器 (PAgeSelector)
class PAgeSelector : public BaseSelector {
std::vector<std::vector<bool>> groupConflict; // 分组冲突矩阵
public:
std::vector<DynInstPtr> select(std::queue<DynInstPtr>& readyQ,
int numSelect) override {
std::vector<DynInstPtr> selected;
std::vector<bool> usedGroups(numGroups, false);
std::vector<DynInstPtr> candidates;
while (!readyQ.empty()) {
candidates.push_back(readyQ.front());
readyQ.pop();
}
// 按年龄排序
std::sort(candidates.begin(), candidates.end(),
[](const DynInstPtr& a, const DynInstPtr& b) {
return a->seqNum < b->seqNum;
});
// 选择时避免分组冲突
for (auto& inst : candidates) {
int group = getInstructionGroup(inst);
if (!usedGroups[group] && selected.size() < numSelect) {
selected.push_back(inst);
// 标记冲突组
for (int i = 0; i < numGroups; i++) {
if (groupConflict[group][i]) {
usedGroups[i] = true;
}
}
} else {
readyQ.push(inst); // 放回队列
}
}
return selected;
}
};
3.3 寄存器文件端口仲裁机制
3.3.1 端口编码系统
// 端口编码格式:[7:6]类型 [5:2]端口ID [1:0]优先级
#define MAKE_PORT_ID(type, port, pri) (((type) << 6) | ((port) << 2) | (pri))
// 整数寄存器读端口
#define IntRD(id, p) MAKE_PORT_ID(0, id, p) // 0x00-0x3F
#define IntWR(id, p) MAKE_PORT_ID(0, id+8, p)// 0x20-0x3F
// 浮点寄存器端口
#define FpRD(id, p) MAKE_PORT_ID(1, id, p) // 0x40-0x7F
#define FpWR(id, p) MAKE_PORT_ID(1, id+8, p)// 0x60-0x7F
// 向量寄存器端口
#define VecRD(id, p) MAKE_PORT_ID(2, id, p) // 0x80-0xBF
#define VecWR(id, p) MAKE_PORT_ID(2, id+8, p)// 0xA0-0xBF
3.3.2 端口仲裁逻辑
// src/cpu/o3/issue_queue.cc 中的关键函数
void Scheduler::useRegfilePort(const DynInstPtr& inst,
const PhysRegIdPtr& regid,
int typePortId, int pri) {
// 检查端口是否已被占用
if (rfPortOccupancy[typePortId].first) {
// 优先级比较仲裁
if (rfPortOccupancy[typePortId].second < pri) {
// 当前指令仲裁失败,加入失败列表
arbFailedInsts.push_back(inst);
schedulerStats.arbFailed++;
return;
} else {
// 已占用指令仲裁失败,替换
DynInstPtr failedInst = rfPortOccupancy[typePortId].first;
arbFailedInsts.push_back(failedInst);
}
}
// 分配端口给当前指令
rfPortOccupancy[typePortId] = std::make_pair(inst, pri);
schedulerStats.portissued[typePortId]++;
DPRINTF(IssueQueue, "[sn:%lli] allocated port %d (type=%d, port=%d, pri=%d)\n",
inst->seqNum, typePortId,
(typePortId >> 6) & 0x3, // type
(typePortId >> 2) & 0xF, // port id
typePortId & 0x3); // priority
}
4. IEW核心执行流程详解
4.1 IEW::tick() - 主驱动函数
void IEW::tick() {
// 1. 统计信息收集
for (int i = 0; i < fromRename->fetchStallReason.size(); i++) {
iewStats.fetchStallReason[fromRename->fetchStallReason[i]]++;
}
// 2. 初始化周期状态
wbNumInst = 0;
wbCycle = 0;
wroteToTimeBuffer = false;
updatedQueues = false;
// 3. 子模块时钟推进
scheduler->tick(); // 分布式调度器时钟
ldstQueue.tick(); // LSQ时钟
// 4. 指令排序和分类
sortInsts(); // 从Rename接收的指令按线程分类
// 5. 各线程处理循环
std::list<ThreadID>::iterator threads = activeThreads->begin();
while (threads != activeThreads->end()) {
ThreadID tid = *threads++;
// 获取LSQ统计信息
lastClockLQPopEntries[tid] = ldstQueue.getAndResetLastLQPopEntries(tid);
lastClockSQPopEntries[tid] = ldstQueue.getAndResetLastSQPopEntries(tid);
// 检查信号和更新状态
checkSignalsAndUpdate(tid);
// 执行分发
dispatch(tid);
// 计算停顿原因
toRename->iewInfo[tid].robHeadStallReason =
checkDispatchStall(tid, NumDQ, nullptr, -1);
toRename->iewInfo[tid].lqHeadStallReason =
ldstQueue.lqEmpty() ? StallReason::NoStall : checkLSQStall(tid, true);
toRename->iewInfo[tid].sqHeadStallReason =
ldstQueue.sqEmpty() ? StallReason::NoStall : checkLSQStall(tid, false);
}
// 6. 执行阶段(非Squashing状态)
if (exeStatus != Squashing) {
instQueue.scheduleReadyInsts(); // 调度就绪指令
executeInsts(); // 执行指令
writebackInsts(); // 写回指令
}
// 7. 调度器发射和选择
scheduler->issueAndSelect();
// 8. 后处理
ldstQueue.writebackStoreBuffer(); // Store Buffer写回
// 9. 提交阶段处理
processCommitSignals();
// 10. 状态更新
updateStatus();
}
4.2 两阶段分发机制详解
4.2.1 第一阶段:分类到分发队列
void IEW::classifyInstToDispQue(ThreadID tid) {
std::deque<DynInstPtr> &insts_to_dispatch =
dispatchStatus[tid] == Unblocking ? skidBuffer[tid] : insts[tid];
bool emptyROB = fromCommit->commitInfo[tid].emptyROB;
unsigned dispatched = 0;
while (!insts_to_dispatch.empty()) {
auto& inst = insts_to_dispatch.front();
// 确定指令类型
int dqType = getInstDQType(inst); // IntDQ/FVDQ/MemDQ
// 检查分发队列容量
if (dispQue[dqType].size() < dqSize[dqType]) {
// 处理已废除指令
if (inst->isSquashed()) {
updateDispatchStats(inst, tid);
insts_to_dispatch.pop_front();
continue;
}
// 序列化检查
if ((inst->isSerializeBefore() && !inst->isSerializeHandled())
? !emptyROB : false) {
// 需要等待ROB为空
break;
}
// 设置HTM状态
setHtmTransactionalState(inst, tid);
// 添加到对应分发队列!!!
dispQue[dqType].push_back(inst);
inst->enterDQTick = curTick(); // 记录进入时间
// 生产者依赖管理
if (!inst->isNop() && !inst->isEliminated()) {
scheduler->addProducer(inst);
}
// 更新统计
updateDispatchStats(inst, tid);
insts_to_dispatch.pop_front();
dispatched++;
} else {
// 队列满,停止分发
break;
}
}
// 处理阻塞情况
if (!insts_to_dispatch.empty()) {
block(tid);
disp_stall = true;
}
}
4.2.2 第二阶段:从分发队列到调度器
void IEW::dispatchInstFromDispQue(ThreadID tid) {
bool add_to_iq = false;
int totalDispatched = 0;
// 遍历所有分发队列类型
for (int dqType = 0; dqType < NumDQ; dqType++) {
int dispatched = 0;
int disp_seq = -1;
// 预调度分析
scheduler->lookahead(dispQue[dqType]);
// 按分发带宽处理
while (!dispQue[dqType].empty() && dispatched < dispWidth[dqType]) {
DynInstPtr inst = dispQue[dqType].front();
disp_seq++;
// 跳过已废除指令
if (inst->isSquashed()) {
dispQue[dqType].pop_front();
continue;
}
// 检查调度器就绪状态
if (!scheduler->ready(inst, disp_seq)) {
// 调度器忙或满
break;
}
// 检查LSQ容量
if (checkLSQCapacity(inst, tid)) {
break;
}
// 指令分类处理
if (inst->isAtomic()) {
handleAtomicInst(inst, tid);
add_to_iq = false;
} else if (inst->isLoad()) {
handleLoadInst(inst, tid);
add_to_iq = true;
} else if (inst->isStore()) {
handleStoreInst(inst, tid);
add_to_iq = !inst->isStoreConditional();
} else if (inst->isReadBarrier() || inst->isWriteBarrier()) {
handleBarrierInst(inst);
add_to_iq = false;
} else if (inst->isNop() || inst->isEliminated()) {
handleNopInst(inst, tid);
add_to_iq = false;
} else {
add_to_iq = true;
}
// 非推测指令处理
if (add_to_iq && inst->isNonSpeculative()) {
inst->setCanCommit();
instQueue.insertNonSpec(inst);
add_to_iq = false;
}
// 插入到分布式指令队列,当两个源操作数都准备好后,会进入readyQ中
if (add_to_iq) {
instQueue.insert(inst, disp_seq);
}
// 清理和统计
inst->exitDQTick = curTick();
ppDispatch->notify(inst);
dispQue[dqType].pop_front();
dispatched++;
totalDispatched++;
}
}
// 更新分发统计
iewStats.dispDist.sample(totalDispatched);
}
4.3 执行阶段:分布式并行执行
4.3.1 executeInsts() - 执行控制器
void IEW::executeInsts() {
wbNumInst = 0;
wbCycle = 0;
// 重置重定向标志
std::list<ThreadID>::iterator threads = activeThreads->begin();
while (threads != activeThreads->end()) {
ThreadID tid = *threads++;
fetchRedirect[tid] = false;
}
// 从调度器获取待执行指令
int insts_to_execute = fromIssue->size;
fromIssue->size = 0;
for (int inst_num = 0; inst_num < insts_to_execute; ++inst_num) {
DynInstPtr inst = instQueue.getInstToExecute();
DPRINTF(IEW, "Execute: Processing PC %s, [tid:%i] [sn:%llu].\n",
inst->pcState(), inst->threadNumber, inst->seqNum);
// 通知执行开始
ppExecute->notify(inst);
// 跳过已废除指令
if (inst->isSquashed()) {
inst->setExecuted();
inst->setCanCommit();
++iewStats.executedInstStats.numSquashedInsts;
continue;
}
Fault fault = NoFault;
// 内存指令执行
if (inst->isMemRef()) {
if (inst->isAtomic()) {
fault = ldstQueue.executeAmo(inst);
handleTranslationDelay(inst, fault);
} else if (inst->isLoad()) {
ldstQueue.issueToLoadPipe(inst); // 发射到Load流水线
} else if (inst->isStore()) {
ldstQueue.issueToStorePipe(inst); // 发射到Store流水线
}
} else {
// 非内存指令直接执行
if (inst->getFault() == NoFault) {
inst->execute();
if (!inst->readPredicate()) {
inst->forwardOldRegs(); // 条件执行失败时恢复寄存器
}
}
// 处理分离的Store数据
if (!inst->isSplitStoreData()) {
inst->setExecuted();
readyToFinish(inst); // 准备写回
} else {
handleSplitStoreData(inst);
}
}
updateExeInstStats(inst);
// 分支预测和内存序冲突检查
if (!(inst->isLoad() || inst->isStore() || inst->isSplitStoreData())) {
SquashCheckAfterExe(inst); // 非内存指令立即检查
}
}
// 执行内存流水线
ldstQueue.executePipeSx();
// 更新执行状态
if (inst_num) {
if (exeStatus == Idle) exeStatus = Running;
updatedQueues = true;
cpu->activityThisCycle();
}
}
4.3.2 推测执行与异常处理
void IEW::SquashCheckAfterExe(DynInstPtr inst) {
ThreadID tid = inst->threadNumber;
// 检查之前是否已有重定向
if (!fetchRedirect[tid] || !execWB->squash[tid] ||
execWB->squashedSeqNum[tid] > inst->seqNum) {
// 分支预测错误检测
bool loadNotExecuted = !inst->isExecuted() && inst->isLoad();
if (inst->mispredicted() && !loadNotExecuted) {
fetchRedirect[tid] = true;
DPRINTF(IEW, "[tid:%i] [sn:%llu] Branch mispredict detected.\n",
tid, inst->seqNum);
DPRINTF(IEW, "[tid:%i] [sn:%llu] Predicted: %s, Actual: %s\n",
tid, inst->seqNum, inst->readPredTarg(), inst->pcState());
squashDueToBranch(inst, tid); // 发起分支错误冲刷
ppMispredict->notify(inst);
// 更新预测统计
if (inst->readPredTaken()) {
iewStats.predictedTakenIncorrect++;
} else {
iewStats.predictedNotTakenIncorrect++;
}
}
// 内存序冲突检测
else if (ldstQueue.violation(tid)) {
DynInstPtr violator = ldstQueue.getMemDepViolator(tid);
DPRINTF(IEW, "Memory violation detected. Violator PC: %s [sn:%lli]\n",
violator->pcState(), violator->seqNum);
fetchRedirect[tid] = true;
// 存储集训练
if (enableStoreSetTrain) {
instQueue.violation(inst, violator);
}
squashDueToMemOrder(violator, tid); // 发起内存序冲刷
++iewStats.memOrderViolationEvents;
}
}
}
4.4 写回阶段:结果广播与依赖唤醒
4.4.1 writebackInsts() - 写回控制器
void IEW::writebackInsts() {
// 处理执行完成的指令
for (int inst_num = 0; inst_num < wbWidth &&
execWB->insts[inst_num]; inst_num++) {
DynInstPtr inst = execWB->insts[inst_num];
ThreadID tid = inst->threadNumber;
// 处理预取信息
if (inst->savedRequest && inst->isLoad()) {
inst->pf_source = inst->savedRequest->mainReq()->getPFSource();
}
DPRINTF(IEW, "Writeback: [sn:%lli] PC %s to commit.\n",
inst->seqNum, inst->pcState());
iewStats.instsToCommit[tid]++;
ppToCommit->notify(inst); // 通知即将提交
// 只有成功执行的指令才进行依赖唤醒
if (!inst->isSquashed() && inst->isExecuted() &&
inst->getFault() == NoFault) {
// 调度器写回唤醒
scheduler->writebackWakeup(inst);
// 指令队列依赖唤醒
int dependents = instQueue.wakeDependents(inst);
// 更新物理寄存器记分板
for (int i = 0; i < inst->numDestRegs(); i++) {
auto destReg = inst->renamedDestIdx(i);
if (destReg->getNumPinnedWritesToComplete() == 0) {
DPRINTF(IEW, "Setting destination register %i (%s) ready\n",
destReg->index(), destReg->className());
scoreboard->setReg(destReg); // 标记寄存器就绪
}
}
// 更新唤醒统计
if (dependents) {
iewStats.producerInst[tid]++;
iewStats.consumerInst[tid] += dependents;
}
iewStats.writebackCount[tid]++;
}
}
}
4.4.2 推测性唤醒实现
void Scheduler::specWakeUpDependents(const DynInstPtr& inst,
IssueQue* from_issue_queue) {
// 只有流水化操作且非Load指令才推测唤醒
if (!opPipelined[inst->opClass()] || inst->numDestRegs() == 0 ||
inst->isLoad()) {
return;
}
for (int i = 0; i < inst->numDestRegs(); i++) {
auto dst = inst->renamedDestIdx(i);
// 遍历推测唤醒网络
for (auto& channel : specWakeupNetwork) {
if (!channelMatch(from_issue_queue->name(), channel.srcIQs)) {
continue;
}
for (auto& dstName : channel.dstIQs) {
IssueQue* targetQueue = findIssueQueue(dstName);
if (!targetQueue) continue;
// 计算唤醒延迟
int oplat = getCorrectedOpLat(inst);
int wakeDelay = oplat - 1;
// 跨队列延迟调整
int diff = std::abs(from_issue_queue->getIssueStages() -
targetQueue->getIssueStages());
wakeDelay += diff;
if (wakeDelay == 0) {
// 立即推测唤醒
targetQueue->wakeUpDependents(inst, true);
earlyScoreboard[dst->flatIndex()] = true;
} else {
// 调度延迟唤醒事件
auto wakeEvent = new SpecWakeupCompletion(
inst, targetQueue, &specWakeEvents);
specWakeEvents[inst].push_back(wakeEvent);
Tick when = cpu->clockEdge(Cycles(wakeDelay)) - 1;
cpu->schedule(wakeEvent, when);
DPRINTF(IssueQueue,
"[sn:%lli] Scheduled spec wakeup for %s in %d cycles\n",
inst->seqNum, dstName.c_str(), wakeDelay);
}
}
}
}
}
4.5 内存指令特殊处理
4.5.1 Load指令处理流程
void LSQ::issueToLoadPipe(const DynInstPtr& load_inst) {
ThreadID tid = load_inst->threadNumber;
// 分配LQ表项
int lq_idx = loadQueue[tid].allocate(load_inst);
if (lq_idx == -1) {
// LQ满,延迟处理
DPRINTF(LSQUnit, "[tid:%i] Load queue full, deferring [sn:%lli]\n",
tid, load_inst->seqNum);
return;
}
load_inst->setLSQIdx(lq_idx);
// S0阶段:地址计算
if (load_inst->isUncacheable()) {
loadQueue[tid][lq_idx].setUncacheable();
}
// 检查Store forwarding
auto forward_result = checkStoreForwarding(load_inst, tid);
if (forward_result.first) {
// 可以从Store buffer获取数据
loadQueue[tid][lq_idx].setForwarded();
load_inst->setExecuted();
DPRINTF(LSQUnit, "[tid:%i] Load [sn:%lli] forwarded from store\n",
tid, load_inst->seqNum);
return;
}
// S1阶段:TLB查找
loadPipeS1[tid].push(load_inst);
}
void LSQ::executeLoadPipeS1() {
for (ThreadID tid = 0; tid < numThreads; tid++) {
if (loadPipeS1[tid].empty()) continue;
DynInstPtr load_inst = loadPipeS1[tid].front();
loadPipeS1[tid].pop();
// TLB转换
Fault fault = load_inst->initiateAcc();
if (fault != NoFault) {
// TLB miss或页错误
handleLoadFault(load_inst, fault);
continue;
}
// S2阶段:Cache访问
loadPipeS2[tid].push(load_inst);
}
}
4.5.2 Store指令处理流程
void LSQ::issueToStorePipe(const DynInstPtr& store_inst) {
ThreadID tid = store_inst->threadNumber;
// 分配SQ表项
int sq_idx = storeQueue[tid].allocate(store_inst);
store_inst->setSQIdx(sq_idx);
if (store_inst->isSplitStoreData()) {
// Store地址计算
storePipeSTA[tid].push(store_inst);
} else {
// 完整Store操作
storePipeSTD[tid].push(store_inst);
}
}
void LSQ::executeStorePipeSTA() {
// Store地址计算阶段
for (ThreadID tid = 0; tid < numThreads; tid++) {
while (!storePipeSTA[tid].empty()) {
DynInstPtr store_inst = storePipeSTA[tid].front();
storePipeSTA[tid].pop();
// 计算有效地址
Fault fault = store_inst->initiateAcc();
if (fault != NoFault) {
handleStoreFault(store_inst, fault);
continue;
}
// 检查内存序冲突
if (checkMemoryOrderViolation(store_inst, tid)) {
signalMemoryOrderViolation(store_inst, tid);
continue;
}
store_inst->setExecuted();
// 地址计算完成,等待数据
if (store_inst->splitStoreFinish()) {
// 地址和数据都准备好
readyToCommitStores[tid].push(store_inst);
}
}
}
}
5. 模块间交互机制详解
5.1 IEW与Rename阶段的接口协议
5.1.1 指令接收接口
// IEW从Rename接收指令的数据结构
struct RenameStruct {
DynInstPtr insts[renameWidth]; // 重命名完成的指令数组
int size; // 本周期指令数量
// 停顿原因统计
std::vector<StallReason> fetchStallReason; // Fetch阶段停顿原因
std::vector<StallReason> decodeStallReason; // Decode阶段停顿原因
std::vector<StallReason> renameStallReason; // Rename阶段停顿原因
bool squash[MaxThreads]; // 各线程是否需要冲刷
InstSeqNum squashedSeqNum[MaxThreads]; // 冲刷起始序号
};
// IEW向Rename反馈的状态信息
struct IEWInfo {
bool usedIQ; // 是否使用了IQ
bool usedLSQ; // 是否使用了LSQ
unsigned freeLQEntries; // 可用Load队列表项
unsigned freeSQEntries; // 可用Store队列表项
unsigned dispatched; // 本周期分发的指令数
unsigned dispatchedToLQ; // 分发到LQ的指令数
unsigned dispatchedToSQ; // 分发到SQ的指令数
bool iewBlock; // IEW是否阻塞
bool iewUnblock; // IEW是否解除阻塞
// 停顿原因分析
StallReason robHeadStallReason; // ROB头指令停顿原因
StallReason lqHeadStallReason; // LQ头指令停顿原因
StallReason sqHeadStallReason; // SQ头指令停顿原因
StallReason blockReason; // 阻塞原因
};
5.1.2 反压控制机制
void IEW::checkSignalsAndUpdate(ThreadID tid) {
// 处理来自Commit的冲刷信号
if (fromCommit->commitInfo[tid].squash) {
squash(tid); // 执行冲刷操作
localSquashVer.update(fromCommit->commitInfo[tid].squashVersion.getVersion());
// 解除可能的阻塞状态
if (dispatchStatus[tid] == Blocked || dispatchStatus[tid] == Unblocking) {
toRename->iewUnblock[tid] = true;
wroteToTimeBuffer = true;
}
dispatchStatus[tid] = Squashing;
return;
}
// 处理ROB冲刷状态
if (fromCommit->commitInfo[tid].robSquashing) {
dispatchStatus[tid] = Squashing;
emptyRenameInsts(tid); // 清空来自Rename的指令
setAllStalls(StallReason::CommitSquash);
return;
}
// 检查是否需要阻塞
if (checkStall(tid)) {
block(tid);
dispatchStatus[tid] = Blocked;
return;
}
// 处理解除阻塞
if (dispatchStatus[tid] == Blocked) {
dispatchStatus[tid] = Unblocking;
unblock(tid);
}
}
5.2 IEW与Commit阶段的协调
5.2.1 指令提交接口
// IEW向Commit发送执行完成指令的结构
struct IEWStruct {
DynInstPtr insts[wbWidth]; // 写回的指令数组
int size; // 指令数量
// 冲刷信息
bool squash[MaxThreads]; // 是否需要冲刷
bool includeSquashInst[MaxThreads]; // 是否包含冲刷指令本身
InstSeqNum squashedSeqNum[MaxThreads]; // 冲刷序号
// 分支预测信息
std::unique_ptr<PCStateBase> pc[MaxThreads]; // 正确的PC
bool branchTaken[MaxThreads]; // 分支是否跳转
DynInstPtr mispredictInst[MaxThreads]; // 预测错误的指令
// 前端重定向信息(香山扩展)
uint64_t squashedStreamId[MaxThreads]; // 冲刷的流ID
uint64_t squashedTargetId[MaxThreads]; // 冲刷的目标ID
uint32_t squashedLoopIter[MaxThreads]; // 冲刷的循环迭代
};
// 来自Commit的反馈信息
struct CommitInfo {
bool squash; // 是否冲刷
bool robSquashing; // ROB是否正在冲刷
InstSeqNum doneSeqNum; // 已提交的最大序号
InstSeqNum doneMemSeqNum; // 已提交的最大内存序号
// 非推测执行控制
InstSeqNum nonSpecSeqNum; // 需要非推测执行的序号
bool strictlyOrdered; // 是否严格有序
DynInstPtr strictlyOrderedLoad; // 严格有序的Load指令
bool emptyROB; // ROB是否为空
SquashVersion squashVersion; // 冲刷版本号
};
5.2.2 写回调度逻辑
void IEW::readyToFinish(const DynInstPtr& inst) {
// 寻找可用的写回时间槽
while ((*iewQueue)[wbCycle].insts[wbNumInst]) {
++wbNumInst;
if (wbNumInst == wbWidth) {
++wbCycle; // 延迟到下一个周期
wbNumInst = 0;
}
}
// 调度器旁路写回优化
scheduler->bypassWriteback(inst);
inst->completionTick = curTick();
// 添加到写回队列
(*iewQueue)[wbCycle].insts[wbNumInst] = inst;
(*iewQueue)[wbCycle].size++;
DPRINTF(IEW, "Scheduled writeback for [sn:%lli] at cycle %d, slot %d\n",
inst->seqNum, wbCycle, wbNumInst);
}
5.3 与功能单元池(FUPool)的接口
5.3.1 功能单元分配策略
class IssuePort {
std::string fuType; // 功能单元类型(如"IntALU", "FpMul")
int opLat; // 操作延迟
int pipelined; // 是否流水化
// 端口资源编码
std::vector<int> srcPorts; // 源操作数端口
std::vector<int> dstPorts; // 目标操作数端口
};
// 香山处理器的典型功能单元配置
std::vector<IssuePort> intIQ0_ports = {
{.fuType="IntALU", .opLat=1, .pipelined=1, .srcPorts={IntRD(0,0), IntRD(1,0)}, .dstPorts={IntWR(0,0)}},
{.fuType="IntMul", .opLat=3, .pipelined=1, .srcPorts={IntRD(0,1), IntRD(1,1)}, .dstPorts={IntWR(0,1)}},
{.fuType="Branch", .opLat=1, .pipelined=1, .srcPorts={IntRD(0,2), IntRD(1,2)}, .dstPorts={}},
};
5.3.2 发射端口仲裁
bool Scheduler::issueInstToFU(const DynInstPtr& inst, IssueQue* srcQueue) {
// 选择合适的发射端口
IssuePort* selectedPort = nullptr;
for (auto& port : srcQueue->getOutputPorts()) {
if (port.canHandle(inst->opClass()) && port.isAvailable()) {
selectedPort = &port;
break;
}
}
if (!selectedPort) {
DPRINTF(IssueQueue, "[sn:%lli] No available FU port\n", inst->seqNum);
return false;
}
// 寄存器端口分配
bool portAllocSuccess = true;
for (int i = 0; i < inst->numSrcRegs(); i++) {
auto srcReg = inst->renamedSrcIdx(i);
int portId = selectedPort->srcPorts[i];
if (!allocateRegPort(inst, srcReg, portId)) {
portAllocSuccess = false;
break;
}
}
if (!portAllocSuccess) {
// 端口分配失败,释放已分配的端口
releaseAllocatedPorts(inst);
return false;
}
// 成功分配,发射指令
selectedPort->issue(inst);
instsToFu.push_back(inst);
DPRINTF(IssueQueue, "[sn:%lli] Issued to %s FU\n",
inst->seqNum, selectedPort->fuType.c_str());
return true;
}
6. 性能优化与调优策略
6.1 关键性能瓶颈分析
6.1.1 分发带宽限制
// 分发带宽配置(香山V3实例)
std::vector<uint32_t> dispWidth = {
4, // IntDQ: 每周期最多分发4条整数指令
2, // FVDQ: 每周期最多分发2条浮点/向量指令
3 // MemDQ: 每周期最多分发3条内存指令
};
// 分发效率统计
struct DispatchStats {
statistics::Distribution dispDist; // 分发指令数分布
statistics::Vector dispatchStallReason; // 分发停顿原因
statistics::Scalar dispBWFull; // 分发带宽满的次数
};
6.1.2 调度器瓶颈识别
// 调度器性能监控点
void Scheduler::collectPerfStats() {
// 发射队列利用率
for (auto& iq : issueQues) {
float utilization = (float)iq->getOccupancy() / iq->getCapacity();
schedulerStats.queueUtilization[iq->getName()].sample(utilization);
// 检测热点队列
if (utilization > 0.8) {
schedulerStats.hotQueues[iq->getName()]++;
}
}
// 端口冲突统计
for (int i = 0; i < rfPortOccupancy.size(); i++) {
if (rfPortOccupancy[i].first) {
schedulerStats.portBusy[i]++;
}
}
// 推测唤醒效果
int totalSpecWakeups = 0;
int successfulSpecWakeups = 0;
for (auto& [inst, events] : specWakeEvents) {
totalSpecWakeups += events.size();
if (!inst->isSquashed()) {
successfulSpecWakeups += events.size();
}
}
float specWakeupAccuracy = (float)successfulSpecWakeups / totalSpecWakeups;
schedulerStats.specWakeupAccuracy.sample(specWakeupAccuracy);
}
6.2 性能调优建议
6.2.1 队列容量调优
# 基于工作负载特征的队列容量调优
class IQSizeOptimizer:
def __init__(self):
self.workload_profiles = {
'compute_intensive': {
'intIQ_size': 20, # 增大整数队列
'fpIQ_size': 24, # 大幅增大浮点队列
'memIQ_size': 12 # 适中的内存队列
},
'memory_intensive': {
'intIQ_size': 16, # 标准整数队列
'fpIQ_size': 16, # 标准浮点队列
'memIQ_size': 32 # 大幅增大内存队列
},
'mixed_workload': {
'intIQ_size': 18, # 平衡配置
'fpIQ_size': 18,
'memIQ_size': 20
}
}
def tune_for_workload(self, workload_type):
config = self.workload_profiles[workload_type]
print(f"Optimizing for {workload_type}:")
for queue, size in config.items():
print(f" {queue}: {size} entries")
6.2.2 推测唤醒网络优化
// 基于分析的唤醒网络调优
class WakeupNetworkTuner {
public:
struct WakeupStats {
int totalWakeups;
int successfulWakeups;
int cancelledWakeups;
float averageDelay;
};
void optimizeWakeupNetwork() {
// 分析跨队列依赖模式
std::map<std::pair<std::string, std::string>, WakeupStats> channelStats;
for (auto& channel : specWakeupNetwork) {
for (auto& src : channel.srcIQs) {
for (auto& dst : channel.dstIQs) {
auto key = std::make_pair(src, dst);
auto& stats = channelStats[key];
// 计算推测唤醒效率
if (stats.totalWakeups > 0) {
float accuracy = (float)stats.successfulWakeups / stats.totalWakeups;
if (accuracy < 0.7) {
// 准确率低,考虑移除或增加延迟
DPRINTF(Optimize, "Low accuracy channel %s->%s: %.2f\n",
src.c_str(), dst.c_str(), accuracy);
} else if (accuracy > 0.95 && stats.averageDelay > 2) {
// 准确率高但延迟大,考虑减少延迟
DPRINTF(Optimize, "High latency channel %s->%s: %.2f cycles\n",
src.c_str(), dst.c_str(), stats.averageDelay);
}
}
}
}
}
}
};
6.2.3 内存子系统协调优化
// LSQ与缓存系统的协调优化
class LSQOptimizer {
public:
struct MemoryPerfProfile {
float l1HitRate;
float l2HitRate;
float l3HitRate;
int averageMemLatency;
};
void optimizeLSQForMemProfile(const MemoryPerfProfile& profile) {
// 基于缓存性能调整LSQ容量
if (profile.l1HitRate < 0.8) {
// L1缓存命中率低,增大LQ容量以容纳更多在途请求
int recommendedLQSize = baseLQSize * (1.5 - profile.l1HitRate);
DPRINTF(Optimize, "Recommending LQ size: %d (based on L1 hit rate %.2f)\n",
recommendedLQSize, profile.l1HitRate);
}
if (profile.averageMemLatency > 300) {
// 内存延迟高,启用更激进的Store-to-Load forwarding
enableAggressiveForwarding = true;
DPRINTF(Optimize, "Enabling aggressive store forwarding due to high mem latency\n");
}
// 调整预取策略
if (profile.l2HitRate > 0.9) {
// L2命中率高,可以启用更激进的预取
prefetchAggressiveness = 2;
}
}
};
6.3 调试和性能分析工具
6.3.1 实时性能监控
// IEW性能监控面板
class IEWPerfMonitor {
struct RealTimeStats {
float iewIPC; // IEW阶段的IPC
float dispatchUtilization; // 分发利用率
float issueQueueUtilization; // 发射队列利用率
float memoryStallRatio; // 内存停顿比例
// 热点分析
std::string bottleneckQueue; // 瓶颈队列
std::string hottestStallReason; // 主要停顿原因
};
public:
void updateRealTimeStats() {
RealTimeStats stats;
// 计算实时IPC
stats.iewIPC = (float)executedInsts / totalCycles;
// 找出瓶颈队列
float maxUtil = 0;
for (auto& iq : issueQues) {
float util = (float)iq->getOccupancy() / iq->getCapacity();
if (util > maxUtil) {
maxUtil = util;
stats.bottleneckQueue = iq->getName();
}
}
// 分析主要停顿原因
auto maxStallReason = std::max_element(
stallReasonCounts.begin(), stallReasonCounts.end(),
[](const auto& a, const auto& b) { return a.second < b.second; });
stats.hottestStallReason = stallReasonToString(maxStallReason->first);
// 输出性能报告
if (curTick() % 1000000 == 0) { // 每100万个周期报告一次
DPRINTF(PerfMonitor,
"IEW Performance: IPC=%.2f, Bottleneck=%s, Main stall=%s\n",
stats.iewIPC, stats.bottleneckQueue.c_str(),
stats.hottestStallReason.c_str());
}
}
};
7. 总结与展望
7.1 XS-GEM5 IEW的创新特性
香山V3处理器的IEW实现体现了现代超标量处理器设计的最新趋势:
- 分布式调度架构: 18个独立发射队列支持高度并行的指令处理
- 智能推测唤醒: 跨队列的推测唤醒网络显著减少依赖链延迟
- 灵活的分发机制: 两阶段分发提供了更好的负载均衡和阻塞处理
- 精确的资源管理: 寄存器文件端口的精确仲裁避免资源冲突
- 现代内存子系统: 支持Store forwarding、内存序检测等高级特性
7.2 性能影响与优化空间
通过对IEW各个组件的深入分析,可以识别出几个关键的性能优化方向:
- 调度延迟: 推测唤醒网络可进一步优化,减少跨队列通信延迟
- 端口利用: 寄存器文件端口的动态分配策略可以更加智能化
- 内存流水: Load/Store流水线可以进一步深化,支持更高的内存访问带宽
- 能耗优化: 分布式设计在降低复杂度的同时,也为功耗优化提供了机会
7.3 理解IEW的价值
掌握IEW阶段的实现原理对于: - 处理器设计: 理解现代超标量处理器的核心机制 - 性能调优: 识别和解决性能瓶颈 - 架构研究: 探索新的乱序执行优化技术 - 系统优化: 在软硬件协同设计中发挥重要作用
IEW阶段作为乱序执行的核心,其设计质量直接决定了处理器的指令级并行度和整体性能。通过深入理解其dispatch、issue、execute、writeback四个环节以及分布式调度器的工作原理,我们能够更好地把握现代高性能处理器的设计精髓,为未来的处理器架构创新奠定坚实的理论基础。
3. 指令分类:
- 如果指令是内存操作(isLoad/isStore),则调用ldstQueue.insertLoad/insertStore()将其插入LSQ。
- 如果指令是计算或其他类型,则调用instQueue.insert()将其插入IQ。
- 在插入IQ时,Scheduler会立即分析其依赖关系,并更新依赖图。
3.2 Issue 阶段 (InstructionQueue::scheduleReadyInsts)
这是真正的乱序发生点。指令不再按照程序顺序执行,而是"谁先准备好,谁先执行"。
- 选择就绪指令:
Scheduler会遍历IQ中的指令,寻找那些所有源寄存器都已就绪的指令。 - 分配功能单元(FU):
Scheduler会为就绪指令寻找一个可用的功能单元(如整数ALU, 浮点乘法器等)。 - 发射: 如果找到了可用的FU,指令就被"发射"出去,从IQ中移除,并放入一个名为
instsToExecute的待执行队列中。
举例说明:
假设有两条指令:
1. ADD R3, R1, R2
2. SUB R4, R3, R5
ADD指令进入IQ,它的源寄存器R1和R2都是就绪的,所以它可以被立即发射。SUB指令进入IQ,它依赖于R3。此时,Scheduler会在依赖图中记录: "SUB指令正在等待R3"。- 即使
SUB指令比其他后来的、但不依赖R3的指令更早进入IQ,它也必须等待。
3.3 Execute 阶段 (IEW::executeInsts)
- 执行: 从
instsToExecute队列中取出指令,调用inst->execute()。- 对于内存指令,会调用
ldstQueue中的相应执行函数,与缓存系统交互。
- 对于内存指令,会调用
- 异常检测:
- 分支预测错误: 如果一条分支指令的实际跳转方向/地址与预测不符,它会被标记为
mispredicted。IEW会立即调用squashDueToBranch(),通知流水线前端(Fetch)从正确的地址重新取指,并废弃掉所有错误路径上的指令。这是一个Squash(冲刷)信号的来源。 - 内存顺序冲突:
ldstQueue会检测到此问题(例如,一个load比一个更早的、地址相同的store先执行了)。IEW会调用squashDueToMemOrder(),同样触发流水线冲刷,但这次需要重新执行发生冲突的load以及它之后的所有指令。
- 分支预测错误: 如果一条分支指令的实际跳转方向/地址与预测不符,它会被标记为
3.4 Writeback 阶段 (IEW::writebackInsts & InstructionQueue::wakeDependents)
- 广播结果: 指令执行完毕后,其结果(如果有)和目标物理寄存器ID会被广播出去。
- 更新Scoreboard:
Scoreboard是一个位图,用于跟踪每个物理寄存器是否准备就绪。写回阶段会更新这个位图,将指令的目标寄存器标记为"就绪"。 - 唤醒依赖者: 这是至关重要的一步。
instQueue.wakeDependents()被调用。- 它会查找依赖图中所有等待这个刚刚写回结果的指令。
- 对于每一条等待指令,它会检查其对应的源操作数,并将其标记为"就绪"。
- 如果一条等待指令的所有源操作数现在都已就绪,那么它在下一个周期就可以被
scheduleReadyInsts发射了。
继续上面的例子:
- 当ADD指令执行完毕,进入写回阶段。
- 它的目标寄存器R3被标记为就绪。
- wakeDependents被调用,它发现SUB指令正在等待R3。
- 于是,SUB指令的R3源操作数被标记为就绪。假设R5也已就绪,那么SUB指令在下一个周期就可以被发射执行了。
4. 总结
IEW阶段通过指令队列(IQ)和加载/存储队列(LSQ)实现了指令的乱序发射和执行。其核心机制是基于依赖关系图和唤醒逻辑: 指令完成执行后,会"唤醒"所有等待其结果的后续指令,从而驱动整个乱序引擎的运转。同时,IEW也肩负着检测分支预测错误和内存访问冲突的重任,是确保程序正确执行的关键保障。理解了dispatch, issue, execute, writeback这四个环节以及它们之间的数据流和控制流,就掌握了O3 CPU的精髓。
5. 深入细节:分布式调度器(Scheduler)与发射逻辑
现代O3 CPU的实现已经从一个单一、庞大的指令队列(monolithic IQ)演变为一个更精细化的分布式调度系统。GEM5中的Scheduler和IssueQue类正是这一思想的体现。这种设计旨在降低硬件复杂性、减少功耗并可能提高时钟频率。
5.1 分布式指令队列架构 (Scheduler & IssueQue)
取代原先单一的InstructionQueue,新的架构由两个核心组件构成:
-
Scheduler: 扮演着中央协调者的角色。它本身不存储指令,但负责:- 指令分派: 决定一条新来的指令应该进入哪个
IssueQue。 - 全局仲裁: 在所有
IssueQue都选出待发射指令后,进行最终的资源仲裁(如读寄存器堆端口冲突)。 - 唤醒协调: 管理不同
IssueQue之间的唤醒事件。
- 指令分派: 决定一条新来的指令应该进入哪个
-
IssueQue: 多个IssueQue实例,每个都是一个小型的、独立的指令队列。通常,它们会根据指令类型进行划分,例如一个用于整数和内存操作,一个用于浮点操作。每个IssueQue负责:- 存储一小部分指令。
- 维护自己的局部依赖图。
- 独立地进行指令选择(selection),找出自己队列中的就绪指令。
这种架构的好处是,每个IssueQue的规模变小了,使得在其中寻找就绪指令的逻辑(通常是硬件实现中最复杂和耗电的部分之一)变得更简单、更快。
5.2 分发详解:两阶段分发流程
在IEW.cc中,当enableDispatchStage被启用时,分发过程分为两个步骤,这提供了一个更灵活的缓冲机制。
-
classifyInstToDispQue(分类到分发队列)- 目的: 这是第一阶段,作为进入
Scheduler前的预处理/缓冲池。 - 逻辑:
- 从Rename阶段接收指令(
insts[tid])。 - 调用
getInstDQType()判断指令类型(例如,IntDQ,FVDQ,MemDQ)。 - 将指令放入对应类型的分发队列
dispQue[type]中。dispQue是一个简单的FIFO队列。
- 从Rename阶段接收指令(
- 作用: 这一步将不同类型的指令流分离开,并提供了一个缓冲层。如果下游的某个
IssueQue满了,只有对应类型的dispQue会开始积压,而不会立即阻塞整个Dispatch阶段。
- 目的: 这是第一阶段,作为进入
-
dispatchInstFromDispQue(从分发队列到IQ)- 目的: 这是第二阶段,正式将指令插入到
Scheduler管理的IssueQue中。 - 逻辑:
- 遍历所有的
dispQue。 - 从每个
dispQue中取出指令。 - 调用
scheduler->ready()检查Scheduler中对应的IssueQue是否还有空间。 - 如果
Scheduler准备好了,就调用instQueue.insert(inst, ...)(这里的instQueue实际上会把请求转发给Scheduler)将指令正式插入。
- 遍历所有的
- 总结: 这个两阶段设计解耦了从Rename接收指令和向
Scheduler插入指令两个过程,提高了流水线的弹性和吞吐量。
- 目的: 这是第二阶段,正式将指令插入到
5.3 发射与选择详解 (Scheduler::issueAndSelect & IssueQue::tick)
这是分布式调度最核心的部分,整个过程被分解到Scheduler和IssueQue的tick函数中,可以概括为 “局部选择,全局仲裁”。
Scheduler::issueAndSelect()是每周期由IEW::tick()调用的驱动函数,它协调所有IssueQue完成以下多步操作:
-
唤醒 (Wakeup):
- 当一条指令执行完毕,
Scheduler::writebackWakeup()被调用。 Scheduler使用wakeMatrix(一个唤醒关系矩阵)来确定这个结果需要通知哪些IssueQue。- 被通知的
IssueQue调用自己的wakeUpDependents(),更新其内部的依赖关系,并将新就绪的指令放入就绪队列readyQ。
- 当一条指令执行完毕,
-
调度/选择 (Schedule/Select) - 在每个
IssueQue内部并行进行:- 每个
IssueQue的tick()函数会调用scheduleInst()和selectInst()。 scheduleInst(): 处理readyQ,准备进行选择。selectInst(): 这是局部选择阶段。IssueQue会使用一个Selector对象(例如PAgeSelector, 它实现了基于年龄和分组的选择策略)来遍历readyQ。Selector会根据一定的策略(如最老优先)选出若干条最佳的待发射指令,并将它们放入IssueQue的选择队列selectQ中。此时,只考虑了指令是否就绪,尚未考虑跨IssueQue的资源冲突。
- 每个
-
仲裁 (Arbitrate) - 在
Scheduler中集中进行:Scheduler::issueAndSelect()会遍历所有的issueQues。- 它会收集所有
IssueQue的selectQ中的候选指令。 - 全局仲裁开始。
Scheduler会检查这些候选指令是否会竞争同一资源,最典型的就是物理寄存器堆的读端口。 checkRfPortBusy()函数用于检查读端口是否冲突。如果多条指令需要同时读取同一个端口,Scheduler会根据优先级进行裁决,失败的指令(arbFailedInsts)会在下一个周期重试。
-
发射 (Issue):
- 通过了全局仲裁的指令,最终被
Scheduler放入instsToFu队列,正式发射到功能单元。
- 通过了全局仲裁的指令,最终被
5.4 推测性唤醒 (SpecWakeupChannel)
这是一个高级性能优化。
- 问题: 传统的唤醒机制必须等待指令完全执行完毕(例如,一个长延迟的除法运算)才能唤醒其依赖者。
- 解决方案: 对于某些延迟可知且固定的操作,可以在指令发射时就预知它何时会完成。因此,可以提前安排一个唤醒事件,在结果实际可用之前就推测性地唤醒依赖指令。
SpecWakeupChannel: 定义了哪些IssueQue之间可以进行这种推测性唤醒。例如,整数IssueQue中的一条指令可能会推测性地唤醒浮点IssueQue中的依赖指令。- 风险与回滚: 如果推测出错(例如,指令被squash了),这个推测性唤醒必须被取消。
Scheduler中的specWakeEvents和SpecWakeupCompletion事件类就是用来管理和取消这些在飞行中的推测性事件的。