Netlisting, schematic capture, cell libraries
Week 2, Session 1 — Fab Futures
Jennifer Volk, Alexander Wynn, Andreas Olofsson
# Setup
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from matplotlib.patches import FancyBboxPatch, Circle, Rectangle
import numpy as np
print("Setup complete.")
Setup complete.
What is a Schematic?¶
A schematic is a graphical representation of a circuit using symbols for components and lines for wires.
Schematic Capture Tools¶
| Tool | Type | Notes |
|---|---|---|
| Xschem | Open source | Integrates with ngspice |
| KiCad | Open source | Great for PCB design too |
| Cadence Virtuoso | Commercial | Industry standard for IC |
Basic Schematic Elements¶
| Element | Symbol | Purpose |
|---|---|---|
| Wire | Line | Electrical connection |
| Net label | Text | Names a wire (same name = connected) |
| Instance | Symbol | A component (resistor, transistor, etc.) |
| Port | I/O marker | Input/output of the circuit |
| Ground | GND symbol | Reference voltage (0V) |
# Simple schematic: Inverter
fig, ax = plt.subplots(figsize=(10, 8))
# VDD rail
ax.plot([5, 5], [7, 6.5], 'k-', linewidth=2)
ax.plot([4.5, 5.5], [7, 7], 'k-', linewidth=3)
ax.text(5, 7.3, 'VDD', ha='center', fontsize=11, fontweight='bold')
# PMOS (circle on gate)
ax.add_patch(Rectangle((4, 5), 2, 1.5, fill=False, edgecolor='blue', linewidth=2))
ax.text(5, 5.75, 'PMOS', ha='center', va='center', fontsize=10, color='blue')
ax.add_patch(Circle((3.8, 5.75), 0.15, fill=False, edgecolor='blue', linewidth=2))
# PMOS connections
ax.plot([5, 5], [6.5, 6.5], 'k-', linewidth=2) # drain to VDD
ax.plot([5, 5], [5, 4], 'k-', linewidth=2) # source to output
ax.plot([3.5, 3.65], [5.75, 5.75], 'k-', linewidth=2) # gate
# NMOS
ax.add_patch(Rectangle((4, 2), 2, 1.5, fill=False, edgecolor='red', linewidth=2))
ax.text(5, 2.75, 'NMOS', ha='center', va='center', fontsize=10, color='red')
# NMOS connections
ax.plot([5, 5], [3.5, 4], 'k-', linewidth=2) # drain to output
ax.plot([5, 5], [2, 1.5], 'k-', linewidth=2) # source to GND
ax.plot([3.5, 4], [2.75, 2.75], 'k-', linewidth=2) # gate
# Connect gates
ax.plot([3.5, 3.5], [2.75, 5.75], 'k-', linewidth=2)
ax.plot([1.5, 3.5], [4.25, 4.25], 'k-', linewidth=2) # input
ax.plot([3.5, 3.5], [4.25, 4.25], 'ko', markersize=6) # junction dot
# Output
ax.plot([5, 7], [4, 4], 'k-', linewidth=2)
ax.plot([5, 5], [4, 4], 'ko', markersize=6) # junction dot
# GND symbol
ax.plot([5, 5], [1.5, 1.2], 'k-', linewidth=2)
ax.plot([4.5, 5.5], [1.2, 1.2], 'k-', linewidth=2)
ax.plot([4.65, 5.35], [1.0, 1.0], 'k-', linewidth=2)
ax.plot([4.8, 5.2], [0.8, 0.8], 'k-', linewidth=2)
ax.text(5, 0.4, 'GND', ha='center', fontsize=11)
# Labels
ax.text(1.2, 4.25, 'IN', fontsize=12, fontweight='bold', va='center')
ax.text(7.3, 4, 'OUT', fontsize=12, fontweight='bold', va='center')
# Input/output markers
ax.plot([1.5], [4.25], 'g>', markersize=12)
ax.plot([7], [4], 'r>', markersize=12)
ax.set_xlim(0, 9)
ax.set_ylim(0, 8)
ax.set_aspect('equal')
ax.set_title('CMOS Inverter Schematic', fontsize=14, fontweight='bold')
ax.axis('off')
plt.tight_layout()
plt.show()
What is SPICE?¶
SPICE (Simulation Program with Integrated Circuit Emphasis) is the industry standard for analog circuit simulation.
SPICE Netlist Format¶
* Inverter circuit
.include "sky130.lib"
* Power supply
Vdd vdd gnd 1.8
* Input signal (rise/fall times realistic for 130nm)
Vin in gnd PULSE(0 1.8 0 100p 100p 1n 2n)
* PMOS: drain gate source body
Mp out in vdd vdd sky130_fd_pr__pfet_01v8 W=1u L=150n
* NMOS: drain gate source body
Mn out in gnd gnd sky130_fd_pr__nfet_01v8 W=0.5u L=150n
* Simulation commands
.tran 10p 10n
.end
Netlist Elements¶
| First Letter | Component |
|---|---|
| R | Resistor |
| C | Capacitor |
| L | Inductor |
| V | Voltage source |
| I | Current source |
| M | MOSFET |
| X | Subcircuit instance |
Hands-On: Simulating an Inverter¶
Let's walk through a complete simulation from netlist to waveform.
Step 1: Create the netlist file
Save this as inverter.spice:
* CMOS Inverter Simulation
* For Fab Futures - Week 2
* Include the Sky130 device models
.lib "sky130A/libs.tech/ngspice/sky130.lib.spice" tt
* Power supply: 1.8V
Vdd vdd gnd 1.8
* Input: pulse from 0V to 1.8V
* PULSE(initial final delay rise fall width period)
Vin in gnd PULSE(0 1.8 1n 100p 100p 2n 4n)
* PMOS transistor (W=1u, L=150n)
* Format: Mname drain gate source body model W=... L=...
Xp out in vdd vdd sky130_fd_pr__pfet_01v8 W=1u L=150n
* NMOS transistor (W=0.5u, L=150n)
Xn out in gnd gnd sky130_fd_pr__nfet_01v8 W=0.5u L=150n
* Output load capacitor (typical gate load)
Cload out gnd 10f
* Simulation: transient analysis, 10ps step, 20ns duration
.tran 10p 20n
* Save node voltages for plotting
.save v(in) v(out)
* Control block for ngspice
.control
run
plot v(in) v(out)
meas tran tpd_hl TRIG v(in) VAL=0.9 RISE=1 TARG v(out) VAL=0.9 FALL=1
meas tran tpd_lh TRIG v(in) VAL=0.9 FALL=1 TARG v(out) VAL=0.9 RISE=1
.endc
.end
Step 2: Run the simulation
# Run ngspice in batch mode
ngspice -b inverter.spice
# Or run interactively
ngspice inverter.spice
Step 3: Understand the output
Successful simulation output looks like:
Circuit: * cmos inverter simulation
Doing analysis at TEMP = 27.000000 and target temp = 27.000000
Initial Transient Solution
--------------------------
Node Voltage
---- -------
vdd 1.8
in 0
out 1.79998
...
tpd_hl = 2.34567e-11 targ= 3.234e-09 trig= 3.000e-09
tpd_lh = 3.12345e-11 targ= 5.312e-09 trig= 5.000e-09
The .meas statements give you propagation delays:
- tpd_hl: High-to-low delay (~23 ps in this example)
- tpd_lh: Low-to-high delay (~31 ps)
Common ngspice Error Messages¶
| Error | Meaning | Fix |
|---|---|---|
model xxx not found |
Missing PDK library | Check .lib path |
singular matrix |
Floating node or short | Check connections |
timestep too small |
Convergence failure | Try .option reltol=1e-3 |
node xxx is floating |
Unconnected node | Add connection or resistor |
out of region |
Transistor bias issue | Check power supplies |
Interactive Commands¶
When running ngspice interactively:
ngspice 1 -> source inverter.spice # Load the netlist
ngspice 1 -> run # Run simulation
ngspice 1 -> plot v(in) v(out) # Plot waveforms
ngspice 1 -> print v(out) # Print values
ngspice 1 -> wrdata output.csv v(out) # Export data
ngspice 1 -> quit # Exit
Saving Results¶
Export to CSV for external plotting:
.control
run
wrdata results.csv v(in) v(out)
.endc
Then in Python:
import numpy as np
import matplotlib.pyplot as plt
data = np.loadtxt('results.csv')
time = data[:, 0]
v_out = data[:, 2] # Column 0=time, 1=v(in), 2=v(out)
plt.plot(time * 1e9, v_out)
plt.xlabel('Time (ns)')
plt.ylabel('Vout (V)')
Common SPICE Analyses¶
| Analysis | Command | Purpose |
|---|---|---|
| DC | .dc |
Sweep DC voltage/current, find operating point |
| AC | .ac |
Frequency response (gain, phase) |
| Transient | .tran |
Time-domain response |
| Operating Point | .op |
DC node voltages and currents |
# Demonstrate different analysis types
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# DC Sweep (inverter transfer curve)
ax = axes[0, 0]
vin = np.linspace(0, 1.8, 100)
vout = 1.8 / (1 + np.exp(10 * (vin - 0.9)))
ax.plot(vin, vout, 'b-', linewidth=2)
ax.set_xlabel('Vin (V)')
ax.set_ylabel('Vout (V)')
ax.set_title('.dc Analysis: Inverter Transfer Curve', fontsize=11)
ax.grid(True, alpha=0.3)
ax.set_xlim(0, 1.8)
ax.set_ylim(0, 1.8)
# Transient (pulse response)
ax = axes[0, 1]
t = np.linspace(0, 10, 1000)
vin = 0.9 + 0.9 * np.sign(np.sin(2 * np.pi * t / 4))
# Simulate RC delay
vout = np.zeros_like(t)
tau = 0.15
for i in range(1, len(t)):
target = 0 if vin[i] > 0.9 else 1.8
dt = t[i] - t[i-1]
vout[i] = vout[i-1] + (target - vout[i-1]) * (1 - np.exp(-dt/tau))
ax.plot(t, vin, 'b-', linewidth=2, label='Vin')
ax.plot(t, vout, 'r-', linewidth=2, label='Vout')
ax.set_xlabel('Time (ns)')
ax.set_ylabel('Voltage (V)')
ax.set_title('.tran Analysis: Pulse Response', fontsize=11)
ax.legend()
ax.grid(True, alpha=0.3)
# AC Analysis (Bode plot)
ax = axes[1, 0]
freq = np.logspace(3, 10, 100) # 1kHz to 10GHz
# Simple single-pole response
f_3db = 1e8 # 100 MHz bandwidth
gain_db = 20 - 20 * np.log10(np.sqrt(1 + (freq/f_3db)**2))
ax.semilogx(freq, gain_db, 'b-', linewidth=2)
ax.axhline(y=17, color='r', linestyle='--', alpha=0.5, label='-3dB point')
ax.axvline(x=f_3db, color='r', linestyle='--', alpha=0.5)
ax.set_xlabel('Frequency (Hz)')
ax.set_ylabel('Gain (dB)')
ax.set_title('.ac Analysis: Frequency Response', fontsize=11)
ax.grid(True, alpha=0.3, which='both')
ax.set_ylim(-20, 25)
# Operating Point
ax = axes[1, 1]
ax.text(0.5, 0.8, 'Operating Point (.op)', ha='center', fontsize=14, fontweight='bold',
transform=ax.transAxes)
op_text = """
Node Voltages:
V(vdd) = 1.800 V
V(in) = 0.900 V
V(out) = 0.892 V
Device Currents:
I(Mp) = -15.2 µA
I(Mn) = 15.2 µA
"""
ax.text(0.5, 0.5, op_text, ha='center', va='center', fontsize=11,
family='monospace', transform=ax.transAxes,
bbox=dict(boxstyle='round', facecolor='lightyellow', edgecolor='gray'))
ax.axis('off')
plt.tight_layout()
plt.show()
PDK Device Libraries¶
A PDK provides SPICE models for all available devices:
SkyWater 130nm examples:
| Device | SPICE Model Name | Description |
|---|---|---|
| 1.8V NFET | sky130_fd_pr__nfet_01v8 |
Standard NMOS |
| 1.8V PFET | sky130_fd_pr__pfet_01v8 |
Standard PMOS |
| 3.3V NFET | sky130_fd_pr__nfet_03v3_nvt |
I/O transistor |
| Poly resistor | sky130_fd_pr__res_high_po |
High-R poly |
| MIM capacitor | sky130_fd_pr__cap_mim_m3_1 |
Metal capacitor |
Corner Models¶
Manufacturing varies! We simulate at multiple corners:
| Corner | NMOS | PMOS | Meaning |
|---|---|---|---|
| TT | Typical | Typical | Nominal process |
| FF | Fast | Fast | Best-case speed |
| SS | Slow | Slow | Worst-case speed |
| SF | Slow | Fast | Skewed |
| FS | Fast | Slow | Skewed |
Corner Simulation Example¶
To run simulations at multiple corners, modify your netlist:
* Multi-corner simulation
* Run the same circuit at TT, FF, and SS corners
.param corner_select = 0
.if (corner_select == 0)
.lib "sky130A/libs.tech/ngspice/sky130.lib.spice" tt
.elseif (corner_select == 1)
.lib "sky130A/libs.tech/ngspice/sky130.lib.spice" ff
.elseif (corner_select == 2)
.lib "sky130A/libs.tech/ngspice/sky130.lib.spice" ss
.endif
* ... rest of your circuit ...
Or run multiple simulations with a script:
#!/bin/bash
# run_corners.sh - Simulate at all process corners
for corner in tt ff ss sf fs; do
echo "=== Simulating corner: $corner ==="
sed "s/\.lib.*sky130.lib.spice.*/\.lib \"sky130A\/libs.tech\/ngspice\/sky130.lib.spice\" $corner/" \
inverter.spice > inverter_${corner}.spice
ngspice -b inverter_${corner}.spice > results_${corner}.log 2>&1
grep "tpd" results_${corner}.log
done
Expected corner behavior:
| Corner | Delay | Power | Notes |
|---|---|---|---|
| TT | Nominal | Nominal | Design target |
| FF | Fastest | Highest | Best-case timing, worst-case power |
| SS | Slowest | Lowest | Worst-case timing |
| SF/FS | Mixed | Mixed | Check for skew-related issues |
Rule: Your design must meet timing at SS corner (slow-slow) and not exceed power budget at FF corner (fast-fast).
When to Use Behavioral Models¶
Sometimes you don't need transistor-level detail:
- Early design exploration - get quick answers
- Mixed-signal simulation - analog blocks with digital
- Complex functions - ADCs, PLLs, etc.
Verilog-A Example¶
// Ideal voltage-controlled switch
module vcswitch(p, n, ctrl);
inout p, n;
input ctrl;
electrical p, n, ctrl;
parameter real ron = 1; // On resistance
parameter real roff = 1e9; // Off resistance
parameter real vth = 0.5; // Threshold
analog begin
if (V(ctrl) > vth)
I(p, n) <+ V(p, n) / ron;
else
I(p, n) <+ V(p, n) / roff;
end
endmodule
Common Issues and Solutions¶
| Problem | Cause | Solution |
|---|---|---|
| Convergence failure | Bad initial conditions | Add .nodeset or .ic |
| Slow simulation | Too many points | Increase step size |
| Oscillation | Positive feedback | Check connections |
| Wrong DC point | Floating nodes | Add weak pullup/pulldown |
Simulation Checklist¶
- Include correct PDK library
- Set appropriate simulation time/frequency range
- Use realistic input waveforms
- Add load capacitance on outputs
- Check multiple process corners
- Verify power consumption
Summary¶
- Schematic capture is the first step in circuit design
- SPICE simulates circuit behavior with detailed models
- Analysis types: DC, AC, Transient, Operating Point
- PDK libraries provide calibrated device models
- Corner simulation ensures robustness across process variation
Homework¶
- Reuse and.sp netlist, which has a 2-input AND gate to make a 2-input NAND gate (e.g., remove the output inverter) and change the models to refer to the PDK models
- Simulate it in SPICE, verify truth table (it will look something like the right table), and measure propagation delays (low-to-high and high-to-low)
- Write an initial analog block that you can use in your chip project (e.g., an adder, counter, etc.)
- Here’s a good starting point: https://analoghub.ie/category/verilogModels/article/counter