Metadata-Version: 2.4
Name: circuitsilk
Version: 0.1.1
Summary: Python framework for analog circuit design automation with SPICE netlist generation and simulation
Author: Levant Labs
License: MIT
Project-URL: Homepage, https://github.com/levantlabs/silk
Project-URL: Repository, https://github.com/levantlabs/silk
Project-URL: Documentation, https://github.com/levantlabs/silk/blob/main/docs/quickstart.md
Project-URL: Bug Tracker, https://github.com/levantlabs/silk/issues
Keywords: spice,analog,eda,sky130,ngspice,circuit,netlist
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)
Classifier: Topic :: Scientific/Engineering
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy
Requires-Dist: matplotlib
Requires-Dist: pyyaml
Requires-Dist: pandas
Provides-Extra: ui
Requires-Dist: gradio; extra == "ui"
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"
Dynamic: license-file

# circuitsilk

**circuitsilk** (`import silk`) is a Python framework for analog circuit design automation.
It lets you describe circuits programmatically, generate SPICE netlists, run ngspice
simulations, sweep design spaces, and track versions in a local database. The PDK layer
is pluggable — SkyWater 130nm is the first supported PDK, with more to follow.

```python
from silk import Module, load_pdk
from silk.simulation import Transient

pdk = load_pdk('sky130')          # reads $PDK_ROOT env var
ckt = Module('PDK_skywater130', 'out', 'diffpair.cir', pdkDetails=pdk)

mn1 = pdk.nmos1p8('mn1', d='voutn', g='vinp', s='tail', b='gnd', w=4.0, l=0.15)
mp1 = pdk.pmos1p8('mp1', d='voutp', g='voutp', s='vdd',  b='vdd',  w=4.0, l=0.15)
ckt.add(mn1, mp1)
ckt.addSimType(Transient(step=1e-9, stop=100e-9))
ckt.simulate(corner='tt')
vout = ckt.loadSignal('v(voutp)')
```

## Installation

```bash
pip install circuitsilk          # PyPI (when published)
# — or from source —
git clone https://github.com/...
cd silk
pip install -e ".[dev]"
```

**System requirements:**

- Python 3.9+
- ngspice (for simulation) — `sudo apt install ngspice` on Debian/Ubuntu
- SkyWater 130nm PDK models at `$PDK_ROOT` (for `load_pdk`)

The `PDK_ROOT` environment variable should point to a directory that contains
`libraries/sky130_fd_pr/latest/models/sky130.lib.spice`.

## Quick start

See [docs/quickstart.md](docs/quickstart.md) for a step-by-step walkthrough.

Working examples live in `silk/examples/`:

| File | What it shows |
|---|---|
| `silk/examples/main.py` | Differential pair with factory API |
| `silk/examples/factory_api_example.py` | Full factory API: devices, passives, SPICE inspection |
| `silk/examples/parameter_sweep_example.py` | Design-space sweep with `Space`/`Variable` |
| `silk/examples/ldo_design.py` | Parameterized LDO with `Module` + `PyNetlist` DB tracking |
| `silk/examples/source_examples.py` | Voltage/current source waveforms |

## Core concepts

### `Module` — the circuit object

`Module` is the central design object. It holds devices, sources, passives, and
simulation commands and generates SPICE netlists.

```python
from silk import Module
ckt = Module('PDK_skywater130', 'out_dir', 'my_circuit.cir', pdkDetails=pdk)
ckt.add(device1, device2)          # add pre-constructed device objects
ckt.addSource(vsource)             # add a voltage or current source
ckt.addPassive(capacitor)          # add R, C, or L
ckt.addSimType(Transient(...))     # set the simulation type
ckt.simulate(corner='tt')          # generate netlist + run ngspice + load results
sig = ckt.loadSignal('v(out)')     # retrieve a waveform
```

### Factory API — device instantiation

Devices are instantiated directly from the PDK object. Pins and parameters are
keyword arguments — IDE autocomplete works on them.

```python
mn = pdk.nmos1p8('mn', d='out', g='in', s='gnd', b='gnd', w=1.0, l=0.35, m=4)
mp = pdk.pmos1p8('mp', d='out', g='in', s='vdd', b='vdd', w=2.0, l=0.35)

# Parameters can be updated by direct attribute assignment after construction:
mn.w = 2.0
mn.d = 'new_out'
print(mn.to_spice())               # inspect the SPICE line
```

### PDK loading

```python
# Option 1: load_pdk (uses $PDK_ROOT env var or ~/pdk)
from silk import load_pdk
pdk = load_pdk('sky130')

# Option 2: explicit paths
import os
from silk.pdk import PDK_skywater130
pdk = PDK_skywater130(
    os.path.join(os.path.dirname(__file__), 'silk', 'pdk', 'data',
                 'skywater_130_device_details.yaml'),
    libraryLocation='/path/to/sky130.lib.spice',
)
```

### Simulation types

```python
from silk.simulation import Transient, AC, DC, Noise, Corner

ckt.addSimType(Transient(step=1e-9, stop=100e-9))
ckt.addSimType(AC('dec', 100, 1e3, 1e9))
ckt.addSimType(DC(source='vin', start=0, stop=1.8, step=0.01))

# Corner is a str-subclass enum — plain strings also work
ckt.simulate(corner=Corner.TT)
ckt.simulate(corner='ff')          # equivalent
```

### Sources

```python
from silk import sources

vs = sources.VoltageSource('vdd', positive='vdd', negative='gnd', dc=1.8)
cs = sources.CurrentSource('ibias', positive='vdd', negative='vnbias', dc=100e-6)

# Waveforms
vs_pulse = sources.VoltageSource('vin', positive='in', negative='gnd',
                                  pulse=sources.Pulse(v1=0, v2=1.8, td=0,
                                                      tr=100e-12, tf=100e-12,
                                                      pw=5e-9, per=10e-9))
vs_sine  = sources.VoltageSource('vin', positive='in', negative='gnd',
                                  sine=sources.Sine(voffset=0, vampl=1, freq=1e6))
```

### Passives

```python
from silk.elements import passives

r = passives.Resistor('r1',   pos='out', neg='gnd', resistance=10e3)
c = passives.Capacitor('c1',  pos='out', neg='gnd', capacitance=100e-12)
l = passives.Inductor('l1',   pos='a',   neg='b',   inductance=1e-9)
ckt.addPassive(r)
ckt.addPassive(c)
ckt.addPassive(l)
```

### Ideal elements (ElementLibrary)

```python
from silk.elements import elements

r  = elements.resistor('r1',  p='out', n='gnd', value=10e3)
c  = elements.capacitor('c1', p='out', n='gnd', value=100e-15)
vs = elements.vsource('vdd',  p='vdd', n='gnd', dc=1.8)
ckt.add(r, c, vs)
```

### Design-space exploration

```python
from silk import ModuleVariable, VarType
from silk.optimize import Space, Variable, Optimizer

# Create variables with bounds
w_n = pdk.nmos1p8.w.variable(name='w_n', value=1.0, bounds=(0.42, 20.0))
w_p = pdk.pmos1p8.w.variable(name='w_p', value=2.0, bounds=(0.42, 20.0))
l   = pdk.nmos1p8.l.variable(name='l',   value=0.15, bounds=(0.15, 1.0))

# Pass ModuleVariable objects directly as device parameters
mn = pdk.nmos1p8('mn', d='out', g='in', s='gnd', b='gnd', w=w_n, l=l)
mp = pdk.pmos1p8('mp', d='out', g='in', s='vdd', b='vdd', w=w_p, l=l)

# Build a ParameterSpace and sample it
space = Space([w_n, w_p, l])
points = space.sample(20, method='lhs')    # Latin hypercube sampling
# also: method='random', 'sobol', 'grid'

for pt in points:
    space.apply(pt)                        # updates variable .value fields
    ckt.generateNetlist(corner='tt')       # picks up new values automatically
```

### Schema export (GUI / code-gen integration)

```python
from silk import export_schema, module_schema
import json

# Export all PDK devices as a machine-readable dict
schema = export_schema(pdk)
print(json.dumps(schema, indent=2))

# Export a single Module's interface
mod_schema = module_schema(my_module)
```

### Database / version tracking

```python
from silk.db import Database, TrackedModule, DesignStage

db = Database('designs.db')
tracked = TrackedModule(
    'PDK_skywater130', 'out', 'ldo.cir', pdkDetails=pdk,
    circuit_name='ldo_nmos', version='1.0',
    stage=DesignStage.DEV, db_path='designs.db',
)
tracked.create_circuit_version(description='Initial attempt')
# ... build circuit ...
tracked.store_results(results, corner='tt', temperature=27)
tracked.promote()                   # DEV → STABLE → POST_LAYOUT → TAPEOUT
```

### Code generation

```python
from silk.codegen import Generator

gen = Generator(pdk)
code = gen.generate(my_module)
print(code)
```

## Full API reference

See [docs/api-reference.md](docs/api-reference.md).

## Backward compatibility

Old `pynetlist.*` class names are still importable via `silk._compat`:

```python
from silk._compat import PyModule, PySignal, ParameterSpace  # deprecated
```

The `PyNetlist` class is available at `silk.circuit.netlist.PyNetlist` as a
thin subclass of `Module` with optional legacy DB tracking.

## Package layout

```
silk/
  __init__.py         top-level re-exports
  exceptions.py       CircuitError hierarchy
  _compat.py          backward-compat aliases for old Py* names

  circuit/            Module, ModuleTestbench, schema export
  pdk/                PDK_skywater130, BasePDK, load_pdk
  elements/           ElementLibrary, passives, AnalogElement
  sources/            VoltageSource, CurrentSource, waveforms
  signals/            Signal, NgspiceSignal, metrics
  simulation/         Transient, AC, DC, Noise, Corner, NgspiceRunner
  optimize/           Space, Variable, Optimizer
  db/                 Database, TrackedModule, DesignStage
  codegen/            Generator
  utils/              internal helpers
  examples/           runnable example scripts
```

## Development

```bash
pip install -e ".[dev]"
pytest tests/ -q
```
