com.sun.electric.tool.erc.ERCAntenna Maven / Gradle / Ivy
/* -*- tab-width: 4 -*-
*
* Electric(tm) VLSI Design System
*
* File: ERCAntenna.java
*
* Copyright (c) 2004, Oracle and/or its affiliates. All rights reserved.
*
* 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.erc;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.prefs.Preferences;
import com.sun.electric.database.geometry.Poly;
import com.sun.electric.database.geometry.PolyBase;
import com.sun.electric.database.geometry.PolyMerge;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.hierarchy.Export;
import com.sun.electric.database.id.ArcProtoId;
import com.sun.electric.database.prototype.PortProto;
import com.sun.electric.database.text.PrefPackage;
import com.sun.electric.database.topology.ArcInst;
import com.sun.electric.database.topology.Connection;
import com.sun.electric.database.topology.Geometric;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.database.topology.PortInst;
import com.sun.electric.database.variable.UserInterface;
import com.sun.electric.database.variable.VarContext;
import com.sun.electric.technology.ArcProto;
import com.sun.electric.technology.Layer;
import com.sun.electric.technology.TechPool;
import com.sun.electric.technology.Technology;
import com.sun.electric.technology.TransistorSize;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.JobException;
import com.sun.electric.tool.user.ErrorLogger;
import com.sun.electric.util.ElapseTimer;
import com.sun.electric.util.math.DBMath;
import com.sun.electric.util.math.FixpTransform;
/**
* This is the Antenna checker of the Electrical Rule Checker tool.
*
* Antenna rules are required by some IC manufacturers to ensure that the transistors of the
* chip are not destroyed during fabrication. This is because, during fabrication, the wafer is
* bombarded with ions while making the polysilicon and metal layers.
* These ions must find a path to through the wafer (to the substrate and active layers at the bottom).
* If there is a large area of poly or metal, and if it connects ONLY to gates of transistors
* (not to source or drain or any other active material) then these ions will travel through
* the transistors. If the ratio of the poly or metal "sidewall area" to the transistor gate area
* is too large, the transistors will be destroyed. The "sidewall area" is the area of the sides
* of the poly or metal wires, so it is the perimeter times the thickness.
*
* Things to do:
* Have errors show the gates;
* Not all active connections excuse the area trouble...they should be part of the ratio formula
*/
public class ERCAntenna
{
public static class AntennaPreferences extends PrefPackage
{
// In TECH_NODE
private static final String KEY_ANTENNA_RATIO = "DefaultAntennaRatio";
private transient final TechPool techPool;
public Map antennaRatio = new HashMap();
public AntennaPreferences(boolean factory, TechPool techPool)
{
super(factory);
this.techPool = techPool;
if (factory) return;
Preferences techPrefs = getPrefRoot().node(TECH_NODE);
for (Technology tech: techPool.values()) {
for (Iterator it = tech.getArcs(); it.hasNext(); ) {
ArcProto ap = it.next();
ArcProtoId apId = ap.getId();
double factoryValue = ap.getFactoryAntennaRatio();
double value = techPrefs.getDouble(getKey(KEY_ANTENNA_RATIO, apId), factoryValue);
if (value == factoryValue) continue;
antennaRatio.put(apId, Double.valueOf(value));
}
}
}
/**
* Store annotated option fields of the subclass into the speciefied Preferences subtree.
* @param prefRoot the root of the Preferences subtree.
* @param removeDefaults remove from the Preferences subtree options which have factory default value.
*/
@Override
public void putPrefs(Preferences prefRoot, boolean removeDefaults) {
super.putPrefs(prefRoot, removeDefaults);
Preferences techPrefs = prefRoot.node(TECH_NODE);
for (Technology tech: techPool.values()) {
for (Iterator it = tech.getArcs(); it.hasNext(); ) {
ArcProto ap = it.next();
ArcProtoId apId = ap.getId();
String key = getKey(KEY_ANTENNA_RATIO, apId);
double factoryValue = ap.getFactoryAntennaRatio();
Double valueObj = antennaRatio.get(apId);
double value = valueObj != null ? valueObj.doubleValue() : factoryValue;
if (removeDefaults && value == factoryValue)
techPrefs.remove(key);
else
techPrefs.putDouble(key, value);
}
}
}
public double getAntennaRatio(ArcProto ap) {
Double valueObj = antennaRatio.get(ap.getId());
return valueObj != null ? valueObj.doubleValue() : ap.getFactoryAntennaRatio();
}
}
private static class AntennaObject
{
/** the object */ Geometric geom;
/** the depth of hierarchy at this node */ int depth;
/** end of arc to walk along */ int otherend;
/** the hierarchical stack at this node */ NodeInst [] hierstack;
AntennaObject(Geometric geom) { this.geom = geom; }
/**
* Method to load antenna object "ao" with the hierarchical stack in "stack" that is
* "depth" deep.
*/
private void loadAntennaObject(NodeInst [] stack, int depth)
{
hierstack = new NodeInst[depth];
for(int i=0; i firstSpreadAntennaObj;
/** current technology being considered */ private Technology curTech;
/** accumulated gate area */ private double totalGateArea;
/** the worst ratio found */ private double worstRatio;
/** A list of AntennaObjects to process. */ private List pathList;
/** Map from ArcProtos to Layers. */ private Map arcProtoToLayer;
/** Map from Layers to ArcProtos. */ private Map layerToArcProto;
/** Map for marking ArcInsts and NodeInsts. */ private Set fsGeom;
/** Map for marking Cells. */ private Set fsCell;
/** for storing errors */ private ErrorLogger errorLogger;
/** preferences */ private AntennaPreferences antennaPrefs;
/************************ CONTROL ***********************/
private ERCAntenna(AntennaPreferences antennaPrefs) {
this.antennaPrefs = antennaPrefs;
}
/**
* The main entrypoint for Antenna checking.
* Creating an ERCAntenna object checks the current cell.
*/
public static void doAntennaCheck()
{
UserInterface ui = Job.getUserInterface();
Cell cell = ui.needCurrentCell();
if (cell == null) return;
new AntennaCheckJob(cell);
}
/**
* For test/regression/internal run
*/
public static int checkERCAntenna(Cell cell, AntennaPreferences prefs, Job job)
{
ERCAntenna handler = new ERCAntenna(prefs);
return handler.doCheck(job, cell);
}
/**
* Class to do antenna checking in a new thread.
*/
private static class AntennaCheckJob extends Job
{
private AntennaPreferences antennaPrefs = new AntennaPreferences(false, getTechPool());
private Cell cell;
private AntennaCheckJob(Cell cell)
{
super("ERC Antenna Check", ERC.tool, Job.Type.SERVER_EXAMINE, null, null, Job.Priority.USER);
this.cell = cell;
startJob();
}
public boolean doIt() throws JobException
{
// ERCAntenna handler = new ERCAntenna(antennaPrefs);
// handler.doCheck(this, cell);
checkERCAntenna(cell, antennaPrefs, this);
return true;
}
}
/**
* Method to do the Antenna check.
*/
private int doCheck(Job job, Cell topCell)
{
curTech = topCell.getTechnology();
// maps for marking nodes and arcs, and also for marking cells
fsGeom = new HashSet();
fsCell = new HashSet();
// create mappings between ArcProtos and Layers
arcProtoToLayer = new HashMap();
layerToArcProto = new HashMap();
for(Iterator it = curTech.getArcs(); it.hasNext(); )
{
ArcProto ap = it.next();
ArcProto.Function aFun = ap.getFunction();
if (!aFun.isMetal() && aFun != ArcProto.Function.POLY1) continue;
for(Iterator lIt = curTech.getLayers(); lIt.hasNext(); )
{
Layer lay = lIt.next();
Layer.Function lFun = lay.getFunction();
if ((aFun.isMetal() && lFun.isMetal() && aFun.getLevel() == lFun.getLevel()) ||
(aFun.isPoly() && lFun.isPoly() && aFun.getLevel() == lFun.getLevel()))
{
arcProtoToLayer.put(ap, lay);
layerToArcProto.put(lay, ap);
break;
}
}
}
// initialize error logging
ElapseTimer timer = ElapseTimer.createInstance().start();
errorLogger = ErrorLogger.newInstance("ERC Antenna Rules Check");
// now check each layer of the cell
int lasterrorcount = 0;
worstRatio = 0;
for(Layer lay : layerToArcProto.keySet())
{
System.out.println("Checking Antenna rules for " + lay.getName() + "...");
// clear timestamps on all cells
fsCell.clear();
// do the check for this level
if (checkThisCell(topCell, lay, job)) break;
int i = errorLogger.getNumErrors();
if (i != lasterrorcount)
{
System.out.println(" Found " + (i - lasterrorcount) + " errors");
lasterrorcount = i;
}
}
timer.end();
int errorCount = errorLogger.getNumErrors();
if (errorCount == 0)
{
System.out.println("No antenna errors found (took " + timer + ")");
} else
{
System.out.println("FOUND " + errorCount + " ANTENNA ERRORS (took " + timer + ")");
}
errorLogger.termLogging(true);
return errorCount;
}
/**
* Method to check the contents of a cell.
* @param cell the Cell to check.
* @param lay the Layer to check in the Cell.
* @return true if aborted
*/
private boolean checkThisCell(Cell cell, Layer lay, Job job)
{
// examine every node and follow all relevant arcs
fsGeom.clear();
for(Iterator it = cell.getNodes(); it.hasNext(); )
{
if (job != null && job.checkAbort()) return true;
NodeInst ni = it.next();
if (fsGeom.contains(ni)) continue;
fsGeom.add(ni);
// check every connection on the node
for(Iterator pIt = ni.getPortInsts(); pIt.hasNext(); )
{
PortInst pi = pIt.next();
// ignore if an arc on this port is already seen
boolean seen = false;
for(Iterator cIt = pi.getConnections(); cIt.hasNext(); )
{
Connection con = cIt.next();
ArcInst ai = con.getArc();
if (fsGeom.contains(ai)) { seen = true; break; }
}
if (seen) continue;
totalGateArea = 0.0;
pathList = new ArrayList();
int found = followNode(ni, pi.getPortProto(), lay, DBMath.MATID, job);
if (found == ERCABORTED) return true;
if (found == ERCANTPATHGATE)
{
// gather the geometry here
PolyMerge vmerge = null;
for(AntennaObject ao : pathList)
{
if (ao.geom instanceof NodeInst)
{
NodeInst oni = (NodeInst)ao.geom;
FixpTransform trans = oni.rotateOut();
for(int i = ao.depth-1; i >= 0; i--)
{
FixpTransform tTrans = ao.hierstack[i].translateOut();
trans.concatenate(tTrans);
FixpTransform rTrans = ao.hierstack[i].rotateOut();
trans.concatenate(rTrans);
}
Technology tech = oni.getProto().getTechnology();
if (tech != curTech) continue;
Poly [] polyList = tech.getShapeOfNode(oni);
if (polyList == null) continue;
for(int i=0; i= 0; i--)
{
FixpTransform tTrans = ao.hierstack[i].translateOut();
trans.concatenate(tTrans);
FixpTransform rTrans = ao.hierstack[i].rotateOut();
trans.concatenate(rTrans);
}
Technology tech = ai.getProto().getTechnology();
if (tech != curTech) continue;
Poly [] polyList = tech.getShapeOfArc(ai);
for(int i=0; i merges = vmerge.getMergedPoints(oLay, true);
for(PolyBase merged : merges)
{
totalRegionPerimeterArea += merged.getPerimeter() * thickness;
}
}
// see if it is an antenna violation
double ratio = totalRegionPerimeterArea / totalGateArea;
double neededratio = getAntennaRatio(lay);
if (ratio > worstRatio) worstRatio = ratio;
if (ratio >= neededratio)
{
// error
String errMsg = "layer " + lay.getName() + " has perimeter-area " + totalRegionPerimeterArea +
"; gates have area " + totalGateArea + ", ratio is " + ratio + " but limit is " + neededratio;
List polyList = new ArrayList();
for (Layer oLay : vmerge.getKeySet())
{
List merges = vmerge.getMergedPoints(oLay, true);
for(PolyBase merged : merges)
{
polyList.add(merged);
}
}
errorLogger.logMessage(errMsg, polyList, cell, 0, true);
}
}
}
}
}
// now look at subcells
fsCell.add(cell);
for(Iterator it = cell.getNodes(); it.hasNext(); )
{
NodeInst ni = it.next();
if (!ni.isCellInstance()) continue;
Cell subCell = (Cell)ni.getProto();
if (fsCell.contains(subCell)) continue;
if (checkThisCell(subCell, lay, job)) return true;
}
return false;
}
/**
* Method to follow a node around the cell.
* @param ni the NodeInst to follow.
* @param pp the PortProto on the NodeInst.
* @param lay the layer to consider.
* @param trans a transformation to the top-level.
* @param job the Job that is running (for abort checking).
* @return ERCANTPATHNULL if it found no gate or active on the path.
* Returns ERCANTPATHGATE if it found gates on the path.
* Returns ERCANTPATHACTIVE if it found active on the path.
*/
private int followNode(NodeInst ni, PortProto pp, Layer lay, FixpTransform trans, Job job)
{
// presume that nothing was found
int ret = ERCANTPATHNULL;
firstSpreadAntennaObj = new ArrayList();
NodeInst [] antstack = new NodeInst[200];
int depth = 0;
// keep walking along the nodes and arcs
for(;;)
{
if (job != null && job.checkAbort()) return ERCABORTED;
// if this is a subcell, recurse on it
fsGeom.add(ni);
NodeInst thisni = ni;
while (thisni.isCellInstance())
{
antstack[depth] = thisni;
depth++;
thisni = ((Export)pp).getOriginalPort().getNodeInst();
pp = ((Export)pp).getOriginalPort().getPortProto();
}
// see if we hit a transistor
boolean seen = false;
if (thisni.getFunction().isFET())
{
// stop tracing
if (thisni.getTransistorDrainPort().getPortProto() == pp ||
thisni.getTransistorSourcePort().getPortProto() == pp)
{
// touching the diffusion side of the transistor
return ERCANTPATHACTIVE;
}
// touching the gate side of the transistor
TransistorSize dim = thisni.getTransistorSize(VarContext.globalContext);
totalGateArea += dim.getDoubleLength() * dim.getDoubleWidth();
ret = ERCANTPATHGATE;
} else
{
// normal primitive: propagate
if (hasDiffusion(thisni)) return ERCANTPATHACTIVE;
AntennaObject ao = new AntennaObject(ni);
ao.loadAntennaObject(antstack, depth);
if (haveAntennaObject(ao))
{
// already in the list
seen = true;
} else
{
// not in the list: add it
addAntennaObject(ao);
}
}
// look at all arcs on the node
if (!seen)
{
int found = findArcs(thisni, pp, lay, depth, antstack);
if (found == ERCANTPATHACTIVE) return found;
if (depth > 0)
{
found = findExports(thisni, pp, lay, depth, antstack);
if (found == ERCANTPATHACTIVE) return found;
}
}
// look for an unspread antenna object and keep walking
if (firstSpreadAntennaObj.size() == 0) break;
AntennaObject ao = firstSpreadAntennaObj.get(0);
firstSpreadAntennaObj.remove(0);
ArcInst ai = (ArcInst)ao.geom;
ni = ai.getPortInst(ao.otherend).getNodeInst();
pp = ai.getPortInst(ao.otherend).getPortProto();
depth = ao.hierstack.length;
for(int i=0; i it = pi.getConnections(); it.hasNext(); )
{
Connection con = it.next();
ArcInst ai = con.getArc();
// see if it is the desired layer
if (ai.getProto().getFunction().isDiffusion()) return ERCANTPATHACTIVE;
Layer aLayer = arcProtoToLayer.get(ai.getProto());
if (aLayer == null) continue;
if (ai.getProto().getFunction().isMetal() != aLayer.getFunction().isMetal()) continue;
if (ai.getProto().getFunction().isPoly() != aLayer.getFunction().isPoly()) continue;
if (ai.getProto().getFunction().getLevel() > aLayer.getFunction().getLevel()) continue;
// make an antenna object for this arc
fsGeom.add(ai);
AntennaObject ao = new AntennaObject(ai);
if (haveAntennaObject(ao)) continue;
ao.loadAntennaObject(antstack, depth);
int other = 1 - con.getEndIndex();
ao.otherend = other;
addAntennaObject(ao);
// add to the list of "unspread" antenna objects
firstSpreadAntennaObj.add(ao);
}
return ERCANTPATHNULL;
}
private int findExports(NodeInst ni, PortProto pp, Layer lay, int depth, NodeInst [] antstack)
{
depth--;
for(Iterator it = ni.getExports(); it.hasNext(); )
{
Export e = it.next();
if (e != pp) continue;
ni = antstack[depth];
pp = e;
int found = findArcs(ni, pp, lay, depth, antstack);
if (found == ERCANTPATHACTIVE) return found;
if (depth > 0)
{
found = findExports(ni, pp, lay, depth, antstack);
if (found == ERCANTPATHACTIVE) return found;
}
}
return ERCANTPATHNULL;
}
/**
* Method to tell whether an AntennaObject is in the active list.
* @param ao the AntennaObject.
* @return true if the AntennaObject is already in the list.
*/
private boolean haveAntennaObject(AntennaObject ao)
{
for(AntennaObject oAo : pathList)
{
if (oAo.geom == ao.geom && oAo.depth == ao.depth)
{
boolean found = true;
int len = 0;
if (ao.hierstack != null) len = ao.hierstack.length;
int oLen = 0;
if (oAo.hierstack != null) oLen = oAo.hierstack.length;
if (len != oLen) continue;
for(int i=0; i | |