/*
 * Electric(tm) VLSI Design System
 *
 * File: ioedifo.c
 * Input/output aid: EDIF netlist generator
 * Written by: Steven M. Rubin, Static Free Software
 * Modifications and extensions by B G West and G. Lawson
 *
 * 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 IOEDIF

#include "global.h"
#include "efunction.h"
#include "eio.h"
#include "egraphics.h"
#include "tech.h"
#include "tecgen.h"
#include "network.h"
#include "edialogs.h"
#include <math.h>

#define WORD	   256
#define MAXDEPTH	40
#define LINE      1024

#define GETBLANKS(C, M) (C ? "" : &io_edifo_blanks[sizeof(io_edifo_blanks)-(M)-1])
#define DOSPACE         (stream->compress ? "" : " ")
#define NOEO_STREAM     ((struct _eo_stream *)0)

/* primary stream structure */
typedef enum { EO_OPENED, EO_CLOSED, EO_TCLOSED } EO_FSTATE;
typedef struct _eo_stream
{
	char              *filename;			/* the file name */
	FILE              *file;				/* the opened stream */
	EO_FSTATE          state;				/* the file state */
	INTBIG             fpos;				/* the saved file position */
	char              *blkstack[MAXDEPTH];	/* the stream keyword stack */
	INTBIG             blkstack_ptr;		/* the current position */
	struct _eo_stream *next;				/* the next stream file */
	INTBIG             compress;			/* the compress flag */
} EO_STREAM, *EO_STREAM_PTR;

char *io_edifoextlibname[WORD+1];
INTSML io_edifoextlibcount;
static INTBIG io_edifospice_model = 0;
INTSML io_edifo_gateindex;
INTBIG io_ediforev;
static char io_edifo_blanks[] = "                                                  ";

/* globals for SCHEMATIC View */
static INTBIG schematic_view = 0;
static double schematic_scale = 1.0;
typedef enum
{
	EGUNKNOWN = 0,
	EGART = 1,
	EGTEXT = 2,
	EGWIRE = 3,
	EGBUS = 4
} _egraphic;
static _egraphic egraphic = EGUNKNOWN;
static _egraphic egraphic_override = EGUNKNOWN;
static char *egraphic_text[5] = {"UNKNOWN", "ARTWORK", "TEXT", "WIRE", "BUS"};
static EO_STREAM_PTR edif_stream;

/* prototypes */
INTBIG io_edif_scale(INTBIG val);
INTSML io_edifsearch(NODEPROTO *np);
void io_edifextsearch(NODEPROTO *np);
char *io_ediffind_path(NODEPROTO *np);
void io_edifwriteprim(NODEPROTO *np, INTSML i, INTSML fun);
char *io_edif_orientation(NODEINST *ni);
char *io_edifwritecompname(NODEINST *ni, INTSML fun, INTSML serindex);
void io_edif_pt(INTBIG x, INTBIG y);
INTSML io_edifwritefacet(NODEPROTO *np, INTSML external);
char *io_edifdescribepriminst(NODEINST *ni, INTSML fun);
INTSML io_edifisglobal(PORTPROTO *pp);
INTSML io_edifmarknetports(NODEPROTO *np);
INTSML io_edifwritelibext(NODEPROTO *np);
char *io_ediftoken(char *str);
char io_edifnotwhitespace(char ch);
INTSML io_edifvalid(char ch);
char *io_edifvalidname(VARIABLE *var);
INTSML io_edifwritefootprint(NODEPROTO *np, char *name, INTBIG routegrid);
INTSML io_edifwriteportpositions(PORTPROTO *pp, INTBIG cellx, INTBIG celly,
	char *accessrules, INTBIG routegrid);
char *io_ediflowerstring(char *str);
char *io_edifupperstring(char *str);
void io_edifsymbol(NODEPROTO *np);
void io_edifsymbol_facet(NODEINST *ni, XARRAY prevtrans);
void io_edifsymbol_nodeinst(NODEINST *ni, XARRAY prevtrans);
void io_edifsymbol_arcinst(ARCINST *ai, XARRAY trans);
void io_compute_center(INTBIG xc, INTBIG yc, INTBIG x1, INTBIG y1,
	INTBIG x2, INTBIG y2, INTBIG *cx, INTBIG *cy);
void io_edifsetgraphic(_egraphic type);
INTBIG io_edifsymbol_showpoly(POLYGON *obj);
EO_STREAM_PTR EO_open_stream(char *, INTBIG);
INTBIG EO_close_stream(EO_STREAM_PTR);
INTBIG EO_open_block(EO_STREAM_PTR, char *);
INTBIG EO_put_block(EO_STREAM_PTR, char *, char *);
INTBIG EO_put_comment(EO_STREAM_PTR, char *);
INTBIG EO_put_identifier(EO_STREAM_PTR, char *);
INTBIG EO_put_string(EO_STREAM_PTR, char *);
INTBIG EO_put_integer(EO_STREAM_PTR, INTBIG);
INTBIG EO_put_float(EO_STREAM_PTR, double);
INTBIG EO_put_date(EO_STREAM_PTR);
INTBIG EO_put_header(EO_STREAM_PTR, char *, char *, char *);
INTBIG EO_close_block(EO_STREAM_PTR, char *);
EO_STREAM_PTR EO_alloc_stream(void);
INTBIG EO_free_stream(EO_STREAM_PTR);
char *EO_get_timestamp(char *);
char *EO_make_string(char *);
char *EO_get_exp(double);
void io_edifoptionsdlog(void);

/*
 * Routine to initialize EDIF I/O.
 */
void io_initedif(void)
{
	extern COMCOMP io_edifp;

	DiaDeclareHook("edifopt", &io_edifp, io_edifoptionsdlog);
}

/*
 * routine to write a ".edif" file (or a ".foot" file for layouts)
 * describing the current facet from the library "lib"
 */
INTSML io_writeediflibrary(LIBRARY *lib)
{
	char name[100], fname[100];
	REGISTER NODEPROTO *lnp, *np;
	REGISTER INTSML i, backannotate;
	REGISTER INTBIG lambda, *curstate;
	REGISTER LIBRARY *olib;
	REGISTER TECHNOLOGY *tech;
	INTBIG fun, rgrid;
	VARIABLE *var;
	double meters_to_lambda;

	/* make sure network tool is on */
	if ((net_aid->aidstate & AIDON) == 0)
	{
		ttyputerr(_("Network tool must be running...turning it on"));
		aidturnon(net_aid, 0);
		ttyputerr(_("...now reissue the EDIF I/O command"));
		return(1);
	}

	/* initialize counters for automatic name generation */
	io_edifo_gateindex = 1;

	/* first write the "edif" file */
	np = lib->curnodeproto;
	if (np == NONODEPROTO)
	{
		ttyputerr(_("Must be editing a facet to generate EDIF output"));
		return(1);
	}

	/* get some keys */
	io_edifospice_model = makekey("SIM_spice_model");

	/* See if schematic view is requested */
	lambda = lib->lambda[el_curtech->techindex];
	curstate = io_getstatebits();
	if ((curstate[0] & EDIFSCHEMATIC) != 0)
	{
		schematic_view = 1;
		schematic_scale = 1.0 / ((double)lambda);
		meters_to_lambda = scaletodispunit(lambda, DISPUNITCM) / 100;
	} else
		schematic_view = 0;

	(void)strcpy(name, io_ediftoken(np->cell->cellname));

	/* If this is a layout representation, then create the footprint */
	if (np->cellview == el_layoutview)
	{
		/* default routing grid is 6.6u = 660 centimicrons */
		var = getval((INTBIG) np, VNODEPROTO, VINTEGER, "EDIF_routegrid");
		if (var != NOVARIABLE) rgrid = var->addr; else
			rgrid = 660;
		return(io_edifwritefootprint(np, name, rgrid));
	}

	/* Not a layout view - create the netlist */
	(void)sprintf(fname, "%s.edif", name);
	if ((edif_stream = EO_open_stream(fname, 0)) == NOEO_STREAM)
	{
		/* ttyputerr(_("Cannot write %s"), fname); */
		return(1);
	}

	/* write the header */
	(void)sprintf(name, _("Electric Design System v%s"), el_version);
	(void)EO_put_header(edif_stream, name, _("EDIF Writer"), lib->libname);

	/* write the external primitive reference library, if any */
	if (io_edifwritelibext(np) != 0)
	{
		/* determine the primitives being used */
		for (tech = el_technologies; tech != NOTECHNOLOGY; tech = tech->nexttechnology)
			for (lnp = tech->firstnodeproto; lnp != NONODEPROTO; lnp = lnp->nextnodeproto)
				lnp->temp1 = 0;
		for(olib = el_curlib; olib != NOLIBRARY; olib = olib->nextlibrary)
			for (lnp = olib->firstnodeproto; lnp != NONODEPROTO; lnp = lnp->nextnodeproto)
				lnp->temp1 = 0;

		/* search recursively for all primitives used */
		if (io_edifsearch(np) != 0)
		{
			/* advise user that generic primitives are being used */
			ttyputerr(_("WARNING: external primitive library undefined - using generic models"));

			/* write out all primitives used in the library */
			EO_open_block(edif_stream, "library");
			EO_put_identifier(edif_stream, "lib0");
			EO_put_block(edif_stream, "edifLevel", "0");
			EO_open_block(edif_stream, "technology");
			EO_open_block(edif_stream, "numberDefinition");
			if (schematic_view)
			{
				EO_open_block(edif_stream, "scale");
				EO_put_integer(edif_stream, io_edif_scale(lambda));
				EO_put_float(edif_stream, meters_to_lambda);
				EO_put_block(edif_stream, "unit", "DISTANCE");
				EO_close_block(edif_stream, "scale");
			}
			EO_close_block(edif_stream, "technology");
			for (tech = el_technologies; tech != NOTECHNOLOGY; tech = tech->nexttechnology)
				for (lnp = tech->firstnodeproto; lnp != NONODEPROTO; lnp = lnp->nextnodeproto)
			{
				if (lnp->temp1 != 0)
				{
					/* write primitive "lnp" */
					fun = (lnp->userbits & NFUNCTION) >> NFUNCTIONSH;
					if (fun == NPUNKNOWN || fun == NPPIN || fun == NPCONTACT ||
						fun == NPNODE || fun == NPCONNECT || fun == NPART) continue;
					for (i = 0; i < lnp->temp1; i++)
						io_edifwriteprim(lnp, i, (INTSML)fun);
				}
			}
			EO_close_block(edif_stream, "library");
		}
	}

	/* search recursively for all external libraries required */
	backannotate = 0;
	for(olib = el_curlib; olib != NOLIBRARY; olib = olib->nextlibrary)
		for (lnp = olib->firstnodeproto; lnp != NONODEPROTO; lnp = lnp->nextnodeproto)
	{
		lnp->temp1 = 0;
		lnp->temp2 = 0;
	}
	io_edifoextlibcount = 0;
	io_edifextsearch(np);

	/* mark all node prototypes for final netlisting */
	for(olib = el_curlib; olib != NOLIBRARY; olib = olib->nextlibrary)
		for (lnp = olib->firstnodeproto; lnp != NONODEPROTO; lnp = lnp->nextnodeproto)
			lnp->temp1 = 0;

	/* write out all external references in the library */
	if (io_edifoextlibcount > 0)
		for (i = 1; i <= io_edifoextlibcount; i++)
	{
		EO_open_block(edif_stream, "external");
		(void)sprintf(name, "schem_lib_%d", i);
		EO_put_identifier(edif_stream, name);
		EO_put_block(edif_stream, "edifLevel", "0");
		EO_open_block(edif_stream, "technology");
		EO_open_block(edif_stream, "numberDefinition");
		if (schematic_view)
		{
			EO_open_block(edif_stream, "scale");
			EO_put_integer(edif_stream, io_edif_scale(lambda));
			EO_put_float(edif_stream, meters_to_lambda);
			EO_put_block(edif_stream, "unit", "DISTANCE");
			EO_close_block(edif_stream, "scale");
		}
		EO_close_block(edif_stream, "technology");
		if (io_edifwritefacet(np, i) != 0) backannotate = 1;
		EO_close_block(edif_stream, "external");
	}

	/* establish the resistor model revision level */
	io_ediforev = 1;
	var = getval((INTBIG) np, VNODEPROTO, VINTEGER, "EDIF_resistor_rev");
	if (var != NOVARIABLE) io_ediforev = var->addr;

	/* now recursively write the facets expanded within the library */
	EO_open_block(edif_stream, "library");
	EO_put_identifier(edif_stream, io_ediftoken(lib->libname));
	EO_put_block(edif_stream, "edifLevel", "0");
	EO_open_block(edif_stream, "technology");
	EO_open_block(edif_stream, "numberDefinition");
	if (schematic_view)
	{
		EO_open_block(edif_stream, "scale");
		EO_put_integer(edif_stream, io_edif_scale(lambda));
		EO_put_float(edif_stream, meters_to_lambda);
		EO_put_block(edif_stream, "unit", "DISTANCE");
		EO_close_block(edif_stream, "scale");
	}
	EO_close_block(edif_stream, "technology");

	if (io_edifwritefacet(np, 0) != 0) backannotate = 1;
	EO_close_block(edif_stream, "library");

	/* post-identify the design and library */
	EO_open_block(edif_stream, "design");
	EO_put_identifier(edif_stream, io_ediftoken(np->cell->cellname));
	EO_open_block(edif_stream, "cellRef");
	EO_put_identifier(edif_stream, io_ediftoken(np->cell->cellname));
	EO_put_block(edif_stream, "libraryRef", io_ediftoken(lib->libname));

	/* clean up */
	ttyputmsg(_("%s written"), edif_stream->filename);
	EO_close_stream(edif_stream);
	if (backannotate != 0)
		ttyputmsg(_("Back-annotation information has been added (library must be saved)"));
	return(0);
}

/*
 * routine to count the usage of primitives hierarchically below facet "np"
 */
INTSML io_edifsearch(NODEPROTO *np)
{
	REGISTER NODEINST *ni;
	REGISTER NODEPROTO *onp;
	REGISTER PORTARCINST *pi;
	REGISTER INTSML i, fun, primcount;
	char *extra;

	/* do not search this facet if it is an icon */
	if (np->cellview == el_iconview) return(0);

	/* keep a count of the total number of primitives encountered */
	primcount = 0;

	for (ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
	{
		if (ni->proto->primindex != 0)
		{
			fun = nodefunction(ni, &extra);
			i = 1;
			if (fun == NPGATEAND || fun == NPGATEOR || fun == NPGATEXOR)
			{
				/* count the number of inputs */
				for (pi = ni->firstportarcinst; pi != NOPORTARCINST; pi = pi->nextportarcinst)
					if (pi->proto == ni->proto->firstportproto) i++;
			}
			ni->proto->temp1 = maxi(ni->proto->temp1, i);

			if (fun != NPUNKNOWN && fun != NPPIN && fun != NPCONTACT &&
				fun != NPNODE && fun != NPCONNECT && fun != NPMETER &&
					fun != NPCONPOWER && fun != NPCONGROUND && fun != NPSOURCE &&
						fun != NPSUBSTRATE && fun != NPWELL && fun != NPART)
							primcount++;
			continue;
		}

		/* get actual subfacet (including contents/body distinction) */
		onp = contentsview(ni->proto);
		if (onp == NONODEPROTO) onp = ni->proto;

		/* search the subfacet */
		if (onp->temp1 == 0) primcount += io_edifsearch(onp);
	}
	np->temp1++;
	return(primcount);
}

/*
 * Routine to identify facets with external connection models hierarchically below
 * facet "np" and locate the libraries that contain their models.  Sets np->temp2
 * to point to the location in the external library array containing the library
 * name.
 */
void io_edifextsearch(NODEPROTO *np)
{
	REGISTER NODEINST *ni;
	REGISTER NODEPROTO *onp;
	REGISTER INTSML i;
	char *libname;

	/* return if primitive (handled separately) */
	if (np->primindex != 0) return;

	/* check if this is a previously unencountered icon with no contents */
	onp = contentsview(np);
	if (np->temp2 == 0 && np->cellview == el_iconview && onp == NONODEPROTO)
	{
		/* do not mention monitor_probes */
		if (namesame(np->cell->cellname, "monitor_probe") == 0) return;

		/* this facet is not expanded in this library */
		libname = io_ediffind_path(np);

		if (io_edifoextlibcount == 0) i = 1; else
			for (i = 1; i <= io_edifoextlibcount; i++)
				if (namesame(libname, io_edifoextlibname[i]) == 0) break;
		if (i > io_edifoextlibcount)
		{
			io_edifoextlibcount = i;
			(void)initinfstr();
			(void)addstringtoinfstr(libname);
			(void)allocstring(&io_edifoextlibname[i], returninfstr(), io_aid->cluster);
			ttyputmsg(_("External library %s, lib number %d"), io_edifoextlibname[i], i);
		}
		np->temp2 = i;
		np->temp1++;
		return;
	}
	if (onp == NONODEPROTO) onp = np;
	for (ni = onp->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
		if (ni->proto->temp1 == 0) io_edifextsearch(ni->proto);

	np->temp1++;
}

/*
 * Routine to find the path to the EDIF file in the standard library
 * structure, as contained in /usr/local/electric/lib/edifpath.
 */
char *io_ediffind_path(NODEPROTO *np)
{
	char *path, libpath[255], sep[2], ch, *filename;
	FILE *f, *etry;
	INTSML i;

	/* initialize the local path name variable */
	f = xopen("edifpath", io_filetypeedif, el_libdir, &filename);
	if (f != NULL)
	{
		for(ch = (char)xgetc(f), i = 0; ; ch = (char)xgetc(f), i++)
		{
			if (ch == ' ' || ch == '\t') continue;
			if (ch == '\n' || ch == EOF || ch == ';')
			{
				libpath[i] = DIRSEP;
				libpath[i + 1] = 0;
				strcat(libpath, np->cell->cellname);
				sep[0] = DIRSEP;   sep[1] = 0;
				strcat(libpath, sep);
				strcat(libpath, "edif");
				strcat(libpath, sep);
				strcat(libpath, "source");
				etry = xopen(libpath, io_filetypeedif, el_libdir, &filename);
				if (etry != NULL)
				{
					libpath[i] = 0;
					xclose(etry);
					break;
				}
				i = -1;			/* start another line after incrementing i */
			} else libpath[i] = ch;
			if (ch == EOF)
			{
				(void)strcpy(libpath, "unknown");
				break;
			}
		}
		xclose(f);
	} else (void)strcpy(libpath, "unknown");

	(void)allocstring(&path, libpath, io_aid->cluster);
	return(path);
}

/*
 * routine to dump the description of primitive "np" to the EDIF file
 * If the primitive is a schematic gate, use "i" as the number of inputs
 */
void io_edifwriteprim(NODEPROTO *np, INTSML i, INTSML fun)
{
	REGISTER INTSML j;
	REGISTER PORTPROTO *firstport, *pp;
	REGISTER char *direction;
	char name[100];

	/* write primitive name */
	if (fun == NPGATEAND || fun == NPGATEOR || fun == NPGATEXOR)
	{
		EO_open_block(edif_stream, "cell");
		(void)sprintf(name, "%s%d", io_ediftoken(np->primname), i);
		EO_put_identifier(edif_stream, name);
	} else
	{
		EO_open_block(edif_stream, "cell");
		EO_put_identifier(edif_stream, io_ediftoken(np->primname));
	}

	/* write primitive connections */
	EO_put_block(edif_stream, "cellType", "GENERIC");
	EO_open_block(edif_stream, "view");
	EO_put_identifier(edif_stream, "cell");
	EO_put_block(edif_stream, "viewType", schematic_view ? "SCHEMATIC" : "NETLIST");
	EO_open_block(edif_stream, "interface");

	firstport = np->firstportproto;
	if (fun == NPGATEAND || fun == NPGATEOR || fun == NPGATEXOR)
	{
		for (j = 0; j < i; j++)
		{
			EO_open_block(edif_stream, "port");
			(void)sprintf(name, "IN%d", j + 1);
			EO_put_identifier(edif_stream, name);
			EO_put_block(edif_stream, "direction", "INPUT");
			EO_close_block(edif_stream, "port");
		}
		firstport = np->firstportproto->nextportproto;
	}
	for (pp = firstport; pp != NOPORTPROTO; pp = pp->nextportproto)
	{
		switch (pp->userbits & STATEBITS)
		{
			case OUTPORT:
				direction = "output";
				break;
			case BIDIRPORT:
				direction = "inout";
				break;
			default:
				direction = "input";
				break;
		}
		EO_open_block(edif_stream, "port");
		EO_put_identifier(edif_stream, io_ediftoken(pp->protoname));
		EO_put_block(edif_stream, "direction", direction);
		EO_close_block(edif_stream, "port");
	}
	if (schematic_view)
	{

	}
	EO_close_block(edif_stream, "cell");
}

/* module: io_edif_scale
 * function: will scale the requested integer
 * returns the scaled value
 */
INTBIG io_edif_scale(INTBIG val)
{
	if (val < 0)
		return((INTBIG)(((double) val - 0.5) * schematic_scale));
	return((INTBIG)(((double) val + 0.5) * schematic_scale));
}

/*
 * Routine to map Electric orientations to EDIF orientations
 */
char *io_edif_orientation(NODEINST *ni)
{
	if (ni->transpose)
	{
		switch (ni->rotation)
		{
			case 0:    return("MYR90");
			case 900:  return("MY");
			case 1800: return("MXR90");
			case 2700: return("MX");
		}
	} else
	{
		switch (ni->rotation)
		{
			case 0:    return("R0");
			case 900:  return("R90");
			case 1800: return("R180");
			case 2700: return("R270");
		}
	}
	return("ERROR");
}

/*
 * Helper name builder
 */
char *io_edifwritecompname(NODEINST *ni, INTSML fun, INTSML serindex)
{
	REGISTER VARIABLE *var;
	REGISTER char *okname;
	static char name[WORD+1];
	static INTBIG EDIF_name_key = 0;

	if (EDIF_name_key == 0) EDIF_name_key = makekey("EDIF_name");

	/* always use EDIF_name if required */
	var = getvalkey((INTBIG) ni, VNODEINST, VSTRING, EDIF_name_key);
	okname = io_edifvalidname(var);
	if (okname == 0)
	{
		/* check for node name */
		var = getvalkey((INTBIG) ni, VNODEINST, VSTRING, el_node_name);
		okname = io_edifvalidname(var);
		if (okname == 0)
		{
			/* create a new EDIF_name */
			(void)sprintf(name, "INSTANCE%d", io_edifo_gateindex++);
			(void)setvalkey((INTBIG) ni, VNODEINST, EDIF_name_key, (INTBIG)name, VSTRING);
		} else
		{
			(void)setvalkey((INTBIG) ni, VNODEINST, EDIF_name_key, (INTBIG)okname, VSTRING);
		}
	}

	if (isdigit(*okname) || *okname == '_')
		(void)sprintf(name, "&%s", okname); else
			(void)strcpy(name, okname);
	return(name);
}

/* module: io_edif_pt
 * function: will generate a pt symbol (pt x y)
 * returns success or failure
 */
void io_edif_pt(INTBIG x, INTBIG y)
{
	EO_open_block(edif_stream, "pt");
	EO_put_integer(edif_stream, io_edif_scale(x));
	EO_put_integer(edif_stream, io_edif_scale(y));
	EO_close_block(edif_stream, "pt");
}

/*
 * Routine to recursively dump facet "np" to the EDIF file
 * Recurses for contents if 'external' is zero, creates only
 * interface models for external elements for library number
 * 'external' if 'external' is non-zero.
 * Returns nonzero if back-annotation was added.
 */
INTSML io_edifwritefacet(NODEPROTO *np, INTSML external)
{
	INTBIG i, netcount, displaytotal;
	INTSML fun, backannotate;
	NETWORK *net, *cnet, *onet;
	VARIABLE *var;
	NODEINST *ni;
	PORTARCINST *pi;
	PORTEXPINST *pe;
	PORTPROTO *pp, *cpp;
	NODEPROTO *onp, *cnp;
	ARCINST *ai;
	char *pt, *extra, *iname, *oname, line[WORD+1], *direction;
	INTSML globalport;
	INTSML contents, schematic;
	INTBIG netindex, pageindex, is_array, sx, mx, rx, sy, my, ry;
	INTSML diffcount;
	XARRAY trans;
	INTBIG bx, by, xpos, ypos;
	char name[WORD+1], page[10];
	static POLYGON *poly = NOPOLYGON;
	static INTBIG EDIF_name_key = 0;
	static INTBIG EDIF_array_key = 0;

	if (EDIF_name_key == 0) EDIF_name_key = makekey("EDIF_name");
	if (EDIF_array_key == 0) EDIF_array_key = makekey("EDIF_array");

	/* stop if requested */
	if (el_pleasestop != 0)
	{
		stopping(STOPREASONEDIF);
		return(0);
	}

	/* get polygon */
	if (schematic_view && poly == NOPOLYGON) poly = allocstaticpolygon(4, io_aid->cluster);

	/* recurse on sub-facets first */
	backannotate = 0;
	if (np->cellview != el_iconview)
	{
		for (ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
		{
			ni->temp1 = 0;
			if (ni->proto->primindex != 0) continue;

			/* do not expand "monitor_probe" construct (icon) */
			if (namesame(ni->proto->cell->cellname, "monitor_probe") == 0) continue;

			/* get actual subfacet (including contents/body distinction) */
			onp = contentsview(ni->proto);
			if (onp == NONODEPROTO) onp = ni->proto;

			/* write the subfacet */
			if (onp->temp1 == 0)
			{
				if (io_edifwritefacet(onp, external) != 0) backannotate = 1;
			}
		}
	}

	/* check whether writing external or contents facets */
	if (external > 0 && np->cellview != el_iconview) return(backannotate);

	/* check whether this facet is in this external library */
	if (external > 0 && np->temp2 != external) return(backannotate);

	/* make sure that all nodes and networks have names on them */
	if (askaid(net_aid, "name-nodes", (INTBIG)np) != 0) backannotate++;
	if (askaid(net_aid, "name-nets", (INTBIG)np) != 0) backannotate++;

	/* mark this facet as written */
	np->temp1++;

	/* assign bus names to unnamed bus wires connecting bus ports on objects */
	netindex = 1;
	/* sim_namebusnets(np, &netindex); */

	/* clear unnamed net identifier field */
	for (net = np->firstnetwork; net != NONETWORK; net = net->nextnetwork)
		net->temp1 = 0;

	/* write out the cell header information */
	EO_open_block(edif_stream, "cell");
	EO_put_identifier(edif_stream, io_ediftoken(np->cell->cellname));
	EO_put_block(edif_stream, "cellType", "generic");
	EO_open_block(edif_stream, "view");
	EO_put_identifier(edif_stream, "cell");
	EO_put_block(edif_stream, "viewType", schematic_view ? "SCHEMATIC" : "NETLIST");

	/* write out the interface description */
	EO_open_block(edif_stream, "interface");

	/* list all ports in interface except global ports in network order */
	(void)io_edifmarknetports(np);

	/* count check on differentialGroup property */
	diffcount = 0;
	for (net = np->firstnetwork; net != NONETWORK; net = net->nextnetwork)
	{
		if ((pp = (PORTPROTO *) net->temp2) != NOPORTPROTO && io_edifisglobal(pp) == 0)
		{
			switch (pp->userbits & STATEBITS)
			{
				case OUTPORT:
					direction = "OUTPUT";
					break;
				case BIDIRPORT:
					direction = "INOUT";
					break;
				case REFOUTPORT:
					direction = "OUTPUT";
					break;
				case INPORT:
				case REFINPORT:
				default:
					direction = "INPUT";
					break;
			}
			EO_open_block(edif_stream, "port");
			EO_put_identifier(edif_stream, io_ediftoken(net->netname));
			if (strlen(direction) > 0)
				EO_put_block(edif_stream, "direction", direction);

			/* list port properties if they exist on this schematic */
			/* if (np->cellview != el_iconview) listPortProperties(pp, direction, net, diffcount);   */

			EO_close_block(edif_stream, "port");
		}
	}
	if (schematic_view && np->cellview == el_iconview)
	{
		/* output the icon */
		io_edifsymbol(np);
	}

	EO_close_block(edif_stream, "interface");

	if (diffcount != 0)
		ttyputmsg(_("** WARNING - unmatched constructed differentialGroup property in %s"),
			describenodeproto(np));

	/* list contents if expanding */
	if (np->cellview != el_iconview)
	{
		/* write the components, if there are any */
		contents = 0;

		/* determine if this is a schematic view */
		if (schematic_view && !strncmp(np->cellview->viewname, "schematic-page-", 15))
		{
			/* set beginning page */
			pageindex = 1;
			(void)sprintf(page, "P%d", pageindex);

			/* locate the next like in cell */
			for (np = np->cell->firstincell; np != NONODEPROTO; np = np->nextincell)
				if (!namesame(np->cellview->sviewname, page))
			{
				/* list all ports in interface except global ports in network order */
				(void)io_edifmarknetports(np);
				break;
			}
			schematic = 1;
		} else schematic = 0;

		contents = 0;
		while (np != NONODEPROTO)
		{
			if (np->firstnodeinst != NONODEINST)
			{
				if (contents++ == 0) EO_open_block(edif_stream, "contents");
				if (schematic_view && schematic)
				{
					EO_open_block(edif_stream, "page");
					EO_put_identifier(edif_stream, np->cellview->sviewname);
				}
				for (ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
				{
					fun = nodefunction(ni, &extra);
					if (ni->proto->primindex != 0)
					{
						if (fun == NPUNKNOWN || fun == NPPIN || fun == NPCONTACT ||
							fun == NPNODE || fun == NPCONNECT || fun == NPART) continue;
					} else if (namesame(ni->proto->cell->cellname, "monitor_probe") == 0)
					continue;

					EO_open_block(edif_stream, "instance");
					iname = io_edifwritecompname(ni, fun, 0);
					if ((var = getvalkey((INTBIG) ni, VNODEINST, VSTRING, el_node_name)) != NOVARIABLE)
						oname = (char *)var->addr; else
							oname = iname;

					/* check for an array */
					is_array = 0;
					if ((var = getvalkey((INTBIG) ni, VNODEINST, VSTRING, EDIF_array_key)) != NOVARIABLE)
					{
						/* decode the array bounds min:max:range min:max:range */
						(void)sscanf((char *)var->addr, "%ld:%ld:%ld %ld:%ld:%ld", &sx, &mx, &rx, &sy, &my, &ry);
						if (sx != mx || sy != my)
						{
							is_array = 1;
							EO_open_block(edif_stream, "array");
						}
					}

					if (namesame(oname, iname))
					{
						EO_open_block(edif_stream, "rename");
						EO_put_identifier(edif_stream, iname);
						EO_put_string(edif_stream, oname);
						EO_close_block(edif_stream, "rename");
					} else EO_put_identifier(edif_stream, iname);

					if (is_array)
					{
						if (rx > 1) EO_put_integer(edif_stream, rx);
						if (ry > 1) EO_put_integer(edif_stream, ry);
						EO_close_block(edif_stream, "array");
					}

					if (ni->proto->primindex != 0)
					{
						EO_open_block(edif_stream, "viewRef");
						EO_put_identifier(edif_stream, "cell");
						EO_open_block(edif_stream, "cellRef");
						if (fun == NPGATEAND || fun == NPGATEOR || fun == NPGATEXOR)
						{
							/* count the number of inputs */
							i = 0;
							for (pi = ni->firstportarcinst; pi != NOPORTARCINST; pi = pi->nextportarcinst)
								if (pi->proto == ni->proto->firstportproto) i++;
							(void)sprintf(name, "%s%d", io_ediftoken(ni->proto->primname), i);
							EO_put_identifier(edif_stream, name);
						} else EO_put_identifier(edif_stream, io_edifdescribepriminst(ni, fun));
						EO_put_block(edif_stream, "libraryRef", "lib0");
						EO_close_block(edif_stream, "viewRef");
					} else if (ni->proto->cellview == el_iconview &&
						contentsview(ni->proto) == NONODEPROTO)
					{
						/* this node came from an external schematic library */
						EO_open_block(edif_stream, "viewRef");
						EO_put_identifier(edif_stream, "cell");
						EO_open_block(edif_stream, "cellRef");
						EO_put_identifier(edif_stream, io_ediftoken(ni->proto->cell->cellname));
						(void)sprintf(name, "schem_lib_%ld", ni->proto->temp2);
						EO_put_block(edif_stream, "libraryRef", name);
						EO_close_block(edif_stream, "viewRef");
					} else
					{
						/* this node came from this library */
						EO_open_block(edif_stream, "viewRef");
						EO_put_identifier(edif_stream, "cell");
						EO_open_block(edif_stream, "cellRef");
						EO_put_identifier(edif_stream, io_ediftoken(ni->proto->cell->cellname));
						EO_put_block(edif_stream, "libraryRef", np->cell->lib->libname);
						EO_close_block(edif_stream, "viewRef");
					}

					/* now graphical information */
					if (schematic_view)
					{
						EO_open_block(edif_stream, "transform");

						/* get the orientation (note only support orthogonal) */
						EO_put_block(edif_stream, "orientation", io_edif_orientation(ni));

						/* now the origin */
						EO_open_block(edif_stream, "origin");
						var = getvalkey((INTBIG) ni->proto, VNODEPROTO, VINTEGER | VISARRAY,
							el_prototype_center);
						if (var != NOVARIABLE)
						{
							bx = ((INTBIG *) var->addr)[0] + (ni->lowx + ni->highx) / 2 -
								(ni->proto->lowx + ni->proto->highx) / 2;
							by = ((INTBIG *) var->addr)[1] + (ni->lowy + ni->highy) / 2 -
								(ni->proto->lowy + ni->proto->highy) / 2;
						} else
						{
							/* use center of node */
							/* now origin, normal placement */
							bx = (ni->lowx - ni->proto->lowx);
							by = (ni->lowy - ni->proto->lowy);
						}
						makerot(ni, trans);
						xform(bx, by, &xpos, &ypos, trans);
						io_edif_pt(xpos, ypos);
						EO_close_block(edif_stream, "transform");
					}


					/* check for variables to write as properties */
					if (schematic_view)
					{
						/* do all display variables first */
						displaytotal = tech_displayablenvars(ni);
						for (i = 0; i < displaytotal; i++)
						{
							var = tech_filldisplayablenvar(ni, poly);
							xformpoly(poly, trans);
							/* check for name */
							if (namesame((pt = makename(var->key)), "EDIF_annotate"))
							{
								/* open the property (all properties are strings at this time) */
								EO_open_block(edif_stream, "property");
								EO_put_identifier(edif_stream, pt);
								EO_open_block(edif_stream, "string");
							} else
							{
								EO_open_block(edif_stream, "annotate");
								pt = NULL;
							}
							io_edifsymbol_showpoly(poly);
							if (pt != NULL) EO_close_block(edif_stream, "property"); else
								EO_close_block(edif_stream, "annotate");
						}
					}
					EO_close_block(edif_stream, "instance");
				}

				/* search for unconnected inputs */
				for (ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
				{
					if (ni->proto->primindex != 0)
					{
						fun = nodefunction(ni, &extra);
						if (fun != NPUNKNOWN && fun != NPPIN && fun != NPCONTACT &&
							fun != NPNODE && fun != NPCONNECT && fun != NPART) continue;
					} else if (namesame(ni->proto->cell->cellname, "monitor_probe") == 0) continue;

					for (pp = ni->proto->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
					{
						if ((pp->userbits & STATEBITS) == INPORT || (pp->userbits & STATEBITS) == REFINPORT)
						{
							onet = NONETWORK;
							for (pi = ni->firstportarcinst; pi != NOPORTARCINST; pi = pi->nextportarcinst)
								if (pi->proto == pp) break;

							if (pi != NOPORTARCINST) onet = pi->conarcinst->network; else
							{
								for (pe = ni->firstportexpinst; pe != NOPORTEXPINST; pe = pe->nextportexpinst)
									if (pe->proto == pp) break;
								if (pe != NOPORTEXPINST) onet = pe->exportproto->network;
							}

							if (onet == NONETWORK)
								ttyputmsg(_("** WARNING - no connection to %s port %s on %s in %s"),
									(((pp->userbits & STATEBITS) == INPORT) ? _("input") : _("vbias")),
										pp->protoname, describenodeinst(ni),
											describenodeproto(ni->parent));
							else if (onet->signals < pp->network->signals)
								for (i = onet->signals; i != pp->network->signals; i++)
									ttyputmsg(_("** WARNING - no connection to %s port %s (signal %d) on %s in %s"),
										((pp->userbits & STATEBITS) == INPORT ? _("input") : _("vbias")),
							 				pp->network->networklist[i]->netname, i,
												 describenodeinst(ni), describenodeproto(ni->parent));
						}
					}
				}

				transid(trans);

				/* if there is anything to connect, write the networks in the facet */
				for (net = np->firstnetwork; net != NONETWORK; net = net->nextnetwork)
				{
					/* skip bus networks altogether (they are done wire by wire) */
					if (net->signals > 1)
					{
						/* handle bus description, note that most nets have single arc
						description which is handled below */
						/* evaluate the bus name, look for net arrays */
						if (net->namecount > 0)
						{
						}
						EO_open_block(edif_stream, "netBundle");
						if (net->namecount > 0)
						{
							oname = io_ediftoken(net->netname);
							if (namesame(oname, net->netname))
							{
								/* different names */
								EO_open_block(edif_stream, "rename");
								EO_put_identifier(edif_stream, oname);
								EO_put_string(edif_stream, net->netname);
								EO_close_block(edif_stream, "rename");
							} else EO_put_identifier(edif_stream, oname);
						} else
						{
							net->temp1 = netindex++;
							(void)sprintf(line, "BUS%ld", net->temp1);
							EO_put_identifier(edif_stream, line);
						}
						EO_open_block(edif_stream, "listOfNets");

						/* now each sub-net name */
						for (i = 0; i < net->signals; i++)
						{
							EO_open_block(edif_stream, "net");

							/* now output this name */
							if (net->networklist[i]->namecount > 0)
							{
								oname = io_ediftoken(net->netname);
								if (namesame(oname, net->netname))
								{
									/* different names */
									EO_open_block(edif_stream, "rename");
									EO_put_identifier(edif_stream, oname);
									EO_put_string(edif_stream, net->netname);
									EO_close_block(edif_stream, "rename");
								} else EO_put_identifier(edif_stream, oname);
								(void)setvalkey((INTBIG)net, VNETWORK, EDIF_name_key, (INTBIG)line, VSTRING);
							} else
							{
								if (net->networklist[i]->temp1 != 0)
									net->networklist[i]->temp1 = netindex++;
								(void)sprintf(line, "NET%ld", net->networklist[i]->temp1);
								(void)setvalkey((INTBIG)net, VNETWORK, EDIF_name_key, (INTBIG)line, VSTRING);
								EO_put_identifier(edif_stream, line);
							}
							EO_close_block(edif_stream, "net");
						}

						/* now graphics for the bus */
						if (schematic_view)
						{
							/* output net graphic information */
							/* output all arc instances connected to this net */
							egraphic = EGUNKNOWN;
							egraphic_override = EGBUS;
							for (ai = np->firstarcinst; ai != NOARCINST; ai = ai->nextarcinst)
								if (ai->network == net) io_edifsymbol_arcinst(ai, trans);
							io_edifsetgraphic(EGUNKNOWN);
							egraphic_override = EGUNKNOWN;
						}

						EO_close_block(edif_stream, "netBundle");
						continue;
					}

					/* skip networks that are not connected to anything real */
					if (net->buslinkcount == 0 && net->portcount == 0)
					{
						for (ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
						{
							if (ni->proto->primindex == 0 && namesame(ni->proto->cell->cellname,
								"monitor_probe") == 0) continue;

							for (pi = ni->firstportarcinst; pi != NOPORTARCINST; pi = pi->nextportarcinst)
								if (pi->conarcinst->network == net) break;

							if (pi == NOPORTARCINST)
							{
								for (pe = ni->firstportexpinst; pe != NOPORTEXPINST; pe = pe->nextportexpinst)
									if (pe->exportproto->network == net) break;
								if (pe == NOPORTEXPINST) continue;
							}
							if (ni->proto->primindex == 0) break;

							fun = nodefunction(ni, &extra);
							if (fun != NPUNKNOWN && fun != NPPIN && fun != NPCONTACT &&
								fun != NPNODE && fun != NPCONNECT && fun != NPART) break;
						}
						if (ni == NONODEINST) continue;
					}

					/* establish if this is a global net */
					globalport = 0;
					if ((pp = (PORTPROTO *) net->temp2) != NOPORTPROTO)
						globalport = io_edifisglobal(pp);

					EO_open_block(edif_stream, "net");
					if (net->namecount > 0)
					{
						if (globalport != 0)
						{
							EO_open_block(edif_stream, "rename");
							EO_put_identifier(edif_stream, io_ediftoken(net->netname));
							(void)sprintf(name, "%s!", io_ediftoken(net->netname));
							EO_put_identifier(edif_stream, name);
							EO_close_block(edif_stream, "rename");
							EO_put_block(edif_stream, "property", "GLOBAL");
						} else
						{
							oname = io_ediftoken(net->netname);
							if (namesame(oname, net->netname))
							{
								/* different names */
								EO_open_block(edif_stream, "rename");
								EO_put_identifier(edif_stream, oname);
								EO_put_string(edif_stream, net->netname);
								EO_close_block(edif_stream, "rename");
							} else EO_put_identifier(edif_stream, oname);
							(void)setvalkey((INTBIG)net, VNETWORK, EDIF_name_key, (INTBIG)line, VSTRING);
						}
					} else
					{
						net->temp1 = netindex++;
						(void)sprintf(line, "NET%ld", net->temp1);
						(void)setvalkey((INTBIG)net, VNETWORK, EDIF_name_key, (INTBIG)line, VSTRING);
						EO_put_identifier(edif_stream, line);
					}

					/* write net connections */
					EO_open_block(edif_stream, "joined");

					/* include exported ports (by net name) */
					if (pp != NOPORTPROTO && globalport == 0)
						EO_put_block(edif_stream, "portRef", io_ediftoken(net->netname));

					/* now include components using existing net-port pointers */
					for (ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
					{
						if (ni->proto->primindex != 0)
						{
							/* ignore passive components */
							fun = nodefunction(ni, &extra);
							if (fun == NPUNKNOWN || fun == NPPIN || fun == NPCONTACT ||
								fun == NPNODE || fun == NPCONNECT || fun == NPART) continue;
							cnp = ni->proto;
						} else
						{
							if (namesame(ni->proto->cell->cellname, "monitor_probe") == 0)
								continue;
							fun = NPUNKNOWN;

							/* get contents of the nodeinst to establish net connections */
							if ((cnp = contentsview(ni->proto)) == NONODEPROTO) cnp = ni->proto;
						}

						/* be sure each connection is written only once */
						for (cpp = cnp->firstportproto; cpp != NOPORTPROTO; cpp = cpp->nextportproto)
							cpp->temp1 = 0;

						/* write connection to ports exported directly */
						for (pe = ni->firstportexpinst; pe != NOPORTEXPINST; pe = pe->nextportexpinst)
						{
							if (pe->exportproto != NOPORTPROTO && pe->exportproto->network == net)
							{
								/* locate the name being used */
								if ((cpp = equivalentport(ni->proto, pe->proto, cnp)) == NOPORTPROTO)
									cpp = pe->proto;
								if (ni->proto->primindex == 0 && (PORTPROTO *)(cpp->network->temp2) != NOPORTPROTO)
									cpp = (PORTPROTO *)(cpp->network->temp2);
								if (cpp->temp1++ != 0) continue;

								if (ni->proto->primindex == 0) pt = cpp->network->netname; else
									pt = cpp->protoname;

								EO_open_block(edif_stream, "portRef");
								EO_put_identifier(edif_stream, io_ediftoken(pt));
								EO_put_block(edif_stream, "instanceRef", io_edifwritecompname(ni, fun, 0));
								EO_close_block(edif_stream, "portRef");
								cpp->temp1++;
							}
						}

						/* write single-wire direct connections */
						for (pi = ni->firstportarcinst; pi != NOPORTARCINST; pi = pi->nextportarcinst)
						{
							if ((pi->conarcinst->network == net))
							{
								if ((cpp = equivalentport(ni->proto, pi->proto, cnp)) == NOPORTPROTO)
									cpp = pi->proto;

								if (net_buswidth(pi->proto->protoname) == 1)
								{
									if (ni->proto->primindex == 0 && (PORTPROTO *)(cpp->network->temp2) != NOPORTPROTO)
										cpp = (PORTPROTO *)(cpp->network->temp2);
									if (cpp->temp1++ != 0) continue;

									if (ni->proto->primindex == 0) pt = cpp->network->netname; else
										pt = cpp->protoname;
									EO_open_block(edif_stream, "portRef");
									EO_put_identifier(edif_stream, io_ediftoken(pt));
									EO_put_block(edif_stream, "instanceRef", io_edifwritecompname(ni, fun, 0));
									EO_close_block(edif_stream, "portRef");
								} else
								{
									/* connect to first signal in the bus */
									if (ni->proto->primindex == 0)
									{
										EO_open_block(edif_stream, "portRef");
										EO_put_identifier(edif_stream,
											io_ediftoken(pi->proto->network->networklist[0]->netname));
										EO_open_block(edif_stream, "portRef");
										EO_put_block(edif_stream, "instanceRef",
											io_edifwritecompname(ni, fun, 0));
										EO_close_block(edif_stream, "portRef");
									} else ttyputerr(_("Cannot handle primitives with bus pins"));
								}
								cpp->temp1++;
							}
						}

						/* match up exported net with bus connections on this nodeinst */
						for (pi = ni->firstportarcinst; pi != NOPORTARCINST; pi = pi->nextportarcinst)
						{
							if ((netcount = pi->conarcinst->network->signals) > 1)
							{
								/* first, find the connection point if there is one */
								for (i = 0; i < netcount; i++)
									if (pi->conarcinst->network->networklist[i] == net) break;
								if (i == netcount) continue;

								if ((cpp = equivalentport(ni->proto, pi->proto, cnp)) == NOPORTPROTO)
									cpp = pi->proto;

								/* skip if already connected */
								if (cpp->temp1 != 0) continue;

								/* associate by the i-th position in the connection */
								EO_open_block(edif_stream, "portRef");
								if (cpp->network->signals > i)
								{
									if (cpp->network->signals > 1)
										cnet = cpp->network->networklist[i]; else
											cnet = cpp->network;

									/* now transform to the port identification network */
									cpp = (PORTPROTO *) cnet->temp2;

									/* skip if already connected */
									if (cpp->temp1++ != 0) continue;
									EO_put_identifier(edif_stream, io_ediftoken(cnet->netname));
								} else
								{
									ttyputerr(_("Proto bus width too narrow at %s {signal %d} in %s"),
										cpp->network->netname, i, describenodeproto(cpp->parent));
									EO_put_identifier(edif_stream,
										io_ediftoken(cpp->network->netname));
								}
								EO_put_block(edif_stream, "instanceRef",
									io_edifwritecompname(ni, fun, 0));
								EO_close_block(edif_stream, "portRef");
							}
						}

						/* continue with connected busses */
						for (pe = ni->firstportexpinst; pe != NOPORTEXPINST; pe = pe->nextportexpinst)
						{
							if ((netcount = pe->exportproto->network->signals) > 1)
							{
								/* first, find the connection point if there is one */
								for (i = 0; i < netcount; i++)
									if (pe->exportproto->network->networklist[i] == net) break;
								if (i == netcount) continue;

								if ((cpp = equivalentport(ni->proto, pe->proto, cnp)) == NOPORTPROTO)
									cpp = pe->proto;

								/* associate by the i-th position in the connection */
								EO_open_block(edif_stream, "portRef");
								if (cpp->network->signals > i)
								{
									if (cpp->network->signals > 1)
										EO_put_identifier(edif_stream,
											io_ediftoken(cpp->network->networklist[i]->netname));
									else EO_put_identifier(edif_stream,
										io_ediftoken(cpp->network->netname));
								} else
								{
									ttyputerr(_("Proto bus width too narrow at %s{signal %d}"),
										cpp->network->netname);
									EO_put_identifier(edif_stream,
										io_ediftoken(cpp->network->netname));
								}
								EO_put_block(edif_stream, "instanceRef",
									io_edifwritecompname(ni, fun, 0));
								EO_close_block(edif_stream, "portRef");

								cpp->temp1++;
							}
						}
					} /* for ni = ... */
					EO_close_block(edif_stream, "joined");

					if (schematic_view)
					{
						/* output net graphic information */
						/* output all arc instances connected to this net */
						egraphic = EGUNKNOWN;
						egraphic_override = EGWIRE;
						for (ai = np->firstarcinst; ai != NOARCINST; ai = ai->nextarcinst)
							if (ai->network == net) io_edifsymbol_arcinst(ai, trans);
						io_edifsetgraphic(EGUNKNOWN);
						egraphic_override = EGUNKNOWN;
					}

					if (globalport != 0)
						EO_put_block(edif_stream, "userData", "global");
					EO_close_block(edif_stream, "net");
				} /* for (net = ... */
				if (schematic_view && schematic)
					EO_close_block(edif_stream, "page");
			}	/* if np->firstnodeinst != NONODEINST */

			if (schematic)
			{
				/* get next schematic */
				pageindex++;
				(void)sprintf(page, "P%d", pageindex);
				for (np = np->cell->firstincell; np != NONODEPROTO; np = np->nextincell)
					if (!namesame(np->cellview->sviewname, page))
				{
					/* list all ports in interface except global ports in network order */
					(void)io_edifmarknetports(np);
					break;
				}
				if (np == NONODEPROTO) break;
			} else break;
		} /* while np != NONODEPROTO */
	}

	/* matches "(cell " */
	EO_close_block(edif_stream, "cell");
	return(backannotate);
}

/*
 * procedure to properly identify an instance of a primitive node
 * for ASPECT netlists
 */
char *io_edifdescribepriminst(NODEINST *ni, INTSML fun)
{
	REGISTER VARIABLE *var;
	char *model;

	switch (fun)
	{
		case NPRESIST:
			var = getvalkey((INTBIG) ni, VNODEINST, VSTRING, io_edifospice_model);
			if (var == NOVARIABLE) return("Resistor");

			model = (char *) var->addr;
			if (namesamen(model, "PN", 2) == 0) return("res_pnpoly");
			if (namesamen(model, "NP", 2) == 0) return("res_nppoly");
			if (namesamen(model, "PP", 2) == 0) return("res_pppoly");
			if (namesamen(model, "BL", 2) == 0) return("res_bl");
			if (namesamen(model, "EP", 2) == 0) return("res_epi");
			return("Resistor");
		case NPTRANPN:
		case NPTRANSREF:
			return("npn");
		case NPTRAPNP:
			return("pnp");
		case NPSUBSTRATE:
			return("gtap");
		case NPDIODE:
			var = getvalkey((INTBIG) ni, VNODEINST, VSTRING, io_edifospice_model);
			if (var != NOVARIABLE && namesamen("subtap", (char *) var->addr, 6) == 0)
				return("gtap");
	}
	return(io_ediftoken(ni->proto->primname));
}

/*
 * Establish whether port 'pp' is a global port or not
 */
INTSML io_edifisglobal(PORTPROTO *pp)
{
	NODEPROTO *inp;

	/* pp is a global port if it is marked global */
	if ((pp->userbits & BODYONLY) != 0) return(1);

	/* or if it does not exist on the icon */
	if ((inp = iconview(pp->parent)) == NONODEPROTO) return(0);
	if (equivalentport(pp->parent, pp, inp) == NOPORTPROTO) return(1);
	return(0);
}

/*
 * routine to mark all nets' temp2 variables for interfacing purposes.
 * To determine the connection, temp2 is marked with a pointer to the
 * port which exports the net.  Returns the number of nets exported.
 * Note: only single-wire nets are marked; all bus nets' temp2 are
 * marked NOPORTPROTO.  Multiply exported nets have been collapsed at
 * both levels of hierarchy to a single net by the network maintainer.
 */
INTSML io_edifmarknetports(NODEPROTO *np)
{
	NETWORK *net;
	PORTPROTO *pp;
	REGISTER INTSML i, count, portnetcount;

	for (net = np->firstnetwork; net != NONETWORK; net = net->nextnetwork)
		net->temp2 = (INTBIG) NOPORTPROTO;

	/* initialize count of exported individual nets */
	portnetcount = 0;
	for (pp = np->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
	{
		/* mark the nets that were exported with a pointer to their first port */
		if ((count = pp->network->signals) > 1)
			for (i = 0; i < count; i++)
		{
			if (pp->network->networklist[i]->temp2 == (INTBIG) NOPORTPROTO)
			{
				pp->network->networklist[i]->temp2 = (INTBIG) pp;
				portnetcount++;
			}
		} else if (pp->network->temp2 == (INTBIG) NOPORTPROTO)
		{
			pp->network->temp2 = (INTBIG) pp;
			portnetcount++;
		}
	}
	return(portnetcount);
}

/*
 * function to incorporate external library interface data or reference file
 * for current facet into EDIF netlist - returns 0 if successful
 */
INTSML io_edifwritelibext(NODEPROTO *np)
{
	REGISTER FILE *f;
	char buf[256], *filename;
	REGISTER INTBIG count;
	REGISTER VARIABLE *var;

	/* import the external library for this facet, if it exists */
	var = getval((INTBIG) np, VNODEPROTO, VSTRING, "EDIF_external_lib");
	if (var == NOVARIABLE) return(1);
	f = xopen((char *)var->addr, io_filetypeedif, "", &filename);
	if (f == NULL)
	{
		ttyputerr(_("Cannot find EDIF external reference file %s on facet %s"),
			(char *) var->addr, describenodeproto(np));
		return(1);
	}
	for (;;)
	{			/* copy the file */
		count = xfread(buf, 1, 256, f);
		if (count <= 0) break;
		if (xfwrite(buf, 1, count, edif_stream->file) != count)
		{
			ttyputerr(_("Error copying EDIF reference file %s"), (char *) var->addr);
			xclose(f);
			return(1);
		}
	}
	xclose(f);
	ttyputmsg(_("Incorporated external EDIF reference file %s"), (char *) var->addr);
	return(0);
}

/*
 * convert a string token into a valid EDIF string token (note - NOT re-entrant coding)
 * In order to use NSC program ce2verilog, we need to suppress the '_' which replaces
 * ']' in bus definitions.
 */
char *io_ediftoken(char *str)
{
	static char token[100];
	char *tkptr;
	REGISTER INTSML i;

	i = 0;
	if (*str >= '0' && *str <= '9') token[i++] = 'X';
	for (tkptr = token; (token[i++] = io_edifnotwhitespace(*str)); str++)
	{
		if (*str == ']') i--;			/* suppress the ']' */
			else if (io_edifvalid(*str) == 0) token[i - 1] = '_';
	}
	token[i - 1] = '\0';
	return(tkptr);
}

char io_edifnotwhitespace(char ch)
{
	if (ch == '\t' || ch == '\r' || ch == '\n' || ch == ' ') return('\0');
	return(ch);
}

INTSML io_edifvalid(char ch)
{
	if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') ||
		(ch >= '0' && ch <= '9') || ch == '&' || ch == '_') return(1);
	return(0);
}

/*
 * Returns 0 there is no valid name in "var", corrected name if valid.
 */
char *io_edifvalidname(VARIABLE *var)
{
	char *iptr;
	static char name[WORD+1];

	if (var == NOVARIABLE) return(0);
	strcpy(name, (char *)var->addr);
	if (strlen(name) == 0) return(0);
	iptr = name;

	/* allow '&' for the first character (this must be fixed latter if digit or '_') */
	if (*iptr == '&') iptr++;

	/* allow _ and alphanumeric for others */
	for (iptr++; *iptr != 0; iptr++)
		if (*iptr != '_' && !(*iptr >= 'a' && *iptr <= 'z') && !(*iptr >= 'A' && *iptr <= 'Z') &&
			!(*iptr >= '0' && *iptr <= '9')) *iptr = '_';

	return(name);
}

/*
 * Write the EDIF format of the footprint, assuming standard
 * cell grid of routegrid (centimicrons) and standard connections
 * only on Metal1 and Metal2.  cellType is obtained from the
 * stopExpand property, which is a property attached to
 * either the layout view or the schematic view (if there is one).
 */
INTSML io_edifwritefootprint(NODEPROTO *np, char *name, INTBIG routegrid)
{
	char iname[100], *cellType, *cellRep, *portAccess, *defaultAccess, *truename;
	INTSML connections, allports;
	NODEPROTO *vnp;
	PORTPROTO *pp;
	VARIABLE *var;
	INTBIG width, height, access;
	float route;

	(void)strcpy(iname, name);
	(void)strcat(iname, ".foot");
	io_fileout = xcreate(iname, io_filetypeedif, "EDIF File", &truename);
	if (io_fileout == NULL)
	{
		if (truename != 0) ttyputerr(_("Cannot write %s"), iname);
		return(1);
	}

	/* make the access variable key */
	access = makekey("EDIF_access");

	/* calculate the actual routing grid in microns */
	route = (float)routegrid;
	route /= 100;

	/* write the header */
	ttyputmsg(_("Writing footprint for cell %s"), io_ediftoken(np->cell->cellname));
	xprintf(io_fileout, _("(footprint %.2fe-06\n"), route);

	/* identify the layout type from the schematic 'stopExpand' property */
	var = NOVARIABLE;
	for (vnp = np->cell->firstincell; vnp != NONODEPROTO; vnp = vnp->nextincell)
		if (vnp->cellview == el_schematicview) break;
	if (vnp != NONODEPROTO)
		var = getval((INTBIG) vnp, VNODEPROTO, VSTRING, "EDIF_stopExpand");
	if (var != NOVARIABLE) cellType = (char *) var->addr; else
	{
		ttyputerr(_("cannot find stopExpand property on schematic view"));
		var = getval((INTBIG) np, VNODEPROTO, VSTRING, "EDIF_stopExpand");
		if (var != NOVARIABLE)
		{
			cellType = (char *) var->addr;
			ttyputmsg(_(" - - (but it is attached to the layout)"));
		} else cellType = "unknownLayoutRep";
	}
	if (namesamen(cellType, "subchip", 7) == 0) cellType = "subchip";
	xprintf(io_fileout, " (%s\n", cellType);

	/* get representation type from schematic (or layout) 'repType' property */
	var = NOVARIABLE;
	for (vnp = np->cell->firstincell; vnp != NONODEPROTO; vnp = vnp->nextincell)
		if (vnp->cellview == el_schematicview) break;
	if (vnp != NONODEPROTO)
		var = getval((INTBIG) vnp, VNODEPROTO, VSTRING, "EDIF_repType");
	if (var != NOVARIABLE) cellRep = (char *) var->addr; else
	{
		var = getval((INTBIG) np, VNODEPROTO, VSTRING, "EDIF_repType");
		if (var != NOVARIABLE) cellRep = (char *) var->addr; else
			if (namesame(cellType, "subchip") == 0) cellRep = "logicCell"; else
				cellRep = "standard";
	}

	/* get standard cell dimensions */
	width = (np->highx - np->lowx) / routegrid;
	height = (np->highy - np->lowy) / routegrid;
	xprintf(io_fileout, "  (%s %s (%d %d)\n",
		io_ediftoken(np->cell->cellname), cellRep, height, width);

	/* find out if all port connections are wanted */
	var = getval((INTBIG) np, VNODEPROTO, VSTRING, "EDIF_placeallports");
	if (var != NOVARIABLE)
	{
		allports = 1;
		defaultAccess = (char *) var->addr;
	} else allports = 0;

	/* find all ports */
	for (pp = np->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
	{
		/* global connections are made in standard cells by abutment */
		if (allports == 0 && (pp->userbits & BODYONLY) != 0) continue;

		var = getvalkey((INTBIG) pp, VPORTPROTO, VSTRING, access);
		if (var == NOVARIABLE)
		{
			var = getvalkey((INTBIG) pp->subnodeinst, VNODEINST, VSTRING, access);
			if (allports == 0 && var == NOVARIABLE)
			{
				ttyputmsg(_("Non-global port %s has no access direction"), pp->protoname);
				continue;
			}
		}

		/* set up access field */
		if (var != NOVARIABLE) portAccess = (char *) var->addr; else
			portAccess = defaultAccess;

		/* find the connecting layers */
		connections = io_edifwriteportpositions(pp, np->lowx, np->lowy, portAccess, routegrid);

		if (connections == 0)
			ttyputmsg(_("Non-global port %s has no connection access"), pp->protoname);
	}

	/* finish up */
	xprintf(io_fileout, ")))\n");
	xclose(io_fileout);
	ttyputmsg(_("%s written"), iname);

	return(0);
}

/*
 * Access rules for port positions are in the port variable "EDIF_access"
 * which takes the form "material:DIR,material:DIR," for all valid
 * materials.
 */
INTSML io_edifwriteportpositions(PORTPROTO *pp, INTBIG cellx, INTBIG celly,
	char *accessrules, INTBIG routegrid)
{
	char accessdirection[20], material[30], *pt, *portdirection;
	float xpos, ypos, scale;
	INTBIG xsize, ysize, cx, cy;
	INTSML i, j, connections;
	NODEINST *ni;
	PORTPROTO *ppt;
	static POLYGON *poly = NOPOLYGON;

	/* establish the subnodeinst on which this port prototype resides */
	ni = pp->subnodeinst;

	/* find which port proto is exported here */
	if (ni->parent->primindex != 0) ppt = pp; else
		ppt = pp->subportproto;

	/* get the port signal direction */
	switch (pp->userbits & STATEBITS)
	{
		case REFINPORT:
		case INPORT:
			portdirection = "input";
			break;
		case REFOUTPORT:
		case OUTPORT:
			portdirection = "output";
			break;
		case BIDIRPORT:
			portdirection = "inout";
			break;
		default:
			if (namesamen(pp->protoname, "feed", 4) == 0) portdirection = "feed"; else
				portdirection = "unknown";
	}

	/* get polygon */
	if (poly == NOPOLYGON) poly = allocstaticpolygon(4, io_aid->cluster);

	/* get this port geometry */
	shapeportpoly(ni, ppt, poly, 0);
	getcenter(poly, &cx, &cy);

	/* offset center based on cell lower left corner */
	cx -= cellx;
	cy -= celly;

	/* default all connection sizes to 4x4 microns */
	xsize = 4, ysize = 4;

	/* make floating point port positions and scale them */
	scale = (float)routegrid;
	xpos = (float)cx;
	xpos /= scale;
	ypos = (float)cy;
	ypos /= scale;

	i = j = connections = 0;
	for (pt = accessrules;; pt++)
	{
		if (i == 0)
		{
			if (*pt == 0) break;
			if (*pt != ':')
			{
				material[j] = *pt;
				if (isupper(material[j])) tolower(material[j]);
				j++;
				continue;
			}
			material[j] = 0;
			i++;
			j = 0;
			continue;
		}
		if (*pt != ',' && *pt != 0)
		{
			accessdirection[j++] = *pt;
			continue;
		}
		accessdirection[j] = 0;
		xprintf(io_fileout, "   (%-9s %-7s (%-7s (%5.2f %5.2f) (%lde-6 %lde-6) %s))\n",
			(namesame(portdirection, "feed") == 0 ? io_edifupperstring(io_ediftoken(pp->protoname)) :
				io_ediflowerstring(io_ediftoken(pp->protoname))), portdirection, material,
					xpos, ypos, xsize, ysize, accessdirection);
		connections++;

		if (*pt == 0) break;
		i = j = 0;
	}
	return(connections);
}

/*
 * sometimes all the characters have to be lower case
 */
char *io_ediflowerstring(char *str)
{
	char *pt;
	static char newstr[1000];
	INTSML i;

	for (pt = str, i = 0; *pt != 0; i++)
		newstr[i] = (isupper(*pt) ? tolower(*pt++) : *pt++);
	newstr[i] = '\0';
	return(newstr);
}

/*
 * sometimes they all have to be upper case
 */
char *io_edifupperstring(char *str)
{
	char *pt;
	static char newstr[1000];
	INTSML i;

	for (pt = str, i = 0; *pt != 0; i++)
		newstr[i] = (islower(*pt) ? toupper(*pt++) : *pt++);
	newstr[i] = '\0';
	return(newstr);
}

/* module: io_edifsymbol
 * function: will output all graphic objects of a symbol (extracted from
 * us_drawfacet).
 */
void io_edifsymbol(NODEPROTO *np)
{
	XARRAY trans;
	NODEINST *ni;
	ARCINST *ai;
	PORTPROTO *pp;
	static POLYGON *poly = NOPOLYGON;

	/* get polygon */
	if (poly == NOPOLYGON) poly = allocstaticpolygon(4, io_aid->cluster);

	EO_open_block(edif_stream, "symbol");
	egraphic_override = EGWIRE;
	egraphic = EGUNKNOWN;
	for (pp = np->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
	{
		EO_open_block(edif_stream, "portImplementation");
		EO_put_identifier(edif_stream, io_ediftoken(pp->protoname));
		EO_open_block(edif_stream, "connectLocation");
		shapeportpoly(pp->subnodeinst, pp->subportproto, poly, 0);
		io_edifsymbol_showpoly(poly);

		/* close figure */
		io_edifsetgraphic(EGUNKNOWN);
		EO_close_block(edif_stream, "portImplementation");
	}
	egraphic_override = EGUNKNOWN;

	/* create the identity transform for this window */
	transid(trans);

	for (ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
	{
		io_edifsymbol_facet(ni, trans);
	}
	for (ai = np->firstarcinst; ai != NOARCINST; ai = ai->nextarcinst)
	{
		io_edifsymbol_arcinst(ai, trans);
	}

	/* close figure */
	io_edifsetgraphic(EGUNKNOWN);
	EO_close_block(edif_stream, "symbol");
}

/* module: io_edifsymbol_facet
 * function: will output a specific symbol facet
 */
void io_edifsymbol_facet(NODEINST *ni, XARRAY prevtrans)
{
	XARRAY localtran, trans;

	/* make transformation matrix within the current nodeinst */
	if (ni->rotation == 0 && ni->transpose == 0)
	{
		io_edifsymbol_nodeinst(ni, prevtrans);
	} else
	{
		makerot(ni, localtran);
		transmult(localtran, prevtrans, trans);
		io_edifsymbol_nodeinst(ni, trans);
	}
}

/*
 * routine to symbol "ni" when transformed through "prevtrans".
 */
void io_edifsymbol_nodeinst(NODEINST *ni, XARRAY prevtrans)
{
	INTBIG i, j, displaytotal, low, high, istext;
	char *name;
	XARRAY localtran, subrot, trans;
	INTBIG bx, by, ux, uy, swap;
	static POLYGON *poly = NOPOLYGON;
	GRAPHICS *gra;
	NODEPROTO *np;
	PORTPROTO *pp;
	PORTEXPINST *pe;
	NODEINST *ino;
	ARCINST *iar;
	VARIABLE *var;

	/* get polygon */
	if (poly == NOPOLYGON) poly = allocstaticpolygon(4, io_aid->cluster);

	np = ni->proto;

	/* get outline of nodeinst in the window */
	xform(ni->lowx, ni->lowy, &bx, &by, prevtrans);
	xform(ni->highx, ni->highy, &ux, &uy, prevtrans);
	if (bx > ux)
	{
		swap = bx;
		bx = ux;
		ux = swap;
	}
	if (by > uy)
	{
		swap = by;
		by = uy;
		uy = swap;
	}

	/* write port names if appropriate */
	if (ni->firstportexpinst != NOPORTEXPINST)
	{
		for (pe = ni->firstportexpinst; pe != NOPORTEXPINST; pe = pe->nextportexpinst)
		{
			/* us_writeprotoname(pe->exportproto, on, prevtrans, LAYERO,
			el_colfacettxt&on, w, 0, 0, 0, 0); */
		}
	}

	/* primitive nodeinst: ask the technology how to draw it */
	if (np->primindex != 0)
	{
		high = nodepolys(ni, 0);

		/* don't draw invisible pins */
		if (np == gen_invispinprim) low = 1; else
			low = 0;

		for (j = low; j < high; j++)
		{
			/* get description of this layer */
			shapenodepoly(ni, j, poly);

			/* ignore if this layer is not being displayed */
			gra = poly->desc;
			if ((gra->colstyle & INVISIBLE) != 0) continue;

			/* draw the nodeinst */
			xformpoly(poly, prevtrans);

			/* draw the nodeinst and restore the color */
			/* check for text ... */
			if (poly->style >= TEXTCENT && poly->style <= TEXTBOX)
			{
				istext = 1;
				/* close the current figure ... */
				io_edifsetgraphic(EGUNKNOWN);
				EO_open_block(edif_stream, "annotate");
			} else istext = 0;

			(void)io_edifsymbol_showpoly(poly);
			if (istext) EO_close_block(edif_stream, "annotate");
		}
	} else
	{
		/* transform into the nodeinst for display of its guts */
		maketrans(ni, localtran);
		transmult(localtran, prevtrans, subrot);

		/* get facet rectangle */
		maketruerectpoly(ni->lowx, ni->highx, ni->lowy, ni->highy, poly);
		poly->style = CLOSEDRECT;
		xformpoly(poly, prevtrans);

		/* write ports that must always be displayed */
		for (pp = ni->proto->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
		{
			if ((pp->userbits & PORTDRAWN) != 0)
			{
				if (pp->subnodeinst->rotation == 0 && pp->subnodeinst->transpose == 0)
				{
					/* us_writeprotoname(pp, LAYERA, subrot, LAYERO, el_colfacettxt, w,
					portcliplx, portcliphx, portcliply, portcliphy); */
				} else
				{
					makerot(pp->subnodeinst, localtran);
					transmult(localtran, subrot, trans);
					/* us_writeprotoname(pp, LAYERA, trans, LAYERO, el_colfacettxt, w,
					portcliplx, portcliphx, portcliply, portcliphy); */
				}
			}
		}

		/* see if there are displayable variables on the facet */
		if ((displaytotal = tech_displayablenvars(ni)) != 0)
			io_edifsetgraphic(EGUNKNOWN);
		for (i = 0; i < displaytotal; i++)
		{
			var = tech_filldisplayablenvar(ni, poly);
			xformpoly(poly, prevtrans);
			/* check for name */
			if (namesame((name = makename(var->key)), "EDIF_annotate"))
			{
				/* open the property */
				EO_open_block(edif_stream, "property");
				EO_put_identifier(edif_stream, name);
				EO_open_block(edif_stream, "string");
			} else
			{
				EO_open_block(edif_stream, "annotate");
				name = NULL;
			}
			io_edifsymbol_showpoly(poly);
			if (name != NULL) EO_close_block(edif_stream, "property"); else
				EO_close_block(edif_stream, "annotate");
		}

		/* search through facet */
		for (ino = np->firstnodeinst; ino != NONODEINST; ino = ino->nextnodeinst)
		{
			io_edifsymbol_facet(ino, subrot);
		}
		for (iar = np->firstarcinst; iar != NOARCINST; iar = iar->nextarcinst)
		{
			io_edifsymbol_arcinst(iar, subrot);
		}
	}
}

/*
 * routine to draw an arcinst.  Returns indicator of what else needs to
 * be drawn.  Returns negative if display interrupted
 */
void io_edifsymbol_arcinst(ARCINST *ai, XARRAY trans)
{
	INTBIG i, j, displaytotal;
	INTBIG width;
	VARIABLE *var;
	char *name;
	static POLYGON *poly = NOPOLYGON;

	/* ask the technology how to draw the arcinst */
	/* get polygon */
	if (poly == NOPOLYGON) poly = allocstaticpolygon(4, io_aid->cluster);

	i = arcpolys(ai);

	/* get the endpoints of the arcinst */
	for (j = 0; j < i; j++)
	{
		/* generate a polygon, force line for path generation */
		width = ai->width;
		ai->width = 0;
		shapearcpoly(ai, j, poly);
		ai->width = width;

		/* check for text (all arcs should not have text), do variables below */
		if (poly->style >= TEXTCENT && poly->style <= TEXTBOX) break;

		/* draw the arcinst */
		xformpoly(poly, trans);

		/* draw the arcinst and restore the color */
		io_edifsymbol_showpoly(poly);
	}

	/* now get the variables */
	if ((displaytotal = tech_displayableavars(ai)) != 0) io_edifsetgraphic(EGUNKNOWN);
	for (i = 0; i < displaytotal; i++)
	{
		var = tech_filldisplayableavar(ai, poly);
		xformpoly(poly, trans);

		/* check for name */
		if (namesame((name = makename(var->key)), "EDIF_annotate"))
		{
			/* open the property */
			EO_open_block(edif_stream, "property");
			EO_put_identifier(edif_stream, name);
			EO_open_block(edif_stream, "string");
		} else
		{
			EO_open_block(edif_stream, "annotate");
			name = NULL;
		}
		io_edifsymbol_showpoly(poly);
		if (name != NULL) EO_close_block(edif_stream, "property"); else
		EO_close_block(edif_stream, "annotate");
	}
}

void io_edifsetgraphic(_egraphic type)
{
	if (type == EGUNKNOWN)
	{
		/* terminate the figure */
		if (egraphic != EGUNKNOWN) EO_close_block(edif_stream, "figure");
		egraphic = EGUNKNOWN;
	} else if (egraphic_override == EGUNKNOWN)
	{
		/* normal case */
		if (type != egraphic)
		{
			/* new egraphic type */
			if (egraphic != EGUNKNOWN) EO_close_block(edif_stream, "figure");
			egraphic = type;
			EO_open_block(edif_stream, "figure");
			EO_put_identifier(edif_stream, egraphic_text[egraphic]);
		}
	} else if (egraphic != egraphic_override)
	{
		/* override figure */
		if (egraphic != EGUNKNOWN) EO_close_block(edif_stream, "figure");
		egraphic = egraphic_override;
		EO_open_block(edif_stream, "figure");
		EO_put_identifier(edif_stream, egraphic_text[egraphic]);
	}
}

/* module: io_edifsymbol_showpoly
 * function: will write polys into EDIF syntax
 * inputs: poly
 */
INTBIG io_edifsymbol_showpoly(POLYGON *obj)
{
	INTBIG i, lx, ux, ly, uy, six, siy, height;

	/* now draw the polygon */
	switch (obj->style)
	{
		case CIRCLE:
		case DISC:			/* a circle */
			io_edifsetgraphic(EGART);
			i = computedistance(obj->xv[0], obj->yv[0], obj->xv[1], obj->yv[1]);
			EO_open_block(edif_stream, "circle");
			io_edif_pt(obj->xv[0] - i, obj->yv[0]);
			io_edif_pt(obj->xv[0] + i, obj->yv[0]);
			EO_close_block(edif_stream, "circle");
			return(0);

		case CIRCLEARC:
			io_edifsetgraphic(EGART);

			/* arcs at [i] points [1+i] [2+i] clockwise */
			if (obj->count == 0) return(1);
			if ((obj->count % 3) != 0) return(1);
			for (i = 0; i < obj->count; i += 3)
			{
				EO_open_block(edif_stream, "openShape");
				EO_open_block(edif_stream, "curve");
				EO_open_block(edif_stream, "arc");
				io_edif_pt(obj->xv[i + 1], obj->yv[i + 1]);

				/* calculate a point between the first and second point */
				io_compute_center(obj->xv[i], obj->yv[i],
					obj->xv[i + 1], obj->yv[i + 1], obj->xv[i + 2], obj->yv[i + 2], &six, &siy);
				io_edif_pt(six, siy);
				io_edif_pt(obj->xv[i + 2], obj->yv[i + 2]);
				EO_close_block(edif_stream, "openShape");
			}
			return(0);

		case FILLED:			/* filled polygon */
		case FILLEDRECT:		/* filled rectangle */
		case CLOSED:			/* closed polygon outline */
		case CLOSEDRECT:		/* closed rectangle outline */
			if (isbox(obj, &lx, &ux, &ly, &uy))
			{
				/* simple rectangular box */
				if (lx == ux && ly == uy)
				{
					if (egraphic_override == EGUNKNOWN) return(0);
					io_edifsetgraphic(EGART);
					EO_open_block(edif_stream, "dot");
					io_edif_pt(lx, ly);
					EO_close_block(edif_stream, "dot");
				} else
				{
					io_edifsetgraphic(EGART);
					EO_open_block(edif_stream, "rectangle");
					io_edif_pt(lx, ly);
					io_edif_pt(ux, uy);
					EO_close_block(edif_stream, "rectangle");
				}
			} else
			{
				io_edifsetgraphic(EGART);
				EO_open_block(edif_stream, "path");
				EO_open_block(edif_stream, "pointList");
				for (i = 0; i < obj->count; i++)
					io_edif_pt(obj->xv[i], obj->yv[i]);
				if (obj->count > 2) io_edif_pt(obj->xv[0], obj->yv[0]);
				EO_close_block(edif_stream, "path");
			}
			return(0);

		case TEXTCENT:		/* text centered in box */
		case TEXTTOP:		/* text below top of box */
		case TEXTBOT:		/* text above bottom of box */
		case TEXTLEFT:		/* text right of left edge of box */
		case TEXTRIGHT:		/* text left of right edge of box */
		case TEXTTOPLEFT:		/* text to lower-right of upper-left corner */
		case TEXTBOTLEFT:		/* text to upper-right of lower-left corner */
		case TEXTTOPRIGHT:		/* text to lower-left of upper-right corner */
		case TEXTBOTRIGHT:		/* text to upper-left of lower-right corner */
			getbbox(obj, &lx, &ux, &ly, &uy);
			io_edifsetgraphic(EGUNKNOWN);
			EO_open_block(edif_stream, "stringDisplay");
			EO_put_string(edif_stream, obj->string);
			EO_open_block(edif_stream, "display");
			if (obj->font >= TXT4P && obj->font <= TXT20P)
			{
				EO_open_block(edif_stream, "figureGroupOverride");
				EO_put_identifier(edif_stream, egraphic_text[EGART]);

				/* output the text height */
				EO_open_block(edif_stream, "textHeight");

				/* 2 pixels = 0.0278 in or 36 double pixels per inch */
				height = muldiv((el_curlib->lambda[el_curtech->techindex] * 10), (obj->font + 2), 36);
				EO_put_integer(edif_stream, io_edif_scale(height));
				EO_close_block(edif_stream, "figureGroupOverride");
			} else EO_put_identifier(edif_stream, egraphic_text[EGART]);
			switch (obj->style)
			{
				case TEXTCENT:
					EO_put_block(edif_stream, "justify", "CENTERCENTER");
					break;
				case TEXTTOP:		/* text below top of box */
					EO_put_block(edif_stream, "justify", "LOWERCENTER");
					break;
				case TEXTBOT:		/* text above bottom of box */
					EO_put_block(edif_stream, "justify", "UPPERCENTER");
					break;
				case TEXTLEFT:		/* text right of left edge of box */
					EO_put_block(edif_stream, "justify", "CENTERRIGHT");
					break;
				case TEXTRIGHT:		/* text left of right edge of box */
					EO_put_block(edif_stream, "justify", "CENTERLEFT");
					break;
				case TEXTTOPLEFT:		/* text to lower-right of upper-left corner */
					EO_put_block(edif_stream, "justify", "LOWERRIGHT");
					break;
				case TEXTBOTLEFT:		/* text to upper-right of lower-left corner */
					EO_put_block(edif_stream, "justify", "UPPERRIGHT");
					break;
				case TEXTTOPRIGHT:		/* text to lower-left of upper-right corner */
					EO_put_block(edif_stream, "justify", "LOWERLEFT");
					break;
				case TEXTBOTRIGHT:		/* text to upper-left of lower-right corner */
					EO_put_block(edif_stream, "justify", "UPPERLEFT");
					break;
			}
			EO_put_block(edif_stream, "orientation", "R0");
			EO_open_block(edif_stream, "origin");
			io_edif_pt(lx, ly);
			EO_close_block(edif_stream, "stringDisplay");
			return(0);

		case TEXTBOX:		/* text centered and contained in box */
			getbbox(obj, &lx, &ux, &ly, &uy);
			return(0);

		case OPENED:		/* opened polygon outline */
		case OPENEDT1:		/* opened polygon outline, texture 1 */
		case OPENEDT2:		/* opened polygon outline, texture 2 */
		case OPENEDT3:		/* opened polygon outline, texture 3 */
		case OPENEDO1:		/* extended opened polygon outline */
			/* check for closed 4 sided figure */
			if (obj->count == 5 && obj->xv[4] == obj->xv[0] && obj->yv[4] == obj->yv[0])
			{
				obj->count = 4;
				i = obj->style;
				obj->style = CLOSED;
				if (isbox(obj, &lx, &ux, &ly, &uy))
				{
					/* simple rectangular box */
					if (lx == ux && ly == uy)
					{
						if (egraphic_override == EGUNKNOWN) return(0);
						io_edifsetgraphic(EGART);
						EO_open_block(edif_stream, "dot");
						io_edif_pt(lx, ly);
						EO_close_block(edif_stream, "dot");
					} else
					{
						io_edifsetgraphic(EGART);
						EO_open_block(edif_stream, "rectangle");
						io_edif_pt(lx, ly);
						io_edif_pt(ux, uy);
						EO_close_block(edif_stream, "rectangle");
					}
					obj->count = 5;
					obj->style = (INTSML)i;
					return(0);
				}
				obj->count = 5;
				obj->style = (INTSML)i;
			}
			io_edifsetgraphic(EGART);
			EO_open_block(edif_stream, "path");
			EO_open_block(edif_stream, "pointList");
			for (i = 0; i < obj->count; i++)
				io_edif_pt(obj->xv[i], obj->yv[i]);
			EO_close_block(edif_stream, "path");
			return(0);

		case VECTORS:
			io_edifsetgraphic(EGART);
			for (i = 0; i < obj->count; i += 2)
			{
				EO_open_block(edif_stream, "path");
				EO_open_block(edif_stream, "pointList");
				io_edif_pt(obj->xv[i], obj->yv[i]);
				io_edif_pt(obj->xv[i + 1], obj->yv[i + 1]);
				EO_close_block(edif_stream, "path");
			}
			return(0);

		/* unsupported operators */
		case CROSS:			/* crosses (always have one point) */
			getcenter(obj, &six, &siy);
			return(0);
	}

	/* unknown polygon type */
	return(1);
}

/******** OUTPUT SUPPORT ********/

/* module: EO_open_stream
   function:  Will create a stream block for a new edif file, this stream
   block is used for all future references.
   inputs:
   filename - the name of the file to open
   compress - the compress file or pretty-print
   returns:  The new stream block
 */
EO_STREAM_PTR EO_open_stream(char *filename, INTBIG compress)
{
	EO_STREAM_PTR stream;
	char *truename;

	/* get a new stream */
	if ((stream = EO_alloc_stream()) == NOEO_STREAM) return(NOEO_STREAM);

	/* open the file */
	if ((stream->file = xcreate(filename, io_filetypeedif, _("EDIF File"), &truename)) == NULL)
	{
		if (truename != 0)
		{
			ttyputerr(_("edifout: could not create stream <%s>"), truename);
			ttyputerr(_("Cannot write %s"), truename);
		}
		(void)EO_free_stream(stream);
		return(NOEO_STREAM);
	}

	/* update filename - allocate name and initialize the structure */
	if (allocstring(&stream->filename, truename, io_aid->cluster) != 0)
	{
		ttyputnomemory();
		return(NOEO_STREAM);
	}

	stream->state = EO_OPENED;
	stream->compress = compress;
	return(stream);
}

/* module:  EO_close_stream
   function:  Will close a stream file, and terminate all currently open
   blocks.
   returns: 0 on success, 1 on error
 */
INTBIG EO_close_stream(EO_STREAM_PTR stream)
{
	if (stream != NOEO_STREAM)
	{
		if (stream->blkstack_ptr)
		{
			if (EO_close_block(stream, stream->blkstack[0]))
			ttyputerr(_("edifout: internal error, closing stream <%s>"), stream->filename);
		}
		if (stream->state == EO_OPENED)
		{
			xprintf(stream->file, "\n");
			xclose(stream->file);
		}
		(void)EO_free_stream(stream);
		return(0);
	}
	return(1);
}

/* module:  EO_open_block
   function:  Will open a new keyword block, will indent the new block
   depending on depth of the keyword
   returns: 0 on success, 1 on error
 */
INTBIG EO_open_block(EO_STREAM_PTR stream, char *keyword)
{
	if (stream != NOEO_STREAM && keyword != NULL)
	{
		/* output the new block */
		if (stream->blkstack_ptr)
		{
			xprintf(stream->file, "\n");
		}
		if (allocstring(&stream->blkstack[stream->blkstack_ptr++], keyword,
			io_aid->cluster) != 0)
		{
			ttyputnomemory();
			return(1);
		}

		/* output the keyword */
		xprintf(stream->file, "%s(%s%s",
			GETBLANKS(stream->compress, stream->blkstack_ptr-1), DOSPACE, keyword);
		return(0);
	}
	return(1);
}

/* module: EO_put_block
   function:  Will output a one identifier block
   returns: 0 on success, 1 on error
 */
INTBIG EO_put_block(EO_STREAM_PTR stream, char *keyword, char *identifier)
{
	if (stream != NOEO_STREAM && keyword != NULL)
	{
		/* output the new block */
		if (stream->blkstack_ptr)
		{
			(void)fprintf(stream->file, "\n");
		}

		/* output the keyword */
		xprintf(stream->file, "%s(%s%s %s%s)",
			GETBLANKS(stream->compress, stream->blkstack_ptr), DOSPACE, keyword,
				identifier, DOSPACE);
		return(0);
	}
	return(1);
}

/* module: EO_put_comment
   function:  Will output a quoted comment
   returns: 0 on success, 1 on error
 */
INTBIG EO_put_comment(EO_STREAM_PTR stream, char *comment)
{
	if (stream != NOEO_STREAM)
	{
		/* output the new block */
		if (stream->blkstack_ptr)
		{
			xprintf(stream->file, "\n");
		}

		/* output the comment */
		xprintf(stream->file, "%s(%scomment %s%s)",
			GETBLANKS(stream->compress, stream->blkstack_ptr),
				DOSPACE, EO_make_string(comment), DOSPACE);
		return(0);
	}
	return(1);
}

/* module: EO_put_identifier
   function:  Will output a string identifier to the stream file
   returns: 0 on success, 1 on error
 */
INTBIG EO_put_identifier(EO_STREAM_PTR stream, char *str)
{
	if (stream != NOEO_STREAM && str != NULL)
	{
		xprintf(stream->file, " %s", str);
		return(0);
	}
	return(1);
}

/* module: EO_put_string
   function:  Will output a quoted string to the stream file
   returns: 0 on success, 1 on error
 */
INTBIG EO_put_string(EO_STREAM_PTR stream, char *str)
{
	if (stream != NOEO_STREAM && str != NULL)
	{
		xprintf(stream->file, " \"%s\"", str);
		return(0);
	}
	return(1);
}

/* module: EO_put_integer
   function:  Will output an integer to the stream edif file
   returns: 0 on success, 1 on error
 */
INTBIG EO_put_integer(EO_STREAM_PTR stream, INTBIG val)
{
	if (stream != NOEO_STREAM)
	{
		xprintf(stream->file, " %ld", val);
		return(0);
	}
	return(1);
}

/* module: EO_put_float
   function:  Will output an integer to the stream edif file
   returns: 0 on success, 1 on error
 */
INTBIG EO_put_float(EO_STREAM_PTR stream, double val)
{
	if (stream != NOEO_STREAM)
	{
		xprintf(stream->file, "%s(%se %s%s)", DOSPACE, DOSPACE, EO_get_exp(val), DOSPACE);
		return(0);
	}
	return(1);
}

/* module: EO_put_date
   function:  Will output the current date to the stream edif file
   returns: 0 on success, 1 on error
 */
INTBIG EO_put_date(EO_STREAM_PTR stream)
{
	if (stream != NOEO_STREAM)
	{
		xprintf(stream->file, " %s", EO_get_timestamp((char *)0));
		return(0);
	}
	return(1);
}

INTBIG EO_put_header(EO_STREAM_PTR stream, char *program, char *comment, char *origin)
{
	char name[WORD+1];
	NODEPROTO *np;

	/* output the standard EDIF 2 0 0 header */
	if (EO_open_block(stream, "edif")) return(1);
	np = el_curlib->curnodeproto;
	(void)sprintf(name, "%s", np->cell->cellname);
	if (EO_put_identifier(stream, name)) return(1);
	if (EO_put_block(stream, "edifVersion", "2 0 0")) return(1);
	if (EO_put_block(stream, "edifLevel", "0")) return(1);
	if (EO_open_block(stream, "keywordMap")) return(1);
	if (EO_put_block(stream, "keywordLevel", "0")) return(1);		/* was "1" */
	if (EO_close_block(stream, "keywordMap")) return(1);
	if (EO_open_block(stream, "status")) return(1);
	if (EO_open_block(stream, "written")) return(1);
	if (EO_put_block(stream, "timeStamp", EO_get_timestamp((char *)0))) return(1);
	if (program != NULL && EO_put_block(stream, "program", EO_make_string(program))) return(1);
	if (comment != NULL && EO_put_block(stream, "comment", EO_make_string(comment))) return(1);
	if (origin != NULL && EO_put_block(stream, "dataOrigin", EO_make_string(origin))) return(1);
	if (EO_close_block(stream, "status")) return(1);
	return(0);
}

INTBIG EO_close_block(EO_STREAM_PTR stream, char *keyword)
{
	INTBIG depth;

	if (stream != NOEO_STREAM)
	{
		if (stream->blkstack_ptr == 0) return(0);
		if (keyword == NULL)
		{
			depth = 1;
		} else
		{
			/* scan for this saved keyword */
			for (depth = 1; depth <= stream->blkstack_ptr; depth++)
			{
				if (!namesame(stream->blkstack[stream->blkstack_ptr - depth ], keyword)) break;
			}
			if (depth > stream->blkstack_ptr)
			{
				ttyputerr(_("edifout: could not match keyword <%s>"), keyword);
				return(1);
			}
		}

		/* now terminate and free keyword list */
		do
		{
			efree(stream->blkstack[--stream->blkstack_ptr]);
			xprintf(stream->file, "%s)", DOSPACE);
		} while (--depth);
		return(0);
	}
	return(1);
}

static EO_STREAM_PTR EO_stream_active = NOEO_STREAM;

EO_STREAM_PTR EO_alloc_stream(void)
{
	EO_STREAM_PTR stream;

	if ((stream = (EO_STREAM_PTR)emalloc(sizeof (EO_STREAM), io_aid->cluster)) == NOEO_STREAM)
	{
		ttyputnomemory();
		return(NOEO_STREAM);
	}

	/* now initialize the structure */
	stream->filename = NULL;
	stream->file = NULL;
	stream->state = EO_CLOSED;
	stream->fpos = 0;
	stream->blkstack_ptr = 0;

	/* add to the active list */
	stream->next = EO_stream_active;
	EO_stream_active = stream;
	return(stream);
}

INTBIG EO_free_stream(EO_STREAM_PTR stream)
{
	EO_STREAM_PTR temp;

	if (stream == NOEO_STREAM)
	{
		ttyputerr(_("edifout: internal error, no stream block"));
		return(1);
	}

	/* remove from the active list */
	if (stream == EO_stream_active) EO_stream_active = stream->next; else
	{
		/* scan for this stream */
		for (temp = EO_stream_active; temp != NOEO_STREAM; temp = temp->next)
		{
			if (temp->next == stream)
			{
				temp->next = stream->next;
				break;
			}
		}
		if (temp == NOEO_STREAM)
		{
			ttyputerr(_("edifout: internal error, can't find stream <%s>"),
				(stream->filename?stream->filename:"noname"));
		}
	}
	if (stream->filename != NULL)
	{
		efree(stream->filename);
		stream->filename = NULL;
	}
	efree((char *)stream);
	return(0);
}

char *EO_get_timestamp(char *buffer)
{
	static char get_timestamp_buf[81];
	UINTBIG t;
	char month[4];
	unsigned short mon, yr, day, hour, min, sec;

	t = getcurrenttime();
	(void)sscanf(timetostring(t), "%*s %s %hd %hd:%hd:%hd %hd", &month[0], &day, &hour,
		&min, &sec, &yr);

	mon = (unsigned short)parsemonth(month);

	/* now make the time string */
	if (buffer)
	{
		(void)sprintf(buffer, "%d %02d %02d %02d %02d %02d", yr, mon, day, hour, min, sec);
		return(buffer);
	} else
	{
		(void)sprintf(get_timestamp_buf, "%d %02d %02d %02d %02d %02d",
			yr, mon, day, hour, min, sec);
		return(get_timestamp_buf);
	}
}

/* module: EO_make_string
   function: Will add quotes to a string
   returns: new string
 */
char *EO_make_string(char *str)
{
	static char newstr[LINE+1];

	(void)sprintf(newstr, "\"%s\"", str);
	return(newstr);
}

/* module: EO_get_exp
   function:  Will expand an floating point number to a integer matissa and
   exponent
   inputs:
   val - the double to convert
   returns a pointer to the integer string
 */
char *EO_get_exp(double val)
{
	static char result[WORD+1];
	char temp[WORD+1], *pp, *rp;
	INTBIG nonzero, cnt, exp;

	/* first generate an expanded value */
	(void)sprintf(temp, "%9e", val);

	/* now parse out the result */
	pp = temp; rp = result;
	while (*pp != '.') *rp++ = *pp++;

	/* now the rest of the matissa */
	nonzero = cnt = 0;
	pp++;
	while (*pp != 'e')
	{
		*rp++ = *pp;
		cnt++;
		if (*pp != '0') nonzero = cnt;
		pp++;
	}

	/* now determine the integer conversion factor */
	rp -= (cnt - nonzero);
	*rp++ = ' ';

	/* now convert the exponent */
	exp = atoi(++pp);
	(void)sprintf(rp, "%d", exp-nonzero);
	return(result);
}

/* EDIF Options */
DIALOGITEM io_edifoptionsdialogitems[] =
{
 /*  1 */ {0, {32,120,56,192}, BUTTON, N_("OK")},
 /*  2 */ {0, {32,16,56,88}, BUTTON, N_("Cancel")},
 /*  3 */ {0, {8,8,24,200}, CHECK, N_("Use Schematic View")}
};
DIALOG io_edifoptionsdialog = {{50,75,115,284}, N_("EDIF Options"), 0, 3, io_edifoptionsdialogitems};

/*
 * special case for the "EDIF Options" dialog
 * Use Schematic View   = 3 (check)
 */
void io_edifoptionsdlog(void)
{
	REGISTER INTBIG itemHit, i, *curstate;
	INTBIG newstate[NUMIOSTATEBITWORDS];

	if (DiaInitDialog(&io_edifoptionsdialog) != 0) return;
	curstate = io_getstatebits();
	for(i=0; i<NUMIOSTATEBITWORDS; i++) newstate[i] = curstate[i];
	if ((curstate[0]&EDIFSCHEMATIC) != 0) DiaSetControl(3, 1);

	/* loop until done */
	for(;;)
	{
		itemHit = DiaNextHit();
		if (itemHit == OK || itemHit == CANCEL) break;
		if (itemHit == 3)
		{
			DiaSetControl(itemHit, 1 - DiaGetControl(itemHit));
			continue;
		}
	}

	if (itemHit != CANCEL)
	{
		if (DiaGetControl(3) != 0) newstate[0] |= EDIFSCHEMATIC; else
			newstate[0] &= ~EDIFSCHEMATIC;
		for(i=0; i<NUMIOSTATEBITWORDS; i++) if (curstate[i] != newstate[i]) break;
		if (i < NUMIOSTATEBITWORDS) io_setstatebits(newstate);
	}
	DiaDoneDialog();
}

#endif  /* IOEDIF - at top */

