From 8816249c2bacf2e8596b1b768a1bb26ae57b466c Mon Sep 17 00:00:00 2001 From: Arthur Koucher Date: Thu, 16 Jul 2026 12:18:56 -0300 Subject: [PATCH 1/5] rcx: add support for 3D-IC parasitics extraction Signed-off-by: Arthur Koucher --- src/rcx/BUILD | 2 + src/rcx/include/rcx/extRCap.h | 15 +- src/rcx/include/rcx/interChipModel.h | 20 +++ src/rcx/include/rcx/multiChipExtractor.h | 15 ++ src/rcx/include/rcx/multiChipSpefWriter.h | 15 +- src/rcx/src/CMakeLists.txt | 1 + src/rcx/src/ext.cpp | 3 +- src/rcx/src/ext.i | 10 +- src/rcx/src/interChipModel.cpp | 80 +++++++++ src/rcx/src/multiChipExtractor.cpp | 148 +++++++++++++++- src/rcx/src/multiChipSpefWriter.cpp | 198 +++++++++++++++++++++- src/rcx/src/netRC.cpp | 8 +- 12 files changed, 503 insertions(+), 12 deletions(-) create mode 100644 src/rcx/include/rcx/interChipModel.h create mode 100644 src/rcx/src/interChipModel.cpp diff --git a/src/rcx/BUILD b/src/rcx/BUILD index 5161698c46f..36768655d50 100644 --- a/src/rcx/BUILD +++ b/src/rcx/BUILD @@ -15,6 +15,7 @@ package( cc_library( name = "rcx", srcs = [ + "src/interChipModel.cpp", "src/dbUtil.cpp", "src/ext.cpp", "src/extBench.cpp", @@ -69,6 +70,7 @@ cc_library( ], hdrs = [ "include/rcx/array1.h", + "include/rcx/interChipModel.h", "include/rcx/box.h", "include/rcx/dbUtil.h", "include/rcx/ext.h", diff --git a/src/rcx/include/rcx/extRCap.h b/src/rcx/include/rcx/extRCap.h index dad9cb26820..838fc44cddb 100644 --- a/src/rcx/include/rcx/extRCap.h +++ b/src/rcx/include/rcx/extRCap.h @@ -1867,7 +1867,7 @@ class extMain int _metal_flag_22; // dkf: 06242024 uint32_t _wire_extracted_progress_count; // dkf: 06242024 - bool _v2; // new flow dkf: 10302023 + bool _v2{false}; // new flow dkf: 10302023 void skip_via_wires(bool v) { _skip_via_wires = v; }; void printUpdateCoup(uint32_t netId1, @@ -2129,8 +2129,6 @@ class extMain void setCornerCount(); - Array1D* getProcessCornerTable() { return _processCornerTable; } - uint32_t getShortSrcJid(uint32_t jid); void make1stRSeg(odb::dbNet* net, odb::dbWirePath& path, @@ -2667,6 +2665,16 @@ class extMain utl::Logger* getLogger() { return logger_; } + const Array1D* getProcessCornerTable() const + { + return _processCornerTable; + } + + void setDeleteModelAtExtraction(bool delete_model_at_extraction) + { + delete_model_at_extraction_ = delete_model_at_extraction; + } + private: utl::Logger* logger_; @@ -2677,6 +2685,7 @@ class extMain Array1D* _scaledCornerTable = nullptr; Array1D* _modelTable; + bool delete_model_at_extraction_{true}; Array1D _modelMap; // TO_TEST Array1D _metRCTable; double _resistanceTable[20][20]; diff --git a/src/rcx/include/rcx/interChipModel.h b/src/rcx/include/rcx/interChipModel.h new file mode 100644 index 00000000000..d4a798e9b5e --- /dev/null +++ b/src/rcx/include/rcx/interChipModel.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2019-2025, The OpenROAD Authors + +#pragma once + +#include + +#include "utl/Logger.h" + +namespace rcx { + +struct InterChipModel +{ + double resistance{0.0}; // In Ohms. +}; + +InterChipModel parseInterChipRules(const std::string& rules_file_path, + utl::Logger* logger); + +} // namespace rcx diff --git a/src/rcx/include/rcx/multiChipExtractor.h b/src/rcx/include/rcx/multiChipExtractor.h index 76e0d7e1f2e..e35cc0f8a5c 100644 --- a/src/rcx/include/rcx/multiChipExtractor.h +++ b/src/rcx/include/rcx/multiChipExtractor.h @@ -3,11 +3,14 @@ #pragma once +#include #include #include "odb/PtrSetMap.h" #include "odb/db.h" +#include "rcx/extRCap.h" #include "rcx/ext_options.h" +#include "rcx/interChipModel.h" #include "utl/Logger.h" namespace rcx { @@ -25,11 +28,23 @@ class MultiChipExtractor const std::string& assembly_extraction_rules_file); private: + void loadRules(); + void extractChipParasitics(odb::dbChip* chip, const ExtractOptions& options); + void extractInterChipParasitics(); + odb::dbDatabase* db_{nullptr}; utl::Logger* logger_{nullptr}; + // Rules' files paths. odb::PtrMap extraction_rules_files_; std::string assembly_extraction_rules_file_; + + // Rules' models i.e., the structures populated with rules file data. + odb::PtrMap> tech_to_rules_model_; + InterChipModel inter_chip_model_; + + const int corner_index_{0}; + const std::string corner_name_{"Typical"}; }; } // namespace rcx diff --git a/src/rcx/include/rcx/multiChipSpefWriter.h b/src/rcx/include/rcx/multiChipSpefWriter.h index 82152d4cdfb..eefefb7145c 100644 --- a/src/rcx/include/rcx/multiChipSpefWriter.h +++ b/src/rcx/include/rcx/multiChipSpefWriter.h @@ -3,7 +3,10 @@ #pragma once +#include + #include "odb/db.h" +#include "rcx/extSpef.h" #include "rcx/ext_options.h" #include "utl/Logger.h" @@ -12,13 +15,23 @@ namespace rcx { class MultiChipSpefWriter { public: - MultiChipSpefWriter(odb::dbDatabase* db, utl::Logger* logger); + MultiChipSpefWriter(odb::dbDatabase* db, + utl::Logger* logger, + const std::string& spef_version); void run(const SpefOptions& options); private: + void writeChipSpef(odb::dbChip* chip, const SpefOptions& options); + void writeInterChipSpef(); + std::string chipNetSpefString(odb::dbChipNet* chip_net); + std::string bondNodeName(odb::dbChipCapNode* cap_node); + odb::dbDatabase* db_{nullptr}; utl::Logger* logger_{nullptr}; + + std::string file_base_name_; + SpefHeader spef_header_; }; } // namespace rcx diff --git a/src/rcx/src/CMakeLists.txt b/src/rcx/src/CMakeLists.txt index ec9d172b632..59b91bade45 100644 --- a/src/rcx/src/CMakeLists.txt +++ b/src/rcx/src/CMakeLists.txt @@ -53,6 +53,7 @@ add_library(rcx_lib find_some_net.cpp multiChipExtractor.cpp multiChipSpefWriter.cpp + interChipModel.cpp ) target_include_directories(rcx_lib diff --git a/src/rcx/src/ext.cpp b/src/rcx/src/ext.cpp index f588ed1fb0d..e10caaa8067 100644 --- a/src/rcx/src/ext.cpp +++ b/src/rcx/src/ext.cpp @@ -33,7 +33,8 @@ Ext::Ext(odb::dbDatabase* db, Logger* logger, const char* spef_version) logger_(logger), spef_version_(spef_version), multi_chip_extractor_(std::make_unique(db, logger)), - multi_chip_spef_writer_(std::make_unique(db, logger)) + multi_chip_spef_writer_( + std::make_unique(db, logger, spef_version)) { _ext->init(db, logger); } diff --git a/src/rcx/src/ext.i b/src/rcx/src/ext.i index f2ec05b7858..d05b400cfea 100644 --- a/src/rcx/src/ext.i +++ b/src/rcx/src/ext.i @@ -121,11 +121,19 @@ extract(const char* ext_model_file, opts._dbg= dbg; odb::dbChip* top_chip = ord::getOpenRoad()->getDb()->getChip(); + if (!top_chip) { getLogger()->error(utl::RCX, 517, "No design is loaded."); } if (top_chip->getChipType() == odb::dbChip::ChipType::HIER) { + if (opts._v2) { + getLogger()->error(utl::RCX, + 515, + "Multi-chip (3D) parasitic extraction supports only the v1 " + "RC flow; the v2 flow (-version >= 2.0) is not supported."); + } + ext->extractMultiChip(opts); return; } @@ -148,7 +156,7 @@ write_spef(const char* file, opts.nets = nets; opts.net_id = net_id; opts.coordinates= coordinates; - if (coordinates) { + if (coordinates) { opts.N = "Y"; } diff --git a/src/rcx/src/interChipModel.cpp b/src/rcx/src/interChipModel.cpp new file mode 100644 index 00000000000..c4db9e01426 --- /dev/null +++ b/src/rcx/src/interChipModel.cpp @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2019-2025, The OpenROAD Authors + +#include "rcx/interChipModel.h" + +#include +#include +#include +#include + +#include "utl/Logger.h" + +namespace { + +constexpr std::string_view kHybridBondVia = "HBV"; +constexpr std::string_view kViaResistanceTable = "VIA_RESISTANCE"; +constexpr std::string_view kTableEnd = "END"; + +} // namespace + +namespace rcx { + +InterChipModel parseInterChipRules(const std::string& rules_file_path, + utl::Logger* logger) +{ + std::ifstream file(rules_file_path); + + if (!file.is_open()) { + logger->error(utl::RCX, + 516, + "Could not open assembly rules file {}.", + rules_file_path); + } + + InterChipModel inter_chip_model; + bool found_hybrid_bond_via = false; + bool inside_via_resistance_table = false; + + std::string line; + while (std::getline(file, line)) { + line = line.substr(0, line.find('#')); + + std::istringstream line_stream(line); + std::string token; + if (!(line_stream >> token)) { + continue; + } + + if (token == kViaResistanceTable) { + inside_via_resistance_table = true; + continue; + } + + if (!inside_via_resistance_table) { + continue; + } + + if (token == kTableEnd) { + break; + } + + if (token == kHybridBondVia) { + line_stream >> inter_chip_model.resistance; + found_hybrid_bond_via = true; + break; + } + } + + if (!found_hybrid_bond_via) { + logger->error( + utl::RCX, + 538, + "Could not find HBV via resistance in assembly rules file {}.", + rules_file_path); + } + + return inter_chip_model; +} + +} // namespace rcx diff --git a/src/rcx/src/multiChipExtractor.cpp b/src/rcx/src/multiChipExtractor.cpp index f73912ef529..36844778d33 100644 --- a/src/rcx/src/multiChipExtractor.cpp +++ b/src/rcx/src/multiChipExtractor.cpp @@ -3,6 +3,14 @@ #include "rcx/multiChipExtractor.h" +#include +#include +#include +#include + +#include "odb/db.h" +#include "odb/wOrder.h" + namespace rcx { MultiChipExtractor::MultiChipExtractor(odb::dbDatabase* db, utl::Logger* logger) @@ -10,9 +18,18 @@ MultiChipExtractor::MultiChipExtractor(odb::dbDatabase* db, utl::Logger* logger) { } -void MultiChipExtractor::run(const ExtractOptions& /* options */) +void MultiChipExtractor::run(const ExtractOptions& options) { - logger_->error(utl::RCX, 515, "3D extraction is not yet supported."); + loadRules(); + + for (odb::dbChip* chip : db_->getChips()) { + if (chip->getChipType() == odb::dbChip::ChipType::DIE) { + odb::orderWires(logger_, chip->getBlock()); + extractChipParasitics(chip, options); + } + } + + extractInterChipParasitics(); } void MultiChipExtractor::setExtractionRulesFile( @@ -28,4 +45,131 @@ void MultiChipExtractor::setAssemblyExtractionRulesFile( assembly_extraction_rules_file_ = assembly_extraction_rules_file; } +void MultiChipExtractor::loadRules() +{ + if (assembly_extraction_rules_file_.empty()) { + logger_->error(utl::RCX, + 529, + "No assembly rules file defined; set one with " + "set_extraction_rules_file -assembly."); + } + + // The rules parser requires an instantiated process corner table, + // so we use an auxiliary extractor to generate it. + auto auxiliary_extractor = std::make_unique(); + auxiliary_extractor->init(db_, logger_); + + for (odb::dbChip* chip : db_->getChips()) { + const odb::dbChip::ChipType type = chip->getChipType(); + + if (type == odb::dbChip::ChipType::RDL) { + logger_->error( + utl::RCX, + 530, + "RDL chips are not supported in this version of 3D extraction."); + } + + if (type == odb::dbChip::ChipType::DIE) { + auxiliary_extractor->setBlockFromChip(chip); + auxiliary_extractor->addRCCorner(corner_name_.c_str(), corner_index_); + + odb::dbTech* tech = chip->getTech(); + + if (tech_to_rules_model_.contains(tech)) { + continue; + } + + const std::string& rules_file = extraction_rules_files_.at(tech); + const Array1D* corner_table + = auxiliary_extractor->getProcessCornerTable(); + + std::unique_ptr rules_model + = parseRules(tech, rules_file, corner_table, false, logger_); + + tech_to_rules_model_[tech] = std::move(rules_model); + } + } + + inter_chip_model_ + = parseInterChipRules(assembly_extraction_rules_file_, logger_); + + // Avoid leaving the block pointing at the extractor we destroy. + if (auxiliary_extractor) { + auxiliary_extractor->getBlock()->setExtmi(nullptr); + } +} + +void MultiChipExtractor::extractChipParasitics(odb::dbChip* chip, + const ExtractOptions& options) +{ + logger_->info(utl::RCX, + 531, + "Running parasitics extraction for chip {}.", + chip->getName()); + + auto block_extractor = std::make_unique(); + + block_extractor->init(db_, logger_); + block_extractor->setExtractionOptions(options); + + // The block must be set before adding the corner to it. + block_extractor->setBlockFromChip(chip); + block_extractor->addRCCorner(corner_name_.c_str(), corner_index_); + + // In a single-chip extraction, the extractor owns the model, so here + // we ensure that the ownership is kept with the multi chip extractor + // rather than the block extractor. + block_extractor->setDeleteModelAtExtraction(false); + + const auto& rules_model = tech_to_rules_model_.at(chip->getTech()); + block_extractor->registerRulesModel(rules_model.get()); + + block_extractor->run(); + + // Avoid leaving the block pointing at the extractor we destroy. + chip->getBlock()->setExtmi(nullptr); +} + +void MultiChipExtractor::extractInterChipParasitics() +{ + odb::dbChip* top_chip = db_->getChip(); + + int bond_count = 0; + for (odb::dbChipNet* chip_net : top_chip->getChipNets()) { + const uint32_t bump_count = chip_net->getNumBumpInsts(); + + // Skip chip nets whose bump does not connect to a bump in the other chip. + if (bump_count < 2) { + continue; + } + + if (bump_count > 2) { + logger_->error(utl::RCX, + 532, + "Inter-chip net {} bonds {} bumps; nets bonding more " + "than two bumps are not supported.", + chip_net->getName(), + bump_count); + } + + // Model the bond as a resistor between a cap node at each bump landing. + // Each cap node references its bump so the network stitches to that die's + // net. For this initial version, we consider negligible capacitive + // contribution from the bumps. + std::vector path; + odb::dbChipCapNode* source = odb::dbChipCapNode::create(chip_net); + source->setChipBumpInst(chip_net->getBumpInst(0, path)); + + odb::dbChipCapNode* target = odb::dbChipCapNode::create(chip_net); + target->setChipBumpInst(chip_net->getBumpInst(1, path)); + + odb::dbChipRSeg* r_seg = odb::dbChipRSeg::create(chip_net, source, target); + r_seg->setResistance(inter_chip_model_.resistance); + + bond_count++; + } + + logger_->info(utl::RCX, 533, "Extracted {} inter-chip bonds.", bond_count); +} + } // namespace rcx diff --git a/src/rcx/src/multiChipSpefWriter.cpp b/src/rcx/src/multiChipSpefWriter.cpp index 14f9bf6143b..a636fd17129 100644 --- a/src/rcx/src/multiChipSpefWriter.cpp +++ b/src/rcx/src/multiChipSpefWriter.cpp @@ -3,17 +3,209 @@ #include "rcx/multiChipSpefWriter.h" +#include +#include +#include +#include +#include +#include + +#include "odb/db.h" +#include "rcx/extRCap.h" +#include "utl/Logger.h" + namespace rcx { +namespace { + +std::string stripSuffix(const std::string& target, const std::string& suffix) +{ + if (target.ends_with(suffix)) { + return target.substr(0, target.size() - suffix.size()); + } + + return target; +} + +char pinDirection(odb::dbBTerm* bterm) +{ + char direction = 'B'; + + switch (bterm->getIoType().getValue()) { + case odb::dbIoType::INPUT: + direction = 'I'; + break; + case odb::dbIoType::OUTPUT: + direction = 'O'; + break; + case odb::dbIoType::INOUT: + case odb::dbIoType::FEEDTHRU: + break; + } + + return direction; +} + +} // namespace + MultiChipSpefWriter::MultiChipSpefWriter(odb::dbDatabase* db, - utl::Logger* logger) + utl::Logger* logger, + const std::string& spef_version) : db_{db}, logger_{logger} { + spef_header_.version = spef_version; +} + +void MultiChipSpefWriter::run(const SpefOptions& options) +{ + file_base_name_ = stripSuffix(options.file, ".spef"); + + for (odb::dbChip* chip : db_->getChips()) { + if (chip->getChipType() == odb::dbChip::ChipType::DIE) { + writeChipSpef(chip, options); + } + } + + writeInterChipSpef(); +} + +void MultiChipSpefWriter::writeChipSpef(odb::dbChip* chip, + const SpefOptions& options) +{ + const std::string file_path + = file_base_name_ + "." + chip->getName() + ".spef"; + + auto block_spef_writer = std::make_unique(); + block_spef_writer->init(db_, logger_); + block_spef_writer->setBlockFromChip(chip); + + block_spef_writer->writeSPEF((char*) file_path.c_str(), + (char*) options.nets, + options.no_name_map, + (char*) options.N, + options.term_junction_xy, + options.cap_units, + options.res_units, + options.gz, + options.stop_after_map, + options.w_clock, + options.w_conn, + options.w_cap, + options.w_cc_cap, + options.w_res, + options.no_c_num, + false, + options.single_pi, + options.no_backslash, + options.corner, + options.ext_corner_name, + spef_header_.version.c_str(), + options.parallel); + + logger_->info(utl::RCX, + 534, + "Wrote parasitics for chip {} to {}.", + chip->getName(), + file_path); +} + +std::string MultiChipSpefWriter::bondNodeName(odb::dbChipCapNode* cap_node) +{ + odb::dbChipNet* chip_net = cap_node->getChipNet(); + odb::dbChipBumpInst* bump_inst = cap_node->getChipBumpInst(); + + std::vector chip_inst_path; + for (uint32_t i = 0; i < chip_net->getNumBumpInsts(); ++i) { + std::vector candidate_path; + if (chip_net->getBumpInst(i, candidate_path) == bump_inst) { + chip_inst_path = candidate_path; + break; + } + } + + odb::dbBTerm* bterm = cap_node->getBTerm(); + + if (!bterm) { + logger_->error(utl::RCX, + 535, + "Inter-chip net {} lands on a bump with no boundary " + "terminal; cannot write its SPEF node.", + chip_net->getName()); + } + + std::string bond_node_name; + for (odb::dbChipInst* chip_inst : chip_inst_path) { + if (!bond_node_name.empty()) { + bond_node_name += '/'; + } + bond_node_name += chip_inst->getName(); + } + bond_node_name += ':'; + bond_node_name += bterm->getName(); + + return bond_node_name; +} + +void MultiChipSpefWriter::writeInterChipSpef() +{ + const std::string file_path = file_base_name_ + ".bonds.spef"; + odb::dbChip* top_chip = db_->getChip(); + std::ofstream out(file_path); + + if (!out) { + logger_->error(utl::RCX, + 536, + "Can't open file {} to write inter-chip spef.", + file_path); + } + + spef_header_.design_name = top_chip->getName(); + out << spef_header_.string(logger_); + + int bond_count = 0; + + for (odb::dbChipNet* chip_net : top_chip->getChipNets()) { + const std::string chip_net_spef = chipNetSpefString(chip_net); + + if (!chip_net_spef.empty()) { + out << chip_net_spef; + bond_count++; + } + } + + logger_->info( + utl::RCX, 537, "Wrote {} inter-chip bonds to {}.", bond_count, file_path); } -void MultiChipSpefWriter::run(const SpefOptions& /* options */) +std::string MultiChipSpefWriter::chipNetSpefString(odb::dbChipNet* chip_net) { - logger_->error(utl::RCX, 516, "3D SPEF writing is not yet supported."); + odb::dbSet rsegs = chip_net->getChipRSegs(); + + if (rsegs.empty()) { + return ""; + } + + std::ostringstream out; + + out << "\n*D_NET " << chip_net->getName() << " 0\n"; + + out << "*CONN\n"; + for (odb::dbChipCapNode* cap_node : chip_net->getChipCapNodes()) { + out << "*I " << bondNodeName(cap_node) << " " + << pinDirection(cap_node->getBTerm()) << "\n"; + } + + out << "*RES\n"; + int res_id = 1; + for (odb::dbChipRSeg* rseg : rsegs) { + out << res_id++ << " " << bondNodeName(rseg->getSourceCapNode()) << " " + << bondNodeName(rseg->getTargetCapNode()) << " " + << rseg->getResistance() << "\n"; + } + + out << "*END\n"; + + return out.str(); } } // namespace rcx diff --git a/src/rcx/src/netRC.cpp b/src/rcx/src/netRC.cpp index 1dfea056847..f840bfbd02f 100644 --- a/src/rcx/src/netRC.cpp +++ b/src/rcx/src/netRC.cpp @@ -1855,7 +1855,13 @@ void extMain::run() */ while (_modelTable->notEmpty()) { - delete _modelTable->pop(); + // For multi chip extraction, we don't delete the model as it is + // owned by the multi chip extractor. However, we still need to + // clear the table for a possible subsequent block extraction. + extRCModel* rules_model = _modelTable->pop(); + if (delete_model_at_extraction_) { + delete rules_model; + } } if (_batchScaleExt) { genScaledExt(); From 941ff65bfa953e3f57b403f099b91626d895e990 Mon Sep 17 00:00:00 2001 From: Arthur Koucher Date: Tue, 11 Aug 2026 17:31:40 -0300 Subject: [PATCH 2/5] rcx: document new behavior for 3D-IC Signed-off-by: Arthur Koucher --- src/rcx/README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/rcx/README.md b/src/rcx/README.md index 17cca3ee12b..ac633fa15c8 100644 --- a/src/rcx/README.md +++ b/src/rcx/README.md @@ -70,6 +70,13 @@ The `extract_parasitics` command performs parasitic extraction based on the routed design. If there are no information on routed design, no parasitics are returned. +For a 3D design, the command extracts each die using the extraction rules +set for its technology and then extracts the inter-chip parasitics using the +assembly design kit rules (see `set_extraction_rules_file`). Each bond +between two dies is modeled as a resistor connecting the boundary terminals +of the bonded bumps. 3D extraction supports only the v1 RC flow and a single +process corner. + ```tcl extract_parasitics [-ext_model_file filename] @@ -112,6 +119,10 @@ extract_parasitics The `write_spef` command writes the `.spef` output of the parasitics stored in the database. +For a 3D design, the command writes one file per die, appending the chip +name to the given filename (e.g., `design.chip_name.spef`), and one +additional file with the inter-chip parasitics (`design.bonds.spef`). + ```tcl write_spef [-net_id net_id] From eb68fbfca15b56449053306ff0653f96a1ade796 Mon Sep 17 00:00:00 2001 From: Arthur Koucher Date: Tue, 11 Aug 2026 18:12:04 -0300 Subject: [PATCH 3/5] rcx: fix three issues from 3D-IC review 1) Guard the block null check instead of the always-created auxiliary extractor 2) Pass the logger to pinDirection and error on a missing boundary terminal 3) Sort interChipModel entries in BUILD Signed-off-by: Arthur Koucher --- src/rcx/BUILD | 4 ++-- src/rcx/src/multiChipExtractor.cpp | 5 +++-- src/rcx/src/multiChipSpefWriter.cpp | 11 +++++++++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/rcx/BUILD b/src/rcx/BUILD index 36768655d50..69bb42ea146 100644 --- a/src/rcx/BUILD +++ b/src/rcx/BUILD @@ -15,7 +15,6 @@ package( cc_library( name = "rcx", srcs = [ - "src/interChipModel.cpp", "src/dbUtil.cpp", "src/ext.cpp", "src/extBench.cpp", @@ -59,6 +58,7 @@ cc_library( "src/grids.cpp", "src/gs.cpp", "src/gseq.h", + "src/interChipModel.cpp", "src/multiChipExtractor.cpp", "src/multiChipSpefWriter.cpp", "src/name.cpp", @@ -70,7 +70,6 @@ cc_library( ], hdrs = [ "include/rcx/array1.h", - "include/rcx/interChipModel.h", "include/rcx/box.h", "include/rcx/dbUtil.h", "include/rcx/ext.h", @@ -88,6 +87,7 @@ cc_library( "include/rcx/ext_options.h", "include/rcx/extprocess.h", "include/rcx/grids.h", + "include/rcx/interChipModel.h", "include/rcx/multiChipExtractor.h", "include/rcx/multiChipSpefWriter.h", "include/rcx/rcx.h", diff --git a/src/rcx/src/multiChipExtractor.cpp b/src/rcx/src/multiChipExtractor.cpp index 36844778d33..9c6f389823d 100644 --- a/src/rcx/src/multiChipExtractor.cpp +++ b/src/rcx/src/multiChipExtractor.cpp @@ -94,8 +94,9 @@ void MultiChipExtractor::loadRules() = parseInterChipRules(assembly_extraction_rules_file_, logger_); // Avoid leaving the block pointing at the extractor we destroy. - if (auxiliary_extractor) { - auxiliary_extractor->getBlock()->setExtmi(nullptr); + odb::dbBlock* block = auxiliary_extractor->getBlock(); + if (block) { + block->setExtmi(nullptr); } } diff --git a/src/rcx/src/multiChipSpefWriter.cpp b/src/rcx/src/multiChipSpefWriter.cpp index a636fd17129..dd689363947 100644 --- a/src/rcx/src/multiChipSpefWriter.cpp +++ b/src/rcx/src/multiChipSpefWriter.cpp @@ -27,8 +27,15 @@ std::string stripSuffix(const std::string& target, const std::string& suffix) return target; } -char pinDirection(odb::dbBTerm* bterm) +char pinDirection(odb::dbBTerm* bterm, utl::Logger* logger) { + if (!bterm) { + logger->error(utl::RCX, + 490, + "Could not determine the pin direction. The boundary " + "terminal does not exist."); + } + char direction = 'B'; switch (bterm->getIoType().getValue()) { @@ -192,7 +199,7 @@ std::string MultiChipSpefWriter::chipNetSpefString(odb::dbChipNet* chip_net) out << "*CONN\n"; for (odb::dbChipCapNode* cap_node : chip_net->getChipCapNodes()) { out << "*I " << bondNodeName(cap_node) << " " - << pinDirection(cap_node->getBTerm()) << "\n"; + << pinDirection(cap_node->getBTerm(), logger_) << "\n"; } out << "*RES\n"; From 1406a8bec5ac3465fee62d579d33ea4a41de0701 Mon Sep 17 00:00:00 2001 From: Arthur Koucher Date: Thu, 20 Aug 2026 11:34:42 -0300 Subject: [PATCH 4/5] rcx: ensure that all multi-chip spefs have consistent units Signed-off-by: Arthur Koucher --- src/odb/include/odb/db.h | 2 ++ src/odb/src/db/dbChipNet.cpp | 11 +++++++++++ src/rcx/include/rcx/multiChipSpefWriter.h | 4 ++++ src/rcx/src/multiChipSpefWriter.cpp | 16 ++++++++++++++-- 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/odb/include/odb/db.h b/src/odb/include/odb/db.h index 47b4d9902c4..5e18d071690 100644 --- a/src/odb/include/odb/db.h +++ b/src/odb/include/odb/db.h @@ -7540,6 +7540,8 @@ class dbChipNet : public dbObject dbSet getChipCapNodes() const; + float getTotalCapacitance() const; + dbSet getChipRSegs() const; uint32_t getNumBumpInsts() const; diff --git a/src/odb/src/db/dbChipNet.cpp b/src/odb/src/db/dbChipNet.cpp index 89971e9d0e0..554e9e36ceb 100644 --- a/src/odb/src/db/dbChipNet.cpp +++ b/src/odb/src/db/dbChipNet.cpp @@ -119,6 +119,17 @@ dbSet dbChipNet::getChipCapNodes() const return dbSet(chip_net, chip->chip_net_cap_node_itr_); } +float dbChipNet::getTotalCapacitance() const +{ + float total_capacitance = 0.0; + + for (odb::dbChipCapNode* cap_node : getChipCapNodes()) { + total_capacitance += cap_node->getCapacitance(); + } + + return total_capacitance; +} + dbSet dbChipNet::getChipRSegs() const { _dbChipNet* chip_net = (_dbChipNet*) this; diff --git a/src/rcx/include/rcx/multiChipSpefWriter.h b/src/rcx/include/rcx/multiChipSpefWriter.h index eefefb7145c..39e4af98a47 100644 --- a/src/rcx/include/rcx/multiChipSpefWriter.h +++ b/src/rcx/include/rcx/multiChipSpefWriter.h @@ -22,6 +22,8 @@ class MultiChipSpefWriter void run(const SpefOptions& options); private: + void setUnits(const SpefOptions& options); + void writeChipSpef(odb::dbChip* chip, const SpefOptions& options); void writeInterChipSpef(); std::string chipNetSpefString(odb::dbChipNet* chip_net); @@ -32,6 +34,8 @@ class MultiChipSpefWriter std::string file_base_name_; SpefHeader spef_header_; + + ScaleFactors scale_factors_; }; } // namespace rcx diff --git a/src/rcx/src/multiChipSpefWriter.cpp b/src/rcx/src/multiChipSpefWriter.cpp index dd689363947..e546062a6d7 100644 --- a/src/rcx/src/multiChipSpefWriter.cpp +++ b/src/rcx/src/multiChipSpefWriter.cpp @@ -67,6 +67,8 @@ void MultiChipSpefWriter::run(const SpefOptions& options) { file_base_name_ = stripSuffix(options.file, ".spef"); + setUnits(options); + for (odb::dbChip* chip : db_->getChips()) { if (chip->getChipType() == odb::dbChip::ChipType::DIE) { writeChipSpef(chip, options); @@ -76,6 +78,15 @@ void MultiChipSpefWriter::run(const SpefOptions& options) writeInterChipSpef(); } +void MultiChipSpefWriter::setUnits(const SpefOptions& options) +{ + spef_header_.capacitance_unit_word = options.cap_units; + spef_header_.resistance_unit_word = options.res_units; + + scale_factors_ = extSpef::computeScaleFactors( + spef_header_.capacitance_unit_word, spef_header_.resistance_unit_word); +} + void MultiChipSpefWriter::writeChipSpef(odb::dbChip* chip, const SpefOptions& options) { @@ -194,7 +205,8 @@ std::string MultiChipSpefWriter::chipNetSpefString(odb::dbChipNet* chip_net) std::ostringstream out; - out << "\n*D_NET " << chip_net->getName() << " 0\n"; + out << "\n*D_NET " << chip_net->getName() << " " + << chip_net->getTotalCapacitance() * scale_factors_.capacitance << "\n"; out << "*CONN\n"; for (odb::dbChipCapNode* cap_node : chip_net->getChipCapNodes()) { @@ -207,7 +219,7 @@ std::string MultiChipSpefWriter::chipNetSpefString(odb::dbChipNet* chip_net) for (odb::dbChipRSeg* rseg : rsegs) { out << res_id++ << " " << bondNodeName(rseg->getSourceCapNode()) << " " << bondNodeName(rseg->getTargetCapNode()) << " " - << rseg->getResistance() << "\n"; + << rseg->getResistance() * scale_factors_.resistance << "\n"; } out << "*END\n"; From 63bcd6db9ab05c418442ab3641b5bf88d1f108c1 Mon Sep 17 00:00:00 2001 From: Arthur Koucher Date: Thu, 20 Aug 2026 11:34:53 -0300 Subject: [PATCH 5/5] rcx: make naming more honest and some style adjustments Signed-off-by: Arthur Koucher --- src/rcx/src/multiChipExtractor.cpp | 6 +- src/rcx/src/multiChipSpefWriter.cpp | 99 +++++++++++++++-------------- 2 files changed, 54 insertions(+), 51 deletions(-) diff --git a/src/rcx/src/multiChipExtractor.cpp b/src/rcx/src/multiChipExtractor.cpp index 9c6f389823d..d8219062f70 100644 --- a/src/rcx/src/multiChipExtractor.cpp +++ b/src/rcx/src/multiChipExtractor.cpp @@ -49,7 +49,7 @@ void MultiChipExtractor::loadRules() { if (assembly_extraction_rules_file_.empty()) { logger_->error(utl::RCX, - 529, + 20, "No assembly rules file defined; set one with " "set_extraction_rules_file -assembly."); } @@ -65,7 +65,7 @@ void MultiChipExtractor::loadRules() if (type == odb::dbChip::ChipType::RDL) { logger_->error( utl::RCX, - 530, + 22, "RDL chips are not supported in this version of 3D extraction."); } @@ -104,7 +104,7 @@ void MultiChipExtractor::extractChipParasitics(odb::dbChip* chip, const ExtractOptions& options) { logger_->info(utl::RCX, - 531, + 23, "Running parasitics extraction for chip {}.", chip->getName()); diff --git a/src/rcx/src/multiChipSpefWriter.cpp b/src/rcx/src/multiChipSpefWriter.cpp index e546062a6d7..9e44a5ce2a6 100644 --- a/src/rcx/src/multiChipSpefWriter.cpp +++ b/src/rcx/src/multiChipSpefWriter.cpp @@ -93,32 +93,35 @@ void MultiChipSpefWriter::writeChipSpef(odb::dbChip* chip, const std::string file_path = file_base_name_ + "." + chip->getName() + ".spef"; - auto block_spef_writer = std::make_unique(); - block_spef_writer->init(db_, logger_); - block_spef_writer->setBlockFromChip(chip); - - block_spef_writer->writeSPEF((char*) file_path.c_str(), - (char*) options.nets, - options.no_name_map, - (char*) options.N, - options.term_junction_xy, - options.cap_units, - options.res_units, - options.gz, - options.stop_after_map, - options.w_clock, - options.w_conn, - options.w_cap, - options.w_cc_cap, - options.w_res, - options.no_c_num, - false, - options.single_pi, - options.no_backslash, - options.corner, - options.ext_corner_name, - spef_header_.version.c_str(), - options.parallel); + // The block spef writer was originally conceived to live within the block + // extractor, that's why we use the latter here. In the future, we can + // modify the writer so that it does not require an extractor. + auto block_extractor = std::make_unique(); + block_extractor->init(db_, logger_); + block_extractor->setBlockFromChip(chip); + + block_extractor->writeSPEF((char*) file_path.c_str(), + (char*) options.nets, + options.no_name_map, + (char*) options.N, + options.term_junction_xy, + options.cap_units, + options.res_units, + options.gz, + options.stop_after_map, + options.w_clock, + options.w_conn, + options.w_cap, + options.w_cc_cap, + options.w_res, + options.no_c_num, + false, + options.single_pi, + options.no_backslash, + options.corner, + options.ext_corner_name, + spef_header_.version.c_str(), + options.parallel); logger_->info(utl::RCX, 534, @@ -152,12 +155,15 @@ std::string MultiChipSpefWriter::bondNodeName(odb::dbChipCapNode* cap_node) } std::string bond_node_name; + for (odb::dbChipInst* chip_inst : chip_inst_path) { if (!bond_node_name.empty()) { bond_node_name += '/'; } + bond_node_name += chip_inst->getName(); } + bond_node_name += ':'; bond_node_name += bterm->getName(); @@ -167,7 +173,6 @@ std::string MultiChipSpefWriter::bondNodeName(odb::dbChipCapNode* cap_node) void MultiChipSpefWriter::writeInterChipSpef() { const std::string file_path = file_base_name_ + ".bonds.spef"; - odb::dbChip* top_chip = db_->getChip(); std::ofstream out(file_path); if (!out) { @@ -177,11 +182,12 @@ void MultiChipSpefWriter::writeInterChipSpef() file_path); } + odb::dbChip* top_chip = db_->getChip(); spef_header_.design_name = top_chip->getName(); + out << spef_header_.string(logger_); int bond_count = 0; - for (odb::dbChipNet* chip_net : top_chip->getChipNets()) { const std::string chip_net_spef = chipNetSpefString(chip_net); @@ -197,33 +203,30 @@ void MultiChipSpefWriter::writeInterChipSpef() std::string MultiChipSpefWriter::chipNetSpefString(odb::dbChipNet* chip_net) { + std::ostringstream out; odb::dbSet rsegs = chip_net->getChipRSegs(); - if (rsegs.empty()) { - return ""; - } - - std::ostringstream out; + if (!rsegs.empty()) { + out << "\n*D_NET " << chip_net->getName() << " " + << chip_net->getTotalCapacitance() * scale_factors_.capacitance << "\n"; - out << "\n*D_NET " << chip_net->getName() << " " - << chip_net->getTotalCapacitance() * scale_factors_.capacitance << "\n"; + out << "*CONN\n"; + for (odb::dbChipCapNode* cap_node : chip_net->getChipCapNodes()) { + out << "*I " << bondNodeName(cap_node) << " " + << pinDirection(cap_node->getBTerm(), logger_) << "\n"; + } - out << "*CONN\n"; - for (odb::dbChipCapNode* cap_node : chip_net->getChipCapNodes()) { - out << "*I " << bondNodeName(cap_node) << " " - << pinDirection(cap_node->getBTerm(), logger_) << "\n"; - } + out << "*RES\n"; + int res_id = 1; + for (odb::dbChipRSeg* rseg : rsegs) { + out << res_id++ << " " << bondNodeName(rseg->getSourceCapNode()) << " " + << bondNodeName(rseg->getTargetCapNode()) << " " + << rseg->getResistance() * scale_factors_.resistance << "\n"; + } - out << "*RES\n"; - int res_id = 1; - for (odb::dbChipRSeg* rseg : rsegs) { - out << res_id++ << " " << bondNodeName(rseg->getSourceCapNode()) << " " - << bondNodeName(rseg->getTargetCapNode()) << " " - << rseg->getResistance() * scale_factors_.resistance << "\n"; + out << "*END\n"; } - out << "*END\n"; - return out.str(); }