Skip to content

Commit

Permalink
Branch Prediction API (#139)
Browse files Browse the repository at this point in the history
A code introducing branch prediction API.
  • Loading branch information
arupc-vmicro authored Mar 25, 2024
1 parent 9d9a345 commit 8d187b2
Show file tree
Hide file tree
Showing 7 changed files with 264 additions and 0 deletions.
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;
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
{
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)

0 comments on commit 8d187b2

Please sign in to comment.