HAL  v4.5.0-136-gbce33ee73
The Hardware Analyzer - a comprehensive reverse engineering and manipulation framework for gate-level netlists.
python_bindings.cpp
Go to the documentation of this file.
2 
5 #include "pybind11/operators.h"
6 #include "pybind11/pybind11.h"
7 #include "pybind11/stl.h"
8 #include "pybind11/stl_bind.h"
10 #include "solve_fsm/solve_fsm.h"
11 
12 #include <map>
13 namespace py = pybind11;
14 
15 namespace hal
16 {
17  // the name in PYBIND11_MODULE/PYBIND11_PLUGIN *MUST* match the filename of the output library (without extension),
18  // otherwise you will get "ImportError: dynamic module does not define module export function" when importing the module
19 
20 #ifdef PYBIND11_MODULE
21  PYBIND11_MODULE(solve_fsm, m)
22  {
23  m.doc() = "Plugin to automatically generate FSM state transition graphs for given FSMs.";
24 #else
26  {
27  py::module m("solve_fsm", "Plugin to automatically generate FSM state transition graphs for given FSMs.");
28 #endif // ifdef PYBIND11_MODULE
29 
30  py::class_<SolveFsmPlugin, RawPtrWrapper<SolveFsmPlugin>, BasePluginInterface> py_solve_fsm(
31  m, "SolveFsmPlugin", R"(This class provides an interface to integrate FSM solving as a plugin within the HAL framework.)");
32  py_solve_fsm.def_property_readonly("name", &SolveFsmPlugin::get_name, R"(
33  The name of the plugin.
34 
35  :type: str
36  )");
37 
38  py_solve_fsm.def("get_name", &SolveFsmPlugin::get_name, R"(
39  Get the name of the plugin.
40 
41  :returns: Plugin name.
42  :rtype: str
43  )");
44 
45  py_solve_fsm.def_property_readonly("version", &SolveFsmPlugin::get_version, R"(
46  The version of the plugin.
47 
48  :type: str
49  )");
50 
51  py_solve_fsm.def("get_version", &SolveFsmPlugin::get_version, R"(
52  Get the version of the plugin.
53 
54  :returns: Plugin version.
55  :rtype: str
56  )");
57 
58  py_solve_fsm.def_property_readonly("description", &SolveFsmPlugin::get_description, R"(
59  The description of the plugin.
60 
61  :type: str
62  )");
63 
64  py_solve_fsm.def("get_description", &SolveFsmPlugin::get_description, R"(
65  Get the description of the plugin.
66 
67  :returns: The description of the plugin.
68  :rtype: str
69  )");
70 
71  py::class_<solve_fsm::Configuration> py_solve_fsm_configuration(m, "Configuration", R"(
72  The configuration of a run of the FSM solver.
73 
74  Holds everything the solver needs to know about the FSM, including the netlist that implements it.
75  The state register and the transition logic are mandatory, everything else is optional.
76 
77  States are encoded as integers, with the first flip-flop of the state register providing the least significant bit.
78  )");
79 
80  py_solve_fsm_configuration.def(py::init<Netlist*>(), py::arg("nl"), R"(
81  Construct a new FSM solver configuration for the given netlist.
82 
83  :param hal_py.Netlist nl: The netlist that implements the FSM.
84  )");
85 
86  py_solve_fsm_configuration.def_readwrite("netlist", &solve_fsm::Configuration::netlist, R"(
87  The netlist that implements the FSM.
88 
89  :type: hal_py.Netlist
90  )");
91 
92  py_solve_fsm_configuration.def_readwrite("state_register", &solve_fsm::Configuration::state_register, borrowed(), R"(
93  The flip-flops that make up the state register of the FSM.
94 
95  The first flip-flop provides the least significant bit of the state.
96  Defaults to an empty list, but a state register is required for the solver to run.
97 
98  :type: list[hal_py.Gate]
99  )");
100 
101  py_solve_fsm_configuration.def_readwrite("transition_logic", &solve_fsm::Configuration::transition_logic, borrowed(), R"(
102  The combinational gates that compute the next state of the FSM.
103 
104  Defaults to an empty list, but transition logic is required for the solver to run.
105 
106  :type: list[hal_py.Gate]
107  )");
108 
109  py_solve_fsm_configuration.def_readwrite("outputs", &solve_fsm::Configuration::outputs, borrowed(), R"(
110  The outputs of the FSM, each given as a name and the nets that make up that output.
111 
112  The first net of an output provides its least significant bit, so a single-bit output is a list holding one net.
113  Defaults to an empty list, in which case no outputs are computed.
114 
115  :type: list[tuple(str,list[hal_py.Net])]
116  )");
117 
118  py_solve_fsm_configuration.def_readwrite("initial_state", &solve_fsm::Configuration::initial_state, borrowed(), R"(
119  The initial value of each flip-flop of the state register.
120 
121  Only states reachable from the resulting initial state are explored.
122  Defaults to an empty dict, in which case the FSM starts in state 0.
123 
124  :type: dict[hal_py.Gate,bool]
125  )");
126 
127  py_solve_fsm_configuration.def_readwrite("timeout", &solve_fsm::Configuration::timeout, R"(
128  The timeout for the underlying SMT solver in milliseconds. Defaults to 600000 ms.
129 
130  Has no effect when ``brute_force`` is set, as no SMT solver is used then.
131 
132  :type: int
133  )");
134 
135  py_solve_fsm_configuration.def_readwrite("brute_force", &solve_fsm::Configuration::brute_force, R"(
136  Enumerate all states instead of using an SMT solver. Defaults to ``False``.
137 
138  Brute forcing needs no external solver and is faster for small state registers, but its runtime doubles with every additional flip-flop.
139  Both approaches produce the same state transition graph.
140 
141  :type: bool
142  )");
143 
144  py_solve_fsm_configuration.def("with_state_register", &solve_fsm::Configuration::with_state_register, py::arg("state_register"), R"(
145  Set the flip-flops that make up the state register of the FSM.
146 
147  :param list[hal_py.Gate] state_register: The flip-flops of the state register, least significant bit first.
148  :returns: The updated FSM solver configuration.
149  :rtype: solve_fsm.Configuration
150  )");
151 
152  py_solve_fsm_configuration.def("with_transition_logic", &solve_fsm::Configuration::with_transition_logic, py::arg("transition_logic"), R"(
153  Set the combinational gates that compute the next state of the FSM.
154 
155  :param list[hal_py.Gate] transition_logic: The gates of the transition logic.
156  :returns: The updated FSM solver configuration.
157  :rtype: solve_fsm.Configuration
158  )");
159 
160  py_solve_fsm_configuration.def("with_outputs", &solve_fsm::Configuration::with_outputs, py::arg("outputs"), R"(
161  Set the outputs of the FSM that the solver should evaluate in each state.
162 
163  :param list[tuple(str,list[hal_py.Net])] outputs: The outputs, each given as a name and the nets that make up that output, least significant bit first.
164  :returns: The updated FSM solver configuration.
165  :rtype: solve_fsm.Configuration
166  )");
167 
168  py_solve_fsm_configuration.def("with_initial_state", &solve_fsm::Configuration::with_initial_state, py::arg("initial_state"), R"(
169  Set the initial value of each flip-flop of the state register.
170 
171  :param dict[hal_py.Gate,bool] initial_state: The initial value of each flip-flop of the state register.
172  :returns: The updated FSM solver configuration.
173  :rtype: solve_fsm.Configuration
174  )");
175 
176  py_solve_fsm_configuration.def("with_timeout", &solve_fsm::Configuration::with_timeout, py::arg("timeout"), R"(
177  Set the timeout for the underlying SMT solver.
178 
179  :param int timeout: The timeout in milliseconds.
180  :returns: The updated FSM solver configuration.
181  :rtype: solve_fsm.Configuration
182  )");
183 
184  py_solve_fsm_configuration.def("with_brute_force", &solve_fsm::Configuration::with_brute_force, py::arg("brute_force") = true, R"(
185  Set whether to enumerate all states instead of using an SMT solver.
186 
187  :param bool brute_force: Set ``True`` to enumerate all states, ``False`` to use an SMT solver. Defaults to ``True``.
188  :returns: The updated FSM solver configuration.
189  :rtype: solve_fsm.Configuration
190  )");
191 
192  py::class_<solve_fsm::StateTransitionGraph> py_state_transition_graph(m, "StateTransitionGraph", R"(
193  The state transition graph of an FSM, i.e., the behavior that its netlist implements.
194 
195  States are encoded as integers, with the first flip-flop of the state register providing the least significant bit.
196  )");
197 
198  py_state_transition_graph.def_readonly("transitions", &solve_fsm::StateTransitionGraph::transitions, R"(
199  A dict from each state to its successor states, together with the condition under which the respective transition is taken.
200 
201  :type: dict[int,dict[int,hal_py.BooleanFunction]]
202  )");
203 
204  py_state_transition_graph.def_readonly("outputs", &solve_fsm::StateTransitionGraph::outputs, R"(
205  A dict from each state to the value of every output of the FSM in that state.
206 
207  The outputs of a state are given in the order in which they were configured.
208  An output of a Moore FSM only depends on the state, so its Boolean function is constant.
209  An output of a Mealy FSM may also depend on the inputs of the FSM, in which case its Boolean function still contains the input variables.
210 
211  Empty unless outputs were configured.
212 
213  :type: dict[int,list[tuple(str,hal_py.BooleanFunction)]]
214  )");
215 
216  py_state_transition_graph.def_readonly("netlist", &solve_fsm::StateTransitionGraph::netlist, R"(
217  The netlist that implements the FSM.
218 
219  :type: hal_py.Netlist
220  )");
221 
222  py_state_transition_graph.def_readonly("state_register", &solve_fsm::StateTransitionGraph::state_register, borrowed(), R"(
223  The flip-flops that make up the state register, in the order that determines the encoding of a state.
224 
225  The first flip-flop provides the least significant bit, so this is what maps a state back to the netlist.
226 
227  :type: list[hal_py.Gate]
228  )");
229 
230  py_state_transition_graph.def_readonly("output_nets", &solve_fsm::StateTransitionGraph::output_nets, borrowed(), R"(
231  The outputs of the FSM, each given as a name and the nets that make up that output.
232 
233  The first net of an output provides its least significant bit. Empty unless outputs were configured.
234 
235  :type: list[tuple(str,list[hal_py.Net])]
236  )");
237 
238  py_state_transition_graph.def("get_state_size", &solve_fsm::StateTransitionGraph::get_state_size, R"(
239  Get the number of flip-flops that make up the state register, i.e., the bit-size of a state.
240 
241  :returns: The bit-size of a state.
242  :rtype: int
243  )");
244 
245  py_state_transition_graph.def(
246  "generate_dot_graph",
247  [](const solve_fsm::StateTransitionGraph& self, const std::filesystem::path& graph_path, const u32 max_condition_length, const u32 base) -> std::optional<std::string> {
248  auto res = self.generate_dot_graph(graph_path, max_condition_length, base);
249  if (res.is_ok())
250  {
251  return res.get();
252  }
253  else
254  {
255  log_error("python_context", "{}", res.get_error().get());
256  return std::nullopt;
257  }
258  },
259  py::arg("graph_path") = "",
260  py::arg("max_condition_length") = 128,
261  py::arg("base") = 10,
262  R"(
263  Render the state transition graph in the DOT format.
264 
265  Each state becomes a node labeled with its value and, if outputs were computed, with the value of every output in that state.
266  Each transition becomes an edge labeled with its condition.
267 
268  :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.
269  :param int max_condition_length: The maximum number of characters printed for a Boolean function. Defaults to 128.
270  :param int base: The base in which state and output values are printed, either 2 or 10. Defaults to 10.
271  :returns: The graph in the DOT format on success, ``None`` otherwise.
272  :rtype: str or None
273  )");
274 
275  py_state_transition_graph.def(
276  "to_string",
277  [](const solve_fsm::StateTransitionGraph& self, const u32 base) -> std::optional<std::string> {
278  auto res = self.to_string(base);
279  if (res.is_ok())
280  {
281  return res.get();
282  }
283  else
284  {
285  log_error("python_context", "{}", res.get_error().get());
286  return std::nullopt;
287  }
288  },
289  py::arg("base") = 10,
290  R"(
291  Render the state transition graph as human-readable text, without truncating anything.
292 
293  Starts with a legend that maps each bit of the state to the flip-flop holding it, each output to the nets that make it up, and every net variable appearing in a Boolean function to the net it stands for.
294  The legend is followed by one block per state holding its outputs and all of its outgoing transitions together with the full condition of each.
295 
296  :param int base: The base in which state and output values are printed, either 2 or 10. Defaults to 10.
297  :returns: The state transition graph as text on success, ``None`` otherwise.
298  :rtype: str or None
299  )");
300 
301  py_state_transition_graph.def(
302  "write_txt",
303  [](const solve_fsm::StateTransitionGraph& self, const std::filesystem::path& file_path, const u32 base) -> bool {
304  auto res = self.write_txt(file_path, base);
305  if (res.is_ok())
306  {
307  return true;
308  }
309  log_error("python_context", "{}", res.get_error().get());
310  return false;
311  },
312  py::arg("file_path"),
313  py::arg("base") = 10,
314  R"(
315  Write the state transition graph to a text file, without truncating anything.
316 
317  :param pathlib.Path file_path: The file path at which to store the text representation.
318  :param int base: The base in which state and output values are printed, either 2 or 10. Defaults to 10.
319  :returns: ``True`` on success, ``False`` otherwise.
320  :rtype: bool
321  )");
322 
323  m.def(
324  "solve_fsm",
325  [](const solve_fsm::Configuration& config) -> std::optional<solve_fsm::StateTransitionGraph> {
326  auto res = solve_fsm::solve_fsm(config);
327  if (res.is_ok())
328  {
329  return res.get();
330  }
331  else
332  {
333  log_error("python_context", "{}", res.get_error().get());
334  return std::nullopt;
335  }
336  },
337  py::arg("config"),
338  R"(
339  Recover the state transition graph of an FSM from the netlist that implements it.
340 
341  Explores the states that are reachable from the initial state and determines, for each of them, which successor states it can reach and under which condition.
342  If outputs are configured, the value of each output in each state is computed as well.
343 
344  No file is written. Use ``StateTransitionGraph.generate_dot_graph`` on the result to render the graph.
345 
346  :param solve_fsm.Configuration config: The configuration of the FSM solver run.
347  :returns: The state transition graph of the FSM on success, ``None`` otherwise.
348  :rtype: solve_fsm.StateTransitionGraph or None
349  )");
350 
351 
352 #ifndef PYBIND11_MODULE
353  return m.ptr();
354 #endif // PYBIND11_MODULE
355  }
356 } // namespace hal
std::string get_name() const override
Get the name of the plugin.
std::string get_description() const override
Get a short description of the plugin.
std::string get_version() const override
Get the version of the plugin.
uint32_t u32
Definition: defines.h:41
#define log_error(channel,...)
Definition: log.h:78
const Module * module(const Gate *g, const NodeBoxes &boxes)
Result< StateTransitionGraph > solve_fsm(const Configuration &config)
Recover the state transition graph of an FSM from the netlist that implements it.
Definition: solve_fsm.cpp:622
Definition: defines.h:45
PYBIND11_PLUGIN(hal_py)
This file contains the function to recover the state transition graph of a finite state machine.
The configuration of a run of the FSM solver.
Definition: configuration.h:59
Configuration & with_brute_force(const bool brute_force=true)
Set whether to enumerate all states instead of using an SMT solver.
Configuration & with_transition_logic(const std::vector< Gate * > &transition_logic)
Set the combinational gates that compute the next state of the FSM.
Configuration & with_timeout(const u32 timeout)
Set the timeout for the underlying SMT solver.
u32 timeout
The timeout for the underlying SMT solver in milliseconds. Defaults to 600000 ms.
std::vector< Gate * > state_register
The flip-flops that make up the state register of the FSM.
Definition: configuration.h:78
Configuration & with_outputs(const std::vector< std::pair< std::string, std::vector< Net * >>> &outputs)
Set the outputs of the FSM that the solver should evaluate in each state.
Configuration & with_state_register(const std::vector< Gate * > &state_register)
Set the flip-flops that make up the state register of the FSM.
Netlist * netlist
The netlist that implements the FSM.
Definition: configuration.h:70
std::vector< std::pair< std::string, std::vector< Net * > > > outputs
The outputs of the FSM, each given as a name and the nets that make up that output.
Definition: configuration.h:93
Configuration & with_initial_state(const std::map< Gate *, bool > &initial_state)
Set the initial value of each flip-flop of the state register.
bool brute_force
Enumerate all states instead of using an SMT solver. Defaults to false.
std::map< Gate *, bool > initial_state
The initial value of each flip-flop of the state register.
std::vector< Gate * > transition_logic
The combinational gates that compute the next state of the FSM.
Definition: configuration.h:85
The state transition graph of an FSM, i.e., the behavior that its netlist implements.
std::vector< std::pair< std::string, std::vector< Net * > > > output_nets
The outputs of the FSM, each given as a name and the nets that make up that output.
std::map< u64, std::map< u64, BooleanFunction > > transitions
A map from each state to its successor states, together with the condition under which the respective...
u32 get_state_size() const
Get the number of flip-flops that make up the state register, i.e., the bit-size of a state.
std::vector< Gate * > state_register
The flip-flops that make up the state register, in the order that determines the encoding of a state.
Netlist * netlist
The netlist that implements the FSM.
std::map< u64, std::vector< std::pair< std::string, BooleanFunction > > > outputs
A map from each state to the value of every output of the FSM in that state.