/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: PLA.java
 * MOSIS CMOS PLA Generator.
 * Originally written by Wallace Kroeker at the University of Calgary
 * Translated to Java by Steven Rubin, Sun Microsystems.
 *
 * Copyright (c) 2005, 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.sun.electric.tool.generator.cmosPLA;

import com.sun.electric.database.EditingPreferences;
import com.sun.electric.database.geometry.Poly;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.hierarchy.Export;
import com.sun.electric.database.hierarchy.Library;
import com.sun.electric.database.prototype.PortProto;
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.Variable;
import com.sun.electric.lib.LibFile;
import com.sun.electric.technology.ArcProto;
import com.sun.electric.technology.PrimitiveNode;
import com.sun.electric.technology.Technology;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.JobException;
import com.sun.electric.tool.io.FileType;
import com.sun.electric.tool.io.input.LibraryFiles;
import com.sun.electric.tool.user.User;
import com.sun.electric.tool.user.dialogs.EDialog;
import com.sun.electric.tool.user.dialogs.OpenFile;
import com.sun.electric.tool.user.ui.WindowFrame;
import com.sun.electric.util.math.Orientation;

import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.net.URL;

import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JTextField;

/**
 * Class to generate MOSIS CMOS PLAs from personality files.
 */
public class PLA
{
	/** maximum number of columns */						static final int MAX_COL_SIZE = 500;
	/** Lambda seperation between grid lines in array */	static final int X_SEP        =  10;
	/** Lambda seperation between grid line */				static final int Y_MIR_SEP    =  10;
	/** Lambda seperation between grid lines in array */	static final int Y_SEP        =  10;

	static class UCItem
	{
		int       value;
		NodeInst  nodeInst;
		UCItem    rightItem;
		UCItem    bottomItem;
	};

	/**
	 * Row and column pointers
	 */
	static class UCRow
	{
		UCItem firstItem;
		UCItem lastitem;

		UCRow()
		{
			firstItem = new UCItem();
			lastitem = new UCItem();
		}
	};

	/** the cells in the pla_mocmos library */	Cell nmosOne, pmosOne, decoderInv, ioInv4, pullUps;
	/** the generated cells */					private Cell pmosCell, nmosCell, orCell, decodeCell, orPlaneCell;
	/** primitives in the mocmos technology */	PrimitiveNode m1Pin, m2Pin, m12Con, mpCon, maCon, mwBut, msBut, pwNode;
	/** arcs in the mocmos technology */		ArcProto m1Arc, m2Arc, pArc, aArc;
	UCRow [] columnList;
	UCRow [][] rowList;
	private NGrid ng;
	private PGrid pg;
	private Decode dec;
	private String cellName;
	private String andFileName;
	private String orFileName;
	private boolean inputsOnTop;
	private boolean outputsOnBottom;
    private final EditingPreferences ep;

	/**
	 * Method called to generate a CMOS PLA.
	 * Invokes a dialog to ask for options.
	 */
	public static void generate()
	{
		// prompt the user for some information
		SetupPLAGen dialog = new SetupPLAGen();
		if (dialog.failed()) return;

		generate(Library.getCurrent(), dialog.getCellName(), dialog.getAndFileName(), dialog.getOrFileName(),
			dialog.isInputsOnTop(), dialog.isOutputsOnBottom(), false);
	}

	/**
	 * Method called to generate a CMOS PLA, given all options.
     * @param destLib library where create PLA cells.
	 * @param cellName the name of the PLA cell to generate.
	 * @param andFileName the disk file with the AND plane.
	 * @param orFileName the disk file with the OR plane.
	 * @param inputsOnTop true to place inputs on the top of the plane.
	 * @param outputsOnBottom true to place outputs on the bottom of the plane.
	 * @param doItNow true if already in a Job and so do not start another.
	 */
	public static void generate(Library destLib, String cellName, String andFileName, String orFileName,
                                boolean inputsOnTop, boolean outputsOnBottom, boolean doItNow)
	{
		new GeneratePLAJob(destLib, cellName, andFileName, orFileName, inputsOnTop, outputsOnBottom, doItNow);
	}

	/**
	 * Class to build a CMOS PLA in a new Job.
	 */
	private static class GeneratePLAJob extends Job
	{
        private Library destLib;
		private String cellName;
		private String andFileName;
		private String orFileName;
		private boolean inputsOnTop;
		private boolean outputsOnBottom;
		private Cell newCell;
        private boolean doItNow; // for regression

        protected GeneratePLAJob(Library destLib, String cellName, String andFileName, String orFileName,
                                 boolean inputsOnTop, boolean outputsOnBottom, boolean doItNow)
		{
			super("Generate MOSIS CMOS PLA", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
            this.destLib = destLib;
			this.cellName = cellName;
			this.andFileName = andFileName;
			this.orFileName = orFileName;
			this.inputsOnTop = inputsOnTop;
			this.outputsOnBottom = outputsOnBottom;
            this.doItNow = doItNow;
            if (doItNow)
            {
                try {doIt();} catch (Exception e) { e.printStackTrace();}
            } else
            {
			    startJob();
            }
		}

        @Override
		public boolean doIt() throws JobException
		{
            EditingPreferences ep = getEditingPreferences();
			String libName = "pla_mocmos";
			if (Library.findLibrary(libName) == null)
			{
	            // start a job to read the PLA support library
				URL fileURL = LibFile.getLibFile(libName + ".jelib");
	    		LibraryFiles.readLibrary(ep, fileURL, libName, FileType.JELIB, true);
			}

			PLA pla = new PLA(cellName, andFileName, orFileName, inputsOnTop, outputsOnBottom, ep);
			newCell = pla.doStep(destLib);
            if (!doItNow)
			    fieldVariableChanged("newCell");
			return true;
		}

        @Override
        public void terminateOK()
        {
            if (newCell != null)
            {
                WindowFrame wf = WindowFrame.getCurrentWindowFrame();
    			if (User.isShowCellsInNewWindow()) wf = null;
    			if (wf == null) wf = WindowFrame.createEditWindow(newCell);
                wf.setCellWindow(newCell, null);
            }
        }
	}

	private PLA(String cellName, String andFileName, String orFileName, boolean inputsOnTop, boolean outputsOnBottom, EditingPreferences ep)
	{
        this.ep = ep;
		this.cellName = cellName;
		this.andFileName = andFileName;
		this.orFileName = orFileName;
		this.inputsOnTop = inputsOnTop;
		this.outputsOnBottom = outputsOnBottom;
		pg = new PGrid(this, ep);
		ng = new NGrid(this, ep);
		dec = new Decode(this, ep);
	}

	private Cell doStep(Library lib)
		throws JobException
	{
		initialize();

		// generate the AND plane (Decode unit of a ROM)
		String thisCellName = cellName + "_p_cell{lay}";
		pmosCell = pg.pmosGrid(lib, andFileName, thisCellName);
		if (pmosCell == null) return null;

		thisCellName = cellName + "_n_cell{lay}";
		nmosCell = ng.nmosGrid(lib, andFileName, thisCellName);
		if (nmosCell == null) return null;

		thisCellName = cellName + "_decode{lay}";
		decodeCell = dec.decodeGen(lib, pmosCell, nmosCell, thisCellName, inputsOnTop);
		if (decodeCell == null) return null;

		// Generate the OR plane
		thisCellName = cellName + "_or_cell{lay}";
		orCell = ng.nmosGrid(lib, orFileName, thisCellName);
		if (orCell == null) return null;

		thisCellName = cellName + "_or_plane{lay}";
		orPlaneCell = makeOrPlane(lib, thisCellName, outputsOnBottom);
		if (orPlaneCell == null) return null;

		Cell plaCell = makePLA(lib, cellName + "{lay}");
		return plaCell;
	}

	private void initialize()
		throws JobException
	{
		// get the right technology and all its nodes and arcs
		Technology theTech = Technology.getMocmosTechnology();
		if (theTech == null) throw new JobException("Cannot find technology 'mocmos'");

		m1Pin = theTech.findNodeProto("Metal-1-Pin");
		if (m1Pin == null) throw new JobException("Cannot find Metal-1-Pin primitive");
		m2Pin = theTech.findNodeProto("Metal-2-Pin");
		if (m2Pin == null) throw new JobException("Cannot find Metal-2-Pin primitive");
		m12Con = theTech.findNodeProto("Metal-1-Metal-2-Con");
		if (m12Con == null) throw new JobException("Cannot find Metal-1-Metal-2-Con primitive");
		mpCon = theTech.findNodeProto("Metal-1-Polysilicon-1-Con");
		if (mpCon == null) throw new JobException("Cannot find Metal-1-Polysilicon-1-Con primitive");
		maCon = theTech.findNodeProto("Metal-1-N-Active-Con");
		if (maCon == null) throw new JobException("Cannot find Metal-1-N-Active-Con primitive");
		mwBut = theTech.findNodeProto("Metal-1-P-Well-Con");
		if (mwBut == null) throw new JobException("Cannot find Metal-1-P-Well-Con primitive");
		msBut = theTech.findNodeProto("Metal-1-N-Well-Con");
		if (msBut == null) throw new JobException("Cannot find Metal-1-N-Well-Con primitive");
		pwNode = theTech.findNodeProto("P-Well-Node");
		if (pwNode == null) throw new JobException("Cannot find P-Well-Node primitive");

		m1Arc = theTech.findArcProto("Metal-1");
		if (msBut == null) throw new JobException("Cannot find Metal-1 arc");
		m2Arc = theTech.findArcProto("Metal-2");
		if (msBut == null) throw new JobException("Cannot find Metal-2 arc");
		pArc = theTech.findArcProto("Polysilicon-1");
		if (msBut == null) throw new JobException("Cannot find Polysilicon-1 arc");
		aArc = theTech.findArcProto("N-Active");
		if (msBut == null) throw new JobException("Cannot find N-Active arc");

		// make sure the standard cell library is read in
		String libName = "pla_mocmos";
		Library cellLib = Library.findLibrary(libName);
		if (cellLib == null) throw new JobException("Cannot find the 'pla_mocmos' support library");

		// find required cells in the library
		nmosOne = cellLib.findNodeProto("nmos_one");
		if (nmosOne == null) throw new JobException("Unable to find cell 'nmos_one'");
		pmosOne = cellLib.findNodeProto("pmos_one");
		if (pmosOne == null) throw new JobException("Unable to find cell 'pmos_one'");
		decoderInv = cellLib.findNodeProto("decoder_inv1");
		if (decoderInv == null) throw new JobException("Unable to find cell 'decoder_inv1'");
		ioInv4 = cellLib.findNodeProto("io-inv-4");
		if (ioInv4 == null) throw new JobException("Unable to find cell 'io-inv-4'");
		pullUps = cellLib.findNodeProto("pullups");
		if (pullUps == null) throw new JobException("Unable to find cell 'pullups'");

		// initialize the global tables
		columnList = new UCRow[MAX_COL_SIZE];
		rowList = new UCRow[MAX_COL_SIZE][3];
		for(int i=0; i<MAX_COL_SIZE; i++)
		{
			columnList[i] = new UCRow();
			rowList[i][0] = new UCRow();
			rowList[i][1] = new UCRow();
			rowList[i][2] = new UCRow();
		}
	}

	/**
	 * This class displays a dialog to setup PLA generation.
	 */
	private static class SetupPLAGen extends EDialog
	{
		private JTextField andPlaneFile, orPlaneFile, cellName;
		private JCheckBox inputs, outputs;
		private boolean good;

		/** Creates new form to setup PLA generation */
		private SetupPLAGen()
		{
			super(null, true);
			good = false;
			initComponents();
			setVisible(true);
		}

		protected void escapePressed() { exit(false); }

		public boolean failed() { return !good; }

		public String getAndFileName() { return andPlaneFile.getText(); }

		public String getOrFileName() { return orPlaneFile.getText(); }

		public boolean isInputsOnTop() { return inputs.isSelected(); }

		public boolean isOutputsOnBottom() { return outputs.isSelected(); }

		public String getCellName() { return cellName.getText(); }

		// Call this method when the user clicks the OK button
		private void exit(boolean goodButton)
		{
			good = goodButton;
			setVisible(false);
		}

		private void browse(boolean andPlane)
		{
			String fileName = OpenFile.chooseInputFile(FileType.ANY,
				(andPlane ? "AND Plane File:" : "OR Plane File:"), null);
			if (fileName == null) return;
			if (andPlane) andPlaneFile.setText(fileName); else
				orPlaneFile.setText(fileName);
		}

		private void initComponents()
		{
			getContentPane().setLayout(new GridBagLayout());

			setTitle("MOSIS CMOS PLA Generation Control");
			setName("");
			addWindowListener(new WindowAdapter()
			{
				public void windowClosing(WindowEvent evt) { exit(false); }
			});

			// the AND plane
			JLabel lab1 = new JLabel("AND Plane File:");
			GridBagConstraints gbc = new GridBagConstraints();
			gbc.gridx = 0;   gbc.gridy = 0;
			gbc.anchor = GridBagConstraints.WEST;
			gbc.insets = new java.awt.Insets(4, 4, 4, 4);
			getContentPane().add(lab1, gbc);

			andPlaneFile = new JTextField();
			andPlaneFile.setColumns(35);
			gbc = new GridBagConstraints();
			gbc.gridx = 1;   gbc.gridy = 0;
			gbc.fill = GridBagConstraints.BOTH;
			gbc.weightx = 1;   gbc.weighty = 0.5;
			gbc.anchor = GridBagConstraints.WEST;
			gbc.insets = new java.awt.Insets(4, 4, 4, 4);
			getContentPane().add(andPlaneFile, gbc);

			JButton andPlaneBrowse = new JButton("Browse");
			gbc = new GridBagConstraints();
			gbc.gridx = 2;    gbc.gridy = 0;
			gbc.insets = new java.awt.Insets(4, 4, 4, 4);
			getContentPane().add(andPlaneBrowse, gbc);
			andPlaneBrowse.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt) { browse(true); }
			});

			// the OR plane
			JLabel lab2 = new JLabel("OR Plane File:");
			gbc = new GridBagConstraints();
			gbc.gridx = 0;   gbc.gridy = 1;
			gbc.anchor = GridBagConstraints.WEST;
			gbc.insets = new java.awt.Insets(4, 4, 4, 4);
			getContentPane().add(lab2, gbc);

			orPlaneFile = new JTextField();
			orPlaneFile.setColumns(35);
			gbc = new GridBagConstraints();
			gbc.gridx = 1;   gbc.gridy = 1;
			gbc.weightx = 1;   gbc.weighty = 0.5;
			gbc.fill = GridBagConstraints.BOTH;
			gbc.anchor = GridBagConstraints.WEST;
			gbc.insets = new java.awt.Insets(4, 4, 4, 4);
			getContentPane().add(orPlaneFile, gbc);

			JButton orPlaneBrowse = new JButton("Browse");
			gbc = new GridBagConstraints();
			gbc.gridx = 2;    gbc.gridy = 1;
			gbc.insets = new java.awt.Insets(4, 4, 4, 4);
			getContentPane().add(orPlaneBrowse, gbc);
			orPlaneBrowse.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt) { browse(false); }
			});

			// other questions
			inputs = new JCheckBox("Input to the Top of the AND plane");
			inputs.setSelected(true);
			gbc = new GridBagConstraints();
			gbc.gridx = 0;   gbc.gridy = 2;
			gbc.gridwidth = 3;
			gbc.anchor = GridBagConstraints.WEST;
			gbc.insets = new java.awt.Insets(4, 4, 4, 4);
			getContentPane().add(inputs, gbc);

			outputs = new JCheckBox("Outputs from the Bottom of the OR plane");
			outputs.setSelected(true);
			gbc = new GridBagConstraints();
			gbc.gridx = 0;   gbc.gridy = 3;
			gbc.gridwidth = 3;
			gbc.anchor = GridBagConstraints.WEST;
			gbc.insets = new java.awt.Insets(4, 4, 4, 4);
			getContentPane().add(outputs, gbc);

			JLabel lab3 = new JLabel("Cell name:");
			gbc = new GridBagConstraints();
			gbc.gridx = 0;   gbc.gridy = 4;
			gbc.anchor = GridBagConstraints.WEST;
			gbc.insets = new java.awt.Insets(4, 4, 4, 4);
			getContentPane().add(lab3, gbc);

			cellName = new JTextField();
			cellName.setText("pla");
			gbc = new GridBagConstraints();
			gbc.gridx = 1;   gbc.gridy = 4;
			gbc.fill = GridBagConstraints.HORIZONTAL;
			gbc.anchor = GridBagConstraints.WEST;
			gbc.insets = new java.awt.Insets(4, 4, 4, 4);
			getContentPane().add(cellName, gbc);

			// OK and Cancel
			JButton cancel = new JButton("Cancel");
			gbc = new GridBagConstraints();
			gbc.gridx = 0;   gbc.gridy = 5;
			gbc.anchor = GridBagConstraints.CENTER;
			gbc.insets = new java.awt.Insets(4, 4, 4, 4);
			getContentPane().add(cancel, gbc);
			cancel.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt) { exit(false); }
			});

			JButton ok = new JButton("OK");
			getRootPane().setDefaultButton(ok);
			gbc = new java.awt.GridBagConstraints();
			gbc.gridx = 1;   gbc.gridy = 5;
			gbc.gridwidth = 2;
			gbc.anchor = GridBagConstraints.CENTER;
			gbc.insets = new java.awt.Insets(4, 4, 4, 4);
			getContentPane().add(ok, gbc);
			ok.addActionListener(new java.awt.event.ActionListener()
			{
				public void actionPerformed(java.awt.event.ActionEvent evt) { exit(true); }
			});

			pack();
		}
	}

	/**
	 * Plus Output options eg. column decode etc.
	 */
	private Cell makeOrPlane(Library library, String cellName, boolean outputsOnBottom)
	{
		Cell cell = Cell.makeInstance(ep, library, cellName);
		NodeInst orNode = makeInstance(cell, orCell, 0, 0, false);
		if (orNode == null) return null;

		NodeInst newNode = null;
		NodeInst lastNode = null;
		int columns = 2;
		Variable var = orCell.getVar("PLA_data_cols");
		if (var != null) columns = ((Integer)var.getObject()).intValue(); else
			System.out.println("DATA_cols defaulting to 2");

		NodeInst gndNode2 = null, pwrNode1 = null;
		char side = 'n';
		if (outputsOnBottom) side = 's';
		int limit = columns / 4;
		Rectangle2D nodeBounds = orNode.getBounds();
		double lowX = nodeBounds.getMinX();   double highX = nodeBounds.getMaxX();
		double lowY = nodeBounds.getMinY();   double highY = nodeBounds.getMaxY();

		Rectangle2D cellBounds = ioInv4.getBounds();
		double diff = cellBounds.getHeight();
		if (diff < 0) diff = -diff;
		if (limit == 0 || (columns % 4) != 0) limit++;
		int invCnt = 0;
		double invY = highY;
		if (outputsOnBottom) invY = lowY - diff;
		invY -= 5;
		double invXOffset = lowX + 17;
		PortProto invPwrW = ioInv4.findExport("PWR.m-2.w");
		PortProto invPwrE = ioInv4.findExport("PWR.m-2.e");
		PortProto invGnd1W = ioInv4.findExport("GND.m-2.sw");
		PortProto invGnd1E = ioInv4.findExport("GND.m-2.se");
		PortProto invGnd2W = ioInv4.findExport("GND.m-2.nw");
		PortProto invGnd2E = ioInv4.findExport("GND.m-2.ne");
		for (int i = 0; i < limit; i++)
		{
			newNode = makeInstance(cell, ioInv4, i*50 + invXOffset, invY, !outputsOnBottom);
			if (newNode == null) return null;
			if (lastNode == null)
			{
				Export.newInstance(cell, newNode.findPortInstFromProto(invPwrW), "PWR.m-2.w", ep);
				Export.newInstance(cell, newNode.findPortInstFromProto(invGnd1W), "GND.m-2.w", ep);

				// Put in GND bar pin (metal-2)
				Poly poly = newNode.findPortInstFromProto(invGnd1W).getPoly();
				double pwrY = poly.getCenterY();
				NodeInst gndNode1 = makePin(cell, lowX-13, pwrY, 6, m12Con);
				if (gndNode1 == null) return null;
				makeWire(m2Arc, 14, newNode, invGnd1W, gndNode1, gndNode1.getProto().getPort(0), cell);

				poly = newNode.findPortInstFromProto(invGnd2W).getPoly();
				pwrY = poly.getCenterY();
				gndNode2 = makePin(cell, lowX-13, pwrY, 6, m12Con);
				if (gndNode2 == null) return null;
				makeWire(m2Arc, 14, newNode, invGnd2W, gndNode2, gndNode2.getProto().getPort(0), cell);
				makeWire(m2Arc, 14, gndNode1, gndNode1.getProto().getPort(0), gndNode2,
					gndNode2.getProto().getPort(0), cell);
			} else
			{
				// get wired
				if (invPwrW != null && invPwrE != null)
					makeWire(m2Arc, 14, lastNode, invPwrE, newNode, invPwrW, cell);
				if (invGnd1W != null && invGnd1E != null)
					makeWire(m2Arc, 14, lastNode, invGnd1E, newNode, invGnd1W, cell);
				if (invGnd2W != null && invGnd2E != null)
					makeWire(m2Arc, 14, lastNode, invGnd2E, newNode, invGnd2W, cell);
			}
			lastNode = newNode;
			for (int x = invCnt; x < invCnt+4; x++)
			{
				Export pPort = orCell.findExport("DATA" + x + ".m-1." + side);
				Export nPort = ioInv4.findExport("in" + (x%4) + ".m-1.n");
				Export outPort = ioInv4.findExport("out" + (x % 4) + "-bar.m-1.s");
				if (outPort != null)
					Export.newInstance(cell, newNode.findPortInstFromProto(outPort), "out" + (invCnt + (x % 4)) + ".m-1." + side, ep);
				if (pPort != null && nPort != null)
					makeWire(m1Arc, 4, orNode, pPort, newNode, nPort, cell);
			}
			invCnt += 4;
		}
		if (invPwrE != null)
		{
			Poly poly = lastNode.findPortInstFromProto(invPwrE).getPoly();
			double pwrY = poly.getCenterY();

			pwrNode1 = makePin(cell, highX+13, pwrY, 14, m2Pin);
			if (pwrNode1 == null) return null;
			makeWire(m2Arc, 14, lastNode, invPwrE, pwrNode1, pwrNode1.getProto().getPort(0), cell);
			Export.newInstance(cell, lastNode.findPortInstFromProto(invPwrE), "PWR.m-2.e", ep);
		}
		if (invGnd1W != null)
			Export.newInstance(cell, lastNode.findPortInstFromProto(invGnd1E), "GND.m-2.e", ep);

		// OK PUT in the PULLUPS
		newNode = null;
		lastNode = null;
		side = 's';
		if (outputsOnBottom) side = 'n';

		Rectangle2D pullUpsBounds = pullUps.getBounds();
		double pullUpsDiff = pullUpsBounds.getHeight();
		if (pullUpsDiff < 0) pullUpsDiff = -pullUpsDiff;
		int pullUpsCnt = 0;
		double pullUpsY = highY;
		if (!outputsOnBottom) pullUpsY = lowY - pullUpsDiff;
		pullUpsY += 16;
		double pullUpsXOffset = lowX + 27;
		Export pullUpsPwrW = pullUps.findExport("PWR.m-2w");
		if (pullUpsPwrW == null)
		{
			System.out.println("Cannot find port PWR.m-2w in "+pullUps);
			return null;
		}
		Export pullUpsPwrE = pullUps.findExport("PWR.m-2e");
		if (pullUpsPwrE == null)
		{
			System.out.println("Cannot find port PWR.m-2e in "+pullUps);
			return null;
		}
		Export pullUpsGnd1W = pullUps.findExport("GND.m-1.sw");
		if (pullUpsGnd1W == null)
		{
			System.out.println("Cannot find port GND.m-1.sw in "+pullUps);
			return null;
		}
		Export pullUpsGnd1E = pullUps.findExport("GND.m-1.se");
		if (pullUpsGnd1E == null)
		{
			System.out.println("Cannot find port GND.m-1.se in "+pullUps);
			return null;
		}
		for (int i = 0; i < limit; i++)
		{
			newNode = makeInstance(cell, pullUps, i*50 + pullUpsXOffset, pullUpsY, !outputsOnBottom);
			if (newNode == null) return null;
			if (lastNode == null)
			{
				Export.newInstance(cell, newNode.findPortInstFromProto(pullUpsPwrW), "PWR0.m-2.w", ep);
				Export.newInstance(cell, newNode.findPortInstFromProto(pullUpsGnd1W), "GND0.m-1.w", ep);
				PortInst pi = newNode.findPortInstFromProto(pullUpsGnd1W);
				Poly poly = pi.getPoly();
				double pwrY = poly.getCenterY();
				NodeInst gndNode1 = makePin(cell, lowX-13, pwrY, 6, m12Con);
				if (gndNode1 == null) return null;
				makeWire(m1Arc, 4, newNode, pullUpsGnd1W, gndNode1, gndNode1.getProto().getPort(0), cell);
				makeWire(m2Arc, 14, gndNode1, gndNode1.getProto().getPort(0), gndNode2,
					gndNode2.getProto().getPort(0), cell);

				Export pullUpsGnd2W = orCell.findExport("GND" + i + ".m-1." + side);
				Export pullUpsGnd2E = orCell.findExport("GND" + (i+1) + ".m-1." + side);

				if (pullUpsGnd1W != null && pullUpsGnd2W != null)
					makeWire(m1Arc, 4, newNode, pullUpsGnd1W, orNode, pullUpsGnd2W, cell);
				if (pullUpsGnd2W != null && pullUpsGnd2E != null)
					makeWire(m1Arc, 4, newNode, pullUpsGnd1E, orNode, pullUpsGnd2E, cell);
			} else
			{
				// get wired
				if (pullUpsPwrW != null && pullUpsPwrE != null)
					makeWire(m2Arc, 14, lastNode, pullUpsPwrE, newNode, pullUpsPwrW, cell);
				Export pullUpsGnd2W = orCell.findExport("GND" + i + ".m-1." + side);
				Export pullUpsGnd2E = orCell.findExport("GND" + (i+1) + ".m-1." + side);
				if (pullUpsGnd1W != null && pullUpsGnd2W != null)
					makeWire(m1Arc, 4, newNode, pullUpsGnd1W, orNode, pullUpsGnd2W, cell);
				if (pullUpsGnd1E != null && pullUpsGnd2E != null)
					makeWire(m1Arc, 4, newNode, pullUpsGnd1E, orNode, pullUpsGnd2E, cell);
			}
			lastNode = newNode;
			for (int x = pullUpsCnt; x < pullUpsCnt+4; x++)
			{
				Export pPort = orCell.findExport("DATA" + x + ".m-1." + side);
				Export nPort = pullUps.findExport("PULLUP" + (x%4) + ".m-1.s");
				if (pPort != null && nPort != null)
					makeWire(m1Arc, 4, orNode, pPort, newNode, nPort, cell);
			}
			pullUpsCnt += 4;
		}
		if (pullUpsPwrE != null)
		{
			PortInst pi = lastNode.findPortInstFromProto(pullUpsPwrE);
			Poly poly = pi.getPoly();
			double pwrY = poly.getCenterY();
			NodeInst pwrNode2 = makePin(cell, highX+13, pwrY, 14, m2Pin);
			if (pwrNode2 == null) return null;
			makeWire(m2Arc, 14, lastNode, pullUpsPwrE, pwrNode2, pwrNode2.getProto().getPort(0), cell);
			makeWire(m2Arc, 14, pwrNode1, pwrNode1.getProto().getPort(0), pwrNode2,
				pwrNode2.getProto().getPort(0), cell);
			Export.newInstance(cell, lastNode.findPortInstFromProto(pullUpsPwrE), "PWR0.m-2.e", ep);
		}
		if (pullUpsGnd1E != null)
			Export.newInstance(cell, lastNode.findPortInstFromProto(pullUpsGnd1E), "GND0.m-1.e", ep);
		int x = 0;
		String aName = "ACCESS" + x + ".p.w";
		PortProto pPort = orCell.findPortProto(aName);
		while (pPort != null)
		{
			Export.newInstance(cell, orNode.findPortInstFromProto(pPort), aName, ep);
			x++;
			aName = "ACCESS" + x + ".p.w";
			pPort = orCell.findPortProto(aName);
		}

		return cell;
	}

	private Cell makePLA(Library library, String name)
	{
		Cell cell = Cell.makeInstance(ep, library, name);
		NodeInst decodeNode = makeInstance(cell, decodeCell, 0, 0, false);
		if (decodeNode == null) return null;
		Rectangle2D decodeBounds = decodeNode.getBounds();
		NodeInst orNode = makeInstance(cell, orPlaneCell, decodeBounds.getMaxX()+27, 0, false);
		if (orNode == null) return null;

		Export orPort = orPlaneCell.findExport("ACCESS0.p.w");
		Poly orPoly = orNode.findPortInstFromProto(orPort).getPoly();
		double orY = orPoly.getCenterY();

		Export decodePort = decodeCell.findExport("DATA0.m-1.n");
		Poly decodePoly = decodeNode.findPortInstFromProto(decodePort).getPoly();
		double decodeY = decodePoly.getCenterY();
		double dY = decodeY - orY;
		if (orY != decodeY) orNode.move(0, dY);

		int x = 0;
		while (orPort != null && decodePort != null)
		{
			makeWire(m1Arc, 4, orNode, orPort, decodeNode, decodePort, cell);
			x++;
			orPort = orPlaneCell.findExport("ACCESS" + x + ".p.w");
			decodePort = decodeCell.findExport("DATA" + x + ".m-1.n");
		}

		return cell;
	}

	NodeInst makePin(Cell cell, double x, double y, double size, PrimitiveNode node)
	{
		double xS = node.getDefWidth(ep);
		double yS = node.getDefHeight(ep);
		if (size < xS) size = xS;
		if (size < yS) size = yS;
		NodeInst ni = NodeInst.makeInstance(node, ep, new Point2D.Double(x, y), size, size, cell);
		if (ni == null)
			System.out.println("Unable to create " + node + " in " + cell);
		return ni;
	}

	void makeWire(ArcProto typ, double width, NodeInst fromNodeInst,
		PortProto fromPortProto, NodeInst toNodeInst, PortProto toPortProto, Cell cell)
	{
		PortInst fromPi = fromNodeInst.findPortInstFromProto(fromPortProto);
		PortInst toPi = toNodeInst.findPortInstFromProto(toPortProto);
		if (typ == null)
		{
			System.out.println("Attempting to wire with unknown arc layer");
			return;
		}
		double wid = typ.getDefaultLambdaBaseWidth(ep);
//		double wid = typ.getDefaultLambdaFullWidth();
//		w = us_widestarcinst(typ, fromnodeinst, fromportproto);
//		if (w > wid) wid = w;
//		w = us_widestarcinst(typ, tonodeinst, toportproto);
//		if (w > wid) wid = w;
		if (width > wid) wid = width;
		ArcInst ai = ArcInst.makeInstanceBase(typ, ep, wid, fromPi, toPi);
//		ArcInst ai = ArcInst.makeInstanceFull(typ, wid, fromPi, toPi);
		if (ai == null)
		{
			System.out.println("Unable to run " + typ + " from " + fromNodeInst +
				", " + fromPortProto + " to " + toNodeInst + ", " +
				toPortProto + " in " + cell);
		}
	}

	NodeInst makeInstance(Cell cell, Cell instProto, double x, double y, boolean mirror)
	{
		Rectangle2D protoBounds = instProto.getBounds();
		double sX = protoBounds.getWidth();
		double sY = protoBounds.getHeight();
		Orientation orient = Orientation.IDENT;
		if (mirror)
		{
			orient = Orientation.Y;
			double y1 = protoBounds.getMinY() + y;
			double y2 = protoBounds.getMaxY() + y;
			y = y2 + y1 - y;
		}
		NodeInst ni = NodeInst.makeInstance(instProto, ep, new Point2D.Double(x, y), sX, sY, cell, orient, null);
		return ni;
	}
}
