/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: PGrid.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.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.NodeInst;
import com.sun.electric.database.topology.PortInst;

/**
 * Class to generate the P grid part of MOSIS CMOS PLAs.
 */
public class PGrid
{
	private PLA pla;
    private EditingPreferences ep;

	PGrid(PLA pla, EditingPreferences ep)
	{
		this.pla = pla;
        this.ep = ep;
	}

	Cell pmosGrid(Library library, String fileName, String cellName)
	{
		int y = 0;
		int x = 3;
		int xOffset = PLA.X_SEP;
		int yOffset = PLA.Y_SEP;
		int yMOffset = PLA.Y_MIR_SEP;

		Cell cell = Cell.makeInstance(ep, library, cellName);

		IO perFile = new IO();
		if (!perFile.readHeader(fileName))
		{
			System.out.println("Error reading height and width");
			return null;
		}
		int width = perFile.getWidth();
		int height = perFile.getHeight();
		int widthIn = perFile.getWidthIn();
		int heightIn = perFile.getHeightIn();

		cell.newVar("PLA_data_cols", new Integer(widthIn), ep);
		cell.newVar("PLA_access_rows", new Integer(heightIn), ep);
		cell.newVar("PLA_cols", new Integer(width), ep);
		cell.newVar("PLA_rows", new Integer(height), ep);

		// initialize the columns
		if (pmosInitColumns(width, x, y, xOffset, cell)) return null;

		// initialize the rows
		if (pmosInitRows(heightIn, x, y, yOffset, yMOffset, cell)) return null;

		y = 3 - yOffset;
		x = xOffset + 4;
		int row = 0;
		int readRows = 0;

		while (readRows < heightIn)
		{
			// read in the two rows first
			int [] row1 = null, row2 = null;
			if (readRows < heightIn)
			{
				row1 = perFile.readRow();
				if (row1 == null) return null;
				readRows++;
			}
			if (readRows < heightIn)
			{
				row2 = perFile.readRow();
				if (row2 == null) return null;
				readRows++;
			}

			for(int i = 0; i < width; i++)
			{
				// place a pmos_one cell and connect to power, and cell up above
				if (((i % 5) == 0) && (i != 0)) // put in power strap
				{
					if (pwrStrap(i, ((xOffset*i)+3), y, cell)) return null;
				} else
				{
					// put in bits -- make an instance of a pmos_one
					if ((row1[i] == 1) && (row1[0] != -1))
					{
						// make an instance of a pmos_one
						if (pmosMakeOne(i, ((i*xOffset)+6), y, 0, row, cell)) return null;
					}
					if ((row2[i] == 1) && (row2[0] != -1))
					{
						// make an instance of a pmos_one
						if (pmosMakeOne(i, ((i*xOffset)+6), y-yMOffset, 2, row, cell)) return null;
					}
				}
			}

			// Now put the poly metal contact at on the other side of the row
			if (pmosCompleteRow(row, ((xOffset*width)+0), y, cell)) return null;

			if ((readRows % 4) == 0) y -= yOffset;

			row++;
			y -= 2*yOffset;
		}
		x = 3;
		if ((readRows % 4) == 0) y += 2*yOffset-3; else
			y += yOffset-3;
		if (pmosFinishColumns(width, x, y, xOffset, cell)) return null;

		perFile.done();
		return cell;
	}

	private boolean pmosInitColumns(int width, int x, int y, int xOffset, Cell arrayCell)
	{
		int pwrCnt = 0;
		for (int i = 0; i < width; i++)
		{
			pla.columnList[i].firstItem = new PLA.UCItem();
			pla.columnList[i].lastitem =  pla.columnList[i].firstItem;

			String name;
			if ((i % 5) == 0)
			{
				// put in a Power pin every 5th position
				name = "PWR" + pwrCnt + ".m-1.n";
				pla.columnList[i].firstItem.nodeInst = pla.makePin(arrayCell, xOffset * i + x, y, 14, pla.msBut);
				if (pla.columnList[i].firstItem.nodeInst == null) return true;
				pwrCnt++;
			} else
			{
				// must be a data pin, don't count power pins
				name = "DATA" + (i - pwrCnt) + ".m-1.n";
				pla.columnList[i].firstItem.nodeInst = pla.makePin(arrayCell, xOffset * i + x, y, 4, pla.m1Pin);
				if (pla.columnList[i].firstItem.nodeInst == null) return true;
			}
			PortProto pp = pla.columnList[i].firstItem.nodeInst.getProto().getPort(0);
			PortInst pi = pla.columnList[i].firstItem.nodeInst.findPortInstFromProto(pp);
			Export.newInstance(arrayCell, pi, name, ep);
		}
		return false;
	}

	private boolean pmosInitRows(int heightIn, int x, int y, int yOffset, int yMOffset, Cell arrayCell)
	{
		int limit = (heightIn/2) + (heightIn % 2);

		for (int i = 0; i < limit; i++)
		{
			PLA.UCItem newItem = new PLA.UCItem();

			if (((i % 2) == 0) && (i != 0)) y -= yOffset;

			// put in Substrate contact first
			newItem.nodeInst = pla.makePin(arrayCell, x, y-yMOffset+10, 14, pla.msBut);
			if (newItem.nodeInst == null) return true;

			// wire in the first power strap
			// connect to last column object
			// only put this in a column list
			PortProto nodeColPort = newItem.nodeInst.getProto().getPort(0);
			PortProto lastColPort = pla.columnList[0].lastitem.nodeInst.getProto().getPort(0);
			pla.makeWire(pla.m1Arc, 4, pla.columnList[0].lastitem.nodeInst, lastColPort, newItem.nodeInst, nodeColPort, arrayCell);

			pla.columnList[0].lastitem.bottomItem = newItem;
			pla.columnList[0].lastitem = pla.columnList[0].lastitem.bottomItem;

			pla.rowList[i][0].firstItem = new PLA.UCItem();
			pla.rowList[i][0].lastitem =  pla.rowList[i][0].firstItem;
			pla.rowList[i][2].firstItem = new PLA.UCItem();
			pla.rowList[i][2].lastitem =  pla.rowList[i][2].firstItem;

			pla.rowList[i][0].firstItem.nodeInst = pla.makePin(arrayCell, x-7, y-5, 6, pla.mpCon);
			if (pla.rowList[i][0].firstItem.nodeInst == null) return true;
			pla.rowList[i][2].firstItem.nodeInst = pla.makePin(arrayCell, x-7, y-15, 6, pla.mpCon);
			if (pla.rowList[i][2].firstItem.nodeInst == null) return true;

			y -= 2*yOffset;
		}
		return false;
	}

	private boolean pwrStrap(int i, int x, int y, Cell arrayCell)
	{
		PLA.UCItem newItem = new PLA.UCItem();

		// put in Substrate contact first
		newItem.nodeInst = pla.makePin(arrayCell, x, y+7, 14, pla.msBut);
		if (newItem.nodeInst == null) return true;

		// wire in the first power strap and connect to last column object
		PortProto nodeColPort = newItem.nodeInst.getProto().getPort(0);
		PortProto lastColPort = pla.columnList[i].lastitem.nodeInst.getProto().getPort(0);

		pla.makeWire(pla.m1Arc, 4, pla.columnList[i].lastitem.nodeInst, lastColPort, newItem.nodeInst, nodeColPort, arrayCell);

		// only put this in a column list
		pla.columnList[i].lastitem.bottomItem = newItem;
		pla.columnList[i].lastitem = pla.columnList[i].lastitem.bottomItem;
		return false;
	}

	private boolean pmosMakeOne(int i, int x, int y, int rowPos, int row, Cell arrayCell)
	{
		NodeInst ni = pla.makeInstance(arrayCell, pla.pmosOne, x, y, false);
		if (ni == null) return true;

		// find ports that need to be connected and connect to last column object
		PortProto nodeColPort = ni.getProto().findPortProto("IN.m-1.n");
		PortProto lastColPort;
		if (pla.columnList[i].lastitem.nodeInst.getProto() != pla.pmosOne) // a Metal pin
			lastColPort = pla.columnList[i].firstItem.nodeInst.getProto().getPort(0); else
				lastColPort = pla.columnList[i].lastitem.nodeInst.getProto().findPortProto("OUT.m-1.s");

		pla.makeWire(pla.m1Arc, 4, pla.columnList[i].lastitem.nodeInst, lastColPort, ni, nodeColPort, arrayCell);

		// connect to last GATE object
		if (pla.rowList[row][rowPos].lastitem.nodeInst.getProto() != pla.pmosOne)
			lastColPort = pla.rowList[row][rowPos].lastitem.nodeInst.getProto().getPort(0); else
				lastColPort =  pla.rowList[row][rowPos].lastitem.nodeInst.getProto().findPortProto("GATE.p.e");
		nodeColPort = ni.getProto().findPortProto("GATE.p.w");

		pla.makeWire(pla.pArc, 0, pla.rowList[row][rowPos].lastitem.nodeInst, lastColPort, ni, nodeColPort, arrayCell);

		// put in column list
		pla.columnList[i].lastitem.bottomItem = new PLA.UCItem();
		pla.columnList[i].lastitem = pla.columnList[i].lastitem.bottomItem;
		pla.columnList[i].lastitem.nodeInst = ni;

		// put in row list
		pla.rowList[row][rowPos].lastitem.rightItem = pla.columnList[i].lastitem;
		pla.rowList[row][rowPos].lastitem = pla.rowList[row][rowPos].lastitem.rightItem;
		return false;
	}

	private boolean pmosCompleteRow(int row, int x, int y, Cell arrayCell)
	{
		PLA.UCItem newItem = new PLA.UCItem();
		newItem.nodeInst = pla.makePin(arrayCell, x, y+2, 6, pla.mpCon);
		if (newItem.nodeInst == null) return true;

		// connect to last GATE object
		PortProto lastColPort;
		if (pla.rowList[row][0].lastitem.nodeInst.getProto() != pla.pmosOne)
			lastColPort = pla.rowList[row][0].lastitem.nodeInst.getProto().getPort(0); else
				lastColPort = pla.rowList[row][0].lastitem.nodeInst.getProto().findPortProto("GATE.p.e");
		PortProto nodeColPort = newItem.nodeInst.getProto().getPort(0);
		pla.makeWire(pla.pArc, 0, pla.rowList[row][0].lastitem.nodeInst, lastColPort, newItem.nodeInst, nodeColPort, arrayCell);
		pla.rowList[row][0].lastitem = newItem;

		// Now export port at the beginning and end of this half of row grouping
		PortProto pp = pla.rowList[row][0].firstItem.nodeInst.getProto().getPort(0);
		PortInst pi = pla.rowList[row][0].firstItem.nodeInst.findPortInstFromProto(pp);
		Export.newInstance(arrayCell, pi, "ACCESS" + (row * 2) + ".m-1.w", ep);

		pp = pla.rowList[row][0].lastitem.nodeInst.getProto().getPort(0);
		pi = pla.rowList[row][0].lastitem.nodeInst.findPortInstFromProto(pp);
		Export.newInstance(arrayCell, pi, "ACCESS" + (row * 2) + ".m-1.e", ep);

		pp = pla.rowList[row][0].firstItem.nodeInst.getProto().getPort(0);
		pi = pla.rowList[row][0].firstItem.nodeInst.findPortInstFromProto(pp);
		Export.newInstance(arrayCell, pi, "ACCESS" + (row * 2) + ".p.w", ep);

		pp = pla.rowList[row][0].lastitem.nodeInst.getProto().getPort(0);
		pi = pla.rowList[row][0].lastitem.nodeInst.findPortInstFromProto(pp);
		Export.newInstance(arrayCell, pi, "ACCESS" + (row * 2) + ".p.e", ep);

		newItem = new PLA.UCItem();
		newItem.nodeInst = pla.makePin(arrayCell, x, y-8, 6, pla.mpCon);
		if (newItem.nodeInst == null) return true;

		// connect to last GATE object
		if (pla.rowList[row][2].lastitem.nodeInst.getProto() != pla.pmosOne)
			lastColPort = pla.rowList[row][2].lastitem.nodeInst.getProto().getPort(0); else
				lastColPort =  pla.rowList[row][2].lastitem.nodeInst.getProto().findPortProto("GATE.p.e");
		nodeColPort = newItem.nodeInst.getProto().getPort(0);
		pla.makeWire(pla.pArc, 0, pla.rowList[row][2].lastitem.nodeInst, lastColPort, newItem.nodeInst, nodeColPort, arrayCell);
		pla.rowList[row][2].lastitem = newItem;

		pp = pla.rowList[row][2].firstItem.nodeInst.getProto().getPort(0);
		pi = pla.rowList[row][2].firstItem.nodeInst.findPortInstFromProto(pp);
		Export.newInstance(arrayCell, pi, "ACCESS" + (row * 2 + 1) + ".m-1.w", ep);

		pp = pla.rowList[row][2].lastitem.nodeInst.getProto().getPort(0);
		pi = pla.rowList[row][2].lastitem.nodeInst.findPortInstFromProto(pp);
		Export.newInstance(arrayCell, pi, "ACCESS" + (row * 2 + 1) + ".m-1.e", ep);

		pp = pla.rowList[row][2].firstItem.nodeInst.getProto().getPort(0);
		pi = pla.rowList[row][2].firstItem.nodeInst.findPortInstFromProto(pp);
		Export.newInstance(arrayCell, pi, "ACCESS" + (row * 2 + 1) + ".p1.w", ep);

		pp = pla.rowList[row][2].lastitem.nodeInst.getProto().getPort(0);
		pi = pla.rowList[row][2].lastitem.nodeInst.findPortInstFromProto(pp);
		Export.newInstance(arrayCell, pi, "ACCESS" + (row * 2 + 1) + ".p.e", ep);
		return false;
	}

	private boolean pmosFinishColumns(int width, int x, int y, int xOffset, Cell arrayCell)
	{
		int pwrCnt = 0;
		for (int i = 0; i < width; i++)
		{
			PLA.UCItem newItem = new PLA.UCItem();

			String name;
			PortProto lastColPort;
			if ((i % 5) == 0)	// put in a power pin every 5th position
			{
				name = "PWR" + pwrCnt + ".m-1.s";
				newItem.nodeInst = pla.makePin(arrayCell, xOffset * i + x, y, 14, pla.msBut);
				if (newItem.nodeInst == null) return true;
				lastColPort = pla.columnList[i].lastitem.nodeInst.getProto().getPort(0);
				pwrCnt++;
			} else
			{
				// must be a data pin, don't count power pins
				name = "DATA" + (i - pwrCnt) + ".m-1.s";
				newItem.nodeInst = pla.makePin(arrayCell, xOffset * i + x, y, 4, pla.m1Pin);
				if (newItem.nodeInst == null) return true;
				if (pla.columnList[i].lastitem.nodeInst.getProto() != pla.pmosOne)
					lastColPort = pla.columnList[i].lastitem.nodeInst.getProto().getPort(0); else
						lastColPort = pla.columnList[i].lastitem.nodeInst.getProto().findPortProto("OUT.m-1.s");
			}

			// wire in the first power strap
			PortProto nodeColPort = newItem.nodeInst.getProto().getPort(0);

			// connect to last column object
			pla.makeWire(pla.m1Arc, 4, pla.columnList[i].lastitem.nodeInst, lastColPort, newItem.nodeInst, nodeColPort, arrayCell);

			// only put this in a column list
			pla.columnList[i].lastitem.bottomItem = newItem;
			pla.columnList[i].lastitem = pla.columnList[i].lastitem.bottomItem;

			PortInst pi = newItem.nodeInst.findPortInstFromProto(newItem.nodeInst.getProto().getPort(0));
			Export.newInstance(arrayCell, pi, name, ep);
		}
		return false;
	}
}
