def eval_cqs(self, vPort): """ Models intrinsic diode and charge. vPort[0]: x as in Rizolli's equations Returns a tuple with 2 or 3 elements: current, voltage and charge. Charge is ommited if both cj0 and tt are zero. """ # Calculate state-variable PN junction current, voltage and charge (iD, vD) = self.jtn.get_idvd(vPort[0]) if self.cj0: qD = self.jtn.get_qd(vD) if self.tt: qD += self.tt * iD # add breakdown current if self.bv < np.inf: iD -= self.ibv * ad.safe_exp(-(vD + self.bv) / self.n / self.vt) # area effect iD *= self.area # Scale voltage (gyrator gain) vD *= glVar.gyr iVec = np.array([iD, vD]) if self._qd: # area effect for charge qD *= self.area qVec = np.array([qD]) return (iVec, qVec) else: return (iVec, np.array([]))
def eval_cqs(self, vPort): """ Models intrinsic diode and charge. vPort is a vector with 1 element (diode voltage) Returns one vector for current and another for charge. Charge vector is empty if both cj0 and tt are zero """ # Calculate regular PN junction current and charge iD = self.jtn.get_id(vPort[0]) if self.cj0: qD = self.jtn.get_qd(vPort[0]) else: qD = 0. if self.tt: qD += self.tt * iD # add breakdown current if (self.bv < np.inf): iD -= self.ibv * \ ad.safe_exp(-(vPort[0] + self.bv) / self.n / self.vt) iD *= self.area idV = np.array([iD]) if self._qd: qD *= self.area qdV = np.array([qD]) return (idV, qdV) else: return (idV, np.array([]))
def eval_cqs(self, vPort): """ Models intrinsic diode and charge. vPort is a vector with 1 element (diode voltage) Returns one vector for current and another for charge. Charge vector is empty if both cj0 and tt are zero """ # Calculate regular PN junction current and charge iD = self.jtn.get_id(vPort[0]) if self.cj0 != 0.: qD = self.jtn.get_qd(vPort[0]) else: qD = 0. if self.tt != 0.: qD += self.tt * iD # add breakdown current if (self.bv < np.inf): iD -= self.ibv * \ ad.safe_exp(-(vPort[0] + self.bv) / self.n / self.vt) iD *= self.area idV = np.array([iD]) if self._qd: qD *= self.area qdV = np.array([qD]) return (idV, qdV) else: return (idV, np.array([]))
def eval_cqs(self, vPort): """ Models intrinsic diode and charge. vPort[0]: x as in Rizolli's equations Returns a tuple with 2 or 3 elements: current, voltage and charge. Charge is ommited if both cj0 and tt are zero. """ # Calculate state-variable PN junction current, voltage and charge (iD, vD) = self.jtn.get_idvd(vPort[0]) if self.cj0 != 0.: qD = self.jtn.get_qd(vD) if self.tt != 0.: qD += self.tt * iD # add breakdown current if (self.bv < np.inf): iD -= self.ibv * \ ad.safe_exp(-(vD + self.bv) / self.n / self.vt) # area effect iD *= self.area # Scale voltage (gyrator gain) vD *= glVar.gyr iVec = np.array([iD, vD]) if self._qd: # area effect for charge qD *= self.area qVec = np.array([qD]) return (iVec, qVec) else: return (iVec, np.array([]))
def get_id(self, vd): """ Returns junction current vd: diode voltage """ # Regular junction current return self._t_is * (ad.safe_exp(vd / self._kexp) - 1.)
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 eval_cqs(self, vPort, getOP=False): """ Calculates Ids, Idb, Isb currents and D, G, S, charges. Input: vPort = [vdb , vgb , vsb] Output: vector with Ids, Idb, Isb currents and vector with D, G, S charges. If getOP = True, return normal output vector plus operating point variables in tuple: (iVec, qVec, opV) """ # Invert all voltages in case of a P-channel device vPort1 = self._tf * vPort # If vds is negative, swap Vd and Vs and (also swap charges # later) DtoSswap = ad.condassign(vPort1[0] - vPort1[2], 1.0, -1.0) # perform the swap (need tmp variable) tmp = ad.condassign(DtoSswap, vPort1[0], vPort1[2]) vPort1[2] = ad.condassign(DtoSswap, vPort1[2], vPort1[0]) vPort1[0] = tmp # Effective gate voltage including reverse short channel effect vgprime = vPort1[1] - self._vt0a - self._deltavRSCE + self.phiT + self._gammaa * np.sqrt(self.phiT) # Effective substrate factor including charge-sharing for # short and narrow channels. Pinch-off voltage for # narrow-channel effect vp0 = ( vgprime - self.phiT - self._gammaa * (np.sqrt(vgprime + self._gammaa * self._gammaa / 4.0) - self._gammaa / 2.0) ) vp0 = ad.condassign(vgprime, vp0, -self.phiT) # Effective substrate factor accounting for charge-sharing tmp = 16.0 * self._Vt * self._Vt vsprime = 0.5 * (vPort1[2] + self.phiT + np.sqrt(pow(vPort1[2] + self.phiT, 2) + tmp)) vdprime = 0.5 * (vPort1[0] + self.phiT + np.sqrt(pow(vPort1[0] + self.phiT, 2) + tmp)) tmp = self.leta / self._leff * (np.sqrt(vsprime) + np.sqrt(vdprime)) tmp -= 3.0 * self.weta * np.sqrt(vp0 + self.phiT) / self._weff gammao = self._gammaa - const.epSi / self.cox * tmp gammaprime = 0.5 * (gammao + np.sqrt(gammao * gammao + 0.1 * self._Vt)) # Pinch-off voltage including short- and narrow-channel effect vp = vgprime - self.phiT vp -= gammaprime * (np.sqrt(vgprime + pow(0.5 * gammaprime, 2)) - gammaprime / 2.0) vp = ad.condassign(vgprime, vp, -self.phiT) # Slope factor n = 1 + self._gammaa / (2.0 * np.sqrt(vp + self.phiT + 4.0 * self._Vt)) # Forward normalized current i_f = (vp - vPort1[2]) / self._Vt i_f = self.interp(i_f) # Velocity saturation voltage vdss = np.sqrt(0.25 + self._Vt * np.sqrt(i_f) / self._vc) - 0.5 vdss *= self._vc # Drain-to-source saturation voltage for reverse normalized current tmp = np.sqrt(i_f) - 0.75 * np.log(i_f) vdssprime = np.sqrt(0.25 + self._Vt * tmp / self._vc) - 0.5 vdssprime *= self._vc vdssprime += self._Vt * (np.log(self._vc / (2.0 * self._Vt)) - 0.6) # Channel-length modulation tmp = np.sqrt(i_f) - vdss / self._Vt deltav = np.sqrt(self.Lambda * tmp + 1.0 / 64) deltav *= 4.0 * self._Vt vds = 0.5 * (vPort1[0] - vPort1[2]) vip = np.sqrt(vdss * vdss + deltav * deltav) vip -= np.sqrt(pow(vds - vdss, 2) + deltav * deltav) deltal = np.log(1.0 + (vds - vip) / self._lc / self._t_ucrit) deltal *= self.Lambda * self._lc # Equivalent channel length including channel-length # modulation and velocity saturation lprime = self.ns * self._leff - deltal + (vds + vip) / self._t_ucrit leq = 0.5 * (lprime + np.sqrt(lprime * lprime + self._lmin * self._lmin)) # Reverse normalized current tmp = vp - vds - vPort1[2] tmp -= np.sqrt(vdssprime * vdssprime + deltav * deltav) tmp += np.sqrt(pow(vds - vdssprime, 2) + deltav * deltav) irprime = self.interp(tmp / self._Vt) # Reverse normalized currect for mobility model, intrinsic # charges/capacitances, thermal noise model and NQS time-constant ??? ir = self.interp((vp - vPort1[0]) / self._Vt) # Quasi-static model equations # Dynamic model for the intrinsic node charges sqvpphi = np.sqrt(vp + self.phiT + 1.0e-6) nq = 1.0 + self._gammaa / 2.0 / sqvpphi # Normalized intrinsic node charges xf = np.sqrt(0.25 + i_f) xr = np.sqrt(0.25 + ir) tmp = pow(xf + xr, 2) qd = 3.0 * xr ** 3 + 6.0 * xr * xr * xf + 4.0 * xr * xf * xf + 2.0 * xf ** 3 qd *= 4.0 / 15.0 / tmp qd = -nq * (qd - 0.5) qs = 3.0 * xf ** 3 + 6.0 * xf * xf * xr + 4.0 * xf * xr * xr + 2.0 * xr ** 3 qs *= 4.0 / 15.0 / tmp qs = -nq * (qs - 0.5) qi = qs + qd qb1 = -self._gammaa * sqvpphi / self._Vt qb1 -= (nq - 1.0) * qi / nq qb = ad.condassign(vgprime, qb1, -vgprime / self._Vt) # qg = -qi - qox - qb, but qox == 0, so: qg = -qi - qb - self.qox # Transconductance factor and mobility reduction due to vertical field betao = self.kpa * self.np * self._weff / leq if self.theta != 0.0: # Simple mobility reduction model vpprime = 0.5 * (vp + np.sqrt(vp * vp + 2.0 * self._Vt * self._Vt)) beta = betao / (1.0 + theta * vpprime) else: # Rigorous mobility reduction model betaoprime = 1.0 + self.cox * self._qb0 / self.e0 / const.epSi betaoprime *= betao tmp = np.abs(qb + self.eta * qi) tmp = 1.0 + self.cox * self._Vt * tmp / self.e0 / const.epSi beta = betaoprime / tmp # Specific current IS = 2.0 * n * beta * self._Vt * self._Vt # Drain-to-source current ids = IS * (i_f - irprime) # import pdb; pdb.set_trace() # Impact ionization current vib = vPort1[0] - vPort1[2] - 2.0 * self.ibn * vdss idb1 = ids * self.iba * vib / self._t_ibb idb1 *= ad.safe_exp(-self._t_ibb * self._lc / vib) idb = ad.condassign(vib, idb1, 0.0) # ------------------------------------------------------------- # Create output vectors qVec = np.array([0.0, qg, 0.0], dtype=type(idb)) # have to switch charges if Drain and Source voltages switched qVec[0] = ad.condassign(DtoSswap, qd, qs) qVec[2] = ad.condassign(DtoSswap, qs, qd) # De-normalize charge and invert if needed qVec *= self._Cox * self._Vt * self._tf iVec = np.array([0.0, 0.0, 0.0], dtype=type(idb)) iVec[0] = DtoSswap * ids iVec[1] = ad.condassign(DtoSswap, idb, 0.0) iVec[2] = ad.condassign(DtoSswap, 0.0, idb) # Revert currents if needed iVec *= self._tf # -------------------------------------------------------------- # Operating point information if getOP: # Vth Vth = self._vt0a + self._deltavRSCE + gammaprime * np.sqrt(vsprime) - self._gammaa * np.sqrt(self.phiT) # Non quasi-static equations tau0 = 0.5 * self._Cox / self._Vt / beta tmp = (xf ** 2 + 3.0 * xf * xr + xr ** 2) / pow(xf + xr, 3) tau = tau0 * 4.0 / 15.0 * tmp # Create operating point variables dictionary return { "Vp": vp, "n": n, "Beta": beta, "IS": IS, "IF": i_f, "IR": ir, "IRprime": irprime, "tef": 1.0 / (np.sqrt(0.25 + i_f) + 0.5), "Vth": Vth, "Vov": self._tf * n * (vp - vPort[2]), "Vdsat": self._tf * self._Vt * (2.0 * np.sqrt(i_f) + 4.0), "tau0": tau0, "tau": tau, "Sthermal": self._kSt * beta * np.abs(qi), "Reversed": DtoSswap < 0.0, } else: return (iVec, qVec)
def eval_cqs(self, vPort, saveOP = False): """ Calculates currents and charges Input: vPort = [vdb , vgb , vsb] Output: iVec = [ids, idb, isb], qVec = [qd, qg, qs] If saveOP = True, return normal output vector plus operating point variables in tuple: (iVec, qVec, opV) """ # import pdb; pdb.set_trace() # Invert all voltages in case of a P-channel device vPort1 = self._tf * vPort # If vds is negative, swap Vd and Vs and (also swap charges # later) DtoSswap = ad.condassign(vPort1[0] - vPort1[2], 1., -1.) # perform the swap (need tmp variable) tmp = ad.condassign(DtoSswap, vPort1[0], vPort1[2]) vPort1[2] = ad.condassign(DtoSswap, vPort1[2], vPort1[0]) vPort1[0] = tmp # Calculate VDS, VGS and VBS for bsim model VDS = vPort1[0] - vPort1[2] VGS = vPort1[1] - vPort1[2] VBS = -vPort1[2] # ---------------------------------------------------------------- T0 = VBS - self.vbsc - 0.001 T1 = np.sqrt(T0 * T0 - 0.004 * self.vbsc) Vbseff = self.vbsc + .5 * (T0 + T1) Vbseff = ad.condassign(-Vbseff + VBS, VBS, Vbseff) #Calculation of Phis, sqrtPhis and Xdep Phis = ad.condassign(Vbseff, self.phi**2 / (self.phi + Vbseff), self.phi - Vbseff) sqrtPhis = ad.condassign(Vbseff, self.phis3 / (self.phi + 0.5 * Vbseff), np.sqrt(Phis)) Xdep = self.Xdep0 * sqrtPhis / self.sqrtPhi #Calculation of Threshold voltage-vth T3 = np.sqrt(Xdep) T1 = ad.condassign(self.dvt2 * Vbseff + .5, 1. + self.dvt2 * Vbseff, (1. + 3. * self.dvt2 * Vbseff) / (3. + 8. * self.dvt2 * Vbseff)) ltl = self.factor1 * T3 * T1 T1 = ad.condassign(self.dvt2w * Vbseff + .5, 1. + self.dvt2w * Vbseff, (1. + 3. * self.dvt2w * Vbseff) / (3. + 8. * self.dvt2w * Vbseff)) ltw = self.factor1 * T3 * T1 # Alternative to prevent overflow (apparently not needed) #T2 = ad.safe_exp(-.5 * self.dvt1 * self.leff / ltl) k_temp = -.5 * self.dvt1 * self.leff / ltl T2 = ad.condassign(k_temp + EXP_THRESHOLD, ad.safe_exp(k_temp), MIN_EXP) Theta0 = T2 * (1. + 2. * T2) thetavth = self.dvt0 * Theta0 Delt_vth = thetavth * self.V0 # Alternative to prevent overflow (apparently not needed) #T2 = ad.safe_exp(-.5 * self.dvt1w * self.weff * self.leff / ltw) # Modified from freeda's source to prevent using uninitialized # variable k_temp = -.5 * self.dvt1w * self.weff * self.leff / ltw T2 = ad.condassign(k_temp + EXP_THRESHOLD, ad.safe_exp(k_temp), MIN_EXP) T2 *= (1. + 2. * T2) T0 = self.dvt0w * T2 T2 = T0 * self.V0 T0 = np.sqrt(1. + self.nlx / self.leff) T1 = self.k1ox * (T0 - 1.) * self.sqrtPhi \ + (self.kt1 + self.kt1l / self.leff + self.kt2 * Vbseff) \ * self._ToTnm1 TMP2 = self.tox * self.phi / (self.weff + self.w0) T3 = self.eta0 + self.etab * Vbseff T4 = ad.condassign(-T3 + 1.0e-4, 1. / (3. - 2e4 * self.eta0 + self.etab * Vbseff), 1.) dDIBL_Sft_dVd = T3 * self.theta0vb0 DIBL_Sft = dDIBL_Sft_dVd * VDS Vth = self._vth0 - self._k1 * self.sqrtPhi + self.k1ox * sqrtPhis \ - self.k2ox * Vbseff - Delt_vth - T2 \ + (self.k3 + self.k3b * Vbseff) * TMP2 + T1 - DIBL_Sft #Calculate n temp_tmp2 = self.nfactor * const.epSi / Xdep temp_tmp3 = self.cdsc + self.cdscb * Vbseff + self.cdscd * VDS temp_tmp4 = (temp_tmp2 + temp_tmp3 * Theta0 + self.cit) / self.cox n = ad.condassign(temp_tmp4 + .5, 1. + temp_tmp4, (1. + 3. * temp_tmp4) \ * (1. / (3. + 8. * temp_tmp4))) #Poly Gate Si Depletion Effect Vgs_eff = VGS Vgst = Vgs_eff - Vth # not in Nikhil's code #Effective Vgst (Vgsteff) Calculation T10 = 2. * n * self._Vt VgstNVt = Vgst / T10 ExpArg = -(2. * 0.08 + Vgst) / T10 T1 = T10 * log1pexp(VgstNVt) T2 = 1. + T10 * self.cox * ad.safe_exp(ExpArg) / self._Vt / self.cdep0 Vgsteff = ad.condassign(VgstNVt - EXP_THRESHOLD, Vgst, T1 / T2) Vgsteff = ad.condassign( ExpArg - EXP_THRESHOLD, self._Vt * self.cdep0 \ / self.cox / ad.safe_exp((Vgst + 0.08) / n / self._Vt), T1 / T2) T3 = T2 * T2 # Calculate Effective Channel Geometry T9 = sqrtPhis - self.sqrtPhi k_temp = self.weff - 2. * (self.dwg * Vgsteff + self.dwb * T9) Weff = ad.condassign(-k_temp + 2.0e-8, 2e-8 * (4.0e-8 - k_temp) * T0, k_temp) T0 = self.prwg * Vgsteff + self.prwb * (sqrtPhis - self.sqrtPhi) Rds = ad.condassign(T0 + 0.9, self.rds0 * (1. + T0), self.rds0 * (.8 +T0) / (17. + 20. * T0)) #Calculate Abulk T1 = 0.5 * self.k1ox / sqrtPhis T9 = np.sqrt(self.xj * Xdep) T5 = self.leff / (self.leff + 2. * T9) T2 = (self.a0 * T5) + self.b0 / (self.weff + self.b1) T6 = T5 * T5 T7 = T5 * T6 Abulk0 = 1. + T1 * T2 T8 = self.ags * self.a0 * T7 Abulk = Abulk0 - T1 * T8 * Vgsteff Abulk0 = ad.condassign(-Abulk0 + .1, (.2 - Abulk0) / (3. - 20. * Abulk0), Abulk0) Abulk = ad.condassign(-Abulk + .1, (.2 - Abulk) / (3. - 20. * Abulk), Abulk) T2 = self.keta * Vbseff T0 = ad.condassign(T2 + 0.9, 1. / (1. + T2), (17. + 20. * T2) / (0.8 + T2)) Abulk *= T0 Abulk0 *= T0 T0 = Vgsteff + 2. * Vth T2 = self._ua + self._uc * Vbseff T3 = T0 / self.tox T5 = T3 * (T2 + self._ub * T3) Denomi = ad.condassign(T5 + .8, 1. + T5, (.6 + T5) / (7. + 10. * T5)) ueff = self.u0temp / Denomi Esat = 2. * self.vsattemp / ueff # Saturation Drain Voltage Vdsat WVCox = Weff * self.vsattemp * self.cox WVCoxRds = WVCox * Rds EsatL = Esat * self.leff Lambda = self.a2 Vgst2Vtm = Vgsteff + 2. * self._Vt T0 = 1. / (Abulk * EsatL + Vgst2Vtm) T3 = EsatL * Vgst2Vtm Vdsat = T3 * T0 # Effective Vds(Vdseff) Calculation T1 = Vdsat - VDS - self.delta T2 = np.sqrt(T1**2 + 4. * self.delta * Vdsat) #T0 = T1 / T2 #T3 = 2. * self.delta / T2 k_temp = Vdsat - .5 * (T1 + T2) Vdseff = ad.condassign(k_temp - VDS, VDS, k_temp) # The following seems unnnecessary: # Added to eliminate non-zero Vdseff at Vds=0.0 #Vdseff = ad.condassign(abs(VDS), # Vdseff, # 0.) # Calculate Vasat T6 = 1. - .5 * Abulk * Vdsat / Vgst2Vtm #T6=tmp4 T9 = WVCoxRds * Vgsteff #expanded T0 = EsatL + Vdsat + 2. * T9 * T6 T9 = WVCoxRds * Abulk T1 = 2. / Lambda - 1. + T9 Vasat = T0 / T1 diffVds = VDS - Vdseff #Calculate VACLM VACLM = ad.condassign( (diffVds - 1.0e-10) * self.pclm, self.leff * (Abulk + Vgsteff / EsatL) * diffVds \ / (self.pclm * Abulk * self.litl), MAX_EXP) #Calculate VADIBL T1 = np.sqrt(const.epSi / const.epOx * self.tox * self.Xdep0) T0 = ad.safe_exp(-.5 * self.drout * self.leff / T1) T2 = T0 + 2. * T0**2 thetaRout = self.pdibl1 * T2 + self.pdibl2 #drout, pdibl1, #pdibl2 are given VADIBL = ad.condassign( thetaRout, (Vgst2Vtm - Vgst2Vtm * Abulk * Vdsat / (Vgst2Vtm + Abulk * Vdsat)) \ / thetaRout, MAX_EXP) VADIBL = ad.condassign(self.pdiblb * Vbseff + 0.9, VADIBL / (1. + self.pdiblb * Vbseff), VADIBL * (17. + 20. * self.pdiblb * Vbseff) \ / (.8 + self.pdiblb * Vbseff)) #Calculate Va T8 = self.pvag / EsatL T9 = T8 * Vgsteff T0 = ad.condassign(T9 + 0.9, 1. + T9, (.8 + T9) / (17. + 20. * T9)) T3 = VACLM + VADIBL #tmp3 = T3 T1 = VACLM * VADIBL / T3 Va = Vasat + T0 * T1 #Calculate VASCBE if self.pscbe1: rcpVASCBE = ad.condassign( abs(diffVds), self.pscbe2 * ad.safe_exp(-self.pscbe1 * self.litl/diffVds) \ / self.leff, 0.) else: rcpVASCBE = self.pscbe2 / self.leff # Original: #VASCBE = ad.condassign( # diffVds - self.pscbe1 * self.litl / EXP_THRESHOLD, # self.leff * np.exp(self.pscbe1 * self.litl/diffVds) / self.pscbe2, # MAX_EXP * self.leff / self.pscbe2) #Calculate Ids CoxWovL = self.cox * Weff / self.leff beta = ueff * CoxWovL T0 = 1. - .5 * Abulk * Vdseff / Vgst2Vtm fgche1 = Vgsteff * T0 T9 = Vdseff / EsatL fgche2 = 1. + T9 gche = beta * fgche1 / fgche2 T0 = 1. + gche * Rds T9 = Vdseff / T0 Idl = gche * T9 T9 = diffVds / Va T0 = 1. + T9 Idsa = Idl * T0 T9 = diffVds * rcpVASCBE T0 = 1. + T9 Ids = Idsa * T0 #Substrate current begins T1 = self.alpha0 + self.alpha1 * self.leff k_temp = T1 / self.leff * diffVds #T2 = k_temp * ad.safe_exp(-self.beta0 / diffVds) # Original T2: ad.condassign( diffVds - self.beta0 / EXP_THRESHOLD, k_temp * ad.safe_exp(-self.beta0 / diffVds), k_temp * MIN_EXP) k_temp = ad.condassign(T1, T2 * Idsa, 0.) Isub = ad.condassign(self.beta0, k_temp, 0.) # ********* Output current vector ************** iVec = np.array([0., 0., 0.], dtype=type(Ids)) iVec[0] = DtoSswap * Ids iVec[1] = ad.condassign(DtoSswap, Isub, 0.) iVec[2] = ad.condassign(DtoSswap, 0., Isub) # Revert currents if needed iVec *= self._tf # ********************************************** #-------------------------------------------------------------- # Charge calculation follows (does not work): #calculation of vfbzb T0 = -.5 * self.dvt1w * self.weff * self.leff \ / self.factor1 / np.sqrt(self.Xdep0) #T2 = ad.safe_exp(T0) * (1. + 2. * ad.safe_exp(T0)) T2 = ad.condassign(T0 + EXP_THRESHOLD, ad.safe_exp(T0) * (1. + 2. * ad.safe_exp(T0)), MIN_1) T0 = self.dvt0w * T2 T2 = T0 * (self.vbi - self.phi) T0 = -.5 * self.dvt1 * self.leff / (self.factor1 * np.sqrt(self.Xdep0)) #T3 = ad.safe_exp(T0) * (1. + 2. * ad.safe_exp(T0)) T3 = ad.condassign(T0 + EXP_THRESHOLD, ad.safe_exp(T0) * (1. + 2. * ad.safe_exp(T0)), MIN_1) T3 = self.dvt0 * T3 * (self.vbi - self.phi) T4 = self.tox * self.phi / (self.weff + self.w0) T5 = self.k1ox * (T0 - 1.) * self.sqrtPhi \ + (self.kt1 + self.kt1l / self.leff) * self._ToTnm1 T0 = np.sqrt(1. + self.nlx / self.leff) T6 = self._vth0 - T2 -T3 + self.k3 * T4 + T5 vfbzb = T6 - self.phi - self._k1 * self.sqrtPhi #Calculation for VbseffCV VbseffCV = ad.condassign(Vbseff, self.phi - Phis, Vbseff) #Calculation for VgsteffCV T0 = n * self.noff * self._Vt T1 = (Vgs_eff - Vth) / T0 Vgsteff = ad.condassign(T1 - EXP_THRESHOLD, Vgs_eff - Vth - self.voffcv, T0 * log1pexp(T1)) # This (after the previous) can not be right: #Vgsteff = ad.condassign(-T1 - EXP_THRESHOLD, # T0 * log(1. + MIN_EXP), # T0 * log(1. + np.exp(T1))) #Calculation for Vfbeff V3 = vfbzb - Vgs_eff + VbseffCV - .02 T0 = np.sqrt(V3**2 + .08 * abs(vfbzb)) Vfbeff = vfbzb - 0.5 * (V3 + T0) T0 = (Vgs_eff - VbseffCV - vfbzb) / self._Tox #Calculation for Tcen T1 = T0 * self.acde Tcen = ad.condassign(EXP_THRESHOLD + T1, self.ldeb * np.exp(T1), self.ldeb * MIN_EXP) Tcen = ad.condassign(-EXP_THRESHOLD + T1, self.ldeb * MAX_EXP, Tcen) V3 = self.ldeb - Tcen - 1e-3 * self.tox V4 = np.sqrt(V3**2 + 4e-3 * self.tox * self.ldeb) Tcen = self.ldeb - .5 * (V3 + V4) Ccen = const.epSi / Tcen Coxeff = Ccen * self.cox / (Ccen + self.cox) #Calculation for QoverlapCox CoxWLcen = Weff * self.leff * Coxeff Qac0 = CoxWLcen * (Vfbeff - vfbzb) # QovCox = Qac0 / Coxeff T0 = .5 * self.k1ox T3 = Vgs_eff - Vfbeff - VbseffCV - Vgsteff T1 = np.sqrt(T0 * T0 + T3) T2 = CoxWLcen * T0 / T1 T1 = ad.condassign(-T3, T0 + T3 / self.k1ox, np.sqrt(T0**2 + T3)) T2 = ad.condassign(-T3, CoxWLcen, CoxWLcen * T0 / T1) Qsub0 = CoxWLcen * self.k1ox * (T1 - T0) # QovCox = Qsub0 / Coxeff #Calculation for Delta_phis T2 = ad.condassign(self.k1ox, self.moin * self._Vt * self.k1ox**2, .25 * self.moin * self._Vt) T0 = ad.condassign(self.k1ox, self.k1ox * np.sqrt(self.phi), .5 * np.sqrt(self.phi)) T1 = 2. * T0 + Vgsteff DeltaPhi = self._Vt * np.log(1. + T1 * Vgsteff / T2) #The calculation for Tcen must be done once more # fREEDA: #T0 = (Vgsteff + 4.*(self._vth0 - self.vfb - self.phi))/ (2. * self._Tox) # ngspice: T3 = 4. * (Vth - vfbzb - self.phi) T0 = ad.condassign(T3, .5 * (Vgsteff + T3) / self._Tox, .5 * (Vgsteff + 1.0e-20) / self._Tox) k_temp = 2.01375270747048 * T0 # was np.exp(.7 * log(T0)) T1 = 1. + k_temp T2 = 0.35 * k_temp / (T0 * self._Tox) Tcen = 1.9e-9 / T1 Ccen = const.epSi / Tcen Coxeff = Ccen * self.cox / (Ccen + self.cox) CoxWLcen = Weff * self.leff * Coxeff AbulkCV = Abulk0 * self.AbulkCVfactor VdsatCV = (Vgsteff - DeltaPhi) / AbulkCV T0 = VdsatCV - VDS - .02 T1 = np.sqrt(T0**2 + .08 * VdsatCV) # From ngspice: internal version BSIM3v32V32 VdseffCV = VdsatCV - .5 * (T0 + T1) # From freeda: # VdseffCV = ad.condassign(T0, # VdsatCV - .5 * (T0 + T1), # VdsatCV * (1. - .04/(T1-T0))) # Need this to prevent NaN in charge Jacobian VdseffCV += 1e-200 # Seems not needed #VdseffCV = ad.condassign(abs(VDS), # Vdseff, # 0.) T0 = AbulkCV * VdseffCV T1 = Vgsteff - DeltaPhi T2 = 12. * (T1 - .5 * T0 + 1e-20) T3 = T0 / T2 T4 = 1. - 12. * T3**2 T5 = AbulkCV * (6. * T0 * (4. * T1 - T0) / (T2**2) - .5) T6 = T5 * VdseffCV / AbulkCV qgate = CoxWLcen * (T1 - T0 * (.5 - T3)) qbulk = CoxWLcen * (1. - AbulkCV) * (.5*VdseffCV - T0*VdseffCV/T2) #QovCox = qbulk / Coxeff T2 = T2 / 12. T3 = .5 * CoxWLcen / (T2**2) T4 = T1 * (2. * T0**2 / 3. + T1*(T1 - 4. * T0 / 3.)) - 2. * T0**3 / 15. qsrc = -T3 * T4 qgate += Qac0 + Qsub0 - qbulk qbulk -= (Qac0 + Qsub0) qdrn = -(qbulk + qgate + qsrc) # ************ Output charge vector **************** qVec = np.array([0., qgate, 0.], dtype=type(qsrc)) # have to switch charges if Drain and Source voltages switched qVec[0] = ad.condassign(DtoSswap, qdrn, qsrc) qVec[2] = ad.condassign(DtoSswap, qsrc, qdrn) # invert sign if needed qVec *= self._tf # Return numpy array with one element per current source. return (iVec, qVec)
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) if self.type == 'n': self._tf = 1. elif self.type == 'p': self._tf = -1. # Change parameter default values if not self.is_set('u0'): self.u0 = 250 else: raise cir.CircuitError( '{0}: unrecognized type: {1}. Valid types are "n" or "p"'.format(self.nodeName, self.type)) # Nominal abs temperature self._Tn = const.T0 + self.tnom # Nominal Thermal voltage self._Vtn = const.k * self._Tn / const.q self.factor1 = np.sqrt(const.epSi / const.epOx * self.tox) Eg0 = 1.16 - 7.02e-4 * (self._Tn**2) / (self._Tn + 1108.0) ni = 1.45e10 * (self._Tn / 300.15) * np.sqrt(self._Tn / 300.15) \ * np.exp(21.5565981 - Eg0 / (2. * self._Vtn)) #self.esi = 11.7 * const.epsilon0 (replaced by const.epSi) self.ldrn = self.l self.wdrn = self.w t0 = pow(self.ldrn, self.lln) t1 = pow(self.wdrn, self.lwn) tmp1 = self.ll / t0 + self.lw / t1 + self.lwl / (t0 * t1) self.dl = self.lint + tmp1 #tmp2 = llc / t0 + lwc / t1 + lwlc / (t0 * t1) #self.dlc = dlc + tmp2 # ??? t2 = pow(self.ldrn, self.wln) t3 = pow(self.wdrn, self.wwn) tmp3 = self.wl / t2 + self.ww / t3 + self.wwl / (t2 * t3) self.dw = self.wint + tmp3 #tmp4 = wlc / t2 + wwc / t3 + wwlc / (t2 * t3) #self.dwc = dwc + tmp4 # ??? self.leff = self.l - 2.0 * self.dl self.weff = self.w - 2.0 * self.dw self.AbulkCVfactor = (1. + pow(self.clc/self.leff, self.cle)) #self.leffCV = l - 2.0 * dlc #self.t11 = leffCV * leffCV # was epOx = 3.453133e-11 self.cox = const.epOx / self.tox self.phi = 2. * self._Vtn * np.log(self.nch / ni) self.sqrtPhi = np.sqrt(self.phi) self.phis3 = self.sqrtPhi * self.phi self.Xdep0 = np.sqrt(2. * const.epSi / (const.q * self.nch * 1e6)) * self.sqrtPhi self.litl = np.sqrt(3. * self.xj * self.tox) self.vbi = self._Vtn * np.log(1.0e20 * self.nch / (ni**2)) self.cdep0 = np.sqrt(const.q * const.epSi * self.nch * 1e6 / 2. / self.phi) if not self.is_set('toxm'): self.toxm = self.tox if not self.is_set('dsub'): self.dsub = self.drout self.ldeb = np.sqrt(const.epSi * self._Vtn / (const.q * self.nch * 1e6)) / 3. #import pdb; pdb.set_trace() if self.k1enable and not (self.is_set('k1') or self.is_set('k2')): vbx = self.phi - 7.7348e-4 * self.nch * self.xt**2 # From ngspice vbx = -abs(vbx) Vbm = -abs(self.vbm) gamma1 = 5.753e-12 * np.sqrt(self.nch) / self.cox gamma2 = 5.753e-12 * np.sqrt(self.nsub) / self.cox T0 = gamma1 - gamma2 T1 = np.sqrt(self.phi - vbx) - self.sqrtPhi T2 = np.sqrt(self.phi * (self.phi - Vbm)) - self.phi self._k2 = T0 * T1 / (2. * T2 + Vbm) self._k1 = gamma2 - 2. * self._k2 * np.sqrt(self.phi - Vbm) # print self._k1, self._k2 else: self._k1 = self.k1 self._k2 = self.k2 if not self.is_set('vth0'): self._vth0 = self.vfb + self.phi + self._k1 * self.sqrtPhi else: self._vth0 = abs(self.vth0) self.k1ox = self._k1 * self.tox / self.toxm self.k2ox = self._k2 * self.tox / self.toxm t1 = np.sqrt(const.epSi / const.epOx * self.tox * self.Xdep0) t0 = ad.safe_exp(-0.5 * self.dsub * self.leff / t1) self.theta0vb0 = (t0 + 2.0 * t0**2) # From freeda, ngspice: self._Tox = 1e8 * self.tox #Calculation of vbsc(Vbc) and Vbseff if self._k2 < 0.: self.vbsc = .9 * (self.phi - (.5 * self._k1 / self._k2)**2) if self.vbsc > -3.: self.vbsc = -3. elif self.vbsc < -30.: self.vbsc = -30. else: self.vbsc = -30. if self.u0 > 1.: self._u0 = self.u0 * 1e-4 else: self._u0 = self.u0 if not thermal: # Calculate temperature-dependent variables self.set_temp_vars(self.temp)
def eval_cqs(self, vPort, getOP=False): """ Calculates Ids, Idb, Isb currents and D, G, S, charges. Input: vPort = [vdb , vgb , vsb] Output: vector with Ids, Idb, Isb currents and vector with D, G, S charges. If getOP = True, return normal output vector plus operating point variables in tuple: (iVec, qVec, opV) """ # Invert all voltages in case of a P-channel device vPort1 = self._tf * vPort # If vds is negative, swap Vd and Vs and (also swap charges # later) DtoSswap = ad.condassign(vPort1[0] - vPort1[2], 1., -1.) # perform the swap (need tmp variable) tmp = ad.condassign(DtoSswap, vPort1[0], vPort1[2]) vPort1[2] = ad.condassign(DtoSswap, vPort1[2], vPort1[0]) vPort1[0] = tmp # Effective gate voltage including reverse short channel effect vgprime = vPort1[1] - self._vt0a - self._deltavRSCE \ + self.phiT + self._gammaa * np.sqrt(self.phiT) # Effective substrate factor including charge-sharing for # short and narrow channels. Pinch-off voltage for # narrow-channel effect vp0 = vgprime - self.phiT - \ self._gammaa * (np.sqrt(vgprime + self._gammaa*self._gammaa / 4.) - self._gammaa / 2.) vp0 = ad.condassign(vgprime, vp0, -self.phiT) # Effective substrate factor accounting for charge-sharing tmp = 16. * self._Vt * self._Vt vsprime = 0.5 * (vPort1[2] + self.phiT + np.sqrt(pow(vPort1[2] + self.phiT, 2) + tmp)) vdprime = 0.5 * (vPort1[0] + self.phiT + np.sqrt(pow(vPort1[0] + self.phiT, 2) + tmp)) tmp = self.leta / self._leff * (np.sqrt(vsprime) + np.sqrt(vdprime)) tmp -= 3. * self.weta * np.sqrt(vp0 + self.phiT) / self._weff gammao = self._gammaa - const.epSi / self.cox * tmp gammaprime = 0.5 * (gammao + np.sqrt(gammao * gammao + 0.1 * self._Vt)) # Pinch-off voltage including short- and narrow-channel effect vp = vgprime - self.phiT vp -= gammaprime * (np.sqrt(vgprime + pow(.5 * gammaprime, 2)) - gammaprime / 2.) vp = ad.condassign(vgprime, vp, -self.phiT) # Slope factor n = 1 + self._gammaa / (2. * np.sqrt(vp + self.phiT + 4. * self._Vt)) # Forward normalized current i_f = (vp - vPort1[2]) / self._Vt i_f = self.interp(i_f) # Velocity saturation voltage vdss = np.sqrt(0.25 + self._Vt * np.sqrt(i_f) / self._vc) - 0.5 vdss *= self._vc # Drain-to-source saturation voltage for reverse normalized current tmp = np.sqrt(i_f) - 0.75 * np.log(i_f) vdssprime = np.sqrt(0.25 + self._Vt * tmp / self._vc) - 0.5 vdssprime *= self._vc vdssprime += self._Vt * (np.log(self._vc / (2. * self._Vt)) - 0.6) # Channel-length modulation tmp = np.sqrt(i_f) - vdss / self._Vt deltav = np.sqrt(self.Lambda * tmp + 1. / 64) deltav *= 4. * self._Vt vds = .5 * (vPort1[0] - vPort1[2]) vip = np.sqrt(vdss * vdss + deltav * deltav) vip -= np.sqrt(pow(vds - vdss, 2) + deltav * deltav) deltal = np.log(1. + (vds - vip) / self._lc / self._t_ucrit) deltal *= self.Lambda * self._lc # Equivalent channel length including channel-length # modulation and velocity saturation lprime = self.ns * self._leff - deltal + (vds + vip) / self._t_ucrit leq = 0.5 * (lprime + np.sqrt(lprime * lprime + self._lmin * self._lmin)) # Reverse normalized current tmp = vp - vds - vPort1[2] tmp -= np.sqrt(vdssprime * vdssprime + deltav * deltav) tmp += np.sqrt(pow(vds - vdssprime, 2) + deltav * deltav) irprime = self.interp(tmp / self._Vt) # Reverse normalized currect for mobility model, intrinsic # charges/capacitances, thermal noise model and NQS time-constant ??? ir = self.interp((vp - vPort1[0]) / self._Vt) # Quasi-static model equations # Dynamic model for the intrinsic node charges sqvpphi = np.sqrt(vp + self.phiT + 1.e-6) nq = 1. + self._gammaa / 2. / sqvpphi # Normalized intrinsic node charges xf = np.sqrt(0.25 + i_f) xr = np.sqrt(0.25 + ir) tmp = pow(xf + xr, 2) qd = 3. * xr**3 + 6. * xr * xr * xf + 4. * xr * xf * xf + 2. * xf**3 qd *= 4. / 15. / tmp qd = -nq * (qd - .5) qs = 3. * xf**3 + 6. * xf * xf * xr + 4. * xf * xr * xr + 2. * xr**3 qs *= 4. / 15. / tmp qs = -nq * (qs - .5) qi = qs + qd qb1 = -self._gammaa * sqvpphi / self._Vt qb1 -= (nq - 1.) * qi / nq qb = ad.condassign(vgprime, qb1, -vgprime / self._Vt) # qg = -qi - qox - qb, but qox == 0, so: qg = -qi - qb - self.qox # Transconductance factor and mobility reduction due to vertical field betao = self.kpa * self.np * self._weff / leq if self.theta != 0.: # Simple mobility reduction model vpprime = 0.5 * (vp + np.sqrt(vp * vp + 2. * self._Vt * self._Vt)) beta = betao / (1. + theta * vpprime) else: # Rigorous mobility reduction model betaoprime = 1. + self.cox * self._qb0 / self.e0 / const.epSi betaoprime *= betao tmp = np.abs(qb + self.eta * qi) tmp = 1. + self.cox * self._Vt * tmp / self.e0 / const.epSi beta = betaoprime / tmp # Specific current IS = 2. * n * beta * self._Vt * self._Vt # Drain-to-source current ids = IS * (i_f - irprime) # import pdb; pdb.set_trace() # Impact ionization current vib = vPort1[0] - vPort1[2] - 2. * self.ibn * vdss idb1 = ids * self.iba * vib / self._t_ibb idb1 *= ad.safe_exp(-self._t_ibb * self._lc / vib) idb = ad.condassign(vib, idb1, 0.) # ------------------------------------------------------------- # Create output vectors qVec = np.array([0., qg, 0.], dtype=type(idb)) # have to switch charges if Drain and Source voltages switched qVec[0] = ad.condassign(DtoSswap, qd, qs) qVec[2] = ad.condassign(DtoSswap, qs, qd) # De-normalize charge and invert if needed qVec *= self._Cox * self._Vt * self._tf iVec = np.array([0., 0., 0.], dtype=type(idb)) iVec[0] = DtoSswap * ids iVec[1] = ad.condassign(DtoSswap, idb, 0.) iVec[2] = ad.condassign(DtoSswap, 0., idb) # Revert currents if needed iVec *= self._tf #-------------------------------------------------------------- # Operating point information if getOP: # Vth Vth = self._vt0a + self._deltavRSCE \ + gammaprime * np.sqrt(vsprime) \ - self._gammaa * np.sqrt(self.phiT) # Non quasi-static equations tau0 = .5 * self._Cox / self._Vt / beta tmp = (xf**2 + 3. * xf * xr + xr**2) / pow(xf + xr, 3) tau = tau0 * 4. / 15. * tmp # Create operating point variables dictionary return { 'Vp': vp, 'n': n, 'Beta': beta, 'IS': IS, 'IF': i_f, 'IR': ir, 'IRprime': irprime, 'tef': 1. / (np.sqrt(.25 + i_f) + .5), 'Vth': Vth, 'Vov': self._tf * n * (vp - vPort[2]), 'Vdsat': self._tf * self._Vt * (2. * np.sqrt(i_f) + 4.), 'tau0': tau0, 'tau': tau, 'Sthermal': self._kSt * beta * np.abs(qi), 'Reversed': DtoSswap < 0. } else: return (iVec, qVec)
def eval_cqs(self, vPort, getOP = False): """ Calculates currents and charges Input: vPort = [vdb , vgb , vsb] Output: iVec = [ids, idb, isb], qVec = [qd, qg, qs] If getOP = True, return normal output vector plus operating point variables in tuple: (iVec, qVec, opV) """ # import pdb; pdb.set_trace() # Invert all voltages in case of a P-channel device vPort1 = self._tf * vPort # If vds is negative, swap Vd and Vs and (also swap charges # later) DtoSswap = ad.condassign(vPort1[0] - vPort1[2], 1., -1.) # perform the swap (need tmp variable) tmp = ad.condassign(DtoSswap, vPort1[0], vPort1[2]) vPort1[2] = ad.condassign(DtoSswap, vPort1[2], vPort1[0]) vPort1[0] = tmp # Calculate VDS, VGS and VBS for bsim model VDS = vPort1[0] - vPort1[2] VGS = vPort1[1] - vPort1[2] VBS = -vPort1[2] # ---------------------------------------------------------------- T0 = VBS - self.vbsc - 0.001 T1 = np.sqrt(T0 * T0 - 0.004 * self.vbsc) Vbseff = self.vbsc + .5 * (T0 + T1) Vbseff = ad.condassign(-Vbseff + VBS, VBS, Vbseff) #Calculation of Phis, sqrtPhis and Xdep Phis = ad.condassign(Vbseff, self.phi**2 / (self.phi + Vbseff), self.phi - Vbseff) sqrtPhis = ad.condassign(Vbseff, self.phis3 / (self.phi + 0.5 * Vbseff), np.sqrt(Phis)) Xdep = self.Xdep0 * sqrtPhis / self.sqrtPhi #Calculation of Threshold voltage-vth T3 = np.sqrt(Xdep) T1 = ad.condassign(self.dvt2 * Vbseff + .5, 1. + self.dvt2 * Vbseff, (1. + 3. * self.dvt2 * Vbseff) / (3. + 8. * self.dvt2 * Vbseff)) ltl = self.factor1 * T3 * T1 T1 = ad.condassign(self.dvt2w * Vbseff + .5, 1. + self.dvt2w * Vbseff, (1. + 3. * self.dvt2w * Vbseff) / (3. + 8. * self.dvt2w * Vbseff)) ltw = self.factor1 * T3 * T1 # Alternative to prevent overflow (apparently not needed) #T2 = ad.safe_exp(-.5 * self.dvt1 * self.leff / ltl) k_temp = -.5 * self.dvt1 * self.leff / ltl T2 = ad.condassign(k_temp + EXP_THRESHOLD, ad.safe_exp(k_temp), MIN_EXP) Theta0 = T2 * (1. + 2. * T2) thetavth = self.dvt0 * Theta0 Delt_vth = thetavth * self.V0 # Alternative to prevent overflow (apparently not needed) #T2 = ad.safe_exp(-.5 * self.dvt1w * self.weff * self.leff / ltw) # Modified from freeda's source to prevent using uninitialized # variable k_temp = -.5 * self.dvt1w * self.weff * self.leff / ltw T2 = ad.condassign(k_temp + EXP_THRESHOLD, ad.safe_exp(k_temp), MIN_EXP) T2 *= (1. + 2. * T2) T0 = self.dvt0w * T2 T2 = T0 * self.V0 T0 = np.sqrt(1. + self.nlx / self.leff) T1 = self.k1ox * (T0 - 1.) * self.sqrtPhi \ + (self.kt1 + self.kt1l / self.leff + self.kt2 * Vbseff) \ * self._ToTnm1 TMP2 = self.tox * self.phi / (self.weff + self.w0) T3 = self.eta0 + self.etab * Vbseff T4 = ad.condassign(-T3 + 1.0e-4, 1. / (3. - 2e4 * self.eta0 + self.etab * Vbseff), 1.) dDIBL_Sft_dVd = T3 * self.theta0vb0 DIBL_Sft = dDIBL_Sft_dVd * VDS Vth = self._vth0 - self._k1 * self.sqrtPhi + self.k1ox * sqrtPhis \ - self.k2ox * Vbseff - Delt_vth - T2 \ + (self.k3 + self.k3b * Vbseff) * TMP2 + T1 - DIBL_Sft #Calculate n temp_tmp2 = self.nfactor * const.epSi / Xdep temp_tmp3 = self.cdsc + self.cdscb * Vbseff + self.cdscd * VDS temp_tmp4 = (temp_tmp2 + temp_tmp3 * Theta0 + self.cit) / self.cox n = ad.condassign(temp_tmp4 + .5, 1. + temp_tmp4, (1. + 3. * temp_tmp4) \ * (1. / (3. + 8. * temp_tmp4))) #Poly Gate Si Depletion Effect Vgs_eff = VGS Vgst = Vgs_eff - Vth # not in Nikhil's code #Effective Vgst (Vgsteff) Calculation T10 = 2. * n * self._Vt VgstNVt = Vgst / T10 ExpArg = -(2. * 0.08 + Vgst) / T10 T1 = T10 * log1pexp(VgstNVt) T2 = 1. + T10 * self.cox * ad.safe_exp(ExpArg) / self._Vt / self.cdep0 Vgsteff = ad.condassign(VgstNVt - EXP_THRESHOLD, Vgst, T1 / T2) Vgsteff = ad.condassign( ExpArg - EXP_THRESHOLD, self._Vt * self.cdep0 \ / self.cox / ad.safe_exp((Vgst + 0.08) / n / self._Vt), T1 / T2) T3 = T2 * T2 # Calculate Effective Channel Geometry T9 = sqrtPhis - self.sqrtPhi k_temp = self.weff - 2. * (self.dwg * Vgsteff + self.dwb * T9) Weff = ad.condassign(-k_temp + 2.0e-8, 2e-8 * (4.0e-8 - k_temp) * T0, k_temp) T0 = self.prwg * Vgsteff + self.prwb * (sqrtPhis - self.sqrtPhi) Rds = ad.condassign(T0 + 0.9, self.rds0 * (1. + T0), self.rds0 * (.8 +T0) / (17. + 20. * T0)) #Calculate Abulk T1 = 0.5 * self.k1ox / sqrtPhis T9 = np.sqrt(self.xj * Xdep) T5 = self.leff / (self.leff + 2. * T9) T2 = (self.a0 * T5) + self.b0 / (self.weff + self.b1) T6 = T5 * T5 T7 = T5 * T6 Abulk0 = 1. + T1 * T2 T8 = self.ags * self.a0 * T7 Abulk = Abulk0 - T1 * T8 * Vgsteff Abulk0 = ad.condassign(-Abulk0 + .1, (.2 - Abulk0) / (3. - 20. * Abulk0), Abulk0) Abulk = ad.condassign(-Abulk + .1, (.2 - Abulk) / (3. - 20. * Abulk), Abulk) T2 = self.keta * Vbseff T0 = ad.condassign(T2 + 0.9, 1. / (1. + T2), (17. + 20. * T2) / (0.8 + T2)) Abulk *= T0 Abulk0 *= T0 T0 = Vgsteff + 2. * Vth T2 = self._ua + self._uc * Vbseff T3 = T0 / self.tox T5 = T3 * (T2 + self._ub * T3) Denomi = ad.condassign(T5 + .8, 1. + T5, (.6 + T5) / (7. + 10. * T5)) ueff = self.u0temp / Denomi Esat = 2. * self.vsattemp / ueff # Saturation Drain Voltage Vdsat WVCox = Weff * self.vsattemp * self.cox WVCoxRds = WVCox * Rds EsatL = Esat * self.leff Lambda = self.a2 Vgst2Vtm = Vgsteff + 2. * self._Vt T0 = 1. / (Abulk * EsatL + Vgst2Vtm) T3 = EsatL * Vgst2Vtm Vdsat = T3 * T0 # Effective Vds(Vdseff) Calculation T1 = Vdsat - VDS - self.delta T2 = np.sqrt(T1**2 + 4. * self.delta * Vdsat) #T0 = T1 / T2 #T3 = 2. * self.delta / T2 k_temp = Vdsat - .5 * (T1 + T2) Vdseff = ad.condassign(k_temp - VDS, VDS, k_temp) # The following seems unnnecessary: # Added to eliminate non-zero Vdseff at Vds=0.0 #Vdseff = ad.condassign(abs(VDS), # Vdseff, # 0.) # Calculate Vasat T6 = 1. - .5 * Abulk * Vdsat / Vgst2Vtm #T6=tmp4 T9 = WVCoxRds * Vgsteff #expanded T0 = EsatL + Vdsat + 2. * T9 * T6 T9 = WVCoxRds * Abulk T1 = 2. / Lambda - 1. + T9 Vasat = T0 / T1 diffVds = VDS - Vdseff #Calculate VACLM VACLM = ad.condassign( (diffVds - 1.0e-10) * self.pclm, self.leff * (Abulk + Vgsteff / EsatL) * diffVds \ / (self.pclm * Abulk * self.litl), MAX_EXP) #Calculate VADIBL T1 = np.sqrt(const.epSi / const.epOx * self.tox * self.Xdep0) T0 = ad.safe_exp(-.5 * self.drout * self.leff / T1) T2 = T0 + 2. * T0**2 thetaRout = self.pdibl1 * T2 + self.pdibl2 #drout, pdibl1, #pdibl2 are given VADIBL = ad.condassign( thetaRout, (Vgst2Vtm - Vgst2Vtm * Abulk * Vdsat / (Vgst2Vtm + Abulk * Vdsat)) \ / thetaRout, MAX_EXP) VADIBL = ad.condassign(self.pdiblb * Vbseff + 0.9, VADIBL / (1. + self.pdiblb * Vbseff), VADIBL * (17. + 20. * self.pdiblb * Vbseff) \ / (.8 + self.pdiblb * Vbseff)) #Calculate Va T8 = self.pvag / EsatL T9 = T8 * Vgsteff T0 = ad.condassign(T9 + 0.9, 1. + T9, (.8 + T9) / (17. + 20. * T9)) T3 = VACLM + VADIBL #tmp3 = T3 T1 = VACLM * VADIBL / T3 Va = Vasat + T0 * T1 #Calculate VASCBE if self.pscbe1 != 0.: rcpVASCBE = ad.condassign( abs(diffVds), self.pscbe2 * ad.safe_exp(-self.pscbe1 * self.litl/diffVds) \ / self.leff, 0.) else: rcpVASCBE = self.pscbe2 / self.leff # Original: #VASCBE = ad.condassign( # diffVds - self.pscbe1 * self.litl / EXP_THRESHOLD, # self.leff * np.exp(self.pscbe1 * self.litl/diffVds) / self.pscbe2, # MAX_EXP * self.leff / self.pscbe2) #Calculate Ids CoxWovL = self.cox * Weff / self.leff beta = ueff * CoxWovL T0 = 1. - .5 * Abulk * Vdseff / Vgst2Vtm fgche1 = Vgsteff * T0 T9 = Vdseff / EsatL fgche2 = 1. + T9 gche = beta * fgche1 / fgche2 T0 = 1. + gche * Rds T9 = Vdseff / T0 Idl = gche * T9 T9 = diffVds / Va T0 = 1. + T9 Idsa = Idl * T0 T9 = diffVds * rcpVASCBE T0 = 1. + T9 Ids = Idsa * T0 #Substrate current begins T1 = self.alpha0 + self.alpha1 * self.leff k_temp = T1 / self.leff * diffVds #T2 = k_temp * ad.safe_exp(-self.beta0 / diffVds) # Original T2: ad.condassign( diffVds - self.beta0 / EXP_THRESHOLD, k_temp * ad.safe_exp(-self.beta0 / diffVds), k_temp * MIN_EXP) k_temp = ad.condassign(T1, T2 * Idsa, 0.) Isub = ad.condassign(self.beta0, k_temp, 0.) # ********* Output current vector ************** iVec = np.array([0., 0., 0.], dtype=type(Ids)) iVec[0] = DtoSswap * Ids iVec[1] = ad.condassign(DtoSswap, Isub, 0.) iVec[2] = ad.condassign(DtoSswap, 0., Isub) # Revert currents if needed iVec *= self._tf # ********************************************** #-------------------------------------------------------------- # Charge calculation follows (does not work): #calculation of vfbzb T0 = -.5 * self.dvt1w * self.weff * self.leff \ / self.factor1 / np.sqrt(self.Xdep0) #T2 = ad.safe_exp(T0) * (1. + 2. * ad.safe_exp(T0)) T2 = ad.condassign(T0 + EXP_THRESHOLD, ad.safe_exp(T0) * (1. + 2. * ad.safe_exp(T0)), MIN_1) T0 = self.dvt0w * T2 T2 = T0 * (self.vbi - self.phi) T0 = -.5 * self.dvt1 * self.leff / (self.factor1 * np.sqrt(self.Xdep0)) #T3 = ad.safe_exp(T0) * (1. + 2. * ad.safe_exp(T0)) T3 = ad.condassign(T0 + EXP_THRESHOLD, ad.safe_exp(T0) * (1. + 2. * ad.safe_exp(T0)), MIN_1) T3 = self.dvt0 * T3 * (self.vbi - self.phi) T4 = self.tox * self.phi / (self.weff + self.w0) T5 = self.k1ox * (T0 - 1.) * self.sqrtPhi \ + (self.kt1 + self.kt1l / self.leff) * self._ToTnm1 T0 = np.sqrt(1. + self.nlx / self.leff) T6 = self._vth0 - T2 -T3 + self.k3 * T4 + T5 vfbzb = T6 - self.phi - self._k1 * self.sqrtPhi #Calculation for VbseffCV VbseffCV = ad.condassign(Vbseff, self.phi - Phis, Vbseff) #Calculation for VgsteffCV T0 = n * self.noff * self._Vt T1 = (Vgs_eff - Vth) / T0 Vgsteff = ad.condassign(T1 - EXP_THRESHOLD, Vgs_eff - Vth - self.voffcv, T0 * log1pexp(T1)) # This (after the previous) can not be right: #Vgsteff = ad.condassign(-T1 - EXP_THRESHOLD, # T0 * log(1. + MIN_EXP), # T0 * log(1. + np.exp(T1))) #Calculation for Vfbeff V3 = vfbzb - Vgs_eff + VbseffCV - .02 T0 = np.sqrt(V3**2 + .08 * abs(vfbzb)) Vfbeff = vfbzb - 0.5 * (V3 + T0) T0 = (Vgs_eff - VbseffCV - vfbzb) / self._Tox #Calculation for Tcen T1 = T0 * self.acde Tcen = ad.condassign(EXP_THRESHOLD + T1, self.ldeb * np.exp(T1), self.ldeb * MIN_EXP) Tcen = ad.condassign(-EXP_THRESHOLD + T1, self.ldeb * MAX_EXP, Tcen) V3 = self.ldeb - Tcen - 1e-3 * self.tox V4 = np.sqrt(V3**2 + 4e-3 * self.tox * self.ldeb) Tcen = self.ldeb - .5 * (V3 + V4) Ccen = const.epSi / Tcen Coxeff = Ccen * self.cox / (Ccen + self.cox) #Calculation for QoverlapCox CoxWLcen = Weff * self.leff * Coxeff Qac0 = CoxWLcen * (Vfbeff - vfbzb) # QovCox = Qac0 / Coxeff T0 = .5 * self.k1ox T3 = Vgs_eff - Vfbeff - VbseffCV - Vgsteff T1 = np.sqrt(T0 * T0 + T3) T2 = CoxWLcen * T0 / T1 T1 = ad.condassign(-T3, T0 + T3 / self.k1ox, np.sqrt(T0**2 + T3)) T2 = ad.condassign(-T3, CoxWLcen, CoxWLcen * T0 / T1) Qsub0 = CoxWLcen * self.k1ox * (T1 - T0) # QovCox = Qsub0 / Coxeff #Calculation for Delta_phis T2 = ad.condassign(self.k1ox, self.moin * self._Vt * self.k1ox**2, .25 * self.moin * self._Vt) T0 = ad.condassign(self.k1ox, self.k1ox * np.sqrt(self.phi), .5 * np.sqrt(self.phi)) T1 = 2. * T0 + Vgsteff DeltaPhi = self._Vt * np.log(1. + T1 * Vgsteff / T2) #The calculation for Tcen must be done once more # fREEDA: #T0 = (Vgsteff + 4.*(self._vth0 - self.vfb - self.phi))/ (2. * self._Tox) # ngspice: T3 = 4. * (Vth - vfbzb - self.phi) T0 = ad.condassign(T3, .5 * (Vgsteff + T3) / self._Tox, .5 * (Vgsteff + 1.0e-20) / self._Tox) k_temp = 2.01375270747048 * T0 # was np.exp(.7 * log(T0)) T1 = 1. + k_temp T2 = 0.35 * k_temp / (T0 * self._Tox) Tcen = 1.9e-9 / T1 Ccen = const.epSi / Tcen Coxeff = Ccen * self.cox / (Ccen + self.cox) CoxWLcen = Weff * self.leff * Coxeff AbulkCV = Abulk0 * self.AbulkCVfactor VdsatCV = (Vgsteff - DeltaPhi) / AbulkCV T0 = VdsatCV - VDS - .02 T1 = np.sqrt(T0**2 + .08 * VdsatCV) # From ngspice: internal version BSIM3v32V32 VdseffCV = VdsatCV - .5 * (T0 + T1) # From freeda: # VdseffCV = ad.condassign(T0, # VdsatCV - .5 * (T0 + T1), # VdsatCV * (1. - .04/(T1-T0))) # Need this to prevent NaN in charge Jacobian VdseffCV += 1e-200 # Seems not needed #VdseffCV = ad.condassign(abs(VDS), # Vdseff, # 0.) T0 = AbulkCV * VdseffCV T1 = Vgsteff - DeltaPhi T2 = 12. * (T1 - .5 * T0 + 1e-20) T3 = T0 / T2 T4 = 1. - 12. * T3**2 T5 = AbulkCV * (6. * T0 * (4. * T1 - T0) / (T2**2) - .5) T6 = T5 * VdseffCV / AbulkCV qgate = CoxWLcen * (T1 - T0 * (.5 - T3)) qbulk = CoxWLcen * (1. - AbulkCV) * (.5*VdseffCV - T0*VdseffCV/T2) #QovCox = qbulk / Coxeff T2 = T2 / 12. T3 = .5 * CoxWLcen / (T2**2) T4 = T1 * (2. * T0**2 / 3. + T1*(T1 - 4. * T0 / 3.)) - 2. * T0**3 / 15. qsrc = -T3 * T4 qgate += Qac0 + Qsub0 - qbulk qbulk -= (Qac0 + Qsub0) qdrn = -(qbulk + qgate + qsrc) # ************ Output charge vector **************** qVec = np.array([0., qgate, 0.], dtype=type(qsrc)) # have to switch charges if Drain and Source voltages switched qVec[0] = ad.condassign(DtoSswap, qdrn, qsrc) qVec[2] = ad.condassign(DtoSswap, qsrc, qdrn) # invert sign if needed qVec *= self._tf # Return numpy array with one element per current source. return (iVec, qVec)
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) if self.type == 'n': self._tf = 1. elif self.type == 'p': self._tf = -1. # Change parameter default values if not self.is_set('u0'): self.u0 = 250 else: raise cir.CircuitError( '{0}: unrecognized type: {1}. Valid types are "n" or "p"'.format(self.instanceName, self.type)) # Nominal abs temperature self._Tn = const.T0 + self.tnom # Nominal Thermal voltage self._Vtn = const.k * self._Tn / const.q self.factor1 = np.sqrt(const.epSi / const.epOx * self.tox) Eg0 = 1.16 - 7.02e-4 * (self._Tn**2) / (self._Tn + 1108.0) ni = 1.45e10 * (self._Tn / 300.15) * np.sqrt(self._Tn / 300.15) \ * np.exp(21.5565981 - Eg0 / (2. * self._Vtn)) #self.esi = 11.7 * const.epsilon0 (replaced by const.epSi) self.ldrn = self.l self.wdrn = self.w t0 = pow(self.ldrn, self.lln) t1 = pow(self.wdrn, self.lwn) tmp1 = self.ll / t0 + self.lw / t1 + self.lwl / (t0 * t1) self.dl = self.lint + tmp1 #tmp2 = llc / t0 + lwc / t1 + lwlc / (t0 * t1) #self.dlc = dlc + tmp2 # ??? t2 = pow(self.ldrn, self.wln) t3 = pow(self.wdrn, self.wwn) tmp3 = self.wl / t2 + self.ww / t3 + self.wwl / (t2 * t3) self.dw = self.wint + tmp3 #tmp4 = wlc / t2 + wwc / t3 + wwlc / (t2 * t3) #self.dwc = dwc + tmp4 # ??? self.leff = self.l - 2.0 * self.dl self.weff = self.w - 2.0 * self.dw self.AbulkCVfactor = (1. + pow(self.clc/self.leff, self.cle)) #self.leffCV = l - 2.0 * dlc #self.t11 = leffCV * leffCV # was epOx = 3.453133e-11 self.cox = const.epOx / self.tox self.phi = 2. * self._Vtn * np.log(self.nch / ni) self.sqrtPhi = np.sqrt(self.phi) self.phis3 = self.sqrtPhi * self.phi self.Xdep0 = np.sqrt(2. * const.epSi / (const.q * self.nch * 1e6)) * self.sqrtPhi self.litl = np.sqrt(3. * self.xj * self.tox) self.vbi = self._Vtn * np.log(1.0e20 * self.nch / (ni**2)) self.cdep0 = np.sqrt(const.q * const.epSi * self.nch * 1e6 / 2. / self.phi) if not self.is_set('toxm'): self.toxm = self.tox if not self.is_set('dsub'): self.dsub = self.drout self.ldeb = np.sqrt(const.epSi * self._Vtn / (const.q * self.nch * 1e6)) / 3. #import pdb; pdb.set_trace() if (self.k1enable != 0.) and \ not (self.is_set('k1') or self.is_set('k2')): vbx = self.phi - 7.7348e-4 * self.nch * self.xt**2 # From ngspice vbx = -abs(vbx) Vbm = -abs(self.vbm) gamma1 = 5.753e-12 * np.sqrt(self.nch) / self.cox gamma2 = 5.753e-12 * np.sqrt(self.nsub) / self.cox T0 = gamma1 - gamma2 T1 = np.sqrt(self.phi - vbx) - self.sqrtPhi T2 = np.sqrt(self.phi * (self.phi - Vbm)) - self.phi self._k2 = T0 * T1 / (2. * T2 + Vbm) self._k1 = gamma2 - 2. * self._k2 * np.sqrt(self.phi - Vbm) # print self._k1, self._k2 else: self._k1 = self.k1 self._k2 = self.k2 if not self.is_set('vth0'): self._vth0 = self.vfb + self.phi + self._k1 * self.sqrtPhi else: self._vth0 = abs(self.vth0) self.k1ox = self._k1 * self.tox / self.toxm self.k2ox = self._k2 * self.tox / self.toxm t1 = np.sqrt(const.epSi / const.epOx * self.tox * self.Xdep0) t0 = ad.safe_exp(-0.5 * self.dsub * self.leff / t1) self.theta0vb0 = (t0 + 2.0 * t0**2) # From freeda, ngspice: self._Tox = 1e8 * self.tox #Calculation of vbsc(Vbc) and Vbseff if self._k2 < 0.: self.vbsc = .9 * (self.phi - (.5 * self._k1 / self._k2)**2) if self.vbsc > -3.: self.vbsc = -3. elif self.vbsc < -30.: self.vbsc = -30. else: self.vbsc = -30. if self.u0 > 1.: self._u0 = self.u0 * 1e-4 else: self._u0 = self.u0 if not thermal: # Calculate temperature-dependent variables self.set_temp_vars(self.temp)
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)