diff --git a/core/FTQ.cpp b/core/FTQ.cpp new file mode 100644 index 00000000..f9ab2d2d --- /dev/null +++ b/core/FTQ.cpp @@ -0,0 +1,126 @@ +#include "FTQ.hpp" + +namespace olympia +{ + const char* FTQ::name = "ftq"; + + FTQ::FTQ(sparta::TreeNode* node, const FTQParameterSet* p) : + sparta::Unit(node), + ftq_capacity_(p->ftq_capacity) + { + sparta::StartupEvent(node, CREATE_SPARTA_HANDLER(FTQ, sendInitialCreditsToBPU_)); + + in_bpu_first_prediction_output_.registerConsumerHandler(CREATE_SPARTA_HANDLER_WITH_DATA( + FTQ, getFirstPrediction_, BranchPredictor::PredictionOutput)); + + in_bpu_second_prediction_output_.registerConsumerHandler(CREATE_SPARTA_HANDLER_WITH_DATA( + FTQ, getSecondPrediction_, BranchPredictor::PredictionOutput)); + + in_fetch_credits_.registerConsumerHandler( + CREATE_SPARTA_HANDLER_WITH_DATA(FTQ, getFetchCredits_, uint32_t)); + + /**in_rob_signal_.registerConsumerHandler + (CREATE_SPARTA_HANDLER_WITH_DATA(FTQ, getROBSignal_, uint32_t)); + **/ + } + + FTQ::~FTQ() {} + + void FTQ::sendInitialCreditsToBPU_() { sendCreditsToBPU_(5); } + + void FTQ::sendCreditsToBPU_(const uint32_t & credits) + { + ILOG("Send " << credits << " credits to BPU"); + out_bpu_credits_.send(credits); + } + + void FTQ::getFirstPrediction_(const BranchPredictor::PredictionOutput & prediction) + { + if (fetch_target_queue_.size() < ftq_capacity_) + { + ILOG("FTQ receives first PredictionOutput from BPU"); + fetch_target_queue_.push_back(prediction); + + if(fetch_target_queue_.size() == 1) { + ftq_it = fetch_target_queue_.begin(); + } + sendPrediction_(); + } + } + + void FTQ::getSecondPrediction_(const BranchPredictor::PredictionOutput & prediction) + { + // check if it matches the prediction made by first tier of bpu + ILOG("FTQ receives second PredictionOutput from BPU"); + handleMismatch(prediction); + } + + #define TAKEN 0 + #define NOT_TAKEN 1 + + void FTQ::handleMismatch(const BranchPredictor::PredictionOutput & tage_prediction) + { + ILOG("Checking mismatch between BasePredictor and TAGE_SC_L"); + + std::deque :: iterator it = fetch_target_queue_.begin(); + while(it != fetch_target_queue_.end()) { + if(it->instrPC == tage_prediction.instrPC) { + ILOG("BasePredictor prediction direction: " << it->predDirection_); + ILOG("TAGE_SC_L prediction direction: " << tage_prediction.predDirection_); + if(it->predDirection_ != tage_prediction.predDirection_) { + ILOG("Prediction mismatch between tage and base predictor"); + + /*** + * TODO: + * We need to update predPC_ in case there is an update of output + * How to do it in case a not taken prediction is updated to taken? + */ + if(it->predDirection_ == TAKEN && tage_prediction.predDirection_ == NOT_TAKEN) { + it->predPC_ = it->instrPC + 4; + } + else if(it->predDirection_ == NOT_TAKEN && tage_prediction.predDirection_ == TAKEN) { + // ?? + } + + it->predDirection_ = tage_prediction.predDirection_; + + ftq_it = it; + + sendPrediction_(); + break; + } + } + } + } + + void FTQ::getFetchCredits_(const uint32_t & credits) + { + ILOG("FTQ: Received " << credits << " credits from Fetch") + fetch_credits_ += credits; + + sendPrediction_(); + } + + void FTQ::sendPrediction_() + { + if (fetch_credits_ > 0) + { + // send prediction to Fetch + if (fetch_target_queue_.size() > 0) + { + fetch_credits_--; + ILOG("Send prediction from FTQ to Fetch"); + auto output = *ftq_it; + ftq_it++; + + out_fetch_prediction_output_.send(output); + } + } + } + + void FTQ::firstMispredictionFlush_() {} + + void FTQ::getROBSignal_(const uint32_t & signal) {} + + void FTQ::deallocateEntry_() {} +} // namespace olympia \ No newline at end of file diff --git a/core/FTQ.hpp b/core/FTQ.hpp new file mode 100644 index 00000000..7a5f8542 --- /dev/null +++ b/core/FTQ.hpp @@ -0,0 +1,88 @@ +#pragma once + +#include "sparta/ports/DataPort.hpp" +#include "sparta/events/UniqueEvent.hpp" +#include "sparta/simulation/Unit.hpp" +#include "sparta/simulation/ParameterSet.hpp" +#include "sparta/simulation/TreeNode.hpp" +#include "sparta/log/MessageSource.hpp" +#include "sparta/utils/DataContainer.hpp" + +#include "fetch/BPU.hpp" +#include "ROB.hpp" +#include "FlushManager.hpp" + +#include + +namespace olympia +{ + class FTQ : public sparta::Unit + { + public: + class FTQParameterSet : public sparta::ParameterSet + { + public: + FTQParameterSet(sparta::TreeNode* n) : sparta::ParameterSet(n) {} + + // for now set default to 10, chnage it later + PARAMETER(uint32_t, ftq_capacity, 10, "Capacity of fetch target queue") + }; + + static const char* name; + + FTQ(sparta::TreeNode* node, const FTQParameterSet* p); + + ~FTQ(); + + private: + const uint32_t ftq_capacity_; + + uint32_t fetch_credits_ = 0; + std::deque fetch_target_queue_; + + // Iterator pointing to next element to send further + std::deque :: iterator ftq_it; + + sparta::DataInPort in_bpu_first_prediction_output_{ + &unit_port_set_, "in_bpu_first_prediction_output", 1}; + sparta::DataInPort in_bpu_second_prediction_output_{ + &unit_port_set_, "in_bpu_second_prediction_output", 1}; + sparta::DataInPort in_fetch_credits_{&unit_port_set_, "in_fetch_credits", 1}; + /**sparta::DataOutPort out_first_misprediction_flush_ + {&unit_port_set_, + "out_first_misprediction_flush", 1};***/ + sparta::DataOutPort out_fetch_prediction_output_{ + &unit_port_set_, "out_fetch_prediction_output", 1}; + /***sparta::DataInPort in_rob_signal_ {&unit_port_set_, "in_rob_signal", 1}; ***/ + sparta::DataOutPort out_bpu_update_input_{ + &unit_port_set_, "out_bpu_update_input", 1}; + sparta::DataOutPort out_bpu_credits_{&unit_port_set_, "out_bpu_credits", 1}; + + void sendInitialCreditsToBPU_(); + + void sendCreditsToBPU_(const uint32_t &); + + // receives prediction from BasePredictor and pushes it into FTQ + void getFirstPrediction_(const BranchPredictor::PredictionOutput &); + + // receives prediction from TAGE_SC_L, checks if there's a mismatch + // updates ftq appropriately + void getSecondPrediction_(const BranchPredictor::PredictionOutput &); + + void handleMismatch(const BranchPredictor::PredictionOutput &); + + void getFetchCredits_(const uint32_t &); + + // continuously send instructions to fetch/icache + void sendPrediction_(); + + // flushes instruction if first prediction does not matvh second prediction + void firstMispredictionFlush_(); + + // receives branch resolution signal from ROB at the time of commit + void getROBSignal_(const uint32_t & signal); + + // deallocate FTQ entry once branch instruction is committed + void deallocateEntry_(); + }; +} // namespace olympia \ No newline at end of file diff --git a/core/fetch/BPU.cpp b/core/fetch/BPU.cpp new file mode 100644 index 00000000..1934d536 --- /dev/null +++ b/core/fetch/BPU.cpp @@ -0,0 +1,165 @@ + +#include "fetch/BPU.hpp" + +namespace olympia +{ + namespace BranchPredictor + { + const char* BPU::name = "bpu"; + + BPU::BPU(sparta::TreeNode* node, const BPUParameterSet* p) : + sparta::Unit(node), + ghr_size_(p->ghr_size), + ghr_hash_bits_(p->ghr_hash_bits), + pht_size_(p->pht_size), + ctr_bits_(p->ctr_bits), + btb_size_(p->btb_size), + ras_size_(p->ras_size), + ras_enable_overwrite_(p->ras_enable_overwrite), + tage_bim_table_size_(p->tage_bim_table_size), + tage_bim_ctr_bits_(p->tage_bim_ctr_bits), + tage_tagged_table_num_(p->tage_tagged_table_num), + logical_table_num_(p->logical_table_num), + loop_pred_table_size_(p->loop_pred_table_size), + loop_pred_table_way_(p->loop_pred_table_way), + base_predictor_(pht_size_, ctr_bits_, btb_size_, ras_size_, ras_enable_overwrite_), + tage_predictor_(tage_bim_table_size_, tage_bim_ctr_bits_, /*5,*/ 2, 3, 10, 2, 2, 1024, + tage_tagged_table_num_, 10) + { + sparta::StartupEvent(node, CREATE_SPARTA_HANDLER(BPU, sendIntitialCreditsToFetch_)); + + in_fetch_prediction_request_.registerConsumerHandler( + CREATE_SPARTA_HANDLER_WITH_DATA(BPU, getPredictionRequest_, PredictionRequest)); + + in_ftq_credits_.registerConsumerHandler( + CREATE_SPARTA_HANDLER_WITH_DATA(BPU, getCreditsFromFTQ_, uint32_t)); + + in_ftq_update_input_.registerConsumerHandler( + CREATE_SPARTA_HANDLER_WITH_DATA(BPU, getUpdateInput_, UpdateInput)); + } + + BPU::~BPU() {} + + PredictionOutput BPU::getPrediction(const PredictionRequest &) + { + PredictionOutput output; + output.predDirection_ = true; + output.predPC_ = 5; + return output; + } + + void BPU::updatePredictor(const UpdateInput &) {} + + void BPU::getPredictionRequest_(const PredictionRequest & request) + { + predictionRequestBuffer_.push_back(request); + ILOG("BPU received PredictionRequest from Fetch"); + } + + /***void BPU::makePrediction_() { + ILOG("making prediction"); + std::cout << "making prediction\n"; + if(predictionRequestBuffer_.size() > ev_make_second_prediction_0) { + //auto input = predictionRequestBuffer_.front(); + predictionRequestBuffer_.pop_front(); + + // call base predictor on input + PredictionOutput output; + + output.predDirection_ = true; + output.predPC_ = 100; + generatedPredictionOutputBuffer_.push_back(output); + + sendFirstPrediction_(); + + // call tage_sc_l on input + } + }**/ + + void BPU::getCreditsFromFTQ_(const uint32_t & credits) + { + ftq_credits_ += credits; + ILOG("BPU received " << credits << " credits from FTQ"); + ev_send_first_prediction_.schedule(1); + ev_send_second_prediction_.schedule(4); + } + + void BPU::sendFirstPrediction_() + { + // take first PredictionRequest from buffer + if (ftq_credits_ > 0 && predictionRequestBuffer_.size() > 0) + { + PredictionOutput output; + + PredictionRequest in = predictionRequestBuffer_.front(); + + ILOG("Getting direction from base predictor"); + bool dir = base_predictor_.getDirection(in.PC_, in.instType_); + ILOG("Getting target from base predictor"); + uint64_t target = base_predictor_.getTarget(in.PC_, in.instType_); + + output.instrPC = in.PC_; + output.predPC_ = target; + output.predDirection_ = dir; + + generatedPredictionOutputBuffer_.push_back(output); + + auto firstPrediction = generatedPredictionOutputBuffer_.front(); + generatedPredictionOutputBuffer_.pop_front(); + ILOG("Sending first PredictionOutput from BPU to FTQ"); + out_ftq_first_prediction_output_.send(firstPrediction); + ftq_credits_--; + } + } + + void BPU::sendSecondPrediction_() + { + // send prediction made by TAGE_SC_L + PredictionOutput output; + + PredictionRequest in = predictionRequestBuffer_.front(); + + ILOG("Getting direction prediction from TAGE"); + output.instrPC = in.PC_; + output.predDirection_ = tage_predictor_.predict(in.PC_); + // TAGE only predicts whether branch will be taken or not, so predPC_ value will be ignored + output.predPC_ = 0; + ILOG("Sending second PredictionOutput from BPU to FTQ"); + out_ftq_second_prediction_output_.send(output); + } + + void BPU::getUpdateInput_(const UpdateInput & input) + { + // internal_update_input_ = input; + + ILOG("BPU received UpdateInput from FTQ"); + + // updateBPU_(internal_update_input_); + } + + /** + + void BPU::updateBPU_(const UpdateInput & input) { + + // Update internal state of BasePredictor according to UpdateInput received + //base_predictor_.update(input); + + // Update internal state of TAGE_SC_L according to UpdateInput received + // TODO + // tage_sc_l.update(input); + } + **/ + + void BPU::sendCreditsToFetch_(const uint32_t & credits) + { + ILOG("Send " << credits << " credits from BPU to Fetch"); + out_fetch_credits_.send(credits); + } + + void BPU::sendIntitialCreditsToFetch_() { sendCreditsToFetch_(pred_req_buffer_capacity_); } + + void BPU::updateGHRTaken_() {} + + void BPU::updateGHRNotTaken_() {} + } // namespace BranchPredictor +} // namespace olympia diff --git a/core/fetch/BPU.hpp b/core/fetch/BPU.hpp new file mode 100644 index 00000000..b4d4a67a --- /dev/null +++ b/core/fetch/BPU.hpp @@ -0,0 +1,239 @@ +#pragma once + +#include "sparta/ports/DataPort.hpp" +#include "sparta/events/SingleCycleUniqueEvent.hpp" +#include "sparta/simulation/Unit.hpp" +#include "sparta/simulation/TreeNode.hpp" +#include "sparta/simulation/ParameterSet.hpp" +#include "sparta/utils/LogUtils.hpp" + +#include "BranchPredIF.hpp" +#include "BasePredictor.hpp" +#include "TAGE_SC_L.hpp" + +#include + +namespace olympia +{ + namespace BranchPredictor + { + class PredictionRequest + { + public: + PredictionRequest() {} + + uint64_t PC_; + uint8_t instType_; + + friend std::ostream & operator<<(std::ostream & os, const PredictionRequest &) + { + return os; + } + }; + + class PredictionOutput + { + public: + PredictionOutput() {} + + // PC of instruction for which PredictionOutput is generated + // this can be used as an index to find mismatch and update prediction + // between BasePredictor and TAGE_SC_L. + uint64_t instrPC; + bool predDirection_; + uint64_t predPC_; + + friend std::ostream & operator<<(std::ostream & os, const PredictionOutput &) + { + return os; + } + }; + + class UpdateInput + { + public: + UpdateInput() {} + + // UpdateInput(uint64_t instrPC, bool correctedDirection, uint64_t correctedTargetPC) {} + + uint64_t instrPC_; + bool correctedDirection_; + uint64_t correctedTargetPC_; + + friend std::ostream & operator<<(std::ostream & os, const UpdateInput &) { return os; } + }; + + class BPU : + public BranchPredictorIF, + public sparta::Unit + { + public: + class BPUParameterSet : public sparta::ParameterSet + { + public: + BPUParameterSet(sparta::TreeNode* n) : sparta::ParameterSet(n) {} + + // TODO: choose default values properly + // currently values were chosen randomly + PARAMETER(uint32_t, ghr_size, 1024, "Number of branch history bits stored in GHR"); + PARAMETER(uint32_t, ghr_hash_bits, 4, + "Number of bits from GHR used for hashing with PC, to index PHT") + PARAMETER(uint32_t, pht_size, 1024, "Number of entries stored in PHT") + PARAMETER(uint32_t, ctr_bits, 8, + "Number of bits used by counter in PHT to make prediction") + PARAMETER(uint32_t, btb_size, 512, "Maximum possible number of entries in BTB") + PARAMETER(uint32_t, ras_size, 128, "Maximum possible number of entries in RAS") + PARAMETER(bool, ras_enable_overwrite, true, + "New entries on maximum capacity overwrite") + PARAMETER(uint32_t, tage_bim_table_size, 1024, "Size of TAGE bimodal table") + PARAMETER(uint32_t, tage_bim_ctr_bits, 8, + "Number of bits used by TAGE bimodal table to make prediction") + PARAMETER(uint32_t, tage_tagged_table_num, 6, + "Number of tagged components in TAGE predictor") + PARAMETER(uint32_t, logical_table_num, 8, "Number of logical table in SC") + PARAMETER(uint32_t, loop_pred_table_size, 64, + "Maximum possible entries in loop predictor table") + PARAMETER(uint32_t, loop_pred_table_way, 4, "Way size of loop predictor table") + }; + + BPU(sparta::TreeNode* node, const BPUParameterSet* p); + + ~BPU(); + + PredictionOutput getPrediction(const PredictionRequest &); + void updatePredictor(const UpdateInput &); + + //! \brief Name of this resource. Required by sparta::UnitFactory + static const char* name; + + private: + void getPredictionRequest_(const PredictionRequest &); + void makePrediction_(); + void getCreditsFromFTQ_(const uint32_t &); + void sendFirstPrediction_(); + void sendSecondPrediction_(); + void getUpdateInput_(const UpdateInput &); + void updateBPU_(const UpdateInput &); + void sendCreditsToFetch_(const uint32_t &); + void sendIntitialCreditsToFetch_(); + void updateGHRTaken_(); + void updateGHRNotTaken_(); + + const uint32_t ghr_size_; + const uint32_t ghr_hash_bits_; + const uint32_t pht_size_; + const uint32_t ctr_bits_; + const uint32_t btb_size_; + const uint32_t ras_size_; + const bool ras_enable_overwrite_; + const uint32_t tage_bim_table_size_; + const uint32_t tage_bim_ctr_bits_; + const uint32_t tage_tagged_table_num_; + const uint32_t logical_table_num_; + const uint32_t loop_pred_table_size_; + const uint32_t loop_pred_table_way_; + + std::list predictionRequestBuffer_; + std::list generatedPredictionOutputBuffer_; + const uint32_t pred_req_buffer_capacity_ = 10; + + uint32_t ftq_credits_ = 0; + // UpdateInput internal_update_input_; + + BasePredictor base_predictor_; + Tage tage_predictor_; + + /////////////////////////////////////////////////////////////////////////////// + // Ports + /////////////////////////////////////////////////////////////////////////////// + + // Internal DataInPort from Fetch unit for PredictionRequest + sparta::DataInPort in_fetch_prediction_request_{ + &unit_port_set_, "in_fetch_prediction_request", sparta::SchedulingPhase::Tick, 0}; + + // Internal DataInPort from FTQ unit for credits to indicate + // availabilty of slots for sending PredictionOutput + sparta::DataInPort in_ftq_credits_{&unit_port_set_, "in_ftq_credits", + sparta::SchedulingPhase::Tick, 0}; + // Internal DataInPort from FTQ for UpdateInput + sparta::DataInPort in_ftq_update_input_{ + &unit_port_set_, "in_ftq_update_input", sparta::SchedulingPhase::Tick, 0}; + + // DataOutPort to Fetch unit to send credits to indicate availability + // of slots to receive PredictionRequest + sparta::DataOutPort out_fetch_credits_{&unit_port_set_, "out_fetch_credits"}; + + // DataOutPort to FTQ unit to send prediction made by BasePredictor + sparta::DataOutPort out_ftq_first_prediction_output_{ + &unit_port_set_, "out_ftq_first_prediction_output"}; + + // DataOutPort to FTQ unit to send prediction made by TAGE_SC_L + sparta::DataOutPort out_ftq_second_prediction_output_{ + &unit_port_set_, "out_ftq_second_prediction_output"}; + + ////////////////////////////////////////////////////////////////////////////// + // Events + ////////////////////////////////////////////////////////////////////////////// + /***sparta::PayloadEvent ev_sendPrediction_{ + &unit_event_set_, "ev_sendPrediction", + CREATE_SPARTA_HANDLER_WITH_DATA(BPU, sendPrediction_, PredictionOutput)}; + ***/ + + sparta::UniqueEvent<> ev_send_first_prediction_{ + &unit_event_set_, "ev_send_first_prediction_", + CREATE_SPARTA_HANDLER(BPU, sendFirstPrediction_)}; + + sparta::UniqueEvent<> ev_send_second_prediction_{ + &unit_event_set_, "ev_send_second_prediction_", + CREATE_SPARTA_HANDLER(BPU, sendSecondPrediction_)}; + + ////////////////////////////////////////////////////////////////////////////// + // Counters + ////////////////////////////////////////////////////////////////////////////// + sparta::Counter pred_req_num_{getStatisticSet(), "pred_req_num", + "Number of prediction requests", + sparta::Counter::COUNT_NORMAL}; + sparta::Counter mispred_num_{getStatisticSet(), "mispred_num", + "Number of mis-predictions", + sparta::Counter::COUNT_NORMAL}; + sparta::StatisticDef mispred_ratio_{getStatisticSet(), "mispred_ratio", + "Percenatge of mis-prediction", getStatisticSet(), + "mispred_num/pred_req_num"}; + sparta::Counter branch_req_num_{getStatisticSet(), "branch_req_num", + "Number of branch requests", + sparta::Counter::COUNT_NORMAL}; + sparta::Counter call_req_num_{getStatisticSet(), "call_req_num", + "Number of call requests", sparta::Counter::COUNT_NORMAL}; + sparta::Counter return_req_num_{getStatisticSet(), "return_req_num", + "Number of return requests", + sparta::Counter::COUNT_NORMAL}; + sparta::Counter pht_req_num_{getStatisticSet(), "pht_req_num", + "Number of requests made to PHT", + sparta::Counter::COUNT_NORMAL}; + sparta::Counter pht_hit_num_{getStatisticSet(), "pht_hit_num", "Number of hits on PHT", + sparta::Counter::COUNT_NORMAL}; + sparta::Counter pht_miss_num_{getStatisticSet(), "pht_miss_num", + "Number of misses on PHT", sparta::Counter::COUNT_NORMAL}; + sparta::StatisticDef pht_mispred_ratio_{getStatisticSet(), "pht_mispred_ratio", + "Percentage of PHT mis-prediction", + getStatisticSet(), "pht_miss_num/pht_req_num"}; + sparta::Counter btb_req_num_{getStatisticSet(), "btb_req_num", + "Number of requests to BTB", + sparta::Counter::COUNT_NORMAL}; + sparta::Counter btb_hit_num_{getStatisticSet(), "btb_hit_num", "NUmber of BTB hits", + sparta::Counter::COUNT_NORMAL}; + sparta::Counter btb_miss_num_{getStatisticSet(), "btb_miss_num", "Number of BTB misses", + sparta::Counter::COUNT_NORMAL}; + sparta::StatisticDef btb_hit_rate_{getStatisticSet(), "btb_hit_rate", + "Rate of BTB hits", getStatisticSet(), + "btb_hit_num/btb_req_num"}; + sparta::StatisticDef btb_miss_rate_{getStatisticSet(), "btb_miss_rate", + "Rate of BTB misses", getStatisticSet(), + "btb_miss_num/btb_req_num"}; + sparta::Counter ras_high_mark_{getStatisticSet(), "ras_high_mark", "RAS high mark", + sparta::Counter::COUNT_NORMAL}; + sparta::Counter ras_low_mark_{getStatisticSet(), "ras_low_mark", "RAS low mark", + sparta::Counter::COUNT_NORMAL}; + }; + } // namespace BranchPredictor +} // namespace olympia diff --git a/core/fetch/BasePredictor.cpp b/core/fetch/BasePredictor.cpp new file mode 100644 index 00000000..0eb46f17 --- /dev/null +++ b/core/fetch/BasePredictor.cpp @@ -0,0 +1,245 @@ +#include "BasePredictor.hpp" + +#define CALL 0 +#define RET 1 +#define BRANCH 2 + +namespace olympia +{ + namespace BranchPredictor + { + BasePredictor::BasePredictor(const uint32_t & pht_size, const uint8_t & pht_ctr_bits, const uint32_t & btb_size, + const uint32_t & ras_size, const bool & ras_enable_overwrite) : + pht_size_(pht_size), + pht_ctr_bits_(pht_ctr_bits), + pht_ctr_max_val_(1 << pht_ctr_bits_), + btb_size_(btb_size), + ras_size_(ras_size), + ras_enable_overwrite_(ras_enable_overwrite) + { + for (uint32_t i = 0; i < pht_size_; i++) + { + pattern_history_table_[i] = 0; + } + for (uint32_t i = 0; i < btb_size; i++) + { + branch_target_buffer_[i] = 0; + } + } + + bool BasePredictor::getDirection(const uint64_t & PC, const uint8_t & instType) + { + std::cout << "ggggetting direction from base predictor\n"; + // branch taken or not-taken? + + // hardcoding values for each type for now + // instType_ = 0 -> call + // instType_ = 1 -> ret + // instType_ = 2 -> conditional branch + + // if instruction type is call/jump or ret branch is always taken + if (instType == 0 || instType == 1) + { + return true; + } + else + { + return branchTaken(PC); + } + } + + uint64_t BasePredictor::getTarget(const uint64_t & PC, const uint8_t & instType) + { + std::cout << "gggggetting pc from base predictor\n"; + // target PC + + // hardcoding values for each type for now + // instType_ = 0 -> call + // instType_ = 1 -> ret + // instType_ = 2 -> conditional branch + + // if call -> instruction then save current pc to ctr_max_val_RAS + // if ret -> pop pc from RAS + // if conditonal branch -> use BTB + uint64_t targetPC = 0; + if (instType == 0) + { + pushAddress(PC); + if (isHit(PC)) + { + targetPC = getTargetPC(PC, instType); + } + else + { + targetPC = PC + 8; + } + } + else if (instType == RET) + { + targetPC = popAddress(); + } + else + { + targetPC = getTargetPC(PC, instType); + } + return targetPC; + } + + // done + void BasePredictor::incrementCtr(const uint32_t & idx) + { + if (pattern_history_table_.find(idx) != pattern_history_table_.end()) + { + if (pattern_history_table_[idx] < pht_ctr_max_val_) + { + pattern_history_table_[idx]++; + } + } + } + + // done + void BasePredictor::decrementCtr(const uint32_t & idx) + { + if (pattern_history_table_.find(idx) != pattern_history_table_.end()) + { + if (pattern_history_table_[idx] > 0) + { + pattern_history_table_[idx]--; + } + } + } + + // done + uint8_t BasePredictor::getCtr(const uint32_t & idx) + { + if (pattern_history_table_.find(idx) != pattern_history_table_.end()) + { + return pattern_history_table_[idx]; + } + else + { + return 0; + } + } + + // done + bool BasePredictor::branchTaken(const uint32_t & idx) + { + uint8_t ctr = getCtr(idx); + if (ctr > pht_ctr_max_val_ / 2) + { + return true; + } + else + { + return false; + } + } + + // done + bool BasePredictor::addEntry(const uint64_t & PC, const uint64_t & targetPC) + { + if (branch_target_buffer_.size() < btb_size_) + { + branch_target_buffer_[PC] = targetPC; + return true; + } + return false; + } + + // done + bool BasePredictor::isHit(const uint64_t & PC) + { + if (branch_target_buffer_.find(PC) != branch_target_buffer_.end()) + { + return true; + } + else + { + return false; + } + } + + // i think good enough for review + uint64_t BasePredictor::getTargetPC(const uint64_t & PC, const uint8_t & instType) + { + uint64_t targetPC = PC + 8; + if (isHit(PC)) + { + if (instType == CALL) + { + targetPC = branch_target_buffer_[PC]; + } + else if (instType == BRANCH) + { + if (branchTaken(PC)) + { + targetPC = branch_target_buffer_[PC]; + } + else + { + branch_target_buffer_.erase(PC); + targetPC = PC + 8; + } + } + } + else + { + if (instType == CALL) + { + // TODO: put something random and + // rely on update to correct it later? + addEntry(PC, PC + 8); + targetPC = PC + 8; + } + else if (instType == BRANCH) + { + if (branchTaken(PC)) + { + // TODO: put something random and + // rely on update to correct it later? + addEntry(PC, PC + 8); + } + else + { + targetPC = PC + 8; + } + } + } + return targetPC; + } + + // i think done + void BasePredictor::pushAddress(const uint64_t & PC) + { + if (return_address_stack_.size() < ras_size_) + { + return_address_stack_.push_front(PC); + } + else if (ras_enable_overwrite_) + { + // oldest entry of RAS is overwritten to make space for new entry + return_address_stack_.pop_back(); + return_address_stack_.push_front(PC); + } + else + { + return; + } + } + + // i think done for now with TODO left + uint64_t BasePredictor::popAddress() + { + if (return_address_stack_.size() > 0) + { + uint64_t address = return_address_stack_.front(); + return_address_stack_.pop_front(); + return address; + } + // TODO: what to return when ras is empty but popAddress() is + // inocrrectly called? + return 0; + } + } // namespace BranchPredictor +} // namespace olympia \ No newline at end of file diff --git a/core/fetch/BasePredictor.hpp b/core/fetch/BasePredictor.hpp new file mode 100644 index 00000000..41aedef2 --- /dev/null +++ b/core/fetch/BasePredictor.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +namespace olympia +{ + namespace BranchPredictor + { + class BasePredictor + { + public: + BasePredictor(const uint32_t & pht_size, const uint8_t & pht_ctr_bits, const uint32_t & btb_size, + const uint32_t & ras_size, const bool & ras_enable_overwrite); + + bool getDirection(const uint64_t &, const uint8_t &); + uint64_t getTarget(const uint64_t &, const uint8_t &); + + // PHT + void incrementCtr(const uint32_t & idx); + void decrementCtr(const uint32_t & idx); + uint8_t getCtr(const uint32_t & idx); + bool branchTaken(const uint32_t & idx); + + // BTB + bool addEntry(const uint64_t & PC, const uint64_t & targetPC); + bool isHit(const uint64_t & PC); + uint64_t getTargetPC(const uint64_t & PC, const uint8_t & instType); + + // RAS + void pushAddress(const uint64_t & PC); + uint64_t popAddress(); + + private: + const uint32_t pht_size_; + const uint8_t pht_ctr_bits_; + const uint8_t pht_ctr_max_val_; + const uint32_t btb_size_; + const uint32_t ras_size_; + // Boolean flag to set whether newer entries to RAS on maximum + // capacity should overwrite or not. + const bool ras_enable_overwrite_; + + std::map pattern_history_table_; + std::map branch_target_buffer_; + std::deque return_address_stack_; + }; + } // namespace BranchPredictor +} // namespace olympia \ No newline at end of file diff --git a/core/fetch/CMakeLists.txt b/core/fetch/CMakeLists.txt index c1ba38a0..76e2651a 100644 --- a/core/fetch/CMakeLists.txt +++ b/core/fetch/CMakeLists.txt @@ -2,5 +2,8 @@ add_library(fetch Fetch.cpp ICache.cpp SimpleBranchPred.cpp + BasePredictor.cpp + TAGE_SC_L.cpp + BPU.cpp ) target_link_libraries(fetch instgen) diff --git a/core/fetch/TAGE_SC_L.cpp b/core/fetch/TAGE_SC_L.cpp new file mode 100644 index 00000000..c6d7035e --- /dev/null +++ b/core/fetch/TAGE_SC_L.cpp @@ -0,0 +1,204 @@ +#include "TAGE_SC_L.hpp" +#include +#include + +namespace olympia +{ + namespace BranchPredictor + { + + // Tage Tagged Component Entry + TageTaggedComponentEntry::TageTaggedComponentEntry(const uint8_t & tage_ctr_bits, + const uint8_t & tage_useful_bits, + const uint8_t & ctr_initial, + const uint8_t & useful_initial) : + tage_ctr_bits_(tage_ctr_bits), + tage_useful_bits_(tage_useful_bits), + tage_ctr_max_val_(1 << tage_ctr_bits_), + tage_useful_max_val_(1 << tage_useful_bits_), + ctr_(ctr_initial), + useful_(useful_initial) + { + } + + void TageTaggedComponentEntry::incrementCtr() + { + if(ctr_ < tage_ctr_max_val_) { + ctr_++; + } + } + + void TageTaggedComponentEntry::decrementCtr() + { + if(ctr_ > 0) { + ctr_--; + } + } + + uint8_t TageTaggedComponentEntry::getCtr() { return ctr_; } + + void TageTaggedComponentEntry::incrementUseful() + { + if(useful_ < tage_useful_max_val_) { + useful_++; + } + } + + void TageTaggedComponentEntry::decrementUseful() + { + if(useful_ > 0) { + useful_--; + } + } + + void TageTaggedComponentEntry::resetUseful() + { + useful_ >>= 1; + } + + uint8_t TageTaggedComponentEntry::getUseful() { return useful_; } + + // Tagged component + TageTaggedComponent::TageTaggedComponent(const uint8_t & tage_ctr_bits, const uint8_t & tage_useful_bits, + const uint16_t & num_tagged_entry) : + tage_ctr_bits_(tage_ctr_bits), + tage_useful_bits_(tage_useful_bits), + num_tagged_entry_(num_tagged_entry), + tage_tagged_component_(num_tagged_entry_, {tage_ctr_bits_, tage_useful_bits_, 0, 1}) + { + } + + TageTaggedComponentEntry TageTaggedComponent::getTageComponentEntryAtIndex(const uint16_t & index) + { + return tage_tagged_component_[index]; + } + + // Tage Bimodal + TageBIM::TageBIM(const uint32_t & tage_bim_table_size, const uint8_t & tage_base_ctr_bits) : + tage_bim_table_size_(tage_bim_table_size), + tage_bim_ctr_bits_(tage_base_ctr_bits), + tage_bimodal_(tage_bim_table_size_) + { + tage_bim_max_ctr_ = 1 << tage_bim_ctr_bits_; + + // initialize counter at all index to be 0 + for (auto & val : tage_bimodal_) + { + val = 0; + } + } + + void TageBIM::incrementCtr(const uint32_t & ip) + { + if (tage_bimodal_[ip] < tage_bim_max_ctr_) + { + tage_bimodal_[ip]++; + } + } + + void TageBIM::decrementCtr(const uint32_t & ip) + { + if (tage_bimodal_[ip] > 0) + { + tage_bimodal_[ip]--; + } + } + + uint8_t TageBIM::getPrediction(const uint32_t & ip) { return tage_bimodal_[ip]; } + + // TAGE + Tage::Tage(const uint32_t & tage_bim_table_size, const uint8_t & tage_bim_ctr_bits, + //const uint16_t & tage_max_index_bits, + const uint8_t & tage_tagged_ctr_bits, + const uint8_t & tage_tagged_useful_bits, const uint32_t & tage_global_history_len, + const uint32_t & tage_min_hist_len, const uint8_t & tage_hist_alpha, + const uint32_t & tage_reset_useful_interval, const uint8_t & tage_num_component, + const uint16_t & tage_tagged_component_entry_num) : + tage_bim_table_size_(tage_bim_table_size), + tage_bim_ctr_bits_(tage_bim_ctr_bits), + //tage_max_index_bits_(tage_max_index_bits), + tage_tagged_ctr_bits_(tage_tagged_ctr_bits), + tage_tagged_useful_bits_(tage_tagged_useful_bits), + tage_global_history_len_(tage_global_history_len), + tage_min_hist_len_(tage_min_hist_len), + tage_hist_alpha_(tage_hist_alpha), + tage_reset_useful_interval_(tage_reset_useful_interval), + tage_num_component_(tage_num_component), + tage_tagged_component_entry_num_(tage_tagged_component_entry_num), + tage_bim_(tage_bim_table_size_, tage_bim_ctr_bits_), + tage_tagged_components_(tage_num_component_, + {tage_tagged_ctr_bits_, tage_tagged_useful_bits_, tage_tagged_component_entry_num_}), + tage_global_history_(tage_global_history_len_, 0) + { + } + + uint64_t Tage::compressed_ghr(const uint8_t & req_length) + { + uint64_t resultant_ghr = 0; + // if req_length is more than tage_global_history_len_ then return whole tage_global_history_ + uint8_t length = (req_length > tage_global_history_len_) ? tage_global_history_len_ : req_length; + for(uint8_t idx = 0; idx < length; idx++) { + resultant_ghr = (resultant_ghr << 1) | tage_global_history_[tage_global_history_len_ - idx - 1]; + } + return resultant_ghr; + } + + uint32_t Tage::hashAddr(const uint64_t & PC, const uint8_t & component_number) + { + uint8_t local_hist_len = tage_min_hist_len_ * pow(tage_hist_alpha_, component_number - 1); + uint64_t effective_hist = compressed_ghr(local_hist_len); + uint32_t hashedValue = (uint32_t) (PC ^ effective_hist); + return hashedValue; + } + + uint16_t Tage::calculatedTag(const uint64_t & PC, const uint8_t & component_number) + { + uint8_t local_hist_len = tage_min_hist_len_ * pow(tage_hist_alpha_, component_number - 1); + uint64_t effective_hist = compressed_ghr(local_hist_len); + uint16_t calculated_tag = (uint16_t) (PC ^ effective_hist); + return calculated_tag; + } + + void Tage::updateUsefulBits() + { + for(auto & tage_table : tage_tagged_components_) + { + for(auto & entry : tage_table.tage_tagged_component_) + { + entry.resetUseful(); + } + } + } + + uint8_t Tage::predict(const uint64_t & ip) + { + if(reset_counter < tage_reset_useful_interval_) + { + reset_counter++; + } + else if(reset_counter == tage_reset_useful_interval_) + { + reset_counter = 0; + updateUsefulBits(); + } + + uint8_t default_prediction = tage_bim_.getPrediction(ip); + uint8_t num_counter = 1; + uint8_t tage_prediction = default_prediction; + for (auto tage_i : tage_tagged_components_) + { + uint64_t effective_addr = hashAddr(ip, num_counter); + TageTaggedComponentEntry tage_entry = + tage_i.getTageComponentEntryAtIndex(effective_addr); + + // Tag match + if (tage_entry.tag == calculatedTag(ip, num_counter)) + { + tage_prediction = tage_entry.getCtr(); + } + num_counter++; + } + return tage_prediction; + } + } // namespace BranchPredictor +} // namespace olympia \ No newline at end of file diff --git a/core/fetch/TAGE_SC_L.hpp b/core/fetch/TAGE_SC_L.hpp new file mode 100644 index 00000000..e3adf564 --- /dev/null +++ b/core/fetch/TAGE_SC_L.hpp @@ -0,0 +1,133 @@ +#pragma once + +#include +#include +#include + +namespace olympia +{ + namespace BranchPredictor + { + class TageTaggedComponentEntry + { + public: + TageTaggedComponentEntry(const uint8_t & tage_ctr_bits, const uint8_t & tage_useful_bits, + const uint8_t & ctr_initial, const uint8_t & useful_initial); + + void incrementCtr(); + void decrementCtr(); + uint8_t getCtr(); + void incrementUseful(); + void decrementUseful(); + void resetUseful(); + uint8_t getUseful(); + + uint16_t tag; + + private: + const uint8_t tage_ctr_bits_; + const uint8_t tage_useful_bits_; + const uint8_t tage_ctr_max_val_; + const uint8_t tage_useful_max_val_; + + uint8_t ctr_; + uint8_t useful_; + }; + + class TageTaggedComponent + { + public: + TageTaggedComponent(const uint8_t & tage_ctr_bits, const uint8_t & tage_useful_bits, + const uint16_t & num_tagged_entry); + + TageTaggedComponentEntry getTageComponentEntryAtIndex(const uint16_t & index); + + //private: + const uint8_t tage_ctr_bits_; + const uint8_t tage_useful_bits_; + const uint16_t num_tagged_entry_; + std::vector tage_tagged_component_; + }; + + class TageBIM + { + public: + TageBIM(const uint32_t & tage_bim_table_size, const uint8_t & tage_base_ctr_bits); + + void incrementCtr(const uint32_t & ip); + void decrementCtr(const uint32_t & ip); + uint8_t getPrediction(const uint32_t & ip); + + private: + const uint32_t tage_bim_table_size_; + const uint8_t tage_bim_ctr_bits_; + uint8_t tage_bim_max_ctr_; + std::vector tage_bimodal_; + }; + + class Tage + { + public: + Tage(const uint32_t & tage_bim_table_size, const uint8_t & tage_bim_ctr_bits, + //const uint16_t & tage_max_index_bits, + const uint8_t & tage_tagged_ctr_bits, + const uint8_t & tage_tagged_useful_bits, const uint32_t & tage_global_history_len, + const uint32_t & tage_min_hist_len, const uint8_t & tage_hist_alpha, + const uint32_t & tage_reset_useful_interval, const uint8_t & tage_num_component, + const uint16_t & tage_tagged_component_entry_num); + + uint8_t predict(const uint64_t & ip); + + private: + const uint32_t tage_bim_table_size_; + const uint8_t tage_bim_ctr_bits_; + //uint16_t tage_max_index_bits_; + const uint8_t tage_tagged_ctr_bits_; + const uint8_t tage_tagged_useful_bits_; + const uint32_t tage_global_history_len_; + const uint32_t tage_min_hist_len_; + const uint8_t tage_hist_alpha_; + const uint32_t tage_reset_useful_interval_; + const uint8_t tage_num_component_; + const uint16_t tage_tagged_component_entry_num_; + TageBIM tage_bim_; + std::vector tage_tagged_components_; + std::vector tage_global_history_; + + uint32_t reset_counter = 0; + + // Helper function to compress tage ghr into a numeric + uint64_t compressed_ghr(const uint8_t & req_length); + // Helper function to calculate hash too index into tage table + uint32_t hashAddr(const uint64_t & PC, const uint8_t & component_number); + // Helper function to calcuate tag + uint16_t calculatedTag(const uint64_t & PC, const uint8_t & component_number); + + void updateUsefulBits(); + }; + + /*** + * TODO + class StatisticalCorrector + { + public: + private: + }; + + class LoopPredictor + { + public: + private: + }; + + class TAGE_SC_L + { + public: + private: + Tage tage; + StatisticalCorrector sc; + LoopPredictor l; + }; + ***/ + } // namespace BranchPredictor +} // namespace olympia \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 29e7ddf8..bf6e8b6a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -37,4 +37,5 @@ add_subdirectory(core/icache) add_subdirectory(core/dcache) add_subdirectory(core/vector) add_subdirectory(core/unit_template) +add_subdirectory(core/bpu) add_subdirectory(fusion) diff --git a/test/core/bpu/BPUSink.hpp b/test/core/bpu/BPUSink.hpp new file mode 100644 index 00000000..bed86892 --- /dev/null +++ b/test/core/bpu/BPUSink.hpp @@ -0,0 +1,68 @@ +#pragma once + +#include "core/MemoryAccessInfo.hpp" +#include "sparta/simulation/TreeNode.hpp" +#include "sparta/ports/DataPort.hpp" +#include "sparta/events/SingleCycleUniqueEvent.hpp" +#include "sparta/utils/SpartaSharedPointer.hpp" +#include "sparta/utils/LogUtils.hpp" + +#include "../../../core/fetch/BPU.hpp" + +#include + +namespace bpu_test +{ + class BPUSink : public sparta::Unit + { + public: + static constexpr char name[] = "bpu_sink_unit"; + + class BPUSinkParameters : public sparta::ParameterSet + { + public: + BPUSinkParameters(sparta::TreeNode* n) : sparta::ParameterSet(n) {} + }; + + BPUSink(sparta::TreeNode* n, const BPUSinkParameters* params) : sparta::Unit(n) + { + sparta::StartupEvent(n, CREATE_SPARTA_HANDLER(BPUSink, sendInitialCreditsToFTQ_)); + + in_ftq_prediction_output_.registerConsumerHandler(CREATE_SPARTA_HANDLER_WITH_DATA( + BPUSink, getPredictionOutput_, olympia::BranchPredictor::PredictionOutput)); + } + + private: + std::list pred_output_buffer_; + uint32_t ftq_credits_ = 0; + + void sendCreditsToFTQ_(const uint32_t & credits) + { + ILOG("Send " << credits << " credits from Fetch to FTQ"); + out_ftq_credits_.send(credits); + } + + void sendInitialCreditsToFTQ_() { sendCreditsToFTQ_(5); } + + void getPredictionOutput_(const olympia::BranchPredictor::PredictionOutput & output) + { + ILOG("Fetch recieves prediction output from FTQ"); + pred_output_buffer_.push_back(output); + } + + //////////////////////////////////////////////////////////////////////////////// + // Ports + //////////////////////////////////////////////////////////////////////////////// + sparta::DataInPort in_ftq_prediction_output_{ + &unit_port_set_, "in_ftq_prediction_output", 0}; + + sparta::DataOutPort out_ftq_credits_{&unit_port_set_, "out_ftq_credits"}; + + /**sparta::UniqueEvent<> ev_return_credits_{ + &unit_event_set_, "return_credits", + CREATE_SPARTA_HANDLER(BPUSink, sendPredictionOutputCredits_)}; + ***/ + }; + + using SinkFactory = sparta::ResourceFactory; +} // namespace bpu_test \ No newline at end of file diff --git a/test/core/bpu/BPUSource.hpp b/test/core/bpu/BPUSource.hpp new file mode 100644 index 00000000..f560c2fb --- /dev/null +++ b/test/core/bpu/BPUSource.hpp @@ -0,0 +1,113 @@ +#pragma once + +#include "core/InstGenerator.hpp" +#include "core/decode/MavisUnit.hpp" +#include "core/InstGroup.hpp" +#include "mavis/ExtractorDirectInfo.h" +#include "core/MemoryAccessInfo.hpp" +#include "sparta/simulation/TreeNode.hpp" +#include "sparta/ports/DataPort.hpp" +#include "sparta/events/SingleCycleUniqueEvent.hpp" +#include "sparta/utils/SpartaSharedPointer.hpp" +#include "sparta/utils/LogUtils.hpp" + +#include "OlympiaAllocators.hpp" + +#include "../../../core/fetch/BPU.hpp" + +#include + +namespace bpu_test +{ + class BPUSource : public sparta::Unit + { + public: + static constexpr char name[] = "bpu_source_unit"; + + class BPUSourceParameters : public sparta::ParameterSet + { + public: + explicit BPUSourceParameters(sparta::TreeNode* n) : sparta::ParameterSet(n) {} + + PARAMETER(std::string, test_type, "single", "Test mode to run: single or multiple") + + PARAMETER(std::string, input_file, "", "Input file: STF or JSON") + }; + + BPUSource(sparta::TreeNode* n, const BPUSourceParameters* params) : + sparta::Unit(n), + test_type_(params->test_type), + mavis_facade_(olympia::getMavis(n)) + { + sparta_assert(mavis_facade_ != nullptr, "Could not find the Mavis Unit"); + in_bpu_credits_.registerConsumerHandler( + CREATE_SPARTA_HANDLER_WITH_DATA(BPUSource, getCreditsFromBPU_, uint32_t)); + + if (params->input_file != "") + { + inst_generator_ = olympia::InstGenerator::createGenerator( + mavis_facade_, params->input_file, false); + } + } + + private: + const std::string test_type_; + olympia::MavisType* mavis_facade_ = nullptr; + std::unique_ptr inst_generator_; + + uint32_t bpu_credits_ = 0; + + std::list pred_request_buffer_; + + const uint32_t pred_req_buffer_capacity = 8; + + void generatePredictionOutput_() { + while(pred_request_buffer_.size() < pred_req_buffer_capacity) { + olympia::BranchPredictor::PredictionRequest pred_request; + pred_request.instType_ = 1; + pred_request.PC_ = 5; + pred_request_buffer_.push_back(pred_request); + } + } + + void getCreditsFromBPU_(const uint32_t & credits) + { + bpu_credits_ += credits; + ILOG("Received " << credits << " credits from BPU"); + + generatePredictionOutput_(); + + //ev_send_pred_req_.schedule(sparta::Clock::Cycle(0)); + /**while(pred_request_buffer_.size() > 0) { + sendPredictionRequest_(); + //ev_send_pred_req_.schedule(sparta::Clock::Cycle(1)); + }**/ + sendPredictionRequest_(); + } + + void sendPredictionRequest_() + { + if (bpu_credits_ > 0) + { + ILOG("Sending PredictionRequest from Fetch to BPU"); + olympia::BranchPredictor::PredictionRequest req_to_send = pred_request_buffer_.front(); + pred_request_buffer_.pop_front(); + out_bpu_prediction_request_.send(req_to_send); + bpu_credits_--; + } + } + + //////////////////////////////////////////////////////////////////////////////// + // Ports + //////////////////////////////////////////////////////////////////////////////// + sparta::DataOutPort + out_bpu_prediction_request_{&unit_port_set_, "out_bpu_prediction_request"}; + + sparta::DataInPort in_bpu_credits_{&unit_port_set_, "in_bpu_credits", 0}; + + //sparta::Event<> ev_send_pred_req_{&unit_event_set_, "ev_send_pred_req_", + // CREATE_SPARTA_HANDLER(BPUSource, sendPredictionRequest_)}; + }; + + using SrcFactory = sparta::ResourceFactory; +} // namespace bpu_test \ No newline at end of file diff --git a/test/core/bpu/BPU_testbench.cpp b/test/core/bpu/BPU_testbench.cpp new file mode 100644 index 00000000..3bab3ccd --- /dev/null +++ b/test/core/bpu/BPU_testbench.cpp @@ -0,0 +1,255 @@ +#include "../../../core/fetch/BPU.hpp" +#include "../../../core/FTQ.hpp" +#include "decode/MavisUnit.hpp" +#include "OlympiaAllocators.hpp" +#include "BPUSink.hpp" +#include "BPUSource.hpp" + +#include "sparta/app/CommandLineSimulator.hpp" +#include "sparta/log/MessageSource.hpp" +#include "sparta/utils/SpartaTester.hpp" + +#include +#include + +#include + +using namespace std; + +TEST_INIT +olympia::InstAllocator inst_allocator(2000, 1000); + +// ---------------------------------------------------------------------- +// Testbench reference - example starting point for unit benches +// ---------------------------------------------------------------------- +class Simulator : public sparta::app::Simulation +{ + using BPUFactory = sparta::ResourceFactory; + + using FTQFactory = sparta::ResourceFactory; + + public: + Simulator(sparta::Scheduler* sched, const string & mavis_isa_files, + const string & mavis_uarch_files, const string & output_file, + const string & input_file) : + sparta::app::Simulation("Simulator", sched), + input_file_(input_file), + test_tap_(getRoot(), "info", output_file) + { + } + + virtual ~Simulator() { + std::cout << "Simulator destructor\n"; + getRoot()->enterTeardown(); + } + + void runRaw(uint64_t run_time) override final + { + (void)run_time; // ignored + + sparta::app::Simulation::runRaw(run_time); + } + + private: + void buildTree_() override + { + auto rtn = getRoot(); + + // Create the common allocators + allocators_tn_.reset(new olympia::OlympiaAllocators(rtn)); + + // Create a Mavis Unit + tns_to_delete_.emplace_back(new sparta::ResourceTreeNode( + rtn, olympia::MavisUnit::name, sparta::TreeNode::GROUP_NAME_NONE, + sparta::TreeNode::GROUP_IDX_NONE, "Mavis Unit", &mavis_fact)); + + // Create a Source Unit + sparta::ResourceTreeNode* src_unit = new sparta::ResourceTreeNode( + rtn, "src", sparta::TreeNode::GROUP_NAME_NONE, sparta::TreeNode::GROUP_IDX_NONE, + "Source Unit", &source_fact); + + tns_to_delete_.emplace_back(src_unit); + + //auto* src_params = src_unit->getParameterSet(); + //src_params->getParameter("input_file")->setValueFromString(input_file_); + + // Create the device under test + + // Create BPU + sparta::ResourceTreeNode* bpu = + new sparta::ResourceTreeNode(rtn, "bpu", sparta::TreeNode::GROUP_NAME_NONE, + sparta::TreeNode::GROUP_IDX_NONE, "BPU", &bpu_fact); + + tns_to_delete_.emplace_back(bpu); + + // Create FTQ + sparta::ResourceTreeNode* ftq = + new sparta::ResourceTreeNode(rtn, "ftq", sparta::TreeNode::GROUP_NAME_NONE, + sparta::TreeNode::GROUP_IDX_NONE, "FTQ", &ftq_fact); + + tns_to_delete_.emplace_back(ftq); + + // Create the Sink unit + sparta::ResourceTreeNode* sink = + new sparta::ResourceTreeNode(rtn, "sink", sparta::TreeNode::GROUP_NAME_NONE, + sparta::TreeNode::GROUP_IDX_NONE, "Sink Unit", &sink_fact); + + tns_to_delete_.emplace_back(sink); + + //auto* sink_params = sink->getParameterSet(); + //sink_params->getParameter("purpose")->setValueFromString("grp"); + } + + void configureTree_() override {} + + void bindTree_() override + { + auto* root_node = getRoot(); + + // See the README.md for A/B/Cx/Dx + // + // Credit is transferred from BPU to Source + sparta::bind( + root_node->getChildAs("bpu.ports.out_fetch_credits"), + root_node->getChildAs("src.ports.in_bpu_credits")); + + // Movement of PredictionRequest from Source to BPU + sparta::bind( + root_node->getChildAs("bpu.ports.in_fetch_prediction_request"), + root_node->getChildAs("src.ports.out_bpu_prediction_request")); + + // Credits is transferred from Sink to FTQ + sparta::bind( + root_node->getChildAs("ftq.ports.in_fetch_credits"), + root_node->getChildAs("sink.ports.out_ftq_credits")); + + // Movement of PredictionOutput from FTQ to Sink + sparta::bind( + root_node->getChildAs("ftq.ports.out_fetch_prediction_output"), + root_node->getChildAs("sink.ports.in_ftq_prediction_output")); + + // Binding BPU and FTQ + sparta::bind( + root_node->getChildAs("ftq.ports.out_bpu_update_input"), + root_node->getChildAs("bpu.ports.in_ftq_update_input")); + + sparta::bind( + root_node->getChildAs("ftq.ports.out_bpu_credits"), + root_node->getChildAs("bpu.ports.in_ftq_credits")); + + sparta::bind( + root_node->getChildAs("ftq.ports.in_bpu_first_prediction_output"), + root_node->getChildAs("bpu.ports.out_ftq_first_prediction_output")); + + sparta::bind( + root_node->getChildAs("ftq.ports.in_bpu_second_prediction_output"), + root_node->getChildAs("bpu.ports.out_ftq_second_prediction_output")); + + } + + // Allocators. Last thing to delete + std::unique_ptr allocators_tn_; + + olympia::MavisFactory mavis_fact; + BPUFactory bpu_fact; + FTQFactory ftq_fact; + + bpu_test::SrcFactory source_fact; + bpu_test::SinkFactory sink_fact; + + vector> tns_to_delete_; + vector exe_units_; + + const string input_file_; + sparta::log::Tap test_tap_; +}; + +const char USAGE[] = "Usage:\n\n" + "Testbench options \n" + " [ --input_file ] : json or stf input file\n" + " [ --output_file ] : output file for results checking\n" + " \n" + "Commonly used options \n" + " [-i insts] [-r RUNTIME] [--show-tree] [--show-dag]\n" + " [-p PATTERN VAL] [-c FILENAME]\n" + " [-l PATTERN CATEGORY DEST]\n" + " [-h,--help] \n" + "\n"; + +sparta::app::DefaultValues DEFAULTS; + +// ---------------------------------------------------------------- +// ---------------------------------------------------------------- +bool runTest(int argc, char** argv) +{ + DEFAULTS.auto_summary_default = "off"; + vector datafiles; + string input_file; + + sparta::app::CommandLineSimulator cls(USAGE, DEFAULTS); + auto & app_opts = cls.getApplicationOptions(); + + app_opts.add_options()("output_file", + sparta::app::named_value>("output_file", &datafiles), + "Specifies the output file") + + ("input_file", + sparta::app::named_value("INPUT_FILE", &input_file)->default_value(""), + "Provide a JSON or STF instruction stream"); + + po::positional_options_description & pos_opts = cls.getPositionalOptions(); + // example, look for the at the end + pos_opts.add("output_file", -1); + + int err_code = 0; + + if (!cls.parse(argc, argv, err_code)) + { + sparta_assert(false, "Command line parsing failed"); + } + + auto & vm = cls.getVariablesMap(); + if (vm.count("tbhelp") != 0) + { + cout << USAGE << endl; + return false; + } + + sparta_assert(false == datafiles.empty(), + "Need an output file as the last argument of the test"); + + sparta::Scheduler sched; + Simulator sim(&sched, "mavis_isa_files", "arch/isa_json", datafiles[0], input_file); + + if (input_file.length() == 0) + { + sparta::log::MessageSource il(sim.getRoot(), "info", "Info Messages"); + il << "No input file specified, exiting gracefully, output not checked"; + return true; // not an error + } + cls.populateSimulation(&sim); + cls.runSimulator(&sim); + + std::cout << "file name: " << datafiles[0] << std::endl; + ifstream Myfile(datafiles[0]); + + std::string str; + while(getline(Myfile, str)) { + std::cout << str << std::endl; + } + Myfile.close(); + + EXPECT_FILES_EQUAL(datafiles[0], "expected_output/" + datafiles[0] + ".EXPECTED"); + return true; +} + +int main(int argc, char** argv) +{ + if (!runTest(argc, argv)) + return 1; + + REPORT_ERROR; + return (int)ERROR_CODE; +} diff --git a/test/core/bpu/CMakeLists.txt b/test/core/bpu/CMakeLists.txt new file mode 100644 index 00000000..05234b93 --- /dev/null +++ b/test/core/bpu/CMakeLists.txt @@ -0,0 +1,42 @@ +set(PRJ "BPU_test") +set(EXE "BPU_test") + +set(TST1 "${PRJ}_tst1") +set(TST2 "${PRJ}_json") +#set(TST3 "${PRJ}_stf") + +set(CFG config/config.yaml) + +set(BPUDir ../../../core/fetch) +set(CoreDir ../../../core) + +set(JSON ../json/branch.json) + +project(${PRJ}) + +add_executable(${EXE} BPU_testbench.cpp ${BPUDir}/BPU.cpp ${BPUDir}/BPU.hpp ${BPUDir}/BasePredictor.cpp ${BPUDir}/BasePredictor.hpp ${CoreDir}/FTQ.cpp ${CoreDir}/FTQ.hpp BPUSource.hpp BPUSink.hpp) +target_include_directories(${EXE} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + +target_link_libraries(${EXE} core common_test ${STF_LINK_LIBS} mavis SPARTA::sparta) + +file(CREATE_LINK ${SIM_BASE}/mavis/json + ${CMAKE_CURRENT_BINARY_DIR}/mavis_isa_files SYMBOLIC) + +file(CREATE_LINK ${SIM_BASE}/arches + ${CMAKE_CURRENT_BINARY_DIR}/arches SYMBOLIC) + +file(CREATE_LINK ${CMAKE_CURRENT_SOURCE_DIR}/config + ${CMAKE_CURRENT_BINARY_DIR}/config SYMBOLIC) + +file(CREATE_LINK ${CMAKE_CURRENT_SOURCE_DIR}/json + ${CMAKE_CURRENT_BINARY_DIR}/json SYMBOLIC) + +file(CREATE_LINK ${CMAKE_CURRENT_SOURCE_DIR}/traces + ${CMAKE_CURRENT_BINARY_DIR}/traces SYMBOLIC) + +file(CREATE_LINK ${CMAKE_CURRENT_SOURCE_DIR}/expected_output + ${CMAKE_CURRENT_BINARY_DIR}/expected_output SYMBOLIC) + +sparta_named_test(${TST1} ${EXE} bpu_tb_test1.out -c ${CFG}) +sparta_named_test(${TST2} ${EXE} bpu_tb_json.out --input_file ${JSON} -c ${CFG}) +#sparta_named_test(${TST3} ${EXE} bpu_tb_stf.out --input_file ${STF} -c ${CFG}) \ No newline at end of file diff --git a/test/core/bpu/check_files.sh b/test/core/bpu/check_files.sh new file mode 100644 index 00000000..31f4beb6 --- /dev/null +++ b/test/core/bpu/check_files.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +# Check files w/ clang-format +# This does not modify the source files + +# The argument is required +if [[ -z "$1" ]]; then + echo "Usage: $0 " + exit 1 +fi + +compile_commands="$1" + +# Make sure the json exists +if [[ ! -f "$compile_commands" ]]; then + echo "Error: File '$compile_commands' does not exist." + exit 1 +fi + +# List of files +files=( + "../../../core/fetch/BPU.cpp" + "../../../core/fetch/BPU.hpp" + "BPUSink.hpp" + "BPUSource.hpp" + "BPU_testbench.cpp" +) + +for file in "${files[@]}"; do + echo "Checking $file for formatting issues..." + if [[ -f "$file" ]]; then + clang-format --dry-run --Werror "$file" + if [[ $? -eq 0 ]]; then + echo "$file is properly formatted." + else + echo "$file needs formatting." + clang-format "$file" > "$file.formatted" + fi + else + echo "Warning: File $file does not exist, skipping." + fi +done diff --git a/test/core/bpu/config/config.yaml b/test/core/bpu/config/config.yaml new file mode 100644 index 00000000..8a95e130 --- /dev/null +++ b/test/core/bpu/config/config.yaml @@ -0,0 +1,11 @@ +# +# Placeholder +# +#top.cpu: +# dispatch.num_to_dispatch: 2 + +#top.extension.core_extensions: +# execution_topology: +# [["alu", "10"], +# ["fpu", "10"], +# ["br", "10"]] diff --git a/test/core/bpu/expected_output/bpu_tb_json.out.EXPECTED b/test/core/bpu/expected_output/bpu_tb_json.out.EXPECTED new file mode 100644 index 00000000..d8386e28 --- /dev/null +++ b/test/core/bpu/expected_output/bpu_tb_json.out.EXPECTED @@ -0,0 +1,30 @@ +#Name: +#Cmdline: +#Exe: +#SimulatorVersion: +#Repro: +#Start: Tuesday Tue Apr 22 05:37:54 2025 +#Elapsed: 0.001596s +{0000000000 00000000 top.bpu info} sendCreditsToFetch_: Send 10 credits from BPU to Fetch +{0000000000 00000000 top.ftq info} sendCreditsToBPU_: Send 5 credits to BPU +{0000000000 00000000 top.sink info} sendCreditsToFTQ_: Send 5 credits from Fetch to FTQ +{0000000000 00000000 top.bpu info} getCreditsFromFTQ_: BPU received 5 credits from FTQ +{0000000000 00000000 top.src info} getCreditsFromBPU_: Received 10 credits from BPU +{0000000000 00000000 top.src info} sendPredictionRequest_: Sending PredictionRequest from Fetch to BPU +{0000000000 00000000 top.bpu info} getPredictionRequest_: BPU received PredictionRequest from Fetch +{0000000001 00000001 top.ftq info} getFetchCredits_: FTQ: Received 5 credits from Fetch +{0000000001 00000001 top.bpu info} sendFirstPrediction_: Getting direction from base predictor +{0000000001 00000001 top.bpu info} sendFirstPrediction_: Getting target from base predictor +{0000000001 00000001 top.bpu info} sendFirstPrediction_: Sending first PredictionOutput from BPU to FTQ +{0000000002 00000002 top.ftq info} getFirstPrediction_: FTQ receives first PredictionOutput from BPU +{0000000002 00000002 top.ftq info} sendPrediction_: Send prediction from FTQ to Fetch +{0000000002 00000002 top.sink info} getPredictionOutput_: Fetch recieves prediction output from FTQ +{0000000004 00000004 top.bpu info} sendSecondPrediction_: Getting direction prediction from TAGE +{0000000004 00000004 top.bpu info} sendSecondPrediction_: Sending second PredictionOutput from BPU to FTQ +{0000000005 00000005 top.ftq info} getSecondPrediction_: FTQ receives second PredictionOutput from BPU +{0000000005 00000005 top.ftq info} handleMismatch: Checking mismatch between BasePredictor and TAGE_SC_L +{0000000005 00000005 top.ftq info} handleMismatch: BasePredictor prediction direction: 1 +{0000000005 00000005 top.ftq info} handleMismatch: TAGE_SC_L prediction direction: 0 +{0000000005 00000005 top.ftq info} handleMismatch: Prediction mismatch between tage and base predictor +{0000000005 00000005 top.ftq info} sendPrediction_: Send prediction from FTQ to Fetch +{0000000005 00000005 top.sink info} getPredictionOutput_: Fetch recieves prediction output from FTQ diff --git a/test/core/bpu/json/branch.json b/test/core/bpu/json/branch.json new file mode 100644 index 00000000..18f5347e --- /dev/null +++ b/test/core/bpu/json/branch.json @@ -0,0 +1,44 @@ +[ + { + "mnemonic": "beq", + "rs1": 1, + "rs2": 2, + "vaddr": "0x1000", + "target": "0x2000" + }, + { + "mnemonic": "bne", + "rs1": 3, + "rs2": 4, + "vaddr": "0x1004", + "target": "0x2004" + }, + { + "mnemonic": "blt", + "rs1": 5, + "rs2": 6, + "vaddr": "0x1008", + "target": "0x2008" + }, + { + "mnemonic": "bge", + "rs1": 7, + "rs2": 8, + "vaddr": "0x100C", + "target": "0x200C" + }, + { + "mnemonic": "bltu", + "rs1": 9, + "rs2": 10, + "vaddr": "0x1010", + "target": "0x2010" + }, + { + "mnemonic": "bgeu", + "rs1": 11, + "rs2": 12, + "vaddr": "0x1014", + "target": "0x2014" + } +] \ No newline at end of file