From 3d3031dae279919532bf93f641df56c19392b82d Mon Sep 17 00:00:00 2001 From: Xu Boran Date: Wed, 18 Jun 2025 16:38:47 +0800 Subject: [PATCH 01/23] cpu-o3: pred: functional implementation of 2-taken. conditional missprediction see a significant increase after this commit, it might be that TAGE's history are affected by two-taken and thus needs fixing Change-Id: Ic4d0ea2cb7b05ed466e91ae7a606f6e8c376fe7d --- configs/common/Options.py | 2 + configs/example/xiangshan.py | 1 + src/cpu/pred/BranchPredictor.py | 3 +- src/cpu/pred/btb/btb_ittage.cc | 11 ++ src/cpu/pred/btb/btb_ittage.hh | 1 + src/cpu/pred/btb/btb_mgsc.cc | 13 ++ src/cpu/pred/btb/btb_mgsc.hh | 1 + src/cpu/pred/btb/btb_tage.cc | 11 ++ src/cpu/pred/btb/btb_tage.hh | 1 + src/cpu/pred/btb/btb_ubtb.cc | 37 +++- src/cpu/pred/btb/btb_ubtb.hh | 4 +- src/cpu/pred/btb/decoupled_bpred.cc | 266 +++++++++++++++++++++++----- src/cpu/pred/btb/decoupled_bpred.hh | 52 +++++- src/cpu/pred/btb/ras.cc | 13 ++ src/cpu/pred/btb/ras.hh | 1 + src/cpu/pred/btb/stream_struct.hh | 2 + src/cpu/pred/btb/timed_base_pred.hh | 1 + 17 files changed, 362 insertions(+), 58 deletions(-) diff --git a/configs/common/Options.py b/configs/common/Options.py index 58098be57e..1af71a348a 100644 --- a/configs/common/Options.py +++ b/configs/common/Options.py @@ -273,6 +273,8 @@ def addCommonOptions(parser, configure_xiangshan=False): "available subdatabase: basic, tage, ras, loop") parser.add_argument("--disable-sc", default=False, action="store_true", help="disable SC (enabled by default, only for FTBTAGE)") + parser.add_argument("--disable-2taken", default=False, action="store_true", + help="disable 2-taken feature (enabled by default for DecoupledBPUWithBTB)") parser.add_argument("--enable-loop-buffer", default=False, action="store_true", help="enable loop buffer (only for ftb branch predictor)") parser.add_argument("--enable-loop-predictor", default=False, action="store_true", diff --git a/configs/example/xiangshan.py b/configs/example/xiangshan.py index 6f320916c1..2893e23983 100644 --- a/configs/example/xiangshan.py +++ b/configs/example/xiangshan.py @@ -382,6 +382,7 @@ def setKmhV3IdealParams(args, system): cpu.branchPred.btb.numEntries = 16384 # TODO: BTB TAGE do not bave base table, do not support SC cpu.branchPred.tage.tableSizes = [2048] * 14 # 2ways, 2048 sets + cpu.branchPred.enable2Taken = not args.disable_2taken cpu.branchPred.tage.enableSC = False # TODO(bug): When numBr changes, enabling SC will trigger an assert cpu.branchPred.ftq_size = 256 diff --git a/src/cpu/pred/BranchPredictor.py b/src/cpu/pred/BranchPredictor.py index de7f222d38..e3b4685fc0 100644 --- a/src/cpu/pred/BranchPredictor.py +++ b/src/cpu/pred/BranchPredictor.py @@ -1142,7 +1142,8 @@ class DecoupledBPUWithBTB(BranchPredictor): predictWidth = Param.Unsigned(64, "Maximum range in bytes that a single prediction can cover") numStages = Param.Unsigned(4, "Maximum number of stages in the pipeline") - ubtb = Param.UBTB(UBTB(), "UBTB predictor") + ubtb1 = Param.UBTB(UBTB(), "Primary UBTB predictor") + ubtb2 = Param.UBTB(UBTB(), "Secondary UBTB predictor for 2-taken") abtb = Param.DefaultBTB(ABTB(), "ABTB predictor") btb = Param.DefaultBTB(DefaultBTB(), "BTB") tage = Param.BTBTAGE(BTBTAGE(), "TAGE predictor") diff --git a/src/cpu/pred/btb/btb_ittage.cc b/src/cpu/pred/btb/btb_ittage.cc index f3df9cdec9..5ade86fdc6 100644 --- a/src/cpu/pred/btb/btb_ittage.cc +++ b/src/cpu/pred/btb/btb_ittage.cc @@ -188,6 +188,17 @@ BTBITTAGE::getPredictionMeta() { return meta; } +std::shared_ptr +BTBITTAGE::getSecondPredictionMeta() +{ + // Create a new meta object to checkpoint the ITTAGE state for the second prediction. + auto second_meta = std::make_shared(); + second_meta->tagFoldedHist = tagFoldedHist; + second_meta->altTagFoldedHist = altTagFoldedHist; + second_meta->indexFoldedHist = indexFoldedHist; + return second_meta; +} + void BTBITTAGE::update(const FetchStream &stream) { diff --git a/src/cpu/pred/btb/btb_ittage.hh b/src/cpu/pred/btb/btb_ittage.hh index 22f8eea594..4eb050b9df 100644 --- a/src/cpu/pred/btb/btb_ittage.hh +++ b/src/cpu/pred/btb/btb_ittage.hh @@ -99,6 +99,7 @@ class BTBITTAGE : public TimedBaseBTBPredictor std::vector &stagePreds) override; std::shared_ptr getPredictionMeta() override; + std::shared_ptr getSecondPredictionMeta() override; void specUpdateHist(const boost::dynamic_bitset<> &history, FullBTBPrediction &pred) override; diff --git a/src/cpu/pred/btb/btb_mgsc.cc b/src/cpu/pred/btb/btb_mgsc.cc index 9074134c54..aed81e784b 100755 --- a/src/cpu/pred/btb/btb_mgsc.cc +++ b/src/cpu/pred/btb/btb_mgsc.cc @@ -481,6 +481,19 @@ BTBMGSC::getPredictionMeta() { return meta; } +std::shared_ptr +BTBMGSC::getSecondPredictionMeta() +{ + // Create a new meta object for the second prediction's history state. + auto second_meta = std::make_shared(); + second_meta->indexBwFoldedHist = indexBwFoldedHist; + second_meta->indexLFoldedHist = indexLFoldedHist; + second_meta->indexIFoldedHist = indexIFoldedHist; + second_meta->indexGFoldedHist = indexGFoldedHist; + second_meta->indexPFoldedHist = indexPFoldedHist; + return second_meta; +} + /** * @brief Prepare BTB entries for update by filtering and processing * diff --git a/src/cpu/pred/btb/btb_mgsc.hh b/src/cpu/pred/btb/btb_mgsc.hh index fafb154f24..1a62662307 100755 --- a/src/cpu/pred/btb/btb_mgsc.hh +++ b/src/cpu/pred/btb/btb_mgsc.hh @@ -149,6 +149,7 @@ class BTBMGSC : public TimedBaseBTBPredictor std::vector &stagePreds) override; std::shared_ptr getPredictionMeta() override; + std::shared_ptr getSecondPredictionMeta() override; // speculative update all folded history, according history and pred.taken void specUpdateHist(const boost::dynamic_bitset<> &history, FullBTBPrediction &pred) override; diff --git a/src/cpu/pred/btb/btb_tage.cc b/src/cpu/pred/btb/btb_tage.cc index ecb8b592ac..8aabad6a2f 100644 --- a/src/cpu/pred/btb/btb_tage.cc +++ b/src/cpu/pred/btb/btb_tage.cc @@ -303,6 +303,17 @@ BTBTAGE::getPredictionMeta() { return meta; } +std::shared_ptr +BTBTAGE::getSecondPredictionMeta() +{ + // Create a new meta object to checkpoint the history state for the second prediction. + auto second_meta = std::make_shared(); + second_meta->tagFoldedHist = tagFoldedHist; + second_meta->altTagFoldedHist = altTagFoldedHist; + second_meta->indexFoldedHist = indexFoldedHist; + return second_meta; +} + /** * @brief Prepare BTB entries for update by filtering and processing * diff --git a/src/cpu/pred/btb/btb_tage.hh b/src/cpu/pred/btb/btb_tage.hh index df56e027a1..bb6f35dbaf 100644 --- a/src/cpu/pred/btb/btb_tage.hh +++ b/src/cpu/pred/btb/btb_tage.hh @@ -111,6 +111,7 @@ class BTBTAGE : public TimedBaseBTBPredictor std::vector &stagePreds) override; std::shared_ptr getPredictionMeta() override; + std::shared_ptr getSecondPredictionMeta() override; // speculative update 3 folded history, according history and pred.taken // the other specUpdateHist methods are left blank diff --git a/src/cpu/pred/btb/btb_ubtb.cc b/src/cpu/pred/btb/btb_ubtb.cc index 701a462566..35c964e1d7 100644 --- a/src/cpu/pred/btb/btb_ubtb.cc +++ b/src/cpu/pred/btb/btb_ubtb.cc @@ -90,7 +90,7 @@ UBTB::PredStatistics(const TickedUBTBEntry entry, Addr startAddr) { if (entry.valid) { Addr mbtb_end = (startAddr + predictWidth) & ~mask(floorLog2(predictWidth) - 1); - assert(entry.pc >= startAddr && entry.pc < mbtb_end); + //assert(entry.pc >= startAddr && entry.pc < mbtb_end); DPRINTF(UBTB, "UBTB: lookup hit: \n"); ubtbStats.predHit += 1; printTickedUBTBEntry(entry); @@ -272,6 +272,41 @@ UBTB::updateUsingS3Pred(FullBTBPrediction &s3Pred) } } +void +UBTB::update2Taken(FullBTBPrediction &s3Pred) +{ + auto s3TakenEntry = s3Pred.getTakenEntry(); + if (!s3TakenEntry.valid) { + return; + } + + auto iter = lookup(s3Pred.bbStart); + + if (iter != ubtb.end()) { + // Hit: Unconditionally replace the existing entry. + replaceOldEntry(iter, s3Pred); + } else { + // Miss: Find a victim and create a new entry. + UBTBIter toBeReplacedIter; + bool foundInvalidEntry = false; + + for (auto it = ubtb.begin(); it != ubtb.end(); ++it) { + if (!it->valid) { + toBeReplacedIter = it; + foundInvalidEntry = true; + break; + } + } + + if (!foundInvalidEntry) { + std::make_heap(mruList.begin(), mruList.end(), older()); + toBeReplacedIter = mruList.front(); + } + + replaceOldEntry(toBeReplacedIter, s3Pred); + } +} + void UBTB::update(const FetchStream &stream) diff --git a/src/cpu/pred/btb/btb_ubtb.hh b/src/cpu/pred/btb/btb_ubtb.hh index 5e5b7511f1..cd1a11c316 100644 --- a/src/cpu/pred/btb/btb_ubtb.hh +++ b/src/cpu/pred/btb/btb_ubtb.hh @@ -129,6 +129,8 @@ class UBTB : public TimedBaseBTBPredictor */ void updateUsingS3Pred(FullBTBPrediction &s3Pred); + void update2Taken(FullBTBPrediction &s3Pred); + /** for statistics only * @param stream The fetch stream containing execution results and prediction metadata */ @@ -170,8 +172,6 @@ class UBTB : public TimedBaseBTBPredictor } } - - private: /** this struct holds the lastest prediction made by uBTB, diff --git a/src/cpu/pred/btb/decoupled_bpred.cc b/src/cpu/pred/btb/decoupled_bpred.cc index d26a687f20..2752aecbcc 100644 --- a/src/cpu/pred/btb/decoupled_bpred.cc +++ b/src/cpu/pred/btb/decoupled_bpred.cc @@ -30,7 +30,8 @@ DecoupledBPUWithBTB::DecoupledBPUWithBTB(const DecoupledBPUWithBTBParams &p) predictWidth(p.predictWidth), maxInstsNum(p.predictWidth / 2), historyBits(p.maxHistLen), - ubtb(p.ubtb), + ubtb1(p.ubtb1), + ubtb2(p.ubtb2), abtb(p.abtb), btb(p.btb), tage(p.tage), @@ -49,7 +50,9 @@ DecoupledBPUWithBTB::DecoupledBPUWithBTB(const DecoupledBPUWithBTBParams &p) bpType = DecoupledBTBType; // TODO: better impl (use vector to assign in python) // problem: btb->getAndSetNewBTBEntry - components.push_back(ubtb); + components.push_back(ubtb1); + // we don't push ubtb2 into the component list, because its putPCHistory() + // and update methods are called explicitly. components.push_back(abtb); // components.push_back(uras); components.push_back(btb); @@ -564,44 +567,66 @@ DecoupledBPUWithBTB::tick() numOverrideBubbles = 0; DPRINTF(Override, "Squashing, BPU state updated.\n"); squashing = false; + predDFF.reset(); // consider putting it in squash(); return; } - // 1. Request new prediction if FSQ not full and we are idle + // 1. Request prediction, finalize it, and get ready to enqueue. + // This all happens if we're idle and not blocked. if (bpuState == BpuState::IDLE && !streamQueueFull()) { requestNewPrediction(); - bpuState = BpuState::PREDICTOR_DONE; - } - // 2. Handle pending prediction if available - if (bpuState == BpuState::PREDICTOR_DONE) { - DPRINTF(Override, "Generating final prediction for PC %#lx\n", s0PC); + // The training logic runs here, based on the previous cycle's DFF state. + trainUbtbFor2Taken(); numOverrideBubbles = generateFinalPredAndCreateBubbles(); - bpuState = BpuState::PREDICTION_OUTSTANDING; - // Clear each predictor's output + // Check if the second prediction is still valid after overrides. + validateSecondFBPrediction(); + + // Now, update the DFF for the *next* cycle using the results of this one. + updateDFF(); + + bpuState = BpuState::PREDS_READY; + + // Clear predictor outputs. for (int i = 0; i < numStages; i++) { predsOfEachStage[i].btbEntries.clear(); } } - // 3. Process enqueue operations and bubble counter + // try Enqueue FTQ tryEnqFetchTarget(); + // 2. Enqueue predictions if there are no bubbles. // check if: // 1. FSQ has space // 2. there's no bubble - // 3. PREDICTION_OUTSTANDING - if (validateFSQEnqueue()) { - // Create new FSQ entry with the current prediction - makeNewPrediction(true); + // 3. Prediction is ready - DPRINTF(Override, "FSQ entry enqueued, prediction state reset\n"); - bpuState = BpuState::IDLE; + // Try to enqueue the first (or only) prediction. + if (bpuState == BpuState::PREDS_READY && validateFSQEnqueue()) { + makeNewPrediction(true, false); // Enqueues finalPred + + if (hasSecondPrediction) { + // 2-taken produced a second prediction. + finalPred = secondPrediction; + hasSecondPrediction = false; // It's in the hot seat now. + bpuState = BpuState::WAITING_FOR_SECOND_ENQ; + } else { + // just one single prediction, this cycle is done. + bpuState = BpuState::IDLE; + } } + // If we're waiting on the second prediction, try to enqueue it. + // This can happen in the same tick as the first if the FSQ has space. + if (bpuState == BpuState::WAITING_FOR_SECOND_ENQ && validateFSQEnqueue()) { + tryEnqFetchTarget(); + makeNewPrediction(true, true); // Enqueues what was the second prediction + bpuState = BpuState::IDLE; // All done. Finally. + } - // Decrement override bubbles counter + // Decrement override bubbles counter, if applicable if (numOverrideBubbles > 0) { numOverrideBubbles--; dbpBtbStats.overrideBubbleNum++; @@ -609,7 +634,6 @@ DecoupledBPUWithBTB::tick() } DPRINTF(Override, "Prediction cycle complete\n"); - } /** @@ -621,19 +645,48 @@ DecoupledBPUWithBTB::tick() void DecoupledBPUWithBTB::requestNewPrediction() { + DPRINTF(Override, "Requesting new prediction for PC %#lx\n", s0PC); - DPRINTF(Override, "Requesting new prediction for PC %#lx\n", s0PC); + // Initialize prediction state for each stage + for (int i = 0; i < numStages; i++) { + predsOfEachStage[i].bbStart = s0PC; + } - // Initialize prediction state for each stage - for (int i = 0; i < numStages; i++) { - predsOfEachStage[i].bbStart = s0PC; - } + // Query each predictor component with current PC and history + for (int i = 0; i < numComponents; i++) { + components[i]->putPCHistory(s0PC, s0History, predsOfEachStage); //s0History not used + } + + // Get prediction from secondary uBTB, if the first prediction is valid. + // This uses the same s0PC for lookup, as ubtb2 is trained to hold the + // subsequent block's info. + // TODO: handles the case when there's abtb prediction + if (enable2Taken && predsOfEachStage[0].btbEntries.size() > 0) { + + std::vector ubtb2Preds(1); + ubtb2Preds[0].bbStart = s0PC; + ubtb2->putPCHistory(s0PC, s0History, ubtb2Preds); // ubtb2 lookup based on s0PC + + // After getting the prediction, morph its start PC to reflect its true position, + // which is the target of the first branch. + if (ubtb2Preds[0].btbEntries.size() > 0) { + ubtb2Preds[0].bbStart = predsOfEachStage[0].getTarget(predictWidth); + secondPrediction = ubtb2Preds[0]; - // Query each predictor component with current PC and history - for (int i = 0; i < numComponents; i++) { - components[i]->putPCHistory(s0PC, s0History, predsOfEachStage); //s0History not used - } + hasSecondPrediction = true; + // Instead of asserting, let's check the condition and discard the second prediction if it doesn't fit. + if (!(ubtb2Preds[0].controlAddr() >= ubtb2Preds[0].bbStart && + ubtb2Preds[0].controlAddr() < ubtb2Preds[0].getFallThrough(predictWidth))) { + hasSecondPrediction = false; + secondPrediction.btbEntries.clear(); + DPRINTF(DecoupleBP, "Second FB prediction from uBTB2 failed range check, discarding.\n"); + } + DPRINTF(DecoupleBP, "Got second FB prediction from uBTB2 for target %#lx\n", ubtb2Preds[0].bbStart); + } else { + hasSecondPrediction = false; + } + } } void DecoupledBPUWithBTB::overrideStats(OverrideReason overrideReason) @@ -704,10 +757,6 @@ DecoupledBPUWithBTB::generateFinalPredAndCreateBubbles() overrideReason = reason; } - // update ubtb using mbtb prediction - if (predsOfEachStage[numStages - 1].btbEntries.size() > 0) { - ubtb->updateUsingS3Pred(predsOfEachStage[numStages - 1]); - } // 4. Record override bubbles and update statistics if (first_hit_stage > 0) { @@ -1049,7 +1098,11 @@ void DecoupledBPUWithBTB::update(unsigned stream_id, ThreadID tid) updateStatistics(stream); // Update predictor components - updatePredictorComponents(stream); + if (!stream.isSecondFBPred) { + updatePredictorComponents(stream); + } else { + DPRINTF(DecoupleBP, "Skipping predictor update for second FB prediction at %#lx\n", stream.startPC); + } it = fetchStreamQueue.erase(it); dbpBtbStats.fsqEntryCommitted++; @@ -1331,8 +1384,12 @@ DecoupledBPUWithBTB::commitBranch(const DynInstPtr &inst, bool mispred) } // ---------- Update predictor components ---------- - for (auto component : components) { - component->commitBranch(entry, inst); + // Do not update component stats for the second prediction, as its + // metadata might be invalid for this purpose and cause a segfault. + if (!entry.isSecondFBPred) { + for (auto &component : components) { + component->commitBranch(entry, inst); + } } } @@ -1554,12 +1611,6 @@ DecoupledBPUWithBTB::validateFSQEnqueue() return false; } - // 1. Check if a prediction is available to enqueue - if (bpuState != BpuState::PREDICTION_OUTSTANDING) { - DPRINTF(Override, "No prediction available to enqueue into FSQ\n"); - return false; - } - // 2. Validate PC value if (s0PC == MaxAddr) { DPRINTF(DecoupleBP, "Invalid PC value %#lx, cannot make prediction\n", s0PC); @@ -1747,7 +1798,7 @@ DecoupledBPUWithBTB::pHistShiftIn(int shamt, bool taken, boost::dynamic_bitset<> * @return FetchStream The created fetch stream */ FetchStream -DecoupledBPUWithBTB::createFetchStreamEntry() +DecoupledBPUWithBTB::createFetchStreamEntry(bool is_second_pred) { // Create a new fetch stream entry FetchStream entry; @@ -1780,10 +1831,15 @@ DecoupledBPUWithBTB::createFetchStreamEntry() entry.predTick = finalPred.predTick; entry.predSource = finalPred.predSource; entry.overrideReason = finalPred.overrideReason; + entry.isSecondFBPred = is_second_pred; // Save predictors' metadata for (int i = 0; i < numComponents; i++) { - entry.predMetas[i] = components[i]->getPredictionMeta(); + if (is_second_pred) { + entry.predMetas[i] = components[i]->getSecondPredictionMeta(); + } else { + entry.predMetas[i] = components[i]->getPredictionMeta(); + } } // Initialize default resolution state @@ -1818,12 +1874,12 @@ DecoupledBPUWithBTB::fillAheadPipeline(FetchStream &entry) // this function enqueues fsq and update s0PC and s0History void -DecoupledBPUWithBTB::makeNewPrediction(bool create_new_stream) +DecoupledBPUWithBTB::makeNewPrediction(bool create_new_stream, bool is_second_pred) { DPRINTF(DecoupleBP, "Creating new prediction for PC %#lx\n", s0PC); // 1. Create a new fetch stream entry with prediction information - FetchStream entry = createFetchStreamEntry(); + FetchStream entry = createFetchStreamEntry(is_second_pred); // 2. Update global PC state to target or fall-through s0PC = finalPred.getTarget(predictWidth);; @@ -1847,14 +1903,13 @@ DecoupledBPUWithBTB::makeNewPrediction(bool create_new_stream) // 7. Debug output and update statistics dumpFsq("after insert new stream"); - DPRINTF(DecoupleBP, "Inserted fetch stream %lu starting at PC %#lx\n", + DPRINTF(DecoupleBP, "Inserted fetch stream %lu starting at PC %#lx\n", fsqId, entry.startPC); - + // 8. Update FSQ ID and increment statistics fsqId++; printStream(entry); dbpBtbStats.fsqEntryEnqueued++; - } void @@ -2065,6 +2120,121 @@ DecoupledBPUWithBTB::recoverHistoryForSquash( } +bool DecoupledBPUWithBTB::check2TakenConditions(FullBTBPrediction& dff, const FullBTBPrediction& s3Pred) { + + assert(dff.getTarget(predictWidth) == s3Pred.bbStart); + + // 1. Both predictions must have at least one branch. + if (dff.btbEntries.empty() || s3Pred.btbEntries.empty()) { + return false; + } + + auto dffEntry = dff.getTakenEntry(); + auto& s3PredEntry = s3Pred.btbEntries[0]; + + // 2. The first branch must be taken for a 2-taken sequence to form. + if (!dff.isTaken()) { + return false; + } + + // 3. Check branch type compatibility based on spec table. + + // Rule: 'multi-target indirect' as 1st branch is not allowed. + if (dffEntry.isIndirect) { + return false; + } + + // Rule: 'multi-target indirect' as 2nd branch is not allowed. + if (s3PredEntry.isIndirect) { + return false; + } + + // Rule: 'cond' as 2nd branch is not allowed. + if (s3PredEntry.isCond) { + return false; + } + + // Rule: 'ret -> ret' is not allowed to avoid multiple RAS reads. + if (dffEntry.isReturn && s3PredEntry.isReturn) { + return false; + } + + // Rule: 'call -> call' is not allowed to avoid multiple RAS writes. + if (dffEntry.isCall && s3PredEntry.isCall) { + return false; + } + + // (call -> ret is allowed, so no check needed) + + // All conditions passed. + return true; +} + +// Renamed function containing only uBTB training logic. +void DecoupledBPUWithBTB::trainUbtbFor2Taken() +{ + // Get the S3 prediction from s3 predictors. This is our 'ground truth' inside the BP. + auto& s3_pred = predsOfEachStage[numStages-1]; + + // Update ubtb1 based on the S3 prediction. + if (s3_pred.btbEntries.size() > 0) { + ubtb1->updateUsingS3Pred(s3_pred); + } + + // Perform 2-taken learning for ubtb2. + // This compares the *current* S3 prediction with the *previous* cycle's S3 prediction (stored in DFF). + if (enable2Taken) { + if (predDFF.valid && check2TakenConditions(predDFF.prevS3Pred, s3_pred)) { + // trainSecondUBTB logic: + // Train uBTB2: when indexed by dff.bbstart, predict the content of s3_pred. + // This way, when both ubtb1 and ubtb2 use the same S0PC as input, they predict consecutive FBs. + FullBTBPrediction trainingPred = s3_pred; + // The training entry should be indexed by the PC of the dff block. + trainingPred.bbStart = predDFF.prevS3Pred.bbStart; + + ubtb2->update2Taken(trainingPred); + } + } +} + +// New function to update the DFF buffer for the next cycle. +void DecoupledBPUWithBTB::updateDFF() +{ + // CRITICAL: Update the DFF with the fetch block that will precede + // the first fetch block of the next prediction cycle. This stored block + // is used for 2-taken training. + + if (hasSecondPrediction) { + // Case 1: A valid second prediction exists. + // It's the most recent block, so we store it for the next cycle's training. + predDFF.storePrediction(secondPrediction); + } else { + // Case 2: No second prediction. + // This could be because the primary prediction was overridden, or uBTB2 + // simply didn't find a 2-taken entry. In either situation, `finalPred` + // represents the one and only fetch block for this cycle. We store it. + predDFF.storePrediction(finalPred); + } +} + +void DecoupledBPUWithBTB::validateSecondFBPrediction() +{ + if (!hasSecondPrediction) { + return; // No second prediction to validate. + } + + // The second prediction is only valid if the first prediction from uBTB1 + // was not overridden by a later-stage predictor. + // We check if the final prediction's source is stage 0. + if (finalPred.predSource != 0) { + DPRINTF(DecoupleBP, "uBTB1 prediction was overridden (finalPred source is stage %d), " + "invalidating second FB prediction.\n", finalPred.predSource); + hasSecondPrediction = false; + // We're clearing secondPrediction just to be tidy. + secondPrediction.btbEntries.clear(); + } +} + } // namespace btb_pred } // namespace branch_prediction diff --git a/src/cpu/pred/btb/decoupled_bpred.hh b/src/cpu/pred/btb/decoupled_bpred.hh index 48ae7c5fcc..060e9c4b83 100644 --- a/src/cpu/pred/btb/decoupled_bpred.hh +++ b/src/cpu/pred/btb/decoupled_bpred.hh @@ -79,9 +79,36 @@ class DecoupledBPUWithBTB : public BPredUnit JumpAheadPredictor jap; bool enableJumpAheadPredictor{false}; + // 2taken feature support + bool enable2Taken{true}; // will be overridden by the constructor + + // Add DFF buffer structure to store previous S3 prediction + struct PredictionDFF + { + bool valid{false}; + // Previous S3 final prediction result, + // this field sometimes stores the second prediction from the previous cycle + FullBTBPrediction prevS3Pred; + + void reset() { + valid = false; + } + + void storePrediction(const FullBTBPrediction& s3_pred) { + prevS3Pred = s3_pred; + valid = true; + } + }; + private: std::string _name; + PredictionDFF predDFF; // DFF buffer to store previous pipeline result + + // Storage for second fetch block prediction + FullBTBPrediction secondPrediction; // Second fetch block prediction from uBTB2 + bool hasSecondPrediction{false}; // Whether we have a valid second FB prediction + FetchTargetQueue fetchTargetQueue; std::map fetchStreamQueue; @@ -97,7 +124,8 @@ class DecoupledBPUWithBTB : public BPredUnit const Addr MaxAddr{~(0ULL)}; - UBTB *ubtb{}; + UBTB *ubtb1{}; // Primary uBTB for first fetch block + UBTB *ubtb2{}; // Secondary uBTB for 2-taken patterns DefaultBTB *abtb{}; DefaultBTB *btb{}; BTBTAGE *tage{}; @@ -141,9 +169,9 @@ class DecoupledBPUWithBTB : public BPredUnit enum class BpuState { - IDLE, // Waiting to start a prediction. - PREDICTOR_DONE, // Prediction in progress (conceptually replaces `predictorFinished`). - PREDICTION_OUTSTANDING, // Prediction is ready to be enqueued (replaces `receivedPred`). + IDLE, // Waiting to start a prediction. + PREDS_READY, // One or two predictions are finalized and ready to enqueue. + WAITING_FOR_SECOND_ENQ // First prediction enqueued, second is waiting for space. }; BpuState bpuState; @@ -168,6 +196,13 @@ class DecoupledBPUWithBTB : public BPredUnit using JAInfo = JumpAheadPredictor::JAInfo; JAInfo jaInfo; + // Helper method to check 2-taken conditions + bool check2TakenConditions(FullBTBPrediction& dff_pred, const FullBTBPrediction& s3_pred); + void update2TakenEntry(Addr prevAddr, const FullBTBPrediction& dff_pred, const FullBTBPrediction& s3_pred); + void trainUbtbFor2Taken(); + void updateDFF(); + void validateSecondFBPrediction(); + bool validateFSQEnqueue(); void tryEnqFetchTarget(); @@ -175,11 +210,16 @@ class DecoupledBPUWithBTB : public BPredUnit // Helper function to validate FTQ and FSQ state before enqueueing bool validateFTQEnqueue(); - void makeNewPrediction(bool create_new_stream); + void makeNewPrediction(bool enqueue, bool is_second_pred = false); FtqEntry createFtqEntryFromStream(const FetchStream &stream, const FetchTargetEnqState &ftq_enq_state); - FetchStream createFetchStreamEntry(); + /** + * @brief Creates a new FetchStream entry with prediction information + * + * @return FetchStream The created fetch stream + */ + FetchStream createFetchStreamEntry(bool is_second_pred); void updateHistoryForPrediction(FetchStream &entry); diff --git a/src/cpu/pred/btb/ras.cc b/src/cpu/pred/btb/ras.cc index 8db1dbf29c..9b8b899b42 100644 --- a/src/cpu/pred/btb/ras.cc +++ b/src/cpu/pred/btb/ras.cc @@ -78,6 +78,19 @@ BTBRAS::getPredictionMeta() return meta; } +std::shared_ptr +BTBRAS::getSecondPredictionMeta() +{ + // Create a new meta object to checkpoint the RAS state for the second prediction. + auto second_meta = std::make_shared(); + second_meta->ssp = ssp; + second_meta->sctr = sctr; + second_meta->TOSR = TOSR; + second_meta->TOSW = TOSW; + second_meta->target = getTop().retAddr; + return second_meta; +} + void BTBRAS::specUpdateHist(const boost::dynamic_bitset<> &history, FullBTBPrediction &pred) { diff --git a/src/cpu/pred/btb/ras.hh b/src/cpu/pred/btb/ras.hh index 5f614f25f9..c3359dd8fd 100644 --- a/src/cpu/pred/btb/ras.hh +++ b/src/cpu/pred/btb/ras.hh @@ -68,6 +68,7 @@ class BTBRAS : public TimedBaseBTBPredictor std::vector &stagePreds) override; std::shared_ptr getPredictionMeta() override; + std::shared_ptr getSecondPredictionMeta() override; void specUpdateHist(const boost::dynamic_bitset<> &history, FullBTBPrediction &pred) override; diff --git a/src/cpu/pred/btb/stream_struct.hh b/src/cpu/pred/btb/stream_struct.hh index aaa876afba..328b314ceb 100644 --- a/src/cpu/pred/btb/stream_struct.hh +++ b/src/cpu/pred/btb/stream_struct.hh @@ -300,6 +300,8 @@ struct FetchStream unsigned predSource; // source of the prediction(numStage) OverrideReason overrideReason; // reason of the override(for profiling) + bool isSecondFBPred{false}; // New flag for 2-taken + // prediction metas // FIXME: use vec std::array, 7> predMetas; // each component has a meta, TODO diff --git a/src/cpu/pred/btb/timed_base_pred.hh b/src/cpu/pred/btb/timed_base_pred.hh index 204fd7f7e8..900612cd37 100644 --- a/src/cpu/pred/btb/timed_base_pred.hh +++ b/src/cpu/pred/btb/timed_base_pred.hh @@ -39,6 +39,7 @@ class TimedBaseBTBPredictor: public SimObject std::vector &stagePreds) {} virtual std::shared_ptr getPredictionMeta() { return nullptr; } + virtual std::shared_ptr getSecondPredictionMeta() { return nullptr; } virtual void specUpdateHist(const boost::dynamic_bitset<> &history, FullBTBPrediction &pred) {} virtual void specUpdatePHist(const boost::dynamic_bitset<> &history, FullBTBPrediction &pred) {} From 3de33af52f47af6e5da21dc98ac7e1b08a186430 Mon Sep 17 00:00:00 2001 From: Xu Boran Date: Mon, 23 Jun 2025 16:37:00 +0800 Subject: [PATCH 02/23] cpu-o3: pred: implemented realistic 2-taken. this commit is based on the BPU tick implemented in the previous commit: "functional implementation of 2taken". Unlike the previous commit where 2 ubtbs are combined to produce 2 taken predictions, in this commit we modified our ubtb entry format as well as prediction/ training logic to support two taken. Change-Id: Ie9802a231f0e3607f49edb2415a040934a0beef3 --- src/cpu/pred/BranchPredictor.py | 5 +- src/cpu/pred/btb/btb_ubtb.cc | 409 +++++++++++++++++++++------- src/cpu/pred/btb/btb_ubtb.hh | 86 +++++- src/cpu/pred/btb/decoupled_bpred.cc | 184 +++++-------- src/cpu/pred/btb/decoupled_bpred.hh | 17 +- 5 files changed, 467 insertions(+), 234 deletions(-) diff --git a/src/cpu/pred/BranchPredictor.py b/src/cpu/pred/BranchPredictor.py index e3b4685fc0..628c51bb55 100644 --- a/src/cpu/pred/BranchPredictor.py +++ b/src/cpu/pred/BranchPredictor.py @@ -1142,8 +1142,7 @@ class DecoupledBPUWithBTB(BranchPredictor): predictWidth = Param.Unsigned(64, "Maximum range in bytes that a single prediction can cover") numStages = Param.Unsigned(4, "Maximum number of stages in the pipeline") - ubtb1 = Param.UBTB(UBTB(), "Primary UBTB predictor") - ubtb2 = Param.UBTB(UBTB(), "Secondary UBTB predictor for 2-taken") + ubtb = Param.UBTB(UBTB(), "UBTB predictor") abtb = Param.DefaultBTB(ABTB(), "ABTB predictor") btb = Param.DefaultBTB(DefaultBTB(), "BTB") tage = Param.BTBTAGE(BTBTAGE(), "TAGE predictor") @@ -1155,3 +1154,5 @@ class DecoupledBPUWithBTB(BranchPredictor): enableLoopBuffer = Param.Bool(False, "Enable loop buffer to supply inst for loops") enableLoopPredictor = Param.Bool(False, "Use loop predictor to predict loop exit") enableJumpAheadPredictor = Param.Bool(False, "Use jump ahead predictor to skip no-need-to-predict blocks") + + enable2Taken = Param.Bool(False, "Enable 2taken feature") diff --git a/src/cpu/pred/btb/btb_ubtb.cc b/src/cpu/pred/btb/btb_ubtb.cc index 35c964e1d7..989aa07e8b 100644 --- a/src/cpu/pred/btb/btb_ubtb.cc +++ b/src/cpu/pred/btb/btb_ubtb.cc @@ -142,13 +142,46 @@ UBTB::fillStagePredictions(const TickedUBTBEntry &entry, std::vector &history, std::vector &stagePreds) +UBTB::fillSecondPrediction(const BranchInfo &branchInfo, Addr bbStart, FullBTBPrediction &prediction) { + // According to 2-taken design rules, the second branch should never be conditional + assert(!branchInfo.isCond && "Second prediction should never be conditional branch"); + + prediction.btbEntries.clear(); + prediction.condTakens.clear(); + prediction.indirectTargets.clear(); + prediction.bbStart = bbStart; + prediction.predTick = curTick(); + prediction.predSource = 0; // uBTB is stage 0 + + // Create BTBEntry from BranchInfo + BTBEntry entry(branchInfo); + prediction.btbEntries.push_back(entry); + + // Handle indirect branches (including returns and calls) + // TODO: I tend to think indirect branches should not be allowed in the 2nd prediction + // not even return, since the second branch will not be validated by RAS + if (entry.isIndirect) { + DPRINTF(UBTB, "setting indirect target for 2nd prediction pc %#lx to %#lx\n", entry.pc, entry.target); + prediction.indirectTargets.push_back({entry.pc, entry.target}); + if (entry.isReturn) { + prediction.returnTarget = entry.target; + } + } + // For direct unconditional branches, no additional setup needed beyond the BTBEntry +} + +void +UBTB::putPCHistory(Addr startAddr, const boost::dynamic_bitset<> &history, + std::vector &stagePreds) +{ + // Reuse existing lookup and prediction logic meta = std::make_shared(); - auto it = lookup(startAddr); + int hit_index = lookup(startAddr); auto& entry = meta->hit_entry; - entry = (it != ubtb.end()) ? *it : TickedUBTBEntry(); + entry = (hit_index != -1) ? ubtb[hit_index] : TickedUBTBEntry(); PredStatistics(entry, startAddr); @@ -156,48 +189,110 @@ UBTB::putPCHistory(Addr startAddr, const boost::dynamic_bitset<> &history, std:: fillStagePredictions(entry, stagePreds); // Update metadata for later stages - lastPred.hit_entry = it; + lastPred.hit_index = hit_index; +} + +std::pair +UBTB::getTwoTakenPrediction(Addr startAddr, const boost::dynamic_bitset<> &history, + std::vector &stagePreds, + FullBTBPrediction &secondPrediction) +{ + // Reuse existing lookup and prediction logic + meta = std::make_shared(); + int hit_index = lookup(startAddr); + auto& entry = meta->hit_entry; + entry = (hit_index != -1) ? ubtb[hit_index] : TickedUBTBEntry(); + + //PredStatistics(entry, startAddr); + + // Fill primary prediction for each pipeline stage + fillStagePredictions(entry, stagePreds); + + // Update metadata for later stages + lastPred.hit_index = hit_index; + + bool has_second_prediction = false; + + // Check if we have a second prediction to provide + if (entry.valid && entry.valid_2nd) { + DPRINTF(UBTB, "uBTB: Found second prediction in entry, constructing 2nd FB\n"); + + // Calculate target address for second prediction (where the second prediction should start) + Addr second_bb_start = stagePreds[0].getTarget(predictWidth); + + // Construct the second prediction from the stored branch info + fillSecondPrediction(entry.branch_info_2nd, second_bb_start, secondPrediction); + + // Validate range: the second branch should be within its own fetch block + if (secondPrediction.btbEntries.size() > 0) { + assert(secondPrediction.isTaken()); // this is guaranteed by the 2-taken design rules + Addr control_addr = secondPrediction.controlAddr(); + Addr fall_through = secondPrediction.getFallThrough(predictWidth); + + if (control_addr >= second_bb_start && control_addr < fall_through) { + has_second_prediction = true; + DPRINTF(UBTB, "uBTB: Valid second prediction - bbStart: %#lx, controlAddr: %#lx, target: %#lx\n", + second_bb_start, control_addr, secondPrediction.getTarget(predictWidth)); + } else { + // Range check failed, discard second prediction + secondPrediction.btbEntries.clear(); + DPRINTF(UBTB, + "uBTB: Second prediction failed range check - bbStart: %#lx, controlAddr: %#lx, fallThrough: %#lx\n", + second_bb_start, control_addr, fall_through); + } + } + } + + return std::make_pair(hit_index, has_second_prediction); } -UBTB::UBTBIter +int UBTB::lookup(Addr startAddr) { if (startAddr & 0x1) { - return ubtb.end(); // ignore false hit when lowest bit is 1 + return -1; // ignore false hit when lowest bit is 1 } Addr current_tag = getTag(startAddr); DPRINTF(UBTB, "UBTB: Doing tag comparison for tag %#lx\n", current_tag); - auto it = std::find_if(ubtb.begin(), ubtb.end(), - [current_tag](const TickedUBTBEntry &way) { return way.valid && way.tag == current_tag; }); + // Find the matching entry and return its index + for (size_t i = 0; i < ubtb.size(); ++i) { + if (ubtb[i].valid && ubtb[i].tag == current_tag) { + // Found a hit - verify no duplicates + for (size_t j = i + 1; j < ubtb.size(); ++j) { + assert(!(ubtb[j].valid && ubtb[j].tag == current_tag) && + "Multiple hits found in uBTB for the same tag!"); + } + + // Update timestamp for MRU + ubtb[i].tick = curTick(); - if (it != ubtb.end()) { - // Found a hit - verify no duplicates - auto duplicate = std::find_if(std::next(it), ubtb.end(), [current_tag](const TickedUBTBEntry &way) { - return way.valid && way.tag == current_tag; - }); - assert(duplicate == ubtb.end() && "Multiple hits found in uBTB for the same tag!"); + // the following line might be unnecessary, considering the + // heap is updated on every LRU replacement, TODO: confirm this + // std::make_heap(mruList.begin(), mruList.end(), older()); - // go on to update the mruList - it->tick = curTick(); // Update timestamp for MRU - // might be unnecessary, considering the heap is updated on every reaplacement - std::make_heap(mruList.begin(), mruList.end(), older()); + DPRINTF(UBTB, "UBTB: Hit at index %zu for tag %#lx\n", i, current_tag); + return static_cast(i); + } } - return it; + DPRINTF(UBTB, "UBTB: Miss for tag %#lx\n", current_tag); + return -1; // Miss } void -UBTB::replaceOldEntry(UBTBIter oldEntryIter, FullBTBPrediction &newPrediction) +UBTB::replaceOldEntry(int entryIndex, FullBTBPrediction & newPrediction) { + assert(entryIndex >= 0 && entryIndex < static_cast(ubtb.size())); assert(newPrediction.getTakenEntry().valid); - TickedUBTBEntry newEntry = TickedUBTBEntry(newPrediction.getTakenEntry(), curTick()); + + TickedUBTBEntry newEntry = TickedUBTBEntry(newPrediction.getTakenEntry(), curTick()); //valid_2nd initialized to false // important! this is so that target set by RAS or ITTAGE is used newEntry.target = newPrediction.getTarget(predictWidth); - // important: update tag (mbtb and ubtb have different tags, even diffferent tag length) + // important: update tag (mbtb and ubtb have different tags, even different tag length) newEntry.tag = getTag(newPrediction.bbStart); /* save the number of conditional branches before the taken branch * this is useful in the prediction phase: to generate the correct speculative history information @@ -207,104 +302,227 @@ UBTB::replaceOldEntry(UBTBIter oldEntryIter, FullBTBPrediction &newPrediction) newEntry.numNTConds--; assert(newEntry.numNTConds >= 0); } - *oldEntryIter = newEntry; + + ubtb[entryIndex] = newEntry; + + DPRINTF(UBTB, "UBTB: Replaced entry at index %d with new prediction for PC %#lx\n", + entryIndex, newPrediction.controlAddr()); +} + +void +UBTB::addSecondPredictionToEntry(int entryIndex, FullBTBPrediction* secondPred) +{ + assert(entryIndex >= 0 && entryIndex < static_cast(ubtb.size())); + assert(secondPred != nullptr && "Second prediction must not be null"); + assert(secondPred->getTakenEntry().valid && "Second prediction must be valid for 2-taken"); + + auto& entry = ubtb[entryIndex]; + assert(entry.valid && "Entry must be valid to add second prediction"); + + // Only add if not already present + if (!entry.valid_2nd) { + entry.valid_2nd = true; + auto s3TakenEntry = secondPred->getTakenEntry(); + + // Copy branch info (BTBEntry inherits from BranchInfo) + entry.branch_info_2nd = s3TakenEntry; + // Override target with the one from prediction (may be set by RAS/ITTAGE) + entry.branch_info_2nd.target = secondPred->getTarget(predictWidth); + + DPRINTF(UBTB, "UBTB: Added second prediction to entry at index %d: secondary PC %#lx\n", + entryIndex, secondPred->controlAddr()); + } else { + DPRINTF(UBTB, "UBTB: Entry at index %d already has second prediction, skipping\n", entryIndex); + } } void UBTB::updateUsingS3Pred(FullBTBPrediction &s3Pred) { + DPRINTF(UBTB, "1-taken updateUsingS3Pred: hit_index=%d, s3Pred.bbStart=%#lx\n", + lastPred.hit_index, s3Pred.bbStart); + + // Use the common helper function with the hit index from lastPred (no second prediction) + updateEntryAtIndex(lastPred.hit_index, s3Pred, nullptr); +} - UBTBIter s0EntryIter = lastPred.hit_entry; - if (s0EntryIter != ubtb.end()) { - assert(s0EntryIter->valid); //lookup() should only return valid entry +bool +UBTB::check2TakenConditions(FullBTBPrediction& dff, const FullBTBPrediction& s3Pred) +{ + assert(dff.getTarget(predictWidth) == s3Pred.bbStart); + + // 1. Both predictions must have at least one branch. + if (dff.btbEntries.empty() || s3Pred.btbEntries.empty()) { + return false; } - auto s3TakenEntry = s3Pred.getTakenEntry(); - if (s0EntryIter != ubtb.end() && !s3TakenEntry.valid) { - // S0 has a hit entry, but S3 predicts fall through - updateUCtr(s0EntryIter->uctr, false); - if (s0EntryIter->uctr == 0) { - s0EntryIter->valid = false; - } - } else if (s0EntryIter == ubtb.end() && s3TakenEntry.valid) { - /* S0 misses, but S3 predicts taken, - * generate new entry and replace another using LRU - */ - UBTBIter toBeReplacedIter; - // First try to find an invalid entry in the set - bool foundInvalidEntry = false; - - for (auto it = ubtb.begin(); it != ubtb.end(); ++it) { - if (!it->valid) { - toBeReplacedIter = it; - foundInvalidEntry = true; - break; + + auto dffEntry = dff.getTakenEntry(); + auto& s3PredEntry = s3Pred.btbEntries[0]; + + // 2. The first branch must be taken for a 2-taken sequence to form. + if (!dff.isTaken()) { + return false; + } + + // 3. Check branch type compatibility based on spec table. + + // Rule: 'multi-target indirect' as 1st branch is not allowed. + if (dffEntry.isIndirect) { + return false; + } + + // Rule: 'multi-target indirect' as 2nd branch is not allowed. + if (s3PredEntry.isIndirect) { + return false; + } + + // Rule: 'cond' as 2nd branch is not allowed. + if (s3PredEntry.isCond) { + return false; + } + + // Rule: 'ret -> ret' is not allowed to avoid multiple RAS reads. + if (dffEntry.isReturn && s3PredEntry.isReturn) { + return false; + } + + // Rule: 'call -> call' is not allowed to avoid multiple RAS writes. + if (dffEntry.isCall && s3PredEntry.isCall) { + return false; + } + + // (call -> ret is allowed, so no check needed) + + // All conditions passed. + return true; +} + +// theoretically pred is a const reference, but certain functions +// like getTakenEntry() are factually const but not declared as const +void +UBTB::updateEntryAtIndex(int entry_index, FullBTBPrediction& pred, FullBTBPrediction* secondPred) +{ + DPRINTF(UBTB, "updateEntryAtIndex: entry_index=%d, pred.bbStart=%#lx, secondPred=%s\n", + entry_index, pred.bbStart, secondPred ? "provided" : "null"); + + auto s3TakenEntry = pred.getTakenEntry(); + + if (entry_index >= 0) { + // Hit case: We have a valid entry at entry_index + assert(entry_index < static_cast(ubtb.size())); + auto& entry = ubtb[entry_index]; + assert(entry.valid && "Hit entry should be valid"); + assert(entry.tag == getTag(pred.bbStart)); + + if (!s3TakenEntry.valid) { + // S0 has a hit entry, but S3 predicts fall through + updateUCtr(entry.uctr, false); + if (entry.uctr == 0) { + entry.valid = false; + entry.valid_2nd = false; + DPRINTF(UBTB, "updateEntryAtIndex: Invalidated entry at index %d (fall through)\n", entry_index); } - } + } else { + // Both S0 and S3 predict taken - check if they match + if (entry.pc != pred.controlAddr() || entry.target != pred.getTarget(predictWidth)) { + // S0 and S3 predict different branch instruction + updateUCtr(entry.uctr, false); + if (entry.uctr == 0) { + // Replace the old entry with the new one + replaceOldEntry(entry_index, const_cast(pred)); + // Add second prediction if provided + if (secondPred != nullptr) { + addSecondPredictionToEntry(entry_index, secondPred); + } + DPRINTF(UBTB, "updateEntryAtIndex: Replaced entry at index %d (mismatch)\n", entry_index); + } + } else { + // S0 and S3 predict the same (brpc and target) + updateUCtr(entry.uctr, true); - // If no invalid entry found, use LRU policy - // TODO: consider using LRU only among the entries with the least confidence(smallest uctr) - if (!foundInvalidEntry) { - // Find the least recently used entry - std::make_heap(mruList.begin(), mruList.end(), older()); - toBeReplacedIter = mruList.front(); + // Add second prediction if provided + if (secondPred != nullptr) { + addSecondPredictionToEntry(entry_index, secondPred); + } + + DPRINTF(UBTB, "updateEntryAtIndex: Reinforced entry at index %d (match)\n", entry_index); + } } + } else { + // Miss case: entry_index == -1 + if (s3TakenEntry.valid) { + /* S0 misses, but S3 predicts taken, + * generate new entry and replace another using LRU + */ + // check if the new entry exist in the uBTB + for (size_t i = 0; i < ubtb.size(); ++i) { + if (ubtb[i].tag == getTag(pred.bbStart)) { + //warn("updateEntryAtIndex: New entry already exists in uBTB\n"); + return; + } + } + + int toBeReplacedIndex = -1; + + // First try to find an invalid entry + for (size_t i = 0; i < ubtb.size(); ++i) { + if (!ubtb[i].valid) { + toBeReplacedIndex = static_cast(i); + break; + } + } + + // If no invalid entry found, use LRU policy + if (toBeReplacedIndex == -1) { + // Find the least recently used entry + std::make_heap(mruList.begin(), mruList.end(), older()); + UBTBIter lru_iter = mruList.front(); + toBeReplacedIndex = lru_iter - ubtb.begin(); + } - // Replace the entry with the new prediction - replaceOldEntry(toBeReplacedIter, s3Pred); - - } else if (s0EntryIter != ubtb.end() && s3TakenEntry.valid) { - // both S0 and S3 predict taken - if (s0EntryIter->pc != s3Pred.controlAddr() || s0EntryIter->target != s3Pred.getTarget(predictWidth)) { - // S0 and S3 predict different branch instruction - updateUCtr(s0EntryIter->uctr, false); - if (s0EntryIter->uctr == 0) { - // replace the old entry with the new one - replaceOldEntry(s0EntryIter, s3Pred); + // Replace the entry with the new prediction + replaceOldEntry(toBeReplacedIndex, const_cast(pred)); + // Add second prediction if provided + if (secondPred != nullptr) { + addSecondPredictionToEntry(toBeReplacedIndex, secondPred); } + DPRINTF(UBTB, "updateEntryAtIndex: Created new entry at index %d (miss->hit)\n", toBeReplacedIndex); } else { - // S0 and S3 predict the same (brpc and target) - updateUCtr(s0EntryIter->uctr, true); + // Both S0 and S3 predict fall through - do nothing + DPRINTF(UBTB, "updateEntryAtIndex: No action needed (miss->fall through)\n"); } - } else { - // both S0 and S3 predict fall through, do nothing } } void -UBTB::update2Taken(FullBTBPrediction &s3Pred) +UBTB::updateUsingS3Pred(FullBTBPrediction &dff_pred, + FullBTBPrediction &s3_pred, + int hit_index) // hit index is the index stored in dff, along with dff_pred { - auto s3TakenEntry = s3Pred.getTakenEntry(); - if (!s3TakenEntry.valid) { + DPRINTF(UBTB, "2-taken updateUsingS3Pred: hit_index=%d, dff_pred.bbStart=%#lx, s3_pred.bbStart=%#lx\n", + hit_index, dff_pred.bbStart, s3_pred.bbStart); + + // Validate consecutive FB condition + if (dff_pred.getTarget(predictWidth) != s3_pred.bbStart) { + DPRINTF(UBTB, "2-taken training rejected: FBs are not consecutive (%#lx -> %#lx vs %#lx)\n", + dff_pred.bbStart, dff_pred.getTarget(predictWidth), s3_pred.bbStart); + // Fall back to training only with dff_pred using the correct entry (previous cycle's hit) + updateEntryAtIndex(hit_index, dff_pred, nullptr); return; } - auto iter = lookup(s3Pred.bbStart); - - if (iter != ubtb.end()) { - // Hit: Unconditionally replace the existing entry. - replaceOldEntry(iter, s3Pred); - } else { - // Miss: Find a victim and create a new entry. - UBTBIter toBeReplacedIter; - bool foundInvalidEntry = false; - - for (auto it = ubtb.begin(); it != ubtb.end(); ++it) { - if (!it->valid) { - toBeReplacedIter = it; - foundInvalidEntry = true; - break; - } - } - - if (!foundInvalidEntry) { - std::make_heap(mruList.begin(), mruList.end(), older()); - toBeReplacedIter = mruList.front(); - } - - replaceOldEntry(toBeReplacedIter, s3Pred); + // Check 2-taken conditions + if (!check2TakenConditions(dff_pred, s3_pred)) { + DPRINTF(UBTB, "2-taken training rejected: conditions not met\n"); + // Fall back to training only with dff_pred using the correct entry (previous cycle's hit) + updateEntryAtIndex(hit_index, dff_pred, nullptr); + return; } + + // Train as 2-taken: pass s3_pred as second prediction + updateEntryAtIndex(hit_index, dff_pred, &s3_pred); } @@ -469,6 +687,7 @@ UBTB::UBTBStats::UBTBStats(statistics::Group *parent) { } + } // namespace btb_pred } // namespace branch_prediction } // namespace gem5 diff --git a/src/cpu/pred/btb/btb_ubtb.hh b/src/cpu/pred/btb/btb_ubtb.hh index cd1a11c316..10434ea89e 100644 --- a/src/cpu/pred/btb/btb_ubtb.hh +++ b/src/cpu/pred/btb/btb_ubtb.hh @@ -89,14 +89,20 @@ class UBTB : public TimedBaseBTBPredictor * - tag: tag bits from branch address [23:1] * - tick: timestamp used for MRU (Most Recently Used) replacement policy * - numNTConds: number of not-taken conditional branches before the taken branch + * - valid_2nd: existence of the second branch (for 2-taken support) + * - branch_info_2nd: branch attributes for the second branch (for 2-taken support) */ typedef struct TickedUBTBEntry : public BTBEntry { unsigned uctr; //2-bit saturation counter used in replacement policy uint64_t tick; // timestamp for MRU replacement int numNTConds; // number of conditional branches before the taken branch - TickedUBTBEntry() : BTBEntry(), uctr(0), tick(0), numNTConds(0) {} - TickedUBTBEntry(const BTBEntry &be, uint64_t tick) : BTBEntry(be), uctr(0), tick(tick), numNTConds(0) {} + bool valid_2nd; // existence of the second branch + BranchInfo branch_info_2nd; // branch attributes for the second branch + + TickedUBTBEntry() : BTBEntry(), uctr(0), tick(0), numNTConds(0), valid_2nd(false), branch_info_2nd() {} + TickedUBTBEntry(const BTBEntry &be, uint64_t tick) : BTBEntry(be), uctr(0), + tick(tick), numNTConds(0), valid_2nd(false), branch_info_2nd() {} }TickedUBTBEntry; using UBTBIter = typename std::vector::iterator; @@ -119,6 +125,19 @@ class UBTB : public TimedBaseBTBPredictor void putPCHistory(Addr startAddr, const boost::dynamic_bitset<> &history, std::vector &stagePreds) override; + /** New unified prediction function for 2-taken support. + * Performs uBTB lookup and fills both primary and secondary predictions if available. + * @param startAddr The FB start address to look up + * @param history Branch history register (not used) + * @param stagePreds Predictions for each pipeline stage (filled with primary prediction) + * @param secondPrediction Reference to store secondary prediction if available + * @return Pair containing (hit_index, has_second_prediction) + */ + std::pair getTwoTakenPrediction(Addr startAddr, + const boost::dynamic_bitset<> &history, + std::vector &stagePreds, + FullBTBPrediction &secondPrediction); + /** Updates the uBTB predictions based on S3 prediction results. * This function is called from decoupled_bpred during S3 prediction * specifically, it reconciles differences between S1 (uBTB) and S3 predictions, @@ -129,7 +148,17 @@ class UBTB : public TimedBaseBTBPredictor */ void updateUsingS3Pred(FullBTBPrediction &s3Pred); - void update2Taken(FullBTBPrediction &s3Pred); + /** + * Updates the uBTB using S3 prediction with 2-taken support (training/learning phase) + * + * @param dff_pred The first FB (from DFF buffer, represents previous + * S3 pred), factually const but not declared as const + * @param s3_pred The second FB (current S3 prediction) + * @param hit_index The hit index from getTwoTakenPrediction (-1 if miss) + */ + void updateUsingS3Pred(FullBTBPrediction &dff_pred, + FullBTBPrediction &s3_pred, + int hit_index); /** for statistics only * @param stream The fetch stream containing execution results and prediction metadata @@ -158,11 +187,17 @@ class UBTB : public TimedBaseBTBPredictor void setTrace() override; TraceManager *ubtbTrace; - // for debuggin purpose + // for debugging purpose void printTickedUBTBEntry(const TickedUBTBEntry &e) { DPRINTF(UBTB, "uBTB entry: valid %d, pc:%#lx, tag: %#lx, size:%d, target:%#lx, \ - cond:%d, indirect:%d, call:%d, return:%d, tick:%lu\n", - e.valid, e.pc, e.tag, e.size, e.target, e.isCond, e.isIndirect, e.isCall, e.isReturn, e.tick); + cond:%d, indirect:%d, call:%d, return:%d, tick:%lu, valid_2nd:%d", + e.valid, e.pc, e.tag, e.size, e.target, e.isCond, e.isIndirect, e.isCall, e.isReturn, e.tick, e.valid_2nd); + if (e.valid_2nd) { + DPRINTF(UBTB, ", 2nd_pc:%#lx, 2nd_target:%#lx, 2nd_cond:%d, 2nd_indirect:%d, 2nd_call:%d, 2nd_return:%d", + e.branch_info_2nd.pc, e.branch_info_2nd.target, e.branch_info_2nd.isCond, + e.branch_info_2nd.isIndirect, e.branch_info_2nd.isCall, e.branch_info_2nd.isReturn); + } + DPRINTF(UBTB, "\n"); } void dumpMruList() { @@ -179,9 +214,9 @@ class UBTB : public TimedBaseBTBPredictor */ struct LastPred { - UBTBIter hit_entry; // this might point to ubtb.end() + int hit_index; // -1 for miss, array index for hit - LastPred() { + LastPred() : hit_index(-1) { // Default constructor - will be assigned proper value later } }; @@ -231,9 +266,9 @@ class UBTB : public TimedBaseBTBPredictor /** helper method called by putPCHistory: Searches for a entry in the uBTB. * @param startAddr The FB start address to look up - * @return Iterator to the matching entry if found, or ubtb.end() if not found + * @return Index of the matching entry if found, or -1 if not found */ - UBTBIter lookup(Addr startAddr); + int lookup(Addr startAddr); /** helper method called by putPCHistory: Check uBTB entry pc range and update statistics * @param entry The uBTB entry to check @@ -248,13 +283,40 @@ class UBTB : public TimedBaseBTBPredictor void fillStagePredictions(const TickedUBTBEntry& entry, std::vector& stagePreds); + /** helper method for 2-taken: Construct a FullBTBPrediction from BranchInfo + * @param branchInfo The branch information for the second prediction + * @param bbStart The basic block start address for the prediction + * @param prediction The prediction object to fill + */ + void fillSecondPrediction(const BranchInfo& branchInfo, Addr bbStart, FullBTBPrediction& prediction); + + /** helper method for 2-taken: Check if two predictions can form a valid 2-taken sequence + * @param dff The first prediction (from DFF buffer) + * @param s3Pred The second prediction (current S3 prediction) + * @return true if the predictions can form a valid 2-taken sequence + */ + bool check2TakenConditions(FullBTBPrediction& dff, const FullBTBPrediction& s3Pred); + + /** Common helper function for training logic - handles entry update based on hit/miss scenarios + * @param entry_index Index of the entry that was hit during prediction (-1 for miss) + * @param pred The S3 prediction to train with + * @param secondPred Second prediction for 2-taken training (can be nullptr for 1-taken) + */ + void updateEntryAtIndex(int entry_index, FullBTBPrediction& pred, FullBTBPrediction* secondPred); + /** helper method called in updateUsingS3Pred: This function replaces an existing uBTB entry with new prediction * - * @param oldEntry Iterator to the entry to replace + * @param entryIndex Index of the entry to replace * @param newPrediction The new prediction to store */ - void replaceOldEntry(UBTBIter oldEntry, FullBTBPrediction & newPrediction); + void replaceOldEntry(int entryIndex, FullBTBPrediction & newPrediction); + /** helper method for 2-taken: Add second prediction to an existing uBTB entry + * + * @param entryIndex Index of the entry to update + * @param secondPred The second prediction to add (must not be nullptr) + */ + void addSecondPredictionToEntry(int entryIndex, FullBTBPrediction* secondPred); /** The uBTB structure: * - Implemented as a fully associative table diff --git a/src/cpu/pred/btb/decoupled_bpred.cc b/src/cpu/pred/btb/decoupled_bpred.cc index 2752aecbcc..fe1200efd6 100644 --- a/src/cpu/pred/btb/decoupled_bpred.cc +++ b/src/cpu/pred/btb/decoupled_bpred.cc @@ -25,13 +25,13 @@ DecoupledBPUWithBTB::DecoupledBPUWithBTB(const DecoupledBPUWithBTBParams &p) enableLoopBuffer(p.enableLoopBuffer), enableLoopPredictor(p.enableLoopPredictor), enableJumpAheadPredictor(p.enableJumpAheadPredictor), + enable2Taken(p.enable2Taken), fetchTargetQueue(p.ftq_size), fetchStreamQueueSize(p.fsq_size), predictWidth(p.predictWidth), maxInstsNum(p.predictWidth / 2), historyBits(p.maxHistLen), - ubtb1(p.ubtb1), - ubtb2(p.ubtb2), + ubtb(p.ubtb), abtb(p.abtb), btb(p.btb), tage(p.tage), @@ -50,9 +50,7 @@ DecoupledBPUWithBTB::DecoupledBPUWithBTB(const DecoupledBPUWithBTBParams &p) bpType = DecoupledBTBType; // TODO: better impl (use vector to assign in python) // problem: btb->getAndSetNewBTBEntry - components.push_back(ubtb1); - // we don't push ubtb2 into the component list, because its putPCHistory() - // and update methods are called explicitly. + components.push_back(ubtb); components.push_back(abtb); // components.push_back(uras); components.push_back(btb); @@ -583,8 +581,13 @@ DecoupledBPUWithBTB::tick() // Check if the second prediction is still valid after overrides. validateSecondFBPrediction(); - // Now, update the DFF for the *next* cycle using the results of this one. - updateDFF(); + // Inline updateDFF() - Always store finalPred + // This stored block is used for 2-taken training. + // Admittedly, this FB doesn't always directly precede the s3 pred of the next cycle, + // actually, when the current cycle produce a two-taken, dff and next cycls's s3 pred are not consecutive. + // this case is handled inside updateUsingS3Pred(), it simply train with dff. + DPRINTF(DecoupleBP, "updateDFF: Storing finalPred for next cycle (ubtbHitIndex=%d)\n", ubtbHitIndex); + predDFF.storePrediction(finalPred, ubtbHitIndex); bpuState = BpuState::PREDS_READY; @@ -652,39 +655,42 @@ DecoupledBPUWithBTB::requestNewPrediction() predsOfEachStage[i].bbStart = s0PC; } + // Reset prediction flags + hasSecondPrediction = false; + ubtbHitIndex = -1; + secondPrediction.btbEntries.clear(); + // Query each predictor component with current PC and history for (int i = 0; i < numComponents; i++) { - components[i]->putPCHistory(s0PC, s0History, predsOfEachStage); //s0History not used - } - - // Get prediction from secondary uBTB, if the first prediction is valid. - // This uses the same s0PC for lookup, as ubtb2 is trained to hold the - // subsequent block's info. - // TODO: handles the case when there's abtb prediction - if (enable2Taken && predsOfEachStage[0].btbEntries.size() > 0) { - - std::vector ubtb2Preds(1); - ubtb2Preds[0].bbStart = s0PC; - ubtb2->putPCHistory(s0PC, s0History, ubtb2Preds); // ubtb2 lookup based on s0PC - - // After getting the prediction, morph its start PC to reflect its true position, - // which is the target of the first branch. - if (ubtb2Preds[0].btbEntries.size() > 0) { - ubtb2Preds[0].bbStart = predsOfEachStage[0].getTarget(predictWidth); - secondPrediction = ubtb2Preds[0]; - - - hasSecondPrediction = true; - // Instead of asserting, let's check the condition and discard the second prediction if it doesn't fit. - if (!(ubtb2Preds[0].controlAddr() >= ubtb2Preds[0].bbStart && - ubtb2Preds[0].controlAddr() < ubtb2Preds[0].getFallThrough(predictWidth))) { + if (components[i] == ubtb) { + // Special handling for uBTB - use 2-taken prediction if enabled + if (enable2Taken) { + auto [hitIndex, secondAvailable] = ubtb->getTwoTakenPrediction( + s0PC, s0History, predsOfEachStage, secondPrediction); + + // Store hit index for cross-cycle tracking + ubtbHitIndex = hitIndex; + + // Update second prediction state + if (secondAvailable) { + // If second prediction is available, first prediction must exist + assert(predsOfEachStage[0].btbEntries.size() > 0 && + "Second prediction available but no first prediction found"); + + hasSecondPrediction = true; + } else { + hasSecondPrediction = false; + } + } else { + // Regular 1-taken prediction for uBTB + ubtb->putPCHistory(s0PC, s0History, predsOfEachStage); + ubtbHitIndex = -1; // No hit index tracking in 1-taken mode hasSecondPrediction = false; - secondPrediction.btbEntries.clear(); - DPRINTF(DecoupleBP, "Second FB prediction from uBTB2 failed range check, discarding.\n"); + DPRINTF(DecoupleBP, "1-taken prediction mode\n"); } - DPRINTF(DecoupleBP, "Got second FB prediction from uBTB2 for target %#lx\n", ubtb2Preds[0].bbStart); } else { - hasSecondPrediction = false; + // Regular handling for other components (ABTB, etc.) + components[i]->putPCHistory(s0PC, s0History, predsOfEachStage); //s0History not used } } } @@ -726,6 +732,15 @@ DecoupledBPUWithBTB::generateFinalPredAndCreateBubbles() printFullBTBPrediction(predsOfEachStage[i]); } + // Debug output for 2-taken predictions + if (enable2Taken) { + DPRINTF(DecoupleBP, "2-taken prediction: hit index %d, %ssecond prediction\n", + ubtbHitIndex, hasSecondPrediction ? "" : "no "); + if (hasSecondPrediction) { + printFullBTBPrediction(secondPrediction); + } + } + // 2. Select the most accurate prediction (prioritize later stages) // Initially assume stage 0 (UBTB) prediction FullBTBPrediction *chosenPrediction = &predsOfEachStage[0]; @@ -2120,55 +2135,7 @@ DecoupledBPUWithBTB::recoverHistoryForSquash( } -bool DecoupledBPUWithBTB::check2TakenConditions(FullBTBPrediction& dff, const FullBTBPrediction& s3Pred) { - - assert(dff.getTarget(predictWidth) == s3Pred.bbStart); - - // 1. Both predictions must have at least one branch. - if (dff.btbEntries.empty() || s3Pred.btbEntries.empty()) { - return false; - } - - auto dffEntry = dff.getTakenEntry(); - auto& s3PredEntry = s3Pred.btbEntries[0]; - - // 2. The first branch must be taken for a 2-taken sequence to form. - if (!dff.isTaken()) { - return false; - } - - // 3. Check branch type compatibility based on spec table. - - // Rule: 'multi-target indirect' as 1st branch is not allowed. - if (dffEntry.isIndirect) { - return false; - } - - // Rule: 'multi-target indirect' as 2nd branch is not allowed. - if (s3PredEntry.isIndirect) { - return false; - } - - // Rule: 'cond' as 2nd branch is not allowed. - if (s3PredEntry.isCond) { - return false; - } - - // Rule: 'ret -> ret' is not allowed to avoid multiple RAS reads. - if (dffEntry.isReturn && s3PredEntry.isReturn) { - return false; - } - - // Rule: 'call -> call' is not allowed to avoid multiple RAS writes. - if (dffEntry.isCall && s3PredEntry.isCall) { - return false; - } - // (call -> ret is allowed, so no check needed) - - // All conditions passed. - return true; -} // Renamed function containing only uBTB training logic. void DecoupledBPUWithBTB::trainUbtbFor2Taken() @@ -2176,46 +2143,27 @@ void DecoupledBPUWithBTB::trainUbtbFor2Taken() // Get the S3 prediction from s3 predictors. This is our 'ground truth' inside the BP. auto& s3_pred = predsOfEachStage[numStages-1]; - // Update ubtb1 based on the S3 prediction. + // Update ubtb based on the S3 prediction. if (s3_pred.btbEntries.size() > 0) { - ubtb1->updateUsingS3Pred(s3_pred); - } - - // Perform 2-taken learning for ubtb2. - // This compares the *current* S3 prediction with the *previous* cycle's S3 prediction (stored in DFF). - if (enable2Taken) { - if (predDFF.valid && check2TakenConditions(predDFF.prevS3Pred, s3_pred)) { - // trainSecondUBTB logic: - // Train uBTB2: when indexed by dff.bbstart, predict the content of s3_pred. - // This way, when both ubtb1 and ubtb2 use the same S0PC as input, they predict consecutive FBs. - FullBTBPrediction trainingPred = s3_pred; - // The training entry should be indexed by the PC of the dff block. - trainingPred.bbStart = predDFF.prevS3Pred.bbStart; - - ubtb2->update2Taken(trainingPred); + if (enable2Taken) { + if (predDFF.valid) { + // 2-taken mode with valid DFF: Use overloaded updateUsingS3Pred + DPRINTF(DecoupleBP, "trainUbtbFor2Taken: 2-taken training with DFF (prevIndex=%d)\n", + predDFF.prevUbtbHitIndex); + ubtb->updateUsingS3Pred(predDFF.prevS3Pred, s3_pred, predDFF.prevUbtbHitIndex); + } else { + // 2-taken mode with invalid DFF: Skip training + DPRINTF(DecoupleBP, "trainUbtbFor2Taken: 2-taken mode but DFF invalid, skipping training\n"); + } + } else { + // 1-taken mode: Use original updateUsingS3Pred + DPRINTF(DecoupleBP, "trainUbtbFor2Taken: 1-taken training\n"); + ubtb->updateUsingS3Pred(s3_pred); } } } -// New function to update the DFF buffer for the next cycle. -void DecoupledBPUWithBTB::updateDFF() -{ - // CRITICAL: Update the DFF with the fetch block that will precede - // the first fetch block of the next prediction cycle. This stored block - // is used for 2-taken training. - - if (hasSecondPrediction) { - // Case 1: A valid second prediction exists. - // It's the most recent block, so we store it for the next cycle's training. - predDFF.storePrediction(secondPrediction); - } else { - // Case 2: No second prediction. - // This could be because the primary prediction was overridden, or uBTB2 - // simply didn't find a 2-taken entry. In either situation, `finalPred` - // represents the one and only fetch block for this cycle. We store it. - predDFF.storePrediction(finalPred); - } -} + void DecoupledBPUWithBTB::validateSecondFBPrediction() { diff --git a/src/cpu/pred/btb/decoupled_bpred.hh b/src/cpu/pred/btb/decoupled_bpred.hh index 060e9c4b83..a6d62ef713 100644 --- a/src/cpu/pred/btb/decoupled_bpred.hh +++ b/src/cpu/pred/btb/decoupled_bpred.hh @@ -89,13 +89,16 @@ class DecoupledBPUWithBTB : public BPredUnit // Previous S3 final prediction result, // this field sometimes stores the second prediction from the previous cycle FullBTBPrediction prevS3Pred; + int prevUbtbHitIndex{-1}; // Store previous cycle's hit index void reset() { valid = false; + prevUbtbHitIndex = -1; } - void storePrediction(const FullBTBPrediction& s3_pred) { + void storePrediction(const FullBTBPrediction& s3_pred, int hit_index) { prevS3Pred = s3_pred; + prevUbtbHitIndex = hit_index; valid = true; } }; @@ -106,9 +109,12 @@ class DecoupledBPUWithBTB : public BPredUnit PredictionDFF predDFF; // DFF buffer to store previous pipeline result // Storage for second fetch block prediction - FullBTBPrediction secondPrediction; // Second fetch block prediction from uBTB2 + FullBTBPrediction secondPrediction; // Second fetch block prediction from unified uBTB bool hasSecondPrediction{false}; // Whether we have a valid second FB prediction + // Hit index tracking for 2-taken training + int ubtbHitIndex{-1}; // Store hit index from getTwoTakenPrediction + FetchTargetQueue fetchTargetQueue; std::map fetchStreamQueue; @@ -124,8 +130,7 @@ class DecoupledBPUWithBTB : public BPredUnit const Addr MaxAddr{~(0ULL)}; - UBTB *ubtb1{}; // Primary uBTB for first fetch block - UBTB *ubtb2{}; // Secondary uBTB for 2-taken patterns + UBTB *ubtb{}; // Single uBTB for prediction (supports 2-taken internally) DefaultBTB *abtb{}; DefaultBTB *btb{}; BTBTAGE *tage{}; @@ -196,11 +201,9 @@ class DecoupledBPUWithBTB : public BPredUnit using JAInfo = JumpAheadPredictor::JAInfo; JAInfo jaInfo; - // Helper method to check 2-taken conditions - bool check2TakenConditions(FullBTBPrediction& dff_pred, const FullBTBPrediction& s3_pred); + void update2TakenEntry(Addr prevAddr, const FullBTBPrediction& dff_pred, const FullBTBPrediction& s3_pred); void trainUbtbFor2Taken(); - void updateDFF(); void validateSecondFBPrediction(); bool validateFSQEnqueue(); From 7d4da8b793c7a6bf9abb115329b7cdb13914f27e Mon Sep 17 00:00:00 2001 From: Xu Boran Date: Wed, 25 Jun 2025 11:55:54 +0800 Subject: [PATCH 03/23] cpu-o3: pred: add performance counter for 2taken. This commit introduces new statistics to monitor the performance of 2taken: 1. tracks how many 1-taken and 2-taken are produced in BPU pipeline 2. control squashes caused by the second predictions in 2taken. 3. tracks how many second predictions are commited --- src/cpu/pred/btb/decoupled_bpred.cc | 27 +++++++++++++++++++++++++-- src/cpu/pred/btb/decoupled_bpred.hh | 4 ++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/cpu/pred/btb/decoupled_bpred.cc b/src/cpu/pred/btb/decoupled_bpred.cc index fe1200efd6..50bcca75c7 100644 --- a/src/cpu/pred/btb/decoupled_bpred.cc +++ b/src/cpu/pred/btb/decoupled_bpred.cc @@ -493,6 +493,8 @@ DecoupledBPUWithBTB::DBPBTBStats::DBPBTBStats(statistics::Group* parent, unsigne ADD_STAT(predsOfEachStage, statistics::units::Count::get(), "the number of preds of each stage that account for final pred"), ADD_STAT(overrideBubbleNum, statistics::units::Count::get(), "the number of override bubbles"), ADD_STAT(overrideCount, statistics::units::Count::get(), "the number of overrides"), + ADD_STAT(predProduce2Taken, statistics::units::Count::get(), "the number of predictions that produce 2-taken"), + ADD_STAT(predProduce1Taken, statistics::units::Count::get(), "the number of predictions that produce 1-taken"), ADD_STAT(commitPredsFromEachStage, statistics::units::Count::get(), "the number of preds of each stage that account for a committed stream"), ADD_STAT(commitOverrideBubbleNum, statistics::units::Count::get(), @@ -511,8 +513,10 @@ DecoupledBPUWithBTB::DBPBTBStats::DBPBTBStats(statistics::Group* parent, unsigne ADD_STAT(fsqEntryDist, statistics::units::Count::get(), "the distribution of number of entries in fsq"), ADD_STAT(fsqEntryEnqueued, statistics::units::Count::get(), "the number of fsq entries enqueued"), ADD_STAT(fsqEntryCommitted, statistics::units::Count::get(), "the number of fsq entries committed at last"), + ADD_STAT(secondPredCommitted, statistics::units::Count::get(), "the number of second predictions that committed successfully"), ADD_STAT(controlSquashFromDecode, statistics::units::Count::get(), "the number of control squashes in bpu from decode"), ADD_STAT(controlSquashFromCommit, statistics::units::Count::get(), "the number of control squashes in bpu from commit"), + ADD_STAT(controlSquashFromSecondPred, statistics::units::Count::get(), "the number of control squashes caused by second predictions"), ADD_STAT(nonControlSquash, statistics::units::Count::get(), "the number of non-control squashes in bpu"), ADD_STAT(trapSquash, statistics::units::Count::get(), "the number of trap squashes in bpu"), ADD_STAT(ftqNotValid, statistics::units::Count::get(), "fetch needs ftq req but ftq not valid"), @@ -558,6 +562,8 @@ void DecoupledBPUWithBTB::tick() { DPRINTF(Override, "DecoupledBPUWithBTB::tick()\n"); + // Monitor FSQ size for statistics + dbpBtbStats.fsqEntryDist.sample(fetchStreamQueue.size(), 1); // On squash, reset state if there was a valid prediction. if (squashing) { @@ -591,6 +597,13 @@ DecoupledBPUWithBTB::tick() bpuState = BpuState::PREDS_READY; + // Update performance counters based on prediction type + if (hasSecondPrediction) { + dbpBtbStats.predProduce2Taken++; + } else { + dbpBtbStats.predProduce1Taken++; + } + // Clear predictor outputs. for (int i = 0; i < numStages; i++) { predsOfEachStage[i].btbEntries.clear(); @@ -1038,6 +1051,12 @@ DecoupledBPUWithBTB::controlSquash(unsigned target_id, unsigned stream_id, return; } auto &stream = stream_it->second; + + // Track control squashes caused by second predictions + if (stream.isSecondFBPred) { + dbpBtbStats.controlSquashFromSecondPred++; + DPRINTF(DecoupleBP, "Control squash caused by second prediction at %#lx\n", stream.startPC); + } // Get target address Addr real_target = corr_target.instAddr(); if (!fromCommit && static_inst->isReturn() && !static_inst->isNonSpeculative()) { @@ -1119,6 +1138,11 @@ void DecoupledBPUWithBTB::update(unsigned stream_id, ThreadID tid) DPRINTF(DecoupleBP, "Skipping predictor update for second FB prediction at %#lx\n", stream.startPC); } + // Track successful second prediction commits + if (stream.isSecondFBPred) { + dbpBtbStats.secondPredCommitted++; + } + it = fetchStreamQueue.erase(it); dbpBtbStats.fsqEntryCommitted++; } @@ -1618,8 +1642,7 @@ DecoupledBPUWithBTB::dumpFsq(const char *when) bool DecoupledBPUWithBTB::validateFSQEnqueue() { - // Monitor FSQ size for statistics - dbpBtbStats.fsqEntryDist.sample(fetchStreamQueue.size(), 1); + if (streamQueueFull()) { dbpBtbStats.fsqFullCannotEnq++; DPRINTF(Override, "FSQ is full (%lu entries)\n", fetchStreamQueue.size()); diff --git a/src/cpu/pred/btb/decoupled_bpred.hh b/src/cpu/pred/btb/decoupled_bpred.hh index a6d62ef713..2a45d1152f 100644 --- a/src/cpu/pred/btb/decoupled_bpred.hh +++ b/src/cpu/pred/btb/decoupled_bpred.hh @@ -356,6 +356,8 @@ class DecoupledBPUWithBTB : public BPredUnit statistics::Vector predsOfEachStage; statistics::Scalar overrideBubbleNum; statistics::Scalar overrideCount; + statistics::Scalar predProduce2Taken; + statistics::Scalar predProduce1Taken; statistics::Vector commitPredsFromEachStage; statistics::Formula commitOverrideBubbleNum; @@ -370,9 +372,11 @@ class DecoupledBPUWithBTB : public BPredUnit statistics::Distribution fsqEntryDist; statistics::Scalar fsqEntryEnqueued; statistics::Scalar fsqEntryCommitted; + statistics::Scalar secondPredCommitted; // statistics::Distribution ftqEntryDist; statistics::Scalar controlSquashFromDecode; statistics::Scalar controlSquashFromCommit; + statistics::Scalar controlSquashFromSecondPred; statistics::Scalar nonControlSquash; statistics::Scalar trapSquash; From a6a376193ff7dcef06cbe754e99f522c7cd1fa75 Mon Sep 17 00:00:00 2001 From: Xu Boran Date: Wed, 25 Jun 2025 12:05:19 +0800 Subject: [PATCH 04/23] cpu-o3: pred: clear ubtb during squashing: this commit clears the ubtb during squashing, we aim to fix the pattern where there's a wrong 2nd branch in a ubtb entry that couldn't be replaced for a long time Change-Id: I9aa98da029f8ebf5890c78628b37421e66fa9f45 --- src/cpu/pred/btb/btb_ubtb.cc | 37 ++++++++++++++++++++++++++++-------- src/cpu/pred/btb/btb_ubtb.hh | 2 +- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/cpu/pred/btb/btb_ubtb.cc b/src/cpu/pred/btb/btb_ubtb.cc index 989aa07e8b..69812d32ca 100644 --- a/src/cpu/pred/btb/btb_ubtb.cc +++ b/src/cpu/pred/btb/btb_ubtb.cc @@ -203,7 +203,7 @@ UBTB::getTwoTakenPrediction(Addr startAddr, const boost::dynamic_bitset<> &histo auto& entry = meta->hit_entry; entry = (hit_index != -1) ? ubtb[hit_index] : TickedUBTBEntry(); - //PredStatistics(entry, startAddr); + PredStatistics(entry, startAddr); // Fill primary prediction for each pipeline stage fillStagePredictions(entry, stagePreds); @@ -358,8 +358,8 @@ UBTB::check2TakenConditions(FullBTBPrediction& dff, const FullBTBPrediction& s3P return false; } - auto dffEntry = dff.getTakenEntry(); - auto& s3PredEntry = s3Pred.btbEntries[0]; + auto firstBr = dff.getTakenEntry(); + auto& secondBr = s3Pred.btbEntries[0]; // 2. The first branch must be taken for a 2-taken sequence to form. if (!dff.isTaken()) { @@ -369,27 +369,28 @@ UBTB::check2TakenConditions(FullBTBPrediction& dff, const FullBTBPrediction& s3P // 3. Check branch type compatibility based on spec table. // Rule: 'multi-target indirect' as 1st branch is not allowed. - if (dffEntry.isIndirect) { + if (firstBr.isIndirect) { return false; } // Rule: 'multi-target indirect' as 2nd branch is not allowed. - if (s3PredEntry.isIndirect) { + if (secondBr.isIndirect) { return false; } // Rule: 'cond' as 2nd branch is not allowed. - if (s3PredEntry.isCond) { + // this rule implies that the second branch is always taken + if (secondBr.isCond) { return false; } // Rule: 'ret -> ret' is not allowed to avoid multiple RAS reads. - if (dffEntry.isReturn && s3PredEntry.isReturn) { + if (firstBr.isReturn && secondBr.isReturn) { return false; } // Rule: 'call -> call' is not allowed to avoid multiple RAS writes. - if (dffEntry.isCall && s3PredEntry.isCall) { + if (firstBr.isCall && secondBr.isCall) { return false; } @@ -525,6 +526,26 @@ UBTB::updateUsingS3Pred(FullBTBPrediction &dff_pred, updateEntryAtIndex(hit_index, dff_pred, &s3_pred); } +void +UBTB::recoverHist(const boost::dynamic_bitset<> &history, + const FetchStream &entry, int shamt, bool cond_taken) +{ + DPRINTF(UBTB, "uBTB squash recovery: clearing all entries (had %lu valid entries)\n", + std::count_if(ubtb.begin(), ubtb.end(), [](const TickedUBTBEntry& e) { return e.valid; })); + + // Clear all uBTB entries by marking them as invalid + // This removes pollution from wrong-path predictions + for (auto &entry : ubtb) { + //entry.valid = false; + entry.valid_2nd = false; // Also clear second branch validity + } + + // we don't explicitly clear entry.tick, because tick will be updated when the entry is filled again + + + DPRINTF(UBTB, "uBTB squash recovery complete: all entries cleared\n"); +} + void UBTB::update(const FetchStream &stream) diff --git a/src/cpu/pred/btb/btb_ubtb.hh b/src/cpu/pred/btb/btb_ubtb.hh index 10434ea89e..2f926ee43d 100644 --- a/src/cpu/pred/btb/btb_ubtb.hh +++ b/src/cpu/pred/btb/btb_ubtb.hh @@ -182,7 +182,7 @@ class UBTB : public TimedBaseBTBPredictor // the following methods are not used void specUpdateHist(const boost::dynamic_bitset<> &history, FullBTBPrediction &pred) override {} void recoverHist(const boost::dynamic_bitset<> &history, - const FetchStream &entry, int shamt, bool cond_taken) override{}; + const FetchStream &entry, int shamt, bool cond_taken) override; void reset(); void setTrace() override; TraceManager *ubtbTrace; From 845e030c8f58889b981495d9f3f78d758bb61874 Mon Sep 17 00:00:00 2001 From: Xu Boran Date: Thu, 26 Jun 2025 16:35:08 +0800 Subject: [PATCH 05/23] cpu-o3: pred: update ras predictor during second FB prediction This commit ensure that the return address stack (ras) is updated when skipping the predictor update for the second prediction of a 2taken-pair. This change ensures the internal state of RAS is consistent and synchronized. (RAS tracks two stacks: one speculative, another commited) Change-Id: I9d719af64e6291 --- src/cpu/pred/btb/decoupled_bpred.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/cpu/pred/btb/decoupled_bpred.cc b/src/cpu/pred/btb/decoupled_bpred.cc index 50bcca75c7..6fbdf72cee 100644 --- a/src/cpu/pred/btb/decoupled_bpred.cc +++ b/src/cpu/pred/btb/decoupled_bpred.cc @@ -1136,6 +1136,8 @@ void DecoupledBPUWithBTB::update(unsigned stream_id, ThreadID tid) updatePredictorComponents(stream); } else { DPRINTF(DecoupleBP, "Skipping predictor update for second FB prediction at %#lx\n", stream.startPC); + // ras is the only predictor that relies on update from all FBs + ras->update(stream); } // Track successful second prediction commits From 5b698bf5d23df8d79c7156c5704a913bb66bf981 Mon Sep 17 00:00:00 2001 From: Xu Boran Date: Fri, 27 Jun 2025 18:11:52 +0800 Subject: [PATCH 06/23] ci: modify ci for testing purpose of this branch remember to delete this commit --- ...erf.yml => gem5-ideal-btb-perf-2taken.yml} | 4 +- .../workflows/gem5-ideal-btb-perf-weekly.yml | 29 --- .../workflows/gem5-ideal-rvv-simple-perf.yml | 16 -- .github/workflows/gem5-vector.yml | 30 --- .github/workflows/gem5.yml | 239 ------------------ util/xs_scripts/kmh_v3_btb.sh | 2 +- util/xs_scripts/kmh_v3_btb_2taken.sh | 10 + 7 files changed, 13 insertions(+), 317 deletions(-) rename .github/workflows/{gem5-perf.yml => gem5-ideal-btb-perf-2taken.yml} (68%) delete mode 100644 .github/workflows/gem5-ideal-btb-perf-weekly.yml delete mode 100644 .github/workflows/gem5-ideal-rvv-simple-perf.yml delete mode 100644 .github/workflows/gem5-vector.yml delete mode 100644 .github/workflows/gem5.yml create mode 100644 util/xs_scripts/kmh_v3_btb_2taken.sh diff --git a/.github/workflows/gem5-perf.yml b/.github/workflows/gem5-ideal-btb-perf-2taken.yml similarity index 68% rename from .github/workflows/gem5-perf.yml rename to .github/workflows/gem5-ideal-btb-perf-2taken.yml index 7d2dfc0873..48b36b30ba 100644 --- a/.github/workflows/gem5-perf.yml +++ b/.github/workflows/gem5-ideal-btb-perf-2taken.yml @@ -1,4 +1,4 @@ -name: gem5 Performance Test +name: gem5 Ideal BTB Performance Test (2Taken) on: push: @@ -10,5 +10,5 @@ jobs: perf_test: uses: ./.github/workflows/gem5-perf-template.yml with: - script_path: ../kmh_6wide.sh + script_path: ../kmh_v3_btb_2taken.sh benchmark_type: "spec06-0.8c" \ No newline at end of file diff --git a/.github/workflows/gem5-ideal-btb-perf-weekly.yml b/.github/workflows/gem5-ideal-btb-perf-weekly.yml deleted file mode 100644 index 26aab4f198..0000000000 --- a/.github/workflows/gem5-ideal-btb-perf-weekly.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: gem5 Ideal BTB Weekly Performance Test - -on: - schedule: - # Run every Thursday at 23:59 UTC+8 (15:59 UTC) - - cron: '59 15 * * 4' - workflow_dispatch: - # Allow manual triggering of the workflow - -jobs: - perf_test_spec06: - uses: ./.github/workflows/gem5-perf-template.yml - with: - script_path: ../kmh_v3_btb.sh - benchmark_type: "spec06-1.0c" - - perf_test_spec17: - uses: ./.github/workflows/gem5-perf-template.yml - with: - script_path: ../kmh_v3_btb.sh - benchmark_type: "spec17-1.0c" - - perf_test_spec06_vector: - uses: ./.github/workflows/gem5-perf-template.yml - with: - script_path: ../kmh_v3_btb.sh - benchmark_type: "spec06-rvv-1.0c" - vector_type: "simple" - check_result: false \ No newline at end of file diff --git a/.github/workflows/gem5-ideal-rvv-simple-perf.yml b/.github/workflows/gem5-ideal-rvv-simple-perf.yml deleted file mode 100644 index 075ed0179f..0000000000 --- a/.github/workflows/gem5-ideal-rvv-simple-perf.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: gem5 Simple RVV Performance Test (Ideal BTB) - -on: - push: - branches: [ xs-dev ] - pull_request: - branches: [ xs-dev ] - -jobs: - perf_test: - uses: ./.github/workflows/gem5-perf-template.yml - with: - script_path: ../kmh_v3_btb.sh - benchmark_type: "spec06int-rvv-0.8c" - vector_type: "simple" - check_result: false # Warning: rvv test will not show the difftest failure \ No newline at end of file diff --git a/.github/workflows/gem5-vector.yml b/.github/workflows/gem5-vector.yml deleted file mode 100644 index a03d77be32..0000000000 --- a/.github/workflows/gem5-vector.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: gem5 vector Test - -on: - push: - branches: [ xs-dev ] - pull_request: - branches: [ xs-dev ] - -jobs: - vector-test: - runs-on: node - continue-on-error: false - name: XS-GEM5 - Running vector test - steps: - - uses: actions/checkout@v2 - - name: Build DRAMSim - run: | - export GEM5_HOME=$(pwd) - cd ext/dramsim3 - git clone https://github.com/umd-memsys/DRAMsim3.git DRAMsim3 - cd DRAMsim3 && mkdir -p build - cd build - cmake .. - make -j 48 - cd $GEM5_HOME - - name: Build GEM5 opt - run: | - CC=gcc CXX=g++ scons build/RISCV/gem5.opt --linker=gold -j64 --rvv-impl=simple - - name: run vector test - run: python3 .github/workflows/autotest/script/autotest.py -f .github/workflows/autotest/gem5-vec.cfg \ No newline at end of file diff --git a/.github/workflows/gem5.yml b/.github/workflows/gem5.yml deleted file mode 100644 index a5b652dcd0..0000000000 --- a/.github/workflows/gem5.yml +++ /dev/null @@ -1,239 +0,0 @@ -name: gem5 Test - -on: - push: - branches: [ xs-dev ] - pull_request: - branches: [ xs-dev ] - -jobs: - paralel_cpt_test: - # 由于gem5.cfg使用的切片ck_path都在小机房上,默认使用小机房运行这个测试 - runs-on: [self-hosted, open] # 所有open*的机器上运行 - continue-on-error: false - name: XS-GEM5 - Running test checkpoints - steps: - - uses: actions/checkout@v2 - - name: Build DRAMSim - run: | - export GEM5_HOME=$(pwd) - cd ext/dramsim3 - git clone git@github.com:umd-memsys/DRAMSim3.git DRAMsim3 - cd DRAMsim3 && mkdir -p build - cd build - cmake .. - make -j 48 - cd $GEM5_HOME - - name: Build GEM5 opt - run: | - CC=gcc CXX=g++ scons build/RISCV/gem5.opt --linker=gold -j64 - - name: Run paralel autotest script - run: python3 .github/workflows/autotest/script/autotest.py -f .github/workflows/autotest/gem5.cfg - - paralel_cpt_h_test: - # 由于gem5.cfg使用的切片ck_path都在小机房上,默认使用小机房运行这个测试 - runs-on: [self-hosted, open] # 所有open*的机器上运行 - continue-on-error: false - name: XS-GEM5 - Running h test checkpoints - steps: - - uses: actions/checkout@v2 - - name: Build DRAMSim - run: | - export GEM5_HOME=$(pwd) - cd ext/dramsim3 - git clone git@github.com:umd-memsys/DRAMSim3.git DRAMsim3 - cd DRAMsim3 && mkdir -p build - cd build - cmake .. - make -j 48 - cd $GEM5_HOME - - name: Build GEM5 opt - run: | - CC=gcc CXX=g++ scons build/RISCV/gem5.opt --linker=gold -j64 - - name: Run paralel h autotest script - run: | - export GCBH_REF_SO="/nfs-nvme/home/share/zhenhao/ref-h-u/riscv64-nemu-interpreter-so" - export GCBV_REF_SO="/nfs-nvme/home/share/zhenhao/ref-h-u/riscv64-nemu-interpreter-so" - export GCB_RESTORER="None" - python3 .github/workflows/autotest/script/autotest.py -f .github/workflows/autotest/gem5-h.cfg - - valgrind_memory_check: - runs-on: [self-hosted, open] - continue-on-error: false - name: XS-GEM5 - Check memory corruption - steps: - - uses: actions/checkout@v2 - - name: Build DRAMSim - run: | - export GEM5_HOME=$(pwd) - cd ext/dramsim3 - git clone https://github.com/umd-memsys/DRAMsim3.git DRAMsim3 - cd DRAMsim3 && mkdir -p build - cd build - cmake .. - make -j 48 - cd $GEM5_HOME - - name: Build GEM5 debug - run: CC=gcc CXX=g++ scons build/RISCV/gem5.debug --linker=gold -j64 - - name: Memory check - run: | - export GEM5_HOME=$(pwd) - bash util/memory_check/run-xs-with-valgrind.sh - cd $GEM5_HOME - - new_sim_script_test_gcb: - runs-on: [self-hosted, open] - continue-on-error: false - name: XS-GEM5 - Test new simulation script on RV64GCB - steps: - - uses: actions/checkout@v2 - - name: Build DRAMSim - run: | - export GEM5_HOME=$(pwd) - cd ext/dramsim3 - git clone https://github.com/umd-memsys/DRAMsim3.git DRAMsim3 - cd DRAMsim3 && mkdir -p build - cd build - cmake .. - make -j 48 - cd $GEM5_HOME - - name: Build GEM5 opt - run: CC=gcc CXX=g++ scons build/RISCV/gem5.opt --linker=gold -j64 - - name: XS-GEM5 - Test xiangshan.py simulation scripts - run: | - export GCBV_REF_SO="/nfs/home/share/gem5_ci/ref/normal/riscv64-nemu-interpreter-so" - export GCB_RESTORER="/nfs/home/share/gem5_ci/tools/normal-gcb-restorer.bin" - export GEM5_HOME=$(pwd) - mkdir -p $GEM5_HOME/util/xs_scripts/test - cd $GEM5_HOME/util/xs_scripts/test - bash ../kmh_6wide.sh /nfs/home/share/gem5_ci/checkpoints/gcb_test.zstd - - new_sim_script_test_gcbv: - runs-on: [self-hosted, open] - continue-on-error: false - name: XS-GEM5 - Test new simulation script on RV64GCBV - steps: - - uses: actions/checkout@v2 - - name: Build DRAMSim - run: | - export GEM5_HOME=$(pwd) - cd ext/dramsim3 - git clone https://github.com/umd-memsys/DRAMsim3.git DRAMsim3 - cd DRAMsim3 && mkdir -p build - cd build - cmake .. - make -j 48 - cd $GEM5_HOME - - name: Build GEM5 opt - run: CC=gcc CXX=g++ scons build/RISCV/gem5.opt --linker=gold -j64 --rvv-impl=simple - - name: XS-GEM5 - Test xiangshan.py simulation scripts - run: | - export GCBV_REF_SO="/nfs/home/share/gem5_ci/ref/normal/riscv64-nemu-notama-so" - export GCBV_RESTORER="/nfs/home/share/gem5_ci/tools/gcbv-restorer.bin" - export GEM5_HOME=$(pwd) - mkdir -p $GEM5_HOME/util/xs_scripts/test_v - cd $GEM5_HOME/util/xs_scripts/test_v - bash ../kmh_6wide_vector.sh /nfs/home/share/gem5_ci/checkpoints/gcbv_test.zstd - - new_sim_script_test_gcb_multi_core: - runs-on: [self-hosted, open] - continue-on-error: false - name: XS-GEM5 - Test Multi-core + RV64GCB - steps: - - uses: actions/checkout@v2 - - name: Build GEM5 opt - run: | - CC=clang CXX=clang++ scons build/RISCV_CHI/gem5.opt -j 48 --gold-linker - - name: XS-GEM5 - Test xiangshan.py simulation scripts - run: | - export GCBV_MULTI_CORE_REF_SO="/nfs/home/share/gem5_ci/ref/multi/riscv64-nemu-interpreter-so" - export GCB_MULTI_CORE_RESTORER="/nfs/home/share/gem5_ci/tools/gcb-2core-restorer.bin" - export GEM5_HOME=$(pwd) - mkdir -p $GEM5_HOME/util/xs_scripts/test_multi_core - cd $GEM5_HOME/util/xs_scripts/test_multi_core - bash ../kmh-ruby-dual.sh /nfs/home/share/gem5_ci/checkpoints/multi_core_test.gz - - difftest_check: - runs-on: [self-hosted, open] - continue-on-error: false - name: XS-GEM5 - Check difftest - steps: - - uses: actions/checkout@v2 - - name: Build DRAMSim - run: | - export GEM5_HOME=$(pwd) - cd ext/dramsim3 - git clone https://github.com/umd-memsys/DRAMsim3.git DRAMsim3 - cd DRAMsim3 && mkdir -p build - cd build - cmake .. - make -j 48 - cd $GEM5_HOME - - name: Build GEM5 debug - run: CC=clang CXX=clang++ scons build/RISCV/gem5.opt -j 48 --gold-linker - - name: difftest check - run: | - export GCBV_REF_SO="/nfs/home/share/gem5_ci/ref/error/riscv64-nemu-interpreter-so" - export GCB_RESTORER="/nfs/home/share/gem5_ci/tools/normal-gcb-restorer.bin" - export GEM5_HOME=$(pwd) - mkdir -p $GEM5_HOME/util/xs_scripts/test - cd $GEM5_HOME/util/xs_scripts/test - bash ../kmh_6wide.sh /nfs/home/share/gem5_ci/checkpoints/gcb_test.zstd 2>log.txt || exit_code=$? - if [ ${exit_code} -eq 0 ]; then echo "Difftest is broken, it should report error!" exit 1; fi - match=$(grep ".*Difftest failed!.*" log.txt -c) - if [ ${match} -eq 0 ]; then echo "Difftest is broken, it should report at least one agnostic related difference!" exit 1; fi - - test_fix_l2tlb_bugs: - runs-on: [self-hosted, open] - continue-on-error: false - name: XS-GEM5 - Test fix L2TLB bugs - steps: - - uses: actions/checkout@v2 - - name: Build DRAMSim - run: | - export GEM5_HOME=$(pwd) - cd ext/dramsim3 - git clone https://github.com/umd-memsys/DRAMsim3.git DRAMsim3 - cd DRAMsim3 && mkdir -p build - cd build - cmake .. - make -j 48 - cd $GEM5_HOME - - name: Build GEM5 opt - run: CC=gcc CXX=g++ scons build/RISCV/gem5.opt --linker=gold -j64 - - name: XS-GEM5 - Test xiangshan.py simulation scripts - run: | - export GCBV_REF_SO="/nfs/home/share/gem5_ci/ref/normal/riscv64-nemu-interpreter-so" - export GCB_RESTORER="" - export GEM5_HOME=$(pwd) - mkdir -p $GEM5_HOME/util/xs_scripts/test_l2tlb - cd $GEM5_HOME/util/xs_scripts/test_l2tlb - bash ../kmh_6wide.sh /nfs/home/share/gem5_ci/checkpoints/l2tlb_test.zstd - - new_sim_script_test_gcbh: - runs-on: [self-hosted, open] - continue-on-error: false - name: XS-GEM5 - Test new simulation script on RV64GCBH - steps: - - uses: actions/checkout@v2 - - name: Build DRAMSim - run: | - export GEM5_HOME=$(pwd) - cd ext/dramsim3 - git clone https://github.com/umd-memsys/DRAMsim3.git DRAMsim3 - cd DRAMsim3 && mkdir -p build - cd build - cmake .. - make -j 48 - cd $GEM5_HOME - - name: Build GEM5 opt - run: CC=gcc CXX=g++ scons build/RISCV/gem5.opt --linker=gold -j64 - - name: XS-GEM5 - Test xiangshan.py simulation scripts - run: | - export GCBH_REF_SO="/nfs/home/share/gem5_ci/ref/h/riscv64-nemu-interpreter-so" - export GCBH_RESTORER="/nfs/home/share/gem5_ci/tools/gcpt.bin" - export GEM5_HOME=$(pwd) - mkdir -p $GEM5_HOME/util/xs_scripts/test_h - cd $GEM5_HOME/util/xs_scripts/test_h - bash ../kmh_6wide_h.sh /nfs/home/share/gem5_ci/checkpoints/gcbh_test.zstd - diff --git a/util/xs_scripts/kmh_v3_btb.sh b/util/xs_scripts/kmh_v3_btb.sh index 12d4789fb4..c1071dac38 100644 --- a/util/xs_scripts/kmh_v3_btb.sh +++ b/util/xs_scripts/kmh_v3_btb.sh @@ -7,4 +7,4 @@ for var in GCBV_REF_SO GCB_RESTORER gem5_home; do checkForVariable $var done -$gem5 $gem5_home/configs/example/xiangshan.py --generic-rv-cpt=$1 --bp-type=DecoupledBPUWithBTB --ideal-kmhv3 \ No newline at end of file +$gem5 $gem5_home/configs/example/xiangshan.py --generic-rv-cpt=$1 --bp-type=DecoupledBPUWithBTB --ideal-kmhv3 --disable-2taken \ No newline at end of file diff --git a/util/xs_scripts/kmh_v3_btb_2taken.sh b/util/xs_scripts/kmh_v3_btb_2taken.sh new file mode 100644 index 0000000000..12d4789fb4 --- /dev/null +++ b/util/xs_scripts/kmh_v3_btb_2taken.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +script_dir=$(dirname -- "$( readlink -f -- "$0"; )") +source $script_dir/common.sh + +for var in GCBV_REF_SO GCB_RESTORER gem5_home; do + checkForVariable $var +done + +$gem5 $gem5_home/configs/example/xiangshan.py --generic-rv-cpt=$1 --bp-type=DecoupledBPUWithBTB --ideal-kmhv3 \ No newline at end of file From f23f3eb49c955c8f24d9b3ee03101980cf3f59d5 Mon Sep 17 00:00:00 2001 From: Xu Boran Date: Fri, 27 Jun 2025 18:14:05 +0800 Subject: [PATCH 07/23] cpu-o3: pred: statistics for 2 taken training condition This commit tracks the reason we can or cannot train a two taken entry in ubtb Change-Id: I5565927502b6af4a687d3d0b9025c74283ffb0b4 --- src/cpu/pred/btb/btb_ubtb.cc | 33 ++++++++++++++++++++++++++++++++- src/cpu/pred/btb/btb_ubtb.hh | 11 +++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/cpu/pred/btb/btb_ubtb.cc b/src/cpu/pred/btb/btb_ubtb.cc index 69812d32ca..36249862db 100644 --- a/src/cpu/pred/btb/btb_ubtb.cc +++ b/src/cpu/pred/btb/btb_ubtb.cc @@ -353,8 +353,12 @@ UBTB::check2TakenConditions(FullBTBPrediction& dff, const FullBTBPrediction& s3P { assert(dff.getTarget(predictWidth) == s3Pred.bbStart); + // Increment total check counter + ubtbStats.twoTakenConditionChecks++; + // 1. Both predictions must have at least one branch. if (dff.btbEntries.empty() || s3Pred.btbEntries.empty()) { + ubtbStats.twoTakenFailEmptyPreds++; return false; } @@ -363,6 +367,7 @@ UBTB::check2TakenConditions(FullBTBPrediction& dff, const FullBTBPrediction& s3P // 2. The first branch must be taken for a 2-taken sequence to form. if (!dff.isTaken()) { + ubtbStats.twoTakenFailFirstNotTaken++; return false; } @@ -370,33 +375,39 @@ UBTB::check2TakenConditions(FullBTBPrediction& dff, const FullBTBPrediction& s3P // Rule: 'multi-target indirect' as 1st branch is not allowed. if (firstBr.isIndirect) { + ubtbStats.twoTakenFailFirstIndirect++; return false; } // Rule: 'multi-target indirect' as 2nd branch is not allowed. if (secondBr.isIndirect) { + ubtbStats.twoTakenFailSecondIndirect++; return false; } // Rule: 'cond' as 2nd branch is not allowed. // this rule implies that the second branch is always taken if (secondBr.isCond) { + ubtbStats.twoTakenFailSecondCond++; return false; } // Rule: 'ret -> ret' is not allowed to avoid multiple RAS reads. if (firstBr.isReturn && secondBr.isReturn) { + ubtbStats.twoTakenFailRetRet++; return false; } // Rule: 'call -> call' is not allowed to avoid multiple RAS writes. if (firstBr.isCall && secondBr.isCall) { + ubtbStats.twoTakenFailCallCall++; return false; } // (call -> ret is allowed, so no check needed) // All conditions passed. + ubtbStats.twoTakenConditionPassed++; return true; } @@ -704,7 +715,27 @@ UBTB::UBTBStats::UBTBStats(statistics::Group *parent) ADD_STAT(callHits, statistics::units::Count::get(), "calls committed that was predicted hit"), ADD_STAT(callMisses, statistics::units::Count::get(), "calls committed that was predicted miss"), ADD_STAT(returnHits, statistics::units::Count::get(), "returns committed that was predicted hit"), - ADD_STAT(returnMisses, statistics::units::Count::get(), "returns committed that was predicted miss") + ADD_STAT(returnMisses, statistics::units::Count::get(), "returns committed that was predicted miss"), + + // 2-taken condition check statistics + ADD_STAT(twoTakenConditionChecks, statistics::units::Count::get(), + "Total number of 2-taken condition checks performed"), + ADD_STAT(twoTakenConditionPassed, statistics::units::Count::get(), + "Number of times all 2-taken conditions passed"), + ADD_STAT(twoTakenFailEmptyPreds, statistics::units::Count::get(), + "2-taken rejected due to empty predictions (dff or s3)"), + ADD_STAT(twoTakenFailFirstNotTaken, statistics::units::Count::get(), + "2-taken rejected due to first branch not taken"), + ADD_STAT(twoTakenFailFirstIndirect, statistics::units::Count::get(), + "2-taken rejected due to first branch being indirect"), + ADD_STAT(twoTakenFailSecondIndirect, statistics::units::Count::get(), + "2-taken rejected due to second branch being indirect"), + ADD_STAT(twoTakenFailSecondCond, statistics::units::Count::get(), + "2-taken rejected due to second branch being conditional"), + ADD_STAT(twoTakenFailRetRet, statistics::units::Count::get(), + "2-taken rejected due to ret->ret sequence"), + ADD_STAT(twoTakenFailCallCall, statistics::units::Count::get(), + "2-taken rejected due to call->call sequence") { } diff --git a/src/cpu/pred/btb/btb_ubtb.hh b/src/cpu/pred/btb/btb_ubtb.hh index 2f926ee43d..2a13598eb7 100644 --- a/src/cpu/pred/btb/btb_ubtb.hh +++ b/src/cpu/pred/btb/btb_ubtb.hh @@ -378,6 +378,17 @@ class UBTB : public TimedBaseBTBPredictor statistics::Scalar returnHits; statistics::Scalar returnMisses; + // 2-taken condition check statistics + statistics::Scalar twoTakenConditionChecks; ///< Total number of 2-taken condition checks + statistics::Scalar twoTakenConditionPassed; ///< Number of times all conditions passed + statistics::Scalar twoTakenFailEmptyPreds; ///< Rejected due to empty predictions + statistics::Scalar twoTakenFailFirstNotTaken; ///< Rejected due to first branch not taken + statistics::Scalar twoTakenFailFirstIndirect; ///< Rejected due to first branch being indirect + statistics::Scalar twoTakenFailSecondIndirect; ///< Rejected due to second branch being indirect + statistics::Scalar twoTakenFailSecondCond; ///< Rejected due to second branch being conditional + statistics::Scalar twoTakenFailRetRet; ///< Rejected due to ret->ret sequence + statistics::Scalar twoTakenFailCallCall; ///< Rejected due to call->call sequence + UBTBStats(statistics::Group* parent); } ubtbStats; From bbe9e900e976658f4ae5ad496c8d202d43d62373 Mon Sep 17 00:00:00 2001 From: Xu Boran Date: Fri, 27 Jun 2025 18:38:41 +0800 Subject: [PATCH 08/23] test: the upperbound of 2taken when ubtb is large enough --- configs/example/xiangshan.py | 1 + 1 file changed, 1 insertion(+) diff --git a/configs/example/xiangshan.py b/configs/example/xiangshan.py index 2893e23983..db1fe8da72 100644 --- a/configs/example/xiangshan.py +++ b/configs/example/xiangshan.py @@ -383,6 +383,7 @@ def setKmhV3IdealParams(args, system): # TODO: BTB TAGE do not bave base table, do not support SC cpu.branchPred.tage.tableSizes = [2048] * 14 # 2ways, 2048 sets cpu.branchPred.enable2Taken = not args.disable_2taken + cpu.branchPred.ubtb.numEntries = 1024 cpu.branchPred.tage.enableSC = False # TODO(bug): When numBr changes, enabling SC will trigger an assert cpu.branchPred.ftq_size = 256 From b84ba797820470ccbecf1a7096d0d8fbee1d9a2e Mon Sep 17 00:00:00 2001 From: Xu Boran Date: Mon, 30 Jun 2025 10:24:00 +0800 Subject: [PATCH 09/23] cpu-o3: pred: allow alwaysTaken branch as 2nd pred of 2taken. Change-Id: I8b38a9daa93de81ce056129e2f1e468d421f0a49 --- src/cpu/pred/btb/btb_ubtb.cc | 29 +++++++++++++++++++++++------ src/cpu/pred/btb/btb_ubtb.hh | 1 + 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/cpu/pred/btb/btb_ubtb.cc b/src/cpu/pred/btb/btb_ubtb.cc index 36249862db..31f3ef37d9 100644 --- a/src/cpu/pred/btb/btb_ubtb.cc +++ b/src/cpu/pred/btb/btb_ubtb.cc @@ -146,9 +146,6 @@ UBTB::fillStagePredictions(const TickedUBTBEntry &entry, std::vector ret' is not allowed to avoid multiple RAS reads. @@ -732,6 +747,8 @@ UBTB::UBTBStats::UBTBStats(statistics::Group *parent) "2-taken rejected due to second branch being indirect"), ADD_STAT(twoTakenFailSecondCond, statistics::units::Count::get(), "2-taken rejected due to second branch being conditional"), + ADD_STAT(twoTakenAcceptAlwaysTaken, statistics::units::Count::get(), + "2-taken accepted alwaysTaken conditional branch as second prediction"), ADD_STAT(twoTakenFailRetRet, statistics::units::Count::get(), "2-taken rejected due to ret->ret sequence"), ADD_STAT(twoTakenFailCallCall, statistics::units::Count::get(), diff --git a/src/cpu/pred/btb/btb_ubtb.hh b/src/cpu/pred/btb/btb_ubtb.hh index 2a13598eb7..35aa5b0662 100644 --- a/src/cpu/pred/btb/btb_ubtb.hh +++ b/src/cpu/pred/btb/btb_ubtb.hh @@ -386,6 +386,7 @@ class UBTB : public TimedBaseBTBPredictor statistics::Scalar twoTakenFailFirstIndirect; ///< Rejected due to first branch being indirect statistics::Scalar twoTakenFailSecondIndirect; ///< Rejected due to second branch being indirect statistics::Scalar twoTakenFailSecondCond; ///< Rejected due to second branch being conditional + statistics::Scalar twoTakenAcceptAlwaysTaken; ///< Accepted alwaysTaken conditional branch as 2nd prediction statistics::Scalar twoTakenFailRetRet; ///< Rejected due to ret->ret sequence statistics::Scalar twoTakenFailCallCall; ///< Rejected due to call->call sequence From e60e6ffffd28bcc0865c81b87ae19185a361a8ca Mon Sep 17 00:00:00 2001 From: Xu Boran Date: Tue, 1 Jul 2025 10:25:32 +0800 Subject: [PATCH 10/23] cpu-o3: Enable mBTB updates for 2nd prediction fetch blocks Implement optimization to allow MBTB to update 2nd prediction fetch blocks. Previously, always-taken conditional branches in the 2nd prediction of a 2-taken pair would suffer performance degradation when becoming bi-directional, as the first "not taken" squash wouldn't update MBTB due to missing mBTB meta, requiring multiple squashes before proper training. Solution: for 2nd pred of a 2taken pair, have uBTB generate and store a copy of BTBMeta during getTwoTakenPrediction(). DecoupledBPU now retrieves this stored meta when creating fetch stream entries for the second predictions, enabling immediate MBTB updates when the fetch block eventually get commited. Changes: - Add BTBMeta storage and retrieval to uBTB for second predictions - Integrate meta generation into getTwoTakenPrediction() flow - Modify DecoupledBPU to use uBTB's stored meta for MBTB's predMeta - Maintain backward compatibility for all other prediction scenarios This reduces squashes and improves performance for workloads with conditional branches in 2-taken prediction patterns. Change-Id: Ifb91988bce44a5e5d618b597c82babdbd6b54f96 --- src/cpu/pred/btb/btb.hh | 3 +++ src/cpu/pred/btb/btb_ubtb.cc | 25 +++++++++++++++++++++++++ src/cpu/pred/btb/btb_ubtb.hh | 16 ++++++++++++++++ src/cpu/pred/btb/decoupled_bpred.cc | 22 +++++++++++++++++++--- 4 files changed, 63 insertions(+), 3 deletions(-) diff --git a/src/cpu/pred/btb/btb.hh b/src/cpu/pred/btb/btb.hh index c531876e48..5d2e6f031e 100644 --- a/src/cpu/pred/btb/btb.hh +++ b/src/cpu/pred/btb/btb.hh @@ -67,6 +67,9 @@ namespace btb_pred class DefaultBTB : public TimedBaseBTBPredictor { + // Allow UBTB to access private BTBMeta for second prediction support + friend class UBTB; + private: public: diff --git a/src/cpu/pred/btb/btb_ubtb.cc b/src/cpu/pred/btb/btb_ubtb.cc index 31f3ef37d9..f83e784260 100644 --- a/src/cpu/pred/btb/btb_ubtb.cc +++ b/src/cpu/pred/btb/btb_ubtb.cc @@ -32,6 +32,7 @@ #include "base/intmath.hh" #include "base/trace.hh" #include "cpu/o3/dyn_inst.hh" +#include "cpu/pred/btb/btb.hh" #include "debug/Fetch.hh" #include "stream_struct.hh" @@ -188,6 +189,9 @@ void UBTB::putPCHistory(Addr startAddr, const boost::dynamic_bitset<> &history, std::vector &stagePreds) { + // Clear any previous MBTB meta + mbtbSecondPredMeta = nullptr; + // Reuse existing lookup and prediction logic meta = std::make_shared(); int hit_index = lookup(startAddr); @@ -208,6 +212,9 @@ UBTB::getTwoTakenPrediction(Addr startAddr, const boost::dynamic_bitset<> &histo std::vector &stagePreds, FullBTBPrediction &secondPrediction) { + // Clear any previous MBTB meta + mbtbSecondPredMeta = nullptr; + // Reuse existing lookup and prediction logic meta = std::make_shared(); int hit_index = lookup(startAddr); @@ -242,6 +249,10 @@ UBTB::getTwoTakenPrediction(Addr startAddr, const boost::dynamic_bitset<> &histo if (control_addr >= second_bb_start && control_addr < fall_through) { has_second_prediction = true; + + // Create MBTB meta for the second prediction + createMBTBMetaForSecondPrediction(entry.branch_info_2nd); + DPRINTF(UBTB, "uBTB: Valid second prediction - bbStart: %#lx, controlAddr: %#lx, target: %#lx\n", second_bb_start, control_addr, secondPrediction.getTarget(predictWidth)); } else { @@ -347,6 +358,20 @@ UBTB::addSecondPredictionToEntry(int entryIndex, FullBTBPrediction* secondPred) } } +void +UBTB::createMBTBMetaForSecondPrediction(const BranchInfo& branch_info_2nd) +{ + // Create a standard BTBMeta with the second prediction's branch info + mbtbSecondPredMeta = std::make_shared(); + + // Convert BranchInfo to BTBEntry for MBTB - much simpler! + BTBEntry btb_entry(branch_info_2nd); + + // Add to hit_entries (standard BTBMeta field) + mbtbSecondPredMeta->hit_entries.push_back(btb_entry); + + DPRINTF(UBTB, "Created MBTB meta for 2nd pred branch at PC %#lx\n", btb_entry.pc); +} void UBTB::updateUsingS3Pred(FullBTBPrediction &s3Pred) diff --git a/src/cpu/pred/btb/btb_ubtb.hh b/src/cpu/pred/btb/btb_ubtb.hh index 35aa5b0662..daebfa5e08 100644 --- a/src/cpu/pred/btb/btb_ubtb.hh +++ b/src/cpu/pred/btb/btb_ubtb.hh @@ -51,6 +51,7 @@ #include "base/logging.hh" #include "base/types.hh" #include "config/the_isa.hh" +#include "cpu/pred/btb/btb.hh" #include "cpu/pred/btb/stream_struct.hh" #include "cpu/pred/btb/timed_base_pred.hh" #include "debug/UBTB.hh" @@ -179,6 +180,13 @@ class UBTB : public TimedBaseBTBPredictor return meta; } + /** Retrieve stored MBTB meta for second prediction + * @return Returns the stored MBTB meta or nullptr if none available + */ + std::shared_ptr getMBTBSecondPredictionMeta() const { + return mbtbSecondPredMeta; + } + // the following methods are not used void specUpdateHist(const boost::dynamic_bitset<> &history, FullBTBPrediction &pred) override {} void recoverHist(const boost::dynamic_bitset<> &history, @@ -236,6 +244,9 @@ class UBTB : public TimedBaseBTBPredictor }; std::shared_ptr meta; + // Storage for MBTB meta created during getTwoTakenPrediction + std::shared_ptr mbtbSecondPredMeta{nullptr}; + // helper methods /* * Comparator for MRU heap @@ -318,6 +329,11 @@ class UBTB : public TimedBaseBTBPredictor */ void addSecondPredictionToEntry(int entryIndex, FullBTBPrediction* secondPred); + /** Helper to create MBTB meta for second prediction + * @param branch_info_2nd The branch information for the second prediction + */ + void createMBTBMetaForSecondPrediction(const BranchInfo& branch_info_2nd); + /** The uBTB structure: * - Implemented as a fully associative table * - Each entry can store one branch diff --git a/src/cpu/pred/btb/decoupled_bpred.cc b/src/cpu/pred/btb/decoupled_bpred.cc index 6fbdf72cee..d926d41456 100644 --- a/src/cpu/pred/btb/decoupled_bpred.cc +++ b/src/cpu/pred/btb/decoupled_bpred.cc @@ -1135,9 +1135,20 @@ void DecoupledBPUWithBTB::update(unsigned stream_id, ThreadID tid) if (!stream.isSecondFBPred) { updatePredictorComponents(stream); } else { - DPRINTF(DecoupleBP, "Skipping predictor update for second FB prediction at %#lx\n", stream.startPC); - // ras is the only predictor that relies on update from all FBs + DPRINTF(DecoupleBP, "Performing selective update for second FB prediction at %#lx\n", stream.startPC); + // For second predictions, only update RAS and MBTB ras->update(stream); + + // Prepare stream for MBTB update + stream.setUpdateInstEndPC(predictWidth); + stream.setUpdateBTBEntries(); + + // Generate new BTB entry for MBTB + btb->getAndSetNewBTBEntry(stream); + + // Update only MBTB component + btb->update(stream); + } // Track successful second prediction commits @@ -1876,7 +1887,12 @@ DecoupledBPUWithBTB::createFetchStreamEntry(bool is_second_pred) // Save predictors' metadata for (int i = 0; i < numComponents; i++) { if (is_second_pred) { - entry.predMetas[i] = components[i]->getSecondPredictionMeta(); + // For MBTB during second prediction, use uBTB's stored meta instead + if (components[i] == btb) { + entry.predMetas[i] = ubtb->getMBTBSecondPredictionMeta(); + } else { + entry.predMetas[i] = components[i]->getSecondPredictionMeta(); + } } else { entry.predMetas[i] = components[i]->getPredictionMeta(); } From 26c081402a63ca3dd9ffd5ca022a2d7f86995cdc Mon Sep 17 00:00:00 2001 From: Xu Boran Date: Tue, 1 Jul 2025 17:03:48 +0800 Subject: [PATCH 11/23] cpu-o3: Restore ABTB accuracy under 2-taken MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ABTB (the ahead-pipelined BTB) assumes that `putPCHistory()` is called once per consecutive fetch block (A, B, C, …). * With 2-taken the uBTB returns two blocks (A and B) in a single predictor tick. We therefore only call `putPCHistory()` once (for A). * For ABTB this looks like the sequence A → C, breaking the ahead-pipeline queue (aheadReadBtbEntries) and tanking its hit-rate in this commit, we Keep ABTB's *consecutive* invariant by silently pushing B into its ahead-read queue, but without asking it to produce a real prediction in the current cycle. Change-Id: I8a643866287f22575f38624594d2901ba20afc78 Result: ABTB’s ahead-pipeline queue remains consecutive even when uBTB emits two fetch blocks, recovering its hit-rate without affecting non-ABTB paths. --- src/cpu/pred/btb/btb.cc | 29 +++++++++++++++++++++++++++++ src/cpu/pred/btb/btb.hh | 2 +- src/cpu/pred/btb/decoupled_bpred.cc | 18 ++++++++++++++---- 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/cpu/pred/btb/btb.cc b/src/cpu/pred/btb/btb.cc index 128018b589..56ed60616a 100644 --- a/src/cpu/pred/btb/btb.cc +++ b/src/cpu/pred/btb/btb.cc @@ -951,6 +951,35 @@ DefaultBTB::BTBStats::BTBStats(statistics::Group* parent) : } } +void +DefaultBTB::preloadBlock(Addr block_pc) +{ + // Only meaningful for ahead-pipelined variants (ABTB) which are mutually exclusive with half-aligned mode. + if (aheadPipelinedStages == 0) { + return; + } + + // Ahead-pipeline and half-aligned cannot coexist (constructor already asserts), reinforce here. + assert(!entryHalfAligned); + + // Ignore mis-aligned sentinel addresses (bit0==1). + if (block_pc & 0x1) { + return; + } + + Addr btb_idx = getIndex(block_pc); + assert(btb_idx < numSets); + auto btb_set = btb[btb_idx]; + aheadReadBtbEntries.push(std::make_tuple(block_pc, btb_idx, btb_set)); + + if (aheadReadBtbEntries.size() >= aheadPipelinedStages+1) { + // pop the oldest entry + aheadReadBtbEntries.pop(); + } + + // Silent queue padding – no tag compare/pop or stats. +} + } // namespace btb_pred } // namespace branch_prediction } // namespace gem5 diff --git a/src/cpu/pred/btb/btb.hh b/src/cpu/pred/btb/btb.hh index 5d2e6f031e..89ae19f6a8 100644 --- a/src/cpu/pred/btb/btb.hh +++ b/src/cpu/pred/btb/btb.hh @@ -196,7 +196,7 @@ class DefaultBTB : public TimedBaseBTBPredictor } } - + void preloadBlock(Addr pc); private: /** Returns the index into the BTB, based on the branch's PC. diff --git a/src/cpu/pred/btb/decoupled_bpred.cc b/src/cpu/pred/btb/decoupled_bpred.cc index d926d41456..70a9d764f3 100644 --- a/src/cpu/pred/btb/decoupled_bpred.cc +++ b/src/cpu/pred/btb/decoupled_bpred.cc @@ -1,15 +1,16 @@ #include "cpu/pred/btb/decoupled_bpred.hh" -#include "base/output.hh" #include "base/debug_helper.hh" +#include "base/output.hh" #include "cpu/o3/cpu.hh" #include "cpu/o3/dyn_inst.hh" -#include "debug/DecoupleBPVerbose.hh" -#include "debug/DecoupleBPHist.hh" -#include "debug/Override.hh" +#include "debug/AheadPipeline.hh" #include "debug/BTB.hh" +#include "debug/DecoupleBPHist.hh" +#include "debug/DecoupleBPVerbose.hh" #include "debug/ITTAGE.hh" #include "debug/JumpAheadPredictor.hh" +#include "debug/Override.hh" #include "debug/Profiling.hh" #include "sim/core.hh" @@ -587,6 +588,13 @@ DecoupledBPUWithBTB::tick() // Check if the second prediction is still valid after overrides. validateSecondFBPrediction(); + // If we still have a valid second FB, pad ABTB ahead-pipeline now. + if (hasSecondPrediction && abtb && abtb->aheadPipelinedStages > 0) { + abtb->preloadBlock(secondPrediction.bbStart); + DPRINTF(AheadPipeline, "preloadBlock: queued second FB %#lx for ABTB ahead pipeline (stages=%d)\n", + secondPrediction.bbStart, abtb->aheadPipelinedStages); + } + // Inline updateDFF() - Always store finalPred // This stored block is used for 2-taken training. // Admittedly, this FB doesn't always directly precede the s3 pred of the next cycle, @@ -672,6 +680,8 @@ DecoupledBPUWithBTB::requestNewPrediction() hasSecondPrediction = false; ubtbHitIndex = -1; secondPrediction.btbEntries.clear(); + secondPrediction.predSource = 0; + secondPrediction.overrideReason = OverrideReason::NO_OVERRIDE; // Query each predictor component with current PC and history for (int i = 0; i < numComponents; i++) { From 8d972ae42aef37442b6841605d1a77f35b335ce0 Mon Sep 17 00:00:00 2001 From: Xu Boran Date: Wed, 2 Jul 2025 16:01:20 +0800 Subject: [PATCH 12/23] cpu-o3: pred: check histInfo match in ubtb replacement. before this commit, the hisotry info of a S3 pred is directly trained into ubtb and its correctness will not be checked when trained with a new S3 pred, this results in a massive increase in intra flush caused by history info mismatch. Now, evertime a new S3pred gets trained into the ubtb, we check if the existing entry has the right amount of numNTCond, which is ubtb's way of storing the histInfo. Change-Id: I88fb40b8efd7e7a5702b4dc915ce5a210121396e --- src/cpu/pred/btb/btb_ubtb.cc | 30 ++++++++++++++++++++++++------ src/cpu/pred/btb/btb_ubtb.hh | 6 ++++++ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/cpu/pred/btb/btb_ubtb.cc b/src/cpu/pred/btb/btb_ubtb.cc index f83e784260..3601a2d9c9 100644 --- a/src/cpu/pred/btb/btb_ubtb.cc +++ b/src/cpu/pred/btb/btb_ubtb.cc @@ -319,11 +319,7 @@ UBTB::replaceOldEntry(int entryIndex, FullBTBPrediction & newPrediction) /* save the number of conditional branches before the taken branch * this is useful in the prediction phase: to generate the correct speculative history information */ - newEntry.numNTConds = newPrediction.getHistInfo().first; - if (newPrediction.getTakenEntry().isCond) { - newEntry.numNTConds--; - assert(newEntry.numNTConds >= 0); - } + newEntry.numNTConds = calculateNumNTConds(newPrediction); ubtb[entryIndex] = newEntry; @@ -373,6 +369,25 @@ UBTB::createMBTBMetaForSecondPrediction(const BranchInfo& branch_info_2nd) DPRINTF(UBTB, "Created MBTB meta for 2nd pred branch at PC %#lx\n", btb_entry.pc); } +int +UBTB::calculateNumNTConds(FullBTBPrediction& prediction) +{ + /* Calculate the number of conditional branches before the taken branch + * This is useful in the prediction phase to generate correct speculative history information + * + * Logic: + * - Start with shift amount from getHistInfo().first (total conditional branches) + * - If the taken branch itself is conditional, subtract 1 (don't count the taken branch) + */ + int numNTConds = prediction.getHistInfo().first; + if (prediction.getTakenEntry().isCond) { + numNTConds--; + assert(numNTConds >= 0 && "numNTConds should not be negative"); + } + + return numNTConds; +} + void UBTB::updateUsingS3Pred(FullBTBPrediction &s3Pred) { @@ -478,7 +493,10 @@ UBTB::updateEntryAtIndex(int entry_index, FullBTBPrediction& pred, FullBTBPredic } } else { // Both S0 and S3 predict taken - check if they match - if (entry.pc != pred.controlAddr() || entry.target != pred.getTarget(predictWidth)) { + // this check has a correspondence with match() in stream_struct.hh + if (entry.pc != pred.controlAddr() || + entry.target != pred.getTarget(predictWidth) || + entry.numNTConds != calculateNumNTConds(pred)) { // S0 and S3 predict different branch instruction updateUCtr(entry.uctr, false); if (entry.uctr == 0) { diff --git a/src/cpu/pred/btb/btb_ubtb.hh b/src/cpu/pred/btb/btb_ubtb.hh index daebfa5e08..3f495560ba 100644 --- a/src/cpu/pred/btb/btb_ubtb.hh +++ b/src/cpu/pred/btb/btb_ubtb.hh @@ -334,6 +334,12 @@ class UBTB : public TimedBaseBTBPredictor */ void createMBTBMetaForSecondPrediction(const BranchInfo& branch_info_2nd); + /** Helper function to calculate numNTConds (number of not-taken conditional branches) + * @param prediction The prediction containing history information + * @return Number of conditional branches before the taken branch + */ + int calculateNumNTConds(FullBTBPrediction& prediction); + /** The uBTB structure: * - Implemented as a fully associative table * - Each entry can store one branch From a676b7749300e92812b8bd59688ff8f6ca1956ec Mon Sep 17 00:00:00 2001 From: Xu Boran Date: Thu, 3 Jul 2025 10:38:22 +0800 Subject: [PATCH 13/23] cpu-o3: pred: added ratio stats for 2 taken Change-Id: I401fc3bda70847d0ea9c4e31caa4f76019ce63ed --- src/cpu/pred/btb/btb_ubtb.cc | 7 ++++++- src/cpu/pred/btb/btb_ubtb.hh | 3 +++ src/cpu/pred/btb/decoupled_bpred.cc | 12 +++++++++++- src/cpu/pred/btb/decoupled_bpred.hh | 4 ++++ 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/cpu/pred/btb/btb_ubtb.cc b/src/cpu/pred/btb/btb_ubtb.cc index 3601a2d9c9..0c79a0f5e2 100644 --- a/src/cpu/pred/btb/btb_ubtb.cc +++ b/src/cpu/pred/btb/btb_ubtb.cc @@ -795,8 +795,13 @@ UBTB::UBTBStats::UBTBStats(statistics::Group *parent) ADD_STAT(twoTakenFailRetRet, statistics::units::Count::get(), "2-taken rejected due to ret->ret sequence"), ADD_STAT(twoTakenFailCallCall, statistics::units::Count::get(), - "2-taken rejected due to call->call sequence") + "2-taken rejected due to call->call sequence"), + ADD_STAT(twoTakenTrainSuccessfulRatio, statistics::units::Rate< + statistics::units::Count, statistics::units::Count>::get(), + "Ratio of successful 2-taken conditions to total checks") { + // Initialize formula statistics + twoTakenTrainSuccessfulRatio = twoTakenConditionPassed / twoTakenConditionChecks; } diff --git a/src/cpu/pred/btb/btb_ubtb.hh b/src/cpu/pred/btb/btb_ubtb.hh index 3f495560ba..db3ef330a4 100644 --- a/src/cpu/pred/btb/btb_ubtb.hh +++ b/src/cpu/pred/btb/btb_ubtb.hh @@ -412,6 +412,9 @@ class UBTB : public TimedBaseBTBPredictor statistics::Scalar twoTakenFailRetRet; ///< Rejected due to ret->ret sequence statistics::Scalar twoTakenFailCallCall; ///< Rejected due to call->call sequence + // Formula statistics for performance ratios + statistics::Formula twoTakenTrainSuccessfulRatio; ///< Ratio of successful 2-taken conditions to total checks + UBTBStats(statistics::Group* parent); } ubtbStats; diff --git a/src/cpu/pred/btb/decoupled_bpred.cc b/src/cpu/pred/btb/decoupled_bpred.cc index 70a9d764f3..4e19aaec37 100644 --- a/src/cpu/pred/btb/decoupled_bpred.cc +++ b/src/cpu/pred/btb/decoupled_bpred.cc @@ -534,7 +534,13 @@ DecoupledBPUWithBTB::DBPBTBStats::DBPBTBStats(statistics::Group* parent, unsigne ADD_STAT(btbEntriesWithDifferentStart, statistics::units::Count::get(), "number of btb entries with different start PC"), ADD_STAT(btbEntriesWithOnlyOneJump, statistics::units::Count::get(), "number of btb entries with different start PC starting with a jump"), ADD_STAT(predFalseHit, statistics::units::Count::get(), "false hit detected at pred"), - ADD_STAT(commitFalseHit, statistics::units::Count::get(), "false hit detected at commit") + ADD_STAT(commitFalseHit, statistics::units::Count::get(), "false hit detected at commit"), + ADD_STAT(predTwoTakenRatio, statistics::units::Rate< + statistics::units::Count, statistics::units::Count>::get(), + "Ratio of 2-taken BPU cycles to total BPU cycles"), + ADD_STAT(commitSecondPredRatio, statistics::units::Rate< + statistics::units::Count, statistics::units::Count>::get(), + "Ratio of committed second predictions(in a 2 taken pair) to total FSQ entries") { predsOfEachStage.init(numStages); commitPredsFromEachStage.init(numStages+1); @@ -543,6 +549,10 @@ DecoupledBPUWithBTB::DBPBTBStats::DBPBTBStats(statistics::Group* parent, unsigne fsqEntryDist.init(0, fsqSize, 20).flags(statistics::total); commitFsqEntryHasInsts.init(0, maxInstsNum >> 1, 1); commitFsqEntryFetchedInsts.init(0, maxInstsNum >> 1, 1); + + // Initialize formula statistics + predTwoTakenRatio = predProduce2Taken / (predProduce2Taken + predProduce1Taken); + commitSecondPredRatio = secondPredCommitted / fsqEntryCommitted; } DecoupledBPUWithBTB::BpTrace::BpTrace(uint64_t fsqId, FetchStream &stream, const DynInstPtr &inst, bool mispred) diff --git a/src/cpu/pred/btb/decoupled_bpred.hh b/src/cpu/pred/btb/decoupled_bpred.hh index 2a45d1152f..22d6ab15de 100644 --- a/src/cpu/pred/btb/decoupled_bpred.hh +++ b/src/cpu/pred/btb/decoupled_bpred.hh @@ -400,6 +400,10 @@ class DecoupledBPUWithBTB : public BPredUnit statistics::Scalar predFalseHit; statistics::Scalar commitFalseHit; + // Formula statistics for performance ratios + statistics::Formula predTwoTakenRatio; ///< Ratio of 2-taken predictions to total predictions + statistics::Formula commitSecondPredRatio; ///< Ratio of committed second predictions to total FSQ entries + DBPBTBStats(statistics::Group* parent, unsigned numStages, unsigned fsqSize, unsigned maxInstsNum); } dbpBtbStats; From 995a6266c1a0401b9ff29f5032ed8ee622255a66 Mon Sep 17 00:00:00 2001 From: Xu Boran Date: Fri, 4 Jul 2025 10:28:34 +0800 Subject: [PATCH 14/23] cpu-o3: pred: give more intuitive name to methods Change-Id: Ieae48d4fdee30e62cdca45d1d0eec6fe9a47e56f --- src/cpu/pred/btb/btb_ubtb.cc | 26 +++++++++++++------------- src/cpu/pred/btb/btb_ubtb.hh | 21 +++++++++++---------- src/cpu/pred/btb/decoupled_bpred.cc | 8 ++++---- 3 files changed, 28 insertions(+), 27 deletions(-) diff --git a/src/cpu/pred/btb/btb_ubtb.cc b/src/cpu/pred/btb/btb_ubtb.cc index 0c79a0f5e2..cbbe194e84 100644 --- a/src/cpu/pred/btb/btb_ubtb.cc +++ b/src/cpu/pred/btb/btb_ubtb.cc @@ -208,7 +208,7 @@ UBTB::putPCHistory(Addr startAddr, const boost::dynamic_bitset<> &history, } std::pair -UBTB::getTwoTakenPrediction(Addr startAddr, const boost::dynamic_bitset<> &history, +UBTB::putPCHistory2Taken(Addr startAddr, const boost::dynamic_bitset<> &history, std::vector &stagePreds, FullBTBPrediction &secondPrediction) { @@ -251,7 +251,7 @@ UBTB::getTwoTakenPrediction(Addr startAddr, const boost::dynamic_bitset<> &histo has_second_prediction = true; // Create MBTB meta for the second prediction - createMBTBMetaForSecondPrediction(entry.branch_info_2nd); + createSecondPredictionMetaForMBTB(entry.branch_info_2nd); DPRINTF(UBTB, "uBTB: Valid second prediction - bbStart: %#lx, controlAddr: %#lx, target: %#lx\n", second_bb_start, control_addr, secondPrediction.getTarget(predictWidth)); @@ -306,7 +306,7 @@ UBTB::lookup(Addr startAddr) void -UBTB::replaceOldEntry(int entryIndex, FullBTBPrediction & newPrediction) +UBTB::replaceEntry(int entryIndex, FullBTBPrediction & newPrediction) { assert(entryIndex >= 0 && entryIndex < static_cast(ubtb.size())); assert(newPrediction.getTakenEntry().valid); @@ -355,7 +355,7 @@ UBTB::addSecondPredictionToEntry(int entryIndex, FullBTBPrediction* secondPred) } void -UBTB::createMBTBMetaForSecondPrediction(const BranchInfo& branch_info_2nd) +UBTB::createSecondPredictionMetaForMBTB(const BranchInfo& branch_info_2nd) { // Create a standard BTBMeta with the second prediction's branch info mbtbSecondPredMeta = std::make_shared(); @@ -389,13 +389,13 @@ UBTB::calculateNumNTConds(FullBTBPrediction& prediction) } void -UBTB::updateUsingS3Pred(FullBTBPrediction &s3Pred) +UBTB::train1Taken(FullBTBPrediction &s3Pred) { DPRINTF(UBTB, "1-taken updateUsingS3Pred: hit_index=%d, s3Pred.bbStart=%#lx\n", lastPred.hit_index, s3Pred.bbStart); // Use the common helper function with the hit index from lastPred (no second prediction) - updateEntryAtIndex(lastPred.hit_index, s3Pred, nullptr); + trainCommon(lastPred.hit_index, s3Pred, nullptr); } @@ -469,7 +469,7 @@ UBTB::check2TakenConditions(FullBTBPrediction& dff, const FullBTBPrediction& s3P // theoretically pred is a const reference, but certain functions // like getTakenEntry() are factually const but not declared as const void -UBTB::updateEntryAtIndex(int entry_index, FullBTBPrediction& pred, FullBTBPrediction* secondPred) +UBTB::trainCommon(int entry_index, FullBTBPrediction& pred, FullBTBPrediction* secondPred) { DPRINTF(UBTB, "updateEntryAtIndex: entry_index=%d, pred.bbStart=%#lx, secondPred=%s\n", entry_index, pred.bbStart, secondPred ? "provided" : "null"); @@ -501,7 +501,7 @@ UBTB::updateEntryAtIndex(int entry_index, FullBTBPrediction& pred, FullBTBPredic updateUCtr(entry.uctr, false); if (entry.uctr == 0) { // Replace the old entry with the new one - replaceOldEntry(entry_index, const_cast(pred)); + replaceEntry(entry_index, const_cast(pred)); // Add second prediction if provided if (secondPred != nullptr) { addSecondPredictionToEntry(entry_index, secondPred); @@ -553,7 +553,7 @@ UBTB::updateEntryAtIndex(int entry_index, FullBTBPrediction& pred, FullBTBPredic } // Replace the entry with the new prediction - replaceOldEntry(toBeReplacedIndex, const_cast(pred)); + replaceEntry(toBeReplacedIndex, const_cast(pred)); // Add second prediction if provided if (secondPred != nullptr) { addSecondPredictionToEntry(toBeReplacedIndex, secondPred); @@ -567,7 +567,7 @@ UBTB::updateEntryAtIndex(int entry_index, FullBTBPrediction& pred, FullBTBPredic } void -UBTB::updateUsingS3Pred(FullBTBPrediction &dff_pred, +UBTB::train2Taken(FullBTBPrediction &dff_pred, FullBTBPrediction &s3_pred, int hit_index) // hit index is the index stored in dff, along with dff_pred { @@ -579,7 +579,7 @@ UBTB::updateUsingS3Pred(FullBTBPrediction &dff_pred, DPRINTF(UBTB, "2-taken training rejected: FBs are not consecutive (%#lx -> %#lx vs %#lx)\n", dff_pred.bbStart, dff_pred.getTarget(predictWidth), s3_pred.bbStart); // Fall back to training only with dff_pred using the correct entry (previous cycle's hit) - updateEntryAtIndex(hit_index, dff_pred, nullptr); + trainCommon(hit_index, dff_pred, nullptr); return; } @@ -587,12 +587,12 @@ UBTB::updateUsingS3Pred(FullBTBPrediction &dff_pred, if (!check2TakenConditions(dff_pred, s3_pred)) { DPRINTF(UBTB, "2-taken training rejected: conditions not met\n"); // Fall back to training only with dff_pred using the correct entry (previous cycle's hit) - updateEntryAtIndex(hit_index, dff_pred, nullptr); + trainCommon(hit_index, dff_pred, nullptr); return; } // Train as 2-taken: pass s3_pred as second prediction - updateEntryAtIndex(hit_index, dff_pred, &s3_pred); + trainCommon(hit_index, dff_pred, &s3_pred); } void diff --git a/src/cpu/pred/btb/btb_ubtb.hh b/src/cpu/pred/btb/btb_ubtb.hh index db3ef330a4..801c49ac57 100644 --- a/src/cpu/pred/btb/btb_ubtb.hh +++ b/src/cpu/pred/btb/btb_ubtb.hh @@ -134,7 +134,7 @@ class UBTB : public TimedBaseBTBPredictor * @param secondPrediction Reference to store secondary prediction if available * @return Pair containing (hit_index, has_second_prediction) */ - std::pair getTwoTakenPrediction(Addr startAddr, + std::pair putPCHistory2Taken(Addr startAddr, const boost::dynamic_bitset<> &history, std::vector &stagePreds, FullBTBPrediction &secondPrediction); @@ -147,7 +147,7 @@ class UBTB : public TimedBaseBTBPredictor * * @param s3Pred The S3 prediction containing branch information and target */ - void updateUsingS3Pred(FullBTBPrediction &s3Pred); + void train1Taken(FullBTBPrediction &s3Pred); /** * Updates the uBTB using S3 prediction with 2-taken support (training/learning phase) @@ -157,7 +157,7 @@ class UBTB : public TimedBaseBTBPredictor * @param s3_pred The second FB (current S3 prediction) * @param hit_index The hit index from getTwoTakenPrediction (-1 if miss) */ - void updateUsingS3Pred(FullBTBPrediction &dff_pred, + void train2Taken(FullBTBPrediction &dff_pred, FullBTBPrediction &s3_pred, int hit_index); @@ -183,14 +183,15 @@ class UBTB : public TimedBaseBTBPredictor /** Retrieve stored MBTB meta for second prediction * @return Returns the stored MBTB meta or nullptr if none available */ - std::shared_ptr getMBTBSecondPredictionMeta() const { + std::shared_ptr getSecondPredictionMetaForMBTB() const { return mbtbSecondPredMeta; } - // the following methods are not used - void specUpdateHist(const boost::dynamic_bitset<> &history, FullBTBPrediction &pred) override {} void recoverHist(const boost::dynamic_bitset<> &history, const FetchStream &entry, int shamt, bool cond_taken) override; + + // the following methods are not used + void specUpdateHist(const boost::dynamic_bitset<> &history, FullBTBPrediction &pred) override {} void reset(); void setTrace() override; TraceManager *ubtbTrace; @@ -218,7 +219,7 @@ class UBTB : public TimedBaseBTBPredictor private: /** this struct holds the lastest prediction made by uBTB, - * it's set in putPCHistory, and used in updateUsingS3Pred + * it's set in putPCHistory, and used in train1Taken */ struct LastPred { @@ -313,14 +314,14 @@ class UBTB : public TimedBaseBTBPredictor * @param pred The S3 prediction to train with * @param secondPred Second prediction for 2-taken training (can be nullptr for 1-taken) */ - void updateEntryAtIndex(int entry_index, FullBTBPrediction& pred, FullBTBPrediction* secondPred); + void trainCommon(int entry_index, FullBTBPrediction& pred, FullBTBPrediction* secondPred); /** helper method called in updateUsingS3Pred: This function replaces an existing uBTB entry with new prediction * * @param entryIndex Index of the entry to replace * @param newPrediction The new prediction to store */ - void replaceOldEntry(int entryIndex, FullBTBPrediction & newPrediction); + void replaceEntry(int entryIndex, FullBTBPrediction & newPrediction); /** helper method for 2-taken: Add second prediction to an existing uBTB entry * @@ -332,7 +333,7 @@ class UBTB : public TimedBaseBTBPredictor /** Helper to create MBTB meta for second prediction * @param branch_info_2nd The branch information for the second prediction */ - void createMBTBMetaForSecondPrediction(const BranchInfo& branch_info_2nd); + void createSecondPredictionMetaForMBTB(const BranchInfo& branch_info_2nd); /** Helper function to calculate numNTConds (number of not-taken conditional branches) * @param prediction The prediction containing history information diff --git a/src/cpu/pred/btb/decoupled_bpred.cc b/src/cpu/pred/btb/decoupled_bpred.cc index 4e19aaec37..21ba7bb75a 100644 --- a/src/cpu/pred/btb/decoupled_bpred.cc +++ b/src/cpu/pred/btb/decoupled_bpred.cc @@ -698,7 +698,7 @@ DecoupledBPUWithBTB::requestNewPrediction() if (components[i] == ubtb) { // Special handling for uBTB - use 2-taken prediction if enabled if (enable2Taken) { - auto [hitIndex, secondAvailable] = ubtb->getTwoTakenPrediction( + auto [hitIndex, secondAvailable] = ubtb->putPCHistory2Taken( s0PC, s0History, predsOfEachStage, secondPrediction); // Store hit index for cross-cycle tracking @@ -1909,7 +1909,7 @@ DecoupledBPUWithBTB::createFetchStreamEntry(bool is_second_pred) if (is_second_pred) { // For MBTB during second prediction, use uBTB's stored meta instead if (components[i] == btb) { - entry.predMetas[i] = ubtb->getMBTBSecondPredictionMeta(); + entry.predMetas[i] = ubtb->getSecondPredictionMetaForMBTB(); } else { entry.predMetas[i] = components[i]->getSecondPredictionMeta(); } @@ -2211,7 +2211,7 @@ void DecoupledBPUWithBTB::trainUbtbFor2Taken() // 2-taken mode with valid DFF: Use overloaded updateUsingS3Pred DPRINTF(DecoupleBP, "trainUbtbFor2Taken: 2-taken training with DFF (prevIndex=%d)\n", predDFF.prevUbtbHitIndex); - ubtb->updateUsingS3Pred(predDFF.prevS3Pred, s3_pred, predDFF.prevUbtbHitIndex); + ubtb->train2Taken(predDFF.prevS3Pred, s3_pred, predDFF.prevUbtbHitIndex); } else { // 2-taken mode with invalid DFF: Skip training DPRINTF(DecoupleBP, "trainUbtbFor2Taken: 2-taken mode but DFF invalid, skipping training\n"); @@ -2219,7 +2219,7 @@ void DecoupledBPUWithBTB::trainUbtbFor2Taken() } else { // 1-taken mode: Use original updateUsingS3Pred DPRINTF(DecoupleBP, "trainUbtbFor2Taken: 1-taken training\n"); - ubtb->updateUsingS3Pred(s3_pred); + ubtb->train1Taken(s3_pred); } } } From 5d0b801e50d625f5e85013e5a8f3513c0841b763 Mon Sep 17 00:00:00 2001 From: Xu Boran Date: Fri, 4 Jul 2025 13:33:20 +0800 Subject: [PATCH 15/23] cpu-o3: pred: add ubtb training perf counters Change-Id: Iafc909f31c2c3ec4dce487ccb443860e1c04d58d --- src/cpu/pred/btb/btb_ubtb.cc | 32 ++++++++++++++++++++++++++++++++ src/cpu/pred/btb/btb_ubtb.hh | 11 +++++++++++ 2 files changed, 43 insertions(+) diff --git a/src/cpu/pred/btb/btb_ubtb.cc b/src/cpu/pred/btb/btb_ubtb.cc index cbbe194e84..86413a7e73 100644 --- a/src/cpu/pred/btb/btb_ubtb.cc +++ b/src/cpu/pred/btb/btb_ubtb.cc @@ -474,6 +474,9 @@ UBTB::trainCommon(int entry_index, FullBTBPrediction& pred, FullBTBPrediction* s DPRINTF(UBTB, "updateEntryAtIndex: entry_index=%d, pred.bbStart=%#lx, secondPred=%s\n", entry_index, pred.bbStart, secondPred ? "provided" : "null"); + // Count total training attempts + ubtbStats.trainAttempts++; + auto s3TakenEntry = pred.getTakenEntry(); if (entry_index >= 0) { @@ -485,10 +488,12 @@ UBTB::trainCommon(int entry_index, FullBTBPrediction& pred, FullBTBPrediction* s if (!s3TakenEntry.valid) { // S0 has a hit entry, but S3 predicts fall through + ubtbStats.trainHitFallThru++; updateUCtr(entry.uctr, false); if (entry.uctr == 0) { entry.valid = false; entry.valid_2nd = false; + ubtbStats.trainHitFallThruInvalidate++; DPRINTF(UBTB, "updateEntryAtIndex: Invalidated entry at index %d (fall through)\n", entry_index); } } else { @@ -498,9 +503,11 @@ UBTB::trainCommon(int entry_index, FullBTBPrediction& pred, FullBTBPrediction* s entry.target != pred.getTarget(predictWidth) || entry.numNTConds != calculateNumNTConds(pred)) { // S0 and S3 predict different branch instruction + ubtbStats.trainHitMismatch++; updateUCtr(entry.uctr, false); if (entry.uctr == 0) { // Replace the old entry with the new one + ubtbStats.trainHitMismatchReplace++; replaceEntry(entry_index, const_cast(pred)); // Add second prediction if provided if (secondPred != nullptr) { @@ -510,6 +517,7 @@ UBTB::trainCommon(int entry_index, FullBTBPrediction& pred, FullBTBPrediction* s } } else { // S0 and S3 predict the same (brpc and target) + ubtbStats.trainHitMatch++; updateUCtr(entry.uctr, true); // Add second prediction if provided @@ -526,10 +534,12 @@ UBTB::trainCommon(int entry_index, FullBTBPrediction& pred, FullBTBPrediction* s /* S0 misses, but S3 predicts taken, * generate new entry and replace another using LRU */ + ubtbStats.trainMissTaken++; // check if the new entry exist in the uBTB for (size_t i = 0; i < ubtb.size(); ++i) { if (ubtb[i].tag == getTag(pred.bbStart)) { //warn("updateEntryAtIndex: New entry already exists in uBTB\n"); + ubtbStats.trainDuplicateEntry++; return; } } @@ -561,6 +571,7 @@ UBTB::trainCommon(int entry_index, FullBTBPrediction& pred, FullBTBPrediction* s DPRINTF(UBTB, "updateEntryAtIndex: Created new entry at index %d (miss->hit)\n", toBeReplacedIndex); } else { // Both S0 and S3 predict fall through - do nothing + ubtbStats.trainMissFallThru++; DPRINTF(UBTB, "updateEntryAtIndex: No action needed (miss->fall through)\n"); } } @@ -796,6 +807,27 @@ UBTB::UBTBStats::UBTBStats(statistics::Group *parent) "2-taken rejected due to ret->ret sequence"), ADD_STAT(twoTakenFailCallCall, statistics::units::Count::get(), "2-taken rejected due to call->call sequence"), + + // Training scenario statistics + ADD_STAT(trainHitFallThru, statistics::units::Count::get(), + "Training scenarios: S0 hit but S3 fall through"), + ADD_STAT(trainHitMismatch, statistics::units::Count::get(), + "Training scenarios: S0 hit, S3 taken, but mismatch"), + ADD_STAT(trainHitMatch, statistics::units::Count::get(), + "Training scenarios: S0 hit, S3 taken, and match"), + ADD_STAT(trainMissTaken, statistics::units::Count::get(), + "Training scenarios: S0 miss, S3 taken (new entry created)"), + ADD_STAT(trainMissFallThru, statistics::units::Count::get(), + "Training scenarios: S0 miss, S3 fall through (no action)"), + ADD_STAT(trainHitMismatchReplace, statistics::units::Count::get(), + "Training scenarios: Hit mismatch leading to entry replacement"), + ADD_STAT(trainHitFallThruInvalidate, statistics::units::Count::get(), + "Training scenarios: Hit fall through leading to entry invalidation"), + ADD_STAT(trainAttempts, statistics::units::Count::get(), + "Total number of training attempts (trainCommon function calls)"), + ADD_STAT(trainDuplicateEntry, statistics::units::Count::get(), + "Early returns due to duplicate entry already existing in uBTB"), + ADD_STAT(twoTakenTrainSuccessfulRatio, statistics::units::Rate< statistics::units::Count, statistics::units::Count>::get(), "Ratio of successful 2-taken conditions to total checks") diff --git a/src/cpu/pred/btb/btb_ubtb.hh b/src/cpu/pred/btb/btb_ubtb.hh index 801c49ac57..a8bed88626 100644 --- a/src/cpu/pred/btb/btb_ubtb.hh +++ b/src/cpu/pred/btb/btb_ubtb.hh @@ -413,6 +413,17 @@ class UBTB : public TimedBaseBTBPredictor statistics::Scalar twoTakenFailRetRet; ///< Rejected due to ret->ret sequence statistics::Scalar twoTakenFailCallCall; ///< Rejected due to call->call sequence + // Training scenario statistics + statistics::Scalar trainHitFallThru; ///< S0 hit but S3 fall through + statistics::Scalar trainHitMismatch; ///< S0 hit, S3 taken, but mismatch + statistics::Scalar trainHitMatch; ///< S0 hit, S3 taken, and match + statistics::Scalar trainMissTaken; ///< S0 miss, S3 taken (new entry) + statistics::Scalar trainMissFallThru; ///< S0 miss, S3 fall through (no action) + statistics::Scalar trainHitMismatchReplace; ///< Hit mismatch leading to replacement + statistics::Scalar trainHitFallThruInvalidate; ///< Hit fall through leading to invalidation + statistics::Scalar trainAttempts; ///< Total number of training attempts (trainCommon calls) + statistics::Scalar trainDuplicateEntry; ///< Early returns due to duplicate entry already existing + // Formula statistics for performance ratios statistics::Formula twoTakenTrainSuccessfulRatio; ///< Ratio of successful 2-taken conditions to total checks From e222f42e176b31032e21614cc2428a11a2f399dc Mon Sep 17 00:00:00 2001 From: Xu Boran Date: Fri, 4 Jul 2025 10:57:03 +0800 Subject: [PATCH 16/23] cpu-o3: pred: dff buffer stores s3_pred instead of finalPred Change-Id: I4d3c7561bb74c43dcbfb312bf9e8cc59fbbb0272 --- src/cpu/pred/btb/decoupled_bpred.cc | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/cpu/pred/btb/decoupled_bpred.cc b/src/cpu/pred/btb/decoupled_bpred.cc index 21ba7bb75a..f7ca351a88 100644 --- a/src/cpu/pred/btb/decoupled_bpred.cc +++ b/src/cpu/pred/btb/decoupled_bpred.cc @@ -85,8 +85,8 @@ DecoupledBPUWithBTB::DecoupledBPUWithBTB(const DecoupledBPUWithBTBParams &p) predsOfEachStage.resize(numStages); for (unsigned i = 0; i < numStages; i++) { predsOfEachStage[i].predSource = i; - clearPreds(); } + clearPreds(); s0PC = 0x80000000; @@ -593,8 +593,20 @@ DecoupledBPUWithBTB::tick() // The training logic runs here, based on the previous cycle's DFF state. trainUbtbFor2Taken(); + + // Store s3_pred BEFORE clearing predictions in generateFinalPredAndCreateBubbles() + // This stored block is used for 2-taken training. + // Admittedly, this FB doesn't always directly precede the s3 pred of the next cycle, + // actually, when the current cycle produce a two-taken, dff and next cycls's s3 pred are not consecutive. + // this case is handled inside updateUsingS3Pred(), it simply train with dff. + DPRINTF(DecoupleBP, "updateDFF: Storing s3_pred for next cycle (ubtbHitIndex=%d)\n", ubtbHitIndex); + predDFF.storePrediction(predsOfEachStage[numStages-1], ubtbHitIndex); + numOverrideBubbles = generateFinalPredAndCreateBubbles(); + // Clear stage predictions for next cycle + clearPreds(); + // Check if the second prediction is still valid after overrides. validateSecondFBPrediction(); @@ -605,14 +617,6 @@ DecoupledBPUWithBTB::tick() secondPrediction.bbStart, abtb->aheadPipelinedStages); } - // Inline updateDFF() - Always store finalPred - // This stored block is used for 2-taken training. - // Admittedly, this FB doesn't always directly precede the s3 pred of the next cycle, - // actually, when the current cycle produce a two-taken, dff and next cycls's s3 pred are not consecutive. - // this case is handled inside updateUsingS3Pred(), it simply train with dff. - DPRINTF(DecoupleBP, "updateDFF: Storing finalPred for next cycle (ubtbHitIndex=%d)\n", ubtbHitIndex); - predDFF.storePrediction(finalPred, ubtbHitIndex); - bpuState = BpuState::PREDS_READY; // Update performance counters based on prediction type @@ -622,10 +626,6 @@ DecoupledBPUWithBTB::tick() dbpBtbStats.predProduce1Taken++; } - // Clear predictor outputs. - for (int i = 0; i < numStages; i++) { - predsOfEachStage[i].btbEntries.clear(); - } } // try Enqueue FTQ @@ -819,8 +819,6 @@ DecoupledBPUWithBTB::generateFinalPredAndCreateBubbles() printFullBTBPrediction(finalPred); dbpBtbStats.predsOfEachStage[first_hit_stage]++; - // Clear stage predictions for next cycle - clearPreds(); DPRINTF(Override, "Prediction complete: override bubbles=%d\n", first_hit_stage); return first_hit_stage; From efe74b26c1c5faf6bcc5584d139f413feff1a8ea Mon Sep 17 00:00:00 2001 From: Xu Boran Date: Fri, 4 Jul 2025 17:45:52 +0800 Subject: [PATCH 17/23] cpu-o3: pred: fixed bug where 2taken training gets skipped Change-Id: Ic6e6a1d47dc1ed60266007b53d53f9692356e984 --- src/cpu/pred/btb/decoupled_bpred.cc | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/cpu/pred/btb/decoupled_bpred.cc b/src/cpu/pred/btb/decoupled_bpred.cc index f7ca351a88..455507e180 100644 --- a/src/cpu/pred/btb/decoupled_bpred.cc +++ b/src/cpu/pred/btb/decoupled_bpred.cc @@ -2203,23 +2203,19 @@ void DecoupledBPUWithBTB::trainUbtbFor2Taken() auto& s3_pred = predsOfEachStage[numStages-1]; // Update ubtb based on the S3 prediction. - if (s3_pred.btbEntries.size() > 0) { - if (enable2Taken) { - if (predDFF.valid) { - // 2-taken mode with valid DFF: Use overloaded updateUsingS3Pred - DPRINTF(DecoupleBP, "trainUbtbFor2Taken: 2-taken training with DFF (prevIndex=%d)\n", - predDFF.prevUbtbHitIndex); - ubtb->train2Taken(predDFF.prevS3Pred, s3_pred, predDFF.prevUbtbHitIndex); - } else { - // 2-taken mode with invalid DFF: Skip training - DPRINTF(DecoupleBP, "trainUbtbFor2Taken: 2-taken mode but DFF invalid, skipping training\n"); - } - } else { - // 1-taken mode: Use original updateUsingS3Pred - DPRINTF(DecoupleBP, "trainUbtbFor2Taken: 1-taken training\n"); - ubtb->train1Taken(s3_pred); + if (enable2Taken) { + if (predDFF.valid) { + // 2-taken mode with valid DFF: Use train2Taken + DPRINTF(DecoupleBP, "trainUbtbFor2Taken: 2-taken training with DFF (prevIndex=%d)\n", + predDFF.prevUbtbHitIndex); + ubtb->train2Taken(predDFF.prevS3Pred, s3_pred, predDFF.prevUbtbHitIndex); } + } else { + // 1-taken mode: Use original train1Taken + DPRINTF(DecoupleBP, "trainUbtbFor2Taken: 1-taken training\n"); + ubtb->train1Taken(s3_pred); } + predDFF.reset(); } From bb8a86faddac20dabe5267fed9ae9dbdc72ad4da Mon Sep 17 00:00:00 2001 From: Xu Boran Date: Fri, 4 Jul 2025 18:02:21 +0800 Subject: [PATCH 18/23] Revert "test: the upperbound of 2taken when ubtb is large enough" This reverts commit 2b26c52c77cbfd885c0e9da065bf1b017aa3a0a9. --- configs/example/xiangshan.py | 1 - 1 file changed, 1 deletion(-) diff --git a/configs/example/xiangshan.py b/configs/example/xiangshan.py index db1fe8da72..2893e23983 100644 --- a/configs/example/xiangshan.py +++ b/configs/example/xiangshan.py @@ -383,7 +383,6 @@ def setKmhV3IdealParams(args, system): # TODO: BTB TAGE do not bave base table, do not support SC cpu.branchPred.tage.tableSizes = [2048] * 14 # 2ways, 2048 sets cpu.branchPred.enable2Taken = not args.disable_2taken - cpu.branchPred.ubtb.numEntries = 1024 cpu.branchPred.tage.enableSC = False # TODO(bug): When numBr changes, enabling SC will trigger an assert cpu.branchPred.ftq_size = 256 From 24f38a21c5a96fc3f8d0b976e7da8e348285b518 Mon Sep 17 00:00:00 2001 From: Xu Boran Date: Tue, 8 Jul 2025 16:01:40 +0800 Subject: [PATCH 19/23] cpu-o3: pred: 2taken support pt_2nd Change-Id: I2a4b9b4530c740883b6c11a7ffd11e9fa5d14044 --- src/cpu/pred/btb/btb_ubtb.cc | 177 +++++++++++++++++++++++++---------- src/cpu/pred/btb/btb_ubtb.hh | 40 ++++++-- 2 files changed, 156 insertions(+), 61 deletions(-) diff --git a/src/cpu/pred/btb/btb_ubtb.cc b/src/cpu/pred/btb/btb_ubtb.cc index 86413a7e73..5171363d15 100644 --- a/src/cpu/pred/btb/btb_ubtb.cc +++ b/src/cpu/pred/btb/btb_ubtb.cc @@ -185,6 +185,23 @@ UBTB::fillSecondPrediction(const BranchInfo &branchInfo, Addr bbStart, FullBTBPr // For direct unconditional branches, no additional setup needed beyond the BTBEntry } +// Helper function to construct a fallthrough FullBTBPrediction (for pt_2nd = false case) +void +UBTB::fillSecondPredictionFallthrough(Addr secondFBStart, FullBTBPrediction &prediction) +{ + prediction.btbEntries.clear(); + prediction.condTakens.clear(); + prediction.indirectTargets.clear(); + prediction.bbStart = secondFBStart; + prediction.predTick = curTick(); + prediction.predSource = 0; // uBTB is stage 0 + + // No BTB entries - this FB has no branches, just sequential execution + // Target is just the fallthrough address + DPRINTF(UBTB, "Created fallthrough second prediction: bbStart=%#lx, target=%#lx\n", + secondFBStart, prediction.getTarget(predictWidth)); +} + void UBTB::putPCHistory(Addr startAddr, const boost::dynamic_bitset<> &history, std::vector &stagePreds) @@ -233,35 +250,51 @@ UBTB::putPCHistory2Taken(Addr startAddr, const boost::dynamic_bitset<> &history, // Check if we have a second prediction to provide if (entry.valid && entry.valid_2nd) { - DPRINTF(UBTB, "uBTB: Found second prediction in entry, constructing 2nd FB\n"); - // Calculate target address for second prediction (where the second prediction should start) Addr second_bb_start = stagePreds[0].getTarget(predictWidth); - // Construct the second prediction from the stored branch info - fillSecondPrediction(entry.branch_info_2nd, second_bb_start, secondPrediction); + if (entry.pt_2nd) { + // Case 1: Second FB has a taken branch (existing behavior) + DPRINTF(UBTB, "uBTB: Found second prediction with branch in entry, constructing 2nd FB\n"); - // Validate range: the second branch should be within its own fetch block - if (secondPrediction.btbEntries.size() > 0) { - assert(secondPrediction.isTaken()); // this is guaranteed by the 2-taken design rules - Addr control_addr = secondPrediction.controlAddr(); - Addr fall_through = secondPrediction.getFallThrough(predictWidth); + fillSecondPrediction(entry.branch_info_2nd, second_bb_start, secondPrediction); - if (control_addr >= second_bb_start && control_addr < fall_through) { - has_second_prediction = true; + // Validate range: the second branch should be within its own fetch block + if (secondPrediction.btbEntries.size() > 0) { + assert(secondPrediction.isTaken()); // this is guaranteed by the 2-taken design rules + Addr control_addr = secondPrediction.controlAddr(); + Addr fall_through = secondPrediction.getFallThrough(predictWidth); - // Create MBTB meta for the second prediction - createSecondPredictionMetaForMBTB(entry.branch_info_2nd); + if (control_addr >= second_bb_start && control_addr < fall_through) { + has_second_prediction = true; + ubtbStats.twoTakenPredTaken++; - DPRINTF(UBTB, "uBTB: Valid second prediction - bbStart: %#lx, controlAddr: %#lx, target: %#lx\n", - second_bb_start, control_addr, secondPrediction.getTarget(predictWidth)); - } else { - // Range check failed, discard second prediction - secondPrediction.btbEntries.clear(); - DPRINTF(UBTB, - "uBTB: Second prediction failed range check - bbStart: %#lx, controlAddr: %#lx, fallThrough: %#lx\n", - second_bb_start, control_addr, fall_through); + // Create MBTB meta for the second prediction + createSecondPredictionMetaForMBTB(entry.branch_info_2nd); + + DPRINTF(UBTB, "uBTB: Valid second prediction - bbStart: %#lx, controlAddr: %#lx, target: %#lx\n", + second_bb_start, control_addr, secondPrediction.getTarget(predictWidth)); + } else { + // Range check failed, discard second prediction + ubtbStats.twoTakenPredRangeFailed++; + secondPrediction.btbEntries.clear(); + DPRINTF(UBTB, + "uBTB: Second prediction failed range check - bbStart: %#lx,\ + controlAddr: %#lx, fallThrough: %#lx\n", + second_bb_start, control_addr, fall_through); + } } + } else { + // Case 2: Second FB has no branches, just sequential execution (pt_2nd = false) + DPRINTF(UBTB, "uBTB: Found fallthrough second prediction (pt_2nd=false), constructing 2nd FB\n"); + + fillSecondPredictionFallthrough(second_bb_start, secondPrediction); + has_second_prediction = true; // Always valid for fallthrough case + mbtbSecondPredMeta = std::make_shared(); // empty meta is passed for mbtb + ubtbStats.twoTakenPredFallThrough++; + + DPRINTF(UBTB, "uBTB: Created fallthrough second prediction - bbStart: %#lx, target: %#lx\n", + second_bb_start, secondPrediction.getTarget(predictWidth)); } } @@ -332,7 +365,6 @@ UBTB::addSecondPredictionToEntry(int entryIndex, FullBTBPrediction* secondPred) { assert(entryIndex >= 0 && entryIndex < static_cast(ubtb.size())); assert(secondPred != nullptr && "Second prediction must not be null"); - assert(secondPred->getTakenEntry().valid && "Second prediction must be valid for 2-taken"); auto& entry = ubtb[entryIndex]; assert(entry.valid && "Entry must be valid to add second prediction"); @@ -340,15 +372,30 @@ UBTB::addSecondPredictionToEntry(int entryIndex, FullBTBPrediction* secondPred) // Only add if not already present if (!entry.valid_2nd) { entry.valid_2nd = true; - auto s3TakenEntry = secondPred->getTakenEntry(); - - // Copy branch info (BTBEntry inherits from BranchInfo) - entry.branch_info_2nd = s3TakenEntry; - // Override target with the one from prediction (may be set by RAS/ITTAGE) - entry.branch_info_2nd.target = secondPred->getTarget(predictWidth); + entry.pt_2nd = shouldSetPtSecond(*secondPred); + + if (entry.pt_2nd) { + // pt_2nd = true: second FB has branches + auto s3TakenEntry = secondPred->getTakenEntry(); + assert(s3TakenEntry.valid && "Second prediction must have valid taken entry for pt_2nd = true"); + assert(s3TakenEntry == secondPred->btbEntries[0] && + "after 2taken condition check, the BPU's Second Pred's first branch must be taken"); + + // Copy branch info (BTBEntry inherits from BranchInfo) + entry.branch_info_2nd = s3TakenEntry; + // Override target with the one from prediction (may be set by RAS/ITTAGE) + entry.branch_info_2nd.target = secondPred->getTarget(predictWidth); + + DPRINTF(UBTB, "UBTB: Added second prediction (pt_2nd=true) to entry at index %d: secondary PC %#lx\n", + entryIndex, secondPred->controlAddr()); + } else { + // pt_2nd = false: second FB has no branches (pure sequential execution) + // branch_info_2nd is not used in this case, but should be initialized for safety + entry.branch_info_2nd = BTBEntry(); // default constructor initializes to safe values - DPRINTF(UBTB, "UBTB: Added second prediction to entry at index %d: secondary PC %#lx\n", - entryIndex, secondPred->controlAddr()); + DPRINTF(UBTB, "UBTB: Added second prediction (pt_2nd=false) to entry at index %d: fallthrough at %#lx\n", + entryIndex, secondPred->bbStart); + } } else { DPRINTF(UBTB, "UBTB: Entry at index %d already has second prediction, skipping\n", entryIndex); } @@ -388,6 +435,16 @@ UBTB::calculateNumNTConds(FullBTBPrediction& prediction) return numNTConds; } +bool +UBTB::shouldSetPtSecond(const FullBTBPrediction& secondPred) +{ + // pt_2nd = true if second FB has any branches + // pt_2nd = false if second FB has no branches (pure sequential execution) + return !secondPred.btbEntries.empty(); +} + + + void UBTB::train1Taken(FullBTBPrediction &s3Pred) { @@ -407,29 +464,37 @@ UBTB::check2TakenConditions(FullBTBPrediction& dff, const FullBTBPrediction& s3P // Increment total check counter ubtbStats.twoTakenConditionChecks++; - // 1. Both predictions must have at least one branch. - if (dff.btbEntries.empty() || s3Pred.btbEntries.empty()) { + // 1. First prediction must have at least one branch. + if (dff.btbEntries.empty()) { ubtbStats.twoTakenFailEmptyPreds++; return false; } auto firstBr = dff.getTakenEntry(); - auto& secondBr = s3Pred.btbEntries[0]; // 2. The first branch must be taken for a 2-taken sequence to form. + // partly because ubtb only stores entries for 1st FBs that are taken if (!dff.isTaken()) { ubtbStats.twoTakenFailFirstNotTaken++; return false; } - // 3. Check branch type compatibility based on spec table. - - // Rule: 'multi-target indirect' as 1st branch is not allowed. + // 3. Rule: 'multi-target indirect' as 1st branch is not allowed. if (firstBr.isIndirect) { ubtbStats.twoTakenFailFirstIndirect++; return false; } + // 4. Handle pt_2nd = false case: second FB has no branches (sequential execution) + if (s3Pred.btbEntries.empty()) { + // This is the pt_2nd = false case - just sequential execution after taken branch + ubtbStats.twoTakenAcceptFallthrough++; + return true; + } + + // 5. pt_2nd = true case: both FBs have branches - apply compatibility rules + auto& secondBr = s3Pred.btbEntries[0]; + // Rule: 'multi-target indirect' as 2nd branch is not allowed. if (secondBr.isIndirect) { ubtbStats.twoTakenFailSecondIndirect++; @@ -437,14 +502,12 @@ UBTB::check2TakenConditions(FullBTBPrediction& dff, const FullBTBPrediction& s3P } // Rule: 'cond' as 2nd branch is not allowed, except for alwaysTaken conditional branches. - // this rule implies that the second branch is taken if (secondBr.isCond && !secondBr.alwaysTaken) { ubtbStats.twoTakenFailSecondCond++; return false; } else if (secondBr.isCond && secondBr.alwaysTaken) { - // Track when we accept alwaysTaken conditional branches as second prediction ubtbStats.twoTakenAcceptAlwaysTaken++; - DPRINTF(UBTB, "Accepted alwaysTaken conditional branch %#lx as second prediction\n", secondBr.pc); + return true; } // Rule: 'ret -> ret' is not allowed to avoid multiple RAS reads. @@ -459,10 +522,8 @@ UBTB::check2TakenConditions(FullBTBPrediction& dff, const FullBTBPrediction& s3P return false; } - // (call -> ret is allowed, so no check needed) - - // All conditions passed. - ubtbStats.twoTakenConditionPassed++; + // All conditions passed for pt_2nd = true case. + ubtbStats.twoTakenAcceptOther++; return true; } @@ -789,8 +850,6 @@ UBTB::UBTBStats::UBTBStats(statistics::Group *parent) // 2-taken condition check statistics ADD_STAT(twoTakenConditionChecks, statistics::units::Count::get(), "Total number of 2-taken condition checks performed"), - ADD_STAT(twoTakenConditionPassed, statistics::units::Count::get(), - "Number of times all 2-taken conditions passed"), ADD_STAT(twoTakenFailEmptyPreds, statistics::units::Count::get(), "2-taken rejected due to empty predictions (dff or s3)"), ADD_STAT(twoTakenFailFirstNotTaken, statistics::units::Count::get(), @@ -801,12 +860,27 @@ UBTB::UBTBStats::UBTBStats(statistics::Group *parent) "2-taken rejected due to second branch being indirect"), ADD_STAT(twoTakenFailSecondCond, statistics::units::Count::get(), "2-taken rejected due to second branch being conditional"), - ADD_STAT(twoTakenAcceptAlwaysTaken, statistics::units::Count::get(), - "2-taken accepted alwaysTaken conditional branch as second prediction"), ADD_STAT(twoTakenFailRetRet, statistics::units::Count::get(), "2-taken rejected due to ret->ret sequence"), ADD_STAT(twoTakenFailCallCall, statistics::units::Count::get(), "2-taken rejected due to call->call sequence"), + ADD_STAT(twoTakenAcceptAlwaysTaken, statistics::units::Count::get(), + "2-taken accepted alwaysTaken conditional branch as second prediction"), + ADD_STAT(twoTakenAcceptFallthrough, statistics::units::Count::get(), + "2-taken accepted pt_2nd=false cases (fallthrough execution)"), + ADD_STAT(twoTakenAcceptOther, statistics::units::Count::get(), + "2-taken accepted other cases (e.g., jump)"), + ADD_STAT(twoTakenTrainSuccessfulRatio, statistics::units::Rate< + statistics::units::Count, statistics::units::Count>::get(), + "Ratio of successful 2-taken conditions to total checks"), + + // pt_2nd prediction tracking statistics + ADD_STAT(twoTakenPredTaken, statistics::units::Count::get(), + "Number of pt_2nd=true predictions made (second FB has branch)"), + ADD_STAT(twoTakenPredFallThrough, statistics::units::Count::get(), + "Number of pt_2nd=false predictions made (second FB is fallthrough)"), + ADD_STAT(twoTakenPredRangeFailed, statistics::units::Count::get(), + "Number of pt_2nd=true predictions that failed range validation"), // Training scenario statistics ADD_STAT(trainHitFallThru, statistics::units::Count::get(), @@ -826,14 +900,13 @@ UBTB::UBTBStats::UBTBStats(statistics::Group *parent) ADD_STAT(trainAttempts, statistics::units::Count::get(), "Total number of training attempts (trainCommon function calls)"), ADD_STAT(trainDuplicateEntry, statistics::units::Count::get(), - "Early returns due to duplicate entry already existing in uBTB"), + "Early returns due to duplicate entry already existing in uBTB") + - ADD_STAT(twoTakenTrainSuccessfulRatio, statistics::units::Rate< - statistics::units::Count, statistics::units::Count>::get(), - "Ratio of successful 2-taken conditions to total checks") { // Initialize formula statistics - twoTakenTrainSuccessfulRatio = twoTakenConditionPassed / twoTakenConditionChecks; + twoTakenTrainSuccessfulRatio = (twoTakenAcceptOther + twoTakenAcceptAlwaysTaken + twoTakenAcceptFallthrough) + / twoTakenConditionChecks; } diff --git a/src/cpu/pred/btb/btb_ubtb.hh b/src/cpu/pred/btb/btb_ubtb.hh index a8bed88626..4a68f6b5ab 100644 --- a/src/cpu/pred/btb/btb_ubtb.hh +++ b/src/cpu/pred/btb/btb_ubtb.hh @@ -90,8 +90,9 @@ class UBTB : public TimedBaseBTBPredictor * - tag: tag bits from branch address [23:1] * - tick: timestamp used for MRU (Most Recently Used) replacement policy * - numNTConds: number of not-taken conditional branches before the taken branch - * - valid_2nd: existence of the second branch (for 2-taken support) - * - branch_info_2nd: branch attributes for the second branch (for 2-taken support) + * - valid_2nd: existence of the second fetch block (for 2-taken support) + * - pt_2nd: predict taken for second FB (true = has branch, false = no branch) + * - branch_info_2nd: branch attributes for the second branch (only valid when pt_2nd = true) */ typedef struct TickedUBTBEntry : public BTBEntry { @@ -99,11 +100,13 @@ class UBTB : public TimedBaseBTBPredictor uint64_t tick; // timestamp for MRU replacement int numNTConds; // number of conditional branches before the taken branch bool valid_2nd; // existence of the second branch - BranchInfo branch_info_2nd; // branch attributes for the second branch + bool pt_2nd; // predict taken for second FB (true = has branch, false = no branch) + BranchInfo branch_info_2nd; // branch attributes for the second branch (only valid when pt_2nd = true) - TickedUBTBEntry() : BTBEntry(), uctr(0), tick(0), numNTConds(0), valid_2nd(false), branch_info_2nd() {} + TickedUBTBEntry() : BTBEntry(), uctr(0), tick(0), numNTConds(0), + valid_2nd(false), pt_2nd(false), branch_info_2nd() {} TickedUBTBEntry(const BTBEntry &be, uint64_t tick) : BTBEntry(be), uctr(0), - tick(tick), numNTConds(0), valid_2nd(false), branch_info_2nd() {} + tick(tick), numNTConds(0), valid_2nd(false), pt_2nd(false), branch_info_2nd() {} }TickedUBTBEntry; using UBTBIter = typename std::vector::iterator; @@ -302,6 +305,12 @@ class UBTB : public TimedBaseBTBPredictor */ void fillSecondPrediction(const BranchInfo& branchInfo, Addr bbStart, FullBTBPrediction& prediction); + /** helper method for pt_2nd=false: Construct a fallthrough FullBTBPrediction + * @param secondFBStart The start address for the fallthrough prediction + * @param prediction The prediction object to fill + */ + void fillSecondPredictionFallthrough(Addr secondFBStart, FullBTBPrediction& prediction); + /** helper method for 2-taken: Check if two predictions can form a valid 2-taken sequence * @param dff The first prediction (from DFF buffer) * @param s3Pred The second prediction (current S3 prediction) @@ -341,6 +350,12 @@ class UBTB : public TimedBaseBTBPredictor */ int calculateNumNTConds(FullBTBPrediction& prediction); + /** Determine pt_2nd value based on second FB content + * @param secondPred The second fetch block prediction + * @return true if second FB has branches (pt_2nd=true), false if sequential (pt_2nd=false) + */ + bool shouldSetPtSecond(const FullBTBPrediction& secondPred); + /** The uBTB structure: * - Implemented as a fully associative table * - Each entry can store one branch @@ -403,15 +418,23 @@ class UBTB : public TimedBaseBTBPredictor // 2-taken condition check statistics statistics::Scalar twoTakenConditionChecks; ///< Total number of 2-taken condition checks - statistics::Scalar twoTakenConditionPassed; ///< Number of times all conditions passed statistics::Scalar twoTakenFailEmptyPreds; ///< Rejected due to empty predictions statistics::Scalar twoTakenFailFirstNotTaken; ///< Rejected due to first branch not taken statistics::Scalar twoTakenFailFirstIndirect; ///< Rejected due to first branch being indirect statistics::Scalar twoTakenFailSecondIndirect; ///< Rejected due to second branch being indirect statistics::Scalar twoTakenFailSecondCond; ///< Rejected due to second branch being conditional - statistics::Scalar twoTakenAcceptAlwaysTaken; ///< Accepted alwaysTaken conditional branch as 2nd prediction statistics::Scalar twoTakenFailRetRet; ///< Rejected due to ret->ret sequence statistics::Scalar twoTakenFailCallCall; ///< Rejected due to call->call sequence + statistics::Scalar twoTakenAcceptAlwaysTaken; ///< Accepted alwaysTaken conditional branch as 2nd prediction + statistics::Scalar twoTakenAcceptFallthrough; ///< Accepted pt_2nd=false cases (fallthrough) + statistics::Scalar twoTakenAcceptOther; ///< Accepted other cases (e.g., jump) + // Formula statistics for performance ratios + statistics::Formula twoTakenTrainSuccessfulRatio; ///< Ratio of successful 2-taken conditions to total checks + + // pt_2nd prediction tracking statistics + statistics::Scalar twoTakenPredTaken; ///< pt_2nd = true predictions made + statistics::Scalar twoTakenPredFallThrough; ///< pt_2nd = false predictions made + statistics::Scalar twoTakenPredRangeFailed; ///< pt_2nd = true predictions failed range validation // Training scenario statistics statistics::Scalar trainHitFallThru; ///< S0 hit but S3 fall through @@ -424,8 +447,7 @@ class UBTB : public TimedBaseBTBPredictor statistics::Scalar trainAttempts; ///< Total number of training attempts (trainCommon calls) statistics::Scalar trainDuplicateEntry; ///< Early returns due to duplicate entry already existing - // Formula statistics for performance ratios - statistics::Formula twoTakenTrainSuccessfulRatio; ///< Ratio of successful 2-taken conditions to total checks + UBTBStats(statistics::Group* parent); } ubtbStats; From 976d7fb9ad0b608f99b8aec752507a648f9069c6 Mon Sep 17 00:00:00 2001 From: Xu Boran Date: Fri, 15 Aug 2025 11:59:34 +0800 Subject: [PATCH 20/23] ci: for testing purpose, remember to revert --- .github/workflows/gem5-ideal-btb-perf-2taken.yml | 4 +--- .github/workflows/gem5-ideal-btb-perf.yml | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/gem5-ideal-btb-perf-2taken.yml b/.github/workflows/gem5-ideal-btb-perf-2taken.yml index 48b36b30ba..252c700067 100644 --- a/.github/workflows/gem5-ideal-btb-perf-2taken.yml +++ b/.github/workflows/gem5-ideal-btb-perf-2taken.yml @@ -2,9 +2,7 @@ name: gem5 Ideal BTB Performance Test (2Taken) on: push: - branches: [ xs-dev ] - pull_request: - branches: [ xs-dev ] + branches: [ 2-taken-v8 ] jobs: perf_test: diff --git a/.github/workflows/gem5-ideal-btb-perf.yml b/.github/workflows/gem5-ideal-btb-perf.yml index 3bc64980e0..354412b9e2 100644 --- a/.github/workflows/gem5-ideal-btb-perf.yml +++ b/.github/workflows/gem5-ideal-btb-perf.yml @@ -2,9 +2,7 @@ name: gem5 Ideal BTB Performance Test on: push: - branches: [ xs-dev ] - pull_request: - branches: [ xs-dev ] + branches: [ 2-taken-v8 ] jobs: perf_test: From be558d5aa9150607ee0553e197927e2763dac4d9 Mon Sep 17 00:00:00 2001 From: Xu Boran Date: Mon, 18 Aug 2025 15:36:57 +0800 Subject: [PATCH 21/23] cpu-o3: pred: refactor, add 2taken stats. --- src/cpu/pred/btb/btb_ubtb.cc | 42 ++++++++++++++++++++--------- src/cpu/pred/btb/decoupled_bpred.cc | 40 ++++++++++++++++++++++----- src/cpu/pred/btb/decoupled_bpred.hh | 9 +++++++ 3 files changed, 72 insertions(+), 19 deletions(-) diff --git a/src/cpu/pred/btb/btb_ubtb.cc b/src/cpu/pred/btb/btb_ubtb.cc index 5171363d15..58dc4abec4 100644 --- a/src/cpu/pred/btb/btb_ubtb.cc +++ b/src/cpu/pred/btb/btb_ubtb.cc @@ -155,6 +155,7 @@ UBTB::fillSecondPrediction(const BranchInfo &branchInfo, Addr bbStart, FullBTBPr prediction.predSource = 0; // uBTB is stage 0 // Create BTBEntry from BranchInfo + // alwaysTaken initialized to true here, which is consistent with the 2-taken design BTBEntry entry(branchInfo); // According to 2-taken design rules, the second branch should be either: @@ -408,6 +409,7 @@ UBTB::createSecondPredictionMetaForMBTB(const BranchInfo& branch_info_2nd) mbtbSecondPredMeta = std::make_shared(); // Convert BranchInfo to BTBEntry for MBTB - much simpler! + // alwaysTaken Initialized to True, which is consistent with 2-taken design BTBEntry btb_entry(branch_info_2nd); // Add to hit_entries (standard BTBMeta field) @@ -479,11 +481,23 @@ UBTB::check2TakenConditions(FullBTBPrediction& dff, const FullBTBPrediction& s3P return false; } - // 3. Rule: 'multi-target indirect' as 1st branch is not allowed. - if (firstBr.isIndirect) { - ubtbStats.twoTakenFailFirstIndirect++; - return false; - } + /* + * this rule is created with the following argument: since ubtb + * can't accurately predict a multi target indirect branch, + * there's no use predicting a second branch following it. + + * however! in the rare but not impossible cases where ubtb's first + * prediction has the right target, our second prediction can come in handy. + * When the first target is wrong, and we have a intra flush + * we automatically discard the second prediction, according to the 2 taken design, creating no additional penalty. + + * this is why we skip this rule in this version + */ + // // 3. Rule: 'multi-target indirect' as 1st branch is not allowed. + // if (firstBr.isIndirect) { + // ubtbStats.twoTakenFailFirstIndirect++; + // return false; + // } // 4. Handle pt_2nd = false case: second FB has no branches (sequential execution) if (s3Pred.btbEntries.empty()) { @@ -510,17 +524,19 @@ UBTB::check2TakenConditions(FullBTBPrediction& dff, const FullBTBPrediction& s3P return true; } + // isReturn implies isIndirect, therefore this rule is unnecessary // Rule: 'ret -> ret' is not allowed to avoid multiple RAS reads. - if (firstBr.isReturn && secondBr.isReturn) { - ubtbStats.twoTakenFailRetRet++; - return false; - } + // if (firstBr.isReturn && secondBr.isReturn) { + // ubtbStats.twoTakenFailRetRet++; + // return false; + // } + // we skip this rule for now // Rule: 'call -> call' is not allowed to avoid multiple RAS writes. - if (firstBr.isCall && secondBr.isCall) { - ubtbStats.twoTakenFailCallCall++; - return false; - } + // if (firstBr.isCall && secondBr.isCall) { + // ubtbStats.twoTakenFailCallCall++; + // return false; + // } // All conditions passed for pt_2nd = true case. ubtbStats.twoTakenAcceptOther++; diff --git a/src/cpu/pred/btb/decoupled_bpred.cc b/src/cpu/pred/btb/decoupled_bpred.cc index 455507e180..afddf621ea 100644 --- a/src/cpu/pred/btb/decoupled_bpred.cc +++ b/src/cpu/pred/btb/decoupled_bpred.cc @@ -533,6 +533,11 @@ DecoupledBPUWithBTB::DBPBTBStats::DBPBTBStats(statistics::Group* parent, unsigne ADD_STAT(btbMiss, statistics::units::Count::get(), "btb misses (in predict block)"), ADD_STAT(btbEntriesWithDifferentStart, statistics::units::Count::get(), "number of btb entries with different start PC"), ADD_STAT(btbEntriesWithOnlyOneJump, statistics::units::Count::get(), "number of btb entries with different start PC starting with a jump"), + ADD_STAT(twoTakenHit, statistics::units::Count::get(), "2-taken prediction hits"), + ADD_STAT(twoTakenMiss, statistics::units::Count::get(), "2-taken prediction misses"), + ADD_STAT(twoTakenDiscardedByOverride, statistics::units::Count::get(), "2-taken predictions discarded due to override"), + ADD_STAT(twoTakenRemainsAfterOverride, statistics::units::Count::get(), "2-taken predictions remaining after override"), + ADD_STAT(totalPredCount, statistics::units::Count::get(), "total number of predictions made"), ADD_STAT(predFalseHit, statistics::units::Count::get(), "false hit detected at pred"), ADD_STAT(commitFalseHit, statistics::units::Count::get(), "false hit detected at commit"), ADD_STAT(predTwoTakenRatio, statistics::units::Rate< @@ -540,19 +545,28 @@ DecoupledBPUWithBTB::DBPBTBStats::DBPBTBStats(statistics::Group* parent, unsigne "Ratio of 2-taken BPU cycles to total BPU cycles"), ADD_STAT(commitSecondPredRatio, statistics::units::Rate< statistics::units::Count, statistics::units::Count>::get(), - "Ratio of committed second predictions(in a 2 taken pair) to total FSQ entries") + "Ratio of committed second predictions(in a 2 taken pair) to total FSQ entries"), + ADD_STAT(twoTakenHitRatio, statistics::units::Rate< + statistics::units::Count, statistics::units::Count>::get(), + "Ratio of 2-taken hits to total predictions"), + ADD_STAT(twoTakenRemainsRatio, statistics::units::Rate< + statistics::units::Count, statistics::units::Count>::get(), + "Ratio of 2-taken predictions remaining after override to total predictions") { predsOfEachStage.init(numStages); commitPredsFromEachStage.init(numStages+1); - commitOverrideBubbleNum = commitPredsFromEachStage[1] + 2 * commitPredsFromEachStage[2] ; + // TODO: count the third stage + commitOverrideBubbleNum = commitPredsFromEachStage[1] + 2 * commitPredsFromEachStage[2]; commitOverrideCount = commitPredsFromEachStage[1] + commitPredsFromEachStage[2]; fsqEntryDist.init(0, fsqSize, 20).flags(statistics::total); commitFsqEntryHasInsts.init(0, maxInstsNum >> 1, 1); commitFsqEntryFetchedInsts.init(0, maxInstsNum >> 1, 1); // Initialize formula statistics - predTwoTakenRatio = predProduce2Taken / (predProduce2Taken + predProduce1Taken); + predTwoTakenRatio = predProduce2Taken / totalPredCount; commitSecondPredRatio = secondPredCommitted / fsqEntryCommitted; + twoTakenHitRatio = twoTakenHit / totalPredCount; + twoTakenRemainsRatio = twoTakenRemainsAfterOverride / totalPredCount; } DecoupledBPUWithBTB::BpTrace::BpTrace(uint64_t fsqId, FetchStream &stream, const DynInstPtr &inst, bool mispred) @@ -589,6 +603,8 @@ DecoupledBPUWithBTB::tick() // 1. Request prediction, finalize it, and get ready to enqueue. // This all happens if we're idle and not blocked. if (bpuState == BpuState::IDLE && !streamQueueFull()) { + dbpBtbStats.totalPredCount++; + requestNewPrediction(); // The training logic runs here, based on the previous cycle's DFF state. @@ -610,6 +626,10 @@ DecoupledBPUWithBTB::tick() // Check if the second prediction is still valid after overrides. validateSecondFBPrediction(); + if (hasSecondPrediction) { + assert(finalPred.getTarget(predictWidth) == secondPrediction.bbStart); + } + // If we still have a valid second FB, pad ABTB ahead-pipeline now. if (hasSecondPrediction && abtb && abtb->aheadPipelinedStages > 0) { abtb->preloadBlock(secondPrediction.bbStart); @@ -689,9 +709,11 @@ DecoupledBPUWithBTB::requestNewPrediction() // Reset prediction flags hasSecondPrediction = false; ubtbHitIndex = -1; - secondPrediction.btbEntries.clear(); secondPrediction.predSource = 0; secondPrediction.overrideReason = OverrideReason::NO_OVERRIDE; + secondPrediction.condTakens.clear(); + secondPrediction.indirectTargets.clear(); + secondPrediction.btbEntries.clear(); // Query each predictor component with current PC and history for (int i = 0; i < numComponents; i++) { @@ -711,8 +733,10 @@ DecoupledBPUWithBTB::requestNewPrediction() "Second prediction available but no first prediction found"); hasSecondPrediction = true; + dbpBtbStats.twoTakenHit++; } else { hasSecondPrediction = false; + dbpBtbStats.twoTakenMiss++; } } else { // Regular 1-taken prediction for uBTB @@ -2229,12 +2253,16 @@ void DecoupledBPUWithBTB::validateSecondFBPrediction() // The second prediction is only valid if the first prediction from uBTB1 // was not overridden by a later-stage predictor. // We check if the final prediction's source is stage 0. + // note that hasSecondPrediction implys that ubtb hit, which means + // predSource == 0 <==> predSource is ubtb if (finalPred.predSource != 0) { DPRINTF(DecoupleBP, "uBTB1 prediction was overridden (finalPred source is stage %d), " "invalidating second FB prediction.\n", finalPred.predSource); hasSecondPrediction = false; - // We're clearing secondPrediction just to be tidy. - secondPrediction.btbEntries.clear(); + dbpBtbStats.twoTakenDiscardedByOverride++; + } else { + // Second prediction remains valid after override check + dbpBtbStats.twoTakenRemainsAfterOverride++; } } diff --git a/src/cpu/pred/btb/decoupled_bpred.hh b/src/cpu/pred/btb/decoupled_bpred.hh index 22d6ab15de..a3430e9b98 100644 --- a/src/cpu/pred/btb/decoupled_bpred.hh +++ b/src/cpu/pred/btb/decoupled_bpred.hh @@ -397,12 +397,21 @@ class DecoupledBPUWithBTB : public BPredUnit statistics::Scalar btbEntriesWithDifferentStart; statistics::Scalar btbEntriesWithOnlyOneJump; + // 2-taken prediction accuracy statistics + statistics::Scalar twoTakenHit; ///< 2-taken prediction hits + statistics::Scalar twoTakenMiss; ///< 2-taken prediction misses + statistics::Scalar twoTakenDiscardedByOverride; ///< 2-taken predictions discarded due to override + statistics::Scalar twoTakenRemainsAfterOverride; ///< 2-taken predictions remaining after override + + statistics::Scalar totalPredCount; ///< Total number of predictions made statistics::Scalar predFalseHit; statistics::Scalar commitFalseHit; // Formula statistics for performance ratios statistics::Formula predTwoTakenRatio; ///< Ratio of 2-taken predictions to total predictions statistics::Formula commitSecondPredRatio; ///< Ratio of committed second predictions to total FSQ entries + statistics::Formula twoTakenHitRatio; ///< Ratio of 2-taken hits to total predictions + statistics::Formula twoTakenRemainsRatio; ///< Ratio of 2-taken predictions remaining after override DBPBTBStats(statistics::Group* parent, unsigned numStages, unsigned fsqSize, unsigned maxInstsNum); } dbpBtbStats; From a032b26cde70007114a825c3ea4a742bf4ebc6de Mon Sep 17 00:00:00 2001 From: Xu Boran Date: Mon, 18 Aug 2025 16:23:54 +0800 Subject: [PATCH 22/23] cpu-o3: pred: reduce the frequency of clearing ubtb 2ndBrInfo --- src/cpu/pred/btb/btb_ubtb.cc | 18 +++++++----------- src/cpu/pred/btb/decoupled_bpred.cc | 9 +++++---- src/cpu/pred/btb/decoupled_bpred.hh | 12 ++++++------ 3 files changed, 18 insertions(+), 21 deletions(-) diff --git a/src/cpu/pred/btb/btb_ubtb.cc b/src/cpu/pred/btb/btb_ubtb.cc index 58dc4abec4..834c179efd 100644 --- a/src/cpu/pred/btb/btb_ubtb.cc +++ b/src/cpu/pred/btb/btb_ubtb.cc @@ -687,20 +687,16 @@ void UBTB::recoverHist(const boost::dynamic_bitset<> &history, const FetchStream &entry, int shamt, bool cond_taken) { - DPRINTF(UBTB, "uBTB squash recovery: clearing all entries (had %lu valid entries)\n", - std::count_if(ubtb.begin(), ubtb.end(), [](const TickedUBTBEntry& e) { return e.valid; })); - - // Clear all uBTB entries by marking them as invalid - // This removes pollution from wrong-path predictions - for (auto &entry : ubtb) { - //entry.valid = false; - entry.valid_2nd = false; // Also clear second branch validity - } - // we don't explicitly clear entry.tick, because tick will be updated when the entry is filled again + // Clear all uBTB 2nd branch info by marking them as invalid + // This feature removes "persistently wrong" second preds + if (entry.isSecondFBPred){ + for (auto &entry : ubtb) { + entry.valid_2nd = false; // clear second branch validity + } + } - DPRINTF(UBTB, "uBTB squash recovery complete: all entries cleared\n"); } diff --git a/src/cpu/pred/btb/decoupled_bpred.cc b/src/cpu/pred/btb/decoupled_bpred.cc index afddf621ea..a4151e22fe 100644 --- a/src/cpu/pred/btb/decoupled_bpred.cc +++ b/src/cpu/pred/btb/decoupled_bpred.cc @@ -484,11 +484,11 @@ DecoupledBPUWithBTB::DBPBTBStats::DBPBTBStats(statistics::Group* parent, unsigne ADD_STAT(condNum, statistics::units::Count::get(), "the number of cond branches"), ADD_STAT(uncondNum, statistics::units::Count::get(), "the number of uncond branches"), ADD_STAT(returnNum, statistics::units::Count::get(), "the number of return branches"), - ADD_STAT(otherNum, statistics::units::Count::get(), "the number of other branches"), + ADD_STAT(indirectNum, statistics::units::Count::get(), "the number of indirect branches(including return)"), ADD_STAT(condMiss, statistics::units::Count::get(), "the number of cond branch misses"), ADD_STAT(uncondMiss, statistics::units::Count::get(), "the number of uncond branch misses"), ADD_STAT(returnMiss, statistics::units::Count::get(), "the number of return branch misses"), - ADD_STAT(otherMiss, statistics::units::Count::get(), "the number of other branch misses"), + ADD_STAT(IndirectMiss, statistics::units::Count::get(), "the number of indirect branch misses(including return miss)"), ADD_STAT(staticBranchNum, statistics::units::Count::get(), "the number of all (different) static branches"), ADD_STAT(staticBranchNumEverTaken, statistics::units::Count::get(), "the number of all (different) static branches that are once taken"), ADD_STAT(predsOfEachStage, statistics::units::Count::get(), "the number of preds of each stage that account for final pred"), @@ -1447,8 +1447,9 @@ DecoupledBPUWithBTB::commitBranch(const DynInstPtr &inst, bool mispred) } if (inst->isReturn()) { addCfi(RETURN, mispred); - } else if (inst->isIndirectCtrl()) { - addCfi(OTHER, mispred); + } + if (inst->isIndirectCtrl()) { + addCfi(INDIRECT, mispred); } // ---------- Find corresponding fetch stream entry ---------- diff --git a/src/cpu/pred/btb/decoupled_bpred.hh b/src/cpu/pred/btb/decoupled_bpred.hh index a3430e9b98..6a688d8cd8 100644 --- a/src/cpu/pred/btb/decoupled_bpred.hh +++ b/src/cpu/pred/btb/decoupled_bpred.hh @@ -341,13 +341,13 @@ class DecoupledBPUWithBTB : public BPredUnit statistics::Scalar condNum; ///< Number of conditional branches statistics::Scalar uncondNum; ///< Number of unconditional branches statistics::Scalar returnNum; ///< Number of return instructions - statistics::Scalar otherNum; ///< Number of other control instructions + statistics::Scalar indirectNum; ///< Number of other control instructions // Misprediction statistics statistics::Scalar condMiss; ///< Conditional branch mispredictions statistics::Scalar uncondMiss; ///< Unconditional branch mispredictions statistics::Scalar returnMiss; ///< Return mispredictions - statistics::Scalar otherMiss; ///< Other control mispredictions + statistics::Scalar IndirectMiss; ///< Other control mispredictions // Branch coverage statistics statistics::Scalar staticBranchNum; ///< Total static branches seen @@ -880,7 +880,7 @@ class DecoupledBPUWithBTB : public BPredUnit COND, ///< Conditional branch UNCOND, ///< Unconditional branch RETURN, ///< Return instruction - OTHER ///< Other control flow instruction + INDIRECT ///< Other control flow instruction }; void addCfi(CfiType type, bool mispred) { @@ -900,10 +900,10 @@ class DecoupledBPUWithBTB : public BPredUnit if (mispred) dbpBtbStats.returnMiss++; break; - case OTHER: - dbpBtbStats.otherNum++; + case INDIRECT: + dbpBtbStats.indirectNum++; if (mispred) - dbpBtbStats.otherMiss++; + dbpBtbStats.IndirectMiss++; break; } DPRINTF(DBPBTBStats, "Miss type: %d\n", type); From 1564af59872af3163d34992d8a2a3939b4311491 Mon Sep 17 00:00:00 2001 From: Xu Boran Date: Thu, 21 Aug 2025 12:03:43 +0800 Subject: [PATCH 23/23] cpu-o3: pred: update 2 taken doc Change-Id: Ifa2218af9a88b1200606c909383d9ccd2a55f809 --- ...36\347\216\260\346\214\207\345\215\227.md" | 745 ++++++++++++++++++ 1 file changed, 745 insertions(+) create mode 100644 "docs/Gem5_Docs/frontend/2-Taken_\344\273\243\347\240\201\345\256\236\347\216\260\346\214\207\345\215\227.md" diff --git "a/docs/Gem5_Docs/frontend/2-Taken_\344\273\243\347\240\201\345\256\236\347\216\260\346\214\207\345\215\227.md" "b/docs/Gem5_Docs/frontend/2-Taken_\344\273\243\347\240\201\345\256\236\347\216\260\346\214\207\345\215\227.md" new file mode 100644 index 0000000000..360b226510 --- /dev/null +++ "b/docs/Gem5_Docs/frontend/2-Taken_\344\273\243\347\240\201\345\256\236\347\216\260\346\214\207\345\215\227.md" @@ -0,0 +1,745 @@ +# 2-Taken 分支预测器代码实现指南 + +## 目录 +1. [核心数据结构](#核心数据结构) +2. [预测流程实现](#预测流程实现) +3. [训练逻辑实现](#训练逻辑实现) +4. [流水线集成](#流水线集成) +5. [高级特性](#高级特性) +6. [Bug修复](#bug修复) +7. [代码变更清单](#代码变更清单) + +--- + +## 核心数据结构 + +### 扩展的uBTB表项结构 + +**文件**: `src/cpu/pred/btb/btb_ubtb.hh` + +```cpp +typedef struct TickedUBTBEntry : public BTBEntry { + unsigned uctr; // 2位饱和计数器,用于替换策略 + uint64_t tick; // MRU替换的时间戳 + int numNTConds; // taken分支前的条件分支数量 + bool valid_2nd; // 第二个取指块是否存在 + bool pt_2nd; // 第二个FB是否预测taken(true=有分支,false=顺序执行) + BranchInfo branch_info_2nd; // 第二个分支的属性信息(仅当pt_2nd=true时有效) + + TickedUBTBEntry() : BTBEntry(), uctr(0), tick(0), numNTConds(0), + valid_2nd(false), pt_2nd(false), branch_info_2nd() {} +} TickedUBTBEntry; +``` + +**关键点**: +- `valid_2nd`: 控制是否有第二个预测 +- `pt_2nd`: 区分第二个FB是否包含分支(true)或仅为顺序执行(false) +- `branch_info_2nd`: 仅在`pt_2nd=true`时使用 + +### DFF缓冲区用于跨周期训练 + +**文件**: `src/cpu/pred/btb/decoupled_bpred.hh` + +```cpp +struct PredictionDFF { + bool valid{false}; + FullBTBPrediction prevS3Pred; // 前一周期的S3最终预测结果 + int prevUbtbHitIndex{-1}; // 前一周期的命中索引,用于训练 + + void reset() { + valid = false; + prevUbtbHitIndex = -1; + } + + void storePrediction(const FullBTBPrediction& s3_pred, int hit_index) { + prevS3Pred = s3_pred; + prevUbtbHitIndex = hit_index; + valid = true; + } +}; +``` + +### BPU状态机 + +```cpp +enum class BpuState { + IDLE, // 等待开始新预测 + PREDS_READY, // 1-2个预测已完成,等待入队 + WAITING_FOR_SECOND_ENQ // 第一个预测已入队,第二个等待FSQ空间 +}; +``` + +--- + +## 预测流程实现 + +### 核心预测函数:putPCHistory2Taken + +**文件**: `src/cpu/pred/btb/btb_ubtb.cc` + +```cpp +std::pair UBTB::putPCHistory2Taken( + Addr startAddr, + const boost::dynamic_bitset<> &history, + std::vector &stagePreds, + FullBTBPrediction &secondPrediction) +{ + // 清理之前的MBTB meta + mbtbSecondPredMeta = nullptr; + + // 执行标准uBTB查找 + int hit_index = lookup(startAddr); + bool hit_found = (hit_index != -1); + + if (hit_found) { + auto& entry = entries[hit_index]; + // 更新时间戳和历史 + updateTimestampAndHistory(hit_index, history, stagePreds); + + // 检查是否有第二个预测 + if (entry.valid_2nd) { + if (entry.pt_2nd) { + // 情况1:第二个FB有taken分支 + fillSecondPrediction(secondPrediction, entry.branch_info_2nd); + + // 范围检查 + if (isSecondPredictionInRange(stagePreds[0], secondPrediction)) { + createSecondPredictionMetaForMBTB(entry.branch_info_2nd); + ubtbStats.twotaken_pt_true++; + return {hit_index, true}; + } else { + ubtbStats.twotaken_range_check_failed++; + } + } else { + // 情况2:第二个FB无分支,顺序执行 + Addr secondFBStart = stagePreds[0].getTarget(predictWidth); + fillSecondPredictionFallthrough(secondPrediction, secondFBStart); + + // 为MBTB创建空meta保持一致性 + mbtbSecondPredMeta = std::make_shared(); + ubtbStats.twotaken_pt_false++; + return {hit_index, true}; + } + } + } else { + // Miss处理:创建第一个预测但标记为miss + createFirstPredictionOnMiss(startAddr, stagePreds); + } + + return {hit_index, false}; +} +``` + +### 第二个预测的构造 + +**情况1:pt_2nd=true(有分支)** +```cpp +void UBTB::fillSecondPrediction(FullBTBPrediction& secondPred, + const BranchInfo& branch_info_2nd) { + secondPred.bbStart = /* 第一个预测的目标 */; + secondPred.predSource = 0; // uBTB预测 + + // 从BranchInfo构造BTBEntry + BTBEntry btbEntry(branch_info_2nd); + secondPred.btbEntries.push_back(btbEntry); + + DPRINTF(UBTB, "构造第二个预测(有分支): PC=%#lx, target=%#lx\n", + btbEntry.pc, btbEntry.target); +} +``` + +**情况2:pt_2nd=false(顺序执行)** +```cpp +void UBTB::fillSecondPredictionFallthrough(FullBTBPrediction& secondPred, + Addr secondFBStart) { + secondPred.bbStart = secondFBStart; + secondPred.predSource = 0; + secondPred.btbEntries.clear(); // 无分支 + + DPRINTF(UBTB, "构造第二个预测(顺序): bbStart=%#lx\n", secondFBStart); +} +``` + +### BPU中的预测请求 + +**文件**: `src/cpu/pred/btb/decoupled_bpred.cc` + +```cpp +void DecoupledBPUWithBTB::requestNewPrediction() { + // 初始化状态 + hasSecondPrediction = false; + ubtbHitIndex = -1; + + // 对各个组件进行预测 + for (int i = 0; i < numComponents; i++) { + if (components[i] == ubtb) { + // uBTB使用2-taken接口 + auto [hit_index, has_second] = ubtb->putPCHistory2Taken( + s0PC, s0History, predsOfEachStage, secondPrediction); + + ubtbHitIndex = hit_index; + hasSecondPrediction = has_second; + + if (has_second) { + DPRINTF(DecoupleBP, "获得第二个预测: target=%#lx\n", + secondPrediction.bbStart); + } + } else { + // 其他组件使用标准接口 + components[i]->putPCHistory(s0PC, s0History, predsOfEachStage); + } + } + + // ABTB兼容性:如果有第二个预测,需要预加载维护队列 + if (hasSecondPrediction && abtb && abtb->getAheadPipelinedStages() > 0) { + abtb->preloadBlock(secondPrediction.bbStart); + DPRINTF(DecoupleBP, "为ABTB预加载第二个块: %#lx\n", + secondPrediction.bbStart); + } +} +``` + +--- + +## 训练逻辑实现 + +### 2-taken条件检查 + +**文件**: `src/cpu/pred/btb/btb_ubtb.cc` + +```cpp +bool UBTB::check2TakenConditions(FullBTBPrediction& dff, + const FullBTBPrediction& s3Pred) { + assert(dff.getTarget(predictWidth) == s3Pred.bbStart); + ubtbStats.twoTakenConditionChecks++; + + // 1. 第一个预测必须至少有一个分支 + if (dff.btbEntries.empty()) { + ubtbStats.twoTakenFailEmptyPreds++; + return false; + } + + auto firstBr = dff.getTakenEntry(); + + // 2. 第一个分支必须taken才能形成2-taken序列 + if (!dff.isTaken()) { + ubtbStats.twoTakenFailFirstNotTaken++; + return false; + } + + // 3. 第一个分支不能是多目标间接跳转 + if (firstBr.isIndirect) { + ubtbStats.twoTakenFailFirstIndirect++; + return false; + } + + // 4. 处理pt_2nd=false情况:第二个FB无分支(顺序执行) + if (s3Pred.btbEntries.empty()) { + ubtbStats.twoTakenAcceptFallthrough++; + return true; // pt_2nd=false情况总是允许 + } + + // 5. pt_2nd=true情况:两个FB都有分支 - 应用兼容性规则 + auto& secondBr = s3Pred.btbEntries[0]; + + // 第二个分支不能是多目标间接跳转 + if (secondBr.isIndirect) { + ubtbStats.twoTakenFailSecondIndirect++; + return false; + } + + // 第二个分支不能是条件分支,除非是alwaysTaken + if (secondBr.isCond && !secondBr.alwaysTaken) { + ubtbStats.twoTakenFailSecondCond++; + return false; + } + + // 不允许ret->ret(避免多次RAS读取) + if (firstBr.isReturn && secondBr.isReturn) { + ubtbStats.twoTakenFailRetRet++; + return false; + } + + // 不允许call->call(避免多次RAS写入) + if (firstBr.isCall && secondBr.isCall) { + ubtbStats.twoTakenFailCallCall++; + return false; + } + + ubtbStats.twoTakenConditionPassed++; + return true; +} +``` + +### 统一训练函数 + +```cpp +void UBTB::trainCommon(int entry_index, FullBTBPrediction& pred, + FullBTBPrediction* secondPred) { + if (entry_index == -1) { + // Miss情况:查找替换受害者 + entry_index = findVictimEntry(pred.bbStart); + DPRINTF(UBTB, "Miss训练,使用受害者索引: %d\n", entry_index); + + // 安装新表项 + replaceEntry(entry_index, pred); + + // 如果有第二个预测,添加到表项 + if (secondPred != nullptr) { + addSecondPredictionToEntry(entry_index, secondPred); + } + } else { + // Hit情况:更新现有表项 + auto& entry = entries[entry_index]; + + if (entry.match(pred)) { + // 命中且匹配:更新UCtr,可能添加第二个预测 + entry.uctr = std::min(3U, entry.uctr + 1); + updateMRUPosition(entry_index); + + if (secondPred != nullptr && !entry.valid_2nd) { + addSecondPredictionToEntry(entry_index, secondPred); + DPRINTF(UBTB, "为现有表项添加第二个预测\n"); + } + } else { + // 命中但不匹配:替换表项 + if (entry.uctr > 0) { + entry.uctr--; + DPRINTF(UBTB, "UCtr递减到: %d\n", entry.uctr); + } else { + replaceEntry(entry_index, pred); + if (secondPred != nullptr) { + addSecondPredictionToEntry(entry_index, secondPred); + } + } + } + } +} +``` + +### 2-taken训练主函数 + +```cpp +void UBTB::train2Taken(FullBTBPrediction &dff_pred, + FullBTBPrediction &s3_pred, int hit_index) { + // 验证连续FB条件 + if (dff_pred.getTarget(predictWidth) != s3_pred.bbStart) { + // 回退到1-taken训练 + trainCommon(hit_index, dff_pred, nullptr); + DPRINTF(UBTB, "FB不连续,回退到1-taken训练\n"); + return; + } + + // 检查2-taken条件 + if (!check2TakenConditions(dff_pred, s3_pred)) { + // 回退到1-taken训练 + trainCommon(hit_index, dff_pred, nullptr); + DPRINTF(UBTB, "2-taken条件不满足,回退到1-taken训练\n"); + return; + } + + // 作为2-taken训练:传递s3_pred作为第二个预测 + trainCommon(hit_index, dff_pred, &s3_pred); + DPRINTF(UBTB, "2-taken训练成功\n"); +} +``` + +### 添加第二个预测到表项 + +```cpp +void UBTB::addSecondPredictionToEntry(int entryIndex, FullBTBPrediction* secondPred) { + assert(entryIndex >= 0 && entryIndex < numEntries); + assert(secondPred != nullptr); + + auto& entry = entries[entryIndex]; + + // 根据第二个FB是否有分支确定pt_2nd + bool pt_2nd_value = shouldSetPtSecond(*secondPred); + + if (pt_2nd_value) { + // 情况1:第二个FB有taken分支 + if (!secondPred->btbEntries.empty()) { + auto& btbEntry = secondPred->btbEntries[0]; + entry.branch_info_2nd = BranchInfo(btbEntry); + entry.valid_2nd = true; + entry.pt_2nd = true; + + ubtbStats.twotaken_pt_true_trained++; + DPRINTF(UBTB, "添加第二个预测(有分支): PC=%#lx\n", btbEntry.pc); + } + } else { + // 情况2:第二个FB无分支(仅顺序执行) + entry.valid_2nd = true; + entry.pt_2nd = false; + // branch_info_2nd在此情况下无关 + + ubtbStats.twotaken_pt_false_trained++; + DPRINTF(UBTB, "添加第二个预测(顺序): bbStart=%#lx\n", + secondPred->bbStart); + } +} +``` + +--- + +## 流水线集成 + +### 增强的tick()函数 + +**文件**: `src/cpu/pred/btb/decoupled_bpred.cc` + +```cpp +void DecoupledBPUWithBTB::tick() { + DPRINTF(Override, "DecoupledBPUWithBTB::tick()\n"); + + // 1. 请求预测,完成训练,准备入队 + if (bpuState == BpuState::IDLE && !streamQueueFull()) { + requestNewPrediction(); + + // 训练逻辑基于前一周期的DFF状态 + trainUbtbFor2Taken(); + numOverrideBubbles = generateFinalPredAndCreateBubbles(); + + // 检查第二个预测在override后是否仍然有效 + validateSecondFBPrediction(); + + // 为下一周期更新DFF + predDFF.storePrediction(finalPred, ubtbHitIndex); + + bpuState = BpuState::PREDS_READY; + + // 清理预测器输出 + for (int i = 0; i < numStages; i++) { + predsOfEachStage[i].btbEntries.clear(); + } + } + + // 2. 入队预测(如果没有气泡) + + // 尝试入队第一个(或唯一的)预测 + if (bpuState == BpuState::PREDS_READY && validateFSQEnqueue()) { + makeNewPrediction(true, false); // 第一个预测 + + if (hasSecondPrediction) { + // 有第二个预测需要处理 + finalPred = secondPrediction; + hasSecondPrediction = false; + bpuState = BpuState::WAITING_FOR_SECOND_ENQ; + } else { + // 只有一个预测,回到空闲状态 + bpuState = BpuState::IDLE; + } + } + + // 如果在等待第二个预测入队,尝试入队 + if (bpuState == BpuState::WAITING_FOR_SECOND_ENQ && validateFSQEnqueue()) { + makeNewPrediction(true, true); // 第二个预测 + bpuState = BpuState::IDLE; + } + + // 递减override气泡计数 + if (numOverrideBubbles > 0) { + numOverrideBubbles--; + dbpBtbStats.overrideBubbleNum++; + } +} +``` + +### 训练协调 + +```cpp +void DecoupledBPUWithBTB::trainUbtbFor2Taken() { + auto& s3_pred = predsOfEachStage[numStages-1]; + + if (enable2Taken) { + if (predDFF.valid) { + // 2-taken训练:使用DFF中的前一周期预测 + ubtb->train2Taken(predDFF.prevS3Pred, s3_pred, predDFF.prevUbtbHitIndex); + DPRINTF(DecoupleBP, "执行2-taken训练\n"); + } else { + DPRINTF(DecoupleBP, "DFF无效,跳过2-taken训练\n"); + } + } else { + // 1-taken训练 + ubtb->train1Taken(s3_pred); + DPRINTF(DecoupleBP, "执行1-taken训练\n"); + } +} +``` + +### 第二个预测验证 + +```cpp +void DecoupledBPUWithBTB::validateSecondFBPrediction() { + if (!hasSecondPrediction) { + return; + } + + // 仅当第一个预测来自uBTB(阶段0)且未被覆盖时,第二个预测才有效 + if (finalPred.predSource != 0) { + DPRINTF(UBTB, "uBTB1预测被覆盖(finalPred来源是阶段%d)," + "使第二个FB预测无效\n", finalPred.predSource); + hasSecondPrediction = false; + secondPrediction.btbEntries.clear(); + } +} +``` + +--- + +## 高级特性 + +### AlwaysTaken条件分支支持 + +**问题**:第二个预测位置的alwaysTaken条件分支在变为双向时性能下降。 + +**解决方案**:为第二个预测选择性更新MBTB + +**实现**: + +1. **Meta存储**(在uBTB中): +```cpp +// src/cpu/pred/btb/btb_ubtb.cc +void UBTB::createSecondPredictionMetaForMBTB(const BranchInfo& branch_info_2nd) { + // 为MBTB创建标准BTBMeta + mbtbSecondPredMeta = std::make_shared(); + + // 将BranchInfo转换为BTBEntry + BTBEntry btb_entry(branch_info_2nd); + mbtbSecondPredMeta->hit_entries.push_back(btb_entry); + + DPRINTF(UBTB, "为第二个预测创建MBTB meta: PC=%#lx\n", btb_entry.pc); +} + +// 公共检索函数 +std::shared_ptr UBTB::getMBTBSecondPredictionMeta() const { + return mbtbSecondPredMeta; +} +``` + +2. **Meta集成**(在DecoupledBPU中): +```cpp +// src/cpu/pred/btb/decoupled_bpred.cc +FetchStream DecoupledBPUWithBTB::createFetchStreamEntry(bool is_second_pred) { + // ... 现有逻辑 ... + + // 保存预测器metadata + for (int i = 0; i < numComponents; i++) { + if (is_second_pred) { + if (components[i] == btb) { + // 对于MBTB,获取uBTB在getTwoTakenPrediction期间创建的meta + entry.predMetas[i] = ubtb->getMBTBSecondPredictionMeta(); + } else { + entry.predMetas[i] = components[i]->getSecondPredictionMeta(); + } + } else { + entry.predMetas[i] = components[i]->getPredictionMeta(); + } + } + + return entry; +} +``` + +3. **选择性更新**: +```cpp +void DecoupledBPUWithBTB::updateSecondPredictionComponents(FetchStream &stream) { + // RAS始终需要更新以保持正确的状态跟踪 + ras->update(stream); + + // MBTB需要更新以管理alwaysTaken标志 + stream.setUpdateInstEndPC(predictWidth); + btb->update(stream); + + DPRINTF(DecoupleBP, "为第二个预测更新MBTB,PC=%#lx\n", stream.startPC); +} + +// 在主更新函数中 +void DecoupledBPUWithBTB::update(/* 参数 */) { + // ... + if (!stream.isSecondFBPred) { + updatePredictorComponents(stream); + } else { + // 对第二个预测选择性更新特定组件 + updateSecondPredictionComponents(stream); + } + // ... +} +``` + +### pt_2nd支持(顺序执行增强) + +**扩展2-taken从连续taken分支到包含顺序执行情况** + +**关键实现**: + +1. **条件简化**: +```cpp +bool UBTB::check2TakenConditions(FullBTBPrediction& dff, + const FullBTBPrediction& s3Pred) { + // ... 现有检查 ... + + // 4. 处理pt_2nd=false情况:第二个FB无分支 + if (s3Pred.btbEntries.empty()) { + ubtbStats.twoTakenAcceptFallthrough++; + return true; // pt_2nd=false情况总是允许 + } + + // ... pt_2nd=true的其他规则 ... +} +``` + +2. **动态pt_2nd设置**: +```cpp +bool UBTB::shouldSetPtSecond(const FullBTBPrediction& secondPred) { + // pt_2nd=true如果第二个FB有任何分支 + // pt_2nd=false如果第二个FB无分支(纯顺序执行) + return !secondPred.btbEntries.empty(); +} +``` + +--- + +## Bug修复 + +### ABTB兼容性修复 + +**问题**:ABTB期望每个连续取指块调用一次`putPCHistory()`。2-taken返回块A和B时,ABTB看到A→C序列,破坏ahead-pipeline队列。 + +**解决方案**:队列填充策略 + +**实现**: + +1. **新ABTB API**: +```cpp +// src/cpu/pred/btb/btb.cc +void DefaultBTB::preloadBlock(Addr pc) { + // 仅执行数据数组读取+队列推送,无标签比较 + if (aheadPipelinedStages > 0) { + // 克隆lookupSingleBlock()的前半部分到push操作 + auto entries = lookupDataArray(pc); + aheadReadBtbEntries.push(entries); + + DPRINTF(BTB, "预加载块到ahead队列: PC=%#lx\n", pc); + // 立即返回,不做标签比较 + } +} +``` + +2. **集成到预测流程**: +```cpp +// 在requestNewPrediction()中,在uBTB 2-taken逻辑之后 +if (hasSecondPrediction && abtb && abtb->getAheadPipelinedStages() > 0) { + abtb->preloadBlock(secondPrediction.bbStart); // 推送B,无比较 +} +``` + +### 元数据检查点 + +我们的2nd FB在提交后不需要发到BPU进行训练,因为高级预测器没有与它对应的meta信息,然而, +我们的2nd FB在发生重定向后恢复时需要触发bpu内部状态的恢复,这里只要求meta里存恢复相关的信息,比如TAGE的折叠历史,换句话说,2nd FB的meta里不存训练相关的信息,但是存恢复相关的信息 + +**为所有需要历史状态的组件实现`getSecondPredictionMeta()`**: + +**TAGE**: +```cpp +// src/cpu/pred/btb/btb_tage.cc +std::shared_ptr BTBTAGE::getSecondPredictionMeta() { + auto second_meta = std::make_shared(); + second_meta->tagFoldedHist = tagFoldedHist; + second_meta->altTagFoldedHist = altTagFoldedHist; + second_meta->indexFoldedHist = indexFoldedHist; + return second_meta; +} +``` + +**RAS**: +```cpp +// src/cpu/pred/btb/ras.cc +std::shared_ptr BTBRAS::getSecondPredictionMeta() { + auto second_meta = std::make_shared(); + second_meta->ssp = ssp; + second_meta->sctr = sctr; + second_meta->TOSR = TOSR; + second_meta->TOSW = TOSW; + second_meta->target = getTop().retAddr; + return second_meta; +} +``` + +--- + +## 代码变更清单 + +### 配置文件 +- **src/cpu/pred/BranchPredictor.py**: 添加`enable2Taken`参数 +- **configs/example/xiangshan.py**: 默认启用2-taken +- **util/xs_scripts/Options.py**: 添加`--disable-2taken`选项 + +### 核心BTB基础设施 +- **src/cpu/pred/btb/btb.hh/.cc**: 添加`preloadBlock()`方法 +- **src/cpu/pred/btb/timed_base_pred.hh**: 添加虚拟`getSecondPredictionMeta()`接口 + +### BTB组件更新 +- **src/cpu/pred/btb/btb_tage.hh/.cc**: TAGE历史检查点实现 +- **src/cpu/pred/btb/btb_mgsc.hh/.cc**: MGSC历史检查点实现 +- **src/cpu/pred/btb/btb_ittage.hh/.cc**: ITTAGE历史检查点实现 +- **src/cpu/pred/btb/ras.hh/.cc**: RAS状态检查点实现 + +### 核心uBTB实现 +- **src/cpu/pred/btb/btb_ubtb.hh**: 2-taken数据结构和函数声明 +- **src/cpu/pred/btb/btb_ubtb.cc**: 完整的2-taken预测和训练逻辑 + +### 主BPU逻辑 +- **src/cpu/pred/btb/decoupled_bpred.hh**: 2-taken状态管理 +- **src/cpu/pred/btb/decoupled_bpred.cc**: BPU流水线集成 + +### 流接口 +- **src/cpu/pred/btb/stream_struct.hh**: 添加`isSecondFBPred`标志 + +### 测试脚本 +- **util/xs_scripts/kmh_v3_btb.sh**: 更新测试选项 +- **util/xs_scripts/xs-DecoupledBPU-ideal-kmhv3.sh**: 新的2-taken评估脚本 + +### 关键统计信息 + +**预测统计**: +```cpp +Stats::Scalar twotaken_pt_true; // pt_2nd=true预测成功 +Stats::Scalar twotaken_pt_false; // pt_2nd=false预测 +Stats::Scalar twotaken_range_check_failed; // 范围检查失败 +Stats::Scalar secondPredHit, secondPredMiss; // 第二个预测准确性 +``` + +**训练统计**: +```cpp +Stats::Scalar twotaken_pt_true_trained; // 创建pt_2nd=true表项 +Stats::Scalar twotaken_pt_false_trained; // 创建pt_2nd=false表项 +Stats::Scalar twoTakenConditionPassed; // 条件检查通过 +Stats::Scalar twoTakenAcceptFallthrough; // 接受pt_2nd=false情况 +``` + +**性能比率**: +```cpp +// 公式统计用于分析 +secondPredHitRatio = secondPredHit / (secondPredHit + secondPredMiss) +twoTakenUtilization = (twotaken_pt_true + twotaken_pt_false) / totalPredictions +``` + +--- + +## 总结 + +这个2-taken实现通过以下关键创新实现了性能提升: + +1. **单uBTB架构**:相比双uBTB减少50%硬件复杂度 +2. **pt_2nd支持**:扩展到顺序执行情况,大幅增加适用性 +3. **统一训练逻辑**:`trainCommon()`函数处理所有训练场景 +4. **ABTB兼容**:`preloadBlock()`保持ahead-pipeline不变性 +5. **选择性更新**:针对第二个预测的精确组件更新 +6. **完整的元数据管理**:所有组件的正确squash恢复 + +**硬件开销**:每个uBTB表项增加约25%空间 +**性能收益**:在适用场景下获得高达2倍的取指带宽 + +这个实现为未来的多预测研究奠定了坚实的基础,并提供了学术和工业环境中2-taken分支预测的参考实现。