CIFIntPhase2.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
by Jim Gasbarro March 12, 1985 12:38:00 pm PST
Ripped off from: File IntPhase2.mesa
Last Edited by: Gasbarro, May 8, 1985 2:54:53 pm PDT
DIRECTORY
Atom, Basics, CD, CDProperties, CDRects, CDX, CDCells, CDDirectory, CDOps, CDPolygons, CDViewer, IntDefs, IntStorageDefs, IntTransDefs, IntUtilityDefs, IO, IODefs, OutputDefs, ParserTypeDefs, PolygonDefs, ReadCif, Real, RealFns, Rope;
CIFIntPhase2: CEDAR PROGRAM
IMPORTS Atom, CD, CDProperties, CDRects, CDX, CDCells, CDDirectory, CDOps, CDPolygons, CDViewer, IntTransDefs, IntStorageDefs, IntUtilityDefs, IO, IODefs, PolygonDefs, ReadCif, Real, RealFns, Rope
EXPORTS IntDefs, OutputDefs =
BEGIN OPEN IntUtilityDefs, IntStorageDefs;
CIFMap: TYPE = RECORD [
size: NAT ← 0,
map: SEQUENCE maxSize: NAT OF RECORD[
cifName: ATOM,
cdLevel: CD.Layer,
compensation: INT]];
cifMap: REF CIFMap;
CIFDestRec: TYPE = RECORD [ -- from BrandyCIFter.mesa, wasn't in an interface...
cifDest: Rope.ROPE,
deltaRadius: INT -- nanometers, + means cif is bigger
];
design: CD.Design ← NIL;
unknownLayers: LIST OF ATOM ← NIL;
nanometersPerCifUnit: INT = 10;
nextCellNumber: INT ← 0;
numSides: CARDINAL ← 12;
ErrorMsgType: TYPE = {Warning, FatalError};
Map: PUBLIC PROCEDURE [layerName: Rope.ROPE] RETURNS [n: CARDINAL] =
BEGIN
cm: REF CIFMap;
cifLayerAtom: ATOM = Atom.MakeAtom[layerName];
cdLevel: CD.Layer;
compensation: INT;
-- first time through, initialize data structures
IF cifMap = NIL THEN
BEGIN
cifMap ← NEW[CIFMap[1]];
cifMap.size ← 1;
BEGIN -- initialize design with "proper" technology based on first char of layer name
c: CHAR ← Rope.Fetch[layerName, 0];
r: Rope.ROPE;
technology: ATOM;
SELECT c FROM
'C => {technology ← $cmos; r ← "Cmos"};
'N => {technology ← $nmos; r ← "Nmos"};
ENDCASE => {IODefs.WriteLine[Rope.Cat["Unknown technology for layer ", layerName]]; Abort[]};
IF CD.FetchTechnology[technology] = NIL THEN
BEGIN
IODefs.WriteLine[Rope.Cat["Error: Chipndale technology ", r, " not loaded"]];
Abort[];
END;
design ← CDOps.CreateDesign[CD.FetchTechnology[technology]];
design.name ← ReadCif.designName;
END;
END;
--if layer is defined in map, return the mapped number
FOR n IN [0..cifMap.size) DO
IF cifMap[n].cifName = cifLayerAtom THEN RETURN;
ENDLOOP;
-- layer not in map, see if there is a corresponding Chipndale Level
FOR l: LIST OF CD.Layer ← design.technology.usedLayers, l.rest WHILE l#NIL DO
r: Rope.ROPE;
property: REF ← CDProperties.GetPropFromLayer[l.first, $CDxCIFName];
DO
WITH property SELECT FROM
layer: Rope.ROPE => {r ← layer; compensation ← 0};
layer: REF CIFDestRec => {r ← layer.cifDest; compensation ← layer.deltaRadius};
layer: LIST OF REF ANY => {property ← layer.first; LOOP}; --if it's a list use the first property
ENDCASE => {NULL}; -- not defined, ignore
EXIT;
ENDLOOP;
IF Atom.MakeAtom[r] = cifLayerAtom THEN {cdLevel ← l.first; EXIT};
REPEAT
FINISHED =>
-- no corresponding Chipndale Level, add to unknownLayers if necessary
FOR u: LIST OF ATOM ← unknownLayers, u.rest WHILE u#NIL DO
IF u.first = cifLayerAtom THEN RETURN[LAST[CARDINAL]];
REPEAT
FINISHED =>
BEGIN
unknownLayers ← CONS[cifLayerAtom, unknownLayers];
IODefs.WriteLine[Rope.Cat["CIF layer ", layerName, " is not registered with ChipNDale, objects on this layer will be deleted"]];
RETURN[LAST[CARDINAL]];
END;
ENDLOOP;
ENDLOOP;
-- layer was not in map, but was known to Chipndale, add to map
cm ← NEW[CIFMap[cifMap.size+1]]; -- make space for a new layer
cm.size ← cifMap.size;
FOR i: NAT IN [0..cifMap.size) DO
cm[i] ← cifMap[i];
ENDLOOP;
cifMap ← cm;
cifMap[cifMap.size] ← [cifName: cifLayerAtom, cdLevel: cdLevel, compensation: compensation/nanometersPerCifUnit];
n ← cifMap.size;
cifMap.size ← cifMap.size+1;
END;
Instantiate: PUBLIC PROCEDURE [] =
BEGIN
ClearBinding: PROCEDURE[sym: STEntry] =
BEGIN
sym.bound ← FALSE;
END;
IncludeObjectsNotDrawn: PROCEDURE[sym: STEntry] =
BEGIN
IF NOT sym.bound THEN InstantiateSymbol[sym: sym, root: FALSE];
END;
CIFtoCDPosition: PROCEDURE[call: Call] RETURNS [position: CD.Position, orientation: CD.Orientation] =
BEGIN
a11, a21, a12, a22, a33: REAL;
IntTransDefs.Push[];
IntTransDefs.ApplyLocal[call.t];
IntTransDefs.Scale[1, ReadCif.cifUnitsPerLambda];
position ← LOOPHOLE[IntTransDefs.TransformPoint[0,0]];
IntTransDefs.Pop[];
a33 ← call.t.a33;
a11 ← call.t.a11/a33;
a21 ← call.t.a21/a33;
a12 ← call.t.a12/a33;
a22 ← call.t.a22/a33;
IF a11 # 0 THEN
BEGIN
SELECT TRUE FROM
a11>0 AND a22>0 => orientation ← 0;
a11>0 AND a22<0 => orientation ← 5;
a11<0 AND a22>0 => orientation ← 1;
a11<0 AND a22<0 => orientation ← 4;
ENDCASE => ERROR;
END ELSE
BEGIN
SELECT TRUE FROM
a21>0 AND a12>0 => orientation ← 3;
a21>0 AND a12<0 => orientation ← 6;
a21<0 AND a12>0 => orientation ← 2;
a21<0 AND a12<0 => orientation ← 7;
ENDCASE => ERROR;
END;
RETURN[position, orientation];
END;
OrthogonalTransform: PROCEDURE[call: Call] RETURNS [BOOL] =
BEGIN -- checks transformation to make sure any rotation is a multiple of 90 deg
IF (call.t.a11 = 0 AND call.t.a22 = 0) OR (call.t.a21 = 0 AND call.t.a12 = 0) THEN RETURN[TRUE] ELSE RETURN[FALSE];
END;
InstantiateSymbol: PROCEDURE[sym: STEntry, root: BOOL] =
BEGIN
ReportError: PROCEDURE[r: Rope.ROPE, msgType: ErrorMsgType] =
BEGIN
IODefs.WriteLine[r];
SELECT msgType FROM
FatalError => curCell.errorInSubCell ← TRUE;
Warning => curCell.warningInSubCell ← TRUE;
ENDCASE => ERROR;
END;
IncludePolygon: PROCEDURE[poly: Polygon] =
BEGIN
pd: PolygonDefs.PolygonDescriptor;
IncludeTrapezoid: PROCEDURE[lowerLeftX, lowerRightX, lowerY, upperLeftX, upperRightX, upperY: REAL] =
BEGIN
IF upperLeftX # lowerLeftX OR upperRightX # lowerRightX THEN
BEGIN
points: LIST OF CD.DesignPosition ← NIL;
ob: CD.ObPtr;
offset: CD.DesignPosition;
IF ~curCell.warningInSubCell THEN ReportError["Non-rectilinear geometry", Warning];
points ← CONS[
[Real.RoundLI[(lowerLeftX+cifMap[poly.layer].compensation)/ReadCif.cifUnitsPerLambda],
Real.RoundLI[(lowerY+cifMap[poly.layer].compensation)/ReadCif.cifUnitsPerLambda]],
points];
points ← CONS[
[Real.RoundLI[(lowerRightX-cifMap[poly.layer].compensation)/ReadCif.cifUnitsPerLambda],
Real.RoundLI[(lowerY+cifMap[poly.layer].compensation)/ReadCif.cifUnitsPerLambda]],
points];
points ← CONS[
[Real.RoundLI[(upperRightX-cifMap[poly.layer].compensation)/ReadCif.cifUnitsPerLambda],
Real.RoundLI[(upperY-cifMap[poly.layer].compensation)/ReadCif.cifUnitsPerLambda]],
points];
points ← CONS[
[Real.RoundLI[(upperLeftX+cifMap[poly.layer].compensation)/ReadCif.cifUnitsPerLambda],
Real.RoundLI[(upperY-cifMap[poly.layer].compensation)/ReadCif.cifUnitsPerLambda]],
points];
[ob, offset] ← CDPolygons.CreatePolygon[points, cifMap[poly.layer].cdLevel];
[] ← CDX.IncludeOb[
design: IF curCell.cdCellObj = NIL THEN design ELSE NIL,
cell: curCell.cdCellObj,
ob: ob,
position: offset,
orientation: 0,
cellCSystem: originCoords,
obCSystem: interrestCoords,
mode: dontPropagate];
END ELSE
[] ← CDX.IncludeOb[
design: IF curCell.cdCellObj = NIL THEN design ELSE NIL,
cell: curCell.cdCellObj,
ob: CDRects.CreateRect[
size: [Real.RoundLI[(upperRightX-upperLeftX-2*cifMap[poly.layer].compensation)/ReadCif.cifUnitsPerLambda], Real.RoundLI[(upperY-lowerY-2*cifMap[poly.layer].compensation)/ReadCif.cifUnitsPerLambda]],
l: cifMap[poly.layer].cdLevel],
position: [Real.RoundLI[(upperLeftX+cifMap[poly.layer].compensation)/ReadCif.cifUnitsPerLambda], Real.RoundLI[(lowerY+cifMap[poly.layer].compensation)/ReadCif.cifUnitsPerLambda]],
orientation: 0,
cellCSystem: originCoords,
obCSystem: interrestCoords,
mode: dontPropagate];
END;
pd ← PolygonDefs.PolyCreate[];
FOR l: LIST OF ParserTypeDefs.Point ← poly.p.first, l.rest WHILE l#NIL DO
PolygonDefs.PolyVertex[polygon: pd, x: l.first.x, y: l.first.y];
ENDLOOP;
PolygonDefs.PolyGenerate[polygon: pd, outputTrapezoid: IncludeTrapezoid];
END;
IncludeWire: PROCEDURE[wire: Wire] =
BEGIN
OnAxis: PROC[p1, p2: ParserTypeDefs.Point] RETURNS[BOOL] =
BEGIN
IF p1.x = p2.x OR p1.y = p2.y THEN RETURN[TRUE] ELSE RETURN[FALSE];
END;
FOR l: LIST OF ParserTypeDefs.Point ← wire.p.first, l.rest WHILE l.rest#NIL DO
IF OnAxis[l.first, l.rest.first] THEN
[] ← CDX.IncludeOb[
design: IF curCell.cdCellObj = NIL THEN design ELSE NIL,
cell: curCell.cdCellObj,
ob: CDRects.CreateRect[
size: IF l.first.x = l.rest.first.x THEN
[(wire.width-2*cifMap[wire.layer].compensation) / ReadCif.cifUnitsPerLambda,
(ABS[l.first.y - l.rest.first.y] - 2*cifMap[wire.layer].compensation) / ReadCif.cifUnitsPerLambda]
ELSE
[(ABS[l.first.x - l.rest.first.x] - 2*cifMap[wire.layer].compensation) / ReadCif.cifUnitsPerLambda,
(wire.width-2*cifMap[wire.layer].compensation) / ReadCif.cifUnitsPerLambda],
l: cifMap[wire.layer].cdLevel],
position: IF l.first.x = l.rest.first.x THEN
[(l.rest.first.x - (wire.width/2) + cifMap[wire.layer].compensation) / ReadCif.cifUnitsPerLambda,
((IF l.first.y < l.rest.first.y THEN l.first.y ELSE l.rest.first.y) + cifMap[wire.layer].compensation) / ReadCif.cifUnitsPerLambda]
ELSE
[((IF l.first.x < l.rest.first.x THEN l.first.x ELSE l.rest.first.x) + cifMap[wire.layer].compensation) / ReadCif.cifUnitsPerLambda,
(l.rest.first.y - (wire.width/2) + cifMap[wire.layer].compensation) / ReadCif.cifUnitsPerLambda],
orientation: 0,
cellCSystem: originCoords,
obCSystem: interrestCoords,
mode: dontPropagate]
ELSE
BEGIN
RealPoint: TYPE = RECORD[x,y: REAL];
vector, unitVector: RealPoint;
h0, h90, h180, h270: RealPoint;
length: REAL;
poly: IntStorageDefs.Polygon ← NEW[IntStorageDefs.PolygonRec];
poly.p ← NEW[ParserTypeDefs.PathRecord];
vector ← [l.rest.first.x-l.first.x, l.rest.first.y-l.first.y];
length ← RealFns.SqRt[vector.x*vector.x + vector.y*vector.y];
unitVector ← [vector.x/length, vector.y/length];
h0 ← [unitVector.x*wire.width/2, unitVector.y*wire.width/2];
h90 ← [-h0.y, h0.x];
h180 ← [-h90.y, h90.x];
h270 ← [-h180.y, h180.x];
poly.p.first ← CONS[[Real.RoundLI[l.first.x+h270.x], Real.RoundLI[l.first.y+h270.y]], poly.p.first];
poly.p.first ← CONS[[Real.RoundLI[l.rest.first.x+h270.x], Real.RoundLI[l.rest.first.y+h270.y]], poly.p.first];
poly.p.first ← CONS[[Real.RoundLI[l.rest.first.x+h90.x], Real.RoundLI[l.rest.first.y+h90.y]], poly.p.first];
poly.p.first ← CONS[[Real.RoundLI[l.first.x+h90.x], Real.RoundLI[l.first.y+h90.y]], poly.p.first];
poly.layer ← wire.layer;
IncludePolygon[poly];
END;
ENDLOOP;
BEGIN -- fix up the endpoints with flashes or boxes
prevSegOnAxis: BOOLEANTRUE;
nextSegOnAxis: BOOLEANFALSE;
flash: IntStorageDefs.Flash ← NEW[IntStorageDefs.FlashRec];
box: IntStorageDefs.Box ← NEW[IntStorageDefs.BoxRec];
flash.layer ← wire.layer;
flash.diameter ← wire.width;
box.layer ← wire.layer;
box.length ← wire.width;
box.width ← wire.width;
box.xRot ← 1;
box.yRot ← 0;
box.bb.left ← 0;
box.bb.bottom ← 0;
box.bb.right ← wire.width;
box.bb.top ← wire.width;
FOR l: LIST OF ParserTypeDefs.Point ← wire.p.first, l.rest WHILE l.rest#NIL DO
nextSegOnAxis ← OnAxis[l.first, l.rest.first];
IF prevSegOnAxis AND nextSegOnAxis THEN
BEGIN
box.center.x ← l.first.x;
box.center.y ← l.first.y;
IncludeBox[box];
END
ELSE
BEGIN
flash.center.x ← l.first.x;
flash.center.y ← l.first.y;
IncludeFlash[flash];
END;
prevSegOnAxis ← nextSegOnAxis;
REPEAT
FINISHED =>
BEGIN
IF prevSegOnAxis THEN
BEGIN
box.center.x ← l.first.x;
box.center.y ← l.first.y;
IncludeBox[box];
END
ELSE
BEGIN
flash.center.x ← l.first.x;
flash.center.y ← l.first.y;
IncludeFlash[flash];
END;
END;
ENDLOOP;
END;
END;
IncludeFlash: PROC [flash: Flash] =
BEGIN
theta: REAL ← 360.0/numSides;
poly: IntStorageDefs.Polygon ← NEW[IntStorageDefs.PolygonRec];
poly.p ← NEW[ParserTypeDefs.PathRecord];
FOR i: NAT IN [0..numSides) DO
poly.p.first ← CONS[[Real.RoundLI[flash.diameter/2.0*RealFns.CosDeg[theta/2 + i*theta] + flash.center.x], Real.RoundLI[flash.diameter/2.0*RealFns.SinDeg[theta/2 + i*theta] + flash.center.y]], poly.p.first];
ENDLOOP;
poly.layer ← flash.layer;
IncludePolygon[poly];
END;
IncludeMBox: PROC [box: MBox] =
BEGIN
[] ← CDX.IncludeOb[
design: IF curCell.cdCellObj = NIL THEN design ELSE NIL,
cell: curCell.cdCellObj,
ob: CDRects.CreateRect[
size: [(box.bb.right-box.bb.left-2*cifMap[box.layer].compensation)/ReadCif.cifUnitsPerLambda, (box.bb.top-box.bb.bottom-2*cifMap[box.layer].compensation)/ReadCif.cifUnitsPerLambda],
l: cifMap[box.layer].cdLevel],
position: [(box.bb.left+cifMap[box.layer].compensation)/ReadCif.cifUnitsPerLambda, (box.bb.bottom+cifMap[box.layer].compensation)/ReadCif.cifUnitsPerLambda],
orientation: 0,
cellCSystem: originCoords,
obCSystem: interrestCoords,
mode: dontPropagate];
END;
IncludeBox: PROC [box: Box] =
BEGIN
IF (box.xRot=0 AND box.yRot#0) OR (box.xRot#0 AND box.yRot=0) THEN
BEGIN
xLength: LONG CARDINALIF box.xRot#0 THEN box.length ELSE box.width;
yWidth: LONG CARDINALIF box.xRot#0 THEN box.width ELSE box.length;
[] ← CDX.IncludeOb[
design: IF curCell.cdCellObj = NIL THEN design ELSE NIL,
cell: curCell.cdCellObj,
ob: CDRects.CreateRect[
size: [(xLength-2*cifMap[box.layer].compensation)/ReadCif.cifUnitsPerLambda, (yWidth-2*cifMap[box.layer].compensation)/ReadCif.cifUnitsPerLambda],
l: cifMap[box.layer].cdLevel],
position: [(box.center.x-xLength/2+cifMap[box.layer].compensation)/ReadCif.cifUnitsPerLambda, (box.center.y-yWidth/2+cifMap[box.layer].compensation)/ReadCif.cifUnitsPerLambda],
orientation: 0,
cellCSystem: originCoords,
obCSystem: interrestCoords,
mode: dontPropagate]
END
ELSE ReportError["Only Manhatten boxes implemented", FatalError];
END;
pending: LIST OF Object ← LIST[sym.guts];
objectCount: INT ← 0;
curCell: Cell ← NEW[CellRec];
Cell: TYPE = REF CellRec;
CellRec: TYPE = RECORD [
cdCellObj: CD.ObPtr,
name: Rope.ROPENIL,
errorInSubCell: BOOLEANFALSE,
warningInSubCell: BOOLEANFALSE];
IF NOT root THEN -- we are including a cell into the directory only, i.e. it is not an application, i.e. it does not appear on the screen
BEGIN
sym.expanded ← TRUE;
curCell.cdCellObj ← CDCells.CreateEmptyCell[];
curCell.name ← NIL;
END;
WHILE pending # NIL DO
WHILE pending.first # NIL DO
WITH pending.first.first SELECT FROM
call: Call =>
BEGIN
SELECT TRUE FROM
call.callee.bound =>
BEGIN
position: CD.Position;
orientation: CD.Orientation;
IF ~OrthogonalTransform[call] THEN ReportError[IO.PutFR["Cell %g called with non-orthogonal cell placement", IO.card[call.callee.symNumber]], FatalError];
[position, orientation] ← CIFtoCDPosition[call];
[] ← CDX.IncludeOb[
design: IF curCell.cdCellObj = NIL THEN design ELSE NIL,
cell: curCell.cdCellObj,
ob: NARROW[call.callee.spare, Cell].cdCellObj, -- Obj REF is hung on spare
position: position,
orientation: orientation,
cellCSystem: originCoords,
obCSystem: originCoords,
mode: dontPropagate
];
curCell.errorInSubCell ← NARROW[call.callee.spare, Cell].errorInSubCell; -- propagate errors and warnings
curCell.warningInSubCell ← NARROW[call.callee.spare, Cell].warningInSubCell;
END;
call.callee.defined AND ~call.callee.expanded =>
BEGIN
call.callee.expanded ← TRUE;
pending.first ← CONS[curCell, pending.first]; -- push the current cell
curCell ← NEW[CellRec];
curCell.cdCellObj ← CDCells.CreateEmptyCell[]; -- get a new one
pending.first ← CONS[call.callee, pending.first]; -- CONS return symbol
pending ← CONS[call.callee.guts, pending]; -- CONS contents of cell
LOOP; -- don't pop the stack
END;
call.callee.expanded =>
ReportError[IO.PutFR["Cell %g calls itself, inner call ignored.", IO.int[call.callee.symNumber]], FatalError];
~call.callee.defined =>
ReportError[IO.PutFR["Cell %g is undefined, call ignored.", IO.int[call.callee.symNumber]], FatalError];
ENDCASE => ERROR;
END;
symbol: STEntry =>
BEGIN
prevCell: Cell ← curCell;
symbol.expanded ← FALSE; -- returning from symbol call
symbol.bound ← TRUE;
symbol.spare ← curCell; -- keep the obj ref on spare
IF curCell.name = NIL THEN curCell.name IO.PutFR["CifCell #%g", IO.int[nextCellNumber ← nextCellNumber + 1]];
IF curCell.errorInSubCell THEN ReportError[Rope.Cat["*** Error in ", curCell.name], FatalError];
IF curCell.warningInSubCell THEN ReportError[Rope.Cat["* Warning in ", curCell.name], Warning];
[] ← CDCells.RepositionCell[curCell.cdCellObj, NIL];
[] ← CDDirectory.Include[
design: design,
object: curCell.cdCellObj,
alternateName: curCell.name];
pending.first ← pending.first.rest; -- remove called symbol
curCell ← NARROW[pending.first.first]; -- restore context
END;
box: Box =>
IF box.layer # LAST[CARDINAL] THEN IncludeBox[box];
box: MBox =>
IF box.layer # LAST[CARDINAL] THEN IncludeMBox[box];
flash: Flash =>
IF flash.layer # LAST[CARDINAL] THEN IncludeFlash[flash];
poly: Polygon =>
IF poly.layer # LAST[CARDINAL] THEN IncludePolygon[poly];
wire: Wire =>
IF wire.layer # LAST[CARDINAL] THEN IncludeWire[wire];
userOb: UserOb =>
ReportError["UserOb not defined", Warning];
userCmd: UserCmd =>
BEGIN
if last object on list (first cif command after a DefineStart) is a UserCmd = 9 then assume that it is the cell name
IF userCmd.command = 9 AND pending.first.rest = NIL THEN
curCell.name ← NARROW[userCmd.data]
ELSE
ReportError[IO.PutFR["UserCmd %g not defined", IO.int[userCmd.command]], Warning];
END;
ENDCASE => ERROR;
pending.first ← pending.first.rest;
objectCount ← objectCount+1;
IF objectCount MOD 1000 = 0 THEN
IODefs.PostIt[IO.PutFR["Objects read: %g", IO.int[objectCount]]];
ENDLOOP;
pending ← pending.rest;
ENDLOOP;
IF NOT root THEN -- we are including a cell into the directory only
BEGIN
sym.expanded ← FALSE;
sym.bound ← TRUE;
sym.spare ← curCell; -- keep the cd name on spare
IF curCell.name = NIL THEN curCell.name IO.PutFR["CifCell #%g", IO.int[nextCellNumber ← nextCellNumber + 1]];
IF curCell.errorInSubCell THEN ReportError[Rope.Cat["*** Error in ", curCell.name], FatalError];
IF curCell.warningInSubCell THEN ReportError[Rope.Cat["* Warning in ", curCell.name], Warning];
[] ← CDCells.RepositionCell[curCell.cdCellObj, NIL];
[] ← CDDirectory.Include[
design: design,
object: curCell.cdCellObj,
alternateName: curCell.name];
END
END; -- InstantiateSymbol
MapSymbols[proc: ClearBinding]; -- clear symbol table
InstantiateSymbol[sym: rootSymbol, root: TRUE]; -- instantiate top level design
MapSymbols[proc: IncludeObjectsNotDrawn]; -- includes cells which are not drawn
[] ← CDViewer.CreateViewer[design: design]
END;
FinishOutput: PUBLIC PROCEDURE[] RETURNS[BOOL] =
BEGIN
cifMap ← NIL;
design ← NIL;
unknownLayers ← NIL;
nextCellNumber ← 0;
RETURN[TRUE];
END;
Abort: PROCEDURE[] =
BEGIN
[] ← FinishOutput[];
ERROR ABORTED;
END;
END.