gary/tasks.py

200 lines
7.5 KiB
Python

#!/usr/bin/env python
import os.path
from pathlib import Path
from invoke import task
from textwrap import dedent
import shutil
def ensure_build_dirs(root : Path, *subdirs : list[Path]):
if root.is_file():
root = root.parent / root.stem
ret = []
for d in subdirs:
out = "out" / root / d
out.mkdir(mode=0o750, parents=True, exist_ok=True)
ret.append(out)
return ret
def bsc_root(c):
out = c.run("bsc --help", hide="both")
for l in out.stdout.splitlines():
dir_prefix = "Bluespec directory: "
if l.startswith(dir_prefix):
return Path(l[len(dir_prefix):])
raise RuntimeError("Couldn't locate Bluespec root dir")
def find_verilog_modules(c, modules):
libpaths = [Path("lib"), bsc_root(c) / "Verilog"]
ret = []
for module in modules:
module_path = None
for p in libpaths:
f = p / Path(module).with_suffix(".v")
if f.is_file():
module_path = f
break
if module_path is None:
raise RuntimeError(f"Cannot find verilog module {module} in {libpaths}")
ret.append(module_path)
return ret
def print_filtered_paragraphs(s, *paragraph_prefixes, common_prefix=""):
for block in s.split("\n\n"):
for p in paragraph_prefixes:
if block.removeprefix(common_prefix).startswith(p):
print('\n'.join(s.removeprefix(common_prefix) for s in block.splitlines()))
print("")
def expand_build_target(target):
t = Path(target)
if t.is_dir():
yield from t.glob("**/*.bsv")
elif t.is_file() and t.suffix == ".bsv":
yield t
else:
raise ValueError(f"Unknown target type {t}")
def resolve_synth_target(target):
target = Path(target)
if '/' not in str(target):
target = "hardware" / target
if target.is_dir():
target /= "Top.bsv"
if not target.is_file():
raise ValueError(f"Unknown target type {target}")
return target
def expand_test_target(target):
t = Path(target)
if t.is_dir():
yield from t.glob("**/*_Test.bsv")
elif t.is_file() and t.name.endswith("_Test.bsv"):
yield t
else:
raise ValueError(f"Unknown target type {t}")
def phase(name):
print("")
print("*"*(len(name)+6))
print(f"** {name} **")
print("*"*(len(name)+6))
print("")
@task
def build(c, target="."):
phase("Compile Bluespec")
verilog_files = []
for target in expand_build_target(target):
out_info, out_verilog, out_bsc = ensure_build_dirs(target, "info", "verilog", "bsc")
print(f"Building {target}")
c.run(f"bsc -aggressive-conditions -check-assert -remove-dollar -remove-empty-rules -remove-false-rules -remove-starved-rules -remove-unused-modules -show-method-conf -show-method-bvi -u -verilog -info-dir {out_info} -vdir {out_verilog} -bdir {out_bsc} -p {target.parent}:vram:lib:%/Libraries -show-module-use -show-compiles {target}")
module_name = Path(f"mk{target.stem}")
verilog_main_file = out_verilog / module_name.with_suffix(".v")
if verilog_main_file.is_file():
verilog_files.append(verilog_main_file)
use_file = out_verilog / module_name.with_suffix(".use")
if use_file.is_file():
with open(out_verilog / module_name.with_suffix(".use")) as f:
verilog_files.extend(find_verilog_modules(c, f.read().splitlines()))
if verilog_files:
print("\nVerilog files for synthesis:")
for v in verilog_files:
print(" "+str(v))
return verilog_files
@task
def synth(c, target):
target = resolve_synth_target(target)
out_info, out_verilog, out_bsc, out_yosys, out_nextpnr = ensure_build_dirs(target, "info", "verilog", "bsc", "yosys", "nextpnr")
module_name = Path(f"mk{target.stem}")
verilog_files = build(c, target)
if not verilog_files:
print("\nWARNING: bluespec compile didn't output any verilog files, did you (* synthesize *) something?")
return
phase("Logic synthesis")
yosys_script = out_yosys / "script.ys"
yosys_json = out_yosys / module_name.with_suffix(".json")
yosys_report = out_yosys / module_name.with_suffix(".stats")
yosys_log = out_yosys / module_name.with_suffix(".log")
yosys_preprocessed = out_yosys / module_name.with_suffix(".v")
yosys_preprocessed_rtl = out_yosys / module_name.with_suffix(".rtlil")
yosys_preprocessed_graph = out_yosys / module_name.stem
yosys_compiled = out_yosys / f"{module_name.stem}_compiled.v"
with open(yosys_script, "w", encoding="UTF-8") as f:
f.write(dedent(f"""\
read_verilog -sv -defer {' '.join(str(f) for f in verilog_files)}
hierarchy -top {module_name}
design -push-copy
synth_lattice -family ecp5 -abc9 -run :map_ram
opt -full
clean -purge
write_verilog -sv {yosys_preprocessed}
write_rtlil {yosys_preprocessed_rtl}
show -format dot -colors 1 -stretch -viewer none -prefix {yosys_preprocessed_graph}
design -pop
synth_lattice -family ecp5 -abc9 -no-rw-check
clean -purge
write_json {yosys_json}
write_verilog -sv {yosys_compiled}
tee -o {yosys_report} stat
"""))
c.run(f"yosys -q -L {yosys_log} -T {yosys_script}")
with open(yosys_report) as f:
ls = f.readlines()[3:]
print("".join(ls))
print(f" JSON : {yosys_json}")
print(f" Log : {yosys_log}")
print(f" Flat : {yosys_preprocessed}")
print(f"Compiled : {yosys_compiled}")
print(f" Dot : {yosys_preprocessed_graph}.dot")
pin_map = target.parent / "pin_map.lpf"
if not pin_map.is_file():
print(f"WARNING: no pin map at {pin_map}, skipping place&route and bitstream generation")
return
phase("Place and route")
nextpnr_out = out_nextpnr / module_name.with_suffix(".pnr")
nextpnr_log = out_nextpnr / module_name.with_suffix(".log")
nextpnr_timing = out_nextpnr / module_name.with_suffix(".log")
out = c.run(f"nextpnr-ecp5 --85k --detailed-timing-report -l {nextpnr_log} --report {nextpnr_timing} --json {yosys_json} --lpf {pin_map} --package=CABGA381 --speed=6 --textcfg {nextpnr_out}", hide='stderr')
print_filtered_paragraphs(out.stderr, "Device utilisation", "Critical path", "Max frequency", "Max delay", common_prefix="Info: ")
print(f" PNR : {nextpnr_out}")
print(f" Log : {nextpnr_log}")
print(f"Timing : {nextpnr_timing}")
phase("Bitstream generation")
bitstream = nextpnr_out.with_suffix(".bit")
c.run(f"ecppack {nextpnr_out} {bitstream}")
print(f"Wrote bitstream to {bitstream}")
@task
def test(c, target):
for target in expand_test_target(target):
out_info, out_sim, out_bsc = ensure_build_dirs(target, "info", "sim", "bsc")
c.run(f"bsc -show-schedule -aggressive-conditions -check-assert -u -sim -info-dir {out_info} -simdir {out_sim} -bdir {out_bsc} -g mkTB -p {target.parent}:lib:sim:%/Libraries {target}")
exec = out_sim / "TB"
c.run(f"bsc -p {out_bsc} -sim -simdir {out_sim} -e mkTB -o {exec}")
testdata = out_sim / "testdata"
testdata_tgt = target.parent / "testdata"
testdata_tgt = testdata_tgt.relative_to(out_sim, walk_up=True)
testdata.unlink(missing_ok=True)
testdata.symlink_to(testdata_tgt, target_is_directory=True)
with c.cd(out_sim):
c.run(f"./TB -V {target.stem}.vcd")
@task
def clean(c):
if Path("out").is_dir():
shutil.rmtree("out")