/*--------------------------------------------------------------*/
/* vesta.c ---							*/
/*								*/
/*	This file reads two files:  (1) A verilog netlist file	*/
/*	of a circuit (structural verilog, that is, gate-level	*/
/*	information only), and (2) a liberty format file 	*/
/*	containing timing information for the standard cell 	*/
/*	macros instantiated in the verilog netlist.  In		*/
/*	addition, it may take an optional third file containing	*/
/*	information about the resistance and capacitance of the	*/
/*	wiring (see below).					*/
/*								*/
/*	Options are supplied as command-line arguments:		*/
/*								*/
/*		-d <delay_file>	Wiring delays (see below)	*/
/*		-p <value>  	Clock period, in ps		*/
/*		-l <value>	Output load, in fF		*/
/*		-v <level>	set verbose mode		*/
/*		-V		report version number		*/
/*		-e		exhaustive search		*/
/*								*/
/*	Currently the only output this tool generates is a	*/
/*	list of paths with negative slack.  If no paths have	*/
/*	negative slack, then the 20 paths with the smallest	*/
/*	positive slack are output, following a "success"	*/
/*	message.  If no clock period is supplied, then the	*/
/*	clock period is set to equal the delay of the longest	*/
/*	delay path, and the 20 paths with the smallest positive	*/
/*	slack are output, following a statement indicated the	*/
/*	computed minimum clock period.				*/
/*--------------------------------------------------------------*/

/*--------------------------------------------------------------*/
/*	Wiring delay file:					*/
/*	For qflow, the wiring delay is generated by the tool	*/
/*	"def2delays".  The file format is as follows:		*/
/*								*/
/*	<net_name>						*/
/*	<output_terminal> [<net_capacitance>]			*/
/*	<input_terminal_1> <delay_1>				*/
/*	...							*/
/*	<input_terminal_N> <delay_N>				*/
/*								*/
/*	Optional value <net_capacitance> is in fF		*/
/*	Values <delay_i> are in ps				*/
/*--------------------------------------------------------------*/

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>
#include <math.h>	// Temporary, for fabs()
#include "hash.h"	// For net hash table
 
#define LIB_LINE_MAX  65535

int fileCurrentLine;

// Analysis types --- note that maximum flop-to-flop delay
// requires calculating minimum clock skew time, and vice
// versa, so it is necessary that these have TRUE/FALSE
// values.

#define MINIMUM_TIME	0
#define MAXIMUM_TIME	1

// Multiple-use definition
#define	UNKNOWN		-1

// Sections of liberty file
#define INIT		0
#define LIBBLOCK	1
#define CELLDEF		2
#define PINDEF		3
#define FLOPDEF		4
#define LATCHDEF	5
#define TIMING		6

// Sections of verilog file
#define MODULE		0
#define IOLIST		1
#define GATELIST	2
#define INSTANCE	3
#define INSTPIN		4
#define PINCONN		5

// Pin types (these are masks---e.g., a pin can be an INPUT and a CLOCK)
#define INPUT		0x01	// The default
#define OUTPUT		0x02
#define DFFCLK		0x04
#define DFFIN		0x08	// Flop input
#define DFFOUT		0x10	// Flop output
#define DFFSET		0x20	// Flop set (preset)
#define DFFRST		0x40	// Flop reset (clear)
#define LATCHIN		0x80	// Latch input
#define LATCHEN		0x100	// Latch enable
#define LATCHOUT	0x200	// Latch output

// Timing type (for tables)

#define TIMING_PROP_TRANS	0
#define TIMING_HOLD		1
#define TIMING_SETUP		2
#define TIMING_SET_RESET	3
#define TIMING_RECOVERY		4
#define TIMING_REMOVAL		5
#define TIMING_TRISTATE		6

// A few short-hand definitions
#define DFF_ALL_IN	(DFFCLK | DFFIN | DFFSET | DFFRST)	
#define LATCH_ALL_IN	(LATCHIN | LATCHEN)

#define DFF_IN_NOT_CLK	(DFFIN | DFFSET | DFFRST)	
#define LATCH_IN_NOT_EN	(LATCHIN)

#define REGISTER_IN	(DFF_ALL_IN | LATCH_ALL_IN)
#define REG_IN_NOT_CLK	(DFF_IN_NOT_CLK | LATCH_IN_NOT_EN)

#define IOMASK		0x03
#define DFFMASK		0x7c
#define LATCHMASK	0x180

// Pin sense
#define SENSE_UNKNOWN	0	// Sense unknown
#define SENSE_NONE	1	// Non-unate
#define SENSE_POSITIVE	2	// Positive-unate
#define SENSE_NEGATIVE	3	// Negative-unate

// Signal transition direction
#define EDGE_UNKNOWN	0
#define RISING		1
#define FALLING		2
#define EITHER		3

// Function translation
#define GROUPBEGIN	1
#define GROUPEND	2
#define SIGNAL		3
#define OPERATOR	4
#define XOPERATOR	5
#define SEPARATOR	6

// Net types
#define	NET		0x00	// Ordinary net (default)
#define	CLOCK		0x01	// Clock net (path start)
#define OUTTERM		0x02	// Module output
#define	ASYNC		0x04	// Asynchronous set/reset
#define TERMINAL	0x08	// DFF input (path terminal)
#define LATCHTERM	0x10	// Latch input (dependent path terminal)
#define ENABLE		0x20	// Latch enable (path start)

// Cell types
#define GATE		0x00	// Combinatorial gate (default)
#define DFF		0x01	// Flip-flop (shared bit field)
#define CLK_SENSE_MASK	0x02	// Clock edge mask (0=positive, 1=negative)
#define RST_MASK	0x04	// Reset mask (0=no reset, 1=reset)
#define RST_SENSE_MASK	0x08	// Reset edge mask (0=positive, 1=negative)
#define SET_MASK	0x10	// Set mask (0=no set, 1=set)
#define SET_SENSE_MASK	0x20	// Set edge mask (0=positive, 1=negative)
#define LATCH		0x40	// Latch type
#define EN_SENSE_MASK	0x80	// Latch enable edge mask (0=positive, 1=negative)

// Some names for cell types based on masks

#define DFFCP		0x01	// Pos clock
#define DFFCN		0x03	// Neg clock
#define DFFCPRP		0x05	// Pos clock, Pos reset
#define DFFCNRP		0x07	// Neg clock, Pos reset
#define DFFCPRN		0x0d	// Pos clock, Neg reset
#define DFFCNRN		0x0f	// Neg clock, Neg reset
#define DFFCPSP		0x11	// Pos clock, Pos set
#define DFFCNSP		0x13	// Neg clock, Pos set
#define DFFCPRPSP	0x15	// Pos clock, Pos reset, Pos set
#define DFFCNRPSP	0x17	// Neg clock, Pos reset, Pos set
#define DFFCPRNSP	0x1d	// Pos clock, Neg reset, Pos set
#define DFFCNRNSP	0x1f	// Neg clock, Neg reset, Pos set
#define DFFCPSN		0x31	// Pos clock, Neg set
#define DFFCNSN		0x33	// Neg clock, Neg set
#define DFFCPRPSN	0x35	// Pos clock, Pos reset, Neg set
#define DFFCNRPSN	0x37	// Neg clock, Pos reset, Neg set
#define DFFCPRNSN	0x3d	// Pos clock, Neg reset, Neg set
#define DFFCNRNSN	0x3f	// Neg clock, Neg reset, Neg set

/*--------------------------------------------------------------*/
/* Liberty file database					*/
/*--------------------------------------------------------------*/

// Types of array

// Array type 1:  Propagation time
#define OUTPUT_CAP		0	
#define TRANSITION_TIME		1

// Array type 2:  Setup, hold, recovery, removal times
#define RELATED_TIME		2
#define CONSTRAINED_TIME	3

typedef struct _lutable *lutableptr;

typedef struct _lutable {
    char *name;
    char invert;	// 0 if times x caps, 1 if caps x times
    int  var1;		// Type of array in index1
    int  var2;		// Type of array in index2
    int  size1;		// Number of entries in time array
    int  size2;		// Number of entries in cap (or constrained timing) array
    union {
        double *times;	// Time array (units ps)
        double *rel;	// Related pin transition time array (units ps)
    } idx1;
    union {
        double *caps;	// Cap array (units fF)
        double *cons;	// Constrained pin transition time array (units ps)
    } idx2;
    double *values;	// Matrix of values (used locally, not for templates)
    lutableptr next;
} lutable;

typedef struct _pin *pinptr;
typedef struct _cell *cellptr;

typedef struct _pin {
    char *name;
    short type;

    double capr;	// Capacitance for rising input
    double capf;	// Capacitance for falling input (optional)

    short sense;	// Sense (positive-unate, negative-unate, non-unate)
    lutable *propdelr;	// Reference table for rising output prop delay relative to driver
    lutable *propdelf;	// Reference table for falling output prop delay relative to driver
    lutable *transr;	// Reference table for transition rise time
    lutable *transf;	// Reference table for transition fall time

    cellptr refcell;	// Pointer back to parent cell

    pinptr next;
} pin;

typedef struct _cell {
    short type;
    char *name;
    char *function;
    pin	 *pins;		/* List of input pins with timing info */
    double area;
    double maxtrans;	/* Maximum transition time */
    double maxcap;	/* Maximum allowable load */
    cellptr next;
} cell;

/*--------------------------------------------------------------*/
/* Verilog netlist database					*/
/*--------------------------------------------------------------*/

typedef struct _net *netptr;
typedef struct _connect *connptr;
typedef struct _delaydata *ddataptr;

typedef struct _net {
   char *name;
   connptr driver;
   short type;
   int fanout;
   connptr *receivers;
   double loadr;	/* Total load capacitance for rising input */
   double loadf;	/* Total load capacitance for falling input */
   netptr next;
} net;

typedef struct _instance *instptr;

typedef struct _connect {
   double   metric;		/* Delay metric at connection */
   instptr  refinst;
   pinptr   refpin;
   netptr   refnet;
   ddataptr tag;		/* Tag value for checking for loops and endpoints */
   double   *prvector;		/* Prop delay rising (at load condition) vector */
   double   *pfvector;		/* Prop delay falling (at load condition) vector */
   double   *trvector;		/* Transition time rising (at load condition) vector */
   double   *tfvector;		/* Transition time falling (at load condition) vector */
   connptr  next;
} connect;

typedef struct _instance {
   char *name;
   cellptr refcell;
   connptr in_connects;
   connptr out_connects;
   instptr next;
} instance;

// Linked list of delays (backtrace to source)

typedef struct _btdata *btptr;

typedef struct _btdata {
   double  delay;	/* Propagation delay to this point */
   double  trans;	/* Transition time at this point */
   short   dir;		/* Edge direction at this point */
   connptr receiver;	/* Receiver connection at end of path */
   int	   refcnt;	/* Reference counter for backtrace data */
   btptr   next;	/* Path of propagation */
} btdata;

// Linked list of backtrace records

typedef struct _delaydata {
   double delay;	/* Total delay, including setup and clock skew */
   double trans;	/* Transition time at destination, used to find setup */
   btptr backtrace;
   ddataptr  next;
} delaydata;

// Linked list of connection pointers
// (Much like delaydata, but without all the timing information)

typedef struct _connlist *connlistptr;

typedef struct _connlist {
   connptr connection;
   connlistptr next;
} connlist;

/* Global variables */

unsigned char verbose;		/* Level of debug output generated */
unsigned char exhaustive;	/* Exhaustive search mode */

/*--------------------------------------------------------------*/
/* Grab a token from the input					*/
/* Return the token, or NULL if we have reached end-of-file.	*/
/*--------------------------------------------------------------*/

char *
advancetoken(FILE *flib, char delimiter)
{
    static char token[LIB_LINE_MAX];
    static char line[LIB_LINE_MAX];
    static char *linepos = NULL;

    char *lineptr = linepos;
    char *lptr, *tptr;
    char *result;
    int commentblock, concat, nest;

    commentblock = 0;
    concat = 0;
    nest = 0;
    while (1) {		/* Keep processing until we get a token or hit EOF */

	if (lineptr != NULL && *lineptr == '/' && *(lineptr + 1) == '*') {
	    commentblock = 1;
	}

	if (commentblock == 1) {
	    if ((lptr = strstr(lineptr, "*/")) != NULL) {
		lineptr = lptr + 2;
		commentblock = 0;
	    }
	    else lineptr = NULL;
	}

	if (lineptr == NULL || *lineptr == '\n' || *lineptr == '\0') {
	    result = fgets(line, LIB_LINE_MAX, flib);
	    fileCurrentLine++;
	    if (result == NULL) return NULL;

	    /* Keep pulling stuff in if the line ends with a continuation character */
 	    lptr = line;
	    while (*lptr != '\n' && *lptr != '\0') {
		if (*lptr == '\\') {
		    // To be considered a line continuation marker, there must be
		    // only whitespace or newline between the backslash and the
		    // end of the string.
		    char *eptr = lptr + 1;
		    while (isspace(*eptr)) eptr++;
		    if (*eptr == '\0') {
		        result = fgets(lptr, LIB_LINE_MAX - (lptr - line), flib);
		        fileCurrentLine++;
		        if (result == NULL) break;
		    }
		    else
			lptr++;
		}
		else
		    lptr++;
	    }	
	    if (result == NULL) return NULL;
	    lineptr = line;
	}

	if (commentblock == 1) continue;

	while (isspace(*lineptr)) lineptr++;
	if (concat == 0)
	    tptr = token;

	// Find the next token and return just the token.  Update linepos
	// to the position just beyond the token.  All delimiters like
	// parentheses, quotes, etc., are returned as single tokens

	// If delimiter is declared, then we stop when we reach the
	// delimiter character, and return all the text preceding it
	// as the token.  If delimiter is 0, then we look for standard
	// delimiters, and separate them out and return them as tokens
	// if found.

	while (1) {
	    if (*lineptr == '\n' || *lineptr == '\0')
		break;
	    if (*lineptr == '/' && *(lineptr + 1) == '*')
		break;
	    if (delimiter != 0 && *lineptr == delimiter) {
		if (nest > 0)
		    nest--;
		else
		    break;
	    }

	    // Watch for nested delimiters!
	    if (delimiter == '}' && *lineptr == '{') nest++;
	    if (delimiter == ')' && *lineptr == '(') nest++;

	    if (delimiter == 0)
		if (*lineptr == ' ' || *lineptr == '\t')
		    break;

	    if (delimiter == 0) {
		if (*lineptr == '(' || *lineptr == ')') {
		    if (tptr == token) *tptr++ = *lineptr++;
		    break;
		}
		if (*lineptr == '{' || *lineptr == '}') {
		    if (tptr == token) *tptr++ = *lineptr++;
		    break;
		}
		if (*lineptr == '\"' || *lineptr == ':' || *lineptr == ';') {
		    if (tptr == token) *tptr++ = *lineptr++;
		    break;
		}
	    }

	    *tptr++ = *lineptr++;
	}
	*tptr = '\0';
	if ((delimiter != 0) && (*lineptr != delimiter))
	    concat = 1;
	else if ((delimiter != 0) && (*lineptr == delimiter))
	    break;
	else if (tptr > token)
	    break;
    }
    if (delimiter != 0) lineptr++;

    while (isspace(*lineptr)) lineptr++;
    linepos = lineptr;

    // Final:  Remove trailing whitespace
    tptr = token + strlen(token) - 1;
    while (isspace(*tptr)) {
	*tptr = '\0';
	tptr--;
    }
    return token;
}

/*--------------------------------------------------------------*/
/* Parse a pin name.  Check if the cell has a pin of that name,	*/
/* and if not, add the pin to the cell, giving it default	*/
/* values.  The pin name may contain quotes, parentheses, or	*/
/* negations ("!" or "'");  these should be ignored.		*/
/*--------------------------------------------------------------*/

pinptr parse_pin(cellptr newcell, char *token, short sense_predef)
{
    pinptr newpin, lastpin;
    char *pinname, *sptr;

    // Advance to first legal pin name character

    pinname = token;
    while (isspace(*pinname) || (*pinname == '\'') || (*pinname == '\"') ||
		(*pinname == '!') || (*pinname == '(') || (*pinname == ')'))
	pinname++;

    sptr = pinname;
    while (*sptr != '\0') {
	if (isspace(*sptr) || (*sptr == '\'') || (*sptr == '\"') ||
		(*sptr == '!') || (*sptr == '(') || (*sptr == ')')) {
	    *sptr = '\0';
	    break;
	}
	sptr++;
    }

    // Check if pin was already defined

    lastpin = NULL;
    newpin = newcell->pins;
    while (newpin) {
	lastpin = newpin;
	if (!strcmp(newpin->name, pinname))
	    return newpin;
	newpin = newpin->next;
    }

    // Pin was not defined, so create a new one and add it to the cell
    // at the end of the cell's pin list.

    newpin = (pin *)malloc(sizeof(pin));
    newpin->name = strdup(pinname);
    newpin->next = NULL;

    if (lastpin != NULL)
	lastpin->next = newpin;
    else
	newcell->pins = newpin;

    newpin->type = INPUT;	// The default; modified later, if not an input
    newpin->capr = 0.0;
    newpin->capf = 0.0;
    newpin->sense = sense_predef;  // Again, modified later if not true.
    newpin->propdelr = NULL;
    newpin->propdelf = NULL;
    newpin->transr = NULL;
    newpin->transf = NULL;
    newpin->refcell = newcell;	// Create link back to cell
    return newpin;
}

/*--------------------------------------------------------------*/
/* Create a new net record					*/
/*--------------------------------------------------------------*/

netptr create_net(netptr *netlist) {

    netptr newnet;

    newnet = (netptr)malloc(sizeof(net));
    newnet->name = NULL;
    newnet->next = *netlist;
    *netlist = newnet;
    newnet->driver = NULL;
    newnet->fanout = 0;
    newnet->receivers = NULL;
    newnet->loadr = 0.0;
    newnet->loadf = 0.0;
    newnet->type = NET;

    return newnet;
}

/*----------------------------------------------------------------------*/
/* Interpolate or extrapolate a vector from a time vs. capacitance	*/
/* lookup table.							*/
/*----------------------------------------------------------------------*/

double *table_collapse(lutableptr tableptr, double load)
{
    double *vector;
    double cfrac, vlow, vhigh;
    int i, j;

    vector = (double *)malloc(tableptr->size1 * sizeof(double));

    // If the table is 1-dimensional, then just return a copy of the table.
    if (tableptr->size2 <= 1) {
       for (i = 0; i < tableptr->size1; i++) {
	  *(vector + i) = *(tableptr->values + i);
       }
       return vector;
    }

    // Find cap load index entries bounding  "load", or the two nearest
    // entries, if extrapolating

    if (load < tableptr->idx2.caps[0])
	j = 1;
    else if (load >= tableptr->idx2.caps[tableptr->size2 - 1])
	j = tableptr->size2 - 1;
    else {
	for (j = 0; j < tableptr->size2; j++)
	    if (tableptr->idx2.caps[j] > load)
		break;
    }

    cfrac = (load - tableptr->idx2.caps[j - 1]) /
			(tableptr->idx2.caps[j] - tableptr->idx2.caps[j - 1]);

    for (i = 0; i < tableptr->size1; i++) {

	// Interpolate value at cap load for each transition value

	vlow = *(tableptr->values + i * tableptr->size1 + (j - 1));
	vhigh = *(tableptr->values + i * tableptr->size1 + j);
	*(vector + i) = vlow + (vhigh - vlow) * cfrac;
    }
    return vector;
}

/*----------------------------------------------------------------------*/
/* Interpolate/extrapolate a delay or transition value from a vector of	*/
/* values at a known output load.  The original full 2D table contains	*/
/* the transition time index values.					*/
/*----------------------------------------------------------------------*/

double vector_get_value(lutableptr tableptr, double *vector, double trans)
{
    int i;
    double tfrac, vlow, vhigh, value;

    // Find time index entries bounding  "trans", or the two nearest
    // entries, if extrapolating

    if (trans < tableptr->idx1.times[0])
	i = 1;
    else if (trans >= tableptr->idx1.times[tableptr->size1 - 1])
	i = tableptr->size1 - 1;
    else {
	for (i = 0; i < tableptr->size1; i++)
	    if (tableptr->idx1.times[i] > trans)
		break;
    }

    // Compute transition time as a fraction of the nearest table indexes
    // for transition times

    tfrac = (trans - tableptr->idx1.times[i - 1]) /
		(tableptr->idx1.times[i] - tableptr->idx1.times[i - 1]);

    // Interpolate value
    vlow = *(vector + (i - 1));
    vhigh = *(vector + i);
    value = vlow + (vhigh - vlow) * tfrac;
    return value;
}

/*----------------------------------------------------------------------*/
/* Interpolate or extrapolate a value from a related time vs.		*/
/* constrained time lookup table.					*/
/*----------------------------------------------------------------------*/

double binomial_get_value(lutableptr tableptr, double rtrans, double ctrans)
{
    int i, j;
    double rfrac, cfrac, vlow, vhigh, valuel, valueh, value;

    /* Tables have been arranged such that idx1 is related time,	*/
    /* idx2 is constrained time */

    // Find time index entries bounding  "rtrans", or the two nearest
    // entries, if extrapolating

    if (rtrans < tableptr->idx1.rel[0])
	i = 1;
    else if (rtrans >= tableptr->idx1.rel[tableptr->size1 - 1])
	i = tableptr->size1 - 1;
    else {
	for (i = 0; i < tableptr->size1; i++)
	    if (tableptr->idx1.rel[i] > rtrans)
		break;
    }

    // Compute transition time as a fraction of the nearest table indexes
    // for transition times

    rfrac = (rtrans - tableptr->idx1.rel[i - 1]) /
		(tableptr->idx1.rel[i] - tableptr->idx1.rel[i - 1]);

    // 1-dimensional computation, if this table is 1-dimensional

    if (tableptr->size2 == 0) {
	vlow = *(tableptr->values + (i - 1));
	vhigh = *(tableptr->values + i);
	value = vlow + (vhigh - vlow) * rfrac;
	return value;
    }

    // Find cons index entries bounding  "ctrans", or the two nearest
    // entries, if extrapolating

    if (ctrans < tableptr->idx2.cons[0])
	j = 1;
    else if (ctrans >= tableptr->idx2.cons[tableptr->size2 - 1])
	j = tableptr->size2 - 1;
    else {
	for (j = 0; j < tableptr->size2; j++)
	    if (tableptr->idx2.cons[j] > ctrans)
		break;
    }

    // Compute cons transition as a fraction of the nearest table indexes for cons

    cfrac = (ctrans - tableptr->idx2.cons[j - 1]) /
			(tableptr->idx2.cons[j] - tableptr->idx2.cons[j - 1]);

    // Interpolate value at cons lower bound
    vlow = *(tableptr->values + (i - 1) * tableptr->size1 + (j - 1));
    vhigh = *(tableptr->values + i * tableptr->size1 + (j - 1));
    valuel = vlow + (vhigh - vlow) * rfrac;

    // Interpolate value at cons upper bound
    vlow = *(tableptr->values + (i - 1) * tableptr->size1 + j);
    vhigh = *(tableptr->values + i * tableptr->size1 + j);
    valueh = vlow + (vhigh - vlow) * rfrac;

    // Final interpolation (binomial interpolation)
    value = valuel + (valueh - valuel) * cfrac;
    return value;
}

/*----------------------------------------------------------------------*/
/* Determine how the sense of a signal changes going from a gate's 	*/
/* input to its output.  If the gate's input pin is positive unate	*/
/* relative to the gate output, then the signal sense remains the same.	*/
/* If it is negative unate, then the signal sense inverts.  If it is	*/
/* non-unate, then the signal sense becomes non-unate, and we calculate	*/
/* timing for both edges from that point forward, always accepting the	*/
/* maximum time.							*/
/*----------------------------------------------------------------------*/

short calc_dir(pinptr testpin, short dir)
{
    short outdir;

    outdir = UNKNOWN;
    if (testpin == NULL) return dir;

    switch(dir) {
	case RISING:
	    if (testpin->sense == SENSE_POSITIVE)
		outdir = RISING;	/* rising input, rising output */
	    else if (testpin->sense = SENSE_NEGATIVE)
		outdir = FALLING;	/* rising input, falling output */
	    else
		outdir = EITHER;	/* output can be rising or falling */
	    break;
	case FALLING:
	    if (testpin->sense == SENSE_POSITIVE)
		outdir = FALLING;	/* falling input, falling output */
	    else if (testpin->sense = SENSE_NEGATIVE)
		outdir = RISING;	/* falling input, rising output */
	    else
		outdir = EITHER;		/* output can be rising or falling */
	    break;
	case EITHER:
	    outdir = EITHER;		/* output can be rising or falling */
	    break;
    }
    return outdir;
}

/*----------------------------------------------------------------------*/
/* Calculate the propagation delay from "testpin" to the output		*/
/* of the gate to which "testpin" is an input.				*/
/*									*/
/* "sense" is a pointer to the sense at the input.  SENSE_POSITIVE	*/
/* indicates a rising edge at the pin, SENSE_NEGATIVE indicates a	*/
/* falling edge at the pin.  "sense" is updated to indicate if the	*/
/* output transition is rising, falling, or unknown (SENSE_NONE).	*/
/*									*/
/* "loadnet" is a pointer to the net connected to the cell instance's	*/
/* output pin.  Load values will be taken from this net, depending on	*/
/* the sense of the output.						*/
/*									*/
/* "testpin" is the pin receiving the input signal, and the pin	record	*/
/* containing the relevant timing tables.				*/
/*----------------------------------------------------------------------*/

double calc_prop_delay(double trans, connptr testconn, short sense, char minmax)
{
    pinptr testpin;
    double propdelayr, propdelayf;

    propdelayr = 0.0;
    propdelayf = 0.0;

    testpin = testconn->refpin;
    if (testpin == NULL) return 0.0;

    if (sense != SENSE_NEGATIVE) {
	if (testconn->prvector)
	    propdelayr = vector_get_value(testpin->propdelr, testconn->prvector, trans);
	if (sense == SENSE_POSITIVE) return propdelayr;
    }

    if (sense != SENSE_POSITIVE) {
	if (testconn->pfvector)
	    propdelayf = vector_get_value(testpin->propdelf, testconn->pfvector, trans);
	if (sense == SENSE_NEGATIVE) return propdelayf;
    }

    if (minmax == MAXIMUM_TIME)
	return (propdelayr > propdelayf) ? propdelayr : propdelayf;
    else
	return (propdelayr < propdelayf) ? propdelayr : propdelayf;
}

/*----------------------------------------------------------------------*/
/* Calculate the transition time from "testpin" to the output		*/
/* of the gate to which "testpin" is an input.	This is equivalent to	*/
/* the propagation delay calculation routine above, apart from using	*/
/* the lookup tables for transition time instead of propagation delay.	*/
/*----------------------------------------------------------------------*/

double calc_transition(double trans, connptr testconn, short sense, char minmax)
{
    pinptr testpin;
    double transr, transf;

    testpin = testconn->refpin;
    if (testpin == NULL) return 0.0;

    transr = 0.0;
    transf = 0.0;

    if (sense != SENSE_NEGATIVE) {
	if (testconn->trvector)
	    transr = vector_get_value(testpin->transr, testconn->trvector, trans);
	if (sense == SENSE_POSITIVE) return transr;
    }

    if (sense != SENSE_POSITIVE) {
	if (testconn->tfvector)
	    transf = vector_get_value(testpin->transf, testconn->tfvector, trans);
	if (sense == SENSE_NEGATIVE) return transf;
    }

    if (minmax == MAXIMUM_TIME)
	return (transr > transf) ? transr : transf;
    else
	return (transr < transf) ? transr : transf;
}

/*----------------------------------------------------------------------*/
/* Calculate the hold time for a flop input "testpin" relative to the	*/
/* flop clock, where "trans" is the transition time of the signal at	*/
/* "testpin", and "clktrans" is the transition time of the clock	*/
/* signal at the clock pin.  "sense" is the sense of the input signal	*/
/* at "testpin".							*/
/*----------------------------------------------------------------------*/

double calc_hold_time(double trans, pinptr testpin, double clktrans, short sense,
		char minmax)
{
    double holdr, holdf;

    if (testpin == NULL) return 0.0;

    holdr = 0.0;
    holdf = 0.0;

    if (sense != SENSE_NEGATIVE) {
	if (testpin->transr)
	    holdr = binomial_get_value(testpin->transr, trans, clktrans);
	if (sense == SENSE_POSITIVE) return holdr;
    }

    if (sense != SENSE_POSITIVE) {
	if (testpin->transf)
	    holdf = binomial_get_value(testpin->transf, trans, clktrans);
	if (sense == SENSE_NEGATIVE) return holdf;
    }

    if (minmax == MAXIMUM_TIME)
	return (holdr > holdf) ? holdr : holdf;
    else
	return (holdr < holdf) ? holdr : holdf;
}

/*----------------------------------------------------------------------*/
/* Calculate the setup time for a flop input "testpin" relative to the	*/
/* flop clock, where "trans" is the transition time of the signal at	*/
/* "testpin", and "clktrans" is the transition time of the clock	*/
/* signal at the clock pin.  "sense" is the sense of the input signal	*/
/* at "testpin".							*/
/*----------------------------------------------------------------------*/

double calc_setup_time(double trans, pinptr testpin, double clktrans, short sense,
		char minmax)
{
    double setupr, setupf;

    if (testpin == NULL) return 0.0;

    setupr = 0.0;
    setupf = 0.0;

    if (sense != SENSE_NEGATIVE) {
	if (testpin->propdelr)
	    setupr = binomial_get_value(testpin->propdelr, trans, clktrans);
	if (sense == SENSE_POSITIVE) return setupr;
    }

    if (sense != SENSE_POSITIVE) {
	if (testpin->propdelf)
	    setupf = binomial_get_value(testpin->propdelf, trans, clktrans);
	if (sense == SENSE_NEGATIVE) return setupf;
    }

    if (minmax == MAXIMUM_TIME)
	return (setupr > setupf) ? setupr : setupf;
    else
	return (setupr < setupf) ? setupr : setupf;
}

/*--------------------------------------------------------------*/
/* Find the path from a clock back to all inputs or flop	*/
/* outputs.  This list will be used to find nodes that are	*/
/* common to other clocks.					*/
/*--------------------------------------------------------------*/

void
find_clock_source(connptr testlink, btptr *clocklist, short dir)
{
    netptr clknet;
    connptr driver, iinput;
    instptr iupstream;
    btptr newclock;
    short newdir;

    /* Add this connection record to clocklist */

    newclock = (btptr)malloc(sizeof(btdata));
    newclock->delay = 0.0;
    newclock->trans = 0.0;
    newclock->dir = dir;
    newclock->refcnt = 1;
    newclock->receiver = testlink;
    newclock->next = *clocklist;
    *clocklist = newclock;

    clknet = testlink->refnet;
    driver = clknet->driver;
    if (driver == NULL) return;			/* Reached a module input */
    iupstream = driver->refinst;

    if (iupstream == NULL) return;		/* Not supposed to happen? */
    if (driver->refpin->type & DFFOUT) return;	/* Reached a flop output */
    if (driver->refpin->type & LATCHOUT) return; /* Reached a latch output */

    for (iinput = iupstream->in_connects; iinput; iinput = iinput->next) {
	newdir = calc_dir(iinput->refpin, dir);
	find_clock_source(iinput, clocklist, newdir);
    }
}

/*--------------------------------------------------------------*/
/* Find a net that is common to both "clocklist" and		*/
/* "clock2list".  If one exists, return a pointer to the	*/
/* connection.							*/
/*--------------------------------------------------------------*/

netptr find_common_clock(btptr clocklist, btptr clock2list)
{
    btptr srch1ptr, srch2ptr;

    for (srch1ptr = clocklist; srch1ptr; srch1ptr = srch1ptr->next)
        for (srch2ptr = clock2list; srch2ptr; srch2ptr = srch2ptr->next)
	    if (srch1ptr->receiver->refnet == srch2ptr->receiver->refnet)
		return srch1ptr->receiver->refnet;

    return NULL;
}

/*--------------------------------------------------------------*/
/* Determine the delay to a clock pin from the farthest point	*/
/* back in the network, either to an input pin or the output of	*/
/* another flop (e.g., a ripple counter).  If there is more	*/
/* than one such source (which can happen with, say, a gated	*/
/* clock, because this routine will not differentiate between	*/
/* the clock signal and the gating signal), then all sources	*/
/* recorded (it is only necessary to find a common root of all	*/
/* other related clocks downstream).				*/
/*								*/
/* This is a recursive routine, continuing to find all delays	*/
/* through the circuit until it reaches "terminal".  The 	*/
/* "delaylist" linked list is not modified by this routine.	*/
/*--------------------------------------------------------------*/

void
find_clock_delay(int dir, double delay, double trans, connptr receiver,
		btptr clocklist, connptr terminal, char minmax) {

    pinptr  testpin;
    netptr  loadnet;
    cellptr testcell;
    instptr testinst;
    btptr   testbtdata, newbtdata;
    double  newdelayr, newdelayf, newtransr, newtransf;
    short   outdir;
    int	    i;

    testpin = receiver->refpin;

    // Stop when receiver matches terminal.

    if (receiver != terminal) {

	testinst = receiver->refinst;
	testcell = (testpin) ? testpin->refcell : NULL;

	// Don't follow signal through any DFF pins
	if (testcell && (testcell->type & DFF)) return;

	// Compute delay from gate input to output

	outdir = calc_dir(testpin, dir);
	if (outdir & RISING) {
	    newdelayr = delay + calc_prop_delay(trans, receiver, RISING, minmax);
	    newtransr = calc_transition(trans, receiver, RISING, minmax);
	}
	if (outdir & FALLING) {
	    newdelayf = delay + calc_prop_delay(trans, receiver, FALLING, minmax);
	    newtransf = calc_transition(trans, receiver, FALLING, minmax);
	}

	loadnet = (testinst) ? testinst->out_connects->refnet : NULL;
	if (loadnet != NULL) {
	    for (i = 0; i < loadnet->fanout; i++) {
		if (outdir & RISING)
		    find_clock_delay(RISING, newdelayr, newtransr, loadnet->receivers[i],
				clocklist, terminal, minmax);
		if (outdir & FALLING)
		    find_clock_delay(FALLING, newdelayf, newtransf, loadnet->receivers[i],
				clocklist, terminal, minmax);
	    }
	}
    }
    else {

	/* Determine if receiver is in clocklist */
	for (testbtdata = clocklist; testbtdata; testbtdata = testbtdata->next) {
	    if (testbtdata->receiver == receiver) {
		/* Is delay greater than that already recorded?  If so, replace it */
		if (minmax == MAXIMUM_TIME) {
		    if (delay > testbtdata->delay) {
			testbtdata->delay = delay;
			testbtdata->trans = trans;
			testbtdata->dir = dir;
		    }
		}
		else {
		    if (delay < testbtdata->delay) {
			testbtdata->delay = delay;
			testbtdata->trans = trans;
			testbtdata->dir = dir;
		    }
		}
		break;
	    }
	}
    }
}

/*--------------------------------------------------------------*/
/* Determine the delay from input to output through a gate	*/
/*								*/
/* This is a recursive routine, continuing to find all delays	*/
/* through the circuit until it reaches a terminal or flop	*/
/* input.  It is similar to find_clock_delay, but stops on all	*/
/* terminal points found in the path, rather than stopping on	*/
/* a specific connection.					*/
/*								*/
/* Also unlike find_clock_delay, the routine keeps a running	*/
/* record of the path followed from the source, as a character	*/
/* string.  When a terminal is found, the path and delay are	*/
/* saved and added to "delaylist".  After the recursive search,	*/
/* "delaylist" contains a list of all paths starting from the	*/
/* original connection "receiver" and ending on a clock or an	*/
/* output pin.  Where multiple paths exist between source and	*/
/* destination, only the path with the longest delay is kept.	*/
/*								*/
/* Return the number of new paths recorded.			*/
/*--------------------------------------------------------------*/

int find_path_delay(int dir, double delay, double trans, connptr receiver,
		btptr backtrace, ddataptr *delaylist, char minmax) {

    pinptr   testpin;
    netptr   loadnet;
    cellptr  testcell;
    instptr  testinst;
    btptr    newbtdata, freebt, testbt;
    ddataptr testddata, newddata;
    double   newdelayr, newdelayf, newtransr, newtransf;
    short    outdir;
    char     replace;
    int	     i, numpaths;

    numpaths = 0;
    testpin = receiver->refpin;

    // Prevent exhaustive search by stopping on a metric.  Note that the
    // nonlinear table-based delay data requires an exhaustive search;
    // generally, the tables can be assumed to be monotonic, in which case
    // we can stop if the delay is less than the greatest delay recorded
    // at this point AND the transition time is less than the transition
    // time recorded along with that delay.  A more relaxed metric is to
    // use the delay plus the transition time, and an even more relaxed
    // metric is to use only the delay.  Any relaxing of the metric
    // implies that the final result may not be the absolute maximum delay,
    // although it will typically vary by less than an average gate delay.

    if (!exhaustive) {
	if (minmax == MAXIMUM_TIME) {
	    if (delay <= receiver->metric)
		return numpaths;
	}
	else {
	    if (delay >= receiver->metric)
		return numpaths;
	}
    }

    // Check for a logic loop, and truncate the path to avoid infinite
    // looping in the path search.

    if (receiver->tag == (ddataptr)(-1)) return numpaths;
    else if (receiver->tag == NULL) receiver->tag = (ddataptr)(-1);

    // Record this position and delay/transition information

    newbtdata = (btptr)malloc(sizeof(btdata));
    newbtdata->delay = delay;
    newbtdata->trans = trans;
    newbtdata->dir = dir;
    newbtdata->receiver = receiver;
    newbtdata->refcnt = 1;
    newbtdata->next = backtrace;

    // Stop when we hit a module output pin or any flop/latch input.
    // We must allow the routine to pass through the 1st register clock (on the first
    // time through, backtrace is NULL).

    if ((backtrace == NULL) || (testpin && ((testpin->type & REGISTER_IN) == 0))) {

	testinst = receiver->refinst;
	testcell = (testpin) ? testpin->refcell : NULL;

	// Compute delay from gate input to output

	outdir = calc_dir(testpin, dir);
	if (outdir & RISING) {
	    newdelayr = delay + calc_prop_delay(trans, receiver, RISING, minmax);
	    newtransr = calc_transition(trans, receiver, RISING, minmax);
	}
	if (outdir & FALLING) {
	    newdelayf = delay + calc_prop_delay(trans, receiver, FALLING, minmax);
	    newtransf = calc_transition(trans, receiver, FALLING, minmax);
	}

	loadnet = (testinst) ? testinst->out_connects->refnet : receiver->refnet;
	for (i = 0; i < loadnet->fanout; i++) {
	    if (outdir & RISING)
		numpaths += find_path_delay(RISING, newdelayr, newtransr,
			loadnet->receivers[i], newbtdata, delaylist, minmax);
	    if (outdir & FALLING)
		numpaths += find_path_delay(FALLING, newdelayf, newtransf,
			loadnet->receivers[i], newbtdata, delaylist, minmax);
	}
	receiver->tag = NULL;
    }
    else {

	/* Is receiver already in delaylist? */
	if ((receiver->tag != (ddataptr)(-1)) && (receiver->tag != NULL)) {

	    /* Position in delaylist is recorded in tag field */
	    testddata = receiver->tag;

	    if (testddata->backtrace->receiver == receiver) {
		replace = 0;
		if (minmax == MAXIMUM_TIME) {
		    /* Is delay greater than that already recorded?  If so, replace it */
		    if (delay > testddata->backtrace->delay)
			replace = 1;
		}
		else {
		    /* Is delay less than that already recorded?  If so, replace it */
		    if (delay < testddata->backtrace->delay)
			replace = 1;
		}
		if (replace) {
	
		    /* Remove the existing path record and replace it */
		    while (testddata->backtrace != NULL) {
			freebt = testddata->backtrace;
			testddata->backtrace = testddata->backtrace->next;
		 	freebt->refcnt--;
			if (freebt->refcnt <= 0) free(freebt);
		    }
		    testddata->backtrace = newbtdata;

		    /* Increment the refcounts along the backtrace */
		    for (testbt = newbtdata; testbt; testbt = testbt->next)
			testbt->refcnt++;
		}
	    }
	    else
		fprintf(stderr, "ERROR:  Bad endpoint tag!\n");
	}
	else
	    testddata = NULL;

	// If we have found a propagation path from source to dest,
	// record it in delaylist.

	if (testddata == NULL) {
	    numpaths++;
	    newddata = (ddataptr)malloc(sizeof(delaydata));
	    newddata->delay = 0.0;
	    newddata->trans = 0.0;
	    newddata->backtrace = newbtdata;
	    newddata->next = *delaylist;
	    *delaylist = newddata;

	    /* Mark the receiver as having been visited */
	    receiver->tag = *delaylist;

	    /* Increment the refcounts along the backtrace */
	    for (testbt = newbtdata; testbt; testbt = testbt->next)
		testbt->refcnt++;
	}

    }

    receiver->metric = delay;
    newbtdata->refcnt--;
    if (newbtdata->refcnt <= 0) free(newbtdata);
    return numpaths;
}

/*--------------------------------------------------------------*/
/* Search the list "clocklist" for all points that are module	*/
/* inputs or flop outputs, and compute the worst-case		*/
/* transition time downstream at testlink.			*/
/* 								*/
/* Return a pointer to the ddataptr entry that contains the	*/
/* worst-case transition time.					*/
/*--------------------------------------------------------------*/

btptr find_clock_transition(btptr clocklist, connptr testlink, short dir, char minmax)
{
    btptr testclock, testlinkptr, resetclock;
    connptr testconn;
    double tdriver;

    // Find out where testlink is in clocklist, and save the position.

    for (testclock = clocklist; testclock; testclock = testclock->next) {
	if (testclock->receiver == testlink) {
	    testlinkptr = testclock;
	    break;
	}
    }
    if (testclock == NULL) return NULL;	// Error---testlink wasn't in clocklist!

    for (testclock = clocklist; testclock; testclock = testclock->next) {
	testconn = testclock->receiver;
	tdriver = 0.0;		// to-do:  set to default input transition time
	find_clock_delay(testlinkptr->dir, 0.0, tdriver, testconn, clocklist, testlink,
			minmax);
    }

    // Return the linkptr containing the recorded transition time from
    // source to destination clock pins

    return testlinkptr;
}

/*--------------------------------------------------------------*/
/* Given an instance record, find the pin of the instance that	*/
/* is the clock, if the instance is a flop.  If the instance is	*/
/* not a flop, return NULL.					*/
/*--------------------------------------------------------------*/

connptr find_register_clock(instptr testinst)
{
    connptr testconn;

    for (testconn = testinst->in_connects; testconn; testconn = testconn->next)
	if (testconn->refpin && (testconn->refpin->type & DFFCLK))
	    return testconn;

    return NULL;
}

/*--------------------------------------------------------------*/
/* Given an edge direction (RISING or FALLING) at a source net,	*/
/* and given a destination net, find the sense of the signal	*/
/* when it arrives at the destination net.			*/
/*--------------------------------------------------------------*/

short find_edge_dir(short dir, netptr sourcenet, netptr destnet) {
    int i;
    short outdir, rdir;
    connptr testconn, nextconn;
    instptr testinst;
    netptr nextnet;

    for (i = 0; i < sourcenet->fanout; i++) {
	testconn = sourcenet->receivers[i];
	testinst = testconn->refinst;
	if (testinst == NULL) continue;
	if (testconn->refpin == NULL) continue;
	if ((testconn->refpin->type & REGISTER_IN) != 0) continue;
	nextconn = testinst->out_connects;
	nextnet  = nextconn->refnet;
	outdir = calc_dir(testconn->refpin, dir);
	if (nextnet == destnet) return outdir;

	rdir = find_edge_dir(outdir, nextnet, destnet);
	if (rdir != 0) return rdir;
    }
    return 0;
}

/*--------------------------------------------------------------*/
/* Search all paths from the clocked data outputs of		*/
/* "clockedlist" to either output pins or data inputs of other	*/
/* flops.							*/
/*								*/
/* Return a master list of all backtraces in "masterlist".	*/
/*								*/
/* Return value is the number of paths recorded in masterlist.	*/
/*								*/
/* If minmax == MAXIMUM_TIME, return the maximum delay.		*/
/* If minmax == MINIMUM_TIME, return the minimum delay.		*/
/*--------------------------------------------------------------*/

int find_clock_to_term_paths(connlistptr clockedlist, ddataptr *masterlist, netptr netlist,
		char minmax)
{
    netptr	commonclock, testnet;
    connptr     testconn, thisconn;
    connlistptr testlink;
    pinptr      testpin;
    cellptr     testcell;
    instptr     testinst;
    btptr 	clocklist, clock2list, backtrace, freebt;
    btptr	selectedsource, selecteddest;
    ddataptr    delaylist, testddata;
    ddataptr	freeddata;

    short	srcdir, destdir;		// Signal direction in/out
    double	tdriver, setupdelay, holddelay;
    char	clk_sense_inv, clk_invert;
    int		numpaths, n, i;

    delaylist = NULL;
    clocklist = NULL;
    clock2list = NULL;

    numpaths = 0;
    for (testlink = clockedlist; testlink; testlink = testlink->next) {

	// Remove all tags and reset delay metrics before each run

	for (testnet = netlist; testnet; testnet = testnet->next) {
	    for (i = 0; i < testnet->fanout; i++) {
		testconn = testnet->receivers[i];
		testconn->tag = NULL;
		if (minmax == MAXIMUM_TIME)
		    testconn->metric = -1.0;
		else
		    testconn->metric = 1E50;
	    }
	}

	thisconn = testlink->connection;
	testpin = thisconn->refpin;
	if (testpin) {
	    testcell = testpin->refcell;

	    // Sense is positive for rising edge-triggered flops, negative for
	    // falling edge-triggered flops
	    srcdir = (testcell->type & CLK_SENSE_MASK) ? FALLING : RISING;

	    // Find the sources of the clock at the path start
	    find_clock_source(thisconn, &clocklist, srcdir);

	    // Find the clock source with the worst-case transition time at testlink
	    // (Note:  For maximum path delay, find minimum clock transistion, and vice versa)
	    selectedsource = find_clock_transition(clocklist, thisconn, srcdir, ~minmax);
	    if (selectedsource == NULL)
		tdriver = 0.0;
	    else
		tdriver = selectedsource->trans;

	    // Report on paths and their maximum delays
	    if (verbose > 0)
		fprintf(stdout, "Paths starting at flop \"%s\" clock:\n\n",
				thisconn->refinst->name);

	}
	else {
	    // Connection is an input pin;  must calculate both rising and falling edges.
	    srcdir = EITHER;
	    tdriver = 0.0;	// To-do: use designated input transition time

	    // Report on paths and their maximum delays
	    if (verbose > 0)
		fprintf(stdout, "Paths starting at input pin \"%s\"\n\n",
				thisconn->refnet->name);
	}

	if (verbose > 0) fflush(stdout);

	// Find all paths from "thisconn" to output or a flop input, and compute delay
	n = find_path_delay(srcdir, 0.0, tdriver, thisconn, NULL, &delaylist, minmax);
	numpaths += n;

	if (verbose > 0) fprintf(stdout, "%d paths traced (%d total).\n\n", n, numpaths);

	for (testddata = delaylist; testddata; testddata = testddata->next) {
	    // Copy last backtrace delay to testddata.
	    testddata->delay = testddata->backtrace->delay;
	    testddata->trans = testddata->backtrace->trans;
	    testinst = testddata->backtrace->receiver->refinst;

	    if (testinst != NULL) {
		// Find the sources of the clock at the path end
		destdir = (testinst->refcell->type & CLK_SENSE_MASK) ? FALLING : RISING;
		testconn = find_register_clock(testinst);
		// If testconn is NULL, this is not a register (latch, maybe?)
		if (testconn == NULL) continue;
		find_clock_source(testconn, &clock2list, destdir);
		selecteddest = find_clock_transition(clock2list, testconn, destdir, ~minmax);

		// Find the connection that is common to both clocks
		commonclock = find_common_clock(clocklist, clock2list);
		if (commonclock == NULL) {
		    // Warn about asynchronous clock sources
		    if (verbose > 0) {
			fflush(stdout);
			fprintf(stderr, "Independent clock nets \"%s\" and \"%s\""
				" drive related gates!\n",
				testconn->refnet->name, thisconn->refnet->name);
		    }
		    clk_invert = -1;
		}
		else {
		    // Add or subtract difference in arrival times between source and
		    // destination clocks

		    if (selecteddest != NULL && selectedsource != NULL) {
			testddata->delay += selecteddest->delay;
			testddata->delay -= selectedsource->delay;

			/* Check if the clock signal arrives at both flops with the	*/
			/* same edge type (both rising or both falling).		*/

			clk_invert = (find_edge_dir(RISING, commonclock,
						selectedsource->receiver->refnet) ==
					find_edge_dir(RISING, commonclock,
						selecteddest->receiver->refnet)) ? 0 : 1;
		    }

		    if (minmax == MAXIMUM_TIME) {
			// Add setup time for destination clocks
			setupdelay = calc_setup_time(testddata->trans,
					testddata->backtrace->receiver->refpin,
					selecteddest->trans,
					testddata->backtrace->dir, minmax);
			testddata->delay += setupdelay;
		    }
		    else {
			// Subtract hold time for destination clocks
			holddelay = calc_hold_time(testddata->trans,
					testddata->backtrace->receiver->refpin,
					selecteddest->trans,
					testddata->backtrace->dir, minmax);
			testddata->delay -= holddelay;
		    }

		    if (verbose > 0)
			fprintf(stdout, "Path terminated on flop \"%s\" input with max delay %g ps\n",
				testconn->refinst->name, testddata->delay);

		    for (backtrace = testddata->backtrace; backtrace->next;
				backtrace = backtrace->next) {
			if (verbose > 0)
			    fprintf(stdout, "   %g (%s) %s/%s -> %s/%s\n",
					backtrace->delay,
					backtrace->receiver->refnet->name,
					backtrace->receiver->refnet->driver->refinst->name,
					backtrace->receiver->refnet->driver->refpin->name,
					backtrace->receiver->refinst->name,
					backtrace->receiver->refpin->name);
		    }
		    if (verbose > 0)
			fprintf(stdout, "   000.000 (%s) %s/%s -> %s/%s\n",
				backtrace->receiver->refnet->name,
				backtrace->receiver->refinst->name,
				backtrace->receiver->refpin->name,
				backtrace->receiver->refinst->name,
				backtrace->receiver->refinst->out_connects->refpin->name);

		    if (selecteddest != NULL && selectedsource != NULL) {
			if (verbose > 0) {
			    if (selectedsource->receiver->refnet != selecteddest->receiver->refnet) {
				fprintf(stdout, "   %g %s to %s clock skew\n",
					selecteddest->delay - selectedsource->delay,
					selectedsource->receiver->refnet->name,
					selecteddest->receiver->refnet->name);
			    }
			}

			/* Check if the flops have the same clock sense */
			/* (both are clock rising edge or both are clock falling edge type) */

			if ((testinst->refcell->type & CLK_SENSE_MASK) !=
					(backtrace->receiver->refinst->refcell->type
					& CLK_SENSE_MASK))
			    clk_sense_inv = 1;
			else
			    clk_sense_inv = 0;

			/* If the two flops don't clock at the same time, then issue a	*/
			/* warning that the slack time loses half a clock period.	*/

			if ((verbose > 0) && (clk_invert != -1) && (clk_sense_inv != clk_invert)) {
			    fprintf(stdout, "   Clocks are inverted relative to one another,\n");
			    fprintf(stdout, "   implying a maximum propagation delay of 1/2 period.\n");
			}
		    }
		    if (selecteddest != NULL && selectedsource != NULL) {
			if (verbose > 0) {
			    if (minmax == MAXIMUM_TIME)
				fprintf(stdout, "   %g setup time at destination\n", setupdelay);
			    else
				fprintf(stdout, "   %g hold time at destination\n", holddelay);
			}
		    }

		    if (verbose > 0) fprintf(stdout, "\n");
		}
	    }
	    else if (verbose > 0) {
		fprintf(stdout, "Path terminated on output \"%s\" with max delay %g ps\n",
				testddata->backtrace->receiver->refnet->name, testddata->delay);

		backtrace = testddata->backtrace;
		fprintf(stdout, "   %g (%s) %s/%s -> [output pin]\n",
			backtrace->delay,
			backtrace->receiver->refnet->name,
			backtrace->receiver->refnet->driver->refinst->name,
			backtrace->receiver->refnet->driver->refpin->name);

		for (backtrace = backtrace->next; backtrace->next; backtrace = backtrace->next) {
		    fprintf(stdout, "   %g (%s) %s/%s -> %s/%s\n",
				backtrace->delay,
				backtrace->receiver->refnet->name,
				backtrace->receiver->refnet->driver->refinst->name,
				backtrace->receiver->refnet->driver->refpin->name,
				backtrace->receiver->refinst->name,
				backtrace->receiver->refpin->name);
		}
		fprintf(stdout, "   000.000 (%s) %s/%s -> %s/%s\n\n",
			backtrace->receiver->refnet->name,
			backtrace->receiver->refinst->name,
			backtrace->receiver->refpin->name,
			backtrace->receiver->refinst->name,
			backtrace->receiver->refinst->out_connects->refpin->name);
	    }

	    // Clean up clock2list
	    while (clock2list != NULL) {
		freebt = clock2list;
		clock2list = clock2list->next;
		free(freebt);
	    }
	}

	// Link delaylist data to the beginning of masterlist, and null out
	// delaylist for the next set of paths.

	if (delaylist) {
	    for (testddata = delaylist; testddata->next; testddata = testddata->next);
	    testddata->next = *masterlist;
	    *masterlist = delaylist;
	    delaylist = NULL;
	}

	// Free up clocklist
	while (clocklist != NULL) {
	    freebt = clocklist;
	    clocklist = clocklist->next;
	    free(freebt);
	}
    }
    return numpaths;
}

/*--------------------------------------------------------------*/
/* Parse a table variable type from a liberty format file	*/
/*--------------------------------------------------------------*/

int get_table_type(char *token) {
    if (!strcasecmp(token, "input_net_transition"))
	return TRANSITION_TIME;
    else if (!strcasecmp(token, "total_output_net_capacitance"))
	return OUTPUT_CAP;
    else if (!strcasecmp(token, "related_pin_transition"))
	return RELATED_TIME;
    else if (!strcasecmp(token, "constrained_pin_transition"))
	return CONSTRAINED_TIME;
    else
	return UNKNOWN;
}

/*--------------------------------------------------------------*/
/* Read a liberty format file and collect information about	*/
/* the timing properties of each standard cell.			*/
/*--------------------------------------------------------------*/

void
libertyRead(FILE *flib, lutable **tablelist, cell **celllist)
{
    char *token;
    char *libname = NULL;
    int section = INIT;

    double time_unit = 1.0;	// Time unit multiplier, to get ps
    double cap_unit = 1.0;	// Capacitive unit multiplier, to get fF

    pinptr testpin;
    lutable *tableptr;

    int i, j;
    double gval;
    char *iptr;
    short timing_type, sense_type;

    lutable *newtable, *reftable;
    cell *newcell, *lastcell;
    pin *newpin;

    lastcell = NULL;
    timing_type = UNKNOWN;

    /* Read tokens off of the line */
    token = advancetoken(flib, 0);

    while (token != NULL) {

	switch (section) {
	    case INIT:
		if (!strcasecmp(token, "library")) {
		    token = advancetoken(flib, 0);
		    if (strcmp(token, "("))
			fprintf(stderr, "Library not followed by name\n");
		    else
			token = advancetoken(flib, ')');
		    fprintf(stderr, "Parsing library \"%s\"\n", token);
		    libname = strdup(token);
		    token = advancetoken(flib, 0);
		    if (strcmp(token, "{")) {
			fprintf(stderr, "Did not find opening brace "
					"on library block\n");
			exit(1);
		    }
		    section = LIBBLOCK;
		}
		else
		    fprintf(stderr, "Unknown input \"%s\", looking for "
					"\"library\"\n", token);
		break;

	    case LIBBLOCK:
		// Here we check for the main blocks, again not rigorously. . .

		if (!strcasecmp(token, "}")) {
		    fprintf(stdout, "End of library at line %d\n", fileCurrentLine);
		    section = INIT;			// End of library block
		}
		else if (!strcasecmp(token, "delay_model")) {
		    token = advancetoken(flib, 0);
		    if (strcmp(token, ":"))
			fprintf(stderr, "Input missing colon\n");
		    token = advancetoken(flib, ';');
		    if (strcasecmp(token, "table_lookup")) {
			fprintf(stderr, "Sorry, only know how to "
					"handle table lookup!\n");
			exit(1);
		    }
		}
		else if (!strcasecmp(token, "lu_table_template") ||
			 !strcasecmp(token, "power_lut_template")) {
		    // Read in template information;
		    newtable = (lutable *)malloc(sizeof(lutable));
		    newtable->name = NULL;
		    newtable->invert = 0;
		    newtable->var1 = UNKNOWN;
		    newtable->var2 = UNKNOWN;
		    newtable->size1 = 0;
		    newtable->size2 = 0;
		    newtable->idx1.times = NULL;
		    newtable->idx2.caps = NULL;
		    newtable->values = NULL;
		    newtable->next = *tablelist;
		    *tablelist = newtable;

		    token = advancetoken(flib, 0);
		    if (strcmp(token, "("))
			fprintf(stderr, "Input missing open parens\n");
		    else
			token = advancetoken(flib, ')');
		    newtable->name = strdup(token);
		    while (*token != '}') {
			token = advancetoken(flib, 0);
			if (!strcasecmp(token, "variable_1")) {
			    token = advancetoken(flib, 0);
			    token = advancetoken(flib, ';');
			    newtable->var1 = get_table_type(token);
			    if (newtable->var1 == OUTPUT_CAP || newtable->var1 == CONSTRAINED_TIME)
				newtable->invert = 1;
			}
			else if (!strcasecmp(token, "variable_2")) {
			    token = advancetoken(flib, 0);
			    token = advancetoken(flib, ';');
			    newtable->var2 = get_table_type(token);
			    if (newtable->var2 == TRANSITION_TIME || newtable->var2 == RELATED_TIME)
				newtable->invert = 1;
			}
			else if (!strcasecmp(token, "index_1")) {
			    token = advancetoken(flib, 0);	// Open parens
			    token = advancetoken(flib, 0);	// Quote
			    if (!strcmp(token, "\""))
				token = advancetoken(flib, '\"');

			    if (newtable->invert == 1) {
				// Count entries
				iptr = token;
				newtable->size2 = 1;
				while ((iptr = strchr(iptr, ',')) != NULL) {
				    iptr++;
				    newtable->size2++;
				}
				newtable->idx2.caps = (double *)malloc(newtable->size2 *
					sizeof(double));
				newtable->size2 = 0;
				iptr = token;
				sscanf(iptr, "%lg", &newtable->idx2.caps[0]);
				if (newtable->var2 == OUTPUT_CAP)
				    newtable->idx2.caps[0] *= cap_unit;
				else
				    newtable->idx2.caps[0] *= time_unit;

				while ((iptr = strchr(iptr, ',')) != NULL) {
				    iptr++;
				    newtable->size2++;
				    sscanf(iptr, "%lg",
						&newtable->idx2.caps[newtable->size2]);
				    if (newtable->var2 == OUTPUT_CAP)
					newtable->idx2.caps[newtable->size2] *= cap_unit;
				    else
					newtable->idx2.cons[newtable->size2] *= time_unit;
				}
				newtable->size2++;
			    }
			    else {	// newtable->invert = 0
				// Count entries
				iptr = token;
				newtable->size1 = 1;
				while ((iptr = strchr(iptr, ',')) != NULL) {
				    iptr++;
				    newtable->size1++;
				}
				newtable->idx1.times = (double *)malloc(newtable->size1 *
					sizeof(double));
				newtable->size1 = 0;
				iptr = token;
				sscanf(iptr, "%lg", &newtable->idx1.times[0]);
				newtable->idx1.times[0] *= time_unit; 
				while ((iptr = strchr(iptr, ',')) != NULL) {
				    iptr++;
				    newtable->size1++;
				    sscanf(iptr, "%lg",
						&newtable->idx1.times[newtable->size1]);
				    newtable->idx1.times[newtable->size1] *= time_unit;
				}
				newtable->size1++;
			    }

			    token = advancetoken(flib, ';'); // EOL semicolon
			}
			else if (!strcasecmp(token, "index_2")) {
			    token = advancetoken(flib, 0);	// Open parens
			    token = advancetoken(flib, 0);	// Quote
			    if (!strcmp(token, "\""))
				token = advancetoken(flib, '\"');

			    if (newtable->invert == 0) {
				// Count entries
				iptr = token;
				newtable->size2 = 1;
				while ((iptr = strchr(iptr, ',')) != NULL) {
				    iptr++;
				    newtable->size2++;
				}
				newtable->idx2.caps = (double *)malloc(newtable->size2 *
					sizeof(double));
				newtable->size2 = 0;
				iptr = token;
				sscanf(iptr, "%lg", &newtable->idx2.caps[0]);
				if (newtable->var2 == OUTPUT_CAP)
				    newtable->idx2.caps[0] *= cap_unit;
				else
				    newtable->idx2.cons[0] *= time_unit;
				while ((iptr = strchr(iptr, ',')) != NULL) {
				    iptr++;
				    newtable->size2++;
				    sscanf(iptr, "%lg",
						&newtable->idx2.caps[newtable->size2]);
				    if (newtable->var2 == OUTPUT_CAP)
					newtable->idx2.caps[newtable->size2] *= cap_unit;
				    else
					newtable->idx2.cons[newtable->size2] *= time_unit;
				}
				newtable->size2++;
			    }
			    else { 	// newtable->invert == 1
				// Count entries
				iptr = token;
				newtable->size1 = 1;
				while ((iptr = strchr(iptr, ',')) != NULL) {
				    iptr++;
				    newtable->size1++;
				}
				newtable->idx1.times = (double *)malloc(newtable->size1 *
					sizeof(double));
				newtable->size1 = 0;
				iptr = token;
				sscanf(iptr, "%lg", &newtable->idx1.times[0]);
				newtable->idx1.times[0] *= time_unit;
				while ((iptr = strchr(iptr, ',')) != NULL) {
				    iptr++;
				    newtable->size1++;
				    sscanf(iptr, "%lg",
						&newtable->idx1.times[newtable->size1]);
				    newtable->idx1.times[newtable->size1] *= time_unit;
				}
				newtable->size1++;
			    }

			    token = advancetoken(flib, ';'); // EOL semicolon
			}
		    }
		}
		else if (!strcasecmp(token, "cell")) {
		    newcell = (cell *)malloc(sizeof(cell));
		    newcell->next = NULL;
		    if (lastcell != NULL)
			lastcell->next = newcell;
		    else
			*celllist = newcell;
		    lastcell = newcell;
		    token = advancetoken(flib, 0);	// Open parens
		    if (!strcmp(token, "("))
			token = advancetoken(flib, ')');	// Cellname
		    newcell->name = strdup(token);
		    token = advancetoken(flib, 0);	// Find start of block
		    if (strcmp(token, "{"))
			fprintf(stderr, "Error: failed to find start of block\n");
		    newcell->type = GATE;		// Default type
		    newcell->function = NULL;
		    newcell->pins = NULL;
		    newcell->area = 1.0;
		    newcell->maxtrans = 0.0;
		    newcell->maxcap = 0.0;
		    section = CELLDEF;
		}
		else if (!strcasecmp(token, "time_unit")) {
		   char *metric;

		   token = advancetoken(flib, 0);
		   if (token == NULL) break;
		   if (!strcmp(token, ":")) {
		      token = advancetoken(flib, 0);
		      if (token == NULL) break;
		   }
		   if (!strcmp(token, "\"")) {
		      token = advancetoken(flib, '\"');
		      if (token == NULL) break;
		   }
		   time_unit = strtod(token, &metric);
		   if (*metric != '\0') {
		      if (!strcmp(metric, "ns"))
			 time_unit *= 1E3;
		      else if (!strcmp(metric, "us"))
			 time_unit *= 1E6;
		      else if (!strcmp(metric, "fs"))
			 time_unit *= 1E-3;
		      else if (strcmp(metric, "ps"))
			 fprintf(stderr, "Don't understand time units \"%s\"\n",
				token);
		   }
		   else {
		      token = advancetoken(flib, 0);
		      if (token == NULL) break;
		      if (!strcmp(token, "ns"))
			 time_unit *= 1E3;
		      else if (!strcmp(token, "us"))
			 time_unit *= 1E6;
		      else if (!strcmp(token, "fs"))
			 time_unit *= 1E-3;
		      else if (strcmp(token, "ps"))
			 fprintf(stderr, "Don't understand time units \"%s\"\n",
				token);
		   }
		   token = advancetoken(flib, ';');
		}
		else if (!strcasecmp(token, "capacitive_load_unit")) {
		   char *metric;

		   token = advancetoken(flib, 0);
		   if (token == NULL) break;
		   if (!strcmp(token, "(")) {
		      token = advancetoken(flib, ')');
		      if (token == NULL) break;
		   }
		   cap_unit = strtod(token, &metric);
		   if (*metric != '\0') {
		      while (isspace(*metric)) metric++;
		      if (*metric == ',') metric++;
		      while ((*metric != '\0') && isspace(*metric)) metric++;
		      if (!strcasecmp(metric, "af"))
			 cap_unit *= 1E-3;
		      else if (!strcasecmp(metric, "pf"))
			 cap_unit *= 1000;
		      else if (!strcasecmp(metric, "nf"))
			 cap_unit *= 1E6;
		      else if (!strcasecmp(metric, "uf"))
			 cap_unit *= 1E9;
		      else if (strcasecmp(metric, "ff"))
			 fprintf(stderr, "Don't understand capacitive units \"%s\"\n",
				token);
		   }
		   else {
		      token = advancetoken(flib, 0);
		      if (token == NULL) break;
		      if (!strcasecmp(token, "af"))
			 cap_unit *= 1E-3;
		      else if (!strcasecmp(token, "pf"))
			 cap_unit *= 1000;
		      else if (!strcasecmp(token, "nf"))
			 cap_unit *= 1E6;
		      else if (!strcasecmp(token, "uf"))
			 cap_unit *= 1E9;
		      else if (strcasecmp(token, "ff"))
			 fprintf(stderr, "Don't understand capacitive units \"%s\"\n",
				token);
		   }
		   token = advancetoken(flib, ';');
		}
		else {
		    // For unhandled tokens, read in tokens.  If it is
		    // a definition or function, read to end-of-line.  If
		    // it is a block definition, read to end-of-block.
		    while (1) {
			token = advancetoken(flib, 0);
			if (token == NULL) break;
			if (!strcmp(token, ";")) break;
			if (!strcmp(token, "\""))
			    token = advancetoken(flib, '\"');
			if (!strcmp(token, "{")) {
			    token = advancetoken(flib, '}');
			    break;
			}
		    }
		}
		break;

	    case CELLDEF:

		if (!strcmp(token, "}")) {
		    section = LIBBLOCK;			// End of cell def
		}
		else if (!strcasecmp(token, "pin")) {
		    token = advancetoken(flib, 0);	// Open parens
		    if (!strcmp(token, "("))
			token = advancetoken(flib, ')');	// Close parens

		    newpin = parse_pin(newcell, token, SENSE_NONE);

		    token = advancetoken(flib, 0);	// Find start of block
		    if (strcmp(token, "{"))
			fprintf(stderr, "Error: failed to find start of block\n");
		    section = PINDEF;
		}		
		else if (!strcasecmp(token, "area")) {
		    token = advancetoken(flib, 0);	// Colon
		    token = advancetoken(flib, ';');	// To end-of-statement
		    sscanf(token, "%lg", &newcell->area);
		}
		else if (!strcasecmp(token, "ff")) {
		    newcell->type |= DFF;
		    token = advancetoken(flib, '{');
		    section = FLOPDEF;
		}
		else if (!strcasecmp(token, "latch")) {
		    newcell->type |= LATCH;
		    token = advancetoken(flib, '{');
		    section = LATCHDEF;
		}
		else {
		    // For unhandled tokens, read in tokens.  If it is
		    // a definition or function, read to end-of-line.  If
		    // it is a block definition, read to end-of-block.
		    while (1) {
			token = advancetoken(flib, 0);
			if (token == NULL) break;
			if (!strcmp(token, ";")) break;
			if (!strcmp(token, "\""))
			    token = advancetoken(flib, '\"');
			if (!strcmp(token, "("))
			    token = advancetoken(flib, ')');
			if (!strcmp(token, "{")) {
			    token = advancetoken(flib, '}');
			    break;
			}
		    }
		}
		break;

	    case FLOPDEF:

		if (!strcmp(token, "}")) {
		    section = CELLDEF;			// End of flop def
		}
		else if (!strcasecmp(token, "next_state")) {
		    token = advancetoken(flib, 0);	// Colon
		    token = advancetoken(flib, ';');	// To end-of-statement
		    newpin = parse_pin(newcell, token, SENSE_NONE);
		    newpin->type |= DFFIN;
		}
		else if (!strcasecmp(token, "clocked_on")) {
		    token = advancetoken(flib, 0);	// Colon
		    token = advancetoken(flib, ';');	// To end-of-statement
		    if (strchr(token, '\'') != NULL)
			newcell->type |= CLK_SENSE_MASK;
		    else if (strchr(token, '!') != NULL)
			newcell->type |= CLK_SENSE_MASK;
		    newpin = parse_pin(newcell, token, SENSE_NONE);
		    newpin->type |= DFFCLK;
		}
		else if (!strcasecmp(token, "clear")) {
		    newcell->type |= RST_MASK;
		    token = advancetoken(flib, 0);	// Colon
		    token = advancetoken(flib, ';');	// To end-of-statement
		    if (strchr(token, '\'') != NULL)
			newcell->type |= RST_SENSE_MASK;
		    else if (strchr(token, '!') != NULL)
			newcell->type |= RST_SENSE_MASK;
		    newpin = parse_pin(newcell, token, SENSE_NONE);
		    newpin->type |= DFFRST;
		}
		else if (!strcasecmp(token, "preset")) {
		    newcell->type |= SET_MASK;
		    token = advancetoken(flib, 0);	// Colon
		    token = advancetoken(flib, ';');	// To end-of-statement
		    if (strchr(token, '\'') != NULL)
			newcell->type |= SET_SENSE_MASK;
		    else if (strchr(token, '!') != NULL)
			newcell->type |= SET_SENSE_MASK;
		    newpin = parse_pin(newcell, token, SENSE_NONE);
		    newpin->type |= DFFSET;
		}
		else
		    token = advancetoken(flib, ';');	// Read to end-of-statement

		break;

	    case LATCHDEF:

		if (!strcmp(token, "}")) {
		    section = CELLDEF;			// End of flop def
		}
		else if (!strcasecmp(token, "data_in")) {
		    token = advancetoken(flib, 0);	// Colon
		    token = advancetoken(flib, ';');	// To end-of-statement
		    newpin = parse_pin(newcell, token, SENSE_NONE);
		    newpin->type |= LATCHIN;
		}
		else if (!strcasecmp(token, "enable")) {
		    token = advancetoken(flib, 0);	// Colon
		    token = advancetoken(flib, ';');	// To end-of-statement
		    if (strchr(token, '\'') != NULL)
			newcell->type |= EN_SENSE_MASK;
		    else if (strchr(token, '!') != NULL)
			newcell->type |= EN_SENSE_MASK;
		    newpin = parse_pin(newcell, token, SENSE_NONE);
		    newpin->type |= LATCHEN;
		}
		else
		    token = advancetoken(flib, ';');	// Read to end-of-statement

		break;

	    case PINDEF:

		if (!strcmp(token, "}")) {
		    section = CELLDEF;			// End of pin def
		}
		else if (!strcasecmp(token, "capacitance")) {
		    token = advancetoken(flib, 0);	// Colon
		    token = advancetoken(flib, ';');	// To end-of-statement
		    sscanf(token, "%lg", &newpin->capr);
		    newpin->capr *= cap_unit;
		}
		else if (!strcasecmp(token, "rise_capacitance")) {
		    token = advancetoken(flib, 0);	// Colon
		    token = advancetoken(flib, ';');	// To end-of-statement
		    sscanf(token, "%lg", &newpin->capr);
		    newpin->capr *= cap_unit;
		}
		else if (!strcasecmp(token, "fall_capacitance")) {
		    token = advancetoken(flib, 0);	// Colon
		    token = advancetoken(flib, ';');	// To end-of-statement
		    sscanf(token, "%lg", &newpin->capf);
		    newpin->capf *= cap_unit;
		}
		else if (!strcasecmp(token, "function")) {
		    token = advancetoken(flib, 0);	// Colon
		    token = advancetoken(flib, 0);	// Open quote
		    if (!strcmp(token, "\""))
			token = advancetoken(flib, '\"');	// Find function string
		    if (newpin->type & OUTPUT) {
			newcell->function = strdup(token);
		    }
		    token = advancetoken(flib, 0);
		    if (strcmp(token, ";")) {
		        if (!strcmp(token, "}"))
			    section = CELLDEF;		// End of pin def
			else
			    fprintf(stderr, "Expected end-of-statement.\n");
		    }
		}
		else if (!strcasecmp(token, "direction")) {
		    token = advancetoken(flib, 0);	// Colon
		    token = advancetoken(flib, ';');
		    if (!strcasecmp(token, "input")) {
			newpin->type |= INPUT;
		    }
		    else if (!strcasecmp(token, "output")) {
			newpin->type |= OUTPUT;
			if (newcell->type & DFF) newpin->type |= DFFOUT;
		        if (newcell->type & LATCH) newpin->type |= LATCHOUT;
		    }
		}
		else if (!strcasecmp(token, "max_transition")) {
		    token = advancetoken(flib, 0);	// Colon
		    token = advancetoken(flib, ';');	// To end-of-statement
		    sscanf(token, "%lg", &newcell->maxtrans);
		    newcell->maxtrans *= time_unit;
		}
		else if (!strcasecmp(token, "max_capacitance")) {
		    token = advancetoken(flib, 0);	// Colon
		    token = advancetoken(flib, ';');	// To end-of-statement
		    sscanf(token, "%lg", &newcell->maxcap);
		    newcell->maxcap *= cap_unit;
		}
		else if (!strcasecmp(token, "timing")) {
		    token = advancetoken(flib, 0);	// Arguments, if any
		    if (strcmp(token, "("))
			fprintf(stderr, "Error: failed to find start of block\n");
		    else
		       token = advancetoken(flib, ')');	// Arguments, if any
		    token = advancetoken(flib, 0);	// Find start of block
		    if (strcmp(token, "{"))
			fprintf(stderr, "Error: failed to find start of block\n");
		    testpin = NULL;
		    sense_type = SENSE_NONE;
		    section = TIMING;
		}
		else {
		    // For unhandled tokens, read in tokens.  If it is
		    // a definition or function, read to end-of-line.  If
		    // it is a block definition, read to end-of-block.
		    while (1) {
			token = advancetoken(flib, 0);
			if (token == NULL) break;
			if (!strcmp(token, ";")) break;
			if (!strcmp(token, "\""))
			    token = advancetoken(flib, '\"');
			if (!strcmp(token, "{")) {
			    token = advancetoken(flib, '}');
			    break;
			}
		    }
		}
		break;

	    case TIMING:

		if (!strcmp(token, "}")) {
		    section = PINDEF;			// End of timing def
		}
		else if (!strcasecmp(token, "related_pin")) {
		    token = advancetoken(flib, 0);	// Colon
		    token = advancetoken(flib, ';');	// Read to end of statement
		    // Create the related pin if needed
		    testpin = parse_pin(newcell, token, sense_type);
		}
		else if (!strcasecmp(token, "timing_sense")) {
		    token = advancetoken(flib, 0);	// Colon
		    token = advancetoken(flib, ';');	// Read to end of statement
		    if (!strcasecmp(token, "positive_unate")) {
			if (testpin)
			   testpin->sense = SENSE_POSITIVE;
			else
			   sense_type = SENSE_POSITIVE;
		    }
		    else if (!strcasecmp(token, "negative_unate")) {
			if (testpin)
			   testpin->sense = SENSE_NEGATIVE;
			else
			   sense_type = SENSE_NEGATIVE;
		    }
		    else if (!strcasecmp(token, "non_unate")) {
			if (testpin)
			   testpin->sense = SENSE_NONE;
			else
			   sense_type = SENSE_NONE;
		    }
		}
		else if (!strcasecmp(token, "timing_type")) {
		    token = advancetoken(flib, 0);	// Colon
		    token = advancetoken(flib, ';');	// Read to end of statement

		    // Note:  Timing type is apparently redundant information;
		    // e.g., "falling_edge" can be determined by "clocked_on : !CLK"
		    // in the ff {} block.  How reliable is this?

		    if (!strcasecmp(token, "rising_edge"))
			timing_type = TIMING_PROP_TRANS;
		    else if (!strcasecmp(token, "falling_edge"))
			timing_type = TIMING_PROP_TRANS;
		    else if (!strcasecmp(token, "hold_rising"))
			timing_type = TIMING_HOLD;
		    else if (!strcasecmp(token, "hold_falling"))
			timing_type = TIMING_HOLD;
		    else if (!strcasecmp(token, "setup_rising"))
			timing_type = TIMING_SETUP;
		    else if (!strcasecmp(token, "setup_falling"))
			timing_type = TIMING_SETUP;
		    else if (!strcasecmp(token, "clear"))
			timing_type = TIMING_SET_RESET;
		    else if (!strcasecmp(token, "preset"))
			timing_type = TIMING_SET_RESET;
		    else if (!strcasecmp(token, "recovery_rising"))
			timing_type = TIMING_RECOVERY;
		    else if (!strcasecmp(token, "recovery_falling"))
			timing_type = TIMING_RECOVERY;
		    else if (!strcasecmp(token, "removal_rising"))
			timing_type = TIMING_REMOVAL;
		    else if (!strcasecmp(token, "removal_falling"))
			timing_type = TIMING_REMOVAL;
		    else if (!strcasecmp(token, "three_state_enable"))
			timing_type = TIMING_TRISTATE;
		    else if (!strcasecmp(token, "three_state_disable"))
			timing_type = TIMING_TRISTATE;
		}
		else if ((!strcasecmp(token, "cell_rise")) ||
			(!strcasecmp(token, "cell_fall")) ||
			(!strcasecmp(token, "rise_transition")) ||
			(!strcasecmp(token, "fall_transition")) ||
			(!strcasecmp(token, "rise_constraint")) ||
			(!strcasecmp(token, "fall_constraint"))) {

		    tableptr = (lutable *)malloc(sizeof(lutable));
		    tableptr->name = NULL;	// Not used
		    tableptr->invert = 0;
		    tableptr->var1 = UNKNOWN;
		    tableptr->var2 = UNKNOWN;
		    tableptr->size1 = 0;
		    tableptr->size2 = 0;
		    tableptr->idx1.times = NULL;
		    tableptr->idx2.caps = NULL;
		    tableptr->values = NULL;
		    tableptr->next = NULL;	// Not used

		    // Note that propagation delays (cell rise, cell fall) and
		    // transition times (rise transition, fall transition) have
		    // their lookup tables stored in the "related pin" pin record.
		    // Setup and hold times (rise constraint, fall constraint)
		    // have their lookup tables stored in the original pin record.
		    // These should not overlap.

		    // Recovery and removal tables are not yet handled. . .

		    if (!strcasecmp(token, "cell_rise"))
			testpin->propdelr = tableptr;
		    else if (!strcasecmp(token, "cell_fall"))
			testpin->propdelf = tableptr;
		    else if (!strcasecmp(token, "rise_transition"))
			testpin->transr = tableptr;
		    else if (!strcasecmp(token, "fall_transition"))
			testpin->transf = tableptr;
		    else if (!strcasecmp(token, "rise_constraint")) {
			if (timing_type == TIMING_SETUP)
			    newpin->propdelr = tableptr;
			else if (timing_type == TIMING_HOLD)
			    newpin->transr = tableptr;
		    }
		    else if (!strcasecmp(token, "fall_constraint")) {
			if (timing_type == TIMING_SETUP)
			    newpin->propdelf = tableptr;
			else if (timing_type == TIMING_HOLD)
			    newpin->transf = tableptr;
		    }

		    token = advancetoken(flib, 0);	// Open parens
		    if (!strcmp(token, "("))
			token = advancetoken(flib, ')');
			
		    for (reftable = *tablelist; reftable; reftable = reftable->next)
			if (!strcmp(reftable->name, token))
			    break;
		    if (reftable == NULL)
			fprintf(stderr, "Failed to find a valid table \"%s\"\n",
				token);
		    else {
			// Fill in default values from template reftable
			tableptr->invert = reftable->invert;
			if (reftable->size1 > 0) {
			    tableptr->var1 = reftable->var1;
			    tableptr->size1 = reftable->size1;
			    tableptr->idx1.times = (double *)malloc(tableptr->size1 * sizeof(double));
			    memcpy(tableptr->idx1.times, reftable->idx1.times,
						tableptr->size1 * sizeof(double));
			}
			if (reftable->size2 > 0) {
			    tableptr->var2 = reftable->var2;
			    tableptr->size2 = reftable->size2;
			    tableptr->idx2.caps = (double *)malloc(tableptr->size2 * sizeof(double));
			    memcpy(tableptr->idx2.caps, reftable->idx2.caps,
						tableptr->size2 * sizeof(double));
			}
		    }

		    token = advancetoken(flib, 0);
		    if (strcmp(token, "{"))
			fprintf(stderr, "Failed to find start of timing block\n");

		    while (*token != '}') {
		        token = advancetoken(flib, 0);
		        if (!strcasecmp(token, "index_1")) {

			    // Local index values override those in the template

			    token = advancetoken(flib, 0);	// Open parens
			    token = advancetoken(flib, 0);	// Quote
			    if (!strcmp(token, "\""))
				token = advancetoken(flib, '\"');

			    //-------------------------

			    if (reftable && (reftable->invert == 1)) {
				// Entries had better match the ref table
				iptr = token;
				i = 0;
				sscanf(iptr, "%lg", &tableptr->idx2.caps[0]);
				if (tableptr->var2 == OUTPUT_CAP)
				    tableptr->idx2.caps[0] *= cap_unit;
				else
				    tableptr->idx2.cons[0] *= time_unit;
				while ((iptr = strchr(iptr, ',')) != NULL) {
				    iptr++;
				    i++;
				    sscanf(iptr, "%lg", &tableptr->idx2.caps[i]);
				    if (tableptr->var2 == OUTPUT_CAP)
					tableptr->idx2.caps[i] *= cap_unit;
				    else
					tableptr->idx2.cons[i] *= time_unit;
				}
			    }
			    else if (reftable && (reftable->invert == 0)) {
				iptr = token;
				i = 0;
				sscanf(iptr, "%lg", &tableptr->idx1.times[0]);
				tableptr->idx1.times[0] *= time_unit;
				while ((iptr = strchr(iptr, ',')) != NULL) {
				    iptr++;
				    i++;
				    sscanf(iptr, "%lg", &tableptr->idx1.times[i]);
				    tableptr->idx1.times[i] *= time_unit;
				}
			    }

			    token = advancetoken(flib, ')'); 	// Close paren
			    token = advancetoken(flib, ';');	// EOL semicolon
			}
		        else if (!strcasecmp(token, "index_2")) {

			    // Local index values override those in the template

			    token = advancetoken(flib, 0);	// Open parens
			    token = advancetoken(flib, 0);	// Quote
			    if (!strcmp(token, "\""))
				token = advancetoken(flib, '\"');

			    //-------------------------

			    if (reftable && (reftable->invert == 1)) {
				// Entries had better match the ref table
				iptr = token;
				i = 0;
				sscanf(iptr, "%lg", &tableptr->idx1.times[0]);
				tableptr->idx1.times[0] *= time_unit;
				while ((iptr = strchr(iptr, ',')) != NULL) {
				    iptr++;
				    i++;
				    sscanf(iptr, "%lg", &tableptr->idx1.times[i]);
				    tableptr->idx1.times[i] *= time_unit;
				}
			    }
			    else if (reftable && (reftable->invert == 0)) {
				iptr = token;
				i = 0;
				sscanf(iptr, "%lg", &tableptr->idx2.caps[0]);
				tableptr->idx2.caps[0] *= cap_unit;
				while ((iptr = strchr(iptr, ',')) != NULL) {
				    iptr++;
				    i++;
				    sscanf(iptr, "%lg", &tableptr->idx2.caps[i]);
				    tableptr->idx2.caps[i] *= cap_unit;
				}
			    }

			    token = advancetoken(flib, ')'); 	// Close paren
			    token = advancetoken(flib, ';');	// EOL semicolon
			}
			else if (!strcasecmp(token, "values")) {
			    token = advancetoken(flib, 0);	
			    if (strcmp(token, "("))
				fprintf(stderr, "Failed to find start of"
						" value table\n");
			    token = advancetoken(flib, ')');

			    // Parse the string of values and enter it into the
			    // table "values", which is size size2 x size1

			    if (reftable && reftable->size1 > 0) {
				int locsize2;
			        locsize2 = (reftable->size2 > 0) ? reftable->size2 : 1;
				if (reftable->invert) {
				    tableptr->values = (double *)malloc(locsize2 *
						reftable->size1 * sizeof(double));
				    iptr = token;
				    for (i = 0; i < reftable->size1; i++) {
					for (j = 0; j < locsize2; j++) {
					    while (*iptr == ' ' || *iptr == '\"' ||
							*iptr == ',')
						iptr++;
					    sscanf(iptr, "%lg", &gval);
					    *(tableptr->values + j * reftable->size1
							+ i) = gval * time_unit;
					    while (*iptr != ' ' && *iptr != '\"' &&
							*iptr != ',')
						iptr++;
					}
				    }
				}
				else {
				    tableptr->values = (double *)malloc(locsize2 *
						reftable->size1 * sizeof(double));
				    iptr = token;
				    for (j = 0; j < locsize2; j++) {
					for (i = 0; i < reftable->size1; i++) {
					    while (*iptr == ' ' || *iptr == '\"' ||
							*iptr == ',')
						iptr++;
					    sscanf(iptr, "%lg", &gval);
					    *(tableptr->values + j * reftable->size1
							+ i) = gval * time_unit;
					    while (*iptr != ' ' && *iptr != '\"' &&
							*iptr != ',')
						iptr++;
					}
				    }
				}
			    }

			    token = advancetoken(flib, 0);
			    if (strcmp(token, ";"))
				fprintf(stderr, "Failed to find end of value table\n");
			    token = advancetoken(flib, 0);

			}
			else if (strcmp(token, "{"))
			    fprintf(stderr, "Failed to find end of timing block\n");
		    }
		}
		else {
		    // For unhandled tokens, read in tokens.  If it is
		    // a definition or function, read to end-of-line.  If
		    // it is a block definition, read to end-of-block.
		    while (1) {
			token = advancetoken(flib, 0);
			if (token == NULL) break;
			if (!strcmp(token, ";")) break;
			if (!strcmp(token, "\""))
			    token = advancetoken(flib, '\"');
			if (!strcmp(token, "{")) {
			    token = advancetoken(flib, '}');
			    break;
			}
		    }
		}
		break;
	}
	token = advancetoken(flib, 0);
    }
}

/*--------------------------------------------------------------*/
/* Read a verilog netlist and collect information about the	*/
/* cells instantiated and the network structure			*/
/*--------------------------------------------------------------*/

void
verilogRead(FILE *fsrc, cell *cells, net **netlist, instance **instlist,
		connect **inputlist, connect **outputlist)
{
    char *token;
    char *modname = NULL;
    int section = MODULE;

    instptr newinst;
    netptr newnet, testnet;
    cellptr testcell;
    connptr newconn, testconn;
    pinptr testpin;

    int vstart, vend, vtarget, isinput;
    struct hashlist *Nethash[OBJHASHSIZE];

    /* See hash.c for these routines and variables */
    hashfunc = hash;
    matchfunc = match;

    /* Initialize net hash table */
    InitializeHashTable(Nethash);

    /* Read tokens off of the line */
    token = advancetoken(fsrc, 0);

    while (token != NULL) {

	switch (section) {
	    case MODULE:
		if (!strcasecmp(token, "module")) {
		    token = advancetoken(fsrc, 0);
		    fprintf(stderr, "Parsing module \"%s\"\n", token);

		    token = advancetoken(fsrc, 0);
		    if (strcmp(token, "("))
			fprintf(stderr, "Module not followed by pin list\n");
		    else
			token = advancetoken(fsrc, ')');
		    token = advancetoken(fsrc, ';');	// Get end-of-line

		    // Ignore the pin list, go straight to the input/output declarations
		    section = IOLIST;
		}
		break;

	    case IOLIST:
		if (!strcasecmp(token, "input") || !strcasecmp(token, "output")) {

		    testconn = NULL;
		    vstart = vend = 0;

		    if (!strcasecmp(token, "input")) {
			isinput = 1;
		    }
		    else { 	// output
			isinput = 0;
		    }

		    token = advancetoken(fsrc, 0);
		    if (*token == '[') {
			sscanf(token + 1, "%d", &vstart);
			token = advancetoken(fsrc, ':');
			token = advancetoken(fsrc, ']');	// Read to end of vector
			sscanf(token, "%d", &vend);
			token = advancetoken(fsrc, 0);		// Read signal name
		    }

		    // Create a net entry for the input or output, add to the list of nets

		    if (vstart == 0 && vend == 0) {
			newnet = create_net(netlist);
			newnet->name = strdup(token);
			HashPtrInstall(newnet->name, newnet, Nethash);

			testconn = (connptr)malloc(sizeof(connect));
			testconn->refnet = newnet;
			testconn->refpin = NULL;	// No associated pin
			testconn->refinst = NULL;	// No associated instance
			testconn->tag = NULL;
			testconn->metric = -1.0;
			testconn->prvector = NULL;
			testconn->pfvector = NULL;
			testconn->trvector = NULL;
			testconn->tfvector = NULL;

			if (isinput) {			// driver (input)
			    testconn->next = *inputlist;
			    *inputlist = testconn;
			}
			else {				// receiver (output)
			    testconn->next = *outputlist;
			    *outputlist = testconn;
			}
		    }
		    else {
			vtarget = vend + (vstart < vend) ? 1 : -1;
			while (vstart != vtarget) {
			    newnet = create_net(netlist);
			    newnet->name = (char *)malloc(strlen(token) + 6);
			    sprintf(newnet->name, "%s[%d]", token, vstart);
			    HashPtrInstall(newnet->name, newnet, Nethash);

			    vstart += (vtarget > vend) ? 1 : -1;

			    testconn = (connptr)malloc(sizeof(connect));
			    testconn->refnet = newnet;
			    testconn->refpin = NULL;	// No associated pin
			    testconn->refinst = NULL;	// No associated instance
			    testconn->tag = NULL;
			    testconn->metric = -1.0;
			    testconn->prvector = NULL;
			    testconn->pfvector = NULL;
			    testconn->trvector = NULL;
			    testconn->tfvector = NULL;

			    if (isinput) {		// driver (input)
				testconn->next = *inputlist;
				*inputlist = testconn;
			    }
			    else {			// receiver (output)
				testconn->next = *outputlist;
				*outputlist = testconn;
			    }
			}
		    }
		    token = advancetoken(fsrc, ';');	// Get rest of input/output entry
		    break;
		}

		/* Drop through on anything that isn't an input, output, or blank line */

	    case GATELIST:

		if (!strcasecmp(token, "endmodule")) {
		    section = MODULE;
		    break;    
		}

		/* Confirm that the token is a known cell, and continue parsing line if so */
		/* Otherwise, parse to semicolon line end and continue */

		for (testcell = cells; testcell; testcell = testcell->next)
		    if (!strcasecmp(testcell->name, token))
			break;

		if (testcell != NULL) {
		    section = INSTANCE;
		    newinst = (instptr)malloc(sizeof(instance));
		    newinst->next = *instlist;
		    *instlist = newinst;
		    newinst->refcell = testcell;
		    newinst->in_connects = NULL;
		    newinst->out_connects = NULL;
		}
		else {
		    /* Ignore all wire and assign statements	*/
		    /* Qflow does not generate these, but other	*/
		    /* synthesis tools may.			*/

		    if (!strcasecmp(token, "assign") && (verbose > 0)) {
			fprintf(stdout, "Wire assignments are not handled!\n");
		    }
		    else if (strcasecmp(token, "wire") && (verbose > 0)) {
			fprintf(stdout, "Unknown cell \"%s\" instanced.\n",
				token);
		    }
		    token = advancetoken(fsrc, ';');	// Get rest of entry, and ignore
		}
		break;

	    case INSTANCE:
		newinst->name = strdup(token);
		token = advancetoken(fsrc, '(');	// Find beginning of pin list
		section = INSTPIN;
		break;

	    case INSTPIN:
		if (*token == '.') {
		    newconn = (connptr)malloc(sizeof(connect));
		    // Pin name is in (token + 1)
		    for (testpin = testcell->pins; testpin; testpin = testpin->next) {
			if (!strcmp(testpin->name, token + 1))
			    break;
		    }
		    // Sanity check
		    if (testpin == NULL) {
			fprintf(stderr, "No such pin \"%s\" in cell \"%s\"!\n",
				token + 1, testcell->name);
		    }
		    else {
			if (testpin->type & OUTPUT) {
			    newconn->next = newinst->out_connects;
			    newinst->out_connects = newconn;
			}
			else {
			    newconn->next = newinst->in_connects;
			    newinst->in_connects = newconn;
			}
		    }
		    newconn->refinst = newinst;
		    newconn->refpin = testpin;
		    newconn->refnet = NULL;
		    newconn->tag = NULL;
		    newconn->metric = -1.0;
		    newconn->prvector = NULL;
		    newconn->pfvector = NULL;
		    newconn->trvector = NULL;
		    newconn->tfvector = NULL;
		    token = advancetoken(fsrc, '(');	// Read to beginning of pin name
		    section = PINCONN;
		}
		else if (*token == ';') {
		    // End of instance record
		    section = GATELIST;
		}
		else if (*token != ',' && *token != ')') {
		    fprintf(stderr, "Unexpected entry in instance pin connection list!\n");
		    token = advancetoken(fsrc, ';');	// Read to end-of-line
		    section = GATELIST;
		}
		break;

	    case PINCONN:
		// Token is net name
		testnet = (netptr)HashLookup(token, Nethash);
		if (testnet == NULL) {
		    // This is a new net, and we need to record it
		    newnet = create_net(netlist);
		    newnet->name = strdup(token);
		    HashPtrInstall(newnet->name, newnet, Nethash);
		    newconn->refnet = newnet;
		}
		else
		    newconn->refnet = testnet;
		section = INSTPIN;
		break;
	}
	if (section == PINCONN)
	    token = advancetoken(fsrc, ')');	// Name token parsing
	else
	    token = advancetoken(fsrc, 0);
    }

    /* Hash table no longer needed */
    HashKill(Nethash);
}

/*--------------------------------------------------------------*/
/* For each net, go through the list of receivers and add the	*/
/* contributions of each to the total load.  This is either	*/
/* the input pin capacitance, if the receiver is a pin, or the	*/
/* designated output load (given on the command line), if the	*/
/* receiver is an output pin.					*/
/*--------------------------------------------------------------*/

void
computeLoads(netptr netlist, instptr instlist, double out_load)
{
    instptr testinst;
    pinptr testpin;
    netptr testnet, driver, loadnet;
    connptr testconn;
    int i;

    for (testnet = netlist; testnet; testnet = testnet->next) {
	for (i = 0; i < testnet->fanout; i++) {
	    testconn = testnet->receivers[i];
	    testpin = testconn->refpin;
	    if (testpin == NULL) {
		testnet->loadr += out_load;
		testnet->loadf += out_load;
	    }
	    else {
		testnet->loadr += testpin->capr;
		testnet->loadf += testpin->capf;
	    }
	}
    }

    // For each instance input pin, collapse the pin's lookup table
    // to a vector by interpolating/extrapolating the table at the
    // calculated output load.  Save this vector in the connection
    // record for the pin.

    for (testinst = instlist; testinst; testinst = testinst->next) {
	loadnet = testinst->out_connects->refnet;
	for (testconn = testinst->in_connects; testconn; testconn = testconn->next) {
	    testpin = testconn->refpin;

	    if (testpin->propdelr)
		testconn->prvector = table_collapse(testpin->propdelr, loadnet->loadr);
	    if (testpin->propdelf)
		testconn->pfvector = table_collapse(testpin->propdelf, loadnet->loadf);
	    if (testpin->transr)
		testconn->trvector = table_collapse(testpin->transr, loadnet->loadr);
	    if (testpin->transf)
		testconn->tfvector = table_collapse(testpin->transf, loadnet->loadf);
	}
    }
}

/*--------------------------------------------------------------*/
/* Assign types to each net.  This identifies which nets are	*/
/* clock inputs, which are latch enable inputs, and which are	*/
/* asynchronous set/reset inputs,.				*/
/*								*/
/* Whenever a clock input to a flop is found, add the		*/
/* connection record to clockedlist				*/
/*								*/
/* For diagnostics, return the number of entries in clockedlist	*/
/*--------------------------------------------------------------*/

int assign_net_types(netptr netlist, connlistptr *clockedlist)
{
    int i, numterms;
    netptr testnet;
    connptr testrcvr;
    pinptr testpin;
    connlistptr newclocked;

    numterms = 0;

    for (testnet = netlist; testnet; testnet = testnet->next) {
	for (i = 0; i < testnet->fanout; i++) {
	    testrcvr = testnet->receivers[i];
	    testpin = testrcvr->refpin;
	    if (testpin == NULL)
		testnet->type |= OUTTERM;
	    else {
		switch (testpin->type & (DFFMASK | LATCHMASK)) {
		    case DFFCLK:
			testnet->type |= CLOCK;
			newclocked = (connlistptr)malloc(sizeof(connlist));
			newclocked->connection = testrcvr;
			newclocked->next = *clockedlist;
			*clockedlist = newclocked;
			numterms++;
			break;
		    case DFFIN:
			testnet->type |= TERMINAL;
			break;
		    case DFFSET:
		    case DFFRST:
			testnet->type |= ASYNC;
			break;
		    case LATCHIN:
			testnet->type |= LATCHTERM;
			break;
		    case LATCHEN:
			testnet->type |= ENABLE;
			break;
		}
	    }
	}
    }
    return numterms;
}

/*--------------------------------------------------------------*/
/* Create the links representing the netlist connections.	*/
/*								*/
/* The verilogRead() routine added all nets and instances, and	*/
/* for each instance, generated a list of net connections to	*/
/* each pin.  To make the netlist easily traversible, this	*/
/* routine does the following:					*/
/*								*/
/* For each instance, work through the list of pin		*/
/*	connections.  If the pin is an output, then add the	*/
/*	connection as the net's driver entry.  If the pin is	*/
/*	an input, then add the connection to the list of the	*/
/*	net's receivers, and increment the net's fanout.	*/
/*								*/
/* For each module input, add the input connection as the	*/
/*	net's driver (flag an error if the net already has a	*/
/*	driver).						*/
/*								*/
/* For each module output, add the output connection as one of	*/
/*	the net's receivers (it may be the only one).		*/
/*								*/
/*--------------------------------------------------------------*/

void
createLinks(netptr netlist, instptr instlist, connptr inputlist, connptr outputlist)
{
    netptr testnet;
    instptr testinst;
    connptr testconn;

    for (testinst = instlist; testinst; testinst = testinst->next) {
	for (testconn = testinst->in_connects; testconn; testconn = testconn->next) {
	    testnet = testconn->refnet;
	    testnet->fanout++;
	    if (testnet->receivers == NULL)
		testnet->receivers = (connptr *)malloc(sizeof(connptr));

	    else
		testnet->receivers = (connptr *)realloc(testnet->receivers,
				testnet->fanout * sizeof(connptr));

	    testnet->receivers[testnet->fanout - 1] = testconn;
	} 

	for (testconn = testinst->out_connects; testconn; testconn = testconn->next) {
	    testnet = testconn->refnet;
	    testnet->driver = testconn;
	} 
    }

    for (testconn = inputlist; testconn; testconn = testconn->next) {
	testnet = testconn->refnet;
	if (testnet->driver != NULL)
	    fprintf(stderr, "Error:  Input pin \"%s\" has an internal driver!\n",
			testnet->name);
	// else
	//    testnet->driver = testconn;	// Don't do this, makes connectivity circular
    }

    for (testconn = outputlist; testconn; testconn = testconn->next) {
	testnet = testconn->refnet;
	testnet->fanout++;
	if (testnet->receivers == NULL)
	    testnet->receivers = (connptr *)malloc(sizeof(connptr));

	else 
	    testnet->receivers = (connptr *)realloc(testnet->receivers,
			testnet->fanout * sizeof(connptr));

	testnet->receivers[testnet->fanout - 1] = testconn;
    }
}

/*--------------------------------------------------------------*/
/* Delay comparison used by qsort() to sort paths in order from	*/
/* longest to shortest propagation delay.			*/
/*--------------------------------------------------------------*/

int
compdelay(ddataptr *a, ddataptr *b)
{
    ddataptr p = *a;
    ddataptr q = *b;

    if (p->delay < q->delay)
	return (1);
    if (p->delay > q->delay)
	return (-1);
    return (0);
}

/*--------------------------------------------------------------*/
/* Main program							*/
/*--------------------------------------------------------------*/

int
main(int objc, char *argv[])
{
    FILE *flib;
    FILE *fsrc;
    double period = 0.0;
    double outLoad = 0.0;
    double inTrans = 0.0;
    char *delayfile = NULL;
    int ival, firstarg = 1;

    // Liberty database

    lutable *tables = NULL;
    cell *cells = NULL;
    lutable *scalar;

    // Verilog netlist database

    instptr     instlist = NULL;
    netptr      netlist = NULL;
    connlistptr clockconnlist = NULL;
    connlistptr newinputconn, inputconnlist = NULL;
    connptr     testconn, inputlist = NULL;
    connptr     outputlist = NULL;

    // Timing path database
    ddataptr	pathlist = NULL;
    ddataptr	freeddata, testddata, *orderedpaths;
    btptr	freebt, testbt;
    int		numpaths, numterms, i;
    char	badtiming;
    double	slack;

    verbose = 0;
    exhaustive = 0;

    while ((firstarg < objc) && (*argv[firstarg] == '-')) {
       if (!strcmp(argv[firstarg], "-d") || !strcmp(argv[firstarg], "--delay")) {
	  delayfile = strdup(argv[firstarg + 1]);
	  firstarg += 2;
       }
       else if (!strcmp(argv[firstarg], "-p") || !strcmp(argv[firstarg], "--period")) {
	  period = strtod(argv[firstarg + 1], NULL);
	  firstarg += 2;
       }
       else if (!strcmp(argv[firstarg], "-l") || !strcmp(argv[firstarg], "--load")) {
	  outLoad = strtod(argv[firstarg + 1], NULL);
	  firstarg += 2;
       }
       else if (!strcmp(argv[firstarg], "-t") || !strcmp(argv[firstarg], "--trans")) {
	  inTrans = strtod(argv[firstarg + 1], NULL);
	  firstarg += 2;
       }
       else if (!strcmp(argv[firstarg], "-v") || !strcmp(argv[firstarg], "--verbose")) {
	  sscanf(argv[firstarg + 1], "%d", &ival);
	  verbose = (unsigned char)ival;
	  firstarg += 2;
       }
       else if (!strcmp(argv[firstarg], "-e") || !strcmp(argv[firstarg], "--exhaustive")) {
	  exhaustive = 1;
	  firstarg++;
       }
       else if (!strcmp(argv[firstarg], "-V") || !strcmp(argv[firstarg], "--version")) {
	  fprintf(stderr, "Vesta Static Timing Analzyer version 0.2\n");
	  exit(0);
       }
       else {
	  fprintf(stderr, "Unknown option \"%s\"\n", argv[firstarg]);
	  firstarg++;
       }
    }

    if (objc - firstarg != 2) {
	fprintf(stderr, "Usage:  vesta [options] <name.v> <name.lib>\n");
	fprintf(stderr, "Options:\n");
	fprintf(stderr, "--delay <delay_file>	or	-d <delay_file>\n");
	fprintf(stderr, "--period <period>	or	-p <period>\n");
	fprintf(stderr, "--load <load>		or	-l <load>\n");
	fprintf(stderr, "--verbose <level>	or	-v <level>\n");
	fprintf(stderr, "--exhaustive		or 	-e\n");
	fprintf(stderr, "--version		or	-V\n");
	exit (1);
    }
    else {
	fflush(stdout);
	fprintf(stdout, "-----------------------------------------\n");
	fprintf(stdout, "Vesta static timing analysis tool\n");
	fprintf(stdout, "(c) 2013 Tim Edwards, Open Circuit Design\n");
	fprintf(stdout, "-----------------------------------------\n\n");
	fflush(stdout);
    }

    fsrc = fopen(argv[firstarg], "r");
    if (fsrc == NULL) {
	fprintf(stderr, "Cannot open %s for reading\n", argv[firstarg]);
	exit (1);
    }

    flib = fopen(argv[firstarg + 1], "r");
    if (flib == NULL) {
	fprintf(stderr, "Cannot open %s for reading\n", argv[firstarg + 1]);
	exit (1);
    }

    /*------------------------------------------------------------------*/
    /* Generate one table template for the "scalar" case		*/
    /*------------------------------------------------------------------*/

    scalar = (lutable *)malloc(sizeof(lutable));
    scalar->name = strdup("scalar");
    scalar->invert = 0;
    scalar->var1 = CONSTRAINED_TIME;
    scalar->var2 = OUTPUT_CAP;
    scalar->size1 = 1;
    scalar->size2 = 1;
    scalar->idx1.times = (double *)malloc(sizeof(double));
    scalar->idx2.caps = (double *)malloc(sizeof(double));
    scalar->values = (double *)malloc(sizeof(double));

    scalar->idx1.times[0] = 0.0;
    scalar->idx2.caps[0] = 0.0;
    scalar->values[0] = 0.0;

    scalar->next = NULL;
    tables = scalar;

    /*------------------------------------------------------------------*/
    /* Read the liberty format file.  This is not a rigorous parser!	*/
    /*------------------------------------------------------------------*/

    fileCurrentLine = 0;
    libertyRead(flib, &tables, &cells);
    fflush(stdout);
    fprintf(stdout, "Lib Read:  Processed %d lines.\n", fileCurrentLine);
    if (flib != NULL) fclose(flib);

    /*--------------------------------------------------*/
    /* Debug:  Print summary of liberty database	*/
    /*--------------------------------------------------*/

    if (verbose > 2) {

	lutable *newtable;
	cell *newcell;
	pin *newpin;

	for (newtable = tables; newtable; newtable = newtable->next) {
	    fprintf(stdout, "Table: %s\n", newtable->name);
	}

	for (newcell = cells; newcell; newcell = newcell->next) {
	    fprintf(stdout, "Cell: %s\n", newcell->name);
	    fprintf(stdout, "   Function: %s\n", newcell->function);
	    for (newpin = newcell->pins; newpin; newpin = newpin->next) {
		if (newpin->type == INPUT)
		    fprintf(stdout, "   Pin: %s  cap=%g\n", newpin->name, newpin->capr);
	    }
	    fprintf(stdout, "\n");
	}
    }

    /*------------------------------------------------------------------*/
    /* Read verilog netlist.  This is also not a rigorous parser!	*/
    /*------------------------------------------------------------------*/

    fileCurrentLine = 0;
    verilogRead(fsrc, cells, &netlist, &instlist, &inputlist, &outputlist);
    fflush(stdout);
    fprintf(stdout, "Verilog netlist read:  Processed %d lines.\n", fileCurrentLine);
    if (fsrc != NULL) fclose(fsrc);

    /*--------------------------------------------------*/
    /* Debug:  Print summary of verilog source		*/
    /*--------------------------------------------------*/

    if (verbose > 1) {
	connect *testoutput;
	connect *testinput;
	net *testnet;
	instance *testinst;

	for (testinput = inputlist; testinput; testinput = testinput->next) {
	    if (testinput->refnet)
		fprintf(stdout, "   Input: %s\n", testinput->refnet->name);
	}
	for (testoutput = outputlist; testoutput; testoutput = testoutput->next) {
	    if (testoutput->refnet)
		fprintf(stdout, "   Output: %s\n", testoutput->refnet->name);
	}
	for (testnet = netlist; testnet; testnet = testnet->next) {
	    fprintf(stdout, "   Net: %s\n", testnet->name);
	}
	for (testinst = instlist; testinst; testinst = testinst->next) {
	    fprintf(stdout, "   Gate: %s\n", testinst->name);
	}
    }

    /*--------------------------------------------------*/
    /* Generate internal links representing the network	*/
    /*--------------------------------------------------*/

    createLinks(netlist, instlist, inputlist, outputlist);

    /* Generate a connection list from inputlist */

    for (testconn = inputlist; testconn; testconn = testconn->next) {
	newinputconn = (connlistptr)malloc(sizeof(connlist));
	newinputconn->connection = testconn;
	newinputconn->next = inputconnlist;
	inputconnlist = newinputconn;
    }

    /*--------------------------------------------------*/
    /* Calculate total load on each net			*/
    /* To do:  Add wire models or computed wire delays	*/
    /*--------------------------------------------------*/

    computeLoads(netlist, instlist, outLoad);

    /*--------------------------------------------------*/
    /* Assign net types, mainly to identify clocks	*/
    /* Return a list of clock nets			*/
    /*--------------------------------------------------*/

    numterms = assign_net_types(netlist, &clockconnlist);

    if (verbose > 1) 
	fprintf(stdout, "Number of terminals to check: %d\n", numterms);

    /*--------------------------------------------------*/
    /* Identify all clock-to-terminal paths		*/
    /*--------------------------------------------------*/

    numpaths = find_clock_to_term_paths(clockconnlist, &pathlist, netlist, MAXIMUM_TIME);
    fprintf(stdout, "Number of paths analyzed:  %d\n", numpaths);

    /*--------------------------------------------------*/
    /* Collect paths into a non-linked array so that	*/
    /* they can be sorted by delay time			*/
    /*--------------------------------------------------*/

    orderedpaths = (ddataptr *)malloc(numpaths * sizeof(ddataptr));

    i = 0;
    for (testddata = pathlist; testddata; testddata = testddata->next) {
       orderedpaths[i] = testddata;
       i++;
    }

    qsort(orderedpaths, numpaths, sizeof(ddataptr), (__compar_fn_t)compdelay);

    /*--------------------------------------------------*/
    /* Report on top 20 maximum delay paths		*/
    /*--------------------------------------------------*/

    fprintf(stdout, "\nTop %d maximum delay paths:\n", (numpaths >= 20) ? 20 : numpaths);
    badtiming = 0;
    for (i = 0; ((i < 20) && (i < numpaths)); i++) {
	testddata = orderedpaths[i];
	for (testbt = testddata->backtrace; testbt->next; testbt = testbt->next);
		
	if (testddata->backtrace->receiver->refinst != NULL) {
	    fprintf(stdout, "Path %s/%s to %s/%s delay %g ps",
			testbt->receiver->refinst->name,
			testbt->receiver->refpin->name,
			testddata->backtrace->receiver->refinst->name,
			testddata->backtrace->receiver->refpin->name,
			testddata->delay);
	}
	else {
	    fprintf(stdout, "Path %s/%s to output pin %s delay %g ps",
			testbt->receiver->refinst->name,
			testbt->receiver->refpin->name,
			testddata->backtrace->receiver->refnet->name,
			testddata->delay);
	}

	if (period > 0.0) {
	    slack = period - testddata->delay;
	    fprintf(stdout, "   Slack = %g ps", slack);
	    if (slack < 0.0) badtiming = 1;
	}
	fprintf(stdout, "\n");
    }

    if (period > 0.0) {
	if (badtiming) {
	    fprintf(stdout, "ERROR:  Design fails timing requirements.\n");
	}
	else {
	    fprintf(stdout, "Design meets timing requirements.\n");
	}
    }
    else if (orderedpaths[0] != NULL) {
	fprintf(stdout, "Computed maximum clock frequency (zero slack) = %g MHz\n",
		(1.0E6 / orderedpaths[0]->delay));
    }
    fprintf(stdout, "-----------------------------------------\n\n");
    fflush(stdout);

    /*--------------------------------------------------*/
    /* Clean up the path list				*/
    /*--------------------------------------------------*/

    while (pathlist != NULL) {
	freeddata = pathlist;
	pathlist = pathlist->next;
	while (freeddata->backtrace != NULL) {
	    freebt = freeddata->backtrace;
	    freeddata->backtrace = freeddata->backtrace->next;
	    freebt->refcnt--;
	    if (freebt->refcnt == 0) free(freebt);
	}
	free(freeddata);
    }

    free(orderedpaths);

    /*--------------------------------------------------*/
    /* Now calculate minimum delay paths		*/
    /*--------------------------------------------------*/

    numpaths = find_clock_to_term_paths(clockconnlist, &pathlist, netlist, MINIMUM_TIME);
    fprintf(stdout, "Number of paths analyzed:  %d\n", numpaths);

    /*--------------------------------------------------*/
    /* Collect paths into a non-linked array so that	*/
    /* they can be sorted by delay time			*/
    /*--------------------------------------------------*/

    orderedpaths = (ddataptr *)malloc(numpaths * sizeof(ddataptr));

    i = 0;
    for (testddata = pathlist; testddata; testddata = testddata->next) {
       orderedpaths[i] = testddata;
       i++;
    }

    qsort(orderedpaths, numpaths, sizeof(ddataptr), (__compar_fn_t)compdelay);

    /*--------------------------------------------------*/
    /* Report on top 20 minimum delay paths		*/
    /*--------------------------------------------------*/

    fprintf(stdout, "\nTop %d minimum delay paths:\n", (numpaths >= 20) ? 20 : numpaths);
    badtiming = 0;
    for (i = numpaths; (i > (numpaths - 20)) && (i > 0); i--) {
	testddata = orderedpaths[i - 1];
	for (testbt = testddata->backtrace; testbt->next; testbt = testbt->next);
		
	if (testddata->backtrace->receiver->refinst != NULL) {
	    fprintf(stdout, "Path %s/%s to %s/%s delay %g ps\n",
			testbt->receiver->refinst->name,
			testbt->receiver->refpin->name,
			testddata->backtrace->receiver->refinst->name,
			testddata->backtrace->receiver->refpin->name,
			testddata->delay);
	}
	else {
	    fprintf(stdout, "Path %s/%s to output pin %s delay %g ps\n",
			testbt->receiver->refinst->name,
			testbt->receiver->refpin->name,
			testddata->backtrace->receiver->refnet->name,
			testddata->delay);
	}

	if (testddata->delay < 0.0) badtiming = 1;
    }
    if (badtiming)
	fprintf(stdout, "ERROR:  Design fails minimum hold timing.\n");
    else
	fprintf(stdout, "Design meets minimum hold timing.\n");

    fprintf(stdout, "-----------------------------------------\n\n");
    fflush(stdout);

    /*--------------------------------------------------*/
    /* Clean up the path list				*/
    /*--------------------------------------------------*/

    while (pathlist != NULL) {
	freeddata = pathlist;
	pathlist = pathlist->next;
	while (freeddata->backtrace != NULL) {
	    freebt = freeddata->backtrace;
	    freeddata->backtrace = freeddata->backtrace->next;
	    freebt->refcnt--;
	    if (freebt->refcnt == 0) free(freebt);
	}
	free(freeddata);
    }

    free(orderedpaths);

    for (testconn = inputlist; testconn; testconn = testconn->next) {
	testconn->tag = NULL;
	testconn->metric = -1;
    }

    /*--------------------------------------------------*/
    /* Identify all input-to-terminal paths		*/
    /*--------------------------------------------------*/

    numpaths = find_clock_to_term_paths(inputconnlist, &pathlist, netlist, MAXIMUM_TIME);
    fprintf(stdout, "Number of paths analyzed:  %d\n", numpaths);

    /*--------------------------------------------------*/
    /* Collect paths into a non-linked array so that	*/
    /* they can be sorted by delay time			*/
    /*--------------------------------------------------*/

    orderedpaths = (ddataptr *)malloc(numpaths * sizeof(ddataptr));

    i = 0;
    for (testddata = pathlist; testddata; testddata = testddata->next) {
       orderedpaths[i] = testddata;
       i++;
    }

    qsort(orderedpaths, numpaths, sizeof(ddataptr), (__compar_fn_t)compdelay);

    /*--------------------------------------------------*/
    /* Report on top 20 maximum delay paths		*/
    /*--------------------------------------------------*/

    fprintf(stdout, "\nTop %d maximum delay paths:\n", (numpaths >= 20) ? 20 : numpaths);
    for (i = 0; ((i < 20) && (i < numpaths)); i++) {
	testddata = orderedpaths[i];
	for (testbt = testddata->backtrace; testbt->next; testbt = testbt->next);
		
	if (testddata->backtrace->receiver->refinst != NULL) {
	    fprintf(stdout, "Path input pin %s to %s/%s delay %g ps\n",
			testbt->receiver->refnet->name,
			testddata->backtrace->receiver->refinst->name,
			testddata->backtrace->receiver->refpin->name,
			testddata->delay);
	}
	else {
	    fprintf(stdout, "Path input pin %s to output pin %s delay %g ps\n",
			testbt->receiver->refnet->name,
			testddata->backtrace->receiver->refnet->name,
			testddata->delay);
	}
    }

    fprintf(stdout, "-----------------------------------------\n\n");
    fflush(stdout);

    /*--------------------------------------------------*/
    /* Clean up the path list				*/
    /*--------------------------------------------------*/

    while (pathlist != NULL) {
	freeddata = pathlist;
	pathlist = pathlist->next;
	while (freeddata->backtrace != NULL) {
	    freebt = freeddata->backtrace;
	    freeddata->backtrace = freeddata->backtrace->next;
	    freebt->refcnt--;
	    if (freebt->refcnt == 0) free(freebt);
	}
	free(freeddata);
    }

    free(orderedpaths);

    for (testconn = inputlist; testconn; testconn = testconn->next) {
	testconn->tag = NULL;
	testconn->metric = 1E50;
    }

    /*--------------------------------------------------*/
    /* Now calculate minimum delay paths from inputs	*/
    /*--------------------------------------------------*/

    numpaths = find_clock_to_term_paths(inputconnlist, &pathlist, netlist, MINIMUM_TIME);
    fprintf(stdout, "Number of paths analyzed:  %d\n", numpaths);

    /*--------------------------------------------------*/
    /* Collect paths into a non-linked array so that	*/
    /* they can be sorted by delay time			*/
    /*--------------------------------------------------*/

    orderedpaths = (ddataptr *)malloc(numpaths * sizeof(ddataptr));

    i = 0;
    for (testddata = pathlist; testddata; testddata = testddata->next) {
       orderedpaths[i] = testddata;
       i++;
    }

    qsort(orderedpaths, numpaths, sizeof(ddataptr), (__compar_fn_t)compdelay);

    /*--------------------------------------------------*/
    /* Report on top 20 minimum delay paths		*/
    /*--------------------------------------------------*/

    fprintf(stdout, "\nTop %d minimum delay paths:\n", (numpaths >= 20) ? 20 : numpaths);
    for (i = numpaths; (i > (numpaths - 20)) && (i > 0); i--) {
	testddata = orderedpaths[i - 1];
	for (testbt = testddata->backtrace; testbt->next; testbt = testbt->next);
		
	if (testddata->backtrace->receiver->refinst != NULL) {
	    fprintf(stdout, "Path input pin %s to %s/%s delay %g ps\n",
			testbt->receiver->refnet->name,
			testddata->backtrace->receiver->refinst->name,
			testddata->backtrace->receiver->refpin->name,
			testddata->delay);
	}
	else {
	    fprintf(stdout, "Path input pin %s to output pin %s delay %g ps\n",
			testbt->receiver->refnet->name,
			testddata->backtrace->receiver->refnet->name,
			testddata->delay);
	}
    }

    fprintf(stdout, "-----------------------------------------\n\n");
    fflush(stdout);

    /*--------------------------------------------------*/
    /* Clean up the path list				*/
    /*--------------------------------------------------*/

    while (pathlist != NULL) {
	freeddata = pathlist;
	pathlist = pathlist->next;
	while (freeddata->backtrace != NULL) {
	    freebt = freeddata->backtrace;
	    freeddata->backtrace = freeddata->backtrace->next;
	    freebt->refcnt--;
	    if (freebt->refcnt == 0) free(freebt);
	}
	free(freeddata);
    }

    free(orderedpaths);

    return 0;
}
