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
4 changes: 2 additions & 2 deletions src/dbSta/include/db_sta/IpChecker.hh
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ namespace sta {
// Checks performed:
// LEF-CHK-001: Macro dimensions aligned to manufacturing grid
// LEF-CHK-002: Pin coordinates aligned to manufacturing grid
// LEF-CHK-003: Pin routing grid alignment
// LEF-CHK-003: Pin routing grid alignment, including cross-layer compatibility
// LEF-CHK-004-005: Pin accessibility (signal and power)
// LEF-CHK-006: Polygon count
// LEF-CHK-007: Antenna information present
Expand Down Expand Up @@ -66,7 +66,7 @@ class IpChecker
// LEF-CHK-002: Pin coordinates aligned to manufacturing grid
void checkPinManufacturingGridAlignment(odb::dbMaster* master);

// LEF-CHK-003: Pin routing grid alignment
// LEF-CHK-003: Pin routing grid alignment, including cross-layer compatibility
void checkPinRoutingGridAlignment(odb::dbMaster* master);

// LEF-CHK-004-005: Pin accessibility (signal and power)
Expand Down
119 changes: 83 additions & 36 deletions src/dbSta/src/IpChecker.cc
Original file line number Diff line number Diff line change
Expand Up @@ -294,8 +294,9 @@ void IpChecker::checkPinRoutingGridAlignment(odb::dbMaster* master)

std::string master_name = master->getName();

// Collect minimum-width signal pin centers grouped by layer
// key: layer, value: list of pin center positions along routing direction
// Collect minimum-width signal pin centers grouped by layer.
// For horizontal routing layers the center is a Y coordinate; for vertical
// routing layers it is an X coordinate.
odb::PtrMap<odb::dbTechLayer, std::vector<int>> layer_pin_centers;

for (odb::dbMTerm* mterm : master->getMTerms()) {
Expand All @@ -315,65 +316,59 @@ void IpChecker::checkPinRoutingGridAlignment(odb::dbMaster* master)
bool is_horizontal
= (layer->getDirection() == odb::dbTechLayerDir::HORIZONTAL);

// Only check minimum-width pins
// For wider pins, routing might connect regardless of offset
// Only check minimum-width pins. Wider pins may connect to a track
// regardless of their center offset.
int pin_dim = is_horizontal ? rect.dy() : rect.dx();
if (min_width > 0 && static_cast<uint32_t>(pin_dim) > min_width) {
continue; // Wider than minimum, skip for now
continue;
}

// Pin center along the routing direction
int center = is_horizontal ? rect.yCenter() : rect.xCenter();
layer_pin_centers[layer].push_back(center);
}
}
}

// For each layer, compute GCD of distances and check against track pitch
for (auto& [layer, centers] : layer_pin_centers) {
if (centers.size() < 2) {
continue; // Need at least 2 pins to compute distances
}
struct LayerAlignmentConstraint
{
odb::dbTechLayer* layer;
int pitch;
int required_offset;
bool is_horizontal;
};
std::vector<LayerAlignmentConstraint> constraints;

// Sort to compute distances between consecutive pin centers.
// Sorting is needed because pins are collected per-mterm, not in spatial
// order. Consecutive distances after sorting give the minimal spacings
// whose GCD represents the pin grid.
std::ranges::sort(centers);
int distance_gcd = 0;
for (size_t i = 1; i < centers.size(); i++) {
int dist = centers[i] - centers[i - 1];
if (dist > 0) {
distance_gcd = std::gcd(distance_gcd, dist);
}
}
auto positive_mod = [](int value, int modulus) {
int result = value % modulus;
return result < 0 ? result + modulus : result;
};

if (distance_gcd == 0) {
continue; // All pins at same position
// First validate alignment within each layer, then keep the translation
// congruence required by that layer for the cross-layer check below.
for (auto& [layer, centers] : layer_pin_centers) {
if (centers.empty()) {
continue;
}

odb::dbTrackGrid* track_grid = block->findTrackGrid(layer);
if (!track_grid) {
continue;
}

// Compute the effective pitch across all track patterns on this layer.
// Multiple patterns with different offsets create a finer effective pitch.
bool is_horizontal
= (layer->getDirection() == odb::dbTechLayerDir::HORIZONTAL);
int num_patterns = is_horizontal ? track_grid->getNumGridPatternsY()
: track_grid->getNumGridPatternsX();

if (num_patterns == 0) {
continue;
}

// Collect all origins and pitches
std::vector<int> origins;
int effective_pitch = 0;

for (int i = 0; i < num_patterns; i++) {
int origin = 0, line_count = 0, pitch = 0;
int origin = 0;
int line_count = 0;
int pitch = 0;
if (is_horizontal) {
track_grid->getGridPatternY(i, origin, line_count, pitch);
} else {
Expand All @@ -386,22 +381,27 @@ void IpChecker::checkPinRoutingGridAlignment(odb::dbMaster* master)
}
}

// The offsets between pattern origins also refine the effective pitch
for (size_t i = 1; i < origins.size(); i++) {
int offset_diff = std::abs(origins[i] - origins[0]);
if (offset_diff > 0) {
effective_pitch = std::gcd(effective_pitch, offset_diff);
}
}

if (effective_pitch <= 0) {
if (effective_pitch <= 0 || origins.empty()) {
continue;
}

// Pin distance GCD must be a multiple of the effective pitch
bool compatible = (distance_gcd % effective_pitch == 0);
std::ranges::sort(centers);
int distance_gcd = 0;
for (size_t i = 1; i < centers.size(); i++) {
int dist = centers[i] - centers[i - 1];
if (dist > 0) {
distance_gcd = std::gcd(distance_gcd, dist);
}
}

if (!compatible) {
if (distance_gcd > 0 && distance_gcd % effective_pitch != 0) {
logger_->warn(utl::CHK,
30,
"Master {} signal pins on layer {} cannot be aligned "
Expand All @@ -412,6 +412,53 @@ void IpChecker::checkPinRoutingGridAlignment(odb::dbMaster* master)
distance_gcd,
effective_pitch);
warning_count_++;
continue;
}

// All track-pattern origins are congruent modulo effective_pitch because
// origin differences participate in the GCD above. A macro translation
// must therefore satisfy this residue for this layer.
constraints.push_back(
{layer,
effective_pitch,
positive_mod(origins[0] - centers[0], effective_pitch),
is_horizontal});
}

// A single macro translation must satisfy every layer that moves on the
// same placement axis. By the generalized Chinese Remainder Theorem,
// t = a (mod m) and t = b (mod n) are compatible iff a-b is divisible by
// gcd(m, n). Horizontal routing layers constrain Y; vertical layers X.
for (size_t i = 0; i < constraints.size(); i++) {
for (size_t j = i + 1; j < constraints.size(); j++) {
const auto& first = constraints[i];
const auto& second = constraints[j];
if (first.is_horizontal != second.is_horizontal) {
continue;
}

int common_pitch = std::gcd(first.pitch, second.pitch);
if (common_pitch <= 0) {
continue;
}

int offset_delta = first.required_offset - second.required_offset;
if (positive_mod(offset_delta, common_pitch) != 0) {
logger_->warn(
utl::CHK,
31,
"Master {} signal pins on layers {} and {} cannot be aligned "
"simultaneously to their track grids (required offsets {} mod {} "
"and {} mod {})",
master_name,
first.layer->getName(),
second.layer->getName(),
first.required_offset,
first.pitch,
second.required_offset,
second.pitch);
warning_count_++;
}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/dbSta/test/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ ALL_TESTS = [

PASSFAIL_TESTS = [
"check_ip_liberty_pins",
"check_ip_multilayer_alignment",
]

filegroup(
Expand Down
1 change: 1 addition & 0 deletions src/dbSta/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ or_integration_tests(
write_verilog9_hier
PASSFAIL_TESTS
check_ip_liberty_pins
check_ip_multilayer_alignment
)

if(ENABLE_TESTS)
Expand Down
14 changes: 14 additions & 0 deletions src/dbSta/test/check_ip_multilayer_alignment.def
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
VERSION 5.8 ;
DIVIDERCHAR "/" ;
BUSBITCHARS "[]" ;

DESIGN check_ip_multilayer_alignment ;

UNITS DISTANCE MICRONS 1000 ;

DIEAREA ( 0 0 ) ( 100000 100000 ) ;

TRACKS Y 100 DO 500 STEP 200 LAYER M1 ;
TRACKS Y 0 DO 500 STEP 200 LAYER M3 ;

END DESIGN
83 changes: 83 additions & 0 deletions src/dbSta/test/check_ip_multilayer_alignment.lef
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
VERSION 5.8 ;
BUSBITCHARS "[]" ;
DIVIDERCHAR "/" ;

UNITS
DATABASE MICRONS 1000 ;
END UNITS

MANUFACTURINGGRID 0.005 ;

LAYER M1
TYPE ROUTING ;
DIRECTION HORIZONTAL ;
PITCH 0.200 ;
WIDTH 0.100 ;
END M1

LAYER M3
TYPE ROUTING ;
DIRECTION HORIZONTAL ;
PITCH 0.200 ;
WIDTH 0.100 ;
END M3

SITE unit
CLASS CORE ;
SIZE 0.200 BY 2.000 ;
SYMMETRY Y ;
END unit

# M1 center_y=150: with track origin 100 it needs translation 150 mod 200.
# M3 center_y=50: with track origin 0 it also needs translation 150 mod 200.
MACRO cross_layer_pass
CLASS BLOCK ;
ORIGIN 0 0 ;
SIZE 10.000 BY 10.000 ;
PIN A
DIRECTION INPUT ;
USE SIGNAL ;
ANTENNAMODEL OXIDE1 ;
PORT
LAYER M1 ;
RECT 0.000 0.100 0.200 0.200 ;
END
END A
PIN B
DIRECTION INPUT ;
USE SIGNAL ;
ANTENNAMODEL OXIDE1 ;
PORT
LAYER M3 ;
RECT 0.000 0.000 0.200 0.100 ;
END
END B
END cross_layer_pass

# Both centers are 150. M1 needs translation 150 mod 200, while M3 needs
# translation 50 mod 200. Each layer can align alone, but not simultaneously.
MACRO cross_layer_fail
CLASS BLOCK ;
ORIGIN 0 0 ;
SIZE 10.000 BY 10.000 ;
PIN A
DIRECTION INPUT ;
USE SIGNAL ;
ANTENNAMODEL OXIDE1 ;
PORT
LAYER M1 ;
RECT 0.000 0.100 0.200 0.200 ;
END
END A
PIN B
DIRECTION INPUT ;
USE SIGNAL ;
ANTENNAMODEL OXIDE1 ;
PORT
LAYER M3 ;
RECT 0.000 0.100 0.200 0.200 ;
END
END B
END cross_layer_fail

END LIBRARY
19 changes: 19 additions & 0 deletions src/dbSta/test/check_ip_multilayer_alignment.tcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright (c) 2026, The OpenROAD Authors

source "helpers.tcl"

read_lef check_ip_multilayer_alignment.lef
read_def check_ip_multilayer_alignment.def

if { [catch { check_ip -master cross_layer_pass } err] } {
puts "FAIL: expected cross_layer_pass to pass: $err"
exit 1
}

if { ![catch { check_ip -master cross_layer_fail } err] } {
puts "FAIL: expected cross_layer_fail to fail"
exit 1
}

puts "pass"
Loading