Session 3: Schematic Design & Simulation
Assignments
- 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.)
Rewiring
This session is definitely pushing me into the growth zone. There were many new terms, tools, and methods mentioned that I want to properly understand. Before diving into the assignments, I decided to map things out to get a clearer overview.
Mental Mapping
I began by mapping the PCB world to the IC world. Because I already understand PCB processes, I wanted to identify the equivalent concepts in silicon design.
| Category | PCB World | IC World |
|---|---|---|
| Design Tool | KiCad, Altium | Xschem, Magic, OpenROAD |
| File Format | Gerber + Drill files | GDSII or OASIS |
| Fabrication Site | Board house | Semiconductor foundry |
| Material | Fiberglass (FR-4) with copper layers | Silicon wafer with doped regions and metal layers |
| Manufacturing | Mechanical drilling, copper etching, lamination | Photolithography, ion implantation, deposition, etching |
| Scale | mm to µm traces | nm transistor features |
| Iteration Time | Days to weeks | Months |
| Cost of Error | Low - redesign and re-spin | High - requires new mask set |
| Final Product | Assembled circuit board | Packaged integrated circuit |
| Role | The body - connects and distributes | The brain - computes and controls |
IC Terms
There were also several new terms that I wanted to better understand. Some were mentioned in the lecture, and others came up while researching the topic.
| Term | What It Is | Why It Matters |
|---|---|---|
| PDK (Process Design Kit) | A collection of models, rules, and files describing a semiconductor manufacturing process | Without it, your simulator doesn’t know how real transistors behave |
| sky130 | An open-source 130nm CMOS process provided by SkyWater | The specific manufacturing technology you are designing for |
| propagation delay | Time required for a signal transition to propagate through a circuit. | Limits processor speeds |
| ngspice | Open-source SPICE simulator | Used to simulate circuit behavior before fabrication |
| SPICE | Circuit simulation language and engine | Industry standard way to describe and simulate analog circuits |
| Netlist | Text description of circuit components and connections | What Ngspice actually reads to simulate your schematic. |
| Corner (TT, SS, FF) | Process variation models (Typical, Slow, Fast) | Used to test how circuits behave under manufacturing variation |
| DRC (Design Rule Check) | Check that layout obeys fabrication rules | Prevents illegal geometries before fabrication |
| LVS (Layout vs Schematic) | Verifies layout matches schematic connectivity | Ensures what you draw is what you designed |
| Tapeout | Final submission of chip design for fabrication | The point of no return, silicon gets made |
Adapting the AND gate netlist to a PDK NAND gate
On the class page I saw this line:
* Include the Sky130 device models .lib "sky130A/libs.tech/ngspice/sky130.lib.spice" tt
I assumed I neded to reference the PDK models instead of using the simple .model definitions from the previous netlist.

What is sky130.lib.spice
Seeing the path, I wanted to explore what was actually inside that directory. I navigated there and saw the file so I ran:
cat sky130.lib.spice
The file was not that large larger about 600 lines. I tried searching inside the file for _pfet, expecting to find something obvious, but nothing showed up directly.

At that point I switched strategy and used grep to search the entire ngspice directory:
grep -R "sky130_fd_pr__pfet_01v8" -n $PDK_ROOT/sky130A/libs.tech/ngspice
And yeah that returned many matches but the model was definitely there, just not in the simple, flat way I initially expected. Seams like the PDK organizes the models across multiple files and layers of abstraction and sky130.lib.spice is a index with lots of include lines.

That was enough confirmation for me.
Changing the code
I saw on the class page there was a Spice netlist format and Hands-On: Simulating an Inverter examples, that gave me the idea how to do it
Models: I basically kept the connections as they were and just removed and added the new models in the end of that line:
PFET: Conection: Mp1 nOUT A VDD VDD Model: sky130_fd_pr__pfet_01v8 W=1u L=150n
NFET: Conection: Mn1 nOUT A npd 0 Model: sky130_fd_pr__nfet_01v8 W=0.5u L=150n
Libary: In the the libary was included in two ways .lib "sky130A/libs.tech/ngspice/sky130.lib.spice" tt and .include "sky130.lib" I had also used $PDK_ROOT/sky130A/libs.tech/ngspice when I did the grep comand and desided to use this line:
.lib "$PDK_ROOT/sky130A/libs.tech/ngspice/sky130.lib.spice" tt
commented out: I commented out the old models and the inverter. I also change the plot to one line and renamed AND to D.
Troubleshooting
I then ran Ngspice and got this error immediately:
Error: Could not find library file sky130A/libs.tech/ngspice/sky130.lib.spice
ERROR, library file sky130A/libs.tech/ngspice/sky130.lib.spice not found
That one was simple, I had forgotten $PDK_ROOT/ in the path.
After fixing the path, Ngspice stopped giving errors but nothing happened, just the banner.
Solution!
At this point I had hours of troubleshooting (with breaks). The sky130 library was not working as expected. I used Google, AI, read forums. Tried every trick in the book: simplifying the code, making .log files, using grep, and so on. Then I looked at the documentation of my fellow students and found the soulution on Philippe Libioulle page
From his documentation I learned that sky130_fd_pr__pfet_01v8 was not working in my setup, but changing it to sky130_fd_pr__pfet_01v8_hvt worked.
Being tired of having nothing working for hours, I puzzled his code together and ran it and it worked!

* NAND gate ngspice with PDK / code from Philippe
* Include the Sky130 device models
.lib "/foss/pdks/sky130A/libs.tech/ngspice/sky130.lib.spice" tt
* Power supply: 1.8V
Vdd vdd gnd 1.8
* Inputs
* PULSE(initial final delay rise fall widht period)
VinA inA gnd PULSE(0 1.8 0ns 100ps 100ps 2ns 4ns)
VinB inB gnd PULSE(0 1.8 0ns 100ps 100ps 2ns 8ns)
* Two PMOS transistors in parralel for the pull-up network (W=1u, L=150n)
* Format: Mname drain gate source body model W=... L=...
Xp1 NAND inA vdd vdd sky130_fd_pr__pfet_01v8_hvt l=0.150 w=0.99
Xp2 NAND inB vdd vdd sky130_fd_pr__pfet_01v8_hvt l=0.150 w=0.99
* Two NMOS transistors in series for the pull-down network (W=0.5u, L=150n)
Xn1 NAND inA npd gnd sky130_fd_pr__nfet_01v8 l=0.150 w=0.495
Xn2 npd inB gnd gnd sky130_fd_pr__nfet_01v8 l=0.150 w=0.495
* Output load capacitor (typical gate load)
Cload NAND gnd 10f
* Simulation: transient analysis for 30ns
.tran 10p 30ns
* Save node voltages for plotting
.save v(inA) v(inB) v(NAND)
* Control block for ngspice
.control
run
plot v(inA) v(inB) v(NAND)
plot v(inA) v(NAND)
plot v(inB) v(NAND)
meas tran tpd_hl TRIG v(inA) VAL=0.9 RISE=1 TARG v(NAND) VAL=0.9 FALL=1
meas tran tpd_lh TRIG v(inA) VAL=0.9 FALL=1 TARG v(NAND) VAL=0.9 RISE=1
meas tran tpd_hl TRIG v(inB) VAL=0.9 RISE=1 TARG v(NAND) VAL=0.9 FALL=1
meas tran tpd_lh TRIG v(inB) VAL=0.9 FALL=1 TARG v(NAND) VAL=0.9 RISE=1
.endc
.end
I was very happy that this was solved and that my container was fine and I could move on.
Second attempt
After this I went back to the code and adapted it with this in mind. I took some time to run and after it was done I could see the NAND operation working. In short NAND gate only goes LOW when both inputs are HIGH.

Here is the code I ended with before and after the troubleshoot:
* AND gate ngspice to a PDK sky130 NAND gate
* Include the Sky130 device models
.lib "/foss/pdks/sky130A/libs.tech/ngspice/sky130.lib.spice" tt
* Power supply 1.8V
Vdd vdd 0 1.8
* NAND pull-up network two PMOS in parallel _hvt
Xp1 nOUT A vdd vdd sky130_fd_pr__pfet_01v8_hvt l=0.150 w=0.99
Xp2 nOUT B vdd vdd sky130_fd_pr__pfet_01v8_hvt l=0.150 w=0.99
* Pull-down network two NMOS in series
Xn1 nOUT A npd 0 sky130_fd_pr__nfet_01v8 l=0.150 w=0.495
Xn2 npd B 0 0 sky130_fd_pr__nfet_01v8 l=0.150 w=0.495
* Inputs
vin1 A 0 PWL(0 0 2mS 0 2.001mS 1.8 3mS 1.8 3.001mS 0)
vin2 B 0 PWL(0 0 1mS 0 1.001mS 1.8 2.5mS 1.8 2.5001mS 0)
* Load
Cload nOUT 0 10f
Rleak nOUT 0 1G
.control
tran 100n 4m uic
plot v(A) v(B)+2 v(nOUT)+4
.endc
.end
* AND gate ngspice changed to a PDK sky130 NAND gate // note this code does not work
** NGSPICE transistor models ### should probably change this or remove it
*.model mosn NMOS level=49 version=3.3.0 tox=10n nch=1e17 nsub=5e16
*.model mosp PMOS level=49 version=3.3.0 tox=10n nch=1e17 nsub=5e16
* Include the Sky130 device models ### I took this line from the class page
.lib "foss/pdks/sky130A/libs.tech/ngspice/sky130.lib.spice" tt
* 1 V power supply ### stays the same
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
Mp1 nOUT A VDD VDD sky130_fd_pr__pfet_01v8 W=1u L=150n
Mp2 nOUT B VDD VDD sky130_fd_pr__pfet_01v8 W=1u L=150n
** Pull-down network -- two nmos in series
*Mn1 nOUT A npd 0 mosn L=0.35u W=2u
*Mn2 npd B 0 0 mosn L=0.35u W=2u
Mn1 nOUT A npd 0 sky130_fd_pr__nfet_01v8 W=0.5u L=150n
Mn2 npd B 0 0 sky130_fd_pr__nfet_01v8 W=0.5u L=150n
** Inverter, or a logical NOT ### removed the inverter
*Mp3 AND nOUT VDD VDD mosp L=0.35u W=2u
*Mn3 AND nOUT 0 0 mosn L=0.35u W=2u
* Input voltage source, ramps up to VDD then back down ### stays the same
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 stays the same
.control
* transient simulation using vin sweep
tran 100n 4m
* plot vout against vin AND change to D to make the plot work
plot v(A) v(B) v(D)+0.2
.endc
Propagation Delays
Next up was learning how to measure propagation delay. I’ve done this on a physical test bench before, so I understand that propagation delay is measured from the 50% (VDD/2) crossing of the input to the 50% (VDD/2) crossing of the output.
Doing this in ngspice was new to me, so I had to understand how measurements are handled inside the simulator. There was no direct way to see the delay from my earlier plot. I wanted both to zoom in on the time axis to visually inspect the crossing points and to make ngspice calculate and print the delay automatically.
- tPHL = time from input 50% rising to output 50% falling
- tPLH = time from input 50% falling to output 50% rising
- tr = time for the signal to rise from 10% to 90% of VDD
- tf = time for the signal to fall from 90% to 10% of VDD
Zoom in
I ran the SKY130 NAND gate simulation again. In the plot window you can right-click and drag to zoom in. Left-click (or left-click drag) prints cursor data in the terminal. This is a rough but quick way to extract numerical values for analysis.
Here are the results from the terminal:
x0 = 0.00200045, y0 = 4.89706 x1 = 0.0020005, y1 = 0.882353 Start and end points of X and Y
dx = 5.34161e-08, dy = -4.01471 Distance in X and Y
dy/dx = -7.5159e+07 dx/dy = -1.33051e-08 Slope information
dx = 5.34161e-08 corresponds to the propagation delay and equals
5.34 × 10⁻⁸ seconds ≈ 53.4 nanoseconds.
This was obviously not very accurate, just a first test. I had also shifted B (in) by +2 and nOUT by +4 for visibility.

I re-plotted the signals without vertical offsets so the 50% (VDD/2) crossing points could be checked directly from the graph. The measured delay was approximately 55 nanoseconds.
You can also see the rise and fall times in the zoomed waveform. The fall time of nOUT is shorter than the rise time of B.

Acurate messurment
The zoom method was quick and easy, and in some cases it might be “good enough”. I also wanted to learn how to get more accurate numbers for tPHL and tPLH directly from ngspice.
Looking at the code from Philippe earlier, I noticed he used meas statements to measure propagation delay. I also saw Luis using similar measurements in his documentation. Since B = 1 during the A transition, I wanted to compare A rising with nOUT falling. Then, since A = 1 during the B transition, I wanted to compare B falling with nOUT rising.
I added these lines inside .control:
* tPHL: A rises B=1 nOUT falls
meas tran tphl TRIG v(A) VAL=0.9 RISE=1 TARG v(nOUT) VAL=0.9 FALL=1
* tPLH: B falls A=1 nOUT rises
meas tran tplh TRIG v(B) VAL=0.9 FALL=1 TARG v(nOUT) VAL=0.9 RISE=1
When I ran the simulation, tplh looked reasonable, but tphl came out negative:
tphl = -5.609586e-08 targ= 2.000444e-03 trig= 2.000500e-03
tplh = 1.381938e-08 targ= 2.500064e-03 trig= 2.500050e-03
I then tried measurement lines from Luis’ documentation:
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
This time I just got more negative values.
tphl_a = -5.118797e-08 targ= 2.000449e-03 trig= 2.000500e-03
tphl_a = -5.004422e-04 targ= 2.500058e-03 trig= 3.000500e-03
tphl_b = 9.999488e-04 targ= 2.000449e-03 trig= 1.000500e-03
tphl_b = -4.996012e-04 targ= 2.000449e-03 trig= 2.500050e-03
I decided to move on at this point. I measured the propagation delay with the zoom method at ~55ns. The meas behavior needs a bit more investigation, but the main objective of the assignment is complete.
Then came the “wait a second (or nanosecond)” moment.
Just as I was closing ngspice and reviewing the documentation. I noticed in the plot that A actually reaches 50% after nOUT. Even though the measured delay was negative, it was still about 55 ns.

Initial analog block
Behavioral Modeling is used in early design
Verilog A Exsample
On the class page the was a verilog A exsample that I wanted to run I was a bit confused if I would use the Iverilog or Ngspice tools to run it but both failed. Reading Ngspice and Verilog A wiki page I leared about openvaf a Verilog A compiler so I renamed the file with .va format and ran openvaf verilog-a.va but got an error I then tried to add these two lines to the top of the file that I saw on the Wiki page:
I ran it again and got a nice Finished building verilog-a.va in 0.11s message. OpenVAF compiled a .osdi file. At this point I was not sure what the next step was.
Note
I'm currently stuck and in the progress of researching what the next thing to do is
Links to things I'm looking at:
- https://www.simetrix.co.uk/Files/manuals/8.1/Verilog-A.pdf
- https://www.edaplayground.com/home
- https://verilogams.com/tutorials/vloga-intro.html
- https://designers-guide.org/verilog-ams/index.html
- https://github.com/pascalkuthe/OpenVAF
- https://www.reddit.com/r/chipdesign/comments/t5t08c/verilog_a_tutorials/
- https://ngspice.sourceforge.io/docs/ngspice-html-manual/manual.xhtml#chap_Verilog_A_Compact_Device
Understanding Verilog-A
I was barely starting to understand Verilog in general, so I took some time to read up on Verilog-A. I also learned that there is Verilog-AMS. I found a series called Verilog-A Tutorial | What is Verilog-A? (5 Videos) and it gave a better overview.
| Feature | Verilog | Verilog-A | Verilog-AMS |
|---|---|---|---|
| Domain | Digital | Analog | Mixed-Signal |
| Time Model | Discrete events | Continuous time | Discrete + Continuous |
| Value Type | Discrete values (0/1) | Continuous values (real numbers) | Both |
| Simulation Type | Digital simulation | Analog simulation | Mixed-signal simulation |
| File Extension | .v |
.va |
.vams |
| Modeling Style | Event-driven | Signal flow / Conservative | Circuit + Behavioral modeling |
| Primary Use | RTL / Digital logic | Analog behavioral models | Analog + Digital systems |
I started looking for some simple examples on how to work with Verilog-A and ngspice. It was hard to search because the results kept showing just standard Verilog examples or extra tools that made things more complex than what I was after. I then found this example, and it was exactly what I needed. This is the code I used from the page.
`include "constants.vams"
`include "disciplines.vams"
module amplifier(in, out);
input in;
output out;
electrical in, out;
parameter real Gain = 1.0; // Default gain is 1.0
analog begin
// The output voltage is the input voltage multiplied by the gain
V(out) <+ Gain * V(in);
end
endmodule
I then ran openvaf amplifier.va to compile the .osdi file. I then used this SPICE file from the page:
%%writefile amplifier_testbench.cir
* -----------------------------------------------------------------
* Testbench netlist for amplifier Verilog-A module
* -----------------------------------------------------------------
* 1) Include the Verilog-A model file:
.model Amp amplifier PARAMS: Gain=1.5
* 2) Define the sinusoidal input source:
* sin( Voffset Vamp Freq TD Theta Phase )
* Here: offset=0, amplitude=1V, freq=1Hz, no delay, no damping, no phase
VIN in 0 sin(0 1 1 0 0 0)
* 3) Instantiate the Verilog-A amplifier:
* The Verilog-A module is named 'amplifier' and we pass Gain=1.0 (optional)
NAMP in out Amp
* 4) Simulation directives:
* We'll run a transient from t=0 to t=5s with a timestep of 0.01s
.tran 0.01 5
* 5) Control statements:
.control
set wr_singlescale
set wr_vecnames
* Load the Verilog-A model (OSDI file)
pre_osdi amplifier.osdi
run
* Save results to file
wrdata amplifier.txt V(in) V(out)
.endc
.end
I first ran it in bach mode with ngspice -b amplifier_testbench.cir and that created a text file ampilifier.txt with a list of the time, V(in) and v(out).
I then ran it without bach ngspice amplifier_testbench.cir and ran plot V(in) V(out) and saw a nice aplifided since wave with gain of 1.5.

This example helped me understand that Verilog-A describes analog behavior in continuous time, unlike standard Verilog which is event-driven and I was able to understand the workflow of Verilog A.
Here you can see the workflow visually.
flowchart TB
subgraph VA["Verilog-A side"]
A["amplifier.va<br/>Verilog-A module"] --> B["openvaf compiler"]
B --> C["amplifier.osdi<br/>compiled model"]
end
subgraph SP["SPICE side"]
D["amplifier_testbench.cir<br/>netlist + sources + .tran"] --> E["ngspice"]
C -->|pre_osdi| E
E --> F["Interactive plot<br/>plot V(in) V(out)"]
E --> G["Batch export<br/>wrdata amplifier.txt ..."]
end
Write an initial analog block - Basic 2-Bit ADC (for 1-3)
For the assignment I needed something simple so I desided to make a 2 bit ADC I looked at this exsample. I took the clock out
`include "constants.vams"
`include "disciplines.vams"
module adc2bit(in, b0, b1);
input in;
output b0, b1;
electrical in, b0, b1;
parameter real VREF = 3.0;
parameter real VDD = 1.8;
analog begin
// LSB (b0)
if (V(in) < 0.75)
V(b0) <+ 0;
else if (V(in) < 1.5)
V(b0) <+ VDD;
else if (V(in) < 2.25)
V(b0) <+ 0;
else
V(b0) <+ VDD;
// MSB (b1)
if (V(in) < 1.5)
V(b1) <+ 0;
else
V(b1) <+ VDD;
end
endmodule