-- File: [Thyme]<Thyme>System>Spice>spModels.mesa
-- Last editted by SChen  8-Feb-84 20:21:27

DIRECTORY AltoDefs, Real, RealFns, spModelDefs;
spModels: PROGRAM
  IMPORTS RealFns, spModelDefs=
BEGIN

  modelError: SIGNAL[s: STRING]= CODE;

-- Temporary square root kluge until RealFns is fixed.

  SingleReal: TYPE= RECORD[m2: CARDINAL, -- Backwards!!!!
                           sign: BOOLEAN,
                           exp: [0..256),
                           m1: [0..128)];

  SquareReal: TYPE= RECORD[m2: CARDINAL, -- Backwards!!!!
                           sign: BOOLEAN,
                           expD2: [0..128),
                           index: [0..16),
                           m1: [0..16)];

  guesses: ARRAY[0..16) OF REAL;

  SqRt: PROCEDURE[x: REAL] RETURNS[y: REAL]= BEGIN
    xFmt: SquareReal;
    yFmt: SingleReal;

    IF x < 0.0 THEN SIGNAL modelError["SqRt of non-positive"];
    xFmt← LOOPHOLE[x, SquareReal];
    yFmt← LOOPHOLE[guesses[xFmt.index], SingleReal];
    yFmt.exp← yFmt.exp + xFmt.expD2 - 63;
    y← LOOPHOLE[yFmt, REAL];
    y← LOOPHOLE[LOOPHOLE[y + x/y, LONG CARDINAL] - 40000000B, REAL];
    y← LOOPHOLE[LOOPHOLE[y + x/y, LONG CARDINAL] - 40000000B, REAL];
  END; -- SqRt

  initSqRt: PROCEDURE= BEGIN
    i: CARDINAL;
    x1, x2: REAL;
    xFmt: SquareReal;

    FOR i IN [0..16) DO
      xFmt← [0, FALSE, 63, i, 0];
      x1← LOOPHOLE[xFmt, REAL];
      IF i < 15 THEN xFmt← [0, FALSE, 63, i + 1, 0]
      ELSE xFmt← [0, FALSE, 64, 0, 0];
      x2← LOOPHOLE[xFmt, REAL];
      guesses[i]← 2.0*(RealFns.SqRt[x1]*x2 -
                          RealFns.SqRt[x2]*x1)/(x2-x1)
    ENDLOOP
  END; -- initSqRt

  dcDiode: spModelDefs.model= BEGIN
    Vd, e: REAL;

    Va: CARDINAL= 0;
    Vc: CARDINAL= 1;

    Io: CARDINAL= 0;
    kTq: CARDINAL= 1;
    expMax: CARDINAL= 2;

    Vd← args[Va] - args[Vc];
    IF Vd + parms[kTq] <= 0.0 THEN results[0]← -parms[Io]
    ELSE BEGIN
      e← Vd/parms[kTq];
      IF e >= parms[expMax] THEN
        SIGNAL spModelDefs.Retreat["dcDiode."];
      results[0]← parms[Io]*(RealFns.Exp[e] - 1.0)
    END
  END; -- dcDiode

  acDiode: spModelDefs.model= BEGIN
    Vc: CARDINAL= 0; -- cathode voltage index
    Va: CARDINAL= 1; -- anode voltage index
  
    Co: CARDINAL= 0;
    Pb: CARDINAL= 1;
    Vt: CARDINAL= 2;
    Io: CARDINAL= 3;
    expMax: CARDINAL= 4;
  
    C: CARDINAL= 0;
    I: CARDINAL= 1;
  
    t1: REAL;
    V: REAL← args[Va] - args[Vc];
    IF V > 0 THEN BEGIN -- forward biased
      t1← V/parms[Vt];
      IF t1 >= parms[expMax] THEN SIGNAL spModelDefs.Retreat[
        "acDiode forward biased too much"];
      results[I]← -parms[Io]*(RealFns.Exp[t1]- 1.0);
    END ELSE results[I]← parms[Io];
    results[C]← parms[Co]/SqRt[1.0 - V/parms[Pb]];
  END; -- acDiode

  Baum: PROC[VgsX, Vbin, Eta, Phi, Vbs, Vmax, 
    L, UEffective, gammaD, SArg3: REAL] RETURNS[vdsat: REAL]= BEGIN

    v1, v2, xv, a1, b1, c1, d1, a3, b3, A, B, C, R, S, 
    r3, s2, P, p0, p2, fi, y3, delta4, xValid: REAL← 0.0;
    x4, poly4: ARRAY[1..8] OF REAL;
    a4, b4, Sig1, Sig2: ARRAY[1..4] OF REAL;
    Icount, Jcount: CARDINAL← 0;

    Icount← Jcount← 0;
    x4← poly4← ALL[0.0];
    a4← b4← ALL[0.0];
    Sig1← [1.0, -1.0, 1.0, -1.0];
    Sig2← [1.0, 1.0, -1.0, -1.0];
        
    v1← (VgsX-Vbin)/Eta+Phi-Vbs; 
    v2← Phi-Vbs;
    xv← Vmax*L/UEffective;
         
    a1← gammaD/0.75; b1← -2.0*(v1+xv); c1← -2.0*gammaD*xv; 
    d1← 2.0*v1*(v2+xv)-v2*v2-4.0/3.0*gammaD*SArg3;
        
    A← -b1; B← a1*c1-4.0*d1; C← -d1*(a1*a1-4.0*b1)-c1*c1;
       
    R← -A*A/3.0+B; r3← R*R*R;
    S← 2.0*A*A*A/27.0-A*B/3.0+C; s2← S*S;
       
    P← s2/4.0+r3/27.0; p0← ABS[P]; p2← SqRt[p0];
       
    -- get y3 value.
    IF P<0.0 THEN BEGIN 
      ro: REAL;
      ro← SqRt[s2/4.0+p0]; 
      ro← RealFns.Ln[ro]/3.0; 
      ro← RealFns.Exp[ro]; 
      fi← RealFns.ArcTan[-2.0*p2/S, 1]; -- arcTan(-2.0*p2/S); 
      y3← 2.0*ro*RealFns.Cos[fi/3.0]-A/3.0;
    END ELSE BEGIN -- 550 
      p3, p4: REAL;
      p3← RealFns.Exp[ RealFns.Ln[ABS[-S/2.0+p2]]/3.0 ];
      p4← RealFns.Exp[ RealFns.Ln[ABS[-S/2.0-p2]]/3.0 ];
      y3← p3+p4-A/3.0;
    END;
    
    a3← SqRt[a1*a1/4.0-b1+y3]; 
    b3← SqRt[y3*y3/4.0-d1];
    
    -- from a3, b3, Sig1, Sig2, y3 -> a4, b4 -> delta4 -> x4
    FOR I:CARDINAL IN[1..4] DO
      a4[I]← a1/2.0+Sig1[I]*a3; 
      b4[I]← y3/2.0+Sig2[I]*b3; 
      delta4← a4[I]*a4[I]/4.0-b4[I];
      IF delta4>=0.0 THEN BEGIN
        sqrtd4: REAL;
        sqrtd4← SqRt[delta4];
        Icount← Icount+1; 
        x4[Icount]← a4[I]/(-2.0)+sqrtd4; 
        Icount← Icount+1; 
        x4[Icount]← a4[I]/(-2.0)-sqrtd4;
      END; 
    ENDLOOP;
    
    FOR J:CARDINAL IN [1..Icount] DO 
      IF x4[J]<=0.0 AND J<Icount THEN LOOP; 
      
      poly4[J]← x4[J]*x4[J]*x4[J]*x4[J] + a1*x4[J]*x4[J]*x4[J]; 
      poly4[J]← poly4[J]+b1*x4[J]*x4[J]+c1*x4[J]+d1;
      
      IF ABS[poly4[J]]>1.0E-6 AND J<Icount THEN LOOP; 
      
      Jcount← Jcount+1; 
      IF Jcount<=1 THEN xValid← x4[J];
      IF x4[J]<=xValid THEN xValid← x4[J];
    ENDLOOP;
    
    IF Jcount>0 THEN vdsat← xValid*xValid+Vbs-Phi
    ELSE SIGNAL spModelDefs.Retreat[
      "Error in immediate velocity saturation model"];
  END; -- Baum

  Grove: PROC[gammaD, VgsX, Vbin, Eta, Phi, Vbs: REAL]
    RETURNS[vdsat: REAL]= BEGIN
    gammaD2, ArgV: REAL;
    gammaD2← gammaD*gammaD;
    ArgV← (VgsX-Vbin)/Eta+Phi-Vbs;
    IF ArgV<=0.0 THEN vdsat← 0.0
    ELSE BEGIN
      Arg: REAL;
      Arg← SqRt[1.0+4.0*ArgV/gammaD2];
      vdsat← (VgsX-Vbin)/Eta+gammaD2*(1.0-Arg)/2.0;
      IF vdsat<0.0 THEN vdsat←0.0;
    END;
  END; -- Grove
        
  SpiceLevel2: spModelDefs.model= BEGIN

    -- args
    VdX: CARDINAL= 0;
    VgX: CARDINAL= 1;
    VsX: CARDINAL= 2;
    VbX: CARDINAL= 3;

    -- parms
    LX: CARDINAL= 0;
    WX: CARDINAL= 1;
    XdX: CARDINAL= 2;
    XjX: CARDINAL= 3;
    IssatX: CARDINAL= 4;
    IdsatX: CARDINAL= 5;
    BetaX: CARDINAL= 6;
    PhiX: CARDINAL= 7;
    SqRtPhiX: CARDINAL= 8;
    SqRtPhi3X: CARDINAL= 9;
    PbX: CARDINAL= 10;
    SqRtPbX: CARDINAL= 11;
    VbpX: CARDINAL= 12;
    VbiX: CARDINAL= 13;
    GammaX: CARDINAL= 14;
    LambdaX: CARDINAL= 15;
    VtX: CARDINAL= 16;
    UoX: CARDINAL= 17;
    VmaxX: CARDINAL= 18;
    QNfsX: CARDINAL= 19;
    NsubX: CARDINAL= 20;
    UexpX: CARDINAL= 21;
    NeffX: CARDINAL= 22;
    EtaX: CARDINAL= 23;
    FactorX: CARDINAL= 24;
    CoxX: CARDINAL= 25;
    CovGSX: CARDINAL= 26;
    CovDSX: CARDINAL= 27;
    CgbMX: CARDINAL= 28;
    Cgb23rdsX: CARDINAL= 29;
    CbsBottomX: CARDINAL= 30;
    CbdBottomX: CARDINAL= 31;
    CbsSWX: CARDINAL= 32;
    CbdSWX: CARDINAL= 33;
    CbsFwd1X: CARDINAL= 34;
    CbsFwd2X: CARDINAL= 35;
    CbdFwd1X: CARDINAL= 36;
    CbdFwd2X: CARDINAL= 37;
    MjX: CARDINAL= 38;
    MjswX: CARDINAL= 39;
    FcPbX: CARDINAL= 40;
    SignX: CARDINAL= 41;
    
    reverse: BOOLEAN;
    Vd, Vg, Vs, Vb,
    Vds, Vgs, Vbs, Vbd, Id, Ibs, Ibd, 
    Cgb, Cgs, Cgd, Cbs, Cbd, t1,
    SArg, DsargDb, BArg, DbargDb,
    Vbin, XdBarg, XdSarg, ArgSS, ArgSD, DBArgS, DBArgD,
    ArgD, ArgS, gamaSD, DBXWD, DBXWS, gammaD, 
    DgdDvb, VgsT, VgdT,
    SArg3, body, UFactor, UEffective, 
    VgsX, bodyS,
    clFactor, von, vdsat, ArgG, beta1: REAL;
    
    Vd← MAX[-5.0, MIN[10.0, args[VdX]]]; 
    Vg← MAX[-5.0, MIN[10.0, args[VgX]]]; 
    Vs← MAX[-5.0, MIN[10.0, args[VsX]]]; 
    Vb← MAX[-5.0, MIN[10.0, args[VbX]]];
 
    IF parms[SignX]= -1 THEN 
      {Vd← -Vd; Vg← -Vg; Vs← -Vs; Vb← -Vb};
    
    IF Vs < Vd THEN reverse← FALSE
    ELSE {reverse← TRUE; t1← Vs; Vs← Vd; Vd← t1};
      
    Vds← Vd - Vs; Vgs← Vg - Vs; Vbs← Vb - Vs; Vbd← Vb - Vd;
    Id← 0;
    
    -- get Ibs
    t1← Vbs/parms[VtX];
    IF Vbs > 0 THEN BEGIN
      t1← MIN[t1, 85.0];
      Ibs← parms[SignX]*parms[IssatX]*(RealFns.Exp[t1]- 1.0);
    END ELSE Ibs← parms[SignX]*parms[IssatX]*t1;
    
    -- get Ibd  
    t1← Vbd/parms[VtX];
    IF Vbd > 0 THEN BEGIN
      t1← MIN[t1, 85.0];
      Ibd← parms[SignX]*parms[IdsatX]*(RealFns.Exp[t1]- 1.0);
    END ELSE Ibd← parms[SignX]*parms[IdsatX]*t1;
      
    -- 100 
    IF Vbs <= 0.0 THEN BEGIN 
      SArg← SqRt[parms[PhiX]-Vbs]; 
      DsargDb← -0.5/SArg;
    END ELSE BEGIN -- ( 110 )
      SArg← parms[SqRtPhiX]/(1.0+0.5*Vbs/parms[PhiX]); 
      DsargDb← -0.5*SArg*SArg/parms[SqRtPhi3X]; 
    END;
  
    -- 120
    IF Vbd <= 0 THEN BEGIN
      BArg← SqRt[parms[PhiX]+Vds-Vbs]; 
      DbargDb← -0.5/BArg;
    END ELSE BEGIN -- 130
      BArg← parms[SqRtPhiX]/(1.0+0.5*(Vbs-Vds)/parms[PhiX]); 
      DbargDb← -0.5*BArg*BArg/parms[SqRtPhi3X]; 
    END;
    
    -- 200, Calculate von, narrow channel effect
    Vbin← parms[VbiX]+parms[FactorX]*(parms[PhiX]-Vbs);
    XdBarg← parms[XdX]*BArg; 
    XdSarg← parms[XdX]*SArg; 

    -- short channel effect with Vds # 0
    ArgSS← ArgSD← DBArgS← DBArgD← 0.0;
    IF parms[XjX] > 0.0 THEN BEGIN
      ArgXS, ArgXD: REAL;
      ArgXS← 1.0+2.0*XdSarg/parms[XjX];
      ArgS← SqRt[ArgXS]; 
      ArgSS← 0.5*parms[XjX]/parms[LX]*(ArgS-1.0);
      ArgXD← 1.0+2.0*XdBarg/parms[XjX];
      ArgD← SqRt[ArgXD]; 
      ArgSD← 0.5*parms[XjX]/parms[LX]*(ArgD-1.0);
    END;
    
    --(205)
    gamaSD← parms[GammaX]*(1.0-ArgSS-ArgSD);
    DBXWD← parms[XdX]*DbargDb; 
    DBXWS← parms[XdX]*DsargDb; 
    IF parms[XjX] > 0.0 THEN BEGIN
      DBArgS← 0.5/parms[LX]*DBXWS/ArgS;
      DBArgD← 0.5/parms[LX]*DBXWD/ArgD;
    END;
    
    DgdDvb← -parms[GammaX]*(DBArgS+DBArgD); 
    
    --220 
    von← Vbin+gamaSD*SArg;
    vdsat← 0.0;

    --225
    BEGIN
      IF parms[QNfsX] = 0.0 THEN BEGIN -- 230
        VgsT← Vgs-von;
        IF VgsT<=0.0 THEN GOTO done; -- (Id=0) 1050 
      END ELSE BEGIN
        CdOnCo, Xn: REAL;
        CdOnCo← -(gamaSD*DsargDb+DgdDvb*SArg)+parms[FactorX];
        Xn← 1.0+parms[QNfsX]/parms[CoxX]+CdOnCo; 
        von← von+parms[VtX]*Xn; 
        ArgG← 1.0/(parms[VtX]*Xn);
        VgsT← Vgs-von;
      END; 
        
      -- 300
      SArg3← SArg*SArg*SArg;
      gammaD← gamaSD; 
      body← BArg*BArg*BArg-SArg3; 
      
      -- 400, EVALUATE EFFECTIVE MOBILITY AND ITS DERIVATIVES
      IF VgsT > parms[VbpX] THEN BEGIN
        UFactor← RealFns.Exp[ parms[UexpX]*RealFns.Ln[parms[VbpX]/VgsT] ]; 
        UEffective← parms[UoX]*UFactor; 
      END ELSE BEGIN  -- 410
        UFactor← 1.0; 
        UEffective← parms[UoX]; 
      END;
      
      -- 500 EVALUATE SATURATION VOLTAGE AND ITS DERIVATIVES
      -- ACCORDING TO GROVE-FROHMAN EQUATION
      VgsX← Vgs;
      gammaD← gamaSD/parms[EtaX]; 
      IF parms[QNfsX]#0.0 THEN VgsX← MAX[Vgs, von];
      vdsat← IF gammaD <= 0.0 THEN MAX[0.0, (VgsX-Vbin)/parms[EtaX]]
      ELSE Grove[gammaD, VgsX, Vbin, parms[EtaX], parms[PhiX], Vbs]; 
      
      -- 545
      IF parms[VmaxX] > 0.0 THEN vdsat← Baum[
        VgsX, Vbin, parms[EtaX], parms[PhiX], Vbs, parms[VmaxX], 
        parms[LX], UEffective, gammaD, SArg3];
        
      -- 600, EVALUATE EFFECTIVE CHANNEL LENGTH AND ITS DERIVATIVES
      IF Vds # 0.0 THEN BEGIN
        lFactor, ArgV, xdv, lv, ls, SArgV, BSArg: REAL;
         
        gammaD← gamaSD; 
        BSArg← IF Vbs <= vdsat THEN SqRt[vdsat-Vbs+parms[PhiX]]
               ELSE parms[SqRtPhiX]/(1.0+0.5*(Vbs-vdsat)/parms[PhiX]);
          
        bodyS← BSArg*BSArg*BSArg-SArg3; 
        IF parms[VmaxX] <=0.0 THEN BEGIN
          IF parms[NsubX] # 0.0 AND parms[LambdaX] <= 0.0 THEN BEGIN
            Arg: REAL;
            ArgV← (Vds-vdsat)/4.0;
            SArgV← SqRt[1.0+ArgV*ArgV];
            Arg← SqRt[ArgV+SArgV]; 
            lFactor← parms[XdX]/(parms[LX]*Vds);
            parms[LambdaX]← lFactor*Arg;
          END;
        END ELSE BEGIN -- 603 
          ArgV← (VgsX-Vbin)/parms[EtaX]-vdsat;
          xdv← parms[XdX]/SqRt[parms[NeffX]]; 
          lv← parms[VmaxX]*xdv/(2.0*UEffective); 
          IF parms[NsubX] # 0.0 AND parms[LambdaX] <= 0.0 THEN BEGIN
            ArgV← MAX[Vds-vdsat, 0.0]; 
            ls← SqRt[lv*lv+ArgV]; 
            lFactor← xdv/(parms[LX]*Vds); 
            parms[LambdaX]← lFactor*(ls-lv);
          END;
        END; 
      END; -- block 600
      
        -- 620, LIMIT CHANNEL SHORTENING AT PUNCH-THROUGH 
      BEGIN
        leffective, deltaL, xwb, ld: REAL;
        xwb← parms[XdX]*parms[SqRtPbX];
        ld← parms[LX]-xwb;
        clFactor← 1.0-parms[LambdaX]*Vds; 
        leffective← parms[LX]*clFactor; 
        deltaL← parms[LambdaX]*Vds*parms[LX];
        IF parms[NsubX]=0.0 THEN xwb← 0.25E-6; 
        IF leffective<xwb THEN BEGIN  
          leffective← xwb/(1.0+(deltaL-ld)/xwb);
          clFactor← leffective/parms[LX]; 
        END;
      END;

      -- 700 EVALUATE EFFECTIVE beta (EFFECTIVE KP) 
      beta1← parms[BetaX]*UFactor/clFactor;
 
      -- TEST FOR MODE OF OPERATION AND BRANCH APPROPRIATELY
      gammaD← gamaSD; 
      IF Vds <= 1.0E-8 THEN GOTO done; -- (Id=0) 1050
      -- 730
      IF VgsT <= 0.0 THEN BEGIN -- else goto 900 
        VdsOn, IdsOn: REAL;
      
        -- SUBTHRESHOLD REGION, IF vdsat<=0.0 THEN Id← 0.0
        IF  vdsat > 0.0 THEN BEGIN -- 830
          VdsOn← MIN[vdsat,Vds];
          IF Vds > vdsat THEN body← bodyS;
          -- 850 
          IdsOn← beta1*((von-Vbin-parms[EtaX]*VdsOn*0.5)*VdsOn-gammaD*body/1.5);
          Id← parms[SignX]*IdsOn*RealFns.Exp[ArgG*VgsT];
        END;
      
      END ELSE 
        Id← IF Vds <= vdsat THEN -- 900 LINEAR REGION
              parms[SignX]*beta1*((Vgs-Vbin-parms[EtaX]*Vds/2.0)*Vds-gammaD*body/1.5) 
            ELSE -- 1000 SATURATION REGION
              parms[SignX]*beta1*((Vgs-Vbin-parms[EtaX]*vdsat/2.0)*vdsat-gammaD*bodyS/1.5);
    EXITS
      done=> NULL;
    END;
      
    -- capacitances, and results
    VgdT← Vg - Vd - von;

    -- Cgb:
    Cgb← IF VgsT <= 0 THEN parms[CgbMX] ELSE 0;
                       
    -- Cgs and Cgd:

    t1← VgsT+VgdT;
    t1← t1*t1; -- (Vgs + Vgd - 2von)↑2
    Cgs← IF VgsT <= 0.0 THEN parms[CovGSX]
       ELSE IF VgdT <= 0.0 THEN parms[Cgb23rdsX] + parms[CovGSX]
       ELSE parms[CovGSX] + parms[Cgb23rdsX]*(1.0 - VgdT*VgdT/t1);
    Cgd← IF VgdT <= 0.0 THEN parms[CovDSX]
       ELSE parms[CovDSX] + parms[Cgb23rdsX]*(1.0 - VgsT*VgsT/t1);

    -- Cbs and Cbd

    IF Vbs >= parms[FcPbX] THEN -- 520
      Cbs← parms[CbsFwd1X] + Vbs*parms[CbsFwd2X]
    ELSE BEGIN
      t1← RealFns.Ln[1.0-Vbs/parms[PbX]];
      Cbs← parms[CbsBottomX]*RealFns.Exp[-parms[MjX]*t1] + parms[CbsSWX]*RealFns.Exp[-parms[MjswX]*t1]
    END;
    
    IF Vbd >= parms[FcPbX] THEN -- 520
      Cbd← parms[CbdFwd1X] + Vbd*parms[CbdFwd2X]
    ELSE BEGIN
      t1← RealFns.Ln[1.0-Vbd/parms[PbX]];
      Cbd← parms[CbdBottomX]*RealFns.Exp[-parms[MjX]*t1] + parms[CbdSWX]*RealFns.Exp[-parms[MjswX]*t1]
    END;
    
    -- results order: Id, Cgb, Cgs, Cgd, Cbs, Cbd, Ibs, Ibd.
    IF reverse THEN BEGIN
      results[0]← -Id;
      results[1]← Cgb;
      results[2]← Cgd;
      results[3]← Cgs;
      results[4]← Cbd;
      results[5]← Cbs;
      results[6]← Ibd;
      results[7]← Ibs;
    END ELSE BEGIN
      results[0]← Id;
      results[1]← Cgb;
      results[2]← Cgs;
      results[3]← Cgd;
      results[4]← Cbs;
      results[5]← Cbd;
      results[6]← Ibs;
      results[7]← Ibd;
    END;
  END; -- SpiceLevel2  
  
  initSqRt[];
  
  spModelDefs.EnterModels["SpiceLevel2", SpiceLevel2, 4, 42, 8];
  spModelDefs.EnterModels["acDiode",    acDiode,    2,  5, 2];
  spModelDefs.EnterModels["dcDiode",    dcDiode,    2,  3, 1];

END.