Source code for ahkab.mosq

# -*- coding: iso-8859-1 -*-
# mosq.py
# Implementation of the square-law MOS transistor model
# Copyright 2012 Giuseppe Venturini
#
# This file is part of the ahkab simulator.
#
# Ahkab is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 2 of the License.
#
# Ahkab is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License v2
# along with ahkab.  If not, see <http://www.gnu.org/licenses/>.

"""
The Square Law Mos Model
------------------------

This module defines two classes:

- :class:`mosq_device`, the device
- :class:`mosq_model`, the model


Implementation details
----------------------

Assuming :math:`V_{ds} > 0` and a transistor type N in the
following, we have the following regions implemented:

1. No subthreshold conduction.
       - :math:`V_{gs} < V_T`
       - :math:`I_D = 0`
2. Ohmic region
       - :math:`V_{GS} > V_T` and :math:`V_{GD} > V_T`
       - :math:`I_D = k_n W/L ((V_{GS}-V_{T})V_{DS} - V_{DS}^2/2)`
3. Saturation region
       - :math:`V_{GS} > V_T` and :math:`V_{DS} > V_{GS} - V_{T}`
       - :math:`V_{GS} < V_{T}`
       - :math:`I_D = 1/2 k_n W/L (V_{GS}-V_T)^2 * [1 + \lambda*(V_{DS}-V_{GS}+V_T)]`

Module reference
----------------

"""
from __future__ import (unicode_literals, absolute_import,
                        division, print_function)

import math
import numpy as np

from . import constants
from . import options
from . import utilities
from . import printing

# DEFAULT VALUES FOR 500n CH LENGTH
COX_DEFAULT = .7e-3
VTO_DEFAULT = .5
GAMMA_DEFAULT = 1.0
PHI_DEFAULT = .7
KP_DEFAULT = 50e-6
LAMBDA_DEFAULT = .5
AVT_DEFAULT = 7.1e-3 * 1e-6
AKP_DEFAULT = 1.8e-2 * 1e-6

TCV_DEFAULT = 1e-3
BEX_DEFAULT = -1.5

ISMALL_GUESS_MIN = 1e-10


[docs]class mosq_device(object): def __init__(self, part_id, nd, ng, ns, nb, W, L, model, M=1, N=1): """Quadratic Law MOSFET device **Parameters:** part_id : string The part ID of the model. Eg. ``'M1'`` or ``'Mlow'``, the first letter should always be ``'M'``. nd : int drain node ng : int gate node ns : int source node nb : int bulk node L : float element width [m] W : float element length [m] model : mosq_mos_model instance the model for the device M : int, optional shunt multiplier (n. of shunt devices) N : int, optional series multiplier (n. of series devices) """ self.ng = ng self.nb = nb self.n1 = nd self.n2 = ns self.ports = ((self.n1, self.n2), ( self.ng, self.n2), (self.nb, self.n2)) class dev_class(object): pass # empty class to hold device parameters self.device = dev_class() self.device.L = float(L) # channel length - self.device.W = float(W) # channel width - self.device.M = int(M) # parallel multiple device number self.device.N = int(N) # series multiple device number self.device.mckey = None self.device.part_id = part_id self.mosq_model = model self.mc_enabled = False self.opdict = {} self.opdict.update( {'state':(float('nan'), float('nan'), float('nan'))}) self.part_id = part_id self.is_nonlinear = True self.is_symbolic = True self.dc_guess = [self.mosq_model.VTO*0.4*self.mosq_model.NPMOS, self.mosq_model.VTO*1.1*self.mosq_model.NPMOS, 0] devcheck, reason = self.mosq_model.device_check(self.device) if not devcheck: raise ValueError(reason + " out of boundaries.")
[docs] def get_drive_ports(self, op): """Get the circuit ports that drive the device. **Returns:** tp : a tuple of tuples of nodes, each node being a drive port of the device. Eg. ``tp`` might be defined as: :: tp = (port0, port1, port2...) Where each port in the tuple is of the form: :: port0 = (nplus, nminus) In the case of a MOSQ device, the method returns: :: ((nd, nb), (ng, nb), (ns, nb)) Where: * ``nd`` is the internal identifier of the drain node, * ``ng`` is the internal identifier of the gate node, * ``ns`` is the internal identifier of the source node. * ``nb`` is the internal identifier of the bulk node, """ return self.ports # d,g,b
[docs] def get_output_ports(self): """Get the circuit ports where the device injects current. **Returns:** ports : a tuple of tuples of nodes, such as as: :: (port0, port1, port2...) Where each port in the tuple is itself a tuple, made of two nodes, eg. :: port0 = (nplus, nminus) In the case of a MOS device, the method returns: :: ((nd, ns),) Where: * ``nd`` is the internal identifier of the drain node, * ``ns`` is the internal identifier of the source node. """ return ((self.n1, self.n2),)
def __str__(self): rep = self.part_id + " %(nd)s %(ng)s %(ns)s %(nb)%s " + \ self.mosq_model.name + " w=" + str(self.device.W) + " l=" + \ str(self.device.L) + " M=" + str(self.device.M) + " N=" + \ str(self.device.N) return rep def _get_mos_type(self): """Returns N or P (capitalized), depending on the device type. """ mtype = 'N' if self.mosq_model.NPMOS == 1 else 'P' return mtype
[docs] def istamp(self, ports_v, time=0, reduced=True): """Get the current stamp matrix A stamp matrix corresponding to the current flowing in the element with the voltages applied as specified in the ``ports_v`` vector. **Parameters:** ports_v : list A list in the form: ``[voltage_across_port0, voltage_across_port1, ...]`` time: float the simulation time at which the evaluation is performed. It has no effect here. Set it to ``None`` during DC analysis. """ sw_vect, CS = self.mosq_model.get_voltages(*ports_v) ids = self.mosq_model.get_ids(self.mosq_model, self.device, sw_vect) istamp = np.array((CS*ids, -CS*ids), dtype=np.float64) indices = ((self.n1 - 1*reduced, self.n2 - 1*reduced), (0, 0)) if reduced: delete_i = [pos for pos, i in enumerate(indices[0]) if i == -1] istamp = np.delete(istamp, delete_i, axis=0) indices = tuple(zip(*[(i, j) for i, j in zip(*indices) if i != -1])) return indices, istamp
[docs] def update_status_dictionary(self, ports_v): """Update the status dictionary The status dictionary may be accessed at ``elem.opdict`` and contains several pieces of information that may be of interest regarding the biasing of the MOS device. """ if self.opdict is None: self.opdict = {} if not (self.opdict['state'] == ports_v[0]).all() or 'gmd' not in self.opdict \ or 'gm' not in self.opdict or 'gmb' not in self.opdict \ or 'Ids' not in self.opdict or 'SAT' not in self.opdict: vds, vgs, _ = ports_v[0] self.opdict['state'] = ports_v[0] gstamp = self.gstamp(ports_v[0], reduced=False)[1] self.opdict['gmd'] = gstamp[0, 0] self.opdict['gm'] = gstamp[0, 1] self.opdict['gmb'] = gstamp[0, 3] self.opdict['Ids'] = self.istamp(ports_v[0], reduced=False)[1][0] self.opdict.update({'VTH':self.mosq_model.get_VT(ports_v[0], self.device)}) self.opdict.update({'W':self.device.W, 'L':self.device.L, 'ON':(vgs >= self.opdict['VTH'])}) self.opdict.update({'beta':.5*self.mosq_model.KP*self.device.W/self.device.L}) self.opdict.update({'VOD':self.mosq_model.NPMOS*(vgs - self.opdict['VTH']), 'SAT':vds > vgs - self.opdict['VTH']}) else: pass
#already up to date
[docs] def get_op_info(self, ports_v): """Information regarding the Operating Point (OP) **Parameters:** ports_v : list of lists The voltages applied to all the driving ports, grouped by output port. i.e. :: [<list of voltages for the drive ports of output port 0>, <list of voltages for the drive ports of output port 1>, ..., <list of voltages for the drive ports of output port N>] Usually, this method returns ``op_keys`` and the corresponding ``op_info``, two lists, one holding the labels, the other the corresponding values. In the case of MOSFETs, the values are way too many to be shown in a linear table. For this reason, we return ``None`` as ``op_keys``, and we return for ``op_info`` a list which holds both labels and values in a table-like manner, spanning the vertical and horizontal dimension. For this reason, each MOSFET has to have its OP info printed alone, not grouped as it happens with most other elements. **Returns:** op_keys : ``None`` See above for why this value is always ``None``. op_info : list of floats The OP information ready to be passed to :func:`printing.table` for arranging it in a pretty table to display. """ self.update_status_dictionary(ports_v) sat_status = "SATURATION" if self.opdict['SAT'] else "LINEAR" if not self.opdict["ON"]: status = "OFF" else: status = "ON" arr = [[self.part_id + " ch", status, "", "", sat_status, "", "", "", "", "", "", ""], ] arr.append(["beta", "[A/V^2]:", self.opdict['beta'], "Weff", "[m]:", str(self.opdict['W']) + " (" + str(self.device.W) + ")", "L", "[m]:", str(self.opdict['L']) + " (" + str(self.device.L) + ")", "M/N:", "", str(self.device.M) + "/" + str(self.device.N)]) arr.append(["Vds", "[V]:", float(ports_v[0][0]), "Vgs", "[V]:", float(ports_v[0][1]), "Vbs", "[V]:", float(ports_v[0][2]), "", "", ""]) arr.append(["VTH", "[V]:", self.opdict['VTH'], "VOD", "[V]:", self.opdict['VOD'], "", "", "", "VA", "[V]:", str(self.opdict['Ids'] / self.opdict['gmd'])]) arr.append(["Ids", "[A]:", self.opdict['Ids'], "", "", "", "", "", "", "", "", '']) arr.append(["gm", "[S]:", self.opdict['gm'], "gmb", "[S]:", self.opdict['gmb'], "ro", u"[\u2126]:", 1./self.opdict['gmd'], "", "", ""]) return None, arr
[docs] def gstamp(self, ports_v, time=0, reduced=True): """Get the transconductance stamp matrix **Parameters:** ports_v : sequence a sequence of the form: ``[voltage_across_port0, voltage_across_port1, ...]`` time : float, optional the simulation time at which the evaluation is performed. Set it to ``None`` during DC analysis. Defaults to 0. reduced : bool, optional Whether the returned matrix should be in reduced form or not. Defaults to ``True``, corresponding to reduced form. **Returns:** indices : sequence of sequences The indices corresponding to the stamp matrix. stamp : ndarray The stamp matrix. """ indices = ([self.n1 - 1]*4 + [self.ng - 1]*4 + [self.n2 - 1]*4 + [self.nb - 1]*4, [self.n1 - 1, self.ng - 1, self.n2 - 1, self.nb - 1]*4) sw_vect, CS = self.mosq_model.get_voltages(*ports_v) gmd = self.mosq_model.get_gmd(self.mosq_model, self.device, sw_vect) gmg = self.mosq_model.get_gm(self.mosq_model, self.device, sw_vect) gmb = self.mosq_model.get_gmb(self.mosq_model, self.device, sw_vect) if gmd == 0: gmd = options.gmin*2 if gmg == 0: gmg = options.gmin*2 if gmb == 0: gmb = -2*options.gmin stamp = np.array(((gmd, gmg, -gmd-gmb-gmg, gmb), (0, 0, 0, 0), (-gmd, -gmg, gmd + gmg + gmb, -gmb), (0, 0, 0, 0)), dtype=np.float64) if CS == -1: stamp = self.mosq_model.T1*stamp*self.mosq_model.T2 if (self.opdict['state'] != ports_v[0]).any(): self.opdict = {'state':ports_v[0]} self.opdict.update({'gmd': stamp[0, 0]}) self.opdict.update({'gm': stamp[0, 1]}) self.opdict.update({'gmb': stamp[0, 3]}) if reduced: zap_rc = [pos for pos, i in enumerate(indices[1][:4]) if i == -1] stamp = np.delete(stamp, zap_rc, axis=0) stamp = np.delete(stamp, zap_rc, axis=1) indices = tuple(zip(*[(i, y) for i, y in zip(*indices) if (i != -1 and y != -1)])) stamp_flat = stamp.reshape(-1) stamp_folded = [] indices_folded = [] for ix, it in enumerate([(i, y) for i, y in zip(*indices)]): if it not in indices_folded: indices_folded.append(it) stamp_folded.append(stamp_flat[ix]) else: w = indices_folded.index(it) stamp_folded[w] += stamp_flat[ix] indices = tuple(zip(*indices_folded)) stamp = np.array(stamp_folded) return indices, stamp
[docs] def get_value_function(self, identifier): def get_value(self): return self.opdict[identifier] return get_value
[docs] def get_mc_requirements(self): return True, 2
[docs] def setup_mc(self, status, mckey): self.mc_enabled = status if self.mc_enabled: self.device.mckey = mckey else: self.device.mckey = None
[docs] def get_netlist_elem_line(self, nodes_dict): """Get the element netlist entry""" mos_type = self._get_mos_type() return "%s %s %s %s %s %s type=%s w=%g l=%g m=%g n=%g" % \ (self.part_id, nodes_dict[self.n1], nodes_dict[self.ng], nodes_dict[self.n2], nodes_dict[self.nb], self.mosq_model.name, mos_type, self.device.W, self.device.L, self.device.M, self.device.N)
[docs]class mosq_mos_model(object): def __init__(self, name=None, TYPE='n', TNOM=None, COX=None, GAMMA=None, NSUB=None, PHI=None, VTO=None, KP=None, LAMBDA=None, AKP=None, AVT=None, TOX=None, VFB=None, U0=None, TCV=None, BEX=None): self.name = "model_mosq0" if name is None else name self.TNOM = float(TNOM) if TNOM is not None else constants.Tref # print "TYPE IS:" + TYPE if TYPE.lower() == 'n': self.NPMOS = 1 elif TYPE.lower() == 'p': self.NPMOS = -1 else: raise ValueError("Unknown MOS type %s" % TYPE) # optional parameters (no defaults) self.TOX = float(TOX) if TOX is not None else None self.NSUB = float(NSUB) if NSUB is not None else None self.VFB = self.NPMOS * float(VFB) if VFB is not None else None self.U0 = float(U0) if U0 is not None else None # crucial parameters if COX is not None: self.COX = float(COX) elif TOX is not None: self.COX = constants.si.eox / TOX else: self.COX = COX_DEFAULT if GAMMA is not None: self.GAMMA = float(GAMMA) elif NSUB is not None: self.GAMMA = math.sqrt( 2 * constants.e * constants.si.esi * NSUB * 10 ** 6 / self.COX) else: self.GAMMA = GAMMA_DEFAULT if PHI is not None: self.PHI = float(PHI) elif NSUB is not None: self.PHI = 2 * constants.Vth(self.TNOM) * math.log( NSUB * 10 ** 6 / constants.si.ni(self.TNOM)) else: self.PHI = PHI_DEFAULT if VTO is not None: self.VTO = self.NPMOS * float(VTO) if self.VTO < 0: print("(W): model %s has internal negative VTO (%f V)." % (self.name, self.VTO)) elif VFB is not None: self.VTO = VFB + PHI + GAMMA * PHI # inv here?? else: self.VTO = VTO_DEFAULT if KP is not None: self.KP = float(KP) elif U0 is not None: self.KP = (U0 * 10 ** -4) * self.COX else: self.KP = KP_DEFAULT self.LAMBDA = LAMBDA if LAMBDA is not None else LAMBDA_DEFAULT # Intrinsic model temperature parameters self.TCV = self.NPMOS * \ float(TCV) if TCV is not None else self.NPMOS * TCV_DEFAULT self.BEX = float(BEX) if BEX is not None else BEX_DEFAULT # Monte carlo self.AVT = AVT if AVT is not None else AVT_DEFAULT self.AKP = AKP if AKP is not None else AKP_DEFAULT self.set_device_temperature(constants.T) sc, sc_reason = self._self_check() if not sc: raise Exception(sc_reason + " out of range") self.T1 = np.array(((0, 0, 1, 0), (0, 1, 0, 0), (1, 0, 0, 0), (0, 0, 0, 1))) self.T2 = np.array(((0, 0, 1, 0), (0, 1, 0, 0), (1, 0, 0, 0), (0, 0, 0, 1)))
[docs] def set_device_temperature(self, T): """Change the temperature of the device. Correspondingly, ``VTO``, ``KP`` and ``PHI`` get updated. """ self.TEMP = T self.VTO = self.VTO - self.TCV * (T - self.TNOM) self.KP = self.KP * (T / self.TNOM) ** self.BEX self.PHI = (self.PHI * T / self.TNOM + 3 * constants.Vth(self.TNOM) * math.log(T / self.TNOM) - constants.si.Eg(self.TNOM) * T / self.TNOM + constants.si.Eg(T))
[docs] def get_device_temperature(self): """Returns the temperature of the device - in K. """ return self.TEMP
[docs] def print_model(self): """Print out the model All the internal parameters of the model get printed out, for visual inspection. Notice some can be set to ``None`` (ie not available) if they were not provided and some of those not provided are calculated from the others. """ arr = [] TYPE = 'N' if self.NPMOS == 1 else "P" arr.append([self.name, "", "", TYPE + " MOS", "SQUARE MODEL", "", "", "", "", "", "", ""]) arr.append(["KP", "[A/V^2]", self.KP, "VTO", "[V]:", self.VTO, "TOX", "[m]", self.TOX, "COX", "[F/m^2]:", self.COX]) arr.append(["PHI", "[V]:", self.PHI, "GAMMA", "sqrt(V)", self.GAMMA, "NSUB", "[cm^-3]", self.NSUB, "VFB", "[V]:", self.VFB]) arr.append(["U0", "[cm^2/(V*s)]:", self.U0, "TCV", "[V/K]", self.TCV, "BEX", "", self.BEX, "", "", ""]) print(printing.table(arr))
[docs] def get_voltages(self, vds, vgs, vbs): """Performs the D <-> S swap if needed. **Returns:** voltages : tuple A tuple containing ``(VDS, VGS, VBS)`` after the swap CS : int ``CS`` is an integer which equals to: * +1 if no swap was necessary, * -1 if VD and VS have been swapped. """ # vd / vs swap vds = float(vds) vgs = float(vgs) vbs = float(vbs) vds = vds * self.NPMOS vgs = vgs * self.NPMOS vbs = vbs * self.NPMOS if vds < 0: vds_new = -vds vgs_new = vgs - vds vbs_new = vbs - vds cs = -1 else: vds_new = vds vgs_new = vgs vbs_new = vbs cs = +1 # print ((float(vds_new), float(vgs_new), float(vbs_new)), cs) return (float(vds_new), float(vgs_new), float(vbs_new)), cs
[docs] def get_svt_skp(self, device, debug=False): if device.mckey and debug: print("Monte carlo enabled. key:", device.mckey) if device.mckey: svt = device.mckey[0] * self.AVT / math.sqrt( 2 * device.W * device.L) skp = device.mckey[1] * self.AKP / math.sqrt( 2 * device.W * device.L) else: svt, skp = 0, 0 return svt, skp
[docs] def get_VT(self, voltages, device): """Get the threshold voltage""" #vds, vgs, vbs = voltages _, _, vbs = voltages #(_, _, vbs), CS = self.get_voltages(*voltages) vsqrt1 = max(-vbs + 2*self.PHI, 0.) vsqrt2 = max(2*self.PHI, 0.) svt, _ = self.get_svt_skp(device) VT = self.VTO + svt + self.GAMMA * (math.sqrt(vsqrt1) - math.sqrt(vsqrt2)) return VT
@utilities.memoize
[docs] def get_ids(self, device, voltages): """Get the drain-source current **Parameters:** device : object The device object holding the device parameters as attributes. voltages : tuple A tuple containing the voltages applied to the driving ports. In this case, the tuple is ``(vds, vgs, vbs)``. **Returns:** ids : float The drain-source current """ """ Returns: IDS, the drain-to-source current """ (vds, vgs, vbs) = voltages debug = False if debug: print("=== %s (%sch) current for vds: %g, vgs: %g, vbs: %g" \ % (device.part_id, 'n'*(self.NPMOS == 1) + 'p'*(self.NPMOS == -1), vds, vgs, vbs)) if debug: print("PHI:", self.PHI, "vbs:", vbs) VT = self.get_VT((vds, vgs, vbs), device) _, skp = self.get_svt_skp(device) if vgs < VT: ids = options.iea * (vgs / VT + vds / VT) / 100 if debug: print("OFF: %g" % ids) else: if vds < vgs - VT -0.5*self.LAMBDA*(VT - vgs)**2: ids = (skp + 1) * self.KP * device.W / \ device.L * ((vgs - VT) * vds - .5 * vds ** 2) if debug: print("OHMIC: %g" % ids) else: ids = (skp + 1) * .5 * self.KP * device.W / device.L * ( vgs - VT) ** 2 * (1 + self.LAMBDA * (vds - vgs + VT + 0.25*self.LAMBDA*(VT - vgs)**2)) if debug: print("SAT: %g" % ids) Ids = self.NPMOS * device.M / device.N * ids return Ids
@utilities.memoize
[docs] def get_gmb(self, device, voltages): """Get the bulk-source transconductance Mathematically: .. math:: g_{mb} = \\frac{dI_{DS}}{d(VS-VB)} **Parameters:** device : object The device object holding the device parameters as attributes. voltages : tuple A tuple containing the voltages applied to the driving ports. In this case, the tuple is ``(vds, vgs, vbs)``. **Returns:** gmb : float The source-bulk transconductace. """ (vds, vgs, vbs) = voltages debug = False svt, skp = self.get_svt_skp(device, debug=False) assert vds >= 0 vsqrt1 = max(-vbs + 2*self.PHI, 0.) vsqrt2 = max(2*self.PHI, 0.) VT = self.VTO + svt + self.GAMMA * \ (math.sqrt(vsqrt1) - math.sqrt(vsqrt2)) gmb = 0 if vgs < VT: pass # gmb = 0 else: if vds < vgs - VT: if vsqrt1 > 0: gmb = self.KP * self.GAMMA * vds * device.W / \ (2 * device.L * vsqrt1 ** .5) else: if vsqrt1 > 0: gmb += -0.25*self.KP*self.GAMMA*self.LAMBDA*device.W * (vsqrt1 > 0) * \ (-self.GAMMA*(-vsqrt2**.5 + vsqrt1**.5) + vgs - self.VTO)**2 / \ (device.L * vsqrt1**.5) gmb += +0.5*self.KP*self.GAMMA*device.W*(self.LAMBDA* \ (self.GAMMA * (vsqrt2**.5 + vsqrt1**.5) + vds - vgs + self.VTO) + 1.0) *\ (-self.GAMMA * (vsqrt2**.5 + vsqrt1**.5) \ + vgs - self.VTO) / (device.L * vsqrt1**.5) gmb = self.NPMOS * (1 + skp) * gmb * device.M / device.N if debug: print("gmb %g" % gmb) return gmb
@utilities.memoize
[docs] def get_gmd(self, device, voltages): """Get the drain-source transconductance Mathematically: .. math:: g_{md} = \\frac{dI_{DS}}{d(VD-VS)} **Parameters:** device : object The device object holding the device parameters as attributes. voltages : tuple A tuple containing the voltages applied to the driving ports. In this case, the tuple is ``(vds, vgs, vbs)``. **Returns:** gmb : float The drain-source transconductace. """ (vds, vgs, vbs) = voltages debug = False svt, skp = self.get_svt_skp(device, debug=False) assert vds >= 0 vsqrt1 = max(-vbs + 2*self.PHI, 0.) vsqrt2 = max(2*self.PHI, 0.) VT = self.VTO + svt + self.GAMMA * \ (math.sqrt(vsqrt1) - math.sqrt(vsqrt2)) if vgs < VT: gmd = options.iea / VT / 100 else: if vds < vgs -VT -0.5*self.LAMBDA*(VT - vgs)**2: # correction term disc. due to LAMBDA gmd = self.KP * device.W / device.L * (vgs - vds - VT) else: gmd = 0.5 * self.KP * self.LAMBDA * device.W / device.L * \ (vgs - VT)**2 gmd = (1 + skp) * gmd * device.M / device.N if debug: print("gmd %g" % gmd) return gmd
@utilities.memoize
[docs] def get_gm(self, device, voltages): """Get the gate-source transconductance Mathematically: .. math:: g_{ms} = \\frac{dI_{DS}}{d(VG-VS)} Often this is referred to as just :math:`g_m`. **Parameters:** device : object The device object holding the device parameters as attributes. voltages : tuple A tuple containing the voltages applied to the driving ports. In this case, the tuple is ``(vds, vgs, vbs)``. **Returns:** gmb : float The gate-source transconductace. """ (vds, vgs, vbs) = voltages debug = False svt, skp = self.get_svt_skp(device, debug=False) assert vds >= 0 vsqrt1 = max(-vbs + 2*self.PHI, 0.) vsqrt2 = max(2*self.PHI, 0.) VT = self.VTO + svt + self.GAMMA * \ (math.sqrt(vsqrt1) - math.sqrt(vsqrt2)) if vgs < VT: gm = options.iea / VT / 100 else: if vds < vgs - VT: gm = self.KP * device.W / device.L * vds else: gm = -0.5*self.KP*self.LAMBDA * device.W/device.L * (-self.GAMMA*(-vsqrt2**.5 + vsqrt1**.5) + vgs - self.VTO)**2 \ +0.5*self.KP * device.W/device.L *(self.LAMBDA*( self.GAMMA*(-vsqrt2**.5 + vsqrt1**.5) + vds - vgs + self.VTO) + 1.0) *\ (-2 * self.GAMMA * (-vsqrt2**.5 + vsqrt1**.5) + 2*vgs - 2*self.VTO) gm = (1 + skp) * gm * device.M / device.N if debug: print("gmg %g" % gm) return gm
def _self_check(self): """Performs sanity check on the model parameters.""" ret = True, "" if self.NSUB is not None and self.NSUB < 0: ret = (False, "NSUB " + str(self.NSUB)) elif self.U0 is not None and not self.U0 > 0: ret = (False, "UO " + str(self.U0)) elif not self.GAMMA > 0: ret = (False, "GAMMA " + str(self.GAMMA)) elif not self.PHI > 0.1: ret = (False, "PHI " + str(self.PHI)) elif self.AVT and self.AVT < 0: ret = (False, "AVT " + str(self.AVT)) elif self.AKP and self.AKP < 0: ret = (False, "AKP " + str(self.AKP)) return ret
[docs] def device_check(self, adev): """Performs sanity check on the device parameters.""" if not adev.L > 0: ret = (False, "L") elif not adev.W > 0: ret = (False, "W") elif not adev.N > 0: ret = (False, "N") elif not adev.M > 0: ret = (False, "M") else: ret = (True, "") return ret