Beginning to tear apart the extresist code; work in progress

and a lot of construction mess.

Resolved conflicts after rebasing on master, since both were
modifying the list of options to "extract".
This commit is contained in:
R. Timothy Edwards 2026-01-22 12:38:10 -05:00
parent 4943da5ce4
commit b768fcc3f9
6 changed files with 380 additions and 233 deletions

141
README
View File

@ -2,33 +2,126 @@ Second major code overhaul of 2026
-----------------------------------------
Implement the planned changes to "extresist":
(1) Remove the .sim format as a requirement.
extresist will get its node list from doing
a standard extraction (extFindNodes() or
ExtFindRegions(), whichever is appropriate.
This should be able to be done as a relatively
simple change, allowing "extresist" to be used
(1) Remove the .sim format as a requirement. extresist will get its node
list from doing a standard extraction (extFindNodes() or
ExtFindRegions(), whichever is appropriate. This should be able to be
done as a relatively simple change, allowing "extresist" to be used
without doing "ext2sim" as part of the process.
(2) Move extresist into the "extract" code.
"extresist" will become a command "extract do
extresist" and "ext2spice extresist on|off"
will determine if split nodes and terminals
are kept apart in the final netlist or
aggregated---Exactly as capacitance is done
now.
(2) Move extresist into the "extract" code. "extresist" will become a
command "extract do extresist" and "ext2spice extresist on|off"
will determine if split nodes and terminals are kept apart in the
final netlist or aggregated---Exactly as capacitance is done now.
Need to figure out how "tile junk" client data
and extract client data can exist simultaneously.
Need to figure out how "tile junk" client data and extract client
data can exist simultaneously.
"extresist" will run within "extBasic", being
called on a node after the devices and nodes are
identified; nodes created by ExtFindRegions()
will get sub-divided into more nodes. The
difficult case will be where a single tile gets
multiple nodes (such as where other wires branch
off the tile in the up or down direction). Maybe
this can be done like ClientData for split tiles,
with an indicator bit in ti_body?
"extresist" will run within "extBasic", being called on a node after
the devices and nodes are identified; nodes created by ExtFindRegions()
will get sub-divided into more nodes. The difficult case will be where
a single tile gets multiple nodes (such as where other wires branch off
the tile in the up or down direction). Maybe this can be done like
ClientData for split tiles, with an indicator bit in ti_body?
-----------------------------------------------------
For (1):
Reading the "nodes" file only creates "node" entries, one per net.
With an ExtFindRegions and ExtLabelRegions (or extFindNodes?) there
should be an equivalent list from which node names, locations, and
types can be taken. This information will be redundant, for now.
Note that ExtResisForDef() runs extResPrepSubstrate() which is like
extPrepSubstrate(), which is run from extCellFile().
So ExtResisForDef() could replace lines 150 to 174 with the ExtBasic
calls to ExtFindRegions(), extFindNodes(), and ExtLabelRegions().
This is followed by temporary code that creates the tables and lists
that the extresist code uses. Then, run the ResCheckSimNodes() code,
or at least the part for a single node (which is ResRex.c lines 1071
to 1240)
Add a ClientData record to a NodeRegion, and extresist can run
by adding "tileJunk" records to the NodeRegion client data, and
leave the region records in place. (done)
Then need to figure out how to output the data all at once.
Replace the "extresist" command with "extract do extresist".
EXT_DOEXTRESIST. Maybe later replace "resistance" and make it
equivalent to "extresist", or maybe make "resistance" *be*
DOEXTRESIST and use "lumped" instead of "resistance" (I like
this better). (done)
"ext2spice extresist on" will then output the netlist exactly as
it appears in the .ext file, while "ext2spice extresist off" will
conglomerate the .tX and .nX sub-nodes and produce what is
hopefully the equivalent of a netlist from a .ext file without
"extract do extresist".
We keep (maybe)
HashTable ResNodeTable
RDev *ResRDevList
At issue: Doing it this way might be more convenient, but by
keeping the NodeRegions in place and adding "tileJunk" records,
it's harder to find where the subnets are connected. If the
NodeRegions get replaced, then there are way more regions but,
for example, a transistor terminal node is easily found by
looking at the Region pointer connected to the terminal type.
However, nodes are output by working through the node list and
devices are output by working through the device list, so the
information is readily available.
Currently the "to-do" work starts at ExtBasic.c line 313.
Pulled out the central part of ResCheckSimNodes() into "ResProcessNode()".
This allows ExtBasic.c to loop over its own nodeList, prepare the
ResSimNode structure, and then call ResProcessNode() to process a
single network.
These are all then bypassed:
CmdExtResist()
ExtResisForDef()
extResPrepSubstrate()
ResReadSim()
ResCheckPorts()
ResCheckSimNodes()
(clean up ResNodeTable and ResRDevList)
The only one of the bypassed routines which may need to be handled
is "ResCheckPorts()". Possibly the ExtSubtree/ExtHier stuff can be
used in place? The main issue is figuring out where the ports are.
Unlike the original "extresist" method, it should be possible to do
this properly. Punt on it for the moment.
Try to compile with the separated-out ResProcessNode() routine.
Okay, got that working.
Now, the hard work: Prepare an extresist node record from ExtBasic.c.
Don't worry about how ClientData records are managed, for now.
Now, what does ResCleanUpEverything() do? We don't want to destroy
the records until the end. Maybe node regions should be replaced by
subnets here? And the resistors can be kept in a linked list or
hash table to be output along with the devices.
The issue here is that some tiles can contain multiple sub-nets,
if there are wires branching off. Note, however, that wires can
only branch off of a single rectangle in the north or south directions,
due to the "maximum horizontal stripes" rule. What if subnets within
a tile were handled by splitting the tile, which would only be done
horizontally, and keeping the tiles separate by setting a flag in
ti_body such that a long wire with many branches would be split into
multiple segments with the flag set to 1010101. . ., which allows the
normal tile handling to work. The flags can be cleaned up afterward
and the tiles merged (maybe just erase and repaint the tile)? Note
that this would never happen with split tiles. Probably okay with
contacts?
Due to the maximum horizontal stripes consideration, does
ResCalcNorthSouth() *ever* get called??
Okay, a long vertical contact with metal branches on either side
would invalidate my assumption, and yes, ResCalcNorthSouth() would
be called.

View File

@ -931,6 +931,7 @@ cmdExpandFunc(
#define DOLABELCHECK 7
#define DOALIASES 8
#define DOUNIQUE 9
#define DOEXTRESIST 10
#define LENCLEAR 0
#define LENDRIVER 1
@ -975,10 +976,11 @@ CmdExtract(
"coupling extract coupling capacitance",
"length compute driver-receiver pathlengths",
"local put all generated files in the current directory",
"resistance estimate resistance",
"lumped estimate lumped resistance",
"labelcheck check for connections through sticky labels",
"aliases output all net name aliases",
"unique ensure unique node names during extraction",
"resistance extract resistance",
NULL
};
static const char * const cmdExtLength[] =
@ -1280,10 +1282,11 @@ CmdExtract(
TxPrintf("%s capacitance\n", OPTSET(EXT_DOCAPACITANCE));
TxPrintf("%s coupling\n", OPTSET(EXT_DOCOUPLING));
TxPrintf("%s length\n", OPTSET(EXT_DOLENGTH));
TxPrintf("%s resistance\n", OPTSET(EXT_DORESISTANCE));
TxPrintf("%s lumped R\n", OPTSET(EXT_DORESISTANCE));
TxPrintf("%s label check\n", OPTSET(EXT_DOLABELCHECK));
TxPrintf("%s aliases\n", OPTSET(EXT_DOALIASES));
TxPrintf("%s unique\n", OPTSET(EXT_DOUNIQUE));
TxPrintf("%s resistance\n", OPTSET(EXT_DOEXTRESIST));
return;
#undef OPTSET
}
@ -1314,6 +1317,7 @@ CmdExtract(
case DOLABELCHECK: option = EXT_DOLABELCHECK; break;
case DOALIASES: option = EXT_DOALIASES; break;
case DOUNIQUE: option = EXT_DOUNIQUE; break;
case DOEXTRESIST: option = EXT_DOEXTRESIST; break;
case DOLOCAL:
/* "extract do local" and "extract no local" are kept for
* backwards compatibility, but now effectively implement

View File

@ -301,6 +301,28 @@ extBasic(def, outFile)
if (!SigInterruptPending && (ExtDoWarn & EXTWARN_DUP) && !isabstract)
extFindDuplicateLabels(def, nodeList);
/*
* If full R-C extraction is requested, then run it now. This
* code was previously run as the "extresist" command, but it
* makes more sense to be integral to the extraction process.
* It must be run prior to extFindCoupling() so that coupling
* capacitances are correctly assigned to subnets.
*/
if (!SigInterruptPending && (ExtOptions & EXT_DOEXTRESIST))
{
ResSimNode *rsimnode;
ResisData resisdata;
int n_ext = 0, n_out = 0;
for (reg = nodeList; reg && !SigInterruptPending; reg = reg->nreg_next)
{
rsimnode = ResCreateNode();
ResProcessNode(rsimnode, def, &resisdata, &n_ext, &n_out);
/* To be completed */
}
}
/*
* Build up table of coupling capacitances (overlap, sidewall).
* This comes before extOutputNodes because we may have to adjust

View File

@ -68,13 +68,14 @@ extern const char * const extDevTable[];
#define EXT_DOADJUST 0x001 /* Extract hierarchical adjustments */
#define EXT_DOCAPACITANCE 0x002 /* Extract capacitance */
#define EXT_DOCOUPLING 0x004 /* Extract coupling capacitance */
#define EXT_DORESISTANCE 0x008 /* Extract resistance */
#define EXT_DORESISTANCE 0x008 /* Extract lumped resistance */
#define EXT_DOLENGTH 0x010 /* Extract pathlengths */
#define EXT_DOFRINGEHALO 0x020 /* Distributed fringe capacitance */
#define EXT_DOALL 0x03f /* ALL OF THE ABOVE */
#define EXT_DOLABELCHECK 0x040 /* Check for connections by label */
#define EXT_DOALIASES 0x080 /* Output all node aliases */
#define EXT_DOUNIQUE 0x100 /* Force unique nodes during extraction */
#define EXT_DOEXTRESIST 0x200 /* Do full R-C extraction */
extern int ExtOptions; /* Bitmask of above */
extern char *ExtLocalPath; /* If non-NULL, location to write .ext files */

View File

@ -209,6 +209,7 @@ typedef struct nreg
* in X, then in Y.
*/
LabelList *nreg_labels; /* See LabRegion for description */
ClientData nreg_subnet; /* Subnet record generated by extresist */
CapValue nreg_cap; /* Capacitance to ground */
ResValue nreg_resist; /* Resistance estimate */
PerimArea nreg_pa[1]; /* Dummy; each node actually has

View File

@ -966,6 +966,223 @@ ResCheckPorts(cellDef)
return result;
}
/*
*-------------------------------------------------------------------------
*
* ResCreateNode ---
*
* Create a node structure for extresist for the method where
* ResProcessNode() is called from extBasic() as part of the
* "extract" command.
*
* Results:
* Returns the node structure to be passed to ResProcessNode().
*
* Side effects:
* Memory is allocated for the node structure.
*
*-------------------------------------------------------------------------
*/
ResSimNode *ResCreateNode()
{
ResSimNode *node;
/* To be completed */
return node;
}
/*
*-------------------------------------------------------------------------
*
* ResProcessNode ---
*
* Do resistance extraction for a single network.
*
* Results:
* 1 if node was processed, 0 otherwise.
*
* Side effects:
* Many. Update the totals for number of nets extracted and
* number of nets output in the pointers in the argument list.
*
*-------------------------------------------------------------------------
*/
int
ResProcessNode(
ResSimNode *node, /* Node record for network */
CellDef *celldef, /* Cell def being processed */
ResisData *resisdata, /* Extraction parameters kept here */
char *outfile, /* Name of output file */
int *num_extracted, /* Number of nets extracted so far */
int *num_output) /* Number of nets output so far */
{
HashEntry *he;
devPtr *ptr;
float ftolerance, minRes, cumRes;
float rctol = resisdata->tdiTolerance;
float rthresh = resisdata->rthresh;
int nidx = 1, eidx = 1; /* node & segment counters for geom. */
/* Ignore or include specified nodes */
if (ResIncludeTable.ht_nEntries > 0)
{
he = HashLookOnly(&ResIncludeTable, node->name);
if (he == NULL) return 0;
}
else
{
he = HashLookOnly(&ResIgnoreTable, node->name);
if (he != NULL) return 0;
}
/* Has this node been merged away or is it marked as skipped? */
/* If so, skip it */
if ((node->status & (FORWARD | REDUNDANT)) ||
((node->status & SKIP) &&
(ResOptionsFlags & ResOpt_ExtractAll) == 0))
return 0;
ResCurrentNode = node->name;
ResSortByGate(&node->firstDev);
/* Find largest SD device connected to node. */
minRes = FLT_MAX;
gparams.rg_devloc = (Point *) NULL;
gparams.rg_status = FALSE;
gparams.rg_nodecap = node->capacitance;
/* The following is only used if there is a drivepoint */
/* to identify which tile the drivepoint is on. */
gparams.rg_ttype = node->rs_ttype;
for (ptr = node->firstDev; ptr != NULL; ptr = ptr->nextDev)
{
RDev *t1;
RDev *t2;
if (ptr->terminal == GATE)
{
break;
}
else
{
/* Get cumulative resistance of all devices */
/* with same connections. */
cumRes = ptr->thisDev->resistance;
t1 = ptr->thisDev;
for (; ptr->nextDev != NULL; ptr = ptr->nextDev)
{
t1 = ptr->thisDev;
t2 = ptr->nextDev->thisDev;
if (t1->gate != t2->gate) break;
if ((t1->source != t2->source || t1->drain != t2->drain) &&
(t1->source != t2->drain || t1->drain != t2->source))
break;
/* Do parallel combination */
if ((cumRes != 0.0) && (t2->resistance != 0.0))
cumRes = (cumRes * t2->resistance) / (cumRes + t2->resistance);
else
cumRes = 0;
}
if (minRes > cumRes)
{
minRes = cumRes;
gparams.rg_devloc = &t1->location;
gparams.rg_ttype = t1->rs_ttype;
}
}
}
/* special handling for FORCE and DRIVELOC labels: */
/* set minRes = node->minsizeres if it exists, 0 otherwise */
if (node->status & (FORCE|DRIVELOC))
{
if (node->status & MINSIZE)
{
minRes = node->minsizeres;
}
else
{
minRes = 0;
}
if (node->status & DRIVELOC)
{
gparams.rg_devloc = &node->drivepoint;
gparams.rg_status |= DRIVEONLY;
}
if (node->status & PORTNODE)
{
/* The node is a port, not a device, so make */
/* sure rg_ttype is set accordingly. */
gparams.rg_ttype = node->rs_ttype;
}
}
if ((gparams.rg_devloc == NULL) && (node->status & FORCE))
{
TxError("Node %s has force label but no drive point or "
"driving device\n", node->name);
}
if ((minRes == FLT_MAX) || (gparams.rg_devloc == NULL))
return 1;
gparams.rg_bigdevres = (int)minRes * OHMSTOMILLIOHMS;
if (minRes > resisdata->rthresh)
ftolerance = minRes;
else
ftolerance = resisdata->rthresh;
/*
* Is the device resistance greater than the lumped node
* resistance? If so, extract net.
*/
if ((node->resistance > ftolerance) || (node->status & FORCE) ||
(ResOpt_ExtractAll & ResOptionsFlags))
{
ResFixPoint fp;
(*num_extracted)++;
if (ResExtractNet(node, &gparams, outfile) != 0)
{
/* On error, don't output this net, but keep going */
if (node->type == TT_SPACE)
TxPrintf("Note: Substrate node %s not extracted as network.\n",
node->name);
else
TxError("Error in extracting node %s\n", node->name);
}
else
{
ResDoSimplify(ftolerance, rctol, &gparams);
if (ResOptionsFlags & ResOpt_DoLumpFile)
ResWriteLumpFile(node);
if (gparams.rg_maxres >= ftolerance ||
(ResOptionsFlags & ResOpt_ExtractAll))
{
resNodeNum = 0;
(*num_output) += ResWriteExtFile(celldef, node, rctol, &nidx, &eidx);
}
}
#ifdef PARANOID
ResSanityChecks(node->name, ResResList, ResNodeList, ResDevList);
#endif
ResCleanUpEverything();
}
return 1;
}
/*
*-------------------------------------------------------------------------
*
@ -988,15 +1205,10 @@ ResCheckSimNodes(celldef, resisdata)
ResisData *resisdata;
{
ResSimNode *node;
devPtr *ptr;
float ftolerance, minRes, cumRes;
int failed1=0;
int failed3=0;
int total =0;
int numext = 0; /* Number of nets extracted */
int numout = 0; /* Number of nets output */
int total = 0; /* Total number of nets processed */
char *outfile = celldef->cd_name;
float rctol = resisdata->tdiTolerance;
float rthresh = resisdata->rthresh;
int nidx = 1, eidx = 1; /* node & segment counters for geom. */
if (ResOptionsFlags & ResOpt_DoExtFile)
{
@ -1005,8 +1217,8 @@ ResCheckSimNodes(celldef, resisdata)
if (strcmp(ExtLocalPath, "."))
{
char *namebuf;
namebuf = alloc = mallocMagic(strlen(ExtLocalPath) + strlen(celldef->cd_name)
+ 2);
namebuf = alloc = mallocMagic(strlen(ExtLocalPath) +
strlen(celldef->cd_name) + 2);
sprintf(namebuf, "%s/%s", ExtLocalPath, celldef->cd_name);
outfile = namebuf;
}
@ -1015,17 +1227,13 @@ ResCheckSimNodes(celldef, resisdata)
outfile = celldef->cd_name;
}
else
{
ResExtFile = NULL;
}
if (ResOptionsFlags & ResOpt_DoLumpFile)
{
ResLumpFile = PaOpen(outfile, "w", ".res.lump", ".", (char *)NULL, (char **)NULL);
}
else
{
ResLumpFile = NULL;
}
if (ResOptionsFlags & ResOpt_FastHenry)
{
char *geofilename;
@ -1034,9 +1242,7 @@ ResCheckSimNodes(celldef, resisdata)
ResPortIndex = 0;
}
else
{
ResFHFile = NULL;
}
if ((ResExtFile == NULL && (ResOptionsFlags & ResOpt_DoExtFile))
|| ((ResOptionsFlags & ResOpt_DoLumpFile) && ResLumpFile == NULL)
@ -1052,191 +1258,23 @@ ResCheckSimNodes(celldef, resisdata)
*/
if (ResExtFile != NULL)
{
fprintf(ResExtFile, "scale %d %d %g\n",
ExtCurStyle->exts_resistScale,
ExtCurStyle->exts_capScale,
ExtCurStyle->exts_unitsPerLambda);
}
/*
* Write reference plane (substrate) definition and end statement
* to the FastHenry geometry file.
*/
if (ResOptionsFlags & ResOpt_FastHenry)
{
ResPrintReference(ResFHFile, ResRDevList, celldef);
}
for (node = ResOriginalNodes; node != NULL; node=node->nextnode)
{
HashEntry *he;
if (SigInterruptPending) break;
/* Ignore or include specified nodes */
if (ResIncludeTable.ht_nEntries > 0)
{
he = HashLookOnly(&ResIncludeTable, node->name);
if (he == NULL) continue;
}
else
{
he = HashLookOnly(&ResIgnoreTable, node->name);
if (he != NULL) continue;
}
/* Has this node been merged away or is it marked as skipped? */
/* If so, skip it */
if ((node->status & (FORWARD | REDUNDANT)) ||
((node->status & SKIP) &&
(ResOptionsFlags & ResOpt_ExtractAll) == 0))
continue;
ResCurrentNode = node->name;
total++;
ResSortByGate(&node->firstDev);
/* Find largest SD device connected to node. */
minRes = FLT_MAX;
gparams.rg_devloc = (Point *) NULL;
gparams.rg_status = FALSE;
gparams.rg_nodecap = node->capacitance;
/* The following is only used if there is a drivepoint */
/* to identify which tile the drivepoint is on. */
gparams.rg_ttype = node->rs_ttype;
for (ptr = node->firstDev; ptr != NULL; ptr = ptr->nextDev)
{
RDev *t1;
RDev *t2;
if (ptr->terminal == GATE)
{
break;
}
else
{
/* Get cumulative resistance of all devices */
/* with same connections. */
cumRes = ptr->thisDev->resistance;
t1 = ptr->thisDev;
for (; ptr->nextDev != NULL; ptr = ptr->nextDev)
{
t1 = ptr->thisDev;
t2 = ptr->nextDev->thisDev;
if (t1->gate != t2->gate) break;
if ((t1->source != t2->source ||
t1->drain != t2->drain) &&
(t1->source != t2->drain ||
t1->drain != t2->source)) break;
/* Do parallel combination */
if ((cumRes != 0.0) && (t2->resistance != 0.0))
{
cumRes = (cumRes * t2->resistance) /
(cumRes + t2->resistance);
}
else
{
cumRes = 0;
}
}
if (minRes > cumRes)
{
minRes = cumRes;
gparams.rg_devloc = &t1->location;
gparams.rg_ttype = t1->rs_ttype;
}
}
}
/* special handling for FORCE and DRIVELOC labels: */
/* set minRes = node->minsizeres if it exists, 0 otherwise */
if (node->status & (FORCE|DRIVELOC))
{
if (node->status & MINSIZE)
{
minRes = node->minsizeres;
}
else
{
minRes = 0;
}
if (node->status & DRIVELOC)
{
gparams.rg_devloc = &node->drivepoint;
gparams.rg_status |= DRIVEONLY;
}
if (node->status & PORTNODE)
{
/* The node is a port, not a device, so make */
/* sure rg_ttype is set accordingly. */
gparams.rg_ttype = node->rs_ttype;
}
}
if ((gparams.rg_devloc == NULL) && (node->status & FORCE))
{
TxError("Node %s has force label but no drive point or "
"driving device\n", node->name);
}
if ((minRes == FLT_MAX) || (gparams.rg_devloc == NULL))
{
continue;
}
gparams.rg_bigdevres = (int)minRes * OHMSTOMILLIOHMS;
if (minRes > resisdata->rthresh)
ftolerance = minRes;
else
ftolerance = resisdata->rthresh;
/*
* Is the device resistance greater than the lumped node
* resistance? If so, extract net.
*/
if ((node->resistance > ftolerance) || (node->status & FORCE) ||
(ResOpt_ExtractAll & ResOptionsFlags))
{
ResFixPoint fp;
failed1++;
if (ResExtractNet(node, &gparams, outfile) != 0)
{
/* On error, don't output this net, but keep going */
if (node->type == TT_SPACE)
TxPrintf("Note: Substrate node %s not extracted as network.\n",
node->name);
else
TxError("Error in extracting node %s\n", node->name);
}
else
{
ResDoSimplify(ftolerance, rctol, &gparams);
if (ResOptionsFlags & ResOpt_DoLumpFile)
{
ResWriteLumpFile(node);
}
if (gparams.rg_maxres >= ftolerance ||
(ResOptionsFlags & ResOpt_ExtractAll))
{
resNodeNum = 0;
failed3 += ResWriteExtFile(celldef, node, rctol,
&nidx, &eidx);
}
}
#ifdef PARANOID
ResSanityChecks(node->name, ResResList, ResNodeList, ResDevList);
#endif
ResCleanUpEverything();
}
total += ResProcessNode(node, celldef, resisdata, outfile,
&numext, &numout);
}
/*
@ -1245,9 +1283,7 @@ ResCheckSimNodes(celldef, resisdata)
*/
if (ResOptionsFlags & ResOpt_DoExtFile)
{
ResPrintExtDev(ResExtFile, ResRDevList);
}
/*
* Write end statement to the FastHenry geometry file.
@ -1280,30 +1316,20 @@ ResCheckSimNodes(celldef, resisdata)
/* Output statistics about extraction */
if (total)
{
TxPrintf("Total Nets: %d\nNets extracted: "
"%d (%f)\nNets output: %d (%f)\n", total, failed1,
(float)failed1 / (float)total, failed3,
(float)failed3 / (float)total);
}
"%d (%f)\nNets output: %d (%f)\n", total, numext,
(float)numext / (float)total, numout,
(float)numout / (float)total);
else
{
TxPrintf("Total Nodes: %d\n",total);
}
/* close output files */
if (ResExtFile != NULL)
(void) fclose(ResExtFile);
if (ResLumpFile != NULL)
(void) fclose(ResLumpFile);
if (ResFHFile != NULL)
(void) fclose(ResFHFile);
if (ResExtFile != NULL) (void) fclose(ResExtFile);
if (ResLumpFile != NULL) (void) fclose(ResLumpFile);
if (ResFHFile != NULL) (void) fclose(ResFHFile);
}
/*
*-------------------------------------------------------------------------
*