def attach_testbench(
device: dict | kfnl.Netlist,
*,
sources: dict[str, dict[str, Any]] | None = None,
loads: dict[str, dict[str, Any]] | None = None,
gnd: Iterable[str] | None = None,
) -> dict | kfnl.Netlist:
"""Wrap a device netlist with sources, loads, and GND terminations.
Accepts either a SAX-format dict or a ``kfnetlist.Netlist``.
Returns the same type as the input.
"""
sources = sources or {}
loads = loads or {}
gnd_list = list(gnd or [])
# A device's own ports are still declared here (attach_testbench is the
# last stage that knows them — it rewrites every device port into a
# concrete source/load/GND connection below). An inert, unmodeled
# structural instance (die frame, logo, ...) must be dropped now, not
# after: the wrapped netlist this function returns exposes no top-level
# ports at all, so compile_netlist's own prune guard is a no-op on it.
device = prune_unreachable_instances(device)
if isinstance(device, kfnl.Netlist):
return _attach_testbench_kfnetlist(
device,
sources=sources,
loads=loads,
gnd_list=gnd_list,
)
# --- Legacy SAX dict path ---
device_ports = device.get("ports", {})
used = [*sources, *loads, *gnd_list]
duplicates = [p for p, c in Counter(used).items() if c > 1]
if duplicates:
msg = f"Device ports appear in multiple roles: {duplicates}"
raise ValueError(msg)
unknown = [p for p in used if p not in device_ports]
if unknown:
msg = f"Ports {unknown} not in device['ports']; available: {list(device_ports)}"
raise ValueError(msg)
instances = dict(device.get("instances", {}))
if "GND" not in instances:
instances["GND"] = {"component": "ground"}
nets: list[dict] = []
for src, tgts in device.get("connections", {}).items():
if isinstance(tgts, str):
tgts = (tgts,)
nets.extend({"p1": src, "p2": t} for t in tgts)
nets.extend(device.get("nets", []))
def _add_terminator(device_port: str, default_prefix: str, spec: dict[str, Any]) -> None:
name = spec.get("name") or f"{default_prefix}_{device_port}"
if name in instances:
msg = f"Instance name '{name}' already exists in device netlist"
raise ValueError(msg)
instances[name] = {k: v for k, v in spec.items() if k != "name"}
nets.append({"p1": f"{name},p1", "p2": device_ports[device_port]})
nets.append({"p1": f"{name},p2", "p2": "GND,p1"})
for port, spec in sources.items():
_add_terminator(port, "src", spec)
for port, spec in loads.items():
_add_terminator(port, "load", spec)
for port in gnd_list:
nets.append({"p1": device_ports[port], "p2": "GND,p1"})
return {"instances": instances, "nets": nets}