Skip to content

Commit ae0cf47

Browse files
authored
Merge pull request #10175 from gudeh/dpl-negotiation-fixes
dpl: negotiation, introduce regions and fix power rail alignment
2 parents bf6f201 + 0504146 commit ae0cf47

5 files changed

Lines changed: 216 additions & 42 deletions

File tree

src/dpl/src/NegotiationLegalizer.cpp

Lines changed: 163 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "infrastructure/Grid.h"
2424
#include "infrastructure/Objects.h"
2525
#include "infrastructure/Padding.h"
26+
#include "infrastructure/architecture.h"
2627
#include "infrastructure/network.h"
2728
#include "odb/db.h"
2829
#include "odb/geom.h"
@@ -533,18 +534,39 @@ bool NegotiationLegalizer::initFromDb()
533534
grid_w_ = dpl_grid->getRowSiteCount().v;
534535
grid_h_ = dpl_grid->getRowCount().v;
535536

536-
// Assign power-rail types using row-index parity (VSS at even rows).
537-
// Replace with explicit LEF pg_pin parsing for advanced PDKs.
537+
// Assign power-rail types from DB row orientations.
538+
// R0/MY = right-side-up → VSS rail at bottom; MX/R180 = flipped → VDD at
539+
// bottom. Rows missing from the DB default to VSS.
538540
row_rail_.clear();
539-
row_rail_.resize(grid_h_);
540-
for (int r = 0; r < grid_h_; ++r) {
541-
row_rail_[r] = (r % 2 == 0) ? NLPowerRailType::kVss : NLPowerRailType::kVdd;
541+
row_rail_.resize(grid_h_, NLPowerRailType::kVss);
542+
for (auto* db_row : block->getRows()) {
543+
const int y_dbu = db_row->getOrigin().y() - die_ylo_;
544+
if (y_dbu < 0 || y_dbu % row_height_ != 0) {
545+
continue;
546+
}
547+
const int r = y_dbu / row_height_;
548+
if (r >= grid_h_) {
549+
continue;
550+
}
551+
const auto orient = db_row->getOrient();
552+
row_rail_[r]
553+
= (orient == odb::dbOrientType::MX || orient == odb::dbOrientType::R180)
554+
? NLPowerRailType::kVdd
555+
: NLPowerRailType::kVss;
542556
}
543557

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

562+
// Cache region boundaries converted to grid coordinates, keyed by dbRegion*.
563+
struct RegionRectInline
564+
{
565+
int xlo, ylo, xhi, yhi;
566+
};
567+
std::unordered_map<odb::dbRegion*, std::vector<RegionRectInline>>
568+
region_rect_cache;
569+
548570
for (auto* db_inst : block->getInsts()) {
549571
const auto status = db_inst->getPlacementStatus();
550572
if (status == odb::dbPlacementStatus::NONE) {
@@ -624,7 +646,54 @@ bool NegotiationLegalizer::initFromDb()
624646
// in original DPL.
625647
// they achieve the same objective, and the previous is more simple,
626648
// consider replacing this.
627-
if (!isValidSite(cell.init_x, cell.init_y)) {
649+
// For region-constrained cells, collect the region rects in grid
650+
// coordinates so the BFS below can verify containment.
651+
// initFenceRegions() has not run yet, so we read ODB directly.
652+
// Instances reach their region via a GROUP, not via dbInst::region_,
653+
// so we must check both paths.
654+
odb::dbRegion* odb_region = db_inst->getRegion();
655+
if (odb_region == nullptr) {
656+
auto* grp = db_inst->getGroup();
657+
if (grp != nullptr) {
658+
odb_region = grp->getRegion();
659+
}
660+
}
661+
// Look up (or populate) the cache for this region.
662+
const std::vector<RegionRectInline>* region_rects_ptr = nullptr;
663+
if (odb_region != nullptr) {
664+
auto it = region_rect_cache.find(odb_region);
665+
if (it == region_rect_cache.end()) {
666+
std::vector<RegionRectInline> rects;
667+
for (auto* box : odb_region->getBoundaries()) {
668+
RegionRectInline r;
669+
r.xlo = (box->xMin() - die_xlo_) / site_width_;
670+
r.ylo = (box->yMin() - die_ylo_) / row_height_;
671+
r.xhi = (box->xMax() - die_xlo_) / site_width_;
672+
r.yhi = (box->yMax() - die_ylo_) / row_height_;
673+
rects.push_back(r);
674+
}
675+
it = region_rect_cache.emplace(odb_region, std::move(rects)).first;
676+
}
677+
region_rects_ptr = &it->second;
678+
}
679+
// Returns true when (gx,gy) satisfies the region constraint:
680+
// region-constrained cells must land inside their region;
681+
// unconstrained cells have no restriction here (negotiation handles it).
682+
auto isInRegionOk = [&](int gx, int gy) -> bool {
683+
if (region_rects_ptr == nullptr) {
684+
return true;
685+
}
686+
for (const auto& r : *region_rects_ptr) {
687+
if (gx >= r.xlo && gy >= r.ylo && gx + cell.width <= r.xhi
688+
&& gy + cell.height <= r.yhi) {
689+
return true;
690+
}
691+
}
692+
return false;
693+
};
694+
695+
if (!isValidSite(cell.init_x, cell.init_y)
696+
|| !isInRegionOk(cell.init_x, cell.init_y)) {
628697
debugPrint(logger_,
629698
utl::DPL,
630699
"negotiation",
@@ -666,7 +735,7 @@ bool NegotiationLegalizer::initFromDb()
666735
while (!pq.empty()) {
667736
auto [dist, gx, gy] = pq.top();
668737
pq.pop();
669-
if (isValidSite(gx, gy)) {
738+
if (isValidSite(gx, gy) && isInRegionOk(gx, gy)) {
670739
cell.init_x = gx;
671740
cell.init_y = gy;
672741
cell.x = cell.init_x;
@@ -680,25 +749,59 @@ bool NegotiationLegalizer::initFromDb()
680749
}
681750
}
682751

683-
cell.rail_type = inferRailType(cell.init_y);
684-
// If the instance is currently flipped relative to the row's standard
685-
// orientation, its internal rail design is opposite of the row's bottom
686-
// rail.
687-
auto siteOrient = dpl_grid->getSiteOrientation(
688-
GridX{cell.init_x}, GridY{cell.init_y}, master->getSite());
689-
if (siteOrient.has_value() && db_inst->getOrient() != siteOrient.value()) {
690-
cell.rail_type = (cell.rail_type == NLPowerRailType::kVss)
691-
? NLPowerRailType::kVdd
692-
: NLPowerRailType::kVss;
752+
// Derive power-rail types directly from the LEF geometry stored by the
753+
// Network — never infer from the cell's current row or orientation.
754+
//
755+
// rail_type = bottom rail in R0 (unflipped) orientation.
756+
// For most CORE cells this is kVss (VSS at bottom).
757+
// rail_type_flipped = bottom rail in MX (flipped) orientation, which
758+
// equals the TOP rail in R0. For most cells: kVdd.
759+
// For symmetric multi-height cells whose VSS appears
760+
// at both top and bottom (e.g. some double-height
761+
// flops): kVss — meaning a flip cannot resolve a
762+
// VDD-bottom row mismatch.
763+
{
764+
int bot_pwr = Architecture::Row::Power_UNK;
765+
int top_pwr = Architecture::Row::Power_UNK;
766+
if (network_ != nullptr) {
767+
if (Master* dpl_master = network_->getMaster(master)) {
768+
bot_pwr = dpl_master->getBottomPowerType();
769+
top_pwr = dpl_master->getTopPowerType();
770+
}
771+
}
772+
auto toRailType = [](int pwr, NLPowerRailType fallback) {
773+
if (pwr == Architecture::Row::Power_VSS) {
774+
return NLPowerRailType::kVss;
775+
}
776+
if (pwr == Architecture::Row::Power_VDD) {
777+
return NLPowerRailType::kVdd;
778+
}
779+
return fallback;
780+
};
781+
cell.rail_type = toRailType(bot_pwr, NLPowerRailType::kVss);
782+
cell.rail_type_flipped = toRailType(top_pwr, NLPowerRailType::kVdd);
693783
}
694784

695785
cell.flippable
696786
= master->getSymmetryX(); // X-symmetry allows vertical flip (MX)
697-
if (cell.height % 2 == 1) {
698-
// For 1-row cells, we usually assume they are flippable in most PDKs.
787+
if (cell.height == 1) {
788+
// Consider all single height cells flippable
699789
cell.flippable = true;
700790
}
701791

792+
debugPrint(
793+
logger_,
794+
utl::DPL,
795+
"negotiation",
796+
1,
797+
"DEBUG cell init: {} height={} flippable={} rail_type={} "
798+
"rail_type_flipped={}",
799+
db_inst->getName(),
800+
cell.height,
801+
cell.flippable,
802+
cell.rail_type == NLPowerRailType::kVss ? "kVss" : "kVdd",
803+
cell.rail_type_flipped == NLPowerRailType::kVss ? "kVss" : "kVdd");
804+
702805
if (padding_ != nullptr) {
703806
cell.pad_left = padding_->padLeft(db_inst).v;
704807
cell.pad_right = padding_->padRight(db_inst).v;
@@ -710,25 +813,22 @@ bool NegotiationLegalizer::initFromDb()
710813
return true;
711814
}
712815

713-
NLPowerRailType NegotiationLegalizer::inferRailType(int rowIdx) const
714-
{
715-
if (rowIdx >= 0 && rowIdx < static_cast<int>(row_rail_.size())) {
716-
return row_rail_[rowIdx];
717-
}
718-
return NLPowerRailType::kVss;
719-
}
720-
721816
void NegotiationLegalizer::buildGrid()
722817
{
723818
Grid* dplGrid = opendp_->grid_.get();
724819

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

@@ -793,11 +893,20 @@ void NegotiationLegalizer::initFenceRegions()
793893
}
794894

795895
// Map each instance to its fence region (if any).
896+
// Instances are assigned to regions via GROUPS in DEF, which sets
897+
// dbInst::group_ but NOT dbInst::region_. We must go through the group.
796898
for (auto& cell : cells_) {
797899
if (cell.db_inst == nullptr) {
798900
continue;
799901
}
800-
auto* region = cell.db_inst->getRegion();
902+
// Try direct region first, then group-based region.
903+
odb::dbRegion* region = cell.db_inst->getRegion();
904+
if (region == nullptr) {
905+
auto* grp = cell.db_inst->getGroup();
906+
if (grp != nullptr) {
907+
region = grp->getRegion();
908+
}
909+
}
801910
if (region == nullptr) {
802911
continue;
803912
}
@@ -973,14 +1082,29 @@ bool NegotiationLegalizer::isValidRow(int rowIdx,
9731082
return false;
9741083
}
9751084
}
976-
const NLPowerRailType rowBot = row_rail_[rowIdx];
977-
if (cell.height % 2 == 1) {
978-
// Odd-height: bottom rail must match, or cell can be vertically flipped.
979-
return cell.flippable || (rowBot == cell.rail_type);
980-
}
981-
// Even-height: bottom boundary must be the correct rail type, and the
982-
// cell may only move by an even number of rows.
983-
return rowBot == cell.rail_type;
1085+
const NLPowerRailType row_bottom_rail = row_rail_[rowIdx];
1086+
// row and cell rail must match, or cell can be flipped.
1087+
auto railStr = [](NLPowerRailType r) {
1088+
return r == NLPowerRailType::kVss ? "kVss" : "kVdd";
1089+
};
1090+
bool ret = (row_bottom_rail == cell.rail_type)
1091+
|| (cell.flippable && row_bottom_rail == cell.rail_type_flipped);
1092+
debugPrint(
1093+
logger_,
1094+
utl::DPL,
1095+
"negotiation",
1096+
1,
1097+
"rowIdx: {}, row_bottom_rail: {}, cell: {}, cell.rail_type: {}, "
1098+
"rail_type_flipped: {}, flippable: {}, rail match: {}, is_valid: {}",
1099+
rowIdx,
1100+
railStr(row_bottom_rail),
1101+
cell.db_inst ? cell.db_inst->getName() : "?",
1102+
railStr(cell.rail_type),
1103+
railStr(cell.rail_type_flipped),
1104+
cell.flippable,
1105+
(row_bottom_rail == cell.rail_type),
1106+
ret);
1107+
return ret;
9841108
}
9851109

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

1125+
// TODO: remove this function!
10011126
std::pair<int, int> NegotiationLegalizer::snapToLegal(int cell_idx,
10021127
int x,
10031128
int y) const
@@ -1030,7 +1155,7 @@ std::pair<int, int> NegotiationLegalizer::snapToLegal(int cell_idx,
10301155
}
10311156
}
10321157

1033-
if (ok) {
1158+
if (ok && respectsFence(cell_idx, tx, r)) {
10341159
const int dx = std::abs(tx - x);
10351160
if (dx < local_best_dx) {
10361161
local_best_dx = dx;

src/dpl/src/NegotiationLegalizer.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,12 @@ struct NegCell
9292

9393
bool fixed{false};
9494
NLPowerRailType rail_type{NLPowerRailType::kVss};
95+
// Bottom rail type of the cell when it is MX-flipped (= the top rail in
96+
// the natural R0 orientation). For most cells this is kVdd (VDD↔VSS swap
97+
// at the bottom edge after flip). For multi-height cells whose power is
98+
// symmetric (VSS at both top and bottom, e.g. some double-height flops),
99+
// this equals rail_type — flipping cannot fix a power-rail mismatch.
100+
NLPowerRailType rail_type_flipped{NLPowerRailType::kVdd};
95101
int fence_id{-1}; // -1 → default region
96102
bool flippable{true}; // odd-height cells may require fliping for moving
97103
bool legal{false}; // updated each negotiation iteration
@@ -160,7 +166,6 @@ class NegotiationLegalizer
160166
bool initFromDb();
161167
void buildGrid();
162168
void initFenceRegions();
163-
[[nodiscard]] NLPowerRailType inferRailType(int rowIdx) const;
164169
void flushToDb(); // Write current cell positions to ODB (for GUI updates)
165170
void pushNegotiationPixels();
166171
void debugPause(const std::string& msg);

src/dpl/src/NegotiationLegalizerPass.cpp

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -398,9 +398,21 @@ void NegotiationLegalizer::place(int cell_idx, int x, int y)
398398
const odb::dbInst* debug_inst = debug_observer_->getDebugInstance();
399399
if (!debug_inst || cells_[cell_idx].db_inst == debug_inst) {
400400
pushNegotiationPixels();
401-
logger_->report("Pause at placing of cell {}.",
402-
cells_[cell_idx].db_inst->getName());
403-
debug_observer_->drawSelected(cells_[cell_idx].db_inst, !debug_inst);
401+
const NegCell& c = cells_[cell_idx];
402+
const int orig_x_dbu = die_xlo_ + c.init_x * site_width_;
403+
const int orig_y_dbu = die_ylo_ + c.init_y * row_height_;
404+
const int tgt_x_dbu = die_xlo_ + c.x * site_width_;
405+
const int tgt_y_dbu = die_ylo_ + c.y * row_height_;
406+
logger_->report(
407+
"Pause at placing of cell {}. orig=({},{}) target=({},{}) dbu. "
408+
"rowidx={}.",
409+
c.db_inst->getName(),
410+
orig_x_dbu,
411+
orig_y_dbu,
412+
tgt_x_dbu,
413+
tgt_y_dbu,
414+
c.y);
415+
debug_observer_->drawSelected(c.db_inst, !debug_inst);
404416
}
405417
}
406418
}
@@ -546,6 +558,22 @@ std::pair<int, int> NegotiationLegalizer::findBestLocation(int cell_idx,
546558
}
547559
}
548560
}
561+
562+
if (best_cost == static_cast<double>(kInfCost)) {
563+
// Every candidate in the search window was filtered out (out-of-die,
564+
// invalid row, or fence violation). The cell falls back to its current
565+
// position, which may already be illegal — a likely stuck-cell scenario.
566+
debugPrint(logger_,
567+
utl::DPL,
568+
"negotiation",
569+
1,
570+
"findBestLocation: no valid candidate found for cell '{}' "
571+
"(iter {}) — all {} candidates filtered, cell may be stuck.",
572+
cell.db_inst->getName(),
573+
iter,
574+
prof_candidates_filtered_);
575+
}
576+
549577
return {best_x, best_y};
550578
}
551579

src/dpl/src/Opendp.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,12 @@ void Opendp::detailedPlacement(const int max_displacement_x,
206206
} else {
207207
initGrid();
208208
setFixedGridCells();
209+
// Populate pixel->group for each fence region so diamondRecovery's
210+
// underlying diamondSearch correctly enforces region constraints.
211+
if (!arch_->getRegions().empty()) {
212+
groupInitPixels2();
213+
groupInitPixels();
214+
}
209215
logger_->info(DPL, 1102, "Legalizing using negotiation legalizer.");
210216

211217
NegotiationLegalizer negotiation(this,

0 commit comments

Comments
 (0)