Skip to content

Commit 18c4954

Browse files
fix: clear scoreboard callbacks for all register files on flush (#295)
# fix: clear scoreboard callbacks for all register files on flush ## Summary This PR fixes incomplete scoreboard callback cleanup during instruction flush in `IssueQueue` and addresses minor code quality issues in `LSU`. ## Problem In `IssueQueue::flushInst_()`, when an instruction is flushed from the issue queue, only callbacks for the **INTEGER register file** are cleared: ```cpp scoreboard_views_[core_types::RegFile::RF_INTEGER]->clearCallbacks( inst_ptr->getUniqueID()); ``` However, `handleOperandIssueCheck_()` registers callbacks for **all register file types** (INTEGER, FLOAT, VECTOR): ```cpp for (auto reg_file = 0; reg_file < core_types::RegFile::N_REGFILES; ++reg_file) { // ... registers callbacks for each register file type scoreboard_views_[reg_file]->registerReadyCallback(...); } ``` ### Impact When floating-point or vector instructions are flushed, their scoreboard callbacks remain registered. This could potentially cause: - Stale callbacks firing for already-flushed instructions - Incorrect instruction scheduling behavior - Memory leaks from dangling callback references ## Solution ### IssueQueue.cpp Replace single register file callback clear with loop over all register files, matching how callbacks are registered: **Before:** ```cpp scoreboard_views_[core_types::RegFile::RF_INTEGER]->clearCallbacks( inst_ptr->getUniqueID()); ``` **After:** ```cpp // Clear scoreboard callbacks for all register file types for (uint32_t rf = 0; rf < core_types::N_REGFILES; ++rf) { scoreboard_views_[rf]->clearCallbacks(inst_ptr->getUniqueID()); } ``` This change is consistent with the pattern already used in `LSU.cpp` lines 1421-1426: ```cpp std::vector<core_types::RegFile> reg_files = {core_types::RF_INTEGER, core_types::RF_FLOAT}; for (const auto rf : reg_files) { scoreboard_views_[rf]->clearCallbacks(inst_ptr->getUniqueID()); } ``` ### LSU.cpp - Fixed typo: `readdy` → `ready` in log message (line 266) - Removed duplicate semicolon in `store_buffer_.erase()` call (line 290) ## Testing Existing regression tests pass. The IssueQueue flush callback bug primarily manifests in scenarios with floating-point/vector instruction flushes, which are not fully covered by current tests.
1 parent 76d1a46 commit 18c4954

2 files changed

Lines changed: 46 additions & 32 deletions

File tree

‎core/execute/IssueQueue.cpp‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,8 +230,11 @@ namespace olympia
230230
{
231231
issue_queue_.erase(delete_iter);
232232

233-
scoreboard_views_[core_types::RegFile::RF_INTEGER]->clearCallbacks(
234-
inst_ptr->getUniqueID());
233+
// Clear scoreboard callbacks for all register file types
234+
for (uint32_t rf = 0; rf < core_types::N_REGFILES; ++rf)
235+
{
236+
scoreboard_views_[rf]->clearCallbacks(inst_ptr->getUniqueID());
237+
}
235238

236239
++credits_to_send;
237240

‎core/lsu/LSU.cpp‎

Lines changed: 41 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ namespace olympia
2020
replay_buffer_("replay_buffer", p->replay_buffer_size, getClock()),
2121
replay_buffer_size_(p->replay_buffer_size),
2222
replay_issue_delay_(p->replay_issue_delay),
23-
store_buffer_("store_buffer", p->ldst_inst_queue_size, getClock()), // Add this line
23+
store_buffer_("store_buffer", p->ldst_inst_queue_size, getClock()), // Add this line
2424
store_buffer_size_(p->ldst_inst_queue_size),
2525
ready_queue_(),
2626
load_store_info_allocator_(sparta::notNull(OlympiaAllocators::getOlympiaAllocators(node))
@@ -108,9 +108,9 @@ namespace olympia
108108
node->getParent()->registerForNotification<bool, LSU, &LSU::onROBTerminate_>(
109109
this, "rob_stopped_notif_channel", false /* ROB maybe not be constructed yet */);
110110

111-
112111
auto & events = ldst_pipeline_.getEventsAtStage(cache_read_stage_);
113-
for (auto & event : events) {
112+
for (auto & event : events)
113+
{
114114
in_cache_lookup_ack_.registerConsumerEvent(event->getScheduleable());
115115
}
116116

@@ -221,8 +221,8 @@ namespace olympia
221221
{
222222
for (auto reg_file = 0; reg_file < core_types::RegFile::N_REGFILES; ++reg_file)
223223
{
224-
const auto & data_bits = inst_ptr->
225-
getDataRegisterBitMask(static_cast<core_types::RegFile>(reg_file));
224+
const auto & data_bits = inst_ptr->getDataRegisterBitMask(
225+
static_cast<core_types::RegFile>(reg_file));
226226
// if x0 is a data operand, we don't need to check scoreboard
227227
if (!inst_ptr->getRenameData().getDataReg().op_info.is_x0)
228228
{
@@ -234,7 +234,7 @@ namespace olympia
234234
[this, inst_ptr](const sparta::Scoreboard::RegisterBitMask &)
235235
{ this->handleOperandIssueCheck_(inst_ptr); });
236236
ILOG("Instruction NOT ready: " << inst_ptr << " Bits needed:"
237-
<< sparta::printBitSet(data_bits));
237+
<< sparta::printBitSet(data_bits));
238238
}
239239
}
240240
}
@@ -263,7 +263,7 @@ namespace olympia
263263
// either a new issue event, or a re-issue event
264264
// however, we can ONLY update instruction status as SCHEDULED for a new issue event
265265

266-
ILOG("Inst fully readdy: " << inst_ptr);
266+
ILOG("Inst fully ready: " << inst_ptr);
267267

268268
if (isReadyToIssueInsts_())
269269
{
@@ -281,17 +281,18 @@ namespace olympia
281281
if (inst_ptr->isStoreInst())
282282
{
283283
auto oldest_store = getOldestStore_();
284-
sparta_assert(oldest_store && oldest_store->getInstPtr()->getUniqueID() == inst_ptr->getUniqueID(),
285-
"Attempting to retire store out of order! Expected: "
286-
<< (oldest_store ? oldest_store->getInstPtr()->getUniqueID() : 0)
287-
<< " Got: " << inst_ptr->getUniqueID());
288-
284+
sparta_assert(oldest_store
285+
&& oldest_store->getInstPtr()->getUniqueID()
286+
== inst_ptr->getUniqueID(),
287+
"Attempting to retire store out of order! Expected: "
288+
<< (oldest_store ? oldest_store->getInstPtr()->getUniqueID() : 0)
289+
<< " Got: " << inst_ptr->getUniqueID());
290+
289291
// Remove from store buffer -> don't actually need to send cache request
290-
store_buffer_.erase(store_buffer_.begin());;
292+
store_buffer_.erase(store_buffer_.begin());
291293
++stores_retired_;
292294
}
293295

294-
295296
updateIssuePriorityAfterStoreInstRetire_(inst_ptr);
296297
if (isReadyToIssueInsts_())
297298
{
@@ -508,7 +509,8 @@ namespace olympia
508509
return;
509510
}
510511

511-
// Loads don't perform a cache lookup if there are older stores haven't issued in the load store queue
512+
// Loads don't perform a cache lookup if there are older stores haven't issued in the load
513+
// store queue
512514
if (!inst_ptr->isStoreInst() && !allOlderStoresIssued_(inst_ptr)
513515
&& allow_speculative_load_exec_)
514516
{
@@ -946,13 +948,14 @@ namespace olympia
946948
ILOG("Store added to store buffer: " << inst_ptr);
947949
}
948950

949-
bool LSU::tryStoreToLoadForwarding(const InstPtr& load_inst_ptr) const
951+
bool LSU::tryStoreToLoadForwarding(const InstPtr & load_inst_ptr) const
950952
{
951953
const uint64_t load_addr = load_inst_ptr->getTargetVAddr();
952954
const uint32_t load_size = load_inst_ptr->getMemAccessSize();
953955

954956
// A load must have a non-zero size to access memory.
955-
if (load_size == 0) {
957+
if (load_size == 0)
958+
{
956959
return false;
957960
}
958961

@@ -964,30 +967,33 @@ namespace olympia
964967
// the data from the youngest store is effectively used.
965968
for (auto it = store_buffer_.rbegin(); it != store_buffer_.rend(); ++it)
966969
{
967-
const auto& store_info_ptr = *it; // LoadStoreInstInfoPtr
968-
const InstPtr& store_inst_ptr = store_info_ptr->getInstPtr();
970+
const auto & store_info_ptr = *it; // LoadStoreInstInfoPtr
971+
const InstPtr & store_inst_ptr = store_info_ptr->getInstPtr();
969972

970973
const uint64_t store_addr = store_inst_ptr->getTargetVAddr();
971974
const uint32_t store_size = store_inst_ptr->getMemAccessSize();
972975

973-
if (store_size == 0) {
976+
if (store_size == 0)
977+
{
974978
continue; // Skip stores that don't actually write data.
975979
}
976980

977981
// Determine the overlapping region [overlap_start_addr, overlap_end_addr)
978982
// The overlap is in terms of global memory addresses.
979983
uint64_t overlap_start_addr = std::max(load_addr, store_addr);
980-
uint64_t overlap_end_addr = std::min(load_addr + load_size, store_addr + store_size);
984+
uint64_t overlap_end_addr = std::min(load_addr + load_size, store_addr + store_size);
981985

982986
// If there's an actual overlap (i.e., the range is not empty)
983987
if (overlap_start_addr < overlap_end_addr)
984988
{
985989
// Iterate over the bytes *within the load's address range* that this store covers.
986-
for (uint64_t current_byte_global_addr = overlap_start_addr; current_byte_global_addr < overlap_end_addr; ++current_byte_global_addr)
990+
for (uint64_t current_byte_global_addr = overlap_start_addr;
991+
current_byte_global_addr < overlap_end_addr; ++current_byte_global_addr)
987992
{
988993
// Calculate the index of this byte relative to the load's start address.
989994
// This index is used for the coverage_mask.
990-
uint32_t load_byte_idx = static_cast<uint32_t>(current_byte_global_addr - load_addr);
995+
uint32_t load_byte_idx =
996+
static_cast<uint32_t>(current_byte_global_addr - load_addr);
991997

992998
// If this byte within the load's coverage_mask hasn't been marked true yet
993999
// (meaning it hasn't been covered by an even younger store), mark it.
@@ -1000,7 +1006,8 @@ namespace olympia
10001006
}
10011007

10021008
// If all bytes of the load are now covered, no need to check even older stores
1003-
if (bytes_covered_count == load_size) {
1009+
if (bytes_covered_count == load_size)
1010+
{
10041011
break;
10051012
}
10061013
}
@@ -1009,10 +1016,10 @@ namespace olympia
10091016
return bytes_covered_count == load_size;
10101017
}
10111018

1012-
1013-
LoadStoreInstInfoPtr LSU::getOldestStore_() const
1019+
LoadStoreInstInfoPtr LSU::getOldestStore_() const
10141020
{
1015-
if(store_buffer_.empty()) {
1021+
if (store_buffer_.empty())
1022+
{
10161023
return nullptr;
10171024
}
10181025
return store_buffer_.read(0);
@@ -1495,14 +1502,18 @@ namespace olympia
14951502
void LSU::flushStoreBuffer_(const FlushCriteria & criteria)
14961503
{
14971504
auto sb_iter = store_buffer_.begin();
1498-
while(sb_iter != store_buffer_.end()) {
1505+
while (sb_iter != store_buffer_.end())
1506+
{
14991507
auto inst_ptr = (*sb_iter)->getInstPtr();
1500-
if(criteria.includedInFlush(inst_ptr)) {
1508+
if (criteria.includedInFlush(inst_ptr))
1509+
{
15011510
auto delete_iter = sb_iter++;
15021511
// store buffer didn't return an iterator
15031512
store_buffer_.erase(delete_iter);
15041513
ILOG("Flushed store from store buffer: " << inst_ptr);
1505-
} else {
1514+
}
1515+
else
1516+
{
15061517
++sb_iter;
15071518
}
15081519
}

0 commit comments

Comments
 (0)