# -*- coding: iso-8859-1 -*-
# diode.py
# Diode model
# Copyright 2006-2013 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/>.
"""
This module contains a diode element and its model class.
.. image:: images/elem/diode.svg
"""
#
# |\|
# n1 o---| ]---o n2
# |/|
#
from __future__ import (unicode_literals, absolute_import,
division, print_function)
from math import e as E, isinf, sqrt
import numpy as np
from scipy.optimize import newton
from . import constants
from . import utilities
from . import options
damping_factor = 4.
[docs]class diode(object):
"""A diode element.
**Parameters:**
n1, n2 : string
The diode anode and cathode.
model : model instance
The diode model providing the mathemathical modeling.
ic : float
The diode initial voltage condition for transient analysis
(ie :math:`V_D = V(n_1) - V(n_2)` at :math:`t = 0`).
off : bool
Whether the diode should be initially assumed to be off when
computing an OP.
The other are the physical parameters reported in the following table:
+---------------+-------------------+-----------------------------------+
| *Parameter* | *Default value* | *Description* |
+===============+===================+===================================+
| AREA | 1.0 | Area multiplier |
+---------------+-------------------+-----------------------------------+
| T | circuit temp | Operating temperature |
+---------------+-------------------+-----------------------------------+
"""
def __init__(self, part_id, n1, n2, model, AREA=None, T=None, ic=None, off=False):
self.part_id = part_id
self.is_nonlinear = True
self.is_symbolic = True
self.dc_guess = [0.425]
class _dev_class(object):
pass
self.device = _dev_class()
self.device.AREA = AREA if AREA is not None else 1.0
self.device.T = T
self.device.last_vd = .425
self.n1 = n1
self.n2 = n2
self.ports = ((self.n1, self.n2),)
self.model = model
if self.device.T is None:
self.device.T = constants.T
if ic is not None: # fixme
print("(W): ic support in diodes is very experimental.")
self.dc_guess = ic
self.ic = ic
self.off = off
if self.off:
if self.ic is None:
self.ic = 0
else:
print("(W): IC statement in diodes takes precedence over OFF.")
print("(W): If you are performing a transient simulation with uic=2,")
print("(W): you may want to check the initial value.")
def _get_T(self):
return self.device.T
[docs] def set_temperature(self, T):
"""Set the operating temperature IN KELVIN degrees
"""
# this automatically makes the memoization cache obsolete. self.device
# is in fact passed as one of the arguments, hashed and stored:
# if it changes, the old cache will return misses.
self.device.T = T
def __str__(self):
rep = "%s area=%g T=%g" % (
self.model.name, self.device.AREA, self._get_T())
if self.ic is not None:
rep = rep + " ic=" + str(self.ic)
elif self.off:
rep += " off"
return rep
[docs] def get_output_ports(self):
return self.ports
[docs] def get_drive_ports(self, op):
if not op == 0:
raise ValueError('Diode %s has no output port %d' %
(self.part_id, op))
return self.ports
[docs] def istamp(self, ports_v, time=0, reduced=True):
"""Get the current matrix
A 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.
"""
v = ports_v[0]
i = self.model.get_i(self.model, v, self.device)
istamp = np.array((i, -i), dtype=np.float64)
indices = ((self.n1 - 1*reduced, self.n2 - 1*reduced), (0, 0))
if reduced:
delete_i = [pos for pos, ix in enumerate(indices[0]) if ix == -1]
istamp = np.delete(istamp, delete_i, axis=0)
indices = tuple(zip(*[(ix, j) for ix, j in zip(*indices) if ix != -1]))
return indices, istamp
[docs] def i(self, op_index, ports_v, time=0): # with gmin added
v = ports_v[0]
i = self.model.get_i(self.model, v, self.device)
return i
[docs] def gstamp(self, ports_v, time=0, reduced=True):
"""Returns the differential (trans)conductance wrt the port specified by port_index
when the element has the voltages specified in ports_v across its ports,
at (simulation) time.
ports_v: a list in the form: [voltage_across_port0, voltage_across_port1, ...]
port_index: an integer, 0 <= port_index < len(self.get_ports())
time: the simulation time at which the evaluation is performed. Set it to
None during DC analysis.
"""
indices = ([self.n1 - 1]*2 + [self.n2 - 1]*2,
[self.n1 - 1, self.n2 - 1]*2)
gm = self.model.get_gm(self.model, 0, utilities.tuplinator(ports_v), 0, self.device)
if gm == 0:
gm = options.gmin*2
stamp = np.array(((gm, -gm),
(-gm, gm)), dtype=np.float64)
if reduced:
zap_rc = [pos for pos, i in enumerate(indices[1][:2]) 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 g(self, op_index, ports_v, port_index, time=0):
if not port_index == 0:
raise Exception("Attepted to evaluate a diode's gm on an unknown port.")
gm = self.model.get_gm(self.model, op_index, utilities.tuplinator(ports_v), port_index, self.device)
return gm
[docs] def get_op_info(self, ports_v_v):
"""Information regarding the Operating Point (OP)
**Parameters:**
ports_v : list of lists
The parameter is to be set to ``[[v]]``, where ``v`` is the voltage
applied to the diode terminals.
**Returns:**
op_keys : list of strings
The labels corresponding to the numeric values in ``op_info``.
op_info : list of floats
The values corresponding to ``op_keys``.
"""
vn1n2 = float(ports_v_v[0][0])
idiode = self.i(0, (vn1n2,))
gmdiode = self.g(0, (vn1n2,), 0)
op_keys = ["Part ID", "V(n1-n2) [V]", "I(n1-n2) [A]", "P [W]",
"gm [A/V]", u"T [\u00b0K]"]
op_info = [self.part_id.upper(), vn1n2, idiode, vn1n2*idiode, gmdiode,
self._get_T()]
return op_keys, op_info
[docs] def get_netlist_elem_line(self, nodes_dict):
ext_n1, ext_n2 = nodes_dict[self.n1], nodes_dict[self.n2]
ret = "%s %s %s %s" % (self.part_id, ext_n1, ext_n2, self.model.name)
# append the optional part:
# [<AREA=float> <T=float> <IC=float> <OFF=boolean>]
ret += " AREA=%g" % self.device.AREA
if self.device.T is not None:
ret += " T=%g" % self.device.T
if self.ic is not None:
ret += " IC=%g" % self.ic
if self.off:
ret += " OFF=1"
return ret
IS_DEFAULT = 1e-14 # saturation current A
N_DEFAULT = 1.0 # forvard branch ideality factor
NBV_DEFAULT = 1.0 # breakdown ideality factor
ISR_DEFAULT = 0.0 # recombination current parameter A
NR_DEFAULT = 2.0 # emission coefficient for Isr
RS_DEFAULT = 0.0 # series resistance ohm
CJ0_DEFAULT = 0.0 # zero bias junction capacitance
M_DEFAULT = .5 # grading coefficient
VJ_DEFAULT = .7 # junction built-in potential
FC_DEFAULT = .5 # forward bias depletion capacitance coefficient
CP_DEFAULT = .0 # linear capacitance F
TT_DEFAULT = .0 # transit time s
BV_DEFAULT = float('inf') # reverse breakdown voltage
IBV_DEFAULT = 1e-3 # reverse breakdown current IBV=-I(-BV)
IKF_DEFAULT = float('inf') # high-injection knee current A
KF_DEFAULT = .0 # flicker noise coefficient
AF_DEFAULT = 1. # flicker noise exponent
FFE_DEFAULT = 1. # flicker noise frequency exponent
TEMP_DEFAULT = 26.85 # temperature
XTI_DEFAULT = 3.0 # saturation current exponent
EG_DEFAULT = 1.11 # band gap
TBV_DEFAULT = 0.0 # BV linear temperature coefficient 1/â¦C
TRS_DEFAULT = 0.0 # RS linear temperature coefficient 1/â¦C
TTT1_DEFAULT = 0.0 # TT linear temperature coefficient
TTT2_DEFAULT = 0.0 # TT quadratic temperature coefficient
TM1_DEFAULT = 0.0 # M linear temperature coefficient
TM2_DEFAULT = 0.0 # M quadratic temperature coefficient
T_DEFAULT = utilities.Celsius2Kelvin(26.85)
AREA_DEFAULT = 1.0 # area
[docs]class diode_model(object):
"""A diode model implementing the `Shockley diode equation
<http://en.wikipedia.org/wiki/Shockley_diode_equation#Shockley_diode_equation>`__.
Currently the capacitance modeling part is missing.
The principal parameters are:
+---------------+-------------------+-----------------------------------+
| *Parameter* | *Default value* | *Description* |
+===============+===================+===================================+
| IS | 1e-14 A | Specific current |
+---------------+-------------------+-----------------------------------+
| N | 1.0 | Emission coefficient |
+---------------+-------------------+-----------------------------------+
| ISR | 0.0 A | Recombination current |
+---------------+-------------------+-----------------------------------+
| NR | 2.0 | Recombination coefficient |
+---------------+-------------------+-----------------------------------+
| RS | 0.0 ohm | Series resistance per unit area |
+---------------+-------------------+-----------------------------------+
please refer to a textbook description of the Shockley diode equation
or to the source file ``diode.py`` file for the other parameters.
"""
def __init__(self, name, IS=None, N=None, NBV=None, ISR=None, NR=None, RS=None,
CJ0=None, M=None, VJ=None, FC=None, CP=None, TT=None,
BV=None, IBV=None, IKF=None, KF=None, AF=None, FFE=None, TEMP=None,
XTI=None, EG=None, TBV=None, TRS=None, TTT1=None, TTT2=None,
TM1=None, TM2=None, material=constants.si):
self.name = name
self.IS = float(IS) if IS is not None else IS_DEFAULT
self.N = float(N) if N is not None else N_DEFAULT
self.NBV = float(NBV) if NBV is not None else NBV_DEFAULT
self.ISR = float(ISR) if ISR is not None else ISR_DEFAULT
self.NR = float(NR) if NR is not None else NR_DEFAULT
self.RS = float(RS) if RS is not None else RS_DEFAULT
self.CJ0 = float(CJ0) if CJ0 is not None else CJ0_DEFAULT
self.M = float(M) if M is not None else M_DEFAULT
self.VJ = float(VJ) if VJ is not None else VJ_DEFAULT
self.FC = float(FC) if FC is not None else FC_DEFAULT
self.CP = float(CP) if CP is not None else CP_DEFAULT
self.TT = float(TT) if TT is not None else TT_DEFAULT
self.BV = float(BV) if BV is not None else BV_DEFAULT
self.IBV = float(IBV) if IBV is not None else IBV_DEFAULT
self.IKF = float(IKF) if IKF is not None else IKF_DEFAULT
self.KF = float(KF) if KF is not None else KF_DEFAULT
self.AF = float(AF) if AF is not None else AF_DEFAULT
self.FFE = float(FFE) if FFE is not None else FFE_DEFAULT
self.TEMP = utilities.Celsius2Kelvin(
float(TEMP)) if TEMP is not None else TEMP_DEFAULT
self.XTI = float(XTI) if XTI is not None else XTI_DEFAULT
self.EG = float(EG) if EG is not None else EG_DEFAULT
self.TBV = float(TBV) if TBV is not None else TBV_DEFAULT
self.TRS = float(TRS) if TRS is not None else TRS_DEFAULT
self.TTT1 = float(TTT1) if TTT1 is not None else TTT1_DEFAULT
self.TTT2 = float(TTT2) if TTT2 is not None else TTT2_DEFAULT
self.TM1 = float(TM1) if TM1 is not None else TM1_DEFAULT
self.TM2 = float(TM2) if TM2 is not None else TM2_DEFAULT
self.T = T_DEFAULT
self.last_vd = None
self.VT = constants.Vth(self.T)
self.material=material
[docs] def print_model(self):
strm = ".model diode %s IS=%g N=%g ISR=%g NR=%g RS=%g CJ0=%g M=%g " + \
"VJ=%g FC=%g CP=%g TT=%g BV=%g IBV=%g KF=%g AF=%g FFE=%g " + \
"TEMP=%g XTI=%g EG=%g TBV=%g TRS=%g TTT1=%g TTT2=%g TM1=%g " + \
"TM2=%g"
print(strm % (self.name, self.IS, self.N, self.ISR, self.NR, self.RS,
self.CJ0, self.M, self.VJ, self.FC, self.CP, self.TT,
self.BV, self.IBV, self.KF, self.AF, self.FFE, self.TEMP,
self.XTI, self.EG, self.TBV, self.TRS, self.TTT1,
self.TTT2, self. TM1, self. TM2))
@utilities.memoize
[docs] def get_i(self, vext, dev):
if dev.T != self.T:
self.set_temperature(dev.T)
if not self.RS:
i = self._get_i(vext) * dev.AREA
dev.last_vd = vext
else:
vd = dev.last_vd if dev.last_vd is not None else 10*self.VT
vd = newton(self._obj_irs, vd, fprime=self._obj_irs_prime,
args=(vext, dev), tol=options.vea, maxiter=500)
i = self._get_i(vext-vd)
dev.last_vd = vd
return i
def _obj_irs(self, x, vext, dev):
# obj fn for newton
return x/self.RS-self._get_i(vext-x)*dev.AREA
def _obj_irs_prime(self, x, vext, dev):
# obj fn derivative for newton
# first term
ret = 1./self.RS
# disable RS
RSSAVE = self.RS
self.RS = 0
# second term
ret += self.get_gm(self, 0, (vext-x,), 0, dev)
# renable RS
self.RS = RSSAVE
return ret
def _safe_exp(self, x):
return np.exp(x) if x < 70 else np.exp(70) + 10 * x
def _get_i(self, v):
i_fwd= self.IS * (self._safe_exp(v/(self.N * self.VT)) - 1)
i_rec= self.ISR* (self._safe_exp(v/(self.NR * self.VT)) - 1)
i_rev=-self.IS * (self._safe_exp(-(v+self.BV)/(self.NBV *self.VT)) - 1)
k_inj = 1
if (not isinf(self.IKF)) and (self.IKF>0) and (i_fwd>0):
k_inj = sqrt(self.IKF/(self.IKF+i_fwd))
return k_inj*i_fwd+i_rec+i_rev
@utilities.memoize
[docs] def get_gm(self, op_index, ports_v, port_index, dev):
if dev.T != self.T:
self.set_temperature(dev.T)
v=ports_v[0]
gm = self.IS / (self.N * self.VT) *\
self._safe_exp(v / (self.N * self.VT)) +\
-self.IS/self.VT * (self._safe_exp(-(v+self.BV)/self.VT)) +\
self.ISR / (self.NR * self.VT) *\
self._safe_exp(v / (self.NR * self.VT))
if self.RS != 0.0:
gm = 1. / (self.RS + 1. / (gm + 1e-3*options.gmin))
return dev.AREA * gm
def __str__(self):
pass
[docs] def set_temperature(self, T):
T = float(T)
self.EG = self.material.Eg(T) if self.material!=None else self.EG
self.IS = self.IS*(T/self.T)**(self.XTI/self.N)* \
np.exp(-constants.e*(self.material.Eg(constants.Tref) if self.material!=None else self.EG)/\
(self.N*constants.k*T)*
(1 - T/self.T))
self.BV = self.BV - self.TBV*(T - self.T)
self.RS = self.RS*(1 + self.TRS*(T - self.T))
self.T = T