mirror of
https://github.com/YosysHQ/yosys
synced 2025-10-08 17:01:57 +00:00
Merge pull request #5370 from donn/pyosys_pybind11
pyosys: rewrite using pybind11
This commit is contained in:
commit
ba1a347d59
38 changed files with 2382 additions and 2722 deletions
|
@ -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
|
||||
|
|
14
.github/workflows/wheels.yml
vendored
14
.github/workflows/wheels.yml
vendored
|
@ -55,11 +55,6 @@ jobs:
|
|||
submodules: true
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@v5
|
||||
- name: Get Boost Source
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p boost
|
||||
curl -L https://github.com/boostorg/boost/releases/download/boost-1.86.0/boost-1.86.0-b2-nodocs.tar.gz | tar --strip-components=1 -xzC boost
|
||||
- name: Get FFI
|
||||
shell: bash
|
||||
run: |
|
||||
|
@ -103,21 +98,16 @@ jobs:
|
|||
CIBW_BEFORE_ALL: bash ./.github/workflows/wheels/cibw_before_all.sh
|
||||
CIBW_ENVIRONMENT: >
|
||||
OPTFLAGS=-O3
|
||||
CXXFLAGS=-I./boost/pfx/include
|
||||
LINKFLAGS=-L./boost/pfx/lib
|
||||
PKG_CONFIG_PATH=./ffi/pfx/lib/pkgconfig
|
||||
makeFlags='BOOST_PYTHON_LIB=./boost/pfx/lib/libboost_python*.a'
|
||||
PATH="$PWD/bison/src:$PATH"
|
||||
CIBW_ENVIRONMENT_MACOS: >
|
||||
OPTFLAGS=-O3
|
||||
CXXFLAGS=-I./boost/pfx/include
|
||||
LINKFLAGS=-L./boost/pfx/lib
|
||||
PKG_CONFIG_PATH=./ffi/pfx/lib/pkgconfig
|
||||
MACOSX_DEPLOYMENT_TARGET=11
|
||||
makeFlags='BOOST_PYTHON_LIB=./boost/pfx/lib/libboost_python*.a CONFIG=clang'
|
||||
makeFlags='CONFIG=clang'
|
||||
PATH="$PWD/bison/src:$PATH"
|
||||
CIBW_BEFORE_BUILD: bash ./.github/workflows/wheels/cibw_before_build.sh
|
||||
CIBW_TEST_COMMAND: python3 {project}/tests/arch/ecp5/add_sub.py
|
||||
CIBW_TEST_COMMAND: python3 {project}/tests/pyosys/run_tests.py
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: python-wheels-${{ matrix.os.runner }}
|
||||
|
|
25
.github/workflows/wheels/cibw_before_build.sh
vendored
25
.github/workflows/wheels/cibw_before_build.sh
vendored
|
@ -1,8 +1,8 @@
|
|||
set -e
|
||||
set -x
|
||||
|
||||
# Don't use objects from previous compiles on Windows/macOS
|
||||
make clean
|
||||
# Don't use Python objects from previous compiles
|
||||
make clean-py
|
||||
|
||||
# DEBUG: show python3 and python3-config outputs
|
||||
if [ "$(uname)" != "Linux" ]; then
|
||||
|
@ -11,24 +11,3 @@ if [ "$(uname)" != "Linux" ]; then
|
|||
fi
|
||||
python3 --version
|
||||
python3-config --includes
|
||||
|
||||
# Build boost
|
||||
cd ./boost
|
||||
## Delete the artefacts from previous builds (if any)
|
||||
rm -rf ./pfx
|
||||
## Bootstrap bjam
|
||||
./bootstrap.sh --prefix=./pfx
|
||||
## Build Boost against current version of Python, only for
|
||||
## static linkage (Boost is statically linked because system boost packages
|
||||
## wildly vary in versions, including the libboost_python3 version)
|
||||
./b2\
|
||||
-j$(getconf _NPROCESSORS_ONLN 2>/dev/null || sysctl -n hw.ncpu)\
|
||||
--prefix=./pfx\
|
||||
--with-filesystem\
|
||||
--with-system\
|
||||
--with-python\
|
||||
cxxflags="$(python3-config --includes) -std=c++17 -fPIC"\
|
||||
cflags="$(python3-config --includes) -fPIC"\
|
||||
link=static\
|
||||
variant=release\
|
||||
install
|
||||
|
|
|
@ -38,7 +38,8 @@ techlibs/gowin/ @pepijndevos
|
|||
techlibs/gatemate/ @pu-cc
|
||||
|
||||
# pyosys
|
||||
misc/*.py @btut
|
||||
pyosys/* @donn
|
||||
setup.py @donn
|
||||
|
||||
backends/firrtl @ucbjrl @azidar
|
||||
|
||||
|
|
53
Makefile
53
Makefile
|
@ -102,7 +102,7 @@ all: top-all
|
|||
YOSYS_SRC := $(dir $(firstword $(MAKEFILE_LIST)))
|
||||
VPATH := $(YOSYS_SRC)
|
||||
|
||||
CXXSTD ?= c++17
|
||||
export CXXSTD ?= c++17
|
||||
CXXFLAGS := $(CXXFLAGS) -Wall -Wextra -ggdb -I. -I"$(YOSYS_SRC)" -MD -MP -D_YOSYS_ -fPIC -I$(PREFIX)/include
|
||||
LIBS := $(LIBS) -lstdc++ -lm
|
||||
PLUGIN_LINKFLAGS :=
|
||||
|
@ -133,10 +133,6 @@ LINKFLAGS += -rdynamic
|
|||
ifneq ($(shell :; command -v brew),)
|
||||
BREW_PREFIX := $(shell brew --prefix)/opt
|
||||
$(info $$BREW_PREFIX is [${BREW_PREFIX}])
|
||||
ifeq ($(ENABLE_PYOSYS),1)
|
||||
CXXFLAGS += -I$(BREW_PREFIX)/boost/include
|
||||
LINKFLAGS += -L$(BREW_PREFIX)/boost/lib -L$(BREW_PREFIX)/boost-python3/lib
|
||||
endif
|
||||
CXXFLAGS += -I$(BREW_PREFIX)/readline/include -I$(BREW_PREFIX)/flex/include
|
||||
LINKFLAGS += -L$(BREW_PREFIX)/readline/lib -L$(BREW_PREFIX)/flex/lib
|
||||
PKG_CONFIG_PATH := $(BREW_PREFIX)/libffi/lib/pkgconfig:$(PKG_CONFIG_PATH)
|
||||
|
@ -348,31 +344,24 @@ ifeq ($(ENABLE_LIBYOSYS),1)
|
|||
TARGETS += libyosys.so
|
||||
endif
|
||||
|
||||
PY_WRAPPER_FILE = pyosys/wrappers
|
||||
|
||||
# running make clean on just those and then recompiling saves a lot of
|
||||
# time when running cibuildwheel
|
||||
PYTHON_OBJECTS = pyosys/wrappers.o kernel/drivers.o kernel/yosys.o passes/cmds/plugin.o
|
||||
|
||||
ifeq ($(ENABLE_PYOSYS),1)
|
||||
# python-config --ldflags includes -l and -L, but LINKFLAGS is only -L
|
||||
LINKFLAGS += $(filter-out -l%,$(shell $(PYTHON_CONFIG) --ldflags))
|
||||
LIBS += $(shell $(PYTHON_CONFIG) --libs)
|
||||
EXE_LIBS += $(filter-out $(LIBS),$(shell $(PYTHON_CONFIG_FOR_EXE) --libs))
|
||||
CXXFLAGS += $(shell $(PYTHON_CONFIG) --includes) -DWITH_PYTHON
|
||||
PYBIND11_INCLUDE ?= $(shell $(PYTHON_EXECUTABLE) -m pybind11 --includes)
|
||||
CXXFLAGS += -I$(PYBIND11_INCLUDE) -DYOSYS_ENABLE_PYTHON
|
||||
CXXFLAGS += $(shell $(PYTHON_CONFIG) --includes) -DYOSYS_ENABLE_PYTHON
|
||||
|
||||
# Detect name of boost_python library. Some distros use boost_python-py<version>, other boost_python<version>, some only use the major version number, some a concatenation of major and minor version numbers
|
||||
CHECK_BOOST_PYTHON = (echo "int main(int argc, char ** argv) {return 0;}" | $(CXX) -xc -o /dev/null $(LINKFLAGS) $(EXE_LIBS) $(LIBS) -l$(1) - > /dev/null 2>&1 && echo "-l$(1)")
|
||||
BOOST_PYTHON_LIB ?= $(shell \
|
||||
$(call CHECK_BOOST_PYTHON,boost_python-py$(subst .,,$(PYTHON_VERSION))) || \
|
||||
$(call CHECK_BOOST_PYTHON,boost_python-py$(PYTHON_MAJOR_VERSION)) || \
|
||||
$(call CHECK_BOOST_PYTHON,boost_python$(subst .,,$(PYTHON_VERSION))) || \
|
||||
$(call CHECK_BOOST_PYTHON,boost_python$(PYTHON_MAJOR_VERSION)) \
|
||||
)
|
||||
|
||||
ifeq ($(BOOST_PYTHON_LIB),)
|
||||
$(error BOOST_PYTHON_LIB could not be detected. Please define manually)
|
||||
endif
|
||||
|
||||
LIBS += $(BOOST_PYTHON_LIB) -lboost_system -lboost_filesystem
|
||||
PY_WRAPPER_FILE = kernel/python_wrappers
|
||||
OBJS += $(PY_WRAPPER_FILE).o
|
||||
PY_GEN_SCRIPT= py_wrap_generator
|
||||
PY_WRAP_INCLUDES := $(shell $(PYTHON_EXECUTABLE) -c "from misc import $(PY_GEN_SCRIPT); $(PY_GEN_SCRIPT).print_includes()")
|
||||
PY_GEN_SCRIPT = pyosys/generator.py
|
||||
PY_WRAP_INCLUDES := $(shell $(PYTHON_EXECUTABLE) $(PY_GEN_SCRIPT) --print-includes)
|
||||
endif # ENABLE_PYOSYS
|
||||
|
||||
ifeq ($(ENABLE_READLINE),1)
|
||||
|
@ -786,9 +775,9 @@ endif
|
|||
$(P) cat $< | grep -E -v "#[ ]*(include|error)" | $(CXX) $(CXXFLAGS) -x c++ -o $@ -E -P -
|
||||
|
||||
ifeq ($(ENABLE_PYOSYS),1)
|
||||
$(PY_WRAPPER_FILE).cc: misc/$(PY_GEN_SCRIPT).py $(PY_WRAP_INCLUDES)
|
||||
$(PY_WRAPPER_FILE).cc: $(PY_GEN_SCRIPT) pyosys/wrappers_tpl.cc $(PY_WRAP_INCLUDES) pyosys/hashlib.h
|
||||
$(Q) mkdir -p $(dir $@)
|
||||
$(P) $(PYTHON_EXECUTABLE) -c "from misc import $(PY_GEN_SCRIPT); $(PY_GEN_SCRIPT).gen_wrappers(\"$(PY_WRAPPER_FILE).cc\")"
|
||||
$(P) $(PYTHON_EXECUTABLE) $(PY_GEN_SCRIPT) $(PY_WRAPPER_FILE).cc
|
||||
endif
|
||||
|
||||
%.o: %.cpp
|
||||
|
@ -1035,12 +1024,12 @@ ifeq ($(ENABLE_LIBYOSYS),1)
|
|||
if [ -n "$(STRIP)" ]; then $(INSTALL_SUDO) $(STRIP) -S $(DESTDIR)$(LIBDIR)/libyosys.so; fi
|
||||
ifeq ($(ENABLE_PYOSYS),1)
|
||||
$(INSTALL_SUDO) mkdir -p $(DESTDIR)$(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys
|
||||
$(INSTALL_SUDO) cp pyosys/__init__.py $(DESTDIR)$(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys/__init__.py
|
||||
$(INSTALL_SUDO) cp libyosys.so $(DESTDIR)$(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys/libyosys.so
|
||||
$(INSTALL_SUDO) cp -r share $(DESTDIR)$(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys
|
||||
ifeq ($(ENABLE_ABC),1)
|
||||
$(INSTALL_SUDO) cp yosys-abc $(DESTDIR)$(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys/yosys-abc
|
||||
endif
|
||||
$(INSTALL_SUDO) cp misc/__init__.py $(DESTDIR)$(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys/
|
||||
endif
|
||||
endif
|
||||
ifeq ($(ENABLE_PLUGINS),1)
|
||||
|
@ -1130,12 +1119,10 @@ DOC_TARGET ?= html
|
|||
docs: docs/prep
|
||||
$(Q) $(MAKE) -C docs $(DOC_TARGET)
|
||||
|
||||
clean:
|
||||
clean: clean-py
|
||||
rm -rf share
|
||||
rm -rf kernel/*.pyh
|
||||
rm -f $(OBJS) $(GENFILES) $(TARGETS) $(EXTRA_TARGETS) $(EXTRA_OBJS) $(PY_WRAP_INCLUDES) $(PY_WRAPPER_FILE).cc
|
||||
rm -f $(OBJS) $(GENFILES) $(TARGETS) $(EXTRA_TARGETS) $(EXTRA_OBJS) $(PY_WRAP_INCLUDES)
|
||||
rm -f kernel/version_*.o kernel/version_*.cc
|
||||
rm -f kernel/python_wrappers.o
|
||||
rm -f libs/*/*.d frontends/*/*.d passes/*/*.d backends/*/*.d kernel/*.d techlibs/*/*.d
|
||||
rm -rf tests/asicworld/*.out tests/asicworld/*.log
|
||||
rm -rf tests/hana/*.out tests/hana/*.log
|
||||
|
@ -1149,8 +1136,14 @@ clean:
|
|||
rm -f $(addsuffix /run-test.mk,$(MK_TEST_DIRS))
|
||||
-$(MAKE) -C docs clean
|
||||
rm -rf docs/util/__pycache__
|
||||
rm -f libyosys.so
|
||||
|
||||
clean-py:
|
||||
rm -f $(PY_WRAPPER_FILE).inc.cc $(PY_WRAPPER_FILE).cc
|
||||
rm -f $(PYTHON_OBJECTS)
|
||||
rm -f *.whl
|
||||
rm -f libyosys.so
|
||||
rm -rf kernel/*.pyh
|
||||
|
||||
clean-abc:
|
||||
$(MAKE) -C abc DEP= clean
|
||||
|
|
1
docs/.gitignore
vendored
1
docs/.gitignore
vendored
|
@ -6,3 +6,4 @@
|
|||
/source/_images/**/*.svg
|
||||
/source/_images/**/*.dot
|
||||
/source/_images/code_examples
|
||||
/venv
|
||||
|
|
37
docs/source/code_examples/pyosys/pass.py
Normal file
37
docs/source/code_examples/pyosys/pass.py
Normal file
|
@ -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)
|
51
docs/source/code_examples/pyosys/simple_database.py
Normal file
51
docs/source/code_examples/pyosys/simple_database.py
Normal file
|
@ -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)
|
|
@ -17,3 +17,4 @@ ways Yosys can interact with designs for a deeper investigation.
|
|||
more_scripting/index
|
||||
bugpoint
|
||||
verilog
|
||||
pyosys
|
||||
|
|
208
docs/source/using_yosys/pyosys.rst
Normal file
208
docs/source/using_yosys/pyosys.rst
Normal file
|
@ -0,0 +1,208 @@
|
|||
Scripting with Pyosys
|
||||
=====================
|
||||
|
||||
Pyosys is a limited subset of the Yosys C++ API (aka "libyosys") made available
|
||||
using the Python programming language.
|
||||
|
||||
Like ``.ys`` and ``.tcl`` scripts, Pyosys provides an interface to write Yosys
|
||||
scripts in the Python programming language, giving you the benefits of
|
||||
a type system, control flow, object-oriented programming, and more; especially
|
||||
that the other options lack a type system and control flow/OOP in Tcl is
|
||||
limited.
|
||||
|
||||
Though unlike these two, Pyosys goes a bit further, allowing you to use the
|
||||
Yosys API to implement advanced functionality that would otherwise require
|
||||
custom passes written in C++.
|
||||
|
||||
|
||||
Getting Pyosys
|
||||
--------------
|
||||
|
||||
Pyosys supports CPython 3.8 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 you can install pre-built wheels of Yosys using
|
||||
``pip``:
|
||||
|
||||
``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 like conditional execution, 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 strictly 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 a huge chunk of the design database API as 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 <https://docs.python.org/3/glossary.html#term-iterable>`_ in
|
||||
Python.
|
||||
- ``std::set`` and hashlib ``pool`` support the same methods as ``set``\s in
|
||||
Python. While ``set`` is ordered, ``pool`` is not and modifications may
|
||||
cause a complete reordering of the set.
|
||||
- ``dict`` supports the same methods as ``dict``\s in Python, albeit it is
|
||||
unordered, and modifications may cause a complete reordering of the
|
||||
dictionary.
|
||||
- ``idict`` uses a custom set of methods because it doesn't map very cleanly
|
||||
to an existing Python data structure. See ``pyosys/hashlib.h`` for more
|
||||
info.
|
||||
|
||||
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 after you're done with a set of
|
||||
changes:
|
||||
|
||||
.. 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 voilà, you will note that in the intermediate output, all ``always @``
|
||||
statements should have an ``if (enable)``\.
|
||||
|
||||
Encapsulating as Passes
|
||||
-----------------------
|
||||
|
||||
Just like when writing C++, you can encapsulate routines in terms of "passes",
|
||||
which adds your Pass to a global registry of commands accessible 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``
|
|
@ -36,7 +36,7 @@ The main characteristics are:
|
|||
all compilers, standard libraries and architectures.
|
||||
|
||||
In addition to ``dict<K, T>`` and ``pool<T>`` there is also an ``idict<K>`` that
|
||||
creates a bijective map from ``K`` to the integers. For example:
|
||||
creates a bijective map from ``K`` to incrementing integers. For example:
|
||||
|
||||
::
|
||||
|
||||
|
|
|
@ -1,22 +1,25 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
import libyosys as ys
|
||||
from pyosys import libyosys as ys
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
__file_dir__ = Path(__file__).absolute().parent
|
||||
|
||||
class CellStatsPass(ys.Pass):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("cell_stats", "Shows cell stats as plot")
|
||||
|
||||
def py_help(self):
|
||||
def help(self):
|
||||
ys.log("This pass uses the matplotlib library to display cell stats\n")
|
||||
|
||||
def py_execute(self, args, design):
|
||||
def execute(self, args, design):
|
||||
ys.log_header(design, "Plotting cell stats\n")
|
||||
cell_stats = {}
|
||||
for module in design.selected_whole_modules_warn():
|
||||
for module in design.all_selected_whole_modules():
|
||||
for cell in module.selected_cells():
|
||||
if cell.type.str() in cell_stats:
|
||||
cell_stats[cell.type.str()] += 1
|
||||
|
@ -26,7 +29,14 @@ 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()
|
||||
p = CellStatsPass() # register
|
||||
|
||||
if __name__ == "__main__":
|
||||
design = ys.Design()
|
||||
ys.run_pass(f"read_verilog {__file_dir__.parents[1] / 'tests' / 'simple' / 'fiedler-cooley.v'}", design)
|
||||
ys.run_pass("prep", design)
|
||||
ys.run_pass("opt -full", design)
|
||||
ys.run_pass("cell_stats", design)
|
||||
|
|
|
@ -37,6 +37,12 @@
|
|||
# include <tcl.h>
|
||||
#endif
|
||||
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
# include <Python.h>
|
||||
# include <pybind11/pybind11.h>
|
||||
namespace py = pybind11;
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
|
@ -91,9 +97,10 @@ int main(int argc, char **argv)
|
|||
log_error_stderr = true;
|
||||
yosys_banner();
|
||||
yosys_setup();
|
||||
#ifdef WITH_PYTHON
|
||||
PyRun_SimpleString(("sys.path.append(\""+proc_self_dirname()+"\")").c_str());
|
||||
PyRun_SimpleString(("sys.path.append(\""+proc_share_dirname()+"plugins\")").c_str());
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
py::object sys = py::module_::import("sys");
|
||||
sys.attr("path").attr("append")(proc_self_dirname());
|
||||
sys.attr("path").attr("append")(proc_share_dirname());
|
||||
#endif
|
||||
|
||||
if (argc == 2)
|
||||
|
@ -226,10 +233,10 @@ int main(int argc, char **argv)
|
|||
cxxopts::value<std::string>(),"<tcl_scriptfile>")
|
||||
("C,tcl-interactive", "enters TCL interactive shell mode")
|
||||
#endif // YOSYS_ENABLE_TCL
|
||||
#ifdef WITH_PYTHON
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
("y,py-scriptfile", "execute the Python <script>",
|
||||
cxxopts::value<std::string>(), "<script>")
|
||||
#endif // WITH_PYTHON
|
||||
#endif // YOSYS_ENABLE_PYTHON
|
||||
("p,commands", "execute <commands> (to chain commands, separate them with semicolon + whitespace: 'cmd1; cmd2')",
|
||||
cxxopts::value<std::vector<std::string>>(), "<commands>")
|
||||
("r,top", "elaborate the specified HDL <top> module",
|
||||
|
@ -515,9 +522,10 @@ int main(int argc, char **argv)
|
|||
#endif
|
||||
|
||||
yosys_setup();
|
||||
#ifdef WITH_PYTHON
|
||||
PyRun_SimpleString(("sys.path.append(\""+proc_self_dirname()+"\")").c_str());
|
||||
PyRun_SimpleString(("sys.path.append(\""+proc_share_dirname()+"plugins\")").c_str());
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
py::object sys = py::module_::import("sys");
|
||||
sys.attr("path").attr("append")(proc_self_dirname());
|
||||
sys.attr("path").attr("append")(proc_share_dirname());
|
||||
#endif
|
||||
log_error_atexit = yosys_atexit;
|
||||
|
||||
|
@ -566,22 +574,19 @@ int main(int argc, char **argv)
|
|||
log_error("Can't execute TCL script: this version of yosys is not built with TCL support enabled.\n");
|
||||
#endif
|
||||
} else if (scriptfile_python) {
|
||||
#ifdef WITH_PYTHON
|
||||
PyObject *sys = PyImport_ImportModule("sys");
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
py::list new_argv;
|
||||
int py_argc = special_args.size() + 1;
|
||||
PyObject *new_argv = PyList_New(py_argc);
|
||||
PyList_SetItem(new_argv, 0, PyUnicode_FromString(scriptfile.c_str()));
|
||||
new_argv.append(scriptfile);
|
||||
for (int i = 1; i < py_argc; ++i)
|
||||
PyList_SetItem(new_argv, i, PyUnicode_FromString(special_args[i - 1].c_str()));
|
||||
new_argv.append(special_args[i - 1]);
|
||||
|
||||
PyObject *old_argv = PyObject_GetAttrString(sys, "argv");
|
||||
PyObject_SetAttrString(sys, "argv", new_argv);
|
||||
Py_DECREF(old_argv);
|
||||
py::setattr(sys, "argv", new_argv);
|
||||
|
||||
PyObject *py_path = PyUnicode_FromString(scriptfile.c_str());
|
||||
PyObject_SetAttrString(sys, "_yosys_script_path", py_path);
|
||||
Py_DECREF(py_path);
|
||||
PyRun_SimpleString("import os, sys; sys.path.insert(0, os.path.dirname(os.path.abspath(sys._yosys_script_path)))");
|
||||
py::object Path = py::module_::import("pathlib").attr("Path");
|
||||
py::object scriptfile_python_path = Path(scriptfile).attr("parent");
|
||||
|
||||
sys.attr("path").attr("insert")(0, py::str(scriptfile_python_path));
|
||||
|
||||
FILE *scriptfp = fopen(scriptfile.c_str(), "r");
|
||||
if (scriptfp == nullptr) {
|
||||
|
|
|
@ -1083,7 +1083,7 @@ RTLIL::Design::Design()
|
|||
refcount_modules_ = 0;
|
||||
push_full_selection();
|
||||
|
||||
#ifdef WITH_PYTHON
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
RTLIL::Design::get_all_designs()->insert(std::pair<unsigned int, RTLIL::Design*>(hashidx_, this));
|
||||
#endif
|
||||
}
|
||||
|
@ -1094,12 +1094,12 @@ RTLIL::Design::~Design()
|
|||
delete pr.second;
|
||||
for (auto n : bindings_)
|
||||
delete n;
|
||||
#ifdef WITH_PYTHON
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
RTLIL::Design::get_all_designs()->erase(hashidx_);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef WITH_PYTHON
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
static std::map<unsigned int, RTLIL::Design*> all_designs;
|
||||
std::map<unsigned int, RTLIL::Design*> *RTLIL::Design::get_all_designs(void)
|
||||
{
|
||||
|
@ -1430,7 +1430,7 @@ RTLIL::Module::Module()
|
|||
refcount_wires_ = 0;
|
||||
refcount_cells_ = 0;
|
||||
|
||||
#ifdef WITH_PYTHON
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
RTLIL::Module::get_all_modules()->insert(std::pair<unsigned int, RTLIL::Module*>(hashidx_, this));
|
||||
#endif
|
||||
}
|
||||
|
@ -1447,12 +1447,12 @@ RTLIL::Module::~Module()
|
|||
delete pr.second;
|
||||
for (auto binding : bindings_)
|
||||
delete binding;
|
||||
#ifdef WITH_PYTHON
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
RTLIL::Module::get_all_modules()->erase(hashidx_);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef WITH_PYTHON
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
static std::map<unsigned int, RTLIL::Module*> all_modules;
|
||||
std::map<unsigned int, RTLIL::Module*> *RTLIL::Module::get_all_modules(void)
|
||||
{
|
||||
|
@ -4109,19 +4109,19 @@ RTLIL::Wire::Wire()
|
|||
upto = false;
|
||||
is_signed = false;
|
||||
|
||||
#ifdef WITH_PYTHON
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
RTLIL::Wire::get_all_wires()->insert(std::pair<unsigned int, RTLIL::Wire*>(hashidx_, this));
|
||||
#endif
|
||||
}
|
||||
|
||||
RTLIL::Wire::~Wire()
|
||||
{
|
||||
#ifdef WITH_PYTHON
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
RTLIL::Wire::get_all_wires()->erase(hashidx_);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef WITH_PYTHON
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
static std::map<unsigned int, RTLIL::Wire*> all_wires;
|
||||
std::map<unsigned int, RTLIL::Wire*> *RTLIL::Wire::get_all_wires(void)
|
||||
{
|
||||
|
@ -4138,7 +4138,7 @@ RTLIL::Memory::Memory()
|
|||
width = 1;
|
||||
start_offset = 0;
|
||||
size = 0;
|
||||
#ifdef WITH_PYTHON
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
RTLIL::Memory::get_all_memorys()->insert(std::pair<unsigned int, RTLIL::Memory*>(hashidx_, this));
|
||||
#endif
|
||||
}
|
||||
|
@ -4159,19 +4159,19 @@ RTLIL::Cell::Cell() : module(nullptr)
|
|||
// log("#memtrace# %p\n", this);
|
||||
memhasher();
|
||||
|
||||
#ifdef WITH_PYTHON
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
RTLIL::Cell::get_all_cells()->insert(std::pair<unsigned int, RTLIL::Cell*>(hashidx_, this));
|
||||
#endif
|
||||
}
|
||||
|
||||
RTLIL::Cell::~Cell()
|
||||
{
|
||||
#ifdef WITH_PYTHON
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
RTLIL::Cell::get_all_cells()->erase(hashidx_);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef WITH_PYTHON
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
static std::map<unsigned int, RTLIL::Cell*> all_cells;
|
||||
std::map<unsigned int, RTLIL::Cell*> *RTLIL::Cell::get_all_cells(void)
|
||||
{
|
||||
|
@ -5957,7 +5957,7 @@ RTLIL::Process *RTLIL::Process::clone() const
|
|||
return new_proc;
|
||||
}
|
||||
|
||||
#ifdef WITH_PYTHON
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
RTLIL::Memory::~Memory()
|
||||
{
|
||||
RTLIL::Memory::get_all_memorys()->erase(hashidx_);
|
||||
|
|
|
@ -1680,7 +1680,7 @@ struct RTLIL::Design
|
|||
// returns all selected unboxed whole modules, warning the user if any
|
||||
// partially selected or boxed modules have been ignored
|
||||
std::vector<RTLIL::Module*> selected_unboxed_whole_modules_warn() const { return selected_modules(SELECT_WHOLE_WARN, SB_UNBOXED_WARN); }
|
||||
#ifdef WITH_PYTHON
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
static std::map<unsigned int, RTLIL::Design*> *get_all_designs(void);
|
||||
#endif
|
||||
};
|
||||
|
@ -2040,7 +2040,7 @@ public:
|
|||
RTLIL::SigSpec OriginalTag (RTLIL::IdString name, const std::string &tag, const RTLIL::SigSpec &sig_a, const std::string &src = "");
|
||||
RTLIL::SigSpec FutureFF (RTLIL::IdString name, const RTLIL::SigSpec &sig_e, const std::string &src = "");
|
||||
|
||||
#ifdef WITH_PYTHON
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
static std::map<unsigned int, RTLIL::Module*> *get_all_modules(void);
|
||||
#endif
|
||||
};
|
||||
|
@ -2093,7 +2093,7 @@ public:
|
|||
return zero_index + start_offset;
|
||||
}
|
||||
|
||||
#ifdef WITH_PYTHON
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
static std::map<unsigned int, RTLIL::Wire*> *get_all_wires(void);
|
||||
#endif
|
||||
};
|
||||
|
@ -2110,7 +2110,7 @@ struct RTLIL::Memory : public RTLIL::NamedObject
|
|||
Memory();
|
||||
|
||||
int width, start_offset, size;
|
||||
#ifdef WITH_PYTHON
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
~Memory();
|
||||
static std::map<unsigned int, RTLIL::Memory*> *get_all_memorys(void);
|
||||
#endif
|
||||
|
@ -2168,7 +2168,7 @@ public:
|
|||
template<typename T> void rewrite_sigspecs(T &functor);
|
||||
template<typename T> void rewrite_sigspecs2(T &functor);
|
||||
|
||||
#ifdef WITH_PYTHON
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
static std::map<unsigned int, RTLIL::Cell*> *get_all_cells(void);
|
||||
#endif
|
||||
|
||||
|
|
|
@ -37,6 +37,12 @@
|
|||
# include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
# include <Python.h>
|
||||
# include <pybind11/pybind11.h>
|
||||
namespace py = pybind11;
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
# include <windows.h>
|
||||
# include <io.h>
|
||||
|
@ -63,14 +69,9 @@
|
|||
# include <sys/sysctl.h>
|
||||
#endif
|
||||
|
||||
#ifdef WITH_PYTHON
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
# define INIT_MODULE PyInit_libyosys
|
||||
extern "C" PyObject* INIT_MODULE();
|
||||
#else
|
||||
# define INIT_MODULE initlibyosys
|
||||
extern "C" void INIT_MODULE();
|
||||
#endif
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
extern "C" PyObject* PyInit_libyosys();
|
||||
extern "C" PyObject* PyInit_pyosys();
|
||||
#include <signal.h>
|
||||
#endif
|
||||
|
||||
|
@ -189,6 +190,25 @@ int run_command(const std::string &command, std::function<void(const std::string
|
|||
bool already_setup = false;
|
||||
bool already_shutdown = false;
|
||||
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
// Include pyosys as a package for some compatibility with wheels.
|
||||
//
|
||||
// This should not affect using wheels as the dylib has to actually be called
|
||||
// pyosys.so for this function to be interacted with at all.
|
||||
PYBIND11_MODULE(pyosys, m) {
|
||||
m.add_object("__path__", py::list());
|
||||
}
|
||||
|
||||
// Catch uses of 'import libyosys' which can import libyosys.so, causing a ton
|
||||
// of symbol collisions and overall weird behavior.
|
||||
//
|
||||
// This should not affect using wheels as the dylib has to actually be called
|
||||
// libyosys_dummy.so for this function to be interacted with at all.
|
||||
PYBIND11_MODULE(libyosys_dummy, _) {
|
||||
throw py::import_error("Change your import from 'import libyosys' to 'from pyosys import libyosys'.");
|
||||
}
|
||||
#endif
|
||||
|
||||
void yosys_setup()
|
||||
{
|
||||
if(already_setup)
|
||||
|
@ -198,12 +218,16 @@ void yosys_setup()
|
|||
|
||||
IdString::ensure_prepopulated();
|
||||
|
||||
#ifdef WITH_PYTHON
|
||||
// With Python 3.12, calling PyImport_AppendInittab on an already
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
// Starting Python 3.12, calling PyImport_AppendInittab on an already
|
||||
// initialized platform fails (such as when libyosys is imported
|
||||
// from a Python interpreter)
|
||||
if (!Py_IsInitialized()) {
|
||||
PyImport_AppendInittab((char*)"libyosys", INIT_MODULE);
|
||||
PyImport_AppendInittab((char*)"pyosys.libyosys", PyInit_libyosys);
|
||||
// compatibility with wheels
|
||||
PyImport_AppendInittab((char*)"pyosys", PyInit_pyosys);
|
||||
// prevent catastrophes
|
||||
PyImport_AppendInittab((char*)"libyosys", PyInit_libyosys_dummy);
|
||||
Py_Initialize();
|
||||
PyRun_SimpleString("import sys");
|
||||
signal(SIGINT, SIG_DFL);
|
||||
|
@ -260,13 +284,13 @@ void yosys_shutdown()
|
|||
dlclose(it.second);
|
||||
|
||||
loaded_plugins.clear();
|
||||
#ifdef WITH_PYTHON
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
loaded_python_plugins.clear();
|
||||
#endif
|
||||
loaded_plugin_aliases.clear();
|
||||
#endif
|
||||
|
||||
#ifdef WITH_PYTHON
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
Py_Finalize();
|
||||
#endif
|
||||
}
|
||||
|
@ -542,7 +566,7 @@ void init_share_dirname()
|
|||
#else
|
||||
void init_share_dirname()
|
||||
{
|
||||
# ifdef WITH_PYTHON
|
||||
# ifdef YOSYS_ENABLE_PYTHON
|
||||
PyObject *sys_obj = PyImport_ImportModule("sys");
|
||||
|
||||
if (PyObject_HasAttrString(sys_obj, "_pyosys_share_dirname")) {
|
||||
|
@ -602,7 +626,7 @@ void init_abc_executable_name()
|
|||
yosys_abc_executable = proc_self_dirname() + "..\\" + proc_program_prefix() + "yosys-abc";
|
||||
# endif
|
||||
|
||||
# ifdef WITH_PYTHON
|
||||
# ifdef YOSYS_ENABLE_PYTHON
|
||||
PyObject *sys_obj = PyImport_ImportModule("sys");
|
||||
|
||||
if (PyObject_HasAttrString(sys_obj, "_pyosys_abc")) {
|
||||
|
|
|
@ -51,7 +51,7 @@ YOSYS_NAMESPACE_BEGIN
|
|||
|
||||
void yosys_setup();
|
||||
|
||||
#ifdef WITH_PYTHON
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
bool yosys_already_setup();
|
||||
#endif
|
||||
|
||||
|
@ -89,7 +89,7 @@ extern std::vector<RTLIL::Design*> pushed_designs;
|
|||
|
||||
// from passes/cmds/pluginc.cc
|
||||
extern std::map<std::string, void*> loaded_plugins;
|
||||
#ifdef WITH_PYTHON
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
extern std::map<std::string, void*> loaded_python_plugins;
|
||||
#endif
|
||||
extern std::map<std::string, std::string> loaded_plugin_aliases;
|
||||
|
|
|
@ -53,10 +53,6 @@
|
|||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
|
||||
#ifdef WITH_PYTHON
|
||||
#include <Python.h>
|
||||
#endif
|
||||
|
||||
#ifndef _YOSYS_
|
||||
# error It looks like you are trying to build Yosys without the config defines set. \
|
||||
When building Yosys with a custom make system, make sure you set all the \
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -24,16 +24,16 @@
|
|||
# include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
#ifdef WITH_PYTHON
|
||||
# include <boost/algorithm/string/predicate.hpp>
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
# include <Python.h>
|
||||
# include <boost/filesystem.hpp>
|
||||
# include <pybind11/pybind11.h>
|
||||
namespace py = pybind11;
|
||||
#endif
|
||||
|
||||
YOSYS_NAMESPACE_BEGIN
|
||||
|
||||
std::map<std::string, void*> loaded_plugins;
|
||||
#ifdef WITH_PYTHON
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
std::map<std::string, void*> loaded_python_plugins;
|
||||
#endif
|
||||
std::map<std::string, std::string> loaded_plugin_aliases;
|
||||
|
@ -49,7 +49,7 @@ void load_plugin(std::string filename, std::vector<std::string> aliases)
|
|||
filename = "./" + filename;
|
||||
|
||||
|
||||
#ifdef WITH_PYTHON
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
const bool is_loaded = loaded_plugins.count(orig_filename) && loaded_python_plugins.count(orig_filename);
|
||||
#else
|
||||
const bool is_loaded = loaded_plugins.count(orig_filename);
|
||||
|
@ -57,23 +57,23 @@ void load_plugin(std::string filename, std::vector<std::string> aliases)
|
|||
|
||||
if (!is_loaded) {
|
||||
// Check if we're loading a python script
|
||||
if(filename.find(".py") != std::string::npos)
|
||||
{
|
||||
#ifdef WITH_PYTHON
|
||||
boost::filesystem::path full_path(filename);
|
||||
std::string path(full_path.parent_path().c_str());
|
||||
filename = full_path.filename().c_str();
|
||||
filename = filename.substr(0,filename.size()-3);
|
||||
PyRun_SimpleString(("sys.path.insert(0,\""+path+"\")").c_str());
|
||||
PyErr_Print();
|
||||
PyObject *module_p = PyImport_ImportModule(filename.c_str());
|
||||
if(module_p == NULL)
|
||||
{
|
||||
PyErr_Print();
|
||||
log_cmd_error("Can't load python module `%s'\n", full_path.filename());
|
||||
if (filename.rfind(".py") != std::string::npos) {
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
py::object Path = py::module_::import("pathlib").attr("Path");
|
||||
py::object full_path = Path(py::cast(filename));
|
||||
py::object plugin_python_path = full_path.attr("parent");
|
||||
auto basename = py::cast<std::string>(full_path.attr("stem"));
|
||||
|
||||
py::object sys = py::module_::import("sys");
|
||||
sys.attr("path").attr("insert")(0, py::str(plugin_python_path));
|
||||
|
||||
try {
|
||||
auto module_container = py::module_::import(basename.c_str());
|
||||
loaded_python_plugins[orig_filename] = module_container.ptr();
|
||||
} catch (py::error_already_set &e) {
|
||||
log_cmd_error("Can't load python module `%s': %s\n", basename, e.what());
|
||||
return;
|
||||
}
|
||||
loaded_python_plugins[orig_filename] = module_p;
|
||||
Pass::init_register();
|
||||
#else
|
||||
log_error(
|
||||
|
@ -177,7 +177,7 @@ struct PluginPass : public Pass {
|
|||
if (list_mode)
|
||||
{
|
||||
log("\n");
|
||||
#ifdef WITH_PYTHON
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
if (loaded_plugins.empty() and loaded_python_plugins.empty())
|
||||
#else
|
||||
if (loaded_plugins.empty())
|
||||
|
@ -189,7 +189,7 @@ struct PluginPass : public Pass {
|
|||
for (auto &it : loaded_plugins)
|
||||
log(" %s\n", it.first);
|
||||
|
||||
#ifdef WITH_PYTHON
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
for (auto &it : loaded_python_plugins)
|
||||
log(" %s\n", it.first);
|
||||
#endif
|
||||
|
|
2
pyosys/.gitignore
vendored
Normal file
2
pyosys/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
wrappers.cc
|
||||
wrappers.inc.cc
|
788
pyosys/generator.py
Normal file
788
pyosys/generator.py
Normal file
|
@ -0,0 +1,788 @@
|
|||
#!/usr/bin/env python3
|
||||
# yosys -- Yosys Open SYnthesis Suite
|
||||
#
|
||||
# Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
|
||||
#
|
||||
# Permission to use, copy, modify, and/or distribute this software for any
|
||||
# purpose with or without fee is hereby granted, provided that the above
|
||||
# copyright notice and this permission notice appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
#
|
||||
# Written by Mohamed Gaber <me@donn.website>
|
||||
#
|
||||
# Inspired by py_wrap_generator.py by Benedikt Tutzer
|
||||
"""
|
||||
This generates:
|
||||
- Wrapper calls for opaque container types
|
||||
- Full wrappers for select classes and all enums, global functions and global
|
||||
variables
|
||||
|
||||
Jump to "MARK: Inclusion and Exclusion" to control the above.
|
||||
|
||||
Please run ruff on this file in particular to make sure it parses with Python
|
||||
<= 3.12. There is so much f-string use here that the outer quote thing
|
||||
is a common problem. ``ruff check pyosys/generator.py`` suffices.
|
||||
"""
|
||||
import os
|
||||
import io
|
||||
import shutil
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from sysconfig import get_paths
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Dict, FrozenSet, Iterable, Tuple, Union, Optional, List
|
||||
|
||||
import pybind11
|
||||
from cxxheaderparser.simple import parse_file, ClassScope, NamespaceScope, EnumDecl
|
||||
from cxxheaderparser.options import ParserOptions
|
||||
from cxxheaderparser.preprocessor import make_gcc_preprocessor
|
||||
from cxxheaderparser.types import (
|
||||
PQName,
|
||||
Type,
|
||||
Pointer,
|
||||
Reference,
|
||||
MoveReference,
|
||||
AnonymousName,
|
||||
Method,
|
||||
Function,
|
||||
Field,
|
||||
Variable,
|
||||
Array,
|
||||
FundamentalSpecifier,
|
||||
)
|
||||
|
||||
__file_dir__ = Path(__file__).absolute().parent
|
||||
__yosys_root__ = __file_dir__.parent
|
||||
|
||||
|
||||
@dataclass
|
||||
class PyosysClass:
|
||||
"""
|
||||
Metadata about classes or structs intended to be wrapped using Pyosys.
|
||||
|
||||
:param name: The base name of the class (without extra qualifiers)
|
||||
:param ref_only: Whether this class can be copied to Python, or only
|
||||
referenced.
|
||||
:param string_expr:
|
||||
A C++ expression that will be used for the ``__str__`` method in Python.
|
||||
|
||||
The object will be available as a const reference with the identifier
|
||||
`s`.
|
||||
:param hash_expr:
|
||||
A C++ expression that will be fed to ``run_hash`` to declare a
|
||||
``__hash__`` method for Python.
|
||||
|
||||
The object will be vailable as a const reference with the identifier
|
||||
`s`.
|
||||
:param denylist: If specified, one or more methods can be excluded from
|
||||
wrapping.
|
||||
"""
|
||||
|
||||
name: str
|
||||
ref_only: bool = False
|
||||
|
||||
# in the format s.(method or property) (or just s)
|
||||
string_expr: Optional[str] = None
|
||||
hash_expr: Optional[str] = None
|
||||
|
||||
denylist: FrozenSet[str] = frozenset({})
|
||||
|
||||
|
||||
@dataclass
|
||||
class PyosysHeader:
|
||||
"""
|
||||
:param name: The name of the header, i.e., its relative path to the Yosys root
|
||||
:param classes: A list of ``PyosysClass`` classes to be wrapped
|
||||
:param enums: A list of enums to be wrapped
|
||||
"""
|
||||
|
||||
name: str
|
||||
classes: List[PyosysClass] = field(default_factory=lambda: [])
|
||||
|
||||
def __post_init__(self):
|
||||
self.classes_by_name = {}
|
||||
if classes := self.classes:
|
||||
for cls in classes:
|
||||
self.classes_by_name[cls.name] = cls
|
||||
|
||||
|
||||
# MARK: Inclusion and Exclusion
|
||||
global_denylist = frozenset(
|
||||
{
|
||||
# deprecated
|
||||
"builtin_ff_cell_types",
|
||||
"logv_file_error",
|
||||
# no implementation
|
||||
"set_verific_logging",
|
||||
# can't bridge to python cleanly
|
||||
## std::regex
|
||||
"log_warn_regexes",
|
||||
"log_nowarn_regexes",
|
||||
"log_werror_regexes",
|
||||
## function pointers
|
||||
"log_error_atexit",
|
||||
"log_verific_callback",
|
||||
}
|
||||
)
|
||||
pyosys_headers = [
|
||||
# Headers for incomplete types
|
||||
PyosysHeader("kernel/binding.h"),
|
||||
PyosysHeader("libs/sha1/sha1.h"),
|
||||
# Headers for globals
|
||||
PyosysHeader("kernel/log.h"),
|
||||
PyosysHeader("kernel/yosys.h"),
|
||||
PyosysHeader("kernel/cost.h"),
|
||||
# Headers with classes
|
||||
PyosysHeader(
|
||||
"kernel/celltypes.h",
|
||||
[PyosysClass("CellType", hash_expr="s.type"), PyosysClass("CellTypes")],
|
||||
),
|
||||
PyosysHeader("kernel/consteval.h", [PyosysClass("ConstEval")]),
|
||||
PyosysHeader(
|
||||
"kernel/register.h",
|
||||
[
|
||||
# PyosysClass("Pass") # Virtual methods, manually bridged
|
||||
],
|
||||
),
|
||||
PyosysHeader(
|
||||
"kernel/rtlil.h",
|
||||
[
|
||||
PyosysClass(
|
||||
"IdString",
|
||||
string_expr="s.str()",
|
||||
hash_expr="s.str()",
|
||||
denylist=frozenset(
|
||||
# shouldn't be messed with from python in general
|
||||
{
|
||||
"global_id_storage_",
|
||||
"global_id_index_",
|
||||
"global_refcount_storage_",
|
||||
"global_free_idx_list_",
|
||||
"last_created_idx_ptr_",
|
||||
"last_created_idx_",
|
||||
"builtin_ff_cell_types",
|
||||
}
|
||||
),
|
||||
),
|
||||
PyosysClass(
|
||||
"Const",
|
||||
string_expr="s.as_string()",
|
||||
denylist=frozenset({"bits", "bitvectorize"}),
|
||||
),
|
||||
PyosysClass("AttrObject", denylist=frozenset({"get_blackbox_attribute"})),
|
||||
PyosysClass("NamedObject", denylist=frozenset({"get_blackbox_attribute"})),
|
||||
PyosysClass("Selection"),
|
||||
# PyosysClass("Monitor"), # Virtual methods, manually bridged
|
||||
PyosysClass("CaseRule", denylist=frozenset({"get_blackbox_attribute"})),
|
||||
PyosysClass("SwitchRule", denylist=frozenset({"get_blackbox_attribute"})),
|
||||
PyosysClass("SyncRule"),
|
||||
PyosysClass(
|
||||
"Process",
|
||||
ref_only=True,
|
||||
string_expr="s.name.c_str()",
|
||||
hash_expr="s.name",
|
||||
),
|
||||
PyosysClass("SigChunk"),
|
||||
PyosysClass("SigBit", hash_expr="s"),
|
||||
PyosysClass("SigSpec", hash_expr="s"),
|
||||
PyosysClass(
|
||||
"Cell",
|
||||
ref_only=True,
|
||||
string_expr="s.name.c_str()",
|
||||
hash_expr="s",
|
||||
),
|
||||
PyosysClass(
|
||||
"Wire",
|
||||
ref_only=True,
|
||||
string_expr="s.name.c_str()",
|
||||
hash_expr="s",
|
||||
),
|
||||
PyosysClass(
|
||||
"Memory",
|
||||
ref_only=True,
|
||||
string_expr="s.name.c_str()",
|
||||
hash_expr="s",
|
||||
),
|
||||
PyosysClass(
|
||||
"Module",
|
||||
ref_only=True,
|
||||
string_expr="s.name.c_str()",
|
||||
hash_expr="s",
|
||||
denylist=frozenset({"Pow"}), # has no implementation
|
||||
),
|
||||
PyosysClass(
|
||||
"Design",
|
||||
string_expr="s.hashidx_",
|
||||
hash_expr="s",
|
||||
denylist=frozenset({"selected_whole_modules"}), # deprecated
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@dataclass(frozen=True) # hashable
|
||||
class PyosysType:
|
||||
"""
|
||||
Bit of a hacky object all-around: this is more or less used to encapsulate
|
||||
container types so they can be later made opaque using pybind.
|
||||
"""
|
||||
|
||||
base: str
|
||||
specialization: Tuple["PyosysType", ...]
|
||||
const: bool = False
|
||||
|
||||
@classmethod
|
||||
def from_type(Self, type_obj, drop_const=False) -> "PyosysType":
|
||||
const = type_obj.const and not drop_const
|
||||
if isinstance(type_obj, Pointer):
|
||||
ptr_to = Self.from_type(type_obj.ptr_to)
|
||||
return Self("ptr", (ptr_to,), const)
|
||||
elif isinstance(type_obj, Reference):
|
||||
ref_to = Self.from_type(type_obj.ref_to)
|
||||
return Self("ref", (ref_to,), const)
|
||||
assert isinstance(
|
||||
type_obj, Type
|
||||
), f"unexpected c++ type object of type {type(type_obj)}"
|
||||
last_segment = type_obj.typename.segments[-1]
|
||||
base = last_segment.name
|
||||
specialization = tuple()
|
||||
if (
|
||||
hasattr(last_segment, "specialization")
|
||||
and last_segment.specialization is not None
|
||||
):
|
||||
for template_arg in last_segment.specialization.args:
|
||||
specialization = (*specialization, Self.from_type(template_arg.arg))
|
||||
return Self(base, specialization, const)
|
||||
|
||||
def generate_identifier(self):
|
||||
title = self.base.title()
|
||||
if len(self.specialization) == 0:
|
||||
return title
|
||||
|
||||
if title == "Dict":
|
||||
key, value = self.specialization
|
||||
return f"{key.generate_identifier()}To{value.generate_identifier()}{title}"
|
||||
|
||||
return (
|
||||
"".join(spec.generate_identifier() for spec in self.specialization) + title
|
||||
)
|
||||
|
||||
def generate_cpp_name(self):
|
||||
const_prefix = "const " * self.const
|
||||
if len(self.specialization) == 0:
|
||||
return const_prefix + self.base
|
||||
elif self.base == "ptr":
|
||||
return const_prefix + f"{self.specialization[0].generate_cpp_name()} *"
|
||||
elif self.base == "ref":
|
||||
return const_prefix + f"{self.specialization[0].generate_cpp_name()} &"
|
||||
else:
|
||||
return (
|
||||
const_prefix
|
||||
+ f"{self.base}<{', '.join(spec.generate_cpp_name() for spec in self.specialization)}>"
|
||||
)
|
||||
|
||||
|
||||
class PyosysWrapperGenerator(object):
|
||||
def __init__(
|
||||
self,
|
||||
from_headers: Iterable[PyosysHeader],
|
||||
wrapper_stream: io.TextIOWrapper,
|
||||
header_stream: io.TextIOWrapper,
|
||||
):
|
||||
self.headers = from_headers
|
||||
self.f = wrapper_stream
|
||||
self.f_inc = header_stream
|
||||
self.found_containers: Dict[PyosysType, Any] = {}
|
||||
self.class_registry: Dict[str, ClassScope] = {}
|
||||
|
||||
# entry point
|
||||
def generate(self):
|
||||
tpl = __file_dir__ / "wrappers_tpl.cc"
|
||||
preprocessor_opts = self.make_preprocessor_options()
|
||||
with open(tpl, encoding="utf8") as f:
|
||||
do_line_directive = True
|
||||
for i, line in enumerate(f):
|
||||
if do_line_directive:
|
||||
self.f.write(f'#line {i + 1} "{tpl}"\n')
|
||||
do_line_directive = False
|
||||
if "<!-- generated includes -->" in line:
|
||||
for header in self.headers:
|
||||
self.f.write(f'#include "{header.name}"\n')
|
||||
do_line_directive = True
|
||||
elif "<!-- generated pymod-level code -->" in line:
|
||||
for header in self.headers:
|
||||
header_path = __yosys_root__ / header.name
|
||||
parsed = parse_file(header_path, options=preprocessor_opts)
|
||||
global_namespace = parsed.namespace
|
||||
self.process_namespace(header, global_namespace)
|
||||
else:
|
||||
self.f.write(line)
|
||||
|
||||
for container, _ in self.found_containers.items():
|
||||
identifier = container.generate_identifier()
|
||||
print(
|
||||
f"using {identifier} = {container.generate_cpp_name()};",
|
||||
file=self.f_inc,
|
||||
)
|
||||
print(f"PYBIND11_MAKE_OPAQUE({identifier})", file=self.f_inc)
|
||||
|
||||
print(
|
||||
f"static void bind_autogenerated_opaque_containers(py::module &m) {{",
|
||||
file=self.f_inc,
|
||||
)
|
||||
for container, _ in self.found_containers.items():
|
||||
identifier = container.generate_identifier()
|
||||
cxx = container.generate_cpp_name()
|
||||
tpl_args = [cxx]
|
||||
for spec in container.specialization:
|
||||
tpl_args.append(spec.generate_cpp_name())
|
||||
print(
|
||||
f'\tpy::hashlib::bind_{container.base}<{", ".join(tpl_args)}>(m, "{container.generate_identifier()}");',
|
||||
file=self.f_inc,
|
||||
)
|
||||
print(
|
||||
f"\tpy::implicitly_convertible<py::iterable, {identifier}>();",
|
||||
file=self.f_inc,
|
||||
)
|
||||
print(f"}}", file=self.f_inc)
|
||||
|
||||
# helpers
|
||||
def make_preprocessor_options(self):
|
||||
py_include = get_paths()["include"]
|
||||
preprocessor_bin = shutil.which("clang++") or "g++"
|
||||
cxx_std = os.getenv("CXX_STD", "c++17")
|
||||
return ParserOptions(
|
||||
preprocessor=make_gcc_preprocessor(
|
||||
defines=["_YOSYS_", "YOSYS_ENABLE_PYTHON"],
|
||||
gcc_args=[preprocessor_bin, "-fsyntax-only", f"-std={cxx_std}"],
|
||||
include_paths=[str(__yosys_root__), py_include, pybind11.get_include()],
|
||||
),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def find_containers(
|
||||
containers: Iterable[str], type_info: Any
|
||||
) -> Dict[PyosysType, Any]:
|
||||
if isinstance(type_info, Pointer):
|
||||
return PyosysWrapperGenerator.find_containers(containers, type_info.ptr_to)
|
||||
if isinstance(type_info, MoveReference):
|
||||
return PyosysWrapperGenerator.find_containers(
|
||||
containers, type_info.moveref_to
|
||||
)
|
||||
if isinstance(type_info, Reference):
|
||||
return PyosysWrapperGenerator.find_containers(containers, type_info.ref_to)
|
||||
if not isinstance(type_info, Type):
|
||||
return ()
|
||||
segments = type_info.typename.segments
|
||||
containers_found = {}
|
||||
for segment in segments:
|
||||
if isinstance(segment, FundamentalSpecifier):
|
||||
continue
|
||||
if segment.name in containers:
|
||||
containers_found.update(
|
||||
{PyosysType.from_type(type_info, drop_const=True): type_info}
|
||||
)
|
||||
if segment.specialization is not None:
|
||||
for arg in segment.specialization.args:
|
||||
sub_containers = PyosysWrapperGenerator.find_containers(
|
||||
containers, arg.arg
|
||||
)
|
||||
containers_found.update(sub_containers)
|
||||
return containers_found
|
||||
|
||||
@staticmethod
|
||||
def find_anonymous_union(cls: ClassScope):
|
||||
if cls.class_decl.typename.classkey != "union":
|
||||
return None
|
||||
for s in cls.class_decl.typename.segments:
|
||||
if isinstance(s, AnonymousName):
|
||||
return s
|
||||
return None # named union
|
||||
|
||||
@staticmethod
|
||||
def get_parameter_types(function: Function) -> str:
|
||||
return ", ".join(p.type.format() for p in function.parameters)
|
||||
|
||||
def register_containers(self, target: Union[Function, Field, Variable]):
|
||||
supported = ("dict", "idict", "pool", "set", "vector")
|
||||
if isinstance(target, Function):
|
||||
self.found_containers.update(
|
||||
self.find_containers(supported, target.return_type)
|
||||
)
|
||||
|
||||
for parameter in target.parameters:
|
||||
self.found_containers.update(
|
||||
self.find_containers(supported, parameter.type)
|
||||
)
|
||||
else:
|
||||
self.found_containers.update(self.find_containers(supported, target.type))
|
||||
|
||||
# processors
|
||||
def get_overload_cast(
|
||||
self, function: Function, class_basename: Optional[str]
|
||||
) -> str:
|
||||
is_method = isinstance(function, Method)
|
||||
function_return_type = function.return_type.format()
|
||||
if class_basename == "Const" and function_return_type in {
|
||||
"iterator",
|
||||
"const_iterator",
|
||||
}:
|
||||
# HACK: qualify Const's iterators
|
||||
function_return_type = f"{class_basename}::{function_return_type}"
|
||||
|
||||
pointer_kind = (
|
||||
f"{class_basename}::*" if (is_method and not function.static) else "*"
|
||||
)
|
||||
|
||||
retval = f"static_cast <"
|
||||
retval += function_return_type
|
||||
retval += f"({pointer_kind})"
|
||||
retval += f"({self.get_parameter_types(function)})"
|
||||
if is_method and function.const:
|
||||
retval += " const"
|
||||
retval += ">"
|
||||
retval += "(&"
|
||||
if is_method:
|
||||
retval += f"{class_basename}::"
|
||||
retval += function.name.segments[-1].format()
|
||||
retval += ")"
|
||||
return retval
|
||||
|
||||
def get_definition_args(
|
||||
self,
|
||||
function: Function,
|
||||
class_basename: Optional[str],
|
||||
python_name_override: Optional[str] = None,
|
||||
) -> List[str]:
|
||||
function_basename = function.name.segments[-1].format()
|
||||
|
||||
python_function_basename = python_name_override or keyword_aliases.get(
|
||||
function_basename, function_basename
|
||||
)
|
||||
|
||||
def_args = [f'"{python_function_basename}"']
|
||||
def_args.append(self.get_overload_cast(function, class_basename))
|
||||
for parameter in function.parameters:
|
||||
# ASSUMPTION: there are no unnamed parameters in the yosys codebase
|
||||
parameter_arg = f'py::arg("{parameter.name}")'
|
||||
if parameter.default is not None:
|
||||
parameter_arg += f" = {parameter.default.format()}"
|
||||
def_args.append(parameter_arg)
|
||||
|
||||
return def_args
|
||||
|
||||
def process_method(self, metadata: PyosysClass, function: Method):
|
||||
if (
|
||||
function.deleted
|
||||
or function.template
|
||||
or function.vararg
|
||||
or function.access != "public"
|
||||
or function.pure_virtual
|
||||
or function.destructor
|
||||
):
|
||||
return
|
||||
|
||||
if any(isinstance(p.type, MoveReference) for p in function.parameters):
|
||||
# skip move constructors
|
||||
return
|
||||
|
||||
if len(function.name.segments) > 1:
|
||||
# can't handle, skip
|
||||
return
|
||||
|
||||
if function.constructor:
|
||||
if (
|
||||
not metadata.ref_only
|
||||
): # ref-only classes should not be constructed from python
|
||||
print(
|
||||
f"\t\t\t.def(py::init<{self.get_parameter_types(function)}>())",
|
||||
file=self.f,
|
||||
)
|
||||
return
|
||||
|
||||
python_name_override = None
|
||||
if function.operator is not None:
|
||||
if function.operator == "==":
|
||||
python_name_override = "__eq__"
|
||||
elif function.operator == "!=":
|
||||
python_name_override = "__ne__"
|
||||
elif function.operator == "<":
|
||||
python_name_override = "__lt__"
|
||||
else:
|
||||
return
|
||||
|
||||
self.register_containers(function)
|
||||
|
||||
definition_fn = "def"
|
||||
if function.static:
|
||||
definition_fn = "def_static"
|
||||
|
||||
print(
|
||||
f"\t\t\t.{definition_fn}({', '.join(self.get_definition_args(function, metadata.name, python_name_override))})",
|
||||
file=self.f,
|
||||
)
|
||||
|
||||
def process_function(self, function: Function):
|
||||
if function.deleted or function.template or function.vararg or function.static:
|
||||
return
|
||||
|
||||
if function.operator is not None:
|
||||
# Python doesn't do global operators
|
||||
return
|
||||
|
||||
if function.name.segments[-1].format() in global_denylist:
|
||||
return
|
||||
|
||||
self.register_containers(function)
|
||||
|
||||
print(
|
||||
f"\t\t\tm.def({', '.join(self.get_definition_args(function, None))});",
|
||||
file=self.f,
|
||||
)
|
||||
|
||||
def process_field(self, metadata: PyosysClass, field: Field):
|
||||
if field.access != "public":
|
||||
return
|
||||
|
||||
if field.name is None:
|
||||
# anonymous structs and unions
|
||||
# unions handled in calling function
|
||||
# ASSUMPTION: No anonymous structs in the yosys codebase
|
||||
# (they're not in the C++ standard anyway)
|
||||
return
|
||||
|
||||
unique_ptrs = self.find_containers(("unique_ptr",), field.type)
|
||||
if len(unique_ptrs):
|
||||
# TODO: figure out how to bridge unique pointers maybe does anyone
|
||||
# care
|
||||
return
|
||||
|
||||
self.register_containers(field)
|
||||
|
||||
definition_fn = f"def_{'readonly' if field.type.const else 'readwrite'}"
|
||||
if field.static:
|
||||
definition_fn += "_static"
|
||||
|
||||
field_python_basename = keyword_aliases.get(field.name, field.name)
|
||||
|
||||
print(
|
||||
f'\t\t\t.{definition_fn}("{field_python_basename}", &{metadata.name}::{field.name})',
|
||||
file=self.f,
|
||||
)
|
||||
|
||||
def process_variable(self, variable: Variable):
|
||||
if isinstance(variable.type, Array):
|
||||
return
|
||||
|
||||
variable_basename = variable.name.segments[-1].format()
|
||||
if variable_basename in global_denylist:
|
||||
return
|
||||
|
||||
self.register_containers(variable)
|
||||
|
||||
definition_fn = (
|
||||
f"def_{'readonly' if variable.type.const else 'readwrite'}_static"
|
||||
)
|
||||
|
||||
variable_python_basename = keyword_aliases.get(
|
||||
variable_basename, variable_basename
|
||||
)
|
||||
variable_name = variable.name.format()
|
||||
|
||||
print(
|
||||
f'\t\t\tglobal_variables.{definition_fn}("{variable_python_basename}", &{variable_name});',
|
||||
file=self.f,
|
||||
)
|
||||
|
||||
def process_class_members(
|
||||
self, metadata: PyosysClass, cls: ClassScope, basename: str
|
||||
):
|
||||
for method in cls.methods:
|
||||
if method.name.segments[-1].name in metadata.denylist:
|
||||
continue
|
||||
self.process_method(metadata, method)
|
||||
|
||||
visited_anonymous_unions = set()
|
||||
for field_ in cls.fields:
|
||||
if field_.name in metadata.denylist:
|
||||
continue
|
||||
self.process_field(metadata, field_)
|
||||
|
||||
# Handle anonymous unions
|
||||
for subclass in cls.classes:
|
||||
if subclass.class_decl.access != "public":
|
||||
continue
|
||||
if au := self.find_anonymous_union(subclass):
|
||||
if au.id in visited_anonymous_unions:
|
||||
continue
|
||||
visited_anonymous_unions.add(au.id)
|
||||
for subfield in subclass.fields:
|
||||
self.process_field(metadata, subfield)
|
||||
|
||||
def process_class(
|
||||
self,
|
||||
metadata: PyosysClass,
|
||||
cls: ClassScope,
|
||||
namespace_components: Tuple[str, ...],
|
||||
):
|
||||
pqname: PQName = cls.class_decl.typename
|
||||
full_path = list(namespace_components) + [
|
||||
segment.format() for segment in pqname.segments
|
||||
]
|
||||
basename = full_path.pop()
|
||||
self.class_registry[basename] = cls
|
||||
|
||||
declaration_namespace = "::".join(full_path)
|
||||
tpl_args = [basename]
|
||||
if metadata.ref_only:
|
||||
tpl_args.append(f"std::unique_ptr<{basename}, py::nodelete>")
|
||||
print(
|
||||
f'\t\t{{using namespace {declaration_namespace}; py::class_<{", ".join(tpl_args)}>(m, "{basename}")',
|
||||
file=self.f,
|
||||
)
|
||||
|
||||
self.process_class_members(metadata, cls, basename)
|
||||
for base in cls.class_decl.bases:
|
||||
if base.access != "public":
|
||||
continue
|
||||
name = base.typename.segments[-1].format()
|
||||
if base_scope := self.class_registry.get(name):
|
||||
self.process_class_members(metadata, base_scope, basename)
|
||||
|
||||
if expr := metadata.string_expr:
|
||||
print(
|
||||
f'\t\t.def("__str__", [](const {basename} &s) {{ return {expr}; }})',
|
||||
file=self.f,
|
||||
)
|
||||
|
||||
if expr := metadata.hash_expr:
|
||||
print(
|
||||
f'\t\t.def("__hash__", [](const {basename} &s) {{ return run_hash({expr}); }})',
|
||||
file=self.f,
|
||||
)
|
||||
|
||||
print(f"\t\t;}}", file=self.f)
|
||||
|
||||
def process_enum(
|
||||
self,
|
||||
enum: EnumDecl,
|
||||
namespace_components: Tuple[str, ...],
|
||||
):
|
||||
pqname: PQName = enum.typename
|
||||
full_path = list(namespace_components) + [
|
||||
segment.format() for segment in pqname.segments
|
||||
]
|
||||
basename = full_path.pop()
|
||||
|
||||
declaration_namespace = "::".join(full_path)
|
||||
print(
|
||||
f'\t\t{{using namespace {declaration_namespace}; py::native_enum<{basename}>(m, "{basename}", "enum.Enum")',
|
||||
file=self.f,
|
||||
)
|
||||
enum_class = enum.typename.classkey == "enum class"
|
||||
for value in enum.values:
|
||||
enum_class_qualifier = f"{basename}::" * enum_class
|
||||
print(
|
||||
f'\t\t\t.value("{value.name}", {enum_class_qualifier}{value.name})',
|
||||
file=self.f,
|
||||
)
|
||||
print(f"\t\t\t.finalize();}}", file=self.f)
|
||||
|
||||
def process_namespace(
|
||||
self,
|
||||
header: PyosysHeader,
|
||||
ns: NamespaceScope,
|
||||
namespace_components: Tuple[str, ...] = (),
|
||||
):
|
||||
for name, subns in ns.namespaces.items():
|
||||
self.process_namespace(header, subns, (*namespace_components, name))
|
||||
if len(namespace_components) and (len(ns.functions) + len(ns.variables)):
|
||||
# TODO: Not essential but maybe move namespace usage into
|
||||
# process_function for consistency?
|
||||
print(
|
||||
f"\t\t{{ using namespace {'::'.join(namespace_components)};",
|
||||
file=self.f,
|
||||
)
|
||||
for function in ns.functions:
|
||||
self.process_function(function)
|
||||
for variable in ns.variables:
|
||||
self.process_variable(variable)
|
||||
print(f"\t\t}}", file=self.f)
|
||||
for enum in ns.enums:
|
||||
self.process_enum(enum, namespace_components)
|
||||
for cls in ns.classes:
|
||||
pqname = cls.class_decl.typename
|
||||
declared_name_str = pqname.segments[-1].format()
|
||||
if cls_metadata := header.classes_by_name.get(declared_name_str):
|
||||
self.process_class(cls_metadata, cls, namespace_components)
|
||||
|
||||
|
||||
keyword_aliases = {
|
||||
"False": "False_",
|
||||
"None": "None_",
|
||||
"True": "True_",
|
||||
"and": "and_",
|
||||
"as": "as_",
|
||||
"assert": "assert_",
|
||||
"break": "break_",
|
||||
"class": "class_",
|
||||
"continue": "continue_",
|
||||
"def": "def_",
|
||||
"del": "del_",
|
||||
"elif": "elif_",
|
||||
"else": "else_",
|
||||
"except": "except_",
|
||||
"for": "for_",
|
||||
"from": "from_",
|
||||
"global": "global_",
|
||||
"if": "if_",
|
||||
"import": "import_",
|
||||
"in": "in_",
|
||||
"is": "is_",
|
||||
"lambda": "lambda_",
|
||||
"nonlocal": "nonlocal_",
|
||||
"not": "not_",
|
||||
"or": "or_",
|
||||
"pass": "pass_",
|
||||
"raise": "raise_",
|
||||
"return": "return_",
|
||||
"try": "try_",
|
||||
"while": "while_",
|
||||
"with": "with_",
|
||||
"yield": "yield_",
|
||||
}
|
||||
|
||||
|
||||
def print_includes():
|
||||
for header in pyosys_headers:
|
||||
print(header.name)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--debug", default=0, type=int)
|
||||
group = ap.add_mutually_exclusive_group(required=True)
|
||||
group.add_argument("--print-includes", action="store_true")
|
||||
group.add_argument("output", nargs="?")
|
||||
ns = ap.parse_args()
|
||||
if ns.print_includes:
|
||||
print_includes()
|
||||
exit(0)
|
||||
|
||||
out_path = Path(ns.output)
|
||||
out_inc = out_path.parent / (out_path.stem + ".inc.cc")
|
||||
with open(out_path, "w", encoding="utf8") as f, open(
|
||||
out_inc, "w", encoding="utf8"
|
||||
) as inc_f:
|
||||
generator = PyosysWrapperGenerator(
|
||||
from_headers=pyosys_headers, wrapper_stream=f, header_stream=inc_f
|
||||
)
|
||||
generator.generate()
|
529
pyosys/hashlib.h
Normal file
529
pyosys/hashlib.h
Normal file
|
@ -0,0 +1,529 @@
|
|||
// -------------------------------------------------------
|
||||
// Written by Mohamed Gaber in 2025 <me@donn.website>
|
||||
// Based on kernel/hashlib.h by Claire Xenia Wolf <claire@yosyshq.com>
|
||||
// -------------------------------------------------------
|
||||
// This header is free and unencumbered software released into the public domain.
|
||||
//
|
||||
// Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
// distribute this software, either in source code form or as a compiled
|
||||
// binary, for any purpose, commercial or non-commercial, and by any
|
||||
// means.
|
||||
//
|
||||
// In jurisdictions that recognize copyright laws, the author or authors
|
||||
// of this software dedicate any and all copyright interest in the
|
||||
// software to the public domain. We make this dedication for the benefit
|
||||
// of the public at large and to the detriment of our heirs and
|
||||
// successors. We intend this dedication to be an overt act of
|
||||
// relinquishment in perpetuity of all present and future rights to this
|
||||
// software under copyright law.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
// OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
// For more information, please refer to <https://unlicense.org/>
|
||||
// -------------------------------------------------------
|
||||
//
|
||||
// pybind11 bridging headers for hashlib template
|
||||
//
|
||||
// These are various binding functions that expose hashlib templates as opaque
|
||||
// types (https://pybind11.readthedocs.io/en/latest/advanced/cast/stl.html#making-opaque-types).
|
||||
//
|
||||
// Opaque types cross language barries by reference, not value. This allows
|
||||
// things like mutating containers that are class properties.
|
||||
//
|
||||
// All methods should be vaguely in the same order as the python reference
|
||||
// https://docs.python.org/3.13/library/stdtypes.html
|
||||
//
|
||||
#include <optional> // optional maps cleanest to methods that accept None in Python
|
||||
|
||||
#include <pybind11/stl.h> // std::optional
|
||||
#include <pybind11/pybind11.h> // base
|
||||
#include <pybind11/operators.h> // easier operator binding
|
||||
#include <pybind11/stl_bind.h> // vector
|
||||
|
||||
#include "kernel/hashlib.h"
|
||||
|
||||
namespace pybind11 {
|
||||
namespace hashlib {
|
||||
|
||||
// "traits"
|
||||
template <typename T> struct is_pointer: std::false_type {};
|
||||
template <typename T> struct is_pointer<T*>: std::true_type {};
|
||||
template <typename T> struct is_optional: std::false_type {};
|
||||
template <typename T> struct is_optional< std::optional<T> >: std::true_type {};
|
||||
|
||||
bool is_mapping(object obj) {
|
||||
object mapping = module_::import("collections.abc").attr("Mapping");
|
||||
return isinstance(obj, mapping);
|
||||
}
|
||||
|
||||
// Set Operations
|
||||
bool is_subset(const iterable &lhs, const iterable &rhs, bool strict = false) {
|
||||
for (auto &element: lhs) {
|
||||
if (!rhs.contains(element)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (strict) {
|
||||
return len(rhs) > len(lhs);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename C, typename T>
|
||||
void unionize(C &lhs, const iterable &rhs) {
|
||||
for (auto &element: rhs) {
|
||||
lhs.insert(cast<T>(element));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename C, typename T>
|
||||
void difference(C &lhs, const iterable &rhs) {
|
||||
for (auto &element: rhs) {
|
||||
auto element_cxx = cast<T>(element);
|
||||
if (lhs.count(element_cxx)) {
|
||||
lhs.erase(element_cxx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename C, typename T>
|
||||
void intersect(C &lhs, const iterable &rhs) {
|
||||
// Doing it in-place is a lot slower
|
||||
// TODO?: Leave modifying lhs to caller (saves a copy in some cases)
|
||||
// but complicates chaining intersections.
|
||||
C storage(lhs);
|
||||
|
||||
for (auto &element_cxx: lhs) {
|
||||
if (!rhs.contains(cast(element_cxx))) {
|
||||
storage.erase(element_cxx);
|
||||
}
|
||||
}
|
||||
lhs = std::move(storage);
|
||||
}
|
||||
|
||||
template <typename C, typename T>
|
||||
void symmetric_difference(C &lhs, const iterable &rhs) {
|
||||
C storage(lhs);
|
||||
|
||||
for (auto &element: rhs) {
|
||||
auto element_cxx = cast<T>(element);
|
||||
if (lhs.count(element_cxx)) {
|
||||
storage.erase(element_cxx);
|
||||
} else {
|
||||
storage.insert(element_cxx);
|
||||
}
|
||||
}
|
||||
for (auto &element_cxx: lhs) {
|
||||
if (rhs.contains(cast(element_cxx))) {
|
||||
storage.erase(element_cxx);
|
||||
}
|
||||
}
|
||||
lhs = std::move(storage);
|
||||
}
|
||||
|
||||
// shim
|
||||
template <typename C, typename V>
|
||||
void bind_vector(module &m, const char *name_cstr) {
|
||||
pybind11::bind_vector<C>(m, name_cstr);
|
||||
}
|
||||
|
||||
// also used for hashlib pool because the semantics are close enough
|
||||
template <typename C, typename T>
|
||||
void bind_set(module &m, const char *name_cstr) {
|
||||
class_<C>(m, name_cstr)
|
||||
.def(init<>())
|
||||
.def(init<const C &>()) // copy constructor
|
||||
.def(init([](const iterable &other){ // copy instructor from arbitrary iterables
|
||||
auto s = new C();
|
||||
unionize<C, T>(*s, other);
|
||||
return s;
|
||||
}))
|
||||
.def("__len__", [](const C &s){ return (size_t)s.size(); })
|
||||
.def("__contains__", [](const C &s, const T &v){ return s.count(v); })
|
||||
.def("__delitem__", [](C &s, const T &v) {
|
||||
auto n = s.erase(v);
|
||||
if (n == 0) throw key_error(str(cast(v)));
|
||||
})
|
||||
.def("disjoint", [](const C &s, const iterable &other) {
|
||||
for (const auto &element: other) {
|
||||
if (s.count(cast<T>(element))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.def("issubset", [](const iterable &s, const iterable &other) {
|
||||
return is_subset(s, other);
|
||||
})
|
||||
.def("__eq__", [](const iterable &s, const iterable &other) {
|
||||
return is_subset(s, other) && len(s) == len(other);
|
||||
})
|
||||
.def("__le__", [](const iterable &s, const iterable &other) {
|
||||
return is_subset(s, other);
|
||||
})
|
||||
.def("__lt__", [](const iterable &s, const iterable &other) {
|
||||
return is_subset(s, other, true);
|
||||
})
|
||||
.def("issuperset", [](const iterable &s, const iterable &other) {
|
||||
return is_subset(other, s);
|
||||
})
|
||||
.def("__ge__", [](const iterable &s, const iterable &other) {
|
||||
return is_subset(other, s);
|
||||
})
|
||||
.def("__gt__", [](const iterable &s, const iterable &other) {
|
||||
return is_subset(other, s, true);
|
||||
})
|
||||
.def("union", [](const C &s, const args &others) {
|
||||
auto result = new C(s);
|
||||
for (const auto &arg: others) {
|
||||
auto arg_iterable = reinterpret_borrow<iterable>(arg);
|
||||
unionize<C, T>(*result, arg_iterable);
|
||||
}
|
||||
return result;
|
||||
})
|
||||
.def("__or__", [](const C &s, const iterable &other) {
|
||||
auto result = new C(s);
|
||||
unionize<C, T>(*result, other);
|
||||
return result;
|
||||
})
|
||||
.def("__ior__", [](C &s, const iterable &other) {
|
||||
unionize<C, T>(s, other);
|
||||
return s;
|
||||
})
|
||||
.def("intersection", [](const C &s, const args &others) {
|
||||
auto result = new C(s);
|
||||
for (const auto &arg: others) {
|
||||
auto arg_iterable = reinterpret_borrow<iterable>(arg);
|
||||
intersect<C, T>(*result, arg_iterable);
|
||||
}
|
||||
return result;
|
||||
})
|
||||
.def("__and__", [](const C &s, const iterable &other) {
|
||||
auto result = new C(s);
|
||||
intersect<C, T>(*result, other);
|
||||
return result;
|
||||
})
|
||||
.def("__iand__", [](C &s, const iterable &other) {
|
||||
intersect<C, T>(s, other);
|
||||
return s;
|
||||
})
|
||||
.def("difference", [](const C &s, const args &others) {
|
||||
auto result = new C(s);
|
||||
for (const auto &arg: others) {
|
||||
auto arg_iterable = reinterpret_borrow<iterable>(arg);
|
||||
difference<C, T>(*result, arg_iterable);
|
||||
}
|
||||
return result;
|
||||
})
|
||||
.def("__sub__", [](const C &s, const iterable &other) {
|
||||
auto result = new C(s);
|
||||
difference<C, T>(*result, other);
|
||||
return result;
|
||||
})
|
||||
.def("__isub__", [](C &s, const iterable &other) {
|
||||
difference<C, T>(s, other);
|
||||
return s;
|
||||
})
|
||||
.def("symmetric_difference", [](const C &s, const iterable &other) {
|
||||
auto result = new C(s);
|
||||
symmetric_difference<C, T>(*result, other);
|
||||
return result;
|
||||
})
|
||||
.def("__xor__", [](const C &s, const iterable &other) {
|
||||
auto result = new C(s);
|
||||
symmetric_difference<C, T>(*result, other);
|
||||
return result;
|
||||
})
|
||||
.def("__ixor__", [](C &s, const iterable &other) {
|
||||
symmetric_difference<C, T>(s, other);
|
||||
return s;
|
||||
})
|
||||
.def("copy", [](const C &s) {
|
||||
return new C(s);
|
||||
})
|
||||
.def("update", [](C &s, iterable iterable) {
|
||||
for (auto item: iterable) {
|
||||
s.insert(item.cast<T>());
|
||||
}
|
||||
})
|
||||
.def("add", [](C &s, const T &v){ s.insert(v); })
|
||||
.def("remove", [](C &s, const T &v){
|
||||
auto n = s.erase(v);
|
||||
if (n == 0) throw key_error(str(cast(v)));
|
||||
})
|
||||
.def("discard", [](C &s, const T &v){ s.erase(v); })
|
||||
.def("clear", [](C &s){ s.clear(); })
|
||||
.def("pop", [](C &s){
|
||||
if (s.size() == 0) {
|
||||
throw key_error("empty pool");
|
||||
}
|
||||
auto result = *s.begin();
|
||||
s.erase(result);
|
||||
return result;
|
||||
})
|
||||
.def("__bool__", [](const C &s) { return s.size() != 0; })
|
||||
.def("__iter__", [](const C &s){
|
||||
return make_iterator(s.begin(), s.end());
|
||||
}, keep_alive<0,1>())
|
||||
.def("__eq__", [](const C &s, const C &other) { return s == other; })
|
||||
.def("__eq__", [](const C &s, const iterable &other) {
|
||||
C other_cast;
|
||||
unionize<C, T>(other_cast, other);
|
||||
return s == other_cast;
|
||||
})
|
||||
.def("__repr__", [name_cstr](const iterable &s){
|
||||
// repr(set(s)) where s is iterable would be more terse/robust
|
||||
// but are there concerns with copying?
|
||||
str representation = str(name_cstr) + str("({");
|
||||
str comma(", ");
|
||||
for (const auto &element: s) {
|
||||
representation += repr(element);
|
||||
representation += comma; // python supports trailing commas
|
||||
}
|
||||
representation += str("})");
|
||||
return representation;
|
||||
});
|
||||
}
|
||||
|
||||
// shim
|
||||
template <typename C, typename T>
|
||||
void bind_pool(module &m, const char *name_cstr) {
|
||||
bind_set<C, T>(m, name_cstr);
|
||||
}
|
||||
|
||||
|
||||
template <typename C, typename K, typename V>
|
||||
void update_dict(C &target, const iterable &iterable_or_mapping) {
|
||||
if (is_mapping(iterable_or_mapping)) {
|
||||
for (const auto &key: iterable_or_mapping) {
|
||||
target[cast<K>(key)] = cast<V>(iterable_or_mapping[key]);
|
||||
}
|
||||
} else {
|
||||
for (const auto &pair: iterable_or_mapping) {
|
||||
if (len(pair) != 2) {
|
||||
throw value_error(str("iterable element %s has more than two elements").format(str(pair)));
|
||||
}
|
||||
target[cast<K>(pair[cast(0)])] = cast<V>(pair[cast(1)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename C, typename K, typename V>
|
||||
void bind_dict(module &m, const char *name_cstr) {
|
||||
auto cls = class_<C>(m, name_cstr)
|
||||
.def(init<>())
|
||||
.def(init<const C &>()) // copy constructor
|
||||
.def(init([](const iterable &other){ // copy instructor from arbitrary iterables and mappings
|
||||
auto s = new C();
|
||||
update_dict<C, K, V>(*s, other);
|
||||
return s;
|
||||
}))
|
||||
.def("__len__", [](const C &s){ return (size_t)s.size(); })
|
||||
.def("__getitem__", [](const C &s, const K &k) { return s.at(k); })
|
||||
.def("__setitem__", [](C &s, const K &k, const V &v) { s[k] = v; })
|
||||
.def("__delitem__", [](C &s, const K &k) {
|
||||
auto n = s.erase(k);
|
||||
if (n == 0) throw key_error("remove: key not found");
|
||||
})
|
||||
.def("__contains__", [](const C &s, const K &k) { return s.count(k) != 0; })
|
||||
.def("__iter__", [](const C &s){
|
||||
return make_key_iterator(s.begin(), s.end());
|
||||
}, keep_alive<0,1>())
|
||||
.def("clear", [](C &s){ s.clear(); })
|
||||
.def("copy", [](const C &s) {
|
||||
return new C(s);
|
||||
})
|
||||
.def("get", [](const C &s, const K& k, std::optional<const V> &default_) {
|
||||
if (default_.has_value()) {
|
||||
return s.at(k, *default_);
|
||||
} else {
|
||||
return s.at(k);
|
||||
}
|
||||
}, arg("key"), arg("default") = std::nullopt)
|
||||
.def("items", [](const C &s){
|
||||
return make_iterator(s.begin(), s.end());
|
||||
}, keep_alive<0,1>())
|
||||
.def("keys", [](const C &s){
|
||||
return make_key_iterator(s.begin(), s.end());
|
||||
}, keep_alive<0,1>())
|
||||
.def("pop", [](const C &s, const K& k, std::optional<const V> &default_) {
|
||||
if (default_.has_value()) {
|
||||
return s.at(k, *default_);
|
||||
} else {
|
||||
return s.at(k);
|
||||
}
|
||||
}, arg("key"), arg("default") = std::nullopt)
|
||||
.def("popitem", [](C &s) {
|
||||
auto it = s.begin();
|
||||
if (it == s.end()) {
|
||||
throw key_error("dict is empty");
|
||||
}
|
||||
auto copy = *it;
|
||||
s.erase(it);
|
||||
return copy;
|
||||
})
|
||||
.def("setdefault", [name_cstr](C &s, const K& k, std::optional<const V> &default_) {
|
||||
auto it = s.find(k);
|
||||
if (it != s.end()) {
|
||||
return it->second;
|
||||
}
|
||||
if (default_.has_value()) {
|
||||
s[k] = *default_;
|
||||
return *default_;
|
||||
}
|
||||
// if pointer, nullptr can be our default
|
||||
if constexpr (is_pointer<V>::value) {
|
||||
s[k] = nullptr;
|
||||
return (V)nullptr;
|
||||
}
|
||||
if constexpr (is_optional<V>::value) {
|
||||
s[k] = std::nullopt;
|
||||
return std::nullopt;
|
||||
}
|
||||
throw type_error(std::string("the value type of ") + name_cstr + " is not nullable");
|
||||
}, arg("key"), arg("default") = std::nullopt)
|
||||
.def("update", [](C &s, iterable iterable_or_mapping) {
|
||||
update_dict<C, K, V>(s, iterable_or_mapping);
|
||||
}, arg("iterable_or_mapping"))
|
||||
.def("values", [](const C &s){
|
||||
return make_value_iterator(s.begin(), s.end());
|
||||
}, keep_alive<0,1>())
|
||||
.def("__or__", [](const C &s, iterable iterable_or_mapping) {
|
||||
auto result = new C(s);
|
||||
update_dict<C, K, V>(*result, iterable_or_mapping);
|
||||
return result;
|
||||
})
|
||||
.def("__ior__", [](C &s, iterable iterable_or_mapping) {
|
||||
update_dict<C, K, V>(s, iterable_or_mapping);
|
||||
return s;
|
||||
})
|
||||
.def("__bool__", [](const C &s) { return s.size() != 0; })
|
||||
.def("__repr__", [name_cstr](const C &s) {
|
||||
// repr(dict(s)) where s is iterable would be more terse/robust
|
||||
// but are there concerns with copying?
|
||||
str representation = str(name_cstr) + str("({");
|
||||
str colon(": ");
|
||||
str comma(", ");
|
||||
for (const auto &item: s) {
|
||||
representation += repr(cast(item.first));
|
||||
representation += colon;
|
||||
representation += repr(cast(item.second));
|
||||
representation += comma; // python supports trailing commas
|
||||
}
|
||||
representation += str("})");
|
||||
return representation;
|
||||
});
|
||||
|
||||
// K is always comparable
|
||||
// Python implements `is` as a fallback to check if it's the same object
|
||||
if constexpr (detail::is_comparable<V>::value) {
|
||||
cls.def("__eq__", [](const C &s, const C &other) { return s == other; });
|
||||
cls.def("__eq__", [](const C &s, const iterable &other) {
|
||||
C other_cast;
|
||||
update_dict<C, K, V>(other_cast, other);
|
||||
return s == other_cast;
|
||||
});
|
||||
}
|
||||
|
||||
// Inherit from collections.abc.Mapping so update operators (and a bunch
|
||||
// of other things) work.
|
||||
auto collections_abc = module_::import("collections.abc");
|
||||
auto mapping = getattr(collections_abc, "Mapping");
|
||||
auto current_bases = list(getattr(cls, "__bases__"));
|
||||
current_bases.append(mapping);
|
||||
setattr(cls, "__bases__", tuple(current_bases));
|
||||
}
|
||||
|
||||
// idict is a special bijection and doesn't map cleanly to dict
|
||||
//
|
||||
// it's cleanest, despite the inconsistency with __getitem__, to just think of
|
||||
// the hashable as key and the integer as value
|
||||
template <typename C, typename K>
|
||||
void bind_idict(module &m, const char *name_cstr) {
|
||||
auto cls = class_<C>(m, name_cstr)
|
||||
.def(init<>())
|
||||
.def(init<const C &>()) // copy constructor
|
||||
.def(init([](const iterable &other){ // copy instructor from arbitrary iterables
|
||||
auto s = new C();
|
||||
for (auto &e: other) {
|
||||
(*s)(cast<K>(e));
|
||||
}
|
||||
return s;
|
||||
}))
|
||||
.def("__len__", [](const C &s){ return (size_t)s.size(); })
|
||||
.def("__getitem__", [](const C &s, int v) { return s[v]; })
|
||||
.def("__call__", [](C &s, const K &k) { return s(k); })
|
||||
.def("__contains__", [](const C &s, const K &k) {
|
||||
return s.count(k) != 0;
|
||||
})
|
||||
.def("__iter__", [](const C &s){
|
||||
return make_iterator(s.begin(), s.end());
|
||||
}, keep_alive<0,1>())
|
||||
.def("clear", [](C &s) {
|
||||
s.clear();
|
||||
})
|
||||
.def("copy", [](const C &s) {
|
||||
return new C(s);
|
||||
})
|
||||
.def("get", [](const C &s, const K& k, std::optional<int> &default_) {
|
||||
if (default_.has_value()) {
|
||||
return s.at(k, *default_);
|
||||
} else {
|
||||
return s.at(k);
|
||||
}
|
||||
}, arg("key"), arg("default") = std::nullopt)
|
||||
.def("keys", [](const C &s){
|
||||
return make_iterator(s.begin(), s.end());
|
||||
})
|
||||
.def("values", [](args _){
|
||||
throw type_error("idicts do not support iteration on the integers");
|
||||
})
|
||||
.def("items", [](args _){
|
||||
throw type_error("idicts do not support pairwise iteration");
|
||||
})
|
||||
.def("update", [](C &s, iterable other) {
|
||||
for (auto &e: other) {
|
||||
s(cast<K>(e));
|
||||
}
|
||||
})
|
||||
.def("__or__", [](const C &s, iterable other) {
|
||||
auto result = new C(s);
|
||||
for (auto &e: other) {
|
||||
(*result)(cast<K>(e));
|
||||
}
|
||||
return result;
|
||||
})
|
||||
.def("__ior__", [](C &s, iterable other) {
|
||||
for (auto &e: other) {
|
||||
s(cast<K>(e));
|
||||
}
|
||||
return s;
|
||||
})
|
||||
.def("__bool__", [](const C &s) { return s.size() != 0; })
|
||||
.def("__repr__", [name_cstr](const C &s){
|
||||
// repr(dict(s)) where s is iterable would be more terse/robust
|
||||
// but are there concerns with copying?
|
||||
str representation = str(name_cstr) + str("() | {");
|
||||
str comma(", ");
|
||||
for (const auto &item: s) {
|
||||
representation += repr(cast(item));
|
||||
representation += comma; // python supports trailing commas
|
||||
}
|
||||
representation += str("}");
|
||||
return representation;
|
||||
});
|
||||
|
||||
for (const char *mutator: {"__setitem__", "__delitem__", "pop", "popitem", "setdefault"}) {
|
||||
cls.def(mutator, [](args _) {
|
||||
throw type_error("idicts do not support arbitrary element mutation");
|
||||
});
|
||||
}
|
||||
}
|
||||
}; // namespace hashlib
|
||||
}; // namespace pybind11
|
257
pyosys/wrappers_tpl.cc
Normal file
257
pyosys/wrappers_tpl.cc
Normal file
|
@ -0,0 +1,257 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifdef YOSYS_ENABLE_PYTHON
|
||||
|
||||
// <!-- generated includes -->
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/native_enum.h>
|
||||
#include "pyosys/hashlib.h"
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
USING_YOSYS_NAMESPACE
|
||||
|
||||
using std::set;
|
||||
using std::regex;
|
||||
using std::ostream;
|
||||
using namespace RTLIL;
|
||||
|
||||
#include "wrappers.inc.cc"
|
||||
|
||||
namespace pyosys {
|
||||
struct Globals {};
|
||||
|
||||
// Trampolines for Classes with Python-Overridable Virtual Methods
|
||||
// https://pybind11.readthedocs.io/en/stable/advanced/classes.html#overriding-virtual-functions-in-python
|
||||
class PassTrampoline : public Pass {
|
||||
public:
|
||||
using Pass::Pass;
|
||||
|
||||
void help() override {
|
||||
PYBIND11_OVERRIDE(void, Pass, help);
|
||||
}
|
||||
|
||||
bool formatted_help() override {
|
||||
PYBIND11_OVERRIDE(bool, Pass, formatted_help);
|
||||
}
|
||||
|
||||
void clear_flags() override {
|
||||
PYBIND11_OVERRIDE(void, Pass, clear_flags);
|
||||
}
|
||||
|
||||
void execute(std::vector<std::string> args, RTLIL::Design *design) override {
|
||||
PYBIND11_OVERRIDE_PURE(
|
||||
void,
|
||||
Pass,
|
||||
execute,
|
||||
args,
|
||||
design
|
||||
);
|
||||
}
|
||||
|
||||
void on_register() override {
|
||||
PYBIND11_OVERRIDE(void, Pass, on_register);
|
||||
}
|
||||
|
||||
void on_shutdown() override {
|
||||
PYBIND11_OVERRIDE(void, Pass, on_shutdown);
|
||||
}
|
||||
|
||||
bool replace_existing_pass() const override {
|
||||
PYBIND11_OVERRIDE(
|
||||
bool,
|
||||
Pass,
|
||||
replace_existing_pass
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
class MonitorTrampoline : public RTLIL::Monitor {
|
||||
public:
|
||||
using RTLIL::Monitor::Monitor;
|
||||
|
||||
void notify_module_add(RTLIL::Module *module) override {
|
||||
PYBIND11_OVERRIDE(
|
||||
void,
|
||||
RTLIL::Monitor,
|
||||
notify_module_add,
|
||||
module
|
||||
);
|
||||
}
|
||||
|
||||
void notify_module_del(RTLIL::Module *module) override {
|
||||
PYBIND11_OVERRIDE(
|
||||
void,
|
||||
RTLIL::Monitor,
|
||||
notify_module_del,
|
||||
module
|
||||
);
|
||||
}
|
||||
|
||||
void notify_connect(
|
||||
RTLIL::Cell *cell,
|
||||
const RTLIL::IdString &port,
|
||||
const RTLIL::SigSpec &old_sig,
|
||||
const RTLIL::SigSpec &sig
|
||||
) override {
|
||||
PYBIND11_OVERRIDE(
|
||||
void,
|
||||
RTLIL::Monitor,
|
||||
notify_connect,
|
||||
cell,
|
||||
port,
|
||||
old_sig,
|
||||
sig
|
||||
);
|
||||
}
|
||||
|
||||
void notify_connect(
|
||||
RTLIL::Module *module,
|
||||
const RTLIL::SigSig &sigsig
|
||||
) override {
|
||||
PYBIND11_OVERRIDE(
|
||||
void,
|
||||
RTLIL::Monitor,
|
||||
notify_connect,
|
||||
module,
|
||||
sigsig
|
||||
);
|
||||
}
|
||||
|
||||
void notify_connect(
|
||||
RTLIL::Module *module,
|
||||
const std::vector<RTLIL::SigSig> &sigsig_vec
|
||||
) override {
|
||||
PYBIND11_OVERRIDE(
|
||||
void,
|
||||
RTLIL::Monitor,
|
||||
notify_connect,
|
||||
module,
|
||||
sigsig_vec
|
||||
);
|
||||
}
|
||||
|
||||
void notify_blackout(
|
||||
RTLIL::Module *module
|
||||
) override {
|
||||
PYBIND11_OVERRIDE(
|
||||
void,
|
||||
RTLIL::Monitor,
|
||||
notify_blackout,
|
||||
module
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
PYBIND11_MODULE(libyosys, m) {
|
||||
// this code is run on import
|
||||
m.doc() = "python access to libyosys";
|
||||
|
||||
if (!yosys_already_setup()) {
|
||||
log_streams.push_back(&std::cout);
|
||||
log_error_stderr = true;
|
||||
yosys_setup();
|
||||
|
||||
// Cleanup
|
||||
m.add_object("_cleanup_handle", py::capsule([](){
|
||||
yosys_shutdown();
|
||||
}));
|
||||
}
|
||||
|
||||
// Logging Methods
|
||||
m.def("log_header", [](Design *d, std::string s) { log_formatted_header(d, "%s", s); });
|
||||
m.def("log", [](std::string s) { log_formatted_string("%s", s); });
|
||||
m.def("log_file_info", [](std::string_view file, int line, std::string s) { log_formatted_file_info(file, line, s); });
|
||||
m.def("log_warning", [](std::string s) { log_formatted_warning("Warning: ", s); });
|
||||
m.def("log_warning_noprefix", [](std::string s) { log_formatted_warning("", s); });
|
||||
m.def("log_file_warning", [](std::string_view file, int line, std::string s) { log_formatted_file_warning(file, line, s); });
|
||||
m.def("log_error", [](std::string s) { log_formatted_error(s); });
|
||||
m.def("log_file_error", [](std::string_view file, int line, std::string s) { log_formatted_file_error(file, line, s); });
|
||||
|
||||
// Namespace to host global objects
|
||||
auto global_variables = py::class_<Globals>(m, "Globals");
|
||||
|
||||
// Trampoline Classes
|
||||
py::class_<Pass, pyosys::PassTrampoline, std::unique_ptr<Pass, py::nodelete>>(m, "Pass")
|
||||
.def(py::init([](std::string name, std::string short_help) {
|
||||
auto created = new pyosys::PassTrampoline(name, short_help);
|
||||
Pass::init_register();
|
||||
return created;
|
||||
}), py::arg("name"), py::arg("short_help"))
|
||||
.def("help", &Pass::help)
|
||||
.def("formatted_help", &Pass::formatted_help)
|
||||
.def("execute", &Pass::execute)
|
||||
.def("clear_flags", &Pass::clear_flags)
|
||||
.def("on_register", &Pass::on_register)
|
||||
.def("on_shutdown", &Pass::on_shutdown)
|
||||
.def("replace_existing_pass", &Pass::replace_existing_pass)
|
||||
.def("experimental", &Pass::experimental)
|
||||
.def("internal", &Pass::internal)
|
||||
.def("pre_execute", &Pass::pre_execute)
|
||||
.def("post_execute", &Pass::post_execute)
|
||||
.def("cmd_log_args", &Pass::cmd_log_args)
|
||||
.def("cmd_error", &Pass::cmd_error)
|
||||
.def("extra_args", &Pass::extra_args)
|
||||
.def("call", py::overload_cast<RTLIL::Design *,std::string>(&Pass::call))
|
||||
.def("call", py::overload_cast<RTLIL::Design *,std::vector<std::string>>(&Pass::call))
|
||||
;
|
||||
|
||||
py::class_<RTLIL::Monitor, pyosys::MonitorTrampoline>(m, "Monitor")
|
||||
.def(py::init([]() {
|
||||
return new pyosys::MonitorTrampoline();
|
||||
}))
|
||||
.def("notify_module_add", &RTLIL::Monitor::notify_module_add)
|
||||
.def("notify_module_del", &RTLIL::Monitor::notify_module_del)
|
||||
.def(
|
||||
"notify_connect",
|
||||
py::overload_cast<
|
||||
RTLIL::Cell *,
|
||||
const RTLIL::IdString &,
|
||||
const RTLIL::SigSpec &,
|
||||
const RTLIL::SigSpec &
|
||||
>(&RTLIL::Monitor::notify_connect)
|
||||
)
|
||||
.def(
|
||||
"notify_connect",
|
||||
py::overload_cast<
|
||||
RTLIL::Module *,
|
||||
const RTLIL::SigSig &
|
||||
>(&RTLIL::Monitor::notify_connect)
|
||||
)
|
||||
.def(
|
||||
"notify_connect",
|
||||
py::overload_cast<
|
||||
RTLIL::Module *,
|
||||
const std::vector<RTLIL::SigSig> &
|
||||
>(&RTLIL::Monitor::notify_connect)
|
||||
)
|
||||
.def("notify_blackout", &RTLIL::Monitor::notify_blackout)
|
||||
;
|
||||
|
||||
// Bind Opaque Containers
|
||||
bind_autogenerated_opaque_containers(m);
|
||||
|
||||
// <!-- generated pymod-level code -->
|
||||
|
||||
py::implicitly_convertible<std::string, RTLIL::IdString>();
|
||||
py::implicitly_convertible<const char *, RTLIL::IdString>();
|
||||
};
|
||||
};
|
||||
|
||||
#endif // YOSYS_ENABLE_PYTHON
|
11
pyproject.toml
Normal file
11
pyproject.toml
Normal file
|
@ -0,0 +1,11 @@
|
|||
[build-system]
|
||||
requires = [
|
||||
"setuptools>=42",
|
||||
"pybind11>=3,<4",
|
||||
"cxxheaderparser",
|
||||
]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.ruff]
|
||||
target-version = "py38"
|
||||
lint.ignore = ["F541"]
|
43
setup.py
43
setup.py
|
@ -16,18 +16,19 @@ import os
|
|||
import re
|
||||
import shlex
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from setuptools import setup, Extension
|
||||
from setuptools.command.build_ext import build_ext
|
||||
|
||||
__dir__ = os.path.dirname(os.path.abspath(__file__))
|
||||
import pybind11
|
||||
from pybind11.setup_helpers import build_ext
|
||||
|
||||
__yosys_root__ = Path(__file__).parent
|
||||
|
||||
yosys_version_rx = re.compile(r"YOSYS_VER\s*:=\s*([\w\-\+\.]+)")
|
||||
|
||||
version = yosys_version_rx.search(
|
||||
open(os.path.join(__dir__, "Makefile"), encoding="utf8").read()
|
||||
)[1].replace(
|
||||
"+", "."
|
||||
) # Convert to patch version
|
||||
with open(__yosys_root__ / "Makefile", encoding="utf8") as f:
|
||||
# Extract and convert + to patch version
|
||||
version = yosys_version_rx.search(f.read())[1].replace("+", ".")
|
||||
|
||||
|
||||
class libyosys_so_ext(Extension):
|
||||
|
@ -38,7 +39,13 @@ class libyosys_so_ext(Extension):
|
|||
"libyosys.so",
|
||||
[],
|
||||
)
|
||||
|
||||
# when iterating locally, you probably want to set this variable
|
||||
# to avoid mass rebuilds bec of pybind11's include path changing
|
||||
pybind_include = os.getenv("_FORCE_PYBIND_INCLUDE", pybind11.get_include())
|
||||
|
||||
self.args = [
|
||||
f"PYBIND11_INCLUDE={pybind_include}",
|
||||
"ENABLE_PYOSYS=1",
|
||||
# Would need to be installed separately by the user
|
||||
"ENABLE_TCL=0",
|
||||
|
@ -73,22 +80,23 @@ class libyosys_so_ext(Extension):
|
|||
*self.args,
|
||||
]
|
||||
)
|
||||
build_path = os.path.dirname(os.path.dirname(bext.get_ext_fullpath(self.name)))
|
||||
pyosys_path = os.path.join(build_path, "pyosys")
|
||||
ext_fullpath = Path(bext.get_ext_fullpath(self.name))
|
||||
build_path = ext_fullpath.parents[1]
|
||||
pyosys_path = build_path / "pyosys"
|
||||
os.makedirs(pyosys_path, exist_ok=True)
|
||||
|
||||
# libyosys.so
|
||||
target = os.path.join(pyosys_path, os.path.basename(self.name))
|
||||
target = pyosys_path / self.name
|
||||
shutil.copy(self.name, target)
|
||||
bext.spawn(["strip", "-S", target])
|
||||
bext.spawn(["strip", "-S", str(target)])
|
||||
|
||||
# yosys-abc
|
||||
yosys_abc_target = os.path.join(pyosys_path, "yosys-abc")
|
||||
yosys_abc_target = pyosys_path / "yosys-abc"
|
||||
shutil.copy("yosys-abc", yosys_abc_target)
|
||||
bext.spawn(["strip", "-S", yosys_abc_target])
|
||||
bext.spawn(["strip", "-S", str(yosys_abc_target)])
|
||||
|
||||
# share directory
|
||||
share_target = os.path.join(pyosys_path, "share")
|
||||
share_target = pyosys_path / "share"
|
||||
try:
|
||||
shutil.rmtree(share_target)
|
||||
except FileNotFoundError:
|
||||
|
@ -104,14 +112,16 @@ class custom_build_ext(build_ext):
|
|||
return ext.custom_build(self)
|
||||
|
||||
|
||||
with open(__yosys_root__ / "README.md", encoding="utf8") as f:
|
||||
long_description = f.read()
|
||||
|
||||
setup(
|
||||
name="pyosys",
|
||||
packages=["pyosys"],
|
||||
version=version,
|
||||
description="Python access to libyosys",
|
||||
long_description=open(os.path.join(__dir__, "README.md")).read(),
|
||||
long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
install_requires=["wheel", "setuptools"],
|
||||
license="MIT",
|
||||
classifiers=[
|
||||
"Programming Language :: Python :: 3",
|
||||
|
@ -119,7 +129,6 @@ setup(
|
|||
"Operating System :: POSIX :: Linux",
|
||||
"Operating System :: MacOS :: MacOS X",
|
||||
],
|
||||
package_dir={"pyosys": "misc"},
|
||||
python_requires=">=3.8",
|
||||
ext_modules=[libyosys_so_ext()],
|
||||
cmdclass={
|
||||
|
|
46
tests/pyosys/run_tests.py
Normal file
46
tests/pyosys/run_tests.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
import sys
|
||||
import shutil
|
||||
import shlex
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
__file_dir__ = Path(__file__).absolute().parent
|
||||
|
||||
if len(sys.argv) > 2 or len(sys.argv) == 2 and sys.argv[1] != 'yosys':
|
||||
print(f"Usage: {sys.argv[0]} [yosys]")
|
||||
exit(64)
|
||||
|
||||
if len(sys.argv) == 2:
|
||||
binary = [str(__file_dir__.parents[1] / "yosys"), "-Qy"]
|
||||
else:
|
||||
binary = [sys.executable or shutil.which("python3") or "python3"] # sys.executable can actually be None
|
||||
|
||||
tests = __file_dir__.glob("test_*.py")
|
||||
|
||||
log_dir = __file_dir__ / "logs"
|
||||
try:
|
||||
shutil.rmtree(log_dir)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
fail_logs = set()
|
||||
for test in tests:
|
||||
print(f"* {test.name} ", end="")
|
||||
log_dir.mkdir(parents=True, exist_ok=True)
|
||||
log = log_dir / (test.stem + ".log")
|
||||
cmd = [*binary, str(test)]
|
||||
log_file = open(log, "w", encoding="utf8")
|
||||
log_file.write(f"$ {shlex.join(cmd)}\n")
|
||||
log_file.flush()
|
||||
result = subprocess.run(cmd, stdout=log_file, stderr=subprocess.STDOUT)
|
||||
if result.returncode == 0:
|
||||
print("OK!")
|
||||
else:
|
||||
print(f"FAILED: {log}")
|
||||
fail_logs.add(log)
|
||||
log_file.close()
|
||||
for log in fail_logs:
|
||||
print(f">>> {log}")
|
||||
with open(log, encoding="utf8") as f:
|
||||
print(f.read())
|
||||
if len(fail_logs):
|
||||
exit(1)
|
BIN
tests/pyosys/spm.cut.v.gz
Normal file
BIN
tests/pyosys/spm.cut.v.gz
Normal file
Binary file not shown.
45
tests/pyosys/test_data_read.py
Normal file
45
tests/pyosys/test_data_read.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
from pyosys import libyosys as ys
|
||||
from pathlib import Path
|
||||
|
||||
__file_dir__ = Path(__file__).absolute().parent
|
||||
|
||||
d = ys.Design()
|
||||
|
||||
ys.run_pass(f"read_verilog {__file_dir__ / 'spm.cut.v.gz'}", d)
|
||||
ys.run_pass("hierarchy -top spm", d)
|
||||
|
||||
name_by_tv_location = []
|
||||
name_by_au_location = []
|
||||
|
||||
# test both dictionary mapping and equiv operators working fine
|
||||
module = None
|
||||
print(d.modules_)
|
||||
for idstr, module_obj in d.modules_.items():
|
||||
if idstr != ys.IdString("\\spm"):
|
||||
continue
|
||||
if idstr.str() != "\\spm":
|
||||
continue
|
||||
module = module_obj
|
||||
break
|
||||
|
||||
assert module == d.top_module(), "top module search failed"
|
||||
for name in module.ports:
|
||||
wire = module.wires_[name]
|
||||
name_str = name.str()
|
||||
if name_str.endswith(".d"): # single reg output, in au
|
||||
name_by_au_location.append(name_str[1:-2])
|
||||
elif name_str.endswith(".q"): # single reg input, in tv
|
||||
name_by_tv_location.append(name_str[1:-2])
|
||||
else: # port/boundary scan
|
||||
frm = wire.start_offset + wire.width
|
||||
to = wire.start_offset
|
||||
for i in range(frm - 1, to - 1, -1):
|
||||
bit_name = name_str[1:] + f"\\[{i}\\]"
|
||||
if wire.port_input:
|
||||
name_by_tv_location.append(bit_name)
|
||||
elif wire.port_output:
|
||||
name_by_au_location.append(bit_name)
|
||||
|
||||
assert name_by_tv_location == ['x\\[0\\]', 'a\\[31\\]', 'a\\[30\\]', 'a\\[29\\]', 'a\\[28\\]', 'a\\[27\\]', 'a\\[26\\]', 'a\\[25\\]', 'a\\[24\\]', 'a\\[23\\]', 'a\\[22\\]', 'a\\[21\\]', 'a\\[20\\]', 'a\\[19\\]', 'a\\[18\\]', 'a\\[17\\]', 'a\\[16\\]', 'a\\[15\\]', 'a\\[14\\]', 'a\\[13\\]', 'a\\[12\\]', 'a\\[11\\]', 'a\\[10\\]', 'a\\[9\\]', 'a\\[8\\]', 'a\\[7\\]', 'a\\[6\\]', 'a\\[5\\]', 'a\\[4\\]', 'a\\[3\\]', 'a\\[2\\]', 'a\\[1\\]', 'a\\[0\\]', '_315_', '_314_', '_313_', '_312_', '_311_', '_310_', '_309_', '_308_', '_307_', '_306_', '_305_', '_304_', '_303_', '_302_', '_301_', '_300_', '_299_', '_298_', '_297_', '_296_', '_295_', '_294_', '_293_', '_292_', '_291_', '_290_', '_289_', '_288_', '_287_', '_286_', '_285_', '_284_', '_283_', '_282_', '_281_', '_280_', '_279_', '_278_', '_277_', '_276_', '_275_', '_274_', '_273_', '_272_', '_271_', '_270_', '_269_', '_268_', '_267_', '_266_', '_265_', '_264_', '_263_', '_262_', '_261_', '_260_', '_259_', '_258_', '_257_', '_256_', '_255_', '_254_', '_253_', '_252_'], "failed to extract test vector register locations"
|
||||
assert name_by_au_location == ['y\\[0\\]', '_315_', '_314_', '_313_', '_312_', '_311_', '_310_', '_309_', '_308_', '_307_', '_306_', '_305_', '_304_', '_303_', '_302_', '_301_', '_300_', '_299_', '_298_', '_297_', '_296_', '_295_', '_294_', '_293_', '_292_', '_291_', '_290_', '_289_', '_288_', '_287_', '_286_', '_285_', '_284_', '_283_', '_282_', '_281_', '_280_', '_279_', '_278_', '_277_', '_276_', '_275_', '_274_', '_273_', '_272_', '_271_', '_270_', '_269_', '_268_', '_267_', '_266_', '_265_', '_264_', '_263_', '_262_', '_261_', '_260_', '_259_', '_258_', '_257_', '_256_', '_255_', '_254_', '_253_', '_252_'], "failed to extract golden output register locations"
|
||||
print("ok!")
|
44
tests/pyosys/test_dict.py
Normal file
44
tests/pyosys/test_dict.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
from typing import Mapping
|
||||
from pyosys import libyosys as ys
|
||||
|
||||
StringToStringDict = ys.StringToStringDict
|
||||
|
||||
my_dict = StringToStringDict()
|
||||
|
||||
assert isinstance(my_dict, Mapping)
|
||||
|
||||
my_dict["foo"] = "bar"
|
||||
my_dict.update([("first", "second")])
|
||||
my_dict.update({"key": "value"})
|
||||
for key, value in my_dict.items():
|
||||
print(key, value)
|
||||
|
||||
new_dict = my_dict | {"tomato": "tomato"}
|
||||
del new_dict["foo"]
|
||||
assert set(my_dict.keys()) == {"first", "key", "foo"}
|
||||
assert set(new_dict.keys()) == {"first", "key", "tomato"}
|
||||
|
||||
constructor_test_1 = ys.StringToStringDict(new_dict)
|
||||
constructor_test_2 = ys.StringToStringDict([("tomato", "tomato")])
|
||||
constructor_test_3 = ys.StringToStringDict({ "im running": "out of string ideas" })
|
||||
|
||||
the_great_or = constructor_test_1 | constructor_test_2 | constructor_test_3
|
||||
|
||||
assert set(the_great_or) == {"first", "key", "tomato", "im running"}
|
||||
repr_test = eval(repr(the_great_or))
|
||||
|
||||
assert repr_test == the_great_or # compare dicts
|
||||
assert repr_test == {'tomato': 'tomato', 'first': 'second', 'key': 'value', 'im running': 'out of string ideas', } # compare dict with mapping
|
||||
|
||||
before = len(repr_test)
|
||||
print(repr_test.popitem())
|
||||
assert before - 1 == len(repr_test)
|
||||
|
||||
# test noncomparable
|
||||
## if ys.CellType ever gets an == operator just disable this section
|
||||
uncomparable_value = ys.Globals.yosys_celltypes.cell_types[ys.IdString("$not")]
|
||||
|
||||
x = ys.IdstringToCelltypeDict({ ys.IdString("\\a"): uncomparable_value})
|
||||
y = ys.IdstringToCelltypeDict({ ys.IdString("\\a"): uncomparable_value})
|
||||
|
||||
assert x != y # not comparable
|
31
tests/pyosys/test_idict.py
Normal file
31
tests/pyosys/test_idict.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
from pyosys import libyosys as ys
|
||||
|
||||
my_idict = ys.IdstringIdict()
|
||||
print(my_idict(ys.IdString("\\hello"))) # test explicit IdString construction
|
||||
print(my_idict("\\world"))
|
||||
print(my_idict.get("\\world"))
|
||||
try:
|
||||
print(my_idict.get("\\dummy"))
|
||||
except IndexError as e:
|
||||
print(f"{repr(e)}")
|
||||
print(my_idict[0])
|
||||
print(my_idict[1])
|
||||
try:
|
||||
print(my_idict[2])
|
||||
except IndexError as e:
|
||||
print(f"{repr(e)}")
|
||||
|
||||
for i in my_idict:
|
||||
print(i)
|
||||
|
||||
current_len = len(my_idict)
|
||||
assert current_len == 2, "copy"
|
||||
|
||||
my_copy = my_idict.copy()
|
||||
my_copy("\\copy")
|
||||
assert len(my_idict) == current_len, "copy seemed to have mutate original idict"
|
||||
assert len(my_copy) == current_len + 1, "copy not behaving as expected"
|
||||
|
||||
current_copy_len = len(my_copy)
|
||||
my_copy |= ("\\the", "\\world") # 1 new element
|
||||
assert len(my_copy) == current_copy_len + 1, "or operator returned unexpected result"
|
20
tests/pyosys/test_import.py
Normal file
20
tests/pyosys/test_import.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
from pyosys import libyosys as ys
|
||||
|
||||
print(ys)
|
||||
|
||||
ys.log("Hello, world!\n")
|
||||
|
||||
from pyosys.libyosys import log
|
||||
|
||||
print(log)
|
||||
|
||||
log("Goodbye, world!\n")
|
||||
|
||||
import pyosys
|
||||
|
||||
if os.path.basename(sys.executable) == "yosys":
|
||||
# make sure it's not importing the directory
|
||||
assert "built-in" in repr(pyosys)
|
8
tests/pyosys/test_logs.py
Normal file
8
tests/pyosys/test_logs.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
from pyosys import libyosys as ys
|
||||
|
||||
d = ys.Design(); ys.log_header(d, "foo\n")
|
||||
ys.log("foo\n")
|
||||
ys.log_warning("foo\n")
|
||||
ys.log_warning_noprefix("foo\n")
|
||||
ys.log_file_info("foo.ys", 1, "foo\n")
|
||||
ys.log_file_warning("foo.ys", 1, "foo\n")
|
22
tests/pyosys/test_monitor.py
Normal file
22
tests/pyosys/test_monitor.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
from pyosys import libyosys as ys
|
||||
from pathlib import Path
|
||||
|
||||
__file_dir__ = Path(__file__).absolute().parent
|
||||
|
||||
d = ys.Design()
|
||||
|
||||
class Monitor(ys.Monitor):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.mods = []
|
||||
|
||||
def notify_module_add(self, mod):
|
||||
self.mods.append(mod.name.str())
|
||||
|
||||
m = Monitor()
|
||||
d.monitors.add(m)
|
||||
|
||||
ys.run_pass(f"read_verilog {__file_dir__ / 'spm.cut.v.gz'}", d)
|
||||
ys.run_pass("hierarchy -top spm", d)
|
||||
|
||||
assert m.mods == ["\\spm"]
|
34
tests/pyosys/test_pass.py
Normal file
34
tests/pyosys/test_pass.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
from pyosys import libyosys as ys
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
__file_dir__ = Path(__file__).absolute().parent
|
||||
|
||||
class CellStatsPass(ys.Pass):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
"cell_stats",
|
||||
"dumps cell statistics in JSON format"
|
||||
)
|
||||
|
||||
def execute(self, args, design):
|
||||
ys.log_header(design, "Dumping cell stats\n")
|
||||
ys.log_push()
|
||||
cell_stats = {}
|
||||
for module in design.all_selected_whole_modules():
|
||||
for cell in module.selected_cells():
|
||||
if cell.type.str() in cell_stats:
|
||||
cell_stats[cell.type.str()] += 1
|
||||
else:
|
||||
cell_stats[cell.type.str()] = 1
|
||||
ys.log(json.dumps(cell_stats))
|
||||
ys.log_pop()
|
||||
|
||||
p = CellStatsPass() # registration
|
||||
|
||||
design = ys.Design()
|
||||
ys.run_pass(f"read_verilog {__file_dir__.parent / 'simple' / 'fiedler-cooley.v'}", design)
|
||||
ys.run_pass("prep", design)
|
||||
ys.run_pass("opt -full", design)
|
||||
ys.run_pass("cell_stats", design)
|
21
tests/pyosys/test_script.py
Normal file
21
tests/pyosys/test_script.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
from pathlib import Path
|
||||
from pyosys import libyosys as ys
|
||||
|
||||
|
||||
__file_dir__ = Path(__file__).absolute().parent
|
||||
add_sub = __file_dir__.parent / "arch" / "common" / "add_sub.v"
|
||||
|
||||
base = ys.Design()
|
||||
ys.run_pass(f"read_verilog {add_sub}", base)
|
||||
ys.run_pass("hierarchy -top top", base)
|
||||
ys.run_pass("proc", base)
|
||||
ys.run_pass("equiv_opt -assert -map +/ecp5/cells_sim.v synth_ecp5", base)
|
||||
|
||||
postopt = ys.Design()
|
||||
ys.run_pass("design -load postopt", postopt)
|
||||
ys.run_pass("cd top", postopt)
|
||||
ys.run_pass("select -assert-min 25 t:LUT4", postopt)
|
||||
ys.run_pass("select -assert-max 26 t:LUT4", postopt)
|
||||
ys.run_pass("select -assert-count 10 t:PFUMX", postopt)
|
||||
ys.run_pass("select -assert-count 6 t:L6MUX21", postopt)
|
||||
ys.run_pass("select -assert-none t:LUT4 t:PFUMX t:L6MUX21 %% t:* %D", postopt)
|
42
tests/pyosys/test_set.py
Normal file
42
tests/pyosys/test_set.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from pyosys.libyosys import StringSet, StringPool
|
||||
|
||||
for cls in [StringSet, StringPool]:
|
||||
print(f"Testing {cls.__name__}...")
|
||||
A = cls()
|
||||
A.add("a")
|
||||
|
||||
B = cls()
|
||||
B = A | {"b"}
|
||||
|
||||
assert A < B
|
||||
assert A <= B
|
||||
|
||||
A.add("b")
|
||||
|
||||
assert A == B
|
||||
assert A <= B
|
||||
assert not A < B
|
||||
|
||||
A.add("c")
|
||||
|
||||
assert A > B
|
||||
|
||||
A &= B
|
||||
assert A == B
|
||||
|
||||
Ø = A - B
|
||||
assert len(Ø) == 0
|
||||
|
||||
C = cls({"A", "B", "C"})
|
||||
D = cls()
|
||||
C |= {"A", "B", "C"}
|
||||
D |= {"C", "D", "E"}
|
||||
c_symdiff_d = (C ^ D)
|
||||
assert c_symdiff_d == {"A", "B", "D", "E"} # compare against iterable
|
||||
|
||||
repr_test = eval(repr(c_symdiff_d))
|
||||
assert c_symdiff_d == repr_test # compare against self
|
||||
|
||||
|
||||
print("Done.")
|
Loading…
Add table
Add a link
Reference in a new issue