/* * ExtBasic.c -- * * Circuit extraction. * Flat extraction of a single CellDef. * * ********************************************************************* * * Copyright (C) 1985, 1990 Regents of the University of California. * * * Permission to use, copy, modify, and distribute this * * * software and its documentation for any purpose and without * * * fee is hereby granted, provided that the above copyright * * * notice appear in all copies. The University of California * * * makes no representations about the suitability of this * * * software for any purpose. It is provided "as is" without * * * express or implied warranty. Export of this software outside * * * of the United States of America may require an export license. * * ********************************************************************* */ #ifndef lint static char sccsid[] = "@(#)ExtBasic.c 4.13 MAGIC (Berkeley) 12/5/85"; #endif /* not lint */ #include #include #include #include #include "tcltk/tclmagic.h" #include "utils/magic.h" #include "utils/geometry.h" #include "utils/geofast.h" #include "tiles/tile.h" #include "utils/hash.h" #include "database/database.h" #include "utils/malloc.h" #include "textio/textio.h" #include "debug/debug.h" #include "extract/extract.h" #include "extract/extractInt.h" #include "utils/signals.h" #include "windows/windows.h" #include "dbwind/dbwind.h" #include "utils/styles.h" #include "utils/stack.h" #include "utils/utils.h" /* These must be in the order of "known devices" in extract.h. */ /* Note: "fet" refers to the original fet type; "mosfet" refers to the */ /* new type. The main difference is that "fet" records area/perimeter */ /* while "mosfet" records length/width. */ /* Also: Note that this table is repeated in extflat/EFread.c when */ /* ext2spice/ext2sim are compiled as separate programs (i.e., non-Tcl) */ #ifdef MAGIC_WRAPPER char *extDevTable[] = {"fet", "mosfet", "asymmetric", "bjt", "devres", "devcap", "devcaprev", "vsource", "diode", "pdiode", "ndiode", "subckt", "rsubckt", "msubckt", "csubckt", NULL}; #endif /* --------------------- Data local to this file ---------------------- */ /* * The following are used to accumulate perimeter and area * on each layer when building up the node list. They are * used to compute the resistance of each node. Each is * indexed by sheet resistivity class. */ int extResistPerim[NT]; dlong extResistArea[NT]; /* * The following structure is used in extracting transistors. * * A "terminal" below refers to any port on the transistor that * is not the gate. In most cases, these are the "diffusion" * ports of the transistor. */ #define MAXSD 10 /* Maximum # of terminals per transistor */ typedef struct /* Position of each terminal (below) tile position */ { int pnum; Point pt; } TermTilePos; /* Field definitions for tr_devmatch */ #define MATCH_ID 0x01 /* Device matches identifier in devrec */ #define MATCH_SUB 0x02 /* Device matches substrate type in devrec */ #define MATCH_TERM 0x04 /* Device matches terminal in devrec */ /* (additional fields: bit shifts up by 1 for each defined device terminal) */ struct transRec { ExtDevice *tr_devrec; /* Device record in ExtCurStyle */ int tr_devmatch; /* Fields of tr_devrec that match device */ int tr_nterm; /* Number of terminals */ int tr_gatelen; /* Perimeter of connection to gate */ NodeRegion *tr_gatenode; /* Node region for gate terminal */ NodeRegion *tr_termnode[MAXSD]; /* Node region for each diff terminal */ NodeRegion *tr_subsnode; /* Substrate node */ int tr_termlen[MAXSD]; /* Length of each diff terminal edge, * used for computing L/W for the fet. */ Point tr_termvector[MAXSD]; /* Perimeter traversal vector, used to * find and calculate correct parameters * for annular (ring) devices and other * non-rectangular geometries. */ int tr_perim; /* Total perimeter */ TermTilePos tr_termpos[MAXSD]; /* lowest tile connecting to term */ } extTransRec; typedef struct LB1 { Rect r; /* Boundary segment */ int dir; /* Direction of travel */ struct LB1 *b_next; } LinkedBoundary; LinkedBoundary **extSpecialBounds; /* Linked Boundary List */ NodeRegion *glob_subsnode = NULL; /* Global substrate node */ NodeRegion *temp_subsnode = NULL; /* Last subsnode found */ #define EDGENULL(r) ((r)->r_xbot > (r)->r_xtop || (r)->r_ybot > (r)->r_ytop) /* Forward declarations */ void extOutputNodes(); int extTransTileFunc(); int extTransPerimFunc(); int extTransFindSubs(); int extTransFindId(); int extAnnularTileFunc(); int extResistorTileFunc(); int extSpecialPerimFunc(); void extFindDuplicateLabels(); void extOutputDevices(); void extOutputParameters(); void extTransOutTerminal(); void extTransBad(); ExtDevice *extDevFindMatch(); bool extLabType(); /* Function returns 1 if a tile is found by DBTreeSrTiles() */ /* that is not in the topmost def of the search. */ int extFoundFunc(tile, cxp) Tile *tile; TreeContext *cxp; { CellDef *def = (CellDef *)cxp->tc_filter->tf_arg; return (def == cxp->tc_scx->scx_use->cu_def) ? 0 : 1; } /* * ---------------------------------------------------------------------------- * * extBasic -- * * Extract a single CellDef, and output the result to the * file 'outFile'. * * Results: * Returns a list of Region structs that comprise all * the nodes in 'def'. It is the caller's responsibility * to call ExtResetTile() and ExtFreeLabRegions() to restore * the CellDef to its original state and to free the list * of regions we build up. * * Side effects: * Writes the result of extracting just the paint of * the CellDef 'def' to the output file 'outFile'. * The following kinds of records are output: * * node * substrate * equiv * fet * device * * Interruptible in a limited sense. We will still return a * Region list, but labels may not have been assigned, and * nodes and fets may not have been output. * * ---------------------------------------------------------------------------- */ NodeRegion * extBasic(def, outFile) CellDef *def; /* Cell being extracted */ FILE *outFile; /* Output file */ { NodeRegion *nodeList, *extFindNodes(); bool coupleInitialized = FALSE; TransRegion *transList, *reg; HashTable extCoupleHash; char *propptr; bool propfound = FALSE; bool isabstract = FALSE; glob_subsnode = (NodeRegion *)NULL; /* * Build up a list of the device regions for extOutputDevices() * below. We're only interested in pointers from each region to * a tile in that region, not the back pointers from the tiles to * the regions. */ transList = (TransRegion *) ExtFindRegions(def, &TiPlaneRect, &ExtCurStyle->exts_deviceMask, ExtCurStyle->exts_deviceConn, extUnInit, extTransFirst, extTransEach); ExtResetTiles(def, extUnInit); for (reg = transList; reg && !SigInterruptPending; reg = reg->treg_next) { /* For each transistor region, check if there is an equivalent */ /* region at the same location in a subcell. The device in the */ /* subcell is given priority. This avoids duplicating devices */ /* when, for example, a device contact is placed in another */ /* cell, which can happen for devices like capacitors and */ /* diodes, where the device identifier layer may include */ /* a contact type. NOTE: This routine needs to limit the */ /* search to devices in the same plane as the transistor under */ /* consideration. */ SearchContext scontext; CellUse dummy; int extFoundFunc(); TileTypeBitMask transPlaneMask; scontext.scx_use = &dummy; dummy.cu_def = def; dummy.cu_id = NULL; scontext.scx_trans = GeoIdentityTransform; scontext.scx_area.r_ll = scontext.scx_area.r_ur = reg->treg_tile->ti_ll; scontext.scx_area.r_ur.p_x++; scontext.scx_area.r_ur.p_y++; TTMaskAndMask3(&transPlaneMask, &ExtCurStyle->exts_deviceMask, &DBPlaneTypes[reg->treg_pnum]); if (DBTreeSrTiles(&scontext, &transPlaneMask, 0, extFoundFunc, (ClientData)def) != 0) reg->treg_type = TT_SPACE; /* Disables the trans record */ } /* * Build up a list of the electrical nodes (equipotentials) * for extOutputNodes() below. For this, we definitely want * to leave each tile pointing to its associated Region struct. * Compute resistance and capacitance on the fly. * Use a special-purpose version of ExtFindRegions for speed. */ if (!SigInterruptPending) nodeList = extFindNodes(def, (Rect *) NULL, FALSE); glob_subsnode = temp_subsnode; // Keep a record of the def's substrate /* Assign the labels to their associated regions */ if (!SigInterruptPending) ExtLabelRegions(def, ExtCurStyle->exts_nodeConn, &nodeList, &TiPlaneRect); /* Check for "LEFview", for which special output handling */ /* can be specified in ext2spice. */ DBPropGet(def, "LEFview", &isabstract); /* * Make sure all geometry with the same label is part of the * same electrical node. However: Unconnected labels are allowed * on abstract views. */ if (!SigInterruptPending && (ExtDoWarn & EXTWARN_DUP) && !isabstract) extFindDuplicateLabels(def, nodeList); /* * Build up table of coupling capacitances (overlap, sidewall). * This comes before extOutputNodes because we may have to adjust * node capacitances in this step. */ if (!SigInterruptPending && (ExtOptions&EXT_DOCOUPLING)) { coupleInitialized = TRUE; HashInit(&extCoupleHash, 256, HashSize(sizeof (CoupleKey))); extFindCoupling(def, &extCoupleHash, (Rect *) NULL); /* Convert coupling capacitance to the substrate node to * substrate capacitance on each node in nreg_cap */ if (ExtCurStyle->exts_globSubstratePlane != -1) if (!SigInterruptPending && (ExtOptions&EXT_DOCOUPLING)) extRelocateSubstrateCoupling(&extCoupleHash, glob_subsnode); } /* Output device parameters for any subcircuit devices */ if (!SigInterruptPending) extOutputParameters(def, transList, outFile); /* Check for "device", as it modifies handling of parasitics */ propptr = (char *)DBPropGet(def, "device", &propfound); if (propfound) { /* Remove parasitics from local nodes */ NodeRegion *tnode; for (tnode = nodeList; tnode; tnode = tnode->nreg_next) { tnode->nreg_cap = (CapValue)0.0; tnode->nreg_resist = (ResValue)0; } } if (isabstract) fprintf(outFile, "abstract\n"); /* Output each node, along with its resistance and capacitance to substrate */ if (!SigInterruptPending) extOutputNodes(nodeList, outFile, glob_subsnode); /* Output coupling capacitances */ if (!SigInterruptPending && (ExtOptions&EXT_DOCOUPLING) && (!propfound)) extOutputCoupling(&extCoupleHash, outFile); /* Output devices and connectivity between nodes */ if (!SigInterruptPending) { int llx, lly, urx, ury, devidx, l, w; char *token, *modelname, *subsnode; char *propvalue; modelname = NULL; subsnode = NULL; propvalue = NULL; if (propfound) { /* Sanity checking on syntax of property line, plus */ /* conversion of values to internal units. */ propvalue = StrDup((char **)NULL, propptr); token = strtok(propvalue, " "); devidx = Lookup(token, extDevTable); if (devidx < 0) { TxError("Extract error: \"device\" property has unknown " "device type.\n", token); propfound = FALSE; } if (propfound) { token = strtok(NULL, " "); if (token == NULL) propfound = FALSE; else modelname = StrDup((char **)NULL, token); } if (propfound) { token = strtok(NULL, " "); if ((token == NULL) || !sscanf(token, "%d", &llx)) propfound = FALSE; else llx *= ExtCurStyle->exts_unitsPerLambda; } if (propfound) { token = strtok(NULL, " "); if ((token == NULL) || !sscanf(token, "%d", &lly)) propfound = FALSE; else lly *= ExtCurStyle->exts_unitsPerLambda; } if (propfound) { token = strtok(NULL, " "); if ((token == NULL) || !sscanf(token, "%d", &urx)) propfound = FALSE; else urx *= ExtCurStyle->exts_unitsPerLambda; if (urx <= llx) urx++; } if (propfound) { token = strtok(NULL, " "); if ((token == NULL) || !sscanf(token, "%d", &ury)) propfound = FALSE; else ury *= ExtCurStyle->exts_unitsPerLambda; if (ury <= lly) ury++; } if (propfound) { switch (devidx) { case DEV_FET: /* Read area */ token = strtok(NULL, " "); if ((token == NULL) || !sscanf(token, "%d", &w)) propfound = FALSE; else w *= ExtCurStyle->exts_unitsPerLambda * ExtCurStyle->exts_unitsPerLambda; /* Read perimeter */ token = strtok(NULL, " "); if ((token == NULL) || !sscanf(token, "%d", &l)) propfound = FALSE; else l *= ExtCurStyle->exts_unitsPerLambda; break; case DEV_MOSFET: case DEV_ASYMMETRIC: case DEV_BJT: /* Read width */ token = strtok(NULL, " "); if ((token == NULL) || !sscanf(token, "%d", &w)) propfound = FALSE; else w *= ExtCurStyle->exts_unitsPerLambda; /* Read length */ token = strtok(NULL, " "); if ((token == NULL) || !sscanf(token, "%d", &l)) propfound = FALSE; else l *= ExtCurStyle->exts_unitsPerLambda; break; case DEV_RES: if (strcmp(modelname, "None")) { /* Read width */ token = strtok(NULL, " "); if ((token == NULL) || !sscanf(token, "%d", &w)) propfound = FALSE; else w *= ExtCurStyle->exts_unitsPerLambda; /* Read length */ token = strtok(NULL, " "); if ((token == NULL) || !sscanf(token, "%d", &l)) propfound = FALSE; else l *= ExtCurStyle->exts_unitsPerLambda; break; } break; case DEV_CAP: case DEV_CAPREV: if (strcmp(modelname, "None")) { /* Read area */ token = strtok(NULL, " "); if ((token == NULL) || !sscanf(token, "%d", &w)) propfound = FALSE; else w *= ExtCurStyle->exts_unitsPerLambda * ExtCurStyle->exts_unitsPerLambda; /* Read perimeter */ token = strtok(NULL, " "); if ((token == NULL) || !sscanf(token, "%d", &l)) propfound = FALSE; else l *= ExtCurStyle->exts_unitsPerLambda; break; } break; } } if (propfound) { if (devidx == DEV_FET) fprintf(outFile, "fet"); else fprintf(outFile, "device %s", extDevTable[devidx]); fprintf(outFile, " %s %d %d %d %d", modelname, llx, lly, urx, ury); switch (devidx) { case DEV_FET: case DEV_MOSFET: case DEV_ASYMMETRIC: case DEV_BJT: fprintf(outFile, " %d %d", w, l); break; case DEV_RES: case DEV_CAP: case DEV_CAPREV: if (strcmp(modelname, "None")) fprintf(outFile, " %d %d", w, l); break; } /* Print remainder of arguments verbatim. */ /* Note: There should be additional checks on */ /* node triplets including area and perim. conversions */ while (1) { token = strtok(NULL, " "); if (token == NULL) break; else fprintf(outFile, " %s", token); } } else if (devidx >= 0) { TxError("Extract error: \"device %s\" property syntax" " error\n", extDevTable[devidx]); } if (modelname) freeMagic(modelname); if (propvalue) freeMagic(propvalue); } if (!propfound) extOutputDevices(def, transList, outFile); } /* Clean up */ if (coupleInitialized) extCapHashKill(&extCoupleHash); ExtFreeLabRegions((LabRegion *) transList); return (nodeList); } /* * ---------------------------------------------------------------------------- * * extSetResist -- * * The input to this procedure is a pointer to a NodeRegion. * Its resistance is computed from the area and perimeter stored * in the arrays extResistPerim[] and extResistArea[]. These arrays * are then reset to zero. * * We approximate the resistive region as a collection of rectangles * of width W and length L, one for each set of layers having a different * sheet resistivity. We do so by noting that for a rectangle, * * Area = L * W * Perimeter = 2 * (L + W) * * Solving the two simultaneous equations for L yields the following * quadratic: * * 2 * (L**2) - Perimeter * L + 2 * Area = 0 * * Solving this quadratic for L, the longer dimension, we get * * L = (Perimeter + S) / 4 * * where * * S = sqrt( (Perimeter**2) - 16 * Area ) * * The smaller dimension is W, ie, * * W = (Perimeter - S) / 4 * * The resistance is L / W squares: * * Perimeter + S * R = ------------- * Perimeter - S * * Results: * None. * * Side effects: * See the comments above. * * ---------------------------------------------------------------------------- */ void extSetResist(reg) NodeRegion *reg; { int n, perim; dlong area; float s, fperim, v; for (n = 0; n < ExtCurStyle->exts_numResistClasses; n++) { reg->nreg_pa[n].pa_area = area = extResistArea[n]; reg->nreg_pa[n].pa_perim = perim = extResistPerim[n]; if (area > 0 && perim > 0) { v = (double) (perim*perim - 16*area); /* Approximate by one square if v < 0 */ if (v < 0) s = 0; else s = sqrt(v); fperim = (float) perim; reg->nreg_resist += (fperim + s) / (fperim - s) * ExtCurStyle->exts_resistByResistClass[n]; } /* Reset for the next pass */ extResistArea[n] = extResistPerim[n] = 0; } } /* * ---------------------------------------------------------------------------- * * extOutputNodes -- * * The resistance and capacitance of each node have already been * computed, so all we need do is output them. * * Results: * None. * * Side effects: * Writes a number of 'node' and 'equiv' records to the file 'outFile'. * * Interruptible. If SigInterruptPending is detected, we stop outputting * nodes and return. * * ---------------------------------------------------------------------------- */ void extOutputNodes(nodeList, outFile) NodeRegion *nodeList; /* Nodes */ FILE *outFile; /* Output file */ { ResValue rround = ExtCurStyle->exts_resistScale / 2; CapValue finC; int intR; NodeRegion *reg; LabelList *ll; char *cp; int n; Label *lab; char *text; /* If this node is a subcircuit port, it gets special treatment. */ /* There may be multiple ports per node. */ for (reg = nodeList; reg && !SigInterruptPending; reg = reg->nreg_next) for (ll = reg->nreg_labels; ll; ll = ll->ll_next) if (ll->ll_attr == LL_PORTATTR) { fprintf(outFile, "port \"%s\" %d %d %d %d %d %s\n", ll->ll_label->lab_text, ll->ll_label->lab_port, ll->ll_label->lab_rect.r_xbot, ll->ll_label->lab_rect.r_ybot, ll->ll_label->lab_rect.r_xtop, ll->ll_label->lab_rect.r_ytop, DBTypeShortName(ll->ll_label->lab_type)); /* If the port name matches the node name to be written */ /* to the node record, then reassign the node position */ /* and type to be that of the port, so we don't have a */ /* conflict. */ if (!strcmp(extNodeName((LabRegion *) reg), ll->ll_label->lab_text)) { reg->nreg_ll.p_x = ll->ll_label->lab_rect.r_xbot; reg->nreg_ll.p_y = ll->ll_label->lab_rect.r_ybot; reg->nreg_type = ll->ll_label->lab_type; reg->nreg_pnum = DBPlane(reg->nreg_type); } } for (reg = nodeList; reg && !SigInterruptPending; reg = reg->nreg_next) { /* Output the node */ text = extNodeName((LabRegion *) reg); /* Check if this node is the substrate */ if (reg == glob_subsnode) { fprintf(outFile, "substrate \"%s\" 0 0", text); } else { intR = (reg->nreg_resist + rround) / ExtCurStyle->exts_resistScale; finC = reg->nreg_cap/ExtCurStyle->exts_capScale; fprintf(outFile, "node \"%s\" %d %lg", text, intR, finC); } /* Output its location (lower-leftmost point and type name) */ if (reg->nreg_type & TT_DIAGONAL) { /* Node may be recorded as a diagonal tile if no other */ /* non-diagonal tiles are adjoining it. */ TileType loctype = (reg->nreg_type & TT_SIDE) ? ((reg->nreg_type & TT_RIGHTMASK) >> 14) : (reg->nreg_type & TT_LEFTMASK); fprintf(outFile, " %d %d %s", reg->nreg_ll.p_x, reg->nreg_ll.p_y, DBTypeShortName(loctype)); } else { fprintf(outFile, " %d %d %s", reg->nreg_ll.p_x, reg->nreg_ll.p_y, DBTypeShortName(reg->nreg_type)); } /* Output its area and perimeter for each resistivity class */ for (n = 0; n < ExtCurStyle->exts_numResistClasses; n++) fprintf(outFile, " %"DLONG_PREFIX"d %d", reg->nreg_pa[n].pa_area, reg->nreg_pa[n].pa_perim); (void) putc('\n', outFile); /* Output its attribute list */ for (ll = reg->nreg_labels; ll; ll = ll->ll_next) if (extLabType(ll->ll_label->lab_text, LABTYPE_NODEATTR)) { /* Don't output the trailing character for node attributes */ lab = ll->ll_label; fprintf(outFile, "attr %s %d %d %d %d %s \"", text, lab->lab_rect.r_xbot, lab->lab_rect.r_ybot, lab->lab_rect.r_xtop, lab->lab_rect.r_ytop, DBTypeShortName(lab->lab_type)); cp = lab->lab_text; n = strlen(cp) - 1; while (n-- > 0) putc(*cp++, outFile); fprintf(outFile, "\"\n"); } /* Output the alternate names for the node. Avoid generating */ /* unnecessary "equiv A A" entries for labels on disconnected */ /* nets. Also avoid multiple "equiv" statements with the same */ /* nets (happens when ports with the same name have different */ /* port numbers, which should probably just be prohibited), and */ /* raise an error if two ports with different names are being */ /* marked as equivalent. */ for (ll = reg->nreg_labels; ll; ll = ll->ll_next) { bool isPort; /* Do not export aliases that are not ports unless the */ /* "extract do aliases" options was selected. */ if (ll->ll_label->lab_text == text) { char *portname = NULL; char *lastname = NULL; isPort = (ll->ll_attr == LL_PORTATTR) ? TRUE : FALSE; if (isPort) portname = text; for (ll = ll->ll_next; ll; ll = ll->ll_next) if (extLabType(ll->ll_label->lab_text, LABTYPE_NAME)) if (strcmp(text, ll->ll_label->lab_text)) { if ((ll->ll_attr == LL_PORTATTR) || (ExtOptions & EXT_DOALIASES)) { if ((portname == NULL) || (strcmp(ll->ll_label->lab_text, portname))) { if ((lastname == NULL) || (strcmp(ll->ll_label->lab_text, lastname))) fprintf(outFile, "equiv \"%s\" \"%s\"\n", text, ll->ll_label->lab_text); lastname = ll->ll_label->lab_text; } if ((portname != NULL) && (strcmp(ll->ll_label->lab_text, portname))) TxError("Warning: Ports \"%s\" and \"%s\" are" " electrically shorted.\n", text, ll->ll_label->lab_text); if (!isPort && (ll->ll_attr == LL_PORTATTR)) portname = ll->ll_label->lab_text; } } break; } } } } /* * --------------------------------------------------------------------- * * extSubsName -- * * Return the name of the substrate node, if the node belongs to * the substrate region and a global substrate node name has been * specified by the tech file. If the substrate node name is a * Tcl variable name, then perform the variable substitution. * * Results: * Pointer to a character string. * * Side Effects: * None. * * --------------------------------------------------------------------- */ char * extSubsName(node) LabRegion *node; { char *subsName; /* If the techfile specifies a global name for the substrate, use */ /* that in preference to the default "p_x_y#" name. Use this name */ /* only to substitute for nodes with tiles at -(infinity). */ if (ExtCurStyle->exts_globSubstrateName != NULL) { if (node->lreg_ll.p_x <= (MINFINITY + 3)) { if (ExtCurStyle->exts_globSubstrateName[0] == '$' && ExtCurStyle->exts_globSubstrateName[1] != '$') { // If subsName is a Tcl variable (begins with "$"), make the // variable substitution, if one exists. Ignore double-$. // If the variable is undefined in the interpreter, then // strip the "$" from the front as this is not legal in most // netlist formats. char *varsub = (char *)Tcl_GetVar(magicinterp, &ExtCurStyle->exts_globSubstrateName[1], TCL_GLOBAL_ONLY); return (varsub != NULL) ? varsub : ExtCurStyle->exts_globSubstrateName + 1; } else return ExtCurStyle->exts_globSubstrateName; } else return NULL; } return NULL; } /* * ---------------------------------------------------------------------------- * * extMakeNodeNumPrint -- * * Construct a node name from the plane number "plane" and lower left Point * "coord", and place it in the string "buf" (which must be large enough). * * Results: * None. * * Side Effects: * Fills in string "buf". * * ---------------------------------------------------------------------------- */ void extMakeNodeNumPrint(buf, lreg) char *buf; LabRegion *lreg; { int plane = lreg->lreg_pnum; Point *p = &lreg->lreg_ll; char *subsName; subsName = extSubsName(lreg); if (subsName != NULL) strcpy(buf, subsName); else sprintf(buf, "%s_%s%d_%s%d#", DBPlaneShortName(plane), (p->p_x < 0) ? "n": "", abs(p->p_x), (p->p_y < 0) ? "n": "", abs(p->p_y)); } /* * ---------------------------------------------------------------------------- * * extNodeName -- * * Given a pointer to a LabRegion, return a pointer to a string * that can be printed as the name of the node. If the LabRegion * has a list of attached labels, use one of the labels; otherwise, * use its node number. * * Results: * Returns a pointer to a string. If the node had a label, this * is a pointer to the lab_text field of the first label on the * label list for the node; otherwise, it is a pointer to a static * buffer into which we have printed the node number. * * Side effects: * May overwrite the static buffer used to hold the printable * version of a node number. * * ---------------------------------------------------------------------------- */ char * extNodeName(node) LabRegion *node; { static char namebuf[256]; /* Big enough to hold a generated nodename */ LabelList *ll; if (node == (LabRegion *) NULL || SigInterruptPending) return ("(none)"); for (ll = node->lreg_labels; ll; ll = ll->ll_next) if (extLabType(ll->ll_label->lab_text, LABTYPE_NAME)) return (ll->ll_label->lab_text); extMakeNodeNumPrint(namebuf, node); return (namebuf); } /* * ---------------------------------------------------------------------------- * * extFindDuplicateLabels -- * * Verify that no node in the list 'nreg' has a label that appears in * any other node in the list. Leave a warning turd if one is. * * Results: * None. * * Side effects: * Leaves feedback attached to each node that contains a label * duplicated in another node. * * ---------------------------------------------------------------------------- */ void extFindDuplicateLabels(def, nreg) CellDef *def; NodeRegion *nreg; { static char *badmesg = "Label \"%s\" attached to more than one unconnected node: %s"; bool hashInitialized = FALSE; char message[512], name[512], *text; NodeRegion *np, *np2; LabelList *ll, *ll2; HashEntry *he; NodeRegion *lastreg; NodeRegion badLabel; HashTable labelHash; Rect r; for (np = nreg; np; np = np->nreg_next) { for (ll = np->nreg_labels; ll; ll = ll->ll_next) { text = ll->ll_label->lab_text; if (!extLabType(text, LABTYPE_NAME)) continue; if (!hashInitialized) HashInit(&labelHash, 32, 0), hashInitialized = TRUE; he = HashFind(&labelHash, text); lastreg = (NodeRegion *) HashGetValue(he); if (lastreg == (NodeRegion *) NULL) HashSetValue(he, (ClientData) np); else if (lastreg != np && lastreg != &badLabel) { /* * Make a pass through all labels for all nodes. * Leave a feedback turd over each instance of the * offending label. */ for (np2 = nreg; np2; np2 = np2->nreg_next) { for (ll2 = np2->nreg_labels; ll2; ll2 = ll2->ll_next) { if (strcmp(ll2->ll_label->lab_text, text) == 0) { extNumWarnings++; if (!DebugIsSet(extDebugID, extDebNoFeedback)) { r.r_ll = r.r_ur = ll2->ll_label->lab_rect.r_ll; r.r_xbot--, r.r_ybot--, r.r_xtop++, r.r_ytop++; extMakeNodeNumPrint(name, np2); (void) sprintf(message, badmesg, text, name); DBWFeedbackAdd(&r, message, def, 1, STYLE_PALEHIGHLIGHTS); } } } } /* Mark this label as already having generated an error */ HashSetValue(he, (ClientData) &badLabel); } } } if (hashInitialized) HashKill(&labelHash); } /* * --------------------------------------------------------------------- * * ExtSortTerminals -- * * Sort the terminals of a transistor so that the terminal with the * lowest leftmost coordinate on the plane with the lowest number is * output first. * * Results: * None * * Side effects: * The tr_termnode, tr_termlen, and tr_termpos entries may change. * * --------------------------------------------------------------------- */ void ExtSortTerminals(tran, ll) struct transRec *tran; LabelList *ll; { int nsd, changed; TermTilePos *p1, *p2; NodeRegion *tmp_node; TermTilePos tmp_pos; int tmp_len; LabelList *lp; do { changed = 0; for( nsd = 0; nsd < tran->tr_nterm-1; nsd++ ) { p1 = &(tran->tr_termpos[nsd]); p2 = &(tran->tr_termpos[nsd+1]); if( p2->pnum > p1->pnum ) continue; else if( p2->pnum == p1->pnum ) { if( p2->pt.p_x > p1->pt.p_x ) continue; else if( p2->pt.p_x == p1->pt.p_x && p2->pt.p_y > p1->pt.p_y ) continue; else if( p2->pt.p_x == p1->pt.p_x && p2->pt.p_y == p1->pt.p_y ) { TxPrintf("Extract error: Duplicate tile position, ignoring\n"); continue; } } changed = 1; tmp_node = tran->tr_termnode[nsd]; tmp_pos = tran->tr_termpos[nsd]; tmp_len = tran->tr_termlen[nsd]; tran->tr_termnode[nsd] = tran->tr_termnode[nsd+1]; tran->tr_termpos[nsd] = tran->tr_termpos[nsd+1]; tran->tr_termlen[nsd] = tran->tr_termlen[nsd+1]; tran->tr_termnode[nsd+1] = tmp_node; tran->tr_termpos[nsd+1] = tmp_pos; tran->tr_termlen[nsd+1] = tmp_len; /* Need to SWAP the indices in the labRegion too. * These for loops within the bubblesort in here are kinda slow * but S,D attributes are not that common so it should not matter * that much -- Stefanos 5/96 */ for ( lp = ll ; lp ; lp = lp->ll_next ) if ( lp->ll_attr == nsd ) lp->ll_attr = LL_SORTATTR ; else if ( lp->ll_attr == nsd+1 ) lp->ll_attr = nsd ; for ( lp = ll ; lp ; lp = lp->ll_next ) if ( lp->ll_attr == LL_SORTATTR ) lp->ll_attr = nsd+1; } } while( changed ); } /* *---------------------------------------------------------------------- * * extComputeCapLW -- * * Determine effective length and width of a rectangular capacitor, * based on the boundary vectors stored in extSpecialBounds. This * routine should only be called for capacitors that have exactly * one terminal. * * Results: * None * * Side Effects: * Puts effective length and width into the pointers * passed as arguments. *---------------------------------------------------------------------- */ void extComputeCapLW(rlengthptr, rwidthptr) int *rlengthptr, *rwidthptr; { LinkedBoundary *lb; Rect bbox; /* Quick algorithm---ignore tabs, compute max extents of */ /* the special bounds vector. */ lb = extSpecialBounds[0]; if (lb == NULL) { TxError("extract: Can't get capacitor L and W\n"); return; /* error condition */ } bbox = lb->r; for (lb = extSpecialBounds[0]; lb != NULL; lb = lb->b_next) GeoIncludeAll(&lb->r, &bbox); *rwidthptr = bbox.r_xtop - bbox.r_xbot; *rlengthptr = bbox.r_ytop - bbox.r_ybot; } /* *---------------------------------------------------------------------- * * extComputeEffectiveLW -- * * Determine effective length and width of an annular (or otherwise * non-rectangular) transistor structure, based on the boundary vectors * stored in extSpecialBounds. * * Note that "L" and "W" are reversed when this routine is called * to compute L and W for a resistor. The sense of "length" and * "width" as used in the routine are appropriate for a transistor. * * Also note that this algorithm will tend to over-estimate the width * of transistors with angled bends. This problem would be eliminated * if non-Manhattan geometry were evaluated directly rather than being * first converted to Manhattan geometry. * * Results: * None. * * Side Effects: * Puts effective length and width into the pointers * passed as arguments. *---------------------------------------------------------------------- */ void extComputeEffectiveLW(rlengthptr, rwidthptr, numregions, chop) int *rlengthptr, *rwidthptr; int numregions; float chop; { int i, j, p, jmax; LinkedBoundary *lb, *lb2; int oppdir, length, loclength, testlen, width; int locwidth, testwid, cornerw; int segp, segn, segc, sege; bool isComplex = FALSE; /* First, check for MOScap-connected transistors. In such * cases, one or more extSpecialBounds[] is NULL. Try to * separate the existing extSpecialBounds[] vectors into * independent (non-connecting) vectors. */ /* For each segment in the primary list, find the closest * segment in the other list which lies on the opposite * side of the gate area. Calculate the length, and check * for overlap, treating the length as a corner extension. * * The primary list is chosen as the one with the largest * number of elements. This helps prevent the algorithm from * producing a different result for devices at different * orientations. */ p = 0; jmax = 0; for (i = 0; i < numregions; i++) { j = 0; for (lb = extSpecialBounds[i]; lb != NULL; lb = lb->b_next) j++; if (j > jmax) { jmax = j; p = i; } } /* fprintf(stderr, "Annular transistor detailed L,W computation:\n"); */ width = 0; length = 0; for (lb = extSpecialBounds[p]; lb != NULL; lb = lb->b_next) { loclength = INFINITY; switch (lb->dir) { case BD_LEFT: oppdir = BD_RIGHT; break; case BD_RIGHT: oppdir = BD_LEFT; break; case BD_TOP: oppdir = BD_BOTTOM; break; case BD_BOTTOM: oppdir = BD_TOP; break; } /* First pass: Find the distance of the closest segment within */ /* the range of its corner extension. We do two passes because */ /* there may be more than one segment at this distance. */ for (i = 0; i < numregions; i++) { if ((i == p) && (numregions > 1)) continue; for (lb2 = extSpecialBounds[i]; lb2 != NULL; lb2 = lb2->b_next) { if (lb2->dir == oppdir) { switch (lb->dir) { case BD_LEFT: if (lb2->r.r_xbot > lb->r.r_xbot) { testlen = lb2->r.r_xbot - lb->r.r_xbot; if (lb2->r.r_ybot < lb->r.r_ytop + testlen && lb2->r.r_ytop > lb->r.r_ybot - testlen) { /* Adjustments for offset segments */ if (lb2->r.r_ybot > lb->r.r_ytop) testlen += lb2->r.r_ybot - lb->r.r_ytop; else if (lb2->r.r_ytop < lb->r.r_ybot) testlen += lb->r.r_ybot - lb2->r.r_ytop; if (testlen < loclength) loclength = testlen; } } break; case BD_RIGHT: if (lb2->r.r_xtop < lb->r.r_xtop) { testlen = lb->r.r_xtop - lb2->r.r_xtop; if (lb2->r.r_ybot < lb->r.r_ytop + testlen && lb2->r.r_ytop > lb->r.r_ybot - testlen) { /* Adjustments for offset segments */ if (lb2->r.r_ybot > lb->r.r_ytop) testlen += lb2->r.r_ybot - lb->r.r_ytop; else if (lb2->r.r_ytop < lb->r.r_ybot) testlen += lb->r.r_ybot - lb2->r.r_ytop; if (testlen < loclength) loclength = testlen; } } break; case BD_TOP: if (lb2->r.r_ytop < lb->r.r_ytop) { testlen = lb->r.r_ytop - lb2->r.r_ytop; if (lb2->r.r_xbot < lb->r.r_xtop + testlen && lb2->r.r_xtop > lb->r.r_xbot - testlen) { /* Adjustments for offset segments */ if (lb2->r.r_xbot > lb->r.r_xtop) testlen += lb2->r.r_xbot - lb->r.r_xtop; else if (lb2->r.r_xtop < lb->r.r_xbot) testlen += lb->r.r_xbot - lb2->r.r_xtop; if (testlen < loclength) loclength = testlen; } } break; case BD_BOTTOM: if (lb2->r.r_ybot > lb->r.r_ybot) { testlen = lb2->r.r_ybot - lb->r.r_ybot; if (lb2->r.r_xbot < lb->r.r_xtop + testlen && lb2->r.r_xtop > lb->r.r_xbot - testlen) { /* Adjustments for offset segments */ if (lb2->r.r_xbot > lb->r.r_xtop) testlen += lb2->r.r_xbot - lb->r.r_xtop; else if (lb2->r.r_xtop < lb->r.r_xbot) testlen += lb->r.r_xbot - lb2->r.r_xtop; if (testlen < loclength) loclength = testlen; } } break; } } } } /* This segment should not be considered current-carrying; it */ /* only adds to the gate capacitance. Should we output the */ /* extra capacitance somewhere? */ if (loclength == INFINITY) continue; /* Note that the L/W calculation ignores the possibility that a */ /* transistor may have multiple lengths. Such cases should */ /* either 1) scale the width to one of the lengths, or 2) out- */ /* put a separate transistor record for each length. */ if (length == 0) length = loclength; /* Default length */ else if ((length != 0) && (length != loclength)) { /* If the newly computed length is less than the */ /* original, scale the original. Otherwise, scale */ /* the new length. */ if (loclength < length) { width *= loclength; width /= length; length = loclength; } isComplex = TRUE; } /* fprintf(stderr, " segment length = %d\n", loclength); */ /* Second pass: All segments at "length" distance add to the */ /* length and width calculation. Sides opposite and corner */ /* extensions are treated separately. Areas outside the corner */ /* extension are ignored. */ locwidth = 0; cornerw = 0; for (i = 0; i < numregions; i++) { if ((i == p) && (numregions > 1)) continue; for (lb2 = extSpecialBounds[i]; lb2 != NULL; lb2 = lb2->b_next) { if (lb2->dir == oppdir) { if (((lb->dir == BD_LEFT) && (lb2->r.r_xbot - lb->r.r_xbot == loclength)) || ((lb->dir == BD_RIGHT) && (lb->r.r_xtop - lb2->r.r_xtop == loclength))) { /* opposite */ segp = MIN(lb2->r.r_ytop, lb->r.r_ytop); segn = MAX(lb2->r.r_ybot, lb->r.r_ybot); testwid = segp - segn; if (testwid > 0) locwidth += testwid * 2; if (testwid <= -loclength) continue; /* corner extend top */ segc = MAX(lb2->r.r_ytop, lb->r.r_ytop); sege = MAX(segp, segn); testwid = segc - sege; if (testwid > loclength) testwid = loclength; if (testwid > 0) cornerw += testwid; /* corner extend bottom */ segc = MIN(lb2->r.r_ybot, lb->r.r_ybot); sege = MIN(segp, segn); testwid = sege - segc; if (testwid > loclength) testwid = loclength; if (testwid > 0) cornerw += testwid; } else if (((lb->dir == BD_TOP) && (lb->r.r_ytop - lb2->r.r_ytop == loclength)) || ((lb->dir == BD_BOTTOM) && (lb2->r.r_ybot - lb->r.r_ybot == loclength))) { /* opposite */ segp = MIN(lb2->r.r_xtop, lb->r.r_xtop); segn = MAX(lb2->r.r_xbot, lb->r.r_xbot); testwid = segp - segn; if (testwid > 0) locwidth += testwid * 2; if (testwid <= -loclength) continue; /* corner extend right */ segc = MAX(lb2->r.r_xtop, lb->r.r_xtop); sege = MAX(segp, segn); testwid = segc - sege; if (testwid > loclength) testwid = loclength; if (testwid > 0) cornerw += testwid; /* corner extend left */ segc = MIN(lb2->r.r_xbot, lb->r.r_xbot); sege = MIN(segp, segn); testwid = sege - segc; if (testwid > loclength) testwid = loclength; if (testwid > 0) cornerw += testwid; } } } } /* if (width > 0) fprintf(stderr, " segment width = %d\n", width); */ /* Width scaling for transistor sections with different lengths */ locwidth += (int)(0.5 + ((float)cornerw * chop)); if (loclength != length) { locwidth *= length; locwidth /= loclength; } width += locwidth; } if ((length > 0) && (width > 0)) { *rlengthptr = length; // If numregions == 1 then everything was put in one record, // and we have double-counted the width. if (numregions == 1) *rwidthptr = (width >> 2); else *rwidthptr = (width >> 1); /* fprintf(stderr, "total L = %d, W = %d\n", length, width); */ /* fflush(stderr); */ if (isComplex) TxError("Device has multiple lengths: scaling" " all widths to length %d\n", length); } } /* * ---------------------------------------------------------------------------- * * extSeparateBounds -- * * Because the non-source/drain perimeter is not a node, all the * boundary vectors end up in one record. So we have to pry them * apart. * * Results: * None. * * Side effects: * Messes with the extSpecialBounds[] linked lists. * * ---------------------------------------------------------------------------- */ void extSeparateBounds(nterm) int nterm; /* last terminal (# terminals - 1) */ { Rect lbrect; LinkedBoundary *lb, *lbstart, *lbend, *lblast, *lbnext; bool found; /* Avoid crash condition on a badly-defined extract definition */ if ((nterm < 0) || (extSpecialBounds[0] == NULL)) return; if (extSpecialBounds[nterm] == NULL) { /* Put first record into the unused terminal entry */ extSpecialBounds[nterm] = extSpecialBounds[0]; extSpecialBounds[0] = extSpecialBounds[nterm]->b_next; extSpecialBounds[nterm]->b_next = NULL; /* Add connected segments until no more are found */ lbstart = lbend = extSpecialBounds[nterm]; lbrect = lbstart->r; found = TRUE; while (found == TRUE) { lblast = NULL; found = FALSE; for (lb = extSpecialBounds[0]; lb != NULL; lb = lbnext) { /* perhaps we should cut down on these cases by */ /* checking the direction of the segment. . . */ lbnext = lb->b_next; if (((lb->r.r_xbot == lbrect.r_xbot) && (lb->r.r_ybot == lbrect.r_ybot))) { if (lblast == NULL) extSpecialBounds[0] = lb->b_next; else lblast->b_next = lb->b_next; // Insert lb after lbstart lb->b_next = lbstart->b_next; lbstart->b_next = lb; lbstart = lb; lbrect.r_xbot = lb->r.r_xtop; lbrect.r_ybot = lb->r.r_ytop; found = TRUE; } else if (((lb->r.r_xtop == lbrect.r_xbot) && (lb->r.r_ytop == lbrect.r_ybot))) { if (lblast == NULL) extSpecialBounds[0] = lb->b_next; else lblast->b_next = lb->b_next; lb->b_next = lbstart->b_next; lbstart->b_next = lb; lbstart = lb; lbrect.r_xbot = lb->r.r_xbot; lbrect.r_ybot = lb->r.r_ybot; found = TRUE; } else if (((lb->r.r_xtop == lbrect.r_xtop) && (lb->r.r_ytop == lbrect.r_ytop))) { if (lblast == NULL) extSpecialBounds[0] = lb->b_next; else lblast->b_next = lb->b_next; lb->b_next = lbend->b_next; lbend->b_next = lb; lbend = lb; lbrect.r_xtop = lb->r.r_xbot; lbrect.r_ytop = lb->r.r_ybot; found = TRUE; } else if (((lb->r.r_xbot == lbrect.r_xtop) && (lb->r.r_ybot == lbrect.r_ytop))) { if (lblast == NULL) extSpecialBounds[0] = lb->b_next; else lblast->b_next = lb->b_next; lb->b_next = lbend->b_next; lbend->b_next = lb; lbend = lb; lbrect.r_xtop = lb->r.r_xtop; lbrect.r_ytop = lb->r.r_ytop; found = TRUE; } else lblast = lb; } } } } /* * ---------------------------------------------------------------------------- * * extOutputParameters -- * * Scan through the TransRegion in the supplied list, and collect a mask of * all transistor types used in the layout. Then for each transistor type, * find if it belongs to a "subcircuit" (including "rsubcircuit" and * "msubcircuit") definition. If it does, output a record containing the * list of parameter names used by that subcircuit. * * Results: * None. * * Side effects: * Possibly writes to outFile. The purpose of this scan is not to have * to write out shared parameter information for every individual device. * ---------------------------------------------------------------------------- */ void extOutputParameters(def, transList, outFile) CellDef *def; /* Cell being extracted */ TransRegion *transList; /* Transistor regions built up in first pass */ FILE *outFile; /* Output file */ { ParamList *plist; TransRegion *reg; TileType t; TileTypeBitMask tmask; ExtDevice *devptr; TTMaskZero(&tmask); for (reg = transList; reg && !SigInterruptPending; reg = reg->treg_next) { TileType loctype = reg->treg_type; if (loctype == TT_SPACE) continue; /* This has been disabled */ /* Watch for rare split reg->treg_type */ if (loctype & TT_DIAGONAL) loctype = (reg->treg_type & TT_SIDE) ? ((reg->treg_type & TT_RIGHTMASK) >> 14) : (reg->treg_type & TT_LEFTMASK); TTMaskSetType(&tmask, loctype); } for (t = TT_TECHDEPBASE; t < DBNumTypes; t++) { if (TTMaskHasType(&tmask, t)) { /* Note: If there are multiple variants of a device type, they */ /* will all be listed even if they are not all present in the */ /* design. */ for (devptr = ExtCurStyle->exts_device[t]; devptr; devptr = devptr->exts_next) { /* Do not output parameters for ignored devices */ if (!strcmp(devptr->exts_deviceName, "Ignore")) continue; plist = devptr->exts_deviceParams; if (plist != (ParamList *)NULL) { fprintf(outFile, "parameters %s", devptr->exts_deviceName); for (; plist != NULL; plist = plist->pl_next) { if (plist->pl_param[1] != '\0') { if (plist->pl_scale != 1.0) fprintf(outFile, " %c%c=%s*%g", plist->pl_param[0], plist->pl_param[1], plist->pl_name, plist->pl_scale); else fprintf(outFile, " %c%c=%s", plist->pl_param[0], plist->pl_param[1], plist->pl_name); } else { if (plist->pl_scale != 1.0) fprintf(outFile, " %c=%s*%g", plist->pl_param[0], plist->pl_name, plist->pl_scale); else fprintf(outFile, " %c=%s", plist->pl_param[0], plist->pl_name); } } fprintf(outFile, "\n"); } } } } } /* * ---------------------------------------------------------------------------- * * extOutputDevParams --- * * Write information to the output in the form of parameters * representing pre-defined aspects of the device geometry * that may be specified for any device. * * Results: * None. * * Side effects: * Writes non-terminated output to the file 'outFile'. * * ---------------------------------------------------------------------------- */ void extOutputDevParams(reg, devptr, outFile, length, width) TransRegion *reg; ExtDevice *devptr; FILE *outFile; int length; int width; { ParamList *chkParam; for (chkParam = devptr->exts_deviceParams; chkParam != NULL; chkParam = chkParam->pl_next) { switch(tolower(chkParam->pl_param[0])) { case 'a': if (chkParam->pl_param[1] == '\0' || chkParam->pl_param[1] == '0') fprintf(outFile, " %c=%d", chkParam->pl_param[0], reg->treg_area); break; case 'p': if (chkParam->pl_param[1] == '\0' || chkParam->pl_param[1] == '0') fprintf(outFile, " %c=%d", chkParam->pl_param[0], extTransRec.tr_perim); break; case 'l': fprintf(outFile, " %c=%d", chkParam->pl_param[0], length); break; case 'w': fprintf(outFile, " %c=%d", chkParam->pl_param[0], width); break; case 'c': fprintf(outFile, " %c=%g", chkParam->pl_param[0], (extTransRec.tr_devrec->exts_deviceGateCap * reg->treg_area) + (extTransRec.tr_devrec->exts_deviceSDCap * extTransRec.tr_perim)); break; case 's': case 'x': case 'y': /* Do nothing; these values are standard output */ break; default: fprintf(outFile, " %c=", chkParam->pl_param[0]); break; } } } /* * ---------------------------------------------------------------------------- * * extOutputDevices -- * * For each TransRegion in the supplied list, corresponding to a single * transistor in the layout, compute and output: * - Its type * - Its area and perimeter OR length and width OR capacitance OR resistance * - Its substrate node * - For each of the gate, and the various diff terminals (eg, * source, drain): * Node to which the terminal connects * Length of the terminal * Attributes (comma-separated), or 0 if none. * * The tiles in 'def' don't point back to the TransRegions in this list, * but rather to the NodeRegions corresponding to their electrical nodes. * * Results: * None. * * Side effects: * Writes a number of 'device' records to the file 'outFile'. * * Interruptible. If SigInterruptPending is detected, we stop traversing * the transistor list and return. * * ---------------------------------------------------------------------------- */ void extOutputDevices(def, transList, outFile) CellDef *def; /* Cell being extracted */ TransRegion *transList; /* Transistor regions built up in first pass */ FILE *outFile; /* Output file */ { NodeRegion *node, *subsNode; TransRegion *reg; ExtDevice *devptr, *deventry; char *subsName; FindRegion arg; LabelList *ll; TileType t; int nsd, length, width, n, i, ntiles, corners, tn, rc, termcount; double dres, dcap; char mesg[256]; bool isAnnular, hasModel; for (reg = transList; reg && !SigInterruptPending; reg = reg->treg_next) { if (reg->treg_type == TT_SPACE) continue; /* This has been disabled */ /* * Visit all of the tiles in the transistor region, updating * extTransRec.tr_termnode[] and extTransRec.tr_termlen[], * and the attribute lists for this transistor. * * Algorithm: first visit all tiles in the transistor, marking * them with 'reg', then visit them again re-marking them with * the gate node (extGetRegion(reg->treg_tile)). */ extTransRec.tr_devrec = (ExtDevice *)NULL; extTransRec.tr_devmatch = 0; extTransRec.tr_nterm = 0; extTransRec.tr_gatelen = 0; extTransRec.tr_perim = 0; extTransRec.tr_subsnode = (NodeRegion *)NULL; arg.fra_def = def; arg.fra_connectsTo = ExtCurStyle->exts_deviceConn; extTransRec.tr_gatenode = (NodeRegion *) extGetRegion(reg->treg_tile); t = reg->treg_type; /* Watch for rare split reg->treg_type */ if (t & TT_DIAGONAL) t = (reg->treg_type & TT_SIDE) ? ((reg->treg_type & TT_RIGHTMASK) >> 14) : (reg->treg_type & TT_LEFTMASK); arg.fra_pNum = DBPlane(t); /* Set all terminals to NULL to guard against */ /* asymmetric devices missing a terminal. */ /* 5/30/09---but, reinitialize the array out to MAXSD, */ /* or devices declaring minterms < maxterms screw up! */ for (i = 0; i < MAXSD; i++) extTransRec.tr_termnode[i] = NULL; /* Mark with reg and process each perimeter segment */ arg.fra_uninit = (ClientData) extTransRec.tr_gatenode; arg.fra_region = (Region *) reg; arg.fra_each = extTransTileFunc; ntiles = ExtFindNeighbors(reg->treg_tile, arg.fra_pNum, &arg); /* Re-mark with extTransRec.tr_gatenode */ arg.fra_uninit = (ClientData) reg; arg.fra_region = (Region *) extTransRec.tr_gatenode; arg.fra_each = (int (*)()) NULL; (void) ExtFindNeighbors(reg->treg_tile, arg.fra_pNum, &arg); /* Are the terminal types on a compeletely different */ /* plane than the top type? If so, do an area search */ /* on that plane in the area under the device node. */ /* Devices may define one terminal per plane, but this */ /* method cannot handle several different layer types */ /* on one plane under the device identifier layer */ /* acting as separate device nodes. If any terminal */ /* search fails, give up and proceed with the reduced */ /* number of terminals. */ devptr = extTransRec.tr_devrec; if (devptr == NULL) continue; /* Bad device; do not output */ deventry = devptr; /* Use devmatch flags to determine if the specific S/D */ /* terminal was handled by extTransPerimFunc already. */ /* Only look at required S/D terminals that have not */ /* yet been found. */ while (TRUE) { if (devptr == NULL) break; /* Bad device */ nsd = devptr->exts_deviceSDCount; for (termcount = 0; termcount < nsd; termcount++) { TileTypeBitMask *tmask; if ((extTransRec.tr_devmatch & (MATCH_TERM << termcount)) != 0) continue; /* This terminal already found by perimeter search */ tmask = &devptr->exts_deviceSDTypes[termcount]; if (TTMaskIsZero(tmask)) { if (termcount < nsd) { ExtDevice *devcheck; /* Not finding another device record just means that */ /* terminals are tied together on the same net, such as */ /* with a MOS cap. Accept this fact and move on. */ } break; /* End of SD terminals */ } else if (!TTMaskIntersect(tmask, &DBPlaneTypes[reg->treg_pnum]) || (TTMaskHasType(tmask, TT_SPACE))) { node = NULL; /* First try to find a region under the device */ extTransFindSubs(reg->treg_tile, t, tmask, def, &node, NULL); if ((node == NULL) && (TTMaskHasType(tmask, TT_SPACE))) { /* Device node is the global substrate. */ node = glob_subsnode; } else if (node == NULL) { /* See if there is another matching device record */ /* with a different terminal type, and try again. */ devptr = extDevFindMatch(devptr, t); break; } extTransRec.tr_devmatch |= (MATCH_TERM << termcount); extTransRec.tr_termnode[termcount] = node; } else { /* Determine if there is another matching device record */ /* that has fewer required terminals. */ devptr = extDevFindMatch(devptr, t); break; } if (termcount == nsd) break; /* All terminals accounted for */ } if (termcount == nsd) break; /* All terminals accounted for */ if (devptr == deventry) break; /* No other device records available */ /* Try again with a different device record */ } extTransRec.tr_nterm = termcount; /* * For types that require a minimum number of terminals, * check to make sure that they all exist. If they don't, * issue a warning message and make believe the missing * terminals are the same as the last terminal we do have. */ if (extTransRec.tr_nterm < nsd) { int missing = nsd - extTransRec.tr_nterm; (void) sprintf(mesg, "device missing %d terminal%s", missing, missing == 1 ? "" : "s"); if (extTransRec.tr_nterm > 0) { node = extTransRec.tr_termnode[extTransRec.tr_nterm - 1]; (void) strcat(mesg, ";\n connecting remainder to node "); (void) strcat(mesg, extNodeName((LabRegion *) node)); while (extTransRec.tr_nterm < nsd) { extTransRec.tr_termlen[extTransRec.tr_nterm] = 0; extTransRec.tr_termnode[extTransRec.tr_nterm++] = node; } } if (ExtDoWarn & EXTWARN_FETS) extTransBad(def, reg->treg_tile, mesg); /* Devices with no terminals or a null node are badly */ /* formed and should not be output. This can happen when */ /* parts of devices are split into different cells. */ if ((extTransRec.tr_nterm == 0) || (node == NULL)) continue; } else if (extTransRec.tr_nterm > nsd) { /* It is not an error condition to have more terminals */ /* than the minimum. */ } if (devptr == NULL) { TxError("Warning: No matching extraction type for device at (%d %d)\n", reg->treg_tile->ti_ll.p_x, reg->treg_tile->ti_ll.p_y); continue; } /* * Output the transistor record. * The type is devptr->exts_deviceName, which should have * some meaning to the simulator we are producing this file for. * Use the default substrate node unless the transistor overlaps * material whose type is in exts_deviceSubstrateTypes, in which * case we use the node of the overlapped material. * * Technology files using the "substrate" keyword (magic-8.1 or * newer) should have the text "error" in the substrate node * name. */ subsName = devptr->exts_deviceSubstrateName; if (!TTMaskIsZero(&devptr->exts_deviceSubstrateTypes) && (subsNode = extTransRec.tr_subsnode)) { subsName = extNodeName(subsNode); } #ifdef MAGIC_WRAPPER // Substrate variable substitution when in backwards-compatibility // substrate mode. else if ((ExtCurStyle->exts_globSubstratePlane == -1) && (subsName && subsName[0] == '$' && subsName[1] != '$')) { // If subsName is a Tcl variable (begins with "$"), make the // variable substitution, if one exists. Ignore double-$. char *varsub = (char *)Tcl_GetVar(magicinterp, &subsName[1], TCL_GLOBAL_ONLY); if (varsub != NULL) subsName = varsub; } #endif extTransRec.tr_devrec = devptr; /* Model type "Ignore" in the techfile indicates a device */ /* to be ignored (i.e., a specific combination of layers */ /* does not form an extractable device, or overlaps another */ /* device type that should take precedence). */ if (!strcmp(devptr->exts_deviceName, "Ignore")) continue; /* Original-style FET record backward compatibility */ if (devptr->exts_deviceClass != DEV_FET) fprintf(outFile, "device "); fprintf(outFile, "%s %s", extDevTable[devptr->exts_deviceClass], devptr->exts_deviceName); fprintf(outFile, " %d %d %d %d", reg->treg_ll.p_x, reg->treg_ll.p_y, reg->treg_ll.p_x + 1, reg->treg_ll.p_y + 1); /* NOTE: The following code makes unreasonable simplifying */ /* assumptions about how to calculate device length and width. */ /* However, it is the same as was always used by ext2sim and */ /* ext2spice. By putting it here, where all the tile */ /* information exists, it is at least theoretically possible to */ /* write better routines that can deal with bends in resistors */ /* and transistors, annular devices, multiple-drain devices, */ /* etc., etc. */ /* Tim, 2/20/03 */ switch (devptr->exts_deviceClass) { case DEV_FET: /* old style, perimeter & area */ fprintf(outFile, " %d %d \"%s\"", reg->treg_area, extTransRec.tr_perim, (subsName == NULL) ? "None" : subsName); break; /* "device " types, calculation of length & width */ case DEV_MOSFET: case DEV_BJT: case DEV_SUBCKT: case DEV_MSUBCKT: case DEV_ASYMMETRIC: length = extTransRec.tr_gatelen / 2; /* (default) */ width = 0; isAnnular = FALSE; /* Note that width is accumulated on one tr_termlen */ /* record when nodes are merged, so proper behavior */ /* for transistors w/connected S-D is to count over */ /* non-NULL termnodes, not non-zero termlens. */ for (n = 0; n < extTransRec.tr_nterm; n++) { if (extTransRec.tr_termnode[n] == NULL) continue; width += extTransRec.tr_termlen[n]; /* Mark annular transistors as requiring extra processing */ if (extTransRec.tr_termvector[n].p_x == 0 && extTransRec.tr_termvector[n].p_y == 0) isAnnular = TRUE; } if (n) width /= n; /*------------------------------------------------------*/ /* Note that the tr_termvector says a lot about the */ /* device geometry. If the sum of x and y for any */ /* vector is 0, then the terminal is enclosed (annular */ /* device). If the sum of x and y for all vectors is */ /* zero, then we have a normal rectangular device. But */ /* if the sum of all x and y is nonzero, then the */ /* device length changes along the device (including */ /* bends). This is a trigger to do a more extensive */ /* boundary search to find the exact dimensions of the */ /* device. */ /*------------------------------------------------------*/ if (n == 0) { /* Don't issue a warning on devices such as a */ /* vertical diode that may declare zero terminals */ /* because the default substrate node is the other */ /* terminal. */ if (ExtDoWarn && (devptr->exts_deviceSDCount > 0)) extTransBad(def, reg->treg_tile, "Could not determine device boundary"); length = width = 0; } else { LinkedBoundary *lb; extSpecialBounds = (LinkedBoundary **)mallocMagic(n * sizeof(LinkedBoundary *)); for (i = 0; i < n; i++) extSpecialBounds[i] = NULL; /* Mark with reg and process each perimeter segment */ arg.fra_uninit = (ClientData) extTransRec.tr_gatenode; arg.fra_region = (Region *) reg; arg.fra_each = extAnnularTileFunc; (void) ExtFindNeighbors(reg->treg_tile, arg.fra_pNum, &arg); extSeparateBounds(n - 1); /* Handle MOScaps (if necessary) */ extComputeEffectiveLW(&length, &width, n, ExtCurStyle->exts_cornerChop[t]); /* Free the lists */ for (i = 0; i < n; i++) for (lb = extSpecialBounds[i]; lb != NULL; lb = lb->b_next) freeMagic((char *)lb); freeMagic((char *)extSpecialBounds); /* Put the region list back the way we found it: */ /* Re-mark with extTransRec.tr_gatenode */ arg.fra_uninit = (ClientData) reg; arg.fra_region = (Region *) extTransRec.tr_gatenode; arg.fra_each = (int (*)()) NULL; (void) ExtFindNeighbors(reg->treg_tile, arg.fra_pNum, &arg); } if (devptr->exts_deviceClass == DEV_MOSFET || devptr->exts_deviceClass == DEV_ASYMMETRIC || devptr->exts_deviceClass == DEV_BJT) { fprintf(outFile, " %d %d", length, width); } extOutputDevParams(reg, devptr, outFile, length, width); fprintf(outFile, " \"%s\"", (subsName == NULL) ? "None" : subsName); break; case DEV_DIODE: /* Only handle the optional substrate node */ case DEV_NDIODE: case DEV_PDIODE: extOutputDevParams(reg, devptr, outFile, length, width); if (subsName != NULL) fprintf(outFile, " \"%s\"", subsName); break; case DEV_RES: case DEV_RSUBCKT: hasModel = strcmp(devptr->exts_deviceName, "None"); length = extTransRec.tr_perim; isAnnular = FALSE; /* Boundary perimeter scan for resistors with more than */ /* one tile. */ for (n = 0; n < extTransRec.tr_nterm; n++) { if (extTransRec.tr_termnode[n] == NULL) continue; /* Mark annular resistors as requiring extra processing */ if (extTransRec.tr_termvector[n].p_x == 0 && extTransRec.tr_termvector[n].p_y == 0) isAnnular = TRUE; } if (n == 0) width = length = 0; else if (ntiles > 1) { LinkedBoundary *lb; extSpecialBounds = (LinkedBoundary **)mallocMagic(n * sizeof(LinkedBoundary *)); for (i = 0; i < n; i++) extSpecialBounds[i] = NULL; /* Mark with reg and process each perimeter segment */ arg.fra_uninit = (ClientData) extTransRec.tr_gatenode; arg.fra_region = (Region *) reg; if (isAnnular) arg.fra_each = extAnnularTileFunc; else arg.fra_each = extResistorTileFunc; (void) ExtFindNeighbors(reg->treg_tile, arg.fra_pNum, &arg); if (extSpecialBounds[0] != NULL) { extSeparateBounds(n - 1); if (isAnnular) extComputeEffectiveLW(&length, &width, n, ExtCurStyle->exts_cornerChop[t]); else extComputeEffectiveLW(&width, &length, n, ExtCurStyle->exts_cornerChop[t]); } else { if (ExtDoWarn) extTransBad(def, reg->treg_tile, "Could not determine resistor boundary"); length = width = 0; } /* Free the lists */ for (i = 0; i < n; i++) for (lb = extSpecialBounds[i]; lb != NULL; lb = lb->b_next) freeMagic((char *)lb); freeMagic((char *)extSpecialBounds); /* Put the region list back the way we found it: */ /* Re-mark with extTransRec.tr_gatenode */ arg.fra_uninit = (ClientData) reg; arg.fra_region = (Region *) extTransRec.tr_gatenode; arg.fra_each = (int (*)()) NULL; (void) ExtFindNeighbors(reg->treg_tile, arg.fra_pNum, &arg); } else { /* Single tile resistor means a simple L,W */ /* calculation from perimeter & area. */ width = 0; for (n = 0; extTransRec.tr_termlen[n] != 0; n++) { width += extTransRec.tr_termlen[n]; length -= extTransRec.tr_termlen[n]; } width >>= 1; length >>= 1; } if (width) { dres = ExtCurStyle->exts_sheetResist[t] * (double)length / (double)width; if (ExtDoWarn && (n > 2)) { if (hasModel) sprintf(mesg, "Resistor has %d terminals: " "extracted L/W will be wrong", n); else sprintf(mesg, "Resistor has %d terminals: " "extracted value will be wrong", n); extTransBad(def, reg->treg_tile, mesg); } } else { dres = 0.0; if (ExtDoWarn) extTransBad(def, reg->treg_tile, "Resistor has zero width"); } if (devptr->exts_deviceClass == DEV_RSUBCKT) { /* (Nothing) */ } else if (hasModel) /* SPICE semiconductor resistor */ { fprintf(outFile, " %d %d", length, width); if (subsName != NULL) fprintf(outFile, " \"%s\"", subsName); } else /* regular resistor */ fprintf(outFile, " %g", dres / 1000.0); /* mOhms -> Ohms */ extOutputDevParams(reg, devptr, outFile, length, width); if (devptr->exts_deviceClass == DEV_RSUBCKT) { fprintf(outFile, " \"%s\"", (subsName == NULL) ? "None" : subsName); } break; case DEV_CAP: case DEV_CAPREV: case DEV_CSUBCKT: hasModel = strcmp(devptr->exts_deviceName, "None"); if (hasModel) { for (n = 0; n < extTransRec.tr_nterm && extTransRec.tr_termnode[n] != NULL; n++); /* Don't know what to do (yet) with capacitors */ /* multiple terminals (see below); treat them in */ /* the original naive manner. */ if (n == 0) { width = 0; extTransBad(def, reg->treg_tile, "Capacitor has zero size"); fprintf(outFile, " 0 0"); } else { /* Special handling of multiple-tile areas. */ /* This algorithm assumes that the capacitor */ /* has one terminal and that the area is a */ /* rectangle. It should be extended to output */ /* multiple capacitors for multiple rectangular */ /* areas, combining to form any arbitrary shape */ LinkedBoundary *lb; extSpecialBounds = (LinkedBoundary **)mallocMagic(n * sizeof(LinkedBoundary *)); for (i = 0; i < n; i++) extSpecialBounds[i] = NULL; /* Mark with reg and process each perimeter segment */ arg.fra_uninit = (ClientData) extTransRec.tr_gatenode; arg.fra_region = (Region *) reg; arg.fra_each = extAnnularTileFunc; (void) ExtFindNeighbors(reg->treg_tile, arg.fra_pNum, &arg); extComputeCapLW(&length, &width); /* Free the lists */ for (i = 0; i < n; i++) for (lb = extSpecialBounds[i]; lb != NULL; lb = lb->b_next) freeMagic((char *)lb); freeMagic((char *)extSpecialBounds); /* Put the region list back the way we found it: */ /* Re-mark with extTransRec.tr_gatenode */ arg.fra_uninit = (ClientData) reg; arg.fra_region = (Region *) extTransRec.tr_gatenode; arg.fra_each = (int (*)()) NULL; (void) ExtFindNeighbors(reg->treg_tile, arg.fra_pNum, &arg); } if (devptr->exts_deviceClass == DEV_CSUBCKT) { /* (Nothing) */ } else /* SPICE semiconductor resistor */ { if ((length * width) > reg->treg_area) { if (ExtDoWarn) extTransBad(def, reg->treg_tile, "L,W estimated " "for non-rectangular capacitor."); fprintf(outFile, " %d %d", width, reg->treg_area / width); } else fprintf(outFile, " %d %d", length, width); if (subsName != NULL) fprintf(outFile, " \"%s\"", subsName); } extOutputDevParams(reg, devptr, outFile, length, width); if (devptr->exts_deviceClass == DEV_CSUBCKT) { fprintf(outFile, " \"%s\"", (subsName == NULL) ? "None" : subsName); } } else { dcap = (devptr->exts_deviceGateCap * reg->treg_area) + (devptr->exts_deviceSDCap * extTransRec.tr_perim); fprintf(outFile, " %g", dcap / 1000.0); /* aF -> fF */ } break; } /* gate */ node = (NodeRegion *) extGetRegion(reg->treg_tile); ll = node->nreg_labels; extTransOutTerminal((LabRegion *) node, ll, LL_GATEATTR, extTransRec.tr_gatelen, outFile); /* Sort source and drain terminals by position, unless the */ /* device is asymmetric, in which case source and drain do not */ /* permute, and the terminal order is fixed. */ if (TTMaskIsZero(&devptr->exts_deviceSDTypes[1])) ExtSortTerminals(&extTransRec, ll); /* each non-gate terminal */ for (nsd = 0; nsd < extTransRec.tr_nterm; nsd++) extTransOutTerminal((LabRegion *) extTransRec.tr_termnode[nsd], ll, nsd, extTransRec.tr_termlen[nsd], outFile); (void) fputs("\n", outFile); } } /* Structure to hold a node region and a tile type */ typedef struct _node_type { NodeRegion *region; TileType layer; } NodeAndType; int extTransFindSubs(tile, t, mask, def, sn, layerptr) Tile *tile; TileType t; TileTypeBitMask *mask; CellDef *def; NodeRegion **sn; TileType *layerptr; { Rect tileArea, tileAreaPlus; int pNum; int extTransFindSubsFunc1(); /* Forward declaration */ NodeAndType noderec; noderec.region = (NodeRegion *)NULL; noderec.layer = TT_SPACE; TiToRect(tile, &tileArea); /* Expand tile area by 1 in all directions. This catches terminals */ /* on certain extended drain MOSFET devices. */ GEO_EXPAND(&tileArea, 1, &tileAreaPlus); for (pNum = PL_TECHDEPBASE; pNum < DBNumPlanes; pNum++) { if (TTMaskIntersect(&DBPlaneTypes[pNum], mask)) { if (DBSrPaintArea((Tile *) NULL, def->cd_planes[pNum], &tileAreaPlus, mask, extTransFindSubsFunc1, (ClientData)&noderec)) { *sn = noderec.region; if (layerptr) *layerptr = noderec.layer; return 1; } } } return 0; } int extTransFindSubsFunc1(tile, noderecptr) Tile *tile; NodeAndType *noderecptr; { TileType type; /* Report split substrate region errors (two different substrate * regions under the same device) */ if (tile->ti_client != (ClientData) extUnInit) { if ((noderecptr->region != (NodeRegion *)NULL) && (noderecptr->region != tile->ti_client)) TxError("Warning: Split substrate under device at (%d %d)\n", tile->ti_ll.p_x, tile->ti_ll.p_y); if (IsSplit(tile)) type = (SplitSide(tile)) ? SplitRightType(tile): SplitLeftType(tile); else type = TiGetTypeExact(tile); noderecptr->region = (NodeRegion *)tile->ti_client; noderecptr->layer = type; return 1; } return 0; } int extTransFindId(tile, mask, def, idtypeptr) Tile *tile; TileTypeBitMask *mask; CellDef *def; TileType *idtypeptr; { TileType type; Rect tileArea; int pNum; int extTransFindIdFunc1(); /* Forward declaration */ TiToRect(tile, &tileArea); for (pNum = PL_TECHDEPBASE; pNum < DBNumPlanes; pNum++) { if (TTMaskIntersect(&DBPlaneTypes[pNum], mask)) { if (DBSrPaintArea((Tile *) NULL, def->cd_planes[pNum], &tileArea, mask, extTransFindIdFunc1, (ClientData)idtypeptr)) return 1; } } return 0; } int extTransFindIdFunc1(tile, idtypeptr) Tile *tile; TileType *idtypeptr; { /* * ID Layer found overlapping device area, so return 1 to halt search. */ if (IsSplit(tile)) *idtypeptr = (SplitSide(tile)) ? SplitRightType(tile): SplitLeftType(tile); else *idtypeptr = TiGetTypeExact(tile); return 1; } /* * ---------------------------------------------------------------------------- * * extDevFindMatch -- * * Find and return the next matching device record. extTransRec is queried * to find how many fields have been matched to the device already. Then * it finds the next record with the same matching fields. Because records * are in no specific order, it will loop to the beginning of the records * after reaching the end. It is the responsibility of the calling routine to * determine if all matching devices have been tested. * * deventry is the record position to start the search. * t is the tile type of the device. * ---------------------------------------------------------------------------- */ ExtDevice * extDevFindMatch(deventry, t) ExtDevice *deventry; TileType t; { ExtDevice *devptr; int i, j, k, matchflags; bool match; matchflags = extTransRec.tr_devmatch; if (deventry->exts_next == NULL) devptr = ExtCurStyle->exts_device[t]; else devptr = deventry->exts_next; for (; devptr != deventry; devptr = (devptr->exts_next) ? devptr->exts_next : ExtCurStyle->exts_device[t]) { if (matchflags == 0) break; /* Always return next entry */ if (matchflags & MATCH_ID) /* Must have the same identifier */ if (!TTMaskEqual(&devptr->exts_deviceIdentifierTypes, &deventry->exts_deviceIdentifierTypes)) continue; if (matchflags & MATCH_SUB) /* Must have the same substrate type */ if (!TTMaskEqual(&devptr->exts_deviceSubstrateTypes, &deventry->exts_deviceSubstrateTypes)) continue; j = MATCH_TERM; i = 0; match = True; for (k = 0; k < devptr->exts_deviceSDCount; k++) { if (extTransRec.tr_termnode[k] == NULL) break; if (matchflags & j) /* Must have the same terminal type */ { if (TTMaskIsZero(&devptr->exts_deviceSDTypes[i])) { match = False; break; } if (!TTMaskEqual(&devptr->exts_deviceSDTypes[i], &deventry->exts_deviceSDTypes[i])) { match = False; break; } } j <<= 1; /* NOTE: There are fewer exts_deviceSDTypes records than */ /* terminals if all S/D terminals are the same type. In */ /* that case k increments and j bit shifts but i remains */ /* the same. */ if (!TTMaskIsZero(&devptr->exts_deviceSDTypes[i + 1])) i++; } if (match) break; } return (devptr == deventry) ? NULL : devptr; } /* * ---------------------------------------------------------------------------- * * extTransTileFunc -- * * Filter function called by ExtFindNeighbors for each tile in a * transistor. Responsible for collecting the nodes, lengths, * and attributes of all the terminals on this transistor. * * Results: * Returns 0 always. * * Side effects: * Fills in the transRec structure extTransRec. * * ---------------------------------------------------------------------------- */ int extTransTileFunc(tile, pNum, arg) Tile *tile; int pNum; FindRegion *arg; { TileTypeBitMask mask, cmask, *smask; TileType loctype, idlayer, sublayer; int perim, result; bool allow_globsubsnode; ExtDevice *devptr, *deventry, *devtest; NodeRegion *region; LabelList *ll; Label *lab; Rect r; for (ll = extTransRec.tr_gatenode->nreg_labels; ll; ll = ll->ll_next) { /* Skip if already marked */ if (ll->ll_attr != LL_NOATTR) continue; lab = ll->ll_label; TITORECT(tile, &r); if (GEO_TOUCH(&r, &lab->lab_rect) && extLabType(lab->lab_text, LABTYPE_GATEATTR)) { ll->ll_attr = LL_GATEATTR; } } /* * Visit each segment of the perimeter of this tile that * that borders on something of a different type. */ if (IsSplit(tile)) { loctype = (SplitSide(tile)) ? SplitRightType(tile): SplitLeftType(tile); // return (0); /* Hack alert! We must properly handle diagonals! */ } else loctype = TiGetTypeExact(tile); mask = ExtCurStyle->exts_deviceConn[loctype]; TTMaskCom(&mask); /* NOTE: DO NOT USE extTransRec.tr_perim += extEnumTilePerim(...) */ /* The AMD target gcc compile works and the Intel target gcc */ /* compile doesn't! The following code works the same on both. */ perim = extEnumTilePerim(tile, mask, pNum, extTransPerimFunc, (ClientData)NULL); extTransRec.tr_perim += perim; devptr = extTransRec.tr_devrec; if (devptr == NULL) return 0; /* No matching devices, so forget it. */ allow_globsubsnode = FALSE; /* Create a mask of all substrate types of all device records, and */ /* search for substrate types on this combined mask. */ TTMaskZero(&cmask); for (devtest = ExtCurStyle->exts_device[loctype]; devtest; devtest = devtest->exts_next) TTMaskSetMask(&cmask, &devtest->exts_deviceSubstrateTypes); if (!TTMaskIsZero(&cmask)) { if (TTMaskHasType(&cmask, TT_SPACE)) { allow_globsubsnode = TRUE; TTMaskClearType(&cmask, TT_SPACE); } if (extTransRec.tr_subsnode == (NodeRegion *)NULL) { sublayer = TT_SPACE; region = NULL; extTransFindSubs(tile, loctype, &cmask, arg->fra_def, ®ion, &sublayer); /* If the device does not connect to a defined node, and * the substrate types include "space", then it is assumed to * connect to the global substrate. */ if (region == (NodeRegion *)NULL) if (allow_globsubsnode) region = glob_subsnode; extTransRec.tr_subsnode = region; if ((region != (NodeRegion *)NULL) && !(TTMaskHasType(&devptr->exts_deviceSubstrateTypes, sublayer))) { /* A substrate layer was found but is not compatible with the */ /* current device. Find a device record with the substrate */ /* layer that was found, and set the substrate match flag. */ deventry = devptr; while (devptr != NULL) { devptr = extDevFindMatch(devptr, loctype); if ((devptr == NULL) || (devptr == deventry)) { TxError("No matching device for %s with substrate layer %s\n", DBTypeLongNameTbl[loctype], DBTypeLongNameTbl[sublayer]); devptr = NULL; break; } if (TTMaskHasType(&devptr->exts_deviceSubstrateTypes, sublayer)) { extTransRec.tr_devmatch |= MATCH_SUB; break; } } } else if (region == (NodeRegion *)NULL) { /* If ExtCurStyle->exts_globSubstrateTypes contains no types */ /* then this is an older style techfile without a "substrate" */ /* definition in the extract section. In that case, it is */ /* expected that the substrate name in the device line will be */ /* used. */ if (!TTMaskIsZero(&ExtCurStyle->exts_globSubstrateTypes) || (devptr->exts_deviceSubstrateName == NULL)) { TxError("Device %s does not have a compatible substrate node!\n", DBTypeLongNameTbl[loctype]); devptr = NULL; } } } extTransRec.tr_devrec = devptr; if (devptr == NULL) return 0; /* No matching devices, so forget it. */ } /* If at least one device type declares an ID layer, then make a */ /* mask of all device ID types, and search on the area of the */ /* device to see if any device identifier layers are found. */ TTMaskZero(&cmask); for (devtest = ExtCurStyle->exts_device[loctype]; devtest; devtest = devtest->exts_next) TTMaskSetMask(&cmask, &devtest->exts_deviceIdentifierTypes); if (!TTMaskIsZero(&cmask)) { idlayer = TT_SPACE; extTransFindId(tile, &cmask, arg->fra_def, &idlayer); if ((idlayer == TT_SPACE) && !TTMaskIsZero(&devptr->exts_deviceIdentifierTypes)) { /* Device expected an ID layer but none was present. Find a device */ /* record with no ID layer, and set the ID match flag. */ deventry = devptr; while (devptr != NULL) { devptr = extDevFindMatch(devptr, loctype); if ((devptr == NULL) || (devptr == deventry)) { TxError("No matching device for %s with no ID layer\n", DBTypeLongNameTbl[loctype]); devptr = NULL; break; } if (TTMaskIsZero(&devptr->exts_deviceIdentifierTypes)) { extTransRec.tr_devmatch |= MATCH_ID; break; } } } else if ((idlayer != TT_SPACE) && !TTMaskHasType(&devptr->exts_deviceIdentifierTypes, idlayer)) { /* Device expected no ID layer but one was present. Find a device */ /* record with the ID layer and set the ID match flag. If there is */ /* a valid device without the ID layer, then ignore the ID layer */ /* and flag a warning. */ deventry = devptr; while (devptr != NULL) { devptr = extDevFindMatch(devptr, loctype); if ((devptr == NULL) || (devptr == deventry)) { TxError("ID layer %s on non-matching device %s was ignored.\n", DBTypeLongNameTbl[idlayer], DBTypeLongNameTbl[loctype]); devptr = deventry; break; } if (TTMaskHasType(&devptr->exts_deviceIdentifierTypes, idlayer)) { extTransRec.tr_devmatch |= MATCH_ID; break; } } } else extTransRec.tr_devmatch |= MATCH_ID; } extTransRec.tr_devrec = devptr; return 0; } int extTransPerimFunc(bp) Boundary *bp; { TileType tinside, toutside; Tile *tile; NodeRegion *diffNode = (NodeRegion *) extGetRegion(bp->b_outside); ExtDevice *devptr, *deventry; int i, len = BoundaryLength(bp); int thisterm; LabelList *ll; Label *lab; Rect r; bool SDterm = FALSE; tile = bp->b_inside; if (IsSplit(tile)) tinside = (SplitSide(tile)) ? SplitRightType(tile): SplitLeftType(tile); else tinside = TiGetTypeExact(bp->b_inside); tile = bp->b_outside; if (IsSplit(tile)) toutside = (SplitSide(tile)) ? SplitRightType(tile): SplitLeftType(tile); else toutside = TiGetTypeExact(bp->b_outside); if (extTransRec.tr_devrec != NULL) devptr = extTransRec.tr_devrec; else devptr = ExtCurStyle->exts_device[tinside]; deventry = devptr; while(devptr) { extTransRec.tr_devrec = devptr; for (i = 0; !TTMaskIsZero(&devptr->exts_deviceSDTypes[i]); i++) { /* TT_SPACE is allowed, for declaring that a device terminal is */ /* the substrate. However, it should not be in the plane of */ /* the device identifier layer, so space tiles should never be */ /* flagged during a device perimeter search. */ if (toutside == TT_SPACE) break; if (TTMaskHasType(&devptr->exts_deviceSDTypes[i], toutside)) { /* * It's a diffusion terminal (source or drain). See if the node is * already in our table; add it if it wasn't already there. * Asymmetric devices must have terminals in order. */ if (TTMaskIsZero(&devptr->exts_deviceSDTypes[1])) { for (thisterm = 0; thisterm < extTransRec.tr_nterm; thisterm++) if (extTransRec.tr_termnode[thisterm] == diffNode) break; } else thisterm = i; if (extTransRec.tr_termnode[thisterm] == NULL) { extTransRec.tr_nterm++; extTransRec.tr_termnode[thisterm] = diffNode; extTransRec.tr_termlen[thisterm] = 0; extTransRec.tr_termvector[thisterm].p_x = 0; extTransRec.tr_termvector[thisterm].p_y = 0; extTransRec.tr_termpos[thisterm].pnum = DBPlane(toutside); extTransRec.tr_termpos[thisterm].pt = bp->b_outside->ti_ll; /* Find the total area of this terminal */ } else if (extTransRec.tr_termnode[thisterm] == diffNode) { TermTilePos *pos = &(extTransRec.tr_termpos[thisterm]); Tile *otile = bp->b_outside; /* update the region tile position */ if( DBPlane(TiGetType(otile)) < pos->pnum ) { pos->pnum = DBPlane(TiGetType(otile)); pos->pt = otile->ti_ll; } else if( DBPlane(TiGetType(otile)) == pos->pnum ) { if( LEFT(otile) < pos->pt.p_x ) pos->pt = otile->ti_ll; else if( LEFT(otile) == pos->pt.p_x && BOTTOM(otile) < pos->pt.p_y ) pos->pt.p_y = BOTTOM(otile); } } else { TxError("Error: Asymmetric device with multiple terminals!\n"); break; } /* Add the length to this terminal's perimeter */ extTransRec.tr_termlen[thisterm] += len; /* Update the boundary traversal vector */ switch(bp->b_direction) { case BD_LEFT: extTransRec.tr_termvector[thisterm].p_y += len; break; case BD_TOP: extTransRec.tr_termvector[thisterm].p_x += len; break; case BD_RIGHT: extTransRec.tr_termvector[thisterm].p_y -= len; break; case BD_BOTTOM: extTransRec.tr_termvector[thisterm].p_x -= len; break; } /* * Mark this attribute as belonging to this transistor * if it is either: * (1) a terminal attribute whose LL corner touches bp->b_segment, * or (2) a gate attribute that lies inside bp->b_inside. */ for (ll = extTransRec.tr_gatenode->nreg_labels; ll; ll = ll->ll_next) { /* Skip if already marked */ if (ll->ll_attr != LL_NOATTR) continue; lab = ll->ll_label; if (GEO_ENCLOSE(&lab->lab_rect.r_ll, &bp->b_segment) && extLabType(lab->lab_text, LABTYPE_TERMATTR)) { ll->ll_attr = thisterm; } } /* Check if number of terminals exceeds the number allowed in */ /* this device record. If so, check if there is another device */ /* record with a different number of terminals. */ extTransRec.tr_devmatch |= (MATCH_TERM << thisterm); if (thisterm >= devptr->exts_deviceSDCount) { devptr = extDevFindMatch(devptr, tinside); /* Should this be an error instead of a warning? */ /* Traditionally more terminals than defined was allowed */ /* but not necessarily handled correctly by ext2spice. */ if (devptr == deventry) TxError("Warning: Device has more terminals than defined " "for type!\n"); else extTransRec.tr_devrec = devptr; } SDterm = TRUE; break; } } if (toutside == TT_SPACE) break; if (SDterm) break; if (extConnectsTo(tinside, toutside, ExtCurStyle->exts_nodeConn)) { /* Not in a terminal, but are in something that connects to gate */ extTransRec.tr_gatelen += len; break; } /* Did not find a matching terminal, so see if a different extraction */ /* record matches the terminal type. */ devptr = extDevFindMatch(devptr, tinside); if (devptr == deventry) devptr = NULL; } if (devptr == NULL) { /* Outside type is not a terminal, so return to the original */ /* device record. NOTE: Should probably check if this device */ /* type is a FET, as being here would indicate an error. */ /* However, failure to find all terminals will be flagged as an */ /* error elsewhere. */ extTransRec.tr_devrec = deventry; } /* * Total perimeter (separate from terminals, for dcaps * that might not be surrounded by terminals on all sides). */ /* Don't double-count contact perimeters (added by Tim 1/9/07) */ if ((!DBIsContact(toutside) && !DBIsContact(tinside)) || (bp->b_plane == extTransRec.tr_gatenode->nreg_pnum)) extTransRec.tr_perim += len; return (0); } /* * ---------------------------------------------------------------------------- * * extAnnularTileFunc -- * * Filter function called by ExtFindNeighbors for each tile in a * transistor. Responsible for doing an extensive boundary * survey to determine the length of the transistor. * This is basically a subset of the code in extTransTileFunc() * but passes a different function to extEnumTilePerim(). * * Results: * Returns 0 always. * * Side effects: * * ---------------------------------------------------------------------------- */ int extAnnularTileFunc(tile, pNum) Tile *tile; int pNum; { TileTypeBitMask mask; TileType loctype; /* * Visit each segment of the perimeter of this tile that * that borders on something of a different type. */ if (IsSplit(tile)) { loctype = (SplitSide(tile)) ? SplitRightType(tile): SplitLeftType(tile); } else loctype = TiGetTypeExact(tile); mask = ExtCurStyle->exts_deviceConn[loctype]; TTMaskCom(&mask); extEnumTilePerim(tile, mask, pNum, extSpecialPerimFunc, (ClientData) TRUE); return (0); } /* * ---------------------------------------------------------------------------- * * extResistorTileFunc -- * * Filter function called by ExtFindNeighbors for each tile in a * resistor. This is very similar to the extAnnularTileFunc * above, but it looks a boundaries with non-source/drain types * rather than the source/drain boundaries themselves. This is * correct for tracing the detailed perimeter of a device where * L > W. * * Ideally, one wants to call both of these functions to check * both the case of L > W and the case W > L, assuming that both * are legal resistor layouts. * * Results: * Returns 0 always. * * Side effects: * * ---------------------------------------------------------------------------- */ int extResistorTileFunc(tile, pNum) Tile *tile; int pNum; { TileTypeBitMask mask; TileType loctype; ExtDevice *devptr; /* * Visit each segment of the perimeter of this tile that * that borders on something of a different type. */ if (IsSplit(tile)) { loctype = (SplitSide(tile)) ? SplitRightType(tile): SplitLeftType(tile); } else loctype = TiGetTypeExact(tile); mask = ExtCurStyle->exts_deviceConn[loctype]; devptr = extTransRec.tr_devrec; if (devptr == NULL) devptr = ExtCurStyle->exts_device[loctype]; while (devptr) { TTMaskSetMask(&mask, &devptr->exts_deviceSDTypes[0]); TTMaskCom(&mask); extEnumTilePerim(tile, mask, pNum, extSpecialPerimFunc, (ClientData)FALSE); if (extSpecialBounds[0] != NULL) break; devptr = devptr->exts_next; } if (devptr != NULL) extTransRec.tr_devrec = devptr; return (0); } /*----------------------------------------------------------------------*/ /* Detailed boundary survey for unusual transistor geometries (esp. */ /* annular). If "sense" is TRUE, look at boundaries with source/drain */ /* types. If "sense" is FALSE, looks at non-source/drain boundaries. */ /*----------------------------------------------------------------------*/ int extSpecialPerimFunc(bp, sense) Boundary *bp; bool sense; { TileType tinside, toutside; NodeRegion *diffNode = (NodeRegion *) extGetRegion(bp->b_outside); int thisterm, extended, i; LinkedBoundary *newBound, *lb, *lastlb; ExtDevice *devptr; bool needSurvey; /* Note that extEnumTilePerim() assumes for the non-Manhattan case */ /* that non-Manhattan tiles should be incorporated into the device */ /* gate for purposes of computing effective length and width. In */ /* most cases this will be only a slight deviation from the true */ /* result. */ switch (bp->b_direction) { case BD_TOP: tinside = TiGetTopType(bp->b_inside); toutside = TiGetBottomType(bp->b_outside); break; case BD_BOTTOM: tinside = TiGetBottomType(bp->b_inside); toutside = TiGetTopType(bp->b_outside); break; case BD_RIGHT: tinside = TiGetRightType(bp->b_inside); toutside = TiGetLeftType(bp->b_outside); break; case BD_LEFT: tinside = TiGetLeftType(bp->b_inside); toutside = TiGetRightType(bp->b_outside); break; } /* Required to use the same device record that was used to find */ /* the terminals. */ if ((devptr = extTransRec.tr_devrec) == NULL) return 0; /* Check all terminal classes for a matching type */ needSurvey = FALSE; for (i = 0; !TTMaskIsZero(&devptr->exts_deviceSDTypes[i]); i++) { if (TTMaskHasType(&devptr->exts_deviceSDTypes[i], toutside)) { needSurvey = TRUE; break; } } if (!sense || needSurvey) { if (toutside == TT_SPACE) if (glob_subsnode != NULL) diffNode = glob_subsnode; } /* Check for terminal on different plane than the device */ if (!needSurvey) { for (i = 0; !TTMaskIsZero(&devptr->exts_deviceSDTypes[i]); i++) { TileTypeBitMask mmask; PlaneMask pmask; mmask = devptr->exts_deviceSDTypes[i]; if (toutside != TT_SPACE) TTMaskClearType(&mmask, TT_SPACE); pmask = DBTechTypesToPlanes(&mmask); if (!PlaneMaskHasPlane(pmask, DBPlane(tinside))) { diffNode = extTransRec.tr_termnode[i]; needSurvey = TRUE; break; } } } if (!sense || needSurvey) { /* * Since we're repeating the search, all terminals should be there. */ if (!sense) thisterm = 0; else { for (thisterm = 0; thisterm < extTransRec.tr_nterm; thisterm++) if (extTransRec.tr_termnode[thisterm] == diffNode) break; if (thisterm >= extTransRec.tr_nterm) { /* This is not necessarily an error; e.g., happens for */ /* a device like a diode with TT_SPACE in the source/ */ /* drain list. */ return 1; } } /* * Check the existing segment list to see if this segment * extends an existing segment. */ extended = 0; for (lb = extSpecialBounds[thisterm]; lb != NULL; lb = lb->b_next) { if (bp->b_direction == lb->dir) { switch(lb->dir) { case BD_LEFT: case BD_RIGHT: if (bp->b_segment.r_xbot == lb->r.r_xbot) { if (bp->b_segment.r_ybot == lb->r.r_ytop) { if (extended) lastlb->r.r_ybot = lb->r.r_ybot; else lb->r.r_ytop = bp->b_segment.r_ytop; extended++; lastlb = lb; } else if (bp->b_segment.r_ytop == lb->r.r_ybot) { if (extended) lastlb->r.r_ytop = lb->r.r_ytop; else lb->r.r_ybot = bp->b_segment.r_ybot; extended++; lastlb = lb; } } break; case BD_TOP: case BD_BOTTOM: if (bp->b_segment.r_ybot == lb->r.r_ybot) { if (bp->b_segment.r_xbot == lb->r.r_xtop) { if (extended) lastlb->r.r_xbot = lb->r.r_xbot; else lb->r.r_xtop = bp->b_segment.r_xtop; extended++; lastlb = lb; } else if (bp->b_segment.r_xtop == lb->r.r_xbot) { if (extended) lastlb->r.r_xtop = lb->r.r_xtop; else lb->r.r_xbot = bp->b_segment.r_xbot; extended++; lastlb = lb; } } break; } } if (extended == 2) { /* Connected two existing entries---need to remove lastlb, */ /* which is now a redundant segment. */ if (lastlb == extSpecialBounds[thisterm]) extSpecialBounds[thisterm] = lastlb->b_next; else { for (lb = extSpecialBounds[thisterm]; lb != NULL; lb = lb->b_next) { if (lastlb == lb->b_next) { lb->b_next = lastlb->b_next; break; } } } freeMagic((char *)lastlb); /* New segment cannot extend more than two existing segments */ break; } } if (!extended) { newBound = (LinkedBoundary *)mallocMagic(sizeof(LinkedBoundary)); newBound->r = bp->b_segment; newBound->dir = bp->b_direction; newBound->b_next = extSpecialBounds[thisterm]; extSpecialBounds[thisterm] = newBound; } } return (0); } /* * ---------------------------------------------------------------------------- * * extTransOutTerminal -- * * Output the information associated with one terminal of a * transistor. This consists of three things: * - the name of the node to which the terminal is connected * - the length of the terminal along the perimeter of the transistor * - a list of attributes pertinent to this terminal. * * If 'whichTerm' is LL_GATEATTR, this is the gate; otherwise, it is one * of the diffusion terminals. * * Results: * None. * * Side effects: * Writes the above information to 'outFile'. * Resets ll_attr for each attribute we output to LL_NOATTR. * * ---------------------------------------------------------------------------- */ void extTransOutTerminal(lreg, ll, whichTerm, len, outFile) LabRegion *lreg; /* Node connected to terminal */ LabelList *ll; /* Gate's label list */ int whichTerm; /* Which terminal we are processing. The gate * is indicated by LL_GATEATTR. */ int len; /* Length of perimeter along terminal */ FILE *outFile; /* Output file */ { char *cp; int n; char fmt; fprintf(outFile, " \"%s\" %d", extNodeName(lreg), len); for (fmt = ' '; ll; ll = ll->ll_next) if (ll->ll_attr == whichTerm) { fprintf(outFile, "%c\"", fmt); cp = ll->ll_label->lab_text; n = strlen(cp) - 1; while (n-- > 0) putc(*cp++, outFile); ll->ll_attr = LL_NOATTR; fprintf(outFile, "\""); fmt = ','; } if (fmt == ' ') fprintf(outFile, " 0"); } /* * ---------------------------------------------------------------------------- * * extTransBad -- * * For a transistor where an error was encountered, give feedback * as to the location of the error. * * Results: * None. * * Side effects: * Complains to the user. * * ---------------------------------------------------------------------------- */ void extTransBad(def, tp, mesg) CellDef *def; Tile *tp; char *mesg; { Rect r; if (!DebugIsSet(extDebugID, extDebNoFeedback)) { TiToRect(tp, &r); DBWFeedbackAdd(&r, mesg, def, 1, STYLE_PALEHIGHLIGHTS); } extNumWarnings++; } /* * ---------------------------------------------------------------------------- * * extLabType -- * * Check to see whether the text passed as an argument satisfies * any of the label types in 'typeMask'. * * Results: * TRUE if the text is of one of the label types in 'typeMask', * FALSE if not. * * Side effects: * None. * * ---------------------------------------------------------------------------- */ bool extLabType(text, typeMask) char *text; int typeMask; { if (*text == '\0') return (FALSE); while (*text) text++; switch (*--text) { case '@': /* Node attribute */ return ((bool)(typeMask & LABTYPE_NODEATTR)); case '$': /* Terminal (source/drain) attribute */ return ((bool)(typeMask & LABTYPE_TERMATTR)); case '^': /* Gate attribute */ return ((bool)(typeMask & LABTYPE_GATEATTR)); default: return ((bool)(typeMask & LABTYPE_NAME)); } /*NOTREACHED*/ } /* * ---------------------------------------------------------------------------- * extNodeToTile -- * * Sets tp to be the tile containing the lower-leftmost point of the * NodeRegion *np, but in the tile planes of the ExtTree *et instead * of the tile planes originally containing *np. This routine used * to be defined as the macro NODETOTILE(). * * Results: * Returns a pointer to the tile * * Side effects: * None. * ---------------------------------------------------------------------------- */ Tile *extNodeToTile(np, et) NodeRegion *np; ExtTree *et; { Tile *tp; Plane *myplane; myplane = et->et_use->cu_def->cd_planes[np->nreg_pnum]; tp = myplane->pl_hint; GOTOPOINT(tp, &np->nreg_ll); myplane->pl_hint = tp; if (IsSplit(tp)) { TileType tpt = TiGetTypeExact(tp); if ((tpt & TT_LEFTMASK) == (np->nreg_type & TT_LEFTMASK)) TiSetBody(tp, tpt & ~TT_SIDE); else TiSetBody(tp, tpt | TT_SIDE); } return tp; } /* * ---------------------------------------------------------------------------- * * extSetNodeNum -- * * Update reg->lreg_ll and reg->lreg_pnum so that they are always the * lowest leftmost coordinate in a cell, on the plane with the lowest * number (formerly a macro in extractInt.h). * * (10/1/05: Changed from a macro to a subroutine and modified for * handling non-Manhattan geometry) * * Results: * None. * * Side effects: * None. * * ---------------------------------------------------------------------------- */ void extSetNodeNum(reg, plane, tile) LabRegion *reg; int plane; Tile *tile; { TileType type; if (IsSplit(tile)) { /* Only consider split tiles if the lower-left-hand corner */ /* is only the type under consideration. */ if (!SplitSide(tile) && SplitDirection(tile)) type = SplitSide(tile) ? SplitRightType(tile) : SplitLeftType(tile); else if (reg->lreg_pnum == DBNumPlanes) type = TiGetTypeExact(tile); else return; } else type = TiGetType(tile); if ((plane < reg->lreg_pnum) || (reg->lreg_type & TT_DIAGONAL)) { reg->lreg_type = type; reg->lreg_pnum = plane; reg->lreg_ll = tile->ti_ll; } else if (plane == reg->lreg_pnum) { if (LEFT(tile) < reg->lreg_ll.p_x) { reg->lreg_ll = tile->ti_ll; reg->lreg_type = type; } else if (LEFT(tile) == reg->lreg_ll.p_x && BOTTOM(tile) < reg->lreg_ll.p_y) { reg->lreg_ll.p_y = BOTTOM(tile); reg->lreg_type = type; } } } /* * ---------------------------------------------------------------------------- * * extTransFirst -- * extTransEach -- * * Filter functions passed to ExtFindRegions when tracing out transistor * regions as part of flat circuit extraction. * * Results: * extTransFirst returns a pointer to a new TransRegion. * extTransEach returns NULL. * * Side effects: * Memory is allocated by extTransFirst. * We cons the newly allocated region onto the front of the existing * region list. * * The area of each transistor is updated by extTransEach. * * ---------------------------------------------------------------------------- */ Region * extTransFirst(tile, arg) Tile *tile; FindRegion *arg; { TransRegion *reg; reg = (TransRegion *) mallocMagic((unsigned) (sizeof (TransRegion))); reg->treg_next = (TransRegion *) NULL; reg->treg_labels = (LabelList *) NULL; reg->treg_area = 0; reg->treg_tile = tile; reg->treg_pnum = DBNumPlanes; if (IsSplit(tile)) reg->treg_type = SplitSide(tile) ? SplitRightType(tile) : SplitLeftType(tile); else reg->treg_type = TiGetTypeExact(tile); /* Prepend it to the region list */ reg->treg_next = (TransRegion *) arg->fra_region; arg->fra_region = (Region *) reg; return ((Region *) reg); } /*ARGSUSED*/ int extTransEach(tile, pNum, arg) Tile *tile; int pNum; FindRegion *arg; { TransRegion *reg = (TransRegion *) arg->fra_region; int area = TILEAREA(tile); if (IsSplit(tile)) area /= 2; /* Split tiles are 1/2 area! */ else if (IsSplit(reg->treg_tile)) { /* Avoid setting the region's tile pointer to a split tile */ reg->treg_tile = tile; reg->treg_type = TiGetTypeExact(tile); } /* The following is non-ideal. It assumes that the lowest plane of */ /* types connected to a device is the plane of the device itself. */ /* Otherwise, the area of the device will be miscalculated. */ if (pNum < reg->treg_pnum) reg->treg_area = 0; extSetNodeNum((LabRegion *) reg, pNum, tile); if (pNum == reg->treg_pnum) reg->treg_area += area; return (0); } /* * ---------------------------------------------------------------------------- * * extFindNodes -- * * Build up, in the manner of ExtFindRegions, a list of all the * node regions in the CellDef 'def'. This procedure is heavily * optimized for speed. * * Results: * Returns a pointer to a NULL-terminated list of NodeRegions * that correspond to the nodes in the circuit. The label lists * for each node region have not yet been filled in. * * Side effects: * Memory is allocated. * * ---------------------------------------------------------------------------- */ Stack *extNodeStack = NULL; Rect *extNodeClipArea = NULL; NodeRegion * extFindNodes(def, clipArea, subonly) CellDef *def; /* Def whose nodes are being found */ Rect *clipArea; /* If non-NULL, ignore perimeter and area that extend * outside this rectangle. */ bool subonly; /* If true, only find the substrate node, and return */ { int extNodeAreaFunc(); int extSubsFunc(); int extSubsFunc2(); FindRegion arg; int pNum, n; TileTypeBitMask subsTypesNonSpace; bool space_is_substrate; /* Reset perimeter and area prior to node extraction */ for (n = 0; n < ExtCurStyle->exts_numResistClasses; n++) extResistArea[n] = extResistPerim[n] = 0; extNodeClipArea = clipArea; if (extNodeStack == (Stack *) NULL) extNodeStack = StackNew(64); arg.fra_def = def; arg.fra_region = (Region *) NULL; SigDisableInterrupts(); /* First pass: Find substrate. Collect all tiles belonging */ /* to the substrate and push them onto the stack. Then */ /* call extNodeAreaFunc() on the first of these to generate */ /* a single substrate node. */ /* Refinement: Split search into two parts, one on the */ /* globSubstratePlane and one on all other planes. ONLY */ /* search other planes if TT_SPACE is in the list of */ /* substrate types, and then only consider those types to */ /* be part of the substrate node if they have only space */ /* below them on the globSubstratePlane. This method lets */ /* a single type like "psd" operate on, for example, both */ /* the substrate and an isolated pwell, without implicitly */ /* connecting the isolated pwell to the substrate. */ temp_subsnode = (NodeRegion *)NULL; // Reset for new search if (TTMaskHasType(&ExtCurStyle->exts_globSubstrateTypes, TT_SPACE)) space_is_substrate = True; else space_is_substrate = False; TTMaskZero(&subsTypesNonSpace); TTMaskSetMask(&subsTypesNonSpace, &ExtCurStyle->exts_globSubstrateTypes); TTMaskClearType(&subsTypesNonSpace, TT_SPACE); pNum = ExtCurStyle->exts_globSubstratePlane; /* Does the type set of this plane intersect the substrate types? */ if (TTMaskIntersect(&DBPlaneTypes[pNum], &subsTypesNonSpace)) { arg.fra_pNum = pNum; DBSrPaintClient((Tile *) NULL, def->cd_planes[pNum], &TiPlaneRect, &subsTypesNonSpace, extUnInit, extSubsFunc, (ClientData) &arg); } for (pNum = PL_TECHDEPBASE; pNum < DBNumPlanes; pNum++) { if (pNum == ExtCurStyle->exts_globSubstratePlane) continue; /* Does the type set of this plane intersect the substrate types? */ if (TTMaskIntersect(&DBPlaneTypes[pNum], &subsTypesNonSpace)) { arg.fra_pNum = pNum; if (space_is_substrate) DBSrPaintClient((Tile *) NULL, def->cd_planes[pNum], &TiPlaneRect, &subsTypesNonSpace, extUnInit, extSubsFunc2, (ClientData) &arg); else DBSrPaintClient((Tile *) NULL, def->cd_planes[pNum], &TiPlaneRect, &subsTypesNonSpace, extUnInit, extSubsFunc, (ClientData) &arg); } } /* If there was a substrate connection, process it and everything */ /* that was connected to it. If not, then create a new node */ /* to represent the substrate. */ if (!StackEmpty(extNodeStack)) { Tile *tile; int tilePlaneNum; POPTILE(tile, tilePlaneNum); arg.fra_pNum = tilePlaneNum; extNodeAreaFunc(tile, &arg); temp_subsnode = (NodeRegion *)arg.fra_region; } else if (ExtCurStyle->exts_globSubstratePlane != -1) { NodeRegion *loc_subsnode; extNodeAreaFunc((Tile *)NULL, (FindRegion *)&arg); loc_subsnode = (NodeRegion *)arg.fra_region; loc_subsnode->nreg_pnum = ExtCurStyle->exts_globSubstratePlane; loc_subsnode->nreg_type = TT_SPACE; loc_subsnode->nreg_ll.p_x = MINFINITY + 3; loc_subsnode->nreg_ll.p_y = MINFINITY + 3; loc_subsnode->nreg_labels = NULL; temp_subsnode = loc_subsnode; } if (subonly == TRUE) return ((NodeRegion *) arg.fra_region); /* Second pass: Find all other nodes */ for (pNum = PL_TECHDEPBASE; pNum < DBNumPlanes; pNum++) { arg.fra_pNum = pNum; (void) DBSrPaintClient((Tile *) NULL, def->cd_planes[pNum], &TiPlaneRect, &ExtCurStyle->exts_activeTypes, extUnInit, extNodeAreaFunc, (ClientData) &arg); } SigEnableInterrupts(); /* Compute resistance for last node */ if (arg.fra_region && (ExtOptions & EXT_DORESISTANCE)) extSetResist((NodeRegion *) arg.fra_region); return ((NodeRegion *) arg.fra_region); } int extSubsFunc(tile, arg) Tile *tile; FindRegion *arg; { int pNum; Rect tileArea; TileType type; TileTypeBitMask *smask; int extSubsFunc3(); if (IsSplit(tile)) { type = (SplitSide(tile)) ? SplitRightType(tile) : SplitLeftType(tile); if (type == TT_SPACE) return 0; /* Should not happen */ } /* Run second search in the area of the tile on the substrate plane */ /* to make sure that no shield types are covering this tile. */ TiToRect(tile, &tileArea); smask = &ExtCurStyle->exts_globSubstrateShieldTypes; for (pNum = PL_TECHDEPBASE; pNum < DBNumPlanes; pNum++) if (TTMaskIntersect(&DBPlaneTypes[pNum], smask)) if (DBSrPaintArea((Tile *) NULL, arg->fra_def->cd_planes[pNum], &tileArea, smask, extSubsFunc3, (ClientData)NULL) != 0) return (0); /* Mark this tile as pending and push it */ PUSHTILE(tile, arg->fra_pNum); /* That's all we do */ return (0); } int extSubsFunc2(tile, arg) Tile *tile; FindRegion *arg; { int pNum; Rect tileArea; TileTypeBitMask *smask; int extSubsFunc3(); TiToRect(tile, &tileArea); /* Run second search in the area of the tile on the substrate plane */ /* to make sure that no shield types are covering this tile. */ smask = &ExtCurStyle->exts_globSubstrateShieldTypes; for (pNum = PL_TECHDEPBASE; pNum < DBNumPlanes; pNum++) if (TTMaskIntersect(&DBPlaneTypes[pNum], smask)) if (DBSrPaintArea((Tile *) NULL, arg->fra_def->cd_planes[pNum], &tileArea, smask, extSubsFunc3, (ClientData)NULL) != 0) return (1); /* Run third search in the area of the tile on the substrate plane */ /* to make sure that nothing but space is under these tiles. */ pNum = ExtCurStyle->exts_globSubstratePlane; if (DBSrPaintArea((Tile *) NULL, arg->fra_def->cd_planes[pNum], &tileArea, &DBAllButSpaceBits, extSubsFunc3, (ClientData)NULL) == 0) { /* Mark this tile as pending and push it */ PUSHTILE(tile, arg->fra_pNum); } return (0); } int extSubsFunc3(tile) Tile *tile; { /* Stops the search because something that was not space was found */ return 1; } int extNodeAreaFunc(tile, arg) Tile *tile; FindRegion *arg; { int tilePlaneNum, pNum, len, resistClass, n, nclasses; dlong area; PlaneMask pMask; CapValue capval; TileTypeBitMask *mask, *resMask; NodeRegion *reg; Tile *tp; TileType type, t, residue, tres; NodeRegion *old; Rect r; PlaneAndArea pla; if (tile && IsSplit(tile)) { type = (SplitSide(tile)) ? SplitRightType(tile) : SplitLeftType(tile); if (type == TT_SPACE) return 0; /* Should not happen */ } /* Compute the resistance for the previous region */ if (old = (NodeRegion *) arg->fra_region) if (ExtOptions & EXT_DORESISTANCE) extSetResist(old); /* Allocate a new node */ nclasses = ExtCurStyle->exts_numResistClasses; n = sizeof (NodeRegion) + (sizeof (PerimArea) * (nclasses - 1)); reg = (NodeRegion *) mallocMagic((unsigned) n); reg->nreg_labels = (LabelList *) NULL; reg->nreg_cap = (CapValue) 0; reg->nreg_resist = 0; reg->nreg_pnum = DBNumPlanes; reg->nreg_next = (NodeRegion *) NULL; for (n = 0; n < nclasses; n++) reg->nreg_pa[n].pa_perim = reg->nreg_pa[n].pa_area = 0; /* Prepend the new node to the region list */ reg->nreg_next = (NodeRegion *) arg->fra_region; arg->fra_region = (Region *) reg; /* Used by substrate generating routine */ if (tile == NULL) return 1; /* Mark this tile as pending and push it */ PUSHTILE(tile, arg->fra_pNum); /* Continue processing tiles until there are none left */ while (!StackEmpty(extNodeStack)) { POPTILE(tile, tilePlaneNum); /* * Since tile was pushed on the stack, we know that it * belongs to this region. Check to see that it hasn't * been visited in the meantime. If it's still unvisited, * visit it and process its neighbors. */ if (tile->ti_client == (ClientData) reg) continue; tile->ti_client = (ClientData) reg; if (DebugIsSet(extDebugID, extDebNeighbor)) extShowTile(tile, "neighbor", 1); if (IsSplit(tile)) { type = (SplitSide(tile)) ? SplitRightType(tile): SplitLeftType(tile); } else type = TiGetTypeExact(tile); /* Contacts are replaced by their residues when calculating */ /* area/perimeter capacitance and resistance. */ residue = (DBIsContact(type)) ? DBPlaneToResidue(type, tilePlaneNum) : type; mask = &ExtCurStyle->exts_nodeConn[type]; resMask = &ExtCurStyle->exts_typesResistChanged[residue]; resistClass = ExtCurStyle->exts_typeToResistClass[residue]; /* * Make sure the lower-leftmost point in the node is * kept up to date, so we can generate an internal * node name that does not depend on any other nodes * in this cell. */ extSetNodeNum((LabRegion *) reg, tilePlaneNum, tile); /* * Keep track of the total area of this node, and the * contribution to parasitic ground capacitance resulting * from area. */ if (extNodeClipArea) { TITORECT(tile, &r); GEOCLIP(&r, extNodeClipArea); area = (dlong)(r.r_xtop - r.r_xbot) * (dlong)(r.r_ytop - r.r_ybot); } else area = (dlong)(TOP(tile) - BOTTOM(tile)) * (dlong)(RIGHT(tile) - LEFT(tile)); if (IsSplit(tile)) area /= 2; /* Split tiles are 1/2 area! */ if (resistClass != -1) extResistArea[resistClass] += area; reg->nreg_cap += area * ExtCurStyle->exts_areaCap[residue]; /* Compute perimeter of nonManhattan edges */ if (IsSplit(tile)) { len = ((RIGHT(tile) - LEFT(tile)) * (RIGHT(tile) - LEFT(tile))) + ((TOP(tile) - BOTTOM(tile)) * (TOP(tile) - BOTTOM(tile))); len = (int)sqrt((double)len); if (extNodeClipArea) { /* To-do: Find perimeter length of clipped edge */ } /* Find the type on the other side of the tile */ t = (SplitSide(tile)) ? SplitLeftType(tile): SplitRightType(tile); tres = (DBIsContact(t)) ? DBPlaneToResidue(t, tilePlaneNum) : t; if ((capval = ExtCurStyle->exts_perimCap[residue][tres]) != (CapValue) 0) reg->nreg_cap += capval * len; if (TTMaskHasType(resMask, tres) && resistClass != -1) extResistPerim[resistClass] += len; } /* * Walk along all four sides of tile. * Sum perimeter capacitance as we go. * Keep track of the contribution to the total perimeter * of this node, for computing resistance. */ /* Top */ topside: if (IsSplit(tile) && (SplitSide(tile) ^ SplitDirection(tile))) goto leftside; for (tp = RT(tile); RIGHT(tp) > LEFT(tile); tp = BL(tp)) { if (extNodeClipArea) { r.r_ybot = r.r_ytop = TOP(tile); r.r_xtop = MIN(RIGHT(tile), RIGHT(tp)); r.r_xbot = MAX(LEFT(tile), LEFT(tp)); GEOCLIP(&r, extNodeClipArea); len = EDGENULL(&r) ? 0 : r.r_xtop - r.r_xbot; } else len = MIN(RIGHT(tile), RIGHT(tp)) - MAX(LEFT(tile), LEFT(tp)); if (IsSplit(tp)) { t = SplitBottomType(tp); if (tp->ti_client == extUnInit && TTMaskHasType(mask, t)) { PUSHTILEBOTTOM(tp, tilePlaneNum); } else if (tp->ti_client != (ClientData)reg && TTMaskHasType(mask, t)) { /* Count split tile twice, once for each node it belongs to. */ tp->ti_client = extUnInit; PUSHTILEBOTTOM(tp, tilePlaneNum); } } else { t = TiGetTypeExact(tp); if (tp->ti_client == extUnInit && TTMaskHasType(mask, t)) { PUSHTILE(tp, tilePlaneNum); } } tres = (DBIsContact(t)) ? DBPlaneToResidue(t, tilePlaneNum) : t; if ((capval = ExtCurStyle->exts_perimCap[residue][tres]) != (CapValue) 0) reg->nreg_cap += capval * len; if (TTMaskHasType(resMask, tres) && resistClass != -1) extResistPerim[resistClass] += len; } /* Left */ leftside: if (IsSplit(tile) && SplitSide(tile)) goto bottomside; for (tp = BL(tile); BOTTOM(tp) < TOP(tile); tp = RT(tp)) { if (extNodeClipArea) { r.r_xbot = r.r_xtop = LEFT(tile); r.r_ytop = MIN(TOP(tile), TOP(tp)); r.r_ybot = MAX(BOTTOM(tile), BOTTOM(tp)); GEOCLIP(&r, extNodeClipArea); len = EDGENULL(&r) ? 0 : r.r_ytop - r.r_ybot; } else len = MIN(TOP(tile), TOP(tp)) - MAX(BOTTOM(tile), BOTTOM(tp)); if (IsSplit(tp)) { t = SplitRightType(tp); if (tp->ti_client == extUnInit && TTMaskHasType(mask, t)) { PUSHTILERIGHT(tp, tilePlaneNum); } else if (tp->ti_client != (ClientData)reg && TTMaskHasType(mask, t)) { /* Count split tile twice, once for each node it belongs to. */ tp->ti_client = extUnInit; PUSHTILERIGHT(tp, tilePlaneNum); } } else { t = TiGetTypeExact(tp); if (tp->ti_client == extUnInit && TTMaskHasType(mask, t)) { PUSHTILE(tp, tilePlaneNum); } } tres = (DBIsContact(t)) ? DBPlaneToResidue(t, tilePlaneNum) : t; if ((capval = ExtCurStyle->exts_perimCap[residue][tres]) != (CapValue) 0) reg->nreg_cap += capval * len; if (TTMaskHasType(resMask, tres) && resistClass != -1) extResistPerim[resistClass] += len; } /* Bottom */ bottomside: if (IsSplit(tile) && (!(SplitSide(tile) ^ SplitDirection(tile)))) goto rightside; for (tp = LB(tile); LEFT(tp) < RIGHT(tile); tp = TR(tp)) { if (extNodeClipArea) { r.r_ybot = r.r_ytop = BOTTOM(tile); r.r_xtop = MIN(RIGHT(tile), RIGHT(tp)); r.r_xbot = MAX(LEFT(tile), LEFT(tp)); GEOCLIP(&r, extNodeClipArea); len = EDGENULL(&r) ? 0 : r.r_xtop - r.r_xbot; } else len = MIN(RIGHT(tile), RIGHT(tp)) - MAX(LEFT(tile), LEFT(tp)); if (IsSplit(tp)) { t = SplitTopType(tp); if (tp->ti_client == extUnInit && TTMaskHasType(mask, t)) { PUSHTILETOP(tp, tilePlaneNum); } else if (tp->ti_client != (ClientData)reg && TTMaskHasType(mask, t)) { /* Count split tile twice, once for each node it belongs to. */ tp->ti_client = extUnInit; PUSHTILETOP(tp, tilePlaneNum); } } else { t = TiGetTypeExact(tp); if (tp->ti_client == extUnInit && TTMaskHasType(mask, t)) { PUSHTILE(tp, tilePlaneNum); } } tres = (DBIsContact(t)) ? DBPlaneToResidue(t, tilePlaneNum) : t; if ((capval = ExtCurStyle->exts_perimCap[residue][tres]) != (CapValue) 0) reg->nreg_cap += capval * len; if (TTMaskHasType(resMask, tres) && resistClass != -1) extResistPerim[resistClass] += len; } /* Right */ rightside: if (IsSplit(tile) && !SplitSide(tile)) goto donesides; for (tp = TR(tile); TOP(tp) > BOTTOM(tile); tp = LB(tp)) { if (extNodeClipArea) { r.r_xbot = r.r_xtop = RIGHT(tile); r.r_ytop = MIN(TOP(tile), TOP(tp)); r.r_ybot = MAX(BOTTOM(tile), BOTTOM(tp)); GEOCLIP(&r, extNodeClipArea); len = EDGENULL(&r) ? 0 : r.r_ytop - r.r_ybot; } else len = MIN(TOP(tile), TOP(tp)) - MAX(BOTTOM(tile), BOTTOM(tp)); if (IsSplit(tp)) { t = SplitLeftType(tp); if (tp->ti_client == extUnInit && TTMaskHasType(mask, t)) { PUSHTILELEFT(tp, tilePlaneNum); } else if (tp->ti_client != (ClientData)reg && TTMaskHasType(mask, t)) { /* Count split tile twice, once for each node it belongs to */ tp->ti_client = extUnInit; PUSHTILELEFT(tp, tilePlaneNum); } } else { t = TiGetTypeExact(tp); if (tp->ti_client == extUnInit && TTMaskHasType(mask, t)) { PUSHTILE(tp, tilePlaneNum); } } tres = (DBIsContact(t)) ? DBPlaneToResidue(t, tilePlaneNum) : t; if ((capval = ExtCurStyle->exts_perimCap[residue][tres]) != (CapValue) 0) reg->nreg_cap += capval * len; if (TTMaskHasType(resMask, tres) && resistClass != -1) extResistPerim[resistClass] += len; } donesides: /* No capacitance */ if ((ExtOptions & EXT_DOCAPACITANCE) == 0) reg->nreg_cap = (CapValue) 0; /* If this is a contact, visit all the other planes */ if (DBIsContact(type)) { pMask = DBConnPlanes[type]; pMask &= ~(PlaneNumToMaskBit(tilePlaneNum)); for (pNum = PL_TECHDEPBASE; pNum < DBNumPlanes; pNum++) if (PlaneMaskHasPlane(pMask, pNum)) { Plane *plane = arg->fra_def->cd_planes[pNum]; tp = plane->pl_hint; GOTOPOINT(tp, &tile->ti_ll); plane->pl_hint = tp; if (tp->ti_client != extUnInit) continue; /* tp and tile should have the same geometry for a contact */ if (IsSplit(tile) && IsSplit(tp)) { if (SplitSide(tile)) { t = SplitRightType(tp); if (TTMaskHasType(mask, t)) { PUSHTILERIGHT(tp, pNum); } } else { t = SplitLeftType(tp); if (TTMaskHasType(mask, t)) { PUSHTILELEFT(tp, pNum); } } } else if (IsSplit(tp)) { /* Need to test both sides of the tile */ t = SplitRightType(tp); if (TTMaskHasType(mask, t)) { PUSHTILERIGHT(tp, pNum); } t = SplitLeftType(tp); if (TTMaskHasType(mask, t)) { PUSHTILELEFT(tp, pNum); } } else { t = TiGetTypeExact(tp); if (TTMaskHasType(mask, t)) { PUSHTILE(tp, pNum); } } } } /* * The hairiest case is when this type connects to stuff on * other planes, but isn't itself connected as a contact. * For example, a CMOS pwell connects to diffusion of the * same doping (p substrate diff). In a case like this, * we need to search the entire AREA of the tile plus a * 1-lambda halo to find everything it overlaps or touches * on the other plane. */ if (pMask = DBAllConnPlanes[type]) { Rect biggerArea; bool is_split = IsSplit(tile); extNbrUn = extUnInit; TITORECT(tile, &pla.area); GEO_EXPAND(&pla.area, 1, &biggerArea); for (pNum = PL_TECHDEPBASE; pNum < DBNumPlanes; pNum++) if ((pNum != tilePlaneNum) && PlaneMaskHasPlane(pMask, pNum)) { pla.plane = pNum; if (is_split) DBSrPaintNMArea((Tile *) NULL, arg->fra_def->cd_planes[pNum], TiGetTypeExact(tile) & (TT_DIAGONAL | TT_SIDE | TT_DIRECTION), &biggerArea, mask, extNbrPushFunc, (ClientData) &pla); else DBSrPaintArea((Tile *) NULL, arg->fra_def->cd_planes[pNum], &biggerArea, mask, extNbrPushFunc, (ClientData) &pla); } } } return (0); } /* * ---------------------------------------------------------------------------- * * extGetCapValue -- * extSetCapValue -- * * Procedures to get/set a value from our capacitance tables. * * ---------------------------------------------------------------------------- */ void extSetCapValue(he, value) HashEntry *he; CapValue value; { if (HashGetValue(he) == NULL) HashSetValue(he, (CapValue *) mallocMagic(sizeof(CapValue))); *( (CapValue *) HashGetValue(he)) = value; } CapValue extGetCapValue(he) HashEntry *he; { if (HashGetValue(he) == NULL) extSetCapValue(he, (CapValue) 0); return *( (CapValue *) HashGetValue(he)); } /* * ---------------------------------------------------------------------------- * * extCapHashKill -- * * Kill off a coupling capacitance hash table. * * Results: * None. * * Side effects: * Frees up storage in the table. * ---------------------------------------------------------------------------- */ void extCapHashKill(ht) HashTable *ht; { HashSearch hs; HashEntry *he; HashStartSearch(&hs); while (he = HashNext(ht, &hs)) { if (HashGetValue(he) != NULL) { freeMagic(HashGetValue(he)); /* Free a malloc'ed CapValue */ HashSetValue(he, (ClientData) NULL); } } HashKill(ht); }