---
title: |
  Fundamentals of Analog VLSI Design \
  Exercise 7 - Solution
subtitle: Gain and Bandwidth Enhancement
author: Christian Enz (christian.enz@epfl.ch)
affiliation: EPFL
date: 11-05-2025
date-format: "DD.MM.YYYY"
execute:
  echo: false
  warning: false
toc: true
number-sections: true
number-depth: 3
highlight-style: pygments
bibliography: references.bib
csl: ieee.csl
format: 
  html:
    include-in-header: mathjax.html
    code-fold: true
    html-math-method: mathjax
    css: styles.css
  pdf:
    keep-tex: true
    pdf-engine: pdflatex
    documentclass: scrreprt
    papersize: a4
    geometry:
      - top=20mm
      - bottom=20mm
      - left=20mm
      - right=20mm
    include-in-header:
      text: |
        \usepackage{scrlayer-scrpage}
        \rohead[\thepage]{\thepage}
        \cohead[Exercise 7]{Exercise 7}
        \lofoot[$\copyright$ C. Enz]{$\copyright$ C. Enz}
        \cofoot[Fundamentals of Analog VLSI Design]{Fundamentals of Analog VLSI Design}
        \rofoot[05.11.2025]{05.11.2025}
---

```{python}
# Initialization

from ekv_functions import *

import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import subprocess
import shutil
import os
import os.path as op
import sys
sys.path.append(".")
import re

from IPython.display import Markdown
from tabulate import tabulate

plt.rcParams['svg.fonttype'] = 'none'
plt.rcParams['pdf.fonttype'] = 42
plt.rcParams['font.family'] = 'Arial'
plt.rcParams['mathtext.fontset'] = 'custom'
plt.rcParams['mathtext.rm'] = 'Arial'
plt.rcParams['mathtext.it'] = 'Arial:italic'
plt.rcParams['mathtext.bf'] = 'Arial:bold'

inkscapePath = r"C:\\Program Files\\Inkscape\bin\\inkscape.exe"
savePlotPath = "./Plots/"
#savePlots=True
savePlots=False

if savePlots:
  lw=1
  curvelw=2
  msize=6
  textFontSize=12
  BodePlotParams = {'figure.figsize': (6,6)}
  pltStyle='plt_style_medium.mplstyle'
else:
  lw=1
  curvelw=1
  msize=5
  textFontSize=9
  BodePlotParams = {'figure.figsize': (5,5)}
  pltStyle='plt_style_small.mplstyle'

plt.style.use(pltStyle)

otaName="Gm_enhanced_OTA"
version = "_v1" # Allows you to save different versions
newDesign=True
#newDesign=False
newSim=True
#newSim=False
refloat=r'.*?([+-]?\d+([.]\d*)?([eE][+-]?\d+)?|[.]\d+([eE][+-]?\d+)?)'

def saveFigures(savePath, plotName):
#  print("Patience, saving plot!")
  figFolder = savePath + r"\\{}.{}"
  pngFile = figFolder.format(plotName,"png")
  pdfFile = figFolder.format(plotName,"pdf")
  svgFile = figFolder.format(plotName,"svg")
  plt.savefig(pngFile, format="png", dpi=1200, bbox_inches="tight", transparent=True)
  plt.savefig(pdfFile, format="pdf", dpi=1200, bbox_inches="tight", transparent=True)
  plt.savefig(svgFile, format="svg", dpi=1200, bbox_inches="tight", transparent=True)
  cmd_emf = '"{}" "{}\\{}.svg" --export-filename="{}\\{}.emf" --export-overwrite'.format(inkscapePath, savePath, plotName, savePath, plotName)
#    cmd_emf = '"{}" "{}\{}.pdf" --export-filename="{}\{}.emf" --export-overwrite'.format(inkscapePath, savePath, plotName, savePath, plotName)
#    cmd_pdf = '"{}" "{}\{}.svg" --export-filename="{}\{}.pdf" --export-overwrite'.format(inkscapePath, savePath, plotName, savePath, plotName)
  subprocess.call(cmd_emf, shell=True)
#    subprocess.call(cmd_pdf, shell=True)
```

# Problem 1: Gain Enhancement

![Schematic of gain enhancement amplifier.](Figures/Gain_enhancement_schematic_with_CL.png){#fig-Gain_enhancement_schematic}

The amplifier shown in @fig-Gain_enhancement_schematic allows to enhance the DC gain by reducing the load conductance seen by the differential pair M~1a~-M~1b~ @bib:sansen:aacd:2016 @bib:ohri:jssc:14:1:1979.

## Small-signal analysis

![Negative conductance.](Figures/negative_gm.png){#fig-negative_gm}

The cross-coupled transistors M~3a~-M~3b~ which are redrawn in @fig-negative_gm, allow to implement a negative conductance. When combined with transistors M~2a~-M~2b~, they allow to reduce the conductance seen by the differential pair M~1a~-M~1b~.

The small-signal circuit corresponding to @fig-negative_gm is shown in @fig-negative_gm_small-signal. 

![Small-signal circuit of @fig-negative_gm.](Figures/negative_gm_small-signal.png){#fig-negative_gm_small-signal}

With $\Delta I_{in} \triangleq \Delta I_1-\Delta I_2$ and $V_{in} \triangleq V_1-V_2$ is then straightforward to show that
\begin{equation}
  G \triangleq \frac{\Delta I_{in}}{\Delta V_{in}} = -G_{m3}.
\end{equation}
The circuit of @fig-negative_gm therefore implements a negative conductance equal to the transconductance of M~3a~-M~3b~.

::: {.callout-note}
Note that the circuit of @fig-Gain_enhancement_schematic is used in regenerative comparators @bib:allstot:jssc:17:6:1982 @bib:yukawa:jssc:20:3:1985 @bib:gregorian:book:1999. It is also used in oscillators to compensate the losses in the oscillator in order to sustain the oscillations.
:::

![Small-signal circuit of @fig-negative_gm in differential mode.](Figures/Gain_enhancement_small-signal_schematic1.png){#fig-Gain_enhancement_small-signal_schematic1}

![Small-signal circuit of @fig-negative_gm in differential mode.](Figures/Gain_enhancement_small-signal_schematic2.png){#fig-Gain_enhancement_small-signal_schematic2}

![Small-signal circuit of @fig-negative_gm in differential mode.](Figures/Gain_enhancement_small-signal_schematic3.png){#fig-Gain_enhancement_small-signal_schematic3}

The small-signal circuit of @fig-Gain_enhancement_schematic is shown in @fig-Gain_enhancement_small-signal_schematic1, where $G_o$ is the total conductance at the output node given by
\begin{equation}
  G_o = G_{m2} + G_{ds2} + G_{ds3} \cong G_{m2}.
\end{equation}

In differential operation we have
\begin{align}
  \Delta V_{i1} &= \frac{\Delta V_{id}}{2},\\
  \Delta V_{i2} &= -\frac{\Delta V_{id}}{2},
\end{align}
where $\Delta V_{id}$ is the small-signal differential input voltage. If we assume a perfect matching between the transistors in left and right branches, then $\Delta V_S = 0$ and the small-signal circuit of @fig-Gain_enhancement_small-signal_schematic1 simplifies to the one shown in @fig-Gain_enhancement_small-signal_schematic2. We can then use the half-circuit shown in @fig-Gain_enhancement_small-signal_schematic3 to calculate the small-signal differential transfer function. It is easy to show that the differential transfer function is given by
\begin{equation}
  A_d(s) \triangleq \frac{\Delta V_{od}}{\Delta V_{id}} = \frac{A_{dc}}{1 + \frac{s}{\omega_p}},
\end{equation}
where
\begin{align}
  A_{dc} &= -\frac{G_{m1}}{G_{od}-G_{m3}} = -\frac{G_{m1}}{G_{m2}+G_{ds1}+G_{ds2}+G_{ds3}-G_{m3}},\\
  \omega_p &= \frac{G_{od}-G_{m3}}{C_L} = \frac{G_{m2}+G_{ds1}+G_{ds2}+G_{ds3}-G_{m3}}{C_L},
\end{align}
with
\begin{equation}
  G_{od} = G_o + G_{ds1} = G_{m2}+G_{ds1}+G_{ds2}+G_{ds3}.
\end{equation}
The gain-bandwidth product or unity gain frequency $\omega_u$ is then simply given by
\begin{equation}
  \omega_u \triangleq |A_{dc}| \cdot \omega_p = \frac{G_{m1}}{C_L}.
\end{equation}
Contrary to the DC gain, the gain-bandwidth product is not increased and is the same as the amplifier without M~3a~-M~3b~.

From the expression of the pole $\omega_p$, we see that for it to be located in the left half-plane, we need $G_{m3} < G_{od} = G_{m2} + G_{ds1} + G_{ds1} + G_{ds3}$.

We can rewrite the DC gain as
\begin{equation}
  A_{dc} = -\frac{G_{m1}}{G_{m2}} \cdot K,
\end{equation}
where $K$ is the DC gain enhancement factor compared to the circuit without the cross-coupled pair M~3a~-M~3b~ given by
\begin{equation}
  K = \frac{1}{\frac{G_{od}}{G_{m2}}-\frac{G_{m3}}{G_{m2}}} = \frac{1}{1+\frac{G_{ds1}}{G_{m2}}+\frac{G_{ds2}}{G_{m2}}+\frac{G_{ds3}}{G_{m2}}-\frac{G_{m3}}{G_{m2}}}.
\end{equation}
If we set $G_{m3} = G_{m2}$, the gain-enhancement factor becomes
\begin{equation}
  K = \frac{G_{m2}}{G_{ds1} + G_{ds1} + G_{ds3}},
\end{equation}
which seems large. But looking at the DC gain, it reduces to
\begin{equation}
  A_{dc} = -\frac{G_{m1}}{G_{ds1}+G_{ds2}+G_{ds3}}.
\end{equation}
We see that it is basically the gain of a common-source stage loaded by an active load (current source). So we have indeed increased the gain compared to $G_{m1}/G_{m2}$, but we don't really improve the DC gain compared to a simple CS stage. We could have achieved the same gain with replacing the diode-connected transistors M~2a~-M~2b~ with current sources.

To really boost the gain we need to compensate for more than $G_{m2}$ but making sure that $G_{m3} < G_{od}$ to avoid any instability. Because of variability and mismatch, it is however not easy to control $G_{m3}$ such that it always remains smaller than $G_{od}$ in presence of mismatch and instability.

## Noise analysis

![Small-signal half schematic of the amplifier of @fig-Gain_enhancement_schematic including all the noise sources.](Figures/Gain_enhancement_noise_half.png){#fig-Gain_enhancement_noise_half}

The small-signal half schematic of the amplifier of @fig-Gain_enhancement_schematic including all the noise sources is shown in @fig-Gain_enhancement_noise_half. The output noise voltage is then simply given by
\begin{equation}
  V_{nout1} = \frac{I_{n1}+I_{n2}-I_{n3}}{G_{od}-G_{m3}}.
\end{equation}
The corresponding output noise resistance is then given by
\begin{equation}\label{eqn:gain:rnout}
  R_{nout} = 2\,\frac{G_{n1}+G_{n2}+G_{n3}}{(G_{od}-G_{m3})^2},
\end{equation}
where
\begin{equation}
  G_{ni} = \gamma_{ni}\,G_{mi} + G_{mi}^2\,\frac{\rho_i}{f\,W_i\,L_i}\quad\textsf{for $i=1,2,3$}.
\end{equation}

The noise can be referred to the input by dividing \eqref{eqn:gain:rnout} by $A_{dc}^2$ resulting in
\begin{equation}
  R_{nin} = \frac{R_{nout}}{A_d^2} = 2\,\frac{G_{n1}+G_{n2}+G_{n3}}{G_{m1}^2} = 2\frac{G_{n1}}{G_{m1}^2} \; (1+\eta)
\end{equation}
where
\begin{equation}
  \eta = \frac{G_{n2}}{G_{n1}} + \frac{G_{n3}}{G_{n1}}
\end{equation}
corresponds to the noise contributions of M~2a~-M~2b~ and M~3a~-M~3b~ relative to that of the differential pair M~1a~-M~1b~. If we have no gain enhancement (i.e. remove transistors M~3a~-M~3b~), the noise is increased by the contribution of M~2a~-M~2b~ only. The cross-coupled pair M~3a~-M~3b~ does not cancel the noise of M~2a~-M~2b~ but simply adds some noise resulting in the additional term in $G_{n3}/G_{n1}$ in $\eta$ further degrading the input-referred noise.

We can now have a closer look at the input-referred thermal noise resistance.

### Input-referred thermal noise resistance
The input-referred thermal noise resistance $R_{nt}$ is given by
\begin{equation}
  R_{nt} = 2\,\frac{\gamma_{n1}}{G_{m1}} \, (1+\eta_{th}),
\end{equation}
where
\begin{equation}
  \eta_{th} = \frac{\gamma_{n2}}{\gamma_{n1}} \, \frac{G_{m2}}{G_{m1}} + \frac{\gamma_{n3}}{\gamma_{n1}} \, \frac{G_{m3}}{G_{m1}},
\end{equation}
represents the contributions of M~2a~-M~2b~ and M~3a~-M~3b~ to the input-referred thermal noise relative to that of M~1a~-M~1b~. If we set $G_{m3} = G_{m2}$, the contribution of M~3a~-M~3b~ is then equal to that of M~2a~-M~2b~. The value of $\eta_{th}$ is then twice that of of the circuit without M~3a~-M~3b~. Note that $G_{m1}/G_{m2}$ represents the voltage gain without M~3a~-M~3b~, the contributions of M~2a~-M~2b~ and M~3a~-M~3b~ should remain reasonable.

### Input-referred flicker noise resistance
The input-referred flicker noise resistance $R_{nf}$ is given by
\begin{equation}
  R_{nf} = 2\,\frac{\rho_n}{f\,W_1\,L_1} \, (1+\eta_{fl}),
\end{equation}
where
\begin{equation}
  \eta_{fl} = \frac{\rho_p}{\rho_n}\,\left(\frac{G_{m2}}{G_{m1}}\right)^2\,\frac{W_1\,L_1}{W_2\,L_2} + \frac{\rho_p}{\rho_n}\,\left(\frac{G_{m3}}{G_{m1}}\right)^2\,\frac{W_1\,L_1}{W_3\,L_3},
\end{equation}
represents the contributions of M~2a~-M~2b~ and M~3a~-M~3b~ to the input-referred flicker noise relative to that of M~1a~-M~1b~. For the same reasons as discussed above, the contributions of M~2a~-M~2b~ and M~3a~-M~3b~ should remain small thanks to the square of the $G_m$ ratios. They can be further reduced by making the gate area of M~2a~-M~2b~ and M~3a~-M~3b~ larger than that of M~1a~-M~1b~.

## Conclusion
Adding the cross-coupled pair M~3a~-M~3b~ seems very attractive to boost the DC gain. However, if we set $G_{m3} = G_{m2}$, we have seen that the gain is indeed increased but only to that of a simple CS stage. The same gain would be easier to reach with replacing the diode-connected transistors M~2a~-M~2b~ by simple current source. In addition, the cross-coupled pair M~3a~-M~3b~, although canceling part of the output conductance $G_{od}$, adds more noise than what we get from a simple CS stage.

# Problem 2: Bandwidth enhancement

![Enhanced $G_m$ differential OTA @bib:sansen:aacd:2016 @bib:castello:jssc:25:3:1990](Figures/Gm_enhanced_ota_schematic.png){#fig-Gm_enhanced_ota_schematic}

The fully differential OTA shown in @fig-Gm_enhanced_ota_schematic shows a larger gain-bandwidth product compared to the case without the cross-coupled transistors M~2a~-M~2b~ @bib:sansen:aacd:2016 @bib:castello:jssc:25:3:1990.

## Small-signal analysis

![Enhanced $G_m$ differential transconductor](Figures/Gm_enhancement_schematic.png){#fig-Gm_enhancement_schematic}

The OTA of @fig-Gm_enhanced_ota_schematic uses the differential transconductor shown in @fig-Gm_enhancement_schematic which allows to increase the equivalent transconductance compared to the case of a simple differential pair.

In small-signal differential operation (i.e. $V_{i1}=\Delta V_{id}/2$ and $V_{i2}=-\Delta V_{id}/2$), assuming that transistors M~1a~-M~1b~ and M~2a~-M~2b~ are perfectly matched, the common source voltage of M~2a~-M~2b~ does not change $\Delta V_{S2} = 0$ and can be considered as an AC ground. By symmetry we also can state that $\Delta V_{G2a} = \Delta V_{D2b} = -\Delta V_{D2a}$. The VCCS modeling M~2a~ is therefore controlled by its drain voltage $\Delta V_{D2a}$ which is equal to $-\Delta V_{G2a}$. This means that the VCCS can be replaced by a negative conductance $-G_{m2}$. The small-signal circuit then simplifies to the half-circuit shown in @fig-Gm_enhancement_small-signal_half

![Small-signal circuit of the transconductor of @fig-Gm_enhancement_schematic in differential mode assuming that transistors M~1a~-M~1b~ and M~2a~-M~2b~ are perfectly matched.](Figures/Gm_enhancement_small-signal_half.png){#fig-Gm_enhancement_small-signal_half}

From the circuit shown in @fig-Gm_enhancement_small-signal_half, it is easy to derive the equivalent transconductance $G_{meq}$ as @bib:castello:jssc:25:3:1990 @bib:sansen:book:2006
\begin{equation}
  G_{meq} \triangleq \frac{\Delta I_{od}}{\Delta V_{id}} = \frac{G_{m1}\,(G_{ds2}-G_{m2})}{G_{ms1}+G_{ds1}+G_{ds2}-G_{m2}}.
\end{equation}
We see that setting $G_{m2}=G_{ms1}+G_{ds1}+G_{ds2} \cong G_{ms1}$ gives an infinite transconductance $G_{meq}$. We can now distinguish two cases $G_{m2} < G_{ms1}$ or $G_{m2} > G_{ms1}$. To investigate whether both choices are possible, we need to have a closer look at the small-signal circuit including the parasitic capacitance $C_p$ at the sources of M~1a~-M~1b~. The latter is equal to the parasitic capacitances of M~2a~-M~2b~
\begin{equation}
  C_p \cong C_{GS2} + 4\,C_{GD2}.
\end{equation}
The factor 4 comes from splitting the $2\,C_{GD2}$ capacitances in two capacitances of value $4\,C_{GD2}$ which are connected in series with the middle point connected to ground. They therefore come in parallel to $C_{GS2}$ on each side.

To derive the transadmittance we can use the half-circuit shown in @fig-Gm_enhancement_ym.

![Half small-signal circuit in differential mode including parasitic capacitance $C_p$.](Figures/Gm_enhancement_ym.png){#fig-Gm_enhancement_ym}

The resulting transadmittance is then given by
\begin{equation}
  Y_{meq} = G_{meq} \, \frac{1-s/\omega_z}{1+s/\omega_p},
\end{equation}
where
\begin{align}
  G_{meq} &= \frac{G_{m1}\,(G_{ds2}-G_{m2})}{G_{ms1}+G_{ds1}+G_{ds2}-G_{m2}} \cong -\frac{G_{m1}\,G_{m2}}{G_{ms1}-G_{m2}},\\
  \omega_z &= \frac{G_{m2}-G_{ds2}}{C_p} \cong \frac{G_{m2}}{C_p},\\
  \omega_p &= \frac{G_{ms1}+G_{ds1}+G_{ds2}-G_{m2}}{C_p} \cong \frac{G_{ms1}-G_{m2}}{C_p},\\
  C_p &= C_{GS2} + 4\,C_{GD2}.
\end{align}
From the expression of the pole $\omega_p$, we can deduce that for the pole to be in the left half-plane requires $G_{ms1} > G_{m2}$. We can then rewrite
\begin{align}
  G_{meq} &\cong -\frac{G_{m1}}{G_{ms1}/G_{m2}-1} = -\frac{G_{m1}}{\alpha-1},\\
  \omega_z &\cong \frac{G_{m2}}{C_p},\\
  \omega_p &\cong \frac{G_{m2}}{C_p}\,\left(1-\frac{G_{ms1}}{G_{m2}}\right) = \frac{G_{m2}}{C_p}\,(\alpha-1),
\end{align}
where
\begin{equation}
  \alpha \triangleq \frac{G_{ms1}}{G_{m2}} > 1
\end{equation}
Notice that the equivalent transconductance $G_{meq}$ becomes negative for $\alpha > 1$.

The equivalent transconductance of a differential pair is therefore increased by a factor $K$ given by
\begin{equation}
  K = \frac{1}{\alpha-1}.
\end{equation}
To achieve a transconductance increase of $K$, the transconductance ratio $\alpha = G_{ms1}/G_{m2}$ needs to be equal to $1+1/K$.

If transistors M~1a~-M~1b~ and M~2a~-M~2b~ are all biased in strong inversion then
\begin{align}
  G_{ms1} &= \sqrt{2\,n_1\,\beta_1\,I_{D1}},\\
  G_{m2} &= \sqrt{\frac{2\,\beta_2\,I_{D2}}{n_2}}.
\end{align}
Since M~1a~-M~1b~ and M~2a~-M~2b~ share the same current $I_{D1}=I_{D2}=I_b$ and the transconductance ratio is then given by
\begin{equation}
  \alpha = \frac{G_{ms1}}{G_{m2}} = \sqrt{\frac{n_1\,n_2\,\beta_1}{\beta_2}},
\end{equation}
which only depends on the $\beta_1/\beta_2$ ratio.

```{python}
n1=1.3
n2=1.3
alpha=sqrt(n1*n2)
K=1/(alpha-1)
```

Choosing $\beta_1 = \beta_2$ leads to $\alpha =$ `{python} f'{alpha:.2f}'` and a transconductance increase $K =$ `{python} f'{K:.3f}'` for $n_1 = n_2 =$ `{python} f'{n1:.2f}'`.

To minimize the $V_{GS}$ voltages of M~1a~-M~1b~ and M~2a~-M~2b~ and better control the transconductance ratio $\alpha = G_{ms1}/G_{m2}$ by a current ratio, we should bias them in weak inversion @bib:castello:jssc:25:3:1990. In this case the transconductances are given by
\begin{align}
  G_{ms1} &= \frac{I_{D1}}{U_T},\\
  G_{m2} &= \frac{I_{D2}}{n_2\,UT}.
\end{align}
The transconductance ratio is then proportional to the current ratio 
\begin{equation}
  \alpha = \frac{G_{ms1}}{G_{m2}} = n_2\,\frac{I_{D1}}{I_{D2}}
\end{equation}
The equivalent transconductance then writes
\begin{equation}
  G_{meq} = \frac{G_{m1}}{n_2\,\frac{I_{D1}}{I_{D2}}-1} = \frac{I_{D1}}{n_1\,U_T}\,\frac{1}{n_2\,\frac{I_{D1}}{I_{D2}}-1}.
\end{equation}
Note that $I_{D1}$ is actually half the total bias current.

```{python}
n2=1.3
alpha=n2
K=1/(alpha-1)
```

If M~1a~-M~1b~ and M~2a~-M~2b~ share the same bias current $I_b$ as it is the case in the circuit of @fig-Gm_enhancement_schematic then $I_{D1}=I_{D2}=I_b$ and $\alpha = n_2 > 1$ which is larger than 1. For $n_2 =$ `{python} f'{n2:.2f}'`, we get $K =$ `{python} f'{K:.3f}'`. 

We can compare the equivalent transconductance to the transconductance achieved in weak inversion by M~1a~-M~1b~
\begin{equation}
  G_{mwi} = \frac{I_{D1}}{n_1\,U_T},
\end{equation}
by defining the ratio
\begin{equation}\label{eqn:alpha}
  \frac{G_{meq}}{G_{mwi}} = K = \frac{1}{n_2\,\frac{I_{D1}}{I_{D2}}-1}.
\end{equation}
The $K$-factor is plotted versus the current ratio $I_{D1}/I_{D2}$ in @fig-gmeq_gmwi.

```{python}
#| label: fig-gmeq_gmwi
#| fig-cap: Equivalent transconductance compared to the transconductance in weak inversion with the same total current versus $I_{D2}/I_{D1}$ ratio.

Npts=101
n2=1.3
id1lim=1/n2
id1cross=2/n2
id1_id2=np.linspace(0.77,2,Npts,endpoint=True)
K=np.zeros(Npts)

for k in range(0,Npts):
  K[k]=1/(n2*id1_id2[k]-1)

plt.plot([id1lim,id1lim], [0,10], 'k--', linewidth=lw)
plt.plot([0,2], [1,1], 'k--', linewidth=lw)
plt.plot([id1cross,id1cross], [0,1], 'k--', linewidth=lw)
plt.plot(id1_id2, K, '-r')
plt.xlim(0.5,2)
#plt.xticks(np.arange(1,3.2,0.2))
plt.xlabel('$I_{D1}/I_{D2}$')
plt.ylim(0,10)
plt.yticks(np.arange(0,11,1))
plt.ylabel('$K$')
#plt.grid(False)
#plt.legend(loc='lower left')
textstr = '\n'.join((
    f'$n_2 =$ {n2:.1f}',
    f'$\\left.I_{{D1}}/I_{{D2}}\\right|_{{lim}} =$ {id1lim:.3f}',
    f'$\\left.I_{{D1}}/I_{{D2}}\\right|_{{cross}} =$ {id1cross:.3f}'))
plt.text(0.6, 0.95, textstr, ha='left', va='top', size=textFontSize, transform=plt.gca().transAxes,
         bbox={'facecolor':'w', 'alpha':1.0, 'pad':5})
plt.text(2/n2, 1.2, f'$2/n_2 =$ {2/n2:.2f}', ha='center', va='bottom', size=textFontSize)
plt.text(1/n2, 1.2, f'$1/n_2 =$ {1/n2:.2f}', ha='center', va='bottom', size=textFontSize)
if savePlots: saveFigures(savePlotPath, 'CMOS_inverter_large_signal')
plt.show()
```

From \eqref{eqn:alpha} and @fig-gmeq_gmwi, we see that we only gain if the drain current ratio $I_{D1}/I_{D2}$ is smaller than $2/n_2 =$ `{python} f'{id1cross:.3f}'`. On the other hand, the condition $\alpha > 1$ requires $I_{D1}/I_{D2}$ to be larger than $1/n_2 =$ `{python} f'{id1lim:.3f}'`. The drain current ratio $I_{D1}/I_{D2}$ needs therefore to satisfy the following inequalities
\begin{equation}
 \frac{1}{n_2} < \frac{I_{D1}}{I_{D2}} < \frac{2}{n_2},
\end{equation}
which for $n_2 =$ `{python} f'{n2:.3f}'` gives `{python} f'{1/n2:.3f}'` $< I_{D1}/I_{D2} <$ `{python} f'{2/n2:.3f}'`. This actually leaves little margin. Choosing $I_{D1} = I_{D2}$ is a good choice because of its simple implementation (it does not require to add any bias current to increase the current in M~1a~-M~1b~).

The transfer function of the $G_m$-enhanced OTA of @fig-Gm_enhanced_ota_schematic is given by
\begin{equation}
  A_d \triangleq \frac{V_{od}}{V_{id}} \cong \frac{G_{meq}}{G_{ds3}+s\,C_L} = \frac{A_{dc}}{1 + \frac{s}{\omega_p}}.
\end{equation}
with
\begin{align}
  A_{dc} &\cong = K \cdot \frac{G_{m1}}{G_{ds3}}.\\
\end{align}
The gain-bandwidth product is then given by
\begin{equation}
  \omega_u =|A_{dc}|\;\omega_c \cong K \cdot \frac{G_{m1}}{C_L}.
\end{equation}
We see that adding the cross-coupled pair M~2a~-M~2b~ increases both the DC gain and the gain-bandwidth product by the factor $K$ compared to the same circuit of @fig-Gm_enhanced_ota_schematic but without the cross-coupled pair M~2a~-M~2b~.

We can rewrite the DC gain as
\begin{equation}\label{eqn:gm:adc}
  A_{dc} \cong -\frac{G_{m1}}{G_{ds3}}\,\frac{1}{1+\frac{G_{ds1}}{G_{ds3}}-\frac{G_{ms1}}{G_{m2}}}
\end{equation}
From \eqref{eqn:gm:adc}, we see that in order to also increase the DC gain, we need to make $G_{ms1}/G_{m2}$ sligthly larger than 1 but still smaller than $1+G_{ds1}/G_{ds3}$ to avoid instability. Indeed if we set $G_{ms1}/G_{m2}=1$ the DC gain becomes $-G_{m1}/G_{ds1}$ which corresponds to the gain of a simple common-source stage. We have compensated the output conductance of M~3a~-M~3b~ but not that of M~1a~-M~1b~. To boost the DC gain we need
\begin{equation}
  1 < \frac{G_{ms1}}{G_{m2}} < 1 + \frac{G_{ds1}}{G_{ds3}}.
\end{equation}
It is however dangerous to set $G_{ms1}/G_{m2}$ sligthly larger than 1, because we don't control the $G_{ds1}/G_{ds3}$ very weel due to variability and mismatch.

To really take advantage of the $G_m$-enhancement also for the DC gain, the latter should be developped at another node than the drain of M~1a~-M~1b~. An example of such circuit is shown in @fig-Gm_enhanced_ota_symmetric which is close to the one proposed in @bib:castello:jssc:25:3:1990.

## Noise analysis

![Small-signal schematic of the circuit of @fig-Gm_enhancement_schematic](Figures/Gm_enhancement_noise_half.png){#fig-Gm_enhancement_noise_half}

The small-signal schematic of @fig-Gm_enhancement_schematic including all the noise sources is shown in @fig-Gm_enhancement_noise_half. The output noise current $I_{nout1}$ is given by
\begin{equation}
  I_{nout1} = H_{n1} \cdot I_{n1} + H_{n2} \cdot I_{n2},
\end{equation}
with
\begin{align}
  H_{n1} &= \frac{G_{m2}}{G_{m2}-G_{ms1}} = -\frac{1}{\alpha-1},\\
  H_{n2} &= \frac{G_{ms1}}{G_{ms1}-G_{m2}} = \frac{\alpha}{\alpha-1},
\end{align}
where $\alpha = G_{ms1}/G_{m2}$. The differential output noise current is simply $I_{nout} = 2\,I_{nout1}$ and the output noise conductance then writes
\begin{equation}
  G_{nout} = 2\,\left(|H_{n1}|^2\,G_{n1}+|H_{n2}|^2\,G_{n2}\right),
\end{equation}
with
\begin{equation}
  G_{ni} = \gamma_{ni}\,G_{mi} + G_{mi}^2\,\frac{\rho_i}{f\,W_i\,L_i}\quad\textsf{for $i=1,2$}.
\end{equation}

The input-referred noise resistance is then given by
\begin{equation}
  R_{nin} = \frac{G_{nout}}{G_{meq}^2} = 2\frac{G_{n1}}{G_{m1}^2}\,(1+\eta)
\end{equation}
where
\begin{equation}
  \eta = \left(\frac{G_{ms1}}{G_{m2}}\right)^2\,\frac{G_{n2}}{G_{n1}} = \alpha^2\,\frac{G_{n2}}{G_{n1}}
\end{equation}
corresponds to the noise contributions of M~2a~-M~2b~ relative to that of the differential pair M~1a~-M~1b~. We see that the input-referred noise is degraded by M~2a~-M~2b~.

### Input-referred thermal noise resistance

```{python}
n1=n2
etath=n1*n2
etafl=n1**2
```

The input-referred thermal noise resistance $R_{nt}$ is given by
\begin{equation}
  R_{nt} = 2\,\frac{\gamma_{n1}}{G_{m1}} \, (1+\eta_{th}),
\end{equation}
where
\begin{equation}
  \eta_{th} = \frac{\gamma_{n2}}{\gamma_{n1}} \, \frac{n_1\,G_{ms1}}{G_{m2}} = \frac{\gamma_{n2}}{\gamma_{n1}} \, n_1 \, \alpha,
\end{equation}
represents the contributions of M~2a~-M~2b~ to the input-referred thermal noise relative to that of M~1a~-M~1b~. If both transistors M~1a~-M~1b~ and M~2a~-M~2b~ are biased in weak inversion then
\begin{equation}
  \alpha = \,n_2\,\frac{I_{D1}}{I_{D2}}
\end{equation}
and
\begin{equation}
  \eta_{th} = \frac{\gamma_{n2}}{\gamma_{n1}}\,n_1\,n_2\,\frac{I_{D1}}{I_{D2}} \cong n_1\,n_2\,\frac{I_{D1}}{I_{D2}},
\end{equation}
In the case $I_{D1}=I_{d2}=I_b$ then
\begin{equation}
  \eta_{th} \cong n_1\,n_2.
\end{equation}
For $n_1=n_2=$ `{python} f'{n1:.2f}'`, we get $\eta_{th} =$ `{python} f'{etath:.3f}'`, which means that M~2a~-M~2b~ contribute `{python} f'{etath:.3f}'`-times more than M~1a~-M~1b~ to the input-referred thermal noise.

### Input-referred flicker noise resistance
The input-referred flicker noise resistance $R_{nf}$ is given by
\begin{equation}
  R_{nf} = 2\,\frac{\rho_n}{f\,W_1\,L_1} \, (1+\eta_{fl}),
\end{equation}
where
\begin{equation}
  \eta_{fl} = n_1^2\,\frac{W_1\,L_1}{W_2\,L_2},
\end{equation}
represents the contributions of M~2a~-M~2b~ to the input-referred flicker noise relative to that of M~1a~-M~1b~. We see that if M~1a~-M~1b~ and M~2a~-M~2b~ have the same gate area, their contribution to the input-referred flicker noise is about the same. For $n_1=$ `{python} f'{n1:.2f}'` and $W_1\,L_1 = W_2\,L_2$, we get $\eta_{fl} =$ `{python} f'{etafl:.3f}'`, which means that M~2a~-M~2b~ contributes `{python} f'{etafl:.3f}'` more than M~1a~-M~1b~. We can of course reduce this contribution by making $W_2\,L_2$ larger than $W_1\,L_1$.

The above analysis has shown that the $G_m$-enhanced OTA of @fig-Gm_enhanced_ota_schematic can increase the gain-bandwidth product and eventually the DC gain compared to the same OTA without the cross-coupled pair M~2a~-M~2b~, but this comes at the cost of a higher noise. 

## Example
### Design

![Single-ended version of the $G_m$-enhanced OTA @bib:castello:jssc:25:3:1990](Figures/Gm_enhanced_ota_symmetric.png){#fig-Gm_enhanced_ota_symmetric}

We want to design the single-ended $G_m$-enhanced OTA shown in @fig-Gm_enhanced_ota_symmetric @bib:castello:jssc:25:3:1990 for the specifications given in @tbl-specs. The circuit of @fig-Gm_enhanced_ota_symmetric corresponds to a symmetrical OTA to which we have added the cross-coupled pair M~2a~-M~2b~. It is close to the original OTA proposed by Castello @bib:castello:jssc:25:3:1990. As explained above, this circuit takes advantage of the $G_m$-enhancement provided by the cross-coupled pair M~2a~-M~2b~ to increase both the gain-bandwidth product and the DC gain. The reason is that the DC gain is realized at the output node instead of the drain of M~1a~-M~1b~ like it is the case for the OTA of @fig-Gm_enhanced_ota_schematic.

We need to find the bias current and size the transistors to achieve the specs @tbl-specs. We will design the amplifier for a generic 180nm bulk CMOS process. The physical parameters are given in @tbl-physics_param, the global process parameters in @tbl-process_param and finally the MOSFET parameters in @tbl-mos_param. 

```{python}
#| label: tbl-specs
#| tbl-cap: Specifications for the differential pair with resistive loads.

from specs import *

table = [
        ["Input common mode voltage","$V_{{ic}}$",f'{Vic:.1f}',"$V$"],
        ["DC gain","$A_{{dc}}$",f'{AdcdBspec:.0f}',"$dB$"],
        ["DC gain","$A_{{dc}}$",f'{Adcspec:.0e}',"-"],
        ["Gain-bandwidth product","$GBW$",f'{GBWspec/1e6:.0f}',"$MHz$"],
        ["Load capacitance","$C_L$",f'{CL/1e-12:.0f}',"$pF$"]
        ]

Markdown(tabulate(
  table,
  headers=["Specification","Symbol","Value","Unit"],
  numalign="center",
  stralign="center",
  colalign=("left","center","center","center")
))
```

```{python}
#| label: tbl-physics_param
#| tbl-cap: Physical parameters

from tsmc018_sekv_ekv2p6 import *

table = [["$T$",f'{T:.0f}',"$K$"],
         ["$U_T$",f'{UT/1e-3:.3f}',"$mV$"]
         ]

Markdown(tabulate(
  table,
  headers=["Parameter","Value","Unit"],
  numalign="center",
  stralign="center",
  colalign=("center", "center", "center")
))
```

```{python}
#| label: tbl-process_param
#| tbl-cap: Global process parameters

table = [["$V_{DD}$",f'{VDD:.1f}',"$V$"],
         ["$C_{ox}$",f'{Cox*1e3:.3f}',"$\\frac{fF}{\\mu m^2}$"],
         ["$W_{min}$",f'{Wmin/1e-9:.0f}',"$nm$"],
         ["$L_{min}$",f'{Lmin/1e-9:.0f}',"$nm$"]
         ]

Markdown(tabulate(
  table,
  headers=["Parameter","Value","Unit"],
  numalign="center",
  stralign="center",
  colalign=("center", "center", "center")
))
```

```{python}
#| label: tbl-mos_param
#| tbl-cap: Transistor process parameters

table = [["sEKV parameters"],
         ["$n$",f'{n0n:.2f}',f'{n0p:.2f}',"-"],
         ["$I_{{spec\\Box}}$",f'{Ispecsqn/1e-9:.0f}',f'{Ispecsqp/1e-9:.0f}',"$nA$"],
         ["$V_{{T0}}$",f'{VT0n:.3f}',f'{VT0p:.3f}',"$V$"],
         ["$L_{{sat}}$",f'{Lsatn/1e-9:.0f}',f'{Lsatp/1e-9:.0f}',"$nm$"],
         ["$\\lambda$",f'{lambdan*1e-6:.0f}',f'{lambdap*1e-6:.0f}',"$\\frac{{V}}{{\\mu m}}$"],
         ["Overlap capacitances parameters"],
         ["$C_{{GDo}}$",f'{CGDOn*1e9:.3f}',f'{CGDOp*1e9:.3f}','$\\frac{{fF}}{{\\mu m}}$'],
         ["$C_{{GSo}}$",f'{CGSOn*1e9:.3f}',f'{CGSOp*1e9:.3f}','$\\frac{{fF}}{{\\mu m}}$'],
         ["$C_{{GBo}}$",f'{CGBOn*1e9:.3f}',f'{CGBOp*1e9:.3f}','$\\frac{{fF}}{{\\mu m}}$'],
         ["Junction capacitances parameters"],
         ["$C_J$",f'{CJn*1e3:.3f}',f'{CJp*1e3:.3f}','$\\frac{{fF}}{{\\mu m^2}}$'],
         ["$C_{{JSW}}$",f'{CJSWn*1e9:.3f}',f'{CJSWp*1e9:.3f}','$\\frac{{fF}}{{\\mu m}}$'],
         ["Flicker noise parameters"],
         ["$K_F$",f'{KFn:.1e}',f'{KFn:.1e}',"$J$"],
         ["$AF$",f'{AFn:.1f}',f'{AFp:.1f}',"-"],
         ["$\\rho$",f'{rhon:.3e}',f'{rhop:.3e}','$\\frac{{V \\cdot m^2}}{{A \\cdot s}}$'],
         ["Matching parameters"],
         ["$A_{{VT}}$",f'{AVTn*1e9:.0f}',f'{AVTp*1e9:.0f}',"$mV \\cdot \\mu m$"],
         ["$A_{{\\beta}}$",f'{Abetan*1e8:.0f}',f'{Abetap*1e8:.0f}',"$\\% \\cdot \\mu m$"],
         ["Source and drain sheet resistance parameter"],
         ["$R_{{sh}}$",f'{RSHn:.0f}',f'{RSHp:.0f}',"$\\frac{{\\Omega}}{{\\mu m}}$"],
         ["Width and length parameters"],
         ["$\\Delta W$",f'{DWn/1e-9:.0f}',f'{DWp/1e-9:.0f}',"$\\,nm$"],
         ["$\\Delta L$",f'{DLn/1e-9:.0f}',f'{DLp/1e-9:.0f}',"$\\,nm$"]
         ]

Markdown(tabulate(
  table,
  headers=["Parameter","NMOS","PMOS","Unit"],
  numalign="center",
  stralign="center",
  colalign=("center", "center", "center")
))
```


```{python}
n2=n0n
alpha=n0n
K=1/(alpha-1)
```

The DC gain also benefits from the current gain $B$ in the pMOS current mirrors. The equivalent transconductance is then given by
\begin{equation}
  G_{meq} = B \cdot K \cdot G_{m1}.
\end{equation}
where
\begin{equation}
  K = \frac{1}{\alpha-1}
\end{equation}
with
\begin{equation}
  \alpha = \frac{G_{ms1}}{G_{m2}} = n_2.
\end{equation}
For the chosen technology this gives $\alpha =$ `{python} f'{alpha:.3f}'` and $K =$ `{python} f'{K:.3f}'`.

The small-signal differential DC voltage gain is approximately given by
\begin{equation}
  A_{dc} \cong - \frac{G_{meq}}{G_o}
\end{equation}
with
\begin{equation}
  G_o = G_{ds3d} + G_{ds4b}.
\end{equation}

```{python}
B=3
wu=2*pi*GBWspec
Gmeq=wu*CL
Gm1=Gmeq/(K*B)
IC1=0.1
Ibmin=Gm1*n0n*UT/gmsid_ic(IC1)
Ib=Ibmin
Ib=210e-9
Gm1a=wu*CL
Ibmina=Gm1a*n0n*UT/gmsid_ic(IC1)
Ispec1=Ib/IC1
WoverL1=Ispec1/Ispecsqn
Gms1=Ispec1/UT*gms_ic(IC1)
Gm1=Gms1/n0n
L1=Lmin
Leff1=L1-DLn
Weff1=Leff1*WoverL1
W1=Weff1-DWn
IC2=0.1
Gm2=Gms1/alpha
gmsid2=Gm2*n0n*UT/Ib
IC2=ic_gmsid(gmsid2)
Ispec2=Ib/IC2
WoverL2=Ispec2/Ispecsqn
L2=Lmin
Leff2=L2+DLn
Weff2=WoverL2*Leff2
W2=Weff2+DWn
IC3=10
Ispec3=Ib/IC3
WoverL3=Ispec3/Ispecsqp
Go=Gmeq/abs(Adcspec)
Gds3d=Go/2
Leff3=B*Ib/(lambdap*Gds3d)
Weff3=WoverL3*Leff3
W3=Weff3-DWp
L3=Leff3-DLp
IC4=10
Ispec4=B*Ib/IC4
WoverL4=Ispec4/Ispecsqn
Gds4b=Go/2
Leff4=B*Ib/(lambdan*Gds4b)
Weff4=WoverL4*Leff4
W4=Weff4-DWp
L4=Leff4-DLp
IC5=10
Ispec5=2*Ib/IC5
WoverL5=Ispec5/Ispecsqn
W5=Wmin
Weff5=W5+DWn
Leff5=Weff5/WoverL5
L5=Leff5-DLn
```

We additionnally set the current gain of the pMOS current mirrors to $B =$ `{python} f'{B:.0f}'`. With $K =$ `{python} f'{K:.3f}'` we have a total $G_m$-enhancement factor $B \cdot K =$ `{python} f'{B*K:.3f}'`. From the specified gain-bandwidth product and the load capacitance we can deduce the transconductance of M~1a~-M~1b~ $G_{m1} =$ `{python} f'{Gm1/1e-6:.3f}'` $\mu A/V$. Setting the inversion coefficient of M~1a~-M~1b~ to $IC_1 =$ `{python} f'{IC1:.1f}'`, we get the bias current $I_b =$ `{python} f'{Ibmin/1e-9:.0f}'` $nA$, which we slightly increase to $I_b =$ `{python} f'{Ib/1e-9:.0f}'` $nA$ in order to have some margin. The differential pair bias current $2I_b =$ `{python} f'{2*Ib/1e-9:.0f}'` $nA$.

::: {.callout-note}
Note that if we want to achieve the same gain-bandwidth product $GBW =$ `{python} f'{GBWspec/1e6:.0f}'` $MHz$ without the cross-coupled pair and current gain, keeping the same inversion coefficient for M~1a~-M~1b~, we would need a bias current $I_b =$ `{python} f'{Ibmina/1e-6:.3f}'` $\mu A$, which is `{python} f'{Ibmina/Ibmin:.3f}'` times larger than the bias current with the cross-coupled pair and the current gain.
:::

We can now deduce the $I_{spec}$ and $W/L$ for M~1a~-M~1b~ as $I_{spec1} =$ `{python} f'{Ispec1/1e-6:.0f}'` $\mu A$ and $W_1/L_1 =$ `{python} f'{WoverL1:.3f}'`. Choosing $L_1 = L_{min} =$ `{python} f'{L1/1e-9:.0f}'` $nm$ we get and $W_1 =$ `{python} f'{W1/1e-6:.2f}'` $\mu m$.

The transconductance of M~2a~-M~2b~ is $G_{m2} = G_{ms1}/\alpha =$ `{python} f'{Gm2/1e-6:.3f}'` $\mu A/V$. For a current $I_{D2} = I_b =$ `{python} f'{Ib/1e-9:.0f}'` $nA$, we get $G_{m2}\,n_2\,U_T/I_{D2} =$ `{python} f'{gmsid2:.3f}'` leading to $IC_2 =$ `{python} f'{IC2:.3f}'`. The specific current $I_{spec}$ and $W/L$ for M~2a~-M~2b~ are given by $I_{spec2} =$ `{python} f'{Ispec2/1e-6:.3f}'` $\mu A$ and $W_2/L_2 =$ `{python} f'{WoverL2:.3f}'`. Choosing $L_2 = L_{min} =$ `{python} f'{Lmin/1e-9:.0f}'` $nm$ we get and $W_2 =$ `{python} f'{W2/1e-6:.2f}'` $\mu m$.

For M~3a~ to M~3d~, choosing $IC_3 =$ `{python} f'{IC3:.0f}'`, we get $I_{spec3} =$ `{python} f'{Ispec3/1e-9:.0f}'` $nA$ and $W_3/L_3 =$ `{python} f'{WoverL3:.3f}'`. If we split $G_o$ equally between M~3d~ and M~4b~ $G_{ds3d}=G_{ds4b}$, then $G_{ds3d} =$ `{python} f'{Gds3d/1e-9:.3f}'` $nA/V$. This leads to $L_3 =$ `{python} f'{L3/1e-6:.2f}'` $\mu m$ and $W_3 =$ `{python} f'{W3/1e-6:.2f}'` $\mu m$.

Similarly for M~4a~-M~4b~, choosing $IC_4 =$ `{python} f'{IC4:.0f}'`, we get $I_{spec4} =$ `{python} f'{Ispec4/1e-9:.0f}'` $nA$ and $W_4/L_4 =$ `{python} f'{WoverL4:.3f}'`. With $G_{ds4b} =$ `{python} f'{Gds4b/1e-9:.3f}'` $nA/V$. This leads to $L_4 =$ `{python} f'{L4/1e-6:.2f}'` $\mu m$ and $W_4 =$ `{python} f'{W4/1e-6:.2f}'` $\mu m$.

```{python}
W3=Wmin
Weff3=W3+DWp
Leff3=Weff3/WoverL3
L3=Leff3-DLp
W4=Wmin
Weff4=W4+DWn
Leff4=Weff4/WoverL4
L4=Leff4-DLn
Gds3d=B*Ib/(lambdap*Leff3)
Gds4b=B*Ib/(lambdan*Leff4)
Go=Gds3d+Gds4b
Adcthe=-Gmeq/Go
AdcthedB=20*log(abs(Adcthe))
```

This gives very big transistors for M~3a~ to M~3d~ and M~4a~-M~4b~ that will add large parasitic capacitances at the mirror nodes and at the output penalizing the gain-bandwidth product and the phase margin. This is even more the case when accounting for the current gain $B =$ `{python} f'{B:.0f}'` which makes the width of M~3c~-M~3d~ `{python} f'{B:.0f}'`-times larger than the width of M~3a~-M~3b~. In order to minimize the parasitic capacitances at the mirror nodes and at the output, we choose for M~3a~-M~3b~ $W_3 = W_{min} =$ `{python} f'{W3/1e-9:.0f}'` $nm$, which leads to $L_3 =$ `{python} f'{L3/1e-6:.2f}'` $\mu m$. Note that the width of M~3c~-M~3d~ is `{python} f'{B:.0f}'`-times that of M~3a~-M~3b~.

Similarly for M~4a~-M~4b~, we choose $W_4 = W_{min} =$ `{python} f'{W4/1e-9:.0f}'` $nm$, which leads to $L_4 =$ `{python} f'{L4/1e-6:.2f}'` $\mu m$. This will obviously reduce the DC gain below the target value. The estimated DC gain is now only $A_{dc} \cong$ `{python} f'{AdcthedB:.2f}'` $dB$. Simulations will show that this value is pessimistic and that we loose less DC gain, but still do not achieve the target value. Of course we could cascode the M~3d~ and M~4b~ to save the DC gain without penalty on the current consumption (except the additional bias circuit for biasing the cascode transistors).

Finally, choosing $IC_5 =$ `{python} f'{IC4:.0f}'` for M~5a~-M~5b~, we get $I_{spec5} =$ `{python} f'{Ispec5/1e-9:.0f}'` $nA$ and $W_5/L_5 =$ `{python} f'{WoverL5:.3f}'`. Choosing $W_5 = W_{min} =$ `{python} f'{W5/1e-9:.0f}'` $nm$, we get $L_5 =$ `{python} f'{L5/1e-6:.2f}'` $\mu m$.


### Transistor information
The transistor sizes and large-signal variables are summarized in @tbl-transistor_info1, whereas @tbl-transistor_info2 gives the small-signal and thermal noise parameters. An Excel table is generated with more information (e.g. all the parasitic capacitances).


```{python}
#| label: tbl-transistor_info1
#| tbl-cap: Transistor size and bias information.

excelFilename = otaName + version + ".xlsx"

transistor_info={
    "Type": ["n","n","n","n","p","p","p","p","n","n","n","n"],
    "Function": ["DP","DP","DP","DP","CM","CM","CM","CM","CM","CM","CM","CM"],
    "W": [W1,W1,W2,W2,W3,W3,B*W3,B*W3,W4,W4,W5,W5],
    "L": [L1,L1,L2,L2,L3,L3,L3,L3,L4,L4,L5,L5],
    "ID": [Ib,Ib,Ib,Ib,Ib,Ib,B*Ib,B*Ib,B*Ib,B*Ib,2*Ib,2*Ib]
}
transistor_list=["M1a","M1b","M2a","M2b","M3a","M3b","M3c","M3d","M4a","M4b","M5a","M5b"]
ota_df=pd.DataFrame(transistor_info, index=transistor_list)

ota_df["Weff"]=np.where(ota_df["Type"]=="n",
                                     ota_df["W"]+DWn,
                                     ota_df["W"]+DWp)
ota_df["Leff"]=np.where(ota_df["Type"]=="n",
                                     ota_df["L"]+DLn,
                                     ota_df["L"]+DLp)
ota_df["W/L"]=ota_df["Weff"]/ota_df["Leff"]
ota_df["Ispec"]=np.where(ota_df["Type"]=="n",
                                     ota_df["W/L"]*Ispecsqn,
                                     ota_df["W/L"]*Ispecsqp)
ota_df["IC"]=ota_df["ID"]/ota_df["Ispec"]
ota_df["VP-VS"]=UT*vps_ic(ota_df["IC"])
ota_df["n"]=np.where(ota_df["Type"]=="n",
                                     n0n,
                                     n0p)
ota_df["VG-VT0"]=np.where(ota_df["Type"]=="n",
                                     ota_df["VP-VS"]/n0n,
                                     ota_df["VP-VS"]/n0p)
ota_df["VDSsat"]=UT*vdssat_ic(ota_df["IC"])
ota_df["Gspec"]=ota_df["Ispec"]/UT
ota_df["Gms"]=ota_df["Gspec"]*gms_ic(ota_df["IC"])
ota_df["Gm"]=np.where(ota_df["Type"]=="n",
                                     ota_df["Gms"]/n0n,
                                     ota_df["Gms"]/n0p)
ota_df["Gds"]=np.where(ota_df["Type"]=="n",
                                     ota_df["ID"]/(lambdan*ota_df["L"]),
                                     ota_df["ID"]/(lambdap*ota_df["L"]))
ota_df["gamman"]=np.where(ota_df["Type"]=="n",
                                     gamman_ic(ota_df["IC"],n0n),
                                     gamman_ic(ota_df["IC"],n0p))
ota_df["COX"]=Cox*ota_df["W"]*ota_df["L"]
ota_df["CGSi"]=ota_df["COX"]*cgsi_ic(ota_df["IC"])
ota_df["CGDi"]=0
ota_df["CGBi"]=np.where(ota_df["Type"]=="n",
                                     ota_df["COX"]*cgbi_ic(ota_df["IC"],n0n),
                                     ota_df["COX"]*cgbi_ic(ota_df["IC"],n0p))
ota_df["CBSi"]=np.where(ota_df["Type"]=="n",
                                     ota_df["COX"]*cbsi_ic(ota_df["IC"],n0n),
                                     ota_df["COX"]*cbsi_ic(ota_df["IC"],n0p))
ota_df["CBDi"]=np.where(ota_df["Type"]=="n",
                                     ota_df["CGDi"]*(n0n-1),
                                     ota_df["CGDi"]*(n0p-1))
ota_df["CGSe"]=np.where(ota_df["Type"]=="n",
                                     ota_df["W"]*CGSOn,
                                     ota_df["W"]*CGSOp)
ota_df["CGDe"]=np.where(ota_df["Type"]=="n",
                                     ota_df["W"]*CGDOn,
                                     ota_df["W"]*CGDOp)
ota_df["CGBe"]=np.where(ota_df["Type"]=="n",
                                    ota_df["W"]*CGBOn,
                                    ota_df["W"]*CGBOp)
ota_df["CBSe"]=np.where(ota_df["Type"]=="n",
                                    ota_df["W"]*ota_df["L"]*CJn+2*(ota_df["W"]+ota_df["L"])*CJSWn,
                                    ota_df["W"]*ota_df["L"]*CJp+2*(ota_df["W"]+ota_df["L"])*CJSWp)
ota_df["CBDe"]=np.where(ota_df["Type"]=="n",
                                    ota_df["W"]*ota_df["L"]*CJn+2*(ota_df["W"]+ota_df["L"])*CJSWn,
                                    ota_df["W"]*ota_df["L"]*CJp+2*(ota_df["W"]+ota_df["L"])*CJSWp)
ota_df["CGS"]=ota_df["CGSi"]+ota_df["CGSe"]
ota_df["CGD"]=ota_df["CGDi"]+ota_df["CGDe"]
ota_df["CGB"]=ota_df["CGBi"]+ota_df["CGBe"]
ota_df["CBS"]=ota_df["CBSi"]+ota_df["CBSe"]
ota_df["CBD"]=ota_df["CBDi"]+ota_df["CBDe"]
#pd.set_option('display.float_format', '{:.2E}'.format)
#ota_df

if newDesign:
  with pd.ExcelWriter(excelFilename) as writer:
#    ota_specs_df.to_excel(writer, sheet_name="Specs", index=False)
#    ota_bias_df.to_excel(writer, sheet_name="Bias", index=False)
    ota_df.to_excel(writer, sheet_name="Transistors")

# Generate the table from dataframe
feature_list=["W","L","ID","Ispec","IC","VG-VT0","VDSsat"]
Nmos=len(transistor_list)
Nfeature=len(feature_list)
table=[]
for mos in transistor_list:
  row=[mos]
  for feature in feature_list:
    row.append(ota_df.at[mos,feature])
  table.append(row)

# Scale data for proper units
for i in range(Nmos):
  for j in [1,2]:
    table[i][j]=table[i][j]/1e-6
  for j in [3,4]:
    table[i][j]=table[i][j]/1e-9
  for j in [6,7]:
    table[i][j]=table[i][j]/1e-3

Markdown(tabulate(
  table,
  headers=["Transistor","$W\\;[\\mu m]$","$L\\;[\\mu m]$","$I_D\\;[nA]$","$I_{{spec}}\\;[nA]$","$IC$","$V_G-V_{{T0}}\\;[mV]$","$V_{{DSsat}}\\;[mV]$"],
  numalign="center",
  stralign="center",
  colalign=("center","center","center","center","center","center","center","center"),
  floatfmt=(".3f",".2f",".2f",".0f",".0f",".3f",".0f",".0f")
))
```


```{python}
#| label: tbl-transistor_info2
#| tbl-cap: Transistor small-signal and thermal noise parameters.

# Generate the table from dataframe
feature_list=["Gspec","Gms","Gm","Gds","gamman"]
Nmos=len(transistor_list)
Nfeature=len(feature_list)
table=[]
for mos in transistor_list:
  row=[mos]
  for feature in feature_list:
    row.append(ota_df.at[mos,feature])
  table.append(row)

# Scale data for proper units
for i in range(Nmos):
  for j in [1,2,3]:
    table[i][j]=table[i][j]/1e-6
  for j in [4]:
    table[i][j]=table[i][j]/1e-9

Markdown(tabulate(
  table,
  headers=["Transistor","$G_{{spec}}\\;[\\mu A/V]$","$G_{{ms}}\\;[\\mu A/V]$","$G_m\\;[\\mu A/V]$","$G_{{ds}}\\;[nA/V]$","$\\gamma_n$"],
  numalign="center",
  stralign="center",
  colalign=("center","center","center","center","center","center"),
  floatfmt=(".3f",".3f",".3f",".3f",".3f",".3f")
))
```


### Simulation
#### Operating point
We first write the parameter file for this specific design for running the ngspice simulations.
Before running the AC and NOISE simulations, we first need to check the quiescent voltages and currents and the operating points of all transistors by running an .OP simulation. The node voltages are extracted from the .ic file and presented in @tbl-ng_openloop_voltages.

```{python}
#| label: tbl-ng_openloop_voltages
#| tbl-cap: OTA node voltages with the OTA in open-loop without offset correction.

simtype = ".op"
fileName = otaName + simtype
simulationPath = "./Simulations/ngspice/Gm-enhanced/"
simulationFile = simulationPath + fileName + ".cir"
simulationLog = simulationPath + fileName + ".log"
simulationVoltages = simulationPath + fileName + ".ic"
simulationData = simulationPath + fileName + ".dat"
ngspiceNodeVoltageFile = simulationPath + otaName + version + simtype + ".ic"
ngspiceDataFile = simulationPath + otaName + version + simtype + ".dat"

W1=ota_df.at['M1a','W']
L1=ota_df.at['M1a','L']
W2=ota_df.at['M2a','W']
L2=ota_df.at['M2a','L']
W3=ota_df.at['M3a','W']
L3=ota_df.at['M3a','L']
W4=ota_df.at['M4a','W']
L4=ota_df.at['M4a','L']
W5=ota_df.at['M5a','W']
L5=ota_df.at['M5a','L']

Vos=0

if newSim:
  paramstr = '\n'.join((
    f'.param VDD={VDD:.1f} Vic={Vic:.1f} Vos={Vos/1e-6:.1f}u',
    f'.param Ib={Ib/1e-9:.0f}n B={B:.0f}',
    f'.param CL={CL/1e-12:.0f}p',
    f'.param W1={W1/1e-6:.2f}u L1={L1/1e-6:.2f}u',
    f'.param W2={W2/1e-6:.2f}u L2={L2/1e-6:.2f}u',
    f'.param W3={W3/1e-6:.2f}u L3={L3/1e-6:.2f}u',
    f'.param W4={W4/1e-6:.2f}u L4={L4/1e-6:.2f}u',
    f'.param W5={W5/1e-6:.2f}u L5={L5/1e-6:.2f}u',
  ))
  # Write the transistors sizes and bias to the ngspice directory
  with open(simulationPath+'size_bias.par', 'w') as f:
    f.write(paramstr)
  #print('Starting ngspice simulation...\n')
  result = subprocess.run(f"""ngspice_con -b -o "{simulationLog}" "{simulationFile}" """, shell=True, capture_output=True, text=True)
  # We need to copy the resulting operating point information file because it will be overwritten by subsequent simulations
  shutil.copy2(simulationVoltages, ngspiceNodeVoltageFile)
  shutil.copy2(simulationData, ngspiceDataFile)

nodeList=["vdd","inp","inn","id","ic","out","1","2","3","4","5","6","7"]
v_df=pd.DataFrame(index=nodeList)
Nnodes=len(nodeList)
for k in range(0,Nnodes):
  nodeName=nodeList[k]
  regexp = re.compile(r'v\(' + nodeName + r'\)' + refloat)
  with open(ngspiceNodeVoltageFile) as f:
    for line in f:
      match = re.search(regexp, line)
      if match:
        v_df.at[nodeName, 'Voltage']=float(match.group(1))

Voutq0=v_df.loc['out','Voltage']
#pd.set_option('display.float_format', '{:.2f}'.format)
#v_df

Markdown( 
  tabulate(v_df,
  showindex=True,
  headers=["Node","Voltage"],
  numalign="center",
  stralign="center",
  colalign=("center","center")
))
```

From @tbl-ng_openloop_voltages, we see that the ouput voltage is equal to $V_{outq} =$ `{python} f'{Voutq0/1e-3:.0f}'` $mV$ for an input common mode voltage $V_{ic} =$ `{python} f'{Vic:.1f}'` $V$. We need to check wether this quiescent output voltage is in the high gain region for running the AC simulation. This is done below by looking at the large-signal transfer characteristic.

The operating point information for all transistors coming from the EKV2.6 compact model are extracted from the ngspice .op.dat file. The data is split into the large-signal operating informations presented in @tbl-ng_op1, the small-signal operating point informations shown in @tbl-ng_op2 and the noise operating point informations in @tbl-ng_op3.

```{python}
#| label: tbl-ng_op1
#| tbl-cap: Large-signal operating point information extracted from ngspice .op file for each transistor.

ngspiceDataFile = simulationPath + fileName + ".dat"
df_mos=pd.read_table(ngspiceDataFile, sep=r'\s+', skiprows=[2,4,6,8,10,12,14,16,18,20,22,24], dtype=np.float64, engine='python')
df_mos=df_mos.rename(columns={'@n1a[id]': 'Transistor', '@N1a[Id]': 'ID', '@N1a[Ispec]': 'Ispec', '@N1a[IC]': 'IC', '@N1a[n0]': 'n', '@N1a[Vdsat]': 'VDsat',
                            '@N1a[gm]': 'Gm', '@N1a[gms]': 'Gms', '@N1a[gds]': 'Gds', '@N1a[gmbs]': 'Gmb',
                            '@N1a[Rn]': 'Rn', '@N1a[Vnth]': 'Vnth', '@N1a[gamman]': 'gamman', '@N1a[Vnfl]': 'Vnfl at 1Hz'})
df_mos['Transistor'] = df_mos['Transistor'].astype(str)
df_mos.at[0, 'Transistor'] = 'M1a'
df_mos.at[1, 'Transistor'] = 'M1b'
df_mos.at[2, 'Transistor'] = 'M2a'
df_mos.at[3, 'Transistor'] = 'M2b'
df_mos.at[4, 'Transistor'] = 'M3a'
df_mos.at[5, 'Transistor'] = 'M3b'
df_mos.at[6, 'Transistor'] = 'M3c'
df_mos.at[7, 'Transistor'] = 'M3d'
df_mos.at[8, 'Transistor'] = 'M4a'
df_mos.at[9, 'Transistor'] = 'M4b'
df_mos.at[10, 'Transistor'] = 'M5a'
df_mos.at[11, 'Transistor'] = 'M5b'
df_mos.set_index('Transistor', inplace=True)
df_mos.rename_axis(index=None, inplace=True)
pd.set_option('display.float_format', '{:.2E}'.format)
#df_mos

feature_list=["ID","Ispec","IC","n","VDsat"]

Nfeature=len(feature_list)
table=[]
for mos in transistor_list:
  row=[mos]
  for feature in feature_list:
    row.append(df_mos.at[mos,feature])
  table.append(row)

for i in range(Nmos):
  for j in [1,2]:
    table[i][j]=table[i][j]/1e-9
  for j in [5]:
    table[i][j]=table[i][j]/1e-3

Markdown(tabulate(
  table,
  headers=["Transistor","$I_D\\;[nA]$","$I_{spec}\\;[nA]$","$IC$ [-]","$n$ [-]","$V_{DSsat}\\;[mV]$"],
  numalign="center",
  stralign="center",
  colalign=("center","center","center","center","center","center"),
  floatfmt=(".3f",".1f",".1f",".3f",".2f",".0f")
))
```

```{python}
#| label: tbl-ng_op2
#| tbl-cap: Small-signal operating point information extracted from ngspice .op file for each transistor.

feature_list=["Gm","Gms","Gds"]

Nfeature=len(feature_list)
table=[]
for mos in transistor_list:
  row=[mos]
  for feature in feature_list:
    row.append(df_mos.at[mos,feature])
  table.append(row)

for i in range(Nmos):
  for j in [1,2]:
    table[i][j]=table[i][j]/1e-6
  for j in [3]:
    table[i][j]=table[i][j]/1e-9

Markdown(tabulate(
  table,
  headers=["Transistor","$G_m\\;[\\mu A/V]$","$G_{ms}\\;[\\mu A/V]$","$G_{ds}\\;[nA/V]$"],
  numalign="center",
  stralign="center",
  colalign=("center","center","center","center"),
  floatfmt=(".3f",".3f",".3f",".3f")
))
```


```{python}
#| label: tbl-ng_op3
#| tbl-cap: Noise operating point information extracted from ngspice .op file for each transistor.

feature_list=["Rn","Vnth","gamman","Vnfl at 1Hz"]

Nfeature=len(feature_list)
table=[]
for mos in transistor_list:
  row=[mos]
  for feature in feature_list:
    row.append(df_mos.at[mos,feature])
  table.append(row)

for i in range(Nmos):
  for j in [1]:
    table[i][j]=table[i][j]/1e3
  for j in [2,4]:
    table[i][j]=table[i][j]/1e-9

Markdown(tabulate(
  table,
  headers=["Transistor","$R_n\\;[k\\Omega]$","$\\sqrt{S_{ID,th}}\\;[nA/\\sqrt{Hz}$","$\\gamma_n$ [-]","$\\sqrt{S_{ID,fl}}$ at 1Hz $[nA/\\sqrt{Hz}]$"],
  numalign="center",
  stralign="center",
  colalign=("center","center","center","center"),
  floatfmt=(".3f",".3f",".3f",".3f")
))
```


#### Large-signal differential transfer characteristic
We can now simulate the large-signal DC input-output transfer characteristic. The simulation result is presented in @fig-ng_large_signal.

```{python}
#| label: fig-ng_large_signal
#| fig-cap: "Simulated large-signal input-output characteristic."

simtype = ".dc"
fileName = otaName + simtype
simulationPath = "./Simulations/ngspice/Gm-enhanced/"
simulationFile = simulationPath + fileName + ".cir"
simulationLog = simulationPath + fileName + ".log"
simulationData = simulationPath + fileName + ".dat"
ngspiceDataFile = simulationPath + otaName + version + simtype + "1.dat"
ngspiceLogFile = simulationPath + otaName + version + simtype + "1.log"
qucsPath = "./Simulations/qucs-s/"


Vidmin=-0.2e-3
Vidmax=0.2e-3
Npts=1001
dVid=(Vidmax-Vidmin)/(Npts-1)

if newSim:
  simpar = '\n'.join((
    f'.csparam Vidmin={Vidmin:.3e}',
    f'.csparam Vidmax={Vidmax:.3e}',
    f'.csparam dVid={dVid:.3e}'
  ))
  simparFile = simulationPath + "simulation.dc.par"
  with open(simparFile, 'w') as f:
    f.write(simpar)
  #print('Starting NGSpice simulation...\n')
  result = subprocess.run(f"""ngspice_con -b -o "{simulationLog}" "{simulationFile}" """, shell=True, capture_output=True, text=True)
  # We need to copy the resulting dc simulation data file because it will be overwritten by the next dc simulation
  shutil.copy2(simulationData, ngspiceDataFile)
  # We also need to copy the dc simulation log file because it will be overwritten by the next dc simulation
  shutil.copy2(simulationLog, ngspiceLogFile)

df_sim=pd.read_table(ngspiceDataFile, sep=' +', engine='python')

Vidsim=df_sim['v-sweep'].to_numpy()
Voutsim=df_sim['Vout'].to_numpy()

regexp = re.compile(r'voutmax' + refloat)
with open(ngspiceLogFile) as f:
  for line in f:
    match = re.search(regexp, line)
    if match:
      Voutmax=float(match.group(1))

regexp = re.compile(r'voutmin' + refloat)
with open(ngspiceLogFile) as f:
  for line in f:
    match = re.search(regexp, line)
    if match:
      Voutmin=float(match.group(1))

regexp = re.compile(r'vos' + refloat)
with open(ngspiceLogFile) as f:
  for line in f:
    match = re.search(regexp, line)
    if match:
      Vos=float(match.group(1))

Voutq=0.9
Voutmax=1.4
Voutmin=0.4
Vswing=Voutmax-Voutmin

plt.plot([Vidmin/1e-6,0],[Voutq0,Voutq0],'k--', linewidth=lw)
plt.plot([0,0],[0,Voutq0],'k--', linewidth=lw)
plt.plot([Vidmin/1e-3,Vos/1e-3],[Voutq,Voutq],'k--', linewidth=lw)
plt.plot([Vos/1e-3,Vos/1e-3],[0,Voutq],'k--', linewidth=lw)
plt.plot([Vidmin/1e-3,Vidmax/1e-3],[Voutmax,Voutmax],'k--', linewidth=lw)
plt.plot([Vidmin/1e-3,Vidmax/1e-3],[Voutmin,Voutmin],'k--', linewidth=lw)
plt.plot(Vidsim/1e-3,Voutsim,'r')
plt.xlim(Vidmin/1e-3,Vidmax/1e-3)
#plt.xticks(np.arange(-0.4,0.5,0.1))
plt.xlabel('$V_{id}$ [$mV$]')
plt.ylim(0,1.8)
plt.yticks(np.arange(0,2,0.2))
plt.ylabel('$V_{out}$ [V]')
#plt.legend(loc='best')
textstr = '\n'.join((
  f'$V_{{DD}} =$ {VDD:.1f} V',
  f'$V_{{ic}} =$ {Vic:.1f} V',
  f'$V_{{out0}} =$ {Voutq0:.3f} V',
  f'$V_{{outq}} =$ {Voutq:.3f} V',
  f'$V_{{os}} =$ {Vos/1e-6:.3f} $\\mu\\,V$',
  f'$V_{{outmax}} \\cong$ {Voutmax:.3f} V',
  f'$V_{{outmin}} \\cong$ {Voutmin:.3f} V',
  f'$V_{{swing}} \\cong$ {Vswing:.2f} V'))
plt.text(1.05, 0.5, textstr, ha='left', va='center', size=textFontSize, transform=plt.gca().transAxes,
         bbox={'facecolor':'w', 'alpha':1.0, 'pad':5})
if savePlots: saveFigures(savePlotPath, otaName + '_transfer_characteristic_sim')
plt.show()
if newSim:
  paramstr = '\n'.join((
    f'.param VDD={VDD:.1f} Vic={Vic:.1f} Vos={Vos/1e-6:.1f}u',
    f'.param Ib={Ib/1e-9:.0f}n B={B:.0f}',
    f'.param CL={CL/1e-12:.0f}p',
    f'.param W1={W1/1e-6:.2f}u L1={L1/1e-6:.2f}u',
    f'.param W2={W2/1e-6:.2f}u L2={L2/1e-6:.2f}u',
    f'.param W3={W3/1e-6:.2f}u L3={L3/1e-6:.2f}u',
    f'.param W4={W4/1e-6:.2f}u L4={L4/1e-6:.2f}u',
    f'.param W5={W5/1e-6:.2f}u L5={L5/1e-6:.2f}u',
  ))
  # Write the transistors sizes and bias to the ngspice directory
  with open(simulationPath+'size_bias.par', 'w') as f:
    f.write(paramstr)
  # We also need to copy the dc simulation log file because it will be overwritten by the next dc simulation
  shutil.copy2(simulationPath+'size_bias.par', qucsPath+'size_bias.par')
```

We see from @fig-ng_large_signal that the quiescent output voltage is in the linear region. We nevertheless can impose the extracted offset voltage $V_{os} =$ `{python} f'{Vos/1e-6:.1f}'` $\mu V$ to center the output voltage at $V_{outq} = V_{DD}/2 =$ `{python} f'{Voutq:.1f}'` $V$. We also see that the OTA offers a rather large output voltage swing $V_{swing} =$ `{python} f'{Vswing:.1f}'` $V$.

#### Open-loop gain
After having checked the operating point information and making sure that the OTA output is not saturated and in the high gain region, we can now perform the AC simulation. The simulation results are compared to the theoretical estimations in @fig-ng_open_loop_gain.

```{python}
#| label: fig-ng_open_loop_gain
#| fig-cap: "Simulated gain response compared to theoretical estimation."

def Adm1(s,Adc,wp):
  return Adc/(1+s/wp)

def Adm2(s,Adc,tau0,taup4,taup5,taup6):
  return Adc/2/(1+s*tau0)*(1/(1+s*taup5)+1/((1+s*taup4)*(1+s*taup6)))

simtype = ".ac"
fileName = otaName + simtype
simulationPath = "./Simulations/ngspice/Gm-enhanced/"
simulationFile = simulationPath + fileName + ".cir"
simulationLog = simulationPath + fileName + ".log"
simulationData = simulationPath + fileName + ".dat"
ngspiceDataFile = simulationPath + otaName + version + simtype + ".dat"
ngspiceLogFile = simulationPath + otaName + version + simtype + ".log"

if newSim:
  #print('Starting NGSpice simulation...\n')
  result = subprocess.run(f"""ngspice_con -b -o "{simulationLog}" "{simulationFile}" """, shell=True, capture_output=True, text=True)
  # We need to copy the resulting dc simulation data file because it will be overwritten by the next dc simulation
  shutil.copy2(simulationData, ngspiceDataFile)
  # We also need to copy the dc simulation log file because it will be overwritten by the next dc simulation
  shutil.copy2(simulationLog, ngspiceLogFile)

df_sim=pd.read_table(ngspiceDataFile, sep=' +', engine='python')
fsim=df_sim['frequency'].to_numpy()
AmagdBsim=df_sim['AmagdB'].to_numpy()
Aphidegsim=df_sim['Aphdeg'].to_numpy()
Aphidegsim=np.unwrap(Aphidegsim)

Npts=len(fsim)
fmin=fsim[0]
fmax=fsim[Npts-1]

regexp = re.compile(r'adc' + refloat)
with open(ngspiceLogFile) as f:
  for line in f:
    match = re.match(regexp, line)
    if match:
      AdcdBsim=float(match.group(1))

regexp = re.compile(r'gbw' + refloat)
with open(ngspiceLogFile) as f:
  for line in f:
    match = re.match(regexp, line)
    if match:
      GBWsim=float(match.group(1))

regexp = re.compile(r'pgbw' + refloat)
with open(ngspiceLogFile) as f:
  for line in f:
    match = re.match(regexp, line)
    if match:
      PGBWsim=float(match.group(1))

regexp = re.compile(r'fp' + refloat)
with open(ngspiceLogFile) as f:
  for line in f:
    match = re.match(regexp, line)
    if match:
      fpsim=float(match.group(1))

PGBWsim=PGBWsim
PMsim=180+PGBWsim

Gm1=ota_df.at['M1a','Gm']
Gms1=ota_df.at['M1a','Gms']
Gm2=ota_df.at['M2a','Gm']
alpha=Gms1/Gm2
K=1/(alpha-1)
Gmeq=B*K*Gm1
Gds3d=ota_df.at['M3d','Gds']
Gds4b=ota_df.at['M4b','Gds']
Go=Gds3d+Gds4b
CBD3d=ota_df.at['M3d','CBD']
CGD3d=ota_df.at['M3d','CGD']
CGS3d=ota_df.at['M3d','CGS']
CGS3b=ota_df.at['M3b','CGS']
Cp3=CBD3d+1/(1/CGD3d+1/(CGS3d+CGS3b))
CBD4b=ota_df.at['M4b','CBD']
CGD4b=ota_df.at['M4b','CGD']
CGS4b=ota_df.at['M4b','CGS']
CGS4a=ota_df.at['M4a','CGS']
Cp4=CBD4b+1/(1/CGD4b+1/(CGS4a+CGS4b))
Cp=Cp3+Cp4
CLtot=CL+Cp
tau0=CLtot/Go
Gm3a=ota_df.at['M3a','Gm']
Cp4=ota_df.at['M3a','CGS']+ota_df.at['M3c','CGS']+ota_df.at['M3a','CGB']+ota_df.at['M3c','CGB']
taup4=Cp4/Gm3a
Gm3b=ota_df.at['M3b','Gm']
Cp5=ota_df.at['M3b','CGS']+ota_df.at['M3d','CGS']+ota_df.at['M3b','CGB']+ota_df.at['M3d','CGB']
taup5=Cp5/Gm3b
Gm4a=ota_df.at['M4a','Gm']
Cp6=ota_df.at['M4a','CGS']+ota_df.at['M4b','CGS']+ota_df.at['M4a','CGB']+ota_df.at['M4b','CGB']
taup6=Cp6/Gm4a
Adcthe=Gmeq/Go
AdcdBthe=20*log(abs(Adcthe))
wuthe=Gmeq/CLtot
GBWthe=wuthe/(2*pi)
wp=wuthe/Adcthe

Npts=101
logfmin=1
logfmax=8
fmin=pow(10,logfmin)
fmax=pow(10,logfmax)
fthe=np.logspace(logfmin,logfmax,Npts,endpoint=True,base=10.0)

AmagdBthe=np.zeros(Npts)
Aphiradthe=np.zeros(Npts)
Aphidegthe=np.zeros(Npts)
for k in range(0,Npts):
  jw=1j*2*pi*fthe[k]
#  AmagdBthe[k]=20*log(abs(Adm1(jw,Adcthe,wp)))
#  Aphiradthe[k]=np.angle(Adm1(jw,Adcthe,wp))
  AmagdBthe[k]=20*log(abs(Adm2(jw,Adcthe,tau0,taup4,taup5,taup6)))
  Aphiradthe[k]=np.angle(Adm2(jw,Adcthe,tau0,taup4,taup5,taup6))

#Aphidegthe=Aphiradthe*180/pi-180
Aphidegthe=np.unwrap(Aphiradthe)*180/pi

PGBWthe=np.angle(Adm2(1j*2*pi*GBWthe,Adcthe,tau0,taup4,taup5,taup6),deg=True)
PMthe=180+PGBWthe

plt.rcParams.update(BodePlotParams)
fig, axs = plt.subplots(2, sharex=True)

axs[0].semilogx([fmin,GBWsim],[0,0],'k--', linewidth=lw)
axs[0].semilogx([GBWsim,GBWsim],[0,-60],'k--', linewidth=lw)
axs[0].semilogx([fmin,fpsim],[AdcdBsim-3,AdcdBsim-3],'k--', linewidth=lw)
axs[0].semilogx([fpsim,fpsim],[-30,AdcdBsim-3],'k--', linewidth=lw)
axs[0].semilogx(fsim,AmagdBsim,'r',label='Simulation')
axs[0].semilogx(fthe,AmagdBthe,'r--',label='Theory')
axs[0].set(xlim=(fmin,fmax), ylim=(-20,80), ylabel='$|A_{vd}|$ [dB]')
#axs[0].set_yticks([-50,-40,-30,-20,-10,0,10,20,30,40,60,70])
axs[0].set_yticks(range(-40,100,20))
axs[0].legend(loc='upper right')
textstr1 = '\n'.join((
    f'$A_{{dc}} =$ {AdcdBthe:.1f} dB (theory)',
    f'$A_{{dc}} =$ {AdcdBsim:.1f} dB (sim.)',
    f'$GBW =$ {GBWthe/1e6:.1f} MHz (theory)',
    f'$GBW =$ {GBWsim/1e6:.1f} MHz (sim.)'))
axs[0].text(0.03, 0.07, textstr1, ha='left', va='bottom', transform=axs[0].transAxes, size=textFontSize,
         bbox={'facecolor':'w', 'alpha':1.0, 'pad':5})

axs[1].semilogx([fmin,GBWsim],[PGBWsim,PGBWsim],'k--', linewidth=lw)
axs[1].semilogx([GBWsim,GBWsim],[PGBWsim,-180],'k--', linewidth=lw)
axs[1].semilogx([fmin,fpsim],[-45,-45],'k--', linewidth=lw)
axs[1].semilogx([fpsim,fpsim],[-45,0],'k--', linewidth=lw)
axs[1].semilogx(fsim,Aphidegsim,'b',label='Simulation')
axs[1].semilogx(fthe,Aphidegthe,'b--',label='Theory')
axs[1].set(xlim=(fmin,fmax), ylim=(-180,0), xlabel='Frequency [Hz]', ylabel='$arg\\{A_{vd}\\}$ [degree]')
axs[1].set_xticks([1e1,1e2,1e3,1e4,1e5,1e6,1e7,1e8])
axs[1].set_yticks([-180,-135,-90,-45,0])
axs[1].legend(loc='lower left')
textstr2 = '\n'.join((
    f'$PM =$ {PMthe:.1f} deg (theory)',
    f'$PM =$ {PMsim:.1f} deg (sim.)'))
axs[1].text(0.62, 0.95, textstr2, ha='left', va='top', transform=axs[1].transAxes, size=textFontSize,
         bbox={'facecolor':'w', 'alpha':1.0, 'pad':5})
arrow = mpatches.FancyArrowPatch((GBWsim, PGBWsim), (GBWsim, -180),
                                 arrowstyle='<|-|>', color="black", mutation_scale=15)
axs[1].add_patch(arrow)
axs[1].annotate(f'Phase margin = {PMsim:.0f} deg', (0, 0.5), xycoords=arrow, ha='right', va='center', size=textFontSize)
plt.subplots_adjust(hspace=0.1)
if savePlots: saveFigures(savePlotPath, otaName + '_open_loop_gain_sim')
plt.show()
plt.style.use(pltStyle)
```

From @fig-ng_open_loop_gain, we see that the simulated gain-bandwidth product is right on target and larger than the theoretical estimation. The simulated DC gain is slightly smaller than the specs, but larger than the theoretical estimation.

## Conclusion
The OTA of @fig-Gm_enhanced_ota_symmetric shows that the DC gain and gain-bandwidth product can both be increased without spending more current thanks to the cross-coupled pair M~2a~-M~2b~ @bib:castello:jssc:25:3:1990. However, this achievement comes at the cost of a larger voltage required to bias the input transconductor M~1a~-M~1b~ and M~2a~-M~2b~. Indeed, the minimum supply voltage is now increased by the $V_{GS}$ voltage of M~2a~-M~2b~ which is typically $V_{T0n} =$ `{python} f'{VT0n/1e-3:.0f}'` $mV$. In addition, the OTA of @fig-Gm_enhanced_ota_symmetric shows more noise because the cros-coupled pair contributes $n_1\,n_2$-times more than the differential pair. For $n_1 = n_2=$ `{python} f'{n0n:.2f}'` the cross-coupled pair contributes `{python} f'{n0n**2:.2f}'`-times more than the differential pair, which is significant. We also have to remember that the $G_m$-enhancement is directly related to transconductance ratio $\alpha = G_{ms1}/G_{m2}$ which depend on the current ratio $I_{D1}/I_{D2}$. This current ratio might not be well controlled and subject to variability and mismatch. Note that taking $I_{D1}=I_{D2}$ would result in $\alpha \cong 1$ which could lead to instability. 

# Conclusion
In the exercise we have investigated two techniques to boost the gain and bandwidth of an OTA. They are both based on the use of a crsoss-coupled pair either at the drain for gain-enhancement or at the source for $G_m$-enhancement. Although these techniques look attarctive at first glance they don't really provide more performance compared to other techniques like simple cascode. Indeed cascode or its improved versions regulated cascode and gain-boosting turn out to be the simplest and most efficient gain-enhancement techniques @bib:sackinger:jssc:25:1:1990 @bib:bult:jssc:25:6:dec:1990 @bib:bult:aicsp:1991.

# References