/* Rosemary.c */
	
#include "CoreBasics.h"
#include "Core.h"
#include "CoreFlat.h"
#include "Rosemary.h"

#define roseFixedWirePropName "roseFixedWire"
	
String RoseLevelNames[RoseLevelCount]={"L", "H", "X"};
	
String RoseDriveNames[RoseDriveCount]={"inspect", "expect", "n", "cw", "cmw", "c", "cms", "cs", "f", "dw", "dmw", "d", "dms", "ds", "i"};
	
/* Instantiation and Relaxation */
	
	Atom roseFixedWireProp;
	
	void RoseFixedWirePrint(property, value, indent, level, cr)
		Atom property;
		Ref value;
		Nat indent;
		Nat level;
		Bool cr;
		{
		RoseLevel *lev=(RoseLevel *)value;
		StringPrintIndent(indent, cr);
		printf("%s: %s", roseFixedWirePropName, RoseLevelNames[(Nat)(*lev)]);
		};
		
	Wire RoseSetFixedWire(wire, level)
		Wire wire;
		RoseLevel level;
		{
		RoseLevel *levelRef=New(RoseLevel *, RoseLevel);
		*levelRef=level;
		PutWireProp(wire, roseFixedWireProp, levelRef);
		return(wire);
		};
		
	Wire RoseSetNamedFixedWire(cellType, name, level)
		CellType cellType;
		String name;
		RoseLevel level;
		{
		return(RoseSetFixedWire(FindWire(cellType->public, name), level));
		};
		
	RoseWire LookUpRoseWire(coreWire, bindings, coreToRoseWires)
		Wire coreWire;
		HashTable bindings;
		HashTable coreToRoseWires;
		{
		FlatWire leftUpper=(FlatWire)HashTableGet(bindings, coreWire);
		return((RoseWire)HashTableGet(coreToRoseWires, leftUpper));
		};
		
	typedef struct AllocateWireContextRec {
		HashTable bindings;
		FlatCellTypeRec flatCell;
		HashTable coreToRoseWires;
		} AllocateWireContextRec;
		
	typedef AllocateWireContextRec *AllocateWireContext;
		
	void AllocateWire(coreWire, context)
		Wire coreWire;
		Ref context;
		{
		AllocateWireContext c=(AllocateWireContext)context;
		FlatWire flatWire=nil;
		if (c->bindings!=nil) flatWire=(FlatWire)HashTableGet(c->bindings, coreWire);
		if (flatWire==nil) {
			FlatWireRec wireKey;
			wireKey.flatCell=c->flatCell;
			wireKey.wire=coreWire;
			if (HashTableGet(c->coreToRoseWires, &wireKey)==nil) {
				RoseLevel *wireFixedRef=(RoseLevel *)GetWireProp(coreWire, roseFixedWireProp);
				RoseWire roseWire=New(RoseWire, RoseWireRec);
				roseWire->nextPerturbedWire=nil;
				roseWire->previousPerturbedWire=nil;
				roseWire->nextRecomputed=nil;
				roseWire->nextVicinityWire=nil;
				roseWire->probes=nil;
				roseWire->channels=nil;
				roseWire->notOffChannels=nil;
				roseWire->gates=nil;
				roseWire->wireSize=RoseCharge;
				roseWire->wireLevel=X;
				roseWire->mark=false;
				roseWire->flatWire=New(FlatWire, FlatWireRec);
				*(roseWire->flatWire)=wireKey;
				if (wireFixedRef!=nil) {
					roseWire->switchDrive=roseWire->wireSize=RoseInfinite;
					roseWire->wireLevel=(*wireFixedRef);
					};
				HashTablePut(c->coreToRoseWires, roseWire->flatWire, roseWire);
				};
			};
		};
		
	void FlattenCell(cell, target, flatCell, instance, index, parent, flatParent, data, bindings)
		CellType cell;
		FlatCellTypeRec target;
		FlatCellTypeRec flatCell;
		CellInstance instance;
		Nat index;
		CellType parent;
		FlatCellTypeRec flatParent;
		Ref data;
		HashTable bindings;
		{
		RoseSimulation simulation=(RoseSimulation)data;
		if (cell->class==transistorCellClass) {
			HashTable coreToRoseWires=simulation->coreToRoseWires;
			Wire public=cell->public;
			Transistor coreTransistor=(Transistor)cell->data;
			RoseTransistor roseTransistor=New(RoseTransistor, RoseTransistorRec);
			roseTransistor->gate=LookUpRoseWire(public->elements[(Nat)gate], bindings, coreToRoseWires);
			roseTransistor->gate->gates=ListCons(roseTransistor->gate->gates, roseTransistor);
			roseTransistor->ch1=LookUpRoseWire(public->elements[(Nat)ch1], bindings, coreToRoseWires);
			roseTransistor->ch1->channels=ListCons(roseTransistor->ch1->channels, roseTransistor);
			roseTransistor->ch1->notOffChannels=ListCons(roseTransistor->ch1->notOffChannels, roseTransistor);
			roseTransistor->ch2=LookUpRoseWire(public->elements[(Nat)ch2], bindings, coreToRoseWires);
			roseTransistor->ch2->channels=ListCons(roseTransistor->ch2->channels, roseTransistor);
			roseTransistor->ch2->notOffChannels=ListCons(roseTransistor->ch2->notOffChannels, roseTransistor);
			roseTransistor->transistorType=coreTransistor->type;
			roseTransistor->conductivity=RoseDrive;
			roseTransistor->flatCellType=flatCell;
			}
		else if (cell->class==recordCellClass) {
			AllocateWireContextRec context;
			RecordCellType cellTypeRCT=(RecordCellType)cell->data;
			context.bindings=bindings;
			context.flatCell=flatCell;
			context.coreToRoseWires=simulation->coreToRoseWires;
			VisitRootAtomics(cellTypeRCT->internal, AllocateWire, &context);
			NextBoundCellType(cell, target, flatCell, instance, index, parent, flatParent, data, bindings, FlattenCell);
			}
		else NextBoundCellType(cell, target, flatCell, instance, index, parent, flatParent, data, bindings, FlattenCell);
		};
		
	Nat CountVicinity (wire, currentCount)
		RoseWire wire;
		Nat currentCount;
		{
		Nat newCount=currentCount + 1;
		List rts=wire->channels;
		wire->mark=true;
		while (rts) {
			RoseTransistor tran=(RoseTransistor)rts->first;
			RoseWire otherWire=tran->ch1;
			if (otherWire==wire) otherWire=tran->ch2;
			if (otherWire->wireSize!=RoseInfinite && !otherWire->mark) newCount=CountVicinity(otherWire, newCount);
			rts=rts->rest;
			};
		return(newCount);
		};
		
	Ref ComputeMaxVicinity(key, value, context)
		Ref key;
		Ref value;
		Ref context;
		{
		RoseWire roseWire=(RoseWire)value;
		Nat *maxVicinity=(Nat *)context;
		if (roseWire->wireSize!=RoseInfinite && !roseWire->mark) *maxVicinity=Max(*maxVicinity, CountVicinity(roseWire, 0));
		return(value);
		};
		
	typedef enum Conductance {on, off, unknown} Conductance;
		
	Conductance TranOn (tran, gateValue)
		RoseTransistor tran;
		RoseLevel gateValue;
		{
		Conductance cond;
		switch (tran->transistorType) {
			case nE : switch (gateValue) {
				case L : cond=off; break;
				case H : cond=on; break;
				case X : cond=unknown; break;
				};
				break;
			case pE : switch (gateValue) {
				case L : cond=on; break;
				case H : cond=off; break;
				case X : cond=unknown; break;
				};
				break;
			case nD : cond=on; break;
			};
		return(cond);
		};
		
	void Perturb(simulation, wire)
		RoseSimulation simulation;
		RoseWire wire;
		{
		if (wire->nextPerturbedWire==nil && wire->previousPerturbedWire==nil && simulation->perturbed!=wire && (wire->wireSize)<RoseInfinite) {
			RoseWire currentHead=simulation->perturbed;
			wire->nextPerturbedWire=currentHead;
			if (currentHead!=nil) currentHead->previousPerturbedWire=wire;
			simulation->perturbed=wire;
			};
		};
		
	Ref EstablishWireInvariants(key, value, context)
		Ref key;
		Ref value;
		Ref context;
		{
		RoseWire roseWire=(RoseWire)value;
		RoseSimulation simulation=(RoseSimulation)context;
		List rts=roseWire->channels;
		roseWire->mark=false;
		Perturb(simulation, roseWire);
		roseWire->notOffChannels=nil;
		while (rts) {
			RoseTransistor roseTransistor=(RoseTransistor)rts->first;
			if (TranOn(roseTransistor, roseTransistor->gate->wireLevel)!=off) roseWire->notOffChannels=ListCons(roseWire->notOffChannels, roseTransistor);
			rts=rts->rest;
			};
		return(value);
		};
		
	void EstablishInvariants(simulation)
		RoseSimulation simulation;
		{
		HashTableUpdate(simulation->coreToRoseWires, EstablishWireInvariants, simulation);
		};
		
	RoseSimulation RoseInstantiate(cellType)
		CellType cellType;
		{
		RoseSimulation simulation=New(RoseSimulation, RoseSimulationRec);
		Nat maxVicinity=0;
		RoseStrength strength;
		simulation->cellType=cellType;
		simulation->coreToRoseWires=HashTableCreate(1019, FlatWireHash, FlatWireEqual);
		FlattenCell(cellType, allFlatCells, rootCellType, nil, NatLast, nil, rootCellType, simulation, nil);
		HashTableUpdate(simulation->coreToRoseWires, ComputeMaxVicinity, &maxVicinity);
		for (strength=RoseInspect; strength<=RoseInfinite; strength++) {
			simulation->vicinityByStrength[strength].wires=NewSeq(RoseWires, RoseWiresRec, RoseWire, maxVicinity);
			simulation->vicinityByStrength[strength].wires->size=maxVicinity;
			simulation->vicinityByStrength[strength].firstFree=0;
			};
		EstablishInvariants(simulation);
		return(simulation);
		};
		
	void PushWireByStrength(simulation, wire, thisDrive)
		RoseSimulation simulation;
		RoseWire wire;
		RoseStrength thisDrive;
		{
		simulation->vicinityByStrength[thisDrive].wires->elements[ simulation->vicinityByStrength[thisDrive].firstFree]=wire;
		simulation->vicinityByStrength[thisDrive].firstFree=simulation->vicinityByStrength[thisDrive].firstFree+1;
		};
		
	RoseStrength UpDownStrength(wire, notLevel)
		RoseWire wire;
		RoseLevel notLevel;
		{
		RoseStrength strength;
		if (wire->wireLevel==notLevel) strength=RoseNone;
		else strength=wire->wireSize;
		return(strength);
		};
		
	typedef enum PushWhat {upDown, up, down} PushWhat;
		
	void FindVicinity(simulation, wire, what)
		RoseSimulation simulation;
		RoseWire wire;
		PushWhat what;
		{
		RoseStrength wireSize;
		RoseTransistors rts=wire->notOffChannels;
		RoseProbePrimitives rps=wire->probes;
		switch (what) {
			case upDown : wireSize=wire->wireSize; break;
			case up : wireSize=UpDownStrength(wire, L); break;
			case down : wireSize=UpDownStrength(wire, H); break;
			};
		wire->mark=true;
		while (rts) {
			RoseTransistor tran=(RoseTransistor)rts->first;
			Conductance tranCond=TranOn(tran, tran->gate->wireLevel);
			RoseWire otherWire=tran->ch1;
			if (otherWire==wire) otherWire=tran->ch2;
			if (otherWire->wireSize==RoseInfinite) {
				if (((what==upDown && tranCond==on) || (what==up && otherWire->wireLevel!=L) || (what==down && otherWire->wireLevel!=H)) && wireSize < tran->conductivity) wireSize=tran->conductivity;
				}
			else if (!otherWire->mark) FindVicinity(simulation, otherWire, what);
			rts=rts->rest;
			};
		while (rps) {
			RoseProbePrimitive probe=(RoseProbePrimitive)rps->first;
			if (((what==upDown && probe->drive>RoseNone) || (what==up && probe->level!=L) || (what==down && probe->level!=H)) && wireSize < probe->drive) wireSize=probe->drive;
			rps=rps->rest;
			};
		if (what!=upDown && wireSize < wire->switchDrive) wireSize=RoseNone;
		PushWireByStrength(simulation, wire, wireSize);
		switch (what) {
			case upDown : wire->switchDrive=wireSize; break;
			case up : wire->upDrive=wireSize; break;
			case down : wire->downDrive=wireSize; break;
			};
		};
		
	void PushStrength(simulation, what)
		RoseSimulation simulation;
		PushWhat what;
		{
		RoseStrength thisDrive;
		for (thisDrive=RoseInfinite-1; ; thisDrive--) {
			Nat firstFreeWire;
			while (firstFreeWire=simulation->vicinityByStrength[thisDrive].firstFree) {
				RoseWire wire=simulation->vicinityByStrength[thisDrive].wires->elements[firstFreeWire-1];
				RoseStrength whatDrive;
				RoseTransistors rts=wire->notOffChannels;
				wire->mark=false;
				simulation->vicinityByStrength[thisDrive].firstFree=firstFreeWire-1;
				switch (what) {
					case upDown : whatDrive=wire->switchDrive; break;
					case up : whatDrive=wire->upDrive; break;
					case down : whatDrive=wire->downDrive; break;
					}
				if (thisDrive==RoseNone || thisDrive < whatDrive) continue;
				while (rts) {
					RoseTransistor tran=(RoseTransistor)rts->first;
					Conductance tranCond=TranOn(tran, tran->gate->wireLevel);
					RoseStrength driveThrough=Min(thisDrive, tran->conductivity);
					RoseWire otherWire=tran->ch1;
					if (otherWire==wire) otherWire=tran->ch2;
					switch (what) {
						case upDown : if (tranCond==on && driveThrough > otherWire->switchDrive) {
							otherWire->switchDrive=driveThrough;
							PushWireByStrength(simulation, otherWire, driveThrough);
							};
							break;
						case up : if (driveThrough >= otherWire->switchDrive && driveThrough > otherWire->upDrive) {
							otherWire->upDrive=driveThrough;
							PushWireByStrength(simulation, otherWire, driveThrough);
							};
							break;
						case down : if (driveThrough >= otherWire->switchDrive && driveThrough > otherWire->downDrive) {
							otherWire->downDrive=driveThrough;
							PushWireByStrength(simulation, otherWire, driveThrough);
							};
							break;
						};
					rts=rts->rest;
					};
				};
			if (thisDrive==RoseNone) break;
			};
		};
		
	void CheckAddOrRemove(channel, tran, wasNotOff, isNotOff)
		RoseWire channel;
		RoseTransistor tran;
		Bool wasNotOff;
		Bool isNotOff;
		{
		if (channel->channels!=nil) {
			if (wasNotOff && !isNotOff)  {
				RoseTransistors trail=nil;
				RoseTransistors rts=channel->notOffChannels;
				while (rts) {
					if ((RoseTransistor)rts->first==tran) {
						if (trail==nil) channel->notOffChannels=rts->rest;
						else trail->rest=rts->rest;
						break;
						};
					trail=rts;
					rts=rts->rest;
					};
				}
			else if (!wasNotOff && isNotOff) channel->notOffChannels=ListCons(channel->notOffChannels, tran);
			};
		};
		
	void UpdateRecomputed(simulation, recomputed)
		RoseSimulation simulation;
		RoseWire recomputed;
		{
		RoseWire thisWire=recomputed;
		while (thisWire) {
			RoseLevel wireLevel;
			if (thisWire->upDrive==RoseNone && thisWire->downDrive>=RoseForce) wireLevel=L;
			else if (thisWire->downDrive==RoseNone && thisWire->upDrive>=RoseForce) wireLevel=H;
			else wireLevel=X;
			if (thisWire->wireLevel!=wireLevel) {
				RoseTransistors rts=thisWire->gates;
				while (rts) {
					RoseTransistor tran=(RoseTransistor)rts->first;
					Bool wasNotOff=TranOn(tran, thisWire->wireLevel)!=off;
					Bool isNotOff=TranOn(tran, wireLevel)!=off;
					Perturb(simulation, tran->ch1);
					Perturb(simulation, tran->ch2);
					CheckAddOrRemove(tran->ch1, tran, wasNotOff, isNotOff);
					CheckAddOrRemove(tran->ch2, tran, wasNotOff, isNotOff);
					rts=rts->rest;
					};
				thisWire->wireLevel=wireLevel;
				};
			thisWire=thisWire->nextRecomputed;
			};
		};
		
	typedef struct FastFindVicinityContextRec {
		RoseSimulation simulation;
		RoseLevel sameValue;
		Bool allSameValue;
		Bool singleInfiniteNoUnknownConductance;
		Bool infiniteFound;
		RoseLevel infiniteValue;
		RoseWire recomputed;
		RoseWire vicinityWire;
		} FastFindVicinityContextRec;
		
	typedef FastFindVicinityContextRec *FastFindVicinityContext;
		
	void FastFindVicinity(wire, context)
		RoseWire wire;
		FastFindVicinityContext context;
		{
		RoseTransistors rts=wire->notOffChannels;
		wire->mark=true;
		if (wire->wireLevel!=context->sameValue || wire->probes!=nil) {
			context->allSameValue=false;
			context->singleInfiniteNoUnknownConductance=false;
			};
		while (rts) {
			RoseTransistor tran=(RoseTransistor)rts->first;
			Conductance tranCond=TranOn(tran, tran->gate->wireLevel);
			RoseWire otherWire=tran->ch1;
			if (otherWire==wire) otherWire=tran->ch2;
			if (tranCond==unknown) context->singleInfiniteNoUnknownConductance=false;
			if (otherWire->wireSize==RoseInfinite) {
				if (context->infiniteFound && context->infiniteValue!=otherWire->wireLevel) context->singleInfiniteNoUnknownConductance=false;
				context->infiniteFound=true;
				context->infiniteValue=otherWire->wireLevel;
				if (otherWire->wireLevel!=context->sameValue) context->allSameValue=false;
				}
			else if (!otherWire->mark) FastFindVicinity(otherWire, context);
			rts=rts->rest;
			};
		{
			RoseWire next=wire->nextPerturbedWire;
			RoseWire previous=wire->previousPerturbedWire;
			if (next!=nil) next->previousPerturbedWire=previous;
			if (previous!=nil) previous->nextPerturbedWire=next;
			if (context->simulation->perturbed==wire) context->simulation->perturbed=next;
			wire->nextPerturbedWire=nil;
			wire->previousPerturbedWire=nil;
			wire->nextRecomputed=context->recomputed;
			context->recomputed=wire;
			wire->nextVicinityWire=context->vicinityWire;
			context->vicinityWire=wire;
			};
		};
		
	void ForceUpDown(vicinityWire, up, down)
		RoseWire vicinityWire;
		RoseStrength up;
		RoseStrength down;
		{
		RoseWire thisWire=vicinityWire;
		while (thisWire) {
			thisWire->mark=false;
			thisWire->upDrive=up;
			thisWire->downDrive=down;
			thisWire=thisWire->nextVicinityWire;
			};
		};
		
	void UpdatePerturbed(simulation)
		RoseSimulation simulation;
		{
		FastFindVicinityContextRec context;
		context.simulation=simulation;
		context.recomputed=nil;
		while (simulation->perturbed) {
			RoseWire wire=simulation->perturbed;
			context.sameValue=wire->wireLevel;
			context.vicinityWire=nil;
			context.infiniteFound=false;
			context.infiniteValue=X;
			context.singleInfiniteNoUnknownConductance=true;
			context.allSameValue=true;
			FastFindVicinity(wire, &context);
			if (context.infiniteFound && context.singleInfiniteNoUnknownConductance) {
				switch (context.infiniteValue) {
					case L : ForceUpDown(context.vicinityWire, RoseNone, RoseInfinite); break;
					case H : ForceUpDown(context.vicinityWire, RoseInfinite, RoseNone); break;
					};
				}
			else if (context.allSameValue) {
				RoseStrength strength;
				if (context.infiniteFound) strength=RoseInfinite;
				else strength=RoseCharge;
				switch (context.sameValue) {
					case L : ForceUpDown(context.vicinityWire, RoseNone, strength); break;
					case H : ForceUpDown(context.vicinityWire, strength, RoseNone); break;
					case X : ForceUpDown(context.vicinityWire, strength, strength); break;
					};
				}
			else {
				RoseWire thisWire=context.vicinityWire;
				while (thisWire) {
					thisWire->mark=false;
					thisWire=thisWire->nextVicinityWire;
					};
				FindVicinity(simulation, wire, upDown);
				PushStrength(simulation, upDown);
				FindVicinity(simulation, wire, up);
				PushStrength(simulation, up);
				FindVicinity(simulation, wire, down);
				PushStrength(simulation, simulation, down);
				};
			};
		UpdateRecomputed(simulation, context.recomputed);
		};
		
	void RoseSettle(simulation)
		RoseSimulation simulation;
		{
		simulation->settle=simulation->settle+1;
		while (simulation->perturbed) {
			simulation->step=simulation->step+1;
			UpdatePerturbed(simulation);
			};
		};
		
	RoseProbe RoseCreateProbe(simulation, flatWire)
		RoseSimulation simulation;
		FlatWireRec flatWire;
		{
		RoseProbe probe;
		FlatWire fw=New(FlatWire, FlatWireRec);
		*fw=flatWire;
		/* This is an interim hack that requires the argument to be an atomic, canonical wire. */
		probe=NewSeq(RoseProbe, RoseProbeRec, RoseProbePrimitive, 1);
		probe->size=1;
		probe->elements[0]=New(RoseProbePrimitive, RoseProbePrimitiveRec);
		probe->elements[0]->wire=(RoseWire)HashTableGet(simulation->coreToRoseWires, fw);
		if (probe->elements[0]->wire==nil) Die;
		probe->elements[0]->wire->probes=ListCons(probe->elements[0]->wire->probes, probe->elements[0]);
		probe->simulation=simulation;
		return(probe);
		};
		
	RoseProbe RoseBindProbe(simulation, cellType, name,  flatCell)
		RoseSimulation simulation;
		CellType cellType;
		String name;
		FlatCellTypeRec flatCell;
		{
		FlatWireRec flatWire;
		flatWire.flatCell=flatCell;
		flatWire.wireRoot=internal;
		flatWire.validPath=false;
		flatWire.wire=FindWire(cellType->public, name);
		return(RoseCreateProbe(simulation, flatWire));
		};
		
	RoseLevel RoseGL(probe)
		RoseProbe probe;
		{
		if (probe->size!=1) Die;
		return(probe->elements[0]->wire->wireLevel);
		};
		
	void RosePL(probe, level, drive)
		RoseProbe probe;
		RoseLevel level;
		RoseStrength drive;
		{
		Nat i;
		for (i=0; i<probe->size; i++) {
			probe->elements[i]->level=level;
			probe->elements[i]->drive=drive;
			Perturb(probe->simulation, probe->elements[i]->wire);
			};
		};
		
/* Initialization */
	
	void RoseInitialize()
		{
		roseFixedWireProp=PropertyRegister(roseFixedWirePropName, nil);
		};