Skip to content

Commit d0dfbcc

Browse files
committed
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
1 parent d4c4b21 commit d0dfbcc

27 files changed

+2876
-2641
lines changed

.github/workflows/wheels.yml

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,6 @@ jobs:
5555
submodules: true
5656
persist-credentials: false
5757
- uses: actions/setup-python@v5
58-
- name: Get Boost Source
59-
shell: bash
60-
run: |
61-
mkdir -p boost
62-
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
6358
- name: Get FFI
6459
shell: bash
6560
run: |
@@ -103,21 +98,16 @@ jobs:
10398
CIBW_BEFORE_ALL: bash ./.github/workflows/wheels/cibw_before_all.sh
10499
CIBW_ENVIRONMENT: >
105100
OPTFLAGS=-O3
106-
CXXFLAGS=-I./boost/pfx/include
107-
LINKFLAGS=-L./boost/pfx/lib
108101
PKG_CONFIG_PATH=./ffi/pfx/lib/pkgconfig
109-
makeFlags='BOOST_PYTHON_LIB=./boost/pfx/lib/libboost_python*.a'
110102
PATH="$PWD/bison/src:$PATH"
111103
CIBW_ENVIRONMENT_MACOS: >
112104
OPTFLAGS=-O3
113-
CXXFLAGS=-I./boost/pfx/include
114-
LINKFLAGS=-L./boost/pfx/lib
115105
PKG_CONFIG_PATH=./ffi/pfx/lib/pkgconfig
116106
MACOSX_DEPLOYMENT_TARGET=11
117-
makeFlags='BOOST_PYTHON_LIB=./boost/pfx/lib/libboost_python*.a CONFIG=clang'
107+
makeFlags='CONFIG=clang'
118108
PATH="$PWD/bison/src:$PATH"
119109
CIBW_BEFORE_BUILD: bash ./.github/workflows/wheels/cibw_before_build.sh
120-
CIBW_TEST_COMMAND: python3 {project}/tests/arch/ecp5/add_sub.py
110+
CIBW_TEST_COMMAND: python3 {project}/tests/pyosys/run_tests.py python3
121111
- uses: actions/upload-artifact@v4
122112
with:
123113
name: python-wheels-${{ matrix.os.runner }}

.github/workflows/wheels/cibw_before_build.sh

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,3 @@ if [ "$(uname)" != "Linux" ]; then
1111
fi
1212
python3 --version
1313
python3-config --includes
14-
15-
# Build boost
16-
cd ./boost
17-
## Delete the artefacts from previous builds (if any)
18-
rm -rf ./pfx
19-
## Bootstrap bjam
20-
./bootstrap.sh --prefix=./pfx
21-
## Build Boost against current version of Python, only for
22-
## static linkage (Boost is statically linked because system boost packages
23-
## wildly vary in versions, including the libboost_python3 version)
24-
./b2\
25-
-j$(getconf _NPROCESSORS_ONLN 2>/dev/null || sysctl -n hw.ncpu)\
26-
--prefix=./pfx\
27-
--with-filesystem\
28-
--with-system\
29-
--with-python\
30-
cxxflags="$(python3-config --includes) -std=c++17 -fPIC"\
31-
cflags="$(python3-config --includes) -fPIC"\
32-
link=static\
33-
variant=release\
34-
install

CODEOWNERS

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ techlibs/gowin/ @pepijndevos
3838
techlibs/gatemate/ @pu-cc
3939

4040
# pyosys
41-
misc/*.py @btut
41+
pyosys/* @donn
42+
setup.py @donn
4243

4344
backends/firrtl @ucbjrl @azidar
4445

Makefile

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -128,10 +128,6 @@ LINKFLAGS += -rdynamic
128128
ifneq ($(shell :; command -v brew),)
129129
BREW_PREFIX := $(shell brew --prefix)/opt
130130
$(info $$BREW_PREFIX is [${BREW_PREFIX}])
131-
ifeq ($(ENABLE_PYOSYS),1)
132-
CXXFLAGS += -I$(BREW_PREFIX)/boost/include
133-
LINKFLAGS += -L$(BREW_PREFIX)/boost/lib -L$(BREW_PREFIX)/boost-python3/lib
134-
endif
135131
CXXFLAGS += -I$(BREW_PREFIX)/readline/include -I$(BREW_PREFIX)/flex/include
136132
LINKFLAGS += -L$(BREW_PREFIX)/readline/lib -L$(BREW_PREFIX)/flex/lib
137133
PKG_CONFIG_PATH := $(BREW_PREFIX)/libffi/lib/pkgconfig:$(PKG_CONFIG_PATH)
@@ -347,26 +343,14 @@ ifeq ($(ENABLE_PYOSYS),1)
347343
LINKFLAGS += $(filter-out -l%,$(shell $(PYTHON_CONFIG) --ldflags))
348344
LIBS += $(shell $(PYTHON_CONFIG) --libs)
349345
EXE_LIBS += $(filter-out $(LIBS),$(shell $(PYTHON_CONFIG_FOR_EXE) --libs))
346+
PYBIND11_INCLUDE ?= $(shell $(PYTHON_EXECUTABLE) -m pybind11 --includes)
347+
CXXFLAGS += -I$(PYBIND11_INCLUDE) -DWITH_PYTHON
350348
CXXFLAGS += $(shell $(PYTHON_CONFIG) --includes) -DWITH_PYTHON
351349

352-
# Detect name of boost_python library. Some distros use boost_python-py<version>, other boost_python<version>, some only use the major version number, some a concatenation of major and minor version numbers
353-
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)")
354-
BOOST_PYTHON_LIB ?= $(shell \
355-
$(call CHECK_BOOST_PYTHON,boost_python-py$(subst .,,$(PYTHON_VERSION))) || \
356-
$(call CHECK_BOOST_PYTHON,boost_python-py$(PYTHON_MAJOR_VERSION)) || \
357-
$(call CHECK_BOOST_PYTHON,boost_python$(subst .,,$(PYTHON_VERSION))) || \
358-
$(call CHECK_BOOST_PYTHON,boost_python$(PYTHON_MAJOR_VERSION)) \
359-
)
360-
361-
ifeq ($(BOOST_PYTHON_LIB),)
362-
$(error BOOST_PYTHON_LIB could not be detected. Please define manually)
363-
endif
364-
365-
LIBS += $(BOOST_PYTHON_LIB) -lboost_system -lboost_filesystem
366-
PY_WRAPPER_FILE = kernel/python_wrappers
350+
PY_WRAPPER_FILE = pyosys/wrappers
367351
OBJS += $(PY_WRAPPER_FILE).o
368-
PY_GEN_SCRIPT= py_wrap_generator
369-
PY_WRAP_INCLUDES := $(shell $(PYTHON_EXECUTABLE) -c "from misc import $(PY_GEN_SCRIPT); $(PY_GEN_SCRIPT).print_includes()")
352+
PY_GEN_SCRIPT = pyosys/generator.py
353+
PY_WRAP_INCLUDES := $(shell $(PYTHON_EXECUTABLE) $(PY_GEN_SCRIPT) --print-includes)
370354
endif # ENABLE_PYOSYS
371355

372356
ifeq ($(ENABLE_READLINE),1)
@@ -759,9 +743,9 @@ endif
759743
$(P) cat $< | grep -E -v "#[ ]*(include|error)" | $(CXX) $(CXXFLAGS) -x c++ -o $@ -E -P -
760744

761745
ifeq ($(ENABLE_PYOSYS),1)
762-
$(PY_WRAPPER_FILE).cc: misc/$(PY_GEN_SCRIPT).py $(PY_WRAP_INCLUDES)
746+
$(PY_WRAPPER_FILE).cc: $(PY_GEN_SCRIPT) pyosys/wrappers_tpl.cc $(PY_WRAP_INCLUDES) pyosys/hashlib.h
763747
$(Q) mkdir -p $(dir $@)
764-
$(P) $(PYTHON_EXECUTABLE) -c "from misc import $(PY_GEN_SCRIPT); $(PY_GEN_SCRIPT).gen_wrappers(\"$(PY_WRAPPER_FILE).cc\")"
748+
$(P) $(PYTHON_EXECUTABLE) $(PY_GEN_SCRIPT) $(PY_WRAPPER_FILE).cc
765749
endif
766750

767751
%.o: %.cpp

docs/source/yosys_internals/hashing.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ The main characteristics are:
3636
all compilers, standard libraries and architectures.
3737

3838
In addition to ``dict<K, T>`` and ``pool<T>`` there is also an ``idict<K>`` that
39-
creates a bijective map from ``K`` to the integers. For example:
39+
creates a bijective map from ``K`` to incrementing integers. For example:
4040

4141
::
4242

examples/python-api/pass.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,25 @@
11
#!/usr/bin/python3
22

3-
import libyosys as ys
3+
from pyosys import libyosys as ys
4+
5+
from pathlib import Path
46

57
import matplotlib.pyplot as plt
6-
import numpy as np
8+
9+
__file_dir__ = Path(__file__).absolute().parent
710

811
class CellStatsPass(ys.Pass):
912

1013
def __init__(self):
1114
super().__init__("cell_stats", "Shows cell stats as plot")
1215

13-
def py_help(self):
16+
def help(self):
1417
ys.log("This pass uses the matplotlib library to display cell stats\n")
1518

16-
def py_execute(self, args, design):
19+
def execute(self, args, design):
1720
ys.log_header(design, "Plotting cell stats\n")
1821
cell_stats = {}
19-
for module in design.selected_whole_modules_warn():
22+
for module in design.all_selected_whole_modules():
2023
for cell in module.selected_cells():
2124
if cell.type.str() in cell_stats:
2225
cell_stats[cell.type.str()] += 1
@@ -29,4 +32,11 @@ def py_execute(self, args, design):
2932
def py_clear_flags(self):
3033
ys.log("Clear Flags - CellStatsPass\n")
3134

32-
p = CellStatsPass()
35+
p = CellStatsPass() # register
36+
37+
if __name__ == "__main__":
38+
design = ys.Design()
39+
ys.run_pass(f"read_verilog {__file_dir__.parents[1] / 'tests' / 'simple' / 'fiedler-cooley.v'}", design)
40+
ys.run_pass("prep", design)
41+
ys.run_pass("opt -full", design)
42+
ys.run_pass("cell_stats", design)

kernel/driver.cc

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,9 @@ int main(int argc, char **argv)
9292
yosys_banner();
9393
yosys_setup();
9494
#ifdef WITH_PYTHON
95-
PyRun_SimpleString(("sys.path.append(\""+proc_self_dirname()+"\")").c_str());
96-
PyRun_SimpleString(("sys.path.append(\""+proc_share_dirname()+"plugins\")").c_str());
95+
py::object sys = py::module_::import("sys");
96+
sys.attr("path").attr("append")(proc_self_dirname());
97+
sys.attr("path").attr("append")(proc_share_dirname());
9798
#endif
9899

99100
if (argc == 2)
@@ -516,8 +517,9 @@ int main(int argc, char **argv)
516517

517518
yosys_setup();
518519
#ifdef WITH_PYTHON
519-
PyRun_SimpleString(("sys.path.append(\""+proc_self_dirname()+"\")").c_str());
520-
PyRun_SimpleString(("sys.path.append(\""+proc_share_dirname()+"plugins\")").c_str());
520+
py::object sys = py::module_::import("sys");
521+
sys.attr("path").attr("append")(proc_self_dirname());
522+
sys.attr("path").attr("append")(proc_share_dirname());
521523
#endif
522524
log_error_atexit = yosys_atexit;
523525

@@ -567,21 +569,18 @@ int main(int argc, char **argv)
567569
#endif
568570
} else if (scriptfile_python) {
569571
#ifdef WITH_PYTHON
570-
PyObject *sys = PyImport_ImportModule("sys");
572+
py::list new_argv;
571573
int py_argc = special_args.size() + 1;
572-
PyObject *new_argv = PyList_New(py_argc);
573-
PyList_SetItem(new_argv, 0, PyUnicode_FromString(scriptfile.c_str()));
574+
new_argv.append(scriptfile);
574575
for (int i = 1; i < py_argc; ++i)
575-
PyList_SetItem(new_argv, i, PyUnicode_FromString(special_args[i - 1].c_str()));
576+
new_argv.append(special_args[i - 1]);
576577

577-
PyObject *old_argv = PyObject_GetAttrString(sys, "argv");
578-
PyObject_SetAttrString(sys, "argv", new_argv);
579-
Py_DECREF(old_argv);
578+
py::setattr(sys, "argv", new_argv);
580579

581-
PyObject *py_path = PyUnicode_FromString(scriptfile.c_str());
582-
PyObject_SetAttrString(sys, "_yosys_script_path", py_path);
583-
Py_DECREF(py_path);
584-
PyRun_SimpleString("import os, sys; sys.path.insert(0, os.path.dirname(os.path.abspath(sys._yosys_script_path)))");
580+
py::object Path = py::module_::import("pathlib").attr("Path");
581+
py::object scriptfile_python_path = Path(scriptfile).attr("parent");
582+
583+
sys.attr("path").attr("insert")(0, py::str(scriptfile_python_path));
585584

586585
FILE *scriptfp = fopen(scriptfile.c_str(), "r");
587586
if (scriptfp == nullptr) {

kernel/yosys.cc

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,8 @@
6464
#endif
6565

6666
#ifdef WITH_PYTHON
67-
#if PY_MAJOR_VERSION >= 3
68-
# define INIT_MODULE PyInit_libyosys
69-
extern "C" PyObject* INIT_MODULE();
70-
#else
71-
# define INIT_MODULE initlibyosys
72-
extern "C" void INIT_MODULE();
73-
#endif
67+
extern "C" PyObject* PyInit_libyosys();
68+
extern "C" PyObject* PyInit_pyosys();
7469
#include <signal.h>
7570
#endif
7671

@@ -189,6 +184,17 @@ int run_command(const std::string &command, std::function<void(const std::string
189184
bool already_setup = false;
190185
bool already_shutdown = false;
191186

187+
#ifdef WITH_PYTHON
188+
// Include pyosys as a module so 'from pyosys import libyosys' also works
189+
// in interpreter mode.
190+
//
191+
// This should not affect using wheels as the dylib has to actually be called
192+
// pyosys.so for this module to be interacted with at all.
193+
PYBIND11_MODULE(pyosys, m) {
194+
m.add_object("libyosys", m.import("libyosys"));
195+
}
196+
#endif
197+
192198
void yosys_setup()
193199
{
194200
if(already_setup)
@@ -197,11 +203,12 @@ void yosys_setup()
197203
already_shutdown = false;
198204

199205
#ifdef WITH_PYTHON
200-
// With Python 3.12, calling PyImport_AppendInittab on an already
206+
// Starting Python 3.12, calling PyImport_AppendInittab on an already
201207
// initialized platform fails (such as when libyosys is imported
202208
// from a Python interpreter)
203209
if (!Py_IsInitialized()) {
204-
PyImport_AppendInittab((char*)"libyosys", INIT_MODULE);
210+
PyImport_AppendInittab((char*)"libyosys", PyInit_libyosys);
211+
PyImport_AppendInittab((char*)"pyosys", PyInit_pyosys);
205212
Py_Initialize();
206213
PyRun_SimpleString("import sys");
207214
signal(SIGINT, SIG_DFL);

kernel/yosys_common.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@
5555

5656
#ifdef WITH_PYTHON
5757
#include <Python.h>
58+
#include <pybind11/pybind11.h>
59+
60+
namespace py = pybind11;
5861
#endif
5962

6063
#ifndef _YOSYS_

0 commit comments

Comments
 (0)