/*
 * Electric(tm) VLSI Design System
 *
 * File: iosuei.c
 * Input/output aid: SUE (Schematic User Environment) reader
 * Written by: Steven M. Rubin, Static Free Software
 *
 * Copyright (c) 2000 Static Free Software.
 *
 * Electric(tm) is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Electric(tm); see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, Mass 02111-1307, USA.
 *
 * Static Free Software
 * 4119 Alpine Road
 * Portola Valley, California 94028
 * info@staticfreesoft.com
 */

#include "config.h"
#if IOSUE

#include "global.h"
#include "edialogs.h"
#include "egraphics.h"
#include "eio.h"
#include "tech.h"
#include "tecart.h"
#include "tecgen.h"
#include "tecschem.h"
#include "network.h"
#include "usr.h"

/* #define SHOWORIGINAL 1 */		/* uncomment to show original nodes */

#define	E0     (WHOLE/8)	/*  0.125 */
#define	S1     (T1+E0)		/*  1.875 */
#define F3     (K3+H0+E0)	/*  3.625 */
#define	S6     (T6+E0)		/*  6.875 */
#define	Q11    (K11+Q0)		/* 11.25 */

/*************** SUE EQUIVALENCES ***************/

typedef struct
{
	char   *portname;
	INTBIG  xoffset;
	INTBIG  yoffset;
} SUEEXTRAWIRE;

typedef struct
{
	char         *suename;
	NODEPROTO   **intproto;
	INTSML        netateoutput;
	INTSML        rotation;
	INTBIG        xoffset;
	INTBIG        yoffset;
	char         *detailvar;
	char         *detailvalue;
	INTBIG        numextrawires;
	SUEEXTRAWIRE *extrawires;
} SUEEQUIV;

SUEEXTRAWIRE io_suetransistorwires[] =
{
	{"d",    K3,   0},
	{"s",   -K3,   0},
	{"g",     0,  H4}
};

SUEEXTRAWIRE io_sueresistorwires[] =
{
	{"a",   -K3,   0},
	{"b",    K3,   0}
};

SUEEXTRAWIRE io_suecapacitorwires[] =
{
	{"a",     0,  T1},
	{"b",     0, -T1}
};

SUEEXTRAWIRE io_suesourcewires[] =
{
	{"minus", 0, -Q1},
	{"plus",  0,  H1}
};

SUEEXTRAWIRE io_suetwoportwires[] =
{
	{"a",  -Q11,  F3},
	{"b",  -Q11, -F3},
	{"x",   Q11,  F3},
	{"y",   Q11, -F3}
};

SUEEQUIV io_sueequivs[] =
{
	/* name         primitive          NEG ANG    X   Y  VAR                      VALUE  WIR EXTRA-WIRES */
#if 0
	{"nand2_my",   &sch_andprim,        1,   0,   0,  0, 0,                       0,      0, 0},
	{"nand3_my",   &sch_andprim,        1,   0,   0,  0, 0,                       0,      0, 0},
	{"nor2_my",    &sch_orprim,         1,   0,   0,  0, 0,                       0,      0, 0},
	{"nor3_my",    &sch_orprim,         1,   0,   0,  0, 0,                       0,      0, 0},
	{"invert2",    &sch_bufprim,        1,   0,   0,  0, 0,                       0,      0, 0},
#endif
	{"pmos10",     &sch_transistorprim, 0, 900, -K2,  0, "SCHEM_transistor_type", "pmos", 3, io_suetransistorwires},
	{"pmos4",      &sch_transistorprim, 0, 900, -K2,  0, "SCHEM_transistor_type", "pmos", 3, io_suetransistorwires},
	{"pmos",       &sch_transistorprim, 0, 900, -K2,  0, "SCHEM_transistor_type", "pmos", 3, io_suetransistorwires},
	{"nmos10",     &sch_transistorprim, 0, 900, -K2,  0, "SCHEM_transistor_type", "nmos", 3, io_suetransistorwires},
	{"nmos4",      &sch_transistorprim, 0, 900, -K2,  0, "SCHEM_transistor_type", "nmos", 3, io_suetransistorwires},
	{"nmos",       &sch_transistorprim, 0, 900, -K2,  0, "SCHEM_transistor_type", "nmos", 3, io_suetransistorwires},
	{"capacitor",  &sch_capacitorprim,  0,   0,   0,  0, 0,                       0,      2, io_suecapacitorwires},
	{"resistor",   &sch_resistorprim,   0, 900,   0,  0, 0,                       0,      2, io_sueresistorwires},
	{"inductor",   &sch_inductorprim,   0,   0,   0,  0, 0,                       0,      0, 0},
	{"dc_supply",  &sch_sourceprim,     0,   0,   0, H0, "SCHEM_source",          "v",    2, io_suesourcewires},
	{"dc_Isupply", &sch_sourceprim,     0,   0,   0, H0, "SCHEM_source",          "c",    2, io_suesourcewires},
	{"ammeter",    &sch_sourceprim,     0,   0,   0,  0, "SCHEM_source",          "cm",   0, 0},
	{"cccs",       &sch_twoportprim,    0,   0,  Q1,-S6, "SCHEM_twoport_type",    "f",    4, io_suetwoportwires},
	{"ccvs",       &sch_twoportprim,    0,   0,  Q1,-S6, "SCHEM_twoport_type",    "h",    4, io_suetwoportwires},
	{"vcvs",       &sch_twoportprim,    0,   0,  Q1,-S6, "SCHEM_twoport_type",    "u",    4, io_suetwoportwires},
	{"vccs",       &sch_twoportprim,    0,   0, -S1,-K5, "SCHEM_twoport_type",    "g",    0, 0},
	{0, 0, 0, 0, 0, 0, 0, 0, 0}
};

/*************** SUE WIRES ***************/

#define NOSUEWIRE ((SUEWIRE *)-1)

typedef struct Isuewire
{
	INTBIG           x[2], y[2];
	NODEINST        *ni[2];
	PORTPROTO       *pp[2];
	ARCPROTO        *proto;
	struct Isuewire *nextsuewire;
} SUEWIRE;

SUEWIRE *io_suefreewire = NOSUEWIRE;

/*************** SUE NETWORKS ***************/

#define NOSUENET ((SUENET *)-1)

typedef struct Isuenet
{
	INTBIG          x, y;
	char           *label;
	struct Isuenet *nextsuenet;
} SUENET;

SUENET *io_suefreenet = NOSUENET;

/*************** MISCELLANEOUS ***************/

#define MAXLINE       300			/* maximum characters on an input line */
#define MAXKEYWORDS    50			/* maximum keywords on an input line */
#define MAXICONPOINTS  25			/* maximum coordinates on an "icon_line" line */

INTBIG io_suelineno;
INTBIG io_suefilesize;
char   io_suelastline[MAXLINE];
char   io_sueorigline[MAXLINE];
char   io_suecurline[MAXLINE];
char **io_suedirectories;
INTBIG io_suenumdirectories = 0;
FILE  *io_suefilein;

INTSML     io_sueadddirectory(char *directory);
INTBIG     io_suearraylength(char *name);
void       io_suecleardirectories(void);
NODEPROTO *io_suecreatepseudo(NODEPROTO *proto, INTBIG count);
INTSML     io_suefindnode(INTBIG *x, INTBIG *y, INTBIG ox, INTBIG oy, NODEPROTO *np,
			NODEINST **ni, PORTPROTO **pp, NODEINST *notthisnode);
NODEINST  *io_suefindpinnode(INTBIG x, INTBIG y, NODEPROTO *np, PORTPROTO **thepp);
void       io_suefreenets(SUENET *firstsn);
void       io_suefreewires(SUEWIRE *firstsw);
INTSML     io_suegetnextline(char **keywords, INTSML curlydepth);
NODEPROTO *io_suegetnodeproto(LIBRARY *lib, char *protoname);
void       io_suekillnet(SUENET *sn);
void       io_suekillwire(SUEWIRE *sw);
INTBIG     io_suemakex(INTBIG x);
INTBIG     io_suemakey(INTBIG y);
PORTPROTO *io_suenewexport(NODEPROTO *facet, NODEINST *ni, PORTPROTO *pp, char *thename);
SUENET    *io_suenewnet(void);
SUEWIRE   *io_suenewwire(void);
void       io_sueplacenets(SUENET *firstsuenet, NODEPROTO *facet);
void       io_sueparseparameters(char **keywords, INTBIG count, INTBIG *x, INTBIG *y,
			INTSML *rot, INTSML *trn, INTBIG *type, char **thename, char **thelabel);
void       io_sueplacewires(SUEWIRE *firstsuewire, SUENET *firstsuenet, NODEPROTO *facet);
NODEPROTO *io_suereadfile(LIBRARY *lib, char *facetname);
NODEPROTO *io_suereadfromdisk(LIBRARY *lib, char *name);
PORTPROTO *io_suewiredport(NODEINST *ni, INTBIG *x, INTBIG *y, INTBIG ox, INTBIG oy);

/*
 * Routine to free all memory associated with this module.
 */
void io_freesuememory(void)
{
	SUEWIRE *sw;
	SUENET *sn;

	while (io_suefreewire != NOSUEWIRE)
	{
		sw = io_suefreewire;
		io_suefreewire = io_suefreewire->nextsuewire;
		efree((char *)sw);
	}
	while (io_suefreenet != NOSUENET)
	{
		sn = io_suefreenet;
		io_suefreenet = io_suefreenet->nextsuenet;
		efree((char *)sn);
	}
	io_suecleardirectories();
}

INTSML io_readsuelibrary(LIBRARY *lib)
{
	REGISTER INTBIG len, i, filecount;
	REGISTER NODEPROTO *topfacet;
	extern DIALOG us_progressdialog;
	char facetname[300], dirname[300], topdirname[300], truesuefile[300],
		*filename, **filelist;

	/* open the file */
	io_suefilein = xopen(lib->libfile, FILETYPESUE, "", &filename);
	if (io_suefilein == 0)
	{
		ttyputerr("File %s not found", lib->libfile);
		return(1);
	}

	/* determine the facet name */
	strcpy(truesuefile, filename);
	strcpy(facetname, filename);
	len = strlen(facetname);
	if (namesame(&facetname[len-4], ".sue") == 0)
		facetname[len-4] = 0;
	for(i = strlen(facetname)-1; i>0; i--)
		if (facetname[i] == DIRSEP) break;
	i++;
	strcpy(facetname, &facetname[i]);

	/* initialize the number of directories that need to be searched */
	io_suecleardirectories();

	/* determine the current directory */
	strcpy(topdirname, filename);
	len = strlen(topdirname);
	for(i = len-1; i>0; i--)
		if (topdirname[i] == DIRSEP) break;
	topdirname[i+1] = 0;
	if (io_sueadddirectory(topdirname) != 0) return(1);

	/* find all subdirectories that start with "suelib_" and include them in the search */
	filecount = filesindirectory(topdirname, &filelist);
	for(i=0; i<filecount; i++)
	{
		if (namesamen(filelist[i], "suelib_", 7) != 0) continue;
		strcpy(dirname, topdirname);
		strcat(dirname, filelist[i]);
		if (fileexistence(dirname) != 2) continue;
		len = strlen(dirname);
		dirname[len++] = DIRSEP;
		dirname[len] = 0;
		if (io_sueadddirectory(dirname) != 0) return(1);
	}

	/* see if the current directory is inside of a SUELIB */
	len = strlen(topdirname);
	for(i = len-2; i>0; i--)
		if (topdirname[i] == DIRSEP) break;
	i++;
	if (namesamen(&topdirname[i], "suelib_", 7) == 0)
	{
		topdirname[i] = 0;
		filecount = filesindirectory(topdirname, &filelist);
		for(i=0; i<filecount; i++)
		{
			if (namesamen(filelist[i], "suelib_", 7) != 0) continue;
			strcpy(dirname, topdirname);
			strcat(dirname, filelist[i]);
			if (fileexistence(dirname) != 2) continue;
			len = strlen(dirname);
			dirname[len++] = DIRSEP;
			dirname[len] = 0;
			if (io_sueadddirectory(dirname) != 0) return(1);
		}
	}

	/* prepare for input */
	io_suefilesize = filesize(io_suefilein);
	if (DiaInitDialog(&us_progressdialog) != 0)
	{
		xclose(io_suefilein);
		return(1);
	}
	io_suelineno = 0;

	/* read the file */
	DiaPercent(1, 0);
	DiaSetText(2, "Reading SUE file...");
	topfacet = io_suereadfile(lib, facetname);
	if (topfacet != NONODEPROTO)
		lib->curnodeproto = topfacet;

	/* clean up */
	DiaDoneDialog();
	xclose(io_suefilein);
	ttyputmsg("%s read", truesuefile);

	return(0);
}

/*
 * Routine to read the SUE file in "f"/
 */
NODEPROTO *io_suereadfile(LIBRARY *lib, char *facetname)
{
	char *keywords[MAXKEYWORDS], *thename, *thelabel, *namestring;
	char suevarname[200], *detailvar, *detailvalue,
		*pt, *cpt, *startkey, save;
	char **argnames, **argvalues, **newargnames, **newargvalues;
	INTBIG outline[MAXICONPOINTS*2], x, y, type, dx, dy, px, py, pinx, piny, xoff, yoff,
		numargs, namestrlen;
	REGISTER INTBIG count, i, lx, hx, ly, hy, curly, xshrink, yshrink,
		varcount, varindex, varoffset, pos, invertoutput,
		keycount, cx, cy, p1x, p1y, p2x, p2y, start, extent, numextrawires, len;
	INTSML rot, trn, rotation;
	XARRAY trans;
	double rextent, rstart;
	REGISTER NODEPROTO *facet, *schemfacet, *iconfacet, *proto, *np;
	REGISTER PORTPROTO *pp, *ppt;
	PORTPROTO *npp;
	REGISTER VARIABLE *var;
	REGISTER NODEINST *ni;
	NODEINST *nni;
	REGISTER ARCINST *ai;
	REGISTER SUEWIRE *sw, *firstsuewire;
	REGISTER SUENET *sn, *firstsuenet;
	REGISTER SUEEXTRAWIRE *extrawires;

	firstsuewire = NOSUEWIRE;
	firstsuenet = NOSUENET;
	schemfacet = iconfacet = facet = NONODEPROTO;
	io_suelastline[0] = 0;
	numargs = 0;
	namestrlen = 0;
	for(;;)
	{
	   /* get the next line of text */
		count = io_suegetnextline(keywords, 0);
		if (count < 0) break;
		if (count == 0) continue;

		/* handle "proc" for defining views */
		if (namesame(keywords[0], "proc") == 0)
		{
			/* write any wires from the last proc */
			if (facet != NONODEPROTO)
			{
				io_sueplacewires(firstsuewire, firstsuenet, facet);
				io_suefreewires(firstsuewire);
				io_sueplacenets(firstsuenet, facet);
				io_suefreenets(firstsuenet);
				firstsuewire = NOSUEWIRE;
				firstsuenet = NOSUENET;
			}

			if (count < 2)
			{
				ttyputerr("Facet %s, line %d: 'proc' is missing arguments: %s",
					facetname, io_suelineno, io_sueorigline);
				continue;
			}

			if (namesamen(keywords[1], "SCHEMATIC_", 10) == 0)
			{
				/* create the schematic facet */
				(void)initinfstr();
				if (namesame(&keywords[1][10], "[get_file_name]") == 0)
					(void)addstringtoinfstr(facetname); else
						(void)addstringtoinfstr(&keywords[1][10]);
				(void)addstringtoinfstr("{sch}");
				schemfacet = facet = newnodeproto(returninfstr(), lib);
			} else if (namesamen(keywords[1], "ICON_", 5) == 0)
			{
				/* create the icon facet */
				(void)initinfstr();
				if (namesame(&keywords[1][5], "[get_file_name]") == 0)
					(void)addstringtoinfstr(facetname); else
						(void)addstringtoinfstr(&keywords[1][5]);
				(void)addstringtoinfstr("{ic}");
				iconfacet = facet = newnodeproto(returninfstr(), lib);
			} else
			{
				ttyputerr("Facet %s, line %d: unknown 'proc' statement: %s",
					facetname, io_suelineno, io_sueorigline);
			}
			continue;
		}

		/* handle "make" for defining components */
		if (namesame(keywords[0], "make") == 0)
		{
			if (count < 2)
			{
				ttyputerr("Facet %s, line %d: 'make' is missing arguments: %s",
					facetname, io_suelineno, io_sueorigline);
				continue;
			}

			/* extract parameters */
			io_sueparseparameters(&keywords[2], count-2, &x, &y, &rot, &trn,
				&type, &thename, &thelabel);

			/* save the name string */
			if (thename != 0)
			{
				len = strlen(thename) + 1;
				if (len > namestrlen)
				{
					if (namestrlen > 0) efree(namestring);
					namestring = (char *)emalloc(len, el_tempcluster);
					namestrlen = len;
				}
				strcpy(namestring, thename);
				thename = namestring;
			}


			/* ignore self-references */
			if (namesame(keywords[1], facetname) == 0)
			{
				if (x != 0 || y != 0)
				{
					/* place facet-center */
				}
				continue;
			}

			/* special case for network names: queue them */
			if (namesame(keywords[1], "name_net_m") == 0 ||
				namesame(keywords[1], "name_net_s") == 0 ||
				namesame(keywords[1], "name_net") == 0)
			{
				sn = io_suenewnet();
				sn->x = x;
				sn->y = y;
				(void)allocstring(&sn->label, thename, io_aid->cluster);
				sn->nextsuenet = firstsuenet;
				firstsuenet = sn;
				continue;
			}

			/* first check for special names */
			proto = NONODEPROTO;
			invertoutput = 0;
			rotation = 0;
			xoff = yoff = 0;
			xshrink = yshrink = 0;
			detailvar = 0;
			detailvalue = 0;
			numextrawires = 0;
			extrawires = 0;
			if (namesame(keywords[1], "inout") == 0)
			{
				proto = sch_offpageprim;
				makeangle(rot, trn, trans);
				xform(K2, 0, &xoff, &yoff, trans);
				xoff = muldiv(xoff, sch_tech->deflambda, WHOLE);
				yoff = muldiv(yoff, sch_tech->deflambda, WHOLE);
			} else if (namesame(keywords[1], "input") == 0)
			{
				proto = sch_offpageprim;
				makeangle(rot, trn, trans);
				xform(-K2, 0, &xoff, &yoff, trans);
				xoff = muldiv(xoff, sch_tech->deflambda, WHOLE);
				yoff = muldiv(yoff, sch_tech->deflambda, WHOLE);
			} else if (namesame(keywords[1], "output") == 0)
			{
				proto = sch_offpageprim;
				makeangle(rot, trn, trans);
				xform(K2, 0, &xoff, &yoff, trans);
				xoff = muldiv(xoff, sch_tech->deflambda, WHOLE);
				yoff = muldiv(yoff, sch_tech->deflambda, WHOLE);
			} else if (namesame(keywords[1], "rename_net") == 0)
			{
				proto = sch_wirepinprim;
			} else if (namesame(keywords[1], "global") == 0)
			{
				if (net_buswidth(thename) > 1) proto = sch_buspinprim; else
				{
					proto = sch_wirepinprim;
					if (namesame(thename, "gnd") == 0)
					{
						makeangle(rot, trn, trans);
						xform(0, -K2, &xoff, &yoff, trans);
						xoff = muldiv(xoff, sch_tech->deflambda, WHOLE);
						yoff = muldiv(yoff, sch_tech->deflambda, WHOLE);
						proto = sch_gndprim;
					}
					if (namesame(thename, "vdd") == 0)
						proto = sch_pwrprim;
				}
			} else if (namesame(keywords[1], "join_net") == 0)
			{
				proto = sch_wireconprim;
				xshrink = -K2;
				makeangle(rot, trn, trans);
				xform(Q1, 0, &xoff, &yoff, trans);
				xoff = muldiv(xoff, sch_tech->deflambda, WHOLE);
				yoff = muldiv(yoff, sch_tech->deflambda, WHOLE);
			}

			/* now check for internal associations to known primitives */
			if (proto == NONODEPROTO)
			{
				for(i=0; io_sueequivs[i].suename != 0; i++)
					if (namesame(keywords[1], io_sueequivs[i].suename) == 0) break;
				if (io_sueequivs[i].suename != 0)
				{
					proto = *io_sueequivs[i].intproto;
					invertoutput = io_sueequivs[i].netateoutput;
					rotation = io_sueequivs[i].rotation;
					makeangle(rot, trn, trans);
					xform(io_sueequivs[i].xoffset, io_sueequivs[i].yoffset, &xoff, &yoff, trans);
					xoff = muldiv(xoff, sch_tech->deflambda, WHOLE);
					yoff = muldiv(yoff, sch_tech->deflambda, WHOLE);
					rot += rotation;
					if (rot >= 3600) rot -= 3600;
					detailvar = io_sueequivs[i].detailvar;
					detailvalue = io_sueequivs[i].detailvalue;
					numextrawires = io_sueequivs[i].numextrawires;
					extrawires = io_sueequivs[i].extrawires;
#ifdef SHOWORIGINAL
					defaultnodesize(proto, &px, &py);
					lx = x - px/2 + xoff;   hx = lx + px;
					ly = y - py/2 + yoff;   hy = ly + py;
					ni = newnodeinst(proto, lx, hx, ly, hy, trn, rot, facet);
					if (ni == NONODEINST) continue;
					if (detailvalue != 0)
						setval((INTBIG)ni, VNODEINST, detailvar,
							(INTBIG)detailvalue, VSTRING);
					endobjectchange((INTBIG)ni, VNODEINST);
					proto = NONODEPROTO;
					invertoutput = 0;
					rotation = 0;
					xoff = yoff = 0;
					detailvar = 0;
					detailvalue = 0;
					numextrawires = 0;
					extrawires = 0;
#endif
				}
			}

			/* now check for references to facets */
			if (proto == NONODEPROTO)
			{
				/* see if the reference is arrayed */
				len = io_suearraylength(thename);
				if (len > 0)
				{
					/* arrayed node: search for the pseudo version */
					strcpy(suevarname, keywords[1]);
					strcat(suevarname, thename);
					sprintf(suevarname, "%s-Size-%ld", keywords[1], len);
					proto = io_suegetnodeproto(lib, suevarname);
					if (proto == NONODEPROTO)
					{
						/* no pseudo version: create it from unarrayed version */
						proto = io_suegetnodeproto(lib, keywords[1]);
						if (proto == NONODEPROTO)
							proto = io_suereadfromdisk(lib, keywords[1]);
						if (proto != NONODEPROTO)
							proto = io_suecreatepseudo(proto, len);
					}
				} else
				{
					/* nonarrayed node: find it or read it from disk */
					proto = io_suegetnodeproto(lib, keywords[1]);
					if (proto == NONODEPROTO)
						proto = io_suereadfromdisk(lib, keywords[1]);
				}

				/* set proper offsets for the facet */
				if (proto != NONODEPROTO)
				{
					np = iconview(proto);
					if (np != NONODEPROTO) proto = np;
					xoff = (proto->lowx + proto->highx) / 2;
					yoff = (proto->lowy + proto->highy) / 2;
					makeangle(rot, trn, trans);
					xform(xoff, yoff, &xoff, &yoff, trans);
				}
			}

			/* ignore "title" specifications */
			if (namesamen(keywords[1], "title_", 6) == 0) continue;

			/* stop now if SUE node is unknown */
			if (proto == NONODEPROTO)
			{
				ttyputmsg("Cannot make '%s' in facet %s", keywords[1], describenodeproto(facet));
				continue;
			}

			/* create the instance */
			defaultnodesize(proto, &px, &py);
			px -= muldiv(xshrink, sch_tech->deflambda, WHOLE);
			py -= muldiv(yshrink, sch_tech->deflambda, WHOLE);
			lx = x - px/2 + xoff;   hx = lx + px;
			ly = y - py/2 + yoff;   hy = ly + py;
			ni = newnodeinst(proto, lx, hx, ly, hy, trn, rot, facet);
			if (ni == NONODEINST) continue;
			if (detailvalue != 0)
				setval((INTBIG)ni, VNODEINST, detailvar,
					(INTBIG)detailvalue, VSTRING);
			ni->temp1 = invertoutput;
			if (proto->primindex == 0 && proto->cellview == el_iconview)
				ni->userbits |= NEXPAND;
			endobjectchange((INTBIG)ni, VNODEINST);

			/* add any extra wires to the node */
			for(i=0; i<numextrawires; i++)
			{
				for(pp = proto->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
					if (namesame(pp->protoname, extrawires[i].portname) == 0) break;
				if (pp == NOPORTPROTO) continue;
				portposition(ni, pp, &x, &y);
				makeangle(ni->rotation, ni->transpose, trans);
				px = muldiv(extrawires[i].xoffset, sch_tech->deflambda, WHOLE);
				py = muldiv(extrawires[i].yoffset, sch_tech->deflambda, WHOLE);
				xform(px, py, &dx, &dy, trans);
				defaultnodesize(sch_wirepinprim, &px, &py);
				pinx = x + dx;   piny = y + dy;
				nni = io_suefindpinnode(pinx, piny, facet, &npp);
				if (nni == NONODEINST)
				{
					lx = pinx - px/2;   hx = lx + px;
					ly = piny - py/2;   hy = ly + py;
					nni = newnodeinst(sch_wirepinprim, lx, hx, ly, hy, 0, 0, facet);
					if (nni == NONODEINST) continue;
					npp = nni->proto->firstportproto;
				}
				ai = newarcinst(sch_wirearc, 0, us_makearcuserbits(sch_wirearc),
					ni, pp, x, y, nni, npp, pinx, piny, facet);
				if (ai == NOARCINST)
				{
					ttyputerr("Error adding extra wires to node %s", keywords[1]);
					break;
				}
			}

			/* handle names assigned to the node */
			if (thename != 0)
			{
				/* export a port if this is an input, output, inout */
				if (proto == sch_offpageprim && thename != 0)
				{
					pp = proto->firstportproto;
					if (namesame(keywords[1], "output") == 0) pp = pp->nextportproto;
					ppt = io_suenewexport(facet, ni, pp, thename);
					if (ppt == NOPORTPROTO)
					{
						ttyputmsg("Facet %s, line %d, could not create port %s", 
							facetname, io_suelineno, thename);
					} else
					{
						endobjectchange((INTBIG)ppt, VPORTPROTO);
						ppt->textdescript = defaulttextdescript(NOGEOM);
					}
				} else
				{
					/* just name the node */
					(void)setvalkey((INTBIG)ni, VNODEINST, el_node_name, (INTBIG)thename,
						VSTRING|VDISPLAY);
				}
			}

			/* count the variables */
			varcount = 0;
			for(i=2; i<count; i += 2)
			{
				if (keywords[i][0] != '-') continue;
				if (namesame(keywords[i], "-origin") == 0 ||
					namesame(keywords[i], "-orient") == 0 ||
					namesame(keywords[i], "-type") == 0 ||
					namesame(keywords[i], "-name") == 0) continue;
				varcount++;
			}

			/* add variables */
			varindex = 1;
			for(i=2; i<count; i += 2)
			{
				if (keywords[i][0] != '-') continue;
				if (namesame(keywords[i], "-origin") == 0 ||
					namesame(keywords[i], "-orient") == 0 ||
					namesame(keywords[i], "-type") == 0 ||
					namesame(keywords[i], "-name") == 0) continue;
				sprintf(suevarname, "SUE_%s", &keywords[i][1]);
				for(pt = suevarname; *pt != 0; pt++) if (*pt == ' ')
				{
					ttyputmsg("Facet %s, line %d, bad variable name (%s)", 
						facetname, io_suelineno, suevarname);
					break;
				}
				(void)initinfstr();
				if (keywords[i][1] == 'W' && keywords[i][2] != 0)
				{
					(void)addstringtoinfstr(&keywords[i][2]);
					(void)addstringtoinfstr(":");
				}
				(void)addstringtoinfstr(keywords[i+1]);
				var = setval((INTBIG)ni, VNODEINST, suevarname, (INTBIG)returninfstr(),
					VSTRING|VDISPLAY);
				if (var != NOVARIABLE)
				{
					var->textdescript = defaulttextdescript(NOGEOM);
					varoffset = (ni->highy - ni->lowy) / (varcount+1);
					pos = (ni->highy - ni->lowy) / 2 - varindex * varoffset;
					varindex++;
					if (pos < 0)
					{
						pos = -pos;
						var->textdescript |= VTYOFFNEG;
					}
					var->textdescript |= (pos * 4 / sch_tech->deflambda) << VTYOFFSH;
				}
			}
			continue;
		}

		/* handle "make_wire" for defining arcs */
		if (namesame(keywords[0], "make_wire") == 0)
		{
			sw = io_suenewwire();
			sw->x[0] = io_suemakex(atoi(keywords[1]));
			sw->y[0] = io_suemakey(atoi(keywords[2]));
			sw->x[1] = io_suemakex(atoi(keywords[3]));
			sw->y[1] = io_suemakey(atoi(keywords[4]));
			sw->nextsuewire = firstsuewire;
			firstsuewire = sw;
			continue;
		}

		/* handle "icon_term" for defining ports in icons */
		if (namesame(keywords[0], "icon_term") == 0)
		{
			io_sueparseparameters(&keywords[1], count-1, &x, &y, &rot, &trn,
				&type, &thename, &thelabel);
			strcpy(suevarname, thename);

			if (net_buswidth(suevarname) > 1) proto = sch_buspinprim; else
				proto = sch_wirepinprim;
			defaultnodesize(proto, &px, &py);
			lx = x - px/2;   hx = lx + px;
			ly = y - py/2;   hy = ly + py;
			ni = newnodeinst(proto, lx, hx, ly, hy, trn, (INTSML)(rot%3600), facet);
			if (ni == NONODEINST) continue;
			endobjectchange((INTBIG)ni, VNODEINST);

			ppt = io_suenewexport(facet, ni, proto->firstportproto, suevarname);
			if (ppt == NOPORTPROTO)
			{
				ttyputmsg("Facet %s, line %d, could not create port %s", 
					facetname, io_suelineno, suevarname);
			} else
			{
				ppt->textdescript = defaulttextdescript(NOGEOM);
				ppt->userbits = (ppt->userbits & ~STATEBITS) | type;
				endobjectchange((INTBIG)ppt, VPORTPROTO);
			}
			continue;
		}

		/* handle "icon_arc" for defining icon curves */
		if (namesame(keywords[0], "icon_arc") == 0)
		{
			if (count != 9)
			{
				ttyputerr("Facet %s, line %d: needs 9 arguments, has %d: %s",
					facetname, io_suelineno, count, io_sueorigline);
				continue;
			}
			start = 0;   extent = 359;
			p1x = io_suemakex(atoi(keywords[1]));
			p1y = io_suemakey(atoi(keywords[2]));
			p2x = io_suemakex(atoi(keywords[3]));
			p2y = io_suemakey(atoi(keywords[4]));
			if (namesame(keywords[5], "-start") == 0) start = atoi(keywords[6]);
			if (namesame(keywords[7], "-extent") == 0) extent = atoi(keywords[8]);
			lx = mini(p1x, p2x);   hx = maxi(p1x, p2x);
			ly = mini(p1y, p2y);   hy = maxi(p1y, p2y);

			ni = newnodeinst(art_circleprim, lx, hx, ly, hy, 0, 0, facet);
			if (ni == NONODEINST) continue;
			if (extent != 359)
			{
				if (extent < 0)
				{
					start += extent;
					extent = -extent;
				}
				rextent = extent+1;   rextent = rextent * EPI / 180.0;
				rstart = start * EPI / 180.0;
				setarcdegrees(ni, rstart, rextent);
			}
			endobjectchange((INTBIG)ni, VNODEINST);
			continue;
		}

		/* handle "icon_line" for defining icon outlines */
		if (namesame(keywords[0], "icon_line") == 0)
		{
			for(i=1; i<count; i++)
			{
				if ((i%2) != 0) outline[i-1] = io_suemakex(atoi(keywords[i])); else
					outline[i-1] = io_suemakey(atoi(keywords[i]));
			}
			keycount = count / 2;

			/* determine bounds of icon */
			lx = hx = outline[0];
			ly = hy = outline[1];
			for(i=1; i<keycount; i++)
			{
				if (outline[i*2]   < lx) lx = outline[i*2];
				if (outline[i*2]   > hx) hx = outline[i*2];
				if (outline[i*2+1] < ly) ly = outline[i*2+1];
				if (outline[i*2+1] > hy) hy = outline[i*2+1];
			}
			ni = newnodeinst(art_openedpolygonprim, lx, hx, ly, hy, 0, 0, facet);
			if (ni == NONODEINST) return(NONODEPROTO);
			cx = (lx + hx) / 2;  cy = (ly + hy) / 2;
			for(i=0; i<keycount; i++)
			{
				outline[i*2] -= cx;   outline[i*2+1] -= cy;
			}
			(void)setvalkey((INTBIG)ni, VNODEINST, el_trace, (INTBIG)outline,
				VINTEGER|VISARRAY|((keycount*2)<<VLENGTHSH));
			endobjectchange((INTBIG)ni, VNODEINST);
			continue;
		}

		/* handle "icon_setup" for defining variables */
		if (namesame(keywords[0], "icon_setup") == 0)
		{
			/* extract parameters */
			if (namesame(keywords[1], "$args") != 0)
			{
				ttyputerr("Facet %s, line %d: has unrecognized 'icon_setup'",
					facetname, io_suelineno);
				continue;
			}
			pt = keywords[2];
			if (*pt == '{') pt++;
			for(;;)
			{
				while (*pt == ' ') pt++;
				if (*pt == '}' || *pt == 0) break;

				/* collect up to a space or close curly */
				startkey = pt;
				curly = 0;
				for(;;)
				{
					if (curly == 0)
					{
						if (*pt == 0 || *pt == ' ' || *pt == '}') break;
					}
					if (*pt == '{') curly++;
					if (*pt == '}') curly--;
					if (*pt == 0) break;
					pt++;
				}
				save = *pt;
				*pt = 0;

				/* parse the keyword pair in "startkey" */
				i = numargs+1;
				newargnames = (char **)emalloc(i * (sizeof (char *)), el_tempcluster);
				newargvalues = (char **)emalloc(i * (sizeof (char *)), el_tempcluster);
				for(i=0; i<numargs; i++)
				{
					newargnames[i] = argnames[i];
					newargvalues[i] = argvalues[i];
				}
				if (numargs > 0)
				{
					efree((char *)argnames);
					efree((char *)argvalues);
				}
				argnames = newargnames;
				argvalues = newargvalues;
				startkey++;
				for(cpt = startkey; *cpt != 0; cpt++) if (*cpt == ' ') break;
				if (*cpt != 0) *cpt++ = 0;
				(void)allocstring(&argnames[numargs], startkey, el_tempcluster);
				while (*cpt == ' ') cpt++;
				if (*cpt == '{') cpt++;
				startkey = cpt;
				for(cpt = startkey; *cpt != 0; cpt++) if (*cpt == '}') break;
				if (*cpt != 0) *cpt++ = 0;
				(void)allocstring(&argvalues[numargs], startkey, el_tempcluster);
				numargs++;

				*pt = save;
			}
			continue;
		}

		/* handle "icon_property" for defining icon strings */
		if (namesame(keywords[0], "icon_property") == 0)
		{
			/* extract parameters */
			io_sueparseparameters(&keywords[1], count-1, &x, &y, &rot, &trn,
				&type, &thename, &thelabel);
			if (thelabel == 0) continue;

			/* substitute parameters */
			(void)initinfstr();
			for(pt = thelabel; *pt != 0; pt++)
			{
				if (*pt == '$')
				{
					for(i=0; i<numargs; i++)
						if (namesamen(&pt[1], argnames[i], strlen(argnames[i])) == 0) break;
					if (i < numargs)
					{
						(void)addstringtoinfstr(argvalues[i]);
						pt += strlen(argnames[i]);
						continue;
					}
				}
				(void)addtoinfstr(*pt);
			}
			thelabel = returninfstr();

			ni = newnodeinst(gen_invispinprim, x, x, y, y, 0, 0, facet);
			if (ni == NONODEINST) continue;
			var = setvalkey((INTBIG)ni, VNODEINST, art_messagekey, (INTBIG)thelabel, VSTRING|VDISPLAY);
			if (var != NOVARIABLE) var->textdescript = (TXTSMALL << VTSIZESH) | VTPOSCENT;
			endobjectchange((INTBIG)ni, VNODEINST);
			continue;
		}

		/* ignore known keywords */
		if (namesame(keywords[0], "icon_title") == 0 ||
			namesame(keywords[0], "make_text") == 0 ||
			namesame(keywords[0], "make_line") == 0 ||
			namesame(keywords[0], "}") == 0)
		{
			continue;
		}

		ttyputerr("Facet %s, line %d: unknown keyword (%s): %s", facetname,
			io_suelineno, keywords[0], io_sueorigline);
	}

	for(i=0; i<numargs; i++)
	{
		efree(argnames[i]);
		efree(argvalues[i]);
	}
	if (numargs > 0)
	{
		efree((char *)argnames);
		efree((char *)argvalues);
	}
	if (namestrlen > 0) efree(namestring);

	/* cleanup the current facet */
	if (facet != NONODEPROTO)
	{
		io_sueplacewires(firstsuewire, firstsuenet, facet);
		io_suefreewires(firstsuewire);
		io_sueplacenets(firstsuenet, facet);
		io_suefreenets(firstsuenet);
	}

	/* make sure facets are the right size */
	if (schemfacet != NONODEPROTO) (*el_curconstraint->solve)(schemfacet);
	if (iconfacet != NONODEPROTO) (*el_curconstraint->solve)(iconfacet);

	/* return the facet */
	if (schemfacet != NONODEPROTO) return(schemfacet);
	return(iconfacet);

}

/*
 * Routine to parse the "count" parameters in "keywords" and fill in the values
 * that are found.  Fills in:
 * "-origin"  placed into "x" and "y"
 * "-orient"  placed into "rot" and "trn"
 * "-type"    placed into "type"
 * "-name"    placed into "thename".
 * "-label"   placed into "thelabel".
 */
void io_sueparseparameters(char **keywords, INTBIG count, INTBIG *x, INTBIG *y,
	INTSML *rot, INTSML *trn, INTBIG *type, char **thename, char **thelabel)
{
	char *pt;
	REGISTER INTSML doingname;
	REGISTER INTBIG i;

	*x = *y = 0;
	*rot = 0;
	*trn = 0;
	*type = 0;
	*thename = 0;
	*thelabel = 0;
	for(i=0; i<count; i += 2)
	{
		if (namesame(keywords[i], "-origin") == 0)
		{
			pt = keywords[i+1];
			if (*pt == '{') pt++;
			*x = atoi(pt);
			while (*pt != ' ' && *pt != '\t' && *pt != 0) pt++;
			while (*pt == ' ' || *pt == '\t') pt++;
			*y = atoi(pt);
		}
		if (namesame(keywords[i], "-orient") == 0)
		{
			if (namesame(keywords[i+1], "R90")  == 0) { *rot = 900;  } else
			if (namesame(keywords[i+1], "R270") == 0) { *rot = 2700; } else
			if (namesame(keywords[i+1], "RXY")  == 0) { *rot = 1800; } else
			if (namesame(keywords[i+1], "RY")   == 0) { *rot = 900;  *trn = 1; } else
			if (namesame(keywords[i+1], "R90X") == 0) { *rot = 0;    *trn = 1; } else
			if (namesame(keywords[i+1], "R90Y") == 0) { *rot = 1800; *trn = 1; } else
			if (namesame(keywords[i+1], "RX")   == 0) { *rot = 2700; *trn = 1; }
		}
		if (namesame(keywords[i], "-type") == 0)
		{
			if (namesame(keywords[i+1], "input") == 0) *type = INPORT; else
			if (namesame(keywords[i+1], "output") == 0) *type = OUTPORT; else
			if (namesame(keywords[i+1], "inout") == 0) *type = BIDIRPORT;
		}
		if (namesame(keywords[i], "-name") == 0 ||
			namesame(keywords[i], "-label") == 0)
		{
			if (namesame(keywords[i], "-name") == 0) doingname = 1; else
				doingname = 0;
			(void)initinfstr();
			pt = keywords[i+1];
			if (pt[0] != '{') (void)addstringtoinfstr(pt); else
			{
				pt++;
				for(;;)
				{
					while (*pt != 0)
					{
						if (*pt == '}') break;
						(void)addtoinfstr(*pt);
						pt++;
					}
					if (*pt == '}') break;
					(void)addtoinfstr(' ');

					count = io_suegetnextline(keywords, 1);
					if (count < 0) break;
					if (count == 0) continue;
					pt = keywords[0];
					i = -1;
				}
			}
			if (doingname != 0) *thename = returninfstr(); else
				*thelabel = returninfstr();
		}
	}
	*x = io_suemakex(*x);
	*y = io_suemakey(*y);
	*rot = (3600 - *rot) % 3600;
}

/*
 * Routine to find facet "protoname" in library "lib".
 */
NODEPROTO *io_suegetnodeproto(LIBRARY *lib, char *protoname)
{
	REGISTER CELL *c;

	for(c = lib->firstcell; c != NOCELL; c = c->nextcell)
		if (namesame(protoname, c->cellname) == 0) break;
	if (c == NOCELL) return(NONODEPROTO);
	return(c->firstincell);
}

/*
 * Routine to create a port called "thename" on port "pp" of node "ni" in facet "facet".
 * The name is modified if it already exists.
 */
PORTPROTO *io_suenewexport(NODEPROTO *facet, NODEINST *ni, PORTPROTO *pp, char *thename)
{
	REGISTER PORTPROTO *ppt;
	REGISTER INTBIG i;
	REGISTER char *portname, *pt;
	char numbuf[20];

	portname = thename;
	for(i=0; ; i++)
	{
		for(ppt = facet->firstportproto; ppt != NOPORTPROTO; ppt = ppt->nextportproto)
			if (namesame(portname, ppt->protoname) == 0) break;
		if (ppt == NOPORTPROTO)
		{
			ppt = newportproto(facet, ni, pp, portname);
			break;
		}

		/* make space for modified name */
		(void)initinfstr();
		for(pt = thename; *pt != 0 && *pt != '['; pt++)
			(void)addtoinfstr(*pt);
		sprintf(numbuf, "-%d", i);
		(void)addstringtoinfstr(numbuf);
		for( ; *pt != 0; pt++)
			(void)addtoinfstr(*pt);
		portname = returninfstr();
	}
	return(ppt);
}

/* Routine to convert SUE X coordinate "x" to Electric coordinates */
INTBIG io_suemakex(INTBIG x)
{
	return(x * sch_tech->deflambda / 8);
}

/* Routine to convert SUE Y coordinate "y" to Electric coordinates */
INTBIG io_suemakey(INTBIG y)
{
	return(-y * sch_tech->deflambda / 8);
}

/*
 * Routine to place all SUE nets into the facet (they are in a linked
 * list headed by "firstsuenet").
 */
void io_sueplacenets(SUENET *firstsuenet, NODEPROTO *facet)
{
	SUENET *sn;
	REGISTER INTBIG pass;
	REGISTER ARCINST *ai, *bestai;
	REGISTER INTBIG cx, cy, dist, bestdist, sea;
	REGISTER GEOM *geom;

	for(pass=0; pass<2; pass++)
	{
		for(sn = firstsuenet; sn != NOSUENET; sn = sn->nextsuenet)
		{
			/* unqualified labels (starting with "[") happen second */
			if (*sn->label == '[')
			{
				if (pass == 0) continue;
			} else
			{
				if (pass == 1) continue;
			}

			sea = initsearch(sn->x, sn->x, sn->y, sn->y, facet);
			bestai = NOARCINST;
			for(;;)
			{
				geom = nextobject(sea);
				if (geom == NOGEOM) break;
				if (geom->entrytype != OBJARCINST) continue;
				ai = geom->entryaddr.ai;
				cx = (ai->end[0].xpos + ai->end[1].xpos) / 2;
				cy = (ai->end[0].ypos + ai->end[1].ypos) / 2;
				dist = computedistance(cx, cy, sn->x, sn->y);
				if (bestai == NOARCINST || dist < bestdist)
				{
					bestai = ai;
					bestdist = dist;
				}
			}
			if (bestai != NOARCINST)
				us_setarcname(bestai, sn->label);
		}
	}
}

/*
 * Routine to place all SUE wires into the facet (they are in a linked
 * list headed by "firstsuewire").
 */
void io_sueplacewires(SUEWIRE *firstsuewire, SUENET *firstsuenet, NODEPROTO *facet)
{
	SUEWIRE *sw, *osw;
	SUENET *sn;
	REGISTER INTBIG i, j, propagatedbus, isbus, wid, px, py, lx, hx, ly, hy;
	INTBIG xsize, ysize;
	REGISTER NODEPROTO *proto;
	REGISTER PORTEXPINST *pe;
	NODEINST *ni;
	REGISTER NODEINST *bottomni;
	PORTPROTO *pp;
	REGISTER PORTPROTO *bottompp;
	REGISTER ARCINST *ai;

	/* mark all wire ends as "unassigned", all wire types as unknown */
	for(sw = firstsuewire; sw != NOSUEWIRE; sw = sw->nextsuewire)
	{
		sw->ni[0] = sw->ni[1] = NONODEINST;
		sw->proto = NOARCPROTO;
	}

	/* examine all network names and assign wire types appropriately */
	for(sn = firstsuenet; sn != NOSUENET; sn = sn->nextsuenet)
	{
		for(sw = firstsuewire; sw != NOSUEWIRE; sw = sw->nextsuewire)
		{
			for(i=0; i<2; i++)
			{
				if (sw->x[i] == sn->x && sw->y[i] == sn->y)
				{
					if (net_buswidth(sn->label) > 1) sw->proto = sch_busarc; else
						sw->proto = sch_wirearc;
				}
			}
		}
	}

	/* find connections that are exactly on existing nodes */
	for(sw = firstsuewire; sw != NOSUEWIRE; sw = sw->nextsuewire)
	{
		for(i=0; i<2; i++)
		{
			if (sw->ni[i] != NONODEINST) continue;
			for(ni = facet->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
			{
				pp = io_suewiredport(ni, &sw->x[i], &sw->y[i], sw->x[1-i], sw->y[1-i]);
				if (pp == NOPORTPROTO) continue;
				sw->ni[i] = ni;
				sw->pp[i] = pp;

				/* determine whether this port is a bus */
				isbus = 0;
				bottomni = ni;   bottompp = pp;
				while (bottomni->proto->primindex == 0)
				{
					bottomni = bottompp->subnodeinst;
					bottompp = bottompp->subportproto;
				}
				if (bottomni->proto == sch_wireconprim) continue;
				if (bottomni->proto == sch_buspinprim) isbus = 1;
				if (isbus == 0 && ni->proto == sch_offpageprim)
				{
					/* see if there is a bus port on this primitive */
					for(pe = ni->firstportexpinst; pe != NOPORTEXPINST; pe = pe->nextportexpinst)
					{
						if (net_buswidth(pe->exportproto->protoname) > 1) isbus = 1;
					}
				}

				if (isbus != 0)
				{
					sw->proto = sch_busarc;
				} else
				{
					if (sw->proto == NOARCPROTO)
						sw->proto = sch_wirearc;
				}
			}
		}
	}

	/* now iteratively extend bus wires to connections with others */
	propagatedbus = 1;
	while (propagatedbus != 0)
	{
		propagatedbus = 0;
		for(sw = firstsuewire; sw != NOSUEWIRE; sw = sw->nextsuewire)
		{
			if (sw->proto != sch_busarc) continue;
			for(osw = firstsuewire; osw != NOSUEWIRE; osw = osw->nextsuewire)
			{
				if (osw->proto != NOARCPROTO) continue;
				for(i=0; i<2; i++)
				{
					for(j=0; j<2; j++)
					{
						if (sw->x[i] == osw->x[j] && sw->y[i] == osw->y[j])
						{
							/* common point found: continue the bus request */
							osw->proto = sch_busarc;
							propagatedbus = 1;
						}
					}
				}
			}
		}
	}

	/* now make pins where wires meet */
	for(sw = firstsuewire; sw != NOSUEWIRE; sw = sw->nextsuewire)
	{
		for(i=0; i<2; i++)
		{
			if (sw->ni[i] != NONODEINST) continue;
			if (sw->proto == sch_busarc) proto = sch_buspinprim; else
				proto = sch_wirepinprim;

			/* look at all other wires at this point and figure out type of pin to make */
			for(osw = firstsuewire; osw != NOSUEWIRE; osw = osw->nextsuewire)
			{
				if (osw == sw) continue;
				for(j=0; j<2; j++)
				{
					if (sw->x[i] != osw->x[j] || sw->y[i] != osw->y[j]) continue;
					if (osw->ni[j] != NONODEINST)
					{
						sw->ni[i] = osw->ni[j];
						sw->pp[i] = osw->pp[j];
						break;
					}
					if (osw->proto == sch_busarc) proto = sch_buspinprim;
				}
				if (sw->ni[i] != NONODEINST) break;
			}

			/* make the pin if it doesn't exist */
			if (sw->ni[i] == NONODEINST)
			{
				/* common point found: make a pin */
				defaultnodesize(proto, &xsize , &ysize);
				sw->ni[i] = newnodeinst(proto, sw->x[i] - xsize/2,
					sw->x[i] + xsize/2, sw->y[i] - ysize/2,
					sw->y[i] + ysize/2, 0, 0, facet);
				endobjectchange((INTBIG)sw->ni[i], VNODEINST);
				sw->pp[i] = proto->firstportproto;
			}

			/* put that node in all appropriate locations */
			for(osw = firstsuewire; osw != NOSUEWIRE; osw = osw->nextsuewire)
			{
				if (osw == sw) continue;
				for(j=0; j<2; j++)
				{
					if (sw->x[i] != osw->x[j] || sw->y[i] != osw->y[j]) continue;
					if (osw->ni[j] != NONODEINST) continue;
					osw->ni[j] = sw->ni[i];
					osw->pp[j] = sw->pp[i];
				}
			}
		}
	}

	/* make pins at all of the remaining wire ends */
	for(sw = firstsuewire; sw != NOSUEWIRE; sw = sw->nextsuewire)
	{
		for(i=0; i<2; i++)
		{
			if (sw->ni[i] != NONODEINST) continue;
			if (io_suefindnode(&sw->x[i], &sw->y[i], sw->x[1-i], sw->y[1-i], facet,
				&sw->ni[i], &sw->pp[i], sw->ni[1-i]) == 0)
			{
				if (sw->proto == sch_busarc) proto = sch_buspinprim; else
					proto = sch_wirepinprim;
				defaultnodesize(proto, &xsize , &ysize);
				sw->ni[i] = newnodeinst(proto, sw->x[i] - xsize/2,
					sw->x[i] + xsize/2, sw->y[i] - ysize/2, sw->y[i] + ysize/2,
					0, 0, facet);
				endobjectchange((INTBIG)sw->ni[i], VNODEINST);
				sw->pp[i] = sw->ni[i]->proto->firstportproto;
			}
		}
	}

	/* now make the connections */
	for(sw = firstsuewire; sw != NOSUEWIRE; sw = sw->nextsuewire)
	{
		if (sw->proto == NOARCPROTO) sw->proto = sch_wirearc;
		wid = defaultarcwidth(sw->proto);

		/* if this is a bus, make sure it can connect */
		if (sw->proto == sch_busarc)
		{
			for(i=0; i<2; i++)
			{
				for(j=0; sw->pp[i]->connects[j] != NOARCPROTO; j++)
					if (sw->pp[i]->connects[j] == sch_busarc) break;
				if (sw->pp[i]->connects[j] == NOARCPROTO)
				{
					/* this end cannot connect: fake the connection */
					px = (sw->x[0] + sw->x[1]) / 2;
					py = (sw->y[0] + sw->y[1]) / 2;
					defaultnodesize(sch_buspinprim, &xsize , &ysize);
					lx = px - xsize/2;   hx = lx + xsize;
					ly = py - ysize/2;   hy = ly + ysize;
					ni = newnodeinst(sch_buspinprim, lx, hx, ly, hy, 0, 0, facet);
					if (ni == NONODEINST) break;
					endobjectchange((INTBIG)ni, VNODEINST);
					pp = ni->proto->firstportproto;
					ai = newarcinst(gen_unroutedarc, defaultarcwidth(gen_unroutedarc),
						us_makearcuserbits(gen_unroutedarc), ni, pp, px, py,
							sw->ni[i], sw->pp[i], sw->x[i], sw->y[i], facet);
					if (ai == NOARCINST)
					{
						ttyputerr("Error making fake connection");
						break;
					}
					endobjectchange((INTBIG)ai, VARCINST);
					sw->ni[i] = ni;
					sw->pp[i] = pp;
					sw->x[i] = px;
					sw->y[i] = py;
				}
			}
		}

		ai = newarcinst(sw->proto, wid, us_makearcuserbits(sw->proto),
			sw->ni[0], sw->pp[0], sw->x[0], sw->y[0],
			sw->ni[1], sw->pp[1], sw->x[1], sw->y[1], facet);
		if (ai == NOARCINST)
		{
			ttyputerr("Error placing wire");
			continue;
		}

		/* negate the wire if requested */
		if (sw->ni[0]->temp1 != 0 &&
			strcmp(sw->pp[0]->protoname, "y") == 0)
		{
			ai->userbits |= ISNEGATED;
		} else if (sw->ni[1]->temp1 != 0 &&
			strcmp(sw->pp[1]->protoname, "y") == 0)
		{
			ai->userbits |= ISNEGATED | REVERSEEND;
		}
		endobjectchange((INTBIG)ai, VARCINST);
	}
}
/*
 * Create a copy of "proto" that is arrayed "count" times.
 */
NODEPROTO *io_suecreatepseudo(NODEPROTO *proto, INTBIG count)
{
	REGISTER NODEPROTO *iconnp, *newnp, *type;
	REGISTER NODEINST *ni, *newni, *end0, *end1;
	REGISTER PORTPROTO *port0, *port1, *pp, *newpp;
	REGISTER ARCINST *ai, *newai;
	char size[20];

	iconnp = iconview(proto);
	if (iconnp == NONODEPROTO) iconnp = proto;
	(void)initinfstr();
	(void)addstringtoinfstr(proto->cell->cellname);
	sprintf(size, "-Size-%ld{ic}", count);
	(void)addstringtoinfstr(size);
	newnp = newnodeproto(returninfstr(), proto->cell->lib);
	if (newnp == NONODEPROTO) return(NONODEPROTO);
	newnp->userbits |= WANTNEXPAND;

	/* copy the contents */
	for(ni = iconnp->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
	{
		type = ni->proto;
		if (type == sch_wirepinprim && ni->firstportexpinst != NOPORTEXPINST)
			type = sch_buspinprim;
		newni = newnodeinst(type, ni->lowx, ni->highx, ni->lowy, ni->highy, ni->transpose,
			ni->rotation, newnp);
		if (newni == NONODEINST) return(NONODEPROTO);
		if (copyvars((INTBIG)ni, VNODEINST, (INTBIG)newni, VNODEINST) != 0)
			return(NONODEPROTO);
		endobjectchange((INTBIG)newni, VNODEINST);
		ni->temp1 = (INTBIG)newni;
	}
	for(ai = iconnp->firstarcinst; ai != NOARCINST; ai = ai->nextarcinst)
	{
		end0 = (NODEINST *)ai->end[0].nodeinst->temp1;
		end1 = (NODEINST *)ai->end[1].nodeinst->temp1;
		if (end0->proto == ai->end[0].nodeinst->proto) port0 = ai->end[0].portarcinst->proto; else
			port0 = sch_buspinprim->firstportproto;
		if (end1->proto == ai->end[1].nodeinst->proto) port1 = ai->end[1].portarcinst->proto; else
			port1 = sch_buspinprim->firstportproto;
		newai = newarcinst(ai->proto, ai->width, ai->userbits, end0, port0, ai->end[0].xpos,
			ai->end[0].ypos, end0, port1, ai->end[1].xpos, ai->end[1].ypos, newnp);
		if (newai == NOARCINST) return(NONODEPROTO);
		if (copyvars((INTBIG)ai, VARCINST, (INTBIG)newai, VARCINST) != 0)
			return(NONODEPROTO);
		endobjectchange((INTBIG)newai, VARCINST);
	}
	for(pp = iconnp->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
	{
		end0 = (NODEINST *)pp->subnodeinst->temp1;
		if (end0->proto == pp->subnodeinst->proto) port0 = pp->subportproto; else
			port0 = sch_buspinprim->firstportproto;
		newpp = newportproto(newnp, end0, port0, pp->protoname);
	}
	return(newnp);
}

/*
 * Routine to find the port on node "ni" that attaches to the wire from (x,y) to (ox,oy).
 * Returns NOPORTPROTO if not found.
 */
PORTPROTO *io_suewiredport(NODEINST *ni, INTBIG *x, INTBIG *y, INTBIG ox, INTBIG oy)
{
	REGISTER PORTPROTO *pp, *bestpp;
	REGISTER INTBIG dist, bestdist;
	INTBIG px, py;
	static POLYGON *poly = NOPOLYGON;

	/* make sure there is a polygon */
	if (poly == NOPOLYGON) poly = allocstaticpolygon(4, io_aid->cluster);
	for(pp = ni->proto->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
	{
		shapeportpoly(ni, pp, poly, 0);
		if (isinside(*x, *y, poly) != 0) return(pp);
	}
	if ((ni->lowx+ni->highx) / 2 != *x ||
		(ni->lowy+ni->highy) / 2 != *y) return(NOPORTPROTO);

	/* find port that is closest to OTHER end */
	bestdist = HUGEINT;
	bestpp = NOPORTPROTO;
	for(pp = ni->proto->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
	{
		portposition(ni, pp, &px, &py);
		dist = computedistance(px, py, ox, oy);
		if (dist > bestdist) continue;
		bestdist = dist;
		bestpp = pp;
	}
	portposition(ni, bestpp, x, y);
	return(bestpp);
}

/*
 * Routine to return the length of the arrayed name "name".  Returns zero
 * if the name is not arrayed.
 */
INTBIG io_suearraylength(char *name)
{
	REGISTER char *pt;

	if (name == 0) return(0);
	for(pt = name; *pt != 0; pt++) if (*pt == '[') break;
	if (*pt == 0) return(0);
	return(net_buswidth(name));
}

/*
 * Routine to find the pin at (x, y) and return it.
 */
NODEINST *io_suefindpinnode(INTBIG x, INTBIG y, NODEPROTO *np, PORTPROTO **thepp)
{
	REGISTER GEOM *geom;
	REGISTER INTBIG sea;
	REGISTER NODEINST *ni;
	REGISTER PORTPROTO *pp;
	INTBIG px, py;

	*thepp = NOPORTPROTO;
	sea = initsearch(x, x, y, y, np);
	for(;;)
	{
		geom = nextobject(sea);
		if (geom == NOGEOM) break;
		if (geom->entrytype != OBJNODEINST) continue;
		ni = geom->entryaddr.ni;

		/* find closest port */
		for(pp = ni->proto->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
		{
			/* make sure there is a polygon */
			portposition(ni, pp, &px, &py);
			if (px == x && py == y)
			{
				*thepp = pp;
				termsearch(sea);
				return(ni);
			}
		}
	}
	return(NONODEINST);
}

/*
 * Routine to find the node at (x, y) and return it.
 */
INTSML io_suefindnode(INTBIG *x, INTBIG *y, INTBIG ox, INTBIG oy,
	NODEPROTO *np, NODEINST **rni, PORTPROTO **rpp, NODEINST *notthisnode)
{
	REGISTER GEOM *geom;
	REGISTER INTBIG sea;
	REGISTER NODEINST *ni, *bestni;
	INTBIG plx, phx, ply, phy;
	REGISTER INTBIG dist, bestdist, thisx, thisy, bestx, besty, slop;
	REGISTER PORTPROTO *pp, *bestpp;
	static POLYGON *poly = NOPOLYGON;

	slop = sch_tech->deflambda * 10;
	sea = initsearch(*x-slop, *x+slop, *y-slop, *y+slop, np);
	bestpp = NOPORTPROTO;
	for(;;)
	{
		geom = nextobject(sea);
		if (geom == NOGEOM) break;
		if (geom->entrytype != OBJNODEINST) continue;
		ni = geom->entryaddr.ni;
		if (ni == notthisnode) continue;

		/* ignore pins */
		if (ni->proto == sch_wirepinprim) continue;

		/* find closest port */
		for(pp = ni->proto->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
		{
			/* make sure there is a polygon */
			if (poly == NOPOLYGON) poly = allocstaticpolygon(4, io_aid->cluster);

			/* get the polygon describing the port */
			shapeportpoly(ni, pp, poly, 0);
			getbbox(poly, &plx, &phx, &ply, &phy);

			/* find out if the line crosses the polygon */
			if (*x == ox)
			{
				/* line is vertical: look for intersection with polygon */
				if (ox < plx || ox > phx) continue;
				thisx = ox;
				thisy = (ply+phy)/2;
			} else if (*y == oy)
			{
				/* line is horizontal: look for intersection with polygon */
				if (oy < ply || oy > phy) continue;
				thisx = (plx+phx)/2;
				thisy = oy;
			} else
			{
				if (isinside(ox, oy, poly) == 0) continue;
				thisx = ox;
				thisy = oy;
			}

			dist = computedistance(ox, oy, thisx, thisy);
			if (bestpp == NOPORTPROTO || dist < bestdist)
			{
				bestpp = pp;
				bestni = ni;
				bestdist = dist;
				bestx = thisx;
				besty = thisy;
			}
		}
	}

	/* report the hit */
	if (bestpp == NOPORTPROTO) return(0);
	*rni = bestni;   *rpp = bestpp;
	*x = bestx;      *y = besty;
	return(1);
}

/*
 * Routine to find the SUE file "name" on disk, and read it into library "lib".
 * Returns NONODEPROTO if the file is not found or not read properly.
 */
NODEPROTO *io_suereadfromdisk(LIBRARY *lib, char *name)
{
	REGISTER INTBIG i, savelineno, filepos, savefilesize;
	char suevarname[200], subfilename[300], savesuelastline[MAXLINE], saveorigline[MAXLINE],
		lastprogressmsg[MAXLINE], savecurline[MAXLINE], *truename;
	REGISTER FILE *f, *savefp;
	REGISTER NODEPROTO *proto;

	/* look for another "sue" file that describes this facet */
	for(i=0; i<io_suenumdirectories; i++)
	{
		strcpy(subfilename, io_suedirectories[i]);
		strcat(subfilename, name);
		strcat(subfilename, ".sue");
		f = xopen(subfilename, FILETYPESUE, "", &truename);
		if (f != 0)
		{
			savefp = io_suefilein;
			io_suefilein = f;
			for(i=0; i<MAXLINE; i++) savecurline[i] = io_suecurline[i];
			strcpy(saveorigline, io_sueorigline);
			strcpy(savesuelastline, io_suelastline);
			strcpy(lastprogressmsg, DiaGetText(2));
			strcpy(suevarname, "Reading ");
			strcat(suevarname, name);
			strcat(suevarname, "...");
			DiaSetText(2, suevarname);
			DiaPercent(1, 0);

			strcpy(subfilename, name);
			savefilesize = io_suefilesize;
			savelineno = io_suelineno;
			io_suefilesize = filesize(io_suefilein);
			io_suelineno = 0;
			(void)io_suereadfile(lib, subfilename);
			xclose(io_suefilein);
			io_suefilein = savefp;
			io_suefilesize = savefilesize;
			io_suelineno = savelineno;
			strcpy(io_suelastline, savesuelastline);
			strcpy(io_sueorigline, saveorigline);
			for(i=0; i<MAXLINE; i++) io_suecurline[i] = savecurline[i];
			filepos = xtell(io_suefilein);
			DiaSetText(2, lastprogressmsg);
			DiaPercent(1, filepos*100/io_suefilesize);

			/* now try to find the facet in the library */
			proto = io_suegetnodeproto(lib, subfilename);
			return(proto);
		}
	}
	return(NONODEPROTO);
}

/*
 * Routine to read the next line from file "io_suefilein" and break
 * it up into space-separated keywords.  Returns the number
 * of keywords (-1 on EOF)
 */
INTSML io_suegetnextline(char **keywords, INTSML curlydepth)
{
	char *pt;
	REGISTER INTBIG filepos, inblank, keypos, lineno;

	for(lineno=0; ; lineno++)
	{
		if (io_suelastline[0] == 0)
		{
			if (xfgets(io_suelastline, MAXLINE, io_suefilein) != 0) return(-1);
			io_suelineno++;
			if ((io_suelineno%50) == 0)
			{
				filepos = xtell(io_suefilein);
				DiaPercent(1, filepos*100/io_suefilesize);
			}
		}
		if (lineno == 0)
		{
			/* first line: use it */
			strcpy(io_suecurline, io_suelastline);
		} else
		{
			/* subsequent line: use it only if a continuation */
			if (io_suelastline[0] != '+') break;
			strcat(io_suecurline, &io_suelastline[1]);
		}
		io_suelastline[0] = 0;
	}
	strcpy(io_sueorigline, io_suecurline);

	/* parse the line */
	inblank = 1;
	keypos = 0;
	for(pt=io_suecurline; *pt != 0; pt++)
	{
		if (*pt == '{') curlydepth++;
		if (*pt == '}') curlydepth--;
		if ((*pt == ' ' || *pt == '\t') && curlydepth == 0)
		{
			*pt = 0;
			inblank = 1;
		} else
		{
			if (inblank != 0)
			{
				keywords[keypos++] = pt;
			}
			inblank = 0;
		}
	}
	return((INTSML)keypos);
}

/*
 * Routine to add "directory" to the list of places where SUE files may be found.
 */
INTSML io_sueadddirectory(char *directory)
{
	REGISTER INTBIG newnumdir, i;
	char **newdir;

	newnumdir = io_suenumdirectories + 1;
	newdir = (char **)emalloc(newnumdir * (sizeof (char *)), io_aid->cluster);
	if (newdir == 0) return(1);
	for(i=0; i<io_suenumdirectories; i++)
		newdir[i] = io_suedirectories[i];
	newdir[io_suenumdirectories] = (char *)emalloc(strlen(directory)+1, io_aid->cluster);
	if (newdir[io_suenumdirectories] == 0) return(1);
	strcpy(newdir[io_suenumdirectories], directory);
	if (io_suenumdirectories > 0) efree((char *)io_suedirectories);
	io_suedirectories = newdir;
	io_suenumdirectories = newnumdir;
	return(0);
}

void io_suecleardirectories(void)
{
	REGISTER INTBIG i;

	for(i=0; i<io_suenumdirectories; i++)
		efree((char *)io_suedirectories[i]);
	if (io_suenumdirectories > 0) efree((char *)io_suedirectories);
	io_suenumdirectories = 0;
}

/*************************** OBJECT MANAGEMENT ***************************/

SUEWIRE *io_suenewwire(void)
{
	SUEWIRE *sw;

	if (io_suefreewire != NOSUEWIRE)
	{
		sw = io_suefreewire;
		io_suefreewire = sw->nextsuewire;
	} else
	{
		sw = (SUEWIRE *)emalloc(sizeof(SUEWIRE), io_aid->cluster);
	}
	return(sw);
}

void io_suekillwire(SUEWIRE *sw)
{
	sw->nextsuewire = io_suefreewire;
	io_suefreewire = sw;
}

void io_suefreewires(SUEWIRE *firstsw)
{
	SUEWIRE *sw, *nextsw;

	for(sw = firstsw; sw != NOSUEWIRE; sw = nextsw)
	{
		nextsw = sw->nextsuewire;
		io_suekillwire(sw);
	}
}

SUENET *io_suenewnet(void)
{
	SUENET *sn;

	if (io_suefreenet != NOSUENET)
	{
		sn = io_suefreenet;
		io_suefreenet = sn->nextsuenet;
	} else
	{
		sn = (SUENET *)emalloc(sizeof(SUENET), io_aid->cluster);
	}
	sn->label = 0;
	return(sn);
}

void io_suekillnet(SUENET *sn)
{
	sn->nextsuenet = io_suefreenet;
	io_suefreenet = sn;
	if (sn->label != 0) efree(sn->label);
}

void io_suefreenets(SUENET *firstsn)
{
	SUENET *sn, *nextsn;

	for(sn = firstsn; sn != NOSUENET; sn = nextsn)
	{
		nextsn = sn->nextsuenet;
		io_suekillnet(sn);
	}
}

#endif  /* IOSUE - at top */
