Skip to content
Open
201 changes: 163 additions & 38 deletions src/dpl/src/NegotiationLegalizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "infrastructure/Grid.h"
#include "infrastructure/Objects.h"
#include "infrastructure/Padding.h"
#include "infrastructure/architecture.h"
#include "infrastructure/network.h"
#include "odb/db.h"
#include "odb/geom.h"
Expand Down Expand Up @@ -533,18 +534,39 @@ bool NegotiationLegalizer::initFromDb()
grid_w_ = dpl_grid->getRowSiteCount().v;
grid_h_ = dpl_grid->getRowCount().v;

// Assign power-rail types using row-index parity (VSS at even rows).
// Replace with explicit LEF pg_pin parsing for advanced PDKs.
// Assign power-rail types from DB row orientations.
// R0/MY = right-side-up → VSS rail at bottom; MX/R180 = flipped → VDD at
// bottom. Rows missing from the DB default to VSS.
row_rail_.clear();
row_rail_.resize(grid_h_);
for (int r = 0; r < grid_h_; ++r) {
row_rail_[r] = (r % 2 == 0) ? NLPowerRailType::kVss : NLPowerRailType::kVdd;
row_rail_.resize(grid_h_, NLPowerRailType::kVss);
for (auto* db_row : block->getRows()) {
const int y_dbu = db_row->getOrigin().y() - die_ylo_;
if (y_dbu < 0 || y_dbu % row_height_ != 0) {
continue;
}
const int r = y_dbu / row_height_;
if (r >= grid_h_) {
continue;
}
const auto orient = db_row->getOrient();
row_rail_[r]
= (orient == odb::dbOrientType::MX || orient == odb::dbOrientType::R180)
? NLPowerRailType::kVdd
: NLPowerRailType::kVss;
}

// Build NegCell records from all placed instances.
cells_.clear();
cells_.reserve(block->getInsts().size());

// Cache region boundaries converted to grid coordinates, keyed by dbRegion*.
struct RegionRectInline
{
int xlo, ylo, xhi, yhi;
};
std::unordered_map<odb::dbRegion*, std::vector<RegionRectInline>>
region_rect_cache;

for (auto* db_inst : block->getInsts()) {
const auto status = db_inst->getPlacementStatus();
if (status == odb::dbPlacementStatus::NONE) {
Expand Down Expand Up @@ -624,7 +646,54 @@ bool NegotiationLegalizer::initFromDb()
// in original DPL.
// they achieve the same objective, and the previous is more simple,
// consider replacing this.
if (!isValidSite(cell.init_x, cell.init_y)) {
// For region-constrained cells, collect the region rects in grid
// coordinates so the BFS below can verify containment.
// initFenceRegions() has not run yet, so we read ODB directly.
// Instances reach their region via a GROUP, not via dbInst::region_,
// so we must check both paths.
odb::dbRegion* odb_region = db_inst->getRegion();
if (odb_region == nullptr) {
auto* grp = db_inst->getGroup();
if (grp != nullptr) {
odb_region = grp->getRegion();
}
}
// Look up (or populate) the cache for this region.
const std::vector<RegionRectInline>* region_rects_ptr = nullptr;
if (odb_region != nullptr) {
auto it = region_rect_cache.find(odb_region);
if (it == region_rect_cache.end()) {
std::vector<RegionRectInline> rects;
for (auto* box : odb_region->getBoundaries()) {
RegionRectInline r;
r.xlo = (box->xMin() - die_xlo_) / site_width_;
r.ylo = (box->yMin() - die_ylo_) / row_height_;
r.xhi = (box->xMax() - die_xlo_) / site_width_;
r.yhi = (box->yMax() - die_ylo_) / row_height_;
rects.push_back(r);
}
it = region_rect_cache.emplace(odb_region, std::move(rects)).first;
}
region_rects_ptr = &it->second;
}
Comment on lines +654 to +678
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Accessing ODB to fetch region boundaries and converting them to grid coordinates for every single instance is inefficient, as many instances typically share the same region. This information should be cached in a map (e.g., std::unordered_map<odb::dbRegion*, std::vector<RegionRectInline>>) to avoid redundant processing.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

// Returns true when (gx,gy) satisfies the region constraint:
// region-constrained cells must land inside their region;
// unconstrained cells have no restriction here (negotiation handles it).
auto isInRegionOk = [&](int gx, int gy) -> bool {
if (region_rects_ptr == nullptr) {
return true;
}
for (const auto& r : *region_rects_ptr) {
if (gx >= r.xlo && gy >= r.ylo && gx + cell.width <= r.xhi
&& gy + cell.height <= r.yhi) {
return true;
}
}
return false;
};

if (!isValidSite(cell.init_x, cell.init_y)
|| !isInRegionOk(cell.init_x, cell.init_y)) {
debugPrint(logger_,
utl::DPL,
"negotiation",
Expand Down Expand Up @@ -666,7 +735,7 @@ bool NegotiationLegalizer::initFromDb()
while (!pq.empty()) {
auto [dist, gx, gy] = pq.top();
pq.pop();
if (isValidSite(gx, gy)) {
if (isValidSite(gx, gy) && isInRegionOk(gx, gy)) {
cell.init_x = gx;
cell.init_y = gy;
cell.x = cell.init_x;
Expand All @@ -680,25 +749,59 @@ bool NegotiationLegalizer::initFromDb()
}
}

cell.rail_type = inferRailType(cell.init_y);
// If the instance is currently flipped relative to the row's standard
// orientation, its internal rail design is opposite of the row's bottom
// rail.
auto siteOrient = dpl_grid->getSiteOrientation(
GridX{cell.init_x}, GridY{cell.init_y}, master->getSite());
if (siteOrient.has_value() && db_inst->getOrient() != siteOrient.value()) {
cell.rail_type = (cell.rail_type == NLPowerRailType::kVss)
? NLPowerRailType::kVdd
: NLPowerRailType::kVss;
// Derive power-rail types directly from the LEF geometry stored by the
// Network — never infer from the cell's current row or orientation.
//
// rail_type = bottom rail in R0 (unflipped) orientation.
// For most CORE cells this is kVss (VSS at bottom).
// rail_type_flipped = bottom rail in MX (flipped) orientation, which
// equals the TOP rail in R0. For most cells: kVdd.
// For symmetric multi-height cells whose VSS appears
// at both top and bottom (e.g. some double-height
// flops): kVss — meaning a flip cannot resolve a
// VDD-bottom row mismatch.
{
int bot_pwr = Architecture::Row::Power_UNK;
int top_pwr = Architecture::Row::Power_UNK;
if (network_ != nullptr) {
if (Master* dpl_master = network_->getMaster(master)) {
bot_pwr = dpl_master->getBottomPowerType();
top_pwr = dpl_master->getTopPowerType();
}
}
auto toRailType = [](int pwr, NLPowerRailType fallback) {
if (pwr == Architecture::Row::Power_VSS) {
return NLPowerRailType::kVss;
}
if (pwr == Architecture::Row::Power_VDD) {
return NLPowerRailType::kVdd;
}
return fallback;
};
cell.rail_type = toRailType(bot_pwr, NLPowerRailType::kVss);
cell.rail_type_flipped = toRailType(top_pwr, NLPowerRailType::kVdd);
}

cell.flippable
= master->getSymmetryX(); // X-symmetry allows vertical flip (MX)
if (cell.height % 2 == 1) {
// For 1-row cells, we usually assume they are flippable in most PDKs.
if (cell.height == 1) {
// Consider all single height cells flippable
cell.flippable = true;
Comment on lines +787 to 789
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Restricting the automatic flippability to only cell.height == 1 excludes multi-row cells with an odd number of rows (e.g., 3-row cells). These cells also have different power rail types at their top and bottom edges, and thus flipping them is a valid way to resolve rail mismatches. The previous logic using % 2 == 1 was more general and correct for rail alignment.

Suggested change
if (cell.height == 1) {
// Consider all single height cells flippable
cell.flippable = true;
if (cell.height % 2 == 1) {
// Consider all odd height cells flippable

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was actually considering to remove this and keeping only the getSymmetryX() which comes right before these lines and not override it.

}

debugPrint(
logger_,
utl::DPL,
"negotiation",
1,
"DEBUG cell init: {} height={} flippable={} rail_type={} "
"rail_type_flipped={}",
db_inst->getName(),
cell.height,
cell.flippable,
cell.rail_type == NLPowerRailType::kVss ? "kVss" : "kVdd",
cell.rail_type_flipped == NLPowerRailType::kVss ? "kVss" : "kVdd");

if (padding_ != nullptr) {
cell.pad_left = padding_->padLeft(db_inst).v;
cell.pad_right = padding_->padRight(db_inst).v;
Expand All @@ -710,25 +813,22 @@ bool NegotiationLegalizer::initFromDb()
return true;
}

NLPowerRailType NegotiationLegalizer::inferRailType(int rowIdx) const
{
if (rowIdx >= 0 && rowIdx < static_cast<int>(row_rail_.size())) {
return row_rail_[rowIdx];
}
return NLPowerRailType::kVss;
}

void NegotiationLegalizer::buildGrid()
{
Grid* dplGrid = opendp_->grid_.get();

// Reset all pixels to default negotiation state.
// Reset all pixels to default negotiation state. Also clear any cell
// and padding pointers (including dummy_cell_ set by groupInitPixels2 for
// region boundaries) — fixed cells and movable cells are re-painted later
// by syncAllCellsToDplGrid() before any DRC check runs.
for (int gy = 0; gy < grid_h_; ++gy) {
for (int gx = 0; gx < grid_w_; ++gx) {
Pixel& pixel = dplGrid->pixel(GridY{gy}, GridX{gx});
pixel.capacity = pixel.is_valid ? 1 : 0;
pixel.usage = 0;
pixel.hist_cost = 1.0;
pixel.cell = nullptr;
pixel.padding_reserved_by = nullptr;
}
}

Expand Down Expand Up @@ -793,11 +893,20 @@ void NegotiationLegalizer::initFenceRegions()
}

// Map each instance to its fence region (if any).
// Instances are assigned to regions via GROUPS in DEF, which sets
// dbInst::group_ but NOT dbInst::region_. We must go through the group.
for (auto& cell : cells_) {
if (cell.db_inst == nullptr) {
continue;
}
auto* region = cell.db_inst->getRegion();
// Try direct region first, then group-based region.
odb::dbRegion* region = cell.db_inst->getRegion();
if (region == nullptr) {
auto* grp = cell.db_inst->getGroup();
if (grp != nullptr) {
region = grp->getRegion();
}
}
if (region == nullptr) {
continue;
}
Expand Down Expand Up @@ -973,14 +1082,29 @@ bool NegotiationLegalizer::isValidRow(int rowIdx,
return false;
}
}
const NLPowerRailType rowBot = row_rail_[rowIdx];
if (cell.height % 2 == 1) {
// Odd-height: bottom rail must match, or cell can be vertically flipped.
return cell.flippable || (rowBot == cell.rail_type);
}
// Even-height: bottom boundary must be the correct rail type, and the
// cell may only move by an even number of rows.
return rowBot == cell.rail_type;
const NLPowerRailType row_bottom_rail = row_rail_[rowIdx];
// row and cell rail must match, or cell can be flipped.
auto railStr = [](NLPowerRailType r) {
return r == NLPowerRailType::kVss ? "kVss" : "kVdd";
};
bool ret = (row_bottom_rail == cell.rail_type)
|| (cell.flippable && row_bottom_rail == cell.rail_type_flipped);
debugPrint(
logger_,
utl::DPL,
"negotiation",
1,
"rowIdx: {}, row_bottom_rail: {}, cell: {}, cell.rail_type: {}, "
"rail_type_flipped: {}, flippable: {}, rail match: {}, is_valid: {}",
rowIdx,
railStr(row_bottom_rail),
cell.db_inst ? cell.db_inst->getName() : "?",
railStr(cell.rail_type),
railStr(cell.rail_type_flipped),
cell.flippable,
(row_bottom_rail == cell.rail_type),
ret);
return ret;
}

bool NegotiationLegalizer::respectsFence(int cell_idx, int x, int y) const
Expand All @@ -998,6 +1122,7 @@ bool NegotiationLegalizer::respectsFence(int cell_idx, int x, int y) const
return fences_[cell.fence_id].contains(x, y, cell.width, cell.height);
}

// TODO: remove this function!
std::pair<int, int> NegotiationLegalizer::snapToLegal(int cell_idx,
int x,
int y) const
Expand Down Expand Up @@ -1030,7 +1155,7 @@ std::pair<int, int> NegotiationLegalizer::snapToLegal(int cell_idx,
}
}

if (ok) {
if (ok && respectsFence(cell_idx, tx, r)) {
const int dx = std::abs(tx - x);
if (dx < local_best_dx) {
local_best_dx = dx;
Expand Down
7 changes: 6 additions & 1 deletion src/dpl/src/NegotiationLegalizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ struct NegCell

bool fixed{false};
NLPowerRailType rail_type{NLPowerRailType::kVss};
// Bottom rail type of the cell when it is MX-flipped (= the top rail in
// the natural R0 orientation). For most cells this is kVdd (VDD↔VSS swap
// at the bottom edge after flip). For multi-height cells whose power is
// symmetric (VSS at both top and bottom, e.g. some double-height flops),
// this equals rail_type — flipping cannot fix a power-rail mismatch.
NLPowerRailType rail_type_flipped{NLPowerRailType::kVdd};
int fence_id{-1}; // -1 → default region
bool flippable{true}; // odd-height cells may require fliping for moving
bool legal{false}; // updated each negotiation iteration
Expand Down Expand Up @@ -160,7 +166,6 @@ class NegotiationLegalizer
bool initFromDb();
void buildGrid();
void initFenceRegions();
[[nodiscard]] NLPowerRailType inferRailType(int rowIdx) const;
void flushToDb(); // Write current cell positions to ODB (for GUI updates)
void pushNegotiationPixels();
void debugPause(const std::string& msg);
Expand Down
34 changes: 31 additions & 3 deletions src/dpl/src/NegotiationLegalizerPass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -398,9 +398,21 @@ void NegotiationLegalizer::place(int cell_idx, int x, int y)
const odb::dbInst* debug_inst = debug_observer_->getDebugInstance();
if (!debug_inst || cells_[cell_idx].db_inst == debug_inst) {
pushNegotiationPixels();
logger_->report("Pause at placing of cell {}.",
cells_[cell_idx].db_inst->getName());
debug_observer_->drawSelected(cells_[cell_idx].db_inst, !debug_inst);
const NegCell& c = cells_[cell_idx];
const int orig_x_dbu = die_xlo_ + c.init_x * site_width_;
const int orig_y_dbu = die_ylo_ + c.init_y * row_height_;
const int tgt_x_dbu = die_xlo_ + c.x * site_width_;
const int tgt_y_dbu = die_ylo_ + c.y * row_height_;
logger_->report(
"Pause at placing of cell {}. orig=({},{}) target=({},{}) dbu. "
"rowidx={}.",
c.db_inst->getName(),
orig_x_dbu,
orig_y_dbu,
tgt_x_dbu,
tgt_y_dbu,
c.y);
debug_observer_->drawSelected(c.db_inst, !debug_inst);
}
}
}
Expand Down Expand Up @@ -546,6 +558,22 @@ std::pair<int, int> NegotiationLegalizer::findBestLocation(int cell_idx,
}
}
}

if (best_cost == static_cast<double>(kInfCost)) {
// Every candidate in the search window was filtered out (out-of-die,
// invalid row, or fence violation). The cell falls back to its current
// position, which may already be illegal — a likely stuck-cell scenario.
debugPrint(logger_,
utl::DPL,
"negotiation",
1,
"findBestLocation: no valid candidate found for cell '{}' "
"(iter {}) — all {} candidates filtered, cell may be stuck.",
cell.db_inst->getName(),
iter,
prof_candidates_filtered_);
}

return {best_x, best_y};
}

Expand Down
6 changes: 6 additions & 0 deletions src/dpl/src/Opendp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,12 @@ void Opendp::detailedPlacement(const int max_displacement_x,
} else {
initGrid();
setFixedGridCells();
// Populate pixel->group for each fence region so diamondRecovery's
// underlying diamondSearch correctly enforces region constraints.
if (!arch_->getRegions().empty()) {
groupInitPixels2();
groupInitPixels();
}
logger_->info(DPL, 1102, "Legalizing using negotiation legalizer.");

NegotiationLegalizer negotiation(this,
Expand Down
Loading
Loading