diff --git a/src/dpl/src/NegotiationLegalizer.cpp b/src/dpl/src/NegotiationLegalizer.cpp index df06af4dcaa..924ad5a650c 100644 --- a/src/dpl/src/NegotiationLegalizer.cpp +++ b/src/dpl/src/NegotiationLegalizer.cpp @@ -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" @@ -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> + region_rect_cache; + for (auto* db_inst : block->getInsts()) { const auto status = db_inst->getPlacementStatus(); if (status == odb::dbPlacementStatus::NONE) { @@ -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* region_rects_ptr = nullptr; + if (odb_region != nullptr) { + auto it = region_rect_cache.find(odb_region); + if (it == region_rect_cache.end()) { + std::vector 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; + } + // 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", @@ -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; @@ -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; } + 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; @@ -710,25 +813,22 @@ bool NegotiationLegalizer::initFromDb() return true; } -NLPowerRailType NegotiationLegalizer::inferRailType(int rowIdx) const -{ - if (rowIdx >= 0 && rowIdx < static_cast(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; } } @@ -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; } @@ -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 @@ -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 NegotiationLegalizer::snapToLegal(int cell_idx, int x, int y) const @@ -1030,7 +1155,7 @@ std::pair 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; diff --git a/src/dpl/src/NegotiationLegalizer.h b/src/dpl/src/NegotiationLegalizer.h index c155d990088..3c8c81b4a1d 100644 --- a/src/dpl/src/NegotiationLegalizer.h +++ b/src/dpl/src/NegotiationLegalizer.h @@ -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 @@ -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); diff --git a/src/dpl/src/NegotiationLegalizerPass.cpp b/src/dpl/src/NegotiationLegalizerPass.cpp index 2a9c052bfeb..d68f6885591 100644 --- a/src/dpl/src/NegotiationLegalizerPass.cpp +++ b/src/dpl/src/NegotiationLegalizerPass.cpp @@ -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); } } } @@ -546,6 +558,22 @@ std::pair NegotiationLegalizer::findBestLocation(int cell_idx, } } } + + if (best_cost == static_cast(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}; } diff --git a/src/dpl/src/Opendp.cpp b/src/dpl/src/Opendp.cpp index b776062688c..49ab13088d6 100644 --- a/src/dpl/src/Opendp.cpp +++ b/src/dpl/src/Opendp.cpp @@ -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, diff --git a/src/dpl/src/graphics/Graphics.cpp b/src/dpl/src/graphics/Graphics.cpp index 06b969dfd48..213ac184e0c 100644 --- a/src/dpl/src/graphics/Graphics.cpp +++ b/src/dpl/src/graphics/Graphics.cpp @@ -3,6 +3,7 @@ #include "Graphics.h" +#include #include #include #include @@ -144,6 +145,15 @@ void Graphics::drawObjects(gui::Painter& painter) painter.setPen(outline_color, /* cosmetic */ true); painter.setBrush(gui::Painter::kTransparent); painter.drawRect(target_bbox); + + // Indicate orientation change at the target location with a corner notch + // (mirroring the ODB orientation marker style) + painter.setPen(outline_color, /* cosmetic */ true); + const int tag_size = std::min(width / 4, height / 8); + painter.drawLine(target_bbox.xMin() + tag_size, + target_bbox.yMin(), + target_bbox.xMin(), + target_bbox.yMin() + tag_size * 2); } else if (std::abs(dx) > std::abs(dy)) { line_color = (dx > 0) ? gui::Painter::kGreen : gui::Painter::kRed; } else {