Skip to content

Commit

Permalink
Adding first draft for current mirror section
Browse files Browse the repository at this point in the history
  • Loading branch information
hpretl committed Aug 21, 2024
1 parent 07bc091 commit 98e3d70
Show file tree
Hide file tree
Showing 7 changed files with 642 additions and 0 deletions.
26 changes: 26 additions & 0 deletions _sec_current_mirror.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
In this section we will look into a fundamental building block which is often used in integrated circuit design, the current mirror. A diagram is shown in @fig-current-mirror with one MOSFET diode converting the incoming bias current into a voltage, and two output MOSFETs working as current sources, which are biased from the diode. By properly selecting all $W$ and $L$ the input current can be scaled, and multiple copies can be created atn once. Shown in the figure are two output currents, but any number of parallel branches can be realized.

{{< include figures/_fig_current_mirror.qmd >}}

The output current $I_\mathrm{out1}$ is then given by
$$
I_\mathrm{out1} = I_\mathrm{bias} \frac{W_2}{L_2} \frac{L_1}{W_1}
$$
and the output current $I_\mathrm{out2}$ is given by
$$
I_\mathrm{out2} = I_\mathrm{bias} \frac{W_3}{L_3} \frac{L_1}{W_1}.
$$

For good matching in layout care has to be taken that the MOSFET widths and lengths are constructed out of **unit elements** of identical size, where an appropiate amount of these single units are then arranged in series or parallel configuration to arrive at the target $W$ and $L$.

As we know from earlier investigations of the MOSFET performance in @sec-gmid-method the drain current of a MOSFET is a function of $\VGS$ and $\VDS$. As long as the MOSFET stays in saturation (i.e., $\VDS > V_\mathrm{ds,dsat}$) the drain current is just a mild function of $\VDS$ (essentially the effect of $\gds$, which is the output conductance of the MOSFET). A fundamental flaw of the basic current mirror shown in @fig-current-mirror is the mismatch of the $\VDS$ of the MOSFET. The input-side diode has $\VGS = \VDS$, whereas the output current sources have a $\VDS$ depending on the connected circuitry. Improved current mirrors exist (basically fixing this flaw), still, when just a simple current mirror is required this structure is used for its simplicity.

::: {.callout-tip title="Exercise"}
Please construct a current mirror based on the MOSFET-diode which we sized in @sec-mosfet-diode. The input current $I_\mathrm{bias} = 20\,\mu\text{A}$, and we want three output currents of size $10\,\mu\text{A}$, $20\,\mu\text{A}$, and $40\,\mu\text{A}$.

Sweep the output voltage of all three current branches and see over which voltage range an acceptable current is created. For which output voltage range is the current departing from its ideal value, and why?

You see that the slope of the output current is quite bad, as $\gds$ is too large. We can improve this by changing the length to $L = 5\,\mu\text{m}$ (for motivation, please look at the graphs in @sec-gmid-method). In addition, for a current mirror we are not interested in a high $\gmid$ value, so we can use $\gmid = 5$ in this case. Please size the current mirror MOSFETs accordinly (please round the $W$ to half micron, to keep sizes a bit more practical). Compare this result to the previous one, what changed?

In case you get stuck, here are Xschem schematics for the [original](./xschem/current_mirror.sch) and the [improved](./xschem/current_mirror_improved.sch) current mirrors.
:::
33 changes: 33 additions & 0 deletions figures/_fig_current_mirror.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
```{python}
#| label: fig-current-mirror
#| echo: false
#| fig-cap: "A current mirror with two output branches."
import schemdraw as sd
import schemdraw.elements as elm
with sd.Drawing(canvas='svg') as d:
d.config(unit=2)
d.config(fontsize=16)
elm.Vdd()
elm.SourceI().down().label('Ibias')
elm.Dot()
M1 = elm.AnalogNFet(offset_gate=False).drop('source').theta(0).label('W1/L1')
elm.Ground()
elm.Line().right().at(M1.gate).length(0.5).dot()
elm.Line().up().toy(M1.drain).dot()
d.push()
elm.Line().left().to(M1.drain)
elm.Line().right().at(M1.gate).length(1.5)
M2 = elm.AnalogNFet(offset_gate=False).anchor('gate').theta(0).reverse().label('W2/L2', ofst=-2)
elm.Ground()
elm.Line().at(M2.drain).up().length(1).dot(open=True).label('Iout1', 'right')
d.pop()
elm.Line().right().length(3.5)
elm.Line().down().toy(M1.gate)
elm.Line().right().length(1)
d.push()
M3 = elm.AnalogNFet(offset_gate=False).anchor('gate').theta(0).reverse().label('W3/L3', ofst=-2)
elm.Ground()
elm.Line().at(M3.drain).up().length(1).dot(open=True).label('Iout2', 'right')
```
178 changes: 178 additions & 0 deletions sizing/sizing_current_mirror.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Sizing for Improved Current Mirror \n",
"\n",
"**Copyright 2024 Harald Pretl**\n",
"\n",
"Licensed under the Apache License, Version 2.0 (the \"License\");\n",
"you may not use this file except in compliance with the License.\n",
"You may obtain a copy of the License at\n",
"http://www.apache.org/licenses/LICENSE-2.0\n",
"\n",
"_Important note: Numpy version 1.x.x is required; an error is thrown for Numpy 2.x.x_"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"# Read table data\n",
"from pygmid import Lookup as lk\n",
"import numpy as np\n",
"lv_nmos = lk('sg13_lv_nmos.mat')\n",
"lv_pmos = lk('sg13_lv_pmos.mat')\n",
"# List of parameters: VGS, VDS, VSB, L, W, NFING, ID, VT, GM, GMB, GDS, CGG, CGB, CGD, CGS, CDD, CSS, STH, SFL\n",
"# If not specified, minimum L, VDS=max(vgs)/2=0.9 and VSB=0 are used "
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"# Define the given values\n",
"id_spec = 20e-6\n",
"gm_id_spec = 5\n",
"L_spec = 5"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"gm = 0.1 mS\n"
]
}
],
"source": [
"# We can calculate the gm directly\n",
"gm = gm_id_spec * id_spec\n",
"print('gm =', gm/1e-3, 'mS')"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"gds = 1.8605125381715726 uS\n"
]
}
],
"source": [
"# The gm_gds we look up and calculate gds from it\n",
"gm_gds = lv_nmos.lookup('GM_GDS', GM_ID=gm_id_spec, L=L_spec, VDS=0.75, VSB=0)\n",
"gds = gm / gm_gds\n",
"print('gds =', gds/1e-6, 'uS')"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Cgg = 152.09963774170419 fF\n",
"f_T = 104.63860759627349 MHz\n"
]
}
],
"source": [
"# Find f_T (which is not stored directly, but we can find the gm to gate capacitance ratio)\n",
"gm_cgg = lv_nmos.lookup('GM_CGG', GM_ID=gm_id_spec, L=L_spec, VDS=0.75, VSB=0)\n",
"f_T = gm_cgg / (2*np.pi)\n",
"print('Cgg =', gm/gm_cgg/1e-15, 'fF')\n",
"print('f_T =', f_T/1e6, 'MHz')\n"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"W = 3.6516417714659495 um, rounded W = 3.5 um\n"
]
}
],
"source": [
"# Find the W of the diode transistor\n",
"id_w = lv_nmos.lookup('ID_W', GM_ID=gm_id_spec, L=L_spec, VDS=0.75, VSB=0)\n",
"w = id_spec / id_w\n",
"print('W =', w, 'um, rounded W =', round(w*2)/2, 'um')"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"V_GS = 0.5912200307058603 V\n",
"gamma = 1.0946142921484647\n",
"f_co = 0.1269030886927012 MHz\n"
]
}
],
"source": [
"# Let's now find the other interesting values\n",
"vgs = lv_nmos.look_upVGS(GM_ID=gm_id_spec, L=L_spec, VDS=0.75, VSB=0.0)\n",
"sth = lv_nmos.lookup('STH', VGS=vgs, L=L_spec, VDS=0.75, VSB=0)\n",
"sfl = lv_nmos.lookup('SFL', VGS=vgs, L=L_spec, VDS=0.75, VSB=0)\n",
"\n",
"gamma = (sth**2)/(4*1.38e-23*300*gm)\n",
"\n",
"f_co = sfl**2/sth**2\n",
"print('V_GS =', vgs, 'V')\n",
"print('gamma =', gamma)\n",
"print('f_co =', f_co/1e6, 'MHz')"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.5"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
Binary file added xschem/current_mirror.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 98e3d70

Please sign in to comment.