/* -*- tab-width: 4 -*-
*
* Electric(tm) VLSI Design System
*
* File: GDS.java
* Input/output tool: GDS output
* Original C Code written by Sid Penstone, Queens University
* Translated to Java by Steven M. Rubin, Sun Microsystems.
*
* Copyright (c) 2003 Sun Microsystems and 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 3 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.
*/
package com.sun.electric.tool.io.output;
import com.sun.electric.database.geometry.Poly;
import com.sun.electric.database.geometry.PolyBase;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.hierarchy.Export;
import com.sun.electric.database.hierarchy.Nodable;
import com.sun.electric.database.hierarchy.View;
import com.sun.electric.database.prototype.PortOriginal;
import com.sun.electric.database.prototype.PortProto;
import com.sun.electric.database.text.TextUtils;
import com.sun.electric.database.topology.ArcInst;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.database.topology.PortInst;
import com.sun.electric.database.variable.VarContext;
import com.sun.electric.technology.ArcProto;
import com.sun.electric.technology.Layer;
import com.sun.electric.technology.PrimitiveNode;
import com.sun.electric.technology.Technology;
import com.sun.electric.technology.technologies.Generic;
import com.sun.electric.tool.io.GDSLayers;
import com.sun.electric.tool.io.IOTool;
import com.sun.electric.tool.ncc.basic.NccCellAnnotations;
import java.awt.Point;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
/**
* This class writes files in GDS format.
*/
public class GDS extends Geometry
{
private static final int GDSVERSION = 3;
private static final int BYTEMASK = 0xFF;
private static final int DSIZE = 512; /* data block */
private static final int EXPORTPRESENTATION= 0; /* centered (was 8 for bottomleft) */
// GDSII bit assignments in STRANS record
private static final int STRANS_REFLX = 0x8000;
// private static final int STRANS_ABSA = 0x2;
// data type codes
private static final int DTYP_NONE = 0;
// header codes
private static final short HDR_HEADER = 0x0002;
private static final short HDR_BGNLIB = 0x0102;
private static final short HDR_LIBNAME = 0x0206;
private static final short HDR_UNITS = 0x0305;
private static final short HDR_ENDLIB = 0x0400;
private static final short HDR_BGNSTR = 0x0502;
private static final short HDR_STRNAME = 0x0606;
private static final short HDR_ENDSTR = 0x0700;
private static final short HDR_BOUNDARY = 0x0800;
private static final short HDR_PATH = 0x0900;
private static final short HDR_SREF = 0x0A00;
// private static final short HDR_AREF = 0x0B00;
private static final short HDR_TEXT = 0x0C00;
private static final short HDR_LAYER = 0x0D02;
private static final short HDR_DATATYPE = 0x0E02;
private static final short HDR_XY = 0x1003;
private static final short HDR_ENDEL = 0x1100;
private static final short HDR_SNAME = 0x1206;
private static final short HDR_TEXTTYPE = 0x1602;
private static final short HDR_PRESENTATION= 0x1701;
private static final short HDR_STRING = 0x1906;
private static final short HDR_STRANS = 0x1A01;
private static final short HDR_MAG = 0x1B05;
private static final short HDR_ANGLE = 0x1C05;
// private static final short HDR_PROPATTR = 0x2B02;
// private static final short HDR_PROPVALUE = 0x2C06;
// Header byte counts
private static final short HDR_N_BGNLIB = 28;
private static final short HDR_N_UNITS = 20;
private static final short HDR_N_ANGLE = 12;
private static final short HDR_N_MAG = 12;
// Maximum string sizes
private static final int HDR_M_SNAME = 32;
//private static final int HDR_M_STRNAME = 32; // replace by preference IOTool.getGDSCellNameMaxLen
private static final int HDR_M_ASCII = 256;
/** for buffering output data */ private static byte [] dataBufferGDS = new byte[DSIZE];
/** for buffering output data */ private static byte [] emptyBuffer = new byte[DSIZE];
/** Current layer for gds output */ private static GDSLayers currentLayerNumbers;
/** Position of next byte in the buffer */ private static int bufferPosition;
/** Number data buffers output so far */ private static int blockCount;
/** constant for GDS units */ private double scaleFactor;
/** cell naming map */ private Map<Cell,String> cellNames;
/** layer number map */ private Map<Layer,GDSLayers> layerNumbers;
/** separator string for lib + cell concatanated cell names */ public static final String concatStr = ".";
/** Name remapping if NCC annotation */ private Map<String,Set<String>> nameRemapping;
private GDSPreferences localPrefs;
public static class GDSPreferences extends OutputPreferences
{
// GDS Settings
/** write pins at Export locations? */
public boolean writeExportPins = IOTool.isGDSOutWritesExportPins();
/** converts bracket to underscores in export names.*/
public boolean convertBracketsInExports = IOTool.getGDSOutputConvertsBracketsInExports();
boolean convertNCCExportsConnectedByParentPins = IOTool.getGDSConvertNCCExportsConnectedByParentPins();
public boolean collapseVddGndPinNames = IOTool.isGDSColapseVddGndPinNames();
int outDefaultTextLayer = IOTool.getGDSOutDefaultTextLayer();
boolean outMergesBoxes = IOTool.isGDSOutMergesBoxes();
int cellNameLenMax = IOTool.getGDSCellNameLenMax();
boolean outUpperCase = IOTool.isGDSOutUpperCase();
boolean includeText = IOTool.isGDSInIncludesText();
double outputScale = IOTool.getGDSOutputScale();
public GDSPreferences(boolean factory)
{
super(factory);
}
@Override
public Output doOutput(Cell cell, VarContext context, String filePath)
{
if (cell.getView() != View.LAYOUT)
{
System.out.println("Can only write GDS for layout cells");
return null;
}
GDS out = new GDS(this);
if (out.openBinaryOutputStream(filePath)) return null;
BloatVisitor visitor = out.makeBloatVisitor(getMaxHierDepth(cell));
if (out.writeCell(cell, context, visitor)) return null;
if (out.closeBinaryOutputStream()) return null;
System.out.println(filePath + " written");
// warn if library name was changed
String topCellName = cell.getName();
String mangledTopCellName = makeGDSName(topCellName, HDR_M_ASCII, outUpperCase);
if (!topCellName.equals(mangledTopCellName))
out.reportWarning("Warning: library name in this file is " + mangledTopCellName +
" (special characters were changed)");
return out.finishWrite();
}
}
private GDS(GDSPreferences gp)
{
localPrefs = gp;
}
protected void start()
{
initOutput();
outputBeginLibrary(topCell);
}
protected void done()
{
outputHeader(HDR_ENDLIB, 0);
doneWritingOutput();
}
/** Method to write cellGeom */
protected void writeCellGeom(CellGeom cellGeom)
{
// write this cell
Cell cell = cellGeom.cell;
outputBeginStruct(cell);
boolean renamePins = (cell == topCell && localPrefs.convertNCCExportsConnectedByParentPins);
boolean colapseGndVddNames = (cell == topCell && localPrefs.collapseVddGndPinNames);
if (renamePins)
{
// rename pins to allow external LVS programs to virtually connect nets as specified
// by the NCC annotation exportsConnectedByParent
NccCellAnnotations annotations = NccCellAnnotations.getAnnotations(cell);
if (annotations == null)
renamePins = false;
else
nameRemapping = createExportNameMap(annotations, cell);
}
// write all polys by Layer
Set<Layer> layers = cellGeom.polyMap.keySet();
for (Layer layer : layers)
{
// No technology associated, case when art elements are added in layout
// r.getTechnology() == Generic.tech for layer Glyph
if (layer == null || layer.getTechnology() == null || layer.getTechnology() == Generic.tech()) continue;
if (!selectLayer(layer))
{
System.out.println("Skipping " + layer + " in GDS output");
continue;
}
List<Object> polyList = cellGeom.polyMap.get(layer);
for (Object obj : polyList)
{
PolyBase poly = (PolyBase)obj;
Integer firstLayer = currentLayerNumbers.getFirstLayer();
int layerNum = firstLayer.intValue() & 0xFFFF;
int layerType = (firstLayer.intValue() >> 16) & 0xFFFF;
writePoly(poly, layerNum, layerType);
}
}
// write all instances
for (Nodable no : cellGeom.nodables)
{
writeNodable(no);
}
// now write exports
if (localPrefs.outDefaultTextLayer >= 0)
{
for(Iterator<PortProto> it = cell.getPorts(); it.hasNext(); )
{
Export pp = (Export)it.next();
// find the node at the bottom of this export
PortOriginal fp = new PortOriginal(pp.getOriginalPort());
PortInst bottomPort = fp.getBottomPort();
NodeInst bottomNi = bottomPort.getNodeInst();
// find the layer associated with this node
PrimitiveNode pNp = (PrimitiveNode)bottomNi.getProto();
Technology.NodeLayer [] nLay = pNp.getNodeLayers();
Layer layer = nLay[0].getLayer().getNonPseudoLayer();
selectLayer(layer);
// int textLayer = localPrefs.outDefaultTextLayer, textType = 0;
// if (currentLayerNumbers.getTextLayer() != -1)
// {
// textLayer = currentLayerNumbers.getTextLayer() & 0xFFFF;
// textType = (currentLayerNumbers.getTextLayer() >> 16) & 0xFFFF;
// }
int pinLayer = localPrefs.outDefaultTextLayer, pinType = 0;
if (currentLayerNumbers.getPinLayer() != -1)
{
pinLayer = currentLayerNumbers.getPinLayer() & 0xFFFF;
pinType = (currentLayerNumbers.getPinLayer() >> 16) & 0xFFFF;
}
// put out a pin if requested
if (localPrefs.writeExportPins)
{
writeExportOnLayer(pp, pinLayer, pinType, renamePins, colapseGndVddNames);
}
}
}
if (localPrefs.includeText) {
for (Iterator<NodeInst> it = cell.getNodes(); it.hasNext(); ) {
NodeInst ni = it.next();
if (!isTextNode(ni)) continue;
// this is a text annotation
PrimitiveNode pNp = (PrimitiveNode)ni.getProto();
Technology.NodeLayer [] nLay = pNp.getNodeLayers();
Layer layer = nLay[0].getLayer().getNonPseudoLayer();
if (layer == null || layer.getTechnology() == null || layer.getTechnology() == Generic.tech()) continue;
if (!selectLayer(layer))
{
System.out.println("Skipping " + layer + " in GDS output");
continue;
}
Integer firstLayer = currentLayerNumbers.getFirstLayer();
int layerNum = firstLayer.intValue() & 0xFFFF;
int layerType = (firstLayer.intValue() >> 16) & 0xFFFF;
writeTextOnLayer(ni.getName(), layerNum, layerType, ni.getAnchorCenterX(), ni.getAnchorCenterY(),
renamePins, colapseGndVddNames);
}
}
outputHeader(HDR_ENDSTR, 0);
}
private boolean isTextNode(NodeInst ni) {
if (!(ni.getProto() instanceof PrimitiveNode)) return false;
if (ni.getXSize() != 0 || ni.getYSize() != 0) return false;
if (ni.getNameKey().isTempname()) return false;
return true;
}
private Map<String,Set<String>> createExportNameMap(NccCellAnnotations ann, Cell cell)
{
Map<String,Set<String>> nameMap = new HashMap<String,Set<String>>();
for (Iterator<List<NccCellAnnotations.NamePattern>> it2 = ann.getExportsConnected(); it2.hasNext(); )
{
List<NccCellAnnotations.NamePattern> list = it2.next();
// list of all patterns that should be connected
Set<String> connectedExports = new TreeSet<String>(new StringComparator());
for (NccCellAnnotations.NamePattern pat : list)
{
for (Iterator<PortProto> it = cell.getPorts(); it.hasNext(); )
{
Export e = (Export)it.next();
String name = e.getName();
if (pat.matches(name))
{
connectedExports.add(name);
nameMap.put(name, connectedExports);
}
}
for (Iterator<NodeInst> it = cell.getNodes(); it.hasNext(); )
{
NodeInst ni = it.next();
if (!isTextNode(ni)) continue;
String name = ni.getName();
if (pat.matches(name))
{
connectedExports.add(name);
nameMap.put(name, connectedExports);
}
}
}
}
return nameMap;
}
private static class StringComparator implements Comparator<String>
{
/**
* Method to sort Objects by their string name.
*/
public int compare(String s1, String s2)
{
return s1.compareTo(s2);
}
public boolean equals(Object obj)
{
return (this == obj);
}
}
private void writeExportOnLayer(Export pp, int layer, int type, boolean remapNames, boolean colapseGndVddNames)
{
outputHeader(HDR_TEXT, 0);
outputHeader(HDR_LAYER, layer);
outputHeader(HDR_TEXTTYPE, type);
outputHeader(HDR_PRESENTATION, EXPORTPRESENTATION);
// now the orientation
NodeInst ni = pp.getOriginalPort().getNodeInst();
int transValue = 0;
int angle = ni.getAngle();
if (ni.isXMirrored() != ni.isYMirrored()) transValue |= STRANS_REFLX;
if (ni.isYMirrored()) angle = (3600 - angle)%3600;
if (ni.isXMirrored()) angle = (1800 - angle)%3600;
outputHeader(HDR_STRANS, transValue);
// reduce the size of export text by a factor of 2
outputMag(0.5);
outputAngle(angle);
outputShort((short)12);
outputShort(HDR_XY);
Poly portPoly = pp.getPoly();
outputInt(scaleDBUnit(portPoly.getCenterX()));
outputInt(scaleDBUnit(portPoly.getCenterY()));
// now the string
String str = pp.getName();
if (remapNames)
{
Set<String> nameSet = nameRemapping.get(str);
if (nameSet != null)
{
str = nameSet.iterator().next();
str = str + ":" + str;
//System.out.println("Remapping export "+pp.getName()+" to "+str);
}
}
if (localPrefs.convertBracketsInExports)
{
// convert brackets to underscores
str = str.replaceAll("[\\[\\]]", "_");
}
if (colapseGndVddNames)
{
String tmp = str.toLowerCase();
// Detecting string in lower case and later search for "_"
if (tmp.startsWith("vdd_") || tmp.startsWith("gnd_"))
str = str.substring(0, str.indexOf("_"));
}
outputString(str, HDR_STRING);
outputHeader(HDR_ENDEL, 0);
}
private void writeTextOnLayer(String str, int layer, int type, double x, double y, boolean remapNames, boolean colapseGndVddNames)
{
outputHeader(HDR_TEXT, 0);
outputHeader(HDR_LAYER, layer);
outputHeader(HDR_TEXTTYPE, type);
outputHeader(HDR_PRESENTATION, EXPORTPRESENTATION);
// now the orientation
int transValue = 0;
outputHeader(HDR_STRANS, transValue);
// reduce the size of export text by a factor of 2
outputMag(0.5);
outputAngle(0);
outputShort((short)12);
outputShort(HDR_XY);
outputInt(scaleDBUnit(x));
outputInt(scaleDBUnit(y));
if (remapNames)
{
Set<String> nameSet = nameRemapping.get(str);
if (nameSet != null)
{
str = nameSet.iterator().next();
str = str + ":" + str;
//System.out.println("Remapping export "+pp.getName()+" to "+str);
}
}
if (localPrefs.convertBracketsInExports)
{
// convert brackets to underscores
str = str.replaceAll("[\\[\\]]", "_");
}
if (colapseGndVddNames)
{
String tmp = str.toLowerCase();
// Detecting string in lower case and later search for "_"
if (tmp.startsWith("vdd_") || tmp.startsWith("gnd_"))
str = str.substring(0, str.indexOf("_"));
}
outputString(str, HDR_STRING);
outputHeader(HDR_ENDEL, 0);
}
/**
* Method to determine whether or not to merge geometry.
*/
protected boolean mergeGeom(int hierLevelsFromBottom)
{
return localPrefs.outMergesBoxes;
}
/**
* Method to determine whether or not to include the original Geometric with a Poly
*/
protected boolean includeGeometric() { return false; }
private boolean selectLayer(Layer layer)
{
GDSLayers numbers = layerNumbers.get(layer);
if (numbers == null)
{
Technology tech = layer.getTechnology();
for (Iterator<Layer> it = tech.getLayers(); it.hasNext(); )
{
Layer l = it.next();
layerNumbers.put(l, GDSLayers.EMPTY);
}
for (Map.Entry<Layer,String> e: tech.getGDSLayers().entrySet())
{
Layer l = e.getKey();
String gdsLayer = e.getValue();
layerNumbers.put(l, GDSLayers.parseLayerString(gdsLayer));
}
numbers = layerNumbers.get(layer);
}
// might be null because Artwork layers are auto-generated and not in the Technology list
if (numbers == null) numbers = GDSLayers.EMPTY;
currentLayerNumbers = numbers;
// validLayer false if layerName = "" like for pseudo metals
return numbers.getNumLayers() > 0;
}
protected void writePoly(PolyBase poly, int layerNumber, int layerType)
{
// ignore negative layer numbers
if (layerNumber < 0) return;
Point2D [] points = poly.getPoints();
if (poly.getStyle() == Poly.Type.DISC)
{
// Make a square of the size of the diameter
double r = points[0].distance(points[1]);
if (r <= 0) return;
Poly newPoly = new Poly(points[0].getX(), points[0].getY(), r*2, r*2);
outputBoundary(newPoly, layerNumber, layerType);
return;
}
Rectangle2D polyBounds = poly.getBox();
if (polyBounds != null)
{
// rectangular manhattan shape: make sure it has positive area
if (polyBounds.getWidth() == 0 || polyBounds.getHeight() == 0)
return;
outputBoundary(poly, layerNumber, layerType);
return;
}
// non-manhattan or worse .. direct output
if (points.length == 1)
{
reportWarning("WARNING: Single point cannot be written in GDS-II");
return;
}
if (points.length > 200)
{
reportWarning("WARNING: GDS-II Polygons may not have more than 200 points (this has " + points.length + ")");
return;
}
if (points.length == 2) outputPath(poly, layerNumber, layerType); else
outputBoundary(poly, layerNumber, layerType);
}
protected void writeNodable(Nodable no)
{
NodeInst ni = (NodeInst)no; // In layout cell all Nodables are NodeInsts
Cell subCell = (Cell)ni.getProto();
// figure out transformation
int transValue = 0;
int angle = ni.getAngle();
if (ni.isXMirrored() != ni.isYMirrored()) transValue |= STRANS_REFLX;
if (ni.isYMirrored()) angle = (3600 - angle)%3600;
if (ni.isXMirrored()) angle = (1800 - angle)%3600;
// write a call to a cell
outputHeader(HDR_SREF, 0);
String name = cellNames.get(subCell);
outputName(HDR_SNAME, name, HDR_M_SNAME);
outputHeader(HDR_STRANS, transValue);
outputAngle(angle);
outputShort((short)12);
outputShort(HDR_XY);
outputInt(scaleDBUnit(ni.getAnchorCenterX()));
outputInt(scaleDBUnit(ni.getAnchorCenterY()));
outputHeader(HDR_ENDEL, 0);
}
/****************************** VISITOR SUBCLASS ******************************/
private BloatVisitor makeBloatVisitor(int maxDepth)
{
BloatVisitor visitor = new BloatVisitor(this, maxDepth);
return visitor;
}
/**
* Class to override the Geometry visitor and add bloating to all polygons.
* Currently, no bloating is being done.
*/
private class BloatVisitor extends Geometry.Visitor
{
BloatVisitor(Geometry outGeom, int maxHierDepth)
{
super(outGeom, maxHierDepth);
}
public void addNodeInst(NodeInst ni, AffineTransform trans)
{
PrimitiveNode prim = (PrimitiveNode)ni.getProto();
Technology tech = prim.getTechnology();
Poly [] polys = tech.getShapeOfNode(ni);
Layer firstLayer = null;
for (int i=0; i<polys.length; i++)
{
Poly poly = polys[i];
if (poly.isPseudoLayer()) continue;
Layer thisLayer = poly.getLayer();
if (thisLayer != null && firstLayer == null) firstLayer = thisLayer;
if (poly.getStyle().isText())
{
// dump this text field
outputHeader(HDR_TEXT, 0);
if (firstLayer != null) selectLayer(firstLayer);
Integer firstLayerVal = currentLayerNumbers.getFirstLayer();
int layerNum = firstLayerVal.intValue() & 0xFFFF;
int layerType = (firstLayerVal.intValue() >> 16) & 0xFFFF;
outputHeader(HDR_LAYER, layerNum);
outputHeader(HDR_TEXTTYPE, layerType);
outputHeader(HDR_PRESENTATION, EXPORTPRESENTATION);
// figure out transformation
int transValue = 0;
int angle = ni.getAngle();
if (ni.isXMirrored() != ni.isYMirrored()) transValue |= STRANS_REFLX;
if (ni.isYMirrored()) angle = (3600 - angle)%3600;
if (ni.isXMirrored()) angle = (1800 - angle)%3600;
outputHeader(HDR_STRANS, transValue);
outputAngle(angle);
outputShort((short)12);
outputShort(HDR_XY);
Point2D [] points = poly.getPoints();
outputInt(scaleDBUnit(points[0].getX()));
outputInt(scaleDBUnit(points[0].getY()));
// now the string
String str = poly.getString();
outputString(str, HDR_STRING);
outputHeader(HDR_ENDEL, 0);
}
poly.transform(trans);
}
cellGeom.addPolys(polys, ni);
}
public void addArcInst(ArcInst ai)
{
ArcProto ap = ai.getProto();
Technology tech = ap.getTechnology();
Poly [] polys = tech.getShapeOfArc(ai);
cellGeom.addPolys(polys, ai);
}
}
/*************************** GDS OUTPUT ROUTINES ***************************/
/**
* Method to initialize various fields, get some standard values
*/
private void initOutput()
{
blockCount = 0;
bufferPosition = 0;
// all zeroes
for (int i=0; i<DSIZE; i++) emptyBuffer[i] = 0;
Technology tech = topCell.getTechnology();
scaleFactor = tech.getScale() * localPrefs.outputScale;
layerNumbers = new HashMap<Layer,GDSLayers>();
nameRemapping = new HashMap<String,Set<String>>();
// precache the layers in this technology
boolean foundValid = false;
for(Iterator<Layer> it = tech.getLayers(); it.hasNext(); )
{
Layer layer = it.next();
if (selectLayer(layer)) foundValid = true;
}
if (!foundValid)
{
reportWarning("Warning: there are no GDS II layers defined for the " +
tech.getTechName() + " technology");
}
// make a hashmap of all names to use for cells
cellNames = new HashMap<Cell,String>();
buildUniqueNames(topCell, cellNames, localPrefs.cellNameLenMax, localPrefs.outUpperCase);
}
/**
* Recursive method to add all cells in the hierarchy to the hashMap
* with unique names.
* @param cell the cell whose nodes and subnode cells will be given unique names.
* @param cellNames a hashmap, key: cell, value: unique name (String).
*/
public static void buildUniqueNames(Cell cell, Map<Cell,String> cellNames, int maxLen, boolean upperCase)
{
if (!cellNames.containsKey(cell))
cellNames.put(cell, makeUniqueName(cell, cellNames, maxLen, upperCase));
for (Iterator<NodeInst> it = cell.getNodes(); it.hasNext(); )
{
NodeInst ni = it.next();
if (ni.isCellInstance())
{
Cell c = (Cell)ni.getProto();
Cell cproto = c.contentsView();
if (cproto == null) cproto = c;
if (!cellNames.containsKey(cproto))
cellNames.put(cproto, makeUniqueName(cproto, cellNames, maxLen, upperCase));
buildUniqueNames(cproto, cellNames, maxLen, upperCase);
}
}
}
public static String makeUniqueName(Cell cell, Map<Cell,String> cellNames, int maxLen, boolean upperCase)
{
String name = makeGDSName(cell.getName(), maxLen, upperCase);
if (cell.getNewestVersion() != cell)
name += "_" + cell.getVersion();
// see if the name is unique
String baseName = name;
Collection existing = cellNames.values();
// try prepending the library name first
if (existing.contains(name))
{
int liblen = maxLen - (name.length() + concatStr.length()); // space for lib name
if (liblen > 0)
{
String lib = cell.getLibrary().getName();
liblen = (liblen > lib.length()) ? lib.length() : liblen;
String libname = lib.substring(0, liblen) + concatStr + name;
if (!existing.contains(libname))
{
System.out.println("Warning: GDSII out renaming cell "+cell.describe(false)+" to "+libname);
return libname;
}
baseName = libname;
}
}
for(int index = 1; ; index++)
{
if (!existing.contains(name)) break;
name = baseName + "_" + index;
int extra = name.length() - maxLen;
if (extra > 0)
{
name = baseName.substring(0, baseName.length()-extra);
name +="_" + index;
}
}
if (!name.equals(cell.getName()))
System.out.println("Warning: GDSII out renaming cell "+cell.describe(false)+" to "+name);
return name;
}
/**
* function to create proper GDSII names with restricted character set
* from input string str.
* Uses only 'A'-'Z', '_', $, ?, and '0'-'9'
*/
private static String makeGDSName(String str, int maxLen, boolean upperCase)
{
// filter the name string for the GDS output cell
StringBuffer ret = new StringBuffer();
int max = str.length();
if (max > maxLen-3) max = maxLen-3;
for(int k=0; k<max; k++)
{
char ch = str.charAt(k);
if (upperCase) ch = Character.toUpperCase(ch);
if (ch != '$' && !TextUtils.isDigit(ch) && ch != '?' && !Character.isLetter(ch))
ch = '_';
ret.append(ch);
}
return ret.toString();
}
/**
* Get the name map. GDS output may mangle cell names
* because of all cells occupy the same name space (no libraries).
*/
public Map<Cell,String> getCellNames() { return cellNames; }
/**
* Close the file, pad to make the file match the tape format
*/
private void doneWritingOutput()
{
try
{
// Write out the current buffer
if (bufferPosition > 0)
{
// Pack with zeroes
for (int i = bufferPosition; i < DSIZE; i++) dataBufferGDS[i] = 0;
dataOutputStream.write(dataBufferGDS, 0, DSIZE);
blockCount++;
}
// Pad to 2048
while (blockCount%4 != 0)
{
dataOutputStream.write(emptyBuffer, 0, DSIZE);
blockCount++;
}
} catch (IOException e)
{
reportError("End of file reached while finishing GDS");
}
}
/**
* Method to write a library header, get the date information
*/
private void outputBeginLibrary(Cell cell)
{
outputHeader(HDR_HEADER, GDSVERSION);
outputHeader(HDR_BGNLIB, 0);
outputDate(cell.getCreationDate());
outputDate(cell.getRevisionDate());
outputName(HDR_LIBNAME, makeGDSName(cell.getName(), HDR_M_ASCII, localPrefs.outUpperCase), HDR_M_ASCII);
outputShort(HDR_N_UNITS);
outputShort(HDR_UNITS);
/* GDS floating point values - -
* 0x3E418937,0x4BC6A7EF = 0.001
* 0x3944B82F,0xA09B5A53 = 1e-9
* 0x3F28F5C2,0x8F5C28F6 = 0.01
* 0x3A2AF31D,0xC4611874 = 1e-8
*/
// set units
outputDouble(1e-3);
outputDouble(1.0e-9);
}
void outputBeginStruct(Cell cell)
{
outputHeader(HDR_BGNSTR, 0);
outputDate(cell.getCreationDate());
outputDate(cell.getRevisionDate());
String name = cellNames.get(cell);
if (name == null)
{
reportWarning("Warning, sub"+cell+" in hierarchy is not the same view" +
" as top level cell");
name = makeUniqueName(cell, cellNames, localPrefs.cellNameLenMax, localPrefs.outUpperCase);
cellNames.put(cell, name);
}
outputName(HDR_STRNAME, name, localPrefs.cellNameLenMax);
}
/**
* Method to output date information
*/
private void outputDate(Date val)
{
short [] date = new short[6];
Calendar cal = Calendar.getInstance();
cal.setTime(val);
int year = cal.get(Calendar.YEAR) - 1900;
date[0] = (short)year;
date[1] = (short)cal.get(Calendar.MONTH);
date[2] = (short)cal.get(Calendar.DAY_OF_MONTH);
date[3] = (short)cal.get(Calendar.HOUR);
date[4] = (short)cal.get(Calendar.MINUTE);
date[5] = (short)cal.get(Calendar.SECOND);
outputShortArray(date, 6);
}
/**
* Write a simple header, with a fixed length
* Enter with the header as argument, the routine will output
* the count, the header, and the argument (if present) in p1.
*/
private void outputHeader(short header, int p1)
{
int type = header & BYTEMASK;
short count = 4;
if (type != DTYP_NONE)
{
switch (header)
{
case HDR_HEADER:
case HDR_LAYER:
case HDR_DATATYPE:
case HDR_TEXTTYPE:
case HDR_STRANS:
case HDR_PRESENTATION:
count = 6;
break;
case HDR_BGNSTR:
case HDR_BGNLIB:
count = HDR_N_BGNLIB;
break;
case HDR_UNITS:
count = HDR_N_UNITS;
break;
default:
reportError("No entry for header " + header);
return;
}
}
outputShort(count);
outputShort(header);
if (type == DTYP_NONE) return;
if (count == 6) outputShort((short)p1);
if (count == 8) outputInt(p1);
}
/**
* Add a name (STRNAME, LIBNAME, etc.) to the file. The header
* to be used is in header; the string starts at p1
* if there is an odd number of bytes, then output the 0 at
* the end of the string as a pad. The maximum length of string is "max"
*/
private void outputName(short header, String p1, int max)
{
outputString(p1, header, max);
}
/**
* Method to output an angle as part of a STRANS
*/
private void outputAngle(int ang)
{
double gdfloat = ang / 10.0;
outputShort(HDR_N_ANGLE);
outputShort(HDR_ANGLE);
outputDouble(gdfloat);
}
/**
* Method to output a magnification as part of a STRANS
*/
private void outputMag(double scale)
{
outputShort(HDR_N_MAG);
outputShort(HDR_MAG);
outputDouble(scale);
}
private List<Point> reducePolygon(PolyBase poly)
{
List<Point> pts = new ArrayList<Point>();
Point2D [] points = poly.getPoints();
int lastX = scaleDBUnit(points[0].getX());
int lastY = scaleDBUnit(points[0].getY());
int firstX = lastX;
int firstY = lastY;
pts.add(new Point(lastX, lastY));
for(int i=1; i<points.length; i++)
{
int x = scaleDBUnit(points[i].getX());
int y = scaleDBUnit(points[i].getY());
if (x == lastX && y == lastY) continue;
lastX = x;
lastY = y;
pts.add(new Point(lastX, lastY));
}
if (pts.size() > 2)
{
Point endPoint = pts.get(pts.size()-1);
if (firstX == endPoint.x && firstY == endPoint.y)
pts.remove(pts.size()-1);
}
return pts;
}
/**
* Method to output the pairs of XY points to the file
*/
private void outputBoundary(PolyBase poly, int layerNumber, int layerType)
{
// remove redundant points
List<Point> reducedPoints = reducePolygon(poly);
int count = reducedPoints.size();
if (count <= 2) return;
int start = 0;
for(;;)
{
// look for a closed section
int sofar = start+1;
for( ; sofar<count; sofar++)
if (reducedPoints.get(sofar).x == reducedPoints.get(start).x &&
reducedPoints.get(sofar).y == reducedPoints.get(start).y) break;
if (sofar < count) sofar++;
outputHeader(HDR_BOUNDARY, 0);
outputHeader(HDR_LAYER, layerNumber);
outputHeader(HDR_DATATYPE, layerType);
outputShort((short)(8 * (sofar+1) + 4));
outputShort(HDR_XY);
for (int i = start; i <= sofar; i++)
{
int j = i;
if (i == sofar) j = 0;
outputInt(reducedPoints.get(j).x);
outputInt(reducedPoints.get(j).y);
}
outputHeader(HDR_ENDEL, 0);
if (sofar >= count) break;
count -= sofar;
start = sofar;
}
}
private void outputPath(PolyBase poly, int layerNumber, int layerType)
{
// remove redundant points
List<Point> reducedPoints = reducePolygon(poly);
int numPoints = reducedPoints.size();
if (numPoints <= 2) return;
outputHeader(HDR_PATH, 0);
outputHeader(HDR_LAYER, layerNumber);
outputHeader(HDR_DATATYPE, layerType);
int count = 8 * numPoints + 4;
outputShort((short)count);
outputShort(HDR_XY);
for (int i = 0; i < numPoints; i ++)
{
outputInt(reducedPoints.get(i).x);
outputInt(reducedPoints.get(i).y);
}
outputHeader(HDR_ENDEL, 0);
}
/**
* Method to add one byte to the file
*/
private void outputByte(byte val)
{
dataBufferGDS[bufferPosition++] = val;
if (bufferPosition >= DSIZE)
{
try
{
dataOutputStream.write(dataBufferGDS, 0, DSIZE);
} catch (IOException e)
{
reportError("End of file reached while writing GDS");
}
blockCount++;
bufferPosition = 0;
}
}
private int scaleDBUnit(double dbunit)
{
// scale according to technology
double scaled = scaleFactor*dbunit;
// round to nearest nanometer
int unit = (int)Math.round(scaled);
return unit;
}
/*************************** GDS LOW-LEVEL OUTPUT ROUTINES ***************************/
/**
* Method to add a 2-byte integer to the output
*/
private void outputShort(short val)
{
outputByte((byte)((val>>8)&BYTEMASK));
outputByte((byte)(val&BYTEMASK));
}
/**
* Method to add a 4-byte integer to the output
*/
private void outputInt(int val)
{
outputShort((short)(val>>16));
outputShort((short)val);
}
/**
* Method to add an array of 2 byte integers to the output.
* @param ptr the array.
* @param n the array length.
*/
private void outputShortArray(short [] ptr, int n)
{
for (int i = 0; i < n; i++) outputShort(ptr[i]);
}
private void outputString(String str, short header)
{
// The usual maximum length for string is 512, though names etc may need to be shorter
outputString(str, header, 512);
}
/**
* String of n bytes, starting at ptr
* Revised 90-11-23 to convert to upper case (SRP)
*/
private void outputString(String str, short header, int max)
{
int j = str.length();
if (j > max) j = max;
// round up string length to the nearest integer
if ((j % 2) != 0)
{
j = (j / 2)*2 + 2;
}
// pad with a blank
outputShort((short)(4+j));
outputShort(header);
assert( (j%2) == 0);
int i = 0;
if (localPrefs.outUpperCase)
{
// convert to upper case
for( ; i<str.length(); i++)
outputByte((byte)Character.toUpperCase(str.charAt(i)));
} else
{
for( ; i<str.length(); i++)
outputByte((byte)str.charAt(i));
}
for ( ; i < j; i++)
outputByte((byte)0);
}
/**
* Method to write a GDSII representation of a double.
* New conversion code contributed by Tom Valine <tomv@transmeta.com>.
* @param data the double to process.
*/
public void outputDouble(double data)
{
if (data == 0.0)
{
for(int i=0; i<8; i++) outputByte((byte)0);
return;
}
BigDecimal reg = new BigDecimal(data).setScale(64, BigDecimal.ROUND_HALF_EVEN);
boolean negSign = false;
if (reg.doubleValue() < 0)
{
negSign = true;
reg = reg.negate();
}
int exponent = 64;
for(; (reg.doubleValue() < 0.0625) && (exponent > 0); exponent--)
reg = reg.multiply(new BigDecimal(16.0));
if (exponent == 0) System.out.println("Exponent underflow");
for(; (reg.doubleValue() >= 1) && (exponent < 128); exponent++)
reg = reg.divide(new BigDecimal(16.0), BigDecimal.ROUND_HALF_EVEN);
if (exponent > 127) System.out.println("Exponent overflow");
if (negSign) exponent |= 0x00000080;
BigDecimal f_mantissa = reg.subtract(new BigDecimal(reg.intValue()));
for(int i = 0; i < 56; i++)
f_mantissa = f_mantissa.multiply(new BigDecimal(2.0));
long mantissa = f_mantissa.longValue();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(exponent);
for(int i = 6; i >= 0; i--)
baos.write((int)((mantissa >> (i * 8)) & 0xFF));
byte [] result = baos.toByteArray();
for(int i=0; i<8; i++) outputByte(result[i]);
}
// /**
// * Method to write a GDSII representation of a double.
// * Original C-Electric code (no longer used).
// * @param data the double to process.
// */
// private void outputDouble(double a)
// {
// int [] ret = new int[2];
//
// // handle default
// if (a == 0)
// {
// ret[0] = 0x40000000;
// ret[1] = 0;
// outputIntArray(ret, 2);
// return;
// }
//
// // identify sign
// double temp = a;
// boolean negsign = false;
// if (temp < 0)
// {
// negsign = true;
// temp = -temp;
// }
//
// // establish the excess-64 exponent value
// int exponent = 64;
//
// // scale the exponent and mantissa
// for (; temp < 0.0625 && exponent > 0; exponent--) temp *= 16.0;
//
// if (exponent == 0) System.out.println("Exponent underflow");
//
// for (; temp >= 1 && exponent < 128; exponent++) temp /= 16.0;
//
// if (exponent > 127) System.out.println("Exponent overflow");
//
// // set the sign
// if (negsign) exponent |= 0x80;
//
// // convert temp to 7-byte binary integer
// double top = temp;
// for (int i = 0; i < 24; i++) top *= 2;
// int highmantissa = (int)top;
// double frac = top - highmantissa;
// for (int i = 0; i < 32; i++) frac *= 2;
// ret[0] = highmantissa | (exponent<<24);
// ret[1] = (int)frac;
// outputIntArray(ret, 2);
// }
//
// /**
// * Method to add an array of 4 byte integers or floating numbers to the output.
// * @param ptr the array.
// * @param n the array length.
// */
// private void outputIntArray(int [] ptr, int n)
// {
// for (int i = 0; i < n; i++) outputInt(ptr[i]);
// }
}