diff --git a/src/odb/include/odb/3dblox.h b/src/odb/include/odb/3dblox.h index ab170cd11b..61873bb8aa 100644 --- a/src/odb/include/odb/3dblox.h +++ b/src/odb/include/odb/3dblox.h @@ -32,6 +32,7 @@ struct ChipletInst; struct Connection; struct DesignDef; struct BumpMapEntry; +struct DbxData; class ThreeDBlox { @@ -59,6 +60,7 @@ class ThreeDBlox std::vector& path_insts); void readHeaderIncludes(const std::vector& includes); void calculateSize(dbChip* chip); + void buildChipNetsFromVerilog(dbChip* chip, const DbxData& data); utl::Logger* logger_ = nullptr; odb::dbDatabase* db_ = nullptr; diff --git a/src/odb/src/3dblox/3dblox.cpp b/src/odb/src/3dblox/3dblox.cpp index f492dcae4e..a5b4753583 100644 --- a/src/odb/src/3dblox/3dblox.cpp +++ b/src/odb/src/3dblox/3dblox.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -29,7 +30,10 @@ #include "odb/geom.h" #include "odb/lefin.h" #include "odb/lefout.h" +#include "sta/ConcreteNetwork.hh" +#include "sta/NetworkClass.hh" #include "sta/Sta.hh" +#include "sta/VerilogReader.hh" #include "utl/Logger.h" #include "utl/ScopedTemporaryFile.h" namespace odb { @@ -78,6 +82,111 @@ void ThreeDBlox::readDbv(const std::string& dbv_file) } } +void ThreeDBlox::buildChipNetsFromVerilog(dbChip* chip, const DbxData& data) +{ + // Read Verilog and create nets + if (!sta_ || data.design.external.verilog_file.empty()) { + return; + } + + std::string verilog_file = data.design.external.verilog_file; + if (!std::filesystem::exists(verilog_file)) { + return; + } + + sta::ConcreteNetwork temp_network; + temp_network.copyState(sta_); + sta::VerilogReader verilog_reader(&temp_network); + + if (!verilog_reader.read(verilog_file.c_str())) { + return; + } + + sta::Instance* top_inst + = verilog_reader.linkNetwork(data.design.name.c_str(), true, false); + if (!top_inst) { + logger_->warn(utl::ODB, + 555, + "Verilog module {} not found in Verilog file {}.", + data.design.name, + verilog_file); + return; + } + + // Pre-process master chips to map port names to bumps + std::map> master_bump_map; + for (auto* inst : chip->getChipInsts()) { + dbChip* master = inst->getMasterChip(); + + // Skip if already processed + if (master_bump_map.contains(master)) { + continue; + } + + for (auto* region : master->getChipRegions()) { + for (auto* bump : region->getChipBumps()) { + if (auto* bterm = bump->getBTerm()) { + master_bump_map[master][bterm->getName()] = bump; + } + } + } + } + + // Process nets + std::unique_ptr net_iter( + temp_network.netIterator(top_inst)); + while (net_iter->hasNext()) { + auto* net = net_iter->next(); + const char* net_name = temp_network.name(net); + auto* chip_net = dbChipNet::create(chip, net_name); + + debugPrint(logger_, + utl::ODB, + "3dblox", + 1, + "Created dbChipNet {} for chip {} from Verilog", + net_name, + chip->getName()); + + std::unique_ptr pin_iter( + temp_network.pinIterator(net)); + while (pin_iter->hasNext()) { + const sta::Pin* pin = pin_iter->next(); + const sta::Instance* instance = temp_network.instance(pin); + + if (instance == top_inst) { + continue; + } + + auto* chip_inst = chip->findChipInst(temp_network.name(instance)); + if (!chip_inst) { + continue; + } + + const char* port_name = temp_network.name(temp_network.port(pin)); + dbChip* master = chip_inst->getMasterChip(); + + auto bump_it = master_bump_map[master].find(port_name); + if (bump_it == master_bump_map[master].end()) { + continue; + } + + dbChipBump* bump = bump_it->second; + auto* region_inst = chip_inst->findChipRegionInst(bump->getChipRegion()); + if (!region_inst) { + continue; + } + + for (auto bump_inst : region_inst->getChipBumpInsts()) { + if (bump_inst->getChipBump() == bump) { + chip_net->addBumpInst(bump_inst, {chip_inst}); + break; + } + } + } + } +} + void ThreeDBlox::readDbx(const std::string& dbx_file) { read_files_.insert(std::filesystem::absolute(dbx_file).string()); @@ -88,6 +197,9 @@ void ThreeDBlox::readDbx(const std::string& dbx_file) for (const auto& [_, chip_inst] : data.chiplet_instances) { createChipInst(chip_inst); } + + buildChipNetsFromVerilog(chip, data); + for (const auto& [_, connection] : data.connections) { createConnection(connection); } @@ -332,12 +444,7 @@ void ThreeDBlox::createChiplet(const ChipletDef& chiplet) chip = dbChip::create( db_, tech, chiplet.name, getChipType(chiplet.type, logger_)); } - if (!chiplet.external.verilog_file.empty()) { - if (odb::dbProperty::find(chip, "verilog_file") == nullptr) { - odb::dbStringProperty::create( - chip, "verilog_file", chiplet.external.verilog_file.c_str()); - } - } + // Read DEF file if (!chiplet.external.def_file.empty()) { odb::defin def_reader(db_, logger_, odb::defin::DEFAULT); diff --git a/src/odb/src/3dblox/checker.cpp b/src/odb/src/3dblox/checker.cpp index 86a3e63c66..cf41866060 100644 --- a/src/odb/src/3dblox/checker.cpp +++ b/src/odb/src/3dblox/checker.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -94,9 +95,9 @@ Checker::Checker(utl::Logger* logger) : logger_(logger) void Checker::check(dbChip* chip) { - UnfoldedModel model(logger_, chip); auto* top_cat = dbMarkerCategory::createOrReplace(chip, "3DBlox"); - + UnfoldedModel model(logger_, chip); + checkLogicalConnectivity(top_cat, model); checkFloatingChips(top_cat, model); checkOverlappingChips(top_cat, model); checkInternalExtUsage(top_cat, model); @@ -288,4 +289,78 @@ void Checker::checkNetConnectivity(dbMarkerCategory* top_cat, { } +void Checker::checkLogicalConnectivity(dbMarkerCategory* top_cat, + const UnfoldedModel& model) +{ + std::unordered_map bump_net_map; + for (const auto& net : model.getNets()) { + for (const auto* bump : net.connected_bumps) { + bump_net_map[bump] = &net; + } + } + + auto get_net_name = [&](const UnfoldedBump* bump) -> std::string { + auto it = bump_net_map.find(bump); + if (it != bump_net_map.end()) { + return it->second->chip_net->getName(); + } + return "defines no net"; + }; + + dbMarkerCategory* cat = nullptr; + for (const auto& conn : model.getConnections()) { + if (!isValid(conn)) { + continue; + } + if (!conn.top_region || !conn.bottom_region) { + continue; + } + + std::map bot_bumps; + for (const auto& bump : conn.bottom_region->bumps) { + Point p(bump.global_position.x(), bump.global_position.y()); + bot_bumps[p] = ≎ + } + + for (const auto& top_bump : conn.top_region->bumps) { + Point p(top_bump.global_position.x(), top_bump.global_position.y()); + auto it = bot_bumps.find(p); + if (it != bot_bumps.end()) { + const UnfoldedBump* bot_bump = it->second; + + // Check logical connectivity + auto top_net_it = bump_net_map.find(&top_bump); + auto bot_net_it = bump_net_map.find(bot_bump); + + const UnfoldedNet* top_net + = top_net_it != bump_net_map.end() ? top_net_it->second : nullptr; + const UnfoldedNet* bot_net + = bot_net_it != bump_net_map.end() ? bot_net_it->second : nullptr; + + if (top_net != bot_net) { + if (!cat) { + cat = dbMarkerCategory::createOrReplace(top_cat, + "Logical Connectivity"); + } + auto* marker = dbMarker::create(cat); + marker->addSource(top_bump.bump_inst); + marker->addSource(bot_bump->bump_inst); + marker->addShape(conn.top_region->cuboid.intersect( + conn.bottom_region->cuboid)); // Mark overlap region + + std::string msg = fmt::format( + "Bumps at ({}, {}) align physically but logical connectivity " + "mismatch: Top bump {} vs Bottom bump {}", + p.x(), + p.y(), + get_net_name(&top_bump), + get_net_name(bot_bump)); + marker->setComment(msg); + logger_->warn(utl::ODB, 208, msg); + } + } + } + } +} + } // namespace odb diff --git a/src/odb/src/3dblox/checker.h b/src/odb/src/3dblox/checker.h index 2690b5f981..e37de374d2 100644 --- a/src/odb/src/3dblox/checker.h +++ b/src/odb/src/3dblox/checker.h @@ -7,6 +7,10 @@ #include "odb/unfoldedModel.h" #include "utl/Logger.h" +namespace sta { +class Sta; +} + namespace odb { class dbChip; class dbMarkerCategory; @@ -26,6 +30,8 @@ class Checker void check(dbChip* chip); private: + void checkLogicalConnectivity(dbMarkerCategory* top_cat, + const UnfoldedModel& model); void checkFloatingChips(dbMarkerCategory* top_cat, const UnfoldedModel& model); void checkOverlappingChips(dbMarkerCategory* top_cat, diff --git a/src/odb/test/cpp/CMakeLists.txt b/src/odb/test/cpp/CMakeLists.txt index 63bc8ab751..7b510837d9 100644 --- a/src/odb/test/cpp/CMakeLists.txt +++ b/src/odb/test/cpp/CMakeLists.txt @@ -36,7 +36,7 @@ add_executable(TestMaster TestMaster.cpp) add_executable(TestGDSIn TestGDSIn.cpp) add_executable(TestChips TestChips.cpp) add_executable(Test3DBloxParser Test3DBloxParser.cpp) -add_executable(Test3DBloxChecker Test3DBloxChecker.cpp) +add_executable(Test3DBloxChecker Test3DBloxChecker.cpp Test3DBloxCheckerLogicalConn.cpp) add_executable(TestSwapMaster TestSwapMaster.cpp) add_executable(TestSwapMasterUnusedPort TestSwapMasterUnusedPort.cpp) add_executable(TestWriteReadDbHier TestWriteReadDbHier.cpp) diff --git a/src/odb/test/cpp/Test3DBloxChecker.cpp b/src/odb/test/cpp/Test3DBloxChecker.cpp index 46becc8b7c..88d23aac48 100644 --- a/src/odb/test/cpp/Test3DBloxChecker.cpp +++ b/src/odb/test/cpp/Test3DBloxChecker.cpp @@ -4,88 +4,16 @@ #include #include +#include "Test3DBloxCheckerFixture.h" #include "gtest/gtest.h" #include "odb/3dblox.h" #include "odb/db.h" #include "odb/dbObject.h" #include "odb/geom.h" -#include "tst/fixture.h" namespace odb { namespace { -class CheckerFixture : public tst::Fixture -{ - protected: - CheckerFixture() - { - tech_ = dbTech::create(db_.get(), "tech"); - top_chip_ - = dbChip::create(db_.get(), nullptr, "TopChip", dbChip::ChipType::HIER); - - // Create master chips - chip1_ = dbChip::create(db_.get(), tech_, "Chip1", dbChip::ChipType::DIE); - chip1_->setWidth(2000); - chip1_->setHeight(2000); - chip1_->setThickness(500); - - chip2_ = dbChip::create(db_.get(), tech_, "Chip2", dbChip::ChipType::DIE); - chip2_->setWidth(1500); - chip2_->setHeight(1500); - chip2_->setThickness(500); - - // Create regions on master chips - auto r1_fr = dbChipRegion::create( - chip1_, "r1_fr", dbChipRegion::Side::FRONT, nullptr); - r1_fr->setBox(Rect(0, 0, 2000, 2000)); - - auto r2_bk = dbChipRegion::create( - chip2_, "r2_bk", dbChipRegion::Side::BACK, nullptr); - r2_bk->setBox(Rect(0, 0, 1500, 1500)); - - auto r2_fr = dbChipRegion::create( - chip2_, "r2_fr", dbChipRegion::Side::FRONT, nullptr); - r2_fr->setBox(Rect(0, 0, 1500, 1500)); - } - - void check() - { - db_->setTopChip(top_chip_); - ThreeDBlox three_dblox(&logger_, db_.get()); - three_dblox.check(); - } - - std::vector getMarkers(const char* category_name) - { - auto top_cat = top_chip_->findMarkerCategory("3DBlox"); - if (!top_cat) { - return {}; - } - auto cat = top_cat->findMarkerCategory(category_name); - if (!cat) { - return {}; - } - - std::vector markers; - for (auto* m : cat->getMarkers()) { - markers.push_back(m); - } - return markers; - } - - dbTech* tech_; - dbChip* top_chip_; - dbChip* chip1_; - dbChip* chip2_; - - static constexpr const char* floating_chips_category = "Floating chips"; - static constexpr const char* overlapping_chips_category = "Overlapping chips"; - static constexpr const char* unused_internal_ext_category - = "Unused internal_ext"; - static constexpr const char* connected_regions_category - = "Connection regions"; -}; - TEST_F(CheckerFixture, test_no_violations) { auto inst1 = dbChipInst::create(top_chip_, chip1_, "inst1"); diff --git a/src/odb/test/cpp/Test3DBloxCheckerFixture.h b/src/odb/test/cpp/Test3DBloxCheckerFixture.h new file mode 100644 index 0000000000..aa6abf3d59 --- /dev/null +++ b/src/odb/test/cpp/Test3DBloxCheckerFixture.h @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2023-2026, The OpenROAD Authors + +#pragma once + +#include + +#include "odb/3dblox.h" +#include "odb/db.h" +#include "odb/dbWireCodec.h" +#include "odb/geom.h" +#include "tst/fixture.h" + +namespace odb { +class CheckerFixture : public tst::Fixture +{ + protected: + CheckerFixture() + { + tech_ = dbTech::create(db_.get(), "tech"); + top_chip_ + = dbChip::create(db_.get(), nullptr, "TopChip", dbChip::ChipType::HIER); + + // Create master chips + chip1_ = dbChip::create(db_.get(), tech_, "Chip1", dbChip::ChipType::DIE); + chip1_->setWidth(2000); + chip1_->setHeight(2000); + chip1_->setThickness(500); + + chip2_ = dbChip::create(db_.get(), tech_, "Chip2", dbChip::ChipType::DIE); + chip2_->setWidth(1500); + chip2_->setHeight(1500); + chip2_->setThickness(500); + + // Create regions on master chips + auto r1_fr = dbChipRegion::create( + chip1_, "r1_fr", dbChipRegion::Side::FRONT, nullptr); + r1_fr->setBox(Rect(0, 0, 2000, 2000)); + + auto r2_bk = dbChipRegion::create( + chip2_, "r2_bk", dbChipRegion::Side::BACK, nullptr); + r2_bk->setBox(Rect(0, 0, 1500, 1500)); + + auto r2_fr = dbChipRegion::create( + chip2_, "r2_fr", dbChipRegion::Side::FRONT, nullptr); + r2_fr->setBox(Rect(0, 0, 1500, 1500)); + } + + void check() + { + db_->setTopChip(top_chip_); + ThreeDBlox three_dblox(&logger_, db_.get()); + three_dblox.check(); + } + + std::vector getMarkers(const char* category_name) + { + auto top_cat = top_chip_->findMarkerCategory("3DBlox"); + if (!top_cat) { + return {}; + } + auto cat = top_cat->findMarkerCategory(category_name); + if (!cat) { + return {}; + } + + std::vector markers; + for (auto* m : cat->getMarkers()) { + markers.push_back(m); + } + return markers; + } + + dbTech* tech_; + dbChip* top_chip_; + dbChip* chip1_; + dbChip* chip2_; + + static constexpr const char* floating_chips_category = "Floating chips"; + static constexpr const char* overlapping_chips_category = "Overlapping chips"; + static constexpr const char* unused_internal_ext_category + = "Unused internal_ext"; + static constexpr const char* connected_regions_category + = "Connection regions"; + static constexpr const char* logical_connectivity_category + = "Logical Connectivity"; +}; + +} // namespace odb diff --git a/src/odb/test/cpp/Test3DBloxCheckerLogicalConn.cpp b/src/odb/test/cpp/Test3DBloxCheckerLogicalConn.cpp new file mode 100644 index 0000000000..26026e9a4e --- /dev/null +++ b/src/odb/test/cpp/Test3DBloxCheckerLogicalConn.cpp @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2023-2026, The OpenROAD Authors + +#include +#include + +#include "Test3DBloxCheckerFixture.h" +#include "gtest/gtest.h" +#include "odb/3dblox.h" +#include "odb/db.h" +#include "odb/geom.h" + +namespace odb { +namespace { + +class CheckerLogicalConnFixture : public CheckerFixture +{ + protected: + CheckerLogicalConnFixture() + { + dbTechLayer::create(tech_, "layer1", dbTechLayerType::ROUTING); + + // DIE chips need blocks (for bump masters/cells) + dbBlock::create(chip1_, "block1"); + dbBlock::create(chip2_, "block2"); + + // Lib + bump master + lib_ = dbLib::create(db_.get(), "bump_lib", tech_, ','); + bump_master_ = dbMaster::create(lib_, "bump_pad"); + bump_master_->setWidth(100); + bump_master_->setHeight(100); + bump_master_->setType(dbMasterType::CORE); + dbMTerm::create(bump_master_, "pin", dbIoType::INOUT, dbSigType::SIGNAL); + bump_master_->setFrozen(); + } + + // Create a bump on a chip region. + dbChipBump* createBump(dbChip* chip, + dbChipRegion* region, + const char* bump_name, + int x, + int y) + { + dbBlock* block = chip->getBlock(); + dbTechLayer* layer = tech_->findLayer("layer1"); + + // Physical cell in the block + dbInst* inst = dbInst::create(block, bump_master_, bump_name); + inst->setOrigin(x, y); + inst->setPlacementStatus(dbPlacementStatus::PLACED); + + // Chip-level bump association + dbChipBump* chip_bump = dbChipBump::create(region, inst); + + // Net + BTerm for wire-graph connectivity + std::string net_name = std::string(bump_name) + "_net"; + dbNet* net = block->findNet(net_name.c_str()); + if (!net) { + net = dbNet::create(block, net_name.c_str()); + } + dbBTerm* bterm = dbBTerm::create(net, bump_name); + bterm->setIoType(dbIoType::INOUT); + + // BPin with box at the bump location + dbBPin* bpin = dbBPin::create(bterm); + bpin->setPlacementStatus(dbPlacementStatus::PLACED); + dbBox::create(bpin, layer, x, y, x + 100, y + 100); + + chip_bump->setNet(net); + chip_bump->setBTerm(bterm); + + return chip_bump; + } + + void check() + { + utl::Logger logger; + ThreeDBlox three_dblox(&logger, db_.get()); + three_dblox.check(); + } + + dbLib* lib_; + dbMaster* bump_master_; +}; + +TEST_F(CheckerLogicalConnFixture, test_logical_connectivity_matching_nets) +{ + auto* r1_fr = chip1_->findChipRegion("r1_fr"); + auto* r2_bk = chip2_->findChipRegion("r2_bk"); + + createBump(chip1_, r1_fr, "bump1", 100, 100); + createBump(chip2_, r2_bk, "bump2", 100, 100); + + auto inst1 = dbChipInst::create(top_chip_, chip1_, "inst1"); + inst1->setLoc(Point3D(0, 0, 0)); + inst1->setOrient(dbOrientType3D(dbOrientType::R0, false)); + + auto inst2 = dbChipInst::create(top_chip_, chip2_, "inst2"); + inst2->setLoc(Point3D(0, 0, 500)); // Stacked on top + inst2->setOrient(dbOrientType3D(dbOrientType::R0, false)); + + auto* ri1 = inst1->findChipRegionInst("r1_fr"); + auto* ri2 = inst2->findChipRegionInst("r2_bk"); + + auto* conn1 = dbChipConn::create("c1", top_chip_, {inst1}, ri1, {inst2}, ri2); + conn1->setThickness(0); + + auto inst1_bump1 = *ri1->getChipBumpInsts().begin(); + auto inst2_bump2 = *ri2->getChipBumpInsts().begin(); + + auto* chip_net = dbChipNet::create(top_chip_, "net1"); + chip_net->addBumpInst(inst1_bump1, {inst1}); + chip_net->addBumpInst(inst2_bump2, {inst2}); + + check(); + + auto markers = getMarkers(logical_connectivity_category); + EXPECT_TRUE(markers.empty()); +} + +TEST_F(CheckerLogicalConnFixture, test_logical_connectivity_mismatching_nets) +{ + auto* r1_fr = chip1_->findChipRegion("r1_fr"); + auto* r2_bk = chip2_->findChipRegion("r2_bk"); + + createBump(chip1_, r1_fr, "bump1", 200, 200); + createBump(chip2_, r2_bk, "bump2", 200, 200); + + auto inst1 = dbChipInst::create(top_chip_, chip1_, "inst1"); + inst1->setLoc(Point3D(0, 0, 0)); + inst1->setOrient(dbOrientType3D(dbOrientType::R0, false)); + + auto inst2 = dbChipInst::create(top_chip_, chip2_, "inst2"); + inst2->setLoc(Point3D(0, 0, 500)); // Stacked on top + inst2->setOrient(dbOrientType3D(dbOrientType::R0, false)); + + auto* ri1 = inst1->findChipRegionInst("r1_fr"); + auto* ri2 = inst2->findChipRegionInst("r2_bk"); + + auto* conn1 = dbChipConn::create("c1", top_chip_, {inst1}, ri1, {inst2}, ri2); + conn1->setThickness(0); + + auto inst1_bump1 = *ri1->getChipBumpInsts().begin(); + auto inst2_bump2 = *ri2->getChipBumpInsts().begin(); + + auto* chip_net1 = dbChipNet::create(top_chip_, "net1"); + chip_net1->addBumpInst(inst1_bump1, {inst1}); + + auto* chip_net2 = dbChipNet::create(top_chip_, "net2"); + chip_net2->addBumpInst(inst2_bump2, {inst2}); + + check(); + + auto markers = getMarkers(logical_connectivity_category); + EXPECT_EQ(markers.size(), 1); + if (!markers.empty()) { + auto sources = markers[0]->getSources(); + EXPECT_EQ(sources.size(), 2); + + // Check message correctness conceptually + EXPECT_NE(markers[0]->getComment().find("logical connectivity mismatch"), + std::string::npos); + } +} + +} // namespace +} // namespace odb