/*
 * Electric(tm) VLSI Design System
 *
 * File: simalssim.c
 * Asynchronous Logic Simulator engine
 * From algorithms by: Brent Serbin and Peter J. Gallant
 * Last maintained by: Steven M. Rubin
 *
 * Copyright (c) 2000 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 2 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.
 *
 * Static Free Software
 * 4119 Alpine Road
 * Portola Valley, California 94028
 * info@staticfreesoft.com
 */

#include "config.h"
#if SIMAID

#include "global.h"
#include "sim.h"
#include "simals.h"

LINKPTR simals_freelink = 0;

/* prototypes for local routines */
INTSML simals_fire_event(void);
void simals_create_check_list(NODEPTR);
INTSML simals_schedule_new_events(void);
void simals_calculate_clock_time(LINKPTR, ROWPTR);
INTSML simals_calculate_event_time(MODPTR, ROWPTR);

/*
 * Routine to free all memory associated with this module.
 */
void simals_freesimmemory(void)
{
	LINKPTR link;

	while (simals_freelink != 0)
	{
		link = simals_freelink;
		simals_freelink = simals_freelink->left;
		efree((char *)link);
	}
}

/*
 * Name: simals_initialize_simulator
 *
 * Description:
 *	This procedure initializes 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.
 */
float simals_initialize_simulator(INTSML force)
{
	LINKPTR linkptr2, linkhead, link;
	NODEPTR nodehead;
	STATPTR stathead;
	char s1[40];
	float tmin, tmax;
	REGISTER VARIABLE *var;

	simals_time_abs = 0.0;
	simals_trakptr = simals_trakfull = 0;

	while (simals_linkfront)
	{
		link = simals_linkfront;
		if (simals_linkfront->down)
		{
			simals_linkfront->down->right = simals_linkfront->right;
			simals_linkfront = simals_linkfront->down;
		} else
		{
			simals_linkfront = simals_linkfront->right;
		}
		simals_free_link_mem(link);
	}
	simals_linkback = 0;

	for (linkhead = simals_setroot; linkhead; linkhead = linkhead->right)
	{
		linkptr2 = simals_alloc_link_mem();
		if (linkptr2 == 0) return(simals_time_abs);
		linkptr2->type = linkhead->type;
		linkptr2->ptr = linkhead->ptr;
		linkptr2->state = linkhead->state;
		linkptr2->strength = linkhead->strength;
		linkptr2->priority = linkhead->priority;
		linkptr2->time = linkhead->time;
		simals_insert_link_list(linkptr2);
	}

	for (nodehead = simals_noderoot; nodehead; nodehead = nodehead->next)
	{
		nodehead->sum_state = LOGIC_LOW;
		nodehead->sum_strength = 0;
		nodehead->new_state = LOGIC_LOW;
		nodehead->new_strength = 0;
		nodehead->maxsize = 0;
		nodehead->arrive = 0;
		nodehead->depart = 0;
		nodehead->tk_sec = 0.0;
		nodehead->t_last = 0.0;
		for (stathead = nodehead->statptr; stathead; stathead = stathead->next)
		{
			stathead->new_state = LOGIC_LOW;
			stathead->new_strength = 0;
			stathead->sched_op = 0;
		}
	}

	if (simals_seed_flag) (void)srand(3);

	/* now run the simulation */
	if (force != 0) var = NOVARIABLE; else
		var = getvalkey((INTBIG)sim_aid, VAID, VINTEGER, simals_no_update);
	if (var == NOVARIABLE || var->addr == 0)
	{
		/* fire events until end of time or quiesced */
		sim_window_gettimerange(&tmin, &tmax);
		while (simals_linkfront && (simals_linkfront->time <= tmax))
		{
			if (stopping(STOPREASONSIMULATE)) break;
			if (simals_fire_event() != 0) break;
			if (simals_chekroot)
			{
				if (simals_schedule_new_events() != 0) break;
			}
		}

		/* redisplay results */
		simals_fill_display_arrays();
		sim_window_redraw();
		sim_window_updatelayoutwindow();
		sim_windowconvertengineeringnotation(simals_time_abs, s1);
		ttyputverbose(_("Simulation completed at time %s"), s1);
	}

	return(simals_time_abs);
}

/*
 * Name: simals_fire_event
 *
 * Description:
 *	This procedure gets 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 nonzero on error.
 */
INTSML simals_fire_event(void)
{
	INTBIG operand, state;
	unsigned char operatr;
	float time;
	LINKPTR	linkhead, linkptr2, vecthead;
	NODEPTR	nodehead;
	ROWPTR rowhead;
	STATPTR	stathead;

	simals_time_abs = simals_linkfront->time;
	linkhead = simals_linkfront;
	if (simals_linkfront->down)
	{
		simals_linkfront = simals_linkfront->down;
		simals_linkfront->left = 0;
		simals_linkfront->right = linkhead->right;
		simals_linkfront->up = linkhead->up;
		if (simals_linkfront->right)
		{
			simals_linkfront->right->left = simals_linkfront;
		} else
		{
			simals_linkback = simals_linkfront;
		}
	} else
	{
		simals_linkfront = simals_linkfront->right;
		if (simals_linkfront)
		{
			simals_linkfront->left = 0;
		} else
		{
			simals_linkback = 0;
		}
	}

	switch (linkhead->type)
	{
		case 'G':
			stathead = (STATPTR) linkhead->ptr;
			if ((stathead->sched_op != linkhead->operatr)
				|| (stathead->sched_state != linkhead->state)
				|| (stathead->sched_strength != linkhead->strength)) break;
			stathead->sched_op = 0;

			/* ttyputmsg(_("Fire: $N%ld"), stathead->nodeptr->num); */
			operatr = linkhead->operatr;
			if (operatr < 128)
			{
				operand = (INTBIG) linkhead->state;
			} else
			{
				operatr -= 128;
				nodehead = (NODEPTR) linkhead->state;
				operand = nodehead->sum_state;
			}

			switch (operatr)
			{
				case '=':
					state = operand;
					break;
				case '+':
					state = stathead->nodeptr->sum_state + operand;
					break;
				case '-':
					state = stathead->nodeptr->sum_state - operand;
					break;
				case '*':
					state = stathead->nodeptr->sum_state * operand;
					break;
				case '/':
					state = stathead->nodeptr->sum_state / operand;
					break;
				case '%':
					state = stathead->nodeptr->sum_state % operand;
					break;
				default:
					ttyputerr(_("Invalid arithmetic operator: %c"), operatr);
					return(1);
			}

			if (stathead->new_state == state && stathead->new_strength == linkhead->strength)
				break;
			stathead->new_state = state;
			stathead->new_strength = linkhead->strength;
			simals_create_check_list(stathead->nodeptr);
			break;

		case 'N':
			nodehead = (NODEPTR) linkhead->ptr;
			if (linkhead->state == nodehead->new_state && linkhead->strength == nodehead->new_strength)
				break;

			/* ttyputmsg(_("Fire: $N%ld"), nodehead->num);*/
			nodehead->new_state = linkhead->state;
			nodehead->new_strength = linkhead->strength;
			simals_create_check_list(nodehead);
			break;

		case 'C':
			time = simals_time_abs;
			rowhead = (ROWPTR) linkhead->ptr;
			for (vecthead = (LINKPTR) rowhead->inptr; vecthead; vecthead = vecthead->right)
			{
				linkptr2 = simals_alloc_link_mem();
				if (linkptr2 == 0) return(1);
				linkptr2->type = 'N';
				linkptr2->ptr = vecthead->ptr;
				linkptr2->state = vecthead->state;
				linkptr2->strength = vecthead->strength;
				linkptr2->priority = vecthead->priority;
				linkptr2->time = time;
				simals_insert_link_list(linkptr2);
				time += vecthead->time;
			}
			if (linkhead->state == 0)
			{
				simals_calculate_clock_time(linkhead, rowhead);
				return(0);
			}
			--(linkhead->state);
			if (linkhead->state)
			{
				simals_calculate_clock_time(linkhead, rowhead);
				return(0);
			}
	}

	simals_free_link_mem(linkhead);
	return(0);
}

/*
 * Name: simals_create_check_list
 *
 * Description:
 *	This procedure calculates 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.
 */
void simals_create_check_list(NODEPTR nodehead)
{
	INTBIG	state;
	INTSML	strength;
	STATPTR stathead;
	TRAKPTR trakhead;

	state = nodehead->new_state;
	strength = nodehead->new_strength;

	for (stathead = nodehead->statptr; stathead; stathead = stathead->next)
	{
		if (stathead->new_strength > strength)
		{
			state = stathead->new_state;
			strength = stathead->new_strength;
			continue;
		}
		if (stathead->new_strength == strength)
		{
			if (stathead->new_state != state)
			{
				state = LOGIC_X;
			}
		}
	}

	if (strength == OFF_STRENGTH)
	{
		state = nodehead->sum_state;
		strength = NODE_STRENGTH;
	}

	if (nodehead->sum_state == state && nodehead->sum_strength == strength) return;

	if (nodehead->plot_node)
	{
		trakhead = &(simals_trakroot[simals_trakptr]);
		trakhead->ptr = (NODEPTR) nodehead;
		trakhead->state = state;
		trakhead->strength = strength;
		trakhead->time = simals_time_abs;
		simals_trakptr = (simals_trakptr + 1) % simals_trace_size;
		if (! simals_trakptr)
		{
			simals_trakfull = 1;
		}
	}

	nodehead->sum_state = state;
	nodehead->sum_strength = strength;
	nodehead->t_last = simals_time_abs;

	simals_drive_node = nodehead;
	simals_chekroot = nodehead->pinptr;
}

/*
 * Name: simals_schedule_new_events
 *
 * Description:
 *	This procedure examines 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 nonzero on error.
 */
INTSML simals_schedule_new_events(void)
{
	INTBIG operand;
	unsigned char flag, operatr;
	LOADPTR	chekhead;
	MODPTR primhead;
	ROWPTR rowhead;
	IOPTR iohead;
	NODEPTR	nodehead;
	FUNCPTR	funchead;

	for (chekhead = simals_chekroot; chekhead; chekhead = chekhead->next)
	{
		primhead = (MODPTR) chekhead->ptr;

		if (primhead->type == 'F')
		{
			funchead = (FUNCPTR) primhead->ptr;
			(*(funchead->procptr))(primhead);
			continue;
		}

		for (rowhead = (ROWPTR) primhead->ptr; rowhead; rowhead = rowhead->next)
		{
			flag = 1;
			for (iohead = rowhead->inptr; iohead; iohead = iohead->next)
			{
				operatr = iohead->operatr;
				if (operatr < 128)
				{
					operand = (INTBIG) iohead->operand;
				} else
				{
					operatr -= 128;
					nodehead = (NODEPTR) iohead->operand;
					operand = nodehead->sum_state;
				}

				switch (operatr)
				{
					case '=':
						if (iohead->nodeptr->sum_state != operand) flag = 0;
						break;
					case '!':
						if (iohead->nodeptr->sum_state == operand) flag = 0;
						break;
					case '<':
						if (iohead->nodeptr->sum_state >= operand) flag = 0;
						break;
					case '>':
						if (iohead->nodeptr->sum_state <= operand) flag = 0;
						break;
					default:
						ttyputerr(_("Invalid logical operator: %c"), operatr);
						return(1);
				}

				if (! flag) break;
			}

			if (flag)
			{
				if (simals_calculate_event_time(primhead, rowhead) != 0) return(1);
				break;
			}
		}
	}
	simals_chekroot = 0;
	return(0);
}

/*
 * Name: simals_calculate_clock_time
 *
 * Description:
 *	This procedure calculates 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
 */
void simals_calculate_clock_time(LINKPTR linkhead, ROWPTR rowhead)
{
	float time, prob;

	time = simals_time_abs;

	if (rowhead->delta) time += rowhead->delta;
	if (rowhead->linear)
	{
		prob = (float)(rand() / HUGEINT);
		time += 2.0f * prob * rowhead->linear;
	}

	/*
	if (rowhead->exp)
	{
		prob = rand() / HUGEINT;
		time += (-log(prob) * (rowhead->exp));
	}
	*/

	linkhead->time = time;
	simals_insert_link_list(linkhead);

	return;
}

/*
 * Name: simals_calculate_event_time
 *
 * Description:
 *	This procedure calculates the time of occurance of an event and then
 * places an entry into the event scheduling linklist for later execution.
 * Returns nonzero 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
 */
INTSML simals_calculate_event_time(MODPTR primhead, ROWPTR rowhead)
{
	INTSML priority;
	float time, prob;
	STATPTR	stathead;
	IOPTR iohead;
	LINKPTR	linkptr2;

	time = 0.0;
	priority = primhead->priority;

	if (rowhead->delta) time += rowhead->delta;
	if (rowhead->abs) time += rowhead->abs;
	if (rowhead->linear)
	{
		prob = (float)(rand() / HUGEINT);
		time += 2.0f * prob * rowhead->linear;
	}

	/*
	if (rowhead->exp)
	{
		prob = rand() / HUGEINT;
		time += (-log(prob) * (rowhead->exp));
	}
	*/

	if (rowhead->random)
	{
		prob = (float)(rand() / HUGEINT);
		if (prob <= rowhead->random)
		{
			priority = -1;
		}
	}

	if (primhead->fanout)
	{
		stathead = (STATPTR) rowhead->outptr->nodeptr;
		time *= stathead->nodeptr->load;
	}
	time += simals_time_abs;

	for (iohead = rowhead->outptr; iohead; iohead = iohead->next)
	{
		stathead = (STATPTR) iohead->nodeptr;
		if ((stathead->sched_op == iohead->operatr)
			&& (stathead->sched_state == (INTBIG)iohead->operand)
				&& (stathead->sched_strength == iohead->strength))
		{
			continue;
		}

		linkptr2 = simals_alloc_link_mem();
		if (linkptr2 == 0) return(1);
		linkptr2->type = 'G';
		linkptr2->ptr = (char *) stathead;
		linkptr2->operatr = stathead->sched_op = iohead->operatr;
		linkptr2->state = stathead->sched_state = (INTBIG) iohead->operand;
		linkptr2->strength = stathead->sched_strength = iohead->strength;
		linkptr2->time = time;
		linkptr2->priority = priority;
		simals_insert_link_list(linkptr2);
	}
	return(0);
}

/*
 * Name: simals_alloc_link_mem
 *
 * Description:
 *	This procedure allocates a block of memory for a single entry in the
 * link list data structure.  This procedure uses its own memory pool if some is
 * available to speed up the simulation process.  Returns zero on error.
 */
LINKPTR simals_alloc_link_mem(void)
{
	LINKPTR link;

	if (simals_freelink == 0)
	{
		link = (LINKPTR)simals_alloc_mem((INTBIG)sizeof(LINK));
		if (link == 0) return(0);
	} else
	{
		link = simals_freelink;
		simals_freelink = simals_freelink->left;
	}
	return(link);
}

/*
 * Name: simals_free_link_mem
 *
 * Description:
 *	This procedure frees a block of memory for a link list data structure.
 * Rather than returning the memory to the system it enters the memory location
 * inot its own memory pool.  This is done to speed up the performance of a
 * simulation.
 *
 * Calling Argument
 *	linkhead = pointer to data structure that should be added to memory pool
 */
void simals_free_link_mem(LINKPTR linkhead)
{
	linkhead->left = simals_freelink;
	simals_freelink = linkhead;
}

/*
 * Name: simals_insert_set_list
 *
 * Description:
 *	This procedure inserts a data element into a linklist that is sorted
 * 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
 */
char **simals_insert_set_list(LINKPTR linkhead)
{
	char **linkptr1;
	LINKPTR linkptr2;

	linkptr1 = (char**) &simals_setroot;
	while (1)
	{
		if ((*linkptr1) == 0)
		{
			*linkptr1 = (char*)linkhead;
			return(linkptr1);
		}
		linkptr2 = (LINKPTR) *linkptr1;
		if ((linkptr2->time > linkhead->time) || ((linkptr2->time == linkhead->time) &&
			(linkptr2->priority > linkhead->priority)))
		{
			linkhead->right = linkptr2;
			*linkptr1 = (char*) linkhead;
			return(linkptr1);
		}
		linkptr1 = (char**) &(linkptr2->right);
	}
}

/*
 * Name: simals_insert_link_list
 *
 * Description:
 *	This procedure inserts 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 simals_insert_link_list(LINKPTR linkhead)
{
	char **linkptr1;
	LINKPTR linkptr2, linkptr3;

	/* if (linkhead->type == 'G')
		ttyputmsg(_("Schedule: $N%ld"), ((STATPTR) linkhead->ptr)->nodeptr->num); */

	linkptr1 = (char**) &simals_linkback;
	linkptr2 = simals_linkback;
	linkptr3 = 0;
	while (1)
	{
		if (! linkptr2)
		{
			simals_linkfront = (LINKPTR)(*linkptr1 = (char*)linkhead);
			linkhead->left = 0;
			linkhead->right = linkptr3;
			linkhead->up = linkhead;
			linkhead->down = 0;
			return;
		}

		if (linkptr2->time < linkhead->time)
		{
			*linkptr1 = (char*)(linkptr2->right = linkhead);
			linkhead->left = linkptr2;
			linkhead->right = linkptr3;
			linkhead->up = linkhead;
			linkhead->down = 0;
			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 = (LINKPTR)(*linkptr1 = (char*)linkhead);
				if (linkhead->left)
				{
					linkhead->left->right = linkhead;
				} else
				{
					simals_linkfront = linkhead;
				}
				return;
			}

			linkptr1 = (char**) &(linkptr2->up);
			linkptr2 = linkptr2->up;
			linkptr3 = 0;
			while (1)
			{
				if (linkptr2->priority <= linkhead->priority)
				{
					*linkptr1 = (char*)(linkptr2->down = linkhead);
					linkhead->up = linkptr2;
					linkhead->down = linkptr3;
					return;
				}

				linkptr3 = linkptr2;
				linkptr1 = (char**) &(linkptr2->up);
				linkptr2 = linkptr2->up;
			}
		}

		linkptr3 = linkptr2;
		linkptr1 = (char**) &(linkptr2->left);
		linkptr2 = linkptr2->left;
	}
}

#endif  /* SIMAID - at top */
