Session 3: Schematic Design & Simulation¶
On Monday (23/02/2026), Jennifer Volk gave a class focused on schematic design and simulation as the bridge between circuit ideas and verifiable behavior. The session started with schematic capture basics—what a schematic is, the core elements (wires, net labels, instances, ports, ground), and common tools such as Xschem, KiCad, and Cadence Virtuoso. It then introduced SPICE simulation fundamentals, including what SPICE is, how a netlist is structured, and the role of standard netlist elements and sources in describing a circuit. A CMOS inverter example was used to connect the schematic view with a realistic SPICE description (including Sky130 device models and transient simulation commands).
The second half of the class moved into a practical simulation workflow with ngspice: running an inverter example, using interactive commands, saving results, and understanding common error messages. It also covered the main analysis types (.dc, .ac, .tran, .op), introduced PDK device libraries and corner models (with Sky130 examples), and explained when behavioral modeling (including a Verilog-A example) is useful to simplify or abstract parts of a circuit. The class closed with simulation best practices, including using the correct PDK libraries, realistic inputs, output loading, checking multiple process corners, and verifying power consumption—basically a solid checklist for building reliable simulation habits from the start.
In addition to studying these new concepts more deeply, we are expected to complete the following tasks:
- 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.)
Build a NAND gate¶
The previous task was to convert the AND project that Jennifer had shown us into an OR gate. Now we need to convert it into a NAND gate by removing the final inverter.

I edited the original and.sp file so that it generates a NAND gate. I also added titles to each plot window so it is easier to identify what each one shows.

* NAND gate ngspice
* NGSPICE transistor models
. model mosn NMOS Level=49 version=3.3.0 tox=10n fich=1e17 nsub=5e16
. model mosp PMOS level=49 version=3.3.0 tox=10n
* 1 V power supply
vsup VDD 0 1
* AND pull-up network -- two pmos in parallel
Mp1 nOUT A VDD VDD mosp L=0.35u W=2u
Mp2 nOUT B VDD VDD mosp L=0.35u W=2u
* Pull-down network -- two nmos in series
Mnl nOUT A npd 0 mosn L=0.35u W=2u
Mn2 npd B 0 0 mosn L=0.35u W=2u
* Input voltage source, ramps up to VDD then back down
vin1 A 0 PWL(0 0 2mS 0 2.001mS 1V 3mS 1V 3.001mS 0)
vin2 B 0 PWL(0 0 1mS 0 1.001mS 1V 2.5mS 1V 2.5001mS 0)
. control
* transient simulation using vin sweep
tran 100n 4m
28 * plot vout against vin
plot v (A) title "A"
plot v (B) title "B"
plot v (nOUT) title "NAND output"
plot v (A) v (B) v (nOUT) +0.5 title "A & B & NAND output"
. endc

By adding (nOUT) + 0.5, the plot displays it with a +0.5 V offset so the NAND waveform does not overlap with the A and B plots.

… and update the model references to use the PDK models.¶
To refer to the PDK models, I replaced the generic .model NMOS/PMOS definitions with the transistor models provided by the PDK library, and updated each MOS instance to use the corresponding Sky130 NMOS/PMOS device names.
The library is in the folder /foss/pdks/sky130A/.
I need call with .lib /foss/pdks/sky130A/libs.tech/ngspice/sky130.lib.spice tt and remove the NGSPICE transistor models of .model mosn and .model mosp lines
nmos is now sky130_fd_pr__pfet_01v8 and mosp is sky130_fd_pr__nfet_01v8
The new code is:
* NAND gate ngspice (using PDK models)
* PDK model library (example: Sky130, typical corner)
.lib /foss/pdks/sky130A/libs.tech/ngspice/sky130.lib.spice tt
* 1 V power supply
vsup VDD 0 1
* NAND pull-up network -- two pmos in parallel
Mp1 nOUT A VDD VDD sky130_fd_pr__pfet_01v8 L=0.35u W=2u
Mp2 nOUT B VDD VDD sky130_fd_pr__pfet_01v8 L=0.35u W=2u
* Pull-down network -- two nmos in series
Mn1 nOUT A npd 0 sky130_fd_pr__nfet_01v8 L=0.35u W=2u
Mn2 npd B 0 0 sky130_fd_pr__nfet_01v8 L=0.35u W=2u
* Input voltage source, ramps up to VDD then back down
vin1 A 0 PWL(0 0 2mS 0 2.001mS 1V 3mS 1V 3.001mS 0)
vin2 B 0 PWL(0 0 1mS 0 1.001mS 1V 2.5mS 1V 2.5001mS 0)
.control
* transient simulation using vin sweep
tran 100n 4m
plot v(A) title "A"
plot v(B) title "B"
plot v(nOUT) title "NAND output"
plot v(A) v(B) (v(nOUT)+0.5) title "A, B and NAND output"
.endc
After running ngspice, I got an error. I need to check whether the issue comes from the PDK library path, the selected corner, or the transistor model names used in the netlist.

While debugging the ngspice error, I checked the SkyWater SKY130 PDK documentation and noticed that the device/cell name shown in the library documentation is not always the same identifier used as the SPICE model name in simulation. Once I verified the correct SPICE model names in the PDK documentation, the netlist references made much more sense.
* NAND pull-up network -- two pmos in parallel
Mp1 nOUT A VDD VDD sky130_fd_pr__pfet_01v8_hvt L=0.35u W=2u
Mp2 nOUT B VDD VDD sky130_fd_pr__pfet_01v8_hvt L=0.35u W=2u

Even so, I still couldn’t get it to work. Thanks to the help of my classmates Philippe and Þórarinn, I finally got a version of the code that worked. The issue was not only how the libraries were referenced—there were several other details as well, such as changing the transistor instance names from Mp1/Mp2 to Xp1/Xp2 (and similarly Mn1/Mn2 to Xn1/Xn2), as well as fixing upper/lowercase usage where needed. In addition, we had to include a capacitor for the circuit to behave correctly.

* NAND gate ngspice (using PDK models)
* Include PDK model library the Sky130 device models
.lib "/foss/pdks/sky130A/libs.tech/ngspice/sky130.lib.spice" tt
* 1.8V power supply
Vdd VDD 0 1.8
* AND pull-up network -- two PMOS in parallel
Xp1 nOUT A VDD VDD sky130_fd_pr__pfet_01v8_hvt l=0.35 w=0.99
Xp2 nOUT B VDD VDD sky130_fd_pr__pfet_01v8_hvt l=0.35 w=0.99
* Pull-down network -- two NMOS in series
Xn1 nOUT B npd 0 sky130_fd_pr__nfet_01v8 l=0.35 w=0.495
Xn2 npd A 0 0 sky130_fd_pr__nfet_01v8 l=0.35 w=0.495
* Inputs voltage source, ramps to VDD then back down
vin1 A 0 PWL(0 0 1mS 0 1.001mS 1.8 2.5mS 1.8 2.501mS 0)
vin2 B 0 PWL(0 0 1.5mS 0 1.501mS 1.8 3.5mS 1.8 3.501mS 0)
* Load
Cload nOUT 0 10f
Rleak nOUT 0 1G
.control
tran 100n 4m uic
plot v(A) v(B)+2 v(nOUT)+4 title "A & B & NAND output"
.endc
.end


Simulate the circuit in SPICE, verify its truth table, and measure its propagation delays.¶
The circuit was simulated in ngspice using a transient analysis (tran 100n 4m) with two PWL input voltage sources (vin1 and vin2) that drive inputs A and B through different timing intervals. This allows the simulation to sweep the logic combinations and observe the NAND gate behavior at the output (nOUT).
The truth table was verified by checking the output waveform V(nOUT) in the stable regions of the transient plot:
- the output stays high unless both inputs are high,
- and it goes low only when A = 1 and B = 1.
This matches the expected behavior of a NAND gate.
The propagation delays were measured in ngspice using .measure commands at the 50% threshold of VDD (0.9 V, since VDD = 1.8 V). Two delays are measured:
- tPHL: output transition from high to low
- tPLH: output transition from low to high
Because this is a 2-input gate, the delay depends on which input is switching and whether the other input is in the correct state to sensitize the output transition. For that reason, delays are measured for both A and B.
* NAND gate ngspice (using PDK models)
* Include PDK model library the Sky130 device models
.lib "/foss/pdks/sky130A/libs.tech/ngspice/sky130.lib.spice" tt
* 1.8 V power supply
Vdd VDD 0 1.8
* AND pull-up network -- two pmos in parallel
Xp1 nOUT A VDD VDD sky130_fd_pr__pfet_01v8_hvt l=0.35 w=0.99
Xp2 nOUT B VDD VDD sky130_fd_pr__pfet_01v8_hvt l=0.35 w=0.99
* Pull-down network -- two nmos in series
Xn1 nOUT B npd 0 sky130_fd_pr__nfet_01v8 l=0.35 w=0.495
Xn2 npd A 0 0 sky130_fd_pr__nfet_01v8 l=0.35 w=0.495
* Input voltage source, ramps up to VDD then back down
vin1 A 0 PWL(0 0 1mS 0 1.001mS 1.8 2.5mS 1.8 2.501mS 0)
vin2 B 0 PWL(0 0 1.5mS 0 1.501mS 1.8 3.5mS 1.8 3.501mS 0)
* Load
Cload nOUT 0 10f
Rleak nOUT 0 1G
.control
* transient simulation using vin sweep
tran 100n 4m uic
* plot NAND Output
plot v(A) v(B)+2 v(nOUT)+4 title "A & B & NAND output"
* Propagation delay measurements at 50% of VDD (0.9 V)
* NAND gate: output falls when a valid input rises, and rises when that input falls
meas tran tPHL_A trig v(A) val=0.9 rise=1 targ v(nOUT) val=0.9 fall=1
meas tran tPHL_A trig v(A) val=0.9 fall=1 targ v(nOUT) val=0.9 rise=1
meas tran tPHL_B trig v(B) val=0.9 rise=1 targ v(nOUT) val=0.9 fall=1
meas tran tPHL_B trig v(B) val=0.9 fall=1 targ v(nOUT) val=0.9 fall=1
.endc
.end


The circuit was simulated in SPICE using transient analysis with pulse-driven inputs to exercise all input combinations. The output waveform was checked against the expected logic behavior, and the simulated truth table matches the target function. Propagation delays were measured at the 50% voltage level (0.9 V for VDD = 1.8 V), obtaining both low-to-high (tPLH) and high-to-low (tPHL) delays.
A quick pause!¶
I was working on the course documentation and assignments using the Docker environment and tools provided by the academy coordinators, but I kept running into little friction points: typing certain characters, moving files back and forth between VNC and my Mac (WeTransfer), and generally fighting the workflow instead of the homework.
That also meant that some of my code ended up being wrong—since I sometimes had to type it twice in two different places—and a few things didn’t work as expected. Thanks to my classmate Þórarinn for the heads-up!
So I decided to install the Docker setup locally (how?).

Now I can edit the same project files both on my Mac and inside the Docker (via VNC), with everything fully synced between my documentation and my tasks. Best decision I’ve made this week 😉

Now it’s great ;) I’ll continue ;)
Develop an initial analog building block for use in your chip project.¶
Ring Modulator¶
For this practice, I chose to design an initial analog block based on a ring modulator concept, since it is directly relevant to my modular synthesizer work.
As a first step, I implemented a simple behavioral version in ngspice that multiplies a carrier and a modulator signal. This allows me to validate the signal-processing concept (generation of sidebands and new timbral content) before moving to a more hardware-realistic implementation.
* Simple ring modulator (behavioral multiplier) - ngspice
* Carrier signal (audio)
Vcar car 0 SIN(0 1 1k)
* Modulator signal (audio/LFO)
Vmod mod 0 SIN(0 1 200)
* Behavioral voltage source: multiplication (ring modulation concept)
* Scale factor 0.5 to keep amplitude reasonable
Bmix out 0 V = 0.5 * v(car) * v(mod)
* Load
Rload out 0 10k
.control
tran 10u 20m
plot v(car) title "Carrier"
plot v(mod) title "Modulator"
plot v(out) title "Ring mod output"
plot v(car) v(mod) (v(out)+2.5) title "Carrier, Modulator, Ring Mod Output"
.endc
.end

For export the result to csv I add this line to code into control wrdata RM_results.csv v(car) v(mod) v(out)

After run the simulation, generate the .csvfile.

I used the Jupyter installation on my Synology NAS to plot the .csv file using Python.
import numpy as np
import matplotlib.pyplot as plt
data = np.loadtxt('RM_results.csv')
time = data[:, 0] # Column 0=time, 1=v(car), 3=v(mod), 5=v(out)
v_car = data[:, 1]
v_mod = data[:, 3]
v_out = data[:, 5]
plt.plot(time * 1e9, v_out)
plt.plot(time * 1e9, v_car)
plt.plot(time * 1e9, v_mod)
plt.xlabel('Time (ns)')
plt.ylabel('Voltage (V)')
To build a ring modulator, the MC1496 IC can be used, and it typically follows a schematic like this.
The MC1496 is an analog balanced modulator/demodulator IC, so a realistic implementation is not possible in synthesizable digital Verilog. Instead, I used a Verilog-A behavioral model to represent its core function as an analog signal multiplier (carrier × modulator), which is suitable for simulation and concept validation.
`include "constants.vams"
`include "disciplines.vams"
module mc1496_behavioral(out, in_car, in_mod);
inout out, in_car, in_mod;
electrical out, in_car, in_mod;
// Ganancia del multiplicador
parameter real k = 0.5;
// Offset opcional de salida
parameter real vout_offset = 0.0;
analog begin
// Multiplicador balanceado ideal (ring mod / product modulator)
V(out) <+ vout_offset + k * V(in_car) * V(in_mod);
end
endmodule
After read the ngspice manual, I learned about OpenVAF, a Verilog-A compiler.
OSDI is a simulator independent interface for device models. Since release 39 ngspice contains an integrated adapter to serve this interface and communicate with the compiled shared library device models.
OpenVAF generates shared objects that can be loaded by circuit simulators at run-time. To ensure compatibility with a wide variety of simulators SemiMod has developed a simulator independent interface called OSDI (Open Source Device Interface). This interface is very flexible and allows efficient integration with a wide variety of different simulators. As a result it can support both modern harmonic balance solvers and traditional SPICE based engines.
I renamed the source file with a .va extension and ran openvaf RM_verilog.va
OpenVAF generates a .osdi file (RM_Verilog.osdi). It looks like an executable, but it doesn’t do anything on its own…
