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:
parent
f7120e9c2a
commit
88be728353
27 changed files with 2879 additions and 2674 deletions
39
tests/pyosys/run_tests.py
Normal file
39
tests/pyosys/run_tests.py
Normal 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
BIN
tests/pyosys/spm.cut.v.gz
Normal file
Binary file not shown.
45
tests/pyosys/test_data_read.py
Normal file
45
tests/pyosys/test_data_read.py
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
from pyosys import libyosys as ys
|
||||
from pathlib import Path
|
||||
|
||||
__file_dir__ = Path(__file__).absolute().parent
|
||||
|
||||
d = ys.Design()
|
||||
|
||||
ys.run_pass(f"read_verilog {__file_dir__ / 'spm.cut.v.gz'}", d)
|
||||
ys.run_pass("hierarchy -top spm", d)
|
||||
|
||||
name_by_tv_location = []
|
||||
name_by_au_location = []
|
||||
|
||||
# test both dictionary mapping and equiv operators working fine
|
||||
module = None
|
||||
print(d.modules_)
|
||||
for idstr, module_obj in d.modules_.items():
|
||||
if idstr != ys.IdString("\\spm"):
|
||||
continue
|
||||
if idstr.str() != "\\spm":
|
||||
continue
|
||||
module = module_obj
|
||||
break
|
||||
|
||||
assert module == d.top_module(), "top module search failed"
|
||||
for name in module.ports:
|
||||
wire = module.wires_[name]
|
||||
name_str = name.str()
|
||||
if name_str.endswith(".d"): # single reg output, in au
|
||||
name_by_au_location.append(name_str[1:-2])
|
||||
elif name_str.endswith(".q"): # single reg input, in tv
|
||||
name_by_tv_location.append(name_str[1:-2])
|
||||
else: # port/boundary scan
|
||||
frm = wire.start_offset + wire.width
|
||||
to = wire.start_offset
|
||||
for i in range(frm - 1, to - 1, -1):
|
||||
bit_name = name_str[1:] + f"\\[{i}\\]"
|
||||
if wire.port_input:
|
||||
name_by_tv_location.append(bit_name)
|
||||
elif wire.port_output:
|
||||
name_by_au_location.append(bit_name)
|
||||
|
||||
assert name_by_tv_location == ['x\\[0\\]', 'a\\[31\\]', 'a\\[30\\]', 'a\\[29\\]', 'a\\[28\\]', 'a\\[27\\]', 'a\\[26\\]', 'a\\[25\\]', 'a\\[24\\]', 'a\\[23\\]', 'a\\[22\\]', 'a\\[21\\]', 'a\\[20\\]', 'a\\[19\\]', 'a\\[18\\]', 'a\\[17\\]', 'a\\[16\\]', 'a\\[15\\]', 'a\\[14\\]', 'a\\[13\\]', 'a\\[12\\]', 'a\\[11\\]', 'a\\[10\\]', 'a\\[9\\]', 'a\\[8\\]', 'a\\[7\\]', 'a\\[6\\]', 'a\\[5\\]', 'a\\[4\\]', 'a\\[3\\]', 'a\\[2\\]', 'a\\[1\\]', 'a\\[0\\]', '_315_', '_314_', '_313_', '_312_', '_311_', '_310_', '_309_', '_308_', '_307_', '_306_', '_305_', '_304_', '_303_', '_302_', '_301_', '_300_', '_299_', '_298_', '_297_', '_296_', '_295_', '_294_', '_293_', '_292_', '_291_', '_290_', '_289_', '_288_', '_287_', '_286_', '_285_', '_284_', '_283_', '_282_', '_281_', '_280_', '_279_', '_278_', '_277_', '_276_', '_275_', '_274_', '_273_', '_272_', '_271_', '_270_', '_269_', '_268_', '_267_', '_266_', '_265_', '_264_', '_263_', '_262_', '_261_', '_260_', '_259_', '_258_', '_257_', '_256_', '_255_', '_254_', '_253_', '_252_'], "failed to extract test vector register locations"
|
||||
assert name_by_au_location == ['y\\[0\\]', '_315_', '_314_', '_313_', '_312_', '_311_', '_310_', '_309_', '_308_', '_307_', '_306_', '_305_', '_304_', '_303_', '_302_', '_301_', '_300_', '_299_', '_298_', '_297_', '_296_', '_295_', '_294_', '_293_', '_292_', '_291_', '_290_', '_289_', '_288_', '_287_', '_286_', '_285_', '_284_', '_283_', '_282_', '_281_', '_280_', '_279_', '_278_', '_277_', '_276_', '_275_', '_274_', '_273_', '_272_', '_271_', '_270_', '_269_', '_268_', '_267_', '_266_', '_265_', '_264_', '_263_', '_262_', '_261_', '_260_', '_259_', '_258_', '_257_', '_256_', '_255_', '_254_', '_253_', '_252_'], "failed to extract golden output register locations"
|
||||
print("ok!")
|
||||
13
tests/pyosys/test_dict.py
Normal file
13
tests/pyosys/test_dict.py
Normal 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"}
|
||||
31
tests/pyosys/test_idict.py
Normal file
31
tests/pyosys/test_idict.py
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
from pyosys import libyosys as ys
|
||||
|
||||
my_idict = ys.IdstringIdict()
|
||||
print(my_idict(ys.IdString("\\hello")))
|
||||
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"
|
||||
3
tests/pyosys/test_import.py
Normal file
3
tests/pyosys/test_import.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from pyosys import libyosys as ys
|
||||
|
||||
ys.log("Hello, world!")
|
||||
22
tests/pyosys/test_monitor.py
Normal file
22
tests/pyosys/test_monitor.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
from pyosys import libyosys as ys
|
||||
from pathlib import Path
|
||||
|
||||
__file_dir__ = Path(__file__).absolute().parent
|
||||
|
||||
d = ys.Design()
|
||||
|
||||
class Monitor(ys.Monitor):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.mods = []
|
||||
|
||||
def notify_module_add(self, mod):
|
||||
self.mods.append(mod.name.str())
|
||||
|
||||
m = Monitor()
|
||||
d.monitors.add(m)
|
||||
|
||||
ys.run_pass(f"read_verilog {__file_dir__ / 'spm.cut.v.gz'}", d)
|
||||
ys.run_pass("hierarchy -top spm", d)
|
||||
|
||||
assert m.mods == ["\\spm"]
|
||||
34
tests/pyosys/test_pass.py
Normal file
34
tests/pyosys/test_pass.py
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
from pyosys import libyosys as ys
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
__file_dir__ = Path(__file__).absolute().parent
|
||||
|
||||
class CellStatsPass(ys.Pass):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
"cell_stats",
|
||||
"dumps cell statistics in JSON format"
|
||||
)
|
||||
|
||||
def execute(self, args, design):
|
||||
ys.log_header(design, "Dumping cell stats\n")
|
||||
ys.log_push()
|
||||
cell_stats = {}
|
||||
for module in design.all_selected_whole_modules():
|
||||
for cell in module.selected_cells():
|
||||
if cell.type.str() in cell_stats:
|
||||
cell_stats[cell.type.str()] += 1
|
||||
else:
|
||||
cell_stats[cell.type.str()] = 1
|
||||
ys.log(json.dumps(cell_stats))
|
||||
ys.log_pop()
|
||||
|
||||
p = CellStatsPass() # registration
|
||||
|
||||
design = ys.Design()
|
||||
ys.run_pass(f"read_verilog {__file_dir__.parent / 'simple' / 'fiedler-cooley.v'}", design)
|
||||
ys.run_pass("prep", design)
|
||||
ys.run_pass("opt -full", design)
|
||||
ys.run_pass("cell_stats", design)
|
||||
21
tests/pyosys/test_script.py
Normal file
21
tests/pyosys/test_script.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
from pathlib import Path
|
||||
from pyosys import libyosys as ys
|
||||
|
||||
|
||||
__file_dir__ = Path(__file__).absolute().parent
|
||||
add_sub = __file_dir__.parent / "arch" / "common" / "add_sub.v"
|
||||
|
||||
base = ys.Design()
|
||||
ys.run_pass(f"read_verilog {add_sub}", base)
|
||||
ys.run_pass("hierarchy -top top", base)
|
||||
ys.run_pass("proc", base)
|
||||
ys.run_pass("equiv_opt -assert -map +/ecp5/cells_sim.v synth_ecp5", base)
|
||||
|
||||
postopt = ys.Design()
|
||||
ys.run_pass("design -load postopt", postopt)
|
||||
ys.run_pass("cd top", postopt)
|
||||
ys.run_pass("select -assert-min 25 t:LUT4", postopt)
|
||||
ys.run_pass("select -assert-max 26 t:LUT4", postopt)
|
||||
ys.run_pass("select -assert-count 10 t:PFUMX", postopt)
|
||||
ys.run_pass("select -assert-count 6 t:L6MUX21", postopt)
|
||||
ys.run_pass("select -assert-none t:LUT4 t:PFUMX t:L6MUX21 %% t:* %D", postopt)
|
||||
Loading…
Add table
Add a link
Reference in a new issue