HAL  v4.5.0-254-g8f9f6a423
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 
3 #include "pybind11/operators.h"
4 #include "pybind11/pybind11.h"
5 #include "pybind11/stl.h"
6 #include "pybind11/stl_bind.h"
9 
10 namespace py = pybind11;
11 
12 namespace hal
13 {
14 
15  // the name in PYBIND11_MODULE/PYBIND11_PLUGIN *MUST* match the filename of the output library (without extension),
16  // otherwise you will get "ImportError: dynamic module does not define module export function" when importing the module
17 
18 #ifdef PYBIND11_MODULE
19  PYBIND11_MODULE(xilinx_toolbox, m)
20  {
21  m.doc() = "A collection of functions specifically designed to operate on Xilinx FPGA netlists.";
22 #else
23  PYBIND11_PLUGIN(xilinx_toolbox)
24  {
25  py::module m("xilinx_toolbox", "A collection of functions specifically designed to operate on Xilinx FPGA netlists.");
26 #endif // ifdef PYBIND11_MODULE
27 
28  py::class_<XilinxToolboxPlugin, RawPtrWrapper<XilinxToolboxPlugin>, BasePluginInterface> py_xilinx_toolbox_plugin(
29  m, "XilinxToolboxPlugin", R"(This class provides an interface to integrate the Xilinx toolbox as a plugin within the HAL framework.)");
30 
31  py_xilinx_toolbox_plugin.def_property_readonly("name", &XilinxToolboxPlugin::get_name, R"(
32  The name of the plugin.
33 
34  :type: str
35  )");
36 
37  py_xilinx_toolbox_plugin.def("get_name", &XilinxToolboxPlugin::get_name, R"(
38  Get the name of the plugin.
39 
40  :returns: The name of the plugin.
41  :rtype: str
42  )");
43 
44  py_xilinx_toolbox_plugin.def_property_readonly("version", &XilinxToolboxPlugin::get_version, R"(
45  The version of the plugin.
46 
47  :type: str
48  )");
49 
50  py_xilinx_toolbox_plugin.def("get_version", &XilinxToolboxPlugin::get_version, R"(
51  Get the version of the plugin.
52 
53  :returns: The version of the plugin.
54  :rtype: str
55  )");
56 
57  py_xilinx_toolbox_plugin.def_property_readonly("description", &XilinxToolboxPlugin::get_description, R"(
58  The description of the plugin.
59 
60  :type: str
61  )");
62 
63  py_xilinx_toolbox_plugin.def("get_description", &XilinxToolboxPlugin::get_description, R"(
64  Get the description of the plugin.
65 
66  :returns: The description of the plugin.
67  :rtype: str
68  )");
69 
70  py_xilinx_toolbox_plugin.def_property_readonly("dependencies", &XilinxToolboxPlugin::get_dependencies, R"(
71  A set of plugin names that this plugin depends on.
72 
73  :type: set[str]
74  )");
75 
76  py_xilinx_toolbox_plugin.def("get_dependencies", &XilinxToolboxPlugin::get_dependencies, R"(
77  Get a set of plugin names that this plugin depends on.
78 
79  :returns: A set of plugin names that this plugin depends on.
80  :rtype: set[str]
81  )");
82 
83  m.def(
84  "split_lut",
85  [](Gate* g, bool create_module) -> bool {
86  auto res = xilinx_toolbox::split_lut(g, create_module);
87  if (res.is_ok())
88  {
89  return true;
90  }
91  log_error("python_context", "{}", res.get_error().get());
92  return false;
93  },
94  py::arg("g"),
95  py::arg("create_module") = false,
96  R"(
97  Split a single ``LUT6_2`` gate into up to two separate LUT gates.
98 
99  Creates replacement gates depending on which outputs are connected:
100 
101  - ``O6`` → ``LUT6`` using all 64 bits of the INIT string and all 6 inputs.
102  - ``O5`` → ``LUT5`` using bits [0, 31] of the INIT string and inputs I0-I4 (I5 is excluded).
103 
104  The original ``LUT6_2`` gate is always deleted. The original INIT string is stored on each replacement gate under ``xilinx_preprocessing_information/original_init``.
105 
106  :param hal_py.Gate g: The ``LUT6_2`` gate to split.
107  :param bool create_module: If ``True``, a new module named after the original gate is created as a child of its parent module and all replacement gates are placed into it. If ``False`` (default), replacement gates inherit the parent module of the original gate.
108  :returns: ``True`` on success, ``False`` otherwise.
109  :rtype: bool
110  )");
111 
112  m.def(
113  "split_luts",
114  [](const std::vector<Gate*>& gates, bool create_module) -> u32 {
115  return xilinx_toolbox::split_luts(gates, create_module).get();
116  },
117  py::arg("gates"),
118  py::arg("create_module") = false,
119  R"(
120  Split a list of ``LUT6_2`` gates into separate LUT gates.
121 
122  Calls :func:`split_lut` for each gate in ``gates``. Gates that cannot be split are skipped
123  with a warning; processing always continues with the remaining gates.
124 
125  :param list[hal_py.Gate] gates: The ``LUT6_2`` gates to split.
126  :param bool create_module: If ``True``, each split gate's replacements are placed into a new module named after the original gate. If ``False`` (default), replacements inherit the parent module of the original gate.
127  :returns: The number of successfully split gates.
128  :rtype: int
129  )");
130 
131  m.def(
132  "split_luts",
133  [](Netlist* nl, const std::vector<Gate*>& gates, bool create_module) -> std::optional<u32> {
134  auto res = xilinx_toolbox::split_luts(nl, gates, create_module);
135  if (res.is_ok())
136  {
137  return res.get();
138  }
139  log_error("python_context", "{}", res.get_error().get());
140  return std::nullopt;
141  },
142  py::arg("nl"),
143  py::arg("gates") = std::vector<Gate*>(),
144  py::arg("create_module") = false,
145  R"(
146  Split the ``LUT6_2`` gates of the netlist, or of the given gates, into separate LUT gates.
147 
148  Finds all gates of type ``LUT6_2`` within ``gates`` (or within the netlist if ``gates`` is empty) and calls :func:`split_luts` on them.
149 
150  :param hal_py.Netlist nl: The netlist to operate on.
151  :param list[hal_py.Gate] gates: The gates to consider. Defaults to an empty list, in which case all gates of the netlist are considered.
152  :param bool create_module: If ``True``, each split gate's replacements are placed into a new module named after the original gate. If ``False`` (default), replacements inherit the parent module of the original gate.
153  :returns: The number of successfully split ``LUT6_2`` gates on success, ``None`` otherwise.
154  :rtype: int or None
155  )");
156 
157  m.def(
158  "split_shift_register",
159  [](Gate* g, bool create_module) -> bool {
160  auto res = xilinx_toolbox::split_shift_register(g, create_module);
161  if (res.is_ok())
162  return true;
163  log_error("python_context", "{}", res.get_error().get());
164  return false;
165  },
166  py::arg("g"),
167  py::arg("create_module") = false,
168  R"(
169  Split a single shift register primitive into an equivalent chain of ``FDCE`` flip-flops.
170 
171  Supported gate types: ``SRL16``, ``SRL16E``, ``SRLC16E``, ``SRLC32E``.
172 
173  Every address pin must be connected and driven by a constant (GND/VCC) net; an error is
174  returned otherwise. The number of flip-flops created equals ``select_value + 1``, where
175  ``select_value`` is the binary value encoded by the address pins (A0 = bit 0). When the
176  cascade output (``Q15`` for ``SRLC16E``, ``Q31`` for ``SRLC32E``) has downstream consumers,
177  all stages up to the maximum depth (15 or 31) are materialised so that the last flip-flop
178  correctly drives the cascade net.
179 
180  The original gate is always deleted on success.
181 
182  :param hal_py.Gate g: The shift register gate to split.
183  :param bool create_module: If ``True``, a new module named after the original gate is created as a child of its parent module and all replacement flip-flops are placed into it. If ``False`` (default), replacement flip-flops inherit the parent module of the original gate.
184  :returns: ``True`` on success, ``False`` if ``g`` is not a supported type, if any address
185  pin is unconnected or not driven by a constant net, or if the gate cannot be deleted.
186  :rtype: bool
187  )");
188 
189  m.def(
190  "split_shift_registers",
191  [](const std::vector<Gate*>& gates, bool create_module) -> std::optional<u32> {
192  auto res = xilinx_toolbox::split_shift_registers(gates, create_module);
193  if (res.is_ok())
194  return res.get();
195  log_error("python_context", "{}", res.get_error().get());
196  return std::nullopt;
197  },
198  py::arg("gates"),
199  py::arg("create_module") = false,
200  R"(
201  Split a list of shift register primitives into equivalent ``FDCE`` flip-flop chains.
202 
203  Supported gate types: ``SRL16``, ``SRL16E``, ``SRLC16E``, ``SRLC32E``.
204 
205  Calls :func:`split_shift_register` for each gate in ``gates``. Gates that cannot be split
206  (e.g. because an address pin is not driven by a constant net) are skipped with a warning;
207  processing always continues with the remaining gates.
208 
209  :param list[hal_py.Gate] gates: The shift register gates to split.
210  :param bool create_module: If ``True``, each split gate's replacements are placed into a new module named after the original gate. If ``False`` (default), replacements inherit the parent module of the original gate.
211  :returns: The number of successfully split gates.
212  :rtype: int
213  )");
214 
215  m.def(
216  "split_shift_registers",
217  [](Netlist* nl, const std::vector<Gate*>& gates, bool create_module) -> std::optional<u32> {
218  auto res = xilinx_toolbox::split_shift_registers(nl, gates, create_module);
219  if (res.is_ok())
220  return res.get();
221  log_error("python_context", "{}", res.get_error().get());
222  return std::nullopt;
223  },
224  py::arg("nl"),
225  py::arg("gates") = std::vector<Gate*>(),
226  py::arg("create_module") = false,
227  R"(
228  Split the shift register primitives of the netlist, or of the given gates, into equivalent ``FDCE`` flip-flop chains.
229 
230  Supported gate types: ``SRL16``, ``SRL16E``, ``SRLC16E``, ``SRLC32E``.
231 
232  Finds all gates of one of the supported types within ``gates`` (or within the netlist if ``gates`` is empty)
233  and calls :func:`split_shift_registers` on them.
234 
235  :param hal_py.Netlist nl: The netlist to operate on.
236  :param list[hal_py.Gate] gates: The gates to consider. Defaults to an empty list, in which case all gates of the netlist are considered.
237  :param bool create_module: If ``True``, each split gate's replacements are placed into a new module named after the original gate. If ``False`` (default), replacements inherit the parent module of the original gate.
238  :returns: The number of successfully split shift register gates.
239  :rtype: int
240  )");
241 
242  m.def(
243  "remove_no_load_wires",
244  [](Netlist* nl, const std::vector<Net*>& nets) -> std::optional<u32> {
245  auto res = xilinx_toolbox::remove_no_load_wires(nl, nets);
246  if (res.is_ok())
247  {
248  return res.get();
249  }
250  else
251  {
252  log_error("python_context", "{}", res.get_error().get());
253  return std::nullopt;
254  }
255  },
256  py::arg("nl"),
257  py::arg("nets") = std::vector<Net*>(),
258  R"(
259  Remove the "no load wire" nets that Vivado writes for unused output pins.
260  Vivado names such nets ``NLW_<instance>_<pin>_UNCONNECTED`` and only connects them to the output pin they stand in for.
261  A net is removed if its name starts with ``NLW_`` (or has ``NLW_`` right after a ``/`` in a flattened path) and ends with ``_UNCONNECTED``, optionally followed by a bit index such as ``(2)`` or ``[2]``, it has no destinations, and it is not a global output.
262  The output pins that drove the removed nets are left unconnected.
263 
264  :param hal_py.Netlist nl: The netlist to operate on.
265  :param list[hal_py.Net] nets: The nets to consider. Defaults to an empty list, in which case all nets of the netlist are considered.
266  :returns: The number of removed nets on success, ``None`` otherwise.
267  :rtype: int or None
268  )");
269 
270  m.def(
271  "parse_xdc_file",
272  [](Netlist* nl, const std::filesystem::path& xdc_file) -> bool {
273  auto res = xilinx_toolbox::parse_xdc_file(nl, xdc_file);
274  if (res.is_ok())
275  {
276  return true;
277  }
278  else
279  {
280  log_error("python_context", "{}", res.get_error().get());
281  return false;
282  }
283  },
284  py::arg("nl"),
285  py::arg("xdc_file"),
286  R"(
287  Parse an ``.xdc`` file and extract the position LOC and BEL data of each gate.
288  Translates the coordinates extracted from the ``.xdc`` file into integer values.
289 
290  :param hal_py.Netlist nl: The netlist to operate on.
291  :param path xdc_file: The path to the ``.xdc`` file.
292  :returns: ``True`` on success, ``False`` otherwise.
293  :rtype: bool
294  )");
295 
296 #ifndef PYBIND11_MODULE
297  return m.ptr();
298 #endif // PYBIND11_MODULE
299  }
300 } // namespace hal
Definition: gate.h:59
std::string get_name() const override
Get the name of the plugin.
std::string get_version() const override
Get the version of the plugin.
std::set< std::string > get_dependencies() const override
Get the plugin dependencies.
std::string get_description() const override
Get a short description 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< std::monostate > split_shift_register(Gate *g, bool create_module=false)
Split a single shift register primitive into an equivalent chain of FDCE flip-flops.
Result< u32 > remove_no_load_wires(Netlist *nl, const std::vector< Net * > &nets={})
Remove the "no load wire" nets that Vivado writes for unused output pins.
Result< u32 > split_shift_registers(const std::vector< Gate * > &gates, bool create_module=false)
Split a set of shift register primitives into equivalent FDCE flip-flop chains.
Result< std::monostate > parse_xdc_file(Netlist *nl, const std::filesystem::path &xdc_file)
Parse an .xdc file and extract the position LOC and BEL data of each gate.
Definition: xdc_parser.cpp:220
Result< std::monostate > split_lut(Gate *g, bool create_module=false)
Split a single LUT6_2 gate into up to two separate LUT gates.
Result< u32 > split_luts(const std::vector< Gate * > &gates, bool create_module=false)
Split a set of LUT6_2 gates into separate LUT gates.
Definition: defines.h:45
PYBIND11_PLUGIN(hal_py)
This file contains all functions related to the HAL plugin API.
This file contains functions specifically designed to preprocess Xilinx FPGA netlists.