Skip to content

Commit

Permalink
Add execute flushing (#137)
Browse files Browse the repository at this point in the history
- Fix pipeline flush mechanisms by removing inflight instructions inside
functional units, recovering the rename map, and adding trace rewind
functionality
- Add FlushCriteria class for requesting flushes to and from the
FlushManager
- Update FlushManager to handle and arbitrate between multiple flush
sources
- Add flush request port from ExecutePipe to FlushManager intended to be
used for mispredicted branches
- Delay completing instructions in ExecutePipe by a cycle to fix execute
flush race conditions
- Add branch identification methods for Inst
- Add test to send random flushes from the branch unit

---------

Signed-off-by: danbone <[email protected]>
Co-authored-by: Daniel Bone <[email protected]>
  • Loading branch information
danbone and Daniel Bone authored Jan 11, 2024
1 parent 648a316 commit ce29ae2
Show file tree
Hide file tree
Showing 18 changed files with 663 additions and 152 deletions.
34 changes: 22 additions & 12 deletions core/CPUTopology.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -259,11 +259,7 @@ olympia::CoreTopologySimple::CoreTopologySimple(){
},
{
"cpu.core*.rob.ports.out_retire_flush",
"cpu.core*.flushmanager.ports.in_retire_flush"
},
{
"cpu.core*.rob.ports.out_fetch_flush_redirect",
"cpu.core*.flushmanager.ports.in_fetch_flush_redirect"
"cpu.core*.flushmanager.ports.in_flush_request"
},
{
"cpu.core*.rob.ports.out_rob_retire_ack",
Expand All @@ -274,27 +270,35 @@ olympia::CoreTopologySimple::CoreTopologySimple(){
"cpu.core*.rename.ports.in_rename_retire_ack"
},
{
"cpu.core*.flushmanager.ports.out_retire_flush",
"cpu.core*.flushmanager.ports.out_flush_upper",
"cpu.core*.dispatch.ports.in_reorder_flush"
},
{
"cpu.core*.flushmanager.ports.out_retire_flush",
"cpu.core*.flushmanager.ports.out_flush_upper",
"cpu.core*.decode.ports.in_reorder_flush"
},
{
"cpu.core*.flushmanager.ports.out_flush_lower",
"cpu.core*.decode.ports.in_reorder_flush"
},
{
"cpu.core*.flushmanager.ports.out_retire_flush",
"cpu.core*.flushmanager.ports.out_flush_upper",
"cpu.core*.rename.ports.in_reorder_flush"
},
{
"cpu.core*.flushmanager.ports.out_retire_flush",
"cpu.core*.flushmanager.ports.out_flush_upper",
"cpu.core*.rob.ports.in_reorder_flush"
},
{
"cpu.core*.flushmanager.ports.out_retire_flush",
"cpu.core*.flushmanager.ports.out_flush_upper",
"cpu.core*.lsu.ports.in_reorder_flush"
},
{
"cpu.core*.flushmanager.ports.out_fetch_flush_redirect",
"cpu.core*.flushmanager.ports.out_flush_upper",
"cpu.core*.fetch.ports.in_fetch_flush_redirect"
},
{
"cpu.core*.flushmanager.ports.out_flush_lower",
"cpu.core*.fetch.ports.in_fetch_flush_redirect"
}
};
Expand Down Expand Up @@ -344,8 +348,14 @@ void olympia::CoreTopologySimple::bindTree(sparta::RootTreeNode* root_node)
// Bind flushing
const std::string exe_flush_in =
core_node + ".execute." + unit_name + ".ports.in_reorder_flush";;
const std::string flush_manager = flushmanager_ports + ".out_retire_flush";
const std::string flush_manager = flushmanager_ports + ".out_flush_upper";
bind_ports(exe_flush_in, flush_manager);

// Bind flush requests
const std::string exe_flush_out =
core_node + ".execute." + unit_name + ".ports.out_execute_flush";;
const std::string flush_manager_in = flushmanager_ports + ".in_flush_request";
bind_ports(exe_flush_out, flush_manager_in);
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions core/Decode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,10 @@ namespace olympia
uop_queue_outp_.send(insts);

// Decrement internal Uop Queue credits
uop_queue_credits_ -= num_decode;
uop_queue_credits_ -= insts->size();

// Send credits back to Fetch to get more instructions
fetch_queue_credits_outp_.send(num_decode);
fetch_queue_credits_outp_.send(insts->size());
}

// If we still have credits to send instructions as well as
Expand Down
137 changes: 114 additions & 23 deletions core/ExecutePipe.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ namespace olympia
execute_time_(p->execute_time),
scheduler_size_(p->scheduler_size),
in_order_issue_(p->in_order_issue),
enable_random_misprediction_(p->enable_random_misprediction),
reg_file_(determineRegisterFile(node->getGroup())),
collected_inst_(node, node->getName())
{
Expand All @@ -43,7 +44,13 @@ namespace olympia
// Complete should come before issue because it schedules issue with a 0 cycle delay
// issue should always schedule complete with a non-zero delay (which corresponds to the
// insturction latency)
complete_inst_ >> issue_inst_;
execute_inst_ >> issue_inst_;

if (enable_random_misprediction_)
{
sparta_assert(node->getGroup() == "br",
"random branch misprediction can only be enabled on a branch unit");
}

ILOG("ExecutePipe construct: #" << node->getGroupIdx());

Expand All @@ -67,6 +74,14 @@ namespace olympia
////////////////////////////////////////////////////////////////////////////////
// Callbacks
void ExecutePipe::getInstsFromDispatch_(const InstPtr & ex_inst)
{
sparta_assert(ex_inst->getStatus() == Inst::Status::DISPATCHED, "Bad instruction status: " << ex_inst);
appendIssueQueue_(ex_inst);
handleOperandIssueCheck_(ex_inst);
}

// Callback from Scoreboard to inform Operand Readiness
void ExecutePipe::handleOperandIssueCheck_(const InstPtr & ex_inst)
{
// FIXME: Now every source operand should be ready
const auto & src_bits = ex_inst->getSrcRegisterBitMask(reg_file_);
Expand Down Expand Up @@ -106,7 +121,7 @@ namespace olympia
registerReadyCallback(src_bits, ex_inst->getUniqueID(),
[this, ex_inst](const sparta::Scoreboard::RegisterBitMask&)
{
this->getInstsFromDispatch_(ex_inst);
this->handleOperandIssueCheck_(ex_inst);
});
ILOG("Instruction NOT ready: " << ex_inst << " Bits needed:" << sparta::printBitSet(src_bits));
}
Expand All @@ -130,19 +145,19 @@ namespace olympia

++total_insts_issued_;
// Mark the instruction complete later...
complete_inst_.preparePayload(ex_inst_ptr)->schedule(exe_time);
execute_inst_.preparePayload(ex_inst_ptr)->schedule(exe_time);
// Mark the alu as busy
unit_busy_ = true;
// Pop the insturction from the scheduler and send a credit back to dispatch
// Pop the insturction from the ready queue and send a credit back to dispatch
popIssueQueue_(ex_inst_ptr);
ready_queue_.pop_front();
out_scheduler_credits_.send(1, 0);
}

// Called by the scheduler, scheduled by complete_inst_.
void ExecutePipe::completeInst_(const InstPtr & ex_inst)
void ExecutePipe::executeInst_(const InstPtr & ex_inst)
{
ex_inst->setStatus(Inst::Status::COMPLETED);
ILOG("Completing inst: " << ex_inst);
ILOG("Executed inst: " << ex_inst);

// set scoreboard
if(SPARTA_EXPECT_FALSE(ex_inst->isTransfer()))
Expand Down Expand Up @@ -170,6 +185,17 @@ namespace olympia
scoreboard_views_[reg_file_]->setReady(dest_bits);
}

// Testing mode to inject random branch misprediction to stress flushing mechanism
if (enable_random_misprediction_)
{
if (ex_inst->isBranch() && (std::rand() % 20) == 0)
{
ILOG("Randomly injecting a mispredicted branch: " << ex_inst);
FlushManager::FlushingCriteria criteria(FlushManager::FlushCause::MISPREDICTION, ex_inst);
out_execute_flush_.send(criteria);
}
}

// We're not busy anymore
unit_busy_ = false;

Expand All @@ -180,43 +206,108 @@ namespace olympia
if (ready_queue_.size() > 0) {
issue_inst_.schedule(sparta::Clock::Cycle(0));
}

// Schedule completion
complete_inst_.preparePayload(ex_inst)->schedule(1);

}

// Called by the scheduler, scheduled by complete_inst_.
void ExecutePipe::completeInst_(const InstPtr & ex_inst)
{
ex_inst->setStatus(Inst::Status::COMPLETED);
ILOG("Completing inst: " << ex_inst);
}

void ExecutePipe::flushInst_(const FlushManager::FlushingCriteria & criteria)
{
ILOG("Got flush for criteria: " << criteria);

// Flush instructions in the ready queue
ReadyQueue::iterator it = ready_queue_.begin();
// Flush instructions in the issue queue
uint32_t credits_to_send = 0;
while(it != ready_queue_.end()) {
if((*it)->getUniqueID() >= uint64_t(criteria)) {
ready_queue_.erase(it++);

auto issue_queue_iter = issue_queue_.begin();
while (issue_queue_iter != issue_queue_.end())
{
auto delete_iter = issue_queue_iter++;

auto inst_ptr = *delete_iter;

// Remove flushed instruction from issue queue and clear scoreboard callbacks
if (criteria.includedInFlush(inst_ptr))
{
issue_queue_.erase(delete_iter);

scoreboard_views_[reg_file_]->clearCallbacks(inst_ptr->getUniqueID());

++credits_to_send;
}
else {
++it;

ILOG("Flush Instruction ID: " << inst_ptr->getUniqueID() << " from issue queue");
}
}

if(credits_to_send) {
out_scheduler_credits_.send(credits_to_send, 0);

ILOG("Flush " << credits_to_send << " instructions in issue queue!");
}

// Flush instructions in the ready queue
auto ready_queue_iter = ready_queue_.begin();
while (ready_queue_iter != ready_queue_.end())
{
auto delete_iter = ready_queue_iter++;
auto inst_ptr = *delete_iter;
if (criteria.includedInFlush(inst_ptr))
{
ready_queue_.erase(delete_iter);
ILOG("Flush Instruction ID: " << inst_ptr->getUniqueID() << " from ready queue");
}
}

// Cancel outstanding instructions awaiting completion and
// instructions on their way to issue
auto cancel_critera = [criteria](const InstPtr & inst) -> bool {
if(inst->getUniqueID() >= uint64_t(criteria)) {
return true;
}
return false;
auto flush = [criteria](const InstPtr & inst) -> bool {
return criteria.includedInFlush(inst);
};
complete_inst_.cancelIf(cancel_critera);
issue_inst_.cancel();

if(complete_inst_.getNumOutstandingEvents() == 0) {
complete_inst_.cancelIf(flush);
execute_inst_.cancelIf(flush);
if(execute_inst_.getNumOutstandingEvents() == 0) {
unit_busy_ = false;
collected_inst_.closeRecord();
if (!ready_queue_.empty()) {
// issue non-flushed instructions
issue_inst_.schedule(sparta::Clock::Cycle(1));
}
}
}

////////////////////////////////////////////////////////////////////////////////
// Regular Function/Subroutine Call
////////////////////////////////////////////////////////////////////////////////

// Append instruction into issue queue
void ExecutePipe::appendIssueQueue_(const InstPtr & inst_ptr)
{
sparta_assert(issue_queue_.size() < scheduler_size_,
"Appending issue queue causes overflows!");

issue_queue_.emplace_back(inst_ptr);
}

// Pop completed instruction out of issue queue
void ExecutePipe::popIssueQueue_(const InstPtr & inst_ptr)
{
// Look for the instruction to be completed, and remove it from issue queue
for (auto iter = issue_queue_.begin(); iter != issue_queue_.end(); iter++) {
if (*iter == inst_ptr) {
issue_queue_.erase(iter);
return;
}
}
sparta_assert(false, "Attempt to complete instruction no longer exiting in issue queue!");
}


}
28 changes: 26 additions & 2 deletions core/ExecutePipe.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ namespace olympia
PARAMETER(uint32_t, execute_time, 1, "Time for execution")
PARAMETER(uint32_t, scheduler_size, 8, "Scheduler queue size")
PARAMETER(bool, in_order_issue, true, "Force in order issue")
HIDDEN_PARAMETER(bool, enable_random_misprediction, false,
"test mode to inject random branch mispredictions")
};

/**
Expand All @@ -71,10 +73,13 @@ namespace olympia
sparta::DataOutPort<uint32_t> out_scheduler_credits_{&unit_port_set_, "out_scheduler_credits"};
sparta::DataInPort<FlushManager::FlushingCriteria> in_reorder_flush_
{&unit_port_set_, "in_reorder_flush", sparta::SchedulingPhase::Flush, 1};
sparta::DataOutPort<FlushManager::FlushingCriteria> out_execute_flush_
{&unit_port_set_, "out_execute_flush"};

// Ready queue
typedef std::list<InstPtr> ReadyQueue;
ReadyQueue ready_queue_;
ReadyQueue issue_queue_;

// Scoreboards
using ScoreboardViews = std::array<std::unique_ptr<sparta::ScoreboardView>, core_types::N_REGFILES>;
Expand All @@ -87,14 +92,18 @@ namespace olympia
const uint32_t execute_time_;
const uint32_t scheduler_size_;
const bool in_order_issue_;
const bool enable_random_misprediction_;
const core_types::RegFile reg_file_;
sparta::collection::IterableCollector<std::list<InstPtr>>
ready_queue_collector_ {getContainer(), "scheduler_queue",
&ready_queue_, scheduler_size_};

// Events used to issue and complete the instruction
// Events used to issue, execute and complete the instruction
sparta::UniqueEvent<> issue_inst_{&unit_event_set_, getName() + "_issue_inst",
CREATE_SPARTA_HANDLER(ExecutePipe, issueInst_)};
sparta::PayloadEvent<InstPtr> execute_inst_{
&unit_event_set_, getName() + "_execute_inst",
CREATE_SPARTA_HANDLER_WITH_DATA(ExecutePipe, executeInst_, InstPtr)};
sparta::PayloadEvent<InstPtr> complete_inst_{
&unit_event_set_, getName() + "_complete_inst",
CREATE_SPARTA_HANDLER_WITH_DATA(ExecutePipe, completeInst_, InstPtr)};
Expand All @@ -119,11 +128,26 @@ namespace olympia
void issueInst_();
void getInstsFromDispatch_(const InstPtr&);

// Callback from Scoreboard to inform Operand Readiness
void handleOperandIssueCheck_(const InstPtr &);

// Write result to registers
void executeInst_(const InstPtr&);

// Used to complete the inst in the FPU
void completeInst_(const InstPtr&);

// Used to flush the ALU
void flushInst_(const FlushManager::FlushingCriteria & criteria);
void flushInst_(const FlushManager::FlushingCriteria &);

////////////////////////////////////////////////////////////////////////////////
// Regular Function/Subroutine Call

// Append new instruction into issue queue
void appendIssueQueue_(const InstPtr &);

// Pop completed instruction out of issue queue
void popIssueQueue_(const InstPtr &);

// Friend class used in rename testing
friend class ExecutePipeTester;
Expand Down
Loading

0 comments on commit ce29ae2

Please sign in to comment.