Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
36 changes: 36 additions & 0 deletions src/mpl/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,42 @@ Do note that while action probabilities are normalized to 1.0, the weights are n
| `-notch_weight` | Weight for the notch, or the existence of dead space that cannot be used for placement & routing. Note that this cost applies only to hard macro clusters. The allowed values are floats, and the default value is `10.0`. |
| `-soft_blockage_weight` | Weight for the penalty based on overlap between clusters with macros and areas of virtual blockages generated by the macro placer. The idea is to prevent macros from occupying certain areas: currently we may generate these blockages to protect the surroundings of areas reserved for IO pins. The allowed values are floats, and the default value is `10.0`. |

### Check Macro Placement

Run the same feasibility checks used before `rtl_macro_placer` clusters the
design, without clustering, annealing, report files, or database changes.
The command returns `true` on success and raises the placer's existing error on
failure. It checks macro dimensions including halos, movable-cell area including
placement blockages, the instance-area budget including macro halos, fixed
non-macro cells in the placement area, and whether the global fence intersects
the core. As in the placer, the area and fixed-cell checks are skipped when
there are no unfixed macros.

```tcl
check_macro_placement
[-min_channel_size {width [height]}]
[-fence_lx fence_lx]
[-fence_ly fence_ly]
[-fence_ux fence_ux]
[-fence_uy fence_uy]
[-pin_aware_channels]
```

#### Options

| Switch Name | Description |
| ----- | ----- |
| `-min_channel_size` | Minimum channel width and height in microns, as for `rtl_macro_placer`. Defaults to zero for this invocation and does not change the placer's settings. Existing instance halos and technology minimum spacing are honored. |
| `-fence_lx`, `-fence_ly`, `-fence_ux`, `-fence_uy` | Global fence coordinates in microns. Supply all four or omit all four to use the core, independently of earlier placement settings. Lower coordinates must be strictly less than upper coordinates after DBU conversion. An explicit empty or reversed fence is an error. A partially overlapping fence is clipped to the core. |
| `-pin_aware_channels` | Trim channels based on macro pin locations, as for `rtl_macro_placer`. Requires `-min_channel_size`. |

Fence and channel values must be finite and round to a signed integer DBU value.
Values outside this range are rejected before conversion.

This is an area-feasibility preflight, not a placement-legality or packing proof.
It does not source placement scripts, check macro/halo overlaps or orientation
legality, or check pin access, routing capacity, or placement density windows.

### Place Macro

Command for manual placement of a single macro.
Expand Down
7 changes: 7 additions & 0 deletions src/mpl/include/mpl/rtl_mp.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include <map>
#include <memory>
#include <optional>
#include <string>
#include <vector>

Expand Down Expand Up @@ -98,6 +99,12 @@ class MacroPlacer

bool run(int threads, const MacroPlacerOptions& options = {});

// Check the placer's pre-clustering rules without changing the database.
void checkMacroPlacement(std::optional<odb::Rect> global_fence = std::nullopt,
int min_channel_width = 0,
int min_channel_height = 0,
bool pin_aware_channels = false) const;

void blockMacroChannels();

void placeMacro(odb::dbInst* inst,
Expand Down
58 changes: 40 additions & 18 deletions src/mpl/src/clusterEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <map>
#include <memory>
#include <ranges>
Expand Down Expand Up @@ -97,6 +98,17 @@ void ClusteringEngine::setChannel(const Channel min_channel,
// Also report some design data relevant for the user and
// initialize the tree with data from the design.
void ClusteringEngine::init()
{
const size_t num_macros_to_place = checkFeasibility();
if (!tree_->has_unfixed_macros) {
return;
}

tree_->io_pads = getIOPads();
reportDesignData(num_macros_to_place);
}

size_t ClusteringEngine::checkFeasibility()
{
setDieArea();
setFloorplanShape();
Expand All @@ -109,7 +121,7 @@ void ClusteringEngine::init()
if (unfixed_macros.empty()) {
tree_->has_unfixed_macros = false;
logger_->info(MPL, 17, "No unfixed macros.");
return;
return 0;
}
Comment on lines 121 to 125

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

To ensure robustness and defensive programming, explicitly set tree_->has_unfixed_macros = true; when unfixed_macros is not empty. This prevents potential issues if tree_ is reused or if has_unfixed_macros is not default-initialized to true.

Suggested change
if (unfixed_macros.empty()) {
tree_->has_unfixed_macros = false;
logger_->info(MPL, 17, "No unfixed macros.");
return;
return 0;
}
if (unfixed_macros.empty()) {
tree_->has_unfixed_macros = false;
logger_->info(MPL, 17, "No unfixed macros.");
return 0;
}
tree_->has_unfixed_macros = true;


if (!movableCellsFitInMacroPlacementArea()) {
Expand All @@ -133,9 +145,7 @@ void ClusteringEngine::init()
block_->dbuAreaToMicrons(tree_->floorplan_shape.area()));
}

tree_->io_pads = getIOPads();

reportDesignData(unfixed_macros.size());
return unfixed_macros.size();
}

bool ClusteringEngine::movableCellsFitInMacroPlacementArea() const
Expand Down Expand Up @@ -217,7 +227,8 @@ std::vector<odb::dbInst*> ClusteringEngine::getUnfixedMacros()

void ClusteringEngine::setFloorplanShape()
{
tree_->floorplan_shape = block_->getCoreArea().intersect(tree_->global_fence);
block_->getCoreArea().intersection(tree_->global_fence,
tree_->floorplan_shape);

if (tree_->floorplan_shape.area() == 0) {
logger_->error(
Expand Down Expand Up @@ -2040,19 +2051,22 @@ void ClusteringEngine::replaceByStdCellCluster(
}

std::string ClusteringEngine::generateMacroAndCoreDimensionsTable(
const HardMacro* hard_macro,
int64_t macro_width,
int64_t macro_height,
const odb::Rect& core) const
{
std::string table;

table += fmt::format("\n | Macro + Halos | Core ");
table += fmt::format("\n-----------------------------------------");
table += fmt::format("\n Width | {:>17.2f} | {:>8.2f}",
block_->dbuToMicrons(hard_macro->getWidth()),
block_->dbuToMicrons(core.dx()));
table += fmt::format("\n Height | {:>17.2f} | {:>8.2f}\n",
block_->dbuToMicrons(hard_macro->getHeight()),
block_->dbuToMicrons(core.dy()));
table += fmt::format(
"\n Width | {:>17.2f} | {:>8.2f}",
block_->dbuToMicrons(macro_width),
block_->dbuToMicrons(static_cast<int64_t>(core.xMax()) - core.xMin()));
table += fmt::format(
"\n Height | {:>17.2f} | {:>8.2f}\n",
block_->dbuToMicrons(macro_height),
block_->dbuToMicrons(static_cast<int64_t>(core.yMax()) - core.yMin()));

return table;
}
Expand All @@ -2061,6 +2075,8 @@ std::string ClusteringEngine::generateMacroAndCoreDimensionsTable(
void ClusteringEngine::createHardMacros()
{
const odb::Rect& core = block_->getCoreArea();
const int64_t core_width = static_cast<int64_t>(core.xMax()) - core.xMin();
const int64_t core_height = static_cast<int64_t>(core.yMax()) - core.yMin();
const int minimum_spacing = getMinimumSpacing();

for (odb::dbInst* inst : block_->getInsts()) {
Expand All @@ -2082,19 +2098,25 @@ void ClusteringEngine::createHardMacros()
}

HardMacro::Halo halo = buildMacroHalo(inst, minimum_spacing);

auto macro = std::make_unique<HardMacro>(inst, halo);

if (macro->getWidth() > core.dx() || macro->getHeight() > core.dy()) {
odb::dbMaster* master = inst->getMaster();
const int64_t width
= static_cast<int64_t>(master->getWidth()) + halo.left + halo.right;
const int64_t height
= static_cast<int64_t>(master->getHeight()) + halo.bottom + halo.top;

// Validate the resolved halo before HardMacro sums its dimensions in int.
if (width < 0 || height < 0 || width > std::numeric_limits<int>::max()
|| height > std::numeric_limits<int>::max() || width > core_width
|| height > core_height) {
logger_->error(
MPL,
6,
"Found macro that does not fit in the core.\nName: {}\n{}",
inst->getName(),
generateMacroAndCoreDimensionsTable(macro.get(), core));
generateMacroAndCoreDimensionsTable(width, height, core));
}

tree_->maps.inst_to_hard[inst] = std::move(macro);
tree_->maps.inst_to_hard[inst] = std::make_unique<HardMacro>(inst, halo);
}
}
}
Expand Down
7 changes: 6 additions & 1 deletion src/mpl/src/clusterEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ class ClusteringEngine

void run();

// Populate only in-memory feasibility data, without clustering or DB edits.
// Returns the number of macros to place.
size_t checkFeasibility();

void setTree(PhysicalHierarchy* tree);
void setChannel(Channel min_channel, bool pin_aware_channels);

Expand All @@ -115,7 +119,8 @@ class ClusteringEngine
int cluster_id,
bool include_macro);

std::string generateMacroAndCoreDimensionsTable(const HardMacro* hard_macro,
std::string generateMacroAndCoreDimensionsTable(int64_t macro_width,
int64_t macro_height,
const odb::Rect& core) const;
void createTempMacroClusters(const std::vector<HardMacro*>& hard_macros,
std::vector<HardMacro>& sa_macros,
Expand Down
24 changes: 24 additions & 0 deletions src/mpl/src/hier_rtlmp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <map>
#include <memory>
#include <numeric>
#include <optional>
#include <queue>
#include <regex>
#include <set>
Expand Down Expand Up @@ -243,6 +244,29 @@ void HierRTLMP::init()
clustering_engine_->setTree(tree_.get());
}

void HierRTLMP::checkMacroPlacement(std::optional<odb::Rect> global_fence,
int min_channel_width,
int min_channel_height,
bool pin_aware_channels) const
{
odb::dbBlock* block = db_->getChip()->getBlock();

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

If no design is loaded, db_->getChip() can return nullptr. Dereferencing it directly with db_->getChip()->getBlock() will cause a segmentation fault. Check if the chip and block exist before proceeding.

  odb::dbChip* chip = db_->getChip();
  if (chip == nullptr || chip->getBlock() == nullptr) {
    logger_->error(MPL, 1, "No block found for Macro Placement.");
  }
  odb::dbBlock* block = chip->getBlock();

if (global_fence
&& (global_fence->xMin() >= global_fence->xMax()
|| global_fence->yMin() >= global_fence->yMax())) {
logger_->error(MPL,
82,
"Invalid global fence: lower coordinates must be strictly "
"less than upper coordinates.");
}
PhysicalHierarchy tree;
tree.global_fence = global_fence.value_or(block->getCoreArea());
ClusteringEngine engine(block, logger_, tritonpart_, nullptr);
engine.setTree(&tree);
engine.setChannel({min_channel_width, min_channel_height},
pin_aware_channels);
engine.checkFeasibility();
}

////////////////////////////////////////////////////////////////////////
// Private functions
////////////////////////////////////////////////////////////////////////
Expand Down
5 changes: 5 additions & 0 deletions src/mpl/src/hier_rtlmp.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include <map>
#include <memory>
#include <optional>
#include <queue>
#include <set>
#include <string>
Expand Down Expand Up @@ -77,6 +78,10 @@ class HierRTLMP
void init();
void run();
void blockMacroChannels();
void checkMacroPlacement(std::optional<odb::Rect> global_fence,
int min_channel_width,
int min_channel_height,
bool pin_aware_channels) const;

// Applies every option accepted by the rtl_macro_placer command in one
// call. Must be called after init() (it reads/writes tree_ and block_)
Expand Down
24 changes: 24 additions & 0 deletions src/mpl/src/mpl-util.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,30 @@ using ClusterToBoundaryRegionMap = std::map<Cluster*, BoundaryRegion>;
using BoundaryRegionList = std::vector<BoundaryRegion>;
using RectList = std::vector<odb::Rect>;

inline int checkedMicronsToDbu(odb::dbBlock* block,
double microns,
const char* option,
utl::Logger* logger)
{
double rounded_dbu = microns;
if (std::isfinite(microns)) {
rounded_dbu = std::round(microns * block->getTech()->getDbUnitsPerMicron());
}
if (!std::isfinite(rounded_dbu)
|| rounded_dbu < std::numeric_limits<int>::min()
|| rounded_dbu > std::numeric_limits<int>::max()) {
logger->error(utl::MPL,
83,
"Invalid {} value {}: must be finite and round to a DBU "
"value in [{}, {}].",
option,
microns,
std::numeric_limits<int>::min(),
std::numeric_limits<int>::max());
}
return static_cast<int>(rounded_dbu);
}

// One of the edges of the die area.
enum class Boundary
{
Expand Down
31 changes: 31 additions & 0 deletions src/mpl/src/mpl.i
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
// Copyright (c) 2021-2025, The OpenROAD Authors

%{
#include <optional>

#include "ord/OpenRoad.hh"
#include "mpl/rtl_mp.h"
#include "mpl-util.h"
#include "MplObserver.h"
#include "graphics.h"
#include "odb/db.h"
Expand Down Expand Up @@ -115,6 +118,34 @@ bool rtl_macro_placer_cmd(const std::map<std::string, std::string>& keys,
return macro_placer->run(threads, options);
}

void check_macro_placement_cmd(double fence_lx,
double fence_ly,
double fence_ux,
double fence_uy,
double min_channel_width,
double min_channel_height,
bool pin_aware_channels,
bool fence_provided)
{
auto block = ord::OpenRoad::openRoad()->getDb()->getChip()->getBlock();

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

If no design is loaded, ord::OpenRoad::openRoad()->getDb()->getChip() can return nullptr. Dereferencing it directly will cause a segmentation fault. Check if the chip and block exist before proceeding.

  auto chip = ord::OpenRoad::openRoad()->getDb()->getChip();
  if (chip == nullptr || chip->getBlock() == nullptr) {
    ord::getLogger()->error(utl::MPL, 1, "No block found for Macro Placement.");
  }
  auto block = chip->getBlock();

auto logger = ord::getLogger();
const int channel_width = checkedMicronsToDbu(
block, min_channel_width, "-min_channel_size width", logger);
const int channel_height = checkedMicronsToDbu(
block, min_channel_height, "-min_channel_size height", logger);
std::optional<odb::Rect> global_fence;
if (fence_provided) {
global_fence.emplace();
// Preserve corner ordering so malformed bounds are not silently normalized.
global_fence->reset(checkedMicronsToDbu(block, fence_lx, "-fence_lx", logger),
checkedMicronsToDbu(block, fence_ly, "-fence_ly", logger),
checkedMicronsToDbu(block, fence_ux, "-fence_ux", logger),
checkedMicronsToDbu(block, fence_uy, "-fence_uy", logger));
}
getMacroPlacer()->checkMacroPlacement(
global_fence, channel_width, channel_height, pin_aware_channels);
}

void set_debug_cmd(const std::map<std::string, std::string>& keys,
const std::map<std::string, std::string>& flags)
{
Expand Down
Loading
Loading