-- Sim-estep.mesa
-- last edited by Suzuki:  September 8, 1981  9:38 AM 

DIRECTORY
	InlineDefs,
	JaMFnsDefs USING [GetJaMBreak, SetJaMBreak],
	MOSSIMFns USING [UnderJaM],
	SimStep,
	SimTsim,
	stdio,
	WF;

SimEstep: PROGRAM 
	IMPORTS InlineDefs, JaMFnsDefs, MOSSIMFns, SimTsim, stdio, WF
	EXPORTS MOSSIMFns, SimStep = { OPEN InlineDefs, SimTsim, stdio, WF;

--#include "tsim.h"

numsteps: CARDINAL ← 0;
warning: BOOLEAN;
warningDisplay: BOOLEAN ← FALSE;

incdbg: CARDINAL ← 1;		-- define this symbol IF you want debugging info 

-- event driven simulation step for tsim  Chris Terman 2/80 
-- added timing stuff - cjt - 4/80 

-- transTbl[gate][source][drain] RETURNs new value for source node.  Use
-- gateTbl to map node potential to one of the three allowable gate
-- 'potentials'.  S'posed to tells what effect transistor with the given
-- initial potentials has on source node.  Since transistor is symmetrical,
-- just reverse second and third subscripts to get affect on drain node.
 
-- extern char transTbl[3][NPOTS][NPOTS];

transTblRESIST: POINTER TO ARRAY Potential OF ARRAY Potential OF Potential ← @transTbl[RESIST];

-- extern nptr hinputs[],linputs[],xinputs[];
-- extern int nhinputs,nlinputs,nxinputs;
-- extern int debug;

-- extern char *potential[];

-- initial accumulator value indexed by previous node value 
initTbl: ARRAY Potential OF Potential = [ CX, CX, CHIGH, CLOW, CX, CHIGH, CLOW, CHIGH, CX, CX, CX ];

-- translation of potential to effect as a gate 
gateTbl: ARRAY Potential OF GateState = [
  RESIST, RESIST, ON, OFF, RESIST, ON, OFF, ON, RESIST, RESIST, RESIST
];

-- table for translating potentials into function values 
funTbl: ARRAY Potential OF FunctionValue = [ NotFX, NotFX, NotFH, NotFL, NotFX, NotFH, NotFL, NotFH, NotFX, NotFX, NotFX ];
ansTbl: ARRAY FunctionValue OF Potential = [ DLOW, DXLOW, DXLOW, P ];

elist: nptr;	-- event list 
elast: nptr;	-- pointer to last node on event list 
clist: nptr;	-- list of nodes connected to node being processed 

nevent: INTEGER;	-- number of current event 

-- add a node to event list 
--#ifdef incdbg
enque: PROC[m: nptr] = {
	IF NOT queued[m] AND NOT (m).input THEN {
			  IF (debug=1 AND m.watched) THEN 
			    FWF3[stdout,"enquing %s [event %d: %s]*n",
				    pnode[m],nevent,pnode[elist]];
			  IF (elast#NIL) THEN { elast.elink ← (m); elast ← (m); }
			  ELSE { elist ← elast ← (m); }; };
	};

xenque: PROC[m: nptr] = {
	IF (NOT queued[m]) THEN {
			  IF (debug=1 AND m.watched) THEN
			    FWF1[stdout,"xenquing %s*n",pnode[m]];
			  m.xqueued ← TRUE;
			  IF (elast#NIL) THEN { elast.elink ← (m); elast ← (m); }
			  ELSE { elist ← elast ← (m); }; };
	};

--#endif

--#ifndef incdbg
--#define enque(m)	IF (NOT queued(m) AND NOT ((m).nflags&INPUT)) THEN {\
--			  IF (elast) THEN { elast.elink ← (m); elast ← (m); };\
--			  ELSE { elist ← elast ← (m); }; };
--
--#define xenque(m)	IF (NOT queued(m)) {\
--			  m.nflags ← BITOR[m.nflags, XQUEUED];\
--			  IF (elast) THEN { elast.elink ← (m); elast ← (m); }\
--			  ELSE { elist ← elast ← (m); }; };
--#endif

queued: PROC[m: nptr] RETURNS[BOOLEAN] = INLINE {
	RETURN[((m).elink#NIL OR elast=(m))]
	};

-- do a simulation step after initializing queue from changed input nodes 
step: PUBLIC PROC RETURNS[CARDINAL] = 
  {	eptr: nptr;	-- current node from event list 
	ac: INTEGER;
	newpot: Potential;
	v: CARDINAL;

	-- initialize event list with all input nodes whose input values
	-- differ from their current value.
	 
	numsteps ← numsteps + 1;
	warning ← FALSE;

	elist ← elast ← NIL;

	FOR ac IN [0..nxinputs) DO
	  eptr ← xinputs[ac]; newpot ← initTbl[eptr.npot]; 
	  OutTrace[eptr, newpot];
	  xenque[eptr];
	ENDLOOP;
	nxinputs ← 0;

	FOR ac IN [0..nhinputs) DO
	  eptr ← hinputs[ac];
	  IF (eptr.npot # DHIGH) THEN {
	    OutTrace[eptr, DHIGH];
	    eptr.fpot ← NotFH;
	    xenque[eptr];
	  };
	ENDLOOP;

	FOR ac IN [0..nlinputs) DO
	  eptr ← linputs[ac];
	  IF (eptr.npot # DLOW) THEN {
	    OutTrace[eptr, DLOW];
	    eptr.fpot ← NotFL;
	    xenque[eptr];
	  };
	ENDLOOP;

	v ← simStep[];
	IF warningDisplay AND warning THEN WF0[";*n"];
	RETURN[v];
};

-- IF node is a "storage node", i.e. is not pulled up or an input, initialize
-- its value to CLOW and xenque it (pretend it's an input).
 
esetup: PROC[n: nptr] RETURNS [CARDINAL] =
  {	IF n.npot=CX AND NOT n.pullup AND NOT n.input THEN {
	  OutTrace[n, CLOW];
	  n.fpot ← NotFL;
	  xenque[n];
	};
	RETURN[0];
};

-- trial network initialization routine 
einit: PUBLIC PROC RETURNS[INTEGER] = {
	v: CARDINAL;
	elist ← elast ← NIL;	-- initialize event list 
	warning ← FALSE;
	numsteps ← numsteps + 1;
	[] ← walkNet[esetup];	-- initialize and enque all storage nodes 
	v ← simStep[];
	IF warningDisplay AND warning THEN WF0[";*n"];
	RETURN[v];
};

-- actual simulation step, assumes event list has some events on it! 
simStep: PROC RETURNS[CARDINAL] =
  {	ac: Potential;	-- potential accumulator 
	eptr: nptr;	-- current node from event list 
	t: tptr;
	n: nptr;

	IF traceNumber > 0 THEN FWF0[log, "Simulation step:*n"];

	nevent ← 0;

	-- start thru event list, doing calculation for each node in turn 
	WHILE (eptr ← elist)#NIL DO

	  IF MOSSIMFns.UnderJaM AND JaMFnsDefs.GetJaMBreak[] THEN {
	    JaMFnsDefs.SetJaMBreak[FALSE]; 
	    RETURN[nevent]};
	  nevent←nevent+1; -- this is the only place where nevent is increased
--#ifdef incdbg
	  IF (debug=1 AND (nevent>10000 OR eptr.watched)) THEN {
	    FWF1[stdout,"event %d: ",nevent];
	    pvalue[eptr];
	    FWF0[stdout,"*n"];
	  };
--#endif

	-- calculate new value and construct connection list for current node 
	  clist ← LOOPHOLE[LONG[-1], nptr];
	  ac ← calcVal[eptr,INIT];

	-- IF current node is an input, force its value 
	  IF eptr.input THEN ac ← eptr.npot;

--#ifdef incdbg
	  IF (debug=1 AND eptr.watched) THEN {
	    FWF0[stdout,"clist: "];
	    FOR eptr←clist, eptr.nlink WHILE eptr#LOOPHOLE[LONG[-1], nptr] DO
	      FWF1[stdout,"%s ",pnode[eptr]];
	    ENDLOOP;
	    FWF1[stdout,"*nnew value ← %s*n",potential[ac]];
	  };
--#endif

	-- IF new value indicates charge sharing, a second calculation is needed
	-- which takes into account the relative capacitances of the involved nodes.
	 
	  IF (ac = CSHARE) THEN {
	    cap: REAL;
	    hac: REAL ← 0.0;		-- charged high accumulator 
	    lac: REAL ← 0.0;		-- charged low accumulator 
	    xac: REAL ← 0.0;		-- other accumulator 

	    FOR eptr ← clist, eptr.nlink WHILE eptr#LOOPHOLE[LONG[-1], nptr] DO 
	      IF ((cap ← eptr.ncap) = 0.0) THEN { ac ← CX; GOTO sDone; };
	      IF ((ac ← eptr.npot) = CHIGH) THEN hac ← hac + cap
	      ELSE IF (ac = CLOW) THEN lac ← lac + cap
	      ELSE xac ← xac + cap;
	    ENDLOOP;

	-- for charge sharing to work, ratio must be at least 3:1 in favor of
	-- majority carrier.
	 
	    IF (lac + xac = 0.0 OR hac/(lac + xac) >= 3.0) THEN ac ← CHIGH
	    ELSE IF (hac+xac = 0.0 OR lac/(hac+xac) >= 3.0) THEN ac ← CLOW
	    ELSE ac ← CX;
	    GOTO sDone;

	  EXITS
		sDone =>
--#ifdef incdbg
	    IF (debug=1 AND elist.watched) THEN
	      FWF1[stdout,"charge-shared value ← %s*n",potential[ac]];
--#endif
	  };


	-- save away new value, enquing nodes affected by change (if any) 

	  eptr ← clist; 
	  WHILE eptr#LOOPHOLE[LONG[-1], nptr] DO {  -- We need block to enable goto's: N.S.

	    IF (elist#eptr AND eptr.input) THEN GOTO nextvalue;

	    FOR t ← eptr.nsource, t.slink WHILE t#NIL DO
	      IF (gateTbl[t.gate.npot] = RESIST) THEN
		IF (transTblRESIST[t.drain.npot][ac] # t.drain.npot) THEN
		  enque[t.drain];
	    ENDLOOP;

	    FOR t ← eptr.ndrain, t.dlink WHILE t#NIL DO
	      IF (gateTbl[t.gate.npot] = RESIST) THEN
		IF (transTblRESIST[t.source.npot][ac] # t.source.npot) THEN
		  enque[t.source];
	    ENDLOOP;

	    IF (gateTbl[ac]#gateTbl[eptr.npot] OR eptr.xqueued) THEN {
	      IF (gateTbl[ac] = ON) THEN
		FOR t ← eptr.ngate, t.glink WHILE t#NIL DO 
		  IF NOT t.drain.input THEN { enque[t.drain]; }
		  ELSE enque[t.source];
		ENDLOOP
	      ELSE
		FOR t ← eptr.ngate, t.glink WHILE t#NIL DO 
		  chain[t.drain];
		  chain[t.source];
		ENDLOOP;
	      FOR t ← eptr.nfuns, t.vlink WHILE t#NIL DO 
			enque[t.output] 
	      ENDLOOP;
	    };

	    OutTrace[eptr, ac];
	    eptr.fpot ← funTbl[ac];
	    eptr.xqueued ← FALSE;
	    IF (queued[eptr] AND eptr#elist) THEN {
	      FOR n ← elist, n.elink WHILE n.elink#NIL DO
	        IF (n.elink = eptr) THEN {
		  IF ((n.elink ← eptr.elink) = NIL) THEN elast ← n ELSE eptr.elink ← NIL;
		  EXIT;
	        };
	      ENDLOOP;
	    };
	    GOTO nextvalue;

	  EXITS
  nextvalue => {
	    n ← eptr.nlink;
	    eptr.nlink ← NIL;
	    eptr ← n};
	  }
	  ENDLOOP;

	-- move on to next event on event list 
	  eptr ← elist;
	  elist ← eptr.elink;
	  eptr.elink ← NIL;
	ENDLOOP;

	RETURN[nevent];
};

-- calculate function value 
calcFun: PROC[n: nptr] RETURNS[Potential] =
  {	p: LONG POINTER TO POINTER TO ARRAY OF CHARACTER;
	q: LONG POINTER TO ARRAY OF CHARACTER;
	temp,ac: FunctionValue;

	-- calculate function value, term by term 
	ac ← FH;
	p ← LOOPHOLE[n.ndef, LONG POINTER TO POINTER TO ARRAY OF CHARACTER];
	WHILE p↑#NIL DO {
	  temp ← FH;
	  WHILE (q ← p↑)#NIL DO p←p+1; temp ← BITAND[temp, BITNOT[q[0]]] ENDLOOP;
	  IF ((ac ← BITAND[ac, BITNOT[temp]]) = FL) THEN EXIT;
	};
	ENDLOOP;
	RETURN[ansTbl[ac]];
};

-- add current node and all connected to it by 'on' transistors to clist then
-- merge current value of node into accumulator, RETURNing new answer
 
calcVal: PROC[n: nptr, ac: Potential] RETURNS [Potential] =
  {	t: tptr;
	pot: Potential;
	p: LONG POINTER TO POINTER TO ARRAY OF CHARACTER;
	q: LONG POINTER TO ARRAY OF CHARACTER;
	fv, temp: FunctionValue;
	gate: GateState;

	n.nlink ← clist;
	clist ← n;

	IF n.input THEN pot ← n.npot
	ELSE IF (n.ndef#NIL) THEN {
	-- calculate function value, term by term 
	    fv ← FH;
	    p ← LOOPHOLE[n.ndef, LONG POINTER TO POINTER TO ARRAY OF CHARACTER];
	    WHILE (p↑#NIL) DO {
	      temp ← FH;
	      WHILE (q ← p↑)#NIL DO p←p+1; temp ← BITAND[temp, BITNOT[q[0]]] ENDLOOP;
	      IF ((fv ← BITAND[fv, BITNOT[temp]]) = FL) THEN EXIT;
	    };
	    ENDLOOP;
	    pot ← ansTbl[fv];
	  } ELSE pot ← (IF n.pullup THEN P ELSE initTbl[n.npot]);

	ac ← transTbl[ON][ac][pot];

	IF (elist#n AND n.input) THEN RETURN[ac];

	FOR t ← n.nsource, t.slink WHILE t#NIL DO 
	  IF ((gate ← gateTbl[t.gate.npot])=ON AND t.drain.nlink=NIL) THEN
	    ac ← calcVal[t.drain,ac]
	  ELSE IF (gate = RESIST) THEN ac ← transTblRESIST[ac][t.drain.npot];
	ENDLOOP;
	FOR t ← n.ndrain, t.dlink WHILE t#NIL DO 
	  IF ((gate ← gateTbl[t.gate.npot])=ON AND t.source.nlink=NIL) THEN
	    ac ← calcVal[t.source,ac]
	  ELSE IF (gate = RESIST) THEN ac ← transTblRESIST[ac][t.source.npot];
	ENDLOOP;

	RETURN[ac];
};

ToggleWarning: PUBLIC PROC = {
	warningDisplay ← NOT warningDisplay};

OutTrace: PROC[n: nptr, pot: Potential] = {
	IF n.npot = pot THEN RETURN;
	IF pchars[n.npot] = pchars[pot] THEN 
	  {n.npot ← pot; RETURN};
	n.changecount ← n.changecount+1;
	IF warningDisplay AND pchars[pot]#'X THEN {
	  IF n.stepcount=numsteps THEN {
	    IF n.lastpot#pot AND NOT n.warned THEN {
	      IF warning THEN WF1[",%s", pnode[n]]
	      ELSE {
	        warning ← TRUE;
	        WF1["Warning: the following nodes changed more than once: %s", pnode[n]];
	        n.lastpot ← pot;
	        n.warned ← TRUE}}}
	  ELSE {n.stepcount ← numsteps; n.lastpot ← pot; n.warned ← FALSE}};
	IF NOT n.traced THEN {n.npot ← pot; RETURN};
	FWF3[log, "Potential changed.  Node = %s.*nThe old value is %c. The new value is %c*n*n", pnode[n], pchars[n.npot], pchars[pot]];
	n.npot ← pot;
};

chain: PROC[n:nptr] =
  {	t: tptr;
	temp: nptr;
	newpot: Potential;

	IF (n.input) THEN RETURN;
	newpot ← initTbl[n.npot];
	OutTrace[n, newpot];

	enque[n];

	FOR t ← n.nsource, t.slink WHILE t#NIL DO
	  IF (gateTbl[t.gate.npot]#OFF AND NOT queued[temp ← t.drain]) THEN chain[temp];
	ENDLOOP;
	FOR t ← n.ndrain, t.dlink WHILE t#NIL DO
	  IF (gateTbl[t.gate.npot]#OFF AND NOT queued[temp ← t.source]) THEN chain[temp];
	ENDLOOP;
};
}.