summaryrefslogtreecommitdiff
path: root/src/gpgpu-sim
diff options
context:
space:
mode:
authorDavit Grigoryan <[email protected]>2026-04-28 22:06:08 +0000
committerDavit Grigoryan <[email protected]>2026-04-28 22:06:08 +0000
commitfbb90ef3a76cd6c469782c84668d25c5f3bfdd22 (patch)
tree08d320b6da8f5c6c5e276c94ba19a8d5d5bc9216 /src/gpgpu-sim
parent3d4d274eb3a6a23a5924b557d010590e219c5256 (diff)
critical fix - add option to have masks for SB entries
Diffstat (limited to 'src/gpgpu-sim')
-rw-r--r--src/gpgpu-sim/gpu-sim.cc8
-rw-r--r--src/gpgpu-sim/scoreboard.cc181
-rw-r--r--src/gpgpu-sim/scoreboard.h50
-rw-r--r--src/gpgpu-sim/shader.cc277
-rw-r--r--src/gpgpu-sim/shader.h37
5 files changed, 466 insertions, 87 deletions
diff --git a/src/gpgpu-sim/gpu-sim.cc b/src/gpgpu-sim/gpu-sim.cc
index 1c0c6c6..4fb288b 100644
--- a/src/gpgpu-sim/gpu-sim.cc
+++ b/src/gpgpu-sim/gpu-sim.cc
@@ -720,6 +720,14 @@ void shader_core_config::reg_options(class OptionParser *opp) {
"may share the LDST unit in one pipe slot; atomics, shared, tex, "
"const, barriers, LDGSTS, and BRU spill/fill are still excluded.",
"0");
+ option_parser_register(
+ opp, "-gpgpu_scoreboard_mode", OPT_UINT32, &gpgpu_scoreboard_mode,
+ "Scoreboard implementation for intra-warp co-issue hazard tracking: "
+ "0=legacy (primary + secondary tables, today's behavior), "
+ "1=mask-aware single scoreboard (entries keyed by (reg, active_mask)), "
+ "2=slot-pinned per-half scoreboards with wait-for-clean at "
+ "divergence/reconvergence. Default 0 preserves legacy behavior.",
+ "0");
for (unsigned j = 0; j < SPECIALIZED_UNIT_NUM; ++j) {
std::stringstream ss;
diff --git a/src/gpgpu-sim/scoreboard.cc b/src/gpgpu-sim/scoreboard.cc
index 6cdcaf5..70e6ac1 100644
--- a/src/gpgpu-sim/scoreboard.cc
+++ b/src/gpgpu-sim/scoreboard.cc
@@ -33,13 +33,16 @@
#include "shader_trace.h"
// Constructor
-Scoreboard::Scoreboard(unsigned sid, unsigned n_warps, class gpgpu_t* gpu)
+Scoreboard::Scoreboard(unsigned sid, unsigned n_warps, class gpgpu_t* gpu,
+ unsigned mode)
: longopregs() {
m_sid = sid;
+ m_mode = mode;
// Initialize size of table
reg_table.resize(n_warps);
longopregs.resize(n_warps);
sec_reg_table.resize(n_warps);
+ mask_reg_table.resize(n_warps);
m_gpu = gpu;
@@ -55,6 +58,11 @@ Scoreboard::Scoreboard(unsigned sid, unsigned n_warps, class gpgpu_t* gpu)
m_sec_total_duration_cycles = 0;
m_sec_completed_reservations = 0;
m_sec_max_duration_cycles = 0;
+ m_mask_reserve_inserts = 0;
+ m_mask_reserve_inc_refcount = 0;
+ m_mask_release_match = 0;
+ m_mask_release_nomatch = 0;
+ m_mask_release_erase = 0;
}
// Print scoreboard contents
@@ -111,15 +119,26 @@ const bool Scoreboard::islongop(unsigned warp_id, unsigned regnum) {
}
void Scoreboard::reserveRegisters(const class warp_inst_t* inst) {
- for (unsigned r = 0; r < MAX_OUTPUT_VALUES; r++) {
- if (inst->out[r] > 0) {
- reserveRegister(inst->warp_id(), inst->out[r]);
- SHADER_DPRINTF(SCOREBOARD, "Reserved register - warp:%d, reg: %d\n",
- inst->warp_id(), inst->out[r]);
+ if (m_mode == 1) {
+ // Mask-aware: insert (reg, inst's active mask, ref_count++) per output.
+ const active_mask_t &mask = inst->get_active_mask();
+ for (unsigned r = 0; r < MAX_OUTPUT_VALUES; r++) {
+ if (inst->out[r] > 0) {
+ reserveRegisterMask(inst->warp_id(), inst->out[r], mask);
+ }
+ }
+ } else {
+ for (unsigned r = 0; r < MAX_OUTPUT_VALUES; r++) {
+ if (inst->out[r] > 0) {
+ reserveRegister(inst->warp_id(), inst->out[r]);
+ SHADER_DPRINTF(SCOREBOARD, "Reserved register - warp:%d, reg: %d\n",
+ inst->warp_id(), inst->out[r]);
+ }
}
}
- // Keep track of long operations
+ // Keep track of long operations (longopregs is mode-agnostic — used as a
+ // hint by issue eligibility, not for hazard tracking)
if (inst->is_load() && (inst->space.get_type() == global_space ||
inst->space.get_type() == local_space ||
inst->space.get_type() == param_space_kernel ||
@@ -138,12 +157,22 @@ void Scoreboard::reserveRegisters(const class warp_inst_t* inst) {
// Release registers for an instruction
void Scoreboard::releaseRegisters(const class warp_inst_t* inst) {
- for (unsigned r = 0; r < MAX_OUTPUT_VALUES; r++) {
- if (inst->out[r] > 0) {
- SHADER_DPRINTF(SCOREBOARD, "Register Released - warp:%d, reg: %d\n",
- inst->warp_id(), inst->out[r]);
- releaseRegister(inst->warp_id(), inst->out[r]);
- longopregs[inst->warp_id()].erase(inst->out[r]);
+ if (m_mode == 1) {
+ const active_mask_t &mask = inst->get_active_mask();
+ for (unsigned r = 0; r < MAX_OUTPUT_VALUES; r++) {
+ if (inst->out[r] > 0) {
+ releaseRegisterMask(inst->warp_id(), inst->out[r], mask);
+ longopregs[inst->warp_id()].erase(inst->out[r]);
+ }
+ }
+ } else {
+ for (unsigned r = 0; r < MAX_OUTPUT_VALUES; r++) {
+ if (inst->out[r] > 0) {
+ SHADER_DPRINTF(SCOREBOARD, "Register Released - warp:%d, reg: %d\n",
+ inst->warp_id(), inst->out[r]);
+ releaseRegister(inst->warp_id(), inst->out[r]);
+ longopregs[inst->warp_id()].erase(inst->out[r]);
+ }
}
}
}
@@ -180,9 +209,14 @@ bool Scoreboard::checkCollision(unsigned wid, const class inst_t* inst) const {
}
bool Scoreboard::pendingWrites(unsigned wid) const {
+ if (m_mode == 1) return !mask_reg_table[wid].empty();
return !reg_table[wid].empty();
}
+bool Scoreboard::pendingWritesAny(unsigned wid) const {
+ return pendingWrites(wid);
+}
+
// Secondary scoreboard methods for intra-warp co-issue
void Scoreboard::reserveRegisterSecondary(unsigned wid, unsigned regnum) {
@@ -237,6 +271,88 @@ void Scoreboard::clearSecondary(unsigned wid) {
sec_reg_table[wid].clear();
}
+// =====================================================================
+// Mode 1: mask-aware scoreboard
+// =====================================================================
+//
+// Each reservation is a (reg, active_mask, ref_count) tuple. Identical
+// (reg, mask) reservations are coalesced into one entry with ref_count
+// incremented. Hazard detection is mask intersection — an instruction
+// with active_mask M collides on register R iff there exists an entry
+// (R, M', _) with (M & M').any().
+//
+// Release is idempotent: matching entry decrements ref_count and is
+// erased when ref_count hits zero; mismatch is silently ignored (so
+// the existing per-set release loops, which call release once per valid
+// set, work without dedup).
+
+void Scoreboard::reserveRegisterMask(unsigned wid, unsigned reg,
+ const active_mask_t &mask) {
+ for (auto &e : mask_reg_table[wid]) {
+ if (e.reg == reg && e.mask == mask) {
+ e.ref_count++;
+ m_mask_reserve_inc_refcount++;
+ return;
+ }
+ }
+ mask_resv e;
+ e.reg = reg;
+ e.mask = mask;
+ e.ref_count = 1;
+ e.resv_cycle = m_gpu->gpu_sim_cycle + m_gpu->gpu_tot_sim_cycle;
+ mask_reg_table[wid].push_back(e);
+ m_mask_reserve_inserts++;
+}
+
+void Scoreboard::releaseRegisterMask(unsigned wid, unsigned reg,
+ const active_mask_t &mask) {
+ for (auto it = mask_reg_table[wid].begin();
+ it != mask_reg_table[wid].end(); ++it) {
+ if (it->reg == reg && it->mask == mask) {
+ assert(it->ref_count > 0);
+ it->ref_count--;
+ m_mask_release_match++;
+ if (it->ref_count == 0) {
+ mask_reg_table[wid].erase(it);
+ m_mask_release_erase++;
+ }
+ return;
+ }
+ }
+ m_mask_release_nomatch++;
+ // No matching entry — idempotent no-op (mirrors legacy releaseRegister).
+}
+
+void Scoreboard::releaseSetReg(unsigned wid, unsigned reg,
+ const active_mask_t &mask,
+ bool is_intra_legacy) {
+ if (m_mode == 1) {
+ releaseRegisterMask(wid, reg, mask);
+ } else if (is_intra_legacy) {
+ releaseRegisterSecondary(wid, reg);
+ } else {
+ releaseRegister(wid, reg);
+ }
+}
+
+bool Scoreboard::checkCollisionMask(unsigned wid, const class inst_t *inst,
+ const active_mask_t &mask) const {
+ std::set<int> inst_regs;
+ for (unsigned i = 0; i < inst->outcount; i++) inst_regs.insert(inst->out[i]);
+ for (unsigned j = 0; j < inst->incount; j++) inst_regs.insert(inst->in[j]);
+ if (inst->pred > 0) inst_regs.insert(inst->pred);
+ if (inst->ar1 > 0) inst_regs.insert(inst->ar1);
+ if (inst->ar2 > 0) inst_regs.insert(inst->ar2);
+
+ for (auto reg : inst_regs) {
+ if (reg <= 0) continue;
+ for (const auto &e : mask_reg_table[wid]) {
+ if (e.reg == (unsigned)reg && (e.mask & mask).any()) return true;
+ }
+ }
+ return false;
+}
+
// End-of-run accounting dump.
void Scoreboard::dumpAccounting(FILE* out) const {
unsigned long long primary_remaining = 0;
@@ -268,6 +384,34 @@ void Scoreboard::dumpAccounting(FILE* out) const {
m_sec_release_calls, sec_remaining, m_sec_completed_reservations,
sec_avg_dur, m_sec_max_duration_cycles, m_sec_clear_calls,
m_sec_clear_regs_dropped);
+ // Mode 1 mask-aware scoreboard accounting.
+ unsigned long long mask_remaining_entries = 0;
+ unsigned long long mask_remaining_refcount = 0;
+ for (unsigned w = 0; w < mask_reg_table.size(); w++) {
+ mask_remaining_entries += mask_reg_table[w].size();
+ for (const auto &e : mask_reg_table[w]) mask_remaining_refcount += e.ref_count;
+ }
+ fprintf(out,
+ "mask_scoreboard_accounting sid=%u inserts=%llu inc_refcount=%llu "
+ "release_match=%llu release_nomatch=%llu erase=%llu "
+ "remaining_entries=%llu remaining_refcount=%llu\n",
+ m_sid, m_mask_reserve_inserts, m_mask_reserve_inc_refcount,
+ m_mask_release_match, m_mask_release_nomatch, m_mask_release_erase,
+ mask_remaining_entries, mask_remaining_refcount);
+ // If anything is leaked, dump per-warp leak details.
+ if (mask_remaining_entries > 0) {
+ for (unsigned w = 0; w < mask_reg_table.size(); w++) {
+ if (mask_reg_table[w].empty()) continue;
+ fprintf(out, " mask_leak sid=%u wid=%u entries=%zu:\n", m_sid, w,
+ mask_reg_table[w].size());
+ for (const auto &e : mask_reg_table[w]) {
+ fprintf(out,
+ " reg=%u ref_count=%u resv_cycle=%llu mask_count=%lu mask=%s\n",
+ e.reg, e.ref_count, e.resv_cycle, e.mask.count(),
+ e.mask.to_string().c_str());
+ }
+ }
+ }
}
// Read-only diagnostic. Prints the primary and secondary scoreboard
@@ -290,4 +434,15 @@ void Scoreboard::dump_warp_state(FILE *out, unsigned wid) const {
for (auto r : longopregs[wid]) fprintf(out, " r%u", r);
fprintf(out, "\n");
}
+ // Mode 1: mask_reg_table for this warp.
+ if (wid < mask_reg_table.size() && !mask_reg_table[wid].empty()) {
+ fprintf(out, " mask_reg_table[%u] (size=%zu):\n", wid,
+ mask_reg_table[wid].size());
+ for (const auto &e : mask_reg_table[wid]) {
+ fprintf(out,
+ " reg=%u ref_count=%u resv_cycle=%llu mask_count=%lu mask=%s\n",
+ e.reg, e.ref_count, e.resv_cycle, e.mask.count(),
+ e.mask.to_string().c_str());
+ }
+ }
}
diff --git a/src/gpgpu-sim/scoreboard.h b/src/gpgpu-sim/scoreboard.h
index 3dbb1ce..660587e 100644
--- a/src/gpgpu-sim/scoreboard.h
+++ b/src/gpgpu-sim/scoreboard.h
@@ -41,7 +41,12 @@
class Scoreboard {
public:
- Scoreboard(unsigned sid, unsigned n_warps, class gpgpu_t *gpu);
+ // mode: 0 = legacy (primary + secondary tables), 1 = mask-aware,
+ // 2 = slot-pinned. See scoreboard_redesign_plan.md.
+ Scoreboard(unsigned sid, unsigned n_warps, class gpgpu_t *gpu,
+ unsigned mode = 0);
+
+ unsigned mode() const { return m_mode; }
void reserveRegisters(const warp_inst_t *inst);
void releaseRegisters(const warp_inst_t *inst);
@@ -52,12 +57,35 @@ class Scoreboard {
void printContents() const;
const bool islongop(unsigned warp_id, unsigned regnum);
- // Secondary scoreboard for intra-warp co-issue (split-level tracking)
+ // Secondary scoreboard for intra-warp co-issue (split-level tracking).
+ // Mode 0 (legacy) only — bypassed under modes 1 and 2.
void reserveRegisterSecondary(unsigned wid, unsigned regnum);
void releaseRegisterSecondary(unsigned wid, unsigned regnum);
bool checkCollisionSecondary(unsigned wid, const inst_t *inst) const;
void clearSecondary(unsigned wid);
+ // Mode 1: mask-aware single scoreboard.
+ // Each reservation entry tracks (reg, active_mask, ref_count). Hazard
+ // checks return true iff any of the inst's read/written regs has an
+ // entry whose mask overlaps the candidate's active_mask. See
+ // for_claude/docs/scoreboard_redesign_plan.md for design.
+ void reserveRegisterMask(unsigned wid, unsigned reg,
+ const active_mask_t &mask);
+ void releaseRegisterMask(unsigned wid, unsigned reg,
+ const active_mask_t &mask);
+ bool checkCollisionMask(unsigned wid, const inst_t *inst,
+ const active_mask_t &mask) const;
+ // Mask-agnostic helper: any in-flight writes for this warp under the
+ // active scoreboard table. In mode 1 this checks mask_reg_table; in
+ // mode 0 it falls through to legacy reg_table emptiness.
+ bool pendingWritesAny(unsigned wid) const;
+
+ // Per-set release dispatcher used at writeback. In mode 0, picks
+ // primary vs secondary release based on `is_intra_legacy`. In mode 1,
+ // releases (reg, mask) from mask_reg_table; `is_intra_legacy` ignored.
+ void releaseSetReg(unsigned wid, unsigned reg, const active_mask_t &mask,
+ bool is_intra_legacy);
+
public:
// Accounting / leak-detection (called from gpu-sim end-of-run dump).
// Aggregates reserve/release counts, reservation-duration histogram,
@@ -74,6 +102,7 @@ class Scoreboard {
int get_sid() const { return m_sid; }
unsigned m_sid;
+ unsigned m_mode; // 0=legacy, 1=mask-aware, 2=slot-pinned
// keeps track of pending writes to registers
// indexed by warp id, reg_id => pending write count
@@ -84,6 +113,23 @@ class Scoreboard {
// Secondary path register tracking (for I-Buffer half 1, intra-warp co-issue)
std::vector<std::set<unsigned> > sec_reg_table;
+ // Mode 1: per-warp list of mask-aware reservations.
+ struct mask_resv {
+ unsigned reg;
+ active_mask_t mask;
+ unsigned ref_count;
+ unsigned long long resv_cycle;
+ };
+ std::vector<std::vector<mask_resv> > mask_reg_table;
+
+ // Mode 1 accounting (used by dumpAccounting + dump_warp_state for leak
+ // detection on the deadlock path).
+ unsigned long long m_mask_reserve_inserts; // new entry pushes
+ unsigned long long m_mask_reserve_inc_refcount; // ref_count++ on existing
+ unsigned long long m_mask_release_match; // found and decremented
+ unsigned long long m_mask_release_nomatch; // no matching entry
+ unsigned long long m_mask_release_erase; // ref_count hit 0 + erased
+
class gpgpu_t *m_gpu;
// ---- Accounting (per-shader, scalar; cheap to update) ----
diff --git a/src/gpgpu-sim/shader.cc b/src/gpgpu-sim/shader.cc
index a2a77a7..ec764aa 100644
--- a/src/gpgpu-sim/shader.cc
+++ b/src/gpgpu-sim/shader.cc
@@ -191,7 +191,8 @@ void shader_core_ctx::create_front_pipeline() {
}
void shader_core_ctx::create_schedulers() {
- m_scoreboard = new Scoreboard(m_sid, m_config->max_warps_per_shader, m_gpu);
+ m_scoreboard = new Scoreboard(m_sid, m_config->max_warps_per_shader, m_gpu,
+ m_config->gpgpu_scoreboard_mode);
// scedulers
// must currently occur after all inputs have been initialized.
@@ -1528,21 +1529,31 @@ void shader_core_ctx::co_issue_warp(warp_inst_t *composite,
}
// Reserve scoreboard for the co-issued warp.
- // For intra-warp co-issue (same warp_id), skip reservation — the primary
- // instruction already reserved for this warp, and the scoreboard tracks at
- // warp granularity. Both splits may write to the same register number
- // (which is safe since they operate on exclusive threads), but the
- // scoreboard would abort on a duplicate reservation.
- if (split_id == (unsigned)-1) {
- // Inter-warp co-issue: different warp_id, always safe to reserve
+ if (m_config->gpgpu_scoreboard_mode == 1) {
+ // Mode 1 (mask-aware): both inter-warp and intra-warp use the unified
+ // mask-aware scoreboard. The active_mask carried in temp_inst already
+ // differentiates the two splits' reservations — disjoint-lane writes
+ // get distinct (reg, mask) entries even when they share warp_id.
m_scoreboard->reserveRegisters(&temp_inst);
} else {
- // Intra-warp co-issue: reserve on secondary scoreboard to track
- // dependencies within the secondary split's instruction stream.
- // Cannot use primary scoreboard (would abort on duplicate register names).
- for (unsigned r = 0; r < MAX_OUTPUT_VALUES; r++) {
- if (temp_inst.out[r] > 0) {
- m_scoreboard->reserveRegisterSecondary(warp_id, temp_inst.out[r]);
+ // Mode 0 (legacy):
+ // For intra-warp co-issue (same warp_id), skip primary reservation —
+ // the primary instruction already reserved for this warp, and the
+ // scoreboard tracks at warp granularity. Both splits may write to the
+ // same register number (which is safe since they operate on exclusive
+ // threads), but the scoreboard would abort on a duplicate reservation.
+ if (split_id == (unsigned)-1) {
+ // Inter-warp co-issue: different warp_id, always safe to reserve
+ m_scoreboard->reserveRegisters(&temp_inst);
+ } else {
+ // Intra-warp co-issue: reserve on secondary scoreboard to track
+ // dependencies within the secondary split's instruction stream.
+ // Cannot use primary scoreboard (would abort on duplicate register
+ // names).
+ for (unsigned r = 0; r < MAX_OUTPUT_VALUES; r++) {
+ if (temp_inst.out[r] > 0) {
+ m_scoreboard->reserveRegisterSecondary(warp_id, temp_inst.out[r]);
+ }
}
}
}
@@ -1555,6 +1566,10 @@ void shader_core_ctx::co_issue_warp(warp_inst_t *composite,
// between primary and secondary-split sets that share warp_id).
temp_inst.set_source_inst_on_sets(next_inst);
temp_inst.set_split_id_on_sets(split_id);
+ // Mode 1: stamp the source's full issue mask so per-set writeback can
+ // release the matching mask-aware reservation. Cheap and harmless in
+ // mode 0 (field is unused there).
+ temp_inst.set_source_mask_on_sets(active_mask);
// Mixed-space MEM co-issue (v2): for SHARED coissuers, capture per-lane
// memreqaddr + temp_inst.cycles into each valid set BEFORE merge. The
@@ -1948,7 +1963,20 @@ void scheduler_unit::try_inter_warp_coissue(
}
// Scoreboard check.
- if (m_scoreboard->checkCollision(cand_warp_id, cand_inst)) {
+ // Mode 1 needs the candidate's active mask for hazard intersection;
+ // hoist its lookup before the legacy/mode-1 dispatch.
+ bool sb_collision_inter;
+ if (m_shader->m_config->gpgpu_scoreboard_mode == 1) {
+ const active_mask_t &cand_mask_pre =
+ m_shader->get_active_mask(cand_warp_id, cand_inst);
+ sb_collision_inter = m_scoreboard->checkCollisionMask(cand_warp_id,
+ cand_inst,
+ cand_mask_pre);
+ } else {
+ sb_collision_inter = m_scoreboard->checkCollision(cand_warp_id,
+ cand_inst);
+ }
+ if (sb_collision_inter) {
m_stats->coissue_denied_by_scoreboard[get_sid()]++;
continue;
}
@@ -2091,21 +2119,30 @@ void scheduler_unit::try_intra_warp_coissue(
}
}
- if (m_scoreboard->checkCollisionSecondary(primary_warp_id, sec_inst)) {
- m_stats->coissue_denied_by_scoreboard[get_sid()]++;
- continue;
- }
-
// Stale-mask guard: see commentary on the same fix in
// try_utilization_max_coissue. Cached `sec_mask` may diverge from
// current `split_mask` (lane re-bucketing or split-ID reuse). Dispatch
// only the intersection — lanes in BOTH masks are guaranteed to have
- // their per-thread PC at sec_inst->pc.
+ // their per-thread PC at sec_inst->pc. Hoisted before the scoreboard
+ // check so mode 1 can use the effective mask for hazard detection.
active_mask_t sec_mask_eff;
for (unsigned t = 0; t < MAX_WARP_SIZE; t++) {
if (sec_mask.test(t) && split_mask.test(t)) sec_mask_eff.set(t);
}
if (!sec_mask_eff.any()) continue;
+
+ bool sb_collision;
+ if (m_shader->m_config->gpgpu_scoreboard_mode == 1) {
+ sb_collision = m_scoreboard->checkCollisionMask(primary_warp_id,
+ sec_inst, sec_mask_eff);
+ } else {
+ sb_collision = m_scoreboard->checkCollisionSecondary(primary_warp_id,
+ sec_inst);
+ }
+ if (sb_collision) {
+ m_stats->coissue_denied_by_scoreboard[get_sid()]++;
+ continue;
+ }
unsigned sec_active = sec_mask_eff.count();
unsigned sec_sets_needed = (sec_active + set_width - 1) / set_width;
if (sec_sets_needed > available_sets) {
@@ -2190,7 +2227,18 @@ void scheduler_unit::try_utilization_max_coissue(
&cand_rpc);
if (cand_pc != cand_inst->pc) continue;
- if (m_scoreboard->checkCollision(cand_warp_id, cand_inst)) {
+ bool sb_collision_inter2;
+ if (m_shader->m_config->gpgpu_scoreboard_mode == 1) {
+ const active_mask_t &cand_mask_pre =
+ m_shader->get_active_mask(cand_warp_id, cand_inst);
+ sb_collision_inter2 = m_scoreboard->checkCollisionMask(cand_warp_id,
+ cand_inst,
+ cand_mask_pre);
+ } else {
+ sb_collision_inter2 = m_scoreboard->checkCollision(cand_warp_id,
+ cand_inst);
+ }
+ if (sb_collision_inter2) {
m_stats->coissue_denied_by_scoreboard[get_sid()]++;
continue;
}
@@ -2326,11 +2374,6 @@ void scheduler_unit::try_utilization_max_coissue(
continue;
}
}
- if (m_scoreboard->checkCollisionSecondary(cand_warp_id, sec_inst)) {
- m_stats->coissue_denied_by_scoreboard[get_sid()]++;
- continue;
- }
-
// Stale-mask guard: the cached `sec_mask` is from secondary-fetch
// time and may not match the current splits-table mask. Two
// failure modes:
@@ -2344,12 +2387,27 @@ void scheduler_unit::try_utilization_max_coissue(
// cached mask is stale.
// Both reduce to: trust only lanes present in BOTH masks. The
// ibuffer slot ownership invariant guarantees lanes in `split_mask`
- // are at `split_pc == sec_inst->pc`.
+ // are at `split_pc == sec_inst->pc`. Hoisted before the scoreboard
+ // check so mode 1 can use the effective mask for hazard detection.
active_mask_t sec_mask_eff;
for (unsigned t = 0; t < MAX_WARP_SIZE; t++) {
if (sec_mask.test(t) && split_mask.test(t)) sec_mask_eff.set(t);
}
if (!sec_mask_eff.any()) continue;
+
+ bool sb_collision;
+ if (m_shader->m_config->gpgpu_scoreboard_mode == 1) {
+ sb_collision = m_scoreboard->checkCollisionMask(cand_warp_id,
+ sec_inst,
+ sec_mask_eff);
+ } else {
+ sb_collision = m_scoreboard->checkCollisionSecondary(cand_warp_id,
+ sec_inst);
+ }
+ if (sb_collision) {
+ m_stats->coissue_denied_by_scoreboard[get_sid()]++;
+ continue;
+ }
unsigned sec_active = sec_mask_eff.count();
unsigned sec_needed = (sec_active + set_width - 1) / set_width;
if (sec_needed > available_sets) {
@@ -2548,15 +2606,23 @@ void scheduler_unit::cycle() {
m_scoreboard->clearSecondary(warp_id);
} else {
valid_inst = true;
- if (!m_scoreboard->checkCollision(warp_id, pI)) {
+ // Mode 1 needs the warp's active mask for hazard intersection;
+ // hoist it before the scoreboard check.
+ const active_mask_t &active_mask =
+ m_shader->get_active_mask(warp_id, pI);
+ bool sb_collision_primary;
+ if (m_shader->m_config->gpgpu_scoreboard_mode == 1) {
+ sb_collision_primary =
+ m_scoreboard->checkCollisionMask(warp_id, pI, active_mask);
+ } else {
+ sb_collision_primary = m_scoreboard->checkCollision(warp_id, pI);
+ }
+ if (!sb_collision_primary) {
SCHED_DPRINTF(
"Warp (warp_id %u, dynamic_warp_id %u) passes scoreboard\n",
(*iter)->get_warp_id(), (*iter)->get_dynamic_warp_id());
ready_inst = true;
- const active_mask_t &active_mask =
- m_shader->get_active_mask(warp_id, pI);
-
assert(warp(warp_id).inst_in_pipeline());
if ((pI->op == LOAD_OP) || (pI->op == STORE_OP) ||
@@ -3330,14 +3396,19 @@ void shader_core_ctx::writeback() {
}
// Release scoreboard for primary instruction (covers the outer warp_id)
m_scoreboard->releaseRegisters(pipe_reg);
- // Release scoreboard for ALL co-issued sets (inter-warp AND intra-warp)
+ // Release scoreboard for ALL co-issued sets (inter-warp AND intra-warp).
+ // Dispatcher: mode 0 -> primary release; mode 1 -> mask-aware release
+ // using the set's stamped source_mask. Idempotent; safe to call once
+ // per set even when multiple sets share the same source_inst.
for (unsigned s = 0; s < sets.size(); s++) {
if (!sets[s].valid || sets[s].source_inst == NULL) continue;
unsigned set_wid = sets[s].warp_id;
for (unsigned r = 0; r < MAX_OUTPUT_VALUES; r++) {
if (sets[s].source_inst->out[r] > 0) {
- m_scoreboard->releaseRegister(set_wid,
- sets[s].source_inst->out[r]);
+ m_scoreboard->releaseSetReg(set_wid,
+ sets[s].source_inst->out[r],
+ sets[s].source_mask,
+ /*is_intra_legacy=*/false);
}
}
}
@@ -3359,12 +3430,14 @@ void shader_core_ctx::writeback() {
co_issued_warps_decremented.insert(sets[s].warp_id);
}
} else {
- // Intra-warp: release from secondary scoreboard and dec once
+ // Intra-warp: release from secondary scoreboard (mode 0) or
+ // mask-aware (mode 1) and dec once.
if (!intra_warp_decremented) {
for (unsigned r = 0; r < MAX_OUTPUT_VALUES; r++) {
if (sets[s].source_inst->out[r] > 0) {
- m_scoreboard->releaseRegisterSecondary(
- sets[s].warp_id, sets[s].source_inst->out[r]);
+ m_scoreboard->releaseSetReg(
+ sets[s].warp_id, sets[s].source_inst->out[r],
+ sets[s].source_mask, /*is_intra_legacy=*/true);
}
}
m_warp[warp_id]->dec_inst_in_pipeline();
@@ -3532,6 +3605,11 @@ mem_stage_stall_type ldst_unit::process_cache_access(
[src.out_inst->out[r]]--;
else
m_pending_writes[src.wid][src.out_inst->out[r]]--;
+ // Mode 1: per-mask decrement + release when the inst's own
+ // accesses fully drain (independent of other in-flight insts'
+ // contributions to the aggregate counter).
+ dec_mask_pw_and_maybe_release(src.wid, src.out_inst->out[r],
+ src.source_mask);
}
// release LDGSTS (primary-only: LDGSTS is excluded from MEM co-issue)
@@ -3695,7 +3773,8 @@ void ldst_unit::L1_latency_queue_cycle() {
if (!still_pending) {
m_pending_writes_secondary[src.wid][src_split_mf].erase(
reg);
- m_scoreboard->releaseRegisterSecondary(src.wid, reg);
+ m_scoreboard->releaseSetReg(src.wid, reg, src.source_mask,
+ /*is_intra_legacy=*/true);
any_completed = true;
}
} else {
@@ -3703,10 +3782,14 @@ void ldst_unit::L1_latency_queue_cycle() {
unsigned still_pending = --m_pending_writes[src.wid][reg];
if (!still_pending) {
m_pending_writes[src.wid].erase(reg);
- m_scoreboard->releaseRegister(src.wid, reg);
+ m_scoreboard->releaseSetReg(src.wid, reg, src.source_mask,
+ /*is_intra_legacy=*/false);
any_completed = true;
}
}
+ // Mode 1: per-mask release fires per inst, decoupled from
+ // the aggregate counter above.
+ dec_mask_pw_and_maybe_release(src.wid, reg, src.source_mask);
}
}
if (any_completed) m_core->warp_inst_complete(mf_next->get_inst());
@@ -4259,17 +4342,41 @@ ldst_unit::ldst_unit(mem_fetch_interface *icnt,
mem_config, stats, sid, tpc);
}
+void ldst_unit::dec_mask_pw_and_maybe_release(unsigned wid, unsigned reg,
+ const active_mask_t &mask) {
+ if (m_core->get_config()->gpgpu_scoreboard_mode != 1) return;
+ unsigned long mk = mask.to_ulong();
+ auto wit = m_pending_writes_mask.find(wid);
+ if (wit == m_pending_writes_mask.end()) return;
+ auto rit = wit->second.find(reg);
+ if (rit == wit->second.end()) return;
+ auto mit = rit->second.find(mk);
+ if (mit == rit->second.end()) return;
+ assert(mit->second > 0);
+ if (--(mit->second) == 0) {
+ rit->second.erase(mit);
+ if (rit->second.empty()) {
+ wit->second.erase(rit);
+ if (wit->second.empty()) m_pending_writes_mask.erase(wit);
+ }
+ m_scoreboard->releaseRegisterMask(wid, reg, mask);
+ }
+}
+
ldst_unit::mem_src_t ldst_unit::resolve_source(
const warp_inst_t &inst, unsigned access_src_wid,
unsigned access_src_split_id) const {
mem_src_t r;
r.wid = (access_src_wid == (unsigned)-1) ? inst.warp_id() : access_src_wid;
r.out_inst = &inst;
+ r.source_mask.reset();
// Primary access: (wid == primary AND split_id == -1). Return early with
// out_inst = composite (primary's out[]). Note: split_id is the
// discriminator — a coissuer can have the same warp_id as primary when
// it came from the primary warp's own secondary ibuffer slot.
if (r.wid == inst.warp_id() && access_src_split_id == (unsigned)-1) {
+ // Primary's own mask is on the composite itself.
+ r.source_mask = inst.get_active_mask();
return r;
}
if (!inst.has_simd_sets()) return r;
@@ -4280,6 +4387,7 @@ ldst_unit::mem_src_t ldst_unit::resolve_source(
if (sets[s].warp_id == r.wid &&
sets[s].split_id == access_src_split_id) {
r.out_inst = sets[s].source_inst;
+ r.source_mask = sets[s].source_mask;
break;
}
}
@@ -4487,9 +4595,19 @@ void ldst_unit::issue(register_set &reg_set) {
unsigned n_primary =
n_acc_per_src[std::make_pair(primary_wid, (unsigned)-1)];
if (n_primary > 0) {
+ const bool mode1 =
+ (m_core->get_config()->gpgpu_scoreboard_mode == 1);
+ unsigned long pri_mask_key =
+ mode1 ? inst->get_active_mask().to_ulong() : 0;
for (unsigned r = 0; r < MAX_OUTPUT_VALUES; r++) {
unsigned reg_id = inst->out[r];
- if (reg_id > 0) m_pending_writes[primary_wid][reg_id] += n_primary;
+ if (reg_id > 0) {
+ m_pending_writes[primary_wid][reg_id] += n_primary;
+ if (mode1) {
+ m_pending_writes_mask[primary_wid][reg_id][pri_mask_key] +=
+ n_primary;
+ }
+ }
}
}
}
@@ -4513,6 +4631,10 @@ void ldst_unit::issue(register_set &reg_set) {
// is_intra = "uses secondary scoreboard map" = split came from a
// secondary ibuffer slot. Not tied to warp_id == primary_wid.
bool is_intra = (sets[s].split_id != (unsigned)-1);
+ const bool mode1 =
+ (m_core->get_config()->gpgpu_scoreboard_mode == 1);
+ unsigned long set_mask_key =
+ mode1 ? sets[s].source_mask.to_ulong() : 0;
for (unsigned r = 0; r < MAX_OUTPUT_VALUES; r++) {
unsigned reg_id = sets[s].source_inst->out[r];
if (reg_id > 0) {
@@ -4522,15 +4644,29 @@ void ldst_unit::issue(register_set &reg_set) {
} else {
m_pending_writes[sets[s].warp_id][reg_id] += n_src;
}
+ if (mode1) {
+ m_pending_writes_mask[sets[s].warp_id][reg_id][set_mask_key] +=
+ n_src;
+ }
}
}
}
} else if (primary_is_tracked_load) {
// Legacy (non-co-issue) path: single-warp pending_writes.
unsigned n_accesses = inst->accessq_count();
+ const bool mode1 =
+ (m_core->get_config()->gpgpu_scoreboard_mode == 1);
+ unsigned long pri_mask_key =
+ mode1 ? inst->get_active_mask().to_ulong() : 0;
for (unsigned r = 0; r < MAX_OUTPUT_VALUES; r++) {
unsigned reg_id = inst->out[r];
- if (reg_id > 0) m_pending_writes[primary_wid][reg_id] += n_accesses;
+ if (reg_id > 0) {
+ m_pending_writes[primary_wid][reg_id] += n_accesses;
+ if (mode1) {
+ m_pending_writes_mask[primary_wid][reg_id][pri_mask_key] +=
+ n_accesses;
+ }
+ }
}
}
@@ -4631,7 +4767,9 @@ void ldst_unit::writeback() {
if (!still_pending) {
m_pending_writes_secondary[src_i.wid][src_split_i]
.erase(reg);
- m_scoreboard->releaseRegisterSecondary(src_i.wid, reg);
+ m_scoreboard->releaseSetReg(src_i.wid, reg,
+ src_i.source_mask,
+ /*is_intra_legacy=*/true);
insn_completed = true;
}
} else {
@@ -4640,10 +4778,14 @@ void ldst_unit::writeback() {
--m_pending_writes[src_i.wid][reg];
if (!still_pending) {
m_pending_writes[src_i.wid].erase(reg);
- m_scoreboard->releaseRegister(src_i.wid, reg);
+ m_scoreboard->releaseSetReg(src_i.wid, reg,
+ src_i.source_mask,
+ /*is_intra_legacy=*/false);
insn_completed = true;
}
}
+ // Mode 1: per-mask release fires per inst.
+ dec_mask_pw_and_maybe_release(src_i.wid, reg, src_i.source_mask);
} else if (m_next_wb.m_is_ldgsts) {
// LDGSTS excluded from co-issue; source_list always size<=1
m_pending_ldgsts[primary_wid][m_next_wb.pc]
@@ -4663,7 +4805,9 @@ void ldst_unit::writeback() {
(void)is_intra_coissued; // unused on shared path
for (unsigned r = 0; r < MAX_OUTPUT_VALUES; r++) {
if (src.out_inst->out[r] > 0) {
- m_scoreboard->releaseRegister(src.wid, src.out_inst->out[r]);
+ m_scoreboard->releaseSetReg(src.wid, src.out_inst->out[r],
+ src.source_mask,
+ /*is_intra_legacy=*/false);
insn_completed = true;
}
}
@@ -4682,16 +4826,13 @@ void ldst_unit::writeback() {
sets[s].split_id);
if (released.count(key)) continue;
released.insert(key);
- // Secondary slot coissuer → secondary scoreboard map.
+ // Secondary slot coissuer → secondary scoreboard map (mode 0).
bool coissued_intra = (sets[s].split_id != (unsigned)-1);
for (unsigned r = 0; r < MAX_OUTPUT_VALUES; r++) {
if (sets[s].source_inst->out[r] > 0) {
- if (coissued_intra)
- m_scoreboard->releaseRegisterSecondary(
- sets[s].warp_id, sets[s].source_inst->out[r]);
- else
- m_scoreboard->releaseRegister(
- sets[s].warp_id, sets[s].source_inst->out[r]);
+ m_scoreboard->releaseSetReg(
+ sets[s].warp_id, sets[s].source_inst->out[r],
+ sets[s].source_mask, /*is_intra_legacy=*/coissued_intra);
insn_completed = true;
}
}
@@ -5013,10 +5154,9 @@ void ldst_unit::cycle() {
for (unsigned r = 0; r < MAX_OUTPUT_VALUES; r++) {
unsigned reg = sets[s].source_inst->out[r];
if (reg == 0) continue;
- if (is_intra)
- m_scoreboard->releaseRegisterSecondary(sets[s].warp_id, reg);
- else
- m_scoreboard->releaseRegister(sets[s].warp_id, reg);
+ m_scoreboard->releaseSetReg(sets[s].warp_id, reg,
+ sets[s].source_mask,
+ /*is_intra_legacy=*/is_intra);
}
}
pipe_reg.set_shared_side_released(true);
@@ -5188,10 +5328,9 @@ void ldst_unit::cycle() {
for (unsigned r = 0; r < MAX_OUTPUT_VALUES; r++) {
unsigned reg_id = sets[s].source_inst->out[r];
if (reg_id == 0) continue;
- if (is_intra)
- m_scoreboard->releaseRegisterSecondary(sets[s].warp_id, reg_id);
- else
- m_scoreboard->releaseRegister(sets[s].warp_id, reg_id);
+ m_scoreboard->releaseSetReg(sets[s].warp_id, reg_id,
+ sets[s].source_mask,
+ /*is_intra_legacy=*/is_intra);
}
}
}
@@ -5331,16 +5470,14 @@ void ldst_unit::cycle() {
sets[s].split_id);
if (released.count(key)) continue;
released.insert(key);
- // Secondary slot → secondary scoreboard map.
+ // Secondary slot → secondary scoreboard map (mode 0).
bool is_intra = (sets[s].split_id != (unsigned)-1);
for (unsigned r = 0; r < MAX_OUTPUT_VALUES; r++) {
unsigned reg_id = sets[s].source_inst->out[r];
if (reg_id == 0) continue;
- if (is_intra)
- m_scoreboard->releaseRegisterSecondary(sets[s].warp_id,
- reg_id);
- else
- m_scoreboard->releaseRegister(sets[s].warp_id, reg_id);
+ m_scoreboard->releaseSetReg(sets[s].warp_id, reg_id,
+ sets[s].source_mask,
+ /*is_intra_legacy=*/is_intra);
}
}
}
@@ -5423,11 +5560,9 @@ void ldst_unit::cycle() {
for (unsigned r = 0; r < MAX_OUTPUT_VALUES; r++) {
unsigned reg_id = sets[s].source_inst->out[r];
if (reg_id == 0) continue;
- if (is_intra)
- m_scoreboard->releaseRegisterSecondary(sets[s].warp_id,
- reg_id);
- else
- m_scoreboard->releaseRegister(sets[s].warp_id, reg_id);
+ m_scoreboard->releaseSetReg(sets[s].warp_id, reg_id,
+ sets[s].source_mask,
+ /*is_intra_legacy=*/is_intra);
}
}
}
diff --git a/src/gpgpu-sim/shader.h b/src/gpgpu-sim/shader.h
index 51ed214..ebcfc8d 100644
--- a/src/gpgpu-sim/shader.h
+++ b/src/gpgpu-sim/shader.h
@@ -1659,13 +1659,27 @@ class ldst_unit : public pipelined_simd_unit {
mem_stage_stall_type process_memory_access_queue_l1cache(l1_cache *cache,
warp_inst_t &inst);
+ // Mode 1 helper: decrement m_pending_writes_mask[wid][reg][mask] and, if
+ // it hits zero, release the (reg, mask) entry in the mask-aware
+ // scoreboard. No-op in mode 0. Inline here so the compiler can elide the
+ // mode-0 path entirely.
+ void dec_mask_pw_and_maybe_release(unsigned wid, unsigned reg,
+ const active_mask_t &mask);
+
// MEM co-issue: resolve which source warp + instruction an access/mem_fetch
// belongs to (for routing pending_writes / scoreboard release back to the
// originating warp under composites). Returns {src_wid, src_out_inst}.
// For non-composite or primary-source accesses, returns {primary_wid, &inst}.
// split_id differentiates intra-warp co-issued sets (same wid as primary)
// from the primary set itself; use (unsigned)-1 when not applicable.
- struct mem_src_t { unsigned wid; const inst_t *out_inst; };
+ struct mem_src_t {
+ unsigned wid;
+ const inst_t *out_inst;
+ // Mode 1: source_inst's full issue mask, looked up from the
+ // matching simd_set's source_mask. Empty for primary access (which
+ // uses pipe_reg's own get_active_mask() at release time).
+ active_mask_t source_mask;
+ };
mem_src_t resolve_source(const warp_inst_t &inst,
unsigned access_src_wid,
unsigned access_src_split_id) const;
@@ -1721,6 +1735,19 @@ class ldst_unit : public pipelined_simd_unit {
std::map<unsigned /*split_id*/,
std::map<unsigned /*reg*/, unsigned /*count*/>>>
m_pending_writes_secondary;
+ // Mode 1 (mask-aware scoreboard) pending-writes counter, keyed by
+ // (wid, reg, mask). Mode 0's m_pending_writes aggregates across all
+ // in-flight insts to the same (wid, reg), which is fine for mode 0's
+ // single-entry-per-(wid,reg) scoreboard but causes leaks under mode 1's
+ // per-mask scoreboard: when the aggregate hits zero only the
+ // last-retiring access's mask gets a scoreboard release; other masks
+ // leak. This per-mask counter fires the mask-aware release at the
+ // correct time for each inst. Updated only when mode==1.
+ std::map<unsigned /*wid*/,
+ std::map<unsigned /*reg*/,
+ std::map<unsigned long /*mask.to_ulong()*/,
+ unsigned /*count*/>>>
+ m_pending_writes_mask;
unsigned m_writeback_arb; // round-robin arbiter for writeback contention
// between L1T, L1C, shared
unsigned m_num_writeback_clients;
@@ -1851,6 +1878,13 @@ class shader_core_config : public core_config {
gpgpu_co_issue_priority);
abort();
}
+ if (gpgpu_scoreboard_mode > 2) {
+ fprintf(stderr,
+ "GPGPU-Sim: invalid -gpgpu_scoreboard_mode %u; must be "
+ "0 (legacy), 1 (mask-aware), or 2 (slot-pinned)\n",
+ gpgpu_scoreboard_mode);
+ abort();
+ }
if (gpgpu_enable_compaction && gpgpu_compaction_mode == 0) {
gpgpu_compaction_mode = 2;
}
@@ -2014,6 +2048,7 @@ class shader_core_config : public core_config {
unsigned gpgpu_co_issue_priority; // 0=greedy..4=same-PC
bool gpgpu_simd_partitioning_debug; // verbose SIMD_SETS printf gate
bool gpgpu_mem_coissue; // enable MEM/LDST co-issue path
+ unsigned gpgpu_scoreboard_mode; // 0=legacy, 1=mask-aware, 2=slot-pinned
unsigned n_simt_cores_per_cluster;
unsigned n_simt_clusters;