From 93fae3c6068db5c2ca23d31b2864ebdd3167a3b3 Mon Sep 17 00:00:00 2001 From: Mohamed Gaber Date: Tue, 30 Sep 2025 10:30:11 +0300 Subject: [PATCH] docs: write small guide for using pyosys --- .editorconfig | 4 + docs/.gitignore | 1 + docs/source/code_examples/pyosys/pass.py | 37 ++++ .../code_examples/pyosys/simple_database.py | 51 +++++ docs/source/using_yosys/index.rst | 1 + docs/source/using_yosys/pyosys.rst | 197 ++++++++++++++++++ examples/python-api/pass.py | 2 +- 7 files changed, 292 insertions(+), 1 deletion(-) create mode 100644 docs/source/code_examples/pyosys/pass.py create mode 100644 docs/source/code_examples/pyosys/simple_database.py create mode 100644 docs/source/using_yosys/pyosys.rst diff --git a/.editorconfig b/.editorconfig index 572b73bd2..bbe9c3107 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,6 +11,10 @@ indent_style = space indent_size = 2 trim_trailing_whitespace = false +[*.rst] +indent_style = space +indent_size = 3 + [*.yml] indent_style = space indent_size = 2 diff --git a/docs/.gitignore b/docs/.gitignore index 09bb59048..30a903f9a 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -6,3 +6,4 @@ /source/_images/**/*.svg /source/_images/**/*.dot /source/_images/code_examples +/venv diff --git a/docs/source/code_examples/pyosys/pass.py b/docs/source/code_examples/pyosys/pass.py new file mode 100644 index 000000000..2108b48ab --- /dev/null +++ b/docs/source/code_examples/pyosys/pass.py @@ -0,0 +1,37 @@ +from pyosys import libyosys as ys + +class AllEnablePass(ys.Pass): + def __init__(self): + super().__init__( + "all_enable", + "makes all _DFF_P_ registers require an enable signal" + ) + + def execute(self, args, design): + ys.log_header(design, "Adding enable signals\n") + ys.log_push() + top_module = design.top_module() + + if "\\enable" not in top_module.wires_: + enable_line = top_module.addWire("\\enable") + enable_line.port_input = True + top_module.fixup_ports() + + for cell in top_module.cells_.values(): + if cell.type != "$_DFF_P_": + continue + cell.type = "$_DFFE_PP_" + cell.setPort("\\E", ys.SigSpec(enable_line)) + ys.log_pop() + +p = AllEnablePass() # register the pass + +# using the pass + +design = ys.Design() +ys.run_pass("read_verilog tests/simple/fiedler-cooley.v", design) +ys.run_pass("hierarchy -check -auto-top", design) +ys.run_pass("synth", design) +ys.run_pass("all_enable", design) +ys.run_pass("write_verilog out.v", design) +ys.run_pass("synth_ice40 -json out.json", design) diff --git a/docs/source/code_examples/pyosys/simple_database.py b/docs/source/code_examples/pyosys/simple_database.py new file mode 100644 index 000000000..4cfe6b586 --- /dev/null +++ b/docs/source/code_examples/pyosys/simple_database.py @@ -0,0 +1,51 @@ +from pyosys import libyosys as ys + +# loading design +design = ys.Design() + +ys.run_pass("read_verilog tests/simple/fiedler-cooley.v", design) +ys.run_pass("hierarchy -check -auto-top", design) + +# top module inspection +top_module = design.top_module() + +for id, wire in top_module.wires_.items(): + if not wire.port_input and not wire.port_output: + continue + description = "input" if wire.port_input else "output" + description += " " + wire.name.str() + if wire.width != 1: + frm = wire.start_offset + to = wire.start_offset + wire.width + if wire.upto: + to, frm = frm, to + description += f" [{to}:{frm}]" + print(description) + +# synth + +ys.run_pass("synth", design) + +# adding the enable line + +enable_line = top_module.addWire("\\enable") +enable_line.port_input = True +top_module.fixup_ports() + +# hooking the enable line to the internal dff cells + +for cell in top_module.cells_.values(): + if cell.type != "$_DFF_P_": + continue + cell.type = "$_DFFE_PP_" + cell.setPort("\\E", ys.SigSpec(enable_line)) + +# run check + +top_module.check() +ys.run_pass("stat", design) + +# write outputs + +ys.run_pass("write_verilog out.v", design) +ys.run_pass("synth_ice40 -json out.json", design) diff --git a/docs/source/using_yosys/index.rst b/docs/source/using_yosys/index.rst index b243d431e..93dd3629b 100644 --- a/docs/source/using_yosys/index.rst +++ b/docs/source/using_yosys/index.rst @@ -17,3 +17,4 @@ ways Yosys can interact with designs for a deeper investigation. more_scripting/index bugpoint verilog + pyosys diff --git a/docs/source/using_yosys/pyosys.rst b/docs/source/using_yosys/pyosys.rst new file mode 100644 index 000000000..7e3b0f77d --- /dev/null +++ b/docs/source/using_yosys/pyosys.rst @@ -0,0 +1,197 @@ +Scripting with Pyosys +===================== + +Pyosys is a limited subset of the Yosys C++ API (aka "libyosys") made available +using the Python programming language. + +It offers access both to writing Yosys scripts like ``.ys`` and ``.tcl`` files +with the amenities of the Python programming language (functions, flow control, +etc), but also allows some access to internal data structures at the same time +unlike those two platforms, allowing you to also implement complex functionality +that is would otherwise not possible without writing custom passes using C++. + + +Getting Pyosys +-------------- + +Pyosys supports Python 3.8.1 or higher. You can access Pyosys using one of two +methods: + +1. Compiling Yosys with the Makefile flag ``ENABLE_PYOSYS=1`` + + This adds the flag ``-y`` to the Yosys binary, which allows you to execute + Python scripts using an interpreter embedded in Yosys itself: + + ``yosys -y ./my_pyosys_script.py`` + +2. Installing the Pyosys wheels + + On macOS and GNU/Linux (specifically, not musllinux,) you can install + pre-built wheels of Yosys using ``pip`` as follows: + + ``python3 -m pip install pyosys`` + + Which then allows you to run your scripts as follows: + + ``python3 ./my_pyosys_script.py`` + + +Scripting and Database Inspection +--------------------------------- + +To start with, you have to import libyosys as follows: + +.. code-block:: python + + from pyosys import libyosys + + +As a reminder, Python allows you to alias imported modules and objects, so +this import may be preferable for terseness: + +.. code-block:: python + + from pyosys import libyosys as ys + + +Now, scripting is actually quite similar to ``.ys`` and ``.tcl`` script in that +you can provide mostly text commands. Albeit, you can construct your scripts +to use Python's amenities including flow controls, loops, and functions: + +.. code-block:: python + + do_flatten = True + + ys.run_pass("read_verilog tests/simple/fiedler-cooley.v") + ys.run_pass("hierarchy -check -auto-top") + if do_flatten: + ys.run_pass("flatten") + +…but this does not provide anything that Tcl scripts do not provide you with. +The real power of using Pyosys comes from the fact you can manually instantiate, +manage, and interact with the design database. + +As an example, here is the same script with a manually instantiated design. + +.. literalinclude:: /code_examples/pyosys/simple_database.py + :start-after: loading design + :end-before: top module inspection + :language: python + +What's new here is that you can manually inspect the design's database. This +gives you access to huge chunk of the design database API as in declared in the +``kernel/rtlil.h`` header. + +For example, here's how to list the input and output ports of the top module +of your design: + +.. literalinclude:: /code_examples/pyosys/simple_database.py + :start-after: top module inspection + :end-before: # synth + :language: python + +.. tip:: + + C++ data structures in Yosys are bridged to Python such that they have a + pretty similar API to Python objects, for example: + + - ``std::vector`` supports the same methods as iterables in Python. + - ``std::set`` and hashlib ``pool`` support the same methods as ``set``\s in + Python. + - ``dict`` supports the same methods as ``dict``\s in Python, albeit it is + unordered, and modifications may cause a complete reordering of the + dictionary. + + For most operations, the Python equivalents are also supported as arguments + where they will automatically be cast to the right type, so you do not have + to manually instantiate the right underlying C++ object(s) yourself. + +Modifying the Database +---------------------- + +.. warning:: + + Any modifications to the database may invalidate previous references held + by Python, just as if you were writing C++. Pyosys does not currently attempt + to keep deleted objects alive if a reference is held by Python. + +You are not restricted to inspecting the database either: you have the ability +to modify it, and introduce new elements and/or changes to your design. + +As a demonstrative example, let's assume we want to add an enable line to all +flip-flops in our fiedler-cooley design. + +First of all, we will run :yoscrypt:`synth` to convert all of the logic to Yosys's +internal cell structure (see :ref:`sec:celllib_gates`): + +.. literalinclude:: /code_examples/pyosys/simple_database.py + :start-after: # synth + :end-before: adding the enable line + :language: python + +Next, we need to add the new port. The method for this is ``Module::addWire``\. + +.. tip:: + + IdString is Yosys's internal representation of strings used as identifiers + within Verilog designs. They are efficient as only integers are stored and + passed around, but they can be translated to and from normal strings at will. + + Pyosys will automatically cast Python strings to IdStrings for you, but the + rules around IdStrings apply, namely that *broadly*: + + - Identifiers for internal cells must start with ``$``\. + - All other identifiers must start with ``\``\. + +.. literalinclude:: /code_examples/pyosys/simple_database.py + :start-after: adding the enable line + :end-before: hooking the enable line + :language: python + +Notice how we modified the wire then called a method to make Yosys re-process +the ports. + +Next, we can iterate over all constituent cells, and if they are of the type +``$_DFF_P_``, we do two things: + +1. Change their type to ``$_DFFE_PP_`` to enable hooking up an enable signal. +2. Hooking up the enable signal. + +.. literalinclude:: /code_examples/pyosys/simple_database.py + :start-after: hooking the enable line + :end-before: run check + :language: python + +To verify that you did everything correctly, it is prudent to call ``.check()`` +on the module you're manipulating as follows: + +.. literalinclude:: /code_examples/pyosys/simple_database.py + :start-after: run check + :end-before: write output + :language: python + +And then finally, write your outputs. Here, I choose an intermediate Verilog +file and :yoscrypt:`synth_ice40` to map it to the iCE40 architecture. + +.. literalinclude:: /code_examples/pyosys/simple_database.py + :start-after: write output + :language: python + +And voila, you will note that in the intermediate output, all ``always @`` +statements have an ``if (enable)``\. + +Encapsulating as Passes +----------------------- + +Just like when writing C++, you can encapsulate behavior in terms of "passes", +which are the commands you access using ``run_pass``\. This adds it to a global +registry of commands that you can use using ``run_pass``. + +.. literalinclude:: /code_examples/pyosys/pass.py + :language: python + +In general, abstract classes and virtual methods are not really supported by +Pyosys due to their complexity, but there are two exceptions which are: + +- ``Pass`` in ``kernel/register.h`` +- ``Monitor`` in ``kernel/rtlil.h`` diff --git a/examples/python-api/pass.py b/examples/python-api/pass.py index d87e09078..d5e5868b0 100755 --- a/examples/python-api/pass.py +++ b/examples/python-api/pass.py @@ -29,7 +29,7 @@ class CellStatsPass(ys.Pass): plt.xticks(range(len(cell_stats)), list(cell_stats.keys())) plt.show() - def py_clear_flags(self): + def clear_flags(self): ys.log("Clear Flags - CellStatsPass\n") p = CellStatsPass() # register