mirror of
https://github.com/RTimothyEdwards/magic.git
synced 2026-09-07 11:31:01 +02:00
tech file format "version" section. This can be used to specify the version of magic that must be used to be compatible with the tech file. This effectively supercedes the technology version number. (2) Changed the behavior of "make" to set the version and revision numbers on doing "make" instead of "configure". This allows the version to update correctly after doing a "git pull" followed by "make" without doing "configure" in between. (3) Fixed a couple of issues that were flagged as compile-time warnings.
4042 lines
104 KiB
C
4042 lines
104 KiB
C
/*
|
|
* 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 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 <stdlib.h> /* for atof() */
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
|
|
#include "tcltk/tclmagic.h"
|
|
#include "utils/magic.h"
|
|
#include "utils/malloc.h"
|
|
#include "utils/geometry.h"
|
|
#include "utils/hash.h"
|
|
#include "utils/dqueue.h"
|
|
#include "utils/utils.h"
|
|
#include "tiles/tile.h"
|
|
#ifdef MAGIC_WRAPPER
|
|
#include "database/database.h"
|
|
#include "windows/windows.h"
|
|
#include "textio/textio.h"
|
|
#include "dbwind/dbwind.h" /* for DBWclientID */
|
|
#include "commands/commands.h" /* for module auto-load */
|
|
#include "textio/txcommands.h"
|
|
#endif
|
|
#include "extflat/extflat.h"
|
|
#include "extflat/EFint.h"
|
|
#include "extract/extract.h" /* for extDevTable */
|
|
#include "utils/runstats.h"
|
|
|
|
#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];
|
|
int esCapAccuracy = 2;
|
|
char esSpiceCapFormat[FNSIZE];
|
|
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;
|
|
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[MAXDEVTYPES];
|
|
|
|
/* Record for keeping a list of global names */
|
|
|
|
typedef struct GLL *globalListPtr;
|
|
|
|
typedef struct GLL {
|
|
globalListPtr gll_next;
|
|
char *gll_name;
|
|
} globalList;
|
|
|
|
unsigned long initMask = 0;
|
|
|
|
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;
|
|
|
|
devMerge *devMergeList = NULL ;
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* Apply modifications to a global node name and output to file "outf"
|
|
*
|
|
* Results:
|
|
* None.
|
|
*
|
|
* Side effects:
|
|
* Output to file
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
void
|
|
esFormatSubs(outf, suf)
|
|
FILE *outf;
|
|
char *suf;
|
|
{
|
|
char *specchar;
|
|
int l;
|
|
|
|
if (outf)
|
|
{
|
|
l = strlen(suf) - 1;
|
|
if ((EFTrimFlags & EF_TRIMGLOB ) && suf[l] == '!' ||
|
|
(EFTrimFlags & EF_TRIMLOCAL) && suf[l] == '#')
|
|
suf[l] = '\0' ;
|
|
if (EFTrimFlags & EF_CONVERTCOMMA)
|
|
while ((specchar = strchr(suf, ',')) != NULL)
|
|
*specchar = '|';
|
|
if (EFTrimFlags & EF_CONVERTBRACKETS)
|
|
{
|
|
while ((specchar = strchr(suf, '[')) != NULL)
|
|
*specchar = '_';
|
|
while ((specchar = strchr(suf, ']')) != NULL)
|
|
*specchar = '_';
|
|
}
|
|
if (EFTrimFlags & 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(interp)
|
|
Tcl_Interp *interp;
|
|
{
|
|
/* Sanity checks! */
|
|
if (interp == NULL) return TCL_ERROR;
|
|
if (Tcl_PkgRequire(interp, "Tclmagic", MAGIC_VERSION, 0) == NULL)
|
|
return TCL_ERROR;
|
|
if (Tcl_InitStubs(interp, "8.5", 0) == NULL) return TCL_ERROR;
|
|
|
|
TxPrintf("Auto-loading EXTTOSPICE module\n");
|
|
TxFlushOut();
|
|
|
|
/* Replace the auto-load function with the one defined in */
|
|
/* this package in the command functions list. */
|
|
|
|
if (WindReplaceCommand(DBWclientID, "exttospice", CmdExtToSpice) < 0)
|
|
return TCL_ERROR;
|
|
|
|
/* Add "ext2spice" as an alias for "exttospice" */
|
|
if (WindReplaceCommand(DBWclientID, "ext2spice", CmdExtToSpice) < 0)
|
|
return TCL_ERROR;
|
|
|
|
Tcl_PkgProvide(interp, "Exttospice", MAGIC_VERSION);
|
|
return TCL_OK;
|
|
}
|
|
#endif /* EXT2SPICE_AUTO */
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* 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_SUBCIRCUITS 9
|
|
#define EXTTOSPC_HIERARCHY 10
|
|
#define EXTTOSPC_BLACKBOX 11
|
|
#define EXTTOSPC_RENUMBER 12
|
|
#define EXTTOSPC_MERGENAMES 13
|
|
#define EXTTOSPC_LVS 14
|
|
#define EXTTOSPC_HELP 15
|
|
|
|
void
|
|
CmdExtToSpice(w, cmd)
|
|
MagWindow *w;
|
|
TxCommand *cmd;
|
|
{
|
|
int i,flatFlags;
|
|
char *inName;
|
|
FILE *f;
|
|
|
|
int value;
|
|
int option = EXTTOSPC_RUN;
|
|
int argc = cmd->tx_argc;
|
|
char **argv = cmd->tx_argv;
|
|
char **msg;
|
|
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 char *spiceFormats[] = {
|
|
"SPICE2", "SPICE3", "HSPICE", "NGSPICE", NULL
|
|
};
|
|
|
|
static char *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",
|
|
"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 char *cmdMergeTypes[] = {
|
|
"none don't merge parallel devices",
|
|
"conservative merge devices with same L, W",
|
|
"aggressive merge devices with same L",
|
|
NULL
|
|
};
|
|
|
|
static char *cmdExtToSpcFormat[] = {
|
|
"spice2",
|
|
"spice3",
|
|
"hspice",
|
|
"ngspice",
|
|
NULL
|
|
};
|
|
|
|
static char *yesno[] = {
|
|
"yes",
|
|
"true",
|
|
"on",
|
|
"no",
|
|
"false",
|
|
"off",
|
|
NULL
|
|
};
|
|
|
|
static char *subcktopts[] = {
|
|
"yes",
|
|
"true",
|
|
"on",
|
|
"no",
|
|
"false",
|
|
"off",
|
|
"automatic",
|
|
"top",
|
|
"descend",
|
|
NULL
|
|
};
|
|
|
|
typedef enum {
|
|
IDX_YES, IDX_TRUE, IDX_ON, IDX_NO, IDX_FALSE, IDX_OFF,
|
|
IDX_AUTO, IDX_TOP, IDX_DESCEND
|
|
} yesnoType;
|
|
|
|
esNoModelType = -1;
|
|
|
|
if (cmd->tx_argc > 1)
|
|
{
|
|
option = Lookup(cmd->tx_argv[1], cmdExtToSpcOption);
|
|
if (option < 0) option = EXTTOSPC_RUN;
|
|
else argv++;
|
|
}
|
|
|
|
switch (option)
|
|
{
|
|
case EXTTOSPC_EXTRESIST:
|
|
if (cmd->tx_argc == 2)
|
|
{
|
|
Tcl_SetResult(magicinterp, (esDoExtResis) ? "on" : "off", NULL);
|
|
return;
|
|
}
|
|
else if (cmd->tx_argc != 3)
|
|
goto usage;
|
|
idx = Lookup(cmd->tx_argv[2], yesno);
|
|
if (idx < 0) goto usage;
|
|
else if (idx < 3) esDoExtResis = TRUE;
|
|
else esDoExtResis = FALSE;
|
|
break;
|
|
|
|
case EXTTOSPC_RESISTORTEE:
|
|
if (cmd->tx_argc == 3)
|
|
{
|
|
Tcl_SetResult(magicinterp, (esDoResistorTee) ? "on" : "off", NULL);
|
|
return;
|
|
}
|
|
else if (cmd->tx_argc != 4)
|
|
goto usage;
|
|
idx = Lookup(cmd->tx_argv[3], yesno);
|
|
if (idx < 0) goto usage;
|
|
else if (idx < 3) esDoResistorTee = TRUE;
|
|
else esDoResistorTee = FALSE;
|
|
break;
|
|
|
|
case EXTTOSPC_SCALE:
|
|
if (cmd->tx_argc == 2)
|
|
{
|
|
Tcl_SetResult(magicinterp, (esScale < 0) ? "on" : "off", NULL);
|
|
return;
|
|
}
|
|
else if (cmd->tx_argc != 3)
|
|
goto usage;
|
|
idx = Lookup(cmd->tx_argv[2], yesno);
|
|
if (idx < 0) goto usage;
|
|
else if (idx < 3) esScale = -1.0;
|
|
else esScale = 0.0;
|
|
break;
|
|
|
|
case EXTTOSPC_HIERARCHY:
|
|
if (cmd->tx_argc == 2)
|
|
{
|
|
Tcl_SetResult(magicinterp, (esDoHierarchy) ? "on" : "off", NULL);
|
|
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)
|
|
{
|
|
Tcl_SetResult(magicinterp, (esDoBlackBox) ? "on" : "off", NULL);
|
|
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)
|
|
{
|
|
Tcl_SetResult(magicinterp, (esDoRenumber) ? "on" : "off", NULL);
|
|
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)
|
|
{
|
|
Tcl_SetResult(magicinterp, (esMergeNames) ? "on" : "off", NULL);
|
|
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_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)
|
|
{
|
|
Tcl_SetResult(magicinterp, (esDoPorts) ? "on" : "off", NULL);
|
|
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)
|
|
{
|
|
Tcl_SetResult(magicinterp, (esDoPorts) ? "on" : "off", NULL);
|
|
return;
|
|
}
|
|
break;
|
|
case IDX_TOP:
|
|
if (cmd->tx_argc == 3)
|
|
{
|
|
Tcl_SetResult(magicinterp,
|
|
(esDoSubckt == 2) ? "auto" :
|
|
(esDoSubckt == 1) ? "on" : "off", NULL);
|
|
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)
|
|
{
|
|
Tcl_SetResult(magicinterp, cmdExtToSpcFormat[esFormat], NULL);
|
|
return;
|
|
}
|
|
else if (cmd->tx_argc < 3) goto usage;
|
|
idx = Lookup(cmd->tx_argv[2], cmdExtToSpcFormat);
|
|
if (idx < 0)
|
|
{
|
|
Tcl_SetResult(magicinterp, "Bad format type. Formats are:"
|
|
"spice2, spice3, hspice, and ngspice.", NULL);
|
|
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))
|
|
Tcl_SetResult(magicinterp, "infinite", NULL);
|
|
else
|
|
Tcl_SetObjResult(magicinterp,
|
|
Tcl_NewDoubleObj((double)LocCapThreshold));
|
|
return;
|
|
}
|
|
else if (cmd->tx_argc < 3) goto usage;
|
|
|
|
/* Note that strtod() (called by StrIsNumeric()) accepts */
|
|
/* "infinite" as a valid numerical value; however, the */
|
|
/* conversion to C type INF is *not* INFINITE_THRESHOLD, so */
|
|
/* we need to check this case first. . . */
|
|
|
|
if (!strncmp(cmd->tx_argv[2], "inf", 3))
|
|
LocCapThreshold = (EFCapValue)INFINITE_THRESHOLD_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)
|
|
Tcl_SetResult(magicinterp, "infinite", NULL);
|
|
else
|
|
Tcl_SetObjResult(magicinterp,
|
|
Tcl_NewIntObj(LocResistThreshold));
|
|
return;
|
|
}
|
|
else if (cmd->tx_argc < 3) goto usage;
|
|
if (StrIsInt(cmd->tx_argv[2]))
|
|
LocResistThreshold = atoi(cmd->tx_argv[2]);
|
|
else if (!strncmp(cmd->tx_argv[2], "inf", 3))
|
|
LocResistThreshold = INFINITE_THRESHOLD;
|
|
else
|
|
TxError("exttospice: integer value or \"infinite\" expected.\n");
|
|
break;
|
|
|
|
case EXTTOSPC_MERGE:
|
|
if (cmd->tx_argc == 2)
|
|
{
|
|
if (esMergeDevsA)
|
|
Tcl_SetResult(magicinterp, "aggressive", NULL);
|
|
else if (esMergeDevsC)
|
|
Tcl_SetResult(magicinterp, "conservative", NULL);
|
|
else
|
|
Tcl_SetResult(magicinterp, "none", NULL);
|
|
return;
|
|
}
|
|
else if (cmd->tx_argc < 3) goto usage;
|
|
value = Lookup(cmd->tx_argv[2], cmdMergeTypes);
|
|
if (value < 0)
|
|
{
|
|
TxError("Merge types are:\n");
|
|
for (msg = &(cmdMergeTypes[0]); *msg != NULL; msg++)
|
|
TxPrintf(" %s\n", *msg);
|
|
}
|
|
else switch (value) {
|
|
case 0:
|
|
esMergeDevsA = FALSE;
|
|
esMergeDevsC = FALSE;
|
|
break;
|
|
case 1:
|
|
esMergeDevsA = FALSE;
|
|
esMergeDevsC = TRUE;
|
|
break;
|
|
case 2:
|
|
esMergeDevsA = TRUE;
|
|
esMergeDevsC = FALSE;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case EXTTOSPC_DEFAULT:
|
|
LocCapThreshold = 2;
|
|
LocResistThreshold = INFINITE_THRESHOLD;
|
|
EFTrimFlags = 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;
|
|
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, spcmainArgs, (ClientData) NULL);
|
|
if (err_result == TRUE)
|
|
{
|
|
EFDone();
|
|
return;
|
|
}
|
|
|
|
if (inName == NULL)
|
|
{
|
|
/* Assume that we want to do exttospice on the currently loaded cell */
|
|
|
|
if (w == (MagWindow *) NULL)
|
|
{
|
|
windCheckOnlyWindow(&w, DBWclientID);
|
|
}
|
|
|
|
if (w == (MagWindow *) NULL)
|
|
{
|
|
TxError("Point to a window or specify a cell name.\n");
|
|
return;
|
|
}
|
|
if ((inName = ((CellUse *) w->w_surfaceID)->cu_def->cd_name) == NULL)
|
|
{
|
|
TxError("No cell present\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Initializations specific to this program.
|
|
* Make output name inName.spice if they weren't
|
|
* explicitly specified
|
|
*/
|
|
|
|
if (spcesOutName == spcesDefaultOut)
|
|
sprintf(spcesDefaultOut, "%s.spice", inName);
|
|
|
|
/* Read the hierarchical description of the input circuit */
|
|
if (EFReadFile(inName, TRUE, esDoExtResis, FALSE) == FALSE)
|
|
{
|
|
EFDone();
|
|
return;
|
|
}
|
|
|
|
/* If the .ext file was read without error, then open the output file */
|
|
|
|
if ((esSpiceF = fopen(spcesOutName, "w")) == NULL)
|
|
{
|
|
char *tclres = Tcl_Alloc(128);
|
|
sprintf(tclres, "exttospice: Unable to open file %s for writing\n",
|
|
spcesOutName);
|
|
Tcl_SetResult(magicinterp, tclres, TCL_DYNAMIC);
|
|
EFDone();
|
|
return;
|
|
}
|
|
|
|
if (EFStyle == NULL)
|
|
{
|
|
TxError("Warning: Current extraction style does not match .ext file!\n");
|
|
TxError("Area/Perimeter values and parasitic values will be zero.\n");
|
|
}
|
|
|
|
/* create default devinfo entries (MOSIS) which can be overridden by
|
|
the command line arguments */
|
|
|
|
for ( i = 0 ; i < MAXDEVTYPES ; i++ ) {
|
|
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 == MAXDEVTYPES)
|
|
{
|
|
TxError("Error: Ran out of space for device types!\n");
|
|
break;
|
|
}
|
|
i = efBuildAddStr(EFDevTypes, &EFDevNumTypes, MAXDEVTYPES, devname);
|
|
if (!strcmp(devname, "None"))
|
|
esNoModelType = i;
|
|
if (EFStyle != NULL)
|
|
{
|
|
esFetInfo[i].resClassSource = s_rclass;
|
|
esFetInfo[i].resClassDrain = d_rclass;
|
|
esFetInfo[i].resClassSub = sub_rclass;
|
|
esFetInfo[i].defSubs = subname;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
if (esDoHierarchy && (subname != NULL))
|
|
{
|
|
globalList *glptr;
|
|
char *locsubname, *bangptr;
|
|
bool isgood = TRUE;
|
|
|
|
locsubname = StrDup(NULL, subname);
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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 */
|
|
}
|
|
|
|
/* 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=%gu\n\n", EFScale / 100.0);
|
|
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. . .
|
|
EFTrimFlags |= EF_TRIMGLOB | EF_CONVERTEQUAL | EF_CONVERTCOMMA;
|
|
if (IS_FINITE_F(EFCapThreshold)) flatFlags |= EF_FLATCAPS;
|
|
if (esFormat == HSPICE)
|
|
EFTrimFlags |= EF_TRIMLOCAL;
|
|
|
|
/* Write globals under a ".global" card */
|
|
|
|
if (esDoHierarchy && (glist != NULL))
|
|
{
|
|
fprintf(esSpiceF, ".global ");
|
|
while (glist != NULL)
|
|
{
|
|
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
|
|
esFormatSubs(esSpiceF, glist->gll_name);
|
|
|
|
fprintf(esSpiceF, " ");
|
|
freeMagic(glist->gll_name);
|
|
freeMagic(glist);
|
|
glist = glist->gll_next;
|
|
}
|
|
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);
|
|
|
|
initMask = ( esDistrJunct ) ? (unsigned long)0 : DEV_CONNECT_MASK;
|
|
|
|
if (esMergeDevsA || esMergeDevsC)
|
|
{
|
|
devMerge *p;
|
|
|
|
EFVisitDevs(devMergeVisit, (ClientData) NULL);
|
|
TxPrintf("Devs merged: %d\n", esSpiceDevsMerged);
|
|
esFMIndex = 0;
|
|
for (p = devMergeList; p != NULL; p = p->next)
|
|
freeMagic(p);
|
|
devMergeList = NULL;
|
|
}
|
|
else if (esDistrJunct)
|
|
EFVisitDevs(devDistJunctVisit, (ClientData) NULL);
|
|
EFVisitDevs(spcdevVisit, (ClientData) NULL);
|
|
initMask = (unsigned long) 0;
|
|
if (flatFlags & EF_FLATCAPS)
|
|
{
|
|
(void) sprintf( esSpiceCapFormat, "C%%d %%s %%s %%.%dlffF\n",
|
|
esCapAccuracy);
|
|
EFVisitCaps(spccapVisit, (ClientData) NULL);
|
|
}
|
|
EFVisitResists(spcresistVisit, (ClientData) NULL);
|
|
EFVisitSubcircuits(subcktVisit, (ClientData) NULL);
|
|
|
|
/* Visit nodes to find the substrate node */
|
|
EFVisitNodes(spcsubVisit, (ClientData)&substr);
|
|
if (substr == NULL)
|
|
substr = StrDup((char **)NULL, "0");
|
|
|
|
(void) sprintf( esSpiceCapFormat, "C%%d %%s %s %%.%dlffF%%s",
|
|
substr, esCapAccuracy);
|
|
EFVisitNodes(spcnodeVisit, (ClientData) NULL);
|
|
|
|
if (EFCompat == FALSE) freeMagic(substr);
|
|
|
|
if ((esDoSubckt == TRUE) || (locDoSubckt == TRUE))
|
|
fprintf(esSpiceF, ".ends\n");
|
|
|
|
if (esFormat == HSPICE)
|
|
printSubcktDict();
|
|
|
|
EFFlatDone();
|
|
}
|
|
EFDone();
|
|
|
|
if (esSpiceF) fclose(esSpiceF);
|
|
|
|
TxPrintf("exttospice finished.\n");
|
|
return;
|
|
}
|
|
|
|
#else /* MAGIC_WRAPPER */
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* main --
|
|
*
|
|
* Top level of ext2spice.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
int
|
|
main(argc, argv)
|
|
int argc;
|
|
char *argv[];
|
|
{
|
|
|
|
int i,flatFlags;
|
|
char *inName;
|
|
FILE *f;
|
|
bool locDoSubckt;
|
|
|
|
esSpiceDevsMerged = 0;
|
|
|
|
static char *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 < MAXDEVTYPES ; i++ ) {
|
|
esFetInfo[i].resClassSource = NO_RESCLASS;
|
|
esFetInfo[i].resClassDrain = NO_RESCLASS;
|
|
esFetInfo[i].resClassSub = NO_RESCLASS;
|
|
esFetInfo[i].defSubs = NULL;
|
|
}
|
|
i = efBuildAddStr(EFDevTypes, &EFDevNumTypes, MAXDEVTYPES, "ndev");
|
|
esFetInfo[i].resClassSource = esFetInfo[i].resClassDrain = 0 ;
|
|
esFetInfo[i].resClassSub = NO_RESCLASS ;
|
|
esFetInfo[i].defSubs = "Gnd!";
|
|
i = efBuildAddStr(EFDevTypes, &EFDevNumTypes, MAXDEVTYPES, "pdev");
|
|
esFetInfo[i].resClassSource = esFetInfo[i].resClassDrain = 1 ;
|
|
esFetInfo[i].resClassSub = 8 ;
|
|
esFetInfo[i].defSubs = "Vdd!";
|
|
i = efBuildAddStr(EFDevTypes, &EFDevNumTypes, MAXDEVTYPES, "nmos");
|
|
esFetInfo[i].resClassSource = esFetInfo[i].resClassDrain = 0 ;
|
|
esFetInfo[i].resClassSub = NO_RESCLASS ;
|
|
esFetInfo[i].defSubs = "Gnd!";
|
|
i = efBuildAddStr(EFDevTypes, &EFDevNumTypes, MAXDEVTYPES, "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, spcmainArgs, (ClientData) NULL);
|
|
if (inName == NULL)
|
|
exit (1);
|
|
|
|
/*
|
|
* Initializations specific to this program.
|
|
* Make output name inName.spice if they weren't
|
|
* explicitly specified
|
|
*/
|
|
|
|
if (spcesOutName == spcesDefaultOut)
|
|
sprintf(spcesDefaultOut, "%s.spice", inName);
|
|
|
|
if ((esSpiceF = fopen(spcesOutName, "w")) == NULL)
|
|
{
|
|
perror(spcesOutName);
|
|
exit (1);
|
|
}
|
|
|
|
/* Read the hierarchical description of the input circuit */
|
|
if (EFReadFile(inName, TRUE, esDoExtResis, FALSE) == FALSE)
|
|
{
|
|
exit (1);
|
|
}
|
|
|
|
fprintf(esSpiceF, "* %s file created from %s.ext - technology: %s\n\n",
|
|
spiceFormats[esFormat], inName, EFTech);
|
|
if (esScale < 0)
|
|
fprintf(esSpiceF,".option scale=%gu\n\n", EFScale / 100.0);
|
|
else
|
|
esScale = EFScale / 100.0;
|
|
|
|
/* Convert the hierarchical description to a flat one */
|
|
flatFlags = EF_FLATNODES;
|
|
EFTrimFlags |= EF_TRIMGLOB ;
|
|
if (IS_FINITE_F(EFCapThreshold)) flatFlags |= EF_FLATCAPS;
|
|
if (esFormat == HSPICE) {
|
|
EFTrimFlags |= EF_TRIMLOCAL ;
|
|
HashInit(&subcktNameTable, 32, HT_STRINGKEYS);
|
|
#ifndef UNSORTED_SUBCKT
|
|
DQInit(&subcktNameQueue, 64);
|
|
#endif
|
|
}
|
|
EFFlatBuild(inName, flatFlags);
|
|
|
|
/* Determine if this is a subcircuit */
|
|
locDoSubckt = FALSE;
|
|
if (esDoSubckt == AUTO) {
|
|
if (efFlatRootDef->def_flags & DEF_SUBCIRCUIT)
|
|
locDoSubckt = TRUE;
|
|
}
|
|
if ((esDoSubckt == TRUE) || (locDoSubckt == TRUE))
|
|
topVisit(efFlatRootDef, 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);
|
|
|
|
initMask = ( esDistrJunct ) ? (unsigned long)0 : DEV_CONNECT_MASK ;
|
|
|
|
if ( esMergeDevsA || esMergeDevsC ) {
|
|
EFVisitDevs(devMergeVisit, (ClientData) NULL);
|
|
TxPrintf("Devs merged: %d\n", esSpiceDevsMerged);
|
|
esFMIndex = 0 ;
|
|
{
|
|
devMerge *p;
|
|
|
|
for ( p = devMergeList ; p != NULL ; p=p->next ) freeMagic(p);
|
|
}
|
|
} else if ( esDistrJunct )
|
|
EFVisitDevs(devDistJunctVisit, (ClientData) NULL);
|
|
EFVisitDevs(spcdevVisit, (ClientData) NULL);
|
|
initMask = (unsigned long) 0;
|
|
if (flatFlags & EF_FLATCAPS) {
|
|
(void) sprintf( esSpiceCapFormat, "C%%d %%s %%s %%.%dlffF\n",esCapAccuracy);
|
|
EFVisitCaps(spccapVisit, (ClientData) NULL);
|
|
}
|
|
EFVisitResists(spcresistVisit, (ClientData) NULL);
|
|
EFVisitSubcircuits(subcktVisit, (ClientData) NULL);
|
|
(void) sprintf( esSpiceCapFormat, "C%%d %%s GND %%.%dlffF%%s", esCapAccuracy);
|
|
EFVisitNodes(spcnodeVisit, (ClientData) NULL);
|
|
|
|
if ((esDoSubckt == TRUE) || (locDoSubckt == TRUE))
|
|
fprintf(esSpiceF, ".ends\n");
|
|
|
|
if (esFormat == HSPICE)
|
|
printSubcktDict();
|
|
|
|
EFFlatDone();
|
|
EFDone();
|
|
|
|
if (esSpiceF) fclose(esSpiceF);
|
|
|
|
TxPrintf("Memory used: %s\n", RunStats(RS_MEM, NULL, NULL));
|
|
exit (0);
|
|
}
|
|
|
|
#endif /* MAGIC_WRAPPER */
|
|
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* spcmainArgs --
|
|
*
|
|
* Process those arguments that are specific to ext2spice.
|
|
* Assumes that *pargv[0][0] is '-', indicating a flag
|
|
* argument.
|
|
*
|
|
* Results:
|
|
* None.
|
|
*
|
|
* Side effects:
|
|
* After processing an argument, updates *pargc and *pargv
|
|
* to point to after the argument.
|
|
*
|
|
* May initialize various global variables based on the
|
|
* arguments given to us.
|
|
*
|
|
* Exits in the event of an improper argument.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
int
|
|
spcmainArgs(pargc, pargv)
|
|
int *pargc;
|
|
char ***pargv;
|
|
{
|
|
char **argv = *pargv, *cp;
|
|
int argc = *pargc;
|
|
|
|
char usage_text[] = "Usage: ext2spice "
|
|
"[-B] [-o spicefile] [-M|-m] [-y cap_digits] "
|
|
"[-J flat|hier]\n"
|
|
"[-f spice2|spice3|hspice|ngspice] [-M] [-m] "
|
|
#ifdef MAGIC_WRAPPER
|
|
"[file]\n";
|
|
#else
|
|
"[-j device:sdRclass[/subRclass]/defaultSubstrate]\n"
|
|
"file\n\n or else see options to extcheck(1)\n";
|
|
#endif
|
|
|
|
switch (argv[0][1])
|
|
{
|
|
case 'd':
|
|
esDistrJunct = TRUE;
|
|
break;
|
|
case 'M':
|
|
esMergeDevsA = TRUE;
|
|
break;
|
|
case 'm':
|
|
esMergeDevsC = TRUE;
|
|
break;
|
|
case 'B':
|
|
esNoAttrs = TRUE;
|
|
break;
|
|
case 'F':
|
|
esDevNodesOnly = TRUE;
|
|
break;
|
|
case 'o':
|
|
if ((spcesOutName = ArgStr(&argc, &argv, "filename")) == NULL)
|
|
goto usage;
|
|
break;
|
|
case 'f': {
|
|
char *ftmp ;
|
|
|
|
if ((ftmp = ArgStr(&argc, &argv, "format")) == NULL)
|
|
goto usage;
|
|
if (strcasecmp(ftmp, "SPICE2") == 0)
|
|
esFormat = SPICE2;
|
|
else if (strcasecmp(ftmp, "SPICE3") == 0)
|
|
esFormat = SPICE3;
|
|
else if (strcasecmp(ftmp, "HSPICE") == 0)
|
|
{
|
|
esFormat = HSPICE;
|
|
esScale = -1.0;
|
|
}
|
|
else if (strcasecmp(ftmp, "NGSPICE") == 0)
|
|
esFormat = NGSPICE;
|
|
else goto usage;
|
|
break;
|
|
}
|
|
case 'J':
|
|
{
|
|
char *ftmp ;
|
|
|
|
if ((ftmp = ArgStr(&argc, &argv, "hierAP_SD")) == NULL)
|
|
goto usage;
|
|
if ( strcasecmp(ftmp, "HIER") == 0 )
|
|
esHierAP = TRUE ;
|
|
else if ( strcasecmp(ftmp, "FLAT") == 0 )
|
|
esHierAP = FALSE ;
|
|
else goto usage;
|
|
|
|
break;
|
|
}
|
|
case 'y': {
|
|
char *t;
|
|
|
|
if (( t = ArgStr(&argc, &argv, "cap-accuracy") ) == NULL)
|
|
goto usage;
|
|
esCapAccuracy = atoi(t);
|
|
break;
|
|
}
|
|
#ifndef MAGIC_WRAPPER
|
|
case 'j':
|
|
{
|
|
char *rp, subsNode[80] ;
|
|
int ndx, rClass, rClassSub = -1;
|
|
|
|
if ((cp = ArgStr(&argc,&argv,"resistance class")) == NULL)
|
|
goto usage;
|
|
if ( (rp = (char *) strchr(cp,':')) == NULL )
|
|
goto usage;
|
|
else *rp++ = '\0';
|
|
if ( sscanf(rp, "%d/%d/%s", &rClass, &rClassSub, subsNode) != 3 ) {
|
|
rClassSub = -1 ;
|
|
if ( sscanf(rp, "%d/%s", &rClass, subsNode) != 2 ) goto usage;
|
|
}
|
|
ndx = efBuildAddStr(EFDevTypes, &EFDevNumTypes, MAXDEVTYPES, cp);
|
|
esFetInfo[ndx].resClassSource = esFetInfo[ndx].resClassDrain = rClass;
|
|
esFetInfo[ndx].resClassSub = rClassSub;
|
|
if ( ((1<<rClass) & DEV_CONNECT_MASK) ||
|
|
((1<<rClass) & DEV_CONNECT_MASK) ) {
|
|
TxError("Oops it seems that you have 31\n");
|
|
TxError("resistance classes. You will need to recompile");
|
|
TxError("the extflat package and change ext2sim/spice\n");
|
|
TxError("DEV_CONNECT_MASK and or nodeClient\n");
|
|
exit (1);
|
|
}
|
|
esFetInfo[ndx].defSubs = (char *)mallocMagic((unsigned)(strlen(subsNode)+1));
|
|
strcpy(esFetInfo[ndx].defSubs,subsNode);
|
|
TxError("info: dev %s(%d) srcRclass=%d drnRclass=%d subRclass=%d dSub=%s\n",
|
|
cp, ndx, esFetInfo[ndx].resClassSource, esFetInfo[ndx].resClassDrain,
|
|
esFetInfo[ndx].resClassSub, esFetInfo[ndx].defSubs);
|
|
break;
|
|
}
|
|
#endif /* MAGIC_WRAPPER */
|
|
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);
|
|
|
|
#ifdef MAGIC_WRAPPER
|
|
return 1;
|
|
#else
|
|
exit (1);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* SpiceGetNode --
|
|
*
|
|
* function to find a node given its hierarchical prefix and suffix
|
|
*
|
|
* Results:
|
|
* a pointer to the node struct or NULL
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
EFNode *
|
|
SpiceGetNode(prefix, suffix)
|
|
HierName *prefix;
|
|
HierName *suffix;
|
|
{
|
|
HashEntry *he;
|
|
EFNodeName *nn;
|
|
|
|
he = EFHNConcatLook(prefix, suffix, "output");
|
|
if (he == NULL) return NULL;
|
|
nn = (EFNodeName *) HashGetValue(he);
|
|
if (nn == NULL) return NULL;
|
|
return(nn->efnn_node);
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* extHierSDAttr --
|
|
* Check if the attribute of the argument dev_terminal or the global
|
|
* settings are such that we want a hierarchical extraction of its S/D
|
|
*
|
|
*
|
|
* Results:
|
|
* TRUE or FALSE
|
|
*
|
|
* Side effects:
|
|
* None.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
bool extHierSDAttr(term)
|
|
DevTerm *term;
|
|
{
|
|
bool r = esHierAP;
|
|
|
|
if (term->dterm_attrs)
|
|
{
|
|
if (Match(ATTR_HIERAP, term->dterm_attrs) != FALSE)
|
|
r = TRUE;
|
|
else if (Match(ATTR_FLATAP, term->dterm_attrs) != FALSE)
|
|
r = FALSE;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* subcktVisit --
|
|
*
|
|
* Procedure to output a subcircuit definition to the .spice file.
|
|
* Called by EFVisitSubcircuits().
|
|
*
|
|
* Results:
|
|
* Returns 0 always.
|
|
*
|
|
* Side effects:
|
|
* Writes to the file esSpiceF.
|
|
*
|
|
* Format of a .spice subcircuit call:
|
|
*
|
|
* X%d node1 node2 ... noden name
|
|
*
|
|
* where
|
|
* node1 node2 ... noden are the nodes connecting to the ports of
|
|
* the subcircuit. "name" is the name of the subcircuit. It is
|
|
* assumed that the definition of "name" (.defs name ... .ends)
|
|
* exists elsewhere and will be appended to the SPICE deck prior
|
|
* to simulation (as is also assumed for device models).
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
int
|
|
subcktVisit(use, hierName, is_top)
|
|
Use *use;
|
|
HierName *hierName;
|
|
bool is_top; /* TRUE if this is the top-level cell */
|
|
{
|
|
EFNode *snode;
|
|
Def *def = use->use_def;
|
|
EFNodeName *nodeName;
|
|
int portorder, portmax, portidx, imp_max, 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 = EFTrimFlags;
|
|
EFTrimFlags = 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);
|
|
EFTrimFlags = 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, &imp_max);
|
|
|
|
if (portmax < 0)
|
|
{
|
|
/* No port order declared; print them as we see them. */
|
|
/* This shouldn't happen for proper .ext files written */
|
|
/* by the magic extractor. */
|
|
|
|
for (snode = (EFNode *) def->def_firstn.efnode_next;
|
|
snode != &def->def_firstn;
|
|
snode = (EFNode *) snode->efnode_next)
|
|
{
|
|
if (snode->efnode_flags & EF_PORT)
|
|
for (nodeName = snode->efnode_name; nodeName != NULL;
|
|
nodeName = nodeName->efnn_next)
|
|
if (nodeName->efnn_port >= 0)
|
|
{
|
|
portmax++;
|
|
if (tchars > 80)
|
|
{
|
|
fprintf(esSpiceF, "\n+");
|
|
tchars = 1;
|
|
}
|
|
tchars += spcdevOutNode(hierName, nodeName->efnn_hier,
|
|
"subcircuit", esSpiceF);
|
|
}
|
|
}
|
|
|
|
/* Look for all implicit substrate connections that are */
|
|
/* declared as local node names, and put them last. */
|
|
|
|
for (snode = (EFNode *) def->def_firstn.efnode_next;
|
|
snode != &def->def_firstn;
|
|
snode = (EFNode *) snode->efnode_next)
|
|
{
|
|
if (snode->efnode_flags & EF_SUBS_PORT)
|
|
{
|
|
nodeName = snode->efnode_name;
|
|
if (nodeName->efnn_port < 0)
|
|
nodeName->efnn_port = ++portmax;
|
|
|
|
/* This is not a hierarchical name or node! */
|
|
EFHNSprintf(stmp, nodeName->efnn_hier);
|
|
if (tchars > 80)
|
|
{
|
|
fprintf(esSpiceF, "\n+");
|
|
tchars = 1;
|
|
}
|
|
fprintf(esSpiceF, " %s", stmp);
|
|
tchars += (1 + strlen(stmp));
|
|
}
|
|
}
|
|
}
|
|
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;
|
|
|
|
for (snode = (EFNode *) def->def_firstn.efnode_next;
|
|
snode != &def->def_firstn;
|
|
snode = (EFNode *) snode->efnode_next)
|
|
{
|
|
if (!(snode->efnode_flags & EF_PORT)) continue;
|
|
for (nodeName = snode->efnode_name; nodeName != NULL;
|
|
nodeName = nodeName->efnn_next)
|
|
{
|
|
EFNodeName *nn;
|
|
HashEntry *he;
|
|
char *pname;
|
|
|
|
portidx = nodeName->efnn_port;
|
|
if (portidx < 0) continue;
|
|
if (nodeList[portidx] == NULL)
|
|
{
|
|
nodeList[portidx] = nodeName;
|
|
}
|
|
else if (EFHNBest(nodeName->efnn_hier, nodeList[portidx]->efnn_hier))
|
|
{
|
|
nodeList[portidx] = nodeName;
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
else
|
|
{
|
|
// As port indexes do not have to be contiguous, this does not
|
|
// necessarily indicate an error condition. No need to report?
|
|
// TxError("No port connection on port %d; need to resolve.\n", portidx);
|
|
}
|
|
}
|
|
freeMagic(nodeList);
|
|
|
|
/* Look for all implicit substrate connections that are */
|
|
/* declared as local node names, and put them last. */
|
|
|
|
portorder = portmax;
|
|
while (portorder <= imp_max)
|
|
{
|
|
for (snode = (EFNode *) def->def_firstn.efnode_next;
|
|
snode != &def->def_firstn;
|
|
snode = (EFNode *) snode->efnode_next)
|
|
{
|
|
if (!(snode->efnode_flags & EF_SUBS_PORT)) continue;
|
|
nodeName = snode->efnode_name;
|
|
if (nodeName->efnn_port == portorder)
|
|
{
|
|
/* This is not a hierarchical name or node! */
|
|
EFHNSprintf(stmp, nodeName->efnn_hier);
|
|
if (tchars > 80)
|
|
{
|
|
fprintf(esSpiceF, "\n+");
|
|
tchars = 1;
|
|
}
|
|
fprintf(esSpiceF, " %s", stmp);
|
|
tchars += (1 + strlen(stmp));
|
|
}
|
|
}
|
|
portorder++;
|
|
}
|
|
}
|
|
|
|
/* SPICE subcircuit names must begin with A-Z. This will also be */
|
|
/* enforced when writing X subcircuit calls. */
|
|
subcktname = def->def_name;
|
|
while (!isalpha(*subcktname)) subcktname++;
|
|
|
|
if (tchars > 80) fprintf(esSpiceF, "\n+");
|
|
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.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
int
|
|
subcktUndef(use, hierName, is_top)
|
|
Use *use;
|
|
HierName *hierName;
|
|
bool is_top; /* TRUE if this is the top-level cell */
|
|
{
|
|
Def *def = use->use_def;
|
|
|
|
def->def_flags &= ~(DEF_SUBCIRCUIT);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* topVisit --
|
|
*
|
|
* Procedure to output a subcircuit definition to the .spice file.
|
|
*
|
|
* Results:
|
|
* None.
|
|
*
|
|
* Side effects:
|
|
* Writes to the file esSpiceF.
|
|
*
|
|
* Format of a .spice subcircuit definition:
|
|
*
|
|
* .subckt name node1 node2 ... noden
|
|
*
|
|
* where
|
|
* node1 node2 ... noden are the nodes connecting to the ports of
|
|
* the subcircuit. "name" is the name of the cell def. 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, doStub)
|
|
Def *def;
|
|
bool doStub;
|
|
{
|
|
EFNode *snode, *basenode;
|
|
EFNodeName *sname, *nodeName;
|
|
HashSearch hs;
|
|
HashEntry *he, *hep;
|
|
HashTable portNameTable;
|
|
int portorder, portmax, tchars;
|
|
DevParam *plist, *pptr;
|
|
char *instname;
|
|
char *subcktname;
|
|
char *pname;
|
|
|
|
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;
|
|
while (!isalpha(*subcktname)) 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;
|
|
for (nodeName = sname; nodeName != NULL; nodeName = nodeName->efnn_next)
|
|
{
|
|
portorder = nodeName->efnn_port;
|
|
if (portorder > portmax) portmax = portorder;
|
|
}
|
|
}
|
|
|
|
if (portmax < 0)
|
|
{
|
|
/* No port order declared; print them and number them */
|
|
/* as we encounter them. This normally happens only */
|
|
/* when writing hierarchical decks for LVS. */
|
|
|
|
portorder = 0;
|
|
|
|
HashStartSearch(&hs);
|
|
while (he = HashNext(&def->def_nodes, &hs))
|
|
{
|
|
sname = (EFNodeName *) HashGetValue(he);
|
|
if (sname == NULL) continue;
|
|
snode = sname->efnn_node;
|
|
|
|
if (snode->efnode_flags & EF_PORT)
|
|
{
|
|
pname = nodeSpiceName(snode->efnode_name->efnn_hier, &basenode);
|
|
if (HashLookOnly(&portNameTable, pname) == NULL)
|
|
{
|
|
hep = HashFind(&portNameTable, pname);
|
|
if (basenode->efnode_name->efnn_port < 0)
|
|
{
|
|
if (tchars > 80)
|
|
{
|
|
/* Line continuation */
|
|
fprintf(esSpiceF, "\n+");
|
|
tchars = 1;
|
|
}
|
|
fprintf(esSpiceF, " %s", pname);
|
|
tchars += strlen(pname) + 1;
|
|
basenode->efnode_name->efnn_port = portorder++;
|
|
}
|
|
snode->efnode_name->efnn_port = basenode->efnode_name->efnn_port;
|
|
HashSetValue(hep,
|
|
(ClientData)(pointertype)snode->efnode_name->efnn_port);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Port numbers need not start at zero or be contiguous. */
|
|
/* They will be printed in numerical order. */
|
|
|
|
portorder = 0;
|
|
while (portorder <= portmax)
|
|
{
|
|
HashStartSearch(&hs);
|
|
while (he = HashNext(&def->def_nodes, &hs))
|
|
{
|
|
char stmp[MAX_STR_SIZE];
|
|
int portidx;
|
|
EFNodeName *unnumbered;
|
|
|
|
sname = (EFNodeName *) HashGetValue(he);
|
|
if (sname == NULL) continue; /* Should not happen */
|
|
snode = sname->efnn_node;
|
|
|
|
if ((!snode) || (!(snode->efnode_flags & EF_PORT))) continue;
|
|
|
|
for (nodeName = sname; nodeName != NULL; nodeName = nodeName->efnn_next)
|
|
{
|
|
portidx = nodeName->efnn_port;
|
|
if (portidx == portorder)
|
|
{
|
|
if (tchars > 80)
|
|
{
|
|
/* Line continuation */
|
|
fprintf(esSpiceF, "\n+");
|
|
tchars = 1;
|
|
}
|
|
// 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.
|
|
|
|
if (def->def_flags & DEF_ABSTRACT)
|
|
{
|
|
EFHNSprintf(stmp, nodeName->efnn_hier);
|
|
pname = stmp;
|
|
}
|
|
else
|
|
pname = nodeSpiceName(snode->efnode_name->efnn_hier, NULL);
|
|
|
|
if (HashLookOnly(&portNameTable, pname) == NULL)
|
|
{
|
|
hep = HashFind(&portNameTable, pname);
|
|
HashSetValue(hep,
|
|
(ClientData)(pointertype)nodeName->efnn_port);
|
|
fprintf(esSpiceF, " %s", pname);
|
|
tchars += strlen(pname) + 1;
|
|
}
|
|
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.
|
|
|
|
hep = HashFind(&portNameTable, pname);
|
|
nodeName->efnn_port = (int)(pointertype)HashGetValue(hep);
|
|
}
|
|
break;
|
|
}
|
|
else if (portidx < 0)
|
|
unnumbered = nodeName;
|
|
}
|
|
if (nodeName != NULL)
|
|
break;
|
|
else if (portidx < 0)
|
|
{
|
|
// Node has not been assigned a port number.
|
|
// Give it one unless this is a black-box circuit
|
|
// and "ext2spice blackbox on" is in effect.
|
|
|
|
if (esDoBlackBox == FALSE || !(def->def_flags & DEF_ABSTRACT))
|
|
unnumbered->efnn_port = ++portmax;
|
|
}
|
|
}
|
|
portorder++;
|
|
}
|
|
}
|
|
HashKill(&portNameTable);
|
|
|
|
/* Add all implicitly-defined local substrate node names */
|
|
|
|
if (!doStub)
|
|
{
|
|
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_SUBS_PORT))
|
|
{
|
|
if (snode->efnode_name->efnn_port < 0)
|
|
{
|
|
char stmp[MAX_STR_SIZE];
|
|
|
|
if (tchars > 80)
|
|
{
|
|
/* Line continuation */
|
|
fprintf(esSpiceF, "\n+");
|
|
tchars = 1;
|
|
}
|
|
/* This is not a hierarchical name or node! */
|
|
EFHNSprintf(stmp, snode->efnode_name->efnn_hier);
|
|
fprintf(esSpiceF, " %s", stmp);
|
|
snode->efnode_name->efnn_port = portorder++;
|
|
tchars += strlen(stmp) + 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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");
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* 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, hierName, scale, l, w, sdM)
|
|
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 hierD;
|
|
DevParam *plist;
|
|
int parmval;
|
|
EFNode *dnode, *subnodeFlat = NULL;
|
|
|
|
bool extHierSDAttr();
|
|
|
|
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", parmval * scale * scale
|
|
* esScale * esScale * plist->parm_scale
|
|
* 1E-12);
|
|
else
|
|
fprintf(esSpiceF, "%gp", parmval * 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(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(dnode, resclass, scale, plist->parm_name, NULL,
|
|
sdM, esSpiceF, w);
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
case 'p':
|
|
// 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_perim;
|
|
if (esScale < 0)
|
|
fprintf(esSpiceF, "%g", parmval * scale);
|
|
else if (plist->parm_scale != 1.0)
|
|
fprintf(esSpiceF, "%g", parmval * scale
|
|
* esScale * plist->parm_scale * 1E-6);
|
|
else
|
|
fprintf(esSpiceF, "%gu", parmval * 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(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(dnode, resclass, scale, NULL, plist->parm_name,
|
|
sdM, esSpiceF, w);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'l':
|
|
fprintf(esSpiceF, " %s=", plist->parm_name);
|
|
if (esScale < 0)
|
|
fprintf(esSpiceF, "%g", l * scale);
|
|
else if (plist->parm_scale != 1.0)
|
|
fprintf(esSpiceF, "%g", l * scale * esScale
|
|
* plist->parm_scale * 1E-6);
|
|
else
|
|
fprintf(esSpiceF, "%gu", l * scale * esScale);
|
|
break;
|
|
case 'w':
|
|
fprintf(esSpiceF, " %s=", plist->parm_name);
|
|
if (esScale < 0)
|
|
fprintf(esSpiceF, "%g", w * scale);
|
|
else if (plist->parm_scale != 1.0)
|
|
fprintf(esSpiceF, "%g", w * scale * esScale
|
|
* plist->parm_scale * 1E-6);
|
|
else
|
|
fprintf(esSpiceF, "%gu", w * scale * esScale);
|
|
break;
|
|
case 's':
|
|
fprintf(esSpiceF, " %s=", plist->parm_name);
|
|
subnodeFlat = 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", dev->dev_rect.r_xbot * scale
|
|
* esScale * plist->parm_scale * 1E-6);
|
|
else
|
|
fprintf(esSpiceF, "%gu", dev->dev_rect.r_xbot * scale
|
|
* esScale);
|
|
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", dev->dev_rect.r_ybot * scale
|
|
* esScale * plist->parm_scale * 1E-6);
|
|
else
|
|
fprintf(esSpiceF, "%gu", dev->dev_rect.r_ybot * scale
|
|
* esScale);
|
|
break;
|
|
case 'r':
|
|
fprintf(esSpiceF, " %s=", plist->parm_name);
|
|
fprintf(esSpiceF, "%f", (double)(dev->dev_res));
|
|
break;
|
|
case 'c':
|
|
fprintf(esSpiceF, " %s=", plist->parm_name);
|
|
fprintf(esSpiceF, "%ff", (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, hierName, scale, term1, term2, has_model, l, w, dscale)
|
|
Dev *dev; /* Dev being output */
|
|
HierName *hierName; /* Hierarchical path down to this dev */
|
|
float scale; /* Scale transform for output */
|
|
DevTerm *term1, *term2; /* Terminals of the device */
|
|
bool has_model; /* Is this a modeled resistor? */
|
|
int l, w; /* Device length and width */
|
|
int dscale; /* Device scaling (for split resistors) */
|
|
{
|
|
float sdM ;
|
|
char name[12], devchar;
|
|
|
|
/* Resistor is "Rnnn term1 term2 value" */
|
|
/* extraction sets two terminals, which are assigned */
|
|
/* term1=gate term2=source by the above code. */
|
|
/* extracted units are Ohms; output is in Ohms */
|
|
|
|
spcdevOutNode(hierName, term1->dterm_node->efnode_name->efnn_hier,
|
|
name, esSpiceF);
|
|
spcdevOutNode(hierName, term2->dterm_node->efnode_name->efnn_hier,
|
|
name, esSpiceF);
|
|
|
|
sdM = getCurDevMult();
|
|
|
|
/* SPICE has two resistor types. If the "name" (EFDevTypes) is */
|
|
/* "None", the simple resistor type is used, and a value given. */
|
|
/* If not, the "semiconductor resistor" is used, and L and W */
|
|
/* and the device name are output. */
|
|
|
|
if (!has_model)
|
|
{
|
|
fprintf(esSpiceF, " %f", ((double)(dev->dev_res)
|
|
/ (double)(dscale)) / (double)sdM);
|
|
spcWriteParams(dev, hierName, scale, l, w, sdM);
|
|
}
|
|
else
|
|
{
|
|
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=%gu l=%gu",
|
|
w * scale * esScale,
|
|
((l * scale * esScale) / dscale));
|
|
|
|
spcWriteParams(dev, hierName, scale, l, w, sdM);
|
|
if (sdM != 1.0)
|
|
fprintf(esSpiceF, " M=%g", sdM);
|
|
}
|
|
}
|
|
|
|
/* Report if device at index n has been deleted due to merging */
|
|
|
|
bool
|
|
devIsKilled(n)
|
|
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(f)
|
|
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(i, f)
|
|
int i;
|
|
float f;
|
|
{
|
|
esFMult[i] = f;
|
|
}
|
|
|
|
/* Get the multiplier value of the device at the current index esFMIndex */
|
|
|
|
float
|
|
getCurDevMult()
|
|
{
|
|
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 typycally 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.
|
|
*
|
|
*/
|
|
void
|
|
swapDrainSource(dev, source, drain)
|
|
Dev *dev;
|
|
DevTerm **source, **drain;
|
|
{
|
|
DevParam *plist;
|
|
|
|
/* swap drain/source ordering */
|
|
if (drain) *drain = &dev->dev_terms[1];
|
|
if (source) *source = &dev->dev_terms[2];
|
|
|
|
/* Swap drain/source-related parameters. Note that the parameter */
|
|
/* *definitions* are swapped, so if this is done, it must be */
|
|
/* reverted before the next device is processed. */
|
|
|
|
plist = efGetDeviceParams(EFDevTypes[dev->dev_type]);
|
|
while (plist != NULL)
|
|
{
|
|
// Diagnostic
|
|
// TxPrintf(" * param: %s; type: %c%c\n", plist->parm_name, plist->parm_type[0], plist->parm_type[1]);
|
|
|
|
/* Swap drain/source parameters only */
|
|
if (!(strcmp(plist->parm_type, "a1")) || !(strcmp(plist->parm_type, "p1")))
|
|
plist->parm_type[1] = '0' + 2;
|
|
|
|
else if (!(strcmp(plist->parm_type, "a2")) || !(strcmp(plist->parm_type, "p2")))
|
|
plist->parm_type[1] = '0' + 1;
|
|
|
|
/* move pointer */
|
|
plist = plist->parm_next;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* spcdevVisit --
|
|
*
|
|
* Procedure to output a single dev to the .spice file.
|
|
* Called by EFVisitDevs().
|
|
*
|
|
* Results:
|
|
* Returns 0 always.
|
|
*
|
|
* Side effects:
|
|
* Writes to the file esSpiceF.
|
|
*
|
|
* Format of a .spice dev line:
|
|
*
|
|
* M%d drain gate source substrate type w=w l=l * x y
|
|
* + ad= pd= as= ps= * asub= psub=
|
|
* **devattr g= s= d=
|
|
*
|
|
* where
|
|
* type is a name identifying this type of transistor
|
|
* other types of transistors are extracted with
|
|
* an M card but it should be easy to turn them to whatever
|
|
* you want.
|
|
* gate, source, and drain are the nodes to which these three
|
|
* terminals connect
|
|
* l, w are the length and width of the channel
|
|
* x, y are the x, y coordinates of a point within the channel.
|
|
* g=, s=, d= are the (optional) attributes; if present, each
|
|
* is followed by a comma-separated list of attributes.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
int
|
|
spcdevVisit(dev, hc, scale, trans)
|
|
Dev *dev; /* Dev being output */
|
|
HierContext *hc; /* Hierarchical context down to this dev */
|
|
float scale; /* Scale transform for output */
|
|
Transform *trans; /* (unused) */
|
|
{
|
|
DevParam *plist, *pptr;
|
|
DevTerm *gate, *source, *drain;
|
|
EFNode *subnode, *snode, *dnode, *subnodeFlat = NULL;
|
|
int l, w, i, parmval;
|
|
bool subAP= FALSE, hierS, hierD, extHierSDAttr(), swapped = FALSE ;
|
|
float sdM;
|
|
char name[12], devchar;
|
|
bool has_model = TRUE;
|
|
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)
|
|
{
|
|
/* 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, &source, &drain);
|
|
swapped = True;
|
|
}
|
|
else
|
|
drain = &dev->dev_terms[2];
|
|
}
|
|
subnode = dev->dev_subsnode;
|
|
|
|
/* Check for minimum number of terminals. */
|
|
|
|
switch(dev->dev_class)
|
|
{
|
|
case DEV_SUBCKT:
|
|
case DEV_RSUBCKT:
|
|
case DEV_CSUBCKT:
|
|
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)
|
|
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_CAP:
|
|
case DEV_CAPREV:
|
|
devchar = 'C';
|
|
break;
|
|
case DEV_SUBCKT:
|
|
case DEV_RSUBCKT:
|
|
case DEV_CSUBCKT:
|
|
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_SUBCKT:
|
|
case DEV_RSUBCKT:
|
|
case DEV_CSUBCKT:
|
|
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:
|
|
|
|
/* BJT is "Qnnn collector emitter base model" */
|
|
/* extraction sets collector=subnode, emitter=gate, base=drain */
|
|
|
|
sprintf(name, "fet");
|
|
spcdevOutNode(hierName, subnode->efnode_name->efnn_hier, name, esSpiceF);
|
|
spcdevOutNode(hierName, gate->dterm_node->efnode_name->efnn_hier,
|
|
name, esSpiceF);
|
|
|
|
/* fix mixed up drain/source for bjts hace 2/2/99 */
|
|
if (gate->dterm_node->efnode_name->efnn_hier ==
|
|
source->dterm_node->efnode_name->efnn_hier)
|
|
spcdevOutNode(hierName, drain->dterm_node->efnode_name->efnn_hier,
|
|
name, esSpiceF);
|
|
else
|
|
spcdevOutNode(hierName, source->dterm_node->efnode_name->efnn_hier,
|
|
name, esSpiceF);
|
|
|
|
fprintf(esSpiceF, " %s", EFDevTypes[dev->dev_type]);
|
|
sdM = getCurDevMult();
|
|
spcWriteParams(dev, hierName, scale, l, w, sdM);
|
|
break;
|
|
|
|
case DEV_MSUBCKT:
|
|
|
|
/* 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:
|
|
|
|
/* 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_MSUBCKT)
|
|
{
|
|
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);
|
|
}
|
|
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*, which may define as */
|
|
/* many terminal types as it wants. */
|
|
|
|
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);
|
|
}
|
|
fprintf(esSpiceF, " %s", EFDevTypes[dev->dev_type]);
|
|
|
|
/* Write all requested parameters to the subcircuit call. */
|
|
|
|
sdM = getCurDevMult();
|
|
spcWriteParams(dev, hierName, scale, l, w, sdM);
|
|
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. */
|
|
|
|
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);
|
|
}
|
|
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);
|
|
|
|
fprintf(esSpiceF, " %s", EFDevTypes[dev->dev_type]);
|
|
sdM = getCurDevMult();
|
|
spcWriteParams(dev, hierName, scale, l, w, sdM);
|
|
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);
|
|
|
|
fprintf(esSpiceF, " %s", EFDevTypes[dev->dev_type]);
|
|
sdM = getCurDevMult();
|
|
spcWriteParams(dev, hierName, scale, l, w, sdM);
|
|
break;
|
|
|
|
case DEV_CAP:
|
|
|
|
/* Capacitor is "Cnnn top bottom value" */
|
|
/* extraction sets top=gate bottom=source */
|
|
/* extracted units are fF; output is in fF */
|
|
|
|
spcdevOutNode(hierName, gate->dterm_node->efnode_name->efnn_hier,
|
|
name, esSpiceF);
|
|
spcdevOutNode(hierName, source->dterm_node->efnode_name->efnn_hier,
|
|
name, esSpiceF);
|
|
|
|
sdM = getCurDevMult();
|
|
|
|
/* SPICE has two capacitor types. If the "name" (EFDevTypes) is */
|
|
/* "None", the simple capacitor type is used, and a value given. */
|
|
/* If not, the "semiconductor capacitor" is used, and L and W */
|
|
/* and the device name are output. */
|
|
|
|
if (!has_model)
|
|
{
|
|
fprintf(esSpiceF, " %ffF", (double)sdM *
|
|
(double)(dev->dev_cap));
|
|
spcWriteParams(dev, hierName, scale, l, w, sdM);
|
|
}
|
|
else
|
|
{
|
|
fprintf(esSpiceF, " %s", EFDevTypes[dev->dev_type]);
|
|
|
|
if (esScale < 0)
|
|
fprintf(esSpiceF, " w=%g l=%g", w*scale, l*scale);
|
|
else
|
|
fprintf(esSpiceF, " w=%gu l=%gu",
|
|
w * scale * esScale,
|
|
l * scale * esScale);
|
|
|
|
spcWriteParams(dev, hierName, scale, l, w, sdM);
|
|
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; output is in 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, " %ffF", (double)sdM *
|
|
(double)(dev->dev_cap));
|
|
spcWriteParams(dev, hierName, scale, l, w, sdM);
|
|
}
|
|
else
|
|
{
|
|
fprintf(esSpiceF, " %s", EFDevTypes[dev->dev_type]);
|
|
|
|
if (esScale < 0)
|
|
fprintf(esSpiceF, " w=%g l=%g", w*scale, l*scale);
|
|
else
|
|
fprintf(esSpiceF, " w=%gu l=%gu",
|
|
w * scale * esScale,
|
|
l * scale * esScale);
|
|
|
|
spcWriteParams(dev, hierName, scale, l, w, sdM);
|
|
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=%gu l=%gu",
|
|
w * scale * esScale,
|
|
l * scale * esScale);
|
|
|
|
spcWriteParams(dev, hierName, scale, l, w, sdM);
|
|
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(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(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(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)
|
|
{
|
|
if (gate->dterm_attrs || source->dterm_attrs || drain->dterm_attrs)
|
|
fprintf(esSpiceF,"\n**devattr");
|
|
if (gate->dterm_attrs)
|
|
fprintf(esSpiceF, " g=%s", gate->dterm_attrs);
|
|
if (source->dterm_attrs)
|
|
fprintf(esSpiceF, " s=%s", source->dterm_attrs);
|
|
if (drain->dterm_attrs)
|
|
fprintf(esSpiceF, " d=%s", drain->dterm_attrs);
|
|
}
|
|
break;
|
|
}
|
|
fprintf(esSpiceF, "\n");
|
|
|
|
/* If S/D parameters on a subcircuit were swapped, put them back */
|
|
if (swapped) swapDrainSource(dev, NULL, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* spcdevSubstrate -
|
|
*
|
|
* Output the node name of the substrate of a dev. If the suffix is the
|
|
* same as the default dont go looking for it just output the default
|
|
* (trimmed appropriately). Otherwise look it up ..
|
|
*
|
|
* Results:
|
|
* NULL if not found or the default substrate or the node pointer
|
|
* otherwise (might be reused to output area and perimeter of
|
|
* the substrate).
|
|
*
|
|
* Side effects:
|
|
* Might allocate the nodeClient for the node through nodeSpiceName.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
EFNode *spcdevSubstrate( prefix, suffix, type, outf)
|
|
HierName *prefix, *suffix;
|
|
int type;
|
|
FILE *outf;
|
|
{
|
|
HashEntry *he;
|
|
EFNodeName *nn;
|
|
char *suf;
|
|
|
|
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)
|
|
fprintf(outf, "%s", nodeSpiceName(nn->efnn_node->efnode_name->efnn_hier,
|
|
NULL));
|
|
|
|
/* Mark node as visited */
|
|
if ((nodeClient *)nn->efnn_node->efnode_client == (ClientData)NULL)
|
|
initNodeClientHier(nn->efnn_node);
|
|
|
|
if (!esDistrJunct)
|
|
((nodeClient *)nn->efnn_node->efnode_client)->m_w.visitMask |=
|
|
DEV_CONNECT_MASK;
|
|
return nn->efnn_node;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* spcnAP, spcnAPHier --
|
|
*
|
|
* Output the area perimeter of the node with type type if it has not
|
|
* been visited.
|
|
* The spcnAPHier version outputs the area and perimeter only within the
|
|
* local subcell with hierarchical name hierName.
|
|
*
|
|
* 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(node, resClass, scale, asterm, psterm, m, outf, w)
|
|
EFNode *node;
|
|
int resClass;
|
|
float scale, m;
|
|
char *asterm, *psterm;
|
|
FILE *outf;
|
|
int w;
|
|
{
|
|
char afmt[15], pfmt[15];
|
|
float dsc;
|
|
|
|
if ((node == NULL) || (node->efnode_client == (ClientData)NULL))
|
|
{
|
|
TxError("spcnAP: major internal inconsistency\n");
|
|
return 1;
|
|
}
|
|
|
|
if (esScale < 0)
|
|
{
|
|
if (asterm) sprintf(afmt, " %s=%%g", asterm);
|
|
if (psterm) sprintf(pfmt, " %s=%%g", psterm);
|
|
}
|
|
else
|
|
{
|
|
if (asterm) sprintf(afmt, " %s=%%gp", asterm);
|
|
if (psterm) sprintf(pfmt, " %s=%%gu", psterm);
|
|
}
|
|
|
|
if (!esDistrJunct || w == -1) goto oldFmt;
|
|
|
|
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)
|
|
fprintf(outf, afmt,
|
|
node->efnode_pa[resClass].pa_area * scale * scale * dsc);
|
|
if (psterm)
|
|
fprintf(outf, pfmt,
|
|
node->efnode_pa[resClass].pa_perim * scale * dsc);
|
|
}
|
|
else
|
|
{
|
|
if (asterm)
|
|
fprintf(outf, afmt,
|
|
((float)node->efnode_pa[resClass].pa_area * scale * scale)
|
|
* esScale * esScale * dsc);
|
|
if (psterm)
|
|
fprintf(outf, pfmt,
|
|
((float)node->efnode_pa[resClass].pa_perim * scale)
|
|
* esScale * dsc);
|
|
}
|
|
|
|
return 0;
|
|
|
|
oldFmt:
|
|
if (resClass == NO_RESCLASS ||
|
|
beenVisited((nodeClient *)node->efnode_client, resClass))
|
|
scale = 0;
|
|
else
|
|
markVisited((nodeClient *)node->efnode_client, resClass);
|
|
|
|
if (esScale < 0)
|
|
{
|
|
if (asterm)
|
|
fprintf(outf, afmt,
|
|
node->efnode_pa[resClass].pa_area * scale * scale / m);
|
|
if (psterm)
|
|
fprintf(outf, pfmt,
|
|
node->efnode_pa[resClass].pa_perim * scale / m);
|
|
}
|
|
else
|
|
{
|
|
if (asterm)
|
|
fprintf(outf, afmt,
|
|
((float)node->efnode_pa[resClass].pa_area * scale * scale)
|
|
* esScale * esScale);
|
|
if (psterm)
|
|
fprintf(outf, pfmt,
|
|
((float)node->efnode_pa[resClass].pa_perim * scale)
|
|
* esScale);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int spcnAPHier(dterm, hierName, resClass, scale, asterm, psterm, m, outf)
|
|
DevTerm *dterm;
|
|
HierName *hierName;
|
|
int resClass;
|
|
float scale, m;
|
|
char *asterm, *psterm;
|
|
FILE *outf;
|
|
{
|
|
EFNode *node = dterm->dterm_node;
|
|
nodeClientHier *nc;
|
|
char afmt[15], pfmt[15];
|
|
|
|
if (esScale < 0)
|
|
{
|
|
sprintf(afmt," %s=%%g", asterm);
|
|
sprintf(pfmt," %s=%%g", psterm);
|
|
}
|
|
else
|
|
{
|
|
sprintf(afmt," %s=%%gp", asterm);
|
|
sprintf(pfmt," %s=%%gu", psterm);
|
|
}
|
|
if (node->efnode_client == (ClientData) NULL)
|
|
initNodeClientHier(node);
|
|
|
|
nc = (nodeClientHier *)node->efnode_client;
|
|
if (nc->lastPrefix != hierName)
|
|
{
|
|
clearVisited(nc);
|
|
nc->lastPrefix = hierName;
|
|
}
|
|
if (resClass == NO_RESCLASS ||
|
|
beenVisited((nodeClientHier *)node->efnode_client, resClass) )
|
|
scale = 0.0;
|
|
else
|
|
markVisited((nodeClientHier *)node->efnode_client, resClass);
|
|
|
|
if (esScale < 0)
|
|
{
|
|
fprintf(outf, afmt,
|
|
node->efnode_pa[resClass].pa_area * scale * scale / m);
|
|
fprintf(outf, pfmt,
|
|
node->efnode_pa[resClass].pa_perim * scale / m);
|
|
}
|
|
else
|
|
{
|
|
fprintf(outf, afmt,
|
|
((float)node->efnode_pa[resClass].pa_area * scale)
|
|
* esScale * esScale);
|
|
fprintf(outf, pfmt,
|
|
((float)node->efnode_pa[resClass].pa_perim * scale)
|
|
* esScale);
|
|
}
|
|
return 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(prefix, suffix, name, outf)
|
|
HierName *prefix;
|
|
HierName *suffix;
|
|
char *name;
|
|
FILE *outf;
|
|
{
|
|
HashEntry *he;
|
|
EFNodeName *nn;
|
|
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 */
|
|
if (!esDistrJunct)
|
|
((nodeClient *)nn->efnn_node->efnode_client)->m_w.visitMask |= DEV_CONNECT_MASK;
|
|
|
|
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).
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
int
|
|
spccapVisit(hierName1, hierName2, cap)
|
|
HierName *hierName1;
|
|
HierName *hierName2;
|
|
double cap;
|
|
{
|
|
cap = cap / 1000;
|
|
if (cap <= EFCapThreshold)
|
|
return 0;
|
|
|
|
fprintf(esSpiceF, esSpiceCapFormat ,esCapNum++,nodeSpiceName(hierName1, NULL),
|
|
nodeSpiceName(hierName2, NULL), cap);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* spcresistVisit --
|
|
*
|
|
* Procedure to output a single resistor to the .spice file.
|
|
* Called by EFVisitResists().
|
|
*
|
|
* Results:
|
|
* Returns 0 always.
|
|
*
|
|
* Side effects:
|
|
* Writes to the file esSpiceF. Increments esResNum.
|
|
*
|
|
* Format of a .spice resistor line:
|
|
*
|
|
* R%d node1 node2 res
|
|
*
|
|
* where
|
|
* node1, node2 are the terminals of the resistor
|
|
* res is the resistance in ohms (NOT milliohms)
|
|
*
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
int
|
|
spcresistVisit(hierName1, hierName2, res)
|
|
HierName *hierName1;
|
|
HierName *hierName2;
|
|
float res;
|
|
{
|
|
fprintf(esSpiceF, "R%d %s %s %g\n", esResNum++, nodeSpiceName(hierName1, NULL),
|
|
nodeSpiceName(hierName2, NULL), res / 1000.);
|
|
|
|
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(node, res, cap, resstr)
|
|
EFNode *node;
|
|
int res; // Unused
|
|
double cap; // Unused
|
|
char **resstr;
|
|
{
|
|
HierName *hierName;
|
|
char *nsn;
|
|
|
|
if (node->efnode_flags & EF_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
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
int
|
|
spcnodeVisit(node, res, cap)
|
|
EFNode *node;
|
|
int res;
|
|
double cap;
|
|
{
|
|
EFNodeName *nn;
|
|
HierName *hierName;
|
|
bool isConnected = FALSE;
|
|
char *fmt, *nsn;
|
|
EFAttr *ap;
|
|
|
|
if (node->efnode_client)
|
|
{
|
|
isConnected = (esDistrJunct) ?
|
|
(((nodeClient *)node->efnode_client)->m_w.widths != NULL) :
|
|
((((nodeClient *)node->efnode_client)->m_w.visitMask
|
|
& DEV_CONNECT_MASK) != 0);
|
|
}
|
|
if (!isConnected && esDevNodesOnly)
|
|
return 0;
|
|
|
|
/* 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, esSpiceCapFormat, esCapNum++, nsn, cap,
|
|
(isConnected) ? "\n" :
|
|
(esFormat == NGSPICE) ? " $ **FLOATING\n" :
|
|
" **FLOATING\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 */
|
|
int
|
|
nodeVisitDebug(node, res, cap)
|
|
EFNode *node;
|
|
int res;
|
|
double cap;
|
|
{
|
|
EFNodeName *nn;
|
|
HierName *hierName;
|
|
char *fmt, *nsn;
|
|
EFAttr *ap;
|
|
|
|
hierName = (HierName *) node->efnode_name->efnn_hier;
|
|
nsn = nodeSpiceName(hierName, NULL);
|
|
TxError("** %s (%x)\n", nsn, node);
|
|
|
|
printf("\t client.name=%s, client.m_w=%p\n",
|
|
((nodeClient *)node->efnode_client)->spiceNodeName,
|
|
((nodeClient *)node->efnode_client)->m_w.widths);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* nodeSpiceName --
|
|
* Find the real spice name for the node with hierarchical name hname.
|
|
* SPICE2 ==> numeric
|
|
* SPICE3 ==> full magic path
|
|
* HSPICE ==> less than 15 characters long
|
|
*
|
|
* Results:
|
|
* Returns the spice node name.
|
|
*
|
|
* Side effects:
|
|
* Allocates nodeClients for the node.
|
|
* Returns the node in the "rnode" pointer, if non-NULL.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
static char esTempName[MAX_STR_SIZE];
|
|
|
|
char *nodeSpiceName(hname, rnode)
|
|
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 EFTrimFlags control whether global (!) or local (#)
|
|
* suffixes are to be trimmed. Also substitutes \. with \@ if the
|
|
* format is hspice.
|
|
*
|
|
* Results:
|
|
* None.
|
|
*
|
|
* Side effects:
|
|
* Changes the area pointed to by str
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
int
|
|
EFHNSprintf(str, hierName)
|
|
char *str;
|
|
HierName *hierName;
|
|
{
|
|
bool trimGlob, trimLocal, convertComma, convertEqual, convertBrackets;
|
|
char *s, *cp, c;
|
|
char *efHNSprintfPrefix(HierName *, char *);
|
|
|
|
s = str;
|
|
if (hierName->hn_parent) str = efHNSprintfPrefix(hierName->hn_parent, str);
|
|
if (EFTrimFlags)
|
|
{
|
|
cp = hierName->hn_name;
|
|
trimGlob = (EFTrimFlags & EF_TRIMGLOB);
|
|
trimLocal = (EFTrimFlags & EF_TRIMLOCAL);
|
|
convertComma = (EFTrimFlags & EF_CONVERTCOMMA);
|
|
convertEqual = (EFTrimFlags & EF_CONVERTEQUAL);
|
|
convertBrackets = (EFTrimFlags & 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, str)
|
|
HierName *hierName;
|
|
char *str;
|
|
{
|
|
char *cp, c;
|
|
bool convertEqual = (EFTrimFlags & EF_CONVERTEQUAL) ? TRUE : FALSE;
|
|
bool convertComma = (EFTrimFlags & EF_CONVERTCOMMA) ? TRUE : FALSE;
|
|
bool convertBrackets = (EFTrimFlags & 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(s)
|
|
char *s;
|
|
{
|
|
char *p, *sf;
|
|
int l, snum = -1;
|
|
HashEntry *he;
|
|
static char map[MAX_STR_SIZE];
|
|
|
|
/*
|
|
* find the suffix
|
|
*/
|
|
l = strlen(s);
|
|
for (p = s + l; (p > s) && *p != '/'; p--);
|
|
if (p == s)
|
|
{
|
|
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()
|
|
{
|
|
HashSearch hs;
|
|
HashEntry *he;
|
|
|
|
fprintf(esSpiceF,"\n** hspice subcircuit dictionary\n");
|
|
|
|
#ifndef UNSORTED_SUBCKT
|
|
while ((he = (HashEntry *)DQPopFront(&subcktNameQueue)) != NULL)
|
|
#else
|
|
HashStartSearch(&hs);
|
|
while ((he = HashNext(&subcktNameTable, &hs)) != NULL)
|
|
#endif
|
|
fprintf(esSpiceF,"* x%"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(l, w, g, s, d, b, hn, dev)
|
|
float l, w;
|
|
EFNode *g, *s, *d, *b;
|
|
HierName *hn;
|
|
Dev *dev;
|
|
{
|
|
devMerge *fp;
|
|
|
|
fp = (devMerge *) mallocMagic((unsigned) (sizeof(devMerge)));
|
|
fp->l = l; fp->w = w;
|
|
fp->g = g; fp->s = s;
|
|
fp->d = d; fp->b = b;
|
|
fp->dev = dev;
|
|
fp->esFMIndex = esFMIndex;
|
|
fp->hierName = hn;
|
|
fp->next = NULL;
|
|
addDevMult(1.0);
|
|
|
|
return fp;
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* parallelDevs --
|
|
*
|
|
* Determine if two devs are in parallel
|
|
*
|
|
* Results:
|
|
* NOT_PARALLEL if not in parallel
|
|
* PARALLEL if parallel and an exact match
|
|
* ANTIPARALLEL if parallel but reversed source<->drain nodes
|
|
*
|
|
* Side effects:
|
|
* None.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
int
|
|
parallelDevs(f1, f2)
|
|
devMerge *f1, *f2;
|
|
{
|
|
/* If the devices are not in the same class, then */
|
|
/* they cannot be parallel. */
|
|
|
|
if (f1->dev->dev_class != f2->dev->dev_class)
|
|
return NOT_PARALLEL;
|
|
|
|
/* Can't merge devices with different models */
|
|
if (f1->dev->dev_type != f2->dev->dev_type)
|
|
return NOT_PARALLEL;
|
|
|
|
/* Class-dependent action */
|
|
switch(f1->dev->dev_class)
|
|
{
|
|
case DEV_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_RSUBCKT:
|
|
case DEV_CSUBCKT:
|
|
break;
|
|
}
|
|
return NOT_PARALLEL;
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* mergeAttr --
|
|
*
|
|
* merge two attribute strings
|
|
*
|
|
* Results:
|
|
* The merged strings
|
|
*
|
|
* Side effects:
|
|
* Might allocate and free memory.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
void
|
|
mergeAttr(a1, a2)
|
|
char **a1, **a2;
|
|
{
|
|
if (*a1 == NULL)
|
|
*a1 = *a2;
|
|
else
|
|
{
|
|
char *t;
|
|
int l1 = strlen(*a1);
|
|
int l2 = strlen(*a2);
|
|
t = (char *) mallocMagic((unsigned int)((l1 + l2) + 1));
|
|
t = (char *) strcat(*a1, *a2);
|
|
freeMagic(*a1);
|
|
*a1 = t;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* devMergeVisit --
|
|
* Visits each dev throu EFVisitDevs and finds if it is in parallel with
|
|
* any previously visited dev.
|
|
*
|
|
* Results:
|
|
* 0 always to keep the caller going.
|
|
*
|
|
* Side effects:
|
|
* Numerous.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
int
|
|
devMergeVisit(dev, hc, scale, trans)
|
|
Dev *dev; /* Dev to examine */
|
|
HierContext *hc; /* Hierarchical context down to this dev */
|
|
float scale; /* Scale transform */
|
|
Transform *trans; /* (unused) */
|
|
{
|
|
DevTerm *gate, *source, *drain;
|
|
Dev *cf;
|
|
DevTerm *cg, *cs, *cd;
|
|
EFNode *subnode, *snode, *dnode, *gnode;
|
|
int pmode, l, w;
|
|
bool hS, hD, chS, chD;
|
|
devMerge *fp, *cfp;
|
|
float m;
|
|
HierName *hierName = hc->hc_hierName;
|
|
|
|
if (esDistrJunct)
|
|
devDistJunctVisit(dev, hc, scale, trans);
|
|
|
|
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)
|
|
{
|
|
cf = cfp->dev;
|
|
cg = &cfp->dev->dev_terms[0];
|
|
cs = cd = &cfp->dev->dev_terms[1];
|
|
if (cfp->dev->dev_nterm >= 3)
|
|
{
|
|
if (pmode == PARALLEL)
|
|
cd = &cfp->dev->dev_terms[2];
|
|
else if (pmode == ANTIPARALLEL)
|
|
cs = &cfp->dev->dev_terms[2];
|
|
}
|
|
|
|
chS = extHierSDAttr(cs); chD = extHierSDAttr(cd);
|
|
if (!(chS || chD || hS || hD)) /* all flat S, D */
|
|
goto mergeThem;
|
|
if (cfp->hierName != hierName &&
|
|
((chS && !hS) || (chD && !hD) ||
|
|
(!chS && hS) || (!chD && hD)))
|
|
{
|
|
|
|
efHNSprintfPrefix((cfp->hierName)?cfp->hierName:hierName,
|
|
esTempName);
|
|
TxError("Warning: conflicting SD attributes of parallel"
|
|
" devs in cell: %s\n", esTempName);
|
|
break;
|
|
}
|
|
else if (cfp->hierName == hierName)
|
|
{
|
|
if (hS && !chS)
|
|
{
|
|
mergeAttr(&cs->dterm_attrs, &source->dterm_attrs);
|
|
}
|
|
if (hD && !chD)
|
|
{
|
|
mergeAttr(&cd->dterm_attrs, &drain->dterm_attrs);
|
|
}
|
|
}
|
|
else /* cfp->hierName != hierName */
|
|
break;
|
|
mergeThem:
|
|
switch(dev->dev_class)
|
|
{
|
|
case DEV_MSUBCKT:
|
|
case DEV_MOSFET:
|
|
case DEV_ASYMMETRIC:
|
|
case DEV_FET:
|
|
m = esFMult[cfp->esFMIndex] + (fp->w / cfp->w);
|
|
break;
|
|
case DEV_RSUBCKT:
|
|
case DEV_RES:
|
|
if (fp->dev->dev_type == esNoModelType)
|
|
m = esFMult[cfp->esFMIndex] + (fp->dev->dev_res
|
|
/ cfp->dev->dev_res);
|
|
else
|
|
m = esFMult[cfp->esFMIndex] + (fp->l / cfp->l);
|
|
break;
|
|
case DEV_CSUBCKT:
|
|
case DEV_CAP:
|
|
case DEV_CAPREV:
|
|
if (fp->dev->dev_type == esNoModelType)
|
|
m = esFMult[cfp->esFMIndex] + (fp->dev->dev_cap
|
|
/ cfp->dev->dev_cap);
|
|
else
|
|
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(resClass, w, n)
|
|
short resClass;
|
|
int w;
|
|
EFNode *n;
|
|
{
|
|
nodeClient *nc;
|
|
int i;
|
|
|
|
if (n->efnode_client == (ClientData)NULL)
|
|
initNodeClient(n);
|
|
nc = (nodeClient *) n->efnode_client;
|
|
if (nc->m_w.widths == NULL)
|
|
{
|
|
(nc->m_w.widths) = (float *)mallocMagic((unsigned)sizeof(float)
|
|
* efNumResistClasses);
|
|
for (i = 0; i < EFDevNumTypes; i++)
|
|
nc->m_w.widths[i] = 0.0;
|
|
}
|
|
nc->m_w.widths[resClass] += (float)w;
|
|
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
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
int
|
|
devDistJunctVisit(dev, hc, scale, trans)
|
|
Dev *dev; /* Dev to examine */
|
|
HierContext *hc; /* Hierarchical path down to this dev */
|
|
float scale; /* Scale transform */
|
|
Transform *trans; /* (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;
|
|
}
|
|
|