From 6c8eb55a8e0b51aaf9f196354ca94468c0b75cf2 Mon Sep 17 00:00:00 2001 From: "julian.speith" Date: Wed, 10 Jul 2024 13:07:10 +0200 Subject: [PATCH 01/17] cleanup and new load_netlist function that takes gate library pointer --- include/hal_core/netlist/netlist_factory.h | 62 ++++++++++++------- src/netlist/netlist_factory.cpp | 34 ++++++++-- .../bindings/netlist_factory.cpp | 53 +++++++++++----- 3 files changed, 103 insertions(+), 46 deletions(-) diff --git a/include/hal_core/netlist/netlist_factory.h b/include/hal_core/netlist/netlist_factory.h index d6b80985ad6..55fa753a543 100644 --- a/include/hal_core/netlist/netlist_factory.h +++ b/include/hal_core/netlist/netlist_factory.h @@ -23,6 +23,11 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +/** + * @file netlist_factory.h + * @brief This file contains various functions to create and load netlists. + */ + #pragma once #include "hal_core/defines.h" @@ -36,10 +41,6 @@ namespace hal class GateLibrary; class ProjectDirectory; - /** - * @file - */ - /** * \namespace netlist_factory * @ingroup netlist @@ -47,57 +48,72 @@ namespace hal namespace netlist_factory { /** - * Create a new empty netlist using the specified gate library. + * @brief Create a new empty netlist using the specified gate library. * * @param[in] gate_library - The gate library. - * @returns The netlist on success, nullptr otherwise. + * @returns The netlist on success, a `nullptr` otherwise. */ NETLIST_API std::unique_ptr create_netlist(const GateLibrary* gate_library); /** - * Create a netlist from the given file. Will either deserialize '.hal' file or call parser plugin for other formats. + * @brief Create a netlist from the given file. + * + * Will either deserialize `.hal` file or call parser plugin for other formats. * In the latter case the specified gate library file is mandatory. * * @param[in] netlist_file - Path to the netlist file. - * @param[in] gate_library_file - Path to the gate library file. Optional argument for '.hal' file. - * @returns The netlist on success, nullptr otherwise. + * @param[in] gate_library_file - Path to the gate library file. Optional argument for `.hal` file. + * @returns The netlist on success, a `nullptr` otherwise. */ NETLIST_API std::unique_ptr load_netlist(const std::filesystem::path& netlist_file, const std::filesystem::path& gate_library_file = std::filesystem::path()); /** - * Create a netlist from the given string. The string must contain a netlist in HAL-(JSON)-format. - * In the latter case the specified gate library file is mandatory. + * @brief Create a netlist from the given file trying to parse it with the specified gate library. + * + * Will either deserialize `.hal` file or call parser plugin for other formats. + * + * @param[in] netlist_file - Path to the netlist file. + * @param[in] gate_library - The gate library. + * @returns The netlist on success, a `nullptr` otherwise. + */ + NETLIST_API std::unique_ptr load_netlist(const std::filesystem::path& netlist_file, GateLibrary* gate_library); + + /** + * @brief Create a netlist from the given string. + * + * The string must contain a netlist in HAL-(JSON)-format. * * @param[in] netlist_string - The string containing the netlist. - * @param[in] gate_library_file - Path to the gate library file. Optional argument. - * @returns The netlist on success, nullptr otherwise. + * @param[in] gate_library_file - Path to the gate library file. + * @returns The netlist on success, a `nullptr` otherwise. */ NETLIST_API std::unique_ptr load_netlist_from_string(const std::string& netlist_string, const std::filesystem::path& gate_library_file = std::filesystem::path()); /** - * Create a netlist from the given hal project. + * @brief Create a netlist from the given hal project. * * @param[in] project_dir - Path to the hal project directory. - * @returns The netlist on success, nullptr otherwise. + * @returns The netlist on success, a `nullptr` otherwise. */ NETLIST_API std::unique_ptr load_hal_project(const std::filesystem::path& project_dir); /** - * Create a netlist using information specified in command line arguments on startup.
- * Invokes parsers or serializers as needed. + * @brief Create a netlist using information specified in command line arguments on startup. + * + * Will either deserialize `.hal` file or call parser plugin for other formats. * * @param[in] pdir - The HAL project directory. * @param[in] args - Command line arguments. - * @returns The netlist on success, nullptr otherwise. + * @returns The netlist on success, a `nullptr` otherwise. */ NETLIST_API std::unique_ptr load_netlist(const ProjectDirectory& pdir, const ProgramArguments& args); /** - * Create a netlist from a given file for each matching pre-loaded gate library. - * - * @param[in] netlist_file - Path to the netlist file. - * @returns A vector of netlists. - */ + * @brief Create a netlist from a given file for each matching pre-loaded gate library. + * + * @param[in] netlist_file - Path to the netlist file. + * @returns A vector of netlists, one for each suitable gate library. + */ NETLIST_API std::vector> load_netlists(const std::filesystem::path& netlist_file); } // namespace netlist_factory } // namespace hal diff --git a/src/netlist/netlist_factory.cpp b/src/netlist/netlist_factory.cpp index 4062eae1f3c..605ab831a7e 100644 --- a/src/netlist/netlist_factory.cpp +++ b/src/netlist/netlist_factory.cpp @@ -4,9 +4,9 @@ #include "hal_core/netlist/netlist.h" #include "hal_core/netlist/netlist_parser/netlist_parser_manager.h" #include "hal_core/netlist/persistent/netlist_serializer.h" +#include "hal_core/netlist/project_manager.h" #include "hal_core/utilities/log.h" #include "hal_core/utilities/program_arguments.h" -#include "hal_core/netlist/project_manager.h" #include "hal_core/utilities/project_directory.h" #include @@ -59,7 +59,7 @@ namespace hal lib = gate_library_manager::load(gate_library_file); if (!lib) { - log_critical("netlist", "could not parse gate library '{}', will not read netlist.", gate_library_file.string()); + log_critical("netlist", "could not parse gate library '{}', will not load netlist.", gate_library_file.string()); return nullptr; } } @@ -72,13 +72,37 @@ namespace hal { if (!lib) { - log_critical("netlist", "could not read netlist '{}' without gate library.", netlist_file.string()); + log_critical("netlist", "could not load netlist '{}' without gate library.", netlist_file.string()); return nullptr; } return netlist_parser_manager::parse(netlist_file, lib); } } + std::unique_ptr load_netlist(const std::filesystem::path& netlist_file, GateLibrary* gate_library) + { + if (access(netlist_file.c_str(), F_OK | R_OK) == -1) + { + log_critical("netlist", "could not access file '{}'.", netlist_file.string()); + return nullptr; + } + + if (!gate_library) + { + log_critical("netlist", "gate library is a nullptr, will not load netlist."); + return nullptr; + } + + if (netlist_file.extension() == ".hal") + { + return netlist_serializer::deserialize_from_file(netlist_file, gate_library); + } + else + { + return netlist_parser_manager::parse(netlist_file, gate_library); + } + } + std::unique_ptr load_hal_project(const std::filesystem::path& project_dir) { if (!std::filesystem::is_directory(project_dir)) @@ -100,9 +124,7 @@ namespace hal std::unique_ptr load_netlist(const ProjectDirectory& pdir, const ProgramArguments& args) { - std::filesystem::path netlist_file = args.is_option_set("--import-netlist") - ? std::filesystem::path(args.get_parameter("--import-netlist")) - : pdir.get_default_filename(); + std::filesystem::path netlist_file = args.is_option_set("--import-netlist") ? std::filesystem::path(args.get_parameter("--import-netlist")) : pdir.get_default_filename(); if (access(netlist_file.c_str(), F_OK | R_OK) == -1) { diff --git a/src/python_bindings/bindings/netlist_factory.cpp b/src/python_bindings/bindings/netlist_factory.cpp index e264ffe4c5e..5cf5263cea8 100644 --- a/src/python_bindings/bindings/netlist_factory.cpp +++ b/src/python_bindings/bindings/netlist_factory.cpp @@ -10,24 +10,41 @@ namespace hal Create a new empty netlist using the specified gate library. :param hal_py.GateLibrary gate_library: The gate library. - :returns: The netlist on success, None otherwise. + :returns: The netlist on success, ``None`` otherwise. :rtype: hal_py.Netlist or None )") .def( "load_netlist", - [](const std::filesystem::path& hdl_file, const std::filesystem::path& gate_library_file) { - return std::shared_ptr(netlist_factory::load_netlist(hdl_file, gate_library_file)); + [](const std::filesystem::path& netlist_file, const std::filesystem::path& gate_library_file) { + return std::shared_ptr(netlist_factory::load_netlist(netlist_file, gate_library_file)); }, - py::arg("hdl_file"), + py::arg("netlist_file"), py::arg("gate_library_file") = "", R"( Create a netlist from the given file using the specified gate library file. + Will either deserialize ``.hal`` file or call parser plugin for other formats. + In the latter case the specified gate library file is mandatory. - :param pathlib.Path hdl_file: Path to the file. + :param pathlib.Path netlist_file: Path to the file. :param pathlib.Path gate_library_file: Path to the gate library file. - :returns: The netlist on success, None otherwise. - :rtype: hal_py.Netlist + :returns: The netlist on success, ``None`` otherwise. + :rtype: hal_py.Netlist or None + )") + + .def( + "load_netlist", + [](const std::filesystem::path& netlist_file, GateLibrary* gate_library) { return std::shared_ptr(netlist_factory::load_netlist(netlist_file, gate_library)); }, + py::arg("netlist_file"), + py::arg("gate_library"), + R"( + Create a netlist from the given file trying to parse it with the specified gate library. + Will either deserialize ``.hal`` file or call parser plugin for other formats. + + :param pathlib.Path netlist_file: Path to the file. + :param pathlib.Path gate_library_file: Path to the gate library file. + :returns: The netlist on success, ``None`` otherwise. + :rtype: hal_py.Netlist or None )") .def( @@ -38,21 +55,23 @@ namespace hal py::arg("hdl_string"), py::arg("gate_library_file") = "", R"( - Create a netlist from the given string. The string must contain a netlist in HAL-(JSON)-format. + Create a netlist from the given string. + The string must contain a netlist in HAL-(JSON)-format. - :param pathlib.Path hdl_file: The string containing the netlist. + :param pathlib.Path netlist_file: The string containing the netlist. :param pathlib.Path gate_library_file: Path to the gate library file. - :returns: The netlist on success, None otherwise. - :rtype: hal_py.Netlist + :returns: The netlist on success, ``None`` otherwise. + :rtype: hal_py.Netlist or None )") .def( "load_hal_project", [](const std::filesystem::path& project_dir) { return std::shared_ptr(netlist_factory::load_hal_project(project_dir)); }, py::arg("project_dir"), R"( - Create a netlist from the given .hal file. + Create a netlist using information specified in command line arguments on startup. + Will either deserialize ``.hal`` file or call parser plugin for other formats. :param pathlib.Path project_dir: Path to the hal project directory. - :returns: The netlist on success, None otherwise. - :rtype: hal_py.Netlist + :returns: The netlist on success, ``None`` otherwise. + :rtype: hal_py.Netlist or None )") .def( @@ -65,12 +84,12 @@ namespace hal } return result; }, - py::arg("hdl_file"), + py::arg("netlist_file"), R"( Create a netlist from a given file for each matching pre-loaded gate library. - :param pathlib.Path hdl_file: Path to the file. - :returns: A list of netlists. + :param pathlib.Path netlist_file: Path to the netlist file. + :returns: A list of netlists, one for each suitable gate library. :rtype: list[hal_py.Netlist] )"); } From 3b34727b61beeb3bb12bef01fcfb5367857035c0 Mon Sep 17 00:00:00 2001 From: "julian.speith" Date: Wed, 10 Jul 2024 13:07:19 +0200 Subject: [PATCH 02/17] documentation cleanup --- .../bitorder_propagation/bitorder_propagation.h | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/plugins/bitorder_propagation/include/bitorder_propagation/bitorder_propagation.h b/plugins/bitorder_propagation/include/bitorder_propagation/bitorder_propagation.h index 6dc3e24710f..024a69dbc99 100644 --- a/plugins/bitorder_propagation/include/bitorder_propagation/bitorder_propagation.h +++ b/plugins/bitorder_propagation/include/bitorder_propagation/bitorder_propagation.h @@ -46,7 +46,8 @@ namespace hal namespace bitorder_propagation { /** - * Propagate known bit-order information from the given module pin groups to module pin groups of unknown bit order. + * @brief Propagate known bit-order information from the given module pin groups to module pin groups of unknown bit order. + * * The known bit-order information is taken from the map from net to index given for each pair of module and pin group in `src`. * After propagation, the algorithm tries to reconstruct valid bit orders from the propagated information. * @@ -61,7 +62,7 @@ namespace hal const bool enforce_continuous_bitorders = true); /** - * Reorder and rename the pins of the pin groups according to the provided bit-order information. + * @brief Reorder and rename the pins of the pin groups according to the provided bit-order information. * * @param[in] ordered_module_pin_groups - A mapping from pairs of modules and their pin groups to known bit-order information given as a mapping from nets to their index. * @returns OK on success, an error otherwise. @@ -69,7 +70,8 @@ namespace hal Result reorder_module_pin_groups(const std::map*>, std::map>& ordered_module_pin_groups); /** - * Propagate known bit-order information from one module pin group to another module pin group of unknown bit order. + * @brief Propagate known bit-order information from one module pin group to another module pin group of unknown bit order. + * * The known bit-order information is taken from the order of pins in the pin group of `src`. * After propagation, the algorithm tries to reconstruct a valid bit order from the propagated information. * The valid bit order is then annotated to the module pin group, i.e., the pins of the respective pin group are renamed and reordered. @@ -82,7 +84,8 @@ namespace hal Result*>, std::map>> propagate_bitorder(Netlist* nl, const std::pair& src, const std::pair& dst); /** - * Propagate known bit-order information from one module pin group to another module pin group of unknown bit order. + * @brief Propagate known bit-order information from one module pin group to another module pin group of unknown bit order. + * * The known bit-order information is taken from the order of pins in the pin group of `src`. * After propagation, the algorithm tries to reconstruct a valid bit order from the propagated information. * The valid bit order is then annotated to the module pin group, i.e., the pins of the respective pin group are renamed and reordered. @@ -95,7 +98,8 @@ namespace hal const std::pair*>& dst); /** - * Propagate known bit-order information from the given module pin groups to module pin groups of unknown bit order. + * @brief Propagate known bit-order information from the given module pin groups to module pin groups of unknown bit order. + * * The known bit-order information is taken from the order of pins in the pin groups of `src`. * After propagation, the algorithm tries to reconstruct valid bit orders from the propagated information. * The valid bit orders are then annotated to the module pin groups, i.e., the pins of the respective pin groups are renamed and reordered. @@ -109,7 +113,8 @@ namespace hal propagate_bitorder(Netlist* nl, const std::vector>& src, const std::vector>& dst); /** - * Propagate known bit-order information from the given module pin groups to module pin groups of unknown bit order. + * @brief Propagate known bit-order information from the given module pin groups to module pin groups of unknown bit order. + * * The known bit-order information is taken from the order of pins in the pin groups of `src`. * After propagation, the algorithm tries to reconstruct valid bit orders from the propagated information. * The valid bit orders are then annotated to the module pin groups, i.e., the pins of the respective pin groups are renamed and reordered. From ff44ddf25f5d0c2e1414ebea038c19be2f898277 Mon Sep 17 00:00:00 2001 From: "julian.speith" Date: Wed, 10 Jul 2024 15:49:23 +0200 Subject: [PATCH 03/17] fixed docs --- documentation/sphinx_doc/hal_py.rst | 3 ++- include/hal_core/doxy_groups.h | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/documentation/sphinx_doc/hal_py.rst b/documentation/sphinx_doc/hal_py.rst index 2f7cb9f5ac6..8262556f553 100644 --- a/documentation/sphinx_doc/hal_py.rst +++ b/documentation/sphinx_doc/hal_py.rst @@ -27,8 +27,9 @@ HAL Core Documentation netlist netlist_factory netlist_modification_decorator - netlist_utils netlist_serializer + netlist_traversal_decorator + netlist_utils netlist_writer_manager pin_direction pin_type diff --git a/include/hal_core/doxy_groups.h b/include/hal_core/doxy_groups.h index 514667f51bf..cb87c381724 100644 --- a/include/hal_core/doxy_groups.h +++ b/include/hal_core/doxy_groups.h @@ -34,12 +34,12 @@ /** * @defgroup decorators Decorators - * @ingroup core + * @ingroup netlist */ /** * @defgroup pins Pins - * @ingroup core + * @ingroup netlist */ /** From 36567d8fb71e17bc3ec2e44c7b3159bbf1d62282 Mon Sep 17 00:00:00 2001 From: "julian.speith" Date: Wed, 10 Jul 2024 15:49:37 +0200 Subject: [PATCH 04/17] boolean function fixes --- src/netlist/boolean_function.cpp | 5 +- .../boolean_function/parser_standard.cpp | 18 +-- .../boolean_function/simplification_abc.cpp | 2 +- .../boolean_function/symbolic_execution.cpp | 105 ++++++++++++++---- src/netlist/boolean_function/translator.cpp | 4 +- src/netlist/boolean_function/types.cpp | 6 +- tests/netlist/boolean_function.cpp | 2 +- 7 files changed, 101 insertions(+), 41 deletions(-) diff --git a/src/netlist/boolean_function.cpp b/src/netlist/boolean_function.cpp index 47bbb4e6bb8..fb059f2e6b0 100644 --- a/src/netlist/boolean_function.cpp +++ b/src/netlist/boolean_function.cpp @@ -415,7 +415,8 @@ namespace hal auto end = p2.get_index_value().get(); if ((start > end) || (start >= p0.size()) || (end >= p0.size()) || (end - start + 1) != size) { - return ERR("could not apply SLICE operation: bit-sizes do not match (p0 = " + std::to_string(p0.size()) + ", p1 = " + std::to_string(start) + ", p2 = " + std::to_string(end) + ")"); + return ERR("could not apply SLICE operation: bit indices are not valid, p1 must be larger or equal than p1 and smaller than p0 (p0 = " + std::to_string(p0.size()) + + ", p1 = " + std::to_string(start) + ", p2 = " + std::to_string(end) + ")"); } return OK(BooleanFunction(Node::Operation(NodeType::Slice, size), std::move(p0), std::move(p1), std::move(p2))); @@ -742,7 +743,7 @@ namespace hal u16 BooleanFunction::size() const { - return this->m_nodes.back().size; + return this->get_top_level_node().size; } bool BooleanFunction::is(u16 type) const diff --git a/src/netlist/boolean_function/parser_standard.cpp b/src/netlist/boolean_function/parser_standard.cpp index 74fea775327..fd980d862ea 100644 --- a/src/netlist/boolean_function/parser_standard.cpp +++ b/src/netlist/boolean_function/parser_standard.cpp @@ -4,7 +4,6 @@ #include #include #include - #include namespace hal @@ -31,7 +30,7 @@ namespace hal const auto VariableAction = [&tokens](auto& ctx) { // # Developer Note - // We combine the first matched character with the remaining + // We combine the first matched character with the remaining // string and do not remove any preceding '/' character. std::stringstream name; name << std::string(1, boost::fusion::at_c<0>(_attr(ctx))); @@ -40,9 +39,9 @@ namespace hal tokens.emplace_back(BooleanFunctionParser::Token::Variable(name.str(), 1)); }; const auto VariableIndexAction = [&tokens](auto& ctx) { - // # Developer Note - // Since the first character is an optional '\' character and - // generally escaped a.k.a. removed within HAL, we also do not + // # Developer Note + // Since the first character is an optional '\' character and + // generally escaped a.k.a. removed within HAL, we also do not // touch the part and only assemble the remaining string. std::stringstream name; name << std::string(1, boost::fusion::at_c<1>(_attr(ctx))); @@ -71,10 +70,11 @@ namespace hal const auto BracketOpenRule = x3::lit("(")[BracketOpenAction]; const auto BracketCloseRule = x3::lit(")")[BracketCloseAction]; - const auto VariableRule = x3::lexeme[(x3::char_("a-zA-Z") >> *x3::char_("a-zA-Z0-9_"))][VariableAction]; - const auto VariableIndexRoundBracketRule = x3::lexeme[(-(x3::char_("\\")) >> x3::char_("a-zA-Z") >> *x3::char_("a-zA-Z0-9_") >> x3::char_("(") >> x3::int_ >> x3::char_(")"))] [VariableIndexAction]; - const auto VariableIndexSquareBracketRule = x3::lexeme[(-(x3::char_("\\")) >> x3::char_("a-zA-Z") >> *x3::char_("a-zA-Z0-9_") >> x3::char_("[") >> x3::int_ >> x3::char_("]"))] [VariableIndexAction]; - const auto VariableIndexRule = VariableIndexRoundBracketRule | VariableIndexSquareBracketRule; + const auto VariableRule = x3::lexeme[(x3::char_("a-zA-Z") >> *x3::char_("a-zA-Z0-9_"))][VariableAction]; + const auto VariableIndexRoundBracketRule = + x3::lexeme[(-(x3::char_("\\")) >> x3::char_("a-zA-Z") >> *x3::char_("a-zA-Z0-9_") >> x3::char_("(") >> x3::int_ >> x3::char_(")"))][VariableIndexAction]; + const auto VariableIndexSquareBracketRule = + x3::lexeme[(-(x3::char_("\\")) >> x3::char_("a-zA-Z") >> *x3::char_("a-zA-Z0-9_") >> x3::char_("[") >> x3::int_ >> x3::char_("]"))][ const auto ConstantRule = x3::lexeme[x3::char_("0-1")][ConstantAction]; const auto ConstantPrefixRule = x3::lit("0b") >> x3::lexeme[x3::char_("0-1")][ConstantAction]; diff --git a/src/netlist/boolean_function/simplification_abc.cpp b/src/netlist/boolean_function/simplification_abc.cpp index 201caab46e5..0b409ff0edf 100644 --- a/src/netlist/boolean_function/simplification_abc.cpp +++ b/src/netlist/boolean_function/simplification_abc.cpp @@ -4,8 +4,8 @@ #include #include #include -#include #include +#include extern "C" { //////////////////////////////////////////////////////////////////////////// diff --git a/src/netlist/boolean_function/symbolic_execution.cpp b/src/netlist/boolean_function/symbolic_execution.cpp index e9a39e46a89..702b366d44a 100644 --- a/src/netlist/boolean_function/symbolic_execution.cpp +++ b/src/netlist/boolean_function/symbolic_execution.cpp @@ -132,6 +132,20 @@ namespace hal */ BooleanFunction Add(const std::vector& p0, const std::vector& p1) { + if (p0.size() <= 64 && p1.size() <= 64) + { + const auto a_res = BooleanFunction::to_u64(p0); + const auto b_res = BooleanFunction::to_u64(p1); + + if (a_res.is_ok() && b_res.is_ok()) + { + const auto res = (a_res.get() + b_res.get()) & 0xffffffff; + return BooleanFunction::Const(res, p0.size()); + } + + return BooleanFunction::Const(std::vector(p0.size(), BooleanFunction::Value::X)); + } + if (std::any_of(p0.begin(), p0.end(), [](auto val) { return val == BooleanFunction::Value::X || val == BooleanFunction::Value::Z; }) || std::any_of(p1.begin(), p1.end(), [](auto val) { return val == BooleanFunction::Value::X || val == BooleanFunction::Value::Z; })) { @@ -159,6 +173,20 @@ namespace hal */ BooleanFunction Sub(const std::vector& p0, const std::vector& p1) { + if (p0.size() <= 64 && p1.size() <= 64) + { + const auto a_res = BooleanFunction::to_u64(p0); + const auto b_res = BooleanFunction::to_u64(p1); + + if (a_res.is_ok() && b_res.is_ok()) + { + const auto res = (a_res.get() - b_res.get()) & 0xffffffff; + return BooleanFunction::Const(res, p0.size()); + } + + return BooleanFunction::Const(std::vector(p0.size(), BooleanFunction::Value::X)); + } + if (std::any_of(p0.begin(), p0.end(), [](auto val) { return val == BooleanFunction::Value::X || val == BooleanFunction::Value::Z; }) || std::any_of(p1.begin(), p1.end(), [](auto val) { return val == BooleanFunction::Value::X || val == BooleanFunction::Value::Z; })) { @@ -470,6 +498,41 @@ namespace hal return std::move(p); } + namespace + { + /** + * Helper function to check whether one of the two functions is just the other function negated. + */ + bool is_x_not_y(const BooleanFunction& x, const BooleanFunction& y) + { + const BooleanFunction& smaller = (x.get_nodes().size() < y.get_nodes().size()) ? x : y; + const BooleanFunction& bigger = (x.get_nodes().size() < y.get_nodes().size()) ? y : x; + + // The node vector of the first function needs to be exactly one element shorter than the second + if (smaller.get_nodes().size() != (bigger.get_nodes().size() - 1)) + { + return false; + } + + // The top level node of the bigger node vector needs to be a NOT node + if (bigger.get_top_level_node().type != BooleanFunction::NodeType::Not) + { + return false; + } + + // Every n'th element in the smaller node vector has to be identical to the n'th element of the bigger node vector, except the last/top level node + for (u32 idx = 0; idx < smaller.get_nodes().size(); idx++) + { + if (smaller.get_nodes().at(idx) != bigger.get_nodes().at(idx)) + { + return false; + } + } + + return true; + } + } // namespace + Result SymbolicExecution::simplify(const BooleanFunction::Node& node, std::vector&& p) const { if (!p.empty() && std::all_of(p.begin(), p.end(), [](const auto& function) { return function.is_constant() || function.is_index(); })) @@ -523,7 +586,7 @@ namespace hal return OK(p[0]); } // X & ~X => 0 - if (~p[0] == p[1]) + if (is_x_not_y(p[0], p[1])) { return OK(BooleanFunction::Const(0, node.size)); } @@ -571,12 +634,12 @@ namespace hal } // X & (~X & Y) => 0 - if ((~p1_parameter[0] == p[0]) || (p1_parameter[0] == ~p[0])) + if (is_x_not_y(p1_parameter[0], p[0])) { return OK(BooleanFunction::Const(0, node.size)); } // X & (Y & ~X) => 0 - if ((~p1_parameter[1] == p[0]) || (p1_parameter[1] == ~p[0])) + if (is_x_not_y(p1_parameter[1], p[0])) { return OK(BooleanFunction::Const(0, node.size)); } @@ -597,12 +660,12 @@ namespace hal return OK(p[0]); } // X & (~X | Y) => X & Y - if ((~p1_parameter[0] == p[0]) || (p1_parameter[0] == ~p[0])) + if (is_x_not_y(p1_parameter[0], p[0])) { return BooleanFunction::And(p[0].clone(), p1_parameter[1].clone(), node.size); } // X & (Y | ~X) => X & Y - if ((~p1_parameter[1] == p[0]) || (p1_parameter[1] == ~p[0])) + if (is_x_not_y(p1_parameter[1], p[0])) { return BooleanFunction::And(p[0].clone(), p1_parameter[0].clone(), node.size); } @@ -624,12 +687,12 @@ namespace hal return OK(p[0]); } // (~X & Y) & X => 0 - if ((~p0_parameter[0] == p[1]) || (p0_parameter[0] == ~p[1])) + if (is_x_not_y(p0_parameter[0], p[1])) { return OK(BooleanFunction::Const(0, node.size)); } // (Y & ~X) & X => 0 - if ((~p0_parameter[1] == p[1]) || (p0_parameter[1] == ~p[1])) + if (is_x_not_y(p0_parameter[1], p[1])) { return OK(BooleanFunction::Const(0, node.size)); } @@ -650,12 +713,12 @@ namespace hal return OK(p[1]); } // (~X | Y) & X => X & Y - if ((~p0_parameter[0] == p[1]) || (p0_parameter[0] == ~p[1])) + if (is_x_not_y(p0_parameter[0], p[1])) { return BooleanFunction::And(p[1].clone(), p0_parameter[1].clone(), node.size); } // (Y | ~X) & X => X & Y - if ((~p0_parameter[1] == p[1]) || (p0_parameter[1] == ~p[1])) + if (is_x_not_y(p0_parameter[1], p[1])) { return BooleanFunction::And(p[1].clone(), p0_parameter[0].clone(), node.size); } @@ -727,7 +790,7 @@ namespace hal } // X | ~X => 111...1 - if ((~p[0] == p[1]) || (p[0] == ~p[1])) + if (is_x_not_y(p[0], p[1])) { return OK(One(node.size)); } @@ -763,7 +826,7 @@ namespace hal { auto p1_parameter = p[1].get_parameters(); // X | (Y & !X) => X | Y - if ((~p1_parameter[1]) == p[0]) + if (is_x_not_y(p1_parameter[1], p[0])) { return BooleanFunction::Or(p[0].clone(), p1_parameter[0].clone(), node.size); } @@ -775,12 +838,12 @@ namespace hal } // X | (~X & Y) => X | Y - if ((~p1_parameter[0] == p[0]) || (p1_parameter[0] == ~p[0])) + if (is_x_not_y(p1_parameter[0], p[0])) { return BooleanFunction::Or(p[0].clone(), p1_parameter[1].clone(), node.size); } // X | (Y & ~X) => X | Y - if ((~p1_parameter[1] == p[0]) || (p1_parameter[1] == ~p[0])) + if (is_x_not_y(p1_parameter[1], p[0])) { return BooleanFunction::Or(p[0].clone(), p1_parameter[0].clone(), node.size); } @@ -802,13 +865,13 @@ namespace hal } // X | (~X | Y) => 1 - if ((~p1_parameter[0] == p[0]) || (p1_parameter[0] == ~p[0])) + if (is_x_not_y(p1_parameter[0], p[0])) { return OK(One(node.size)); } // X | (Y | ~X) => 1 - if ((~p1_parameter[1] == p[0]) || (p1_parameter[1] == ~p[0])) + if (is_x_not_y(p1_parameter[1], p[0])) { return OK(One(node.size)); } @@ -830,13 +893,13 @@ namespace hal } // (~X | Y) | X => 1 - if ((~p0_parameter[0] == p[1]) || (p0_parameter[0] == ~p[1])) + if (is_x_not_y(p0_parameter[0], p[1])) { return OK(One(node.size)); } // (Y | ~X) | X => 1 - if ((~p0_parameter[1] == p[1]) || (p0_parameter[1] == ~p[1])) + if (is_x_not_y(p0_parameter[1], p[1])) { return OK(One(node.size)); } @@ -858,13 +921,13 @@ namespace hal } // (~X & Y) | X => X | Y - if ((~p0_parameter[0] == p[1]) || (p0_parameter[0] == ~p[1])) + if (is_x_not_y(p0_parameter[0], p[1])) { return BooleanFunction::Or(p0_parameter[1].clone(), p[1].clone(), node.size); } // (X & ~Y) | Y => X | Y - if ((~p0_parameter[1] == p[1]) || (p0_parameter[1] == ~p[1])) + if (is_x_not_y(p0_parameter[1], p[1])) { return BooleanFunction::Or(p0_parameter[0].clone(), p[1].clone(), node.size); } @@ -889,7 +952,7 @@ namespace hal return OK(BooleanFunction::Const(0, node.size)); } // X ^ ~X => 1 - if (~p[0] == p[1]) + if (is_x_not_y(p[0], p[1])) { return OK(One(node.size)); } @@ -1244,7 +1307,7 @@ namespace hal case BooleanFunction::NodeType::Slice: { auto start = p[1].get_index_value().get(); auto end = p[2].get_index_value().get(); - return OK(BooleanFunction::Const(std::vector(values[0].begin() + start, values[0].begin() + end))); + return OK(BooleanFunction::Const(std::vector(values[0].begin() + start, values[0].begin() + end + 1))); } case BooleanFunction::NodeType::Concat: { values[1].insert(values[1].end(), values[0].begin(), values[0].end()); diff --git a/src/netlist/boolean_function/translator.cpp b/src/netlist/boolean_function/translator.cpp index 271943d56c9..682c7255171 100644 --- a/src/netlist/boolean_function/translator.cpp +++ b/src/netlist/boolean_function/translator.cpp @@ -99,7 +99,7 @@ namespace hal case BooleanFunction::NodeType::Concat: return OK("(concat " + p[0] + " " + p[1] + ")"); case BooleanFunction::NodeType::Slice: - return OK("((_ extract " + p[1] + " " + p[2] + ") " + p[0] + ")"); + return OK("((_ extract " + p[2] + " " + p[1] + ") " + p[0] + ")"); case BooleanFunction::NodeType::Zext: return OK("((_ zero_extend " + std::to_string(node.size - function.get_nodes().at(index - 2).index) + ") " + p[0] + ")"); case BooleanFunction::NodeType::Sext: @@ -163,5 +163,5 @@ namespace hal } } } // namespace Translator - } // namespace SMT + } // namespace SMT } // namespace hal diff --git a/src/netlist/boolean_function/types.cpp b/src/netlist/boolean_function/types.cpp index 775534e9a2c..7c0a6739c54 100644 --- a/src/netlist/boolean_function/types.cpp +++ b/src/netlist/boolean_function/types.cpp @@ -255,8 +255,6 @@ namespace hal return OK(Model(ModelParser::parser_context.model)); } - std::cout << "Model: " << s << std::endl; - exit(0); return ERR("could not parse SMT-Lib model"); } @@ -358,9 +356,7 @@ namespace hal {SMT::SolverType::Unknown, "Unknown"}}; template<> - std::map EnumStrings::data = {{SMT::SolverCall::Library, "Library"}, - {SMT::SolverCall::Binary, "Binary"}}; - + std::map EnumStrings::data = {{SMT::SolverCall::Library, "Library"}, {SMT::SolverCall::Binary, "Binary"}}; template<> std::map EnumStrings::data = {{SMT::SolverResultType::Sat, "sat"}, diff --git a/tests/netlist/boolean_function.cpp b/tests/netlist/boolean_function.cpp index dc2e25b1537..b05ddca2ade 100644 --- a/tests/netlist/boolean_function.cpp +++ b/tests/netlist/boolean_function.cpp @@ -308,7 +308,7 @@ namespace hal { { auto res = BooleanFunction::Slice(_A.clone(), i1.clone(), i1.clone(), 1); ASSERT_TRUE(res.is_ok()); - EXPECT_TRUE(res.get().simplify().has_constant_value(0)); + EXPECT_TRUE(res.get().simplify().has_constant_value(1)); } { auto res = BooleanFunction::Slice(_A.clone(), i2.clone(), i2.clone(), 1); From 9c113b0a4021cf575cd90801abf175f5021040f1 Mon Sep 17 00:00:00 2001 From: "julian.speith" Date: Wed, 10 Jul 2024 16:11:48 +0200 Subject: [PATCH 05/17] fixed the fixes --- src/netlist/boolean_function/parser_standard.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/netlist/boolean_function/parser_standard.cpp b/src/netlist/boolean_function/parser_standard.cpp index fd980d862ea..142c6290171 100644 --- a/src/netlist/boolean_function/parser_standard.cpp +++ b/src/netlist/boolean_function/parser_standard.cpp @@ -74,7 +74,8 @@ namespace hal const auto VariableIndexRoundBracketRule = x3::lexeme[(-(x3::char_("\\")) >> x3::char_("a-zA-Z") >> *x3::char_("a-zA-Z0-9_") >> x3::char_("(") >> x3::int_ >> x3::char_(")"))][VariableIndexAction]; const auto VariableIndexSquareBracketRule = - x3::lexeme[(-(x3::char_("\\")) >> x3::char_("a-zA-Z") >> *x3::char_("a-zA-Z0-9_") >> x3::char_("[") >> x3::int_ >> x3::char_("]"))][ + x3::lexeme[(-(x3::char_("\\")) >> x3::char_("a-zA-Z") >> *x3::char_("a-zA-Z0-9_") >> x3::char_("[") >> x3::int_ >> x3::char_("]"))][VariableIndexAction]; + const auto VariableIndexRule = VariableIndexRoundBracketRule | VariableIndexSquareBracketRule; const auto ConstantRule = x3::lexeme[x3::char_("0-1")][ConstantAction]; const auto ConstantPrefixRule = x3::lit("0b") >> x3::lexeme[x3::char_("0-1")][ConstantAction]; From 364e8551ae04bd799d20890919b81ec525743b1e Mon Sep 17 00:00:00 2001 From: "julian.speith" Date: Wed, 10 Jul 2024 16:12:03 +0200 Subject: [PATCH 06/17] added use_net_variables parameetr --- CHANGELOG.md | 5 ++++ include/hal_core/netlist/gate.h | 6 +++-- src/netlist/gate.cpp | 23 +++++++++------- src/python_bindings/bindings/gate.cpp | 39 ++++++++++++--------------- 4 files changed, 39 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3875a711f9..26a76b6e9a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,13 @@ All notable changes to this project will be documented in this file. * changed propagation logic for better results * miscellaneous * added backward compatibility for view management + * slightly improved symbolic execution engine + * added a version of `netlist_factory::load_netlist` that takes a path to a netlist file as well as a pointer to a gate library + * added `use_net_variables` parameter to `Gate::get_resolved_boolean_function` to choose whether to use input pins or nets as variable names * bugfixes * fixed incompatibility between shipped zlib and QuaZip libraries + * fixed a bug when checking whether one Boolean function is just a negated version of another one during symbolic execution + * fixed bugs related to the Boolean function SLICE operation ## [4.3.0](v4.3.0) - 2024-07-02 13:42:55+02:00 (urgency: medium) * **WARNING:** this release breaks compatibility with Ubuntu 20.04 LTS diff --git a/include/hal_core/netlist/gate.h b/include/hal_core/netlist/gate.h index 6df2968b41c..6966ffc5414 100644 --- a/include/hal_core/netlist/gate.h +++ b/include/hal_core/netlist/gate.h @@ -226,12 +226,14 @@ namespace hal std::unordered_map get_boolean_functions(bool only_custom_functions = false) const; /** - * Get the resolved Boolean function corresponding to the given output pin, i.e., a Boolean function that is only dependent on input nets and no internal or output pins. + * Get the resolved Boolean function corresponding to the given output pin, i.e., a Boolean function that only depends on input pins (or nets) and no internal or output pins. + * If fan-in nets are used to derive variable names, the variable names are generated using the `BooleanFunctionNetDecorator`. * * @param[in] pin - The output pin. + * @param[in] use_net_variables - Set `true` to use variable names derived from fan-in nets of the gate, `false` to use input pin names instead. Defaults to `false`. * @returns The Boolean function on success, an error otherwise. */ - Result get_resolved_boolean_function(const GatePin* pin) const; + Result get_resolved_boolean_function(const GatePin* pin, const bool use_net_variables = false) const; /** * Add a Boolean function with the given name to the gate. diff --git a/src/netlist/gate.cpp b/src/netlist/gate.cpp index b9226d46365..04e963bfc91 100644 --- a/src/netlist/gate.cpp +++ b/src/netlist/gate.cpp @@ -284,10 +284,10 @@ namespace hal return res; } - Result Gate::get_resolved_boolean_function(const GatePin* pin) const + Result Gate::get_resolved_boolean_function(const GatePin* pin, const bool use_net_variables) const { const std::function(const GatePin*, std::unordered_set&)> get_resolved_boolean_function_internal = - [this, &get_resolved_boolean_function_internal](const GatePin* output_pin, std::unordered_set& on_stack) -> Result { + [this, &get_resolved_boolean_function_internal, use_net_variables](const GatePin* output_pin, std::unordered_set& on_stack) -> Result { if (output_pin == nullptr) { return ERR("could not get resolved Boolean function of gate '" + this->get_name() + "' with ID " + std::to_string(this->get_id()) + ": given output pin is null."); @@ -316,16 +316,19 @@ namespace hal const PinDirection pin_dir = pin->get_direction(); if (pin_dir == PinDirection::input) { - const Net* const input_net = this->get_fan_in_net(var); - if (input_net == nullptr) + if (!use_net_variables) { - // if no net is connected, the input pin name cannot be replaced - return ERR("could not get resolved Boolean function of gate '" + this->get_name() + "' with ID " + std::to_string(this->get_id()) + ": failed to get fan-in net at pin '" - + pin->get_name() + "'"); - } + const Net* const input_net = this->get_fan_in_net(var); + if (input_net == nullptr) + { + // if no net is connected, the input pin name cannot be replaced + return ERR("could not get resolved Boolean function of gate '" + this->get_name() + "' with ID " + std::to_string(this->get_id()) + ": failed to get fan-in net at pin '" + + pin->get_name() + "'"); + } - const auto net_dec = BooleanFunctionNetDecorator(*input_net); - input_to_bf.insert({var, net_dec.get_boolean_variable()}); + const auto net_dec = BooleanFunctionNetDecorator(*input_net); + input_to_bf.insert({var, net_dec.get_boolean_variable()}); + } } else if ((pin_dir == PinDirection::internal) || (pin_dir == PinDirection::output)) { diff --git a/src/python_bindings/bindings/gate.cpp b/src/python_bindings/bindings/gate.cpp index da4d3fa3293..8a4ef7c63a9 100644 --- a/src/python_bindings/bindings/gate.cpp +++ b/src/python_bindings/bindings/gate.cpp @@ -42,15 +42,13 @@ namespace hal :type: int )"); - py_gate.def_property_readonly( - "netlist", [](Gate* g) { return RawPtrWrapper(g->get_netlist()); }, R"( + py_gate.def_property_readonly("netlist", [](Gate* g) { return RawPtrWrapper(g->get_netlist()); }, R"( The netlist this gate is associated with. :type: hal_py.Netlist )"); - py_gate.def( - "get_netlist", [](Gate* g) { return RawPtrWrapper(g->get_netlist()); }, R"( + py_gate.def("get_netlist", [](Gate* g) { return RawPtrWrapper(g->get_netlist()); }, R"( Get the netlist this gate is associated with. :returns: The netlist. @@ -174,8 +172,7 @@ namespace hal :rtype: hal_py.Module )"); - py_gate.def_property_readonly( - "modules", [](Gate* g) { return g->get_modules(); }, R"( + py_gate.def_property_readonly("modules", [](Gate* g) { return g->get_modules(); }, R"( A list of all modules that contain this gate, either directly or as parent of another module. :type: list[hal_py.Module] @@ -220,8 +217,8 @@ namespace hal py_gate.def( "get_resolved_boolean_function", - [](const Gate& self, const GatePin* pin) -> std::optional { - auto res = self.get_resolved_boolean_function(pin); + [](const Gate& self, const GatePin* pin, const bool use_net_variables = false) -> std::optional { + auto res = self.get_resolved_boolean_function(pin, use_net_variables); if (res.is_ok()) { return res.get(); @@ -232,17 +229,19 @@ namespace hal return std::nullopt; } }, - py::arg("pin") = nullptr, + py::arg("pin"), + py::arg("use_net_variables") = false, R"( - Get the resolved Boolean function corresponding to the given output pin, i.e., a Boolean function that is only dependent on input nets and no internal or output pins. + Get the resolved Boolean function corresponding to the given output pin, i.e., a Boolean function that only depends on input pins (or nets) and no internal or output pins. + If fan-in nets are used to derive variable names, the variable names are generated using the ``BooleanFunctionNetDecorator``. :param hal_py.GatePin pin: The output pin. - :returns: The Boolean function on success, an error otherwise. - :rtype: hal_py.BooleanFunction + :param bool use_net_variables: Set ``True`` to use variable names derived from fan-in nets of the gate, ``False`` to use input pin names instead. Defaults to ``False``. + :returns: The Boolean function on success, ``None`` otherwise. + :rtype: hal_py.BooleanFunction or None )"); - py_gate.def_property_readonly( - "boolean_functions", [](Gate* g) { return g->get_boolean_functions(); }, R"( + py_gate.def_property_readonly("boolean_functions", [](Gate* g) { return g->get_boolean_functions(); }, R"( A dictionary from function name to Boolean function for all boolean functions associated with this gate. :rtype: dict[str,hal_py.BooleanFunction] @@ -491,8 +490,7 @@ namespace hal :rtype: hal_py.Endpoint or None )"); - py_gate.def_property_readonly( - "unique_predecessors", [](Gate* g) { return g->get_unique_predecessors(); }, R"( + py_gate.def_property_readonly("unique_predecessors", [](Gate* g) { return g->get_unique_predecessors(); }, R"( A list of all unique predecessor gates of the gate. :type: list[hal_py.Gate] @@ -507,8 +505,7 @@ namespace hal :rtype: list[hal_py.Gate] )"); - py_gate.def_property_readonly( - "predecessors", [](Gate* g) { return g->get_predecessors(); }, R"( + py_gate.def_property_readonly("predecessors", [](Gate* g) { return g->get_predecessors(); }, R"( A list of all direct predecessor endpoints of the gate, i.e., all predecessor endpoints that are connected to an input pin of the gate. :type: list[hal_py.Endpoint] @@ -541,8 +538,7 @@ namespace hal :rtype: hal_py.Endpoint or None )"); - py_gate.def_property_readonly( - "unique_successors", [](Gate* g) { return g->get_unique_successors(); }, R"( + py_gate.def_property_readonly("unique_successors", [](Gate* g) { return g->get_unique_successors(); }, R"( A list of all unique successor gates of the gate. :type: list[hal_py.Gate] @@ -557,8 +553,7 @@ namespace hal :rtype: list[hal_py.Gate] )"); - py_gate.def_property_readonly( - "successors", [](Gate* g) { return g->get_successors(); }, R"( + py_gate.def_property_readonly("successors", [](Gate* g) { return g->get_successors(); }, R"( A list of all direct successor endpoints of the gate, i.e., all successor endpoints that are connected to an output pin of the gate. :type: list[hal_py.Endpoint] From d441b2cec130cafac3817fabf1743551b5ba5b81 Mon Sep 17 00:00:00 2001 From: "julian.speith" Date: Wed, 10 Jul 2024 16:43:55 +0200 Subject: [PATCH 07/17] utils update --- CHANGELOG.md | 2 ++ include/hal_core/utilities/utils.h | 17 ++++++++++--- src/utilities/utils.cpp | 38 ++++++++++++++++++++++++------ 3 files changed, 47 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26a76b6e9a1..d42fb7b24cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ All notable changes to this project will be documented in this file. * slightly improved symbolic execution engine * added a version of `netlist_factory::load_netlist` that takes a path to a netlist file as well as a pointer to a gate library * added `use_net_variables` parameter to `Gate::get_resolved_boolean_function` to choose whether to use input pins or nets as variable names + * added `utils::get_unique_temp_directory` + * added `base` parameter to `utils::wrapped_stoull` and `utils::wrapped_stoul` * bugfixes * fixed incompatibility between shipped zlib and QuaZip libraries * fixed a bug when checking whether one Boolean function is just a negated version of another one during symbolic execution diff --git a/include/hal_core/utilities/utils.h b/include/hal_core/utilities/utils.h index 141c1243d86..036343ed5c5 100644 --- a/include/hal_core/utilities/utils.h +++ b/include/hal_core/utilities/utils.h @@ -634,6 +634,15 @@ namespace hal */ CORE_API std::filesystem::path get_file(std::string file_name, std::vector path_hints); + /** + * Try to generate a unique temporary directory. + * + * @param[in] prefix - A prefix that is added in front of the unique identifier. Defaults to an empty string. + * @param[in] max_attmeps - The maximum amount of attempts before the function fails. Defaults to `5`. + * @returns OK and the created directory path on success, an error otherwise. + */ + CORE_API Result get_unique_temp_directory(const std::string& prefix = "", const u32 max_attempts = 5); + /** * Get the licenses of all embedded OpenSource Projects. * @@ -750,18 +759,20 @@ namespace hal * A safe wrapper around the std::stoull function that provides a Result<> for error handling instead of exceptions. * * @param[in] s - The string represntation of the number. + * @param[in] base - The base of the integer, defaults to 10. * * @returns OK and an integer on success, an ERROR otherwise. */ - CORE_API Result wrapped_stoull(const std::string& s); + CORE_API Result wrapped_stoull(const std::string& s, const u32 base = 10); /** * A safe wrapper around the std::stoul function that provides a Result<> for error handling instead of exceptions. * * @param[in] s - The string represntation of the number. + * @param[in] base - The base of the integer, defaults to 10. * * @returns OK and an integer on success, an ERROR otherwise. */ - CORE_API Result wrapped_stoul(const std::string& s); + CORE_API Result wrapped_stoul(const std::string& s, const u32 base = 10); } // namespace utils -} // namespace hal +} // namespace hal \ No newline at end of file diff --git a/src/utilities/utils.cpp b/src/utilities/utils.cpp index 1df92082a17..23fbc096f8f 100644 --- a/src/utilities/utils.cpp +++ b/src/utilities/utils.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -122,7 +123,7 @@ namespace hal get_base_directory() / "lib64/", get_base_directory() / "lib/", }; - + for (const auto& path : path_hints) { hal::error_code ec; @@ -262,6 +263,29 @@ namespace hal return std::filesystem::path(); } + Result get_unique_temp_directory(const std::string& prefix, const u32 max_attmeps) + { + const auto tmp_dir = std::filesystem::temp_directory_path(); + + std::random_device dev; + std::mt19937 prng(dev()); + std::uniform_int_distribution rand(0); + + for (u32 i = 0; i < max_attmeps; i++) + { + std::stringstream ss; + ss << std::setw(16) << std::setfill('0') << std::hex << rand(prng); + std::filesystem::path tmp_path = tmp_dir / (prefix + ss.str()); + + if (std::filesystem::create_directories(tmp_path)) + { + return OK(tmp_path); + } + } + + return ERR("failed to create unique temporary directory path"); + } + std::string get_open_source_licenses() { return R"(pybind11 (https://github.com/pybind/pybind11): @@ -659,11 +683,11 @@ permanent authorization for you to choose that version for the Library. )"; } - Result wrapped_stoull(const std::string& s) + Result wrapped_stoull(const std::string& s, const u32 base) { try { - return OK(std::stoull(s)); + return OK(std::stoull(s, nullptr, base)); } catch (const std::invalid_argument& e) { @@ -677,11 +701,11 @@ permanent authorization for you to choose that version for the Library. return ERR("encountered unknown error"); } - Result wrapped_stoul(const std::string& s) + Result wrapped_stoul(const std::string& s, const u32 base) { try { - return OK((u32)std::stoul(s)); + return OK((u32)std::stoul(s, nullptr, base)); } catch (const std::invalid_argument& e) { @@ -695,5 +719,5 @@ permanent authorization for you to choose that version for the Library. return ERR("encountered unknown error"); } - } // namespace core_utils -} // namespace hal + } // namespace utils +} // namespace hal \ No newline at end of file From bbf34f7ade0970961dfc875f242cd867d0f9c204 Mon Sep 17 00:00:00 2001 From: "julian.speith" Date: Thu, 11 Jul 2024 10:00:03 +0200 Subject: [PATCH 08/17] use c++17 and activate insertion of braces --- .clang-format | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.clang-format b/.clang-format index 9f6a8cce50e..1a051c38124 100644 --- a/.clang-format +++ b/.clang-format @@ -32,6 +32,7 @@ BraceWrapping: BeforeCatch: true BeforeElse: true IndentBraces: false +InsertBraces: true BreakBeforeBinaryOperators: NonAssignment BreakBeforeBraces: Custom # BreakBeforeInheritanceComma: false @@ -90,7 +91,7 @@ SpacesInContainerLiterals: false SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false -Standard: Cpp11 +Standard: Cpp17 TabWidth: 4 UseTab: Never ... From d7033bed125b2d0a68561deccdc304e0feedda6e Mon Sep 17 00:00:00 2001 From: "julian.speith" Date: Thu, 11 Jul 2024 10:11:14 +0200 Subject: [PATCH 09/17] fixed standard --- .clang-format | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.clang-format b/.clang-format index 1a051c38124..83a5fc9787a 100644 --- a/.clang-format +++ b/.clang-format @@ -91,7 +91,7 @@ SpacesInContainerLiterals: false SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false -Standard: Cpp17 +Standard: c++17 TabWidth: 4 UseTab: Never ... From 5ff6dc509925239a8b1d7e1ff06efe642d7c6af9 Mon Sep 17 00:00:00 2001 From: "julian.speith" Date: Thu, 11 Jul 2024 10:13:06 +0200 Subject: [PATCH 10/17] fixes --- .../src/vcd_serializer.cpp | 512 ++++++++++++------ 1 file changed, 335 insertions(+), 177 deletions(-) diff --git a/plugins/simulator/netlist_simulator_controller/src/vcd_serializer.cpp b/plugins/simulator/netlist_simulator_controller/src/vcd_serializer.cpp index 9e50d1191d5..b5ae118642e 100644 --- a/plugins/simulator/netlist_simulator_controller/src/vcd_serializer.cpp +++ b/plugins/simulator/netlist_simulator_controller/src/vcd_serializer.cpp @@ -1,26 +1,30 @@ #include "netlist_simulator_controller/vcd_serializer.h" + +#include "hal_core/netlist/net.h" +#include "hal_core/utilities/log.h" #include "netlist_simulator_controller/netlist_simulator_controller.h" -#include "netlist_simulator_controller/saleae_writer.h" -#include "netlist_simulator_controller/saleae_parser.h" #include "netlist_simulator_controller/saleae_file.h" +#include "netlist_simulator_controller/saleae_parser.h" +#include "netlist_simulator_controller/saleae_writer.h" #include "netlist_simulator_controller/wave_data.h" -#include "hal_core/utilities/log.h" -#include "hal_core/netlist/net.h" -#include -#include + +#include #include -#include +#include #include +#include +#include #include -#include -namespace hal { +namespace hal +{ const int maxErrorMessages = 3; - VcdSerializerElement::VcdSerializerElement(int inx, const WaveData* wd) - : mIndex(inx), mData(wd), mTime(0), mValue(SaleaeDataTuple::sReadError) - {;} + VcdSerializerElement::VcdSerializerElement(int inx, const WaveData* wd) : mIndex(inx), mData(wd), mTime(0), mValue(SaleaeDataTuple::sReadError) + { + ; + } QString VcdSerializerElement::name() const { @@ -35,30 +39,32 @@ namespace hal { void VcdSerializerElement::reset() { mValue = SaleaeDataTuple::sReadError; - mTime = 0; + mTime = 0; } QByteArray VcdSerializerElement::charCode() const { QByteArray retval; - int z = mIndex; + int z = mIndex; char firstChar = '!'; do { - retval += (char) (firstChar + z%92); + retval += (char)(firstChar + z % 92); z /= 92; - if (z<92) firstChar = ' '; // most significant digit must be non-zero - } while (z>0); + if (z < 92) + { + firstChar = ' '; // most significant digit must be non-zero + } + } while (z > 0); return retval; } -//---------------------------------- - VcdSerializer::VcdSerializer(const QString& workdir, bool saleae_cli, QObject *parent) - : QObject(parent), mSaleaeWriter(nullptr), mWorkdir(workdir), mLastProgress(-1) + //---------------------------------- + VcdSerializer::VcdSerializer(const QString& workdir, bool saleae_cli, QObject* parent) : QObject(parent), mSaleaeWriter(nullptr), mWorkdir(workdir), mLastProgress(-1) { if (!mWorkdir.isEmpty() && !saleae_cli) { - QDir saleaeDir = QDir(mWorkdir).absoluteFilePath("saleae"); + QDir saleaeDir = QDir(mWorkdir).absoluteFilePath("saleae"); mSaleaeDirectoryFilename = saleaeDir.absoluteFilePath("saleae.json"); } else @@ -67,7 +73,6 @@ namespace hal { } } - void VcdSerializer::deleteFiles() { mSaleaeFiles.clear(); @@ -75,9 +80,12 @@ namespace hal { memset(mErrorCount, 0, sizeof(mErrorCount)); } - void VcdSerializer::writeVcdEvent(QFile&of) + void VcdSerializer::writeVcdEvent(QFile& of) { - if (mTime < mFirstTimestamp || mTime > mLastTimestamp) return; + if (mTime < mFirstTimestamp || mTime > mLastTimestamp) + { + return; + } bool first = true; for (VcdSerializerElement* vse : mWriteElements) { @@ -92,129 +100,156 @@ namespace hal { of.write(QByteArray::number(vse->value()) + vse->charCode() + '\n'); vse->value(); vse->reset(); - } } } - bool VcdSerializer::exportVcd(const QString &filename, const QList& waves, u32 startTime, u32 endTime, u32 timeSift) + bool VcdSerializer::exportVcd(const QString& filename, const QList& waves, u32 startTime, u32 endTime, u32 timeSift) { - mTimeShift = timeSift; + mTimeShift = timeSift; mFirstTimestamp = startTime; mLastTimestamp = endTime - mTimeShift; - if (waves.isEmpty()) return false; + if (waves.isEmpty()) + { + return false; + } SaleaeParser parser(mSaleaeDirectoryFilename.toStdString()); QFile of(filename); - if (!of.open(QIODevice::WriteOnly)) return false; + if (!of.open(QIODevice::WriteOnly)) + { + return false; + } mTime = 0; of.write(QByteArray("$scope module top_module $end\n")); int n = waves.size(); - for (int i=0; iname().toStdString(),wd->id(),[this,&of](const void* obj, uint64_t t, int val) { - VcdSerializerElement* vse = (VcdSerializerElement*) obj; - if ((int)t - (int)mTimeShift < 0) { - vse->setEvent(0,val); - } - else { - if (t != mTime) + parser.register_callback( + wd->name().toStdString(), + wd->id(), + [this, &of](const void* obj, uint64_t t, int val) { + VcdSerializerElement* vse = (VcdSerializerElement*)obj; + if ((int)t - (int)mTimeShift < 0) { - writeVcdEvent(of); - mTime = t - mTimeShift; + vse->setEvent(0, val); } - vse->setEvent(t,val); - } - },vse); + else + { + if (t != mTime) + { + writeVcdEvent(of); + mTime = t - mTimeShift; + } + vse->setEvent(t, val); + } + }, + vse); QString line = QString("$var wire 1 %1 %2 $end\n").arg(QString::fromUtf8(vse->charCode())).arg(vse->name()); of.write(line.toUtf8()); } of.write(QByteArray("$upscope $end\n$enddefinitions $end\n")); - while (parser.next_event()) {;} + while (parser.next_event()) + { + ; + } for (VcdSerializerElement* vse : mWriteElements) + { delete vse; + } mWriteElements.clear(); return true; } - bool VcdSerializer::parseVcdDataNonDecimal(const QByteArray &line, int base) + bool VcdSerializer::parseVcdDataNonDecimal(const QByteArray& line, int base) { QList sl = line.split(' '); - if (sl.size()!=2) return false; + if (sl.size() != 2) + { + return false; + } bool ok; - int val = sl.at(0).toUInt(&ok,base); + int val = sl.at(0).toUInt(&ok, base); if (!ok) { if (mErrorCount[0]++ < maxErrorMessages) + { log_warning("waveform_viewer", "Cannot parse VCD data value '{}'", std::string(sl.at(0).data())); + } val = 0; } storeValue(val, sl.at(1)); // [return ok] return statement if we want to bail out upon data parse error - return true; // ignore parse errors + return true; // ignore parse errors } - bool VcdSerializer::parseVcdDataline(char *buf, int len) + bool VcdSerializer::parseVcdDataline(char* buf, int len) { int pos = 0; while (len) { int val = -1; - switch(*(buf+pos)) - { - case 'b': return true; // parseVcdDataNonDecimal(QByteArray(buf+pos+1,len-1),2); - case 'o': return true; //parseVcdDataNonDecimal(QByteArray(buf+pos+1,len-1),8); - case 'h': return true; // parseVcdDataNonDecimal(QByteArray(buf+pos+1,len-1),16); - case '$': + switch (*(buf + pos)) { - QByteArray testKeyword = QByteArray(buf+pos+1,len-1); - if (testKeyword.startsWith("dumpvars") || testKeyword.startsWith("end")) + case 'b': + return true; // parseVcdDataNonDecimal(QByteArray(buf+pos+1,len-1),2); + case 'o': + return true; //parseVcdDataNonDecimal(QByteArray(buf+pos+1,len-1),8); + case 'h': + return true; // parseVcdDataNonDecimal(QByteArray(buf+pos+1,len-1),16); + case '$': { + QByteArray testKeyword = QByteArray(buf + pos + 1, len - 1); + if (testKeyword.startsWith("dumpvars") || testKeyword.startsWith("end")) + { + return true; + } + return false; + } + case '#': { + bool ok; + mTime = QByteArray(buf + pos + 1, len - 1).toULongLong(&ok); + Q_ASSERT(ok); return true; - return false; + } + case 'x': + val = -1; + break; + case 'z': + val = -2; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + val = *(buf + pos) - '0'; + break; + default: + qDebug() << "cannot parse dataline entries starting with" << *(buf + pos) << buf; + return false; } - case '#': + int p = pos + 1; + while (p < len && buf[p] > ' ') { - bool ok; - mTime = QByteArray(buf+pos+1,len-1).toULongLong(&ok); - Q_ASSERT(ok); - return true; - } - case 'x': - val = -1; - break; - case 'z': - val = -2; - break; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - val = *(buf+pos)-'0'; - break; - default: - qDebug() << "cannot parse dataline entries starting with" << *(buf+pos) << buf; - return false; + ++p; } - int p = pos+1; - while (p' ') ++p; - Q_ASSERT(p > pos+1); + Q_ASSERT(p > pos + 1); int abbrevLen = p - pos - 1; - storeValue(val,QByteArray(buf+pos+1,abbrevLen)); + storeValue(val, QByteArray(buf + pos + 1, abbrevLen)); pos = p; - len -= (abbrevLen+1) ; - while (buf[pos]==' ' && len > 0) + len -= (abbrevLen + 1); + while (buf[pos] == ' ' && len > 0) { pos++; len--; @@ -226,50 +261,72 @@ namespace hal { void VcdSerializer::storeValue(int val, const QByteArray& abrev) { SaleaeOutputFile* sof = mSaleaeFiles.value(abrev); - if (!sof) return; - // Q_ASSERT(wd); - sof->writeTimeValue(mTime,val); + if (!sof) + { + return; + } + // Q_ASSERT(wd); + sof->writeTimeValue(mTime, val); } - bool VcdSerializer::parseCsvHeader(char *buf) + bool VcdSerializer::parseCsvHeader(char* buf) { - int icol = 0; + int icol = 0; char* pos = buf; bool loop = (*pos != 0); QString abbrev; while (loop) { QByteArray header; - while (*pos && *pos!=',' && *pos != '\n') header += *(pos++); + while (*pos && *pos != ',' && *pos != '\n') + { + header += *(pos++); + } loop = (*(pos++) == ','); if (!icol) { - if (mSaleae) abbrev = QString::fromUtf8(header); + if (mSaleae) + { + abbrev = QString::fromUtf8(header); + } } else { bool ok; - if (!mSaleae) abbrev = QString::number(icol); + if (!mSaleae) + { + abbrev = QString::number(icol); + } QString name; u32 id = header.trimmed().toUInt(&ok); if (ok && id) + { name = QString("net[%1]").arg(id); + } else { - name = QString::fromUtf8(header.trimmed()); + name = QString::fromUtf8(header.trimmed()); int n = name.size() - 1; - if (n<2 || name.at(0) != '"' || name.at(n) != '"') return false; - name = name.mid(1,n-1); - id = 0; + if (n < 2 || name.at(0) != '"' || name.at(n) != '"') + { + return false; + } + name = name.mid(1, n - 1); + id = 0; } if (!name.isEmpty() || id) { - SaleaeOutputFile* sof = mSaleaeWriter->add_or_replace_waveform(name.toStdString(),0); - if (!sof) return false; - mSaleaeFiles.insert(abbrev,sof); + SaleaeOutputFile* sof = mSaleaeWriter->add_or_replace_waveform(name.toStdString(), id); + if (!sof) + { + return false; + } + mSaleaeFiles.insert(abbrev, sof); } else + { return false; + } } icol++; } @@ -277,7 +334,6 @@ namespace hal { return true; } - bool VcdSerializer::parseCsvDataline(char* buf, int dataLineIndex) { int icol = 0; @@ -287,62 +343,84 @@ namespace hal { while (loop) { QByteArray value; - while (*pos && *pos != ',' && *pos != '\n') value += *(pos++); + while (*pos && *pos != ',' && *pos != '\n') + { + value += *(pos++); + } loop = (*(pos++) == ','); if (!value.isEmpty()) { if (icol) { int ival = 0; - ok = true; - if (value.size()==1) + ok = true; + if (value.size() == 1) + { switch (value.at(0)) { - case '0': break; - case '1': - ival = 1; - break; - default: - ival = value.trimmed().toInt(&ok); - break; + case '0': + break; + case '1': + ival = 1; + break; + default: + ival = value.trimmed().toInt(&ok); + break; } + } else + { ival = value.trimmed().toInt(&ok); - if (!ok) return false; + } + if (!ok) + { + return false; + } bool wdInsert = false; if (icol >= mLastValue.size()) { - while (icol > mLastValue.size()) mLastValue.append(-99); + while (icol > mLastValue.size()) + { + mLastValue.append(-99); + } mLastValue.append(ival); wdInsert = true; } else if (mLastValue.at(icol) != ival) { mLastValue[icol] = ival; - wdInsert = true; + wdInsert = true; } if (wdInsert) { SaleaeOutputFile* sof = mSaleaeFiles.value(QString::number(icol)); - if (!sof) return false; - sof->writeTimeValue(mTime,ival); + if (!sof) + { + return false; + } + sof->writeTimeValue(mTime, ival); } } else { // time double tDouble = value.toDouble(&ok); - if (!ok) return false; - u64 tInt = (u64) floor ( tDouble * SaleaeParser::sTimeScaleFactor + 0.5); + if (!ok) + { + return false; + } + u64 tInt = (u64)floor(tDouble * SaleaeParser::sTimeScaleFactor + 0.5); if (!dataLineIndex) { mFirstTimestamp = tInt; - mTime = 0; + mTime = 0; } else + { mTime = tInt - mFirstTimestamp; + } } } icol++; @@ -355,7 +433,7 @@ namespace hal { mWorkdir = workdir.isEmpty() ? QDir::currentPath() : workdir; mLastValue.clear(); deleteFiles(); - mTime = 0; + mTime = 0; mSaleae = false; SaleaeParser::sTimeScaleFactor = timeScale; @@ -368,7 +446,7 @@ namespace hal { } createSaleaeDirectory(); - mSaleaeWriter = new SaleaeWriter(mSaleaeDirectoryFilename.toStdString()); + mSaleaeWriter = new SaleaeWriter(mSaleaeDirectoryFilename.toStdString()); bool retval = parseCsvInternal(ff, onlyNets); @@ -376,16 +454,25 @@ namespace hal { mSaleaeWriter = nullptr; mSaleaeFiles.clear(); - if (retval) emitImportDone(); + if (retval) + { + emitImportDone(); + } return retval; } void VcdSerializer::emitProgress(double step, double max) { NetlistSimulatorController* nsc = static_cast(parent()); - if (!nsc) return; - int percent = floor(step*100 / max + 0.5); - if (percent == mLastProgress) return; + if (!nsc) + { + return; + } + int percent = floor(step * 100 / max + 0.5); + if (percent == mLastProgress) + { + return; + } nsc->emitLoadProgress(percent); mLastProgress = percent; qApp->processEvents(); @@ -394,7 +481,10 @@ namespace hal { void VcdSerializer::emitImportDone() { NetlistSimulatorController* nsc = static_cast(parent()); - if (!nsc) return; + if (!nsc) + { + return; + } nsc->emitLoadProgress(-1); mLastProgress = -1; } @@ -406,51 +496,64 @@ namespace hal { mSaleaeDirectoryFilename = saleaeDir.absoluteFilePath("saleae.json"); } - bool VcdSerializer::parseCsvInternal(QFile& ff, const QList& onlyNets) + bool VcdSerializer::parseCsvInternal(QFile& ff, const QList& onlyNets) { QMap netNames; for (const Net* n : onlyNets) - netNames.insert(QString::fromStdString(n->get_name()),n); + { + netNames.insert(QString::fromStdString(n->get_name()), n); + } static const int bufsize = 65535; - char buf[bufsize+1]; + char buf[bufsize + 1]; - bool parseHeader = true; + bool parseHeader = true; int dataLineIndex = 0; - while(!ff.atEnd()) + while (!ff.atEnd()) { - int sizeRead = ff.readLine(buf,bufsize); + int sizeRead = ff.readLine(buf, bufsize); if (sizeRead >= bufsize) { if (mErrorCount[1]++ < maxErrorMessages) + { log_warning("waveform_viewer", "CSV line {} exceeds buffer size {}.", dataLineIndex, bufsize); + } return false; } if (sizeRead < 0) { if (mErrorCount[2]++ < maxErrorMessages) + { log_warning("waveform_viewer", "CSV parse error reading line {} from file '{}'.", dataLineIndex, ff.fileName().toStdString()); + } return false; } - if (!sizeRead) continue; + if (!sizeRead) + { + continue; + } if (parseHeader) { if (!parseCsvHeader(buf)) { if (mErrorCount[3]++ < maxErrorMessages) + { log_warning("waveform_viewer", "Cannot parse CSV header line '{}'.", buf); + } return false; } parseHeader = false; } else { - if (!parseCsvDataline(buf,dataLineIndex++)) + if (!parseCsvDataline(buf, dataLineIndex++)) { if (mErrorCount[4]++ < maxErrorMessages) + { log_warning("waveform_viewer", "Cannot parse CSV data line '{}'.", buf); + } return false; } } @@ -472,16 +575,19 @@ namespace hal { } createSaleaeDirectory(); - mSaleaeWriter = new SaleaeWriter(mSaleaeDirectoryFilename.toStdString()); + mSaleaeWriter = new SaleaeWriter(mSaleaeDirectoryFilename.toStdString()); - bool retval = parseVcdInternal(ff,onlyNets); + bool retval = parseVcdInternal(ff, onlyNets); delete mSaleaeWriter; mSaleaeWriter = nullptr; mSaleaeFiles.clear(); mAbbrevByName.clear(); - if (retval) emitImportDone(); + if (retval) + { + emitImportDone(); + } return retval; } @@ -491,70 +597,102 @@ namespace hal { QMap netNames; for (const Net* n : onlyNets) - netNames.insert(QString::fromStdString(n->get_name()),n); + { + netNames.insert(QString::fromStdString(n->get_name()), n); + } QRegularExpression reHead("\\$(\\w*) (.*)\\$end"); QRegularExpression reWire("wire\\s+(\\d+) ([^ ]+) (.*)$"); - quint64 fileSize = ff.size(); + quint64 fileSize = ff.size(); quint64 totalRead = 0; static const int bufsize = 4095; - char buf[bufsize+1]; + char buf[bufsize + 1]; int iline = 0; - while(!ff.atEnd()) + while (!ff.atEnd()) { - int sizeRead = ff.readLine(buf,bufsize); + int sizeRead = ff.readLine(buf, bufsize); ++iline; totalRead += sizeRead; - emitProgress(totalRead,fileSize); + emitProgress(totalRead, fileSize); if (sizeRead >= bufsize) { if (mErrorCount[5]++ < maxErrorMessages) + { log_warning("waveform_viewer", "VCD line {} exceeds buffer size {}.", iline, bufsize); + } return false; } if (sizeRead < 0) { if (mErrorCount[6]++ < maxErrorMessages) + { log_warning("waveform_viewer", "VCD parse error reading line {} from file '{}'.", iline, ff.fileName().toStdString()); + } return false; } - if (sizeRead > 0 && buf[sizeRead-1]=='\n') --sizeRead; - if (sizeRead > 0 && buf[sizeRead-1]=='\r') --sizeRead; - if (!sizeRead) continue; - + if (sizeRead > 0 && buf[sizeRead - 1] == '\n') + { + --sizeRead; + } + if (sizeRead > 0 && buf[sizeRead - 1] == '\r') + { + --sizeRead; + } + if (!sizeRead) + { + continue; + } if (parseHeader) { - QByteArray line(buf,sizeRead); + QByteArray line(buf, sizeRead); QRegularExpressionMatch mHead = reHead.match(line); if (mHead.hasMatch()) { if (mHead.captured(1) == "enddefinitions") + { parseHeader = false; + } else if (mHead.captured(1) == "var") { QRegularExpressionMatch mWire = reWire.match(mHead.captured(2)); bool ok; - QString wireName = mWire.captured(3); - if (!wireName.isEmpty() && wireName.at(0)=='\\') wireName.remove(0,1); - wireName = wireName.trimmed(); + QString wireName = mWire.captured(3); + if (!wireName.isEmpty() && wireName.at(0) == '\\') + { + wireName.remove(0, 1); + } + wireName = wireName.trimmed(); const Net* net = netNames.value(wireName); - if (!netNames.isEmpty() && !net) continue; // net not found in given name list + + if (!netNames.isEmpty() && !net) + { + continue; // net not found in given name list + } + if (mAbbrevByName.contains(wireName)) { if (mErrorCount[7]++ < maxErrorMessages) + { log_warning("waveform_viewer", "Waveform duplicate for '{}' in VCD file '{}'.", wireName.toStdString(), ff.fileName().toStdString()); + } continue; } QString wireAbbrev = mWire.captured(2); - mAbbrevByName.insert(wireName,wireAbbrev); - int wireBits = mWire.captured(1).toUInt(&ok); - if (!ok) wireBits = 1; - if (wireBits > 1) continue; // TODO : decision whether we will be able to handle VCD with more bits + mAbbrevByName.insert(wireName, wireAbbrev); + int wireBits = mWire.captured(1).toUInt(&ok); + if (!ok) + { + wireBits = 1; + } + if (wireBits > 1) + { + continue; // TODO : decision whether we will be able to handle VCD with more bits + } u32 netId = net ? net->get_id() : 0; @@ -563,22 +701,30 @@ namespace hal { { // output file already exists, need name entry sof = mSaleaeFiles.value(wireAbbrev); - if (sof) mSaleaeWriter->add_directory_entry(sof->index(), wireName.toStdString(), netId); + if (sof) + { + mSaleaeWriter->add_directory_entry(sof->index(), wireName.toStdString(), netId); + } } else { sof = mSaleaeWriter->add_or_replace_waveform(wireName.toStdString(), netId); - if (sof) mSaleaeFiles.insert(wireAbbrev,sof); + if (sof) + { + mSaleaeFiles.insert(wireAbbrev, sof); + } } } } } else { - if (!parseVcdDataline(buf,sizeRead)) + if (!parseVcdDataline(buf, sizeRead)) { if (mErrorCount[8]++ < maxErrorMessages) - log_warning("waveform_viewer", "Cannot parse VCD data line '{}'.", QByteArray(buf,sizeRead).data()); + { + log_warning("waveform_viewer", "Cannot parse VCD data line '{}'.", QByteArray(buf, sizeRead).data()); + } return false; } } @@ -586,47 +732,59 @@ namespace hal { return true; } - bool VcdSerializer::importSaleae(const QString& saleaeDirecotry, const std::unordered_map &lookupTable, const QString& workdir, u64 timeScale) + bool VcdSerializer::importSaleae(const QString& saleaeDirecotry, const std::unordered_map& lookupTable, const QString& workdir, u64 timeScale) { mWorkdir = workdir.isEmpty() ? QDir::currentPath() : workdir; qDebug() << "workdir" << mWorkdir; deleteFiles(); - mTime = 0; + mTime = 0; SaleaeParser::sTimeScaleFactor = timeScale; - int nstep = lookupTable.size() + 1; - int istep = 0; + int nstep = lookupTable.size() + 1; + int istep = 0; - emitProgress(istep++,nstep); + emitProgress(istep++, nstep); createSaleaeDirectory(); SaleaeDirectory sd(get_saleae_directory_filename()); SaleaeDirectoryStoreRequest save(&sd); QDir sourceDir(saleaeDirecotry); QDir targetDir(QFileInfo(mSaleaeDirectoryFilename).path()); - emitProgress(istep++,nstep); + emitProgress(istep++, nstep); for (auto it = lookupTable.begin(); it != lookupTable.end(); ++it) { Q_ASSERT(it->first); bool removeOldFile = false; - int inx = sd.get_datafile_index(it->first->get_name(),it->first->get_id()); + int inx = sd.get_datafile_index(it->first->get_name(), it->first->get_id()); if (inx < 0) + { // create new file in import direcotry inx = sd.get_next_available_index(); + } else + { removeOldFile = true; + } QString source = sourceDir.absoluteFilePath(QString("digital_%1.bin").arg(it->second)); QString target = targetDir.absoluteFilePath(QString("digital_%1.bin").arg(inx)); if (removeOldFile) + { QFile::remove(target); - if (!QFile::copy(source,target)) return false; + } + if (!QFile::copy(source, target)) + { + return false; + } SaleaeInputFile sif(target.toStdString()); - if (!sif.header()) return false; + if (!sif.header()) + { + return false; + } SaleaeDirectoryNetEntry sdne(it->first->get_name(), it->first->get_id()); - sdne.addIndex(SaleaeDirectoryFileIndex(inx,sif.header()->beginTime(),sif.header()->endTime(),sif.header()->numTransitions()+1)); + sdne.addIndex(SaleaeDirectoryFileIndex(inx, sif.header()->beginTime(), sif.header()->endTime(), sif.header()->numTransitions() + 1)); sd.add_or_replace_net(sdne); - emitProgress(istep++,nstep); + emitProgress(istep++, nstep); } emitImportDone(); return true; } -} +} // namespace hal From 1d62b61242ec59b610077506b19525b8394fa9a3 Mon Sep 17 00:00:00 2001 From: "julian.speith" Date: Thu, 11 Jul 2024 10:45:50 +0200 Subject: [PATCH 11/17] fixed const 1 and 0 nets --- CHANGELOG.md | 2 + .../include/verilog_parser/verilog_parser.h | 3 ++ plugins/verilog_parser/src/verilog_parser.cpp | 33 ++++++++------- plugins/verilog_writer/src/verilog_writer.cpp | 42 +++++++++++-------- plugins/vhdl_parser/src/vhdl_parser.cpp | 29 +++++++------ 5 files changed, 64 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d42fb7b24cb..7f57263619b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ All notable changes to this project will be documented in this file. * fixed incompatibility between shipped zlib and QuaZip libraries * fixed a bug when checking whether one Boolean function is just a negated version of another one during symbolic execution * fixed bugs related to the Boolean function SLICE operation + * fixed VCD writer of `netlist_simulation_controller` plugin + * fixed handling of const `0` and `1` nets in `verilog_parser`, `vhdl_parser`, and `verilog_writer` plugins ## [4.3.0](v4.3.0) - 2024-07-02 13:42:55+02:00 (urgency: medium) * **WARNING:** this release breaks compatibility with Ubuntu 20.04 LTS diff --git a/plugins/verilog_parser/include/verilog_parser/verilog_parser.h b/plugins/verilog_parser/include/verilog_parser/verilog_parser.h index 66bf56d5ffd..9b8960b28a0 100644 --- a/plugins/verilog_parser/include/verilog_parser/verilog_parser.h +++ b/plugins/verilog_parser/include/verilog_parser/verilog_parser.h @@ -194,6 +194,9 @@ namespace hal std::unordered_map m_net_by_name; std::vector> m_nets_to_merge; + // parser settings + const std::string instance_name_seperator = "/"; + // parse HDL into intermediate format void tokenize(); Result parse_tokens(); diff --git a/plugins/verilog_parser/src/verilog_parser.cpp b/plugins/verilog_parser/src/verilog_parser.cpp index 216250f66e4..3791bf11dca 100644 --- a/plugins/verilog_parser/src/verilog_parser.cpp +++ b/plugins/verilog_parser/src/verilog_parser.cpp @@ -1311,6 +1311,16 @@ namespace hal { continue; } + else if (slave_net == m_zero_net || slave_net == m_one_net) + { + auto* tmp_net = master_net; + master_net = slave_net; + slave_net = tmp_net; + + auto tmp_name = master; + master = slave; + slave = tmp_name; + } // merge sources if (slave_net->is_global_input_net()) @@ -1444,7 +1454,7 @@ namespace hal // add global GND gate if required by any instance if (m_netlist->get_gnd_gates().empty()) { - if (!m_zero_net->get_destinations().empty()) + if (m_zero_net->get_num_of_destinations() > 0) { GateType* gnd_type = m_gnd_gate_types.begin()->second; GatePin* output_pin = gnd_type->get_output_pins().front(); @@ -1470,7 +1480,7 @@ namespace hal // add global VCC gate if required by any instance if (m_netlist->get_vcc_gates().empty()) { - if (!m_one_net->get_destinations().empty()) + if (m_one_net->get_num_of_destinations() > 0) { GateType* vcc_type = m_vcc_gate_types.begin()->second; GatePin* output_pin = vcc_type->get_output_pins().front(); @@ -1593,7 +1603,9 @@ namespace hal { std::string unique_net_name = get_unique_alias(module->get_name(), expanded_name, m_net_name_occurences); if (unique_net_name != expanded_name) + { m_net_name_occurences[unique_net_name]++; + } signal_alias[expanded_name] = unique_net_name; // create new net for the signal @@ -1641,18 +1653,11 @@ namespace hal { b = alias_it->second; } - else if (b == "'0'" || b == "'1'") - { - // '0' or '1' is assigned, make sure that '0' or '1' net does not get deleted by merging - const auto tmp = a; - a = b; - b = tmp; - } else if (b == "'Z'" || b == "'X'") { continue; } - else + else if (b != "'0'" && b != "'1'") { return ERR("could not create instance '" + instance_identifier + "' of type '" + instance_type + "': failed to find alias for net '" + b + "'"); } @@ -1734,7 +1739,7 @@ namespace hal { // create the new gate instance_alias[instance->m_name] = get_unique_alias(module->get_name(), instance->m_name, m_instance_name_occurences); - Gate* new_gate = m_netlist->create_gate(gate_type_it->second, instance_alias.at(instance->m_name)); + Gate* new_gate = m_netlist->create_gate(gate_type_it->second, instance_alias.at(instance->m_name)); if (new_gate == nullptr) { return ERR("could not create instance '" + instance_identifier + "' of type '" + instance_type + "': failed to create gate '" + instance->m_name + "'"); @@ -1924,8 +1929,6 @@ namespace hal {'Z', {BooleanFunction::Value::Z, BooleanFunction::Value::Z, BooleanFunction::Value::Z, BooleanFunction::Value::Z}}}; } // namespace - const std::string instance_name_seperator = "/"; - // generate a unique name for a gate/module instance std::string VerilogParser::get_unique_alias(const std::string& parent_name, const std::string& name, const std::unordered_map& name_occurences) const { @@ -1941,14 +1944,14 @@ namespace hal // it is OK if base name (first loop cnt=0) is already in name_occurences once // unique_alias (cnt > 0) must not be in name_occurences - while (instance_name_it != name_occurences.end() && (cnt || instance_name_it->second > 1) ) + while (instance_name_it != name_occurences.end() && (cnt || instance_name_it->second > 1)) { std::string extension; if (cnt++) { extension = "_u" + std::to_string(cnt); } - unique_alias = parent_name + instance_name_seperator + unique_alias + extension; + unique_alias = parent_name + instance_name_seperator + unique_alias + extension; instance_name_it = name_occurences.find(unique_alias); } } diff --git a/plugins/verilog_writer/src/verilog_writer.cpp b/plugins/verilog_writer/src/verilog_writer.cpp index 66984a25ac4..6a4a8c0e501 100644 --- a/plugins/verilog_writer/src/verilog_writer.cpp +++ b/plugins/verilog_writer/src/verilog_writer.cpp @@ -52,8 +52,6 @@ namespace hal assert(ordered_modules.back()->is_top_module() == true); } - // TODO take care of 1 and 0 nets (probably rework their handling within the core to supply a "is_gnd_net" and "is_vcc_net" function) - std::unordered_map module_aliases; std::unordered_map module_identifier_occurrences; for (Module* mod : ordered_modules) @@ -177,8 +175,21 @@ namespace hal if (aliases.find(net) == aliases.end()) { - aliases[net] = escape(get_unique_alias(identifier_occurrences, net->get_name())); - res_stream << " wire " << aliases.at(net) << ";" << std::endl; + auto net_alias = escape(get_unique_alias(identifier_occurrences, net->get_name())); + aliases[net] = net_alias; + + res_stream << " wire " << net_alias; + + if (net->is_vcc_net() && net->get_num_of_sources() == 0) + { + res_stream << " = 1'b1"; + } + else if (net->is_gnd_net() && net->get_num_of_sources() == 0) + { + res_stream << " = 1'b0"; + } + + res_stream << ";" << std::endl; } } @@ -367,7 +378,7 @@ namespace hal res_stream << " ." << escape(pin) << "("; if (nets.size() > 1) { - res_stream << "{"; + res_stream << "{" << std::endl << " "; } bool first_net = true; @@ -375,17 +386,14 @@ namespace hal { const Net* net = *it; - if (net != nullptr) + if (!first_net) { - if (first_net) - { - first_net = false; - } - else - { - res_stream << "," << std::endl; - } + res_stream << "," << std::endl << " "; + } + first_net = false; + if (net != nullptr) + { if (const auto alias_it = aliases.find(net); alias_it != aliases.end()) { res_stream << alias_it->second; @@ -398,13 +406,13 @@ namespace hal else { // unconnected pin of a group with at least one connection - res_stream << "1'bz"; + res_stream << "HAL_UNUSED_SIGNAL"; } } if (nets.size() > 1) { - res_stream << "}"; + res_stream << std::endl << " }"; } res_stream << ")"; @@ -474,7 +482,7 @@ namespace hal std::string VerilogWriter::get_unique_alias(std::unordered_map& name_occurrences, const std::string& name) const { name_occurrences[name]++; - + // if the name only appears once, we don't have to suffix it if (name_occurrences[name] < 2) { diff --git a/plugins/vhdl_parser/src/vhdl_parser.cpp b/plugins/vhdl_parser/src/vhdl_parser.cpp index 93e3ab452d5..6a50aca0606 100644 --- a/plugins/vhdl_parser/src/vhdl_parser.cpp +++ b/plugins/vhdl_parser/src/vhdl_parser.cpp @@ -340,7 +340,7 @@ namespace hal // add global GND gate if required by any instance if (m_netlist->get_gnd_gates().empty()) { - if (!m_zero_net->get_destinations().empty()) + if (m_zero_net->get_num_of_destinations() > 0) { GateType* gnd_type = m_gnd_gate_types.begin()->second; GatePin* output_pin = gnd_type->get_output_pins().front(); @@ -365,7 +365,7 @@ namespace hal // add global VCC gate if required by any instance if (m_netlist->get_vcc_gates().empty()) { - if (!m_one_net->get_destinations().empty()) + if (m_one_net->get_num_of_destinations() > 0) { GateType* vcc_type = m_vcc_gate_types.begin()->second; GatePin* output_pin = vcc_type->get_output_pins().front(); @@ -1510,6 +1510,16 @@ namespace hal { continue; } + else if (slave_net == m_zero_net || slave_net == m_one_net) + { + auto* tmp_net = master_net; + master_net = slave_net; + slave_net = tmp_net; + + auto tmp_name = master; + master = slave; + slave = tmp_name; + } // merge sources if (slave_net->is_global_input_net()) @@ -1621,7 +1631,7 @@ namespace hal // annotate all merged slave wire names as a JSON formatted list of list of strings // each net can span a tree of "consumed" slave wire names where the nth list represents all wire names that where merged at depth n ci_string merged_str = ""; - bool has_merged_nets = false; + bool has_merged_nets = false; for (const auto& vec : merged_slaves) { if (!vec.empty()) @@ -1634,7 +1644,7 @@ namespace hal { s += ci_string(", ") + vec.at(idx); } - + merged_str += "[" + s + "], "; } merged_str = merged_str.substr(0, merged_str.size() - 2); @@ -1710,7 +1720,7 @@ namespace hal // TODO check parent module assignments for port aliases - const std::string parent_name = parent == (nullptr) ? "" : parent->get_name(); + const std::string parent_name = parent == (nullptr) ? "" : parent->get_name(); instance_alias[instance_identifier] = get_unique_alias(core_strings::to(parent_name), instance_identifier, m_instance_name_occurrences); // create netlist module @@ -1816,18 +1826,11 @@ namespace hal { b = alias_it->second; } - else if (b == "'0'" || b == "'1'") - { - // '0' or '1' is assigned, make sure that '0' or '1' net does not get deleted by merging - const auto tmp = a; - a = b; - b = tmp; - } else if (b == "'Z'" || b == "'X'") { continue; } - else + else if (b != "'0'" && b != "'1'") { return ERR("could not create instance '" + core_strings::to(instance_identifier) + "' of type '" + core_strings::to(instance_type) + "': failed to find alias for net '" + core_strings::to(b) + "'"); From 040a3144d486f47301eff58df931cda1b15f60d9 Mon Sep 17 00:00:00 2001 From: "julian.speith" Date: Thu, 11 Jul 2024 11:15:34 +0200 Subject: [PATCH 12/17] create empty plugin --- plugins/.gitignore | 2 + plugins/resynthesis/CMakeLists.txt | 15 ++++ .../resynthesis/documentation/resynthesis.rst | 8 ++ .../include/resynthesis/plugin_resynthesis.h | 86 +++++++++++++++++++ .../resynthesis/python/python_bindings.cpp | 86 +++++++++++++++++++ .../resynthesis/src/plugin_resynthesis.cpp | 29 +++++++ .../xilinx_toolbox/plugin_xilinx_toolbox.h | 20 ----- .../xilinx_toolbox/python/python_bindings.cpp | 3 +- 8 files changed, 228 insertions(+), 21 deletions(-) create mode 100644 plugins/resynthesis/CMakeLists.txt create mode 100644 plugins/resynthesis/documentation/resynthesis.rst create mode 100644 plugins/resynthesis/include/resynthesis/plugin_resynthesis.h create mode 100644 plugins/resynthesis/python/python_bindings.cpp create mode 100644 plugins/resynthesis/src/plugin_resynthesis.cpp diff --git a/plugins/.gitignore b/plugins/.gitignore index b88558e8765..508c59dc261 100644 --- a/plugins/.gitignore +++ b/plugins/.gitignore @@ -27,6 +27,8 @@ !netlist_preprocessing/**/* !python_shell* !python_shell/**/* +!resynthesis* +!resynthesis/**/* !simulator !solve_fsm* !solve_fsm/**/* diff --git a/plugins/resynthesis/CMakeLists.txt b/plugins/resynthesis/CMakeLists.txt new file mode 100644 index 00000000000..801ed9193ca --- /dev/null +++ b/plugins/resynthesis/CMakeLists.txt @@ -0,0 +1,15 @@ +option(PL_RESYNTHESIS "PL_RESYNTHESIS" OFF) + +if(PL_RESYNTHESIS OR BUILD_ALL_PLUGINS) + + file(GLOB_RECURSE RESYNTHESIS_INC ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h) + file(GLOB_RECURSE RESYNTHESIS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) + file(GLOB_RECURSE RESYNTHESIS_PYTHON_SRC ${CMAKE_CURRENT_SOURCE_DIR}/python/*.cpp) + + hal_add_plugin(resynthesis + SHARED + HEADER ${RESYNTHESIS_INC} + SOURCES ${RESYNTHESIS_SRC} ${RESYNTHESIS_PYTHON_SRC} + PYDOC SPHINX_DOC_INDEX_FILE ${CMAKE_CURRENT_SOURCE_DIR}/documentation/resynthesis.rst + ) +endif() diff --git a/plugins/resynthesis/documentation/resynthesis.rst b/plugins/resynthesis/documentation/resynthesis.rst new file mode 100644 index 00000000000..ba026ae2985 --- /dev/null +++ b/plugins/resynthesis/documentation/resynthesis.rst @@ -0,0 +1,8 @@ +Resynthesis +========================== + +.. automodule:: resynthesis + :members: + +.. autoclass:: resynthesis.ResynthesisPlugin + :members: \ No newline at end of file diff --git a/plugins/resynthesis/include/resynthesis/plugin_resynthesis.h b/plugins/resynthesis/include/resynthesis/plugin_resynthesis.h new file mode 100644 index 00000000000..bb9a698c820 --- /dev/null +++ b/plugins/resynthesis/include/resynthesis/plugin_resynthesis.h @@ -0,0 +1,86 @@ +// 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. +// +// 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. + +/** + * @file plugin_xilinx_toolbox.h + * @brief This file contains all functions related to the HAL plugin API. + */ + +#pragma once + +#include "hal_core/plugin_system/plugin_interface_base.h" + +namespace hal +{ + class Netlist; + + /** + * @class ResynthesisPlugin + * @brief Plugin interface for the netlist resynthesis plugin. + * + * This class provides an interface to integrate the netlist resynthesis as a plugin within the HAL framework. + */ + class PLUGIN_API ResynthesisPlugin : public BasePluginInterface + { + public: + /** + * @brief Default constructor for `ResynthesisPlugin`. + */ + ResynthesisPlugin() = default; + + /** + * @brief Default destructor for `ResynthesisPlugin`. + */ + ~ResynthesisPlugin() = default; + + /** + * @brief Get the name of the plugin. + * + * @returns The name of the plugin. + */ + std::string get_name() const override; + + /** + * @brief Get the version of the plugin. + * + * @returns The version of the plugin. + */ + std::string get_version() const override; + + /** + * @brief Get a short description of the plugin. + * + * @returns The short description of the plugin. + */ + std::string get_description() const override; + + /** + * @brief Get the plugin dependencies. + * + * @returns A set of plugin names that this plugin depends on. + */ + std::set get_dependencies() const override; + }; +} // namespace hal diff --git a/plugins/resynthesis/python/python_bindings.cpp b/plugins/resynthesis/python/python_bindings.cpp new file mode 100644 index 00000000000..7d2e42a08a6 --- /dev/null +++ b/plugins/resynthesis/python/python_bindings.cpp @@ -0,0 +1,86 @@ +#include "hal_core/python_bindings/python_bindings.h" + +#include "pybind11/operators.h" +#include "pybind11/pybind11.h" +#include "pybind11/stl.h" +#include "pybind11/stl_bind.h" +#include "resynthesis/plugin_resynthesis.h" + +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(resynthesis, m) + { + m.doc() = "Provides functions to re-synthesize (parts of) a gate-level netlist."; +#else + PYBIND11_PLUGIN(resynthesis) + { + py::module m("resynthesis", "Provides functions to re-synthesize (parts of) a gate-level netlist."); +#endif // ifdef PYBIND11_MODULE + + py::class_, BasePluginInterface> py_resynthesis_plugin( + m, "ResynthesisPlugin", R"(This class provides an interface to integrate the netlist resynthesis as a plugin within the HAL framework.)"); + + py_resynthesis_plugin.def_property_readonly("name", &ResynthesisPlugin::get_name, R"( + The name of the plugin. + + :type: str + )"); + + py_resynthesis_plugin.def("get_name", &ResynthesisPlugin::get_name, R"( + Get the name of the plugin. + + :returns: The name of the plugin. + :rtype: str + )"); + + py_resynthesis_plugin.def_property_readonly("version", &ResynthesisPlugin::get_version, R"( + The version of the plugin. + + :type: str + )"); + + py_resynthesis_plugin.def("get_version", &ResynthesisPlugin::get_version, R"( + Get the version of the plugin. + + :returns: The version of the plugin. + :rtype: str + )"); + + py_resynthesis_plugin.def_property_readonly("description", &ResynthesisPlugin::get_description, R"( + The description of the plugin. + + :type: str + )"); + + py_resynthesis_plugin.def("get_description", &ResynthesisPlugin::get_description, R"( + Get the description of the plugin. + + :returns: The description of the plugin. + :rtype: str + )"); + + py_resynthesis_plugin.def_property_readonly("dependencies", &ResynthesisPlugin::get_dependencies, R"( + A set of plugin names that this plugin depends on. + + :type: set[str] + )"); + + py_resynthesis_plugin.def("get_dependencies", &ResynthesisPlugin::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] + )"); + +#ifndef PYBIND11_MODULE + return m.ptr(); +#endif // PYBIND11_MODULE + } +} // namespace hal diff --git a/plugins/resynthesis/src/plugin_resynthesis.cpp b/plugins/resynthesis/src/plugin_resynthesis.cpp new file mode 100644 index 00000000000..0d1bdcd2639 --- /dev/null +++ b/plugins/resynthesis/src/plugin_resynthesis.cpp @@ -0,0 +1,29 @@ +#include "resynthesis/plugin_resynthesis.h" + +namespace hal +{ + extern std::unique_ptr create_plugin_instance() + { + return std::make_unique(); + } + + std::string ResynthesisPlugin::get_name() const + { + return std::string("resynthesis"); + } + + std::string ResynthesisPlugin::get_version() const + { + return std::string("0.1"); + } + + std::string ResynthesisPlugin::get_description() const + { + return "Provides functions to re-synthesize (parts of) a gate-level netlist."; + } + + std::set ResynthesisPlugin::get_dependencies() const + { + return {}; + } +} // namespace hal diff --git a/plugins/xilinx_toolbox/include/xilinx_toolbox/plugin_xilinx_toolbox.h b/plugins/xilinx_toolbox/include/xilinx_toolbox/plugin_xilinx_toolbox.h index 76500469348..eb33d2b6637 100644 --- a/plugins/xilinx_toolbox/include/xilinx_toolbox/plugin_xilinx_toolbox.h +++ b/plugins/xilinx_toolbox/include/xilinx_toolbox/plugin_xilinx_toolbox.h @@ -31,7 +31,6 @@ #pragma once #include "hal_core/plugin_system/plugin_interface_base.h" -#include "hal_core/utilities/result.h" namespace hal { @@ -83,24 +82,5 @@ namespace hal * @returns A set of plugin names that this plugin depends on. */ std::set get_dependencies() const override; - - // preprocessing - - /** - * Removes all LUTs with multiple outputs and replaces them with equivalent smaller LUTs. - * - * @param[in] nl - The netlist to operate on. - * @return The number of removed gates on success, an error otherwise. - */ - static Result split_luts(Netlist* nl); - - /** - * Parses an .xdc file and extracts the position LOC and BEL data. - * Afterwards translates the found LOC and BEL data into integer coordinates. - * - * @param[in] nl - The netlist to operate on. - * @return The number of removed gates on success, an error otherwise. - */ - static Result parse_xdc_file(Netlist* nl, const std::filesystem::path& xdc_file); }; } // namespace hal diff --git a/plugins/xilinx_toolbox/python/python_bindings.cpp b/plugins/xilinx_toolbox/python/python_bindings.cpp index 7fb44c62ec2..46ebbb50bb1 100644 --- a/plugins/xilinx_toolbox/python/python_bindings.cpp +++ b/plugins/xilinx_toolbox/python/python_bindings.cpp @@ -25,7 +25,8 @@ namespace hal py::module m("xilinx_toolbox", "A collection of functions specifically designed to operate on Xilinx FPGA netlists."); #endif // ifdef PYBIND11_MODULE - py::class_, BasePluginInterface> py_xilinx_toolbox_plugin(m, "XilinxToolboxPlugin"); + py::class_, BasePluginInterface> py_xilinx_toolbox_plugin( + m, "XilinxToolboxPlugin", R"(This class provides an interface to integrate the Xilinx toolbox as a plugin within the HAL framework.)"); py_xilinx_toolbox_plugin.def_property_readonly("name", &XilinxToolboxPlugin::get_name, R"( The name of the plugin. From 94c53d3177bfa9640a0cf2c0aae8eeb5ac951d9d Mon Sep 17 00:00:00 2001 From: "julian.speith" Date: Mon, 15 Jul 2024 12:55:39 +0200 Subject: [PATCH 13/17] allow to control labeling of global I/O for copied netlists --- CHANGELOG.md | 1 + .../decorators/subgraph_netlist_decorator.h | 9 +++-- .../decorators/subgraph_netlist_decorator.cpp | 35 ++++++++++++------- .../bindings/subgraph_netlist_decorator.cpp | 12 ++++--- 4 files changed, 38 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f57263619b..f122f0d6b06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ All notable changes to this project will be documented in this file. * added `use_net_variables` parameter to `Gate::get_resolved_boolean_function` to choose whether to use input pins or nets as variable names * added `utils::get_unique_temp_directory` * added `base` parameter to `utils::wrapped_stoull` and `utils::wrapped_stoul` + * added `all_global_io` parameter to `SubgraphNetlistDecorator::copy_subgraph_netlist` to configure labeling of global inputs and outputs * bugfixes * fixed incompatibility between shipped zlib and QuaZip libraries * fixed a bug when checking whether one Boolean function is just a negated version of another one during symbolic execution diff --git a/include/hal_core/netlist/decorators/subgraph_netlist_decorator.h b/include/hal_core/netlist/decorators/subgraph_netlist_decorator.h index 06729ad2328..0eee1a8d31e 100644 --- a/include/hal_core/netlist/decorators/subgraph_netlist_decorator.h +++ b/include/hal_core/netlist/decorators/subgraph_netlist_decorator.h @@ -51,25 +51,28 @@ namespace hal * Get a deep copy of a netlist subgraph including all of its gates and nets, but excluding modules and groupings. * * @param[in] subgraph_gates - The gates making up the subgraph that shall be copied from the netlist. + * @param[in] all_global_io - Set `true` to mark all nets as global input or output that lost at least one source or destination in the copied netlist, `false` to only mark them if all sources or destinations were removed. Global inputs and outputs of the parent netlist will always also be annotated as global inputs or outputs. Defaults to `false`. * @return The copied subgraph netlist on success, an error otherwise. */ - Result> copy_subgraph_netlist(const std::vector& subgraph_gates) const; + Result> copy_subgraph_netlist(const std::vector& subgraph_gates, const bool all_global_io = false) const; /** * Get a deep copy of a netlist subgraph including all of its gates and nets, but excluding modules and groupings. * * @param[in] subgraph_gates - The gates making up the subgraph that shall be copied from the netlist. + * @param[in] all_global_io - Set `true` to mark all nets as global input or output that lost at least one source or destination in the copied netlist, `false` to only mark them if all sources or destinations were removed. Global inputs and outputs of the parent netlist will always also be annotated as global inputs or outputs. Defaults to `false`. * @return The copied subgraph netlist on success, an error otherwise. */ - Result> copy_subgraph_netlist(const std::vector& subgraph_gates) const; + Result> copy_subgraph_netlist(const std::vector& subgraph_gates, const bool all_global_io = false) const; /** * Get a deep copy of a netlist subgraph including all of its gates and nets, but excluding modules and groupings. * * @param[in] subgraph_module - The module making up the subgraph that shall be copied from the netlist. + * @param[in] all_global_io - Set `true` to mark all nets as global input or output that lost at least one source or destination in the copied netlist, `false` to only mark them if all sources or destinations were removed. Global inputs and outputs of the parent netlist will always also be annotated as global inputs or outputs. Defaults to `false`. * @return The copied subgraph netlist on success, an error otherwise. */ - Result> copy_subgraph_netlist(const Module* subgraph_module) const; + Result> copy_subgraph_netlist(const Module* subgraph_module, const bool all_global_io = false) const; /** * Get the combined Boolean function of a subgraph of combinational gates starting at the source of the provided subgraph output net. diff --git a/src/netlist/decorators/subgraph_netlist_decorator.cpp b/src/netlist/decorators/subgraph_netlist_decorator.cpp index b353074f0e3..c6956e90675 100644 --- a/src/netlist/decorators/subgraph_netlist_decorator.cpp +++ b/src/netlist/decorators/subgraph_netlist_decorator.cpp @@ -13,7 +13,7 @@ namespace hal { } - Result> SubgraphNetlistDecorator::copy_subgraph_netlist(const std::vector& subgraph_gates) const + Result> SubgraphNetlistDecorator::copy_subgraph_netlist(const std::vector& subgraph_gates, const bool all_global_io) const { std::unique_ptr c_netlist = netlist_factory::create_netlist(m_netlist.get_gate_library()); c_netlist->enable_automatic_net_checks(false); @@ -86,19 +86,30 @@ namespace hal { Net* net = m_netlist.get_net_by_id(c_net->get_id()); - // mark new global inputs - if (c_net->get_num_of_sources() == 0) + if (all_global_io) { - if (net->get_num_of_sources() != 0 || net->is_global_input_net()) + // mark all nets as global input that either lost a source or were a global input originally + if ((c_net->get_num_of_sources() < net->get_num_of_sources()) || net->is_global_input_net()) { c_netlist->mark_global_input_net(c_net); } - } - // mark nets that had a destination previously but now dont as global outputs - if (c_net->get_num_of_destinations() == 0) + // mark all nets as global output that either lost a destination or were a global output originally + if ((c_net->get_num_of_destinations() < net->get_num_of_destinations()) || net->is_global_output_net()) + { + c_netlist->mark_global_output_net(c_net); + } + } + else { - if (net->get_num_of_destinations() != 0 || net->is_global_output_net()) + // mark all nets as global input that now have zero sources but had a source in the orginal netlist or were a global input originally + if ((c_net->get_num_of_sources() == 0 && net->get_num_of_sources() != 0) || net->is_global_input_net()) + { + c_netlist->mark_global_input_net(c_net); + } + + // mark all nets as global output that now have zero destinations but had at least one destination in the orginal netlist or were a global output originally + if ((net->get_num_of_destinations() != 0 && c_net->get_num_of_destinations() == 0) || net->is_global_output_net()) { c_netlist->mark_global_output_net(c_net); } @@ -151,10 +162,10 @@ namespace hal return OK(std::move(c_netlist)); } - Result> SubgraphNetlistDecorator::copy_subgraph_netlist(const std::vector& subgraph_gates) const + Result> SubgraphNetlistDecorator::copy_subgraph_netlist(const std::vector& subgraph_gates, const bool all_global_io) const { const auto subgraph_gates_const = std::vector(subgraph_gates.begin(), subgraph_gates.end()); - if (auto res = copy_subgraph_netlist(subgraph_gates_const); res.is_ok()) + if (auto res = copy_subgraph_netlist(subgraph_gates_const, all_global_io); res.is_ok()) { return res; } @@ -164,9 +175,9 @@ namespace hal } } - Result> SubgraphNetlistDecorator::copy_subgraph_netlist(const Module* subgraph_module) const + Result> SubgraphNetlistDecorator::copy_subgraph_netlist(const Module* subgraph_module, const bool all_global_io) const { - if (auto res = copy_subgraph_netlist(subgraph_module->get_gates()); res.is_ok()) + if (auto res = copy_subgraph_netlist(subgraph_module->get_gates(), all_global_io); res.is_ok()) { return res; } diff --git a/src/python_bindings/bindings/subgraph_netlist_decorator.cpp b/src/python_bindings/bindings/subgraph_netlist_decorator.cpp index ab11183a31b..f67e44298c4 100644 --- a/src/python_bindings/bindings/subgraph_netlist_decorator.cpp +++ b/src/python_bindings/bindings/subgraph_netlist_decorator.cpp @@ -16,8 +16,8 @@ namespace hal py_subgraph_netlist_decorator.def( "copy_subgraph_netlist", - [](SubgraphNetlistDecorator& self, const std::vector& subgraph_gates) -> std::shared_ptr { - auto res = self.copy_subgraph_netlist(subgraph_gates); + [](SubgraphNetlistDecorator& self, const std::vector& subgraph_gates, const bool all_global_io = false) -> std::shared_ptr { + auto res = self.copy_subgraph_netlist(subgraph_gates, all_global_io); if (res.is_ok()) { return std::shared_ptr(res.get()); @@ -29,18 +29,20 @@ namespace hal } }, py::arg("subgraph_gates"), + py::arg("all_global_io") = false, R"( Get a deep copy of a netlist subgraph including all of its gates and nets, but excluding modules and groupings. :param list[hal_py.Gate] subgraph_gates: The gates making up the subgraph that shall be copied from the netlist. + :param bool all_global_io: Set ``True`` to mark all nets as global input or output that lost at least one source or destination in the copied netlist, ``False`` to only mark them if all sources or destinations were removed. Global inputs and outputs of the parent netlist will always also be annotated as global inputs or outputs. Defaults to ``False``. :returns: The copied subgraph netlist on success, None otherwise. :rtype: hal_py.Netlist or None )"); py_subgraph_netlist_decorator.def( "copy_subgraph_netlist", - [](SubgraphNetlistDecorator& self, const Module* subgraph_module) -> std::shared_ptr { - auto res = self.copy_subgraph_netlist(subgraph_module); + [](SubgraphNetlistDecorator& self, const Module* subgraph_module, const bool all_global_io = false) -> std::shared_ptr { + auto res = self.copy_subgraph_netlist(subgraph_module, all_global_io); if (res.is_ok()) { return std::shared_ptr(res.get()); @@ -52,10 +54,12 @@ namespace hal } }, py::arg("subgraph_module"), + py::arg("all_global_io") = false, R"( Get a deep copy of a netlist subgraph including all of its gates and nets, but excluding modules and groupings. :param hal_py.Module subgraph_module: The module making up the subgraph that shall be copied from the netlist. + :param bool all_global_io: Set ``True`` to mark all nets as global input or output that lost at least one source or destination in the copied netlist, ``False`` to only mark them if all sources or destinations were removed. Global inputs and outputs of the parent netlist will always also be annotated as global inputs or outputs. Defaults to ``False``. :returns: The copied subgraph netlist on success, None otherwise. :rtype: hal_py.Netlist or None )"); From f3e91d7cb4bbe3cea9085960c07426dc088e1ba4 Mon Sep 17 00:00:00 2001 From: "julian.speith" Date: Mon, 15 Jul 2024 16:51:34 +0200 Subject: [PATCH 14/17] move resynthesis functions --- .../include/resynthesis/plugin_resynthesis.h | 2 +- .../include/resynthesis/resynthesis.h | 146 ++ .../resynthesis/python/python_bindings.cpp | 165 ++- .../resynthesis/src/plugin_resynthesis.cpp | 6 +- plugins/resynthesis/src/resynthesis.cpp | 1279 +++++++++++++++++ 5 files changed, 1593 insertions(+), 5 deletions(-) create mode 100644 plugins/resynthesis/include/resynthesis/resynthesis.h create mode 100644 plugins/resynthesis/src/resynthesis.cpp diff --git a/plugins/resynthesis/include/resynthesis/plugin_resynthesis.h b/plugins/resynthesis/include/resynthesis/plugin_resynthesis.h index bb9a698c820..5946db914bd 100644 --- a/plugins/resynthesis/include/resynthesis/plugin_resynthesis.h +++ b/plugins/resynthesis/include/resynthesis/plugin_resynthesis.h @@ -24,7 +24,7 @@ // SOFTWARE. /** - * @file plugin_xilinx_toolbox.h + * @file plugin_resynthesis.h * @brief This file contains all functions related to the HAL plugin API. */ diff --git a/plugins/resynthesis/include/resynthesis/resynthesis.h b/plugins/resynthesis/include/resynthesis/resynthesis.h new file mode 100644 index 00000000000..8dec0e00090 --- /dev/null +++ b/plugins/resynthesis/include/resynthesis/resynthesis.h @@ -0,0 +1,146 @@ + +// 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. +// +// 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. + +/** + * @file resynthesis.h + * @brief This file contains functions to decompose or re-synthesize combinational parts of a gate-level netlist. + */ + +#pragma once + +#include "hal_core/defines.h" +#include "hal_core/utilities/result.h" + +namespace hal +{ + class Netlist; + class Gate; + class GateType; + class GateLibrary; + + namespace resynthesis + { + /** + * Decompose a combinational gate into a small circuit of AND, OR, XOR, and INVERT gates. + * For each output pin, the resolved Boolean function (only dependent on input pins) is determined. + * All these Boolean functions are then converted into a netlist using the previously mentioned primitive gates. + * The target gate is then replaced in the original netlist with the circuit that was just generated. + * The target gate is only deleted if `delete_gate` is set to `true`. + * Gate replacement will fail if the gate library of the netlist does not contain suitable AND, OR, XOR, and INVERT gate types. + * + * @param[in] nl - The netlist to operate on. + * @param[in] gate - The gate to decompose. + * @param[in] delete_gate - Set `true` to delete the original gate, `false` to keep it in the netlist. Defaults to `true`. + * @return OK() on success, an error otherwise. + */ + Result decompose_gate(Netlist* nl, Gate* gate, const bool delete_gate = true); + + /** + * Decompose all combinational gates of the specified types into small circuits of AND, OR, XOR, and INVERT gates. + * For all output pins of each gate, the resolved Boolean function (only dependent on input pins) is determined. + * All these Boolean functions are then converted into a circuit using the previously mentioned primitive gates. + * The target gates are then replaced (and thereby deleted) in the original netlist with the circuit that was just generated. + * Gate replacement will fail if the gate library of the netlist does not contain suitable AND, OR, XOR, and INVERT gate types. + * + * @param[in] nl - The netlist to operate on. + * @param[in] gate_types - The gate types to be decomposed. + * @return OK() and the number of decomposed gates on success, an error otherwise. + */ + Result decompose_gates_of_type(Netlist* nl, const std::vector& gate_types); + + /** + * Re-synthesize a combinational gate by calling Yosys on a functional description of the gate using a reduced gate library. + * For each output pin, the resolved Boolean function (only dependent on input pins) is determined. + * All these Boolean functions are then written to an HDL file that is functionally equivalent to the target gate. + * This file is fed to Yosys and subsequently synthesized to a netlist again by using the provided gate library. + * The provided gate library should be a subset of the gate library that was used to parse the netlist. + * The target gate is then replaced in the original netlist with the circuit that was just generated. + * The target gate is only deleted if `delete_gate` is set to `true`. + * + * @param[in] nl - The netlist to operate on. + * @param[in] gate - The gate to re-synthesize. + * @param[in] target_gl - The gate library that is a subset of the gate library used to parse the netlist. + * @param[in] delete_gate - Set `true` to delete the original gate, `false` to keep it in the netlist. Defaults to `true`. + * @return OK() on success, an error otherwise. + */ + Result resynthesize_gate(Netlist* nl, Gate* gate, GateLibrary* target_gl, const bool delete_gate = true); + + /** + * Re-synthesize all specified combinational gates by calling Yosys on a functional description of the gates using a reduced gate library. + * For all output pins of each gate, the resolved Boolean function (only dependent on input pins) is determined. + * All these Boolean functions are then written to an HDL file that is functionally equivalent to the target gates. + * This file is fed to Yosys and subsequently synthesized to a netlist again by using the provided gate library. + * The provided gate library should be a subset of the gate library that was used to parse the netlist. + * The target gates are then replaced in the original netlist with the circuit that was just generated. + * + * @param[in] nl - The netlist to operate on. + * @param[in] gates - The gates to re-synthesize. + * @param[in] target_gl - The gate library that is a subset of the gate library used to parse the netlist. + * @return OK() and the number of re-synthesized gates on success, an error otherwise. + */ + Result resynthesize_gates(Netlist* nl, const std::vector& gates, GateLibrary* target_gl); + + /** + * Re-synthesize all combinational gates of the specified types by calling Yosys on a functional description of the gates using a reduced gate library. + * For all output pins of each gate, the resolved Boolean function (only dependent on input pins) is determined. + * All these Boolean functions are then written to an HDL file that is functionally equivalent to the target gates. + * This file is fed to Yosys and subsequently synthesized to a netlist again by using the provided gate library. + * The provided gate library should be a subset of the gate library that was used to parse the netlist. + * The target gates are then replaced in the original netlist with the circuit that was just generated. + * + * @param[in] nl - The netlist to operate on. + * @param[in] gate_types - The gate types to be re-synthesized. + * @param[in] target_gl - The gate library that is a subset of the gate library used to parse the netlist. + * @return OK() and the number of re-synthesized gates on success, an error otherwise. + */ + Result resynthesize_gates_of_type(Netlist* nl, const std::vector& gate_types, GateLibrary* target_gl); + + // TODO update docs below + + /** + * Build a Verilog description of a subgraph of gates and synthesize a new technology mapped netlist of the whole subgraph with a logic synthesizer. + * Afterwards the original subgraph is replaced by the technology mapped netlist produced by the synthesizer. + * + * @param[in] nl - The netlist to operate on. + * @param[in] subgraph - The subgraph to resynthesize. + * @param[in] target_gl - Gate library containing the gates used for technology mapping. + * @return OK() and the number of decomposed gates on success, an error otherwise. + */ + Result resynthesize_subgraph(Netlist* nl, const std::vector& subgraph, GateLibrary* target_gl); + + /** + * Build a verilog description of the subgraph consisting of all the gates of the specified types. + * Then synthesize a new technology mapped netlist of the whole subgraph with a logic synthesizer. + * Afterwards the original subgraph is replaced by the technology mapped netlist produced by the synthesizer. + * + * @param[in] nl - The netlist to operate on. + * @param[in] gate_types - The gate types specifying which gates should be part of the subgraph. + * @param[in] target_gl - Gate library containing the gates used for technology mapping. + * @return OK() and the number of decomposed gates on success, an error otherwise. + */ + Result resynthesize_subgraph_of_type(Netlist* nl, const std::vector& gate_types, GateLibrary* target_gl); + } // namespace resynthesis +} // namespace hal diff --git a/plugins/resynthesis/python/python_bindings.cpp b/plugins/resynthesis/python/python_bindings.cpp index 7d2e42a08a6..5dfdc37bc9e 100644 --- a/plugins/resynthesis/python/python_bindings.cpp +++ b/plugins/resynthesis/python/python_bindings.cpp @@ -5,6 +5,7 @@ #include "pybind11/stl.h" #include "pybind11/stl_bind.h" #include "resynthesis/plugin_resynthesis.h" +#include "resynthesis/resynthesis.h" namespace py = pybind11; @@ -17,11 +18,11 @@ namespace hal #ifdef PYBIND11_MODULE PYBIND11_MODULE(resynthesis, m) { - m.doc() = "Provides functions to re-synthesize (parts of) a gate-level netlist."; + m.doc() = "Provides functions to decompose or re-synthesize combinational parts of a gate-level netlist."; #else PYBIND11_PLUGIN(resynthesis) { - py::module m("resynthesis", "Provides functions to re-synthesize (parts of) a gate-level netlist."); + py::module m("resynthesis", "Provides functions to decompose or re-synthesize combinational parts of a gate-level netlist."); #endif // ifdef PYBIND11_MODULE py::class_, BasePluginInterface> py_resynthesis_plugin( @@ -79,6 +80,166 @@ namespace hal :rtype: set[str] )"); + m.def( + "decompose_gate", + [](Netlist* nl, Gate* gate, const bool delete_gate = true) -> bool { + auto res = resynthesis::decompose_gate(nl, gate, delete_gate); + if (res.is_ok()) + { + return true; + } + else + { + log_error("python_context", "{}", res.get_error().get()); + return false; + } + }, + py::arg("nl"), + py::arg("gate"), + py::arg("delete_gate") = true, + R"( + Decompose a combinational gate into a small circuit of AND, OR, XOR, and INVERT gates. + For each output pin, the resolved Boolean function (only dependent on input pins) is determined. + All these Boolean functions are then converted into a netlist using the previously mentioned primitive gates. + The target gate is then replaced in the original netlist with the circuit that was just generated. + The target gate is only deleted if ``delete_gate`` is set to ``True``. + Gate replacement will fail if the gate library of the netlist does not contain suitable AND, OR, XOR, and INVERT gate types. + + :param hal_py.Netlist nl: The netlist to operate on. + :param hal_py.Gate gate: The gate to decompose. + :param bool delete_gate: Set ``True`` to delete the original gate, ``False`` to keep it in the netlist. Defaults to ``True``. + :returns: ``True`` on success, ``False`` otherwise. + :rtype: bool + )"); + + m.def( + "decompose_gates_of_type", + [](Netlist* nl, const std::vector& gate_types) -> std::optional { + auto res = resynthesis::decompose_gates_of_type(nl, gate_types); + if (res.is_ok()) + { + return res.get(); + } + else + { + log_error("python_context", "{}", res.get_error().get()); + return std::nullopt; + } + }, + py::arg("nl"), + py::arg("gate_types"), + R"( + Decompose all combinational gates of the specified types into small circuits of AND, OR, XOR, and INVERT gates. + For all output pins of each gate, the resolved Boolean function (only dependent on input pins) is determined. + All these Boolean functions are then converted into a circuit using the previously mentioned primitive gates. + The target gates are then replaced (and thereby deleted) in the original netlist with the circuit that was just generated. + Gate replacement will fail if the gate library of the netlist does not contain suitable AND, OR, XOR, and INVERT gate types. + + :param hal_py.Netlist nl: The netlist to operate on. + :param list[hal_py.GateType] gate_types: The gate types to be decomposed. + :returns: The number of decomposed gates on success, ``None`` otherwise. + :rtype: int or ``None`` + )"); + + m.def( + "resynthesize_gate", + [](Netlist* nl, Gate* gate, GateLibrary* target_gl, const bool delete_gate) -> bool { + auto res = resynthesis::resynthesize_gate(nl, gate, target_gl, delete_gate); + if (res.is_ok()) + { + return true; + } + else + { + log_error("python_context", "{}", res.get_error().get()); + return false; + } + }, + py::arg("nl"), + py::arg("gate"), + py::arg("target_gl"), + py::arg("delete_gate") = true, + R"( + Re-synthesize a combinational gate by calling Yosys on a functional description of the gate using a reduced gate library. + For all output pins of each gate, the resolved Boolean function (only dependent on input pins) is determined. + All these Boolean functions are then written to an HDL file that is functionally equivalent to the target gate. + This file is fed to Yosys and subsequently synthesized to a netlist again by using the provided gate library. + The provided gate library should be a subset of the gate library that was used to parse the netlist. + The target gate is then replaced in the original netlist with the circuit that was just generated. + The target gate is only deleted if ``delete_gate`` is set to ``True``. + + :param hal_py.Netlist nl: The netlist to operate on. + :param hal_py.Gate gate: The gate to re-synthesize. + :param hal_py.GateLibrary target_gl: The gate library that is a subset of the gate library used to parse the netlist. + :param bool delete_gate: Set ``True`` to delete the original gate, ``False`` to keep it in the netlist. Defaults to ``True``. + :returns: ``True`` on success, ``False`` otherwise. + :rtype: bool + )"); + + m.def( + "resynthesize_gates", + [](Netlist* nl, const std::vector& gates, GateLibrary* target_gl) -> std::optional { + auto res = resynthesis::resynthesize_gates(nl, gates, target_gl); + if (res.is_ok()) + { + return res.get(); + } + else + { + log_error("python_context", "{}", res.get_error().get()); + return std::nullopt; + } + }, + py::arg("nl"), + py::arg("gates"), + py::arg("target_gl"), + R"( + Re-synthesize all specified combinational gates by calling Yosys on a functional description of the gates using a reduced gate library. + For all output pins of each gate, the resolved Boolean function (only dependent on input pins) is determined. + All these Boolean functions are then written to an HDL file that is functionally equivalent to the target gates. + This file is fed to Yosys and subsequently synthesized to a netlist again by using the provided gate library. + The provided gate library should be a subset of the gate library that was used to parse the netlist. + The target gates are then replaced in the original netlist with the circuit that was just generated. + + :param hal_py.Netlist nl: The netlist to operate on. + :param hal_py.Gate g: The gates to re-synthesize. + :param hal_py.GateLibrary target_gl: The gate library that is a subset of the gate library used to parse the netlist. + :returns: The number of re-synthesized gates on success, ``None`` otherwise. + :rtype: int or ``None`` + )"); + + m.def( + "resynthesize_gates_of_type", + [](Netlist* nl, const std::vector& gate_types, GateLibrary* target_gl) -> std::optional { + auto res = resynthesis::resynthesize_gates_of_type(nl, gate_types, target_gl); + if (res.is_ok()) + { + return res.get(); + } + else + { + log_error("python_context", "{}", res.get_error().get()); + return std::nullopt; + } + }, + py::arg("nl"), + py::arg("gate_types"), + py::arg("target_gl"), + R"( + Re-synthesize all combinational gates of the specified types by calling Yosys on a functional description of the gates using a reduced gate library. + For all output pins of each gate, the resolved Boolean function (only dependent on input pins) is determined. + All these Boolean functions are then written to an HDL file that is functionally equivalent to the target gates. + This file is fed to Yosys and subsequently synthesized to a netlist again by using the provided gate library. + The provided gate library should be a subset of the gate library that was used to parse the netlist. + The target gates are then replaced in the original netlist with the circuit that was just generated. + + :param hal_py.Netlist nl: The netlist to operate on. + :param list[hal_py.GateType] gate_types: The gate types to be re-synthesized. + :param hal_py.GateLibrary target_gl: The gate library that is a subset of the gate library used to parse the netlist. + :returns: The number of re-synthesized gates on success, ``None`` otherwise. + :rtype: int or ``None`` + )"); + #ifndef PYBIND11_MODULE return m.ptr(); #endif // PYBIND11_MODULE diff --git a/plugins/resynthesis/src/plugin_resynthesis.cpp b/plugins/resynthesis/src/plugin_resynthesis.cpp index 0d1bdcd2639..a37286b7f7d 100644 --- a/plugins/resynthesis/src/plugin_resynthesis.cpp +++ b/plugins/resynthesis/src/plugin_resynthesis.cpp @@ -19,11 +19,13 @@ namespace hal std::string ResynthesisPlugin::get_description() const { - return "Provides functions to re-synthesize (parts of) a gate-level netlist."; + return "Provides functions to decompose or re-synthesize combinational parts of a gate-level netlist."; } std::set ResynthesisPlugin::get_dependencies() const { - return {}; + std::set retval; + retval.insert("genlib_writer"); + return retval; } } // namespace hal diff --git a/plugins/resynthesis/src/resynthesis.cpp b/plugins/resynthesis/src/resynthesis.cpp new file mode 100644 index 00000000000..345bfddb358 --- /dev/null +++ b/plugins/resynthesis/src/resynthesis.cpp @@ -0,0 +1,1279 @@ +#include "resynthesis/resynthesis.h" + +#include "hal_core/netlist/decorators/boolean_function_net_decorator.h" +#include "hal_core/netlist/decorators/netlist_modification_decorator.h" +#include "hal_core/netlist/decorators/subgraph_netlist_decorator.h" +#include "hal_core/netlist/gate.h" +#include "hal_core/netlist/gate_library/gate_library_manager.h" +#include "hal_core/netlist/module.h" +#include "hal_core/netlist/net.h" +#include "hal_core/netlist/netlist.h" +#include "hal_core/netlist/netlist_factory.h" +#include "hal_core/netlist/netlist_writer/netlist_writer_manager.h" + +#include + +namespace hal +{ + namespace yosys + { + const std::string get_helper_gate_lib() + { + std::string yosys_gate_lib = ""; + yosys_gate_lib += "// GND\n"; + yosys_gate_lib += "module GND (G);\n"; + yosys_gate_lib += "output G;\n"; + yosys_gate_lib += "assign G = 1\'b0;\n"; + yosys_gate_lib += "endmodule\n"; + yosys_gate_lib += "\n"; + yosys_gate_lib += "// VCC\n"; + yosys_gate_lib += "module VCC (P);\n"; + yosys_gate_lib += "output P;\n"; + yosys_gate_lib += "assign P = 1\'b1;\n"; + yosys_gate_lib += "endmodule\n"; + yosys_gate_lib += "\n"; + yosys_gate_lib += "// BUF\n"; + yosys_gate_lib += "module HAL_BUF (A, O);\n"; + yosys_gate_lib += "input A;\n"; + yosys_gate_lib += "output O;\n"; + yosys_gate_lib += "assign O = A;\n"; + yosys_gate_lib += "endmodule\n"; + yosys_gate_lib += "\n"; + yosys_gate_lib += "// INV\n"; + yosys_gate_lib += "module HAL_INV (A, O);\n"; + yosys_gate_lib += "input A;\n"; + yosys_gate_lib += "output O;\n"; + yosys_gate_lib += "assign O = ~A;\n"; + yosys_gate_lib += "endmodule\n"; + yosys_gate_lib += "\n"; + yosys_gate_lib += "// AND2\n"; + yosys_gate_lib += "module HAL_AND2 (A, B, O);\n"; + yosys_gate_lib += "input A, B;\n"; + yosys_gate_lib += "output O;\n"; + yosys_gate_lib += "assign O = A & B;\n"; + yosys_gate_lib += "endmodule\n"; + yosys_gate_lib += "\n"; + yosys_gate_lib += "// AND3\n"; + yosys_gate_lib += "module HAL_AND3 (A, B, C, O);\n"; + yosys_gate_lib += "input A, B, C;\n"; + yosys_gate_lib += "output O;\n"; + yosys_gate_lib += "assign O = A & B & C;\n"; + yosys_gate_lib += "endmodule\n"; + yosys_gate_lib += "\n"; + yosys_gate_lib += "// AND4\n"; + yosys_gate_lib += "module HAL_AND4 (A, B, C, D, O);\n"; + yosys_gate_lib += "input A, B, C, D;\n"; + yosys_gate_lib += "output O;\n"; + yosys_gate_lib += "assign O = A & B & C & D;\n"; + yosys_gate_lib += "endmodule\n"; + yosys_gate_lib += "\n"; + yosys_gate_lib += "// OR2\n"; + yosys_gate_lib += "module HAL_OR2 (A, B, O);\n"; + yosys_gate_lib += "input A, B;\n"; + yosys_gate_lib += "output O;\n"; + yosys_gate_lib += "assign O = A | B;\n"; + yosys_gate_lib += "endmodule\n"; + yosys_gate_lib += "\n"; + yosys_gate_lib += "// OR3\n"; + yosys_gate_lib += "module HAL_OR3 (A, B, C, O);\n"; + yosys_gate_lib += "input A, B, C;\n"; + yosys_gate_lib += "output O;\n"; + yosys_gate_lib += "assign O = A | B | C;\n"; + yosys_gate_lib += "endmodule\n"; + yosys_gate_lib += "\n"; + yosys_gate_lib += "// OR4\n"; + yosys_gate_lib += "module HAL_OR4 (A, B, C, D, O);\n"; + yosys_gate_lib += "input A, B, C, D;\n"; + yosys_gate_lib += "output O;\n"; + yosys_gate_lib += "assign O = A | B | C | D;\n"; + yosys_gate_lib += "endmodule\n"; + yosys_gate_lib += "\n"; + yosys_gate_lib += "// XOR2\n"; + yosys_gate_lib += "module HAL_XOR2 (A, B, O);\n"; + yosys_gate_lib += "input A, B;\n"; + yosys_gate_lib += "output O;\n"; + yosys_gate_lib += "assign O = A ^ B;\n"; + yosys_gate_lib += "endmodule\n"; + yosys_gate_lib += "\n"; + yosys_gate_lib += "// XOR3\n"; + yosys_gate_lib += "module HAL_XOR3 (A, B, C, O);\n"; + yosys_gate_lib += "input A, B, C;\n"; + yosys_gate_lib += "output O;\n"; + yosys_gate_lib += "assign O = A ^ B ^ C;\n"; + yosys_gate_lib += "endmodule\n"; + yosys_gate_lib += "\n"; + yosys_gate_lib += "// XOR4\n"; + yosys_gate_lib += "module HAL_XOR4 (A, B, C, D, O);\n"; + yosys_gate_lib += "input A, B, C, D;\n"; + yosys_gate_lib += "output O;\n"; + yosys_gate_lib += "assign O = A ^ B ^ C ^ D;\n"; + yosys_gate_lib += "endmodule\n"; + yosys_gate_lib += "\n"; + yosys_gate_lib += "// XNOR2\n"; + yosys_gate_lib += "module HAL_XNOR2 (A, B, O);\n"; + yosys_gate_lib += "input A, B;\n"; + yosys_gate_lib += "output O;\n"; + yosys_gate_lib += "assign O = (! (A ^ B));\n"; + yosys_gate_lib += "endmodule\n"; + yosys_gate_lib += "\n"; + yosys_gate_lib += "// XNOR3\n"; + yosys_gate_lib += "module HAL_XNOR3 (A, B, C, O);\n"; + yosys_gate_lib += "input A, B, C;\n"; + yosys_gate_lib += "output O;\n"; + yosys_gate_lib += "assign O =(! (A ^ (B ^ C)));\n"; + yosys_gate_lib += "endmodule\n"; + yosys_gate_lib += "\n"; + yosys_gate_lib += "// XNOR4\n"; + yosys_gate_lib += "module HAL_XNOR4 (A, B, C, D, O);\n"; + yosys_gate_lib += "input A, B, C, D;\n"; + yosys_gate_lib += "output O;\n"; + yosys_gate_lib += "assign O = (! (A ^ (B ^ (C ^ D))));\n"; + yosys_gate_lib += "endmodule\n"; + yosys_gate_lib += "\n"; + yosys_gate_lib += "module HAL_MUX (A, B, S, O);\n"; + yosys_gate_lib += "input A, B, S;\n"; + yosys_gate_lib += "output O;\n"; + yosys_gate_lib += "assign O = ((A & S) | (B & (! S)));\n"; + yosys_gate_lib += "endmodule\n"; + yosys_gate_lib += "\n"; + yosys_gate_lib += "module HAL_MUX3 (A, B, C, S1, S2, O);\n"; + yosys_gate_lib += "input A, B, C, S1, S2;\n"; + yosys_gate_lib += "output O;\n"; + yosys_gate_lib += "assign O = ((A & S1) | (!S1 & ((B & S2) | (C & !S2))));\n"; + yosys_gate_lib += "endmodule\n"; + yosys_gate_lib += "\n"; + yosys_gate_lib += "module HAL_MUX4 (A, B, C, D, S1, S2, O);\n"; + yosys_gate_lib += "input A, B, C, D, S1, S2;\n"; + yosys_gate_lib += "output O;\n"; + yosys_gate_lib += "assign O = ((A & S1 & S2) | (B & S1 & !S2) | (C & !S1 & S2) | (D & !S1 & !S2));\n"; + yosys_gate_lib += "endmodule\n"; + yosys_gate_lib += "\n"; + yosys_gate_lib += "module HAL_MUX5 (A, B, C, D, E, S1, S2, S3, O);\n"; + yosys_gate_lib += "input A, B, C, D, E, S1, S2, S3;\n"; + yosys_gate_lib += "output O;\n"; + yosys_gate_lib += "assign O = ((~S1 & ~S2 & ~S3 & A) | (S1 & ~S2 & ~S3 & B) | (~S1 & S2 & ~S3 & C) | (~S1 & ~S2 & S3 & D) | (S1 & S2 & ~S3 & E));\n"; + yosys_gate_lib += "endmodule\n"; + yosys_gate_lib += "\n"; + yosys_gate_lib += "module HAL_MUX6 (A, B, C, D, E, F, S1, S2, S3, O);\n"; + yosys_gate_lib += "input A, B, C, D, E, F, S1, S2, S3;\n"; + yosys_gate_lib += "output O;\n"; + yosys_gate_lib += "assign O = ((~S1 & ~S2 & ~S3 & A) | (S1 & ~S2 & ~S3 & B) | (~S1 & S2 & ~S3 & C) | (~S1 & ~S2 & S3 & D) | (S1 & S2 & ~S3 & E) | (S1 & ~S2 & S3 & F));\n"; + yosys_gate_lib += "endmodule\n"; + yosys_gate_lib += "\n"; + yosys_gate_lib += "module HAL_MUX7 (A, B, C, D, E, F, G, S1, S2, S3, O);\n"; + yosys_gate_lib += "input A, B, C, D, E, F, G, S1, S2, S3;\n"; + yosys_gate_lib += "output O;\n"; + yosys_gate_lib += + "assign O = ((~S1 & ~S2 & ~S3 & A) | (S1 & ~S2 & ~S3 & B) | (~S1 & S2 & ~S3 & C) | (~S1 & ~S2 & S3 & D) | (S1 & S2 & ~S3 & E) | (S1 & ~S2 & S3 & F) | (S1 & S2 & S3 & G));\n"; + yosys_gate_lib += "endmodule\n"; + yosys_gate_lib += "\n"; + yosys_gate_lib += "module HAL_MUX8 (A, B, C, D, E, F, G, H, S1, S2, S3, O);\n"; + yosys_gate_lib += "input A, B, C, D, E, F, G, H, S1, S2, S3;\n"; + yosys_gate_lib += "output O;\n"; + yosys_gate_lib += + "assign O = ((~S1 & ~S2 & ~S3 & A) | (S1 & ~S2 & ~S3 & B) | (~S1 & S2 & ~S3 & C) | (~S1 & ~S2 & S3 & D) | (S1 & S2 & ~S3 & E) | (S1 & ~S2 & S3 & F) | (S1 & S2 & S3 & G) | " + "(H & S1 & S2 & S3));\n"; + yosys_gate_lib += "endmodule\n"; + yosys_gate_lib += "\n"; + + return yosys_gate_lib; + } + + Result query_binary_path() + { + static const std::vector yosys_binary_paths = {"/usr/bin/yosys", "/usr/local/bin/yosys", "/opt/homebrew/bin/yosys", "/opt/yosys/yosys"}; + + for (const auto& path : yosys_binary_paths) + { + if (std::filesystem::exists(path)) + { + return OK(path); + } + } + + return ERR("could not query binary path: no binary found for yosys logic synthesis tool"); + } + } // namespace yosys + + namespace + { + std::string new_net_name(const Net* dst_net, const Net* new_net) + { + return new_net->get_name() + "_" + std::to_string(dst_net->get_id()) + "_NEW_NET"; + } + + std::string new_gate_name(const Gate* dst_gate, const Gate* new_gate) + { + return new_gate->get_name() + "_" + std::to_string(dst_gate->get_id()) + "_NEW_GATE"; + } + + Result delete_subgraph(Netlist* nl, const std::vector subgraph) + { + // TODO currently only gates are deleted, not nets... + for (const auto& g : subgraph) + { + if (!nl->delete_gate(g)) + { + return ERR("unable to delete subgraph: failed to delete gate '" + g->get_name() + "' with ID " + std::to_string(g->get_id()) + " in netlist with ID " + + std::to_string(nl->get_id())); + } + } + + return OK({}); + } + + // NOTE there are about a hundred more checks that we could do here + Result replace_subgraph_with_netlist(const std::vector& subgraph, + const std::unordered_map>& global_io_mapping, + const Netlist* src_nl, + Netlist* dst_nl, + const bool delete_subgraph_gates) + { + std::unordered_map gate_name_to_gate; + + const auto dst_gl = dst_nl->get_gate_library(); + + // add all gates of the source netlist to the destination netlist + for (const auto src_g : src_nl->get_gates()) + { + const auto src_gt = src_g->get_type(); + const auto dst_gt = dst_gl->get_gate_type_by_name(src_gt->get_name()); + if (!dst_gt) + { + return ERR("unable to replace subgraph with netlist: gate library '" + dst_gl->get_name() + "' does not contain the required gate type " + src_gt->get_name()); + } + + auto new_gate = dst_nl->create_gate(dst_gt, "TEMP"); + const std::string new_name = new_gate_name(new_gate, src_g); + new_gate->set_name(new_name); + + gate_name_to_gate.insert({src_g->get_name(), new_gate}); + } + + // connect all nets of the source netlist to the destination netlist + for (const auto src_n : src_nl->get_nets()) + { + Net* new_net = nullptr; + + // edge case for global inputs + if (src_n->is_global_input_net()) + { + if (const auto it = global_io_mapping.find(src_n); it != global_io_mapping.end()) + { + const auto& net_connections = global_io_mapping.at(src_n); + if (net_connections.size() != 1) + { + return ERR("unable to replace subgraph with netlist: found " + std::to_string(net_connections.size()) + " net connections to the global input " + src_n->get_name() + + ", this would lead to multi-driven nets"); + } + new_net = net_connections.front(); + } + else + { + return ERR("unable to replace subgraph with netlist: failed to locate mapped net in destination netlist for global I/O net '" + src_n->get_name() + "' with ID " + + std::to_string(src_n->get_id())); + } + } + else if (src_n->is_global_output_net()) + { + if (const auto it = global_io_mapping.find(src_n); it != global_io_mapping.end()) + { + const auto& net_connections = global_io_mapping.at(src_n); + new_net = net_connections.front(); + + if (net_connections.size() != 1) + { + log_warning("resynthesis", "found multiple io connections for net '{}' with ID {}, this might lead to missing nets in the netlist", src_n->get_name(), src_n->get_id()); + // for (const auto& net : net_connections) + // { + // std::cout << net->get_id() << " - " << net->get_name() << std::endl; + // } + + // if a single global output of the src netlist leads to multiple nets in the dst netlist, that means that the nets are functionally equivalent and we can connect/merge them. + // however this can lead to nets disappearing from the dst netlist which might be unexpected behavior. + + for (u32 i = 1; i < net_connections.size(); i++) + { + const auto& res = NetlistModificationDecorator(*dst_nl).connect_nets(new_net, net_connections.at(i)); + if (res.is_error()) + { + return ERR("unable to replace subgraph with netlist: failed to connect/merge all the net connections of net '" + src_n->get_name() + "' with ID " + + std::to_string(src_n->get_id())); + } + } + } + else + { + new_net = net_connections.front(); + } + } + else + { + return ERR("unable to replace subgraph with netlist: failed to locate mapped net in destination netlist for global I/O net '" + src_n->get_name() + "' with ID " + + std::to_string(src_n->get_id())); + } + } + else if (src_n->is_gnd_net()) + { + // set new net to an existing gnd net + const auto gnd_gate = dst_nl->get_gnd_gates().front(); + const auto out_net = gnd_gate->get_fan_out_nets().front(); + new_net = out_net; + } + else if (src_n->is_vcc_net()) + { + const auto vcc_gate = dst_nl->get_vcc_gates().front(); + const auto out_net = vcc_gate->get_fan_out_nets().front(); + new_net = out_net; + } + else + { + new_net = dst_nl->create_net("TEMP"); + const std::string new_name = new_net_name(new_net, src_n); + new_net->set_name(new_name); + } + + // connect net to sources + for (const auto src_ep : src_n->get_sources()) + { + const auto org_src_name = src_ep->get_gate()->get_name(); + const auto org_src_pin_name = src_ep->get_pin()->get_name(); + auto new_src_g = gate_name_to_gate.at(org_src_name); + if (new_net->add_source(new_src_g, org_src_pin_name) == nullptr) + { + return ERR("unable to replace subgraph with netlist: failed to add gate '" + new_src_g->get_name() + "' with ID " + std::to_string(new_src_g->get_id()) + " at pin '" + + org_src_pin_name + "' as new source to net '" + new_net->get_name() + "' with ID " + std::to_string(new_net->get_id())); + } + } + + // connect net to destinations + for (const auto src_ep : src_n->get_destinations()) + { + const auto org_dst_name = src_ep->get_gate()->get_name(); + const auto org_dst_pin_name = src_ep->get_pin()->get_name(); + auto new_dst_g = gate_name_to_gate.at(org_dst_name); + if (!new_net->add_destination(new_dst_g, org_dst_pin_name)) + { + return ERR("unable to replace subgraph with netlist: failed to add gate '" + new_dst_g->get_name() + "' with ID " + std::to_string(new_dst_g->get_id()) + " at pin '" + + org_dst_pin_name + "' as new destination to net '" + new_net->get_name() + "' with ID " + std::to_string(new_net->get_id())); + } + } + } + + // delete subgraph gates if flag is set + if (delete_subgraph_gates) + { + auto delete_res = delete_subgraph(dst_nl, subgraph); + if (delete_res.is_error()) + { + return ERR_APPEND(delete_res.get_error(), "unable to replace subgraph with netlist: failed to delete subgraph"); + } + } + + return OK({}); + } + + Result replace_gate_with_netlist(Gate* g, const Netlist* src_nl, Netlist* dst_nl, const bool delete_gate = true) + { + std::unordered_map> global_io_mapping; + + for (const auto& g_i : src_nl->get_top_module()->get_input_pins()) + { + const auto& pin_name = g_i->get_name(); + const auto i_net = g->get_fan_in_net(pin_name); + + if (i_net == nullptr) + { + return ERR("unable to replace gate '" + g->get_name() + "' with ID " + std::to_string(g->get_id()) + " with netlist: failed to find net connected to gate pin '" + pin_name + "'"); + } + + global_io_mapping[g_i->get_net()].push_back(i_net); + } + + for (const auto& g_o : src_nl->get_top_module()->get_output_pins()) + { + const auto& pin_name = g_o->get_name(); + const auto o_net = g->get_fan_out_net(pin_name); + + if (o_net == nullptr) + { + return ERR("unable to replace gate '" + g->get_name() + "' with ID " + std::to_string(g->get_id()) + " with netlist: failed to find net connected to gate pin '" + pin_name + "'"); + } + + global_io_mapping[g_o->get_net()].push_back(o_net); + } + + return replace_subgraph_with_netlist({g}, global_io_mapping, src_nl, dst_nl, delete_gate); + } + + Result, std::vector>> + find_gate_type(const GateLibrary* gl, const std::set& properties, const u32 num_inputs, const u32 num_outputs) + { + const auto get_valid_input_pins = [](const GateType* gt) -> std::vector { + return gt->get_pins([](const GatePin* gp) { return (gp->get_direction() == PinDirection::input) && (gp->get_type() != PinType::power) && (gp->get_type() != PinType::ground); }); + }; + + const auto get_valid_output_pins = [](const GateType* gt) -> std::vector { + return gt->get_pins([](const GatePin* gp) { return (gp->get_direction() == PinDirection::output) && (gp->get_type() != PinType::power) && (gp->get_type() != PinType::ground); }); + }; + + // get types that match exactly with the properties and have the exact amount of input pins (excluding power pins) + const auto candidates = gl->get_gate_types([properties, num_inputs, get_valid_input_pins, num_outputs, get_valid_output_pins](const GateType* gt) { + return (gt->get_properties() == properties) && (get_valid_input_pins(gt).size() == num_inputs) && (get_valid_output_pins(gt).size() == num_outputs); + }); + + if (candidates.empty()) + { + return ERR("unable to find gate type matching the description"); + } + + GateType* valid_gate_type = candidates.begin()->second; + + return OK({valid_gate_type, get_valid_input_pins(valid_gate_type), get_valid_output_pins(valid_gate_type)}); + } + + // TODO change this to return a netlist. This would allow saving the decomposition of a specific gate type + Result build_gate_tree_from_boolean_function(Netlist* nl, const BooleanFunction& bf, const std::map& var_name_to_net, const Gate* org_gate = nullptr) + { + const auto create_gate_name = [](const Gate* new_gate, const Gate* original_gate) -> std::string { + const std::string new_name = (original_gate == nullptr) ? "new_gate_" : original_gate->get_name() + "_decomposed_"; + return new_name + std::to_string(new_gate->get_id()); + }; + + const auto create_net_name = [](const Net* new_net, const Gate* original_gate) -> std::string { + const std::string new_name = (original_gate == nullptr) ? "new_net_" : original_gate->get_name() + "_decomposed_"; + return new_name + std::to_string(new_net->get_id()); + }; + + if (bf.is_empty()) + { + return ERR("cannot build gate tree for Boolean function: Boolean function is empty"); + } + + if (bf.is_index()) + { + return ERR("cannot build gate tree for Boolean function: Boolean function is of type index"); + } + + if (bf.size() != 1) + { + return ERR("cannot build gate tree for Boolean function: Boolean function if of size " + std::to_string(bf.size()) + " but we only handle size 1"); + } + + if (bf.is_constant()) + { + if (bf.has_constant_value(0)) + { + static Net* zero = nl->get_nets([](const Net* n) { return n->is_gnd_net(); }).front(); + return OK(zero); + } + + if (bf.has_constant_value(1)) + { + static Net* one = nl->get_nets([](const Net* n) { return n->is_vcc_net(); }).front(); + return OK(one); + } + } + + if (bf.is_variable()) + { + if (const auto it = var_name_to_net.find(bf.get_variable_name().get()); it == var_name_to_net.end()) + { + return ERR("cannot build gate tree for Boolean function: found variable '" + bf.get_variable_name().get() + "' with no corresponding net provided"); + } + else + { + return OK(it->second); + } + } + + if (!bf.get_top_level_node().is_operation()) + { + return ERR("cannot build gate tree for Boolean function: cannot handle node type of top level node '" + bf.get_top_level_node().to_string() + "'"); + } + + const auto operation = bf.get_top_level_node().type; + const auto parameters = bf.get_parameters(); + + // TODO put this into a function that only searches for the gate types when they are actually needed + static const auto inv_type_res = find_gate_type(nl->get_gate_library(), {GateTypeProperty::combinational, GateTypeProperty::c_inverter}, 1, 1); + static const auto and_type_res = find_gate_type(nl->get_gate_library(), {GateTypeProperty::combinational, GateTypeProperty::c_and}, 2, 1); + static const auto or_type_res = find_gate_type(nl->get_gate_library(), {GateTypeProperty::combinational, GateTypeProperty::c_or}, 2, 1); + static const auto xor_type_res = find_gate_type(nl->get_gate_library(), {GateTypeProperty::combinational, GateTypeProperty::c_xor}, 2, 1); + + if (inv_type_res.is_error()) + { + return ERR("cannot build gate tree for Boolean function: failed to find valid 'INVERT' gate type"); + } + + if (and_type_res.is_error()) + { + return ERR("cannot build gate tree for Boolean function: failed to find valid 'AND' gate type"); + } + + if (or_type_res.is_error()) + { + return ERR("cannot build gate tree for Boolean function: failed to find valid 'OR' gate type"); + } + + if (xor_type_res.is_error()) + { + return ERR("cannot build gate tree for Boolean function: failed to find valid 'XOR' gate type"); + } + + const std::map, std::vector>> node_type_to_gate_type = { + {BooleanFunction::NodeType::Not, inv_type_res.get()}, + {BooleanFunction::NodeType::And, and_type_res.get()}, + {BooleanFunction::NodeType::Or, or_type_res.get()}, + {BooleanFunction::NodeType::Xor, xor_type_res.get()}, + }; + + std::vector parameter_nets; + for (const auto& p : parameters) + { + const auto tree_res = build_gate_tree_from_boolean_function(nl, p, var_name_to_net, org_gate); + if (tree_res.is_error()) + { + return ERR_APPEND(tree_res.get_error(), "cannot build gate tree for Boolean function: failed to do so for sub tree"); + } + parameter_nets.push_back(tree_res.get()); + } + + Gate* new_gate = nullptr; + Net* output_net = nl->create_net("__TEMP_NET_NAME__DECOMPOSED__"); + output_net->set_name(create_net_name(output_net, org_gate)); + + switch (operation) + { + case BooleanFunction::NodeType::Not: + case BooleanFunction::NodeType::And: + case BooleanFunction::NodeType::Or: + case BooleanFunction::NodeType::Xor: { + auto [gt, in_pins, out_pins] = node_type_to_gate_type.at(operation); + new_gate = nl->create_gate(gt, "__TEMP_GATE_NAME__DECOMPOSED__"); + for (u32 idx = 0; idx < parameter_nets.size(); idx++) + { + parameter_nets.at(idx)->add_destination(new_gate, in_pins.at(idx)); + } + output_net->add_source(new_gate, out_pins.front()); + break; + } + default: + break; + } + + if (new_gate == nullptr) + { + return ERR("cannot build gate tree for Boolean function: failed to create gate for operation '" + bf.get_top_level_node().to_string() + "'"); + } + + new_gate->set_name(create_gate_name(new_gate, org_gate)); + + if (org_gate != nullptr && !org_gate->get_module()->is_top_module()) + { + org_gate->get_module()->assign_gate(new_gate); + } + + return OK(output_net); + } + + Result> generate_decomposed_netlist_from_boolean_function(const std::vector>& bfs, const GateLibrary* gl) + { + auto nl = netlist_factory::create_netlist(gl); + + std::map var_name_to_net; + for (const auto& [out_pin_name, bf] : bfs) + { + const auto bf_vars = bf.get_variable_names(); + for (const auto& var : bf_vars) + { + if (var_name_to_net.find(var) != var_name_to_net.end()) + { + continue; + } + + Net* new_net = nl->create_net(var); + if (!new_net) + { + return ERR("unable to generate decomposed netlist from Boolean function: failed to create net for boolean input var '" + var + "'"); + } + + new_net->mark_global_input_net(); + var_name_to_net.insert({var, new_net}); + } + + auto new_out_net_res = build_gate_tree_from_boolean_function(nl.get(), bf, var_name_to_net, nullptr); + if (new_out_net_res.is_error()) + { + return ERR_APPEND(new_out_net_res.get_error(), "unable to generate decomposed netlist from Boolean function: failed to build gate tree for Boolean function"); + } + + Net* new_out_net = new_out_net_res.get(); + new_out_net->mark_global_output_net(); + new_out_net->set_name(out_pin_name); + + // rename top module pins to the var names. + // NOTE: this seems to be really depended on the order. Doing this earlier causes a crash. I did not fully understand what causes the top module pins to be created. + for (const auto& [var, in_net] : var_name_to_net) + { + auto in_pin = nl->get_top_module()->get_pin_by_net(in_net); + in_pin->set_name(var); + } + auto out_pin = nl->get_top_module()->get_pin_by_net(new_out_net); + out_pin->set_name(out_pin_name); + } + + return OK(std::move(nl)); + } + + std::string build_functional_verilog_module_from(const std::unordered_map& bfs) + { + std::unordered_set input_variable_names; + + for (const auto& [name, bf] : bfs) + { + for (const auto& var_name : bf.get_variable_names()) + { + input_variable_names.insert(var_name); + } + } + + std::string verilog_str = "module top ("; + + std::string var_str = ""; + std::string io_str = ""; + std::string function_str = ""; + + for (const auto& input_var : input_variable_names) + { + var_str += (input_var + ", "); + io_str += ("input " + input_var + ";\n"); + } + + for (const auto& [output_var, bf] : bfs) + { + var_str += (output_var + ", "); + io_str += ("output " + output_var + ";\n"); + function_str += ("assign " + output_var + " = " + bf.to_string() + ";\n"); + } + + var_str = var_str.substr(0, var_str.size() - 2); + + verilog_str += var_str; + verilog_str += ");\n"; + + verilog_str += io_str; + + verilog_str += "\n"; + + verilog_str += function_str; + + verilog_str += "\n"; + verilog_str += "endmodule\n"; + verilog_str += "\n"; + + return verilog_str; + } + + Result> generate_resynth_netlist_for_boolean_functions(const std::unordered_map& bfs, + const std::filesystem::path& genlib_path, + GateLibrary* target_gl, + const bool optimize_area) + { + const auto verilog_module = build_functional_verilog_module_from(bfs); + + auto base_path_res = utils::get_unique_temp_directory("resynthesis_"); + if (base_path_res.is_error()) + { + return ERR_APPEND(base_path_res.get_error(), "unable to resynthesize Boolean functions with yosys: failed to get unique temp directory"); + } + const std::filesystem::path base_path = base_path_res.get(); + const std::filesystem::path functional_netlist_path = base_path / "func_netlist.v"; + const std::filesystem::path resynthesized_netlist_path = base_path / "resynth_netlist.v"; + + std::filesystem::create_directory(base_path); + + std::ofstream out(functional_netlist_path); + out << verilog_module; + out.close(); + + auto yosys_query_res = yosys::query_binary_path(); + if (yosys_query_res.is_error()) + { + return ERR_APPEND(yosys_query_res.get_error(), "unable to resynthesize Boolean functions with yosys: failed to find yosys path"); + } + + const auto yosys_path = yosys_query_res.get(); + const std::string command = yosys_path + " -q -p " + "\"read -sv " + functional_netlist_path.string() + "; hierarchy -top top; proc; fsm; opt; memory; opt; techmap; opt; abc -genlib " + + genlib_path.string() + "; " + "write_verilog " + resynthesized_netlist_path.string() + "; clean\""; + + // TODO check again the proper way to start a subprocess + system(command.c_str()); + + auto resynth_nl = netlist_factory::load_netlist(resynthesized_netlist_path, target_gl); + + if (resynth_nl == nullptr) + { + return ERR("unable to resynthesize Boolean functions with yosys: failed to load re-synthesized netlist at " + resynthesized_netlist_path.string()); + } + + // TODO check whether this is needed here or maybe move this somewhere else + // yosys workaround for stupid net renaming + for (const auto& pin : resynth_nl->get_top_module()->get_input_pins()) + { + auto net = pin->get_net(); + net->set_name(pin->get_name()); + } + + // delete the created directory and the contained files + std::filesystem::remove_all(base_path); + + return OK(std::move(resynth_nl)); + } + + Result> generate_resynth_netlist_for_gate_level_subgraph(const Netlist* nl, + const std::vector& subgraph, + const std::filesystem::path& genlib_path, + GateLibrary* target_gl, + const bool optimize_area) + { + // TODO sanity check wether all gates in the subgraph have types that are in the genlib/target library + if (nl == nullptr) + { + return ERR("unable to re-synthesize gate-level subgraph: netlist is a 'nullptr'"); + } + + if (target_gl == nullptr) + { + return ERR("unable to re-synthesize gate-level subgraph: gate library is a 'nullptr'"); + } + + auto subgraph_nl_res = SubgraphNetlistDecorator(*nl).copy_subgraph_netlist(subgraph, true); + if (subgraph_nl_res.is_error()) + { + return ERR_APPEND(subgraph_nl_res.get_error(), "unable to re-synthesize gate-level subgraph: failed to copy subgraph netlist"); + } + const auto subgraph_nl = subgraph_nl_res.get(); + + auto base_path_res = utils::get_unique_temp_directory("resynthesis_"); + if (base_path_res.is_error()) + { + return ERR_APPEND(base_path_res.get_error(), "unable to re-synthesize Boolean functions with yosys: failed to get unique temp directory"); + } + const std::filesystem::path base_path = base_path_res.get(); + const std::filesystem::path org_netlist_path = base_path / "org_netlist.v"; + const std::filesystem::path resynthesized_netlist_path = base_path / "resynth_netlist.v"; + const std::filesystem::path yosys_helper_lib_path = base_path / "yosys_helper_lib.v"; + + std::filesystem::create_directory(base_path); + + if (!netlist_writer_manager::write(subgraph_nl.get(), org_netlist_path)) + { + return ERR("unable to re-synthesize gate-level subgraph: failed to write netlist to file"); + } + + // NOTE this is a way to get rid of the stupid timescale annotation that is added by the verilog writer (for the simulator!?) but cannot be parsed by yosys... + // Read the file into a vector of lines + std::vector lines; + std::ifstream input_file(org_netlist_path); + + std::string line; + while (std::getline(input_file, line)) + { + lines.push_back(line); + } + input_file.close(); + + // Remove the first line from the vector + if (!lines.empty()) + { + lines.erase(lines.begin()); + } + + // Write the modified lines back to the file + std::ofstream output_file(org_netlist_path); + for (const auto& line : lines) + { + output_file << line << std::endl; + } + output_file.close(); + + std::ofstream yosys_helper_lib_file(yosys_helper_lib_path); + yosys_helper_lib_file << yosys::get_helper_gate_lib(); + yosys_helper_lib_file.close(); + + auto yosys_query_res = yosys::query_binary_path(); + if (yosys_query_res.is_error()) + { + return ERR_APPEND(yosys_query_res.get_error(), "unable to re-synthesize Boolean functions with yosys: failed to find yosys path"); + } + + const auto yosys_path = yosys_query_res.get(); + const std::string command = yosys_path + " -q -p " + '"' + "read_verilog -sv " + org_netlist_path.string() + "; read_verilog " + yosys_helper_lib_path.string() + "; synth -flatten -top " + + subgraph_nl->get_design_name() + "; abc -genlib " + genlib_path.string() + "; " + "write_verilog " + resynthesized_netlist_path.string() + ";" + '"'; + + // log_debug("netlist_preprocessing", "yosys command: {}", command); + + system(command.c_str()); + + auto resynth_nl = netlist_factory::load_netlist(resynthesized_netlist_path, target_gl); + if (resynth_nl == nullptr) + { + return ERR("unable to re-synthesize gate-level netlist with yosys: failed to load resynthesized netlist at " + resynthesized_netlist_path.string()); + } + + // delete the created directory and the contained files + std::filesystem::remove_all(base_path); + + return OK(std::move(resynth_nl)); + } + + // TODO move to the netlist traversal decorator, currently existing in the machine learning branch + std::vector get_outputs_of_subgraph(const std::vector& subgraph) + { + std::unordered_set subgraph_set = {subgraph.begin(), subgraph.end()}; + std::unordered_set outputs; + + for (const auto g : subgraph) + { + for (const auto ep : g->get_successors()) + { + // check whether gate has a successor outside the subgraph + if (subgraph_set.find(ep->get_gate()) == subgraph_set.end()) + { + outputs.insert(ep->get_net()); + } + } + } + + return {outputs.begin(), outputs.end()}; + } + + Result> generate_resynth_netlist_for_gate(const Gate* g, GateLibrary* target_gl, const std::filesystem::path& genlib_path) + { + // build Boolean function for each output pin of the gate type + std::unordered_map output_pin_name_to_bf; + for (const auto pin : g->get_type()->get_output_pins()) + { + const auto bf_res = g->get_resolved_boolean_function(pin, true); + if (bf_res.is_error()) + { + return ERR_APPEND(bf_res.get_error(), + "unable to re-synthesize LUT type '" + g->get_type()->get_name() + "' for gate instance '" + g->get_name() + "' with ID " + std::to_string(g->get_id()) + + ": failed to build resolved Boolean function for pin '" + pin->get_name() + "'"); + } + + const auto bf = bf_res.get(); + output_pin_name_to_bf.insert({pin->get_name(), bf}); + } + + auto resynth_res = generate_resynth_netlist_for_boolean_functions(output_pin_name_to_bf, genlib_path, target_gl, true); + if (resynth_res.is_error()) + { + return ERR_APPEND(resynth_res.get_error(), + "unable to re-synthesize LUT type '" + g->get_type()->get_name() + "' for gate instance '" + g->get_name() + "' with ID " + std::to_string(g->get_id()) + + ": failed to re-synthesize Boolean functions of gate"); + } + + return OK(resynth_res.get()); + } + } // namespace + + namespace resynthesis + { + Result decompose_gate(Netlist* nl, Gate* g, const bool delete_gate) + { + // build Boolean function for each output pin of the gate + + // TODO use vector of pairs + std::vector> output_pin_name_to_bf; + for (const auto pin : g->get_type()->get_output_pins()) + { + const auto bf_res = g->get_resolved_boolean_function(pin, true); + if (bf_res.is_error()) + { + return ERR_APPEND(bf_res.get_error(), + "unable to decompose gate '" + g->get_name() + "' with ID " + std::to_string(g->get_id()) + ": failed to build resolved boolean function for pin '" + + pin->get_name() + "'"); + } + + const auto bf = bf_res.get(); + output_pin_name_to_bf.push_back({pin->get_name(), bf}); + } + + auto resynth_res = generate_decomposed_netlist_from_boolean_function(output_pin_name_to_bf, nl->get_gate_library()); + if (resynth_res.is_error()) + { + return ERR_APPEND(resynth_res.get_error(), "unable to decompose gate '" + g->get_name() + "' with ID " + std::to_string(g->get_id()) + ": failed to generate decomposed netlist"); + } + auto unique_resynth_nl = resynth_res.get(); + + const auto replace_res = replace_gate_with_netlist(g, unique_resynth_nl.get(), nl, false); + if (replace_res.is_error()) + { + return ERR_APPEND(replace_res.get_error(), + "unable to decompose gate '" + g->get_name() + "' with ID " + std::to_string(g->get_id()) + ": failed to replace gate with decomposed netlist"); + } + + if (delete_gate) + { + if (!nl->delete_gate(g)) + { + return ERR("unable to decompose gate '" + g->get_name() + "' with ID " + std::to_string(g->get_id()) + ": failed to delete original gate"); + } + } + + return OK({}); + } + + Result decompose_gates_of_type(Netlist* nl, const std::vector& gate_types) + { + std::map>, std::unique_ptr> gt_to_decomposed; + + u32 counter = 0; + for (const auto& gt : gate_types) + { + std::vector to_delete; + for (const auto& g : nl->get_gates([gt](const Gate* g) { return g->get_type() == gt; })) + { + const Netlist* resynth_nl = nullptr; + std::vector init_data; + + if (gt->has_property(GateTypeProperty::c_lut)) + { + const auto init_res = g->get_init_data(); + if (init_res.is_error()) + { + return ERR_APPEND(init_res.get_error(), + "unable to decompose gates of type: failed to get INIT string from gate '" + g->get_name() + "' with ID " + std::to_string(g->get_id())); + } + const auto init_vec = init_res.get(); + if (init_vec.size() != 1) + { + return ERR("unable tor decompose gates of type: got " + std::to_string(init_vec.size()) + " INIT strings for gate '" + g->get_name() + "' with ID " + + std::to_string(g->get_id())); + } + + init_data = init_vec; + } + + // get resynth netlist from cache or create it + if (const auto it = gt_to_decomposed.find({gt, init_data}); it != gt_to_decomposed.end()) + { + resynth_nl = it->second.get(); + } + else + { + // build Boolean function for each output pin of the gate type + + // TODO use vector of pairs + std::vector> output_pin_name_to_bf; + for (const auto pin : g->get_type()->get_output_pins()) + { + const auto bf_res = g->get_resolved_boolean_function(pin, true); + if (bf_res.is_error()) + { + return ERR_APPEND(bf_res.get_error(), + "unable to decompose gate type '" + g->get_type()->get_name() + "' for gate instance '" + g->get_name() + "' with ID " + std::to_string(g->get_id()) + + ": failed to build resolved Boolean function for pin '" + pin->get_name() + "'"); + } + + const auto bf = bf_res.get(); + output_pin_name_to_bf.push_back({pin->get_name(), bf}); + } + + auto resynth_res = generate_decomposed_netlist_from_boolean_function(output_pin_name_to_bf, nl->get_gate_library()); + if (resynth_res.is_error()) + { + return ERR_APPEND(resynth_res.get_error(), "unable to decompose gates of type: failed to generate decomposed netlist for gate type '" + gt->get_name() + "'"); + } + auto unique_resynth_nl = resynth_res.get(); + resynth_nl = unique_resynth_nl.get(); + + gt_to_decomposed.insert({std::make_pair(gt, init_data), std::move(unique_resynth_nl)}); + } + + const auto replace_res = replace_gate_with_netlist(g, resynth_nl, nl, false); + if (replace_res.is_error()) + { + return ERR_APPEND(replace_res.get_error(), + "unable to decompose gates of type '" + gt->get_name() + "': failed to replace gate '" + g->get_name() + "' with ID " + std::to_string(g->get_id())); + } + to_delete.push_back(g); + } + + for (const auto& g : to_delete) + { + counter += 1; + if (!nl->delete_gate(g)) + { + return ERR("unable to resynthesize gates of type '" + gt->get_name() + "': failed to delete gate '" + g->get_name() + "' with ID " + std::to_string(g->get_id())); + } + } + } + + return OK(counter); + } + + Result resynthesize_gate(Netlist* nl, Gate* gate, GateLibrary* target_gl, const bool delete_gate) + { + if (nl == nullptr) + { + return ERR("could not re-synthesize gate, netlist is a 'nullptr'"); + } + + if (gate == nullptr) + { + return ERR("could not re-synthesize gate, gate is a 'nullptr'"); + } + + if (target_gl == nullptr) + { + return ERR("could not re-synthesize gate, target gate library is a 'nullptr'"); + } + + auto base_path_res = utils::get_unique_temp_directory("resynthesis_"); + if (base_path_res.is_error()) + { + return ERR_APPEND(base_path_res.get_error(), "unable to re-synthesize Boolean functions with yosys: failed to get unique temp directory"); + } + const auto base_path = base_path_res.get(); + const auto genlib_path = base_path / "new_gate_library.genlib"; + std::filesystem::create_directory(base_path); + + const auto gl_save_res = gate_library_manager::save(genlib_path, target_gl, true); + if (!gl_save_res) + { + return ERR("unable to re-synthesize gates of type: failed to save gate library '" + target_gl->get_name() + "' at location '" + genlib_path.string() + "'"); + } + + auto resynth_res = generate_resynth_netlist_for_gate(gate, target_gl, genlib_path); + if (resynth_res.is_error()) + { + return ERR_APPEND(resynth_res.get_error(), + "unable to re-synthesize gate '" + gate->get_name() + "' with ID " + std::to_string(gate->get_id()) + ": failed to generate re-synthesized netlist"); + } + const auto resynth_nl = resynth_res.get(); + const auto replace_res = replace_gate_with_netlist(gate, resynth_nl.get(), nl, false); + if (replace_res.is_error()) + { + return ERR_APPEND(replace_res.get_error(), + "unable to re-synthesize gate '" + gate->get_name() + "' with ID " + std::to_string(gate->get_id()) + ": failed to replace gate with re-synthesized netlist"); + } + + if (delete_gate) + { + if (!nl->delete_gate(gate)) + { + return ERR("unable to decompose gate '" + gate->get_name() + "' with ID " + std::to_string(gate->get_id()) + ": failed to delete original gate."); + } + } + + return OK({}); + } + + Result resynthesize_gates(Netlist* nl, const std::vector& gates, GateLibrary* target_gl) + { + auto base_path_res = utils::get_unique_temp_directory("resynthesis_"); + if (base_path_res.is_error()) + { + return ERR_APPEND(base_path_res.get_error(), "unable to re-synthesize Boolean functions with yosys: failed to get unique temp directory"); + } + const auto base_path = base_path_res.get(); + const auto genlib_path = base_path / "new_gate_library.genlib"; + std::filesystem::create_directory(base_path); + + const auto gl_save_res = gate_library_manager::save(genlib_path, target_gl, true); + if (!gl_save_res) + { + return ERR("unable to re-synthesize gates of type: failed to save gate library '" + target_gl->get_name() + "' to location " + genlib_path.string()); + } + + std::map>, std::unique_ptr> gt_to_resynth; + + std::vector to_delete; + u32 counter = 0; + for (const auto& g : gates) + { + const auto& gt = g->get_type(); + + Netlist* resynth_nl = nullptr; + std::vector init_data = {}; + + if (gt->has_property(GateTypeProperty::c_lut)) + { + const auto init_res = g->get_init_data(); + if (init_res.is_error()) + { + return ERR_APPEND(init_res.get_error(), + "unable to re-synthesize gates of type: failed to get INIT string from gate '" + g->get_name() + "' with ID " + std::to_string(g->get_id())); + } + const auto init_vec = init_res.get(); + if (init_vec.size() != 1) + { + return ERR("unable tor re-synthesize gates of type: got " + std::to_string(init_vec.size()) + " INIT strings for gate '" + g->get_name() + "' with ID " + + std::to_string(g->get_id())); + } + + init_data = init_vec; + } + + // get resynth netlist from cache or create it + if (const auto it = gt_to_resynth.find({gt, init_data}); it != gt_to_resynth.end()) + { + resynth_nl = it->second.get(); + } + else + { + auto resynth_res = generate_resynth_netlist_for_gate(g, target_gl, genlib_path); + if (resynth_res.is_error()) + { + return ERR_APPEND(resynth_res.get_error(), + "unable to re-synthesize gates of type: failed to re-synthesize gate '" + g->get_name() + "' with ID " + std::to_string(g->get_id())); + } + auto unique_resynth_nl = resynth_res.get(); + resynth_nl = unique_resynth_nl.get(); + gt_to_resynth.insert({std::make_pair(gt, init_data), std::move(unique_resynth_nl)}); + } + + const auto replace_res = replace_gate_with_netlist(g, resynth_nl, nl, false); + if (replace_res.is_error()) + { + return ERR_APPEND(replace_res.get_error(), + "unable to re-synthesize gates of type '" + gt->get_name() + "': failed for gate '" + g->get_name() + "' with ID " + std::to_string(g->get_id())); + } + to_delete.push_back(g); + } + + for (const auto& g : to_delete) + { + counter += 1; + if (!nl->delete_gate(g)) + { + return ERR("unable to re-synthesize gates of type '" + g->get_type()->get_name() + "': failed to delete gate '" + g->get_name() + "' with ID " + std::to_string(g->get_id())); + } + } + + // delete the created directory and the contained files + std::filesystem::remove_all(base_path); + + return OK(counter); + } + + Result resynthesize_gates_of_type(Netlist* nl, const std::vector& gate_types, GateLibrary* target_gl) + { + const std::vector filtered_gates = nl->get_gates([gate_types](const auto& gate) { + bool is_included = false; + for (const auto& gt : gate_types) + { + if (gate->get_type() == gt) + { + is_included = true; + break; + } + } + + return is_included; + }); + + return resynthesize_gates(nl, filtered_gates, target_gl); + } + + Result resynthesize_subgraph(Netlist* nl, const std::vector& subgraph, GateLibrary* target_gl) + { + if (nl == nullptr) + { + return ERR("netlist is a 'nullptr'"); + } + + if (target_gl == nullptr) + { + return ERR("gate library is a 'nullptr'"); + } + + auto base_path_res = utils::get_unique_temp_directory("resynthesis_"); + if (base_path_res.is_error()) + { + return ERR_APPEND(base_path_res.get_error(), "unable to re-synthesize Boolean functions with yosys: failed to get unique temp directory"); + } + const std::filesystem::path base_path = base_path_res.get(); + const std::filesystem::path genlib_path = base_path / "new_gate_library.genlib"; + std::filesystem::create_directory(base_path); + + const auto gl_save_res = gate_library_manager::save(genlib_path, target_gl, true); + if (!gl_save_res) + { + return ERR("unable to re-synthesize gates of type: failed to save gate library '" + target_gl->get_name() + "' to location " + genlib_path.string()); + } + + // auto resynth_res = resynthesize_functional_subgraph_with_yosys(nl, subgraph, genlib_path, target_gl, true); + auto resynth_res = generate_resynth_netlist_for_gate_level_subgraph(nl, subgraph, genlib_path, target_gl, true); + if (resynth_res.is_error()) + { + return ERR_APPEND(resynth_res.get_error(), "unable to re-synthesize subgraphs of type: failed to re-synthesize subgraph to netlist"); + } + auto resynth_nl = resynth_res.get(); + + std::unordered_map name_to_net; + for (const auto n : nl->get_nets()) + { + name_to_net.insert({n->get_name(), n}); + } + + std::unordered_map> global_io_mapping; + + // use top module pin names to find correponding nets in original netlist + for (const auto& pin : resynth_nl->get_top_module()->get_input_pins()) + { + auto net_it = name_to_net.find(pin->get_name()); + if (net_it == name_to_net.end()) + { + return ERR("unable to re-synthesize subgraphs of type: failed to locate net in destination netlist from global input '" + pin->get_name() + "' in re-synthesized netlist"); + } + global_io_mapping[pin->get_net()].push_back(net_it->second); + } + for (const auto& pin : resynth_nl->get_top_module()->get_output_pins()) + { + auto net_it = name_to_net.find(pin->get_name()); + if (net_it == name_to_net.end()) + { + return ERR("unable to re-synthesize subgraphs of type: failed to locate net in destination netlist from global output '" + pin->get_name() + "' in re-synthesized netlist"); + } + global_io_mapping[pin->get_net()].push_back(net_it->second); + } + + auto replace_res = replace_subgraph_with_netlist(subgraph, global_io_mapping, resynth_nl.get(), nl, false); + if (replace_res.is_error()) + { + return ERR_APPEND(replace_res.get_error(), "unable to re-synthesize subgraphs of type: failed to replace subgraph with re-synthesized netlist"); + } + + // delete subgraph gates + auto delete_res = delete_subgraph(nl, subgraph); + if (delete_res.is_error()) + { + return ERR_APPEND(delete_res.get_error(), "unable to replace subgraph with netlist: failed to delete subgraph"); + } + + // delete the created directory and the contained files + std::filesystem::remove_all(base_path); + + return OK(subgraph.size()); + } + + Result resynthesize_subgraph_of_type(Netlist* nl, const std::vector& gate_types, GateLibrary* target_gl) + { + std::vector subgraph; + for (const auto& gt : gate_types) + { + for (const auto& g : nl->get_gates([gt](const Gate* g) { return g->get_type() == gt; })) + { + subgraph.push_back(g); + } + } + + return resynthesize_subgraph(nl, subgraph, target_gl); + } + } // namespace resynthesis + +} // namespace hal \ No newline at end of file From 98e96769d35504d07761ee013661670c11849a29 Mon Sep 17 00:00:00 2001 From: "julian.speith" Date: Mon, 15 Jul 2024 16:57:38 +0200 Subject: [PATCH 15/17] docu fixes --- .../resynthesis/include/resynthesis/resynthesis.h | 14 ++++++++------ plugins/resynthesis/python/python_bindings.cpp | 14 ++++++++------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/plugins/resynthesis/include/resynthesis/resynthesis.h b/plugins/resynthesis/include/resynthesis/resynthesis.h index 8dec0e00090..677e491dd6b 100644 --- a/plugins/resynthesis/include/resynthesis/resynthesis.h +++ b/plugins/resynthesis/include/resynthesis/resynthesis.h @@ -91,10 +91,11 @@ namespace hal /** * Re-synthesize all specified combinational gates by calling Yosys on a functional description of the gates using a reduced gate library. * For all output pins of each gate, the resolved Boolean function (only dependent on input pins) is determined. - * All these Boolean functions are then written to an HDL file that is functionally equivalent to the target gates. - * This file is fed to Yosys and subsequently synthesized to a netlist again by using the provided gate library. + * All Boolean functions of a gate are then written to an HDL file that is functionally equivalent to the gate. + * These files are fed to Yosys and subsequently synthesized to a netlist again by using the provided gate library. * The provided gate library should be a subset of the gate library that was used to parse the netlist. - * The target gates are then replaced in the original netlist with the circuit that was just generated. + * The gate is then replaced in the original netlist with the circuit that was just generated. + * This process is repeated for every gate, hence they are re-synthesized in isolation. * * @param[in] nl - The netlist to operate on. * @param[in] gates - The gates to re-synthesize. @@ -106,10 +107,11 @@ namespace hal /** * Re-synthesize all combinational gates of the specified types by calling Yosys on a functional description of the gates using a reduced gate library. * For all output pins of each gate, the resolved Boolean function (only dependent on input pins) is determined. - * All these Boolean functions are then written to an HDL file that is functionally equivalent to the target gates. - * This file is fed to Yosys and subsequently synthesized to a netlist again by using the provided gate library. + * All Boolean functions of a gate are then written to an HDL file that is functionally equivalent to the gate. + * These files are fed to Yosys and subsequently synthesized to a netlist again by using the provided gate library. * The provided gate library should be a subset of the gate library that was used to parse the netlist. - * The target gates are then replaced in the original netlist with the circuit that was just generated. + * The gate is then replaced in the original netlist with the circuit that was just generated. + * This process is repeated for every gate, hence they are re-synthesized in isolation. * * @param[in] nl - The netlist to operate on. * @param[in] gate_types - The gate types to be re-synthesized. diff --git a/plugins/resynthesis/python/python_bindings.cpp b/plugins/resynthesis/python/python_bindings.cpp index 5dfdc37bc9e..32ff6b44f93 100644 --- a/plugins/resynthesis/python/python_bindings.cpp +++ b/plugins/resynthesis/python/python_bindings.cpp @@ -196,10 +196,11 @@ namespace hal R"( Re-synthesize all specified combinational gates by calling Yosys on a functional description of the gates using a reduced gate library. For all output pins of each gate, the resolved Boolean function (only dependent on input pins) is determined. - All these Boolean functions are then written to an HDL file that is functionally equivalent to the target gates. - This file is fed to Yosys and subsequently synthesized to a netlist again by using the provided gate library. + All Boolean functions of a gate are then written to an HDL file that is functionally equivalent to the gate. + These files are fed to Yosys and subsequently synthesized to a netlist again by using the provided gate library. The provided gate library should be a subset of the gate library that was used to parse the netlist. - The target gates are then replaced in the original netlist with the circuit that was just generated. + The gate is then replaced in the original netlist with the circuit that was just generated. + This process is repeated for every gate, hence they are re-synthesized in isolation. :param hal_py.Netlist nl: The netlist to operate on. :param hal_py.Gate g: The gates to re-synthesize. @@ -228,10 +229,11 @@ namespace hal R"( Re-synthesize all combinational gates of the specified types by calling Yosys on a functional description of the gates using a reduced gate library. For all output pins of each gate, the resolved Boolean function (only dependent on input pins) is determined. - All these Boolean functions are then written to an HDL file that is functionally equivalent to the target gates. - This file is fed to Yosys and subsequently synthesized to a netlist again by using the provided gate library. + All Boolean functions of a gate are then written to an HDL file that is functionally equivalent to the gate. + These files are fed to Yosys and subsequently synthesized to a netlist again by using the provided gate library. The provided gate library should be a subset of the gate library that was used to parse the netlist. - The target gates are then replaced in the original netlist with the circuit that was just generated. + The gate is then replaced in the original netlist with the circuit that was just generated. + This process is repeated for every gate, hence they are re-synthesized in isolation. :param hal_py.Netlist nl: The netlist to operate on. :param list[hal_py.GateType] gate_types: The gate types to be re-synthesized. From a528a3c8508bb24f632f5186a5a5bfe261471ea0 Mon Sep 17 00:00:00 2001 From: "julian.speith" Date: Tue, 16 Jul 2024 09:59:41 +0200 Subject: [PATCH 16/17] resynthesis completed --- .../include/resynthesis/resynthesis.h | 29 +++++---- .../resynthesis/python/python_bindings.cpp | 64 ++++++++++++++++++- 2 files changed, 78 insertions(+), 15 deletions(-) diff --git a/plugins/resynthesis/include/resynthesis/resynthesis.h b/plugins/resynthesis/include/resynthesis/resynthesis.h index 677e491dd6b..f49597ea254 100644 --- a/plugins/resynthesis/include/resynthesis/resynthesis.h +++ b/plugins/resynthesis/include/resynthesis/resynthesis.h @@ -94,7 +94,7 @@ namespace hal * All Boolean functions of a gate are then written to an HDL file that is functionally equivalent to the gate. * These files are fed to Yosys and subsequently synthesized to a netlist again by using the provided gate library. * The provided gate library should be a subset of the gate library that was used to parse the netlist. - * The gate is then replaced in the original netlist with the circuit that was just generated. + * The gates are then replaced in the original netlist with the circuits that were just generated. * This process is repeated for every gate, hence they are re-synthesized in isolation. * * @param[in] nl - The netlist to operate on. @@ -110,7 +110,7 @@ namespace hal * All Boolean functions of a gate are then written to an HDL file that is functionally equivalent to the gate. * These files are fed to Yosys and subsequently synthesized to a netlist again by using the provided gate library. * The provided gate library should be a subset of the gate library that was used to parse the netlist. - * The gate is then replaced in the original netlist with the circuit that was just generated. + * The gates are then replaced in the original netlist with the circuits that were just generated. * This process is repeated for every gate, hence they are re-synthesized in isolation. * * @param[in] nl - The netlist to operate on. @@ -123,25 +123,28 @@ namespace hal // TODO update docs below /** - * Build a Verilog description of a subgraph of gates and synthesize a new technology mapped netlist of the whole subgraph with a logic synthesizer. - * Afterwards the original subgraph is replaced by the technology mapped netlist produced by the synthesizer. + * Re-synthesize the combinational gates of the subgraph by calling Yosys on a Verilog netlist representation of the subgraph using a reduced gate library. + * All gates of the subgraph are written to a Verilog netlist file which is then fed to Yosys and subsequently synthesized to a netlist again by using the provided gate library. + * The provided gate library should be a subset of the gate library that was used to parse the netlist. + * The gates are then replaced in the original netlist with the circuit that was just generated. * * @param[in] nl - The netlist to operate on. - * @param[in] subgraph - The subgraph to resynthesize. - * @param[in] target_gl - Gate library containing the gates used for technology mapping. - * @return OK() and the number of decomposed gates on success, an error otherwise. + * @param[in] subgraph - The subgraph to re-synthesize. + * @param[in] target_gl - The gate library that is a subset of the gate library used to parse the netlist. + * @return OK() and the number of re-synthesized gates on success, an error otherwise. */ Result resynthesize_subgraph(Netlist* nl, const std::vector& subgraph, GateLibrary* target_gl); /** - * Build a verilog description of the subgraph consisting of all the gates of the specified types. - * Then synthesize a new technology mapped netlist of the whole subgraph with a logic synthesizer. - * Afterwards the original subgraph is replaced by the technology mapped netlist produced by the synthesizer. + * Re-synthesize the combinational gates of the specified types as a subgraph by calling Yosys on a Verilog netlist representation of the subgraph induced by these gates using a reduced gate library. + * All gates of the subgraph are written to a Verilog netlist file which is then fed to Yosys and subsequently synthesized to a netlist again by using the provided gate library. + * The provided gate library should be a subset of the gate library that was used to parse the netlist. + * The gates are then replaced in the original netlist with the circuit that was just generated. * * @param[in] nl - The netlist to operate on. - * @param[in] gate_types - The gate types specifying which gates should be part of the subgraph. - * @param[in] target_gl - Gate library containing the gates used for technology mapping. - * @return OK() and the number of decomposed gates on success, an error otherwise. + * @param[in] gate_types - The gate types to be re-synthesized. + * @param[in] target_gl - The gate library that is a subset of the gate library used to parse the netlist. + * @return OK() and the number of re-synthesized gates on success, an error otherwise. */ Result resynthesize_subgraph_of_type(Netlist* nl, const std::vector& gate_types, GateLibrary* target_gl); } // namespace resynthesis diff --git a/plugins/resynthesis/python/python_bindings.cpp b/plugins/resynthesis/python/python_bindings.cpp index 32ff6b44f93..4de29aa6787 100644 --- a/plugins/resynthesis/python/python_bindings.cpp +++ b/plugins/resynthesis/python/python_bindings.cpp @@ -199,7 +199,7 @@ namespace hal All Boolean functions of a gate are then written to an HDL file that is functionally equivalent to the gate. These files are fed to Yosys and subsequently synthesized to a netlist again by using the provided gate library. The provided gate library should be a subset of the gate library that was used to parse the netlist. - The gate is then replaced in the original netlist with the circuit that was just generated. + The gates are then replaced in the original netlist with the circuits that were just generated. This process is repeated for every gate, hence they are re-synthesized in isolation. :param hal_py.Netlist nl: The netlist to operate on. @@ -232,7 +232,7 @@ namespace hal All Boolean functions of a gate are then written to an HDL file that is functionally equivalent to the gate. These files are fed to Yosys and subsequently synthesized to a netlist again by using the provided gate library. The provided gate library should be a subset of the gate library that was used to parse the netlist. - The gate is then replaced in the original netlist with the circuit that was just generated. + The gates are then replaced in the original netlist with the circuits that were just generated. This process is repeated for every gate, hence they are re-synthesized in isolation. :param hal_py.Netlist nl: The netlist to operate on. @@ -242,6 +242,66 @@ namespace hal :rtype: int or ``None`` )"); + m.def( + "resynthesize_subgraph", + [](Netlist* nl, const std::vector& subgraph, GateLibrary* target_gl) -> std::optional { + auto res = resynthesis::resynthesize_subgraph(nl, subgraph, target_gl); + if (res.is_ok()) + { + return res.get(); + } + else + { + log_error("python_context", "{}", res.get_error().get()); + return std::nullopt; + } + }, + py::arg("nl"), + py::arg("subgraph"), + py::arg("target_gl"), + R"( + Re-synthesize the combinational gates of the subgraph by calling Yosys on a Verilog netlist representation of the subgraph using a reduced gate library. + All gates of the subgraph are written to a Verilog netlist file which is then fed to Yosys and subsequently synthesized to a netlist again by using the provided gate library. + The provided gate library should be a subset of the gate library that was used to parse the netlist. + The gates are then replaced in the original netlist with the circuit that was just generated. + + :param hal_py.Netlist nl: The netlist to operate on. + :param list[hal_py.Gate] subgraph: The subgraph to re-synthesize. + :param hal_py.GateLibrary target_lib: The gate library that is a subset of the gate library used to parse the netlist. + :returns: The number of re-synthesized gates on success, ``None`` otherwise. + :rtype: int or ``None`` + )"); + + m.def( + "resynthesize_subgraph_of_type", + [](Netlist* nl, const std::vector& gate_types, GateLibrary* target_gl) -> std::optional { + auto res = resynthesis::resynthesize_subgraph_of_type(nl, gate_types, target_gl); + if (res.is_ok()) + { + return res.get(); + } + else + { + log_error("python_context", "{}", res.get_error().get()); + return std::nullopt; + } + }, + py::arg("nl"), + py::arg("gate_types"), + py::arg("target_gl"), + R"( + Re-synthesize the combinational gates of the specified types as a subgraph by calling Yosys on a Verilog netlist representation of the subgraph induced by these gates using a reduced gate library. + All gates of the subgraph are written to a Verilog netlist file which is then fed to Yosys and subsequently synthesized to a netlist again by using the provided gate library. + The provided gate library should be a subset of the gate library that was used to parse the netlist. + The gates are then replaced in the original netlist with the circuit that was just generated. + + :param hal_py.Netlist nl: The netlist to operate on. + :param list[hal_py.GateType] gate_types: The gate types to be re-synthesized. + :param hal_py.GateLibrary target_lib: The gate library that is a subset of the gate library used to parse the netlist. + :returns: The number of re-synthesized gates on success, ``None`` otherwise. + :rtype: int or ``None`` + )"); + #ifndef PYBIND11_MODULE return m.ptr(); #endif // PYBIND11_MODULE From 5e1dbcdd7c52ec953b510676285caa6ab79b3176 Mon Sep 17 00:00:00 2001 From: "julian.speith" Date: Tue, 16 Jul 2024 10:02:56 +0200 Subject: [PATCH 17/17] changelog update --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f122f0d6b06..078358a1171 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file. ## [Unreleased] * **WARNING:** this release breaks the API of the `bitorder_propagation` plugin * plugins + * added `resynthesis` plugin + * moved `decompose_gate` and `decompose_gates_of_type` from `netlist_preprocessing` plugin and slightly changed their API + * added functions for logic re-synthesis that write out parts of the current netlist and call Yosys as an open-source synthesizer to produce a new gate-level netlist based on a user-defined gate library * changed `bitorder_propagation` plugin * changed API so that no instance of the plugin needs to be created anymore to apply its algorithms * changed propagation logic for better results