from itertools import product
from matplotlib import pyplot as _plt
import numpy as _np
from pdkmaster.technology import primitive as _prm
from pdkmaster.design import circuit as _ckt, library as _lbry
from c4m.pdk import sky130
prims = sky130.tech.primitives
Compiled libraries for ngspice 36 are provided compiled for either CentOS 7 or Ubuntu20.04 so simulation can be performed without needing to install ngspice. One can comment out the setting of the NGPSICE_LIBRARY_PATH
environment variable and then ngspice executable in the path will be used to find the shared library. When using own ngspice it is adviced to use version 35 or later to speed up the
Also the provided .spiceinit
file in the running directory is needed to get this speed up.
import os
os.environ["NGSPICE_LIBRARY_PATH"] = "/home/verhaegs/software/mint20/stow/ngspice-36/lib/libngspice.so"
from PySpice.Unit import *
import pya
from pdkmaster.io.klayout import export as _klexp
ktech = pya.Technology.technology_by_name("Skywater_S8")
ksaveopts = ktech.save_layout_options.dup()
ksaveopts.write_context_info = False
from imp import reload
import ADC_support
reload(ADC_support)
from ADC_support import ADC
common library to store generated ADC cells
adclib = _lbry.Library(name="adclib", tech=sky130.tech, cktfab=sky130.cktfab, layoutfab=sky130.layoutfab)
common setup for verification; verify ADC operation at 100MHz clock
tr = 50e-12
period = 10e-9
hperiod = period/2
qperiod = period/4
vdd = 1.8
def fullcapture(*, bits: int) -> float:
return (bits + 3)*period
Currently the 8 bit version of the ADC is highest number of bits supported for layout generation.
adc8bit = ADC(
lib=adclib, name="ADC8bit",
bits=8,
# Take nominal w as w values of the transistors in inv_x1
nmos=prims.nfet_01v8, nmos_nom_w=1.8,
pmos=prims.pfet_01v8, pmos_nom_w=3.6,
pmos_amppair_w=3.6, pmos_amppair_l=1.0,
# hold PMOS switch low Vt pmos
pmos_holdpasstrans=prims.pfet_01v8_lvt, pmos_holdpasstrans_w=20.0, pmos_holdpasstrans_l=0.35,
stdcelllib=sky130.stdcelllib,
cap=prims.MIM_m3_capm, cap_unit_args={"width": 3.0, "height": 1.5, "bottom_connect_up": False},
)
adclib.cells += adc8bit
tb8bit = sky130.pyspicefab.new_pyspicecircuit(corner=("logic_tt", "rc_tt"), top=adc8bit.circuit)
tb8bit.V('supply', 'vdd', 'vss', vdd)
tb8bit.V('gnd', 'vss', tb8bit.gnd, 0.0)
for n in range(adc8bit.bits):
tb8bit.C(f"bit[{n}]", f"bit[{n}]", tb8bit.gnd, 1e-15)
tb8bit.C(f"bit_n[{n}]", f"bit_n[{n}]", tb8bit.gnd, 1e-15)
tb8bit.PulseVoltageSource(
"vin", "vin", "vss", delay_time=hperiod, period=2*fullcapture(bits=8), pulse_width=fullcapture(bits=8),
rise_time=tr, fall_time=tr, initial_value=0.0, pulsed_value=vdd,
)
tb8bit.PulseVoltageSource(
'clk', 'clk', 'vss',
delay_time=qperiod, period=period, pulse_width=hperiod, rise_time=tr, fall_time=tr,
initial_value=0.0, pulsed_value=vdd,
)
tb8bit.PulseVoltageSource(
'start', 'start', 'vss',
delay_time=period, period=fullcapture(bits=8), pulse_width=0.75*period, rise_time=tr, fall_time=tr,
initial_value=0.0, pulsed_value=vdd,
)
PulseVoltageSource Vstart
sim8bit = tb8bit.simulator()
trans8bit = sim8bit.transient(end_time=(3*fullcapture(bits=8) + period), step_time=tr/2)
time8bit = 1e9*_np.array(trans8bit.time)
Unsupported Ngspice version 36
_plt.figure(figsize=(16, 25))
_plt.subplot(adc8bit.bits + 4, 1, 1)
_plt.plot(time8bit, trans8bit.clk, label="clk")
_plt.plot(time8bit, trans8bit.vin, label="Vin")
_plt.plot(time8bit, trans8bit.start, label="start")
_plt.legend()
_plt.subplot(adc8bit.bits + 4, 1, 2)
_plt.plot(time8bit, trans8bit.clk, label="clk")
_plt.plot(time8bit, trans8bit.nodes["xtop.start_latch"], label="start_latch")
_plt.plot(time8bit, trans8bit.nodes["xtop.start_latch_n"], label="start_latch_n")
_plt.legend()
_plt.subplot(adc8bit.bits + 4, 1, 3)
_plt.plot(time8bit, trans8bit.clk, label="clk")
_plt.plot(time8bit, trans8bit.nodes["xtop.dac_caps_common"], label="daccaps_common")
_plt.plot(time8bit, trans8bit.nodes["xtop.amp_outn"], label="amp_outn")
_plt.plot(time8bit, trans8bit.nodes["xtop.amp_outp"], label="amp_outp")
_plt.plot(time8bit, trans8bit.nodes["xtop.amp_bufn"], label="amp_bufn")
_plt.plot(time8bit, trans8bit.nodes["xtop.amp_bufp"], label="amp_bufp")
_plt.legend()
_plt.subplot(adc8bit.bits + 4, 1, 4)
_plt.plot(time8bit, trans8bit.clk, label="clk")
_plt.plot(time8bit, trans8bit.nodes["xtop.dac_caps_common"], label="daccaps_common")
_plt.plot(time8bit, trans8bit.nodes["xtop.cmp_set"], label="cmp_set")
_plt.plot(time8bit, trans8bit.nodes["xtop.cmp_rst"], label="cmp_rst")
_plt.legend()
for bit in range(adc8bit.bits):
_plt.subplot(adc8bit.bits + 4, 1, bit + 5)
node = f"bit[{bit}]"
_plt.plot(time8bit, trans8bit.nodes[node])
_plt.title(node)
_plt.figure(figsize=(16, 10))
_plt.subplot(3, 1, 1)
_plt.plot(time8bit, trans8bit.clk, label="clk")
_plt.plot(time8bit, trans8bit.vin, label="Vin")
_plt.plot(time8bit, trans8bit.nodes["bit[0]"], label="bit[0]")
_plt.axis((1e9*fullcapture(bits=8), 2e9*fullcapture(bits=8), 0, 1.81))
_plt.legend()
_plt.subplot(3, 1, 2)
_plt.plot(time8bit, trans8bit.clk, label="clk")
_plt.plot(time8bit, trans8bit.nodes["xtop.dac_caps_common"], label="daccaps_common")
_plt.plot(time8bit, trans8bit.nodes["xtop.amp_outn"], label="amp_outn")
_plt.plot(time8bit, trans8bit.nodes["xtop.amp_outp"], label="amp_outp")
_plt.plot(time8bit, trans8bit.nodes["xtop.amp_bufn"], label="amp_bufn")
_plt.plot(time8bit, trans8bit.nodes["xtop.amp_bufp"], label="amp_bufp")
_plt.axis((1e9*fullcapture(bits=8), 2e9*fullcapture(bits=8), 0, 1.81))
_plt.legend()
_plt.subplot(3, 1, 3)
_plt.plot(time8bit, trans8bit.vin, label="vin")
_plt.plot(time8bit, trans8bit.nodes["xtop.dac_caps_common"], label="daccaps_common")
_plt.plot(time8bit, trans8bit.nodes["xtop.cmp_set"], label="cmp_set")
_plt.plot(time8bit, trans8bit.nodes["xtop.cmp_rst"], label="cmp_rst")
_plt.axis((1e9*fullcapture(bits=8), 2e9*fullcapture(bits=8), -1, 1.81))
_plt.legend()
<matplotlib.legend.Legend at 0x7f9a2149ef50>
The PMOS switch to drive the capacitance during the capture cycle limits the input range of the ADC to about 0.6V-1.8V.
_plt.figure(figsize=(16, 10))
_plt.subplot(3, 1, 1)
_plt.plot(time8bit, trans8bit.clk, label="clk")
_plt.plot(time8bit, trans8bit.vin, label="Vin")
_plt.plot(time8bit, trans8bit.nodes["bit[0]"], label="bit[0]")
_plt.axis((2e9*fullcapture(bits=8), 3e9*fullcapture(bits=8), 0, 1.81))
_plt.legend()
_plt.subplot(3, 1, 2)
_plt.plot(time8bit, trans8bit.clk, label="clk")
_plt.plot(time8bit, trans8bit.nodes["xtop.dac_caps_common"], label="daccaps_common")
_plt.plot(time8bit, trans8bit.nodes["xtop.amp_outn"], label="amp_outn")
_plt.plot(time8bit, trans8bit.nodes["xtop.amp_outp"], label="amp_outp")
_plt.plot(time8bit, trans8bit.nodes["xtop.amp_bufn"], label="amp_bufn")
_plt.plot(time8bit, trans8bit.nodes["xtop.amp_bufp"], label="amp_bufp")
_plt.axis((2e9*fullcapture(bits=8), 3e9*fullcapture(bits=8), 0, 1.81))
_plt.legend()
_plt.subplot(3, 1, 3)
_plt.plot(time8bit, trans8bit.vin, label="vin")
_plt.plot(time8bit, trans8bit.nodes["xtop.dac_caps_common"], label="daccaps_common")
_plt.plot(time8bit, trans8bit.nodes["xtop.cmp_set"], label="cmp_set")
_plt.plot(time8bit, trans8bit.nodes["xtop.cmp_rst"], label="cmp_rst")
_plt.axis((2e9*fullcapture(bits=8), 3e9*fullcapture(bits=8), -1, 1.81))
_plt.legend()
<matplotlib.legend.Legend at 0x7f9a256d5210>
_plt.figure(figsize=(16, 10))
_plt.subplot(3, 1, 1)
_plt.plot(time8bit, trans8bit.clk, label="clk")
_plt.plot(time8bit, trans8bit.vin, label="Vin")
_plt.plot(time8bit, trans8bit.nodes["xtop.clk_n"], label="clk_n")
_plt.plot(time8bit, trans8bit.nodes["xtop.clk2"], label="clk2")
_plt.plot(time8bit, trans8bit.nodes["bit[0]"], label="bit[0]")
_plt.axis((1e9*(fullcapture(bits=8) + 7*period), 1e9*(fullcapture(bits=8) + 7.5*period), -0.01, 1.81))
_plt.legend()
_plt.subplot(3, 1, 2)
_plt.plot(time8bit, trans8bit.clk, label="clk")
_plt.plot(time8bit, trans8bit.nodes["xtop.dac_caps_common"], label="daccaps_common")
_plt.plot(time8bit, trans8bit.nodes["xtop.amp_outn"], label="amp_outn")
_plt.plot(time8bit, trans8bit.nodes["xtop.amp_outp"], label="amp_outp")
_plt.plot(time8bit, trans8bit.nodes["xtop.amp_bufn"], label="amp_bufn")
_plt.plot(time8bit, trans8bit.nodes["xtop.amp_bufp"], label="amp_bufp")
_plt.axis((1e9*(fullcapture(bits=8) + 7*period), 1e9*(fullcapture(bits=8) + 7.5*period), -0.5, 1.81))
_plt.legend()
_plt.subplot(3, 1, 3)
_plt.plot(time8bit, trans8bit.vin, label="vin")
_plt.plot(time8bit, trans8bit.nodes["xtop.dac_caps_common"], label="daccaps_common")
_plt.plot(time8bit, trans8bit.nodes["xtop.cmp_set"], label="cmp_set")
_plt.plot(time8bit, trans8bit.nodes["xtop.cmp_rst"], label="cmp_rst")
_plt.axis((1e9*(fullcapture(bits=8) + 7*period), 1e9*(fullcapture(bits=8) + 7.5*period), -0.1, 1.81))
_plt.legend()
<matplotlib.legend.Legend at 0x7f9a2568ae10>
Also a 6-bit version of the ADC is generated; this is to show the flexibility of the layout generation. Reducing the number of bits also reduces the highest capacitance in the capacitive DAC; this is now only 32 times the unit capacitance and not 128 as for the 8 bit one. As the same drive strength is used to drive the capacitances this means that unit capacitance can be increased to increase the offset caused by transistor matching.
adc6bit = ADC(
lib=adclib, name="ADC6bit", bits=6,
# Take nominal w as w values of the transistors in inv_x1
nmos=prims.nfet_01v8, nmos_nom_w=1.8,
pmos=prims.pfet_01v8, pmos_nom_w=3.6,
pmos_amppair_w=3.6, pmos_amppair_l=1.0,
# hold PMOS switch low Vt pmos
pmos_holdpasstrans=prims.pfet_01v8_lvt, pmos_holdpasstrans_w=20.0, pmos_holdpasstrans_l=0.35,
stdcelllib=sky130.stdcelllib,
cap=prims.MIM_m3_capm, cap_unit_args={"width": 6.0, "height": 2.0, "bottom_connect_up": False},
)
adclib.cells += adc6bit
tb6bit = sky130.pyspicefab.new_pyspicecircuit(corner=("logic_tt", "rc_tt"), top=adc6bit.circuit)
tb6bit.V('supply', 'vdd', 'vss', vdd)
tb6bit.V('gnd', 'vss', tb6bit.gnd, 0.0)
for n in range(adc6bit.bits):
tb6bit.C(f"bit[{n}]", f"bit[{n}]", tb6bit.gnd, 1e-15)
tb6bit.C(f"bit_n[{n}]", f"bit_n[{n}]", tb6bit.gnd, 1e-15)
tb6bit.PulseVoltageSource(
"vin", "vin", "vss", delay_time=hperiod, period=2*fullcapture(bits=6), pulse_width=fullcapture(bits=6),
rise_time=tr, fall_time=tr, initial_value=0.0, pulsed_value=vdd,
)
tb6bit.PulseVoltageSource(
'clk', 'clk', 'vss',
delay_time=qperiod, period=period, pulse_width=hperiod, rise_time=tr, fall_time=tr,
initial_value=0.0, pulsed_value=vdd,
)
tb6bit.PulseVoltageSource(
'start', 'start', 'vss',
delay_time=period, period=fullcapture(bits=6), pulse_width=0.75*period, rise_time=tr, fall_time=tr,
initial_value=0.0, pulsed_value=vdd,
)
PulseVoltageSource Vstart
sim6bit = tb6bit.simulator()
trans6bit = sim6bit.transient(end_time=(3*fullcapture(bits=6) + period), step_time=tr/2)
time6bit = 1e9*_np.array(trans6bit.time)
_plt.figure(figsize=(16, 25))
_plt.subplot(adc6bit.bits + 4, 1, 1)
_plt.plot(time6bit, trans6bit.clk, label="clk")
_plt.plot(time6bit, trans6bit.vin, label="Vin")
_plt.plot(time6bit, trans6bit.start, label="start")
_plt.legend()
_plt.subplot(adc6bit.bits + 4, 1, 2)
_plt.plot(time6bit, trans6bit.clk, label="clk")
_plt.plot(time6bit, trans6bit.nodes["xtop.start_latch"], label="start_latch")
_plt.plot(time6bit, trans6bit.nodes["xtop.start_latch_n"], label="start_latch_n")
_plt.legend()
_plt.subplot(adc6bit.bits + 4, 1, 3)
_plt.plot(time6bit, trans6bit.clk, label="clk")
_plt.plot(time6bit, trans6bit.nodes["xtop.dac_caps_common"], label="daccaps_common")
_plt.plot(time6bit, trans6bit.nodes["xtop.amp_outn"], label="amp_outn")
_plt.plot(time6bit, trans6bit.nodes["xtop.amp_outp"], label="amp_outp")
_plt.plot(time6bit, trans6bit.nodes["xtop.amp_bufn"], label="amp_bufn")
_plt.plot(time6bit, trans6bit.nodes["xtop.amp_bufp"], label="amp_bufp")
_plt.legend()
_plt.subplot(adc6bit.bits + 4, 1, 4)
_plt.plot(time6bit, trans6bit.clk, label="clk")
_plt.plot(time6bit, trans6bit.nodes["xtop.dac_caps_common"], label="daccaps_common")
_plt.plot(time6bit, trans6bit.nodes["xtop.cmp_set"], label="cmp_set")
_plt.plot(time6bit, trans6bit.nodes["xtop.cmp_rst"], label="cmp_rst")
_plt.legend()
for bit in range(adc6bit.bits):
_plt.subplot(adc6bit.bits + 4, 1, bit + 5)
node = f"bit[{bit}]"
_plt.plot(time6bit, trans6bit.nodes[node])
_plt.title(node)
file_name = f"adclib_C4MSky130.gds"
conv_name = f"adclib_Sky130.gds"
klay1v8db = _klexp.export2db(
obj=adclib, add_pin_label=True, gds_layers=sky130.gds_layers, cell_name=None, merge=True,
)
klay1v8db.write(file_name, ksaveopts)
print(f"{file_name} saved")
!../open_pdk/C4M.Sky130/libs.tech/klayout/bin/conv_c4msky130_to_sky130.py {file_name} {conv_name}
print(f"and converted to {conv_name}")
adclib_C4MSky130.gds saved and converted to adclib_Sky130.gds