diff --git a/core/BranchPredIF.hpp b/core/BranchPredIF.hpp new file mode 100644 index 00000000..775521a1 --- /dev/null +++ b/core/BranchPredIF.hpp @@ -0,0 +1,45 @@ +// -*- 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 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 diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 40eebfa1..ce27d253 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -1,6 +1,7 @@ project (core) add_library(core Core.cpp + SimpleBranchPred.cpp Fetch.cpp Decode.cpp Rename.cpp diff --git a/core/SimpleBranchPred.cpp b/core/SimpleBranchPred.cpp new file mode 100644 index 00000000..9c679ea5 --- /dev/null +++ b/core/SimpleBranchPred.cpp @@ -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(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( + input.fetch_PC, BTBEntry(prediction.branch_idx, prediction.predicted_PC))); + } + + return prediction; + } + +} // namespace BranchPredictor +} // namespace olympia diff --git a/core/SimpleBranchPred.hpp b/core/SimpleBranchPred.hpp new file mode 100644 index 00000000..53f9bccc --- /dev/null +++ b/core/SimpleBranchPred.hpp @@ -0,0 +1,89 @@ +// -*- 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 +#include +#include +#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::max(); + // predicted target PC + uint64_t predicted_PC = std::numeric_limits::max(); + }; + + class DefaultUpdate + { + public: + uint64_t fetch_PC = std::numeric_limits::max(); + uint32_t branch_idx = std::numeric_limits::max(); + uint64_t corrected_PC = std::numeric_limits::max(); + bool actually_taken = false; + }; + + class DefaultInput + { + public: + // PC of first instruction of fetch packet + uint64_t fetch_PC = std::numeric_limits::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::max()}; + uint64_t predicted_PC {std::numeric_limits::max()}; + }; + + // Currently SimpleBranchPredictor works only with uncompressed instructions + // TODO: generalize SimpleBranchPredictor for both compressed and uncompressed instructions + class SimpleBranchPredictor : public BranchPredictorIF + { + 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 branch_history_table_; // BHT + // a map of branch PC to target of the branch + std::map branch_target_buffer_; // BTB + }; + +} // namespace BranchPredictor +} // namespace olympia diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 35bb2a8d..be0e6c6e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -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) diff --git a/test/core/branch_pred/BranchPred_test.cpp b/test/core/branch_pred/BranchPred_test.cpp new file mode 100644 index 00000000..b37ffe15 --- /dev/null +++ b/test/core/branch_pred/BranchPred_test.cpp @@ -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; +} diff --git a/test/core/branch_pred/CMakeLists.txt b/test/core/branch_pred/CMakeLists.txt new file mode 100644 index 00000000..a70c99c2 --- /dev/null +++ b/test/core/branch_pred/CMakeLists.txt @@ -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)