diff --git a/include/ord/OpenRoad.hh b/include/ord/OpenRoad.hh index 546d4803432..35b6ad067a1 100644 --- a/include/ord/OpenRoad.hh +++ b/include/ord/OpenRoad.hh @@ -3,8 +3,6 @@ #pragma once -#include -#include #include #include diff --git a/src/OpenRoad.cc b/src/OpenRoad.cc index 0922c026b61..eef476375e0 100644 --- a/src/OpenRoad.cc +++ b/src/OpenRoad.cc @@ -79,7 +79,6 @@ #include "rsz/MakeResizer.hh" #include "rsz/Resizer.hh" #include "sta/VerilogReader.hh" -#include "sta/VerilogWriter.hh" #include "stt/MakeSteinerTreeBuilder.h" #include "tap/MakeTapcell.h" #include "tap/tapcell.h" diff --git a/src/odb/include/odb/3dblox.h b/src/odb/include/odb/3dblox.h index 61873bb8aa4..b6e07ff5626 100644 --- a/src/odb/include/odb/3dblox.h +++ b/src/odb/include/odb/3dblox.h @@ -46,6 +46,7 @@ class ThreeDBlox void writeDbv(const std::string& dbv_file, odb::dbChip* chip); void writeDbx(const std::string& dbx_file, odb::dbChip* chip); void writeBMap(const std::string& bmap_file, odb::dbChipRegion* region); + void writeVerilog(const std::string& verilog_file, odb::dbChip* chip); private: void createChiplet(const ChipletDef& chiplet); diff --git a/src/odb/src/3dblox/3dblox.cpp b/src/odb/src/3dblox/3dblox.cpp index 11c34ddb602..7fac6ab5ac1 100644 --- a/src/odb/src/3dblox/3dblox.cpp +++ b/src/odb/src/3dblox/3dblox.cpp @@ -37,6 +37,7 @@ #include "sta/VerilogReader.hh" #include "utl/Logger.h" #include "utl/ScopedTemporaryFile.h" +#include "verilogWriter.h" namespace odb { static std::map dup_orient_map @@ -301,10 +302,23 @@ void ThreeDBlox::writeDbx(const std::string& dbx_file, odb::dbChip* chip) writeDbv(current_dir_path + chip->getName() + ".3dbv", chip); + // Write the Verilog connectivity file for this HIER chiplet. + writeVerilog(current_dir_path + chip->getName() + ".v", chip); + DbxWriter writer(logger_, db_); writer.writeChiplet(dbx_file, chip); } +void ThreeDBlox::writeVerilog(const std::string& verilog_file, + odb::dbChip* chip) +{ + if (chip == nullptr) { + return; + } + VerilogWriter writer(logger_); + writer.writeChiplet(verilog_file, chip); +} + void ThreeDBlox::writeBMap(const std::string& bmap_file, odb::dbChipRegion* region) { diff --git a/src/odb/src/3dblox/CMakeLists.txt b/src/odb/src/3dblox/CMakeLists.txt index b6423b687de..dde022f4aa3 100644 --- a/src/odb/src/3dblox/CMakeLists.txt +++ b/src/odb/src/3dblox/CMakeLists.txt @@ -12,6 +12,7 @@ add_library(3dblox bmapWriter.cpp dbvWriter.cpp dbxWriter.cpp + verilogWriter.cpp 3dblox.cpp checker.cpp ) diff --git a/src/odb/src/3dblox/dbxWriter.cpp b/src/odb/src/3dblox/dbxWriter.cpp index c777e5a41cc..39684afd5f8 100644 --- a/src/odb/src/3dblox/dbxWriter.cpp +++ b/src/odb/src/3dblox/dbxWriter.cpp @@ -49,6 +49,8 @@ void DbxWriter::writeYamlContent(YAML::Node& root, odb::dbChip* chiplet) void DbxWriter::writeDesign(YAML::Node& design_node, odb::dbChip* chiplet) { design_node["name"] = chiplet->getName(); + YAML::Node external_node = design_node["external"]; + external_node["verilog_file"] = std::string(chiplet->getName()) + ".v"; } void DbxWriter::writeChipletInsts(YAML::Node& instances_node, diff --git a/src/odb/src/3dblox/verilogWriter.cpp b/src/odb/src/3dblox/verilogWriter.cpp new file mode 100644 index 00000000000..e2e183e1913 --- /dev/null +++ b/src/odb/src/3dblox/verilogWriter.cpp @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2019-2025, The OpenROAD Authors + +#include "verilogWriter.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "odb/db.h" +#include "utl/Logger.h" + +namespace odb { + +VerilogWriter::VerilogWriter(utl::Logger* logger) : logger_(logger) +{ +} + +void VerilogWriter::writeChiplet(const std::string& filename, odb::dbChip* chip) +{ + std::ofstream verilog_file(filename); + if (!verilog_file.is_open()) { + logger_->error( + utl::ODB, 563, "Unable to open Verilog file for writing: {}", filename); + return; + } + + // chip_inst -> list of (port_name, net_name) + std::map>> + inst_connections; + + for (dbChipNet* net : chip->getChipNets()) { + const std::string net_name = net->getName(); + const uint32_t num_bumps = net->getNumBumpInsts(); + for (uint32_t i = 0; i < num_bumps; i++) { + std::vector path; + dbChipBumpInst* bump_inst = net->getBumpInst(i, path); + if (bump_inst == nullptr || path.size() != 1) { + continue; + } + // Only handle direct children (path length 1) — "single bump + // connections". + dbChipInst* chip_inst = path[0]; + dbChipBump* bump = bump_inst->getChipBump(); + if (bump == nullptr) { + continue; + } + dbBTerm* bterm = bump->getBTerm(); + if (bterm == nullptr) { + continue; + } + inst_connections[chip_inst].emplace_back(bterm->getName(), net_name); + } + } + + // Sort each instance's port connections alphabetically by port name. + for (std::pair>>& entry : + inst_connections) { + std::ranges::sort(entry.second); + } + + // Write module header. + fmt::print(verilog_file, "module {} ();\n", chip->getName()); + + // Collect and sort net names alphabetically for deterministic wire order. + std::vector net_names; + for (dbChipNet* net : chip->getChipNets()) { + net_names.push_back(net->getName()); + } + std::ranges::sort(net_names); + + // Write wire declarations in sorted order. + for (const std::string& name : net_names) { + fmt::print(verilog_file, " wire {};\n", name); + } + + // Collect and sort instances alphabetically by instance name. + std::vector chip_insts; + for (dbChipInst* chip_inst : chip->getChipInsts()) { + chip_insts.push_back(chip_inst); + } + std::ranges::sort(chip_insts, [](dbChipInst* a, dbChipInst* b) { + return a->getName() < b->getName(); + }); + + // Write instance declarations in sorted order. + for (dbChipInst* chip_inst : chip_insts) { + fmt::print(verilog_file, + " {} {} (\n", + chip_inst->getMasterChip()->getName(), + chip_inst->getName()); + auto it = inst_connections.find(chip_inst); + if (it != inst_connections.end()) { + const std::vector>& conns + = it->second; + for (size_t j = 0; j < conns.size(); j++) { + const bool is_last = (j + 1 == conns.size()); + fmt::print(verilog_file, + " .{}({}){}\n", + conns[j].first, + conns[j].second, + is_last ? "" : ","); + } + } + fmt::print(verilog_file, " );\n"); + } + + fmt::print(verilog_file, "endmodule\n"); + verilog_file.close(); +} + +} // namespace odb diff --git a/src/odb/src/3dblox/verilogWriter.h b/src/odb/src/3dblox/verilogWriter.h new file mode 100644 index 00000000000..73fe78bb5b2 --- /dev/null +++ b/src/odb/src/3dblox/verilogWriter.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2019-2025, The OpenROAD Authors + +#pragma once + +#include + +namespace utl { +class Logger; +} +namespace odb { +class dbChip; + +class VerilogWriter +{ + public: + VerilogWriter(utl::Logger* logger); + void writeChiplet(const std::string& filename, odb::dbChip* chip); + + private: + utl::Logger* logger_ = nullptr; +}; + +} // namespace odb diff --git a/src/odb/test/cpp/BUILD b/src/odb/test/cpp/BUILD index 979ebaf1e3e..6d5fa2e2bdc 100644 --- a/src/odb/test/cpp/BUILD +++ b/src/odb/test/cpp/BUILD @@ -293,6 +293,25 @@ cc_test( ], ) +cc_test( + name = "Test3DBloxVerilogWriter", + srcs = [ + "Test3DBloxVerilogWriter.cpp", + ], + data = [ + "//src/odb/test:regression_resources", + ], + deps = [ + "//src/odb", + "//src/odb/test/cpp/helper", + "//src/tst", + "//src/tst:nangate45_fixture", + "//src/utl", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + cc_test( name = "TestWriteReadDbHier", srcs = [ diff --git a/src/odb/test/cpp/CMakeLists.txt b/src/odb/test/cpp/CMakeLists.txt index 2b16ec39287..f4f9c231aac 100644 --- a/src/odb/test/cpp/CMakeLists.txt +++ b/src/odb/test/cpp/CMakeLists.txt @@ -37,6 +37,7 @@ add_executable(TestGDSIn TestGDSIn.cpp) add_executable(TestChips TestChips.cpp) add_executable(Test3DBloxParser Test3DBloxParser.cpp) add_executable(Test3DBloxChecker Test3DBloxChecker.cpp Test3DBloxCheckerLogicalConn.cpp Test3DBloxCheckerBumps.cpp) +add_executable(Test3DBloxVerilogWriter Test3DBloxVerilogWriter.cpp) add_executable(TestSwapMaster TestSwapMaster.cpp) add_executable(TestSwapMasterUnusedPort TestSwapMasterUnusedPort.cpp) add_executable(TestWriteReadDbHier TestWriteReadDbHier.cpp) @@ -58,6 +59,7 @@ target_link_libraries(TestGDSIn ${TEST_LIBS}) target_link_libraries(TestChips ${TEST_LIBS}) target_link_libraries(Test3DBloxParser ${TEST_LIBS}) target_link_libraries(Test3DBloxChecker ${TEST_LIBS}) +target_link_libraries(Test3DBloxVerilogWriter ${TEST_LIBS}) target_link_libraries(TestSwapMaster ${TEST_LIBS}) target_link_libraries(TestSwapMasterUnusedPort ${TEST_LIBS}) target_link_libraries(TestWriteReadDbHier ${TEST_LIBS}) @@ -87,6 +89,7 @@ add_dependencies(build_and_test TestChips Test3DBloxParser Test3DBloxChecker + Test3DBloxVerilogWriter TestSwapMaster TestSwapMasterUnusedPort TestWriteReadDbHier diff --git a/src/odb/test/cpp/Test3DBloxVerilogWriter.cpp b/src/odb/test/cpp/Test3DBloxVerilogWriter.cpp new file mode 100644 index 00000000000..63190891478 --- /dev/null +++ b/src/odb/test/cpp/Test3DBloxVerilogWriter.cpp @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2023-2026, The OpenROAD Authors +// +// Tests for VerilogWriter path-length filtering: +// - bumps with path.size() == 1 (direct child) must appear in output +// - bumps with path.size() > 1 (nested child) must be silently skipped + +#include +#include +#include + +#include "gtest/gtest.h" +#include "odb/3dblox.h" +#include "odb/db.h" +#include "odb/geom.h" +#include "tst/fixture.h" + +namespace odb { +namespace { + +// Helper to read an entire file into a string. +static std::string readFileContent(const std::string& path) +{ + std::ifstream file(path); + return std::string(std::istreambuf_iterator(file), + std::istreambuf_iterator()); +} + +class VerilogWriterFixture : public tst::Fixture +{ + protected: + VerilogWriterFixture() + { + tech_ = dbTech::create(db_.get(), "tech"); + dbTechLayer::create(tech_, "layer1", dbTechLayerType::ROUTING); + + // HIER top chip (the module being written). + top_chip_ + = dbChip::create(db_.get(), nullptr, "TopChip", dbChip::ChipType::HIER); + + // DIE chip used as the master for instances. + chip1_ = dbChip::create(db_.get(), tech_, "Chip1", dbChip::ChipType::DIE); + chip1_->setWidth(2000); + chip1_->setHeight(2000); + chip1_->setThickness(500); + dbBlock::create(chip1_, "block1"); + + // A second DIE chip, only needed to form multi-element path vectors. + chip2_ = dbChip::create(db_.get(), tech_, "Chip2", dbChip::ChipType::DIE); + chip2_->setWidth(1500); + chip2_->setHeight(1500); + chip2_->setThickness(500); + dbBlock::create(chip2_, "block2"); + + // Front region on chip1 where bumps will be placed. + dbChipRegion* r1_fr = dbChipRegion::create( + chip1_, "r1_fr", dbChipRegion::Side::FRONT, nullptr); + r1_fr->setBox(Rect(0, 0, 2000, 2000)); + + // Shared bump master for physical bump cells. + 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_, "pad", dbIoType::INOUT, dbSigType::SIGNAL); + bump_master_->setFrozen(); + } + + // Create a bump on a chip region and attach a named BTerm (Verilog port). + // Must be called BEFORE creating any dbChipInst that uses this chip, + // so the bump inst is propagated into the region inst at create time. + dbChipBump* createBump(dbChip* chip, + dbChipRegion* region, + const char* bump_name, + int x, + int y) + { + dbBlock* block = chip->getBlock(); + dbTechLayer* layer = tech_->findLayer("layer1"); + + // Physical placement cell for the bump. + dbInst* inst = dbInst::create(block, bump_master_, bump_name); + inst->setOrigin(x, y); + inst->setPlacementStatus(dbPlacementStatus::PLACED); + + // Chip-level bump object. + dbChipBump* chip_bump = dbChipBump::create(region, inst); + + // Net + BTerm — the BTerm name becomes the Verilog port name. + const std::string net_name = std::string(bump_name) + "_net"; + dbNet* net = dbNet::create(block, net_name.c_str()); + dbBTerm* bterm = dbBTerm::create(net, bump_name); + bterm->setIoType(dbIoType::INOUT); + + // BPin geometry required for placement checks. + 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; + } + + // Write the top chip's Verilog to a fixed temp file and return the path. + std::string writeVerilog() + { + const std::string filename = "/tmp/odb_test_verilog_writer.v"; + ThreeDBlox writer(&logger_, db_.get()); + writer.writeVerilog(filename, top_chip_); + return filename; + } + + dbTech* tech_; + dbChip* top_chip_; + dbChip* chip1_; + dbChip* chip2_; + dbLib* lib_; + dbMaster* bump_master_; +}; + +// A bump registered with path.size() == 1 (direct child of the HIER chip) +// must produce a port connection in the Verilog output. +TEST_F(VerilogWriterFixture, test_direct_child_written) +{ + // Create bump BEFORE the chip inst so it is propagated into the region inst. + dbChipRegion* r1_fr = chip1_->findChipRegion("r1_fr"); + createBump(chip1_, r1_fr, "port_a", 100, 100); + + dbChipInst* inst1 = dbChipInst::create(top_chip_, chip1_, "inst1"); + inst1->setLoc(Point3D(0, 0, 0)); + inst1->setOrient(dbOrientType3D(dbOrientType::R0, false)); + + // Retrieve the bump inst auto-created on the region inst. + dbChipRegionInst* ri1 = inst1->findChipRegionInst("r1_fr"); + ASSERT_NE(ri1, nullptr); + dbChipBumpInst* bump_inst = *ri1->getChipBumpInsts().begin(); + ASSERT_NE(bump_inst, nullptr); + + // Wire the bump to a net via a direct (path length 1) path. + dbChipNet* chip_net = dbChipNet::create(top_chip_, "net_signal"); + chip_net->addBumpInst(bump_inst, {inst1}); + + const std::string filename = writeVerilog(); + const std::string content = readFileContent(filename); + + // The port connection must appear in the output. + EXPECT_NE(content.find(".port_a(net_signal)"), std::string::npos); +} + +// A bump registered with path.size() > 1 (nested child) must be silently +// skipped; no port connection should appear in the Verilog output. +TEST_F(VerilogWriterFixture, test_nested_child_skipped) +{ + // Create bump BEFORE the chip inst so it is propagated into the region inst. + dbChipRegion* r1_fr = chip1_->findChipRegion("r1_fr"); + createBump(chip1_, r1_fr, "port_b", 200, 200); + + dbChipInst* inst1 = dbChipInst::create(top_chip_, chip1_, "inst1"); + inst1->setLoc(Point3D(0, 0, 0)); + inst1->setOrient(dbOrientType3D(dbOrientType::R0, false)); + + // inst2 is used only to form a path vector of length 2. + dbChipInst* inst2 = dbChipInst::create(top_chip_, chip2_, "inst2"); + inst2->setLoc(Point3D(0, 0, 500)); + inst2->setOrient(dbOrientType3D(dbOrientType::R0, false)); + + dbChipRegionInst* ri1 = inst1->findChipRegionInst("r1_fr"); + ASSERT_NE(ri1, nullptr); + dbChipBumpInst* bump_inst = *ri1->getChipBumpInsts().begin(); + ASSERT_NE(bump_inst, nullptr); + + // Path of length 2 — nested child; writer must skip this bump. + dbChipNet* chip_net = dbChipNet::create(top_chip_, "net_deep"); + chip_net->addBumpInst(bump_inst, {inst1, inst2}); + + const std::string filename = writeVerilog(); + const std::string content = readFileContent(filename); + + // No port connection should appear for the skipped bump. + EXPECT_EQ(content.find(".port_b"), std::string::npos); +} + +} // namespace +} // namespace odb diff --git a/src/odb/test/write_3dbx.3dbxok b/src/odb/test/write_3dbx.3dbxok index 89a2e28b8d7..f3725612e82 100644 --- a/src/odb/test/write_3dbx.3dbxok +++ b/src/odb/test/write_3dbx.3dbxok @@ -6,6 +6,8 @@ Header: - TopDesign.3dbv Design: name: TopDesign + external: + verilog_file: TopDesign.v ChipletInst: soc_inst_duplicate: reference: SoC