diff --git a/README.md b/README.md index e6c26070b25..ed1f4c694cd 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ This repository contains a selection of curated plugins: - Detailed widgets with information on all aspects of the inspected netlist - **Netlist Simulator:** A simulator for arbitrary parts of a loaded netlist - **Dataflow Analysis:** Our dataflow analysis plugin [DANA](https://eprint.iacr.org/2020/751.pdf) that recovers high-level registers in an unstructured netlist +- **Clock Tree Extractor:** A plugin to recover clock trees from an unstructured gate-level netlist - **Graph Algorithms:** [igraph](https://igraph.org) integration for direct access to common algorithms from graph-theory - **Python Shell:** A command-line plugin to spawn a Python shell preloaded with the HAL Python bindings - **VHDL & Verilog Parsers:** Adds support for parsing VHDL and Verilog files as netlist input formats diff --git a/install_dependencies.sh b/install_dependencies.sh index 8e8be3264c0..caf94833453 100755 --- a/install_dependencies.sh +++ b/install_dependencies.sh @@ -115,6 +115,6 @@ elif [[ "$platform" == 'docker' ]]; then libqt5svg5-dev libqt5svg5* ninja-build lcov gcovr python3-sphinx \ doxygen python3-sphinx-rtd-theme python3-jedi python3-pip \ pybind11-dev python3-pybind11 python3-dateutil rapidjson-dev \ - libspdlog-dev libz3-dev libreadline-dev \ + libspdlog-dev libz3-dev libreadline-dev libgraphviz-dev \ graphviz libomp-dev libsuitesparse-dev # For documentation fi diff --git a/plugins/clock_tree_extractor/CMakeLists.txt b/plugins/clock_tree_extractor/CMakeLists.txt new file mode 100644 index 00000000000..1fccdf0e035 --- /dev/null +++ b/plugins/clock_tree_extractor/CMakeLists.txt @@ -0,0 +1,24 @@ +option(PL_CLOCK_TREE_EXTRACTOR "PL_CLOCK_TREE_EXTRACTOR" ON) + +if(PL_CLOCK_TREE_EXTRACTOR OR BUILD_ALL_PLUGINS) + + if(IWYU) + set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE "include-what-you-use") + message(STATUS "include-what-you-use turned ON") + else() + message(STATUS "include-what-you-use turned OFF") + endif() + + file(GLOB_RECURSE CLOCK_TREE_EXTRACTOR_INC ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h) + file(GLOB_RECURSE CLOCK_TREE_EXTRACTOR_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) + file(GLOB_RECURSE CLOCK_TREE_EXTRACTOR_PYTHON_SRC ${CMAKE_CURRENT_SOURCE_DIR}/python/*.cpp) + + hal_add_plugin(clock_tree_extractor + SHARED + HEADER ${CLOCK_TREE_EXTRACTOR_INC} + SOURCES ${CLOCK_TREE_EXTRACTOR_SRC} ${CLOCK_TREE_EXTRACTOR_PYTHON_SRC} + LINK_LIBRARIES graph_algorithm + COMPILE_OPTIONS "-march=native" + ) + +endif() diff --git a/plugins/clock_tree_extractor/include/clock_tree_extractor/clock_tree.h b/plugins/clock_tree_extractor/include/clock_tree_extractor/clock_tree.h new file mode 100644 index 00000000000..7cbe2655ef5 --- /dev/null +++ b/plugins/clock_tree_extractor/include/clock_tree_extractor/clock_tree.h @@ -0,0 +1,138 @@ +// MIT License +// +// Copyright (c) 2019 Ruhr University Bochum, Chair for Embedded Security. All Rights reserved. +// Copyright (c) 2019 Marc Fyrbiak, Sebastian Wallat, Max Hoffmann ("ORIGINAL AUTHORS"). All rights reserved. +// Copyright (c) 2021 Max Planck Institute for Security and Privacy. All Rights reserved. +// Copyright (c) 2021 Jörn Langheinrich, Julian Speith, Nils Albartus, René Walendy, Simon Klix ("ORIGINAL AUTHORS"). All Rights reserved. +// Copyright (c) 2025-2026 Sascha Tommasone. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once + +#include "graph_algorithm/netlist_graph.h" +#include "hal_core/defines.h" +#include "hal_core/utilities/result.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace hal +{ + class Netlist; +} + +namespace hal +{ + class Gate; +} + +namespace hal +{ + class Net; +} + +namespace hal +{ + namespace cte + { + enum PtrType { UNKNOWN, GATE, NET }; + + struct VoidPtrHash + { + std::size_t operator()( const std::pair &pair ) const noexcept + { + return std::hash()( pair.first ) ^ ( std::hash()( pair.second ) << 1 ); + } + }; + + struct PairPtrEq + { + bool operator()( const std::pair &p1, + const std::pair &p2 ) const noexcept + { + return p1.first == p2.first && p1.second == p2.second; + } + }; + + class ClockTree + { + public: + ClockTree( const Netlist *netlist, + igraph_t &&graph, + std::unordered_set &&roots, + std::unordered_map &&m_vertices_to_ptrs, + std::unordered_map &&m_ptrs_to_types ); + + ~ClockTree(); + + static Result> from_netlist( const Netlist *netlist ); + + Result export_dot( const std::string &pathname ) const; + + Result>> get_neighbors( const void *ptr, + igraph_neimode_t direction ) const; + + Result> get_subtree( const void *ptr, const bool parent ) const; + + Result get_vertex_from_ptr( const void *ptr ) const; + + Result> get_ptr_from_vertex( const igraph_integer_t vertex ) const; + + Result> get_vertices_from_ptrs( const std::vector &ptrs ) const; + + Result>> + get_ptrs_from_vertices( const std::vector &vertices ) const; + + const std::vector get_gates() const; + + const std::vector get_nets() const; + + const std::unordered_map get_all() const; + + const Netlist *get_netlist() const; + + const igraph_t *get_igraph() const; + + private: + ClockTree() = delete; + + ClockTree( const Netlist *netlist ); + + const Netlist *m_netlist; + + igraph_t m_igraph; + + igraph_t *m_igraph_ptr; + + std::unordered_set m_roots; + + std::unordered_map m_vertices_to_ptrs; + + std::unordered_map m_ptrs_to_vertices; + + std::unordered_map m_ptrs_to_types; + }; + } // namespace cte +} // namespace hal diff --git a/plugins/clock_tree_extractor/include/clock_tree_extractor/plugin_clock_tree_extractor.h b/plugins/clock_tree_extractor/include/clock_tree_extractor/plugin_clock_tree_extractor.h new file mode 100644 index 00000000000..4595755eba4 --- /dev/null +++ b/plugins/clock_tree_extractor/include/clock_tree_extractor/plugin_clock_tree_extractor.h @@ -0,0 +1,58 @@ +// MIT License +// +// Copyright (c) 2019 Ruhr University Bochum, Chair for Embedded Security. All Rights reserved. +// Copyright (c) 2019 Marc Fyrbiak, Sebastian Wallat, Max Hoffmann ("ORIGINAL AUTHORS"). All rights reserved. +// Copyright (c) 2021 Max Planck Institute for Security and Privacy. All Rights reserved. +// Copyright (c) 2021 Jörn Langheinrich, Julian Speith, Nils Albartus, René Walendy, Simon Klix ("ORIGINAL AUTHORS"). All Rights reserved. +// Copyright (c) 2025-2026 Sascha Tommasone. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once + +#include "hal_core/defines.h" +#include "hal_core/plugin_system/plugin_interface_base.h" + +#include +#include + +namespace hal +{ + class PLUGIN_API ClockTreeExtractorPlugin : public BasePluginInterface + { + public: + ClockTreeExtractorPlugin() = default; + + ~ClockTreeExtractorPlugin() = default; + + std::string get_name() const override; + + std::string get_version() const override; + + std::string get_description() const override; + + std::set get_dependencies() const override; + + void initialize() override; + + void on_load() override; + + void on_unload() override; + }; +} // namespace hal diff --git a/plugins/clock_tree_extractor/python/python_bindings.cpp b/plugins/clock_tree_extractor/python/python_bindings.cpp new file mode 100644 index 00000000000..a6e3ab8341e --- /dev/null +++ b/plugins/clock_tree_extractor/python/python_bindings.cpp @@ -0,0 +1,342 @@ +// MIT License +// +// Copyright (c) 2019 Ruhr University Bochum, Chair for Embedded Security. All Rights reserved. +// Copyright (c) 2019 Marc Fyrbiak, Sebastian Wallat, Max Hoffmann ("ORIGINAL AUTHORS"). All rights reserved. +// Copyright (c) 2021 Max Planck Institute for Security and Privacy. All Rights reserved. +// Copyright (c) 2021 Jörn Langheinrich, Julian Speith, Nils Albartus, René Walendy, Simon Klix ("ORIGINAL AUTHORS"). All Rights reserved. +// Copyright (c) 2025-2026 Sascha Tommasone. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "hal_core/python_bindings/python_bindings.h" + +#include "clock_tree_extractor/clock_tree.h" +#include "clock_tree_extractor/plugin_clock_tree_extractor.h" +#include "pybind11/pybind11.h" + +#include +#include +#include +#include +#include +#include + +namespace hal +{ + class BasePluginInterface; +} +namespace hal +{ + class Netlist; +} + +namespace py = pybind11; + +namespace hal +{ + + // the name in PYBIND11_MODULE/PYBIND11_PLUGIN *MUST* match the filename of the output library (without extension), + // otherwise you will get "ImportError: dynamic module does not define module export function" when importing the + // module + +#ifdef PYBIND11_MODULE + PYBIND11_MODULE( clock_tree_extractor, m ) + { + m.doc() = ""; +#else + PYBIND11_PLUGIN( clock_tree_extractor ) + { + py::module m( "clock_tree_extractor", "" ); +#endif // ifdef PYBIND11_MODULE + + py::class_, BasePluginInterface> + py_clock_tree_extractor_plugin( m, "ClockTreeExtractorPlugin", "" ); + + py_clock_tree_extractor_plugin.def_property_readonly( "name", &ClockTreeExtractorPlugin::get_name, R"( + The name of the plugin. + + :type: str + )" ); + + py_clock_tree_extractor_plugin.def( "get_name", &ClockTreeExtractorPlugin::get_name, R"( + Get the name of the plugin. + + :returns: The name of the plugin. + :rtype: str + )" ); + + py_clock_tree_extractor_plugin.def_property_readonly( "version", &ClockTreeExtractorPlugin::get_version, R"( + The version of the plugin. + + :type: str + )" ); + + py_clock_tree_extractor_plugin.def( "get_version", &ClockTreeExtractorPlugin::get_version, R"( + Get the version of the plugin. + + :returns: The version of the plugin. + :rtype: str + )" ); + + py_clock_tree_extractor_plugin.def_property_readonly( + "description", &ClockTreeExtractorPlugin::get_description, R"( + The description of the plugin. + + :type: str + )" ); + + py_clock_tree_extractor_plugin.def( "get_description", &ClockTreeExtractorPlugin::get_description, R"( + Get the description of the plugin. + + :returns: The description of the plugin. + :rtype: str + )" ); + + py_clock_tree_extractor_plugin.def_property_readonly( + "dependencies", &ClockTreeExtractorPlugin::get_dependencies, R"( + A set of plugin names that this plugin depends on. + + :type: set[str] + )" ); + + py_clock_tree_extractor_plugin.def( "get_dependencies", &ClockTreeExtractorPlugin::get_dependencies, R"( + Get a set of plugin names that this plugin depends on. + + :returns: A set of plugin names that this plugin depends on. + :rtype: set[str] + )" ); + + py::class_( m, "ClockTree", R"()" ) + .def_static( + "from_netlist", + []( const Netlist *netlist ) -> std::unique_ptr { + auto result = cte::ClockTree::from_netlist( netlist ); + if( result.is_ok() ) + { + return result.get(); + } + + log_error( "clock_tree_extractor", "{}", result.get_error().get() ); + return nullptr; + }, + py::arg( "netlist" ), + py::return_value_policy::move, + R"()" ) + .def( + "export", + []( const cte::ClockTree &self, const std::string &pathname ) -> bool { + auto result = self.export_dot( pathname ); + if( result.is_ok() ) + { + return true; + } + + log_error( "clock_tree_extractor", "{}", result.get_error().get() ); + return false; + }, + py::arg( "pathname" ), + R"()" ) + .def( + "get_subtree", + []( const cte::ClockTree &self, + const void *ptr, + const bool parent ) -> std::unique_ptr { + auto result = self.get_subtree( ptr, parent ); + if( result.is_ok() ) + { + return result.get(); + } + + log_error( "clock_tree_extractor", "{}", result.get_error().get() ); + return nullptr; + }, + py::arg( "ptr" ), + py::arg( "parent" ) = false, + py::return_value_policy::move, + R"()" ) + .def( + "get_all", + []( const cte::ClockTree &self ) -> py::list { + py::list result; + const auto &map = self.get_all(); + for( auto &[ptr, type] : map ) + { + if( type == cte::PtrType::GATE ) + { + result.append( py::cast( (const Gate *) ptr ) ); + } + else if( type == cte::PtrType::NET ) + { + result.append( py::cast( (const Net *) ptr ) ); + } + } + return result; + }, + borrowed(), + R"()" ) + .def( + "get_vertex_from_ptr", + []( const cte::ClockTree &self, const void *ptr ) -> py::object { + auto result = self.get_vertex_from_ptr( ptr ); + if( result.is_ok() ) + { + return py::int_( result.get() ); + } + log_error( "clock_tree_extractor", "{}", result.get_error().get() ); + return py::none(); + }, + py::arg( "ptr" ), + R"()" ) + .def( + "get_ptr_from_vertex", + []( const cte::ClockTree &self, const igraph_integer_t vertex ) -> py::object { + auto result = self.get_ptr_from_vertex( vertex ); + if( result.is_ok() ) + { + auto [ptr, type] = result.get(); + if( type == cte::PtrType::GATE ) + { + return py::cast( (const Gate *) ptr ); + } + else if( type == cte::PtrType::NET ) + { + return py::cast( (const Net *) ptr ); + } + return py::none(); + } + log_error( "clock_tree_extractor", "{}", result.get_error().get() ); + return py::none(); + }, + py::arg( "vertex" ), + borrowed(), + R"()" ) + .def( + "get_vertices_from_ptrs", + []( const cte::ClockTree &self, const std::vector &ptrs ) -> py::list { + auto result = self.get_vertices_from_ptrs( ptrs ); + if( result.is_ok() ) + { + return py::cast( result.get() ); + } + log_error( "clock_tree_extractor", "{}", result.get_error().get() ); + return py::none(); + }, + py::arg( "ptrs" ), + R"()" ) + .def( + "get_ptrs_from_vertices", + []( const cte::ClockTree &self, const std::vector &vertices ) -> py::list { + auto res = self.get_ptrs_from_vertices( vertices ); + if( res.is_ok() ) + { + py::list result; + for( const auto &[ptr, type] : res.get() ) + { + if( type == cte::PtrType::GATE ) + { + result.append( py::cast( (const Gate *) ptr ) ); + } + else if( type == cte::PtrType::NET ) + { + result.append( py::cast( (const Net *) ptr ) ); + } + else + { + log_error( "clock_tree_extractor", "unknown ptr type" ); + return py::none(); + } + } + return result; + } + log_error( "clock_tree_extractor", "{}", res.get_error().get() ); + return py::none(); + }, + py::arg( "vertices" ), + R"()" ) + .def( + "get_parents", + []( const cte::ClockTree &self, const void *ptr ) -> py::list { + auto res = self.get_neighbors( ptr, IGRAPH_IN ); + if( res.is_ok() ) + { + py::list result; + for( const auto &[ptr, type] : res.get() ) + { + if( type == cte::PtrType::GATE ) + { + result.append( py::cast( (const Gate *) ptr ) ); + } + else if( type == cte::PtrType::NET ) + { + result.append( py::cast( (const Net *) ptr ) ); + } + else + { + log_error( "clock_tree_extractor", "unknown ptr type" ); + return py::none(); + } + } + return result; + } + log_error( "clock_tree_extractor", "{}", res.get_error().get() ); + return py::none(); + }, + py::arg( "ptr" ), + borrowed(), + R"()" ) + .def( + "get_childs", + []( const cte::ClockTree &self, const void *ptr ) -> py::list { + auto res = self.get_neighbors( ptr, IGRAPH_OUT ); + if( res.is_ok() ) + { + py::list result; + for( const auto &[ptr, type] : res.get() ) + { + if( type == cte::PtrType::GATE ) + { + result.append( py::cast( (const Gate *) ptr ) ); + } + else if( type == cte::PtrType::NET ) + { + result.append( py::cast( (const Net *) ptr ) ); + } + else + { + log_error( "clock_tree_extractor", "unknown ptr type" ); + return py::none(); + } + } + return result; + } + log_error( "clock_tree_extractor", "{}", res.get_error().get() ); + return py::none(); + }, + py::arg( "ptr" ), + borrowed(), + R"()" ) + .def( "get_gates", &cte::ClockTree::get_gates, borrowed(), R"()" ) + .def( "get_nets", &cte::ClockTree::get_nets, borrowed(), R"()" ) + .def( "get_netlist", &cte::ClockTree::get_netlist, borrowed(), R"()" ); + +#ifndef PYBIND11_MODULE + return m.ptr(); +#endif // PYBIND11_MODULE + } +} // namespace hal diff --git a/plugins/clock_tree_extractor/src/clock_tree.cpp b/plugins/clock_tree_extractor/src/clock_tree.cpp new file mode 100644 index 00000000000..182846ea55d --- /dev/null +++ b/plugins/clock_tree_extractor/src/clock_tree.cpp @@ -0,0 +1,839 @@ +// MIT License +// +// Copyright (c) 2019 Ruhr University Bochum, Chair for Embedded Security. All Rights reserved. +// Copyright (c) 2019 Marc Fyrbiak, Sebastian Wallat, Max Hoffmann ("ORIGINAL AUTHORS"). All rights reserved. +// Copyright (c) 2021 Max Planck Institute for Security and Privacy. All Rights reserved. +// Copyright (c) 2021 Jörn Langheinrich, Julian Speith, Nils Albartus, René Walendy, Simon Klix ("ORIGINAL AUTHORS"). All Rights reserved. +// Copyright (c) 2025-2026 Sascha Tommasone. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "clock_tree_extractor/clock_tree.h" + +#include "hal_core/netlist/decorators/netlist_traversal_decorator.h" +#include "hal_core/netlist/endpoint.h" +#include "hal_core/netlist/gate.h" +#include "hal_core/netlist/gate_library/enums/gate_type_property.h" +#include "hal_core/netlist/gate_library/enums/pin_direction.h" +#include "hal_core/netlist/gate_library/enums/pin_type.h" +#include "hal_core/netlist/gate_library/gate_type.h" +#include "hal_core/netlist/net.h" +#include "hal_core/netlist/netlist.h" +#include "hal_core/utilities/log.h" +#include "hal_core/utilities/result.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace hal +{ + namespace cte + { + namespace + { + inline bool is_ff( const Gate *gate ) + { + return gate->get_type()->has_property( GateTypeProperty::ff ); + } + + inline bool is_latch( const Gate *gate ) + { + return gate->get_type()->has_property( GateTypeProperty::latch ); + } + + inline bool is_buffer( const Gate *gate ) + { + return gate->get_type()->has_property( GateTypeProperty::c_buffer ); + } + + inline bool is_inverter( const Gate *gate ) + { + return gate->get_type()->has_property( GateTypeProperty::c_inverter ); + } + + inline bool is_delay( const Gate *gate ) + { + return gate->get_type()->has_property( GateTypeProperty::delay ); + } + + inline bool is_control_pin( const PinType &pin_type ) + { + return pin_type == PinType::clock || pin_type == PinType::enable || pin_type == PinType::select + || pin_type == PinType::set || pin_type == PinType::reset; + } + + inline bool is_connected_to_control_pin( const Endpoint *endpoint ) + { + return is_control_pin( endpoint->get_pin()->get_type() ); + } + + const std::unordered_set get_toggle_ffs( const Netlist *netlist ) + { + const std::vector ffs = netlist->get_gates( is_ff ); + + std::unordered_set result; + for( const Gate *ff : ffs ) + { + const std::vector successor_endpoints = ff->get_successors(); + const std::size_t successor_endpoints_size = successor_endpoints.size(); + + if( successor_endpoints_size == 0 ) + { + continue; + } + + std::vector successors; + successors.reserve( successor_endpoints_size ); + + std::transform( successor_endpoints.begin(), + successor_endpoints.end(), + std::back_inserter( successors ), + []( const Endpoint *ep ) { return ep->get_gate(); } ); + + if( std::find( successors.begin(), successors.end(), ff ) != successors.end() ) + { + result.insert( ff ); + } + } + + return result; + } + + igraph_error_t + in_callback( const igraph_t *graph, igraph_integer_t vid, igraph_integer_t dist, void *extra ) + { + return igraph_vector_int_push_back( (igraph_vector_int_t *) extra, vid ); + } + } // namespace + + ClockTree::ClockTree( const Netlist *netlist ) + : m_netlist( netlist ) + , m_igraph_ptr( &m_igraph ) + { + } + + ClockTree::ClockTree( const Netlist *netlist, + igraph_t &&igraph, + std::unordered_set &&roots, + std::unordered_map &&vertices_to_ptrs, + std::unordered_map &&ptrs_to_types ) + : m_netlist( netlist ) + , m_igraph( std::move( igraph ) ) + , m_roots( std::move( roots ) ) + , m_vertices_to_ptrs( std::move( vertices_to_ptrs ) ) + , m_ptrs_to_types( std::move( ptrs_to_types ) ) + { + m_igraph_ptr = &m_igraph; + + for( const auto &[vertex, ptr] : m_vertices_to_ptrs ) + { + m_ptrs_to_vertices[ptr] = vertex; + } + } + + ClockTree::~ClockTree() + { + igraph_destroy( &m_igraph ); + } + + Result> ClockTree::from_netlist( const Netlist *netlist ) + { + if( netlist == nullptr ) + { + return ERR( "no netlist provided" ); + } + + std::unordered_set vertices; + std::unordered_set, VoidPtrHash> edges; + std::unordered_map ptrs_to_type; + + std::queue>> queue; + NetlistTraversalDecorator ntd = NetlistTraversalDecorator( *netlist ); + std::unordered_set, VoidPtrHash> visited; + + for( const Gate *ff : netlist->get_gates( is_ff ) ) + { + vertices.insert( (void *) ff ); + ptrs_to_type[(void *) ff] = PtrType::GATE; + + const std::vector clock_pins = ff->get_type()->get_pins( []( const auto &p ) { + return ( p->get_direction() == PinDirection::input ) && ( p->get_type() == PinType::clock ); + } ); + + if( clock_pins.size() != 1 ) + { + log_error( "clock_tree_extractor", + "invalid number of input clock pins at gate '" + ff->get_name() + "' with ID " + + std::to_string( ff->get_id() ) ); + continue; + } + + const Net *clk = ff->get_fan_in_net( clock_pins.front() ); + if( clk == nullptr ) + { + log_error( "clock_tree_extractor", + "no net connected to clock pin at gate '" + ff->get_name() + "' with ID " + + std::to_string( ff->get_id() ) ); + continue; + } + + if( clk->get_num_of_sources() > 1 ) + { + bool valid = true; + for( const Endpoint *source_ep : clk->get_sources() ) + { + const Gate *gate = source_ep->get_gate(); + if( !( is_buffer( gate ) || is_inverter( gate ) ) ) + { + // In theory, it should be either all buffers or all inverters. But depending on + // extraction results, e.g., it could happen that a buffer is split into two inverters. + // So I just assume it is fine if all the sources are either a buffer or an inverter + // without enforcing strict buffer only or inverter only. + valid = false; + break; + } + } + if( !valid ) + { + log_error( "clock_tree_extractor", + "invalid number of sources for clock net with ID " + + std::to_string( clk->get_id() ) ); + continue; + } + } + else if( clk->is_global_input_net() ) + { + vertices.insert( (void *) clk ); + ptrs_to_type[(void *) clk] = PtrType::NET; + continue; + } + else if( clk->get_num_of_sources() == 0 ) + { + log_warning( "clock_tree_extractor", + "unrouted clock net with ID {} ignored", + std::to_string( clk->get_id() ) ); + continue; + } + + for( const Endpoint *source_ep : clk->get_sources() ) + { + const Gate *gate = source_ep->get_gate(); + queue.push( { ff, gate, std::vector{ ff } } ); + } + } + + const std::unordered_set toggle_ffs = get_toggle_ffs( netlist ); + + while( !queue.empty() ) + { + const std::tuple> tuple = queue.front(); + queue.pop(); + + Gate *source = (Gate *) std::get<1>( tuple ); + Gate *reference = (Gate *) std::get<0>( tuple ); + std::vector path = std::get<2>( tuple ); + + path.push_back( source ); + + if( is_latch( source ) ) + { + // Ignore latches + continue; + } + else if( is_buffer( source ) || is_inverter( source ) || is_delay( source ) || is_ff( source ) ) + { + if( is_ff( source ) && toggle_ffs.find( source ) == toggle_ffs.end() ) + { + // Include only toggle flip-flops for now + continue; + } + + for( const Gate *gate : path ) + { + vertices.insert( (void *) gate ); + ptrs_to_type[(void *) gate] = PtrType::GATE; + } + + for( u32 idx = 0; idx < path.size() - 1; idx++ ) + { + edges.insert( { (void *) path[idx + 1], (void *) path[idx] } ); + } + + path.clear(); + path.push_back( source ); + + if( is_ff( source ) ) + { + continue; + } + + reference = (Gate *) source; + } + + visited.insert( std::make_pair( reference, source ) ); + + for( const Endpoint *ep : source->get_fan_in_endpoints() ) + { + if( is_connected_to_control_pin( ep ) ) + { + // Don't traverse control signals of clock gates + continue; + } + + const Net *net = ep->get_net(); + if( net->get_name() == "'0'" || net->get_name() == "'1'" ) + { + // Don't traverse power/ground signals + continue; + } + + if( net->is_global_input_net() ) + { + for( const Gate *gate : path ) + { + vertices.insert( (void *) gate ); + ptrs_to_type[(void *) gate] = PtrType::GATE; + } + + for( u32 idx = 0; idx < path.size() - 1; idx++ ) + { + edges.insert( { (void *) path[idx + 1], (void *) path[idx] } ); + } + + vertices.insert( (void *) net ); + + ptrs_to_type[(void *) net] = PtrType::NET; + + edges.insert( { (void *) net, (void *) path.back() } ); + + path.clear(); + path.push_back( source ); + + continue; + } + + if( net->get_num_of_sources() == 0 ) + { + log_warning( "clock_tree_extractor", + "unrouted clock net with ID {} ignored", + std::to_string( net->get_id() ) ); + continue; + } + else if( net->get_num_of_sources() > 1 ) + { + log_warning( "clock_tree_extractor", + "multi-driven clock net with ID {} ignored", + std::to_string( net->get_id() ) ); + continue; + } + + const Gate *new_source = net->get_sources().front()->get_gate(); + if( visited.find( { reference, new_source } ) == visited.end() ) + { + queue.push( { reference, new_source, path } ); + } + } + } + + std::unique_ptr clock_tree = std::unique_ptr( new ClockTree( netlist ) ); + + igraph_integer_t idx = 0; + for( const void *vertex : vertices ) + { + const igraph_integer_t vertex_id = idx++; + + clock_tree->m_vertices_to_ptrs[vertex_id] = vertex; + clock_tree->m_ptrs_to_vertices[vertex] = vertex_id; + } + + clock_tree->m_ptrs_to_types = ptrs_to_type; + + igraph_error_t ierror; + igraph_vector_int_t iedges; + if( ( ierror = igraph_vector_int_init( &iedges, 2 * edges.size() ) ) != IGRAPH_SUCCESS ) + { + return ERR( igraph_strerror( ierror ) ); + } + + idx = 0; + for( const auto &[src, dst] : edges ) + { + VECTOR( iedges )[idx++] = clock_tree->m_ptrs_to_vertices.at( src ); + VECTOR( iedges )[idx++] = clock_tree->m_ptrs_to_vertices.at( dst ); + } + + if( ( ierror = igraph_create( clock_tree->m_igraph_ptr, &iedges, vertices.size(), IGRAPH_DIRECTED ) ) + != IGRAPH_SUCCESS ) + { + igraph_vector_int_destroy( &iedges ); + return ERR( igraph_strerror( ierror ) ); + } + + igraph_vector_int_destroy( &iedges ); + + igraph_vector_int_t indegrees; + if( ( ierror = igraph_vector_int_init( &indegrees, 0 ) ) != IGRAPH_SUCCESS ) + { + return ERR( igraph_strerror( ierror ) ); + } + + if( ( ierror = igraph_degree( + clock_tree->m_igraph_ptr, &indegrees, igraph_vss_all(), IGRAPH_IN, IGRAPH_NO_LOOPS ) ) + != IGRAPH_SUCCESS ) + { + igraph_vector_int_destroy( &indegrees ); + return ERR( igraph_strerror( ierror ) ); + } + + for( idx = 0; idx < igraph_vector_int_size( &indegrees ); idx++ ) + { + if( VECTOR( indegrees )[idx] != 0 ) + { + continue; + } + clock_tree->m_roots.insert( idx ); + } + + igraph_vector_int_destroy( &indegrees ); + + return OK( std::move( clock_tree ) ); + } + + Result ClockTree::export_dot( const std::string &pathname ) const + { + std::ofstream dot_fd( pathname ); + + if( !dot_fd ) + { + return ERR( "couldn't export clock tree to '" + pathname + "'" ); + } + + dot_fd << "digraph { comment=\"created by HAL plugin clock_tree_extractor\"\n"; + + for( const auto &[ptr, vertex] : m_ptrs_to_vertices ) + { + if( m_ptrs_to_types.at( ptr ) == PtrType::NET ) + { + dot_fd << " " << ( (Net *) ptr )->get_name() << " [shape=circle];\n"; + continue; + } + + const Gate *gate = (const Gate *) ptr; + + std::string coords = ""; + + // Workaround for negative coordinates + + // const i32 x = gate->get_location_x(); + // const i32 y = gate->get_location_y(); + + try + { + const i32 x = std::stoi( std::get<1>( gate->get_data( "generic", "X" ) ) ); + const i32 y = std::stoi( std::get<1>( gate->get_data( "generic", "Y" ) ) ); + coords = " x=" + std::to_string( x ) + " y=" + std::to_string( y ); + } catch( const std::invalid_argument &err ) + { + log_error( "clock_tree_extractor", "invalid coordinate format: {}", err.what() ); + } + + std::string shape = "shape=hexagon"; // default (clock gates) + + if( is_buffer( gate ) ) + { + shape = "shape=rectangle"; + } + else if( is_inverter( gate ) ) + { + shape = "shape=triangle orientation=180"; + } + else if( is_ff( gate ) ) + { + shape = ""; // no shape + } + else if( is_delay( gate ) ) + { + shape = "shape=square"; + } + + dot_fd << " " << gate->get_id() << " [instance=\"" << gate->get_name() << "\" type=\"" + << gate->get_type()->get_name() << "\"" << coords; + + if( !shape.empty() ) + { + dot_fd << " " << shape; + } + + dot_fd << "];\n"; + } + + std::queue> queue; + for( const igraph_integer_t &root : m_roots ) + { + queue.push( { root, "blue" } ); + } + + igraph_error_t ierror; + std::unordered_set visited; + while( !queue.empty() ) + { + const std::pair pair = queue.front(); + queue.pop(); + + const igraph_integer_t vertex = pair.first; + std::string edge_color = pair.second; + + if( visited.find( vertex ) != visited.end() ) + { + continue; + } + + visited.insert( vertex ); + + const void *sptr = m_vertices_to_ptrs.at( vertex ); + const PtrType stype = m_ptrs_to_types.at( sptr ); + + if( stype == PtrType::GATE && is_inverter( (Gate *) sptr ) ) + { + edge_color = edge_color == "red" ? "blue" : "red"; + } + + igraph_vector_int_t neighbors; + if( ( ierror = igraph_vector_int_init( &neighbors, 0 ) ) != IGRAPH_SUCCESS ) + { + dot_fd.close(); + return ERR( igraph_strerror( ierror ) ); + } + + if( ( ierror = igraph_neighbors( + m_igraph_ptr, &neighbors, vertex, IGRAPH_OUT, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE ) ) + != IGRAPH_SUCCESS ) + { + dot_fd.close(); + igraph_vector_int_destroy( &neighbors ); + return ERR( igraph_strerror( ierror ) ); + } + + for( igraph_integer_t idx = 0; idx < igraph_vector_int_size( &neighbors ); idx++ ) + { + const std::string src_id = stype == PtrType::GATE ? std::to_string( ( (Gate *) sptr )->get_id() ) + : ( (Net *) sptr )->get_name(); + + const void *dptr = m_vertices_to_ptrs.at( VECTOR( neighbors )[idx] ); + const PtrType dtype = m_ptrs_to_types.at( dptr ); + const std::string dst_id = dtype == PtrType::GATE ? std::to_string( ( (Gate *) dptr )->get_id() ) + : ( (Net *) dptr )->get_name(); + + dot_fd << " " << src_id << " -> " << dst_id << " [color=" << edge_color << "];\n"; + queue.push( { VECTOR( neighbors )[idx], edge_color } ); + } + + igraph_vector_int_destroy( &neighbors ); + } + + dot_fd << "}\n"; + dot_fd.close(); + + return OK( {} ); + } + + Result> ClockTree::get_subtree( const void *ptr, const bool parent ) const + { + auto it = m_ptrs_to_vertices.find( ptr ); + if( it == m_ptrs_to_vertices.end() ) + { + return ERR( "object is not part of clock tree" ); + } + + igraph_error_t ierror; + igraph_integer_t root = it->second; + if( parent ) + { + igraph_vector_int_t parents; + if( ( ierror = igraph_vector_int_init( &parents, 0 ) ) != IGRAPH_SUCCESS ) + { + return ERR( igraph_strerror( ierror ) ); + } + + if( ( ierror = igraph_neighbors( + m_igraph_ptr, &parents, root, IGRAPH_IN, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE ) ) + != IGRAPH_SUCCESS ) + { + igraph_vector_int_destroy( &parents ); + return ERR( igraph_strerror( ierror ) ); + } + + // Only accept, if there is only one parent vertex for now. + if( igraph_vector_int_size( &parents ) == 1 ) + { + root = VECTOR( parents )[0]; + } + + igraph_vector_int_destroy( &parents ); + } + + igraph_vector_int_t vertices; + if( ( ierror = igraph_vector_int_init( &vertices, 0 ) ) != IGRAPH_SUCCESS ) + { + return ERR( igraph_strerror( ierror ) ); + } + + if( ( ierror = igraph_dfs( m_igraph_ptr, + root, + IGRAPH_OUT, + false, + nullptr, + nullptr, + nullptr, + nullptr, + in_callback, + nullptr, + &vertices ) ) + != IGRAPH_SUCCESS ) + { + igraph_vector_int_destroy( &vertices ); + return ERR( igraph_strerror( ierror ) ); + } + + igraph_vs_t vs; + if( ( ierror = igraph_vs_vector( &vs, &vertices ) ) != IGRAPH_SUCCESS ) + { + igraph_vector_int_destroy( &vertices ); + return ERR( igraph_strerror( ierror ) ); + } + + igraph_vector_int_t map; + if( ( ierror = igraph_vector_int_init( &map, igraph_vcount( m_igraph_ptr ) ) ) != IGRAPH_SUCCESS ) + { + return ERR( igraph_strerror( ierror ) ); + } + + igraph_t igraph; + if( ( ierror = + igraph_induced_subgraph_map( m_igraph_ptr, &igraph, vs, IGRAPH_SUBGRAPH_AUTO, &map, nullptr ) ) + != IGRAPH_SUCCESS ) + { + igraph_vs_destroy( &vs ); + igraph_vector_int_destroy( &map ); + igraph_vector_int_destroy( &vertices ); + return ERR( igraph_strerror( ierror ) ); + } + + igraph_vs_destroy( &vs ); + igraph_vector_int_destroy( &vertices ); + + std::unordered_set roots; + std::unordered_map ptrs_to_types; + std::unordered_map vertices_to_ptrs; + + for( igraph_integer_t idx = 0; idx < igraph_vector_int_size( &map ); idx++ ) + { + const igraph_integer_t vertex = VECTOR( map )[idx]; + if( vertex == 0 ) + { + continue; + } + + const void *ptr = m_vertices_to_ptrs.at( idx ); + + vertices_to_ptrs[vertex - 1] = ptr; + ptrs_to_types[ptr] = m_ptrs_to_types.at( ptr ); + } + + igraph_vector_int_destroy( &map ); + + igraph_vector_int_t indegrees; + if( ( ierror = igraph_vector_int_init( &indegrees, igraph_vcount( &igraph ) ) ) != IGRAPH_SUCCESS ) + { + return ERR( igraph_strerror( ierror ) ); + } + + if( ( ierror = igraph_degree( &igraph, &indegrees, igraph_vss_all(), IGRAPH_IN, IGRAPH_NO_LOOPS ) ) + != IGRAPH_SUCCESS ) + { + igraph_vector_int_destroy( &indegrees ); + return ERR( igraph_strerror( ierror ) ); + } + + for( igraph_integer_t idx = 0; idx < igraph_vector_int_size( &indegrees ); idx++ ) + { + if( VECTOR( indegrees )[idx] != 0 ) + { + continue; + } + roots.insert( idx ); + } + + igraph_vector_int_destroy( &indegrees ); + + return OK( std::make_unique( m_netlist, + std::move( igraph ), + std::move( roots ), + std::move( vertices_to_ptrs ), + std::move( ptrs_to_types ) ) ); + } + + Result ClockTree::get_vertex_from_ptr( const void *ptr ) const + { + auto it = m_ptrs_to_vertices.find( ptr ); + if( it == m_ptrs_to_vertices.end() ) + { + return ERR( "object is not part of clock tree" ); + } + + return OK( it->second ); + } + + Result> ClockTree::get_ptr_from_vertex( const igraph_integer_t vertex ) const + { + auto it = m_vertices_to_ptrs.find( vertex ); + if( it == m_vertices_to_ptrs.end() ) + { + return ERR( "object is not part of clock tree" ); + } + + return OK( std::make_pair( it->second, m_ptrs_to_types.at( it->second ) ) ); + } + + Result> + ClockTree::get_vertices_from_ptrs( const std::vector &ptrs ) const + { + std::vector result; + + for( const void *ptr : ptrs ) + { + auto res = get_vertex_from_ptr( ptr ); + if( res.is_error() ) + { + return ERR( res.get_error().get() ); + } + + result.push_back( res.get() ); + } + + return OK( result ); + } + + Result>> + ClockTree::get_ptrs_from_vertices( const std::vector &vertices ) const + { + std::vector> result; + + for( const igraph_integer_t vertex : vertices ) + { + auto res = get_ptr_from_vertex( vertex ); + if( res.is_error() ) + { + return ERR( res.get_error().get() ); + } + + result.push_back( res.get() ); + } + + return OK( result ); + } + + const std::vector ClockTree::get_gates() const + { + std::vector result; + + for( const auto &[ptr, type] : m_ptrs_to_types ) + { + if( type == PtrType::GATE ) + { + result.push_back( (const Gate *) ptr ); + } + } + + return result; + } + + const std::vector ClockTree::get_nets() const + { + std::vector result; + + for( const auto &[ptr, type] : m_ptrs_to_types ) + { + if( type == PtrType::NET ) + { + result.push_back( (const Net *) ptr ); + } + } + + return result; + } + + const std::unordered_map ClockTree::get_all() const + { + return m_ptrs_to_types; + } + + const Netlist *ClockTree::get_netlist() const + { + return m_netlist; + } + + const igraph_t *ClockTree::get_igraph() const + { + return m_igraph_ptr; + } + + Result>> + ClockTree::get_neighbors( const void *ptr, igraph_neimode_t direction ) const + { + auto it = m_ptrs_to_vertices.find( ptr ); + if( it == m_ptrs_to_vertices.end() ) + { + return ERR( "object is not part of clock tree" ); + } + + igraph_error_t ierror; + igraph_vector_int_t neighbors; + + if( ( ierror = igraph_vector_int_init( &neighbors, 0 ) ) != IGRAPH_SUCCESS ) + { + return ERR( igraph_strerror( ierror ) ); + } + + if( ( ierror = igraph_neighbors( + m_igraph_ptr, &neighbors, it->second, direction, IGRAPH_NO_LOOPS, IGRAPH_NO_MULTIPLE ) ) + != IGRAPH_SUCCESS ) + { + igraph_vector_int_destroy( &neighbors ); + return ERR( igraph_strerror( ierror ) ); + } + + std::vector> result; + for( igraph_integer_t idx = 0; idx < igraph_vector_int_size( &neighbors ); idx++ ) + { + const void *n_ptr = m_vertices_to_ptrs.at( VECTOR( neighbors )[idx] ); + result.push_back( std::make_pair( n_ptr, m_ptrs_to_types.at( n_ptr ) ) ); + } + + igraph_vector_int_destroy( &neighbors ); + + return OK( result ); + } + } // namespace cte +} // namespace hal diff --git a/plugins/clock_tree_extractor/src/plugin_clock_tree_extractor.cpp b/plugins/clock_tree_extractor/src/plugin_clock_tree_extractor.cpp new file mode 100644 index 00000000000..e3d2457b5a7 --- /dev/null +++ b/plugins/clock_tree_extractor/src/plugin_clock_tree_extractor.cpp @@ -0,0 +1,68 @@ +// MIT License +// +// Copyright (c) 2019 Ruhr University Bochum, Chair for Embedded Security. All Rights reserved. +// Copyright (c) 2019 Marc Fyrbiak, Sebastian Wallat, Max Hoffmann ("ORIGINAL AUTHORS"). All rights reserved. +// Copyright (c) 2021 Max Planck Institute for Security and Privacy. All Rights reserved. +// Copyright (c) 2021 Jörn Langheinrich, Julian Speith, Nils Albartus, René Walendy, Simon Klix ("ORIGINAL AUTHORS"). All Rights reserved. +// Copyright (c) 2025-2026 Sascha Tommasone. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "clock_tree_extractor/plugin_clock_tree_extractor.h" + +namespace hal +{ + extern std::unique_ptr create_plugin_instance() + { + return std::make_unique(); + } + + std::string ClockTreeExtractorPlugin::get_name() const + { + return std::string( "clock_tree_extractor" ); + } + + std::string ClockTreeExtractorPlugin::get_version() const + { + return std::string( "0.1" ); + } + + std::string ClockTreeExtractorPlugin::get_description() const + { + return "Prototype plugin for extracting and visualizing the clock tree of a digital gate-level netlist."; + } + + void ClockTreeExtractorPlugin::on_load() + { + } + + void ClockTreeExtractorPlugin::on_unload() + { + } + + void ClockTreeExtractorPlugin::initialize() + { + } + + std::set ClockTreeExtractorPlugin::get_dependencies() const + { + std::set retval; + return retval; + } +} // namespace hal