Skip to content

Commit 1ab9949

Browse files
committed
Enforce register-file intra-index constraint for mov vs compute
1 parent 47fbe37 commit 1ab9949

File tree

2 files changed

+162
-42
lines changed

2 files changed

+162
-42
lines changed

lib/NeuraDialect/Mapping/MappingState.cpp

Lines changed: 157 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,35 +116,186 @@ void MappingState::unbindOp(Operation *op) {
116116
}
117117

118118
bool MappingState::isAvailableAcrossTime(const MappingLoc &loc) const {
119-
// For spatial mapping, checks if the location is available across all time.
119+
// For spatial mapping, checks if the location stays available at all time steps.
120120
if (this->is_spatial_only) {
121121
for (int t = 0; t < II * kMaxSteps; ++t) {
122122
MappingLoc check_loc = {loc.resource, t};
123123
auto it = occupied_locs.find(check_loc);
124124
if (it != occupied_locs.end()) {
125-
// Check if all existing occupy statuses allow new single-cycle op
125+
// Rejects the location if any non-shareable occupancy is present.
126126
for (const auto &entry : it->second) {
127127
if (entry.first != IN_PIPE_OCCUPY) {
128128
return false;
129129
}
130130
}
131131
}
132132
}
133+
134+
// Enforces identical intra-index when a mov read and a compute read share a register file.
135+
if (loc.resource->getKind() == ResourceKind::Register) {
136+
Register *reg = static_cast<Register *>(loc.resource);
137+
RegisterFile *reg_file = reg->getRegisterFile();
138+
if (reg_file) {
139+
auto isMovOp = [](Operation *op) {
140+
// Identifies bypass routing ops by name to avoid relying on generated op classes.
141+
if (!op) {
142+
return false;
143+
}
144+
auto name = op->getName().getStringRef();
145+
return name == "neura.data_mov" || name == "neura.ctrl_mov";
146+
};
147+
148+
auto violatesClusterReadConstraintAt = [&](int t) -> bool {
149+
// Tracks whether sibling registers in the file are used by mov or compute in this slot.
150+
bool otherRegHasMov = false;
151+
bool otherRegHasCompute = false;
152+
153+
for (const auto &[_, sibling] : reg_file->getRegisters()) {
154+
if (sibling == reg) {
155+
continue;
156+
}
157+
auto itOcc = occupied_locs.find({sibling, t});
158+
if (itOcc == occupied_locs.end()) {
159+
continue;
160+
}
161+
162+
for (const auto &entry : itOcc->second) {
163+
Operation *occOp = entry.second;
164+
if (isMovOp(occOp)) {
165+
otherRegHasMov = true;
166+
} else {
167+
otherRegHasCompute = true;
168+
}
169+
170+
// Rejects the slot once mov and compute are found on different sibling registers.
171+
if (otherRegHasMov && otherRegHasCompute) {
172+
return true;
173+
}
174+
}
175+
}
176+
177+
auto itThis = occupied_locs.find({reg, t});
178+
bool thisRegHasMov = false;
179+
bool thisRegHasCompute = false;
180+
if (itThis != occupied_locs.end()) {
181+
for (const auto &entry : itThis->second) {
182+
Operation *occOp = entry.second;
183+
if (isMovOp(occOp)) {
184+
thisRegHasMov = true;
185+
} else {
186+
thisRegHasCompute = true;
187+
}
188+
}
189+
}
190+
191+
// Rejects the slot when this register mixes with a sibling of the opposite kind.
192+
if (otherRegHasCompute && thisRegHasMov) {
193+
return true;
194+
}
195+
if (otherRegHasMov && thisRegHasCompute) {
196+
return true;
197+
}
198+
199+
return false;
200+
};
201+
202+
for (int t = 0; t < II * kMaxSteps; ++t) {
203+
if (violatesClusterReadConstraintAt(t)) {
204+
return false;
205+
}
206+
}
207+
}
208+
}
209+
133210
return true;
134211
} else {
135-
// Checks the availability across time domain.
212+
// Checks the availability across the modulo-II time domain.
136213
for (int t = loc.time_step % II; t < II * kMaxSteps; t += II) {
137214
MappingLoc check_loc = {loc.resource, t};
138215
auto it = occupied_locs.find(check_loc);
139216
if (it != occupied_locs.end()) {
140-
// Check if all existing occupy statuses allow new single-cycle op
217+
// Rejects the location if any non-shareable occupancy is present.
141218
for (const auto &entry : it->second) {
142219
if (entry.first != IN_PIPE_OCCUPY) {
143220
return false;
144221
}
145222
}
146223
}
147224
}
225+
226+
// Enforces identical intra-index when a mov read and a compute read share a register file.
227+
if (loc.resource->getKind() == ResourceKind::Register) {
228+
Register *reg = static_cast<Register *>(loc.resource);
229+
RegisterFile *reg_file = reg->getRegisterFile();
230+
if (reg_file) {
231+
auto isMovOp = [](Operation *op) {
232+
// Identifies bypass routing ops by name to avoid relying on generated op classes.
233+
if (!op) {
234+
return false;
235+
}
236+
auto name = op->getName().getStringRef();
237+
return name == "neura.data_mov" || name == "neura.ctrl_mov";
238+
};
239+
240+
auto violatesClusterReadConstraintAt = [&](int t) -> bool {
241+
// Tracks whether sibling registers in the file are used by mov or compute in this slot.
242+
bool otherRegHasMov = false;
243+
bool otherRegHasCompute = false;
244+
245+
for (const auto &[_, sibling] : reg_file->getRegisters()) {
246+
if (sibling == reg) {
247+
continue;
248+
}
249+
auto itOcc = occupied_locs.find({sibling, t});
250+
if (itOcc == occupied_locs.end()) {
251+
continue;
252+
}
253+
254+
for (const auto &entry : itOcc->second) {
255+
Operation *occOp = entry.second;
256+
if (isMovOp(occOp)) {
257+
otherRegHasMov = true;
258+
} else {
259+
otherRegHasCompute = true;
260+
}
261+
if (otherRegHasMov && otherRegHasCompute) {
262+
return true;
263+
}
264+
}
265+
}
266+
267+
auto itThis = occupied_locs.find({reg, t});
268+
bool thisRegHasMov = false;
269+
bool thisRegHasCompute = false;
270+
if (itThis != occupied_locs.end()) {
271+
for (const auto &entry : itThis->second) {
272+
Operation *occOp = entry.second;
273+
if (isMovOp(occOp)) {
274+
thisRegHasMov = true;
275+
} else {
276+
thisRegHasCompute = true;
277+
}
278+
}
279+
}
280+
281+
// Rejects the slot when this register mixes with a sibling of the opposite kind.
282+
if (otherRegHasCompute && thisRegHasMov) {
283+
return true;
284+
}
285+
if (otherRegHasMov && thisRegHasCompute) {
286+
return true;
287+
}
288+
return false;
289+
};
290+
291+
for (int t = loc.time_step % II; t < II * kMaxSteps; t += II) {
292+
if (violatesClusterReadConstraintAt(t)) {
293+
return false;
294+
}
295+
}
296+
}
297+
}
298+
148299
return true;
149300
}
150301
}
@@ -159,11 +310,11 @@ bool MappingState::isAvailableForOccupyStatus(const MappingLoc &loc,
159310
return true;
160311
}
161312

162-
// Check against all existing entries at this location
313+
// Checks against all existing entries at this location
163314
for (const auto &entry : it->second) {
164315
int existing_status = entry.first;
165316

166-
// Implement the pipeline-aware availability rules:
317+
// Implements the pipeline-aware availability rules:
167318
// - SINGLE_OCCUPY (0): exclusive, no other op can share
168319
// - START_PIPE_OCCUPY (1): cannot coexist with SINGLE or another START
169320
// - END_PIPE_OCCUPY (2): cannot coexist with SINGLE or another END

lib/NeuraDialect/Mapping/mapping_util.cpp

Lines changed: 5 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -551,43 +551,12 @@ bool mlir::neura::tryRouteBackwardMove(Operation *mov_op, MappingLoc src_loc,
551551
Register *mlir::neura::getAvailableRegister(const MappingState &state,
552552
Tile *tile, int start_time,
553553
int exclusive_end_time) {
554-
// Collect registers occupied by computations (non-MOV operations) on this
555-
// tile within the time range. These represent FU-bound reads that share
556-
// the register file's single read port with any routing bypass.
557-
std::set<Register *> computation_regs;
554+
// The single-read-port constraint (only one register per RegisterFile may be
555+
// used at a time) is now enforced inside isAvailableAcrossTime(), so we only
556+
// need to find the first register that is free across the requested range.
558557
for (Register *reg : tile->getRegisters()) {
559-
for (int t = start_time; t < exclusive_end_time; ++t) {
560-
auto op = state.getOpAt({reg, t});
561-
if (!op.has_value()) continue;
562-
Operation *mapped_op = op.value();
563-
if (isa<neura::DataMovOp>(mapped_op) || isa<neura::CtrlMovOp>(mapped_op))
564-
continue;
565-
computation_regs.insert(reg);
566-
}
567-
}
568-
569-
for (Register *reg : tile->getRegisters()) {
570-
if (!state.isAvailableAcrossTimeInRange(reg, start_time,
571-
exclusive_end_time))
572-
continue;
573-
574-
// Single-read-port constraint: if a computation already reads from the
575-
// same register file (cluster), the bypass must use the identical register.
576-
// If from a different register file, no constraint applies.
577-
RegisterFile *candidate_file = reg->getRegisterFile();
578-
bool conflict = false;
579-
for (Register *comp_reg : computation_regs) {
580-
if (comp_reg->getRegisterFile() != candidate_file)
581-
continue;
582-
// Same register file — only the exact same register is allowed.
583-
if (comp_reg != reg) {
584-
conflict = true;
585-
break;
586-
}
587-
}
588-
if (conflict) continue;
589-
590-
return reg;
558+
if (state.isAvailableAcrossTimeInRange(reg, start_time, exclusive_end_time))
559+
return reg;
591560
}
592561
return nullptr;
593562
}

0 commit comments

Comments
 (0)