2017-04-25 14:41:48 +02:00
|
|
|
/*
|
|
|
|
|
* 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
|
2025-07-18 15:12:02 +02:00
|
|
|
static const char rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-8.0/ext2spice/ext2spice.c,v 1.8 2010/08/25 17:33:56 tim Exp $";
|
2017-04-25 14:41:48 +02:00
|
|
|
#endif /* not lint */
|
|
|
|
|
|
|
|
|
|
#include <stdio.h>
|
2024-09-30 00:00:00 +02:00
|
|
|
#include <stdint.h>
|
2017-04-25 14:41:48 +02:00
|
|
|
#include <stdlib.h> /* for atof() */
|
|
|
|
|
#include <string.h>
|
2024-10-04 19:59:39 +02:00
|
|
|
#include <strings.h>
|
2017-04-25 14:41:48 +02:00
|
|
|
#include <ctype.h>
|
2023-02-16 17:59:13 +01:00
|
|
|
#include <math.h> /* for fabs() */
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
#include "tcltk/tclmagic.h"
|
|
|
|
|
#include "utils/magic.h"
|
|
|
|
|
#include "utils/malloc.h"
|
|
|
|
|
#include "utils/geometry.h"
|
|
|
|
|
#include "utils/hash.h"
|
|
|
|
|
#include "utils/dqueue.h"
|
|
|
|
|
#include "utils/utils.h"
|
|
|
|
|
#include "tiles/tile.h"
|
|
|
|
|
#include "database/database.h"
|
|
|
|
|
#include "windows/windows.h"
|
|
|
|
|
#include "textio/textio.h"
|
|
|
|
|
#include "dbwind/dbwind.h" /* for DBWclientID */
|
|
|
|
|
#include "commands/commands.h" /* for module auto-load */
|
|
|
|
|
#include "textio/txcommands.h"
|
|
|
|
|
#include "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;
|
2017-08-02 04:14:42 +02:00
|
|
|
bool esDoBlackBox = FALSE;
|
2017-04-25 14:41:48 +02:00
|
|
|
bool esDoRenumber = FALSE;
|
|
|
|
|
bool esDoResistorTee = FALSE;
|
|
|
|
|
int esDoSubckt = AUTO;
|
|
|
|
|
bool esDevNodesOnly = FALSE;
|
|
|
|
|
bool esMergeNames = TRUE;
|
|
|
|
|
bool esNoAttrs = FALSE;
|
|
|
|
|
bool esHierAP = FALSE;
|
|
|
|
|
char spcesDefaultOut[FNSIZE];
|
2025-07-24 15:20:12 +02:00
|
|
|
const char *esSpiceCapNode;
|
|
|
|
|
const char esSpiceDefaultGnd[] = "0";
|
2017-04-25 14:41:48 +02:00
|
|
|
char *spcesOutName = spcesDefaultOut;
|
|
|
|
|
FILE *esSpiceF = NULL;
|
|
|
|
|
float esScale = -1.0 ; /* negative if hspice the EFScale/100 otherwise */
|
|
|
|
|
|
|
|
|
|
unsigned short esFormat = SPICE3 ;
|
|
|
|
|
|
2021-07-11 03:13:24 +02:00
|
|
|
int esCapNum, esDevNum, esResNum, esDiodeNum, esVoltNum;
|
2017-04-25 14:41:48 +02:00
|
|
|
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) */
|
|
|
|
|
|
|
|
|
|
/*
|
2020-05-23 23:13:14 +02:00
|
|
|
* 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
|
2017-04-25 14:41:48 +02:00
|
|
|
* which also are meaningful.
|
|
|
|
|
*/
|
|
|
|
|
HashTable subcktNameTable ; /* the hash table itself */
|
2021-12-13 04:09:31 +01:00
|
|
|
DQueue subcktNameQueue ; /* q used to print it sorted at the end */
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2021-05-27 22:13:06 +02:00
|
|
|
fetInfoList esFetInfo[TT_MAXTYPES];
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
/* Record for keeping a list of global names */
|
|
|
|
|
|
|
|
|
|
typedef struct GLL *globalListPtr;
|
|
|
|
|
|
|
|
|
|
typedef struct GLL {
|
|
|
|
|
globalListPtr gll_next;
|
2020-05-23 23:13:14 +02:00
|
|
|
char *gll_name;
|
2017-04-25 14:41:48 +02:00
|
|
|
} globalList;
|
|
|
|
|
|
2021-05-27 22:13:06 +02:00
|
|
|
TileTypeBitMask initMask; /* Used for device types, not tile types */
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
2020-05-23 23:13:14 +02:00
|
|
|
/*
|
2017-04-25 14:41:48 +02:00
|
|
|
*---------------------------------------------------------
|
2020-05-23 23:13:14 +02:00
|
|
|
* 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
|
2017-04-25 14:41:48 +02:00
|
|
|
* 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;
|
|
|
|
|
|
2025-07-24 15:36:22 +02:00
|
|
|
const devMerge *devMergeList = NULL ;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2022-02-08 22:12:07 +01:00
|
|
|
#define atoCap(s) ((EFCapValue)atof(s))
|
|
|
|
|
|
2025-07-18 15:40:53 +02:00
|
|
|
extern void ESGenerateHierarchy(char *inName, int flags); /* forward reference ext2hier.c */
|
2022-02-08 22:12:07 +01:00
|
|
|
|
2021-12-14 00:05:53 +01:00
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* esFreeNodeClient ---
|
|
|
|
|
*
|
|
|
|
|
* Free the string spiceNodeName associated with the nodeClient
|
|
|
|
|
* record that ext2spice allocates per each node structure.
|
|
|
|
|
*
|
|
|
|
|
* Returns:
|
|
|
|
|
* 0 always.
|
|
|
|
|
*
|
|
|
|
|
* Side effects:
|
|
|
|
|
* Frees an allocated string.
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
int
|
2025-07-18 15:11:04 +02:00
|
|
|
esFreeNodeClient(
|
|
|
|
|
nodeClient *client)
|
2021-12-14 00:05:53 +01:00
|
|
|
{
|
2022-02-08 22:12:07 +01:00
|
|
|
if (client != (nodeClient *)NULL)
|
2021-12-14 00:05:53 +01:00
|
|
|
if (client->spiceNodeName != NULL)
|
|
|
|
|
freeMagic((char *)client->spiceNodeName);
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* Apply modifications to a global node name and output to file "outf"
|
|
|
|
|
*
|
|
|
|
|
* Results:
|
|
|
|
|
* None.
|
|
|
|
|
*
|
|
|
|
|
* Side effects:
|
|
|
|
|
* Output to file
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
void
|
2025-07-18 15:11:04 +02:00
|
|
|
esFormatSubs(
|
|
|
|
|
FILE *outf,
|
|
|
|
|
char *suf)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
char *specchar;
|
|
|
|
|
int l;
|
|
|
|
|
|
|
|
|
|
if (outf)
|
|
|
|
|
{
|
|
|
|
|
l = strlen(suf) - 1;
|
2024-10-04 18:24:47 +02:00
|
|
|
if (((EFOutputFlags & EF_TRIMGLOB ) && suf[l] == '!') ||
|
|
|
|
|
((EFOutputFlags & EF_TRIMLOCAL) && suf[l] == '#'))
|
2017-04-25 14:41:48 +02:00
|
|
|
suf[l] = '\0' ;
|
2021-07-11 03:13:24 +02:00
|
|
|
if (EFOutputFlags & EF_CONVERTCOMMA)
|
2017-04-25 14:41:48 +02:00
|
|
|
while ((specchar = strchr(suf, ',')) != NULL)
|
2019-10-15 03:46:07 +02:00
|
|
|
*specchar = '|';
|
2021-07-11 03:13:24 +02:00
|
|
|
if (EFOutputFlags & EF_CONVERTBRACKETS)
|
2019-10-15 03:46:07 +02:00
|
|
|
{
|
|
|
|
|
while ((specchar = strchr(suf, '[')) != NULL)
|
|
|
|
|
*specchar = '_';
|
|
|
|
|
while ((specchar = strchr(suf, ']')) != NULL)
|
|
|
|
|
*specchar = '_';
|
|
|
|
|
}
|
2021-07-11 03:13:24 +02:00
|
|
|
if (EFOutputFlags & EF_CONVERTEQUAL)
|
2017-04-25 14:41:48 +02:00
|
|
|
while ((specchar = strchr(suf, '=')) != NULL)
|
|
|
|
|
*specchar = ':';
|
|
|
|
|
fprintf(outf, "%s", suf);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef MAGIC_WRAPPER
|
|
|
|
|
#ifdef EXT2SPICE_AUTO
|
|
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* Tcl package initialization function
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
int
|
2025-07-18 15:11:04 +02:00
|
|
|
Exttospice_Init(
|
|
|
|
|
Tcl_Interp *interp)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
/* Sanity checks! */
|
|
|
|
|
if (interp == NULL) return TCL_ERROR;
|
|
|
|
|
if (Tcl_PkgRequire(interp, "Tclmagic", MAGIC_VERSION, 0) == NULL)
|
|
|
|
|
return TCL_ERROR;
|
2024-10-21 10:19:15 +02:00
|
|
|
if (Tcl_InitStubs(interp, Tclmagic_InitStubsVersion, 0) == NULL) return TCL_ERROR;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
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 */
|
|
|
|
|
|
2022-02-08 22:12:07 +01:00
|
|
|
#endif /* MAGIC_WRAPPER */
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* 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
|
2021-07-11 03:13:24 +02:00
|
|
|
#define EXTTOSPC_SHORT 9
|
|
|
|
|
#define EXTTOSPC_SUBCIRCUITS 10
|
|
|
|
|
#define EXTTOSPC_HIERARCHY 11
|
|
|
|
|
#define EXTTOSPC_BLACKBOX 12
|
|
|
|
|
#define EXTTOSPC_RENUMBER 13
|
|
|
|
|
#define EXTTOSPC_MERGENAMES 14
|
|
|
|
|
#define EXTTOSPC_LVS 15
|
|
|
|
|
#define EXTTOSPC_HELP 16
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
void
|
2025-07-18 15:11:04 +02:00
|
|
|
CmdExtToSpice(
|
|
|
|
|
MagWindow *w,
|
|
|
|
|
TxCommand *cmd)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
int i,flatFlags;
|
|
|
|
|
char *inName;
|
|
|
|
|
|
|
|
|
|
int value;
|
|
|
|
|
int option = EXTTOSPC_RUN;
|
|
|
|
|
int argc = cmd->tx_argc;
|
|
|
|
|
char **argv = cmd->tx_argv;
|
2024-10-10 21:16:09 +02:00
|
|
|
const char * const *msg;
|
2017-04-25 14:41:48 +02:00
|
|
|
char *resstr = NULL;
|
|
|
|
|
char *substr = NULL;
|
|
|
|
|
bool err_result, locDoSubckt;
|
|
|
|
|
|
2020-09-11 23:29:12 +02:00
|
|
|
short s_rclass, d_rclass, sub_rclass;
|
2017-04-25 14:41:48 +02:00
|
|
|
char *devname;
|
|
|
|
|
char *subname;
|
2020-10-20 18:22:02 +02:00
|
|
|
TileType devtype;
|
2017-04-25 14:41:48 +02:00
|
|
|
int idx, idx2;
|
|
|
|
|
globalList *glist = NULL;
|
|
|
|
|
|
|
|
|
|
static EFCapValue LocCapThreshold = 2;
|
2020-05-23 23:13:14 +02:00
|
|
|
static int LocResistThreshold = INFINITE_THRESHOLD;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2024-10-10 21:16:09 +02:00
|
|
|
static const char * const spiceFormats[] = {
|
A number of changes:
1) Corrected spurious error messages about cells already existing
in GDS when using "flatten" or "flatglob".
2) Fixed handling of resistance as a subcircuit parameter
3) Added area and perimeter resistance for a device; this is done
through the "devresist" statement in the tech file, which is an
extension of the original "fetresist" statement. Where "fetresist"
only supported type "linear", "devresist" supports types "area"
and "perimeter".
4) Support for CDL syntax, including generating subcircuit-like
parameters for components starting with SPICE-standard prefixes
like M, R, C, etc., adding "/" between pins and subcircuit name,
and saving the file as ".cdl" instead of ".spice".
5) Estimated L and W for devices whose geometry is complex and do not
reduce to a simple rectangle. L and W are estimated as the square
root of the area.
6) Changed the method of extracting L and W for diodes to use the same
method as capacitors. Note that diodes are not usually specified
by L and W, but if they are, this will produce the right result.
7) Corrected the reported filename and line number when printing error
messages related to errors inside a technology file, when the
technology file uses "include" to combine multiple files.
2025-10-01 21:17:49 +02:00
|
|
|
"SPICE2", "SPICE3", "HSPICE", "NGSPICE", "CDL", NULL
|
2017-04-25 14:41:48 +02:00
|
|
|
};
|
|
|
|
|
|
2024-10-10 21:16:09 +02:00
|
|
|
static const char * const cmdExtToSpcOption[] = {
|
2017-04-25 14:41:48 +02:00
|
|
|
"[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",
|
2021-07-11 03:13:24 +02:00
|
|
|
"short [voltage|resistor|none]\n"
|
|
|
|
|
" set method for handling shorted ports",
|
2019-10-23 15:33:37 +02:00
|
|
|
"subcircuits [top|descend] [on|off|auto]\n"
|
|
|
|
|
" standard cells become subcircuit calls",
|
2017-04-25 14:41:48 +02:00
|
|
|
"hierarchy [on|off] output hierarchical spice for LVS",
|
2017-08-02 04:14:42 +02:00
|
|
|
"blackbox [on|off] output abstract views as black-box entries",
|
2017-04-25 14:41:48 +02:00
|
|
|
"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",
|
2018-10-31 19:33:24 +01:00
|
|
|
"lvs apply typical default settings for LVS",
|
2017-04-25 14:41:48 +02:00
|
|
|
"help print help information",
|
|
|
|
|
NULL
|
|
|
|
|
};
|
|
|
|
|
|
2024-10-10 21:16:09 +02:00
|
|
|
static const char * const cmdMergeTypes[] = {
|
2017-04-25 14:41:48 +02:00
|
|
|
"none don't merge parallel devices",
|
|
|
|
|
"conservative merge devices with same L, W",
|
|
|
|
|
"aggressive merge devices with same L",
|
|
|
|
|
NULL
|
|
|
|
|
};
|
|
|
|
|
|
2024-10-10 21:16:09 +02:00
|
|
|
static const char * const cmdShortTypes[] = {
|
2021-07-11 03:13:24 +02:00
|
|
|
"none merge shorted ports",
|
|
|
|
|
"resistor separate shorted ports with 0 ohm resistor",
|
|
|
|
|
"voltage separate shorted ports with 0 volt source",
|
|
|
|
|
NULL
|
|
|
|
|
};
|
2024-10-10 21:16:09 +02:00
|
|
|
static const char * const cmdExtToSpcFormat[] = {
|
2017-04-25 14:41:48 +02:00
|
|
|
"spice2",
|
|
|
|
|
"spice3",
|
|
|
|
|
"hspice",
|
|
|
|
|
"ngspice",
|
A number of changes:
1) Corrected spurious error messages about cells already existing
in GDS when using "flatten" or "flatglob".
2) Fixed handling of resistance as a subcircuit parameter
3) Added area and perimeter resistance for a device; this is done
through the "devresist" statement in the tech file, which is an
extension of the original "fetresist" statement. Where "fetresist"
only supported type "linear", "devresist" supports types "area"
and "perimeter".
4) Support for CDL syntax, including generating subcircuit-like
parameters for components starting with SPICE-standard prefixes
like M, R, C, etc., adding "/" between pins and subcircuit name,
and saving the file as ".cdl" instead of ".spice".
5) Estimated L and W for devices whose geometry is complex and do not
reduce to a simple rectangle. L and W are estimated as the square
root of the area.
6) Changed the method of extracting L and W for diodes to use the same
method as capacitors. Note that diodes are not usually specified
by L and W, but if they are, this will produce the right result.
7) Corrected the reported filename and line number when printing error
messages related to errors inside a technology file, when the
technology file uses "include" to combine multiple files.
2025-10-01 21:17:49 +02:00
|
|
|
"cdl",
|
2017-04-25 14:41:48 +02:00
|
|
|
NULL
|
|
|
|
|
};
|
|
|
|
|
|
2024-10-10 21:16:09 +02:00
|
|
|
static const char * const yesno[] = {
|
2017-04-25 14:41:48 +02:00
|
|
|
"yes",
|
|
|
|
|
"true",
|
|
|
|
|
"on",
|
|
|
|
|
"no",
|
|
|
|
|
"false",
|
|
|
|
|
"off",
|
|
|
|
|
NULL
|
|
|
|
|
};
|
|
|
|
|
|
2024-10-10 21:16:09 +02:00
|
|
|
static const char * const subcktopts[] = {
|
2017-04-25 14:41:48 +02:00
|
|
|
"yes",
|
|
|
|
|
"true",
|
|
|
|
|
"on",
|
|
|
|
|
"no",
|
|
|
|
|
"false",
|
|
|
|
|
"off",
|
|
|
|
|
"automatic",
|
|
|
|
|
"top",
|
|
|
|
|
"descend",
|
|
|
|
|
NULL
|
|
|
|
|
};
|
|
|
|
|
|
2025-07-24 15:36:22 +02:00
|
|
|
#if 0 /* -Wunused-variable */
|
2024-10-10 21:16:09 +02:00
|
|
|
static const char * const shorttypes[] = {
|
2021-07-11 03:13:24 +02:00
|
|
|
"none",
|
|
|
|
|
"resistor",
|
|
|
|
|
"voltage",
|
|
|
|
|
NULL
|
|
|
|
|
};
|
2025-07-24 15:36:22 +02:00
|
|
|
#endif
|
2021-07-11 03:13:24 +02:00
|
|
|
|
2025-07-24 15:36:22 +02:00
|
|
|
enum {
|
2017-04-25 14:41:48 +02:00
|
|
|
IDX_YES, IDX_TRUE, IDX_ON, IDX_NO, IDX_FALSE, IDX_OFF,
|
|
|
|
|
IDX_AUTO, IDX_TOP, IDX_DESCEND
|
2025-07-24 15:36:22 +02:00
|
|
|
};
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
2022-02-08 22:12:07 +01:00
|
|
|
#ifdef MAGIC_WRAPPER
|
2017-04-25 14:41:48 +02:00
|
|
|
Tcl_SetResult(magicinterp, (esDoExtResis) ? "on" : "off", NULL);
|
2022-02-08 22:12:07 +01:00
|
|
|
#else
|
|
|
|
|
TxPrintf("Extresist: %s\n", (esDoExtResis) ? "on" : "off");
|
|
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
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)
|
|
|
|
|
{
|
2022-02-08 22:12:07 +01:00
|
|
|
#ifdef MAGIC_WRAPPER
|
2017-04-25 14:41:48 +02:00
|
|
|
Tcl_SetResult(magicinterp, (esDoResistorTee) ? "on" : "off", NULL);
|
2022-02-08 22:12:07 +01:00
|
|
|
#else
|
|
|
|
|
TxPrintf("Resistor tee: %s\n", (esDoResistorTee) ? "on" : "off");
|
|
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
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)
|
|
|
|
|
{
|
2022-02-08 22:12:07 +01:00
|
|
|
#ifdef MAGIC_WRAPPER
|
2017-04-25 14:41:48 +02:00
|
|
|
Tcl_SetResult(magicinterp, (esScale < 0) ? "on" : "off", NULL);
|
2022-02-08 22:12:07 +01:00
|
|
|
#else
|
|
|
|
|
TxPrintf("Scale: %s\n", (esScale < 0) ? "on" : "off");
|
|
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
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)
|
|
|
|
|
{
|
2022-02-08 22:12:07 +01:00
|
|
|
#ifdef MAGIC_WRAPPER
|
2017-04-25 14:41:48 +02:00
|
|
|
Tcl_SetResult(magicinterp, (esDoHierarchy) ? "on" : "off", NULL);
|
2022-02-08 22:12:07 +01:00
|
|
|
#else
|
|
|
|
|
TxPrintf("Hierarchy: %s\n", (esDoHierarchy) ? "on" : "off");
|
|
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
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;
|
|
|
|
|
|
2017-08-02 04:14:42 +02:00
|
|
|
case EXTTOSPC_BLACKBOX:
|
|
|
|
|
if (cmd->tx_argc == 2)
|
|
|
|
|
{
|
2022-02-08 22:12:07 +01:00
|
|
|
#ifdef MAGIC_WRAPPER
|
2017-08-02 04:14:42 +02:00
|
|
|
Tcl_SetResult(magicinterp, (esDoBlackBox) ? "on" : "off", NULL);
|
2022-02-08 22:12:07 +01:00
|
|
|
#else
|
|
|
|
|
TxPrintf("Black box: %s\n", (esDoBlackBox) ? "on" : "off");
|
|
|
|
|
#endif
|
2017-08-02 04:14:42 +02:00
|
|
|
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;
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
case EXTTOSPC_RENUMBER:
|
|
|
|
|
if (cmd->tx_argc == 2)
|
|
|
|
|
{
|
2022-02-08 22:12:07 +01:00
|
|
|
#ifdef MAGIC_WRAPPER
|
2017-04-25 14:41:48 +02:00
|
|
|
Tcl_SetResult(magicinterp, (esDoRenumber) ? "on" : "off", NULL);
|
2022-02-08 22:12:07 +01:00
|
|
|
#else
|
|
|
|
|
TxPrintf("Renumber: %s\n", (esDoRenumber) ? "on" : "off");
|
|
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
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)
|
|
|
|
|
{
|
2022-02-08 22:12:07 +01:00
|
|
|
#ifdef MAGIC_WRAPPER
|
2017-04-25 14:41:48 +02:00
|
|
|
Tcl_SetResult(magicinterp, (esMergeNames) ? "on" : "off", NULL);
|
2022-02-08 22:12:07 +01:00
|
|
|
#else
|
|
|
|
|
TxPrintf("Merge names: %s\n", (esMergeNames) ? "on" : "off");
|
|
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
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;
|
|
|
|
|
|
2021-07-11 03:13:24 +02:00
|
|
|
case EXTTOSPC_SHORT:
|
|
|
|
|
if (cmd->tx_argc == 2)
|
|
|
|
|
{
|
|
|
|
|
if ((EFOutputFlags & EF_SHORT_MASK) == EF_SHORT_NONE)
|
2022-02-08 22:12:07 +01:00
|
|
|
#ifdef MAGIC_WRAPPER
|
2021-07-11 03:13:24 +02:00
|
|
|
Tcl_SetResult(magicinterp, "none", NULL);
|
2022-02-08 22:12:07 +01:00
|
|
|
#else
|
|
|
|
|
TxPrintf("Shorts: none\n");
|
|
|
|
|
#endif
|
2021-07-11 03:13:24 +02:00
|
|
|
else if ((EFOutputFlags & EF_SHORT_MASK) == EF_SHORT_R)
|
2022-02-08 22:12:07 +01:00
|
|
|
#ifdef MAGIC_WRAPPER
|
2021-07-11 03:13:24 +02:00
|
|
|
Tcl_SetResult(magicinterp, "resistor", NULL);
|
2022-02-08 22:12:07 +01:00
|
|
|
#else
|
|
|
|
|
TxPrintf("Shorts: resistor\n");
|
|
|
|
|
#endif
|
2021-07-11 03:13:24 +02:00
|
|
|
else if ((EFOutputFlags & EF_SHORT_MASK) == EF_SHORT_V)
|
2022-02-08 22:12:07 +01:00
|
|
|
#ifdef MAGIC_WRAPPER
|
2021-07-11 03:13:24 +02:00
|
|
|
Tcl_SetResult(magicinterp, "voltage source", NULL);
|
2022-02-08 22:12:07 +01:00
|
|
|
#else
|
|
|
|
|
TxPrintf("Shorts: voltage source\n");
|
|
|
|
|
#endif
|
2021-07-11 03:13:24 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
idx = Lookup(cmd->tx_argv[2], cmdShortTypes);
|
|
|
|
|
if (idx < 0) goto usage;
|
|
|
|
|
else switch (idx)
|
|
|
|
|
{
|
|
|
|
|
case 0:
|
|
|
|
|
EFOutputFlags &= ~EF_SHORT_MASK;
|
|
|
|
|
EFOutputFlags |= EF_SHORT_NONE;
|
|
|
|
|
break;
|
|
|
|
|
case 1:
|
|
|
|
|
EFOutputFlags &= ~EF_SHORT_MASK;
|
|
|
|
|
EFOutputFlags |= EF_SHORT_R;
|
|
|
|
|
break;
|
|
|
|
|
case 2:
|
|
|
|
|
EFOutputFlags &= ~EF_SHORT_MASK;
|
|
|
|
|
EFOutputFlags |= EF_SHORT_V;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
2018-10-31 19:33:24 +01:00
|
|
|
case EXTTOSPC_LVS:
|
|
|
|
|
/* Apply default command settings for LVS */
|
|
|
|
|
/* hierarchy = on */
|
|
|
|
|
/* format = ngspice */
|
|
|
|
|
/* cthresh = infinite */
|
|
|
|
|
/* rthresh = infinite */
|
|
|
|
|
/* renumber = off */
|
|
|
|
|
/* scale = off */
|
|
|
|
|
/* blackbox = on */
|
2018-10-31 20:41:22 +01:00
|
|
|
/* global = off */
|
2018-10-31 19:33:24 +01:00
|
|
|
/* subcircuit top = auto */
|
|
|
|
|
|
|
|
|
|
esDoHierarchy = TRUE;
|
|
|
|
|
esFormat = NGSPICE;
|
|
|
|
|
LocCapThreshold = (EFCapValue)INFINITE_THRESHOLD_F;
|
|
|
|
|
LocResistThreshold = INFINITE_THRESHOLD;
|
|
|
|
|
esDoRenumber = FALSE;
|
|
|
|
|
esScale = 0.0;
|
|
|
|
|
esDoBlackBox = TRUE;
|
2018-10-31 20:41:22 +01:00
|
|
|
esMergeNames = FALSE;
|
2018-10-31 19:33:24 +01:00
|
|
|
esDoSubckt = 2;
|
|
|
|
|
break;
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
case EXTTOSPC_SUBCIRCUITS:
|
|
|
|
|
if (cmd->tx_argc == 2)
|
|
|
|
|
{
|
2022-02-08 22:12:07 +01:00
|
|
|
#ifdef MAGIC_WRAPPER
|
2017-04-25 14:41:48 +02:00
|
|
|
Tcl_SetResult(magicinterp, (esDoPorts) ? "on" : "off", NULL);
|
2022-02-08 22:12:07 +01:00
|
|
|
#else
|
|
|
|
|
TxPrintf("Ports: %s\n", (esDoPorts) ? "on" : "off");
|
|
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
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)
|
|
|
|
|
{
|
2022-02-08 22:12:07 +01:00
|
|
|
#ifdef MAGIC_WRAPPER
|
2017-04-25 14:41:48 +02:00
|
|
|
Tcl_SetResult(magicinterp, (esDoPorts) ? "on" : "off", NULL);
|
2022-02-08 22:12:07 +01:00
|
|
|
#else
|
|
|
|
|
TxPrintf("Ports: %s\n", (esDoPorts) ? "on" : "off");
|
|
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case IDX_TOP:
|
|
|
|
|
if (cmd->tx_argc == 3)
|
|
|
|
|
{
|
2022-02-08 22:12:07 +01:00
|
|
|
#ifdef MAGIC_WRAPPER
|
2017-04-25 14:41:48 +02:00
|
|
|
Tcl_SetResult(magicinterp,
|
|
|
|
|
(esDoSubckt == 2) ? "auto" :
|
|
|
|
|
(esDoSubckt == 1) ? "on" : "off", NULL);
|
2022-02-08 22:12:07 +01:00
|
|
|
#else
|
|
|
|
|
TxPrintf("Top subcircuit: %s\n",
|
|
|
|
|
(esDoSubckt == 2) ? "auto" :
|
|
|
|
|
(esDoSubckt == 1) ? "on" : "off");
|
|
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
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)
|
|
|
|
|
{
|
2022-02-08 22:12:07 +01:00
|
|
|
#ifdef MAGIC_WRAPPER
|
2024-10-10 21:16:09 +02:00
|
|
|
/* FIXME check NULL is ok here, should this be TCL_STATIC ? TCL_STATIC==0 maybe using TCL_STATIC is better? */
|
|
|
|
|
Tcl_SetResult(magicinterp, (char*)cmdExtToSpcFormat[esFormat], NULL);
|
2022-02-08 22:12:07 +01:00
|
|
|
#else
|
|
|
|
|
TxPrintf("Format: %s\n", cmdExtToSpcFormat[esFormat]);
|
|
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
else if (cmd->tx_argc < 3) goto usage;
|
|
|
|
|
idx = Lookup(cmd->tx_argv[2], cmdExtToSpcFormat);
|
|
|
|
|
if (idx < 0)
|
|
|
|
|
{
|
2022-02-08 22:12:07 +01:00
|
|
|
#ifdef MAGIC_WRAPPER
|
2017-04-25 14:41:48 +02:00
|
|
|
Tcl_SetResult(magicinterp, "Bad format type. Formats are:"
|
A number of changes:
1) Corrected spurious error messages about cells already existing
in GDS when using "flatten" or "flatglob".
2) Fixed handling of resistance as a subcircuit parameter
3) Added area and perimeter resistance for a device; this is done
through the "devresist" statement in the tech file, which is an
extension of the original "fetresist" statement. Where "fetresist"
only supported type "linear", "devresist" supports types "area"
and "perimeter".
4) Support for CDL syntax, including generating subcircuit-like
parameters for components starting with SPICE-standard prefixes
like M, R, C, etc., adding "/" between pins and subcircuit name,
and saving the file as ".cdl" instead of ".spice".
5) Estimated L and W for devices whose geometry is complex and do not
reduce to a simple rectangle. L and W are estimated as the square
root of the area.
6) Changed the method of extracting L and W for diodes to use the same
method as capacitors. Note that diodes are not usually specified
by L and W, but if they are, this will produce the right result.
7) Corrected the reported filename and line number when printing error
messages related to errors inside a technology file, when the
technology file uses "include" to combine multiple files.
2025-10-01 21:17:49 +02:00
|
|
|
"spice2, spice3, hspice, ngspice, and cdl.", NULL);
|
2022-02-08 22:12:07 +01:00
|
|
|
#else
|
|
|
|
|
TxError("Bad format type. Formats are:"
|
A number of changes:
1) Corrected spurious error messages about cells already existing
in GDS when using "flatten" or "flatglob".
2) Fixed handling of resistance as a subcircuit parameter
3) Added area and perimeter resistance for a device; this is done
through the "devresist" statement in the tech file, which is an
extension of the original "fetresist" statement. Where "fetresist"
only supported type "linear", "devresist" supports types "area"
and "perimeter".
4) Support for CDL syntax, including generating subcircuit-like
parameters for components starting with SPICE-standard prefixes
like M, R, C, etc., adding "/" between pins and subcircuit name,
and saving the file as ".cdl" instead of ".spice".
5) Estimated L and W for devices whose geometry is complex and do not
reduce to a simple rectangle. L and W are estimated as the square
root of the area.
6) Changed the method of extracting L and W for diodes to use the same
method as capacitors. Note that diodes are not usually specified
by L and W, but if they are, this will produce the right result.
7) Corrected the reported filename and line number when printing error
messages related to errors inside a technology file, when the
technology file uses "include" to combine multiple files.
2025-10-01 21:17:49 +02:00
|
|
|
"spice2, spice3, hspice, ngspice, and cdl.");
|
2022-02-08 22:12:07 +01:00
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
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))
|
2022-02-08 22:12:07 +01:00
|
|
|
#ifdef MAGIC_WRAPPER
|
2017-04-25 14:41:48 +02:00
|
|
|
Tcl_SetResult(magicinterp, "infinite", NULL);
|
2022-02-08 22:12:07 +01:00
|
|
|
#else
|
|
|
|
|
TxPrintf("Capacitance threshold: infinite\n");
|
|
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
else
|
2022-02-08 22:12:07 +01:00
|
|
|
#ifdef MAGIC_WRAPPER
|
2017-04-25 14:41:48 +02:00
|
|
|
Tcl_SetObjResult(magicinterp,
|
|
|
|
|
Tcl_NewDoubleObj((double)LocCapThreshold));
|
2022-02-08 22:12:07 +01:00
|
|
|
#else
|
|
|
|
|
TxPrintf("Capacitance threshold: %lf\n",
|
|
|
|
|
(double)LocCapThreshold);
|
|
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
else if (cmd->tx_argc < 3) goto usage;
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/* 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]);
|
2020-05-23 23:13:14 +02:00
|
|
|
else
|
2017-04-25 14:41:48 +02:00
|
|
|
TxError("exttospice: numeric value or \"infinite\" expected.\n");
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case EXTTOSPC_RTHRESH:
|
|
|
|
|
if (cmd->tx_argc == 2)
|
|
|
|
|
{
|
|
|
|
|
if (LocResistThreshold == INFINITE_THRESHOLD)
|
2022-02-08 22:12:07 +01:00
|
|
|
#ifdef MAGIC_WRAPPER
|
2017-04-25 14:41:48 +02:00
|
|
|
Tcl_SetResult(magicinterp, "infinite", NULL);
|
2022-02-08 22:12:07 +01:00
|
|
|
#else
|
|
|
|
|
TxPrintf("Resistance threshold: infinite\n");
|
|
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
else
|
2022-02-08 22:12:07 +01:00
|
|
|
#ifdef MAGIC_WRAPPER
|
2017-04-25 14:41:48 +02:00
|
|
|
Tcl_SetObjResult(magicinterp,
|
|
|
|
|
Tcl_NewIntObj(LocResistThreshold));
|
2022-02-08 22:12:07 +01:00
|
|
|
#else
|
|
|
|
|
TxPrintf("Resistance threshold: %lf\n",
|
|
|
|
|
(double)LocResistThreshold);
|
|
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
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;
|
2020-05-23 23:13:14 +02:00
|
|
|
else
|
2017-04-25 14:41:48 +02:00
|
|
|
TxError("exttospice: integer value or \"infinite\" expected.\n");
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case EXTTOSPC_MERGE:
|
|
|
|
|
if (cmd->tx_argc == 2)
|
|
|
|
|
{
|
|
|
|
|
if (esMergeDevsA)
|
2022-02-08 22:12:07 +01:00
|
|
|
#ifdef MAGIC_WRAPPER
|
2017-04-25 14:41:48 +02:00
|
|
|
Tcl_SetResult(magicinterp, "aggressive", NULL);
|
2022-02-08 22:12:07 +01:00
|
|
|
#else
|
|
|
|
|
TxPrintf("Merge: aggressive\n");
|
|
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
else if (esMergeDevsC)
|
2022-02-08 22:12:07 +01:00
|
|
|
#ifdef MAGIC_WRAPPER
|
2017-04-25 14:41:48 +02:00
|
|
|
Tcl_SetResult(magicinterp, "conservative", NULL);
|
2022-02-08 22:12:07 +01:00
|
|
|
#else
|
|
|
|
|
TxPrintf("Merge: conservative\n");
|
|
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
else
|
2022-02-08 22:12:07 +01:00
|
|
|
#ifdef MAGIC_WRAPPER
|
2017-04-25 14:41:48 +02:00
|
|
|
Tcl_SetResult(magicinterp, "none", NULL);
|
2022-02-08 22:12:07 +01:00
|
|
|
#else
|
|
|
|
|
TxPrintf("Merge: none\n");
|
|
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
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;
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
case EXTTOSPC_DEFAULT:
|
|
|
|
|
LocCapThreshold = 2;
|
|
|
|
|
LocResistThreshold = INFINITE_THRESHOLD;
|
2021-07-11 03:27:19 +02:00
|
|
|
/* Clear EFOutputFlags but preserve "short" behavior */
|
|
|
|
|
EFOutputFlags &= ~EF_TRIM_MASK;
|
|
|
|
|
EFOutputFlags |= EF_CONVERTCOMMA | EF_CONVERTEQUAL;
|
2017-04-25 14:41:48 +02:00
|
|
|
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;
|
2021-07-11 03:13:24 +02:00
|
|
|
esVoltNum = 0;
|
2017-04-25 14:41:48 +02:00
|
|
|
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 */
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2022-02-08 22:12:07 +01:00
|
|
|
inName = EFArgs(argc, argv, &err_result, spcParseArgs, (ClientData) NULL);
|
2017-04-25 14:41:48 +02:00
|
|
|
if (err_result == TRUE)
|
|
|
|
|
{
|
2024-09-30 00:00:00 +02:00
|
|
|
EFDone(NULL);
|
2017-04-25 14:41:48 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (inName == NULL)
|
|
|
|
|
{
|
|
|
|
|
/* Assume that we want to do exttospice on the currently loaded cell */
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
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.
|
2020-05-23 23:13:14 +02:00
|
|
|
* Make output name inName.spice if they weren't
|
2017-04-25 14:41:48 +02:00
|
|
|
* explicitly specified
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
if (spcesOutName == spcesDefaultOut)
|
A number of changes:
1) Corrected spurious error messages about cells already existing
in GDS when using "flatten" or "flatglob".
2) Fixed handling of resistance as a subcircuit parameter
3) Added area and perimeter resistance for a device; this is done
through the "devresist" statement in the tech file, which is an
extension of the original "fetresist" statement. Where "fetresist"
only supported type "linear", "devresist" supports types "area"
and "perimeter".
4) Support for CDL syntax, including generating subcircuit-like
parameters for components starting with SPICE-standard prefixes
like M, R, C, etc., adding "/" between pins and subcircuit name,
and saving the file as ".cdl" instead of ".spice".
5) Estimated L and W for devices whose geometry is complex and do not
reduce to a simple rectangle. L and W are estimated as the square
root of the area.
6) Changed the method of extracting L and W for diodes to use the same
method as capacitors. Note that diodes are not usually specified
by L and W, but if they are, this will produce the right result.
7) Corrected the reported filename and line number when printing error
messages related to errors inside a technology file, when the
technology file uses "include" to combine multiple files.
2025-10-01 21:17:49 +02:00
|
|
|
{
|
|
|
|
|
if (esFormat == CDL)
|
|
|
|
|
sprintf(spcesDefaultOut, "%s.cdl", inName);
|
|
|
|
|
else
|
|
|
|
|
sprintf(spcesDefaultOut, "%s.spice", inName);
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
/* Read the hierarchical description of the input circuit */
|
2024-04-03 03:55:57 +02:00
|
|
|
if (EFReadFile(inName, esDoHierarchy, esDoExtResis, FALSE, TRUE)
|
|
|
|
|
== FALSE)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
2024-09-30 00:00:00 +02:00
|
|
|
EFDone(NULL);
|
2017-04-25 14:41:48 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* If the .ext file was read without error, then open the output file */
|
|
|
|
|
|
|
|
|
|
if ((esSpiceF = fopen(spcesOutName, "w")) == NULL)
|
|
|
|
|
{
|
2022-02-08 22:12:07 +01:00
|
|
|
#ifdef MAGIC_WRAPPER
|
2017-04-25 14:41:48 +02:00
|
|
|
char *tclres = Tcl_Alloc(128);
|
|
|
|
|
sprintf(tclres, "exttospice: Unable to open file %s for writing\n",
|
|
|
|
|
spcesOutName);
|
|
|
|
|
Tcl_SetResult(magicinterp, tclres, TCL_DYNAMIC);
|
2022-02-08 22:12:07 +01:00
|
|
|
#else
|
|
|
|
|
TxError("exttospice: Unable to open file %s for writing\n", spcesOutName);
|
|
|
|
|
#endif
|
2024-09-30 00:00:00 +02:00
|
|
|
EFDone(NULL);
|
2017-04-25 14:41:48 +02:00
|
|
|
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 */
|
|
|
|
|
|
2021-05-27 22:13:06 +02:00
|
|
|
for ( i = 0 ; i < TT_MAXTYPES ; i++ ) {
|
2020-09-11 23:29:12 +02:00
|
|
|
esFetInfo[i].resClassSource = NO_RESCLASS;
|
|
|
|
|
esFetInfo[i].resClassDrain = NO_RESCLASS;
|
2017-04-25 14:41:48 +02:00
|
|
|
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;
|
2020-10-20 18:22:02 +02:00
|
|
|
while (ExtGetDevInfo(idx++, &devname, &devtype, &s_rclass, &d_rclass,
|
|
|
|
|
&sub_rclass, &subname))
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
2021-05-27 22:13:06 +02:00
|
|
|
if (idx == TT_MAXTYPES)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
TxError("Error: Ran out of space for device types!\n");
|
|
|
|
|
break;
|
|
|
|
|
}
|
2021-05-27 22:13:06 +02:00
|
|
|
i = efBuildAddStr(EFDevTypes, &EFDevNumTypes, TT_MAXTYPES, devname);
|
2017-04-25 14:41:48 +02:00
|
|
|
if (!strcmp(devname, "None"))
|
|
|
|
|
esNoModelType = i;
|
|
|
|
|
if (EFStyle != NULL)
|
|
|
|
|
{
|
2020-09-11 23:29:12 +02:00
|
|
|
esFetInfo[i].resClassSource = s_rclass;
|
|
|
|
|
esFetInfo[i].resClassDrain = d_rclass;
|
2017-04-25 14:41:48 +02:00
|
|
|
esFetInfo[i].resClassSub = sub_rclass;
|
|
|
|
|
esFetInfo[i].defSubs = subname;
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-08 22:12:07 +01:00
|
|
|
#ifdef MAGIC_WRAPPER
|
2017-04-25 14:41:48 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-02-08 22:12:07 +01:00
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
if (esDoHierarchy && (subname != NULL))
|
|
|
|
|
{
|
|
|
|
|
globalList *glptr;
|
|
|
|
|
char *locsubname, *bangptr;
|
|
|
|
|
bool isgood = TRUE;
|
|
|
|
|
|
|
|
|
|
locsubname = StrDup(NULL, subname);
|
|
|
|
|
|
A number of changes:
1) Corrected spurious error messages about cells already existing
in GDS when using "flatten" or "flatglob".
2) Fixed handling of resistance as a subcircuit parameter
3) Added area and perimeter resistance for a device; this is done
through the "devresist" statement in the tech file, which is an
extension of the original "fetresist" statement. Where "fetresist"
only supported type "linear", "devresist" supports types "area"
and "perimeter".
4) Support for CDL syntax, including generating subcircuit-like
parameters for components starting with SPICE-standard prefixes
like M, R, C, etc., adding "/" between pins and subcircuit name,
and saving the file as ".cdl" instead of ".spice".
5) Estimated L and W for devices whose geometry is complex and do not
reduce to a simple rectangle. L and W are estimated as the square
root of the area.
6) Changed the method of extracting L and W for diodes to use the same
method as capacitors. Note that diodes are not usually specified
by L and W, but if they are, this will produce the right result.
7) Corrected the reported filename and line number when printing error
messages related to errors inside a technology file, when the
technology file uses "include" to combine multiple files.
2025-10-01 21:17:49 +02:00
|
|
|
if (esFormat != CDL)
|
|
|
|
|
{
|
|
|
|
|
bangptr = locsubname + strlen(locsubname) - 1;
|
|
|
|
|
if (*bangptr == '!') *bangptr = '\0';
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-09 00:12:35 +02:00
|
|
|
#ifdef MAGIC_WRAPPER
|
A number of changes:
1) Corrected spurious error messages about cells already existing
in GDS when using "flatten" or "flatglob".
2) Fixed handling of resistance as a subcircuit parameter
3) Added area and perimeter resistance for a device; this is done
through the "devresist" statement in the tech file, which is an
extension of the original "fetresist" statement. Where "fetresist"
only supported type "linear", "devresist" supports types "area"
and "perimeter".
4) Support for CDL syntax, including generating subcircuit-like
parameters for components starting with SPICE-standard prefixes
like M, R, C, etc., adding "/" between pins and subcircuit name,
and saving the file as ".cdl" instead of ".spice".
5) Estimated L and W for devices whose geometry is complex and do not
reduce to a simple rectangle. L and W are estimated as the square
root of the area.
6) Changed the method of extracting L and W for diodes to use the same
method as capacitors. Note that diodes are not usually specified
by L and W, but if they are, this will produce the right result.
7) Corrected the reported filename and line number when printing error
messages related to errors inside a technology file, when the
technology file uses "include" to combine multiple files.
2025-10-01 21:17:49 +02:00
|
|
|
if (esFormat == CDL)
|
|
|
|
|
{
|
|
|
|
|
/* In CDL format, if the global substrate ends with "!" then
|
|
|
|
|
* add it to the list of globals; likewise for VDD and GND.
|
|
|
|
|
*/
|
|
|
|
|
char *glbstr;
|
|
|
|
|
globalList *glptr;
|
|
|
|
|
|
|
|
|
|
glbstr = (char *)Tcl_GetVar(magicinterp, "SUB", TCL_GLOBAL_ONLY);
|
|
|
|
|
if ((glbstr != NULL) && (*(glbstr + strlen(glbstr) - 1) == '!'))
|
|
|
|
|
{
|
|
|
|
|
glptr = (globalList *)mallocMagic(sizeof(globalList));
|
|
|
|
|
glptr->gll_name = StrDup((char **)NULL, glbstr);
|
|
|
|
|
glptr->gll_next = glist;
|
|
|
|
|
glist = glptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
glbstr = (char *)Tcl_GetVar(magicinterp, "VDD", TCL_GLOBAL_ONLY);
|
|
|
|
|
if ((glbstr != NULL) && (*(glbstr + strlen(glbstr) - 1) == '!'))
|
|
|
|
|
{
|
|
|
|
|
glptr = (globalList *)mallocMagic(sizeof(globalList));
|
|
|
|
|
glptr->gll_name = StrDup((char **)NULL, glbstr);
|
|
|
|
|
glptr->gll_next = glist;
|
|
|
|
|
glist = glptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
glbstr = (char *)Tcl_GetVar(magicinterp, "GND", TCL_GLOBAL_ONLY);
|
|
|
|
|
if ((glbstr != NULL) && (*(glbstr + strlen(glbstr) - 1) == '!'))
|
|
|
|
|
{
|
|
|
|
|
glptr = (globalList *)mallocMagic(sizeof(globalList));
|
|
|
|
|
glptr->gll_name = StrDup((char **)NULL, glbstr);
|
|
|
|
|
glptr->gll_next = glist;
|
|
|
|
|
glist = glptr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
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 */
|
|
|
|
|
}
|
2022-02-08 22:12:07 +01:00
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
/* Write the output file */
|
|
|
|
|
|
|
|
|
|
fprintf(esSpiceF, "* %s file created from %s.ext - technology: %s\n\n",
|
|
|
|
|
spiceFormats[esFormat], inName, EFTech);
|
2020-05-23 23:13:14 +02:00
|
|
|
if (esScale < 0)
|
2023-02-16 17:59:13 +01:00
|
|
|
{
|
|
|
|
|
fprintf(esSpiceF, ".option scale=");
|
|
|
|
|
esSIvalue(esSpiceF, 1.0E-6 * EFScale / 100.0);
|
|
|
|
|
fprintf(esSpiceF, "\n\n");
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
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. . .
|
2021-07-11 03:13:24 +02:00
|
|
|
EFOutputFlags |= EF_TRIMGLOB | EF_CONVERTEQUAL | EF_CONVERTCOMMA;
|
2017-04-25 14:41:48 +02:00
|
|
|
if (IS_FINITE_F(EFCapThreshold)) flatFlags |= EF_FLATCAPS;
|
|
|
|
|
if (esFormat == HSPICE)
|
2021-07-11 03:13:24 +02:00
|
|
|
EFOutputFlags |= EF_TRIMLOCAL;
|
A number of changes:
1) Corrected spurious error messages about cells already existing
in GDS when using "flatten" or "flatglob".
2) Fixed handling of resistance as a subcircuit parameter
3) Added area and perimeter resistance for a device; this is done
through the "devresist" statement in the tech file, which is an
extension of the original "fetresist" statement. Where "fetresist"
only supported type "linear", "devresist" supports types "area"
and "perimeter".
4) Support for CDL syntax, including generating subcircuit-like
parameters for components starting with SPICE-standard prefixes
like M, R, C, etc., adding "/" between pins and subcircuit name,
and saving the file as ".cdl" instead of ".spice".
5) Estimated L and W for devices whose geometry is complex and do not
reduce to a simple rectangle. L and W are estimated as the square
root of the area.
6) Changed the method of extracting L and W for diodes to use the same
method as capacitors. Note that diodes are not usually specified
by L and W, but if they are, this will produce the right result.
7) Corrected the reported filename and line number when printing error
messages related to errors inside a technology file, when the
technology file uses "include" to combine multiple files.
2025-10-01 21:17:49 +02:00
|
|
|
if (esFormat == CDL)
|
|
|
|
|
EFOutputFlags &= ~EF_TRIMGLOB;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
/* Write globals under a ".global" card */
|
|
|
|
|
|
|
|
|
|
if (esDoHierarchy && (glist != NULL))
|
|
|
|
|
{
|
|
|
|
|
fprintf(esSpiceF, ".global ");
|
|
|
|
|
while (glist != NULL)
|
|
|
|
|
{
|
2022-02-08 22:12:07 +01:00
|
|
|
#ifdef MAGIC_WRAPPER
|
2017-04-25 14:41:48 +02:00
|
|
|
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
|
2022-02-08 22:12:07 +01:00
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
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))
|
2017-08-02 04:14:42 +02:00
|
|
|
topVisit(efFlatRootDef, FALSE);
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
/* 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);
|
|
|
|
|
|
2021-05-27 22:13:06 +02:00
|
|
|
TTMaskZero(&initMask);
|
|
|
|
|
if (!esDistrJunct)
|
2023-01-27 17:47:37 +01:00
|
|
|
TTMaskSetType(&initMask, efNumResistClasses);
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
if (esMergeDevsA || esMergeDevsC)
|
|
|
|
|
{
|
2025-07-24 15:36:22 +02:00
|
|
|
const devMerge *p;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
EFVisitDevs(devMergeVisit, (ClientData) NULL);
|
|
|
|
|
TxPrintf("Devs merged: %d\n", esSpiceDevsMerged);
|
|
|
|
|
esFMIndex = 0;
|
|
|
|
|
for (p = devMergeList; p != NULL; p = p->next)
|
2025-07-24 15:36:22 +02:00
|
|
|
freeMagic((char *) p);
|
2017-04-25 14:41:48 +02:00
|
|
|
devMergeList = NULL;
|
|
|
|
|
}
|
|
|
|
|
else if (esDistrJunct)
|
|
|
|
|
EFVisitDevs(devDistJunctVisit, (ClientData) NULL);
|
|
|
|
|
EFVisitDevs(spcdevVisit, (ClientData) NULL);
|
2021-05-27 22:13:06 +02:00
|
|
|
TTMaskZero(&initMask);
|
2017-04-25 14:41:48 +02:00
|
|
|
if (flatFlags & EF_FLATCAPS)
|
|
|
|
|
EFVisitCaps(spccapVisit, (ClientData) NULL);
|
2023-03-13 18:21:34 +01:00
|
|
|
|
2025-07-20 17:09:08 +02:00
|
|
|
EFVisitResists(spcresistVisit, PTR2CD(NULL));
|
2017-04-25 14:41:48 +02:00
|
|
|
EFVisitSubcircuits(subcktVisit, (ClientData) NULL);
|
|
|
|
|
|
|
|
|
|
/* Visit nodes to find the substrate node */
|
|
|
|
|
EFVisitNodes(spcsubVisit, (ClientData)&substr);
|
|
|
|
|
if (substr == NULL)
|
2023-03-14 17:53:22 +01:00
|
|
|
substr = StrDup((char **)NULL, esSpiceDefaultGnd);
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2023-03-13 18:21:34 +01:00
|
|
|
esSpiceCapNode = substr;
|
2017-04-25 14:41:48 +02:00
|
|
|
EFVisitNodes(spcnodeVisit, (ClientData) NULL);
|
|
|
|
|
|
|
|
|
|
if (EFCompat == FALSE) freeMagic(substr);
|
|
|
|
|
|
|
|
|
|
if ((esDoSubckt == TRUE) || (locDoSubckt == TRUE))
|
|
|
|
|
fprintf(esSpiceF, ".ends\n");
|
|
|
|
|
|
2020-05-23 23:13:14 +02:00
|
|
|
if (esFormat == HSPICE)
|
2017-04-25 14:41:48 +02:00
|
|
|
printSubcktDict();
|
|
|
|
|
|
2021-12-14 00:05:53 +01:00
|
|
|
EFFlatDone(esFreeNodeClient);
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
2021-12-14 00:05:53 +01:00
|
|
|
EFDone(esFreeNodeClient);
|
2021-12-13 04:09:31 +01:00
|
|
|
if (esFormat == HSPICE) {
|
|
|
|
|
HashKill(&subcktNameTable);
|
|
|
|
|
#ifndef UNSORTED_SUBCKT
|
|
|
|
|
DQFree(&subcktNameQueue);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
if (esSpiceF) fclose(esSpiceF);
|
|
|
|
|
|
|
|
|
|
TxPrintf("exttospice finished.\n");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-18 15:12:51 +02:00
|
|
|
#ifdef EXT2SPICE_MAIN /* Independent program "ext2spice" has been deprecated */
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* main --
|
|
|
|
|
*
|
|
|
|
|
* Top level of ext2spice.
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
int
|
2025-07-18 15:11:04 +02:00
|
|
|
main(
|
|
|
|
|
int argc,
|
|
|
|
|
char *argv[])
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
|
|
|
|
|
int i,flatFlags;
|
|
|
|
|
char *inName;
|
|
|
|
|
FILE *f;
|
|
|
|
|
bool locDoSubckt;
|
|
|
|
|
|
|
|
|
|
esSpiceDevsMerged = 0;
|
|
|
|
|
|
2024-10-10 21:16:09 +02:00
|
|
|
static const char * const spiceFormats[] = {
|
2017-04-25 14:41:48 +02:00
|
|
|
"SPICE2", "SPICE3", "HSPICE", "NGSPICE", NULL
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
EFInit();
|
|
|
|
|
EFResistThreshold = INFINITE_THRESHOLD ;
|
|
|
|
|
/* create default devinfo entries (MOSIS) which can be overriden by
|
|
|
|
|
the command line arguments */
|
2021-05-27 22:13:06 +02:00
|
|
|
for ( i = 0 ; i < TT_MAXTYPES ; i++ ) {
|
2020-09-11 23:29:12 +02:00
|
|
|
esFetInfo[i].resClassSource = NO_RESCLASS;
|
|
|
|
|
esFetInfo[i].resClassDrain = NO_RESCLASS;
|
2017-04-25 14:41:48 +02:00
|
|
|
esFetInfo[i].resClassSub = NO_RESCLASS;
|
|
|
|
|
esFetInfo[i].defSubs = NULL;
|
|
|
|
|
}
|
2021-05-27 22:13:06 +02:00
|
|
|
i = efBuildAddStr(EFDevTypes, &EFDevNumTypes, TT_MAXTYPES, "ndev");
|
2020-09-11 23:29:12 +02:00
|
|
|
esFetInfo[i].resClassSource = esFetInfo[i].resClassDrain = 0 ;
|
2017-04-25 14:41:48 +02:00
|
|
|
esFetInfo[i].resClassSub = NO_RESCLASS ;
|
|
|
|
|
esFetInfo[i].defSubs = "Gnd!";
|
2021-05-27 22:13:06 +02:00
|
|
|
i = efBuildAddStr(EFDevTypes, &EFDevNumTypes, TT_MAXTYPES, "pdev");
|
2020-09-11 23:29:12 +02:00
|
|
|
esFetInfo[i].resClassSource = esFetInfo[i].resClassDrain = 1 ;
|
2017-04-25 14:41:48 +02:00
|
|
|
esFetInfo[i].resClassSub = 8 ;
|
|
|
|
|
esFetInfo[i].defSubs = "Vdd!";
|
2021-05-27 22:13:06 +02:00
|
|
|
i = efBuildAddStr(EFDevTypes, &EFDevNumTypes, TT_MAXTYPES, "nmos");
|
2020-09-11 23:29:12 +02:00
|
|
|
esFetInfo[i].resClassSource = esFetInfo[i].resClassDrain = 0 ;
|
2017-04-25 14:41:48 +02:00
|
|
|
esFetInfo[i].resClassSub = NO_RESCLASS ;
|
|
|
|
|
esFetInfo[i].defSubs = "Gnd!";
|
2021-05-27 22:13:06 +02:00
|
|
|
i = efBuildAddStr(EFDevTypes, &EFDevNumTypes, TT_MAXTYPES, "pmos");
|
2020-09-11 23:29:12 +02:00
|
|
|
esFetInfo[i].resClassSource = esFetInfo[i].resClassDrain = 1 ;
|
2017-04-25 14:41:48 +02:00
|
|
|
esFetInfo[i].resClassSub = 8 ;
|
|
|
|
|
esFetInfo[i].defSubs = "Vdd!";
|
|
|
|
|
/* Process command line arguments */
|
|
|
|
|
|
2022-02-08 22:12:07 +01:00
|
|
|
inName = EFArgs(argc, argv, NULL, spcParseArgs, (ClientData) NULL);
|
2017-04-25 14:41:48 +02:00
|
|
|
if (inName == NULL)
|
|
|
|
|
exit (1);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Initializations specific to this program.
|
2020-05-23 23:13:14 +02:00
|
|
|
* Make output name inName.spice if they weren't
|
2017-04-25 14:41:48 +02:00
|
|
|
* explicitly specified
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
if (spcesOutName == spcesDefaultOut)
|
A number of changes:
1) Corrected spurious error messages about cells already existing
in GDS when using "flatten" or "flatglob".
2) Fixed handling of resistance as a subcircuit parameter
3) Added area and perimeter resistance for a device; this is done
through the "devresist" statement in the tech file, which is an
extension of the original "fetresist" statement. Where "fetresist"
only supported type "linear", "devresist" supports types "area"
and "perimeter".
4) Support for CDL syntax, including generating subcircuit-like
parameters for components starting with SPICE-standard prefixes
like M, R, C, etc., adding "/" between pins and subcircuit name,
and saving the file as ".cdl" instead of ".spice".
5) Estimated L and W for devices whose geometry is complex and do not
reduce to a simple rectangle. L and W are estimated as the square
root of the area.
6) Changed the method of extracting L and W for diodes to use the same
method as capacitors. Note that diodes are not usually specified
by L and W, but if they are, this will produce the right result.
7) Corrected the reported filename and line number when printing error
messages related to errors inside a technology file, when the
technology file uses "include" to combine multiple files.
2025-10-01 21:17:49 +02:00
|
|
|
{
|
|
|
|
|
if (esFormat == CDL)
|
|
|
|
|
sprintf(spcesDefaultOut, "%s.cdl", inName);
|
|
|
|
|
else
|
|
|
|
|
sprintf(spcesDefaultOut, "%s.spice", inName);
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
if ((esSpiceF = fopen(spcesOutName, "w")) == NULL)
|
|
|
|
|
{
|
|
|
|
|
perror(spcesOutName);
|
|
|
|
|
exit (1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Read the hierarchical description of the input circuit */
|
2023-07-28 15:40:41 +02:00
|
|
|
if (EFReadFile(inName, TRUE, esDoExtResis, FALSE, TRUE) == FALSE)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
exit (1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fprintf(esSpiceF, "* %s file created from %s.ext - technology: %s\n\n",
|
|
|
|
|
spiceFormats[esFormat], inName, EFTech);
|
2020-05-23 23:13:14 +02:00
|
|
|
if (esScale < 0)
|
2023-02-16 17:59:13 +01:00
|
|
|
{
|
|
|
|
|
fprintf(esSpiceF,".option scale=");
|
|
|
|
|
esSIvalue(esSpiceF, 1.0E-6 * EFScale / 100.0);
|
|
|
|
|
fprintf(esSpiceF, "\n\n");
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
else
|
|
|
|
|
esScale = EFScale / 100.0;
|
|
|
|
|
|
|
|
|
|
/* Convert the hierarchical description to a flat one */
|
|
|
|
|
flatFlags = EF_FLATNODES;
|
2021-07-11 03:13:24 +02:00
|
|
|
EFOutputFlags |= EF_TRIMGLOB ;
|
2017-04-25 14:41:48 +02:00
|
|
|
if (IS_FINITE_F(EFCapThreshold)) flatFlags |= EF_FLATCAPS;
|
|
|
|
|
if (esFormat == HSPICE) {
|
2021-07-11 03:13:24 +02:00
|
|
|
EFOutputFlags |= EF_TRIMLOCAL ;
|
2017-04-25 14:41:48 +02:00
|
|
|
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))
|
2017-08-02 04:14:42 +02:00
|
|
|
topVisit(efFlatRootDef, FALSE);
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
/* 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);
|
|
|
|
|
|
2021-05-27 22:13:06 +02:00
|
|
|
TTMaskZero(&initMask);
|
|
|
|
|
if (!esDistrJunct)
|
2023-01-27 17:47:37 +01:00
|
|
|
TTMaskSetType(&initMask, efNumResistClasses);
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
if ( esMergeDevsA || esMergeDevsC ) {
|
|
|
|
|
EFVisitDevs(devMergeVisit, (ClientData) NULL);
|
|
|
|
|
TxPrintf("Devs merged: %d\n", esSpiceDevsMerged);
|
|
|
|
|
esFMIndex = 0 ;
|
|
|
|
|
{
|
2025-07-24 15:36:22 +02:00
|
|
|
const devMerge *p;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2025-07-24 15:36:22 +02:00
|
|
|
for ( p = devMergeList ; p != NULL ; p=p->next ) freeMagic((char *)p);
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
} else if ( esDistrJunct )
|
|
|
|
|
EFVisitDevs(devDistJunctVisit, (ClientData) NULL);
|
|
|
|
|
EFVisitDevs(spcdevVisit, (ClientData) NULL);
|
2021-05-27 22:13:06 +02:00
|
|
|
TTMaskZero(&initMask);
|
2023-03-13 18:21:34 +01:00
|
|
|
if (flatFlags & EF_FLATCAPS)
|
2017-04-25 14:41:48 +02:00
|
|
|
EFVisitCaps(spccapVisit, (ClientData) NULL);
|
2023-03-13 18:21:34 +01:00
|
|
|
|
2025-07-20 17:09:08 +02:00
|
|
|
EFVisitResists(spcresistVisit, PTR2CD(NULL));
|
2017-04-25 14:41:48 +02:00
|
|
|
EFVisitSubcircuits(subcktVisit, (ClientData) NULL);
|
2023-03-14 17:53:22 +01:00
|
|
|
esSpiceCapNode = StrDup((char **)NULL, esSpiceDefaultGnd);
|
2017-04-25 14:41:48 +02:00
|
|
|
EFVisitNodes(spcnodeVisit, (ClientData) NULL);
|
|
|
|
|
|
|
|
|
|
if ((esDoSubckt == TRUE) || (locDoSubckt == TRUE))
|
|
|
|
|
fprintf(esSpiceF, ".ends\n");
|
|
|
|
|
|
2020-05-23 23:13:14 +02:00
|
|
|
if (esFormat == HSPICE)
|
2017-04-25 14:41:48 +02:00
|
|
|
printSubcktDict();
|
|
|
|
|
|
2021-12-14 00:05:53 +01:00
|
|
|
EFFlatDone(esFreeNodeClient);
|
|
|
|
|
EFDone(esFreeNodeClient);
|
2021-12-13 04:09:31 +01:00
|
|
|
if (esFormat == HSPICE) {
|
|
|
|
|
HashKill(&subcktNameTable);
|
|
|
|
|
#ifndef UNSORTED_SUBCKT
|
|
|
|
|
DQFree(&subcktNameQueue);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
if (esSpiceF) fclose(esSpiceF);
|
|
|
|
|
|
|
|
|
|
TxPrintf("Memory used: %s\n", RunStats(RS_MEM, NULL, NULL));
|
|
|
|
|
exit (0);
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-18 15:12:51 +02:00
|
|
|
#endif /* EXT2SPICE_MAIN Deprecated */
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
2022-02-08 22:12:07 +01:00
|
|
|
* spcParseArgs --
|
2017-04-25 14:41:48 +02:00
|
|
|
*
|
|
|
|
|
* 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
|
2025-07-18 15:11:04 +02:00
|
|
|
spcParseArgs(
|
|
|
|
|
int *pargc,
|
|
|
|
|
char ***pargv)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
2025-07-24 15:36:22 +02:00
|
|
|
char **argv = *pargv;
|
2017-04-25 14:41:48 +02:00
|
|
|
int argc = *pargc;
|
2023-03-13 18:21:34 +01:00
|
|
|
char *ftmp, *t;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2025-01-10 06:19:52 +01:00
|
|
|
const char usage_text[] = "Usage: ext2spice "
|
2023-03-13 18:21:34 +01:00
|
|
|
"[-B] [-o spicefile] [-M|-m] [-J flat|hier]\n"
|
A number of changes:
1) Corrected spurious error messages about cells already existing
in GDS when using "flatten" or "flatglob".
2) Fixed handling of resistance as a subcircuit parameter
3) Added area and perimeter resistance for a device; this is done
through the "devresist" statement in the tech file, which is an
extension of the original "fetresist" statement. Where "fetresist"
only supported type "linear", "devresist" supports types "area"
and "perimeter".
4) Support for CDL syntax, including generating subcircuit-like
parameters for components starting with SPICE-standard prefixes
like M, R, C, etc., adding "/" between pins and subcircuit name,
and saving the file as ".cdl" instead of ".spice".
5) Estimated L and W for devices whose geometry is complex and do not
reduce to a simple rectangle. L and W are estimated as the square
root of the area.
6) Changed the method of extracting L and W for diodes to use the same
method as capacitors. Note that diodes are not usually specified
by L and W, but if they are, this will produce the right result.
7) Corrected the reported filename and line number when printing error
messages related to errors inside a technology file, when the
technology file uses "include" to combine multiple files.
2025-10-01 21:17:49 +02:00
|
|
|
"[-f spice2|spice3|hspice|ngspice|cdl] [-M] [-m] "
|
2019-10-16 15:17:58 +02:00
|
|
|
"[file]\n";
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
switch (argv[0][1])
|
|
|
|
|
{
|
|
|
|
|
case 'd':
|
|
|
|
|
esDistrJunct = TRUE;
|
|
|
|
|
break;
|
2023-03-13 18:21:34 +01:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
case 'M':
|
|
|
|
|
esMergeDevsA = TRUE;
|
|
|
|
|
break;
|
2023-03-13 18:21:34 +01:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
case 'm':
|
|
|
|
|
esMergeDevsC = TRUE;
|
|
|
|
|
break;
|
2023-03-13 18:21:34 +01:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
case 'B':
|
|
|
|
|
esNoAttrs = TRUE;
|
|
|
|
|
break;
|
2023-03-13 18:21:34 +01:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
case 'F':
|
|
|
|
|
esDevNodesOnly = TRUE;
|
|
|
|
|
break;
|
2023-03-13 18:21:34 +01:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
case 'o':
|
|
|
|
|
if ((spcesOutName = ArgStr(&argc, &argv, "filename")) == NULL)
|
|
|
|
|
goto usage;
|
|
|
|
|
break;
|
|
|
|
|
|
2023-03-13 18:21:34 +01:00
|
|
|
case 'f':
|
|
|
|
|
if ((ftmp = ArgStr(&argc, &argv, "format")) == NULL)
|
2017-04-25 14:41:48 +02:00
|
|
|
goto usage;
|
2023-03-13 18:21:34 +01:00
|
|
|
if (strcasecmp(ftmp, "SPICE2") == 0)
|
2017-04-25 14:41:48 +02:00
|
|
|
esFormat = SPICE2;
|
2023-03-13 18:21:34 +01:00
|
|
|
else if (strcasecmp(ftmp, "SPICE3") == 0)
|
2017-04-25 14:41:48 +02:00
|
|
|
esFormat = SPICE3;
|
2023-03-13 18:21:34 +01:00
|
|
|
else if (strcasecmp(ftmp, "HSPICE") == 0)
|
|
|
|
|
{
|
2017-04-25 14:41:48 +02:00
|
|
|
esFormat = HSPICE;
|
|
|
|
|
esScale = -1.0;
|
2023-03-13 18:21:34 +01:00
|
|
|
}
|
|
|
|
|
else if (strcasecmp(ftmp, "NGSPICE") == 0)
|
2017-04-25 14:41:48 +02:00
|
|
|
esFormat = NGSPICE;
|
A number of changes:
1) Corrected spurious error messages about cells already existing
in GDS when using "flatten" or "flatglob".
2) Fixed handling of resistance as a subcircuit parameter
3) Added area and perimeter resistance for a device; this is done
through the "devresist" statement in the tech file, which is an
extension of the original "fetresist" statement. Where "fetresist"
only supported type "linear", "devresist" supports types "area"
and "perimeter".
4) Support for CDL syntax, including generating subcircuit-like
parameters for components starting with SPICE-standard prefixes
like M, R, C, etc., adding "/" between pins and subcircuit name,
and saving the file as ".cdl" instead of ".spice".
5) Estimated L and W for devices whose geometry is complex and do not
reduce to a simple rectangle. L and W are estimated as the square
root of the area.
6) Changed the method of extracting L and W for diodes to use the same
method as capacitors. Note that diodes are not usually specified
by L and W, but if they are, this will produce the right result.
7) Corrected the reported filename and line number when printing error
messages related to errors inside a technology file, when the
technology file uses "include" to combine multiple files.
2025-10-01 21:17:49 +02:00
|
|
|
else if (strcasecmp(ftmp, "CDL") == 0)
|
|
|
|
|
esFormat = CDL;
|
2023-03-13 18:21:34 +01:00
|
|
|
else goto usage;
|
|
|
|
|
break;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2023-03-13 18:21:34 +01:00
|
|
|
case 'J':
|
|
|
|
|
if ((ftmp = ArgStr(&argc, &argv, "hierAP_SD")) == NULL)
|
2017-04-25 14:41:48 +02:00
|
|
|
goto usage;
|
2023-03-13 18:21:34 +01:00
|
|
|
if ( strcasecmp(ftmp, "HIER") == 0 )
|
2017-04-25 14:41:48 +02:00
|
|
|
esHierAP = TRUE ;
|
2023-03-13 18:21:34 +01:00
|
|
|
else if (strcasecmp(ftmp, "FLAT") == 0 )
|
2017-04-25 14:41:48 +02:00
|
|
|
esHierAP = FALSE ;
|
2023-03-13 18:21:34 +01:00
|
|
|
else goto usage;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2023-03-13 18:21:34 +01:00
|
|
|
break;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2023-03-13 18:21:34 +01:00
|
|
|
case 'y':
|
|
|
|
|
if ((t = ArgStr(&argc, &argv, "cap-accuracy")) == NULL)
|
2017-04-25 14:41:48 +02:00
|
|
|
goto usage;
|
2023-03-13 18:21:34 +01:00
|
|
|
TxPrintf("The cap accuracy flag 'y' is deprecated.\n");
|
|
|
|
|
break;
|
|
|
|
|
|
2019-10-16 15:17:58 +02:00
|
|
|
case 'h': /* -h or -help, as suggested by "ext2spice help" */
|
|
|
|
|
TxPrintf(usage_text);
|
|
|
|
|
break;
|
2023-03-13 18:21:34 +01:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
default:
|
|
|
|
|
TxError("Unrecognized flag: %s\n", argv[0]);
|
|
|
|
|
goto usage;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*pargv = argv;
|
|
|
|
|
*pargc = argc;
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
usage:
|
2019-10-16 15:17:58 +02:00
|
|
|
TxError(usage_text);
|
2017-04-25 14:41:48 +02:00
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* SpiceGetNode --
|
|
|
|
|
*
|
|
|
|
|
* function to find a node given its hierarchical prefix and suffix
|
|
|
|
|
*
|
|
|
|
|
* Results:
|
|
|
|
|
* a pointer to the node struct or NULL
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
EFNode *
|
2025-07-18 15:11:04 +02:00
|
|
|
SpiceGetNode(
|
|
|
|
|
HierName *prefix,
|
|
|
|
|
HierName *suffix)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
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);
|
|
|
|
|
}
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* 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
|
2020-05-23 23:13:14 +02:00
|
|
|
*
|
2017-04-25 14:41:48 +02:00
|
|
|
*
|
|
|
|
|
* Results:
|
|
|
|
|
* TRUE or FALSE
|
|
|
|
|
*
|
|
|
|
|
* Side effects:
|
|
|
|
|
* None.
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
2025-07-18 15:11:04 +02:00
|
|
|
bool
|
|
|
|
|
extHierSDAttr(
|
|
|
|
|
DevTerm *term)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
bool r = esHierAP;
|
|
|
|
|
|
|
|
|
|
if (term->dterm_attrs)
|
|
|
|
|
{
|
2020-05-23 23:13:14 +02:00
|
|
|
if (Match(ATTR_HIERAP, term->dterm_attrs) != FALSE)
|
2017-04-25 14:41:48 +02:00
|
|
|
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).
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
2025-07-18 23:02:48 +02:00
|
|
|
|
|
|
|
|
/* @typedef cb_extflat_visitsubcircuits_t (UNUSED) */
|
2017-04-25 14:41:48 +02:00
|
|
|
int
|
2025-07-18 15:11:04 +02:00
|
|
|
subcktVisit(
|
|
|
|
|
Use *use,
|
|
|
|
|
HierName *hierName,
|
|
|
|
|
bool is_top) /* TRUE if this is the top-level cell */
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
EFNode *snode;
|
|
|
|
|
Def *def = use->use_def;
|
2021-12-13 17:33:02 +01:00
|
|
|
EFNodeName *sname, *nodeName;
|
|
|
|
|
HashSearch hs;
|
|
|
|
|
HashEntry *he;
|
2025-07-24 15:36:22 +02:00
|
|
|
int portmax, portidx, tchars;
|
2017-04-25 14:41:48 +02:00
|
|
|
char stmp[MAX_STR_SIZE];
|
|
|
|
|
char *instname, *subcktname;
|
|
|
|
|
DevParam *plist, *pptr;
|
2019-10-29 18:55:28 +01:00
|
|
|
EFNodeName **nodeList;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
2018-12-12 23:23:35 +01:00
|
|
|
/* NOTE: This really needs to update subcktNameTable so that */
|
|
|
|
|
/* it tracks between instance names and node names, when using */
|
|
|
|
|
/* HSPICE format + esDoRenumber. */
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
fprintf(esSpiceF, "X%d", esSbckNum++);
|
|
|
|
|
tchars = 5;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2021-07-11 03:13:24 +02:00
|
|
|
int savflags = EFOutputFlags;
|
2021-07-11 03:27:19 +02:00
|
|
|
EFOutputFlags &= ~EF_TRIM_MASK;
|
|
|
|
|
EFOutputFlags |= EF_CONVERTCOMMA; // Only substitute commas on subcircuit names
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
/* 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);
|
2021-07-11 03:13:24 +02:00
|
|
|
EFOutputFlags = savflags;
|
2017-04-25 14:41:48 +02:00
|
|
|
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. */
|
|
|
|
|
|
2022-01-12 21:30:07 +01:00
|
|
|
portmax = EFGetPortMax(def);
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
if (portmax < 0)
|
|
|
|
|
{
|
|
|
|
|
/* No port order declared; print them as we see them. */
|
|
|
|
|
/* This shouldn't happen for proper .ext files written */
|
2021-08-04 19:54:08 +02:00
|
|
|
/* by the magic extractor, since explicit port order is */
|
|
|
|
|
/* generated during topVisit(). */
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2021-12-13 17:33:02 +01:00
|
|
|
HashStartSearch(&hs);
|
2024-10-04 18:21:15 +02:00
|
|
|
while ((he = HashNext(&def->def_nodes, &hs)))
|
2021-12-13 17:33:02 +01:00
|
|
|
{
|
|
|
|
|
sname = (EFNodeName *) HashGetValue(he);
|
|
|
|
|
if (sname == NULL) continue;
|
|
|
|
|
snode = sname->efnn_node;
|
|
|
|
|
|
|
|
|
|
if (snode && (snode->efnode_flags & EF_PORT))
|
|
|
|
|
for (nodeName = sname; nodeName != NULL; nodeName = nodeName->efnn_next)
|
2017-04-25 14:41:48 +02:00
|
|
|
if (nodeName->efnn_port >= 0)
|
|
|
|
|
{
|
|
|
|
|
portmax++;
|
|
|
|
|
if (tchars > 80)
|
|
|
|
|
{
|
|
|
|
|
fprintf(esSpiceF, "\n+");
|
|
|
|
|
tchars = 1;
|
|
|
|
|
}
|
|
|
|
|
tchars += spcdevOutNode(hierName, nodeName->efnn_hier,
|
2020-05-23 23:13:14 +02:00
|
|
|
"subcircuit", esSpiceF);
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* Port numbers need not start at zero or be contiguous. */
|
|
|
|
|
/* They will be printed in numerical order. */
|
|
|
|
|
|
2021-12-03 18:29:02 +01:00
|
|
|
nodeList = (EFNodeName **)mallocMagic((portmax + 1) * sizeof(EFNodeName *));
|
|
|
|
|
for (portidx = 0; portidx <= portmax; portidx++)
|
2019-10-29 18:55:28 +01:00
|
|
|
nodeList[portidx] = (EFNodeName *)NULL;
|
|
|
|
|
|
2021-12-13 17:33:02 +01:00
|
|
|
HashStartSearch(&hs);
|
2024-10-04 18:21:15 +02:00
|
|
|
while ((he = HashNext(&def->def_nodes, &hs)))
|
2021-12-13 17:33:02 +01:00
|
|
|
{
|
|
|
|
|
sname = (EFNodeName *) HashGetValue(he);
|
|
|
|
|
if (sname == NULL) continue;
|
|
|
|
|
snode = sname->efnn_node;
|
|
|
|
|
|
|
|
|
|
if ((snode == NULL) || !(snode->efnode_flags & EF_PORT)) continue;
|
|
|
|
|
|
|
|
|
|
for (nodeName = sname; nodeName != NULL; nodeName = nodeName->efnn_next)
|
2019-10-29 18:55:28 +01:00
|
|
|
{
|
|
|
|
|
portidx = nodeName->efnn_port;
|
2019-10-31 20:19:30 +01:00
|
|
|
if (portidx < 0) continue;
|
2019-10-29 18:55:28 +01:00
|
|
|
if (nodeList[portidx] == NULL)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
2019-10-29 18:55:28 +01:00
|
|
|
nodeList[portidx] = nodeName;
|
|
|
|
|
}
|
|
|
|
|
else if (EFHNBest(nodeName->efnn_hier, nodeList[portidx]->efnn_hier))
|
|
|
|
|
{
|
|
|
|
|
nodeList[portidx] = nodeName;
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-03 18:29:02 +01:00
|
|
|
for (portidx = 0; portidx <= portmax; portidx++)
|
2019-10-29 18:55:28 +01:00
|
|
|
{
|
|
|
|
|
nodeName = nodeList[portidx];
|
|
|
|
|
|
2020-01-13 18:58:04 +01:00
|
|
|
if (nodeName != NULL)
|
2019-10-29 18:55:28 +01:00
|
|
|
{
|
|
|
|
|
if (tchars > 80)
|
|
|
|
|
{
|
|
|
|
|
fprintf(esSpiceF, "\n+");
|
|
|
|
|
tchars = 1;
|
|
|
|
|
}
|
|
|
|
|
tchars += spcdevOutNode(hierName, nodeName->efnn_hier,
|
2020-05-23 23:13:14 +02:00
|
|
|
"subcircuit", esSpiceF);
|
2019-10-29 18:55:28 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
freeMagic(nodeList);
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
|
2021-03-07 01:39:34 +01:00
|
|
|
/* SPICE subcircuit names must begin with A-Z. */
|
2017-08-02 04:14:42 +02:00
|
|
|
subcktname = def->def_name;
|
2021-03-07 01:39:34 +01:00
|
|
|
if (!isalpha(*subcktname))
|
|
|
|
|
{
|
|
|
|
|
subcktname = mallocMagic(2 + strlen(def->def_name));
|
|
|
|
|
sprintf(subcktname, "x%s", def->def_name);
|
|
|
|
|
freeMagic(def->def_name);
|
|
|
|
|
def->def_name = subcktname;
|
|
|
|
|
}
|
2017-08-02 04:14:42 +02:00
|
|
|
|
|
|
|
|
if (tchars > 80) fprintf(esSpiceF, "\n+");
|
A number of changes:
1) Corrected spurious error messages about cells already existing
in GDS when using "flatten" or "flatglob".
2) Fixed handling of resistance as a subcircuit parameter
3) Added area and perimeter resistance for a device; this is done
through the "devresist" statement in the tech file, which is an
extension of the original "fetresist" statement. Where "fetresist"
only supported type "linear", "devresist" supports types "area"
and "perimeter".
4) Support for CDL syntax, including generating subcircuit-like
parameters for components starting with SPICE-standard prefixes
like M, R, C, etc., adding "/" between pins and subcircuit name,
and saving the file as ".cdl" instead of ".spice".
5) Estimated L and W for devices whose geometry is complex and do not
reduce to a simple rectangle. L and W are estimated as the square
root of the area.
6) Changed the method of extracting L and W for diodes to use the same
method as capacitors. Note that diodes are not usually specified
by L and W, but if they are, this will produce the right result.
7) Corrected the reported filename and line number when printing error
messages related to errors inside a technology file, when the
technology file uses "include" to combine multiple files.
2025-10-01 21:17:49 +02:00
|
|
|
if (esFormat == CDL) fprintf(esSpiceF, " /");
|
2017-08-02 04:14:42 +02:00
|
|
|
fprintf(esSpiceF, " %s", subcktname); /* subcircuit model name */
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
// 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));
|
2020-05-23 23:13:14 +02:00
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
freeMagic(instname);
|
2017-08-02 04:14:42 +02:00
|
|
|
fprintf(esSpiceF, "\n");
|
2017-04-25 14:41:48 +02:00
|
|
|
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.
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
2025-07-18 23:02:48 +02:00
|
|
|
|
|
|
|
|
/* @typedef cb_extflat_visitsubcircuits_t (UNUSED) */
|
2017-04-25 14:41:48 +02:00
|
|
|
int
|
2025-07-18 15:11:04 +02:00
|
|
|
subcktUndef(
|
|
|
|
|
Use *use,
|
|
|
|
|
HierName *hierName,
|
|
|
|
|
bool is_top) /* TRUE if this is the top-level cell */
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
Def *def = use->use_def;
|
|
|
|
|
|
|
|
|
|
def->def_flags &= ~(DEF_SUBCIRCUIT);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-04 19:54:08 +02:00
|
|
|
/* Define a linked node name list */
|
|
|
|
|
|
|
|
|
|
typedef struct _lnn {
|
|
|
|
|
EFNodeName *lnn_nodeName;
|
|
|
|
|
struct _lnn *lnn_next;
|
|
|
|
|
} linkedNodeName;
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* 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
|
2017-08-02 04:14:42 +02:00
|
|
|
* 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.
|
2017-04-25 14:41:48 +02:00
|
|
|
*
|
2020-11-24 21:30:49 +01:00
|
|
|
* 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.
|
|
|
|
|
*
|
2017-04-25 14:41:48 +02:00
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
void
|
2025-07-18 15:11:04 +02:00
|
|
|
topVisit(
|
|
|
|
|
Def *def,
|
|
|
|
|
bool doStub)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
2025-07-24 15:36:22 +02:00
|
|
|
EFNode *snode;
|
2017-04-25 14:41:48 +02:00
|
|
|
EFNodeName *sname, *nodeName;
|
|
|
|
|
HashSearch hs;
|
2022-01-13 23:00:07 +01:00
|
|
|
HashEntry *he, *hep, *heh;
|
2020-11-24 21:30:49 +01:00
|
|
|
HashTable portNameTable;
|
2021-08-04 19:54:08 +02:00
|
|
|
int portorder, portmax, tchars;
|
2021-07-25 19:49:13 +02:00
|
|
|
bool explicit;
|
2017-04-25 14:41:48 +02:00
|
|
|
DevParam *plist, *pptr;
|
|
|
|
|
char *instname;
|
|
|
|
|
char *subcktname;
|
2025-07-23 21:58:41 +02:00
|
|
|
const char *pname;
|
2021-10-05 01:52:22 +02:00
|
|
|
char **sorted_ports;
|
2021-08-04 19:54:08 +02:00
|
|
|
linkedNodeName *lnn = NULL;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2022-05-04 22:43:38 +02:00
|
|
|
/* Primitive devices are not output at all */
|
|
|
|
|
if (def->def_flags & DEF_PRIMITIVE) return;
|
|
|
|
|
|
2020-11-24 21:30:49 +01:00
|
|
|
HashInit(&portNameTable, 32, HT_STRINGKEYS);
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/* SPICE subcircuit names must begin with A-Z. This will also be */
|
|
|
|
|
/* enforced when writing X subcircuit calls. */
|
|
|
|
|
subcktname = def->def_name;
|
2021-03-07 01:39:34 +01:00
|
|
|
if (!isalpha(*subcktname))
|
|
|
|
|
{
|
|
|
|
|
subcktname = mallocMagic(2 + strlen(def->def_name));
|
|
|
|
|
sprintf(subcktname, "x%s", def->def_name);
|
|
|
|
|
freeMagic(def->def_name);
|
|
|
|
|
def->def_name = subcktname;
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
2024-10-04 18:21:15 +02:00
|
|
|
while ((he = HashNext(&def->def_nodes, &hs)))
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
sname = (EFNodeName *) HashGetValue(he);
|
|
|
|
|
if (sname == NULL) continue;
|
|
|
|
|
snode = sname->efnn_node;
|
2020-03-20 19:50:56 +01:00
|
|
|
if ((!snode) || (!(snode->efnode_flags & EF_PORT))) continue;
|
2021-07-25 19:49:13 +02:00
|
|
|
explicit = FALSE;
|
2024-08-06 17:17:27 +02:00
|
|
|
portorder = snode->efnode_name->efnn_port;
|
|
|
|
|
if (portorder > portmax) portmax = portorder;
|
|
|
|
|
if (portorder != -1) explicit = TRUE;
|
2017-04-25 14:41:48 +02:00
|
|
|
for (nodeName = sname; nodeName != NULL; nodeName = nodeName->efnn_next)
|
|
|
|
|
{
|
|
|
|
|
portorder = nodeName->efnn_port;
|
|
|
|
|
if (portorder > portmax) portmax = portorder;
|
2021-07-25 19:49:13 +02:00
|
|
|
if (portorder != -1) explicit = TRUE;
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
2021-08-04 19:54:08 +02:00
|
|
|
if (explicit == FALSE)
|
|
|
|
|
{
|
|
|
|
|
/* Tag this an an implicit port (port without an assigned index) */
|
|
|
|
|
linkedNodeName *newlnn =
|
|
|
|
|
(linkedNodeName *)mallocMagic(sizeof(linkedNodeName));
|
|
|
|
|
newlnn->lnn_next = lnn;
|
|
|
|
|
newlnn->lnn_nodeName = sname;
|
|
|
|
|
lnn = newlnn;
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
|
2021-08-04 19:54:08 +02:00
|
|
|
/* Make all port numbers explicit (unless this is a black-box */
|
|
|
|
|
/* circuit "ext2spice blackbox on" is in effect). */
|
|
|
|
|
|
|
|
|
|
while (lnn != NULL)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
2021-08-04 19:54:08 +02:00
|
|
|
sname = lnn->lnn_nodeName;
|
|
|
|
|
if (esDoBlackBox == FALSE || !(def->def_flags & DEF_ABSTRACT))
|
|
|
|
|
sname->efnn_port = ++portmax;
|
|
|
|
|
freeMagic(lnn);
|
|
|
|
|
lnn = lnn->lnn_next;
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2021-10-05 01:52:22 +02:00
|
|
|
/* Port numbers need not start at zero or be contiguous. They will be */
|
|
|
|
|
/* printed in numerical order. This is done by allocating space for */
|
|
|
|
|
/* the output first and generating text into the allocated array */
|
|
|
|
|
/* indexed by port, to avoid multiple scans through the hash table. */
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2021-10-05 01:52:22 +02:00
|
|
|
sorted_ports = (char **)mallocMagic((portmax + 1) * sizeof(char *));
|
2022-01-12 23:19:29 +01:00
|
|
|
for (portorder = 0; portorder <= portmax; portorder++)
|
|
|
|
|
sorted_ports[portorder] = NULL;
|
2021-10-05 01:52:22 +02:00
|
|
|
|
|
|
|
|
HashStartSearch(&hs);
|
2024-10-04 18:21:15 +02:00
|
|
|
while ((he = HashNext(&def->def_nodes, &hs)))
|
2021-08-04 19:54:08 +02:00
|
|
|
{
|
2021-10-05 01:52:22 +02:00
|
|
|
char stmp[MAX_STR_SIZE];
|
|
|
|
|
int portidx;
|
2021-08-04 19:54:08 +02:00
|
|
|
|
2021-10-05 01:52:22 +02:00
|
|
|
sname = (EFNodeName *) HashGetValue(he);
|
|
|
|
|
if (sname == NULL) continue; /* Should not happen */
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2022-01-12 21:30:07 +01:00
|
|
|
snode = sname->efnn_node;
|
2021-10-05 01:52:22 +02:00
|
|
|
if ((!snode) || (!(snode->efnode_flags & EF_PORT))) continue;
|
2021-08-04 19:54:08 +02:00
|
|
|
|
2021-10-05 01:52:22 +02:00
|
|
|
for (nodeName = sname; nodeName != NULL; nodeName = nodeName->efnn_next)
|
|
|
|
|
{
|
|
|
|
|
portidx = nodeName->efnn_port;
|
2021-10-07 16:58:21 +02:00
|
|
|
if (portidx < 0) continue;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2022-01-13 23:00:07 +01:00
|
|
|
/* Check if the same hierName is recorded in the flattened/optimized
|
|
|
|
|
* def's efNodeHashTable. If not, then it has been optimized out
|
|
|
|
|
* and should be removed from the port list.
|
|
|
|
|
*/
|
|
|
|
|
if (def->def_flags & DEF_ABSTRACT)
|
2022-01-14 17:07:08 +01:00
|
|
|
heh = HashLookOnly(&efNodeHashTable, (char *)nodeName->efnn_hier);
|
2022-01-13 23:00:07 +01:00
|
|
|
else
|
2022-01-14 17:07:08 +01:00
|
|
|
heh = HashLookOnly(&efNodeHashTable,
|
|
|
|
|
(char *)snode->efnode_name->efnn_hier);
|
2022-01-13 23:00:07 +01:00
|
|
|
|
2021-10-05 01:52:22 +02:00
|
|
|
/* 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.
|
|
|
|
|
*/
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2021-10-05 01:52:22 +02:00
|
|
|
if (def->def_flags & DEF_ABSTRACT)
|
|
|
|
|
{
|
|
|
|
|
EFHNSprintf(stmp, nodeName->efnn_hier);
|
|
|
|
|
pname = stmp;
|
|
|
|
|
}
|
|
|
|
|
else
|
2023-06-13 23:14:36 +02:00
|
|
|
// pname = nodeSpiceName(snode->efnode_name->efnn_hier, NULL);
|
|
|
|
|
pname = nodeSpiceName(nodeName->efnn_hier, NULL);
|
2020-11-24 21:30:49 +01:00
|
|
|
|
2024-10-04 21:09:28 +02:00
|
|
|
if (heh == (HashEntry *)NULL) /* pname now resolved for log output */
|
|
|
|
|
{
|
|
|
|
|
/* Port was optimized out */
|
|
|
|
|
snode->efnode_flags &= ~EF_PORT;
|
|
|
|
|
TxPrintf("Note: Port %s was optimized out of %s\n",
|
|
|
|
|
pname, def->def_name);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-13 17:33:02 +01:00
|
|
|
hep = HashLookOnly(&portNameTable, pname);
|
2022-01-13 23:00:07 +01:00
|
|
|
if (hep == (HashEntry *)NULL)
|
2021-10-05 01:52:22 +02:00
|
|
|
{
|
2022-01-13 23:00:07 +01:00
|
|
|
hep = HashFind(&portNameTable, pname);
|
|
|
|
|
HashSetValue(hep, (ClientData)(pointertype)nodeName->efnn_port);
|
|
|
|
|
if (sorted_ports[portidx] == NULL)
|
|
|
|
|
sorted_ports[portidx] = StrDup((char **)NULL, pname);
|
2021-10-05 01:52:22 +02:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2022-01-13 23:00:07 +01:00
|
|
|
/* Node that was unassigned has been found to be
|
|
|
|
|
* a repeat (see NOTE at top), so make sure its
|
|
|
|
|
* port number is set correctly.
|
|
|
|
|
*/
|
|
|
|
|
nodeName->efnn_port = (int)(pointertype)HashGetValue(hep);
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-11-24 21:30:49 +01:00
|
|
|
HashKill(&portNameTable);
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2021-10-05 01:52:22 +02:00
|
|
|
/* Output all ports, in order */
|
|
|
|
|
|
|
|
|
|
for (portorder = 0; portorder <= portmax; portorder++)
|
|
|
|
|
{
|
|
|
|
|
if (sorted_ports[portorder] != NULL)
|
|
|
|
|
{
|
|
|
|
|
if (tchars > 80)
|
|
|
|
|
{
|
|
|
|
|
/* Line continuation */
|
|
|
|
|
fprintf(esSpiceF, "\n+");
|
|
|
|
|
tchars = 1;
|
|
|
|
|
}
|
|
|
|
|
fprintf(esSpiceF, " %s", sorted_ports[portorder]);
|
2021-10-05 14:50:45 +02:00
|
|
|
tchars += strlen(sorted_ports[portorder]) + 1;
|
2021-10-05 01:52:22 +02:00
|
|
|
|
|
|
|
|
freeMagic(sorted_ports[portorder]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
freeMagic(sorted_ports);
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
// 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;
|
2020-05-23 23:13:14 +02:00
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
freeMagic(instname);
|
|
|
|
|
|
|
|
|
|
fprintf(esSpiceF, "\n");
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-06 23:42:34 +02:00
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* spcWriteValue ---
|
|
|
|
|
*
|
|
|
|
|
* Special handling for CDL format: Output any resistor or capacitor value,
|
|
|
|
|
* value only, no parameter name.
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
2025-10-21 15:15:05 +02:00
|
|
|
void
|
2025-10-06 23:42:34 +02:00
|
|
|
spcWriteValue(
|
|
|
|
|
Dev *dev, /* Dev being output */
|
|
|
|
|
HierName *hierName) /* Hierarchical path down to this dev */
|
|
|
|
|
{
|
|
|
|
|
DevParam *plist;
|
|
|
|
|
|
|
|
|
|
plist = efGetDeviceParams(EFDevTypes[dev->dev_type]);
|
|
|
|
|
while (plist != NULL)
|
|
|
|
|
{
|
|
|
|
|
switch (plist->parm_type[0])
|
|
|
|
|
{
|
|
|
|
|
case 'r':
|
|
|
|
|
if (*(plist->parm_name) == '\0')
|
|
|
|
|
{
|
|
|
|
|
fprintf(esSpiceF, " ");
|
|
|
|
|
esSIvalue(esSpiceF, (double)dev->dev_res);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'c':
|
|
|
|
|
if (*(plist->parm_name) == '\0')
|
|
|
|
|
{
|
|
|
|
|
fprintf(esSpiceF, " ");
|
|
|
|
|
esSIvalue(esSpiceF, (double)dev->dev_res);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
plist = plist->parm_next;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-06 22:40:19 +02:00
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* spcWriteSubParam ---
|
|
|
|
|
*
|
|
|
|
|
* Special handling for CDL format: Output any substrate parameter before
|
|
|
|
|
* the device model. Why this makes sense, I have no idea.
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
spcWriteSubParam(
|
|
|
|
|
Dev *dev, /* Dev being output */
|
|
|
|
|
HierName *hierName) /* Hierarchical path down to this dev */
|
|
|
|
|
{
|
|
|
|
|
bool retval; /* True if substrate parameter was output */
|
|
|
|
|
DevParam *plist;
|
|
|
|
|
|
|
|
|
|
retval = FALSE;
|
|
|
|
|
plist = efGetDeviceParams(EFDevTypes[dev->dev_type]);
|
|
|
|
|
while (plist != NULL)
|
|
|
|
|
{
|
|
|
|
|
switch (plist->parm_type[0])
|
|
|
|
|
{
|
|
|
|
|
case 's':
|
|
|
|
|
if (dev->dev_subsnode == NULL)
|
|
|
|
|
TxError("Error: No substrate definition for device %s\n",
|
|
|
|
|
EFDevTypes[dev->dev_type]);
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
fprintf(esSpiceF, " %s=", plist->parm_name);
|
|
|
|
|
spcdevSubstrate(hierName,
|
|
|
|
|
dev->dev_subsnode->efnode_name->efnn_hier,
|
|
|
|
|
dev->dev_type, esSpiceF);
|
|
|
|
|
retval = TRUE;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
plist = plist->parm_next;
|
|
|
|
|
}
|
|
|
|
|
return retval;
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* 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
|
2025-07-18 15:11:04 +02:00
|
|
|
spcWriteParams(
|
|
|
|
|
Dev *dev, /* Dev being output */
|
|
|
|
|
HierName *hierName, /* Hierarchical path down to this dev */
|
|
|
|
|
float scale, /* Scale transform for output */
|
|
|
|
|
int l, /* Device length, in internal units */
|
|
|
|
|
int w, /* Device width, in internal units */
|
2025-10-06 22:40:19 +02:00
|
|
|
float sdM, /* Device multiplier */
|
|
|
|
|
bool subdone) /* If TRUE, substrate parameter was already output */
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
bool hierD;
|
2022-12-15 03:40:24 +01:00
|
|
|
DevParam *plist, *dparam;
|
2017-04-25 14:41:48 +02:00
|
|
|
int parmval;
|
2025-07-24 15:36:22 +02:00
|
|
|
EFNode *dnode;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
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)
|
2024-09-30 08:46:35 +02:00
|
|
|
fprintf(esSpiceF, "%g", (double)parmval * (double)scale * (double)scale
|
|
|
|
|
* (double)esScale * (double)esScale * plist->parm_scale
|
2017-04-25 14:41:48 +02:00
|
|
|
* 1E-12);
|
|
|
|
|
else
|
2023-06-22 02:44:38 +02:00
|
|
|
esSIvalue(esSpiceF, 1.0E-12 * (parmval + plist->parm_offset)
|
|
|
|
|
* scale * scale * esScale * esScale);
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
2020-05-23 23:13:14 +02:00
|
|
|
else
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
2020-09-11 23:29:12 +02:00
|
|
|
int pn, resclass;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
pn = plist->parm_type[1] - '0';
|
|
|
|
|
if (pn >= dev->dev_nterm) pn = dev->dev_nterm - 1;
|
|
|
|
|
|
|
|
|
|
hierD = extHierSDAttr(&dev->dev_terms[pn]);
|
|
|
|
|
|
2021-03-18 16:52:56 +01:00
|
|
|
resclass = (pn > 1) ? esFetInfo[dev->dev_type].resClassDrain :
|
2020-09-11 23:29:12 +02:00
|
|
|
esFetInfo[dev->dev_type].resClassSource;
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
// 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,
|
2020-09-11 23:29:12 +02:00
|
|
|
resclass, scale, plist->parm_type,
|
2017-04-25 14:41:48 +02:00
|
|
|
plist->parm_next->parm_type,
|
|
|
|
|
sdM, esSpiceF);
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
dnode = SpiceGetNode(hierName,
|
|
|
|
|
dev->dev_terms[pn].dterm_node->efnode_name->efnn_hier);
|
2023-02-16 17:59:13 +01:00
|
|
|
spcnAP(&dev->dev_terms[pn], dnode, resclass, scale,
|
|
|
|
|
plist->parm_name, plist->parm_next->parm_name,
|
2017-04-25 14:41:48 +02:00
|
|
|
sdM, esSpiceF, w);
|
|
|
|
|
}
|
|
|
|
|
plist = plist->parm_next;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (hierD)
|
|
|
|
|
spcnAPHier(&dev->dev_terms[pn], hierName,
|
2020-09-11 23:29:12 +02:00
|
|
|
resclass, scale, plist->parm_type, NULL,
|
2017-04-25 14:41:48 +02:00
|
|
|
sdM, esSpiceF);
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
dnode = SpiceGetNode(hierName,
|
|
|
|
|
dev->dev_terms[pn].dterm_node->efnode_name->efnn_hier);
|
2023-02-16 17:59:13 +01:00
|
|
|
spcnAP(&dev->dev_terms[pn], dnode, resclass, scale,
|
|
|
|
|
plist->parm_name, NULL, sdM, esSpiceF, w);
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
case 'p':
|
2022-12-15 03:40:24 +01:00
|
|
|
// Check for perimeter of terminal node vs. device perimeter
|
2017-04-25 14:41:48 +02:00
|
|
|
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)
|
2024-09-30 08:46:50 +02:00
|
|
|
fprintf(esSpiceF, "%g", (double)parmval * scale
|
2017-04-25 14:41:48 +02:00
|
|
|
* esScale * plist->parm_scale * 1E-6);
|
|
|
|
|
else
|
2023-06-22 02:44:38 +02:00
|
|
|
esSIvalue(esSpiceF, 1.0E-12 * (parmval + plist->parm_offset)
|
|
|
|
|
* scale * esScale);
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
2020-05-23 23:13:14 +02:00
|
|
|
else
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
2020-09-11 23:29:12 +02:00
|
|
|
int pn, resclass;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
pn = plist->parm_type[1] - '0';
|
|
|
|
|
if (pn >= dev->dev_nterm) pn = dev->dev_nterm - 1;
|
|
|
|
|
|
2021-03-18 16:52:56 +01:00
|
|
|
resclass = (pn > 1) ? esFetInfo[dev->dev_type].resClassDrain :
|
2020-09-11 23:29:12 +02:00
|
|
|
esFetInfo[dev->dev_type].resClassSource;
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
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,
|
2020-09-11 23:29:12 +02:00
|
|
|
resclass, scale, plist->parm_next->parm_type,
|
2017-04-25 14:41:48 +02:00
|
|
|
plist->parm_type, sdM, esSpiceF);
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
dnode = SpiceGetNode(hierName,
|
|
|
|
|
dev->dev_terms[pn].dterm_node->efnode_name->efnn_hier);
|
2023-02-16 17:59:13 +01:00
|
|
|
spcnAP(&dev->dev_terms[pn], dnode, resclass, scale,
|
|
|
|
|
plist->parm_next->parm_name,
|
2017-04-25 14:41:48 +02:00
|
|
|
plist->parm_name, sdM, esSpiceF, w);
|
|
|
|
|
}
|
|
|
|
|
plist = plist->parm_next;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (hierD)
|
|
|
|
|
spcnAPHier(&dev->dev_terms[pn], hierName,
|
2020-09-11 23:29:12 +02:00
|
|
|
resclass, scale, NULL, plist->parm_type,
|
2017-04-25 14:41:48 +02:00
|
|
|
sdM, esSpiceF);
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
dnode = SpiceGetNode(hierName,
|
|
|
|
|
dev->dev_terms[pn].dterm_node->efnode_name->efnn_hier);
|
2023-02-16 17:59:13 +01:00
|
|
|
spcnAP(&dev->dev_terms[pn], dnode, resclass, scale,
|
|
|
|
|
NULL, plist->parm_name, sdM, esSpiceF, w);
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'l':
|
2022-12-15 03:40:24 +01:00
|
|
|
// Check for length of device vs. depth of terminal
|
|
|
|
|
if (plist->parm_type[1] == '\0' || plist->parm_type[1] == '0')
|
|
|
|
|
{
|
|
|
|
|
fprintf(esSpiceF, " %s=", plist->parm_name);
|
|
|
|
|
if (esScale < 0)
|
|
|
|
|
fprintf(esSpiceF, "%g", l * scale);
|
|
|
|
|
else if (plist->parm_scale != 1.0)
|
2024-09-30 08:47:05 +02:00
|
|
|
fprintf(esSpiceF, "%g", (double)l * scale * esScale
|
2017-04-25 14:41:48 +02:00
|
|
|
* plist->parm_scale * 1E-6);
|
2022-12-15 03:40:24 +01:00
|
|
|
else
|
2023-06-22 02:44:38 +02:00
|
|
|
esSIvalue(esSpiceF, 1.0E-6 * (l + plist->parm_offset)
|
|
|
|
|
* scale * esScale);
|
2022-12-15 03:40:24 +01:00
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
else
|
2022-12-15 03:40:24 +01:00
|
|
|
{
|
|
|
|
|
/* l1, l2, etc. used to indicate the length of the terminal */
|
|
|
|
|
/* Find the value in dev_params */
|
|
|
|
|
for (dparam = dev->dev_params; dparam; dparam = dparam->parm_next)
|
|
|
|
|
{
|
|
|
|
|
if ((strlen(dparam->parm_name) > 2) &&
|
|
|
|
|
(dparam->parm_name[0] == 'l') &&
|
|
|
|
|
(dparam->parm_name[1] == plist->parm_type[1]) &&
|
|
|
|
|
(dparam->parm_name[2] == '='))
|
|
|
|
|
{
|
|
|
|
|
int dval;
|
|
|
|
|
if (sscanf(&dparam->parm_name[3], "%d", &dval) == 1)
|
|
|
|
|
{
|
|
|
|
|
fprintf(esSpiceF, " %s=", plist->parm_name);
|
|
|
|
|
if (esScale < 0)
|
|
|
|
|
fprintf(esSpiceF, "%g", dval * scale);
|
|
|
|
|
else if (plist->parm_scale != 1.0)
|
2024-09-30 00:00:00 +02:00
|
|
|
fprintf(esSpiceF, "%g", (double)dval * scale * esScale
|
2022-12-15 03:40:24 +01:00
|
|
|
* plist->parm_scale * 1E-6);
|
|
|
|
|
else
|
2023-06-22 02:44:38 +02:00
|
|
|
esSIvalue(esSpiceF, (dval + plist->parm_offset)
|
|
|
|
|
* scale * esScale * 1.0E-6);
|
2022-12-15 03:40:24 +01:00
|
|
|
dparam->parm_name[0] = '\0';
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
break;
|
2022-12-15 03:40:24 +01:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
case 'w':
|
2024-12-06 22:42:43 +01:00
|
|
|
// Check for width of device vs. width of terminal
|
|
|
|
|
if (plist->parm_type[1] == '\0' || plist->parm_type[1] == '0')
|
|
|
|
|
{
|
|
|
|
|
fprintf(esSpiceF, " %s=", plist->parm_name);
|
|
|
|
|
if (esScale < 0)
|
|
|
|
|
fprintf(esSpiceF, "%g", w * scale);
|
|
|
|
|
else if (plist->parm_scale != 1.0)
|
|
|
|
|
fprintf(esSpiceF, "%g", (double)w * scale * esScale
|
2017-04-25 14:41:48 +02:00
|
|
|
* plist->parm_scale * 1E-6);
|
2024-12-06 22:42:43 +01:00
|
|
|
else
|
|
|
|
|
esSIvalue(esSpiceF, 1.0E-6 * (w + plist->parm_offset)
|
2023-06-22 02:44:38 +02:00
|
|
|
* scale * esScale);
|
2024-12-06 22:42:43 +01:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* w1, w2, etc. used to indicate the width of the terminal */
|
|
|
|
|
/* Find the value in dev_params */
|
|
|
|
|
for (dparam = dev->dev_params; dparam; dparam = dparam->parm_next)
|
|
|
|
|
{
|
|
|
|
|
if ((strlen(dparam->parm_name) > 2) &&
|
|
|
|
|
(dparam->parm_name[0] == 'w') &&
|
|
|
|
|
(dparam->parm_name[1] == plist->parm_type[1]) &&
|
|
|
|
|
(dparam->parm_name[2] == '='))
|
|
|
|
|
{
|
|
|
|
|
int dval;
|
|
|
|
|
if (sscanf(&dparam->parm_name[3], "%d", &dval) == 1)
|
|
|
|
|
{
|
|
|
|
|
fprintf(esSpiceF, " %s=", plist->parm_name);
|
|
|
|
|
if (esScale < 0)
|
|
|
|
|
fprintf(esSpiceF, "%g", dval * scale);
|
|
|
|
|
else if (plist->parm_scale != 1.0)
|
|
|
|
|
fprintf(esSpiceF, "%g", (double)dval * scale * esScale
|
|
|
|
|
* plist->parm_scale * 1E-6);
|
|
|
|
|
else
|
|
|
|
|
esSIvalue(esSpiceF, (dval + plist->parm_offset)
|
|
|
|
|
* scale * esScale * 1.0E-6);
|
|
|
|
|
dparam->parm_name[0] = '\0';
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
break;
|
|
|
|
|
case 's':
|
2025-10-06 22:40:19 +02:00
|
|
|
if (!subdone)
|
|
|
|
|
{
|
|
|
|
|
if (dev->dev_subsnode == NULL)
|
|
|
|
|
TxError("Error: No substrate definition for device %s\n",
|
|
|
|
|
EFDevTypes[dev->dev_type]);
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
fprintf(esSpiceF, " %s=", plist->parm_name);
|
|
|
|
|
spcdevSubstrate(hierName,
|
|
|
|
|
dev->dev_subsnode->efnode_name->efnn_hier,
|
|
|
|
|
dev->dev_type, esSpiceF);
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
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)
|
2024-09-30 08:47:40 +02:00
|
|
|
fprintf(esSpiceF, "%g", (double)dev->dev_rect.r_xbot * scale
|
2017-04-25 14:41:48 +02:00
|
|
|
* esScale * plist->parm_scale * 1E-6);
|
|
|
|
|
else
|
2023-06-22 02:44:38 +02:00
|
|
|
esSIvalue(esSpiceF, (dev->dev_rect.r_xbot + plist->parm_offset)
|
|
|
|
|
* scale * esScale * 1.0E-6);
|
2017-04-25 14:41:48 +02:00
|
|
|
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)
|
2024-09-30 08:11:16 +02:00
|
|
|
fprintf(esSpiceF, "%g", (double)dev->dev_rect.r_ybot * (double)scale
|
|
|
|
|
* (double)esScale * plist->parm_scale * 1E-6);
|
2017-04-25 14:41:48 +02:00
|
|
|
else
|
2023-06-22 02:44:38 +02:00
|
|
|
esSIvalue(esSpiceF, (dev->dev_rect.r_ybot + plist->parm_offset)
|
|
|
|
|
* scale * esScale * 1.0E-6);
|
2017-04-25 14:41:48 +02:00
|
|
|
break;
|
|
|
|
|
case 'r':
|
2025-10-06 23:42:34 +02:00
|
|
|
if (*(plist->parm_name) != '\0')
|
|
|
|
|
{
|
|
|
|
|
fprintf(esSpiceF, " %s=", plist->parm_name);
|
|
|
|
|
esSIvalue(esSpiceF, (double)dev->dev_res);
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
break;
|
|
|
|
|
case 'c':
|
2025-10-06 23:42:34 +02:00
|
|
|
if (*(plist->parm_name) != '\0')
|
|
|
|
|
{
|
|
|
|
|
fprintf(esSpiceF, " %s=", plist->parm_name);
|
|
|
|
|
esSIvalue(esSpiceF, (double)dev->dev_cap);
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
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
|
2025-07-18 15:11:04 +02:00
|
|
|
esOutputResistor(
|
|
|
|
|
Dev *dev, /* Dev being output */
|
|
|
|
|
HierName *hierName, /* Hierarchical path down to this dev */
|
|
|
|
|
float scale, /* Scale transform for output */
|
|
|
|
|
DevTerm *term1,
|
|
|
|
|
DevTerm *term2, /* Terminals of the device */
|
|
|
|
|
bool has_model, /* Is this a modeled resistor? */
|
|
|
|
|
int l,
|
|
|
|
|
int w, /* Device length and width */
|
|
|
|
|
int dscale) /* Device scaling (for split resistors) */
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
2025-07-24 15:36:22 +02:00
|
|
|
float sdM;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
/* 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,
|
2025-07-24 15:36:22 +02:00
|
|
|
"esOutputResistor", esSpiceF);
|
2017-04-25 14:41:48 +02:00
|
|
|
spcdevOutNode(hierName, term2->dterm_node->efnode_name->efnn_hier,
|
2025-07-24 15:36:22 +02:00
|
|
|
"esOutputResistor", esSpiceF);
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
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);
|
2025-10-06 22:40:19 +02:00
|
|
|
spcWriteParams(dev, hierName, scale, l, w, sdM, FALSE);
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2025-10-06 22:40:19 +02:00
|
|
|
bool subdone = FALSE;
|
2025-10-07 02:37:30 +02:00
|
|
|
spcWriteValue(dev, hierName);
|
2025-10-06 22:40:19 +02:00
|
|
|
if (esFormat == CDL)
|
|
|
|
|
subdone = spcWriteSubParam(dev, hierName);
|
2017-04-25 14:41:48 +02:00
|
|
|
fprintf(esSpiceF, " %s", EFDevTypes[dev->dev_type]);
|
|
|
|
|
|
2020-05-23 23:13:14 +02:00
|
|
|
if (esScale < 0)
|
2017-04-25 14:41:48 +02:00
|
|
|
fprintf(esSpiceF, " w=%g l=%g", w * scale, (l * scale) / dscale);
|
|
|
|
|
else
|
2023-02-16 17:59:13 +01:00
|
|
|
{
|
|
|
|
|
fprintf(esSpiceF, " w=");
|
|
|
|
|
esSIvalue(esSpiceF, 1.0E-6 * w * scale * esScale);
|
|
|
|
|
fprintf(esSpiceF, " l=");
|
|
|
|
|
esSIvalue(esSpiceF, 1.0E-6 * (l * scale * esScale) / dscale);
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2025-10-06 22:40:19 +02:00
|
|
|
spcWriteParams(dev, hierName, scale, l, w, sdM, subdone);
|
2017-04-25 14:41:48 +02:00
|
|
|
if (sdM != 1.0)
|
|
|
|
|
fprintf(esSpiceF, " M=%g", sdM);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-23 15:33:37 +02:00
|
|
|
/* Report if device at index n has been deleted due to merging */
|
|
|
|
|
|
|
|
|
|
bool
|
2025-07-18 15:11:04 +02:00
|
|
|
devIsKilled(
|
|
|
|
|
int n)
|
2019-10-23 15:33:37 +02:00
|
|
|
{
|
|
|
|
|
return (esFMult[(n)] <= (float)0.0) ? TRUE : FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Add a dev's multiplier to the table and grow it if necessary */
|
|
|
|
|
|
|
|
|
|
void
|
2025-07-18 15:11:04 +02:00
|
|
|
addDevMult(
|
|
|
|
|
float f)
|
2019-10-23 15:33:37 +02:00
|
|
|
{
|
|
|
|
|
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
|
2025-07-18 15:11:04 +02:00
|
|
|
setDevMult(
|
|
|
|
|
int i,
|
|
|
|
|
float f)
|
2019-10-23 15:33:37 +02:00
|
|
|
{
|
|
|
|
|
esFMult[i] = f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Get the multiplier value of the device at the current index esFMIndex */
|
|
|
|
|
|
|
|
|
|
float
|
2025-07-18 15:11:04 +02:00
|
|
|
getCurDevMult(void)
|
2019-10-23 15:33:37 +02:00
|
|
|
{
|
|
|
|
|
return (esFMult && (esFMIndex > 0)) ? esFMult[esFMIndex-1] : (float)1.0;
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-09 17:34:24 +02:00
|
|
|
|
|
|
|
|
/*
|
2021-02-18 03:08:10 +01:00
|
|
|
*-----------------------------------------------------------------------------
|
2020-08-09 17:34:24 +02:00
|
|
|
* swapDrainSource
|
|
|
|
|
*
|
|
|
|
|
* Swap drain and source ordering and the related stuff
|
|
|
|
|
* including the drain/source area parameters
|
|
|
|
|
*
|
2021-02-18 03:08:10 +01:00
|
|
|
* This is typically called if any terminal is marked with attribute "D" or "S"
|
2020-08-09 17:34:24 +02:00
|
|
|
* (label "D$" or "S$" at poly-diffusion interface),
|
|
|
|
|
* then swap order of source and drain compared to the default ordering.
|
2021-02-18 03:08:10 +01:00
|
|
|
*
|
|
|
|
|
* Note:
|
|
|
|
|
* Before calling this function, ensure that dev->dev_nterm >= 3
|
|
|
|
|
*
|
|
|
|
|
* Results:
|
|
|
|
|
* None
|
|
|
|
|
*
|
|
|
|
|
* Side effects:
|
|
|
|
|
* Soure (dev->dev_terms[1]) and drain (dev->dev_terms[2]) terminals
|
|
|
|
|
* are swapped.
|
2020-08-09 17:34:24 +02:00
|
|
|
*
|
2021-02-18 03:08:10 +01:00
|
|
|
*-----------------------------------------------------------------------------
|
2020-08-09 17:34:24 +02:00
|
|
|
*/
|
2021-02-18 03:08:10 +01:00
|
|
|
|
2020-08-09 17:34:24 +02:00
|
|
|
void
|
2025-07-18 15:11:04 +02:00
|
|
|
swapDrainSource(
|
|
|
|
|
Dev *dev)
|
2020-08-09 17:34:24 +02:00
|
|
|
{
|
2021-02-18 03:08:10 +01:00
|
|
|
DevTerm tmpTerm;
|
2020-08-09 19:11:38 +02:00
|
|
|
|
2021-02-18 03:08:10 +01:00
|
|
|
/* swap original terminals */
|
|
|
|
|
memcpy(&tmpTerm, &(dev->dev_terms[1]), sizeof(DevTerm));
|
|
|
|
|
memcpy(&(dev->dev_terms[1]), &(dev->dev_terms[2]), sizeof(DevTerm));
|
|
|
|
|
memcpy(&(dev->dev_terms[2]), &tmpTerm, sizeof(DevTerm));
|
2020-08-09 17:34:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* 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
|
2020-05-23 23:13:14 +02:00
|
|
|
* + ad= pd= as= ps= * asub= psub=
|
|
|
|
|
* **devattr g= s= d=
|
2017-04-25 14:41:48 +02:00
|
|
|
*
|
|
|
|
|
* where
|
|
|
|
|
* type is a name identifying this type of transistor
|
|
|
|
|
* other types of transistors are extracted with
|
2020-05-23 23:13:14 +02:00
|
|
|
* an M card but it should be easy to turn them to whatever
|
2017-04-25 14:41:48 +02:00
|
|
|
* 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.
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
2025-07-20 01:03:14 +02:00
|
|
|
/* ARGSUSED */
|
2025-07-18 23:02:48 +02:00
|
|
|
/* @typedef cb_extflat_visitdevs_t (UNUSED) */
|
2017-04-25 14:41:48 +02:00
|
|
|
int
|
2025-07-18 15:11:04 +02:00
|
|
|
spcdevVisit(
|
|
|
|
|
Dev *dev, /* Dev being output */
|
|
|
|
|
HierContext *hc, /* Hierarchical context down to this dev */
|
|
|
|
|
float scale, /* Scale transform for output */
|
2025-07-18 23:02:48 +02:00
|
|
|
Transform *trans, /* (unused) */
|
|
|
|
|
ClientData cdata) /* (unused) */
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
DevParam *plist, *pptr;
|
|
|
|
|
DevTerm *gate, *source, *drain;
|
|
|
|
|
EFNode *subnode, *snode, *dnode, *subnodeFlat = NULL;
|
2025-07-24 15:36:22 +02:00
|
|
|
int l, w, i;
|
2025-07-18 15:40:53 +02:00
|
|
|
bool subAP= FALSE, hierS, hierD;
|
2020-05-23 23:13:14 +02:00
|
|
|
float sdM;
|
2017-04-25 14:41:48 +02:00
|
|
|
char name[12], devchar;
|
|
|
|
|
bool has_model = TRUE;
|
2025-10-06 22:40:19 +02:00
|
|
|
bool subdone = FALSE;
|
2020-06-01 23:14:22 +02:00
|
|
|
HierName *hierName = hc->hc_hierName;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
sprintf(name, "output");
|
|
|
|
|
|
|
|
|
|
/* If no terminals, can't do much of anything */
|
2020-05-23 23:13:14 +02:00
|
|
|
if (dev->dev_nterm < 1 )
|
2017-04-25 14:41:48 +02:00
|
|
|
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];
|
2021-02-18 03:08:10 +01:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
if (dev->dev_nterm >= 3)
|
|
|
|
|
{
|
2021-02-18 03:08:10 +01:00
|
|
|
drain = &dev->dev_terms[2];
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/* 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")))
|
|
|
|
|
{
|
2021-02-18 03:08:10 +01:00
|
|
|
swapDrainSource(dev);
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
subnode = dev->dev_subsnode;
|
|
|
|
|
|
|
|
|
|
/* Check for minimum number of terminals. */
|
|
|
|
|
|
|
|
|
|
switch(dev->dev_class)
|
|
|
|
|
{
|
|
|
|
|
case DEV_SUBCKT:
|
2025-08-07 17:54:49 +02:00
|
|
|
case DEV_VERILOGA:
|
2017-04-25 14:41:48 +02:00
|
|
|
case DEV_RSUBCKT:
|
2018-10-30 21:19:20 +01:00
|
|
|
case DEV_CSUBCKT:
|
2025-10-07 22:45:22 +02:00
|
|
|
case DEV_DSUBCKT:
|
2017-04-25 14:41:48 +02:00
|
|
|
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:
|
2020-05-23 23:13:14 +02:00
|
|
|
if (dev->dev_nterm < 1)
|
2017-04-25 14:41:48 +02:00
|
|
|
return 0;
|
2021-07-11 03:13:24 +02:00
|
|
|
if ((dev->dev_type == esNoModelType) ||
|
|
|
|
|
!strcmp(EFDevTypes[dev->dev_type], "None"))
|
2017-04-25 14:41:48 +02:00
|
|
|
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)
|
|
|
|
|
{
|
2020-06-24 21:22:04 +02:00
|
|
|
if (esFormat == NGSPICE) fprintf(esSpiceF, "$ ");
|
2017-04-25 14:41:48 +02:00
|
|
|
fprintf(esSpiceF, "** SOURCE/DRAIN TIED\n");
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
if (gate == source)
|
|
|
|
|
{
|
2020-06-24 21:22:04 +02:00
|
|
|
if (esFormat == NGSPICE) fprintf(esSpiceF, "$ ");
|
2017-04-25 14:41:48 +02:00
|
|
|
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;
|
2021-07-11 03:13:24 +02:00
|
|
|
case DEV_VOLT:
|
|
|
|
|
devchar = 'V';
|
|
|
|
|
break;
|
2017-04-25 14:41:48 +02:00
|
|
|
case DEV_CAP:
|
|
|
|
|
case DEV_CAPREV:
|
|
|
|
|
devchar = 'C';
|
|
|
|
|
break;
|
2025-08-07 17:54:49 +02:00
|
|
|
case DEV_VERILOGA:
|
|
|
|
|
devchar = 'N';
|
|
|
|
|
break;
|
2017-04-25 14:41:48 +02:00
|
|
|
case DEV_SUBCKT:
|
|
|
|
|
case DEV_RSUBCKT:
|
2018-10-30 21:19:20 +01:00
|
|
|
case DEV_CSUBCKT:
|
2025-10-07 22:45:22 +02:00
|
|
|
case DEV_DSUBCKT:
|
2017-04-25 14:41:48 +02:00
|
|
|
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;
|
2021-07-11 03:13:24 +02:00
|
|
|
case DEV_VOLT:
|
|
|
|
|
fprintf(esSpiceF, "%d", esVoltNum++);
|
|
|
|
|
break;
|
2017-04-25 14:41:48 +02:00
|
|
|
case DEV_SUBCKT:
|
2025-08-07 17:54:49 +02:00
|
|
|
case DEV_VERILOGA:
|
2017-04-25 14:41:48 +02:00
|
|
|
case DEV_RSUBCKT:
|
2018-10-30 21:19:20 +01:00
|
|
|
case DEV_CSUBCKT:
|
2025-10-07 22:45:22 +02:00
|
|
|
case DEV_DSUBCKT:
|
2017-04-25 14:41:48 +02:00
|
|
|
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");
|
2020-05-23 23:13:14 +02:00
|
|
|
spcdevOutNode(hierName, subnode->efnode_name->efnn_hier, name, esSpiceF);
|
2017-04-25 14:41:48 +02:00
|
|
|
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);
|
|
|
|
|
|
2025-10-06 22:40:19 +02:00
|
|
|
if (esFormat == CDL)
|
|
|
|
|
subdone = spcWriteSubParam(dev, hierName);
|
2017-04-25 14:41:48 +02:00
|
|
|
fprintf(esSpiceF, " %s", EFDevTypes[dev->dev_type]);
|
2019-06-07 20:13:50 +02:00
|
|
|
sdM = getCurDevMult();
|
2025-10-06 22:40:19 +02:00
|
|
|
spcWriteParams(dev, hierName, scale, l, w, sdM, subdone);
|
2017-04-25 14:41:48 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case DEV_MSUBCKT:
|
2025-10-07 22:45:22 +02:00
|
|
|
case DEV_DSUBCKT:
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2020-07-29 20:13:23 +02:00
|
|
|
/* MOS-like subcircuit is "Xnnn drain gate [source [sub]]" */
|
2017-04-25 14:41:48 +02:00
|
|
|
/* to more conveniently handle cases where MOS devices are */
|
|
|
|
|
/* modeled by subcircuits with the same pin ordering. */
|
|
|
|
|
|
2020-07-29 20:13:23 +02:00
|
|
|
spcdevOutNode(hierName, drain->dterm_node->efnode_name->efnn_hier,
|
2017-04-25 14:41:48 +02:00
|
|
|
name, esSpiceF);
|
|
|
|
|
|
|
|
|
|
/* Drop through to below (no break statement) */
|
|
|
|
|
|
|
|
|
|
case DEV_SUBCKT:
|
2018-10-31 02:56:05 +01:00
|
|
|
case DEV_CSUBCKT:
|
2025-08-07 17:54:49 +02:00
|
|
|
case DEV_VERILOGA:
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
/* 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. */
|
|
|
|
|
|
2025-10-08 16:04:38 +02:00
|
|
|
if (dev->dev_class == DEV_DSUBCKT)
|
|
|
|
|
{
|
|
|
|
|
/* Do nothing: Both terminals have already been output */
|
|
|
|
|
}
|
|
|
|
|
else if (dev->dev_class != DEV_MSUBCKT)
|
2020-07-29 20:13:23 +02:00
|
|
|
{
|
|
|
|
|
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);
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2025-08-07 17:54:49 +02:00
|
|
|
/* The following only applies to DEV_SUBCKT and DEV_VERILOGA, which */
|
|
|
|
|
/* may define as many terminal types as they want. */
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
A number of changes:
1) Corrected spurious error messages about cells already existing
in GDS when using "flatten" or "flatglob".
2) Fixed handling of resistance as a subcircuit parameter
3) Added area and perimeter resistance for a device; this is done
through the "devresist" statement in the tech file, which is an
extension of the original "fetresist" statement. Where "fetresist"
only supported type "linear", "devresist" supports types "area"
and "perimeter".
4) Support for CDL syntax, including generating subcircuit-like
parameters for components starting with SPICE-standard prefixes
like M, R, C, etc., adding "/" between pins and subcircuit name,
and saving the file as ".cdl" instead of ".spice".
5) Estimated L and W for devices whose geometry is complex and do not
reduce to a simple rectangle. L and W are estimated as the square
root of the area.
6) Changed the method of extracting L and W for diodes to use the same
method as capacitors. Note that diodes are not usually specified
by L and W, but if they are, this will produce the right result.
7) Corrected the reported filename and line number when printing error
messages related to errors inside a technology file, when the
technology file uses "include" to combine multiple files.
2025-10-01 21:17:49 +02:00
|
|
|
|
|
|
|
|
/* CDL format support: Output a slash followed by a space. */
|
|
|
|
|
if (esFormat == CDL) fprintf(esSpiceF, " /");
|
2017-04-25 14:41:48 +02:00
|
|
|
fprintf(esSpiceF, " %s", EFDevTypes[dev->dev_type]);
|
|
|
|
|
|
|
|
|
|
/* Write all requested parameters to the subcircuit call. */
|
|
|
|
|
|
|
|
|
|
sdM = getCurDevMult();
|
2025-10-06 22:40:19 +02:00
|
|
|
spcWriteParams(dev, hierName, scale, l, w, sdM, FALSE);
|
2017-04-25 14:41:48 +02:00
|
|
|
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;
|
|
|
|
|
|
2021-07-11 03:13:24 +02:00
|
|
|
case DEV_VOLT:
|
|
|
|
|
/* The voltage source is "Vnnn term1 term2 0.0" and is used
|
|
|
|
|
* only to separate shorted port names.
|
|
|
|
|
*/
|
|
|
|
|
if (dev->dev_nterm > 1)
|
|
|
|
|
spcdevOutNode(hierName, source->dterm_node->efnode_name->efnn_hier,
|
|
|
|
|
name, esSpiceF);
|
2021-09-21 02:14:25 +02:00
|
|
|
if (dev->dev_nterm > 2)
|
2021-07-11 03:13:24 +02:00
|
|
|
spcdevOutNode(hierName, drain->dterm_node->efnode_name->efnn_hier,
|
|
|
|
|
name, esSpiceF);
|
|
|
|
|
fprintf(esSpiceF, " 0.0");
|
|
|
|
|
break;
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
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,
|
2020-05-23 23:13:14 +02:00
|
|
|
name, esSpiceF);
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2025-10-06 22:40:19 +02:00
|
|
|
|
|
|
|
|
if (esFormat == CDL)
|
|
|
|
|
subdone = spcWriteSubParam(dev, hierName);
|
2017-04-25 14:41:48 +02:00
|
|
|
fprintf(esSpiceF, " %s", EFDevTypes[dev->dev_type]);
|
2019-06-07 21:00:39 +02:00
|
|
|
sdM = getCurDevMult();
|
2025-10-06 22:40:19 +02:00
|
|
|
spcWriteParams(dev, hierName, scale, l, w, sdM, subdone);
|
2017-04-25 14:41:48 +02:00
|
|
|
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,
|
2020-05-23 23:13:14 +02:00
|
|
|
name, esSpiceF);
|
2017-04-25 14:41:48 +02:00
|
|
|
spcdevOutNode(hierName, gate->dterm_node->efnode_name->efnn_hier,
|
|
|
|
|
name, esSpiceF);
|
|
|
|
|
|
2025-10-06 22:40:19 +02:00
|
|
|
if (esFormat == CDL)
|
|
|
|
|
subdone = spcWriteSubParam(dev, hierName);
|
2017-04-25 14:41:48 +02:00
|
|
|
fprintf(esSpiceF, " %s", EFDevTypes[dev->dev_type]);
|
2019-06-07 21:00:39 +02:00
|
|
|
sdM = getCurDevMult();
|
2025-10-06 22:40:19 +02:00
|
|
|
spcWriteParams(dev, hierName, scale, l, w, sdM, subdone);
|
2017-04-25 14:41:48 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case DEV_CAP:
|
|
|
|
|
|
|
|
|
|
/* Capacitor is "Cnnn top bottom value" */
|
|
|
|
|
/* extraction sets top=gate bottom=source */
|
2023-03-13 18:21:34 +01:00
|
|
|
/* extracted units are fF. */
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
2023-03-13 18:21:34 +01:00
|
|
|
esSIvalue(esSpiceF, 1.0E-15 * (double)sdM * (double)dev->dev_cap);
|
2025-10-06 22:40:19 +02:00
|
|
|
spcWriteParams(dev, hierName, scale, l, w, sdM, FALSE);
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2025-10-07 02:37:30 +02:00
|
|
|
spcWriteValue(dev, hierName);
|
2025-10-06 22:40:19 +02:00
|
|
|
if (esFormat == CDL)
|
|
|
|
|
subdone = spcWriteSubParam(dev, hierName);
|
2017-04-25 14:41:48 +02:00
|
|
|
fprintf(esSpiceF, " %s", EFDevTypes[dev->dev_type]);
|
|
|
|
|
|
|
|
|
|
if (esScale < 0)
|
|
|
|
|
fprintf(esSpiceF, " w=%g l=%g", w*scale, l*scale);
|
|
|
|
|
else
|
2023-02-16 17:59:13 +01:00
|
|
|
{
|
|
|
|
|
fprintf(esSpiceF, " w=");
|
|
|
|
|
esSIvalue(esSpiceF, 1.0E-6 * w * scale * esScale);
|
|
|
|
|
fprintf(esSpiceF, " l=");
|
|
|
|
|
esSIvalue(esSpiceF, 1.0E-6 * l * scale * esScale);
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2025-10-06 22:40:19 +02:00
|
|
|
spcWriteParams(dev, hierName, scale, l, w, sdM, subdone);
|
2017-04-25 14:41:48 +02:00
|
|
|
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 */
|
2023-03-13 18:21:34 +01:00
|
|
|
/* extracted units are fF. */
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
2023-03-13 18:21:34 +01:00
|
|
|
esSIvalue(esSpiceF, 1.0E-15 * (double)sdM * (double)dev->dev_cap);
|
2025-10-06 22:40:19 +02:00
|
|
|
spcWriteParams(dev, hierName, scale, l, w, sdM, FALSE);
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2025-10-07 02:37:30 +02:00
|
|
|
spcWriteValue(dev, hierName);
|
2025-10-06 22:40:19 +02:00
|
|
|
if (esFormat == CDL)
|
|
|
|
|
subdone = spcWriteSubParam(dev, hierName);
|
2017-04-25 14:41:48 +02:00
|
|
|
fprintf(esSpiceF, " %s", EFDevTypes[dev->dev_type]);
|
|
|
|
|
|
|
|
|
|
if (esScale < 0)
|
|
|
|
|
fprintf(esSpiceF, " w=%g l=%g", w*scale, l*scale);
|
|
|
|
|
else
|
2023-02-16 17:59:13 +01:00
|
|
|
{
|
|
|
|
|
fprintf(esSpiceF, " w=");
|
|
|
|
|
esSIvalue(esSpiceF, 1.0E-6 * w * scale * esScale);
|
|
|
|
|
fprintf(esSpiceF, " l=");
|
|
|
|
|
esSIvalue(esSpiceF, 1.0E-6 * l * scale * esScale);
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2025-10-06 22:40:19 +02:00
|
|
|
spcWriteParams(dev, hierName, scale, l, w, sdM, subdone);
|
2017-04-25 14:41:48 +02:00
|
|
|
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
|
2023-02-16 17:59:13 +01:00
|
|
|
{
|
|
|
|
|
fprintf(esSpiceF, " w=");
|
|
|
|
|
esSIvalue(esSpiceF, 1.0E-6 * w * scale * esScale);
|
|
|
|
|
fprintf(esSpiceF, " l=");
|
|
|
|
|
esSIvalue(esSpiceF, 1.0E-6 * l * scale * esScale);
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2025-10-06 22:40:19 +02:00
|
|
|
spcWriteParams(dev, hierName, scale, l, w, sdM, FALSE);
|
2017-04-25 14:41:48 +02:00
|
|
|
if (sdM != 1.0)
|
|
|
|
|
fprintf(esSpiceF, " M=%g", sdM);
|
|
|
|
|
|
|
|
|
|
/*
|
2020-05-23 23:13:14 +02:00
|
|
|
* Check controlling attributes and output area and perimeter.
|
2017-04-25 14:41:48 +02:00
|
|
|
*/
|
|
|
|
|
hierS = extHierSDAttr(source);
|
|
|
|
|
hierD = extHierSDAttr(drain);
|
2020-05-23 23:13:14 +02:00
|
|
|
if ( gate->dterm_attrs )
|
2017-04-25 14:41:48 +02:00
|
|
|
subAP = Match(ATTR_SUBSAP, gate->dterm_attrs ) ;
|
|
|
|
|
|
|
|
|
|
fprintf(esSpiceF, "\n+ ");
|
2020-05-23 23:13:14 +02:00
|
|
|
if (hierD)
|
2020-09-11 23:29:12 +02:00
|
|
|
spcnAPHier(drain, hierName, esFetInfo[dev->dev_type].resClassDrain,
|
2017-04-25 14:41:48 +02:00
|
|
|
scale, "ad", "pd", sdM, esSpiceF);
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
dnode = SpiceGetNode(hierName, drain->dterm_node->efnode_name->efnn_hier);
|
2023-02-16 17:59:13 +01:00
|
|
|
spcnAP(drain, dnode, esFetInfo[dev->dev_type].resClassDrain, scale,
|
2017-04-25 14:41:48 +02:00
|
|
|
"ad", "pd", sdM, esSpiceF, w);
|
|
|
|
|
}
|
|
|
|
|
if (hierS)
|
2020-09-11 23:29:12 +02:00
|
|
|
spcnAPHier(source, hierName, esFetInfo[dev->dev_type].resClassSource,
|
2017-04-25 14:41:48 +02:00
|
|
|
scale, "as", "ps", sdM, esSpiceF);
|
|
|
|
|
else {
|
|
|
|
|
snode= SpiceGetNode(hierName, source->dterm_node->efnode_name->efnn_hier);
|
2023-02-16 17:59:13 +01:00
|
|
|
spcnAP(source, snode, esFetInfo[dev->dev_type].resClassSource, scale,
|
2017-04-25 14:41:48 +02:00
|
|
|
"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");
|
|
|
|
|
}
|
2020-05-23 23:13:14 +02:00
|
|
|
else if (subnodeFlat)
|
2023-02-16 17:59:13 +01:00
|
|
|
spcnAP(NULL, subnodeFlat, esFetInfo[dev->dev_type].resClassSub,
|
|
|
|
|
scale, "asub", "psub", sdM, esSpiceF, -1);
|
2017-04-25 14:41:48 +02:00
|
|
|
else
|
|
|
|
|
fprintf(esSpiceF, "asub=0 psub=0");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Now output attributes, if present */
|
|
|
|
|
if (!esNoAttrs)
|
|
|
|
|
{
|
2023-02-16 17:59:13 +01:00
|
|
|
bool haveSattr = FALSE;
|
|
|
|
|
bool haveDattr = FALSE;
|
|
|
|
|
|
|
|
|
|
if (source->dterm_attrs && (*source->dterm_attrs))
|
|
|
|
|
haveSattr = TRUE;
|
|
|
|
|
if (drain->dterm_attrs && (*drain->dterm_attrs))
|
|
|
|
|
haveDattr = TRUE;
|
|
|
|
|
|
|
|
|
|
if (gate->dterm_attrs || haveSattr || haveDattr)
|
2017-04-25 14:41:48 +02:00
|
|
|
fprintf(esSpiceF,"\n**devattr");
|
|
|
|
|
if (gate->dterm_attrs)
|
|
|
|
|
fprintf(esSpiceF, " g=%s", gate->dterm_attrs);
|
2023-02-16 17:59:13 +01:00
|
|
|
if (haveSattr)
|
2017-04-25 14:41:48 +02:00
|
|
|
fprintf(esSpiceF, " s=%s", source->dterm_attrs);
|
2023-02-16 17:59:13 +01:00
|
|
|
if (haveDattr)
|
2017-04-25 14:41:48 +02:00
|
|
|
fprintf(esSpiceF, " d=%s", drain->dterm_attrs);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
fprintf(esSpiceF, "\n");
|
2020-08-09 19:11:38 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* spcdevSubstrate -
|
|
|
|
|
*
|
2020-05-23 23:13:14 +02:00
|
|
|
* Output the node name of the substrate of a dev. If the suffix is the
|
2017-04-25 14:41:48 +02:00
|
|
|
* 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.
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
2025-07-18 15:11:04 +02:00
|
|
|
EFNode *
|
|
|
|
|
spcdevSubstrate(
|
|
|
|
|
HierName *prefix,
|
|
|
|
|
HierName *suffix,
|
|
|
|
|
int type,
|
|
|
|
|
FILE *outf)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
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)
|
|
|
|
|
{
|
2020-05-23 23:13:14 +02:00
|
|
|
if (outf)
|
2017-04-25 14:41:48 +02:00
|
|
|
fprintf(outf, "errGnd!");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
/* Canonical name */
|
|
|
|
|
nn = (EFNodeName *) HashGetValue(he);
|
2020-05-23 23:13:14 +02:00
|
|
|
if (outf)
|
2019-10-29 14:44:28 +01:00
|
|
|
fprintf(outf, "%s", nodeSpiceName(nn->efnn_node->efnode_name->efnn_hier,
|
|
|
|
|
NULL));
|
2017-10-20 22:07:52 +02:00
|
|
|
|
2021-12-20 19:12:49 +01:00
|
|
|
/* Create node client if it doesn't exist */
|
2022-02-08 22:12:07 +01:00
|
|
|
if ((nodeClient *)nn->efnn_node->efnode_client == (nodeClient *)NULL)
|
2017-10-20 22:07:52 +02:00
|
|
|
initNodeClientHier(nn->efnn_node);
|
|
|
|
|
|
2021-12-20 19:12:49 +01:00
|
|
|
/* Mark node as visited (set bit one higher than number of resist classes) */
|
|
|
|
|
if (esDistrJunct)
|
|
|
|
|
update_w(efNumResistClasses, 1, nn->efnn_node);
|
|
|
|
|
else
|
|
|
|
|
markVisited((nodeClientHier *)nn->efnn_node->efnode_client,
|
|
|
|
|
efNumResistClasses);
|
2017-04-25 14:41:48 +02:00
|
|
|
return nn->efnn_node;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-06 17:49:29 +01:00
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* nDecimals() ---
|
|
|
|
|
*
|
|
|
|
|
* Code taken from discussion at
|
|
|
|
|
* https://stackoverflow.com/questions/277772/avoid-trailing-zeroes-in-printf
|
|
|
|
|
*
|
|
|
|
|
* Modifies value "d" by truncating it to precision "n", writing the result
|
|
|
|
|
* into string "s".
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
void
|
2025-07-18 15:11:04 +02:00
|
|
|
nDecimals(
|
|
|
|
|
char *s,
|
|
|
|
|
double d,
|
|
|
|
|
int n)
|
2025-01-06 17:49:29 +01:00
|
|
|
{
|
|
|
|
|
int sz;
|
|
|
|
|
double d2;
|
|
|
|
|
|
|
|
|
|
// Allow for negative.
|
|
|
|
|
d2 = (d >= 0) ? d : -d;
|
|
|
|
|
sz = (d >= 0) ? 0 : 1;
|
|
|
|
|
|
|
|
|
|
// Add one for each whole digit (0.xx special case).
|
|
|
|
|
if (d2 < 1) sz++;
|
|
|
|
|
while (d2 >= 1)
|
|
|
|
|
{
|
|
|
|
|
d2 /= 10.0;
|
|
|
|
|
sz++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Adjust for decimal point and fractionals.
|
|
|
|
|
sz += 1 + n;
|
|
|
|
|
|
|
|
|
|
// Create formatted string then use it.
|
|
|
|
|
sprintf(s, "%*.*f", sz, n, d);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* morphNumericString() ---
|
|
|
|
|
*
|
|
|
|
|
* Code taken from discussion at
|
|
|
|
|
* https://stackoverflow.com/questions/277772/avoid-trailing-zeroes-in-printf
|
|
|
|
|
*
|
|
|
|
|
* Remove trailing zeros from the number represented in string "s".
|
|
|
|
|
* Assume a precision (defined as precision after the decimal point) of "n".
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
void
|
2025-07-18 15:11:04 +02:00
|
|
|
morphNumericString(
|
|
|
|
|
char *s,
|
|
|
|
|
int n)
|
2025-01-06 17:49:29 +01:00
|
|
|
{
|
|
|
|
|
char *p;
|
|
|
|
|
int count;
|
|
|
|
|
|
|
|
|
|
p = strchr (s, '.'); // Find decimal point, if any.
|
|
|
|
|
if (p != NULL)
|
|
|
|
|
{
|
|
|
|
|
count = n; // Adjust for more or less decimals.
|
|
|
|
|
while (count >= 0) // Maximum decimals allowed.
|
|
|
|
|
{
|
|
|
|
|
count--;
|
|
|
|
|
if (*p == '\0') // If there's less than desired.
|
|
|
|
|
break;
|
|
|
|
|
p++; // Next character.
|
|
|
|
|
}
|
|
|
|
|
*p-- = '\0'; // Truncate string.
|
|
|
|
|
while (*p == '0') // Remove trailing zeros.
|
|
|
|
|
*p-- = '\0';
|
|
|
|
|
|
|
|
|
|
if (*p == '.') // If all decimals were zeros, remove ".".
|
|
|
|
|
*p = '\0';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-16 17:59:13 +01:00
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* esSIvalue --
|
|
|
|
|
*
|
|
|
|
|
* Print an output in appropriate SI units used by SPICE. e.g., 1.0e-6
|
|
|
|
|
* will be printed as "1.0u"; 1.0e-12 will be printed as "1.0p", etc.
|
|
|
|
|
*
|
|
|
|
|
* Return value:
|
|
|
|
|
* None.
|
|
|
|
|
*
|
|
|
|
|
* Side effects:
|
|
|
|
|
* Generates output to stream "file".
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
void
|
2025-07-18 15:11:04 +02:00
|
|
|
esSIvalue(
|
|
|
|
|
FILE *file,
|
|
|
|
|
float value)
|
2023-02-16 17:59:13 +01:00
|
|
|
{
|
2025-01-06 17:49:29 +01:00
|
|
|
char vstr[32];
|
2023-02-16 17:59:13 +01:00
|
|
|
char suffix = '\0';
|
|
|
|
|
float avalue;
|
|
|
|
|
|
|
|
|
|
avalue = fabsf(value);
|
|
|
|
|
|
|
|
|
|
if (avalue < 1.0E-18)
|
|
|
|
|
{
|
|
|
|
|
/* Do nothing---value is probably zero */
|
|
|
|
|
}
|
2023-03-23 20:38:31 +01:00
|
|
|
else if (avalue < 0.9999E-13)
|
2023-02-16 17:59:13 +01:00
|
|
|
{
|
2023-03-13 18:21:34 +01:00
|
|
|
/* NOTE: ngspice does not support "a" for "atto" */
|
2023-02-16 17:59:13 +01:00
|
|
|
suffix = 'f';
|
|
|
|
|
value *= 1.0E15;
|
|
|
|
|
}
|
2023-03-23 20:38:31 +01:00
|
|
|
else if (avalue < 1.0001E-10)
|
2023-02-16 17:59:13 +01:00
|
|
|
{
|
|
|
|
|
suffix = 'p';
|
|
|
|
|
value *= 1.0E12;
|
|
|
|
|
}
|
2023-03-23 20:38:31 +01:00
|
|
|
else if (avalue < 1.0001E-7)
|
2023-02-16 17:59:13 +01:00
|
|
|
{
|
|
|
|
|
suffix = 'n';
|
|
|
|
|
value *= 1.0E9;
|
|
|
|
|
}
|
2023-03-23 20:38:31 +01:00
|
|
|
else if (avalue < 1.0001E-4)
|
2023-02-16 17:59:13 +01:00
|
|
|
{
|
|
|
|
|
suffix = 'u';
|
|
|
|
|
value *= 1.0E6;
|
|
|
|
|
}
|
2023-03-23 20:38:31 +01:00
|
|
|
else if (avalue < 1.0001E-2)
|
2023-02-16 17:59:13 +01:00
|
|
|
{
|
|
|
|
|
suffix = 'm';
|
|
|
|
|
value *= 1.0E3;
|
|
|
|
|
}
|
2023-03-23 20:38:31 +01:00
|
|
|
else if (avalue > 0.9999E9)
|
2023-02-16 17:59:13 +01:00
|
|
|
{
|
|
|
|
|
suffix = 'G';
|
|
|
|
|
value /= 1.0E9;
|
|
|
|
|
}
|
2023-03-23 20:38:31 +01:00
|
|
|
else if (avalue > 0.9999E3)
|
2023-02-16 17:59:13 +01:00
|
|
|
{
|
|
|
|
|
suffix = 'k';
|
|
|
|
|
value /= 1.0E3;
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-05 17:00:30 +01:00
|
|
|
/* Note that "%g" is preferred because it produces more readable
|
|
|
|
|
* output. However, it changes the definition of the precision
|
|
|
|
|
* from significant digits after the radix to total significant
|
2025-01-06 17:49:29 +01:00
|
|
|
* digits. Using a solution provided in StackOverflow (see above).
|
2023-11-05 17:00:30 +01:00
|
|
|
*/
|
|
|
|
|
|
2025-01-06 17:49:29 +01:00
|
|
|
nDecimals(vstr, (double)value, 5);
|
|
|
|
|
morphNumericString(vstr, 5);
|
2023-11-05 17:00:30 +01:00
|
|
|
|
2023-02-16 17:59:13 +01:00
|
|
|
if (suffix == '\0')
|
2025-01-06 17:49:29 +01:00
|
|
|
fprintf(file, "%s", vstr);
|
2023-02-16 17:59:13 +01:00
|
|
|
else
|
2025-01-06 17:49:29 +01:00
|
|
|
fprintf(file, "%s%c", vstr, suffix);
|
2023-02-16 17:59:13 +01:00
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* spcnAP, spcnAPHier --
|
|
|
|
|
*
|
2020-05-23 23:13:14 +02:00
|
|
|
* Output the area perimeter of the node with type type if it has not
|
2017-04-25 14:41:48 +02:00
|
|
|
* 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
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
2025-07-18 15:11:04 +02:00
|
|
|
int
|
|
|
|
|
spcnAP(
|
|
|
|
|
DevTerm *dterm,
|
|
|
|
|
EFNode *node,
|
|
|
|
|
int resClass,
|
|
|
|
|
float scale,
|
|
|
|
|
char *asterm,
|
|
|
|
|
char *psterm,
|
|
|
|
|
float m,
|
|
|
|
|
FILE *outf,
|
|
|
|
|
int w)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
char afmt[15], pfmt[15];
|
|
|
|
|
float dsc;
|
2023-02-16 17:59:13 +01:00
|
|
|
int area, perim;
|
|
|
|
|
char *cptr;
|
|
|
|
|
bool haveAttrs = FALSE;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
if ((node == NULL) || (node->efnode_client == (ClientData)NULL))
|
|
|
|
|
{
|
|
|
|
|
TxError("spcnAP: major internal inconsistency\n");
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-22 03:04:30 +01:00
|
|
|
if (asterm) snprintf(afmt, sizeof afmt, " %s=", asterm);
|
|
|
|
|
if (psterm) snprintf(pfmt, sizeof pfmt, " %s=", psterm);
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2023-02-16 17:59:13 +01:00
|
|
|
if (!esDistrJunct || w == -1) goto newFmt;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2020-03-03 23:13:37 +01:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
if (esScale < 0)
|
|
|
|
|
{
|
|
|
|
|
if (asterm)
|
2023-02-16 17:59:13 +01:00
|
|
|
{
|
2023-03-22 03:04:30 +01:00
|
|
|
fputs(afmt, outf);
|
2023-02-16 17:59:13 +01:00
|
|
|
esSIvalue(outf, 1.0E-12 * node->efnode_pa[resClass].pa_area
|
|
|
|
|
* scale * scale * dsc);
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
if (psterm)
|
2023-02-16 17:59:13 +01:00
|
|
|
{
|
2023-03-22 03:04:30 +01:00
|
|
|
fputs(pfmt, outf);
|
2023-02-16 17:59:13 +01:00
|
|
|
esSIvalue(outf, 1.0E-6 * node->efnode_pa[resClass].pa_perim * scale * dsc);
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
2020-05-23 23:13:14 +02:00
|
|
|
else
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
if (asterm)
|
2023-02-16 17:59:13 +01:00
|
|
|
{
|
2023-03-22 03:04:30 +01:00
|
|
|
fputs(afmt, outf);
|
2023-02-16 17:59:13 +01:00
|
|
|
esSIvalue(outf, 1.0E-12 * ((float)node->efnode_pa[resClass].pa_area
|
|
|
|
|
* scale * scale) * esScale * esScale * dsc);
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
if (psterm)
|
2023-02-16 17:59:13 +01:00
|
|
|
{
|
2023-03-22 03:04:30 +01:00
|
|
|
fputs(pfmt, outf);
|
2023-02-16 17:59:13 +01:00
|
|
|
esSIvalue(outf, 1.0E-6 * ((float)node->efnode_pa[resClass].pa_perim
|
|
|
|
|
* scale) * esScale * dsc);
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
2023-02-16 17:59:13 +01:00
|
|
|
newFmt:
|
|
|
|
|
/* New format introduced 2/15/2023: Area and perimeter of each terminal
|
|
|
|
|
* are maintained in the terminal attributes for "fet" or "device mosfet"
|
|
|
|
|
* type devices (otherwise, for subcircuit device types, this routine is
|
|
|
|
|
* not used and the same values are found in the device parameters).
|
|
|
|
|
*
|
|
|
|
|
* Values are the last two values in a comma-separated list in
|
|
|
|
|
* dterm_attrs. If not found, then default to the
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
cptr = (dterm) ? dterm->dterm_attrs : NULL;
|
|
|
|
|
while (cptr)
|
|
|
|
|
{
|
|
|
|
|
if (*cptr == ',') cptr++;
|
|
|
|
|
if (sscanf(cptr, "%d,%d", &area, &perim) != 2)
|
|
|
|
|
cptr = strchr(cptr, ',');
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
haveAttrs = TRUE;
|
|
|
|
|
*cptr = '\0';
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!haveAttrs)
|
|
|
|
|
{
|
|
|
|
|
area = node->efnode_pa[resClass].pa_area;
|
|
|
|
|
perim = node->efnode_pa[resClass].pa_perim;
|
|
|
|
|
|
|
|
|
|
if (resClass == NO_RESCLASS ||
|
|
|
|
|
beenVisited((nodeClient *)node->efnode_client, resClass))
|
|
|
|
|
scale = 0;
|
|
|
|
|
else
|
|
|
|
|
markVisited((nodeClient *)node->efnode_client, resClass);
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
if (esScale < 0)
|
|
|
|
|
{
|
|
|
|
|
if (asterm)
|
2023-02-16 17:59:13 +01:00
|
|
|
{
|
2023-03-22 03:04:30 +01:00
|
|
|
fputs(afmt, outf);
|
2023-02-16 17:59:13 +01:00
|
|
|
esSIvalue(outf, 1.0E-12 * area * scale * scale / m);
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
if (psterm)
|
2023-02-16 17:59:13 +01:00
|
|
|
{
|
2023-03-22 03:04:30 +01:00
|
|
|
fputs(pfmt, outf);
|
2023-02-16 17:59:13 +01:00
|
|
|
esSIvalue(outf, 1.0E-6 * perim * scale / m);
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
2020-05-23 23:13:14 +02:00
|
|
|
else
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
if (asterm)
|
2023-02-16 17:59:13 +01:00
|
|
|
{
|
2023-03-22 03:04:30 +01:00
|
|
|
fputs(afmt, outf);
|
2023-02-16 17:59:13 +01:00
|
|
|
esSIvalue(outf, 1.0E-12 * ((float)area * scale * scale) * esScale * esScale);
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
if (psterm)
|
2023-02-16 17:59:13 +01:00
|
|
|
{
|
2023-03-22 03:04:30 +01:00
|
|
|
fputs(pfmt, outf);
|
2023-02-16 17:59:13 +01:00
|
|
|
esSIvalue(outf, 1.0E-6 * ((float)perim * scale) * esScale);
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-18 15:11:04 +02:00
|
|
|
int
|
|
|
|
|
spcnAPHier(
|
|
|
|
|
DevTerm *dterm,
|
|
|
|
|
HierName *hierName,
|
|
|
|
|
int resClass,
|
|
|
|
|
float scale,
|
|
|
|
|
char *asterm,
|
|
|
|
|
char *psterm,
|
|
|
|
|
float m,
|
|
|
|
|
FILE *outf)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
EFNode *node = dterm->dterm_node;
|
|
|
|
|
nodeClientHier *nc;
|
|
|
|
|
char afmt[15], pfmt[15];
|
2023-02-16 17:59:13 +01:00
|
|
|
int area, perim;
|
|
|
|
|
char *cptr;
|
|
|
|
|
bool haveAttrs = FALSE;
|
|
|
|
|
|
|
|
|
|
sprintf(afmt," %s=", asterm);
|
|
|
|
|
sprintf(pfmt," %s=", psterm);
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2020-05-23 23:13:14 +02:00
|
|
|
if (node->efnode_client == (ClientData) NULL)
|
2017-04-25 14:41:48 +02:00
|
|
|
initNodeClientHier(node);
|
|
|
|
|
|
|
|
|
|
nc = (nodeClientHier *)node->efnode_client;
|
|
|
|
|
if (nc->lastPrefix != hierName)
|
|
|
|
|
{
|
|
|
|
|
clearVisited(nc);
|
|
|
|
|
nc->lastPrefix = hierName;
|
|
|
|
|
}
|
2023-02-16 17:59:13 +01:00
|
|
|
|
|
|
|
|
/* Check for area and perim values in dterm_attrs */
|
|
|
|
|
|
|
|
|
|
cptr = dterm->dterm_attrs;
|
|
|
|
|
while (cptr)
|
|
|
|
|
{
|
|
|
|
|
if (*cptr == ',') cptr++;
|
|
|
|
|
if (sscanf(cptr, "%d,%d", &area, &perim) != 2)
|
|
|
|
|
cptr = strchr(cptr, ',');
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
haveAttrs = TRUE;
|
|
|
|
|
*cptr = '\0';
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!haveAttrs)
|
|
|
|
|
{
|
|
|
|
|
area = node->efnode_pa[resClass].pa_area;
|
|
|
|
|
perim = node->efnode_pa[resClass].pa_perim;
|
|
|
|
|
|
|
|
|
|
if (resClass == NO_RESCLASS ||
|
|
|
|
|
beenVisited((nodeClient *)node->efnode_client, resClass))
|
|
|
|
|
scale = 0;
|
|
|
|
|
else
|
|
|
|
|
markVisited((nodeClient *)node->efnode_client, resClass);
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
if (esScale < 0)
|
|
|
|
|
{
|
2023-03-22 03:04:30 +01:00
|
|
|
fputs(afmt, outf);
|
2023-02-16 17:59:13 +01:00
|
|
|
esSIvalue(outf, 1.0E-12 * area * scale * scale / m);
|
2023-03-22 03:04:30 +01:00
|
|
|
fputs(pfmt, outf);
|
2023-02-16 17:59:13 +01:00
|
|
|
esSIvalue(outf, 1.0E-6 * perim * scale / m);
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2023-03-22 03:04:30 +01:00
|
|
|
fputs(afmt, outf);
|
2023-02-16 17:59:13 +01:00
|
|
|
esSIvalue(outf, 1.0E-12 * ((float)area * scale) * esScale * esScale);
|
2023-03-22 03:04:30 +01:00
|
|
|
fputs(pfmt, outf);
|
2023-02-16 17:59:13 +01:00
|
|
|
esSIvalue(outf, 1.0E-6 * ((float)perim * scale) * esScale);
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
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
|
2025-07-18 15:11:04 +02:00
|
|
|
spcdevOutNode(
|
2025-07-23 21:10:03 +02:00
|
|
|
const HierName *prefix,
|
|
|
|
|
const HierName *suffix,
|
|
|
|
|
const char *name,
|
2025-07-18 15:11:04 +02:00
|
|
|
FILE *outf)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
HashEntry *he;
|
|
|
|
|
EFNodeName *nn;
|
2025-07-23 21:10:03 +02:00
|
|
|
const char *nname;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
he = EFHNConcatLook(prefix, suffix, name);
|
|
|
|
|
if (he == NULL)
|
|
|
|
|
{
|
|
|
|
|
fprintf(outf, " errGnd!");
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
nn = (EFNodeName *) HashGetValue(he);
|
2019-10-29 14:44:28 +01:00
|
|
|
nname = nodeSpiceName(nn->efnn_node->efnode_name->efnn_hier, NULL);
|
2017-04-25 14:41:48 +02:00
|
|
|
fprintf(outf, " %s", nname);
|
2019-10-14 19:49:41 +02:00
|
|
|
|
2021-12-20 19:12:49 +01:00
|
|
|
/* Mark node as visited (set bit one higher than number of resist classes) */
|
|
|
|
|
if (esDistrJunct)
|
|
|
|
|
update_w(efNumResistClasses, 1, nn->efnn_node);
|
|
|
|
|
else
|
|
|
|
|
markVisited((nodeClientHier *)nn->efnn_node->efnode_client,
|
|
|
|
|
efNumResistClasses);
|
2019-10-14 19:49:41 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
return (1 + strlen(nname));
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* 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).
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
2025-07-18 23:02:48 +02:00
|
|
|
/* @typedef cb_extflat_visitcaps_t (UNUSED) */
|
2017-04-25 14:41:48 +02:00
|
|
|
int
|
2025-07-18 15:11:04 +02:00
|
|
|
spccapVisit(
|
|
|
|
|
HierName *hierName1,
|
|
|
|
|
HierName *hierName2,
|
2025-07-18 23:02:48 +02:00
|
|
|
double cap,
|
|
|
|
|
ClientData cdata) /* unused */
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
cap = cap / 1000;
|
|
|
|
|
if (cap <= EFCapThreshold)
|
|
|
|
|
return 0;
|
|
|
|
|
|
2023-03-13 18:21:34 +01:00
|
|
|
fprintf(esSpiceF, "C%d %s %s ", esCapNum++, nodeSpiceName(hierName1, NULL),
|
|
|
|
|
nodeSpiceName(hierName2, NULL));
|
|
|
|
|
esSIvalue(esSpiceF, 1.0E-15 * cap);
|
|
|
|
|
fprintf(esSpiceF, "\n");
|
2017-04-25 14:41:48 +02:00
|
|
|
return 0;
|
|
|
|
|
}
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* 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)
|
|
|
|
|
*
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
2025-07-18 23:02:48 +02:00
|
|
|
|
2025-07-20 17:09:08 +02:00
|
|
|
/* ARGSUSED */
|
2025-07-18 23:02:48 +02:00
|
|
|
/* @typedef cb_extflat_visitresists_t (UNUSED) */
|
2017-04-25 14:41:48 +02:00
|
|
|
int
|
2025-07-18 15:11:04 +02:00
|
|
|
spcresistVisit(
|
2025-07-20 17:09:08 +02:00
|
|
|
const HierName *hierName1,
|
|
|
|
|
const HierName *hierName2,
|
2025-07-18 23:02:48 +02:00
|
|
|
float res,
|
|
|
|
|
ClientData cdata) /* unused */
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
2023-02-28 19:11:32 +01:00
|
|
|
HashEntry *he;
|
|
|
|
|
EFNodeName *nn;
|
|
|
|
|
|
2019-10-29 14:44:28 +01:00
|
|
|
fprintf(esSpiceF, "R%d %s %s %g\n", esResNum++, nodeSpiceName(hierName1, NULL),
|
|
|
|
|
nodeSpiceName(hierName2, NULL), res / 1000.);
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2023-02-28 19:11:32 +01:00
|
|
|
/* Mark nodes as visited so that associated capacitances won't be marked
|
|
|
|
|
* as "floating". This is inefficient since nodeSpiceName() already does
|
|
|
|
|
* a hash lookup of the EFNodeName. Could be improved, but is not a big
|
|
|
|
|
* performance issue.
|
|
|
|
|
*/
|
|
|
|
|
he = EFHNLook(hierName1, (char *)NULL, "nodeName");
|
|
|
|
|
if (he != NULL)
|
|
|
|
|
{
|
|
|
|
|
nn = (EFNodeName *) HashGetValue(he);
|
|
|
|
|
|
|
|
|
|
/* Mark node as visited (set bit one higher than number of resist classes) */
|
|
|
|
|
if (esDistrJunct)
|
|
|
|
|
update_w(efNumResistClasses, 1, nn->efnn_node);
|
|
|
|
|
else
|
|
|
|
|
markVisited((nodeClientHier *)nn->efnn_node->efnode_client,
|
|
|
|
|
efNumResistClasses);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
he = EFHNLook(hierName2, (char *)NULL, "nodeName");
|
|
|
|
|
if (he != NULL)
|
|
|
|
|
{
|
|
|
|
|
nn = (EFNodeName *) HashGetValue(he);
|
|
|
|
|
|
|
|
|
|
/* Mark node as visited (set bit one higher than number of resist classes) */
|
|
|
|
|
if (esDistrJunct)
|
|
|
|
|
update_w(efNumResistClasses, 1, nn->efnn_node);
|
|
|
|
|
else
|
|
|
|
|
markVisited((nodeClientHier *)nn->efnn_node->efnode_client,
|
|
|
|
|
efNumResistClasses);
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
return 0;
|
|
|
|
|
}
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* 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
|
2025-07-18 15:11:04 +02:00
|
|
|
spcsubVisit(
|
|
|
|
|
EFNode *node,
|
|
|
|
|
int res, // Unused
|
|
|
|
|
double cap, // Unused
|
2025-07-18 23:02:48 +02:00
|
|
|
ClientData cdata)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
2025-07-18 23:02:48 +02:00
|
|
|
char **resstr = (char **)CD2PTR(cdata);
|
2017-04-25 14:41:48 +02:00
|
|
|
HierName *hierName;
|
2025-07-23 22:06:12 +02:00
|
|
|
const char *nsn;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2021-12-30 19:27:42 +01:00
|
|
|
if (node->efnode_flags & EF_GLOB_SUBS_NODE)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
hierName = (HierName *) node->efnode_name->efnn_hier;
|
2019-10-29 14:44:28 +01:00
|
|
|
nsn = nodeSpiceName(hierName, NULL);
|
2017-04-25 14:41:48 +02:00
|
|
|
*resstr = StrDup((char **)NULL, nsn);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* spcnodeVisit --
|
|
|
|
|
*
|
2020-05-23 23:13:14 +02:00
|
|
|
* Procedure to output a single node to the .spice file along with its
|
2017-04-25 14:41:48 +02:00
|
|
|
* attributes and its dictionary (if present). Called by EFVisitNodes().
|
|
|
|
|
*
|
|
|
|
|
* Results:
|
|
|
|
|
* Returns 0 always.
|
|
|
|
|
*
|
|
|
|
|
* Side effects:
|
|
|
|
|
* Writes to the files esSpiceF
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
2025-07-18 23:02:48 +02:00
|
|
|
/* @typedef cb_extflat_visitnodes_t (UNUSED) */
|
2017-04-25 14:41:48 +02:00
|
|
|
int
|
2025-07-18 15:11:04 +02:00
|
|
|
spcnodeVisit(
|
|
|
|
|
EFNode *node,
|
|
|
|
|
int res,
|
2025-07-18 23:02:48 +02:00
|
|
|
double cap,
|
|
|
|
|
ClientData cdata) /* unused */
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
HierName *hierName;
|
|
|
|
|
bool isConnected = FALSE;
|
2024-10-20 14:46:02 +02:00
|
|
|
const char *fmt;
|
2025-07-23 22:06:12 +02:00
|
|
|
const char *nsn;
|
2017-04-25 14:41:48 +02:00
|
|
|
EFAttr *ap;
|
|
|
|
|
|
|
|
|
|
if (node->efnode_client)
|
|
|
|
|
{
|
|
|
|
|
isConnected = (esDistrJunct) ?
|
|
|
|
|
(((nodeClient *)node->efnode_client)->m_w.widths != NULL) :
|
2021-12-20 19:12:49 +01:00
|
|
|
(!TTMaskHasType(&((nodeClient *)node->efnode_client)->m_w.visitMask,
|
|
|
|
|
efNumResistClasses));
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
2020-05-23 23:13:14 +02:00
|
|
|
if (!isConnected && esDevNodesOnly)
|
2017-04-25 14:41:48 +02:00
|
|
|
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;
|
2019-10-29 14:44:28 +01:00
|
|
|
nsn = nodeSpiceName(hierName, NULL);
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2024-10-04 18:24:47 +02:00
|
|
|
if (esFormat == SPICE2 || (esFormat == HSPICE && strncmp(nsn, "z@", 2)==0 )) {
|
2017-04-25 14:41:48 +02:00
|
|
|
static char ntmp[MAX_STR_SIZE];
|
|
|
|
|
|
|
|
|
|
EFHNSprintf(ntmp, hierName);
|
2020-06-24 21:22:04 +02:00
|
|
|
if (esFormat == NGSPICE) fprintf(esSpiceF, "$ ");
|
2017-04-25 14:41:48 +02:00
|
|
|
fprintf(esSpiceF, "** %s == %s\n", ntmp, nsn);
|
|
|
|
|
}
|
|
|
|
|
cap = cap / 1000;
|
|
|
|
|
if (cap > EFCapThreshold)
|
|
|
|
|
{
|
2023-03-13 18:21:34 +01:00
|
|
|
fprintf(esSpiceF, "C%d %s %s ", esCapNum++, nsn, esSpiceCapNode);
|
|
|
|
|
esSIvalue(esSpiceF, 1.0E-15 * cap);
|
|
|
|
|
if (!isConnected)
|
|
|
|
|
{
|
|
|
|
|
if (esFormat == NGSPICE) fprintf(esSpiceF, " $");
|
|
|
|
|
fprintf(esSpiceF, " **FLOATING");
|
|
|
|
|
}
|
|
|
|
|
fprintf(esSpiceF, "\n");
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
if (node->efnode_attrs && !esNoAttrs)
|
|
|
|
|
{
|
2020-06-24 21:22:04 +02:00
|
|
|
if (esFormat == NGSPICE) fprintf(esSpiceF, " $ ");
|
2017-04-25 14:41:48 +02:00
|
|
|
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 */
|
2025-07-18 23:02:48 +02:00
|
|
|
/* @typedef cb_extflat_visitnodes_t (UNUSED) */
|
2017-04-25 14:41:48 +02:00
|
|
|
int
|
2025-07-18 15:11:04 +02:00
|
|
|
nodeVisitDebug(
|
|
|
|
|
EFNode *node,
|
|
|
|
|
int res,
|
2025-07-18 23:02:48 +02:00
|
|
|
double cap,
|
|
|
|
|
ClientData cdata) /* unused */
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
HierName *hierName;
|
2025-07-23 22:06:12 +02:00
|
|
|
const char *nsn;
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
hierName = (HierName *) node->efnode_name->efnn_hier;
|
2019-10-29 14:44:28 +01:00
|
|
|
nsn = nodeSpiceName(hierName, NULL);
|
2024-09-30 00:00:00 +02:00
|
|
|
TxError("** %s (%lx)\n", nsn, (intmax_t) node);
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
printf("\t client.name=%s, client.m_w=%p\n",
|
|
|
|
|
((nodeClient *)node->efnode_client)->spiceNodeName,
|
2025-01-06 17:12:11 +01:00
|
|
|
(void *)((nodeClient *)node->efnode_client)->m_w.widths);
|
2017-04-25 14:41:48 +02:00
|
|
|
return 0;
|
|
|
|
|
}
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* 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.
|
2019-10-29 14:44:28 +01:00
|
|
|
* Returns the node in the "rnode" pointer, if non-NULL.
|
2017-04-25 14:41:48 +02:00
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
static char esTempName[MAX_STR_SIZE];
|
|
|
|
|
|
2025-07-21 23:46:03 +02:00
|
|
|
const char *
|
2025-07-18 15:11:04 +02:00
|
|
|
nodeSpiceName(
|
2025-07-21 23:46:03 +02:00
|
|
|
const HierName *hname,
|
2025-07-18 15:11:04 +02:00
|
|
|
EFNode **rnode)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
EFNodeName *nn;
|
|
|
|
|
HashEntry *he;
|
|
|
|
|
EFNode *node;
|
|
|
|
|
|
2019-10-29 14:44:28 +01:00
|
|
|
if (rnode) *rnode = (EFNode *)NULL;
|
2017-04-25 14:41:48 +02:00
|
|
|
he = EFHNLook(hname, (char *) NULL, "nodeName");
|
|
|
|
|
if ( he == NULL )
|
|
|
|
|
return "errGnd!";
|
|
|
|
|
nn = (EFNodeName *) HashGetValue(he);
|
|
|
|
|
node = nn->efnn_node;
|
2019-10-29 14:44:28 +01:00
|
|
|
if (rnode) *rnode = node;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
if ( (nodeClient *) (node->efnode_client) == NULL ) {
|
|
|
|
|
initNodeClient(node);
|
|
|
|
|
goto makeName;
|
|
|
|
|
} else if ( ((nodeClient *) (node->efnode_client))->spiceNodeName == NULL)
|
|
|
|
|
goto makeName;
|
|
|
|
|
else goto retName;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
makeName:
|
2020-05-23 23:13:14 +02:00
|
|
|
if ( esFormat == SPICE2 )
|
2017-04-25 14:41:48 +02:00
|
|
|
sprintf(esTempName, "%d", esNodeNum++);
|
|
|
|
|
else {
|
|
|
|
|
EFHNSprintf(esTempName, node->efnode_name->efnn_hier);
|
|
|
|
|
if ( esFormat == HSPICE ) /* more processing */
|
|
|
|
|
nodeHspiceName(esTempName);
|
|
|
|
|
}
|
2021-02-24 18:35:06 +01:00
|
|
|
((nodeClient *) (node->efnode_client))->spiceNodeName =
|
|
|
|
|
StrDup(NULL, esTempName);
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
retName:
|
|
|
|
|
return ((nodeClient *) (node->efnode_client))->spiceNodeName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* EFHNSprintf --
|
|
|
|
|
*
|
|
|
|
|
* Create a hierarchical node name.
|
2021-07-11 03:13:24 +02:00
|
|
|
* The flags in EFOutputFlags control whether global (!) or local (#)
|
2020-05-23 23:13:14 +02:00
|
|
|
* suffixes are to be trimmed. Also substitutes \. with \@ if the
|
2021-02-24 18:35:06 +01:00
|
|
|
* format is hspice.
|
2017-04-25 14:41:48 +02:00
|
|
|
*
|
|
|
|
|
* Results:
|
|
|
|
|
* None.
|
|
|
|
|
*
|
|
|
|
|
* Side effects:
|
|
|
|
|
* Changes the area pointed to by str
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
2020-05-23 23:13:14 +02:00
|
|
|
int
|
2025-07-18 15:11:04 +02:00
|
|
|
EFHNSprintf(
|
|
|
|
|
char *str,
|
|
|
|
|
HierName *hierName)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
2019-10-15 03:46:07 +02:00
|
|
|
bool trimGlob, trimLocal, convertComma, convertEqual, convertBrackets;
|
2021-02-24 18:35:06 +01:00
|
|
|
char *s, *cp, c;
|
2017-04-25 14:41:48 +02:00
|
|
|
char *efHNSprintfPrefix(HierName *, char *);
|
|
|
|
|
|
2021-02-24 18:35:06 +01:00
|
|
|
s = str;
|
2017-04-25 14:41:48 +02:00
|
|
|
if (hierName->hn_parent) str = efHNSprintfPrefix(hierName->hn_parent, str);
|
2021-07-11 03:27:19 +02:00
|
|
|
if (EFOutputFlags & EF_TRIM_MASK)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
2021-02-24 18:35:06 +01:00
|
|
|
cp = hierName->hn_name;
|
2021-07-11 03:13:24 +02:00
|
|
|
trimGlob = (EFOutputFlags & EF_TRIMGLOB);
|
|
|
|
|
trimLocal = (EFOutputFlags & EF_TRIMLOCAL);
|
|
|
|
|
convertComma = (EFOutputFlags & EF_CONVERTCOMMA);
|
|
|
|
|
convertEqual = (EFOutputFlags & EF_CONVERTEQUAL);
|
|
|
|
|
convertBrackets = (EFOutputFlags & EF_CONVERTBRACKETS);
|
2024-10-04 18:21:15 +02:00
|
|
|
while ((c = *cp++))
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
2021-02-24 18:35:06 +01:00
|
|
|
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;
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
2021-02-24 18:35:06 +01:00
|
|
|
*str++ = '\0';
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
2021-02-24 18:35:06 +01:00
|
|
|
else strcpy(str, hierName->hn_name);
|
2017-04-25 14:41:48 +02:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-18 15:11:04 +02:00
|
|
|
char *
|
|
|
|
|
efHNSprintfPrefix(
|
|
|
|
|
HierName *hierName,
|
|
|
|
|
char *str)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
char *cp, c;
|
2021-07-11 03:13:24 +02:00
|
|
|
bool convertEqual = (EFOutputFlags & EF_CONVERTEQUAL) ? TRUE : FALSE;
|
|
|
|
|
bool convertComma = (EFOutputFlags & EF_CONVERTCOMMA) ? TRUE : FALSE;
|
|
|
|
|
bool convertBrackets = (EFOutputFlags & EF_CONVERTBRACKETS) ? TRUE : FALSE;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
if (hierName->hn_parent)
|
|
|
|
|
str = efHNSprintfPrefix(hierName->hn_parent, str);
|
|
|
|
|
|
|
|
|
|
cp = hierName->hn_name;
|
|
|
|
|
while (1) {
|
|
|
|
|
if (convertEqual && (*cp == '='))
|
|
|
|
|
*str = ':';
|
2019-10-15 03:46:07 +02:00
|
|
|
else if (convertBrackets && ((*cp == '[') || (*cp == ']')))
|
|
|
|
|
*str = '_';
|
|
|
|
|
else if (*cp == ',')
|
|
|
|
|
{
|
|
|
|
|
if (convertComma) *str = '|';
|
|
|
|
|
else str--;
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
else
|
|
|
|
|
*str = *cp;
|
|
|
|
|
if (!(*str)) break;
|
|
|
|
|
str++;
|
|
|
|
|
cp++;
|
|
|
|
|
}
|
|
|
|
|
*str = '/';
|
|
|
|
|
return ++str;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* nodeHspiceName --
|
|
|
|
|
*
|
2020-05-23 23:13:14 +02:00
|
|
|
* Convert the hierarchical node name used in Berkeley spice
|
2018-12-12 23:23:35 +01:00
|
|
|
* to a name understood by hspice and hopefully by the user.
|
2017-04-25 14:41:48 +02:00
|
|
|
*
|
|
|
|
|
* Results:
|
2018-12-12 23:23:35 +01:00
|
|
|
* A somewhat meaningful node name
|
2017-04-25 14:41:48 +02:00
|
|
|
*
|
|
|
|
|
* Side effects:
|
|
|
|
|
* Mucks with the hash table above.
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
2025-07-18 15:11:04 +02:00
|
|
|
int
|
|
|
|
|
nodeHspiceName(
|
|
|
|
|
char *s)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
char *p, *sf;
|
|
|
|
|
int l, snum = -1;
|
|
|
|
|
HashEntry *he;
|
|
|
|
|
static char map[MAX_STR_SIZE];
|
|
|
|
|
|
|
|
|
|
/*
|
2020-05-23 23:13:14 +02:00
|
|
|
* find the suffix
|
2017-04-25 14:41:48 +02:00
|
|
|
*/
|
|
|
|
|
l = strlen(s);
|
2020-05-23 23:13:14 +02:00
|
|
|
for (p = s + l; (p > s) && *p != '/'; p--);
|
2017-04-25 14:41:48 +02:00
|
|
|
if (p == s)
|
|
|
|
|
{
|
|
|
|
|
strcpy(map, s);
|
|
|
|
|
goto topLevel;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-23 23:13:14 +02:00
|
|
|
/*
|
|
|
|
|
* break it into prefix '/0' suffix
|
2017-04-25 14:41:48 +02:00
|
|
|
*/
|
2020-05-23 23:13:14 +02:00
|
|
|
if (*p == '/')
|
|
|
|
|
*p = 0;
|
2017-04-25 14:41:48 +02:00
|
|
|
sf = p + 1;
|
|
|
|
|
|
2020-05-23 23:13:14 +02:00
|
|
|
/*
|
|
|
|
|
* look up prefix in the hash table and create it if doesn't exist
|
2017-04-25 14:41:48 +02:00
|
|
|
*/
|
|
|
|
|
if ((he = HashLookOnly(&subcktNameTable, s)) == NULL)
|
|
|
|
|
{
|
|
|
|
|
snum = esSbckNum++;
|
|
|
|
|
he = HashFind(&subcktNameTable, s);
|
|
|
|
|
HashSetValue(he, (ClientData)(pointertype) snum);
|
|
|
|
|
#ifndef UNSORTED_SUBCKT
|
|
|
|
|
DQPushRear(&subcktNameQueue, he);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
2020-05-23 23:13:14 +02:00
|
|
|
else
|
2017-04-25 14:41:48 +02:00
|
|
|
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
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
2025-07-18 15:11:04 +02:00
|
|
|
int
|
|
|
|
|
printSubcktDict(void)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
HashEntry *he;
|
|
|
|
|
|
|
|
|
|
fprintf(esSpiceF,"\n** hspice subcircuit dictionary\n");
|
|
|
|
|
|
|
|
|
|
#ifndef UNSORTED_SUBCKT
|
|
|
|
|
while ((he = (HashEntry *)DQPopFront(&subcktNameQueue)) != NULL)
|
|
|
|
|
#else
|
|
|
|
|
HashStartSearch(&hs);
|
2020-05-23 23:13:14 +02:00
|
|
|
while ((he = HashNext(&subcktNameTable, &hs)) != NULL)
|
2017-04-25 14:41:48 +02:00
|
|
|
#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.
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
2025-07-18 15:11:04 +02:00
|
|
|
devMerge *
|
|
|
|
|
mkDevMerge(
|
|
|
|
|
float l,
|
|
|
|
|
float w,
|
|
|
|
|
EFNode *g,
|
|
|
|
|
EFNode *s,
|
|
|
|
|
EFNode *d,
|
|
|
|
|
EFNode *b,
|
|
|
|
|
HierName *hn,
|
|
|
|
|
Dev *dev)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
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
|
2025-07-18 15:11:04 +02:00
|
|
|
parallelDevs(
|
2025-07-24 15:36:22 +02:00
|
|
|
const devMerge *f1,
|
|
|
|
|
const devMerge *f2)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
/* 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:
|
2025-08-07 17:54:49 +02:00
|
|
|
case DEV_VERILOGA:
|
2017-04-25 14:41:48 +02:00
|
|
|
case DEV_RSUBCKT:
|
2018-10-30 21:19:20 +01:00
|
|
|
case DEV_CSUBCKT:
|
2025-10-07 22:45:22 +02:00
|
|
|
case DEV_DSUBCKT:
|
2017-04-25 14:41:48 +02:00
|
|
|
break;
|
2021-07-11 03:13:24 +02:00
|
|
|
|
|
|
|
|
case DEV_VOLT:
|
|
|
|
|
break;
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
return NOT_PARALLEL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* mergeAttr --
|
|
|
|
|
*
|
|
|
|
|
* merge two attribute strings
|
|
|
|
|
*
|
|
|
|
|
* Results:
|
|
|
|
|
* The merged strings
|
|
|
|
|
*
|
|
|
|
|
* Side effects:
|
|
|
|
|
* Might allocate and free memory.
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
void
|
2025-07-18 15:11:04 +02:00
|
|
|
mergeAttr(
|
|
|
|
|
char **a1,
|
|
|
|
|
char **a2)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
if (*a1 == NULL)
|
2025-07-18 15:11:04 +02:00
|
|
|
*a1 = *a2; /* TODO check strdup() is appropiate due to lifetime ambiguity this creates */
|
2017-04-25 14:41:48 +02:00
|
|
|
else
|
|
|
|
|
{
|
2025-02-24 09:31:42 +01:00
|
|
|
size_t l1 = strlen(*a1);
|
|
|
|
|
size_t l2 = strlen(*a2);
|
|
|
|
|
char *t = (char *) mallocMagic(l1 + l2 + 1);
|
|
|
|
|
strcpy(t, *a1); /* strcpy_advance() */
|
|
|
|
|
strcat(t, *a2);
|
2017-04-25 14:41:48 +02:00
|
|
|
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.
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
2025-07-20 01:03:14 +02:00
|
|
|
/* ARGSUSED */
|
2025-07-18 23:02:48 +02:00
|
|
|
/* @typedef cb_extflat_visitdevs_t (UNUSED) */
|
2017-04-25 14:41:48 +02:00
|
|
|
int
|
2025-07-18 15:11:04 +02:00
|
|
|
devMergeVisit(
|
|
|
|
|
Dev *dev, /* Dev to examine */
|
|
|
|
|
HierContext *hc, /* Hierarchical context down to this dev */
|
|
|
|
|
float scale, /* Scale transform */
|
2025-07-18 23:02:48 +02:00
|
|
|
Transform *trans, /* (unused) */
|
|
|
|
|
ClientData cdata) /* (unused) */
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
DevTerm *gate, *source, *drain;
|
|
|
|
|
EFNode *subnode, *snode, *dnode, *gnode;
|
|
|
|
|
int pmode, l, w;
|
|
|
|
|
bool hS, hD, chS, chD;
|
2025-07-24 15:36:22 +02:00
|
|
|
devMerge *fp;
|
|
|
|
|
const devMerge *cfp;
|
2017-04-25 14:41:48 +02:00
|
|
|
float m;
|
2020-06-01 23:14:22 +02:00
|
|
|
HierName *hierName = hc->hc_hierName;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
if (esDistrJunct)
|
2025-07-18 23:02:48 +02:00
|
|
|
devDistJunctVisit(dev, hc, scale, trans, cdata);
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
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,
|
2020-05-23 23:13:14 +02:00
|
|
|
dev->dev_subsnode->efnode_name->efnn_hier,
|
2017-04-25 14:41:48 +02:00
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
/*
|
2020-05-23 23:13:14 +02:00
|
|
|
* run the list of devs. compare the current one with
|
2017-04-25 14:41:48 +02:00
|
|
|
* each one in the list. if they fullfill the matching requirements
|
|
|
|
|
* merge them only if:
|
|
|
|
|
* 1) they have both apf S, D attributes
|
2020-05-23 23:13:14 +02:00
|
|
|
* or
|
|
|
|
|
* 2) one of them has aph S, D attributes and they have the same
|
2017-04-25 14:41:48 +02:00
|
|
|
* 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)
|
|
|
|
|
{
|
2025-07-24 15:36:22 +02:00
|
|
|
#if 0 /* -Wunused-but-set-variable */
|
|
|
|
|
Dev *cf = cfp->dev;
|
|
|
|
|
DevTerm *cg = &cfp->dev->dev_terms[0];
|
|
|
|
|
#endif
|
|
|
|
|
DevTerm *cs = &cfp->dev->dev_terms[1];
|
|
|
|
|
DevTerm *cd = cs;
|
2017-04-25 14:41:48 +02:00
|
|
|
if (cfp->dev->dev_nterm >= 3)
|
|
|
|
|
{
|
2020-05-23 23:13:14 +02:00
|
|
|
if (pmode == PARALLEL)
|
2017-04-25 14:41:48 +02:00
|
|
|
cd = &cfp->dev->dev_terms[2];
|
2020-05-23 23:13:14 +02:00
|
|
|
else if (pmode == ANTIPARALLEL)
|
2017-04-25 14:41:48 +02:00
|
|
|
cs = &cfp->dev->dev_terms[2];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
chS = extHierSDAttr(cs); chD = extHierSDAttr(cd);
|
|
|
|
|
if (!(chS || chD || hS || hD)) /* all flat S, D */
|
|
|
|
|
goto mergeThem;
|
2020-05-23 23:13:14 +02:00
|
|
|
if (cfp->hierName != hierName &&
|
2017-04-25 14:41:48 +02:00
|
|
|
((chS && !hS) || (chD && !hD) ||
|
|
|
|
|
(!chS && hS) || (!chD && hD)))
|
2020-05-23 23:13:14 +02:00
|
|
|
{
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
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:
|
2023-05-21 18:13:01 +02:00
|
|
|
/* Default case is to add counts */
|
|
|
|
|
m = esFMult[cfp->esFMIndex] + esFMult[fp->esFMIndex];
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
switch(dev->dev_class)
|
|
|
|
|
{
|
|
|
|
|
case DEV_MSUBCKT:
|
|
|
|
|
case DEV_MOSFET:
|
|
|
|
|
case DEV_ASYMMETRIC:
|
|
|
|
|
case DEV_FET:
|
2023-05-21 18:13:01 +02:00
|
|
|
if (cfp->w > 0)
|
|
|
|
|
m = esFMult[cfp->esFMIndex] + (fp->w / cfp->w);
|
2017-04-25 14:41:48 +02:00
|
|
|
break;
|
|
|
|
|
case DEV_RSUBCKT:
|
|
|
|
|
case DEV_RES:
|
|
|
|
|
if (fp->dev->dev_type == esNoModelType)
|
2023-05-21 18:13:01 +02:00
|
|
|
{
|
|
|
|
|
if (cfp->dev->dev_res > 0)
|
|
|
|
|
m = esFMult[cfp->esFMIndex] + (fp->dev->dev_res
|
|
|
|
|
/ cfp->dev->dev_res);
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
else
|
2023-05-21 18:13:01 +02:00
|
|
|
{
|
|
|
|
|
if (cfp->l > 0)
|
|
|
|
|
m = esFMult[cfp->esFMIndex] + (fp->l / cfp->l);
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
break;
|
2018-10-30 21:19:20 +01:00
|
|
|
case DEV_CSUBCKT:
|
2017-04-25 14:41:48 +02:00
|
|
|
case DEV_CAP:
|
|
|
|
|
case DEV_CAPREV:
|
|
|
|
|
if (fp->dev->dev_type == esNoModelType)
|
2023-05-21 18:13:01 +02:00
|
|
|
{
|
|
|
|
|
if (cfp->dev->dev_cap > 0)
|
|
|
|
|
m = esFMult[cfp->esFMIndex] + (fp->dev->dev_cap
|
|
|
|
|
/ cfp->dev->dev_cap);
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
else
|
2023-05-21 18:13:01 +02:00
|
|
|
{
|
|
|
|
|
if ((cfp->l > 0) && (cfp->w > 0))
|
|
|
|
|
m = esFMult[cfp->esFMIndex] +
|
|
|
|
|
((fp->l * fp->w) / (cfp->l * cfp->w));
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
break;
|
|
|
|
|
}
|
2020-05-23 23:13:14 +02:00
|
|
|
setDevMult(fp->esFMIndex, DEV_KILLED);
|
2017-04-25 14:41:48 +02:00
|
|
|
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
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
2020-05-23 23:13:14 +02:00
|
|
|
int
|
2025-07-18 15:11:04 +02:00
|
|
|
update_w(
|
|
|
|
|
short resClass,
|
|
|
|
|
int w,
|
|
|
|
|
EFNode *n)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
nodeClient *nc;
|
|
|
|
|
int i;
|
|
|
|
|
|
2020-05-23 23:13:14 +02:00
|
|
|
if (n->efnode_client == (ClientData)NULL)
|
2017-04-25 14:41:48 +02:00
|
|
|
initNodeClient(n);
|
|
|
|
|
nc = (nodeClient *) n->efnode_client;
|
|
|
|
|
if (nc->m_w.widths == NULL)
|
|
|
|
|
{
|
|
|
|
|
(nc->m_w.widths) = (float *)mallocMagic((unsigned)sizeof(float)
|
2021-12-20 19:12:49 +01:00
|
|
|
* (efNumResistClasses + 1));
|
|
|
|
|
for (i = 0; i <= efNumResistClasses; i++)
|
2017-04-25 14:41:48 +02:00
|
|
|
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
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
2025-07-20 01:03:14 +02:00
|
|
|
/* ARGSUSED */
|
2025-07-18 23:02:48 +02:00
|
|
|
/* @typedef cb_extflat_visitdevs_t (UNUSED) */
|
2020-05-23 23:13:14 +02:00
|
|
|
int
|
2025-07-18 15:11:04 +02:00
|
|
|
devDistJunctVisit(
|
|
|
|
|
Dev *dev, /* Dev to examine */
|
|
|
|
|
HierContext *hc, /* Hierarchical path down to this dev */
|
|
|
|
|
float scale, /* Scale transform */
|
2025-07-18 23:02:48 +02:00
|
|
|
Transform *trans, /* (unused) */
|
|
|
|
|
ClientData cdata) /* (unused) */
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
EFNode *n;
|
|
|
|
|
int i;
|
|
|
|
|
int l, w;
|
2020-06-01 23:14:22 +02:00
|
|
|
HierName *hierName = hc->hc_hierName;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
if (dev->dev_nterm < 2)
|
|
|
|
|
{
|
|
|
|
|
TxError("outPremature\n");
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EFGetLengthAndWidth(dev, &l, &w);
|
2019-06-07 20:13:50 +02:00
|
|
|
w = (int)((float)w * scale);
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
for (i = 1; i<dev->dev_nterm; i++)
|
|
|
|
|
{
|
|
|
|
|
n = SpiceGetNode(hierName,
|
|
|
|
|
dev->dev_terms[i].dterm_node->efnode_name->efnn_hier);
|
2020-09-11 23:29:12 +02:00
|
|
|
if (i == 1)
|
|
|
|
|
update_w(esFetInfo[dev->dev_type].resClassSource, w, n);
|
|
|
|
|
else
|
|
|
|
|
update_w(esFetInfo[dev->dev_type].resClassDrain, w, n);
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|