638 lines
20 KiB
C
638 lines
20 KiB
C
/*
|
|
* ExtCell.c --
|
|
*
|
|
* Circuit extraction.
|
|
* Extract a single cell.
|
|
*
|
|
* *********************************************************************
|
|
* * Copyright (C) 1985, 1990 Regents of the University of California. *
|
|
* * Permission to use, copy, modify, and distribute this *
|
|
* * software and its documentation for any purpose and without *
|
|
* * fee is hereby granted, provided that the above copyright *
|
|
* * notice appear in all copies. The University of California *
|
|
* * makes no representations about the suitability of this *
|
|
* * software for any purpose. It is provided "as is" without *
|
|
* * express or implied warranty. Export of this software outside *
|
|
* * of the United States of America may require an export license. *
|
|
* *********************************************************************
|
|
*/
|
|
|
|
#ifndef lint
|
|
static char rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-8.0/extract/ExtCell.c,v 1.1.1.1 2008/02/03 20:43:50 tim Exp $";
|
|
#endif /* not lint */
|
|
|
|
#include <stdio.h>
|
|
#include <math.h>
|
|
#include <string.h>
|
|
|
|
#include "utils/magic.h"
|
|
#include "utils/geometry.h"
|
|
#include "utils/styles.h"
|
|
#include "tiles/tile.h"
|
|
#include "utils/hash.h"
|
|
#include "database/database.h"
|
|
#include "utils/malloc.h"
|
|
#include "textio/textio.h"
|
|
#include "debug/debug.h"
|
|
#include "extract/extract.h"
|
|
#include "extract/extractInt.h"
|
|
#include "utils/signals.h"
|
|
#include "utils/stack.h"
|
|
#include "utils/utils.h"
|
|
#include "windows/windows.h"
|
|
#include "utils/main.h"
|
|
#include "utils/undo.h"
|
|
|
|
/* --------------------------- Global data ---------------------------- */
|
|
|
|
/*
|
|
* Value normally present in ti_client to indicate tiles that have not
|
|
* been marked with their associated region.
|
|
*/
|
|
ClientData extUnInit = (ClientData) CLIENTDEFAULT;
|
|
|
|
|
|
/* ------------------------ Data local to this file ------------------- */
|
|
|
|
/* Forward declarations */
|
|
int extOutputUsesFunc();
|
|
FILE *extFileOpen();
|
|
|
|
Plane* extCellFile();
|
|
void extHeader();
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* ExtCell --
|
|
*
|
|
* Extract the cell 'def', plus all its interactions with its subcells.
|
|
* Place the result in the file named 'outName'.
|
|
*
|
|
* Results:
|
|
* None.
|
|
*
|
|
* Side effects:
|
|
* Creates the file 'outName'.ext and writes to it.
|
|
* May leave feedback information where errors were encountered.
|
|
* Upon return, extNumErrors contains the number of (likely serious)
|
|
* errors encountered while extracting 'def', and extNumWarnings
|
|
* contains the number of warnings.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
Plane *
|
|
ExtCell(def, outName, doLength)
|
|
CellDef *def; /* Cell being extracted */
|
|
char *outName; /* Name of output file; if NULL, derive from def name */
|
|
bool doLength; /* If TRUE, extract pathlengths from drivers to
|
|
* receivers (the names are stored in ExtLength.c).
|
|
* Should only be TRUE for the root cell in a
|
|
* hierarchy.
|
|
*/
|
|
{
|
|
char *filename;
|
|
FILE *f = NULL;
|
|
Plane *savePlane;
|
|
bool doLocal;
|
|
|
|
doLocal = (ExtOptions & EXT_DOLOCAL) ? TRUE : FALSE;
|
|
|
|
/* Incremental extraction: If the cell is marked for no extraction,
|
|
* then just prepare the substrate plane and return it to the caller.
|
|
*/
|
|
if (def->cd_flags & CDNOEXTRACT)
|
|
return extPrepSubstrate(def);
|
|
|
|
f = extFileOpen(def, outName, "w", doLocal, &filename);
|
|
|
|
TxPrintf("Extracting %s into %s:\n", def->cd_name, filename);
|
|
|
|
if (f == NULL)
|
|
{
|
|
#ifdef MAGIC_WRAPPER
|
|
TxError("Cannot open output file.\n");
|
|
#else
|
|
TxError("Cannot open output file: ");
|
|
perror(filename);
|
|
#endif
|
|
return NULL;
|
|
}
|
|
|
|
extNumErrors = extNumWarnings = 0;
|
|
savePlane = extCellFile(def, f, doLength);
|
|
if (f != NULL) fclose(f);
|
|
|
|
if (extNumErrors > 0 || extNumWarnings > 0)
|
|
{
|
|
TxPrintf("%s:", def->cd_name);
|
|
if (extNumErrors > 0)
|
|
TxPrintf(" %d error%s",
|
|
extNumErrors, extNumErrors != 1 ? "s" : "");
|
|
if (extNumWarnings > 0)
|
|
TxPrintf(" %d warning%s",
|
|
extNumWarnings, extNumWarnings != 1 ? "s" : "");
|
|
TxPrintf("\n");
|
|
}
|
|
return savePlane;
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* extFileOpen --
|
|
*
|
|
* Open the .ext file corresponding to a .mag file.
|
|
* If def->cd_file is non-NULL, the .ext file is just def->cd_file with
|
|
* the trailing .mag replaced by .ext. Otherwise, the .ext file is just
|
|
* def->cd_name followed by .ext.
|
|
*
|
|
* Results:
|
|
* Return a pointer to an open FILE, or NULL if the .ext
|
|
* file could not be opened in the specified mode.
|
|
*
|
|
* Side effects:
|
|
* Opens a file.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
FILE *
|
|
extFileOpen(def, file, mode, doLocal, prealfile)
|
|
CellDef *def; /* Cell whose .ext file is to be written */
|
|
char *file; /* If non-NULL, open 'name'.ext; otherwise,
|
|
* derive filename from 'def' as described
|
|
* above.
|
|
*/
|
|
char *mode; /* Either "r" or "w", the mode in which the .ext
|
|
* file is to be opened.
|
|
*/
|
|
bool doLocal; /* If true, always write to local directory */
|
|
char **prealfile; /* If this is non-NULL, it gets set to point to
|
|
* a string holding the name of the .ext file.
|
|
*/
|
|
{
|
|
char namebuf[512], *name, *endp, *ends;
|
|
int len;
|
|
FILE *rfile, *testf;
|
|
|
|
if (file) name = file;
|
|
else if (doLocal)
|
|
name = def->cd_name; /* No path component, so save locally */
|
|
else if (def->cd_file)
|
|
{
|
|
name = def->cd_file;
|
|
ends = strrchr(def->cd_file, '/');
|
|
if (ends == NULL) ends = def->cd_file;
|
|
if (endp = strrchr(ends + 1, '.'))
|
|
{
|
|
name = namebuf;
|
|
len = endp - def->cd_file;
|
|
if (len > sizeof namebuf - 1) len = sizeof namebuf - 1;
|
|
(void) strncpy(namebuf, def->cd_file, len);
|
|
namebuf[len] = '\0';
|
|
}
|
|
}
|
|
else name = def->cd_name;
|
|
|
|
/* Try once as-is, and if this fails, try stripping any leading */
|
|
/* path information in case cell is in a read-only directory. */
|
|
|
|
if ((rfile = PaOpen(name, mode, ".ext", Path, CellLibPath, prealfile)) != NULL)
|
|
return rfile;
|
|
|
|
if (!strcmp(mode, "r")) return NULL; /* Not even readable */
|
|
|
|
/* Try writing to the cwd IF there is no .mag file by the */
|
|
/* same name in the cwd that would conflict. */
|
|
|
|
name = strrchr(def->cd_name, '/');
|
|
if (name != NULL)
|
|
name++;
|
|
else
|
|
name = def->cd_name;
|
|
|
|
if (def->cd_file)
|
|
{
|
|
ends = strrchr(def->cd_file, '/');
|
|
if (ends != NULL)
|
|
{
|
|
testf = PaOpen(ends + 1, "r", ".mag", ".", ".", NULL);
|
|
if (testf)
|
|
{
|
|
fclose(testf);
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
return (PaOpen(name, mode, ".ext", ".", ".", prealfile));
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* extPrepSubstrate ---
|
|
*
|
|
* Prepare a replacement plane for the plane representing the substrate, as
|
|
* defined in ExtCurStyle->exts_globSubstratePlane. The target CellDef is
|
|
* searched for types that shield (i.e., isolate) a section of the layout
|
|
* from the global substrate. The tile type that represents the substrate
|
|
* is painted into the isolated regions.
|
|
*
|
|
* The purpose of this method is to deal with the common methodology in
|
|
* which the substrate is not represented by any tile type, because no mask
|
|
* is defined for the substrate. Typically, an entire cell such as a digital
|
|
* standard cell may be placed on the default substrate or in a deep nwell
|
|
* region. It is therefore necessary to be able to detect what is underneath
|
|
* a cell on the plane representing the substrate to determine if the area is
|
|
* the default substrate or an isolated region. If an isolated region, it
|
|
* must be painted with a tile type so that the extraction code can tag the
|
|
* tiles with a Region and assign it a node. This code creates the substrate
|
|
* paint in the isolated regions for the duration of the extration, then
|
|
* reverts back to the original plane afterward.
|
|
*
|
|
* Results:
|
|
* Returns a Plane structure that is the original substrate plane from
|
|
* CellDef "def", with isolated substrate regions filled with the
|
|
* substrate tile type. If there are no isolated substrate regions,
|
|
* or if a substrate plane or substrate type is not defined by the
|
|
* technology, then the routine returns NULL.
|
|
*
|
|
* Side effects:
|
|
* All modifications are limited to the returned plane structure.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
Plane *
|
|
extPrepSubstrate(def)
|
|
CellDef *def;
|
|
{
|
|
SearchContext scx;
|
|
CellUse dummy;
|
|
TileType subType;
|
|
TileTypeBitMask subMask, notSubMask;
|
|
Plane *subPlane, *savePlane;
|
|
int pNum;
|
|
|
|
/* Determine if substrate copying is required. */
|
|
|
|
if (ExtCurStyle->exts_globSubstratePlane == -1) return NULL;
|
|
|
|
/* Find a type to use for the substrate, and the mask of all types */
|
|
/* in the same plane as the substrate that are not connected to the */
|
|
/* substrate. If there is not a simple type representing the substrate */
|
|
/* then do not attempt to resolve substrate regions. */
|
|
|
|
if ((subType = ExtCurStyle->exts_globSubstrateDefaultType) == -1) return NULL;
|
|
|
|
TTMaskZero(&subMask);
|
|
TTMaskSetMask(&subMask, &ExtCurStyle->exts_globSubstrateTypes);
|
|
TTMaskCom2(¬SubMask, &subMask);
|
|
TTMaskAndMask(¬SubMask, &DBPlaneTypes[ExtCurStyle->exts_globSubstratePlane]);
|
|
|
|
/* Generate the full flattened substrate into a new plane structure */
|
|
/* (called subPlane). This adds layer geometry for the substrate */
|
|
/* in the typical case where the substrate may be space (implicitly */
|
|
/* defined substrate). */
|
|
|
|
scx.scx_trans = GeoIdentityTransform;
|
|
scx.scx_area = def->cd_bbox;
|
|
scx.scx_use = &dummy;
|
|
dummy.cu_def = def;
|
|
dummy.cu_id = NULL;
|
|
|
|
subPlane = DBCellGenerateSubstrate(&scx, subType, ¬SubMask,
|
|
&ExtCurStyle->exts_globSubstrateShieldTypes, def);
|
|
if (subPlane != NULL)
|
|
{
|
|
pNum = ExtCurStyle->exts_globSubstratePlane;
|
|
savePlane = def->cd_planes[pNum];
|
|
def->cd_planes[pNum] = subPlane;
|
|
return savePlane;
|
|
}
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* extResPrepSubstrate ---
|
|
*
|
|
* This works similarly to extPrepSubstrate above, but is used for the
|
|
* "extresist" command, where the method is to make sure that the whole
|
|
* substrate area of the cell has non-space tiles, so that these can be
|
|
* used to estimate the resistance of the substrate from point to point,
|
|
* and to eliminate isolated substrate regions, since those represent an
|
|
* idealized cutoff of resistance that "extresist" is supposed to be
|
|
* replacing with an accurate resitance network.
|
|
*
|
|
* Results:
|
|
* Returns a Plane structure that is the original substrate plane from
|
|
* CellDef "def", with the entire substrate region filled with the
|
|
* substrate tile type. If a substrate plane or substrate type is not
|
|
* defined by the technology, then the routine returns NULL.
|
|
*
|
|
* Side effects:
|
|
* All modifications are limited to the returned plane structure.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
Plane *
|
|
extResPrepSubstrate(def)
|
|
CellDef *def;
|
|
{
|
|
SearchContext scx;
|
|
CellUse dummy;
|
|
TileType subType;
|
|
TileTypeBitMask subMask, notSubMask;
|
|
Plane *subPlane, *savePlane;
|
|
int pNum;
|
|
|
|
/* Determine if substrate copying is required. */
|
|
|
|
if (ExtCurStyle->exts_globSubstratePlane == -1) return NULL;
|
|
|
|
/* Find a type to use for the substrate, and the mask of all types */
|
|
/* in the same plane as the substrate that are not connected to the */
|
|
/* substrate. If there is not a simple type representing the substrate */
|
|
/* then do not attempt to resolve substrate regions. */
|
|
|
|
if ((subType = ExtCurStyle->exts_globSubstrateDefaultType) == -1) return NULL;
|
|
|
|
TTMaskZero(&subMask);
|
|
TTMaskSetMask(&subMask, &ExtCurStyle->exts_globSubstrateTypes);
|
|
TTMaskCom2(¬SubMask, &subMask);
|
|
TTMaskAndMask(¬SubMask, &DBPlaneTypes[ExtCurStyle->exts_globSubstratePlane]);
|
|
|
|
/* Generate the full flattened substrate into a new plane structure */
|
|
/* (called subPlane). This adds layer geometry for the substrate */
|
|
/* in the typical case where the substrate may be space (implicitly */
|
|
/* defined substrate). */
|
|
|
|
scx.scx_trans = GeoIdentityTransform;
|
|
scx.scx_area = def->cd_bbox;
|
|
scx.scx_use = &dummy;
|
|
dummy.cu_def = def;
|
|
dummy.cu_id = NULL;
|
|
|
|
subPlane = DBCellGenerateSimpleSubstrate(&scx, subType, ¬SubMask, def);
|
|
if (subPlane != NULL)
|
|
{
|
|
pNum = ExtCurStyle->exts_globSubstratePlane;
|
|
savePlane = def->cd_planes[pNum];
|
|
def->cd_planes[pNum] = subPlane;
|
|
return savePlane;
|
|
}
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* ExtRevertSubstrate ---
|
|
*
|
|
* This routine swaps the substrate plane of CellDef "def" with the plane
|
|
* structure provided in the argument "savePlane". It should be called at
|
|
* the end of extraction. "savePlane" should be the pointer to the substrate
|
|
* plane of "def" before it was swapped out for the modified plane created by
|
|
* the routine "extPrepSubstrate", above. The calling routine is responsible
|
|
* for knowing if extPrepSubstrate returned NULL in which case there is
|
|
* nothing to revert.
|
|
*
|
|
* Returns:
|
|
* Nothing.
|
|
*
|
|
* Side effects:
|
|
* The CellDef "def" has its substrate plane swapped out for "savePlane",
|
|
* and the original substrate plane and its contents are freed.
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
|
|
void
|
|
ExtRevertSubstrate(def, savePlane)
|
|
CellDef *def;
|
|
Plane *savePlane;
|
|
{
|
|
int pNum;
|
|
Plane *subPlane;
|
|
|
|
pNum = ExtCurStyle->exts_globSubstratePlane;
|
|
subPlane = def->cd_planes[pNum];
|
|
def->cd_planes[pNum] = savePlane;
|
|
DBFreePaintPlane(subPlane);
|
|
TiFreePlane(subPlane);
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* extCellFile --
|
|
*
|
|
* Internal interface for extracting a single cell.
|
|
* Extracts it to the open FILE 'f'. Doesn't print
|
|
* any messages.
|
|
*
|
|
* Results:
|
|
* None.
|
|
*
|
|
* Side effects:
|
|
* May leave feedback information where errors were encountered.
|
|
* Upon return, extNumErrors has been incremented by the number of
|
|
* fatal errors encountered while extracting 'def', and extNumWarnings
|
|
* by the number of warnings.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
Plane *
|
|
extCellFile(def, f, doLength)
|
|
CellDef *def; /* Def to be extracted */
|
|
FILE *f; /* Output to this file */
|
|
bool doLength; /* TRUE if we should extract driver-receiver path
|
|
* length information for this cell (see ExtCell
|
|
* for more details).
|
|
*/
|
|
{
|
|
NodeRegion *reg;
|
|
Plane *saveSub;
|
|
Label *lab;
|
|
|
|
UndoDisable();
|
|
|
|
/* Prep any isolated substrate areas */
|
|
saveSub = extPrepSubstrate(def);
|
|
|
|
/* Remove any label markers that were made by a previous extraction */
|
|
for (lab = def->cd_labels; lab; lab = lab->lab_next)
|
|
if (lab->lab_port == INFINITY)
|
|
lab->lab_port = 0;
|
|
|
|
/* Output the header: timestamp, technology, calls on cell uses */
|
|
if (!SigInterruptPending) extHeader(def, f);
|
|
|
|
/* Extract the mask information in this cell */
|
|
reg = (NodeRegion *) NULL;
|
|
if (!SigInterruptPending) reg = extBasic(def, f);
|
|
|
|
/* Do hierarchical extraction */
|
|
extParentUse->cu_def = def;
|
|
if (!SigInterruptPending) extSubtree(extParentUse, reg, f);
|
|
if (!SigInterruptPending) extArray(extParentUse, f);
|
|
|
|
/* Clean up from basic extraction */
|
|
if (reg) ExtFreeLabRegions((LabRegion *) reg);
|
|
ExtResetTiles(def, extUnInit);
|
|
|
|
/* Final pass: extract length information if desired */
|
|
if (!SigInterruptPending && doLength && (ExtOptions & EXT_DOLENGTH))
|
|
extLength(extParentUse, f);
|
|
|
|
UndoEnable();
|
|
return saveSub;
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* extHeader --
|
|
*
|
|
* Output header information to the .ext file for a cell.
|
|
* This information consists of:
|
|
*
|
|
* timestamp
|
|
* extractor version number
|
|
* technology
|
|
* scale factors for resistance, capacitance, and lambda
|
|
* calls on all subcells used by this cell (see extOutputUsesFunc)
|
|
*
|
|
* Results:
|
|
* None.
|
|
*
|
|
* Side effects:
|
|
* Writes to (FILE *) 'f'.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
void
|
|
extHeader(def, f)
|
|
CellDef *def; /* Cell being extracted */
|
|
FILE *f; /* Write to this file */
|
|
{
|
|
int n;
|
|
bool propfound;
|
|
char *propvalue;
|
|
|
|
ASSERT(DBTechName != NULL, "extHeader");
|
|
|
|
/* Output a timestamp (should be first) */
|
|
fprintf(f, "timestamp %d\n", def->cd_timestamp);
|
|
|
|
/* Output our version number */
|
|
fprintf(f, "version %s\n", MagicVersion);
|
|
|
|
/* Output the technology */
|
|
fprintf(f, "tech %s\n", DBTechName);
|
|
|
|
/* Output the extract style name */
|
|
fprintf(f, "style %s\n", ExtCurStyle->exts_name);
|
|
|
|
/*
|
|
* Output scaling factors: R C D
|
|
* R = amount to multiply all resistances in the file by
|
|
* C = amount to multiply all capacitances by
|
|
* D = amount to multiply all linear distances by (areas
|
|
* should be multiplied by D**2).
|
|
*/
|
|
fprintf(f, "scale %d %d %g\n",
|
|
ExtCurStyle->exts_resistScale,
|
|
ExtCurStyle->exts_capScale,
|
|
ExtCurStyle->exts_unitsPerLambda);
|
|
|
|
/* Output the sheet resistivity classes */
|
|
fprintf(f, "resistclasses");
|
|
for (n = 0; n < ExtCurStyle->exts_numResistClasses; n++)
|
|
fprintf(f, " %d", ExtCurStyle->exts_resistByResistClass[n]);
|
|
fprintf(f, "\n");
|
|
|
|
/* Output any parameters defined for this cell that */
|
|
/* are to be passed to instances of the cell */
|
|
/* (created by defining property "parameter") */
|
|
|
|
propvalue = (char *)DBPropGet(def, "parameter", &propfound);
|
|
if (propfound)
|
|
{
|
|
// Use device parameter table to store the cell def parameters,
|
|
// but preface name with ":" to avoid any conflict with device
|
|
// names.
|
|
fprintf(f, "parameters :%s %s\n", def->cd_name, propvalue);
|
|
}
|
|
|
|
/* Output all calls on subcells */
|
|
(void) DBCellEnum(def, extOutputUsesFunc, (ClientData) f);
|
|
}
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* extOutputUsesFunc --
|
|
*
|
|
* Filter function, called via DBCellEnum, that outputs all the
|
|
* cell uses contained in the parent's cell tile planes.
|
|
*
|
|
* Results:
|
|
* Always returns 0, for DBCellEnum to keep going.
|
|
*
|
|
* Side effects:
|
|
* Writes a line for each use encountered to 'outf'.
|
|
* The line is of the following form:
|
|
*
|
|
* use defname useid Ta ... Tf
|
|
*
|
|
* where 'defname' is the name of the cell def referenced (cd_name),
|
|
* 'useid' is its use identifier (cu_id), and Ta ... Tf are the six
|
|
* components of the transform from coordinates of this use up to
|
|
* its parent. If the cell is an array, the use id may be followed by:
|
|
*
|
|
* [xlo,xhi,xsep][ylo,yhi,ysep]
|
|
*
|
|
* The indices are xlo through xhi inclusive, or ylo through yhi
|
|
* inclusive. The separation between adjacent elements is xsep
|
|
* or ysep; this is used in computing the transform for a particular
|
|
* array element. If arraying is not present in a given direction,
|
|
* the low and high indices are equal and the separation is ignored.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
int
|
|
extOutputUsesFunc(cu, outf)
|
|
CellUse *cu;
|
|
FILE *outf;
|
|
{
|
|
Transform *t = &cu->cu_transform;
|
|
|
|
if (cu->cu_def->cd_flags & CDDONTUSE) return 0;
|
|
|
|
fprintf(outf, "use %s %s", cu->cu_def->cd_name, cu->cu_id);
|
|
if (cu->cu_xlo != cu->cu_xhi || cu->cu_ylo != cu->cu_yhi)
|
|
{
|
|
fprintf(outf, "[%d:%d:%d]",
|
|
cu->cu_xlo, cu->cu_xhi, cu->cu_xsep);
|
|
fprintf(outf, "[%d:%d:%d]",
|
|
cu->cu_ylo, cu->cu_yhi, cu->cu_ysep);
|
|
}
|
|
|
|
/* Output transform to parent */
|
|
fprintf(outf, " %d %d %d %d %d %d\n",
|
|
t->t_a, t->t_b, t->t_c, t->t_d, t->t_e, t->t_f);
|
|
|
|
return (0);
|
|
}
|