diff --git a/docs/agents/coding.md b/docs/agents/coding.md index 865b60ce8f3..b8556c0ecbe 100644 --- a/docs/agents/coding.md +++ b/docs/agents/coding.md @@ -25,6 +25,21 @@ ### Null Safety - Avoid adding too defensive null checks if the object cannot be null +## Service Interfaces + +Abstract interfaces published through `utl::ServiceRegistry` use the +`Service` suffix (e.g. `est::ParasiticsService`, `drt::PinAccessService`). +Each interface lives in its owning module's public `include//` +directory and is exposed as a small header-only Bazel target that +consumers can depend on without pulling in the full implementation +library. This keeps module dependency graphs acyclic and the decoupling +visible at the build layer. + +Note that this is distinct from the `Abstract*` prefix used for +graphics/visualization mock-swappable classes (e.g. +`AbstractSteinerRenderer`); those are inherited for test/headless modes +rather than looked up through a registry. + ## Common Coding Mistakes ### OpenROAD Message ID Duplicate Checker diff --git a/include/ord/OpenRoad.hh b/include/ord/OpenRoad.hh index 35b6ad067a1..d41b731907f 100644 --- a/include/ord/OpenRoad.hh +++ b/include/ord/OpenRoad.hh @@ -108,7 +108,7 @@ class ICeWall; namespace utl { class Logger; -class CallBackHandler; +class ServiceRegistry; } // namespace utl namespace dst { @@ -151,7 +151,7 @@ class OpenRoad Tcl_Interp* tclInterp() { return tcl_interp_; } utl::Logger* getLogger() { return logger_; } - utl::CallBackHandler* getCallBackHandler() { return callback_handler_; } + utl::ServiceRegistry* getServiceRegistry() { return service_registry_; } odb::dbDatabase* getDb() { return db_; } sta::dbSta* getSta() { return sta_; } sta::dbNetwork* getDbNetwork(); @@ -288,7 +288,7 @@ class OpenRoad dft::Dft* dft_ = nullptr; est::EstimateParasitics* estimate_parasitics_ = nullptr; web::WebServer* web_server_ = nullptr; - utl::CallBackHandler* callback_handler_ = nullptr; + utl::ServiceRegistry* service_registry_ = nullptr; int threads_ = 1; diff --git a/src/OpenRoad.cc b/src/OpenRoad.cc index eef476375e0..5570f6843f5 100644 --- a/src/OpenRoad.cc +++ b/src/OpenRoad.cc @@ -83,11 +83,11 @@ #include "tap/MakeTapcell.h" #include "tap/tapcell.h" #include "upf/MakeUpf.h" -#include "utl/CallBackHandler.h" #include "utl/Logger.h" #include "utl/MakeLogger.h" #include "utl/Progress.h" #include "utl/ScopedTemporaryFile.h" +#include "utl/ServiceRegistry.h" #include "utl/decode.h" #include "web/MakeWeb.h" #include "web/web.h" @@ -150,7 +150,7 @@ OpenRoad::~OpenRoad() delete web_server_; delete logger_; delete verilog_reader_; - delete callback_handler_; + delete service_registry_; } sta::dbNetwork* OpenRoad::getDbNetwork() @@ -195,7 +195,7 @@ void OpenRoad::init(Tcl_Interp* tcl_interp, // Make components. utl::Progress::setBatchMode(batch_mode); logger_ = new utl::Logger(log_filename, metrics_filename); - callback_handler_ = new utl::CallBackHandler(logger_); + service_registry_ = new utl::ServiceRegistry(logger_); db_->setLogger(logger_); sta_ = new sta::dbSta(tcl_interp, db_, logger_); verilog_network_ = new dbVerilogNetwork(sta_); @@ -204,7 +204,7 @@ void OpenRoad::init(Tcl_Interp* tcl_interp, antenna_checker_ = new ant::AntennaChecker(db_, logger_); opendp_ = new dpl::Opendp(db_, logger_); global_router_ = new grt::GlobalRouter(logger_, - callback_handler_, + service_registry_, stt_builder_, db_, sta_, @@ -213,7 +213,7 @@ void OpenRoad::init(Tcl_Interp* tcl_interp, grt::initGui(global_router_, db_, logger_); estimate_parasitics_ = new est::EstimateParasitics( - logger_, callback_handler_, db_, sta_, stt_builder_, global_router_); + logger_, service_registry_, db_, sta_, stt_builder_, global_router_); est::initGui(estimate_parasitics_); resizer_ = new rsz::Resizer(logger_, @@ -240,7 +240,7 @@ void OpenRoad::init(Tcl_Interp* tcl_interp, extractor_ = new rcx::Ext(db_, logger_, getVersion()); distributer_ = new dst::Distributed(logger_); detailed_router_ = new drt::TritonRoute( - db_, logger_, callback_handler_, distributer_, stt_builder_); + db_, logger_, service_registry_, distributer_, stt_builder_); drt::initGui(detailed_router_); replace_ = new gpl::Replace(db_, sta_, resizer_, global_router_, logger_); diff --git a/src/drt/BUILD b/src/drt/BUILD index c98146abcb7..c915cfbac58 100644 --- a/src/drt/BUILD +++ b/src/drt/BUILD @@ -12,6 +12,15 @@ package( features = ["layering_check"], ) +# Abstract service interface published through utl::ServiceRegistry. +# Consumers depend only on this target, not on //src/drt, which keeps +# them free of the full drt implementation surface. +cc_library( + name = "pin_access_service", + hdrs = ["include/drt/PinAccessService.h"], + includes = ["include"], +) + cc_library( name = "db_hdrs", hdrs = [ @@ -79,7 +88,6 @@ cc_library( "src/AbstractGraphicsFactory.h", "src/DesignCallBack.cpp", "src/DesignCallBack.h", - "src/PACallBack.h", "src/TritonRoute.cpp", "src/db/drObj/drAccessPattern.cpp", "src/db/drObj/drNet.cpp", @@ -181,6 +189,7 @@ cc_library( ], deps = [ ":db_hdrs", + ":pin_access_service", "//src/dst", "//src/odb", "//src/stt", @@ -245,6 +254,7 @@ cc_library( deps = [ ":db_hdrs", ":drt", + ":pin_access_service", "//:ord", "//src/dst", "//src/gui", diff --git a/src/drt/include/drt/PinAccessService.h b/src/drt/include/drt/PinAccessService.h new file mode 100644 index 00000000000..334a697875b --- /dev/null +++ b/src/drt/include/drt/PinAccessService.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2026-2026, The OpenROAD Authors + +#pragma once + +namespace drt { + +// Service interface published by the detailed router. Consumers look +// it up through utl::ServiceRegistry and do not depend on the concrete +// drt::TritonRoute type. +class PinAccessService +{ + public: + virtual ~PinAccessService() = default; + + // Recompute pin access for any instances that have become dirty. + virtual void updateDirtyPinAccess() = 0; +}; + +} // namespace drt diff --git a/src/drt/include/drt/TritonRoute.h b/src/drt/include/drt/TritonRoute.h index 75e9ed6016f..574ffe08346 100644 --- a/src/drt/include/drt/TritonRoute.h +++ b/src/drt/include/drt/TritonRoute.h @@ -15,6 +15,7 @@ #include "absl/synchronization/mutex.h" #include "boost/asio/thread_pool.hpp" +#include "drt/PinAccessService.h" #include "odb/geom.h" namespace odb { @@ -27,7 +28,7 @@ class dbWire; namespace utl { class Logger; -class CallBackHandler; +class ServiceRegistry; } // namespace utl namespace stt { @@ -43,7 +44,6 @@ namespace drt { class frDesign; class frInst; class DesignCallBack; -class PACallBack; class FlexDR; class FlexPA; class FlexTA; @@ -80,15 +80,17 @@ struct ParamStruct int num_threads = 1; }; -class TritonRoute +class TritonRoute : public PinAccessService { public: TritonRoute(odb::dbDatabase* db, utl::Logger* logger, - utl::CallBackHandler* callback_handler, + utl::ServiceRegistry* service_registry, dst::Distributed* dist, stt::SteinerTreeBuilder* stt_builder); - ~TritonRoute(); + ~TritonRoute() override; + + void updateDirtyPinAccess() override; void initGraphics(std::unique_ptr graphics_factory); @@ -191,10 +193,10 @@ class TritonRoute std::unique_ptr design_; std::unique_ptr debug_; std::unique_ptr db_callback_; - std::unique_ptr pa_callback_; std::unique_ptr router_cfg_; odb::dbDatabase* db_{nullptr}; utl::Logger* logger_{nullptr}; + utl::ServiceRegistry* service_registry_{nullptr}; std::unique_ptr dr_; // kept for single stepping stt::SteinerTreeBuilder* stt_builder_{nullptr}; int num_drvs_{-1}; diff --git a/src/drt/src/PACallBack.h b/src/drt/src/PACallBack.h deleted file mode 100644 index dabb5c40f14..00000000000 --- a/src/drt/src/PACallBack.h +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2025-2025, The OpenROAD Authors - -#pragma once - -#include "drt/TritonRoute.h" -#include "frDesign.h" -#include "utl/CallBack.h" -#include "utl/Logger.h" -namespace drt { - -class frDesign; -class FlexPA; - -class PACallBack : public utl::CallBack -{ - public: - PACallBack(TritonRoute* router) : router_(router) {} - - ~PACallBack() override = default; - - void onPinAccessUpdateRequired() override - { - auto design = router_->getDesign(); - if (design == nullptr || design->getTopBlock() == nullptr) { - return; - } - router_->updateDirtyPAData(); - } - - private: - TritonRoute* router_; -}; - -} // namespace drt diff --git a/src/drt/src/TritonRoute.cpp b/src/drt/src/TritonRoute.cpp index 801e0a5eb6f..84312a3ddc8 100644 --- a/src/drt/src/TritonRoute.cpp +++ b/src/drt/src/TritonRoute.cpp @@ -17,7 +17,6 @@ #include "AbstractGraphicsFactory.h" #include "DesignCallBack.h" -#include "PACallBack.h" #include "absl/synchronization/mutex.h" #include "boost/asio/post.hpp" #include "boost/bind/bind.hpp" @@ -35,6 +34,7 @@ #include "distributed/frArchive.h" #include "dr/AbstractDRGraphics.h" #include "dr/FlexDR.h" +#include "drt/PinAccessService.h" #include "dst/Distributed.h" #include "dst/JobMessage.h" #include "frBaseTypes.h" @@ -57,21 +57,20 @@ #include "stt/SteinerTreeBuilder.h" #include "ta/AbstractTAGraphics.h" #include "ta/FlexTA.h" -#include "utl/CallBackHandler.h" #include "utl/Logger.h" #include "utl/ScopedTemporaryFile.h" +#include "utl/ServiceRegistry.h" using odb::dbTechLayerType; namespace drt { TritonRoute::TritonRoute(odb::dbDatabase* db, utl::Logger* logger, - utl::CallBackHandler* callback_handler, + utl::ServiceRegistry* service_registry, dst::Distributed* dist, stt::SteinerTreeBuilder* stt_builder) : debug_(std::make_unique()), db_callback_(std::make_unique(this)), - pa_callback_(std::make_unique(this)), router_cfg_(std::make_unique()) { if (distributed_) { @@ -79,14 +78,26 @@ TritonRoute::TritonRoute(odb::dbDatabase* db, } db_ = db; logger_ = logger; + service_registry_ = service_registry; dist_ = dist; stt_builder_ = stt_builder; design_ = std::make_unique(logger_, router_cfg_.get()); dist->addCallBack(new RoutingCallBack(this, dist, logger)); - pa_callback_->setOwner(callback_handler); + service_registry_->provide(this); } -TritonRoute::~TritonRoute() = default; +TritonRoute::~TritonRoute() +{ + service_registry_->withdraw(this); +} + +void TritonRoute::updateDirtyPinAccess() +{ + if (design_ == nullptr || design_->getTopBlock() == nullptr) { + return; + } + updateDirtyPAData(); +} void TritonRoute::initGraphics( std::unique_ptr graphics_factory) diff --git a/src/est/BUILD b/src/est/BUILD index 17fa8887268..fd580fcbc2f 100644 --- a/src/est/BUILD +++ b/src/est/BUILD @@ -14,7 +14,6 @@ cc_library( name = "private_hdrs", hdrs = [ "src/AbstractSteinerRenderer.h", - "src/EstimateParasiticsCallBack.h", "src/MakeWireParasitics.h", "src/OdbCallBack.h", "src/SteinerRenderer.h", @@ -26,11 +25,19 @@ cc_library( ], ) +# Abstract service interface published through utl::ServiceRegistry. +# Consumers depend only on this target, not on //src/est, which keeps +# them free of the full est implementation surface. +cc_library( + name = "parasitics_service", + hdrs = ["include/est/ParasiticsService.h"], + includes = ["include"], +) + cc_library( name = "est", srcs = [ "src/EstimateParasitics.cpp", - "src/EstimateParasiticsCallBack.cpp", "src/MakeWireParasitics.cpp", "src/OdbCallBack.cpp", "src/SteinerTree.cpp", @@ -43,6 +50,7 @@ cc_library( "include", ], deps = [ + ":parasitics_service", ":private_hdrs", "//src/dbSta", "//src/dbSta:SpefWriter", diff --git a/src/est/include/est/EstimateParasitics.h b/src/est/include/est/EstimateParasitics.h index 6fcbe8c4d88..9f157b91b59 100644 --- a/src/est/include/est/EstimateParasitics.h +++ b/src/est/include/est/EstimateParasitics.h @@ -17,6 +17,7 @@ #include "db_sta/SpefWriter.hh" #include "db_sta/dbNetwork.hh" #include "db_sta/dbSta.hh" +#include "est/ParasiticsService.h" #include "est/SteinerTree.h" #include "grt/GRoute.h" #include "grt/GlobalRouter.h" @@ -33,7 +34,7 @@ #include "utl/Logger.h" namespace utl { -class CallBackHandler; +class ServiceRegistry; } // namespace utl namespace est { @@ -68,18 +69,19 @@ struct ParasiticsCapacitance class AbstractSteinerRenderer; class OdbCallBack; -class EstimateParasiticsCallBack; -class EstimateParasitics : public sta::dbStaState +class EstimateParasitics : public sta::dbStaState, public ParasiticsService { public: EstimateParasitics(utl::Logger* logger, - utl::CallBackHandler* callback_handler, + utl::ServiceRegistry* service_registry, odb::dbDatabase* db, sta::dbSta* sta, stt::SteinerTreeBuilder* stt_builder, grt::GlobalRouter* global_router); ~EstimateParasitics() override; + + void estimateAllGlobalRouteParasitics() override; void initSteinerRenderer( std::unique_ptr steiner_renderer); void setLayerRC(odb::dbTechLayer* layer, @@ -238,7 +240,7 @@ class EstimateParasitics : public sta::dbStaState double dbuToMeters(int dist) const; utl::Logger* logger_ = nullptr; - std::unique_ptr estimate_parasitics_cbk_; + utl::ServiceRegistry* service_registry_ = nullptr; stt::SteinerTreeBuilder* stt_builder_ = nullptr; grt::GlobalRouter* global_router_ = nullptr; grt::IncrementalGRoute* incr_groute_ = nullptr; diff --git a/src/est/include/est/ParasiticsService.h b/src/est/include/est/ParasiticsService.h new file mode 100644 index 00000000000..f67e6fd2359 --- /dev/null +++ b/src/est/include/est/ParasiticsService.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2026-2026, The OpenROAD Authors + +#pragma once + +namespace est { + +// Service interface published by the parasitics estimator. Consumers +// look it up through utl::ServiceRegistry and do not depend on the +// concrete est::EstimateParasitics type. +class ParasiticsService +{ + public: + virtual ~ParasiticsService() = default; + + // Clear all existing parasitics and re-estimate them from the + // current global routing state (partial routes). + virtual void estimateAllGlobalRouteParasitics() = 0; +}; + +} // namespace est diff --git a/src/est/src/CMakeLists.txt b/src/est/src/CMakeLists.txt index 4ea8bd379c7..75e755ced74 100644 --- a/src/est/src/CMakeLists.txt +++ b/src/est/src/CMakeLists.txt @@ -12,7 +12,6 @@ swig_lib(NAME est add_library(est_lib EstimateParasitics.cpp - EstimateParasiticsCallBack.cpp MakeWireParasitics.cpp OdbCallBack.cpp SteinerTree.cpp diff --git a/src/est/src/EstimateParasitics.cpp b/src/est/src/EstimateParasitics.cpp index 3273f8199c2..197f7ccb023 100644 --- a/src/est/src/EstimateParasitics.cpp +++ b/src/est/src/EstimateParasitics.cpp @@ -16,12 +16,12 @@ #include #include "AbstractSteinerRenderer.h" -#include "EstimateParasiticsCallBack.h" #include "MakeWireParasitics.h" #include "OdbCallBack.h" #include "db_sta/SpefWriter.hh" #include "db_sta/dbNetwork.hh" #include "db_sta/dbSta.hh" +#include "est/ParasiticsService.h" #include "est/SteinerTree.h" #include "grt/GRoute.h" #include "grt/GlobalRouter.h" @@ -43,8 +43,8 @@ #include "sta/Transition.hh" #include "sta/Units.hh" #include "stt/SteinerTreeBuilder.h" -#include "utl/CallBackHandler.h" #include "utl/Logger.h" +#include "utl/ServiceRegistry.h" namespace est { @@ -63,14 +63,13 @@ using odb::dbMasterType; using odb::dbModInst; EstimateParasitics::EstimateParasitics(utl::Logger* logger, - utl::CallBackHandler* callback_handler, + utl::ServiceRegistry* service_registry, odb::dbDatabase* db, sta::dbSta* sta, stt::SteinerTreeBuilder* stt_builder, grt::GlobalRouter* global_router) : logger_(logger), - estimate_parasitics_cbk_( - std::make_unique(this)), + service_registry_(service_registry), stt_builder_(stt_builder), global_router_(global_router), db_network_(sta->getDbNetwork()), @@ -81,12 +80,23 @@ EstimateParasitics::EstimateParasitics(utl::Logger* logger, wire_clk_res_(0.0), wire_clk_cap_(0.0) { - estimate_parasitics_cbk_->setOwner(callback_handler); dbStaState::init(sta); db_cbk_ = std::make_unique(this, network_, db_network_); + service_registry_->provide(this); } -EstimateParasitics::~EstimateParasitics() = default; +EstimateParasitics::~EstimateParasitics() +{ + service_registry_->withdraw(this); +} + +void EstimateParasitics::estimateAllGlobalRouteParasitics() +{ + clearParasitics(); + for (auto& [db_net, route] : global_router_->getPartialRoutes()) { + estimateGlobalRouteParasitics(db_net, route); + } +} void EstimateParasitics::initSteinerRenderer( std::unique_ptr steiner_renderer) diff --git a/src/est/src/EstimateParasiticsCallBack.cpp b/src/est/src/EstimateParasiticsCallBack.cpp deleted file mode 100644 index 3cd081cf7ee..00000000000 --- a/src/est/src/EstimateParasiticsCallBack.cpp +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2024-2025, The OpenROAD Authors - -#include "EstimateParasiticsCallBack.h" - -#include "est/EstimateParasitics.h" -#include "grt/GlobalRouter.h" - -namespace est { - -void EstimateParasiticsCallBack::onEstimateParasiticsRequired() -{ - estimate_parasitics_->clearParasitics(); - auto routes = estimate_parasitics_->getGlobalRouter()->getPartialRoutes(); - for (auto& [db_net, route] : routes) { - estimate_parasitics_->estimateGlobalRouteParasitics(db_net, route); - } -} - -} // namespace est \ No newline at end of file diff --git a/src/est/src/EstimateParasiticsCallBack.h b/src/est/src/EstimateParasiticsCallBack.h deleted file mode 100644 index 239f113abe1..00000000000 --- a/src/est/src/EstimateParasiticsCallBack.h +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2025-2025, The OpenROAD Authors - -#pragma once - -#include "est/EstimateParasitics.h" -#include "utl/CallBack.h" -#include "utl/Logger.h" - -namespace est { - -class frDesign; -class FlexPA; - -class EstimateParasiticsCallBack : public utl::CallBack -{ - public: - EstimateParasiticsCallBack(EstimateParasitics* estimate_parasitics) - : estimate_parasitics_(estimate_parasitics) - { - } - - ~EstimateParasiticsCallBack() override = default; - - void onEstimateParasiticsRequired() override; - - private: - EstimateParasitics* estimate_parasitics_; -}; - -} // namespace est diff --git a/src/gpl/test/mbff_test.cpp b/src/gpl/test/mbff_test.cpp index 7fe2a17dd50..fc1151d2156 100644 --- a/src/gpl/test/mbff_test.cpp +++ b/src/gpl/test/mbff_test.cpp @@ -19,7 +19,7 @@ #include "src/gpl/src/graphicsNone.h" #include "stt/SteinerTreeBuilder.h" #include "tst/fixture.h" -#include "utl/CallBackHandler.h" +#include "utl/ServiceRegistry.h" namespace gpl { @@ -40,14 +40,14 @@ class MBFFTestFixture : public tst::Fixture void SetUp() override { logger_ = getLogger(); - callback_handler_ = std::make_unique(logger_); + service_registry_ = std::make_unique(logger_); verilog_network_ = std::make_unique(getSta()); stt_builder_ = std::make_unique(getDb(), logger_); antenna_checker_ = std::make_unique(getDb(), logger_); opendp_ = std::make_unique(getDb(), logger_); global_router_ = std::make_unique(logger_, - callback_handler_.get(), + service_registry_.get(), stt_builder_.get(), getDb(), getSta(), @@ -55,7 +55,7 @@ class MBFFTestFixture : public tst::Fixture opendp_.get()); estimate_parasitics_ = std::make_unique(logger_, - callback_handler_.get(), + service_registry_.get(), getDb(), getSta(), stt_builder_.get(), @@ -110,7 +110,7 @@ class MBFFTestFixture : public tst::Fixture } utl::Logger* logger_; - std::unique_ptr callback_handler_; + std::unique_ptr service_registry_; std::unique_ptr verilog_network_; std::unique_ptr stt_builder_; std::unique_ptr antenna_checker_; diff --git a/src/grt/BUILD b/src/grt/BUILD index b08a57ef617..48c776a861d 100644 --- a/src/grt/BUILD +++ b/src/grt/BUILD @@ -56,6 +56,7 @@ cc_library( ":groute", "//src/dbSta", "//src/dbSta:dbNetwork", + "//src/est:parasitics_service", "//src/gui", "//src/odb", "//src/sta:opensta_lib", @@ -104,6 +105,7 @@ cc_library( "//:ord", "//src/dbSta", "//src/dbSta:dbNetwork", + "//src/est:parasitics_service", "//src/odb", "//src/sta:opensta_lib", "//src/stt", @@ -150,6 +152,7 @@ cc_library( "//src/dbSta:SpefWriter", "//src/dbSta:dbNetwork", "//src/dpl", + "//src/drt:pin_access_service", "//src/gui", "//src/odb", "//src/sta:opensta_lib", diff --git a/src/grt/CMakeLists.txt b/src/grt/CMakeLists.txt index 26324df3f52..2a33684d8f8 100644 --- a/src/grt/CMakeLists.txt +++ b/src/grt/CMakeLists.txt @@ -30,6 +30,9 @@ target_include_directories(grt_lib include PRIVATE src + # Header-only abstract interface published by drt; avoid linking + # drt_lib to keep grt's dependency surface small. + ../drt/include ) target_link_libraries(grt_lib diff --git a/src/grt/include/grt/GlobalRouter.h b/src/grt/include/grt/GlobalRouter.h index 4a947c30fd1..95f35555813 100644 --- a/src/grt/include/grt/GlobalRouter.h +++ b/src/grt/include/grt/GlobalRouter.h @@ -26,7 +26,7 @@ using AdjacencyList = std::vector>; namespace utl { class Logger; -class CallBackHandler; +class ServiceRegistry; } // namespace utl namespace odb { @@ -118,7 +118,7 @@ class GlobalRouter { public: GlobalRouter(utl::Logger* logger, - utl::CallBackHandler* callback_handler, + utl::ServiceRegistry* service_registry, stt::SteinerTreeBuilder* stt_builder, odb::dbDatabase* db, sta::dbSta* sta, @@ -506,7 +506,7 @@ class GlobalRouter void configFastRoute(); utl::Logger* logger_; - utl::CallBackHandler* callback_handler_; + utl::ServiceRegistry* service_registry_; stt::SteinerTreeBuilder* stt_builder_; ant::AntennaChecker* antenna_checker_; dpl::Opendp* opendp_; diff --git a/src/grt/src/GlobalRouter.cpp b/src/grt/src/GlobalRouter.cpp index 201b5e70142..56445daa776 100644 --- a/src/grt/src/GlobalRouter.cpp +++ b/src/grt/src/GlobalRouter.cpp @@ -38,6 +38,7 @@ #include "boost/polygon/polygon.hpp" #include "db_sta/dbNetwork.hh" #include "db_sta/dbSta.hh" +#include "drt/PinAccessService.h" #include "grt/GRoute.h" #include "grt/PinGridLocation.h" #include "grt/Rudy.h" @@ -56,8 +57,8 @@ #include "sta/MinMax.hh" #include "sta/Parasitics.hh" #include "stt/SteinerTreeBuilder.h" -#include "utl/CallBackHandler.h" #include "utl/Logger.h" +#include "utl/ServiceRegistry.h" #include "utl/algorithms.h" namespace grt { @@ -66,14 +67,14 @@ using boost::icl::interval; using utl::GRT; GlobalRouter::GlobalRouter(utl::Logger* logger, - utl::CallBackHandler* callback_handler, + utl::ServiceRegistry* service_registry, stt::SteinerTreeBuilder* stt_builder, odb::dbDatabase* db, sta::dbSta* sta, ant::AntennaChecker* antenna_checker, dpl::Opendp* opendp) : logger_(logger), - callback_handler_(callback_handler), + service_registry_(service_registry), stt_builder_(stt_builder), antenna_checker_(antenna_checker), opendp_(opendp), @@ -105,8 +106,8 @@ GlobalRouter::GlobalRouter(utl::Logger* logger, is_incremental_(false) { fastroute_ - = new FastRouteCore(db_, logger_, callback_handler_, stt_builder_, sta_); - cugr_ = new CUGR(db_, logger_, callback_handler_, stt_builder_, sta_); + = new FastRouteCore(db_, logger_, service_registry_, stt_builder_, sta_); + cugr_ = new CUGR(db_, logger_, service_registry_, stt_builder_, sta_); } void GlobalRouter::initGui( @@ -5902,7 +5903,9 @@ void GlobalRouter::updateCUGRNet(odb::dbNet* net) std::vector GlobalRouter::updateDirtyRoutes(bool save_guides) { - callback_handler_->triggerOnPinAccessUpdateRequired(); + if (auto* pa = service_registry_->find()) { + pa->updateDirtyPinAccess(); + } std::vector dirty_nets; if (!initialized_) { diff --git a/src/grt/src/cugr/CMakeLists.txt b/src/grt/src/cugr/CMakeLists.txt index 39a993253b6..393a502d6e9 100644 --- a/src/grt/src/cugr/CMakeLists.txt +++ b/src/grt/src/cugr/CMakeLists.txt @@ -27,6 +27,9 @@ target_include_directories(CUGR # Ugly but necessary to get GRoute.h and avoid a circular dependency # with FastRoute. Remove once this data moves to OpenDB ../../include + # Header-only abstract interface; avoid linking est_lib to prevent a + # link-time cycle (est_lib already depends on grt_lib). + ../../../est/include ) target_link_libraries(CUGR diff --git a/src/grt/src/cugr/include/CUGR.h b/src/grt/src/cugr/include/CUGR.h index f5ea0334826..2d56c0a775c 100644 --- a/src/grt/src/cugr/include/CUGR.h +++ b/src/grt/src/cugr/include/CUGR.h @@ -29,7 +29,7 @@ class SteinerTreeBuilder; namespace utl { class Logger; -class CallBackHandler; +class ServiceRegistry; } // namespace utl namespace grt { @@ -70,7 +70,7 @@ class CUGR public: CUGR(odb::dbDatabase* db, utl::Logger* log, - utl::CallBackHandler* callback_handler, + utl::ServiceRegistry* service_registry, stt::SteinerTreeBuilder* stt_builder, sta::dbSta* sta); ~CUGR(); @@ -116,7 +116,7 @@ class CUGR odb::dbDatabase* db_; utl::Logger* logger_; - utl::CallBackHandler* callback_handler_; + utl::ServiceRegistry* service_registry_; stt::SteinerTreeBuilder* stt_builder_; sta::dbSta* sta_; NetRouteMap routes_; diff --git a/src/grt/src/cugr/src/CUGR.cpp b/src/grt/src/cugr/src/CUGR.cpp index 81004b6f646..9a5236bb54d 100644 --- a/src/grt/src/cugr/src/CUGR.cpp +++ b/src/grt/src/cugr/src/CUGR.cpp @@ -28,14 +28,15 @@ #include "PatternRoute.h" #include "db_sta/dbNetwork.hh" #include "db_sta/dbSta.hh" +#include "est/ParasiticsService.h" #include "geo.h" #include "grt/GRoute.h" #include "odb/db.h" #include "odb/geom.h" #include "sta/MinMax.hh" #include "stt/SteinerTreeBuilder.h" -#include "utl/CallBackHandler.h" #include "utl/Logger.h" +#include "utl/ServiceRegistry.h" using utl::GRT; @@ -43,12 +44,12 @@ namespace grt { CUGR::CUGR(odb::dbDatabase* db, utl::Logger* log, - utl::CallBackHandler* callback_handler, + utl::ServiceRegistry* service_registry, stt::SteinerTreeBuilder* stt_builder, sta::dbSta* sta) : db_(db), logger_(log), - callback_handler_(callback_handler), + service_registry_(service_registry), stt_builder_(stt_builder), sta_(sta) { @@ -83,7 +84,9 @@ float CUGR::calculatePartialSlack() { std::vector slacks; slacks.reserve(gr_nets_.size()); - callback_handler_->triggerOnEstimateParasiticsRequired(); + if (auto* estimator = service_registry_->find()) { + estimator->estimateAllGlobalRouteParasitics(); + } for (const auto& net : gr_nets_) { float slack = getNetSlack(net->getDbNet()); slacks.push_back(slack); diff --git a/src/grt/src/fastroute/include/FastRoute.h b/src/grt/src/fastroute/include/FastRoute.h index 9f5f00538b9..60a325ec5b9 100644 --- a/src/grt/src/fastroute/include/FastRoute.h +++ b/src/grt/src/fastroute/include/FastRoute.h @@ -24,8 +24,8 @@ #include "stt/SteinerTreeBuilder.h" namespace utl { -class CallBackHandler; class Logger; +class ServiceRegistry; } // namespace utl namespace odb { @@ -90,7 +90,7 @@ class FastRouteCore public: FastRouteCore(odb::dbDatabase* db, utl::Logger* log, - utl::CallBackHandler* callback_handler, + utl::ServiceRegistry* service_registry, stt::SteinerTreeBuilder* stt_builder, sta::dbSta* sta); ~FastRouteCore(); @@ -729,7 +729,7 @@ class FastRouteCore std::vector sttrees_; // the Steiner trees std::vector sttrees_bk_; - utl::CallBackHandler* callback_handler_; + utl::ServiceRegistry* service_registry_; utl::Logger* logger_; stt::SteinerTreeBuilder* stt_builder_; sta::dbSta* sta_; diff --git a/src/grt/src/fastroute/src/FastRoute.cpp b/src/grt/src/fastroute/src/FastRoute.cpp index 73bff89c53f..bfda7d20171 100644 --- a/src/grt/src/fastroute/src/FastRoute.cpp +++ b/src/grt/src/fastroute/src/FastRoute.cpp @@ -29,7 +29,7 @@ using utl::GRT; FastRouteCore::FastRouteCore(odb::dbDatabase* db, utl::Logger* log, - utl::CallBackHandler* callback_handler, + utl::ServiceRegistry* service_registry, stt::SteinerTreeBuilder* stt_builder, sta::dbSta* sta) : max_degree_(0), @@ -64,7 +64,7 @@ FastRouteCore::FastRouteCore(odb::dbDatabase* db, h_capacity_lb_(0), regular_x_(false), regular_y_(false), - callback_handler_(callback_handler), + service_registry_(service_registry), logger_(log), stt_builder_(stt_builder), sta_(sta), diff --git a/src/grt/src/fastroute/src/utility.cpp b/src/grt/src/fastroute/src/utility.cpp index 1863dae02a8..c28be41e60d 100644 --- a/src/grt/src/fastroute/src/utility.cpp +++ b/src/grt/src/fastroute/src/utility.cpp @@ -20,13 +20,14 @@ #include "FastRoute.h" #include "db_sta/dbNetwork.hh" #include "db_sta/dbSta.hh" +#include "est/ParasiticsService.h" #include "grt/GRoute.h" #include "odb/db.h" #include "odb/geom.h" #include "sta/MinMax.hh" #include "stt/SteinerTreeBuilder.h" -#include "utl/CallBackHandler.h" #include "utl/Logger.h" +#include "utl/ServiceRegistry.h" #include "utl/algorithms.h" namespace grt { @@ -672,7 +673,9 @@ void FastRouteCore::updateSlacks(float percentage) } if (en_estimate_parasitics_ && !is_incremental_grt_) { - callback_handler_->triggerOnEstimateParasiticsRequired(); + if (auto* estimator = service_registry_->find()) { + estimator->estimateAllGlobalRouteParasitics(); + } } resetWorstMetrics(); @@ -1576,7 +1579,9 @@ float FastRouteCore::CalculatePartialSlack() { std::vector slacks; slacks.reserve(netCount()); - callback_handler_->triggerOnEstimateParasiticsRequired(); + if (auto* estimator = service_registry_->find()) { + estimator->estimateAllGlobalRouteParasitics(); + } for (const int& netID : net_ids_) { auto fr_net = nets_[netID]; odb::dbNet* db_net = fr_net->getDbNet(); diff --git a/src/rsz/test/cpp/TestBufferRemoval.cc b/src/rsz/test/cpp/TestBufferRemoval.cc index c6491398a79..b5e1cb914f2 100644 --- a/src/rsz/test/cpp/TestBufferRemoval.cc +++ b/src/rsz/test/cpp/TestBufferRemoval.cc @@ -30,8 +30,8 @@ #include "stt/SteinerTreeBuilder.h" #include "tst/fixture.h" #include "tst/nangate45_fixture.h" -#include "utl/CallBackHandler.h" #include "utl/Logger.h" +#include "utl/ServiceRegistry.h" #include "utl/deleter.h" namespace rsz { @@ -44,17 +44,17 @@ class BufRemTest : public tst::Nangate45Fixture BufRemTest() : // initializer resizer stt_(db_.get(), &logger_), - callback_handler_(&logger_), + service_registry_(&logger_), dp_(db_.get(), &logger_), ant_(db_.get(), &logger_), grt_(&logger_, - &callback_handler_, + &service_registry_, &stt_, db_.get(), sta_.get(), &ant_, &dp_), - ep_(&logger_, &callback_handler_, db_.get(), sta_.get(), &stt_, &grt_), + ep_(&logger_, &service_registry_, db_.get(), sta_.get(), &stt_, &grt_), resizer_(&logger_, db_.get(), sta_.get(), &stt_, &grt_, &dp_, &ep_) { library_ = readLiberty(prefix + "Nangate45/Nangate45_typ.lib"); @@ -111,7 +111,7 @@ class BufRemTest : public tst::Nangate45Fixture } stt::SteinerTreeBuilder stt_; - utl::CallBackHandler callback_handler_; + utl::ServiceRegistry service_registry_; dpl::Opendp dp_; ant::AntennaChecker ant_; grt::GlobalRouter grt_; diff --git a/src/rsz/test/cpp/TestBufferRemoval2.cc b/src/rsz/test/cpp/TestBufferRemoval2.cc index b7be6493ee0..c9d52b82e1a 100644 --- a/src/rsz/test/cpp/TestBufferRemoval2.cc +++ b/src/rsz/test/cpp/TestBufferRemoval2.cc @@ -22,7 +22,7 @@ #include "sta/VerilogWriter.hh" #include "stt/SteinerTreeBuilder.h" #include "tst/nangate45_fixture.h" -#include "utl/CallBackHandler.h" +#include "utl/ServiceRegistry.h" namespace rsz { @@ -31,17 +31,17 @@ class BufRemTest2 : public tst::Nangate45Fixture protected: BufRemTest2() : stt_(db_.get(), &logger_), - callback_handler_(&logger_), + service_registry_(&logger_), dp_(db_.get(), &logger_), ant_(db_.get(), &logger_), grt_(&logger_, - &callback_handler_, + &service_registry_, &stt_, db_.get(), sta_.get(), &ant_, &dp_), - ep_(&logger_, &callback_handler_, db_.get(), sta_.get(), &stt_, &grt_), + ep_(&logger_, &service_registry_, db_.get(), sta_.get(), &stt_, &grt_), resizer_(&logger_, db_.get(), sta_.get(), &stt_, &grt_, &dp_, &ep_) { const std::string prefix("_main/src/rsz/test/"); @@ -142,7 +142,7 @@ class BufRemTest2 : public tst::Nangate45Fixture } stt::SteinerTreeBuilder stt_; - utl::CallBackHandler callback_handler_; + utl::ServiceRegistry service_registry_; dpl::Opendp dp_; ant::AntennaChecker ant_; grt::GlobalRouter grt_; diff --git a/src/tst/include/tst/IntegratedFixture.h b/src/tst/include/tst/IntegratedFixture.h index 6c80adc4c00..4ee61a35a2d 100644 --- a/src/tst/include/tst/IntegratedFixture.h +++ b/src/tst/include/tst/IntegratedFixture.h @@ -13,7 +13,7 @@ #include "rsz/Resizer.hh" #include "stt/SteinerTreeBuilder.h" #include "tst/fixture.h" -#include "utl/CallBackHandler.h" +#include "utl/ServiceRegistry.h" namespace tst { @@ -54,7 +54,7 @@ class IntegratedFixture : public tst::Fixture sta::dbNetwork* db_network_{nullptr}; stt::SteinerTreeBuilder stt_; - utl::CallBackHandler callback_handler_; + utl::ServiceRegistry service_registry_; dpl::Opendp dp_; ant::AntennaChecker ant_; grt::GlobalRouter grt_; diff --git a/src/tst/src/IntegratedFixture.cpp b/src/tst/src/IntegratedFixture.cpp index 289b7dfd869..2481ec3d91e 100644 --- a/src/tst/src/IntegratedFixture.cpp +++ b/src/tst/src/IntegratedFixture.cpp @@ -32,17 +32,17 @@ namespace tst { IntegratedFixture::IntegratedFixture(Technology tech, const std::string& test_root_path) : stt_(db_.get(), &logger_), - callback_handler_(&logger_), + service_registry_(&logger_), dp_(db_.get(), &logger_), ant_(db_.get(), &logger_), grt_(&logger_, - &callback_handler_, + &service_registry_, &stt_, db_.get(), sta_.get(), &ant_, &dp_), - ep_(&logger_, &callback_handler_, db_.get(), sta_.get(), &stt_, &grt_), + ep_(&logger_, &service_registry_, db_.get(), sta_.get(), &stt_, &grt_), resizer_(&logger_, db_.get(), sta_.get(), &stt_, &grt_, &dp_, &ep_), test_root_path_(test_root_path) { diff --git a/src/utl/BUILD b/src/utl/BUILD index 335e51cf901..2c4e5028a95 100644 --- a/src/utl/BUILD +++ b/src/utl/BUILD @@ -21,14 +21,13 @@ cc_library( name = "utl", srcs = [ "src/CFileUtils.cpp", - "src/CallBack.cpp", - "src/CallBackHandler.cpp", "src/CommandLineProgress.cpp", "src/CommandLineProgress.h", "src/Logger.cpp", "src/Metrics.cpp", "src/Progress.cpp", "src/ScopedTemporaryFile.cpp", + "src/ServiceRegistry.cpp", "src/SuppressStdout.cpp", "src/decode.cpp", "src/histogram.cpp", @@ -39,12 +38,11 @@ cc_library( ], hdrs = [ "include/utl/CFileUtils.h", - "include/utl/CallBack.h", - "include/utl/CallBackHandler.h", "include/utl/Logger.h", "include/utl/Metrics.h", "include/utl/Progress.h", "include/utl/ScopedTemporaryFile.h", + "include/utl/ServiceRegistry.h", "include/utl/SuppressStdout.h", "include/utl/algorithms.h", "include/utl/decode.h", diff --git a/src/utl/CMakeLists.txt b/src/utl/CMakeLists.txt index 68aa72ccac1..df659dab54f 100644 --- a/src/utl/CMakeLists.txt +++ b/src/utl/CMakeLists.txt @@ -36,11 +36,10 @@ if (Python3_FOUND AND BUILD_PYTHON) endif() add_library(utl_lib - src/CallBackHandler.cpp - src/CallBack.cpp src/Metrics.cpp src/CFileUtils.cpp src/ScopedTemporaryFile.cpp + src/ServiceRegistry.cpp src/SuppressStdout.cpp src/Logger.cpp src/Progress.cpp diff --git a/src/utl/include/utl/CallBack.h b/src/utl/include/utl/CallBack.h deleted file mode 100644 index aee267903b5..00000000000 --- a/src/utl/include/utl/CallBack.h +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2025-2025, The OpenROAD Authors - -#pragma once - -namespace utl { -class CallBackHandler; - -/** - * @brief Base class for callbacks to enable inter-module communication. - * - * This class provides a standardized interface for modules to communicate - * with each other through callback mechanisms. Derived classes should - * implement specific callback methods as needed. - */ -class CallBack -{ - public: - virtual ~CallBack(); - - /** - * @brief Called when pin access needs to be updated. - * - * This callback is triggered when modules detect that access points need to - * be recalculated or updated. - */ - virtual void onPinAccessUpdateRequired() {} - - /** - * @brief Called when parasitic estimation is required. - * - * This callback is triggered when modules need to perform parasitic - * estimation for timing. - */ - virtual void onEstimateParasiticsRequired() {} - - void setOwner(CallBackHandler* owner); - CallBackHandler* getOwner() const { return owner_; } - void removeOwner(); - - private: - CallBackHandler* owner_{nullptr}; -}; - -} // namespace utl diff --git a/src/utl/include/utl/CallBackHandler.h b/src/utl/include/utl/CallBackHandler.h deleted file mode 100644 index dc14a4b21b0..00000000000 --- a/src/utl/include/utl/CallBackHandler.h +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2025-2025, The OpenROAD Authors - -#pragma once - -#include - -namespace utl { -class Logger; -class CallBack; - -/** - * @brief Handler class for managing callback registration and triggering. - * - * This class provides a centralized mechanism for modules to register - * callbacks and trigger them when specific events occur. - */ -class CallBackHandler -{ - public: - CallBackHandler(utl::Logger* logger); - ~CallBackHandler() = default; - - /** - * @brief Register a callback for event notifications. - * - * @param callback Pointer to the callback object to register - */ - void addCallBack(CallBack* callback); - - /** - * @brief Unregister a callback from event notifications. - * - * @param callback Pointer to the callback object to unregister - */ - void removeCallBack(CallBack* callback); - - /** - * @brief Trigger the onPinAccessUpdateRequired callback for all registered - * callbacks. - */ - void triggerOnPinAccessUpdateRequired(); - - /** - * @brief Trigger the onEstimateParasiticsRequired callback for all registered - * callbacks. - */ - void triggerOnEstimateParasiticsRequired(); - - private: - utl::Logger* logger_; - std::set callbacks_; -}; - -} // namespace utl diff --git a/src/utl/include/utl/ServiceRegistry.h b/src/utl/include/utl/ServiceRegistry.h new file mode 100644 index 00000000000..937ee653ce9 --- /dev/null +++ b/src/utl/include/utl/ServiceRegistry.h @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2026-2026, The OpenROAD Authors + +#pragma once + +#include +#include +#include + +namespace utl { + +class Logger; + +// A small type-indexed registry that lets modules publish abstract +// interfaces and lets consumers look them up without a shared base +// class or a hard link-time dependency on the provider. +// +// Each interface type may have at most one provider at a time; a +// duplicate provide() call is a programming error and is reported +// through the logger. +class ServiceRegistry +{ + public: + explicit ServiceRegistry(Logger* logger); + + template + void provide(I* impl) + { + provideImpl(std::type_index(typeid(I)), impl, typeid(I).name()); + } + + template + void withdraw(I* impl) + { + withdrawImpl(std::type_index(typeid(I)), impl); + } + + template + I* find() const + { + return static_cast(findImpl(std::type_index(typeid(I)))); + } + + template + I& require() const + { + return *static_cast( + requireImpl(std::type_index(typeid(I)), typeid(I).name())); + } + + private: + void provideImpl(std::type_index key, void* impl, const char* name); + void withdrawImpl(std::type_index key, void* impl); + void* findImpl(std::type_index key) const; + void* requireImpl(std::type_index key, const char* name) const; + + Logger* logger_; + std::unordered_map services_; +}; + +} // namespace utl diff --git a/src/utl/src/CallBack.cpp b/src/utl/src/CallBack.cpp deleted file mode 100644 index b7631aad1fd..00000000000 --- a/src/utl/src/CallBack.cpp +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2025-2025, The OpenROAD Authors - -#include "utl/CallBack.h" - -#include "utl/CallBackHandler.h" - -namespace utl { - -CallBack::~CallBack() -{ - removeOwner(); -} - -void CallBack::setOwner(CallBackHandler* owner) -{ - owner_ = owner; - owner_->addCallBack(this); -} - -void CallBack::removeOwner() -{ - if (owner_) { - owner_->removeCallBack(this); - owner_ = nullptr; - } -} - -} // namespace utl \ No newline at end of file diff --git a/src/utl/src/CallBackHandler.cpp b/src/utl/src/CallBackHandler.cpp deleted file mode 100644 index 9fcb9b82504..00000000000 --- a/src/utl/src/CallBackHandler.cpp +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2025-2025, The OpenROAD Authors - -#include "utl/CallBackHandler.h" - -#include "utl/CallBack.h" -#include "utl/Logger.h" - -namespace utl { - -CallBackHandler::CallBackHandler(utl::Logger* logger) : logger_(logger) -{ -} - -void CallBackHandler::addCallBack(CallBack* callback) -{ - if (callback == nullptr) { - logger_->error(utl::UTL, 200, "Registering null callback is not allowed"); - } - callbacks_.insert(callback); -} - -void CallBackHandler::removeCallBack(CallBack* callback) -{ - callbacks_.erase(callback); -} - -void CallBackHandler::triggerOnPinAccessUpdateRequired() -{ - for (CallBack* callback : callbacks_) { - callback->onPinAccessUpdateRequired(); - } -} - -void CallBackHandler::triggerOnEstimateParasiticsRequired() -{ - for (CallBack* callback : callbacks_) { - callback->onEstimateParasiticsRequired(); - } -} - -} // namespace utl \ No newline at end of file diff --git a/src/utl/src/ServiceRegistry.cpp b/src/utl/src/ServiceRegistry.cpp new file mode 100644 index 00000000000..80a5845b8bf --- /dev/null +++ b/src/utl/src/ServiceRegistry.cpp @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2026-2026, The OpenROAD Authors + +#include "utl/ServiceRegistry.h" + +#include + +#include "utl/Logger.h" + +namespace utl { + +ServiceRegistry::ServiceRegistry(Logger* logger) : logger_(logger) +{ +} + +void ServiceRegistry::provideImpl(std::type_index key, + void* impl, + const char* name) +{ + if (impl == nullptr) { + logger_->error( + UTL, 201, "Registering null service for {} is not allowed", name); + } + auto [it, inserted] = services_.emplace(key, impl); + if (!inserted) { + logger_->error(UTL, 202, "Service {} already has a provider", name); + } +} + +void ServiceRegistry::withdrawImpl(std::type_index key, void* impl) +{ + auto it = services_.find(key); + if (it != services_.end() && it->second == impl) { + services_.erase(it); + } +} + +void* ServiceRegistry::findImpl(std::type_index key) const +{ + auto it = services_.find(key); + return it == services_.end() ? nullptr : it->second; +} + +void* ServiceRegistry::requireImpl(std::type_index key, const char* name) const +{ + auto it = services_.find(key); + if (it == services_.end()) { + logger_->error(UTL, 203, "Required service {} has no provider", name); + } + return it->second; +} + +} // namespace utl diff --git a/src/utl/test/BUILD b/src/utl/test/BUILD index 3415dcec2b3..b3b1ec0d848 100644 --- a/src/utl/test/BUILD +++ b/src/utl/test/BUILD @@ -139,6 +139,16 @@ cc_test( ], ) +cc_test( + name = "TestServiceRegistry", + srcs = ["cpp/TestServiceRegistry.cpp"], + deps = [ + "//src/utl", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + py_test( name = "utl_man_tcl_check", srcs = ["utl_man_tcl_check.py"], diff --git a/src/utl/test/cpp/CMakeLists.txt b/src/utl/test/cpp/CMakeLists.txt index 44eb305aba2..d89ddebd241 100644 --- a/src/utl/test/cpp/CMakeLists.txt +++ b/src/utl/test/cpp/CMakeLists.txt @@ -55,3 +55,15 @@ gtest_discover_tests(TestSuppressStdout add_dependencies(build_and_test TestSuppressStdout ) + +add_executable(TestServiceRegistry TestServiceRegistry.cpp) + +target_link_libraries(TestServiceRegistry ${TEST_LIBS}) + +gtest_discover_tests(TestServiceRegistry + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} +) + +add_dependencies(build_and_test + TestServiceRegistry +) diff --git a/src/utl/test/cpp/TestServiceRegistry.cpp b/src/utl/test/cpp/TestServiceRegistry.cpp new file mode 100644 index 00000000000..bacccb9346c --- /dev/null +++ b/src/utl/test/cpp/TestServiceRegistry.cpp @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2026-2026, The OpenROAD Authors + +#include + +#include "gtest/gtest.h" +#include "utl/Logger.h" +#include "utl/ServiceRegistry.h" + +namespace utl { + +namespace { + +struct IFoo +{ + virtual ~IFoo() = default; + virtual int value() const = 0; +}; + +struct IBar +{ + virtual ~IBar() = default; + virtual int other() const = 0; +}; + +struct FooImpl : IFoo +{ + explicit FooImpl(int v) : v_(v) {} + int value() const override { return v_; } + int v_; +}; + +struct BarImpl : IBar +{ + int other() const override { return 7; } +}; + +} // namespace + +TEST(ServiceRegistry, FindReturnsNullWhenAbsent) +{ + Logger logger; + ServiceRegistry registry(&logger); + EXPECT_EQ(registry.find(), nullptr); +} + +TEST(ServiceRegistry, ProvideAndFind) +{ + Logger logger; + ServiceRegistry registry(&logger); + FooImpl foo(42); + registry.provide(&foo); + EXPECT_EQ(registry.find(), &foo); + EXPECT_EQ(registry.find()->value(), 42); +} + +TEST(ServiceRegistry, DifferentInterfacesAreIndependent) +{ + Logger logger; + ServiceRegistry registry(&logger); + FooImpl foo(1); + BarImpl bar; + registry.provide(&foo); + registry.provide(&bar); + EXPECT_EQ(registry.find(), &foo); + EXPECT_EQ(registry.find(), &bar); +} + +TEST(ServiceRegistry, RequireReturnsReference) +{ + Logger logger; + ServiceRegistry registry(&logger); + FooImpl foo(5); + registry.provide(&foo); + EXPECT_EQ(®istry.require(), &foo); +} + +TEST(ServiceRegistry, RequireErrorsWhenAbsent) +{ + Logger logger; + ServiceRegistry registry(&logger); + EXPECT_THROW(registry.require(), std::runtime_error); +} + +TEST(ServiceRegistry, DuplicateProvideErrors) +{ + Logger logger; + ServiceRegistry registry(&logger); + FooImpl a(1), b(2); + registry.provide(&a); + EXPECT_THROW(registry.provide(&b), std::runtime_error); +} + +TEST(ServiceRegistry, NullProvideErrors) +{ + Logger logger; + ServiceRegistry registry(&logger); + EXPECT_THROW(registry.provide(nullptr), std::runtime_error); +} + +TEST(ServiceRegistry, WithdrawRemovesOnlyMatchingProvider) +{ + Logger logger; + ServiceRegistry registry(&logger); + FooImpl a(1), b(2); + registry.provide(&a); + + // Withdraw with a non-matching pointer must be a no-op. + registry.withdraw(&b); + EXPECT_EQ(registry.find(), &a); + + registry.withdraw(&a); + EXPECT_EQ(registry.find(), nullptr); + + // After withdraw a new provider can register. + registry.provide(&b); + EXPECT_EQ(registry.find(), &b); +} + +TEST(ServiceRegistry, WithdrawUnknownIsNoop) +{ + Logger logger; + ServiceRegistry registry(&logger); + FooImpl a(1); + registry.withdraw(&a); // Nothing registered — must not throw. + SUCCEED(); +} + +} // namespace utl