SPICE + GDS to pic.yml Conversion¶
Extends the basic SPICE converter by reading an existing GDS file to extract
real layout coordinates. This produces a pic.yml that reflects the actual
chip placement rather than an auto-generated grid.
Component positions are matched by extracting layer geometry (e.g. the
PolyResdrawing layer for rppd resistors) and correlating dimensions
against SPICE parameters.
Requirements:
- IHP PDK:
uv pip install ihp-gdsfactory
import sys
sys.path.insert(0, '.')
from pathlib import Path
from spice_and_gds_to_pic_yml import convert_file
Inputs¶
We need both the SPICE netlist and the corresponding GDS layout file.
spice_file = Path('TO_Apr2025/Mixer5GHz.spice')
gds_file = Path('TO_Apr2025/FMD_QNC_16_Mixer5GHz.gds')
print('SPICE:', spice_file.exists())
print('GDS: ', gds_file.exists())
SPICE: True
GDS: True
Convert with real GDS placements¶
Passing gds_file causes the converter to extract component bounding boxes
from each layer and match them against SPICE parameter dimensions. Matched
components use the GDS (x, y) coordinates; unmatched components fall back
to a grid layout.
output_file = Path('/tmp/Mixer5GHz_gds.pic.yml')
convert_file(spice_file, output_file, gds_file=gds_file)
Parsing SPICE netlist: TO_Apr2025/Mixer5GHz.spice
Found 30 instances
Extracting rppd placements from GDS: TO_Apr2025/FMD_QNC_16_Mixer5GHz.gds
Found PolyRes layer by number: 128/0 (index 51)
Extracted 6 rppd instances
Extracting inductor placements from GDS: TO_Apr2025/FMD_QNC_16_Mixer5GHz.gds
[32m2026-07-06 16:04:10.764[0m | [31m[1mERROR [0m | [36mkfactory.kcell[0m:[36mname[0m:[36m691[0m - [31m[1mName conflict in kfactory.kcell::name at line 691
Renaming Unnamed_21 (cell_index=21) to Mixer5GHz would cause it to be named the same as:
- Mixer5GHz (cell_index=0), function_name=None, basename=None[0m
Found inductor layer by number: 1/23 (index 72)
Extracted 4 inductor instances
Extracting NMOS placements from GDS: TO_Apr2025/FMD_QNC_16_Mixer5GHz.gds
[32m2026-07-06 16:04:11.066[0m | [31m[1mERROR [0m | [36mkfactory.kcell[0m:[36mname[0m:[36m691[0m - [31m[1mName conflict in kfactory.kcell::name at line 691
Renaming Unnamed_42 (cell_index=42) to Mixer5GHz would cause it to be named the same as:
- Mixer5GHz (cell_index=0), function_name=None, basename=None
- Mixer5GHz (cell_index=21), function_name=None, basename=None[0m
Extracted 16 NMOS instances
Extracting CMIM placements from GDS: TO_Apr2025/FMD_QNC_16_Mixer5GHz.gds
[32m2026-07-06 16:04:11.402[0m | [31m[1mERROR [0m | [36mkfactory.kcell[0m:[36mname[0m:[36m691[0m - [31m[1mName conflict in kfactory.kcell::name at line 691
Renaming Unnamed_63 (cell_index=63) to Mixer5GHz would cause it to be named the same as:
- Mixer5GHz (cell_index=0), function_name=None, basename=None
- Mixer5GHz (cell_index=21), function_name=None, basename=None
- Mixer5GHz (cell_index=42), function_name=None, basename=None[0m
Found MIM layer by number: 36/0 (index 11)
Extracted 4 CMIM instances
=== SPICE rppd instances (w/l converted to µm) ===
XR1: dx=4.4000, dy=1.5000
XR2: dx=4.4000, dy=1.5000
XR3: dx=4.3500, dy=1.5000
XR4: dx=4.3500, dy=1.5000
XRL1: dx=4.5000, dy=3.2000
XRL2: dx=4.5000, dy=3.2000
=== GDS rppd instances (dx/dy from PolyRes layer) ===
rppd$1: dx=4.4000, dy=1.5000 @ (631.56, 1066.89)
rppd$2: dx=4.5000, dy=3.2000 @ (655.43, 1047.70)
rppd$2: dx=4.5000, dy=3.2000 @ (678.50, 1047.70)
rppd$1: dx=4.4000, dy=1.5000 @ (622.85, 1066.89)
rppd: dx=4.3500, dy=1.5000 @ (622.85, 280.99)
rppd: dx=4.3500, dy=1.5000 @ (631.50, 280.99)
=== Matching ===
dx=4.35, dy=1.5: 2 SPICE → 2 GDS
XR3 → (622.85, 280.99)
XR4 → (631.50, 280.99)
dx=4.4, dy=1.5: 2 SPICE → 2 GDS
XR1 → (631.56, 1066.89)
XR2 → (622.85, 1066.89)
dx=4.5, dy=3.2: 2 SPICE → 2 GDS
XRL1 → (655.43, 1047.70)
XRL2 → (678.50, 1047.70)
✓ Matched 6 of 6 rppd instances
=== Matching Inductors ===
4 SPICE inductors → 4 GDS inductors
L1 → (846.02, 1088.35), diameter=342.04
L2 → (403.98, 1088.35), diameter=342.04
L3 → (403.98, 302.44), diameter=342.04
L4 → (846.02, 302.44), diameter=342.04
✓ Matched 4 of 4 inductors
=== Matching NMOS ===
16 SPICE NMOS → 16 GDS NMOS
XM6 (nf=10) → (648.81, 1025.98), width=5.40
XM8 (nf=10) → (657.61, 1025.98), width=5.40
XM7 (nf=10) → (666.41, 1025.98), width=5.40
XM5 (nf=10) → (675.21, 1025.98), width=5.40
XM4 (nf=15) → (629.46, 1012.88), width=7.95
XM3 (nf=15) → (651.38, 1012.88), width=7.95
XM11 (nf=15) → (668.98, 1012.88), width=7.95
XM12 (nf=15) → (611.86, 1012.88), width=7.95
XM13 (nf=15) → (611.86, 226.97), width=7.95
XM14 (nf=15) → (629.46, 226.97), width=7.95
XM2 (nf=20) → (618.93, 998.26), width=10.50
XM1 (nf=20) → (618.93, 212.36), width=10.50
XM9 (nf=20) → (631.43, 998.26), width=10.50
XM10 (nf=20) → (645.93, 998.26), width=10.50
XM15 (nf=20) → (658.43, 998.26), width=10.50
XM16 (nf=20) → (631.43, 212.36), width=10.50
✓ Matched 16 of 16 NMOS
=== SPICE cmim instances (w/l converted to µm) ===
XC1: width=11.7450, length=9.4450
XC2: width=11.7450, length=9.4450
XC3: width=19.1000, length=10.7000
XC4: width=19.1000, length=10.7000
=== GDS cmim instances (width/length from MIM layer) ===
cmim$1$3: width=11.7450, length=9.4450 @ (637.82, 1061.80)
cmim$1$3: width=11.7450, length=9.4450 @ (600.45, 1061.80)
cmim$4: width=19.1000, length=10.7000 @ (591.60, 275.88)
cmim$4: width=19.1000, length=10.7000 @ (639.31, 275.88)
=== Matching ===
width=11.745, length=9.445: 2 SPICE → 2 GDS
XC1 → (637.82, 1061.80)
XC2 → (600.45, 1061.80)
width=19.1, length=10.7: 2 SPICE → 2 GDS
XC3 → (591.60, 275.88)
XC4 → (639.31, 275.88)
✓ Matched 4 of 4 cmim instances
Converting to pic.yml format...
Writing to /tmp/Mixer5GHz_gds.pic.yml
✓ Conversion complete!
Summary:
- 30 components
- 30 placements
- Used GDS coordinates from: FMD_QNC_16_Mixer5GHz.gds
name: circuit
instances:
XM6:
component: nmos
settings:
width: 60.0
length: 0.13
nf: 10
model: sg13_lv_nmos
XRL2:
component: rppd
settings:
dx: 4.5
dy: 3.2
model: rppd
XRL1:
component: rppd
settings:
dx: 4.5
dy: 3.2
model: rppd
XM8:
component: nmos
settings:
width: 60.0
length: 0.13
nf: 10
model: sg13_lv_nmos
XM7:
component: nmos
settings:
width: 60.0
length: 0.13
nf: 10
model: sg13_lv_nmos
XM5:
component: nmos
settings:
width: 60.0
length: 0.13
nf: 10
model: sg13_lv_nmos
XM4:
component: nmos
settings:
width: 90.0
length: 0.13
nf: 15
model: sg13_lv_nmos
XM3:
component: nmos
settings:
width: 90.0
length: 0.13
nf: 15
model: sg13_lv_nmos
XM2:
component: nmos
settings:
width: 120.0
length: 0.13
nf: 20
model: sg13_lv_nmos
XM1:
component: nmos
settings:
width: 120.0
length: 0.13
nf: 20
model: sg13_lv_nmos
XM11:
component: nmos
settings:
width: 90.0
length: 0.13
nf: 15
model: sg13_lv_nmos
XM12:
component: nmos
settings:
width: 90.0
length: 0.13
nf: 15
model: sg13_lv_nmos
XM9:
component: nmos
settings:
width: 120.0
length: 0.13
nf: 20
model: sg13_lv_nmos
XM10:
component: nmos
settings:
width: 120.0
length: 0.13
nf: 20
model: sg13_lv_nmos
XC1:
component: cmim
settings:
width: 11.745
length: 9.445
XR1:
component: rppd
settings:
dx: 4.4
dy: 1.5
model: rppd
XC2:
component: cmim
settings:
width: 11.745
length: 9.445
XR2:
component: rppd
settings:
dx: 4.4
dy: 1.5
model: rppd
XM13:
component: nmos
settings:
width: 90.0
length: 0.13
nf: 15
model: sg13_lv_nmos
XM14:
component: nmos
settings:
width: 90.0
length: 0.13
nf: 15
model: sg13_lv_nmos
XM15:
component: nmos
settings:
width: 120.0
length: 0.13
nf: 20
model: sg13_lv_nmos
XM16:
component: nmos
settings:
width: 120.0
length: 0.13
nf: 20
model: sg13_lv_nmos
XC3:
component: cmim
settings:
width: 19.1
length: 10.7
XR3:
component: rppd
settings:
dx: 4.35
dy: 1.5
model: rppd
XC4:
component: cmim
settings:
width: 19.1
length: 10.7
XR4:
component: rppd
settings:
dx: 4.35
dy: 1.5
model: rppd
L3:
component: inductor2
settings:
inductance: 2.006e-09
L1:
component: inductor2
settings:
inductance: 2.006e-09
L2:
component: inductor2
settings:
inductance: 2.006e-09
L4:
component: inductor2
settings:
inductance: 2.006e-09
placements:
XM6:
x: 648.8100000000001
y: 1025.98
XRL2:
x: 678.495
y: 1047.705
rotation: 180.0
XRL1:
x: 655.4250000000001
y: 1047.705
rotation: 180.0
XM8:
x: 657.61
y: 1025.98
XM7:
x: 666.41
y: 1025.98
XM5:
x: 675.21
y: 1025.98
XM4:
x: 629.46
y: 1012.88
XM3:
x: 651.38
y: 1012.88
XM2:
x: 618.9300000000001
y: 998.265
XM1:
x: 618.9300000000001
y: 212.35500000000002
XM11:
x: 668.98
y: 1012.88
XM12:
x: 611.86
y: 1012.88
XM9:
x: 631.4300000000001
y: 998.265
XM10:
x: 645.9300000000001
y: 998.265
XC1:
x: 637.815
y: 1061.795
XR1:
x: 631.5550000000001
y: 1066.895
rotation: 180.0
XC2:
x: 600.445
y: 1061.795
XR2:
x: 622.845
y: 1066.895
rotation: 180.0
XM13:
x: 611.86
y: 226.97
XM14:
x: 629.46
y: 226.97
XM15:
x: 658.4300000000001
y: 998.265
XM16:
x: 631.4300000000001
y: 212.35500000000002
XC3:
x: 591.595
y: 275.885
XR3:
x: 622.845
y: 280.985
rotation: 180.0
XC4:
x: 639.3050000000001
y: 275.885
XR4:
x: 631.505
y: 280.985
rotation: 180.0
L3:
x: 403.98
y: 302.435
rotation: 180.0
mirror: true
L1:
x: 846.02
y: 1088.345
L2:
x: 403.98
y: 1088.345
rotation: 180.0
mirror: true
L4:
x: 846.02
y: 302.435
Next steps¶
The generated pic.yml preserves real GDS coordinates and can be loaded with
gf.read.from_yaml() once all component parameters are within the PDK's accepted
ranges. This file is well-suited as a base for schematic-driven layout re-generation.