Ejemplo n.º 1
0
 def __init__(self, instanceName):
     """
     Here the Element constructor must be called. Do not connect
     internal nodes here.
     """
     cir.Element.__init__(self, instanceName)
     self.diogs = Junction()
     self.diogd = Junction()
Ejemplo n.º 2
0
 def __init__(self, instanceName):
     cir.Element.__init__(self, instanceName)
     # Use junctions to model diodes and capacitors
     self.jif = SVJunction()
     self.jir = SVJunction()
     self.jile = Junction()
     self.jilc = Junction()
     # collector/emitter terminal numbers: may be re-mapped by
     # extrinsic device
     self._ct = 0
     self._et = 2
Ejemplo n.º 3
0
class SVBJTi(cir.Element):
    """
    State-variable-based Gummel-Poon intrinsic BJT model based

    This implementation based mainly on previous implementation in
    carrot and some equations from Pspice manual, with the addition of
    the state-variable definitions.
    
    Terminal order: 0 Collector, 1 Base, 2 Emitter, (3 Bulk, not included)::

                      
      C (0) o----,         4----o  E (2)
                  \       /
                   \     /
                  ---------
                      |
                      o 
   
                      B (1)

    Can be used for NPN or PNP transistors.

    Intrinsic Internal Topology
    +++++++++++++++++++++++++++

    The state variable formulation is achieved by replacing the BE and
    BC diodes (Ibf, Ibr) with state-variable based diodes. This
    requires two additional variables (nodes) but eliminates large
    positive exponentials from the model::

                                  Term : x2 
                      +--------------------------+
                      |                          |
                     /|\                        /^\ 
                    ( | ) gyr v2               ( | ) gyr vbc(x)
                     \V/                        \|/  
             tref     |                          |
                 ,----+--------------------------+ 
                 |    |                          |               
                 |   /^\                        /|\              
                 |  ( | ) gyr v1               ( | ) gyr vbe(x)  
                ---  \|/                        \V/  
                 V    |                          |
                      +--------------------------+
                                  Term : x1                
                                                  
    All currents/charges in the model are functions of voltages v3
    (x2) and v4 (x1). Note that vbc and vbe are now also functions of
    x1, x2.

    In addition we may need 2 additional nodes (plus reference) if rb
    is not zero: Bi for the internal base node and tib to measure the
    internal base current and calculate Rb(ib).

    1. If RB == 0::

                           +----------------+--o 0 (C)
                    -      |                |
                          /^\               |
                   v2    ( | ) ibc(x2)      |
                          \|/               |       
                    +      |               /|\       
           (B) 1 o---------+              ( | ) ice(x1,x2)
                    +      |               \V/      
                          /|\               |       
                   v1    ( | ) ibe(x1)      |
                          \V/               |
                    -      |                |
                           +----------------+--o 2 (E)

    2. If RB != 0::

                                     +----------------+--o 0 (C)
                                -    |                |
                                    /^\               |
                  gyr tib      v2  ( | ) ibc(x2)      |
                                    \|/               |       
                     ,---,      +    |               /|\       
         (B) 1 o----( --> )----------+ Term : Bi    ( | ) ice(x1,x2)
                     `---`      +    |               \V/      
                                    /|\               |       
                               v1  ( | ) ibe(x1)      |
                                    \V/               |
                                -    |                |
                   gyr v(1,Bi)       +----------------+--o 2 (E)
                                  
                      ,---,       
                 +---( <-- ) -----+
                 |    `---`       |
          tref   |                | ib/gyr
              ,--+                |
              |  |    ,---,       | Term : ib
              |  +---( --> )------+
              |       `---`       
             --- 
              V     gyr ib Rb(ib)
                                           
    Charge sources are connected between internal nodes defined
    above. If xcjc is not 1 but RB is zero, xcjc is ignored.
    """

    devType = "svbjt"
    
    paramDict = dict(
        cir.Element.tempItem,
        type = ('Type (npn or pnp)', '', str, 'npn'),
        isat = ('Transport saturation current', 'A', float, 1e-16),
        bf = ('Ideal maximum forward beta', '', float, 100.),
        nf = ('Forward current emission coefficient', '', float, 1.),
        vaf = ('Forward early voltage', 'V', float, 0.),
        ikf = ('Forward-beta high current roll-off knee current', 'A', 
               float, 0.),
        ise = ('Base-emitter leakage saturation current', 'A', float, 0.),
        ne = ('Base-emitter leakage emission coefficient', '', float, 1.5),
        br = ('Ideal maximum reverse beta', '', float, 1.),
        nr = ('Reverse current emission coefficient', '', float, 1.),
        var = ('Reverse early voltage', 'V', float, 0.),
        ikr = ('Corner for reverse-beta high current roll off', 'A', float, 0.),
        isc = ('Base collector leakage saturation current', 'A', float, 0.),
        nc = ('Base-collector leakage emission coefficient', '', float, 2.),
        rb = ('Zero bias base resistance', 'Ohm', float, 0.),
        rbm = ('Minimum base resistance', 'Ohm', float, 0.),
        irb = ('Current at which rb falls to half of rbm', 'A', float, 0.),
        eg = ('Badgap voltage', 'eV', float, 1.11),
        cje = ('Base emitter zero bias p-n capacitance', 'F', float, 0.),
        vje = ('Base emitter built in potential', 'V', float, 0.75),
        mje = ('Base emitter p-n grading factor', '', float, 0.33),
        cjc = ('Base collector zero bias p-n capacitance', 'F', float, 0.),
        vjc = ('Base collector built in potential', 'V', float, 0.75),
        mjc = ('Base collector p-n grading factor', '', float, 0.33),
        xcjc = ('Fraction of cbc connected internal to rb', '', float, 1.),
        fc = ('Forward bias depletion capacitor coefficient', '', float, 0.5),
        tf = ('Ideal forward transit time', 's', float, 0.),
        xtf = ('Transit time bias dependence coefficient', '', float, 0.),
        vtf = ('Transit time dependency on vbc', 'V', float, 0.),
        itf = ('Transit time dependency on ic', 'A', float, 0.),
        tr = ('Ideal reverse transit time', 's', float, 0.),
        xtb = ('Forward and reverse beta temperature coefficient', '', 
               float, 0.),
        xti = ('IS temperature effect exponent', '', float, 3.),
        tnom = ('Nominal temperature', 'C', float, 27.),
        area = ('Current multiplier', '', float, 1.)
        )

    def __init__(self, instanceName):
        cir.Element.__init__(self, instanceName)
        # Use junctions to model diodes and capacitors
        self.jif = SVJunction()
        self.jir = SVJunction()
        self.jile = Junction()
        self.jilc = Junction()
        # collector/emitter terminal numbers: may be re-mapped by
        # extrinsic device
        self._ct = 0
        self._et = 2

    def process_params(self):
        """
        Adjusts internal topology and makes preliminary calculations
        according to parameters.
        """
        # Define topology first. Add state variable nodes
        x2 = self.add_internal_term('x2','s.v.')
        x1 = self.add_internal_term('x1','s.v.')
        tref = self.add_reference_term()
        # Default configuration assumes rb == 0
        # ibe, vbe, ibc, vbc, ice 
        self.csOutPorts = [(1, self._et), (tref, x1), (1, self._ct), 
                           (tref, x2), (self._ct, self._et)]
        # Controling voltages are x1, x2
        self.controlPorts = [(x1, tref), (x2, tref)]
        # qbe, qbc
        self.qsOutPorts = [(1, self._et), (1, self._ct)]

        # Flag to signal if the extra charge Qbx is needed or not
        self._qbx = False
        # Default state-variable VCCSs
        self.linearVCCS = [((1, self._et), (x1, tref), glVar.gyr),
                           ((1, self._ct), (x2, tref), glVar.gyr)]
        if self.rb != 0.:
            # rb is not zero: add internal terminals
            tBi = self.add_internal_term('Bi', 'V') 
            tib = self.add_internal_term('ib', '{0} A'.format(glVar.gyr)) 
            # Add Linear VCCS for gyrator(s)
            self.linearVCCS = [((tBi, self._et), (x1, tref), glVar.gyr),
                               ((tBi, 0), (x2, tref), glVar.gyr),
                               ((1, tBi), (tib, tref), glVar.gyr),
                               ((tib, tref), (1, tBi), glVar.gyr)]
            # ibe, vbe, ibc, vbc, ice, Rb(ib) * ib
            self.csOutPorts = [(tBi, self._et), (tref, x1), (tBi, self._ct), 
                               (tref, x2), (self._ct, self._et), (tref, tib)]
            # Controling voltages are x1, x2 and gyrator port
            self.controlPorts = [(x1, tref), (x2, tref), (tib, tref)]
            # qbie, qbic
            self.qsOutPorts = [(tBi, self._et), (tBi, self._ct)]
            # Now check if Cjbc must be splitted (since rb != 0)
            if (self.cjc != 0.) and (self.xcjc < 1.):
                # add extra charge source
                self.qsOutPorts.append((1, self._ct))
                self._qbx = True
        
        # Make sure the guess is consistent
        self.vPortGuess = np.zeros(len(self.controlPorts))
#        # Try guess in active region
#        self.vPortGuess[0] = 100. # x1
#        self.vPortGuess[1] = -1.  # x2
#        if self.rb != 0.:
#            self.vPortGuess[2] = 1e-6 / glVar.gyr  # ib 
        # In principle we may not need any charge
        keepPorts = [ ]
        if self.cje + self.tf != 0.:
            # keep qbe
            keepPorts.append(self.qsOutPorts[0])
        if self.cjc + self.tr != 0.:
            # keep qbc, qbx (if any)
            if self._qbx:
                keepPorts += self.qsOutPorts[-2:]
            else:
                keepPorts.append(self.qsOutPorts[-1])
        self.qsOutPorts = keepPorts

        # keep track of how many output variables are needed
        self.ncurrents = len(self.csOutPorts)
        self.ncharges = len(self.qsOutPorts)

        # NPN or PNP 
        if self.type == 'pnp':
            self._typef = -1.
        else:
            self._typef = 1.

        # Calculate common variables
        # Absolute nominal temperature
        self.Tnomabs = self.tnom + const.T0
        self.egapn = self.eg - .000702 * (self.Tnomabs**2) \
            / (self.Tnomabs + 1108.)
        # jif produces if, cje
        self.jif.process_params(self.isat, self.nf, self.fc, self.cje, 
                                self.vje, self.mje, self.xti, self.eg, 
                                self.Tnomabs)
        # jir produces ir, cjc
        self.jir.process_params(self.isat, self.nr, self.fc, self.cjc, 
                                self.vjc, self.mjc, self.xti, self.eg, 
                                self.Tnomabs)
        if self.ise != 0.:
            # jile produces ile
            self.jile.process_params(self.ise, self.ne, 0, 0, 0, 0, 
                                     self.xti, self.eg, self.Tnomabs)
        if self.isc != 0.:
            # jilc produces ilc
            self.jilc.process_params(self.isc, self.nc, 0, 0, 0, 0, 
                                     self.xti, self.eg, self.Tnomabs)
        # Constants needed for rb(ib) calculation
        if self.irb != 0.:
            self._ck1 = 144. / self.irb / self.area /np.pi/np.pi
            self._ck2 = np.pi*np.pi * np.sqrt(self.irb * self.area) / 24.

    def set_temp_vars(self, temp):
        """
        Calculate temperature-dependent variables, given temp in deg. C
        """
        # Absolute temperature (note self.temp is in deg. C)
        self.Tabs = const.T0 + temp
        # Normalized temp
        self.tnratio = self.Tabs / self.Tnomabs
        tnXTB = pow(self.tnratio, self.xtb)
        # Thermal voltage
        self.vt = const.k * self.Tabs / const.q
        # Temperature-adjusted egap
        self.egap_t = self.eg - .000702 * (self.Tabs**2) / (self.Tabs + 1108.)
        # set temperature in juctions
        self.jif.set_temp_vars(self.Tabs, self.Tnomabs, self.vt, 
                               self.egapn, self.egap_t)
        self.jir.set_temp_vars(self.Tabs, self.Tnomabs, self.vt, 
                               self.egapn, self.egap_t)
        # Adjust ise and isc (which have different temperature variation)
        if self.ise != 0.:
            self.jile.set_temp_vars(self.Tabs, self.Tnomabs, self.vt, 
                                    self.egapn, self.egap_t)
            self.jile._t_is /= tnXTB
        if self.isc != 0.:
            self.jilc.set_temp_vars(self.Tabs, self.Tnomabs, self.vt, 
                                    self.egapn, self.egap_t)
            self.jilc._t_is /= tnXTB
        # Now some BJT-only variables
        self._bf_t = self.bf * tnXTB
        self._br_t = self.br * tnXTB
        

    def eval_cqs(self, vPort):
        """
        Calculates currents/charges

        Input is a vector may be one of the following, depending on
        parameter values::

          vPort = [xbe, xbc]
          vPort = [xbe, xbc, v4_i] (gyrator voltage, irb != 0)

        Output also depends on parameter values. Charges only present
        if parameters make them different than 0 (i.e., cje, tf, cjc,
        etc. are set to nonzero values)::
        
          iVec = [ibe, vbe, ibc, vbc, ice]
          iVec = [ibe, vbe, ibc, vbc, ice, gyr*ib*Rb] (rb != 0)

          qVec = [qbe, qbc]
          qVec = [qbe, qbc, qbx] (rb != 0 and cjc != 1)

        """
        # Invert state variables if needed
        vPort1 = self._typef * vPort

        # Calculate junctions currents and voltages
        (ibf, vbe) = self.jif.get_idvd(vPort1[0])
        (ibr, vbc) = self.jir.get_idvd(vPort1[1])
        if self.ise != 0.:
            ile = self.jile.get_id(vbe)
        else:
            ile = 0.
        if self.isc != 0.:
            ilc = self.jilc.get_id(vbc)
        else:
            ilc = 0.
        # Kqb
        q1m1 = 1.
        if self.var != 0.:
            q1m1 -= vbe / self.var
        if self.vaf != 0.:
            q1m1 -= vbc / self.vaf
        kqb = 1. / q1m1
        # We need extra checking to consider the following
        # possibilities to create the AD tape:
        # 
        # 1. both ikf and ikr are zero -> no tape generated
        # 2. One of them is nonzero but both ibf and ibr are zero -> want tape
        #    but only for the nonzero parameter
        if self.ikf + self.ikr != 0.:
            q2 = 0.
            if self.ikf != 0.:
                q2 += ibf / self.ikf 
            if self.ikr != 0.:
                q2 += ibr / self.ikr
            kqb *= .5 * (1. + np.sqrt(1. + 4. * q2))

        # Create output vector [ibe, ibc, ice, ...]
        iVec = np.zeros(self.ncurrents, dtype = type(ibf))
        qVec = np.zeros(self.ncharges, dtype = type(ibf))
        # ibe, vbe
        iVec[0] = ibf / self._bf_t + ile
        iVec[1] = glVar.gyr * vbe
        # ibc, vbc
        iVec[2] = ibr / self._br_t + ilc
        iVec[3] = glVar.gyr * vbc
        # ice
        iVec[4] = (ibf - ibr) / kqb

        # RB
        if self.rb != 0.:
            # Using gyrator
            # vPort1[2] not defined if rb == 0
            # ib has area effect included (removed by _ck1 and _ck2)
            ib = vPort1[2] * glVar.gyr
            if self.irb != 0.:
                ib1 = np.abs(ib)
                x = np.sqrt(1. + self._ck1 * ib1) - 1.
                x *= self._ck2 / np.sqrt(ib1)
                tx = np.tan(x)
                c = self.rbm + 3. * (self.rb - self.rbm) \
                    * (tx - x) / (x * tx * tx)
                rb = ad.condassign(ib1, c, self.rb)
            else:
                rb = self.rbm + (self.rb - self.rbm) / kqb
            # Output is gyr * ib * rb.  It is divided by area^2 to
            # compensate that the whole vector is multiplied by area
            # at the end
            iVec[5] = glVar.gyr * ib * rb / pow(self.area, 2)
            vbcx = ib * rb / self.area + vbc

        # Charges ----------------------------------------------- 

        # Note that if tf == 0 and cje == 0, nothing is calculated and
        # nothing is assigned to the output vector.

        # qbe is the first charge (0)
        if self.tf != 0.:
            # Effective tf
            tfeff = self.tf
            if self.vtf != 0.:
                x = ibf / (ibf + self.itf)
                # safe_exp() not needed since positive vbc grows
                # logarithmically
                tfeff *= (1. + self.xtf * x*x * 
                          np.exp(vbc /1.44 /self.vtf))
            qVec[0] = tfeff * ibf
        if self.cje != 0.:
            qVec[0] += self.jif.get_qd(vbe) 

        # qbc 
        if self._qbx:
            if self.tr != 0.:
                qVec[-2] = self.tr * ibr
            if self.cjc != 0.:
                qVec[-2] += self.jir.get_qd(vbc) * self.xcjc 
                # qbx
                qVec[-1] = self.jir.get_qd(vbcx) * (1. - self.xcjc)
        else:
            if self.tr != 0.:
                qVec[-1] = self.tr * ibr
            if self.cjc != 0.:
                qVec[-1] += self.jir.get_qd(vbc) 

        # Consider area effect and invert currents if needed
        iVec *= self.area * self._typef
        qVec *= self.area * self._typef

        return (iVec, qVec)


    def power(self, vPort, currV):
        """ 
        Calculate total instantaneous power 

        Input: control voltages as in eval_cqs() and currents from
        returned by eval_cqs()
        """
        # vce = vbe - vbc
        gyrvce = currV[1] - currV[3]
        if self.rb != 0.:
            # currV[5] = ib * Rb * gyr
            # vPort[2] = ib / gyr
            pRb = currV[5] * vPort[2]
        else:
            pRb = 0.

        # pout = ibe * vbie + ibc * vbic + vce * ice + pRb
        pout = (currV[0] * currV[1] + currV[2] * currV[3] 
                + currV[4] * gyrvce) / glVar.gyr + pRb

        return pout

    def get_OP(self, vPort):
        """
        Calculates operating point information

        Input: same as eval_cqs
        Output: dictionary with OP variables
        For now it is quite incomplete
        """
        # First we need the Jacobian
        (outV, jac) = self.eval_and_deriv(vPort)
        power = self.power(vPort, outV)

        # calculate gm, etc. in terms od jac for state-variable
        # formulation
        opDict = dict(
            VBE = outV[1] / glVar.gyr,
            VCE = (outV[1] - outV[3]) / glVar.gyr,
            IB = outV[0] + outV[2],
            IC = outV[4] - outV[2],
            IE = - outV[4] - outV[0],
            Temp = self.temp,
            Power = power,
            )
        return opDict

    def get_noise(self, f):
        """
        Return noise spectral density at frequency f
        
        Requires a previous call to get_OP() 

        Not implemented yet
        """
        return None
Ejemplo n.º 4
0
 def __init__(self, instanceName):
     IBJT.__init__(self, instanceName)
     # Collector-bulk junction
     self.cbjtn = Junction()
     self.__doc__ += IBJT.__doc__
Ejemplo n.º 5
0
class BJTi(cir.Element):
    """
    Gummel-Poon intrinsic BJT model

    This implementation based mainly on previous implementation in
    carrot and some equations from Pspice manual.
    
    Terminal order: 0 Collector, 1 Base, 2 Emitter::

                      
          C (0) o----,         4----o  E (2)
                      \       /
                       \     /
                      ---------
                          |
                          o 
        
                          B (1)

    Can be used for NPN or PNP transistors.

    Intrinsic Internal Topology
    +++++++++++++++++++++++++++

    Internally may add 2 additional nodes (plus reference) if rb is
    not zero: Bi for the internal base node and tib to measure the
    internal base current and calculate Rb(ib). The possible
    configurations are described here.

    1. If RB == 0::

                         +----------------+--o 0 (C)
                         |                |
                        /^\               |
                       ( | ) ibc(vbc)     |
                        \|/               |       
                         |               /|\       
         (B) 1 o---------+              ( | ) ice    
                         |               \V/      
                        /|\               |       
                       ( | ) ibe(vbe)     |
                        \V/               |
                         |                |
                         +----------------+--o 2 (E)

    2. If RB != 0::

                                     +----------------+--o 0 (C)
                                     |                |
                                    /^\               |
                                   ( | ) ibc(vbc)     |
                    gyr * tib       \|/               |       
                     ,---,           |               /|\       
         (B) 1 o----( --> )----------+ Term : Bi    ( | ) ice    
                     `---`           |               \V/      
                                    /|\               |       
                                   ( | ) ibe(vbe)     |
                                    \V/               |
                                     |                |
                                     +----------------+--o 2 (E)
                     gyr v(1,Bi)  
                      ,---,       
                 +---( <-- )------+
                 |    `---`       |
          tref   |                | voltage: ib/gyr
             ,---+                |
             |   |    ,---,       |         
             |   +---( --> )------+ Term : ib
             |        `---`       
            ---     gyr ib Rb(ib)
             V      
                                           
    Charge sources are connected between internal nodes defined
    above. If xcjc is not 1 but RB is zero, xcjc is ignored.
   
    """

    devType = "bjt"
    
    paramDict = dict(
        cir.Element.tempItem,
        type = ('Type (npn or pnp)', '', str, 'npn'),
        isat = ('Transport saturation current', 'A', float, 1e-16),
        bf = ('Ideal maximum forward beta', '', float, 100.),
        nf = ('Forward current emission coefficient', '', float, 1.),
        vaf = ('Forward early voltage', 'V', float, 0.),
        ikf = ('Forward-beta high current roll-off knee current', 'A', 
               float, 0.),
        ise = ('Base-emitter leakage saturation current', 'A', float, 0.),
        ne = ('Base-emitter leakage emission coefficient', '', float, 1.5),
        br = ('Ideal maximum reverse beta', '', float, 1.),
        nr = ('Reverse current emission coefficient', '', float, 1.),
        var = ('Reverse early voltage', 'V', float, 0.),
        ikr = ('Corner for reverse-beta high current roll off', 'A', float, 0.),
        isc = ('Base collector leakage saturation current', 'A', float, 0.),
        nc = ('Base-collector leakage emission coefficient', '', float, 2.),
        rb = ('Zero bias base resistance', 'W', float, 0.),
        rbm = ('Minimum base resistance', 'W', float, 0.),
        irb = ('Current at which rb falls to half of rbm', 'A', float, 0.),
        eg = ('Badgap voltage', 'eV', float, 1.11),
        cje = ('Base emitter zero bias p-n capacitance', 'F', float, 0.),
        vje = ('Base emitter built in potential', 'V', float, 0.75),
        mje = ('Base emitter p-n grading factor', '', float, 0.33),
        cjc = ('Base collector zero bias p-n capacitance', 'F', float, 0.),
        vjc = ('Base collector built in potential', 'V', float, 0.75),
        mjc = ('Base collector p-n grading factor', '', float, 0.33),
        xcjc = ('Fraction of cbc connected internal to rb', '', float, 1.),
        fc = ('Forward bias depletion capacitor coefficient', '', float, 0.5),
        tf = ('Ideal forward transit time', 'S', float, 0.),
        xtf = ('Transit time bias dependence coefficient', '', float, 0.),
        vtf = ('Transit time dependency on vbc', 'V', float, 0.),
        itf = ('Transit time dependency on ic', 'A', float, 0.),
        tr = ('Ideal reverse transit time', 'S', float, 0.),
        xtb = ('Forward and reverse beta temperature coefficient', '', 
               float, 0.),
        xti = ('IS temperature effect exponent', '', float, 3.),
        tnom = ('Nominal temperature', 'C', float, 27.),
        area = ('Current multiplier', '', float, 1.)
        )

    def __init__(self, instanceName):
        cir.Element.__init__(self, instanceName)
        # Use junctions to model diodes and capacitors
        self.jif = Junction()
        self.jir = Junction()
        self.jile = Junction()
        self.jilc = Junction()
        # collector/emitter terminal numbers: may be re-mapped by
        # extrinsic device
        self._ct = 0
        self._et = 2

    def process_params(self):
        """
        Adjusts internal topology and makes preliminary calculations
        according to parameters
        """
        # Default configuration assumes rb == 0
        # ibe, ibc, ice 
        self.csOutPorts = [(1, self._et), (1, self._ct), 
                           (self._ct, self._et)]
        # Controling voltages are vbe, vbc 
        self.controlPorts = [(1, self._et), (1, self._ct)]
        self.vPortGuess = np.array([0., 0.])
        # qbe, qbc
        self.qsOutPorts = [(1, self._et), (1, self._ct)]

        # Define topology first
        # Flag to signal if the extra charge Qbx is needed or not
        self._qbx = False
        if self.rb:
            # rb is not zero: add internal terminals
            tBi = self.add_internal_term('Bi', 'V')
            tib = self.add_internal_term('ib', '{0} A'.format(glVar.gyr)) 
            tref = self.add_reference_term()
            # Linear VCCS for gyrator(s)
            self.linearVCCS = [((1, tBi), (tib, tref), glVar.gyr),
                               ((tib, tref), (1, tBi), glVar.gyr)]
            # ibe, ibc, ice, Rb(ib) * ib
            self.csOutPorts = [(tBi, self._et), (tBi, self._ct), 
                               (self._ct, self._et), (tref, tib)]
            # Controling voltages are vbie, vbic and gyrator port
            self.controlPorts = [(tBi, self._et), (tBi, self._ct), 
                                 (tib, tref)]
            self.vPortGuess = np.array([0., 0., 0.])
            # qbie, qbic
            self.qsOutPorts = [(tBi, self._et), (tBi, self._ct)]
            # Now check if Cjbc must be splitted (since rb != 0)
            if self.cjc and (self.xcjc < 1.):
                self.qsOutPorts.append((1, self._ct))
                self._qbx = True
            
        # In principle we may not need any charge
        keepPorts = [ ]
        if self.cje + self.tf:
            # keep qbe
            keepPorts.append(self.qsOutPorts[0])
        if self.cjc + self.tr:
            # keep qbc, qbx (if any)
            if self._qbx:
                keepPorts += self.qsOutPorts[-2:]
            else:
                keepPorts.append(self.qsOutPorts[-1])
        self.qsOutPorts = keepPorts

        # keep track of how many output variables are needed
        self.ncurrents = len(self.csOutPorts)
        self.ncharges = len(self.qsOutPorts)

        # NPN or PNP 
        if self.type == 'pnp':
            self._typef = -1.
        else:
            self._typef = 1.

        # Calculate common variables
        # Absolute nominal temperature
        self.Tnomabs = self.tnom + const.T0
        self.egapn = self.eg - .000702 * (self.Tnomabs**2) \
            / (self.Tnomabs + 1108.)
        # jif produces if, cje
        self.jif.process_params(self.isat, self.nf, self.fc, self.cje, 
                                self.vje, self.mje, self.xti, self.eg, 
                                self.Tnomabs)
        # jir produces ir, cjc
        self.jir.process_params(self.isat, self.nr, self.fc, self.cjc, 
                                self.vjc, self.mjc, self.xti, self.eg, 
                                self.Tnomabs)
        if self.ise:
            # jile produces ile
            self.jile.process_params(self.ise, self.ne, 0, 0, 0, 0, 
                                     self.xti, self.eg, self.Tnomabs)
        if self.isc:
            # jilc produces ilc
            self.jilc.process_params(self.isc, self.nc, 0, 0, 0, 0, 
                                     self.xti, self.eg, self.Tnomabs)
        # Constants needed for rb(ib) calculation
        if self.irb:
            self._ck1 = 144. / self.irb / self.area /np.pi/np.pi
            self._ck2 = np.pi*np.pi * np.sqrt(self.irb * self.area) / 24.


    def set_temp_vars(self, temp):
        """
        Calculate temperature-dependent variables, given temp in deg. C
        """
        # Absolute temperature (note self.temp is in deg. C)
        self.Tabs = const.T0 + temp
        # Normalized temp
        self.tnratio = self.Tabs / self.Tnomabs
        tnXTB = pow(self.tnratio, self.xtb)
        # Thermal voltage
        self.vt = const.k * self.Tabs / const.q
        # Temperature-adjusted egap
        self.egap_t = self.eg - .000702 * (self.Tabs**2) / (self.Tabs + 1108.)
        # set temperature in juctions
        self.jif.set_temp_vars(self.Tabs, self.Tnomabs, self.vt, 
                               self.egapn, self.egap_t)
        self.jir.set_temp_vars(self.Tabs, self.Tnomabs, self.vt, 
                               self.egapn, self.egap_t)
        # Adjust ise and isc (which have different temperature variation)
        if self.ise:
            self.jile.set_temp_vars(self.Tabs, self.Tnomabs, self.vt, 
                                    self.egapn, self.egap_t)
            self.jile._t_is /= tnXTB
        if self.isc:
            self.jilc.set_temp_vars(self.Tabs, self.Tnomabs, self.vt, 
                                    self.egapn, self.egap_t)
            self.jilc._t_is /= tnXTB
        # Now some BJT-only variables
        self._bf_t = self.bf * tnXTB
        self._br_t = self.br * tnXTB
        

    def eval_cqs(self, vPort):
        """
        Calculates currents/charges

        Input is a vector may be one of the following, depending on
        parameter values::

          vPort = [vbe, vbc]
          vPort = [vbie, vbic, v1_i] (gyrator voltage, rb != 0)

        Output also depends on parameter values. Charges only present
        if parameters make them different than 0 (i.e., cje, tf, cjc,
        etc. are set to nonzero values)::
        
          iVec = [ibe, ibc, ice]
          iVec = [ibe, ibc, ice, gyr*ib*Rb] (rb != 0)

          qVec = [qbe, qbc]
          qVec = [qbe, qbc, qbx] (rb != 0 and cjc != 1)
        """
        # Invert control voltages if needed
        vPort1 = self._typef * vPort

        # Calculate regular PN junctions currents and charges
        ibf = self.jif.get_id(vPort1[0])
        ibr = self.jif.get_id(vPort1[1])
        if self.ise:
            ile = self.jile.get_id(vPort1[0])
        else:
            ile = 0.
        if self.isc:
            ilc = self.jilc.get_id(vPort1[1])
        else:
            ilc = 0.
        # Kqb
        q1m1 = 1.
        if self.var:
            q1m1 -= vPort1[0] / self.var
        if self.vaf:
            q1m1 -= vPort1[1] / self.vaf
        kqb = 1. / q1m1
        q2 = 0.
        if self.ikf:
            q2 += ibf / self.ikf 
        if self.ikr:
            q2 += ibr / self.ikr
        if q2:
            kqb *= .5 * (1. + np.sqrt(1. + 4. * q2))

        # Create output vector [ibe, ibc, ice, ...]
        iVec = np.zeros(self.ncurrents, dtype = type(ibf))
        qVec = np.zeros(self.ncharges, dtype = type(ibf))
        # ibe
        iVec[0] = ibf / self._bf_t + ile
        # ibc
        iVec[1] = ibr / self._br_t + ilc
        # ice
        iVec[2] = (ibf - ibr) / kqb

        # RB
        if self.rb:
            # Using gyrator
            # vPort1[2] not defined if rb == 0
            # ib has area effect included (removed by _ck1 and _ck2)
            ib = vPort1[2] * glVar.gyr
            if self.irb:
                ib1 = np.abs(ib)
                x = np.sqrt(1. + self._ck1 * ib1) - 1.
                x *= self._ck2 / np.sqrt(ib1)
                tx = np.tan(x)
                c = self.rbm + 3. * (self.rb - self.rbm) \
                    * (tx - x) / (x * tx * tx)
                rb = ad.condassign(ib1, c, self.rb)
            else:
                rb = self.rbm + (self.rb - self.rbm) / kqb
            # Output is gyr * ib * rb.  It is divided by area^2 to
            # compensate that the whole vector is multiplied by area
            # at the end
            iVec[3] = glVar.gyr * ib * rb / pow(self.area, 2)
            vbcx = ib * rb / self.area + vPort1[1]

        # Charges ----------------------------------------------- 

        # Note that if tf == 0 and cje == 0, nothing is calculated and
        # nothing is assigned to the output vector.

        # qbe is the first charge (0)
        if self.tf:
            # Effective tf
            tfeff = self.tf
            if self.vtf:
                x = ibf / (ibf + self.itf)
                tfeff *= (1. + self.xtf * x*x * 
                          ad.safe_exp(vPort1[1] /1.44 /self.vtf))
            qVec[0] = tfeff * ibf
        if self.cje:
            qVec[0] += self.jif.get_qd(vPort1[0]) 

        # qbc 
        if self._qbx:
            if self.tr:
                qVec[-2] = self.tr * ibr
            if self.cjc:
                qVec[-2] += self.jir.get_qd(vPort1[1]) * self.xcjc 
                # qbx
                qVec[-1] = self.jir.get_qd(vbcx) * (1. - self.xcjc)
        else:
            if self.tr:
                qVec[-1] = self.tr * ibr
            if self.cjc:
                qVec[-1] += self.jir.get_qd(vPort1[1]) 

        # Consider area effect and invert currents if needed
        iVec *= self.area * self._typef
        qVec *= self.area * self._typef

        return (iVec, qVec)


    def power(self, vPort, currV):
        """ 
        Calculate total instantaneous power 

        Input: control voltages as in eval_cqs() and currents returned
        by eval_cqs()
        """
        vce = vPort[0] - vPort[1]
        if self.rb:
            # currV[3] = ib * Rb * gyr
            # vPort[2] = ib / gyr
            pRb = currV[3] * vPort[2]
        else:
            pRb = 0.

        # pout = ibe * vbie + ibc * vbic + vce * ice + pRb
        pout = currV[0] * vPort[0] + currV[1] * vPort[1] \
            + currV[2] * vce + pRb

        return pout

    def get_OP(self, vPort):
        """
        Calculates operating point information

        Input: same as eval_cqs
        Output: dictionary with OP variables
        For now it is quite incomplete
        """
        # First we need the Jacobian
        (outV, jac) = self.eval_and_deriv(vPort)

        self.OP = dict(
            VBE = vPort[0],
            VCE = vPort[0] - vPort[1],
            IB = outV[0] + outV[1],
            IC = outV[2] - outV[1],
            IE = - outV[2] - outV[0],
            gm = jac[2,0] - jac[1,0],
            rpi = 1./(jac[0,0] + jac[1,0]),
            )
        return self.OP

    def get_noise(self, f):
        """
        Return noise spectral density at frequency f
        
        Requires a previous call to get_OP() 

        Not implemented yet
        """
        return None
Ejemplo n.º 6
0
    class BJT(IBJT):
        """
    Bipolar Junction Transistor
    ---------------------------

    This device accepts 3 or 4 terminal connections.

    Netlist examples::
    
        bjt:q1 2 3 4 1 model = mypnp isat=4e-17 bf=147 iss=10fA
        bjt:q2 2 3 4  model = mypnp isat=4e-17 bf=147 vaf=80 ikf=4m
        svbjt:q3 2 3 4 1 model = mypnp vaf=80 ikf=4m iss=15fA
    
        # Electro-thermal versions
        bjt_t:q2 2 3 5 1 pout gnd model = mypnp
        svbjt_t:q3 2 3 5 1 pout gnd model = mypnp
    
        # Model statement
        .model mypnp bjt_t (type=pnp isat=5e-17 cje=60fF vje=0.83 mje=0.35)

    Extrinsic Internal Topology
    +++++++++++++++++++++++++++

    RC, RE and a Collector-Bulk connection are added to intrinsic
    BJT models::

                  RC    Term: ct      Term: et   RE
      C (0) o---/\/\/\/--+-----,         4----/\/\/\/----o  E (2)
                         |      \       /
                         |       \     /     
                       -----    ---------
                        / \         |
                       /   \        o 
                       -----
                         |          B (1)
                         o Bulk (3)

    If RE or RC are zero the internal nodes (ct, et) are not
    created. If only 3 connections are specified then the
    Bulk-Collector junction is not connected.

    Important Note
    ++++++++++++++

    This implementation does not account for the power dissipation
    in RE, RC. Use external thermal resistors if that is needed.

    Intrinsic Model Information
    +++++++++++++++++++++++++++

        """ 
        # Additional documentation
        extraDoc = IBJT.__doc__

        # Create electrothermal device
        makeAutoThermal = True
        
        isNonlinear = True
    
        # Device category
        category = "Semiconductor devices"

        # devtype is the 'model' name: remove the 'i' from intrinsic name
        devType = IBJT.devType 
    
        # Do not set numTerms to allow 3 or 4 terminals to be connected
        
        paramDict = dict(
            IBJT.paramDict.items(),
            re = ('Emitter ohmic resistance', 'W', float, 0.),
            rc = ('Collector ohmic resistance', 'W', float, 0.),
            cjs = ('Collector substrate capacitance', 'F', float, 0.),
            mjs = ('substrate junction exponential factor', '', float, 0.),
            vjs = ('substrate junction built in potential', 'V', float, 0.75),
            ns = ('substrate p-n coefficient', '', float, 1.),
            iss = ('Substrate saturation current', 'A', float, 1e-14)
            )

        def __init__(self, instanceName):
            IBJT.__init__(self, instanceName)
            # Collector-bulk junction
            self.cbjtn = Junction()
            self.__doc__ += IBJT.__doc__

        def process_params(self, thermal = False):
            # Remove tape if present
            ad.delete_tape(self)

            if thermal:
                extraTerms = 2
            else:
                extraTerms = 0

            # First check external connections
            if self.numTerms == 3 + extraTerms:
                self.__addCBjtn = False
            elif self.numTerms == 4 + extraTerms:
                self.__addCBjtn = True
            else:
                raise cir.CircuitError( 
                    '{0}: Wrong number of connections. \
Can only be {1} or {2}, {3} found.'.format(self.nodeName, 
                                           3 + extraTerms,
                                           4 + extraTerms,
                                           self.numTerms))

            # Remove internal terminals
            self.clean_internal_terms()
            # Tell autothermal to re-generate thermal ports
            self.__addThermalPorts = True

            extraVCCS = list()
            if self.re:
                # Add et node and translate port descriptions
                self._et = self.add_internal_term('et', 'V')
                extraVCCS += [((2, self._et), (2, self._et), 
                               self.area / self.re)]
            if self.rc:
                # Add ct node and translate port descriptions
                self._ct = self.add_internal_term('ct', 'V')
                extraVCCS += [((0, self._ct), (0, self._ct), 
                               self.area / self.rc)]

            # Process parameters from intrinsic device: emitter and
            # collector terminals are already substituted.
            IBJT.process_params(self)
            # Calculate variables in junction
            self.cbjtn.process_params(self.iss, self.ns, self.fc, self.cjs, 
                                      self.vjs, self.mjs, self.xti, self.eg, 
                                      self.Tnomabs)
            # Add RE, RC resistors (if any)
            self.linearVCCS += extraVCCS
            if self.__addCBjtn:
                # Add bulk-collector junction
                self.__bccn = len(self.controlPorts)
                self.__bcon = len(self.csOutPorts) 
                self.controlPorts.append((3, self._ct))
                self.csOutPorts.append((3, self._ct))
                if self.cjs:
                    self.qsOutPorts.append((3, self._ct))

                # Initial guess for input ports: 
                try:
                    if len(self.vPortGuess) < len(self.controlPorts):
                        self.vPortGuess = np.concatenate(
                            (self.vPortGuess, [1]), axis=0)
                except AttributeError:
                    # Ignore if vPortGuess not provided
                    pass

            # Adjust temperature
            self.set_temp_vars(self.temp)
            
        def set_temp_vars(self, temp):
            """
            Calculate temperature-dependent variables, given temp in deg. C
            """
            # Remove tape if present
            ad.delete_tape(self)
            # First calculate variables from base class
            IBJT.set_temp_vars(self, temp)
            # Adjust collector-bulk junction temperature
            self.cbjtn.set_temp_vars(self.Tabs, self.Tnomabs, self.vt, 
                                     self.egapn, self.egap_t)


        def eval_cqs(self, vPort, saveOP = False):
            """
            vPort is a vector with control voltages 
            """
            if self.__addCBjtn:
                # calculate currents and charges in base class
                (iVec1, qVec1) = IBJT.eval_cqs(self, vPort)
                # Add contribution of substrate. Ignore extra control
                # ports only used for power calculation
                v1 = vPort[self.__bccn] * self._typef
                isub = self.cbjtn.get_id(v1) * self.area * self._typef
                iVec = np.concatenate((iVec1, [isub]), axis = 0)
                if self.cjs:
                    qsub = self.cbjtn.get_qd(v1) * self.area * self._typef
                    qVec = np.concatenate((qVec1, [qsub]), axis = 0)
                else:
                    qVec = qVec1
                return (iVec, qVec)
            else:
                return IBJT.eval_cqs(self, vPort)
    
        # Create these using the AD facility
        eval_and_deriv = ad.eval_and_deriv
        eval = ad.eval
    
        def power(self, vPort, currV):
            """ 
            Calculate total instantaneous power 

            Power in RE, RC not considered.

            Input: control voltages as in eval_cqs() and currents
            returned by eval_cqs()
            """
            pout = IBJT.power(self, vPort, currV)
            if self.__addCBjtn:
                # add power from substrate junction (RE, RC not counted)
                pout += vPort[self.__bccn] * currV[self.__bcon]

            return pout
Ejemplo n.º 7
0
class Device(cir.Element):
    """
    Cubic Curtice-Ettemberg Intrinsic MESFET Model
    ----------------------------------------------

    Model derived from fREEDA 1.4 MesfetCT model adapted to re-use
    junction code from ``diode.py``. Some parameter names have been
    changed: ``isat``, ``tau``. Uses symmetric diodes and
    capacitances. Works in reversed mode.

    Terminal order: 0 Drain, 1 Gate, 2 Source::

               Drain 0
                       o
                       |
                       |
                   |---+
                   |
      Gate 1 o---->|
                   |
                   |---+
                       |
                       |
                       o
              Source 2

    Netlist example::

        mesfetc:m1 2 3 4 a0=0.09910 a1=0.08541 a2=-0.02030 a3=-0.01543

    Internal Topology::

                   ,----------------,------------,--o 0 (D)
                   |                |            |
                  /^\               |            |
                 ( | ) igd(Vgd)   ----- Cgd      |
                  \|/             -----          |
                   |                |           /|\ 
        (G) 1 o----+----------------,          ( | ) ids(Vgs, Vgd)
                   |                |           \V/               
                  /|\               |            |
                 ( | ) igs(Vgs)   ----- Cgs      |
                  \V/             -----          |
                   |                |            |
                   `----------------'------------'--o 2 (S)

    """

    # Device category
    category = "Semiconductor devices"

    devType = "mesfetc"
    paramDict = dict(
        cir.Element.tempItem,
        a0=("Drain saturation current for Vgs=0", "A", float, 0.1),
        a1=("Coefficient for V1", "A/V", float, 0.05),
        a2=("Coefficient for V1^2", "A/V^2", float, 0.0),
        a3=("Coefficient for V1^3", "A/V^3", float, 0.0),
        beta=("V1 dependance on Vds", "1/V", float, 0.0),
        vds0=("Vds at which BETA was measured", "V", float, 4.0),
        gama=("Slope of drain characteristic in the linear region", "1/V", float, 1.5),
        vt0=(
            "Voltage at which the channel current is forced to be zero\
 for Vgs<=Vto",
            "V",
            float,
            -np.inf,
        ),
        cgs0=("Gate-source Schottky barrier capacitance for Vgs=0", "F", float, 0.0),
        cgd0=("Gate-drain Schottky barrier capacitance for Vgd=0", "F", float, 0.0),
        isat=("Diode saturation current", "A", float, 0.0),
        n=("Diode ideality factor", "", float, 1.0),
        ib0=("Breakdown current parameter", "A", float, 0.0),
        nr=("Breakdown ideality factor", "", float, 10.0),
        vbd=("Breakdown voltage", "V", float, np.inf),
        tau=("Channel transit time", "s", float, 0.0),
        vbi=("Built-in potential of the Schottky junctions", "V", float, 0.8),
        fcc=("Forward-bias depletion capacitance coefficient", "V", float, 0.5),
        tnom=("Nominal temperature", "C", float, 27.0),
        avt0=("Pinch-off voltage (VP0 or VT0) linear temp. coefficient", "1/K", float, 0.0),
        bvt0=("Pinch-off voltage (VP0 or VT0) quadratic temp. coefficient", "1/K^2", float, 0.0),
        tbet=("BETA power law temperature coefficient", "1/K", float, 0),
        tm=("Ids linear temp. coeff.", "1/K", float, 0.0),
        tme=("Ids power law temp. coeff.", "1/K^2", float, 0.0),
        eg0=("Barrier height at 0 K", "eV", float, 0.8),
        mgs=("Gate-source grading coefficient", "", float, 0.5),
        mgd=("Gate-drain grading coefficient", "", float, 0.5),
        xti=("Diode saturation current temperature exponent", "", float, 2.0),
        area=("Area multiplier", "", float, 1.0),
    )

    numTerms = 3

    # Create electrothermal device
    makeAutoThermal = True

    isNonlinear = True
    nDelays = 2

    # igs, igd, ids
    csOutPorts = [(1, 2), (1, 0), (0, 2)]
    # Controling voltages are Vgs, Vgd
    controlPorts = [(1, 2), (1, 0)]
    # Time-delayed control port added later. Guess includes time-delayed port
    vPortGuess = np.array([0.0, 0.0, 0.0, 0.0])
    # charge sources
    qsOutPorts = [(1, 2), (1, 0)]

    def __init__(self, instanceName):
        """
        Here the Element constructor must be called. Do not connect
        internal nodes here.
        """
        cir.Element.__init__(self, instanceName)
        self.diogs = Junction()
        self.diogd = Junction()

    def process_params(self, thermal=False):
        # Called once the external terminals have been connected and
        # the non-default parameters have been set. Make sanity checks
        # here. Internal terminals/devices should also be defined
        # here.  Raise cir.CircuitError if a fatal error is found.
        ad.delete_tape(self)
        # Time-delayed control port
        self.delayedContPorts = [(1, 2, self.tau), (1, 0, self.tau)]

        # Absolute nominal temperature
        self.Tnomabs = self.tnom + const.T0
        self.egapn = self.eg0 - 0.000702 * (self.Tnomabs ** 2) / (self.Tnomabs + 1108.0)

        # Calculate variables in junctions
        self.diogs.process_params(
            self.isat, self.n, self.fcc, self.cgs0, self.vbi, self.mgs, self.xti, self.eg0, self.Tnomabs
        )
        self.diogd.process_params(
            self.isat, self.n, self.fcc, self.cgd0, self.vbi, self.mgd, self.xti, self.eg0, self.Tnomabs
        )
        if not thermal:
            # Calculate temperature-dependent variables
            self.set_temp_vars(self.temp)

    def set_temp_vars(self, temp):
        """
        Calculate temperature-dependent variables, given temp in deg. C
        """
        ad.delete_tape(self)
        # Absolute temperature (note self.temp is in deg. C)
        self.Tabs = const.T0 + temp
        # Thermal voltage
        self.vt = const.k * self.Tabs / const.q
        # Temperature-adjusted egap
        self.egap_t = self.eg0 - 0.000702 * (self.Tabs ** 2) / (self.Tabs + 1108.0)
        # Everything else is handled by junctions
        self.diogs.set_temp_vars(self.Tabs, self.Tnomabs, self.vt, self.egapn, self.egap_t)
        self.diogd.set_temp_vars(self.Tabs, self.Tnomabs, self.vt, self.egapn, self.egap_t)

        delta_T = temp - self.tnom
        self._k6 = self.nr * self.vt
        self._Vt0 = self.vt0 * (1.0 + (delta_T * self.avt0) + (delta_T ** 2 * self.bvt0))
        if self.tbet:
            self._Beta = self.beta * pow(1.01, (delta_T * self.tbet))
        else:
            self._Beta = self.beta

        if self.tme and self.tm:
            self._idsFac = pow((1.0 + delta_T * self.tm), self.tme)
        else:
            self._idsFac = 1.0

    def eval_cqs(self, vPort, saveOP=False):
        """
        Calculates gate and drain current. Input is a vector as follows:
        vPort = [vgs(t), vgd(t), vgs(t-tau), vgd(t-tau)]

        saveOP has no effect for now
        """
        # Calculate junction currents
        igs = self.diogs.get_id(vPort[0])
        qgs = self.diogs.get_qd(vPort[0])
        igd = self.diogd.get_id(vPort[1])
        qgd = self.diogd.get_qd(vPort[1])

        # Add breakdown current
        igs -= self.ib0 * np.exp(-(vPort[0] + self.vbd) / self._k6)
        igd -= self.ib0 * np.exp(-(vPort[1] + self.vbd) / self._k6)

        DtoSswap = ad.condassign(vPort[0] - vPort[1], 1.0, -1.0)
        vds = DtoSswap * (vPort[0] - vPort[1])
        vgsi = ad.condassign(DtoSswap, vPort[0], vPort[1])
        vgsiT = ad.condassign(DtoSswap, vPort[2], vPort[3])
        # Calculate ids.
        vx = vgsiT * (1.0 + self._Beta * (self.vds0 - vds))
        ids = (self.a0 + vx * (self.a1 + vx * (self.a2 + vx * self.a3))) * np.tanh(self.gama * vds) * self._idsFac
        # vgsiT makes more sense than vgsi below? (vgsi in original doc)
        ids = ad.condassign((vgsi - self._Vt0), ids, 0.0)
        # Must ensure ids > 0 for power conservation
        ids = ad.condassign(ids, ids, 0.0)

        # Return numpy array with one element per current source.
        iVec = np.array([igs, igd, ids * DtoSswap]) * self.area
        qVec = np.array([qgs, qgd]) * self.area

        return (iVec, qVec)

    # Use AD for eval and deriv function
    eval_and_deriv = ad.eval_and_deriv
    eval = ad.eval
    #    get_op_vars = ad.get_op_vars

    def power(self, vPort, currV):
        """ 
        Calculate total instantaneous power 

        Input: control voltages and currents from eval_cqs()
        """
        vds = vPort[0] - vPort[1]
        # pout = vds*ids + vgs*igs + vgd*igd
        pout = vds * currV[2] + vPort[0] * currV[0] + vPort[1] * currV[1]
        return pout

    def get_OP(self, vPort):
        """
        Calculates operating point information

        Input:  vPort = [vgs , vgd , vgs]
        Output: dictionary with OP variables
        """
        # First we need the Jacobian
        (outV, jac) = self.eval_and_deriv(vPort)
        #        opV = self.get_op_vars(vPort)

        self.OP = dict(VGS=vPort[0], VDS=vPort[0] - vPort[1], IDS=outV[2], IGS=outV[0], IGD=outV[1])
        return self.OP

    def get_noise(self, f):
        """
        Return noise spectral density at frequency f
        
        Requires a previous call to get_OP() 

        Not implemented yet
        """
        return None
Ejemplo n.º 8
0
    class ExtMOS(IMOS):
        """
    Extrinsic Silicon MOSFET 
    ------------------------

    Extrinsic Internal Topology
    +++++++++++++++++++++++++++

    The model adds the following to the intrinsic model (for NMOS)::

                                     o D (0)
                                     |
                                     \ 
                      Cgdo           / Rd       Drain/source area plus
                                     \          sidewall model
                       ||            |-----------,-----,
                ,------||------------|           |     |   
                |      ||            |         ----- ----- 
                |                ||---         -----  / \  
                |                ||              |   -----
      G (1) o---+----------------||<-------------+-----+------o B (3)
                |                ||              |   -----
                |                ||---         -----  \ / 
                |      ||            |         ----- -----
                `------||------------|           |     |
                       ||            |-----------'-----'
                                     \ 
                      Cgso           / Rs 
                                     \ 
                                     |
                                     o S (2)
    

    Note: electrothermal implementation (if any) does not account for
    the power dissipation in Rd and Rs. Use external thermal resistors
    if that is needed.

        """ 
        # devtype is the 'model' name: remove the '_i' from intrinsic name
        devType = 'mos' + IMOS.devType.split('_i')[0]

        # Additional documentation
        extraDoc = """

    Netlist examples
    ++++++++++++++++

    The model accepts extrinsic plus intrinsic parameters (only
    extrinsic parameters shown in example)::
    
        {0}:m1 2 3 4 gnd w=10u l=1u asrc=4e-12 ps=8e=12 model=nch
        {0}:m2 4 5 6 6 w=30e-6 l=1e-6 pd=8u ps=16u type=p

        .model nch {0} (type=n js=1e-3 cj=2e-4 cjsw=1n)
    
    Intrinsic model
    +++++++++++++++

    See **{1}** intrinsic model documentation.

        """.format(devType, IMOS.devType)

        paramDict = dict(
            IMOS.paramDict.items(),
            m = ('Parallel multiplier', '', float, 1.),
            cgdo = ('Gate-drain overlap capacitance per meter channel width',
                    'F/m', float, 0.),
            cgso = ('Gate-source overlap capacitance per meter channel width',
                    'F/m', float, 0.),
            cgbo = ('Gate-bulk overlap capacitance per meter channel length',
                    'F/m', float, 0.),
            rsh = ('Drain and source diffusion sheet resistance', 
                   'Ohm/square', float, 0.),
            js = ('Source drain junction current density', 'A/m^2', float, 0.),
            jssw = ('Source drain sidewall junction current density', 
                    'A/m', float, 0.),
            pb = ('Built in potential of source drain junction', 
                  'V', float, .8),
            mj = ('Grading coefficient of source drain junction', 
                  '', float, .5),
            pbsw = ('Built in potential of source, drain junction sidewall',
                    'V', float, .8),
            mjsw = ('Grading coefficient of source drain junction sidewall',
                    '', float, .33),
            cj = ('Source drain junction capacitance per unit area',
                  'F/m^2', float, 0.),
            cjsw = (
                'Source drain junction sidewall capacitance per unit length', 
                'F/m', float, 0.),
            ad = ('Drain area', 'm^2', float, 0.),
            asrc = ('Source area', 'm^2', float, 0.),
            pd = ('Drain perimeter', 'm', float, 0.),
            ps = ('Source perimeter', 'm', float, 0.),
            nrd = ('Number of squares in drain', 'squares', float, 1.),
            nrs = ('Number of squares in source', 'squares', float, 1.),
            fc = ('Coefficient for forward-bias depletion capacitances', ' ', 
                  float, .5),
            xti = ('Junction saturation current temperature exponent', '', 
                   float, 3.),
            eg0 = ('Energy bandgap', 'eV', float, 1.11)
            )

        def __init__(self, instanceName):
            IMOS.__init__(self, instanceName)
            self.__doc__ += IMOS.__doc__

        def process_params(self, thermal = False):
            # Remove tape if present
            ad.delete_tape(self)

            # Remove internal terminals (there should be none created
            # by intrinsic model)
            self.clean_internal_terms()
            # Tell autothermal (if used) to re-generate thermal ports
            self.__addThermalPorts = True

            # By default drain and source are terminals 0 and 2
            self.__di = 0
            self.__si = 2

            # Resistances
            extraVCCS = list()
            if self.rsh:
                if self.nrd:
                    # Drain resistor
                    self.__di = self.add_internal_term('di', 'V')
                    extraVCCS += [((0, self.__di), (0, self.__di), 
                                   1. / self.rsh / self.nrd)]
                if self.nrs:
                    # Source resistor
                    self.__si = self.add_internal_term('si', 'V')
                    extraVCCS += [((2, self.__si), (2, self.__si), 
                                   1. / self.rsh / self.nrs)]
            # Linear capacitances
            extraVCQS = list()
            if self.cgdo:
                # Gate-drain ovelrlap cap
                extraVCQS += [((1, self.__di), (1, self.__di),
                               self.cgdo * self.w)]
            if self.cgso:
                # Gate-source ovelrlap cap
                extraVCQS += [((1, self.__si), (1, self.__si),
                               self.cgso * self.w)]
            if self.cgbo:
                # Gate-bulk ovelrlap cap
                extraVCQS += [((1, 3), (1, 3),
                               self.cgbo * self.l)]

            # Add extra linear resistors/caps (if any)
            self.linearVCCS = extraVCCS
            self.linearVCQS = extraVCQS

            # Override nonlinear port specs if needed
            if extraVCCS:
                # Ids, Idb, Isb
                self.csOutPorts = [(self.__di, self.__si), (self.__di, 3), 
                                   (self.__si, 3)]
                # Controling voltages are DB, GB and SB
                self.controlPorts = [(self.__di, 3), (1, 3), (self.__si, 3)]
                # One charge source connected to each D, G, S
                self.qsOutPorts = [(self.__di, 3), (1, 3), (self.__si, 3)]

            # Calculate some variables (that may also be calculated in
            # intrinsic model)
            self.__Tnabs = const.T0 + self.tnom
            self.__egapn = self.eg0 - .000702 * (self.__Tnabs**2) \
                / (self.__Tnabs + 1108.)
            # Initialize variables in junctions
            if self.ad:
                self.dj = Junction()
                self.dj.process_params(isat = self.js * self.ad, 
                                       cj0 = self.cj * self.ad, 
                                       vj = self.pb, m = self.mj, 
                                       n = 1., fc = self.fc, 
                                       xti = self.xti, eg0 = self.eg0, 
                                       Tnomabs = self.__Tnabs)
            if self.asrc:
                self.sj = Junction()
                self.sj.process_params(isat = self.js * self.asrc, 
                                       cj0 = self.cj * self.asrc, 
                                       vj = self.pb, m = self.mj, 
                                       n = 1., fc = self.fc, 
                                       xti = self.xti, eg0 = self.eg0, 
                                       Tnomabs = self.__Tnabs)
            if self.pd:
                self.djsw = Junction()
                self.djsw.process_params(isat = self.jssw * self.pd, 
                                         cj0 = self.cjsw * self.pd, 
                                         vj = self.pbsw, m = self.mjsw, 
                                         n = 1., fc = self.fc, 
                                         xti = self.xti, eg0 = self.eg0, 
                                         Tnomabs = self.__Tnabs)
            if self.ps:
                self.sjsw = Junction()
                self.sjsw.process_params(isat = self.jssw * self.ps, 
                                         cj0 = self.cjsw * self.ps, 
                                         vj = self.pbsw, m = self.mjsw, 
                                         n = 1., fc = self.fc, 
                                         xti = self.xti, eg0 = self.eg0, 
                                         Tnomabs = self.__Tnabs)

            # Process parameters from intrinsic device:
            # set_temp_vars() called there
            IMOS.process_params(self)

            
        def set_temp_vars(self, temp):
            """
            Calculate temperature-dependent variables, given temp in deg. C
            """
            # Remove tape if present
            ad.delete_tape(self)
            # First calculate variables from base class
            IMOS.set_temp_vars(self, temp)
            # Absolute temperature
            Tabs = temp + const.T0
            # Temperature-adjusted egap
            egap_t = self.eg0 - .000702 * (Tabs**2) / (Tabs + 1108.)
            # Thermal voltage
            Vt = const.k * Tabs / const.q
            # Adjust junction temperatures
            if self.ad:
                self.dj.set_temp_vars(Tabs, self.__Tnabs, Vt, 
                                      self.__egapn, egap_t)
            if self.pd:
                self.djsw.set_temp_vars(Tabs, self.__Tnabs, Vt, 
                                        self.__egapn, egap_t)
            if self.asrc:
                self.sj.set_temp_vars(Tabs, self.__Tnabs, Vt, 
                                      self.__egapn, egap_t)
            if self.ps:
                self.sjsw.set_temp_vars(Tabs, self.__Tnabs, Vt, 
                                        self.__egapn, egap_t)
            

        def eval_cqs(self, vPort, saveOP = False):
            """
            vPort is a vector with control voltages 
            """
            # calculate currents and charges in base class
            if saveOP:
                (iVec, qVec, opVec) = IMOS.eval_cqs(self, vPort, saveOP)
            else:
                (iVec, qVec) = IMOS.eval_cqs(self, vPort)
            # Add contribution drain diode
            v1 = -vPort[0] * self._tf
            if self.ad:
                # substract to idb
                iVec[1] -= self.dj.get_id(v1) * self._tf
                if self.cj:
                    # substract to qd
                    qVec[0] -= self.dj.get_qd(v1) * self._tf
            if self.pd:
                # substract to idb
                iVec[1] -= self.djsw.get_id(v1) * self._tf
                if self.cjsw:
                    qVec[0] -= self.djsw.get_qd(v1) * self._tf
            # Add contribution source diode
            v1 = -vPort[2] * self._tf
            if self.asrc:
                # substract to isb
                iVec[2] -= self.sj.get_id(v1) * self._tf
                if self.cj:
                    # substract to qs
                    qVec[2] -= self.sj.get_qd(v1) * self._tf
            if self.ps:
                # substract to isb
                iVec[2] -= self.sjsw.get_id(v1) * self._tf
                if self.cjsw:
                    qVec[2] -= self.sjsw.get_qd(v1) * self._tf

            # Apply parallel multiplier
            iVec *= self.m
            qVec *= self.m

            if saveOP:
                return (iVec, qVec, opVec)
            else:
                return (iVec, qVec)

    
        # Create these using the AD facility
        eval_and_deriv = ad.eval_and_deriv
        eval = ad.eval
    
        def power(self, vPort, currV):
            """ 
            Calculate total instantaneous power 

            Power in RE, RC not considered.

            Input: control voltages as in eval_cqs() and currents
            returned by eval_cqs()
            """
            pout = IMOS.power(self, vPort, currV)
            if self.__addCBjtn:
                # add power from substrate junction and RE, RC
                pout += vPort[self.__bccn] * currV[self.__bcon]

            return pout
Ejemplo n.º 9
0
        def process_params(self, thermal = False):
            # Remove tape if present
            ad.delete_tape(self)

            # Remove internal terminals (there should be none created
            # by intrinsic model)
            self.clean_internal_terms()
            # Tell autothermal (if used) to re-generate thermal ports
            self.__addThermalPorts = True

            # By default drain and source are terminals 0 and 2
            self.__di = 0
            self.__si = 2

            # Resistances
            extraVCCS = list()
            if self.rsh:
                if self.nrd:
                    # Drain resistor
                    self.__di = self.add_internal_term('di', 'V')
                    extraVCCS += [((0, self.__di), (0, self.__di), 
                                   1. / self.rsh / self.nrd)]
                if self.nrs:
                    # Source resistor
                    self.__si = self.add_internal_term('si', 'V')
                    extraVCCS += [((2, self.__si), (2, self.__si), 
                                   1. / self.rsh / self.nrs)]
            # Linear capacitances
            extraVCQS = list()
            if self.cgdo:
                # Gate-drain ovelrlap cap
                extraVCQS += [((1, self.__di), (1, self.__di),
                               self.cgdo * self.w)]
            if self.cgso:
                # Gate-source ovelrlap cap
                extraVCQS += [((1, self.__si), (1, self.__si),
                               self.cgso * self.w)]
            if self.cgbo:
                # Gate-bulk ovelrlap cap
                extraVCQS += [((1, 3), (1, 3),
                               self.cgbo * self.l)]

            # Add extra linear resistors/caps (if any)
            self.linearVCCS = extraVCCS
            self.linearVCQS = extraVCQS

            # Override nonlinear port specs if needed
            if extraVCCS:
                # Ids, Idb, Isb
                self.csOutPorts = [(self.__di, self.__si), (self.__di, 3), 
                                   (self.__si, 3)]
                # Controling voltages are DB, GB and SB
                self.controlPorts = [(self.__di, 3), (1, 3), (self.__si, 3)]
                # One charge source connected to each D, G, S
                self.qsOutPorts = [(self.__di, 3), (1, 3), (self.__si, 3)]

            # Calculate some variables (that may also be calculated in
            # intrinsic model)
            self.__Tnabs = const.T0 + self.tnom
            self.__egapn = self.eg0 - .000702 * (self.__Tnabs**2) \
                / (self.__Tnabs + 1108.)
            # Initialize variables in junctions
            if self.ad:
                self.dj = Junction()
                self.dj.process_params(isat = self.js * self.ad, 
                                       cj0 = self.cj * self.ad, 
                                       vj = self.pb, m = self.mj, 
                                       n = 1., fc = self.fc, 
                                       xti = self.xti, eg0 = self.eg0, 
                                       Tnomabs = self.__Tnabs)
            if self.asrc:
                self.sj = Junction()
                self.sj.process_params(isat = self.js * self.asrc, 
                                       cj0 = self.cj * self.asrc, 
                                       vj = self.pb, m = self.mj, 
                                       n = 1., fc = self.fc, 
                                       xti = self.xti, eg0 = self.eg0, 
                                       Tnomabs = self.__Tnabs)
            if self.pd:
                self.djsw = Junction()
                self.djsw.process_params(isat = self.jssw * self.pd, 
                                         cj0 = self.cjsw * self.pd, 
                                         vj = self.pbsw, m = self.mjsw, 
                                         n = 1., fc = self.fc, 
                                         xti = self.xti, eg0 = self.eg0, 
                                         Tnomabs = self.__Tnabs)
            if self.ps:
                self.sjsw = Junction()
                self.sjsw.process_params(isat = self.jssw * self.ps, 
                                         cj0 = self.cjsw * self.ps, 
                                         vj = self.pbsw, m = self.mjsw, 
                                         n = 1., fc = self.fc, 
                                         xti = self.xti, eg0 = self.eg0, 
                                         Tnomabs = self.__Tnabs)

            # Process parameters from intrinsic device:
            # set_temp_vars() called there
            IMOS.process_params(self)
Ejemplo n.º 10
0
class Device(cir.Element):
    """
    Cubic Curtice-Ettemberg Intrinsic MESFET Model
    ----------------------------------------------

    Model derived from fREEDA 1.4 MesfetCT model adapted to re-use
    junction code from ``diode.py``. Some parameter names have been
    changed: ``isat``, ``tau``. Uses symmetric diodes and
    capacitances. Works in reversed mode.

    Terminal order: 0 Drain, 1 Gate, 2 Source::

               Drain 0
                       o
                       |
                       |
                   |---+
                   |
      Gate 1 o---->|
                   |
                   |---+
                       |
                       |
                       o
              Source 2

    Netlist example::

        mesfetc:m1 2 3 4 a0=0.09910 a1=0.08541 a2=-0.02030 a3=-0.01543

    Internal Topology::

                   ,----------------,------------,--o 0 (D)
                   |                |            |
                  /^\               |            |
                 ( | ) igd(Vgd)   ----- Cgd      |
                  \|/             -----          |
                   |                |           /|\ 
        (G) 1 o----+----------------,          ( | ) ids(Vgs, Vgd)
                   |                |           \V/               
                  /|\               |            |
                 ( | ) igs(Vgs)   ----- Cgs      |
                  \V/             -----          |
                   |                |            |
                   `----------------'------------'--o 2 (S)

    """
    # Device category
    category = "Semiconductor devices"

    devType = "mesfetc"
    paramDict = dict(
        cir.Element.tempItem,
        a0=('Drain saturation current for Vgs=0', 'A', float, 0.1),
        a1=('Coefficient for V1', 'A/V', float, 0.05),
        a2=('Coefficient for V1^2', 'A/V^2', float, 0.),
        a3=('Coefficient for V1^3', 'A/V^3', float, 0.),
        beta=('V1 dependance on Vds', '1/V', float, 0.),
        vds0=('Vds at which BETA was measured', 'V', float, 4.),
        gama=('Slope of drain characteristic in the linear region', '1/V',
              float, 1.5),
        vt0=('Voltage at which the channel current is forced to be zero\
 for Vgs<=Vto', 'V', float, -np.inf),
        cgs0=('Gate-source Schottky barrier capacitance for Vgs=0', 'F', float,
              0.),
        cgd0=('Gate-drain Schottky barrier capacitance for Vgd=0', 'F', float,
              0.),
        isat=('Diode saturation current', 'A', float, 0.),
        n=('Diode ideality factor', '', float, 1.),
        ib0=('Breakdown current parameter', 'A', float, 0.),
        nr=('Breakdown ideality factor', '', float, 10.),
        vbd=('Breakdown voltage', 'V', float, np.inf),
        tau=('Channel transit time', 's', float, 0.),
        vbi=('Built-in potential of the Schottky junctions', 'V', float, 0.8),
        fcc=('Forward-bias depletion capacitance coefficient', 'V', float,
             0.5),
        tnom=('Nominal temperature', 'C', float, 27.),
        avt0=('Pinch-off voltage (VP0 or VT0) linear temp. coefficient', '1/K',
              float, 0.),
        bvt0=('Pinch-off voltage (VP0 or VT0) quadratic temp. coefficient',
              '1/K^2', float, 0.),
        tbet=('BETA power law temperature coefficient', '1/K', float, 0),
        tm=('Ids linear temp. coeff.', '1/K', float, 0.),
        tme=('Ids power law temp. coeff.', '1/K^2', float, 0.),
        eg0=('Barrier height at 0 K', 'eV', float, 0.8),
        mgs=('Gate-source grading coefficient', '', float, 0.5),
        mgd=('Gate-drain grading coefficient', '', float, 0.5),
        xti=('Diode saturation current temperature exponent', '', float, 2.),
        area=('Area multiplier', '', float, 1.),
    )

    numTerms = 3

    # Create electrothermal device
    makeAutoThermal = True

    isNonlinear = True
    nDelays = 2

    # igs, igd, ids
    csOutPorts = [(1, 2), (1, 0), (0, 2)]
    # Controling voltages are Vgs, Vgd
    controlPorts = [(1, 2), (1, 0)]
    # Time-delayed control port added later. Guess includes time-delayed port
    vPortGuess = np.array([0., 0., 0., 0.])
    # charge sources
    qsOutPorts = [(1, 2), (1, 0)]

    def __init__(self, instanceName):
        """
        Here the Element constructor must be called. Do not connect
        internal nodes here.
        """
        cir.Element.__init__(self, instanceName)
        self.diogs = Junction()
        self.diogd = Junction()

    def process_params(self, thermal=False):
        # Called once the external terminals have been connected and
        # the non-default parameters have been set. Make sanity checks
        # here. Internal terminals/devices should also be defined
        # here.  Raise cir.CircuitError if a fatal error is found.
        ad.delete_tape(self)
        # Time-delayed control port
        self.delayedContPorts = [(1, 2, self.tau), (1, 0, self.tau)]

        # Absolute nominal temperature
        self.Tnomabs = self.tnom + const.T0
        self.egapn = self.eg0 - .000702 * (self.Tnomabs**2) \
            / (self.Tnomabs + 1108.)

        # Calculate variables in junctions
        self.diogs.process_params(self.isat, self.n, self.fcc, self.cgs0,
                                  self.vbi, self.mgs, self.xti, self.eg0,
                                  self.Tnomabs)
        self.diogd.process_params(self.isat, self.n, self.fcc, self.cgd0,
                                  self.vbi, self.mgd, self.xti, self.eg0,
                                  self.Tnomabs)
        if not thermal:
            # Calculate temperature-dependent variables
            self.set_temp_vars(self.temp)

    def set_temp_vars(self, temp):
        """
        Calculate temperature-dependent variables, given temp in deg. C
        """
        ad.delete_tape(self)
        # Absolute temperature (note self.temp is in deg. C)
        self.Tabs = const.T0 + temp
        # Thermal voltage
        self.vt = const.k * self.Tabs / const.q
        # Temperature-adjusted egap
        self.egap_t = self.eg0 - .000702 * (self.Tabs**2) / (self.Tabs + 1108.)
        # Everything else is handled by junctions
        self.diogs.set_temp_vars(self.Tabs, self.Tnomabs, self.vt, self.egapn,
                                 self.egap_t)
        self.diogd.set_temp_vars(self.Tabs, self.Tnomabs, self.vt, self.egapn,
                                 self.egap_t)

        delta_T = temp - self.tnom
        self._k6 = self.nr * self.vt
        self._Vt0 = self.vt0 * (1. + (delta_T * self.avt0) +
                                (delta_T**2 * self.bvt0))
        if (self.tbet):
            self._Beta = self.beta * pow(1.01, (delta_T * self.tbet))
        else:
            self._Beta = self.beta

        if self.tme * self.tm != 0.:
            self._idsFac = pow((1. + delta_T * self.tm), self.tme)
        else:
            self._idsFac = 1.

    def eval_cqs(self, vPort, getOP=False):
        """
        Calculates gate and drain current. Input is a vector as follows:
        vPort = [vgs(t), vgd(t), vgs(t-tau), vgd(t-tau)]

        getOP has no effect for now
        """
        # Calculate junction currents
        igs = self.diogs.get_id(vPort[0])
        qgs = self.diogs.get_qd(vPort[0])
        igd = self.diogd.get_id(vPort[1])
        qgd = self.diogd.get_qd(vPort[1])

        # Add breakdown current
        igs -= self.ib0 * np.exp(-(vPort[0] + self.vbd) / self._k6)
        igd -= self.ib0 * np.exp(-(vPort[1] + self.vbd) / self._k6)

        DtoSswap = ad.condassign(vPort[0] - vPort[1], 1., -1.)
        vds = DtoSswap * (vPort[0] - vPort[1])
        vgsi = ad.condassign(DtoSswap, vPort[0], vPort[1])
        vgsiT = ad.condassign(DtoSswap, vPort[2], vPort[3])
        # Calculate ids.
        vx = vgsiT * (1. + self._Beta * (self.vds0 - vds))
        ids = (self.a0 + vx * (self.a1 + vx * (self.a2 + vx  * self.a3))) \
            * np.tanh(self.gama * vds) * self._idsFac
        # vgsiT makes more sense than vgsi below? (vgsi in original doc)
        ids = ad.condassign((vgsi - self._Vt0), ids, 0.)
        # Must ensure ids > 0 for power conservation
        ids = ad.condassign(ids, ids, 0.)

        # Return numpy array with one element per current source.
        iVec = np.array([igs, igd, ids * DtoSswap]) * self.area
        qVec = np.array([qgs, qgd]) * self.area

        return (iVec, qVec)

    # Use AD for eval and deriv function
    eval_and_deriv = ad.eval_and_deriv
    eval = ad.eval

    def power(self, vPort, currV):
        """ 
        Calculate total instantaneous power 

        Input: control voltages and currents from eval_cqs()
        """
        vds = vPort[0] - vPort[1]
        # pout = vds*ids + vgs*igs + vgd*igd
        pout = vds * currV[2] + vPort[0] * currV[0] + vPort[1] * currV[1]
        return pout

    def get_OP(self, vPort):
        """
        Calculates operating point information

        Input:  vPort = [vgs , vgd , vgs]
        Output: dictionary with OP variables
        """
        # First we need the Jacobian
        (outV, jac) = self.eval_and_deriv(vPort)

        opDict = dict(VGS=vPort[0],
                      VDS=vPort[0] - vPort[1],
                      IDS=outV[2],
                      IGS=outV[0],
                      IGD=outV[1])
        return opDict

    def get_noise(self, f):
        """
        Return noise spectral density at frequency f
        
        Requires a previous call to get_OP() 

        Not implemented yet
        """
        return None
Ejemplo n.º 11
0
 def __init__(self, instanceName):
     IBJT.__init__(self, instanceName)
     # Collector-bulk junction
     self.cbjtn = Junction()
     self.__doc__ += IBJT.__doc__
Ejemplo n.º 12
0
class BJTi(cir.Element):
    """
    Gummel-Poon intrinsic BJT model

    This implementation based mainly on previous implementation in
    carrot and some equations from Pspice manual.
    
    Terminal order: 0 Collector, 1 Base, 2 Emitter::

                      
          C (0) o----,         4----o  E (2)
                      \       /
                       \     /
                      ---------
                          |
                          o 
        
                          B (1)

    Can be used for NPN or PNP transistors.

    Intrinsic Internal Topology
    +++++++++++++++++++++++++++

    Internally may add 2 additional nodes (plus reference) if rb is
    not zero: Bi for the internal base node and tib to measure the
    internal base current and calculate Rb(ib). The possible
    configurations are described here.

    1. If RB == 0::

                         +----------------+--o 0 (C)
                         |                |
                        /^\               |
                       ( | ) ibc(vbc)     |
                        \|/               |       
                         |               /|\       
         (B) 1 o---------+              ( | ) ice    
                         |               \V/      
                        /|\               |       
                       ( | ) ibe(vbe)     |
                        \V/               |
                         |                |
                         +----------------+--o 2 (E)

    2. If RB != 0::

                                     +----------------+--o 0 (C)
                                     |                |
                                    /^\               |
                                   ( | ) ibc(vbc)     |
                    gyr * tib       \|/               |       
                     ,---,           |               /|\       
         (B) 1 o----( --> )----------+ Term : Bi    ( | ) ice    
                     `---`           |               \V/      
                                    /|\               |       
                                   ( | ) ibe(vbe)     |
                                    \V/               |
                                     |                |
                                     +----------------+--o 2 (E)
                     gyr v(1,Bi)  
                      ,---,       
                 +---( <-- )------+
                 |    `---`       |
          tref   |                | voltage: ib/gyr
             ,---+                |
             |   |    ,---,       |         
             |   +---( --> )------+ Term : ib
             |        `---`       
            ---     gyr ib Rb(ib)
             V      
                                           
    Charge sources are connected between internal nodes defined
    above. If xcjc is not 1 but RB is zero, xcjc is ignored.
   
    """

    devType = "bjt"

    paramDict = dict(
        cir.Element.tempItem,
        type=('Type (npn or pnp)', '', str, 'npn'),
        isat=('Transport saturation current', 'A', float, 1e-16),
        bf=('Ideal maximum forward beta', '', float, 100.),
        nf=('Forward current emission coefficient', '', float, 1.),
        vaf=('Forward early voltage', 'V', float, 0.),
        ikf=('Forward-beta high current roll-off knee current', 'A', float,
             0.),
        ise=('Base-emitter leakage saturation current', 'A', float, 0.),
        ne=('Base-emitter leakage emission coefficient', '', float, 1.5),
        br=('Ideal maximum reverse beta', '', float, 1.),
        nr=('Reverse current emission coefficient', '', float, 1.),
        var=('Reverse early voltage', 'V', float, 0.),
        ikr=('Corner for reverse-beta high current roll off', 'A', float, 0.),
        isc=('Base collector leakage saturation current', 'A', float, 0.),
        nc=('Base-collector leakage emission coefficient', '', float, 2.),
        rb=('Zero bias base resistance', 'Ohm', float, 0.),
        rbm=('Minimum base resistance', 'Ohm', float, 0.),
        irb=('Current at which rb falls to half of rbm', 'A', float, 0.),
        eg=('Badgap voltage', 'eV', float, 1.11),
        cje=('Base emitter zero bias p-n capacitance', 'F', float, 0.),
        vje=('Base emitter built in potential', 'V', float, 0.75),
        mje=('Base emitter p-n grading factor', '', float, 0.33),
        cjc=('Base collector zero bias p-n capacitance', 'F', float, 0.),
        vjc=('Base collector built in potential', 'V', float, 0.75),
        mjc=('Base collector p-n grading factor', '', float, 0.33),
        xcjc=('Fraction of cbc connected internal to rb', '', float, 1.),
        fc=('Forward bias depletion capacitor coefficient', '', float, 0.5),
        tf=('Ideal forward transit time', 's', float, 0.),
        xtf=('Transit time bias dependence coefficient', '', float, 0.),
        vtf=('Transit time dependency on vbc', 'V', float, 0.),
        itf=('Transit time dependency on ic', 'A', float, 0.),
        tr=('Ideal reverse transit time', 's', float, 0.),
        xtb=('Forward and reverse beta temperature coefficient', '', float,
             0.),
        xti=('IS temperature effect exponent', '', float, 3.),
        tnom=('Nominal temperature', 'C', float, 27.),
        area=('Current multiplier', '', float, 1.))

    def __init__(self, instanceName):
        cir.Element.__init__(self, instanceName)
        # Use junctions to model diodes and capacitors
        self.jif = Junction()
        self.jir = Junction()
        self.jile = Junction()
        self.jilc = Junction()
        # collector/emitter terminal numbers: may be re-mapped by
        # extrinsic device
        self._ct = 0
        self._et = 2

    def process_params(self):
        """
        Adjusts internal topology and makes preliminary calculations
        according to parameters
        """
        # Default configuration assumes rb == 0
        # ibe, ibc, ice
        self.csOutPorts = [(1, self._et), (1, self._ct), (self._ct, self._et)]
        # Controling voltages are vbe, vbc
        self.controlPorts = [(1, self._et), (1, self._ct)]
        self.vPortGuess = np.array([0., 0.])
        # qbe, qbc
        self.qsOutPorts = [(1, self._et), (1, self._ct)]

        # Define topology first
        # Flag to signal if the extra charge Qbx is needed or not
        self._qbx = False
        self.linearVCCS = []
        if self.rb != 0.:
            # rb is not zero: add internal terminals
            tBi = self.add_internal_term('Bi', 'V')
            tib = self.add_internal_term('ib', '{0} A'.format(glVar.gyr))
            tref = self.add_reference_term()
            # Linear VCCS for gyrator(s)
            self.linearVCCS = [((1, tBi), (tib, tref), glVar.gyr),
                               ((tib, tref), (1, tBi), glVar.gyr)]
            # ibe, ibc, ice, Rb(ib) * ib
            self.csOutPorts = [(tBi, self._et), (tBi, self._ct),
                               (self._ct, self._et), (tref, tib)]
            # Controling voltages are vbie, vbic and gyrator port
            self.controlPorts = [(tBi, self._et), (tBi, self._ct), (tib, tref)]
            self.vPortGuess = np.array([0., 0., 0.])
            # qbie, qbic
            self.qsOutPorts = [(tBi, self._et), (tBi, self._ct)]
            # Now check if Cjbc must be splitted (since rb != 0)
            if (self.cjc != 0.) and (self.xcjc < 1.):
                self.qsOutPorts.append((1, self._ct))
                self._qbx = True

        # In principle we may not need any charge
        keepPorts = []
        if self.cje + self.tf != 0.:
            # keep qbe
            keepPorts.append(self.qsOutPorts[0])
        if self.cjc + self.tr != 0.:
            # keep qbc, qbx (if any)
            if self._qbx:
                keepPorts += self.qsOutPorts[-2:]
            else:
                keepPorts.append(self.qsOutPorts[-1])
        self.qsOutPorts = keepPorts

        # keep track of how many output variables are needed
        self.ncurrents = len(self.csOutPorts)
        self.ncharges = len(self.qsOutPorts)

        # NPN or PNP
        if self.type == 'pnp':
            self._typef = -1.
        else:
            self._typef = 1.

        # Calculate common variables
        # Absolute nominal temperature
        self.Tnomabs = self.tnom + const.T0
        self.egapn = self.eg - .000702 * (self.Tnomabs**2) \
            / (self.Tnomabs + 1108.)
        # jif produces if, cje
        self.jif.process_params(self.isat, self.nf, self.fc, self.cje,
                                self.vje, self.mje, self.xti, self.eg,
                                self.Tnomabs)
        # jir produces ir, cjc
        self.jir.process_params(self.isat, self.nr, self.fc, self.cjc,
                                self.vjc, self.mjc, self.xti, self.eg,
                                self.Tnomabs)
        if self.ise != 0.:
            # jile produces ile
            self.jile.process_params(self.ise, self.ne, 0, 0, 0, 0, self.xti,
                                     self.eg, self.Tnomabs)
        if self.isc != 0.:
            # jilc produces ilc
            self.jilc.process_params(self.isc, self.nc, 0, 0, 0, 0, self.xti,
                                     self.eg, self.Tnomabs)
        # Constants needed for rb(ib) calculation
        if self.irb != 0.:
            self._ck1 = 144. / self.irb / self.area / np.pi / np.pi
            self._ck2 = np.pi * np.pi * np.sqrt(self.irb * self.area) / 24.

    def set_temp_vars(self, temp):
        """
        Calculate temperature-dependent variables, given temp in deg. C
        """
        # Absolute temperature (note self.temp is in deg. C)
        self.Tabs = const.T0 + temp
        # Normalized temp
        self.tnratio = self.Tabs / self.Tnomabs
        tnXTB = pow(self.tnratio, self.xtb)
        # Thermal voltage
        self.vt = const.k * self.Tabs / const.q
        # Temperature-adjusted egap
        self.egap_t = self.eg - .000702 * (self.Tabs**2) / (self.Tabs + 1108.)
        # set temperature in juctions
        self.jif.set_temp_vars(self.Tabs, self.Tnomabs, self.vt, self.egapn,
                               self.egap_t)
        self.jir.set_temp_vars(self.Tabs, self.Tnomabs, self.vt, self.egapn,
                               self.egap_t)
        # Adjust ise and isc (which have different temperature variation)
        if self.ise != 0.:
            self.jile.set_temp_vars(self.Tabs, self.Tnomabs, self.vt,
                                    self.egapn, self.egap_t)
            self.jile._t_is /= tnXTB
        if self.isc != 0.:
            self.jilc.set_temp_vars(self.Tabs, self.Tnomabs, self.vt,
                                    self.egapn, self.egap_t)
            self.jilc._t_is /= tnXTB
        # Now some BJT-only variables
        self._bf_t = self.bf * tnXTB
        self._br_t = self.br * tnXTB

    def eval_cqs(self, vPort):
        """
        Calculates currents/charges

        Input is a vector may be one of the following, depending on
        parameter values::

          vPort = [vbe, vbc]
          vPort = [vbie, vbic, v1_i] (gyrator voltage, rb != 0)

        Output also depends on parameter values. Charges only present
        if parameters make them different than 0 (i.e., cje, tf, cjc,
        etc. are set to nonzero values)::
        
          iVec = [ibe, ibc, ice]
          iVec = [ibe, ibc, ice, gyr*ib*Rb] (rb != 0)

          qVec = [qbe, qbc]
          qVec = [qbe, qbc, qbx] (rb != 0 and cjc != 1)
        """
        # Invert control voltages if needed
        vPort1 = self._typef * vPort

        # Calculate regular PN junctions currents and charges
        ibf = self.jif.get_id(vPort1[0])
        ibr = self.jif.get_id(vPort1[1])
        if self.ise != 0.:
            ile = self.jile.get_id(vPort1[0])
        else:
            ile = 0.
        if self.isc != 0.:
            ilc = self.jilc.get_id(vPort1[1])
        else:
            ilc = 0.
        # Kqb
        q1m1 = 1.
        if self.var != 0.:
            q1m1 -= vPort1[0] / self.var
        if self.vaf != 0.:
            q1m1 -= vPort1[1] / self.vaf
        kqb = 1. / q1m1
        # We need extra checking to consider the following
        # possibilities to create the AD tape:
        #
        # 1. both ikf and ikr are zero -> no tape generated
        # 2. One of them is nonzero but both ibf and ibr are zero -> want tape
        #    but only for the nonzero parameter
        if self.ikf + self.ikr != 0.:
            q2 = 0.
            if self.ikf != 0.:
                q2 += ibf / self.ikf
            if self.ikr != 0.:
                q2 += ibr / self.ikr
            kqb *= .5 * (1. + np.sqrt(1. + 4. * q2))

        # Create output vector [ibe, ibc, ice, ...]
        iVec = np.zeros(self.ncurrents, dtype=type(ibf))
        qVec = np.zeros(self.ncharges, dtype=type(ibf))
        # ibe
        iVec[0] = ibf / self._bf_t + ile
        # ibc
        iVec[1] = ibr / self._br_t + ilc
        # ice
        iVec[2] = (ibf - ibr) / kqb

        # RB
        if self.rb != 0.:
            # Using gyrator
            # vPort1[2] not defined if rb == 0
            # ib has area effect included (removed by _ck1 and _ck2)
            ib = vPort1[2] * glVar.gyr
            if self.irb != 0.:
                ib1 = np.abs(ib)
                x = np.sqrt(1. + self._ck1 * ib1) - 1.
                x *= self._ck2 / np.sqrt(ib1)
                tx = np.tan(x)
                c = self.rbm + 3. * (self.rb - self.rbm) \
                    * (tx - x) / (x * tx * tx)
                rb = ad.condassign(ib1, c, self.rb)
            else:
                rb = self.rbm + (self.rb - self.rbm) / kqb
            # Output is gyr * ib * rb.  It is divided by area^2 to
            # compensate that the whole vector is multiplied by area
            # at the end
            iVec[3] = glVar.gyr * ib * rb / pow(self.area, 2)
            vbcx = ib * rb / self.area + vPort1[1]

        # Charges -----------------------------------------------

        # Note that if tf == 0 and cje == 0, nothing is calculated and
        # nothing is assigned to the output vector.

        # qbe is the first charge (0)
        if self.tf != 0.:
            # Effective tf
            tfeff = self.tf
            if self.vtf != 0.:
                x = ibf / (ibf + self.itf)
                tfeff *= (1. + self.xtf * x * x *
                          ad.safe_exp(vPort1[1] / 1.44 / self.vtf))
            qVec[0] = tfeff * ibf
        if self.cje != 0.:
            qVec[0] += self.jif.get_qd(vPort1[0])

        # qbc
        if self._qbx:
            if self.tr != 0.:
                qVec[-2] = self.tr * ibr
            if self.cjc != 0.:
                qVec[-2] += self.jir.get_qd(vPort1[1]) * self.xcjc
                # qbx
                qVec[-1] = self.jir.get_qd(vbcx) * (1. - self.xcjc)
        else:
            if self.tr != 0.:
                qVec[-1] = self.tr * ibr
            if self.cjc != 0.:
                qVec[-1] += self.jir.get_qd(vPort1[1])

        # Consider area effect and invert currents if needed
        iVec *= self.area * self._typef
        qVec *= self.area * self._typef

        return (iVec, qVec)

    def power(self, vPort, currV):
        """ 
        Calculate total instantaneous power 

        Input: control voltages as in eval_cqs() and currents returned
        by eval_cqs()
        """
        vce = vPort[0] - vPort[1]
        if self.rb != 0.:
            # currV[3] = ib * Rb * gyr
            # vPort[2] = ib / gyr
            pRb = currV[3] * vPort[2]
        else:
            pRb = 0.

        # pout = ibe * vbie + ibc * vbic + vce * ice + pRb
        pout = currV[0] * vPort[0] + currV[1] * vPort[1] \
            + currV[2] * vce + pRb

        return pout

    def get_OP(self, vPort):
        """
        Calculates operating point information

        Input: same as eval_cqs
        Output: dictionary with OP variables
        For now it is quite incomplete
        """
        # First we need the Jacobian
        (outV, jac) = self.eval_and_deriv(vPort)
        power = self.power(vPort, outV)

        opDict = dict(
            VBE=vPort[0],
            VCE=vPort[0] - vPort[1],
            IB=outV[0] + outV[1],
            IC=outV[2] - outV[1],
            IE=-outV[2] - outV[0],
            Temp=self.temp,
            Power=power,
            gm=jac[2, 0] - jac[1, 0],
            rpi=1. / (jac[0, 0] + jac[1, 0]),
        )
        return opDict

    def get_noise(self, f):
        """
        Return noise spectral density at frequency f
        
        Requires a previous call to get_OP() 

        Not implemented yet
        """
        return None
Ejemplo n.º 13
0
    class BJT(IBJT):
        """
    Bipolar Junction Transistor
    ---------------------------

    This device accepts 3 or 4 terminal connections.

    Netlist examples::
    
        bjt:q1 2 3 4 1 model = mypnp isat=4e-17 bf=147 iss=10fA
        bjt:q2 2 3 4  model = mypnp isat=4e-17 bf=147 vaf=80 ikf=4m
        svbjt:q3 2 3 4 1 model = mypnp vaf=80 ikf=4m iss=15fA
    
        # Electro-thermal versions
        bjt_t:q2 2 3 5 1 pout gnd model = mypnp
        svbjt_t:q3 2 3 5 1 pout gnd model = mypnp
    
        # Model statement
        .model mypnp bjt_t (type=pnp isat=5e-17 cje=60fF vje=0.83 mje=0.35)

    Extrinsic Internal Topology
    +++++++++++++++++++++++++++

    RC, RE and a Collector-Bulk connection are added to intrinsic
    BJT models::

                  RC    Term: ct      Term: et   RE
      C (0) o---/\/\/\/--+-----,         4----/\/\/\/----o  E (2)
                         |      \       /
                         |       \     /     
                       -----    ---------
                        / \         |
                       /   \        o 
                       -----
                         |          B (1)
                         o Bulk (3)

    If RE or RC are zero the internal nodes (ct, et) are not
    created. If only 3 connections are specified then the
    Bulk-Collector junction is not connected.

    Important Note
    ++++++++++++++

    This implementation does not account for the power dissipation
    in RE, RC. Use external thermal resistors if that is needed.

    Intrinsic Model Information
    +++++++++++++++++++++++++++

        """
        # Additional documentation
        extraDoc = IBJT.__doc__

        # Create electrothermal device
        makeAutoThermal = True

        isNonlinear = True

        # Device category
        category = "Semiconductor devices"

        # devtype is the 'model' name: remove the 'i' from intrinsic name
        devType = IBJT.devType

        # Do not set numTerms to allow 3 or 4 terminals to be connected

        paramDict = dict(
            IBJT.paramDict.items(),
            re=('Emitter ohmic resistance', 'Ohm', float, None),
            rc=('Collector ohmic resistance', 'Ohm', float, None),
            cjs=('Collector substrate capacitance', 'F', float, 0.),
            mjs=('substrate junction exponential factor', '', float, 0.),
            vjs=('substrate junction built in potential', 'V', float, 0.75),
            ns=('substrate p-n coefficient', '', float, 1.),
            iss=('Substrate saturation current', 'A', float, 1e-14))

        def __init__(self, instanceName):
            IBJT.__init__(self, instanceName)
            # Collector-bulk junction
            self.cbjtn = Junction()
            self.__doc__ += IBJT.__doc__

        def process_params(self, thermal=False):
            # Remove tape if present
            ad.delete_tape(self)

            if thermal:
                extraTerms = 2
            else:
                extraTerms = 0

            # First check external connections
            if self.numTerms == 3 + extraTerms:
                self.__addCBjtn = False
            elif self.numTerms == 4 + extraTerms:
                self.__addCBjtn = True
            else:
                raise cir.CircuitError('{0}: Wrong number of connections. \
Can only be {1} or {2}, {3} found.'.format(self.instanceName, 3 + extraTerms,
                                           4 + extraTerms, self.numTerms))

            # Remove internal terminals
            self.clean_internal_terms()
            # Tell autothermal to re-generate thermal ports
            self.__addThermalPorts = True

            extraVCCS = list()
            if self.re != None:
                # Add et node and translate port descriptions
                self._et = self.add_internal_term('et', 'V')
                extraVCCS += [((2, self._et), (2, self._et),
                               self.area / self.re)]
            if self.rc != None:
                # Add ct node and translate port descriptions
                self._ct = self.add_internal_term('ct', 'V')
                extraVCCS += [((0, self._ct), (0, self._ct),
                               self.area / self.rc)]

            # Process parameters from intrinsic device: emitter and
            # collector terminals are already substituted.
            IBJT.process_params(self)
            # Calculate variables in junction
            self.cbjtn.process_params(self.iss, self.ns, self.fc, self.cjs,
                                      self.vjs, self.mjs, self.xti, self.eg,
                                      self.Tnomabs)
            # Add RE, RC resistors (if any)
            self.linearVCCS += extraVCCS
            if self.__addCBjtn:
                # Add bulk-collector junction
                self.__bccn = len(self.controlPorts)
                self.__bcon = len(self.csOutPorts)
                self.controlPorts.append((3, self._ct))
                self.csOutPorts.append((3, self._ct))
                if self.cjs != 0.:
                    self.qsOutPorts.append((3, self._ct))

                # Initial guess for input ports:
                try:
                    if len(self.vPortGuess) < len(self.controlPorts):
                        self.vPortGuess = np.concatenate(
                            (self.vPortGuess, [1]), axis=0)
                except AttributeError:
                    # Ignore if vPortGuess not provided
                    pass

            # Adjust temperature
            self.set_temp_vars(self.temp)

        def set_temp_vars(self, temp):
            """
            Calculate temperature-dependent variables, given temp in deg. C
            """
            # Remove tape if present
            ad.delete_tape(self)
            # First calculate variables from base class
            IBJT.set_temp_vars(self, temp)
            # Adjust collector-bulk junction temperature
            self.cbjtn.set_temp_vars(self.Tabs, self.Tnomabs, self.vt,
                                     self.egapn, self.egap_t)

        def eval_cqs(self, vPort, getOP=False):
            """
            vPort is a vector with control voltages 
            """
            if self.__addCBjtn:
                # calculate currents and charges in base class
                (iVec1, qVec1) = IBJT.eval_cqs(self, vPort)
                # Add contribution of substrate. Ignore extra control
                # ports only used for power calculation
                v1 = vPort[self.__bccn] * self._typef
                isub = self.cbjtn.get_id(v1) * self.area * self._typef
                iVec = np.concatenate((iVec1, [isub]), axis=0)
                if self.cjs != 0.:
                    qsub = self.cbjtn.get_qd(v1) * self.area * self._typef
                    qVec = np.concatenate((qVec1, [qsub]), axis=0)
                else:
                    qVec = qVec1
                return (iVec, qVec)
            else:
                return IBJT.eval_cqs(self, vPort)

        # Create these using the AD facility
        eval_and_deriv = ad.eval_and_deriv
        eval = ad.eval

        def power(self, vPort, currV):
            """ 
            Calculate total instantaneous power 

            Power in RE, RC not considered.

            Input: control voltages as in eval_cqs() and currents
            returned by eval_cqs()
            """
            pout = IBJT.power(self, vPort, currV)
            if self.__addCBjtn:
                # add power from substrate junction (RE, RC not counted)
                pout += vPort[self.__bccn] * currV[self.__bcon]

            return pout