"""Generate synthesized schematics (SVG) for the BBA design via yosys + graphviz. Run from the workspace root: python -m exi_bba.diagram # render the standard set into diagrams/ python -m exi_bba.diagram ... # render only the named module(s) For each module we emit RTLIL (`amaranth.back.rtlil`), run yosys to elaborate RTL processes (`proc`/`opt`), and `show` it to SVG through graphviz `dot`. A schematic of the *whole* synthesised BBATop is a ~2300-LUT hairball, so we render readable **per-module RTL** views (real muxes/adders/FFs, not anonymous LUTs), plus a top-level **block diagram** (BBATop's submodules as boxes). """ import os import shutil import subprocess import sys from amaranth import Signal from amaranth.back import rtlil from exi_bba.spi_mode3_slave import SPIMode3Slave from exi_bba.tx_frame_drain import TXFrameDrain from exi_bba.status_panel import StatusPanel from exi_bba.spram_arbiter import SPRAMArbiter from exi_bba.rx_frame_assembler import RXFrameAssembler from exi_bba.w5100_parallel_master import W5100ParallelMaster from exi_bba.bba_top import BBATop OUT = "diagrams" _YOSYS = shutil.which("yosys") def _ports(obj): """Top-level interface = all Signal attributes of the instance.""" return [v for v in vars(obj).values() if isinstance(v, Signal)] def render(name, obj, *, block=False): """Emit RTLIL for `obj` and render an SVG schematic into diagrams/. block=True → keep the hierarchy and show submodules as boxes (top-level block diagram). block=False → flatten one module to RTL cells. """ if _YOSYS is None: sys.exit("yosys not found on PATH (install it, or use amaranth_yosys).") os.makedirs(OUT, exist_ok=True) il_path = os.path.join(OUT, f"{name}.il") with open(il_path, "w") as f: f.write(rtlil.convert(obj, name=name, ports=_ports(obj))) prefix = os.path.join(OUT, name) if block: # Keep the hierarchy; show only the top module (submodules → boxes). script = (f"read_rtlil {il_path}; hierarchy -top {name}; proc; " f"show -format svg -prefix {prefix} -notitle {name}") else: # Flatten tiny CDC sub-cells in, convert processes, render RTL cells. # `delete t:$meminit_v2` drops the (cosmetic) memory-init constant — a # 64K-word all-zero vector that overflows graphviz's string limit. script = (f"read_rtlil {il_path}; hierarchy -top {name}; " f"delete t:$meminit_v2; proc; flatten; opt -purge; " f"show -format svg -prefix {prefix} -notitle -colors 1") subprocess.run([_YOSYS, "-q", "-p", script], check=True) for junk in (il_path, f"{prefix}.dot"): # keep only the .svg if os.path.exists(junk): os.remove(junk) print(f" ✓ {prefix}.svg") # name → (factory, block?) TARGETS = { "spi_mode3_slave": (lambda: SPIMode3Slave(domain="sync"), False), "tx_frame_drain": (lambda: TXFrameDrain(), False), "status_panel": (lambda: StatusPanel(), False), "spram_arbiter": (lambda: SPRAMArbiter(), False), "rx_frame_assembler": (lambda: RXFrameAssembler(), False), "w5100_master": (lambda: W5100ParallelMaster(), False), "bbatop_blocks": (lambda: BBATop(eth="w5100"), True), } if __name__ == "__main__": want = sys.argv[1:] or list(TARGETS) for name in want: if name not in TARGETS: print(f" ? unknown target '{name}' (have: {', '.join(TARGETS)})") continue factory, block = TARGETS[name] print(f"rendering {name} …") try: render(name, factory(), block=block) except Exception as exc: print(f" ✗ {name} failed: {exc}") print(f"\nDone → {OUT}/")