Example #1
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
Example #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
Example #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
Example #4
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