Compare commits

...
41 Commits
Author SHA1 Message Date
Tim Edwards e7ae84bbc8 Merge branch 'master' into netgen-1.5 2021-06-10 03:00:09 -04:00
Tim Edwards dfabdaa367 Modified the JSON output so that missing pins which do not match
unconnected pins in the other netlist are marked as "(no pin)"
rather than "(no matching pin)".  This allows a script parsint the
JSON file (e.g., count_lvs.py from qflow) to distinguish between a
real pin error and one that can be ignored.
2021-06-09 09:34:02 -04:00
Tim Edwards c5c1306f6a Corrected a minor error where the property mismatch check outputs
an error message while doing a non-final iteration, where all
output is supposed to be suppressed.
2021-06-09 09:20:45 -04:00
Tim Edwards 43c018f3bc Merge branch 'master' into netgen-1.5 2021-06-09 03:00:10 -04:00
Tim Edwards 0614461c84 Modified MatchPins so that pin mismatches on nets that are not
connected to anything are once again ignored (the prior commit
to prevent netgen from not reporting swapped pins as an error
overcorrected).
2021-06-08 17:25:17 -04:00
Tim Edwards 6e94886b65 Messed up basic pin comparison between black-box vs. non-black-box
subcircuit situations in the last commit.  This commit fixes it.
2021-06-08 16:52:57 -04:00
Tim Edwards ca119edc0a Merge branch 'master' into netgen-1.5 2021-06-04 03:00:10 -04:00
Tim Edwards 0556abf3c1 Finally corrected the issue with pins being able to be swapped
without netgen noticing---this behavior got broken by an
exception for "black box" circuits, but failed to check if the
circuit really was marked as a "black box" or not.  Fixing that
revealed another issue with verilog implicit pins.  Both issues
have now been corrected.
2021-06-03 17:42:54 -04:00
Tim Edwards 84348d2fe1 Merge branch 'master' into netgen-1.5 2021-05-31 03:00:10 -04:00
Tim Edwards 3356dd784e Made a number of usability corrections to the python GUI for
netgen.  More work will be done later.  Also:  Removed the
derived file lvs_manager.py from the repository, and modified
the Makefile to remove it as part of "make clean".
2021-05-30 20:39:47 -04:00
Tim Edwards 813b29cdc4 Merge branch 'master' into netgen-1.5 2021-05-29 03:00:10 -04:00
Tim Edwards 00949970b4 The previous method change for sorting to accommodate capacitors and
other devices that do not have a "critical property" that determines
which properties can add together was flawed and messed up the sorting
of devices like transistors and resistors that do have a critical
property.  Reworked the sorting order so that it makes sense for both
situations.
2021-05-28 21:20:39 -04:00
Tim Edwards ccf4a0a900 Merge branch 'master' into netgen-1.5 2021-05-27 03:00:32 -04:00
Tim Edwards c83b6def5b Corrected an issue with swapped arguments to PromoteProperty().
Also corrected a superficial issue with an attempt to print out an
instance name that doesn't exist.  There is an underlying bug here
related to cells that have no pins and/or no contents which has not
been debugged.  The fix just keeps netgen from segfaulting.
2021-05-26 12:46:57 -04:00
Tim Edwards e34166fdb9 Merge branch 'master' into netgen-1.5 2021-05-26 03:00:32 -04:00
Tim Edwards 8fc2c097d2 Added one modification that automatically ignores any cell that has
no pins at all.  This overrides the default behavior of treating
empty cells as "black-box" entries, and avoids attempts to compare
cells like logo artwork.
2021-05-25 12:41:57 -04:00
Tim Edwards a13491e358 Merge branch 'master' into netgen-1.5 2021-05-25 03:00:09 -04:00
Tim Edwards 2ac2a3b6f8 Fixed a bad assumption; PropertyOptimize allows devices with
different additive properties (like transistor width) to combine;
this is totally wrong and may have been left over from code written
before the routine was split into simple combinations (add similar
devices) and agressive combinations (e.g., add widths together).
2021-05-24 10:05:10 -04:00
Tim Edwards 1d05b8b676 Merge branch 'master' into netgen-1.5 2021-05-20 03:00:31 -04:00
Tim Edwards 759d63cea9 Changed a comment in the code that no longer applies after the last
modification.
2021-05-19 16:42:03 -04:00
Tim Edwards 476da015f0 Added support for expression parameters to be added to a device
class during setup, for the purpose of generating some derived
value that is used for merging and sorting, such as area = l*w.
Note that this likely needs adjusting so that the expression is
evaluated but not replaced for the purpose of sorting, since the
values to the parameter may change after parallel and series
merging.
2021-05-19 16:27:52 -04:00
Tim Edwards 4aa8a72769 Corrected an error found in ResolveAutomorphsByProperty which would
cause inexplicable output in case of a property error by showing a
netlist topography error instead of a property error (but the output
shows that the netlists match, and there is no reporting of any
property errors).  This error was discovered while implementing a
better sorting method for parallel combination.  The improved method
sorts on two properties rather than one, and so should not fall into
the error where, say, devices are sorted on W but have different L
for a device like a capacitor where no "critical" property is
specified (and other similar cases, although that is a common one).
2021-05-19 14:56:38 -04:00
Tim Edwards e97d6f1aeb Merge branch 'master' into netgen-1.5 2021-05-14 03:00:10 -04:00
Tim Edwards 47e7367c26 Updated the versio with the last commit. 2021-05-13 15:58:49 -04:00
Tim Edwards 362fb7120b Modified the behavior of the parallel combination of devices with
"similar no connect" pins so that it is not done on top-level
netlists.  This is mainly to deal with the problem where connections
that would normally be pins (but may have, for example, been
connected to a voltage source in a schematic that was deleted because
it was not a netlistable device) are treated as no-connects.
The parallel combination of devices with similar no-connects can then
differ between netlists that differ in describing transistors as
fingered vs. individual devices.  This is an obscure case, but the
output of netgen then becomes not only wrong but difficult to
understand what happened, so it is better to avoid.
2021-05-13 15:50:12 -04:00
Tim Edwards fb4759a8cd Merge branch 'master' into netgen-1.5 2021-05-04 03:00:10 -04:00
Tim Edwards 92e8fed8be Corrected an error in checking merge flags when doing sorting of
parallel and series devices.  This was a fairly major error
undermining the property sorting (the case where the same number
of devices are in parallel in both circuits and need to be sorted
by, e.g., width, prior to checking for matches).
2021-05-03 11:20:44 -04:00
Tim Edwards 45d4004d43 Merge branch 'master' into netgen-1.5 2021-05-02 03:00:09 -04:00
Tim Edwards 01b2484be3 Updated version. 2021-05-01 22:49:59 -04:00
Tim Edwards fe47d84ace Missed a corner case in the series combination code where a device
has been shuffled to the beginning of the cell, which requires
different handling.
2021-05-01 22:48:59 -04:00
Tim Edwards 0b014e6efd Merge branch 'master' into netgen-1.5 2021-03-20 03:00:09 -04:00
Tim Edwards 0a2adeb593 Modified the "flatten" command to allow class "module" to be
flattened.  This is marginally useful---since the "module" class
is a black box, it is essentially the same as using the "ignore"
command.
2021-03-19 13:57:11 -04:00
Tim Edwards 330b34139c Slight modification to print the list of cells being forced to
match for both the filename option and immediate list.
2021-03-19 10:51:42 -04:00
Tim Edwards 62d0352149 Modified the "-noflatten" command option to "lvs" so that it can be
given a filename as a value in addition to being passed a list of
cells directly.
2021-03-19 09:41:42 -04:00
Tim Edwards 9ca713dfd9 Merge branch 'master' into netgen-1.5 2021-03-18 03:00:29 -04:00
Tim Edwards b1b04b8e87 Corrected a statement related to proxy pins that can cause a
segfault condition.
2021-03-17 16:31:22 -04:00
Tim Edwards 851a1f941e Merge branch 'master' into netgen-1.5 2021-03-06 03:00:09 -05:00
Tim Edwards 6b9d92de65 Corrected some pointer-to-int and int-to-pointer conversions to
keep the compiler happy.
2021-03-05 20:19:04 -05:00
Tim Edwards d763ad0e60 First and simplest (but most effective) cut at aligning the two
sides of the output when presenting side-by-side results.  This
matches up net or device names within a partition.  Where net or
device names match, the contents are also aligned on either side.
I attempted to also do a "best match" of contents between sides,
but as this involves a lot of analyzing the contents, it is very
computationally expensive, and so the code has been disabled.  It
could be added back in as an option.  There are also various ways
to optimize it for speed.
2021-03-05 17:23:22 -05:00
Tim Edwards fd72e24a86 Corrected an error in the code that handles no-connects as valid
pins for enabling parallel combinations, which could cause a crash.
Added a "-force" option to "equate pins" to allow pins to be matched
even on subcircuits that did not correctly match;  this was done in
conjuction with an extra option to the "lvs" command "-noflatten="
to pass a list of cellname to not be flattened even if they do not
match.  This is generally discouraged, as it prevents netgen from
resolving differences between layout and schematic hierarchy, but it
can be useful for checking that the hierarchy above a certain cell
is correct, given that if a subcell is really unmatched, then its
errors will keep propagating up the top level, making additional
errors hard to diagnose.
2021-03-05 11:32:17 -05:00
Tim Edwards 20077d3d56 Modifed the handling of parallelized no-connects so that the behavior
can be turned on or off from the setup using "property parallel open"
to allow parallelizing devices with no-connect pins vs. "property
parallel connected" to only allow parallelizing of devices with all
pins connected.
2021-03-03 17:31:45 -05:00
11 changed files with 1454 additions and 1588 deletions
+1 -1
View File
@@ -1 +1 @@
1.5.170
1.5.185
+1 -1
View File
@@ -319,7 +319,7 @@ int flattenInstancesOf(char *name, int fnum, char *instance)
Printf(" Flattening instance: %s, primitive = %s\n",
ParentParams->instance.name, (ChildCell->class ==
CLASS_SUBCKT) ? "no" : "yes");
if (ChildCell->class != CLASS_SUBCKT) {
if ((ChildCell->class != CLASS_SUBCKT) && (ChildCell->class != CLASS_MODULE)) {
LastObj = ParentParams;
continue;
}
+537 -179
View File
@@ -867,10 +867,10 @@ struct FormattedList *FormatBadNodeFragment(struct Node *N)
ob = pins[i]->subelement->element->object;
for (n = pins[i]->subelement->element->nodelist; n != NULL; n = n->next){
if (n->pin_magic == pins[i]->subelement->pin_magic) {
if (permute == 0) {
if ((permute == 0) && (ob->instance.name != NULL)) {
pinname = ob->name + strlen(ob->instance.name) + 1;
}
else {
else if (ob->instance.name != NULL) {
char *pinsave = pinname;
pinname = (char *)MALLOC(strlen(pinsave) + strlen(ob->name +
strlen(ob->instance.name) + 1) + 2);
@@ -1141,6 +1141,323 @@ Tcl_Obj *ListElementClasses(int legal)
#endif
/*
*---------------------------------------------------------------------
*
* Sort the fanout lists of two formatted list entries so that they
* are as well aligned as they can be made, practically. The matching
* is ad hoc as it does not affect LVS results but only how the results
* are organized and presented in the output.
*
*---------------------------------------------------------------------
*/
void
SortFanoutLists(nlist1, nlist2)
struct FormattedList *nlist1, *nlist2;
{
struct hashdict f1hash, f2hash;
int f1, f2, total;
struct FanoutList temp;
int *matched;
char pinname[1024], pinnameA[1024], pinnameB[1024];
InitializeHashTable(&f1hash, OBJHASHSIZE);
InitializeHashTable(&f2hash, OBJHASHSIZE);
if (nlist1->fanout < nlist2->fanout) {
matched = (int *)CALLOC(nlist2->fanout, sizeof(int));
total = 0;
for (f2 = 0; f2 < nlist2->fanout; f2++) {
sprintf(pinname, "%s/%s", nlist2->flist[f2].model,
nlist2->flist[f2].name);
HashPtrInstall(pinname, (void *)((long)f2 + 1), &f2hash);
}
for (f1 = 0; f1 < nlist1->fanout; f1++) {
sprintf(pinname, "%s/%s", nlist1->flist[f1].model,
nlist1->flist[f1].name);
f2 = (int)(long)HashLookup(pinname, &f2hash);
if (f2 != 0) {
f2 -= 1;
matched[f1] = -1;
total++;
if (f2 != f1) {
temp = nlist2->flist[f2];
nlist2->flist[f2] = nlist2->flist[f1];
nlist2->flist[f1] = temp;
sprintf(pinnameA, "%s/%s", nlist2->flist[f1].model,
nlist2->flist[f1].name);
sprintf(pinnameB, "%s/%s", nlist2->flist[f2].model,
nlist2->flist[f2].name);
HashPtrInstall(pinnameA, (void *)((long)f1 + 1), &f2hash);
HashPtrInstall(pinnameB, (void *)((long)f2 + 1), &f2hash);
}
}
}
/* To do: If full pin names don't match, match by model name only */
}
else {
matched = (int *)CALLOC(nlist1->fanout, sizeof(int));
total = 0;
for (f1 = 0; f1 < nlist1->fanout; f1++) {
sprintf(pinname, "%s/%s", nlist1->flist[f1].model,
nlist1->flist[f1].name);
HashPtrInstall(pinname, (void *)((long)f1 + 1), &f1hash);
}
for (f2 = 0; f2 < nlist2->fanout; f2++) {
sprintf(pinname, "%s/%s", nlist2->flist[f2].model,
nlist2->flist[f2].name);
f1 = (int)(long)HashLookup(pinname, &f1hash);
if (f1 != 0) {
f1 -= 1;
matched[f2] = -1;
total++;
if (f1 != f2) {
temp = nlist1->flist[f1];
nlist1->flist[f1] = nlist1->flist[f2];
nlist1->flist[f2] = temp;
sprintf(pinnameA, "%s/%s", nlist1->flist[f1].model,
nlist1->flist[f1].name);
sprintf(pinnameB, "%s/%s", nlist1->flist[f2].model,
nlist1->flist[f2].name);
HashPtrInstall(pinnameA, (void *)((long)f1 + 1), &f1hash);
HashPtrInstall(pinnameB, (void *)((long)f2 + 1), &f1hash);
}
}
}
/* To do: If full pin names don't match, match by model name only */
}
FREE(matched);
HashKill(&f1hash);
HashKill(&f2hash);
}
/*
*---------------------------------------------------------------------
*
* Determine the match between two entries in a bad node fragment
* according to an ad hoc metric of how many fanout entries are
* the same between the two. This is not used to match circuits
* for LVS, but is used to sort the dump of unmatched nets generated
* for an unmatched subcell, so that the end-user is not presented
* with a list in a confusingly arbitrary order.
*
* Score is normalized to 100.
* If a model/pin has an equivalent on the other size, add 1
* If a model/pin equivalent has the same count, add 1
* Total values and normalize to a 100 score for an exact match.
*
*---------------------------------------------------------------------
*/
int
NodeMatchScore(nlist1, nlist2)
struct FormattedList *nlist1, *nlist2;
{
struct hashdict f1hash, f2hash;
char pinname[1024];
int f1, f2, maxfanout;
int score = 0;
InitializeHashTable(&f1hash, OBJHASHSIZE);
InitializeHashTable(&f2hash, OBJHASHSIZE);
if (nlist1->fanout < nlist2->fanout) {
for (f2 = 0; f2 < nlist2->fanout; f2++) {
sprintf(pinname, "%s/%s", nlist2->flist[f2].model,
nlist2->flist[f2].name);
HashPtrInstall(pinname, (void *)((long)f2 + 1), &f2hash);
}
for (f1 = 0; f1 < nlist1->fanout; f1++) {
sprintf(pinname, "%s/%s", nlist1->flist[f1].model,
nlist1->flist[f1].name);
f2 = (int)(long)HashLookup(pinname, &f2hash);
if (f2 != 0) {
f2 -= 1;
score++;
if (nlist1->flist[f1].count == nlist2->flist[f2].count)
score++;
}
}
}
else {
for (f1 = 0; f1 < nlist1->fanout; f1++) {
sprintf(pinname, "%s/%s", nlist1->flist[f1].model,
nlist1->flist[f1].name);
HashPtrInstall(pinname, (void *)((long)f1 + 1), &f1hash);
}
for (f2 = 0; f2 < nlist2->fanout; f2++) {
sprintf(pinname, "%s/%s", nlist2->flist[f2].model,
nlist2->flist[f2].name);
f1 = (int)(long)HashLookup(pinname, &f1hash);
if (f1 != 0) {
f1 -= 1;
score++;
if (nlist2->flist[f2].count == nlist1->flist[f1].count)
score++;
}
}
}
HashKill(&f1hash);
HashKill(&f2hash);
maxfanout = (nlist1->fanout < nlist2->fanout) ? nlist2->fanout : nlist1->fanout;
score = (50 * score) / maxfanout;
return score;
}
/*
*---------------------------------------------------------------------
*
* Sort node list 2 to match the entries in list 1, to the extent
* possible. Exact name matching is preferred, followed by matching
* of the largest percentage of components.
*
*---------------------------------------------------------------------
*/
void SortUnmatchedLists(nlists1, nlists2, n1max, n2max)
struct FormattedList **nlists1, **nlists2;
int n1max, n2max;
{
struct FormattedList *temp;
int n1, n2;
int *matched, total, best, ibest;
struct hashdict n1hash, n2hash;
InitializeHashTable(&n1hash, OBJHASHSIZE);
InitializeHashTable(&n2hash, OBJHASHSIZE);
if (n1max < n2max) {
matched = (int *)CALLOC(n2max, sizeof(int));
total = 0;
for (n2 = 0; n2 < n2max; n2++)
HashPtrInstall(nlists2[n2]->name, (void *)((long)n2 + 1), &n2hash);
/* Match by name */
for (n1 = 0; n1 < n1max; n1++) {
n2 = (int)(long)HashLookup(nlists1[n1]->name, &n2hash);
if (n2 != 0) {
n2 -= 1;
matched[n1] = -1;
total++;
if (n2 != n1) {
temp = nlists2[n2];
nlists2[n2] = nlists2[n1];
nlists2[n1] = temp;
HashPtrInstall(nlists2[n1]->name, (void *)((long)n1 + 1), &n2hash);
HashPtrInstall(nlists2[n2]->name, (void *)((long)n2 + 1), &n2hash);
SortFanoutLists(nlists1[n1], nlists2[n1]);
}
}
}
/* For all nets that didn't match by name, match by content */
#if 0
/* This is ifdef'd out because the improvement in the presentation
* of the output is minimal, but the amount of computation is huge.
* There are numerous ways to optimize this.
*/
if (total < n1max) {
for (n1 = 0; n1 < n1max; n1++) {
if (matched[n1] != -1) {
best = 0;
ibest = -1;
for (n2 = 0; n2 < n2max; n2++) {
if (matched[n2] != -1) {
matched[n2] = NodeMatchScore(nlists1[n1], nlists2[n2]);
if (matched[n2] > best) {
best = matched[n2];
ibest = n2;
}
}
}
if (ibest >= 0) {
matched[n1] = -1;
temp = nlists2[ibest];
nlists2[ibest] = nlists2[n1];
nlists2[n1] = temp;
SortFanoutLists(nlists1[n1], nlists2[n1]);
}
}
}
}
#endif
}
else {
matched = (int *)CALLOC(n1max, sizeof(int));
total = 0;
for (n1 = 0; n1 < n1max; n1++)
HashPtrInstall(nlists1[n1]->name, (void *)((long)n1 + 1), &n1hash);
for (n2 = 0; n2 < n2max; n2++) {
n1 = (int)(long)HashLookup(nlists2[n2]->name, &n1hash);
if (n1 != 0) {
n1 -= 1;
matched[n2] = -1;
total++;
if (n1 != n2) {
temp = nlists1[n1];
nlists1[n1] = nlists1[n2];
nlists1[n2] = temp;
HashPtrInstall(nlists1[n1]->name, (void *)((long)n1 + 1), &n1hash);
HashPtrInstall(nlists1[n2]->name, (void *)((long)n2 + 1), &n1hash);
SortFanoutLists(nlists2[n2], nlists1[n2]);
}
}
}
/* For all nets that didn't match by name, match by content */
#if 0
/* This is ifdef'd out because the improvement in the presentation
* of the output is minimal, but the amount of computation is huge.
* There are numerous ways to optimize this.
*/
if (total < n2max) {
for (n2 = 0; n2 < n2max; n2++) {
if (matched[n2] != -1) {
best = 0;
ibest = -1;
for (n1 = 0; n1 < n1max; n1++) {
if (matched[n1] != -1) {
matched[n1] = NodeMatchScore(nlists2[n2], nlists1[n1]);
if (matched[n1] > best) {
best = matched[n1];
ibest = n1;
}
}
}
if (ibest >= 0) {
matched[n2] = -1;
temp = nlists1[ibest];
nlists1[ibest] = nlists1[n2];
nlists1[n2] = temp;
SortFanoutLists(nlists2[n2], nlists1[n2]);
}
}
}
}
#endif
}
FREE(matched);
HashKill(&n1hash);
HashKill(&n2hash);
}
/*
*---------------------------------------------------------------------
*---------------------------------------------------------------------
@@ -1219,8 +1536,9 @@ void FormatIllegalElementClasses()
n2++;
}
}
Fprintf(stdout, "\n");
SortUnmatchedLists(elist1, elist2, n1, n2);
Fprintf(stdout, "\n");
for (n = 0; n < ((n1 > n2) ? n1 : n2); n++) {
if (n != 0) {
for (i = 0; i < left_col_end; i++) *(ostr + i) = ' ';
@@ -1522,6 +1840,12 @@ void FormatIllegalNodeClasses()
ostr = CALLOC(right_col_end + 2, sizeof(char));
found = 0;
/*
* To do: match net names across partitions, to make it much clearer how
* two nets are mismatched, when they have been dropped into different
* partitions.
*/
for (nscan = NodeClasses; nscan != NULL; nscan = nscan->next)
if (!(nscan->legalpartition)) {
struct Node *N;
@@ -1574,8 +1898,9 @@ void FormatIllegalNodeClasses()
n2++;
}
}
Fprintf(stdout, "\n");
SortUnmatchedLists(nlists1, nlists2, n1, n2);
Fprintf(stdout, "\n");
for (n = 0; n < ((n1 > n2) ? n1 : n2); n++) {
if (n != 0) {
for (i = 0; i < left_col_end; i++) *(ostr + i) = ' ';
@@ -3733,24 +4058,33 @@ int series_optimize(struct objlist *ob1, struct nlist *tp1, int idx1,
}
typedef struct _propsort {
double value;
double value; /* Primary sorting value */
double avalue; /* Secondary sorting value */
double slop; /* Delta for accepting equality */
int idx;
unsigned char flags;
struct objlist *ob;
} propsort;
/*--------------------------------------------------------------*/
/* Property sorting routine used by qsort() */
/* Property sorting routine used by qsort(). Sorts on "value" */
/* unless the "value" for the two entries differs by less than */
/* "slop", in which case the entries are sorted on "avalue". */
/*--------------------------------------------------------------*/
static int compsort(const void *p1, const void *p2)
{
propsort *s1, *s2;
double smax;
s1 = (propsort *)p1;
s2 = (propsort *)p2;
return (s1->value > s2->value) ? 1 : 0;
smax = fmax(s1->slop, s2->slop);
if (fabs(s1->value - s2->value) <= smax)
return (s1->avalue > s2->avalue) ? 1 : 0;
else
return (s1->value > s2->value) ? 1 : 0;
}
/*--------------------------------------------------------------*/
@@ -3769,7 +4103,7 @@ void series_sort(struct objlist *ob1, struct nlist *tp1, int idx1, int run)
struct property *kl;
struct valuelist *vl, *sl;
int i, p, sval, merge_type;
double cval;
double cval, slop;
obn = ob1->next;
for (i = 0; i < idx1; i++) obn = obn->next;
@@ -3782,7 +4116,7 @@ void series_sort(struct objlist *ob1, struct nlist *tp1, int idx1, int run)
obp = obn;
sval = 1;
cval = 0.0;
cval = slop = 0.0;
for (i = 0; i < run; i++) {
merge_type = MERGE_NONE;
for (p = 0;; p++) {
@@ -3795,23 +4129,37 @@ void series_sort(struct objlist *ob1, struct nlist *tp1, int idx1, int run)
}
else {
kl = (struct property *)HashLookup(vl->key, &(tp1->propdict));
if (kl && (kl->merge & MERGE_S_CRIT)) {
if (vl->type == PROP_INTEGER)
if (kl && (kl->merge & (MERGE_S_ADD | MERGE_S_PAR))) {
if (vl->type == PROP_INTEGER) {
cval = (double)vl->value.ival;
else
slop = (double)kl->slop.ival;
}
else {
cval = vl->value.dval;
slop = kl->slop.dval;
}
merge_type = kl->merge & (MERGE_S_ADD | MERGE_S_PAR);
}
}
}
if (merge_type == MERGE_S_ADD) {
proplist[i].value = cval * (double)sval;
proplist[i].slop = slop;
proplist[i].avalue = 0;
sl->value.ival = 1;
}
else if (merge_type == MERGE_S_PAR) {
proplist[i].value = cval / (double)sval;
proplist[i].slop = slop;
proplist[i].avalue = 0;
sl->value.ival = 1;
}
else {
/* Components which declare no series addition method stay unsorted */
proplist[i].value = (double)0;
proplist[i].avalue = (double)0;
proplist[i].slop = (double)1E-6;
}
proplist[i].idx = i;
proplist[i].ob = obp;
obp = obp->next;
@@ -3906,120 +4254,119 @@ void parallel_sort(struct objlist *ob1, struct nlist *tp1, int idx1, int run)
struct objlist *obn, *obp;
propsort *proplist;
struct property *kl;
struct valuelist *vl, *ml;
int i, p, mval, merge_type, has_crit = FALSE;
char *subs_crit = NULL;
double cval;
struct valuelist *vl;
int i, p, mval, merge_type;
int has_crit;
char ca, co;
double tval, tslop;
double aval, pval, oval, aslop, pslop;
obn = ob1->next;
for (i = 0; i < idx1; i++) obn = obn->next;
// Create a structure of length (run) to hold critical property
// value and index. Then sort that list, then use the sorted
// indexes to sort the actual property linked list.
// If there is no critical property listed, then it is still better
// to sort on any random property than on no properties. Note that
// this can (and should!) be made better by sorting on *all*
// properties, not just the first. Otherwise, circuit 1 can have, e.g.,
// parallel transistors with W=1, L=1 and W=1, L=2 while circuit two
// has W=1, L=2 and W=1, L=1 and property matching will fail because
// sorting was done on W only.
/* Create a structure of length (run) to hold critical property
* value and index. Then sort that list, then use the sorted
* indexes to sort the actual property linked list.
*
* Collect properties of interest that we can potentially sort on.
* That includes the M value, critical parameter, additive value(s),
* and finally any parameter. Keep these values and their slop.
* If there is a critical value, then sort on additive values * M.
* If not, then sort on M and additive values.
*/
proplist = (propsort *)MALLOC(run * sizeof(propsort));
obp = obn;
mval = 1;
cval = 0.0;
pval = aval = oval = 0.0;
for (i = 0; i < run; i++) {
has_crit = FALSE;
merge_type = MERGE_NONE;
ml = NULL;
ca = co = (char)0;
for (p = 0;; p++) {
vl = &(obp->instance.props[p]);
if (vl->type == PROP_ENDLIST) break;
if (vl->key == NULL) continue;
if ((*matchfunc)(vl->key, "M")) {
mval = vl->value.ival;
ml = vl;
continue;
}
kl = (struct property *)HashLookup(vl->key, &(tp1->propdict));
if (kl == NULL) continue; /* Ignored property */
if (kl->merge & MERGE_P_CRIT) {
has_crit = TRUE;
if ((vl->type == PROP_STRING || vl->type == PROP_EXPRESSION) &&
/* Get the property value and slop. Promote if needed. Save */
/* property and slop as type double so they can be sorted. */
if ((vl->type == PROP_STRING || vl->type == PROP_EXPRESSION) &&
(kl->type != vl->type))
PromoteProperty(kl, vl);
if (vl->type == PROP_INTEGER)
cval = (double)vl->value.ival;
else if (vl->type == PROP_STRING)
/* This is unlikely---no method to merge string properties! */
cval = (double)vl->value.string[0]
PromoteProperty(kl, vl, obp, tp1);
if (vl->type == PROP_INTEGER) {
tval = (double)vl->value.ival;
tslop = (double)kl->slop.ival;
}
else if (vl->type == PROP_STRING) {
/* This is unlikely---no method to merge string properties! */
tval = (double)vl->value.string[0]
+ (double)vl->value.string[1] / 10.0;
else
cval = vl->value.dval;
merge_type = kl->merge & (MERGE_P_ADD | MERGE_P_PAR);
tslop = (double)0;
}
else {
tval = vl->value.dval;
tslop = kl->slop.dval;
}
if (kl->merge & MERGE_P_CRIT) {
has_crit = TRUE;
pval = tval;
pslop = tslop;
}
else if (kl->merge & (MERGE_P_ADD | MERGE_P_PAR)) {
if ((ca == (char)0) || (toupper(vl->key[0]) > ca)) {
merge_type = kl->merge & (MERGE_P_ADD | MERGE_P_PAR);
aval = tval;
aslop = tslop;
ca = toupper(vl->key[0]);
}
}
else if ((co == (char)0) || (toupper(vl->key[0]) > co)) {
oval = tval;
co = toupper(vl->key[0]);
}
}
if (merge_type == MERGE_P_ADD) {
proplist[i].value = cval * (double)mval;
if (ml) ml->value.ival = 1;
if (has_crit == TRUE) {
/* If there is a critical value, then sort first */
/* by critical value */
proplist[i].value = pval;
proplist[i].slop = pslop;
/* then sort on additive value times M */
/* or on non-additive value. */
if (merge_type == MERGE_P_ADD)
proplist[i].avalue = aval * (double)mval;
else if (merge_type == MERGE_P_PAR)
proplist[i].avalue = aval / (double)mval;
else
proplist[i].avalue = (double)mval;
}
else if (merge_type == MERGE_P_PAR) {
proplist[i].value = cval / (double)mval;
if (ml) ml->value.ival = 1;
else {
if (merge_type != MERGE_NONE) {
proplist[i].value = aval;
proplist[i].slop = aslop;
proplist[i].avalue = (double)mval;
}
else {
proplist[i].value = (double)mval;
proplist[i].slop = (double)0;
proplist[i].avalue = oval;
}
}
proplist[i].idx = i;
proplist[i].ob = obp;
obp = obp->next;
}
if (has_crit == FALSE) {
/* If no critical property was specified, then choose the first one found */
/* and recalculate all the proplist values. */
mval = 1;
obp = obn;
ml = NULL;
merge_type = MERGE_NONE;
for (i = 0; i < run; i++) {
for (p = 0;; p++) {
vl = &(obp->instance.props[p]);
if (vl->type == PROP_ENDLIST) break;
if (vl->key == NULL) continue;
if ((*matchfunc)(vl->key, "M")) {
mval = vl->value.ival;
ml = vl;
}
kl = (struct property *)HashLookup(vl->key, &(tp1->propdict));
if (kl == NULL) continue; /* Ignored property */
if (subs_crit == NULL)
subs_crit = vl->key;
if ((subs_crit != NULL) && (*matchfunc)(vl->key, subs_crit)) {
if ((vl->type == PROP_STRING || vl->type == PROP_EXPRESSION) &&
(kl->type != vl->type))
PromoteProperty(kl, vl);
if (vl->type == PROP_INTEGER)
cval = (double)vl->value.ival;
else if (vl->type == PROP_STRING)
/* In case property is non-numeric, sort by dictionary order */
cval = (double)vl->value.string[0]
+ (double)vl->value.string[1] / 10.0;
else
cval = vl->value.dval;
merge_type = kl->merge & (MERGE_P_ADD | MERGE_P_PAR);
}
}
if (merge_type == MERGE_P_ADD) {
proplist[i].value = cval * (double)mval;
if (ml) ml->value.ival = 1;
}
else if (merge_type == MERGE_P_PAR) {
proplist[i].value = cval / (double)mval;
if (ml) ml->value.ival = 1;
}
obp = obp->next;
}
}
obn = obp; /* Link from last property */
qsort(&proplist[0], run, sizeof(propsort), compsort);
@@ -4483,12 +4830,12 @@ int PropertyOptimize(struct objlist *ob, struct nlist *tp, int run, int series,
else if (vl == NULL || vl2 == NULL) {
if (vl == NULL) {
if (kl->type != vlist[p][j]->type)
PromoteProperty(kl, vl2);
PromoteProperty(kl, vl2, ob2, tp);
vl = &dfltvl;
}
else {
if (kl->type != vlist[p][i]->type)
PromoteProperty(kl, vl);
PromoteProperty(kl, vl, ob2, tp);
vl2 = &dfltvl;
}
dfltvl.type = kl->type;
@@ -4509,22 +4856,6 @@ int PropertyOptimize(struct objlist *ob, struct nlist *tp, int run, int series,
}
}
// Additive properties do not need to be matched, since
// they can be combined. Critical paroperties must be
// matched. Properties with no merge behavior must match.
ctype = clist[p][i];
if (!(ctype & MERGE_S_CRIT)) {
if ((series == TRUE) && (ctype & (MERGE_S_ADD | MERGE_S_PAR))) {
pmatch++;
continue;
}
if ((series == FALSE) && (ctype & (MERGE_P_ADD | MERGE_P_PAR))) {
pmatch++;
continue;
}
}
switch(vl->type) {
case PROP_DOUBLE:
case PROP_VALUE:
@@ -4963,8 +5294,8 @@ PropertyCheckMismatch(struct objlist *tp1, struct nlist *tc1,
}
/* Promote properties as necessary to make sure they all match */
if (kl1->type != vl1->type) PromoteProperty(kl1, vl1);
if (kl2->type != vl2->type) PromoteProperty(kl2, vl2);
if (kl1->type != vl1->type) PromoteProperty(kl1, vl1, tp1, tc1);
if (kl2->type != vl2->type) PromoteProperty(kl2, vl2, tp2, tc2);
/* If kl1 and kl2 types differ, choose one type to target. Prefer */
/* double if either type is double, otherwise string. */
@@ -4984,8 +5315,8 @@ PropertyCheckMismatch(struct objlist *tp1, struct nlist *tc1,
else
klt = kl1;
if (vl2->type != klt->type) PromoteProperty(klt, vl2);
if (vl1->type != klt->type) PromoteProperty(klt, vl1);
if (vl2->type != klt->type) PromoteProperty(klt, vl2, tp2, tc2);
if (vl1->type != klt->type) PromoteProperty(klt, vl1, tp1, tc1);
if (vl1->type != vl2->type) {
if (do_print && (vl1->type != vl2->type)) {
@@ -5360,16 +5691,24 @@ PropertyMatch(struct objlist *ob1, int file1,
#endif
}
/* WIP---Check for no-connect pins in merged devices on both sides. */
/* Check for no-connect pins in merged devices on both sides. */
/* Both sides should either have no-connects marked, or neither. */
/* (Permutable pins may need to be handled correctly. . . */
for (tp1 = ob1, tp2 = ob2; (tp1 != NULL) && tp1->type >= FIRSTPIN &&
(tp2 != NULL) && tp2->type >= FIRSTPIN; tp1 = tp1->next, tp2 = tp2->next)
{
struct objlist *node1, *node2;
node1 = Circuit1->nodename_cache[tp1->node];
node2 = Circuit2->nodename_cache[tp2->node];
if (file1 == Circuit1->file)
node1 = Circuit1->nodename_cache[tp1->node];
else
node1 = Circuit2->nodename_cache[tp1->node];
if (file2 == Circuit1->file)
node2 = Circuit1->nodename_cache[tp2->node];
else
node2 = Circuit2->nodename_cache[tp2->node];
if (node1->instance.flags != node2->instance.flags)
{
@@ -5407,10 +5746,12 @@ PropertyMatch(struct objlist *ob1, int file1,
if ((t1type != PROPERTY) && (checked_one == TRUE)) {
// t2 has more property records than t1, and they did not get
// merged equally by PropertySortAndCombine().
Fprintf(stdout, "Circuit 1 parallel/series network does not match"
if (do_print) {
Fprintf(stdout, "Circuit 1 parallel/series network does not match"
" Circuit 2\n");
DumpNetwork(ob1, 1);
DumpNetwork(ob2, 2);
DumpNetwork(ob1, 1);
DumpNetwork(ob2, 2);
}
mismatches++;
}
else if (t1type != PROPERTY) {
@@ -5863,6 +6204,7 @@ int ResolveAutomorphsByProperty()
if ((E2->graph != E1->graph) && (E2->hashval == newhash)) {
E2->hashval = orighash;
C2--;
if (C2 == C1) break;
}
}
}
@@ -5871,6 +6213,7 @@ int ResolveAutomorphsByProperty()
if ((E2->graph == E1->graph) && (E2->hashval == newhash)) {
E2->hashval = orighash;
C1--;
if (C1 == C2) break;
}
}
}
@@ -6580,7 +6923,7 @@ struct nlist *addproxies(struct hashlist *p, void *clientdata)
/* But if the target cell instance has proxy(no pins), then reuse
* the record and modify it.
*/
if (!strcmp(ob->name, "proxy(no pins)")) {
if (ob && !strcmp(ob->name, "proxy(no pins)")) {
obn = ob;
FREE(ob->name);
obn->name = (char *)MALLOC(strlen(ob->instance.name)
@@ -6899,71 +7242,75 @@ int MatchPins(struct nlist *tc1, struct nlist *tc2, int dolist)
}
}
/* Do any unmatched pins have the same name? */
/* (This should not happen if unconnected pins are eliminated) */
/* Do any unmatched pins have the same name? */
/* This should not happen if unconnected pins are eliminated */
/* so apply only to black-box (CELL_PLACEHOLDER) entries. */
/* (Semi-hack: Allow "!" global flag) */
ob1 = tc1->cell;
bangptr1 = strrchr(ob1->name, '!');
if (bangptr1 && (*(bangptr1 + 1) == '\0'))
*bangptr1 = '\0';
else bangptr1 = NULL;
if (((tc1->flags & CELL_PLACEHOLDER) && (tc2->flags & CELL_PLACEHOLDER)) ||
(NodeClasses == NULL)) {
ob1 = tc1->cell;
bangptr1 = strrchr(ob1->name, '!');
if (bangptr1 && (*(bangptr1 + 1) == '\0'))
*bangptr1 = '\0';
else bangptr1 = NULL;
for (i = 0; i < numorig; i++) {
if (*(cover + i) == (char)0) {
j = 0;
for (ob2 = tc2->cell; ob2 != NULL; ob2 = ob2->next) {
char *name1, *name2;
for (i = 0; i < numorig; i++) {
if (*(cover + i) == (char)0) {
j = 0;
for (ob2 = tc2->cell; ob2 != NULL; ob2 = ob2->next) {
char *name1, *name2;
if (!IsPort(ob2)) break;
if (!IsPort(ob2)) break;
bangptr2 = strrchr(ob2->name, '!');
if (bangptr2 && (*(bangptr2 + 1) == '\0'))
*bangptr2 = '\0';
else bangptr2 = NULL;
bangptr2 = strrchr(ob2->name, '!');
if (bangptr2 && (*(bangptr2 + 1) == '\0'))
*bangptr2 = '\0';
else bangptr2 = NULL;
name1 = ob1->name;
name2 = ob2->name;
name1 = ob1->name;
name2 = ob2->name;
/* Recognize proxy pins as matching */
if (!strncmp(name1, "proxy", 5)) name1 +=5;
if (!strncmp(name2, "proxy", 5)) name2 +=5;
/* Recognize proxy pins as matching */
if (!strncmp(name1, "proxy", 5)) name1 +=5;
if (!strncmp(name2, "proxy", 5)) name2 +=5;
if ((*matchfunc)(name1, name2)) {
ob2->model.port = i; /* save order */
*(cover + i) = (char)1;
if ((*matchfunc)(name1, name2)) {
ob2->model.port = i; /* save order */
*(cover + i) = (char)1;
if (Debug == 0) {
for (m = 0; m < left_col_end; m++) *(ostr + m) = ' ';
for (m = left_col_end + 1; m < right_col_end; m++) *(ostr + m) = ' ';
snprintf(ostr, left_col_end, "%s", ob1->name);
snprintf(ostr + left_col_end + 1, left_col_end, "%s", ob2->name);
for (m = 0; m < right_col_end + 1; m++)
if (*(ostr + m) == '\0') *(ostr + m) = ' ';
Fprintf(stdout, ostr);
}
else {
Fprintf(stdout, "Circuit %s port %d \"%s\""
if (Debug == 0) {
for (m = 0; m < left_col_end; m++) *(ostr + m) = ' ';
for (m = left_col_end + 1; m < right_col_end; m++) *(ostr + m) = ' ';
snprintf(ostr, left_col_end, "%s", ob1->name);
snprintf(ostr + left_col_end + 1, left_col_end, "%s", ob2->name);
for (m = 0; m < right_col_end + 1; m++)
if (*(ostr + m) == '\0') *(ostr + m) = ' ';
Fprintf(stdout, ostr);
}
else {
Fprintf(stdout, "Circuit %s port %d \"%s\""
" = cell %s port %d \"%s\"\n",
tc1->name, i, ob1->name,
tc2->name, j, ob2->name);
}
}
#ifdef TCL_NETGEN
if (dolist) {
Tcl_ListObjAppendElement(netgeninterp, plist1,
if (dolist) {
Tcl_ListObjAppendElement(netgeninterp, plist1,
Tcl_NewStringObj(ob1->name, -1));
Tcl_ListObjAppendElement(netgeninterp, plist2,
Tcl_ListObjAppendElement(netgeninterp, plist2,
Tcl_NewStringObj(ob2->name, -1));
}
}
#endif
}
if (bangptr2) *bangptr2 = '!';
j++;
}
if (bangptr2) *bangptr2 = '!';
j++;
}
}
ob1 = ob1->next;
}
ob1 = ob1->next;
if (bangptr1) *bangptr1 = '!';
}
if (bangptr1) *bangptr1 = '!';
/* Find the end of the pin list in tc1, for adding proxy pins */
@@ -6997,15 +7344,6 @@ int MatchPins(struct nlist *tc1, struct nlist *tc2, int dolist)
Fprintf(stderr, "No netlist match for cell %s pin %s\n",
tc2->name, ob2->name);
}
#ifdef TCL_NETGEN
if (dolist) {
Tcl_ListObjAppendElement(netgeninterp, plist1,
Tcl_NewStringObj("(no matching pin)", -1));
Tcl_ListObjAppendElement(netgeninterp, plist2,
Tcl_NewStringObj(ob2->name, -1));
}
#endif
result = 0;
/* Before making a proxy pin, check to see if */
/* flattening instances has left a port with a */
@@ -7022,8 +7360,28 @@ int MatchPins(struct nlist *tc1, struct nlist *tc2, int dolist)
if ((obt == NULL) && (notempty == 1)) {
ob2->node = -2; // Will run this through cleanuppins
needclean2 = 1;
#ifdef TCL_NETGEN
if (dolist) {
Tcl_ListObjAppendElement(netgeninterp, plist1,
Tcl_NewStringObj("(no pin)", -1));
Tcl_ListObjAppendElement(netgeninterp, plist2,
Tcl_NewStringObj(ob2->name, -1));
}
#endif
continue;
}
else if (notempty == 1) {
/* Flag this as an error */
result = 0;
#ifdef TCL_NETGEN
if (dolist) {
Tcl_ListObjAppendElement(netgeninterp, plist1,
Tcl_NewStringObj("(no matching pin)", -1));
Tcl_ListObjAppendElement(netgeninterp, plist2,
Tcl_NewStringObj(ob2->name, -1));
}
#endif
}
ob2->model.port = numnodes++; // Assign a port order
/* Add a proxy pin to tc1 */
+577 -528
View File
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -43,7 +43,8 @@ extern int PropertyMerge(char *name, int fnum, char *key, int merge_type,
int merge_mask);
extern void ResolveProperties(char *name1, int file1, char *name2, int file2);
extern void CopyProperties(struct objlist *obj_to, struct objlist *obj_from);
extern int PromoteProperty(struct property *, struct valuelist *);
extern int PromoteProperty(struct property *, struct valuelist *,
struct objlist *, struct nlist *);
extern int SetPropertyDefault(struct property *, struct valuelist *);
extern struct objlist *LinkProperties(char *model, struct keyvalue *topptr);
extern int ReduceExpressions(struct objlist *instprop, struct objlist *parprops,
@@ -149,6 +150,7 @@ extern int NoOutput; /* set this to 1 to disable stdout output */
extern int Composition; /* direction of composition */
extern int UnixWildcards; /* TRUE if *,?,{},[] only; false if full REGEXP */
extern int GlobalParallelNone; /* If TRUE, don't parallel combine any cells */
extern int GlobalParallelOpen; /* If TRUE, parallel combine cells w/no-connects */
/* magic internal flag to restrict searches to recently placed cells */
extern int QuickSearch;
/* does re"CellDef"ing a cell add to it or overwrite it??? */
+90
View File
@@ -63,6 +63,10 @@ the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
#define VLOG_PIN_NAME_DELIMITERS "X///**/(**)X()"
#define VLOG_PIN_CHECK_DELIMITERS "X///**/(**)X,;(){}"
// Used by portelement structure "flags" record.
#define PORT_NOT_FOUND 0
#define PORT_FOUND 1
// Global storage for verilog parameters
struct hashdict verilogparams;
// Global storage for verilog definitions
@@ -1404,6 +1408,7 @@ skip_endmodule:
char *name; // Name of port in subcell
char *net; // Name of net connecting to port in the parent
int width; // Width of port, if port is a bus
char flags; // Used for marking if port was added into netlist
struct portelement *next;
};
@@ -1530,6 +1535,7 @@ nextinst:
new_port = (struct portelement *)CALLOC(1, sizeof(struct portelement));
new_port->name = strsave(nexttok + 1);
new_port->width = -1;
new_port->flags = PORT_NOT_FOUND;
SkipTokComments(VLOG_DELIMITERS);
if (!match(nexttok, "(")) {
Printf("Badly formed subcircuit pin line at \"%s\"\n", nexttok);
@@ -1964,6 +1970,7 @@ nextinst:
obpinidx = -1;
while (scan != NULL) {
if (match(obpinname, scan->name)) {
scan->flags |= PORT_FOUND;
break;
}
scan = scan->next;
@@ -2056,6 +2063,89 @@ nextinst:
if (LookupObject(scan->net, CurrentCell) == NULL) Node(scan->net);
join(scan->net, obptr->name);
}
/* Before exiting the loop, check if all ports in the */
/* scan list were handled. */
if ((obptr->next == NULL) || (obptr->next->type <= FIRSTPIN)) {
for (scan = head; scan; scan = scan->next) {
if (!(scan->flags & PORT_FOUND)) {
if (tp->flags & CELL_PLACEHOLDER) {
char tempname[128];
int maxnode;
/* This pin was probably implicit in the first call */
/* and so it needs to be added to the definition. */
ReopenCellDef(modulename, filenum);
Port(scan->name);
ReopenCellDef((*CellStackPtr)->cellname, filenum);
/* obptr->next now gets the new port. Update the */
/* port number, and copy class and instance name. */
nobj = GetObject();
sprintf(tempname, "%s%s%s", obptr->instance.name,
SEPARATOR, scan->name);
nobj->name = strsave(tempname);
nobj->model.class = strsave(obptr->model.class);
nobj->instance.name = strsave(obptr->instance.name);
nobj->type = obptr->type + 1;
nobj->next = obptr->next;
nobj->node = -1;
obptr->next = nobj;
HashPtrInstall(nobj->name, nobj, &(CurrentCell->objdict));
if (LookupObject(scan->net, CurrentCell) == NULL)
Node(scan->net);
join(scan->net, nobj->name);
scan->flags |= PORT_FOUND;
/* Now any previous instance of the same cell must */
/* insert the same additional pin as a no-connect. */
/* NOTE: This should be running a callback on all */
/* cells in the file, not just CurrentCell. */
for (sobj = CurrentCell->cell; sobj && (sobj != obptr);
sobj = sobj->next) {
if (sobj->type == FIRSTPIN) {
if (match(sobj->model.class, obptr->model.class)) {
while (sobj->next->type > FIRSTPIN)
sobj = sobj->next;
/* Stop when reaching the current instance */
if (sobj->type == obptr->type + 1) break;
nobj = GetObject();
sprintf(tempname, "%s%s%s", sobj->instance.name,
SEPARATOR, scan->name);
nobj->name = strsave(tempname);
nobj->model.class = strsave(sobj->model.class);
nobj->instance.name = strsave(sobj->instance.name);
nobj->type = obptr->type + 1;
nobj->node = -1;
nobj->next = sobj->next;
sobj->next = nobj;
HashPtrInstall(nobj->name, nobj,
&(CurrentCell->objdict));
sprintf(tempname, "_noconnect_%d_", localcount++);
Node(tempname);
join(tempname, nobj->name);
Fprintf(stderr, "Note: Implicit pin %s in instance "
"%s of %s in cell %s\n",
scan->name, sobj->instance.name,
modulename, CurrentCell->name);
}
}
}
}
else {
Fprintf(stderr, "Error: Instance %s has pin %s which is "
"not in the %s cell definition.\n",
locinst, scan->name, modulename);
}
}
}
}
obptr = obptr->next;
} while (obptr != NULL && obptr->type > FIRSTPIN);
}
+2
View File
@@ -8,6 +8,8 @@ SCRIPTINSTALL = $(DESTDIR)${INSTALL_PYDIR}
include ${NETGENDIR}/defs.mak
CLEANS += lvs_manager.py
main: lvs_manager.py
tcl-main: lvs_manager.py
-819
View File
@@ -1,819 +0,0 @@
#!/bin/env python3
#
#--------------------------------------------------------
# LVS Manager GUI.
#
# This is a Python tkinter script that handles the
# process of running LVS and interpreting results.
#
#--------------------------------------------------------
# Written by Tim Edwards
# efabless, inc.
# Version 1. November 30, 2016
# Version 2. March 6, 2017. Reads JSON format output
# Version 3. April 25, 2018. Handles layout vs. verilog
#--------------------------------------------------------
import io
import os
import re
import sys
import json
import shutil
import signal
import socket
import select
import datetime
import contextlib
import subprocess
import tkinter
from tkinter import ttk
from tkinter import filedialog
import tksimpledialog
import tooltip
from consoletext import ConsoleText
from helpwindow import HelpWindow
from treeviewsplit import TreeViewSplit
# User preferences file (if it exists)
prefsfile = '~/.profile/prefs.json'
netgen_script_dir = '/usr/local/lib/netgen/python'
#------------------------------------------------------
# Simple dialog for confirming quit
#------------------------------------------------------
class ConfirmDialog(tksimpledialog.Dialog):
def body(self, master, warning, seed):
if warning:
ttk.Label(master, text=warning, wraplength=500).grid(row = 0, columnspan = 2, sticky = 'wns')
return self
def apply(self):
return 'okay'
#------------------------------------------------------
# Main class for this application
#------------------------------------------------------
class LVSManager(ttk.Frame):
"""LVS Manager GUI."""
def __init__(self, parent, *args, **kwargs):
ttk.Frame.__init__(self, parent, *args, **kwargs)
self.root = parent
self.init_gui()
parent.protocol("WM_DELETE_WINDOW", self.on_quit)
def on_quit(self):
"""Exits program."""
if self.msock:
self.msock.close()
quit()
def init_gui(self):
"""Builds GUI."""
global prefsfile
message = []
fontsize = 11
# Read user preferences file, get default font size from it.
prefspath = os.path.expanduser(prefsfile)
if os.path.exists(prefspath):
with open(prefspath, 'r') as f:
self.prefs = json.load(f)
if 'fontsize' in self.prefs:
fontsize = self.prefs['fontsize']
else:
self.prefs = {}
s = ttk.Style()
available_themes = s.theme_names()
s.theme_use(available_themes[0])
s.configure('bg.TFrame', background='gray40')
s.configure('italic.TLabel', font=('Helvetica', fontsize, 'italic'))
s.configure('title.TLabel', font=('Helvetica', fontsize, 'bold italic'),
foreground = 'brown', anchor = 'center')
s.configure('normal.TLabel', font=('Helvetica', fontsize))
s.configure('red.TLabel', font=('Helvetica', fontsize), foreground = 'red')
s.configure('green.TLabel', font=('Helvetica', fontsize), foreground = 'green3')
s.configure('blue.TLabel', font=('Helvetica', fontsize), foreground = 'blue')
s.configure('normal.TButton', font=('Helvetica', fontsize),
border = 3, relief = 'raised')
s.configure('red.TButton', font=('Helvetica', fontsize), foreground = 'red',
border = 3, relief = 'raised')
s.configure('green.TButton', font=('Helvetica', fontsize), foreground = 'green3',
border = 3, relief = 'raised')
s.configure('blue.TButton', font=('Helvetica', fontsize), foreground = 'blue',
border = 3, relief = 'raised')
s.configure('redtitle.TButton', font=('Helvetica', fontsize, 'bold italic'),
foreground = 'red', border = 3, relief = 'raised')
s.configure('bluetitle.TButton', font=('Helvetica', fontsize, 'bold italic'),
foreground = 'blue', border = 3, relief = 'raised')
# These values to be overridden from arguments
self.rootpath = None
self.project = None
self.logfile = None
self.msock = None
self.help = None
# Create the help window
if os.path.exists(netgen_script_dir + '/netgen_help.txt'):
self.help = HelpWindow(self, fontsize = fontsize)
with io.StringIO() as buf, contextlib.redirect_stdout(buf):
self.help.add_pages_from_file('lvs_help.txt')
message = buf.getvalue()
# Set the help display to the first page
self.help.page(0)
# Variables used by option menus and other stuff
self.project = "(no selection)"
self.layout = "(default)"
self.schematic = "(default)"
self.tech = "(none)"
self.lvs_setup = ''
self.lvsdata = {}
# Root window title
self.root.title('LVS Manager')
self.root.option_add('*tearOff', 'FALSE')
self.pack(side = 'top', fill = 'both', expand = 'true')
pane = tkinter.PanedWindow(self, orient = 'vertical', sashrelief='groove', sashwidth=6)
pane.pack(side = 'top', fill = 'both', expand = 'true')
self.toppane = ttk.Frame(pane)
self.botpane = ttk.Frame(pane)
# Get username
if 'username' in self.prefs:
username = self.prefs['username']
else:
username = os.environ['USER']
# Label with the user
self.toppane.title_frame = ttk.Frame(self.toppane)
self.toppane.title_frame.pack(side = 'top', fill = 'x')
self.toppane.title_frame.title = ttk.Label(self.toppane.title_frame, text='User:', style = 'red.TLabel')
self.toppane.title_frame.user = ttk.Label(self.toppane.title_frame, text=username, style = 'blue.TLabel')
self.toppane.title_frame.title.grid(column=0, row=0, ipadx = 5)
self.toppane.title_frame.user.grid(column=1, row=0, ipadx = 5)
self.toppane.title2_frame = ttk.Frame(self.toppane)
self.toppane.title2_frame.pack(side = 'top', fill = 'x')
self.toppane.title2_frame.project_label = ttk.Label(self.toppane.title2_frame, text="Project:",
style = 'title.TLabel')
self.toppane.title2_frame.project_label.grid(column=0, row=0, ipadx = 5)
# New project select button
self.toppane.title2_frame.project_select = ttk.Button(self.toppane.title2_frame,
text=self.project, style='normal.TButton', command=self.choose_project)
self.toppane.title2_frame.project_select.grid(column=1, row=0, ipadx = 5)
tooltip.ToolTip(self.toppane.title2_frame.project_select,
text = "Select new project")
# Show path to project
self.toppane.title2_frame.path_label = ttk.Label(self.toppane.title2_frame, text=self.project,
style = 'normal.TLabel')
self.toppane.title2_frame.path_label.grid(column=2, row=0, ipadx = 5, padx = 10)
# Show top-level layout cellname with select button. Initial cell name is the top-level cell.
self.toppane.title2_frame.tech_label = ttk.Label(self.toppane.title2_frame, text="Technology setup:",
style = 'title.TLabel')
self.toppane.title2_frame.tech_label.grid(column=3, row=0, ipadx = 5)
self.toppane.title2_frame.tech_select = ttk.Button(self.toppane.title2_frame,
text=self.tech, style='normal.TButton', command=self.choose_tech)
self.toppane.title2_frame.tech_select.grid(column=4, row=0, ipadx = 3, padx = 3)
self.toppane.title2_frame.layout_label = ttk.Label(self.toppane.title2_frame, text="Layout:",
style = 'title.TLabel')
self.toppane.title2_frame.layout_label.grid(column=0, row=1, ipadx = 5)
self.toppane.title2_frame.layout_select = ttk.Button(self.toppane.title2_frame,
text=self.layout, style='normal.TButton', command=self.choose_layout)
self.toppane.title2_frame.layout_select.grid(column=1, row=1, ipadx = 3, padx = 3)
# Show top-level schematic cellname with select button. Initial cell name is the top-level cell.
self.toppane.title2_frame.schem_label = ttk.Label(self.toppane.title2_frame, text="Schematic:",
style = 'title.TLabel')
self.toppane.title2_frame.schem_label.grid(column=3, row=1, ipadx = 5)
self.toppane.title2_frame.schem_select = ttk.Button(self.toppane.title2_frame,
text=self.schematic, style='normal.TButton', command=self.choose_netlist)
self.toppane.title2_frame.schem_select.grid(column=4, row=1, ipadx = 3, padx = 3)
tooltip.ToolTip(self.toppane.title2_frame.project_select,
text = "Select new project")
tooltip.ToolTip(self.toppane.title2_frame.layout_select,
text = "Select a layout subcirucit to compare")
tooltip.ToolTip(self.toppane.title2_frame.schem_select,
text = "Select a schematic subcirucit to compare")
#---------------------------------------------
ttk.Separator(self.toppane, orient='horizontal').pack(side = 'top', fill = 'x')
#---------------------------------------------
# Create listbox of Circuit1 vs. Circuit2 results
height = 10
self.toppane.lvsreport = TreeViewSplit(self.toppane, fontsize = fontsize)
self.toppane.lvsreport.populate("Layout:", [], "Schematic:", [],
[["Run", True, self.run_lvs],
# ["Find", True, self.findrecord]
], height = height)
self.toppane.lvsreport.set_title("Line")
self.toppane.lvsreport.pack(side = 'top', fill = 'both', expand = 'true')
tooltip.ToolTip(self.toppane.lvsreport.get_button(0), text="Run LVS")
#---------------------------------------------
# ttk.Separator(self, orient='horizontal').grid(column=0, row=3, sticky='ew')
#---------------------------------------------
# Add a text window below the project name to capture output. Redirect
# print statements to it.
self.botpane.console = ttk.Frame(self.botpane)
self.botpane.console.pack(side = 'top', fill = 'both', expand = 'true')
self.text_box = ConsoleText(self.botpane.console, wrap='word', height = 4)
self.text_box.pack(side='left', fill='both', expand='true')
console_scrollbar = ttk.Scrollbar(self.botpane.console)
console_scrollbar.pack(side='right', fill='y')
# attach console to scrollbar
self.text_box.config(yscrollcommand = console_scrollbar.set)
console_scrollbar.config(command = self.text_box.yview)
# Add button bar at the bottom of the window
self.botpane.bbar = ttk.Frame(self.botpane)
self.botpane.bbar.pack(side = 'top', fill = 'x')
# Define the "quit" button and action
self.botpane.bbar.quit_button = ttk.Button(self.botpane.bbar, text='Quit', command=self.on_quit,
style = 'normal.TButton')
self.botpane.bbar.quit_button.grid(column=0, row=0, padx = 5)
# Define help button
if self.help:
self.botpane.bbar.help_button = ttk.Button(self.botpane.bbar, text='Help',
command=self.help.open, style = 'normal.TButton')
self.botpane.bbar.help_button.grid(column = 2, row = 0, padx = 5)
tooltip.ToolTip(self.botpane.bbar.help_button, text = "Show help window")
# Add the panes once the internal geometry is known.
pane.add(self.toppane)
pane.add(self.botpane)
pane.paneconfig(self.toppane, stretch='first')
# Redirect stdout and stderr to the console as the last thing to do. . .
# Otherwise errors in the GUI get sucked into the void.
self.stdout = sys.stdout
self.stderr = sys.stderr
sys.stdout = ConsoleText.StdoutRedirector(self.text_box)
sys.stderr = ConsoleText.StderrRedirector(self.text_box)
if message:
print(message)
def logprint(self, message, doflush=False):
if self.logfile:
self.logfile.buffer.write(message.encode('utf-8'))
self.logfile.buffer.write('\n'.encode('utf-8'))
if doflush:
self.logfile.flush()
def printout(self, output):
# Generate output
if not output:
return
outlines = output.splitlines()
for line in outlines:
try:
print(line)
except TypeError:
line = line.decode('utf-8')
pritn(line)
def printwarn(self, output):
# Check output for warning or error
if not output:
return 0
warnrex = re.compile('.*warning', re.IGNORECASE)
errrex = re.compile('.*error', re.IGNORECASE)
errors = 0
outlines = output.splitlines()
for line in outlines:
try:
wmatch = warnrex.match(line)
except TypeError:
line = line.decode('utf-8')
wmatch = warnrex.match(line)
ematch = errrex.match(line)
if ematch:
errors += 1
if ematch or wmatch:
print(line)
return errors
def choose_tech(self):
try:
project_path = self.rootpath
initdirname = self.rootpath + '/tech',
except:
print('Must choose a project first.')
return
techname = filedialog.askopenfilename(multiple=False,
initialdir = initdirname,
filetypes = (("Tcl script", "*.tcl"),("All Files","*.*")),
title = "Choose a netgen technology setup script.")
if techname != '':
print("Selected technology setup script " + techname)
techbase = os.path.split(techname)[1]
self.tech = os.path.splitext(techbase)[0]
self.lvs_setup = techname
self.toppane.title2_frame.layout_select.config(text = self.tech)
def choose_layout(self):
try:
project_path = self.rootpath
initdirname = self.rootpath + '/layout',
except:
print('Must choose a project first.')
return
cellname = filedialog.askopenfilename(multiple=False,
initialdir = initdirname,
filetypes = (("Magic layout", "*.mag"),("All Files","*.*")),
title = "Choose a layout cell to compare.")
if cellname != '':
print("Selected compare cell " + cellname)
cellbase = os.path.split(cellname)[1]
self.layout = os.path.splitext(cellbase)[0]
self.toppane.title2_frame.layout_select.config(text = self.layout)
def choose_netlist(self):
try:
project_path = self.rootpath
initdirname = self.rootpath + '/netlist/' + self.project + '.spi'
except:
print('Must choose a project first.')
return
cellname = filedialog.askopenfilename(multiple=False,
initialdir = initdirname,
filetypes = (("Spice netlist", "*.spi"),("Verilog netlist", "*.v")),
title = "Choose a netlist to compare.")
if cellname != '':
print("Selected compare cell " + cellname)
cellbase = os.path.split(cellname)[1]
self.schematic = os.path.splitext(cellbase)[0]
self.toppane.title2_frame.schem_select.config(text = self.schematic)
fileext = os.path.splitext(cellbase)[1]
if fileext == '.v':
self.toppane.title2_frame.schem_label.config(text = 'Verilog netlist:')
elif fileext == '.sp' or fileext == '.spi' or fileext == '.spice' or fileext == '.spc' or fileext == '.ckt':
self.toppane.title2_frame.schem_label.config(text = 'SPICE netlist:')
elif fileext == '.cdl':
self.toppane.title2_frame.schem_label.config(text = 'CDL netlist:')
else:
self.toppane.title2_frame.schem_label.config(text = 'Unknown netlist:')
def choose_project(self):
project = filedialog.askdirectory(initialdir = os.getcwd(),
title = "Find a project.")
if project != '':
print("Selected project " + str(project))
result = self.set_project(project)
def set_project(self, rootpath, project_name=None):
# Check if rootpath is valid. For LVS, there should be subdirectories
# "layout/" and "netlist/" or "verilog/".
haslay = os.path.isdir(rootpath + '/layout')
hasvlog = os.path.isdir(rootpath + '/verilog')
hasnet = os.path.isdir(rootpath + '/netlist')
if not haslay or not (hasvlog or hasnet):
if not haslay:
print("Project path has no layout (/layout) subdirectory.")
if not (hasvlog or hasnet):
print("Project path has no verilog (/verilog), or netlist (/netlist) subdirectory.")
# Continue anyway; assume that netlists will be selected manually
if self.logfile:
self.logfile.close()
self.logfile = None
if not project_name:
project = os.path.split(rootpath)[1]
else:
project = project_name
if self.project != project:
self.rootpath = rootpath
self.project = project
# Clear out old project data
self.toppane.lvsreport.repopulate([], [])
# Close any open logfile.
if self.logfile:
self.logfile.close()
self.logfile = None
# Put new log file called 'lvs.log' in the mag/ subdirectory
self.logfile = open(rootpath + '/layout/lvs.log', 'w')
# Print some initial information to the logfile.
self.logprint('Starting new log file ' + datetime.datetime.now().strftime('%c'),
doflush=True)
# Update project button
self.toppane.title2_frame.project_select.config(text = self.project)
self.toppane.title2_frame.path_label.config(text = self.rootpath)
# Cell name is the same as project name initially
self.layout = self.project
self.schematic = self.project
self.toppane.title2_frame.layout_select.config(text = self.layout)
self.toppane.title2_frame.schem_select.config(text = self.schematic)
# Update schematic button
if os.path.isfile(rootpath + '/verilog/' + self.schematic + '.v'):
self.toppane.title2_frame.schem_label.config(text = 'Verilog netlist:')
else:
self.toppane.title2_frame.schem_label.config(text = 'Schematic netlist:')
# If there is a comparison file that post-dates both netlists, load it.
self.check_lvs()
return True
def check_layout_out_of_date(self, spipath, layoutpath):
# Check if a netlist (spipath) is out-of-date relative to the layouts
# (layoutpath). Need to read the netlist and check all of the subcells.
need_capture = False
if not os.path.isfile(spipath):
return True
if os.path.isfile(layoutpath):
spi_statbuf = os.stat(spipath)
lay_statbuf = os.stat(layoutpath)
if spi_statbuf.st_mtime < lay_statbuf.st_mtime:
# netlist exists but is out-of-date
need_capture = True
else:
# only found that the top-level-layout is older than the
# netlist. Now need to read the netlist, find all subcircuits,
# and check those dates, too.
layoutdir = os.path.split(layoutpath)[0]
subrex = re.compile('^[^\*]*[ \t]*.subckt[ \t]+([^ \t]+).*$', re.IGNORECASE)
with open(spipath, 'r') as ifile:
duttext = ifile.read()
dutlines = duttext.replace('\n+', ' ').splitlines()
for line in dutlines:
lmatch = subrex.match(line)
if lmatch:
subname = lmatch.group(1)
sublayout = layoutdir + '/' + subname + '.mag'
# subcircuits that cannot be found in the current directory are
# assumed to be library components and therefore never out-of-date.
if os.path.exists(sublayout):
sub_statbuf = os.stat(sublayout)
if spi_statbuf.st_mtime < lay_statbuf.st_mtime:
# netlist exists but is out-of-date
need_capture = True
break
return need_capture
def check_schematic_out_of_date(self, spipath, schempath):
# Check if a netlist (spipath) is out-of-date relative to the schematics
# (schempath). Need to read the netlist and check all of the subcells.
need_capture = False
if not os.path.isfile(spipath):
return True
if os.path.isfile(schempath):
spi_statbuf = os.stat(spipath)
sch_statbuf = os.stat(schempath)
if spi_statbuf.st_mtime < sch_statbuf.st_mtime:
# netlist exists but is out-of-date
need_capture = True
else:
# only found that the top-level-schematic is older than the
# netlist. Now need to read the netlist, find all subcircuits,
# and check those dates, too.
schemdir = os.path.split(schempath)[0]
subrex = re.compile('^[^\*]*[ \t]*.subckt[ \t]+([^ \t]+).*$', re.IGNORECASE)
with open(spipath, 'r') as ifile:
duttext = ifile.read()
dutlines = duttext.replace('\n+', ' ').splitlines()
for line in dutlines:
lmatch = subrex.match(line)
if lmatch:
subname = lmatch.group(1)
# NOTE: Electric uses library:cell internally to track libraries,
# and maps the ":" to "__" in the netlist. Not entirely certain that
# the double-underscore uniquely identifies the library:cell. . .
librex = re.compile('(.*)__(.*)', re.IGNORECASE)
lmatch = librex.match(subname)
if lmatch:
elecpath = os.path.split(os.path.split(schempath)[0])[0]
libname = lmatch.group(1)
subschem = elecpath + '/' + libname + '.delib/' + lmatch.group(2) + '.sch'
else:
libname = {}
subschem = schemdir + '/' + subname + '.sch'
# subcircuits that cannot be found in the current directory are
# assumed to be library components and therefore never out-of-date.
if os.path.exists(subschem):
sub_statbuf = os.stat(subschem)
if spi_statbuf.st_mtime < sub_statbuf.st_mtime:
# netlist exists but is out-of-date
need_capture = True
break
# mapping of characters to what's allowed in SPICE makes finding
# the associated schematic file a bit difficult. Requires wild-card
# searching.
elif libname:
restr = lmatch.group(2) + '.sch'
restr = restr.replace('.', '\.')
restr = restr.replace('_', '.')
schrex = re.compile(restr, re.IGNORECASE)
libpath = elecpath + '/' + libname + '.delib'
if os.path.exists(libpath):
liblist = os.listdir(libpath)
for file in liblist:
lmatch = schrex.match(file)
if lmatch:
subschem = libpath + '/' + file
sub_statbuf = os.stat(subschem)
if spi_statbuf.st_mtime < sch_statbuf.st_mtime:
# netlist exists but is out-of-date
need_capture = True
break
return need_capture
def check_lvs(self):
# If both netlists exist, and comp.json is more recent than both, then
# load LVS results from comp.json
project_path = self.rootpath
project_name = self.project
layout_path = project_path + '/layout/' + project_name + '.spc'
net_path = project_path + '/netlist/' + project_name + '.spi'
comp_path = project_path + '/layout/comp.json'
if os.path.exists(layout_path) and os.path.exists(net_path) and os.path.exists(comp_path):
magtime = os.stat(layout_path).st_mtime
schemtime = os.stat(net_path).st_mtime
comptime = os.stat(comp_path).st_mtime
if comptime > magtime and comptime > schemtime:
print("Loading LVS results from file.")
self.generate(comp_path)
def generate_layout_netlist(self, layout_path, layout_src, project_path):
# Does layout netlist exist and is it current?
if self.check_layout_out_of_date(layout_path, layout_src):
print('Generating layout netlist.')
self.update_idletasks()
mproc = subprocess.Popen(['magic', '-dnull', '-noconsole',
self.layout], stdin = subprocess.PIPE, stdout = subprocess.PIPE,
stderr = subprocess.PIPE, cwd = project_path + '/layout',
universal_newlines = True)
mproc.stdin.write("select top cell\n")
mproc.stdin.write("expand\n")
mproc.stdin.write("extract all\n")
mproc.stdin.write("ext2spice hierarchy on\n")
mproc.stdin.write("ext2spice format ngspice\n")
mproc.stdin.write("ext2spice scale off\n")
mproc.stdin.write("ext2spice renumber off\n")
mproc.stdin.write("ext2spice subcircuit top auto\n")
mproc.stdin.write("ext2spice cthresh infinite\n")
mproc.stdin.write("ext2spice rthresh infinite\n")
mproc.stdin.write("ext2spice blackbox on\n")
mproc.stdin.write("ext2spice -o " + self.layout + ".spi\n")
mproc.stdin.write("quit -noprompt\n")
magicout = mproc.communicate()[0]
self.printwarn(magicout)
if mproc.returncode != 0:
print('Failure to generate new layout netlist.')
return False
# Move .spi netlist to project_dir/netlist/lvs/
shutil.move(project_path + '/layout/' + self.layout + '.spi', layout_path)
# Remove extraction files
for file in os.listdir(project_path + '/layout'):
if os.path.splitext(file)[1] == '.ext':
os.remove(project_path + '/layout/' + file)
else:
print('Layout netlist is up-to-date, not regenerating.')
return True
def run_lvs(self, value):
# "value" is ignored (?)
# Check if netlists exist and are current; otherwise create them.
# Then run LVS.
project_path = self.rootpath
project_name = self.project
comp_path = project_path + '/layout/comp.json'
has_vlog = False
vlog_path = project_path + '/verilog/' + project_name + '.v'
layout_path = project_path + '/netlist/lvs/' + self.layout + '.spi'
net_path = project_path + '/netlist/schem/' + self.schematic + '.spi'
layout_src = project_path + '/layout/' + self.layout + '.mag'
# Does the setup file exist (this is optional)?
if self.lvs_setup != '' and not os.path.isfile('setup.tcl'):
print('No technology setup file selected.')
# Does schematic netlist exist?
if not os.path.isfile(vlog_path) and not os.path.isfile(net_path):
print('Error: No schematic netlist or verilog netlist.')
return
# Does LVS netlist subdirectory exist?
if not os.path.exists(project_path + '/netlist/lvs'):
os.makedirs(project_path + '/netlist/lvs')
# Does layout netlist exist and is it current?
if not self.generate_layout_netlist(layout_path, layout_src, project_path):
return False
# Final check
if not os.path.isfile(layout_path):
print('Error: No netlist generated from magic.')
return
else:
# Read in netlist and convert commas from [X,Y] arrays to vertical bars
# as something that can be converted back as necessary. ngspice treats
# commas as special characters for some reason.
with open(layout_path) as ifile:
spitext = ifile.read()
# spilines = spitext.replace(',', '|')
# with open(layout_path, 'w') as ofile:
# ofile.write(spilines)
# Check the netlist to see if the cell to match is a subcircuit. If
# not, then assume it is the top level.
is_subckt = False
subrex = re.compile('^[^\*]*[ \t]*.subckt[ \t]+([^ \t]+).*$', re.IGNORECASE)
dutlines = spitext.replace('\n+', ' ').splitlines()
for line in dutlines:
lmatch = subrex.match(line)
if lmatch:
subname = lmatch.group(1)
if subname == self.layout:
is_subckt = True
break
if is_subckt:
layout_arg = layout_path + ' ' + self.layout
layout_text = '"' + layout_arg + '"'
else:
layout_arg = layout_path
layout_text = layout_arg
if has_vlog:
schem_arg = vlog_path + ' ' + self.schematic
else:
schem_arg = net_path + ' ' + self.schematic
schem_text = '"' + schem_arg + '"'
# Remove any previous comparison output file
comp_out_path = os.path.splitext(comp_path)[0] + '.out'
if os.path.exists(comp_out_path):
os.remove(comp_out_path)
# Run netgen as subprocess
print('Running: netgen -batch lvs ' + layout_text +
' ' + schem_text + ' ' + self.lvs_setup + ' ' + comp_out_path +
' -json -blackbox')
# Note: Because arguments to subprocess are list items, the {filename cell}
# pair does *not* have to be quoted or braced. Doing so causes a parse
# error.
self.lvsproc = subprocess.Popen(['netgen', '-batch', 'lvs',
layout_arg, schem_arg,
self.lvs_setup, comp_out_path, '-json', '-blackbox'],
cwd=project_path + '/layout',
stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=0)
# This is largely unnecessary as netgen usually runs to completion very quickly.
self.watchclock(comp_path)
def watchclock(self, filename):
if self.lvsproc == None:
return
lvs_status = self.lvsproc.poll()
sresult = select.select([self.lvsproc.stdout, self.lvsproc.stderr], [], [], 0)[0]
if self.lvsproc.stdout in sresult:
outstring = self.lvsproc.stdout.readline().decode().strip()
self.logprint(outstring, doflush=True)
print(outstring)
elif self.lvsproc.stderr in sresult:
errstring = self.lvsproc.stderr.readline().decode().strip()
self.logprint(errstring, doflush = True)
print(errstring, file=sys.stderr)
if lvs_status != None:
print("netgen LVS exited with status " + str(lvs_status))
self.lvsproc = None
if lvs_status != 0:
print('Errors encountered in LVS.')
self.logprint('Errors in LVS, lvs status = ' + str(lvs_status), doflush=True)
# Done; now read comp.json and fill the treeview listbox.
self.generate(filename)
else:
self.after(500, lambda: self.watchclock(filename))
# Generate display from "comp.out" file (json file now preferred)
def generate_orig(self, lvspath):
lefttext = []
righttext = []
print("Reading LVS output file " + lvspath)
if os.path.exists(lvspath):
with open(lvspath, 'r') as ifile:
lvslines = ifile.read().splitlines()
for line in lvslines:
if '|' in line:
# parts = line.split('|')
# lefttext.append(parts[0])
# righttext.append(parts[1])
lefttext.append(line[0:42].strip())
righttext.append(line[44:].strip())
else:
lefttext.append(line)
righttext.append('')
# Populate treeview with text
self.toppane.lvsreport.repopulate(lefttext, righttext)
else:
print("Error: No output file generated from LVS.")
# Generate output from LVS report JSON file comp.json
def generate(self, lvspath):
lefttext = []
righttext = []
print("Reading LVS output file " + lvspath)
if os.path.exists(lvspath):
with open(lvspath, 'r') as ifile:
self.lvsdata = json.load(ifile)
# Populate treeview with text
self.toppane.lvsreport.json_repopulate(self.lvsdata)
else:
print("Error: No output file generated from LVS.")
def findrecord(self, value):
print("Unimplemented function")
def findrecord_test(self, value):
# Check if socket is defined; if not, attempt to open one
if not self.msock:
try:
self.msock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except:
print("No response from layout tool.")
if self.msock:
self.msock.connect(("0.0.0.0", 12946))
self.msock.setblocking(False)
if self.msock:
# Pull name of net or device from 'value'
# This is a test:
self.msock.sendall(b'box 0 0 100 100\r\n')
if __name__ == '__main__':
options = []
arguments = []
for item in sys.argv[1:]:
if item.find('-', 0) == 0:
options.append(item)
else:
arguments.append(item)
root = tkinter.Tk()
app = LVSManager(root)
if arguments:
if len(arguments) >= 2:
app.set_project(arguments[0], project_name=arguments[1])
else:
app.set_project(arguments[0])
root.mainloop()
+108 -36
View File
@@ -343,7 +343,7 @@ class LVSManager(ttk.Frame):
techbase = os.path.split(techname)[1]
self.tech = os.path.splitext(techbase)[0]
self.lvs_setup = techname
self.toppane.title2_frame.layout_select.config(text = self.tech)
self.toppane.title2_frame.tech_select.config(text = self.tech)
def choose_layout(self):
try:
@@ -358,30 +358,37 @@ class LVSManager(ttk.Frame):
title = "Choose a layout cell to compare.")
if cellname != '':
print("Selected compare cell " + cellname)
self.layout = cellname
cellbase = os.path.split(cellname)[1]
self.layout = os.path.splitext(cellbase)[0]
self.toppane.title2_frame.layout_select.config(text = self.layout)
layoutname = os.path.splitext(cellbase)[0]
self.toppane.title2_frame.layout_select.config(text = layoutname)
fileext = os.path.splitext(cellbase)[1]
if fileext == '.mag':
self.toppane.title2_frame.layout_label.config(text = 'Layout:')
else:
self.toppane.title2_frame.layout_label.config(text = 'Layout netlist:')
def choose_netlist(self):
try:
project_path = self.rootpath
initdirname = self.rootpath + '/netlist/' + self.project + '.spi'
initdirname = self.rootpath + '/netlist/' + self.project + '.spice'
except:
print('Must choose a project first.')
return
cellname = filedialog.askopenfilename(multiple=False,
initialdir = initdirname,
filetypes = (("Spice netlist", "*.spi"),("Verilog netlist", "*.v")),
filetypes = (("Spice netlist", "*.spice"),("Verilog netlist", "*.v"),("All Files","*.*")),
title = "Choose a netlist to compare.")
if cellname != '':
print("Selected compare cell " + cellname)
self.schematic = cellname
cellbase = os.path.split(cellname)[1]
self.schematic = os.path.splitext(cellbase)[0]
self.toppane.title2_frame.schem_select.config(text = self.schematic)
schematic_name = os.path.splitext(cellbase)[0]
self.toppane.title2_frame.schem_select.config(text = schematic_name)
fileext = os.path.splitext(cellbase)[1]
if fileext == '.v':
self.toppane.title2_frame.schem_label.config(text = 'Verilog netlist:')
elif fileext == '.sp' or fileext == '.spi' or fileext == '.spice' or fileext == '.spc' or fileext == '.ckt':
elif fileext == '.sp' or fileext == '.spice' or fileext == '.spi' or fileext == '.spc' or fileext == '.ckt':
self.toppane.title2_frame.schem_label.config(text = 'SPICE netlist:')
elif fileext == '.cdl':
self.toppane.title2_frame.schem_label.config(text = 'CDL netlist:')
@@ -433,7 +440,10 @@ class LVSManager(ttk.Frame):
self.logfile = None
# Put new log file called 'lvs.log' in the mag/ subdirectory
self.logfile = open(rootpath + '/layout/lvs.log', 'w')
if os.path.exists(rootpath + '/layout'):
self.logfile = open(rootpath + '/layout/lvs.log', 'w')
else:
self.logfile = open(rootpath + '/lvs.log', 'w')
# Print some initial information to the logfile.
self.logprint('Starting new log file ' + datetime.datetime.now().strftime('%c'),
doflush=True)
@@ -444,15 +454,23 @@ class LVSManager(ttk.Frame):
# Cell name is the same as project name initially
self.layout = self.project
self.schematic = self.project
self.toppane.title2_frame.layout_select.config(text = self.layout)
self.toppane.title2_frame.schem_select.config(text = self.schematic)
layname = os.path.splitext(os.path.split(self.layout)[1])[0]
self.toppane.title2_frame.layout_select.config(text = layname)
schemname = os.path.splitext(os.path.split(self.schematic)[1])[0]
self.toppane.title2_frame.schem_select.config(text = schemname)
# Update schematic button
if os.path.isfile(rootpath + '/verilog/' + self.schematic + '.v'):
if os.path.splitext(self.schematic)[1] == '.v':
self.toppane.title2_frame.schem_label.config(text = 'Verilog netlist:')
else:
self.toppane.title2_frame.schem_label.config(text = 'Schematic netlist:')
# Update layout button
if os.path.splitext(self.layout)[1] == '.mag':
self.toppane.title2_frame.schem_label.config(text = 'Layout:')
else:
self.toppane.title2_frame.schem_label.config(text = 'Layout netlist:')
# If there is a comparison file that post-dates both netlists, load it.
self.check_lvs()
return True
@@ -567,8 +585,8 @@ class LVSManager(ttk.Frame):
# load LVS results from comp.json
project_path = self.rootpath
project_name = self.project
layout_path = project_path + '/layout/' + project_name + '.spc'
net_path = project_path + '/netlist/' + project_name + '.spi'
layout_path = project_path + '/layout/' + project_name + '.spice'
net_path = project_path + '/netlist/' + project_name + '.spice'
comp_path = project_path + '/layout/comp.json'
if os.path.exists(layout_path) and os.path.exists(net_path) and os.path.exists(comp_path):
@@ -599,7 +617,7 @@ class LVSManager(ttk.Frame):
mproc.stdin.write("ext2spice cthresh infinite\n")
mproc.stdin.write("ext2spice rthresh infinite\n")
mproc.stdin.write("ext2spice blackbox on\n")
mproc.stdin.write("ext2spice -o " + self.layout + ".spi\n")
mproc.stdin.write("ext2spice -o " + self.layout + ".spice\n")
mproc.stdin.write("quit -noprompt\n")
magicout = mproc.communicate()[0]
self.printwarn(magicout)
@@ -607,8 +625,8 @@ class LVSManager(ttk.Frame):
print('Failure to generate new layout netlist.')
return False
# Move .spi netlist to project_dir/netlist/lvs/
shutil.move(project_path + '/layout/' + self.layout + '.spi', layout_path)
# Move .spice netlist to project_dir/netlist/lvs/
shutil.move(project_path + '/layout/' + self.layout + '.spice', layout_path)
# Remove extraction files
for file in os.listdir(project_path + '/layout'):
if os.path.splitext(file)[1] == '.ext':
@@ -625,17 +643,37 @@ class LVSManager(ttk.Frame):
project_path = self.rootpath
project_name = self.project
comp_path = project_path + '/layout/comp.json'
# Diagnostic:
print('project_name is ' + project_name)
print('project_path is ' + project_path)
print('self.layout is ' + self.layout)
print('self.schematic is ' + self.schematic)
print('self.lvs_setup is ' + self.lvs_setup)
has_vlog = False
vlog_path = project_path + '/verilog/' + project_name + '.v'
layout_path = project_path + '/netlist/lvs/' + self.layout + '.spi'
net_path = project_path + '/netlist/schem/' + self.schematic + '.spi'
layout_src = project_path + '/layout/' + self.layout + '.mag'
if os.path.isfile(self.layout):
layout_path = self.layout
layout_src = None
else:
layout_path = project_path + '/netlist/lvs/' + self.layout + '.spice'
layout_src = project_path + '/layout/' + self.layout + '.mag'
comp_dir = os.path.split(layout_path)[0]
comp_path = comp_dir + '/comp.json'
if os.path.isfile(self.schematic):
net_path = self.schematic
else:
net_path = project_path + '/netlist/schem/' + self.schematic + '.spice'
# Does the setup file exist (this is optional)?
if self.lvs_setup != '' and not os.path.isfile('setup.tcl'):
if self.lvs_setup == '' and not os.path.isfile('setup.tcl'):
print('No technology setup file selected.')
elif not os.path.isfile(self.lvs_setup):
print("Can't find technology setup file " + self.lvs_setup)
# Does schematic netlist exist?
if not os.path.isfile(vlog_path) and not os.path.isfile(net_path):
@@ -643,14 +681,16 @@ class LVSManager(ttk.Frame):
return
# Does LVS netlist subdirectory exist?
if not os.path.exists(project_path + '/netlist/lvs'):
os.makedirs(project_path + '/netlist/lvs')
if os.path.exists(project_path + '/netlist'):
if not os.path.exists(project_path + '/netlist/lvs'):
os.makedirs(project_path + '/netlist/lvs')
# Does layout netlist exist and is it current?
if not self.generate_layout_netlist(layout_path, layout_src, project_path):
return False
if layout_src:
if not self.generate_layout_netlist(layout_path, layout_src, project_path):
return False
# Final check
# Final checks
if not os.path.isfile(layout_path):
print('Error: No netlist generated from magic.')
return
@@ -662,26 +702,27 @@ class LVSManager(ttk.Frame):
with open(layout_path) as ifile:
spitext = ifile.read()
# spilines = spitext.replace(',', '|')
# with open(layout_path, 'w') as ofile:
# ofile.write(spilines)
# Check the netlist to see if the cell to match is a subcircuit. If
# not, then assume it is the top level.
layoutcell = os.path.splitext(os.path.split(self.layout)[1])[0]
is_subckt = False
subname = None
subrex = re.compile('^[^\*]*[ \t]*.subckt[ \t]+([^ \t]+).*$', re.IGNORECASE)
dutlines = spitext.replace('\n+', ' ').splitlines()
for line in dutlines:
lmatch = subrex.match(line)
if lmatch:
subname = lmatch.group(1)
if subname == self.layout:
if subname == layoutcell:
is_subckt = True
break
if is_subckt:
layout_arg = layout_path + ' ' + self.layout
layout_arg = self.layout + ' ' + layoutcell
layout_text = '"' + layout_arg + '"'
elif subname:
layout_arg = self.layout + ' ' + subname
layout_text = '"' + layout_arg + '"'
else:
layout_arg = layout_path
@@ -690,8 +731,39 @@ class LVSManager(ttk.Frame):
if has_vlog:
schem_arg = vlog_path + ' ' + self.schematic
else:
schem_arg = net_path + ' ' + self.schematic
schem_text = '"' + schem_arg + '"'
# Final checks
if not os.path.isfile(net_path):
print('Error: No netlist from schematic.')
return
with open(net_path) as ifile:
spitext = ifile.read()
# Check the netlist to see if the cell to match is a subcircuit. If
# not, then assume it is the top level.
schemcell = os.path.splitext(os.path.split(self.schematic)[1])[0]
subname = None
is_subckt = False
subrex = re.compile('^[^\*]*[ \t]*.subckt[ \t]+([^ \t]+).*$', re.IGNORECASE)
dutlines = spitext.replace('\n+', ' ').splitlines()
for line in dutlines:
lmatch = subrex.match(line)
if lmatch:
subname = lmatch.group(1)
if subname == schemcell:
is_subckt = True
break
if is_subckt:
schem_arg = self.schematic + ' ' + schemcell
schem_text = '"' + schem_arg + '"'
elif subname:
schem_arg = self.schematic + ' ' + subname
schem_text = '"' + schem_arg + '"'
else:
schem_arg = net_path
schem_text = schem_arg
# Remove any previous comparison output file
comp_out_path = os.path.splitext(comp_path)[0] + '.out'
@@ -708,7 +780,7 @@ class LVSManager(ttk.Frame):
self.lvsproc = subprocess.Popen(['netgen', '-batch', 'lvs',
layout_arg, schem_arg,
self.lvs_setup, comp_out_path, '-json', '-blackbox'],
cwd=project_path + '/layout',
cwd=comp_dir,
stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=0)
# This is largely unnecessary as netgen usually runs to completion very quickly.
self.watchclock(comp_path)
+89 -11
View File
@@ -352,7 +352,7 @@ proc netgen::convert_to_json {filename lvs_final} {
close $fjson
}
#----------------------------------------------------------------
#-----------------------------------------------------------------------
# Define the "lvs" command as a way of calling the netgen options
# for standard compare, essentially the same as the old "netcomp"
# standalone program.
@@ -361,14 +361,20 @@ proc netgen::convert_to_json {filename lvs_final} {
# although if the cells have not been read in yet, then the
# original syntax of filename or {filename cellname} is required.
#
# "args" is passed to verify and may therefore contain only the
# value "-list" or nothing. If "-list", then output is returned
# as a nested list.
#----------------------------------------------------------------
# "args" may be "-list", "-json", or "-blackbox".
# "-list" returns output as a nested list.
# "-json" creates a .json-format output file in addition to stdout.
# "-blackbox" treats empty cells as black-box entries.
# "-noflatten={list}" is a list of cells not to flatten if mismatched.
# i.e., the cells are expected to match and any mismatch cannot be
# expected to be resolved by flattening the contents of the mismatched
# cells.
#-----------------------------------------------------------------------
proc netgen::lvs { name1 name2 {setupfile setup.tcl} {logfile comp.out} args} {
set dolist 0
set dojson 0
set noflat {}
foreach arg $args {
if {$arg == "-list"} {
puts stdout "Generating list result"
@@ -382,6 +388,30 @@ proc netgen::lvs { name1 name2 {setupfile setup.tcl} {logfile comp.out} args} {
} elseif {$arg == "-blackbox"} {
puts stdout "Treating empty subcircuits as black-box cells"
netgen::model blackbox on
} elseif {[string first "-noflatten=" $arg] == 0} {
set value [string range $arg 11 end]
# If argument is a filename then read the list of cells from it;
# otherwise, argument is the list of files itself in quotes or
# braces.
if {![catch {file exists $value}]} {
if {![catch {open $value r} fnf]} {
while {[gets $fnf line] >= 0} {
if {[lindex $line 0] != "#"} {
foreach cell $line {
lappend noflat $cell
}
}
}
close $fnf
} else {
puts stderr "Cannot open file $value for reading cell list."
}
} else {
set noflat [string trim $value \"\{\}]
}
if {[llength $noflat] > 0} {
puts stdout "Will not flatten these subcells: $noflat"
}
}
}
@@ -501,6 +531,7 @@ proc netgen::lvs { name1 name2 {setupfile setup.tcl} {logfile comp.out} args} {
return
}
set properr {}
set matcherr {}
set pinsgood 0
while {$endval != {}} {
if {$dolist == 1} {
@@ -529,9 +560,30 @@ proc netgen::lvs { name1 name2 {setupfile setup.tcl} {logfile comp.out} args} {
# Flatten the non-matching subcircuit (but not the top-level cells)
if {[netgen::print queue] != {}} {
netgen::log put " Flattening non-matched subcircuits $endval"
netgen::flatten class "[lindex $endval 0] $fnum1"
netgen::flatten class "[lindex $endval 1] $fnum2"
if {([lsearch $noflat [lindex $endval 0]] == -1) &&
([lsearch $noflat [lindex $endval 1]] == -1)} {
netgen::log put " Flattening non-matched subcircuits $endval"
netgen::flatten class "[lindex $endval 0] $fnum1"
netgen::flatten class "[lindex $endval 1] $fnum2"
} else {
netgen::log put " Continuing with black-boxed subcircuits $endval"
lappend matcherr [lindex $endval 0]
# Match pins
netgen::log echo off
if {$dolist == 1} {
set result [equate -list -force pins "$fnum1 [lindex $endval 0]" \
"$fnum2 [lindex $endval 1]"]
} else {
set result [equate -force pins "$fnum1 [lindex $endval 0]" \
"$fnum2 [lindex $endval 1]"]
}
if {$result != 0} {
equate classes "$fnum1 [lindex $endval 0]" \
"$fnum2 [lindex $endval 1]"
}
set pinsgood $result
netgen::log echo on
}
}
} else {
# Match pins
@@ -554,9 +606,32 @@ proc netgen::lvs { name1 name2 {setupfile setup.tcl} {logfile comp.out} args} {
} else {
# Flatten the non-matching subcircuit (but not the top-level cells)
if {[netgen::print queue] != {}} {
netgen::log put " Flattening non-matched subcircuits $endval"
netgen::flatten class "[lindex $endval 0] $fnum1"
netgen::flatten class "[lindex $endval 1] $fnum2"
if {([lsearch $noflat [lindex $endval 1]] == -1) &&
([lsearch $noflat [lindex $endval 1]] == -1)} {
netgen::log put " Flattening non-matched subcircuits $endval"
netgen::flatten class "[lindex $endval 0] $fnum1"
netgen::flatten class "[lindex $endval 1] $fnum2"
} else {
netgen::log put " Continuing with black-boxed subcircuits $endval"
lappend matcherr [lindex $endval 0]
netgen::log put " Continuing with black-boxed subcircuits $endval"
lappend matcherr [lindex $endval 0]
# Match pins
netgen::log echo off
if {$dolist == 1} {
set result [equate -list -force pins "$fnum1 [lindex $endval 0]" \
"$fnum2 [lindex $endval 1]"]
} else {
set result [equate -force pins "$fnum1 [lindex $endval 0]" \
"$fnum2 [lindex $endval 1]"]
}
if {$result != 0} {
equate classes "$fnum1 [lindex $endval 0]" \
"$fnum2 [lindex $endval 1]"
}
set pinsgood $result
netgen::log echo on
}
}
}
netgen::log echo off
@@ -579,6 +654,9 @@ proc netgen::lvs { name1 name2 {setupfile setup.tcl} {logfile comp.out} args} {
if {$properr != {}} {
netgen::log put "The following cells had property errors: $properr\n"
}
if {$matcherr != {}} {
netgen::log put "The following subcells failed to match: $matcherr\n"
}
if {$dolog} {
netgen::log end
}
+46 -12
View File
@@ -2457,8 +2457,7 @@ _netcmp_run(ClientData clientData,
automorphisms = ResolveAutomorphsByProperty();
if (automorphisms == 0)
Fprintf(stdout, "Netlists match uniquely.\n");
else {
else if (automorphisms > 0) {
// Next, attempt to resolve automorphisms uniquely by
// using the pin names
automorphisms = ResolveAutomorphsByPin();
@@ -2466,14 +2465,17 @@ _netcmp_run(ClientData clientData,
if (automorphisms == 0)
Fprintf(stdout, "Netlists match uniquely.\n");
else
else if (automorphisms > 0) {
// Anything left is truly indistinguishable
Fprintf(stdout, "Netlists match with %d symmetr%s.\n",
automorphisms, (automorphisms == 1) ? "y" : "ies");
while ((automorphisms = ResolveAutomorphisms()) > 0);
if (automorphisms == -1) Fprintf(stdout, "Netlists do not match.\n");
else Fprintf(stdout, "Circuits match correctly.\n");
while ((automorphisms = ResolveAutomorphisms()) > 0);
}
if (automorphisms == -1)
Fprintf(stdout, "Netlists do not match.\n");
else
Fprintf(stdout, "Circuits match correctly.\n");
}
if (PropertyErrorDetected) {
Fprintf(stdout, "There were property errors.\n");
@@ -2827,11 +2829,13 @@ _netcmp_equate(ClientData clientData,
char *name1 = NULL, *name2 = NULL, *optstart;
struct nlist *tp1, *tp2, *SaveC1, *SaveC2;
struct objlist *ob1, *ob2;
struct ElementClass *saveEclass = NULL;
struct NodeClass *saveNclass = NULL;
int file1, file2;
int i, l1, l2, ltest, lent, dolist = 0;
int i, l1, l2, ltest, lent, dolist = 0, doforce = 0;
Tcl_Obj *tobj1, *tobj2, *tobj3;
if (objc > 1) {
while (objc > 1) {
optstart = Tcl_GetString(objv[1]);
if (*optstart == '-') optstart++;
if (!strcmp(optstart, "list")) {
@@ -2839,6 +2843,13 @@ _netcmp_equate(ClientData clientData,
objv++;
objc--;
}
else if (!strcmp(optstart, "force")) {
doforce = 1;
objv++;
objc--;
}
else
break;
}
if ((objc != 2) && (objc != 4) && (objc != 6)) {
@@ -2996,6 +3007,12 @@ _netcmp_equate(ClientData clientData,
break;
case PINS_IDX:
if ((ElementClasses != NULL) && (doforce == TRUE)) {
saveEclass = ElementClasses;
saveNclass = NodeClasses;
ElementClasses = NULL;
NodeClasses = NULL;
}
if ((ElementClasses == NULL) && (auto_blackbox == FALSE)) {
if (CurrentCell == NULL) {
Fprintf(stderr, "Equate elements: no current cell.\n");
@@ -3058,6 +3075,12 @@ _netcmp_equate(ClientData clientData,
/* Recover temporarily set global variables (see above) */
Circuit1 = SaveC1;
Circuit2 = SaveC2;
/* Recover ElementClasses if forcing pins on mismatched circuits */
if (doforce == TRUE) {
ElementClasses = saveEclass;
NodeClasses = saveNclass;
}
}
break;
@@ -3320,10 +3343,10 @@ _netcmp_property(ClientData clientData,
int result, index, idx2;
char *suboptions[] = {
"integer", "double", "value", "string", NULL
"integer", "double", "value", "string", "expression", NULL
};
enum SubOptionIdx {
INTEGER_IDX, DOUBLE_IDX, VALUE_IDX, STRING_IDX
INTEGER_IDX, DOUBLE_IDX, VALUE_IDX, STRING_IDX, EXPRESSION_IDX
};
/* Note: "merge" has been deprecated, but kept for backwards compatibility. */
@@ -3417,9 +3440,15 @@ _netcmp_property(ClientData clientData,
GlobalParallelNone = FALSE;
SetParallelCombine(TRUE);
}
else if (!strcmp(Tcl_GetString(objv[2]), "connected")) {
GlobalParallelOpen = FALSE;
}
else if (!strcmp(Tcl_GetString(objv[2]), "open")) {
GlobalParallelOpen = TRUE;
}
else {
Tcl_SetResult(interp, "Bad option, should be property parallel none|all",
NULL);
Tcl_SetResult(interp, "Bad option, should be property parallel "
"none|all|connected", NULL);
return TCL_ERROR;
}
return TCL_OK;
@@ -3706,6 +3735,11 @@ _netcmp_property(ClientData clientData,
PropertyString(tp->name, fnum,
Tcl_GetString(tobj1), ival, NULL);
break;
case EXPRESSION_IDX:
PropertyString(tp->name, fnum,
Tcl_GetString(tobj1), 0,
Tcl_GetString(tobj3));
break;
}
break;
}