Adds diagrams.
@@ -6,6 +6,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
nextpnr-ice40 \
|
nextpnr-ice40 \
|
||||||
fpga-icestorm \
|
fpga-icestorm \
|
||||||
nodejs npm \
|
nodejs npm \
|
||||||
|
graphviz \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
RUN npm install -g @anthropic-ai/claude-code
|
RUN npm install -g @anthropic-ai/claude-code
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# Schematics
|
||||||
|
|
||||||
|
Synthesized (RTL-level) schematics of the BBA design, generated from the actual
|
||||||
|
Amaranth elaboration via yosys + graphviz. Regenerate with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m exi_bba.diagram # all of the below
|
||||||
|
python -m exi_bba.diagram w5100_master # just one
|
||||||
|
```
|
||||||
|
|
||||||
|
Open the `.svg` files in a browser or VS Code (they're vector — zoom freely).
|
||||||
|
|
||||||
|
| File | What it shows | Size |
|
||||||
|
|---|---|---|
|
||||||
|
| `bbatop_blocks.svg` | **Top-level block diagram** — BBATop's submodules (ExiCapture, register file, SPRAM arbiter, RX/TX engines, W5100 master, status panel) as boxes with their interconnections. Start here. | 186 cells |
|
||||||
|
| `spi_mode3_slave.svg` | EXI Mode-3 SPI bit engine (the capture-domain front-end) — shift registers, bit counter, MISO/MOSI logic. | 109 cells |
|
||||||
|
| `spram_arbiter.svg` | SPRAM priority arbiter — ETH-write-vs-EXI-read mux + the SB_SPRAM read/write ports. | 80 cells |
|
||||||
|
| `tx_frame_drain.svg` | TX drain FSM — pulls frames from the FIFOs and streams them to the ethernet master with SOF/EOF. | 124 cells |
|
||||||
|
| `status_panel.svg` | Bring-up panel — heartbeat counter, activity-LED stretchers, button debounce. | 180 cells |
|
||||||
|
| `rx_frame_assembler.svg` | RX assembler — writes received frames into the SPRAM ring and advances RWP. | 284 cells |
|
||||||
|
| `w5100_master.svg` | **Full W5100 parallel master** (bus engine + transaction engine + init/TX/RX FSM). Poster-sized (~1100 cells) — zoom in. | 1129 cells |
|
||||||
|
|
||||||
|
These are RTL views: real muxes (`$mux`), adders (`$add`), comparators (`$eq`),
|
||||||
|
and flip-flops (`$dff`/`$adff`) — *not* the post-place LUT soup. The whole
|
||||||
|
flattened-and-mapped `BBATop` would be ~2300 LUTs and illegible, which is why the
|
||||||
|
top level is shown as a block diagram and the leaves as per-module RTL.
|
||||||
|
After Width: | Height: | Size: 179 KiB |
|
After Width: | Height: | Size: 380 KiB |
|
After Width: | Height: | Size: 133 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 251 KiB |
|
After Width: | Height: | Size: 144 KiB |
|
After Width: | Height: | Size: 1.7 MiB |
@@ -0,0 +1,96 @@
|
|||||||
|
"""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 <name>... # 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}/")
|
||||||