1223 lines
37 KiB
C
1223 lines
37 KiB
C
/*
|
|
* EFflat.c -
|
|
*
|
|
* Procedures to flatten the hierarchical description built
|
|
* by efReadDef().
|
|
*
|
|
* *********************************************************************
|
|
* * 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 rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-8.0/extflat/EFflat.c,v 1.5 2010/12/16 18:59:03 tim Exp $";
|
|
#endif /* not lint */
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
|
|
#include "utils/magic.h"
|
|
#include "utils/geometry.h"
|
|
#include "utils/geofast.h"
|
|
#include "utils/hash.h"
|
|
#include "utils/malloc.h"
|
|
#include "utils/utils.h"
|
|
#include "extflat/extflat.h"
|
|
#include "extflat/EFint.h"
|
|
|
|
/* C99 compat */
|
|
#include "textio/textio.h"
|
|
|
|
/* Initial size of the hash table of all flattened node names */
|
|
#define INITFLATSIZE 1024
|
|
|
|
/* Hash table containing all flattened capacitors */
|
|
HashTable efCapHashTable;
|
|
|
|
/* Hash table containing all flattened distances */
|
|
HashTable efDistHashTable;
|
|
|
|
/* Head of circular list of all flattened nodes */
|
|
EFNode efNodeList;
|
|
|
|
/* Root of the tree being flattened */
|
|
Def *efFlatRootDef;
|
|
Use efFlatRootUse;
|
|
HierContext efFlatContext;
|
|
|
|
/* Forward declarations */
|
|
int efFlatSingleCap();
|
|
void efFlatGlob();
|
|
int efFlatGlobHash(HierName *);
|
|
bool efFlatGlobCmp(HierName *, HierName *);
|
|
char *efFlatGlobCopy(HierName *);
|
|
void efFlatGlobError(EFNodeName *, EFNodeName *);
|
|
int efAddNodes(HierContext *, bool);
|
|
int efAddConns(HierContext *, bool);
|
|
int efAddOneConn(HierContext *, char *, char *, Connection *, bool);
|
|
|
|
/* Flags passed to efFlatNode() */
|
|
|
|
#define FLATNODE_STDCELL 0x01
|
|
#define FLATNODE_DOWARN 0x02
|
|
#define FLATNODE_NOABSTRACT 0x04
|
|
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* EFFlatBuild --
|
|
*
|
|
* First pass of flattening a circuit.
|
|
* Builds up the flattened tables of nodes, capacitors, etc, depending
|
|
* on the bits contained in flags: EF_FLATNODES causes the node table
|
|
* to be built, EF_FLATCAPS the internodal capacitor table (implies
|
|
* EF_FLATNODES), and EF_FLATDISTS the distance table.
|
|
*
|
|
* Callers who want various pieces of information should call
|
|
* the relevant EFVisit procedures (e.g., EFVisitDevs(), EFVisitCaps(),
|
|
* EFVisitNodes(), etc).
|
|
*
|
|
* Results:
|
|
* None.
|
|
*
|
|
* Side effects:
|
|
* Allocates lots of memory.
|
|
* Be certain to call EFFlatDone() when this memory is
|
|
* no longer needed.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
void
|
|
EFFlatBuild(name, flags)
|
|
char *name; /* Name of root def being flattened */
|
|
int flags; /* Say what to flatten; see above */
|
|
{
|
|
efFlatRootDef = efDefLook(name);
|
|
if (efHNStats) efHNPrintSizes("before building flattened table");
|
|
|
|
/* Keyed by a full HierName */
|
|
HashInitClient(&efNodeHashTable, INITFLATSIZE, HT_CLIENTKEYS,
|
|
efHNCompare, (char *(*)()) NULL, efHNHash, (int (*)()) NULL);
|
|
|
|
/* Keyed by a pair of HierNames */
|
|
HashInitClient(&efDistHashTable, INITFLATSIZE, HT_CLIENTKEYS,
|
|
efHNDistCompare, efHNDistCopy, efHNDistHash, efHNDistKill);
|
|
|
|
/* Keyed by pairs of EFNode pointers (i.e., EFCoupleKeys) */
|
|
HashInit(&efCapHashTable, INITFLATSIZE, HashSize(sizeof (EFCoupleKey)));
|
|
|
|
/* Keyed by a string and a HierName */
|
|
HashInitClient(&efHNUseHashTable, INITFLATSIZE, HT_CLIENTKEYS,
|
|
efHNUseCompare, (char *(*)()) NULL, efHNUseHash, (int (*)()) NULL);
|
|
|
|
/* Circular list of all nodes contains no elements initially */
|
|
efNodeList.efnode_next = (EFNodeHdr *) &efNodeList;
|
|
efNodeList.efnode_prev = (EFNodeHdr *) &efNodeList;
|
|
|
|
efFlatContext.hc_hierName = (HierName *) NULL;
|
|
efFlatContext.hc_use = &efFlatRootUse;
|
|
efFlatContext.hc_trans = GeoIdentityTransform;
|
|
efFlatContext.hc_x = efFlatContext.hc_y = 0;
|
|
efFlatRootUse.use_def = efFlatRootDef;
|
|
|
|
if (flags & EF_FLATNODES)
|
|
{
|
|
int flatnodeflags = 0;
|
|
if (flags & EF_WARNABSTRACT)
|
|
flatnodeflags = FLATNODE_NOABSTRACT;
|
|
|
|
if (flags & EF_NOFLATSUBCKT)
|
|
{
|
|
/* The top cell must always have the DEF_SUBCIRCUIT flag cleared */
|
|
efFlatRootDef->def_flags &= ~DEF_SUBCIRCUIT;
|
|
efFlatNodesStdCell(&efFlatContext);
|
|
}
|
|
else
|
|
{
|
|
flatnodeflags |= FLATNODE_DOWARN; /* No FLATNODE_STDCELL flag */
|
|
efFlatNodes(&efFlatContext, INT2CD(flatnodeflags));
|
|
}
|
|
efFlatKills(&efFlatContext);
|
|
if (!(flags & EF_NONAMEMERGE))
|
|
efFlatGlob();
|
|
}
|
|
|
|
/* Must happen after kill processing */
|
|
if (flags & EF_FLATCAPS)
|
|
efFlatCaps(&efFlatContext);
|
|
|
|
/* Distances are independent of kill processing */
|
|
if (flags & EF_FLATDISTS)
|
|
efFlatDists(&efFlatContext);
|
|
|
|
if (efHNStats) efHNPrintSizes("after building flattened table");
|
|
|
|
return;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
/* EFFlatBuildOneLevel -- */
|
|
/* */
|
|
/* EFFlatBuild for a single hierarchical level. Note, however that */
|
|
/* where subcircuits have no extracted components, the hierarchy of */
|
|
/* the subcircuit will be traversed and the subcircuit merged into */
|
|
/* the root being flattened. */
|
|
/* */
|
|
/* This routine used for hierarchical extraction. */
|
|
/*----------------------------------------------------------------------*/
|
|
|
|
HierContext *
|
|
EFFlatBuildOneLevel(def, flags)
|
|
Def *def; /* root def being flattened */
|
|
int flags;
|
|
{
|
|
int usecount, savecount;
|
|
Use *use;
|
|
int efFlatNodesDeviceless(); /* Forward declaration */
|
|
int efFlatCapsDeviceless(); /* Forward declaration */
|
|
int flatnodeflags;
|
|
|
|
efFlatRootDef = def;
|
|
|
|
/* Keyed by a full HierName */
|
|
HashInitClient(&efNodeHashTable, INITFLATSIZE, HT_CLIENTKEYS,
|
|
efHNCompare, (char *(*)()) NULL, efHNHash, (int (*)()) NULL);
|
|
|
|
/* Keyed by a pair of HierNames */
|
|
HashInitClient(&efDistHashTable, INITFLATSIZE, HT_CLIENTKEYS,
|
|
efHNDistCompare, efHNDistCopy, efHNDistHash, efHNDistKill);
|
|
|
|
/* Keyed by pairs of EFNode pointers (i.e., EFCoupleKeys) */
|
|
HashInit(&efCapHashTable, INITFLATSIZE, HashSize(sizeof (EFCoupleKey)));
|
|
|
|
/* Keyed by a string and a HierName */
|
|
HashInitClient(&efHNUseHashTable, INITFLATSIZE, HT_CLIENTKEYS,
|
|
efHNUseCompare, (char *(*)()) NULL, efHNUseHash, (int (*)()) NULL);
|
|
|
|
/* Circular list of all nodes contains no elements initially */
|
|
efNodeList.efnode_next = (EFNodeHdr *) &efNodeList;
|
|
efNodeList.efnode_prev = (EFNodeHdr *) &efNodeList;
|
|
|
|
efFlatContext.hc_hierName = (HierName *) NULL;
|
|
efFlatContext.hc_use = &efFlatRootUse;
|
|
efFlatContext.hc_trans = GeoIdentityTransform;
|
|
efFlatContext.hc_x = efFlatContext.hc_y = 0;
|
|
efFlatRootUse.use_def = efFlatRootDef;
|
|
|
|
/* Record all nodes down the hierarchy from here */
|
|
flatnodeflags = 0; /* No FLATNODE_DOWARN */
|
|
efFlatNodes(&efFlatContext, INT2CD(flatnodeflags));
|
|
|
|
/* Expand all subcells that contain connectivity information but */
|
|
/* no active devices (including those in subcells). */
|
|
|
|
usecount = HashGetNumEntries(&efFlatRootUse.use_def->def_uses);
|
|
|
|
/* Recursively flatten uses that have no active devices */
|
|
if (usecount > 0)
|
|
efHierSrUses(&efFlatContext, efFlatNodesDeviceless, (ClientData)&usecount);
|
|
|
|
if ((usecount == 0) && (HashGetNumEntries(&efFlatRootUse.use_def->def_devs) == 0))
|
|
efFlatRootUse.use_def->def_flags |= DEF_NODEVICES;
|
|
|
|
efFlatKills(&efFlatContext);
|
|
if (!(flags & EF_NONAMEMERGE))
|
|
efFlatGlob();
|
|
if (flags & EF_FLATCAPS)
|
|
efFlatCapsDeviceless(&efFlatContext);
|
|
if (flags & EF_FLATDISTS)
|
|
efFlatDists(&efFlatContext);
|
|
|
|
return &efFlatContext;
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* EFFlatDone --
|
|
*
|
|
* Cleanup by removing all memory used by the flattened circuit
|
|
* representation. For use of func(), see efFreeNodeList() in EFbuild.c.
|
|
*
|
|
* Results:
|
|
* None.
|
|
*
|
|
* Side effects:
|
|
* Frees lots of memory.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
void
|
|
EFFlatDone(func)
|
|
int (*func)();
|
|
{
|
|
#ifdef MALLOCTRACE
|
|
/* Hash table statistics */
|
|
TxPrintf("\n\nStatistics for node hash table:\n");
|
|
HashStats(&efNodeHashTable);
|
|
#endif /* MALLOCTRACE */
|
|
|
|
/* Free temporary storage */
|
|
efFreeNodeTable(&efNodeHashTable);
|
|
efFreeNodeList(&efNodeList, func);
|
|
HashFreeKill(&efCapHashTable);
|
|
HashKill(&efNodeHashTable);
|
|
HashKill(&efDistHashTable);
|
|
HashKill(&efHNUseHashTable);
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* efFlatNodes --
|
|
*
|
|
* Recursive procedure to flatten the nodes in hc->hc_use->use_def,
|
|
* using a depth-first post-order traversal of the hierarchy.
|
|
*
|
|
* Algorithm:
|
|
* We first recursivly call efFlatNodes for all of our children uses.
|
|
* This adds their node names to the global node table. Next we add
|
|
* our own nodes to the table. Some nodes will have to be merged
|
|
* by connections made in this def, or at least will require adjustments
|
|
* to their resistance or capacitance. We walk down the connection
|
|
* list hc->hc_use->use_def->def_conns to do this merging. Whenever
|
|
* two nodes merge, the EFNodeName list for the resulting node is
|
|
* rearranged to begin with the highest precedence name from the lists
|
|
* for the two nodes being combined. See efNodeMerge for a discussion
|
|
* of precedence.
|
|
*
|
|
* Results:
|
|
* Returns 0 to keep efHierSrUses going.
|
|
*
|
|
* Side effects:
|
|
* Adds node names to the table of flattened node names efNodeHashTable.
|
|
* May merge nodes from the list efNodeList as per the connection
|
|
* list hc->hc_use->use_def->def_conns.
|
|
*
|
|
* Note:
|
|
* stdcell = TRUE is only used when writing DEF files.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
int
|
|
efFlatNodes(hc, clientData)
|
|
HierContext *hc;
|
|
ClientData clientData;
|
|
{
|
|
int flags = (int)CD2INT(clientData);
|
|
|
|
bool stdcell = (flags & FLATNODE_STDCELL) ? TRUE : FALSE;
|
|
bool doWarn = (flags & FLATNODE_DOWARN) ? TRUE : FALSE;
|
|
|
|
if (flags & FLATNODE_NOABSTRACT)
|
|
{
|
|
Def *def = hc->hc_use->use_def;
|
|
if (def->def_flags & DEF_ABSTRACT)
|
|
TxError("Error: Cell %s was extracted as an abstract view.\n",
|
|
def->def_name);
|
|
}
|
|
|
|
(void) efHierSrUses(hc, efFlatNodes, clientData);
|
|
|
|
/* Add all our own nodes to the table */
|
|
efAddNodes(hc, stdcell);
|
|
|
|
/* Process our own connections and adjustments */
|
|
(void) efAddConns(hc, doWarn);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* efFlatNodesStdCell --
|
|
*
|
|
* Recursive procedure to flatten the nodes in hc->hc_use->use_def,
|
|
* using a depth-first post-order traversal of the hierarchy. We stop
|
|
* whenever we reach a subcircuit definition, only enumerating its ports.
|
|
*
|
|
* Algorithm:
|
|
* We first recursivly call efFlatNodes for all of our children uses.
|
|
* This adds their node names to the global node table. Next we add
|
|
* our own nodes to the table. Some nodes will have to be merged
|
|
* by connections made in this def, or at least will require adjustments
|
|
* to their resistance or capacitance. We walk down the connection
|
|
* list hc->hc_use->use_def->def_conns to do this merging. Whenever
|
|
* two nodes merge, the EFNodeName list for the resulting node is
|
|
* rearranged to begin with the highest precedence name from the lists
|
|
* for the two nodes being combined. See efNodeMerge for a discussion
|
|
* of precedence.
|
|
*
|
|
* Results:
|
|
* Returns 0 to keep efHierSrUses going.
|
|
*
|
|
* Side effects:
|
|
* Adds node names to the table of flattened node names efNodeHashTable.
|
|
* May merge nodes from the list efNodeList as per the connection
|
|
* list hc->hc_use->use_def->def_conns.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
int
|
|
efFlatNodesStdCell(hc)
|
|
HierContext *hc;
|
|
{
|
|
if (!(hc->hc_use->use_def->def_flags & DEF_SUBCIRCUIT))
|
|
{
|
|
/* Recursively flatten each use, except in defined subcircuits */
|
|
(void) efHierSrUses(hc, efFlatNodesStdCell, (ClientData) NULL);
|
|
}
|
|
|
|
/* Add all our own nodes to the table */
|
|
efAddNodes(hc, TRUE);
|
|
|
|
/* Process our own connections and adjustments */
|
|
if (!(hc->hc_use->use_def->def_flags & DEF_SUBCIRCUIT))
|
|
(void) efAddConns(hc, TRUE);
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
efFlatNodesDeviceless(hc, cdata)
|
|
HierContext *hc;
|
|
ClientData cdata;
|
|
{
|
|
int *usecount = (int *)cdata;
|
|
int newcount;
|
|
Use *use;
|
|
|
|
newcount = HashGetNumEntries(&hc->hc_use->use_def->def_uses);
|
|
|
|
/* Recursively flatten uses that have no active devices */
|
|
if (newcount > 0)
|
|
efHierSrUses(hc, efFlatNodesDeviceless, (ClientData)&newcount);
|
|
|
|
if ((HashGetNumEntries(&hc->hc_use->use_def->def_devs) == 0) && (newcount == 0))
|
|
{
|
|
/* Add all our own nodes to the table */
|
|
efAddNodes(hc, TRUE);
|
|
|
|
/* Process our own connections and adjustments */
|
|
efAddConns(hc, TRUE);
|
|
|
|
/* Mark this definition as having no devices, so it will not be visited */
|
|
hc->hc_use->use_def->def_flags |= DEF_NODEVICES;
|
|
|
|
/* If this definition has no devices but has ports, then it is treated */
|
|
/* as a black-box device, so don't decrement the use count of the */
|
|
/* parent. Alternately, devices flagged "abstract" or "primitive" are */
|
|
/* automatically treated as black-box devices. */
|
|
|
|
if (!(hc->hc_use->use_def->def_flags & DEF_SUBCIRCUIT))
|
|
if (!(hc->hc_use->use_def->def_flags & (DEF_ABSTRACT | DEF_PRIMITIVE)))
|
|
(*usecount)--;
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* efAddNodes --
|
|
*
|
|
* Add all the nodes defined by the def 'hc->hc_use->use_def' to the
|
|
* global symbol table. Each global name is prefixed by the hierarchical
|
|
* name component hc->hc_hierName. If "stdcell" is TRUE, we ONLY add nodes
|
|
* that are defined ports. Otherwise, we add all nodes.
|
|
*
|
|
* Results:
|
|
* None.
|
|
*
|
|
* Side effects:
|
|
* Adds node names to the table of flattened node names efNodeHashTable.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
int
|
|
efAddNodes(hc, stdcell)
|
|
HierContext *hc;
|
|
bool stdcell;
|
|
{
|
|
Def *def = hc->hc_use->use_def;
|
|
EFNodeName *nn, *newname, *oldname;
|
|
EFNode *node, *newnode;
|
|
EFAttr *ap, *newap;
|
|
LinkedRect *lr, *newlr;
|
|
HierName *hierName;
|
|
int size, asize;
|
|
HashEntry *he;
|
|
bool is_subcircuit = (def->def_flags & DEF_SUBCIRCUIT) ? TRUE : FALSE;
|
|
|
|
size = sizeof (EFNode) + (efNumResistClasses-1) * sizeof (EFPerimArea);
|
|
|
|
for (node = (EFNode *) def->def_firstn.efnode_next;
|
|
node != &def->def_firstn;
|
|
node = (EFNode *) node->efnode_next)
|
|
{
|
|
/* In subcircuits, only enumerate the ports */
|
|
if (stdcell && is_subcircuit && !(node->efnode_flags & EF_PORT))
|
|
continue;
|
|
|
|
newnode = (EFNode *) mallocMagic((unsigned)(size));
|
|
newnode->efnode_attrs = (EFAttr *) NULL;
|
|
for (ap = node->efnode_attrs; ap; ap = ap->efa_next)
|
|
{
|
|
asize = ATTRSIZE(strlen(ap->efa_text));
|
|
newap = (EFAttr *) mallocMagic((unsigned)(asize));
|
|
(void) strcpy(newap->efa_text, ap->efa_text);
|
|
GeoTransRect(&hc->hc_trans, &ap->efa_loc, &newap->efa_loc);
|
|
|
|
newap->efa_type = ap->efa_type;
|
|
newap->efa_next = newnode->efnode_attrs;
|
|
newnode->efnode_attrs = newap;
|
|
}
|
|
newnode->efnode_disjoint = (LinkedRect *)NULL;
|
|
for (lr = node->efnode_disjoint; lr; lr = lr->r_next)
|
|
{
|
|
newlr = (LinkedRect *)mallocMagic(sizeof(LinkedRect));
|
|
newlr->r_r = lr->r_r;
|
|
newlr->r_type = lr->r_type;
|
|
newlr->r_next = newnode->efnode_disjoint;
|
|
newnode->efnode_disjoint = newlr;
|
|
}
|
|
|
|
// If called with "hierarchy on", all local node caps and adjustments
|
|
// have been output and should be ignored.
|
|
|
|
newnode->efnode_cap = (!stdcell) ? node->efnode_cap : (EFCapValue)0.0;
|
|
newnode->efnode_client = (ClientData) NULL;
|
|
newnode->efnode_flags = node->efnode_flags;
|
|
newnode->efnode_type = node->efnode_type;
|
|
newnode->efnode_num = 1;
|
|
if (!stdcell)
|
|
bcopy((char *) node->efnode_pa, (char *) newnode->efnode_pa,
|
|
efNumResistClasses * sizeof (EFPerimArea));
|
|
else
|
|
bzero((char *) newnode->efnode_pa,
|
|
efNumResistClasses * sizeof (EFPerimArea));
|
|
GeoTransRect(&hc->hc_trans, &node->efnode_loc, &newnode->efnode_loc);
|
|
|
|
/* Add each name for this node to the hash table */
|
|
newnode->efnode_name = (EFNodeName *) NULL;
|
|
|
|
/* Prepend to global node list */
|
|
newnode->efnode_next = efNodeList.efnode_next;
|
|
newnode->efnode_prev = (EFNodeHdr *) &efNodeList;
|
|
efNodeList.efnode_next->efnhdr_prev = (EFNodeHdr *) newnode;
|
|
efNodeList.efnode_next = (EFNodeHdr *) newnode;
|
|
|
|
for (nn = node->efnode_name; nn; nn = nn->efnn_next)
|
|
{
|
|
/*
|
|
* Construct the full hierarchical name of this node.
|
|
* The path down to this point is given by hc->hc_hierName,
|
|
* to which nn->efnn_hier is "appended". Exception: nodes
|
|
* marked with EF_DEVTERM (fet substrate nodes used before
|
|
* declared, so intended to refer to default global names)
|
|
* are added as global nodes.
|
|
*/
|
|
if (node->efnode_flags & EF_DEVTERM) hierName = nn->efnn_hier;
|
|
else hierName = EFHNConcat(hc->hc_hierName, nn->efnn_hier);
|
|
he = HashFind(&efNodeHashTable, (char *) hierName);
|
|
|
|
/*
|
|
* The name should only have been in the hash table already
|
|
* if the node was marked with EF_DEVTERM as described above.
|
|
*/
|
|
if ((oldname = (EFNodeName *) HashGetValue(he)))
|
|
{
|
|
if (hierName != nn->efnn_hier)
|
|
EFHNFree(hierName, hc->hc_hierName, HN_CONCAT);
|
|
if (oldname->efnn_node != newnode)
|
|
efNodeMerge(&oldname->efnn_node, &newnode);
|
|
newnode = oldname->efnn_node;
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* We only guarantee that the first name for the node remains
|
|
* first (since the first name is the "canonical" name for the
|
|
* node). The order of the remaining names will be reversed.
|
|
*/
|
|
newname = (EFNodeName *) mallocMagic((unsigned)(sizeof (EFNodeName)));
|
|
HashSetValue(he, (char *) newname);
|
|
newname->efnn_node = newnode;
|
|
newname->efnn_hier = hierName;
|
|
newname->efnn_port = -1;
|
|
newname->efnn_refc = 0;
|
|
if (newnode->efnode_name)
|
|
{
|
|
newname->efnn_next = newnode->efnode_name->efnn_next;
|
|
newnode->efnode_name->efnn_next = newname;
|
|
}
|
|
else
|
|
{
|
|
newname->efnn_next = (EFNodeName *) NULL;
|
|
newnode->efnode_name = newname;
|
|
}
|
|
}
|
|
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* efAddConns --
|
|
*
|
|
* Make all the connections for a given def. This may cause previously
|
|
* distinct nodes in the flat table to merge.
|
|
*
|
|
* Results:
|
|
* Returns 0
|
|
*
|
|
* Side effects:
|
|
* May merge nodes from the list efNodeList as per the connection
|
|
* list hc->hc_use->use_def->def_conns.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
int
|
|
efAddConns(hc, doWarn)
|
|
HierContext *hc;
|
|
bool doWarn;
|
|
{
|
|
Connection *conn;
|
|
|
|
if (efWatchNodes)
|
|
TxPrintf("Processing %s (%s)\n",
|
|
EFHNToStr(hc->hc_hierName),
|
|
hc->hc_use->use_def->def_name);
|
|
|
|
for (conn = hc->hc_use->use_def->def_conns; conn; conn = conn->conn_next)
|
|
{
|
|
/* Special case for speed when no array info is present */
|
|
if (conn->conn_1.cn_nsubs == 0)
|
|
efAddOneConn(hc, conn->conn_name1, conn->conn_name2, conn, doWarn);
|
|
else
|
|
efHierSrArray(hc, conn, efAddOneConn, INT2CD(doWarn));
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* efAddOneConn --
|
|
*
|
|
* Do the work of adding a single connection. The names of the nodes
|
|
* to be connected are 'name1' and 'name2' (note that these are regular
|
|
* strings, not HierNames). The resistance of the merged node is to be
|
|
* adjusted by 'deltaR' and its capacitance by 'deltaC'. If 'name2' is
|
|
* NULL, we just adjust the R and C of the node 'name1'.
|
|
*
|
|
* Results:
|
|
* Returns 0
|
|
*
|
|
* Side effects:
|
|
* May merge nodes from the list efNodeList.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
int
|
|
efAddOneConn(hc, name1, name2, conn, doWarn)
|
|
HierContext *hc;
|
|
char *name1, *name2; /* These are strings, not HierNames */
|
|
Connection *conn;
|
|
bool doWarn;
|
|
{
|
|
HashEntry *he1, *he2;
|
|
EFNode *node, *newnode;
|
|
int n;
|
|
|
|
he1 = EFHNLook(hc->hc_hierName, name1, (doWarn) ? "connect(1)" : NULL);
|
|
if (he1 == NULL)
|
|
return 0;
|
|
|
|
/* Adjust the resistance and capacitance of its corresponding node */
|
|
node = ((EFNodeName *) HashGetValue(he1))->efnn_node;
|
|
node->efnode_cap += conn->conn_cap;
|
|
for (n = 0; n < efNumResistClasses; n++)
|
|
{
|
|
node->efnode_pa[n].pa_area += conn->conn_pa[n].pa_area;
|
|
node->efnode_pa[n].pa_perim += conn->conn_pa[n].pa_perim;
|
|
}
|
|
|
|
/* Merge this node with conn_name2 if one was specified */
|
|
if (name2)
|
|
{
|
|
he2 = EFHNLook(hc->hc_hierName, name2, (doWarn) ? "connect(2)" : NULL);
|
|
if (he2 == NULL)
|
|
return 0;
|
|
newnode = ((EFNodeName *) HashGetValue(he2))->efnn_node;
|
|
if (node != newnode)
|
|
efNodeMerge(&node, &newnode);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* efFlatGlob --
|
|
*
|
|
* This procedure checks to ensure that all occurrences of the same global
|
|
* name are connected. It also adds the reduced form of the global name
|
|
* (i.e., the global name with no pathname prefix) to the hash table
|
|
* efNodeHashTable, making this the preferred name of the global node.
|
|
*
|
|
* Algorithm:
|
|
* Scan through the node table looking for globals. Add each
|
|
* global to a global name table keyed by just the first component
|
|
* of the HierName. The value of the entry in this table is set
|
|
* initially to the EFNodeName for the first occurrence of the
|
|
* global node. If another occurrence of the global name is
|
|
* found whose EFNode differs from this one's, it's an error.
|
|
* However, we still merge all the pieces of a global node
|
|
* into a single one at the end.
|
|
*
|
|
* Results:
|
|
* None.
|
|
*
|
|
* Side effects:
|
|
* See above.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
void
|
|
efFlatGlob()
|
|
{
|
|
EFNodeName *nameFlat, *nameGlob;
|
|
EFNode *nodeFlat, *nodeGlob;
|
|
HashEntry *heFlat, *heGlob;
|
|
HierName *hnFlat, *hnGlob;
|
|
HashTable globalTable;
|
|
HashSearch hs;
|
|
|
|
HashInitClient(&globalTable, INITFLATSIZE, HT_CLIENTKEYS,
|
|
efFlatGlobCmp, efFlatGlobCopy, efFlatGlobHash, (int (*)()) NULL);
|
|
|
|
/*
|
|
* The following loop examines each global name (the last component of
|
|
* each flat HierName that ends in the global symbol '!'), using the
|
|
* hash table globalTable to keep track of how many times each global
|
|
* name has been seen. Each global name should be seen exactly once.
|
|
* The only exceptions are fet substrate nodes (nodes marked with the
|
|
* flag EF_DEVTERM), which automatically merge with other global nodes
|
|
* with the same name, since they're only implicitly connected anyway.
|
|
*/
|
|
for (nodeFlat = (EFNode *) efNodeList.efnode_next;
|
|
nodeFlat != &efNodeList;
|
|
nodeFlat = (EFNode *) nodeFlat->efnode_next)
|
|
{
|
|
/*
|
|
* Ignore nodes whose names aren't global. NOTE: we rely on
|
|
* the fact that EFHNBest() prefers global names to all others,
|
|
* so if the first name in a node's list isn't global, none of
|
|
* the rest are either.
|
|
*/
|
|
nameFlat = nodeFlat->efnode_name;
|
|
hnFlat = nameFlat->efnn_hier;
|
|
if (!EFHNIsGlob(hnFlat))
|
|
continue;
|
|
|
|
/*
|
|
* Look for an entry corresponding to the global part of hnFlat
|
|
* (only the leaf component) in the global name table. If one
|
|
* isn't found, an entry gets created.
|
|
*/
|
|
heGlob = HashFind(&globalTable, (char *) hnFlat);
|
|
nameGlob = (EFNodeName *) HashGetValue(heGlob);
|
|
if (nameGlob == NULL)
|
|
{
|
|
/*
|
|
* Create a new EFNodeName that points to nodeFlat, but
|
|
* don't link it in to nodeFlat->efnode_name yet.
|
|
*/
|
|
nameGlob = (EFNodeName *) mallocMagic((unsigned)(sizeof (EFNodeName)));
|
|
HashSetValue(heGlob, (ClientData) nameGlob);
|
|
nameGlob->efnn_node = nodeFlat;
|
|
nameGlob->efnn_hier = (HierName *) heGlob->h_key.h_ptr;
|
|
nameGlob->efnn_port = -1;
|
|
nameGlob->efnn_refc = 0;
|
|
}
|
|
else if (nameGlob->efnn_node != nodeFlat)
|
|
{
|
|
/*
|
|
* If either node is a fet substrate node (marked with EF_DEVTERM)
|
|
* it's OK to merge them; otherwise, it's an error, but we still
|
|
* merge the nodes. When merging, we blow away nodeGlob and
|
|
* absorb it into nodeFlat for simplicity in control of the main
|
|
* loop. Note that since nameGlob isn't on the efnode_name list
|
|
* for nodeGlob, we have to update its node backpointer explicitly.
|
|
*/
|
|
nodeGlob = nameGlob->efnn_node;
|
|
if ((nodeGlob->efnode_flags & EF_DEVTERM) == 0
|
|
&& (nodeFlat->efnode_flags & EF_DEVTERM) == 0)
|
|
{
|
|
efFlatGlobError(nameGlob, nameFlat);
|
|
}
|
|
efNodeMerge(&nodeFlat, &nodeGlob);
|
|
nameGlob->efnn_node = nodeFlat;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Now make another pass through the global name table,
|
|
* prepending the global name (the HierName consisting of
|
|
* the trailing component only that was allocated when the
|
|
* name was added to globalTable above) to its node, and
|
|
* also adding it to the global hash table efNodeHashTable.
|
|
*/
|
|
HashStartSearch(&hs);
|
|
while ((heGlob = HashNext(&globalTable, &hs)))
|
|
{
|
|
/*
|
|
* Add the name to the flat node name hash table, and
|
|
* prepend the EFNodeName to the node's list, but only
|
|
* if the node didn't already exist in efNodeHashTable.
|
|
* Otherwise, free nameGlob.
|
|
*/
|
|
nameGlob = (EFNodeName *) HashGetValue(heGlob);
|
|
hnGlob = nameGlob->efnn_hier;
|
|
heFlat = HashFind(&efNodeHashTable, (char *) hnGlob);
|
|
if (HashGetValue(heFlat) == NULL)
|
|
{
|
|
nodeFlat = nameGlob->efnn_node;
|
|
HashSetValue(heFlat, (ClientData) nameGlob);
|
|
nameGlob->efnn_next = nodeFlat->efnode_name;
|
|
nodeFlat->efnode_name = nameGlob;
|
|
}
|
|
else
|
|
{
|
|
freeMagic((char *) nameGlob);
|
|
EFHNFree(hnGlob, (HierName *) NULL, HN_GLOBAL);
|
|
}
|
|
}
|
|
|
|
HashKill(&globalTable);
|
|
return;
|
|
}
|
|
|
|
void
|
|
efFlatGlobError(nameGlob, nameFlat)
|
|
EFNodeName *nameGlob, *nameFlat;
|
|
{
|
|
EFNode *nodeGlob = nameGlob->efnn_node, *nodeFlat = nameFlat->efnn_node;
|
|
EFNodeName *nn;
|
|
int count;
|
|
|
|
TxPrintf("*** Global name %s not fully connected:\n",
|
|
nameGlob->efnn_hier->hn_name);
|
|
TxPrintf("One portion contains the names:\n");
|
|
for (count = 0, nn = nodeGlob->efnode_name;
|
|
count < 10 && nn;
|
|
count++, nn = nn->efnn_next)
|
|
{
|
|
TxPrintf(" %s\n", EFHNToStr(nn->efnn_hier));
|
|
}
|
|
if (nn) TxPrintf(" .... (no more names will be printed)\n");
|
|
TxPrintf("The other portion contains the names:\n");
|
|
for (count = 0, nn = nodeFlat->efnode_name;
|
|
count < 10 && nn;
|
|
count++, nn = nn->efnn_next)
|
|
{
|
|
TxPrintf(" %s\n", EFHNToStr(nn->efnn_hier));
|
|
}
|
|
if (nn) TxPrintf(" .... (no more names will be printed)\n");
|
|
TxPrintf("I'm merging the two pieces into a single node, but you\n");
|
|
TxPrintf("should be sure eventually to connect them in the layout.\n\n");
|
|
return;
|
|
}
|
|
|
|
bool
|
|
efFlatGlobCmp(hierName1, hierName2)
|
|
HierName *hierName1, *hierName2;
|
|
{
|
|
if (hierName1 == hierName2)
|
|
return FALSE;
|
|
|
|
return ((bool)(hierName1 == NULL || hierName2 == NULL
|
|
|| hierName1->hn_hash != hierName2->hn_hash
|
|
|| strcmp(hierName1->hn_name, hierName2->hn_name) != 0
|
|
));
|
|
}
|
|
|
|
char *
|
|
efFlatGlobCopy(hierName)
|
|
HierName *hierName;
|
|
{
|
|
HierName *hNew;
|
|
int size;
|
|
|
|
size = HIERNAMESIZE(strlen(hierName->hn_name));
|
|
hNew = (HierName *) mallocMagic((unsigned)(size));
|
|
(void) strcpy(hNew->hn_name, hierName->hn_name);
|
|
hNew->hn_parent = (HierName *) NULL;
|
|
hNew->hn_hash = hierName->hn_hash;
|
|
if (efHNStats)
|
|
efHNRecord(size, HN_GLOBAL);
|
|
|
|
return (char *) hNew;
|
|
}
|
|
|
|
int
|
|
efFlatGlobHash(hierName)
|
|
HierName *hierName;
|
|
{
|
|
return hierName->hn_hash;
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* efFlatKills --
|
|
*
|
|
* Recursively mark all killed nodes, using a depth-first post-order
|
|
* traversal of the hierarchy. The algorithm is the same as for
|
|
* efFlatNodes above.
|
|
*
|
|
* Results:
|
|
* Returns 0 to keep efHierSrUses going.
|
|
*
|
|
* Side effects:
|
|
* May mark node entries in the global name table as killed
|
|
* by setting EF_KILLED in the efnode_flags field.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
int
|
|
efFlatKills(hc)
|
|
HierContext *hc;
|
|
{
|
|
Def *def = hc->hc_use->use_def;
|
|
HashEntry *he;
|
|
EFNodeName *nn;
|
|
Kill *k;
|
|
|
|
/* Recursively visit each use */
|
|
(void) efHierSrUses(hc, efFlatKills, (ClientData) NULL);
|
|
|
|
/* Process all of our kill information */
|
|
for (k = def->def_kills; k; k = k->kill_next)
|
|
{
|
|
if ((he = EFHNConcatLook(hc->hc_hierName, k->kill_name, "kill")))
|
|
{
|
|
nn = (EFNodeName *) HashGetValue(he);
|
|
nn->efnn_node->efnode_flags |= EF_KILLED;
|
|
}
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* efFlatCapsDeviceless --
|
|
*
|
|
* Recursive procedure to flatten all capacitors in the circuit.
|
|
* Works like efFlatCaps() (see below), but ignores CellDefs that
|
|
* have no devices.
|
|
*
|
|
* Results:
|
|
* Returns 0 to keep efHierSrUses going.
|
|
*
|
|
* Side effects:
|
|
* See description of efFlatCaps().
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
|
|
int
|
|
efFlatCapsDeviceless(hc)
|
|
HierContext *hc;
|
|
{
|
|
Connection *conn;
|
|
int newcount;
|
|
Use *use;
|
|
|
|
newcount = HashGetNumEntries(&hc->hc_use->use_def->def_uses);
|
|
|
|
/* Recursively flatten uses that have no active devices */
|
|
if (newcount > 0)
|
|
efHierSrUses(hc, efFlatCapsDeviceless, (ClientData)NULL);
|
|
|
|
if (!(hc->hc_use->use_def->def_flags & DEF_NODEVICES))
|
|
if (hc->hc_use->use_def->def_flags & DEF_PROCESSED)
|
|
return 0;
|
|
|
|
/* Output our own capacitors */
|
|
for (conn = hc->hc_use->use_def->def_caps; conn; conn = conn->conn_next)
|
|
{
|
|
/* Special case for speed if no arraying info */
|
|
if (conn->conn_1.cn_nsubs == 0)
|
|
efFlatSingleCap(hc, conn->conn_name1, conn->conn_name2, conn);
|
|
else
|
|
efHierSrArray(hc, conn, efFlatSingleCap, (ClientData) NULL);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* efFlatCaps --
|
|
*
|
|
* Recursive procedure to flatten all capacitors in the circuit.
|
|
* Produces a single, global hash table (efCapHashTable) indexed
|
|
* by pairs of EFNode pointers, where the value of each entry is the
|
|
* capacitance between the two nodes.
|
|
*
|
|
* Algorithm:
|
|
* Before this procedure is called, efFlatNodes() should have been
|
|
* called to create a global table of all node names. We do a recursive
|
|
* traversal of the design rooted at 'hc->hc_use->use_def', and construct
|
|
* full hierarchical names from the terminals of each capacitor
|
|
* encountered.
|
|
*
|
|
* These full names are used to find via a lookup in efNodeHashTable the
|
|
* canonical name of the node for which this full name is an alias. The
|
|
* canonical name is output as the node to which this terminal connects.
|
|
*
|
|
* Capacitance where one of the nodes is substrate is treated specially;
|
|
* instead of adding an entry to the global hash table, we update
|
|
* the substrate capacitance of the other node appropriately.
|
|
*
|
|
* Results:
|
|
* Returns 0 to keep efHierSrUses going.
|
|
*
|
|
* Side effects:
|
|
* See above.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
int
|
|
efFlatCaps(hc)
|
|
HierContext *hc;
|
|
{
|
|
Connection *conn;
|
|
|
|
/* Recursively flatten capacitors */
|
|
(void) efHierSrUses(hc, efFlatCaps, (ClientData) 0);
|
|
|
|
/* Output our own capacitors */
|
|
for (conn = hc->hc_use->use_def->def_caps; conn; conn = conn->conn_next)
|
|
{
|
|
/* Special case for speed if no arraying info */
|
|
if (conn->conn_1.cn_nsubs == 0)
|
|
efFlatSingleCap(hc, conn->conn_name1, conn->conn_name2, conn);
|
|
else
|
|
efHierSrArray(hc, conn, efFlatSingleCap, (ClientData) NULL);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* efFlatSingleCap --
|
|
*
|
|
* Add a capacitor with value 'conn->conn_cap' between the nodes
|
|
* 'name1' and 'name2' (text names, not hierarchical names). Don't
|
|
* add the capacitor if either terminal is a killed node.
|
|
*
|
|
* Results:
|
|
* Returns 0
|
|
*
|
|
* Side effects:
|
|
* Adds an entry to efCapHashTable indexed by the nodes of 'name1'
|
|
* and 'name2' respectively. If the two nodes are the same, though,
|
|
* nothing happens. If either node is ground (GND!), the capacitance
|
|
* is added to the substrate capacitance of the other node instead of
|
|
* creating a hash table entry.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
int
|
|
efFlatSingleCap(hc, name1, name2, conn)
|
|
HierContext *hc; /* Contains hierarchical pathname to cell */
|
|
char *name1, *name2; /* Names of nodes connecting to capacitor */
|
|
Connection *conn; /* Contains capacitance to add */
|
|
{
|
|
EFNode *n1, *n2;
|
|
HashEntry *he;
|
|
EFCoupleKey ck;
|
|
static char msg0[] = "cap(1)";
|
|
static char msg1[] = "cap(2)";
|
|
char *msg;
|
|
|
|
/* Connections that are below threshold (ext2spice hierarchy only) */
|
|
/* will be missing. Do not generate errors for these. */
|
|
|
|
msg = (fabs((double)conn->conn_cap / 1000) < EFCapThreshold) ? NULL : msg0;
|
|
|
|
if ((he = EFHNLook(hc->hc_hierName, name1, msg)) == NULL)
|
|
return 0;
|
|
n1 = ((EFNodeName *) HashGetValue(he))->efnn_node;
|
|
if (n1->efnode_flags & EF_KILLED)
|
|
return 0;
|
|
|
|
if (msg) msg = msg1;
|
|
if ((he = EFHNLook(hc->hc_hierName, name2, msg)) == NULL)
|
|
return 0;
|
|
n2 = ((EFNodeName *) HashGetValue(he))->efnn_node;
|
|
if (n2->efnode_flags & EF_KILLED)
|
|
return 0;
|
|
|
|
/* Do nothing if the nodes aren't different */
|
|
if (n1 == n2)
|
|
return 0;
|
|
|
|
if (n1->efnode_flags & EF_GLOB_SUBS_NODE)
|
|
n2->efnode_cap += conn->conn_cap; /* node 2 to substrate */
|
|
else if (n2->efnode_flags & EF_GLOB_SUBS_NODE)
|
|
n1->efnode_cap += conn->conn_cap; /* node 1 to substrate */
|
|
else
|
|
{
|
|
/* node1 to node2 */
|
|
if (n1 < n2) ck.ck_1 = n1, ck.ck_2 = n2;
|
|
else ck.ck_1 = n2, ck.ck_2 = n1;
|
|
he = HashFind(&efCapHashTable, (char *) &ck);
|
|
CapHashSetValue(he, (double) (conn->conn_cap + CapHashGetValue(he)));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* efFlatDists --
|
|
*
|
|
* Recursive procedure to flatten all distance information in the circuit.
|
|
* Produces a single, global hash table (efDistHashTable) indexed
|
|
* by Distance structures, where the value of each entry is the same
|
|
* as the key and gives the min and maximum distances between the two
|
|
* points.
|
|
*
|
|
* Results:
|
|
* Returns 0 to keep efHierSrUses going.
|
|
*
|
|
* Side effects:
|
|
* See above.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
int
|
|
efFlatDists(hc)
|
|
HierContext *hc;
|
|
{
|
|
Distance *dist, *distFlat, distKey;
|
|
HashEntry *he, *heFlat;
|
|
HashSearch hs;
|
|
|
|
/* Recursively flatten distances */
|
|
(void) efHierSrUses(hc, efFlatDists, (ClientData) 0);
|
|
|
|
/* Process our own distances */
|
|
HashStartSearch(&hs);
|
|
while ((he = HashNext(&hc->hc_use->use_def->def_dists, &hs)))
|
|
{
|
|
dist = (Distance *) HashGetValue(he);
|
|
efHNBuildDistKey(hc->hc_hierName, dist, &distKey);
|
|
heFlat = HashFind(&efDistHashTable, (char *) &distKey);
|
|
if ((distFlat = (Distance *) HashGetValue(heFlat)))
|
|
{
|
|
/*
|
|
* This code differs from that in efBuildDist(), in that
|
|
* we replace the min/max information in distFlat from
|
|
* that in dist, rather than computing a new min/max.
|
|
* The reason is that the information in dist (in the
|
|
* parent) is assumed to override that already computed
|
|
* in the child.
|
|
*/
|
|
distFlat->dist_min = dist->dist_min;
|
|
distFlat->dist_max = dist->dist_max;
|
|
EFHNFree(distKey.dist_1, hc->hc_hierName, HN_CONCAT);
|
|
EFHNFree(distKey.dist_2, hc->hc_hierName, HN_CONCAT);
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* If there was no entry in the table already with this
|
|
* key, make the HashEntry point to its key (which is
|
|
* the newly malloc'd Distance structure).
|
|
*/
|
|
HashSetValue(heFlat, (ClientData) he->h_key.h_ptr);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* CapHashGetValue()
|
|
* do a HashGetValue, and if the pointer is null, return (EFCapValue)0.0
|
|
*/
|
|
|
|
EFCapValue CapHashGetValue(he)
|
|
HashEntry *he;
|
|
{
|
|
EFCapValue *capp = (EFCapValue *)HashGetValue(he);
|
|
if(capp == NULL)
|
|
return (EFCapValue)0;
|
|
else
|
|
return *capp;
|
|
}
|
|
|
|
/*
|
|
* CapHashSetValue()
|
|
* if the pointer is null, allocate a EFCapValue and point to it.
|
|
* Then copy in the new value.
|
|
*
|
|
* need to pass doubles regardless of what CapValue is because of
|
|
* argument promotion in ANSI C
|
|
*
|
|
*/
|
|
void
|
|
CapHashSetValue(he, c)
|
|
HashEntry *he;
|
|
double c;
|
|
{
|
|
EFCapValue *capp = (EFCapValue *)HashGetValue(he);
|
|
if(capp == NULL) {
|
|
capp = (EFCapValue *) mallocMagic((unsigned)(sizeof(EFCapValue)));
|
|
HashSetValue(he, capp);
|
|
}
|
|
*capp = (EFCapValue) c;
|
|
return;
|
|
}
|