I think Yosys is one most important open sources projects ever created, at least for hardware development. It enabled so much research and innovation in FPGA and ASIC area. It did what gcc did for software development.
Building and installation Link to heading
These are the steps to build yosys with pyosys(python bindings). It’s not enabled by default. So you have to pass ENABLE_PYOSYS
. Also, I am using virtualenv as I don’t want to install anything with root.
source .venv/bin/activate
make config-gcc
make ENABLE_PYOSYS=1 -j4
make install PREFIX=`pwd`/local ENABLE_PYOSYS=1
export PATH=`pwd`/local/bin:$PATH
Hello world Link to heading
pyosys provides API to run yosys passes
and provide design information using ys.Design()
object.
from pyosys import libyosys as ys
design = ys.Design()
rtl_sources = ["Files here"]
ys.run_pass(f"tee -q -o yosys.log read_verilog -mem2reg -noopt {include_string} {" ".join(rtl_sources)}", design)
ys.run_pass("tee -q -a yosys.log hierarchy", design)
Then you can use all the cool stuff like design iterations and passes. Starting with list of modules compiled and instantiated cell(basically other modules)
for module in self.design.selected_whole_modules_warn():
print(f"module.name.str()")
for cell in module.selected_cells():
print(f"\tcell: {cell.type.str()}")
Deepdive Link to heading
Now that we have hello world is out the way, Let’s dig deeper into the python binding. On core code side, There are several places where specific interfaces are added to be called from python modules like
#ifdef WITH_PYTHON
static std::map<unsigned int, RTLIL::Design*> *get_all_designs(void);
#endif
and definition
#ifdef WITH_PYTHON
static std::map<unsigned int, RTLIL::Design*> all_designs;
std::map<unsigned int, RTLIL::Design*> *RTLIL::Design::get_all_designs(void)
{
return &all_designs;
}
#endif
There are other API that are available in C++ (and python) like selected_whole_modules_warn
struct RTLIL::Design
{
unsigned int hashidx_;
unsigned int hash() const { return hashidx_; }
....
....
std::vector<RTLIL::Module*> selected_modules() const;
std::vector<RTLIL::Module*> selected_whole_modules() const;
std::vector<RTLIL::Module*> selected_whole_modules_warn(bool include_wb = false) const;
For module init, It’s more complicated. Starting In kernel/yosys.cc
, Py_Initialize
is called after appending INIT_MODULE
#ifdef WITH_PYTHON
PyImport_AppendInittab((char*)"libyosys", INIT_MODULE);
Py_Initialize();
PyRun_SimpleString("import sys");
signal(SIGINT, SIG_DFL);
#endif
and INIT_MODULE
is macro in same file
#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
So, where are the wrappers? the complicated PY
stuff.
For that there is script external/yosys/misc/py_wrap_generator.py
which dumps all wrapper code using BOOST PYTHON
. This is snippet of the script showing the module init macro used.
BOOST_PYTHON_MODULE(libyosys)
{
using namespace boost::python;
class_<Initializer>("Initializer");
scope().attr("_hidden") = new Initializer();
def("log_to_stream", &log_to_stream);
""")
Side note about BOOST python modules, From the docs:
Introduction
This header provides the basic facilities needed to create a Boost.Python extension module.
Macros
BOOST_PYTHON_MODULE(name) is used to declare Python module initialization functions. The name argument must exactly match the name of the module to be initialized, and must conform to Python's identifier naming rules. Where you would normally write
extern "C" void initname()
{
...
}
Boost.Python modules should be initialized with
BOOST_PYTHON_MODULE(name)
{
...
}
So, who calls py_wrap_generator.py
?
During build process, Makefile calls that script to generate the wrappers to be compiled into libyosys.so
PY_GEN_SCRIPT= py_wrap_generator
...
...
ifeq ($(ENABLE_PYOSYS),1)
$(PY_WRAPPER_FILE).cc: misc/$(PY_GEN_SCRIPT).py $(PY_WRAP_INCLUDES)
$(Q) mkdir -p $(dir $@)
$(P) python$(PYTHON_VERSION) -c "from misc import $(PY_GEN_SCRIPT); $(PY_GEN_SCRIPT).gen_wrappers(\"$(PY_WRAPPER_FILE).cc\")"
endif
The last bit is installing libyosys.so
to be called from python as normal python module. The full path should be
.venv/lib/python3.10/dist-packages/pyosys/libyosys.so