/* -*- tab-width: 4 -*-
*
* Electric(tm) VLSI Design System
*
* File: ExportChecker.java
*
* Copyright (c) 2003, 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.ncc.processing;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.variable.VarContext;
import com.sun.electric.tool.ncc.NccGlobals;
import com.sun.electric.tool.ncc.basic.NccCellAnnotations;
import com.sun.electric.tool.ncc.basic.NccUtils;
import com.sun.electric.tool.ncc.netlist.NetObject;
import com.sun.electric.tool.ncc.netlist.Port;
import com.sun.electric.tool.ncc.netlist.Wire;
import com.sun.electric.tool.ncc.result.PortReport.PortReportable;
import com.sun.electric.tool.ncc.trees.Circuit;
import com.sun.electric.tool.ncc.trees.EquivRecord;
import com.sun.electric.tool.user.ncc.ExportMismatch;
import com.sun.electric.tool.Job;
/** The ExportChecker checks the Exports of the two Cells that are
* supposed to be equivalent. NCC checks exports in two phases.
*
* The first phase runs before NCC checks circuit topology.
* In the first phase NCC checks to
* see if the two Cells have consistent Exports. For example,
* if the schematic has a network with Export A then
* NCC makes sure that the layout has a network with Export A.
* <p>
* The second phase runs after NCC has checked the circuit topology.
* In the second phase NCC makes sure that the Wire attached
* to the schematic Export
* A is topologically matched to the Wire attached to layout Export A.
* <p>
* Electric allows a network to have multiple exports. For example
* a schematic network can have three Exports {A, B, C}. NCC
* can do one of two things for networks with multiple Exports.
* <p>
* When I first wrote NCC I implemented a flexible comparison. If the
* schematic had Exports {A, B, C} then I just made sure that the
* layout had a network with any of those Export names. For example,
* if the layout had a network with Exports {B, C, D} then the
* flexible comparison would say they match.
* <p>
* Years after the initial implementation the design group
* noticed that commercial tools lacked this flexibility.
* Therefore I changed NCC to mimic the behavior of
* the commercial tools. If a network has more than one
* Export attached, then NCC sorts the Export names and
* chooses only the first name. In that case if the
* schematic network has Exports {A, B, C} then it does not match
* the layout network with Exports {B, C, D}.
* <p>
* ExportChecker implements both flexible and strict Export
* checking in order to maintain compatibility with most
* of the NCC regression database. When NCC is invoked
* interactively it uses strict Export checking. However
* the regression scripts set NccOption.oneNamePerPort to false
* which enables flexible Export checking.
*/
public class ExportChecker {
/** a Port that doesn't match by name */
private static class NoPortNameMatch {
public final Port port;
public final int circuit;
public final int circuitNoMatchingPort;
public NoPortNameMatch(Port port, int circuit,
int circuitNoMatchingPort) {
this.port = port;
this.circuit = circuit;
this.circuitNoMatchingPort = circuitNoMatchingPort;
}
}
private static class NoPortNameMatches {
private List<NoPortNameMatch> unmatched = new LinkedList<NoPortNameMatch>();
public void add(Port port, int circuit, int circuitNoMatchingPort) {
unmatched.add(new NoPortNameMatch(port, circuit,
circuitNoMatchingPort));
}
public NoPortNameMatch removeFirst() {
Iterator<NoPortNameMatch> it = unmatched.iterator();
if (!it.hasNext()) return null;
NoPortNameMatch u = it.next();
it.remove();
return u;
}
/** prune reflexively related mismatches so we don't report
* "port1 matches port2" and "port2 matches port1" */
public void removeMismatches(Port port1, int circuit1,
Port port2, int circuit2) {
for (Iterator<NoPortNameMatch> it=unmatched.iterator(); it.hasNext();) {
NoPortNameMatch u = it.next();
if ((u.port==port1 && u.circuitNoMatchingPort==circuit2) ||
(u.port==port2 && u.circuitNoMatchingPort==circuit1)) {
it.remove();
}
}
}
}
private NccGlobals globals;
/** The 0th element is null. The ith element maps every port of the 0th
* design to the equivalent port of the ith design. */
private HashMap<Port,Port>[] equivPortMaps;
/** List of ports that can't be matched by name. This list is used to
* suggest possible repairs to the design. */
private NoPortNameMatches noPortNameMatches = new NoPortNameMatches();
private Port[][] getPortsForEachCell() {
int numCells = globals.getNumNetlistsBeingCompared();
Port[][] portsPerCell = new Port[numCells][];
EquivRecord portRec = globals.getPorts();
if (portRec==null) {
// special case: every Cell being compared has zero Ports
for (int i=0; i<numCells; i++) portsPerCell[i]=new Port[0];
} else {
globals.error(!portRec.isLeaf(),"globals ports must hold circuits");
int cellNdx = 0;
for (Iterator<Circuit> it=portRec.getCircuits(); it.hasNext(); cellNdx++) {
Circuit ckt = it.next();
portsPerCell[cellNdx] = new Port[ckt.numNetObjs()];
int portNdx = 0;
for (Iterator<NetObject> pit=ckt.getNetObjs(); pit.hasNext(); portNdx++) {
portsPerCell[cellNdx][portNdx] = (Port)pit.next();
}
}
}
return portsPerCell;
}
private HashMap<String,Port> mapFromExportNameToPort(Port[] ports) {
HashMap<String,Port> map = new HashMap<String,Port>();
for (int portNdx=0; portNdx<ports.length; portNdx++) {
Port p = ports[portNdx];
for (Iterator<String> itN=p.getExportNames(); itN.hasNext();) {
map.put(itN.next(), p);
}
}
return map;
}
private void prln(String s) {System.out.println(s);}
//private void pr(String s) {System.out.print(s);}
private void printOneToManyError(String design1, String exports1,
String design2, Set<Port> exports2) {
prln(" A single network in: "+design1+" has Exports with names that "+
"match multiple Exports in: "+design2);
prln(" However, the "+design2+
" Exports are attached to more than one network.");
prln(" The "+design1+" Exports: "+exports1);
for (Port p2 : exports2) {
prln(" matches the "+design2+" Exports: "+p2.exportNamesString());
}
}
/** For each port, p1, in Circuit ckt1 make sure there is exactly one port
* circuit ckt2 that shares any of p1's export names. Return a map from
* ckt1 ports to matching ckt2 ports. If p1 has no matching port in ckt2
* then record the mismatch in noPortNameMatches. */
private boolean matchPortsByName(HashMap<Port,Port> p1ToP2, Port[][] portsPerCell,
String[] designNames, int ckt1, int ckt2) {
boolean match = true;
HashMap<String,Port> exportName2ToPort2 = mapFromExportNameToPort(portsPerCell[ckt2]);
if (p1ToP2==null) p1ToP2=new HashMap<Port,Port>();
Port[] ckt1Ports = portsPerCell[ckt1];
VarContext rootContexts[] = globals.getRootContexts();
Cell rootCells[] = globals.getRootCells();
for (int portNdx=0; portNdx<ckt1Ports.length; portNdx++) {
Port p1 = ckt1Ports[portNdx];
Set<Port> p2ports = new HashSet<Port>();
for (Iterator<String> itN=p1.getExportNames(); itN.hasNext();) {
String exportNm1 = itN.next();
Port p = exportName2ToPort2.get(exportNm1);
if (p!=null) p2ports.add(p);
}
if (p2ports.size()==0) {
prln(" In "+designNames[ckt1]+
" the network with Exports: "+p1.exportNamesString()+
" matches no Export with the same name in: "+
designNames[ckt2]);
noPortNameMatches.add(p1, ckt1, ckt2);
match = false;
ExportMismatch.MultiMatch em = new ExportMismatch.MultiMatch();
em.setNames(designNames[ckt1], designNames[ckt2]);
em.setCells(rootCells[ckt1], rootCells[ckt2]);
em.setContexts(rootContexts[ckt1], rootContexts[ckt2]);
em.add(0, p1);
em.setValidOnlyWhenTopologyMismatch(true);
globals.getNccGuiInfo().addExportMismatch(em);
} else if (p2ports.size()==1){
p1ToP2.put(p1, p2ports.iterator().next());
} else {
printOneToManyError(designNames[ckt1], p1.exportNamesString(),
designNames[ckt2], p2ports);
match = false;
ExportMismatch.MultiMatch em = new ExportMismatch.MultiMatch();
em.setNames(designNames[ckt1], designNames[ckt2]);
em.setCells(rootCells[ckt1], rootCells[ckt2]);
em.setContexts(rootContexts[ckt1], rootContexts[ckt2]);
em.add(0, p1);
Set<PortReportable> p2portReportables = new HashSet<PortReportable>();
p2portReportables.addAll(p2ports);
em.add(1, p2portReportables);
globals.getNccGuiInfo().addExportMismatch(em);
}
}
return match;
}
private SubcircuitInfo getInfoForReferenceCell(Cell refCell, HierarchyInfo hierInfo) {
SubcircuitInfo refInfo = hierInfo.getSubcircuitInfo(refCell);
// If the reference Cell has already been compared to another Cell
// then return existing SubcircuitInfo
if (refInfo!=null) return refInfo;
// This is the first time the first Cell is used as a reference. Create
// a SubcircuitInfo for it. This SubcircuitInfo will be a reference for
// all the other Cells this one is compared with.
Port[] refCktPorts = getFirstCktPorts();
refInfo = new SubcircuitInfo(hierInfo.currentSubcircuitName(),
hierInfo.currentSubcircuitID(),
refCktPorts);
hierInfo.addSubcircuitInfo(refCell, refInfo);
return refInfo;
}
private Port[] getFirstCktPorts() {
Port[][] portsPerCell = getPortsForEachCell();
return portsPerCell[0];
}
private Map<String,Integer> mapExportNameToPortIndex(SubcircuitInfo refCellInfo,
Map<Port,Port> equivPortMap) {
Map<String,Integer> exportNameToPortIndex = new HashMap<String,Integer>();
Port[] firstCktPorts = getFirstCktPorts();
for (int i=0; i<firstCktPorts.length; i++) {
Port firstCktPort = firstCktPorts[i];
String firstCktPortName =
firstCktPort.getExportNames().next();
int portNdx = refCellInfo.getPortIndex(firstCktPortName);
Port equivPort = equivPortMap.get(firstCktPort);
// if export names don't match then there may be no equivPort
if (equivPort==null) continue;
for (Iterator<String> it=equivPort.getExportNames(); it.hasNext();) {
exportNameToPortIndex.put(it.next(), new Integer(portNdx));
}
}
return exportNameToPortIndex;
}
private Circuit getNthCircuit(EquivRecord er, int nth) {
Job.error(!er.isLeaf(), "only leaf EquivRecords have Circuits!");
Iterator<Circuit> it = er.getCircuits();
for (int i=0; i<nth; i++) {
Job.error(!it.hasNext(), "EquivRec has no Circuit at index: "+nth);
it.next();
}
Job.error(!it.hasNext(), "EquivRec has no Circuit at index: "+nth);
return it.next();
}
/** Find a possible matching Wire or Port for lonely in the nth
* circuit by examining lonely's Wire's equivalence record.
* @return the matching Port or Wire. Return null if no unique matching
* Port or Wire can be found */
private Object findMatchingPortOrWire(Port lonely, int nth) {
Wire lonelyWire = lonely.getWire();
Circuit lonelyCkt = lonelyWire.getParent();
if (lonelyCkt.numNetObjs()!=1) return null;
EquivRecord equivRecord = lonelyCkt.getParent();
Circuit nthCircuit = getNthCircuit(equivRecord, nth);
if (nthCircuit.numNetObjs()!=1) return null;
Wire wire = (Wire) nthCircuit.getNetObjs().next();
Port port = wire.getPort();
return port!=null ? (Object)port : (Object)wire;
}
private String getDescription(Object portOrWire) {
if (portOrWire instanceof Wire) {
return "network: "+((Wire)portOrWire).getName();
} else {
Job.error(!(portOrWire instanceof Port), "not a Wire?");
return "network with Exports: "+
((Port)portOrWire).exportNamesString();
}
}
private void markPortForRenaming(Port p, NccCellAnnotations ann) {
for (Iterator<String> it=p.getExportNames(); it.hasNext();) {
String name = it.next();
if (ann.renameExport(name)) {p.setToBeRenamed(); return;}
}
}
private void markPortsForRenaming(Cell cell, Port[] ports) {
NccCellAnnotations ann = NccCellAnnotations.getAnnotations(cell);
if (ann==null) return;
for (int portNdx=0; portNdx<ports.length; portNdx++)
markPortForRenaming(ports[portNdx], ann);
}
private void printNewPortNames(Port[] ports, Cell rootCell, int cktIndex) {
boolean printedHeader = false;
int otherCktIndex = cktIndex==0 ? 1 : 0;
for (int portNdx=0; portNdx<ports.length; portNdx++) {
Port p = ports[portNdx];
if (p.getToBeRenamed()) {
if (!printedHeader) {
prln(" Attempting to find better names for Exports in Cell: "+
NccUtils.fullName(rootCell));
printedHeader = true;
}
Object o = findMatchingPortOrWire(p, otherCktIndex);
prln(" "+ p.exportNamesString()+" -> "+
getDescription(o));
}
}
}
private void printNewNamesForPortsThatTheUserWantsUsToRename() {
Cell[] rootCells = globals.getRootCells();
Port[][] portsPerCell = getPortsForEachCell();
for (int cellNdx=0; cellNdx<portsPerCell.length; cellNdx++)
printNewPortNames(portsPerCell[cellNdx], rootCells[cellNdx],
cellNdx);
}
/** For each port not matched by name, suggest a possible match */
private void suggestMatchForPortsThatDontMatchByName() {
String[] rootCellNames = globals.getRootCellNames();
boolean printedHeader = false;
VarContext rootContexts[] = globals.getRootContexts();
Cell rootCells[] = globals.getRootCells();
for (NoPortNameMatch no=noPortNameMatches.removeFirst(); no!=null;
no=noPortNameMatches.removeFirst()) {
// skip Ports for which the user has already requested a new name
if (no.port.getToBeRenamed()) continue;
Object o = findMatchingPortOrWire(no.port, no.circuitNoMatchingPort);
if (o==null) {
ExportMismatch.MultiMatch em = new ExportMismatch.MultiMatch();
em.setNames(rootCellNames[no.circuit], rootCellNames[no.circuitNoMatchingPort]);
em.setCells(rootCells[no.circuit], rootCells[no.circuitNoMatchingPort]);
em.setContexts(rootContexts[no.circuit], rootContexts[no.circuitNoMatchingPort]);
em.add(0, no.port);
globals.getNccGuiInfo().addExportMismatch(em);
continue;
}
if (!printedHeader) {
printedHeader = true;
prln(" The following list suggests possible matches for "+
"Exports that failed to match by name.");
}
prln(" in Cell "+rootCellNames[no.circuit]+
" the network with Exports: "+
no.port.exportNamesString()+
" might match in Cell "+rootCellNames[no.circuitNoMatchingPort]+
" the "+getDescription(o));
if (o instanceof Port) {
noPortNameMatches.removeMismatches(no.port, no.circuit, (Port)o,
no.circuitNoMatchingPort);
}
ExportMismatch.NameMismatch esm = new ExportMismatch.NameMismatch();
esm.setNames(rootCellNames[no.circuit], rootCellNames[no.circuitNoMatchingPort]);
esm.setCells(rootCells[no.circuit], rootCells[no.circuitNoMatchingPort]);
esm.setContexts(rootContexts[no.circuit], rootContexts[no.circuitNoMatchingPort]);
esm.setFirstExport(no.port);
esm.setSuggestion((NetObject)o);
globals.getNccGuiInfo().addExportMismatch(esm);
}
if (printedHeader) prln("");
}
// -------------------------- public methods ------------------------------
public ExportChecker(NccGlobals globals) {this.globals=globals;}
public void markPortsForRenaming() {
Cell[] rootCells = globals.getRootCells();
Port[][] portsPerCell = getPortsForEachCell();
for (int cellNdx=0; cellNdx<portsPerCell.length; cellNdx++)
markPortsForRenaming(rootCells[cellNdx], portsPerCell[cellNdx]);
}
/** match Exports by name. NCC runs this first phase before
* topology matching in order to
* give user early warning about Export name inconsistencies. */
public boolean matchByName() {
int numCkts = globals.getNumNetlistsBeingCompared();
String[] rootCellNames = globals.getRootCellNames();
Port[][] portsPerCell = getPortsForEachCell();
equivPortMaps = new HashMap[numCkts];
boolean match=true;
for (int i=1; i<numCkts; i++) {
match &= matchPortsByName(null, portsPerCell, rootCellNames, i, 0);
HashMap<Port,Port> p0ToPi = new HashMap<Port,Port>();
match &= matchPortsByName(p0ToPi, portsPerCell, rootCellNames, 0, i);
equivPortMaps[i] = p0ToPi;
}
return match;
}
/** Gather information that will allow hierarchical netlist comparison
* at higher level to treat me as a subcircuit. */
public void saveInfoNeededToMakeMeASubcircuit(HierarchyInfo hierInfo) {
if (hierInfo==null) return;
Cell[] rootCells = globals.getRootCells();
Cell refCell = globals.getRootCells()[0];
SubcircuitInfo refCellInfo = getInfoForReferenceCell(refCell, hierInfo);
// It is reasonable for two different designs to share some
// cells. It's also reasonable to compare the same schematic Cell
// with two different VarContexts. When this happens, don't create
// two SubcircuitInfo's for the same cell.
Set<Cell> doneCells = new HashSet<Cell>();
doneCells.add(refCell);
for (int i=1; i<equivPortMaps.length; i++) {
if (doneCells.contains(rootCells[i])) continue;
doneCells.add(rootCells[i]);
Map<String,Integer> exportNameToPortIndex =
mapExportNameToPortIndex(refCellInfo, equivPortMaps[i]);
SubcircuitInfo subInf =
new SubcircuitInfo(refCellInfo, exportNameToPortIndex);
hierInfo.addSubcircuitInfo(rootCells[i], subInf);
}
}
/** Check that Exports with matching names are on equivalent nets.
* NCC runs this second phase after it has performed topology matching
* @return true if equivalent. */
public boolean ensureExportsWithMatchingNamesAreOnEquivalentNets() {
boolean match=true;
String[] rootCellNames = globals.getRootCellNames();
VarContext rootContexts[] = globals.getRootContexts();
Cell rootCells[] = globals.getRootCells();
for (int i=1; i<equivPortMaps.length; i++) {
HashMap<Port,Port> portToPort = equivPortMaps[i];
for (Port p0 : portToPort.keySet()) {
Port pn = portToPort.get(p0);
// skip Ports that the user wants us to rename
if (p0.getToBeRenamed() || pn.getToBeRenamed()) continue;
Wire w0 = p0.getWire();
Wire wn = pn.getWire();
EquivRecord er0 = w0.getParent().getParent();
EquivRecord ern = wn.getParent().getParent();
if (er0!=ern) {
prln(" Exports that match by name aren't on equivalent"+
" networks");
prln(" Cell1: "+rootCellNames[0]);
prln(" Exports1: "+p0.exportNamesString());
prln(" Cell2: "+rootCellNames[i]);
prln(" Exports2: "+pn.exportNamesString());
Object portOrWire = findMatchingPortOrWire(p0, i);
if (portOrWire!=null)
prln(" However the Cell1 network appears to match Cell2's: "+
getDescription(portOrWire));
prln("");
match = false;
ExportMismatch.TopologyMismatch esm = new ExportMismatch.TopologyMismatch();
esm.setNames(rootCellNames[0], rootCellNames[i]);
esm.setCells(rootCells[0], rootCells[i]);
esm.setContexts(rootContexts[0], rootContexts[i]);
esm.setFirstExport(p0);
esm.setSecondExport(pn);
if(portOrWire != null)
esm.setSuggestion((NetObject)portOrWire);
globals.getNccGuiInfo().addExportMismatch(esm);
}
}
}
return match;
}
/** If the topological comparison is successful, then both circuits
* match. NCC checks to see if the Export names are consistent with
* the match. If not, then NCC will try to suggest how the
* designer should rename Exports.
*/
public void suggestPortMatchesBasedOnTopology() {
printNewNamesForPortsThatTheUserWantsUsToRename();
suggestMatchForPortsThatDontMatchByName();
}
}