Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
82c4a77
Enforce single-read-port constraint in mapping algorithm
guosran Mar 18, 2026
4afbfdd
Enforce register-file intra-index constraint for mov vs compute
guosran Mar 19, 2026
dda69d8
feat: Implement register file occupancy tracking in MappingState, add…
guosran Mar 19, 2026
d26c4cc
Replaced autos with explicit data types
guosran Mar 19, 2026
cc551ee
feat: Enhance register occupancy tracking and add CGRA placement util…
guosran Mar 21, 2026
d6ee173
Replace RegClusterOccupyStatus with Operation* for register occupancy…
guosran Mar 22, 2026
4f64662
Fix shell command error messages in MLIR fusion test cases
guosran Mar 23, 2026
02d974f
Split reg_file map into read/write; allow same-register sharing; upda…
guosran Mar 23, 2026
af2cc0a
Implement read/write separation for register occupancy tracking
guosran Mar 24, 2026
b18095e
Merge remote-tracking branch 'origin/main' into feature/register-bypa…
guosran Mar 24, 2026
cba04b5
Resolved conflicts and implemented feedbacks
guosran Mar 24, 2026
6fb1032
Update mapping utilities and state, refresh test files.
guosran Mar 24, 2026
dac5ca8
Updated tests
guosran Mar 24, 2026
55cd94b
Refactor code for improved readability and consistency in MappingStat…
guosran Mar 25, 2026
7c0d4ef
Refactor mapping_util.cpp for improved type clarity and consistency
guosran Mar 25, 2026
d288203
Refactor MLIR test cases and add script for updating CHECK lines
guosran Mar 25, 2026
228105e
Refactor MappingState and mapping_util for improved clarity and consi…
guosran Mar 25, 2026
82cf52f
Enhance function signatures in MappingState and mapping_util for impr…
guosran Mar 25, 2026
103f489
Delete update_test_checks.py
guosran Mar 25, 2026
411f39f
Remove accidentally committed = file
guosran Mar 25, 2026
d9f4461
Update Zeonica_Testbench submodule to main branch
guosran Mar 25, 2026
2ccd34e
Sync Zeonica_Testbench submodule to match main branch (1e98cf0)
guosran Mar 25, 2026
1f90f12
Merge remote-tracking branch 'origin/main' into feature/register-bypa…
guosran Mar 25, 2026
872e387
Resolved merge conflicts
guosran Mar 25, 2026
6676bab
Fix formatting in comments for clarity in MappingState.cpp and fir_ke…
guosran Mar 26, 2026
048e313
Update tests
guosran Mar 26, 2026
76182e4
Edit variable names
guosran Mar 26, 2026
8510547
Rename `op` parameter to `move_op`
guosran Mar 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 86 additions & 5 deletions include/NeuraDialect/Mapping/MappingState.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@

#include "NeuraDialect/Architecture/Architecture.h"
#include "mlir/IR/Operation.h"
#include "NeuraDialect/NeuraOps.h"
#include "llvm/Support/raw_ostream.h"
#include <optional>
#include <unordered_map>
#include <vector>

namespace mlir {
Expand All @@ -16,7 +18,6 @@ namespace neura {
#define START_PIPE_OCCUPY 1 // A multi-cycle op starts in the FU
#define END_PIPE_OCCUPY 2 // A multi-cycle op ends in the FU
#define IN_PIPE_OCCUPY 3 // A multi-cycle op is occupying the FU (pipelined)

// Represents a spatial-temporal location: (resource, time_step)
struct MappingLoc {
BasicResource *resource;
Expand Down Expand Up @@ -83,16 +84,17 @@ class MappingState {
// Note that the check is performed in II granularity.
// For example, if II is 4, and we want to check (tile 2, step 5), then
// it will check (tile 2, step 1), (tile 2, step 5), (tile 2, step 9), etc.
bool isAvailableAcrossTime(const MappingLoc &loc) const;
bool isAvailableAcrossTime(const MappingLoc &loc,
neura::DataMovOp move_op = nullptr) const;

// Checks if a location is available for a specific occupy status.
// This implements the pipeline-aware availability checking:
// - SINGLE_OCCUPY: only available if location is completely free
// - START_PIPE_OCCUPY: available if free or IN_PIPE_OCCUPY or END_PIPE_OCCUPY
// - END_PIPE_OCCUPY: available if free or IN_PIPE_OCCUPY or START_PIPE_OCCUPY
// - IN_PIPE_OCCUPY: always available (can pipeline with any status)
bool isAvailableForOccupyStatus(const MappingLoc &loc,
int new_occupy_status) const;
bool isAvailableForOccupyStatus(const MappingLoc &loc, int new_occupy_status,
neura::DataMovOp move_op = nullptr) const;

// Gets the occupy status at a specific location across time domain.
// Returns -1 if the location is not occupied.
Expand All @@ -102,7 +104,20 @@ class MappingState {
// This function leverages the isAvailableAcrossTime function in each
// time step.
bool isAvailableAcrossTimeInRange(BasicResource *resource, int start_time,
int exclusive_end_time) const;
int exclusive_end_time,
neura::DataMovOp move_op = nullptr) const;

// Checks availability of a register's cluster write port across the relevant
// time steps. Returns false if a DIFFERENT register in the same
// RegisterFile is already writing at a congruent time slot. Multiple writes
// to the SAME register are allowed (idempotent).
bool isRegisterWriteAvailableAcrossTime(Register *reg, int start_time) const;

// Checks availability of a register's cluster read port across the relevant
// time steps. Returns false if a DIFFERENT register in the same
// RegisterFile is already reading at a congruent time slot. Multiple reads
// from the SAME register are allowed (shared read).
bool isRegisterReadAvailableAcrossTime(Register *reg, int exclusive_end_time) const;

// Gets the operation at a specific (tile/link, time_step) location.
std::optional<Operation *> getOpAt(MappingLoc loc) const;
Expand Down Expand Up @@ -152,6 +167,17 @@ class MappingState {
const std::map<Operation *, std::vector<MappingLoc>> &getOpToLocs() const {
return this->op_to_locs;
}
const std::unordered_map<
RegisterFile *, std::unordered_map<int, std::pair<Register *, int>>> &
getRegFileWriteToOccupyOperations() const {
return this->reg_file_write_to_occupy_operations;
}

const std::unordered_map<
RegisterFile *, std::unordered_map<int, std::pair<Register *, int>>> &
getRegFileReadToOccupyOperations() const {
return this->reg_file_read_to_occupy_operations;
}

// Setters for state information.
void setOccupiedLocs(
Expand All @@ -166,6 +192,19 @@ class MappingState {
const std::map<Operation *, std::vector<MappingLoc>> &op_to_locs) {
this->op_to_locs = op_to_locs;
}
void setRegFileWriteToOccupyOperations(
const std::unordered_map<
RegisterFile *, std::unordered_map<int, std::pair<Register *, int>>>
&records) {
this->reg_file_write_to_occupy_operations = records;
}

void setRegFileReadToOccupyOperations(
const std::unordered_map<
RegisterFile *, std::unordered_map<int, std::pair<Register *, int>>>
&records) {
this->reg_file_read_to_occupy_operations = records;
}

private:
// Initiation interval.
Expand All @@ -178,6 +217,42 @@ class MappingState {
std::map<MappingLoc, std::vector<std::pair<int, Operation *>>> occupied_locs;
std::map<MappingLoc, Operation *> loc_to_op;
std::map<Operation *, std::vector<MappingLoc>> op_to_locs;

// Record table for register cluster write occupancy, keyed by (RegisterFile*,
// time_step % II). Maps to the Register* that is writing to the cluster at
// that canonical time slot. At most one writer is allowed per cluster per
// slot, enforcing the hardware constraint that a register cluster supports
// only a single write port per cycle. However, multiple writes to the SAME
// register are allowed (idempotent). Storing the reference count as the pair
// value ensures proper tracking during overlaps or backtracking.
std::unordered_map<RegisterFile *,
std::unordered_map<int, std::pair<Register *, int>>>
reg_file_write_to_occupy_operations;

// Record table for register cluster read occupancy, keyed by (RegisterFile*,
// time_step % II). Maps to the Register* that is reading from the cluster at
// that canonical time slot. Multiple reads from the SAME register are allowed
// (shared read), but reads from DIFFERENT registers in the same cluster are
// rejected (only one read port).
std::unordered_map<RegisterFile *,
std::unordered_map<int, std::pair<Register *, int>>>
reg_file_read_to_occupy_operations;

// Records the write operation for a register cluster slot.
// Returns false if the cluster slot is already occupied by a writer.
bool addWriteToRegFileRecord(Register *reg, int time_step);

// Records the read operation for a register cluster slot.
// Returns false if the cluster slot is already occupied by a reader.
bool addReadToRegFileRecord(Register *reg, int time_step);

// Removes the write operation from a register cluster slot
// when the op is unbound/released.
void removeWriteFromRegFileRecord(Register *reg, int time_step);

// Removes the read operation from a register cluster slot
// when the op is unbound/released.
void removeReadFromRegFileRecord(Register *reg, int time_step);
};

} // namespace neura
Expand All @@ -199,6 +274,12 @@ class MappingStateSnapshot {
std::map<MappingLoc, std::vector<std::pair<int, Operation *>>> occupied_locs;
std::map<MappingLoc, Operation *> loc_to_op;
std::map<Operation *, std::vector<MappingLoc>> op_to_locs;
std::unordered_map<RegisterFile *,
std::unordered_map<int, std::pair<Register *, int>>>
reg_file_write_to_occupy_operations;
std::unordered_map<RegisterFile *,
std::unordered_map<int, std::pair<Register *, int>>>
reg_file_read_to_occupy_operations;
};
} // namespace neura
} // namespace mlir
Expand Down
12 changes: 11 additions & 1 deletion include/NeuraDialect/Mapping/mapping_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,18 @@ bool canReachLocInTime(const std::vector<Operation *> &producers,
// Gets an available register (for the given time range) in the given tile.
// The end_time is exclusive, meaning the register should be available
// until end_time - 1. Returns nullptr if no available register found.
//
// The optional `move_op` parameter is the DataMovOp being routed. It is forwarded
// to MappingState::isAvailableAcrossTime() / isAvailableForOccupyStatus() so
// that multiple DataMovOps carrying the same materialized source value can
// share a single physical register. When `move_op` is non-null the availability
// check recognises that two DataMovOps reading the identical value do not
// actually conflict, because the single register read port broadcasts the
// value to all consumers. Passing nullptr disables this sharing and falls
// back to the strict one-occupant-per-register rule.
Register *getAvailableRegister(const MappingState &mapping_state, Tile *tile,
int start_time, int exclusive_end_time);
int start_time, int exclusive_end_time,
neura::DataMovOp move_op = nullptr);

// Gets the execution latency of an operation from its "latency" attribute.
// Returns 1 (single-cycle) if the attribute is not present.
Expand Down
Loading
Loading