diff --git a/src/dbSta/include/db_sta/IpChecker.hh b/src/dbSta/include/db_sta/IpChecker.hh index 5ec6eed242e..b72c848f129 100644 --- a/src/dbSta/include/db_sta/IpChecker.hh +++ b/src/dbSta/include/db_sta/IpChecker.hh @@ -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 @@ -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) diff --git a/src/dbSta/src/IpChecker.cc b/src/dbSta/src/IpChecker.cc index 00b4ab7c1c6..71883c5d01e 100644 --- a/src/dbSta/src/IpChecker.cc +++ b/src/dbSta/src/IpChecker.cc @@ -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> layer_pin_centers; for (odb::dbMTerm* mterm : master->getMTerms()) { @@ -315,41 +316,38 @@ 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(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 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); @@ -357,23 +355,20 @@ void IpChecker::checkPinRoutingGridAlignment(odb::dbMaster* master) 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 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 { @@ -386,7 +381,6 @@ 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) { @@ -394,14 +388,20 @@ void IpChecker::checkPinRoutingGridAlignment(odb::dbMaster* master) } } - 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 " @@ -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_++; + } } } } diff --git a/src/dbSta/test/BUILD b/src/dbSta/test/BUILD index 00fde107816..325b781361f 100644 --- a/src/dbSta/test/BUILD +++ b/src/dbSta/test/BUILD @@ -109,6 +109,7 @@ ALL_TESTS = [ PASSFAIL_TESTS = [ "check_ip_liberty_pins", + "check_ip_multilayer_alignment", ] filegroup( diff --git a/src/dbSta/test/CMakeLists.txt b/src/dbSta/test/CMakeLists.txt index 4e267746b97..4b6cc87f4da 100644 --- a/src/dbSta/test/CMakeLists.txt +++ b/src/dbSta/test/CMakeLists.txt @@ -79,6 +79,7 @@ or_integration_tests( write_verilog9_hier PASSFAIL_TESTS check_ip_liberty_pins + check_ip_multilayer_alignment ) if(ENABLE_TESTS) diff --git a/src/dbSta/test/check_ip_multilayer_alignment.def b/src/dbSta/test/check_ip_multilayer_alignment.def new file mode 100644 index 00000000000..e520358ccd6 --- /dev/null +++ b/src/dbSta/test/check_ip_multilayer_alignment.def @@ -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 diff --git a/src/dbSta/test/check_ip_multilayer_alignment.lef b/src/dbSta/test/check_ip_multilayer_alignment.lef new file mode 100644 index 00000000000..7507398e51f --- /dev/null +++ b/src/dbSta/test/check_ip_multilayer_alignment.lef @@ -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 diff --git a/src/dbSta/test/check_ip_multilayer_alignment.tcl b/src/dbSta/test/check_ip_multilayer_alignment.tcl new file mode 100644 index 00000000000..abd2419363e --- /dev/null +++ b/src/dbSta/test/check_ip_multilayer_alignment.tcl @@ -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"