/* -*- tab-width: 4 -*-
*
* Electric(tm) VLSI Design System
*
* File: Sim.java
* Asynchronous Logic Simulator engine
* Original C Code written by Brent Serbin and Peter J. Gallant
* Translated to Java by Steven M. Rubin, Sun Microsystems.
*
* Copyright (c) 2005 Sun Microsystems and Static Free Software
*
* Electric(tm) is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* Electric(tm) is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Electric(tm); see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, Mass 02111-1307, USA.
*/
package com.sun.electric.tool.simulation.als;
import com.sun.electric.database.text.TextUtils;
import com.sun.electric.tool.simulation.DigitalSignal;
import com.sun.electric.tool.simulation.Signal;
import com.sun.electric.tool.simulation.Simulation;
import com.sun.electric.tool.simulation.Stimuli;
import com.sun.electric.tool.simulation.als.ALS.Load;
import com.sun.electric.tool.simulation.als.ALS.Stat;
import com.sun.electric.tool.user.waveform.Panel;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
/**
* Class to do the engine of the ALS Simulator.
*/
public class Sim
{
private ALS als;
private List<Load> chekList = new ArrayList<Load>();
// private Load chekRoot;
private HashMap<ALS.Node,List<ALS.Trak>> tracking;
private static String [] stateDesc = {"High", "Undefined", "Low"};
private static String [] strengthDesc = {"Off-", "Weak-", "Weak-", "", "", "Strong-", "Strong-"};
boolean tracing = false;
Sim(ALS als)
{
this.als = als;
}
/**
* Method to initialize the simulator for a simulation run. The
* vector link list is copied to a master event scheduling link list and
* the database is initialized to its starting values. After these housekeeping
* tasks are completed the simulator is ready to start the actual simulation.
* Returns the time where the simulation quiesces.
*/
double initializeSimulator(boolean force)
{
als.timeAbs = 0.0;
tracking = new HashMap<ALS.Node,List<ALS.Trak>>();
while (als.linkFront != null)
{
if (als.linkFront.down != null)
{
als.linkFront.down.right = als.linkFront.right;
als.linkFront = als.linkFront.down;
} else
{
als.linkFront = als.linkFront.right;
}
}
als.linkBack = null;
for (ALS.Link linkHead = als.setRoot; linkHead != null; linkHead = linkHead.right)
{
ALS.Link linkPtr2 = new ALS.Link();
if (linkPtr2 == null) return(als.timeAbs);
linkPtr2.type = linkHead.type;
linkPtr2.ptr = linkHead.ptr;
linkPtr2.state = linkHead.state;
linkPtr2.strength = linkHead.strength;
linkPtr2.priority = linkHead.priority;
linkPtr2.time = linkHead.time;
linkPtr2.primHead = null;
insertLinkList(linkPtr2);
}
for(ALS.Node nodeHead : als.nodeList)
{
nodeHead.sumState = Stimuli.LOGIC_LOW;
nodeHead.sumStrength = Stimuli.OFF_STRENGTH;
nodeHead.newState = new Integer(Stimuli.LOGIC_LOW);
nodeHead.newStrength = Stimuli.OFF_STRENGTH;
nodeHead.arrive = 0;
nodeHead.depart = 0;
nodeHead.tLast = 0.0;
for(Stat statHead : nodeHead.statList)
{
statHead.newState = Stimuli.LOGIC_LOW;
statHead.newStrength = Stimuli.OFF_STRENGTH;
statHead.schedOp = 0;
}
}
// now run the simulation
boolean update = Simulation.isBuiltInResimulateEach();
if (force) update = true;
if (update)
{
// fire events until end of time or quiesced
System.out.print("Simulating...");
// determine highest time to simulate
Rectangle2D bounds = als.an.getBounds();
double tMax = bounds.getMaxX();
for(Iterator<Panel> it = als.ww.getPanels(); it.hasNext(); )
{
Panel wp = it.next();
double panelMax = wp.getMaxXAxis();
if (panelMax > tMax) tMax = panelMax;
}
while (als.linkFront != null && als.linkFront.time <= tMax)
{
if (fireEvent()) break;
if (chekList.size() != 0)
{
if (scheduleNewEvents()) break;
}
}
// redisplay results
fillDisplayArrays();
System.out.println("Done. Ran to time " + TextUtils.convertToEngineeringNotation(als.timeAbs));
}
return als.timeAbs;
}
/**
* Method to extract the ALS simulation data and update the Stimuli database
*/
private void fillDisplayArrays()
{
HashSet<DigitalSignal> sigsChanged = new HashSet<DigitalSignal>();
for(ALS.Node node : tracking.keySet())
{
DigitalSignal sig = node.sig;
List<ALS.Trak> trakHeads = tracking.get(node);
int count = trakHeads.size();
double [] timeVector = new double[count+1];
int [] stateVector = new int[count+1];
timeVector[0] = 0;
stateVector[0] = Stimuli.LOGIC_LOW | Stimuli.OFF_STRENGTH;
int j=1;
for(ALS.Trak trakHead : trakHeads)
{
timeVector[j] = trakHead.time;
stateVector[j] = trakHead.state;
j++;
}
sig.setTimeVector(timeVector);
sig.setStateVector(stateVector);
sigsChanged.add(sig);
}
// set all other signals to "empty"
int [] stateVector = new int[1];
stateVector[0] = 0;
double [] timeVector = new double[1];
timeVector[0] = 0;
for(Signal s : als.an.getSignals())
{
DigitalSignal sig = (DigitalSignal)s;
if (sigsChanged.contains(sig)) continue;
sig.setTimeVector(timeVector);
sig.setStateVector(stateVector);
}
als.ww.repaint();
}
/**
* Method to get the entry from the front of the event scheduling
* link list and updates the database accordingly. If a node is updated by a
* user defined vector the node value is changed as specified in the linklist
* entry. If a transition fired, all the output nodes are updated as specified
* in the truth table for the transtion. Returns true on error.
*/
private boolean fireEvent()
{
als.timeAbs = als.linkFront.time;
ALS.Link linkHead = als.linkFront;
if (als.linkFront.down != null)
{
als.linkFront = als.linkFront.down;
als.linkFront.left = null;
als.linkFront.right = linkHead.right;
als.linkFront.up = linkHead.up;
if (als.linkFront.right != null)
{
als.linkFront.right.left = als.linkFront;
} else
{
als.linkBack = als.linkFront;
}
} else
{
als.linkFront = als.linkFront.right;
if (als.linkFront != null)
{
als.linkFront.left = null;
} else
{
als.linkBack = null;
}
}
tracing = false;
switch (linkHead.type)
{
case 'G':
ALS.Stat statHead = (ALS.Stat)linkHead.ptr;
if (statHead.nodePtr.traceNode)
{
String s2 = als.computeNodeName(statHead.nodePtr);
System.out.println(TextUtils.convertToEngineeringNotation(als.timeAbs) +
": Firing gate " + statHead.primPtr.name + statHead.primPtr.level + ", net " + s2);
tracing = true;
}
if (statHead.schedState != linkHead.state ||
statHead.schedOp != linkHead.operatr ||
statHead.schedStrength != linkHead.strength)
{
break;
}
statHead.schedOp = 0;
char operatr = linkHead.operatr;
int operand = 0;
if (operatr < 128)
{
operand = ((Integer)linkHead.state).intValue();
} else
{
operatr -= 128;
// ALS.Node nodeHead = (ALS.Node)linkHead.state;
// operand = nodeHead.sumState;
}
int state = 0;
switch (operatr)
{
case '=':
state = operand;
break;
case '+':
state = statHead.nodePtr.sumState + operand;
break;
case '-':
state = statHead.nodePtr.sumState - operand;
break;
case '*':
state = statHead.nodePtr.sumState * operand;
break;
case '/':
state = statHead.nodePtr.sumState / operand;
break;
case '%':
state = statHead.nodePtr.sumState % operand;
break;
default:
System.out.println("Invalid arithmetic operator: " + operatr);
return true;
}
if (state == statHead.newState &&
linkHead.strength == statHead.newStrength)
{
break;
}
statHead.newState = state;
statHead.newStrength = linkHead.strength;
createCheckList(statHead.nodePtr, linkHead);
break;
case 'N':
ALS.Node nodeHead = (ALS.Node)linkHead.ptr;
if (nodeHead.traceNode)
{
String s2 = als.computeNodeName(nodeHead);
System.out.println(TextUtils.convertToEngineeringNotation(als.timeAbs) + ": Changed state of net " + s2);
tracing = true;
}
if (linkHead.state == nodeHead.newState &&
linkHead.strength == nodeHead.newStrength)
break;
nodeHead.newState = linkHead.state;
nodeHead.newStrength = linkHead.strength;
createCheckList(nodeHead, linkHead);
break;
case 'C':
double time = als.timeAbs;
ALS.Row rowHead = (ALS.Row)linkHead.ptr;
for(Object obj : rowHead.inList)
{
ALS.Link vectHead = (ALS.Link)obj;
ALS.Link linkPtr2 = new ALS.Link();
linkPtr2.type = 'N';
linkPtr2.ptr = vectHead.ptr;
linkPtr2.state = vectHead.state;
linkPtr2.strength = vectHead.strength;
linkPtr2.priority = vectHead.priority;
linkPtr2.time = time;
linkPtr2.primHead = null;
insertLinkList(linkPtr2);
time += vectHead.time;
}
if (((Integer)linkHead.state).intValue() == 0)
{
calculateClockTime(linkHead, rowHead);
return false;
}
linkHead.state = new Integer(((Integer)linkHead.state).intValue() - 1);
if (((Integer)linkHead.state).intValue() != 0)
{
calculateClockTime(linkHead, rowHead);
return false;
}
}
return false;
}
/**
* Method to calculate the sum state and strength for a node and if
* it has changed from a previous check it will enter the input transition list
* into a master check list that is used by the event scheduling routine.
* It should be noted that it is neccessary to calculate the sum strength and
* state for a node because it is possible to have nodes that have more than
* one transition driving it.
*/
private void createCheckList(ALS.Node nodeHead, ALS.Link linkHead)
{
// get initial state of the node
int state = ((Integer)nodeHead.newState).intValue();
int strength = nodeHead.newStrength;
// print state of signal if this signal is being traced
if (tracing)
{
System.out.println(" Formerly " + strengthDesc[nodeHead.sumStrength] + stateDesc[nodeHead.sumState+3] +
", starts at " + strengthDesc[strength] + stateDesc[state+3]);
}
// look at all factors affecting the node
for(Stat statHead : nodeHead.statList)
{
int thisState = statHead.newState;
int thisStrength = statHead.newStrength;
if (tracing)
System.out.println(" " + strengthDesc[thisStrength] + stateDesc[thisState+3] +
" from " + statHead.primPtr.name + statHead.primPtr.level);
// higher strength overrides previous node state
if (thisStrength > strength)
{
state = thisState;
strength = thisStrength;
continue;
}
// same strength: must arbitrate
if (thisStrength == strength)
{
if (thisState != state)
state = Stimuli.LOGIC_X;
}
}
// if the node has nothing driving it, set it to the old value
if (strength == Stimuli.OFF_STRENGTH)
{
state = nodeHead.sumState;
strength = Stimuli.NODE_STRENGTH;
}
// stop now if node state did not change
if (nodeHead.sumState == state && nodeHead.sumStrength == strength)
{
if (tracing) System.out.println(" NO CHANGE");
return;
}
if (nodeHead.sig != null)
{
List<ALS.Trak> nodeData = tracking.get(nodeHead);
if (nodeData == null)
{
nodeData = new ArrayList<ALS.Trak>();
tracking.put(nodeHead, nodeData);
}
ALS.Trak trakHead = new ALS.Trak();
trakHead.state = state | strength;
trakHead.time = als.timeAbs;
nodeData.add(trakHead);
}
if (tracing)
System.out.println(" BECOMES " + strengthDesc[strength] + stateDesc[state+3]);
nodeHead.sumState = state;
nodeHead.sumStrength = strength;
nodeHead.tLast = als.timeAbs;
als.driveNode = nodeHead;
for(Load l : nodeHead.pinList)
chekList.add(l);
}
/**
* Method to examine the truth tables for the transitions that are
* specified in the checking list. If there is a match between a truth table
* entry and the state of the logic network the transition is scheduled
* for firing. Returns true on error.
*/
private boolean scheduleNewEvents()
{
// make a copy of the event list, and clear the main list
List<Load> chekListCopy = new ArrayList<Load>();
for(Load l : chekList)
chekListCopy.add(l);
chekList.clear();
for(Load chekHead : chekListCopy)
{
ALS.Model primHead = (ALS.Model)chekHead.ptr;
if (primHead.type == 'F')
{
ALS.Func funcHead = (ALS.Func)primHead.ptr;
funcHead.procPtr.simulate(primHead);
continue;
}
for (ALS.Row rowHead = (ALS.Row)primHead.ptr; rowHead != null; rowHead = rowHead.next)
{
int flag = 1;
for(Object obj : rowHead.inList)
{
ALS.IO ioHead = (ALS.IO)obj;
int operatr = ioHead.operatr;
int operand;
if (operatr < 128)
{
operand = ((Integer)ioHead.operand).intValue();
} else
{
operatr -= 128;
ALS.Node nodeHead = (ALS.Node)ioHead.operand;
operand = nodeHead.sumState;
}
switch (operatr)
{
case '=':
if (((ALS.Node)ioHead.nodePtr).sumState != operand) flag = 0;
break;
case '!':
if (((ALS.Node)ioHead.nodePtr).sumState == operand) flag = 0;
break;
case '<':
if (((ALS.Node)ioHead.nodePtr).sumState >= operand) flag = 0;
break;
case '>':
if (((ALS.Node)ioHead.nodePtr).sumState <= operand) flag = 0;
break;
default:
System.out.println("Invalid logical operator: " + operatr);
return true;
}
if (flag == 0) break;
}
if (flag != 0)
{
calculateEventTime(primHead, rowHead);
break;
}
}
}
return false;
}
/**
* Method to calculate the time when the next occurance of a set of
* clock vectors is to be added to the event scheduling linklist.
*
* Calling Arguments:
* linkHead = pointer to the link element to be reinserted into the list
* rowHead = pointer to a row element containing timing information
*/
private void calculateClockTime(ALS.Link linkHead, ALS.Row rowHead)
{
double time = als.timeAbs;
if (rowHead.delta != 0) time += rowHead.delta;
if (rowHead.linear != 0)
{
double prob = Math.random();
time += 2.0 * prob * rowHead.linear;
}
/*
* if (rowHead.exp)
* {
* prob = rand() / MAXINTBIG;
* time += (-log(prob) * (rowHead.exp));
* }
*/
linkHead.time = time;
insertLinkList(linkHead);
}
/**
* Method to calculate the time of occurance of an event and then
* places an entry into the event scheduling linklist for later execution.
* Returns true on error.
*
* Calling Arguments:
* primHead = pointer to the primitive to be scheduled for firing
* rowHead = pointer to the row containing the event to be scheduled
*/
private void calculateEventTime(ALS.Model primHead, ALS.Row rowHead)
{
double time = 0.0;
int priority = primHead.priority;
if (rowHead.delta != 0) time += rowHead.delta;
if (rowHead.abs != 0) time += rowHead.abs;
if (rowHead.linear != 0)
{
double prob = Math.random();
time += 2.0 * prob * rowHead.linear;
}
/*
* if (rowHead.exp)
* {
* prob = rand() / MAXINTBIG;
* time += (-log(prob) * (rowHead.exp));
* }
*/
if (rowHead.random != 0)
{
double prob = Math.random();
if (prob <= rowHead.random)
{
priority = -1;
}
}
if (primHead.fanOut != 0)
{
Iterator<Object> it = rowHead.outList.iterator();
ALS.IO ioPtr = (ALS.IO)it.next();
ALS.Stat statHead = (ALS.Stat)ioPtr.nodePtr;
time *= statHead.nodePtr.load;
}
time += als.timeAbs;
for(Object obj : rowHead.outList)
{
ALS.IO ioHead = (ALS.IO)obj;
ALS.Stat statHead = (ALS.Stat)ioHead.nodePtr;
if (statHead.schedOp == ioHead.operatr &&
statHead.schedState.equals(ioHead.operand) &&
statHead.schedStrength == ioHead.strength)
{
continue;
}
ALS.Link linkPtr2 = new ALS.Link();
linkPtr2.type = 'G';
linkPtr2.ptr = statHead;
linkPtr2.operatr = statHead.schedOp = ioHead.operatr;
linkPtr2.state = statHead.schedState = ioHead.operand;
linkPtr2.strength = statHead.schedStrength = ioHead.strength;
linkPtr2.time = time;
linkPtr2.priority = priority;
linkPtr2.primHead = primHead;
if (tracing)
{
System.out.println(" Schedule(G): " + statHead.primPtr.name + statHead.primPtr.level +
" at " + TextUtils.convertToEngineeringNotation(time));
}
insertLinkList(linkPtr2);
}
}
/**
* Method to insert a data element into a linklist that is 2
* dimensionally sorted first by time and then priority. This link list is
* used to schedule events for the simulation.
*
* Calling Arguments:
* linkHead = pointer to the data element that is going to be inserted
*/
void insertLinkList(ALS.Link linkHead)
{
// linkPtr1Is: 0: ALS.linkBack 1: linkPtr2.up 2: linkPtr2.left
int linkPtr1Is = 0;
ALS.Link linkPtr2 = als.linkBack;
ALS.Link linkPtr2Val = linkPtr2;
ALS.Link linkPtr3 = null;
for(;;)
{
if (linkPtr2 == null)
{
als.linkFront = linkHead;
switch (linkPtr1Is)
{
case 0: als.linkBack = linkHead; break;
case 1: linkPtr2Val.up = linkHead; break;
case 2: linkPtr2Val.left = linkHead; break;
}
linkHead.left = null;
linkHead.right = linkPtr3;
linkHead.up = linkHead;
linkHead.down = null;
return;
}
if (linkPtr2.time < linkHead.time)
{
linkPtr2.right = linkHead;
switch (linkPtr1Is)
{
case 0: als.linkBack = linkHead; break;
case 1: linkPtr2Val.up = linkHead; break;
case 2: linkPtr2Val.left = linkHead; break;
}
linkHead.left = linkPtr2;
linkHead.right = linkPtr3;
linkHead.up = linkHead;
linkHead.down = null;
return;
}
if (linkPtr2.time == linkHead.time)
{
if (linkPtr2.priority > linkHead.priority)
{
linkHead.left = linkPtr2.left;
linkHead.right = linkPtr2.right;
linkHead.down = linkPtr2;
linkHead.up = linkPtr2.up;
linkPtr2.up = linkHead;
switch (linkPtr1Is)
{
case 0: als.linkBack = linkHead; break;
case 1: linkPtr2Val.up = linkHead; break;
case 2: linkPtr2Val.left = linkHead; break;
}
if (linkHead.left != null)
{
linkHead.left.right = linkHead;
} else
{
als.linkFront = linkHead;
}
return;
}
linkPtr1Is = 1;
linkPtr2Val = linkPtr2;
linkPtr2 = linkPtr2.up;
linkPtr3 = null;
for(;;)
{
if (linkPtr2.priority <= linkHead.priority)
{
linkPtr2.down = linkHead;
switch (linkPtr1Is)
{
case 0: als.linkBack = linkHead; break;
case 1: linkPtr2Val.up = linkHead; break;
case 2: linkPtr2Val.left = linkHead; break;
}
linkHead.up = linkPtr2;
linkHead.down = linkPtr3;
return;
}
linkPtr3 = linkPtr2;
linkPtr1Is = 1;
linkPtr2Val = linkPtr2;
linkPtr2 = linkPtr2.up;
}
}
linkPtr3 = linkPtr2;
linkPtr1Is = 2;
linkPtr2Val = linkPtr2;
linkPtr2 = linkPtr2.left;
}
}
}