/*
 * ext2spice.c --
 *
 * Program to flatten hierarchical .ext files and produce
 * a .spice file, suitable for use as input to spiceulators
 * such as spice and hspice.
 *
 * Flattens the tree rooted at file.ext, reading in additional .ext
 * files as specified by "use" lines in file.ext.  The output is left
 * in file.spice, unless '-o esSpiceFile' is specified, in which case the
 * output is left in 'esSpiceFile'.
 *
 */

#ifndef lint
static char rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-7.4/ext2spice/ext2spice.c,v 1.3 2006/08/13 21:43:30 tim Exp $";
#endif  /* not lint */

#include <stdio.h>
#include <stdlib.h>		/* for atof() */
#include <string.h>
#include <ctype.h>

#include "tcltk/tclmagic.h"
#include "utils/magic.h"
#include "utils/malloc.h"
#include "utils/geometry.h"
#include "utils/hash.h"
#include "utils/dqueue.h"
#include "utils/utils.h"
#include "tiles/tile.h"
#ifdef MAGIC_WRAPPER
#include "database/database.h"
#include "windows/windows.h"
#include "textio/textio.h"
#include "dbwind/dbwind.h"	/* for DBWclientID */
#include "commands/commands.h"  /* for module auto-load */
#include "textio/txcommands.h"
#endif
#include "extflat/extflat.h"
#include "extflat/EFint.h"
#include "extract/extract.h"	/* for extDevTable */
#include "utils/runstats.h"

/* Forward declarations */
#ifdef MAGIC_WRAPPER
void CmdExtToSpice();
#endif
int spcmainArgs();
int spccapVisit(), spcdevVisit(), spcnodeVisit(), subcktVisit(), spcresistVisit(), 
    devMergeVisit(), devDistJunctVisit();
int subcktUndef();
EFNode *spcdevSubstrate();
char *nodeSpiceName();

#define AUTO	2	/* TRUE | FALSE | AUTO for esDoSubckt */

/* Options specific to ext2spice */
bool esDoExtResis = FALSE;
bool esDoPorts = TRUE;
bool esDoResistorTee = FALSE;
int  esDoSubckt = AUTO;
bool esDevNodesOnly = FALSE;
bool esNoAttrs = FALSE;
bool esHierAP = FALSE;
char spcesDefaultOut[FNSIZE];
int  esCapAccuracy = 1;
char esCapFormat[FNSIZE];
char *spcesOutName = spcesDefaultOut;
FILE *esSpiceF = NULL;
float esScale = -1.0 ; /* negative if hspice the EFScale/100 otherwise */

#define	SPICE2	0
#define	SPICE3	1
#define	HSPICE	2

static unsigned short esFormat = SPICE3 ;

int esCapNum, esDevNum, esResNum, esDiodeNum;
int esNodeNum;  /* just in case we' re extracting spice2 */
int esSbckNum; 	/* used in hspice node name shortening   */
int esNoModelType;  /* index for device type "None" (model-less device) */

/*
 * The following hash table and associated functions are used only if 
 * the format is hspice, to keep the translation between the hierarchical 
 * prefix of a node and the x num that we use to output valid hspice 
 * which also are meaningful.
 */
HashTable subcktNameTable ; /* the hash table itself */
DQueue    subcktNameQueue ; /* q used to print it sorted at the end*/


#define MAX_STR_SIZE (1<<11) /* 2K should be enough for keeping temp
                                names even of the most complicated design */

struct {
   short resClassSD ;  /* the resistance class of the src/drn of the dev */
   short resClassSub ; /* the resistance class of the substrate of the dev */
   char  *defSubs ;    /* the default substrate node */
} fetInfo[MAXDEVTYPES];


/* Node clients for figuring out areas and perimeters of sources and drains */
typedef struct { 
	long	_duml:MAXDEVTYPES; 
} _dum; /* if you get an error here you should change 
           the data structures and visitMask */

typedef union {
	long	visitMask; /* mask for normal visits */
	float	*widths; /* width used for distributing area perim */
} maskOrWidth ;

typedef struct {
	char		*spiceNodeName;
	maskOrWidth	m_w;
} nodeClient;

typedef struct {
	HierName 	*lastPrefix;
	maskOrWidth	m_w;
} nodeClientHier;

#define	NO_RESCLASS	-1

#define markVisited(client, rclass) \
  { (client)->m_w.visitMask |= (1<<rclass); }

#define clearVisited(client) \
   { (client)->m_w.visitMask = (long)0; }

#define beenVisited(client, rclass)  \
   ( (client)->m_w.visitMask & (1<<rclass))

/*
 * Used to mark the nodes which are connected to devs. initMask  is set to
 * DEV_CONNECT_MASK only when we are in visitDevs 
 */
#define	DEV_CONNECT_MASK	((unsigned long)1<<(sizeof(long)*BITSPERCHAR-1))
unsigned long	initMask = 0;

#define initNodeClient(node) \
{ \
	(node)->efnode_client = (ClientData) mallocMagic((unsigned) (sizeof(nodeClient))); \
	 (( nodeClient *)(node)->efnode_client)->spiceNodeName = NULL; \
	(( nodeClient *)(node)->efnode_client)->m_w.visitMask = (unsigned long)initMask; \
}


#define initNodeClientHier(node) \
{ \
	(node)->efnode_client = (ClientData) mallocMagic((unsigned)(sizeof(nodeClientHier))); \
	((nodeClientHier *) (node)->efnode_client)->m_w.visitMask = (unsigned long) 0; \
}



/* attributes controlling the Area/Perimeter extraction of dev terminals */
#define ATTR_FLATAP	"*[Ee][Xx][Tt]:[Aa][Pp][Ff]*"
#define ATTR_HIERAP	"*[Ee][Xx][Tt]:[Aa][Pp][Hh]*"
#define ATTR_SUBSAP	"*[Ee][Xx][Tt]:[Aa][Pp][Ss]*"

bool esMergeDevsA = FALSE; /* aggresive merging of devs L1=L2 merge them */
bool esMergeDevsC = FALSE; /* conservative merging of devs L1=L2 and W1=W2 */
			   /* used with the hspice multiplier */
bool esDistrJunct = FALSE;

/* 
 *---------------------------------------------------------
 * Variables & macros used for merging parallel devs       
 * The merging of devs is based on the fact that spcdevVisit 
 * visits the devs in the same order all the time so the 
 * value of esFMult[i] keeps the multiplier for the ith dev
 *---------------------------------------------------------
 */
#define	DEV_KILLED	((float) -1.0)
#define	FMULT_SIZE	(1<<10)

static float	*esFMult = NULL;         /* the array itself */
static int	 esFMIndex = 0;          /* current index to it */
static int	 esFMSize = FMULT_SIZE ; /* its current size (growable) */

static int	 esDevsMerged;

#define	devIsKilled(n) ( esFMult[(n)] <=(float)0.0 )

#define	DEV_KILLED	((float) -1.0)

int nodeVisitDebug();

/* macro to add a dev's multiplier to the table and grow it if necessary */
#define addDevMult(f) \
{  \
	if ( esFMult == NULL ) { \
	  esFMult = (float *) mallocMagic((unsigned) (esFMSize*sizeof(float)));  \
	} else if ( esFMIndex >= esFMSize ) {  \
	  int i;  \
	  float *op = esFMult ;  \
	  esFMult = (float *) mallocMagic((unsigned) ((esFMSize = esFMSize*2)*sizeof(float))); \
	  for ( i = 0 ; i < esFMSize/2 ; i++ ) esFMult[i] = op[i]; \
	  if (op) freeMagic(op); \
	}  \
	esFMult[esFMIndex++] = (float)(f); \
}

#define	setDevMult(i,f) { esFMult[(i)] = (float)(f); }

#define	getCurDevMult() ((esFMult) ? esFMult[esFMIndex-1] : (float)1.0)

/* cache list used to find parallel devs */
typedef struct _devMerge {
	int     l, w;
	EFNode *g, *s, *d, *b;
	Dev * dev;
	int	  esFMIndex;
	HierName *hierName;
	struct _devMerge *next;
} devMerge;

devMerge *devMergeList = NULL ;



void topVisit();

#ifdef MAGIC_WRAPPER

#define 	atoCap(s)	((EFCapValue)atof(s))

/* Forward declaration */
int _ext2spice_start();

/*
 * ----------------------------------------------------------------------------
 *
 * Tcl package initialization function
 *
 * ----------------------------------------------------------------------------
 */

int
Exttospice_Init(interp)
    Tcl_Interp *interp;
{
    /* Sanity checks! */
    if (interp == NULL) return TCL_ERROR;
    if (Tcl_PkgRequire(interp, "Tclmagic", MAGIC_VERSION, 0) == NULL)
	return TCL_ERROR;
    if (Tcl_InitStubs(interp, "8.1", 0) == NULL) return TCL_ERROR;

    TxPrintf("Auto-loading EXTTOSPICE module\n");
    TxFlushOut();

    /* Replace the auto-load function with the one defined in	*/
    /* this package in the command functions list.		*/

    if (WindReplaceCommand(DBWclientID, "exttospice", CmdExtToSpice) < 0)
	return TCL_ERROR;

    /* Add "ext2spice" as an alias for "exttospice" */
    if (WindReplaceCommand(DBWclientID, "ext2spice", CmdExtToSpice) < 0)
	return TCL_ERROR;

    Tcl_PkgProvide(interp, "Exttospice", MAGIC_VERSION);
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------------
 *
 * Main callback for command "magic::exttospice"
 *
 * ----------------------------------------------------------------------------
 */

#define EXTTOSPC_RUN		0
#define EXTTOSPC_DEFAULT	1
#define EXTTOSPC_FORMAT		2
#define EXTTOSPC_RTHRESH	3
#define EXTTOSPC_CTHRESH	4
#define EXTTOSPC_MERGE		5
#define EXTTOSPC_EXTRESIST	6
#define EXTTOSPC_RESISTORTEE	7
#define EXTTOSPC_SUBCIRCUITS	8
#define EXTTOSPC_HELP		9

void
CmdExtToSpice(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
    int i,flatFlags;
    char *inName;
    FILE *f;

    int value;
    int option = EXTTOSPC_RUN;
    int argc = cmd->tx_argc;
    char **argv = cmd->tx_argv;
    char **msg;
    bool err_result, locDoSubckt;
    char *resstr;

    short sd_rclass;
    short sub_rclass;
    char *devname;
    char *subname;
    int idx, idx2;

    static EFCapValue LocCapThreshold = 2;
    static int LocResistThreshold = INFINITE_THRESHOLD; 

    static char *cmdExtToSpcOption[] = {
	"[run] [options]	run exttosim on current cell\n"
	"			use \"run -help\" to get standard options",
	"default		reset to default values",
	"format [<type>]	set output format",
	"rthresh [<value>]	set resistance threshold value",
	"cthresh [<value>]	set capacitance threshold value",
	"merge [<type>]		merge parallel transistors",
	"extresist [on|off]	incorporate information from extresist",
	"resistor tee [on|off]	model resistor capacitance as a T-network",
	"subcircuits [on|off]	standard cells become subcircuit calls",
	"help			print help information",
	NULL
    };

    static char *cmdMergeTypes[] = {
	"none			don't merge parallel devices",
	"conservative		merge devices with same L, W",
	"aggressive		merge devices with same L",
	NULL
    };

    static char *cmdExtToSpcFormat[] = {
	"spice2",
	"spice3",
	"hspice",
	NULL
    };

    static char *yesno[] = {
	"yes",
	"true",
	"on",
	"no",
	"false",
	"off",
	"automatic", 
	"top",
	"descend",
	NULL
    };

    typedef enum {
	IDX_YES, IDX_TRUE, IDX_ON, IDX_NO, IDX_FALSE, IDX_OFF,
	IDX_AUTO, IDX_TOP, IDX_DESCEND
    } yesnoType;  

    esNoModelType = -1;

    if (cmd->tx_argc > 1)
    {
	option = Lookup(cmd->tx_argv[1], cmdExtToSpcOption);
	if (option < 0) option = EXTTOSPC_RUN;
	else argv++;
    }

    switch (option)
    {
	case EXTTOSPC_EXTRESIST:
	    if (cmd->tx_argc == 2)
	    {
		Tcl_SetResult(magicinterp, (esDoExtResis) ? "on" : "off", NULL);
		return;
	    }
	    else if (cmd->tx_argc != 3)
		goto usage;
	    idx = Lookup(cmd->tx_argv[2], yesno);
	    if (idx < 0) goto usage;
	    else if (idx < 3) esDoExtResis = TRUE;
	    else esDoExtResis = FALSE;
	    break;

	case EXTTOSPC_RESISTORTEE:
	    if (cmd->tx_argc == 3)
	    {
		Tcl_SetResult(magicinterp, (esDoResistorTee) ? "on" : "off", NULL);
		return;
	    }
	    else if (cmd->tx_argc != 4)
		goto usage;
	    idx = Lookup(cmd->tx_argv[3], yesno);
	    if (idx < 0) goto usage;
	    else if (idx < 3) esDoResistorTee = TRUE;
	    else esDoResistorTee = FALSE;
	    break;

	case EXTTOSPC_SUBCIRCUITS:
	    if (cmd->tx_argc == 2)
	    {
		Tcl_SetResult(magicinterp, (esDoPorts) ? "on" : "off", NULL);
		return;
	    }
	    idx = Lookup(cmd->tx_argv[2], yesno);
	    switch (idx) {
		case IDX_YES: case IDX_TRUE: case IDX_ON:
		    esDoPorts = TRUE;
		    return;
		    break;
		case IDX_NO: case IDX_FALSE: case IDX_OFF:
		    esDoPorts = FALSE;
		    return;
		    break;
		case IDX_DESCEND:
		    if (cmd->tx_argc == 3)
		    {
			Tcl_SetResult(magicinterp, (esDoPorts) ? "on" : "off", NULL);
			return;
		    }
		    break;
		case IDX_TOP:
		    if (cmd->tx_argc == 3)
		    {
			Tcl_SetResult(magicinterp, (esDoSubckt) ? "on" : "off", NULL);
			return;
		    }
		    break;
		case IDX_AUTO:
		default:
		    goto usage;
		    break;
	    }

	    if (cmd->tx_argc != 4) goto usage;
	    idx2 = Lookup(cmd->tx_argv[3], yesno);
	    switch (idx2) {
		case IDX_YES: case IDX_TRUE: case IDX_ON:
		    if (idx == IDX_DESCEND)
			esDoPorts = TRUE;
		    else
			esDoSubckt = TRUE;
		    break;
		case IDX_NO: case IDX_FALSE: case IDX_OFF:
		    if (idx == IDX_DESCEND)
			esDoPorts = FALSE;
		    else
			esDoSubckt = FALSE;
		    break;
		case IDX_AUTO:
		    esDoSubckt = AUTO;
		    break;
		case IDX_DESCEND:
		case IDX_TOP:
		default:
		    goto usage;
	    }
	    break;

	case EXTTOSPC_FORMAT:
	    if (cmd->tx_argc == 2)
	    {
		Tcl_SetResult(magicinterp, cmdExtToSpcFormat[esFormat], NULL);
		return;
	    }
	    else if (cmd->tx_argc < 3) goto usage;
	    idx = Lookup(cmd->tx_argv[2], cmdExtToSpcFormat);
	    if (idx < 0)
	    {
		Tcl_SetResult(magicinterp, "Bad format type.  Formats are:"
			"spice2, spice3, and hspice.", NULL);
		return;
	    }
	    else esFormat = idx;
	    break;

	case EXTTOSPC_CTHRESH:
	    if (cmd->tx_argc == 2)
	    {
		if (LocCapThreshold == (EFCapValue)INFINITE_THRESHOLD)
		    Tcl_SetResult(magicinterp, "infinite", NULL);
		else
		    Tcl_SetObjResult(magicinterp,
			Tcl_NewDoubleObj((double)LocCapThreshold));
		return;
	    }
	    else if (cmd->tx_argc < 3) goto usage;
	
	    /* Note that strtod() (called by StrIsNumeric()) accepts	*/
	    /* "infinite" as a valid numerical value;  however, the	*/
	    /* conversion to C type INF is *not* INFINITE_THRESHOLD, so	*/
	    /* we need to check this case first. . . 			*/

	    if (!strncmp(cmd->tx_argv[2], "inf", 3))
		LocCapThreshold = (EFCapValue)INFINITE_THRESHOLD;
	    else if (StrIsNumeric(cmd->tx_argv[2]))
		LocCapThreshold = atoCap(cmd->tx_argv[2]);
	    else 
		TxError("exttospice: numeric value or \"infinite\" expected.\n");
	    break;

	case EXTTOSPC_RTHRESH:
	    if (cmd->tx_argc == 2)
	    {
		if (LocResistThreshold == INFINITE_THRESHOLD)
		    Tcl_SetResult(magicinterp, "infinite", NULL);
		else
		    Tcl_SetObjResult(magicinterp,
			Tcl_NewIntObj(LocResistThreshold));
		return;
	    }
	    else if (cmd->tx_argc < 3) goto usage;
	    if (StrIsInt(cmd->tx_argv[2]))
		LocResistThreshold = atoi(cmd->tx_argv[2]);
	    else if (!strncmp(cmd->tx_argv[2], "inf", 3))
		LocResistThreshold = INFINITE_THRESHOLD;
	    else 
		TxError("exttospice: integer value or \"infinite\" expected.\n");
	    break;

	case EXTTOSPC_MERGE:
	    if (cmd->tx_argc == 2)
	    {
		if (esMergeDevsA)
		    Tcl_SetResult(magicinterp, "aggressive", NULL);
		else if (esMergeDevsC)
		    Tcl_SetResult(magicinterp, "conservative", NULL);
		else
		    Tcl_SetResult(magicinterp, "none", NULL);
		return;
	    }
	    else if (cmd->tx_argc < 3) goto usage;
	    value = Lookup(cmd->tx_argv[2], cmdMergeTypes);
	    if (value < 0)
	    {
		TxError("Merge types are:\n");
		for (msg = &(cmdMergeTypes[0]); *msg != NULL; msg++)
		    TxPrintf("    %s\n", *msg);
	    }
	    else switch (value) {
		case 0:
		    esMergeDevsA = FALSE;
		    esMergeDevsC = FALSE;
		    break;
		case 1:
		    esMergeDevsA = FALSE;
		    esMergeDevsC = TRUE;
		    break;
		case 2:
		    esMergeDevsA = TRUE;
		    esMergeDevsC = FALSE;
		    break;
	    }
	    break;
	    
	case EXTTOSPC_DEFAULT:
	    LocCapThreshold = 2;
	    LocResistThreshold = INFINITE_THRESHOLD;
	    EFTrimFlags = 0;
	    EFScale = 0;
	    if (EFArgTech)
	    {
		freeMagic(EFArgTech);
		EFArgTech = NULL;
	    }
	    if (EFSearchPath)
	    {
		freeMagic(EFSearchPath);
		EFSearchPath = NULL;
	    }
	    break;

	case EXTTOSPC_RUN:
	    goto runexttospice;
	    break;

	case EXTTOSPC_HELP:
usage:
	    for (msg = &(cmdExtToSpcOption[0]); *msg != NULL; msg++)
	    {
		TxPrintf("    %s\n", *msg);
	    }
	    break;
    }
    return;

runexttospice:

    /* Reset the device indices */
    esCapNum  = 0;
    esDevNum = 1000;
    esResNum = 0;
    esDiodeNum = 0;
    esSbckNum = 0;
    esNodeNum = 10; /* just in case we're extracting spice2 */
    esFMIndex = 0;
    esDevsMerged = 0;

    EFInit();

    EFResistThreshold = LocResistThreshold;
    EFCapThreshold = LocCapThreshold;

    /* Process command line arguments */

    inName = EFArgs(argc, argv, &err_result, spcmainArgs, (ClientData) NULL);
    if (err_result == TRUE)
    {
	EFDone();
	return;
    }

    if (inName == NULL)
    {
	/* Assume that we want to do exttospice on the currently loaded cell */
	
	if (w == (MagWindow *) NULL)
	{
	    windCheckOnlyWindow(&w, DBWclientID);
	}

	if (w == (MagWindow *) NULL)
	{
	    TxError("Point to a window or specify a cell name.\n");
	    return;
	}
	if ((inName = ((CellUse *) w->w_surfaceID)->cu_def->cd_name) == NULL)
	{
	    TxError("No cell present\n");
	    return;
	}
    }

    /*
     * Initializations specific to this program.
     * Make output name inName.spice if they weren't 
     * explicitly specified
     */

    if (spcesOutName == spcesDefaultOut)
	sprintf(spcesDefaultOut, "%s.spice", inName);

    if ((esSpiceF = fopen(spcesOutName, "w")) == NULL)
    {
	char *tclres = Tcl_Alloc(128);
	sprintf(tclres, "exttospice: Unable to open file %s for writing\n",
		spcesOutName);
	Tcl_SetResult(magicinterp, tclres, TCL_DYNAMIC);
	EFDone();
        return;
    }

    /* Read the hierarchical description of the input circuit */
    if (EFReadFile(inName, TRUE, esDoExtResis, FALSE) == FALSE)
    {
	EFDone();
        return;
    }

    if (EFStyle == NULL)
    {
        TxError("Warning:  Current extraction style does not match .ext file!\n");
        TxError("Area/Perimeter values and parasitic values will be zero.\n");
    }

    /* create default devinfo entries (MOSIS) which can be overridden by
       the command line arguments */

    for ( i = 0 ; i < MAXDEVTYPES ; i++ ) {
	fetInfo[i].resClassSD = NO_RESCLASS;
	fetInfo[i].resClassSub = NO_RESCLASS;
	fetInfo[i].defSubs = NULL;
    }

    /* Get fetInfo information from the current extraction style 	 */
    /* (this works only for the Tcl version with the embedded exttospice */
    /* command)								 */

    idx = 0;
    while (ExtGetDevInfo(idx++, &devname, &sd_rclass, &sub_rclass, &subname))
    {
	if (idx == MAXDEVTYPES)
	{
	    TxError("Error:  Ran out of space for device types!\n");
	    break;
	}
	i = efBuildAddStr(EFDevTypes, &EFDevNumTypes, MAXDEVTYPES, devname);
	if (!strcmp(devname, "None"))
	    esNoModelType = i;
	if (EFStyle != NULL)
	{
	    fetInfo[i].resClassSD = sd_rclass;
	    fetInfo[i].resClassSub = sub_rclass;
	    fetInfo[i].defSubs = subname;
	}

	/* Tcl variable substitution for substrate node names */
	if (subname && (subname[0] == '$'))
	{
	    resstr = (char *)Tcl_GetVar(magicinterp, &subname[1],
			TCL_GLOBAL_ONLY);
	    if (resstr != NULL) fetInfo[i].defSubs = resstr;
	}
    }

    /* Keep a pointer to the "GND" variable, if it exists. */

    resstr = (char *)Tcl_GetVar(magicinterp, "GND", TCL_GLOBAL_ONLY);
    if (resstr == NULL) resstr = "GND";		/* default value */

    /* Write the output file */

    fprintf(esSpiceF, "* %s file created from %s.ext - technology: %s\n\n",
	    (esFormat == SPICE2) ? "SPICE2" :  
	      ( (esFormat == SPICE3) ? "SPICE3" : "HSPICE" ),  
	    inName, EFTech);
    if ( esFormat == HSPICE ) 
    	fprintf(esSpiceF,".option scale=%gu\n\n", ((float) EFScale)/100.0);
    else esScale = EFScale/100.0;

    /* Convert the hierarchical description to a flat one */
    flatFlags = EF_FLATNODES;
    EFTrimFlags |= EF_TRIMGLOB;
    if (EFCapThreshold < INFINITE_THRESHOLD) flatFlags |= EF_FLATCAPS;
    if (esFormat == HSPICE ) {
	EFTrimFlags |= EF_TRIMLOCAL ;
	HashInit(&subcktNameTable, 32, HT_STRINGKEYS);
#ifndef UNSORTED_SUBCKT
	DQInit(&subcktNameQueue, 64);
#endif
    }
    EFFlatBuild(inName, flatFlags);

    /* Determine if this is a subcircuit */
    locDoSubckt = FALSE;
    if (esDoSubckt == AUTO) {
	if (efFlatRootDef->def_flags & DEF_SUBCIRCUIT)
	    locDoSubckt = TRUE;
    }
    if ((esDoSubckt == TRUE) || (locDoSubckt == TRUE))
	topVisit(efFlatRootDef);

    /* If we don't want to write subcircuit calls, remove the	*/
    /* subcircuit flag from all cells at this time.		*/

    if (!esDoPorts)
	EFVisitSubcircuits(subcktUndef, (ClientData) NULL);

    initMask = ( esDistrJunct  ) ? (unsigned long)0 : DEV_CONNECT_MASK;

    if (esMergeDevsA || esMergeDevsC)
    {
	EFVisitDevs(devMergeVisit, (ClientData) NULL);
	TxPrintf("Devs merged: %d\n", esDevsMerged);
	esFMIndex = 0;
	{
	    devMerge *p;

	    for (p = devMergeList; p != NULL; p = p->next)
		freeMagic(p);
	    devMergeList = NULL;
	}
    }
    else if (esDistrJunct)
     	EFVisitDevs(devDistJunctVisit, (ClientData) NULL);
    EFVisitDevs(spcdevVisit, (ClientData) NULL);
    initMask = (unsigned long) 0;
    if (flatFlags & EF_FLATCAPS)
    {
	(void) sprintf( esCapFormat,  "C%%d %%s %%s %%.%dlffF\n", esCapAccuracy);
	EFVisitCaps(spccapVisit, (ClientData) NULL);
    }
    EFVisitResists(spcresistVisit, (ClientData) NULL);
    EFVisitSubcircuits(subcktVisit, (ClientData) NULL);
    (void) sprintf( esCapFormat, "C%%d %%s %s %%.%dlffF%%s", resstr, esCapAccuracy);
    EFVisitNodes(spcnodeVisit, (ClientData) NULL);

    if ((esDoSubckt == TRUE) || (locDoSubckt == TRUE))
	fprintf(esSpiceF, ".ends\n");

    if (esFormat == HSPICE) 
	printSubcktDict();

    EFFlatDone(); 
    EFDone();

    if (esSpiceF) fclose(esSpiceF);

    TxPrintf("exttospice finished.\n");
    return;
}

#else	/* MAGIC_WRAPPER */

/*
 * ----------------------------------------------------------------------------
 *
 * main --
 *
 * Top level of ext2spice.
 *
 * ----------------------------------------------------------------------------
 */

int
main(argc, argv)
    int argc;
    char *argv[];
{

    int i,flatFlags;
    char *inName;
    FILE *f;
    bool locDoSubckt;

    esDevsMerged = 0;

    EFInit();
    EFResistThreshold = INFINITE_THRESHOLD ;
    /* create default devinfo entries (MOSIS) which can be overriden by
       the command line arguments */
    for ( i = 0 ; i < MAXDEVTYPES ; i++ ) {
	fetInfo[i].resClassSD = NO_RESCLASS;
	fetInfo[i].resClassSub = NO_RESCLASS;
	fetInfo[i].defSubs = NULL;
    }
    i = efBuildAddStr(EFDevTypes, &EFDevNumTypes, MAXDEVTYPES, "ndev");
    fetInfo[i].resClassSD = 0 ;
    fetInfo[i].resClassSub = NO_RESCLASS ;
    fetInfo[i].defSubs = "Gnd!";
    i = efBuildAddStr(EFDevTypes, &EFDevNumTypes, MAXDEVTYPES, "pdev");
    fetInfo[i].resClassSD = 1 ;
    fetInfo[i].resClassSub = 8 ;
    fetInfo[i].defSubs = "Vdd!";
    i = efBuildAddStr(EFDevTypes, &EFDevNumTypes, MAXDEVTYPES, "nmos");
    fetInfo[i].resClassSD = 0 ;
    fetInfo[i].resClassSub = NO_RESCLASS ;
    fetInfo[i].defSubs = "Gnd!";
    i = efBuildAddStr(EFDevTypes, &EFDevNumTypes, MAXDEVTYPES, "pmos");
    fetInfo[i].resClassSD = 1 ;
    fetInfo[i].resClassSub = 8 ;
    fetInfo[i].defSubs = "Vdd!";
    /* Process command line arguments */

    inName = EFArgs(argc, argv, NULL, spcmainArgs, (ClientData) NULL);
    if (inName == NULL)
	exit (1);

    /*
     * Initializations specific to this program.
     * Make output name inName.spice if they weren't 
     * explicitly specified
     */

    if (spcesOutName == spcesDefaultOut)
	sprintf(spcesDefaultOut, "%s.spice", inName);

    if ((esSpiceF = fopen(spcesOutName, "w")) == NULL)
    {
	perror(spcesOutName);
	exit (1);
    }

    /* Read the hierarchical description of the input circuit */
    if (EFReadFile(inName, TRUE, esDoExtResis, FALSE) == FALSE)
    {
	exit (1);
    }

    fprintf(esSpiceF, "* %s file created from %s.ext - technology: %s\n\n",
	    (esFormat == SPICE2) ? "SPICE2" :  
	      ( (esFormat == SPICE3) ? "SPICE3" : "HSPICE" ),  
	    inName, EFTech);
    if ( esFormat == HSPICE ) 
    	fprintf(esSpiceF,".option scale=%gu\n\n", ((float) EFScale)/100.0);
    else esScale = EFScale/100.0;

    /* Convert the hierarchical description to a flat one */
    flatFlags = EF_FLATNODES;
    EFTrimFlags |= EF_TRIMGLOB ;
    if (EFCapThreshold < INFINITE_THRESHOLD) flatFlags |= EF_FLATCAPS;
    if (esFormat == HSPICE ) {
	EFTrimFlags |= EF_TRIMLOCAL ;
	HashInit(&subcktNameTable, 32, HT_STRINGKEYS);
#ifndef UNSORTED_SUBCKT
	DQInit(&subcktNameQueue, 64);
#endif
    }
    EFFlatBuild(inName, flatFlags);

    /* Determine if this is a subcircuit */
    locDoSubckt = FALSE;
    if (esDoSubckt == AUTO) {
	if (efFlatRootDef->def_flags & DEF_SUBCIRCUIT)
	    locDoSubckt = TRUE;
    }
    if ((esDoSubckt == TRUE) || (locDoSubckt == TRUE))
	topVisit(efFlatRootDef);

    /* If we don't want to write subcircuit calls, remove the	*/
    /* subcircuit flag from all cells at this time.		*/

    if (!esDoPorts)
	EFVisitSubcircuits(subcktUndef, (ClientData) NULL);

    initMask = ( esDistrJunct  ) ? (unsigned long)0 : DEV_CONNECT_MASK ;

    if ( esMergeDevsA || esMergeDevsC ) {
     	EFVisitDevs(devMergeVisit, (ClientData) NULL);
	TxPrintf("Devs merged: %d\n", esDevsMerged);
	esFMIndex = 0 ;
	{
	  devMerge *p;

	  for ( p = devMergeList ; p != NULL ; p=p->next ) freeMagic(p);
	}
    } else if ( esDistrJunct )
     	EFVisitDevs(devDistJunctVisit, (ClientData) NULL);
    EFVisitDevs(spcdevVisit, (ClientData) NULL);
    initMask = (unsigned long) 0;
    if (flatFlags & EF_FLATCAPS) {
	(void) sprintf( esCapFormat,  "C%%d %%s %%s %%.%dlffF\n",esCapAccuracy);
	EFVisitCaps(spccapVisit, (ClientData) NULL);
    }
    EFVisitResists(spcresistVisit, (ClientData) NULL);
    EFVisitSubcircuits(subcktVisit, (ClientData) NULL);
    (void) sprintf( esCapFormat, "C%%d %%s GND %%.%dlffF%%s", esCapAccuracy);
    EFVisitNodes(spcnodeVisit, (ClientData) NULL);

    if ((esDoSubckt == TRUE) || (locDoSubckt == TRUE))
	fprintf(esSpiceF, ".ends\n");

    if ( esFormat == HSPICE ) 
	printSubcktDict();

    EFFlatDone(); 
    EFDone();

    if (esSpiceF) fclose(esSpiceF);

    TxPrintf("Memory used: %s\n", RunStats(RS_MEM, NULL, NULL));
    exit (0);
}

#endif	/* MAGIC_WRAPPER */


/*
 * ----------------------------------------------------------------------------
 *
 * spcmainArgs --
 *
 * Process those arguments that are specific to ext2spice.
 * Assumes that *pargv[0][0] is '-', indicating a flag
 * argument.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	After processing an argument, updates *pargc and *pargv
 *	to point to after the argument.
 *
 *	May initialize various global variables based on the
 *	arguments given to us.
 *
 *	Exits in the event of an improper argument.
 *
 * ----------------------------------------------------------------------------
 */

int
spcmainArgs(pargc, pargv)
    int *pargc;
    char ***pargv;
{
    char **argv = *pargv, *cp;
    int argc = *pargc;

    switch (argv[0][1])
    {
	case 'd':
	    esDistrJunct = TRUE;
	    break;
	case 'M':
	    esMergeDevsA = TRUE;
	    break;
	case 'm':
	    esMergeDevsC = TRUE;
	    break;
	case 'B':
	    esNoAttrs = TRUE;
	    break;
	case 'F':
	    esDevNodesOnly = TRUE;
	    break;
	case 'o':
	    if ((spcesOutName = ArgStr(&argc, &argv, "filename")) == NULL)
		goto usage;
	    break;
	case 'f': {
	     char *ftmp ;

	     if ((ftmp = ArgStr(&argc, &argv, "format")) == NULL)
		goto usage;
	     if ( strcasecmp(ftmp,"SPICE2") == 0 )
	        esFormat = SPICE2 ;
	     else if ( strcasecmp(ftmp,"SPICE3") == 0 )
		esFormat = SPICE3 ;
	     else if ( strcasecmp(ftmp,"HSPICE") == 0 ) 
		esFormat = HSPICE ;
	     else goto usage;
	     break;
	     }
	case 'J':
	     {
	     char *ftmp ;

	     if ((ftmp = ArgStr(&argc, &argv, "hierAP_SD")) == NULL)
		goto usage;
	     if ( strcasecmp(ftmp, "HIER") == 0 )
		esHierAP = TRUE ;
	     else if ( strcasecmp(ftmp, "FLAT") == 0 )
		esHierAP = FALSE ;
	     else goto usage;

	     break;
	     }
	case 'y': {
	      char *t;

	      if (( t =  ArgStr(&argc, &argv, "cap-accuracy") ) == NULL)
		goto usage;
	      esCapAccuracy = atoi(t);
	      break;
	      }
#ifndef MAGIC_WRAPPER
	case 'j':
	    {
	    char *rp,  subsNode[80] ;
	    int   ndx, rClass, rClassSub = -1;

	    if ((cp = ArgStr(&argc,&argv,"resistance class")) == NULL)
		goto usage;
	    if ( (rp = (char *) index(cp,':')) == NULL )
		goto usage;
	    else *rp++ = '\0';
	    if ( sscanf(rp, "%d/%d/%s", &rClass, &rClassSub, subsNode) != 3 ) {
		rClassSub = -1 ;
	    	if ( sscanf(rp, "%d/%s",  &rClass, subsNode) != 2 ) goto usage;
	    }
	    ndx = efBuildAddStr(EFDevTypes, &EFDevNumTypes, MAXDEVTYPES, cp);
	    fetInfo[ndx].resClassSD = rClass;
	    fetInfo[ndx].resClassSub = rClassSub;
	    if ( ((1<<rClass) & DEV_CONNECT_MASK) ||
	         ((1<<rClass) & DEV_CONNECT_MASK)   ) {
	        TxError("Oops it seems that you have 31\n");
	        TxError("resistance classes. You will need to recompile");
	        TxError("the extflat package and change ext2sim/spice\n");
	        TxError("DEV_CONNECT_MASK and or nodeClient\n");
		exit (1);
	    }
	    fetInfo[ndx].defSubs = (char *) mallocMagic((unsigned) (strlen(subsNode)+1));
	    strcpy(fetInfo[ndx].defSubs,subsNode);
	    TxError("info: dev %s(%d) sdRclass=%d subRclass=%d dSub=%s\n", 
	    cp, ndx, fetInfo[ndx].resClassSD, fetInfo[ndx].resClassSub, 
            fetInfo[ndx].defSubs);
	    break;
	    }
#endif			/* MAGIC_WRAPPER */
	default:
	    TxError("Unrecognized flag: %s\n", argv[0]);
	    goto usage;
    }

    *pargv = argv;
    *pargc = argc;
    return 0;

usage:
    TxError("Usage: ext2spice [-B] [-o spicefile] [-M|-m] [-y cap_digits] "
		"[-J flat|hier]\n"
		"[-f spice2|spice3|hspice] [-d] [-M] [-m] "
#ifdef MAGIC_WRAPPER
		"[file]\n"
#else
		"[-j device:sdRclass[/subRclass]/defaultSubstrate]\n"
		"file\n\n    or else see options to extcheck(1)\n"
#endif
		);

#ifdef MAGIC_WRAPPER
    return 1;
#else
    exit (1);
#endif
}

/*
 * ----------------------------------------------------------------------------
 *
 * GetNode --
 *
 * function to find a node given its hierarchical prefix and suffix
 *
 * Results:
 *  a pointer to the node struct or NULL
 *
 * ----------------------------------------------------------------------------
 */
EFNode *
GetNode(prefix, suffix)
HierName *prefix;
HierName *suffix;
{
    HashEntry *he;
    EFNodeName *nn;

    he = EFHNConcatLook(prefix, suffix, "output");
    if (he == NULL) return NULL;
    nn = (EFNodeName *) HashGetValue(he);
    if (nn == NULL) return NULL;
    return(nn->efnn_node);
}

/*
 * ----------------------------------------------------------------------------
 *
 * extHierSDAttr --
 * Check if the attribute of the argument dev_terminal or the global
 * settings are such that we want a hierarchical extraction of its S/D
 * 
 *
 * Results:
 *  TRUE or FALSE
 *
 * Side effects:
 *  None.
 *
 * ----------------------------------------------------------------------------
 */

bool extHierSDAttr(term)
    DevTerm *term;
{
    bool r = esHierAP;

    if (term->dterm_attrs)
    {
	if (Match(ATTR_HIERAP, term->dterm_attrs) != FALSE) 
	    r = TRUE;
	else if (Match(ATTR_FLATAP, term->dterm_attrs) != FALSE)
	    r = FALSE;
    }
    return r;
}

/*
 * ----------------------------------------------------------------------------
 *
 * subcktVisit --
 *
 * Procedure to output a subcircuit definition to the .spice file.
 * Called by EFVisitSubcircuits().
 *
 * Results:
 *	Returns 0 always.
 *
 * Side effects:
 *	Writes to the file esSpiceF.
 *
 * Format of a .spice subcircuit call:
 *
 *	X%d node1 node2 ... noden name
 *
 * where
 *	node1 node2 ... noden are the nodes connecting to the ports of
 *	the subcircuit.  "name" is the name of the subcircuit.  It is
 *	assumed that the definition of "name" (.defs name ... .ends)
 *	exists elsewhere and will be appended to the SPICE deck prior
 *	to simulation (as is also assumed for device models).
 *
 * ----------------------------------------------------------------------------
 */
int
subcktVisit(use, hierName, is_top)
    Use *use;
    HierName *hierName;
    bool is_top;		/* TRUE if this is the top-level cell */
{
    EFNode *snode;
    Def *def = use->use_def;
    EFNodeName *nodeName;
    int portorder, portmax;

    if (is_top == TRUE) return 0;	/* Ignore the top-level cell */

    fprintf(esSpiceF, "X%d", esSbckNum++);

    /* This is not a DEV, but "spcdevOutNode" is a general-purpose routine that */
    /* turns a local name in the use's def to a hierarchical name in the     */
    /* calling def.							     */

    /* Note that the ports of the subcircuit will not necessarily be	*/
    /* ALL the entries in the hash table, so we have to check.		*/

    portmax = -1;
    for (snode = (EFNode *) def->def_firstn.efnode_next;
		snode != &def->def_firstn;
		snode = (EFNode *) snode->efnode_next)
    {
	if (!(snode->efnode_flags & EF_PORT)) continue;
	for (nodeName = snode->efnode_name; nodeName != NULL; nodeName =
			nodeName->efnn_next)
	{
	    portorder = nodeName->efnn_port;
	    if (portorder > portmax) portmax = portorder;
	}
    }

    if (portmax < 0)
    {
	/* No port order declared; print them as we see them.	*/
	/* This shouldn't happen for proper .ext files written	*/
	/* by the magic extractor.				*/

        for (snode = (EFNode *) def->def_firstn.efnode_next;
		snode != &def->def_firstn;
		snode = (EFNode *) snode->efnode_next)
	    if (snode->efnode_flags & EF_PORT)
		for (nodeName = snode->efnode_name; nodeName != NULL;
			nodeName = nodeName->efnn_next)
		    if (nodeName->efnn_port >= 0)
			spcdevOutNode(hierName, nodeName->efnn_hier,
					"subcircuit", esSpiceF); 
    }
    else
    {
	/* Port numbers need not start at zero or be contiguous. */
	/* They will be printed in numerical order.		 */

	portorder = 0;
	while (portorder <= portmax)
	{
	    for (snode = (EFNode *) def->def_firstn.efnode_next;
			snode != &def->def_firstn;
			snode = (EFNode *) snode->efnode_next)
	    {
		if (!(snode->efnode_flags & EF_PORT)) continue;
		for (nodeName = snode->efnode_name; nodeName != NULL;
			nodeName = nodeName->efnn_next)
		{
		    int portidx = nodeName->efnn_port;
		    if (portidx == portorder)
		    {
			spcdevOutNode(hierName, nodeName->efnn_hier,
					"subcircuit", esSpiceF); 

			break;
		    }
		}
		if (nodeName != NULL) break;
	    }
	    portorder++;
	}
    }

    fprintf(esSpiceF, " %s\n", def->def_name);	/* subcircuit model name */

    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * subcktUndef --
 *
 * Procedure to remove the DEF_SUBCIRCUIT flag from all subcells.
 * Called by EFVisitSubcircuits().
 *
 * Results:
 *	Returns 0 always.
 *
 * Side effects:
 *	Undefines the DEF_SUBCIRCUIT flag in each encountered Def.
 *
 * ----------------------------------------------------------------------------
 */
int
subcktUndef(use, hierName, is_top)
    Use *use;
    HierName *hierName;
    bool is_top;	/* TRUE if this is the top-level cell */
{
    Def *def = use->use_def;

    def->def_flags &= ~(DEF_SUBCIRCUIT);
    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * topVisit --
 *
 * Procedure to output a subcircuit definition to the .spice file.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Writes to the file esSpiceF.
 *
 * Format of a .spice subcircuit definition:
 *
 *	.subckt name node1 node2 ... noden
 *
 * where
 *	node1 node2 ... noden are the nodes connecting to the ports of
 *	the subcircuit.  "name" is the name of the cell def.
 *
 * ----------------------------------------------------------------------------
 */
 
void
topVisit(def)
    Def *def;
{
    EFNode *snode;
    EFNodeName *sname, *nodeName;
    HashSearch hs;
    HashEntry *he;
    int portorder, portmax;

    fprintf(esSpiceF, ".subckt %s", def->def_name);

    /* Note that the ports of the subcircuit will not necessarily be	*/
    /* ALL the entries in the hash table, so we have to check.		*/

    HashStartSearch(&hs);
    portmax = -1;

    while (he = HashNext(&def->def_nodes, &hs))
    {
	sname = (EFNodeName *) HashGetValue(he);
	snode = sname->efnn_node;
	if (!(snode->efnode_flags & EF_PORT)) continue;
	for (nodeName = sname; nodeName != NULL; nodeName = nodeName->efnn_next)
	{
	    portorder = nodeName->efnn_port;
	    if (portorder > portmax) portmax = portorder;
	}
    }
    
    if (portmax < 0)
    {
	/* No port order declared; print them as we see them.	*/
	/* This shouldn't happen for proper .ext files written	*/
	/* by the magic extractor.				*/

	HashStartSearch(&hs);
	while (he = HashNext(&def->def_nodes, &hs))
	{
	    sname = (EFNodeName *) HashGetValue(he);
	    snode = sname->efnn_node;

	    if (snode->efnode_name->efnn_node->efnode_flags & EF_PORT)
		fprintf(esSpiceF, " %s", he->h_key.h_name);
	}
    }
    else
    {
	/* Port numbers need not start at zero or be contiguous. */
	/* They will be printed in numerical order.              */

	portorder = 0;
	while (portorder <= portmax)
	{
	    HashStartSearch(&hs);
	    while (he = HashNext(&def->def_nodes, &hs))
	    {
		int portidx;

		sname = (EFNodeName *) HashGetValue(he);
		snode = sname->efnn_node;

		if (!(snode->efnode_flags & EF_PORT)) continue;
		for (nodeName = sname; nodeName != NULL; nodeName = nodeName->efnn_next)
		{
		    portidx = nodeName->efnn_port;
		    if (portidx == portorder)
		    {
			fprintf(esSpiceF, " %s", he->h_key.h_name);
			break;
		    }
		}
		if (nodeName != NULL) break;
	    }
	    portorder++;
	}
    }
    fprintf(esSpiceF, "\n");
}

/*
 * ----------------------------------------------------------------------------
 *
 * esOutputResistor ---
 *
 * Routine used by spcdevVisit to print a resistor device.  This
 * is broken out into a separate routine so that each resistor
 * device may be represented (if the option is selected) by a
 * "tee" network of two resistors on either side of the central
 * node, which then has a capacitance to ground.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Output to the SPICE deck.
 *
 * ----------------------------------------------------------------------------
 */

void
esOutputResistor(dev, hierName, trans, term1, term2, has_model, l, w, dscale)
    Dev *dev;			/* Dev being output */
    HierName *hierName;		/* Hierarchical path down to this dev */
    Transform *trans;		/* Coordinate transform for output */
    DevTerm *term1, *term2;	/* Terminals of the device */
    bool has_model;		/* Is this a modeled resistor? */
    int l, w;			/* Device length and width */
    int dscale;			/* Device scaling (for split resistors) */
{
    int scale;
    Rect r;
    float sdM ; 
    char name[12], devchar;

    /* Resistor is "Rnnn term1 term2 value" 		 */
    /* extraction sets two terminals, which are assigned */
    /* term1=gate term2=source by the above code.	 */
    /* extracted units are Ohms; output is in Ohms 	 */

    spcdevOutNode(hierName, term1->dterm_node->efnode_name->efnn_hier,
			name, esSpiceF);
    spcdevOutNode(hierName, term2->dterm_node->efnode_name->efnn_hier,
			name, esSpiceF);

    sdM = getCurDevMult();

    /* SPICE has two resistor types.  If the "name" (EFDevTypes) is */
    /* "None", the simple resistor type is used, and a value given. */
    /* If not, the "semiconductor resistor" is used, and L and W    */
    /* and the device name are output.				    */

    if (!has_model)
    {
	fprintf(esSpiceF, " %f", (double)sdM *
			(double)(dev->dev_values.dev_res)
			/ (double)(dscale));
    }
    else
    {
	fprintf(esSpiceF, " %s", EFDevTypes[dev->dev_type]);

	GeoTransRect(trans, &dev->dev_rect, &r);
	scale = GeoScale(trans);

	if (esFormat == HSPICE)
	{
	    fprintf(esSpiceF, " w=%d l=%d", w * scale, (l * scale) / dscale);
	    if (sdM != 1.0)
		fprintf(esSpiceF, " M=%g", sdM);
	}
	else
	{
	    fprintf(esSpiceF, " w=%gu l=%gu",
		(float)w * scale * esScale * sdM,
		(float)((l * scale * esScale) / dscale));
	    sdM = 1.0;
	}
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * spcdevVisit --
 *
 * Procedure to output a single dev to the .spice file.
 * Called by EFVisitDevs().
 *
 * Results:
 *	Returns 0 always.
 *
 * Side effects:
 *	Writes to the file esSpiceF.
 *
 * Format of a .spice dev line:
 *
 *	M%d drain gate source substrate type w=w l=l * x y
 *      + ad= pd= as= ps=  * asub= psub=  
 *      **devattr g= s= d= 
 *
 * where
 *	type is a name identifying this type of transistor
 *      other types of transistors are extracted with
 *      an M card but it should be easy to turn them to whatever 
 *      you want.
 *	gate, source, and drain are the nodes to which these three
 *		terminals connect
 *	l, w are the length and width of the channel
 *	x, y are the x, y coordinates of a point within the channel.
 *	g=, s=, d= are the (optional) attributes; if present, each
 *		is followed by a comma-separated list of attributes.
 *
 * ----------------------------------------------------------------------------
 */

int
spcdevVisit(dev, hierName, trans)
    Dev *dev;		/* Dev being output */
    HierName *hierName;	/* Hierarchical path down to this dev */
    Transform *trans;	/* Coordinate transform for output */
{
    DevTerm *gate, *source, *drain;
    EFNode  *subnode, *snode, *dnode, *subnodeFlat = NULL;
    int scale, l, w;
    Rect r;
    bool subAP= FALSE, hierS, hierD, extHierSDAttr() ;
    float sdM; 
    char name[12], devchar;
    bool has_model = TRUE;

    sprintf(name, "output");

    /* If no terminals, can't do much of anything */
    if (dev->dev_nterm < 1 ) 
	return 0;

    if ( (esMergeDevsA || esMergeDevsC) && devIsKilled(esFMIndex++) )
	    return 0;

    /* Get L and W of device */
    EFGetLengthAndWidth(dev, &l, &w);

    /* If only two terminals, connect the source to the drain */
    gate = &dev->dev_terms[0];
    if (dev->dev_nterm >= 2)
	source = drain = &dev->dev_terms[1];
    if (dev->dev_nterm >= 3)
	drain = &dev->dev_terms[2];
    subnode = dev->dev_subsnode;

    /* Original hack for BiCMOS, Tim 10/4/97, is deprecated.	*/
    /* Use of "device bjt" preferred to "fet" with model="npn"	*/
    /* This should disappear shortly.				*/

    if (!strcmp(EFDevTypes[dev->dev_type], "npn")) dev->dev_class = DEV_BJT;

    /* For resistor and capacitor classes, set a boolean to	*/
    /* denote whether the device has a model or not, so we	*/
    /* don't have to keep doing a string compare on EFDevTypes.	*/

    switch(dev->dev_class)
    {
	case DEV_RES:
	case DEV_CAP:
	    if (dev->dev_type == esNoModelType)
		has_model = FALSE;
	    break;
    }

    /* Generate SPICE device name */
    switch(dev->dev_class)
    {
	case DEV_MOSFET:
	case DEV_FET:
	    devchar = 'M';
	    break;
	case DEV_BJT:
	    devchar = 'Q';
	    break;
	case DEV_DIODE:
	    devchar = 'D';
	    break;
	case DEV_RES:
	    devchar = 'R';
	    break;
	case DEV_CAP:
	    devchar = 'C';
	    break;
	case DEV_SUBCKT:
	case DEV_RSUBCKT:
	    devchar = 'X';
	    break;
    }
    fprintf(esSpiceF, "%c", devchar);

    /* Flag shorted devices */
    switch(dev->dev_class)
    {
	case DEV_MOSFET:
	case DEV_FET:
	    if (source == drain)
		fprintf(esSpiceF, "** SOURCE/DRAIN TIED\n");

	default:
	    if (gate == source)
		fprintf(esSpiceF, "** SHORTED DEVICE\n");
	    break;
    }

    /* Device index is taken from gate attributes if attached;	*/
    /* otherwise, the device is numbered in sequence.		*/

    if (gate->dterm_attrs)
	fprintf(esSpiceF, "%s", gate->dterm_attrs);
    else
    {
	switch (dev->dev_class)
	{
	    case DEV_RES:
		fprintf(esSpiceF, "%d", esResNum++);
		/* For resistor tee networks, use, e.g.,	*/
		/* "R1A" and "R1B", for clarity			*/
		if (esDoResistorTee) fprintf(esSpiceF, "A");
		break;
	    case DEV_DIODE:
		fprintf(esSpiceF, "%d", esDiodeNum++);
		break;
	    case DEV_CAP:
		fprintf(esSpiceF, "%d", esCapNum++);
		break;
	    case DEV_SUBCKT:
	    case DEV_RSUBCKT:
		fprintf(esSpiceF, "%d", esSbckNum++);
		break;
	    default:
		fprintf(esSpiceF, "%d", esDevNum++);
		break;
	}
    }
    /* Order and number of nodes in the output depends on the device class */

    switch (dev->dev_class)
    {
	case DEV_BJT:

	    /* BJT is "Qnnn collector emitter base model" 			*/
	    /* extraction sets collector=subnode, emitter=gate, base=drain	*/

	    sprintf(name, "fet");
	    spcdevOutNode(hierName, subnode->efnode_name->efnn_hier, name, esSpiceF); 
	    spcdevOutNode(hierName, gate->dterm_node->efnode_name->efnn_hier,
			name, esSpiceF);

	    /* fix mixed up drain/source for bjts hace 2/2/99 */
	    if (gate->dterm_node->efnode_name->efnn_hier ==
			source->dterm_node->efnode_name->efnn_hier)
		spcdevOutNode(hierName, drain->dterm_node->efnode_name->efnn_hier,
			name, esSpiceF);
	    else
		spcdevOutNode(hierName, source->dterm_node->efnode_name->efnn_hier,
			name, esSpiceF);

	    fprintf(esSpiceF, " %s", EFDevTypes[dev->dev_type]);
	    break;

	case DEV_SUBCKT:

	    /* Subcircuit is "Xnnn gate [source [drain [sub]]]"		*/
	    /* Subcircuit .subckt record must be ordered to match!	*/

	    spcdevOutNode(hierName, gate->dterm_node->efnode_name->efnn_hier,
			name, esSpiceF);

	    /* Drop through to below (no break statement) */

	case DEV_RSUBCKT:
	    /* RC-like subcircuits are exactly like other subcircuits	*/
	    /* except that the "gate" node is treated as an identifier	*/
	    /* only and is not output.					*/

	    if (dev->dev_nterm > 1)
		spcdevOutNode(hierName, source->dterm_node->efnode_name->efnn_hier,
			name, esSpiceF);
	    if (dev->dev_nterm > 2)
		spcdevOutNode(hierName, drain->dterm_node->efnode_name->efnn_hier,
			name, esSpiceF);
	    if (subnode)
	    {
		fprintf(esSpiceF, " ");
		subnodeFlat = spcdevSubstrate(hierName,
			subnode->efnode_name->efnn_hier,
			dev->dev_type, esSpiceF);
	    }
	    fprintf(esSpiceF, " %s", EFDevTypes[dev->dev_type]);

	    /* Write its length and width and pass them as	*/
	    /* parameters to the subcircuit call.		*/

	    GeoTransRect(trans, &dev->dev_rect, &r);
	    scale = GeoScale(trans);

	    sdM = getCurDevMult();

	    if ((w > 0) && (l > 0))
	    {
		if (esFormat == HSPICE)
		{
		    fprintf(esSpiceF, " w=%d l=%d", w*scale, l*scale);
		    if (sdM != 1.0)
			fprintf(esSpiceF, " M=%g", sdM);
		}
		else
		{
		    fprintf(esSpiceF, " w=%gu l=%gu",
				(float)w * scale * esScale * sdM,
				(float)l * scale * esScale);
		    sdM = 1.0;
		}
	    }
	    break;

	case DEV_RES:
	    if (esDoResistorTee)
	    {
		/* There are three ways of handling capacitance	*/
		/* on resistor networks.  One is to ignore it	*/
		/* (the default; generates "floating" nodes in	*/
		/* the SPICE output) which is okay for LVS. 	*/
		/* Another way is the Pi network, in which the	*/
		/* capacitance is split evenly between the	*/
		/* terminals.  Again, the resistor node is left	*/
		/* floating.  The third is the Tee network, in	*/
		/* which the resistance is split in two parts,	*/
		/* connecting to a capacitor to ground in the	*/
		/* middle.  This is the best solution but plays	*/
		/* havoc with LVS.  So, the choice is a command	*/
		/* line option.					*/

		esOutputResistor(dev, hierName, trans, gate, source, has_model,
			l, w, 2);
		fprintf(esSpiceF, "\n%c", devchar);
		if (gate->dterm_attrs)
		    fprintf(esSpiceF, "%sB", gate->dterm_attrs);
		else
		    fprintf(esSpiceF, "%dB", esResNum - 1);
		esOutputResistor(dev, hierName, trans, gate, drain, has_model,
			l, w, 2);
	    }
	    else
	    {
		esOutputResistor(dev, hierName, trans, source, drain, has_model,
			l, w, 1);
	    }
	    break;

	case DEV_DIODE:

	    /* Diode is "Dnnn top bottom model"	*/

	    spcdevOutNode(hierName, gate->dterm_node->efnode_name->efnn_hier,
			name, esSpiceF);
	    spcdevOutNode(hierName, source->dterm_node->efnode_name->efnn_hier,
			name, esSpiceF);
	    fprintf(esSpiceF, " %s", EFDevTypes[dev->dev_type]);
	    break;

	case DEV_CAP:

	    /* Capacitor is "Cnnn top bottom value"	*/
	    /* extraction sets top=gate bottom=source	*/
	    /* extracted units are fF; output is in fF */

	    spcdevOutNode(hierName, gate->dterm_node->efnode_name->efnn_hier,
			name, esSpiceF);
	    spcdevOutNode(hierName, source->dterm_node->efnode_name->efnn_hier,
			name, esSpiceF);

	    sdM = getCurDevMult();

	    /* SPICE has two capacitor types.  If the "name" (EFDevTypes) is */
	    /* "None", the simple capacitor type is used, and a value given. */
	    /* If not, the "semiconductor capacitor" is used, and L and W    */
	    /* and the device name are output.				     */

	    if (!has_model)
	    {
		fprintf(esSpiceF, " %ffF", (double)sdM *
				(double)(dev->dev_values.dev_cap));
	    }
	    else
	    {
		fprintf(esSpiceF, " %s", EFDevTypes[dev->dev_type]);

		GeoTransRect(trans, &dev->dev_rect, &r);
		scale = GeoScale(trans);

		if (esFormat == HSPICE)
		{
		    fprintf(esSpiceF, " w=%d l=%d", w*scale, l*scale);
		    if (sdM != 1.0)
			fprintf(esSpiceF, " M=%g", sdM);
		}
		else
		{
		    fprintf(esSpiceF, " w=%gu l=%gu",
			(float)w * scale * esScale * sdM,
			(float)l * scale * esScale);
		    sdM = 1.0;
		}
	    }
	    break;

	case DEV_FET:
	case DEV_MOSFET:

	    /* MOSFET is "Mnnn drain gate source [L=x W=x [attributes]]" */

	    spcdevOutNode(hierName, drain->dterm_node->efnode_name->efnn_hier,
			name, esSpiceF);
	    spcdevOutNode(hierName, gate->dterm_node->efnode_name->efnn_hier,
			name, esSpiceF);
	    spcdevOutNode(hierName, source->dterm_node->efnode_name->efnn_hier,
			name, esSpiceF);
	    if (subnode)
	    {
		fprintf(esSpiceF, " ");
		subnodeFlat = spcdevSubstrate(hierName,
			subnode->efnode_name->efnn_hier,
			dev->dev_type, esSpiceF);
	    }
	    fprintf(esSpiceF, " %s", EFDevTypes[dev->dev_type]);

	    /*
	     * Scale L and W appropriately by the same amount as distance
	     * values in the transform.  The transform will have a scale
	     * different from 1 only in the case when the scale factors of
	     * some of the .ext files differed, making it necessary to scale
	     * all dimensions explicitly instead of having a single scale
	     * factor at the beginning of the .spice file.
	     */
	    GeoTransRect(trans, &dev->dev_rect, &r);
	    scale = GeoScale(trans);

	    sdM = getCurDevMult();

	    if (esFormat == HSPICE)
	    {
		fprintf(esSpiceF, " w=%d l=%d", w*scale, l*scale);
		if (sdM != 1.0)
		    fprintf(esSpiceF, " M=%g", sdM);
	    }
	    else
	    {
		fprintf(esSpiceF, " w=%gu l=%gu",
			(float)w * scale * esScale * sdM,
			(float)l * scale * esScale);
		sdM = 1.0;
	    }

	    /*
	     * Check controlling attributes and output area and perimeter. 
	     */
	    hierS = extHierSDAttr(source);
	    hierD = extHierSDAttr(drain);
	    if ( gate->dterm_attrs ) 
		subAP = Match(ATTR_SUBSAP, gate->dterm_attrs ) ;

	    fprintf(esSpiceF, "\n+ ");
	    if (hierD) 
        	spcnAPHier(drain, hierName, fetInfo[dev->dev_type].resClassSD,
			scale, "d", sdM, esSpiceF);
	    else
	    {
		dnode = GetNode(hierName, drain->dterm_node->efnode_name->efnn_hier);
        	spcnAP(dnode, fetInfo[dev->dev_type].resClassSD, scale,
			"d", sdM, esSpiceF, w);
	    }
	    if (hierS)
		spcnAPHier(source, hierName, fetInfo[dev->dev_type].resClassSD,
			scale, "s", sdM, esSpiceF);
	    else {
		snode= GetNode(hierName, source->dterm_node->efnode_name->efnn_hier);
		spcnAP(snode, fetInfo[dev->dev_type].resClassSD, scale,
			"s", sdM, esSpiceF, w);
	    }
	    if (subAP)
	    {
		fprintf(esSpiceF, " * ");
		if (fetInfo[dev->dev_type].resClassSub < 0)
		{
		    TxError("error: subap for devtype %d unspecified\n",
				dev->dev_type);
		    fprintf(esSpiceF, "asub=0 psub=0");
		}
		else if (subnodeFlat) 
		    spcnAP(subnodeFlat, fetInfo[dev->dev_type].resClassSub, scale, 
	       			"sub", sdM, esSpiceF, -1);
		else
		    fprintf(esSpiceF, "asub=0 psub=0");
	    }

	    /* Now output attributes, if present */
	    if (!esNoAttrs)
	    {
		if (gate->dterm_attrs || source->dterm_attrs || drain->dterm_attrs)
		    fprintf(esSpiceF,"\n**devattr");
		if (gate->dterm_attrs)
		    fprintf(esSpiceF, " g=%s", gate->dterm_attrs);
		if (source->dterm_attrs) 
		    fprintf(esSpiceF, " s=%s", source->dterm_attrs);
		if (drain->dterm_attrs) 
		    fprintf(esSpiceF, " d=%s", drain->dterm_attrs);
	    }
	    break;
    }
    fprintf(esSpiceF, "\n");
    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * spcdevSubstrate -
 *
 * Output the node name of the substrate of a dev. If the suffix is the 
 * same as the default dont go looking for it just output the default
 * (trimmed appropriately). Otherwise look it up ..
 *
 * Results:
 *   NULL if not found or the default substrate or the node pointer
 *   otherwise (might be reused to output area and perimeter of
 *   the substrate).
 *
 * Side effects:
 *  Might allocate the nodeClient for the node through nodeSpiceName.
 *
 * ----------------------------------------------------------------------------
 */
EFNode *spcdevSubstrate( prefix, suffix, type,  outf)
HierName *prefix, *suffix;
int type;
FILE *outf;
{
    HashEntry *he;
    EFNodeName *nn;
    char *suf ;
    int  l ;

    suf = EFHNToStr(suffix);
    if (fetInfo[type].defSubs && strcasecmp(suf,fetInfo[type].defSubs) == 0) {
	if ( outf ) {
    	   l = strlen(suf) - 1;
	   if (  ( EFTrimFlags & EF_TRIMGLOB ) && suf[l] =='!' ||
	         ( EFTrimFlags & EF_TRIMLOCAL ) && suf[l] == '#'  )
	        suf[l] = '\0' ;
	   fprintf(outf, "%s", suf);
	}
	return NULL;
    }
    else {
    	he = EFHNConcatLook(prefix, suffix, "substrate");
    	if (he == NULL)
    	{
		if (outf) 
		   fprintf(outf, "errGnd!");
		return NULL;
    	}
    	/* Canonical name */
    	nn = (EFNodeName *) HashGetValue(he);
	if (outf) 
	   fprintf(outf, nodeSpiceName(nn->efnn_node->efnode_name->efnn_hier));
        return nn->efnn_node;
   }
}


/*
 * ----------------------------------------------------------------------------
 *
 * spcnAP, spcnAPHier --
 *
 * Output the area perimeter of the node with type type if it has not 
 * been visited.
 * The spcnAPHier version outputs the area and perimeter only within the
 * local subcell with hierarchical name hierName.
 *
 * Side effects:
 *     Set the visited flags so that the node A/P will not be output multiple
 *     times
 *
 * ----------------------------------------------------------------------------
 */
int spcnAP(node, resClass, scale, sterm, m, outf, w)
    EFNode *node;
    int  resClass, scale;
    char *sterm;
    float m;
    FILE *outf;
    int w;
{
	char fmt[30];
	float dsc ;

	if ((node == NULL) || (node->efnode_client == (ClientData)NULL))
	{
	    TxError("spcnAP: major internal inconsistency\n");
	    return;
	}

	if ( esFormat == HSPICE )
	     sprintf(fmt,"a%s=%%g p%s=%%g ", sterm, sterm);
	else sprintf(fmt,"a%s=%%gp p%s=%%gu ", sterm, sterm);

	if ( !esDistrJunct || w == -1 ) goto oldFmt;

	dsc = w/((nodeClient*)node->efnode_client)->m_w.widths[resClass];
	if ( esFormat == HSPICE )
	   fprintf(outf,fmt,
		node->efnode_pa[resClass].pa_area*scale*scale*dsc,
		node->efnode_pa[resClass].pa_perim*scale*dsc);
	else 
	   fprintf(outf,fmt,
	    ((float)node->efnode_pa[resClass].pa_area*scale*scale)
	    *esScale*esScale*dsc,
	    ((float)node->efnode_pa[resClass].pa_perim*scale)*esScale*dsc);
	return;

oldFmt:
	if ( resClass == NO_RESCLASS ||
	     beenVisited((nodeClient *)node->efnode_client, resClass) ) 
		scale = 0 ;
	else   markVisited((nodeClient *)node->efnode_client, resClass);
	if ( esFormat == HSPICE )
	   fprintf(outf,fmt,
		node->efnode_pa[resClass].pa_area*scale*scale/m,
		node->efnode_pa[resClass].pa_perim*scale/m);
	else 
	   fprintf(outf,fmt,
	    ((float)node->efnode_pa[resClass].pa_area*scale*scale)*esScale*esScale,
	    ((float)node->efnode_pa[resClass].pa_perim*scale)*esScale);
}

int spcnAPHier(dterm, hierName, resClass, scale, sterm, m, outf)
    DevTerm *dterm;
    HierName *hierName;
    int  resClass, scale;
    float m;
    char *sterm;
    FILE *outf;
{
	EFNode *node = dterm->dterm_node;
	nodeClientHier   *nc ;
	char fmt[30];

	if ( esFormat == HSPICE )
	     sprintf(fmt,"a%s=%%g p%s=%%g ", sterm, sterm);
	else sprintf(fmt,"a%s=%%gp p%s=%%gu ", sterm, sterm);
	if ( node->efnode_client == (ClientData) NULL ) 
	if ( node->efnode_client == (ClientData) NULL ) 
		initNodeClientHier(node);
	nc = (nodeClientHier *)node->efnode_client;
	if ( nc->lastPrefix != hierName ) {
		clearVisited(nc);
		nc->lastPrefix = hierName;
	}
	if (resClass == NO_RESCLASS ||
	    beenVisited((nodeClientHier *)node->efnode_client, resClass) ) 
		scale = 0;
	else markVisited((nodeClientHier *)node->efnode_client, resClass);
	if ( esFormat == HSPICE )
	   fprintf(outf,fmt,
		node->efnode_pa[resClass].pa_area*scale*scale/m,
		node->efnode_pa[resClass].pa_perim*scale/m);
	else
	   fprintf(outf,fmt,
	    ((float)node->efnode_pa[resClass].pa_area*scale)*esScale*esScale,
	    ((float)node->efnode_pa[resClass].pa_perim*scale)*esScale);
}

/*
 * ----------------------------------------------------------------------------
 *
 * spcdevOutNode --
 *
 * Output the name of the node whose hierarchical prefix down to this
 * point is 'prefix' and whose name from the end of suffix down to the
 * leaves is 'suffix', just as in the arguments to EFHNConcat().
 *
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Writes to the file 'outf'.
 *	Sets the efnode_client field as described above.
 *
 * ----------------------------------------------------------------------------
 */

int
spcdevOutNode(prefix, suffix, name, outf)
    HierName *prefix;
    HierName *suffix;
    char *name;
    FILE *outf;
{
    HashEntry *he;
    EFNodeName *nn;

    he = EFHNConcatLook(prefix, suffix, name);
    if (he == NULL)
    {
	fprintf(outf, " errGnd!");
	return;
    }
    nn = (EFNodeName *) HashGetValue(he);
    fprintf(outf, " %s", nodeSpiceName(nn->efnn_node->efnode_name->efnn_hier));
}


/*
 * ----------------------------------------------------------------------------
 *
 * spccapVisit --
 *
 * Procedure to output a single capacitor to the .spice file.
 * Called by EFVisitCaps().
 *
 * Results:
 *	Returns 0 always.
 *
 * Side effects:
 *	Writes to the file esSpiceF. Increments esCapNum.
 *
 * Format of a .spice cap line:
 *
 *	C%d node1 node2 cap
 *
 * where
 *	node1, node2 are the terminals of the capacitor
 *	cap is the capacitance in femtofarads (NOT attofarads).
 *
 * ----------------------------------------------------------------------------
 */

int
spccapVisit(hierName1, hierName2, cap)
    HierName *hierName1;
    HierName *hierName2;
    double cap;
{
    cap = cap / 1000;
    if (cap <= EFCapThreshold)
	return 0;

    fprintf(esSpiceF, esCapFormat ,esCapNum++,nodeSpiceName(hierName1),
                                          nodeSpiceName(hierName2), cap);
    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * spcresistVisit --
 *
 * Procedure to output a single resistor to the .spice file.
 * Called by EFVisitResists().
 *
 * Results:
 *	Returns 0 always.
 *
 * Side effects:
 *	Writes to the file esSpiceF. Increments esResNum.
 *
 * Format of a .spice resistor line:
 *
 *	R%d node1 node2 res
 *
 * where
 *	node1, node2 are the terminals of the resistor
 *	res is the resistance in ohms (NOT milliohms)
 *
 *
 * ----------------------------------------------------------------------------
 */
int
spcresistVisit(hierName1, hierName2, res)
    HierName *hierName1;
    HierName *hierName2;
    int res;
{
    res = (res + 500) / 1000;

    fprintf(esSpiceF, "R%d %s %s %d\n", esResNum++, nodeSpiceName(hierName1),
                                          nodeSpiceName(hierName2), res);

    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * spcnodeVisit --
 *
 * Procedure to output a single node to the .spice file along with its 
 * attributes and its dictionary (if present). Called by EFVisitNodes().
 *
 * Results:
 *	Returns 0 always.
 *
 * Side effects:
 *	Writes to the files esSpiceF
 *
 * ----------------------------------------------------------------------------
 */

int
spcnodeVisit(node, res, cap)
    EFNode *node;
    int res; 
    double cap;
{
    EFNodeName *nn;
    HierName *hierName;
    bool isConnected = FALSE;
    char *fmt, *nsn;
    EFAttr *ap;

    if (node->efnode_client)
    {
	isConnected = (esDistrJunct) ?
		(((nodeClient *)node->efnode_client)->m_w.widths != NULL) :
        	((((nodeClient *)node->efnode_client)->m_w.visitMask
		& DEV_CONNECT_MASK) != 0);
    }
    if (!isConnected && esDevNodesOnly) 
	return 0;

    hierName = (HierName *) node->efnode_name->efnn_hier;
    nsn = nodeSpiceName(hierName);

    if (esFormat == SPICE2 || esFormat == HSPICE && strncmp(nsn, "z@", 2)==0 ) {
	static char ntmp[MAX_STR_SIZE];

	EFHNSprintf(ntmp, hierName);
	fprintf(esSpiceF, "** %s == %s\n", ntmp, nsn);
    }
    cap = cap  / 1000;
    if (cap > EFCapThreshold)
    {
	fprintf(esSpiceF, esCapFormat, esCapNum++, nsn, cap,
			  (isConnected) ?  "\n" : " **FLOATING\n");
    }
    if (node->efnode_attrs && !esNoAttrs)
    {
	fprintf(esSpiceF, "**nodeattr %s :",nsn );
	for (fmt = " %s", ap = node->efnode_attrs; ap; ap = ap->efa_next)
	{
	    fprintf(esSpiceF, fmt, ap->efa_text);
	    fmt = ",%s";
	}
	putc('\n', esSpiceF);
    }

    return 0;
}

/* a debugging procedure */
int
nodeVisitDebug(node, res, cap)
    EFNode *node;
    int res; 
    double cap;
{
    EFNodeName *nn;
    HierName *hierName;
    char *fmt, *nsn;
    EFAttr *ap;
    
    hierName = (HierName *) node->efnode_name->efnn_hier;
    nsn = nodeSpiceName(hierName);
    TxError("** %s (%x)\n", nsn, node);

    printf("\t client.name=%s, client.m_w=%x\n", 
    ((nodeClient *)node->efnode_client)->spiceNodeName,
    ((nodeClient *)node->efnode_client)->m_w.widths);
   return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * nodeSpiceName --
 * Find the real spice name for the node with hierarchical name hname.
 *   SPICE2 ==> numeric
 *   SPICE3 ==> full magic path
 *   HSPICE ==> less than 15 characters long
 *
 * Results:
 *	Returns the spice node name.
 *
 * Side effects:
 *      Allocates nodeClients for the node.
 *
 * ----------------------------------------------------------------------------
 */
static char esTempName[MAX_STR_SIZE];

char *nodeSpiceName(hname)
    HierName *hname;
{
    EFNodeName *nn;
    HashEntry *he;
    EFNode *node;

    he = EFHNLook(hname, (char *) NULL, "nodeName");
    if ( he == NULL )
	return "errGnd!";
    nn = (EFNodeName *) HashGetValue(he);
    node = nn->efnn_node;

    if ( (nodeClient *) (node->efnode_client) == NULL ) {
    	initNodeClient(node);
	goto makeName;
    } else if ( ((nodeClient *) (node->efnode_client))->spiceNodeName == NULL)
	goto makeName;
    else goto retName;


makeName:
    if ( esFormat == SPICE2 ) 
	sprintf(esTempName, "%d", esNodeNum++);
    else {
       EFHNSprintf(esTempName, node->efnode_name->efnn_hier);
       if ( esFormat == HSPICE ) /* more processing */
	nodeHspiceName(esTempName);
    }
   ((nodeClient *) (node->efnode_client))->spiceNodeName = 
	    StrDup(NULL, esTempName);

retName:
    return ((nodeClient *) (node->efnode_client))->spiceNodeName;
}

/*
 * ----------------------------------------------------------------------------
 *
 * EFHNSprintf --
 *
 * Create a hierarchical node name.
 * The flags in EFTrimFlags control whether global (!) or local (#)
 * suffixes are to be trimmed. Also substitutes \. with \@ if the 
 * format is hspice.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Changes the area pointed to by str
 *
 * ----------------------------------------------------------------------------
 */

int 
EFHNSprintf(str, hierName)
    char *str;
    HierName *hierName;
{
    bool trimGlob, trimLocal;
    char *s, *cp, c;
    char *efHNSprintfPrefix();

    s = str;
    if (hierName->hn_parent) str = efHNSprintfPrefix(hierName->hn_parent, str);
    if (EFTrimFlags)
    {
	cp = hierName->hn_name; 
	trimGlob = (EFTrimFlags & EF_TRIMGLOB);
	trimLocal = (EFTrimFlags & EF_TRIMLOCAL);
	while (c = *cp++)
	{
	    switch (c)
	    {
		case '!':	if (!trimGlob) *str++ = c; break;
		case '.':	*str++ = (esFormat == HSPICE)?'@':'.'; break;
		case '#':	if (trimLocal) break;
		default:	*str++ = c; break;
	    }
	}
	*str++ = '\0';
    }
    else strcpy(str, hierName->hn_name);
}

char *efHNSprintfPrefix(hierName, str)
    HierName *hierName;
    char *str;
{
    char *cp, c;

    if (hierName->hn_parent)
	str = efHNSprintfPrefix(hierName->hn_parent, str);

    cp = hierName->hn_name;
    while (*str++ = *cp++) ;
    *(--str) = '/';
    return ++str;
}

/*
 * ----------------------------------------------------------------------------
 *
 * nodeHspiceName --
 *
 * Convert the hierarchical node name used in Berkeley spice 
 * to a name understodd by hspice and hopefully by the user.
 *
 * Results:
 *	A somewhat meaningfull node name
 *
 * Side effects:
 *	Mucks with the hash table above.
 *
 * ----------------------------------------------------------------------------
 */

int nodeHspiceName(s)
    char *s;
{
    char *p, *sf;
    int l, snum = -1;
    HashEntry *he;
    static char map[MAX_STR_SIZE];

    /*
     * find the suffix 
     */
    l = strlen(s);
    for (p = s + l; (p > s) && *p != '/'; p--); 
    if (p == s)
    {
	sprintf(map, s);
	goto topLevel;
    }

    /* 
     * break it into prefix '/0' suffix 
     */
    if (*p == '/') 
	*p = 0; 
    sf = p + 1;

    /* 
     * look up prefix in the hash table ad create it if doesnt exist 
     */
    if ((he = HashLookOnly(&subcktNameTable, s)) == NULL)
    {
        snum = esSbckNum++;
	he = HashFind(&subcktNameTable, s);
	HashSetValue(he, (ClientData)(pointertype) snum);
#ifndef UNSORTED_SUBCKT
	DQPushRear(&subcktNameQueue, he);
#endif
    }
    else 
	snum = (spointertype) HashGetValue(he);
    sprintf(map, "x%d/%s", snum, sf);

topLevel:
    strcpy(s, map);
    if (strlen(s) > 15)
    {
	/* still hspice does not get it */
	sprintf(s, "z@%d", esNodeNum++);
	if (strlen(s) > 15)
	{
	    /* screw it: hspice will not work */
	    TxError("Error: too many nodes in this circuit to be "
			"output as names\n");
	    TxError("       use spice2 format or call and complain "
			"to Meta software about their stupid parser\n");
#ifdef MAGIC_WRAPPER
	    return TCL_ERROR;
#else
	    exit(1);
#endif
	}
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * printSubcktDict --
 *
 * Print out the hspice subcircuit dictionary. Ideally this should go to a
 * pa0 file but uncfortunately hspice crashes if the node names contain
 * dots so we just append it at the end of the spice file
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Writes to the output file
 *
 * ----------------------------------------------------------------------------
 */

int printSubcktDict()
{
    HashSearch  hs;
    HashEntry  *he;

    fprintf(esSpiceF,"\n** hspice subcircuit dictionary\n");

#ifndef UNSORTED_SUBCKT
    while ((he = (HashEntry *)DQPopFront(&subcktNameQueue)) != NULL)
#else
    HashStartSearch(&hs);
    while ((he = HashNext(&subcktNameTable, &hs)) != NULL) 
#endif
	fprintf(esSpiceF,"* x%d\t%s\n", HashGetValue(he), he->h_key.h_name);
}

/*
 * ----------------------------------------------------------------------------
 *
 * mkDevMerge --
 * Create a new devMerge structure.
 *
 * Results:
 *    Obvious
 *
 * Side effects:
 *	Allocates memory and sets the fields of the structure.
 *
 * ----------------------------------------------------------------------------
 */

devMerge *mkDevMerge(l, w, g, s, d, b, hn, dev)
    int     l, w;
    EFNode *g, *s, *d, *b;
    HierName *hn;
    Dev    *dev;
{
    devMerge *fp;

    fp = (devMerge *) mallocMagic((unsigned) (sizeof(devMerge)));
    fp->l = l; fp->w = w;
    fp->g = g; fp->s = s;
    fp->d = d; fp->b = b;
    fp->dev = dev;
    fp->esFMIndex = esFMIndex;
    fp->hierName = hn;
    fp->next = NULL;
    addDevMult(1.0);

    return fp;
}

/*
 * ----------------------------------------------------------------------------
 *
 * parallelDevs --
 *
 * Determine if two devs are in parallel
 *
 * Results:
 *    NOT_PARALLEL  if not in parallel
 *    PARALLEL      if parallel and an exact match
 *    ANTIPARALLEL  if parallel but reversed source<->drain nodes
 *
 * Side effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

#define	NOT_PARALLEL	0
#define	PARALLEL	1
#define	ANTIPARALLEL	2

int
parallelDevs(f1, f2)
    devMerge *f1, *f2;
{
    /* If the devices are not in the same class, then	*/
    /* they cannot be parallel.				*/

    if (f1->dev->dev_class != f2->dev->dev_class)
	return NOT_PARALLEL;

    /* Can't merge devices with different models */
    if (f1->dev->dev_type != f2->dev->dev_type)
	return NOT_PARALLEL;

    /* Class-dependent action */
    switch(f1->dev->dev_class)
    {
	case DEV_MOSFET:
	case DEV_FET:

	    if (f1->b != f2->b) return NOT_PARALLEL;
	    if ((f1->g == f2->g) && (f1->l == f2->l)
			&& (esMergeDevsA || (f1->w == f2->w)))
	    {
		if ((f1->d == f2->d) && (f1->s == f2->s))
		    return PARALLEL;
		else if ((f1->s == f2->d) && (f1->d == f2->s))
		    return ANTIPARALLEL;
	    }
	    break;

	/* Capacitors match if top ("gate") and bottom ("source") are	*/
	/* the same.  Do not attempt to swap top and bottom, as we do	*/
	/* not know when it is safe to do so.				*/

	case DEV_CAP:
	    if ((f1->g != f2->g) || (f1->s != f2->s))
		return NOT_PARALLEL;

	    else if (f1->dev->dev_type == esNoModelType)
	    {
		/* Unmodeled capacitor */
		if (esMergeDevsA || (f1->dev->dev_values.dev_cap
				== f2->dev->dev_values.dev_cap))
		    return PARALLEL;
	    }
	    else if (esMergeDevsA || ((f1->l == f2->l) && (f1->w == f2->w)))
		return PARALLEL;
	    break;

	/* We can't merge resistors because we accumulate capacitance	*/
	/* on the central ("gate") node.  Merging the devices would	*/
	/* cause nodes to disappear.					*/

	case DEV_RES:
	    break;

	/* For the remaining devices, it might be possible to merge	*/
	/* if we know that the device model level accepts length and	*/
	/* width parameters.  However, at this time, no such		*/
	/* information is passed to the SPICE deck, so we don't try to	*/
	/* merge these devices.						*/

	case DEV_BJT:
	case DEV_DIODE:
	    break;

	/* There is no way to merge subcircuit devices */

	case DEV_SUBCKT:
	case DEV_RSUBCKT:
	    break;
    }
    return NOT_PARALLEL;
}

/*
 * ----------------------------------------------------------------------------
 *
 * mergeAttr --
 *
 * merge two attribute strings
 *
 * Results:
 *  The merged strings
 *
 * Side effects:
 *  Might allocate and free memory.
 *
 * ----------------------------------------------------------------------------
 */

void
mergeAttr(a1, a2)
    char **a1, **a2;
{
    if (*a1 == NULL)
	*a1 = *a2;
    else
    {
	char *t;
	int l1 = strlen(*a1);
	int l2 = strlen(*a2);
	t = (char *) mallocMagic((unsigned int)((l1 + l2) + 1));
	t = (char *) strcat(*a1, *a2);
	freeMagic(*a1);
	*a1 = t;
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * devMergeVisit --
 * Visits each dev throu EFVisitDevs and finds if it is in parallel with
 * any previously visited dev.
 *
 * Results:
 *  0 always to keep the caller going.
 *
 * Side effects:
 *  Numerous.
 *
 * ----------------------------------------------------------------------------
 */

int
devMergeVisit(dev, hierName, trans)
    Dev *dev;			/* Dev to examine */
    HierName *hierName;		/* Hierarchical path down to this dev */
    Transform *trans;		/* Coordinate transform not used */
{
    DevTerm *gate, *source, *drain;
    Dev     *cf;
    DevTerm *cg, *cs, *cd;
    EFNode *subnode, *snode, *dnode, *gnode;
    int      scale, pmode, l, w;
    bool     hS, hD, chS, chD;
    Rect r;
    devMerge *fp, *cfp;
    float m;

    if (esDistrJunct)
	devDistJunctVisit(dev, hierName, trans);

    if (dev->dev_nterm < 2)
    {
	TxError("outPremature\n");
	return 0;
    }

    gate = &dev->dev_terms[0];
    source = drain = &dev->dev_terms[1];
    if (dev->dev_nterm >= 3)
	drain = &dev->dev_terms[2];


    gnode = GetNode(hierName, gate->dterm_node->efnode_name->efnn_hier);
    snode = GetNode(hierName, source->dterm_node->efnode_name->efnn_hier);
    dnode = GetNode(hierName, drain->dterm_node->efnode_name->efnn_hier);
    if (dev->dev_subsnode)
	subnode = spcdevSubstrate(hierName,
			dev->dev_subsnode->efnode_name->efnn_hier, 
			dev->dev_type, NULL);
    else
	subnode = NULL;

    GeoTransRect(trans, &dev->dev_rect, &r);
    scale = GeoScale(trans);

    /* Get length and width of the device */
    EFGetLengthAndWidth(dev, &l, &w);

    fp = mkDevMerge(l * scale, w * scale, gnode, snode, 
			dnode, subnode, hierName, dev);
    hS = extHierSDAttr(source);
    hD = extHierSDAttr(drain);

    /*
     * run the list of devs. compare the current one with 
     * each one in the list. if they fullfill the matching requirements
     * merge them only if:
     * 1) they have both apf S, D attributes
     * or 
     * 2) one of them has aph S, D attributes and they have the same 
     *    hierarchical prefix
     * If one of them has apf and the other aph print a warning.
     */

    for (cfp = devMergeList; cfp != NULL; cfp = cfp->next)
    {
	if ((pmode = parallelDevs(fp, cfp)) != NOT_PARALLEL)
	{
	    cf = cfp->dev;
	    cg = &cfp->dev->dev_terms[0];
	    cs = cd = &cfp->dev->dev_terms[1];
	    if (cfp->dev->dev_nterm >= 3)
	    {
		if (pmode == PARALLEL) 
		    cd = &cfp->dev->dev_terms[2];
		    else if (pmode == ANTIPARALLEL) 
			cs = &cfp->dev->dev_terms[2];
	    }

	    chS = extHierSDAttr(cs); chD = extHierSDAttr(cd);
	    if (!(chS || chD || hS || hD)) /* all flat S, D */
		goto mergeThem;
	    if (cfp->hierName != hierName && 
			((chS && !hS) || (chD && !hD) ||
			(!chS && hS) || (!chD && hD)))
	    { 

		efHNSprintfPrefix((cfp->hierName)?cfp->hierName:hierName,
				     esTempName);
		TxError("Warning: conflicting SD attributes of parallel"
			" devs in cell: %s\n", esTempName);
		break;
	    }
	    else if (cfp->hierName == hierName)
	    {
		if (hS && !chS)
		{
		    mergeAttr(&cs->dterm_attrs, &source->dterm_attrs);
		}
		if (hD && !chD)
		{
		    mergeAttr(&cd->dterm_attrs, &drain->dterm_attrs);
		}
	    }
	    else /* cfp->hierName != hierName */
		 break;
mergeThem:
	    switch(dev->dev_class)
	    {
		case DEV_MOSFET:
		case DEV_FET:
		    m = esFMult[cfp->esFMIndex] + ((float)fp->w / (float)cfp->w);
		    break;
		case DEV_RES:
		    if (fp->dev->dev_type == esNoModelType)
		        m = esFMult[cfp->esFMIndex] + (fp->dev->dev_values.dev_res
				/ cfp->dev->dev_values.dev_res);
		    else
		        m = esFMult[cfp->esFMIndex] + ((float)fp->l / (float)cfp->l);
		    break;
		case DEV_CAP:
		    if (fp->dev->dev_type == esNoModelType)
		        m = esFMult[cfp->esFMIndex] + (fp->dev->dev_values.dev_cap
				/ cfp->dev->dev_values.dev_cap);
		    else
		        m = esFMult[cfp->esFMIndex] +
				(((float)fp->l  * (float)fp->w)
				/ ((float)cfp->l * (float)cfp->w));
		    break;
	    }
	    setDevMult(fp->esFMIndex, DEV_KILLED); 
	    setDevMult(cfp->esFMIndex, m);
	    esDevsMerged++;
	    /* Need to do attribute stuff here */
	    freeMagic(fp);
	    return 0;
	}
    }

    /* No parallel devs to it yet */
    fp->next = devMergeList;
    devMergeList = fp;
    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * update_w --
 * Updates the width client of node n with the current dev width
 *
 * Results:
 *  N/A
 *
 * Side effects:
 *  might allocate node client and widths
 *
 * ----------------------------------------------------------------------------
 */

int 
update_w(resClass, w,  n)
    short  resClass;
    int w;
    EFNode *n;
{
    nodeClient *nc;
    int i;

    if (n->efnode_client == (ClientData)NULL) 
	initNodeClient(n);
    nc = (nodeClient *) n->efnode_client;
    if (nc->m_w.widths == NULL)
    {
	(nc->m_w.widths) = (float *)mallocMagic((unsigned)sizeof(float)
		* efNumResistClasses);
	for (i = 0; i < EFDevNumTypes; i++)
	    nc->m_w.widths[i] = 0.0;
    }
    nc->m_w.widths[resClass] += (float)w;
}

/*
 * ----------------------------------------------------------------------------
 *
 * devDistJunctVisit --
 *  Called for every dev and updates the nodeclients of its terminals
 *
 * Results:
 *  0 to keep the calling procedure going
 *
 * Side effects:
 *  calls update_w which might allocate stuff
 *
 * ----------------------------------------------------------------------------
 */

int 
devDistJunctVisit(dev, hierName, trans)
    Dev *dev;			/* Dev to examine */
    HierName *hierName;		/* Hierarchical path down to this dev */
    Transform *trans;		/* Coordinate transform not used */
{
    EFNode  *n;
    int      scale, i;
    int l, w;
    Rect r;

    if (dev->dev_nterm < 2)
    {
	TxError("outPremature\n");
	return 0;
    }

    GeoTransRect(trans, &dev->dev_rect, &r);
    scale = GeoScale(trans);
    w *= scale;
    EFGetLengthAndWidth(dev, &l, &w);

    for (i = 1; i<dev->dev_nterm; i++)
    {
	n = GetNode(hierName,
		dev->dev_terms[i].dterm_node->efnode_name->efnn_hier);
		update_w(fetInfo[dev->dev_type].resClassSD, w, n);
    }
    return 0;
}

