Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Branch Prediction API #139

Merged
merged 12 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions core/BranchPredIF.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// <Branch.hpp> -*- C++ -*-

//!
//! \file BranchPred.hpp
//! \brief Definition of Branch Prediction API
//!

/*
* This file defines the Branch Prediction API.
* The goal is to define an API that is generic and yet flexible enough to support various
* branch prediction microarchitecture.
* To the end, we envision a generic branch predictor as a black box with following inputs
* and outputs:
* * A generic Prediction output
* * A generic Prediction input
* * A generic Update input
*
* The generic branch predictor may have two operations:
* * getPrediction: produces Prediction output based on the Prediction input.
* * updatePredictor: updates Predictor with Update input.
*
* It is intended that an implementation of branch predictor must also specify
* implementations of Prediction output, Prediction input and Update input, along with
* implementations of getPrediction and updatePredictor operations.
* */
#pragma once

namespace olympia
{
namespace BranchPredictor
{

template <class PredictionT, class UpdateT, class InputT>
class BranchPredictorIF
{
public:
// TODO: create constexpr for bytes per compressed and uncompressed inst
static constexpr uint8_t bytes_per_inst = 4;
arupc-vmicro marked this conversation as resolved.
Show resolved Hide resolved
virtual ~BranchPredictorIF() { };
virtual PredictionT getPrediction(const InputT &) = 0;
virtual void updatePredictor(const UpdateT &) = 0;
};

} // namespace BranchPredictor
} // namespace olympia
1 change: 1 addition & 0 deletions core/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
project (core)
add_library(core
Core.cpp
SimpleBranchPred.cpp
Fetch.cpp
Decode.cpp
Rename.cpp
Expand Down
79 changes: 79 additions & 0 deletions core/SimpleBranchPred.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#include "SimpleBranchPred.hpp"

/*
* The algorithm used for prediction / update is as follows:
* Prediction:
* - look up BHT to determine if the branch is predicted taken or not
* using 2-bit saturated counter
* - value 3: strongly taken
* - value 2: weakly taken
* - value 1: weakly not taken
* - value 0: strongly not taken
* - look up BTB to see if an entry exists for the input fetch pc
* - if present in BTB and predicted taken, BTB entry is used to determine
* prediction branch idx and predicted_PC
* - if present in BTB but predicted not taken, BTB entry is used to determine
* prediction branch idx, while predicted_PC is the fall through addr
* - if not present in BTB entry, prediction branch idx is the last instr of
* the FetchPacket, while predicted PC is the fall through addr. Also, create
* a new BTB entry
* Update:
* - a valid BTB entry must be present for fetch PC
* - TBD
*
*/
namespace olympia
{
namespace BranchPredictor
{

void SimpleBranchPredictor::updatePredictor(const DefaultUpdate & update) {

sparta_assert(branch_target_buffer_.find(update.fetch_PC) != branch_target_buffer_.end());
branch_target_buffer_[update.fetch_PC].branch_idx = update.branch_idx;
if (update.actually_taken) {
branch_history_table_[update.fetch_PC] =
(branch_history_table_[update.fetch_PC] == 3) ? 3 :
branch_history_table_[update.fetch_PC] + 1;
branch_target_buffer_[update.fetch_PC].predicted_PC = update.corrected_PC;
} else {
branch_history_table_[update.fetch_PC] =
(branch_history_table_[update.fetch_PC] == 0) ? 0 :
branch_history_table_[update.fetch_PC] - 1;
}
}

DefaultPrediction SimpleBranchPredictor::getPrediction(const DefaultInput & input) {
bool predictTaken = false;
if (branch_history_table_.find(input.fetch_PC) != branch_history_table_.end()) {
predictTaken = (branch_history_table_[input.fetch_PC] > 1);
} else {
// add a new entry to BHT, biased towards not taken
branch_history_table_.insert(std::pair<uint64_t, uint8_t>(input.fetch_PC, 1));
}

DefaultPrediction prediction;
if (branch_target_buffer_.find(input.fetch_PC) != branch_target_buffer_.end()) {
// BTB hit
const BTBEntry & btb_entry = branch_target_buffer_[input.fetch_PC];
prediction.branch_idx = btb_entry.branch_idx;
if (predictTaken) {
prediction.predicted_PC = btb_entry.predicted_PC;
} else {
// fall through address
prediction.predicted_PC = input.fetch_PC + prediction.branch_idx + BranchPredictorIF::bytes_per_inst;
}
} else {
// BTB miss
prediction.branch_idx = max_fetch_insts_;
prediction.predicted_PC = input.fetch_PC + max_fetch_insts_ * bytes_per_inst;
// add new entry to BTB
branch_target_buffer_.insert(std::pair<uint64_t,BTBEntry>(
input.fetch_PC, BTBEntry(prediction.branch_idx, prediction.predicted_PC)));
}

return prediction;
}

} // namespace BranchPredictor
} // namespace olympia
89 changes: 89 additions & 0 deletions core/SimpleBranchPred.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// <SimpleBranchPred.hpp> -*- C++ -*-

//!
//! \file SimpleBranchPred.hpp
//! \brief Class definition of a simple brranch prediction using branch prediction interface
//!

/*
* This file defines the class SimpleBranchPredictor, as well as, a default Prediction
* output class, a default Prediction input class, a defeault Prediction input class
* as required by Olympia's Branch Prediction inteface
* */
#pragma once

#include <cstdint>
#include <map>
#include <limits>
#include "sparta/utils/SpartaAssert.hpp"
#include "BranchPredIF.hpp"

namespace olympia
{
namespace BranchPredictor
{

// following class definitions are example inputs & outputs for a very simple branch
// predictor
class DefaultPrediction
{
public:
// index of branch instruction in the fetch packet
// branch_idx can vary from 0 to (FETCH_WIDTH - 1)
// initialized to default max to catch errors
uint32_t branch_idx = std::numeric_limits<uint32_t>::max();
// predicted target PC
uint64_t predicted_PC = std::numeric_limits<uint64_t>::max();
};

class DefaultUpdate
{
public:
uint64_t fetch_PC = std::numeric_limits<uint64_t>::max();
uint32_t branch_idx = std::numeric_limits<uint32_t>::max();
uint64_t corrected_PC = std::numeric_limits<uint64_t>::max();
bool actually_taken = false;
};

class DefaultInput
Copy link
Contributor

Choose a reason for hiding this comment

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

It doesn't feel right to pollute the olympia namespace with a fairly generically named class "DefaultInput". It's not obvious that this is related to branch predictors, let alone that it's used for the SimpleBranchPredictor.

Goes for the others DefaultUpdate/DefaultPrediction as well?

Can't we place them under SimpleBranchPredictor? Or name them SimpleBranchPredictorUpdate

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added a separate namespace for BranchPredictor within olympia.

{
public:
// PC of first instruction of fetch packet
uint64_t fetch_PC = std::numeric_limits<uint64_t>::max();
};

class BTBEntry
{
public:
// use of BTBEntry in std:map operator [] requires default constructor
BTBEntry() = default;
BTBEntry(uint32_t bidx, uint64_t predPC) :
branch_idx(bidx),
predicted_PC(predPC)
{}
uint32_t branch_idx {std::numeric_limits<uint32_t>::max()};
uint64_t predicted_PC {std::numeric_limits<uint64_t>::max()};
};

// Currently SimpleBranchPredictor works only with uncompressed instructions
// TODO: generalize SimpleBranchPredictor for both compressed and uncompressed instructions
class SimpleBranchPredictor : public BranchPredictorIF<DefaultPrediction, DefaultUpdate, DefaultInput>
{
public:
SimpleBranchPredictor(uint32_t max_fetch_insts) :
max_fetch_insts_(max_fetch_insts)
{}
DefaultPrediction getPrediction(const DefaultInput &);
void updatePredictor(const DefaultUpdate &);
private:
// maximum number of instructions in a FetchPacket
const uint32_t max_fetch_insts_;
// BHT and BTB of SimpleBranchPredictor is unlimited in size
// a map of branch PC to 2 bit staurating counter tracking branch history
std::map <uint64_t, uint8_t> branch_history_table_; // BHT
// a map of branch PC to target of the branch
std::map <uint64_t, BTBEntry> branch_target_buffer_; // BTB
};

} // namespace BranchPredictor
} // namespace olympia
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ add_subdirectory(core/l2cache)
add_subdirectory(core/rename)
add_subdirectory(core/lsu)
add_subdirectory(core/issue_queue)
add_subdirectory(core/branch_pred)
43 changes: 43 additions & 0 deletions test/core/branch_pred/BranchPred_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#include "SimpleBranchPred.hpp"
#include "sparta/utils/SpartaTester.hpp"

TEST_INIT

void runTest(int argc, char **argv)
{
olympia::BranchPredictor::SimpleBranchPredictor predictor(4); //specify max num insts to fetch

olympia::BranchPredictor::DefaultInput input;
input.fetch_PC = 0x0;

// BTB miss
olympia::BranchPredictor::DefaultPrediction prediction = predictor.getPrediction(input);

EXPECT_EQUAL(prediction.branch_idx, 4);
EXPECT_EQUAL(prediction.predicted_PC, 16);

// there was a taken branch at the 3rd instruction from fetchPC, with target 0x100
olympia::BranchPredictor::DefaultUpdate update;
update.fetch_PC = 0x0;
update.branch_idx = 2;
update.corrected_PC = 0x100;
update.actually_taken = true;
predictor.updatePredictor(update);

// try the same input with fetchPC 0x0 again
prediction = predictor.getPrediction(input);

EXPECT_EQUAL(prediction.branch_idx, 2);
EXPECT_EQUAL(prediction.predicted_PC, 0x100);

// TODO: add more tests

}

int main(int argc, char **argv)
{
runTest(argc, argv);

REPORT_ERROR;
return (int)ERROR_CODE;
}
6 changes: 6 additions & 0 deletions test/core/branch_pred/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
project(BranchPred_test)

add_executable(BranchPred_test BranchPred_test.cpp)
target_link_libraries(BranchPred_test core common_test SPARTA::sparta)

sparta_named_test(BranchPred_test_Run BranchPred_test)
Loading