Skip to content

testbench ¤

Test-bench helpers for wrapping device netlists with sources and loads.

A device netlist (e.g. from GDSFactory or hand-written SAX) describes the device only — its connectivity and external ports mapping. Running it in circulax additionally requires every external port to be terminated: driven by a source, loaded by a detector, or tied to GND.

:func:attach_testbench wraps the device into a runnable netlist by adding the requested terminations and wiring them to the device's external ports.

Functions:

Name Description
attach_testbench

Wrap a device netlist with sources, loads, and GND terminations.

attach_testbench ¤

attach_testbench(
    device: dict | Netlist,
    *,
    sources: dict[str, dict[str, Any]] | None = None,
    loads: dict[str, dict[str, Any]] | None = None,
    gnd: Iterable[str] | None = None
) -> dict | 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.

Source code in circulax/testbench.py
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 [])

    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}