Netlist Parser

Layer 0 — netlist parsing.

Parses circuitgenome.synthesizer.netlist.to_flat_spice() output — and, now, sized SPICE netlists produced by real design flows or CircuitGenome’s own sizer — into a ParsedNetlist. The unsized form is the structural inverse of to_flat_spice’s _device_line:

{ref} {d} {g} {s} {b} {nmos|pmos}    # MOSFET   (6 tokens)
{ref} {t1} {t2} [1k]                 # resistor (3-4 tokens)
{ref} {p} {m} [1p]                   # capacitor (3-4 tokens)

wrapped in a .subckt <name> <port...> / .ends block (or .suckt for netlists with that common typo). Net and ref names are treated as arbitrary strings – the parser makes no assumptions about to_flat_spice’s own naming conventions (e.g. net_bias{N} or {ref}_{slot}).

Sized dialect. The parser also accepts the sized dialect that real flows and the sizer emit:

  • MOSFET lines may carry trailing key=value parameters — W=0.5u L=0.15u nf=1 m=2 — with values kept as SI-suffixed SPICE strings. Any other key=value token is tolerated and preserved. They land in params keyed as written.

  • The type token may be a real process model name (e.g. sky130_fd_pr__nfet_01v8) mapped to nmos/pmos through a configurable model-name table (see DEFAULT_MODEL_MAP); the bare nmos/pmos tokens keep working. Because real flows instantiate those models as subcircuits, a MOSFET line may be x-prefixed instead of m-prefixed — both are accepted when the model token resolves to a MOSFET.

  • Resistor/capacitor value tokens (1k / 1p) are preserved on params under value rather than ignored.

Sizes ride along on the parsed devices; recognition still matches on topology (device type + terminal connectivity), so recognize() behaves identically on sized and unsized netlists.

Device lines are dispatched by the first character of the ref (SPICE convention): m → MOSFET, c → capacitor, r → resistor, x → subcircuit instance (accepted only when its model token names a MOSFET in the model map — the real-flow SKY130 device shape).

circuitgenome.recognizer.netlist_parser.DEFAULT_MODEL_MAP: dict[str, str] = {'nmos': 'nmos', 'nmos_3p3': 'nmos', 'pmos': 'pmos', 'pmos_3p3': 'pmos', 'sky130_fd_pr__nfet_01v8': 'nmos', 'sky130_fd_pr__nfet_01v8_lvt': 'nmos', 'sky130_fd_pr__pfet_01v8': 'pmos', 'sky130_fd_pr__pfet_01v8_hvt': 'pmos', 'sky130_fd_pr__pfet_01v8_lvt': 'pmos'}

Default model-name → device-type table. Maps the bare synthesizer tokens, the CircuitGenome device_map targets (gf180mcu core), and the common SKY130 process primitives onto "nmos"/"pmos". Lookups are case-insensitive. Callers pass a model_map to parse() to extend or override this table (their entries win).

circuitgenome.recognizer.netlist_parser.parse(spice, model_map=None)[source]

Parse a flat SPICE .subckt block into a ParsedNetlist.

Accepts both the unsized dialect emitted by to_flat_spice() and the sized dialect produced by real design flows / CircuitGenome’s own sizer (see the module docstring): MOSFET lines with trailing W=/L=/nf=/m= (and any other key=value) params, real process model names, and preserved resistor/capacitor value tokens.

Parameters:
  • spice (str) – A SPICE netlist string. The header keyword may be .subckt or .suckt (common typo).

  • model_map (dict[str, str] | None) – Optional model-name → "nmos"/"pmos" table, merged over DEFAULT_MODEL_MAP (caller entries win) and matched case-insensitively. Use it to teach the parser process-specific model names beyond the built-in SKY130/gf180 set.

Returns:

A ParsedNetlist with devices in the same order as the input, and internal_nets = every net referenced by a device terminal that is not in external_ports. Sizing parameters ride along on each params.

Raises:

ValueError – If a MOSFET line’s model token is unknown, a device line has an unexpected token count/shape, or a bare token follows a key=value parameter.

Return type:

ParsedNetlist

Example:

from circuitgenome.recognizer import parse, recognize

parsed = parse('''
.subckt ota in1 in2 out vdd! gnd!
m1 net1 in1 tail vdd! sky130_fd_pr__pfet_01v8 W=4u L=0.5u nf=2 m=1
m2 net2 in2 tail vdd! sky130_fd_pr__pfet_01v8 W=4u L=0.5u nf=2 m=1
.ends''')
print(parsed.devices[0].type)             # "pmos"
print(parsed.devices[0].params["W"])      # "4u"
result = recognize(parsed)                 # topology match, sizes ride along