magic/ext2spice/ext2hier.c

2401 lines
65 KiB
C

/*
* ext2hier.c --
*
* Program to convert hierarchical .ext files into a single
* hierarchical .spice file, suitable for use as input to a
* hierarchy-capable LVS (layout vs. schematic) tool such as
* netgen.
*
* Generates 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/ext2hier.c,v 1.5 2010/12/16 18:59:03 tim Exp $";
#endif /* not lint */
#include <stdio.h>
#include <stdlib.h> /* for atof() */
#include <string.h>
#include <ctype.h>
#include <math.h> /* for fabs() */
#ifdef MAGIC_WRAPPER
#include "tcltk/tclmagic.h"
#endif
#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 "extflat/extflat.h"
#include "extflat/EFint.h"
#include "extract/extract.h" /* for extDevTable */
#include "utils/runstats.h"
#include "ext2spice/ext2spice.h"
/* C99 compat */
#include "extflat/extflat.h"
/* These global values are defined in ext2spice.c */
extern HashTable subcktNameTable;
extern DQueue subcktNameQueue;
// Structure passed to esHierVisit
typedef struct _defflagsdata {
Def *def;
int flags;
} DefFlagsData;
/*
* ----------------------------------------------------------------------------
*
* ESGenerateHierarchy ---
*
* Generate hierarchical SPICE output
*
* ----------------------------------------------------------------------------
*/
void
ESGenerateHierarchy(
char *inName,
int flags)
{
int esHierVisit(HierContext *hc, ClientData cdata); /* (DefFlagsData *) */
int esMakePorts(HierContext *hc, ClientData cdata); /* Forward declaration (UNUSED) */
Use u;
HierContext hc;
DefFlagsData dfd;
u.use_def = efDefLook(inName);
hc.hc_use = &u;
hc.hc_hierName = NULL;
hc.hc_trans = GeoIdentityTransform;
hc.hc_x = hc.hc_y = 0;
EFHierSrDefs(&hc, esMakePorts, NULL);
EFHierSrDefs(&hc, NULL, NULL); /* Clear processed */
dfd.def = u.use_def;
dfd.flags = flags;
EFHierSrDefs(&hc, esHierVisit, (ClientData)(&dfd));
EFHierSrDefs(&hc, NULL, NULL); /* Clear processed */
return;
}
/*
* ----------------------------------------------------------------------------
*
* GetHierNode --
*
* function to find a node structure given its name
*
* Results:
* a pointer to the node struct or NULL
*
* ----------------------------------------------------------------------------
*/
EFNode *
GetHierNode(
HierContext *hc,
HierName *name)
{
HashEntry *he;
EFNodeName *nn;
he = EFHNConcatLook(hc->hc_hierName, name, "node");
if (he == NULL) return NULL;
nn = (EFNodeName *) HashGetValue(he);
if (nn == NULL) return NULL;
return(nn->efnn_node);
}
/*
* ----------------------------------------------------------------------------
*
* spcHierWriteValue ---
*
* Special case of spcHierWriteParams() below. Only check for 'r' or 'c'
* parameters which have no parameter name. Write out the given value,
* which in the case of CDL format is written out after the pins but before
* everything else, arbitrarily.
*
* ----------------------------------------------------------------------------
*/
void
spcHierWriteValue(
HierContext *hc,
Dev *dev) /* Dev being output */
{
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_cap));
}
break;
}
plist = plist->parm_next;
}
}
/*
* ----------------------------------------------------------------------------
*
* spcHierWriteSubParam ---
*
* Special case of spcHierWriteParams() below. Only check for the substrate
* parameter, which in the case of CDL format is written out before the
* device model, for whatever reason. Return TRUE if the substrate parameter
* was output; otherwise return FALSE.
*
* ----------------------------------------------------------------------------
*/
bool
spcHierWriteSubParam(
HierContext *hc,
Dev *dev) /* Dev being output */
{
DevParam *plist;
bool retvalue = FALSE;
plist = efGetDeviceParams(EFDevTypes[dev->dev_type]);
while (plist != NULL)
{
switch (plist->parm_type[0])
{
case 's':
if (dev->dev_subsnode == NULL)
TxError("Error: Device %s missing substrate node!\n",
EFDevTypes[dev->dev_type]);
else
{
fprintf(esSpiceF, " %s=", plist->parm_name);
spcdevSubstrate(hc->hc_hierName,
dev->dev_subsnode->efnode_name->efnn_hier,
dev->dev_type, esSpiceF);
retvalue = TRUE;
}
break;
}
plist = plist->parm_next;
}
return retvalue;
}
/*
* ----------------------------------------------------------------------------
*
* spcHierWriteParams ---
*
* 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
spcHierWriteParams(
HierContext *hc,
Dev *dev, /* Dev being output */
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, then substrate parameter was already output */
{
DevParam *plist, *dparam;
int parmval;
EFNode *dnode;
/* Write all requested parameters to the subcircuit call. */
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", (double)parmval * scale * scale);
else if (plist->parm_scale != 1.0)
fprintf(esSpiceF, "%g", (double)parmval * scale * scale
* esScale * esScale * plist->parm_scale
* 1E-12);
else
esSIvalue(esSpiceF, 1.0E-12 * ((double)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;
resclass = (pn > 1) ? esFetInfo[dev->dev_type].resClassDrain :
esFetInfo[dev->dev_type].resClassSource;
dnode = GetHierNode(hc,
dev->dev_terms[pn].dterm_node->efnode_name->efnn_hier);
// For parameter an followed by parameter pn,
// 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])
{
spcnAP(&dev->dev_terms[pn], dnode, resclass, scale,
plist->parm_name, plist->parm_next->parm_name,
sdM, esSpiceF, w);
plist = plist->parm_next;
}
else
{
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 * (double)scale
* (double)esScale * (double)plist->parm_scale * 1E-6);
else
esSIvalue(esSpiceF, ((double)parmval + (double)plist->parm_offset)
* (double)scale * (double)esScale * 1.0E-6);
}
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;
dnode = GetHierNode(hc,
dev->dev_terms[pn].dterm_node->efnode_name->efnn_hier);
// For parameter pn followed by parameter an,
// 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])
{
spcnAP(&dev->dev_terms[pn], dnode, resclass, scale,
plist->parm_next->parm_name,
plist->parm_name, sdM, esSpiceF, w);
plist = plist->parm_next;
}
else
{
spcnAP(&dev->dev_terms[pn], dnode, resclass, scale, NULL,
plist->parm_name, sdM, esSpiceF, w);
}
}
break;
case 'l':
// Check for device length vs. terminal length
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 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 device width vs. terminal width
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 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: Device %s missing substrate node!\n",
EFDevTypes[dev->dev_type]);
else
{
fprintf(esSpiceF, " %s=", plist->parm_name);
spcdevSubstrate(hc->hc_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 * (double)scale
* (double)esScale * (double)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 * (double)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 (dparam = dev->dev_params; dparam; dparam = dparam->parm_next)
if (dparam->parm_name[0] != '\0')
fprintf(esSpiceF, " %s", dparam->parm_name);
}
/*
* ----------------------------------------------------------------------------
*
* esOutputHierResistor ---
*
* Routine used by spcdevHierVisit 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
esOutputHierResistor(
HierContext *hc,
Dev *dev, /* Dev being output */
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 */
if ((term1->dterm_node == NULL) || (term2->dterm_node == NULL))
{
TxError("Error: Resistor %s missing terminal node!\n",
EFDevTypes[dev->dev_type]);
return;
}
spcdevOutNode(hc->hc_hierName, term1->dterm_node->efnode_name->efnn_hier,
"res_top", esSpiceF);
spcdevOutNode(hc->hc_hierName, term2->dterm_node->efnode_name->efnn_hier,
"res_bot", 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);
spcHierWriteParams(hc, dev, scale, l, w, sdM, FALSE);
}
else
{
bool subdone = FALSE;
spcHierWriteValue(hc, dev);
if (esFormat == CDL)
subdone = spcHierWriteSubParam(hc, dev);
fprintf(esSpiceF, " %s", EFDevTypes[dev->dev_type]);
if (esScale < 0)
{
fprintf(esSpiceF, " w=%d l=%d", (int)((float)w * scale),
(int)(((float)l * scale) / (float)dscale));
}
else
{
fprintf(esSpiceF, " w=");
esSIvalue(esSpiceF, 1.0E-6 * (float)w * scale * esScale);
fprintf(esSpiceF, " l=");
esSIvalue(esSpiceF, 1.0E-6 * (float)((l * scale * esScale) / dscale));
}
spcHierWriteParams(hc, dev, scale, l, w, sdM, subdone);
if (sdM != 1.0)
fprintf(esSpiceF, " M=%g", sdM);
}
}
/*
* ----------------------------------------------------------------------------
* ----------------------------------------------------------------------------
*
*/
int
subcktHierVisit(
Use *use,
HierName *hierName,
bool is_top) /* TRUE if this is the top-level cell */
{
Def *def = use->use_def;
EFNode *snode;
EFNodeName *nodeName;
bool hasports = FALSE;
bool isStub;
/* Avoid generating records for circuits that have no ports. */
/* These are already absorbed into the parent. All other */
/* subcircuits have at least one port marked by the EF_PORT flag. */
/* Do not count the substrate port, as it exists even on cells */
/* with no other ports. */
for (snode = (EFNode *) def->def_firstn.efnode_next;
snode != &def->def_firstn;
snode = (EFNode *) snode->efnode_next)
if (snode->efnode_flags & EF_PORT)
{
for (nodeName = snode->efnode_name; nodeName != NULL;
nodeName = nodeName->efnn_next)
if (nodeName->efnn_port >= 0)
{
hasports = TRUE;
break;
}
}
else if (snode->efnode_flags & EF_SUBS_PORT)
{
hasports = TRUE;
break;
}
/* Same considerations as at line 1831 for determining if the cell */
/* has been folded into the parent and should not be output. */
isStub = ((def->def_flags & (DEF_ABSTRACT | DEF_PRIMITIVE)) && esDoBlackBox) ?
TRUE : FALSE;
if ((!is_top) && (def->def_flags & DEF_NODEVICES) && (!isStub))
return 0;
if (hasports || is_top)
return subcktVisit(use, hierName, is_top);
else if (def->def_flags & DEF_NODEVICES)
return 0;
else
return subcktVisit(use, hierName, is_top);
}
/*
* ----------------------------------------------------------------------------
*
* spcdevHierVisit --
*
* Procedure to output a single dev to the .spice file.
* Called by EFHierVisitDevs().
*
* 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_hiervisitdevs_t (UNUSED) */
int
spcdevHierVisit(
HierContext *hc,
Dev *dev, /* Dev being output */
float scale, /* Scale transform for output */
ClientData cdata) /* unused */
{
DevParam *plist, *pptr;
DevTerm *gate, *source, *drain;
EFNode *subnode, *snode, *dnode, *subnodeFlat = NULL;
int l, w, i;
bool subAP = FALSE;
float sdM;
char devchar;
bool has_model = TRUE;
bool subdone = FALSE;
/* If no terminals, or only a gate, 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];
source = drain = (DevTerm *)NULL;
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);
}
}
else if (dev->dev_nterm == 1) // Is a device with one terminal an error?
source = drain = &dev->dev_terms[0];
subnode = dev->dev_subsnode;
/* 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_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)
fprintf(esSpiceF, "%s", gate->dterm_attrs);
else
{
switch (dev->dev_class)
{
case DEV_RES:
fprintf(esSpiceF, "%d", esResNum++);
/* For resistor tee networks, use, e.g., */
/* "R1A" and "R1B", for clarity */
if (esDoResistorTee) fprintf(esSpiceF, "A");
break;
case DEV_DIODE:
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_SUBCKT:
case DEV_VERILOGA:
case DEV_RSUBCKT:
case DEV_CSUBCKT:
case DEV_DSUBCKT:
case DEV_MSUBCKT:
fprintf(esSpiceF, "%d", esSbckNum++);
break;
default:
fprintf(esSpiceF, "%d", esDevNum++);
break;
}
}
/* Order and number of nodes in the output depends on the device class */
switch (dev->dev_class)
{
case DEV_BJT:
if (source == NULL) break;
/* BJT is "Qnnn collector emitter base model" */
/* extraction sets collector=subnode, emitter=gate, base=drain */
spcdevOutNode(hc->hc_hierName, subnode->efnode_name->efnn_hier,
"collector", esSpiceF);
spcdevOutNode(hc->hc_hierName, gate->dterm_node->efnode_name->efnn_hier,
"emitter", 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(hc->hc_hierName,
drain->dterm_node->efnode_name->efnn_hier,
"base", esSpiceF);
else
spcdevOutNode(hc->hc_hierName,
source->dterm_node->efnode_name->efnn_hier,
"base", esSpiceF);
if (esFormat == CDL)
subdone = spcHierWriteSubParam(hc, dev);
fprintf(esSpiceF, " %s", EFDevTypes[dev->dev_type]);
sdM = getCurDevMult();
spcHierWriteParams(hc, dev, scale, l, w, sdM, subdone);
break;
case DEV_MSUBCKT:
case DEV_DSUBCKT:
/* msubcircuit is "Xnnn drain gate [source [sub]]]" */
/* to more conveniently handle situations where MOSFETs */
/* are modeled by subcircuits with the same pin ordering. */
spcdevOutNode(hc->hc_hierName,
drain->dterm_node->efnode_name->efnn_hier,
"subckt", esSpiceF);
/* Drop through to below (no break statement) */
case DEV_SUBCKT:
case DEV_VERILOGA:
case DEV_CSUBCKT:
/* Subcircuit is "Xnnn gate [source [drain [sub]]]" */
/* Subcircuit .subckt record must be ordered to match! */
spcdevOutNode(hc->hc_hierName,
gate->dterm_node->efnode_name->efnn_hier,
"subckt", 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(hc->hc_hierName, source->dterm_node->efnode_name->efnn_hier,
"subckt", esSpiceF);
if (dev->dev_nterm > 2)
spcdevOutNode(hc->hc_hierName, drain->dterm_node->efnode_name->efnn_hier,
"subckt", esSpiceF);
}
else /* class DEV_MSUBCKT */
{
if (dev->dev_nterm > 2)
spcdevOutNode(hc->hc_hierName, source->dterm_node->efnode_name->efnn_hier,
"subckt", 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(hc->hc_hierName,
drain->dterm_node->efnode_name->efnn_hier,
"subckt", 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(hc->hc_hierName,
subnode->efnode_name->efnn_hier,
dev->dev_type, esSpiceF);
}
/* Support for CDL format */
if (esFormat == CDL) fprintf(esSpiceF, " /");
fprintf(esSpiceF, " %s", EFDevTypes[dev->dev_type]);
/* Write all requested parameters to the subcircuit call. */
sdM = getCurDevMult();
spcHierWriteParams(hc, dev, scale, l, w, sdM, FALSE);
if (sdM != 1.0)
fprintf(esSpiceF, " M=%g", sdM);
break;
case DEV_RES:
if (esDoResistorTee)
{
/* There are three ways of handling capacitance */
/* on resistor networks. One is to ignore it */
/* (the default; generates "floating" nodes in */
/* the SPICE output) which is okay for LVS. */
/* Another way is the Pi network, in which the */
/* capacitance is split evenly between the */
/* terminals. Again, the resistor node is left */
/* floating. The third is the Tee network, in */
/* which the resistance is split in two parts, */
/* connecting to a capacitor to ground in the */
/* middle. This is the best solution but plays */
/* havoc with LVS. So, the choice is a command */
/* line option. */
esOutputHierResistor(hc, dev, scale, gate, source, has_model,
l, w, 2);
fprintf(esSpiceF, "\n%c", devchar);
if (gate->dterm_attrs)
fprintf(esSpiceF, "%sB", gate->dterm_attrs);
else
fprintf(esSpiceF, "%dB", esResNum - 1);
esOutputHierResistor(hc, dev, scale, gate, drain, has_model,
l, w, 2);
}
else
{
esOutputHierResistor(hc, dev, scale, source, drain, has_model,
l, w, 1);
}
break;
case DEV_VOLT:
/* Voltage source is "Vnnn term1 term2 0.0". It is used only to
* separate port names that have been shorted.
*/
if (dev->dev_nterm > 1)
spcdevOutNode(hc->hc_hierName,
source->dterm_node->efnode_name->efnn_hier,
"plus", esSpiceF);
if (dev->dev_nterm > 2)
spcdevOutNode(hc->hc_hierName,
drain->dterm_node->efnode_name->efnn_hier,
"minus", esSpiceF);
fprintf(esSpiceF, " 0.0");
break;
case DEV_DIODE:
case DEV_PDIODE:
if (source == NULL) break;
/* Diode is "Dnnn top bottom model" */
spcdevOutNode(hc->hc_hierName,
gate->dterm_node->efnode_name->efnn_hier,
"diode_top", esSpiceF);
if (dev->dev_nterm > 1)
spcdevOutNode(hc->hc_hierName,
source->dterm_node->efnode_name->efnn_hier,
"diode_bot", esSpiceF);
else if (subnode)
spcdevOutNode(hc->hc_hierName,
subnode->efnode_name->efnn_hier,
"diode_bot", esSpiceF);
if (esFormat == CDL)
subdone = spcHierWriteSubParam(hc, dev);
fprintf(esSpiceF, " %s", EFDevTypes[dev->dev_type]);
sdM = getCurDevMult();
spcHierWriteParams(hc, dev, scale, l, w, sdM, subdone);
break;
case DEV_NDIODE:
if (source == NULL) break;
/* Diode is "Dnnn bottom top model" */
if (dev->dev_nterm > 1)
spcdevOutNode(hc->hc_hierName,
source->dterm_node->efnode_name->efnn_hier,
"diode_bot", esSpiceF);
else if (subnode)
spcdevOutNode(hc->hc_hierName,
subnode->efnode_name->efnn_hier,
"diode_bot", esSpiceF);
spcdevOutNode(hc->hc_hierName,
gate->dterm_node->efnode_name->efnn_hier,
"diode_top", esSpiceF);
if (esFormat == CDL)
subdone = spcHierWriteSubParam(hc, dev);
fprintf(esSpiceF, " %s", EFDevTypes[dev->dev_type]);
sdM = getCurDevMult();
spcHierWriteParams(hc, dev, scale, l, w, sdM, subdone);
break;
case DEV_CAP:
if (source == NULL) break;
/* Capacitor is "Cnnn top bottom value" */
/* extraction sets top=gate bottom=source */
/* extracted units are fF. */
spcdevOutNode(hc->hc_hierName,
gate->dterm_node->efnode_name->efnn_hier,
"cap_top", esSpiceF);
spcdevOutNode(hc->hc_hierName,
source->dterm_node->efnode_name->efnn_hier,
"cap_bot", 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)
{
esSIvalue(esSpiceF, 1.0E-15 * (double)sdM * (double)dev->dev_cap);
spcHierWriteParams(hc, dev, scale, l, w, sdM, FALSE);
}
else
{
spcHierWriteValue(hc, dev);
if (esFormat == CDL)
subdone = spcHierWriteSubParam(hc, dev);
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);
}
spcHierWriteParams(hc, dev, scale, l, w, sdM, subdone);
if (sdM != 1.0)
fprintf(esSpiceF, " M=%g", sdM);
}
break;
case DEV_CAPREV:
if (source == NULL) break;
/* Capacitor is "Cnnn bottom top value" */
/* extraction sets top=source bottom=gate */
/* extracted units are fF. */
spcdevOutNode(hc->hc_hierName,
gate->dterm_node->efnode_name->efnn_hier,
"cap_bot", esSpiceF);
spcdevOutNode(hc->hc_hierName,
source->dterm_node->efnode_name->efnn_hier,
"cap_top", 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)
{
esSIvalue(esSpiceF, 1.0E-15 * (double)sdM * (double)dev->dev_cap);
spcHierWriteParams(hc, dev, scale, l, w, sdM, FALSE);
}
else
{
spcHierWriteValue(hc, dev);
if (esFormat == CDL)
subdone = spcHierWriteSubParam(hc, dev);
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);
}
spcHierWriteParams(hc, dev, scale, l, w, sdM, subdone);
if (sdM != 1.0)
fprintf(esSpiceF, " M=%g", sdM);
}
break;
case DEV_FET:
case DEV_MOSFET:
case DEV_ASYMMETRIC:
if (source == NULL) break;
/* MOSFET is "Mnnn drain gate source [L=x W=x [attributes]]" */
spcdevOutNode(hc->hc_hierName,
drain->dterm_node->efnode_name->efnn_hier,
"drain", esSpiceF);
spcdevOutNode(hc->hc_hierName,
gate->dterm_node->efnode_name->efnn_hier,
"gate", esSpiceF);
spcdevOutNode(hc->hc_hierName,
source->dterm_node->efnode_name->efnn_hier,
"source", esSpiceF);
if (subnode)
{
fprintf(esSpiceF, " ");
subnodeFlat = spcdevSubstrate(hc->hc_hierName,
subnode->efnode_name->efnn_hier,
dev->dev_type, esSpiceF);
}
fprintf(esSpiceF, " %s", EFDevTypes[dev->dev_type]);
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);
}
spcHierWriteParams(hc, dev, scale, l, w, sdM, FALSE);
if (sdM != 1.0)
fprintf(esSpiceF, " M=%g", sdM);
/*
* Check controlling attributes and output area and perimeter.
*/
#if 0 /* -Wunused-but-set-variable extHierSDAttr() has no side-effects */
bool hierS = extHierSDAttr(source);
bool hierD = extHierSDAttr(drain);
#endif
if ( gate->dterm_attrs )
subAP = Match(ATTR_SUBSAP, gate->dterm_attrs ) ;
fprintf(esSpiceF, "\n+ ");
dnode = GetHierNode(hc, drain->dterm_node->efnode_name->efnn_hier);
spcnAP(drain, dnode, esFetInfo[dev->dev_type].resClassDrain, scale,
"ad", "pd", sdM, esSpiceF, w);
snode= GetHierNode(hc, 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");
}
}
/* Output attributes, if present - it looks more convenient here, as other device types may be added */
switch (dev->dev_class)
{
case DEV_FET:
case DEV_MOSFET:
case DEV_ASYMMETRIC:
case DEV_MSUBCKT:
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;
}
/*
* ----------------------------------------------------------------------------
*
* spcdevHierMergeVisit --
*
* First pass visit to devices to determine if they can be merged with
* any previously visited device.
*
* ----------------------------------------------------------------------------
*/
/* ARGSUSED */
/* @typedef cb_extflat_hiervisitdevs_t (UNUSED) */
int
spcdevHierMergeVisit(
HierContext *hc,
Dev *dev, /* Dev being output */
float scale, /* Scale of transform (may be non-integer) */
ClientData cdata) /* unused */
{
DevTerm *gate, *source, *drain;
EFNode *subnode, *snode, *dnode, *gnode;
int pmode, l, w;
devMerge *fp;
const devMerge *cfp;
float m;
/* If no terminals, or only a gate, can't do much of anything */
if (dev->dev_nterm < 2) return 0;
gate = &dev->dev_terms[0];
source = drain = &dev->dev_terms[1];
if (dev->dev_nterm >= 3)
drain = &dev->dev_terms[2];
gnode = GetHierNode(hc, gate->dterm_node->efnode_name->efnn_hier);
snode = GetHierNode(hc, source->dterm_node->efnode_name->efnn_hier);
dnode = GetHierNode(hc, drain->dterm_node->efnode_name->efnn_hier);
subnode = dev->dev_subsnode;
EFGetLengthAndWidth(dev, &l, &w);
fp = mkDevMerge((float)((float)l * scale), (float)((float)w * scale),
gnode, snode, dnode, subnode, hc->hc_hierName, dev);
for (cfp = devMergeList; cfp != NULL; cfp = cfp->next)
{
if ((pmode = parallelDevs(fp, cfp)) != NOT_PARALLEL)
{
/* To-do: add back source, drain attribute check */
/* Default case: Add the counts together */
m = esFMult[cfp->esFMIndex] + esFMult[fp->esFMIndex];
switch(dev->dev_class)
{
case DEV_MOSFET:
case DEV_MSUBCKT:
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) ||
!strcmp(EFDevTypes[fp->dev->dev_type], "None"))
{
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) ||
!strcmp(EFDevTypes[fp->dev->dev_type], "None"))
{
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++;
freeMagic(fp);
return 0;
}
}
/* No devices are parallel to this one (yet) */
fp->next = devMergeList;
devMergeList = fp;
return 0;
}
/*
* ----------------------------------------------------------------------------
*
* spccapHierVisit --
*
* Procedure to output a single capacitor to the .spice file.
* Called by EFHierVisitCaps().
*
* 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).
*
* ----------------------------------------------------------------------------
*/
/* ARGSUSED */
/* @typedef cb_extflat_hiervisitdevs_t (UNUSED) */
int
spccapHierVisit(
HierContext *hc,
HierName *hierName1,
HierName *hierName2,
double cap,
ClientData cdata) /* unused */
{
cap = cap / 1000;
if (fabs(cap) <= EFCapThreshold)
return 0;
fprintf(esSpiceF, "C%d %s %s ", esCapNum++,
nodeSpiceHierName(hc, hierName1),
nodeSpiceHierName(hc, hierName2));
esSIvalue(esSpiceF, 1.0E-15 *cap);
fprintf(esSpiceF, "\n");
return 0;
}
/*
* ----------------------------------------------------------------------------
*
* spcresistHierVisit --
*
* Procedure to output a single resistor to the .spice file.
* Called by EFHierVisitResists().
*
* 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)
*
*
* ----------------------------------------------------------------------------
*/
/*ARGUSED*/
/* @typedef cb_extflat_hiervisitresists_t (UNUSED) */
int
spcresistHierVisit(
HierContext *hc,
const HierName *hierName1,
const HierName *hierName2,
float res,
ClientData cdata) // UNUSED
{
HashEntry *he;
EFNodeName *nn;
fprintf(esSpiceF, "R%d %s %s %g\n", esResNum++,
nodeSpiceHierName(hc, hierName1),
nodeSpiceHierName(hc, hierName2), 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;
}
/*
* ----------------------------------------------------------------------------
*
* spcsubHierVisit --
*
* Find the node that connects to the 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
spcsubHierVisit(
HierContext *hc,
EFNode *node,
int res, // Unused
double cap, // Unused
char **resstrptr)
{
HierName *hierName;
const char *nsn;
if (node->efnode_flags & EF_GLOB_SUBS_NODE)
{
hierName = (HierName *) node->efnode_name->efnn_hier;
nsn = nodeSpiceHierName(hc, hierName);
*resstrptr = StrDup((char **)NULL, nsn);
return 1;
}
return 0;
}
/*
* ----------------------------------------------------------------------------
*
* spcnodeHierVisit --
*
* Procedure to output a single node to the .spice file along with its
* attributes and its dictionary (if present). Called by EFHierVisitNodes().
*
* Results:
* Returns 0 always.
*
* Side effects:
* Writes to the files esSpiceF
*
* ----------------------------------------------------------------------------
*/
int
spcnodeHierVisit(
HierContext *hc,
EFNode *node,
int res,
double cap)
{
HierName *hierName;
bool isConnected = FALSE;
const char *fmt, *nsn;
EFAttr *ap;
if (node->efnode_client)
{
if (esDistrJunct)
isConnected = (((nodeClient *)node->efnode_client)->m_w.widths != NULL);
else
isConnected = !TTMaskIsZero(&((nodeClient *)node->efnode_client)->m_w.visitMask);
}
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 = nodeSpiceHierName(hc, hierName);
if (esFormat == SPICE2 || (esFormat == HSPICE && !strncmp(nsn, "z@", 2))) {
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 (fabs(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;
}
/*
* ----------------------------------------------------------------------------
*
* nodeSpiceHierName --
* Find the real spice name for the node with hierarchical name hname.
* SPICE2 ==> numeric
* SPICE3, NGSPICE ==> full magic path
* HSPICE ==> less than 15 characters long
*
* Results:
* Returns the spice node name.
*
* Side effects:
* Allocates nodeClients for the node.
*
* ----------------------------------------------------------------------------
*/
static char esTempName[MAX_STR_SIZE];
const char *
nodeSpiceHierName(
HierContext *hc,
const HierName *hname)
{
EFNodeName *nn;
HashEntry *he;
EFNode *node;
he = EFHNLook(hname, NULL, "ext2spice");
if (he == NULL) return "error";
nn = (EFNodeName *) HashGetValue(he);
if (nn == NULL)
return "<invalid node>";
node = nn->efnn_node;
if ((nodeClient *) (node->efnode_client) == NULL)
{
initNodeClient(node);
goto makeName;
}
else if (((nodeClient *) (node->efnode_client))->spiceNodeName == NULL)
goto makeName;
else goto retName;
makeName:
if (esFormat == SPICE2)
sprintf(esTempName, "%d", esNodeNum++);
else {
EFHNSprintf(esTempName, node->efnode_name->efnn_hier);
if (esFormat == HSPICE) /* more processing */
nodeHspiceName(esTempName);
}
((nodeClient *) (node->efnode_client))->spiceNodeName =
StrDup(NULL, esTempName);
retName:
return ((nodeClient *) (node->efnode_client))->spiceNodeName;
}
/*
* ----------------------------------------------------------------------------
*
* devMergeVisit --
* Visits each dev throu EFHierVisitDevs and finds if it is in parallel with
* any previously visited dev.
*
* Results:
* 0 always to keep the caller going.
*
* Side effects:
* Numerous.
*
* ----------------------------------------------------------------------------
*/
int
devMergeHierVisit(
HierContext *hc,
Dev *dev, /* Dev to examine */
float scale) /* Scale transform of output */
{
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;
if (esDistrJunct)
devDistJunctHierVisit(hc, dev, scale, PTR2CD(NULL));
if (dev->dev_nterm < 2)
{
TxError("outPremature\n");
return 0;
}
gate = &dev->dev_terms[0];
source = drain = &dev->dev_terms[1];
if (dev->dev_nterm >= 3)
drain = &dev->dev_terms[2];
gnode = GetHierNode(hc, gate->dterm_node->efnode_name->efnn_hier);
snode = GetHierNode(hc, source->dterm_node->efnode_name->efnn_hier);
dnode = GetHierNode(hc, drain->dterm_node->efnode_name->efnn_hier);
if (dev->dev_subsnode)
subnode = spcdevSubstrate(hc->hc_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, NULL, 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 cf, cg */
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 (hS && !chS)
{
mergeAttr(&cs->dterm_attrs, &source->dterm_attrs);
}
if (hD && !chD)
{
mergeAttr(&cd->dterm_attrs, &drain->dterm_attrs);
}
mergeThem:
/* Default case: Add the counts together */
m = esFMult[cfp->esFMIndex] + esFMult[fp->esFMIndex];
switch(dev->dev_class)
{
case DEV_MOSFET:
case DEV_ASYMMETRIC:
case DEV_MSUBCKT:
case DEV_FET:
if (cfp->w > 0)
m = esFMult[cfp->esFMIndex] + ((float)fp->w / (float)cfp->w);
break;
case DEV_RSUBCKT:
case DEV_RES:
if ((fp->dev->dev_type == esNoModelType) ||
!strcmp(EFDevTypes[fp->dev->dev_type], "None"))
{
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) ||
!strcmp(EFDevTypes[fp->dev->dev_type], "None"))
{
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;
}
/*
* ----------------------------------------------------------------------------
*
* 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_hiervisitdevs_t (UNUSED) */
int
devDistJunctHierVisit(
HierContext *hc,
Dev *dev, /* Dev to examine */
float scale, /* Scale tranform of output */
ClientData cdata) /* unused */
{
EFNode *n;
int i, l, w;
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 = GetHierNode(hc, 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;
}
/* Structure used to store flags and an EFNode pointer */
typedef struct _flagDefRecord {
int fdr_flags; /* Flags to propagate (if any) */
EFNode *fdr_node; /* Node of parent cell (if any) */
struct _flagDefRecord *fdr_next; /* Keep in a linked list */
} flagDefRecord;
/*
* ----------------------------------------------------------------------------
*
* esMakePorts ---
*
* Routine called once for each cell definition in the extraction
* hierarchy. Called from EFHierSrDefs(). Looks at all subcircuit
* connections in the cell, and adds a port record to the subcircuit
* for each connection to it. Note that this generates an arbitrary
* port order for each cell. To have a specific port order, it is
* necessary to generate ports for each cell.
*
* ----------------------------------------------------------------------------
*/
int
esMakePorts(
HierContext *hc,
ClientData cdata)
{
Connection *conn;
Def *def = hc->hc_use->use_def, *portdef, *updef;
Use *use;
HashTable flagHashTable;
HashEntry *he, *he1, *he2;
EFNodeName *nn;
flagDefRecord *flagrec, *flagrec2, *flagtop;
char *name, *portname, *tptr, *aptr, *locname;
int j;
/* Done when the bottom of the hierarchy is reached */
if (HashGetNumEntries(&def->def_uses) == 0) return 0;
/* The entire purpose of this hash table is to avoid unnecessary */
/* use of the substrate as a port. Technically, every layout has */
/* a substrate. For LVS, however, only the connections to devices */
/* are relevant. This is tracked with the EF_SUBS_PORT flag. But */
/* the flag needs to be propagated up through the hierarchy, and */
/* that hierarchy can best be determined here. Since the */
/* connections made by "merge" statements may weave through */
/* multiple cells, the child-to-parent propagation of the */
/* EF_SUBS_PORT flag must be made by surveying all of the */
/* connections. */
flagtop = NULL;
HashInit(&flagHashTable, 32, HT_STRINGKEYS);
for (conn = (Connection *)def->def_conns; conn; conn = conn->conn_next)
{
for (j = 0; j < 2; j++)
{
name = (j == 0) ? conn->conn_1.cn_name : conn->conn_2.cn_name;
locname = (j == 0) ? conn->conn_2.cn_name : conn->conn_1.cn_name;
if ((tptr = strchr(name, '/')) == NULL)
continue;
/* Create entries for both node names in the flag hash table, */
/* and make them both point to the same record. */
he1 = HashFind(&flagHashTable, name);
flagrec = (flagDefRecord *)HashGetValue(he1);
he2 = HashFind(&flagHashTable, locname);
flagrec2 = (flagDefRecord *)HashGetValue(he2);
if ((flagrec == NULL) && (flagrec2 == NULL))
{
flagrec = (flagDefRecord *)mallocMagic(sizeof(flagDefRecord));
flagrec->fdr_node = NULL;
flagrec->fdr_flags = 0;
flagrec->fdr_next = flagtop;
flagtop = flagrec;
HashSetValue(he1, flagrec);
HashSetValue(he2, flagrec);
}
else if (flagrec == NULL)
{
flagrec = flagrec2;
HashSetValue(he1, flagrec);
}
else if (flagrec2 == NULL)
HashSetValue(he2, flagrec);
portname = name;
updef = def;
while (tptr != NULL)
{
bool is_array;
/* Ignore array information for the purpose of tracing */
/* the cell definition hierarchy. If a cell use name */
/* contains a bracket, check first if the complete name */
/* matches a use. If not, then check if the part */
/* the last opening bracket matches a known use. */
*tptr = '\0';
aptr = strrchr(portname, '[');
is_array = FALSE;
if (aptr != NULL)
{
he = HashLookOnly(&updef->def_uses, portname);
if (he == NULL)
{
*aptr = '\0';
is_array = TRUE;
}
}
// Find the cell for the instance
portdef = NULL;
he = HashLookOnly(&updef->def_uses, portname);
if (he != NULL)
{
use = (Use *)HashGetValue(he);
portdef = use->use_def;
}
if (is_array)
*aptr = '[';
*tptr = '/';
portname = tptr + 1;
/* Find the net of portname in the subcell and make it a
* port if it is not already. It is possible that the
* preferred node name is in the merge list, so the merging
* code may need to replace it with another name.
*/
if (portdef)
{
he = HashFind(&portdef->def_nodes, portname);
nn = (EFNodeName *) HashGetValue(he);
if (nn == NULL)
{
efBuildNode(portdef, FALSE, FALSE, FALSE, portname,
0.0, 0, 0, NULL, NULL, 0);
nn = (EFNodeName *) HashGetValue(he);
}
if (nn->efnn_node && !(nn->efnn_node->efnode_flags & EF_PORT))
{
/* If a node is marked EF_SUBS_NODE (is substrate) */
/* or EF_GLOB_SUBS_NODE (is global substrate) */
/* but not EF_SUBS_PORT (connects to no devices) */
/* and parasitic output is disabled, then do not */
/* force the substrate connection to be a port. */
if ((EFCapThreshold != (EFCapValue)INFINITE_THRESHOLD_F) ||
(!(nn->efnn_node->efnode_flags &
(EF_SUBS_NODE | EF_GLOB_SUBS_NODE))) ||
(nn->efnn_node->efnode_flags & EF_SUBS_PORT))
{
nn->efnn_node->efnode_flags |= EF_PORT;
nn->efnn_port = -1; // Will be sorted later
// Diagnostic
/* TxPrintf("Port connection in %s from net %s to "
* "net %s (%s)\n",
* def->def_name, locname, name, portname);
*/
}
}
/* Propagate the EF_SUBS_PORT flag */
if (nn->efnn_node && (nn->efnn_node->efnode_flags & EF_SUBS_PORT))
{
flagrec->fdr_flags = EF_SUBS_PORT;
if (flagrec->fdr_node != NULL)
flagrec->fdr_node->efnode_flags |= EF_SUBS_PORT;
}
}
if ((tptr = strchr(portname, '/')) == NULL)
break;
if (portdef == NULL) break; // Error condition?
updef = portdef;
}
/* If locname is a port of the parent, set this in the flag */
/* record, and if the flag is non-zero, apply it immediately. */
if (strchr(locname, '/') == NULL)
{
he = HashFind(&def->def_nodes, locname);
if (he != NULL)
{
nn = (EFNodeName *) HashGetValue(he);
if (nn == NULL)
{
TxError("Error: Node %s not found in cell %s!\n",
locname, def->def_name);
}
else
{
flagrec->fdr_node = nn->efnn_node;
flagrec->fdr_node->efnode_flags |= flagrec->fdr_flags;
}
}
}
}
}
// Now do the same thing for parasitic connections into subcells
// However, restrict the number of ports based on "cthresh".
for (conn = (Connection *)def->def_caps; conn; conn = conn->conn_next)
{
for (j = 0; j < 2; j++)
{
name = (j == 0) ? conn->conn_1.cn_name : conn->conn_2.cn_name;
locname = (j == 0) ? conn->conn_2.cn_name : conn->conn_1.cn_name;
if ((tptr = strchr(name, '/')) == NULL)
continue;
// Ignore capacitances that are less than the threshold.
// In particular, this keeps parasitics out of the netlist for
// LVS purposes if "cthresh" is set to "infinite".
if (fabs((double)conn->conn_cap / 1000) < EFCapThreshold) continue;
portname = name;
updef = def;
while (tptr != NULL)
{
int idum[6];
bool is_array;
/* Ignore array information for the purpose of tracing */
/* the cell definition hierarchy. */
aptr = strchr(portname, '[');
if (aptr && (aptr < tptr) &&
(sscanf(aptr, "[%d:%d:%d][%d:%d:%d]",
&idum[0], &idum[1], &idum[2],
&idum[3], &idum[4], &idum[5]) == 6))
{
*aptr = '\0';
is_array = TRUE;
}
else
{
*tptr = '\0';
is_array = FALSE;
}
// Find the cell for the instance
portdef = NULL;
he = HashLookOnly(&updef->def_uses, portname);
if (he != NULL)
{
use = (Use *)HashGetValue(he);
portdef = use->use_def;
}
if (is_array)
*aptr = '[';
else
*tptr = '/';
portname = tptr + 1;
// Find the net of portname in the subcell and
// make it a port if it is not already.
if (portdef)
{
he = HashFind(&portdef->def_nodes, portname);
nn = (EFNodeName *) HashGetValue(he);
if (nn == NULL)
{
efBuildNode(portdef, FALSE, FALSE, FALSE, portname,
0.0, 0, 0, NULL, NULL, 0);
nn = (EFNodeName *) HashGetValue(he);
}
if (!(nn->efnn_node->efnode_flags & EF_PORT))
{
nn->efnn_node->efnode_flags |= EF_PORT;
nn->efnn_port = -1; // Will be sorted later
}
}
if ((tptr = strchr(portname, '/')) == NULL)
break;
if (portdef == NULL) break; // Error condition?
updef = portdef;
}
// Diagnostic
// TxPrintf("Connection in %s to net %s (%s)\n", def->def_name,
// name, portname);
}
}
/* Free table data */
while (flagtop != NULL)
{
freeMagic((char *)flagtop);
flagtop = flagtop->fdr_next;
}
HashKill(&flagHashTable);
return 0;
}
/*
* ----------------------------------------------------------------------------
*
* esHierVisit ---
*
* Routine called once for each cell definition in the extraction
* hierarchy. Called from EFHierSrDefs(). Outputs a single
* subcircuit record for the cell definition. Note that this format
* ignores all information pertaining to flattened cells, and is
* appropriate mainly for LVS purposes.
* ----------------------------------------------------------------------------
*/
int
esHierVisit(
HierContext *hc,
ClientData cdata)
{
HierContext *hcf;
Def *def = hc->hc_use->use_def;
Def *topdef;
EFNode *snode;
char *resstr = NULL;
DefFlagsData *dfd;
int flags;
int locDoSubckt = esDoSubckt;
bool doStub;
dfd = (DefFlagsData *)cdata;
topdef = dfd->def;
flags = dfd->flags;
/* Cells which are marked as "primitive" get no output at all */
if (def->def_flags & DEF_PRIMITIVE) return 0;
/* Cells without any contents (devices or subcircuits) will */
/* be absorbed into their parents. Use this opportunity to */
/* remove all ports. */
if (def != topdef)
{
if ((HashGetNumEntries(&def->def_devs) == 0) &&
(HashGetNumEntries(&def->def_uses) == 0))
{
if (locDoSubckt == AUTO)
{
/* Determine if there are ports, and don't */
/* kill the cell if it has any. */
locDoSubckt = FALSE;
for (snode = (EFNode *) def->def_firstn.efnode_next;
snode != &def->def_firstn;
snode = (EFNode *) snode->efnode_next)
if (snode->efnode_flags & (EF_PORT | EF_SUBS_PORT))
{
locDoSubckt = TRUE;
break;
}
}
if (locDoSubckt == FALSE)
{
for (snode = (EFNode *) def->def_firstn.efnode_next;
snode != &def->def_firstn;
snode = (EFNode *) snode->efnode_next)
snode->efnode_flags &= ~(EF_PORT | EF_SUBS_PORT);
if (def != topdef) return 0;
}
}
}
/* Flatten this definition only */
hcf = EFFlatBuildOneLevel(hc->hc_use->use_def, flags);
/* If definition has been marked as having no devices, then this */
/* def is not to be output unless it is the top level. However, if */
/* this subcircuit is an abstract view, then create a subcircuit */
/* stub entry for it (declares port names and order but no devices) */
doStub = ((hc->hc_use->use_def->def_flags & DEF_ABSTRACT) && esDoBlackBox) ?
TRUE : FALSE;
if ((def != topdef) && (hc->hc_use->use_def->def_flags & DEF_NODEVICES) &&
(!doStub))
{
EFFlatDone(esFreeNodeClient);
return 0;
}
else if (doStub)
fprintf(esSpiceF, "* Black-box entry subcircuit for %s abstract view\n",
def->def_name);
/* Automatic subcircuit handling for top level: Check if the top */
/* level has any ports defined. If so, make a subcircuit record */
/* for it. If not, don't. */
if ((def == topdef) && (locDoSubckt == AUTO))
{
/* Determine if there are ports */
locDoSubckt = FALSE;
for (snode = (EFNode *) def->def_firstn.efnode_next;
snode != &def->def_firstn;
snode = (EFNode *) snode->efnode_next)
if (snode->efnode_flags & (EF_PORT | EF_SUBS_PORT))
{
locDoSubckt = TRUE;
break;
}
}
/* Generate subcircuit header */
if ((def != topdef) || (def->def_flags & DEF_SUBCIRCUIT) || (locDoSubckt == TRUE))
topVisit(def, doStub);
else
fprintf(esSpiceF, "\n* Top level circuit %s\n\n", topdef->def_name);
if (!doStub) /* By definition, stubs have no internal components */
{
/* Output subcircuit calls */
EFHierVisitSubcircuits(hcf, subcktHierVisit, (ClientData)NULL);
/* Merge devices */
if (esMergeDevsA || esMergeDevsC)
{
const devMerge *p;
EFHierVisitDevs(hcf, spcdevHierMergeVisit, (ClientData)NULL);
TxPrintf("Devs merged: %d\n", esSpiceDevsMerged);
esFMIndex = 0;
for (p = devMergeList; p != NULL; p = p->next)
freeMagic((char *)p);
devMergeList = NULL;
}
else if (esDistrJunct)
EFHierVisitDevs(hcf, devDistJunctHierVisit, PTR2CD(NULL));
/* Output devices */
EFHierVisitDevs(hcf, spcdevHierVisit, PTR2CD(NULL));
/* Output lumped parasitic resistors */
EFHierVisitResists(hcf, spcresistHierVisit, PTR2CD(NULL));
/* Output coupling capacitances */
EFHierVisitCaps(hcf, spccapHierVisit, (ClientData)NULL);
if (EFCompat == FALSE)
{
/* Find the substrate node */
EFHierVisitNodes(hcf, spcsubHierVisit, (ClientData)&resstr);
if (resstr == NULL)
resstr = StrDup((char **)NULL, esSpiceDefaultGnd);
/* Output lumped capacitance and resistance to substrate */
esSpiceCapNode = resstr;
EFHierVisitNodes(hcf, spcnodeHierVisit, (ClientData) NULL);
freeMagic(resstr);
}
/* Reset device merge index for next cell */
if (esMergeDevsA || esMergeDevsC) esFMIndex = 0;
}
if ((def != topdef) || (def->def_flags & DEF_SUBCIRCUIT) || (locDoSubckt == TRUE))
fprintf(esSpiceF, ".ends\n\n");
else
fprintf(esSpiceF, ".end\n\n");
/* Reset device/node/subcircuit instance counts */
esCapNum = 0;
esDevNum = 1000;
esResNum = 0;
esDiodeNum = 0;
esSbckNum = 0;
esNodeNum = 10;
/* Reset the subcircuit hash table, if using HSPICE format */
if (esFormat == HSPICE)
{
HashKill(&subcktNameTable);
HashInit(&subcktNameTable, 32, HT_STRINGKEYS);
#ifndef UNSORTED_SUBCKT
DQFree(&subcktNameQueue);
DQInit(&subcktNameQueue, 64);
#endif
}
EFFlatDone(esFreeNodeClient);
return 0;
}