-- FILE: ModelImpl.mesa -- Last edited by Ousterhout, November 22, 1983 3:32 pm -- This file contains the implementation of various routines -- and constants for dealing with technology-specific electrical -- information. DIRECTORY Globals, IO, Model, Parse, RealFns, Rope, SlopeModel; ModelImpl: CEDAR PROGRAM IMPORTS Globals, IO, Parse, RealFns, Rope, SlopeModel EXPORTS Model = BEGIN OPEN Model, Globals; -- The model-independent parameters, and tables used to access them. diffCPerArea: PUBLIC REF REAL ← NEW[REAL ← .00029]; polyCPerArea: PUBLIC REF REAL ← NEW[REAL ← .00004]; metalCPerArea: PUBLIC REF REAL ← NEW[REAL ← .000026]; diffCPerPerim: PUBLIC REF REAL ← NEW[REAL ← .00023]; diffR: PUBLIC REF REAL ← NEW[REAL ← 60]; polyR: PUBLIC REF REAL ← NEW[REAL ← 3]; metalR: PUBLIC REF REAL ← NEW[REAL ← .04]; vdd: PUBLIC REF REAL ← NEW[REAL ← 5.0]; vinv: PUBLIC REF REAL ← NEW[REAL ← 2.5]; rUpFactor: PUBLIC REF REAL ← NEW[REAL ← 1.0]; rDownFactor: PUBLIC REF REAL ← NEW[REAL ← 1.0]; -- Gives names of various parasitic parameters. parmNames: ARRAY[0..9) OF Rope.ROPE ← [ "diffcperarea", "diffcperperim", "diffresistance", "metalcperarea", "metalresistance", "polycperarea", "polyresistance", "vdd", "vinv" ]; -- Gives units for various parmaters (used in printing them out). parmUnits: ARRAY[0..9) OF Rope.ROPE ← [ "pf/sq. micron", "pf/micron", "ohms/square", "pf/sq. micron", "ohms/square", "pf/sq. micron", "ohms/square", "volts", "volts" ]; parmLocs: ARRAY[0..9) OF REF REAL ← [ diffCPerArea, diffCPerPerim, diffR, metalCPerArea, metalR, polyCPerArea, polyR, vdd, vinv ]; -- Information about different models: nModels: INT = 2; curModel: INT ← 1; modelNames: ARRAY[0..nModels) OF Rope.ROPE ← [ "rc (simple lumped RC approximation)", "slope (not implemented yet)" ]; modelDelayProcs: ARRAY[0..nModels) OF DelayProc ← [ RCDelay, SlopeModel.SlopeDelay ]; TypeTable: PUBLIC ARRAY[0..maxFetTypes) OF TType; -- Names of type table fields: fieldNames: ARRAY[0..9) OF Rope.ROPE ← [ "cperarea", "cperwidth", "histrength", "lowstrength", "on", "rdown", "rup", "slopeparmsdown", "slopeparmsup" ]; ModelCmd: PUBLIC CmdProc = BEGIN index: INT; -- If no arguments, just print out information about the available -- models. IF args = NIL THEN BEGIN FOR i:INT IN [0..nModels) DO IF i = curModel THEN IO.PutF[StdOut, "**** %s\n", IO.rope[modelNames[i]]] ELSE IO.PutF[StdOut, " %s\n", IO.rope[modelNames[i]]]; ENDLOOP; RETURN; END; TRUSTED {index ← Parse.Lookup[args.rope, DESCRIPTOR[modelNames]]}; IF index = -1 THEN BEGIN IO.PutF[StdOut, "%s isn't a model name.\n", IO.rope[args.rope]]; RETURN; END ELSE IF index = -2 THEN BEGIN IO.PutF[StdOut, "%s is an ambiguous model abbreviation.\n", IO.rope[args.rope]]; RETURN; END; curModel ← index; END; ParmCmd: PUBLIC CmdProc = BEGIN name: Rope.ROPE; valueArg: Arg; value: REAL; index: INT; ok: BOOLEAN; -- If no arguments, then just print out all current values. IF args = NIL THEN BEGIN FOR i:INTEGER IN [0..LENGTH[parmNames]) DO IO.PutF[StdOut, "%s = %f %s\n", IO.rope[parmNames[i]], IO.real[parmLocs[i]↑], IO.rope[parmUnits[i]]]; ENDLOOP; RETURN; END; -- Arguments must come in <name value> pairs. WHILE args # NIL DO name ← args.rope; args ← args.next; IF args = NIL THEN BEGIN IO.PutF[StdOut, "No value given for %s.\n", IO.rope[name]]; EXIT; END; valueArg ← args; args ← args.next; TRUSTED {index ← Parse.Lookup[name, DESCRIPTOR[parmNames]]}; IF index = -2 THEN BEGIN IO.PutF[StdOut, "%s isn't a model parameter.\n", IO.rope[name]]; LOOP; END; IF index = -1 THEN BEGIN IO.PutF[StdOut, "%s is an ambiguous abbreviation.\n", IO.rope[name]]; LOOP; END; [ok, value] ← Parse.Real[valueArg]; IF (value <= 0.0) OR (NOT ok) THEN BEGIN IO.PutF[StdOut, "Bad value given for %s.\n", IO.rope[name]]; LOOP; END; parmLocs[index]↑ ← value; ENDLOOP; END; getSlope: PROC[args: Arg, ratio, rEff, outES: REF ParmArray] RETURNS [Arg] = -- This is a utility routine used to read in slope table -- information. The arguments are processed in batches -- of three, filling up the tables. The return result is -- the next argument after the last one used. BEGIN ok: BOOLEAN; val: REAL; i: INT; FOR i IN [0..maxSlopePoints) DO [ok, val] ← Parse.Real[args]; IF NOT ok THEN EXIT; args ← args.next; IF val < 0.0 THEN BEGIN IO.PutF[StdOut, "Can't have negative value %f in slope table.\n", IO.real[val]]; EXIT; END; ratio[i] ← val; [ok, val] ← Parse.Real[args]; IF NOT ok THEN BEGIN IO.PutRope[StdOut, "Only one number supplied for last slope table entry.\n"]; EXIT; END; args ← args.next; rEff[i] ← val; [ok, val] ← Parse.Real[args]; IF NOT ok THEN BEGIN IO.PutRope[StdOut, "Only two numbers supplied for last slope table entry.\n"]; EXIT; END; args ← args.next; outES[i] ← val; -- Watch out for decreasing ratio values. IF i > 0 THEN BEGIN IF ratio[i] < ratio[i-1] THEN BEGIN IO.PutF[StdOut, "Warning: edge speed ratio %f", IO.real[ratio[i]]]; IO.PutRope[StdOut, " isn't monotonically increasing.\n"]; IO.PutRope[StdOut, " Interpolation table truncated.\n"]; EXIT; END; END; ENDLOOP; -- Mark the end of the table with a negative ratio. If there's -- only a single entry, add a second one so that interpolation -- and extrapolation will work. IF i = 0 THEN BEGIN ratio[0] ← 0; rEff[0] ← 1000; outES[0] ← 0; i ← 1; END; IF i = 1 THEN BEGIN ratio[1] ← ratio[0] + 1000; rEff[1] ← rEff[0]; outES[1] ← outES[0]; ratio[2] ← -1; END ELSE IF i < maxSlopePoints THEN ratio[i] ← -1; RETURN [args]; END; printType: PROC[index: INTEGER] = -- This is a utility routine used to print out all the information for -- a transistor of a given type. BEGIN IO.PutF[StdOut, "%s: ", IO.rope[TypeTable[index].name]]; IF TypeTable[index].on0 THEN IO.PutRope[StdOut, "on gate0"]; IF TypeTable[index].on1 THEN IO.PutRope[StdOut, "on gate1"]; IF TypeTable[index].onAlways THEN IO.PutRope[StdOut, "on always"]; IO.PutF[StdOut, " histrength %d", IO.int[TypeTable[index].strengthHi]]; IO.PutF[StdOut, " lowstrength %d", IO.int[TypeTable[index].strengthLo]]; IO.PutF[StdOut, "\n cperarea %.3f cperwidth %.3f", IO.real[TypeTable[index].cPerArea], IO.real[TypeTable[index].cPerWidth]]; IO.PutF[StdOut, " rup %.1f rdown %.1f\n", IO.real[TypeTable[index].rUp], IO.real[TypeTable[index].rDown]]; IO.PutRope[StdOut, " Slope parameters up (ratio, rEff, outES) =\n"]; printSlope[TypeTable[index].upESRatio, TypeTable[index].upREff, TypeTable[index].upOutES, maxSlopePoints]; IO.PutRope[StdOut, " Slope parameters down (ratio, rEff, outES) =\n"]; printSlope[TypeTable[index].downESRatio, TypeTable[index].downREff, TypeTable[index].downOutES, maxSlopePoints]; END; printSlope: PROC[esRatio, rEff, outES: REF ParmArray, maxPoints: INT] = -- This is a utility routine used to print out the values in slope -- tables. The values in the tables are printed in a reasonably -- pretty fashion, two entries from each table per line. BEGIN lineHasStuff: BOOLEAN ← FALSE; FOR i:INT IN [0..maxPoints) DO IF esRatio[i] < 0 THEN EXIT; IO.PutF[StdOut, " %8.3f, %8.1f, %8.3f;", IO.real[esRatio[i]], IO.real[rEff[i]], IO.real[outES[i]]]; IF lineHasStuff THEN BEGIN lineHasStuff ← FALSE; IO.PutRope[StdOut, "\n"]; END ELSE lineHasStuff ← TRUE; ENDLOOP; IF lineHasStuff THEN IO.PutRope[StdOut, "\n"]; END; TransistorCmd: PUBLIC CmdProc = BEGIN type: INT; field: Rope.ROPE; valueArg: Arg; index, intVal: INT; realVal: REAL; ok: BOOLEAN; -- If there are no arguments, print out all fields of all transistor types. -- If there is only one argument print out all fields for that transistor -- type. IF args = NIL THEN BEGIN FOR i:INTEGER IN [0..maxFetTypes) DO IF TypeTable[i] = NIL THEN RETURN; printType[i]; ENDLOOP; RETURN; END ELSE IF args.next = NIL THEN BEGIN FOR i: INTEGER IN [0..maxFetTypes) DO IF TypeTable[i] = NIL THEN EXIT; IF Rope.Equal[args.rope, TypeTable[i].name, FALSE] THEN BEGIN printType[i]; RETURN; END; ENDLOOP; IO.PutF[StdOut, "Transistor type %s doesn't exist.\n", IO.rope[args.rope]]; RETURN; END; -- Make sure that the type name exists. Create a new one -- if necessary. type ← -1; FOR i: INTEGER IN [0..maxFetTypes) DO IF TypeTable[i] = NIL THEN BEGIN type ← i; TypeTable[type] ← NEW[TTypeRec ← [name: args.rope, strengthHi: 0, strengthLo: 0, cPerArea: 0, cPerWidth: 0, rUp: 0, rDown: 0, on0: FALSE, on1: TRUE, onAlways: FALSE, upESRatio: NEW[ParmArray ← [0, 1000, -1, 0, 0, 0, 0, 0, 0, 0]], upREff: NEW[ParmArray ← [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], upOutES: NEW[ParmArray ← [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], downESRatio: NEW[ParmArray ← [0, 1000, -1, 0, 0, 0, 0, 0, 0, 0]], downREff: NEW[ParmArray ← [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], downOutES: NEW[ParmArray ← [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]]]; EXIT; END; IF Rope.Equal[args.rope, TypeTable[i].name, FALSE] THEN BEGIN type ← i; EXIT; END; ENDLOOP; -- Process <field value> pairs. args ← args.next; WHILE args # NIL DO field ← args.rope; args ← args.next; IF args = NIL THEN BEGIN IO.PutF[StdOut, "No value given for %s.\n", IO.rope[field]]; EXIT; END; valueArg ← args; args ← args.next; TRUSTED {index ← Parse.Lookup[field, DESCRIPTOR[fieldNames]]}; IF index = -2 THEN BEGIN IO.PutF[StdOut, "%s isn't a field name.\n", IO.rope[field]]; LOOP; END; IF index = -1 THEN BEGIN IO.PutF[StdOut, "%s is an ambiguous abbreviation.\n", IO.rope[field]]; LOOP; END; SELECT index FROM 0 => -- cperarea BEGIN [ok, realVal] ← Parse.Real[valueArg]; IF (NOT ok) OR (realVal < 0) THEN IO.PutRope[StdOut, "Bad capacitance value for cperarea.\n"] ELSE TypeTable[type].cPerArea ← realVal; END; 1 => -- cperwidth BEGIN [ok, realVal] ← Parse.Real[valueArg]; IF (NOT ok) OR (realVal < 0) THEN IO.PutRope[StdOut, "Bad capacitance value for cperwidth.\n"] ELSE TypeTable[type].cPerWidth ← realVal; END; 2 => -- histrength BEGIN [ok, intVal] ← Parse.Int[valueArg]; IF (NOT ok) OR (intVal < 0) THEN IO.PutRope[StdOut, "Bad value for histrength.\n"] ELSE TypeTable[type].strengthHi ← intVal; END; 3 => -- lowstrength BEGIN [ok, intVal] ← Parse.Int[valueArg]; IF (NOT ok) OR (intVal < 0) THEN BEGIN IO.PutRope[StdOut, "Bad value for lowstrength.\n"]; EXIT; END; TypeTable[type].strengthLo ← intVal; END; 4 => -- on BEGIN IF Rope.Equal[valueArg.rope, "gate1", TRUE] THEN BEGIN TypeTable[type].on1 ← TRUE; TypeTable[type].on0 ← FALSE; TypeTable[type].onAlways ← FALSE; END ELSE IF Rope.Equal[valueArg.rope, "gate0", TRUE] THEN BEGIN TypeTable[type].on0 ← TRUE; TypeTable[type].on1 ← FALSE; TypeTable[type].onAlways ← FALSE; END ELSE IF Rope.Equal[valueArg.rope, "always", TRUE] THEN BEGIN TypeTable[type].on0 ← FALSE; TypeTable[type].on1 ← FALSE; TypeTable[type].onAlways ← TRUE; END ELSE IO.PutF[StdOut, "Unknown \"on\" condition: %s\n", IO.rope[valueArg.rope]]; END; 5 => -- rdown BEGIN [ok, realVal] ← Parse.Real[valueArg]; IF (NOT ok) OR (realVal < 0) THEN IO.PutRope[StdOut, "Bad value for rdown.\n"] ELSE TypeTable[type].rDown ← realVal; END; 6 => -- rup BEGIN [ok, intVal] ← Parse.Int[valueArg]; IF (NOT ok) OR (realVal < 0) THEN IO.PutRope[StdOut, "Bad value for rup.\n"] ELSE TypeTable[type].rUp ← intVal; END; 7 => -- slopeparmsdown BEGIN args ← getSlope[valueArg, TypeTable[type].downESRatio, TypeTable[type].downREff, TypeTable[type].downOutES]; END; 8 => -- slopeparmsup BEGIN args ← getSlope[valueArg, TypeTable[type].upESRatio, TypeTable[type].upREff, TypeTable[type].upOutES]; END; ENDCASE; ENDLOOP; rUpFactor↑ ← - RealFns.Ln[(vdd↑ - vinv↑)/vdd↑]; rDownFactor↑ ← - RealFns.Ln[vinv↑/vdd↑]; END; NameToIndex: PUBLIC PROC[name: Rope.ROPE] RETURNS [index: INT] = BEGIN FOR i:INTEGER IN [0..maxFetTypes) DO IF TypeTable[i] = NIL THEN RETURN [-1]; IF Rope.Equal[TypeTable[i].name, name, FALSE] THEN RETURN [i]; ENDLOOP; RETURN [-1]; END; Delay: PUBLIC DelayProc = BEGIN (modelDelayProcs[curModel])[stage]; END; RCDelay: PUBLIC DelayProc = -- This procedure updates the time value in the given stage by -- computing the delay in the stage and combining that with the -- time from the previous stage. A simple RC model is used to -- compute the delay. BEGIN f: Fet; r, c, tmp, rFactor: REAL; i: INT; -- Make sure that there is stuff in the path. stage.time ← -1.0; IF (stage.piece1Size = 0) AND (stage.piece2Size = 0) THEN BEGIN IO.PutRope[StdOut, "Crystal bug: nothing in stage!"]; RETURN; END; -- Go through the two pieces of the path, summing all the -- resistances in both pieces and the capacitances in piece2 -- (this code assumes that the capacitance in piece1 has already -- been drained away before the driving transistor turns on). -- If a zero FET resistance is found, it means that the transistor -- can't drive in the given direction, so quit. r ← 0.0; c ← 0.0; IF stage.rise THEN rFactor ← rUpFactor↑ ELSE rFactor ← rDownFactor↑; FOR i IN [0..stage.piece1Size) DO f ← stage.piece1Fet[i]; IF stage.rise THEN tmp ← TypeTable[f.type].rUp ELSE tmp ← TypeTable[f.type].rDown; IF tmp = 0 THEN RETURN; r ← r + tmp*f.aspect + (stage.piece1Node[i].res*rFactor); ENDLOOP; FOR i IN [0..stage.piece2Size) DO f ← stage.piece2Fet[i]; IF f # NIL THEN BEGIN IF stage.rise THEN tmp ← TypeTable[f.type].rUp ELSE tmp ← TypeTable[f.type].rDown; IF tmp = 0 THEN RETURN; r ← r + tmp*f.aspect; c ← c + f.area*TypeTable[f.type].cPerArea; END; r ← r + (stage.piece2Node[i].res*rFactor); c ← c + stage.piece2Node[i].cap; ENDLOOP; -- Throw out the capacitance and resistance from the node that -- is supplying the signal (either the first node in piece1, or the -- first node in piece2 if there isn't a piece1). This node is treated -- here as an infinite source of charge. IF stage.piece1Size # 0 THEN r ← r - (stage.piece1Node[stage.piece1Size-1].res*rFactor) ELSE BEGIN r ← r - (stage.piece2Node[0].res*rFactor); c ← c - stage.piece2Node[0].cap; END; stage.time ← (r*c)/1000.0 + stage.prev.time; END; TypeTable[fetNEnh] ← NEW[TTypeRec ← [name: "NETran", strengthHi: 2, strengthLo: 3, cPerArea: .0004, cPerWidth: .00025, rUp: 24800, rDown: 9720, on0: FALSE, on1: TRUE, onAlways: FALSE, upESRatio: NEW[ParmArray ← [0, .0014, .0026, .017, .16, 1.6, 16, -1, 0, 0]], upREff: NEW[ParmArray ← [24800, 24800, 24600, 25800, 38400, 153000, 1070000, 0, 0, 0]], upOutES: NEW[ParmArray ← [37.2, 37.2, 37.5, 38, 42, 162, 1340, 0, 0, 0]], downESRatio: NEW[ParmArray ← [0, .018, .086, .74, 7.3, 73, -1, 0, 0, 0]], downREff: NEW[ParmArray ← [9720, 9720, 10100, 14000, 36300, 164000, 0, 0, 0, 0]], downOutES: NEW[ParmArray ← [4.0, 4.0, 4.0, 4.3, 10.2, 41.0, 0, 0, 0, 0]]]]; TypeTable[fetNEnhP] ← NEW[TTypeRec ← [name: "nenhp", strengthHi: 2, strengthLo: 3, cPerArea: .0004, cPerWidth: .00025, rUp: 200000, rDown: 200000, on0: FALSE, on1: TRUE, onAlways: FALSE, upESRatio: NEW[ParmArray ← [0, 1000, -1, 0, 0, 0, 0, 0, 0, 0]], upREff: NEW[ParmArray ← [200000, 200000, 0, 0, 0, 0, 0, 0, 0, 0]], upOutES: NEW[ParmArray ← [100, 100, 0, 0, 0, 0, 0, 0, 0, 0]], downESRatio: NEW[ParmArray ← [0, 1000, -1, 0, 0, 0, 0, 0, 0, 0]], downREff: NEW[ParmArray ← [200000, 200000, 0, 0, 0, 0, 0, 0, 0, 0]], downOutES: NEW[ParmArray ← [100, 100, 0, 0, 0, 0, 0, 0, 0, 0]]]]; TypeTable[fetNDep] ← NEW[TTypeRec ← [name: "NDTran", strengthHi: 2, strengthLo: 3, cPerArea: .0004, cPerWidth: .00025, rUp: 50000, rDown: 23000, on0: FALSE, on1: FALSE, onAlways: TRUE, upESRatio: NEW[ParmArray ← [0, 1000, -1, 0, 0, 0, 0, 0, 0, 0]], upREff: NEW[ParmArray ← [50000, 50000, 0, 0, 0, 0, 0, 0, 0, 0]], upOutES: NEW[ParmArray ← [75, 75, 0, 0, 0, 0, 0, 0, 0, 0]], downESRatio: NEW[ParmArray ← [0, 1000, -1, 0, 0, 0, 0, 0, 0, 0]], downREff: NEW[ParmArray ← [23000, 23000, 0, 0, 0, 0, 0, 0, 0, 0]], downOutES: NEW[ParmArray ← [9.5, 9.5, 0, 0, 0, 0, 0, 0, 0, 0]]]]; TypeTable[fetNLoad] ← NEW[TTypeRec ← [name: "nload", strengthHi: 2, strengthLo: 0, cPerArea: 0, cPerWidth: .00025, rUp: 21900, rDown: 0, on0: FALSE, on1: FALSE, onAlways: TRUE, upESRatio: NEW[ParmArray ← [0, .406, 1.344, 4.027, 13.4, 40.3, -1, 0, 0, 0]], upREff: NEW[ParmArray ← [21900, 21000, 23800, 31400, 43300, 57800, 0, 0, 0, 0]], upOutES: NEW[ParmArray ← [13.3, 13.3, 13.0, 21, 44.7, 107, 0, 0, 0, 0]], downESRatio: NEW[ParmArray ← [0, 1000, -1, 0, 0, 0, 0, 0, 0, 0]], downREff: NEW[ParmArray ← [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], downOutES: NEW[ParmArray ← [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]]]; TypeTable[fetNSuper] ← NEW[TTypeRec ← [name: "nsuper", strengthHi: 2, strengthLo: 0, cPerArea: .0004, cPerWidth: .00025, rUp: 4800, rDown: 5100, on0: FALSE, on1: FALSE, onAlways: TRUE, upESRatio: NEW[ParmArray ← [0, .214, .464, 1.139, 3.58, 10.96, 36.2, 108, -1, 0]], upREff: NEW[ParmArray ← [4800, 4700, 5400, 6800, 8500, 8200, 1900, -17600, 0, 0]], upOutES: NEW[ParmArray ← [3.5, 3.5, 4.2, 6.7, 15.8, 42.9, 140, 413, 0, 0]], downESRatio: NEW[ParmArray ← [0, 1000, -1, 0, 0, 0, 0, 0, 0, 0]], downREff: NEW[ParmArray ← [5100, 5100, 0, 0, 0, 0, 0, 0, 0, 0]], downOutES: NEW[ParmArray ← [2.5, 2.5, 0, 0, 0, 0, 0, 0, 0, 0]]]]; TypeTable[fetNChan] ← NEW[TTypeRec ← [name: "nchan", strengthHi: 2, strengthLo: 3, cPerArea: .0004, cPerWidth: .00025, rUp: 24800, rDown: 9720, on0: FALSE, on1: TRUE, onAlways: FALSE, upESRatio: NEW[ParmArray ← [0, .0014, .0026, .017, .16, 1.6, 16, -1, 0, 0]], upREff: NEW[ParmArray ← [24800, 24800, 24600, 25800, 38400, 153000, 1070000, 0, 0, 0]], upOutES: NEW[ParmArray ← [37.2, 37.2, 37.5, 38, 42, 162, 1340, 0, 0, 0]], downESRatio: NEW[ParmArray ← [0, .018, .086, .74, 7.3, 73, -1, 0, 0, 0]], downREff: NEW[ParmArray ← [9720, 9720, 10100, 14000, 36300, 164000, 0, 0, 0, 0]], downOutES: NEW[ParmArray ← [4.0, 4.0, 4.0, 4.3, 10.2, 41.0, 0, 0, 0, 0]]]]; TypeTable[fetPChan] ← NEW[TTypeRec ← [name: "PETran", strengthHi: 2, strengthLo: 2, cPerArea: .0004, cPerWidth: .00025, rUp: 19050, rDown: 200000, on0: TRUE, on1: FALSE, onAlways: FALSE, upESRatio: NEW[ParmArray ← [0, .017, .028, .118, 1.05, 10.3, 31, 103, -1, 0]], upREff: NEW[ParmArray ← [19050, 19050, 19100, 20100, 27900, 42400, 23400, -114000, 0, 0]], upOutES: NEW[ParmArray ← [8.9, 8.9, 8.9, 8.9, 10.0, 25, 46.3, 97, 0, 0]], downESRatio: NEW[ParmArray ← [0, 1000, -1, 0, 0, 0, 0, 0, 0, 0]], downREff: NEW[ParmArray ← [200000, 200000, 0, 0, 0, 0, 0, 0, 0, 0]], downOutES: NEW[ParmArray ← [100, 100, 0, 0, 0, 0, 0, 0, 0, 0]]]]; rUpFactor↑ ← - RealFns.Ln[(vdd↑ - vinv↑)/vdd↑]; rDownFactor↑ ← - RealFns.Ln[vinv↑/vdd↑]; END.