Skip to content

Commit

Permalink
Merge pull request #93 from BWRC-AMS-ML-Discovery/fcasc
Browse files Browse the repository at this point in the history
Rail to rail replacement for FoldedCascode
  • Loading branch information
dan-fritchman authored Oct 25, 2023
2 parents c89ac6f + 923f843 commit 7ece5a1
Show file tree
Hide file tree
Showing 5 changed files with 246 additions and 257 deletions.
2 changes: 1 addition & 1 deletion AutoCkt/Server/autockt_server/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@

## OpAmps
auto_ckt_sim_hdl21.impl(opamps.TwoStageOpAmp.opamp_inner)
folded_cascode_sim.impl(opamps.FoldedCascode.folded_cascode_sim)
folded_cascode_sim.impl(opamps.FoldedCascode.endpoint)
two_stage_op_amp_ngm_sim.impl(opamps.TwoStageOpAmp_ngm.two_stage_op_amp_ngm_sim)

## Others
Expand Down
361 changes: 166 additions & 195 deletions AutoCkt/Server/autockt_server/opamps/FoldedCascode.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,215 +2,186 @@
# Folded Cascode OpAmp
"""

from dataclasses import asdict
import hdl21 as h
import vlsirtools.spice as vsp
from hdl21.prefix import µ, m, f

from autockt_shared import FoldedCascodeInput, OpAmpOutput

# Local Imports
from ..pdk import nmos, pmos
from .tb import TbParams, simulate


@h.paramclass
class FoldedCascodeParams:
"""Parameter class"""

w1_2 = h.Param(dtype=int, desc="Width of M1/2", default=10)
w5_6 = h.Param(dtype=int, desc="Width of M5/6", default=10)
w7_8 = h.Param(dtype=int, desc="Width of M7/8", default=10)
w9_10 = h.Param(dtype=int, desc="width of M9/10", default=10)
w11_12 = h.Param(dtype=int, desc="Width of M11/12", default=10)
w13_14 = h.Param(dtype=int, desc="Width of M13/14", default=10)
w15_16 = h.Param(dtype=int, desc="Width of M15/16", default=10)
w17 = h.Param(dtype=int, desc="Width of M17", default=10)
w18 = h.Param(dtype=int, desc="width of M18", default=10)

cl = h.Param(dtype=h.Scalar, desc="cl capacitance", default=1e-14)
cc = h.Param(dtype=h.Scalar, desc="cc capacitance", default=1e-14)
rc = h.Param(dtype=h.Scalar, desc="rc resistor", default=100)
VDD = h.Param(dtype=h.Scalar, desc="VDD voltage", default=1.2)

wb0 = h.Param(dtype=int, desc="Width of MB0 ", default=10)
wb1 = h.Param(dtype=int, desc="Width of MB1 ", default=10)
wb2 = h.Param(dtype=int, desc="Width of MB2 ", default=10)
wb3 = h.Param(dtype=int, desc="Width of MB3 ", default=10)
wb4 = h.Param(dtype=int, desc="Width of MB4 ", default=10)
wb5 = h.Param(dtype=int, desc="Width of MB5 ", default=10)
wb6 = h.Param(dtype=int, desc="Width of MB6 ", default=10)
wb7 = h.Param(dtype=int, desc="Width of MB7 ", default=10)
wb8 = h.Param(dtype=int, desc="Width of MB8 ", default=10)
wb9 = h.Param(dtype=int, desc="Width of MB9 ", default=10)
wb10 = h.Param(dtype=int, desc="Width of MB10", default=10)
wb11 = h.Param(dtype=int, desc="Width of MB11", default=10)
wb12 = h.Param(dtype=int, desc="Width of MB12", default=10)
wb13 = h.Param(dtype=int, desc="Width of MB13", default=10)
wb14 = h.Param(dtype=int, desc="Width of MB14", default=10)
wb15 = h.Param(dtype=int, desc="Width of MB15", default=10)
wb16 = h.Param(dtype=int, desc="Width of MB16", default=10)
wb17 = h.Param(dtype=int, desc="Width of MB17", default=10)
wb18 = h.Param(dtype=int, desc="Width of MB18", default=10)
wb19 = h.Param(dtype=int, desc="Width of MB19", default=10)

ibias = h.Param(dtype=h.Scalar, desc="ibias current", default=30e-6)
Vcm = h.Param(dtype=h.Scalar, desc="Vcm", default=1)
class FcascParams:
"""# Fcasc Generator Parameters
In terms of unit device sizes and current ratios"""

# Unit device sizes
nbias = h.Param(dtype=int, desc="Bias Nmos Unit Width", default=2)
pbias = h.Param(dtype=int, desc="Bias Pmos Unit Width", default=4)
ncasc = h.Param(dtype=int, desc="Cascode Nmos Unit Width", default=2)
pcasc = h.Param(dtype=int, desc="Cascode Pmos Unit Width", default=4)
ninp = h.Param(dtype=int, desc="Input Nmos Unit Width", default=2)
pinp = h.Param(dtype=int, desc="Input Pmos Unit Width", default=4)

# Current Mirror Ratios
alpha = h.Param(dtype=int, desc="Alpha (Pmos Input) Current Ratio", default=2)
beta = h.Param(dtype=int, desc="Beta (Nmos Input) Current Ratio", default=2)
gamma = h.Param(dtype=int, desc="Gamma (Output Cascode) Current Ratio", default=2)

# Input Bias Current
# Applied on *both* bias inputs
ibias = h.Param(dtype=h.Prefixed, desc="Input Bias Current", default=10 * µ)

# Cascode Bias Voltages
vcp = h.Param(dtype=h.Prefixed, desc="Cascode Pmos Bias Voltage", default=200 * m)
vcn = h.Param(dtype=h.Prefixed, desc="Cascode Nmos Bias Voltage", default=200 * m)

# Load/ Compensation Cap Value
cc = h.Param(dtype=h.Scalar, desc="Load/ Compensation Cap Value", default=1000 * f)


@h.generator
def FoldedCascodeGen(p: FoldedCascodeParams) -> h.Module:
"""# Two stage OpAmp"""
def Fcasc(params: FcascParams) -> h.Module:
"""# Rail-to-Rail, Dual Input Pair, Folded Cascode, Diff to SE Op-Amp"""

# Multiplier functions of the parametric devices
nbias = lambda x: nmos(m=params.nbias * x)
ncasc = lambda x: nmos(m=params.ncasc * x)
ninp = lambda x: nmos(m=params.ninp * x)
pbias = lambda x: pmos(m=params.pbias * x)
pcasc = lambda x: pmos(m=params.pcasc * x)
pinp = lambda x: pmos(m=params.pinp * x)

# Give these some shorter-hands
alpha, beta, gamma = params.alpha, params.beta, params.gamma

@h.module
class FoldedCascode:
class Fcasc:
# IO Interface
VDD, VSS = 2 * h.Input()
ibias = h.Input()

inp = h.Diff(desc="Differential Input", port=True, role=h.Diff.Roles.SINK)

# ref = h.Input()
v9 = h.Output()
v10 = h.Output()
# v_nmbias = h.Input()
# v_nbbias = h.Input()
# v_bbias = h.Input()
v_cm = h.Input()
# v_cs = h.Input()
v_cs = h.Signal()

v_nmbias, v_nbbias, v_bbias, v_pcas = h.Signals(4)

# Internal Signals
v1, v2, v3, v4, v5, v6, v7, v8, v11, v12 = h.Signals(10)

# Input Stage
M1 = nmos(m=p.w1_2)(d=v11, g=inp.p, s=v1, b=v1)
M2 = nmos(m=p.w1_2)(d=v12, g=inp.n, s=v1, b=v1)
M17 = nmos(m=p.w17)(d=v1, g=v_nmbias, s=v2, b=v2)
M18 = nmos(m=p.w18)(d=v2, g=v_nbbias, s=VSS, b=VSS)

M5 = pmos(m=p.w5_6)(d=v11, g=v_bbias, s=VDD, b=VDD)
M6 = pmos(m=p.w5_6)(d=v12, g=v_bbias, s=VDD, b=VDD)

M7 = pmos(m=p.w7_8)(d=v5, g=v_cm, s=v11, b=v11)
M8 = pmos(m=p.w7_8)(d=v6, g=v_cm, s=v12, b=v12)
M9 = nmos(m=p.w9_10)(d=v5, g=v_nmbias, s=v3, b=v3)
M10 = nmos(m=p.w9_10)(d=v6, g=v_nmbias, s=v4, b=v4)
M11 = nmos(m=p.w11_12)(d=v3, g=v_nbbias, s=VSS, b=VSS)
M12 = nmos(m=p.w11_12)(d=v4, g=v_nbbias, s=VSS, b=VSS)

# Output Stage
M13 = pmos(m=p.w13_14)(d=v9, g=v_cs, s=VDD, b=VDD)
M14 = pmos(m=p.w13_14)(d=v10, g=v_cs, s=VDD, b=VDD)
M15 = nmos(m=p.w15_16)(d=v9, g=v5, s=VSS, b=VSS)
M16 = nmos(m=p.w15_16)(d=v10, g=v6, s=VSS, b=VSS)

# Compensation Network
Cc_1 = h.Cap(c=p.cc)(p=v9, n=v7) # Miller Capacitance
Cc_2 = h.Cap(c=p.cc)(p=v10, n=v8)
Rc_1 = h.Res(r=p.rc)(p=v7, n=v5)
Rc_2 = h.Res(r=p.rc)(p=v8, n=v6)

# Bias generator
vbias2 = h.Signal()
b1, b2, b3, b4, b5, b6, b7, b8, b9 = h.Signals(9)

MB0 = nmos(m=p.wb0)(d=ibias, g=ibias, s=vbias2, b=vbias2)
MB1 = nmos(m=p.wb1)(d=v_pcas, g=ibias, s=b1, b=b1)
MB2 = nmos(m=p.wb2)(d=vbias2, g=vbias2, s=VSS, b=VSS)
MB3 = nmos(m=p.wb3)(d=b1, g=vbias2, s=VSS, b=VSS)

MB4 = pmos(m=p.wb4)(d=v_bbias, g=v_bbias, s=VDD, b=VDD)
MB5 = pmos(m=p.wb5)(d=v_pcas, g=v_pcas, s=v_bbias, b=v_bbias)
MB6 = pmos(m=p.wb6)(d=b2, g=v_bbias, s=VDD, b=VDD)
MB7 = pmos(m=p.wb7)(d=b3, g=v_pcas, s=b2, b=b2)

MB8 = nmos(m=p.wb8)(d=b3, g=v_nmbias, s=b4, b=b4)
MB9 = nmos(m=p.wb9)(d=b4, g=v_nmbias, s=b5, b=b5)
MB10 = nmos(m=p.wb10)(d=b5, g=v_nmbias, s=b6, b=b6)
MB11 = nmos(m=p.wb11)(d=b6, g=v_nmbias, s=VSS, b=VSS)

MB12 = pmos(m=p.wb12)(d=b7, g=v_bbias, s=VDD, b=VDD)
MB13 = pmos(m=p.wb13)(d=v_nbbias, g=v_pcas, s=b7, b=b7)
MB14 = nmos(m=p.wb14)(
d=v_nbbias, g=v_nmbias, s=b8, b=b8
) # TODO: check where the gate should connect to
MB15 = nmos(m=p.wb15)(d=b8, g=v_nbbias, s=VSS, b=VSS)

MB16 = pmos(m=p.wb16)(d=v_cs, g=v_cs, s=VDD, b=VDD)
MB14 = nmos(m=p.wb14)(d=v_cs, g=vbias2, s=b9, b=b9)
MB15 = nmos(m=p.wb15)(d=b9, g=vbias2, s=VSS, b=VSS)

return FoldedCascode


def folded_cascode_sim(inp: FoldedCascodeInput) -> OpAmpOutput:
"""
FoldedCascode Simulation
"""
opts = vsp.SimOptions(
simulator=vsp.SupportedSimulators.NGSPICE,
fmt=vsp.ResultFormat.SIM_DATA, # Get Python-native result types
rundir="./scratch", # Set the working directory for the simulation. Uses a temporary directory by default.
)
if not vsp.ngspice.available():
print("ngspice is not available. Skipping simulation.")
return

params = FoldedCascodeParams(
w1_2=inp.w1_2,
w5_6=inp.w5_6,
w7_8=inp.w7_8,
w9_10=inp.w9_10,
w11_12=inp.w11_12,
w13_14=inp.w13_14,
w15_16=inp.w15_16,
w17=inp.w17,
w18=inp.w18,
# VDD=inp.VDD,
cl=inp.cl,
cc=inp.cc,
rc=inp.rc,
wb0=inp.wb0,
wb1=inp.wb1,
wb2=inp.wb2,
wb3=inp.wb3,
wb4=inp.wb4,
wb5=inp.wb5,
wb6=inp.wb6,
wb7=inp.wb7,
wb8=inp.wb8,
wb9=inp.wb9,
wb10=inp.wb10,
wb11=inp.wb11,
wb12=inp.wb12,
wb13=inp.wb13,
wb14=inp.wb14,
wb15=inp.wb15,
wb16=inp.wb16,
wb17=inp.wb17,
wb18=inp.wb18,
wb19=inp.wb19,
ibias=inp.ibias,
Vcm=inp.Vcm,
)
VDD, VSS = h.PowerGround()
inp = h.Diff(port=True, role=h.Diff.Roles.SINK)
out = h.Output() # Single ended output
ibias1, ibias2 = 2 * h.Input()

# Implementation
## Internal Signals
outn = h.Signal()
outd = h.bundlize(p=outn, n=out)
psd = h.Diff()
nsd = h.Diff()
pcascg, pbiasg = h.Signals(2)

## ###########################################################################
## Output Stack
## ###########################################################################
## Cascodes have current `gamma`
## Top has current `gamma + beta`
## Bottom has current `gamma + alpha`
## ###########################################################################
pbo = h.Pair(pbias(x=gamma + beta))(g=outn, d=psd, s=VDD, b=VDD)
pco = h.Pair(pcasc(x=gamma))(g=pcascg, s=psd, d=outd, b=VDD)
nco = h.Pair(ncasc(x=gamma))(g=ibias2, s=nsd, d=outd, b=VSS)
nbo = h.Pair(nbias(x=gamma + alpha))(g=ibias1, d=nsd, s=VSS, b=VSS)

## ###########################################################################
## Input Pairs
## Nmos has current `alpha` per leg, `2*alpha` in the bias devices
## Pmos has current `beta` per leg, `2*beta` in the bias devices
## ###########################################################################
##
## Nmos Input Pair
nin_bias = nbias(x=2 * alpha)(g=ibias1, s=VSS, b=VSS)
nin_casc = ncasc(x=2 * alpha)(g=ibias2, s=nin_bias.d, b=VSS)
nin = h.Pair(ninp(x=alpha))(g=inp, d=psd, s=nin_casc.d, b=VSS)
##
## Pmos Input Pair
pin_bias = pbias(x=2 * beta)(g=pbiasg, s=VDD, b=VDD)
pin_casc = pbias(x=2 * beta)(g=pbiasg, s=pin_bias.d, b=VDD)
pin = h.Pair(pinp(x=beta))(g=inp, d=nsd, s=pin_casc.d, b=VDD)

## ###########################################################################
## Bias Section
## ###########################################################################
## Everything in here is current-ratio one, i.e. all branches have `i=ibias`.
## Note every value of `x` equals one.
## ###########################################################################

### Nmos Cascode Gate Generator
rrcn = h.Res(r=params.vcp / params.ibias)(n=VSS)
ncdiode = ncasc(x=1)(g=ibias2, d=ibias2, s=rrcn.p, b=VSS)

### Bottom Nmos Diode (with cascode)
ndiode_casc = ncasc(x=1)(g=ibias2, d=ibias1, b=VSS)
ndiode = nbias(x=1)(g=ibias1, d=ndiode_casc.s, s=VSS, b=VSS)

### Nmos Mirror to pmos Cascode Bias
n1casc = ncasc(x=1)(g=ibias2, d=pcascg, b=VSS)
n1src = nbias(x=1)(g=ibias1, d=n1casc.s, s=VSS, b=VSS)

### Pmos cascode gate generator
rrcp = h.Res(r=params.vcp / params.ibias)(p=VDD)
pcdiode = pcasc(x=1)(g=pcascg, d=pcascg, s=rrcp.n, b=VDD)

### Nmos Mirror to top pmos Bias
n2casc = ncasc(x=1)(g=ibias2, d=pbiasg, b=VSS)
n2src = nbias(x=1)(g=ibias1, d=n2casc.s, s=VSS, b=VSS)

### Top Pmos Bias (with cascode)
pdiode = pbias(x=1)(g=pbiasg, s=VDD, b=VDD)
pdiode_casc = pcasc(x=1)(s=pdiode.d, g=pcascg, d=pbiasg, b=VDD)

## Compensation/ Load Cap
ccc = h.Cap(c=params.cc)(p=out, n=VSS)

return Fcasc


@h.generator
def FcascTb(params: TbParams) -> h.Module:
"""# Folded Cascode Op-Amp Testbench"""

vicm = params.vicm or params.VDD / 2

@h.module
class OpAmpTb:
VSS = h.Port() # The testbench interface: sole port VSS

# Drive VDD
vdc = h.Vdc(dc=params.VDD)(n=VSS)
inp = h.Diff()
sig_out = h.Signal()
sig_p = h.Vdc(dc=vicm, ac=+0.5)(p=inp.p, n=VSS)
sig_n = h.Vdc(dc=vicm, ac=-0.5)(p=inp.n, n=VSS)

# Primary difference: the two bias current inputs
ibias1, ibias2 = 2 * h.Signal()
xibias1 = h.Isrc(dc=params.ibias)(p=vdc.p, n=ibias1)
xibias2 = h.Isrc(dc=params.ibias)(p=vdc.p, n=ibias2)

# The Op-Amp DUT
inst = params.dut(
VDD=vdc.p, VSS=VSS, ibias1=ibias1, ibias2=ibias2, inp=inp, out=sig_out
)

return OpAmpTb


def endpoint(inp: FoldedCascodeInput) -> OpAmpOutput:
"""# Folded Cascode OpAmp RPC Implementation"""

# Convert `inp` into the generator's parameters
# params = as_hdl21_paramclass(inp)

VDD = h.prefix.Prefixed(number=1.2)
ibias = h.prefix.Prefixed(number=3e-5)

# Run the simulation!
results = FoldedCascodeSim(params).run(opts)

# Extract our metrics from those results
ac_result = results["ac"]
sig_out = ac_result.data["v(xtop.sig_out)"]

gain = find_dc_gain(2 * sig_out)
ugbw = find_ugbw(ac_result.freq, 2 * sig_out)
phm = find_phm(ac_result.freq, 2 * sig_out)
idd = ac_result.data["i(v.xtop.vvdc)"]
ibias = find_I_vdd(idd)

# And return them as an `OpAmpOutput`
return OpAmpOutput(
ugbw=ugbw,
gain=gain,
phm=phm,
ibias=ibias,
# Create a testbench, simulate it, and return the metrics!
opamp = FcascParams(**asdict(inp))
tbparams = TbParams(
dut=opamp, VDD=VDD, ibias=ibias, vicm=None # Use the default common mode
)
tbmodule = FcascTb(tbparams)
return simulate(tbmodule)
Loading

0 comments on commit 7ece5a1

Please sign in to comment.