Skip to content

mpl: pin-aware macro halos#10558

Draft
joaomai wants to merge 19 commits into
The-OpenROAD-Project:masterfrom
The-OpenROAD-Project-staging:mpl-halo-on-pins
Draft

mpl: pin-aware macro halos#10558
joaomai wants to merge 19 commits into
The-OpenROAD-Project:masterfrom
The-OpenROAD-Project-staging:mpl-halo-on-pins

Conversation

@joaomai
Copy link
Copy Markdown
Contributor

@joaomai joaomai commented May 29, 2026

Summary

  • This PR introduces a pin-aware halo assignment for MPL. Instead of applying a halo on all four sides of every macro, MPL now only assigns them to macro sides with signal pins.
  • Sides with no signal pins get a smaller halo derived from the minimum spacing rules from layers occupied by macros. This is required to avoid macros from being abutted, allowing snapping to be done correctly and finishing without DRCs at the end of the flow.
  • Orientation improvement requires a special treatment when using pin-aware halos, since it is possible to create deadzones/notches when flipping macros individually. To fix this issue, a new row-/column-wise orientation improvement is used.
  • The default behavior is to use pin-aware halo assignment, but the feature can be disabled using the -use_full_halo flag.

Type of Change

  • New feature
  • Refactoring

Impact

MPL now assigns proper halos only on sides with signal pins, those without are assigned a smaller halo size based on minimum spacing rules. Old behavior can be restored using the -use_full_halo flag.

Verification

  • I have verified that the local build succeeds (./etc/Build.sh).
  • I have run the relevant tests and they pass.
  • My code follows the repository's formatting guidelines.
  • I have included tests to prevent regressions.
  • I have signed my commits (DCO).

Misc

MPL will undergo nomenclature changes (using channel instead of halo and others) and some commands related to halos will be removed/revamped. Since this PR was deep in development when the changes were proposed, it was decided to finish this work first and then follow it up with necessary changes along with any other changes required in MPL.

joaomai added 19 commits May 29, 2026 21:13
Signed-off-by: João Mai <jmai@precisioninno.com>
Signed-off-by: João Mai <jmai@precisioninno.com>
Signed-off-by: João Mai <jmai@precisioninno.com>
Signed-off-by: João Mai <jmai@precisioninno.com>
Signed-off-by: João Mai <jmai@precisioninno.com>
Signed-off-by: João Mai <jmai@precisioninno.com>
Signed-off-by: João Mai <jmai@precisioninno.com>
Signed-off-by: João Mai <jmai@precisioninno.com>
Signed-off-by: João Mai <jmai@precisioninno.com>
Signed-off-by: João Mai <jmai@precisioninno.com>
Signed-off-by: João Mai <jmai@precisioninno.com>
Signed-off-by: João Mai <jmai@precisioninno.com>
Signed-off-by: João Mai <jmai@precisioninno.com>
Signed-off-by: João Mai <jmai@precisioninno.com>
Signed-off-by: João Mai <jmai@precisioninno.com>
Signed-off-by: João Mai <jmai@precisioninno.com>
Signed-off-by: João Mai <jmai@precisioninno.com>
Signed-off-by: João Mai <jmai@precisioninno.com>
Signed-off-by: João Mai <jmai@precisioninno.com>
@joaomai joaomai self-assigned this May 29, 2026
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new -use_full_halo option to the RTL macro placer, allowing users to disable pin-awareness during halo assignment and restore uniform halo behavior. The implementation adds pin-aware halo construction logic, cluster-based macro orientation correction, and updates corresponding tests. The review feedback identifies a high-severity risk of null pointer dereferences and undefined behavior when querying pin geometries in buildMacroHalo. Additionally, it suggests performance optimizations, such as caching the macro halo object and using a set to track visited masters in getMinimumSpacing to avoid redundant scans on large designs.

Comment on lines +2191 to +2192
auto direction
= (mpin->getGeometry().begin())->getTechLayer()->getDirection();
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.

high

Dereferencing mpin->getGeometry().begin() without checking if the geometry list is empty can lead to undefined behavior or a crash. Additionally, getTechLayer() can return nullptr, which would cause a null pointer dereference when calling getDirection().

It is safer to retrieve the tech layer and direction inside the loop over mpin->getGeometry(), checking for null pointers first. This also correctly handles pins with multiple geometries on different layers/directions.

      for (odb::dbBox* box : mpin->getGeometry()) {
        auto* layer = box->getTechLayer();
        if (!layer) {
          continue;
        }
        auto direction = layer->getDirection();
        odb::Rect pin_rect = box->getBox();

Comment on lines +2163 to +2166
if (inst->getHalo() != nullptr) {
odb::Rect inst_halo = inst->getHalo()->getBox();
if (inst->getHalo()->isSoft()) {
full_halo = HardMacro::Halo(inst->getHalo());
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

The method inst->getHalo() is called multiple times. Caching it in a local variable improves readability and avoids redundant function calls.

Suggested change
if (inst->getHalo() != nullptr) {
odb::Rect inst_halo = inst->getHalo()->getBox();
if (inst->getHalo()->isSoft()) {
full_halo = HardMacro::Halo(inst->getHalo());
auto* inst_halo_obj = inst->getHalo();
if (inst_halo_obj != nullptr) {
odb::Rect inst_halo = inst_halo_obj->getBox();
if (inst_halo_obj->isSoft()) {
full_halo = HardMacro::Halo(inst_halo_obj);

Comment on lines +2222 to +2248
int ClusteringEngine::getMinimumSpacing() const
{
int spacing = 0;

for (odb::dbInst* inst : block_->getInsts()) {
if (inst->isBlock()) {
odb::dbMaster* master = inst->getMaster();

for (odb::dbBox* obs : master->getObstructions()) {
if (auto layer = obs->getTechLayer()) {
spacing = std::max(spacing, layer->getSpacing());
}
}

for (odb::dbMTerm* mterm : master->getMTerms()) {
for (odb::dbMPin* mpin : mterm->getMPins()) {
for (odb::dbBox* geom : mpin->getGeometry()) {
if (auto layer = geom->getTechLayer()) {
spacing = std::max(spacing, layer->getSpacing());
}
}
}
}
}
}
return spacing;
}
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

The current implementation of getMinimumSpacing() has two performance bottlenecks:

  1. It iterates over all instances in the block (block_->getInsts()), which can be extremely slow on large designs with millions of standard cells. Since createHardMacros() also iterates over all instances, this results in redundant full-scan passes.
  2. It scans the obstructions and pins of the dbMaster for every macro instance. Since multiple macro instances often share the same dbMaster, this performs redundant scans of the same master geometries.

Using a std::unordered_set<odb::dbMaster*> to cache already visited masters avoids redundant geometry scans.

int ClusteringEngine::getMinimumSpacing() const
{
  int spacing = 0;
  std::unordered_set<odb::dbMaster*> visited_masters;

  for (odb::dbInst* inst : block_->getInsts()) {
    if (inst->isBlock()) {
      odb::dbMaster* master = inst->getMaster();
      if (!visited_masters.insert(master).second) {
        continue;
      }

      for (odb::dbBox* obs : master->getObstructions()) {
        if (auto layer = obs->getTechLayer()) {
          spacing = std::max(spacing, layer->getSpacing());
        }
      }

      for (odb::dbMTerm* mterm : master->getMTerms()) {
        for (odb::dbMPin* mpin : mterm->getMPins()) {
          for (odb::dbBox* geom : mpin->getGeometry()) {
            if (auto layer = geom->getTechLayer()) {
              spacing = std::max(spacing, layer->getSpacing());
            }
          }
        }
      }
    }
  }
  return spacing;
}

@joaomai joaomai requested a review from eder-matheus May 29, 2026 21:55
Comment on lines +2193 to +2214
for (odb::dbBox* box : mpin->getGeometry()) {
odb::Rect pin_rect = box->getBox();

if (direction == odb::dbTechLayerDir::HORIZONTAL) {
int dist_left = pin_rect.xMin();
int dist_right = master->getWidth() - pin_rect.xMax();

if (dist_left < dist_right) {
halo.left = full_halo.left;
} else {
halo.right = full_halo.right;
}
} else {
int dist_bottom = pin_rect.yMin();
int dist_top = master->getHeight() - pin_rect.yMax();

if (dist_bottom < dist_top) {
halo.bottom = full_halo.bottom;
} else {
halo.top = full_halo.top;
}
}
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.

how would this handle macros where the pins are inside the macro and therefore the access is actually from the layer above? in this case, if M3 has pins and is vertical, then you would be creating halos for the top and bottom even though it should be based on M4's orientation

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 don't recall seeing a macro with this kind of pin before, so the possibility didn't cross my mind. Are they closer to the boundary they should be accessed from or something like that?

@oharboe
Copy link
Copy Markdown
Collaborator

oharboe commented May 30, 2026

🥳

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants