1188 lines
36 KiB
C
1188 lines
36 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"
|
||
|
||
/* 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
|
||
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------------
|
||
*
|
||
* 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)
|
||
{
|
||
if (flags & EF_NOFLATSUBCKT)
|
||
efFlatNodesStdCell(&efFlatContext);
|
||
else
|
||
{
|
||
int flags = FLATNODE_DOWARN; /* No FLATNODE_STDCELL flag */
|
||
efFlatNodes(&efFlatContext, (ClientData)flags);
|
||
}
|
||
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 = FLATNODE_STDCELL; /* No FLATDNODE_DOWARN flag */
|
||
efFlatNodes(&efFlatContext, (ClientData)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;
|
||
|
||
/* Record all local nodes */
|
||
efAddNodes(&efFlatContext, FALSE);
|
||
efAddConns(&efFlatContext, TRUE);
|
||
|
||
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.
|
||
*
|
||
* Results:
|
||
* None.
|
||
*
|
||
* Side effects:
|
||
* Frees lots of memory.
|
||
*
|
||
* ----------------------------------------------------------------------------
|
||
*/
|
||
|
||
void
|
||
EFFlatDone()
|
||
{
|
||
#ifdef MALLOCTRACE
|
||
/* Hash table statistics */
|
||
TxPrintf("\n\nStatistics for node hash table:\n");
|
||
HashStats(&efNodeHashTable);
|
||
#endif /* MALLOCTRACE */
|
||
|
||
/* Free temporary storage */
|
||
efFreeNodeTable(&efNodeHashTable);
|
||
efFreeNodeList(&efNodeList);
|
||
HashFreeKill(&efCapHashTable);
|
||
HashKill(&efNodeHashTable);
|
||
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.
|
||
*
|
||
* ----------------------------------------------------------------------------
|
||
*/
|
||
|
||
int
|
||
efFlatNodes(hc, clientData)
|
||
HierContext *hc;
|
||
ClientData clientData;
|
||
{
|
||
int flags = (int)clientData;
|
||
|
||
bool stdcell = (flags & FLATNODE_STDCELL) ? TRUE : FALSE;
|
||
bool doWarn = (flags & FLATNODE_DOWARN) ? TRUE : FALSE;
|
||
|
||
(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" 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))
|
||
(*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;
|
||
HierName *hierName;
|
||
float scale;
|
||
int size, asize;
|
||
HashEntry *he;
|
||
bool is_subcircuit = (def->def_flags & DEF_SUBCIRCUIT) ? TRUE : FALSE;
|
||
|
||
scale = def->def_scale;
|
||
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_loc.r_xbot = (int)((float)(newap->efa_loc.r_xbot) * scale);
|
||
newap->efa_loc.r_xtop = (int)((float)(newap->efa_loc.r_xtop) * scale);
|
||
newap->efa_loc.r_ybot = (int)((float)(newap->efa_loc.r_ybot) * scale);
|
||
newap->efa_loc.r_ytop = (int)((float)(newap->efa_loc.r_ytop) * scale);
|
||
|
||
newap->efa_type = ap->efa_type;
|
||
newap->efa_next = newnode->efnode_attrs;
|
||
newnode->efnode_attrs = newap;
|
||
}
|
||
|
||
// 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);
|
||
|
||
/* Scale the result by "scale" --- hopefully we end up with an integer */
|
||
/* We don't scale the transform because the scale may be non-integer */
|
||
/* and the Transform type has integers only. */
|
||
newnode->efnode_loc.r_xbot = (int)((float)(newnode->efnode_loc.r_xbot) * scale);
|
||
newnode->efnode_loc.r_xtop = (int)((float)(newnode->efnode_loc.r_xtop) * scale);
|
||
newnode->efnode_loc.r_ybot = (int)((float)(newnode->efnode_loc.r_ybot) * scale);
|
||
newnode->efnode_loc.r_ytop = (int)((float)(newnode->efnode_loc.r_ytop) * scale);
|
||
|
||
/* 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;
|
||
|
||
/* Add each name for this node to the hash table */
|
||
newnode->efnode_name = (EFNodeName *) NULL;
|
||
|
||
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;
|
||
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, (ClientData)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;
|
||
}
|
||
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);
|
||
}
|
||
|
||
|
||
/*----
|
||
* WIP
|
||
*----
|
||
*/
|
||
|
||
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_SUBS_NODE)
|
||
n2->efnode_cap += conn->conn_cap; /* node 2 to substrate */
|
||
else if (n2->efnode_flags & EF_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;
|
||
}
|