Skip to content

2 Taken & 2 Fetch Exploration Framework - #772

Closed
Yakkhini wants to merge 8 commits into
xs-devfrom
2-taken-2-fetch
Closed

2 Taken & 2 Fetch Exploration Framework#772
Yakkhini wants to merge 8 commits into
xs-devfrom
2-taken-2-fetch

Conversation

@Yakkhini

@Yakkhini Yakkhini commented Mar 5, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • Optional "Two-Taken" fetch: generate a secondary fetch block from a single prediction (enableTwoTaken) with per-predictor participation control (block1Participate) and fine-grained drop filters for override, FTQ capacity, unsupported conditional/indirect/return branches.
    • New block1-related statistics tracking attempts, accepts, and drop reasons.
  • Documentation

    • Added comprehensive Two-Taken Framework guide with configuration and usage notes.
  • Tests

    • Added unit tests covering secondary-block behavior, drop reasons, and queue capacity.

@coderabbitai

coderabbitai Bot commented Mar 5, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR implements a "2-Taken Framework" in the DecoupledBPUWithBTB: speculative SpecState and PredictionBundle plumbing, optional generation of a second fetch block (block1), per-predictor block1 participation, and multiple gating/drop conditions for block1 publication and queuing.

Changes

Cohort / File(s) Summary
Configuration Surface
src/cpu/pred/BranchPredictor.py
Added block1Participate to TimedBaseBTBPredictor and six gating params to DecoupledBPUWithBTB (enableTwoTaken, dropBlock1On*) to control two-taken behavior.
Core Decoupled Predictor Logic
src/cpu/pred/btb/decoupled_bpred.cc, src/cpu/pred/btb/decoupled_bpred.hh
Introduced SpecState and PredictionBundle, new APIs (make/commit SpecState, buildPredictionBundle, computeNextSpecState, capturePredictionMetas, createFetchTargetEntry), and extended flow to build/enqueue optional block1 with drop-reason tracking and spec-state transitions.
Base Predictor Interface
src/cpu/pred/btb/timed_base_pred.hh, src/cpu/pred/btb/timed_base_pred.cc
Added block1Participate field, accessor/mutator, and virtual putPCHistoryForBlock1 hook (default delegates to putPCHistory).
BTB / Predictor Implementations
src/cpu/pred/btb/...
Added block1-aware history hooks putPCHistoryForBlock1 across TAGE/ITTAGE/MBTB/UBTB/MBTB/RA S and related formatting/refactors; ITTAGE/TAGE expose new members/constructors for block1 support.
UBTB / Unit-test scaffolding
src/cpu/pred/btb/btb_ubtb.*, src/cpu/pred/btb/test/*
UNIT_TEST constructor overloads, test-scoped helpers, and block1 history wrapper methods; tests extended and new fetch_target_queue test added.
Utilities & Common Definitions
src/cpu/pred/btb/common.hh
Added Block1DropReason and HistoryType enums and inline helpers getBlock1DropReason / getInitialBlock1DropReason used to decide block1 eligibility.
FTQ / Queue
src/cpu/pred/btb/ftq.hh, src/cpu/pred/btb/test/fetch_target_queue.test.cc
Added freeSlots(ThreadID) and ensured per-thread ftqSize init; tests verify free-slot accounting and squash behavior with two-taken capacity.
Spec / Stats / Docs
src/cpu/pred/btb/decoupled_bpred_stats.cc, src/cpu/pred/btb/docs/two_taken_framework.md
Stats file mostly formatting-only; new design doc describing 2-Taken framework, gating, hooks, stats, and tests.
Misc Formatting / Small refactors
src/cpu/pred/btb/*.cc, src/cpu/pred/btb/*.hh
Various whitespace/formatting refinements, small signature spacings, and UNIT_TEST guards across multiple files (MBTB, RAS, bt b_tage, etc.).

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant DecoupledBPU as DecoupledBPUWithBTB
    participant Block0Preds as Block0_Predictors
    participant SpecState as SpecState_Manager
    participant Block1Preds as Block1_Predictors
    participant FTQ as FetchTargetQueue

    Client->>DecoupledBPU: requestNewPrediction(tid)
    rect rgba(76,175,80,0.5)
      DecoupledBPU->>Block0Preds: query predictor stages -> stagePreds0
      DecoupledBPU->>SpecState: makeSpecState(tid) -> initialState
    end

    rect rgba(33,150,243,0.5)
      alt enableTwoTaken && gating passes
        DecoupledBPU->>SpecState: computeNextSpecState(initialState, pred0) -> stateAfter0
        DecoupledBPU->>Block1Preds: query block1 predictor stages -> stagePreds1
        DecoupledBPU->>DecoupledBPU: select block1, apply drop-reasons
      end
    end

    rect rgba(255,152,0,0.5)
      DecoupledBPU->>DecoupledBPU: capturePredictionMetas()
      DecoupledBPU->>FTQ: createFetchTargetEntry(pred0) -> enqueue
      alt hasPred1
        DecoupledBPU->>FTQ: createFetchTargetEntry(pred1) -> enqueue
      end
      DecoupledBPU->>SpecState: commitSpecState(tid, finalState)
    end

    DecoupledBPU-->>Client: prediction complete
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • jensen-yan
  • CJ362ff

"🐰 I hopped through specs with glee,
Two blocks now dance where one would be,
I nudged the state, I counted slots,
Block1 blooms — then drops if not,
A rabbit's cheer for two-taken spree!"

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 34.40% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '2 Taken & 2 Fetch Exploration Framework' accurately describes the main feature being introduced: a framework enabling two-block branch predictions and two-fetch capability in the BPU.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 2-taken-2-fetch
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/cpu/o3/fetch.cc (1)

1881-1882: Use FetchBuffer::reset() instead of partial invalidation.

Line 1882 invalidates only valid; using reset() keeps buffer metadata consistent and avoids stale startPC.

♻️ Suggested refactor
-        fetchBuffer[tid].valid = false;
+        fetchBuffer[tid].reset();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cpu/o3/fetch.cc` around lines 1881 - 1882, The code currently invalidates
the fetch buffer by setting fetchBuffer[tid].valid = false, which leaves other
metadata (e.g., startPC) stale; replace this partial invalidation with a full
reset by calling fetchBuffer[tid].reset() so the buffer's metadata and state are
cleared consistently (locate the usage near where fetchBuffer[tid].valid is set
and swap to the FetchBuffer::reset() call).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/cpu/pred/btb/btb_ubtb.cc`:
- Around line 326-350: The early return when secondPred == nullptr in
UBTB::addSecondPredictionToEntry leaves per-entry second-prediction state stale;
change the branch so that before returning you explicitly clear the
second-prediction fields on the target entry (set entry.valid_2nd = false;
entry.pt_2nd = false; entry.branch_info_2nd = BranchInfo()) so stale
valid_2nd/pt_2nd/branch_info_2nd cannot be replayed later; locate the logic in
UBTB::addSecondPredictionToEntry and update the nullptr path to clear those
fields on the referenced entry (use ubtb[entryIndex] as the target).
- Around line 437-462: train1Taken and train2Taken currently perform training
regardless of the usingS3Pred flag; add an early guard in UBTB::train1Taken and
UBTB::train2Taken to return immediately when the instance/member usingS3Pred is
false (the same config gate enforced by updateUsingS3Pred()), so these direct
decoupled-BTB entrypoints do not bypass the config; reference the
UBTB::train1Taken and UBTB::train2Taken methods and the usingS3Pred
member/updateUsingS3Pred() helper when making the change.

In `@src/cpu/pred/btb/mbtb.cc`:
- Around line 321-325: The MBTB::getSecondPredictionMeta() implementation
returns a non-null empty BTBMeta which prevents the fallback used elsewhere;
change MBTB::getSecondPredictionMeta() to return nullptr (matching UBTB and the
base class) so that code like entry.predMetas[i] = second_meta ? second_meta :
components[i]->getPredictionMeta() will fall back to getPredictionMeta() and
preserve proper hit_entries behavior.

---

Nitpick comments:
In `@src/cpu/o3/fetch.cc`:
- Around line 1881-1882: The code currently invalidates the fetch buffer by
setting fetchBuffer[tid].valid = false, which leaves other metadata (e.g.,
startPC) stale; replace this partial invalidation with a full reset by calling
fetchBuffer[tid].reset() so the buffer's metadata and state are cleared
consistently (locate the usage near where fetchBuffer[tid].valid is set and swap
to the FetchBuffer::reset() call).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 778f80bf-c4ae-4624-935f-efcfe8295c61

📥 Commits

Reviewing files that changed from the base of the PR and between 318c57c and 70fb2d7.

📒 Files selected for processing (14)
  • configs/example/idealkmhv3.py
  • configs/example/kmhv3.py
  • src/cpu/o3/fetch.cc
  • src/cpu/o3/fetch.hh
  • src/cpu/pred/BranchPredictor.py
  • src/cpu/pred/btb/btb_ubtb.cc
  • src/cpu/pred/btb/btb_ubtb.hh
  • src/cpu/pred/btb/common.hh
  • src/cpu/pred/btb/decoupled_bpred.cc
  • src/cpu/pred/btb/decoupled_bpred.hh
  • src/cpu/pred/btb/decoupled_bpred_stats.cc
  • src/cpu/pred/btb/mbtb.cc
  • src/cpu/pred/btb/mbtb.hh
  • src/cpu/pred/btb/timed_base_pred.hh

Comment thread src/cpu/pred/btb/btb_ubtb.cc Outdated
Comment on lines +326 to +350
UBTB::addSecondPredictionToEntry(int entryIndex,
FullBTBPrediction* secondPred)
{
if (!secondPred) {
return;
}
assert(entryIndex >= 0 && entryIndex < static_cast<int>(ubtb.size()));
auto &entry = ubtb[entryIndex];
if (!entry.valid) {
return;
}
entry.valid_2nd = true;
entry.pt_2nd = shouldSetPtSecond(*secondPred);
if (entry.pt_2nd) {
auto second_taken = secondPred->getTakenEntry();
if (!second_taken.valid) {
entry.valid_2nd = false;
return;
}
entry.branch_info_2nd = second_taken;
entry.branch_info_2nd.target = secondPred->getTarget(predictWidth);
} else {
entry.branch_info_2nd = BranchInfo();
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Clear second-prediction state when training without a second prediction.

Line 329 returns early for secondPred == nullptr, which leaves valid_2nd/pt_2nd/branch_info_2nd stale. That can replay obsolete second predictions in later putPCHistory2Taken() calls.

🐛 Proposed fix
 void
 UBTB::addSecondPredictionToEntry(int entryIndex,
                                  FullBTBPrediction* secondPred)
 {
-    if (!secondPred) {
-        return;
-    }
     assert(entryIndex >= 0 && entryIndex < static_cast<int>(ubtb.size()));
     auto &entry = ubtb[entryIndex];
     if (!entry.valid) {
         return;
     }
+    if (!secondPred) {
+        entry.valid_2nd = false;
+        entry.pt_2nd = false;
+        entry.branch_info_2nd = BranchInfo();
+        return;
+    }
     entry.valid_2nd = true;
     entry.pt_2nd = shouldSetPtSecond(*secondPred);
     if (entry.pt_2nd) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
UBTB::addSecondPredictionToEntry(int entryIndex,
FullBTBPrediction* secondPred)
{
if (!secondPred) {
return;
}
assert(entryIndex >= 0 && entryIndex < static_cast<int>(ubtb.size()));
auto &entry = ubtb[entryIndex];
if (!entry.valid) {
return;
}
entry.valid_2nd = true;
entry.pt_2nd = shouldSetPtSecond(*secondPred);
if (entry.pt_2nd) {
auto second_taken = secondPred->getTakenEntry();
if (!second_taken.valid) {
entry.valid_2nd = false;
return;
}
entry.branch_info_2nd = second_taken;
entry.branch_info_2nd.target = secondPred->getTarget(predictWidth);
} else {
entry.branch_info_2nd = BranchInfo();
}
}
void
UBTB::addSecondPredictionToEntry(int entryIndex,
FullBTBPrediction* secondPred)
{
assert(entryIndex >= 0 && entryIndex < static_cast<int>(ubtb.size()));
auto &entry = ubtb[entryIndex];
if (!entry.valid) {
return;
}
if (!secondPred) {
entry.valid_2nd = false;
entry.pt_2nd = false;
entry.branch_info_2nd = BranchInfo();
return;
}
entry.valid_2nd = true;
entry.pt_2nd = shouldSetPtSecond(*secondPred);
if (entry.pt_2nd) {
auto second_taken = secondPred->getTakenEntry();
if (!second_taken.valid) {
entry.valid_2nd = false;
return;
}
entry.branch_info_2nd = second_taken;
entry.branch_info_2nd.target = secondPred->getTarget(predictWidth);
} else {
entry.branch_info_2nd = BranchInfo();
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cpu/pred/btb/btb_ubtb.cc` around lines 326 - 350, The early return when
secondPred == nullptr in UBTB::addSecondPredictionToEntry leaves per-entry
second-prediction state stale; change the branch so that before returning you
explicitly clear the second-prediction fields on the target entry (set
entry.valid_2nd = false; entry.pt_2nd = false; entry.branch_info_2nd =
BranchInfo()) so stale valid_2nd/pt_2nd/branch_info_2nd cannot be replayed
later; locate the logic in UBTB::addSecondPredictionToEntry and update the
nullptr path to clear those fields on the referenced entry (use ubtb[entryIndex]
as the target).

Comment thread src/cpu/pred/btb/btb_ubtb.cc Outdated
Comment on lines 437 to 462
UBTB::train1Taken(FullBTBPrediction &s3Pred)
{
auto takenEntry = s3Pred.getTakenEntry();
if (takenEntry.valid) {
ubtbStats.s3UpdateHits++;
}else {
} else {
ubtbStats.s3UpdateMisses++;
}
auto startAddr = s3Pred.bbStart;
UBTBIter oldEntryIter = lastPred.hit_entry;
takenEntry.source = getComponentIdx();
updateNewEntry(oldEntryIter, takenEntry, startAddr);
trainCommon(lastPred.hit_index, s3Pred, nullptr);
}

void
UBTB::train2Taken(FullBTBPrediction &dffPred,
FullBTBPrediction &s3Pred,
int hitIndex)
{
if (dffPred.getTarget(predictWidth) != s3Pred.bbStart) {
trainCommon(hitIndex, dffPred, nullptr);
return;
}
if (!check2TakenConditions(dffPred, s3Pred)) {
trainCommon(hitIndex, dffPred, nullptr);
return;
}
trainCommon(hitIndex, dffPred, &s3Pred);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Respect usingS3Pred in the new direct training entrypoints.

Lines 437-462 train even when usingS3Pred is false. Since these methods are now called directly from decoupled BTB flow, this bypasses the existing config gate in updateUsingS3Pred().

🐛 Proposed fix
 void
 UBTB::train1Taken(FullBTBPrediction &s3Pred)
 {
+    if (!usingS3Pred) {
+        return;
+    }
     auto takenEntry = s3Pred.getTakenEntry();
     if (takenEntry.valid) {
         ubtbStats.s3UpdateHits++;
@@
 void
 UBTB::train2Taken(FullBTBPrediction &dffPred,
                   FullBTBPrediction &s3Pred,
                   int hitIndex)
 {
+    if (!usingS3Pred) {
+        return;
+    }
     if (dffPred.getTarget(predictWidth) != s3Pred.bbStart) {
         trainCommon(hitIndex, dffPred, nullptr);
         return;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
UBTB::train1Taken(FullBTBPrediction &s3Pred)
{
auto takenEntry = s3Pred.getTakenEntry();
if (takenEntry.valid) {
ubtbStats.s3UpdateHits++;
}else {
} else {
ubtbStats.s3UpdateMisses++;
}
auto startAddr = s3Pred.bbStart;
UBTBIter oldEntryIter = lastPred.hit_entry;
takenEntry.source = getComponentIdx();
updateNewEntry(oldEntryIter, takenEntry, startAddr);
trainCommon(lastPred.hit_index, s3Pred, nullptr);
}
void
UBTB::train2Taken(FullBTBPrediction &dffPred,
FullBTBPrediction &s3Pred,
int hitIndex)
{
if (dffPred.getTarget(predictWidth) != s3Pred.bbStart) {
trainCommon(hitIndex, dffPred, nullptr);
return;
}
if (!check2TakenConditions(dffPred, s3Pred)) {
trainCommon(hitIndex, dffPred, nullptr);
return;
}
trainCommon(hitIndex, dffPred, &s3Pred);
}
void
UBTB::train1Taken(FullBTBPrediction &s3Pred)
{
if (!usingS3Pred) {
return;
}
auto takenEntry = s3Pred.getTakenEntry();
if (takenEntry.valid) {
ubtbStats.s3UpdateHits++;
} else {
ubtbStats.s3UpdateMisses++;
}
trainCommon(lastPred.hit_index, s3Pred, nullptr);
}
void
UBTB::train2Taken(FullBTBPrediction &dffPred,
FullBTBPrediction &s3Pred,
int hitIndex)
{
if (!usingS3Pred) {
return;
}
if (dffPred.getTarget(predictWidth) != s3Pred.bbStart) {
trainCommon(hitIndex, dffPred, nullptr);
return;
}
if (!check2TakenConditions(dffPred, s3Pred)) {
trainCommon(hitIndex, dffPred, nullptr);
return;
}
trainCommon(hitIndex, dffPred, &s3Pred);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cpu/pred/btb/btb_ubtb.cc` around lines 437 - 462, train1Taken and
train2Taken currently perform training regardless of the usingS3Pred flag; add
an early guard in UBTB::train1Taken and UBTB::train2Taken to return immediately
when the instance/member usingS3Pred is false (the same config gate enforced by
updateUsingS3Pred()), so these direct decoupled-BTB entrypoints do not bypass
the config; reference the UBTB::train1Taken and UBTB::train2Taken methods and
the usingS3Pred member/updateUsingS3Pred() helper when making the change.

Comment thread src/cpu/pred/btb/mbtb.cc
Comment on lines +321 to +325
std::shared_ptr<void>
MBTB::getSecondPredictionMeta()
{
return std::make_shared<BTBMeta>();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

rg -n "getSecondPredictionMeta" --type=cpp -A 3

Repository: OpenXiangShan/GEM5

Length of output: 1642


🏁 Script executed:

rg -n "class.*BranchPredictor" --type=cpp -A 10 | head -50

Repository: OpenXiangShan/GEM5

Length of output: 1069


🏁 Script executed:

fd -e "ubtb" --type f | grep -E "\.(cc|h)$"

Repository: OpenXiangShan/GEM5

Length of output: 44


🏁 Script executed:

rg -n "auto second_meta = components" --type=cpp -A 3

Repository: OpenXiangShan/GEM5

Length of output: 477


🏁 Script executed:

rg -n "hit_entries" --type=cpp src/cpu/pred/btb/mbtb.cc

Repository: OpenXiangShan/GEM5

Length of output: 293


🏁 Script executed:

rg -n "checkPredictionHit|hit_entries" --type=cpp src/cpu/pred/btb/mbtb.cc -B 2 -A 5

Repository: OpenXiangShan/GEM5

Length of output: 1899


🏁 Script executed:

rg -n "getSecondPredictionMeta\(\)" --type=cpp -B 5 -A 10 | grep -E "(class|override|return|virtual)" | head -30

Repository: OpenXiangShan/GEM5

Length of output: 2923


🏁 Script executed:

rg -n "getSecondPrediction|second.*prediction|second.*meta" --type=cpp src/cpu/pred/btb/mbtb.cc src/cpu/pred/btb/mbtb.hh -B 2 -A 2 | grep -E "(^[0-9]|//|/\*)" | head -40

Repository: OpenXiangShan/GEM5

Length of output: 106


🏁 Script executed:

cat -n src/cpu/pred/btb/mbtb.hh | sed -n '140,160p'

Repository: OpenXiangShan/GEM5

Length of output: 1065


Inconsistent with other BTB predictors: getSecondPredictionMeta() should return nullptr

MBTB is the only component returning a non-null empty BTBMeta, while UBTB and the base class both return nullptr. This breaks the fallback mechanism in decoupled_bpred.cc (line 807):

entry.predMetas[i] = second_meta ? second_meta : components[i]->getPredictionMeta();

Since MBTB returns non-null, this ternary never triggers the fallback to getPredictionMeta(). Second predictions will have empty hit_entries, affecting checkPredictionHit() and commitBranch() statistics.

Return nullptr instead to match the established pattern and allow fallback to the first prediction's metadata (as UBTB does).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cpu/pred/btb/mbtb.cc` around lines 321 - 325, The
MBTB::getSecondPredictionMeta() implementation returns a non-null empty BTBMeta
which prevents the fallback used elsewhere; change
MBTB::getSecondPredictionMeta() to return nullptr (matching UBTB and the base
class) so that code like entry.predMetas[i] = second_meta ? second_meta :
components[i]->getPredictionMeta() will fall back to getPredictionMeta() and
preserve proper hit_entries behavior.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/cpu/pred/btb/decoupled_bpred.cc`:
- Around line 181-190: The fast-path that returns a second uBTB-derived
prediction in shouldGenerateSecondFromUBTB / buildSecondPredFromUBTB bypasses
generateFinalPredAndCreateBubbles and therefore never increments the stage usage
counters (dbpBtbStats.predsOfEachStage); to fix, ensure the same
predsOfEachStage update performed by generateFinalPredAndCreateBubbles is
executed when taking this uBTB-only fast path (i.e., after finalPred is created
and before/when processNewPrediction() is called), either by calling the
existing generateFinalPredAndCreateBubbles helper or by performing the identical
predsOfEachStage increment logic so the second prediction is counted.

In `@src/cpu/pred/btb/decoupled_bpred.hh`:
- Around line 146-151: The field enableTwoTaken is hardcoded true in
DecoupledBPU/BTB (symbol: enableTwoTaken) which forces 2-taken behavior even
when the BTB SimObject params (enable2Fetch, maxFetchBytesPerCycle) don't
request it; change enableTwoTaken to be a param-backed value on
DecoupledBPUWithBTBParams and consume that Param in the
DecoupledBPU/DecoupledBpred constructor so tick() (the path in
decoupled_bpred.cc that enqueues a second FSQ entry) respects the configured
flag; also add a Param.Bool(enable2Taken, default False, docstring) to
DecoupledBPUWithBTB (and expose it on DecoupledBPUWithBTB in BranchPredictor.py
alongside enable2Fetch/maxFetchBytesPerCycle) so existing BTB configs keep
current behavior by default.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2860ba95-91a2-48bd-aaf1-0afecb27fa23

📥 Commits

Reviewing files that changed from the base of the PR and between 70fb2d7 and 196a4a8.

📒 Files selected for processing (3)
  • .gitignore
  • src/cpu/pred/btb/decoupled_bpred.cc
  • src/cpu/pred/btb/decoupled_bpred.hh

Comment thread src/cpu/pred/btb/decoupled_bpred.cc Outdated
Comment thread src/cpu/pred/btb/decoupled_bpred.hh Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/cpu/pred/btb/decoupled_bpred.cc (1)

211-232: Minor: Unnecessary copy of ubtbPred.

Line 215 creates a mutable copy pred solely to call getTakenEntry(). If getTakenEntry() were const-qualified (or a const overload added), this copy could be avoided. This is a minor efficiency concern.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cpu/pred/btb/decoupled_bpred.cc` around lines 211 - 232, The local copy
pred is unnecessary; call FullBTBPrediction::getTakenEntry on the const
reference ubtbPred instead or add/use a const-qualified overload of
FullBTBPrediction::getTakenEntry so
DecoupledBPUWithBTB::shouldGenerateSecondFromUBTB can invoke
ubtbPred.getTakenEntry() directly; update FullBTBPrediction by marking
getTakenEntry() const (or adding a const overload) and remove the pred copy in
shouldGenerateSecondFromUBTB.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/cpu/o3/fetch.cc`:
- Around line 795-823: The bounds check for target_pc uses the global
fetchBufferSize constant instead of the per-buffer tracked size, which can be
incorrect if buffers vary; update the target_in_buffer computation (the
condition setting target_in_buffer in the do_2fetch logic) to use
fetchBuffer[tid].size rather than fetchBufferSize (i.e., replace the right-hand
term fetchBufferSize with fetchBuffer[tid].size) so the check uses the actual
FetchBuffer size field.

---

Nitpick comments:
In `@src/cpu/pred/btb/decoupled_bpred.cc`:
- Around line 211-232: The local copy pred is unnecessary; call
FullBTBPrediction::getTakenEntry on the const reference ubtbPred instead or
add/use a const-qualified overload of FullBTBPrediction::getTakenEntry so
DecoupledBPUWithBTB::shouldGenerateSecondFromUBTB can invoke
ubtbPred.getTakenEntry() directly; update FullBTBPrediction by marking
getTakenEntry() const (or adding a const overload) and remove the pred copy in
shouldGenerateSecondFromUBTB.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bedc4113-6281-4aeb-8b4c-a520d1df4230

📥 Commits

Reviewing files that changed from the base of the PR and between 196a4a8 and 6eb6d4b.

📒 Files selected for processing (8)
  • .gitignore
  • configs/example/idealkmhv3.py
  • configs/example/kmhv3.py
  • src/cpu/o3/fetch.cc
  • src/cpu/o3/fetch.hh
  • src/cpu/pred/BranchPredictor.py
  • src/cpu/pred/btb/decoupled_bpred.cc
  • src/cpu/pred/btb/decoupled_bpred.hh
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/cpu/o3/fetch.hh
  • .gitignore
  • configs/example/idealkmhv3.py

Comment thread src/cpu/o3/fetch.cc Outdated
@github-actions

Copy link
Copy Markdown

🚀 Coremark Smoke Test Results

Branch IPC Change
Base (xs-dev) 2.2607 -
This PR 2.2361 📉 -0.0246 (-1.09%)

✅ Difftest smoke test passed!

@Yakkhini Yakkhini added the perf label Mar 11, 2026
@github-actions

Copy link
Copy Markdown

🚀 Performance test triggered: spec06-0.8c

Change-Id: I2491732a16ac6bb15070b3848065a0feb218c9e4
Change-Id: I082fa1094b15955f5196edc7cfcfc3c4b0df424c
Change-Id: I8afd323e1824f8754c87908b0106ab699f5f1d3b
Change-Id: Ifae479fac072cdbccafafcd53aa803ebd786fe36
Change-Id: I4410e84d6f5fbfcd9c0440d5a5813d5cc4c7fee8
Change-Id: I23e14d9de6c61e6d163b8f4da279351edf3d1f6d
Change-Id: Ic8281eda32625e72fa8a11b77da2a33d8f9beca2
@Yakkhini Yakkhini changed the title Realistic 2 Taken & 2 Fetch 2 Taken & 2 Fetch Exploration Framework Mar 15, 2026
@github-actions

Copy link
Copy Markdown

🚀 Coremark Smoke Test Results

Branch IPC Change
Base (xs-dev) 2.2665 -
This PR 2.2665 ➡️ 0.0000 (0.00%)

✅ Difftest smoke test passed!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/cpu/pred/btb/test/btb_tage.test.cc (1)

156-168: ⚠️ Potential issue | 🟡 Minor

Guard the shared test helper before dereferencing condTakens.

If CondTakens_find() misses here, it->second is UB and can crash the suite instead of producing a useful failure.

Proposed fix
     Addr branch_pc = entry.pc;
     auto it = CondTakens_find(stagePreds[1].condTakens, branch_pc);
-    // ASSERT_TRUE(it != stagePreds[1].condTakens.end()) << "Prediction not found for PC " << std::hex << entry.pc;
-    bool predicted_taken = it->second;
+    if (it == stagePreds[1].condTakens.end()) {
+        ADD_FAILURE() << "Prediction not found for PC " << std::hex << entry.pc;
+        return false;
+    }
+    bool predicted_taken = it->second;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cpu/pred/btb/test/btb_tage.test.cc` around lines 156 - 168, The helper
predictUpdateCycle dereferences the iterator returned by CondTakens_find on
stagePreds[1].condTakens without checking it; guard that lookup by testing if it
== stagePreds[1].condTakens.end() before accessing it->second and produce a
clear test failure if missing (e.g., ASSERT_TRUE/EXPECT_NE with a message
referencing entry.pc or return false from predictUpdateCycle) so the test fails
cleanly instead of invoking undefined behavior.
♻️ Duplicate comments (1)
src/cpu/pred/btb/decoupled_bpred.cc (1)

541-542: ⚠️ Potential issue | 🟡 Minor

Accepted Block1 predictions are not counted in predsOfEachStage.

pred1.predSource is finalized here, but only the pred0 path bumps dbpBtbStats.predsOfEachStage. Once Block1 is enabled, the stage-usage histogram underreports every accepted second prediction.

📊 Minimal fix
     bundle.pred1Metas = capturePredictionMetas();
     bundle.hasPred1 = true;
     dbpBtbStats.block1Accepted++;
+    dbpBtbStats.predsOfEachStage[first_hit_stage]++;
     bundle.stateAfterFinal = computeNextSpecState(tid, bundle.stateAfter0, bundle.pred1, ftq.backId(tid) + 2);

Also applies to: 573-576

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cpu/pred/btb/decoupled_bpred.cc` around lines 541 - 542, The second
(Block1) prediction's finalized source (bundle.pred1.predSource /
bundle.pred1.overrideReason) isn't updating the stage histogram, so mirror the
pred0 update: after setting bundle.pred1.predSource and
bundle.pred1.overrideReason increment dbpBtbStats.predsOfEachStage for the
appropriate stage (use the same stage index calculation used for pred0's
update), i.e. apply the same counter bumping logic used for pred0 to pred1 so
accepted pred1 predictions are counted; repeat the same fix for the analogous
block around the other location (lines shown in the review: the 573-576 region).
🧹 Nitpick comments (2)
src/cpu/pred/btb/test/btb.test.cc (1)

179-194: Add TearDown to clean up allocated predictors.

The fixture allocates mbtb_small, mbtb, and ubtb with new but lacks a TearDown() method to delete them. This causes memory leaks during test execution.

♻️ Proposed fix to add TearDown
     void SetUp() override
     {
         // Create a BTB with 16 entries, 8-bit tags, and 4-way set associative
         mbtb_small = new MBTB(16, 8, 4, 1);  // mbtb (L1 BTB)
         mbtb = new MBTB(2048, 20, 4, 1);     // 2 sram, 4 way each, total 8 ways
         ubtb = new UBTB(16, 12);
     }
+
+    void TearDown() override
+    {
+        delete mbtb_small;
+        delete mbtb;
+        delete ubtb;
+    }

     MBTB* mbtb_small;
     MBTB* mbtb;
     UBTB* ubtb;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cpu/pred/btb/test/btb.test.cc` around lines 179 - 194, The test fixture
BTBTest allocates mbtb_small, mbtb, and ubtb in SetUp but never frees them; add
a TearDown() override to delete mbtb_small, mbtb, and ubtb and reset them to
nullptr to avoid leaks. Implement TearDown in the BTBTest class (matching the
existing SetUp) that checks each pointer (mbtb_small, mbtb, ubtb), deletes it if
non-null, and sets it to nullptr so repeated tests don't leak or use dangling
pointers.
src/cpu/pred/btb/test/btb_tage.test.cc (1)

320-346: Make the block1-enabled test assert prediction contents, not just count.

Comparing only condTakens.size() still passes when both paths emit nothing, or when they predict different PCs/directions with the same cardinality.

Stronger assertion
-    EXPECT_EQ(block1StagePreds.back().condTakens.size(), regularStagePreds.back().condTakens.size());
+    ASSERT_FALSE(regularStagePreds.back().condTakens.empty());
+    EXPECT_EQ(block1StagePreds.back().condTakens, regularStagePreds.back().condTakens);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cpu/pred/btb/test/btb_tage.test.cc` around lines 320 - 346, The test
currently only compares condTakens.size() between regularStagePreds and
block1StagePreds which can miss differing predictions; update TEST_F BTBTAGETest
Block1PredictionUsesRegularPathWhenEnabled to assert the actual prediction
contents returned by tage->putPCHistory and tage->putPCHistoryForBlock1 are
equal: iterate matching indices of regularStagePreds and block1StagePreds and
compare their condTakens vectors element-by-element, their predicted target PCs
and/or btbEntries (e.g., btbEntries[0].target or other relevant BTBEntry
fields), and any direction/prediction flags stored in FullBTBPrediction so the
test fails if the predictions differ, not just if counts match.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/cpu/pred/btb/btb_ittage.hh`:
- Around line 102-117: The active block1 path in putPCHistoryForBlock1 uses the
non-speculative history argument `history`, causing block1 lookups to use the
wrong PHist state; change the call so block1 uses the PHist speculative vector
by calling putPCHistory(startAddr, phistory, stagePreds) when
participatesInBlock1() is true (keep the existing participatesInBlock1() check,
delay loop, and fallback behavior that copies lowerPred.indirectTargets
unchanged).

In `@src/cpu/pred/btb/btb_ubtb.cc`:
- Around line 202-213: UBTB::putPCHistoryForBlock1 currently ignores the block1
participation flag and always calls putPCHistory; modify this method to check
the predictor's block1 participation (e.g., participatesInBlock1() or the
block1Participate config) and only invoke putPCHistory(startAddr, history,
stagePreds) when the predictor participates in block1, otherwise skip generating
block1 predictions so the per-predictor gate is honored.

In `@src/cpu/pred/btb/decoupled_bpred.cc`:
- Around line 413-425: The second FTQ enqueue (when bundle.hasPred1 is true) can
run into a full FTQ because earlier checks only guaranteed space for entry0;
modify the logic in the decoupled_bpred enqueue path to verify
ftq.freeSlots(tid) >= 2 before creating/inserting entry1 (or, if that check
fails, downgrade to pred0-only by clearing bundle.hasPred1 / skipping the second
insert and associated actions). Specifically, gate the createFetchTargetEntry/
fillAheadPipeline/ ftq.insert(Pred1) / predTraceManager->write_record sequence
on ftq.freeSlots(tid) >= 2 (or flip bundle.hasPred1 to false) so that
commitSpecState(tid, bundle.stateAfterFinal) and Block1 counters remain
consistent with what was actually enqueued.
- Line 229: buildPredictionBundle(...) is called after
generateFinalPredAndCreateBubbles(...), but generateFinalPredAndCreateBubbles
clears thread.predsOfEachStage so the Block1 prefilter reads stale/cleared
state; capture the needed stage-0 info before it is cleared and use that in
buildPredictionBundle instead of reading thread.predsOfEachStage[0] after the
helper returns. Concretely, save a local flag or copy (e.g., bool stage0HasBtb =
!thread.predsOfEachStage[0].btbEntries.empty() or a small struct copy of
predsOfEachStage[0]) before calling generateFinalPredAndCreateBubbles, then pass
that saved value into buildPredictionBundle (or call buildPredictionBundle
earlier) so Block1 drop logic uses the original stage-0 data; apply the same fix
for the other occurrence that similarly reads predsOfEachStage after it was
cleared.

---

Outside diff comments:
In `@src/cpu/pred/btb/test/btb_tage.test.cc`:
- Around line 156-168: The helper predictUpdateCycle dereferences the iterator
returned by CondTakens_find on stagePreds[1].condTakens without checking it;
guard that lookup by testing if it == stagePreds[1].condTakens.end() before
accessing it->second and produce a clear test failure if missing (e.g.,
ASSERT_TRUE/EXPECT_NE with a message referencing entry.pc or return false from
predictUpdateCycle) so the test fails cleanly instead of invoking undefined
behavior.

---

Duplicate comments:
In `@src/cpu/pred/btb/decoupled_bpred.cc`:
- Around line 541-542: The second (Block1) prediction's finalized source
(bundle.pred1.predSource / bundle.pred1.overrideReason) isn't updating the stage
histogram, so mirror the pred0 update: after setting bundle.pred1.predSource and
bundle.pred1.overrideReason increment dbpBtbStats.predsOfEachStage for the
appropriate stage (use the same stage index calculation used for pred0's
update), i.e. apply the same counter bumping logic used for pred0 to pred1 so
accepted pred1 predictions are counted; repeat the same fix for the analogous
block around the other location (lines shown in the review: the 573-576 region).

---

Nitpick comments:
In `@src/cpu/pred/btb/test/btb_tage.test.cc`:
- Around line 320-346: The test currently only compares condTakens.size()
between regularStagePreds and block1StagePreds which can miss differing
predictions; update TEST_F BTBTAGETest
Block1PredictionUsesRegularPathWhenEnabled to assert the actual prediction
contents returned by tage->putPCHistory and tage->putPCHistoryForBlock1 are
equal: iterate matching indices of regularStagePreds and block1StagePreds and
compare their condTakens vectors element-by-element, their predicted target PCs
and/or btbEntries (e.g., btbEntries[0].target or other relevant BTBEntry
fields), and any direction/prediction flags stored in FullBTBPrediction so the
test fails if the predictions differ, not just if counts match.

In `@src/cpu/pred/btb/test/btb.test.cc`:
- Around line 179-194: The test fixture BTBTest allocates mbtb_small, mbtb, and
ubtb in SetUp but never frees them; add a TearDown() override to delete
mbtb_small, mbtb, and ubtb and reset them to nullptr to avoid leaks. Implement
TearDown in the BTBTest class (matching the existing SetUp) that checks each
pointer (mbtb_small, mbtb, ubtb), deletes it if non-null, and sets it to nullptr
so repeated tests don't leak or use dangling pointers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f86c735f-0395-453a-b183-d8bd04689a9f

📥 Commits

Reviewing files that changed from the base of the PR and between 6eb6d4b and be726a6.

📒 Files selected for processing (20)
  • src/cpu/pred/BranchPredictor.py
  • src/cpu/pred/btb/btb_ittage.hh
  • src/cpu/pred/btb/btb_tage.cc
  • src/cpu/pred/btb/btb_tage.hh
  • src/cpu/pred/btb/btb_ubtb.cc
  • src/cpu/pred/btb/btb_ubtb.hh
  • src/cpu/pred/btb/common.hh
  • src/cpu/pred/btb/decoupled_bpred.cc
  • src/cpu/pred/btb/decoupled_bpred.hh
  • src/cpu/pred/btb/decoupled_bpred_stats.cc
  • src/cpu/pred/btb/docs/two_taken_framework.md
  • src/cpu/pred/btb/ftq.hh
  • src/cpu/pred/btb/mbtb.hh
  • src/cpu/pred/btb/ras.hh
  • src/cpu/pred/btb/test/SConscript
  • src/cpu/pred/btb/test/btb.test.cc
  • src/cpu/pred/btb/test/btb_tage.test.cc
  • src/cpu/pred/btb/test/fetch_target_queue.test.cc
  • src/cpu/pred/btb/timed_base_pred.cc
  • src/cpu/pred/btb/timed_base_pred.hh

Comment on lines +102 to +117
void putPCHistoryForBlock1(Addr startAddr, const boost::dynamic_bitset<> &history,
const boost::dynamic_bitset<> &phistory, const boost::dynamic_bitset<> &bwhistory,
const std::vector<boost::dynamic_bitset<>> &lhistory,
std::vector<FullBTBPrediction> &stagePreds, const FullBTBPrediction &lowerPred) override
{
(void)phistory;
(void)bwhistory;
(void)lhistory;
if (!participatesInBlock1()) {
for (int s = getDelay(); s < stagePreds.size(); s++) {
stagePreds[s].indirectTargets = lowerPred.indirectTargets;
}
return;
}
putPCHistory(startAddr, history, stagePreds);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use phistory for active ITTAGE block1 lookups.

This class’ speculative/recovery hooks are PHist-based at Lines 123-128, but the active block1 path delegates with history. That hashes block1 indirect lookups from the wrong speculative state and can diverge from later recovery/update behavior.

Proposed fix
-        (void)phistory;
+        (void)history;
         (void)bwhistory;
         (void)lhistory;
         if (!participatesInBlock1()) {
             for (int s = getDelay(); s < stagePreds.size(); s++) {
                 stagePreds[s].indirectTargets = lowerPred.indirectTargets;
             }
             return;
         }
-        putPCHistory(startAddr, history, stagePreds);
+        putPCHistory(startAddr, phistory, stagePreds);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
void putPCHistoryForBlock1(Addr startAddr, const boost::dynamic_bitset<> &history,
const boost::dynamic_bitset<> &phistory, const boost::dynamic_bitset<> &bwhistory,
const std::vector<boost::dynamic_bitset<>> &lhistory,
std::vector<FullBTBPrediction> &stagePreds, const FullBTBPrediction &lowerPred) override
{
(void)phistory;
(void)bwhistory;
(void)lhistory;
if (!participatesInBlock1()) {
for (int s = getDelay(); s < stagePreds.size(); s++) {
stagePreds[s].indirectTargets = lowerPred.indirectTargets;
}
return;
}
putPCHistory(startAddr, history, stagePreds);
}
void putPCHistoryForBlock1(Addr startAddr, const boost::dynamic_bitset<> &history,
const boost::dynamic_bitset<> &phistory, const boost::dynamic_bitset<> &bwhistory,
const std::vector<boost::dynamic_bitset<>> &lhistory,
std::vector<FullBTBPrediction> &stagePreds, const FullBTBPrediction &lowerPred) override
{
(void)history;
(void)bwhistory;
(void)lhistory;
if (!participatesInBlock1()) {
for (int s = getDelay(); s < stagePreds.size(); s++) {
stagePreds[s].indirectTargets = lowerPred.indirectTargets;
}
return;
}
putPCHistory(startAddr, phistory, stagePreds);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cpu/pred/btb/btb_ittage.hh` around lines 102 - 117, The active block1
path in putPCHistoryForBlock1 uses the non-speculative history argument
`history`, causing block1 lookups to use the wrong PHist state; change the call
so block1 uses the PHist speculative vector by calling putPCHistory(startAddr,
phistory, stagePreds) when participatesInBlock1() is true (keep the existing
participatesInBlock1() check, delay loop, and fallback behavior that copies
lowerPred.indirectTargets unchanged).

Comment on lines +202 to +213
void
UBTB::putPCHistoryForBlock1(Addr startAddr, const boost::dynamic_bitset<> &history,
const boost::dynamic_bitset<> &phistory, const boost::dynamic_bitset<> &bwhistory,
const std::vector<boost::dynamic_bitset<>> &lhistory,
std::vector<FullBTBPrediction> &stagePreds, const FullBTBPrediction &lowerPred)
{
(void)phistory;
(void)bwhistory;
(void)lhistory;
(void)lowerPred;
putPCHistory(startAddr, history, stagePreds);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Honor block1Participate in UBTB::putPCHistoryForBlock1().

MBTB and BTBITTAGE both branch on participatesInBlock1(), but this override always calls putPCHistory(). A UBTB configured with block1Participate = False still actively generates block1 predictions, so the new per-predictor gate is ineffective here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cpu/pred/btb/btb_ubtb.cc` around lines 202 - 213,
UBTB::putPCHistoryForBlock1 currently ignores the block1 participation flag and
always calls putPCHistory; modify this method to check the predictor's block1
participation (e.g., participatesInBlock1() or the block1Participate config) and
only invoke putPCHistory(startAddr, history, stagePreds) when the predictor
participates in block1, otherwise skip generating block1 predictions so the
per-predictor gate is honored.


generateFinalPredAndCreateBubbles(tid);

buildPredictionBundle(tid);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

This Block1 prefilter is reading already-cleared stage state.

buildPredictionBundle() runs after generateFinalPredAndCreateBubbles(), and that helper clears thread.predsOfEachStage before returning. The thread.predsOfEachStage[0].btbEntries.empty() input here therefore no longer reflects the stage-0 result that produced finalPred, so any initial Block1 drop rule keyed off that argument is making its decision from post-clear data.

Also applies to: 480-483

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cpu/pred/btb/decoupled_bpred.cc` at line 229, buildPredictionBundle(...)
is called after generateFinalPredAndCreateBubbles(...), but
generateFinalPredAndCreateBubbles clears thread.predsOfEachStage so the Block1
prefilter reads stale/cleared state; capture the needed stage-0 info before it
is cleared and use that in buildPredictionBundle instead of reading
thread.predsOfEachStage[0] after the helper returns. Concretely, save a local
flag or copy (e.g., bool stage0HasBtb =
!thread.predsOfEachStage[0].btbEntries.empty() or a small struct copy of
predsOfEachStage[0]) before calling generateFinalPredAndCreateBubbles, then pass
that saved value into buildPredictionBundle (or call buildPredictionBundle
earlier) so Block1 drop logic uses the original stage-0 data; apply the same fix
for the other occurrence that similarly reads predsOfEachStage after it was
cleared.

Comment on lines +413 to 425
if (bundle.hasPred1) {
FetchTarget entry1 = createFetchTargetEntry(tid, bundle.stateAfter0, bundle.pred1, bundle.pred1Metas);
fillAheadPipeline(entry1);
if (enablePredFSQTrace) {
predTraceManager->write_record(PredictionTrace(ftq.backId(tid), entry1));
}
ftq.insert(entry1);
printTarget(entry1);
dbpBtbStats.fsqEntryEnqueued++;
}

commitSpecState(tid, bundle.stateAfterFinal);
threads[tid].validprediction = false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Require real FTQ space before taking the second enqueue path.

Line 382 only proves there is room for entry0. If this thread has exactly one free slot and dropBlock1WhenFTQHasOnlyOneSlot is false, bundle.hasPred1 can still be true here and the second ftq.insert() will run against a full queue. At that point the FTQ state, stateAfterFinal, and Block1 counters are all assuming an enqueue that never had capacity. Either gate Block1 on ftq.freeSlots(tid) >= 2 or downgrade to pred0-only before the second insert.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cpu/pred/btb/decoupled_bpred.cc` around lines 413 - 425, The second FTQ
enqueue (when bundle.hasPred1 is true) can run into a full FTQ because earlier
checks only guaranteed space for entry0; modify the logic in the decoupled_bpred
enqueue path to verify ftq.freeSlots(tid) >= 2 before creating/inserting entry1
(or, if that check fails, downgrade to pred0-only by clearing bundle.hasPred1 /
skipping the second insert and associated actions). Specifically, gate the
createFetchTargetEntry/ fillAheadPipeline/ ftq.insert(Pred1) /
predTraceManager->write_record sequence on ftq.freeSlots(tid) >= 2 (or flip
bundle.hasPred1 to false) so that commitSpecState(tid, bundle.stateAfterFinal)
and Block1 counters remain consistent with what was actually enqueued.

@github-actions

Copy link
Copy Markdown

🚀 Coremark Smoke Test Results

Branch IPC Change
Base (xs-dev) 2.2665 -
This PR 2.2665 ➡️ 0.0000 (0.00%)

✅ Difftest smoke test passed!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/cpu/pred/btb/mbtb.cc (1)

131-131: ⚠️ Potential issue | 🟡 Minor

Add bounds validation to prevent undefined shift behavior.

The expression (1UL << tagBits) causes undefined behavior if tagBits is 64 or larger. While current configurations use values of 20–38 bits, there is no runtime validation preventing invalid values. Add a guard to enforce the constraint:

+    assert(tagBits < 64 && "tagBits must be less than 64 to avoid undefined shift");
     tagMask = (1UL << tagBits) - 1;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cpu/pred/btb/mbtb.cc` at line 131, The assignment tagMask = (1UL <<
tagBits) - 1 can invoke undefined behavior when tagBits is >= width of unsigned
long; add a runtime guard before this assignment (in the mbtb
initialization/constructor where tagBits is set) to validate tagBits is within
[0, sizeof(unsigned long)*8 - 1]; if the value is out of range, handle it
deterministically (e.g., assert/error/throw or clamp) and only perform tagMask =
(1UL << tagBits) - 1 after the check; reference the tagBits and tagMask symbols
so the protection is colocated with the current assignment.
🧹 Nitpick comments (2)
src/cpu/pred/btb/ras.cc (1)

123-139: Forward the actual history in Block1 path for consistency.

Line 138 calls putPCHistory with an empty bitset even though history is available. Passing through history avoids hidden divergence later.

♻️ Proposed refactor
-    (void)history;
@@
-    putPCHistory(startAddr, boost::dynamic_bitset<>(), stagePreds);
+    putPCHistory(startAddr, history, stagePreds);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cpu/pred/btb/ras.cc` around lines 123 - 139, In
BTBRAS::putPCHistoryForBlock1 replace the empty bitset forwarded to putPCHistory
with the actual history parameter: call putPCHistory(startAddr, history,
stagePreds) instead of putPCHistory(startAddr, boost::dynamic_bitset<>(),
stagePreds) so the real `history` is propagated and avoids divergence; update
the single call site in putPCHistoryForBlock1 accordingly.
src/cpu/pred/btb/mbtb.cc (1)

303-319: Signed/unsigned comparison in loop condition.

On line 313, int s is compared with stagePreds.size() which returns size_t. This causes a signed/unsigned comparison warning and could theoretically cause issues with very large vectors (though unlikely here).

♻️ Proposed fix
     if (!participatesInBlock1()) {
-        for (int s = getDelay(); s < stagePreds.size(); s++) {
+        for (size_t s = getDelay(); s < stagePreds.size(); s++) {
             stagePreds[s].btbEntries = lowerPred.btbEntries;
         }
         return;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cpu/pred/btb/mbtb.cc` around lines 303 - 319, The loop in
MBTB::putPCHistoryForBlock1 uses a signed int for s and compares it to
stagePreds.size() (unsigned), causing a signed/unsigned comparison; fix by
changing the loop variable to an unsigned type (e.g., size_t) and ensure
getDelay() is safely converted (e.g., size_t start =
static_cast<size_t>(getDelay()) or size_t start =
static_cast<size_t>(std::max(0, getDelay()))), then iterate for (size_t s =
start; s < stagePreds.size(); ++s) and assign stagePreds[s].btbEntries =
lowerPred.btbEntries as before.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/cpu/pred/btb/btb_ittage.cc`:
- Around line 213-229: The bypass return in BTBITTAGE::putPCHistoryForBlock1
leaves per-stage predictor metadata stale, causing update() to assume valid
state; when participatesInBlock1() is false, after copying
lowerPred.indirectTargets set each stagePreds[s] predictor metadata to a
safe/invalid default (clear any valid bits, tags, counters, or pointers used by
update()), so stagePreds entries cannot be treated as initialized; locate
BTBITTAGE::putPCHistoryForBlock1 and reset the metadata fields on stagePreds
(the same metadata/update fields that update() reads) before returning.

In `@src/cpu/pred/btb/btb_tage.cc`:
- Around line 393-401: When BTBTAGE bypasses Block1 (the !participatesInBlock1()
path), stagePreds entries are assigned condTakens and tageInfoForMgscs from
lowerPred but their meta fields remain stale and later get captured by
capturePredictionMetas(); update the bypass path in the participatesInBlock1()
false branch to explicitly clear/reset each stagePreds[s].meta (and any other
per-stage snapshot/history fields) for s in [getDelay(), stagePreds.size()) so
the prediction metadata does not carry over old folded-history snapshots —
locate the block around participatesInBlock1(), stagePreds, lowerPred and
putPCHistory(startAddr, history, stagePreds) and reset stagePreds[s].meta to a
fresh/default state before returning.

In `@src/cpu/pred/btb/ras.cc`:
- Around line 25-34: The constructor BTBRAS currently computes maxCtr = (1 <<
ctrWidth) - 1 which is undefined when ctrWidth is >= bitwidth of unsigned;
validate and guard ctrWidth before the shift: in the BTBRAS(unsigned numEntries,
unsigned ctrWidth, unsigned numInflightEntries) constructor check ctrWidth
against the machine word size (e.g. sizeof(unsigned)*8) and either clamp it to a
safe maximum or compute maxCtr using a wider type and safe shifting (e.g. use
1ULL << ctrWidth and then cap to the target type), ensuring maxCtr is set
without invoking undefined behavior; update any related uses of ctrWidth/maxCtr
accordingly (references: BTBRAS constructor, maxCtr, ctrWidth, TOSW).

---

Outside diff comments:
In `@src/cpu/pred/btb/mbtb.cc`:
- Line 131: The assignment tagMask = (1UL << tagBits) - 1 can invoke undefined
behavior when tagBits is >= width of unsigned long; add a runtime guard before
this assignment (in the mbtb initialization/constructor where tagBits is set) to
validate tagBits is within [0, sizeof(unsigned long)*8 - 1]; if the value is out
of range, handle it deterministically (e.g., assert/error/throw or clamp) and
only perform tagMask = (1UL << tagBits) - 1 after the check; reference the
tagBits and tagMask symbols so the protection is colocated with the current
assignment.

---

Nitpick comments:
In `@src/cpu/pred/btb/mbtb.cc`:
- Around line 303-319: The loop in MBTB::putPCHistoryForBlock1 uses a signed int
for s and compares it to stagePreds.size() (unsigned), causing a signed/unsigned
comparison; fix by changing the loop variable to an unsigned type (e.g., size_t)
and ensure getDelay() is safely converted (e.g., size_t start =
static_cast<size_t>(getDelay()) or size_t start =
static_cast<size_t>(std::max(0, getDelay()))), then iterate for (size_t s =
start; s < stagePreds.size(); ++s) and assign stagePreds[s].btbEntries =
lowerPred.btbEntries as before.

In `@src/cpu/pred/btb/ras.cc`:
- Around line 123-139: In BTBRAS::putPCHistoryForBlock1 replace the empty bitset
forwarded to putPCHistory with the actual history parameter: call
putPCHistory(startAddr, history, stagePreds) instead of putPCHistory(startAddr,
boost::dynamic_bitset<>(), stagePreds) so the real `history` is propagated and
avoids divergence; update the single call site in putPCHistoryForBlock1
accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 917f0e04-3397-4508-ada7-0b2e24fc7b16

📥 Commits

Reviewing files that changed from the base of the PR and between be726a6 and f93db0d.

📒 Files selected for processing (10)
  • src/cpu/pred/btb/btb_ittage.cc
  • src/cpu/pred/btb/btb_ittage.hh
  • src/cpu/pred/btb/btb_tage.cc
  • src/cpu/pred/btb/btb_tage.hh
  • src/cpu/pred/btb/btb_ubtb.hh
  • src/cpu/pred/btb/mbtb.cc
  • src/cpu/pred/btb/mbtb.hh
  • src/cpu/pred/btb/ras.cc
  • src/cpu/pred/btb/ras.hh
  • src/cpu/pred/btb/timed_base_pred.hh
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/cpu/pred/btb/btb_ittage.hh

Comment thread src/cpu/pred/btb/btb_ittage.cc Outdated
Comment on lines +213 to +229
void
BTBITTAGE::putPCHistoryForBlock1(Addr startAddr, const boost::dynamic_bitset<> &history,
const boost::dynamic_bitset<> &phistory, const boost::dynamic_bitset<> &bwhistory,
const std::vector<boost::dynamic_bitset<>> &lhistory,
std::vector<FullBTBPrediction> &stagePreds, const FullBTBPrediction &lowerPred)
{
(void)phistory;
(void)bwhistory;
(void)lhistory;
if (!participatesInBlock1()) {
for (int s = getDelay(); s < stagePreds.size(); s++) {
stagePreds[s].indirectTargets = lowerPred.indirectTargets;
}
return;
}
putPCHistory(startAddr, history, stagePreds);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Bypass path needs metadata invalidation to avoid stale/null update state.

At Line 222, non-participating Block1 returns without resetting predictor metadata. update() later assumes metadata is valid, which can cause stale training and potential null dereference.

🔧 Proposed fix
 void
 BTBITTAGE::putPCHistoryForBlock1(Addr startAddr, const boost::dynamic_bitset<> &history,
                                  const boost::dynamic_bitset<> &phistory, const boost::dynamic_bitset<> &bwhistory,
                                  const std::vector<boost::dynamic_bitset<>> &lhistory,
                                  std::vector<FullBTBPrediction> &stagePreds, const FullBTBPrediction &lowerPred)
 {
     (void)phistory;
     (void)bwhistory;
     (void)lhistory;
     if (!participatesInBlock1()) {
+        meta.reset();
         for (int s = getDelay(); s < stagePreds.size(); s++) {
             stagePreds[s].indirectTargets = lowerPred.indirectTargets;
         }
         return;
     }
     putPCHistory(startAddr, history, stagePreds);
 }
 void
 BTBITTAGE::update(const FetchTarget &stream)
 {
@@
-    auto meta = std::static_pointer_cast<TageMeta>(stream.predMetas[getComponentIdx()]);
+    auto meta = std::static_pointer_cast<TageMeta>(stream.predMetas[getComponentIdx()]);
+    if (!meta) {
+        DPRINTF(ITTAGE, "update: no prediction meta, skip\n");
+        return;
+    }
     auto preds = meta->preds;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cpu/pred/btb/btb_ittage.cc` around lines 213 - 229, The bypass return in
BTBITTAGE::putPCHistoryForBlock1 leaves per-stage predictor metadata stale,
causing update() to assume valid state; when participatesInBlock1() is false,
after copying lowerPred.indirectTargets set each stagePreds[s] predictor
metadata to a safe/invalid default (clear any valid bits, tags, counters, or
pointers used by update()), so stagePreds entries cannot be treated as
initialized; locate BTBITTAGE::putPCHistoryForBlock1 and reset the metadata
fields on stagePreds (the same metadata/update fields that update() reads)
before returning.

Comment thread src/cpu/pred/btb/btb_tage.cc Outdated
Comment on lines 393 to 401
if (!participatesInBlock1()) {
for (int s = getDelay(); s < stagePreds.size(); s++) {
stagePreds[s].condTakens = lowerPred.condTakens;
stagePreds[s].tageInfoForMgscs = lowerPred.tageInfoForMgscs;
}
return;
}
putPCHistory(startAddr, history, stagePreds);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
# Verify where component prediction meta is captured/consumed in Block1 flow.
rg -n -C3 "putPCHistoryForBlock1|getPredictionMeta|predMetas\\[" src/cpu/pred/btb

Repository: OpenXiangShan/GEM5

Length of output: 50376


🏁 Script executed:

rg -n "participatesInBlock1|putPCHistoryForBlock1" src/cpu/pred/btb/btb_tage.cc -A 20

Repository: OpenXiangShan/GEM5

Length of output: 1224


🏁 Script executed:

rg -n "getPredictionMeta|putPCHistoryForBlock1|predictBlock1" src/cpu/pred/btb/decoupled_bpred.cc -B 5 -A 10 | head -100

Repository: OpenXiangShan/GEM5

Length of output: 1407


🏁 Script executed:

rg -n "capturePredictionMetas|stream.predMetas" src/cpu/pred/btb/decoupled_bpred.cc -B 3 -A 3

Repository: OpenXiangShan/GEM5

Length of output: 812


🏁 Script executed:

rg -n "class.*BTBTAGE|struct.*TageMeta" src/cpu/pred/btb/btb_tage.hh -A 5 | head -80

Repository: OpenXiangShan/GEM5

Length of output: 497


🏁 Script executed:

rg -n "putPCHistoryForBlock1" src/cpu/pred/btb/ras.cc -A 25

Repository: OpenXiangShan/GEM5

Length of output: 1071


🏁 Script executed:

rg -n "buildPredictionBundle" src/cpu/pred/btb/decoupled_bpred.cc -A 120 | grep -A 120 "buildPredictionBundle"

Repository: OpenXiangShan/GEM5

Length of output: 10460


🏁 Script executed:

rg -n "pred1Metas|stream.predMetas" src/cpu/pred/btb/decoupled_bpred.cc -B 2 -A 5

Repository: OpenXiangShan/GEM5

Length of output: 1313


🏁 Script executed:

rg -n "capturePredictionMetas|putPCHistory\(" src/cpu/pred/btb/decoupled_bpred.cc | head -40

Repository: OpenXiangShan/GEM5

Length of output: 338


🏁 Script executed:

rg -n "participatesInBlock1" src/cpu/pred/btb/btb_tage.hh -B 2 -A 3

Repository: OpenXiangShan/GEM5

Length of output: 44


🏁 Script executed:

rg -n "participatesInBlock1" src/cpu/pred/btb/btb_tage.cc -B 2 -A 3

Repository: OpenXiangShan/GEM5

Length of output: 358


🏁 Script executed:

rg -n "bool participatesInBlock1|participatesInBlock1.*{" src/cpu/pred/btb -A 5

Repository: OpenXiangShan/GEM5

Length of output: 223


🏁 Script executed:

rg -n "participatesInBlock1" src/cpu/pred/btb -A 3 | grep -E "(participatesInBlock1|bool|return)"

Repository: OpenXiangShan/GEM5

Length of output: 954


🏁 Script executed:

rg -n "update.*stream" src/cpu/pred/btb/btb_tage.cc -A 10 | head -50

Repository: OpenXiangShan/GEM5

Length of output: 1555


🏁 Script executed:

cat -n src/cpu/pred/btb/btb_tage.cc | sed -n '385,410p'

Repository: OpenXiangShan/GEM5

Length of output: 1166


🏁 Script executed:

rg -n "struct TageMeta|class TageMeta" src/cpu/pred/btb/btb_tage.hh -A 20

Repository: OpenXiangShan/GEM5

Length of output: 1113


🏁 Script executed:

rg -n "class TageMeta|struct TageMeta" src/cpu/pred/btb/btb_tage.hh -A 30

Repository: OpenXiangShan/GEM5

Length of output: 1741


Clear stale metadata when BTBTAGE does not participate in Block1 prediction.

At lines 393–401, when !participatesInBlock1(), the bypass path copies lower-stage predictions but leaves the member meta unchanged. Later, when capturePredictionMetas() is called, this stale metadata (containing prediction snapshots and history state from a previous cycle) is captured and propagated into the update path, causing the predictor to train on outdated folded history snapshots instead of the actual Block1 state.

💡 Proposed fix
 void
 BTBTAGE::putPCHistoryForBlock1(Addr startAddr, const boost::dynamic_bitset<> &history,
                                const boost::dynamic_bitset<> &phistory, const boost::dynamic_bitset<> &bwhistory,
                                const std::vector<boost::dynamic_bitset<>> &lhistory,
                                std::vector<FullBTBPrediction> &stagePreds, const FullBTBPrediction &lowerPred)
 {
     (void)phistory;
     (void)bwhistory;
     (void)lhistory;
     if (!participatesInBlock1()) {
+        meta = std::make_shared<TageMeta>();
         for (int s = getDelay(); s < stagePreds.size(); s++) {
             stagePreds[s].condTakens = lowerPred.condTakens;
             stagePreds[s].tageInfoForMgscs = lowerPred.tageInfoForMgscs;
         }
         return;
     }
     putPCHistory(startAddr, history, stagePreds);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!participatesInBlock1()) {
for (int s = getDelay(); s < stagePreds.size(); s++) {
stagePreds[s].condTakens = lowerPred.condTakens;
stagePreds[s].tageInfoForMgscs = lowerPred.tageInfoForMgscs;
}
return;
}
putPCHistory(startAddr, history, stagePreds);
}
if (!participatesInBlock1()) {
meta = std::make_shared<TageMeta>();
for (int s = getDelay(); s < stagePreds.size(); s++) {
stagePreds[s].condTakens = lowerPred.condTakens;
stagePreds[s].tageInfoForMgscs = lowerPred.tageInfoForMgscs;
}
return;
}
putPCHistory(startAddr, history, stagePreds);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cpu/pred/btb/btb_tage.cc` around lines 393 - 401, When BTBTAGE bypasses
Block1 (the !participatesInBlock1() path), stagePreds entries are assigned
condTakens and tageInfoForMgscs from lowerPred but their meta fields remain
stale and later get captured by capturePredictionMetas(); update the bypass path
in the participatesInBlock1() false branch to explicitly clear/reset each
stagePreds[s].meta (and any other per-stage snapshot/history fields) for s in
[getDelay(), stagePreds.size()) so the prediction metadata does not carry over
old folded-history snapshots — locate the block around participatesInBlock1(),
stagePreds, lowerPred and putPCHistory(startAddr, history, stagePreds) and reset
stagePreds[s].meta to a fresh/default state before returning.

Comment thread src/cpu/pred/btb/ras.cc Outdated
Comment on lines +25 to +34
BTBRAS::BTBRAS(unsigned numEntries, unsigned ctrWidth, unsigned numInflightEntries)
: TimedBaseBTBPredictor(), numEntries(numEntries), ctrWidth(ctrWidth), numInflightEntries(numInflightEntries)
{
// Initialize RAS state
ssp = 0;
nsp = 0;
sctr = 0;
stack.resize(numEntries);
maxCtr = (1 << ctrWidth) - 1;
TOSW = 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "[1] Find BTBRAS ctrWidth definitions/defaults"
rg -n "BTBRAS|ctrWidth" --type=py --type=hh --type=cc src
echo
echo "[2] Find existing ctrWidth range guards"
rg -n "assert\\(.*ctrWidth|fatal_if\\(.*ctrWidth|maxCtr\\s*=\\s*\\(1\\s*<<\\s*ctrWidth\\)" --type=hh --type=cc src/cpu/pred/btb

Repository: OpenXiangShan/GEM5

Length of output: 151


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "[1] Find BTBRAS header and implementation files"
find src -name "*ras*" -type f | head -20
echo
echo "[2] Search for BTBRAS class definition"
rg -n "class BTBRAS" src --type-add='cc:*.cc' --type-add='h:*.h' --type-add='hh:*.hh' -A 20
echo
echo "[3] Search for ctrWidth parameter defaults"
rg -n "ctrWidth\s*=" src --type-add='cc:*.cc' --type-add='h:*.h' -B 2 -A 2
echo
echo "[4] Check ras.cc constructor implementation"
rg -n "BTBRAS::BTBRAS" src --type-add='cc:*.cc' -A 15

Repository: OpenXiangShan/GEM5

Length of output: 7931


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "[1] Check if Param.Unsigned has bounds validation"
rg -n "class.*Unsigned|def.*Unsigned" src/cpu/pred/BranchPredictor.py -A 5
echo
echo "[2] Look for any ctrWidth validation in the param/config system"
fd -e py src | xargs rg -l "Param\.Unsigned\|verify.*ctrWidth\|check.*ctrWidth" | head -5
echo
echo "[3] Search for comments about ctrWidth limits in header files"
rg -n "ctrWidth" src/cpu/pred/btb/ras.hh -B 2 -A 2
echo
echo "[4] Check complete constructor implementation for validation"
cat -n src/cpu/pred/btb/ras.cc | sed -n '25,50p'

Repository: OpenXiangShan/GEM5

Length of output: 111


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "[1] Check ras.hh for ctrWidth member declaration"
rg -n "unsigned ctrWidth" src/cpu/pred/btb/ras.hh -B 2 -A 2
echo
echo "[2] Get full unit test constructor (lines 25-50)"
sed -n '25,50p' src/cpu/pred/btb/ras.cc
echo
echo "[3] Get full production constructor (lines 52-80)"
sed -n '52,80p' src/cpu/pred/btb/ras.cc
echo
echo "[4] Check if ctrWidth is ever validated"
rg -n "ctrWidth.*assert|ctrWidth.*<|ctrWidth.*>" src/cpu/pred/btb/ras.cc

Repository: OpenXiangShan/GEM5

Length of output: 1969


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "[1] Check what 'unsigned' typedef resolves to"
rg -n "typedef.*unsigned|using.*unsigned" src/cpu/pred/btb/ras.hh
echo
echo "[2] Check for any documentation about ctrWidth valid range"
rg -n "ctrWidth" src/cpu/pred/BranchPredictor.py -B 1 -A 1
echo
echo "[3] Verify bits needed: unsigned is 32-bit minimum, so max safe shift is 31"
echo "Default ctrWidth values (8 and 2) are safe for 32-bit and 64-bit shifts"
echo "But nothing prevents setting ctrWidth to 32+ which would be UB"
echo
echo "[4] Check if maxCtr is used with any assumptions about its range"
rg -n "maxCtr" src/cpu/pred/btb/ras.cc -B 1 -A 1 | head -30

Repository: OpenXiangShan/GEM5

Length of output: 107


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "[1] Check maxCtr usage in the code"
rg -n "maxCtr" src/cpu/pred/btb/ras.cc -A 2 | head -40
echo
echo "[2] Check if ctrWidth is documented in comments"
grep -n "ctrWidth" src/cpu/pred/BranchPredictor.py
echo
echo "[3] Verify the shift operation issue with simple analysis"
echo "For 'unsigned' type (32-bit minimum in C++):"
echo "  (1 << ctrWidth) is UB when ctrWidth >= 32"
echo "Current defaults: ctrWidth=8 (safe) and ctrWidth=2 (safe)"
echo "But no runtime validation prevents ctrWidth >= 32"

Repository: OpenXiangShan/GEM5

Length of output: 1014


Guard ctrWidth before bit-shift to avoid undefined behavior.

Lines 33 and 64 compute maxCtr = (1 << ctrWidth) - 1. This is undefined when ctrWidth ≥ 32 (the width of unsigned). While current defaults (ctrWidth=8, ctrWidth=2) are safe, there is no runtime validation preventing misconfiguration with larger values.

Proposed fix
+#include <limits>
@@
-    maxCtr = (1 << ctrWidth) - 1;
+    assert(ctrWidth > 0 && ctrWidth < std::numeric_limits<unsigned>::digits);
+    maxCtr = static_cast<int>((1u << ctrWidth) - 1u);
@@
-    maxCtr = (1 << ctrWidth) - 1;
+    assert(ctrWidth > 0 && ctrWidth < std::numeric_limits<unsigned>::digits);
+    maxCtr = static_cast<int>((1u << ctrWidth) - 1u);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cpu/pred/btb/ras.cc` around lines 25 - 34, The constructor BTBRAS
currently computes maxCtr = (1 << ctrWidth) - 1 which is undefined when ctrWidth
is >= bitwidth of unsigned; validate and guard ctrWidth before the shift: in the
BTBRAS(unsigned numEntries, unsigned ctrWidth, unsigned numInflightEntries)
constructor check ctrWidth against the machine word size (e.g.
sizeof(unsigned)*8) and either clamp it to a safe maximum or compute maxCtr
using a wider type and safe shifting (e.g. use 1ULL << ctrWidth and then cap to
the target type), ensuring maxCtr is set without invoking undefined behavior;
update any related uses of ctrWidth/maxCtr accordingly (references: BTBRAS
constructor, maxCtr, ctrWidth, TOSW).

@github-actions

Copy link
Copy Markdown

🚀 Coremark Smoke Test Results

Branch IPC Change
Base (xs-dev) 2.2665 -
This PR 2.2665 ➡️ 0.0000 (0.00%)

✅ Difftest smoke test passed!

@github-actions

Copy link
Copy Markdown

🚀 Coremark Smoke Test Results

Branch IPC Change
Base (xs-dev) 2.2665 -
This PR 2.2665 ➡️ 0.0000 (0.00%)

✅ Difftest smoke test passed!

@Yakkhini
Yakkhini force-pushed the 2-taken-2-fetch branch 2 times, most recently from 4252af8 to be726a6 Compare March 16, 2026 05:30
@github-actions

Copy link
Copy Markdown

🚀 Coremark Smoke Test Results

Branch IPC Change
Base (xs-dev) 2.2665 -
This PR 2.2665 ➡️ 0.0000 (0.00%)

✅ Difftest smoke test passed!

@Yakkhini Yakkhini removed the perf label Mar 16, 2026
@Yakkhini

Copy link
Copy Markdown
Collaborator Author

TODO: Currently there is to many modification noise in this PR and they should be clean up before review / merge / continues develop for further evaluations.

@github-actions

Copy link
Copy Markdown

🚀 Coremark Smoke Test Results

Branch IPC Change
Base (xs-dev) 2.2665 -
This PR 2.2665 ➡️ 0.0000 (0.00%)

✅ Difftest smoke test passed!

@Yakkhini

Copy link
Copy Markdown
Collaborator Author

Closed since we'll use another way to explore/approach 2T2F μarch.

@Yakkhini Yakkhini closed this Mar 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant