/*
 * ext2spice.c --
 *
 * Program to flatten hierarchical .ext files and produce
 * a .spice file, suitable for use as input to simulators
 * 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 const char rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-8.0/ext2spice/ext2spice.c,v 1.8 2010/08/25 17:33:56 tim Exp $";
#endif  /* not lint */

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

#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"
#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"
#include "extract/extract.h"	/* for extDevTable */
#include "extflat/extflat.h"
#include "extflat/EFint.h"
#include "utils/runstats.h"

#include "ext2spice/ext2spice.h"

/* Options specific to ext2spice */
bool esDoExtResis = FALSE;
bool esDoPorts = TRUE;
bool esDoHierarchy = FALSE;
bool esDoBlackBox = FALSE;
bool esDoRenumber = FALSE;
bool esDoResistorTee = FALSE;
int  esDoSubckt = AUTO;
bool esDevNodesOnly = FALSE;
bool esMergeNames = TRUE;
bool esNoAttrs = FALSE;
bool esHierAP = FALSE;
char spcesDefaultOut[FNSIZE];
const char *esSpiceCapNode;
const char esSpiceDefaultGnd[] = "0";
char *spcesOutName = spcesDefaultOut;
FILE *esSpiceF = NULL;
float esScale = -1.0 ; /* negative if hspice the EFScale/100 otherwise */

unsigned short esFormat = SPICE3 ;

int esCapNum, esDevNum, esResNum, esDiodeNum, esVoltNum;
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 */

fetInfoList esFetInfo[TT_MAXTYPES];

/* Record for keeping a list of global names */

typedef struct GLL *globalListPtr;

typedef struct GLL {
    globalListPtr gll_next;
    char *gll_name;
} globalList;

TileTypeBitMask initMask;   /* Used for device types, not tile types */

bool esMergeDevsA = FALSE; /* aggressive 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
 *---------------------------------------------------------
 */

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

const devMerge *devMergeList = NULL ;

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

extern void ESGenerateHierarchy(char *inName, int flags);  /* forward reference ext2hier.c */

/*
 * ----------------------------------------------------------------------------
 *
 * esFreeNodeClient ---
 *
 *	Free the string spiceNodeName associated with the nodeClient
 *	record that ext2spice allocates per each node structure.
 *
 * Returns:
 *	0 always.
 *
 * Side effects:
 *	Frees an allocated string.
 *
 * ----------------------------------------------------------------------------
 */

int
esFreeNodeClient(
    nodeClient *client)
{
    if (client != (nodeClient *)NULL)
	if (client->spiceNodeName != NULL)
	    freeMagic((char *)client->spiceNodeName);

    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * Apply modifications to a global node name and output to file "outf"
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Output to file
 * ----------------------------------------------------------------------------
 */

void
esFormatSubs(
    FILE *outf,
    char *suf)
{
    char *specchar;
    int l;

    if (outf)
    {
	l = strlen(suf) - 1;
	if (((EFOutputFlags & EF_TRIMGLOB ) && suf[l] == '!') ||
	         ((EFOutputFlags & EF_TRIMLOCAL) && suf[l] == '#'))
	    suf[l] = '\0' ;
	if (EFOutputFlags & EF_CONVERTCOMMA)
	    while ((specchar = strchr(suf, ',')) != NULL)
		*specchar = '|';
	if (EFOutputFlags & EF_CONVERTBRACKETS)
	{
	    while ((specchar = strchr(suf, '[')) != NULL)
		*specchar = '_';
	    while ((specchar = strchr(suf, ']')) != NULL)
		*specchar = '_';
	}
	if (EFOutputFlags & EF_CONVERTEQUAL)
	    while ((specchar = strchr(suf, '=')) != NULL)
		*specchar = ':';
	fprintf(outf, "%s", suf);
    }
}

#ifdef MAGIC_WRAPPER
#ifdef EXT2SPICE_AUTO
/*
 * ----------------------------------------------------------------------------
 *
 * Tcl package initialization function
 *
 * ----------------------------------------------------------------------------
 */

int
Exttospice_Init(
    Tcl_Interp *interp)
{
    /* Sanity checks! */
    if (interp == NULL) return TCL_ERROR;
    if (Tcl_PkgRequire(interp, "Tclmagic", MagicVersion, 0) == NULL)
	return TCL_ERROR;
    if (Tcl_InitStubs(interp, Tclmagic_InitStubsVersion, 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", MagicVersion);
    return TCL_OK;
}
#endif /* EXT2SPICE_AUTO */

#endif /* MAGIC_WRAPPER */

/*
 * ----------------------------------------------------------------------------
 *
 * 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_SCALE		8
#define EXTTOSPC_SHORT		9
#define EXTTOSPC_SUBCIRCUITS	10
#define EXTTOSPC_HIERARCHY	11
#define EXTTOSPC_BLACKBOX	12
#define EXTTOSPC_RENUMBER	13
#define EXTTOSPC_MERGENAMES	14
#define EXTTOSPC_LVS		15
#define EXTTOSPC_HELP		16

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

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

    short s_rclass, d_rclass, sub_rclass;
    char *devname;
    char *subname;
    TileType devtype;
    int idx, idx2;
    globalList *glist = NULL;

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

    static const char * const spiceFormats[] = {
	"SPICE2", "SPICE3", "HSPICE", "NGSPICE", "CDL", NULL
    };

    static const char * const cmdExtToSpcOption[] = {
	"[run] [options]	run exttospice 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",
	"scale [on|off]		use .option card for scaling",
	"short [voltage|resistor|none]\n"
	"			set method for handling shorted ports",
	"subcircuits [top|descend] [on|off|auto]\n"
	"			standard cells become subcircuit calls",
	"hierarchy [on|off]	output hierarchical spice for LVS",
	"blackbox [on|off]	output abstract views as black-box entries",
	"renumber [on|off]	on = number instances X1, X2, etc.\n"
	"			off = keep instance ID names",
	"global [on|off]	on = merge unconnected global nets by name",
	"lvs    		apply typical default settings for LVS",
	"help			print help information",
	NULL
    };

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

    static const char * const cmdShortTypes[] = {
	"none			merge shorted ports",
	"resistor		separate shorted ports with 0 ohm resistor",
	"voltage		separate shorted ports with 0 volt source",
	NULL
    };
    static const char * const cmdExtToSpcFormat[] = {
	"spice2",
	"spice3",
	"hspice",
	"ngspice",
	"cdl",
	NULL
    };

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

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

#if 0 /* -Wunused-variable */
    static const char * const shorttypes[] = {
	"none",
	"resistor",
	"voltage",
	NULL
    };
#endif

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

    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)
	    {
#ifdef MAGIC_WRAPPER
		Tcl_SetResult(magicinterp, (esDoExtResis) ? "on" : "off", NULL);
#else
		TxPrintf("Extresist:  %s\n", (esDoExtResis) ? "on" : "off");
#endif
		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)
	    {
#ifdef MAGIC_WRAPPER
		Tcl_SetResult(magicinterp, (esDoResistorTee) ? "on" : "off", NULL);
#else
		TxPrintf("Resistor tee:  %s\n", (esDoResistorTee) ? "on" : "off");
#endif
		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_SCALE:
	    if (cmd->tx_argc == 2)
	    {
#ifdef MAGIC_WRAPPER
		Tcl_SetResult(magicinterp, (esScale < 0) ? "on" : "off", NULL);
#else
		TxPrintf("Scale:  %s\n", (esScale < 0) ? "on" : "off");
#endif
		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) esScale = -1.0;
	    else esScale = 0.0;
	    break;

	case EXTTOSPC_HIERARCHY:
	    if (cmd->tx_argc == 2)
	    {
#ifdef MAGIC_WRAPPER
		Tcl_SetResult(magicinterp, (esDoHierarchy) ? "on" : "off", NULL);
#else
		TxPrintf("Hierarchy:  %s\n", (esDoHierarchy) ? "on" : "off");
#endif
		return;
	    }
	    idx = Lookup(cmd->tx_argv[2], yesno);
	    if (idx < 0) goto usage;
	    else if (idx < 3)	/* yes */
		esDoHierarchy = TRUE;
	    else	 /* no */
		esDoHierarchy = FALSE;
	    break;

	case EXTTOSPC_BLACKBOX:
	    if (cmd->tx_argc == 2)
	    {
#ifdef MAGIC_WRAPPER
		Tcl_SetResult(magicinterp, (esDoBlackBox) ? "on" : "off", NULL);
#else
		TxPrintf("Black box:  %s\n", (esDoBlackBox) ? "on" : "off");
#endif
		return;
	    }
	    idx = Lookup(cmd->tx_argv[2], yesno);
	    if (idx < 0) goto usage;
	    else if (idx < 3)	/* yes */
		esDoBlackBox = TRUE;
	    else	 /* no */
		esDoBlackBox = FALSE;
	    break;

	case EXTTOSPC_RENUMBER:
	    if (cmd->tx_argc == 2)
	    {
#ifdef MAGIC_WRAPPER
		Tcl_SetResult(magicinterp, (esDoRenumber) ? "on" : "off", NULL);
#else
		TxPrintf("Renumber:  %s\n", (esDoRenumber) ? "on" : "off");
#endif
		return;
	    }
	    idx = Lookup(cmd->tx_argv[2], yesno);
	    if (idx < 0) goto usage;
	    else if (idx < 3)	/* yes */
		esDoRenumber = TRUE;
	    else	 /* no */
		esDoRenumber = FALSE;
	    break;

	case EXTTOSPC_MERGENAMES:
	    if (cmd->tx_argc == 2)
	    {
#ifdef MAGIC_WRAPPER
		Tcl_SetResult(magicinterp, (esMergeNames) ? "on" : "off", NULL);
#else
		TxPrintf("Merge names:  %s\n", (esMergeNames) ? "on" : "off");
#endif
		return;
	    }
	    idx = Lookup(cmd->tx_argv[2], yesno);
	    if (idx < 0) goto usage;
	    else if (idx < 3)	/* yes */
		esMergeNames = TRUE;
	    else	 /* no */
		esMergeNames = FALSE;
	    break;

	case EXTTOSPC_SHORT:
	    if (cmd->tx_argc == 2)
	    {
		if ((EFOutputFlags & EF_SHORT_MASK) == EF_SHORT_NONE)
#ifdef MAGIC_WRAPPER
		    Tcl_SetResult(magicinterp, "none", NULL);
#else
		    TxPrintf("Shorts:  none\n");
#endif
		else if ((EFOutputFlags & EF_SHORT_MASK) == EF_SHORT_R)
#ifdef MAGIC_WRAPPER
		    Tcl_SetResult(magicinterp, "resistor", NULL);
#else
		    TxPrintf("Shorts:  resistor\n");
#endif
		else if ((EFOutputFlags & EF_SHORT_MASK) == EF_SHORT_V)
#ifdef MAGIC_WRAPPER
		    Tcl_SetResult(magicinterp, "voltage source", NULL);
#else
		    TxPrintf("Shorts:  voltage source\n");
#endif
		return;
	    }
	    idx = Lookup(cmd->tx_argv[2], cmdShortTypes);
	    if (idx < 0) goto usage;
	    else switch (idx)
	    {
		case 0:
		    EFOutputFlags &= ~EF_SHORT_MASK;
		    EFOutputFlags |= EF_SHORT_NONE;
		    break;
		case 1:
		    EFOutputFlags &= ~EF_SHORT_MASK;
		    EFOutputFlags |= EF_SHORT_R;
		    break;
		case 2:
		    EFOutputFlags &= ~EF_SHORT_MASK;
		    EFOutputFlags |= EF_SHORT_V;
		    break;
	    }
	    break;

	case EXTTOSPC_LVS:
	    /* Apply default command settings for LVS */
	    /* hierarchy = on		*/
	    /* format    = ngspice	*/
	    /* cthresh   = infinite	*/
	    /* rthresh   = infinite	*/
	    /* renumber  = off		*/
	    /* scale     = off		*/
	    /* blackbox  = on		*/
	    /* global    = off		*/
	    /* subcircuit top = auto	*/

	    esDoHierarchy = TRUE;
	    esFormat = NGSPICE;
	    LocCapThreshold = (EFCapValue)INFINITE_THRESHOLD_F;
	    LocResistThreshold = INFINITE_THRESHOLD;
	    esDoRenumber = FALSE;
	    esScale = 0.0;
	    esDoBlackBox = TRUE;
	    esMergeNames = FALSE;
	    esDoSubckt = 2;
	    break;

	case EXTTOSPC_SUBCIRCUITS:
	    if (cmd->tx_argc == 2)
	    {
#ifdef MAGIC_WRAPPER
		Tcl_SetResult(magicinterp, (esDoPorts) ? "on" : "off", NULL);
#else
		TxPrintf("Ports:  %s\n", (esDoPorts) ? "on" : "off");
#endif
		return;
	    }
	    idx = Lookup(cmd->tx_argv[2], subcktopts);
	    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)
		    {
#ifdef MAGIC_WRAPPER
			Tcl_SetResult(magicinterp, (esDoPorts) ? "on" : "off", NULL);
#else
			TxPrintf("Ports:  %s\n", (esDoPorts) ? "on" : "off");
#endif
			return;
		    }
		    break;
		case IDX_TOP:
		    if (cmd->tx_argc == 3)
		    {
#ifdef MAGIC_WRAPPER
			Tcl_SetResult(magicinterp,
				(esDoSubckt == 2) ? "auto" :
				(esDoSubckt == 1) ? "on" : "off", NULL);
#else
			TxPrintf("Top subcircuit:  %s\n",
				(esDoSubckt == 2) ? "auto" :
				(esDoSubckt == 1) ? "on" : "off");
#endif
			return;
		    }
		    break;
		default:
		    goto usage;
		    break;
	    }

	    if (cmd->tx_argc != 4) goto usage;
	    idx2 = Lookup(cmd->tx_argv[3], subcktopts);
	    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;
		default:
		    goto usage;
	    }
	    break;

	case EXTTOSPC_FORMAT:
	    if (cmd->tx_argc == 2)
	    {
#ifdef MAGIC_WRAPPER
		/* FIXME check NULL is ok here, should this be TCL_STATIC ?  TCL_STATIC==0  maybe using TCL_STATIC is better? */
		Tcl_SetResult(magicinterp, (char*)cmdExtToSpcFormat[esFormat], NULL);
#else
		TxPrintf("Format:  %s\n", cmdExtToSpcFormat[esFormat]);
#endif
		return;
	    }
	    else if (cmd->tx_argc < 3) goto usage;
	    idx = Lookup(cmd->tx_argv[2], cmdExtToSpcFormat);
	    if (idx < 0)
	    {
#ifdef MAGIC_WRAPPER
		Tcl_SetResult(magicinterp, "Bad format type.  Formats are:"
			"spice2, spice3, hspice, ngspice, and cdl.", NULL);
#else
		TxError("Bad format type.  Formats are:"
			"spice2, spice3, hspice, ngspice, and cdl.");
#endif
		return;
	    }
	    else
	    {
		esFormat = idx;
		/* By default, use .option to declare scale in HSPICE mode */
		if (esFormat == HSPICE) esScale = -1.0;
	    }
	    break;

	case EXTTOSPC_CTHRESH:
	    if (cmd->tx_argc == 2)
	    {
		if (!IS_FINITE_F(LocCapThreshold))
#ifdef MAGIC_WRAPPER
		    Tcl_SetResult(magicinterp, "infinite", NULL);
#else
		    TxPrintf("Capacitance threshold:  infinite\n");
#endif
		else
#ifdef MAGIC_WRAPPER
		    Tcl_SetObjResult(magicinterp,
			Tcl_NewDoubleObj((double)LocCapThreshold));
#else
		    TxPrintf("Capacitance threshold:  %lf\n",
			    (double)LocCapThreshold);
#endif
		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_F;
	    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)
#ifdef MAGIC_WRAPPER
		    Tcl_SetResult(magicinterp, "infinite", NULL);
#else
		    TxPrintf("Resistance threshold:  infinite\n");
#endif
		else
#ifdef MAGIC_WRAPPER
		    Tcl_SetObjResult(magicinterp,
			Tcl_NewIntObj(LocResistThreshold));
#else
		    TxPrintf("Resistance threshold:  %lf\n",
			    (double)LocResistThreshold);
#endif
		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)
#ifdef MAGIC_WRAPPER
		    Tcl_SetResult(magicinterp, "aggressive", NULL);
#else
		    TxPrintf("Merge:  aggressive\n");
#endif
		else if (esMergeDevsC)
#ifdef MAGIC_WRAPPER
		    Tcl_SetResult(magicinterp, "conservative", NULL);
#else
		    TxPrintf("Merge:  conservative\n");
#endif
		else
#ifdef MAGIC_WRAPPER
		    Tcl_SetResult(magicinterp, "none", NULL);
#else
		    TxPrintf("Merge:  none\n");
#endif
		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;
	    /* Clear EFOutputFlags but preserve "short" behavior */
	    EFOutputFlags &= ~EF_TRIM_MASK;
	    EFOutputFlags |= EF_CONVERTCOMMA | EF_CONVERTEQUAL;
	    EFScale = 0.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;
    esVoltNum  = 0;
    esDevNum = 1000;
    esResNum = 0;
    esDiodeNum = 0;
    esSbckNum = 0;
    esNodeNum = 10; /* just in case we're extracting spice2 */
    esFMIndex = 0;
    esSpiceDevsMerged = 0;
    esDevNodesOnly = FALSE;	/* so using -F doesn't become permanent */

    EFInit();

    EFResistThreshold = LocResistThreshold;
    EFCapThreshold = LocCapThreshold;

    /* Process command line arguments */

    inName = EFArgs(argc, argv, &err_result, spcParseArgs, (ClientData) NULL);
    if (err_result == TRUE)
    {
	EFDone(NULL);
	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)
    {
	if (esFormat == CDL)
	    sprintf(spcesDefaultOut, "%s.cdl", inName);
	else
	    sprintf(spcesDefaultOut, "%s.spice", inName);
    }

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

    /* If the .ext file was read without error, then open the output file */

    if ((esSpiceF = fopen(spcesOutName, "w")) == NULL)
    {
#ifdef MAGIC_WRAPPER
	char *tclres = Tcl_Alloc(128);
	sprintf(tclres, "exttospice: Unable to open file %s for writing\n",
		spcesOutName);
	Tcl_SetResult(magicinterp, tclres, TCL_DYNAMIC);
#else
	TxError("exttospice: Unable to open file %s for writing\n", spcesOutName);
#endif
	EFDone(NULL);
        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 < TT_MAXTYPES ; i++ ) {
	esFetInfo[i].resClassSource = NO_RESCLASS;
	esFetInfo[i].resClassDrain = NO_RESCLASS;
	esFetInfo[i].resClassSub = NO_RESCLASS;
	esFetInfo[i].defSubs = NULL;
    }

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

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

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

	if (esDoHierarchy && (subname != NULL))
	{
	    globalList *glptr;
	    char *locsubname, *bangptr;
	    bool isgood = TRUE;

	    locsubname = StrDup(NULL, subname);

	    if (esFormat != CDL)
	    {
		bangptr = locsubname + strlen(locsubname) - 1;
		if (*bangptr == '!') *bangptr = '\0';
	    }

	    // Ad-hoc check: Global names with "Error", "err", etc.
	    // should be rejected from the list.  Also node name
	    // "None" is a common entry indicating that extracting
	    // an implicit substrate is disallowed.

	    if (!strncmp(locsubname, "err", 3)) isgood = FALSE;
	    else if (strstr(locsubname, "error") != NULL) isgood = FALSE;
	    else if (strstr(locsubname, "Error") != NULL) isgood = FALSE;
	    else if (strstr(locsubname, "ERROR") != NULL) isgood = FALSE;
	    else if (!strcasecmp(locsubname, "None")) isgood = FALSE;

	    for (glptr = glist; glptr; glptr = glptr->gll_next)
		if (!strcmp(glptr->gll_name, locsubname))
		    break;

	    if (isgood && (glptr == NULL))
	    {
		glptr = (globalList *)mallocMagic(sizeof(globalList));
		glptr->gll_name = locsubname;
		glptr->gll_next = glist;
		glist = glptr;
	    }
	    else
		freeMagic(locsubname);
	}
    }

#ifdef MAGIC_WRAPPER
    if (esFormat == CDL)
    {
	/* In CDL format, if the global substrate ends with "!" then
	 * add it to the list of globals;  likewise for VDD and GND.
	 */
	char *glbstr;
	globalList *glptr;

	glbstr = (char *)Tcl_GetVar(magicinterp, "SUB", TCL_GLOBAL_ONLY);
	if ((glbstr != NULL) && (*(glbstr + strlen(glbstr) - 1) == '!'))
	{
	    glptr = (globalList *)mallocMagic(sizeof(globalList));
	    glptr->gll_name = StrDup((char **)NULL, glbstr);
	    glptr->gll_next = glist;
	    glist = glptr;
	}

	glbstr = (char *)Tcl_GetVar(magicinterp, "VDD", TCL_GLOBAL_ONLY);
	if ((glbstr != NULL) && (*(glbstr + strlen(glbstr) - 1) == '!'))
	{
	    glptr = (globalList *)mallocMagic(sizeof(globalList));
	    glptr->gll_name = StrDup((char **)NULL, glbstr);
	    glptr->gll_next = glist;
	    glist = glptr;
	}

	glbstr = (char *)Tcl_GetVar(magicinterp, "GND", TCL_GLOBAL_ONLY);
	if ((glbstr != NULL) && (*(glbstr + strlen(glbstr) - 1) == '!'))
	{
	    glptr = (globalList *)mallocMagic(sizeof(globalList));
	    glptr->gll_name = StrDup((char **)NULL, glbstr);
	    glptr->gll_next = glist;
	    glist = glptr;
	}
    }

    if (EFCompat == TRUE)
    {
	/* 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 */
    }
#endif

    /* Write the output file */

    fprintf(esSpiceF, "* %s file created from %s.ext - technology: %s\n\n",
	    spiceFormats[esFormat], inName, EFTech);
    if (esScale < 0)
    {
    	fprintf(esSpiceF, ".option scale=");
	esSIvalue(esSpiceF, 1.0E-6 * EFScale / 100.0);
    	fprintf(esSpiceF, "\n\n");
    }
    else
	esScale = EFScale / 100.0;

    /* Set output format flags */

    flatFlags = EF_FLATNODES;
    if (esMergeNames == FALSE) flatFlags |= EF_NONAMEMERGE;

    // This forces options TRIMGLOB and CONVERTEQUAL, not sure that's such a
    // good idea. . .
    EFOutputFlags |= EF_TRIMGLOB | EF_CONVERTEQUAL | EF_CONVERTCOMMA;
    if (IS_FINITE_F(EFCapThreshold)) flatFlags |= EF_FLATCAPS;
    if (esFormat == HSPICE)
	EFOutputFlags |= EF_TRIMLOCAL;
    if (esFormat == CDL)
	EFOutputFlags &= ~EF_TRIMGLOB;

    /* Write globals under a ".global" card */

    if (esDoHierarchy && (glist != NULL))
    {
	fprintf(esSpiceF, ".global ");
	while (glist != NULL)
	{
#ifdef MAGIC_WRAPPER
	    if (EFCompat == TRUE)
	    {
		/* Handle global names that are TCL variables */
		if (glist->gll_name[0] == '$')
		{
		    resstr = (char *)Tcl_GetVar(magicinterp,
				&(glist->gll_name[1]), TCL_GLOBAL_ONLY);
		    if (resstr != NULL)
			esFormatSubs(esSpiceF, resstr);
		    else
			esFormatSubs(esSpiceF, glist->gll_name);
		}
		else
		    esFormatSubs(esSpiceF, glist->gll_name);
	    }
	    else
#endif
		esFormatSubs(esSpiceF, glist->gll_name);

	    fprintf(esSpiceF, " ");
	    freeMagic(glist->gll_name);
	    free_magic1_t mm1 = freeMagic1_init();
	    freeMagic1(&mm1, glist);
	    glist = glist->gll_next;
	    freeMagic1_end(&mm1);
	}
	fprintf(esSpiceF, "\n\n");
    }

    /* Convert the hierarchical description to a flat one */

    if (esFormat == HSPICE) {
	HashInit(&subcktNameTable, 32, HT_STRINGKEYS);
#ifndef UNSORTED_SUBCKT
	DQInit(&subcktNameQueue, 64);
#endif
    }
    locDoSubckt = FALSE;
    if (esDoHierarchy)
    {
	ESGenerateHierarchy(inName, flatFlags);
    }
    else
    {
	EFFlatBuild(inName, flatFlags);

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

	/* When generating subcircuits, remove the subcircuit	*/
	/* flag from the top level cell.  Other than being	*/
	/* used to generate the subcircuit wrapper, it should	*/
	/* not prevent descending into its own hierarchy.	*/

	efFlatRootDef->def_flags &= ~(DEF_SUBCIRCUIT);

	/* 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);

	TTMaskZero(&initMask);
	if (!esDistrJunct)
	    TTMaskSetType(&initMask, efNumResistClasses);

	if (esMergeDevsA || esMergeDevsC)
	{
	    const devMerge *p;

	    EFVisitDevs(devMergeVisit, (ClientData) NULL);
	    TxPrintf("Devs merged: %d\n", esSpiceDevsMerged);
	    esFMIndex = 0;
	    free_magic1_t mm1 = freeMagic1_init();
	    for (p = devMergeList; p != NULL; p = p->next)
		freeMagic1(&mm1, (char *) p);
	    freeMagic1_end(&mm1);
	    devMergeList = NULL;
	}
	else if (esDistrJunct)
     	    EFVisitDevs(devDistJunctVisit, (ClientData) NULL);
	EFVisitDevs(spcdevVisit, (ClientData) NULL);
	TTMaskZero(&initMask);
	if (flatFlags & EF_FLATCAPS)
	    EFVisitCaps(spccapVisit, (ClientData) NULL);

	EFVisitResists(spcresistVisit, PTR2CD(NULL));
	EFVisitSubcircuits(subcktVisit, (ClientData) NULL);

	/* Visit nodes to find the substrate node */
	EFVisitNodes(spcsubVisit, (ClientData)&substr);
	if (substr == NULL)
	    substr = StrDup((char **)NULL, esSpiceDefaultGnd);

	esSpiceCapNode = substr;
	EFVisitNodes(spcnodeVisit, (ClientData) NULL);

	if (EFCompat == FALSE) freeMagic(substr);

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

	if (esFormat == HSPICE)
	    printSubcktDict();

	EFFlatDone(esFreeNodeClient);
    }
    EFDone(esFreeNodeClient);
    if (esFormat == HSPICE) {
	HashKill(&subcktNameTable);
#ifndef UNSORTED_SUBCKT
	DQFree(&subcktNameQueue);
#endif
    }

    if (esSpiceF) fclose(esSpiceF);

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

#ifdef EXT2SPICE_MAIN	/* Independent program "ext2spice" has been deprecated */

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

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

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

    esSpiceDevsMerged = 0;

    static const char * const spiceFormats[] = {
	"SPICE2", "SPICE3", "HSPICE", "NGSPICE", NULL
    };

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

    inName = EFArgs(argc, argv, NULL, spcParseArgs, (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)
    {
	if (esFormat == CDL)
	    sprintf(spcesDefaultOut, "%s.cdl", inName);
	else
	    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, TRUE) == FALSE)
    {
	exit (1);
    }

    fprintf(esSpiceF, "* %s file created from %s.ext - technology: %s\n\n",
	    spiceFormats[esFormat], inName, EFTech);
    if (esScale < 0)
    {
    	fprintf(esSpiceF,".option scale=");
	esSIvalue(esSpiceF, 1.0E-6 * EFScale / 100.0);
	fprintf(esSpiceF, "\n\n");
    }
    else
	esScale = EFScale / 100.0;

    /* Convert the hierarchical description to a flat one */
    flatFlags = EF_FLATNODES;
    EFOutputFlags |= EF_TRIMGLOB ;
    if (IS_FINITE_F(EFCapThreshold)) flatFlags |= EF_FLATCAPS;
    if (esFormat == HSPICE) {
	EFOutputFlags |= 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, FALSE);

    /* 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);

    TTMaskZero(&initMask);
    if (!esDistrJunct)
	TTMaskSetType(&initMask, efNumResistClasses);

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

	    free_magic1_t mm1 = freeMagic1_init();
	    for (p = devMergeList; p != NULL; p = p->next)
		freeMagic1(&mm1, (char *)p);
	    freeMagic1_end(&mm1);
	}
    } else if ( esDistrJunct )
     	EFVisitDevs(devDistJunctVisit, (ClientData) NULL);
    EFVisitDevs(spcdevVisit, (ClientData) NULL);
    TTMaskZero(&initMask);
    if (flatFlags & EF_FLATCAPS)
	EFVisitCaps(spccapVisit, (ClientData) NULL);

    EFVisitResists(spcresistVisit, PTR2CD(NULL));
    EFVisitSubcircuits(subcktVisit, (ClientData) NULL);
    esSpiceCapNode = StrDup((char **)NULL, esSpiceDefaultGnd);
    EFVisitNodes(spcnodeVisit, (ClientData) NULL);

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

    if (esFormat == HSPICE)
	printSubcktDict();

    EFFlatDone(esFreeNodeClient);
    EFDone(esFreeNodeClient);
    if (esFormat == HSPICE) {
	HashKill(&subcktNameTable);
#ifndef UNSORTED_SUBCKT
	DQFree(&subcktNameQueue);
#endif
    }

    if (esSpiceF) fclose(esSpiceF);

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

#endif /* EXT2SPICE_MAIN Deprecated */


/*
 * ----------------------------------------------------------------------------
 *
 * spcParseArgs --
 *
 * 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
spcParseArgs(
    int *pargc,
    char ***pargv)
{
    char **argv = *pargv;
    int argc = *pargc;
    char *ftmp, *t;

    const char usage_text[] = "Usage: ext2spice "
		"[-B] [-o spicefile] [-M|-m] [-J flat|hier]\n"
		"[-f spice2|spice3|hspice|ngspice|cdl] [-M] [-m] "
		"[file]\n";

    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':
	    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;
		esScale = -1.0;
	    }
	    else if (strcasecmp(ftmp, "NGSPICE") == 0)
		esFormat = NGSPICE;
	    else if (strcasecmp(ftmp, "CDL") == 0)
		esFormat = CDL;
	    else goto usage;
	    break;

	case 'J':
	    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':
	    if ((t = ArgStr(&argc, &argv, "cap-accuracy")) == NULL)
		goto usage;
	    TxPrintf("The cap accuracy flag 'y' is deprecated.\n");
	    break;

	case 'h':	/* -h or -help, as suggested by "ext2spice help" */
	    TxPrintf(usage_text);
	    break;

	default:
	    TxError("Unrecognized flag: %s\n", argv[0]);
	    goto usage;
    }

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

usage:
    TxError(usage_text);
    return 1;
}

/*
 * ----------------------------------------------------------------------------
 *
 * SpiceGetNode --
 *
 * function to find a node given its hierarchical prefix and suffix
 *
 * Results:
 *  a pointer to the node struct or NULL
 *
 * ----------------------------------------------------------------------------
 */
EFNode *
SpiceGetNode(
    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(
    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).
 *
 * ----------------------------------------------------------------------------
 */

/* @typedef cb_extflat_visitsubcircuits_t (UNUSED) */
int
subcktVisit(
    Use *use,
    HierName *hierName,
    bool is_top)		/* TRUE if this is the top-level cell */
{
    EFNode *snode;
    Def *def = use->use_def;
    EFNodeName *sname, *nodeName;
    HashSearch hs;
    HashEntry *he;
    int portmax, portidx, tchars;
    char stmp[MAX_STR_SIZE];
    char *instname, *subcktname;
    DevParam *plist, *pptr;
    EFNodeName **nodeList;

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

    /* Retain instance name unless esDoRenumber is set, or format is Spice2 */
    if (use->use_id == NULL || esDoRenumber == TRUE || esFormat == SPICE2)
    {
	/* NOTE:  This really needs to update subcktNameTable so that	*/
	/* it tracks between instance names and node names, when using	*/
	/* HSPICE format + esDoRenumber.				*/

	fprintf(esSpiceF, "X%d", esSbckNum++);
	tchars = 5;
    }
    else
    {
	int savflags = EFOutputFlags;
	EFOutputFlags &= ~EF_TRIM_MASK;
	EFOutputFlags |= EF_CONVERTCOMMA;  // Only substitute commas on subcircuit names

	/* Use full hierarchical decomposition for name */
	/* (not just use->use_id.  hierName already has use->use_id at end) */
	EFHNSprintf(stmp, hierName);
	fprintf(esSpiceF, "X%s", stmp);
	EFOutputFlags = savflags;
	tchars = 1 + strlen(stmp);
    }

    /* 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 = EFGetPortMax(def);

    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, since explicit port order is	*/
	/* generated during topVisit().				*/

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

	    if (snode && (snode->efnode_flags & EF_PORT))
	        for (nodeName = sname; nodeName != NULL; nodeName = nodeName->efnn_next)
		    if (nodeName->efnn_port >= 0)
		    {
			portmax++;
			if (tchars > 80)
			{
			    fprintf(esSpiceF, "\n+");
			    tchars = 1;
			}
			tchars += 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.		 */

	nodeList = (EFNodeName **)mallocMagic((portmax + 1) * sizeof(EFNodeName *));
	for (portidx = 0; portidx <= portmax; portidx++)
	    nodeList[portidx] = (EFNodeName *)NULL;

    	HashStartSearch(&hs);
	while ((he = HashNext(&def->def_nodes, &hs)))
    	{
	    bool found = FALSE;

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

	    if ((snode == NULL) || !(snode->efnode_flags & EF_PORT)) continue;

	    portidx = snode->efnode_name->efnn_port;

	    if (portidx >= 0)
	    {
		if (nodeList[portidx] == NULL)
		{
		    nodeList[portidx] = snode->efnode_name;
		    found = TRUE;
		}
	    }

	    /* Normally there should be a port associated with snode, but
	     * if not, go looking for one in the node name aliases.
	     */
	    for (nodeName = sname; nodeName != NULL; nodeName = nodeName->efnn_next)
	    {
		if (found == TRUE) break;

		portidx = nodeName->efnn_port;
		if (portidx < 0) continue;
		if (nodeList[portidx] == NULL)
		{
		    nodeList[portidx] = nodeName;
		    found = TRUE;
		}
		else if (EFHNBest(nodeName->efnn_hier, nodeList[portidx]->efnn_hier))
		{
		    nodeList[portidx] = nodeName;
		    found = TRUE;
		}
	    }
	}

	for (portidx = 0; portidx <= portmax; portidx++)
	{
	    nodeName = nodeList[portidx];

	    if (nodeName != NULL)
	    {
		if (tchars > 80)
		{
		    fprintf(esSpiceF, "\n+");
		    tchars = 1;
		}
		tchars += spcdevOutNode(hierName, nodeName->efnn_hier,
				"subcircuit", esSpiceF);
	    }
	}
	freeMagic(nodeList);
    }

    /* SPICE subcircuit names must begin with A-Z. */
    subcktname = def->def_name;
    if (!isalpha(*subcktname))
    {
	subcktname = mallocMagic(2 + strlen(def->def_name));
	sprintf(subcktname, "x%s", def->def_name);
	freeMagic(def->def_name);
	def->def_name = subcktname;
    }

    if (tchars > 80) fprintf(esSpiceF, "\n+");
    if (esFormat == CDL) fprintf(esSpiceF, " /");
    fprintf(esSpiceF, " %s", subcktname);	/* subcircuit model name */

    // Check for a "device parameter" defined with the name of the cell.
    // This contains a list of parameter strings to be passed to the
    // cell instance.

    instname = mallocMagic(2 + strlen(def->def_name));
    sprintf(instname, ":%s", def->def_name);
    plist = efGetDeviceParams(instname);
    for (pptr = plist; pptr; pptr = pptr->parm_next)
    {
	if (tchars > 80)
	{
	    fprintf(esSpiceF, "\n+");
	    tchars = 1;
	}
	fprintf(esSpiceF, " %s", pptr->parm_name);
	tchars += (1 + strlen(pptr->parm_name));
    }
    freeMagic(instname);
    fprintf(esSpiceF, "\n");
    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.
 *
 * ----------------------------------------------------------------------------
 */

/* @typedef cb_extflat_visitsubcircuits_t (UNUSED) */
int
subcktUndef(
    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;
}

/* Define a linked node name list */

typedef struct _lnn {
    EFNodeName *lnn_nodeName;
    struct _lnn *lnn_next;
} linkedNodeName;

/*
 * ----------------------------------------------------------------------------
 *
 * 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.  If "doStub"
 *	is TRUE, then the subcircuit is a stub (empty declaration) for a
 *	subcircuit, and implicit substrate connections should not be
 *	output.
 *
 * NOTE: The cookie-cutter method for extraction can result in multiple
 * connections to the same port if the net spans multiple extraction regions.
 * Because of this, it is necessary to make sure that the same port name is
 * not output twice.
 *
 * ----------------------------------------------------------------------------
 */

void
topVisit(
    Def *def,
    bool doStub)
{
    EFNode *snode;
    EFNodeName *sname, *nodeName;
    HashSearch hs;
    HashEntry *he, *hep, *heh;
    HashTable portNameTable;
    int portorder, portmax, tchars;
    bool explicit;
    DevParam *plist, *pptr;
    char *instname;
    char *subcktname;
    const char *pname;
    char **sorted_ports;
    linkedNodeName *lnn = NULL;

    /* Primitive devices are not output at all */
    if (def->def_flags & DEF_PRIMITIVE) return;

    HashInit(&portNameTable, 32, HT_STRINGKEYS);

    /* SPICE subcircuit names must begin with A-Z.  This will also be	*/
    /* enforced when writing X subcircuit calls.			*/
    subcktname = def->def_name;
    if (!isalpha(*subcktname))
    {
	subcktname = mallocMagic(2 + strlen(def->def_name));
	sprintf(subcktname, "x%s", def->def_name);
	freeMagic(def->def_name);
	def->def_name = subcktname;
    }

    fprintf(esSpiceF, ".subckt %s", subcktname);
    tchars = 8 + strlen(subcktname);

    /* 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);
	if (sname == NULL) continue;
	snode = sname->efnn_node;
	if ((!snode) || (!(snode->efnode_flags & EF_PORT))) continue;
        explicit = FALSE;
	portorder = snode->efnode_name->efnn_port;
	if (portorder > portmax) portmax = portorder;
	if (portorder != -1) explicit = TRUE;
	for (nodeName = sname; nodeName != NULL; nodeName = nodeName->efnn_next)
	{
	    portorder = nodeName->efnn_port;
	    if (portorder > portmax) portmax = portorder;
	    if (portorder != -1) explicit = TRUE;
	}
	if (explicit == FALSE)
	{
	    /* Tag this an an implicit port (port without an assigned index) */
	    linkedNodeName *newlnn =
			(linkedNodeName *)mallocMagic(sizeof(linkedNodeName));
	    newlnn->lnn_next = lnn;
	    newlnn->lnn_nodeName = sname;
	    lnn = newlnn;
	}
    }

    /* Make all port numbers explicit (unless this is a black-box	*/
    /* circuit "ext2spice blackbox on" is in effect).			*/

    while (lnn != NULL)
    {
	sname = lnn->lnn_nodeName;
	if (esDoBlackBox == FALSE || !(def->def_flags & DEF_ABSTRACT))
	    sname->efnn_port = ++portmax;
	free_magic1_t mm1 = freeMagic1_init();
	freeMagic1(&mm1, lnn);
	lnn = lnn->lnn_next;
	freeMagic1_end(&mm1);
    }

    /* Port numbers need not start at zero or be contiguous.  They will be  */
    /* printed in numerical order.  This is done by allocating space for    */
    /* the output first and generating text into the allocated array	    */
    /* indexed by port, to avoid multiple scans through the hash table.	    */

    sorted_ports = (char **)mallocMagic((portmax + 1) * sizeof(char *));
    for (portorder = 0; portorder <= portmax; portorder++)
	sorted_ports[portorder] = NULL;

    HashStartSearch(&hs);
    while ((he = HashNext(&def->def_nodes, &hs)))
    {
	char stmp[MAX_STR_SIZE];
	int portidx;
	bool found = FALSE;

	sname = (EFNodeName *) HashGetValue(he);
	if (sname == NULL) continue;	/* Should not happen */

	snode = sname->efnn_node;
	if ((!snode) || (!(snode->efnode_flags & EF_PORT))) continue;

	/* Found a node which is also a port */

	portidx = snode->efnode_name->efnn_port;
	if (portidx >= 0)
	{
	    if (sorted_ports[portidx] == NULL)
	    {
		if ((def->def_flags & DEF_ABSTRACT))
		{
		    EFHNSprintf(stmp, sname->efnn_hier);
		    pname = stmp;
		}
		else
		    pname = nodeSpiceName(snode->efnode_name->efnn_hier, NULL);

		hep = HashLookOnly(&portNameTable, pname);
		if (hep == (HashEntry *)NULL)
		{
		    hep = HashFind(&portNameTable, pname);
		    HashSetValue(hep, (ClientData)(pointertype)portidx);
		    sorted_ports[portidx] = StrDup((char **)NULL, pname);
		}
		else
		{
		    /* Node that was unassigned has been found to be
		     * a repeat (see NOTE at top), so make sure its
		     * port number is set correctly.
		     */
		    snode->efnode_name->efnn_port = (int)(pointertype)HashGetValue(hep);
		}
		found = TRUE;
	    }
	}

	if (!(def->def_flags & DEF_ABSTRACT))
	    heh = HashLookOnly(&efNodeHashTable, (char *)snode->efnode_name->efnn_hier);

	/* Might need to check here for a port that was optimized out? */

	/* If snode is flagged as a port but no port number was found, then
	 * check the all of the node's name entries to see if any of them has
	 * a port number.
	 */

	for (nodeName = sname; nodeName != NULL; nodeName = nodeName->efnn_next)
	{
	    if (found == TRUE) break;
	    portidx = nodeName->efnn_port;
	    if (portidx < 0) continue;

	    if (def->def_flags & DEF_ABSTRACT)
	    {
    	        heh = HashLookOnly(&efNodeHashTable, (char *)nodeName->efnn_hier);

		/* If view is abstract, rely on the given port name, not
		 * the node.  Otherwise, artifacts of the abstract view
		 * may cause nodes to be merged and the names lost.
		 */
		EFHNSprintf(stmp, nodeName->efnn_hier);
		pname = stmp;
	    }
	    else
		pname = nodeSpiceName(nodeName->efnn_hier, NULL);

	    if (heh == (HashEntry *)NULL) /* pname now resolved for log output */
	    {
		/* Port was optimized out */
		snode->efnode_flags &= ~EF_PORT;
		TxPrintf("Note:  Port %s was optimized out of %s\n",
			pname, def->def_name);
		continue;
	    }

	    hep = HashLookOnly(&portNameTable, pname);
	    if (hep == (HashEntry *)NULL)
	    {
	    	hep = HashFind(&portNameTable, pname);
		HashSetValue(hep, (ClientData)(pointertype)nodeName->efnn_port);
		if (sorted_ports[portidx] == NULL)
		{
		    sorted_ports[portidx] = StrDup((char **)NULL, pname);
		    found = TRUE;
		}
	    }
	    else
	    {
		/* Node that was unassigned has been found to be
		 * a repeat (see NOTE at top), so make sure its
		 * port number is set correctly.
		 */
		nodeName->efnn_port = (int)(pointertype)HashGetValue(hep);
	    }
	}
    }
    HashKill(&portNameTable);

    /* Output all ports, in order */

    for (portorder = 0; portorder <= portmax; portorder++)
    {
	if (sorted_ports[portorder] != NULL)
	{
	    if (tchars > 80)
	    {
		/* Line continuation */
		fprintf(esSpiceF, "\n+");
		tchars = 1;
	    }
	    fprintf(esSpiceF, " %s", sorted_ports[portorder]);
	    tchars += strlen(sorted_ports[portorder]) + 1;

	    freeMagic(sorted_ports[portorder]);
	}
    }
    freeMagic(sorted_ports);

    // Add any parameters defined by "property parameter" in the cell

    instname = mallocMagic(2 + strlen(def->def_name));
    sprintf(instname, ":%s", def->def_name);
    plist = efGetDeviceParams(instname);
    for (pptr = plist; pptr; pptr = pptr->parm_next)
    {
	if (tchars > 80)
	{
	    /* Line continuation */
	    fprintf(esSpiceF, "\n+");
	    tchars = 1;
	}
	pname = pptr->parm_name;
	fprintf(esSpiceF, " %s", pname);
	tchars += strlen(pname) + 1;
    }
    freeMagic(instname);

    fprintf(esSpiceF, "\n");
}

/*
 * ----------------------------------------------------------------------------
 *
 * spcWriteValue ---
 *
 * Special handling for CDL format:  Output any resistor or capacitor value,
 * value only, no parameter name.
 *
 * ----------------------------------------------------------------------------
 */

void
spcWriteValue(
    Dev *dev,		/* Dev being output */
    HierName *hierName)	/* Hierarchical path down to this dev */
{
    DevParam *plist;

    plist = efGetDeviceParams(EFDevTypes[dev->dev_type]);
    while (plist != NULL)
    {
	switch (plist->parm_type[0])
	{
	    case 'r':
		if (*(plist->parm_name) == '\0')
		{
		    fprintf(esSpiceF, " ");
		    esSIvalue(esSpiceF, (double)dev->dev_res);
		}
		break;
	    case 'c':
		if (*(plist->parm_name) == '\0')
		{
		    fprintf(esSpiceF, " ");
		    esSIvalue(esSpiceF, (double)dev->dev_res);
		}
		break;
	}
	plist = plist->parm_next;
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * spcWriteSubParam ---
 *
 * Special handling for CDL format:  Output any substrate parameter before
 * the device model.  Why this makes sense, I have no idea.
 *
 * ----------------------------------------------------------------------------
 */

bool
spcWriteSubParam(
    Dev *dev,		/* Dev being output */
    HierName *hierName)	/* Hierarchical path down to this dev */
{
    bool retval;	/* True if substrate parameter was output */
    DevParam *plist;

    retval = FALSE;
    plist = efGetDeviceParams(EFDevTypes[dev->dev_type]);
    while (plist != NULL)
    {
	switch (plist->parm_type[0])
	{
	    case 's':
		if (dev->dev_subsnode == NULL)
		    TxError("Error:  No substrate definition for device %s\n",
				EFDevTypes[dev->dev_type]);
		else
		{
		    fprintf(esSpiceF, " %s=", plist->parm_name);
		    spcdevSubstrate(hierName,
				dev->dev_subsnode->efnode_name->efnn_hier,
				dev->dev_type, esSpiceF);
		    retval = TRUE;
		}
		break;
	}
	plist = plist->parm_next;
    }
    return retval;
}

/*
 * ----------------------------------------------------------------------------
 *
 * spcWriteParams ---
 *
 * Write parameters to a device line in SPICE output.  This is normally
 * restricted to subcircuit devices but may include other devices to
 * accomodate various extensions to the basic SPICE format.
 *
 * ----------------------------------------------------------------------------
 */

void
spcWriteParams(
    Dev *dev,		/* Dev being output */
    HierName *hierName,	/* Hierarchical path down to this dev */
    float scale,	/* Scale transform for output */
    int l,		/* Device length, in internal units */
    int w,		/* Device width, in internal units */
    float sdM,		/* Device multiplier */
    bool subdone)	/* If TRUE, substrate parameter was already output */
{
    bool hierD;
    DevParam *plist, *dparam;
    int parmval;
    EFNode *dnode;

    plist = efGetDeviceParams(EFDevTypes[dev->dev_type]);
    while (plist != NULL)
    {
	switch (plist->parm_type[0])
	{
	    case 'a':
		// Check for area of terminal node vs. device area
		if (plist->parm_type[1] == '\0' || plist->parm_type[1] == '0')
		{
		    fprintf(esSpiceF, " %s=", plist->parm_name);
		    parmval = dev->dev_area;
		    if (esScale < 0)
			fprintf(esSpiceF, "%g", parmval * scale * scale);
		    else if (plist->parm_scale != 1.0)
			fprintf(esSpiceF, "%g", (double)parmval * (double)scale * (double)scale
				* (double)esScale * (double)esScale * plist->parm_scale
				* 1E-12);
		    else
			esSIvalue(esSpiceF, 1.0E-12 * (parmval + plist->parm_offset)
				* scale * scale * esScale * esScale);
		}
		else
		{
		    int pn, resclass;

		    pn = plist->parm_type[1] - '0';
		    if (pn >= dev->dev_nterm) pn = dev->dev_nterm - 1;

		    hierD = extHierSDAttr(&dev->dev_terms[pn]);

		    resclass = (pn > 1) ? esFetInfo[dev->dev_type].resClassDrain :
				esFetInfo[dev->dev_type].resClassSource;

		    // For parameter a<n> followed by parameter p<n>,
		    // process both at the same time.

		    if (plist->parm_next && plist->parm_next->parm_type[0]
				== 'p' && plist->parm_next->parm_type[1]
				== plist->parm_type[1])
		    {
			if (hierD)
			    spcnAPHier(&dev->dev_terms[pn], hierName,
				resclass, scale, plist->parm_type,
				plist->parm_next->parm_type,
				sdM, esSpiceF);
			else
			{
			    dnode = SpiceGetNode(hierName,
			 	dev->dev_terms[pn].dterm_node->efnode_name->efnn_hier);
			    spcnAP(&dev->dev_terms[pn], dnode, resclass, scale,
				plist->parm_name, plist->parm_next->parm_name,
				sdM, esSpiceF, w);
		 	}
			plist = plist->parm_next;
		    }
		    else
		    {
			if (hierD)
			    spcnAPHier(&dev->dev_terms[pn], hierName,
				resclass, scale, plist->parm_type, NULL,
				sdM, esSpiceF);
			else
			{
			    dnode = SpiceGetNode(hierName,
			    	dev->dev_terms[pn].dterm_node->efnode_name->efnn_hier);
			    spcnAP(&dev->dev_terms[pn], dnode, resclass, scale,
				plist->parm_name, NULL, sdM, esSpiceF, w);
			}
		    }
		}

		break;
	    case 'p':
		// Check for perimeter of terminal node vs. device perimeter
		if (plist->parm_type[1] == '\0' || plist->parm_type[1] == '0')
		{
		    fprintf(esSpiceF, " %s=", plist->parm_name);
		    parmval = dev->dev_perim;
		    if (esScale < 0)
			fprintf(esSpiceF, "%g", parmval * scale);
		    else if (plist->parm_scale != 1.0)
			fprintf(esSpiceF, "%g", (double)parmval * scale
				* esScale * plist->parm_scale * 1E-6);
		    else
			esSIvalue(esSpiceF, 1.0E-12 * (parmval + plist->parm_offset)
				* scale * esScale);
		}
		else
		{
		    int pn, resclass;

		    pn = plist->parm_type[1] - '0';
		    if (pn >= dev->dev_nterm) pn = dev->dev_nterm - 1;

		    resclass = (pn > 1) ? esFetInfo[dev->dev_type].resClassDrain :
				esFetInfo[dev->dev_type].resClassSource;

		    hierD = extHierSDAttr(&dev->dev_terms[pn]);

		    // For parameter p<n> followed by parameter a<n>,
		    // process both at the same time.

		    if (plist->parm_next && plist->parm_next->parm_type[0]
				== 'a' && plist->parm_next->parm_type[1]
				== plist->parm_type[1])
		    {
			if (hierD)
			    spcnAPHier(&dev->dev_terms[pn], hierName,
				resclass, scale, plist->parm_next->parm_type,
				plist->parm_type, sdM, esSpiceF);
			else
			{
			    dnode = SpiceGetNode(hierName,
			 	dev->dev_terms[pn].dterm_node->efnode_name->efnn_hier);
			    spcnAP(&dev->dev_terms[pn], dnode, resclass, scale,
				plist->parm_next->parm_name,
				plist->parm_name, sdM, esSpiceF, w);
		 	}
			plist = plist->parm_next;
		    }
		    else
		    {
			if (hierD)
			    spcnAPHier(&dev->dev_terms[pn], hierName,
				resclass, scale, NULL, plist->parm_type,
				sdM, esSpiceF);
			else
			{
			    dnode = SpiceGetNode(hierName,
			    	dev->dev_terms[pn].dterm_node->efnode_name->efnn_hier);
			    spcnAP(&dev->dev_terms[pn], dnode, resclass, scale,
				NULL, plist->parm_name, sdM, esSpiceF, w);
			}
		    }
		}
		break;

	    case 'l':
		// Check for length of device vs. depth of terminal
		if (plist->parm_type[1] == '\0' || plist->parm_type[1] == '0')
		{
		    fprintf(esSpiceF, " %s=", plist->parm_name);
		    if (esScale < 0)
			fprintf(esSpiceF, "%g", l * scale);
		    else if (plist->parm_scale != 1.0)
			fprintf(esSpiceF, "%g", (double)l * scale * esScale
				* plist->parm_scale * 1E-6);
		    else
			esSIvalue(esSpiceF, 1.0E-6 * (l + plist->parm_offset)
				* scale * esScale);
		}
		else
		{
		    /* l1, l2, etc. used to indicate the length of the terminal */
		    /* Find the value in dev_params */
		    for (dparam = dev->dev_params; dparam; dparam = dparam->parm_next)
		    {
			if ((strlen(dparam->parm_name) > 2) &&
				(dparam->parm_name[0] == 'l') &&
				(dparam->parm_name[1] == plist->parm_type[1]) &&
				(dparam->parm_name[2] == '='))
			{
			    int dval;
			    if (sscanf(&dparam->parm_name[3], "%d", &dval) == 1)
			    {
				fprintf(esSpiceF, " %s=", plist->parm_name);
				if (esScale < 0)
				    fprintf(esSpiceF, "%g", dval * scale);
				else if (plist->parm_scale != 1.0)
				    fprintf(esSpiceF, "%g", (double)dval * scale * esScale
						* plist->parm_scale * 1E-6);
				else
				    esSIvalue(esSpiceF, (dval + plist->parm_offset)
						* scale * esScale * 1.0E-6);
				dparam->parm_name[0] = '\0';
				break;
			    }
			}
		    }
		}
		break;

	    case 'w':
		// Check for width of device vs. width of terminal
		if (plist->parm_type[1] == '\0' || plist->parm_type[1] == '0')
		{
		    fprintf(esSpiceF, " %s=", plist->parm_name);
		    if (esScale < 0)
			fprintf(esSpiceF, "%g", w * scale);
		    else if (plist->parm_scale != 1.0)
			fprintf(esSpiceF, "%g", (double)w * scale * esScale
				* plist->parm_scale * 1E-6);
		    else
			esSIvalue(esSpiceF, 1.0E-6 * (w + plist->parm_offset)
				* scale * esScale);
		}
		else
		{
		    /* w1, w2, etc. used to indicate the width of the terminal */
		    /* Find the value in dev_params */
		    for (dparam = dev->dev_params; dparam; dparam = dparam->parm_next)
		    {
			if ((strlen(dparam->parm_name) > 2) &&
				(dparam->parm_name[0] == 'w') &&
				(dparam->parm_name[1] == plist->parm_type[1]) &&
				(dparam->parm_name[2] == '='))
			{
			    int dval;
			    if (sscanf(&dparam->parm_name[3], "%d", &dval) == 1)
			    {
				fprintf(esSpiceF, " %s=", plist->parm_name);
				if (esScale < 0)
				    fprintf(esSpiceF, "%g", dval * scale);
				else if (plist->parm_scale != 1.0)
				    fprintf(esSpiceF, "%g", (double)dval * scale * esScale
						* plist->parm_scale * 1E-6);
				else
				    esSIvalue(esSpiceF, (dval + plist->parm_offset)
						* scale * esScale * 1.0E-6);
				dparam->parm_name[0] = '\0';
				break;
			    }
			}
		    }
		}
		break;
	    case 's':
		if (!subdone)
		{
		    if (dev->dev_subsnode == NULL)
			TxError("Error:  No substrate definition for device %s\n",
				EFDevTypes[dev->dev_type]);
		    else
		    {
			fprintf(esSpiceF, " %s=", plist->parm_name);
			spcdevSubstrate(hierName,
				dev->dev_subsnode->efnode_name->efnn_hier,
				dev->dev_type, esSpiceF);
		    }
		}
		break;
	    case 'x':
		fprintf(esSpiceF, " %s=", plist->parm_name);
		if (esScale < 0)
		    fprintf(esSpiceF, "%g", dev->dev_rect.r_xbot * scale);
		else if (plist->parm_scale != 1.0)
		    fprintf(esSpiceF, "%g", (double)dev->dev_rect.r_xbot * scale
				* esScale * plist->parm_scale * 1E-6);
		else
		    esSIvalue(esSpiceF, (dev->dev_rect.r_xbot + plist->parm_offset)
				* scale * esScale * 1.0E-6);
		break;
	    case 'y':
		fprintf(esSpiceF, " %s=", plist->parm_name);
		if (esScale < 0)
		    fprintf(esSpiceF, "%g", dev->dev_rect.r_ybot * scale);
		else if (plist->parm_scale != 1.0)
		    fprintf(esSpiceF, "%g", (double)dev->dev_rect.r_ybot * (double)scale
				* (double)esScale * plist->parm_scale * 1E-6);
		else
		    esSIvalue(esSpiceF, (dev->dev_rect.r_ybot + plist->parm_offset)
				* scale * esScale * 1.0E-6);
		break;
	    case 'r':
		if (*(plist->parm_name) != '\0')
		{
		    fprintf(esSpiceF, " %s=", plist->parm_name);
		    esSIvalue(esSpiceF, (double)dev->dev_res);
		}
		break;
	    case 'c':
		if (*(plist->parm_name) != '\0')
		{
		    fprintf(esSpiceF, " %s=", plist->parm_name);
		    esSIvalue(esSpiceF, (double)dev->dev_cap);
		}
		break;
	}
	plist = plist->parm_next;
    }

    /* Add parameters that are to be copied verbatim */
    for (plist = dev->dev_params; plist; plist = plist->parm_next)
	fprintf(esSpiceF, " %s", plist->parm_name);
}

/*
 * ----------------------------------------------------------------------------
 *
 * 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 *dev,			/* Dev being output */
    HierName *hierName,		/* Hierarchical path down to this dev */
    float scale,		/* Scale transform for output */
    DevTerm *term1,
    DevTerm *term2,		/* Terminals of the device */
    bool has_model,		/* Is this a modeled resistor? */
    int l,
    int w,			/* Device length and width */
    int dscale)			/* Device scaling (for split resistors) */
{
    float sdM;

    /* 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,
			"esOutputResistor", esSpiceF);
    spcdevOutNode(hierName, term2->dterm_node->efnode_name->efnn_hier,
			"esOutputResistor", 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)(dev->dev_res)
			/ (double)(dscale)) / (double)sdM);
	spcWriteParams(dev, hierName, scale, l, w, sdM, FALSE);
    }
    else
    {
	bool subdone = FALSE;
	spcWriteValue(dev, hierName);
	if (esFormat == CDL)
	    subdone = spcWriteSubParam(dev, hierName);
	fprintf(esSpiceF, " %s", EFDevTypes[dev->dev_type]);

	if (esScale < 0)
	    fprintf(esSpiceF, " w=%g l=%g", w * scale, (l * scale) / dscale);
	else
	{
	    fprintf(esSpiceF, " w=");
	    esSIvalue(esSpiceF, 1.0E-6 * w * scale * esScale);
	    fprintf(esSpiceF, " l=");
	    esSIvalue(esSpiceF, 1.0E-6 * (l * scale * esScale) / dscale);
	}

	spcWriteParams(dev, hierName, scale, l, w, sdM, subdone);
	if (sdM != 1.0)
	    fprintf(esSpiceF, " M=%g", sdM);
    }
}

/* Report if device at index n has been deleted due to merging */

bool
devIsKilled(
    int n)
{
    return (esFMult[(n)] <= (float)0.0) ? TRUE : FALSE;
}

/* Add a dev's multiplier to the table and grow it if necessary */

void
addDevMult(
    float f)
{
    int i;
    float *op;

    if (esFMult == NULL) {
          esFMult = (float *) mallocMagic((unsigned) (esFMSize*sizeof(float)));
    }
    else if (esFMIndex >= esFMSize)
    {
        op = esFMult;
	esFMSize *= 2;
        esFMult = (float *)mallocMagic((unsigned)(esFMSize * sizeof(float)));
        for (i = 0; i < esFMSize / 2; i++) esFMult[i] = op[i];
        if (op) freeMagic(op);
    }
    esFMult[esFMIndex++] = f;
}

/* Set the multiplier value f of device at index i */

void
setDevMult(
    int i,
    float f)
{
    esFMult[i] = f;
}

/* Get the multiplier value of the device at the current index esFMIndex */

float
getCurDevMult(void)
{
    return (esFMult && (esFMIndex > 0)) ? esFMult[esFMIndex-1] : (float)1.0;
}


/* 
 *-----------------------------------------------------------------------------
 * swapDrainSource
 * 
 * Swap drain and source ordering and the related stuff
 * including the drain/source area parameters
 * 
 * This is typically called if any terminal is marked with attribute "D" or "S"
 * (label "D$" or "S$" at poly-diffusion interface),
 * then swap order of source and drain compared to the default ordering.	
 * 
 * Note:
 *	Before calling this function, ensure that dev->dev_nterm >= 3
 *
 * Results:
 *	None
 *
 * Side effects:
 *	Soure (dev->dev_terms[1]) and drain (dev->dev_terms[2]) terminals
 *	are swapped.
 *
 *-----------------------------------------------------------------------------
 */

void
swapDrainSource(
    Dev *dev)
{
    DevTerm tmpTerm;

    /* swap original terminals */
    memcpy(&tmpTerm, &(dev->dev_terms[1]), sizeof(DevTerm));
    memcpy(&(dev->dev_terms[1]), &(dev->dev_terms[2]), sizeof(DevTerm));
    memcpy(&(dev->dev_terms[2]), &tmpTerm, sizeof(DevTerm));
}


/*
 * ----------------------------------------------------------------------------
 *
 * 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.
 *
 * ----------------------------------------------------------------------------
 */

/* ARGSUSED */
/* @typedef cb_extflat_visitdevs_t (UNUSED) */
int
spcdevVisit(
    Dev *dev,		/* Dev being output */
    HierContext *hc,	/* Hierarchical context down to this dev */
    float scale,	/* Scale transform for output */
    Transform *trans,	/* (unused) */
    ClientData cdata)	/* (unused) */
{
    DevParam *plist, *pptr;
    DevTerm *gate, *source, *drain;
    EFNode  *subnode, *snode, *dnode, *subnodeFlat = NULL;
    int l, w, i;
    bool subAP= FALSE, hierS, hierD;
    float sdM;
    char name[12], devchar;
    bool has_model = TRUE;
    bool subdone = FALSE;
    HierName *hierName = hc->hc_hierName;

    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];

	/* If any terminal is marked with attribute "D" or "S"	*/
 	/* (label "D$" or "S$" at poly-diffusion interface),	*/
	/* then force order of source and drain accordingly.	*/

	if ((dev->dev_terms[1].dterm_attrs &&
		!strcmp(dev->dev_terms[1].dterm_attrs, "D")) ||
		(dev->dev_terms[2].dterm_attrs &&
		!strcmp(dev->dev_terms[2].dterm_attrs, "S")))
	{
	    swapDrainSource(dev);
	}
    }
    subnode = dev->dev_subsnode;

    /* Check for minimum number of terminals. */

    switch(dev->dev_class)
    {
	case DEV_SUBCKT:
	case DEV_VERILOGA:
	case DEV_RSUBCKT:
	case DEV_CSUBCKT:
	case DEV_DSUBCKT:
	case DEV_MSUBCKT:
	    break;
	case DEV_DIODE:
	case DEV_PDIODE:
	case DEV_NDIODE:
	    if ((dev->dev_nterm < 2) && (subnode == NULL))
	    {
		TxError("Diode has only one terminal\n");
		return 0;
	    }
	    break;
	default:
	    if (dev->dev_nterm < 2)
	    {
		TxError("Device other than subcircuit has only "
			"one terminal\n");
		return 0;
	    }
	    break;
    }

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

    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:
	case DEV_CAPREV:
	    if (dev->dev_nterm < 1)
		return 0;
	    if ((dev->dev_type == esNoModelType) ||
		    !strcmp(EFDevTypes[dev->dev_type], "None"))
		has_model = FALSE;
	    break;
    }

    /* Flag shorted devices---this should probably be an option */
    switch(dev->dev_class)
    {
	case DEV_MOSFET:
	case DEV_ASYMMETRIC:
	case DEV_FET:
	    if (source == drain)
	    {
		if (esFormat == NGSPICE) fprintf(esSpiceF, "; ");
		fprintf(esSpiceF, "** SOURCE/DRAIN TIED\n");
	    }
	    break;

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

    /* Generate SPICE device name */
    switch(dev->dev_class)
    {
	case DEV_MOSFET:
	case DEV_ASYMMETRIC:
	case DEV_FET:
	    devchar = 'M';
	    break;
	case DEV_BJT:
	    devchar = 'Q';
	    break;
	case DEV_DIODE:
	case DEV_NDIODE:
	case DEV_PDIODE:
	    devchar = 'D';
	    break;
	case DEV_RES:
	    devchar = 'R';
	    break;
	case DEV_VOLT:
	    devchar = 'V';
	    break;
	case DEV_CAP:
	case DEV_CAPREV:
	    devchar = 'C';
	    break;
	case DEV_VERILOGA:
	    devchar = 'N';
	    break;
	case DEV_SUBCKT:
	case DEV_RSUBCKT:
	case DEV_CSUBCKT:
	case DEV_DSUBCKT:
	case DEV_MSUBCKT:
	    devchar = 'X';
	    break;
    }
    fprintf(esSpiceF, "%c", devchar);

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

    if (gate->dterm_attrs)
    {
        /* Output the name found in the gate attributes	*/
	/* prefixed by the hierarchical name.		*/
	fprintf(esSpiceF, "%s%s", EFHNToStr(hierName), 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:
	    case DEV_NDIODE:
	    case DEV_PDIODE:
		fprintf(esSpiceF, "%d", esDiodeNum++);
		break;
	    case DEV_CAP:
	    case DEV_CAPREV:
		fprintf(esSpiceF, "%d", esCapNum++);
		break;
	    case DEV_VOLT:
		fprintf(esSpiceF, "%d", esVoltNum++);
		break;
	    case DEV_RSUBCKT:
	    case DEV_SUBCKT:
	    case DEV_VERILOGA:
	    case DEV_CSUBCKT:
	    case DEV_DSUBCKT:
	    case DEV_MSUBCKT:
		fprintf(esSpiceF, "%d", esSbckNum++);
		if ((dev->dev_class == DEV_RSUBCKT) && esDoResistorTee)
		    /* For resistor tee networks, use, e.g.,	*/
		    /* "X1A" and "X1B", for clarity		*/
		    fprintf(esSpiceF, "A");
		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);

	    if (esFormat == CDL)
		subdone = spcWriteSubParam(dev, hierName);
	    fprintf(esSpiceF, " %s", EFDevTypes[dev->dev_type]);
	    sdM = getCurDevMult();
	    spcWriteParams(dev, hierName, scale, l, w, sdM, subdone);
	    break;

	case DEV_MSUBCKT:
	case DEV_DSUBCKT:

	    /* MOS-like subcircuit is "Xnnn drain gate [source [sub]]"	*/
	    /* to more conveniently handle cases where MOS devices are	*/
	    /* modeled by subcircuits with the same pin ordering.	*/

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

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

	case DEV_SUBCKT:
	case DEV_CSUBCKT:
	case DEV_VERILOGA:

	    /* 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_class == DEV_DSUBCKT)
	    {
		/* Do nothing:  Both terminals have already been output */
	    }
	    else if (dev->dev_class != DEV_MSUBCKT)
	    {
		if (dev->dev_nterm > 1)
		    spcdevOutNode(hierName, source->dterm_node->efnode_name->efnn_hier,
				name, esSpiceF);
		if ((dev->dev_class == DEV_RSUBCKT) && esDoResistorTee)
		{
		    l /= 2;	/* Halve the resistor.  Note that this may introduce
				 * error if l is an odd value.
				 */
		    spcdevOutNode(hierName, gate->dterm_node->efnode_name->efnn_hier,
				name, esSpiceF);
		}
		else if (dev->dev_nterm > 2)
		{
		    spcdevOutNode(hierName, drain->dterm_node->efnode_name->efnn_hier,
				name, esSpiceF);

		    spcdevResPi(hierName, gate->dterm_node->efnode_name->efnn_hier,
				source->dterm_node->efnode_name->efnn_hier,
				drain->dterm_node->efnode_name->efnn_hier,
				name);
		}
	    }
	    else    /* class DEV_MSUBCKT */
	    {
		if (dev->dev_nterm > 2)
		    spcdevOutNode(hierName, source->dterm_node->efnode_name->efnn_hier,
				name, esSpiceF);
	    }

	    /* The following only applies to DEV_SUBCKT and DEV_VERILOGA, which	*/
	    /* may define as many terminal types as they want.			*/

	    for (i = 4; i < dev->dev_nterm; i++)
	    {
		drain = &dev->dev_terms[i - 1];
		spcdevOutNode(hierName, drain->dterm_node->efnode_name->efnn_hier,
			name, esSpiceF);
	    }

	    /* Get the device parameters now, and check if the substrate is	*/
	    /* passed as a parameter rather than as a node.			*/

	    plist = efGetDeviceParams(EFDevTypes[dev->dev_type]);
	    for (pptr = plist; pptr != NULL; pptr = pptr->parm_next)
		if (pptr->parm_type[0] == 's')
		    break;

	    if ((pptr == NULL) && subnode)
	    {
		fprintf(esSpiceF, " ");
		subnodeFlat = spcdevSubstrate(hierName,
			subnode->efnode_name->efnn_hier,
			dev->dev_type, esSpiceF);
	    }

	    /* CDL format support:  Output a slash followed by a space. */
	    if (esFormat == CDL) fprintf(esSpiceF, " /");
	    fprintf(esSpiceF, " %s", EFDevTypes[dev->dev_type]);

	    /* Write all requested parameters to the subcircuit call.	*/

	    sdM = getCurDevMult();
	    spcWriteParams(dev, hierName, scale, l, w, sdM, FALSE);
	    if (sdM != 1.0)
		fprintf(esSpiceF, " M=%g", sdM);
	    break;

	    if (dev->dev_class == DEV_RSUBCKT && esDoResistorTee)
	    {
		/* Write the second half of the "Tee" resistor when
		 * the resistor is type DEV_RSUBCKT and not DEV_RES.
		 */
		fprintf(esSpiceF, "\n%c%dB", devchar, esSbckNum - 1);

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

		/* Get the device parameters now, and check if the substrate is	*/
		/* passed as a parameter rather than as a node.			*/

		plist = efGetDeviceParams(EFDevTypes[dev->dev_type]);
		for (pptr = plist; pptr != NULL; pptr = pptr->parm_next)
		    if (pptr->parm_type[0] == 's')
			break;

		if ((pptr == NULL) && subnode)
		{
		    fprintf(esSpiceF, " ");
		    subnodeFlat = spcdevSubstrate(hierName,
				subnode->efnode_name->efnn_hier,
				dev->dev_type, esSpiceF);

		    /* There is a substrate pin on the resistor subcircuit, so assume
		     * that the substrate cap is part of the subcircuit model, and
		     * zero it.
		     */
                    gate->dterm_node->efnode_cap = 0;
		}

		/* CDL format support:  Output a slash followed by a space. */
		if (esFormat == CDL) fprintf(esSpiceF, " /");
		fprintf(esSpiceF, " %s", EFDevTypes[dev->dev_type]);

		/* Write all requested parameters to the subcircuit call.	*/

		sdM = getCurDevMult();
		spcWriteParams(dev, hierName, scale, l, w, sdM, FALSE);
		if (sdM != 1.0)
		    fprintf(esSpiceF, " M=%g", sdM);
	    }
	    break;

	case DEV_RES:
	    if (esDoResistorTee)
	    {
		/* There are two ways of handling capacitance	*/
		/* on resistor networks.  The default is the Pi	*/
		/* network, which splits the capacitance of the	*/
		/* resistor itself evenly between the two	*/
		/* terminals.  The resistor itself is not	*/
		/* output as a node.  The other is the Tee	*/
		/* network, in which the resistor is divided in	*/
		/* two, with the capacitance to substrate in	*/
		/* the middle.					*/

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

		spcdevResPi(hierName, gate->dterm_node->efnode_name->efnn_hier,
				source->dterm_node->efnode_name->efnn_hier,
				drain->dterm_node->efnode_name->efnn_hier,
				name);
	    }
	    break;

	case DEV_VOLT:
	    /* The voltage source is "Vnnn term1 term2 0.0" and is used
	     * only to separate shorted port names.
	     */
	    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);
	    fprintf(esSpiceF, " 0.0");
	    break;

	case DEV_DIODE:
	case DEV_PDIODE:

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

	    spcdevOutNode(hierName, gate->dterm_node->efnode_name->efnn_hier,
			name, esSpiceF);
	    if (dev->dev_nterm > 1)
		spcdevOutNode(hierName, source->dterm_node->efnode_name->efnn_hier,
			name, esSpiceF);
	    else if (subnode)
		spcdevOutNode(hierName, subnode->efnode_name->efnn_hier,
			name, esSpiceF);

	
	    if (esFormat == CDL)
		subdone = spcWriteSubParam(dev, hierName);
	    fprintf(esSpiceF, " %s", EFDevTypes[dev->dev_type]);
	    sdM = getCurDevMult();
	    spcWriteParams(dev, hierName, scale, l, w, sdM, subdone);
	    break;

	case DEV_NDIODE:

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

	    if (dev->dev_nterm > 1)
		spcdevOutNode(hierName, source->dterm_node->efnode_name->efnn_hier,
			name, esSpiceF);
	    else if (subnode)
		spcdevOutNode(hierName, subnode->efnode_name->efnn_hier,
			name, esSpiceF);
	    spcdevOutNode(hierName, gate->dterm_node->efnode_name->efnn_hier,
			name, esSpiceF);

	    if (esFormat == CDL)
		subdone = spcWriteSubParam(dev, hierName);
	    fprintf(esSpiceF, " %s", EFDevTypes[dev->dev_type]);
	    sdM = getCurDevMult();
	    spcWriteParams(dev, hierName, scale, l, w, sdM, subdone);
	    break;

	case DEV_CAP:

	    /* Capacitor is "Cnnn top bottom value"	*/
	    /* extraction sets top=gate bottom=source	*/
	    /* extracted units are 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, " ");
		esSIvalue(esSpiceF, 1.0E-15 * (double)sdM * (double)dev->dev_cap);
		spcWriteParams(dev, hierName, scale, l, w, sdM, FALSE);
	    }
	    else
	    {
		spcWriteValue(dev, hierName);
		if (esFormat == CDL)
		    subdone = spcWriteSubParam(dev, hierName);
		fprintf(esSpiceF, " %s", EFDevTypes[dev->dev_type]);

		if (esScale < 0)
		    fprintf(esSpiceF, " w=%g l=%g", w*scale, l*scale);
		else
		{
		    fprintf(esSpiceF, " w=");
	    	    esSIvalue(esSpiceF, 1.0E-6 * w * scale * esScale);
		    fprintf(esSpiceF, " l=");
	    	    esSIvalue(esSpiceF, 1.0E-6 * l * scale * esScale);
		}

		spcWriteParams(dev, hierName, scale, l, w, sdM, subdone);
		if (sdM != 1.0)
		    fprintf(esSpiceF, " M=%g", sdM);
	    }
	    break;

	case DEV_CAPREV:

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

	    spcdevOutNode(hierName, source->dterm_node->efnode_name->efnn_hier,
			name, esSpiceF);
	    spcdevOutNode(hierName, gate->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, " ");
		esSIvalue(esSpiceF, 1.0E-15 * (double)sdM * (double)dev->dev_cap);
		spcWriteParams(dev, hierName, scale, l, w, sdM, FALSE);
	    }
	    else
	    {
		spcWriteValue(dev, hierName);
		if (esFormat == CDL)
		    subdone = spcWriteSubParam(dev, hierName);
		fprintf(esSpiceF, " %s", EFDevTypes[dev->dev_type]);

		if (esScale < 0)
		    fprintf(esSpiceF, " w=%g l=%g", w*scale, l*scale);
		else
		{
		    fprintf(esSpiceF, " w=");
	    	    esSIvalue(esSpiceF, 1.0E-6 * w * scale * esScale);
		    fprintf(esSpiceF, " l=");
	    	    esSIvalue(esSpiceF, 1.0E-6 * l * scale * esScale);
		}

		spcWriteParams(dev, hierName, scale, l, w, sdM, subdone);
		if (sdM != 1.0)
		    fprintf(esSpiceF, " M=%g", sdM);
	    }
	    break;

	case DEV_FET:
	case DEV_MOSFET:
	case DEV_ASYMMETRIC:

	    /* 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.
	     */

	    sdM = getCurDevMult();

	    if (esScale < 0)
		fprintf(esSpiceF, " w=%g l=%g", w*scale, l*scale);
	    else
	    {
		fprintf(esSpiceF, " w=");
	    	esSIvalue(esSpiceF, 1.0E-6 * w * scale * esScale);
		fprintf(esSpiceF, " l=");
	    	esSIvalue(esSpiceF, 1.0E-6 * l * scale * esScale);
	    }

	    spcWriteParams(dev, hierName, scale, l, w, sdM, FALSE);
	    if (sdM != 1.0)
		fprintf(esSpiceF, " M=%g", sdM);

	    /*
	     * 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, esFetInfo[dev->dev_type].resClassDrain,
			scale, "ad", "pd", sdM, esSpiceF);
	    else
	    {
		dnode = SpiceGetNode(hierName, drain->dterm_node->efnode_name->efnn_hier);
        	spcnAP(drain, dnode, esFetInfo[dev->dev_type].resClassDrain, scale,
			"ad", "pd", sdM, esSpiceF, w);
	    }
	    if (hierS)
		spcnAPHier(source, hierName, esFetInfo[dev->dev_type].resClassSource,
			scale, "as", "ps", sdM, esSpiceF);
	    else {
		snode= SpiceGetNode(hierName, source->dterm_node->efnode_name->efnn_hier);
		spcnAP(source, snode, esFetInfo[dev->dev_type].resClassSource, scale,
			"as", "ps", sdM, esSpiceF, w);
	    }
	    if (subAP)
	    {
		fprintf(esSpiceF, " * ");
		if (esFetInfo[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(NULL, subnodeFlat, esFetInfo[dev->dev_type].resClassSub,
				scale, "asub", "psub", sdM, esSpiceF, -1);
		else
		    fprintf(esSpiceF, "asub=0 psub=0");
	    }

	    /* Now output attributes, if present */
	    if (!esNoAttrs)
	    {
		bool haveSattr = FALSE;
		bool haveDattr = FALSE;

		if (source->dterm_attrs && (*source->dterm_attrs))
		    haveSattr = TRUE;
		if (drain->dterm_attrs && (*drain->dterm_attrs))
		    haveDattr = TRUE;

		if (gate->dterm_attrs || haveSattr || haveDattr)
		    fprintf(esSpiceF,"\n**devattr");
		if (gate->dterm_attrs)
		    fprintf(esSpiceF, " g=%s", gate->dterm_attrs);
		if (haveSattr)
		    fprintf(esSpiceF, " s=%s", source->dterm_attrs);
		if (haveDattr)
		    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(
    HierName *prefix,
    HierName *suffix,
    int type,
    FILE *outf)
{
    HashEntry *he;
    EFNodeName *nn;
    char *suf;

    suf = EFHNToStr(suffix);
    if (esFetInfo[type].defSubs && strcasecmp(suf,esFetInfo[type].defSubs) == 0) {
	esFormatSubs(outf, 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)
	{
	    const char *spicename;
	    spicename = nodeSpiceName(nn->efnn_node->efnode_name->efnn_hier, NULL);
	    fprintf(outf, "%s", spicename);
	}

	/* Create node client if it doesn't exist */
	if ((nodeClient *)nn->efnn_node->efnode_client == (nodeClient *)NULL)
	    initNodeClient(nn->efnn_node);

	/* Mark node as visited (set bit one higher than number of resist classes) */
	if (esDistrJunct)
	    update_w(efNumResistClasses, 1, nn->efnn_node);
	else
	    markVisited((nodeClientHier *)nn->efnn_node->efnode_client, 
		    efNumResistClasses);
        return nn->efnn_node;
   }
}

/*
 * ----------------------------------------------------------------------------
 * 
 * nDecimals() --- 
 *
 * Code taken from discussion at
 * https://stackoverflow.com/questions/277772/avoid-trailing-zeroes-in-printf
 *
 * Modifies value "d" by truncating it to precision "n", writing the result
 * into string "s".
 *
 * ----------------------------------------------------------------------------
 */

void
nDecimals(
    char *s,
    double d,
    int n)
{
    int sz;
    double d2;

    // Allow for negative.
    d2 = (d >= 0) ? d : -d;
    sz = (d >= 0) ? 0 : 1;

    // Add one for each whole digit (0.xx special case).
    if (d2 < 1) sz++;
    while (d2 >= 1)
    {
	d2 /= 10.0;
	sz++;
    }

    // Adjust for decimal point and fractionals.
    sz += 1 + n;

    // Create formatted string then use it.
    sprintf(s, "%*.*f", sz, n, d);
}

/*
 * ----------------------------------------------------------------------------
 * 
 * morphNumericString() ---
 *
 * Code taken from discussion at
 * https://stackoverflow.com/questions/277772/avoid-trailing-zeroes-in-printf
 *
 * Remove trailing zeros from the number represented in string "s".
 * Assume a precision (defined as precision after the decimal point) of "n".
 *
 * ----------------------------------------------------------------------------
 */

void
morphNumericString(
    char *s,
    int n)
{
    char *p;
    int count;

    p = strchr (s, '.');	// Find decimal point, if any.
    if (p != NULL)
    {
	count = n;		// Adjust for more or less decimals.
	while (count >= 0)	// Maximum decimals allowed.
	{
	    count--;
	    if (*p == '\0')	// If there's less than desired.
		break;
	    p++;		// Next character.
	}
	*p-- = '\0';		// Truncate string.
	while (*p == '0')	// Remove trailing zeros.
	    *p-- = '\0';

	if (*p == '.')		// If all decimals were zeros, remove ".".
	    *p = '\0';
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * esSIvalue --
 *
 *	Print an output in appropriate SI units used by SPICE.  e.g., 1.0e-6
 *	will be printed as "1.0u";  1.0e-12 will be printed as "1.0p", etc.
 *
 * Return value:
 *	None.
 *
 * Side effects:
 *	Generates output to stream "file".
 *
 * ----------------------------------------------------------------------------
 */

void
esSIvalue(
    FILE *file,
    float value)
{
    char vstr[32];
    char suffix = '\0';
    float avalue;

    avalue = fabsf(value);

    if (avalue < 1.0E-18)
    {
	/* Do nothing---value is probably zero */
    }
    else if (avalue < 0.9999E-13)
    {
	/* NOTE:  ngspice does not support "a" for "atto" */
	suffix = 'f';
	value *= 1.0E15;
    }
    else if (avalue < 1.0001E-10)
    {
	suffix = 'p';
	value *= 1.0E12;
    }
    else if (avalue < 1.0001E-7)
    {
	suffix = 'n';
	value *= 1.0E9;
    }
    else if (avalue < 1.0001E-4)
    {
	suffix = 'u';
	value *= 1.0E6;
    }
    else if (avalue < 1.0001E-2)
    {
	suffix = 'm';
	value *= 1.0E3;
    }
    else if (avalue > 0.9999E9)
    {
	suffix = 'G';
	value /= 1.0E9;
    }
    else if (avalue > 0.9999E3)
    {
	suffix = 'k';
	value /= 1.0E3;
    }

    /* Note that "%g" is preferred because it produces more readable
     * output.  However, it changes the definition of the precision
     * from significant digits after the radix to total significant
     * digits.  Using a solution provided in StackOverflow (see above).
     */

    nDecimals(vstr, (double)value, 5);
    morphNumericString(vstr, 5);

    if (suffix == '\0')
	fprintf(file, "%s", vstr);
    else
	fprintf(file, "%s%c", vstr, suffix);
}

/*
 * ----------------------------------------------------------------------------
 *
 * 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.
 *
 * Return:
 *	0 on success, 1 on error
 *
 * Side effects:
 *     Set the visited flags so that the node A/P will not be output multiple
 *     times
 *
 * ----------------------------------------------------------------------------
 */
int
spcnAP(
    DevTerm *dterm,
    EFNode *node,
    int resClass,
    float scale,
    char *asterm,
    char *psterm,
    float m,
    FILE *outf,
    int w)
{
    char afmt[15], pfmt[15];
    float dsc;
    int area, perim;
    char *cptr;
    bool haveAttrs = FALSE;

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

    if (asterm) snprintf(afmt, sizeof afmt, " %s=", asterm);
    if (psterm) snprintf(pfmt, sizeof pfmt, " %s=", psterm);

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

    if (((nodeClient*)node->efnode_client)->m_w.widths != NULL)
	dsc = w / ((nodeClient*)node->efnode_client)->m_w.widths[resClass];
    else
    {
	TxError("Device missing records for source/drain area/perim.\n");
	dsc = w;
    }

    if (esScale < 0)
    {
	if (asterm)
	{
	    fputs(afmt, outf);
	    esSIvalue(outf, 1.0E-12 * node->efnode_pa[resClass].pa_area
			* scale * scale * dsc);
	}
	if (psterm)
	{
	    fputs(pfmt, outf);
	    esSIvalue(outf, 1.0E-6 * node->efnode_pa[resClass].pa_perim * scale * dsc);
	}
    }
    else
    {
	if (asterm)
	{
	    fputs(afmt, outf);
	    esSIvalue(outf, 1.0E-12 * ((float)node->efnode_pa[resClass].pa_area
			* scale * scale) * esScale * esScale * dsc);
	}
	if (psterm)
	{
	    fputs(pfmt, outf);
	    esSIvalue(outf, 1.0E-6 * ((float)node->efnode_pa[resClass].pa_perim
			* scale) * esScale * dsc);
	}
    }

    return 0;

newFmt:
    /* New format introduced 2/15/2023:  Area and perimeter of each terminal
     * are maintained in the terminal attributes for "fet" or "device mosfet"
     * type devices (otherwise, for subcircuit device types, this routine is
     * not used and the same values are found in the device parameters).
     *
     * Values are the last two values in a comma-separated list in
     * dterm_attrs.  If not found, then default to the 
     */

    cptr = (dterm) ? dterm->dterm_attrs : NULL;
    while (cptr)
    {
	if (*cptr == ',') cptr++;
	if (sscanf(cptr, "%d,%d", &area, &perim) != 2)
	    cptr = strchr(cptr, ',');
	else
	{
	    haveAttrs = TRUE;
	    *cptr = '\0';
	    break;
	}
    }

    if (!haveAttrs)
    {
	area = node->efnode_pa[resClass].pa_area;
	perim = node->efnode_pa[resClass].pa_perim;

	if (resClass == NO_RESCLASS ||
                beenVisited((nodeClient *)node->efnode_client, resClass))
	    scale = 0;
	else
	    markVisited((nodeClient *)node->efnode_client, resClass);
    }

    if (esScale < 0)
    {
	if (asterm)
	{
	    fputs(afmt, outf);
	    esSIvalue(outf, 1.0E-12 * area * scale * scale / m);
	}
	if (psterm)
	{
	    fputs(pfmt, outf);
	    esSIvalue(outf, 1.0E-6 * perim * scale / m);
	}
    }
    else
    {
	if (asterm)
	{
	    fputs(afmt, outf);
	    esSIvalue(outf, 1.0E-12 * ((float)area * scale * scale) * esScale * esScale);
	}
	if (psterm)
	{
	    fputs(pfmt, outf);
	    esSIvalue(outf, 1.0E-6 * ((float)perim * scale) * esScale);
	}
    }
    return 0;
}

int
spcnAPHier(
    DevTerm *dterm,
    HierName *hierName,
    int resClass,
    float scale,
    char *asterm,
    char *psterm,
    float m,
    FILE *outf)
{
    EFNode *node = dterm->dterm_node;
    nodeClientHier   *nc;
    char afmt[15], pfmt[15];
    int area, perim;
    char *cptr;
    bool haveAttrs = FALSE;

    sprintf(afmt," %s=", asterm);
    sprintf(pfmt," %s=", psterm);

    if (node->efnode_client == (ClientData) NULL)
	initNodeClientHier(node);

    nc = (nodeClientHier *)node->efnode_client;
    if (nc->lastPrefix != hierName)
    {
	clearVisited(nc);
	nc->lastPrefix = hierName;
    }

    /* Check for area and perim values in dterm_attrs */

    cptr = dterm->dterm_attrs;
    while (cptr)
    {
	if (*cptr == ',') cptr++;
	if (sscanf(cptr, "%d,%d", &area, &perim) != 2)
	    cptr = strchr(cptr, ',');
	else
	{
	    haveAttrs = TRUE;
	    *cptr = '\0';
	    break;
	}
    }

    if (!haveAttrs)
    {
	area = node->efnode_pa[resClass].pa_area;
	perim = node->efnode_pa[resClass].pa_perim;

	if (resClass == NO_RESCLASS ||
                beenVisited((nodeClient *)node->efnode_client, resClass))
	    scale = 0;
	else
	    markVisited((nodeClient *)node->efnode_client, resClass);
    }

    if (esScale < 0)
    {
	fputs(afmt, outf);
	esSIvalue(outf, 1.0E-12 * area * scale * scale / m);
	fputs(pfmt, outf);
	esSIvalue(outf, 1.0E-6 * perim * scale / m);
    }
    else
    {
	fputs(afmt, outf);
	esSIvalue(outf, 1.0E-12 * ((float)area * scale) * esScale * esScale);
	fputs(pfmt, outf);
	esSIvalue(outf, 1.0E-6 * ((float)perim * scale) * esScale);
    }
    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * spcdevResPi --
 *
 * Handle a "pi" resistor model:  Take the capacitance of the "gate" 
 * (resistor device layer) to substrate, and split it between the two
 * resistor terminals.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Modifies the capacitance values on the terminal and "gate" nodes
 *	of the resistor device.
 *
 * ----------------------------------------------------------------------------
 */

void
spcdevResPi(
    const HierName *prefix,
    const HierName *gate_suffix,
    const HierName *source_suffix,
    const HierName *drain_suffix,
    const char *name)
{
    HashEntry *he;
    EFNodeName *nngate, *nnsource, *nndrain;
    EFCapValue rescap;

    he = EFHNConcatLook(prefix, gate_suffix, name);
    if (he == NULL) return;
    nngate = (EFNodeName *) HashGetValue(he);

    he = EFHNConcatLook(prefix, drain_suffix, name);
    if (he == NULL) return;
    nndrain = (EFNodeName *) HashGetValue(he);

    he = EFHNConcatLook(prefix, source_suffix, name);
    if (he == NULL) return;
    nnsource = (EFNodeName *) HashGetValue(he);

    rescap = nngate->efnn_node->efnode_cap / 2.0;
    nnsource->efnn_node->efnode_cap += rescap;
    nndrain->efnn_node->efnode_cap += rescap;
    nngate->efnn_node->efnode_cap = 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * 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:
 *	Return number of characters printed on success, 0 on error.
 *
 * Side effects:
 *	Writes to the file 'outf'.
 *	Sets the efnode_client field as described above.
 *
 * ----------------------------------------------------------------------------
 */

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

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

    /* Mark node as visited (set bit one higher than number of resist classes) */
    if (esDistrJunct)
	update_w(efNumResistClasses, 1, nn->efnn_node);
    else
	markVisited((nodeClientHier *)nn->efnn_node->efnode_client,
		efNumResistClasses);

    return (1 + strlen(nname));
}


/*
 * ----------------------------------------------------------------------------
 *
 * 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).
 *
 * ----------------------------------------------------------------------------
 */

/* @typedef cb_extflat_visitcaps_t (UNUSED) */
int
spccapVisit(
    HierName *hierName1,
    HierName *hierName2,
    double cap,
    ClientData cdata)	/* unused */
{
    cap = cap / 1000;
    if (cap <= EFCapThreshold)
	return 0;

    fprintf(esSpiceF, "C%d %s %s ", esCapNum++, nodeSpiceName(hierName1, NULL),
                                          nodeSpiceName(hierName2, NULL));
    esSIvalue(esSpiceF, 1.0E-15 * cap);
    fprintf(esSpiceF, "\n");
    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)
 *
 *
 * ----------------------------------------------------------------------------
 */

/* ARGSUSED */
/* @typedef cb_extflat_visitresists_t (UNUSED) */
int
spcresistVisit(
    const HierName *hierName1,
    const HierName *hierName2,
    float res,
    ClientData cdata)	/* unused */
{
    HashEntry *he;
    EFNodeName *nn;

    fprintf(esSpiceF, "R%d %s %s %g\n", esResNum++, nodeSpiceName(hierName1, NULL),
			nodeSpiceName(hierName2, NULL), res / 1000.);

    /* Mark nodes as visited so that associated capacitances won't be marked
     * as "floating".  This is inefficient since nodeSpiceName() already does
     * a hash lookup of the EFNodeName.  Could be improved, but is not a big
     * performance issue.
     */
    he = EFHNLook(hierName1, (char *)NULL, "nodeName");
    if (he != NULL)
    {
	nn = (EFNodeName *) HashGetValue(he);

	/* Mark node as visited (set bit one higher than number of resist classes) */
	if (esDistrJunct)
	    update_w(efNumResistClasses, 1, nn->efnn_node);
	else
	    markVisited((nodeClientHier *)nn->efnn_node->efnode_client, 
			efNumResistClasses);
    }

    he = EFHNLook(hierName2, (char *)NULL, "nodeName");
    if (he != NULL)
    {
	nn = (EFNodeName *) HashGetValue(he);

	/* Mark node as visited (set bit one higher than number of resist classes) */
	if (esDistrJunct)
	    update_w(efNumResistClasses, 1, nn->efnn_node);
	else
	    markVisited((nodeClientHier *)nn->efnn_node->efnode_client, 
			efNumResistClasses);
    }

    return 0;
}


/*
 * ----------------------------------------------------------------------------
 *
 * spcsubVisit --
 *
 *	Routine to find the node that connects to substrate.  Copy the
 *	string name of this node into "resstr" to be returned to the
 *	caller.
 *
 * Results:
 *	Return 1 if the substrate node has been found, to stop the search.
 *	Otherwise return 0 to keep the search going.
 *
 * ----------------------------------------------------------------------------
 */

int
spcsubVisit(
    EFNode *node,
    int res, 		// Unused
    double cap,		// Unused
    ClientData cdata)
{
    char **resstr = (char **)CD2PTR(cdata);
    HierName *hierName;
    const char *nsn;

    if (node->efnode_flags & EF_GLOB_SUBS_NODE)
    {
	hierName = (HierName *) node->efnode_name->efnn_hier;
	nsn = nodeSpiceName(hierName, NULL);
	*resstr = StrDup((char **)NULL, nsn);
	return 1;
    }
    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
 *
 * ----------------------------------------------------------------------------
 */

/* @typedef cb_extflat_visitnodes_t (UNUSED) */
int
spcnodeVisit(
    EFNode *node,
    int res,
    double cap,
    ClientData cdata) /* unused */
{
    HierName *hierName;
    bool isConnected = FALSE;
    const char *fmt;
    const char *nsn;
    EFAttr *ap;

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

    /* Don't mark known ports as "FLOATING" nodes */
    if (!isConnected && node->efnode_flags & EF_PORT) isConnected = TRUE;

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

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

	EFHNSprintf(ntmp, hierName);
	if (esFormat == NGSPICE) fprintf(esSpiceF, "; ");
	fprintf(esSpiceF, "** %s == %s\n", ntmp, nsn);
    }
    cap = cap  / 1000;
    if (cap > EFCapThreshold)
    {
	fprintf(esSpiceF, "C%d %s %s ", esCapNum++, nsn, esSpiceCapNode);
	esSIvalue(esSpiceF, 1.0E-15 * cap);
	if (!isConnected)
	{
	    if (esFormat == NGSPICE) fprintf(esSpiceF, " ;");
	    fprintf(esSpiceF, " **FLOATING");
	}
	fprintf(esSpiceF, "\n");
    }
    if (node->efnode_attrs && !esNoAttrs)
    {
	if (esFormat == NGSPICE) fprintf(esSpiceF, " ; ");
	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 */
/* @typedef cb_extflat_visitnodes_t (UNUSED) */
int
nodeVisitDebug(
    EFNode *node,
    int res,
    double cap,
    ClientData cdata)	/* unused */
{
    HierName *hierName;
    const char *nsn;

    hierName = (HierName *) node->efnode_name->efnn_hier;
    nsn = nodeSpiceName(hierName, NULL);
    TxError("** %s (%lx)\n", nsn, (intmax_t) node);

    printf("\t client.name=%s, client.m_w=%p\n",
    ((nodeClient *)node->efnode_client)->spiceNodeName,
    (void *)((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.
 *	Returns the node in the "rnode" pointer, if non-NULL.
 *
 * ----------------------------------------------------------------------------
 */
static char esTempName[MAX_STR_SIZE];

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

    if (rnode) *rnode = (EFNode *)NULL;
    he = EFHNLook(hname, (char *) NULL, "nodeName");
    if ( he == NULL )
	return "errGnd!";
    nn = (EFNodeName *) HashGetValue(he);
    node = nn->efnn_node;
    if (rnode) *rnode = 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 EFOutputFlags 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(
    char *str,
    HierName *hierName)
{
    bool trimGlob, trimLocal, convertComma, convertEqual, convertBrackets;
    char *s, *cp, c;
    char *efHNSprintfPrefix(HierName *, char *);

    s = str;
    if (hierName->hn_parent) str = efHNSprintfPrefix(hierName->hn_parent, str);
    if (EFOutputFlags & EF_TRIM_MASK)
    {
	cp = hierName->hn_name;
	trimGlob = (EFOutputFlags & EF_TRIMGLOB);
	trimLocal = (EFOutputFlags & EF_TRIMLOCAL);
	convertComma = (EFOutputFlags & EF_CONVERTCOMMA);
	convertEqual = (EFOutputFlags & EF_CONVERTEQUAL);
	convertBrackets = (EFOutputFlags & EF_CONVERTBRACKETS);
	while ((c = *cp++))
	{
	    switch (c)
	    {
		case '!':	if (!trimGlob) *str++ = c; break;
		case '.':	*str++ = (esFormat == HSPICE)?'@':'.'; break;
		case '=':	if (convertEqual) *str++ = ':'; break;
		case ',':	if (convertComma) *str++ = '|'; break;
		case '[':	*str++ = (convertBrackets) ? '_' : '['; break;
		case ']':	*str++ = (convertBrackets) ? '_' : ']'; break;
		case '#':	if (trimLocal) break;	// else fall through
		default:	*str++ = c; break;
	    }
	}
	*str++ = '\0';
    }
    else strcpy(str, hierName->hn_name);
    return 0;
}

char *
efHNSprintfPrefix(
    HierName *hierName,
    char *str)
{
    char *cp, c;
    bool convertEqual = (EFOutputFlags & EF_CONVERTEQUAL) ? TRUE : FALSE;
    bool convertComma = (EFOutputFlags & EF_CONVERTCOMMA) ? TRUE : FALSE;
    bool convertBrackets = (EFOutputFlags & EF_CONVERTBRACKETS) ? TRUE : FALSE;

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

    cp = hierName->hn_name;
    while (1) {
	if (convertEqual && (*cp == '='))
	   *str = ':';
	else if (convertBrackets && ((*cp == '[') || (*cp == ']')))
	   *str = '_';
	else if (*cp == ',')
	{
	    if (convertComma) *str = '|';
	    else str--;
	}
	else
	   *str = *cp;
	if (!(*str)) break;
	str++;
	cp++;
    }
    *str = '/';
    return ++str;
}

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

int
nodeHspiceName(
    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)
    {
	strcpy(map, s);
	goto topLevel;
    }

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

    /*
     * look up prefix in the hash table and create it if doesn't 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
	}
    }
    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * 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(void)
{
    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%"DLONG_PREFIX"d\t%s\n", (dlong) HashGetValue(he), he->h_key.h_name);
    return 0;
}

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

devMerge *
mkDevMerge(
    float l,
    float w,
    EFNode *g,
    EFNode *s,
    EFNode *d,
    EFNode *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.
 *
 * ----------------------------------------------------------------------------
 */

int
parallelDevs(
    const devMerge *f1,
    const devMerge *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_MSUBCKT:
	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;

	case DEV_ASYMMETRIC:

	    if (f1->b != f2->b) return NOT_PARALLEL;
	    if ((f1->g == f2->g) && (f1->d == f2->d) && (f1->s == f2->s)
			&& (f1->l == f2->l) && (esMergeDevsA || (f1->w == f2->w)))
	    {
		    return PARALLEL;
	    }
	    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:
	case DEV_CAPREV:
	    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_cap == f2->dev->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:
	case DEV_NDIODE:
	case DEV_PDIODE:
	    break;

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

	case DEV_SUBCKT:
	case DEV_VERILOGA:
	case DEV_RSUBCKT:
	case DEV_CSUBCKT:
	case DEV_DSUBCKT:
	    break;

	case DEV_VOLT:
	    break;
    }
    return NOT_PARALLEL;
}

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

void
mergeAttr(
    char **a1,
    char **a2)
{
    if (*a1 == NULL)
	*a1 = *a2; /* TODO check strdup() is appropiate due to lifetime ambiguity this creates */
    else
    {
	size_t l1 = strlen(*a1);
	size_t l2 = strlen(*a2);
	char *t = (char *) mallocMagic(l1 + l2 + 1);
	strcpy(t, *a1); /* strcpy_advance() */
	strcat(t, *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.
 *
 * ----------------------------------------------------------------------------
 */

/* ARGSUSED */
/* @typedef cb_extflat_visitdevs_t (UNUSED) */
int
devMergeVisit(
    Dev *dev,			/* Dev to examine */
    HierContext *hc,		/* Hierarchical context down to this dev */
    float scale,		/* Scale transform */
    Transform *trans,		/* (unused) */
    ClientData cdata)		/* (unused) */
{
    DevTerm *gate, *source, *drain;
    EFNode *subnode, *snode, *dnode, *gnode;
    int      pmode, l, w;
    bool     hS, hD, chS, chD;
    devMerge *fp;
    const devMerge *cfp;
    float m;
    HierName *hierName = hc->hc_hierName;

    if (esDistrJunct)
	devDistJunctVisit(dev, hc, scale, trans, cdata);

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

    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];

    gnode = SpiceGetNode(hierName, gate->dterm_node->efnode_name->efnn_hier);
    if (dev->dev_nterm >= 2)
    {
	snode = SpiceGetNode(hierName, source->dterm_node->efnode_name->efnn_hier);
	dnode = SpiceGetNode(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;

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

    fp = mkDevMerge((float)((float)l * scale), (float)((float)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)
	{
#if 0 /* -Wunused-but-set-variable */
	    Dev *cf = cfp->dev;
	    DevTerm *cg = &cfp->dev->dev_terms[0];
#endif
	    DevTerm *cs = &cfp->dev->dev_terms[1];
	    DevTerm *cd = cs;
	    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:
	    /* Default case is to add counts */
	    m = esFMult[cfp->esFMIndex] + esFMult[fp->esFMIndex];

	    switch(dev->dev_class)
	    {
		case DEV_MSUBCKT:
		case DEV_MOSFET:
		case DEV_ASYMMETRIC:
		case DEV_FET:
		    if (cfp->w > 0)
			m = esFMult[cfp->esFMIndex] + (fp->w / cfp->w);
		    break;
		case DEV_RSUBCKT:
		case DEV_RES:
		    if (fp->dev->dev_type == esNoModelType)
		    {
			if (cfp->dev->dev_res > 0)
			    m = esFMult[cfp->esFMIndex] + (fp->dev->dev_res
					/ cfp->dev->dev_res);
		    }
		    else
		    {
			if (cfp->l > 0)
			    m = esFMult[cfp->esFMIndex] + (fp->l / cfp->l);
		    }
		    break;
		case DEV_CSUBCKT:
		case DEV_CAP:
		case DEV_CAPREV:
		    if (fp->dev->dev_type == esNoModelType)
		    {
			if (cfp->dev->dev_cap > 0)
			    m = esFMult[cfp->esFMIndex] + (fp->dev->dev_cap
					/ cfp->dev->dev_cap);
		    }
		    else
		    {
			if ((cfp->l > 0) && (cfp->w > 0))
			    m = esFMult[cfp->esFMIndex] +
					((fp->l  * fp->w) / (cfp->l * cfp->w));
		    }
		    break;
	    }
	    setDevMult(fp->esFMIndex, DEV_KILLED);
	    setDevMult(cfp->esFMIndex, m);
	    esSpiceDevsMerged++;
	    /* 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(
    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 + 1));
	for (i = 0; i <= efNumResistClasses; i++)
	    nc->m_w.widths[i] = 0.0;
    }
    nc->m_w.widths[resClass] += (float)w;
    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * 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
 *
 * ----------------------------------------------------------------------------
 */

/* ARGSUSED */
/* @typedef cb_extflat_visitdevs_t (UNUSED) */
int
devDistJunctVisit(
    Dev *dev,			/* Dev to examine */
    HierContext *hc,		/* Hierarchical path down to this dev */
    float scale,		/* Scale transform */
    Transform *trans,		/* (unused) */
    ClientData cdata)		/* (unused) */
{
    EFNode  *n;
    int      i;
    int l, w;
    HierName *hierName = hc->hc_hierName;

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

    EFGetLengthAndWidth(dev, &l, &w);
    w = (int)((float)w * scale);

    for (i = 1; i<dev->dev_nterm; i++)
    {
	n = SpiceGetNode(hierName,
		dev->dev_terms[i].dterm_node->efnode_name->efnn_hier);
	if (i == 1)
	    update_w(esFetInfo[dev->dev_type].resClassSource, w, n);
	else
	    update_w(esFetInfo[dev->dev_type].resClassDrain, w, n);
    }
    return 0;
}

