From b07f04443e00e050b72aaf5cea4303326576f317 Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Thu, 5 Jan 2023 23:12:46 +0000 Subject: [PATCH 01/10] Remove scratch files --- scratch/bsim4v5.out | 3 --- scratch/empty | 0 scratch/netlist.raw | Bin 12178 -> 0 bytes scratch/netlist.sp | 57 -------------------------------------------- 4 files changed, 60 deletions(-) delete mode 100644 scratch/bsim4v5.out create mode 100644 scratch/empty delete mode 100644 scratch/netlist.raw delete mode 100644 scratch/netlist.sp diff --git a/scratch/bsim4v5.out b/scratch/bsim4v5.out deleted file mode 100644 index f2214b8..0000000 --- a/scratch/bsim4v5.out +++ /dev/null @@ -1,3 +0,0 @@ - -Checking parameters for BSIM 4.5 model xtop.xdut.xsa.xnclk:sky130_fd_pr__nfet_01v8__model.26 -Warning: Cdscd = -0.00522474 is negative. diff --git a/scratch/empty b/scratch/empty new file mode 100644 index 0000000..e69de29 diff --git a/scratch/netlist.raw b/scratch/netlist.raw deleted file mode 100644 index 2845c8914f37528a44069e1781b46649c3938ab6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12178 zcmbu_c{o&izz1*%8M0htE16`?l6|Y+L1ZbR5K0MSnHxh44Q;kaR|$2cD`d$YNyyAt zBNa-kds~EDhRRx&l2>!iJ-1u$`@H5k=Z_iZH*@C9=Q(qZ-*diS2O`23 zlDl76Ffo|N+|S)7*bwg-;2-GcMd7gtP{Z$b4;i}+^5UwF2A;X_ z{QDLjoI1`s$m>vu7s)g1yT5#bhynWhx9o9DgVQXF1whsPbb3GhUF%QTX2x463#OuD9DRM=E3QI|0;p- z!>b01yEa}rd~Yb(ci(qz4RstVTl@I$!?_duiDa@n$#d}_X=p5dIaWS^NLbuEO4HC} zY44ssKKpQ()%vyr`cW+}sO>{qoE+>oi0*j(r`I0KfDOEUqWkiHJPlVjIT+ z`PF7t@d6RGh0D~RgjaLW;~GSp9bJv<&#T5>ymo(Lb49ZT!#HEe2x8P5uuGd5nYEP~ z@VV<4O??cIZcpyq{LlCJzn(edzs#JKonqOun%=CQvN{#@ynFc8(op>a>UqYy(RGUh z9`$6qCh?j&2!g90Z^b*4<7;K6_8lz~;XysV)rChA3a2r9><>KoRI_)MWe;RYjxiIv zjCwk^tvGjuZ4&itJ{c60eOC5gk33|Xb5;aYAGoRbLiv0xze-QotzQLE4^<>Pj_uw& zX3zDSy{Z@AEwJo4ZFzXM)ixXTP`1BR(RnwGdbCwtu9@~Kp&m(HyW3{H5@2du8lTck zX64 zReSbsQ%5~LZ~1Vp+wkBVq3>LjV_~gj7URNe4oTF*KHJEn^~Va#9*4bu89H0zSoX*Y zc<#*NEJZ!sd|ytj{>TpgxgN|*B?~A=X``OCe?1>jj*n{9pP`I-8w7gRH?E`q! z(_xMqAFbxY?CJC0CB6Ai0hT?s%F8rM%qmb%V9H6`8?RQNp608xPf1+{sAs)2@5+=e zB_I;4SVHQmuiepgPsSrd7WL$L;z}5mLYO@aAvRORl_D&AF2q(WoL{IyJ-&5M4GMVq zQBV2Kwjd$-m-r_)^QqY~;dN{`;rmmr4OlNPW2hz&@v z>}hFV*~K+QLp^(phK{E*giudSBiCkXh#Bg+!)O(wg=mA9ohJ$;K0mHqBg$s{Y_~e< z8Q1vRW;jm@vuCSLW9LYVG|QgZR*L*ZgC^7?*t!=od?1E;#E+b7W;9x%o(*?(yN4SM zK+U|QPJdBPtq$)pt==SU)HDC|d16wb3}(-A=093lmOXj*HeSgdZ$&-jYWa@>W~ETi z6+fxnnYwnUC(+M@BTLs99Qrjp%3PdLE5^P#-etf5^?a_$hb|pg!0d_NxT(5ARf%Pf zcgfV3!+q_jCz(CbU-q>O>gnh-oZLlpKt1TZJq>PeIr5ZGSff_iMq`PN%k zSOG?xCcT3yrs46xe+E=xO-2a z9%j$61*^CIYYkZT9BaGMIr;Dtqr7(qO-QMmH_=y)KBcy8l`n+l@Cz~KmKkys%&YLmB;gwg>r z>e-o^-Vk!s1GC3!`)iBg!koAf9aH|hyk3&H~|Fbb)mOUQs_Xe(}uY}?d z&z$7S-{Q+Xz-PqcT&(lvi{5##0r6ND?Y}jz7X$boCh+S;2UB@UGj_+xMWCJ`W4Xjb z*8Z41o+n)Q;k*M__Ux3BbHB(g1a%{x_TTzt55)NZUI@PbW?hA@@3G7TAS&0P6qY^JkJs_<_-zd&n+JRJ_u@sh^MZgi;@RoZ%w4OT3&IeOYq@W4t@3%W zqdcWO?kjO{w8A%$_u<@}aN0PO$7z32XK#nO+BZU4=buU)20p zhaCo|5f3T%xN<$A5L6+aF00L(8whFOVbi?j!8;k$l7O8X!!6FE9*IY1+XcO&Fndn% z8srvKN3-nVVB2M%mmv>%7Qi0C{%x@wXO4mQh=+Fe-kGWCG9cCm-@kU~kRSLsoe9J_ z)E{vW3aF_vY`3q)rJ)}F=Q(_tN^zJyh2yaXS&vS!?3t)qLz@p%fzB4go~DZGh=H^y zaK;F}9%#R5%Q`My2`UkfX2&q)gmf=F8Q z|G+qrWsl45_k4HWX+USnVNXPQvTM(;ap0se>}j1n^U2xq7Rc|1k58e&RjxgbgCtd~#Hn6AT`NToPt_Q$t9QKH=FKgV?RS)hfkST&nL(~iM-uoMUZ=oIo zB(K2gfk2qj*~)??dsZNM~l%?I`{45v;P=>7?Q;rW}HpN76{N-Na83nH4Yj*2>er3MJjJuQ~G zO^-$LQ)eWv!0L$-BJJ!+xv^vq2jU5Hs*Fqx*$rh)!yZ9exTS|vDbSCAJ&8N^fBHc0 z0sJzsXS?h6?PK%@K;M|F>pm|x?b3ZYso`bord1<5P0di-rHI+F8hmh53iJe(2o z1vcic(5pHZ=I`xO$A`x>n6WG_=c#M#24l*t5FL_EdRM4`|`C z$SC@tKoh^j5jbo0Cp`l3lp}csR!?~QXl&N+ZA{fj#V`xMeRsOoFvX2AS7`?D34}FFy1G4Z}7^{%K9xrE)tx<7YkeaKv*6$t$pW zN{no*4o`J1+0%n~q-UQe1vego9LEVvkL#eF{#MO9!0PEc=JBE3ue6pDp9NFD4lz9@ z`#0@bE6xC0A6m53@k2C+_hv*3p11TM#G{7f6<9r|HkI`%9DTfGPaEP9Todc*u_qKN zzvajDD2{AlJk#m`b}k>7o`)~&*B(h-03w47rbl%+=g`sAQ83@Xt3=a2k|t|-xcGRJqkGqgfTt(tzB!H-5&wVhB2mRto3m8 z?Y`wujLEm-l~^|)!spV`HwgslC0~oY*+tVUU${BmG)i|sJfTQlfz^|e@chR0-q%a^ z_#mE-H%lbSsv@C*KN6Upv9jtl+Op5V)Y&Pfr|nhbxdCn*hJT7Pz!^ zZ*?5rM@!!H+6w=5lCFbzNJw6R)srQ#c4Bv6|B^i`5RZvJkJc0GSSTsAnCUs1ob0hq z<`t+&onv}lSzRyP6(azp4SZyJI#aaR9AXwgs-@vbSEUav@;v4KZsl1z2jbyG@(QdT z#niiU&nZJo_5_mP>-94KbsqCV2@t8`CDY@3%i90)?mjT7z~TR`FLr%w&gJGaVQ9v5 zf$15P5ZGAtY&kUDYrRSDZV=5I8f9E7TcB6f!+o)F1vsz3>JgAhi{8fAW*RrW_reqTKTJI#i8|5{EO>}b=`Jct7~Stp|JFn zKC#wgv=K>sJln+bItcL;BY6c@&tt!Fl{19NC3|@MU=JXDGPE)J9SVD$$MlqoN~eVi zz6T6PWu`~2$KZv|A!+D*o*dH?()&S0?~nkbNV>90@?so~t+mNpcnz-ZXdQff_>jB; zt4HkH{NUG_C3~(DVNcZ4CeG<)>CmUWM8j{#Cp~Rf#PRJD;Joinre|U#Bck-)dZ=i8 z8`Japm4@TBd&1EEqxsuIDw1jV2F~`w=>m0@bl5XYh4Tum9tW)bV%tEkAa^p6s{Zug4{7=Cc=@;XFC>Z~*72o2ptowaQ_@@f-e+tGwRfzSmRfr8(>hS*5AXdlLAl6`M@Be3mxTF2s^2KP` zw=Y7^_O@=52or})|i21g~hoXiJdnMg<>i@qp G>VE*?ToDQY diff --git a/scratch/netlist.sp b/scratch/netlist.sp deleted file mode 100644 index 72e2bdf..0000000 --- a/scratch/netlist.sp +++ /dev/null @@ -1,57 +0,0 @@ -* Test Netlist - -* Anonymous circuit.Package -* Written by SpiceNetlister -* - -.SUBCKT CommonSource_1e4f9b44d45e0416f9135fadc4dd89e0_ -+ VSS VDD vin vout -* No parameters - -xnmos -+ vout vin VSS VSS -+ sky130_fd_pr__nfet_01v8 -+ w='1' l='150m' nf='1' mult='1' - -rres -+ VDD vout -+ 1000.0 -* No parameters - - -.ENDS - -.SUBCKT CommonSourceTb -+ VSS -* No parameters - -vVDD_src -+ VDD VSS -+ dc '1800m' -+ ac='0' - -vvin_src -+ vin VSS -+ dc '1500m' -+ ac='1' - -xdut -+ VSS VDD vin vout -+ CommonSource_1e4f9b44d45e0416f9135fadc4dd89e0_ -* No parameters - - -.ENDS - -xtop 0 CommonSourceTb // Top-Level DUT - -.lib "/opt/conda/share/pdk/sky130A/libs.tech/ngspice/sky130.lib.spice" tt -.save @m.xtop.xdut.xnmos.msky130_fd_pr__nfet_01v8[vth] -.save @m.xtop.xdut.xnmos.msky130_fd_pr__nfet_01v8[gm] -.save @m.xtop.xdut.xnmos.msky130_fd_pr__nfet_01v8[id] -.save @m.xtop.xdut.xnmos.msky130_fd_pr__nfet_01v8[cgg] -.save all -.op - -.ac dec 10 1.0 100000.0 - From 609477a2591a23d1170a049745f3dbf0909db2c0 Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Thu, 5 Jan 2023 23:13:04 +0000 Subject: [PATCH 02/10] Vlsir, Hdl21 => 3.0.dev2 --- pyproject.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5d7a062..2741206 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,9 +5,9 @@ name = "adc" version = "0.1.0" [tool.poetry.dependencies] -hdl21 = "3.0.dev0" -vlsirtools = "3.0.dev0" -sky130-hdl21 = "3.0.dev0" +hdl21 = "3.0.dev2" +vlsirtools = "3.0.dev2" +sky130-hdl21 = "3.0.dev2" pydantic = ">=1.9" python = ">=3.7,<3.11" From 52c083e9cd6a08863d7f5b5d26a5deaa01a8e481 Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Thu, 5 Jan 2023 23:13:30 +0000 Subject: [PATCH 03/10] Use sky130.install for model-file references --- .gitignore | 3 +++ README.md | 7 +++---- characterize_technology.py | 6 +++--- strongarm.py | 7 ++----- telescopic_cascode_diffamp_generator.py | 10 +++++----- 5 files changed, 16 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index 7127d15..40d1495 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ + +scratch + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/README.md b/README.md index 6f6f6e4..010422e 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # CT-DS-ADC_generator + A Continuous Time (CT) Delta Sigma (DS) ADC Generator -The (I think?) important things to run in a GitHub codespace: +Installation: ``` -conda activate base conda install -y -c litex-hub open_pdks.sky130a git clone git@github.com:dan-fritchman/Hdl21.git @@ -18,7 +18,6 @@ git checkout 2079c44fd7b34023c737a7c01ea624b50289524f cd .. python scripts/manage.py install -conda install -y poetry -conda upgrade pip +conda upgrade -y pip pip install -e ".[dev]" ``` diff --git a/characterize_technology.py b/characterize_technology.py index 4a49d3b..110eca8 100644 --- a/characterize_technology.py +++ b/characterize_technology.py @@ -5,17 +5,17 @@ import hdl21 as h from hdl21.primitives import Vdc from vlsirtools.spice import SimOptions, SupportedSimulators, ResultFormat +import sitepdks, sky130 from matplotlib import pyplot as plt from matplotlib import cm as cm - import numpy as np import scipy.interpolate import nest_asyncio nest_asyncio.apply() -CONDA_PREFIX = os.environ.get("CONDA_PREFIX", None) + sim_options = SimOptions( rundir=Path("./scratch"), @@ -115,7 +115,7 @@ def run_characterization_sims(np_filename): tb.VGS_src = Vdc(Vdc.Params(dc=str(-vgs)))(p=tb.VGS, n=tb.VSS) tb.VBS_src = Vdc(Vdc.Params(dc=str(-vbs)))(p=tb.VBS, n=tb.VSS) sim = h.sim.Sim(tb=tb) - sim.lib(f"{CONDA_PREFIX}/share/pdk/sky130A/libs.tech/ngspice/sky130.lib.spice", 'tt') + sim.lib(sky130.install.model_lib, 'tt') sim.op() if mos_type == "nch": sim.literal(".save @m.xtop.xdut.msky130_fd_pr__nfet_01v8_lvt[gm]") diff --git a/strongarm.py b/strongarm.py index 55e7138..c5b18c6 100644 --- a/strongarm.py +++ b/strongarm.py @@ -11,10 +11,7 @@ from hdl21.prefix import m, µ, f, n, PICO from hdl21.primitives import Vdc, Idc, C, Vpulse from vlsirtools.spice import SimOptions, SupportedSimulators, ResultFormat - - -CONDA_PREFIX = os.environ.get("CONDA_PREFIX", None) - +import sitepdks, sky130 sim_options = SimOptions( rundir=Path("./scratch"), @@ -326,7 +323,7 @@ class ComparatorSim: tr = hs.Tran(tstop=12 * n, tstep=100*PICO) # Add the PDK dependencies - ComparatorSim.lib(f"{CONDA_PREFIX}/share/pdk/sky130A/libs.tech/ngspice/sky130.lib.spice", 'tt') + ComparatorSim.lib(sky130.install.model_lib, 'tt') ComparatorSim.literal(".option METHOD=Gear") # Run Spice, save important results diff --git a/telescopic_cascode_diffamp_generator.py b/telescopic_cascode_diffamp_generator.py index 4961fa5..5eebe0a 100644 --- a/telescopic_cascode_diffamp_generator.py +++ b/telescopic_cascode_diffamp_generator.py @@ -939,7 +939,7 @@ class TelescopicAmplifierTranSim: tr = hs.Tran(tstop=3000 * n, tstep=1*n) # Add the PDK dependencies - # TelescopicAmplifierSim.lib(f"{CONDA_PREFIX}/share/pdk/sky130A/libs.tech/ngspice/sky130.lib.spice", 'tt') + # TelescopicAmplifierSim.lib(sky130.install.model_lib, 'tt') TelescopicAmplifierSim.lib(sky130.install.model_lib, 'tt') TelescopicAmplifierSim.literal(".save all") results = TelescopicAmplifierSim.run(sim_options) @@ -967,7 +967,7 @@ class TelescopicAmplifierAcSim: myac = hs.Ac(sweep=LogSweep(1e1, 1e11, 10)) # Add the PDK dependencies - TelescopicAmplifierAcSim.lib(f"{CONDA_PREFIX}/share/pdk/sky130A/libs.tech/ngspice/sky130.lib.spice", 'tt') + TelescopicAmplifierAcSim.lib(sky130.install.model_lib, 'tt') TelescopicAmplifierAcSim.literal(".save all") results = TelescopicAmplifierAcSim.run(sim_options) ac_results = results.an[0].data @@ -1266,7 +1266,7 @@ class Stage2AmpTranSim: tr = hs.Tran(tstop=3000 * n, tstep=1*n) # Add the PDK dependencies - Stage2AmpTranSim.lib(f"{CONDA_PREFIX}/share/pdk/sky130A/libs.tech/ngspice/sky130.lib.spice", 'tt') + Stage2AmpTranSim.lib(sky130.install.model_lib, 'tt') Stage2AmpTranSim.literal(".save all") results = Stage2AmpTranSim.run(sim_options) tran_results = results.an[0].data @@ -1685,7 +1685,7 @@ class TwoStageAmpTranSim: tr = hs.Tran(tstop=3000 * n, tstep=1*n) # Add the PDK dependencies - TwoStageAmpTranSim.lib(f"{CONDA_PREFIX}/share/pdk/sky130A/libs.tech/ngspice/sky130.lib.spice", 'tt') + TwoStageAmpTranSim.lib(sky130.install.model_lib, 'tt') TwoStageAmpTranSim.literal(".save all") results = TwoStageAmpTranSim.run(sim_options) tran_results = results.an[0].data @@ -1724,7 +1724,7 @@ class TwoStageAmpAcSim: myac = hs.Ac(sweep=LogSweep(1e1, 1e11, 10)) # Add the PDK dependencies - TwoStageAmpAcSim.lib(f"{CONDA_PREFIX}/share/pdk/sky130A/libs.tech/ngspice/sky130.lib.spice", 'tt') + TwoStageAmpAcSim.lib(sky130.install.model_lib, 'tt') TwoStageAmpAcSim.literal(".save all") results = TwoStageAmpAcSim.run(sim_options) ac_results = results.an[0].data From 669eec4d81a4a4fa6f90174c0d7774c7c180ae10 Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Thu, 5 Jan 2023 23:29:51 +0000 Subject: [PATCH 04/10] Break into adc/ package, data/ dir, scripts/ dir. Or at least start to. --- adc.py | 7 ------- adc/__init__.py | 5 +++++ database_query.py => adc/database_query.py | 11 ----------- strongarm.py => adc/strongarm.py | 0 .../telescopic_cascode_diffamp_generator.py | 0 adc/tests/__init__.py | 0 adc/tests/test_adc.py | 17 +++++++++++++++++ test.py => adc/tests/test_common_source.py | 0 database.npy => data/database.npy | Bin database_nch.npy => data/database_nch.npy | Bin .../database_nch_lvt.npy | Bin database_pch.npy => data/database_pch.npy | Bin .../database_pch_lvt.npy | Bin data/empty | 0 data/readme.md | 4 ++++ tx_char_w=0p5.npy => data/tx_char_w=0p5.npy | Bin .../characterize_technology.py | 7 +++---- scripts/empty | 0 18 files changed, 29 insertions(+), 22 deletions(-) delete mode 100644 adc.py create mode 100644 adc/__init__.py rename database_query.py => adc/database_query.py (96%) rename strongarm.py => adc/strongarm.py (100%) rename telescopic_cascode_diffamp_generator.py => adc/telescopic_cascode_diffamp_generator.py (100%) create mode 100644 adc/tests/__init__.py create mode 100644 adc/tests/test_adc.py rename test.py => adc/tests/test_common_source.py (100%) rename database.npy => data/database.npy (100%) rename database_nch.npy => data/database_nch.npy (100%) rename database_nch_lvt.npy => data/database_nch_lvt.npy (100%) rename database_pch.npy => data/database_pch.npy (100%) rename database_pch_lvt.npy => data/database_pch_lvt.npy (100%) create mode 100644 data/empty create mode 100644 data/readme.md rename tx_char_w=0p5.npy => data/tx_char_w=0p5.npy (100%) rename characterize_technology.py => scripts/characterize_technology.py (98%) create mode 100644 scripts/empty diff --git a/adc.py b/adc.py deleted file mode 100644 index f9995ca..0000000 --- a/adc.py +++ /dev/null @@ -1,7 +0,0 @@ -""" -ADC "Package" - -Really just a placeholder file with a name that is a valid python module-name. -""" - -... diff --git a/adc/__init__.py b/adc/__init__.py new file mode 100644 index 0000000..74758f2 --- /dev/null +++ b/adc/__init__.py @@ -0,0 +1,5 @@ +""" +ADC Package +""" + +... diff --git a/database_query.py b/adc/database_query.py similarity index 96% rename from database_query.py rename to adc/database_query.py index 8da6839..1069eb3 100644 --- a/database_query.py +++ b/adc/database_query.py @@ -158,14 +158,3 @@ def query_db_for_function(database,varname,mos_type,lch): pch_db_filename = "database_pch.npy" pch_lvt_db_filename = "database_pch_lvt.npy" - -def test_db(): - db = MosDB() - for filename in [nch_db_filename, nch_lvt_db_filename, pch_db_filename, pch_lvt_db_filename]: - data = np.load(filename, allow_pickle=True) - db.build(filename) - breakpoint() - - -if __name__ == '__main__': - test_db() diff --git a/strongarm.py b/adc/strongarm.py similarity index 100% rename from strongarm.py rename to adc/strongarm.py diff --git a/telescopic_cascode_diffamp_generator.py b/adc/telescopic_cascode_diffamp_generator.py similarity index 100% rename from telescopic_cascode_diffamp_generator.py rename to adc/telescopic_cascode_diffamp_generator.py diff --git a/adc/tests/__init__.py b/adc/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/adc/tests/test_adc.py b/adc/tests/test_adc.py new file mode 100644 index 0000000..c4f45a5 --- /dev/null +++ b/adc/tests/test_adc.py @@ -0,0 +1,17 @@ +import numpy as np +from pathlib import Path + + +data_dir = Path("./data") # Path to the repo-level `data/` directory + +def test_db(): + """ Test `database_query`""" + from ..database_query import MosDB, nch_db_filename, nch_lvt_db_filename, pch_db_filename, pch_lvt_db_filename + db = MosDB() + for filename in [nch_db_filename, nch_lvt_db_filename, pch_db_filename, pch_lvt_db_filename]: + data = np.load(data_dir / filename, allow_pickle=True) + db.build(data_dir / filename) + + +if __name__ == '__main__': + test_db() diff --git a/test.py b/adc/tests/test_common_source.py similarity index 100% rename from test.py rename to adc/tests/test_common_source.py diff --git a/database.npy b/data/database.npy similarity index 100% rename from database.npy rename to data/database.npy diff --git a/database_nch.npy b/data/database_nch.npy similarity index 100% rename from database_nch.npy rename to data/database_nch.npy diff --git a/database_nch_lvt.npy b/data/database_nch_lvt.npy similarity index 100% rename from database_nch_lvt.npy rename to data/database_nch_lvt.npy diff --git a/database_pch.npy b/data/database_pch.npy similarity index 100% rename from database_pch.npy rename to data/database_pch.npy diff --git a/database_pch_lvt.npy b/data/database_pch_lvt.npy similarity index 100% rename from database_pch_lvt.npy rename to data/database_pch_lvt.npy diff --git a/data/empty b/data/empty new file mode 100644 index 0000000..e69de29 diff --git a/data/readme.md b/data/readme.md new file mode 100644 index 0000000..5d9dec1 --- /dev/null +++ b/data/readme.md @@ -0,0 +1,4 @@ + +# Data Directory + +In numpy binary format, as generated by [scripts/characterize_technology](../scripts/characterize_technology.py). diff --git a/tx_char_w=0p5.npy b/data/tx_char_w=0p5.npy similarity index 100% rename from tx_char_w=0p5.npy rename to data/tx_char_w=0p5.npy diff --git a/characterize_technology.py b/scripts/characterize_technology.py similarity index 98% rename from characterize_technology.py rename to scripts/characterize_technology.py index 110eca8..c41beb9 100644 --- a/characterize_technology.py +++ b/scripts/characterize_technology.py @@ -1,6 +1,5 @@ from pathlib import Path -import os import hdl21 as h from hdl21.primitives import Vdc @@ -23,8 +22,9 @@ simulator=SupportedSimulators.NGSPICE, ) -tb_prefix = 'tb_mos_ibias' +data_dir = Path("./data") # Path to the repo-level `data/` directory np_filename = 'database_nch.npy' +tb_prefix = 'tb_mos_ibias' mos_list = ['nch'] w_unit = 0.5 lch_list = [0.15, 1] @@ -162,7 +162,7 @@ def run_characterization_sims(np_filename): "cgg" : cgg, "cdd" : cdd, } - np.save(np_filename,results) + np.save(data_dir / np_filename,results) return results def compute_small_signal_parameters(filename,plot_results=True): @@ -188,7 +188,6 @@ def compute_small_signal_parameters(filename,plot_results=True): if __name__ == '__main__': run_characterization_sims(np_filename) - compute_small_signal_parameters(np_filename) diff --git a/scripts/empty b/scripts/empty new file mode 100644 index 0000000..e69de29 From 48871fd3cc9236c1e52771c11d256ab3e07e1d89 Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Thu, 5 Jan 2023 23:33:57 +0000 Subject: [PATCH 05/10] Black formatting. Add pre-commit and associated hook. --- .pre-commit-config.yaml | 6 + adc/database_query.py | 195 +- adc/strongarm.py | 172 +- adc/telescopic_cascode_diffamp_generator.py | 2192 +++++++++++-------- adc/tests/test_adc.py | 27 +- pyproject.toml | 1 + scripts/characterize_technology.py | 396 +++- 7 files changed, 1785 insertions(+), 1204 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..f7fac1f --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,6 @@ +repos: + - repo: https://github.com/psf/black + rev: 22.6.0 + hooks: + - id: black + language_version: "python3.10" \ No newline at end of file diff --git a/adc/database_query.py b/adc/database_query.py index 1069eb3..3448ed7 100644 --- a/adc/database_query.py +++ b/adc/database_query.py @@ -5,151 +5,171 @@ class MosDB: - _vars = ['gm', 'ids', 'gds', 'cgg', 'cdd'] + _vars = ["gm", "ids", "gds", "cgg", "cdd"] + def __init__(self): self._mos_list = [] self._mos_to_lch = {} self._map = {} def build(self, filename): - """ Given the filename, build the MOS Database """ + """Given the filename, build the MOS Database""" data = np.load(filename, allow_pickle=True)[()] - self._mos_list.extend(data['mos_list']) - mos = data['mos_list'][0] - lch_list = data['lch_list'] + self._mos_list.extend(data["mos_list"]) + mos = data["mos_list"][0] + lch_list = data["lch_list"] self._mos_to_lch[mos] = lch_list for lch_idx, lch in enumerate(lch_list): # Regardless of how the MOS was characterized # We make all of VGS/VDS/VBS positive - vgs_raw = np.abs(np.array(data['vgs_list'])) - vds_raw = np.abs(np.array(data['vds_list'])) - vbs_raw = np.abs(np.array(data['vbs_list'])) + vgs_raw = np.abs(np.array(data["vgs_list"])) + vds_raw = np.abs(np.array(data["vds_list"])) + vbs_raw = np.abs(np.array(data["vbs_list"])) for var in self._vars: var_raw = data[var][0, lch_idx, :, :, :] self._map[(mos, lch, var)] = interp.RegularGridInterpolator( - (vbs_raw, vgs_raw, vds_raw), var_raw) - self._map[(mos, lch, 'vgs_list')] = vgs_raw - self._map[(mos, lch, 'vds_list')] = vds_raw - self._map[(mos, lch, 'vbs_list')] = vbs_raw + (vbs_raw, vgs_raw, vds_raw), var_raw + ) + self._map[(mos, lch, "vgs_list")] = vgs_raw + self._map[(mos, lch, "vds_list")] = vds_raw + self._map[(mos, lch, "vbs_list")] = vbs_raw def query_db(self, varname, mos_type, lch): - """ Query the database for the given variable name """ + """Query the database for the given variable name""" return self._map[(mos_type, lch, varname)] - def query_db_for_vgs(self, mos_type, lch, vbs, vds, vstar=200e-3, ids=0, - mode='vstar', scale=1): - vgs_vals = self._map[(mos_type, lch, 'vgs_list')] + def query_db_for_vgs( + self, mos_type, lch, vbs, vds, vstar=200e-3, ids=0, mode="vstar", scale=1 + ): + vgs_vals = self._map[(mos_type, lch, "vgs_list")] vgs_min = min(vgs_vals) vgs_max = max(vgs_vals) - gm_func = self._map[(mos_type, lch, 'gm')] - id_func = self._map[(mos_type, lch, 'ids')] - if mode == 'vstar': - # Only search where the VGS is greater than the VGS + gm_func = self._map[(mos_type, lch, "gm")] + id_func = self._map[(mos_type, lch, "ids")] + if mode == "vstar": + # Only search where the VGS is greater than the VGS # at which vstar min was achieved vstar_min_vgs_idx = np.argmin( - 2*id_func((vbs, vgs_vals, vds))/gm_func((vbs, vgs_vals, vds))) + 2 * id_func((vbs, vgs_vals, vds)) / gm_func((vbs, vgs_vals, vds)) + ) vgs_min = vgs_vals[vstar_min_vgs_idx] + def vstar_func(vgs): return vstar - 2 * id_func((vbs, vgs, vds)) / gm_func((vbs, vgs, vds)) return opt.brentq(vstar_func, vgs_min, vgs_max) - - elif mode == 'ids': + + elif mode == "ids": + def ids_func(vgs): return ids - scale * id_func((vbs, vgs, vds)) - + return opt.brentq(ids_func, vgs_min, vgs_max) # Query the given database for the given variable name, for the given lch, vbs, vgs, vds -def query_db(database,varname,mos_type,lch,vbs,vgs,vds): - - mos_list = database['mos_list'] +def query_db(database, varname, mos_type, lch, vbs, vgs, vds): + + mos_list = database["mos_list"] mos_index = mos_list.index(mos_type) - lch_list = database['lch_list'] + lch_list = database["lch_list"] lch_index = lch_list.index(lch) - var_raw = database[varname][mos_index,lch_index,:,:,:] + var_raw = database[varname][mos_index, lch_index, :, :, :] if mos_type == "nch" or mos_type == "nch_lvt": - vgs_raw = np.array(database['vgs_list']) - vds_raw = np.array(database['vds_list']) - vbs_raw = -np.array(database['vbs_list']) + vgs_raw = np.array(database["vgs_list"]) + vds_raw = np.array(database["vds_list"]) + vbs_raw = -np.array(database["vbs_list"]) else: - vgs_raw = -np.array(database['vgs_list']) - vds_raw = -np.array(database['vds_list']) - vbs_raw = np.array(database['vbs_list']) - - interp = scipy.interpolate.RegularGridInterpolator((vbs_raw,vgs_raw,vds_raw),var_raw) - return interp([-vbs,vgs,vds]).item() + vgs_raw = -np.array(database["vgs_list"]) + vds_raw = -np.array(database["vds_list"]) + vbs_raw = np.array(database["vbs_list"]) + + interp = scipy.interpolate.RegularGridInterpolator( + (vbs_raw, vgs_raw, vds_raw), var_raw + ) + return interp([-vbs, vgs, vds]).item() # Specialized query function to find the vgs of a transistor if all other OP conditions are known. # It has two operation modes: - # 'vstar' mode (where vstar = 2 * ID / gm): Find the vgs of the device if its lch, vbs, vds and gm/ID are known - # 'ids' mode: Find the vgs of the device if its lch, vbs, vds and ids are known. -def query_db_for_vgs(database,mos_type,lch,vbs,vds,vstar=200e-3,ids=0,mode='vstar',scale=1): - mos_list = database['mos_list'] +# 'vstar' mode (where vstar = 2 * ID / gm): Find the vgs of the device if its lch, vbs, vds and gm/ID are known +# 'ids' mode: Find the vgs of the device if its lch, vbs, vds and ids are known. +def query_db_for_vgs( + database, mos_type, lch, vbs, vds, vstar=200e-3, ids=0, mode="vstar", scale=1 +): + mos_list = database["mos_list"] mos_index = mos_list.index(mos_type) - lch_list = database['lch_list'] + lch_list = database["lch_list"] lch_index = lch_list.index(lch) if mos_type == "nch" or mos_type == "nch_lvt": - vgs_raw = np.array(database['vgs_list']) - vds_raw = np.array(database['vds_list']) - vbs_raw = -np.array(database['vbs_list']) + vgs_raw = np.array(database["vgs_list"]) + vds_raw = np.array(database["vds_list"]) + vbs_raw = -np.array(database["vbs_list"]) else: - vgs_raw = -np.array(database['vgs_list']) - vds_raw = -np.array(database['vds_list']) - vbs_raw = np.array(database['vbs_list']) - - ids_raw = database['ids'][mos_index,lch_index,:,:,:]*scale - gm_raw = database['gm'][mos_index,lch_index,:,:,:]*scale - - if mode == 'vstar': - target_gm_id = 2/vstar - gm_id_raw = gm_raw/ids_raw - interp = scipy.interpolate.RegularGridInterpolator((vbs_raw,vgs_raw,vds_raw),gm_id_raw) - - resample_vector_vbs = [-vbs]*(np.shape(vgs_raw)[0]) - resample_vector_vds = [vds]*(np.shape(vgs_raw)[0]) - resample_points = np.transpose(np.vstack((resample_vector_vbs,vgs_raw,resample_vector_vds))) + vgs_raw = -np.array(database["vgs_list"]) + vds_raw = -np.array(database["vds_list"]) + vbs_raw = np.array(database["vbs_list"]) + + ids_raw = database["ids"][mos_index, lch_index, :, :, :] * scale + gm_raw = database["gm"][mos_index, lch_index, :, :, :] * scale + + if mode == "vstar": + target_gm_id = 2 / vstar + gm_id_raw = gm_raw / ids_raw + interp = scipy.interpolate.RegularGridInterpolator( + (vbs_raw, vgs_raw, vds_raw), gm_id_raw + ) + + resample_vector_vbs = [-vbs] * (np.shape(vgs_raw)[0]) + resample_vector_vds = [vds] * (np.shape(vgs_raw)[0]) + resample_points = np.transpose( + np.vstack((resample_vector_vbs, vgs_raw, resample_vector_vds)) + ) gm_id_raw_1d = interp(resample_points) - - interp_vgs = scipy.interpolate.interp1d(gm_id_raw_1d,vgs_raw) - + + interp_vgs = scipy.interpolate.interp1d(gm_id_raw_1d, vgs_raw) + return interp_vgs(target_gm_id).item() - - elif mode == 'ids': - interp = scipy.interpolate.RegularGridInterpolator((vbs_raw,vgs_raw,vds_raw),ids_raw) - - resample_vector_vbs = [-vbs]*(np.shape(vgs_raw)[0]) - resample_vector_vds = [vds]*(np.shape(vgs_raw)[0]) - resample_points = np.transpose(np.vstack((resample_vector_vbs,vgs_raw,resample_vector_vds))) + + elif mode == "ids": + interp = scipy.interpolate.RegularGridInterpolator( + (vbs_raw, vgs_raw, vds_raw), ids_raw + ) + + resample_vector_vbs = [-vbs] * (np.shape(vgs_raw)[0]) + resample_vector_vds = [vds] * (np.shape(vgs_raw)[0]) + resample_points = np.transpose( + np.vstack((resample_vector_vbs, vgs_raw, resample_vector_vds)) + ) ids_raw_1d = interp(resample_points) - - interp_vgs = scipy.interpolate.interp1d(ids_raw_1d,vgs_raw) - + + interp_vgs = scipy.interpolate.interp1d(ids_raw_1d, vgs_raw) + return interp_vgs(ids).item() # Query function that returns the function for the given variable instead of a single datapoint. # Useful for optimization kinda stuff where you iterate over functions. -def query_db_for_function(database,varname,mos_type,lch): - - mos_list = database['mos_list'] +def query_db_for_function(database, varname, mos_type, lch): + + mos_list = database["mos_list"] mos_index = mos_list.index(mos_type) - lch_list = database['lch_list'] + lch_list = database["lch_list"] lch_index = lch_list.index(lch) - - var_raw = database[varname][mos_index,lch_index,:,:,:] + + var_raw = database[varname][mos_index, lch_index, :, :, :] if mos_type == "nch" or mos_type == "nch_lvt": - vgs_raw = np.array(database['vgs_list']) - vds_raw = np.array(database['vds_list']) - vbs_raw = np.array(database['vbs_list']) + vgs_raw = np.array(database["vgs_list"]) + vds_raw = np.array(database["vds_list"]) + vbs_raw = np.array(database["vbs_list"]) else: - vgs_raw = -np.array(database['vgs_list']) - vds_raw = -np.array(database['vds_list']) - vbs_raw = -np.array(database['vbs_list']) + vgs_raw = -np.array(database["vgs_list"]) + vds_raw = -np.array(database["vds_list"]) + vbs_raw = -np.array(database["vbs_list"]) - interp = scipy.interpolate.RegularGridInterpolator((vbs_raw,vgs_raw,vds_raw),var_raw) + interp = scipy.interpolate.RegularGridInterpolator( + (vbs_raw, vgs_raw, vds_raw), var_raw + ) return interp @@ -157,4 +177,3 @@ def query_db_for_function(database,varname,mos_type,lch): nch_lvt_db_filename = "database_nch_lvt.npy" pch_db_filename = "database_pch.npy" pch_lvt_db_filename = "database_pch_lvt.npy" - diff --git a/adc/strongarm.py b/adc/strongarm.py index c5b18c6..c304042 100644 --- a/adc/strongarm.py +++ b/adc/strongarm.py @@ -11,7 +11,7 @@ from hdl21.prefix import m, µ, f, n, PICO from hdl21.primitives import Vdc, Idc, C, Vpulse from vlsirtools.spice import SimOptions, SupportedSimulators, ResultFormat -import sitepdks, sky130 +import sitepdks, sky130 sim_options = SimOptions( rundir=Path("./scratch"), @@ -72,13 +72,27 @@ class MosParams: nch = h.ExternalModule( - name="sky130_fd_pr__nfet_01v8", desc="Sky130 NMOS", - port_list=[h.Inout(name="d"), h.Inout(name="g"), h.Inout(name="s"), h.Inout(name="b")], - paramtype=MosParams) + name="sky130_fd_pr__nfet_01v8", + desc="Sky130 NMOS", + port_list=[ + h.Inout(name="d"), + h.Inout(name="g"), + h.Inout(name="s"), + h.Inout(name="b"), + ], + paramtype=MosParams, +) pch = h.ExternalModule( - name="sky130_fd_pr__pfet_01v8", desc="Sky130 PMOS", - port_list=[h.Inout(name="d"), h.Inout(name="g"), h.Inout(name="s"), h.Inout(name="b")], - paramtype=MosParams) + name="sky130_fd_pr__pfet_01v8", + desc="Sky130 PMOS", + port_list=[ + h.Inout(name="d"), + h.Inout(name="g"), + h.Inout(name="s"), + h.Inout(name="b"), + ], + paramtype=MosParams, +) @h.paramclass @@ -96,6 +110,7 @@ class Nor2: """# Nor2 for SR Latch Inputs `i` and `fb` are designated for input and feedback respectively. The feedback input is the faster of the two.""" + VDD, VSS = h.Ports(2) i, fb = h.Inputs(2) z = h.Output() @@ -104,6 +119,7 @@ class Nor2: pfb = pch(params.nor_pfb)(g=fb, d=z, s=pi.d, b=VDD) nfb = nch(params.nor_nfb)(g=fb, d=z, s=VSS, b=VSS) ni = nch(params.nor_ni)(g=i, d=z, s=VSS, b=VSS) + return Nor2 @@ -117,7 +133,7 @@ class SrLatch: norp = nor2(params)(i=inp.p, z=out.n, fb=out.p, VDD=VDD, VSS=VSS) norn = nor2(params)(i=inp.n, z=out.p, fb=out.n, VDD=VDD, VSS=VSS) - + return SrLatch @@ -129,20 +145,21 @@ class StrongarmParams: inv_p = h.Param(dtype=MosParams, desc="Inverter PMos params") reset = h.Param(dtype=MosParams, desc="Reset Device params") meas_vs = h.Param( - dtype=bool, - desc="True to add voltage sources to measure device currents") + dtype=bool, desc="True to add voltage sources to measure device currents" + ) @h.generator def strongarm(params: StrongarmParams) -> h.Module: if params.meas_vs: + @h.module class StrongArm: VDD, VSS = h.Ports(2) inp = Diff(port=True, role=Diff.Roles.SINK) out = Diff(port=True, role=Diff.Roles.SOURCE) clk = h.Input() - + tail_pre = h.Signal() mid_pre = Diff() inv_n_pre = Diff() @@ -171,9 +188,10 @@ class StrongArm: prstn = pch(params.reset)(g=clk, d=out.n, s=VDD, b=VDD) prstp2 = pch(params.reset)(g=clk, d=ninp_meas.p, s=VDD, b=VDD) prstn2 = pch(params.reset)(g=clk, d=ninn_meas.p, s=VDD, b=VDD) - + return StrongArm else: + @h.module class StrongArm: VDD, VSS = h.Ports(2) @@ -194,7 +212,7 @@ class StrongArm: ## Reset pch prstp = pch(params.reset)(g=clk, d=out.p, s=VDD, b=VDD) prstn = pch(params.reset)(g=clk, d=out.n, s=VDD, b=VDD) - + return StrongArm @@ -208,21 +226,21 @@ class ComparatorParams: def comparator(params: ComparatorParams) -> h.Module: @h.module class Comparator: - """# StrongArm Based Comparator """ + """# StrongArm Based Comparator""" + VDD, VSS = h.Ports(2) inp = Diff(port=True, role=Diff.Roles.SINK) out = Diff(port=True, role=Diff.Roles.SOURCE) clk = h.Input() sout = Diff() - - sa = strongarm(params.strongarm)( - inp=inp, out=sout, clk=clk, VDD=VDD, VSS=VSS) + + sa = strongarm(params.strongarm)(inp=inp, out=sout, clk=clk, VDD=VDD, VSS=VSS) sr = sr_latch(params.latch)(inp=sout, out=out, VDD=VDD, VSS=VSS) return Comparator - - + + """ # Comparator Tests """ @@ -285,12 +303,7 @@ def ComparatorTb(p: TbParams) -> h.Module: tb.cln = Cload(p=tb.out.n, n=tb.VSS) # Create the Slicer DUT - tb.dut = comparator(p.dut)( - inp=tb.inp, - out=tb.out, - clk=clk, - VDD=VDD, - VSS=tb.VSS) + tb.dut = comparator(p.dut)(inp=tb.inp, out=tb.out, clk=clk, VDD=VDD, VSS=tb.VSS) return tb @@ -302,17 +315,20 @@ def test_comparator_sim(): nf = 2 comparator_params = ComparatorParams( strongarm=StrongarmParams( - tail=MosParams(w=w*nf*2, l=l, nf=2*nf), - inp_pair=MosParams(w=w*nf, l=l, nf=nf), - inv_n=MosParams(w=w*nf, l=l, nf=nf), - inv_p=MosParams(w=w*nf, l=l, nf=nf), - reset=MosParams(w=w*nf*2, l=l, nf=2*nf), - meas_vs=True), + tail=MosParams(w=w * nf * 2, l=l, nf=2 * nf), + inp_pair=MosParams(w=w * nf, l=l, nf=nf), + inv_n=MosParams(w=w * nf, l=l, nf=nf), + inv_p=MosParams(w=w * nf, l=l, nf=nf), + reset=MosParams(w=w * nf * 2, l=l, nf=2 * nf), + meas_vs=True, + ), latch=LatchParams( - nor_pi=MosParams(w=w*nf, l=l, nf=nf), - nor_pfb=MosParams(w=w*nf, l=l, nf=nf), - nor_ni=MosParams(w=w*nf, l=l, nf=nf), - nor_nfb=MosParams(w=w*nf, l=l, nf=nf))) + nor_pi=MosParams(w=w * nf, l=l, nf=nf), + nor_pfb=MosParams(w=w * nf, l=l, nf=nf), + nor_ni=MosParams(w=w * nf, l=l, nf=nf), + nor_nfb=MosParams(w=w * nf, l=l, nf=nf), + ), + ) # Create our parametric testbench params = TbParams(pvt=Pvt(), vc=900 * m, vd=1 * m, dut=comparator_params) @@ -320,79 +336,85 @@ def test_comparator_sim(): @hs.sim class ComparatorSim: tb = ComparatorTb(params) - tr = hs.Tran(tstop=12 * n, tstep=100*PICO) + tr = hs.Tran(tstop=12 * n, tstep=100 * PICO) # Add the PDK dependencies - ComparatorSim.lib(sky130.install.model_lib, 'tt') + ComparatorSim.lib(sky130.install.model_lib, "tt") ComparatorSim.literal(".option METHOD=Gear") # Run Spice, save important results results = ComparatorSim.run(sim_options) tran_results = results.an[0].data - np.savez('strongarm_results.npz', - t = tran_results['time'], - v_out_diff = tran_results['v(xtop.out_p)'] - tran_results['v(xtop.out_n)'], - v_in_diff = tran_results['v(xtop.inp_p)'] - tran_results['v(xtop.inp_n)'], - v_clk = tran_results['v(xtop.clk)'], - i_tail = tran_results['i(v.xtop.xdut.xsa.vtail_meas)'], - i_inp_pair_cm = tran_results['i(v.xtop.xdut.xsa.vninp_meas)'] +\ - tran_results['i(v.xtop.xdut.xsa.vninn_meas)'], - i_latch_n_pair_cm = tran_results['i(v.xtop.xdut.xsa.vnlatn_meas)'] +\ - tran_results['i(v.xtop.xdut.xsa.vnlatp_meas)'], - i_latch_p_pair_cm = tran_results['i(v.xtop.xdut.xsa.vplatn_meas)'] +\ - tran_results['i(v.xtop.xdut.xsa.vplatp_meas)'], - v_casc_cm = (tran_results['v(xtop.xdut.xsa.ninn_meas_p)'] + - tran_results['v(xtop.xdut.xsa.ninp_meas_p)']) / 2, - v_out_cm = (tran_results['v(xtop.xdut.sout_p)'] + - tran_results['v(xtop.xdut.sout_n)']) / 2, + np.savez( + "strongarm_results.npz", + t=tran_results["time"], + v_out_diff=tran_results["v(xtop.out_p)"] - tran_results["v(xtop.out_n)"], + v_in_diff=tran_results["v(xtop.inp_p)"] - tran_results["v(xtop.inp_n)"], + v_clk=tran_results["v(xtop.clk)"], + i_tail=tran_results["i(v.xtop.xdut.xsa.vtail_meas)"], + i_inp_pair_cm=tran_results["i(v.xtop.xdut.xsa.vninp_meas)"] + + tran_results["i(v.xtop.xdut.xsa.vninn_meas)"], + i_latch_n_pair_cm=tran_results["i(v.xtop.xdut.xsa.vnlatn_meas)"] + + tran_results["i(v.xtop.xdut.xsa.vnlatp_meas)"], + i_latch_p_pair_cm=tran_results["i(v.xtop.xdut.xsa.vplatn_meas)"] + + tran_results["i(v.xtop.xdut.xsa.vplatp_meas)"], + v_casc_cm=( + tran_results["v(xtop.xdut.xsa.ninn_meas_p)"] + + tran_results["v(xtop.xdut.xsa.ninp_meas_p)"] + ) + / 2, + v_out_cm=( + tran_results["v(xtop.xdut.sout_p)"] + tran_results["v(xtop.xdut.sout_n)"] + ) + / 2, ) def extract_windows(t, clk, threshold): - """ Given the clock waveform, this will extract a bunch of single periods - that are the periods at which a certain clock starts and ends """ + """Given the clock waveform, this will extract a bunch of single periods + that are the periods at which a certain clock starts and ends""" clk_above_thres_idcs = np.where(clk > threshold)[0] print(clk_above_thres_idcs.shape) print(np.diff(clk_above_thres_idcs)) - rising_cross_idcs = clk_above_thres_idcs[np.where( - np.diff(np.concatenate( - ([0], clk_above_thres_idcs), axis=0)) > 1)[0]] + rising_cross_idcs = clk_above_thres_idcs[ + np.where(np.diff(np.concatenate(([0], clk_above_thres_idcs), axis=0)) > 1)[0] + ] rising_cross_idcs = rising_cross_idcs.tolist() + [len(clk)] - return [(s,e) for s, e in zip(rising_cross_idcs[:-1], rising_cross_idcs[1:])] + return [(s, e) for s, e in zip(rising_cross_idcs[:-1], rising_cross_idcs[1:])] def plot_windows(): - data = np.load('strongarm_results.npz') - windows = extract_windows(data['t'], data['v_clk'], 0.7) + data = np.load("strongarm_results.npz") + windows = extract_windows(data["t"], data["v_clk"], 0.7) for (s, e) in windows: plt.figure() - plt.plot(data['t'][s:e], data['v_clk'][s:e]) + plt.plot(data["t"][s:e], data["v_clk"][s:e]) plt.show() def plot_data(): - data = np.load('strongarm_results.npz') - t = data['t'] + data = np.load("strongarm_results.npz") + t = data["t"] fig, ax = plt.subplots(3, sharex=True) - ax[0].plot(t, data['v_clk']) - ax[1].plot(t, data['v_in_diff']) - ax[2].plot(t, data['v_out_diff']) + ax[0].plot(t, data["v_clk"]) + ax[1].plot(t, data["v_in_diff"]) + ax[2].plot(t, data["v_out_diff"]) fig, ax = plt.subplots(3, sharex=True) - ax[0].plot(t, data['v_clk']) - ax[1].plot(t, data['i_tail'], label='tail current') - ax[1].plot(t, data['i_inp_pair_cm'], label='input pair current') - ax[1].plot(t, data['i_latch_n_pair_cm'], label='Latch NMOS current') - ax[1].plot(t, data['i_latch_p_pair_cm'], label='Latch PMOS current') + ax[0].plot(t, data["v_clk"]) + ax[1].plot(t, data["i_tail"], label="tail current") + ax[1].plot(t, data["i_inp_pair_cm"], label="input pair current") + ax[1].plot(t, data["i_latch_n_pair_cm"], label="Latch NMOS current") + ax[1].plot(t, data["i_latch_p_pair_cm"], label="Latch PMOS current") ax[1].legend() - ax[2].plot(t, data['v_casc_cm'], label='v_casc_cm') - ax[2].plot(t, data['v_out_cm'], label='v_out_cm') + ax[2].plot(t, data["v_casc_cm"], label="v_casc_cm") + ax[2].plot(t, data["v_out_cm"], label="v_out_cm") ax[2].legend() plt.show() breakpoint() -if __name__ == '__main__': +if __name__ == "__main__": # test_comparator_sim() # plot_data() plot_windows() diff --git a/adc/telescopic_cascode_diffamp_generator.py b/adc/telescopic_cascode_diffamp_generator.py index 5eebe0a..a8c3fec 100644 --- a/adc/telescopic_cascode_diffamp_generator.py +++ b/adc/telescopic_cascode_diffamp_generator.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- # Two-stage Amplifier Generator: - # First stage is a telescopic cascode - # Second stage is a common source amplifier +# First stage is a telescopic cascode +# Second stage is a common source amplifier import pprint @@ -34,6 +34,7 @@ pch_lvt = sky130.modules.sky130_fd_pr__pfet_01v8_lvt import nest_asyncio + nest_asyncio.apply() sim_options = SimOptions( @@ -44,334 +45,406 @@ # Query the given database for the given variable name, for the given lch, vbs, vgs, vds -def query_db(database,varname,mos_type,lch,vbs,vgs,vds): - - mos_list = database['mos_list'] +def query_db(database, varname, mos_type, lch, vbs, vgs, vds): + + mos_list = database["mos_list"] mos_index = mos_list.index(mos_type) - lch_list = database['lch_list'] + lch_list = database["lch_list"] lch_index = lch_list.index(lch) - var_raw = database[varname][mos_index,lch_index,:,:,:] + var_raw = database[varname][mos_index, lch_index, :, :, :] if mos_type == "nch" or mos_type == "nch_lvt": - vgs_raw = np.array(database['vgs_list']) - vds_raw = np.array(database['vds_list']) - vbs_raw = np.array(database['vbs_list']) + vgs_raw = np.array(database["vgs_list"]) + vds_raw = np.array(database["vds_list"]) + vbs_raw = np.array(database["vbs_list"]) else: - vgs_raw = -np.array(database['vgs_list']) - vds_raw = -np.array(database['vds_list']) - vbs_raw = -np.array(database['vbs_list']) - - interp = scipy.interpolate.RegularGridInterpolator((vbs_raw,vgs_raw,vds_raw),var_raw) - return interp([vbs,vgs,vds]).item() + vgs_raw = -np.array(database["vgs_list"]) + vds_raw = -np.array(database["vds_list"]) + vbs_raw = -np.array(database["vbs_list"]) + + interp = scipy.interpolate.RegularGridInterpolator( + (vbs_raw, vgs_raw, vds_raw), var_raw + ) + return interp([vbs, vgs, vds]).item() + # Specialized query function to find the vgs of a transistor if all other OP conditions are known. # It has two operation modes: - # 'vstar' mode (where vstar = 2 * ID / gm): Find the vgs of the device if its lch, vbs, vds and gm/ID are known - # 'ids' mode: Find the vgs of the device if its lch, vbs, vds and ids are known. -def query_db_for_vgs(database,mos_type,lch,vbs,vds,vstar=200e-3,ids=0,mode='vstar',scale=1): - mos_list = database['mos_list'] +# 'vstar' mode (where vstar = 2 * ID / gm): Find the vgs of the device if its lch, vbs, vds and gm/ID are known +# 'ids' mode: Find the vgs of the device if its lch, vbs, vds and ids are known. +def query_db_for_vgs( + database, mos_type, lch, vbs, vds, vstar=200e-3, ids=0, mode="vstar", scale=1 +): + mos_list = database["mos_list"] mos_index = mos_list.index(mos_type) - lch_list = database['lch_list'] + lch_list = database["lch_list"] lch_index = lch_list.index(lch) if mos_type == "nch" or mos_type == "nch_lvt": - vgs_raw = np.array(database['vgs_list']) - vds_raw = np.array(database['vds_list']) - vbs_raw = np.array(database['vbs_list']) + vgs_raw = np.array(database["vgs_list"]) + vds_raw = np.array(database["vds_list"]) + vbs_raw = np.array(database["vbs_list"]) else: - vgs_raw = -np.array(database['vgs_list']) - vds_raw = -np.array(database['vds_list']) - vbs_raw = -np.array(database['vbs_list']) - - ids_raw = database['ids'][mos_index,lch_index,:,:,:]*scale - gm_raw = database['gm'][mos_index,lch_index,:,:,:]*scale - - if mode == 'vstar': - target_gm_id = 2/vstar - gm_id_raw = gm_raw/ids_raw - interp = scipy.interpolate.RegularGridInterpolator((vbs_raw,vgs_raw,vds_raw),gm_id_raw) - - resample_vector_vbs = [vbs]*(np.shape(vgs_raw)[0]) - resample_vector_vds = [vds]*(np.shape(vgs_raw)[0]) - resample_points = np.transpose(np.vstack((resample_vector_vbs,vgs_raw,resample_vector_vds))) + vgs_raw = -np.array(database["vgs_list"]) + vds_raw = -np.array(database["vds_list"]) + vbs_raw = -np.array(database["vbs_list"]) + + ids_raw = database["ids"][mos_index, lch_index, :, :, :] * scale + gm_raw = database["gm"][mos_index, lch_index, :, :, :] * scale + + if mode == "vstar": + target_gm_id = 2 / vstar + gm_id_raw = gm_raw / ids_raw + interp = scipy.interpolate.RegularGridInterpolator( + (vbs_raw, vgs_raw, vds_raw), gm_id_raw + ) + + resample_vector_vbs = [vbs] * (np.shape(vgs_raw)[0]) + resample_vector_vds = [vds] * (np.shape(vgs_raw)[0]) + resample_points = np.transpose( + np.vstack((resample_vector_vbs, vgs_raw, resample_vector_vds)) + ) gm_id_raw_1d = interp(resample_points) - - interp_vgs = scipy.interpolate.interp1d(gm_id_raw_1d,vgs_raw) - + + interp_vgs = scipy.interpolate.interp1d(gm_id_raw_1d, vgs_raw) + try: return interp_vgs(target_gm_id).item() except: raise ValueError - - elif mode == 'ids': - interp = scipy.interpolate.RegularGridInterpolator((vbs_raw,vgs_raw,vds_raw),ids_raw) - - resample_vector_vbs = [vbs]*(np.shape(vgs_raw)[0]) - resample_vector_vds = [vds]*(np.shape(vgs_raw)[0]) - resample_points = np.transpose(np.vstack((resample_vector_vbs,vgs_raw,resample_vector_vds))) + + elif mode == "ids": + interp = scipy.interpolate.RegularGridInterpolator( + (vbs_raw, vgs_raw, vds_raw), ids_raw + ) + + resample_vector_vbs = [vbs] * (np.shape(vgs_raw)[0]) + resample_vector_vds = [vds] * (np.shape(vgs_raw)[0]) + resample_points = np.transpose( + np.vstack((resample_vector_vbs, vgs_raw, resample_vector_vds)) + ) ids_raw_1d = interp(resample_points) - - interp_vgs = scipy.interpolate.interp1d(ids_raw_1d,vgs_raw) - + + interp_vgs = scipy.interpolate.interp1d(ids_raw_1d, vgs_raw) + try: return interp_vgs(ids).item() except: raise ValueError + # Query function that returns the function for the given variable instead of a single datapoint. # Useful for optimization kinda stuff where you iterate over functions. -def query_db_for_function(database,varname,mos_type,lch): - - mos_list = database['mos_list'] +def query_db_for_function(database, varname, mos_type, lch): + + mos_list = database["mos_list"] mos_index = mos_list.index(mos_type) - lch_list = database['lch_list'] + lch_list = database["lch_list"] lch_index = lch_list.index(lch) - - var_raw = database[varname][mos_index,lch_index,:,:,:] + + var_raw = database[varname][mos_index, lch_index, :, :, :] if mos_type == "nch" or mos_type == "nch_lvt": - vgs_raw = np.array(database['vgs_list']) - vds_raw = np.array(database['vds_list']) - vbs_raw = np.array(database['vbs_list']) + vgs_raw = np.array(database["vgs_list"]) + vds_raw = np.array(database["vds_list"]) + vbs_raw = np.array(database["vbs_list"]) else: - vgs_raw = -np.array(database['vgs_list']) - vds_raw = -np.array(database['vds_list']) - vbs_raw = -np.array(database['vbs_list']) + vgs_raw = -np.array(database["vgs_list"]) + vds_raw = -np.array(database["vds_list"]) + vbs_raw = -np.array(database["vbs_list"]) - interp = scipy.interpolate.RegularGridInterpolator((vbs_raw,vgs_raw,vds_raw),var_raw) + interp = scipy.interpolate.RegularGridInterpolator( + (vbs_raw, vgs_raw, vds_raw), var_raw + ) return interp - -# Function that designs the input stage (input & cascode devices) of the telescopic amplifier. + + +# Function that designs the input stage (input & cascode devices) of the telescopic amplifier. # The goal is to find the highest gain-bandwidth product operating point for this stage. -def stage1_telescopic_amplifier_design_input(database,amp_specs,gen_params): +def stage1_telescopic_amplifier_design_input(database, amp_specs, gen_params): """Find operating point that meets the given vstar (2*ID/gm) spec, while maximizing the gain-bandwidth product of the input stage.""" - vdd = amp_specs['vdd'] - voutcm = amp_specs['voutcm'] - vincm = amp_specs['vincm'] - vstar_in = amp_specs['vstar_in'] - vdst_min = amp_specs['vds_tail_min'] - vds_sweep_res = gen_params['vds_sweep_res'] - casc_scale_max = gen_params['casc_scale_max'] - casc_scale_min = gen_params['casc_scale_min'] - casc_scale_step = gen_params['casc_scale_step'] - in_type = amp_specs['in_type'] - lch_in = gen_params['lch_in'] + vdd = amp_specs["vdd"] + voutcm = amp_specs["voutcm"] + vincm = amp_specs["vincm"] + vstar_in = amp_specs["vstar_in"] + vdst_min = amp_specs["vds_tail_min"] + vds_sweep_res = gen_params["vds_sweep_res"] + casc_scale_max = gen_params["casc_scale_max"] + casc_scale_min = gen_params["casc_scale_min"] + casc_scale_step = gen_params["casc_scale_step"] + in_type = amp_specs["in_type"] + lch_in = gen_params["lch_in"] # We will sweep the cascode scale, so initialize the sweep list - num_casc_scale_points = int(np.ceil((casc_scale_max - casc_scale_min) / casc_scale_step)) + 1 - casc_scale_list = np.linspace(casc_scale_min, casc_scale_max, num_casc_scale_points, endpoint=True) - + num_casc_scale_points = ( + int(np.ceil((casc_scale_max - casc_scale_min) / casc_scale_step)) + 1 + ) + casc_scale_list = np.linspace( + casc_scale_min, casc_scale_max, num_casc_scale_points, endpoint=True + ) + # Take care of input-type-specific variables - if in_type == 'nch' or in_type == 'nch_lvt': + if in_type == "nch" or in_type == "nch_lvt": vb = 0 vtail_lim = vdst_min vds_in_lim_0 = vstar_in - vds_in_lim_1 = (voutcm-vtail_lim)*2/3 + vds_in_lim_1 = (voutcm - vtail_lim) * 2 / 3 num_vds_points = int(np.ceil((vds_in_lim_1 - vds_in_lim_0) / vds_sweep_res)) + 1 - vds_in_val_list = np.linspace(vds_in_lim_0, vds_in_lim_1, num_vds_points, endpoint=True) - + vds_in_val_list = np.linspace( + vds_in_lim_0, vds_in_lim_1, num_vds_points, endpoint=True + ) + else: vb = vdd vtail_lim = vb - vdst_min vds_in_lim_0 = -vstar_in - vds_in_lim_1 = (voutcm-vtail_lim)*2/3 + vds_in_lim_1 = (voutcm - vtail_lim) * 2 / 3 num_vds_points = int(np.ceil((vds_in_lim_0 - vds_in_lim_1) / vds_sweep_res)) + 1 - vds_in_val_list = np.linspace(vds_in_lim_0, vds_in_lim_1, num_vds_points, endpoint=True) - + vds_in_val_list = np.linspace( + vds_in_lim_0, vds_in_lim_1, num_vds_points, endpoint=True + ) + # Initialize the metric, given that it being 0 is bad. The metric can be anything, but # in this generator, it is defined to be the gain-bandwidth of the input stage. metric_best = 0 - + # We have to start somewhere, we will sweep the vds of the input device since that should # be something we probably are not as sensitive to (compared to, say, the vgs of the input device) for vds_in in vds_in_val_list: - + # First iteration to find approximate vgs (and hence vsource) to account for vbs later vcasc_mid = vdst_min + vds_in vsource = vdst_min try: vbs_in = vb - vsource - vgs_in = query_db_for_vgs(database, in_type, lch_in, vbs_in, vds_in, vstar=vstar_in) + vgs_in = query_db_for_vgs( + database, in_type, lch_in, vbs_in, vds_in, vstar=vstar_in + ) except ValueError: - continue + continue vsource = vincm - vgs_in vcasc_mid = vsource + vds_in - + # Second iteration, for more exact result with approximately the correct VBS try: vbs_in = vb - vsource - vgs_in = query_db_for_vgs(database, in_type, lch_in, vbs_in, vds_in, vstar=vstar_in) + vgs_in = query_db_for_vgs( + database, in_type, lch_in, vbs_in, vds_in, vstar=vstar_in + ) except ValueError: continue - + # From the more accurate vgs, we can find a whole bunch of new operating points vsource = vincm - vgs_in vcasc_mid = vsource + vds_in vbs_in = vb - vsource vds_in = vcasc_mid - vsource - ids_in = query_db(database,'ids',in_type,lch_in,vbs_in,vgs_in,vds_in) - vbs_casc = (vb - vcasc_mid) - vds_casc = (voutcm - vcasc_mid) - vds_in = (vcasc_mid - vsource) - gm_in = query_db(database,'gm',in_type,lch_in,vbs_in,vgs_in,vds_in) - + ids_in = query_db(database, "ids", in_type, lch_in, vbs_in, vgs_in, vds_in) + vbs_casc = vb - vcasc_mid + vds_casc = voutcm - vcasc_mid + vds_in = vcasc_mid - vsource + gm_in = query_db(database, "gm", in_type, lch_in, vbs_in, vgs_in, vds_in) + # Now we'll sweep the cascode scale list to find the optimum value for that. # More intelligent search algorithms will actually converge to the most optimal cascode scale. for casc_scale in casc_scale_list: - ids_casc = ids_in/casc_scale - + ids_casc = ids_in / casc_scale + # Find the cascode vgs since we know its current, vbs and vds - try: - vgs_casc = query_db_for_vgs(database=database, mos_type = in_type, lch = lch_in, vbs = vbs_casc, vds = vds_casc, ids=ids_casc, mode='ids') + try: + vgs_casc = query_db_for_vgs( + database=database, + mos_type=in_type, + lch=lch_in, + vbs=vbs_casc, + vds=vds_casc, + ids=ids_casc, + mode="ids", + ) except: continue - + # If cascode gate is an unrealizable voltage within the rails, just move on vg_casc = vcasc_mid + vgs_casc if vg_casc > vdd or vg_casc < 0: continue - - gm_casc = query_db(database,'gm',in_type,lch_in,vbs_casc,vgs_casc,vds_casc)*casc_scale - gds_casc = query_db(database,'gds',in_type,lch_in,vbs_casc,vgs_casc,vds_casc)*casc_scale - gds_base = query_db(database,'gds',in_type,lch_in,vbs_in,vgs_in,vds_in) - - gds_in = (gds_base * gds_casc / (gds_base + gds_casc + gm_casc)) + + gm_casc = ( + query_db(database, "gm", in_type, lch_in, vbs_casc, vgs_casc, vds_casc) + * casc_scale + ) + gds_casc = ( + query_db(database, "gds", in_type, lch_in, vbs_casc, vgs_casc, vds_casc) + * casc_scale + ) + gds_base = query_db( + database, "gds", in_type, lch_in, vbs_in, vgs_in, vds_in + ) + + gds_in = gds_base * gds_casc / (gds_base + gds_casc + gm_casc) Av_cur = gm_in / gds_in - - cgg_casc = query_db(database, 'cgg',in_type,lch_in,vbs_casc,vgs_casc,vds_casc)*casc_scale - cgg_in = query_db(database,'cgg',in_type,lch_in,vbs_in,vgs_in,vds_in) - - cdd_casc = cgg_casc * gen_params['cdd_cgg_ratio'] - + + cgg_casc = ( + query_db(database, "cgg", in_type, lch_in, vbs_casc, vgs_casc, vds_casc) + * casc_scale + ) + cgg_in = query_db(database, "cgg", in_type, lch_in, vbs_in, vgs_in, vds_in) + + cdd_casc = cgg_casc * gen_params["cdd_cgg_ratio"] + bw_cur = gds_in / cdd_casc metric_cur = Av_cur * bw_cur - + # If we meet the gain spec AND we exceed the best GBW product, then save this op as the best - if Av_cur > (amp_specs['input_stage_gain_min']) and metric_cur > metric_best: + if ( + Av_cur > (amp_specs["input_stage_gain_min"]) + and metric_cur > metric_best + ): metric_best = metric_cur Av_best = Av_cur vgs_best = vgs_in casc_scale_best = casc_scale input_op = dict( - Av = Av_cur, - vgs = vgs_in, - casc_scale = casc_scale, - casc_bias = vg_casc, - vin_mid = vcasc_mid, - ibias = ids_in, - gm_in = gm_in, - gm_casc = gm_casc, - gds_base = gds_in, - gds_casc = gds_casc, - gds_in = gds_in, - cdd_in = cgg_casc * gen_params['cdd_cgg_ratio'], - cgg_casc = cgg_casc, - cgg_base = cgg_in, - vtail = vsource, - ) - + Av=Av_cur, + vgs=vgs_in, + casc_scale=casc_scale, + casc_bias=vg_casc, + vin_mid=vcasc_mid, + ibias=ids_in, + gm_in=gm_in, + gm_casc=gm_casc, + gds_base=gds_in, + gds_casc=gds_casc, + gds_in=gds_in, + cdd_in=cgg_casc * gen_params["cdd_cgg_ratio"], + cgg_casc=cgg_casc, + cgg_base=cgg_in, + vtail=vsource, + ) + print("New Av Best = %f" % (Av_best)) print("Updated VGS Best = %f" % (vgs_best)) print("Updated Casc Scale Best = %f" % (casc_scale_best)) - + print("--------------------------------") print("Input Stage Design:") print("Av Best = %f" % (Av_best)) print("VGS Best = %f" % (vgs_best)) print("Casc Scale Best = %f" % (casc_scale_best)) - print("VDS_in Best = %f" % (input_op['vin_mid'] - input_op['vtail'])) - print("VDS_casc Best = %f" % (voutcm - input_op['vin_mid'])) + print("VDS_in Best = %f" % (input_op["vin_mid"] - input_op["vtail"])) + print("VDS_casc Best = %f" % (voutcm - input_op["vin_mid"])) print("--------------------------------") - + return input_op -# Function that designs the load stage (load current source & cascode devices) of the telescopic amplifier. + +# Function that designs the load stage (load current source & cascode devices) of the telescopic amplifier. # The goal is to find the highest gain-bandwidth-product-per-µA operating point for this stage. -def stage1_telescopic_amplifier_design_load(database,amp_specs,gen_params,input_op): +def stage1_telescopic_amplifier_design_load(database, amp_specs, gen_params, input_op): """Design load. Sweep vgs. For each vgs, compute gain and max bandwidth. If both gain and BW specs are met, pick operating point that maximizes gamma_r * gm_r """ - vdd = amp_specs['vdd'] - vstar_in = amp_specs['vstar_in'] - voutcm = amp_specs['voutcm'] - vgs_res = gen_params['vgs_sweep_res'] - gain_min = amp_specs['gain_min'] - bw_min = max(amp_specs['selfbw_min'],amp_specs['bw_min']) - casc_scale_max = gen_params['casc_scale_max'] - casc_scale_step = gen_params['casc_scale_step'] - casc_bias_step = gen_params['casc_bias_step'] - vds_sweep_res = gen_params['vds_sweep_res'] - in_type = amp_specs['in_type'] - load_type = amp_specs['load_type'] - lch_load = gen_params['lch_load'] + vdd = amp_specs["vdd"] + vstar_in = amp_specs["vstar_in"] + voutcm = amp_specs["voutcm"] + vgs_res = gen_params["vgs_sweep_res"] + gain_min = amp_specs["gain_min"] + bw_min = max(amp_specs["selfbw_min"], amp_specs["bw_min"]) + casc_scale_max = gen_params["casc_scale_max"] + casc_scale_step = gen_params["casc_scale_step"] + casc_bias_step = gen_params["casc_bias_step"] + vds_sweep_res = gen_params["vds_sweep_res"] + in_type = amp_specs["in_type"] + load_type = amp_specs["load_type"] + lch_load = gen_params["lch_load"] best_load_op = None metric_best = 0 gain_max = 0 bw_max = 0 - - casc_scale_list = np.arange(1, casc_scale_max + casc_scale_step / 2, casc_scale_step) - if in_type == 'nch' or in_type == 'nch_lvt': + + casc_scale_list = np.arange( + 1, casc_scale_max + casc_scale_step / 2, casc_scale_step + ) + if in_type == "nch" or in_type == "nch_lvt": vs = vdd vb = vdd casc_bias_list = np.arange(0.5, voutcm + casc_bias_step / 2, casc_bias_step) vgs_base_max = -0.1 vgs_base_min = -1.5 vds_base_lim_0 = -vstar_in - vds_base_lim_1 = (voutcm-vdd)*2/3 - num_vds_points = int(np.ceil((vds_base_lim_0 - vds_base_lim_1) / vds_sweep_res)) + 1 - vds_base_val_list = np.linspace(vds_base_lim_0, vds_base_lim_1, num_vds_points, endpoint=True) + vds_base_lim_1 = (voutcm - vdd) * 2 / 3 + num_vds_points = ( + int(np.ceil((vds_base_lim_0 - vds_base_lim_1) / vds_sweep_res)) + 1 + ) + vds_base_val_list = np.linspace( + vds_base_lim_0, vds_base_lim_1, num_vds_points, endpoint=True + ) else: vs = 0 vb = 0 - casc_bias_list = np.arange(voutcm, vdd - 0.5 + casc_bias_step / 2, casc_bias_step) + casc_bias_list = np.arange( + voutcm, vdd - 0.5 + casc_bias_step / 2, casc_bias_step + ) vgs_base_min = 0.1 vgs_base_max = 1.5 vds_base_lim_0 = vstar_in - vds_base_lim_1 = (voutcm)*2/3 - num_vds_points = int(np.ceil((vds_base_lim_1 - vds_base_lim_0) / vds_sweep_res)) + 1 - vds_base_val_list = np.linspace(vds_base_lim_0, vds_base_lim_1, num_vds_points, endpoint=True) - - for lch_load in gen_params['lch_load']: - gm_fun = query_db_for_function(database,'gm',load_type,lch_load) - gds_fun = query_db_for_function(database,'gds',load_type,lch_load) - cgg_fun = query_db_for_function(database,'cgg',load_type,lch_load) - gamma = gen_params['gamma'] - ib_fun = query_db_for_function(database,'ids',load_type,lch_load) - + vds_base_lim_1 = (voutcm) * 2 / 3 + num_vds_points = ( + int(np.ceil((vds_base_lim_1 - vds_base_lim_0) / vds_sweep_res)) + 1 + ) + vds_base_val_list = np.linspace( + vds_base_lim_0, vds_base_lim_1, num_vds_points, endpoint=True + ) + + for lch_load in gen_params["lch_load"]: + gm_fun = query_db_for_function(database, "gm", load_type, lch_load) + gds_fun = query_db_for_function(database, "gds", load_type, lch_load) + cgg_fun = query_db_for_function(database, "cgg", load_type, lch_load) + gamma = gen_params["gamma"] + ib_fun = query_db_for_function(database, "ids", load_type, lch_load) + num_points = int(np.ceil((vgs_base_max - vgs_base_min) / vgs_res)) + 1 - - gm_in_base = input_op['gm_in'] - gm_in_casc = input_op['gm_casc'] - gds_in_base = input_op['gds_in'] - gds_in_casc = input_op['gds_casc'] - ibias_in = input_op['ibias'] - gds_in = input_op['gds_in'] - cgg_in = input_op['cgg_casc'] - cdd_in = cgg_in * gen_params['cdd_cgg_ratio'] - + + gm_in_base = input_op["gm_in"] + gm_in_casc = input_op["gm_casc"] + gds_in_base = input_op["gds_in"] + gds_in_casc = input_op["gds_casc"] + ibias_in = input_op["ibias"] + gds_in = input_op["gds_in"] + cgg_in = input_op["cgg_casc"] + cdd_in = cgg_in * gen_params["cdd_cgg_ratio"] + def vgs_base_search_fun(vgs_base, vcasc_mid, casc_scale, vg_casc, vsource): vgs_casc = vg_casc - vcasc_mid vds_casc = voutcm - vcasc_mid vbs_casc = vb - vcasc_mid vds_base = vcasc_mid - vsource vbs_base = 0 - - ids_casc = ib_fun([vbs_casc,vgs_casc,vds_casc])[0]*casc_scale - ids_base = ib_fun([vbs_base,vgs_base,vds_base])[0] - + + ids_casc = ib_fun([vbs_casc, vgs_casc, vds_casc])[0] * casc_scale + ids_base = ib_fun([vbs_base, vgs_base, vds_base])[0] + return ids_casc - ids_base - - - + for vds_base in vds_base_val_list: vcasc_mid = vs + vds_base for casc_scale in casc_scale_list: for casc_bias in casc_bias_list: try: - vgs_base = sciopt.brentq(vgs_base_search_fun, vgs_base_min, vgs_base_max, args=(vcasc_mid,casc_scale,casc_bias,vs,)) + vgs_base = sciopt.brentq( + vgs_base_search_fun, + vgs_base_min, + vgs_base_max, + args=( + vcasc_mid, + casc_scale, + casc_bias, + vs, + ), + ) except ValueError: continue vgs_casc = casc_bias - vcasc_mid @@ -379,175 +452,215 @@ def vgs_base_search_fun(vgs_base, vcasc_mid, casc_scale, vg_casc, vsource): vbs_casc = vb - vcasc_mid vds_base = vcasc_mid - vs vbs_base = 0 - - ibias_base = ib_fun([vbs_base,vgs_base,vds_base])[0] - load_scale = ibias_in/ibias_base - - gm_base = gm_fun([vbs_base,vgs_base,vds_base])[0] - gds_base = gds_fun([vbs_base,vgs_base,vds_base])[0] - cdd_base = cgg_fun([vbs_base,vgs_base,vds_base])[0] * gen_params['cdd_cgg_ratio'] - - gm_casc = gm_fun([vbs_casc,vgs_casc,vds_casc])[0] * casc_scale - gds_casc = gds_fun([vbs_casc,vgs_casc,vds_casc])[0] * casc_scale - cdd_casc = cgg_fun([vbs_casc,vgs_casc,vds_casc])[0] * gen_params['cdd_cgg_ratio'] * casc_scale - + + ibias_base = ib_fun([vbs_base, vgs_base, vds_base])[0] + load_scale = ibias_in / ibias_base + + gm_base = gm_fun([vbs_base, vgs_base, vds_base])[0] + gds_base = gds_fun([vbs_base, vgs_base, vds_base])[0] + cdd_base = ( + cgg_fun([vbs_base, vgs_base, vds_base])[0] + * gen_params["cdd_cgg_ratio"] + ) + + gm_casc = gm_fun([vbs_casc, vgs_casc, vds_casc])[0] * casc_scale + gds_casc = gds_fun([vbs_casc, vgs_casc, vds_casc])[0] * casc_scale + cdd_casc = ( + cgg_fun([vbs_casc, vgs_casc, vds_casc])[0] + * gen_params["cdd_cgg_ratio"] + * casc_scale + ) + gm_load = gm_base * load_scale - gds_load = gds_base * gds_casc / (gds_base + gds_casc + gm_casc) * load_scale + gds_load = ( + gds_base + * gds_casc + / (gds_base + gds_casc + gm_casc) + * load_scale + ) cdd_load = cdd_casc * load_scale - + bw_cur = (gds_load + gds_in) / (cdd_load + cdd_in) / 2 / np.pi gain_cur = gm_in_base / (gds_load + gds_in) - + metric_cur = gain_cur * bw_cur / ibias_in - - if load_type == 'pch' or load_type == 'pch_lvt': + + if load_type == "pch" or load_type == "pch_lvt": base_bias = vdd + vgs_base else: base_bias = vgs_base - + if gain_cur > gain_min and bw_cur > bw_min: if metric_cur > metric_best: metric_best = metric_cur best_load_op = dict( - Av = gain_cur, - bw = bw_cur, - casc_scale = casc_scale, - casc_bias = casc_bias, - base_bias = base_bias, - vload_mid = vcasc_mid, - load_scale = load_scale, - lch_load = lch_load, - gm_load = gm_load, - gds_load = gds_load, - cdd_load = cdd_load, - metric_load = metric_best, - ) - print("New GBW/I Best = %f MHz/µA" % (round(metric_best/1e12,2))) + Av=gain_cur, + bw=bw_cur, + casc_scale=casc_scale, + casc_bias=casc_bias, + base_bias=base_bias, + vload_mid=vcasc_mid, + load_scale=load_scale, + lch_load=lch_load, + gm_load=gm_load, + gds_load=gds_load, + cdd_load=cdd_load, + metric_load=metric_best, + ) + print( + "New GBW/I Best = %f MHz/µA" + % (round(metric_best / 1e12, 2)) + ) print("Updated Av Best = %f" % (gain_cur)) - print("Updated BW Best = %f MHz" % (round(bw_cur/1e6,2))) + print("Updated BW Best = %f MHz" % (round(bw_cur / 1e6, 2))) if gain_cur > gain_max: gain_max = gain_cur if bw_cur > bw_max: bw_max = bw_cur print("Load Av Best = %f" % ((gain_max))) - print("Load BW Best = %f MHz" % (round(bw_max/1e6,2))) + print("Load BW Best = %f MHz" % (round(bw_max / 1e6, 2))) return best_load_op + def stage1_telescopic_amplifier_design_amp(amp_specs, gen_params, input_op, load_op): - vnoise_input_referred_max = amp_specs['vnoise_input_referred'] - bw_min = amp_specs['bw_min'] - cload = amp_specs['cload'] - vdd = amp_specs['vdd'] - in_type = amp_specs['in_type'] + vnoise_input_referred_max = amp_specs["vnoise_input_referred"] + bw_min = amp_specs["bw_min"] + cload = amp_specs["cload"] + vdd = amp_specs["vdd"] + in_type = amp_specs["in_type"] k = 1.38e-23 T = 300 - ibias = input_op['ibias'] - vtail = input_op['vtail'] - gm_in = input_op['gm_in'] - gds_in = input_op['gds_in'] - gds_in_casc = input_op['gds_casc'] - gds_in_base = input_op['gds_base'] - gm_in_casc = input_op['gm_casc'] - gamma_in = gen_params['gamma'] - cdd_in = input_op['cdd_in'] - cgg_in = input_op['cgg_base'] - gm_load = load_op['gm_load'] - gds_load = load_op['gds_load'] - cdd_load = load_op['cdd_load'] - gamma_load = gen_params['gamma'] - load_scale = load_op['load_scale'] - load_casc_scale = load_op['casc_scale'] - load_casc_bias = load_op['casc_bias'] - load_base_bias = load_op['base_bias'] - in_casc_scale = input_op['casc_scale'] - in_casc_bias = input_op['casc_bias'] - Av = load_op['Av'] - + ibias = input_op["ibias"] + vtail = input_op["vtail"] + gm_in = input_op["gm_in"] + gds_in = input_op["gds_in"] + gds_in_casc = input_op["gds_casc"] + gds_in_base = input_op["gds_base"] + gm_in_casc = input_op["gm_casc"] + gamma_in = gen_params["gamma"] + cdd_in = input_op["cdd_in"] + cgg_in = input_op["cgg_base"] + gm_load = load_op["gm_load"] + gds_load = load_op["gds_load"] + cdd_load = load_op["cdd_load"] + gamma_load = gen_params["gamma"] + load_scale = load_op["load_scale"] + load_casc_scale = load_op["casc_scale"] + load_casc_bias = load_op["casc_bias"] + load_base_bias = load_op["base_bias"] + in_casc_scale = input_op["casc_scale"] + in_casc_bias = input_op["casc_bias"] + Av = load_op["Av"] + gds_tot = gds_in + gds_load cdd_tot = cdd_in + cdd_load - vnoise_squared_input_referred_max = vnoise_input_referred_max ** 2 - vnoise_squared_input_referred_per_scale = (4*k*T*(gamma_in * gm_in + gamma_load * gm_load) / (gm_in**2)) - scale_noise = max(1, vnoise_squared_input_referred_per_scale/vnoise_squared_input_referred_max) - scale_bw = max(1, 2 * np.pi * bw_min * cload / (gds_tot - 2 * np.pi * bw_min * cdd_tot)) - print('scale_noise:') + vnoise_squared_input_referred_max = vnoise_input_referred_max**2 + vnoise_squared_input_referred_per_scale = ( + 4 * k * T * (gamma_in * gm_in + gamma_load * gm_load) / (gm_in**2) + ) + scale_noise = max( + 1, vnoise_squared_input_referred_per_scale / vnoise_squared_input_referred_max + ) + scale_bw = max( + 1, 2 * np.pi * bw_min * cload / (gds_tot - 2 * np.pi * bw_min * cdd_tot) + ) + print("scale_noise:") pprint.pprint(scale_noise) - print('scale_bw:') + print("scale_bw:") pprint.pprint(scale_bw) - - scale_amp = max(scale_bw,scale_noise) - - vnoise_density_squared_input_referred = vnoise_squared_input_referred_per_scale / scale_amp + + scale_amp = max(scale_bw, scale_noise) + + vnoise_density_squared_input_referred = ( + vnoise_squared_input_referred_per_scale / scale_amp + ) vnoise_density_input_referred = np.sqrt(vnoise_density_squared_input_referred) - + amplifier_op = dict( - gm = gm_in * scale_amp, - gds = gds_tot * scale_amp, - cgg = cgg_in * scale_amp, - cdd = cdd_tot * scale_amp, - vnoise_density_input = vnoise_density_input_referred, - scale_in_base = scale_amp, - scale_in_casc = scale_amp * in_casc_scale, - scale_load_base = scale_amp * load_scale, - scale_load_casc = scale_amp * load_scale * load_casc_scale, - vtail = vtail, - load_base_bias = load_base_bias, - load_casc_bias = load_casc_bias, - in_casc_bias = in_casc_bias, - ibias = ibias * scale_amp * 2, - gain = Av, - bw = (gds_tot * scale_amp) / (2 * np.pi * (cload + (cdd_tot * scale_amp))), - ) + gm=gm_in * scale_amp, + gds=gds_tot * scale_amp, + cgg=cgg_in * scale_amp, + cdd=cdd_tot * scale_amp, + vnoise_density_input=vnoise_density_input_referred, + scale_in_base=scale_amp, + scale_in_casc=scale_amp * in_casc_scale, + scale_load_base=scale_amp * load_scale, + scale_load_casc=scale_amp * load_scale * load_casc_scale, + vtail=vtail, + load_base_bias=load_base_bias, + load_casc_bias=load_casc_bias, + in_casc_bias=in_casc_bias, + ibias=ibias * scale_amp * 2, + gain=Av, + bw=(gds_tot * scale_amp) / (2 * np.pi * (cload + (cdd_tot * scale_amp))), + ) return amplifier_op - -def stage1_telescopic_amplifier_design_tail(database, amp_specs, gen_params, amplifier_op): - - vdd = amp_specs['vdd'] - vtail = amplifier_op['vtail'] - vstar_tail = vtail - gen_params['tail_vstar_vds_margin'] - in_type = amp_specs['in_type'] - - if in_type == 'nch' or in_type == 'nch_lvt': + + +def stage1_telescopic_amplifier_design_tail( + database, amp_specs, gen_params, amplifier_op +): + + vdd = amp_specs["vdd"] + vtail = amplifier_op["vtail"] + vstar_tail = vtail - gen_params["tail_vstar_vds_margin"] + in_type = amp_specs["in_type"] + + if in_type == "nch" or in_type == "nch_lvt": vds_tail = vtail vbs_tail = 0 else: vds_tail = vtail - vdd vbs_tail = 0 - - lch_tail = gen_params['lch_tail'] - - ib_fun = query_db_for_function(database,'ids',in_type,lch_tail) - itarg = amplifier_op['ibias'] - - vgs_tail = query_db_for_vgs(database=database, mos_type = in_type, lch = lch_tail, vbs = vbs_tail, vds = vds_tail, vstar=vstar_tail, mode='vstar') - - ids_tail = ib_fun([vbs_tail,vgs_tail,vds_tail])[0] - scale_tail = itarg/ids_tail - - gm_tail = query_db(database,'gm',in_type,lch_tail,vbs_tail,vgs_tail,vds_tail) - gds_tail = query_db(database,'gds',in_type,lch_tail,vbs_tail,vgs_tail,vds_tail) - cgg_tail = query_db(database,'cgg',in_type,lch_tail,vbs_tail,vgs_tail,vds_tail) - cdd_tail = cgg_tail * gen_params['cdd_cgg_ratio'] - gmro_tail = gm_tail/gds_tail - - if in_type == 'nch' or in_type == 'nch_lvt': + + lch_tail = gen_params["lch_tail"] + + ib_fun = query_db_for_function(database, "ids", in_type, lch_tail) + itarg = amplifier_op["ibias"] + + vgs_tail = query_db_for_vgs( + database=database, + mos_type=in_type, + lch=lch_tail, + vbs=vbs_tail, + vds=vds_tail, + vstar=vstar_tail, + mode="vstar", + ) + + ids_tail = ib_fun([vbs_tail, vgs_tail, vds_tail])[0] + scale_tail = itarg / ids_tail + + gm_tail = query_db(database, "gm", in_type, lch_tail, vbs_tail, vgs_tail, vds_tail) + gds_tail = query_db( + database, "gds", in_type, lch_tail, vbs_tail, vgs_tail, vds_tail + ) + cgg_tail = query_db( + database, "cgg", in_type, lch_tail, vbs_tail, vgs_tail, vds_tail + ) + cdd_tail = cgg_tail * gen_params["cdd_cgg_ratio"] + gmro_tail = gm_tail / gds_tail + + if in_type == "nch" or in_type == "nch_lvt": vbias_tail = vgs_tail else: vbias_tail = vdd + vgs_tail - + tail_op = dict( - scale_tail = scale_tail, - vbias_tail = vbias_tail, - vgs = vgs_tail, - vds = vds_tail, - gm = gm_tail, - gds = gds_tail, - gmro = gmro_tail, - cdd = cdd_tail, - ) + scale_tail=scale_tail, + vbias_tail=vbias_tail, + vgs=vgs_tail, + vds=vds_tail, + gm=gm_tail, + gds=gds_tail, + gmro=gmro_tail, + cdd=cdd_tail, + ) return tail_op + @h.paramclass class TelescopicAmpParams: tail_params = h.Param(dtype=MosParams, desc="Tail FET params") @@ -567,38 +680,39 @@ class TelescopicAmpParams: @h.generator def stage1_telescopic_amplifier(params: TelescopicAmpParams) -> h.Module: - - if params.in_type == 'nch': + + if params.in_type == "nch": mos_in = nch - elif params.in_type == 'nch_lvt': + elif params.in_type == "nch_lvt": mos_in = nch_lvt - elif params.in_type == 'pch': + elif params.in_type == "pch": mos_in = pch - elif params.in_type == 'pch_lvt': + elif params.in_type == "pch_lvt": mos_in = pch_lvt - - if params.load_type == 'nch': + + if params.load_type == "nch": mos_load = nch - elif params.load_type == 'nch_lvt': + elif params.load_type == "nch_lvt": mos_load = nch_lvt - elif params.load_type == 'pch': + elif params.load_type == "pch": mos_load = pch - elif params.load_type == 'pch_lvt': + elif params.load_type == "pch_lvt": mos_load = pch_lvt - - if params.tail_type == 'nch': + + if params.tail_type == "nch": mos_tail = nch - elif params.tail_type == 'nch_lvt': + elif params.tail_type == "nch_lvt": mos_tail = nch_lvt - elif params.tail_type == 'pch': + elif params.tail_type == "pch": mos_tail = pch - elif params.tail_type == 'pch_lvt': + elif params.tail_type == "pch_lvt": mos_tail = pch_lvt - - if params.in_type == 'nch' or params.in_type == 'nch_lvt': + + if params.in_type == "nch" or params.in_type == "nch_lvt": + @h.module class TelescopicAmp: - + VDD, VSS = h.Ports(2) v_in = Diff(port=True, role=Diff.Roles.SINK) v_out = Diff(port=True, role=Diff.Roles.SOURCE) @@ -606,29 +720,46 @@ class TelescopicAmp: v_load = h.Input() v_pcasc = h.Input() v_ncasc = h.Input() - + ## Tail Device - m_tail = mos_tail(params.tail_params)(g=v_tail,s=VSS,b=VSS) - + m_tail = mos_tail(params.tail_params)(g=v_tail, s=VSS, b=VSS) + ## Input Base Pair - m_in_base_p = mos_in(params.in_base_pair_params)(g=v_in.p, s=m_tail.d , b=VSS) - m_in_base_n = mos_in(params.in_base_pair_params)(g=v_in.n, s=m_tail.d , b=VSS) - + m_in_base_p = mos_in(params.in_base_pair_params)( + g=v_in.p, s=m_tail.d, b=VSS + ) + m_in_base_n = mos_in(params.in_base_pair_params)( + g=v_in.n, s=m_tail.d, b=VSS + ) + ## Input Cascode Pair - m_in_casc_p = mos_in(params.in_casc_pair_params)(g=v_ncasc, s=m_in_base_p.d, d=v_out.n, b=VSS) - m_in_casc_n = mos_in(params.in_casc_pair_params)(g=v_ncasc, s=m_in_base_n.d, d=v_out.p, b=VSS) - + m_in_casc_p = mos_in(params.in_casc_pair_params)( + g=v_ncasc, s=m_in_base_p.d, d=v_out.n, b=VSS + ) + m_in_casc_n = mos_in(params.in_casc_pair_params)( + g=v_ncasc, s=m_in_base_n.d, d=v_out.p, b=VSS + ) + ## Load Base Pair - m_load_base_p = mos_load(params.load_base_pair_params)(g=v_load, s=VDD, b=VDD) - m_load_base_n = mos_load(params.load_base_pair_params)(g=v_load, s=VDD, b=VDD) - + m_load_base_p = mos_load(params.load_base_pair_params)( + g=v_load, s=VDD, b=VDD + ) + m_load_base_n = mos_load(params.load_base_pair_params)( + g=v_load, s=VDD, b=VDD + ) + ## Load Cascode Pair - m_load_casc_p = mos_load(params.load_casc_pair_params)(g=v_pcasc, s=m_load_base_p.d, d=v_out.n, b=VDD) - m_load_casc_n = mos_load(params.load_casc_pair_params)(g=v_pcasc, s=m_load_base_n.d, d=v_out.p, b=VDD) - + m_load_casc_p = mos_load(params.load_casc_pair_params)( + g=v_pcasc, s=m_load_base_p.d, d=v_out.n, b=VDD + ) + m_load_casc_n = mos_load(params.load_casc_pair_params)( + g=v_pcasc, s=m_load_base_n.d, d=v_out.p, b=VDD + ) + return TelescopicAmp - - elif params.load_type == 'pch' or params.load_type == 'pch_lvt': + + elif params.load_type == "pch" or params.load_type == "pch_lvt": + @h.module class TelescopicAmp: VDD, VSS = h.Ports(2) @@ -638,28 +769,45 @@ class TelescopicAmp: v_load = h.Input() v_pcasc = h.Input() v_ncasc = h.Input() - + ## Tail Device - m_tail = mos_tail(params.tail_params)(g=v_tail,s=VDD,b=VDD) - + m_tail = mos_tail(params.tail_params)(g=v_tail, s=VDD, b=VDD) + ## Input Base Pair - m_in_base_p = mos_in(params.in_base_pair_params)(g=v_in.p, s=m_tail.d , b=VDD) - m_in_base_n = mos_in(params.in_base_pair_params)(g=v_in.n, s=m_tail.d , b=VDD) - + m_in_base_p = mos_in(params.in_base_pair_params)( + g=v_in.p, s=m_tail.d, b=VDD + ) + m_in_base_n = mos_in(params.in_base_pair_params)( + g=v_in.n, s=m_tail.d, b=VDD + ) + ## Input Cascode Pair - m_in_casc_p = mos_in(params.in_casc_pair_params)(g=v_pcasc, s=m_in_base_p.d, d=v_out.n, b=VDD) - m_in_casc_n = mos_in(params.in_casc_pair_params)(g=v_pcasc, s=m_in_base_n.d, d=v_out.p, b=VDD) - + m_in_casc_p = mos_in(params.in_casc_pair_params)( + g=v_pcasc, s=m_in_base_p.d, d=v_out.n, b=VDD + ) + m_in_casc_n = mos_in(params.in_casc_pair_params)( + g=v_pcasc, s=m_in_base_n.d, d=v_out.p, b=VDD + ) + ## Load Base Pair - m_load_base_p = mos_load(params.load_base_pair_params)(g=v_load, s=VSS, b=VSS) - m_load_base_n = mos_load(params.load_base_pair_params)(g=v_load, s=VSS, b=VSS) - + m_load_base_p = mos_load(params.load_base_pair_params)( + g=v_load, s=VSS, b=VSS + ) + m_load_base_n = mos_load(params.load_base_pair_params)( + g=v_load, s=VSS, b=VSS + ) + ## Load Cascode Pair - m_load_casc_p = mos_load(params.load_casc_pair_params)(g=v_ncasc, s=m_load_base_p.d, d=v_out.n, b=VSS) - m_load_casc_n = mos_load(params.load_casc_pair_params)(g=v_ncasc, s=m_load_base_n.d, d=v_out.p, b=VSS) - + m_load_casc_p = mos_load(params.load_casc_pair_params)( + g=v_ncasc, s=m_load_base_p.d, d=v_out.n, b=VSS + ) + m_load_casc_n = mos_load(params.load_casc_pair_params)( + g=v_ncasc, s=m_load_base_n.d, d=v_out.p, b=VSS + ) + return TelescopicAmp + @h.paramclass class DiffClkParams: """Differential Clock Generator Parameters""" @@ -670,6 +818,7 @@ class DiffClkParams: vc = h.Param(dtype=hs.ParamVal, desc="Common-Mode Voltage") trf = h.Param(dtype=hs.ParamVal, desc="Rise / Fall Time") + @h.generator def DiffClkGen(p: DiffClkParams) -> h.Module: """# Differential Clock Generator @@ -702,6 +851,7 @@ def vparams(polarity: bool) -> Vpulse.Params: ckg.vn = Vpulse(vparams(False))(p=ck.n, n=VSS) return ckg + @h.paramclass class Pvt: """Process, Voltage, and Temperature Parameters""" @@ -710,6 +860,7 @@ class Pvt: v = h.Param(dtype=h.Prefixed, desc="Supply Voltage Value (V)", default=1800 * m) t = h.Param(dtype=int, desc="Simulation Temperature (C)", default=25) + @h.paramclass class Stage1TbParams: dut = h.Param(dtype=TelescopicAmpParams, desc="DUT params") @@ -719,36 +870,51 @@ class Stage1TbParams: vd = h.Param(dtype=h.Prefixed, desc="Differential Voltage (V)", default=1 * m) vc = h.Param(dtype=h.Prefixed, desc="Common-Mode Voltage (V)", default=1200 * m) cl = h.Param(dtype=h.Prefixed, desc="Load Cap (Single-Ended) (F)", default=100 * f) - rcm = h.Param(dtype=h.Prefixed, desc="Common Mode Sensing Resistor (Ω)", default = 1 * G) - CMFB_gain = h.Param(dtype=h.Prefixed, desc="Common Mode Feedback Gain (V/V)", default = 1 * KILO) + rcm = h.Param( + dtype=h.Prefixed, desc="Common Mode Sensing Resistor (Ω)", default=1 * G + ) + CMFB_gain = h.Param( + dtype=h.Prefixed, desc="Common Mode Feedback Gain (V/V)", default=1 * KILO + ) + @h.generator def AmplifierTbTran(params: Stage1TbParams) -> h.Module: - + tb = h.sim.tb("TelescopicAmplifierTb") tb.VDD = VDD = h.Signal() - tb.vvdd = Vdc(Vdc.Params(dc=params.pvt.v,ac=(0*m)))(p=VDD, n=tb.VSS) - + tb.vvdd = Vdc(Vdc.Params(dc=params.pvt.v, ac=(0 * m)))(p=VDD, n=tb.VSS) + tb.v_tail = h.Signal() - tb.v_tail_src = Vdc(Vdc.Params(dc=(params.dut.v_tail),ac=(0*m)))(p=tb.v_tail, n=tb.VSS) - + tb.v_tail_src = Vdc(Vdc.Params(dc=(params.dut.v_tail), ac=(0 * m)))( + p=tb.v_tail, n=tb.VSS + ) + tb.v_pcasc = h.Signal() - tb.v_pcasc_src = Vdc(Vdc.Params(dc=(params.dut.v_pcasc),ac=(0*m)))(p=tb.v_pcasc, n=tb.VSS) - + tb.v_pcasc_src = Vdc(Vdc.Params(dc=(params.dut.v_pcasc), ac=(0 * m)))( + p=tb.v_pcasc, n=tb.VSS + ) + tb.v_ncasc = h.Signal() - tb.v_ncasc_src = Vdc(Vdc.Params(dc=(params.dut.v_ncasc),ac=(0*m)))(p=tb.v_ncasc, n=tb.VSS) - + tb.v_ncasc_src = Vdc(Vdc.Params(dc=(params.dut.v_ncasc), ac=(0 * m)))( + p=tb.v_ncasc, n=tb.VSS + ) + tb.v_load = h.Signal() tb.voutcm_ideal = h.Signal() - #tb.v_load_src = Vdc(Vdc.Params(dc=(params.dut.v_load)))(p=tb.v_load, n=tb.VSS) - tb.v_outcm_ideal_src = Vdc(Vdc.Params(dc=(params.dut.voutcm_ideal),ac=(0*m)))(p=tb.voutcm_ideal, n=tb.VSS) - + # tb.v_load_src = Vdc(Vdc.Params(dc=(params.dut.v_load)))(p=tb.v_load, n=tb.VSS) + tb.v_outcm_ideal_src = Vdc(Vdc.Params(dc=(params.dut.voutcm_ideal), ac=(0 * m)))( + p=tb.voutcm_ideal, n=tb.VSS + ) + # Input-driving balun tb.inp = Diff() tb.inpgen = DiffClkGen( - DiffClkParams(period= 1000 * n, delay=1 * n, vc=params.vc, vd=params.vd, trf=800 * PICO) + DiffClkParams( + period=1000 * n, delay=1 * n, vc=params.vc, vd=params.vd, trf=800 * PICO + ) )(ck=tb.inp, VSS=tb.VSS) - + # Output & Load Caps tb.out = Diff() tb.CMSense = h.Signal() @@ -760,8 +926,10 @@ def AmplifierTbTran(params: Stage1TbParams) -> h.Module: tb.ccmfb = Ccmfb(p=tb.CMSense, n=tb.VSS) tb.rcmp = Rload(p=tb.out.p, n=tb.CMSense) tb.rcmn = Rload(p=tb.out.n, n=tb.CMSense) - tb.cmfb_src = Vcvs(Vcvs.Params(gain=params.CMFB_gain))(p=tb.v_load, n=tb.VSS, cp=tb.CMSense, cn=tb.voutcm_ideal) - + tb.cmfb_src = Vcvs(Vcvs.Params(gain=params.CMFB_gain))( + p=tb.v_load, n=tb.VSS, cp=tb.CMSense, cn=tb.voutcm_ideal + ) + # Create the Telescopic Amplifier DUT tb.dut = stage1_telescopic_amplifier(params.dut)( v_in=tb.inp, @@ -771,35 +939,45 @@ def AmplifierTbTran(params: Stage1TbParams) -> h.Module: v_pcasc=tb.v_pcasc, v_load=tb.v_load, VDD=VDD, - VSS=tb.VSS) + VSS=tb.VSS, + ) return tb + @h.generator def AmplifierTbAc(params: Stage1TbParams) -> h.Module: - + tb = h.sim.tb("TelescopicAmplifierTb") tb.VDD = VDD = h.Signal() - tb.vvdd = Vdc(Vdc.Params(dc=params.pvt.v,ac=(0*m)))(p=VDD, n=tb.VSS) - + tb.vvdd = Vdc(Vdc.Params(dc=params.pvt.v, ac=(0 * m)))(p=VDD, n=tb.VSS) + tb.v_tail = h.Signal() - tb.v_tail_src = Vdc(Vdc.Params(dc=(params.dut.v_tail),ac=(0*m)))(p=tb.v_tail, n=tb.VSS) - + tb.v_tail_src = Vdc(Vdc.Params(dc=(params.dut.v_tail), ac=(0 * m)))( + p=tb.v_tail, n=tb.VSS + ) + tb.v_pcasc = h.Signal() - tb.v_pcasc_src = Vdc(Vdc.Params(dc=(params.dut.v_pcasc),ac=(0*m)))(p=tb.v_pcasc, n=tb.VSS) - + tb.v_pcasc_src = Vdc(Vdc.Params(dc=(params.dut.v_pcasc), ac=(0 * m)))( + p=tb.v_pcasc, n=tb.VSS + ) + tb.v_ncasc = h.Signal() - tb.v_ncasc_src = Vdc(Vdc.Params(dc=(params.dut.v_ncasc),ac=(0*m)))(p=tb.v_ncasc, n=tb.VSS) - + tb.v_ncasc_src = Vdc(Vdc.Params(dc=(params.dut.v_ncasc), ac=(0 * m)))( + p=tb.v_ncasc, n=tb.VSS + ) + tb.v_load = h.Signal() tb.voutcm_ideal = h.Signal() - #tb.v_load_src = Vdc(Vdc.Params(dc=(params.dut.v_load)))(p=tb.v_load, n=tb.VSS) - tb.v_outcm_ideal_src = Vdc(Vdc.Params(dc=(params.dut.voutcm_ideal),ac=(0*m)))(p=tb.voutcm_ideal, n=tb.VSS) - + # tb.v_load_src = Vdc(Vdc.Params(dc=(params.dut.v_load)))(p=tb.v_load, n=tb.VSS) + tb.v_outcm_ideal_src = Vdc(Vdc.Params(dc=(params.dut.voutcm_ideal), ac=(0 * m)))( + p=tb.voutcm_ideal, n=tb.VSS + ) + # Input-driving balun tb.inp = Diff() - tb.inpgen = Vdc(Vdc.Params(dc=(params.vc),ac=(500*m)))(p=tb.inp.p, n=tb.VSS) - tb.inngen = Vdc(Vdc.Params(dc=(params.vc),ac=(-500*m)))(p=tb.inp.n, n=tb.VSS) - + tb.inpgen = Vdc(Vdc.Params(dc=(params.vc), ac=(500 * m)))(p=tb.inp.p, n=tb.VSS) + tb.inngen = Vdc(Vdc.Params(dc=(params.vc), ac=(-500 * m)))(p=tb.inp.n, n=tb.VSS) + # Output & Load Caps tb.out = Diff() tb.CMSense = h.Signal() @@ -811,8 +989,10 @@ def AmplifierTbAc(params: Stage1TbParams) -> h.Module: tb.ccmfb = Ccmfb(p=tb.CMSense, n=tb.VSS) tb.rcmp = Rload(p=tb.out.p, n=tb.CMSense) tb.rcmn = Rload(p=tb.out.n, n=tb.CMSense) - tb.cmfb_src = Vcvs(Vcvs.Params(gain=params.CMFB_gain))(p=tb.v_load, n=tb.VSS, cp=tb.CMSense, cn=tb.voutcm_ideal) - + tb.cmfb_src = Vcvs(Vcvs.Params(gain=params.CMFB_gain))( + p=tb.v_load, n=tb.VSS, cp=tb.CMSense, cn=tb.voutcm_ideal + ) + # Create the Telescopic Amplifier DUT tb.dut = stage1_telescopic_amplifier(params.dut)( v_in=tb.inp, @@ -822,190 +1002,221 @@ def AmplifierTbAc(params: Stage1TbParams) -> h.Module: v_pcasc=tb.v_pcasc, v_load=tb.v_load, VDD=VDD, - VSS=tb.VSS) + VSS=tb.VSS, + ) return tb - - + + def generate_stage1_telescopic_amplifier(amp_specs): nch_db_filename = "database_nch.npy" nch_lvt_db_filename = "database_nch_lvt.npy" pch_db_filename = "database_pch.npy" pch_lvt_db_filename = "database_pch_lvt.npy" - + gen_params = dict( - tail_vstar_vds_margin = 20e-3, - vgs_sweep_res = 5e-3, - vds_sweep_res = 15e-3, - casc_scale_min = 0.5, - casc_scale_max = 3, - casc_scale_step = 0.5, - casc_bias_step = 10e-3, - gamma = 1, - cdd_cgg_ratio = 1.1, - lch_in = 0.15, - lch_tail = 0.5, - lch_load = [0.15, 0.5, 1], - ) - - if amp_specs['in_type'] == 'nch': + tail_vstar_vds_margin=20e-3, + vgs_sweep_res=5e-3, + vds_sweep_res=15e-3, + casc_scale_min=0.5, + casc_scale_max=3, + casc_scale_step=0.5, + casc_bias_step=10e-3, + gamma=1, + cdd_cgg_ratio=1.1, + lch_in=0.15, + lch_tail=0.5, + lch_load=[0.15, 0.5, 1], + ) + + if amp_specs["in_type"] == "nch": in_db_filename = nch_db_filename - elif amp_specs['in_type'] == 'nch_lvt': + elif amp_specs["in_type"] == "nch_lvt": in_db_filename = nch_lvt_db_filename - elif amp_specs['in_type'] == 'pch': + elif amp_specs["in_type"] == "pch": in_db_filename = pch_db_filename - elif amp_specs['in_type'] == 'pch_lvt': + elif amp_specs["in_type"] == "pch_lvt": in_db_filename = pch_lvt_db_filename - - if amp_specs['load_type'] == 'nch': + + if amp_specs["load_type"] == "nch": load_db_filename = nch_db_filename - elif amp_specs['load_type'] == 'nch_lvt': + elif amp_specs["load_type"] == "nch_lvt": load_db_filename = nch_lvt_db_filename - elif amp_specs['load_type'] == 'pch': + elif amp_specs["load_type"] == "pch": load_db_filename = pch_db_filename - elif amp_specs['load_type'] == 'pch_lvt': + elif amp_specs["load_type"] == "pch_lvt": load_db_filename = pch_lvt_db_filename - - if amp_specs['tail_type'] == 'nch': + + if amp_specs["tail_type"] == "nch": tail_db_filename = nch_db_filename - elif amp_specs['tail_type'] == 'nch_lvt': + elif amp_specs["tail_type"] == "nch_lvt": tail_db_filename = nch_lvt_db_filename - elif amp_specs['tail_type'] == 'pch': + elif amp_specs["tail_type"] == "pch": tail_db_filename = pch_db_filename - elif amp_specs['tail_type'] == 'pch_lvt': + elif amp_specs["tail_type"] == "pch_lvt": tail_db_filename = pch_lvt_db_filename - - database_in = np.load(in_db_filename,allow_pickle=True).item() - database_tail = np.load(tail_db_filename,allow_pickle=True).item() - database_load = np.load(load_db_filename,allow_pickle=True).item() - - amp_specs['gm_id_in'] = 2/amp_specs['vstar_in'] - - input_op = stage1_telescopic_amplifier_design_input(database_in,amp_specs,gen_params) - load_op = stage1_telescopic_amplifier_design_load(database_load,amp_specs,gen_params,input_op) - print('input op:') + + database_in = np.load(in_db_filename, allow_pickle=True).item() + database_tail = np.load(tail_db_filename, allow_pickle=True).item() + database_load = np.load(load_db_filename, allow_pickle=True).item() + + amp_specs["gm_id_in"] = 2 / amp_specs["vstar_in"] + + input_op = stage1_telescopic_amplifier_design_input( + database_in, amp_specs, gen_params + ) + load_op = stage1_telescopic_amplifier_design_load( + database_load, amp_specs, gen_params, input_op + ) + print("input op:") pprint.pprint(input_op) - print('load op:') + print("load op:") pprint.pprint(load_op) - amplifier_op = stage1_telescopic_amplifier_design_amp(amp_specs,gen_params,input_op,load_op) - print('amplifier op:') + amplifier_op = stage1_telescopic_amplifier_design_amp( + amp_specs, gen_params, input_op, load_op + ) + print("amplifier op:") pprint.pprint(amplifier_op) - tail_op = stage1_telescopic_amplifier_design_tail(database_tail,amp_specs, gen_params, amplifier_op) - print('tail op:') + tail_op = stage1_telescopic_amplifier_design_tail( + database_tail, amp_specs, gen_params, amplifier_op + ) + print("tail op:") pprint.pprint(tail_op) - - if amp_specs['tail_type'] == 'nch' or amp_specs['tail_type'] == 'nch_lvt': - v_load = amplifier_op['load_base_bias'] - v_pcasc = amplifier_op['load_casc_bias'] - v_ncasc = amplifier_op['in_casc_bias'] - v_tail = tail_op['vbias_tail'] + + if amp_specs["tail_type"] == "nch" or amp_specs["tail_type"] == "nch_lvt": + v_load = amplifier_op["load_base_bias"] + v_pcasc = amplifier_op["load_casc_bias"] + v_ncasc = amplifier_op["in_casc_bias"] + v_tail = tail_op["vbias_tail"] else: - v_load = amplifier_op['load_base_bias'] - v_ncasc = amplifier_op['load_casc_bias'] - v_pcasc = amplifier_op['in_casc_bias'] - v_tail = tail_op['vbias_tail'] - - ampParams=TelescopicAmpParams( -<<<<<<< HEAD - in_base_pair_params = MosParams(w= 500 * m * int(np.round(amplifier_op['scale_in_base'])), l=h.Prefixed(gen_params['lch_in']), nf=int(np.round(amplifier_op['scale_in_base']))), - in_casc_pair_params = MosParams(w= 500 * m * int(np.round(amplifier_op['scale_in_casc'])), l=h.Prefixed(gen_params['lch_in']), nf=int(np.round(amplifier_op['scale_in_casc']))), - load_base_pair_params = MosParams(w= 500 * m * int(np.round(amplifier_op['scale_load_base'])), l=h.Prefixed(gen_params['lch_load']), nf=int(np.round(amplifier_op['scale_load_base']))), - load_casc_pair_params = MosParams(w= 500 * m * int(np.round(amplifier_op['scale_load_casc'])), l=h.Prefixed(gen_params['lch_load']), nf=int(np.round(amplifier_op['scale_load_casc']))), - tail_params = MosParams(w= 500 * m * int(np.round(tail_op['scale_tail'])), l=h.Prefixed(gen_params['lch_tail']), nf=int(np.round(tail_op['scale_tail']))), -======= - in_base_pair_params = MosParams(w= 500 * m * int(np.round(amplifier_op['scale_in_base'])), l=gen_params['lch_in'], nf=int(np.round(amplifier_op['scale_in_base']))), - in_casc_pair_params = MosParams(w= 500 * m * int(np.round(amplifier_op['scale_in_casc'])), l=gen_params['lch_in'], nf=int(np.round(amplifier_op['scale_in_casc']))), - load_base_pair_params = MosParams(w= 500 * m * int(np.round(amplifier_op['scale_load_base'])), l=load_op['lch_load'], nf=int(np.round(amplifier_op['scale_load_base']))), - load_casc_pair_params = MosParams(w= 500 * m * int(np.round(amplifier_op['scale_load_casc'])), l=load_op['lch_load'], nf=int(np.round(amplifier_op['scale_load_casc']))), - tail_params = MosParams(w= 500 * m * int(np.round(tail_op['scale_tail'])), l=gen_params['lch_tail'], nf=int(np.round(tail_op['scale_tail']))), ->>>>>>> main - in_type = amp_specs['in_type'], - load_type = amp_specs['load_type'], - tail_type = amp_specs['tail_type'], - voutcm_ideal = amp_specs['voutcm'] * 1000 * m, - v_load = v_load * 1000 * m, - v_pcasc = v_pcasc * 1000 * m, - v_ncasc = v_ncasc * 1000 * m, - v_tail = v_tail * 1000 * m, - ) - - - params = Stage1TbParams(pvt=Pvt(), vc=amp_specs['vincm'] * 1000 * m, vd=1 * m, dut=ampParams, cl = amp_specs['cload'] * 1000 * m) + v_load = amplifier_op["load_base_bias"] + v_ncasc = amplifier_op["load_casc_bias"] + v_pcasc = amplifier_op["in_casc_bias"] + v_tail = tail_op["vbias_tail"] + + ampParams = TelescopicAmpParams( + in_base_pair_params=MosParams( + w=500 * m * int(np.round(amplifier_op["scale_in_base"])), + l=h.Prefixed(gen_params["lch_in"]), + nf=int(np.round(amplifier_op["scale_in_base"])), + ), + in_casc_pair_params=MosParams( + w=500 * m * int(np.round(amplifier_op["scale_in_casc"])), + l=h.Prefixed(gen_params["lch_in"]), + nf=int(np.round(amplifier_op["scale_in_casc"])), + ), + load_base_pair_params=MosParams( + w=500 * m * int(np.round(amplifier_op["scale_load_base"])), + l=h.Prefixed(gen_params["lch_load"]), + nf=int(np.round(amplifier_op["scale_load_base"])), + ), + load_casc_pair_params=MosParams( + w=500 * m * int(np.round(amplifier_op["scale_load_casc"])), + l=h.Prefixed(gen_params["lch_load"]), + nf=int(np.round(amplifier_op["scale_load_casc"])), + ), + tail_params=MosParams( + w=500 * m * int(np.round(tail_op["scale_tail"])), + l=h.Prefixed(gen_params["lch_tail"]), + nf=int(np.round(tail_op["scale_tail"])), + ), + in_type=amp_specs["in_type"], + load_type=amp_specs["load_type"], + tail_type=amp_specs["tail_type"], + voutcm_ideal=amp_specs["voutcm"] * 1000 * m, + v_load=v_load * 1000 * m, + v_pcasc=v_pcasc * 1000 * m, + v_ncasc=v_ncasc * 1000 * m, + v_tail=v_tail * 1000 * m, + ) + + params = Stage1TbParams( + pvt=Pvt(), + vc=amp_specs["vincm"] * 1000 * m, + vd=1 * m, + dut=ampParams, + cl=amp_specs["cload"] * 1000 * m, + ) # Create our simulation input @hs.sim class TelescopicAmplifierTranSim: tb = AmplifierTbTran(params) - tr = hs.Tran(tstop=3000 * n, tstep=1*n) + tr = hs.Tran(tstop=3000 * n, tstep=1 * n) # Add the PDK dependencies # TelescopicAmplifierSim.lib(sky130.install.model_lib, 'tt') - TelescopicAmplifierSim.lib(sky130.install.model_lib, 'tt') + TelescopicAmplifierSim.lib(sky130.install.model_lib, "tt") TelescopicAmplifierSim.literal(".save all") results = TelescopicAmplifierSim.run(sim_options) tran_results = results.an[0].data - t = tran_results['time'] - v_out_diff = tran_results['v(xtop.out_p)'] - tran_results['v(xtop.out_n)'] - v_out_cm = (tran_results['v(xtop.out_p)'] + tran_results['v(xtop.out_n)'])/2 - v_in_diff = tran_results['v(xtop.inp_p)'] - tran_results['v(xtop.inp_n)'] - v_in_cm = (tran_results['v(xtop.inp_p)'] + tran_results['v(xtop.inp_n)'])/2 - + t = tran_results["time"] + v_out_diff = tran_results["v(xtop.out_p)"] - tran_results["v(xtop.out_n)"] + v_out_cm = (tran_results["v(xtop.out_p)"] + tran_results["v(xtop.out_n)"]) / 2 + v_in_diff = tran_results["v(xtop.inp_p)"] - tran_results["v(xtop.inp_n)"] + v_in_cm = (tran_results["v(xtop.inp_p)"] + tran_results["v(xtop.inp_n)"]) / 2 + fig, ax = plt.subplots(2, sharex=True) ax[0].plot(t, v_in_diff) ax[1].plot(t, v_out_diff) - + plt.show() - + fig, ax = plt.subplots(2, sharex=True) ax[0].plot(t, v_in_cm) ax[1].plot(t, v_out_cm) - + # Create our simulation input @hs.sim class TelescopicAmplifierAcSim: tb = AmplifierTbAc(params) myac = hs.Ac(sweep=LogSweep(1e1, 1e11, 10)) - + # Add the PDK dependencies - TelescopicAmplifierAcSim.lib(sky130.install.model_lib, 'tt') + TelescopicAmplifierAcSim.lib(sky130.install.model_lib, "tt") TelescopicAmplifierAcSim.literal(".save all") results = TelescopicAmplifierAcSim.run(sim_options) ac_results = results.an[0].data - v_out_diff_ac = ac_results['v(xtop.out_p)'] - ac_results['v(xtop.out_n)'] + v_out_diff_ac = ac_results["v(xtop.out_p)"] - ac_results["v(xtop.out_n)"] f = np.logspace(start=1, stop=11, num=101, endpoint=True) f_ax = np.logspace(start=1, stop=11, num=11, endpoint=True) - f3db_idx = np.squeeze(np.where(abs(v_out_diff_ac) < np.max(abs(v_out_diff_ac))/np.sqrt(2)))[0] + f3db_idx = np.squeeze( + np.where(abs(v_out_diff_ac) < np.max(abs(v_out_diff_ac)) / np.sqrt(2)) + )[0] fgbw_idx = np.squeeze(np.where(abs(v_out_diff_ac) < 1))[0] f3db = f[f3db_idx] fgbw = f[fgbw_idx] Avdc = np.max(abs(v_out_diff_ac)) v_out_diff_ac_dB = 20 * np.log10(abs(v_out_diff_ac)) - v_out_diff_ac_phase = (np.angle(v_out_diff_ac) % (2*np.pi) - 2*np.pi) * 180 / np.pi + v_out_diff_ac_phase = ( + (np.angle(v_out_diff_ac) % (2 * np.pi) - 2 * np.pi) * 180 / np.pi + ) PM_ac = 180 + v_out_diff_ac_phase[fgbw_idx] fig, ax = plt.subplots(2, sharex=True) ax[0].semilogx(f, v_out_diff_ac_dB) - ax[0].grid(color='black', linestyle='--', linewidth=0.5) + ax[0].grid(color="black", linestyle="--", linewidth=0.5) ax[0].grid(visible=True, which="both") ax[0].set_xticks(f_ax) ax[1].semilogx(f, v_out_diff_ac_phase) - ax[1].grid(color='black', linestyle='--', linewidth=0.5) + ax[1].grid(color="black", linestyle="--", linewidth=0.5) ax[1].grid(visible=True, which="both") ax[1].set_xticks(f_ax) plt.show() print("Av (AC Sim) = %f" % ((Avdc))) - print("BW (AC Sim) = %f MHz" % (round(f3db/1e6,2))) - print("GBW (AC Sim) = %f MHz" % (round(fgbw/1e6,2))) + print("BW (AC Sim) = %f MHz" % (round(f3db / 1e6, 2))) + print("GBW (AC Sim) = %f MHz" % (round(fgbw / 1e6, 2))) print("PM (AC Sim) = %f degrees" % PM_ac) - + ampResults = dict( - Av = Avdc, - bw = f3db, - gbw = fgbw, - pm = PM_ac, - ) - + Av=Avdc, + bw=f3db, + gbw=fgbw, + pm=PM_ac, + ) + return ampParams, ampResults + @h.paramclass class Stage2AmpParams: in_params = h.Param(dtype=MosParams, desc="Input pair MOS params") @@ -1015,45 +1226,47 @@ class Stage2AmpParams: voutcm_ideal = h.Param(dtype=Prefixed, desc="Ideal Output CM") v_load = h.Param(dtype=Prefixed, desc="Load MOS Bias") + @h.generator def stage2_common_source_amplifier(params: Stage2AmpParams) -> h.Module: - - if params.in_type == 'nch': + + if params.in_type == "nch": mos_in = nch - elif params.in_type == 'nch_lvt': + elif params.in_type == "nch_lvt": mos_in = nch_lvt - elif params.in_type == 'pch': + elif params.in_type == "pch": mos_in = pch - elif params.in_type == 'pch_lvt': + elif params.in_type == "pch_lvt": mos_in = pch_lvt - - if params.load_type == 'nch': + + if params.load_type == "nch": mos_load = nch - elif params.load_type == 'nch_lvt': + elif params.load_type == "nch_lvt": mos_load = nch_lvt - elif params.load_type == 'pch': + elif params.load_type == "pch": mos_load = pch - elif params.load_type == 'pch_lvt': + elif params.load_type == "pch_lvt": mos_load = pch_lvt @h.module class Stage2Amplifier: - + VDD, VSS = h.Ports(2) v_in = Diff(port=True, role=Diff.Roles.SINK) v_out = Diff(port=True, role=Diff.Roles.SOURCE) v_load = h.Input() - + ## Stage 2 Input Devices m_in_p = mos_in(params.in_params)(g=v_in.n, s=VSS, d=v_out.p, b=VSS) m_in_n = mos_in(params.in_params)(g=v_in.p, s=VSS, d=v_out.n, b=VSS) - + ## Load Base Pair m_load_p = mos_load(params.load_params)(g=v_load, s=VDD, d=v_out.p, b=VDD) m_load_n = mos_load(params.load_params)(g=v_load, s=VDD, d=v_out.n, b=VDD) - + return Stage2Amplifier + @h.paramclass class Stage2AmpTbParams: dut = h.Param(dtype=Stage2AmpParams, desc="DUT params") @@ -1063,27 +1276,36 @@ class Stage2AmpTbParams: vd = h.Param(dtype=h.Prefixed, desc="Differential Voltage (V)", default=1 * m) vc = h.Param(dtype=h.Prefixed, desc="Common-Mode Voltage (V)", default=1200 * m) cl = h.Param(dtype=h.Prefixed, desc="Load Cap (Single-Ended) (F)", default=100 * f) - rcm = h.Param(dtype=h.Prefixed, desc="Common Mode Sensing Resistor (Ω)", default = 1 * G) - CMFB_gain = h.Param(dtype=h.Prefixed, desc="Common Mode Feedback Gain (V/V)", default = 1 * KILO) + rcm = h.Param( + dtype=h.Prefixed, desc="Common Mode Sensing Resistor (Ω)", default=1 * G + ) + CMFB_gain = h.Param( + dtype=h.Prefixed, desc="Common Mode Feedback Gain (V/V)", default=1 * KILO + ) + @h.generator def Stage2AmpTbTran(params: Stage2AmpTbParams) -> h.Module: - + tb = h.sim.tb("Stage2AmplifierTbTran") tb.VDD = VDD = h.Signal() - tb.vvdd = Vdc(Vdc.Params(dc=params.pvt.v,ac=(0*m)))(p=VDD, n=tb.VSS) - + tb.vvdd = Vdc(Vdc.Params(dc=params.pvt.v, ac=(0 * m)))(p=VDD, n=tb.VSS) + tb.v_load = h.Signal() tb.voutcm_ideal = h.Signal() - tb.v_outcm_ideal_src = Vdc(Vdc.Params(dc=(params.dut.voutcm_ideal),ac=(0*m)))(p=tb.voutcm_ideal, n=tb.VSS) - + tb.v_outcm_ideal_src = Vdc(Vdc.Params(dc=(params.dut.voutcm_ideal), ac=(0 * m)))( + p=tb.voutcm_ideal, n=tb.VSS + ) + # Input-driving balun tb.inp = Diff() tb.inpgen = DiffClkGen( - DiffClkParams(period= 1000 * n, delay=1 * n, vc=params.vc, vd=params.vd, trf=800 * PICO) + DiffClkParams( + period=1000 * n, delay=1 * n, vc=params.vc, vd=params.vd, trf=800 * PICO + ) )(ck=tb.inp, VSS=tb.VSS) - + # Output & Load Caps tb.out = Diff() tb.CMSense = h.Signal() @@ -1095,264 +1317,343 @@ def Stage2AmpTbTran(params: Stage2AmpTbParams) -> h.Module: tb.ccmfb = Ccmfb(p=tb.CMSense, n=tb.VSS) tb.rcmp = Rload(p=tb.out.p, n=tb.CMSense) tb.rcmn = Rload(p=tb.out.n, n=tb.CMSense) - tb.cmfb_src = Vcvs(Vcvs.Params(gain=params.CMFB_gain))(p=tb.v_load, n=tb.VSS, cp=tb.CMSense, cn=tb.voutcm_ideal) - + tb.cmfb_src = Vcvs(Vcvs.Params(gain=params.CMFB_gain))( + p=tb.v_load, n=tb.VSS, cp=tb.CMSense, cn=tb.voutcm_ideal + ) + # Create the Telescopic Amplifier DUT tb.dut = stage2_common_source_amplifier(params.dut)( - v_in=tb.inp, - v_out=tb.out, - v_load=tb.v_load, - VDD=VDD, - VSS=tb.VSS) + v_in=tb.inp, v_out=tb.out, v_load=tb.v_load, VDD=VDD, VSS=tb.VSS + ) return tb -def stage2_common_source_amplifier_design_and_scale_amp(database_in,database_load,amp_specs,gen_params): - - vdd = amp_specs['vdd'] - vincm = amp_specs['vincm'] - voutcm = amp_specs['voutcm'] - vgs_res = gen_params['vgs_sweep_res'] - gain_min = amp_specs['gain_min'] - stage1_gain = amp_specs['stage1_gain'] - bw_min = amp_specs['amp_bw_min'] - in_type = amp_specs['in_type'] - load_type = amp_specs['load_type'] - lch_in = gen_params['lch_in'] - lch_load = gen_params['lch_load'] - load_scale_min = gen_params['load_scale_min'] - load_scale_max = gen_params['load_scale_max'] - load_scale_step = gen_params['load_scale_step'] - rload = amp_specs['rload'] - - if in_type == 'nch' or in_type == 'nch_lvt': + +def stage2_common_source_amplifier_design_and_scale_amp( + database_in, database_load, amp_specs, gen_params +): + + vdd = amp_specs["vdd"] + vincm = amp_specs["vincm"] + voutcm = amp_specs["voutcm"] + vgs_res = gen_params["vgs_sweep_res"] + gain_min = amp_specs["gain_min"] + stage1_gain = amp_specs["stage1_gain"] + bw_min = amp_specs["amp_bw_min"] + in_type = amp_specs["in_type"] + load_type = amp_specs["load_type"] + lch_in = gen_params["lch_in"] + lch_load = gen_params["lch_load"] + load_scale_min = gen_params["load_scale_min"] + load_scale_max = gen_params["load_scale_max"] + load_scale_step = gen_params["load_scale_step"] + rload = amp_specs["rload"] + + if in_type == "nch" or in_type == "nch_lvt": vs_in = 0 vs_load = vdd else: vs_in = vdd vs_load = 0 - + vbs_in = 0 vbs_load = 0 vgs_in = vincm - vs_in vds_in = voutcm - vs_in vds_load = voutcm - vs_load - - ids_in = query_db(database_in,'ids',in_type,lch_in,vbs_in,vgs_in,vds_in) - gm_in = query_db(database_in,'gm',in_type,lch_in,vbs_in,vgs_in,vds_in) - gds_in = query_db(database_in,'gds',in_type,lch_in,vbs_in,vgs_in,vds_in) - cgg_in = query_db(database_in,'cgg',in_type,lch_in,vbs_in,vgs_in,vds_in) - cdd_in = cgg_in * gen_params['cdd_cgg_ratio'] - - load_scale_count = int((load_scale_max-load_scale_min)/load_scale_step)+1 - load_scale_list = np.linspace(load_scale_min,load_scale_max,load_scale_count,endpoint=True) - + + ids_in = query_db(database_in, "ids", in_type, lch_in, vbs_in, vgs_in, vds_in) + gm_in = query_db(database_in, "gm", in_type, lch_in, vbs_in, vgs_in, vds_in) + gds_in = query_db(database_in, "gds", in_type, lch_in, vbs_in, vgs_in, vds_in) + cgg_in = query_db(database_in, "cgg", in_type, lch_in, vbs_in, vgs_in, vds_in) + cdd_in = cgg_in * gen_params["cdd_cgg_ratio"] + + load_scale_count = int((load_scale_max - load_scale_min) / load_scale_step) + 1 + load_scale_list = np.linspace( + load_scale_min, load_scale_max, load_scale_count, endpoint=True + ) + metric_best = 0 gain_max = 0 bw_max = 0 - - for lch_load in gen_params['lch_load']: - + + for lch_load in gen_params["lch_load"]: + for load_scale in load_scale_list: - - ids_load = ids_in/load_scale + + ids_load = ids_in / load_scale try: - vgs_load = query_db_for_vgs(database_load,load_type,lch_load,vbs_load,vds_load,ids=ids_load,mode='ids',scale=1) + vgs_load = query_db_for_vgs( + database_load, + load_type, + lch_load, + vbs_load, + vds_load, + ids=ids_load, + mode="ids", + scale=1, + ) except: continue - - gm_load = query_db(database_load,'gm',load_type,lch_load,vbs_load,vgs_load,vds_load)*load_scale - gds_load = query_db(database_load,'gds',load_type,lch_load,vbs_load,vgs_load,vds_load)*load_scale - cgg_load = query_db(database_load,'cgg',load_type,lch_load,vbs_load,vgs_load,vds_load)*load_scale - cdd_load = cgg_load * gen_params['cdd_cgg_ratio'] - - gds = gds_load + gds_in + (1/rload) + + gm_load = ( + query_db( + database_load, + "gm", + load_type, + lch_load, + vbs_load, + vgs_load, + vds_load, + ) + * load_scale + ) + gds_load = ( + query_db( + database_load, + "gds", + load_type, + lch_load, + vbs_load, + vgs_load, + vds_load, + ) + * load_scale + ) + cgg_load = ( + query_db( + database_load, + "cgg", + load_type, + lch_load, + vbs_load, + vgs_load, + vds_load, + ) + * load_scale + ) + cdd_load = cgg_load * gen_params["cdd_cgg_ratio"] + + gds = gds_load + gds_in + (1 / rload) cdd = cdd_in + cdd_load - + gain_cur = gm_in / gds - bw_cur = gds/cdd / 2 / np.pi + bw_cur = gds / cdd / 2 / np.pi metric_cur = gain_cur * bw_cur / ids_in - - if load_type == 'pch' or load_type == 'pch_lvt': + + if load_type == "pch" or load_type == "pch_lvt": load_bias = vdd + vgs_load else: load_bias = vgs_load - + if gain_cur > gain_min and bw_cur > bw_min: if metric_cur > metric_best: metric_best = metric_cur best_op = dict( - Av = gain_cur, - bw = bw_cur, - load_scale = load_scale, - lch_load = lch_load, - gm_in = gm_in, - gm_load = gm_load, - gds = gds, - cdd = cdd, - metric = metric_best, - load_bias = load_bias, - ) - print("New GBW/I Best = %f MHz/µA" % (round(metric_best/1e12,2))) + Av=gain_cur, + bw=bw_cur, + load_scale=load_scale, + lch_load=lch_load, + gm_in=gm_in, + gm_load=gm_load, + gds=gds, + cdd=cdd, + metric=metric_best, + load_bias=load_bias, + ) + print("New GBW/I Best = %f MHz/µA" % (round(metric_best / 1e12, 2))) print("Updated Av Best = %f" % (gain_cur)) - print("Updated BW Best = %f MHz" % (round(bw_cur/1e6,2))) + print("Updated BW Best = %f MHz" % (round(bw_cur / 1e6, 2))) if gain_cur > gain_max: gain_max = gain_cur if bw_cur > bw_max: bw_max = bw_cur print("2nd Stage Av Best = %f" % ((gain_max))) - print("2nd Stage BW Best = %f MHz" % (round(bw_max/1e6,2))) - - vnoise_input_referred_max = amp_specs['vnoise_input_referred'] - cload = amp_specs['cload'] - vdd = amp_specs['vdd'] - in_type = amp_specs['in_type'] + print("2nd Stage BW Best = %f MHz" % (round(bw_max / 1e6, 2))) + + vnoise_input_referred_max = amp_specs["vnoise_input_referred"] + cload = amp_specs["cload"] + vdd = amp_specs["vdd"] + in_type = amp_specs["in_type"] k = 1.38e-23 T = 300 ibias = ids_in - load_bias = best_op['load_bias'] - gm_in = best_op['gm_in'] - gamma_in = gen_params['gamma'] - gamma_load = gen_params['gamma'] - load_scale = best_op['load_scale'] - Av = best_op['Av'] - gds = best_op['gds'] - cdd = best_op['cdd'] - vnoise_squared_input_referred_max = vnoise_input_referred_max ** 2 - vnoise_squared_input_referred_per_scale = (4*k*T*(gamma_in * gm_in + gamma_load * gm_load) / (gm_in**2)) - scale_noise = max(1, vnoise_squared_input_referred_per_scale/vnoise_squared_input_referred_max) + load_bias = best_op["load_bias"] + gm_in = best_op["gm_in"] + gamma_in = gen_params["gamma"] + gamma_load = gen_params["gamma"] + load_scale = best_op["load_scale"] + Av = best_op["Av"] + gds = best_op["gds"] + cdd = best_op["cdd"] + vnoise_squared_input_referred_max = vnoise_input_referred_max**2 + vnoise_squared_input_referred_per_scale = ( + 4 * k * T * (gamma_in * gm_in + gamma_load * gm_load) / (gm_in**2) + ) + scale_noise = max( + 1, vnoise_squared_input_referred_per_scale / vnoise_squared_input_referred_max + ) gbw_min = bw_min * Av * stage1_gain - scale_bw = max(1, 2 * np.pi * gbw_min * cload / (gm_in - 2 * np.pi * gbw_min * (cdd+cgg_in))) - print('2nd stage scale_noise:') + scale_bw = max( + 1, 2 * np.pi * gbw_min * cload / (gm_in - 2 * np.pi * gbw_min * (cdd + cgg_in)) + ) + print("2nd stage scale_noise:") pprint.pprint(scale_noise) - print('2nd stage scale_bw:') + print("2nd stage scale_bw:") pprint.pprint(scale_bw) - - scale_amp = max(scale_bw,scale_noise) - - vnoise_density_squared_input_referred = vnoise_squared_input_referred_per_scale / scale_amp + + scale_amp = max(scale_bw, scale_noise) + + vnoise_density_squared_input_referred = ( + vnoise_squared_input_referred_per_scale / scale_amp + ) vnoise_density_input_referred = np.sqrt(vnoise_density_squared_input_referred) - + amplifier_op = dict( - gm = gm_in * scale_amp, - gds = gds * scale_amp, - cgg = cgg_in * scale_amp, - cdd = cdd * scale_amp, - vnoise_density_input = vnoise_density_input_referred, - scale_in = scale_amp, - scale_load = scale_amp * load_scale, - load_bias = load_bias, - lch_load = best_op['lch_load'], - gain = Av, - ibias = ibias * scale_amp, - bw = (gds * scale_amp) / (2 * np.pi * (cload + (cdd * scale_amp))), - ) - - stage2AmpParams=Stage2AmpParams( - in_params = MosParams(w= 500 * m * int(np.round(amplifier_op['scale_in'])), l=gen_params['lch_in'], nf=int(np.round(amplifier_op['scale_in']))), - load_params = MosParams(w= 500 * m * int(np.round(amplifier_op['scale_load'])), l=amplifier_op['lch_load'], nf=int(np.round(amplifier_op['scale_load']))), - in_type = amp_specs['in_type'], - load_type = amp_specs['load_type'], - voutcm_ideal = voutcm * 1000 * m, - v_load = amplifier_op['load_bias'] * 1000 * m, - ) - - params = Stage2AmpTbParams(pvt=Pvt(), vc=vincm * 1000 * m, vd=1 * m, dut=stage2AmpParams, cl = amp_specs['cload'] * 1000 * m) + gm=gm_in * scale_amp, + gds=gds * scale_amp, + cgg=cgg_in * scale_amp, + cdd=cdd * scale_amp, + vnoise_density_input=vnoise_density_input_referred, + scale_in=scale_amp, + scale_load=scale_amp * load_scale, + load_bias=load_bias, + lch_load=best_op["lch_load"], + gain=Av, + ibias=ibias * scale_amp, + bw=(gds * scale_amp) / (2 * np.pi * (cload + (cdd * scale_amp))), + ) + + stage2AmpParams = Stage2AmpParams( + in_params=MosParams( + w=500 * m * int(np.round(amplifier_op["scale_in"])), + l=gen_params["lch_in"], + nf=int(np.round(amplifier_op["scale_in"])), + ), + load_params=MosParams( + w=500 * m * int(np.round(amplifier_op["scale_load"])), + l=amplifier_op["lch_load"], + nf=int(np.round(amplifier_op["scale_load"])), + ), + in_type=amp_specs["in_type"], + load_type=amp_specs["load_type"], + voutcm_ideal=voutcm * 1000 * m, + v_load=amplifier_op["load_bias"] * 1000 * m, + ) + + params = Stage2AmpTbParams( + pvt=Pvt(), + vc=vincm * 1000 * m, + vd=1 * m, + dut=stage2AmpParams, + cl=amp_specs["cload"] * 1000 * m, + ) # Create our simulation input @hs.sim class Stage2AmpTranSim: tb = Stage2AmpTbTran(params) - tr = hs.Tran(tstop=3000 * n, tstep=1*n) + tr = hs.Tran(tstop=3000 * n, tstep=1 * n) # Add the PDK dependencies - Stage2AmpTranSim.lib(sky130.install.model_lib, 'tt') + Stage2AmpTranSim.lib(sky130.install.model_lib, "tt") Stage2AmpTranSim.literal(".save all") results = Stage2AmpTranSim.run(sim_options) tran_results = results.an[0].data - t = tran_results['time'] - v_out_diff = tran_results['v(xtop.out_p)'] - tran_results['v(xtop.out_n)'] - v_out_cm = (tran_results['v(xtop.out_p)'] + tran_results['v(xtop.out_n)'])/2 - v_in_diff = tran_results['v(xtop.inp_p)'] - tran_results['v(xtop.inp_n)'] - v_in_cm = (tran_results['v(xtop.inp_p)'] + tran_results['v(xtop.inp_n)'])/2 - v_load = tran_results['v(xtop.v_load)'] - - #v_out_stage1_diff = tran_results['v(xtop.out_stage1_p)'] - tran_results['v(xtop.out_stage1_n)'] - - + t = tran_results["time"] + v_out_diff = tran_results["v(xtop.out_p)"] - tran_results["v(xtop.out_n)"] + v_out_cm = (tran_results["v(xtop.out_p)"] + tran_results["v(xtop.out_n)"]) / 2 + v_in_diff = tran_results["v(xtop.inp_p)"] - tran_results["v(xtop.inp_n)"] + v_in_cm = (tran_results["v(xtop.inp_p)"] + tran_results["v(xtop.inp_n)"]) / 2 + v_load = tran_results["v(xtop.v_load)"] + + # v_out_stage1_diff = tran_results['v(xtop.out_stage1_p)'] - tran_results['v(xtop.out_stage1_n)'] + fig, ax = plt.subplots(2, sharex=True) ax[0].plot(t, v_in_diff) ax[1].plot(t, v_out_diff) - + plt.show() - + fig, ax = plt.subplots(2, sharex=True) ax[0].plot(t, v_in_cm) ax[1].plot(t, v_out_cm) - + plt.show() - + fig, ax = plt.subplots(2, sharex=True) ax[0].plot(t, v_load) ax[1].plot(t, v_out_cm) - + plt.show() - + return stage2AmpParams, amplifier_op + def generate_stage2_common_source_amplifier(amp_specs): nch_db_filename = "database_nch.npy" nch_lvt_db_filename = "database_nch_lvt.npy" pch_db_filename = "database_pch.npy" pch_lvt_db_filename = "database_pch_lvt.npy" - + gen_params = dict( - tail_vstar_vds_margin = 50e-3, - vgs_sweep_res = 5e-3, - vds_sweep_res = 15e-3, - load_scale_min = 0.25, - load_scale_max = 2, - load_scale_step = 0.25, - gamma = 1, - cdd_cgg_ratio = 1.1, - lch_in = 0.5, - lch_load = [0.35, 0.5, 1], - ) - - if amp_specs['in_type'] == 'nch': + tail_vstar_vds_margin=50e-3, + vgs_sweep_res=5e-3, + vds_sweep_res=15e-3, + load_scale_min=0.25, + load_scale_max=2, + load_scale_step=0.25, + gamma=1, + cdd_cgg_ratio=1.1, + lch_in=0.5, + lch_load=[0.35, 0.5, 1], + ) + + if amp_specs["in_type"] == "nch": in_db_filename = nch_db_filename - elif amp_specs['in_type'] == 'nch_lvt': + elif amp_specs["in_type"] == "nch_lvt": in_db_filename = nch_lvt_db_filename - elif amp_specs['in_type'] == 'pch': + elif amp_specs["in_type"] == "pch": in_db_filename = pch_db_filename - elif amp_specs['in_type'] == 'pch_lvt': + elif amp_specs["in_type"] == "pch_lvt": in_db_filename = pch_lvt_db_filename - - if amp_specs['load_type'] == 'nch': + + if amp_specs["load_type"] == "nch": load_db_filename = nch_db_filename - elif amp_specs['load_type'] == 'nch_lvt': + elif amp_specs["load_type"] == "nch_lvt": load_db_filename = nch_lvt_db_filename - elif amp_specs['load_type'] == 'pch': + elif amp_specs["load_type"] == "pch": load_db_filename = pch_db_filename - elif amp_specs['load_type'] == 'pch_lvt': + elif amp_specs["load_type"] == "pch_lvt": load_db_filename = pch_lvt_db_filename - - database_in = np.load(in_db_filename,allow_pickle=True).item() - database_load = np.load(load_db_filename,allow_pickle=True).item() - ampParams, amplifier_op = stage2_common_source_amplifier_design_and_scale_amp(database_in,database_load,amp_specs,gen_params) - - print('2nd stage op:') + + database_in = np.load(in_db_filename, allow_pickle=True).item() + database_load = np.load(load_db_filename, allow_pickle=True).item() + ampParams, amplifier_op = stage2_common_source_amplifier_design_and_scale_amp( + database_in, database_load, amp_specs, gen_params + ) + + print("2nd stage op:") pprint.pprint(amplifier_op) - + return ampParams, amplifier_op + @h.paramclass class TwoStageAmpParams: stage1_tail_params = h.Param(dtype=MosParams, desc="Stage 1 Tail MOS params") - stage1_in_base_pair_params = h.Param(dtype=MosParams, desc="Stage 1 Input pair MOS params") - stage1_in_casc_pair_params = h.Param(dtype=MosParams, desc="Stage 1 Input cascode MOS params") - stage1_load_base_pair_params = h.Param(dtype=MosParams, desc="Stage 1 Load base MOS params") - stage1_load_casc_pair_params = h.Param(dtype=MosParams, desc="Stage 1 Load cascode MOS params") + stage1_in_base_pair_params = h.Param( + dtype=MosParams, desc="Stage 1 Input pair MOS params" + ) + stage1_in_casc_pair_params = h.Param( + dtype=MosParams, desc="Stage 1 Input cascode MOS params" + ) + stage1_load_base_pair_params = h.Param( + dtype=MosParams, desc="Stage 1 Load base MOS params" + ) + stage1_load_casc_pair_params = h.Param( + dtype=MosParams, desc="Stage 1 Load cascode MOS params" + ) stage1_in_type = h.Param(dtype=str, desc="Stage 1 Input MOS type") stage1_load_type = h.Param(dtype=str, desc="Stage 1 Load MOS type") stage1_tail_type = h.Param(dtype=str, desc="Stage 1 Tail MOS type") @@ -1370,46 +1671,47 @@ class TwoStageAmpParams: c_comp = h.Param(dtype=Prefixed, desc="Compensation C Value") r_comp = h.Param(dtype=Prefixed, desc="Compensation R Value") + @h.generator def two_stage_amplifier(params: TwoStageAmpParams) -> h.Module: - - if params.stage2_in_type == 'nch': + + if params.stage2_in_type == "nch": stage2_mos_in = nch - elif params.stage2_in_type == 'nch_lvt': + elif params.stage2_in_type == "nch_lvt": stage2_mos_in = nch_lvt - elif params.stage2_in_type == 'pch': + elif params.stage2_in_type == "pch": stage2_mos_in = pch - elif params.stage2_in_type == 'pch_lvt': + elif params.stage2_in_type == "pch_lvt": stage2_mos_in = pch_lvt - - if params.stage2_load_type == 'nch': + + if params.stage2_load_type == "nch": stage2_mos_load = nch - elif params.stage2_load_type == 'nch_lvt': + elif params.stage2_load_type == "nch_lvt": stage2_mos_load = nch_lvt - elif params.stage2_load_type == 'pch': + elif params.stage2_load_type == "pch": stage2_mos_load = pch - elif params.stage2_load_type == 'pch_lvt': + elif params.stage2_load_type == "pch_lvt": stage2_mos_load = pch_lvt - - stage1Params=TelescopicAmpParams( - in_base_pair_params = params.stage1_in_base_pair_params, - in_casc_pair_params = params.stage1_in_casc_pair_params, - load_base_pair_params = params.stage1_load_base_pair_params, - load_casc_pair_params = params.stage1_load_casc_pair_params, - tail_params = params.stage1_tail_params, - in_type = params.stage1_in_type, - load_type = params.stage1_load_type, - tail_type = params.stage1_tail_type, - voutcm_ideal = params.stage1_voutcm_ideal, - v_load = params.stage1_v_load, - v_pcasc = params.stage1_v_pcasc, - v_ncasc = params.stage1_v_ncasc, - v_tail = params.stage1_v_tail, + + stage1Params = TelescopicAmpParams( + in_base_pair_params=params.stage1_in_base_pair_params, + in_casc_pair_params=params.stage1_in_casc_pair_params, + load_base_pair_params=params.stage1_load_base_pair_params, + load_casc_pair_params=params.stage1_load_casc_pair_params, + tail_params=params.stage1_tail_params, + in_type=params.stage1_in_type, + load_type=params.stage1_load_type, + tail_type=params.stage1_tail_type, + voutcm_ideal=params.stage1_voutcm_ideal, + v_load=params.stage1_v_load, + v_pcasc=params.stage1_v_pcasc, + v_ncasc=params.stage1_v_ncasc, + v_tail=params.stage1_v_tail, ) @h.module class TwoStageAmplifier: - + VDD, VSS = h.Ports(2) v_in = Diff(port=True, role=Diff.Roles.SINK) v_out = Diff(port=True, role=Diff.Roles.SOURCE) @@ -1421,7 +1723,7 @@ class TwoStageAmplifier: v_pcasc_stage1 = h.Input() v_ncasc_stage1 = h.Input() v_load_stage2 = h.Input() - + x_stage1 = stage1_telescopic_amplifier(stage1Params)( v_in=v_in, v_out=v_out_stage1, @@ -1430,23 +1732,33 @@ class TwoStageAmplifier: v_pcasc=v_pcasc_stage1, v_load=v_load_stage1, VDD=VDD, - VSS=VSS) - + VSS=VSS, + ) + C_comp_p = C(C.Params(c=params.c_comp))(p=v_out.p, n=v_comp_mid_p) C_comp_n = C(C.Params(c=params.c_comp))(p=v_out.n, n=v_comp_mid_n) R_comp_p = R(R.Params(r=params.r_comp))(p=v_comp_mid_p, n=v_out_stage1.n) R_comp_n = R(R.Params(r=params.r_comp))(p=v_comp_mid_n, n=v_out_stage1.p) - + ## Stage 2 Input Devices - m_in_stage2_p = stage2_mos_in(params.stage2_in_params)(g=v_out_stage1.n, s=VSS, d=v_out.p, b=VSS) - m_in_stage2_n = stage2_mos_in(params.stage2_in_params)(g=v_out_stage1.p, s=VSS, d=v_out.n, b=VSS) - + m_in_stage2_p = stage2_mos_in(params.stage2_in_params)( + g=v_out_stage1.n, s=VSS, d=v_out.p, b=VSS + ) + m_in_stage2_n = stage2_mos_in(params.stage2_in_params)( + g=v_out_stage1.p, s=VSS, d=v_out.n, b=VSS + ) + ## Load Base Pair - m_load_stage2_p = stage2_mos_load(params.stage2_load_params)(g=v_load_stage2, s=VDD, d=v_out.p, b=VDD) - m_load_stage2_n = stage2_mos_load(params.stage2_load_params)(g=v_load_stage2, s=VDD, d=v_out.n, b=VDD) - + m_load_stage2_p = stage2_mos_load(params.stage2_load_params)( + g=v_load_stage2, s=VDD, d=v_out.p, b=VDD + ) + m_load_stage2_n = stage2_mos_load(params.stage2_load_params)( + g=v_load_stage2, s=VDD, d=v_out.n, b=VDD + ) + return TwoStageAmplifier + @h.paramclass class TwoStageTbParams: dut = h.Param(dtype=TwoStageAmpParams, desc="DUT params") @@ -1456,43 +1768,64 @@ class TwoStageTbParams: vd = h.Param(dtype=h.Prefixed, desc="Differential Voltage (V)", default=1 * m) vc = h.Param(dtype=h.Prefixed, desc="Common-Mode Voltage (V)", default=1200 * m) cl = h.Param(dtype=h.Prefixed, desc="Load Cap (Single-Ended) (F)", default=100 * f) - ccm = h.Param(dtype=h.Prefixed, desc="Common Mode Sensing Capacitor (F)", default = 1 * f) - rcm = h.Param(dtype=h.Prefixed, desc="Common Mode Sensing Resistor (Ω)", default = 1 * G) - CMFB_gain_stage1 = h.Param(dtype=h.Prefixed, desc="Common Mode Feedback Gain (V/V)", default = 10 * KILO) - CMFB_gain_stage2 = h.Param(dtype=h.Prefixed, desc="Common Mode Feedback Gain (V/V)", default = 1 * KILO) + ccm = h.Param( + dtype=h.Prefixed, desc="Common Mode Sensing Capacitor (F)", default=1 * f + ) + rcm = h.Param( + dtype=h.Prefixed, desc="Common Mode Sensing Resistor (Ω)", default=1 * G + ) + CMFB_gain_stage1 = h.Param( + dtype=h.Prefixed, desc="Common Mode Feedback Gain (V/V)", default=10 * KILO + ) + CMFB_gain_stage2 = h.Param( + dtype=h.Prefixed, desc="Common Mode Feedback Gain (V/V)", default=1 * KILO + ) + @h.generator def TwoStageAmpTbTran(params: TwoStageTbParams) -> h.Module: - + tb = h.sim.tb("TwoStageAmplifierTbTran") tb.VDD = VDD = h.Signal() - tb.vvdd = Vdc(Vdc.Params(dc=params.pvt.v,ac=(0*m)))(p=VDD, n=tb.VSS) - + tb.vvdd = Vdc(Vdc.Params(dc=params.pvt.v, ac=(0 * m)))(p=VDD, n=tb.VSS) + tb.v_tail_stage1 = h.Signal() - tb.v_tail_stage1_src = Vdc(Vdc.Params(dc=(params.dut.stage1_v_tail),ac=(0*m)))(p=tb.v_tail_stage1, n=tb.VSS) - + tb.v_tail_stage1_src = Vdc(Vdc.Params(dc=(params.dut.stage1_v_tail), ac=(0 * m)))( + p=tb.v_tail_stage1, n=tb.VSS + ) + tb.v_pcasc_stage1 = h.Signal() - tb.v_pcasc_stage1_src = Vdc(Vdc.Params(dc=(params.dut.stage1_v_pcasc),ac=(0*m)))(p=tb.v_pcasc_stage1, n=tb.VSS) - + tb.v_pcasc_stage1_src = Vdc(Vdc.Params(dc=(params.dut.stage1_v_pcasc), ac=(0 * m)))( + p=tb.v_pcasc_stage1, n=tb.VSS + ) + tb.v_ncasc_stage1 = h.Signal() - tb.v_ncasc_stage1_src = Vdc(Vdc.Params(dc=(params.dut.stage1_v_ncasc),ac=(0*m)))(p=tb.v_ncasc_stage1, n=tb.VSS) - + tb.v_ncasc_stage1_src = Vdc(Vdc.Params(dc=(params.dut.stage1_v_ncasc), ac=(0 * m)))( + p=tb.v_ncasc_stage1, n=tb.VSS + ) + tb.v_load_stage1 = h.Signal() - + tb.v_load_stage2 = h.Signal() tb.voutcm_stage1_ideal = h.Signal() - tb.v_outcm_stage1_ideal_src = Vdc(Vdc.Params(dc=(params.dut.stage1_voutcm_ideal),ac=(0*m)))(p=tb.voutcm_stage1_ideal, n=tb.VSS) - + tb.v_outcm_stage1_ideal_src = Vdc( + Vdc.Params(dc=(params.dut.stage1_voutcm_ideal), ac=(0 * m)) + )(p=tb.voutcm_stage1_ideal, n=tb.VSS) + tb.voutcm_stage2_ideal = h.Signal() - tb.v_outcm_stage2_ideal_src = Vdc(Vdc.Params(dc=(params.dut.stage2_voutcm_ideal),ac=(0*m)))(p=tb.voutcm_stage2_ideal, n=tb.VSS) - + tb.v_outcm_stage2_ideal_src = Vdc( + Vdc.Params(dc=(params.dut.stage2_voutcm_ideal), ac=(0 * m)) + )(p=tb.voutcm_stage2_ideal, n=tb.VSS) + # Input-driving balun tb.inp = Diff() tb.inpgen = DiffClkGen( - DiffClkParams(period= 1000 * n, delay=1 * n, vc=params.vc, vd=params.vd, trf=800 * PICO) + DiffClkParams( + period=1000 * n, delay=1 * n, vc=params.vc, vd=params.vd, trf=800 * PICO + ) )(ck=tb.inp, VSS=tb.VSS) - + # Output & Load Caps tb.out = Diff() tb.out_stage1 = Diff() @@ -1509,9 +1842,13 @@ def TwoStageAmpTbTran(params: TwoStageTbParams) -> h.Module: tb.rcmn_stage1 = Rload(p=tb.out_stage1.n, n=tb.CMSense_stage1) tb.rcmp_stage2 = Rload(p=tb.out.p, n=tb.CMSense_stage2) tb.rcmn_stage2 = Rload(p=tb.out.n, n=tb.CMSense_stage2) - tb.cmfb_stage1_src = Vcvs(Vcvs.Params(gain=params.CMFB_gain_stage1))(p=tb.v_load_stage1, n=tb.VSS, cp=tb.CMSense_stage1, cn=tb.voutcm_stage1_ideal) - tb.cmfb_stage2_src = Vcvs(Vcvs.Params(gain=params.CMFB_gain_stage2))(p=tb.v_load_stage2, n=tb.VSS, cp=tb.CMSense_stage2, cn=tb.voutcm_stage2_ideal) - + tb.cmfb_stage1_src = Vcvs(Vcvs.Params(gain=params.CMFB_gain_stage1))( + p=tb.v_load_stage1, n=tb.VSS, cp=tb.CMSense_stage1, cn=tb.voutcm_stage1_ideal + ) + tb.cmfb_stage2_src = Vcvs(Vcvs.Params(gain=params.CMFB_gain_stage2))( + p=tb.v_load_stage2, n=tb.VSS, cp=tb.CMSense_stage2, cn=tb.voutcm_stage2_ideal + ) + # Create the Telescopic Amplifier DUT tb.dut = two_stage_amplifier(params.dut)( v_in=tb.inp, @@ -1523,41 +1860,52 @@ def TwoStageAmpTbTran(params: TwoStageTbParams) -> h.Module: v_load_stage1=tb.v_load_stage1, v_load_stage2=tb.v_load_stage2, VDD=VDD, - VSS=tb.VSS) + VSS=tb.VSS, + ) return tb + @h.generator def TwoStageAmpTbAc(params: TwoStageTbParams) -> h.Module: - + tb = h.sim.tb("TwoStageAmplifierTbTran") tb.VDD = VDD = h.Signal() - tb.vvdd = Vdc(Vdc.Params(dc=params.pvt.v,ac=(0*m)))(p=VDD, n=tb.VSS) - + tb.vvdd = Vdc(Vdc.Params(dc=params.pvt.v, ac=(0 * m)))(p=VDD, n=tb.VSS) + tb.v_tail_stage1 = h.Signal() - tb.v_tail_stage1_src = Vdc(Vdc.Params(dc=(params.dut.stage1_v_tail),ac=(0*m)))(p=tb.v_tail_stage1, n=tb.VSS) - + tb.v_tail_stage1_src = Vdc(Vdc.Params(dc=(params.dut.stage1_v_tail), ac=(0 * m)))( + p=tb.v_tail_stage1, n=tb.VSS + ) + tb.v_pcasc_stage1 = h.Signal() - tb.v_pcasc_stage1_src = Vdc(Vdc.Params(dc=(params.dut.stage1_v_pcasc),ac=(0*m)))(p=tb.v_pcasc_stage1, n=tb.VSS) - + tb.v_pcasc_stage1_src = Vdc(Vdc.Params(dc=(params.dut.stage1_v_pcasc), ac=(0 * m)))( + p=tb.v_pcasc_stage1, n=tb.VSS + ) + tb.v_ncasc_stage1 = h.Signal() - tb.v_ncasc_stage1_src = Vdc(Vdc.Params(dc=(params.dut.stage1_v_ncasc),ac=(0*m)))(p=tb.v_ncasc_stage1, n=tb.VSS) - + tb.v_ncasc_stage1_src = Vdc(Vdc.Params(dc=(params.dut.stage1_v_ncasc), ac=(0 * m)))( + p=tb.v_ncasc_stage1, n=tb.VSS + ) + tb.v_load_stage1 = h.Signal() - + tb.v_load_stage2 = h.Signal() tb.voutcm_stage1_ideal = h.Signal() - tb.v_outcm_stage1_ideal_src = Vdc(Vdc.Params(dc=(params.dut.stage1_voutcm_ideal),ac=(0*m)))(p=tb.voutcm_stage1_ideal, n=tb.VSS) - + tb.v_outcm_stage1_ideal_src = Vdc( + Vdc.Params(dc=(params.dut.stage1_voutcm_ideal), ac=(0 * m)) + )(p=tb.voutcm_stage1_ideal, n=tb.VSS) + tb.voutcm_stage2_ideal = h.Signal() - tb.v_outcm_stage2_ideal_src = Vdc(Vdc.Params(dc=(params.dut.stage2_voutcm_ideal),ac=(0*m)))(p=tb.voutcm_stage2_ideal, n=tb.VSS) - + tb.v_outcm_stage2_ideal_src = Vdc( + Vdc.Params(dc=(params.dut.stage2_voutcm_ideal), ac=(0 * m)) + )(p=tb.voutcm_stage2_ideal, n=tb.VSS) + # Input-driving balun tb.inp = Diff() - tb.inpgen = Vdc(Vdc.Params(dc=(params.vc),ac=(500*m)))(p=tb.inp.p, n=tb.VSS) - tb.inngen = Vdc(Vdc.Params(dc=(params.vc),ac=(-500*m)))(p=tb.inp.n, n=tb.VSS) - - + tb.inpgen = Vdc(Vdc.Params(dc=(params.vc), ac=(500 * m)))(p=tb.inp.p, n=tb.VSS) + tb.inngen = Vdc(Vdc.Params(dc=(params.vc), ac=(-500 * m)))(p=tb.inp.n, n=tb.VSS) + # Output & Load Caps tb.out = Diff() tb.out_stage1 = Diff() @@ -1574,9 +1922,13 @@ def TwoStageAmpTbAc(params: TwoStageTbParams) -> h.Module: tb.rcmn_stage1 = Rload(p=tb.out_stage1.n, n=tb.CMSense_stage1) tb.rcmp_stage2 = Rload(p=tb.out.p, n=tb.CMSense_stage2) tb.rcmn_stage2 = Rload(p=tb.out.n, n=tb.CMSense_stage2) - tb.cmfb_stage1_src = Vcvs(Vcvs.Params(gain=params.CMFB_gain_stage1))(p=tb.v_load_stage1, n=tb.VSS, cp=tb.CMSense_stage1, cn=tb.voutcm_stage1_ideal) - tb.cmfb_stage2_src = Vcvs(Vcvs.Params(gain=params.CMFB_gain_stage2))(p=tb.v_load_stage2, n=tb.VSS, cp=tb.CMSense_stage2, cn=tb.voutcm_stage2_ideal) - + tb.cmfb_stage1_src = Vcvs(Vcvs.Params(gain=params.CMFB_gain_stage1))( + p=tb.v_load_stage1, n=tb.VSS, cp=tb.CMSense_stage1, cn=tb.voutcm_stage1_ideal + ) + tb.cmfb_stage2_src = Vcvs(Vcvs.Params(gain=params.CMFB_gain_stage2))( + p=tb.v_load_stage2, n=tb.VSS, cp=tb.CMSense_stage2, cn=tb.voutcm_stage2_ideal + ) + # Create the Telescopic Amplifier DUT tb.dut = two_stage_amplifier(params.dut)( v_in=tb.inp, @@ -1588,207 +1940,209 @@ def TwoStageAmpTbAc(params: TwoStageTbParams) -> h.Module: v_load_stage1=tb.v_load_stage1, v_load_stage2=tb.v_load_stage2, VDD=VDD, - VSS=tb.VSS) + VSS=tb.VSS, + ) return tb - + + def generate_two_stage_ota(): two_stage_amp_specs = dict( - stage1_in_type='nch_lvt', - stage1_tail_type='nch_lvt', - stage1_load_type='pch', - stage1_vstar_in = 200e-3, + stage1_in_type="nch_lvt", + stage1_tail_type="nch_lvt", + stage1_load_type="pch", + stage1_vstar_in=200e-3, stage1_vincm=1, stage1_voutcm=0.9, stage1_vds_tail_min=0.25, stage1_gain_min=100, stage1_input_stage_gain_min=200, stage1_bw_min=20e6, - - stage2_in_type='nch', - stage2_load_type='pch', + stage2_in_type="nch", + stage2_load_type="pch", stage2_voutcm=1, - - bw_min = 5e5, - gain_min = 500, + bw_min=5e5, + gain_min=500, vnoise_input_referred=10e-9, - cload=1000e-15, rload=100e3, vdd=1.8, - ) - + ) + stage1_telescopic_amp_specs = dict( - in_type=two_stage_amp_specs['stage1_in_type'], - tail_type=two_stage_amp_specs['stage1_tail_type'], - load_type=two_stage_amp_specs['stage1_load_type'], + in_type=two_stage_amp_specs["stage1_in_type"], + tail_type=two_stage_amp_specs["stage1_tail_type"], + load_type=two_stage_amp_specs["stage1_load_type"], cload=100e-15, - vdd=two_stage_amp_specs['vdd'], - vstar_in = two_stage_amp_specs['stage1_vstar_in'], - vincm=two_stage_amp_specs['stage1_vincm'], - voutcm=two_stage_amp_specs['stage1_voutcm'], - vds_tail_min=two_stage_amp_specs['stage1_vds_tail_min'], - gain_min=two_stage_amp_specs['stage1_gain_min'], - input_stage_gain_min=two_stage_amp_specs['stage1_input_stage_gain_min'], - selfbw_min=two_stage_amp_specs['stage1_bw_min'], - bw_min=two_stage_amp_specs['stage1_bw_min'], - vnoise_input_referred=two_stage_amp_specs['vnoise_input_referred'] * (two_stage_amp_specs['gain_min']/(two_stage_amp_specs['stage1_input_stage_gain_min'] + two_stage_amp_specs['gain_min'])), - ) - - telescopic_amp_params, telescopic_amp_results = generate_stage1_telescopic_amplifier(stage1_telescopic_amp_specs) - + vdd=two_stage_amp_specs["vdd"], + vstar_in=two_stage_amp_specs["stage1_vstar_in"], + vincm=two_stage_amp_specs["stage1_vincm"], + voutcm=two_stage_amp_specs["stage1_voutcm"], + vds_tail_min=two_stage_amp_specs["stage1_vds_tail_min"], + gain_min=two_stage_amp_specs["stage1_gain_min"], + input_stage_gain_min=two_stage_amp_specs["stage1_input_stage_gain_min"], + selfbw_min=two_stage_amp_specs["stage1_bw_min"], + bw_min=two_stage_amp_specs["stage1_bw_min"], + vnoise_input_referred=two_stage_amp_specs["vnoise_input_referred"] + * ( + two_stage_amp_specs["gain_min"] + / ( + two_stage_amp_specs["stage1_input_stage_gain_min"] + + two_stage_amp_specs["gain_min"] + ) + ), + ) + + ( + telescopic_amp_params, + telescopic_amp_results, + ) = generate_stage1_telescopic_amplifier(stage1_telescopic_amp_specs) + stage2_common_source_amp_specs = dict( - in_type=two_stage_amp_specs['stage2_in_type'], - load_type=two_stage_amp_specs['stage2_load_type'], - cload=two_stage_amp_specs['cload'], + in_type=two_stage_amp_specs["stage2_in_type"], + load_type=two_stage_amp_specs["stage2_load_type"], + cload=two_stage_amp_specs["cload"], rload=100e3, - vdd=two_stage_amp_specs['vdd'], - vincm=two_stage_amp_specs['stage1_voutcm'], - voutcm=two_stage_amp_specs['stage2_voutcm'], - stage1_gain=telescopic_amp_results['Av'], - gain_min=two_stage_amp_specs['gain_min']/telescopic_amp_results['Av'], - amp_bw_min=two_stage_amp_specs['bw_min'], - vnoise_input_referred=two_stage_amp_specs['vnoise_input_referred'] * (telescopic_amp_results['Av']/(telescopic_amp_results['Av'] + two_stage_amp_specs['gain_min'])) * telescopic_amp_results['Av'], + vdd=two_stage_amp_specs["vdd"], + vincm=two_stage_amp_specs["stage1_voutcm"], + voutcm=two_stage_amp_specs["stage2_voutcm"], + stage1_gain=telescopic_amp_results["Av"], + gain_min=two_stage_amp_specs["gain_min"] / telescopic_amp_results["Av"], + amp_bw_min=two_stage_amp_specs["bw_min"], + vnoise_input_referred=two_stage_amp_specs["vnoise_input_referred"] + * ( + telescopic_amp_results["Av"] + / (telescopic_amp_results["Av"] + two_stage_amp_specs["gain_min"]) ) - - stage2_params, stage2_op = generate_stage2_common_source_amplifier(stage2_common_source_amp_specs) - - twoStageAmpParams=TwoStageAmpParams( - stage1_in_base_pair_params = telescopic_amp_params.in_base_pair_params, - stage1_in_casc_pair_params = telescopic_amp_params.in_casc_pair_params, - stage1_load_base_pair_params = telescopic_amp_params.load_base_pair_params, - stage1_load_casc_pair_params = telescopic_amp_params.load_casc_pair_params, - stage1_tail_params = telescopic_amp_params.tail_params, - stage1_in_type = telescopic_amp_params.in_type, - stage1_load_type = telescopic_amp_params.load_type, - stage1_tail_type = telescopic_amp_params.tail_type, - stage1_voutcm_ideal = telescopic_amp_params.voutcm_ideal, - stage1_v_load = telescopic_amp_params.v_load, - stage1_v_pcasc = telescopic_amp_params.v_pcasc, - stage1_v_ncasc = telescopic_amp_params.v_ncasc, - stage1_v_tail = telescopic_amp_params.v_tail, - stage2_in_type = stage2_params.in_type, - stage2_load_type = stage2_params.load_type, - stage2_in_params = stage2_params.in_params, - stage2_load_params = stage2_params.load_params, - stage2_v_load = stage2_params.v_load, - stage2_voutcm_ideal = stage2_params.voutcm_ideal, - c_comp = 250 * FEMTO, - r_comp = 1 * KILO, - ) - - params = TwoStageTbParams(pvt=Pvt(), vc=two_stage_amp_specs['stage1_vincm'] * 1000 * m, vd=1 * m, dut=twoStageAmpParams, cl = two_stage_amp_specs['cload'] * 1000 * m) + * telescopic_amp_results["Av"], + ) + + stage2_params, stage2_op = generate_stage2_common_source_amplifier( + stage2_common_source_amp_specs + ) + + twoStageAmpParams = TwoStageAmpParams( + stage1_in_base_pair_params=telescopic_amp_params.in_base_pair_params, + stage1_in_casc_pair_params=telescopic_amp_params.in_casc_pair_params, + stage1_load_base_pair_params=telescopic_amp_params.load_base_pair_params, + stage1_load_casc_pair_params=telescopic_amp_params.load_casc_pair_params, + stage1_tail_params=telescopic_amp_params.tail_params, + stage1_in_type=telescopic_amp_params.in_type, + stage1_load_type=telescopic_amp_params.load_type, + stage1_tail_type=telescopic_amp_params.tail_type, + stage1_voutcm_ideal=telescopic_amp_params.voutcm_ideal, + stage1_v_load=telescopic_amp_params.v_load, + stage1_v_pcasc=telescopic_amp_params.v_pcasc, + stage1_v_ncasc=telescopic_amp_params.v_ncasc, + stage1_v_tail=telescopic_amp_params.v_tail, + stage2_in_type=stage2_params.in_type, + stage2_load_type=stage2_params.load_type, + stage2_in_params=stage2_params.in_params, + stage2_load_params=stage2_params.load_params, + stage2_v_load=stage2_params.v_load, + stage2_voutcm_ideal=stage2_params.voutcm_ideal, + c_comp=250 * FEMTO, + r_comp=1 * KILO, + ) + + params = TwoStageTbParams( + pvt=Pvt(), + vc=two_stage_amp_specs["stage1_vincm"] * 1000 * m, + vd=1 * m, + dut=twoStageAmpParams, + cl=two_stage_amp_specs["cload"] * 1000 * m, + ) # Create our simulation input @hs.sim class TwoStageAmpTranSim: tb = TwoStageAmpTbTran(params) - tr = hs.Tran(tstop=3000 * n, tstep=1*n) + tr = hs.Tran(tstop=3000 * n, tstep=1 * n) # Add the PDK dependencies - TwoStageAmpTranSim.lib(sky130.install.model_lib, 'tt') + TwoStageAmpTranSim.lib(sky130.install.model_lib, "tt") TwoStageAmpTranSim.literal(".save all") results = TwoStageAmpTranSim.run(sim_options) tran_results = results.an[0].data - t = tran_results['time'] - v_out_diff = tran_results['v(xtop.out_p)'] - tran_results['v(xtop.out_n)'] - v_out_cm = (tran_results['v(xtop.out_p)'] + tran_results['v(xtop.out_n)'])/2 - v_in_diff = tran_results['v(xtop.inp_p)'] - tran_results['v(xtop.inp_n)'] - v_in_cm = (tran_results['v(xtop.inp_p)'] + tran_results['v(xtop.inp_n)'])/2 - - v_out_stage1_diff = tran_results['v(xtop.out_stage1_p)'] - tran_results['v(xtop.out_stage1_n)'] - v_out_stage1_cm = (tran_results['v(xtop.out_stage1_p)'] + tran_results['v(xtop.out_stage1_n)'])/2 - - + t = tran_results["time"] + v_out_diff = tran_results["v(xtop.out_p)"] - tran_results["v(xtop.out_n)"] + v_out_cm = (tran_results["v(xtop.out_p)"] + tran_results["v(xtop.out_n)"]) / 2 + v_in_diff = tran_results["v(xtop.inp_p)"] - tran_results["v(xtop.inp_n)"] + v_in_cm = (tran_results["v(xtop.inp_p)"] + tran_results["v(xtop.inp_n)"]) / 2 + + v_out_stage1_diff = ( + tran_results["v(xtop.out_stage1_p)"] - tran_results["v(xtop.out_stage1_n)"] + ) + v_out_stage1_cm = ( + tran_results["v(xtop.out_stage1_p)"] + tran_results["v(xtop.out_stage1_n)"] + ) / 2 + fig, ax = plt.subplots(2, sharex=True) ax[0].plot(t, v_in_diff) ax[1].plot(t, v_out_diff) - + plt.show() - + fig, ax = plt.subplots(2, sharex=True) ax[0].plot(t, v_in_cm) ax[1].plot(t, v_out_cm) - + plt.show() - + fig, ax = plt.subplots(2, sharex=True) ax[0].plot(t, v_out_stage1_cm) ax[1].plot(t, v_out_stage1_diff) - + plt.show() - + # Create our simulation input @hs.sim class TwoStageAmpAcSim: tb = TwoStageAmpTbAc(params) myac = hs.Ac(sweep=LogSweep(1e1, 1e11, 10)) - + # Add the PDK dependencies - TwoStageAmpAcSim.lib(sky130.install.model_lib, 'tt') + TwoStageAmpAcSim.lib(sky130.install.model_lib, "tt") TwoStageAmpAcSim.literal(".save all") results = TwoStageAmpAcSim.run(sim_options) ac_results = results.an[0].data - v_out_diff_ac = ac_results['v(xtop.out_p)'] - ac_results['v(xtop.out_n)'] + v_out_diff_ac = ac_results["v(xtop.out_p)"] - ac_results["v(xtop.out_n)"] f = np.logspace(start=1, stop=11, num=101, endpoint=True) f_ax = np.logspace(start=1, stop=11, num=11, endpoint=True) - f3db_idx = np.squeeze(np.where(abs(v_out_diff_ac) < np.max(abs(v_out_diff_ac))/np.sqrt(2)))[0] + f3db_idx = np.squeeze( + np.where(abs(v_out_diff_ac) < np.max(abs(v_out_diff_ac)) / np.sqrt(2)) + )[0] fgbw_idx = np.squeeze(np.where(abs(v_out_diff_ac) < 1))[0] f3db = f[f3db_idx] fgbw = f[fgbw_idx] Avdc = np.max(abs(v_out_diff_ac)) v_out_diff_ac_dB = 20 * np.log10(abs(v_out_diff_ac)) - v_out_diff_ac_phase = (np.angle(v_out_diff_ac) % (2*np.pi) - 2*np.pi) * 180 / np.pi + v_out_diff_ac_phase = ( + (np.angle(v_out_diff_ac) % (2 * np.pi) - 2 * np.pi) * 180 / np.pi + ) PM_ac = 180 + v_out_diff_ac_phase[fgbw_idx] fig, ax = plt.subplots(2, sharex=True) ax[0].semilogx(f, v_out_diff_ac_dB) - ax[0].grid(color='black', linestyle='--', linewidth=0.5) + ax[0].grid(color="black", linestyle="--", linewidth=0.5) ax[0].grid(visible=True, which="both") ax[0].set_xticks(f_ax) ax[1].semilogx(f, v_out_diff_ac_phase) - ax[1].grid(color='black', linestyle='--', linewidth=0.5) + ax[1].grid(color="black", linestyle="--", linewidth=0.5) ax[1].grid(visible=True, which="both") ax[1].set_xticks(f_ax) plt.show() print("Av (AC Sim) = %f" % ((Avdc))) - print("BW (AC Sim) = %f MHz" % (round(f3db/1e6,2))) - print("GBW (AC Sim) = %f MHz" % (round(fgbw/1e6,2))) + print("BW (AC Sim) = %f MHz" % (round(f3db / 1e6, 2))) + print("GBW (AC Sim) = %f MHz" % (round(fgbw / 1e6, 2))) print("PM (AC Sim) = %f degrees" % PM_ac) - + ampResults = dict( - Av = Avdc, - bw = f3db, - gbw = fgbw, - pm = PM_ac, - ) + Av=Avdc, + bw=f3db, + gbw=fgbw, + pm=PM_ac, + ) -if __name__ == '__main__': +if __name__ == "__main__": generate_two_stage_ota() breakpoint() - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/adc/tests/test_adc.py b/adc/tests/test_adc.py index c4f45a5..64fc203 100644 --- a/adc/tests/test_adc.py +++ b/adc/tests/test_adc.py @@ -1,17 +1,30 @@ -import numpy as np -from pathlib import Path +import numpy as np +from pathlib import Path -data_dir = Path("./data") # Path to the repo-level `data/` directory +data_dir = Path("./data") # Path to the repo-level `data/` directory + def test_db(): - """ Test `database_query`""" - from ..database_query import MosDB, nch_db_filename, nch_lvt_db_filename, pch_db_filename, pch_lvt_db_filename + """Test `database_query`""" + from ..database_query import ( + MosDB, + nch_db_filename, + nch_lvt_db_filename, + pch_db_filename, + pch_lvt_db_filename, + ) + db = MosDB() - for filename in [nch_db_filename, nch_lvt_db_filename, pch_db_filename, pch_lvt_db_filename]: + for filename in [ + nch_db_filename, + nch_lvt_db_filename, + pch_db_filename, + pch_lvt_db_filename, + ]: data = np.load(data_dir / filename, allow_pickle=True) db.build(data_dir / filename) -if __name__ == '__main__': +if __name__ == "__main__": test_db() diff --git a/pyproject.toml b/pyproject.toml index 2741206..f59b5dc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ scipy = "*" nest_asyncio = "*" [tool.poetry.dev-dependencies] +pre-commit = "*" black = "22.6.0" pytest = "7.1.2" diff --git a/scripts/characterize_technology.py b/scripts/characterize_technology.py index c41beb9..c87da0e 100644 --- a/scripts/characterize_technology.py +++ b/scripts/characterize_technology.py @@ -1,4 +1,3 @@ - from pathlib import Path import hdl21 as h @@ -12,8 +11,8 @@ import scipy.interpolate import nest_asyncio -nest_asyncio.apply() +nest_asyncio.apply() sim_options = SimOptions( @@ -22,17 +21,18 @@ simulator=SupportedSimulators.NGSPICE, ) -data_dir = Path("./data") # Path to the repo-level `data/` directory -np_filename = 'database_nch.npy' -tb_prefix = 'tb_mos_ibias' -mos_list = ['nch'] +data_dir = Path("./data") # Path to the repo-level `data/` directory +np_filename = "database_nch.npy" +tb_prefix = "tb_mos_ibias" +mos_list = ["nch"] w_unit = 0.5 lch_list = [0.15, 1] -vgs_list = np.linspace(0,1.8,num=21) -vds_list = np.linspace(0,1.8,num=21) +vgs_list = np.linspace(0, 1.8, num=21) +vds_list = np.linspace(0, 1.8, num=21) vbs_list = [0, -0.9, -1.8] + @h.paramclass class MosParams: w = h.Param(dtype=float, desc="Channel Width") @@ -41,13 +41,27 @@ class MosParams: nch = h.ExternalModule( - name="sky130_fd_pr__nfet_01v8_lvt", desc="Sky130 NMOS", - port_list=[h.Inout(name="D"), h.Inout(name="G"), h.Inout(name="S"), h.Inout(name="B")], - paramtype=MosParams) + name="sky130_fd_pr__nfet_01v8_lvt", + desc="Sky130 NMOS", + port_list=[ + h.Inout(name="D"), + h.Inout(name="G"), + h.Inout(name="S"), + h.Inout(name="B"), + ], + paramtype=MosParams, +) pch = h.ExternalModule( - name="sky130_fd_pr__pfet_01v8_lvt", desc="Sky130 PMOS", - port_list=[h.Inout(name="D"), h.Inout(name="G"), h.Inout(name="S"), h.Inout(name="B")], - paramtype=MosParams) + name="sky130_fd_pr__pfet_01v8_lvt", + desc="Sky130 PMOS", + port_list=[ + h.Inout(name="D"), + h.Inout(name="G"), + h.Inout(name="S"), + h.Inout(name="B"), + ], + paramtype=MosParams, +) @h.paramclass @@ -66,23 +80,72 @@ class CommonSource: vout = h.Output() nmos = nch(params.nmos_params)(D=vout, G=vin, S=VSS, B=VSS) res = h.Resistor(r=params.res_value)(p=VDD, n=vout) - + return CommonSource - + def get_tb_name(mos_type, lch): - lch_int = int(round(lch*1e3)) - return '%s_%s_%dnm' % (tb_prefix, mos_type, lch_int) + lch_int = int(round(lch * 1e3)) + return "%s_%s_%dnm" % (tb_prefix, mos_type, lch_int) + def run_characterization_sims(np_filename): - - ids = np.zeros([np.size(mos_list),np.size(lch_list),np.size(vbs_list),np.size(vgs_list),np.size(vds_list)]) - vth = np.zeros([np.size(mos_list),np.size(lch_list),np.size(vbs_list),np.size(vgs_list),np.size(vds_list)]) - cgg = np.zeros([np.size(mos_list),np.size(lch_list),np.size(vbs_list),np.size(vgs_list),np.size(vds_list)]) - cdd = np.zeros([np.size(mos_list),np.size(lch_list),np.size(vbs_list),np.size(vgs_list),np.size(vds_list)]) - gm = np.zeros([np.size(mos_list),np.size(lch_list),np.size(vbs_list),np.size(vgs_list),np.size(vds_list)]) - gds = np.zeros([np.size(mos_list),np.size(lch_list),np.size(vbs_list),np.size(vgs_list),np.size(vds_list)]) - + + ids = np.zeros( + [ + np.size(mos_list), + np.size(lch_list), + np.size(vbs_list), + np.size(vgs_list), + np.size(vds_list), + ] + ) + vth = np.zeros( + [ + np.size(mos_list), + np.size(lch_list), + np.size(vbs_list), + np.size(vgs_list), + np.size(vds_list), + ] + ) + cgg = np.zeros( + [ + np.size(mos_list), + np.size(lch_list), + np.size(vbs_list), + np.size(vgs_list), + np.size(vds_list), + ] + ) + cdd = np.zeros( + [ + np.size(mos_list), + np.size(lch_list), + np.size(vbs_list), + np.size(vgs_list), + np.size(vds_list), + ] + ) + gm = np.zeros( + [ + np.size(mos_list), + np.size(lch_list), + np.size(vbs_list), + np.size(vgs_list), + np.size(vds_list), + ] + ) + gds = np.zeros( + [ + np.size(mos_list), + np.size(lch_list), + np.size(vbs_list), + np.size(vgs_list), + np.size(vds_list), + ] + ) + for mos_type_index in range(np.size(mos_list)): mos_type = mos_list[mos_type_index] for lch_index in range(np.size(lch_list)): @@ -95,121 +158,224 @@ def run_characterization_sims(np_filename): vds = vds_list[vds_index] print("Starting simulation for:") print(mos_type) - print("L = " + str(int(lch*1e3)) + "nm") + print("L = " + str(int(lch * 1e3)) + "nm") print("vgs = " + str(vgs) + "V") print("vds = " + str(vds) + "V") print("vbs = " + str(vbs) + "V") - tb_name = get_tb_name(mos_type,lch) + tb_name = get_tb_name(mos_type, lch) tb = h.sim.tb(tb_name) tb.VDS = h.Signal() tb.VGS = h.Signal() tb.VBS = h.Signal() if mos_type == "nch": - tb.dut = nch(MosParams(w=w_unit, l=lch, nf=1))(D=tb.VDS, G=tb.VGS, S=tb.VSS, B=tb.VBS) - tb.VDS_src = Vdc(Vdc.Params(dc=str(vds)))(p=tb.VDS, n=tb.VSS) - tb.VGS_src = Vdc(Vdc.Params(dc=str(vgs)))(p=tb.VGS, n=tb.VSS) - tb.VBS_src = Vdc(Vdc.Params(dc=str(vbs)))(p=tb.VBS, n=tb.VSS) + tb.dut = nch(MosParams(w=w_unit, l=lch, nf=1))( + D=tb.VDS, G=tb.VGS, S=tb.VSS, B=tb.VBS + ) + tb.VDS_src = Vdc(Vdc.Params(dc=str(vds)))( + p=tb.VDS, n=tb.VSS + ) + tb.VGS_src = Vdc(Vdc.Params(dc=str(vgs)))( + p=tb.VGS, n=tb.VSS + ) + tb.VBS_src = Vdc(Vdc.Params(dc=str(vbs)))( + p=tb.VBS, n=tb.VSS + ) elif mos_type == "pch": - tb.dut = pch(MosParams(w=w_unit, l=lch, nf=1))(D=tb.VDS, G=tb.VGS, S=tb.VSS, B=tb.VBS) - tb.VDS_src = Vdc(Vdc.Params(dc=str(-vds)))(p=tb.VDS, n=tb.VSS) - tb.VGS_src = Vdc(Vdc.Params(dc=str(-vgs)))(p=tb.VGS, n=tb.VSS) - tb.VBS_src = Vdc(Vdc.Params(dc=str(-vbs)))(p=tb.VBS, n=tb.VSS) + tb.dut = pch(MosParams(w=w_unit, l=lch, nf=1))( + D=tb.VDS, G=tb.VGS, S=tb.VSS, B=tb.VBS + ) + tb.VDS_src = Vdc(Vdc.Params(dc=str(-vds)))( + p=tb.VDS, n=tb.VSS + ) + tb.VGS_src = Vdc(Vdc.Params(dc=str(-vgs)))( + p=tb.VGS, n=tb.VSS + ) + tb.VBS_src = Vdc(Vdc.Params(dc=str(-vbs)))( + p=tb.VBS, n=tb.VSS + ) sim = h.sim.Sim(tb=tb) - sim.lib(sky130.install.model_lib, 'tt') + sim.lib(sky130.install.model_lib, "tt") sim.op() if mos_type == "nch": - sim.literal(".save @m.xtop.xdut.msky130_fd_pr__nfet_01v8_lvt[gm]") - sim.literal(".save @m.xtop.xdut.msky130_fd_pr__nfet_01v8_lvt[gds]") - sim.literal(".save @m.xtop.xdut.msky130_fd_pr__nfet_01v8_lvt[cgg]") - sim.literal(".save @m.xtop.xdut.msky130_fd_pr__nfet_01v8_lvt[cdd]") - sim.literal(".save @m.xtop.xdut.msky130_fd_pr__nfet_01v8_lvt[cdb]") + sim.literal( + ".save @m.xtop.xdut.msky130_fd_pr__nfet_01v8_lvt[gm]" + ) + sim.literal( + ".save @m.xtop.xdut.msky130_fd_pr__nfet_01v8_lvt[gds]" + ) + sim.literal( + ".save @m.xtop.xdut.msky130_fd_pr__nfet_01v8_lvt[cgg]" + ) + sim.literal( + ".save @m.xtop.xdut.msky130_fd_pr__nfet_01v8_lvt[cdd]" + ) + sim.literal( + ".save @m.xtop.xdut.msky130_fd_pr__nfet_01v8_lvt[cdb]" + ) sim.literal(".save all") sim_results = sim.run(sim_options) - ids[mos_type_index,lch_index,vbs_index,vgs_index,vds_index] = -sim_results[0].data['i(v.xtop.vvds_src)'] - cgg[mos_type_index,lch_index,vbs_index,vgs_index,vds_index] = sim_results[0].data['@m.xtop.xdut.msky130_fd_pr__nfet_01v8_lvt[cgg]'] - cdd[mos_type_index,lch_index,vbs_index,vgs_index,vds_index] = sim_results[0].data['@m.xtop.xdut.msky130_fd_pr__nfet_01v8_lvt[cdd]'] - cdd[mos_type_index,lch_index,vbs_index,vgs_index,vds_index] = sim_results[0].data['@m.xtop.xdut.msky130_fd_pr__nfet_01v8_lvt[cdb]'] - gm[mos_type_index,lch_index,vbs_index,vgs_index,vds_index] = sim_results[0].data['@m.xtop.xdut.msky130_fd_pr__nfet_01v8_lvt[gm]'] - gds[mos_type_index,lch_index,vbs_index,vgs_index,vds_index] = sim_results[0].data['@m.xtop.xdut.msky130_fd_pr__nfet_01v8_lvt[gds]'] + ids[ + mos_type_index, + lch_index, + vbs_index, + vgs_index, + vds_index, + ] = -sim_results[0].data["i(v.xtop.vvds_src)"] + cgg[ + mos_type_index, + lch_index, + vbs_index, + vgs_index, + vds_index, + ] = sim_results[0].data[ + "@m.xtop.xdut.msky130_fd_pr__nfet_01v8_lvt[cgg]" + ] + cdd[ + mos_type_index, + lch_index, + vbs_index, + vgs_index, + vds_index, + ] = sim_results[0].data[ + "@m.xtop.xdut.msky130_fd_pr__nfet_01v8_lvt[cdd]" + ] + cdd[ + mos_type_index, + lch_index, + vbs_index, + vgs_index, + vds_index, + ] = sim_results[0].data[ + "@m.xtop.xdut.msky130_fd_pr__nfet_01v8_lvt[cdb]" + ] + gm[ + mos_type_index, + lch_index, + vbs_index, + vgs_index, + vds_index, + ] = sim_results[0].data[ + "@m.xtop.xdut.msky130_fd_pr__nfet_01v8_lvt[gm]" + ] + gds[ + mos_type_index, + lch_index, + vbs_index, + vgs_index, + vds_index, + ] = sim_results[0].data[ + "@m.xtop.xdut.msky130_fd_pr__nfet_01v8_lvt[gds]" + ] elif mos_type == "pch": - sim.literal(".save @m.xtop.xdut.msky130_fd_pr__pfet_01v8_lvt[gm]") - sim.literal(".save @m.xtop.xdut.msky130_fd_pr__pfet_01v8_lvt[gds]") - sim.literal(".save @m.xtop.xdut.msky130_fd_pr__pfet_01v8_lvt[cgg]") - sim.literal(".save @m.xtop.xdut.msky130_fd_pr__pfet_01v8_lvt[cdd]") - sim.literal(".save @m.xtop.xdut.msky130_fd_pr__pfet_01v8_lvt[cdb]") + sim.literal( + ".save @m.xtop.xdut.msky130_fd_pr__pfet_01v8_lvt[gm]" + ) + sim.literal( + ".save @m.xtop.xdut.msky130_fd_pr__pfet_01v8_lvt[gds]" + ) + sim.literal( + ".save @m.xtop.xdut.msky130_fd_pr__pfet_01v8_lvt[cgg]" + ) + sim.literal( + ".save @m.xtop.xdut.msky130_fd_pr__pfet_01v8_lvt[cdd]" + ) + sim.literal( + ".save @m.xtop.xdut.msky130_fd_pr__pfet_01v8_lvt[cdb]" + ) sim.literal(".save all") sim_results = sim.run(sim_options) - ids[mos_type_index,lch_index,vbs_index,vgs_index,vds_index] = sim_results[0].data['i(v.xtop.vvds_src)'] - cgg[mos_type_index,lch_index,vbs_index,vgs_index,vds_index] = sim_results[0].data['@m.xtop.xdut.msky130_fd_pr__pfet_01v8_lvt[cgg]'] - cdd[mos_type_index,lch_index,vbs_index,vgs_index,vds_index] = sim_results[0].data['@m.xtop.xdut.msky130_fd_pr__pfet_01v8_lvt[cdd]'] - cdd[mos_type_index,lch_index,vbs_index,vgs_index,vds_index] = sim_results[0].data['@m.xtop.xdut.msky130_fd_pr__pfet_01v8_lvt[cdb]'] - gm[mos_type_index,lch_index,vbs_index,vgs_index,vds_index] = sim_results[0].data['@m.xtop.xdut.msky130_fd_pr__pfet_01v8_lvt[gm]'] - gds[mos_type_index,lch_index,vbs_index,vgs_index,vds_index] = sim_results[0].data['@m.xtop.xdut.msky130_fd_pr__pfet_01v8_lvt[gds]'] - - - + ids[ + mos_type_index, + lch_index, + vbs_index, + vgs_index, + vds_index, + ] = sim_results[0].data["i(v.xtop.vvds_src)"] + cgg[ + mos_type_index, + lch_index, + vbs_index, + vgs_index, + vds_index, + ] = sim_results[0].data[ + "@m.xtop.xdut.msky130_fd_pr__pfet_01v8_lvt[cgg]" + ] + cdd[ + mos_type_index, + lch_index, + vbs_index, + vgs_index, + vds_index, + ] = sim_results[0].data[ + "@m.xtop.xdut.msky130_fd_pr__pfet_01v8_lvt[cdd]" + ] + cdd[ + mos_type_index, + lch_index, + vbs_index, + vgs_index, + vds_index, + ] = sim_results[0].data[ + "@m.xtop.xdut.msky130_fd_pr__pfet_01v8_lvt[cdb]" + ] + gm[ + mos_type_index, + lch_index, + vbs_index, + vgs_index, + vds_index, + ] = sim_results[0].data[ + "@m.xtop.xdut.msky130_fd_pr__pfet_01v8_lvt[gm]" + ] + gds[ + mos_type_index, + lch_index, + vbs_index, + vgs_index, + vds_index, + ] = sim_results[0].data[ + "@m.xtop.xdut.msky130_fd_pr__pfet_01v8_lvt[gds]" + ] + print("a") results = { - "keys" : "[mos_list_index,lch_list_idnex,vbs_list_index,vgs_list_index,vds_list_index]", - "mos_list" : mos_list, - "lch_list" : lch_list, - "vbs_list" : vbs_list, - "vgs_list" : vgs_list, - "vds_list" : vds_list, - "ids" : ids, - "gm" : gm, - "gds" : gds, - "cgg" : cgg, - "cdd" : cdd, - } - np.save(data_dir / np_filename,results) + "keys": "[mos_list_index,lch_list_idnex,vbs_list_index,vgs_list_index,vds_list_index]", + "mos_list": mos_list, + "lch_list": lch_list, + "vbs_list": vbs_list, + "vgs_list": vgs_list, + "vds_list": vds_list, + "ids": ids, + "gm": gm, + "gds": gds, + "cgg": cgg, + "cdd": cdd, + } + np.save(data_dir / np_filename, results) return results -def compute_small_signal_parameters(filename,plot_results=True): - results = np.load(filename,allow_pickle=True).item() - - ids_raw = results['ids'][0,0,0,:,:] - vgs_raw = results['vgs_list'] - vds_raw = results['vds_list'] - gm_raw = results['gm'][0,0,0,:,:] - + +def compute_small_signal_parameters(filename, plot_results=True): + results = np.load(filename, allow_pickle=True).item() + + ids_raw = results["ids"][0, 0, 0, :, :] + vgs_raw = results["vgs_list"] + vds_raw = results["vds_list"] + gm_raw = results["gm"][0, 0, 0, :, :] + ids_spline = scipy.interpolate.RectBivariateSpline(vgs_raw, vds_raw, ids_raw) - gm_spline = ids_spline.partial_derivative(1,0) - gds_spline = ids_spline.partial_derivative(0,1) - + gm_spline = ids_spline.partial_derivative(1, 0) + gds_spline = ids_spline.partial_derivative(0, 1) + if plot_results: fig = plt.figure() - ax = fig.gca(projection='3d') - vds, vgs = np.meshgrid(vds_raw,vgs_raw) - ax.plot_surface(vds,vgs,gm_raw,cmap=cm.jet) - + ax = fig.gca(projection="3d") + vds, vgs = np.meshgrid(vds_raw, vgs_raw) + ax.plot_surface(vds, vgs, gm_raw, cmap=cm.jet) + breakpoint() -if __name__ == '__main__': +if __name__ == "__main__": run_characterization_sims(np_filename) compute_small_signal_parameters(np_filename) - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From d6e51ec14a24172d5bd2556d5f1ae2a95321e11d Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Thu, 5 Jan 2023 23:49:27 +0000 Subject: [PATCH 06/10] Break out some tests. Opamp generator, yeah, not yet. --- README.md | 27 ++-- adc/strongarm.py | 242 +--------------------------- adc/test_amp.py | 0 adc/test_strongarm.py | 250 +++++++++++++++++++++++++++++ scripts/characterize_technology.py | 2 +- 5 files changed, 268 insertions(+), 253 deletions(-) create mode 100644 adc/test_amp.py create mode 100644 adc/test_strongarm.py diff --git a/README.md b/README.md index 010422e..43e7a85 100644 --- a/README.md +++ b/README.md @@ -2,22 +2,27 @@ A Continuous Time (CT) Delta Sigma (DS) ADC Generator -Installation: +Installation in the [Docker](./Dockerfile)-configured GitHub dev-container environment: ``` +# The packages containing the PDK model-files are conda-only, +# and hence not covered by the `pip install` below. +conda activate base conda install -y -c litex-hub open_pdks.sky130a -git clone git@github.com:dan-fritchman/Hdl21.git -cd Hdl21 -git checkout 8eef2239920e4bf61e3b378f028e985d1d41a106 -cd .. - -git clone git@github.com:Vlsir/Vlsir.git -cd Vlsir -git checkout 2079c44fd7b34023c737a7c01ea624b50289524f -cd .. -python scripts/manage.py install +# There *should* be compatible versions of VLSIR and Hdl21 on PyPi. +# If not, install them from source with something like: +##git clone git@github.com:dan-fritchman/Hdl21.git +##cd Hdl21 +##git checkout 8eef2239920e4bf61e3b378f028e985d1d41a106 +##cd .. +##git clone git@github.com:Vlsir/Vlsir.git +##cd Vlsir +##git checkout 2079c44fd7b34023c737a7c01ea624b50289524f +##cd .. +##python scripts/manage.py install +# Pip-install all the other, "normal" dependencies conda upgrade -y pip pip install -e ".[dev]" ``` diff --git a/adc/strongarm.py b/adc/strongarm.py index c304042..c8b1225 100644 --- a/adc/strongarm.py +++ b/adc/strongarm.py @@ -1,67 +1,6 @@ -import matplotlib.pyplot as plt -import numpy as np -import os -from pathlib import Path - import hdl21 as h -import hdl21.sim as hs from hdl21 import Diff -from hdl21.pdk import Corner -from hdl21.sim import Sim, LogSweep -from hdl21.prefix import m, µ, f, n, PICO -from hdl21.primitives import Vdc, Idc, C, Vpulse -from vlsirtools.spice import SimOptions, SupportedSimulators, ResultFormat -import sitepdks, sky130 - -sim_options = SimOptions( - rundir=Path("./scratch"), - fmt=ResultFormat.SIM_DATA, - simulator=SupportedSimulators.NGSPICE, -) - - -@h.paramclass -class DiffClkParams: - """Differential Clock Generator Parameters""" - - period = h.Param(dtype=hs.ParamVal, desc="Period") - delay = h.Param(dtype=hs.ParamVal, desc="Delay") - vd = h.Param(dtype=hs.ParamVal, desc="Differential Voltage") - vc = h.Param(dtype=hs.ParamVal, desc="Common-Mode Voltage") - trf = h.Param(dtype=hs.ParamVal, desc="Rise / Fall Time") - - -@h.generator -def DiffClkGen(p: DiffClkParams) -> h.Module: - """# Differential Clock Generator - For simulation, from ideal pulse voltage sources""" - - ckg = h.Module() - ckg.VSS = VSS = h.Port() - ckg.ck = ck = Diff(role=Diff.Roles.SINK, port=True) - - def vparams(polarity: bool) -> Vpulse.Params: - """Closure to create the pulse-source parameters for each differential half. - Argument `polarity` is True for positive half, False for negative half.""" - # Initially create the voltage levels for the positive half - v1 = p.vc + p.vd / 2 - v2 = p.vc - p.vd / 2 - if not polarity: # And for the negative half, swap them - v1, v2 = v2, v1 - return Vpulse.Params( - v1=v1, - v2=v2, - period=p.period, - rise=p.trf, - fall=p.trf, - width=p.period / 2 - p.trf, - delay=p.delay, - ) - - # Create the two complementary pulse-sources - ckg.vp = Vpulse(vparams(True))(p=ck.p, n=VSS) - ckg.vn = Vpulse(vparams(False))(p=ck.n, n=VSS) - return ckg +from hdl21.primitives import Vdc @h.paramclass @@ -239,182 +178,3 @@ class Comparator: sr = sr_latch(params.latch)(inp=sout, out=out, VDD=VDD, VSS=VSS) return Comparator - - -""" -# Comparator Tests -""" - - -@h.paramclass -class Pvt: - """Process, Voltage, and Temperature Parameters""" - - p = h.Param(dtype=Corner, desc="Process Corner", default=Corner.TYP) - v = h.Param(dtype=h.Prefixed, desc="Supply Voltage Value (V)", default=1800 * m) - t = h.Param(dtype=int, desc="Simulation Temperature (C)", default=25) - - -@h.paramclass -class TbParams: - dut = h.Param(dtype=ComparatorParams, desc="DUT params") - pvt = h.Param( - dtype=Pvt, desc="Process, Voltage, and Temperature Parameters", default=Pvt() - ) - vd = h.Param(dtype=h.Prefixed, desc="Differential Voltage (V)", default=100 * m) - vc = h.Param(dtype=h.Prefixed, desc="Common-Mode Voltage (V)", default=900 * m) - cl = h.Param(dtype=h.Prefixed, desc="Load Cap (Single-Ended) (F)", default=5 * f) - - -@h.generator -def ComparatorTb(p: TbParams) -> h.Module: - """Comparator Testbench""" - - # Create our testbench - tb = h.sim.tb("SlicerTb") - # Generate and drive VDD - tb.VDD = VDD = h.Signal() - tb.vvdd = Vdc(Vdc.Params(dc=p.pvt.v))(p=VDD, n=tb.VSS) - - # Input-driving balun - tb.inp = Diff() - tb.inpgen = DiffClkGen( - DiffClkParams(period=4 * n, delay=1 * n, vc=p.vc, vd=p.vd, trf=800 * PICO) - )(ck=tb.inp, VSS=tb.VSS) - - # Clock generator - tb.clk = clk = h.Signal() - tb.vclk = Vpulse( - Vpulse.Params( - delay=0, - v1=0, - v2=p.pvt.v, - period=2 * n, - rise=100 * PICO, - fall=100 * PICO, - width=1 * n, - ) - )(p=clk, n=tb.VSS) - - # Output & Load Caps - tb.out = Diff() - Cload = C(C.Params(c=p.cl)) - tb.clp = Cload(p=tb.out.p, n=tb.VSS) - tb.cln = Cload(p=tb.out.n, n=tb.VSS) - - # Create the Slicer DUT - tb.dut = comparator(p.dut)(inp=tb.inp, out=tb.out, clk=clk, VDD=VDD, VSS=tb.VSS) - return tb - - -def test_comparator_sim(): - """Comparator Test(s)""" - - w = 1.0 - l = 0.15 - nf = 2 - comparator_params = ComparatorParams( - strongarm=StrongarmParams( - tail=MosParams(w=w * nf * 2, l=l, nf=2 * nf), - inp_pair=MosParams(w=w * nf, l=l, nf=nf), - inv_n=MosParams(w=w * nf, l=l, nf=nf), - inv_p=MosParams(w=w * nf, l=l, nf=nf), - reset=MosParams(w=w * nf * 2, l=l, nf=2 * nf), - meas_vs=True, - ), - latch=LatchParams( - nor_pi=MosParams(w=w * nf, l=l, nf=nf), - nor_pfb=MosParams(w=w * nf, l=l, nf=nf), - nor_ni=MosParams(w=w * nf, l=l, nf=nf), - nor_nfb=MosParams(w=w * nf, l=l, nf=nf), - ), - ) - # Create our parametric testbench - params = TbParams(pvt=Pvt(), vc=900 * m, vd=1 * m, dut=comparator_params) - - # Create our simulation input - @hs.sim - class ComparatorSim: - tb = ComparatorTb(params) - tr = hs.Tran(tstop=12 * n, tstep=100 * PICO) - - # Add the PDK dependencies - ComparatorSim.lib(sky130.install.model_lib, "tt") - ComparatorSim.literal(".option METHOD=Gear") - - # Run Spice, save important results - results = ComparatorSim.run(sim_options) - tran_results = results.an[0].data - np.savez( - "strongarm_results.npz", - t=tran_results["time"], - v_out_diff=tran_results["v(xtop.out_p)"] - tran_results["v(xtop.out_n)"], - v_in_diff=tran_results["v(xtop.inp_p)"] - tran_results["v(xtop.inp_n)"], - v_clk=tran_results["v(xtop.clk)"], - i_tail=tran_results["i(v.xtop.xdut.xsa.vtail_meas)"], - i_inp_pair_cm=tran_results["i(v.xtop.xdut.xsa.vninp_meas)"] - + tran_results["i(v.xtop.xdut.xsa.vninn_meas)"], - i_latch_n_pair_cm=tran_results["i(v.xtop.xdut.xsa.vnlatn_meas)"] - + tran_results["i(v.xtop.xdut.xsa.vnlatp_meas)"], - i_latch_p_pair_cm=tran_results["i(v.xtop.xdut.xsa.vplatn_meas)"] - + tran_results["i(v.xtop.xdut.xsa.vplatp_meas)"], - v_casc_cm=( - tran_results["v(xtop.xdut.xsa.ninn_meas_p)"] - + tran_results["v(xtop.xdut.xsa.ninp_meas_p)"] - ) - / 2, - v_out_cm=( - tran_results["v(xtop.xdut.sout_p)"] + tran_results["v(xtop.xdut.sout_n)"] - ) - / 2, - ) - - -def extract_windows(t, clk, threshold): - """Given the clock waveform, this will extract a bunch of single periods - that are the periods at which a certain clock starts and ends""" - clk_above_thres_idcs = np.where(clk > threshold)[0] - print(clk_above_thres_idcs.shape) - print(np.diff(clk_above_thres_idcs)) - rising_cross_idcs = clk_above_thres_idcs[ - np.where(np.diff(np.concatenate(([0], clk_above_thres_idcs), axis=0)) > 1)[0] - ] - rising_cross_idcs = rising_cross_idcs.tolist() + [len(clk)] - return [(s, e) for s, e in zip(rising_cross_idcs[:-1], rising_cross_idcs[1:])] - - -def plot_windows(): - data = np.load("strongarm_results.npz") - windows = extract_windows(data["t"], data["v_clk"], 0.7) - for (s, e) in windows: - plt.figure() - plt.plot(data["t"][s:e], data["v_clk"][s:e]) - plt.show() - - -def plot_data(): - data = np.load("strongarm_results.npz") - t = data["t"] - fig, ax = plt.subplots(3, sharex=True) - ax[0].plot(t, data["v_clk"]) - ax[1].plot(t, data["v_in_diff"]) - ax[2].plot(t, data["v_out_diff"]) - fig, ax = plt.subplots(3, sharex=True) - ax[0].plot(t, data["v_clk"]) - ax[1].plot(t, data["i_tail"], label="tail current") - ax[1].plot(t, data["i_inp_pair_cm"], label="input pair current") - ax[1].plot(t, data["i_latch_n_pair_cm"], label="Latch NMOS current") - ax[1].plot(t, data["i_latch_p_pair_cm"], label="Latch PMOS current") - ax[1].legend() - ax[2].plot(t, data["v_casc_cm"], label="v_casc_cm") - ax[2].plot(t, data["v_out_cm"], label="v_out_cm") - ax[2].legend() - - plt.show() - breakpoint() - - -if __name__ == "__main__": - # test_comparator_sim() - # plot_data() - plot_windows() diff --git a/adc/test_amp.py b/adc/test_amp.py new file mode 100644 index 0000000..e69de29 diff --git a/adc/test_strongarm.py b/adc/test_strongarm.py new file mode 100644 index 0000000..fc3af0e --- /dev/null +++ b/adc/test_strongarm.py @@ -0,0 +1,250 @@ +""" +# Comparator Tests +""" + +import matplotlib.pyplot as plt +import numpy as np +import os +from pathlib import Path + +import hdl21 as h +import hdl21.sim as hs +from hdl21 import Diff +from hdl21.pdk import Corner +from hdl21.prefix import m, f, n, PICO +from hdl21.primitives import Vdc, Idc, C, Vpulse +from vlsirtools.spice import SimOptions, SupportedSimulators, ResultFormat +import sky130, sitepdks as _ + +sim_options = SimOptions( + rundir=Path("./scratch"), + fmt=ResultFormat.SIM_DATA, + simulator=SupportedSimulators.NGSPICE, +) + +# Local DUT Imports +from .strongarm import ( + comparator, + ComparatorParams, + StrongarmParams, + MosParams, + LatchParams, +) + + +@h.paramclass +class DiffClkParams: + """Differential Clock Generator Parameters""" + + period = h.Param(dtype=hs.ParamVal, desc="Period") + delay = h.Param(dtype=hs.ParamVal, desc="Delay") + vd = h.Param(dtype=hs.ParamVal, desc="Differential Voltage") + vc = h.Param(dtype=hs.ParamVal, desc="Common-Mode Voltage") + trf = h.Param(dtype=hs.ParamVal, desc="Rise / Fall Time") + + +@h.generator +def DiffClkGen(p: DiffClkParams) -> h.Module: + """# Differential Clock Generator + For simulation, from ideal pulse voltage sources""" + + ckg = h.Module() + ckg.VSS = VSS = h.Port() + ckg.ck = ck = Diff(role=Diff.Roles.SINK, port=True) + + def vparams(polarity: bool) -> Vpulse.Params: + """Closure to create the pulse-source parameters for each differential half. + Argument `polarity` is True for positive half, False for negative half.""" + # Initially create the voltage levels for the positive half + v1 = p.vc + p.vd / 2 + v2 = p.vc - p.vd / 2 + if not polarity: # And for the negative half, swap them + v1, v2 = v2, v1 + return Vpulse.Params( + v1=v1, + v2=v2, + period=p.period, + rise=p.trf, + fall=p.trf, + width=p.period / 2 - p.trf, + delay=p.delay, + ) + + # Create the two complementary pulse-sources + ckg.vp = Vpulse(vparams(True))(p=ck.p, n=VSS) + ckg.vn = Vpulse(vparams(False))(p=ck.n, n=VSS) + return ckg + + +@h.paramclass +class Pvt: + """Process, Voltage, and Temperature Parameters""" + + p = h.Param(dtype=Corner, desc="Process Corner", default=Corner.TYP) + v = h.Param(dtype=h.Prefixed, desc="Supply Voltage Value (V)", default=1800 * m) + t = h.Param(dtype=int, desc="Simulation Temperature (C)", default=25) + + +@h.paramclass +class TbParams: + dut = h.Param(dtype=ComparatorParams, desc="DUT params") + pvt = h.Param( + dtype=Pvt, desc="Process, Voltage, and Temperature Parameters", default=Pvt() + ) + vd = h.Param(dtype=h.Prefixed, desc="Differential Voltage (V)", default=100 * m) + vc = h.Param(dtype=h.Prefixed, desc="Common-Mode Voltage (V)", default=900 * m) + cl = h.Param(dtype=h.Prefixed, desc="Load Cap (Single-Ended) (F)", default=5 * f) + + +@h.generator +def ComparatorTb(p: TbParams) -> h.Module: + """Comparator Testbench""" + + # Create our testbench + tb = h.sim.tb("SlicerTb") + # Generate and drive VDD + tb.VDD = VDD = h.Signal() + tb.vvdd = Vdc(Vdc.Params(dc=p.pvt.v))(p=VDD, n=tb.VSS) + + # Input-driving balun + tb.inp = Diff() + tb.inpgen = DiffClkGen( + DiffClkParams(period=4 * n, delay=1 * n, vc=p.vc, vd=p.vd, trf=800 * PICO) + )(ck=tb.inp, VSS=tb.VSS) + + # Clock generator + tb.clk = clk = h.Signal() + tb.vclk = Vpulse( + Vpulse.Params( + delay=0, + v1=0, + v2=p.pvt.v, + period=2 * n, + rise=100 * PICO, + fall=100 * PICO, + width=1 * n, + ) + )(p=clk, n=tb.VSS) + + # Output & Load Caps + tb.out = Diff() + Cload = C(C.Params(c=p.cl)) + tb.clp = Cload(p=tb.out.p, n=tb.VSS) + tb.cln = Cload(p=tb.out.n, n=tb.VSS) + + # Create the Slicer DUT + tb.dut = comparator(p.dut)(inp=tb.inp, out=tb.out, clk=clk, VDD=VDD, VSS=tb.VSS) + return tb + + +def test_comparator_sim(): + """Comparator Test(s)""" + + w = 1.0 + l = 0.15 + nf = 2 + comparator_params = ComparatorParams( + strongarm=StrongarmParams( + tail=MosParams(w=w * nf * 2, l=l, nf=2 * nf), + inp_pair=MosParams(w=w * nf, l=l, nf=nf), + inv_n=MosParams(w=w * nf, l=l, nf=nf), + inv_p=MosParams(w=w * nf, l=l, nf=nf), + reset=MosParams(w=w * nf * 2, l=l, nf=2 * nf), + meas_vs=True, + ), + latch=LatchParams( + nor_pi=MosParams(w=w * nf, l=l, nf=nf), + nor_pfb=MosParams(w=w * nf, l=l, nf=nf), + nor_ni=MosParams(w=w * nf, l=l, nf=nf), + nor_nfb=MosParams(w=w * nf, l=l, nf=nf), + ), + ) + # Create our parametric testbench + params = TbParams(pvt=Pvt(), vc=900 * m, vd=1 * m, dut=comparator_params) + + # Create our simulation input + @hs.sim + class ComparatorSim: + tb = ComparatorTb(params) + tr = hs.Tran(tstop=12 * n, tstep=100 * PICO) + + # Add the PDK dependencies + ComparatorSim.lib(sky130.install.model_lib, "tt") + ComparatorSim.literal(".option METHOD=Gear") + + # Run Spice, save important results + results = ComparatorSim.run(sim_options) + tran_results = results.an[0].data + np.savez( + "strongarm_results.npz", + t=tran_results["time"], + v_out_diff=tran_results["v(xtop.out_p)"] - tran_results["v(xtop.out_n)"], + v_in_diff=tran_results["v(xtop.inp_p)"] - tran_results["v(xtop.inp_n)"], + v_clk=tran_results["v(xtop.clk)"], + i_tail=tran_results["i(v.xtop.xdut.xsa.vtail_meas)"], + i_inp_pair_cm=tran_results["i(v.xtop.xdut.xsa.vninp_meas)"] + + tran_results["i(v.xtop.xdut.xsa.vninn_meas)"], + i_latch_n_pair_cm=tran_results["i(v.xtop.xdut.xsa.vnlatn_meas)"] + + tran_results["i(v.xtop.xdut.xsa.vnlatp_meas)"], + i_latch_p_pair_cm=tran_results["i(v.xtop.xdut.xsa.vplatn_meas)"] + + tran_results["i(v.xtop.xdut.xsa.vplatp_meas)"], + v_casc_cm=( + tran_results["v(xtop.xdut.xsa.ninn_meas_p)"] + + tran_results["v(xtop.xdut.xsa.ninp_meas_p)"] + ) + / 2, + v_out_cm=( + tran_results["v(xtop.xdut.sout_p)"] + tran_results["v(xtop.xdut.sout_n)"] + ) + / 2, + ) + + +def extract_windows(t, clk, threshold): + """Given the clock waveform, this will extract a bunch of single periods + that are the periods at which a certain clock starts and ends""" + clk_above_thres_idcs = np.where(clk > threshold)[0] + print(clk_above_thres_idcs.shape) + print(np.diff(clk_above_thres_idcs)) + rising_cross_idcs = clk_above_thres_idcs[ + np.where(np.diff(np.concatenate(([0], clk_above_thres_idcs), axis=0)) > 1)[0] + ] + rising_cross_idcs = rising_cross_idcs.tolist() + [len(clk)] + return [(s, e) for s, e in zip(rising_cross_idcs[:-1], rising_cross_idcs[1:])] + + +def plot_windows(): + data = np.load("strongarm_results.npz") + windows = extract_windows(data["t"], data["v_clk"], 0.7) + for (s, e) in windows: + plt.figure() + plt.plot(data["t"][s:e], data["v_clk"][s:e]) + plt.show() + + +def plot_data(): + data = np.load("strongarm_results.npz") + t = data["t"] + fig, ax = plt.subplots(3, sharex=True) + ax[0].plot(t, data["v_clk"]) + ax[1].plot(t, data["v_in_diff"]) + ax[2].plot(t, data["v_out_diff"]) + fig, ax = plt.subplots(3, sharex=True) + ax[0].plot(t, data["v_clk"]) + ax[1].plot(t, data["i_tail"], label="tail current") + ax[1].plot(t, data["i_inp_pair_cm"], label="input pair current") + ax[1].plot(t, data["i_latch_n_pair_cm"], label="Latch NMOS current") + ax[1].plot(t, data["i_latch_p_pair_cm"], label="Latch PMOS current") + ax[1].legend() + ax[2].plot(t, data["v_casc_cm"], label="v_casc_cm") + ax[2].plot(t, data["v_out_cm"], label="v_out_cm") + ax[2].legend() + + plt.show() + breakpoint() + + +if __name__ == "__main__": + # test_comparator_sim() + # plot_data() + plot_windows() diff --git a/scripts/characterize_technology.py b/scripts/characterize_technology.py index c87da0e..d95427f 100644 --- a/scripts/characterize_technology.py +++ b/scripts/characterize_technology.py @@ -3,7 +3,7 @@ import hdl21 as h from hdl21.primitives import Vdc from vlsirtools.spice import SimOptions, SupportedSimulators, ResultFormat -import sitepdks, sky130 +import sky130, sitepdks as _ from matplotlib import pyplot as plt from matplotlib import cm as cm From 67ff49fca8abcc4ef80912cbf74128475892603e Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Fri, 6 Jan 2023 01:08:39 +0000 Subject: [PATCH 07/10] Add codespace-install.sh. Get rid of these calls to breakpoint(). --- adc/test_strongarm.py | 1 - adc/tests/test_common_source.py | 10 ++++++---- scripts/characterize_technology.py | 1 - scripts/codespace-install.sh | 32 ++++++++++++++++++++++++++++++ 4 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 scripts/codespace-install.sh diff --git a/adc/test_strongarm.py b/adc/test_strongarm.py index fc3af0e..f709136 100644 --- a/adc/test_strongarm.py +++ b/adc/test_strongarm.py @@ -241,7 +241,6 @@ def plot_data(): ax[2].legend() plt.show() - breakpoint() if __name__ == "__main__": diff --git a/adc/tests/test_common_source.py b/adc/tests/test_common_source.py index c92d53c..b221328 100644 --- a/adc/tests/test_common_source.py +++ b/adc/tests/test_common_source.py @@ -7,8 +7,7 @@ from vlsirtools.spice import SimOptions, SupportedSimulators, ResultFormat # Import the Hdl21 PDK package, and our "site" configuration of its installation -import sitepdks as _ -import sky130 +import sky130, sitepdks as _ # And give a few shorthand names to PDK content MosParams = sky130.Sky130MosParams @@ -68,13 +67,16 @@ def get_amp_performance(amp: h.Module) -> Tuple[float, float]: vout_dc = op_results["v(xtop.vdd)"] dc_current = op_results["i(v.xtop.vvdd_src)"] vout_ac = ac_results["v(xtop.vdd)"] - breakpoint() return vout_dc, dc_current -if __name__ == "__main__": +def run(): get_amp_performance( common_source_amp_gen( CommonSourceParams(MosParams(w=1 * UNIT, l=150 * m, nf=1), 1e3) ) ) + + +def test_common_source_amp(): # Pytest entrypoint + run() diff --git a/scripts/characterize_technology.py b/scripts/characterize_technology.py index d95427f..3ffa4ab 100644 --- a/scripts/characterize_technology.py +++ b/scripts/characterize_technology.py @@ -373,7 +373,6 @@ def compute_small_signal_parameters(filename, plot_results=True): vds, vgs = np.meshgrid(vds_raw, vgs_raw) ax.plot_surface(vds, vgs, gm_raw, cmap=cm.jet) - breakpoint() if __name__ == "__main__": diff --git a/scripts/codespace-install.sh b/scripts/codespace-install.sh new file mode 100644 index 0000000..9a55e37 --- /dev/null +++ b/scripts/codespace-install.sh @@ -0,0 +1,32 @@ +# +# ######################################### +# # GitHub CodeSpace Container Installation +# ######################################### +# +# To be run in the codespace, the first time +# Could this be part of the container build? Probably some day sure! +# + +# Conda will at times complain that "your environment is not set up for `conda activate`". +# If so, this `conda init` bit will set it up. +conda init bash +source ~/.bashrc + +conda activate base +conda install -y -c litex-hub open_pdks.sky130a +conda upgrade -y pip + +# There *should* be compatible versions of VLSIR and Hdl21 on PyPi. +# If not, install them from source with something like: +##git clone git@github.com:dan-fritchman/Hdl21.git +##cd Hdl21 +##git checkout 8eef2239920e4bf61e3b378f028e985d1d41a106 +##cd .. +##git clone git@github.com:Vlsir/Vlsir.git +##cd Vlsir +##git checkout 2079c44fd7b34023c737a7c01ea624b50289524f +##cd .. +##python scripts/manage.py install + +# Pip-install all the other, "normal" dependencies +pip install -e ".[dev]" From 764406b0bbae34766f841c2749b269e86d777187 Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Thu, 5 Jan 2023 17:26:13 -0800 Subject: [PATCH 08/10] Package-level references to scratch/, data/ --- adc/__init__.py | 7 ++++++- adc/test_strongarm.py | 31 ++++++++++++++++-------------- adc/tests/test_adc.py | 9 ++++----- scripts/characterize_technology.py | 6 +++--- 4 files changed, 30 insertions(+), 23 deletions(-) diff --git a/adc/__init__.py b/adc/__init__.py index 74758f2..780f6f5 100644 --- a/adc/__init__.py +++ b/adc/__init__.py @@ -2,4 +2,9 @@ ADC Package """ -... +from pathlib import Path + +# Set some common file paths, relative to the root of the repository +_repo = Path(__file__).parent.parent +data_dir = _repo / "data" +scratch = _repo / "scratch" diff --git a/adc/test_strongarm.py b/adc/test_strongarm.py index f709136..2618391 100644 --- a/adc/test_strongarm.py +++ b/adc/test_strongarm.py @@ -16,13 +16,8 @@ from vlsirtools.spice import SimOptions, SupportedSimulators, ResultFormat import sky130, sitepdks as _ -sim_options = SimOptions( - rundir=Path("./scratch"), - fmt=ResultFormat.SIM_DATA, - simulator=SupportedSimulators.NGSPICE, -) - -# Local DUT Imports +# Local Imports +from . import data_dir, scratch from .strongarm import ( comparator, ComparatorParams, @@ -31,6 +26,14 @@ LatchParams, ) +strongarm_results_file = data_dir / "strongarm_results.npz" + +sim_options = SimOptions( + rundir=scratch, + fmt=ResultFormat.SIM_DATA, + simulator=SupportedSimulators.NGSPICE, +) + @h.paramclass class DiffClkParams: @@ -176,7 +179,7 @@ class ComparatorSim: results = ComparatorSim.run(sim_options) tran_results = results.an[0].data np.savez( - "strongarm_results.npz", + strongarm_results_file, t=tran_results["time"], v_out_diff=tran_results["v(xtop.out_p)"] - tran_results["v(xtop.out_n)"], v_in_diff=tran_results["v(xtop.inp_p)"] - tran_results["v(xtop.inp_n)"], @@ -214,7 +217,7 @@ def extract_windows(t, clk, threshold): def plot_windows(): - data = np.load("strongarm_results.npz") + data = np.load(strongarm_results_file) windows = extract_windows(data["t"], data["v_clk"], 0.7) for (s, e) in windows: plt.figure() @@ -223,7 +226,7 @@ def plot_windows(): def plot_data(): - data = np.load("strongarm_results.npz") + data = np.load(strongarm_results_file) t = data["t"] fig, ax = plt.subplots(3, sharex=True) ax[0].plot(t, data["v_clk"]) @@ -243,7 +246,7 @@ def plot_data(): plt.show() -if __name__ == "__main__": - # test_comparator_sim() - # plot_data() - plot_windows() +# if __name__ == "__main__": +# # test_comparator_sim() +# # plot_data() +# plot_windows() diff --git a/adc/tests/test_adc.py b/adc/tests/test_adc.py index 64fc203..b4f9a13 100644 --- a/adc/tests/test_adc.py +++ b/adc/tests/test_adc.py @@ -1,8 +1,5 @@ import numpy as np -from pathlib import Path - - -data_dir = Path("./data") # Path to the repo-level `data/` directory +from adc import data_dir def test_db(): @@ -22,7 +19,9 @@ def test_db(): pch_db_filename, pch_lvt_db_filename, ]: - data = np.load(data_dir / filename, allow_pickle=True) + data = np.load( + data_dir / filename, allow_pickle=True + ) # FIXME: does this do anything? db.build(data_dir / filename) diff --git a/scripts/characterize_technology.py b/scripts/characterize_technology.py index 3ffa4ab..abe038e 100644 --- a/scripts/characterize_technology.py +++ b/scripts/characterize_technology.py @@ -10,18 +10,19 @@ import numpy as np import scipy.interpolate +from adc import data_dir, scratch + import nest_asyncio nest_asyncio.apply() sim_options = SimOptions( - rundir=Path("./scratch"), + rundir=scratch, fmt=ResultFormat.SIM_DATA, simulator=SupportedSimulators.NGSPICE, ) -data_dir = Path("./data") # Path to the repo-level `data/` directory np_filename = "database_nch.npy" tb_prefix = "tb_mos_ibias" mos_list = ["nch"] @@ -374,7 +375,6 @@ def compute_small_signal_parameters(filename, plot_results=True): ax.plot_surface(vds, vgs, gm_raw, cmap=cm.jet) - if __name__ == "__main__": run_characterization_sims(np_filename) compute_small_signal_parameters(np_filename) From 21fa3ada9d8974620a44219085832b7562147499 Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Thu, 5 Jan 2023 21:13:12 -0800 Subject: [PATCH 09/10] Chop OpAmp parts into sections. Reunify Comparator design & tests --- adc/strongarm.py | 185 +- adc/telescopic_cascode_diffamp_generator.py | 2148 ----------------- .../__init__.py | 5 + .../dbstuff.py | 115 + .../shared.py | 9 + .../stage1.py | 1032 ++++++++ .../stage2.py | 445 ++++ .../twostage.py | 535 ++++ adc/test_amp.py | 0 adc/test_strongarm.py | 252 -- adc/tests/test_common_source.py | 24 +- adc/testutils.py | 68 + 12 files changed, 2405 insertions(+), 2413 deletions(-) delete mode 100644 adc/telescopic_cascode_diffamp_generator.py create mode 100644 adc/telescopic_cascode_diffamp_generator/__init__.py create mode 100644 adc/telescopic_cascode_diffamp_generator/dbstuff.py create mode 100644 adc/telescopic_cascode_diffamp_generator/shared.py create mode 100644 adc/telescopic_cascode_diffamp_generator/stage1.py create mode 100644 adc/telescopic_cascode_diffamp_generator/stage2.py create mode 100644 adc/telescopic_cascode_diffamp_generator/twostage.py delete mode 100644 adc/test_amp.py delete mode 100644 adc/test_strongarm.py create mode 100644 adc/testutils.py diff --git a/adc/strongarm.py b/adc/strongarm.py index c8b1225..4eeaa1f 100644 --- a/adc/strongarm.py +++ b/adc/strongarm.py @@ -1,6 +1,18 @@ +import matplotlib.pyplot as plt +import numpy as np + import hdl21 as h +import hdl21.sim as hs from hdl21 import Diff -from hdl21.primitives import Vdc +from hdl21.prefix import m, f, n, PICO +from hdl21.primitives import Vdc, C, Vpulse + +# PDK Imports +import sky130, sitepdks as _ + +# Local Imports +from . import data_dir +from .testutils import Pvt, DiffClkParams, DiffClkGen, sim_options @h.paramclass @@ -178,3 +190,174 @@ class Comparator: sr = sr_latch(params.latch)(inp=sout, out=out, VDD=VDD, VSS=VSS) return Comparator + + +""" +# Comparator Tests +""" + +strongarm_results_file = data_dir / "strongarm_results.npz" + + +@h.paramclass +class TbParams: + dut = h.Param(dtype=ComparatorParams, desc="DUT params") + pvt = h.Param( + dtype=Pvt, desc="Process, Voltage, and Temperature Parameters", default=Pvt() + ) + vd = h.Param(dtype=h.Prefixed, desc="Differential Voltage (V)", default=100 * m) + vc = h.Param(dtype=h.Prefixed, desc="Common-Mode Voltage (V)", default=900 * m) + cl = h.Param(dtype=h.Prefixed, desc="Load Cap (Single-Ended) (F)", default=5 * f) + + +@h.generator +def ComparatorTb(p: TbParams) -> h.Module: + """Comparator Testbench""" + + # Create our testbench + tb = h.sim.tb("SlicerTb") + # Generate and drive VDD + tb.VDD = VDD = h.Signal() + tb.vvdd = Vdc(Vdc.Params(dc=p.pvt.v))(p=VDD, n=tb.VSS) + + # Input-driving balun + tb.inp = Diff() + tb.inpgen = DiffClkGen( + DiffClkParams(period=4 * n, delay=1 * n, vc=p.vc, vd=p.vd, trf=800 * PICO) + )(ck=tb.inp, VSS=tb.VSS) + + # Clock generator + tb.clk = clk = h.Signal() + tb.vclk = Vpulse( + Vpulse.Params( + delay=0, + v1=0, + v2=p.pvt.v, + period=2 * n, + rise=100 * PICO, + fall=100 * PICO, + width=1 * n, + ) + )(p=clk, n=tb.VSS) + + # Output & Load Caps + tb.out = Diff() + Cload = C(C.Params(c=p.cl)) + tb.clp = Cload(p=tb.out.p, n=tb.VSS) + tb.cln = Cload(p=tb.out.n, n=tb.VSS) + + # Create the Slicer DUT + tb.dut = comparator(p.dut)(inp=tb.inp, out=tb.out, clk=clk, VDD=VDD, VSS=tb.VSS) + return tb + + +def test_comparator_sim(): + """Comparator Test(s)""" + + w = 1.0 + l = 0.15 + nf = 2 + comparator_params = ComparatorParams( + strongarm=StrongarmParams( + tail=MosParams(w=w * nf * 2, l=l, nf=2 * nf), + inp_pair=MosParams(w=w * nf, l=l, nf=nf), + inv_n=MosParams(w=w * nf, l=l, nf=nf), + inv_p=MosParams(w=w * nf, l=l, nf=nf), + reset=MosParams(w=w * nf * 2, l=l, nf=2 * nf), + meas_vs=True, + ), + latch=LatchParams( + nor_pi=MosParams(w=w * nf, l=l, nf=nf), + nor_pfb=MosParams(w=w * nf, l=l, nf=nf), + nor_ni=MosParams(w=w * nf, l=l, nf=nf), + nor_nfb=MosParams(w=w * nf, l=l, nf=nf), + ), + ) + # Create our parametric testbench + params = TbParams(pvt=Pvt(), vc=900 * m, vd=1 * m, dut=comparator_params) + + # Create our simulation input + @hs.sim + class ComparatorSim: + tb = ComparatorTb(params) + tr = hs.Tran(tstop=12 * n, tstep=100 * PICO) + + # Add the PDK dependencies + ComparatorSim.lib(sky130.install.model_lib, "tt") + ComparatorSim.literal(".option METHOD=Gear") + + # Run Spice, save important results + results = ComparatorSim.run(sim_options) + tran_results = results.an[0].data + np.savez( + strongarm_results_file, + t=tran_results["time"], + v_out_diff=tran_results["v(xtop.out_p)"] - tran_results["v(xtop.out_n)"], + v_in_diff=tran_results["v(xtop.inp_p)"] - tran_results["v(xtop.inp_n)"], + v_clk=tran_results["v(xtop.clk)"], + i_tail=tran_results["i(v.xtop.xdut.xsa.vtail_meas)"], + i_inp_pair_cm=tran_results["i(v.xtop.xdut.xsa.vninp_meas)"] + + tran_results["i(v.xtop.xdut.xsa.vninn_meas)"], + i_latch_n_pair_cm=tran_results["i(v.xtop.xdut.xsa.vnlatn_meas)"] + + tran_results["i(v.xtop.xdut.xsa.vnlatp_meas)"], + i_latch_p_pair_cm=tran_results["i(v.xtop.xdut.xsa.vplatn_meas)"] + + tran_results["i(v.xtop.xdut.xsa.vplatp_meas)"], + v_casc_cm=( + tran_results["v(xtop.xdut.xsa.ninn_meas_p)"] + + tran_results["v(xtop.xdut.xsa.ninp_meas_p)"] + ) + / 2, + v_out_cm=( + tran_results["v(xtop.xdut.sout_p)"] + tran_results["v(xtop.xdut.sout_n)"] + ) + / 2, + ) + + +def extract_windows(t, clk, threshold): + """Given the clock waveform, this will extract a bunch of single periods + that are the periods at which a certain clock starts and ends""" + clk_above_thres_idcs = np.where(clk > threshold)[0] + print(clk_above_thres_idcs.shape) + print(np.diff(clk_above_thres_idcs)) + rising_cross_idcs = clk_above_thres_idcs[ + np.where(np.diff(np.concatenate(([0], clk_above_thres_idcs), axis=0)) > 1)[0] + ] + rising_cross_idcs = rising_cross_idcs.tolist() + [len(clk)] + return [(s, e) for s, e in zip(rising_cross_idcs[:-1], rising_cross_idcs[1:])] + + +def plot_windows(): + data = np.load(strongarm_results_file) + windows = extract_windows(data["t"], data["v_clk"], 0.7) + for (s, e) in windows: + plt.figure() + plt.plot(data["t"][s:e], data["v_clk"][s:e]) + plt.show() + + +def plot_data(): + data = np.load(strongarm_results_file) + t = data["t"] + fig, ax = plt.subplots(3, sharex=True) + ax[0].plot(t, data["v_clk"]) + ax[1].plot(t, data["v_in_diff"]) + ax[2].plot(t, data["v_out_diff"]) + fig, ax = plt.subplots(3, sharex=True) + ax[0].plot(t, data["v_clk"]) + ax[1].plot(t, data["i_tail"], label="tail current") + ax[1].plot(t, data["i_inp_pair_cm"], label="input pair current") + ax[1].plot(t, data["i_latch_n_pair_cm"], label="Latch NMOS current") + ax[1].plot(t, data["i_latch_p_pair_cm"], label="Latch PMOS current") + ax[1].legend() + ax[2].plot(t, data["v_casc_cm"], label="v_casc_cm") + ax[2].plot(t, data["v_out_cm"], label="v_out_cm") + ax[2].legend() + + plt.show() + + +# if __name__ == "__main__": +# # test_comparator_sim() +# # plot_data() +# plot_windows() diff --git a/adc/telescopic_cascode_diffamp_generator.py b/adc/telescopic_cascode_diffamp_generator.py deleted file mode 100644 index a8c3fec..0000000 --- a/adc/telescopic_cascode_diffamp_generator.py +++ /dev/null @@ -1,2148 +0,0 @@ -# -*- coding: utf-8 -*- - -# Two-stage Amplifier Generator: -# First stage is a telescopic cascode -# Second stage is a common source amplifier - -import pprint - -import numpy as np -import scipy.interpolate -import scipy.optimize as sciopt -import matplotlib.pyplot as plt - -from pathlib import Path - -import hdl21 as h -import hdl21.sim as hs -from hdl21 import Diff -from hdl21.pdk import Corner -from hdl21.sim import Sim, LogSweep -from hdl21.prefix import m, µ, f, n, PICO, G, KILO, Prefixed, FEMTO -from hdl21.primitives import Vdc, Idc, C, Vpulse, R, Vcvs -from vlsirtools.spice import SimOptions, SupportedSimulators, ResultFormat - -# Import the Hdl21 PDK package, and our "site" configuration of its installation -import sitepdks as _ -import sky130 - -# And give a few shorthand names to PDK content -MosParams = sky130.Sky130MosParams -nch = sky130.modules.sky130_fd_pr__nfet_01v8 -nch_lvt = sky130.modules.sky130_fd_pr__nfet_01v8_lvt -pch = sky130.modules.sky130_fd_pr__pfet_01v8 -pch_lvt = sky130.modules.sky130_fd_pr__pfet_01v8_lvt - -import nest_asyncio - -nest_asyncio.apply() - -sim_options = SimOptions( - rundir=Path("./scratch2"), - fmt=ResultFormat.SIM_DATA, - simulator=SupportedSimulators.NGSPICE, -) - - -# Query the given database for the given variable name, for the given lch, vbs, vgs, vds -def query_db(database, varname, mos_type, lch, vbs, vgs, vds): - - mos_list = database["mos_list"] - mos_index = mos_list.index(mos_type) - lch_list = database["lch_list"] - lch_index = lch_list.index(lch) - var_raw = database[varname][mos_index, lch_index, :, :, :] - if mos_type == "nch" or mos_type == "nch_lvt": - vgs_raw = np.array(database["vgs_list"]) - vds_raw = np.array(database["vds_list"]) - vbs_raw = np.array(database["vbs_list"]) - else: - vgs_raw = -np.array(database["vgs_list"]) - vds_raw = -np.array(database["vds_list"]) - vbs_raw = -np.array(database["vbs_list"]) - - interp = scipy.interpolate.RegularGridInterpolator( - (vbs_raw, vgs_raw, vds_raw), var_raw - ) - return interp([vbs, vgs, vds]).item() - - -# Specialized query function to find the vgs of a transistor if all other OP conditions are known. -# It has two operation modes: -# 'vstar' mode (where vstar = 2 * ID / gm): Find the vgs of the device if its lch, vbs, vds and gm/ID are known -# 'ids' mode: Find the vgs of the device if its lch, vbs, vds and ids are known. -def query_db_for_vgs( - database, mos_type, lch, vbs, vds, vstar=200e-3, ids=0, mode="vstar", scale=1 -): - mos_list = database["mos_list"] - mos_index = mos_list.index(mos_type) - lch_list = database["lch_list"] - lch_index = lch_list.index(lch) - if mos_type == "nch" or mos_type == "nch_lvt": - vgs_raw = np.array(database["vgs_list"]) - vds_raw = np.array(database["vds_list"]) - vbs_raw = np.array(database["vbs_list"]) - else: - vgs_raw = -np.array(database["vgs_list"]) - vds_raw = -np.array(database["vds_list"]) - vbs_raw = -np.array(database["vbs_list"]) - - ids_raw = database["ids"][mos_index, lch_index, :, :, :] * scale - gm_raw = database["gm"][mos_index, lch_index, :, :, :] * scale - - if mode == "vstar": - target_gm_id = 2 / vstar - gm_id_raw = gm_raw / ids_raw - interp = scipy.interpolate.RegularGridInterpolator( - (vbs_raw, vgs_raw, vds_raw), gm_id_raw - ) - - resample_vector_vbs = [vbs] * (np.shape(vgs_raw)[0]) - resample_vector_vds = [vds] * (np.shape(vgs_raw)[0]) - resample_points = np.transpose( - np.vstack((resample_vector_vbs, vgs_raw, resample_vector_vds)) - ) - gm_id_raw_1d = interp(resample_points) - - interp_vgs = scipy.interpolate.interp1d(gm_id_raw_1d, vgs_raw) - - try: - return interp_vgs(target_gm_id).item() - except: - raise ValueError - - elif mode == "ids": - interp = scipy.interpolate.RegularGridInterpolator( - (vbs_raw, vgs_raw, vds_raw), ids_raw - ) - - resample_vector_vbs = [vbs] * (np.shape(vgs_raw)[0]) - resample_vector_vds = [vds] * (np.shape(vgs_raw)[0]) - resample_points = np.transpose( - np.vstack((resample_vector_vbs, vgs_raw, resample_vector_vds)) - ) - ids_raw_1d = interp(resample_points) - - interp_vgs = scipy.interpolate.interp1d(ids_raw_1d, vgs_raw) - - try: - return interp_vgs(ids).item() - except: - raise ValueError - - -# Query function that returns the function for the given variable instead of a single datapoint. -# Useful for optimization kinda stuff where you iterate over functions. -def query_db_for_function(database, varname, mos_type, lch): - - mos_list = database["mos_list"] - mos_index = mos_list.index(mos_type) - lch_list = database["lch_list"] - lch_index = lch_list.index(lch) - - var_raw = database[varname][mos_index, lch_index, :, :, :] - if mos_type == "nch" or mos_type == "nch_lvt": - vgs_raw = np.array(database["vgs_list"]) - vds_raw = np.array(database["vds_list"]) - vbs_raw = np.array(database["vbs_list"]) - else: - vgs_raw = -np.array(database["vgs_list"]) - vds_raw = -np.array(database["vds_list"]) - vbs_raw = -np.array(database["vbs_list"]) - - interp = scipy.interpolate.RegularGridInterpolator( - (vbs_raw, vgs_raw, vds_raw), var_raw - ) - return interp - - -# Function that designs the input stage (input & cascode devices) of the telescopic amplifier. -# The goal is to find the highest gain-bandwidth product operating point for this stage. -def stage1_telescopic_amplifier_design_input(database, amp_specs, gen_params): - - """Find operating point that meets the given vstar (2*ID/gm) spec, - while maximizing the gain-bandwidth product of the input stage.""" - - vdd = amp_specs["vdd"] - voutcm = amp_specs["voutcm"] - vincm = amp_specs["vincm"] - vstar_in = amp_specs["vstar_in"] - vdst_min = amp_specs["vds_tail_min"] - vds_sweep_res = gen_params["vds_sweep_res"] - casc_scale_max = gen_params["casc_scale_max"] - casc_scale_min = gen_params["casc_scale_min"] - casc_scale_step = gen_params["casc_scale_step"] - in_type = amp_specs["in_type"] - lch_in = gen_params["lch_in"] - - # We will sweep the cascode scale, so initialize the sweep list - num_casc_scale_points = ( - int(np.ceil((casc_scale_max - casc_scale_min) / casc_scale_step)) + 1 - ) - casc_scale_list = np.linspace( - casc_scale_min, casc_scale_max, num_casc_scale_points, endpoint=True - ) - - # Take care of input-type-specific variables - if in_type == "nch" or in_type == "nch_lvt": - vb = 0 - vtail_lim = vdst_min - vds_in_lim_0 = vstar_in - vds_in_lim_1 = (voutcm - vtail_lim) * 2 / 3 - num_vds_points = int(np.ceil((vds_in_lim_1 - vds_in_lim_0) / vds_sweep_res)) + 1 - vds_in_val_list = np.linspace( - vds_in_lim_0, vds_in_lim_1, num_vds_points, endpoint=True - ) - - else: - vb = vdd - vtail_lim = vb - vdst_min - vds_in_lim_0 = -vstar_in - vds_in_lim_1 = (voutcm - vtail_lim) * 2 / 3 - num_vds_points = int(np.ceil((vds_in_lim_0 - vds_in_lim_1) / vds_sweep_res)) + 1 - vds_in_val_list = np.linspace( - vds_in_lim_0, vds_in_lim_1, num_vds_points, endpoint=True - ) - - # Initialize the metric, given that it being 0 is bad. The metric can be anything, but - # in this generator, it is defined to be the gain-bandwidth of the input stage. - metric_best = 0 - - # We have to start somewhere, we will sweep the vds of the input device since that should - # be something we probably are not as sensitive to (compared to, say, the vgs of the input device) - for vds_in in vds_in_val_list: - - # First iteration to find approximate vgs (and hence vsource) to account for vbs later - vcasc_mid = vdst_min + vds_in - vsource = vdst_min - try: - vbs_in = vb - vsource - vgs_in = query_db_for_vgs( - database, in_type, lch_in, vbs_in, vds_in, vstar=vstar_in - ) - except ValueError: - continue - vsource = vincm - vgs_in - vcasc_mid = vsource + vds_in - - # Second iteration, for more exact result with approximately the correct VBS - try: - vbs_in = vb - vsource - vgs_in = query_db_for_vgs( - database, in_type, lch_in, vbs_in, vds_in, vstar=vstar_in - ) - except ValueError: - continue - - # From the more accurate vgs, we can find a whole bunch of new operating points - vsource = vincm - vgs_in - vcasc_mid = vsource + vds_in - vbs_in = vb - vsource - vds_in = vcasc_mid - vsource - ids_in = query_db(database, "ids", in_type, lch_in, vbs_in, vgs_in, vds_in) - vbs_casc = vb - vcasc_mid - vds_casc = voutcm - vcasc_mid - vds_in = vcasc_mid - vsource - gm_in = query_db(database, "gm", in_type, lch_in, vbs_in, vgs_in, vds_in) - - # Now we'll sweep the cascode scale list to find the optimum value for that. - # More intelligent search algorithms will actually converge to the most optimal cascode scale. - for casc_scale in casc_scale_list: - ids_casc = ids_in / casc_scale - - # Find the cascode vgs since we know its current, vbs and vds - try: - vgs_casc = query_db_for_vgs( - database=database, - mos_type=in_type, - lch=lch_in, - vbs=vbs_casc, - vds=vds_casc, - ids=ids_casc, - mode="ids", - ) - except: - continue - - # If cascode gate is an unrealizable voltage within the rails, just move on - vg_casc = vcasc_mid + vgs_casc - if vg_casc > vdd or vg_casc < 0: - continue - - gm_casc = ( - query_db(database, "gm", in_type, lch_in, vbs_casc, vgs_casc, vds_casc) - * casc_scale - ) - gds_casc = ( - query_db(database, "gds", in_type, lch_in, vbs_casc, vgs_casc, vds_casc) - * casc_scale - ) - gds_base = query_db( - database, "gds", in_type, lch_in, vbs_in, vgs_in, vds_in - ) - - gds_in = gds_base * gds_casc / (gds_base + gds_casc + gm_casc) - Av_cur = gm_in / gds_in - - cgg_casc = ( - query_db(database, "cgg", in_type, lch_in, vbs_casc, vgs_casc, vds_casc) - * casc_scale - ) - cgg_in = query_db(database, "cgg", in_type, lch_in, vbs_in, vgs_in, vds_in) - - cdd_casc = cgg_casc * gen_params["cdd_cgg_ratio"] - - bw_cur = gds_in / cdd_casc - metric_cur = Av_cur * bw_cur - - # If we meet the gain spec AND we exceed the best GBW product, then save this op as the best - if ( - Av_cur > (amp_specs["input_stage_gain_min"]) - and metric_cur > metric_best - ): - metric_best = metric_cur - Av_best = Av_cur - vgs_best = vgs_in - casc_scale_best = casc_scale - input_op = dict( - Av=Av_cur, - vgs=vgs_in, - casc_scale=casc_scale, - casc_bias=vg_casc, - vin_mid=vcasc_mid, - ibias=ids_in, - gm_in=gm_in, - gm_casc=gm_casc, - gds_base=gds_in, - gds_casc=gds_casc, - gds_in=gds_in, - cdd_in=cgg_casc * gen_params["cdd_cgg_ratio"], - cgg_casc=cgg_casc, - cgg_base=cgg_in, - vtail=vsource, - ) - - print("New Av Best = %f" % (Av_best)) - print("Updated VGS Best = %f" % (vgs_best)) - print("Updated Casc Scale Best = %f" % (casc_scale_best)) - - print("--------------------------------") - print("Input Stage Design:") - print("Av Best = %f" % (Av_best)) - print("VGS Best = %f" % (vgs_best)) - print("Casc Scale Best = %f" % (casc_scale_best)) - print("VDS_in Best = %f" % (input_op["vin_mid"] - input_op["vtail"])) - print("VDS_casc Best = %f" % (voutcm - input_op["vin_mid"])) - print("--------------------------------") - - return input_op - - -# Function that designs the load stage (load current source & cascode devices) of the telescopic amplifier. -# The goal is to find the highest gain-bandwidth-product-per-µA operating point for this stage. -def stage1_telescopic_amplifier_design_load(database, amp_specs, gen_params, input_op): - """Design load. - - Sweep vgs. For each vgs, compute gain and max bandwidth. If - both gain and BW specs are met, pick operating point that maximizes - gamma_r * gm_r - """ - vdd = amp_specs["vdd"] - vstar_in = amp_specs["vstar_in"] - voutcm = amp_specs["voutcm"] - vgs_res = gen_params["vgs_sweep_res"] - gain_min = amp_specs["gain_min"] - bw_min = max(amp_specs["selfbw_min"], amp_specs["bw_min"]) - casc_scale_max = gen_params["casc_scale_max"] - casc_scale_step = gen_params["casc_scale_step"] - casc_bias_step = gen_params["casc_bias_step"] - vds_sweep_res = gen_params["vds_sweep_res"] - in_type = amp_specs["in_type"] - load_type = amp_specs["load_type"] - lch_load = gen_params["lch_load"] - best_load_op = None - metric_best = 0 - gain_max = 0 - bw_max = 0 - - casc_scale_list = np.arange( - 1, casc_scale_max + casc_scale_step / 2, casc_scale_step - ) - if in_type == "nch" or in_type == "nch_lvt": - vs = vdd - vb = vdd - casc_bias_list = np.arange(0.5, voutcm + casc_bias_step / 2, casc_bias_step) - vgs_base_max = -0.1 - vgs_base_min = -1.5 - vds_base_lim_0 = -vstar_in - vds_base_lim_1 = (voutcm - vdd) * 2 / 3 - num_vds_points = ( - int(np.ceil((vds_base_lim_0 - vds_base_lim_1) / vds_sweep_res)) + 1 - ) - vds_base_val_list = np.linspace( - vds_base_lim_0, vds_base_lim_1, num_vds_points, endpoint=True - ) - else: - vs = 0 - vb = 0 - casc_bias_list = np.arange( - voutcm, vdd - 0.5 + casc_bias_step / 2, casc_bias_step - ) - vgs_base_min = 0.1 - vgs_base_max = 1.5 - vds_base_lim_0 = vstar_in - vds_base_lim_1 = (voutcm) * 2 / 3 - num_vds_points = ( - int(np.ceil((vds_base_lim_1 - vds_base_lim_0) / vds_sweep_res)) + 1 - ) - vds_base_val_list = np.linspace( - vds_base_lim_0, vds_base_lim_1, num_vds_points, endpoint=True - ) - - for lch_load in gen_params["lch_load"]: - gm_fun = query_db_for_function(database, "gm", load_type, lch_load) - gds_fun = query_db_for_function(database, "gds", load_type, lch_load) - cgg_fun = query_db_for_function(database, "cgg", load_type, lch_load) - gamma = gen_params["gamma"] - ib_fun = query_db_for_function(database, "ids", load_type, lch_load) - - num_points = int(np.ceil((vgs_base_max - vgs_base_min) / vgs_res)) + 1 - - gm_in_base = input_op["gm_in"] - gm_in_casc = input_op["gm_casc"] - gds_in_base = input_op["gds_in"] - gds_in_casc = input_op["gds_casc"] - ibias_in = input_op["ibias"] - gds_in = input_op["gds_in"] - cgg_in = input_op["cgg_casc"] - cdd_in = cgg_in * gen_params["cdd_cgg_ratio"] - - def vgs_base_search_fun(vgs_base, vcasc_mid, casc_scale, vg_casc, vsource): - vgs_casc = vg_casc - vcasc_mid - vds_casc = voutcm - vcasc_mid - vbs_casc = vb - vcasc_mid - vds_base = vcasc_mid - vsource - vbs_base = 0 - - ids_casc = ib_fun([vbs_casc, vgs_casc, vds_casc])[0] * casc_scale - ids_base = ib_fun([vbs_base, vgs_base, vds_base])[0] - - return ids_casc - ids_base - - for vds_base in vds_base_val_list: - vcasc_mid = vs + vds_base - for casc_scale in casc_scale_list: - for casc_bias in casc_bias_list: - try: - vgs_base = sciopt.brentq( - vgs_base_search_fun, - vgs_base_min, - vgs_base_max, - args=( - vcasc_mid, - casc_scale, - casc_bias, - vs, - ), - ) - except ValueError: - continue - vgs_casc = casc_bias - vcasc_mid - vds_casc = voutcm - vcasc_mid - vbs_casc = vb - vcasc_mid - vds_base = vcasc_mid - vs - vbs_base = 0 - - ibias_base = ib_fun([vbs_base, vgs_base, vds_base])[0] - load_scale = ibias_in / ibias_base - - gm_base = gm_fun([vbs_base, vgs_base, vds_base])[0] - gds_base = gds_fun([vbs_base, vgs_base, vds_base])[0] - cdd_base = ( - cgg_fun([vbs_base, vgs_base, vds_base])[0] - * gen_params["cdd_cgg_ratio"] - ) - - gm_casc = gm_fun([vbs_casc, vgs_casc, vds_casc])[0] * casc_scale - gds_casc = gds_fun([vbs_casc, vgs_casc, vds_casc])[0] * casc_scale - cdd_casc = ( - cgg_fun([vbs_casc, vgs_casc, vds_casc])[0] - * gen_params["cdd_cgg_ratio"] - * casc_scale - ) - - gm_load = gm_base * load_scale - gds_load = ( - gds_base - * gds_casc - / (gds_base + gds_casc + gm_casc) - * load_scale - ) - cdd_load = cdd_casc * load_scale - - bw_cur = (gds_load + gds_in) / (cdd_load + cdd_in) / 2 / np.pi - gain_cur = gm_in_base / (gds_load + gds_in) - - metric_cur = gain_cur * bw_cur / ibias_in - - if load_type == "pch" or load_type == "pch_lvt": - base_bias = vdd + vgs_base - else: - base_bias = vgs_base - - if gain_cur > gain_min and bw_cur > bw_min: - if metric_cur > metric_best: - metric_best = metric_cur - best_load_op = dict( - Av=gain_cur, - bw=bw_cur, - casc_scale=casc_scale, - casc_bias=casc_bias, - base_bias=base_bias, - vload_mid=vcasc_mid, - load_scale=load_scale, - lch_load=lch_load, - gm_load=gm_load, - gds_load=gds_load, - cdd_load=cdd_load, - metric_load=metric_best, - ) - print( - "New GBW/I Best = %f MHz/µA" - % (round(metric_best / 1e12, 2)) - ) - print("Updated Av Best = %f" % (gain_cur)) - print("Updated BW Best = %f MHz" % (round(bw_cur / 1e6, 2))) - if gain_cur > gain_max: - gain_max = gain_cur - if bw_cur > bw_max: - bw_max = bw_cur - print("Load Av Best = %f" % ((gain_max))) - print("Load BW Best = %f MHz" % (round(bw_max / 1e6, 2))) - return best_load_op - - -def stage1_telescopic_amplifier_design_amp(amp_specs, gen_params, input_op, load_op): - - vnoise_input_referred_max = amp_specs["vnoise_input_referred"] - bw_min = amp_specs["bw_min"] - cload = amp_specs["cload"] - vdd = amp_specs["vdd"] - in_type = amp_specs["in_type"] - k = 1.38e-23 - T = 300 - - ibias = input_op["ibias"] - vtail = input_op["vtail"] - gm_in = input_op["gm_in"] - gds_in = input_op["gds_in"] - gds_in_casc = input_op["gds_casc"] - gds_in_base = input_op["gds_base"] - gm_in_casc = input_op["gm_casc"] - gamma_in = gen_params["gamma"] - cdd_in = input_op["cdd_in"] - cgg_in = input_op["cgg_base"] - gm_load = load_op["gm_load"] - gds_load = load_op["gds_load"] - cdd_load = load_op["cdd_load"] - gamma_load = gen_params["gamma"] - load_scale = load_op["load_scale"] - load_casc_scale = load_op["casc_scale"] - load_casc_bias = load_op["casc_bias"] - load_base_bias = load_op["base_bias"] - in_casc_scale = input_op["casc_scale"] - in_casc_bias = input_op["casc_bias"] - Av = load_op["Av"] - - gds_tot = gds_in + gds_load - cdd_tot = cdd_in + cdd_load - vnoise_squared_input_referred_max = vnoise_input_referred_max**2 - vnoise_squared_input_referred_per_scale = ( - 4 * k * T * (gamma_in * gm_in + gamma_load * gm_load) / (gm_in**2) - ) - scale_noise = max( - 1, vnoise_squared_input_referred_per_scale / vnoise_squared_input_referred_max - ) - scale_bw = max( - 1, 2 * np.pi * bw_min * cload / (gds_tot - 2 * np.pi * bw_min * cdd_tot) - ) - print("scale_noise:") - pprint.pprint(scale_noise) - print("scale_bw:") - pprint.pprint(scale_bw) - - scale_amp = max(scale_bw, scale_noise) - - vnoise_density_squared_input_referred = ( - vnoise_squared_input_referred_per_scale / scale_amp - ) - vnoise_density_input_referred = np.sqrt(vnoise_density_squared_input_referred) - - amplifier_op = dict( - gm=gm_in * scale_amp, - gds=gds_tot * scale_amp, - cgg=cgg_in * scale_amp, - cdd=cdd_tot * scale_amp, - vnoise_density_input=vnoise_density_input_referred, - scale_in_base=scale_amp, - scale_in_casc=scale_amp * in_casc_scale, - scale_load_base=scale_amp * load_scale, - scale_load_casc=scale_amp * load_scale * load_casc_scale, - vtail=vtail, - load_base_bias=load_base_bias, - load_casc_bias=load_casc_bias, - in_casc_bias=in_casc_bias, - ibias=ibias * scale_amp * 2, - gain=Av, - bw=(gds_tot * scale_amp) / (2 * np.pi * (cload + (cdd_tot * scale_amp))), - ) - return amplifier_op - - -def stage1_telescopic_amplifier_design_tail( - database, amp_specs, gen_params, amplifier_op -): - - vdd = amp_specs["vdd"] - vtail = amplifier_op["vtail"] - vstar_tail = vtail - gen_params["tail_vstar_vds_margin"] - in_type = amp_specs["in_type"] - - if in_type == "nch" or in_type == "nch_lvt": - vds_tail = vtail - vbs_tail = 0 - else: - vds_tail = vtail - vdd - vbs_tail = 0 - - lch_tail = gen_params["lch_tail"] - - ib_fun = query_db_for_function(database, "ids", in_type, lch_tail) - itarg = amplifier_op["ibias"] - - vgs_tail = query_db_for_vgs( - database=database, - mos_type=in_type, - lch=lch_tail, - vbs=vbs_tail, - vds=vds_tail, - vstar=vstar_tail, - mode="vstar", - ) - - ids_tail = ib_fun([vbs_tail, vgs_tail, vds_tail])[0] - scale_tail = itarg / ids_tail - - gm_tail = query_db(database, "gm", in_type, lch_tail, vbs_tail, vgs_tail, vds_tail) - gds_tail = query_db( - database, "gds", in_type, lch_tail, vbs_tail, vgs_tail, vds_tail - ) - cgg_tail = query_db( - database, "cgg", in_type, lch_tail, vbs_tail, vgs_tail, vds_tail - ) - cdd_tail = cgg_tail * gen_params["cdd_cgg_ratio"] - gmro_tail = gm_tail / gds_tail - - if in_type == "nch" or in_type == "nch_lvt": - vbias_tail = vgs_tail - else: - vbias_tail = vdd + vgs_tail - - tail_op = dict( - scale_tail=scale_tail, - vbias_tail=vbias_tail, - vgs=vgs_tail, - vds=vds_tail, - gm=gm_tail, - gds=gds_tail, - gmro=gmro_tail, - cdd=cdd_tail, - ) - return tail_op - - -@h.paramclass -class TelescopicAmpParams: - tail_params = h.Param(dtype=MosParams, desc="Tail FET params") - in_base_pair_params = h.Param(dtype=MosParams, desc="Input pair FET params") - in_casc_pair_params = h.Param(dtype=MosParams, desc="Inverter NMos params") - load_base_pair_params = h.Param(dtype=MosParams, desc="Inverter PMos params") - load_casc_pair_params = h.Param(dtype=MosParams, desc="Reset Device params") - in_type = h.Param(dtype=str, desc="Input MOS type") - load_type = h.Param(dtype=str, desc="Load MOS type") - tail_type = h.Param(dtype=str, desc="Tail MOS type") - voutcm_ideal = h.Param(dtype=Prefixed, desc="Ideal Output CM") - v_load = h.Param(dtype=Prefixed, desc="Load MOS Bias") - v_pcasc = h.Param(dtype=Prefixed, desc="PMOS Cascode Device Bias") - v_ncasc = h.Param(dtype=Prefixed, desc="NMOS Cascode Device Bias") - v_tail = h.Param(dtype=Prefixed, desc="Tail MOS Bias") - - -@h.generator -def stage1_telescopic_amplifier(params: TelescopicAmpParams) -> h.Module: - - if params.in_type == "nch": - mos_in = nch - elif params.in_type == "nch_lvt": - mos_in = nch_lvt - elif params.in_type == "pch": - mos_in = pch - elif params.in_type == "pch_lvt": - mos_in = pch_lvt - - if params.load_type == "nch": - mos_load = nch - elif params.load_type == "nch_lvt": - mos_load = nch_lvt - elif params.load_type == "pch": - mos_load = pch - elif params.load_type == "pch_lvt": - mos_load = pch_lvt - - if params.tail_type == "nch": - mos_tail = nch - elif params.tail_type == "nch_lvt": - mos_tail = nch_lvt - elif params.tail_type == "pch": - mos_tail = pch - elif params.tail_type == "pch_lvt": - mos_tail = pch_lvt - - if params.in_type == "nch" or params.in_type == "nch_lvt": - - @h.module - class TelescopicAmp: - - VDD, VSS = h.Ports(2) - v_in = Diff(port=True, role=Diff.Roles.SINK) - v_out = Diff(port=True, role=Diff.Roles.SOURCE) - v_tail = h.Input() - v_load = h.Input() - v_pcasc = h.Input() - v_ncasc = h.Input() - - ## Tail Device - m_tail = mos_tail(params.tail_params)(g=v_tail, s=VSS, b=VSS) - - ## Input Base Pair - m_in_base_p = mos_in(params.in_base_pair_params)( - g=v_in.p, s=m_tail.d, b=VSS - ) - m_in_base_n = mos_in(params.in_base_pair_params)( - g=v_in.n, s=m_tail.d, b=VSS - ) - - ## Input Cascode Pair - m_in_casc_p = mos_in(params.in_casc_pair_params)( - g=v_ncasc, s=m_in_base_p.d, d=v_out.n, b=VSS - ) - m_in_casc_n = mos_in(params.in_casc_pair_params)( - g=v_ncasc, s=m_in_base_n.d, d=v_out.p, b=VSS - ) - - ## Load Base Pair - m_load_base_p = mos_load(params.load_base_pair_params)( - g=v_load, s=VDD, b=VDD - ) - m_load_base_n = mos_load(params.load_base_pair_params)( - g=v_load, s=VDD, b=VDD - ) - - ## Load Cascode Pair - m_load_casc_p = mos_load(params.load_casc_pair_params)( - g=v_pcasc, s=m_load_base_p.d, d=v_out.n, b=VDD - ) - m_load_casc_n = mos_load(params.load_casc_pair_params)( - g=v_pcasc, s=m_load_base_n.d, d=v_out.p, b=VDD - ) - - return TelescopicAmp - - elif params.load_type == "pch" or params.load_type == "pch_lvt": - - @h.module - class TelescopicAmp: - VDD, VSS = h.Ports(2) - v_in = Diff(port=True, role=Diff.Roles.SINK) - v_out = Diff(port=True, role=Diff.Roles.SOURCE) - v_tail = h.Input() - v_load = h.Input() - v_pcasc = h.Input() - v_ncasc = h.Input() - - ## Tail Device - m_tail = mos_tail(params.tail_params)(g=v_tail, s=VDD, b=VDD) - - ## Input Base Pair - m_in_base_p = mos_in(params.in_base_pair_params)( - g=v_in.p, s=m_tail.d, b=VDD - ) - m_in_base_n = mos_in(params.in_base_pair_params)( - g=v_in.n, s=m_tail.d, b=VDD - ) - - ## Input Cascode Pair - m_in_casc_p = mos_in(params.in_casc_pair_params)( - g=v_pcasc, s=m_in_base_p.d, d=v_out.n, b=VDD - ) - m_in_casc_n = mos_in(params.in_casc_pair_params)( - g=v_pcasc, s=m_in_base_n.d, d=v_out.p, b=VDD - ) - - ## Load Base Pair - m_load_base_p = mos_load(params.load_base_pair_params)( - g=v_load, s=VSS, b=VSS - ) - m_load_base_n = mos_load(params.load_base_pair_params)( - g=v_load, s=VSS, b=VSS - ) - - ## Load Cascode Pair - m_load_casc_p = mos_load(params.load_casc_pair_params)( - g=v_ncasc, s=m_load_base_p.d, d=v_out.n, b=VSS - ) - m_load_casc_n = mos_load(params.load_casc_pair_params)( - g=v_ncasc, s=m_load_base_n.d, d=v_out.p, b=VSS - ) - - return TelescopicAmp - - -@h.paramclass -class DiffClkParams: - """Differential Clock Generator Parameters""" - - period = h.Param(dtype=hs.ParamVal, desc="Period") - delay = h.Param(dtype=hs.ParamVal, desc="Delay") - vd = h.Param(dtype=hs.ParamVal, desc="Differential Voltage") - vc = h.Param(dtype=hs.ParamVal, desc="Common-Mode Voltage") - trf = h.Param(dtype=hs.ParamVal, desc="Rise / Fall Time") - - -@h.generator -def DiffClkGen(p: DiffClkParams) -> h.Module: - """# Differential Clock Generator - For simulation, from ideal pulse voltage sources""" - - ckg = h.Module() - ckg.VSS = VSS = h.Port() - ckg.ck = ck = Diff(role=Diff.Roles.SINK, port=True) - - def vparams(polarity: bool) -> Vpulse.Params: - """Closure to create the pulse-source parameters for each differential half. - Argument `polarity` is True for positive half, False for negative half.""" - # Initially create the voltage levels for the positive half - v1 = p.vc + p.vd / 2 - v2 = p.vc - p.vd / 2 - if not polarity: # And for the negative half, swap them - v1, v2 = v2, v1 - return Vpulse.Params( - v1=v1, - v2=v2, - period=p.period, - rise=p.trf, - fall=p.trf, - width=p.period / 2 - p.trf, - delay=p.delay, - ) - - # Create the two complementary pulse-sources - ckg.vp = Vpulse(vparams(True))(p=ck.p, n=VSS) - ckg.vn = Vpulse(vparams(False))(p=ck.n, n=VSS) - return ckg - - -@h.paramclass -class Pvt: - """Process, Voltage, and Temperature Parameters""" - - p = h.Param(dtype=Corner, desc="Process Corner", default=Corner.TYP) - v = h.Param(dtype=h.Prefixed, desc="Supply Voltage Value (V)", default=1800 * m) - t = h.Param(dtype=int, desc="Simulation Temperature (C)", default=25) - - -@h.paramclass -class Stage1TbParams: - dut = h.Param(dtype=TelescopicAmpParams, desc="DUT params") - pvt = h.Param( - dtype=Pvt, desc="Process, Voltage, and Temperature Parameters", default=Pvt() - ) - vd = h.Param(dtype=h.Prefixed, desc="Differential Voltage (V)", default=1 * m) - vc = h.Param(dtype=h.Prefixed, desc="Common-Mode Voltage (V)", default=1200 * m) - cl = h.Param(dtype=h.Prefixed, desc="Load Cap (Single-Ended) (F)", default=100 * f) - rcm = h.Param( - dtype=h.Prefixed, desc="Common Mode Sensing Resistor (Ω)", default=1 * G - ) - CMFB_gain = h.Param( - dtype=h.Prefixed, desc="Common Mode Feedback Gain (V/V)", default=1 * KILO - ) - - -@h.generator -def AmplifierTbTran(params: Stage1TbParams) -> h.Module: - - tb = h.sim.tb("TelescopicAmplifierTb") - tb.VDD = VDD = h.Signal() - tb.vvdd = Vdc(Vdc.Params(dc=params.pvt.v, ac=(0 * m)))(p=VDD, n=tb.VSS) - - tb.v_tail = h.Signal() - tb.v_tail_src = Vdc(Vdc.Params(dc=(params.dut.v_tail), ac=(0 * m)))( - p=tb.v_tail, n=tb.VSS - ) - - tb.v_pcasc = h.Signal() - tb.v_pcasc_src = Vdc(Vdc.Params(dc=(params.dut.v_pcasc), ac=(0 * m)))( - p=tb.v_pcasc, n=tb.VSS - ) - - tb.v_ncasc = h.Signal() - tb.v_ncasc_src = Vdc(Vdc.Params(dc=(params.dut.v_ncasc), ac=(0 * m)))( - p=tb.v_ncasc, n=tb.VSS - ) - - tb.v_load = h.Signal() - tb.voutcm_ideal = h.Signal() - # tb.v_load_src = Vdc(Vdc.Params(dc=(params.dut.v_load)))(p=tb.v_load, n=tb.VSS) - tb.v_outcm_ideal_src = Vdc(Vdc.Params(dc=(params.dut.voutcm_ideal), ac=(0 * m)))( - p=tb.voutcm_ideal, n=tb.VSS - ) - - # Input-driving balun - tb.inp = Diff() - tb.inpgen = DiffClkGen( - DiffClkParams( - period=1000 * n, delay=1 * n, vc=params.vc, vd=params.vd, trf=800 * PICO - ) - )(ck=tb.inp, VSS=tb.VSS) - - # Output & Load Caps - tb.out = Diff() - tb.CMSense = h.Signal() - Cload = C(C.Params(c=params.cl)) - Ccmfb = C(C.Params(c=100 * f)) - Rload = R(R.Params(r=params.rcm)) - tb.clp = Cload(p=tb.out.p, n=tb.VSS) - tb.cln = Cload(p=tb.out.n, n=tb.VSS) - tb.ccmfb = Ccmfb(p=tb.CMSense, n=tb.VSS) - tb.rcmp = Rload(p=tb.out.p, n=tb.CMSense) - tb.rcmn = Rload(p=tb.out.n, n=tb.CMSense) - tb.cmfb_src = Vcvs(Vcvs.Params(gain=params.CMFB_gain))( - p=tb.v_load, n=tb.VSS, cp=tb.CMSense, cn=tb.voutcm_ideal - ) - - # Create the Telescopic Amplifier DUT - tb.dut = stage1_telescopic_amplifier(params.dut)( - v_in=tb.inp, - v_out=tb.out, - v_tail=tb.v_tail, - v_ncasc=tb.v_ncasc, - v_pcasc=tb.v_pcasc, - v_load=tb.v_load, - VDD=VDD, - VSS=tb.VSS, - ) - return tb - - -@h.generator -def AmplifierTbAc(params: Stage1TbParams) -> h.Module: - - tb = h.sim.tb("TelescopicAmplifierTb") - tb.VDD = VDD = h.Signal() - tb.vvdd = Vdc(Vdc.Params(dc=params.pvt.v, ac=(0 * m)))(p=VDD, n=tb.VSS) - - tb.v_tail = h.Signal() - tb.v_tail_src = Vdc(Vdc.Params(dc=(params.dut.v_tail), ac=(0 * m)))( - p=tb.v_tail, n=tb.VSS - ) - - tb.v_pcasc = h.Signal() - tb.v_pcasc_src = Vdc(Vdc.Params(dc=(params.dut.v_pcasc), ac=(0 * m)))( - p=tb.v_pcasc, n=tb.VSS - ) - - tb.v_ncasc = h.Signal() - tb.v_ncasc_src = Vdc(Vdc.Params(dc=(params.dut.v_ncasc), ac=(0 * m)))( - p=tb.v_ncasc, n=tb.VSS - ) - - tb.v_load = h.Signal() - tb.voutcm_ideal = h.Signal() - # tb.v_load_src = Vdc(Vdc.Params(dc=(params.dut.v_load)))(p=tb.v_load, n=tb.VSS) - tb.v_outcm_ideal_src = Vdc(Vdc.Params(dc=(params.dut.voutcm_ideal), ac=(0 * m)))( - p=tb.voutcm_ideal, n=tb.VSS - ) - - # Input-driving balun - tb.inp = Diff() - tb.inpgen = Vdc(Vdc.Params(dc=(params.vc), ac=(500 * m)))(p=tb.inp.p, n=tb.VSS) - tb.inngen = Vdc(Vdc.Params(dc=(params.vc), ac=(-500 * m)))(p=tb.inp.n, n=tb.VSS) - - # Output & Load Caps - tb.out = Diff() - tb.CMSense = h.Signal() - Cload = C(C.Params(c=params.cl)) - Ccmfb = C(C.Params(c=10 * f)) - Rload = R(R.Params(r=params.rcm)) - tb.clp = Cload(p=tb.out.p, n=tb.VSS) - tb.cln = Cload(p=tb.out.n, n=tb.VSS) - tb.ccmfb = Ccmfb(p=tb.CMSense, n=tb.VSS) - tb.rcmp = Rload(p=tb.out.p, n=tb.CMSense) - tb.rcmn = Rload(p=tb.out.n, n=tb.CMSense) - tb.cmfb_src = Vcvs(Vcvs.Params(gain=params.CMFB_gain))( - p=tb.v_load, n=tb.VSS, cp=tb.CMSense, cn=tb.voutcm_ideal - ) - - # Create the Telescopic Amplifier DUT - tb.dut = stage1_telescopic_amplifier(params.dut)( - v_in=tb.inp, - v_out=tb.out, - v_tail=tb.v_tail, - v_ncasc=tb.v_ncasc, - v_pcasc=tb.v_pcasc, - v_load=tb.v_load, - VDD=VDD, - VSS=tb.VSS, - ) - return tb - - -def generate_stage1_telescopic_amplifier(amp_specs): - - nch_db_filename = "database_nch.npy" - nch_lvt_db_filename = "database_nch_lvt.npy" - pch_db_filename = "database_pch.npy" - pch_lvt_db_filename = "database_pch_lvt.npy" - - gen_params = dict( - tail_vstar_vds_margin=20e-3, - vgs_sweep_res=5e-3, - vds_sweep_res=15e-3, - casc_scale_min=0.5, - casc_scale_max=3, - casc_scale_step=0.5, - casc_bias_step=10e-3, - gamma=1, - cdd_cgg_ratio=1.1, - lch_in=0.15, - lch_tail=0.5, - lch_load=[0.15, 0.5, 1], - ) - - if amp_specs["in_type"] == "nch": - in_db_filename = nch_db_filename - elif amp_specs["in_type"] == "nch_lvt": - in_db_filename = nch_lvt_db_filename - elif amp_specs["in_type"] == "pch": - in_db_filename = pch_db_filename - elif amp_specs["in_type"] == "pch_lvt": - in_db_filename = pch_lvt_db_filename - - if amp_specs["load_type"] == "nch": - load_db_filename = nch_db_filename - elif amp_specs["load_type"] == "nch_lvt": - load_db_filename = nch_lvt_db_filename - elif amp_specs["load_type"] == "pch": - load_db_filename = pch_db_filename - elif amp_specs["load_type"] == "pch_lvt": - load_db_filename = pch_lvt_db_filename - - if amp_specs["tail_type"] == "nch": - tail_db_filename = nch_db_filename - elif amp_specs["tail_type"] == "nch_lvt": - tail_db_filename = nch_lvt_db_filename - elif amp_specs["tail_type"] == "pch": - tail_db_filename = pch_db_filename - elif amp_specs["tail_type"] == "pch_lvt": - tail_db_filename = pch_lvt_db_filename - - database_in = np.load(in_db_filename, allow_pickle=True).item() - database_tail = np.load(tail_db_filename, allow_pickle=True).item() - database_load = np.load(load_db_filename, allow_pickle=True).item() - - amp_specs["gm_id_in"] = 2 / amp_specs["vstar_in"] - - input_op = stage1_telescopic_amplifier_design_input( - database_in, amp_specs, gen_params - ) - load_op = stage1_telescopic_amplifier_design_load( - database_load, amp_specs, gen_params, input_op - ) - print("input op:") - pprint.pprint(input_op) - print("load op:") - pprint.pprint(load_op) - amplifier_op = stage1_telescopic_amplifier_design_amp( - amp_specs, gen_params, input_op, load_op - ) - print("amplifier op:") - pprint.pprint(amplifier_op) - tail_op = stage1_telescopic_amplifier_design_tail( - database_tail, amp_specs, gen_params, amplifier_op - ) - print("tail op:") - pprint.pprint(tail_op) - - if amp_specs["tail_type"] == "nch" or amp_specs["tail_type"] == "nch_lvt": - v_load = amplifier_op["load_base_bias"] - v_pcasc = amplifier_op["load_casc_bias"] - v_ncasc = amplifier_op["in_casc_bias"] - v_tail = tail_op["vbias_tail"] - else: - v_load = amplifier_op["load_base_bias"] - v_ncasc = amplifier_op["load_casc_bias"] - v_pcasc = amplifier_op["in_casc_bias"] - v_tail = tail_op["vbias_tail"] - - ampParams = TelescopicAmpParams( - in_base_pair_params=MosParams( - w=500 * m * int(np.round(amplifier_op["scale_in_base"])), - l=h.Prefixed(gen_params["lch_in"]), - nf=int(np.round(amplifier_op["scale_in_base"])), - ), - in_casc_pair_params=MosParams( - w=500 * m * int(np.round(amplifier_op["scale_in_casc"])), - l=h.Prefixed(gen_params["lch_in"]), - nf=int(np.round(amplifier_op["scale_in_casc"])), - ), - load_base_pair_params=MosParams( - w=500 * m * int(np.round(amplifier_op["scale_load_base"])), - l=h.Prefixed(gen_params["lch_load"]), - nf=int(np.round(amplifier_op["scale_load_base"])), - ), - load_casc_pair_params=MosParams( - w=500 * m * int(np.round(amplifier_op["scale_load_casc"])), - l=h.Prefixed(gen_params["lch_load"]), - nf=int(np.round(amplifier_op["scale_load_casc"])), - ), - tail_params=MosParams( - w=500 * m * int(np.round(tail_op["scale_tail"])), - l=h.Prefixed(gen_params["lch_tail"]), - nf=int(np.round(tail_op["scale_tail"])), - ), - in_type=amp_specs["in_type"], - load_type=amp_specs["load_type"], - tail_type=amp_specs["tail_type"], - voutcm_ideal=amp_specs["voutcm"] * 1000 * m, - v_load=v_load * 1000 * m, - v_pcasc=v_pcasc * 1000 * m, - v_ncasc=v_ncasc * 1000 * m, - v_tail=v_tail * 1000 * m, - ) - - params = Stage1TbParams( - pvt=Pvt(), - vc=amp_specs["vincm"] * 1000 * m, - vd=1 * m, - dut=ampParams, - cl=amp_specs["cload"] * 1000 * m, - ) - - # Create our simulation input - @hs.sim - class TelescopicAmplifierTranSim: - tb = AmplifierTbTran(params) - tr = hs.Tran(tstop=3000 * n, tstep=1 * n) - - # Add the PDK dependencies - # TelescopicAmplifierSim.lib(sky130.install.model_lib, 'tt') - TelescopicAmplifierSim.lib(sky130.install.model_lib, "tt") - TelescopicAmplifierSim.literal(".save all") - results = TelescopicAmplifierSim.run(sim_options) - tran_results = results.an[0].data - t = tran_results["time"] - v_out_diff = tran_results["v(xtop.out_p)"] - tran_results["v(xtop.out_n)"] - v_out_cm = (tran_results["v(xtop.out_p)"] + tran_results["v(xtop.out_n)"]) / 2 - v_in_diff = tran_results["v(xtop.inp_p)"] - tran_results["v(xtop.inp_n)"] - v_in_cm = (tran_results["v(xtop.inp_p)"] + tran_results["v(xtop.inp_n)"]) / 2 - - fig, ax = plt.subplots(2, sharex=True) - ax[0].plot(t, v_in_diff) - ax[1].plot(t, v_out_diff) - - plt.show() - - fig, ax = plt.subplots(2, sharex=True) - ax[0].plot(t, v_in_cm) - ax[1].plot(t, v_out_cm) - - # Create our simulation input - @hs.sim - class TelescopicAmplifierAcSim: - tb = AmplifierTbAc(params) - myac = hs.Ac(sweep=LogSweep(1e1, 1e11, 10)) - - # Add the PDK dependencies - TelescopicAmplifierAcSim.lib(sky130.install.model_lib, "tt") - TelescopicAmplifierAcSim.literal(".save all") - results = TelescopicAmplifierAcSim.run(sim_options) - ac_results = results.an[0].data - v_out_diff_ac = ac_results["v(xtop.out_p)"] - ac_results["v(xtop.out_n)"] - f = np.logspace(start=1, stop=11, num=101, endpoint=True) - f_ax = np.logspace(start=1, stop=11, num=11, endpoint=True) - f3db_idx = np.squeeze( - np.where(abs(v_out_diff_ac) < np.max(abs(v_out_diff_ac)) / np.sqrt(2)) - )[0] - fgbw_idx = np.squeeze(np.where(abs(v_out_diff_ac) < 1))[0] - f3db = f[f3db_idx] - fgbw = f[fgbw_idx] - Avdc = np.max(abs(v_out_diff_ac)) - v_out_diff_ac_dB = 20 * np.log10(abs(v_out_diff_ac)) - v_out_diff_ac_phase = ( - (np.angle(v_out_diff_ac) % (2 * np.pi) - 2 * np.pi) * 180 / np.pi - ) - PM_ac = 180 + v_out_diff_ac_phase[fgbw_idx] - fig, ax = plt.subplots(2, sharex=True) - ax[0].semilogx(f, v_out_diff_ac_dB) - ax[0].grid(color="black", linestyle="--", linewidth=0.5) - ax[0].grid(visible=True, which="both") - ax[0].set_xticks(f_ax) - ax[1].semilogx(f, v_out_diff_ac_phase) - ax[1].grid(color="black", linestyle="--", linewidth=0.5) - ax[1].grid(visible=True, which="both") - ax[1].set_xticks(f_ax) - plt.show() - print("Av (AC Sim) = %f" % ((Avdc))) - print("BW (AC Sim) = %f MHz" % (round(f3db / 1e6, 2))) - print("GBW (AC Sim) = %f MHz" % (round(fgbw / 1e6, 2))) - print("PM (AC Sim) = %f degrees" % PM_ac) - - ampResults = dict( - Av=Avdc, - bw=f3db, - gbw=fgbw, - pm=PM_ac, - ) - - return ampParams, ampResults - - -@h.paramclass -class Stage2AmpParams: - in_params = h.Param(dtype=MosParams, desc="Input pair MOS params") - load_params = h.Param(dtype=MosParams, desc="Load pair MOS params") - in_type = h.Param(dtype=str, desc="Input MOS type") - load_type = h.Param(dtype=str, desc="Load MOS type") - voutcm_ideal = h.Param(dtype=Prefixed, desc="Ideal Output CM") - v_load = h.Param(dtype=Prefixed, desc="Load MOS Bias") - - -@h.generator -def stage2_common_source_amplifier(params: Stage2AmpParams) -> h.Module: - - if params.in_type == "nch": - mos_in = nch - elif params.in_type == "nch_lvt": - mos_in = nch_lvt - elif params.in_type == "pch": - mos_in = pch - elif params.in_type == "pch_lvt": - mos_in = pch_lvt - - if params.load_type == "nch": - mos_load = nch - elif params.load_type == "nch_lvt": - mos_load = nch_lvt - elif params.load_type == "pch": - mos_load = pch - elif params.load_type == "pch_lvt": - mos_load = pch_lvt - - @h.module - class Stage2Amplifier: - - VDD, VSS = h.Ports(2) - v_in = Diff(port=True, role=Diff.Roles.SINK) - v_out = Diff(port=True, role=Diff.Roles.SOURCE) - v_load = h.Input() - - ## Stage 2 Input Devices - m_in_p = mos_in(params.in_params)(g=v_in.n, s=VSS, d=v_out.p, b=VSS) - m_in_n = mos_in(params.in_params)(g=v_in.p, s=VSS, d=v_out.n, b=VSS) - - ## Load Base Pair - m_load_p = mos_load(params.load_params)(g=v_load, s=VDD, d=v_out.p, b=VDD) - m_load_n = mos_load(params.load_params)(g=v_load, s=VDD, d=v_out.n, b=VDD) - - return Stage2Amplifier - - -@h.paramclass -class Stage2AmpTbParams: - dut = h.Param(dtype=Stage2AmpParams, desc="DUT params") - pvt = h.Param( - dtype=Pvt, desc="Process, Voltage, and Temperature Parameters", default=Pvt() - ) - vd = h.Param(dtype=h.Prefixed, desc="Differential Voltage (V)", default=1 * m) - vc = h.Param(dtype=h.Prefixed, desc="Common-Mode Voltage (V)", default=1200 * m) - cl = h.Param(dtype=h.Prefixed, desc="Load Cap (Single-Ended) (F)", default=100 * f) - rcm = h.Param( - dtype=h.Prefixed, desc="Common Mode Sensing Resistor (Ω)", default=1 * G - ) - CMFB_gain = h.Param( - dtype=h.Prefixed, desc="Common Mode Feedback Gain (V/V)", default=1 * KILO - ) - - -@h.generator -def Stage2AmpTbTran(params: Stage2AmpTbParams) -> h.Module: - - tb = h.sim.tb("Stage2AmplifierTbTran") - tb.VDD = VDD = h.Signal() - tb.vvdd = Vdc(Vdc.Params(dc=params.pvt.v, ac=(0 * m)))(p=VDD, n=tb.VSS) - - tb.v_load = h.Signal() - - tb.voutcm_ideal = h.Signal() - tb.v_outcm_ideal_src = Vdc(Vdc.Params(dc=(params.dut.voutcm_ideal), ac=(0 * m)))( - p=tb.voutcm_ideal, n=tb.VSS - ) - - # Input-driving balun - tb.inp = Diff() - tb.inpgen = DiffClkGen( - DiffClkParams( - period=1000 * n, delay=1 * n, vc=params.vc, vd=params.vd, trf=800 * PICO - ) - )(ck=tb.inp, VSS=tb.VSS) - - # Output & Load Caps - tb.out = Diff() - tb.CMSense = h.Signal() - Cload = C(C.Params(c=params.cl)) - Ccmfb = C(C.Params(c=100 * f)) - Rload = R(R.Params(r=params.rcm)) - tb.clp = Cload(p=tb.out.p, n=tb.VSS) - tb.cln = Cload(p=tb.out.n, n=tb.VSS) - tb.ccmfb = Ccmfb(p=tb.CMSense, n=tb.VSS) - tb.rcmp = Rload(p=tb.out.p, n=tb.CMSense) - tb.rcmn = Rload(p=tb.out.n, n=tb.CMSense) - tb.cmfb_src = Vcvs(Vcvs.Params(gain=params.CMFB_gain))( - p=tb.v_load, n=tb.VSS, cp=tb.CMSense, cn=tb.voutcm_ideal - ) - - # Create the Telescopic Amplifier DUT - tb.dut = stage2_common_source_amplifier(params.dut)( - v_in=tb.inp, v_out=tb.out, v_load=tb.v_load, VDD=VDD, VSS=tb.VSS - ) - return tb - - -def stage2_common_source_amplifier_design_and_scale_amp( - database_in, database_load, amp_specs, gen_params -): - - vdd = amp_specs["vdd"] - vincm = amp_specs["vincm"] - voutcm = amp_specs["voutcm"] - vgs_res = gen_params["vgs_sweep_res"] - gain_min = amp_specs["gain_min"] - stage1_gain = amp_specs["stage1_gain"] - bw_min = amp_specs["amp_bw_min"] - in_type = amp_specs["in_type"] - load_type = amp_specs["load_type"] - lch_in = gen_params["lch_in"] - lch_load = gen_params["lch_load"] - load_scale_min = gen_params["load_scale_min"] - load_scale_max = gen_params["load_scale_max"] - load_scale_step = gen_params["load_scale_step"] - rload = amp_specs["rload"] - - if in_type == "nch" or in_type == "nch_lvt": - vs_in = 0 - vs_load = vdd - else: - vs_in = vdd - vs_load = 0 - - vbs_in = 0 - vbs_load = 0 - vgs_in = vincm - vs_in - vds_in = voutcm - vs_in - vds_load = voutcm - vs_load - - ids_in = query_db(database_in, "ids", in_type, lch_in, vbs_in, vgs_in, vds_in) - gm_in = query_db(database_in, "gm", in_type, lch_in, vbs_in, vgs_in, vds_in) - gds_in = query_db(database_in, "gds", in_type, lch_in, vbs_in, vgs_in, vds_in) - cgg_in = query_db(database_in, "cgg", in_type, lch_in, vbs_in, vgs_in, vds_in) - cdd_in = cgg_in * gen_params["cdd_cgg_ratio"] - - load_scale_count = int((load_scale_max - load_scale_min) / load_scale_step) + 1 - load_scale_list = np.linspace( - load_scale_min, load_scale_max, load_scale_count, endpoint=True - ) - - metric_best = 0 - gain_max = 0 - bw_max = 0 - - for lch_load in gen_params["lch_load"]: - - for load_scale in load_scale_list: - - ids_load = ids_in / load_scale - try: - vgs_load = query_db_for_vgs( - database_load, - load_type, - lch_load, - vbs_load, - vds_load, - ids=ids_load, - mode="ids", - scale=1, - ) - except: - continue - - gm_load = ( - query_db( - database_load, - "gm", - load_type, - lch_load, - vbs_load, - vgs_load, - vds_load, - ) - * load_scale - ) - gds_load = ( - query_db( - database_load, - "gds", - load_type, - lch_load, - vbs_load, - vgs_load, - vds_load, - ) - * load_scale - ) - cgg_load = ( - query_db( - database_load, - "cgg", - load_type, - lch_load, - vbs_load, - vgs_load, - vds_load, - ) - * load_scale - ) - cdd_load = cgg_load * gen_params["cdd_cgg_ratio"] - - gds = gds_load + gds_in + (1 / rload) - cdd = cdd_in + cdd_load - - gain_cur = gm_in / gds - bw_cur = gds / cdd / 2 / np.pi - metric_cur = gain_cur * bw_cur / ids_in - - if load_type == "pch" or load_type == "pch_lvt": - load_bias = vdd + vgs_load - else: - load_bias = vgs_load - - if gain_cur > gain_min and bw_cur > bw_min: - if metric_cur > metric_best: - metric_best = metric_cur - best_op = dict( - Av=gain_cur, - bw=bw_cur, - load_scale=load_scale, - lch_load=lch_load, - gm_in=gm_in, - gm_load=gm_load, - gds=gds, - cdd=cdd, - metric=metric_best, - load_bias=load_bias, - ) - print("New GBW/I Best = %f MHz/µA" % (round(metric_best / 1e12, 2))) - print("Updated Av Best = %f" % (gain_cur)) - print("Updated BW Best = %f MHz" % (round(bw_cur / 1e6, 2))) - if gain_cur > gain_max: - gain_max = gain_cur - if bw_cur > bw_max: - bw_max = bw_cur - print("2nd Stage Av Best = %f" % ((gain_max))) - print("2nd Stage BW Best = %f MHz" % (round(bw_max / 1e6, 2))) - - vnoise_input_referred_max = amp_specs["vnoise_input_referred"] - cload = amp_specs["cload"] - vdd = amp_specs["vdd"] - in_type = amp_specs["in_type"] - k = 1.38e-23 - T = 300 - - ibias = ids_in - load_bias = best_op["load_bias"] - gm_in = best_op["gm_in"] - gamma_in = gen_params["gamma"] - gamma_load = gen_params["gamma"] - load_scale = best_op["load_scale"] - Av = best_op["Av"] - gds = best_op["gds"] - cdd = best_op["cdd"] - vnoise_squared_input_referred_max = vnoise_input_referred_max**2 - vnoise_squared_input_referred_per_scale = ( - 4 * k * T * (gamma_in * gm_in + gamma_load * gm_load) / (gm_in**2) - ) - scale_noise = max( - 1, vnoise_squared_input_referred_per_scale / vnoise_squared_input_referred_max - ) - gbw_min = bw_min * Av * stage1_gain - scale_bw = max( - 1, 2 * np.pi * gbw_min * cload / (gm_in - 2 * np.pi * gbw_min * (cdd + cgg_in)) - ) - print("2nd stage scale_noise:") - pprint.pprint(scale_noise) - print("2nd stage scale_bw:") - pprint.pprint(scale_bw) - - scale_amp = max(scale_bw, scale_noise) - - vnoise_density_squared_input_referred = ( - vnoise_squared_input_referred_per_scale / scale_amp - ) - vnoise_density_input_referred = np.sqrt(vnoise_density_squared_input_referred) - - amplifier_op = dict( - gm=gm_in * scale_amp, - gds=gds * scale_amp, - cgg=cgg_in * scale_amp, - cdd=cdd * scale_amp, - vnoise_density_input=vnoise_density_input_referred, - scale_in=scale_amp, - scale_load=scale_amp * load_scale, - load_bias=load_bias, - lch_load=best_op["lch_load"], - gain=Av, - ibias=ibias * scale_amp, - bw=(gds * scale_amp) / (2 * np.pi * (cload + (cdd * scale_amp))), - ) - - stage2AmpParams = Stage2AmpParams( - in_params=MosParams( - w=500 * m * int(np.round(amplifier_op["scale_in"])), - l=gen_params["lch_in"], - nf=int(np.round(amplifier_op["scale_in"])), - ), - load_params=MosParams( - w=500 * m * int(np.round(amplifier_op["scale_load"])), - l=amplifier_op["lch_load"], - nf=int(np.round(amplifier_op["scale_load"])), - ), - in_type=amp_specs["in_type"], - load_type=amp_specs["load_type"], - voutcm_ideal=voutcm * 1000 * m, - v_load=amplifier_op["load_bias"] * 1000 * m, - ) - - params = Stage2AmpTbParams( - pvt=Pvt(), - vc=vincm * 1000 * m, - vd=1 * m, - dut=stage2AmpParams, - cl=amp_specs["cload"] * 1000 * m, - ) - - # Create our simulation input - @hs.sim - class Stage2AmpTranSim: - tb = Stage2AmpTbTran(params) - tr = hs.Tran(tstop=3000 * n, tstep=1 * n) - - # Add the PDK dependencies - Stage2AmpTranSim.lib(sky130.install.model_lib, "tt") - Stage2AmpTranSim.literal(".save all") - results = Stage2AmpTranSim.run(sim_options) - tran_results = results.an[0].data - t = tran_results["time"] - v_out_diff = tran_results["v(xtop.out_p)"] - tran_results["v(xtop.out_n)"] - v_out_cm = (tran_results["v(xtop.out_p)"] + tran_results["v(xtop.out_n)"]) / 2 - v_in_diff = tran_results["v(xtop.inp_p)"] - tran_results["v(xtop.inp_n)"] - v_in_cm = (tran_results["v(xtop.inp_p)"] + tran_results["v(xtop.inp_n)"]) / 2 - v_load = tran_results["v(xtop.v_load)"] - - # v_out_stage1_diff = tran_results['v(xtop.out_stage1_p)'] - tran_results['v(xtop.out_stage1_n)'] - - fig, ax = plt.subplots(2, sharex=True) - ax[0].plot(t, v_in_diff) - ax[1].plot(t, v_out_diff) - - plt.show() - - fig, ax = plt.subplots(2, sharex=True) - ax[0].plot(t, v_in_cm) - ax[1].plot(t, v_out_cm) - - plt.show() - - fig, ax = plt.subplots(2, sharex=True) - ax[0].plot(t, v_load) - ax[1].plot(t, v_out_cm) - - plt.show() - - return stage2AmpParams, amplifier_op - - -def generate_stage2_common_source_amplifier(amp_specs): - nch_db_filename = "database_nch.npy" - nch_lvt_db_filename = "database_nch_lvt.npy" - pch_db_filename = "database_pch.npy" - pch_lvt_db_filename = "database_pch_lvt.npy" - - gen_params = dict( - tail_vstar_vds_margin=50e-3, - vgs_sweep_res=5e-3, - vds_sweep_res=15e-3, - load_scale_min=0.25, - load_scale_max=2, - load_scale_step=0.25, - gamma=1, - cdd_cgg_ratio=1.1, - lch_in=0.5, - lch_load=[0.35, 0.5, 1], - ) - - if amp_specs["in_type"] == "nch": - in_db_filename = nch_db_filename - elif amp_specs["in_type"] == "nch_lvt": - in_db_filename = nch_lvt_db_filename - elif amp_specs["in_type"] == "pch": - in_db_filename = pch_db_filename - elif amp_specs["in_type"] == "pch_lvt": - in_db_filename = pch_lvt_db_filename - - if amp_specs["load_type"] == "nch": - load_db_filename = nch_db_filename - elif amp_specs["load_type"] == "nch_lvt": - load_db_filename = nch_lvt_db_filename - elif amp_specs["load_type"] == "pch": - load_db_filename = pch_db_filename - elif amp_specs["load_type"] == "pch_lvt": - load_db_filename = pch_lvt_db_filename - - database_in = np.load(in_db_filename, allow_pickle=True).item() - database_load = np.load(load_db_filename, allow_pickle=True).item() - ampParams, amplifier_op = stage2_common_source_amplifier_design_and_scale_amp( - database_in, database_load, amp_specs, gen_params - ) - - print("2nd stage op:") - pprint.pprint(amplifier_op) - - return ampParams, amplifier_op - - -@h.paramclass -class TwoStageAmpParams: - stage1_tail_params = h.Param(dtype=MosParams, desc="Stage 1 Tail MOS params") - stage1_in_base_pair_params = h.Param( - dtype=MosParams, desc="Stage 1 Input pair MOS params" - ) - stage1_in_casc_pair_params = h.Param( - dtype=MosParams, desc="Stage 1 Input cascode MOS params" - ) - stage1_load_base_pair_params = h.Param( - dtype=MosParams, desc="Stage 1 Load base MOS params" - ) - stage1_load_casc_pair_params = h.Param( - dtype=MosParams, desc="Stage 1 Load cascode MOS params" - ) - stage1_in_type = h.Param(dtype=str, desc="Stage 1 Input MOS type") - stage1_load_type = h.Param(dtype=str, desc="Stage 1 Load MOS type") - stage1_tail_type = h.Param(dtype=str, desc="Stage 1 Tail MOS type") - stage1_voutcm_ideal = h.Param(dtype=Prefixed, desc="Stage 1 Ideal Output CM") - stage1_v_load = h.Param(dtype=Prefixed, desc="Stage 1 Load MOS Bias") - stage1_v_pcasc = h.Param(dtype=Prefixed, desc="Stage 1 PMOS Cascode Device Bias") - stage1_v_ncasc = h.Param(dtype=Prefixed, desc="Stage 1 NMOS Cascode Device Bias") - stage1_v_tail = h.Param(dtype=Prefixed, desc="Stage 1 Tail MOS Bias") - stage2_in_type = h.Param(dtype=str, desc="Stage 2 Input MOS type") - stage2_load_type = h.Param(dtype=str, desc="Stage 2 Load MOS type") - stage2_in_params = h.Param(dtype=MosParams, desc="Stage 2 Input MOS params") - stage2_load_params = h.Param(dtype=MosParams, desc="Stage 2 Load MOS params") - stage2_v_load = h.Param(dtype=Prefixed, desc="Stage 2 Load MOS Bias") - stage2_voutcm_ideal = h.Param(dtype=Prefixed, desc="Stage 2 Ideal Output CM") - c_comp = h.Param(dtype=Prefixed, desc="Compensation C Value") - r_comp = h.Param(dtype=Prefixed, desc="Compensation R Value") - - -@h.generator -def two_stage_amplifier(params: TwoStageAmpParams) -> h.Module: - - if params.stage2_in_type == "nch": - stage2_mos_in = nch - elif params.stage2_in_type == "nch_lvt": - stage2_mos_in = nch_lvt - elif params.stage2_in_type == "pch": - stage2_mos_in = pch - elif params.stage2_in_type == "pch_lvt": - stage2_mos_in = pch_lvt - - if params.stage2_load_type == "nch": - stage2_mos_load = nch - elif params.stage2_load_type == "nch_lvt": - stage2_mos_load = nch_lvt - elif params.stage2_load_type == "pch": - stage2_mos_load = pch - elif params.stage2_load_type == "pch_lvt": - stage2_mos_load = pch_lvt - - stage1Params = TelescopicAmpParams( - in_base_pair_params=params.stage1_in_base_pair_params, - in_casc_pair_params=params.stage1_in_casc_pair_params, - load_base_pair_params=params.stage1_load_base_pair_params, - load_casc_pair_params=params.stage1_load_casc_pair_params, - tail_params=params.stage1_tail_params, - in_type=params.stage1_in_type, - load_type=params.stage1_load_type, - tail_type=params.stage1_tail_type, - voutcm_ideal=params.stage1_voutcm_ideal, - v_load=params.stage1_v_load, - v_pcasc=params.stage1_v_pcasc, - v_ncasc=params.stage1_v_ncasc, - v_tail=params.stage1_v_tail, - ) - - @h.module - class TwoStageAmplifier: - - VDD, VSS = h.Ports(2) - v_in = Diff(port=True, role=Diff.Roles.SINK) - v_out = Diff(port=True, role=Diff.Roles.SOURCE) - v_out_stage1 = Diff(port=True, role=Diff.Roles.SOURCE) - v_comp_mid_p = h.Signal() - v_comp_mid_n = h.Signal() - v_tail_stage1 = h.Input() - v_load_stage1 = h.Input() - v_pcasc_stage1 = h.Input() - v_ncasc_stage1 = h.Input() - v_load_stage2 = h.Input() - - x_stage1 = stage1_telescopic_amplifier(stage1Params)( - v_in=v_in, - v_out=v_out_stage1, - v_tail=v_tail_stage1, - v_ncasc=v_ncasc_stage1, - v_pcasc=v_pcasc_stage1, - v_load=v_load_stage1, - VDD=VDD, - VSS=VSS, - ) - - C_comp_p = C(C.Params(c=params.c_comp))(p=v_out.p, n=v_comp_mid_p) - C_comp_n = C(C.Params(c=params.c_comp))(p=v_out.n, n=v_comp_mid_n) - R_comp_p = R(R.Params(r=params.r_comp))(p=v_comp_mid_p, n=v_out_stage1.n) - R_comp_n = R(R.Params(r=params.r_comp))(p=v_comp_mid_n, n=v_out_stage1.p) - - ## Stage 2 Input Devices - m_in_stage2_p = stage2_mos_in(params.stage2_in_params)( - g=v_out_stage1.n, s=VSS, d=v_out.p, b=VSS - ) - m_in_stage2_n = stage2_mos_in(params.stage2_in_params)( - g=v_out_stage1.p, s=VSS, d=v_out.n, b=VSS - ) - - ## Load Base Pair - m_load_stage2_p = stage2_mos_load(params.stage2_load_params)( - g=v_load_stage2, s=VDD, d=v_out.p, b=VDD - ) - m_load_stage2_n = stage2_mos_load(params.stage2_load_params)( - g=v_load_stage2, s=VDD, d=v_out.n, b=VDD - ) - - return TwoStageAmplifier - - -@h.paramclass -class TwoStageTbParams: - dut = h.Param(dtype=TwoStageAmpParams, desc="DUT params") - pvt = h.Param( - dtype=Pvt, desc="Process, Voltage, and Temperature Parameters", default=Pvt() - ) - vd = h.Param(dtype=h.Prefixed, desc="Differential Voltage (V)", default=1 * m) - vc = h.Param(dtype=h.Prefixed, desc="Common-Mode Voltage (V)", default=1200 * m) - cl = h.Param(dtype=h.Prefixed, desc="Load Cap (Single-Ended) (F)", default=100 * f) - ccm = h.Param( - dtype=h.Prefixed, desc="Common Mode Sensing Capacitor (F)", default=1 * f - ) - rcm = h.Param( - dtype=h.Prefixed, desc="Common Mode Sensing Resistor (Ω)", default=1 * G - ) - CMFB_gain_stage1 = h.Param( - dtype=h.Prefixed, desc="Common Mode Feedback Gain (V/V)", default=10 * KILO - ) - CMFB_gain_stage2 = h.Param( - dtype=h.Prefixed, desc="Common Mode Feedback Gain (V/V)", default=1 * KILO - ) - - -@h.generator -def TwoStageAmpTbTran(params: TwoStageTbParams) -> h.Module: - - tb = h.sim.tb("TwoStageAmplifierTbTran") - tb.VDD = VDD = h.Signal() - tb.vvdd = Vdc(Vdc.Params(dc=params.pvt.v, ac=(0 * m)))(p=VDD, n=tb.VSS) - - tb.v_tail_stage1 = h.Signal() - tb.v_tail_stage1_src = Vdc(Vdc.Params(dc=(params.dut.stage1_v_tail), ac=(0 * m)))( - p=tb.v_tail_stage1, n=tb.VSS - ) - - tb.v_pcasc_stage1 = h.Signal() - tb.v_pcasc_stage1_src = Vdc(Vdc.Params(dc=(params.dut.stage1_v_pcasc), ac=(0 * m)))( - p=tb.v_pcasc_stage1, n=tb.VSS - ) - - tb.v_ncasc_stage1 = h.Signal() - tb.v_ncasc_stage1_src = Vdc(Vdc.Params(dc=(params.dut.stage1_v_ncasc), ac=(0 * m)))( - p=tb.v_ncasc_stage1, n=tb.VSS - ) - - tb.v_load_stage1 = h.Signal() - - tb.v_load_stage2 = h.Signal() - - tb.voutcm_stage1_ideal = h.Signal() - tb.v_outcm_stage1_ideal_src = Vdc( - Vdc.Params(dc=(params.dut.stage1_voutcm_ideal), ac=(0 * m)) - )(p=tb.voutcm_stage1_ideal, n=tb.VSS) - - tb.voutcm_stage2_ideal = h.Signal() - tb.v_outcm_stage2_ideal_src = Vdc( - Vdc.Params(dc=(params.dut.stage2_voutcm_ideal), ac=(0 * m)) - )(p=tb.voutcm_stage2_ideal, n=tb.VSS) - - # Input-driving balun - tb.inp = Diff() - tb.inpgen = DiffClkGen( - DiffClkParams( - period=1000 * n, delay=1 * n, vc=params.vc, vd=params.vd, trf=800 * PICO - ) - )(ck=tb.inp, VSS=tb.VSS) - - # Output & Load Caps - tb.out = Diff() - tb.out_stage1 = Diff() - tb.CMSense_stage1 = h.Signal() - tb.CMSense_stage2 = h.Signal() - Cload = C(C.Params(c=params.cl)) - Ccmfb = C(C.Params(c=params.ccm)) - Rload = R(R.Params(r=params.rcm)) - tb.clp = Cload(p=tb.out.p, n=tb.VSS) - tb.cln = Cload(p=tb.out.n, n=tb.VSS) - tb.ccmfb_stage1 = Ccmfb(p=tb.CMSense_stage1, n=tb.VSS) - tb.ccmfb_stage2 = Ccmfb(p=tb.CMSense_stage2, n=tb.VSS) - tb.rcmp_stage1 = Rload(p=tb.out_stage1.p, n=tb.CMSense_stage1) - tb.rcmn_stage1 = Rload(p=tb.out_stage1.n, n=tb.CMSense_stage1) - tb.rcmp_stage2 = Rload(p=tb.out.p, n=tb.CMSense_stage2) - tb.rcmn_stage2 = Rload(p=tb.out.n, n=tb.CMSense_stage2) - tb.cmfb_stage1_src = Vcvs(Vcvs.Params(gain=params.CMFB_gain_stage1))( - p=tb.v_load_stage1, n=tb.VSS, cp=tb.CMSense_stage1, cn=tb.voutcm_stage1_ideal - ) - tb.cmfb_stage2_src = Vcvs(Vcvs.Params(gain=params.CMFB_gain_stage2))( - p=tb.v_load_stage2, n=tb.VSS, cp=tb.CMSense_stage2, cn=tb.voutcm_stage2_ideal - ) - - # Create the Telescopic Amplifier DUT - tb.dut = two_stage_amplifier(params.dut)( - v_in=tb.inp, - v_out=tb.out, - v_out_stage1=tb.out_stage1, - v_tail_stage1=tb.v_tail_stage1, - v_ncasc_stage1=tb.v_ncasc_stage1, - v_pcasc_stage1=tb.v_pcasc_stage1, - v_load_stage1=tb.v_load_stage1, - v_load_stage2=tb.v_load_stage2, - VDD=VDD, - VSS=tb.VSS, - ) - return tb - - -@h.generator -def TwoStageAmpTbAc(params: TwoStageTbParams) -> h.Module: - - tb = h.sim.tb("TwoStageAmplifierTbTran") - tb.VDD = VDD = h.Signal() - tb.vvdd = Vdc(Vdc.Params(dc=params.pvt.v, ac=(0 * m)))(p=VDD, n=tb.VSS) - - tb.v_tail_stage1 = h.Signal() - tb.v_tail_stage1_src = Vdc(Vdc.Params(dc=(params.dut.stage1_v_tail), ac=(0 * m)))( - p=tb.v_tail_stage1, n=tb.VSS - ) - - tb.v_pcasc_stage1 = h.Signal() - tb.v_pcasc_stage1_src = Vdc(Vdc.Params(dc=(params.dut.stage1_v_pcasc), ac=(0 * m)))( - p=tb.v_pcasc_stage1, n=tb.VSS - ) - - tb.v_ncasc_stage1 = h.Signal() - tb.v_ncasc_stage1_src = Vdc(Vdc.Params(dc=(params.dut.stage1_v_ncasc), ac=(0 * m)))( - p=tb.v_ncasc_stage1, n=tb.VSS - ) - - tb.v_load_stage1 = h.Signal() - - tb.v_load_stage2 = h.Signal() - - tb.voutcm_stage1_ideal = h.Signal() - tb.v_outcm_stage1_ideal_src = Vdc( - Vdc.Params(dc=(params.dut.stage1_voutcm_ideal), ac=(0 * m)) - )(p=tb.voutcm_stage1_ideal, n=tb.VSS) - - tb.voutcm_stage2_ideal = h.Signal() - tb.v_outcm_stage2_ideal_src = Vdc( - Vdc.Params(dc=(params.dut.stage2_voutcm_ideal), ac=(0 * m)) - )(p=tb.voutcm_stage2_ideal, n=tb.VSS) - - # Input-driving balun - tb.inp = Diff() - tb.inpgen = Vdc(Vdc.Params(dc=(params.vc), ac=(500 * m)))(p=tb.inp.p, n=tb.VSS) - tb.inngen = Vdc(Vdc.Params(dc=(params.vc), ac=(-500 * m)))(p=tb.inp.n, n=tb.VSS) - - # Output & Load Caps - tb.out = Diff() - tb.out_stage1 = Diff() - tb.CMSense_stage1 = h.Signal() - tb.CMSense_stage2 = h.Signal() - Cload = C(C.Params(c=params.cl)) - Ccmfb = C(C.Params(c=params.ccm)) - Rload = R(R.Params(r=params.rcm)) - tb.clp = Cload(p=tb.out.p, n=tb.VSS) - tb.cln = Cload(p=tb.out.n, n=tb.VSS) - tb.ccmfb_stage1 = Ccmfb(p=tb.CMSense_stage1, n=tb.VSS) - tb.ccmfb_stage2 = Ccmfb(p=tb.CMSense_stage2, n=tb.VSS) - tb.rcmp_stage1 = Rload(p=tb.out_stage1.p, n=tb.CMSense_stage1) - tb.rcmn_stage1 = Rload(p=tb.out_stage1.n, n=tb.CMSense_stage1) - tb.rcmp_stage2 = Rload(p=tb.out.p, n=tb.CMSense_stage2) - tb.rcmn_stage2 = Rload(p=tb.out.n, n=tb.CMSense_stage2) - tb.cmfb_stage1_src = Vcvs(Vcvs.Params(gain=params.CMFB_gain_stage1))( - p=tb.v_load_stage1, n=tb.VSS, cp=tb.CMSense_stage1, cn=tb.voutcm_stage1_ideal - ) - tb.cmfb_stage2_src = Vcvs(Vcvs.Params(gain=params.CMFB_gain_stage2))( - p=tb.v_load_stage2, n=tb.VSS, cp=tb.CMSense_stage2, cn=tb.voutcm_stage2_ideal - ) - - # Create the Telescopic Amplifier DUT - tb.dut = two_stage_amplifier(params.dut)( - v_in=tb.inp, - v_out=tb.out, - v_out_stage1=tb.out_stage1, - v_tail_stage1=tb.v_tail_stage1, - v_ncasc_stage1=tb.v_ncasc_stage1, - v_pcasc_stage1=tb.v_pcasc_stage1, - v_load_stage1=tb.v_load_stage1, - v_load_stage2=tb.v_load_stage2, - VDD=VDD, - VSS=tb.VSS, - ) - return tb - - -def generate_two_stage_ota(): - two_stage_amp_specs = dict( - stage1_in_type="nch_lvt", - stage1_tail_type="nch_lvt", - stage1_load_type="pch", - stage1_vstar_in=200e-3, - stage1_vincm=1, - stage1_voutcm=0.9, - stage1_vds_tail_min=0.25, - stage1_gain_min=100, - stage1_input_stage_gain_min=200, - stage1_bw_min=20e6, - stage2_in_type="nch", - stage2_load_type="pch", - stage2_voutcm=1, - bw_min=5e5, - gain_min=500, - vnoise_input_referred=10e-9, - cload=1000e-15, - rload=100e3, - vdd=1.8, - ) - - stage1_telescopic_amp_specs = dict( - in_type=two_stage_amp_specs["stage1_in_type"], - tail_type=two_stage_amp_specs["stage1_tail_type"], - load_type=two_stage_amp_specs["stage1_load_type"], - cload=100e-15, - vdd=two_stage_amp_specs["vdd"], - vstar_in=two_stage_amp_specs["stage1_vstar_in"], - vincm=two_stage_amp_specs["stage1_vincm"], - voutcm=two_stage_amp_specs["stage1_voutcm"], - vds_tail_min=two_stage_amp_specs["stage1_vds_tail_min"], - gain_min=two_stage_amp_specs["stage1_gain_min"], - input_stage_gain_min=two_stage_amp_specs["stage1_input_stage_gain_min"], - selfbw_min=two_stage_amp_specs["stage1_bw_min"], - bw_min=two_stage_amp_specs["stage1_bw_min"], - vnoise_input_referred=two_stage_amp_specs["vnoise_input_referred"] - * ( - two_stage_amp_specs["gain_min"] - / ( - two_stage_amp_specs["stage1_input_stage_gain_min"] - + two_stage_amp_specs["gain_min"] - ) - ), - ) - - ( - telescopic_amp_params, - telescopic_amp_results, - ) = generate_stage1_telescopic_amplifier(stage1_telescopic_amp_specs) - - stage2_common_source_amp_specs = dict( - in_type=two_stage_amp_specs["stage2_in_type"], - load_type=two_stage_amp_specs["stage2_load_type"], - cload=two_stage_amp_specs["cload"], - rload=100e3, - vdd=two_stage_amp_specs["vdd"], - vincm=two_stage_amp_specs["stage1_voutcm"], - voutcm=two_stage_amp_specs["stage2_voutcm"], - stage1_gain=telescopic_amp_results["Av"], - gain_min=two_stage_amp_specs["gain_min"] / telescopic_amp_results["Av"], - amp_bw_min=two_stage_amp_specs["bw_min"], - vnoise_input_referred=two_stage_amp_specs["vnoise_input_referred"] - * ( - telescopic_amp_results["Av"] - / (telescopic_amp_results["Av"] + two_stage_amp_specs["gain_min"]) - ) - * telescopic_amp_results["Av"], - ) - - stage2_params, stage2_op = generate_stage2_common_source_amplifier( - stage2_common_source_amp_specs - ) - - twoStageAmpParams = TwoStageAmpParams( - stage1_in_base_pair_params=telescopic_amp_params.in_base_pair_params, - stage1_in_casc_pair_params=telescopic_amp_params.in_casc_pair_params, - stage1_load_base_pair_params=telescopic_amp_params.load_base_pair_params, - stage1_load_casc_pair_params=telescopic_amp_params.load_casc_pair_params, - stage1_tail_params=telescopic_amp_params.tail_params, - stage1_in_type=telescopic_amp_params.in_type, - stage1_load_type=telescopic_amp_params.load_type, - stage1_tail_type=telescopic_amp_params.tail_type, - stage1_voutcm_ideal=telescopic_amp_params.voutcm_ideal, - stage1_v_load=telescopic_amp_params.v_load, - stage1_v_pcasc=telescopic_amp_params.v_pcasc, - stage1_v_ncasc=telescopic_amp_params.v_ncasc, - stage1_v_tail=telescopic_amp_params.v_tail, - stage2_in_type=stage2_params.in_type, - stage2_load_type=stage2_params.load_type, - stage2_in_params=stage2_params.in_params, - stage2_load_params=stage2_params.load_params, - stage2_v_load=stage2_params.v_load, - stage2_voutcm_ideal=stage2_params.voutcm_ideal, - c_comp=250 * FEMTO, - r_comp=1 * KILO, - ) - - params = TwoStageTbParams( - pvt=Pvt(), - vc=two_stage_amp_specs["stage1_vincm"] * 1000 * m, - vd=1 * m, - dut=twoStageAmpParams, - cl=two_stage_amp_specs["cload"] * 1000 * m, - ) - - # Create our simulation input - @hs.sim - class TwoStageAmpTranSim: - tb = TwoStageAmpTbTran(params) - tr = hs.Tran(tstop=3000 * n, tstep=1 * n) - - # Add the PDK dependencies - TwoStageAmpTranSim.lib(sky130.install.model_lib, "tt") - TwoStageAmpTranSim.literal(".save all") - results = TwoStageAmpTranSim.run(sim_options) - tran_results = results.an[0].data - t = tran_results["time"] - v_out_diff = tran_results["v(xtop.out_p)"] - tran_results["v(xtop.out_n)"] - v_out_cm = (tran_results["v(xtop.out_p)"] + tran_results["v(xtop.out_n)"]) / 2 - v_in_diff = tran_results["v(xtop.inp_p)"] - tran_results["v(xtop.inp_n)"] - v_in_cm = (tran_results["v(xtop.inp_p)"] + tran_results["v(xtop.inp_n)"]) / 2 - - v_out_stage1_diff = ( - tran_results["v(xtop.out_stage1_p)"] - tran_results["v(xtop.out_stage1_n)"] - ) - v_out_stage1_cm = ( - tran_results["v(xtop.out_stage1_p)"] + tran_results["v(xtop.out_stage1_n)"] - ) / 2 - - fig, ax = plt.subplots(2, sharex=True) - ax[0].plot(t, v_in_diff) - ax[1].plot(t, v_out_diff) - - plt.show() - - fig, ax = plt.subplots(2, sharex=True) - ax[0].plot(t, v_in_cm) - ax[1].plot(t, v_out_cm) - - plt.show() - - fig, ax = plt.subplots(2, sharex=True) - ax[0].plot(t, v_out_stage1_cm) - ax[1].plot(t, v_out_stage1_diff) - - plt.show() - - # Create our simulation input - @hs.sim - class TwoStageAmpAcSim: - tb = TwoStageAmpTbAc(params) - myac = hs.Ac(sweep=LogSweep(1e1, 1e11, 10)) - - # Add the PDK dependencies - TwoStageAmpAcSim.lib(sky130.install.model_lib, "tt") - TwoStageAmpAcSim.literal(".save all") - results = TwoStageAmpAcSim.run(sim_options) - ac_results = results.an[0].data - v_out_diff_ac = ac_results["v(xtop.out_p)"] - ac_results["v(xtop.out_n)"] - f = np.logspace(start=1, stop=11, num=101, endpoint=True) - f_ax = np.logspace(start=1, stop=11, num=11, endpoint=True) - f3db_idx = np.squeeze( - np.where(abs(v_out_diff_ac) < np.max(abs(v_out_diff_ac)) / np.sqrt(2)) - )[0] - fgbw_idx = np.squeeze(np.where(abs(v_out_diff_ac) < 1))[0] - f3db = f[f3db_idx] - fgbw = f[fgbw_idx] - Avdc = np.max(abs(v_out_diff_ac)) - v_out_diff_ac_dB = 20 * np.log10(abs(v_out_diff_ac)) - v_out_diff_ac_phase = ( - (np.angle(v_out_diff_ac) % (2 * np.pi) - 2 * np.pi) * 180 / np.pi - ) - PM_ac = 180 + v_out_diff_ac_phase[fgbw_idx] - fig, ax = plt.subplots(2, sharex=True) - ax[0].semilogx(f, v_out_diff_ac_dB) - ax[0].grid(color="black", linestyle="--", linewidth=0.5) - ax[0].grid(visible=True, which="both") - ax[0].set_xticks(f_ax) - ax[1].semilogx(f, v_out_diff_ac_phase) - ax[1].grid(color="black", linestyle="--", linewidth=0.5) - ax[1].grid(visible=True, which="both") - ax[1].set_xticks(f_ax) - plt.show() - print("Av (AC Sim) = %f" % ((Avdc))) - print("BW (AC Sim) = %f MHz" % (round(f3db / 1e6, 2))) - print("GBW (AC Sim) = %f MHz" % (round(fgbw / 1e6, 2))) - print("PM (AC Sim) = %f degrees" % PM_ac) - - ampResults = dict( - Av=Avdc, - bw=f3db, - gbw=fgbw, - pm=PM_ac, - ) - - -if __name__ == "__main__": - generate_two_stage_ota() - breakpoint() diff --git a/adc/telescopic_cascode_diffamp_generator/__init__.py b/adc/telescopic_cascode_diffamp_generator/__init__.py new file mode 100644 index 0000000..731554b --- /dev/null +++ b/adc/telescopic_cascode_diffamp_generator/__init__.py @@ -0,0 +1,5 @@ + +import nest_asyncio + +nest_asyncio.apply() + diff --git a/adc/telescopic_cascode_diffamp_generator/dbstuff.py b/adc/telescopic_cascode_diffamp_generator/dbstuff.py new file mode 100644 index 0000000..7f53650 --- /dev/null +++ b/adc/telescopic_cascode_diffamp_generator/dbstuff.py @@ -0,0 +1,115 @@ + +import numpy as np +import scipy.interpolate + + +# Query the given database for the given variable name, for the given lch, vbs, vgs, vds +def query_db(database, varname, mos_type, lch, vbs, vgs, vds): + + mos_list = database["mos_list"] + mos_index = mos_list.index(mos_type) + lch_list = database["lch_list"] + lch_index = lch_list.index(lch) + var_raw = database[varname][mos_index, lch_index, :, :, :] + if mos_type == "nch" or mos_type == "nch_lvt": + vgs_raw = np.array(database["vgs_list"]) + vds_raw = np.array(database["vds_list"]) + vbs_raw = np.array(database["vbs_list"]) + else: + vgs_raw = -np.array(database["vgs_list"]) + vds_raw = -np.array(database["vds_list"]) + vbs_raw = -np.array(database["vbs_list"]) + + interp = scipy.interpolate.RegularGridInterpolator( + (vbs_raw, vgs_raw, vds_raw), var_raw + ) + return interp([vbs, vgs, vds]).item() + + +# Specialized query function to find the vgs of a transistor if all other OP conditions are known. +# It has two operation modes: +# 'vstar' mode (where vstar = 2 * ID / gm): Find the vgs of the device if its lch, vbs, vds and gm/ID are known +# 'ids' mode: Find the vgs of the device if its lch, vbs, vds and ids are known. +def query_db_for_vgs( + database, mos_type, lch, vbs, vds, vstar=200e-3, ids=0, mode="vstar", scale=1 +): + mos_list = database["mos_list"] + mos_index = mos_list.index(mos_type) + lch_list = database["lch_list"] + lch_index = lch_list.index(lch) + if mos_type == "nch" or mos_type == "nch_lvt": + vgs_raw = np.array(database["vgs_list"]) + vds_raw = np.array(database["vds_list"]) + vbs_raw = np.array(database["vbs_list"]) + else: + vgs_raw = -np.array(database["vgs_list"]) + vds_raw = -np.array(database["vds_list"]) + vbs_raw = -np.array(database["vbs_list"]) + + ids_raw = database["ids"][mos_index, lch_index, :, :, :] * scale + gm_raw = database["gm"][mos_index, lch_index, :, :, :] * scale + + if mode == "vstar": + target_gm_id = 2 / vstar + gm_id_raw = gm_raw / ids_raw + interp = scipy.interpolate.RegularGridInterpolator( + (vbs_raw, vgs_raw, vds_raw), gm_id_raw + ) + + resample_vector_vbs = [vbs] * (np.shape(vgs_raw)[0]) + resample_vector_vds = [vds] * (np.shape(vgs_raw)[0]) + resample_points = np.transpose( + np.vstack((resample_vector_vbs, vgs_raw, resample_vector_vds)) + ) + gm_id_raw_1d = interp(resample_points) + + interp_vgs = scipy.interpolate.interp1d(gm_id_raw_1d, vgs_raw) + + try: + return interp_vgs(target_gm_id).item() + except: + raise ValueError + + elif mode == "ids": + interp = scipy.interpolate.RegularGridInterpolator( + (vbs_raw, vgs_raw, vds_raw), ids_raw + ) + + resample_vector_vbs = [vbs] * (np.shape(vgs_raw)[0]) + resample_vector_vds = [vds] * (np.shape(vgs_raw)[0]) + resample_points = np.transpose( + np.vstack((resample_vector_vbs, vgs_raw, resample_vector_vds)) + ) + ids_raw_1d = interp(resample_points) + + interp_vgs = scipy.interpolate.interp1d(ids_raw_1d, vgs_raw) + + try: + return interp_vgs(ids).item() + except: + raise ValueError + + +# Query function that returns the function for the given variable instead of a single datapoint. +# Useful for optimization kinda stuff where you iterate over functions. +def query_db_for_function(database, varname, mos_type, lch): + + mos_list = database["mos_list"] + mos_index = mos_list.index(mos_type) + lch_list = database["lch_list"] + lch_index = lch_list.index(lch) + + var_raw = database[varname][mos_index, lch_index, :, :, :] + if mos_type == "nch" or mos_type == "nch_lvt": + vgs_raw = np.array(database["vgs_list"]) + vds_raw = np.array(database["vds_list"]) + vbs_raw = np.array(database["vbs_list"]) + else: + vgs_raw = -np.array(database["vgs_list"]) + vds_raw = -np.array(database["vds_list"]) + vbs_raw = -np.array(database["vbs_list"]) + + interp = scipy.interpolate.RegularGridInterpolator( + (vbs_raw, vgs_raw, vds_raw), var_raw + ) + return interp diff --git a/adc/telescopic_cascode_diffamp_generator/shared.py b/adc/telescopic_cascode_diffamp_generator/shared.py new file mode 100644 index 0000000..b9cd220 --- /dev/null +++ b/adc/telescopic_cascode_diffamp_generator/shared.py @@ -0,0 +1,9 @@ +# PDK Imports +import sky130 + +# And give a few shorthand names to PDK content +MosParams = sky130.Sky130MosParams +nch = sky130.modules.sky130_fd_pr__nfet_01v8 +nch_lvt = sky130.modules.sky130_fd_pr__nfet_01v8_lvt +pch = sky130.modules.sky130_fd_pr__pfet_01v8 +pch_lvt = sky130.modules.sky130_fd_pr__pfet_01v8_lvt diff --git a/adc/telescopic_cascode_diffamp_generator/stage1.py b/adc/telescopic_cascode_diffamp_generator/stage1.py new file mode 100644 index 0000000..b550b4f --- /dev/null +++ b/adc/telescopic_cascode_diffamp_generator/stage1.py @@ -0,0 +1,1032 @@ +import pprint + +import numpy as np +import scipy.optimize as sciopt +import matplotlib.pyplot as plt + + +import hdl21 as h +import hdl21.sim as hs +from hdl21 import Diff +from hdl21.prefix import m, f, n, PICO, G, KILO, Prefixed +from hdl21.primitives import Vdc, C, R, Vcvs + +# PDK Imports +import sky130, sitepdks as _ + +# Local imports +from .dbstuff import query_db, query_db_for_function, query_db_for_vgs +from .shared import MosParams, nch, nch_lvt, pch, pch_lvt +from ..testutils import ( + Pvt, + sim_options, + DiffClkParams, + DiffClkGen, +) + +# Function that designs the input stage (input & cascode devices) of the telescopic amplifier. +# The goal is to find the highest gain-bandwidth product operating point for this stage. +def stage1_telescopic_amplifier_design_input(database, amp_specs, gen_params): + + """Find operating point that meets the given vstar (2*ID/gm) spec, + while maximizing the gain-bandwidth product of the input stage.""" + + vdd = amp_specs["vdd"] + voutcm = amp_specs["voutcm"] + vincm = amp_specs["vincm"] + vstar_in = amp_specs["vstar_in"] + vdst_min = amp_specs["vds_tail_min"] + vds_sweep_res = gen_params["vds_sweep_res"] + casc_scale_max = gen_params["casc_scale_max"] + casc_scale_min = gen_params["casc_scale_min"] + casc_scale_step = gen_params["casc_scale_step"] + in_type = amp_specs["in_type"] + lch_in = gen_params["lch_in"] + + # We will sweep the cascode scale, so initialize the sweep list + num_casc_scale_points = ( + int(np.ceil((casc_scale_max - casc_scale_min) / casc_scale_step)) + 1 + ) + casc_scale_list = np.linspace( + casc_scale_min, casc_scale_max, num_casc_scale_points, endpoint=True + ) + + # Take care of input-type-specific variables + if in_type == "nch" or in_type == "nch_lvt": + vb = 0 + vtail_lim = vdst_min + vds_in_lim_0 = vstar_in + vds_in_lim_1 = (voutcm - vtail_lim) * 2 / 3 + num_vds_points = int(np.ceil((vds_in_lim_1 - vds_in_lim_0) / vds_sweep_res)) + 1 + vds_in_val_list = np.linspace( + vds_in_lim_0, vds_in_lim_1, num_vds_points, endpoint=True + ) + + else: + vb = vdd + vtail_lim = vb - vdst_min + vds_in_lim_0 = -vstar_in + vds_in_lim_1 = (voutcm - vtail_lim) * 2 / 3 + num_vds_points = int(np.ceil((vds_in_lim_0 - vds_in_lim_1) / vds_sweep_res)) + 1 + vds_in_val_list = np.linspace( + vds_in_lim_0, vds_in_lim_1, num_vds_points, endpoint=True + ) + + # Initialize the metric, given that it being 0 is bad. The metric can be anything, but + # in this generator, it is defined to be the gain-bandwidth of the input stage. + metric_best = 0 + + # We have to start somewhere, we will sweep the vds of the input device since that should + # be something we probably are not as sensitive to (compared to, say, the vgs of the input device) + for vds_in in vds_in_val_list: + + # First iteration to find approximate vgs (and hence vsource) to account for vbs later + vcasc_mid = vdst_min + vds_in + vsource = vdst_min + try: + vbs_in = vb - vsource + vgs_in = query_db_for_vgs( + database, in_type, lch_in, vbs_in, vds_in, vstar=vstar_in + ) + except ValueError: + continue + vsource = vincm - vgs_in + vcasc_mid = vsource + vds_in + + # Second iteration, for more exact result with approximately the correct VBS + try: + vbs_in = vb - vsource + vgs_in = query_db_for_vgs( + database, in_type, lch_in, vbs_in, vds_in, vstar=vstar_in + ) + except ValueError: + continue + + # From the more accurate vgs, we can find a whole bunch of new operating points + vsource = vincm - vgs_in + vcasc_mid = vsource + vds_in + vbs_in = vb - vsource + vds_in = vcasc_mid - vsource + ids_in = query_db(database, "ids", in_type, lch_in, vbs_in, vgs_in, vds_in) + vbs_casc = vb - vcasc_mid + vds_casc = voutcm - vcasc_mid + vds_in = vcasc_mid - vsource + gm_in = query_db(database, "gm", in_type, lch_in, vbs_in, vgs_in, vds_in) + + # Now we'll sweep the cascode scale list to find the optimum value for that. + # More intelligent search algorithms will actually converge to the most optimal cascode scale. + for casc_scale in casc_scale_list: + ids_casc = ids_in / casc_scale + + # Find the cascode vgs since we know its current, vbs and vds + try: + vgs_casc = query_db_for_vgs( + database=database, + mos_type=in_type, + lch=lch_in, + vbs=vbs_casc, + vds=vds_casc, + ids=ids_casc, + mode="ids", + ) + except: + continue + + # If cascode gate is an unrealizable voltage within the rails, just move on + vg_casc = vcasc_mid + vgs_casc + if vg_casc > vdd or vg_casc < 0: + continue + + gm_casc = ( + query_db(database, "gm", in_type, lch_in, vbs_casc, vgs_casc, vds_casc) + * casc_scale + ) + gds_casc = ( + query_db(database, "gds", in_type, lch_in, vbs_casc, vgs_casc, vds_casc) + * casc_scale + ) + gds_base = query_db( + database, "gds", in_type, lch_in, vbs_in, vgs_in, vds_in + ) + + gds_in = gds_base * gds_casc / (gds_base + gds_casc + gm_casc) + Av_cur = gm_in / gds_in + + cgg_casc = ( + query_db(database, "cgg", in_type, lch_in, vbs_casc, vgs_casc, vds_casc) + * casc_scale + ) + cgg_in = query_db(database, "cgg", in_type, lch_in, vbs_in, vgs_in, vds_in) + + cdd_casc = cgg_casc * gen_params["cdd_cgg_ratio"] + + bw_cur = gds_in / cdd_casc + metric_cur = Av_cur * bw_cur + + # If we meet the gain spec AND we exceed the best GBW product, then save this op as the best + if ( + Av_cur > (amp_specs["input_stage_gain_min"]) + and metric_cur > metric_best + ): + metric_best = metric_cur + Av_best = Av_cur + vgs_best = vgs_in + casc_scale_best = casc_scale + input_op = dict( + Av=Av_cur, + vgs=vgs_in, + casc_scale=casc_scale, + casc_bias=vg_casc, + vin_mid=vcasc_mid, + ibias=ids_in, + gm_in=gm_in, + gm_casc=gm_casc, + gds_base=gds_in, + gds_casc=gds_casc, + gds_in=gds_in, + cdd_in=cgg_casc * gen_params["cdd_cgg_ratio"], + cgg_casc=cgg_casc, + cgg_base=cgg_in, + vtail=vsource, + ) + + print("New Av Best = %f" % (Av_best)) + print("Updated VGS Best = %f" % (vgs_best)) + print("Updated Casc Scale Best = %f" % (casc_scale_best)) + + print("--------------------------------") + print("Input Stage Design:") + print("Av Best = %f" % (Av_best)) + print("VGS Best = %f" % (vgs_best)) + print("Casc Scale Best = %f" % (casc_scale_best)) + print("VDS_in Best = %f" % (input_op["vin_mid"] - input_op["vtail"])) + print("VDS_casc Best = %f" % (voutcm - input_op["vin_mid"])) + print("--------------------------------") + + return input_op + + +# Function that designs the load stage (load current source & cascode devices) of the telescopic amplifier. +# The goal is to find the highest gain-bandwidth-product-per-µA operating point for this stage. +def stage1_telescopic_amplifier_design_load(database, amp_specs, gen_params, input_op): + """Design load. + + Sweep vgs. For each vgs, compute gain and max bandwidth. If + both gain and BW specs are met, pick operating point that maximizes + gamma_r * gm_r + """ + vdd = amp_specs["vdd"] + vstar_in = amp_specs["vstar_in"] + voutcm = amp_specs["voutcm"] + vgs_res = gen_params["vgs_sweep_res"] + gain_min = amp_specs["gain_min"] + bw_min = max(amp_specs["selfbw_min"], amp_specs["bw_min"]) + casc_scale_max = gen_params["casc_scale_max"] + casc_scale_step = gen_params["casc_scale_step"] + casc_bias_step = gen_params["casc_bias_step"] + vds_sweep_res = gen_params["vds_sweep_res"] + in_type = amp_specs["in_type"] + load_type = amp_specs["load_type"] + lch_load = gen_params["lch_load"] + best_load_op = None + metric_best = 0 + gain_max = 0 + bw_max = 0 + + casc_scale_list = np.arange( + 1, casc_scale_max + casc_scale_step / 2, casc_scale_step + ) + if in_type == "nch" or in_type == "nch_lvt": + vs = vdd + vb = vdd + casc_bias_list = np.arange(0.5, voutcm + casc_bias_step / 2, casc_bias_step) + vgs_base_max = -0.1 + vgs_base_min = -1.5 + vds_base_lim_0 = -vstar_in + vds_base_lim_1 = (voutcm - vdd) * 2 / 3 + num_vds_points = ( + int(np.ceil((vds_base_lim_0 - vds_base_lim_1) / vds_sweep_res)) + 1 + ) + vds_base_val_list = np.linspace( + vds_base_lim_0, vds_base_lim_1, num_vds_points, endpoint=True + ) + else: + vs = 0 + vb = 0 + casc_bias_list = np.arange( + voutcm, vdd - 0.5 + casc_bias_step / 2, casc_bias_step + ) + vgs_base_min = 0.1 + vgs_base_max = 1.5 + vds_base_lim_0 = vstar_in + vds_base_lim_1 = (voutcm) * 2 / 3 + num_vds_points = ( + int(np.ceil((vds_base_lim_1 - vds_base_lim_0) / vds_sweep_res)) + 1 + ) + vds_base_val_list = np.linspace( + vds_base_lim_0, vds_base_lim_1, num_vds_points, endpoint=True + ) + + for lch_load in gen_params["lch_load"]: + gm_fun = query_db_for_function(database, "gm", load_type, lch_load) + gds_fun = query_db_for_function(database, "gds", load_type, lch_load) + cgg_fun = query_db_for_function(database, "cgg", load_type, lch_load) + gamma = gen_params["gamma"] + ib_fun = query_db_for_function(database, "ids", load_type, lch_load) + + num_points = int(np.ceil((vgs_base_max - vgs_base_min) / vgs_res)) + 1 + + gm_in_base = input_op["gm_in"] + gm_in_casc = input_op["gm_casc"] + gds_in_base = input_op["gds_in"] + gds_in_casc = input_op["gds_casc"] + ibias_in = input_op["ibias"] + gds_in = input_op["gds_in"] + cgg_in = input_op["cgg_casc"] + cdd_in = cgg_in * gen_params["cdd_cgg_ratio"] + + def vgs_base_search_fun(vgs_base, vcasc_mid, casc_scale, vg_casc, vsource): + vgs_casc = vg_casc - vcasc_mid + vds_casc = voutcm - vcasc_mid + vbs_casc = vb - vcasc_mid + vds_base = vcasc_mid - vsource + vbs_base = 0 + + ids_casc = ib_fun([vbs_casc, vgs_casc, vds_casc])[0] * casc_scale + ids_base = ib_fun([vbs_base, vgs_base, vds_base])[0] + + return ids_casc - ids_base + + for vds_base in vds_base_val_list: + vcasc_mid = vs + vds_base + for casc_scale in casc_scale_list: + for casc_bias in casc_bias_list: + try: + vgs_base = sciopt.brentq( + vgs_base_search_fun, + vgs_base_min, + vgs_base_max, + args=( + vcasc_mid, + casc_scale, + casc_bias, + vs, + ), + ) + except ValueError: + continue + vgs_casc = casc_bias - vcasc_mid + vds_casc = voutcm - vcasc_mid + vbs_casc = vb - vcasc_mid + vds_base = vcasc_mid - vs + vbs_base = 0 + + ibias_base = ib_fun([vbs_base, vgs_base, vds_base])[0] + load_scale = ibias_in / ibias_base + + gm_base = gm_fun([vbs_base, vgs_base, vds_base])[0] + gds_base = gds_fun([vbs_base, vgs_base, vds_base])[0] + cdd_base = ( + cgg_fun([vbs_base, vgs_base, vds_base])[0] + * gen_params["cdd_cgg_ratio"] + ) + + gm_casc = gm_fun([vbs_casc, vgs_casc, vds_casc])[0] * casc_scale + gds_casc = gds_fun([vbs_casc, vgs_casc, vds_casc])[0] * casc_scale + cdd_casc = ( + cgg_fun([vbs_casc, vgs_casc, vds_casc])[0] + * gen_params["cdd_cgg_ratio"] + * casc_scale + ) + + gm_load = gm_base * load_scale + gds_load = ( + gds_base + * gds_casc + / (gds_base + gds_casc + gm_casc) + * load_scale + ) + cdd_load = cdd_casc * load_scale + + bw_cur = (gds_load + gds_in) / (cdd_load + cdd_in) / 2 / np.pi + gain_cur = gm_in_base / (gds_load + gds_in) + + metric_cur = gain_cur * bw_cur / ibias_in + + if load_type == "pch" or load_type == "pch_lvt": + base_bias = vdd + vgs_base + else: + base_bias = vgs_base + + if gain_cur > gain_min and bw_cur > bw_min: + if metric_cur > metric_best: + metric_best = metric_cur + best_load_op = dict( + Av=gain_cur, + bw=bw_cur, + casc_scale=casc_scale, + casc_bias=casc_bias, + base_bias=base_bias, + vload_mid=vcasc_mid, + load_scale=load_scale, + lch_load=lch_load, + gm_load=gm_load, + gds_load=gds_load, + cdd_load=cdd_load, + metric_load=metric_best, + ) + print( + "New GBW/I Best = %f MHz/µA" + % (round(metric_best / 1e12, 2)) + ) + print("Updated Av Best = %f" % (gain_cur)) + print("Updated BW Best = %f MHz" % (round(bw_cur / 1e6, 2))) + if gain_cur > gain_max: + gain_max = gain_cur + if bw_cur > bw_max: + bw_max = bw_cur + print("Load Av Best = %f" % ((gain_max))) + print("Load BW Best = %f MHz" % (round(bw_max / 1e6, 2))) + return best_load_op + + +def stage1_telescopic_amplifier_design_amp(amp_specs, gen_params, input_op, load_op): + + vnoise_input_referred_max = amp_specs["vnoise_input_referred"] + bw_min = amp_specs["bw_min"] + cload = amp_specs["cload"] + vdd = amp_specs["vdd"] + in_type = amp_specs["in_type"] + k = 1.38e-23 + T = 300 + + ibias = input_op["ibias"] + vtail = input_op["vtail"] + gm_in = input_op["gm_in"] + gds_in = input_op["gds_in"] + gds_in_casc = input_op["gds_casc"] + gds_in_base = input_op["gds_base"] + gm_in_casc = input_op["gm_casc"] + gamma_in = gen_params["gamma"] + cdd_in = input_op["cdd_in"] + cgg_in = input_op["cgg_base"] + gm_load = load_op["gm_load"] + gds_load = load_op["gds_load"] + cdd_load = load_op["cdd_load"] + gamma_load = gen_params["gamma"] + load_scale = load_op["load_scale"] + load_casc_scale = load_op["casc_scale"] + load_casc_bias = load_op["casc_bias"] + load_base_bias = load_op["base_bias"] + in_casc_scale = input_op["casc_scale"] + in_casc_bias = input_op["casc_bias"] + Av = load_op["Av"] + + gds_tot = gds_in + gds_load + cdd_tot = cdd_in + cdd_load + vnoise_squared_input_referred_max = vnoise_input_referred_max**2 + vnoise_squared_input_referred_per_scale = ( + 4 * k * T * (gamma_in * gm_in + gamma_load * gm_load) / (gm_in**2) + ) + scale_noise = max( + 1, vnoise_squared_input_referred_per_scale / vnoise_squared_input_referred_max + ) + scale_bw = max( + 1, 2 * np.pi * bw_min * cload / (gds_tot - 2 * np.pi * bw_min * cdd_tot) + ) + print("scale_noise:") + pprint.pprint(scale_noise) + print("scale_bw:") + pprint.pprint(scale_bw) + + scale_amp = max(scale_bw, scale_noise) + + vnoise_density_squared_input_referred = ( + vnoise_squared_input_referred_per_scale / scale_amp + ) + vnoise_density_input_referred = np.sqrt(vnoise_density_squared_input_referred) + + amplifier_op = dict( + gm=gm_in * scale_amp, + gds=gds_tot * scale_amp, + cgg=cgg_in * scale_amp, + cdd=cdd_tot * scale_amp, + vnoise_density_input=vnoise_density_input_referred, + scale_in_base=scale_amp, + scale_in_casc=scale_amp * in_casc_scale, + scale_load_base=scale_amp * load_scale, + scale_load_casc=scale_amp * load_scale * load_casc_scale, + vtail=vtail, + load_base_bias=load_base_bias, + load_casc_bias=load_casc_bias, + in_casc_bias=in_casc_bias, + ibias=ibias * scale_amp * 2, + gain=Av, + bw=(gds_tot * scale_amp) / (2 * np.pi * (cload + (cdd_tot * scale_amp))), + ) + return amplifier_op + + +def stage1_telescopic_amplifier_design_tail( + database, amp_specs, gen_params, amplifier_op +): + + vdd = amp_specs["vdd"] + vtail = amplifier_op["vtail"] + vstar_tail = vtail - gen_params["tail_vstar_vds_margin"] + in_type = amp_specs["in_type"] + + if in_type == "nch" or in_type == "nch_lvt": + vds_tail = vtail + vbs_tail = 0 + else: + vds_tail = vtail - vdd + vbs_tail = 0 + + lch_tail = gen_params["lch_tail"] + + ib_fun = query_db_for_function(database, "ids", in_type, lch_tail) + itarg = amplifier_op["ibias"] + + vgs_tail = query_db_for_vgs( + database=database, + mos_type=in_type, + lch=lch_tail, + vbs=vbs_tail, + vds=vds_tail, + vstar=vstar_tail, + mode="vstar", + ) + + ids_tail = ib_fun([vbs_tail, vgs_tail, vds_tail])[0] + scale_tail = itarg / ids_tail + + gm_tail = query_db(database, "gm", in_type, lch_tail, vbs_tail, vgs_tail, vds_tail) + gds_tail = query_db( + database, "gds", in_type, lch_tail, vbs_tail, vgs_tail, vds_tail + ) + cgg_tail = query_db( + database, "cgg", in_type, lch_tail, vbs_tail, vgs_tail, vds_tail + ) + cdd_tail = cgg_tail * gen_params["cdd_cgg_ratio"] + gmro_tail = gm_tail / gds_tail + + if in_type == "nch" or in_type == "nch_lvt": + vbias_tail = vgs_tail + else: + vbias_tail = vdd + vgs_tail + + tail_op = dict( + scale_tail=scale_tail, + vbias_tail=vbias_tail, + vgs=vgs_tail, + vds=vds_tail, + gm=gm_tail, + gds=gds_tail, + gmro=gmro_tail, + cdd=cdd_tail, + ) + return tail_op + + +@h.paramclass +class TelescopicAmpParams: + tail_params = h.Param(dtype=MosParams, desc="Tail FET params") + in_base_pair_params = h.Param(dtype=MosParams, desc="Input pair FET params") + in_casc_pair_params = h.Param(dtype=MosParams, desc="Inverter NMos params") + load_base_pair_params = h.Param(dtype=MosParams, desc="Inverter PMos params") + load_casc_pair_params = h.Param(dtype=MosParams, desc="Reset Device params") + in_type = h.Param(dtype=str, desc="Input MOS type") + load_type = h.Param(dtype=str, desc="Load MOS type") + tail_type = h.Param(dtype=str, desc="Tail MOS type") + voutcm_ideal = h.Param(dtype=Prefixed, desc="Ideal Output CM") + v_load = h.Param(dtype=Prefixed, desc="Load MOS Bias") + v_pcasc = h.Param(dtype=Prefixed, desc="PMOS Cascode Device Bias") + v_ncasc = h.Param(dtype=Prefixed, desc="NMOS Cascode Device Bias") + v_tail = h.Param(dtype=Prefixed, desc="Tail MOS Bias") + + +@h.generator +def stage1_telescopic_amplifier(params: TelescopicAmpParams) -> h.Module: + + if params.in_type == "nch": + mos_in = nch + elif params.in_type == "nch_lvt": + mos_in = nch_lvt + elif params.in_type == "pch": + mos_in = pch + elif params.in_type == "pch_lvt": + mos_in = pch_lvt + + if params.load_type == "nch": + mos_load = nch + elif params.load_type == "nch_lvt": + mos_load = nch_lvt + elif params.load_type == "pch": + mos_load = pch + elif params.load_type == "pch_lvt": + mos_load = pch_lvt + + if params.tail_type == "nch": + mos_tail = nch + elif params.tail_type == "nch_lvt": + mos_tail = nch_lvt + elif params.tail_type == "pch": + mos_tail = pch + elif params.tail_type == "pch_lvt": + mos_tail = pch_lvt + + if params.in_type == "nch" or params.in_type == "nch_lvt": + + @h.module + class TelescopicAmp: + + VDD, VSS = h.Ports(2) + v_in = Diff(port=True, role=Diff.Roles.SINK) + v_out = Diff(port=True, role=Diff.Roles.SOURCE) + v_tail = h.Input() + v_load = h.Input() + v_pcasc = h.Input() + v_ncasc = h.Input() + + ## Tail Device + m_tail = mos_tail(params.tail_params)(g=v_tail, s=VSS, b=VSS) + + ## Input Base Pair + m_in_base_p = mos_in(params.in_base_pair_params)( + g=v_in.p, s=m_tail.d, b=VSS + ) + m_in_base_n = mos_in(params.in_base_pair_params)( + g=v_in.n, s=m_tail.d, b=VSS + ) + + ## Input Cascode Pair + m_in_casc_p = mos_in(params.in_casc_pair_params)( + g=v_ncasc, s=m_in_base_p.d, d=v_out.n, b=VSS + ) + m_in_casc_n = mos_in(params.in_casc_pair_params)( + g=v_ncasc, s=m_in_base_n.d, d=v_out.p, b=VSS + ) + + ## Load Base Pair + m_load_base_p = mos_load(params.load_base_pair_params)( + g=v_load, s=VDD, b=VDD + ) + m_load_base_n = mos_load(params.load_base_pair_params)( + g=v_load, s=VDD, b=VDD + ) + + ## Load Cascode Pair + m_load_casc_p = mos_load(params.load_casc_pair_params)( + g=v_pcasc, s=m_load_base_p.d, d=v_out.n, b=VDD + ) + m_load_casc_n = mos_load(params.load_casc_pair_params)( + g=v_pcasc, s=m_load_base_n.d, d=v_out.p, b=VDD + ) + + return TelescopicAmp + + elif params.load_type == "pch" or params.load_type == "pch_lvt": + + @h.module + class TelescopicAmp: + VDD, VSS = h.Ports(2) + v_in = Diff(port=True, role=Diff.Roles.SINK) + v_out = Diff(port=True, role=Diff.Roles.SOURCE) + v_tail = h.Input() + v_load = h.Input() + v_pcasc = h.Input() + v_ncasc = h.Input() + + ## Tail Device + m_tail = mos_tail(params.tail_params)(g=v_tail, s=VDD, b=VDD) + + ## Input Base Pair + m_in_base_p = mos_in(params.in_base_pair_params)( + g=v_in.p, s=m_tail.d, b=VDD + ) + m_in_base_n = mos_in(params.in_base_pair_params)( + g=v_in.n, s=m_tail.d, b=VDD + ) + + ## Input Cascode Pair + m_in_casc_p = mos_in(params.in_casc_pair_params)( + g=v_pcasc, s=m_in_base_p.d, d=v_out.n, b=VDD + ) + m_in_casc_n = mos_in(params.in_casc_pair_params)( + g=v_pcasc, s=m_in_base_n.d, d=v_out.p, b=VDD + ) + + ## Load Base Pair + m_load_base_p = mos_load(params.load_base_pair_params)( + g=v_load, s=VSS, b=VSS + ) + m_load_base_n = mos_load(params.load_base_pair_params)( + g=v_load, s=VSS, b=VSS + ) + + ## Load Cascode Pair + m_load_casc_p = mos_load(params.load_casc_pair_params)( + g=v_ncasc, s=m_load_base_p.d, d=v_out.n, b=VSS + ) + m_load_casc_n = mos_load(params.load_casc_pair_params)( + g=v_ncasc, s=m_load_base_n.d, d=v_out.p, b=VSS + ) + + return TelescopicAmp + + +@h.paramclass +class Stage1TbParams: + dut = h.Param(dtype=TelescopicAmpParams, desc="DUT params") + pvt = h.Param( + dtype=Pvt, desc="Process, Voltage, and Temperature Parameters", default=Pvt() + ) + vd = h.Param(dtype=h.Prefixed, desc="Differential Voltage (V)", default=1 * m) + vc = h.Param(dtype=h.Prefixed, desc="Common-Mode Voltage (V)", default=1200 * m) + cl = h.Param(dtype=h.Prefixed, desc="Load Cap (Single-Ended) (F)", default=100 * f) + rcm = h.Param( + dtype=h.Prefixed, desc="Common Mode Sensing Resistor (Ω)", default=1 * G + ) + CMFB_gain = h.Param( + dtype=h.Prefixed, desc="Common Mode Feedback Gain (V/V)", default=1 * KILO + ) + + +@h.generator +def AmplifierTbTran(params: Stage1TbParams) -> h.Module: + + tb = h.sim.tb("TelescopicAmplifierTb") + tb.VDD = VDD = h.Signal() + tb.vvdd = Vdc(Vdc.Params(dc=params.pvt.v, ac=(0 * m)))(p=VDD, n=tb.VSS) + + tb.v_tail = h.Signal() + tb.v_tail_src = Vdc(Vdc.Params(dc=(params.dut.v_tail), ac=(0 * m)))( + p=tb.v_tail, n=tb.VSS + ) + + tb.v_pcasc = h.Signal() + tb.v_pcasc_src = Vdc(Vdc.Params(dc=(params.dut.v_pcasc), ac=(0 * m)))( + p=tb.v_pcasc, n=tb.VSS + ) + + tb.v_ncasc = h.Signal() + tb.v_ncasc_src = Vdc(Vdc.Params(dc=(params.dut.v_ncasc), ac=(0 * m)))( + p=tb.v_ncasc, n=tb.VSS + ) + + tb.v_load = h.Signal() + tb.voutcm_ideal = h.Signal() + # tb.v_load_src = Vdc(Vdc.Params(dc=(params.dut.v_load)))(p=tb.v_load, n=tb.VSS) + tb.v_outcm_ideal_src = Vdc(Vdc.Params(dc=(params.dut.voutcm_ideal), ac=(0 * m)))( + p=tb.voutcm_ideal, n=tb.VSS + ) + + # Input-driving balun + tb.inp = Diff() + tb.inpgen = DiffClkGen( + DiffClkParams( + period=1000 * n, delay=1 * n, vc=params.vc, vd=params.vd, trf=800 * PICO + ) + )(ck=tb.inp, VSS=tb.VSS) + + # Output & Load Caps + tb.out = Diff() + tb.CMSense = h.Signal() + Cload = C(C.Params(c=params.cl)) + Ccmfb = C(C.Params(c=100 * f)) + Rload = R(R.Params(r=params.rcm)) + tb.clp = Cload(p=tb.out.p, n=tb.VSS) + tb.cln = Cload(p=tb.out.n, n=tb.VSS) + tb.ccmfb = Ccmfb(p=tb.CMSense, n=tb.VSS) + tb.rcmp = Rload(p=tb.out.p, n=tb.CMSense) + tb.rcmn = Rload(p=tb.out.n, n=tb.CMSense) + tb.cmfb_src = Vcvs(Vcvs.Params(gain=params.CMFB_gain))( + p=tb.v_load, n=tb.VSS, cp=tb.CMSense, cn=tb.voutcm_ideal + ) + + # Create the Telescopic Amplifier DUT + tb.dut = stage1_telescopic_amplifier(params.dut)( + v_in=tb.inp, + v_out=tb.out, + v_tail=tb.v_tail, + v_ncasc=tb.v_ncasc, + v_pcasc=tb.v_pcasc, + v_load=tb.v_load, + VDD=VDD, + VSS=tb.VSS, + ) + return tb + + +@h.generator +def AmplifierTbAc(params: Stage1TbParams) -> h.Module: + + tb = h.sim.tb("TelescopicAmplifierTb") + tb.VDD = VDD = h.Signal() + tb.vvdd = Vdc(Vdc.Params(dc=params.pvt.v, ac=(0 * m)))(p=VDD, n=tb.VSS) + + tb.v_tail = h.Signal() + tb.v_tail_src = Vdc(Vdc.Params(dc=(params.dut.v_tail), ac=(0 * m)))( + p=tb.v_tail, n=tb.VSS + ) + + tb.v_pcasc = h.Signal() + tb.v_pcasc_src = Vdc(Vdc.Params(dc=(params.dut.v_pcasc), ac=(0 * m)))( + p=tb.v_pcasc, n=tb.VSS + ) + + tb.v_ncasc = h.Signal() + tb.v_ncasc_src = Vdc(Vdc.Params(dc=(params.dut.v_ncasc), ac=(0 * m)))( + p=tb.v_ncasc, n=tb.VSS + ) + + tb.v_load = h.Signal() + tb.voutcm_ideal = h.Signal() + # tb.v_load_src = Vdc(Vdc.Params(dc=(params.dut.v_load)))(p=tb.v_load, n=tb.VSS) + tb.v_outcm_ideal_src = Vdc(Vdc.Params(dc=(params.dut.voutcm_ideal), ac=(0 * m)))( + p=tb.voutcm_ideal, n=tb.VSS + ) + + # Input-driving balun + tb.inp = Diff() + tb.inpgen = Vdc(Vdc.Params(dc=(params.vc), ac=(500 * m)))(p=tb.inp.p, n=tb.VSS) + tb.inngen = Vdc(Vdc.Params(dc=(params.vc), ac=(-500 * m)))(p=tb.inp.n, n=tb.VSS) + + # Output & Load Caps + tb.out = Diff() + tb.CMSense = h.Signal() + Cload = C(C.Params(c=params.cl)) + Ccmfb = C(C.Params(c=10 * f)) + Rload = R(R.Params(r=params.rcm)) + tb.clp = Cload(p=tb.out.p, n=tb.VSS) + tb.cln = Cload(p=tb.out.n, n=tb.VSS) + tb.ccmfb = Ccmfb(p=tb.CMSense, n=tb.VSS) + tb.rcmp = Rload(p=tb.out.p, n=tb.CMSense) + tb.rcmn = Rload(p=tb.out.n, n=tb.CMSense) + tb.cmfb_src = Vcvs(Vcvs.Params(gain=params.CMFB_gain))( + p=tb.v_load, n=tb.VSS, cp=tb.CMSense, cn=tb.voutcm_ideal + ) + + # Create the Telescopic Amplifier DUT + tb.dut = stage1_telescopic_amplifier(params.dut)( + v_in=tb.inp, + v_out=tb.out, + v_tail=tb.v_tail, + v_ncasc=tb.v_ncasc, + v_pcasc=tb.v_pcasc, + v_load=tb.v_load, + VDD=VDD, + VSS=tb.VSS, + ) + return tb + + +def generate_stage1_telescopic_amplifier(amp_specs): + + nch_db_filename = "database_nch.npy" + nch_lvt_db_filename = "database_nch_lvt.npy" + pch_db_filename = "database_pch.npy" + pch_lvt_db_filename = "database_pch_lvt.npy" + + gen_params = dict( + tail_vstar_vds_margin=20e-3, + vgs_sweep_res=5e-3, + vds_sweep_res=15e-3, + casc_scale_min=0.5, + casc_scale_max=3, + casc_scale_step=0.5, + casc_bias_step=10e-3, + gamma=1, + cdd_cgg_ratio=1.1, + lch_in=0.15, + lch_tail=0.5, + lch_load=[0.15, 0.5, 1], + ) + + if amp_specs["in_type"] == "nch": + in_db_filename = nch_db_filename + elif amp_specs["in_type"] == "nch_lvt": + in_db_filename = nch_lvt_db_filename + elif amp_specs["in_type"] == "pch": + in_db_filename = pch_db_filename + elif amp_specs["in_type"] == "pch_lvt": + in_db_filename = pch_lvt_db_filename + + if amp_specs["load_type"] == "nch": + load_db_filename = nch_db_filename + elif amp_specs["load_type"] == "nch_lvt": + load_db_filename = nch_lvt_db_filename + elif amp_specs["load_type"] == "pch": + load_db_filename = pch_db_filename + elif amp_specs["load_type"] == "pch_lvt": + load_db_filename = pch_lvt_db_filename + + if amp_specs["tail_type"] == "nch": + tail_db_filename = nch_db_filename + elif amp_specs["tail_type"] == "nch_lvt": + tail_db_filename = nch_lvt_db_filename + elif amp_specs["tail_type"] == "pch": + tail_db_filename = pch_db_filename + elif amp_specs["tail_type"] == "pch_lvt": + tail_db_filename = pch_lvt_db_filename + + database_in = np.load(in_db_filename, allow_pickle=True).item() + database_tail = np.load(tail_db_filename, allow_pickle=True).item() + database_load = np.load(load_db_filename, allow_pickle=True).item() + + amp_specs["gm_id_in"] = 2 / amp_specs["vstar_in"] + + input_op = stage1_telescopic_amplifier_design_input( + database_in, amp_specs, gen_params + ) + load_op = stage1_telescopic_amplifier_design_load( + database_load, amp_specs, gen_params, input_op + ) + print("input op:") + pprint.pprint(input_op) + print("load op:") + pprint.pprint(load_op) + amplifier_op = stage1_telescopic_amplifier_design_amp( + amp_specs, gen_params, input_op, load_op + ) + print("amplifier op:") + pprint.pprint(amplifier_op) + tail_op = stage1_telescopic_amplifier_design_tail( + database_tail, amp_specs, gen_params, amplifier_op + ) + print("tail op:") + pprint.pprint(tail_op) + + if amp_specs["tail_type"] == "nch" or amp_specs["tail_type"] == "nch_lvt": + v_load = amplifier_op["load_base_bias"] + v_pcasc = amplifier_op["load_casc_bias"] + v_ncasc = amplifier_op["in_casc_bias"] + v_tail = tail_op["vbias_tail"] + else: + v_load = amplifier_op["load_base_bias"] + v_ncasc = amplifier_op["load_casc_bias"] + v_pcasc = amplifier_op["in_casc_bias"] + v_tail = tail_op["vbias_tail"] + + ampParams = TelescopicAmpParams( + in_base_pair_params=MosParams( + w=500 * m * int(np.round(amplifier_op["scale_in_base"])), + l=h.Prefixed(gen_params["lch_in"]), + nf=int(np.round(amplifier_op["scale_in_base"])), + ), + in_casc_pair_params=MosParams( + w=500 * m * int(np.round(amplifier_op["scale_in_casc"])), + l=h.Prefixed(gen_params["lch_in"]), + nf=int(np.round(amplifier_op["scale_in_casc"])), + ), + load_base_pair_params=MosParams( + w=500 * m * int(np.round(amplifier_op["scale_load_base"])), + l=h.Prefixed(gen_params["lch_load"]), + nf=int(np.round(amplifier_op["scale_load_base"])), + ), + load_casc_pair_params=MosParams( + w=500 * m * int(np.round(amplifier_op["scale_load_casc"])), + l=h.Prefixed(gen_params["lch_load"]), + nf=int(np.round(amplifier_op["scale_load_casc"])), + ), + tail_params=MosParams( + w=500 * m * int(np.round(tail_op["scale_tail"])), + l=h.Prefixed(gen_params["lch_tail"]), + nf=int(np.round(tail_op["scale_tail"])), + ), + in_type=amp_specs["in_type"], + load_type=amp_specs["load_type"], + tail_type=amp_specs["tail_type"], + voutcm_ideal=amp_specs["voutcm"] * 1000 * m, + v_load=v_load * 1000 * m, + v_pcasc=v_pcasc * 1000 * m, + v_ncasc=v_ncasc * 1000 * m, + v_tail=v_tail * 1000 * m, + ) + + params = Stage1TbParams( + pvt=Pvt(), + vc=amp_specs["vincm"] * 1000 * m, + vd=1 * m, + dut=ampParams, + cl=amp_specs["cload"] * 1000 * m, + ) + + # Create our simulation input + @hs.sim + class TelescopicAmplifierTranSim: + tb = AmplifierTbTran(params) + tr = hs.Tran(tstop=3000 * n, tstep=1 * n) + + # Add the PDK dependencies + # TelescopicAmplifierTranSim.lib(sky130.install.model_lib, 'tt') + TelescopicAmplifierTranSim.lib(sky130.install.model_lib, "tt") + TelescopicAmplifierTranSim.literal(".save all") + results = TelescopicAmplifierTranSim.run(sim_options) + tran_results = results.an[0].data + t = tran_results["time"] + v_out_diff = tran_results["v(xtop.out_p)"] - tran_results["v(xtop.out_n)"] + v_out_cm = (tran_results["v(xtop.out_p)"] + tran_results["v(xtop.out_n)"]) / 2 + v_in_diff = tran_results["v(xtop.inp_p)"] - tran_results["v(xtop.inp_n)"] + v_in_cm = (tran_results["v(xtop.inp_p)"] + tran_results["v(xtop.inp_n)"]) / 2 + + fig, ax = plt.subplots(2, sharex=True) + ax[0].plot(t, v_in_diff) + ax[1].plot(t, v_out_diff) + + plt.show() + + fig, ax = plt.subplots(2, sharex=True) + ax[0].plot(t, v_in_cm) + ax[1].plot(t, v_out_cm) + + # Create our simulation input + @hs.sim + class TelescopicAmplifierAcSim: + tb = AmplifierTbAc(params) + myac = hs.Ac(sweep=hs.LogSweep(1e1, 1e11, 10)) + + # Add the PDK dependencies + TelescopicAmplifierAcSim.lib(sky130.install.model_lib, "tt") + TelescopicAmplifierAcSim.literal(".save all") + results = TelescopicAmplifierAcSim.run(sim_options) + ac_results = results.an[0].data + v_out_diff_ac = ac_results["v(xtop.out_p)"] - ac_results["v(xtop.out_n)"] + f = np.logspace(start=1, stop=11, num=101, endpoint=True) + f_ax = np.logspace(start=1, stop=11, num=11, endpoint=True) + f3db_idx = np.squeeze( + np.where(abs(v_out_diff_ac) < np.max(abs(v_out_diff_ac)) / np.sqrt(2)) + )[0] + fgbw_idx = np.squeeze(np.where(abs(v_out_diff_ac) < 1))[0] + f3db = f[f3db_idx] + fgbw = f[fgbw_idx] + Avdc = np.max(abs(v_out_diff_ac)) + v_out_diff_ac_dB = 20 * np.log10(abs(v_out_diff_ac)) + v_out_diff_ac_phase = ( + (np.angle(v_out_diff_ac) % (2 * np.pi) - 2 * np.pi) * 180 / np.pi + ) + PM_ac = 180 + v_out_diff_ac_phase[fgbw_idx] + fig, ax = plt.subplots(2, sharex=True) + ax[0].semilogx(f, v_out_diff_ac_dB) + ax[0].grid(color="black", linestyle="--", linewidth=0.5) + ax[0].grid(visible=True, which="both") + ax[0].set_xticks(f_ax) + ax[1].semilogx(f, v_out_diff_ac_phase) + ax[1].grid(color="black", linestyle="--", linewidth=0.5) + ax[1].grid(visible=True, which="both") + ax[1].set_xticks(f_ax) + plt.show() + print("Av (AC Sim) = %f" % ((Avdc))) + print("BW (AC Sim) = %f MHz" % (round(f3db / 1e6, 2))) + print("GBW (AC Sim) = %f MHz" % (round(fgbw / 1e6, 2))) + print("PM (AC Sim) = %f degrees" % PM_ac) + + ampResults = dict( + Av=Avdc, + bw=f3db, + gbw=fgbw, + pm=PM_ac, + ) + + return ampParams, ampResults diff --git a/adc/telescopic_cascode_diffamp_generator/stage2.py b/adc/telescopic_cascode_diffamp_generator/stage2.py new file mode 100644 index 0000000..6ddbbd4 --- /dev/null +++ b/adc/telescopic_cascode_diffamp_generator/stage2.py @@ -0,0 +1,445 @@ +import pprint + +import numpy as np +import matplotlib.pyplot as plt + +import hdl21 as h +import hdl21.sim as hs +from hdl21 import Diff +from hdl21.prefix import m, f, n, PICO, G, KILO, Prefixed +from hdl21.primitives import Vdc, C, R, Vcvs + +# Import the Hdl21 PDK package, and our "site" configuration of its installation +import sky130, sitepdks as _ + +# Local imports +from .dbstuff import query_db, query_db_for_vgs +from .shared import MosParams, nch, nch_lvt, pch, pch_lvt +from ..testutils import ( + Pvt, + sim_options, + DiffClkParams, + DiffClkGen, +) + + +@h.paramclass +class Stage2AmpParams: + in_params = h.Param(dtype=MosParams, desc="Input pair MOS params") + load_params = h.Param(dtype=MosParams, desc="Load pair MOS params") + in_type = h.Param(dtype=str, desc="Input MOS type") + load_type = h.Param(dtype=str, desc="Load MOS type") + voutcm_ideal = h.Param(dtype=Prefixed, desc="Ideal Output CM") + v_load = h.Param(dtype=Prefixed, desc="Load MOS Bias") + + +@h.generator +def stage2_common_source_amplifier(params: Stage2AmpParams) -> h.Module: + + if params.in_type == "nch": + mos_in = nch + elif params.in_type == "nch_lvt": + mos_in = nch_lvt + elif params.in_type == "pch": + mos_in = pch + elif params.in_type == "pch_lvt": + mos_in = pch_lvt + + if params.load_type == "nch": + mos_load = nch + elif params.load_type == "nch_lvt": + mos_load = nch_lvt + elif params.load_type == "pch": + mos_load = pch + elif params.load_type == "pch_lvt": + mos_load = pch_lvt + + @h.module + class Stage2Amplifier: + + VDD, VSS = h.Ports(2) + v_in = Diff(port=True, role=Diff.Roles.SINK) + v_out = Diff(port=True, role=Diff.Roles.SOURCE) + v_load = h.Input() + + ## Stage 2 Input Devices + m_in_p = mos_in(params.in_params)(g=v_in.n, s=VSS, d=v_out.p, b=VSS) + m_in_n = mos_in(params.in_params)(g=v_in.p, s=VSS, d=v_out.n, b=VSS) + + ## Load Base Pair + m_load_p = mos_load(params.load_params)(g=v_load, s=VDD, d=v_out.p, b=VDD) + m_load_n = mos_load(params.load_params)(g=v_load, s=VDD, d=v_out.n, b=VDD) + + return Stage2Amplifier + + +@h.paramclass +class Stage2AmpTbParams: + dut = h.Param(dtype=Stage2AmpParams, desc="DUT params") + pvt = h.Param( + dtype=Pvt, desc="Process, Voltage, and Temperature Parameters", default=Pvt() + ) + vd = h.Param(dtype=h.Prefixed, desc="Differential Voltage (V)", default=1 * m) + vc = h.Param(dtype=h.Prefixed, desc="Common-Mode Voltage (V)", default=1200 * m) + cl = h.Param(dtype=h.Prefixed, desc="Load Cap (Single-Ended) (F)", default=100 * f) + rcm = h.Param( + dtype=h.Prefixed, desc="Common Mode Sensing Resistor (Ω)", default=1 * G + ) + CMFB_gain = h.Param( + dtype=h.Prefixed, desc="Common Mode Feedback Gain (V/V)", default=1 * KILO + ) + + +@h.generator +def Stage2AmpTbTran(params: Stage2AmpTbParams) -> h.Module: + + tb = h.sim.tb("Stage2AmplifierTbTran") + tb.VDD = VDD = h.Signal() + tb.vvdd = Vdc(Vdc.Params(dc=params.pvt.v, ac=(0 * m)))(p=VDD, n=tb.VSS) + + tb.v_load = h.Signal() + + tb.voutcm_ideal = h.Signal() + tb.v_outcm_ideal_src = Vdc(Vdc.Params(dc=(params.dut.voutcm_ideal), ac=(0 * m)))( + p=tb.voutcm_ideal, n=tb.VSS + ) + + # Input-driving balun + tb.inp = Diff() + tb.inpgen = DiffClkGen( + DiffClkParams( + period=1000 * n, delay=1 * n, vc=params.vc, vd=params.vd, trf=800 * PICO + ) + )(ck=tb.inp, VSS=tb.VSS) + + # Output & Load Caps + tb.out = Diff() + tb.CMSense = h.Signal() + Cload = C(C.Params(c=params.cl)) + Ccmfb = C(C.Params(c=100 * f)) + Rload = R(R.Params(r=params.rcm)) + tb.clp = Cload(p=tb.out.p, n=tb.VSS) + tb.cln = Cload(p=tb.out.n, n=tb.VSS) + tb.ccmfb = Ccmfb(p=tb.CMSense, n=tb.VSS) + tb.rcmp = Rload(p=tb.out.p, n=tb.CMSense) + tb.rcmn = Rload(p=tb.out.n, n=tb.CMSense) + tb.cmfb_src = Vcvs(Vcvs.Params(gain=params.CMFB_gain))( + p=tb.v_load, n=tb.VSS, cp=tb.CMSense, cn=tb.voutcm_ideal + ) + + # Create the Telescopic Amplifier DUT + tb.dut = stage2_common_source_amplifier(params.dut)( + v_in=tb.inp, v_out=tb.out, v_load=tb.v_load, VDD=VDD, VSS=tb.VSS + ) + return tb + + +def stage2_common_source_amplifier_design_and_scale_amp( + database_in, database_load, amp_specs, gen_params +): + + vdd = amp_specs["vdd"] + vincm = amp_specs["vincm"] + voutcm = amp_specs["voutcm"] + vgs_res = gen_params["vgs_sweep_res"] + gain_min = amp_specs["gain_min"] + stage1_gain = amp_specs["stage1_gain"] + bw_min = amp_specs["amp_bw_min"] + in_type = amp_specs["in_type"] + load_type = amp_specs["load_type"] + lch_in = gen_params["lch_in"] + lch_load = gen_params["lch_load"] + load_scale_min = gen_params["load_scale_min"] + load_scale_max = gen_params["load_scale_max"] + load_scale_step = gen_params["load_scale_step"] + rload = amp_specs["rload"] + + if in_type == "nch" or in_type == "nch_lvt": + vs_in = 0 + vs_load = vdd + else: + vs_in = vdd + vs_load = 0 + + vbs_in = 0 + vbs_load = 0 + vgs_in = vincm - vs_in + vds_in = voutcm - vs_in + vds_load = voutcm - vs_load + + ids_in = query_db(database_in, "ids", in_type, lch_in, vbs_in, vgs_in, vds_in) + gm_in = query_db(database_in, "gm", in_type, lch_in, vbs_in, vgs_in, vds_in) + gds_in = query_db(database_in, "gds", in_type, lch_in, vbs_in, vgs_in, vds_in) + cgg_in = query_db(database_in, "cgg", in_type, lch_in, vbs_in, vgs_in, vds_in) + cdd_in = cgg_in * gen_params["cdd_cgg_ratio"] + + load_scale_count = int((load_scale_max - load_scale_min) / load_scale_step) + 1 + load_scale_list = np.linspace( + load_scale_min, load_scale_max, load_scale_count, endpoint=True + ) + + metric_best = 0 + gain_max = 0 + bw_max = 0 + + for lch_load in gen_params["lch_load"]: + + for load_scale in load_scale_list: + + ids_load = ids_in / load_scale + try: + vgs_load = query_db_for_vgs( + database_load, + load_type, + lch_load, + vbs_load, + vds_load, + ids=ids_load, + mode="ids", + scale=1, + ) + except: + continue + + gm_load = ( + query_db( + database_load, + "gm", + load_type, + lch_load, + vbs_load, + vgs_load, + vds_load, + ) + * load_scale + ) + gds_load = ( + query_db( + database_load, + "gds", + load_type, + lch_load, + vbs_load, + vgs_load, + vds_load, + ) + * load_scale + ) + cgg_load = ( + query_db( + database_load, + "cgg", + load_type, + lch_load, + vbs_load, + vgs_load, + vds_load, + ) + * load_scale + ) + cdd_load = cgg_load * gen_params["cdd_cgg_ratio"] + + gds = gds_load + gds_in + (1 / rload) + cdd = cdd_in + cdd_load + + gain_cur = gm_in / gds + bw_cur = gds / cdd / 2 / np.pi + metric_cur = gain_cur * bw_cur / ids_in + + if load_type == "pch" or load_type == "pch_lvt": + load_bias = vdd + vgs_load + else: + load_bias = vgs_load + + if gain_cur > gain_min and bw_cur > bw_min: + if metric_cur > metric_best: + metric_best = metric_cur + best_op = dict( + Av=gain_cur, + bw=bw_cur, + load_scale=load_scale, + lch_load=lch_load, + gm_in=gm_in, + gm_load=gm_load, + gds=gds, + cdd=cdd, + metric=metric_best, + load_bias=load_bias, + ) + print("New GBW/I Best = %f MHz/µA" % (round(metric_best / 1e12, 2))) + print("Updated Av Best = %f" % (gain_cur)) + print("Updated BW Best = %f MHz" % (round(bw_cur / 1e6, 2))) + if gain_cur > gain_max: + gain_max = gain_cur + if bw_cur > bw_max: + bw_max = bw_cur + print("2nd Stage Av Best = %f" % ((gain_max))) + print("2nd Stage BW Best = %f MHz" % (round(bw_max / 1e6, 2))) + + vnoise_input_referred_max = amp_specs["vnoise_input_referred"] + cload = amp_specs["cload"] + vdd = amp_specs["vdd"] + in_type = amp_specs["in_type"] + k = 1.38e-23 + T = 300 + + ibias = ids_in + load_bias = best_op["load_bias"] + gm_in = best_op["gm_in"] + gamma_in = gen_params["gamma"] + gamma_load = gen_params["gamma"] + load_scale = best_op["load_scale"] + Av = best_op["Av"] + gds = best_op["gds"] + cdd = best_op["cdd"] + vnoise_squared_input_referred_max = vnoise_input_referred_max**2 + vnoise_squared_input_referred_per_scale = ( + 4 * k * T * (gamma_in * gm_in + gamma_load * gm_load) / (gm_in**2) + ) + scale_noise = max( + 1, vnoise_squared_input_referred_per_scale / vnoise_squared_input_referred_max + ) + gbw_min = bw_min * Av * stage1_gain + scale_bw = max( + 1, 2 * np.pi * gbw_min * cload / (gm_in - 2 * np.pi * gbw_min * (cdd + cgg_in)) + ) + print("2nd stage scale_noise:") + pprint.pprint(scale_noise) + print("2nd stage scale_bw:") + pprint.pprint(scale_bw) + + scale_amp = max(scale_bw, scale_noise) + + vnoise_density_squared_input_referred = ( + vnoise_squared_input_referred_per_scale / scale_amp + ) + vnoise_density_input_referred = np.sqrt(vnoise_density_squared_input_referred) + + amplifier_op = dict( + gm=gm_in * scale_amp, + gds=gds * scale_amp, + cgg=cgg_in * scale_amp, + cdd=cdd * scale_amp, + vnoise_density_input=vnoise_density_input_referred, + scale_in=scale_amp, + scale_load=scale_amp * load_scale, + load_bias=load_bias, + lch_load=best_op["lch_load"], + gain=Av, + ibias=ibias * scale_amp, + bw=(gds * scale_amp) / (2 * np.pi * (cload + (cdd * scale_amp))), + ) + + stage2AmpParams = Stage2AmpParams( + in_params=MosParams( + w=500 * m * int(np.round(amplifier_op["scale_in"])), + l=gen_params["lch_in"], + nf=int(np.round(amplifier_op["scale_in"])), + ), + load_params=MosParams( + w=500 * m * int(np.round(amplifier_op["scale_load"])), + l=amplifier_op["lch_load"], + nf=int(np.round(amplifier_op["scale_load"])), + ), + in_type=amp_specs["in_type"], + load_type=amp_specs["load_type"], + voutcm_ideal=voutcm * 1000 * m, + v_load=amplifier_op["load_bias"] * 1000 * m, + ) + + params = Stage2AmpTbParams( + pvt=Pvt(), + vc=vincm * 1000 * m, + vd=1 * m, + dut=stage2AmpParams, + cl=amp_specs["cload"] * 1000 * m, + ) + + # Create our simulation input + @hs.sim + class Stage2AmpTranSim: + tb = Stage2AmpTbTran(params) + tr = hs.Tran(tstop=3000 * n, tstep=1 * n) + + # Add the PDK dependencies + Stage2AmpTranSim.lib(sky130.install.model_lib, "tt") + Stage2AmpTranSim.literal(".save all") + results = Stage2AmpTranSim.run(sim_options) + tran_results = results.an[0].data + t = tran_results["time"] + v_out_diff = tran_results["v(xtop.out_p)"] - tran_results["v(xtop.out_n)"] + v_out_cm = (tran_results["v(xtop.out_p)"] + tran_results["v(xtop.out_n)"]) / 2 + v_in_diff = tran_results["v(xtop.inp_p)"] - tran_results["v(xtop.inp_n)"] + v_in_cm = (tran_results["v(xtop.inp_p)"] + tran_results["v(xtop.inp_n)"]) / 2 + v_load = tran_results["v(xtop.v_load)"] + + # v_out_stage1_diff = tran_results['v(xtop.out_stage1_p)'] - tran_results['v(xtop.out_stage1_n)'] + + fig, ax = plt.subplots(2, sharex=True) + ax[0].plot(t, v_in_diff) + ax[1].plot(t, v_out_diff) + + plt.show() + + fig, ax = plt.subplots(2, sharex=True) + ax[0].plot(t, v_in_cm) + ax[1].plot(t, v_out_cm) + + plt.show() + + fig, ax = plt.subplots(2, sharex=True) + ax[0].plot(t, v_load) + ax[1].plot(t, v_out_cm) + + plt.show() + + return stage2AmpParams, amplifier_op + + +def generate_stage2_common_source_amplifier(amp_specs): + nch_db_filename = "database_nch.npy" + nch_lvt_db_filename = "database_nch_lvt.npy" + pch_db_filename = "database_pch.npy" + pch_lvt_db_filename = "database_pch_lvt.npy" + + gen_params = dict( + tail_vstar_vds_margin=50e-3, + vgs_sweep_res=5e-3, + vds_sweep_res=15e-3, + load_scale_min=0.25, + load_scale_max=2, + load_scale_step=0.25, + gamma=1, + cdd_cgg_ratio=1.1, + lch_in=0.5, + lch_load=[0.35, 0.5, 1], + ) + + if amp_specs["in_type"] == "nch": + in_db_filename = nch_db_filename + elif amp_specs["in_type"] == "nch_lvt": + in_db_filename = nch_lvt_db_filename + elif amp_specs["in_type"] == "pch": + in_db_filename = pch_db_filename + elif amp_specs["in_type"] == "pch_lvt": + in_db_filename = pch_lvt_db_filename + + if amp_specs["load_type"] == "nch": + load_db_filename = nch_db_filename + elif amp_specs["load_type"] == "nch_lvt": + load_db_filename = nch_lvt_db_filename + elif amp_specs["load_type"] == "pch": + load_db_filename = pch_db_filename + elif amp_specs["load_type"] == "pch_lvt": + load_db_filename = pch_lvt_db_filename + + database_in = np.load(in_db_filename, allow_pickle=True).item() + database_load = np.load(load_db_filename, allow_pickle=True).item() + ampParams, amplifier_op = stage2_common_source_amplifier_design_and_scale_amp( + database_in, database_load, amp_specs, gen_params + ) + + print("2nd stage op:") + pprint.pprint(amplifier_op) + + return ampParams, amplifier_op diff --git a/adc/telescopic_cascode_diffamp_generator/twostage.py b/adc/telescopic_cascode_diffamp_generator/twostage.py new file mode 100644 index 0000000..156713b --- /dev/null +++ b/adc/telescopic_cascode_diffamp_generator/twostage.py @@ -0,0 +1,535 @@ +import numpy as np +import matplotlib.pyplot as plt + +import hdl21 as h +import hdl21.sim as hs +from hdl21 import Diff +from hdl21.prefix import m, f, n, PICO, G, KILO, Prefixed, FEMTO +from hdl21.primitives import Vdc, C, R, Vcvs + +# PDK Imports +import sky130, sitepdks as _ + +# Local imports +from .shared import MosParams, nch, nch_lvt, pch, pch_lvt +from ..testutils import ( + Pvt, + sim_options, + DiffClkParams, + DiffClkGen, +) +from .stage1 import ( + TelescopicAmpParams, + generate_stage1_telescopic_amplifier, + stage1_telescopic_amplifier, +) +from .stage2 import generate_stage2_common_source_amplifier + + +@h.paramclass +class TwoStageAmpParams: + stage1_tail_params = h.Param(dtype=MosParams, desc="Stage 1 Tail MOS params") + stage1_in_base_pair_params = h.Param( + dtype=MosParams, desc="Stage 1 Input pair MOS params" + ) + stage1_in_casc_pair_params = h.Param( + dtype=MosParams, desc="Stage 1 Input cascode MOS params" + ) + stage1_load_base_pair_params = h.Param( + dtype=MosParams, desc="Stage 1 Load base MOS params" + ) + stage1_load_casc_pair_params = h.Param( + dtype=MosParams, desc="Stage 1 Load cascode MOS params" + ) + stage1_in_type = h.Param(dtype=str, desc="Stage 1 Input MOS type") + stage1_load_type = h.Param(dtype=str, desc="Stage 1 Load MOS type") + stage1_tail_type = h.Param(dtype=str, desc="Stage 1 Tail MOS type") + stage1_voutcm_ideal = h.Param(dtype=Prefixed, desc="Stage 1 Ideal Output CM") + stage1_v_load = h.Param(dtype=Prefixed, desc="Stage 1 Load MOS Bias") + stage1_v_pcasc = h.Param(dtype=Prefixed, desc="Stage 1 PMOS Cascode Device Bias") + stage1_v_ncasc = h.Param(dtype=Prefixed, desc="Stage 1 NMOS Cascode Device Bias") + stage1_v_tail = h.Param(dtype=Prefixed, desc="Stage 1 Tail MOS Bias") + stage2_in_type = h.Param(dtype=str, desc="Stage 2 Input MOS type") + stage2_load_type = h.Param(dtype=str, desc="Stage 2 Load MOS type") + stage2_in_params = h.Param(dtype=MosParams, desc="Stage 2 Input MOS params") + stage2_load_params = h.Param(dtype=MosParams, desc="Stage 2 Load MOS params") + stage2_v_load = h.Param(dtype=Prefixed, desc="Stage 2 Load MOS Bias") + stage2_voutcm_ideal = h.Param(dtype=Prefixed, desc="Stage 2 Ideal Output CM") + c_comp = h.Param(dtype=Prefixed, desc="Compensation C Value") + r_comp = h.Param(dtype=Prefixed, desc="Compensation R Value") + + +@h.generator +def two_stage_amplifier(params: TwoStageAmpParams) -> h.Module: + + if params.stage2_in_type == "nch": + stage2_mos_in = nch + elif params.stage2_in_type == "nch_lvt": + stage2_mos_in = nch_lvt + elif params.stage2_in_type == "pch": + stage2_mos_in = pch + elif params.stage2_in_type == "pch_lvt": + stage2_mos_in = pch_lvt + + if params.stage2_load_type == "nch": + stage2_mos_load = nch + elif params.stage2_load_type == "nch_lvt": + stage2_mos_load = nch_lvt + elif params.stage2_load_type == "pch": + stage2_mos_load = pch + elif params.stage2_load_type == "pch_lvt": + stage2_mos_load = pch_lvt + + stage1Params = TelescopicAmpParams( + in_base_pair_params=params.stage1_in_base_pair_params, + in_casc_pair_params=params.stage1_in_casc_pair_params, + load_base_pair_params=params.stage1_load_base_pair_params, + load_casc_pair_params=params.stage1_load_casc_pair_params, + tail_params=params.stage1_tail_params, + in_type=params.stage1_in_type, + load_type=params.stage1_load_type, + tail_type=params.stage1_tail_type, + voutcm_ideal=params.stage1_voutcm_ideal, + v_load=params.stage1_v_load, + v_pcasc=params.stage1_v_pcasc, + v_ncasc=params.stage1_v_ncasc, + v_tail=params.stage1_v_tail, + ) + + @h.module + class TwoStageAmplifier: + + VDD, VSS = h.Ports(2) + v_in = Diff(port=True, role=Diff.Roles.SINK) + v_out = Diff(port=True, role=Diff.Roles.SOURCE) + v_out_stage1 = Diff(port=True, role=Diff.Roles.SOURCE) + v_comp_mid_p = h.Signal() + v_comp_mid_n = h.Signal() + v_tail_stage1 = h.Input() + v_load_stage1 = h.Input() + v_pcasc_stage1 = h.Input() + v_ncasc_stage1 = h.Input() + v_load_stage2 = h.Input() + + x_stage1 = stage1_telescopic_amplifier(stage1Params)( + v_in=v_in, + v_out=v_out_stage1, + v_tail=v_tail_stage1, + v_ncasc=v_ncasc_stage1, + v_pcasc=v_pcasc_stage1, + v_load=v_load_stage1, + VDD=VDD, + VSS=VSS, + ) + + C_comp_p = C(C.Params(c=params.c_comp))(p=v_out.p, n=v_comp_mid_p) + C_comp_n = C(C.Params(c=params.c_comp))(p=v_out.n, n=v_comp_mid_n) + R_comp_p = R(R.Params(r=params.r_comp))(p=v_comp_mid_p, n=v_out_stage1.n) + R_comp_n = R(R.Params(r=params.r_comp))(p=v_comp_mid_n, n=v_out_stage1.p) + + ## Stage 2 Input Devices + m_in_stage2_p = stage2_mos_in(params.stage2_in_params)( + g=v_out_stage1.n, s=VSS, d=v_out.p, b=VSS + ) + m_in_stage2_n = stage2_mos_in(params.stage2_in_params)( + g=v_out_stage1.p, s=VSS, d=v_out.n, b=VSS + ) + + ## Load Base Pair + m_load_stage2_p = stage2_mos_load(params.stage2_load_params)( + g=v_load_stage2, s=VDD, d=v_out.p, b=VDD + ) + m_load_stage2_n = stage2_mos_load(params.stage2_load_params)( + g=v_load_stage2, s=VDD, d=v_out.n, b=VDD + ) + + return TwoStageAmplifier + + +@h.paramclass +class TwoStageTbParams: + dut = h.Param(dtype=TwoStageAmpParams, desc="DUT params") + pvt = h.Param( + dtype=Pvt, desc="Process, Voltage, and Temperature Parameters", default=Pvt() + ) + vd = h.Param(dtype=h.Prefixed, desc="Differential Voltage (V)", default=1 * m) + vc = h.Param(dtype=h.Prefixed, desc="Common-Mode Voltage (V)", default=1200 * m) + cl = h.Param(dtype=h.Prefixed, desc="Load Cap (Single-Ended) (F)", default=100 * f) + ccm = h.Param( + dtype=h.Prefixed, desc="Common Mode Sensing Capacitor (F)", default=1 * f + ) + rcm = h.Param( + dtype=h.Prefixed, desc="Common Mode Sensing Resistor (Ω)", default=1 * G + ) + CMFB_gain_stage1 = h.Param( + dtype=h.Prefixed, desc="Common Mode Feedback Gain (V/V)", default=10 * KILO + ) + CMFB_gain_stage2 = h.Param( + dtype=h.Prefixed, desc="Common Mode Feedback Gain (V/V)", default=1 * KILO + ) + + +@h.generator +def TwoStageAmpTbTran(params: TwoStageTbParams) -> h.Module: + + tb = h.sim.tb("TwoStageAmplifierTbTran") + tb.VDD = VDD = h.Signal() + tb.vvdd = Vdc(Vdc.Params(dc=params.pvt.v, ac=(0 * m)))(p=VDD, n=tb.VSS) + + tb.v_tail_stage1 = h.Signal() + tb.v_tail_stage1_src = Vdc(Vdc.Params(dc=(params.dut.stage1_v_tail), ac=(0 * m)))( + p=tb.v_tail_stage1, n=tb.VSS + ) + + tb.v_pcasc_stage1 = h.Signal() + tb.v_pcasc_stage1_src = Vdc(Vdc.Params(dc=(params.dut.stage1_v_pcasc), ac=(0 * m)))( + p=tb.v_pcasc_stage1, n=tb.VSS + ) + + tb.v_ncasc_stage1 = h.Signal() + tb.v_ncasc_stage1_src = Vdc(Vdc.Params(dc=(params.dut.stage1_v_ncasc), ac=(0 * m)))( + p=tb.v_ncasc_stage1, n=tb.VSS + ) + + tb.v_load_stage1 = h.Signal() + + tb.v_load_stage2 = h.Signal() + + tb.voutcm_stage1_ideal = h.Signal() + tb.v_outcm_stage1_ideal_src = Vdc( + Vdc.Params(dc=(params.dut.stage1_voutcm_ideal), ac=(0 * m)) + )(p=tb.voutcm_stage1_ideal, n=tb.VSS) + + tb.voutcm_stage2_ideal = h.Signal() + tb.v_outcm_stage2_ideal_src = Vdc( + Vdc.Params(dc=(params.dut.stage2_voutcm_ideal), ac=(0 * m)) + )(p=tb.voutcm_stage2_ideal, n=tb.VSS) + + # Input-driving balun + tb.inp = Diff() + tb.inpgen = DiffClkGen( + DiffClkParams( + period=1000 * n, delay=1 * n, vc=params.vc, vd=params.vd, trf=800 * PICO + ) + )(ck=tb.inp, VSS=tb.VSS) + + # Output & Load Caps + tb.out = Diff() + tb.out_stage1 = Diff() + tb.CMSense_stage1 = h.Signal() + tb.CMSense_stage2 = h.Signal() + Cload = C(C.Params(c=params.cl)) + Ccmfb = C(C.Params(c=params.ccm)) + Rload = R(R.Params(r=params.rcm)) + tb.clp = Cload(p=tb.out.p, n=tb.VSS) + tb.cln = Cload(p=tb.out.n, n=tb.VSS) + tb.ccmfb_stage1 = Ccmfb(p=tb.CMSense_stage1, n=tb.VSS) + tb.ccmfb_stage2 = Ccmfb(p=tb.CMSense_stage2, n=tb.VSS) + tb.rcmp_stage1 = Rload(p=tb.out_stage1.p, n=tb.CMSense_stage1) + tb.rcmn_stage1 = Rload(p=tb.out_stage1.n, n=tb.CMSense_stage1) + tb.rcmp_stage2 = Rload(p=tb.out.p, n=tb.CMSense_stage2) + tb.rcmn_stage2 = Rload(p=tb.out.n, n=tb.CMSense_stage2) + tb.cmfb_stage1_src = Vcvs(Vcvs.Params(gain=params.CMFB_gain_stage1))( + p=tb.v_load_stage1, n=tb.VSS, cp=tb.CMSense_stage1, cn=tb.voutcm_stage1_ideal + ) + tb.cmfb_stage2_src = Vcvs(Vcvs.Params(gain=params.CMFB_gain_stage2))( + p=tb.v_load_stage2, n=tb.VSS, cp=tb.CMSense_stage2, cn=tb.voutcm_stage2_ideal + ) + + # Create the Telescopic Amplifier DUT + tb.dut = two_stage_amplifier(params.dut)( + v_in=tb.inp, + v_out=tb.out, + v_out_stage1=tb.out_stage1, + v_tail_stage1=tb.v_tail_stage1, + v_ncasc_stage1=tb.v_ncasc_stage1, + v_pcasc_stage1=tb.v_pcasc_stage1, + v_load_stage1=tb.v_load_stage1, + v_load_stage2=tb.v_load_stage2, + VDD=VDD, + VSS=tb.VSS, + ) + return tb + + +@h.generator +def TwoStageAmpTbAc(params: TwoStageTbParams) -> h.Module: + + tb = h.sim.tb("TwoStageAmplifierTbTran") + tb.VDD = VDD = h.Signal() + tb.vvdd = Vdc(Vdc.Params(dc=params.pvt.v, ac=(0 * m)))(p=VDD, n=tb.VSS) + + tb.v_tail_stage1 = h.Signal() + tb.v_tail_stage1_src = Vdc(Vdc.Params(dc=(params.dut.stage1_v_tail), ac=(0 * m)))( + p=tb.v_tail_stage1, n=tb.VSS + ) + + tb.v_pcasc_stage1 = h.Signal() + tb.v_pcasc_stage1_src = Vdc(Vdc.Params(dc=(params.dut.stage1_v_pcasc), ac=(0 * m)))( + p=tb.v_pcasc_stage1, n=tb.VSS + ) + + tb.v_ncasc_stage1 = h.Signal() + tb.v_ncasc_stage1_src = Vdc(Vdc.Params(dc=(params.dut.stage1_v_ncasc), ac=(0 * m)))( + p=tb.v_ncasc_stage1, n=tb.VSS + ) + + tb.v_load_stage1 = h.Signal() + + tb.v_load_stage2 = h.Signal() + + tb.voutcm_stage1_ideal = h.Signal() + tb.v_outcm_stage1_ideal_src = Vdc( + Vdc.Params(dc=(params.dut.stage1_voutcm_ideal), ac=(0 * m)) + )(p=tb.voutcm_stage1_ideal, n=tb.VSS) + + tb.voutcm_stage2_ideal = h.Signal() + tb.v_outcm_stage2_ideal_src = Vdc( + Vdc.Params(dc=(params.dut.stage2_voutcm_ideal), ac=(0 * m)) + )(p=tb.voutcm_stage2_ideal, n=tb.VSS) + + # Input-driving balun + tb.inp = Diff() + tb.inpgen = Vdc(Vdc.Params(dc=(params.vc), ac=(500 * m)))(p=tb.inp.p, n=tb.VSS) + tb.inngen = Vdc(Vdc.Params(dc=(params.vc), ac=(-500 * m)))(p=tb.inp.n, n=tb.VSS) + + # Output & Load Caps + tb.out = Diff() + tb.out_stage1 = Diff() + tb.CMSense_stage1 = h.Signal() + tb.CMSense_stage2 = h.Signal() + Cload = C(C.Params(c=params.cl)) + Ccmfb = C(C.Params(c=params.ccm)) + Rload = R(R.Params(r=params.rcm)) + tb.clp = Cload(p=tb.out.p, n=tb.VSS) + tb.cln = Cload(p=tb.out.n, n=tb.VSS) + tb.ccmfb_stage1 = Ccmfb(p=tb.CMSense_stage1, n=tb.VSS) + tb.ccmfb_stage2 = Ccmfb(p=tb.CMSense_stage2, n=tb.VSS) + tb.rcmp_stage1 = Rload(p=tb.out_stage1.p, n=tb.CMSense_stage1) + tb.rcmn_stage1 = Rload(p=tb.out_stage1.n, n=tb.CMSense_stage1) + tb.rcmp_stage2 = Rload(p=tb.out.p, n=tb.CMSense_stage2) + tb.rcmn_stage2 = Rload(p=tb.out.n, n=tb.CMSense_stage2) + tb.cmfb_stage1_src = Vcvs(Vcvs.Params(gain=params.CMFB_gain_stage1))( + p=tb.v_load_stage1, n=tb.VSS, cp=tb.CMSense_stage1, cn=tb.voutcm_stage1_ideal + ) + tb.cmfb_stage2_src = Vcvs(Vcvs.Params(gain=params.CMFB_gain_stage2))( + p=tb.v_load_stage2, n=tb.VSS, cp=tb.CMSense_stage2, cn=tb.voutcm_stage2_ideal + ) + + # Create the Telescopic Amplifier DUT + tb.dut = two_stage_amplifier(params.dut)( + v_in=tb.inp, + v_out=tb.out, + v_out_stage1=tb.out_stage1, + v_tail_stage1=tb.v_tail_stage1, + v_ncasc_stage1=tb.v_ncasc_stage1, + v_pcasc_stage1=tb.v_pcasc_stage1, + v_load_stage1=tb.v_load_stage1, + v_load_stage2=tb.v_load_stage2, + VDD=VDD, + VSS=tb.VSS, + ) + return tb + + +def generate_two_stage_ota(): + two_stage_amp_specs = dict( + stage1_in_type="nch_lvt", + stage1_tail_type="nch_lvt", + stage1_load_type="pch", + stage1_vstar_in=200e-3, + stage1_vincm=1, + stage1_voutcm=0.9, + stage1_vds_tail_min=0.25, + stage1_gain_min=100, + stage1_input_stage_gain_min=200, + stage1_bw_min=20e6, + stage2_in_type="nch", + stage2_load_type="pch", + stage2_voutcm=1, + bw_min=5e5, + gain_min=500, + vnoise_input_referred=10e-9, + cload=1000e-15, + rload=100e3, + vdd=1.8, + ) + + stage1_telescopic_amp_specs = dict( + in_type=two_stage_amp_specs["stage1_in_type"], + tail_type=two_stage_amp_specs["stage1_tail_type"], + load_type=two_stage_amp_specs["stage1_load_type"], + cload=100e-15, + vdd=two_stage_amp_specs["vdd"], + vstar_in=two_stage_amp_specs["stage1_vstar_in"], + vincm=two_stage_amp_specs["stage1_vincm"], + voutcm=two_stage_amp_specs["stage1_voutcm"], + vds_tail_min=two_stage_amp_specs["stage1_vds_tail_min"], + gain_min=two_stage_amp_specs["stage1_gain_min"], + input_stage_gain_min=two_stage_amp_specs["stage1_input_stage_gain_min"], + selfbw_min=two_stage_amp_specs["stage1_bw_min"], + bw_min=two_stage_amp_specs["stage1_bw_min"], + vnoise_input_referred=two_stage_amp_specs["vnoise_input_referred"] + * ( + two_stage_amp_specs["gain_min"] + / ( + two_stage_amp_specs["stage1_input_stage_gain_min"] + + two_stage_amp_specs["gain_min"] + ) + ), + ) + + ( + telescopic_amp_params, + telescopic_amp_results, + ) = generate_stage1_telescopic_amplifier(stage1_telescopic_amp_specs) + + stage2_common_source_amp_specs = dict( + in_type=two_stage_amp_specs["stage2_in_type"], + load_type=two_stage_amp_specs["stage2_load_type"], + cload=two_stage_amp_specs["cload"], + rload=100e3, + vdd=two_stage_amp_specs["vdd"], + vincm=two_stage_amp_specs["stage1_voutcm"], + voutcm=two_stage_amp_specs["stage2_voutcm"], + stage1_gain=telescopic_amp_results["Av"], + gain_min=two_stage_amp_specs["gain_min"] / telescopic_amp_results["Av"], + amp_bw_min=two_stage_amp_specs["bw_min"], + vnoise_input_referred=two_stage_amp_specs["vnoise_input_referred"] + * ( + telescopic_amp_results["Av"] + / (telescopic_amp_results["Av"] + two_stage_amp_specs["gain_min"]) + ) + * telescopic_amp_results["Av"], + ) + + stage2_params, stage2_op = generate_stage2_common_source_amplifier( + stage2_common_source_amp_specs + ) + + twoStageAmpParams = TwoStageAmpParams( + stage1_in_base_pair_params=telescopic_amp_params.in_base_pair_params, + stage1_in_casc_pair_params=telescopic_amp_params.in_casc_pair_params, + stage1_load_base_pair_params=telescopic_amp_params.load_base_pair_params, + stage1_load_casc_pair_params=telescopic_amp_params.load_casc_pair_params, + stage1_tail_params=telescopic_amp_params.tail_params, + stage1_in_type=telescopic_amp_params.in_type, + stage1_load_type=telescopic_amp_params.load_type, + stage1_tail_type=telescopic_amp_params.tail_type, + stage1_voutcm_ideal=telescopic_amp_params.voutcm_ideal, + stage1_v_load=telescopic_amp_params.v_load, + stage1_v_pcasc=telescopic_amp_params.v_pcasc, + stage1_v_ncasc=telescopic_amp_params.v_ncasc, + stage1_v_tail=telescopic_amp_params.v_tail, + stage2_in_type=stage2_params.in_type, + stage2_load_type=stage2_params.load_type, + stage2_in_params=stage2_params.in_params, + stage2_load_params=stage2_params.load_params, + stage2_v_load=stage2_params.v_load, + stage2_voutcm_ideal=stage2_params.voutcm_ideal, + c_comp=250 * FEMTO, + r_comp=1 * KILO, + ) + + params = TwoStageTbParams( + pvt=Pvt(), + vc=two_stage_amp_specs["stage1_vincm"] * 1000 * m, + vd=1 * m, + dut=twoStageAmpParams, + cl=two_stage_amp_specs["cload"] * 1000 * m, + ) + + # Create our simulation input + @hs.sim + class TwoStageAmpTranSim: + tb = TwoStageAmpTbTran(params) + tr = hs.Tran(tstop=3000 * n, tstep=1 * n) + + # Add the PDK dependencies + TwoStageAmpTranSim.lib(sky130.install.model_lib, "tt") + TwoStageAmpTranSim.literal(".save all") + results = TwoStageAmpTranSim.run(sim_options) + tran_results = results.an[0].data + t = tran_results["time"] + v_out_diff = tran_results["v(xtop.out_p)"] - tran_results["v(xtop.out_n)"] + v_out_cm = (tran_results["v(xtop.out_p)"] + tran_results["v(xtop.out_n)"]) / 2 + v_in_diff = tran_results["v(xtop.inp_p)"] - tran_results["v(xtop.inp_n)"] + v_in_cm = (tran_results["v(xtop.inp_p)"] + tran_results["v(xtop.inp_n)"]) / 2 + + v_out_stage1_diff = ( + tran_results["v(xtop.out_stage1_p)"] - tran_results["v(xtop.out_stage1_n)"] + ) + v_out_stage1_cm = ( + tran_results["v(xtop.out_stage1_p)"] + tran_results["v(xtop.out_stage1_n)"] + ) / 2 + + fig, ax = plt.subplots(2, sharex=True) + ax[0].plot(t, v_in_diff) + ax[1].plot(t, v_out_diff) + + plt.show() + + fig, ax = plt.subplots(2, sharex=True) + ax[0].plot(t, v_in_cm) + ax[1].plot(t, v_out_cm) + + plt.show() + + fig, ax = plt.subplots(2, sharex=True) + ax[0].plot(t, v_out_stage1_cm) + ax[1].plot(t, v_out_stage1_diff) + + plt.show() + + # Create our simulation input + @hs.sim + class TwoStageAmpAcSim: + tb = TwoStageAmpTbAc(params) + myac = hs.Ac(sweep=hs.LogSweep(1e1, 1e11, 10)) + + # Add the PDK dependencies + TwoStageAmpAcSim.lib(sky130.install.model_lib, "tt") + TwoStageAmpAcSim.literal(".save all") + results = TwoStageAmpAcSim.run(sim_options) + ac_results = results.an[0].data + v_out_diff_ac = ac_results["v(xtop.out_p)"] - ac_results["v(xtop.out_n)"] + f = np.logspace(start=1, stop=11, num=101, endpoint=True) + f_ax = np.logspace(start=1, stop=11, num=11, endpoint=True) + f3db_idx = np.squeeze( + np.where(abs(v_out_diff_ac) < np.max(abs(v_out_diff_ac)) / np.sqrt(2)) + )[0] + fgbw_idx = np.squeeze(np.where(abs(v_out_diff_ac) < 1))[0] + f3db = f[f3db_idx] + fgbw = f[fgbw_idx] + Avdc = np.max(abs(v_out_diff_ac)) + v_out_diff_ac_dB = 20 * np.log10(abs(v_out_diff_ac)) + v_out_diff_ac_phase = ( + (np.angle(v_out_diff_ac) % (2 * np.pi) - 2 * np.pi) * 180 / np.pi + ) + PM_ac = 180 + v_out_diff_ac_phase[fgbw_idx] + fig, ax = plt.subplots(2, sharex=True) + ax[0].semilogx(f, v_out_diff_ac_dB) + ax[0].grid(color="black", linestyle="--", linewidth=0.5) + ax[0].grid(visible=True, which="both") + ax[0].set_xticks(f_ax) + ax[1].semilogx(f, v_out_diff_ac_phase) + ax[1].grid(color="black", linestyle="--", linewidth=0.5) + ax[1].grid(visible=True, which="both") + ax[1].set_xticks(f_ax) + plt.show() + print("Av (AC Sim) = %f" % ((Avdc))) + print("BW (AC Sim) = %f MHz" % (round(f3db / 1e6, 2))) + print("GBW (AC Sim) = %f MHz" % (round(fgbw / 1e6, 2))) + print("PM (AC Sim) = %f degrees" % PM_ac) + + ampResults = dict( + Av=Avdc, + bw=f3db, + gbw=fgbw, + pm=PM_ac, + ) + + +# if __name__ == "__main__": +# generate_two_stage_ota() +# breakpoint() diff --git a/adc/test_amp.py b/adc/test_amp.py deleted file mode 100644 index e69de29..0000000 diff --git a/adc/test_strongarm.py b/adc/test_strongarm.py deleted file mode 100644 index 2618391..0000000 --- a/adc/test_strongarm.py +++ /dev/null @@ -1,252 +0,0 @@ -""" -# Comparator Tests -""" - -import matplotlib.pyplot as plt -import numpy as np -import os -from pathlib import Path - -import hdl21 as h -import hdl21.sim as hs -from hdl21 import Diff -from hdl21.pdk import Corner -from hdl21.prefix import m, f, n, PICO -from hdl21.primitives import Vdc, Idc, C, Vpulse -from vlsirtools.spice import SimOptions, SupportedSimulators, ResultFormat -import sky130, sitepdks as _ - -# Local Imports -from . import data_dir, scratch -from .strongarm import ( - comparator, - ComparatorParams, - StrongarmParams, - MosParams, - LatchParams, -) - -strongarm_results_file = data_dir / "strongarm_results.npz" - -sim_options = SimOptions( - rundir=scratch, - fmt=ResultFormat.SIM_DATA, - simulator=SupportedSimulators.NGSPICE, -) - - -@h.paramclass -class DiffClkParams: - """Differential Clock Generator Parameters""" - - period = h.Param(dtype=hs.ParamVal, desc="Period") - delay = h.Param(dtype=hs.ParamVal, desc="Delay") - vd = h.Param(dtype=hs.ParamVal, desc="Differential Voltage") - vc = h.Param(dtype=hs.ParamVal, desc="Common-Mode Voltage") - trf = h.Param(dtype=hs.ParamVal, desc="Rise / Fall Time") - - -@h.generator -def DiffClkGen(p: DiffClkParams) -> h.Module: - """# Differential Clock Generator - For simulation, from ideal pulse voltage sources""" - - ckg = h.Module() - ckg.VSS = VSS = h.Port() - ckg.ck = ck = Diff(role=Diff.Roles.SINK, port=True) - - def vparams(polarity: bool) -> Vpulse.Params: - """Closure to create the pulse-source parameters for each differential half. - Argument `polarity` is True for positive half, False for negative half.""" - # Initially create the voltage levels for the positive half - v1 = p.vc + p.vd / 2 - v2 = p.vc - p.vd / 2 - if not polarity: # And for the negative half, swap them - v1, v2 = v2, v1 - return Vpulse.Params( - v1=v1, - v2=v2, - period=p.period, - rise=p.trf, - fall=p.trf, - width=p.period / 2 - p.trf, - delay=p.delay, - ) - - # Create the two complementary pulse-sources - ckg.vp = Vpulse(vparams(True))(p=ck.p, n=VSS) - ckg.vn = Vpulse(vparams(False))(p=ck.n, n=VSS) - return ckg - - -@h.paramclass -class Pvt: - """Process, Voltage, and Temperature Parameters""" - - p = h.Param(dtype=Corner, desc="Process Corner", default=Corner.TYP) - v = h.Param(dtype=h.Prefixed, desc="Supply Voltage Value (V)", default=1800 * m) - t = h.Param(dtype=int, desc="Simulation Temperature (C)", default=25) - - -@h.paramclass -class TbParams: - dut = h.Param(dtype=ComparatorParams, desc="DUT params") - pvt = h.Param( - dtype=Pvt, desc="Process, Voltage, and Temperature Parameters", default=Pvt() - ) - vd = h.Param(dtype=h.Prefixed, desc="Differential Voltage (V)", default=100 * m) - vc = h.Param(dtype=h.Prefixed, desc="Common-Mode Voltage (V)", default=900 * m) - cl = h.Param(dtype=h.Prefixed, desc="Load Cap (Single-Ended) (F)", default=5 * f) - - -@h.generator -def ComparatorTb(p: TbParams) -> h.Module: - """Comparator Testbench""" - - # Create our testbench - tb = h.sim.tb("SlicerTb") - # Generate and drive VDD - tb.VDD = VDD = h.Signal() - tb.vvdd = Vdc(Vdc.Params(dc=p.pvt.v))(p=VDD, n=tb.VSS) - - # Input-driving balun - tb.inp = Diff() - tb.inpgen = DiffClkGen( - DiffClkParams(period=4 * n, delay=1 * n, vc=p.vc, vd=p.vd, trf=800 * PICO) - )(ck=tb.inp, VSS=tb.VSS) - - # Clock generator - tb.clk = clk = h.Signal() - tb.vclk = Vpulse( - Vpulse.Params( - delay=0, - v1=0, - v2=p.pvt.v, - period=2 * n, - rise=100 * PICO, - fall=100 * PICO, - width=1 * n, - ) - )(p=clk, n=tb.VSS) - - # Output & Load Caps - tb.out = Diff() - Cload = C(C.Params(c=p.cl)) - tb.clp = Cload(p=tb.out.p, n=tb.VSS) - tb.cln = Cload(p=tb.out.n, n=tb.VSS) - - # Create the Slicer DUT - tb.dut = comparator(p.dut)(inp=tb.inp, out=tb.out, clk=clk, VDD=VDD, VSS=tb.VSS) - return tb - - -def test_comparator_sim(): - """Comparator Test(s)""" - - w = 1.0 - l = 0.15 - nf = 2 - comparator_params = ComparatorParams( - strongarm=StrongarmParams( - tail=MosParams(w=w * nf * 2, l=l, nf=2 * nf), - inp_pair=MosParams(w=w * nf, l=l, nf=nf), - inv_n=MosParams(w=w * nf, l=l, nf=nf), - inv_p=MosParams(w=w * nf, l=l, nf=nf), - reset=MosParams(w=w * nf * 2, l=l, nf=2 * nf), - meas_vs=True, - ), - latch=LatchParams( - nor_pi=MosParams(w=w * nf, l=l, nf=nf), - nor_pfb=MosParams(w=w * nf, l=l, nf=nf), - nor_ni=MosParams(w=w * nf, l=l, nf=nf), - nor_nfb=MosParams(w=w * nf, l=l, nf=nf), - ), - ) - # Create our parametric testbench - params = TbParams(pvt=Pvt(), vc=900 * m, vd=1 * m, dut=comparator_params) - - # Create our simulation input - @hs.sim - class ComparatorSim: - tb = ComparatorTb(params) - tr = hs.Tran(tstop=12 * n, tstep=100 * PICO) - - # Add the PDK dependencies - ComparatorSim.lib(sky130.install.model_lib, "tt") - ComparatorSim.literal(".option METHOD=Gear") - - # Run Spice, save important results - results = ComparatorSim.run(sim_options) - tran_results = results.an[0].data - np.savez( - strongarm_results_file, - t=tran_results["time"], - v_out_diff=tran_results["v(xtop.out_p)"] - tran_results["v(xtop.out_n)"], - v_in_diff=tran_results["v(xtop.inp_p)"] - tran_results["v(xtop.inp_n)"], - v_clk=tran_results["v(xtop.clk)"], - i_tail=tran_results["i(v.xtop.xdut.xsa.vtail_meas)"], - i_inp_pair_cm=tran_results["i(v.xtop.xdut.xsa.vninp_meas)"] - + tran_results["i(v.xtop.xdut.xsa.vninn_meas)"], - i_latch_n_pair_cm=tran_results["i(v.xtop.xdut.xsa.vnlatn_meas)"] - + tran_results["i(v.xtop.xdut.xsa.vnlatp_meas)"], - i_latch_p_pair_cm=tran_results["i(v.xtop.xdut.xsa.vplatn_meas)"] - + tran_results["i(v.xtop.xdut.xsa.vplatp_meas)"], - v_casc_cm=( - tran_results["v(xtop.xdut.xsa.ninn_meas_p)"] - + tran_results["v(xtop.xdut.xsa.ninp_meas_p)"] - ) - / 2, - v_out_cm=( - tran_results["v(xtop.xdut.sout_p)"] + tran_results["v(xtop.xdut.sout_n)"] - ) - / 2, - ) - - -def extract_windows(t, clk, threshold): - """Given the clock waveform, this will extract a bunch of single periods - that are the periods at which a certain clock starts and ends""" - clk_above_thres_idcs = np.where(clk > threshold)[0] - print(clk_above_thres_idcs.shape) - print(np.diff(clk_above_thres_idcs)) - rising_cross_idcs = clk_above_thres_idcs[ - np.where(np.diff(np.concatenate(([0], clk_above_thres_idcs), axis=0)) > 1)[0] - ] - rising_cross_idcs = rising_cross_idcs.tolist() + [len(clk)] - return [(s, e) for s, e in zip(rising_cross_idcs[:-1], rising_cross_idcs[1:])] - - -def plot_windows(): - data = np.load(strongarm_results_file) - windows = extract_windows(data["t"], data["v_clk"], 0.7) - for (s, e) in windows: - plt.figure() - plt.plot(data["t"][s:e], data["v_clk"][s:e]) - plt.show() - - -def plot_data(): - data = np.load(strongarm_results_file) - t = data["t"] - fig, ax = plt.subplots(3, sharex=True) - ax[0].plot(t, data["v_clk"]) - ax[1].plot(t, data["v_in_diff"]) - ax[2].plot(t, data["v_out_diff"]) - fig, ax = plt.subplots(3, sharex=True) - ax[0].plot(t, data["v_clk"]) - ax[1].plot(t, data["i_tail"], label="tail current") - ax[1].plot(t, data["i_inp_pair_cm"], label="input pair current") - ax[1].plot(t, data["i_latch_n_pair_cm"], label="Latch NMOS current") - ax[1].plot(t, data["i_latch_p_pair_cm"], label="Latch PMOS current") - ax[1].legend() - ax[2].plot(t, data["v_casc_cm"], label="v_casc_cm") - ax[2].plot(t, data["v_out_cm"], label="v_out_cm") - ax[2].legend() - - plt.show() - - -# if __name__ == "__main__": -# # test_comparator_sim() -# # plot_data() -# plot_windows() diff --git a/adc/tests/test_common_source.py b/adc/tests/test_common_source.py index b221328..c43094a 100644 --- a/adc/tests/test_common_source.py +++ b/adc/tests/test_common_source.py @@ -1,25 +1,19 @@ -from pathlib import Path -from typing import Tuple +from dataclasses import dataclass import hdl21 as h from hdl21.prefix import m, UNIT from hdl21.primitives import Vdc -from vlsirtools.spice import SimOptions, SupportedSimulators, ResultFormat -# Import the Hdl21 PDK package, and our "site" configuration of its installation +# PDK Imports import sky130, sitepdks as _ +from ..testutils import sim_options + # And give a few shorthand names to PDK content MosParams = sky130.Sky130MosParams nch = sky130.modules.sky130_fd_pr__nfet_01v8 pch = sky130.modules.sky130_fd_pr__pfet_01v8 -sim_options = SimOptions( - rundir=Path("./scratch"), - fmt=ResultFormat.SIM_DATA, - simulator=SupportedSimulators.NGSPICE, -) - @h.paramclass class CommonSourceParams: @@ -41,7 +35,13 @@ class CommonSource: return CommonSource -def get_amp_performance(amp: h.Module) -> Tuple[float, float]: +@dataclass +class AmpPerformance: + vout_dc: float + dc_current: float + + +def get_amp_performance(amp: h.Module) -> AmpPerformance: """Measure the Amplifier Output DC Voltage and Supply Current""" tb = h.sim.tb("CommonSourceTb") tb.VDD = h.Signal() @@ -67,7 +67,7 @@ def get_amp_performance(amp: h.Module) -> Tuple[float, float]: vout_dc = op_results["v(xtop.vdd)"] dc_current = op_results["i(v.xtop.vvdd_src)"] vout_ac = ac_results["v(xtop.vdd)"] - return vout_dc, dc_current + return AmpPerformance(vout_dc, dc_current) def run(): diff --git a/adc/testutils.py b/adc/testutils.py new file mode 100644 index 0000000..3959aa0 --- /dev/null +++ b/adc/testutils.py @@ -0,0 +1,68 @@ +import hdl21 as h +import hdl21.sim as hs +from hdl21 import Diff +from hdl21.pdk import Corner +from hdl21.prefix import MILLI +from hdl21.primitives import Vdc, Idc, C, Vpulse +from vlsirtools.spice import SimOptions, SupportedSimulators, ResultFormat + +from . import scratch + +sim_options = SimOptions( + rundir=scratch, + fmt=ResultFormat.SIM_DATA, + simulator=SupportedSimulators.NGSPICE, +) + + +@h.paramclass +class Pvt: + """Process, Voltage, and Temperature Parameters""" + + p = h.Param(dtype=Corner, desc="Process Corner", default=Corner.TYP) + v = h.Param(dtype=h.Prefixed, desc="Supply Voltage Value (V)", default=1800 * MILLI) + t = h.Param(dtype=int, desc="Simulation Temperature (C)", default=25) + + +@h.paramclass +class DiffClkParams: + """Differential Clock Generator Parameters""" + + period = h.Param(dtype=hs.ParamVal, desc="Period") + delay = h.Param(dtype=hs.ParamVal, desc="Delay") + vd = h.Param(dtype=hs.ParamVal, desc="Differential Voltage") + vc = h.Param(dtype=hs.ParamVal, desc="Common-Mode Voltage") + trf = h.Param(dtype=hs.ParamVal, desc="Rise / Fall Time") + + +@h.generator +def DiffClkGen(p: DiffClkParams) -> h.Module: + """# Differential Clock Generator + For simulation, from ideal pulse voltage sources""" + + ckg = h.Module() + ckg.VSS = VSS = h.Port() + ckg.ck = ck = Diff(role=Diff.Roles.SINK, port=True) + + def vparams(polarity: bool) -> Vpulse.Params: + """Closure to create the pulse-source parameters for each differential half. + Argument `polarity` is True for positive half, False for negative half.""" + # Initially create the voltage levels for the positive half + v1 = p.vc + p.vd / 2 + v2 = p.vc - p.vd / 2 + if not polarity: # And for the negative half, swap them + v1, v2 = v2, v1 + return Vpulse.Params( + v1=v1, + v2=v2, + period=p.period, + rise=p.trf, + fall=p.trf, + width=p.period / 2 - p.trf, + delay=p.delay, + ) + + # Create the two complementary pulse-sources + ckg.vp = Vpulse(vparams(True))(p=ck.p, n=VSS) + ckg.vn = Vpulse(vparams(False))(p=ck.n, n=VSS) + return ckg From 0d297d68328b013f6862345d9fbcfb8a1dc70423 Mon Sep 17 00:00:00 2001 From: Dan Fritchman Date: Thu, 5 Jan 2023 21:15:40 -0800 Subject: [PATCH 10/10] Black Formatting --- adc/telescopic_cascode_diffamp_generator/__init__.py | 2 -- adc/telescopic_cascode_diffamp_generator/dbstuff.py | 1 - 2 files changed, 3 deletions(-) diff --git a/adc/telescopic_cascode_diffamp_generator/__init__.py b/adc/telescopic_cascode_diffamp_generator/__init__.py index 731554b..9339a90 100644 --- a/adc/telescopic_cascode_diffamp_generator/__init__.py +++ b/adc/telescopic_cascode_diffamp_generator/__init__.py @@ -1,5 +1,3 @@ - import nest_asyncio nest_asyncio.apply() - diff --git a/adc/telescopic_cascode_diffamp_generator/dbstuff.py b/adc/telescopic_cascode_diffamp_generator/dbstuff.py index 7f53650..7e0dd95 100644 --- a/adc/telescopic_cascode_diffamp_generator/dbstuff.py +++ b/adc/telescopic_cascode_diffamp_generator/dbstuff.py @@ -1,4 +1,3 @@ - import numpy as np import scipy.interpolate