diff --git a/src/odb/README.md b/src/odb/README.md index 47aa8dd635e..aa65949b0c0 100644 --- a/src/odb/README.md +++ b/src/odb/README.md @@ -78,6 +78,44 @@ database to have exactly the same layout across save/restores. The database distance units are **nanometers** and use the type `uint32_t`. +### Add Corner + +Specifies a process corner for the current block. Corner names must be unique +within a block, so the command fails if a corner with the same name already +exists. + +```tcl +add_corner corner_name +``` + +#### Options + +| Switch Name | Description | +| ----- | ----- | +| `corner_name` | Name of the process corner to add. | + +### Remove Corner + +Removes a process corner from the current block. + +```tcl +remove_corner corner_name +``` + +#### Options + +| Switch Name | Description | +| ----- | ----- | +| `corner_name` | Name of the process corner to remove. | + +### Remove Corners + +Removes all process corners from the current block. + +```tcl +remove_corners +``` + ### Create Physical Cluster Description TBC. diff --git a/src/odb/include/odb/db.h b/src/odb/include/odb/db.h index d20b0f5d995..76323959c26 100644 --- a/src/odb/include/odb/db.h +++ b/src/odb/include/odb/db.h @@ -121,6 +121,7 @@ class dbChipNet; class dbChipPath; class dbChipRegion; class dbChipRegionInst; +class dbCorner; class dbDatabase; class dbDft; class dbGCellGrid; @@ -1026,6 +1027,10 @@ class dbBlock : public dbObject /// int getCornerCount(); + dbCorner* findCorner(const std::string& corner_name) const; + + void removeCorners(); + /// /// Get the number of corners kept n this block /// @@ -7575,6 +7580,18 @@ class dbChipRegionInst : public dbObject // User Code End dbChipRegionInst }; +class dbCorner : public dbObject +{ + public: + const std::string& getName() const; + + // User Code Begin dbCorner + static dbCorner* create(dbBlock* block_, const std::string& corner_name); + + static void destroy(dbCorner* corner_); + // User Code End dbCorner +}; + class dbDatabase : public dbObject { public: diff --git a/src/odb/include/odb/dbCompare.inc b/src/odb/include/odb/dbCompare.inc index e0f4ea41a1d..d4cbae96fc1 100644 --- a/src/odb/include/odb/dbCompare.inc +++ b/src/odb/include/odb/dbCompare.inc @@ -392,6 +392,13 @@ struct less = delete; }; +template <> +struct less +{ + bool operator()(const odb::dbCorner* lhs, const odb::dbCorner* rhs) const + = delete; +}; + template <> struct less { diff --git a/src/odb/include/odb/dbObject.h b/src/odb/include/odb/dbObject.h index b2e3edf3b35..137984d1f00 100644 --- a/src/odb/include/odb/dbObject.h +++ b/src/odb/include/odb/dbObject.h @@ -70,6 +70,7 @@ enum dbObjectType dbChipPathObj, dbChipRegionObj, dbChipRegionInstObj, + dbCornerObj, dbDatabaseObj, dbDftObj, dbGCellGridObj, diff --git a/src/odb/src/codeGenerator/schema/chip/dbCorner.json b/src/odb/src/codeGenerator/schema/chip/dbCorner.json new file mode 100644 index 00000000000..7b82f8e4db0 --- /dev/null +++ b/src/odb/src/codeGenerator/schema/chip/dbCorner.json @@ -0,0 +1,11 @@ +{ + "name": "dbCorner", + "type": "dbObject", + "fields": [ + { + "name": "name_", + "type": "std::string", + "flags":["cmpgt", "no-set"] + } + ] +} diff --git a/src/odb/src/db/CMakeLists.txt b/src/odb/src/db/CMakeLists.txt index 55944919aad..55059781813 100644 --- a/src/odb/src/db/CMakeLists.txt +++ b/src/odb/src/db/CMakeLists.txt @@ -103,6 +103,7 @@ add_library(db dbChipPath.cpp dbChipRegion.cpp dbChipRegionInst.cpp + dbCorner.cpp dbDatabase.cpp dbDft.cpp dbGCellGrid.cpp diff --git a/src/odb/src/db/dbBlock.cpp b/src/odb/src/db/dbBlock.cpp index 59582f99bd2..0ac7afd5777 100644 --- a/src/odb/src/db/dbBlock.cpp +++ b/src/odb/src/db/dbBlock.cpp @@ -47,6 +47,7 @@ #include "dbChip.h" #include "dbCommon.h" #include "dbCore.h" +#include "dbCorner.h" #include "dbDatabase.h" #include "dbDft.h" #include "dbFill.h" @@ -116,6 +117,7 @@ #include "dbTechLayerRule.h" #include "dbTechNonDefaultRule.h" #include "dbTrackGrid.h" +#include "dbVector.h" #include "dbVia.h" #include "dbWire.h" #include "odb/PtrSetMap.h" @@ -245,6 +247,9 @@ _dbBlock::_dbBlock(_dbDatabase* db) net_tracks_tbl_ = new dbTable<_dbNetTrack>( db, this, (GetObjTbl_t) &_dbBlock::getObjectTable, dbNetTrackObj); + corner_tbl_ = new dbTable<_dbCorner>( + db, this, (GetObjTbl_t) &_dbBlock::getObjectTable, dbCornerObj); + box_tbl_ = new dbTable<_dbBox, 1024>( db, this, (GetObjTbl_t) &_dbBlock::getObjectTable, dbBoxObj); @@ -437,6 +442,7 @@ _dbBlock::~_dbBlock() delete global_connect_tbl_; delete guide_tbl_; delete net_tracks_tbl_; + delete corner_tbl_; delete box_tbl_; delete via_tbl_; delete gcell_grid_tbl_; @@ -616,6 +622,9 @@ dbObjectTable* _dbBlock::getObjectTable(dbObjectType type) case dbNetTrackObj: return net_tracks_tbl_; + case dbCornerObj: + return corner_tbl_; + case dbNetObj: return net_tbl_; @@ -780,6 +789,7 @@ dbOStream& operator<<(dbOStream& stream, const _dbBlock& block) stream << block.max_cc_seg_id_; stream << block.children_; stream << block.component_mask_shift_; + stream << block.corners_; stream << block.currentCcAdjOrder_; stream << *block.bterm_tbl_; @@ -804,6 +814,7 @@ dbOStream& operator<<(dbOStream& stream, const _dbBlock& block) stream << *block.global_connect_tbl_; stream << *block.guide_tbl_; stream << *block.net_tracks_tbl_; + stream << *block.corner_tbl_; stream << *block.box_tbl_; stream << *block.via_tbl_; stream << *block.gcell_grid_tbl_; @@ -975,6 +986,9 @@ dbIStream& operator>>(dbIStream& stream, _dbBlock& block) if (db->isSchema(kSchemaBlockComponentMaskShift)) { stream >> block.component_mask_shift_; } + if (db->isSchema(kSchemaCorner)) { + stream >> block.corners_; + } stream >> block.currentCcAdjOrder_; stream >> *block.bterm_tbl_; stream >> *block.iterm_tbl_; @@ -1041,6 +1055,9 @@ dbIStream& operator>>(dbIStream& stream, _dbBlock& block) if (db->isSchema(kSchemaNetTracks)) { stream >> *block.net_tracks_tbl_; } + if (db->isSchema(kSchemaCorner)) { + stream >> *block.corner_tbl_; + } stream >> *block.box_tbl_; stream >> *block.via_tbl_; stream >> *block.gcell_grid_tbl_; @@ -1336,6 +1353,9 @@ bool _dbBlock::operator==(const _dbBlock& rhs) const if (children_ != rhs.children_) { return false; } + if (corners_ != rhs.corners_) { + return false; + } if (component_mask_shift_ != rhs.component_mask_shift_) { return false; @@ -1411,6 +1431,10 @@ bool _dbBlock::operator==(const _dbBlock& rhs) const return false; } + if (*corner_tbl_ != *rhs.corner_tbl_) { + return false; + } + if (*box_tbl_ != *rhs.box_tbl_) { return false; } @@ -2871,6 +2895,34 @@ void dbBlock::setCornerCount(int cornersStoredCnt, } } +dbCorner* dbBlock::findCorner(const std::string& corner_name) const +{ + _dbBlock* block = (_dbBlock*) this; + + for (const dbId<_dbCorner> corner_id : block->corners_) { + _dbCorner* corner = block->corner_tbl_->getPtr(corner_id); + + if (corner->name_ == corner_name) { + return (dbCorner*) corner; + } + } + + return nullptr; +} + +void dbBlock::removeCorners() +{ + _dbBlock* block = (_dbBlock*) this; + + for (const dbId<_dbCorner> corner_id : block->corners_) { + _dbCorner* corner = block->corner_tbl_->getPtr(corner_id); + dbProperty::destroyProperties((dbCorner*) corner); + } + + block->corners_.clear(); + block->corner_tbl_->clear(); +} + char* dbBlock::getCornerNameList() { _dbBlock* block = (_dbBlock*) this; @@ -3722,6 +3774,7 @@ void _dbBlock::collectMemInfo(MemInfo& info) info.children["bterm_hash"].add(bterm_hash_); info.children["children"].add(children_); + info.children["corners"].add(corners_); info.children["component_mask_shift"].add(component_mask_shift_); bterm_tbl_->collectMemInfo(info.children["bterm"]); @@ -3758,6 +3811,7 @@ void _dbBlock::collectMemInfo(MemInfo& info) global_connect_tbl_->collectMemInfo(info.children["global_connect"]); guide_tbl_->collectMemInfo(info.children["guide"]); net_tracks_tbl_->collectMemInfo(info.children["net_tracks"]); + corner_tbl_->collectMemInfo(info.children["corner"]); dft_tbl_->collectMemInfo(info.children["dft"]); modbterm_tbl_->collectMemInfo(info.children["modbterm"]); moditerm_tbl_->collectMemInfo(info.children["moditerm"]); diff --git a/src/odb/src/db/dbBlock.h b/src/odb/src/db/dbBlock.h index 8c0eba09e05..06976b027d5 100644 --- a/src/odb/src/db/dbBlock.h +++ b/src/odb/src/db/dbBlock.h @@ -76,6 +76,7 @@ class _dbAccessPoint; class _dbGlobalConnect; class _dbGuide; class _dbNetTrack; +class _dbCorner; class _dbMarkerCategory; class dbJournal; @@ -219,6 +220,7 @@ class _dbBlock : public _dbObject uint32_t max_cc_seg_id_; dbVector> children_; dbVector> component_mask_shift_; + dbVector> corners_; uint32_t currentCcAdjOrder_; dbId<_dbDft> dft_; int min_routing_layer_; @@ -267,6 +269,7 @@ class _dbBlock : public _dbObject dbTable<_dbGlobalConnect>* global_connect_tbl_; dbTable<_dbGuide>* guide_tbl_; dbTable<_dbNetTrack>* net_tracks_tbl_; + dbTable<_dbCorner>* corner_tbl_; _dbNameCache* name_cache_; dbTable<_dbDft, 4096>* dft_tbl_; diff --git a/src/odb/src/db/dbCorner.cpp b/src/odb/src/db/dbCorner.cpp new file mode 100644 index 00000000000..e02802e0b61 --- /dev/null +++ b/src/odb/src/db/dbCorner.cpp @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2019-2025, The OpenROAD Authors + +// Generator Code Begin Cpp +#include "dbCorner.h" + +#include + +#include "dbCore.h" +#include "dbDatabase.h" +#include "dbTable.h" +#include "odb/db.h" +// User Code Begin Includes +#include + +#include "dbBlock.h" +#include "utl/Logger.h" +// User Code End Includes +namespace odb { +template class dbTable<_dbCorner>; + +bool _dbCorner::operator==(const _dbCorner& rhs) const +{ + // NOLINTBEGIN(readability-simplify-boolean-expr) + if (name_ != rhs.name_) { + return false; + } + + return true; + // NOLINTEND(readability-simplify-boolean-expr) +} + +bool _dbCorner::operator<(const _dbCorner& rhs) const +{ + if (name_ >= rhs.name_) { + return false; + } + + return true; +} + +_dbCorner::_dbCorner(_dbDatabase* db) +{ +} + +dbIStream& operator>>(dbIStream& stream, _dbCorner& obj) +{ + stream >> obj.name_; + return stream; +} + +dbOStream& operator<<(dbOStream& stream, const _dbCorner& obj) +{ + stream << obj.name_; + return stream; +} + +void _dbCorner::collectMemInfo(MemInfo& info) +{ + info.cnt++; + info.size += sizeof(*this); + + info.children["name"].add(name_); +} + +//////////////////////////////////////////////////////////////////// +// +// dbCorner - Methods +// +//////////////////////////////////////////////////////////////////// + +const std::string& dbCorner::getName() const +{ + _dbCorner* obj = (_dbCorner*) this; + return obj->name_; +} + +// User Code Begin dbCornerPublicMethods + +dbCorner* dbCorner::create(dbBlock* block_, const std::string& corner_name) +{ + _dbBlock* block = (_dbBlock*) block_; + utl::Logger* logger = block->getImpl()->getLogger(); + + if (corner_name.empty()) { + logger->error( + utl::ODB, 9, "Could not add corner. Empty name is not allowed."); + } + + if (block_->findCorner(corner_name)) { + logger->error( + utl::ODB, + 5, + "Could not add corner {}. A corner with that name already exists.", + corner_name); + } + + _dbCorner* corner = block->corner_tbl_->create(); + corner->name_ = corner_name; + block->corners_.push_back(corner->getId()); + + return (dbCorner*) corner; +} + +void dbCorner::destroy(dbCorner* corner_) +{ + _dbCorner* corner = (_dbCorner*) corner_; + _dbBlock* block = (_dbBlock*) corner->getOwner(); + + // Note that std::ranges::find requires the search value + // to be the same type as the range's elements. + const dbId<_dbCorner> corner_id = corner->getId(); + auto corner_position = std::ranges::find(block->corners_, corner_id); + block->corners_.erase(corner_position); + + dbProperty::destroyProperties(corner_); + block->corner_tbl_->destroy(corner); +} + +// User Code End dbCornerPublicMethods +} // namespace odb +// Generator Code End Cpp \ No newline at end of file diff --git a/src/odb/src/db/dbCorner.h b/src/odb/src/db/dbCorner.h new file mode 100644 index 00000000000..263f16f1e25 --- /dev/null +++ b/src/odb/src/db/dbCorner.h @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2019-2025, The OpenROAD Authors + +// Generator Code Begin Header +#pragma once + +#include +#include + +#include "dbCore.h" + +namespace odb { +class dbIStream; +class dbOStream; +class _dbDatabase; + +class _dbCorner : public _dbObject +{ + public: + _dbCorner(_dbDatabase*); + + bool operator==(const _dbCorner& rhs) const; + bool operator!=(const _dbCorner& rhs) const { return !operator==(rhs); } + bool operator<(const _dbCorner& rhs) const; + void collectMemInfo(MemInfo& info); + + std::string name_; +}; +dbIStream& operator>>(dbIStream& stream, _dbCorner& obj); +dbOStream& operator<<(dbOStream& stream, const _dbCorner& obj); +} // namespace odb +// Generator Code End Header \ No newline at end of file diff --git a/src/odb/src/db/dbDatabase.h b/src/odb/src/db/dbDatabase.h index 6efb166d53f..f3c8d2e0b37 100644 --- a/src/odb/src/db/dbDatabase.h +++ b/src/odb/src/db/dbDatabase.h @@ -50,7 +50,10 @@ namespace odb { inline constexpr uint32_t kSchemaMajor = 0; // Not used... inline constexpr uint32_t kSchemaInitial = 57; -inline constexpr uint32_t kSchemaMinor = 134; // Current revision number +inline constexpr uint32_t kSchemaMinor = 135; // Current revision number + +// Revision where dbCorner object was added +inline constexpr uint32_t kSchemaCorner = 135; // Revision where the per-corner child-block feature for parasitics was removed inline constexpr uint32_t kSchemaRemovePerCornerBlock = 134; diff --git a/src/odb/src/db/dbObject.cpp b/src/odb/src/db/dbObject.cpp index 8cb2e1a866a..562d744b626 100644 --- a/src/odb/src/db/dbObject.cpp +++ b/src/odb/src/db/dbObject.cpp @@ -68,6 +68,7 @@ static const char* name_tbl[] = {"dbGDSLib", "dbChipPath", "dbChipRegion", "dbChipRegionInst", + "dbCorner", "dbDatabase", "dbDft", "dbGCellGrid", @@ -195,6 +196,7 @@ static const std::unordered_map hash_to_object_type {0x359DE612, dbChipPathObj}, {0x0676E6F1, dbChipRegionObj}, {0x457A83E5, dbChipRegionInstObj}, + {0xFE85C102, dbCornerObj}, {0x00000000, dbDatabaseObj}, {0x7C713BD7, dbDftObj}, {0x645E6090, dbGCellGridObj}, @@ -323,6 +325,7 @@ static const std::unordered_map object_type_to_hash {dbChipPathObj, 0x359DE612}, {dbChipRegionObj, 0x0676E6F1}, {dbChipRegionInstObj, 0x457A83E5}, + {dbCornerObj, 0xFE85C102}, {dbDatabaseObj, 0x00000000}, {dbDftObj, 0x7C713BD7}, {dbGCellGridObj, 0x645E6090}, diff --git a/src/odb/src/swig/tcl/odb.tcl b/src/odb/src/swig/tcl/odb.tcl index ddcb8432466..6ea93e1630d 100644 --- a/src/odb/src/swig/tcl/odb.tcl +++ b/src/odb/src/swig/tcl/odb.tcl @@ -1,6 +1,57 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2019-2025, The OpenROAD Authors +sta::define_cmd_args "add_corner" {corner_name} + +proc add_corner { args } { + sta::parse_key_args "add_corner" args keys {} flags {} + sta::check_argc_eq1 "add_corner" $args + + set corner_name $args + set block [odb::get_block] + if { $block == "NULL" } { + utl::error ODB 1 "Could not add corner $corner_name. No block found." + } + + odb::dbCorner_create $block $corner_name +} + +sta::define_cmd_args "remove_corner" {corner_name} + +proc remove_corner { args } { + sta::parse_key_args "remove_corner" args keys {} flags {} + sta::check_argc_eq1 "remove_corner" $args + + set corner_name $args + set block [odb::get_block] + if { $block == "NULL" } { + utl::error ODB 7 "Could not remove corner $corner_name. No block found." + } + + set corner [$block findCorner $corner_name] + + if { $corner == "NULL" } { + utl::error ODB 6 \ + "Could not remove corner $corner_name. No corner with that name exists." + } + + odb::dbCorner_destroy $corner +} + +sta::define_cmd_args "remove_corners" {} + +proc remove_corners { args } { + sta::parse_key_args "remove_corners" args keys {} flags {} + sta::check_argc_eq0 "remove_corners" $args + + set block [odb::get_block] + if { $block == "NULL" } { + utl::error ODB 8 "Could not remove corners. No block found." + } + + $block removeCorners +} + sta::define_cmd_args "create_physical_cluster" {cluster_name} proc create_physical_cluster { args } { diff --git a/src/odb/test/cpp/BUILD b/src/odb/test/cpp/BUILD index 10d9d782281..17973156e4e 100644 --- a/src/odb/test/cpp/BUILD +++ b/src/odb/test/cpp/BUILD @@ -67,6 +67,19 @@ cc_test( ], ) +cc_test( + name = "TestCorner", + srcs = [ + "TestCorner.cpp", + ], + deps = [ + "//src/odb/src/db", + "//src/odb/test/cpp/helper", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + cc_test( name = "TestDbWire", srcs = ["TestDbWire.cc"], diff --git a/src/odb/test/cpp/CMakeLists.txt b/src/odb/test/cpp/CMakeLists.txt index f314f4973ff..288eaee171c 100644 --- a/src/odb/test/cpp/CMakeLists.txt +++ b/src/odb/test/cpp/CMakeLists.txt @@ -20,7 +20,7 @@ set(TEST_LIBS GTest::gmock ) -add_executable(OdbGTests TestDbWire.cc TestDbNet.cpp TestAbstractLef.cc TestPolygonalFloorplan.cc) +add_executable(OdbGTests TestDbWire.cc TestDbNet.cpp TestAbstractLef.cc TestPolygonalFloorplan.cc TestCorner.cpp) add_executable(TestCallBacks TestCallBacks.cpp) add_executable(TestGeom TestGeom.cpp) add_executable(TestModule TestModule.cpp) diff --git a/src/odb/test/cpp/TestCorner.cpp b/src/odb/test/cpp/TestCorner.cpp new file mode 100644 index 00000000000..d05aba7d8ef --- /dev/null +++ b/src/odb/test/cpp/TestCorner.cpp @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2024-2025, The OpenROAD Authors + +#include +#include +#include +#include +#include + +#include "gtest/gtest.h" +#include "helper.h" +#include "odb/db.h" + +namespace odb { +namespace { + +TEST_F(SimpleDbFixture, test_add_find_remove) +{ + createSimpleDB(); + dbBlock* block = db_->getChip()->getBlock(); + + dbCorner::create(block, "corner1"); + dbCorner::create(block, "corner2"); + + EXPECT_EQ(block->findCorner("corner1")->getName(), "corner1"); + EXPECT_EQ(block->findCorner("corner2")->getName(), "corner2"); + EXPECT_EQ(block->findCorner("missing"), nullptr); + + // Removing one corner leaves the others in place. + dbCorner::destroy(block->findCorner("corner1")); + EXPECT_EQ(block->findCorner("corner1"), nullptr); + EXPECT_NE(block->findCorner("corner2"), nullptr); + + // removeCorners clears everything that remains. + block->removeCorners(); + EXPECT_EQ(block->findCorner("corner2"), nullptr); +} + +TEST_F(SimpleDbFixture, test_add_duplicate_errors) +{ + createSimpleDB(); + dbBlock* block = db_->getChip()->getBlock(); + + dbCorner::create(block, "corner1"); + // Adding a second corner with the same name is a fatal error. + EXPECT_THROW(dbCorner::create(block, "corner1"), std::runtime_error); +} + +TEST_F(SimpleDbFixture, test_add_empty_name_errors) +{ + createSimpleDB(); + dbBlock* block = db_->getChip()->getBlock(); + + // Empty name is not allowed. + EXPECT_THROW(dbCorner::create(block, ""), std::runtime_error); +} + +TEST_F(SimpleDbFixture, test_write_read_roundtrip) +{ + createSimpleDB(); + dbBlock* block = db_->getChip()->getBlock(); + dbCorner::create(block, "corner1"); + dbCorner::create(block, "corner2"); + + std::filesystem::create_directory("results"); + const std::string tmp_path = "results/TestCorner.odb"; + + std::ofstream write_stream; + write_stream.exceptions(std::ifstream::failbit | std::ifstream::badbit + | std::ios::eofbit); + write_stream.open(tmp_path, std::ios::binary); + db_->write(write_stream); + write_stream.close(); + + dbDatabase* read_db = dbDatabase::create(); + std::ifstream read_stream; + read_stream.exceptions(std::ifstream::failbit | std::ifstream::badbit + | std::ios::eofbit); + read_stream.open(tmp_path, std::ios::binary); + read_db->read(read_stream); + + dbBlock* read_block = read_db->getChip()->getBlock(); + EXPECT_EQ(read_block->findCorner("corner1")->getName(), "corner1"); + EXPECT_EQ(read_block->findCorner("corner2")->getName(), "corner2"); + + dbDatabase::destroy(read_db); +} + +} // namespace +} // namespace odb diff --git a/src/odb/test/odb_readme_msgs_check.ok b/src/odb/test/odb_readme_msgs_check.ok index 390cecb66d4..8a88038a28a 100644 --- a/src/odb/test/odb_readme_msgs_check.ok +++ b/src/odb/test/odb_readme_msgs_check.ok @@ -1,5 +1,5 @@ README.md -Names: 22, Desc: 22, Syn: 22, Options: 22, Args: 22 +Names: 25, Desc: 25, Syn: 25, Options: 25, Args: 25 Global Examples: None Global See Also: None Man2 successfully compiled.