diff --git a/.github/workflows/macOS.yml b/.github/workflows/macOS.yml index 6734c13339b4..ff5b86784774 100644 --- a/.github/workflows/macOS.yml +++ b/.github/workflows/macOS.yml @@ -92,7 +92,7 @@ jobs: cd build export PATH="$(brew --prefix qt)/bin:$PATH" export LDFLAGS="-L$(brew --prefix qt)/lib -Wl,-rpath,$(brew --prefix llvm)/lib" - cmake -G Ninja .. -DQt5_DIR="$(brew --prefix qt)/lib/cmake" -DCMAKE_BUILD_TYPE=Debug -DBUILD_ALL_PLUGINS=ON -DBUILD_TESTS=ON -DPL_GUI=ON + cmake -G Ninja .. -DQt6_DIR="$(brew --prefix qt)/lib/cmake/Qt6" -DCMAKE_BUILD_TYPE=Debug -DBUILD_ALL_PLUGINS=ON -DBUILD_TESTS=ON -DPL_GUI=ON env: HAL_BASE_PATH: ${{runner.workspace}}/hal/build CCACHE_DIR: ${{runner.workspace}}/.ccache diff --git a/Brewfile b/Brewfile index e36983c06d35..7afddac434fd 100644 --- a/Brewfile +++ b/Brewfile @@ -18,4 +18,3 @@ brew "z3" brew "boost" brew "readline" brew "verilator" -brew "graphviz" diff --git a/CHANGELOG.md b/CHANGELOG.md index 65014457fd2e..160c6f2d8091 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,146 +3,213 @@ All notable changes to this project will be documented in this file. ## [Unreleased] * Core - * fixed crash when passing a `nullptr` pin to `Net::remove_source` or `Net::remove_destination`, which is also reachable from Python - * changed `Net` and `Gate` to identify a pin by pointer identity instead of by value when looking up an endpoint - * progress and layout reporting - * added `ProgressScope` and `LayoutLocker` to the core, which report progress and suppress layout updates through the user interface plugin looked up at runtime, replacing the copies of `GuiLayoutLocker` in the dataflow analysis and module identification plugins - * added `UIPluginInterface::set_progress` and `plugin_manager::get_ui_plugin`, so that a plugin no longer needs to provide a `GuiExtensionInterface` and register a callback just to report its progress - * moved the progress bar of the dataflow analysis into the core as `user_feedback::ProgressPrinter`, which reports to the terminal and to the user interface at once and brackets the operation like a `ProgressScope` does, so that a plugin reports its progress once and reaches whoever is watching - * fixed the progress overlay of the graph view staying up until it is clicked away after a dataflow analysis that was started from a script instead of from the plugin dialog, only the dialog reported the analysis as finished although both report its progress - * fixed the progress overlay of the graph view never being dismissed after a module identification run, the call reporting the analysis as finished was commented out - * fixed the progress overlay of the graph view being dismissed while the layout updates deferred during a dataflow analysis were still being applied, which left the graph view showing its spinner - * program options - * added `ProgramOptions::add_flags` that takes the flags and parameters as vectors so that they can be assembled at runtime - * netlist traversal - * deprecated `netlist_utils::get_nets_at_pins` without a relocation: it is a per-pin lookup that `Gate::get_fan_in_net` and `get_fan_out_net` already provide, and it has no callers - * moved `get_common_inputs` from `netlist_utils` onto `NetlistTraversalDecorator` and deprecated the original - * added `NetlistTraversalDecorator::make_traversal_cache` and `get_gates` overloads that share results across calls through a `TraversalCache`. The traversal a cache answers for is sealed in at creation -- direction, match condition, stop rule and endpoint filters -- so a cache can never be consulted by a walk asking a different question, and everything that would make a cached answer depend on how a net was reached is excluded by construction - * fixed `get_next_sequential_gates`, `get_combinational_cone` and `get_next_sequential_gates_map` returning results with gates missing through a shared cache when the netlist contains a combinational cycle. Cache entries were written while a net was still being explored, and a cycle that led the walk back to such a net baked the partial answer into the entries of the nets in flight, so a later call reaching one of them through a side path was silently wrong -- which is how the Boolean influence plugin produced a wrong flip-flop dependency matrix on such netlists. An entry is now published only once its net, and any cycle it belongs to, is fully explored - * deprecated the four `netlist_utils::get_path` overloads, which despite the name return every gate of a cone rather than a path, in favour of `NetlistTraversalDecorator::get_gates` with a negated condition and `TraversalStop::at_mismatch` - * added `NetlistTraversalDecorator::get_gates`, the traversal that the other traversals of the decorator are special cases of. What separated them from one another was never what they collect but where they stop relative to it, which is now said out loud by a `TraversalStop` of `at_match`, `at_mismatch` or `never` rather than implied by a pair of booleans named one syllable apart. Direction is a `TraversalDirection` rather than a bare `bool successors` - * deprecated the three `netlist_utils::get_shortest_path` overloads in favour of `NetlistTraversalDecorator::get_shortest_path`, and `netlist_utils::get_ff_dependency_matrix` in favour of the one in the Boolean influence plugin, which also reports how strongly each flip-flop depends on another rather than only whether it does - * moved `get_gate_chain` and `get_complex_gate_chain` from `netlist_utils` onto `NetlistTraversalDecorator`, where the rest of the traversal lives and where a binding can keep the netlist alive for as long as Python refers to the gates it returns - * renamed `NetlistTraversalDecorator::get_next_combinational_gates` to `get_combinational_cone`, which is what it returns -- every combinational gate up to the sequential boundary, not a next layer of anything -- and removed the raw result-map cache parameter from it and from `get_next_sequential_gates`, whose reuse contract nothing enforced. Repeated traversals share results through a sealed `TraversalCache` instead. The Python bindings of both now also default `forbidden_pins` to an empty set like the C++ side always did - * added `NetlistTraversalDecorator::get_shortest_path` overloads that end at any gate of a module and that connect two modules, which existed only as free functions in `netlist_utils` before + * netlist + * changed `Net` and `Gate` to identify a pin by pointer identity instead of by value when looking up an endpoint + * sped up deleting gates, nets and modules, which searched the object vectors of the netlist and of the owning module linearly; every such vector now tracks the positions of its elements and removal is a constant-time swap + * sped up `Module::is_parent_module_of`, which walked the whole subtree of the module once per endpoint whenever module nets are recomputed; it now walks up the parent chain of the queried module. Together with the constant-time removal, parsing the OpenTitan Earl Grey netlist of 25,906 modules went from 709 s to 31 s + * fixed `utils::split` reading the last character of an empty string + * fixed crash when passing a `nullptr` pin to `Net::remove_source` or `Net::remove_destination` + * module pins + * sped up assigning gates to a module and removing them from it, `Module::get_pin_by_net` scanned every pin of the module for every net of every gate that enters or leaves it; the pins are now indexed by net + * added `PinChangedBulkScope`, which collects the pin events of a bulk operation and sends a single `PinEvent::PinsReload` per affected module instead. Assigning gates to modules opens one, while interactive pin changes keep their fine-grained events + * changed the default pin order of both `Module::create_pin_group` overloads from ascending to descending, in C++ and in Python, matching the order the GUI, the dataflow analysis and the module identification produce. In Python, `start_index` now defaults to the index of the last pin for a descending group and to 0 for an ascending one + * fixed `Module.pins` and `Module.pin_groups` raising a `TypeError` whenever they were read from Python + * fixed `Module::set_pin_group_direction` sending a `GroupTypeChange` pin event instead of `GroupDirChange` + * fixed the pin events of assigning several pins to a group being delivered in pin ID order rather than in the order the rows come were created, which could crash the pins tree of the GUI * gate library + * added the `GateTypeProperty::delay` gate type property for dedicated delay cells, such as those of a clock distribution network, which HGL files read and write as `delay` * fixed reloading a gate library destroying the library a netlist was built against, which silently replaced every gate type of that netlist. Gate libraries are now owned through a `shared_ptr` and outlive both the netlists and the Python handles that refer to them -* Boolean functions - * added `to_string` to `SMT::QueryConfig`, `SMT::Model` and `SMT::SolverResult`, so that all four SMT types offer it the way `SMT::Constraint` already did instead of only an `operator<<` - * fixed the printed form of an `SMT::Model` starting with a stray comma, `{, A:5}` instead of `{A:5}` - * sped up `BooleanFunction::compute_truth_table` by evaluating 64 rows of the table at once instead of running a symbolic execution per row, which walks and simplifies the entire node list every single time. Applies to single-bit functions of bitwise operations whose variables are all part of the truth table, everything else keeps using the previous implementation - * raised the limit on the number of variables a truth table may be computed for from 10 to 20, see `BooleanFunction::MAX_TRUTH_TABLE_VARIABLES` - * sped up the evaluation of Boolean functions, `BooleanFunction::operator<` compared two functions by building and comparing their reverse polish notation strings, which the symbolic state hit on every variable lookup - * fixed silent truncation of additions and subtractions, results of operands wider than 32 bit lost their upper bits - * fixed `Eq` reporting a definite inequality when an undefined bit could have made the two values equal, it now reports an undefined result like the other comparisons do - * added constant folding for the `Sdiv`, `Udiv`, `Srem` and `Urem` operations, which were not implemented and made evaluation of any function containing them fail, following the SMT-LIB definitions these operations are translated to - * sped up evaluation with constant inputs by about 3x by folding the values directly instead of building a Boolean function per operation, which dominates the runtime of `compute_truth_table()` and thereby of the HAWKEYE S-box identification - * fixed `SMT::Solver::has_local_solver_for` testing `SolverCall::Binary` in both of its branches, so the branch for `SolverCall::Library` was unreachable and library availability always reported `false` - * fixed `SMT::SymbolicState::set` using `emplace`, which leaves an existing binding untouched, so setting a variable a second time did nothing and a loop stepping a symbolic state forward silently kept the value it started with - * added simplification rules for the word level operations, which the single-bit simplification through ABC cannot reach: extensions to the width the value already has, nested extensions and slices, slices that fall into one half of a concatenation or into either part of an extension, unsigned comparisons against zero and the maximum, equality of a value with its own negation, and single bit equalities and selections -* Python bindings - * fixed the four `boolean_influence` functions that return influences per net handing out the nets without keeping the netlist alive: they return dicts keyed by net, and nothing protected a borrowed object sitting in a dict key - * added a warning, once per function and process, when a deprecated `NetlistUtils` function is called from Python, naming its replacement. `[[deprecated]]` warns whoever compiles, and a script has no compiler - * fixed the deprecated `NetlistUtils` bindings handing out gates and nets without keeping the netlist alive for as long as Python refers to them, which they keep doing until they are removed - * fixed `netlist_preprocessing.create_multi_bit_gate_modules` and `create_nets_at_unconnected_pins` handing out modules and nets without keeping the netlist alive: the `hal::borrowed()` call policy ties each returned object to the netlist that owns it, which works on a module-level function as well, as the owner is found through the wrapper the caller necessarily passed in - * fixed the Python bindings handing out gates, nets, modules, endpoints and pins without tying them to the netlist that owns them, so that dropping the netlist left them pointing into freed memory. Reading 500 gates and 500 nets of a dropped netlist returned the wrong name and ID for 184 and 230 of them respectively, silently rather than by crashing - * fixed the decorators storing a reference to the netlist or net they were constructed from without keeping it alive - * fixed `NetlistGraph` never being freed by Python: its factories hand over ownership but it was bound with a non-owning holder, so every graph built from a netlist leaked, more than a gigabyte over 1500 graphs on a 3458 gate netlist - * fixed `GateLibrary::get_path` and the `path` property returning the name of the library instead of its path - * fixed `GuiApi::getSelectedModules` and `getSelectedItems` not tying the returned modules to the netlist - * added Python bindings for `NetlistGraph::from_gates`, `is_shadow_vertex`, and `get_all_vertices_from_gate` - * added Python bindings for `ProgramOptions`, `ProgramArguments`, and `FacExtensionInterface` - * added Python bindings for the remaining functions of `plugin_manager` and exposed the `initialize` and `silent` parameters of `get_plugin_instance` - * fixed `GateLibraryManager.get_gate_libraries` handing each library to Python as a newly constructed `shared_ptr` over a pointer it had only borrowed, which opened a second ownership group over a library the manager already owned and freed it twice - * fixed `Module.pins`, `Module.pin_groups` and `GateType.components` raising a `TypeError` whenever they were read, as each was bound to a method whose only parameter has a default in C++, which pybind11 exposes as a required argument that a property cannot pass - * added a test that calls every no-argument binding reachable from a small netlist and imports every plugin module, so that a binding which compiles and only fails when called is caught - * changed every binding that hands out a borrowed object to keep its **owner** alive rather than the object it was read from, through the new `hal::borrowed()` call policy that replaces `py::return_value_policy::reference_internal` at 241 places. The policy was only applied while a wrapper was being created, so whether an object was protected depended on which binding happened to hand it over first, and a module read from a gate was tied to that gate although the netlist is what owns it - * fixed `DataContainer`, `ProjectDirectory`, `hawkeye.DetectionConfiguration`, `hawkeye.SBoxDatabase` and `dataflow.Configuration` leaking every instance created from Python, as each was bound with a holder that never frees. `SBoxDatabase.from_file` leaked 25 KB per call, and `ProjectManager.get_project_directory` leaked a copy on every call, as pybind11 copies a returned reference by default - * fixed `SMT.SymbolicExecution.evaluate` raising a `TypeError` on every call: both overloads were bound directly, so they returned an unregistered `Result`, where every other binding in that file unwraps it - * fixed the `hal::borrowed()` call policy having no effect on any of the 55 properties it was given to, so those still handed out a borrowed object without keeping its owner alive. `def_property_readonly` builds the getter itself before it forwards the attributes that follow, so a call policy given to a property never reaches the function that performs the call - * fixed a Python interpreter that loaded the HAL plugins segfaulting on the way out unless it unloaded them again by hand, as the plugin libraries were closed while the parser and writer registries still held a factory function out of each of them - * added Python bindings for `SMT.SolverCall` and `SMT.Solver.to_smt2`, and the missing `Bitwuzla` value of `SMT.SolverType`. Without `SolverCall`, neither `QueryConfig.with_call` nor `Solver.has_local_solver_for` could be called at all although both were bound - * fixed three enum values that were bound to a different value of their own enum, which made them indistinguishable from Python: `GateTypeProperty.fifo` was bound to `ram`, `module_identification.CandidateType.addition_offset` to `addition`, and `gui_extension_demo.ParameterType.Module` to `Gate` - * added `to_string` and `__str__` to `SMT.QueryConfig`, `SMT.Constraint`, `SMT.Model` and `SMT.SolverResult`, printing any of them showed an object address before - * fixed every binding that carries the `hal::borrowed()` call policy segfaulting when it is called with arguments that its first overload does not accept, which happens with pybind11 3.1 and newer: the post-call hook is now also run for an overload whose arguments did not load and is handed a sentinel instead of an object. `Netlist.create_module` with a name, a parent, and a list of gates was the first such call in the binding smoke test, which is why the macOS CI, whose Homebrew pybind11 moved to 3.1.0 in September 2026, failed while the Ubuntu jobs on older pybind11 did not + * fixed `GateLibraryManager.get_gate_libraries` handing each library to Python as a newly constructed `shared_ptr` over a pointer it had only borrowed, which freed the library twice + * fixed `GateLibrary::get_path` and the `path` property returning the name of the library instead of its path + * fixed `GateLibrary.get_gate_location_data_identifiers` being registered under the name of `get_gate_location_data_category` in Python + * fixed `GateTypeProperty.fifo` being bound to `ram` in Python + * fixed `GateType.components` raising a `TypeError` whenever it was read from Python + * Boolean functions + * sped up `BooleanFunction::compute_truth_table` by evaluating 64 rows of the table at once instead of running a symbolic execution per row. Applies to single-bit functions of bitwise operations whose variables are all part of the truth table + * raised the limit on the number of variables a truth table may be computed for from 10 to 20, see `BooleanFunction::MAX_TRUTH_TABLE_VARIABLES` + * sped up the evaluation of Boolean functions, `BooleanFunction::operator<` compared two functions by building and comparing their reverse polish notation strings + * sped up evaluation with constant inputs by about 3x by folding the values directly instead of building a Boolean function per operation + * added constant folding for the `Sdiv`, `Udiv`, `Srem` and `Urem` operations + * added simplification rules for the word level operations: extensions to the width the value already has, nested extensions and slices, slices that fall into one half of a concatenation or into either part of an extension, unsigned comparisons against zero and the maximum, equality of a value with its own negation, and single bit equalities and selections + * fixed the constant folding of `Add` and `Sub` masking the result to 32 bit before cutting it to the operand width, so for operands of 33 to 64 bit the upper bits of the result were always zero + * fixed `Eq` reporting a definite inequality when an undefined bit could have made the two values equal, it now reports an undefined result like the other comparisons do + * fixed `BooleanFunction::to_string` reading past the end of its digit table when a bit-vector that contains an `X` or `Z` is converted to an octal or hexadecimal string + * fixed `BooleanFunction::from_string` rejecting Liberty expressions that combine Liberty-only syntax such as `A'` or `+` with spaces around the operators; if neither grammar accepts an expression, it is parsed once more with every space removed, added as `BooleanFunctionParser::ParserType::LibertyNoSpace` + * changed `BooleanFunction.nodes` from a method to a read-only property in Python, which is what its documentation always claimed + * SMT + * added `to_string` to `SMT::QueryConfig`, `SMT::Model` and `SMT::SolverResult`, so that all four SMT types offer it the way `SMT::Constraint` already did, and `__str__` to all four in Python + * added `SMT::SymbolicState::get_bindings`, which returns the variables bound in a symbolic state indexed by their name + * added Python bindings for `SMT.SolverCall` and `SMT.Solver.to_smt2`, and the missing `Bitwuzla` value of `SMT.SolverType`. Without `SolverCall`, neither `QueryConfig.with_call` nor `Solver.has_local_solver_for` could be called from Python + * fixed the printed form of an `SMT::Model` starting with a stray comma, `{, A:5}` instead of `{A:5}` + * fixed `SMT::Solver::has_local_solver_for` testing `SolverCall::Binary` in both of its branches, so library availability always reported `false` + * fixed `SMT::SymbolicState::set` using `emplace`, which leaves an existing binding untouched, so setting a variable a second time did nothing + * fixed `SMT.SymbolicExecution.evaluate` raising a `TypeError` on every call from Python, as both overloads returned an unregistered `Result` instead of unwrapping it + * netlist traversal + * added `NetlistTraversalDecorator::get_gates`, the traversal that the other traversals of the decorator are special cases of: where a traversal stops is now said by a `TraversalStop` of `at_match`, `at_mismatch` or `never`, and its direction by a `TraversalDirection`, instead of a pair of booleans + * added `NetlistTraversalDecorator::make_traversal_cache` and `get_gates` overloads that share results across calls through a `TraversalCache`. The specific traversal a cache answers for is set at creation, so a cache can never be consulted by a walk with different parameters + * added `NetlistTraversalDecorator::get_shortest_path` overloads that end at any gate of a module and that connect two modules. The former existed only as a free function in `netlist_utils` before, the latter is new + * renamed `NetlistTraversalDecorator::get_next_combinational_gates` to `get_combinational_cone`, which is what it returns, and removed the raw result-map cache parameter from it and from `get_next_sequential_gates` in favour of a `TraversalCache`. The Python bindings of both now default `forbidden_pins` to an empty set like the C++ side always did + * moved `get_common_inputs`, `get_gate_chain` and `get_complex_gate_chain` from `netlist_utils` onto `NetlistTraversalDecorator` and deprecated the originals + * deprecated the four `netlist_utils::get_path` overloads, which despite the name return every gate of a cone rather than a path, in favour of `NetlistTraversalDecorator::get_gates` with a negated condition and `TraversalStop::at_mismatch` + * deprecated the three `netlist_utils::get_shortest_path` overloads in favour of `NetlistTraversalDecorator::get_shortest_path`, and `netlist_utils::get_ff_dependency_matrix` in favour of the one in the Boolean influence plugin + * deprecated `netlist_utils::get_nets_at_pins` without a relocation: `Gate::get_fan_in_net` and `get_fan_out_net` already provide the lookup, and it has no callers + * added a warning, once per function and process, when a deprecated `NetlistUtils` function is called from Python, naming its replacement + * fixed `get_next_sequential_gates`, `get_combinational_cone` and `get_next_sequential_gates_map` returning results with gates missing through a shared cache when the netlist contains a combinational cycle, which is how the Boolean influence plugin produced a wrong flip-flop dependency matrix on such netlists. A cache entry is now published only once its net, and any cycle it belongs to, is fully explored + * fixed the deprecated `NetlistUtils` bindings handing out gates and nets without keeping the netlist alive for as long as Python refers to them + * progress and layout reporting + * added `user_feedback::ProgressScope`, `LayoutLocker` and `ProgressPrinter` to the core: a scope brackets an operation in the progress display of the user interface, a locker suppresses layout updates while it exists, and a printer reports progress to the terminal and the user interface at once. All three do nothing when no user interface is loaded, so a plugin no longer needs a `GuiExtensionInterface` and a callback of its own to report progress, and the copies of `GuiLayoutLocker` in the dataflow analysis and the module identification are gone + * added `UIPluginInterface::set_progress` next to the existing `set_layout_locker`, and `plugin_manager::get_ui_plugin` to look the user interface up at runtime + * fixed the progress overlay of the graph view never being dismissed after a module identification run, and staying up after a dataflow analysis started from a script, which only reported the analysis as finished from its plugin dialog + * fixed the progress overlay being dismissed while the layout updates deferred during a dataflow analysis were still being applied + * fixed the GUI freezing while a dataflow analysis reported its progress from its worker threads; progress reported from another thread is now posted to the GUI thread + * plugin system + * added `ProgramOptions::add_flags` that takes the flags and parameters as vectors so that they can be assembled at runtime + * added Python bindings for `ProgramOptions`, `ProgramArguments` and `FacExtensionInterface`, and for the remaining functions of `plugin_manager`, exposing the `initialize` and `silent` parameters of `get_plugin_instance` + * added `log_trace`, `log_debug`, `log_info`, `log_warning`, `log_error` and `log_critical` to `hal_py`, which take the channel first like the C++ macros and prefix every severity but `info` with the file and line of the calling Python code + * fixed a Python interpreter that loaded the HAL plugins segfaulting on the way out unless it unloaded them again by hand, as the plugin libraries were closed while the parser and writer registries still held a factory function out of each of them + * Python bindings + * added a test that calls every no-argument binding reachable from a small netlist and imports every plugin module, so that a binding which compiles and only fails when called is caught + * fixed every binding that hands out a borrowed object to keep its **owner** alive rather than the object it was read from, through the new `hal::borrowed()` call policy that replaces `py::return_value_policy::reference_internal`. Before, dropping a netlist left gates, nets, modules, endpoints and pins read from it pointing into freed memory, and a module read from a gate was tied to that gate although the netlist owns it + * fixed the decorators storing a reference to the netlist or net they were constructed from without keeping it alive + * fixed `DataContainer` and `ProjectDirectory` leaking every instance created from Python, as each was bound with a holder that never frees; `ProjectManager.get_project_directory` leaked a copy on every call + * Plugins - * Boolean influence - * fixed `get_ff_dependency_matrix` dereferencing an uninitialized pointer on every call, which segfaulted before it returned anything. The cache it passes on was never initialized, and a pointer that is not null passed the callee's check for one + * clock tree extractor + * added the `clock_tree_extractor` plugin, which recovers the clock distribution network of a gate-level netlist. Starting at the clock pin of every flip-flop it walks against the signal direction through buffers, inverters, delay gates, clock gates and toggle-flip-flop clock dividers up to the global input nets that drive them, and returns the result as a `ClockTree`. Reconvergent clock networks, i.e. meshes, are extracted as well. + * added `ClockTree::from_netlist` that extracts the clock tree of a netlist + * added `ClockTree::export_dot` that writes the tree as a DOT graph + * added `ClockTree::get_subtree` that returns the clock tree below a gate or net as a `ClockTree` of its own + * added `ClockTree::get_neighbors`, bound to Python as `get_parents` and `get_childs`, that returns the gates and nets directly upstream or downstream of an object in the clock tree + * added `ClockTree::get_gates`, `get_nets`, `get_all` and `get_netlist` that list the gates and nets of a clock tree and hand back the netlist it was built from + * added `ClockTree::get_vertex_from_ptr`, `get_ptr_from_vertex`, `get_vertices_from_ptrs` and `get_ptrs_from_vertices` that translate between gates or nets and their igraph vertex IDs, and `ClockTree::get_igraph` that exposes the underlying `igraph_t` to C++ callers + * dot viewer + * added the `dot_viewer` plugin, which renders a Graphviz `.dot` file inside the GUI and, for a file written by another HAL plugin, ties the graph to the netlist + * added Python function `load_dot_file()` to open a `.dot` file in the viewer * HAWKEYE - * replaced `RegisterCandidate`, `RoundCandidate` and the free S-box functions of HAWKEYE with a single `CipherCandidate` that analyzes a candidate in place instead of copying it into a netlist of its own, so its gates and nets are the ones of the netlist under analysis and no longer have to be mapped back - * added `CipherCandidate::identify_sboxes` that identifies every S-box of a candidate at once and annotates it with the outcome, grouping the variants the search produces of one and the same S-box and leaving a group as soon as one of them matches - * added `CipherCandidate::create_modules` that writes a candidate back into the netlist as a module hierarchy of the candidate, its state register, and one submodule per identified S-box - * fixed the candidates of HAWKEYE being ordered by the addresses of their gates, which made the result of `detect_candidates` depend on where the gates of the netlist happened to be allocated and hence differ between runs of the same binary. Two candidates sharing size and input register could also compare equal and silently discard one another, which cost an entire candidate and the S-box identification that depended on it - * fixed `RegisterCandidate::operator==` never reporting a round-based candidate as equal to itself - * fixed the S-box search of HAWKEYE calling `std::includes` on the unsorted result of `get_unique_predecessors`, which decided by an order that is not guaranteed which inverters it drops from an S-box. This made the number of located S-boxes differ between runs of the same binary, 476 to 1508 across three runs of a netlist that holds 16 - * fixed the round function of HAWKEYE walking every path from each flip-flop of the register rather than every gate, which is exponential in a cone of logic that reconverges. Computing it for a 514 flip-flop candidate took 179 seconds and now takes 0.21 seconds with an unchanged result - * fixed the linear independence check of HAWKEYE shifting by more than the width of its type for S-boxes of more than 6 bits, which is undefined and made the check operate on garbage for 7-bit and 8-bit S-boxes - * sped up the S-box identification of HAWKEYE by tabulating each output over the state and the control inputs together and reading the assignment of the control inputs out of that one table, instead of substituting the control values and tabulating anew for each of up to 256 assignments - * changed the round function of a HAWKEYE candidate to determine which flip-flops each of its gates depends on only when the S-box search asks for it, as that is the most expensive part of analyzing a candidate and nothing else reads the result - * fixed `SBoxDatabase::lookup` never terminating for an 8-bit S-box that is not contained in the database, as it counted the constant it adds to the outputs in a `u8`, which never reaches 256 - * fixed `SBoxDatabase::store` reporting a failure although it had written the database, and made it report one if the file cannot be opened - * added a limit to the canonical form search behind an S-box lookup, which finishes quickly for a real S-box but does not terminate in reasonable time for a table that is close to linear, such as two 4-bit S-boxes glued into an 8-bit one by the surrounding logic - * added the HAWKEYE S-box database to the build directory so that it is found at runtime, and clarified that `identify_sbox` returning an empty string means no match rather than an error + * restructured HAWKEYE for improved usability + * replaced `RegisterCandidate`, `RoundCandidate` and the free S-box functions of HAWKEYE with a single `CipherCandidate` that analyzes a candidate in place instead of copying it into a netlist of its own + * added `CipherCandidate::identify_sboxes` that identifies every S-box of a candidate at once and annotates it with the outcome + * added `CipherCandidate::create_modules` that writes a candidate back into the netlist as a module hierarchy of the candidate, its state register, and one submodule per identified S-box + * added the HAWKEYE S-box database to the build directory so that it is found at runtime + * improved speed of HAWKEYE candidate search and identification + * fixed the round function of HAWKEYE walking every path from each flip-flop of the register rather than every gate, which is exponential in a cone of logic that reconverges + * sped up the S-box identification of HAWKEYE by tabulating each output over the state and the control inputs together and reading the assignment of the control inputs out of that one table, instead of substituting the control values and tabulating anew for each of up to 256 assignments + * changed the round function of a HAWKEYE candidate to determine which flip-flops each of its gates depends on only when the S-box search asks for it + * added a limit to the canonical form search behind an S-box lookup, which finishes quickly for a real S-box but does not terminate in reasonable time for a table that is close to linear + * bugfixes + * fixed the candidates of HAWKEYE being ordered by the addresses of their gates, which made the result of `detect_candidates` differ between runs of the same binary + * fixed `RegisterCandidate::operator==` never reporting a round-based candidate as equal to itself + * fixed the S-box search of HAWKEYE calling `std::includes` on the unsorted result of `get_unique_predecessors`, which made the number of located S-boxes differ between runs of the same binary + * fixed the linear independence check of HAWKEYE shifting by more than the width of its type for S-boxes of more than 6 bits, which is undefined and made the check operate on garbage for 7-bit and 8-bit S-boxes + * fixed `SBoxDatabase::lookup` never terminating for an 8-bit S-box that is not contained in the database, as it counted the constant it adds to the outputs in a `u8`, which never reaches 256 + * fixed `SBoxDatabase::store` reporting a failure although it had written the database, and made it report one if the file cannot be opened + * fixed `hawkeye.DetectionConfiguration` and `hawkeye.SBoxDatabase` leaking every instance created from Python, `SBoxDatabase.from_file` leaked 25 KB per call + * simulation + * added selecting the nets of the waveforms selected in the waveform viewer in the graph view as well + * simulation wizard + * added `Load data from file` to the manual input page, which fills the input table from a SALEAE directory, a VCD or a CSV file so that the data can be edited before simulating + * added a `Display values as hex numbers` switch to the input table, and changed the table to accept values in the `0x`, `0o`, `0b` and the Verilog `'h`, `'o`, `'b`, `'d` notations as well as `"` for the code of a character, each checked against the width of the pin group + * changed the input columns of the simulation wizard to list the nets of a descending pin group from the highest index down, so that the bits of an entered value land on the nets in the order the group declares + * changed the window title from `Empty Wizard` to `Simulation Wizard` and greyed out the input method that is not selected + * fixed editing a time in the middle of the input table replacing it with the previous time plus 1000 as if it were the last row; it is now kept if it lies between its neighbours and replaced by their midpoint otherwise + * fixed the wizard starting with the wave data and net groups of the previous run, its SALEAE directory and the controller are now reset when the wizard opens + * bugfixes + * fixed the debug dump of the SALEAE directory that was printed to the console whenever waveforms were loaded + * fixed the waveform viewer keeping the tab of a simulation controller that was deleted, e.g., from Python, and dereferencing the deleted controller from it + * fixed the value of a waveform group being computed with the first net as LSB in `WaveDataGroup::recalcData`, `WaveDataProviderGroup` and `WaveGroupValue` while the rest of the viewer takes the first net as MSB, so the same group showed different values depending on where it was evaluated + * fixed the waveform tree losing the expanded or collapsed state of its groups whenever its items are reordered, e.g., after a drag and drop + * fixed dropping a waveform onto a group in the waveform tree always inserting it as the first entry of the group regardless of the drop position + * dataflow analysis + * fixed running the dataflow analysis from the command line with `--dataflow`, which failed with "no gate types specified" + * fixed the dataflow analysis blocking a thread when it wrote its results, progress was reported while the lock that guards the results was held + * changed `create_modules` to create the pin groups of a DANA module in descending order + * `Result::write_dot` now returns the path it wrote to, as it replaces a wrong extension + * changed the plugin dialog to no longer open the written `.dot` graph in the DOT viewer unprompted + * fixed `dataflow.Configuration` leaking every instance created from Python * graph algorithm * added `NetlistGraph::from_gates` that builds a graph from a subset of the gates of a netlist, optionally representing a gate by a primary and a shadow vertex so that feedback through it does not close a cycle - * dataflow analysis - * fixed broken initialization of DANA plugin when starting via CLI + * fixed `NetlistGraph` destroying an igraph graph that was never created when `from_netlist` or `copy` fail before creating it + * made `NetlistGraph` non-copyable, as the implicitly generated copy shared the igraph internals between two objects that both freed them + * fixed `NetlistGraph` never being freed by Python, as its factories hand over ownership but it was bound with a non-owning holder; every graph built from a netlist leaked + * added Python bindings for `NetlistGraph::from_gates`, `is_shadow_vertex` and `get_all_vertices_from_gate` * FSM solver - * reworked the API of `solve_fsm` into a single function that is set up through a `Configuration` object, which also selects between the SMT and the brute force approach + * changed the API of `solve_fsm` into a single function that is set up through a `Configuration` object, which also selects between the SMT and the brute force approach * changed `solve_fsm` to report the value of each configured output of the FSM in each state, annotating the states of the DOT graph with it * changed `solve_fsm` to no longer write a file on its own, the DOT graph is now rendered by calling `generate_dot_graph` on the returned state transition graph - * changed `solve_fsm` to no longer open the graph in the dot viewer behind the user's back, use `dot_viewer.load_dot_file` to display it + * changed `solve_fsm` to no longer open the graph in the dot viewer unprompted, use `dot_viewer.load_dot_file` to display it * added `to_string` and `write_txt` to the state transition graph, printing the full conditions of all transitions together with a legend that maps the state bits, the outputs, and every net variable of a Boolean function back to the netlist - * split the `solve_fsm` API into one header per struct, mirroring the layout of the dataflow analysis plugin + * added tests * removed the debug output that `solve_fsm` printed to stdout on every run - * fixed `solve_fsm` interpreting a user-provided initial state with the wrong bit order, which made the exploration start from a different state than the one requested - * added tests for the `solve_fsm` plugin, which had none so far + * fixed `solve_fsm` interpreting a user-provided initial state with the wrong bit order * netlist preprocessing - * fixed `remove_redundant_gates` treating two flip-flops as duplicates although they start out at different values, as the fingerprint it groups them by covers the gate type and the fan-in but not the initial value, and flip-flops are merged on that fingerprint alone without the equivalence check that combinational gates get. This affects 11 of the 13 flip-flop types of the Xilinx UNISIM library, all of which carry an `INIT` value - * added an optional gate scope to the preprocessing functions of `netlist_preprocessing` and `xilinx_toolbox`, restricting which gates may be modified or deleted and defaulting to the entire netlist + * added an optional gate scope to the preprocessing functions, restricting which gates may be modified or deleted and defaulting to the entire netlist * changed `split_shift_registers` and `unify_ff_outputs` to assign newly created gates to the module of the gate they replace instead of always to the top module * fixed `simplify_lut_inits` crashing on a LUT whose output pin is unconnected * fixed `remove_unconnected_gates` looping forever if a gate could not be deleted + * fixed `remove_redundant_gates` treating two flip-flops as duplicates although they have different initialization values + * fixed `create_multi_bit_gate_modules` and `create_nets_at_unconnected_pins` handing out modules and nets to Python without keeping the netlist alive * Xilinx toolbox - * fixed `split_luts` crashing on a `LUT6_2` that only uses one of its two output pins, which is the common case the function is meant to handle + * added an optional gate scope to the preprocessing functions, restricting which gates may be modified or deleted and defaulting to the entire netlist + * changed the members of `xilinx_toolbox::LOC` to be default-initialised, `loc_type` to `PIN` and both coordinates to `0` + * added tests + * fixed `split_luts` crashing on a Xilinx `LUT6_2` that only uses one of its two output pins * fixed the documentation of `split_shift_registers`, which claimed that only `SRL16E` is supported although `SRLC32E` is handled as well - * added tests for the `xilinx_toolbox` plugin, which had none so far * bit-order propagation - * changed the interface to speak in a `BitOrder`, which is the order of one module pin group, and a `BitOrderResult`, which is what a propagation reports, in place of a map from pairs of module and pin group to a map from net to index. A bit order is now an object rather than a container, so Python can be given one without losing track of the netlist it belongs to, and a result iterates by module and pin group ID rather than by the addresses they happen to sit at - * added tests for the plugin, which had none + * changed the interface to speak in a `BitOrder`, which is the order of one module pin group, and a `BitOrderResult`, which is what a propagation reports, instead of maps + * added tests * fixed bug in the bitorder propagation algorithm that would assign a wrong propagation order if pingroups with direction none were given as parameters - * simulation - * added feature, selecting a waveform in viewer selects net in graph view as well - * fixed bug in waveform viewer, make sure that deleting a controller causes closing the tab - * fixed the documentation of `NetlistSimulatorController::initialize`, which described the behaviour of the legacy `NetlistSimulator`: it claimed that no gates or clocks may be added afterwards and that `simulate` calls it automatically, neither of which holds since its body became empty - * dot viewer - * added 'hover over node' feature in dot viewer - * fixed the DOT viewer drawing the line break escapes of a node label verbatim instead of breaking the line, and drawing a red debug rectangle around any label that does not fit its node + * Boolean influence + * fixed `get_ff_dependency_matrix` dereferencing an uninitialized pointer on every call + * fixed the four functions that return influences per net handing out the nets to Python without keeping the netlist alive, as nothing protected a borrowed object sitting in a dict key + * module identification + * changed the pin groups of an identified module to be descending. The pin indices are unchanged, and the `CTRL` group is now of type `control` instead of `enable` + * fixed `CandidateType.addition_offset` being bound to `addition` in Python, which made the two indistinguishable + * Liberty parser + * fixed the Liberty parser rejecting a `type` group that declares `bit_to` + * GUI extension demo + * fixed `ParameterType.Module` being bound to `Gate` in Python, which made the two indistinguishable + * GUI - * fixed the GUI hanging for minutes when a module with many gates is selected, `ModuleModel` emitted a row insert signal per item while the model was already being reset, which made the attached filter proxy remap its rows once per item - * fixed the GUI stalling when a large module is unfolded, the tree views measured every row individually and shaped the text of each gate name just to learn how tall the row is - * changed the module elements tree to not rebuild itself twice per selection change - * fixed bug in code and comment editor: avoid hang ups when RegExp-search returns zero-length matches - * added information to GUI setting file so that widgets position and size from previous session gets restored - * added option to focus on pin in pin context menu - * changed default order to 'descending' when creating a pin group via Python command - * changed behavior of GUI plugin manager to keep only those plugins loaded which are requested by user - * fixed the GUI dropping an unrelated gate from the selection instead of the net itself when a selected net is deleted - * fixed the GUI re-laying out its graph views once per gate while a preprocessing function invoked from a context menu deletes or replaces many of them - * added context menu entries to the GUI for `remove_buffers`, `unify_ff_outputs`, `split_luts`, and `split_shift_registers`, each applicable to the current selection or to the entire netlist - * module pin groups - * fixed bug in pin model which must not crash when deleting a non-empty pin group - * fixed bug by disallowing deletion of group comprising a single pin with same name + * module and gate pins + * changed the pin tree of a module to show the number of pins of each group and its order, ↑ for ascending and ↓ for descending, in a `Size/Index` column right after the name, which also holds the index of each pin; the pin tree of a gate shows the number of pins and the order of each group in its `Index` column + * added `Automatically rename pins` to the context menu of a pin group, which renames every pin of the group to `()` + * added `Set focus to pin` to the context menu of a pin in the pin tree of a gate and of a module, which moves the sub-focus of the graph view to that pin + * misc + * added restoring the GUI layout of the previous session on start + * changed the plugin manager of the GUI to unload a plugin, and the dependencies it pulled in, right after loading it to read its description, so that only plugins requested by the user or required by such a plugin stay loaded + * added context menu entries to the GUI for `remove_buffers`, `unify_ff_outputs`, `split_luts`, and `split_shift_registers`, each applicable to the current selection or to the entire netlist + * changed duplicating a view to place the modules and gates of the copy where they are in the original instead of laying the copy out anew + * bugfixes + * fixed the GUI crashing when a pin group that still holds pins is deleted + * fixed the GUI crashing when `Delete pin group` is chosen for a group that holds a single pin named like the group + * fixed toggling a pin group between ascending and descending losing the direction and the type of the group, and deleting a pin group creating the single-pin groups for its pins without direction and type + * fixed a module that reuses the ID of a deleted module being shown with the colour icon of the deleted one in the selection details + * fixed the GUI at times not registering the first change to a netlist after start, and so not offering to save it on close + * fixed unloading the waveform viewer or the DOT viewer plugin leaving its dock button behind + * fixed the GUI crashing when the selection is to be brought into view before the graph view has rendered its layout, e.g. by `GuiApi::selectGate` with `navigate_to_selection` set right after opening a netlist, the scene had no item for the selected element yet + * fixed the GUI dropping an unrelated gate from the selection instead of the net itself when a selected net is deleted + * fixed the GUI re-laying out its graph views once per gate while a preprocessing function invoked from a context menu deletes or replaces many of them + * fixed the GUI hanging for minutes when a module with many gates is selected + * fixed the GUI stalling when a large module is unfolded within a list of modules + * fixed the Python editor and the comment editor hanging when the search string is empty or the regular expression matches an empty string + * fixed the module tree to not rebuild itself twice per selection change + * fixed `GuiApi.selectGate`, `selectNet` and `selectModule` with `clear_current_selection` set, which is the default, keeping the previous selection + * fixed `GuiApi.getSelectedModules` and `getSelectedItems` not tying the returned modules to the netlist * Build and dependencies - * changed the GUI from Qt 5 to Qt 6, which is now required to build the GUI - * added a test that checks the Python bindings never hand out a borrowed pointer without keeping its owner alive, and never give a class bound with a non-owning holder to a factory that returns a `unique_ptr`. It covers plugins kept in a repository of their own as well, and holds free, static and submodule-level functions to the same rule as methods, which `hal::borrowed()` made fixable + * changed the GUI from Qt 5 to Qt 6 + * updated the vendored QuaZip from 1.3 to 1.5 as part of the move to Qt 6 + * added the Graphviz development libraries used by the DOT viewer to the build dependencies + * added support for Ubuntu 26.04, which is now built and tested in CI next to 22.04, 24.04 and macOS + * fixed `install_dependencies.sh` on macOS writing the Homebrew prefix into the shell configuration as the unexpanded text + * fixed the documentation build: Sphinx is now run through the Python interpreter that `hal_py` is linked against * updated the vendored igraph dependency from 0.10.12 to 1.0.1 and ported the graph algorithm and HAWKEYE plugins to the igraph 1.0 API - * removed the tests below `tests/python_binding`, which were neither referenced by the build nor by any workflow and called API that no longer exists ## [4.5.0](v4.5.0) - 2025-09-23 12:00:00+02:00 (urgency: medium) * plugins diff --git a/include/hal_core/netlist/module.h b/include/hal_core/netlist/module.h index 3bc0ce8f5956..0d0e20b4d627 100644 --- a/include/hal_core/netlist/module.h +++ b/include/hal_core/netlist/module.h @@ -484,7 +484,7 @@ namespace hal * @param[in] pins - The pins to be assigned to the pin group. Defaults to an empty vector. * @param[in] direction - The direction of the pin group, if any. Defaults to `PinDirection::none`. * @param[in] type - The type of the pin group, if any. Defaults to `PinType::none`. - * @param[in] ascending - Set `true` for ascending pin order (from 0 to n-1), `false` otherwise (from n-1 to 0). Defaults to `true`. + * @param[in] ascending - Set `true` for ascending pin order (from 0 to n-1), `false` otherwise (from n-1 to 0). Defaults to `false`. * @param[in] start_index - The start index of the pin group. Defaults to `0`. * @param[in] delete_empty_groups - Set `true` to delete groups that are empty after the pins have been assigned to the new group, `false` to keep empty groups. Defaults to `true`. * @param[in] force_name - Set `true` to enforce the name, `false` otherwise. If a pin group with the same name already exists, the existing pin group will be renamed. Defaults to `false`. @@ -495,7 +495,7 @@ namespace hal const std::vector pins = {}, PinDirection direction = PinDirection::none, PinType type = PinType::none, - bool ascending = true, + bool ascending = false, u32 start_index = 0, bool delete_empty_groups = true, bool force_name = false); @@ -508,7 +508,7 @@ namespace hal * @param[in] pins - The pins to be assigned to the pin group. Defaults to an empty vector. * @param[in] direction - The direction of the pin group, if any. Defaults to `PinDirection::none`. * @param[in] type - The type of the pin group, if any. Defaults to `PinType::none`. - * @param[in] ascending - Set `true` for ascending pin order (from 0 to n-1), `false` otherwise (from n-1 to 0). Defaults to `true`. + * @param[in] ascending - Set `true` for ascending pin order (from 0 to n-1), `false` otherwise (from n-1 to 0). Defaults to `false`. * @param[in] start_index - The start index of the pin group. Defaults to `0`. * @param[in] delete_empty_groups - Set `true` to delete groups that are empty after the pins have been assigned to the new group, `false` to keep empty groups. Defaults to `true`. * @param[in] force_name - Set `true` to enforce the name, `false` otherwise. If a pin group with the same name already exists, the existing pin group will be renamed. Defaults to `false`. diff --git a/install_dependencies.sh b/install_dependencies.sh index a08329261b90..4f7219e5687a 100755 --- a/install_dependencies.sh +++ b/install_dependencies.sh @@ -23,45 +23,45 @@ if [[ "$platform" == 'macOS' ]]; then pip3 install -r requirements.txt BREW_PREFIX=$(brew --prefix) if [ -n "$($SHELL -c 'echo $ZSH_VERSION')" ]; then - grep -Fxq 'export PATH="$BREW_PREFIX/opt/qt/bin:$PATH"' ~/.zshrc + grep -Fxq "export PATH=\"$BREW_PREFIX/opt/qt/bin:\$PATH\"" ~/.zshrc if ! [[ $? -eq 0 ]]; then - echo 'export PATH="$BREW_PREFIX/opt/qt/bin:$PATH"' >> ~/.zshrc + echo "export PATH=\"$BREW_PREFIX/opt/qt/bin:\$PATH\"" >> ~/.zshrc fi - grep -Fxq 'export PATH="$BREW_PREFIX/opt/llvm@14/bin:$PATH"' ~/.zshrc + grep -Fxq "export PATH=\"$BREW_PREFIX/opt/llvm@14/bin:\$PATH\"" ~/.zshrc if ! [[ $? -eq 0 ]]; then - echo 'export PATH="$BREW_PREFIX/opt/llvm@14/bin:$PATH"' >> ~/.zshrc + echo "export PATH=\"$BREW_PREFIX/opt/llvm@14/bin:\$PATH\"" >> ~/.zshrc fi - grep -Fxq 'export PATH="$BREW_PREFIX/opt/flex/bin:$PATH"' ~/.zshrc + grep -Fxq "export PATH=\"$BREW_PREFIX/opt/flex/bin:\$PATH\"" ~/.zshrc if ! [[ $? -eq 0 ]]; then - echo 'export PATH="$BREW_PREFIX/opt/flex/bin:$PATH"' >> ~/.zshrc + echo "export PATH=\"$BREW_PREFIX/opt/flex/bin:\$PATH\"" >> ~/.zshrc fi - grep -Fxq 'export PATH="$BREW_PREFIX/opt/bison/bin:$PATH"' ~/.zshrc + grep -Fxq "export PATH=\"$BREW_PREFIX/opt/bison/bin:\$PATH\"" ~/.zshrc if ! [[ $? -eq 0 ]]; then - echo 'export PATH="$BREW_PREFIX/opt/bison/bin:$PATH"' >> ~/.zshrc + echo "export PATH=\"$BREW_PREFIX/opt/bison/bin:\$PATH\"" >> ~/.zshrc fi source ~/.zshrc elif [ -n "$($SHELL -c 'echo $BASH_VERSION')" ]; then - grep -Fxq 'export PATH="$BREW_PREFIX/opt/qt/bin:$PATH"' ~/.bash_profile + grep -Fxq "export PATH=\"$BREW_PREFIX/opt/qt/bin:\$PATH\"" ~/.bash_profile if ! [[ $? -eq 0 ]]; then - echo 'export PATH="$BREW_PREFIX/opt/qt/bin:$PATH"' >> ~/.bash_profile + echo "export PATH=\"$BREW_PREFIX/opt/qt/bin:\$PATH\"" >> ~/.bash_profile fi - grep -Fxq 'export PATH="$BREW_PREFIX/opt/llvm@14/bin:$PATH"' ~/.bash_profile + grep -Fxq "export PATH=\"$BREW_PREFIX/opt/llvm@14/bin:\$PATH\"" ~/.bash_profile if ! [[ $? -eq 0 ]]; then - echo 'export PATH="$BREW_PREFIX/opt/llvm@14/bin:$PATH"' >> ~/.bash_profile + echo "export PATH=\"$BREW_PREFIX/opt/llvm@14/bin:\$PATH\"" >> ~/.bash_profile fi - grep -Fxq 'export PATH="$BREW_PREFIX/opt/flex/bin:$PATH"' ~/.bash_profile + grep -Fxq "export PATH=\"$BREW_PREFIX/opt/flex/bin:\$PATH\"" ~/.bash_profile if ! [[ $? -eq 0 ]]; then - echo 'export PATH="$BREW_PREFIX/opt/flex/bin:$PATH"' >> ~/.bash_profile + echo "export PATH=\"$BREW_PREFIX/opt/flex/bin:\$PATH\"" >> ~/.bash_profile fi - grep -Fxq 'export PATH="$BREW_PREFIX/opt/bison/bin:$PATH"' ~/.bash_profile + grep -Fxq "export PATH=\"$BREW_PREFIX/opt/bison/bin:\$PATH\"" ~/.bash_profile if ! [[ $? -eq 0 ]]; then - echo 'export PATH="$BREW_PREFIX/opt/bison/bin:$PATH"' >> ~/.bash_profile + echo "export PATH=\"$BREW_PREFIX/opt/bison/bin:\$PATH\"" >> ~/.bash_profile fi source ~/.bash_profile else diff --git a/plugins/.gitignore b/plugins/.gitignore index 4d488ba201de..0ffcd9fa994c 100644 --- a/plugins/.gitignore +++ b/plugins/.gitignore @@ -3,6 +3,8 @@ !bitorder_propagation/**/* !boolean_influence* !boolean_influence/**/* +!clock_tree_extractor* +!clock_tree_extractor/**/* !dataflow_analysis* !dataflow_analysis/**/* !dot_viewer* diff --git a/plugins/clock_tree_extractor/CMakeLists.txt b/plugins/clock_tree_extractor/CMakeLists.txt index 1fccdf0e035e..79624f6aafc2 100644 --- a/plugins/clock_tree_extractor/CMakeLists.txt +++ b/plugins/clock_tree_extractor/CMakeLists.txt @@ -18,7 +18,8 @@ if(PL_CLOCK_TREE_EXTRACTOR OR BUILD_ALL_PLUGINS) HEADER ${CLOCK_TREE_EXTRACTOR_INC} SOURCES ${CLOCK_TREE_EXTRACTOR_SRC} ${CLOCK_TREE_EXTRACTOR_PYTHON_SRC} LINK_LIBRARIES graph_algorithm - COMPILE_OPTIONS "-march=native" ) + add_subdirectory(test) + endif() diff --git a/plugins/clock_tree_extractor/python/python_bindings.cpp b/plugins/clock_tree_extractor/python/python_bindings.cpp index a6e3ab8341e7..43309d9d51d4 100644 --- a/plugins/clock_tree_extractor/python/python_bindings.cpp +++ b/plugins/clock_tree_extractor/python/python_bindings.cpp @@ -122,7 +122,9 @@ namespace hal :rtype: set[str] )" ); - py::class_( m, "ClockTree", R"()" ) + py::class_( m, "ClockTree", R"( + The clock distribution network of a netlist as a directed graph whose vertices are gates and nets. + )" ) .def_static( "from_netlist", []( const Netlist *netlist ) -> std::unique_ptr { @@ -137,7 +139,16 @@ namespace hal }, py::arg( "netlist" ), py::return_value_policy::move, - R"()" ) + py::keep_alive<0, 1>(), + R"( + Extract the clock tree of a netlist. + + Starting at the clock pin of every flip-flop, the extraction walks against the signal direction through buffers, inverters, delay gates, clock gates and toggle flip-flops up to the global input nets that drive them. + + :param hal_py.Netlist netlist: The netlist. + :returns: The clock tree on success, ``None`` otherwise. + :rtype: clock_tree_extractor.ClockTree or None + )" ) .def( "export", []( const cte::ClockTree &self, const std::string &pathname ) -> bool { @@ -151,7 +162,13 @@ namespace hal return false; }, py::arg( "pathname" ), - R"()" ) + R"( + Write the clock tree to a DOT file. + + :param str pathname: The path of the file to write. + :returns: ``True`` on success, ``False`` otherwise. + :rtype: bool + )" ) .def( "get_subtree", []( const cte::ClockTree &self, @@ -169,7 +186,16 @@ namespace hal py::arg( "ptr" ), py::arg( "parent" ) = false, py::return_value_policy::move, - R"()" ) + py::keep_alive<0, 1>(), + R"( + Get the clock tree below a gate or net as a clock tree of its own. + + :param ptr: The gate or net. + :type ptr: hal_py.Gate or hal_py.Net + :param bool parent: Set ``True`` to start one level up, at the parent of the given object, if it has exactly one. Defaults to ``False``. + :returns: The subtree on success, ``None`` otherwise. + :rtype: clock_tree_extractor.ClockTree or None + )" ) .def( "get_all", []( const cte::ClockTree &self ) -> py::list { @@ -189,7 +215,12 @@ namespace hal return result; }, borrowed(), - R"()" ) + R"( + Get all gates and nets of the clock tree. + + :returns: A list of gates and nets. + :rtype: list[hal_py.Gate or hal_py.Net] + )" ) .def( "get_vertex_from_ptr", []( const cte::ClockTree &self, const void *ptr ) -> py::object { @@ -202,7 +233,14 @@ namespace hal return py::none(); }, py::arg( "ptr" ), - R"()" ) + R"( + Get the igraph vertex ID of a gate or net of the clock tree. + + :param ptr: The gate or net. + :type ptr: hal_py.Gate or hal_py.Net + :returns: The vertex ID on success, ``None`` otherwise. + :rtype: int or None + )" ) .def( "get_ptr_from_vertex", []( const cte::ClockTree &self, const igraph_integer_t vertex ) -> py::object { @@ -225,7 +263,13 @@ namespace hal }, py::arg( "vertex" ), borrowed(), - R"()" ) + R"( + Get the gate or net behind an igraph vertex ID of the clock tree. + + :param int vertex: The vertex ID. + :returns: The gate or net on success, ``None`` otherwise. + :rtype: hal_py.Gate or hal_py.Net or None + )" ) .def( "get_vertices_from_ptrs", []( const cte::ClockTree &self, const std::vector &ptrs ) -> py::list { @@ -238,7 +282,13 @@ namespace hal return py::none(); }, py::arg( "ptrs" ), - R"()" ) + R"( + Get the igraph vertex IDs of gates and nets of the clock tree. + + :param list[hal_py.Gate or hal_py.Net] ptrs: The gates and nets. + :returns: The vertex IDs on success, ``None`` otherwise. + :rtype: list[int] or None + )" ) .def( "get_ptrs_from_vertices", []( const cte::ClockTree &self, const std::vector &vertices ) -> py::list { @@ -268,7 +318,14 @@ namespace hal return py::none(); }, py::arg( "vertices" ), - R"()" ) + borrowed(), + R"( + Get the gates and nets behind igraph vertex IDs of the clock tree. + + :param list[int] vertices: The vertex IDs. + :returns: The gates and nets on success, ``None`` otherwise. + :rtype: list[hal_py.Gate or hal_py.Net] or None + )" ) .def( "get_parents", []( const cte::ClockTree &self, const void *ptr ) -> py::list { @@ -299,7 +356,14 @@ namespace hal }, py::arg( "ptr" ), borrowed(), - R"()" ) + R"( + Get the gates and nets directly upstream of a gate or net in the clock tree. + + :param ptr: The gate or net. + :type ptr: hal_py.Gate or hal_py.Net + :returns: The neighbors on success, ``None`` otherwise. + :rtype: list[hal_py.Gate or hal_py.Net] or None + )" ) .def( "get_childs", []( const cte::ClockTree &self, const void *ptr ) -> py::list { @@ -330,10 +394,32 @@ namespace hal }, py::arg( "ptr" ), borrowed(), - R"()" ) - .def( "get_gates", &cte::ClockTree::get_gates, borrowed(), R"()" ) - .def( "get_nets", &cte::ClockTree::get_nets, borrowed(), R"()" ) - .def( "get_netlist", &cte::ClockTree::get_netlist, borrowed(), R"()" ); + R"( + Get the gates and nets directly downstream of a gate or net in the clock tree. + + :param ptr: The gate or net. + :type ptr: hal_py.Gate or hal_py.Net + :returns: The neighbors on success, ``None`` otherwise. + :rtype: list[hal_py.Gate or hal_py.Net] or None + )" ) + .def( "get_gates", &cte::ClockTree::get_gates, borrowed(), R"( + Get all gates of the clock tree. + + :returns: The gates. + :rtype: list[hal_py.Gate] + )" ) + .def( "get_nets", &cte::ClockTree::get_nets, borrowed(), R"( + Get all nets of the clock tree. + + :returns: The nets. + :rtype: list[hal_py.Net] + )" ) + .def( "get_netlist", &cte::ClockTree::get_netlist, borrowed(), R"( + Get the netlist the clock tree was extracted from. + + :returns: The netlist. + :rtype: hal_py.Netlist + )" ); #ifndef PYBIND11_MODULE return m.ptr(); diff --git a/plugins/clock_tree_extractor/src/clock_tree.cpp b/plugins/clock_tree_extractor/src/clock_tree.cpp index 182846ea55d3..227779bd4c6a 100644 --- a/plugins/clock_tree_extractor/src/clock_tree.cpp +++ b/plugins/clock_tree_extractor/src/clock_tree.cpp @@ -224,8 +224,10 @@ namespace hal } else if( clk->is_global_input_net() ) { + // the flip-flop is clocked straight from the outside: the net is the root and the flip-flop its only child vertices.insert( (void *) clk ); ptrs_to_type[(void *) clk] = PtrType::NET; + edges.insert( { (void *) clk, (void *) ff } ); continue; } else if( clk->get_num_of_sources() == 0 ) diff --git a/plugins/clock_tree_extractor/test/CMakeLists.txt b/plugins/clock_tree_extractor/test/CMakeLists.txt new file mode 100644 index 000000000000..f8d7bd9edabd --- /dev/null +++ b/plugins/clock_tree_extractor/test/CMakeLists.txt @@ -0,0 +1,13 @@ +if(BUILD_TESTS) + include_directories(${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/tests ${CMAKE_SOURCE_DIR}/plugins/clock_tree_extractor/include ${CMAKE_SOURCE_DIR}/plugins/graph_algorithm/include) + + add_executable(runTest-clock_tree_extractor clock_tree_extractor.cpp) + + target_link_libraries(runTest-clock_tree_extractor clock_tree_extractor graph_algorithm pthread gtest hal::core hal::netlist test_utils) + + add_test(runTest-clock_tree_extractor ${CMAKE_BINARY_DIR}/bin/hal_plugins/runTest-clock_tree_extractor --gtest_output=xml:${CMAKE_BINARY_DIR}/gtestresults-runBasicTests.xml) + + if(${CMAKE_BUILD_TYPE} STREQUAL "Debug") + add_sanitizers(runTest-clock_tree_extractor) + endif() +endif() diff --git a/plugins/clock_tree_extractor/test/clock_tree_extractor.cpp b/plugins/clock_tree_extractor/test/clock_tree_extractor.cpp new file mode 100644 index 000000000000..546aed7dc37e --- /dev/null +++ b/plugins/clock_tree_extractor/test/clock_tree_extractor.cpp @@ -0,0 +1,100 @@ +#include "clock_tree_extractor/clock_tree.h" +#include "hal_core/netlist/gate.h" +#include "hal_core/netlist/net.h" +#include "hal_core/netlist/netlist.h" +#include "netlist_test_utils.h" + +#include "gtest/gtest.h" + +namespace hal +{ + class ClockTreeExtractorTest : public ::testing::Test + { + protected: + virtual void SetUp() + { + test_utils::init_log_channels(); + } + + virtual void TearDown() + { + } + + static std::vector upstream_of(const cte::ClockTree& tree, const Gate* gate) + { + auto res = tree.get_neighbors(gate, IGRAPH_IN); + EXPECT_TRUE(res.is_ok()); + std::vector ptrs; + for (const auto& [ptr, type] : res.get()) + { + ptrs.push_back(ptr); + } + return ptrs; + } + }; + + /** + * A flip-flop whose clock pin is driven by a buffer behind a global input net: the tree runs from the net through the + * buffer to the flip-flop. + * + * Functions: from_netlist, get_neighbors + */ + TEST_F(ClockTreeExtractorTest, check_buffered_clock) + { + TEST_START + { + std::unique_ptr nl = test_utils::create_empty_netlist(); + ASSERT_NE(nl, nullptr); + const GateLibrary* gl = nl->get_gate_library(); + + Gate* buf = nl->create_gate(gl->get_gate_type_by_name("BUF"), "buf"); + Gate* ff = nl->create_gate(gl->get_gate_type_by_name("DFF"), "ff"); + + Net* clk = nl->create_net("clk"); + clk->mark_global_input_net(); + clk->add_destination(buf, "I"); + test_utils::connect(nl.get(), buf, "O", ff, "CLK"); + + auto res = cte::ClockTree::from_netlist(nl.get()); + ASSERT_TRUE(res.is_ok()); + std::unique_ptr tree = res.get(); + + EXPECT_EQ(tree->get_gates().size(), 2); + EXPECT_EQ(tree->get_nets().size(), 1); + EXPECT_EQ(upstream_of(*tree, ff), std::vector{buf}); + EXPECT_EQ(upstream_of(*tree, buf), std::vector{clk}); + } + TEST_END + } + + /** + * A flip-flop clocked straight from a global input net, without any gate in between: the net is the root of the tree and + * the flip-flop hangs below it. + * + * Functions: from_netlist, get_neighbors + */ + TEST_F(ClockTreeExtractorTest, check_direct_clock) + { + TEST_START + { + std::unique_ptr nl = test_utils::create_empty_netlist(); + ASSERT_NE(nl, nullptr); + const GateLibrary* gl = nl->get_gate_library(); + + Gate* ff = nl->create_gate(gl->get_gate_type_by_name("DFF"), "ff"); + + Net* clk = nl->create_net("clk"); + clk->mark_global_input_net(); + clk->add_destination(ff, "CLK"); + + auto res = cte::ClockTree::from_netlist(nl.get()); + ASSERT_TRUE(res.is_ok()); + std::unique_ptr tree = res.get(); + + EXPECT_EQ(tree->get_gates().size(), 1); + EXPECT_EQ(tree->get_nets().size(), 1); + EXPECT_EQ(upstream_of(*tree, ff), std::vector{clk}); + } + TEST_END + } +} // namespace hal diff --git a/plugins/dataflow_analysis/include/dataflow_analysis/api/result.h b/plugins/dataflow_analysis/include/dataflow_analysis/api/result.h index 5e344fe7c647..8d347b74d283 100644 --- a/plugins/dataflow_analysis/include/dataflow_analysis/api/result.h +++ b/plugins/dataflow_analysis/include/dataflow_analysis/api/result.h @@ -148,13 +148,6 @@ namespace hal */ hal::Result> get_gate_predecessors(const Gate* gate) const; - /** - * @brief Open DOT graph in dot viewer if appropriate plugin was loaded. - * - * @param[in] out_path - The output path. - */ - void open_dot_in_viewer(const std::filesystem::path& out_path) const; - /** * @brief Write the dataflow graph as a DOT graph to the specified location. * diff --git a/plugins/dataflow_analysis/src/api/result.cpp b/plugins/dataflow_analysis/src/api/result.cpp index 0d3f339ffcae..b02b1f3c7e9b 100644 --- a/plugins/dataflow_analysis/src/api/result.cpp +++ b/plugins/dataflow_analysis/src/api/result.cpp @@ -4,8 +4,6 @@ #include "hal_core/netlist/gate_library/gate_type.h" #include "hal_core/netlist/module.h" #include "hal_core/netlist/netlist.h" -#include "hal_core/plugin_system/gui_extension_interface.h" -#include "hal_core/plugin_system/plugin_manager.h" #include "hal_core/utilities/log.h" #include "hal_core/utilities/utils.h" @@ -247,28 +245,6 @@ namespace hal return OK({}); } - void dataflow::Result::open_dot_in_viewer(const std::filesystem::path& out_path) const - { - BasePluginInterface* bpif = plugin_manager::get_plugin_instance("dot_viewer"); - if (!bpif) - { - log_info("dataflow", "Cannot find 'dot_viewer' plugin, dot graph not displayed."); - return; - } - GuiExtensionInterface* geif = bpif->get_first_extension(); - if (!geif) - { - log_info("dataflow", "Cannot find dot_viewer GUI interface, dot graph not displayed."); - return; - } - std::vector params; - params.push_back(PluginParameter(PluginParameter::ExistingFile, "filename", "", out_path.string())); - params.push_back(PluginParameter(PluginParameter::String, "plugin", "", "dataflow")); - params.push_back(PluginParameter(PluginParameter::PushButton, "exec", "", "clicked")); - geif->set_parameter(params); - log_info("dataflow", "Request to display graph '{}' send to dot viewer.", out_path.string()); - } - hal::Result dataflow::Result::write_dot(const std::filesystem::path& out_path, const std::unordered_set& group_ids) const { auto write_path = out_path; diff --git a/plugins/dataflow_analysis/src/plugin_dataflow.cpp b/plugins/dataflow_analysis/src/plugin_dataflow.cpp index 65fb038ef336..2a27c0babf19 100644 --- a/plugins/dataflow_analysis/src/plugin_dataflow.cpp +++ b/plugins/dataflow_analysis/src/plugin_dataflow.cpp @@ -212,7 +212,6 @@ namespace hal return; } auto grouping = grouping_res.get(); - std::filesystem::path dot_graph_written_to_path; if (m_write_dot) { @@ -220,10 +219,6 @@ namespace hal { log_error("dataflow", "could not write .dot file:\n{}", res.get_error().get()); } - else - { - dot_graph_written_to_path = res.get(); - } } if (m_write_txt) @@ -241,12 +236,6 @@ namespace hal log_error("dataflow", "could not create modules:\n{}", res.get_error().get()); } } - - // open in dot viewer must be called after modules got created - if (!dot_graph_written_to_path.empty()) - { - grouping.open_dot_in_viewer(dot_graph_written_to_path); - } } diff --git a/plugins/dot_viewer/CMakeLists.txt b/plugins/dot_viewer/CMakeLists.txt index de4035cd5252..e9369a170126 100644 --- a/plugins/dot_viewer/CMakeLists.txt +++ b/plugins/dot_viewer/CMakeLists.txt @@ -21,7 +21,6 @@ if(PL_DOT_VIEWER OR BUILD_ALL_PLUGINS) SHARED HEADER ${DOT_VIEWER_INC} SOURCES ${DOT_VIEWER_SRC} ${DOT_VIEWER_PYTHON_SRC} ${MOC_HDR} - COMPILE_OPTIONS "-march=native" PYDOC SPHINX_DOC_INDEX_FILE ${CMAKE_CURRENT_SOURCE_DIR}/documentation/dot_viewer.rst ) diff --git a/plugins/dot_viewer/deps/QGVCore/CMakeLists.txt b/plugins/dot_viewer/deps/QGVCore/CMakeLists.txt index fe53d6e370a7..f1ef17ca121e 100644 --- a/plugins/dot_viewer/deps/QGVCore/CMakeLists.txt +++ b/plugins/dot_viewer/deps/QGVCore/CMakeLists.txt @@ -50,6 +50,11 @@ ADD_DEFINITIONS(-DQGVCORE_LIB -D_PACKAGE_ast -D_dll_import -D_BLD_cdt -D_DLL_BLD ADD_DEFINITIONS(-DQVGCORE_LIB) +# Graphviz up to 13 lets a reader supply its own memory discipline, later versions dropped it +if(HAS_AGDISC_MEM) + ADD_DEFINITIONS(-DHAS_AGDISC_MEM) +endif() + QT6_WRAP_UI(QGVLIB_UI_H ${qgvlib_UI}) QT6_ADD_RESOURCES(qgvlib_RCCS ${qgvlib_QRC}) diff --git a/plugins/dot_viewer/deps/QGVCore/QGVScene.cpp b/plugins/dot_viewer/deps/QGVCore/QGVScene.cpp index 085892031d9c..79172305a5e7 100644 --- a/plugins/dot_viewer/deps/QGVCore/QGVScene.cpp +++ b/plugins/dot_viewer/deps/QGVCore/QGVScene.cpp @@ -70,7 +70,7 @@ void QGVInteraction::disableHandler() QGVScene::QGVScene(QObject *parent) : QGraphicsScene(parent), _drawGrid(true) { -#ifdef HAS_AGDISK_MEM +#ifdef HAS_AGDISC_MEM aaglex_destroy(); #endif _context = new QGVGvcPrivate(gvContext()); diff --git a/plugins/dot_viewer/deps/QGVCore/private/QGVCore.h b/plugins/dot_viewer/deps/QGVCore/private/QGVCore.h index f82d0624f6f6..b3d27b371da3 100644 --- a/plugins/dot_viewer/deps/QGVCore/private/QGVCore.h +++ b/plugins/dot_viewer/deps/QGVCore/private/QGVCore.h @@ -85,7 +85,7 @@ class QGVCore static Agraph_t *agmemread2(const char *cp) { -#ifdef HAS_AGDISK_MEM +#ifdef HAS_AGDISC_MEM Agraph_t* g; rdr_t rdr; Agdisc_t disc; diff --git a/plugins/dot_viewer/src/dot_viewer.cpp b/plugins/dot_viewer/src/dot_viewer.cpp index 6ed347cfd346..14eff18a62d9 100644 --- a/plugins/dot_viewer/src/dot_viewer.cpp +++ b/plugins/dot_viewer/src/dot_viewer.cpp @@ -184,11 +184,10 @@ namespace hal const char* target[] = {"node", "edge", nullptr }; for (int i=0; target[i]; i++) { - QAction* act = menu.addAction(QString("HAL color style overwrites %1 attibute").arg(target[i])); + QAction* act = menu.addAction(QString("HAL color style overwrites %1 attribute").arg(target[i])); act->setCheckable(true); act->setChecked(QGVStyle::instance()->getStyle((QGVStyle::StyleTarget)i) != QGVStyle::Graphviz); connect (act, &QAction::triggered, this, [target,i,this](bool checked){ - std::cerr << QString("HAL color style overwrites %1 attibute => %2").arg(target[i]).arg(checked?"true":"false").toStdString() << std::endl; if (checked) QGVStyle::instance()->setStyle((QGVStyle::StyleTarget)i, (QGVStyle::StyleType) MainWindow::sSettingStyle->value().toInt()); else diff --git a/plugins/gui/src/selection_details_widget/module_details_widget/module_pins_tree.cpp b/plugins/gui/src/selection_details_widget/module_details_widget/module_pins_tree.cpp index b198ee2686f0..b66e013d7ba3 100644 --- a/plugins/gui/src/selection_details_widget/module_details_widget/module_pins_tree.cpp +++ b/plugins/gui/src/selection_details_widget/module_details_widget/module_pins_tree.cpp @@ -243,7 +243,7 @@ namespace hal ActionPingroup* act = ActionPingroup::toggleAscendingGroup(mod, itemId); if (act) act->exec(); }); - menu.addAction("Autmatically rename pins", [itemId, mod](){ + menu.addAction("Automatically rename pins", [itemId, mod](){ ActionPingroup* act = ActionPingroup::automaticallyRenamePins(mod, itemId); if (act) act->exec(); }); diff --git a/plugins/solve_fsm/python/python_bindings.cpp b/plugins/solve_fsm/python/python_bindings.cpp index 29e2538179e6..4d555266bc99 100644 --- a/plugins/solve_fsm/python/python_bindings.cpp +++ b/plugins/solve_fsm/python/python_bindings.cpp @@ -264,7 +264,6 @@ namespace hal Each state becomes a node labeled with its value and, if outputs were computed, with the value of every output in that state. Each transition becomes an edge labeled with its condition. - If the ``dot_viewer`` plugin is available, the written graph is additionally offered to it for display. :param pathlib.Path graph_path: The file path at which to store the graph. No file is written if the path is left empty. Defaults to an empty path. :param int max_condition_length: The maximum number of characters printed for a Boolean function. Defaults to 128. diff --git a/src/python_bindings/bindings/gate_type.cpp b/src/python_bindings/bindings/gate_type.cpp index 7740389f1fbc..471011bc464d 100644 --- a/src/python_bindings/bindings/gate_type.cpp +++ b/src/python_bindings/bindings/gate_type.cpp @@ -22,6 +22,7 @@ namespace hal .value("pll", GateTypeProperty::pll, R"(PLL gate type.)") .value("oscillator", GateTypeProperty::oscillator, R"(Oscillator gate type.)") .value("scan", GateTypeProperty::scan, R"(Scan gate type.)") + .value("delay", GateTypeProperty::delay, R"(Delay gate type.)") .value("c_buffer", GateTypeProperty::c_buffer, R"(Buffer gate type.)") .value("c_inverter", GateTypeProperty::c_inverter, R"(Inverter gate type.)") .value("c_and", GateTypeProperty::c_and, R"(AND gate type.)") diff --git a/src/python_bindings/bindings/module.cpp b/src/python_bindings/bindings/module.cpp index 6cce93c0f9f3..ca3e4d5ede0b 100644 --- a/src/python_bindings/bindings/module.cpp +++ b/src/python_bindings/bindings/module.cpp @@ -728,8 +728,8 @@ namespace hal :param list[hal_py.ModulePin] pins: The pins to be assigned to the pin group. Defaults to an empty list. :param hal_py.PinDirection direction: The direction of the pin group, if any. Defaults to ``hal_py.PinDirection.none``. :param hal_py.PinType type: The type of the pin group, if any. Defaults to ``hal_py.PinType.none``. - :param bool ascending: Set ``True`` for ascending pin order (from 0 to n-1), ``False`` otherwise (from n-1 to 0). Defaults to ``True``. - :param int start_index: The start index of the pin group. Defaults to ``0``. + :param bool ascending: Set ``True`` for ascending pin order (from 0 to n-1), ``False`` otherwise (from n-1 to 0). Defaults to ``False``. + :param int start_index: The start index of the pin group. Defaults to ``0`` for an ascending group and to the index of the last pin for a descending one. :param bool delete_empty_groups: Set ``True`` to delete groups that are empty after the pins have been assigned to the new group, ``False`` to keep empty groups. Defaults to ``True``. :param bool force_name: Set ``True`` to enforce the name, ``False`` otherwise. If a pin group with the same name already exists, the existing pin group will be renamed. Defaults to ``False``. :returns: The pin group on success, ``None`` otherwise. @@ -781,9 +781,9 @@ namespace hal :param list[hal_py.ModulePin] pins: The pins to be assigned to the pin group. Defaults to an empty list. :param hal_py.PinDirection direction: The direction of the pin group, if any. Defaults to ``hal_py.PinDirection.none``. :param hal_py.PinType type: The type of the pin group, if any. Defaults to ``hal_py.PinType.none``. - :param bool ascending: Set ``True`` for ascending pin order (from 0 to n-1), ``False`` otherwise (from n-1 to 0). Defaults to ``True``. - :param int start_index: The start index of the pin group. Defaults to ``0``. - :param bool delete_empty_groups: Set ``True``` to delete groups that are empty after the pins have been assigned to the new group, ```False``` to keep empty groups. Defaults to ```True```. + :param bool ascending: Set ``True`` for ascending pin order (from 0 to n-1), ``False`` otherwise (from n-1 to 0). Defaults to ``False``. + :param int start_index: The start index of the pin group. Defaults to ``0`` for an ascending group and to the index of the last pin for a descending one. + :param bool delete_empty_groups: Set ``True`` to delete groups that are empty after the pins have been assigned to the new group, ``False`` to keep empty groups. Defaults to ``True``. :param bool force_name: Set ``True`` to enforce the name, ``False`` otherwise. If a pin group with the same name already exists, the existing pin group will be renamed. Defaults to ``False``. :returns: The pin group on success, ``None`` otherwise. :rtype: hal_py.ModulePinGroup or None