3
0
Fork 0
mirror of https://github.com/YosysHQ/yosys synced 2025-11-25 15:09:34 +00:00

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
This commit is contained in:
Mohamed Gaber 2025-09-21 22:36:27 +03:00
parent f7120e9c2a
commit 88be728353
No known key found for this signature in database
27 changed files with 2879 additions and 2674 deletions

39
tests/pyosys/run_tests.py Normal file
View file

@ -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)

BIN
tests/pyosys/spm.cut.v.gz Normal file

Binary file not shown.

View file

@ -0,0 +1,45 @@
from pyosys import libyosys as ys
from pathlib import Path
__file_dir__ = Path(__file__).absolute().parent
d = ys.Design()
ys.run_pass(f"read_verilog {__file_dir__ / 'spm.cut.v.gz'}", d)
ys.run_pass("hierarchy -top spm", d)
name_by_tv_location = []
name_by_au_location = []
# test both dictionary mapping and equiv operators working fine
module = None
print(d.modules_)
for idstr, module_obj in d.modules_.items():
if idstr != ys.IdString("\\spm"):
continue
if idstr.str() != "\\spm":
continue
module = module_obj
break
assert module == d.top_module(), "top module search failed"
for name in module.ports:
wire = module.wires_[name]
name_str = name.str()
if name_str.endswith(".d"): # single reg output, in au
name_by_au_location.append(name_str[1:-2])
elif name_str.endswith(".q"): # single reg input, in tv
name_by_tv_location.append(name_str[1:-2])
else: # port/boundary scan
frm = wire.start_offset + wire.width
to = wire.start_offset
for i in range(frm - 1, to - 1, -1):
bit_name = name_str[1:] + f"\\[{i}\\]"
if wire.port_input:
name_by_tv_location.append(bit_name)
elif wire.port_output:
name_by_au_location.append(bit_name)
assert name_by_tv_location == ['x\\[0\\]', 'a\\[31\\]', 'a\\[30\\]', 'a\\[29\\]', 'a\\[28\\]', 'a\\[27\\]', 'a\\[26\\]', 'a\\[25\\]', 'a\\[24\\]', 'a\\[23\\]', 'a\\[22\\]', 'a\\[21\\]', 'a\\[20\\]', 'a\\[19\\]', 'a\\[18\\]', 'a\\[17\\]', 'a\\[16\\]', 'a\\[15\\]', 'a\\[14\\]', 'a\\[13\\]', 'a\\[12\\]', 'a\\[11\\]', 'a\\[10\\]', 'a\\[9\\]', 'a\\[8\\]', 'a\\[7\\]', 'a\\[6\\]', 'a\\[5\\]', 'a\\[4\\]', 'a\\[3\\]', 'a\\[2\\]', 'a\\[1\\]', 'a\\[0\\]', '_315_', '_314_', '_313_', '_312_', '_311_', '_310_', '_309_', '_308_', '_307_', '_306_', '_305_', '_304_', '_303_', '_302_', '_301_', '_300_', '_299_', '_298_', '_297_', '_296_', '_295_', '_294_', '_293_', '_292_', '_291_', '_290_', '_289_', '_288_', '_287_', '_286_', '_285_', '_284_', '_283_', '_282_', '_281_', '_280_', '_279_', '_278_', '_277_', '_276_', '_275_', '_274_', '_273_', '_272_', '_271_', '_270_', '_269_', '_268_', '_267_', '_266_', '_265_', '_264_', '_263_', '_262_', '_261_', '_260_', '_259_', '_258_', '_257_', '_256_', '_255_', '_254_', '_253_', '_252_'], "failed to extract test vector register locations"
assert name_by_au_location == ['y\\[0\\]', '_315_', '_314_', '_313_', '_312_', '_311_', '_310_', '_309_', '_308_', '_307_', '_306_', '_305_', '_304_', '_303_', '_302_', '_301_', '_300_', '_299_', '_298_', '_297_', '_296_', '_295_', '_294_', '_293_', '_292_', '_291_', '_290_', '_289_', '_288_', '_287_', '_286_', '_285_', '_284_', '_283_', '_282_', '_281_', '_280_', '_279_', '_278_', '_277_', '_276_', '_275_', '_274_', '_273_', '_272_', '_271_', '_270_', '_269_', '_268_', '_267_', '_266_', '_265_', '_264_', '_263_', '_262_', '_261_', '_260_', '_259_', '_258_', '_257_', '_256_', '_255_', '_254_', '_253_', '_252_'], "failed to extract golden output register locations"
print("ok!")

13
tests/pyosys/test_dict.py Normal file
View file

@ -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"}

View file

@ -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"

View file

@ -0,0 +1,3 @@
from pyosys import libyosys as ys
ys.log("Hello, world!")

View file

@ -0,0 +1,22 @@
from pyosys import libyosys as ys
from pathlib import Path
__file_dir__ = Path(__file__).absolute().parent
d = ys.Design()
class Monitor(ys.Monitor):
def __init__(self):
super().__init__()
self.mods = []
def notify_module_add(self, mod):
self.mods.append(mod.name.str())
m = Monitor()
d.monitors.add(m)
ys.run_pass(f"read_verilog {__file_dir__ / 'spm.cut.v.gz'}", d)
ys.run_pass("hierarchy -top spm", d)
assert m.mods == ["\\spm"]

34
tests/pyosys/test_pass.py Normal file
View file

@ -0,0 +1,34 @@
from pyosys import libyosys as ys
import json
from pathlib import Path
__file_dir__ = Path(__file__).absolute().parent
class CellStatsPass(ys.Pass):
def __init__(self):
super().__init__(
"cell_stats",
"dumps cell statistics in JSON format"
)
def execute(self, args, design):
ys.log_header(design, "Dumping cell stats\n")
ys.log_push()
cell_stats = {}
for module in design.all_selected_whole_modules():
for cell in module.selected_cells():
if cell.type.str() in cell_stats:
cell_stats[cell.type.str()] += 1
else:
cell_stats[cell.type.str()] = 1
ys.log(json.dumps(cell_stats))
ys.log_pop()
p = CellStatsPass() # registration
design = ys.Design()
ys.run_pass(f"read_verilog {__file_dir__.parent / 'simple' / 'fiedler-cooley.v'}", design)
ys.run_pass("prep", design)
ys.run_pass("opt -full", design)
ys.run_pass("cell_stats", design)

View file

@ -0,0 +1,21 @@
from pathlib import Path
from pyosys import libyosys as ys
__file_dir__ = Path(__file__).absolute().parent
add_sub = __file_dir__.parent / "arch" / "common" / "add_sub.v"
base = ys.Design()
ys.run_pass(f"read_verilog {add_sub}", base)
ys.run_pass("hierarchy -top top", base)
ys.run_pass("proc", base)
ys.run_pass("equiv_opt -assert -map +/ecp5/cells_sim.v synth_ecp5", base)
postopt = ys.Design()
ys.run_pass("design -load postopt", postopt)
ys.run_pass("cd top", postopt)
ys.run_pass("select -assert-min 25 t:LUT4", postopt)
ys.run_pass("select -assert-max 26 t:LUT4", postopt)
ys.run_pass("select -assert-count 10 t:PFUMX", postopt)
ys.run_pass("select -assert-count 6 t:L6MUX21", postopt)
ys.run_pass("select -assert-none t:LUT4 t:PFUMX t:L6MUX21 %% t:* %D", postopt)