From 88be7283532237b71c1777a9953bb1aab260321a Mon Sep 17 00:00:00 2001 From: Mohamed Gaber Date: Sun, 21 Sep 2025 22:36:27 +0300 Subject: [PATCH] pyosys: rewrite using pybind11 - Rewrite all Python features to use the pybind11 library instead of boost::python. Unlike boost::python, pybind11 is a header-only library that is just included by Pyosys code, saving a lot of compile time on wheels. - Factor out as much "translation" code from the generator into proper C++ files - Fix running the embedded interpreter not supporting "from pyosys import libyosys as ys" like wheels - Move Python-related elements to `pyosys` directory at the root of the repo - Slight shift in bridging semantics: - Containers are declared as "opaque types" and are passed by reference to Python - many methods have been implemented to make them feel right at home without the overhead/ambiguity of copying to Python and then copying back after mutation - Monitor/Pass use "trampoline" pattern to support virual methods overridable in Python: virtual methods no longer require `py_` prefix - Create really short test set for pyosys that just exercises basic functionality --- .github/workflows/wheels.yml | 14 +- .github/workflows/wheels/cibw_before_build.sh | 21 - CODEOWNERS | 3 +- Makefile | 30 +- docs/source/yosys_internals/hashing.rst | 2 +- examples/python-api/pass.py | 22 +- kernel/driver.cc | 29 +- kernel/yosys.cc | 25 +- kernel/yosys_common.h | 3 + misc/py_wrap_generator.py | 2549 ----------------- passes/cmds/plugin.cc | 34 +- pyosys/.gitignore | 1 + {misc => pyosys}/__init__.py | 1 + pyosys/generator.py | 2039 +++++++++++++ pyosys/hashlib.h | 275 ++ pyosys/wrappers_tpl.cc | 248 ++ pyproject.toml | 6 + setup.py | 43 +- tests/pyosys/run_tests.py | 39 + tests/pyosys/spm.cut.v.gz | Bin 0 -> 5294 bytes tests/pyosys/test_data_read.py | 45 + tests/pyosys/test_dict.py | 13 + tests/pyosys/test_idict.py | 31 + tests/pyosys/test_import.py | 3 + tests/pyosys/test_monitor.py | 22 + tests/pyosys/test_pass.py | 34 + tests/pyosys/test_script.py | 21 + 27 files changed, 2879 insertions(+), 2674 deletions(-) delete mode 100644 misc/py_wrap_generator.py create mode 100644 pyosys/.gitignore rename {misc => pyosys}/__init__.py (99%) create mode 100644 pyosys/generator.py create mode 100644 pyosys/hashlib.h create mode 100644 pyosys/wrappers_tpl.cc create mode 100644 pyproject.toml create mode 100644 tests/pyosys/run_tests.py create mode 100644 tests/pyosys/spm.cut.v.gz create mode 100644 tests/pyosys/test_data_read.py create mode 100644 tests/pyosys/test_dict.py create mode 100644 tests/pyosys/test_idict.py create mode 100644 tests/pyosys/test_import.py create mode 100644 tests/pyosys/test_monitor.py create mode 100644 tests/pyosys/test_pass.py create mode 100644 tests/pyosys/test_script.py diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 2a5b7e024..b561b5d32 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -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 python3 - uses: actions/upload-artifact@v4 with: name: python-wheels-${{ matrix.os.runner }} diff --git a/.github/workflows/wheels/cibw_before_build.sh b/.github/workflows/wheels/cibw_before_build.sh index 018b0e0df..4e81688d0 100644 --- a/.github/workflows/wheels/cibw_before_build.sh +++ b/.github/workflows/wheels/cibw_before_build.sh @@ -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 diff --git a/CODEOWNERS b/CODEOWNERS index 46d37ad2f..4617c39bb 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -38,7 +38,8 @@ techlibs/gowin/ @pepijndevos techlibs/gatemate/ @pu-cc # pyosys -misc/*.py @btut +pyosys/* @donn +setup.py @donn backends/firrtl @ucbjrl @azidar diff --git a/Makefile b/Makefile index ca589ff91..c9cbd0810 100644 --- a/Makefile +++ b/Makefile @@ -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) @@ -353,26 +349,14 @@ ifeq ($(ENABLE_PYOSYS),1) LINKFLAGS += $(filter-out -l%,$(shell $(PYTHON_CONFIG) --ldflags)) LIBS += $(shell $(PYTHON_CONFIG) --libs) EXE_LIBS += $(filter-out $(LIBS),$(shell $(PYTHON_CONFIG_FOR_EXE) --libs)) +PYBIND11_INCLUDE ?= $(shell $(PYTHON_EXECUTABLE) -m pybind11 --includes) +CXXFLAGS += -I$(PYBIND11_INCLUDE) -DWITH_PYTHON CXXFLAGS += $(shell $(PYTHON_CONFIG) --includes) -DWITH_PYTHON -# Detect name of boost_python library. Some distros use boost_python-py, other boost_python, 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 +PY_WRAPPER_FILE = pyosys/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) @@ -779,9 +763,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 diff --git a/docs/source/yosys_internals/hashing.rst b/docs/source/yosys_internals/hashing.rst index 1993e617a..1255f6f7c 100644 --- a/docs/source/yosys_internals/hashing.rst +++ b/docs/source/yosys_internals/hashing.rst @@ -36,7 +36,7 @@ The main characteristics are: all compilers, standard libraries and architectures. In addition to ``dict`` and ``pool`` there is also an ``idict`` that -creates a bijective map from ``K`` to the integers. For example: +creates a bijective map from ``K`` to incrementing integers. For example: :: diff --git a/examples/python-api/pass.py b/examples/python-api/pass.py index dbef0a13f..d87e09078 100755 --- a/examples/python-api/pass.py +++ b/examples/python-api/pass.py @@ -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 @@ -29,4 +32,11 @@ class CellStatsPass(ys.Pass): def py_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) diff --git a/kernel/driver.cc b/kernel/driver.cc index bbe4e46f3..a27c0a00f 100644 --- a/kernel/driver.cc +++ b/kernel/driver.cc @@ -92,8 +92,9 @@ int main(int argc, char **argv) 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()); + 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) @@ -516,8 +517,9 @@ int main(int argc, char **argv) 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()); + 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; @@ -567,21 +569,18 @@ int main(int argc, char **argv) #endif } else if (scriptfile_python) { #ifdef WITH_PYTHON - PyObject *sys = PyImport_ImportModule("sys"); + 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) { diff --git a/kernel/yosys.cc b/kernel/yosys.cc index 95beca75c..9cab12bf6 100644 --- a/kernel/yosys.cc +++ b/kernel/yosys.cc @@ -64,13 +64,8 @@ #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 +extern "C" PyObject* PyInit_libyosys(); +extern "C" PyObject* PyInit_pyosys(); #include #endif @@ -189,6 +184,17 @@ int run_command(const std::string &command, std::function +#include + +namespace py = pybind11; #endif #ifndef _YOSYS_ diff --git a/misc/py_wrap_generator.py b/misc/py_wrap_generator.py deleted file mode 100644 index aa1406f9b..000000000 --- a/misc/py_wrap_generator.py +++ /dev/null @@ -1,2549 +0,0 @@ -# -# yosys -- Yosys Open SYnthesis Suite -# -# Copyright (C) 2012 Claire Xenia Wolf -# -# 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. -# -# Author Benedikt Tutzer -# - -import copy - -#Map c++ operator Syntax to Python functions -wrappable_operators = { - "<" : "__lt__", - "==": "__eq__", - "!=": "__ne__", - "+" : "__add__", - "-" : "__sub__", - "*" : "__mul__", - "/" : "__div__", - "()": "__call__" - } - -#Restrict certain strings from being function names in Python -keyword_aliases = { - "in" : "in_", - "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_" - } - -#These can be used without any explicit conversion -primitive_types = ["void", "bool", "int", "double", "size_t", "std::string", - "string", "string_view", "std::string_view", "State", "char_p", "std::source_location", "source_location"] - -from enum import Enum - -#Ways to link between Python- and C++ Objects -class link_types(Enum): - global_list = 1 #Manage a global list of objects in C++, the Python - #object contains a key to find the corresponding C++ - #object and a Pointer to the object to verify it is - #still the same, making collisions unlikely to happen - ref_copy = 2 #The Python object contains a copy of the C++ object. - #The C++ object is deleted when the Python object gets - #deleted - pointer = 3 #The Python Object contains a pointer to it's C++ - #counterpart - derive = 4 #The Python-Wrapper is derived from the C++ object. - -class attr_types(Enum): - star = "*" - amp = "&" - ampamp = "&&" - default = "" - -#For source-files -class Source: - name = "" - classes = [] - - def __init__(self, name, classes): - self.name = name - self.classes = classes - -#Splits a list by the given delimiter, without splitting strings inside -#pointy-brackets (< and >) -def split_list(str_def, delim): - str_def = str_def.strip() - if len(str_def) == 0: - return [] - if str_def.count(delim) == 0: - return [str_def] - if str_def.count("<") == 0: - return str_def.split(delim) - if str_def.find("<") < str_def.find(" "): - closing = find_closing(str_def[str_def.find("<")+1:], "<", ">") + str_def.find("<") - comma = str_def[closing:].find(delim) - if comma == -1: - return [str_def] - comma = closing + comma - else: - comma = str_def.find(delim) - rest = split_list(str_def[comma+1:], delim) - ret = [str_def[:comma]] - if rest != None and len(rest) != 0: - ret.extend(rest) - return ret - -#Represents a Type -class WType: - name = "" - cont = None - attr_type = attr_types.default - - def __init__(self, name = "", cont = None, attr_type = attr_types.default): - self.name = name - self.cont = cont - self.attr_type = attr_type - - #Python type-string - def gen_text(self): - text = self.name - if self.name in enum_names: - text = enum_by_name(self.name).namespace + "::" + self.name - if self.cont != None: - return known_containers[self.name].typename - return text - - #C++ type-string - def gen_text_cpp(self): - postfix = "" - if self.attr_type == attr_types.star: - postfix = "*" - if self.name in primitive_types: - return self.name + postfix - if self.name in enum_names: - return enum_by_name(self.name).namespace + "::" + self.name + postfix - if self.name in classnames: - return class_by_name(self.name).namespace + "::" + self.name + postfix - text = self.name - if self.cont != None: - text += "<" - for a in self.cont.args: - text += a.gen_text_cpp() + ", " - text = text[:-2] - text += ">" - return text - - @staticmethod - def from_string(str_def, containing_file, line_number): - str_def = str_def.strip() - if len(str_def) == 0: - return None - str_def = str_def.replace("RTLIL::SigSig", "std::pair").replace("SigSig", "std::pair") - t = WType() - t.name = "" - t.cont = None - t.attr_type = attr_types.default - if str_def.find("<") != -1:# and str_def.find("<") < str_def.find(" "): - str_def = str_def.replace("const ", "") - - candidate = WContainer.from_string(str_def, containing_file, line_number) - if candidate == None: - return None - t.name = str_def[:str_def.find("<")] - - if t.name.count("*") + t.name.count("&") > 1: - return None - - if t.name.count("*") == 1 or str_def[0] == '*' or str_def[-1] == '*': - t.attr_type = attr_types.star - t.name = t.name.replace("*","") - elif t.name.count("&&") == 1: - t.attr_type = attr_types.ampamp - t.name = t.name.replace("&&","") - elif t.name.count("&") == 1 or str_def[0] == '&' or str_def[-1] == '&': - t.attr_type = attr_types.amp - t.name = t.name.replace("&","") - - t.cont = candidate - if(t.name not in known_containers): - return None - return t - - prefix = "" - - if str.startswith(str_def, "const "): - if "char_p" in str_def: - prefix = "const " - str_def = str_def[6:] - if str.startswith(str_def, "unsigned "): - prefix = "unsigned " + prefix - str_def = str_def[9:] - while str.startswith(str_def, "long "): - prefix= "long " + prefix - str_def = str_def[5:] - while str.startswith(str_def, "short "): - prefix = "short " + prefix - str_def = str_def[6:] - - str_def = str_def.split("::")[-1] - - if str_def.count("*") + str_def.count("&") >= 2: - return None - - if str_def.count("*") == 1: - t.attr_type = attr_types.star - str_def = str_def.replace("*","") - elif str_def.count("&&") == 1: - t.attr_type = attr_types.ampamp - str_def = str_def.replace("&&","") - elif str_def.count("&") == 1: - t.attr_type = attr_types.amp - str_def = str_def.replace("&","") - - if len(str_def) > 0 and str_def.split("::")[-1] not in primitive_types and str_def.split("::")[-1] not in classnames and str_def.split("::")[-1] not in enum_names: - return None - - if str_def.count(" ") == 0: - t.name = (prefix + str_def).replace("char_p", "char *") - t.cont = None - return t - return None - -#Represents a container-type -class WContainer: - name = "" - args = [] - - def from_string(str_def, containing_file, line_number): - if str_def == None or len(str_def) < 4: - return None - cont = WContainer() - cont.name = str_def[:str_def.find("<")] - str_def = str_def[str_def.find("<")+1:find_closing(str_def, "<", ">")] - cont.args = [] - for arg in split_list(str_def, ","): - candidate = WType.from_string(arg.strip(), containing_file, line_number) - if candidate == None: - return None - if candidate.name == "void": - return None - cont.args.append(candidate) - return cont - -#Translators between Python and C++ containers -#Base Type -class Translator: - tmp_cntr = 0 - typename = "DefaultType" - orig_name = "DefaultCpp" - - @classmethod - def gen_type(c, types): - return "\nImplement a function that outputs the c++ type of this container here\n" - - @classmethod - def translate(c, varname, types, prefix): - return "\nImplement a function translating a python container to a c++ container here\n" - - @classmethod - def translate_cpp(c, varname, types, prefix, ref): - return "\nImplement a function translating a c++ container to a python container here\n" - -#Translates list-types (vector, pool, set), that only differ in their name and -#the name of the insertion function -class PythonListTranslator(Translator): - typename = "boost::python::list" - insert_name = "Default" - - #generate the c++ type string - @classmethod - def gen_type(c, types): - text = c.orig_name + "<" - if types[0].name in primitive_types: - text += types[0].name - elif types[0].name in known_containers: - text += known_containers[types[0].name].gen_type(types[0].cont.args) - else: - text += class_by_name(types[0].name).namespace + "::" + types[0].name - if types[0].attr_type == attr_types.star: - text += "*" - text += ">" - return text - - #Generate C++ code to translate from a boost::python::list - @classmethod - def translate(c, varname, types, prefix): - text = prefix + c.gen_type(types) + " " + varname + "___tmp;" - cntr_name = "cntr_" + str(Translator.tmp_cntr) - Translator.tmp_cntr = Translator.tmp_cntr + 1 - text += prefix + "for(int " + cntr_name + " = 0; " + cntr_name + " < len(" + varname + "); " + cntr_name + "++)" - text += prefix + "{" - tmp_name = "tmp_" + str(Translator.tmp_cntr) - Translator.tmp_cntr = Translator.tmp_cntr + 1 - if types[0].name in known_containers: - text += prefix + "\t" + known_containers[types[0].name].typename + " " + tmp_name + " = boost::python::extract<" + known_containers[types[0].name].typename + ">(" + varname + "[" + cntr_name + "]);" - text += known_containers[types[0].name].translate(tmp_name, types[0].cont.args, prefix+"\t") - tmp_name = tmp_name + "___tmp" - text += prefix + "\t" + varname + "___tmp" + c.insert_name + "(" + tmp_name + ");" - elif types[0].name in classnames: - text += prefix + "\t" + types[0].name + "* " + tmp_name + " = boost::python::extract<" + types[0].name + "*>(" + varname + "[" + cntr_name + "]);" - if types[0].attr_type == attr_types.star: - text += prefix + "\t" + varname + "___tmp" + c.insert_name + "(" + tmp_name + "->get_cpp_obj());" - else: - text += prefix + "\t" + varname + "___tmp" + c.insert_name + "(*" + tmp_name + "->get_cpp_obj());" - else: - text += prefix + "\t" + types[0].name + " " + tmp_name + " = boost::python::extract<" + types[0].name + ">(" + varname + "[" + cntr_name + "]);" - text += prefix + "\t" + varname + "___tmp" + c.insert_name + "(" + tmp_name + ");" - text += prefix + "}" - return text - - #Generate C++ code to translate to a boost::python::list - @classmethod - def translate_cpp(c, varname, types, prefix, ref): - text = prefix + c.typename + " " + varname + "___tmp;" - tmp_name = "tmp_" + str(Translator.tmp_cntr) - Translator.tmp_cntr = Translator.tmp_cntr + 1 - if ref: - text += prefix + "for(auto " + tmp_name + " : *" + varname + ")" - else: - text += prefix + "for(auto " + tmp_name + " : " + varname + ")" - text += prefix + "{" - if types[0].name in classnames: - if types[0].attr_type == attr_types.star: - text += prefix + "\t" + varname + "___tmp.append(" + types[0].name + "::get_py_obj(" + tmp_name + "));" - else: - text += prefix + "\t" + varname + "___tmp.append(*" + types[0].name + "::get_py_obj(&" + tmp_name + "));" - elif types[0].name in known_containers: - text += known_containers[types[0].name].translate_cpp(tmp_name, types[0].cont.args, prefix + "\t", types[0].attr_type == attr_types.star) - text += prefix + "\t" + varname + "___tmp.append(" + tmp_name + "___tmp);" - else: - text += prefix + "\t" + varname + "___tmp.append(" + tmp_name + ");" - text += prefix + "}" - return text - -class IDictTranslator(PythonListTranslator): - typename = "boost::python::list" - orig_name = "idict" - insert_name = "" - -#Sub-type for std::set -class SetTranslator(PythonListTranslator): - insert_name = ".insert" - orig_name = "std::set" - -#Sub-type for std::vector -class VectorTranslator(PythonListTranslator): - insert_name = ".push_back" - orig_name = "std::vector" - -#Sub-type for pool -class PoolTranslator(PythonListTranslator): - insert_name = ".insert" - orig_name = "pool" - -#Sub-type for ObjRange -class ObjRangeTranslator(PythonListTranslator): - orig_name = "RTLIL::ObjRange" - -#Translates dict-types (dict, std::map), that only differ in their name and -#the name of the insertion function -class PythonDictTranslator(Translator): - typename = "boost::python::dict" - insert_name = "Default" - - @classmethod - def gen_type(c, types): - text = c.orig_name + "<" - if types[0].name in primitive_types: - text += types[0].name - elif types[0].name in known_containers: - text += known_containers[types[0].name].gen_type(types[0].cont.args) - else: - text += class_by_name(types[0].name).namespace + "::" + types[0].name - if types[0].attr_type == attr_types.star: - text += "*" - text += ", " - if types[1].name in primitive_types: - text += types[1].name - elif types[1].name in known_containers: - text += known_containers[types[1].name].gen_type(types[1].cont.args) - else: - text += class_by_name(types[1].name).namespace + "::" + types[1].name - if types[1].attr_type == attr_types.star: - text += "*" - text += ">" - return text - - #Generate c++ code to translate from a boost::python::dict - @classmethod - def translate(c, varname, types, prefix): - text = prefix + c.gen_type(types) + " " + varname + "___tmp;" - text += prefix + "boost::python::list " + varname + "_keylist = " + varname + ".keys();" - cntr_name = "cntr_" + str(Translator.tmp_cntr) - Translator.tmp_cntr = Translator.tmp_cntr + 1 - text += prefix + "for(int " + cntr_name + " = 0; " + cntr_name + " < len(" + varname + "_keylist); " + cntr_name + "++)" - text += prefix + "{" - key_tmp_name = "key_tmp_" + str(Translator.tmp_cntr) - val_tmp_name = "val_tmp_" + str(Translator.tmp_cntr) - Translator.tmp_cntr = Translator.tmp_cntr + 1 - - if types[0].name in known_containers: - text += prefix + "\t" + known_containers[types[0].name].typename + " " + key_tmp_name + " = boost::python::extract<" + known_containers[types[0].name].typename + ">(" + varname + "_keylist[ " + cntr_name + " ]);" - text += known_containers[types[0].name].translate(key_tmp_name, types[0].cont.args, prefix+"\t") - key_tmp_name = key_tmp_name + "___tmp" - elif types[0].name in classnames: - text += prefix + "\t" + types[0].name + "* " + key_tmp_name + " = boost::python::extract<" + types[0].name + "*>(" + varname + "_keylist[ " + cntr_name + " ]);" - else: - text += prefix + "\t" + types[0].name + " " + key_tmp_name + " = boost::python::extract<" + types[0].name + ">(" + varname + "_keylist[ " + cntr_name + " ]);" - - if types[1].name in known_containers: - text += prefix + "\t" + known_containers[types[1].name].typename + " " + val_tmp_name + " = boost::python::extract<" + known_containers[types[1].name].typename + ">(" + varname + "[" + varname + "_keylist[ " + cntr_name + " ]]);" - text += known_containers[types[1].name].translate(val_tmp_name, types[1].cont.args, prefix+"\t") - val_tmp_name = val_tmp_name + "___tmp" - elif types[1].name in classnames: - text += prefix + "\t" + types[1].name + "* " + val_tmp_name + " = boost::python::extract<" + types[1].name + "*>(" + varname + "[" + varname + "_keylist[ " + cntr_name + " ]]);" - else: - text += prefix + "\t" + types[1].name + " " + val_tmp_name + " = boost::python::extract<" + types[1].name + ">(" + varname + "[" + varname + "_keylist[ " + cntr_name + " ]]);" - - text += prefix + "\t" + varname + "___tmp." + c.insert_name + "(std::pair<" + types[0].gen_text_cpp() + ", " + types[1].gen_text_cpp() + ">(" - - if types[0].name not in classnames: - text += key_tmp_name - else: - if types[0].attr_type != attr_types.star: - text += "*" - text += key_tmp_name + "->get_cpp_obj()" - - text += ", " - if types[1].name not in classnames: - text += val_tmp_name - else: - if types[1].attr_type != attr_types.star: - text += "*" - text += val_tmp_name + "->get_cpp_obj()" - text += "));\n" + prefix + "}" - return text - - #Generate c++ code to translate to a boost::python::dict - @classmethod - def translate_cpp(c, varname, types, prefix, ref): - text = prefix + c.typename + " " + varname + "___tmp;" - tmp_name = "tmp_" + str(Translator.tmp_cntr) - Translator.tmp_cntr = Translator.tmp_cntr + 1 - if ref: - text += prefix + "for(auto " + tmp_name + " : *" + varname + ")" - else: - text += prefix + "for(auto " + tmp_name + " : " + varname + ")" - text += prefix + "{" - if types[1].name in known_containers: - text += prefix + "\tauto " + tmp_name + "_second = " + tmp_name + ".second;" - text += known_containers[types[1].name].translate_cpp(tmp_name + "_second", types[1].cont.args, prefix + "\t", types[1].attr_type == attr_types.star) - - if types[0].name in classnames: - text += prefix + "\t" + varname + "___tmp[" + types[0].name + "::get_py_obj(" + tmp_name + ".first)] = " - elif types[0].name not in known_containers: - text += prefix + "\t" + varname + "___tmp[" + tmp_name + ".first] = " - - if types[1].name in classnames: - if types[1].attr_type == attr_types.star: - text += types[1].name + "::get_py_obj(" + tmp_name + ".second);" - else: - text += "*" + types[1].name + "::get_py_obj(&" + tmp_name + ".second);" - elif types[1].name in known_containers: - text += tmp_name + "_second___tmp;" - else: - text += tmp_name + ".second;" - text += prefix + "}" - return text - -#Sub-type for dict -class DictTranslator(PythonDictTranslator): - insert_name = "insert" - orig_name = "dict" - -#Sub_type for std::map -class MapTranslator(PythonDictTranslator): - insert_name = "insert" - orig_name = "std::map" - -#Translator for std::pair. Derived from PythonDictTranslator because the -#gen_type function is the same (because both have two template parameters) -class TupleTranslator(PythonDictTranslator): - typename = "boost::python::tuple" - orig_name = "std::pair" - - #Generate c++ code to translate from a boost::python::tuple - @classmethod - def translate(c, varname, types, prefix): - text = prefix + types[0].name + " " + varname + "___tmp_0 = boost::python::extract<" + types[0].name + ">(" + varname + "[0]);" - text += prefix + types[1].name + " " + varname + "___tmp_1 = boost::python::extract<" + types[1].name + ">(" + varname + "[1]);" - text += prefix + TupleTranslator.gen_type(types) + " " + varname + "___tmp(" - if types[0].name.split(" ")[-1] in primitive_types: - text += varname + "___tmp_0, " - else: - text += "*" + varname + "___tmp_0.get_cpp_obj(), " - if types[1].name.split(" ")[-1] in primitive_types: - text += varname + "___tmp_1);" - else: - text += "*" + varname + "___tmp_1.get_cpp_obj());" - return text - - #Generate c++ code to translate to a boost::python::tuple - @classmethod - def translate_cpp(c, varname, types, prefix, ref): - # if the tuple is a pair of SigSpecs (aka SigSig), then we need - # to call get_py_obj() on each item in the tuple - if types[0].name in classnames: - first_var = types[0].name + "::get_py_obj(" + varname + ".first)" - else: - first_var = varname + ".first" - if types[1].name in classnames: - second_var = types[1].name + "::get_py_obj(" + varname + ".second)" - else: - second_var = varname + ".second" - text = prefix + TupleTranslator.typename + " " + varname + "___tmp = boost::python::make_tuple(" + first_var + ", " + second_var + ");" - return text - -#Associate the Translators with their c++ type -known_containers = { - "std::set" : SetTranslator, - "std::vector" : VectorTranslator, - "pool" : PoolTranslator, - "idict" : IDictTranslator, - "dict" : DictTranslator, - "std::pair" : TupleTranslator, - "std::map" : MapTranslator, - "RTLIL::ObjRange" : ObjRangeTranslator -} - -class Attribute: - wtype = None - varname = None - is_const = False - default_value = None - pos = None - pos_counter = 0 - coerce_arg = None - - def __init__(self, wtype, varname, is_const = False, default_value = None): - self.wtype = wtype - self.varname = varname - self.is_const = is_const - self.default_value = None - self.container = None - - @staticmethod - def from_string(str_def, containing_file, line_number, *, owner_fn_name=""): - if len(str_def) < 3: - return None - orig = str_def - arg = Attribute(None, None) - prefix = "" - arg.wtype = None - arg.varname = None - arg.is_const = False - arg.default_value = None - arg.container = None - if str.startswith(str_def, "const "): - arg.is_const = True - str_def = str_def[6:] - if str.startswith(str_def, "unsigned "): - prefix = "unsigned " - str_def = str_def[9:] - while str.startswith(str_def, "long "): - prefix= "long " + prefix - str_def = str_def[5:] - while str.startswith(str_def, "short "): - prefix = "short " + prefix - str_def = str_def[6:] - - if str_def.find("<") != -1 and str_def.find("<") < str_def.find(" "): - closing = find_closing(str_def[str_def.find("<"):], "<", ">") + str_def.find("<") + 1 - arg.wtype = WType.from_string(str_def[:closing].strip(), containing_file, line_number) - str_def = str_def[closing+1:] - else: - if str_def.count(" ") > 0: - arg.wtype = WType.from_string(prefix + str_def[:str_def.find(" ")].strip(), containing_file, line_number) - str_def = str_def[str_def.find(" ")+1:] - else: - arg.wtype = WType.from_string(prefix + str_def.strip(), containing_file, line_number) - str_def = "" - arg.varname = "" - - if arg.wtype == None: - return None - if str_def.count("=") == 0: - arg.varname = str_def.strip() - if arg.varname.find(" ") > 0: - return None - else: - arg.varname = str_def[:str_def.find("=")].strip() - if arg.varname.find(" ") > 0: - return None - str_def = str_def[str_def.find("=")+1:].strip() - arg.default_value = str_def[arg.varname.find("=")+1:].strip() - if len(arg.varname) == 0: - arg.varname = None - return arg - if arg.varname[0] == '*': - arg.wtype.attr_type = attr_types.star - arg.varname = arg.varname[1:] - elif arg.varname[0] == '&': - if arg.wtype.attr_type != attr_types.default: - return None - if arg.varname[1] == '&': - arg.wtype.attr_type = attr_types.ampamp - arg.varname = arg.varname[2:] - else: - arg.wtype.attr_type = attr_types.amp - arg.varname = arg.varname[1:] - - # handle string views - if arg.wtype.name in ["std::string_view", "string_view"]: - if arg.varname == "format" and owner_fn_name.startswith("log_"): - # coerce format strings to "%s" (not bridgable) - arg.coerce_arg = '"%s"' - elif arg.varname == "prefix" and "warning" in owner_fn_name: - # coerce warning prefix to "warning:" - arg.coerce_arg = '"Warning: "' - else: - # boost::python can't bridge string views, so just copy them - arg.wtype.name = "string" - - return arg - - #Generates the varname. If the attribute has no name in the header file, - #a name is generated - def gen_varname(self): - if self.varname != None: - return self.varname - if self.wtype.name == "void": - return "" - if self.pos == None: - self.pos = Attribute.pos_counter - Attribute.pos_counter = Attribute.pos_counter + 1 - return "gen_varname_" + str(self.pos) - - #Generates the text for the function headers with wrapper types - def gen_listitem(self): - prefix = "" - if self.is_const: - prefix = "const " - if self.wtype.name in classnames: - return prefix + self.wtype.name + "* " + self.gen_varname() - if self.wtype.name in known_containers: - return prefix + known_containers[self.wtype.name].typename + " " + self.gen_varname() - return prefix + self.wtype.name + " " + self.gen_varname() - - #Generates the test for the function headers with c++ types - def gen_listitem_cpp(self): - prefix = "" - if self.is_const: - prefix = "const " - infix = "" - if self.wtype.attr_type == attr_types.star: - infix = "*" - elif self.wtype.attr_type == attr_types.amp: - infix = "&" - elif self.wtype.attr_type == attr_types.ampamp: - infix = "&&" - if self.wtype.name in known_containers: - return prefix + known_containers[self.wtype.name].gen_type(self.wtype.cont.args) + " " + infix + self.gen_varname() - if self.wtype.name in classnames: - return prefix + class_by_name(self.wtype.name).namespace + "::" + self.wtype.name + " " + infix + self.gen_varname() - return prefix + self.wtype.name + " " + infix + self.gen_varname() - - #Generates the listitem withtout the varname, so the signature can be - #compared - def gen_listitem_hash(self): - prefix = "" - if self.is_const: - prefix = "const " - if self.wtype.name in classnames: - return prefix + self.wtype.name + "* " - if self.wtype.name in known_containers: - return known_containers[self.wtype.name].typename - return prefix + self.wtype.name - - #Generate Translation code for the attribute - def gen_translation(self): - if self.wtype.name in known_containers: - return known_containers[self.wtype.name].translate(self.gen_varname(), self.wtype.cont.args, "\n\t\t") - return "" - - #Generate Translation code from c++ for the attribute - def gen_translation_cpp(self): - if self.wtype.name in known_containers: - return known_containers[self.wtype.name].translate_cpp(self.gen_varname(), self.wtype.cont.args, "\n\t\t", self.wtype.attr_type == attr_types.star) - return "" - - #Generate Text for the call - def gen_call(self): - ret = self.gen_varname() - if self.wtype.name in known_containers: - if self.wtype.attr_type == attr_types.star: - return "&" + ret + "___tmp" - return ret + "___tmp" - if self.wtype.name in classnames: - if self.wtype.attr_type != attr_types.star: - ret = "*" + ret - return ret + "->get_cpp_obj()" - if self.wtype.name == "char *" and self.gen_varname() in ["format", "fmt"]: - return "\"%s\", " + self.gen_varname() - if self.wtype.attr_type == attr_types.star: - return "&" + ret - return ret - - def gen_call_cpp(self): - ret = self.gen_varname() - if self.wtype.name.split(" ")[-1] in primitive_types or self.wtype.name in enum_names: - if self.wtype.attr_type == attr_types.star: - return "&" + ret - return ret - if self.wtype.name not in classnames: - if self.wtype.attr_type == attr_types.star: - return "&" + ret + "___tmp" - return ret + "___tmp" - if self.wtype.attr_type != attr_types.star: - ret = "*" + ret - return self.wtype.name + "::get_py_obj(" + self.gen_varname() + ")" - - #Generate cleanup code - def gen_cleanup(self): - if self.wtype.name in primitive_types or self.wtype.name in classnames or self.wtype.name in enum_names or not self.wtype.attr_type == attr_types.star or (self.wtype.name in known_containers and self.wtype.attr_type == attr_types.star): - return "" - return "\n\t\tdelete " + self.gen_varname() + "___tmp;" - -class WClass: - name = None - namespace = None - link_type = None - base_class = None - id_ = None - string_id = None - hash_id = None - needs_clone = False - found_funs = [] - found_vars = [] - found_constrs = [] - - def __init__(self, name, link_type, id_, string_id = None, hash_id = None, needs_clone = False): - self.name = name - self.namespace = None - self.base_class = None - self.link_type = link_type - self.id_ = id_ - self.string_id = string_id - self.hash_id = hash_id - self.needs_clone = needs_clone - self.found_funs = [] - self.found_vars = [] - self.found_constrs = [] - - def printable_constrs(self): - ret = 0 - for con in self.found_constrs: - if not con.protected: - ret += 1 - return ret - - def gen_decl(self, filename): - long_name = self.namespace + "::" + self.name - - text = "\n\t// WRAPPED from " + filename - text += "\n\tstruct " + self.name - if self.link_type == link_types.derive: - text += " : public " + self.namespace + "::" + self.name - text += "\n\t{\n" - - if self.link_type != link_types.derive: - - text += "\t\t" + long_name + "* ref_obj;\n" - - if self.link_type == link_types.ref_copy or self.link_type == link_types.pointer: - text += "\n\t\t" + long_name + "* get_cpp_obj() const\n\t\t{\n\t\t\treturn ref_obj;\n\t\t}\n" - elif self.link_type == link_types.global_list: - text += "\t\t" + self.id_.wtype.name + " " + self.id_.varname + ";\n" - text += "\n\t\t" + long_name + "* get_cpp_obj() const\n\t\t{" - text += "\n\t\t\t" + long_name + "* ret = " + long_name + "::get_all_" + self.name.lower() + "s()->at(this->" + self.id_.varname + ");" - text += "\n\t\t\tif(ret != NULL && ret == this->ref_obj)" - text += "\n\t\t\t\treturn ret;" - text += "\n\t\t\tthrow std::runtime_error(\"" + self.name + "'s c++ object does not exist anymore.\");" - text += "\n\t\t\treturn NULL;" - text += "\n\t\t}\n" - - #if self.link_type != link_types.pointer: - text += "\n\t\tstatic " + self.name + "* get_py_obj(" + long_name + "* ref)\n\t\t{" - text += "\n\t\t\tif(ref == nullptr){" - text += "\n\t\t\t\tthrow std::runtime_error(\"" + self.name + " does not exist.\");" - text += "\n\t\t\t}" - text += "\n\t\t\t" + self.name + "* ret = (" + self.name + "*)malloc(sizeof(" + self.name + "));" - if self.link_type == link_types.pointer: - text += "\n\t\t\tret->ref_obj = ref;" - if self.link_type == link_types.ref_copy: - if self.needs_clone: - text += "\n\t\t\tret->ref_obj = ref->clone();" - else: - text += "\n\t\t\tret->ref_obj = new "+long_name+"(*ref);" - if self.link_type == link_types.global_list: - text += "\n\t\t\tret->ref_obj = ref;" - text += "\n\t\t\tret->" + self.id_.varname + " = ret->ref_obj->" + self.id_.varname + ";" - text += "\n\t\t\treturn ret;" - text += "\n\t\t}\n" - - if self.link_type == link_types.ref_copy: - text += "\n\t\tstatic " + self.name + "* get_py_obj(" + long_name + " ref)\n\t\t{" - text += "\n\t\t\t" + self.name + "* ret = (" + self.name + "*)malloc(sizeof(" + self.name + "));" - if self.needs_clone: - text += "\n\t\t\tret->ref_obj = ref.clone();" - else: - text += "\n\t\t\tret->ref_obj = new "+long_name+"(ref);" - text += "\n\t\t\treturn ret;" - text += "\n\t\t}\n" - - for con in self.found_constrs: - text += con.gen_decl() - if self.base_class is not None: - text += "\n\t\tvirtual ~" + self.name + "() { };" - for var in self.found_vars: - text += var.gen_decl() - for fun in self.found_funs: - text += fun.gen_decl() - - - if self.link_type == link_types.derive: - duplicates = {} - for fun in self.found_funs: - if fun.name in duplicates: - fun.gen_alias() - duplicates[fun.name].gen_alias() - else: - duplicates[fun.name] = fun - - text += "\n\t\t" + long_name + "* get_cpp_obj() const\n\t\t{\n\t\t\treturn (" + self.namespace + "::" + self.name +"*)this;\n\t\t}\n" - text += "\n\t\tstatic " + self.name + "* get_py_obj(" + long_name + "* ref)\n\t\t{" - text += "\n\t\t\treturn (" + self.name + "*)ref;" - text += "\n\t\t}\n" - - for con in self.found_constrs: - text += con.gen_decl_derive() - for var in self.found_vars: - text += var.gen_decl() - for fun in self.found_funs: - text += fun.gen_decl_virtual() - - if self.hash_id != None: - text += "\n\t\tunsigned int get_hash_py()" - text += "\n\t\t{" - suffix = f"->{self.hash_id}" if self.hash_id else f"->{self.hash_id}" - if self.hash_id == "": - text += f"\n\t\t\treturn run_hash(*(get_cpp_obj()));" - else: - text += f"\n\t\t\treturn run_hash(get_cpp_obj()->{self.hash_id});" - text += "\n\t\t}" - - text += "\n\t};\n" - - if self.link_type == link_types.derive: - text += "\n\tstruct " + self.name + "Wrap : " + self.name + ", boost::python::wrapper<" + self.name + ">" - text += "\n\t{" - - for con in self.found_constrs: - text += con.gen_decl_wrapperclass() - for fun in self.found_funs: - text += fun.gen_default_impl() - - text += "\n\t};" - - text += "\n\tstd::ostream &operator<<(std::ostream &ostr, const " + self.name + " &ref)" - text += "\n\t{" - text += "\n\t\tostr << \"" + self.name - if self.string_id != None: - text +=" \\\"\"" - text += " << ref.get_cpp_obj()->" + self.string_id - text += " << \"\\\"\"" - else: - text += " at \" << ref.get_cpp_obj()" - text += ";" - text += "\n\t\treturn ostr;" - text += "\n\t}" - text += "\n" - - return text - - def gen_funs(self, filename): - text = "" - if self.link_type != link_types.derive: - for con in self.found_constrs: - text += con.gen_def() - for var in self.found_vars: - text += var.gen_def() - for fun in self.found_funs: - text += fun.gen_def() - else: - for var in self.found_vars: - text += var.gen_def() - for fun in self.found_funs: - text += fun.gen_def_virtual() - return text - - def gen_boost_py_body(self): - text = "" - if self.printable_constrs() == 0 or not self.contains_default_constr(): - text += ", no_init" - text += ")" - text += "\n\t\t\t.def(boost::python::self_ns::str(boost::python::self_ns::self))" - text += "\n\t\t\t.def(boost::python::self_ns::repr(boost::python::self_ns::self))" - for con in self.found_constrs: - text += con.gen_boost_py() - for var in self.found_vars: - text += var.gen_boost_py() - static_funs = [] - for fun in self.found_funs: - text += fun.gen_boost_py() - if fun.is_static and fun.alias not in static_funs: - static_funs.append(fun.alias) - for fun in static_funs: - text += "\n\t\t\t.staticmethod(\"" + fun + "\")" - - if self.hash_id != None: - text += "\n\t\t\t.def(\"__hash__\", &" + self.name + "::get_hash_py)" - text += "\n\t\t\t;\n" - return text - - def gen_boost_py(self): - body = self.gen_boost_py_body() - base_info = "" - if self.base_class is not None: - base_info = ", bases<" + (self.base_class.name) + ">" - - if self.link_type == link_types.derive: - text = "\n\t\tclass_<" + self.name + base_info + ">(\"Cpp" + self.name + "\"" - text += body - text += "\n\t\tclass_<" + self.name - text += "Wrap, boost::noncopyable" - text += ">(\"" + self.name + "\"" - text += body - else: - text = "\n\t\tclass_<" + self.name + base_info + ">(\"" + self.name + "\"" - text += body - return text - - - def contains_default_constr(self): - for c in self.found_constrs: - if len(c.args) == 0: - return True - return False - -#CONFIGURE HEADER-FILES TO BE PARSED AND CLASSES EXPECTED IN THEM HERE - -sources = [ - Source("kernel/celltypes",[ - WClass("CellType", link_types.pointer, None, None, "type", True), - WClass("CellTypes", link_types.pointer, None, None, None, True) - ] - ), - Source("kernel/consteval",[ - WClass("ConstEval", link_types.pointer, None, None, None, True) - ] - ), - Source("kernel/log",[]), - Source("kernel/register",[ - WClass("Pass", link_types.derive, None, None, None, True), - ] - ), - Source("kernel/rtlil",[ - WClass("IdString", link_types.ref_copy, None, "str()", ""), - WClass("Const", link_types.ref_copy, None, "as_string()", ""), - WClass("AttrObject", link_types.ref_copy, None, None, None), - WClass("NamedObject", link_types.ref_copy, None, None, None), - WClass("Selection", link_types.ref_copy, None, None, None), - WClass("Monitor", link_types.derive, None, None, None), - WClass("CaseRule",link_types.ref_copy, None, None, None, True), - WClass("SwitchRule",link_types.ref_copy, None, None, None, True), - WClass("SyncRule", link_types.ref_copy, None, None, None, True), - WClass("Process", link_types.ref_copy, None, "name.c_str()", "name"), - WClass("SigChunk", link_types.ref_copy, None, None, None), - WClass("SigBit", link_types.ref_copy, None, None, ""), - WClass("SigSpec", link_types.ref_copy, None, None, ""), - WClass("Cell", link_types.global_list, Attribute(WType("unsigned int"), "hashidx_"), "name.c_str()", ""), - WClass("Wire", link_types.global_list, Attribute(WType("unsigned int"), "hashidx_"), "name.c_str()", ""), - WClass("Memory", link_types.global_list, Attribute(WType("unsigned int"), "hashidx_"), "name.c_str()", ""), - WClass("Module", link_types.global_list, Attribute(WType("unsigned int"), "hashidx_"), "name.c_str()", ""), - WClass("Design", link_types.global_list, Attribute(WType("unsigned int"), "hashidx_"), "hashidx_", "") - ] - ), - #Source("kernel/satgen",[ - # ] - # ), - #Source("libs/ezsat/ezsat",[ - # ] - # ), - #Source("libs/ezsat/ezminisat",[ - # ] - # ), - Source("kernel/sigtools",[ - WClass("SigMap", link_types.pointer, None, None, None, True) - ] - ), - Source("kernel/yosys",[ - ] - ), - Source("kernel/cost",[]) - ] - -blacklist_methods = ["YOSYS_NAMESPACE::Pass::run_register", "YOSYS_NAMESPACE::Module::Pow"] - -enum_names = ["State","SyncType","ConstFlags"] - -enums = [] #Do not edit -glbls = [] - -unowned_functions = [] - -classnames = [] -for source in sources: - for wclass in source.classes: - classnames.append(wclass.name) - -def class_by_name(name): - for source in sources: - for wclass in source.classes: - if wclass.name == name: - return wclass - return None - -def enum_by_name(name): - for e in enums: - if e.name == name: - return e - return None - -def find_closing(text, open_tok, close_tok): - if text.find(open_tok) == -1 or text.find(close_tok) <= text.find(open_tok): - return text.find(close_tok) - return text.find(close_tok) + find_closing(text[text.find(close_tok)+1:], open_tok, close_tok) + 1 - -def unpretty_string(s): - s = s.strip() - while s.find(" ") != -1: - s = s.replace(" "," ") - while s.find("\t") != -1: - s = s.replace("\t"," ") - s = s.replace(" (","(") - return s - -class WEnum: - name = None - namespace = None - values = [] - - def from_string(str_def, namespace, line_number): - str_def = str_def.strip() - if not str.startswith(str_def, "enum "): - return None - if str_def.count(";") != 1: - return None - str_def = str_def[5:] - enum = WEnum() - split = str_def.split(":") - if(len(split) != 2): - return None - enum.name = split[0].strip() - if enum.name not in enum_names: - return None - str_def = split[1] - if str_def.count("{") != str_def.count("}") != 1: - return None - if len(str_def) < str_def.find("}")+2 or str_def[str_def.find("}")+1] != ';': - return None - str_def = str_def.split("{")[-1].split("}")[0] - enum.values = [] - for val in str_def.split(','): - enum.values.append(val.strip().split('=')[0].strip()) - enum.namespace = namespace - return enum - - def gen_boost_py(self): - text = "\n\t\tenum_<" + self.namespace + "::" + self.name + ">(\"" + self.name + "\")\n" - for value in self.values: - text += "\t\t\t.value(\"" + value + "\"," + self.namespace + "::" + value + ")\n" - text += "\t\t\t;\n" - return text - - def __str__(self): - ret = "Enum " + self.namespace + "::" + self.name + "(\n" - for val in self.values: - ret = ret + "\t" + val + "\n" - return ret + ")" - - def __repr__(self): - return __str__(self) - -class WConstructor: - orig_text = None - args = [] - containing_file = None - member_of = None - duplicate = False - protected = False - - def __init__(self, containing_file, class_): - self.orig_text = "Auto generated default constructor" - self.args = [] - self.containing_file = containing_file - self.member_of = class_ - self.protected = False - - def from_string(str_def, containing_file, class_, line_number, protected = False): - if class_ == None: - return None - if str_def.count("delete;") > 0: - return None - con = WConstructor(containing_file, class_) - con.orig_text = str_def - con.args = [] - con.duplicate = False - con.protected = protected - if str.startswith(str_def, "inline "): - str_def = str_def[7:] - if not str.startswith(str_def, class_.name + "("): - return None - str_def = str_def[len(class_.name)+1:] - found = find_closing(str_def, "(", ")") - if found == -1: - return None - str_def = str_def[0:found].strip() - if len(str_def) == 0: - return con - args = split_list(str_def, ",") - for i, arg in enumerate(args): - parsed = Attribute.from_string(arg.strip(), containing_file, line_number) - if parsed == None: - return None - # Only allow std::source_location as defaulted last argument, and - # don't append so it takes default value - if parsed.wtype.name in ["std::source_location", "source_location"]: - if parsed.default_value is None or i != len(args) - 1: - debug("std::source_location not defaulted last arg of " + class_.name + " is unsupported", 2) - return None - continue - con.args.append(parsed) - return con - - def gen_decl(self): - if self.duplicate or self.protected: - return "" - text = "\n\t\t// WRAPPED from \"" + self.orig_text.replace("\n"," ") + "\" in " + self.containing_file - text += "\n\t\t" + self.member_of.name + "(" - for arg in self.args: - text += arg.gen_listitem() + ", " - if len(self.args) > 0: - text = text[:-2] - text += ");\n" - return text - - def gen_decl_derive(self): - if self.duplicate or self.protected: - return "" - text = "\n\t\t// WRAPPED from \"" + self.orig_text.replace("\n"," ") + "\" in " + self.containing_file - text += "\n\t\t" + self.member_of.name + "(" - for arg in self.args: - text += arg.gen_listitem() + ", " - if len(self.args) > 0: - text = text[:-2] - text += ")" - if len(self.args) == 0: - return text + "{}" - text += " : " - text += self.member_of.namespace + "::" + self.member_of.name + "(" - for arg in self.args: - text += arg.gen_call() + ", " - if len(self.args) > 0: - text = text[:-2] - text += "){}\n" - return text - - def gen_decl_wrapperclass(self): - if self.duplicate or self.protected: - return "" - text = "\n\t\t// WRAPPED from \"" + self.orig_text.replace("\n"," ") + "\" in " + self.containing_file - text += "\n\t\t" + self.member_of.name + "Wrap(" - for arg in self.args: - text += arg.gen_listitem() + ", " - if len(self.args) > 0: - text = text[:-2] - text += ")" - if len(self.args) == 0: - return text + "{}" - text += " : " - text += self.member_of.name + "(" - for arg in self.args: - text += arg.gen_call() + ", " - if len(self.args) > 0: - text = text[:-2] - text += "){}\n" - return text - - def gen_decl_hash_py(self): - text = self.member_of.name + "(" - for arg in self.args: - text += arg.gen_listitem_hash() + ", " - if len(self.args) > 0: - text = text[:-2] - text += ");" - return text - - def gen_def(self): - if self.duplicate or self.protected: - return "" - text = "\n\t// WRAPPED from \"" + self.orig_text.replace("\n"," ") + "\" in " + self.containing_file - text += "\n\t" + self.member_of.name + "::" + self.member_of.name + "(" - for arg in self.args: - text += arg.gen_listitem() + ", " - if len(self.args) > 0: - text = text[:-2] - text +=")\n\t{" - for arg in self.args: - text += arg.gen_translation() - if self.member_of.link_type != link_types.derive: - text += "\n\t\tthis->ref_obj = new " + self.member_of.namespace + "::" + self.member_of.name + "(" - for arg in self.args: - text += arg.gen_call() + ", " - if len(self.args) > 0: - text = text[:-2] - if self.member_of.link_type != link_types.derive: - text += ");" - if self.member_of.link_type == link_types.global_list: - text += "\n\t\tthis->" + self.member_of.id_.varname + " = this->ref_obj->" + self.member_of.id_.varname + ";" - for arg in self.args: - text += arg.gen_cleanup() - text += "\n\t}\n" - return text - - def gen_boost_py(self): - if self.duplicate or self.protected or len(self.args) == 0: - return "" - text = "\n\t\t\t.def(init" - text += "<" - for a in self.args: - text += a.gen_listitem_hash() + ", " - text = text[0:-2] + ">())" - return text - -class WFunction: - orig_text = None - is_static = False - is_inline = False - is_virtual = False - ret_attr_type = attr_types.default - is_operator = False - ret_type = None - name = None - alias = None - args = [] - containing_file = None - member_of = None - duplicate = False - namespace = "" - - def from_string(str_def, containing_file, class_, line_number, namespace): - if str_def.count("delete;") > 0: - return None - func = WFunction() - func.is_static = False - func.is_inline = False - func.is_virtual = False - func.is_const = False - func.ret_attr_type = attr_types.default - func.is_operator = False - func.member_of = None - func.orig_text = str_def - func.args = [] - func.containing_file = containing_file - func.member_of = class_ - func.duplicate = False - func.namespace = namespace - str_def = str_def.replace("operator ","operator") - - # remove attributes from the start - if str.startswith(str_def, "[[") and "]]" in str_def: - str_def = str_def[str_def.find("]]")+2:] - - if str.startswith(str_def, "static "): - func.is_static = True - str_def = str_def[7:] - else: - func.is_static = False - if str.startswith(str_def, "inline "): - func.is_inline = True - str_def = str_def[7:] - else: - func.is_inline = False - if str.startswith(str_def, "virtual "): - func.is_virtual = True - str_def = str_def[8:] - else: - func.is_virtual = False - - if str_def.count(" ") == 0: - return None - - parts = split_list(str_def.strip(), " ") - - prefix = "" - i = 0 - for part in parts: - if part in ["unsigned", "long", "short", "const"]: - prefix += part + " " - i += 1 - else: - break - parts = parts[i:] - - if len(parts) <= 1: - return None - - func.ret_type = WType.from_string(prefix + parts[0], containing_file, line_number) - - if func.ret_type == None: - return None - - str_def = parts[1] - for part in parts[2:]: - str_def = str_def + " " + part - - found = str_def.find("(") - if found == -1 or (str_def.find(" ") != -1 and found > str_def.find(" ")): - return None - func.name = str_def[:found] - str_def = str_def[found:] - if func.name.find("operator") != -1 and str.startswith(str_def, "()("): - func.name += "()" - str_def = str_def[2:] - str_def = str_def[1:] - if func.name.find("operator") != -1: - func.is_operator = True - if func.name.find("*") == 0: - func.name = func.name.replace("*", "") - func.ret_type.attr_type = attr_types.star - if func.name.find("&&") == 0: - func.name = func.name.replace("&&", "") - func.ret_type.attr_type = attr_types.ampamp - if func.name.find("&") == 0: - func.name = func.name.replace("&", "") - func.ret_type.attr_type = attr_types.amp - - found = find_closing(str_def, "(", ")") - if found == -1: - return None - - post_qualifiers = str_def[found + 1:].lstrip().replace("{", " {") + " " - if post_qualifiers.startswith("const "): - func.is_const = True - - str_def = str_def[0:found] - if func.name in blacklist_methods: - return None - if func.namespace != None and func.namespace != "": - if (func.namespace + "::" + func.name) in blacklist_methods: - return None - if func.member_of != None: - if (func.namespace + "::" + func.member_of.name + "::" + func.name) in blacklist_methods: - return None - if func.is_operator and func.name.replace(" ","").replace("operator","").split("::")[-1] not in wrappable_operators: - return None - - testname = func.name - if func.is_operator: - testname = testname[:testname.find("operator")] - if testname.count(")") != 0 or testname.count("(") != 0 or testname.count("~") != 0 or testname.count(";") != 0 or testname.count(">") != 0 or testname.count("<") != 0 or testname.count("throw") != 0: - return None - - func.alias = func.name - if func.name in keyword_aliases: - func.alias = keyword_aliases[func.name] - str_def = str_def[:found].strip() - if(len(str_def) == 0): - return func - args = split_list(str_def, ",") - for i, arg in enumerate(args): - if arg.strip() == "...": - continue - parsed = Attribute.from_string(arg.strip(), containing_file, line_number, owner_fn_name=func.name) - if parsed == None: - return None - # Only allow std::source_location as defaulted last argument, and - # don't append so it takes default value - if parsed.wtype.name in ["std::source_location", "source_location"]: - if parsed.default_value is None or i != len(args) - 1: - debug("std::source_location not defaulted last arg of " + func.name + " is unsupported", 2) - return None - continue - func.args.append(parsed) - return func - - @property - def mangled_name(self): - mangled_typename = lambda code: code.replace("::", "_").replace("<","_").replace(">","_") \ - .replace(" ","").replace("*","").replace(",","") - - return self.name + "".join( - f"__{mangled_typename(arg.wtype.gen_text_cpp())}" for arg in self.args - ) - - def gen_alias(self): - self.alias = self.mangled_name - - def gen_post_qualifiers(self, derived=False): - if self.member_of != None and self.member_of.link_type == link_types.derive and self.is_virtual and derived: - # we drop the qualifiers when deriving callbacks to be implemented in Python - return '' - return ' const' if self.is_const else '' - - def gen_decl(self): - if self.duplicate: - return "" - text = "\n\t\t// WRAPPED from \"" + self.orig_text.replace("\n"," ") + "\" in " + self.containing_file - text += "\n\t\t" - if self.is_static: - text += "static " - text += self.ret_type.gen_text() + " " + self.alias + "(" - for arg in self.args: - if arg.coerce_arg: - continue - text += arg.gen_listitem() - text += ", " - if len(self.args) > 0: - text = text[:-2] - text += f"){self.gen_post_qualifiers()};\n" - return text - - def gen_decl_virtual(self): - if self.duplicate: - return "" - if not self.is_virtual: - return self.gen_decl() - text = "\n\t\t// WRAPPED from \"" + self.orig_text.replace("\n"," ") + "\" in " + self.containing_file - text += "\n\t\tvirtual " - if self.is_static: - text += "static " - text += self.ret_type.gen_text() + " py_" + self.alias + "(" - for arg in self.args: - text += arg.gen_listitem() - text += ", " - if len(self.args) > 0: - text = text[:-2] - text += ")" - if len(self.args) == 0 and self.ret_type.name == "void": - text += "{}" - else: - text += "\n\t\t{" - for arg in self.args: - text += "\n\t\t\t(void)" + arg.gen_varname() + ";" - if self.ret_type.name == "void": - pass - elif self.ret_type.name == "bool": - text += "\n\t\t\treturn false;" - else: - raise NotImplementedError(self.ret_type.name) - text += "\n\t\t}\n" - text += "\n\t\tvirtual " - if self.is_static: - text += "static " - text += self.ret_type.gen_text() + " " + self.name + "(" - for arg in self.args: - text += arg.gen_listitem_cpp() - text += ", " - if len(self.args) > 0: - text = text[:-2] - text += f"){self.gen_post_qualifiers()} override;\n" - return text - - def gen_decl_hash_py(self): - text = self.ret_type.gen_text() + " " + self.alias + "(" - for arg in self.args: - text += arg.gen_listitem_hash() + ", " - if len(self.args) > 0: - text = text[:-2] - text += ");" - return text - - def gen_def(self): - if self.duplicate: - return "" - text = "\n\t// WRAPPED from \"" + self.orig_text.replace("\n"," ") + "\" in " + self.containing_file - text += "\n\t" + self.ret_type.gen_text() + " " - if self.member_of != None: - text += self.member_of.name + "::" - text += self.alias + "(" - for arg in self.args: - if arg.coerce_arg: - continue - text += arg.gen_listitem() - text += ", " - if len(self.args) > 0: - text = text[:-2] - text += f"){self.gen_post_qualifiers()}\n\t{{" - for arg in self.args: - text += arg.gen_translation() - text += "\n\t\t" - if self.ret_type.name != "void": - if self.ret_type.name in known_containers: - text += self.ret_type.gen_text_cpp() - else: - text += self.ret_type.gen_text() - if self.ret_type.name in classnames or (self.ret_type.name in known_containers and self.ret_type.attr_type == attr_types.star): - text += "*" - text += " ret_ = " - if self.ret_type.name in classnames: - text += self.ret_type.name + "::get_py_obj(" - if self.member_of == None: - text += "::" + self.namespace + "::" + self.name + "(" - elif self.is_static: - text += self.member_of.namespace + "::" + self.member_of.name + "::" + self.name + "(" - else: - text += "this->get_cpp_obj()->" + self.name + "(" - for arg in self.args: - text += arg.coerce_arg or arg.gen_call() - text += ", " - if len(self.args) > 0: - text = text[:-2] - if self.ret_type.name in classnames: - text += ")" - text += ");" - for arg in self.args: - text += arg.gen_cleanup() - if self.ret_type.name != "void": - if self.ret_type.name in classnames: - text += "\n\t\treturn *ret_;" - elif self.ret_type.name in known_containers: - text += known_containers[self.ret_type.name].translate_cpp("ret_", self.ret_type.cont.args, "\n\t\t", self.ret_type.attr_type == attr_types.star) - text += "\n\t\treturn ret____tmp;" - else: - text += "\n\t\treturn ret_;" - text += "\n\t}\n" - return text - - def gen_def_virtual(self): - if self.duplicate: - return "" - if not self.is_virtual: - return self.gen_def() - text = "\n\t// WRAPPED from \"" + self.orig_text.replace("\n"," ") + "\" in " + self.containing_file - text += "\n\t" - if self.is_static: - text += "static " - text += self.ret_type.gen_text() + " " + self.member_of.name + "::" + self.name + "(" - for arg in self.args: - text += arg.gen_listitem_cpp() - text += ", " - if len(self.args) > 0: - text = text[:-2] - text += f"){self.gen_post_qualifiers()}\n\t{{" - for arg in self.args: - text += arg.gen_translation_cpp() - return_stmt = "return " if self.ret_type.name != "void" else "" - text += f"\n\t\t{return_stmt}" - if self.member_of == None: - text += "::" + self.namespace + "::" + self.alias + "(" - elif self.is_static: - text += self.member_of.namespace + "::" + self.member_of.name + "::" + self.name + "(" - else: - text += f"const_cast<{self.member_of.name}*>(this)->py_" + self.alias + "(" - for arg in self.args: - text += arg.gen_call_cpp() + ", " - if len(self.args) > 0: - text = text[:-2] - if self.ret_type.name in classnames: - text += ")" - text += ");" - for arg in self.args: - text += arg.gen_cleanup() - text += "\n\t}\n" - return text - - def gen_default_impl(self): - if self.duplicate: - return "" - if not self.is_virtual: - return "" - text = "\n\n\t\t" + self.ret_type.gen_text() + " py_" + self.alias + "(" - for arg in self.args: - text += arg.gen_listitem() + ", " - if len(self.args) > 0: - text = text[:-2] - - call_string = "py_" + self.alias + "(" - for arg in self.args: - call_string += arg.gen_varname() + ", " - if len(self.args) > 0: - call_string = call_string[0:-2] - call_string += ");" - - return_stmt = "return " if self.ret_type.name != "void" else "" - - text += ")\n\t\t{" - text += "\n\t\t\tif (boost::python::override py_" + self.alias + " = this->get_override(\"py_" + self.alias + "\")) {" - text += "\n\t\t\t\ttry {" - text += f"\n\t\t\t\t\t{return_stmt}" + call_string - text += "\n\t\t\t\t} catch (boost::python::error_already_set &) {" - text += "\n\t\t\t\t\tlog_python_exception_as_error();" - text += "\n\t\t\t\t}" - text += "\n\t\t\t} else {" - text += f"\n\t\t\t\t{return_stmt}" + self.member_of.name + "::" + call_string - text += "\n\t\t\t}" - text += "\n\t\t}" - - text += "\n\n\t\t" + self.ret_type.gen_text() + " default_py_" + self.alias + "(" - for arg in self.args: - text += arg.gen_listitem() + ", " - if len(self.args) > 0: - text = text[:-2] - text += f")\n\t\t{{" - text += f"\n\t\t\t{return_stmt}this->" + self.member_of.name + "::" + call_string - text += "\n\t\t}" - return text - - - def gen_boost_py(self): - if self.duplicate: - return "" - if self.member_of == None: - text = "\n\t\tdef" - else: - text = "\n\t\t\t.def" - if len(self.args) > -1: - if self.ret_type.name in known_containers: - text += "<" + known_containers[self.ret_type.name].typename + " " - else: - text += "<" + self.ret_type.name + " " - if self.member_of == None or self.is_static: - text += "(*)(" - else: - text += "(" + self.member_of.name + "::*)(" - for a in self.args: - if a.coerce_arg: - continue - text += a.gen_listitem_hash() + ", " - if len(self.args) > 0: - text = text[0:-2] + f"){self.gen_post_qualifiers(True)}>" - else: - text += f"void){self.gen_post_qualifiers(True)}>" - - if self.is_operator: - text += "(\"" + wrappable_operators[self.name.replace("operator","")] + "\"" - else: - if self.member_of != None and self.member_of.link_type == link_types.derive and self.is_virtual: - text += "(\"py_" + self.alias + "\"" - else: - text += "(\"" + self.alias + "\"" - if self.member_of != None: - text += ", &" + self.member_of.name + "::" - if self.member_of.link_type == link_types.derive and self.is_virtual: - text += "py_" + self.alias - text += ", &" + self.member_of.name + "Wrap::default_py_" + self.alias - else: - text += self.alias - - text += ")" - else: - text += ", " + "YOSYS_PYTHON::" + self.alias + ");" - return text - -class WMember: - orig_text = None - wtype = attr_types.default - name = None - containing_file = None - member_of = None - namespace = "" - is_const = False - - def from_string(str_def, containing_file, class_, line_number, namespace): - member = WMember() - member.orig_text = str_def - member.wtype = None - member.name = "" - member.containing_file = containing_file - member.member_of = class_ - member.namespace = namespace - member.is_const = False - - if str.startswith(str_def, "const "): - member.is_const = True - str_def = str_def[6:] - - if str_def.count(" ") == 0: - return None - - parts = split_list(str_def.strip(), " ") - - prefix = "" - i = 0 - for part in parts: - if part in ["unsigned", "long", "short"]: - prefix += part + " " - i += 1 - else: - break - parts = parts[i:] - - if len(parts) <= 1: - return None - - member.wtype = WType.from_string(prefix + parts[0], containing_file, line_number) - - if member.wtype == None: - return None - - str_def = parts[1] - for part in parts[2:]: - str_def = str_def + " " + part - - if str_def.find("(") != -1 or str_def.find(")") != -1 or str_def.find("{") != -1 or str_def.find("}") != -1: - return None - - found = str_def.find(";") - if found == -1: - return None - - found_eq = str_def.find("=") - if found_eq != -1: - found = found_eq - - member.name = str_def[:found] - str_def = str_def[found+1:] - if member.name.find("*") == 0: - member.name = member.name.replace("*", "") - member.wtype.attr_type = attr_types.star - if member.name.find("&&") == 0: - member.name = member.name.replace("&&", "") - member.wtype.attr_type = attr_types.ampamp - if member.name.find("&") == 0: - member.name = member.name.replace("&", "") - member.wtype.attr_type = attr_types.amp - - if(len(str_def.strip()) != 0): - return None - - if len(member.name.split(",")) > 1: - member_list = [] - for name in member.name.split(","): - name = name.strip(); - member_list.append(WMember()) - member_list[-1].orig_text = member.orig_text - member_list[-1].wtype = member.wtype - member_list[-1].name = name - member_list[-1].containing_file = member.containing_file - member_list[-1].member_of = member.member_of - member_list[-1].namespace = member.namespace - member_list[-1].is_const = member.is_const - return member_list - - return member - - def gen_decl(self): - text = "\n\t\t" + self.wtype.gen_text() + " get_var_py_" + self.name + "();\n" - if self.is_const: - return text - if self.wtype.name in classnames: - text += "\n\t\tvoid set_var_py_" + self.name + "(" + self.wtype.gen_text() + " *rhs);\n" - else: - text += "\n\t\tvoid set_var_py_" + self.name + "(" + self.wtype.gen_text() + " rhs);\n" - return text - - def gen_def(self): - text = "\n\t" + self.wtype.gen_text() + " " + self.member_of.name +"::get_var_py_" + self.name + "()" - text += "\n\t{\n\t\t" - if self.wtype.attr_type == attr_types.star: - text += "if(this->get_cpp_obj()->" + self.name + " == NULL)\n\t\t\t" - text += "throw std::runtime_error(\"Member \\\"" + self.name + "\\\" is NULL\");\n\t\t" - if self.wtype.name in known_containers: - text += self.wtype.gen_text_cpp() - else: - text += self.wtype.gen_text() - - if self.wtype.name in classnames or (self.wtype.name in known_containers and self.wtype.attr_type == attr_types.star): - text += "*" - text += " ret_ = " - if self.wtype.name in classnames: - text += self.wtype.name + "::get_py_obj(" - if self.wtype.attr_type != attr_types.star: - text += "&" - text += "this->get_cpp_obj()->" + self.name - if self.wtype.name in classnames: - text += ")" - text += ";" - - if self.wtype.name in classnames: - text += "\n\t\treturn *ret_;" - elif self.wtype.name in known_containers: - text += known_containers[self.wtype.name].translate_cpp("ret_", self.wtype.cont.args, "\n\t\t", self.wtype.attr_type == attr_types.star) - text += "\n\t\treturn ret____tmp;" - else: - text += "\n\t\treturn ret_;" - text += "\n\t}\n" - - if self.is_const: - return text - - ret = Attribute(self.wtype, "rhs"); - - if self.wtype.name in classnames: - text += "\n\tvoid " + self.member_of.name+ "::set_var_py_" + self.name + "(" + self.wtype.gen_text() + " *rhs)" - else: - text += "\n\tvoid " + self.member_of.name+ "::set_var_py_" + self.name + "(" + self.wtype.gen_text() + " rhs)" - text += "\n\t{" - text += ret.gen_translation() - text += "\n\t\tthis->get_cpp_obj()->" + self.name + " = " + ret.gen_call() + ";" - text += "\n\t}\n" - - return text; - - def gen_boost_py(self): - text = "\n\t\t\t.add_property(\"" + self.name + "\", &" + self.member_of.name + "::get_var_py_" + self.name - if not self.is_const: - text += ", &" + self.member_of.name + "::set_var_py_" + self.name - text += ")" - return text - -class WGlobal: - orig_text = None - wtype = attr_types.default - name = None - containing_file = None - namespace = "" - is_const = False - - def from_string(str_def, containing_file, line_number, namespace): - glbl = WGlobal() - glbl.orig_text = str_def - glbl.wtype = None - glbl.name = "" - glbl.containing_file = containing_file - glbl.namespace = namespace - glbl.is_const = False - - if not str.startswith(str_def, "extern"): - return None - str_def = str_def[7:] - - if str.startswith(str_def, "const "): - glbl.is_const = True - str_def = str_def[6:] - - if str_def.count(" ") == 0: - return None - - parts = split_list(str_def.strip(), " ") - - prefix = "" - i = 0 - for part in parts: - if part in ["unsigned", "long", "short"]: - prefix += part + " " - i += 1 - else: - break - parts = parts[i:] - - if len(parts) <= 1: - return None - - glbl.wtype = WType.from_string(prefix + parts[0], containing_file, line_number) - - if glbl.wtype == None: - return None - - str_def = parts[1] - for part in parts[2:]: - str_def = str_def + " " + part - - if str_def.find("(") != -1 or str_def.find(")") != -1 or str_def.find("{") != -1 or str_def.find("}") != -1: - return None - - found = str_def.find(";") - if found == -1: - return None - - found_eq = str_def.find("=") - if found_eq != -1: - found = found_eq - - glbl.name = str_def[:found] - str_def = str_def[found+1:] - if glbl.name.find("*") == 0: - glbl.name = glbl.name.replace("*", "") - glbl.wtype.attr_type = attr_types.star - if glbl.name.find("&&") == 0: - glbl.name = glbl.name.replace("&&", "") - glbl.wtype.attr_type = attr_types.ampamp - if glbl.name.find("&") == 0: - glbl.name = glbl.name.replace("&", "") - glbl.wtype.attr_type = attr_types.amp - - if(len(str_def.strip()) != 0): - return None - - if len(glbl.name.split(",")) > 1: - glbl_list = [] - for name in glbl.name.split(","): - name = name.strip(); - glbl_list.append(WGlobal()) - glbl_list[-1].orig_text = glbl.orig_text - glbl_list[-1].wtype = glbl.wtype - glbl_list[-1].name = name - glbl_list[-1].containing_file = glbl.containing_file - glbl_list[-1].namespace = glbl.namespace - glbl_list[-1].is_const = glbl.is_const - return glbl_list - - return glbl - - def gen_def(self): - text = "\n\t" - if self.is_const: - text += "const " - text += self.wtype.gen_text() + " get_var_py_" + self.name + "()" - text += "\n\t{\n\t\t" - if self.wtype.attr_type == attr_types.star: - text += "if(" + self.namespace + "::" + self.name + " == NULL)\n\t\t\t" - text += "throw std::runtime_error(\"" + self.namespace + "::" + self.name + " is NULL\");\n\t\t" - if self.wtype.name in known_containers: - text += self.wtype.gen_text_cpp() - else: - if self.is_const: - text += "const " - text += self.wtype.gen_text() - - if self.wtype.name in classnames or (self.wtype.name in known_containers and self.wtype.attr_type == attr_types.star): - text += "*" - text += " ret_ = " - if self.wtype.name in classnames: - text += self.wtype.name + "::get_py_obj(" - if self.wtype.attr_type != attr_types.star: - text += "&" - text += self.namespace + "::" + self.name - if self.wtype.name in classnames: - text += ")" - text += ";" - - if self.wtype.name in classnames: - text += "\n\t\treturn *ret_;" - elif self.wtype.name in known_containers: - text += known_containers[self.wtype.name].translate_cpp("ret_", self.wtype.cont.args, "\n\t\t", self.wtype.attr_type == attr_types.star) - text += "\n\t\treturn ret____tmp;" - else: - text += "\n\t\treturn ret_;" - text += "\n\t}\n" - - if self.is_const: - return text - - ret = Attribute(self.wtype, "rhs"); - - if self.wtype.name in classnames: - text += "\n\tvoid set_var_py_" + self.name + "(" + self.wtype.gen_text() + " *rhs)" - else: - text += "\n\tvoid set_var_py_" + self.name + "(" + self.wtype.gen_text() + " rhs)" - text += "\n\t{" - text += ret.gen_translation() - text += "\n\t\t" + self.namespace + "::" + self.name + " = " + ret.gen_call() + ";" - text += "\n\t}\n" - - return text; - - def gen_boost_py(self): - text = "\n\t\t\t.add_static_property(\"" + self.name + "\", &" + "YOSYS_PYTHON::get_var_py_" + self.name - if not self.is_const: - text += ", &YOSYS_PYTHON::set_var_py_" + self.name - text += ")" - return text - -def concat_namespace(tuple_list): - if len(tuple_list) == 0: - return "" - ret = "" - for namespace in tuple_list: - ret += "::" + namespace[0] - return ret[2:] - -def calc_ident(text): - if len(text) == 0 or text[0] != ' ': - return 0 - return calc_ident(text[1:]) + 1 - -def assure_length(text, length, left = False): - if len(text) > length: - return text[:length] - if left: - return text + " "*(length - len(text)) - return " "*(length - len(text)) + text - -def nesting_delta(s): - return s.count("{") - s.count("}") - -def parse_header(source): - debug("Parsing " + source.name + ".pyh",1) - source_file = open(source.name + ".pyh", "r") - - source_text = [] - in_line = source_file.readline() - - namespaces = [] - - while(in_line): - if(len(in_line)>1): - source_text.append(in_line.replace("char *", "char_p ").replace("char* ", "char_p ")) - in_line = source_file.readline() - - i = 0 - - namespaces = [] - classes = [] - private_segment = False - - while i < len(source_text): - line = source_text[i].replace("YOSYS_NAMESPACE_BEGIN", " namespace YOSYS_NAMESPACE{").replace("YOSYS_NAMESPACE_END"," }") - ugly_line = unpretty_string(line) - debug(f"READ:>> {line}", 2) - - # for anonymous unions, ignore union enclosure by skipping start line and replacing end line with new line - if 'union {' in line: - j = i+1 - while j < len(source_text): - union_line = source_text[j] - if '};' in union_line: - source_text[j] = '\n' - break - j += 1 - if j != len(source_text): - i += 1 - continue - - if str.startswith(ugly_line, "namespace "):# and ugly_line.find("std") == -1 and ugly_line.find("__") == -1: - namespace_name = ugly_line[10:].replace("{","").strip() - namespaces.append((namespace_name, ugly_line.count("{"))) - debug("-----NAMESPACE " + concat_namespace(namespaces) + "-----",3) - i += 1 - continue - - if len(namespaces) != 0: - namespaces[-1] = (namespaces[-1][0], namespaces[-1][1] + nesting_delta(ugly_line)) - if namespaces[-1][1] == 0: - debug("-----END NAMESPACE " + concat_namespace(namespaces) + "-----",3) - namespaces.pop() - i += 1 - continue - - if (str.startswith(ugly_line, "struct ") or str.startswith(ugly_line, "class")) and ugly_line.count(";") == 0: - # Opening a record declaration which isn't a forward declaration - struct_name = ugly_line.split(" ")[1].split("::")[-1] - impl_namespaces = ugly_line.split(" ")[1].split("::")[:-1] - complete_namespace = concat_namespace(namespaces) - for namespace in impl_namespaces: - complete_namespace += "::" + namespace - debug("\tFound " + struct_name + " in " + complete_namespace,2) - - base_class_name = None - if len(ugly_line.split(" : ")) > 1: # class is derived - deriv_str = ugly_line.split(" : ")[1] - if len(deriv_str.split("::")) > 1: # namespace of base class is given - base_class_name = deriv_str.split("::", 1)[1] - else: - base_class_name = deriv_str.split(" ")[0] - debug("\t " + struct_name + " is derived from " + base_class_name,2) - base_class = class_by_name(base_class_name) - - c = (class_by_name(struct_name), ugly_line.count("{"))#calc_ident(line)) - debug(f"switch to {struct_name} in namespace {namespaces}", 2) - if struct_name in classnames: - c[0].namespace = complete_namespace - c[0].base_class = base_class - classes.append(c) - i += 1 - continue - - if len(classes): - c = (classes[-1][0], classes[-1][1] + nesting_delta(ugly_line)) - classes[-1] = c - if c[1] == 0: - if c[0] == None: - debug("\tExiting unknown class", 3) - else: - debug("\tExiting class " + c[0].name, 3) - classes.pop() - private_segment = False - i += 1 - continue - - class_ = classes[-1] if classes else None - - if class_ != None and (line.find("private:") != -1 or line.find("protected:") != -1): - private_segment = True - i += 1 - continue - if class_ != None and line.find("public:") != -1: - private_segment = False - i += 1 - continue - - candidate = None - - if private_segment and class_ != None and class_[0] != None: - candidate = WConstructor.from_string(ugly_line, source.name, class_[0], i, True) - if candidate != None: - debug("\t\tFound constructor of class \"" + class_[0].name + "\" in namespace " + concat_namespace(namespaces),2) - class_[0].found_constrs.append(candidate) - i += 1 - continue - - if not private_segment and (class_ == None or class_[0] != None): - if class_ != None: - candidate = WFunction.from_string(ugly_line, source.name, class_[0], i, concat_namespace(namespaces)) - else: - candidate = WFunction.from_string(ugly_line, source.name, None, i, concat_namespace(namespaces)) - if candidate != None and candidate.name.find("::") == -1: - if class_ == None: - debug("\tFound unowned function \"" + candidate.name + "\" in namespace " + concat_namespace(namespaces),2) - unowned_functions.append(candidate) - - # generate log aliases - if candidate.name.startswith("log_formatted"): - alias = candidate.name.replace("log_formatted", "log") - if alias == "log_string": - alias = "log" - copied_candidate = copy.copy(candidate) - copied_candidate.alias = alias - unowned_functions.append(copied_candidate) - - else: - debug("\t\tFound function \"" + candidate.name + "\" of class \"" + class_[0].name + "\" in namespace " + concat_namespace(namespaces),2) - class_[0].found_funs.append(candidate) - else: - candidate = WEnum.from_string(ugly_line, concat_namespace(namespaces), i) - if candidate != None: - enums.append(candidate) - debug("\tFound enum \"" + candidate.name + "\" in namespace " + concat_namespace(namespaces),2) - elif class_ != None and class_[1] == 1: - candidate = WConstructor.from_string(ugly_line, source.name, class_[0], i) - if candidate != None: - debug("\t\tFound constructor of class \"" + class_[0].name + "\" in namespace " + concat_namespace(namespaces),2) - class_[0].found_constrs.append(candidate) - else: - candidate = WMember.from_string(ugly_line, source.name, class_[0], i, concat_namespace(namespaces)) - if candidate != None: - if type(candidate) == list: - for c in candidate: - debug("\t\tFound member \"" + c.name + "\" of class \"" + class_[0].name + "\" of type \"" + c.wtype.name + "\"", 2) - class_[0].found_vars.extend(candidate) - else: - debug("\t\tFound member \"" + candidate.name + "\" of class \"" + class_[0].name + "\" of type \"" + candidate.wtype.name + "\"", 2) - class_[0].found_vars.append(candidate) - if candidate == None and class_ == None: - candidate = WGlobal.from_string(ugly_line, source.name, i, concat_namespace(namespaces)) - if candidate != None: - if type(candidate) == list: - for c in candidate: - glbls.append(c) - debug("\tFound global \"" + c.name + "\" in namespace " + concat_namespace(namespaces), 2) - else: - glbls.append(candidate) - debug("\tFound global \"" + candidate.name + "\" in namespace " + concat_namespace(namespaces), 2) - - j = i - line = unpretty_string(line) - while candidate == None and j+1 < len(source_text) and line.count(';') <= 1 and line.count("(") >= line.count(")"): - j += 1 - line = line + "\n" + unpretty_string(source_text[j]) - if class_ != None: - candidate = WFunction.from_string(ugly_line, source.name, class_[0], i, concat_namespace(namespaces)) - else: - candidate = WFunction.from_string(ugly_line, source.name, None, i, concat_namespace(namespaces)) - if candidate != None and candidate.name.find("::") == -1: - if class_ == None: - debug("\tFound unowned function \"" + candidate.name + "\" in namespace " + concat_namespace(namespaces),2) - unowned_functions.append(candidate) - else: - debug("\t\tFound function \"" + candidate.name + "\" of class \"" + class_[0].name + "\" in namespace " + concat_namespace(namespaces),2) - class_[0].found_funs.append(candidate) - continue - candidate = WEnum.from_string(line, concat_namespace(namespaces), i) - if candidate != None: - enums.append(candidate) - debug("\tFound enum \"" + candidate.name + "\" in namespace " + concat_namespace(namespaces),2) - continue - if class_ != None: - candidate = WConstructor.from_string(line, source.name, class_[0], i) - if candidate != None: - debug("\t\tFound constructor of class \"" + class_[0].name + "\" in namespace " + concat_namespace(namespaces),2) - class_[0].found_constrs.append(candidate) - continue - if class_ == None: - candidate = WGlobal.from_string(line, source.name, i, concat_namespace(namespaces)) - if candidate != None: - if type(candidate) == list: - for c in candidate: - glbls.append(c) - debug("\tFound global \"" + c.name + "\" in namespace " + concat_namespace(namespaces), 2) - else: - glbls.append(candidate) - debug("\tFound global \"" + candidate.name + "\" in namespace " + concat_namespace(namespaces), 2) - continue - if candidate != None: - while i < j: - i += 1 - line = source_text[i].replace("YOSYS_NAMESPACE_BEGIN", " namespace YOSYS_NAMESPACE{").replace("YOSYS_NAMESPACE_END"," }") - ugly_line = unpretty_string(line) - if len(namespaces) != 0: - namespaces[-1] = (namespaces[-1][0], namespaces[-1][1] + nesting_delta(ugly_line)) - if namespaces[-1][1] == 0: - debug("-----END NAMESPACE " + concat_namespace(namespaces) + "-----",3) - namespaces.pop() - if len(classes): - c = (classes[-1][0] , classes[-1][1] + nesting_delta(ugly_line)) - classes[-1] = c - if c[1] == 0: - if c[0] == None: - debug("\tExiting unknown class", 3) - else: - debug("\tExiting class " + c[0].name, 3) - classes.pop() - private_segment = False - i += 1 - else: - i += 1 - -def debug(message, level): - if level <= debug.debug_level: - print(message) - -def expand_function(f): - fun_list = [] - arg_list = [] - for arg in f.args: - if arg.default_value != None and (arg.wtype.name.split(" ")[-1] in primitive_types or arg.wtype.name in enum_names or (arg.wtype.name in classnames and arg.default_value == "nullptr")): - fi = copy.deepcopy(f) - fi.args = copy.deepcopy(arg_list) - fun_list.append(fi) - arg_list.append(arg) - fun_list.append(f) - return fun_list - -def expand_functions(): - global unowned_functions - new_funs = [] - for fun in unowned_functions: - new_funs.extend(expand_function(fun)) - unowned_functions = new_funs - for source in sources: - for class_ in source.classes: - new_funs = [] - for fun in class_.found_funs: - new_funs.extend(expand_function(fun)) - class_.found_funs = new_funs - -def inherit_members(): - for source in sources: - for class_ in source.classes: - if class_.base_class: - base_funs = copy.deepcopy(class_.base_class.found_funs) - for fun in base_funs: - fun.member_of = class_ - fun.namespace = class_.namespace - base_vars = copy.deepcopy(class_.base_class.found_vars) - for var in base_vars: - var.member_of = class_ - var.namespace = class_.namespace - class_.found_funs.extend(base_funs) - class_.found_vars.extend(base_vars) - -def clean_duplicates(): - for source in sources: - for class_ in source.classes: - known_decls = {} - for fun in class_.found_funs: - if fun.gen_decl_hash_py() in known_decls: - debug("Multiple declarations of " + fun.gen_decl_hash_py(),3) - - other = known_decls[fun.gen_decl_hash_py()] - if fun.mangled_name == other.mangled_name: - fun.duplicate = True - debug("Disabled \"" + fun.gen_decl_hash_py() + "\"", 3) - continue - - other.gen_alias() - fun.gen_alias() - else: - known_decls[fun.gen_decl_hash_py()] = fun - known_decls = [] - for con in class_.found_constrs: - if con.gen_decl_hash_py() in known_decls: - debug("Multiple declarations of " + con.gen_decl_hash_py(),3) - con.duplicate = True - else: - known_decls.append(con.gen_decl_hash_py()) - known_decls = [] - for fun in unowned_functions: - if fun.gen_decl_hash_py() in known_decls: - debug("Multiple declarations of " + fun.gen_decl_hash_py(),3) - fun.duplicate = True - else: - known_decls.append(fun.gen_decl_hash_py()) - -def gen_wrappers(filename, debug_level_ = 0): - debug.debug_level = debug_level_ - for source in sources: - parse_header(source) - - expand_functions() - inherit_members() - clean_duplicates() - - import shutil - import math - col = shutil.get_terminal_size((80,20)).columns - debug("-"*col, 1) - debug("-"*math.floor((col-7)/2)+"SUMMARY"+"-"*math.ceil((col-7)/2), 1) - debug("-"*col, 1) - for source in sources: - for class_ in source.classes: - debug("Class " + assure_length(class_.name, len(max(classnames, key=len)), True) + " contains " + assure_length(str(len(class_.found_vars)), 3, False) + " member variables, "+ assure_length(str(len(class_.found_funs)), 3, False) + " methods and " + assure_length(str(len(class_.found_constrs)), 2, False) + " constructors", 1) - if len(class_.found_constrs) == 0: - class_.found_constrs.append(WConstructor(source.name, class_)) - debug(str(len(unowned_functions)) + " functions are unowned", 1) - debug(str(len(unowned_functions)) + " global variables", 1) - for enum in enums: - debug("Enum " + assure_length(enum.name, len(max(enum_names, key=len)), True) + " contains " + assure_length(str(len(enum.values)), 2, False) + " values", 1) - debug("-"*col, 1) - wrapper_file = open(filename, "w+") - wrapper_file.write( -"""/* - * yosys -- Yosys Open SYnthesis Suite - * - * Copyright (C) 2012 Claire Xenia Wolf - * - * 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. - * - * This is a generated file and can be overwritten by make - */ - -#ifdef WITH_PYTHON -""") - for source in sources: - wrapper_file.write("#include \""+source.name+".h\"\n") - wrapper_file.write(""" -#include -#include -#include -#include -#include -#include // std::streamsize -#include -#include // boost::iostreams::sink -#include -USING_YOSYS_NAMESPACE - -using std::string_view; - -namespace YOSYS_PYTHON { - - [[noreturn]] static void log_python_exception_as_error() { - PyErr_Print(); - log_error("Python interpreter encountered an exception.\\n"); - } - - struct YosysStatics{}; -""") - - for source in sources: - for wclass in source.classes: - wrapper_file.write("\n\tstruct " + wclass.name + ";") - - wrapper_file.write("\n") - - for source in sources: - for wclass in source.classes: - wrapper_file.write(wclass.gen_decl(source.name)) - - wrapper_file.write("\n") - - for source in sources: - for wclass in source.classes: - wrapper_file.write(wclass.gen_funs(source.name)) - - for fun in unowned_functions: - wrapper_file.write(fun.gen_def()) - - for glbl in glbls: - wrapper_file.write(glbl.gen_def()) - - wrapper_file.write(""" struct Initializer - { - Initializer() { - if(!Yosys::yosys_already_setup()) - { - Yosys::log_streams.push_back(&std::cout); - Yosys::log_error_stderr = true; - Yosys::yosys_setup(); - } - } - - Initializer(Initializer const &) {} - - ~Initializer() { - Yosys::yosys_shutdown(); - } - }; - - - /// source: https://stackoverflow.com/questions/26033781/converting-python-io-object-to-stdostream-when-using-boostpython?noredirect=1&lq=1 - /// @brief Type that implements the Boost.IOStream's Sink and Flushable - /// concept for writing data to Python object that support: - /// n = object.write(str) # n = None or bytes written - /// object.flush() # if flush exists, then it is callable - class PythonOutputDevice - { - public: - - // This class models both the Sink and Flushable concepts. - struct category - : boost::iostreams::sink_tag, - boost::iostreams::flushable_tag - {}; - - explicit - PythonOutputDevice(boost::python::object object) - : object_(object) - {} - - // Sink concept. - public: - - typedef char char_type; - - std::streamsize write(const char* buffer, std::streamsize buffer_size) - { - namespace python = boost::python; - // Copy the buffer to a python string. - python::str data(buffer, buffer_size); - - // Invoke write on the python object, passing in the data. The following - // is equivalent to: - // n = object_.write(data) - python::extract bytes_written( - object_.attr("write")(data)); - - // Per the Sink concept, return the number of bytes written. If the - // Python return value provides a numeric result, then use it. Otherwise, - // such as the case of a File object, use the buffer_size. - return bytes_written.check() - ? bytes_written - : buffer_size; - } - - // Flushable concept. - public: - - bool flush() - { - // If flush exists, then call it. - boost::python::object flush = object_.attr("flush"); - if (!flush.is_none()) - { - flush(); - } - - // Always return true. If an error occurs, an exception should be thrown. - return true; - } - - private: - boost::python::object object_; - }; - - /// @brief Use an auxiliary function to adapt the legacy function. - void log_to_stream(boost::python::object object) - { - // Create an ostream that delegates to the python object. - boost::iostreams::stream* output = new boost::iostreams::stream(object); - Yosys::log_streams.insert(Yosys::log_streams.begin(), output); - }; - - - BOOST_PYTHON_MODULE(libyosys) - { - using namespace boost::python; - - class_("Initializer"); - scope().attr("_hidden") = new Initializer(); - - def("log_to_stream", &log_to_stream); -""") - - for enum in enums: - wrapper_file.write(enum.gen_boost_py()) - - for source in sources: - for wclass in source.classes: - wrapper_file.write(wclass.gen_boost_py()) - - for fun in unowned_functions: - wrapper_file.write(fun.gen_boost_py()) - - wrapper_file.write("\n\n\t\tclass_(\"Yosys\")\n") - for glbl in glbls: - wrapper_file.write(glbl.gen_boost_py()) - wrapper_file.write("\t\t;\n") - - wrapper_file.write("\n\t}\n}\n#endif") - -def print_includes(): - for source in sources: - print(source.name + ".pyh") diff --git a/passes/cmds/plugin.cc b/passes/cmds/plugin.cc index afa830552..e1e888a85 100644 --- a/passes/cmds/plugin.cc +++ b/passes/cmds/plugin.cc @@ -24,12 +24,6 @@ # include #endif -#ifdef WITH_PYTHON -# include -# include -# include -#endif - YOSYS_NAMESPACE_BEGIN std::map loaded_plugins; @@ -57,23 +51,23 @@ void load_plugin(std::string filename, std::vector aliases) if (!is_loaded) { // Check if we're loading a python script - if(filename.find(".py") != std::string::npos) - { + if (filename.rfind(".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()); + 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(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( diff --git a/pyosys/.gitignore b/pyosys/.gitignore new file mode 100644 index 000000000..f9fbdf4f6 --- /dev/null +++ b/pyosys/.gitignore @@ -0,0 +1 @@ +wrappers.cc diff --git a/misc/__init__.py b/pyosys/__init__.py similarity index 99% rename from misc/__init__.py rename to pyosys/__init__.py index d74e3f5bd..4622464da 100644 --- a/misc/__init__.py +++ b/pyosys/__init__.py @@ -1,3 +1,4 @@ + import os import sys diff --git a/pyosys/generator.py b/pyosys/generator.py new file mode 100644 index 000000000..ebe89737e --- /dev/null +++ b/pyosys/generator.py @@ -0,0 +1,2039 @@ +#!/usr/bin/env python3 +# yosys -- Yosys Open SYnthesis Suite +# +# Copyright (C) 2012 Claire Xenia Wolf +# +# 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. +# +# Author: Benedikt Tutzer +# Modified for pybind11 by: Mohamed Gaber + +import argparse +import copy +from enum import Enum +from io import StringIO +from pathlib import Path +from functools import wraps +from typing import ClassVar, Optional + +__file_dir__ = Path(__file__).absolute().parent + + +def autostring(fn): + @wraps(fn) + def wrapper(*args, **kwargs): + if "stream" not in kwargs: + stream = StringIO() + fn(*args, stream=stream, **kwargs) + return stream.getvalue() + else: + fn(*args, **kwargs) + + return wrapper + + +# Map c++ operator Syntax to Python functions +wrappable_operators = { + "<": "__lt__", + "==": "__eq__", + "!=": "__ne__", + "+": "__add__", + "-": "__sub__", + "*": "__mul__", + "/": "__div__", + "()": "__call__", +} + +# Restrict certain strings from being function names in Python +keyword_aliases = { + "in": "in_", + "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_", +} + +# These can be used without any explicit conversion +autocast_types = { + "void": "void", + "bool": "bool", + "int": "int", + "double": "double", + "size_t": "size_t", + "std::string": "std::string", + "string": "string", + "char_p": "char_p", + "std::source_location": "std::source_location", + "source_location": "source_location", + "State": "RTLIL::State", + # trampoline types + "Pass": "RTLIL::Pass", + "Monitor": "RTLIL::Monitor", +} + +def strip_std_prefix(str_in): + # removesuffix is python 3.9+ + std_namespace = "std::" + if str_in.startswith(std_namespace): + return str_in[len(std_namespace):] + return str_in + + +# Ways to link between Python- and C++ Objects +class link_types(Enum): + global_list = 1 # Identical to pointer, kept for historical reasons + ref_copy = 2 # Copy + pointer = 3 # The Python Object contains a pointer to its C++ + # counterpart + derive = 4 # Identical to ref_copy, kept for historical reasons + + +class attr_types(Enum): + star = "*" + amp = "&" + ampamp = "&&" + default = "" + + +# For source-files +class Source: + name = "" + classes = [] + + def __init__(self, name, classes): + self.name = name + self.classes = classes + + +# Splits a list by the given delimiter, without splitting strings inside +# pointy-brackets (< and >) +def split_list(str_def, delim): + str_def = str_def.strip() + if len(str_def) == 0: + return [] + if str_def.count(delim) == 0: + return [str_def] + if str_def.count("<") == 0: + return str_def.split(delim) + if str_def.find("<") < str_def.find(" "): + closing = find_closing( + str_def[str_def.find("<") + 1 :], "<", ">" + ) + str_def.find("<") + comma = str_def[closing:].find(delim) + if comma == -1: + return [str_def] + comma = closing + comma + else: + comma = str_def.find(delim) + rest = split_list(str_def[comma + 1 :], delim) + ret = [str_def[:comma]] + if rest != None and len(rest) != 0: + ret.extend(rest) + return ret + + +# Represents a Type +class WType: + name = "" + cont = None + attr_type = attr_types.default + + def __init__(self, name="", cont=None, attr_type=attr_types.default): + self.name = name + self.cont = cont + self.attr_type = attr_type + + # Python type-string + def gen_text(self): + text = self.name + if self.name in enum_names: + text = enum_by_name(self.name).namespace + "::" + self.name + if self.cont != None: + return self.cont.gen_identifier() + return text + + # C++ type-string + def gen_text_cpp(self): + postfix = "" + if self.attr_type == attr_types.star: + postfix = " *" + elif self.attr_type == attr_types.amp: + postfix = " &" + elif self.attr_type == attr_types.ampamp: + postfix = " &&" + if self.name in classnames: + return class_by_name(self.name).namespace + "::" + self.name + postfix + if self.name in enum_names: + return enum_by_name(self.name).namespace + "::" + self.name + postfix + if self.name in autocast_types: + return autocast_types[self.name] + postfix + text = self.name + if self.cont != None: + text += "<" + for a in self.cont.args: + text += a.gen_text_cpp() + ", " + text = text[:-2] + text += ">" + return text + + @staticmethod + def from_string(str_def, containing_file, line_number): + str_def = str_def.strip() + if len(str_def) == 0: + return None + str_def = str_def.replace( + "RTLIL::SigSig", "std::pair" + ).replace("SigSig", "std::pair") + t = WType() + t.name = "" + t.cont = None + t.attr_type = attr_types.default + if str_def.find("<") != -1: # and str_def.find("<") < str_def.find(" "): + str_def = str_def.replace("const ", "") + + candidate = WContainer.from_string(str_def, containing_file, line_number) + if candidate == None: + return None + t.name = str_def[: str_def.find("<")] + + if t.name.count("*") + t.name.count("&") > 1: + return None + + if t.name.count("*") == 1 or str_def[0] == "*" or str_def[-1] == "*": + t.attr_type = attr_types.star + t.name = t.name.replace("*", "") + elif t.name.count("&&") == 1: + t.attr_type = attr_types.ampamp + t.name = t.name.replace("&&", "") + elif t.name.count("&") == 1 or str_def[0] == "&" or str_def[-1] == "&": + t.attr_type = attr_types.amp + t.name = t.name.replace("&", "") + + t.cont = candidate + if t.name not in known_containers: + return None + return t + + prefix = "" + + if str.startswith(str_def, "const "): + if "char_p" in str_def: + prefix = "const " + str_def = str_def[6:] + if str.startswith(str_def, "unsigned "): + prefix = "unsigned " + prefix + str_def = str_def[9:] + while str.startswith(str_def, "long "): + prefix = "long " + prefix + str_def = str_def[5:] + while str.startswith(str_def, "short "): + prefix = "short " + prefix + str_def = str_def[6:] + + str_def = str_def.split("::")[-1] + + if str_def.count("*") + str_def.count("&") >= 2: + return None + + if str_def.count("*") == 1: + t.attr_type = attr_types.star + str_def = str_def.replace("*", "") + elif str_def.count("&&") == 1: + t.attr_type = attr_types.ampamp + str_def = str_def.replace("&&", "") + elif str_def.count("&") == 1: + t.attr_type = attr_types.amp + str_def = str_def.replace("&", "") + + if ( + len(str_def) > 0 + and str_def.split("::")[-1] not in autocast_types + and str_def.split("::")[-1] not in classnames + and str_def.split("::")[-1] not in enum_names + ): + return None + + if str_def.count(" ") == 0: + t.name = (prefix + str_def).replace("char_p", "char *") + t.cont = None + return t + return None + + def gen_identifier(self): + if self.cont: + return self.cont.gen_identifier() + return self.name.title() + + def as_wclass(self) -> Optional["WClass"]: + return class_by_name(self.name) + + def __repr__(self): + return f"{self.__class__.__qualname__}(**{repr(self.__dict__)})" + + +# Associate the "Translators" with their c++ type +known_containers = { + "std::set", + "std::vector", + "std::map", + "std::pair", + "pool", + "idict", + "dict", + "RTLIL::ObjRange" +} + +# Represents a container-type +class WContainer: + name = "" + args = [] + + def from_string(str_def, containing_file, line_number): + if str_def == None or len(str_def) < 4: + return None + cont = WContainer() + cont.name = str_def[: str_def.find("<")] + str_def = str_def[str_def.find("<") + 1 : find_closing(str_def, "<", ">")] + cont.args = [] + for arg in split_list(str_def, ","): + candidate = WType.from_string(arg.strip(), containing_file, line_number) + if candidate == None: + return None + if candidate.name == "void": + return None + cont.args.append(candidate) + return cont + + # generate the c++ type string + def gen_type_cpp(self): + tpl_args = [] + for arg in self.args: + postfix = (arg.attr_type == attr_types.star) * " *" + if arg.name in autocast_types: + tpl_args.append(autocast_types[arg.name] + postfix) + elif arg.name in known_containers: + tpl_args.append(arg.cont.gen_type_cpp()) + else: + tpl_args.append(arg.as_wclass().fully_qualified_name() + postfix) + return f'{self.name}<{ ", ".join(tpl_args) }>' + + def gen_identifier(self): + container = strip_std_prefix(self.name).title() + + if container == "Dict": + assert len(self.args) == 2 + return f"{self.args[0].gen_identifier()}To{self.args[1].gen_identifier()}{container}" + + args = [] + for arg in self.args: + arg_name = arg.name.title() + if arg.cont: + arg_name = arg.cont.gen_identifier() + args.append(arg_name) + args.append(container) + result = "".join(args) + if result == "SigspecSigspecPair": + return "SigSig" + return result + + @autostring + def gen_boost_py(self, *, stream): + bind_fn = "py::bind_" + strip_std_prefix(self.name) + tpl_args = [self.gen_type_cpp()] + if bind_fn != "py::bind_vector": + # using custom bind function, can't use ::value so need more + # template arguments + for arg in self.args: + postfix = (arg.attr_type == attr_types.star) * " *" + if arg.name in autocast_types: + tpl_args.append(autocast_types[arg.name] + postfix) + elif arg.name in known_containers: + tpl_args.append(arg.cont.gen_type_cpp()) + else: + tpl_args.append(arg.as_wclass().fully_qualified_name() + postfix) + if bind_fn == "py::bind_set": + bind_fn = "py::bind_pool" + stream.write(f'\t\t{bind_fn}<{",".join(tpl_args)}>(m, "{self.gen_identifier()}");\n') + + def __repr__(self): + return f"{self.__class__.__qualname__}(**{repr(self.__dict__)})" + + +class Attribute: + wtype = None + varname = None + is_const = False + default_value = None + pos = None + pos_counter = 0 + + def __init__(self, wtype, varname, is_const=False, default_value=None): + self.wtype = wtype + self.varname = varname + self.is_const = is_const + self.default_value = None + self.container = None + + @staticmethod + def from_string(str_def, containing_file, line_number): + if len(str_def) < 3: + return None + orig = str_def + arg = Attribute(None, None) + prefix = "" + arg.wtype = None + arg.varname = None + arg.is_const = False + arg.default_value = None + arg.container = None + if str.startswith(str_def, "const "): + arg.is_const = True + str_def = str_def[6:] + if str.startswith(str_def, "unsigned "): + prefix = "unsigned " + str_def = str_def[9:] + while str.startswith(str_def, "long "): + prefix = "long " + prefix + str_def = str_def[5:] + while str.startswith(str_def, "short "): + prefix = "short " + prefix + str_def = str_def[6:] + + if str_def.find("<") != -1 and str_def.find("<") < str_def.find(" "): + closing = ( + find_closing(str_def[str_def.find("<") :], "<", ">") + + str_def.find("<") + + 1 + ) + arg.wtype = WType.from_string( + str_def[:closing].strip(), containing_file, line_number + ) + str_def = str_def[closing + 1 :] + else: + if str_def.count(" ") > 0: + arg.wtype = WType.from_string( + prefix + str_def[: str_def.find(" ")].strip(), + containing_file, + line_number, + ) + str_def = str_def[str_def.find(" ") + 1 :] + else: + arg.wtype = WType.from_string( + prefix + str_def.strip(), containing_file, line_number + ) + str_def = "" + arg.varname = "" + + if arg.wtype == None: + return None + if str_def.count("=") == 0: + arg.varname = str_def.strip() + if arg.varname.find(" ") > 0: + return None + else: + arg.varname = str_def[: str_def.find("=")].strip() + if arg.varname.find(" ") > 0: + return None + str_def = str_def[str_def.find("=") + 1 :].strip() + arg.default_value = str_def[arg.varname.find("=") + 1 :].strip() + if len(arg.varname) == 0: + arg.varname = None + return arg + if arg.varname[0] == "*": + arg.wtype.attr_type = attr_types.star + arg.varname = arg.varname[1:] + elif arg.varname[0] == "&": + if arg.wtype.attr_type != attr_types.default: + return None + if arg.varname[1] == "&": + arg.wtype.attr_type = attr_types.ampamp + arg.varname = arg.varname[2:] + else: + arg.wtype.attr_type = attr_types.amp + arg.varname = arg.varname[1:] + return arg + + # Generates the varname. If the attribute has no name in the header file, + # a name is generated + def gen_varname(self): + if self.varname != None: + return self.varname + if self.wtype.name == "void": + return "" + if self.pos == None: + self.pos = Attribute.pos_counter + Attribute.pos_counter = Attribute.pos_counter + 1 + return "gen_varname_" + str(self.pos) + + # Generates the test for the function headers with c++ types + def gen_listitem_cpp(self, include_varname=True): + postfix = self.gen_varname() * include_varname + prefix = "" + if self.is_const: + prefix = "const " + infix = "" + if self.wtype.attr_type == attr_types.star: + infix = "*" + elif self.wtype.attr_type == attr_types.amp: + infix = "&" + elif self.wtype.attr_type == attr_types.ampamp: + infix = "&&" + if self.wtype.name in known_containers: + return ( + prefix + + self.wtype.cont.gen_type_cpp() + + " " + + infix + + postfix + ) + if self.wtype.name in classnames: + return ( + prefix + + class_by_name(self.wtype.name).namespace + + "::" + + self.wtype.name + + " " + + infix + + postfix + ) + return prefix + self.wtype.name + " " + infix + postfix + + def gen_listitem_pyarg(self): + default = "" + if self.default_value is not None: + default = f" = {self.default_value}" + return f'py::arg("{self.varname}"){default}' + + # Generates the listitem withtout the varname, so the signature can be + # compared + def gen_listitem_hash(self): + prefix = "" + if self.is_const: + prefix = "const " + if self.wtype.name in classnames: + return prefix + self.wtype.name + "* " + if self.wtype.name in known_containers: + return self.wtype.cont.gen_identifier() + return prefix + self.wtype.name + + +class WClass: + name = None + namespace = None + link_type = None + base_class = None + id_ = None + string_id = None + hash_id = None + needs_clone = False + found_funs = [] + found_vars = [] + found_constrs = [] + + def __init__( + self, + name, + link_type, + *, + id_=None, + string_id=None, + hash_id=None, + needs_clone=False, + ): + self.name = name + self.namespace = None + self.base_class = None + self.link_type = link_type + self.id_ = id_ + self.string_id = string_id + self.hash_id = hash_id + self.needs_clone = needs_clone + self.found_funs = [] + self.found_vars = [] + self.found_constrs = [] + + @autostring + def gen_boost_py(self, *, stream): + name_qualified = f"{self.namespace}::{self.name}" + tpl_args = [name_qualified] + if self.link_type in [link_types.pointer, link_types.global_list]: + tpl_args.append(f"std::unique_ptr<{name_qualified}, py::nodelete>") + stream.write(f'\t\tpy::class_<{", ".join(tpl_args)}>(m, "{self.name}")\n') + for con in self.found_constrs: + # HACK: skip move constructors + if "&&" in con.orig_text: + continue + con.gen_boost_py(stream=stream) + for fun in sorted(self.found_funs, key=lambda f: (f.is_operator, f.name)): + fun.gen_boost_py(stream=stream) + if self.string_id: + stream.write( + f'\t\t\t.def("__str__", [](const {name_qualified} &self){{ return self.{self.string_id}; }})\n' + ) + if self.hash_id: + hash_member = f".{self.hash_id}" if self.hash_id != "" else "" + stream.write( + f'\t\t\t.def("__hash__", [](const {name_qualified} &self){{ return run_hash(self{hash_member}); }})\n' + ) + for var in self.found_vars: + var.gen_boost_py(stream=stream) + stream.write("\t\t;\n") + + def fully_qualified_name(self) -> str: + return f"{self.namespace}::{self.name}" + + def __repr__(self): + return f"{self.__class__.__qualname__}({repr(self.__dict__)})" + + +# CONFIGURE HEADER-FILES TO BE PARSED AND CLASSES EXPECTED IN THEM HERE + +sources = [ + Source( + "kernel/celltypes", + [ + WClass("CellType", link_types.ref_copy, hash_id="type", needs_clone=True), + WClass("CellTypes", link_types.ref_copy, needs_clone=True), + ], + ), + Source( + "kernel/consteval", [WClass("ConstEval", link_types.ref_copy, needs_clone=True)] + ), + Source("kernel/log", []), + Source( + "kernel/register", + [ + # WClass("Pass", link_types.derive, needs_clone=True), # Manual mapping because of virtual method + ], + ), + Source( + "kernel/rtlil", + [ + WClass("IdString", link_types.ref_copy, string_id="str()", hash_id="str()"), + WClass("Const", link_types.ref_copy, string_id="as_string()"), + WClass("AttrObject", link_types.ref_copy), + WClass("NamedObject", link_types.ref_copy), + WClass("Selection", link_types.ref_copy), + #WClass("Monitor", link_types.derive), # Moved to tpl for virtual methods + WClass("CaseRule", link_types.ref_copy, needs_clone=True), + WClass("SwitchRule", link_types.ref_copy, needs_clone=True), + WClass("SyncRule", link_types.ref_copy, needs_clone=True), + WClass( + "Process", link_types.pointer, string_id="name.c_str()", hash_id="name" + ), + WClass("SigChunk", link_types.ref_copy), + WClass("SigBit", link_types.ref_copy, hash_id=""), + WClass("SigSpec", link_types.ref_copy, hash_id=""), + WClass( + "Cell", + link_types.global_list, + id_=Attribute(WType("unsigned int"), "hashidx_"), + string_id="name.c_str()", + hash_id="", + ), + WClass( + "Wire", + link_types.global_list, + id_=Attribute(WType("unsigned int"), "hashidx_"), + string_id="name.c_str()", + hash_id="", + ), + WClass( + "Memory", + link_types.global_list, + id_=Attribute(WType("unsigned int"), "hashidx_"), + string_id="name.c_str()", + hash_id="", + ), + WClass( + "Module", + link_types.global_list, + id_=Attribute(WType("unsigned int"), "hashidx_"), + string_id="name.c_str()", + hash_id="", + ), + WClass( + "Design", + link_types.ref_copy, + id_=Attribute(WType("unsigned int"), "hashidx_"), + string_id="hashidx_", + hash_id="", + ), + ], + ), + # Source("kernel/satgen",[ + # ] + # ), + # Source("libs/ezsat/ezsat",[ + # ] + # ), + # Source("libs/ezsat/ezminisat",[ + # ] + # ), + Source( + "kernel/sigtools", [WClass("SigMap", link_types.ref_copy, needs_clone=True)] + ), + Source("kernel/yosys", []), + Source("kernel/cost", []), +] + +blacklist_methods = [ + "YOSYS_NAMESPACE::Pass::run_register", + "YOSYS_NAMESPACE::Module::Pow", + "YOSYS_NAMESPACE::RTLIL::Design::selected_whole_modules", + "YOSYS_NAMESPACE::RTLIL::AttrObject::get_blackbox_attribute" +] + +enum_names = ["State", "SyncType", "ConstFlags"] + +enums = [] # Do not edit +glbls = [] + +unowned_functions = [] + +classnames = [] +for source in sources: + for wclass in source.classes: + classnames.append(wclass.name) + + +def class_by_name(name): + for source in sources: + for wclass in source.classes: + if wclass.name == name: + return wclass + return None + + +def enum_by_name(name): + for e in enums: + if e.name == name: + return e + return None + + +def find_closing(text, open_tok, close_tok): + if text.find(open_tok) == -1 or text.find(close_tok) <= text.find(open_tok): + return text.find(close_tok) + return ( + text.find(close_tok) + + find_closing(text[text.find(close_tok) + 1 :], open_tok, close_tok) + + 1 + ) + + +def unpretty_string(s): + s = s.strip() + while s.find(" ") != -1: + s = s.replace(" ", " ") + while s.find("\t") != -1: + s = s.replace("\t", " ") + s = s.replace(" (", "(") + return s + + +class WEnum: + name = None + namespace = None + values = [] + + def from_string(str_def, namespace, line_number): + str_def = str_def.strip() + if not str.startswith(str_def, "enum "): + return None + if str_def.count(";") != 1: + return None + str_def = str_def[5:] + enum = WEnum() + split = str_def.split(":") + if len(split) != 2: + return None + enum.name = split[0].strip() + if enum.name not in enum_names: + return None + str_def = split[1] + if str_def.count("{") != str_def.count("}") != 1: + return None + if ( + len(str_def) < str_def.find("}") + 2 + or str_def[str_def.find("}") + 1] != ";" + ): + return None + str_def = str_def.split("{")[-1].split("}")[0] + enum.values = [] + for val in str_def.split(","): + enum.values.append(val.strip().split("=")[0].strip()) + enum.namespace = namespace + return enum + + @autostring + def gen_boost_py(self, *, stream): + stream.write( + f'\t\tpy::native_enum<{self.namespace}::{self.name}>(m, "{self.name}", "enum.Enum")\n' + ) + for value in self.values: + stream.write(f'\t\t\t.value("{value}", {self.namespace}::{value})\n') + stream.write("\t\t\t.finalize();\n") + + def __str__(self): + ret = "Enum " + self.namespace + "::" + self.name + "(\n" + for val in self.values: + ret = ret + "\t" + val + "\n" + return ret + ")" + + +class WConstructor: + orig_text = None + args = [] + containing_file = None + member_of = None + duplicate = False + protected = False + + def __init__(self, containing_file, class_): + self.orig_text = "Auto generated default constructor" + self.args = [] + self.containing_file = containing_file + self.member_of = class_ + self.protected = False + + def from_string(str_def, containing_file, class_, line_number, protected=False): + if class_ == None: + return None + if str_def.count("delete;") > 0: + return None + con = WConstructor(containing_file, class_) + con.orig_text = str_def + con.args = [] + con.duplicate = False + con.protected = protected + if str.startswith(str_def, "inline "): + str_def = str_def[7:] + if not str.startswith(str_def, class_.name + "("): + return None + str_def = str_def[len(class_.name) + 1 :] + found = find_closing(str_def, "(", ")") + if found == -1: + return None + str_def = str_def[0:found].strip() + if len(str_def) == 0: + return con + args = split_list(str_def, ",") + for i, arg in enumerate(args): + parsed = Attribute.from_string(arg.strip(), containing_file, line_number) + if parsed == None: + return None + # Only allow std::source_location as defaulted last argument, and + # don't append so it takes default value + if parsed.wtype.name in ["std::source_location", "source_location"]: + if parsed.default_value is None or i != len(args) - 1: + debug( + "std::source_location not defaulted last arg of " + + class_.name + + " is unsupported", + 2, + ) + return None + continue + con.args.append(parsed) + return con + + def gen_decl_hash_py(self): + text = self.member_of.name + "(" + for arg in self.args: + text += arg.gen_listitem_hash() + ", " + if len(self.args) > 0: + text = text[:-2] + text += ");" + return text + + def overload_params(self): + return ", ".join([a.gen_listitem_cpp(include_varname=False) for a in self.args]) + + @autostring + def gen_boost_py(self, *, stream): + if self.duplicate or self.protected: + return + stream.write(f"\t\t\t.def(py::init<{self.overload_params()}>())\n") + + +class WFunction: + orig_text = None + is_static = False + is_inline = False + is_virtual = False + is_const = False + ret_attr_type = attr_types.default + is_operator = False + ret_type = None + name = None + alias = None + args = [] + containing_file = None + member_of = None + duplicate = False + namespace = "" + + def from_string(str_def, containing_file, class_, line_number, namespace): + if str_def.count("delete;") > 0: + return None + func = WFunction() + func.is_static = False + func.is_inline = False + func.is_virtual = False + func.is_const = False + func.ret_attr_type = attr_types.default + func.is_operator = False + func.member_of = None + func.orig_text = str_def + func.args = [] + func.containing_file = containing_file + func.member_of = class_ + func.duplicate = False + func.namespace = namespace + str_def = str_def.replace("operator ", "operator") + + # remove attributes from the start + if str.startswith(str_def, "[[") and "]]" in str_def: + str_def = str_def[str_def.find("]]") + 2 :] + + if str.startswith(str_def, "static "): + func.is_static = True + str_def = str_def[7:] + else: + func.is_static = False + if str.startswith(str_def, "inline "): + func.is_inline = True + str_def = str_def[7:] + else: + func.is_inline = False + if str.startswith(str_def, "virtual "): + func.is_virtual = True + str_def = str_def[8:] + else: + func.is_virtual = False + + if str_def.count(" ") == 0: + return None + + parts = split_list(str_def.strip(), " ") + + prefix = "" + i = 0 + for part in parts: + if part in ["unsigned", "long", "short", "const"]: + prefix += part + " " + i += 1 + else: + break + parts = parts[i:] + + if len(parts) <= 1: + return None + + func.ret_type = WType.from_string( + prefix + parts[0], containing_file, line_number + ) + + if func.ret_type == None: + return None + + str_def = parts[1] + for part in parts[2:]: + str_def = str_def + " " + part + + found = str_def.find("(") + if found == -1 or (str_def.find(" ") != -1 and found > str_def.find(" ")): + return None + func.name = str_def[:found] + str_def = str_def[found:] + if func.name.find("operator") != -1 and str.startswith(str_def, "()("): + func.name += "()" + str_def = str_def[2:] + str_def = str_def[1:] + if func.name.find("operator") != -1: + func.is_operator = True + if func.name.find("*") == 0: + func.name = func.name.replace("*", "") + func.ret_type.attr_type = attr_types.star + if func.name.find("&&") == 0: + func.name = func.name.replace("&&", "") + func.ret_type.attr_type = attr_types.ampamp + if func.name.find("&") == 0: + func.name = func.name.replace("&", "") + func.ret_type.attr_type = attr_types.amp + + found = find_closing(str_def, "(", ")") + if found == -1: + return None + + post_qualifiers = str_def[found + 1 :].lstrip() + if post_qualifiers.startswith("const"): + func.is_const = True + + str_def = str_def[0:found] + if func.name in blacklist_methods: + return None + if func.namespace != None and func.namespace != "": + if (func.namespace + "::" + func.name) in blacklist_methods: + return None + if func.member_of != None: + if ( + func.namespace + "::" + func.member_of.name + "::" + func.name + ) in blacklist_methods: + return None + if ( + func.is_operator + and func.name.replace(" ", "").replace("operator", "").split("::")[-1] + not in wrappable_operators + ): + return None + + testname = func.name + if func.is_operator: + testname = testname[: testname.find("operator")] + if ( + testname.count(")") != 0 + or testname.count("(") != 0 + or testname.count("~") != 0 + or testname.count(";") != 0 + or testname.count(">") != 0 + or testname.count("<") != 0 + or testname.count("throw") != 0 + ): + return None + + func.alias = func.name + if func.name in keyword_aliases: + func.alias = keyword_aliases[func.name] + str_def = str_def[:found].strip() + if len(str_def) == 0: + return func + args = split_list(str_def, ",") + for i, arg in enumerate(args): + if arg.strip() == "...": + continue + parsed = Attribute.from_string(arg.strip(), containing_file, line_number) + if parsed == None: + return None + # Only allow std::source_location as defaulted last argument, and + # don't append so it takes default value + if parsed.wtype.name in ["std::source_location", "source_location"]: + if parsed.default_value is None or i != len(args) - 1: + debug( + "std::source_location not defaulted last arg of " + + func.name + + " is unsupported", + 2, + ) + return None + continue + func.args.append(parsed) + # handle (void) parameter declarations + if len(func.args) == 1 and func.args[0].wtype.name == "void": + func.args = [] + return func + + @property + def mangled_name(self): + mangled_typename = ( + lambda code: code.replace("::", "_") + .replace("<", "_") + .replace(">", "_") + .replace(" ", "") + .replace("*", "") + .replace(",", "") + ) + + return self.name + "".join( + f"__{mangled_typename(arg.wtype.gen_text_cpp())}" for arg in self.args + ) + + def gen_alias(self): + self.alias = self.mangled_name + + def gen_post_qualifiers(self, derived=False): + if ( + self.member_of != None + and self.member_of.link_type == link_types.derive + and self.is_virtual + and derived + ): + # we drop the qualifiers when deriving callbacks to be implemented in Python + return "" + return " const" if self.is_const else "" + + def gen_decl_hash_py(self): + text = self.ret_type.gen_text() + " " + self.alias + "(" + for arg in self.args: + text += arg.gen_listitem_hash() + ", " + if len(self.args) > 0: + text = text[:-2] + text += ");" + return text + + def overload_params(self): + return ", ".join([a.gen_listitem_cpp(False) for a in self.args]) + + def py_args(self): + return ", ".join([a.gen_listitem_pyarg() for a in self.args]) + + def wrapper_params(self): + return ", ".join([a.gen_listitem_cpp() for a in self.args]) + + def wrapper_args(self): + varnames = [] + for a in self.args: + if a.varname == "format": + # HACK: handle format strings (by ignoring the format part) + varnames.extend(['"%s"', "format"]) + else: + varnames.append(a.varname) + return ", ".join(varnames) + + @autostring + def gen_boost_py(self, *, stream): + if self.duplicate: + return + + fully_qualify = False + if self.member_of is not None and ( + (self.member_of.name == "IdString" and self.name == "in") + or (self.member_of.name == "Design" and self.name == "selection") + ): + fully_qualify = True + + # HACK: skip methods with inline-related nonsense + if self.alias in [ + "log_id", + "log_dump_val_worker", + "log_dump_args_worker", + "GetSize", + ]: + return + + prefix = "\t\tm" + ns = self.namespace + if self.member_of: + prefix = "\t\t\t" + ns = self.member_of.fully_qualified_name() + + stream.write(f"{prefix}.def") + if self.member_of and self.is_static: + stream.write("_static") + stream.write("(") + + if self.is_operator: + stream.write(f"py::self {self.name[len('operator'):]} py::self") + else: + stream.write(f'"{self.alias}", ') + # HACK: wrap variadics by only allowing a string + if "..." in self.orig_text: + stream.write( + f"[]({self.wrapper_params()}) {{ {self.namespace}::{self.name}({self.wrapper_args()}); }}" + ) + else: + + # HACK: Some of these needs special handling, i.e., a FULL + # overload disambiguation. Not sure why. Something to do with + # inlines and overloads. + # + # In theory, this disambiguation should work for everything but + # WType doesn't properly retain const return types yet. + if fully_qualify: + stream.write( + f"static_cast < {self.ret_type.gen_text_cpp()} ({ns}::*)({self.overload_params()}) {self.gen_post_qualifiers()} >(" + ) + elif not len(self.args) == 0: + stream.write(f"py::overload_cast<{self.overload_params()}>(") + stream.write(f"&{ns}::{self.name}") + if fully_qualify: + stream.write(")") + elif not len(self.args) == 0: + if self.is_const: + stream.write(f", py::const_") + stream.write(")") + py_args = self.py_args() + if len(py_args): + stream.write(", ") + stream.write(py_args) + stream.write(")\n" if self.member_of is not None else ");\n") + + def __repr__(self): + return f"{self.__class__.__qualname__}({repr(self.__dict__)})" + + +class WMember: + opaque_containers: ClassVar[dict] = dict() + orig_text = None + wtype = attr_types.default + name = None + containing_file = None + member_of = None + namespace = "" + is_const = False + + def from_string(str_def, containing_file, class_, line_number, namespace): + member = WMember() + member.orig_text = str_def + member.wtype = None + member.name = "" + member.containing_file = containing_file + member.member_of = class_ + member.namespace = namespace + member.is_const = False + + if str.startswith(str_def, "const "): + member.is_const = True + str_def = str_def[6:] + + if str_def.count(" ") == 0: + return None + + parts = split_list(str_def.strip(), " ") + + prefix = "" + i = 0 + for part in parts: + if part in ["unsigned", "long", "short"]: + prefix += part + " " + i += 1 + else: + break + parts = parts[i:] + + if len(parts) <= 1: + return None + + member.wtype = WType.from_string( + prefix + parts[0], containing_file, line_number + ) + + if member.wtype == None: + return None + + str_def = parts[1] + for part in parts[2:]: + str_def = str_def + " " + part + + if ( + str_def.find("(") != -1 + or str_def.find(")") != -1 + or str_def.find("{") != -1 + or str_def.find("}") != -1 + ): + return None + + found = str_def.find(";") + if found == -1: + return None + + found_eq = str_def.find("=") + if found_eq != -1: + found = found_eq + + member.name = str_def[:found] + str_def = str_def[found + 1 :] + if member.name.find("*") == 0: + member.name = member.name.replace("*", "") + member.wtype.attr_type = attr_types.star + if member.name.find("&&") == 0: + member.name = member.name.replace("&&", "") + member.wtype.attr_type = attr_types.ampamp + if member.name.find("&") == 0: + member.name = member.name.replace("&", "") + member.wtype.attr_type = attr_types.amp + + if len(str_def.strip()) != 0: + return None + + if len(member.name.split(",")) > 1: + member_list = [] + for name in member.name.split(","): + name = name.strip() + member_list.append(WMember()) + member_list[-1].orig_text = member.orig_text + member_list[-1].wtype = member.wtype + member_list[-1].name = name + member_list[-1].containing_file = member.containing_file + member_list[-1].member_of = member.member_of + member_list[-1].namespace = member.namespace + member_list[-1].is_const = member.is_const + return member_list + + if member.wtype.cont: + WMember.opaque_containers[member.wtype.gen_identifier()] = member.wtype + + return member + + @autostring + def gen_boost_py(self, *, stream): + if False and self.wtype.attr_type == attr_types.default: + property_type = self.wtype.gen_text_cpp() + stream.write(f'\t\t\t.def_property("{self.name}",\n') + stream.write(f'\t\t\t\t[]({self.member_of.fully_qualified_name()} &o) -> {property_type}& {{ return o.{self.name}; }},\n') + stream.write(f'\t\t\t\t[]({self.member_of.fully_qualified_name()} &o, {property_type} const &p) {{ o.{self.name} = p; }},\n') + stream.write(f'\t\t\t\tpy::return_value_policy::reference_internal\n') + stream.write(f'\t\t\t)\n') + else: + meth = "def_readonly" if self.is_const else "def_readwrite" + stream.write( + f'\t\t\t.{meth}("{self.name}", &{self.member_of.fully_qualified_name()}::{self.name})\n' + ) + + def __repr__(self): + return f"{self.__class__.__qualname__}({repr(self.__dict__)})" + + +class WGlobal: + orig_text = None + wtype = attr_types.default + name = None + containing_file = None + namespace = "" + is_const = False + + def from_string(str_def, containing_file, line_number, namespace): + glbl = WGlobal() + glbl.orig_text = str_def + glbl.wtype = None + glbl.name = "" + glbl.containing_file = containing_file + glbl.namespace = namespace + glbl.is_const = False + + if not str.startswith(str_def, "extern"): + return None + str_def = str_def[7:] + + if str.startswith(str_def, "const "): + glbl.is_const = True + str_def = str_def[6:] + + if str_def.count(" ") == 0: + return None + + parts = split_list(str_def.strip(), " ") + + prefix = "" + i = 0 + for part in parts: + if part in ["unsigned", "long", "short"]: + prefix += part + " " + i += 1 + else: + break + parts = parts[i:] + + if len(parts) <= 1: + return None + + glbl.wtype = WType.from_string(prefix + parts[0], containing_file, line_number) + + if glbl.wtype == None: + return None + + str_def = parts[1] + for part in parts[2:]: + str_def = str_def + " " + part + + if ( + str_def.find("(") != -1 + or str_def.find(")") != -1 + or str_def.find("{") != -1 + or str_def.find("}") != -1 + ): + return None + + found = str_def.find(";") + if found == -1: + return None + + found_eq = str_def.find("=") + if found_eq != -1: + found = found_eq + + glbl.name = str_def[:found] + str_def = str_def[found + 1 :] + if glbl.name.find("*") == 0: + glbl.name = glbl.name.replace("*", "") + glbl.wtype.attr_type = attr_types.star + if glbl.name.find("&&") == 0: + glbl.name = glbl.name.replace("&&", "") + glbl.wtype.attr_type = attr_types.ampamp + if glbl.name.find("&") == 0: + glbl.name = glbl.name.replace("&", "") + glbl.wtype.attr_type = attr_types.amp + + if len(str_def.strip()) != 0: + return None + + if len(glbl.name.split(",")) > 1: + glbl_list = [] + for name in glbl.name.split(","): + name = name.strip() + glbl_list.append(WGlobal()) + glbl_list[-1].orig_text = glbl.orig_text + glbl_list[-1].wtype = glbl.wtype + glbl_list[-1].name = name + glbl_list[-1].containing_file = glbl.containing_file + glbl_list[-1].namespace = glbl.namespace + glbl_list[-1].is_const = glbl.is_const + return glbl_list + + return glbl + + @autostring + def gen_boost_py(self, *, stream): + args = [ + f'"{self.name}"', + ] + meth = "def_readonly_static" + if not self.is_const: + meth = "def_readwrite_static" + args.append(f"&{self.namespace}::{self.name}") + stream.write(f'\t\t\t.{meth}({", ".join(args)})\n') + + +def concat_namespace(tuple_list): + if len(tuple_list) == 0: + return "" + ret = "" + for namespace in tuple_list: + ret += "::" + namespace[0] + return ret[2:] + + +def calc_ident(text): + if len(text) == 0 or text[0] != " ": + return 0 + return calc_ident(text[1:]) + 1 + + +def assure_length(text, length, left=False): + if len(text) > length: + return text[:length] + if left: + return text + " " * (length - len(text)) + return " " * (length - len(text)) + text + + +def nesting_delta(s): + return s.count("{") - s.count("}") + + +def parse_header(source): + debug("Parsing " + source.name + ".pyh", 1) + source_file = open(source.name + ".pyh", "r") + + source_text = [] + in_line = source_file.readline() + + namespaces = [] + + while in_line: + if len(in_line) > 1: + source_text.append( + in_line.replace("char *", "char_p ").replace("char* ", "char_p ") + ) + in_line = source_file.readline() + + i = 0 + + namespaces = [] + classes = [] + private_segment = False + + while i < len(source_text): + line = ( + source_text[i] + .replace( + "YOSYS_NAMESPACE_BEGIN", + " namespace YOSYS_NAMESPACE{", + ) + .replace("YOSYS_NAMESPACE_END", " }") + ) + ugly_line = unpretty_string(line) + debug(f"READ:>> {line}", 2) + + # for anonymous unions, ignore union enclosure by skipping start line and replacing end line with new line + if "union {" in line: + j = i + 1 + while j < len(source_text): + union_line = source_text[j] + if "};" in union_line: + source_text[j] = "\n" + break + j += 1 + if j != len(source_text): + i += 1 + continue + + if str.startswith( + ugly_line, "namespace " + ): # and ugly_line.find("std") == -1 and ugly_line.find("__") == -1: + namespace_name = ugly_line[10:].replace("{", "").strip() + namespaces.append((namespace_name, ugly_line.count("{"))) + debug("-----NAMESPACE " + concat_namespace(namespaces) + "-----", 3) + i += 1 + continue + + if len(namespaces) != 0: + namespaces[-1] = ( + namespaces[-1][0], + namespaces[-1][1] + nesting_delta(ugly_line), + ) + if namespaces[-1][1] == 0: + debug("-----END NAMESPACE " + concat_namespace(namespaces) + "-----", 3) + namespaces.pop() + i += 1 + continue + + if ( + str.startswith(ugly_line, "struct ") or str.startswith(ugly_line, "class") + ) and ugly_line.count(";") == 0: + # Opening a record declaration which isn't a forward declaration + struct_name = ugly_line.split(" ")[1].split("::")[-1] + impl_namespaces = ugly_line.split(" ")[1].split("::")[:-1] + complete_namespace = concat_namespace(namespaces) + for namespace in impl_namespaces: + complete_namespace += "::" + namespace + debug("\tFound " + struct_name + " in " + complete_namespace, 2) + + base_class_name = None + if len(ugly_line.split(" : ")) > 1: # class is derived + deriv_str = ugly_line.split(" : ")[1] + if len(deriv_str.split("::")) > 1: # namespace of base class is given + base_class_name = deriv_str.split("::", 1)[1] + else: + base_class_name = deriv_str.split(" ")[0] + debug("\t " + struct_name + " is derived from " + base_class_name, 2) + base_class = class_by_name(base_class_name) + + c = (class_by_name(struct_name), ugly_line.count("{")) # calc_ident(line)) + debug(f"switch to {struct_name} in namespace {namespaces}", 2) + if struct_name in classnames: + c[0].namespace = complete_namespace + c[0].base_class = base_class + classes.append(c) + i += 1 + continue + + if len(classes): + c = (classes[-1][0], classes[-1][1] + nesting_delta(ugly_line)) + classes[-1] = c + if c[1] == 0: + if c[0] == None: + debug("\tExiting unknown class", 3) + else: + debug("\tExiting class " + c[0].name, 3) + classes.pop() + private_segment = False + i += 1 + continue + + class_ = classes[-1] if classes else None + + if class_ != None and ( + line.find("private:") != -1 or line.find("protected:") != -1 + ): + private_segment = True + i += 1 + continue + if class_ != None and line.find("public:") != -1: + private_segment = False + i += 1 + continue + + candidate = None + + if private_segment and class_ != None and class_[0] != None: + candidate = WConstructor.from_string( + ugly_line, source.name, class_[0], i, True + ) + if candidate != None: + debug( + '\t\tFound constructor of class "' + + class_[0].name + + '" in namespace ' + + concat_namespace(namespaces), + 2, + ) + class_[0].found_constrs.append(candidate) + i += 1 + continue + + if not private_segment and (class_ == None or class_[0] != None): + if class_ != None: + candidate = WFunction.from_string( + ugly_line, source.name, class_[0], i, concat_namespace(namespaces) + ) + else: + candidate = WFunction.from_string( + ugly_line, source.name, None, i, concat_namespace(namespaces) + ) + if candidate != None and candidate.name.find("::") == -1: + if class_ == None: + debug( + '\tFound unowned function "' + + candidate.name + + '" in namespace ' + + concat_namespace(namespaces), + 2, + ) + unowned_functions.append(candidate) + else: + debug( + '\t\tFound function "' + + candidate.name + + '" of class "' + + class_[0].name + + '" in namespace ' + + concat_namespace(namespaces), + 2, + ) + class_[0].found_funs.append(candidate) + else: + candidate = WEnum.from_string( + ugly_line, concat_namespace(namespaces), i + ) + if candidate != None: + enums.append(candidate) + debug( + '\tFound enum "' + + candidate.name + + '" in namespace ' + + concat_namespace(namespaces), + 2, + ) + elif class_ != None and class_[1] == 1: + candidate = WConstructor.from_string( + ugly_line, source.name, class_[0], i + ) + if candidate != None: + debug( + '\t\tFound constructor of class "' + + class_[0].name + + '" in namespace ' + + concat_namespace(namespaces), + 2, + ) + class_[0].found_constrs.append(candidate) + else: + candidate = WMember.from_string( + ugly_line, + source.name, + class_[0], + i, + concat_namespace(namespaces), + ) + if candidate != None: + if type(candidate) == list: + for c in candidate: + debug( + '\t\tFound member "' + + c.name + + '" of class "' + + class_[0].name + + '" of type "' + + c.wtype.name + + '"', + 2, + ) + class_[0].found_vars.extend(candidate) + else: + debug( + '\t\tFound member "' + + candidate.name + + '" of class "' + + class_[0].name + + '" of type "' + + candidate.wtype.name + + '"', + 2, + ) + class_[0].found_vars.append(candidate) + if candidate == None and class_ == None: + candidate = WGlobal.from_string( + ugly_line, source.name, i, concat_namespace(namespaces) + ) + if candidate != None: + if type(candidate) == list: + for c in candidate: + glbls.append(c) + debug( + '\tFound global "' + + c.name + + '" in namespace ' + + concat_namespace(namespaces), + 2, + ) + else: + glbls.append(candidate) + debug( + '\tFound global "' + + candidate.name + + '" in namespace ' + + concat_namespace(namespaces), + 2, + ) + + j = i + line = unpretty_string(line) + while ( + candidate == None + and j + 1 < len(source_text) + and line.count(";") <= 1 + and line.count("(") >= line.count(")") + ): + j += 1 + line = line + "\n" + unpretty_string(source_text[j]) + if class_ != None: + candidate = WFunction.from_string( + ugly_line, + source.name, + class_[0], + i, + concat_namespace(namespaces), + ) + else: + candidate = WFunction.from_string( + ugly_line, source.name, None, i, concat_namespace(namespaces) + ) + if candidate != None and candidate.name.find("::") == -1: + if class_ == None: + debug( + '\tFound unowned function "' + + candidate.name + + '" in namespace ' + + concat_namespace(namespaces), + 2, + ) + unowned_functions.append(candidate) + else: + debug( + '\t\tFound function "' + + candidate.name + + '" of class "' + + class_[0].name + + '" in namespace ' + + concat_namespace(namespaces), + 2, + ) + class_[0].found_funs.append(candidate) + continue + candidate = WEnum.from_string(line, concat_namespace(namespaces), i) + if candidate != None: + enums.append(candidate) + debug( + '\tFound enum "' + + candidate.name + + '" in namespace ' + + concat_namespace(namespaces), + 2, + ) + continue + if class_ != None: + candidate = WConstructor.from_string( + line, source.name, class_[0], i + ) + if candidate != None: + debug( + '\t\tFound constructor of class "' + + class_[0].name + + '" in namespace ' + + concat_namespace(namespaces), + 2, + ) + class_[0].found_constrs.append(candidate) + continue + if class_ == None: + candidate = WGlobal.from_string( + line, source.name, i, concat_namespace(namespaces) + ) + if candidate != None: + if type(candidate) == list: + for c in candidate: + glbls.append(c) + debug( + '\tFound global "' + + c.name + + '" in namespace ' + + concat_namespace(namespaces), + 2, + ) + else: + glbls.append(candidate) + debug( + '\tFound global "' + + candidate.name + + '" in namespace ' + + concat_namespace(namespaces), + 2, + ) + continue + if candidate != None: + while i < j: + i += 1 + line = ( + source_text[i] + .replace( + "YOSYS_NAMESPACE_BEGIN", + " namespace YOSYS_NAMESPACE{", + ) + .replace("YOSYS_NAMESPACE_END", " }") + ) + ugly_line = unpretty_string(line) + if len(namespaces) != 0: + namespaces[-1] = ( + namespaces[-1][0], + namespaces[-1][1] + nesting_delta(ugly_line), + ) + if namespaces[-1][1] == 0: + debug( + "-----END NAMESPACE " + + concat_namespace(namespaces) + + "-----", + 3, + ) + namespaces.pop() + if len(classes): + c = (classes[-1][0], classes[-1][1] + nesting_delta(ugly_line)) + classes[-1] = c + if c[1] == 0: + if c[0] == None: + debug("\tExiting unknown class", 3) + else: + debug("\tExiting class " + c[0].name, 3) + classes.pop() + private_segment = False + i += 1 + else: + i += 1 + + +def debug(message, level): + if level <= debug.debug_level: + print(message) + + +def expand_functions(): + global unowned_functions + new_funs = [] + for fun in unowned_functions: + new_funs.append(fun) + unowned_functions = new_funs + for source in sources: + for class_ in source.classes: + new_funs = [] + for fun in class_.found_funs: + new_funs.append(fun) + class_.found_funs = new_funs + + +def inherit_members(): + for source in sources: + for class_ in source.classes: + if class_.base_class: + base_funs = copy.deepcopy(class_.base_class.found_funs) + for fun in base_funs: + fun.member_of = class_ + fun.namespace = class_.namespace + base_vars = copy.deepcopy(class_.base_class.found_vars) + for var in base_vars: + var.member_of = class_ + var.namespace = class_.namespace + class_.found_funs.extend(base_funs) + class_.found_vars.extend(base_vars) + + +def clean_duplicates(): + for source in sources: + for class_ in source.classes: + known_decls = {} + for fun in class_.found_funs: + if fun.gen_decl_hash_py() in known_decls: + debug("Multiple declarations of " + fun.gen_decl_hash_py(), 3) + + other = known_decls[fun.gen_decl_hash_py()] + if fun.mangled_name == other.mangled_name: + fun.duplicate = True + debug('Disabled "' + fun.gen_decl_hash_py() + '"', 3) + continue + + other.gen_alias() + fun.gen_alias() + else: + known_decls[fun.gen_decl_hash_py()] = fun + known_decls = [] + for con in class_.found_constrs: + if con.gen_decl_hash_py() in known_decls: + debug("Multiple declarations of " + con.gen_decl_hash_py(), 3) + con.duplicate = True + else: + known_decls.append(con.gen_decl_hash_py()) + known_decls = [] + for fun in unowned_functions: + if fun.gen_decl_hash_py() in known_decls: + debug("Multiple declarations of " + fun.gen_decl_hash_py(), 3) + fun.duplicate = True + else: + known_decls.append(fun.gen_decl_hash_py()) + + +def gen_wrappers(filename, debug_level_=0): + filename = Path(filename) + + debug.debug_level = debug_level_ + for source in sources: + parse_header(source) + + expand_functions() + inherit_members() + clean_duplicates() + + import shutil + import math + + col = shutil.get_terminal_size((80, 20)).columns + debug("-" * col, 1) + debug( + "-" * math.floor((col - 7) / 2) + "SUMMARY" + "-" * math.ceil((col - 7) / 2), 1 + ) + debug("-" * col, 1) + for source in sources: + for class_ in source.classes: + debug( + "Class " + + assure_length(class_.name, len(max(classnames, key=len)), True) + + " contains " + + assure_length(str(len(class_.found_vars)), 3, False) + + " member variables, " + + assure_length(str(len(class_.found_funs)), 3, False) + + " methods and " + + assure_length(str(len(class_.found_constrs)), 2, False) + + " constructors", + 1, + ) + if len(class_.found_constrs) == 0: + class_.found_constrs.append(WConstructor(source.name, class_)) + debug(str(len(unowned_functions)) + " functions are unowned", 1) + debug(str(len(unowned_functions)) + " global variables", 1) + for enum in enums: + debug( + "Enum " + + assure_length(enum.name, len(max(enum_names, key=len)), True) + + " contains " + + assure_length(str(len(enum.values)), 2, False) + + " values", + 1, + ) + debug("-" * col, 1) + + tpl = __file_dir__ / "wrappers_tpl.cc" + with open(tpl, encoding="utf8") as f, open( + filename, "w", encoding="utf8" + ) as wrapper_file: + do_line_directive = True + for i, line in enumerate(f): + if do_line_directive: + wrapper_file.write(f'#line {i + 1} "{tpl}"\n') + do_line_directive = False + if "" in line: + for source in sources: + wrapper_file.write(f'#include "{source.name}.h"\n') + do_line_directive = True + elif "" in line: + wrapper_file.write("// Opaque Container Declaration\n") + for i, (container_identifier, container) in enumerate(WMember.opaque_containers.items()): + wrapper_file.write(f"using {container_identifier} = {container.gen_text_cpp()};\n") + wrapper_file.write(f'PYBIND11_MAKE_OPAQUE({container_identifier})\n') + wrapper_file.write("\n") + do_line_directive = True + elif "" in line: + do_line_directive = True + elif "" in line: + wrapper_file.write("\t\t// Opaque Container Binding\n") + for container in WMember.opaque_containers.values(): + container.cont.gen_boost_py(stream=wrapper_file) + wrapper_file.write("\n") + + wrapper_file.write("\t\t// Enums\n") + for enum in enums: + enum.gen_boost_py(stream=wrapper_file) + wrapper_file.write("\n") + + wrapper_file.write("\t\t// Classes\n") + for source in sources: + for wclass in source.classes: + wclass.gen_boost_py(stream=wrapper_file) + wrapper_file.write("\n") + + wrapper_file.write("\t\t// Global Functions\n") + for fun in sorted(unowned_functions, key=lambda x: x.alias): + fun.gen_boost_py(stream=wrapper_file) + wrapper_file.write("\n") + + wrapper_file.write("\t\t// Global Variables\n") + wrapper_file.write('\t\tpy::class_(m, "Yosys")\n') + for glbl in sorted(glbls, key=lambda x: (not x.is_const, x.name)): + glbl.gen_boost_py(stream=wrapper_file) + wrapper_file.write("\t\t\t;\n") + do_line_directive = True + else: + wrapper_file.write(line) + + +def print_includes(): + for source in sources: + print(source.name + ".pyh") + + +if __name__ == "__main__": + ap = argparse.ArgumentParser() + ap.add_argument("--print-includes", action="store_true") + ap.add_argument("--debug", default=0, type=int) + ap.add_argument("output", nargs="?") + ns = ap.parse_args() + if ns.print_includes: + print_includes() + exit(0) + gen_wrappers(ns.output, ns.debug) diff --git a/pyosys/hashlib.h b/pyosys/hashlib.h new file mode 100644 index 000000000..e8ace90c8 --- /dev/null +++ b/pyosys/hashlib.h @@ -0,0 +1,275 @@ +// ------------------------------------------------------- +// Written by Mohamed Gaber in 2025 +// Based on kernel/hashlib.h by Claire Xenia Wolf +// ------------------------------------------------------- +// 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 +// ------------------------------------------------------- +// +// 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/library/stdtypes.html +// +#include // optional maps cleanest to methods that accept None in Python + +#include // base +#include // std::optional +#include // easier operator binding + +#include "kernel/hashlib.h" + +namespace pybind11 { + +template +struct is_pointer { static const bool value = false; }; +template +struct is_pointer { static const bool value = true; }; + +bool is_mapping(object obj) { + object mapping = module_::import("collections.abc").attr("Mapping"); + return isinstance(obj, mapping); +} + + +// also used for std::set because the semantics are close enough +template +void bind_pool(module &m, const char *name_cstr) { + std::string {name_cstr}; + + class_(m, name_cstr) + .def(init<>()) + .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))); + }) + // TODO: disjoint, subset, union, intersection, difference, symdif + .def("copy", [](const C &s) { + return new C(s); + }) + .def("update", [](C &s, iterable iterable) { + for (auto item: iterable) { + s.insert(item.cast()); + } + }) + .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("__repr__", [name_cstr](const C &s){ + return std::string("<") + name_cstr + " size=" + std::to_string(s.size()) + ">"; + }); +} + +template +void update_dict(C *target, iterable &iterable_or_mapping) { + if (is_mapping(iterable_or_mapping)) { + for (const auto &key: iterable_or_mapping) { + (*target)[cast(key)] = cast(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(pair[cast(0)])] = cast(pair[cast(1)]); + } + } +} + +template +void bind_dict(module &m, const char *name_cstr) { + std::string {name_cstr}; + + class_(m, name_cstr) + .def(init<>()) + .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 &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 &default_) { + if (default_.has_value()) { + return s.at(k, *default_); + } else { + return s.at(k); + } + }, arg("key"), arg("default") = std::nullopt) + .def("popitem", [name_cstr](args _) { throw std::runtime_error(std::string(name_cstr) + " is not an ordered dictionary"); }) + .def("setdefault", [name_cstr](C &s, const K& k, std::optional &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::value) { + s[k] = nullptr; + return (V)nullptr; + } + // TODO: std::optional? do we care? + 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(&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(result, iterable_or_mapping); + return result; + }) + .def("__ior__", [](C &s, iterable iterable_or_mapping) { + update_dict(&s, iterable_or_mapping); + return s; + }) + .def("__bool__", [](const C &s) { return s.size() != 0; }) + .def("__repr__", [name_cstr](const C &s){ + return std::string("<") + name_cstr + " size=" + std::to_string(s.size()) + ">"; + }); +} + +// 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 +void bind_idict(module &m, const char *name_cstr) { + std::string {name_cstr}; + + auto cls = class_(m, name_cstr) + .def(init<>()) + .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 &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 iterable) { + for (auto &e: iterable) { + s(cast(e)); + } + }) + .def("__or__", [](const C &s, iterable iterable) { + auto result = new C(s); + for (auto &e: iterable) { + (*result)(cast(e)); + } + return result; + }) + .def("__ior__", [](C &s, iterable iterable) { + for (auto &e: iterable) { + s(cast(e)); + } + return s; + }) + .def("__bool__", [](const C &s) { return s.size() != 0; }) + .def("__repr__", [name_cstr](const C &s){ + return std::string("<") + name_cstr + " size=" + std::to_string(s.size()) + ">"; + }); + + for (const char *mutator: {"__setitem__", "__delitem__", "pop", "popitem", "setdefault"}) { + cls.def(mutator, [](args _) { + throw type_error("idicts do not support arbitrary element mutation"); + }); + } +} +}; // namespace pybind11 diff --git a/pyosys/wrappers_tpl.cc b/pyosys/wrappers_tpl.cc new file mode 100644 index 000000000..e921ae17a --- /dev/null +++ b/pyosys/wrappers_tpl.cc @@ -0,0 +1,248 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Claire Xenia Wolf + * + * 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 WITH_PYTHON + +// + +#include +#include +#include "pyosys/hashlib.h" + +USING_YOSYS_NAMESPACE + +// + +namespace YOSYS_PYTHON { + + [[noreturn]] static void log_python_exception_as_error() { + PyErr_Print(); + log_error("Python interpreter encountered an exception.\\n"); + } + + struct YosysStatics{}; + + // + + // 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 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 &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 + ); + } + }; + + /// @brief Use an auxiliary function to adapt the legacy function. + void log_to_stream(py::object object) + { + // TODO + }; + + 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(); + })); + } + + m.def("log_to_stream", &log_to_stream, "pipes yosys logs to a Python stream"); + + // Trampoline Classes + py::class_>(m, "Pass") + .def(py::init([](std::string name, std::string short_help) { + auto created = new YOSYS_PYTHON::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(&Pass::call)) + .def("call", py::overload_cast>(&Pass::call)) + ; + + py::class_(m, "Monitor") + .def(py::init([]() { + return new YOSYS_PYTHON::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::Monitor::notify_connect) + ) + .def("notify_blackout", &RTLIL::Monitor::notify_blackout) + ; + + // + }; +}; + +#endif // WITH_PYTHON diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..caa620528 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,6 @@ +[build-system] +requires = [ + "setuptools>=42", + "pybind11>=3,<4", +] +build-backend = "setuptools.build_meta" diff --git a/setup.py b/setup.py index 4a6ef5af9..59c1c4b33 100644 --- a/setup.py +++ b/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={ diff --git a/tests/pyosys/run_tests.py b/tests/pyosys/run_tests.py new file mode 100644 index 000000000..953462c4f --- /dev/null +++ b/tests/pyosys/run_tests.py @@ -0,0 +1,39 @@ +from pathlib import Path +import shutil +import subprocess +import sys + +__file_dir__ = Path(__file__).absolute().parent + +if len(sys.argv) != 2: + print(f"Usage: {sys.argv[0]} {sys.argv[1]}") + exit(64) + +binary = [] +if sys.argv[1] in ["yosys"]: + binary = [__file_dir__.parents[1] / "yosys", "-Qy"] +else: + binary = [sys.argv[1]] + +tests = __file_dir__.glob("test_*.py") +errors = False +log_dir = __file_dir__ / "logs" +try: + shutil.rmtree(log_dir) +except FileNotFoundError: + pass +for test in tests: + print(f"* {test.name} ", end="") + log_dir.mkdir(parents=True, exist_ok=True) + log = log_dir / (test.stem + ".log") + result = subprocess.run([ + *binary, + test + ], stdout=open(log, "w"), stderr=subprocess.STDOUT) + if result.returncode == 0: + print("OK!") + else: + print(f"FAILED: {log}") + errors = True +if errors: + exit(1) diff --git a/tests/pyosys/spm.cut.v.gz b/tests/pyosys/spm.cut.v.gz new file mode 100644 index 0000000000000000000000000000000000000000..44791a035e534f1fc9ebdfc4c47bb0849bff1da8 GIT binary patch literal 5294 zcmbu92{hFGzsJSc8Oo9ed0H^WWGhj&glt)|W@p5MFvvEHC9*HcRtY1?I)fq2DBC2G zr4$+K7)ugkLNb=I+?k%|-2Z*feeOB;{_i=T^E%(p=lgztKflj+e!uxWorB}r?`_^p zEWv^P@;5_6RuqPI{(iXTU_Bc z8gINxO*nZ-wA}JGB!E@M%c6%cUcP1xW+!Y%10Q!BbblwozQfv`HKv_b> z#!S6lubRa)M^9<)!J%QJ5B&n_rWQ6cO+&Kd8y-?8ramsF_YZz@if%93?sF;VA101G zOmF!;W~aZ{$m?rHc*pKua_nIfZDf_$LsW>_pM5h*BbRhctwT{DS_Xo-Sf5JuD+i}$o z?LpMc=K)30^DAPD%|RFSa{o%aNdIl$lO#jaA6M;bx1eS=2INJTt%xl&w;jC9lKzJs zMoG9a;M6~%M{ZkJS(hs3LDFHMb7it8b{B)#+3LWNU}1~H>3@8#&xHGT`+{5$>uM>Z zk}l8TC%Vbj9V*NvN4GQZUI48#Yrn+}azyFRoM)8KcrR8h$eMlJAXk*_%yZ@vp+8HY zwM%h>oKa>o!OSHB+eP?AfYz@Pca+P_26M^r?N))4*(285JTX4eqk>)hQLHAEl*L11gR^Hg=OOtuEgN zlkyFK6%X8v+Br>rQ}q0pcf?9xiPaC}p?+l22ucU%xEVgoa*PfgtN^B1kb(OYfFCH4 zf&Yz+$Ir_{on_JGAMjzeV|3_a&tr;(7|5p36^RV|n=*+w%F%H;ET9Mg*1&^- zM9R@IiEIXhum)}jJqE1D{(nNwG30#;9fDYp8$yQx%Q55~3g(!Hqw#V8OGwL`i=oEhUV`=s>Tm0_Okj$)^O~qJaAQ(9q3=*;Z#I)(TY;1 z{cJfLvpG&(jcGZU96^yN)@8AKqldfm2Yr*`nW+2LL|J0Qi6Nhsr^DDv zMuZtwQqs&Ok-y?pF&3ickN6e;Cj&7*`4j-cq*k&TsYXc(nW+8NL|$Tq*pScY@Eno{ zSUr=*K~=_p;&i#1Pv6npC-;)>@7AWcn$seORG&pbI!A}wkSn~^(`nx}mFqzfx@ox6 zaLqPyFY*3_PrQojK6TBmHM03twF}X<{(Jh3EZ2v}ug$M7ZFXxMSjSSWP$L)Lw{?Nq zhV7j5GT&C}m#r^-JUh;}Duy0p_Zxgsy7-)S!H?FjhTZSZpv+c!%~xy;jxO@uSiI~~ zQsQbC91C;Z64~7#_U_d^k5vLm4P;f)por77)PRRE8$T}RiG9WOlS&2IDO=5yi65A*N&K1|%lwK=mL0y|x! z%mG<3eyw4BT<`5sd@^GJ8bK-bsB-9C{pQo0%H z{l?q-JvZ3H`>yB&Q)&4b9bXrjMmb+s+UIjk*c0}mndpdu&d%UWLsRh*g4@86nRWcG z8pfLa0U7^x9sg9iIwZ414PDKJ zDyYFcMs^*Cnw~>jaHEvYp_d+E9{Z3*$$1x4ROW9*`d+_>QYxHtVBTp;y9va2r7H?WY>DtN0?lRO9$=lN9>tN$N02&Nv7FyK~WBOmDt@p zG{@MV$RkavSgtW_t^nb*N~27Ou2UA5@6_W4V>^nm69RJm=xCGNO{F?s@n9x>+XN{l ztbDNvnx(J?KY{F8#5>3oqebo2UmrtQds7HQOQGrP31PRA2=}JE&z8QJyj6MZU8Rk9 zFo(WyVl>C{T<>GqCIX?9k{Wf?;Vd#o)PcRU_#PL9@S|78rorc7ci0#Go4-%^Z|1&k zY>vALeEqHl!*|75+uk)<<#Q>em+q2zlHnlY$gHVVb1uyBnheUL*1mTl+c8tB3FgS@ zspKnm@hgAES)OYgPR=^^b-H5YkJJRMWCE0P6T4Wxdk2uMDeFTda2YRM9uGV0<=raD zn6$m3RMP;Q4?Q3T(t4X;#orF4^^Q9YN$%+yMG>;`*w|z5hBd?}5$42Tf_iM;n}e!Y+k%Bb<>ZV(I5 z+{s)J(0g&@Ch}mNkXw%e-M}H8h>^GFYe9S?xi%;edmAV`kDvDbJt!-CAPDb`tKXZ?fKn5_zQT+Z6XeP*271>EoFze3%_Stgaow{L*b0&`c(pMAx9h2U`2UD^6rd6H1A{#%ma79(}g*wFBT%rBo z3|tXMpEQbYc#x}UoDvb-Z?t66BqjiDo$nG;HybPPR+a9Azey!`f)!3gWR@gl>wGJ<=`mXW`jsv@oKbvl+J60^V z>VxRl6eU2OAa&8t1;ZePYw2Tb8JC#m_kVe|=pNNi^c|h?FP|0MIccT_(MQEpw+izz z`~$+CuE$tSjRkZv)^qeVnj2I@Gj-Z3gtw9e8v-rE)s>WsRxBoqhrV_iO$rQ~*-Hdm zD$mT%Z3I^oKi8NJHV$3t3LQ)(BL%XyiY(__)Hvi|;cBv8xK-gB-=U^7=?6W9WKV4!YVg^30J@rG>X;SLx}K*3i_p0O zqyH@`PDM1j<1UUCgX+}HgPJ!y@`-abCk?)76x;-i!=~lx{DlJo(o^3QgIa=c&5ic0 zZ4hZ-I1)W3Cqe#Th5pk+-?0hr)|*-j8K@I!iRfsy_#SCOs{;X5OIF_U&a(v|MCqS0 zPk0G>Oi6S?wJK%9ob)xKhTV5IYPO<>pTm=`(-CGY$!{5m}QHjzN(Y`xg`a+1}4%uub; z@4I(EjUf?Hs%vPNq_nftU0j6F3j? zd_ju}dQ!9*S#H8e3R_){kela>Up8pMU+Q6}{($KfJF(#oES_9C9L!w~k0beZ=uf3| zoj&FbS1^>;c4tcc!Ey+d1ZIEJ=D33aJk_vv7fEIpzs{#{F{jI753qt*2=@{T0u=FW z8^hTw4_tisx~_QSl}z)UIi=0KwaI;-iEeCVVP^sEg}!B$WWEm&jNr>jX^5i)p3nKQ z$jzNIcbtdh1-hr7!=H*&S%A?rbC_O77)+;_t}okCSUFhkb7)U1NddcswuA=_#SI^` z7!4yQlrsR`HiK^Lva8w)q-;E}Tqrz1UR&Gogg1)XS_GApsSBKMWI7VGd9to=*d!_F zN%P!6^KU8ajKYM~i=^x)&CB_Bex<82fTGiNYWA8bD1)GAK1U0u&5wYO1g42yfNMFs|Nq$>qs15S!g$$NZp$@9^ojaIg z94PA9W~gKpdRucL9*~!R-JWET1Gl!_bN9c(Jk)oxb|QE-DO)BHr3K=d=(B5X_cFWg zxbB3{%A@$S)EJ2KCdoP*x=)@ ztns2uIZEE|C_u%XvJ3R&PU~DYJf;VNhNzr*`KqHuL$xLN#?RK6En03eiuUHP1rBfC zpM`C0hi(n3Z}43<>mGYEQmX>LdzU035wy14Z+;PeadvjW(n_*_3xaTTIrNQS(}PZ2 z8=m_VK*MRRn!>viP$tR}()Oi=${}V-{^dR=itWOwY_;wm^Vl5u4$s!6y_ZA`>nmv* z4MXfUh&|IY32YB@?SX7!SwJ*epVe2ZrCjku`O*b+!`sJ$`Cy*go|yGV-F>pYcJ@l9 zLqA1rh3$-#>^!Z=(AIpLA&l&}a)eJLFN70q3^TQXy^wHmvgZ}GmBL9wOzWY%%%~hk zt76AYgJN6Twj`V0TA!SXQA}iG zYB

?mDysFXSuX;4F^O7lLtzQ3eatMa1o@(T$cupie>-ZOUy)BOK^QjcuA_hst*+>b zavH)@KO^WX2#(ORk>EXRF~N1!SmKi;qevD2M+nwvh}t2WM!+%=f=?A&2^=*+Y*JY| z(dHMf@A_WM*WjWng$|f2a|}vd9A4iIEZYUomXT*SjvCH1AB^ECu)Iu iM^_(Fv>?ZgTU5hGU03U44{mSFt_EJpjj<_UV)_p!;ECJ- literal 0 HcmV?d00001 diff --git a/tests/pyosys/test_data_read.py b/tests/pyosys/test_data_read.py new file mode 100644 index 000000000..08b6695c2 --- /dev/null +++ b/tests/pyosys/test_data_read.py @@ -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!") diff --git a/tests/pyosys/test_dict.py b/tests/pyosys/test_dict.py new file mode 100644 index 000000000..81e7b5516 --- /dev/null +++ b/tests/pyosys/test_dict.py @@ -0,0 +1,13 @@ +from pyosys import libyosys as ys + +my_dict = ys.StringToStringDict() +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"} diff --git a/tests/pyosys/test_idict.py b/tests/pyosys/test_idict.py new file mode 100644 index 000000000..fd04f4b8c --- /dev/null +++ b/tests/pyosys/test_idict.py @@ -0,0 +1,31 @@ +from pyosys import libyosys as ys + +my_idict = ys.IdstringIdict() +print(my_idict(ys.IdString("\\hello"))) +print(my_idict(ys.IdString("\\world"))) +print(my_idict.get(ys.IdString("\\world"))) +try: + print(my_idict.get(ys.IdString("\\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(ys.IdString("\\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 |= (ys.IdString(e) for e in ("\\the", "\\world")) # 1 new element +assert len(my_copy) == current_copy_len + 1, "or operator returned unexpected result" diff --git a/tests/pyosys/test_import.py b/tests/pyosys/test_import.py new file mode 100644 index 000000000..48e911033 --- /dev/null +++ b/tests/pyosys/test_import.py @@ -0,0 +1,3 @@ +from pyosys import libyosys as ys + +ys.log("Hello, world!") diff --git a/tests/pyosys/test_monitor.py b/tests/pyosys/test_monitor.py new file mode 100644 index 000000000..2eefdad60 --- /dev/null +++ b/tests/pyosys/test_monitor.py @@ -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"] diff --git a/tests/pyosys/test_pass.py b/tests/pyosys/test_pass.py new file mode 100644 index 000000000..d8174d4fc --- /dev/null +++ b/tests/pyosys/test_pass.py @@ -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) diff --git a/tests/pyosys/test_script.py b/tests/pyosys/test_script.py new file mode 100644 index 000000000..7c3ec96ef --- /dev/null +++ b/tests/pyosys/test_script.py @@ -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)