From 4d1acc35ab350f395eea90bdf3e8fd9807e1c121 Mon Sep 17 00:00:00 2001 From: Jean-Paul Chaput Date: Sun, 2 Aug 2020 18:18:40 +0200 Subject: [PATCH] New: Reimplementation of the Cumulus Block plugin (in alpha). --- cumulus/src/CMakeLists.txt | 14 + cumulus/src/plugins/alpha/__init__.py | 0 cumulus/src/plugins/alpha/block/__init__.py | 0 cumulus/src/plugins/alpha/block/block.py | 565 ++++++++++++++++ cumulus/src/plugins/alpha/block/clocktree.py | 152 +++++ .../src/plugins/alpha/block/configuration.py | 630 ++++++++++++++++++ cumulus/src/plugins/alpha/block/spares.py | 481 +++++++++++++ cumulus/src/plugins/alpha/utils.py | 83 +++ cumulus/src/plugins/block.py | 70 ++ 9 files changed, 1995 insertions(+) create mode 100644 cumulus/src/plugins/alpha/__init__.py create mode 100644 cumulus/src/plugins/alpha/block/__init__.py create mode 100644 cumulus/src/plugins/alpha/block/block.py create mode 100644 cumulus/src/plugins/alpha/block/clocktree.py create mode 100644 cumulus/src/plugins/alpha/block/configuration.py create mode 100644 cumulus/src/plugins/alpha/block/spares.py create mode 100644 cumulus/src/plugins/alpha/utils.py create mode 100644 cumulus/src/plugins/block.py diff --git a/cumulus/src/CMakeLists.txt b/cumulus/src/CMakeLists.txt index 4e375131..99b672fa 100644 --- a/cumulus/src/CMakeLists.txt +++ b/cumulus/src/CMakeLists.txt @@ -13,10 +13,12 @@ ${CMAKE_CURRENT_SOURCE_DIR}/plugins/chiproute.py ${CMAKE_CURRENT_SOURCE_DIR}/plugins/conductor.py ${CMAKE_CURRENT_SOURCE_DIR}/plugins/matrixplacer.py + ${CMAKE_CURRENT_SOURCE_DIR}/plugins/block.py ${CMAKE_CURRENT_SOURCE_DIR}/plugins/rsave.py ${CMAKE_CURRENT_SOURCE_DIR}/plugins/rsaveall.py ${CMAKE_CURRENT_SOURCE_DIR}/plugins/s2r.py ${CMAKE_CURRENT_SOURCE_DIR}/plugins/aboutwindow.py + ${CMAKE_CURRENT_SOURCE_DIR}/plugins/stats.py ) #set ( pyPluginBlock ${CMAKE_CURRENT_SOURCE_DIR}/plugins/block/__init__.py # ${CMAKE_CURRENT_SOURCE_DIR}/plugins/block/vchannels.py @@ -40,6 +42,16 @@ set ( pyTools ${CMAKE_CURRENT_SOURCE_DIR}/tools/blif2vst.py ${CMAKE_CURRENT_SOURCE_DIR}/tools/yosys.py ) + set ( pyPluginAlpha ${CMAKE_CURRENT_SOURCE_DIR}/plugins/alpha/__init__.py + ${CMAKE_CURRENT_SOURCE_DIR}/plugins/alpha/utils.py + ) + set ( pyPluginBlock ${CMAKE_CURRENT_SOURCE_DIR}/plugins/alpha/__init__.py + ${CMAKE_CURRENT_SOURCE_DIR}/plugins/alpha/block/__init__.py + ${CMAKE_CURRENT_SOURCE_DIR}/plugins/alpha/block/configuration.py + ${CMAKE_CURRENT_SOURCE_DIR}/plugins/alpha/block/spares.py + ${CMAKE_CURRENT_SOURCE_DIR}/plugins/alpha/block/block.py + ${CMAKE_CURRENT_SOURCE_DIR}/plugins/alpha/block/clocktree.py + ) install ( FILES ${pySources} DESTINATION ${PYTHON_SITE_PACKAGES}/cumulus ) install ( FILES ${pyPlugins} DESTINATION ${PYTHON_SITE_PACKAGES}/cumulus/plugins ) @@ -47,4 +59,6 @@ install ( FILES ${pyPluginCTS} DESTINATION ${PYTHON_SITE_PACKAGES}/cumulus/plugins/cts ) install ( FILES ${pyPluginC2C} DESTINATION ${PYTHON_SITE_PACKAGES}/cumulus/plugins/core2chip ) install ( FILES ${pyPluginChip} DESTINATION ${PYTHON_SITE_PACKAGES}/cumulus/plugins/chip ) + install ( FILES ${pyPluginAlpha} DESTINATION ${PYTHON_SITE_PACKAGES}/cumulus/plugins/alpha ) + install ( FILES ${pyPluginBlock} DESTINATION ${PYTHON_SITE_PACKAGES}/cumulus/plugins/alpha/block ) #install ( PROGRAMS ${pyTools} DESTINATION bin ) diff --git a/cumulus/src/plugins/alpha/__init__.py b/cumulus/src/plugins/alpha/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cumulus/src/plugins/alpha/block/__init__.py b/cumulus/src/plugins/alpha/block/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cumulus/src/plugins/alpha/block/block.py b/cumulus/src/plugins/alpha/block/block.py new file mode 100644 index 00000000..ef651cba --- /dev/null +++ b/cumulus/src/plugins/alpha/block/block.py @@ -0,0 +1,565 @@ +# +# This file is part of the Coriolis Software. +# Copyright (c) SU 2020-2020, All Rights Reserved +# +# +-----------------------------------------------------------------+ +# | C O R I O L I S | +# | C u m u l u s - P y t h o n T o o l s | +# | | +# | Author : Jean-Paul CHAPUT | +# | E-mail : Jean-Paul.Chaput@lip6.fr | +# | =============================================================== | +# | Python : "./plugins/block/block.py" | +# +-----------------------------------------------------------------+ + + +from __future__ import print_function +import sys +import os.path +import Cfg +from Hurricane import Breakpoint +from Hurricane import DbU +from Hurricane import Box +from Hurricane import Transformation +from Hurricane import Point +from Hurricane import Box +from Hurricane import Path +from Hurricane import Layer +from Hurricane import Occurrence +from Hurricane import Net +from Hurricane import NetExternalComponents +from Hurricane import RoutingPad +from Hurricane import Pad +from Hurricane import Horizontal +from Hurricane import Vertical +from Hurricane import Contact +from Hurricane import Pin +from Hurricane import Plug +from Hurricane import Instance +import CRL +from CRL import RoutingLayerGauge +from helpers import trace +from helpers.io import ErrorMessage +from helpers.io import WarningMessage +from helpers.io import catch +from helpers.overlay import UpdateSession +import Etesian +import Anabatic +import Katana +import plugins.rsave +from plugins import getParameter +from alpha.block.spares import Spares +from alpha.block.clocktree import ClockTree +from alpha.block.configuration import IoPin +from alpha.block.configuration import BlockState + + +# ---------------------------------------------------------------------------- +# Class : "block.Side". + +class Side ( object ): + """ + Collate the informations about all the IoPin on a side of a block + and perform pins creation & placement. + """ + + def __init__ ( self, block, side ): + self.block = block + self.side = side + self.pinSpecs = [] + self.expandPins = True + self.pins = {} + + @property + def pinDirection ( self ): + """Translate the IoPin side into a Pin direction.""" + if self.side & IoPin.WEST: return Pin.Direction.WEST + elif self.side & IoPin.EAST: return Pin.Direction.EAST + elif self.side & IoPin.SOUTH: return Pin.Direction.SOUTH + else: return Pin.Direction.NORTH + + def setupAb ( self ): + """ + Initialise the side coordinate from the block abutmeent box. + Needless to say that it must be called *after* the abutment + box has been setup. + """ + if self.side & IoPin.WEST: + self.gauge = self.block.state.gaugeConf.hDeepRG + self.ubegin = self.block.state.yMin + self.uend = self.block.state.yMax + self.sidePos = self.block.state.xMin + elif self.side & IoPin.EAST: + self.gauge = self.block.state.gaugeConf.hDeepRG + self.ubegin = self.block.state.yMin + self.uend = self.block.state.yMax + self.sidePos = self.block.state.xMax + elif self.side & IoPin.SOUTH: + self.gauge = self.block.state.gaugeConf.vDeepRG + self.ubegin = self.block.state.xMin + self.uend = self.block.state.xMax + self.sidePos = self.block.state.yMin + elif self.side & IoPin.NORTH: + self.gauge = self.block.state.gaugeConf.vDeepRG + self.ubegin = self.block.state.xMin + self.uend = self.block.state.xMax + self.sidePos = self.block.state.yMax + + def getNextPinPosition ( self, flags, upos, ustep ): + """ + Return the point of the next available Pin position on + the side. + + :param flags: Whether to find the next position from the + begin of the side (A_BEGIN) or the end (A_END). + :param upos: Force the position to use, in that cas the + ``flags`` argument is ignored. To let the + function choose, must be set to zero. + :param ustep: The distance to space from the last created Pin. + """ + if not upos: + if not ustep: ustep = self.gauge.getPitch() + while True: + if flags & IoPin.A_BEGIN: + self.ubegin += ustep + pinOffset = self.ubegin + if not self.pins.has_key(self.ubegin): + break + else: + self.uend -= ustep + pinOffset = self.uend + if not self.pins.has_key(self.uend): + break + else: + pinOffset = upos + if (self.side & IoPin.WEST) or (self.side & IoPin.EAST): + return Point( self.sidePos, pinOffset ) + return Point( pinOffset, self.sidePos ) + + def append ( self, pin ): + """ + Append a newly created Pin to a map indexed by the position along + the side. Create a list for each entry. If everything goes well + each list will exacly one entry. + + This function is still too simple. It will consider two Pin of + different layers as overlapping. + """ + if (self.side & IoPin.WEST) or (self.side & IoPin.EAST): + upos = pin.getY() + else: + upos = pin.getX() + if not self.pins.has_key(upos): + self.pins[upos] = [ pin ] + else: + self.pins[upos].append( pin ) + + def place ( self, ioPin ): + """ + Performs the actual creation of the Pin from it's ioPin specification. + Should be called prior to any call to ```Etesian.place()```, so the Pin + is taken into account when building the RoutingPads. Returns the number + of *failed* pins, so zero means that all went well... Currently only + check for out of bounds coordinates. + """ + + status = 0 + if self.side & (IoPin.NORTH | IoPin.SOUTH): + gauge = self.block.state.gaugeConf.vDeepRG + upos = ioPin.upos + for index in ioPin.indexes: + pinName = ioPin.stem.format( index ) + net = self.block.state.cell.getNet( pinName ) + if net is None: + print( ErrorMessage( 1, [ 'Side.place(IoPin): No net named "{}".'.format(pinName) ] )) + continue + pinName += '.{}'.format(self.block.state.getIoPinsCounts(net)) + pinPos = self.getNextPinPosition( ioPin.flags, upos, ioPin.ustep ) + if pinPos.getX() > self.block.state.xMax or pinPos.getX() < self.block.state.xMin: + print( ErrorMessage( 1, [ 'Side.place(IoPin): Pin "{}" is outside north or south abutment box side.' \ + .format(pinName) + , '(x:{}, xAB: [{}:{}])' \ + .format( DbU.getValueString(pinPos.getX()) + , DbU.getValueString(self.block.state.xMin) + , DbU.getValueString(self.block.state.xMax) ) ] )) + status += 1 + trace( 550, '\tIoPin.place() N/S @{} "{}" of "{}".\n'.format(pinPos,pinName,net) ) + pin = Pin.create( net + , pinName + , self.pinDirection + , Pin.PlacementStatus.FIXED + , gauge.getLayer() + , pinPos.getX() + , pinPos.getY() + , gauge.getWireWidth() + , gauge.getWireWidth() / 2 + ) + NetExternalComponents.setExternal( pin ) + self.append( pin ) + self.block.state.incIoPinsCounts( net ) + if upos: upos += ioPin.ustep + else: + gauge = self.block.state.gaugeConf.hDeepRG + upos = ioPin.upos + for index in ioPin.indexes: + pinName = ioPin.stem.format(index) + net = self.block.state.cell.getNet( pinName ) + if net is None: + print( ErrorMessage( 1, [ 'Side.place(IoPin): No net named "{}".'.format(pinName) ] )) + continue + pinName += '.{}'.format(self.block.state.getIoPinsCounts(net)) + pinPos = self.getNextPinPosition( ioPin.flags, upos, ioPin.ustep ) + if pinPos.getY() > self.block.state.yMax or pinPos.getY() < self.block.state.yMin: + print( ErrorMessage( 1, [ 'Side.place(IoPin): Pin "{}" is outside east or west abutment box side.' \ + .format(pinName) + , '(y:{}, yAB: [{}:{}])' \ + .format( DbU.getValueString(pinPos.getY()) + , DbU.getValueString(block.state.yMin) + , DbU.getValueString(block.state.yMax)) ] )) + status += 1 + trace( 550, '\tIoPin.place() E/W @{} "{}" of "{}".\n'.format(pinPos,pinName,net) ) + pin = Pin.create( net + , pinName + , self.pinDirection + , Pin.PlacementStatus.FIXED + , gauge.getLayer() + , pinPos.getX() + , pinPos.getY() + , gauge.getWireWidth() / 2 + , gauge.getWireWidth() + ) + NetExternalComponents.setExternal( pin ) + self.append( pin ) + self.block.state.incIoPinsCounts( net ) + if upos: upos += ioPin.ustep + return status + + def expand ( self ): + """ + After, and only after routing the block, move the I/O pins *outside* + of the abutment box. THey will stick out for one pitch. + """ + if not self.expandPins: return + rg = self.block.state.gaugeConf.routingGauge + for pinsAtPos in self.pins.values(): + for pin in pinsAtPos: + for lg in rg.getLayerGauges(): + if lg.getLayer().getMask() == pin.getLayer().getMask(): + offset = lg.getPitch() + if self.side & IoPin.WEST: pin.setX( pin.getDx()-offset ) + elif self.side & IoPin.EAST: pin.setX( pin.getDx()+offset ) + elif self.side & IoPin.SOUTH: pin.setY( pin.getDy()-offset ) + elif self.side & IoPin.NORTH: pin.setY( pin.getDy()+offset ) + + def checkOverlaps ( self ): + """ + Check for Pin overlap. For now we are only checking that the are not at the + exact same position. We should check for neighboring overlap (Pin too close + from each other, not only exactly at the position). + """ + if self.side & IoPin.EAST: sideName = 'EAST' + elif self.side & IoPin.WEST: sideName = 'WEST' + elif self.side & IoPin.SOUTH: sideName = 'SOUTH' + elif self.side & IoPin.NORTH: sideName = 'NORTH' + for upos in self.pins.keys(): + count = len(self.pins[upos]) + if count > 1: + pinNames = self.pins[upos][0].getName() + for pin in self.pins[upos][1:]: + pinNames += ', ' + pin.getName() + print( ErrorMessage( 1, [ 'Side.checkOverlap(): On {} side of block "{}", {} pins ovelaps.' \ + .format(sideName,self.block.state.cell.getName(),count) + , '(@{}: {})' \ + .format(DbU.getValueString(upos),pinNames) ] ) ) + + +# ---------------------------------------------------------------------------- +# Class : "block.Block". + +class Block ( object ): + """ + The workhorse for turning a Cell into a hierarchically reusable placed and + routed block. Provide support for: + + * Clock tree. + """ + + LUT = {} + + @staticmethod + def lookup ( cell ): + if Block.LUT.has_key(cell): return Block.LUT[cell] + return None + + @staticmethod + def create ( cell, ioPins=[] ): + """Create a Block and it's configuration object.""" + block = Block( BlockState( cell, ioPins ) ) + Block.LUT[ cell ] = block + return block + + def __init__ ( self, state ): + """Not to be used directly, please see Block.create().""" + self.flags = 0 + self.state = state + self.spares = Spares( self ) + self.clockTrees = [] + self.blockInstances = [] + self.sides = { IoPin.WEST : Side( self, IoPin.WEST ) + , IoPin.EAST : Side( self, IoPin.EAST ) + , IoPin.SOUTH : Side( self, IoPin.SOUTH ) + , IoPin.NORTH : Side( self, IoPin.NORTH ) + } + if not self.state.cell.getAbutmentBox().isEmpty(): + self.state.cell.setTerminalNetlist( True ) + self.state.isBuilt = True + + def setUnexpandPins ( self, sides ): + """ + Prevent Pins from the selected sides to be stick out of one pitch. + This may be useful if you plan to route by abutment on a side. + """ + for side in (IoPin.WEST, IoPin.EAST, IoPin.SOUTH, IoPin.NORTH): + if sides & side: self.sides[side].expandPins = False + + def setupAb ( self ): + """ + Compute and set the Block's abutment box. Three cases: + 1. The AB is already set. assume the user as already set it by itself + so do nothing. + 2. The block has *routed* sub-blocks. Then the width and height of the + AB must be specified, and the placement of the sub-blocks, as the + placer cannot guess them. P&R block are placed and any unplaced + block has it's AB & position set to be the same as the one of the + top cell. + 3. Otherwise, let the placer (Etesian) compute the size accorging the + various configuration parameters (aspect ratio, space margin, fixed + height or width, ...). + """ + if not self.state.cell.getAbutmentBox().isEmpty(): return + if len(self.blockInstances): + with UpdateSession(): + ab = Box( 0, 0, self.state.fixedWidth, self.state.fixedHeight ) + self.state.cell.setAbutmentBox( ab ) + for occurrence in self.state.cell.getNonTerminalNetlistInstanceOccurrences(): + instance = occurrence.getEntity() + subCell = instance.getMasterCell() + subCell.setAbutmentBox( ab ) + for occurrence in self.state.cell.getNonTerminalNetlistInstanceOccurrences(): + instance = occurrence.getEntity() + instance.setTransformation( Transformation() ) + for blockInstance in self.blockInstances: + blockInstance.place() + else: + sysSpaceMargin = self.state.cfg.etesian.spaceMargin + blockSpaceMargin = sysSpaceMargin + self.spares.getSpareSpaceMargin() + self.state.cfg.etesian.spaceMargin = blockSpaceMargin + self.state.cfg.apply() + with UpdateSession(): + etesian = Etesian.EtesianEngine.create( self.state.cell ) + if self.state.fixedWidth: etesian.setFixedAbWidth ( self.state.fixedWidth ) + if self.state.fixedHeight: etesian.setFixedAbHeight( self.state.fixedHeight ) + etesian.setDefaultAb() + etesian.destroy() + self.state.cfg.etesian.spaceMargin = sysSpaceMargin + self.state.cfg.apply() + for side in self.sides.values(): side.setupAb() + + def addClockTree ( self, clockNet=None ): + """Create the trunk of the clock tree (recursive H-Tree).""" + if not clockNet: + for net in self.state.cell.getNets(): + if net.isClock(): + clockNet = net + break + if not clockNet: + raise ErrorMessage( 3, 'Block.clockTree(): Cell "{}" has no clock net.'.format(self.state.cell.getName()) ) + with UpdateSession(): + self.clockTrees.append( ClockTree(self.spares,clockNet) ) + self.clockTrees[-1].buildHTree() + return self.clockTrees[-1] + + def splitClocks ( self ): + """ + Break the clock net and attach all it's Pins to the closest leaf + if the H-Tree. + """ + for clockTree in self.clockTrees: + clockTree.splitClock() + + def placeIoPins ( self ): + """ + Place the Pins on all the sides. Raise an exception in case of failure. + (mainly due to Pins outside the side range) + """ + faileds = 0 + with UpdateSession(): + for ioPin in self.state.ioPins: + if ioPin.flags & IoPin.SOUTH: side = self.sides[IoPin.SOUTH] + elif ioPin.flags & IoPin.NORTH: side = self.sides[IoPin.NORTH] + elif ioPin.flags & IoPin.EAST: side = self.sides[IoPin.EAST ] + else: side = self.sides[IoPin.WEST ] + faileds += side.place( ioPin ) + if faileds: + raise ErrorMessage( 3, 'Block.placeIoPins(): Cell "{}" has {} badly placed pins.' \ + .format(self.state.cell.getName(),faileds) ) + + def checkIoPins ( self ): + """ + Check for Pin overlap and that all external Net have at least one Pin. + """ + for side in self.sides.values(): + side.checkOverlaps() + for net in self.state.cell.getNets(): + if not net.isExternal(): continue + if net.isSupply(): continue + hasPins = False + for pin in net.getPins(): + hasPins = True + break + if not hasPins: + print( ErrorMessage( 1, 'Block.checkIoPins() External net "{}" has no pin(s).' \ + .format(net.getName() ))) + + def expandIoPins ( self ): + with UpdateSession(): + for side in self.sides.values(): + side.expand() + + def place ( self ): + etesian = Etesian.EtesianEngine.create( self.state.cell ) + etesian.place() + etesian.destroy() + + def route ( self ): + katana = Katana.KatanaEngine.create( self.state.cell ) + #katana.printConfiguration () + katana.digitalInit () + #katana.runNegociatePreRouted() + katana.runGlobalRouter ( Katana.Flags.NoFlags ) + katana.loadGlobalRouting ( Anabatic.EngineLoadGrByNet ) + katana.layerAssign ( Anabatic.EngineNoNetLayerAssign ) + katana.runNegociate ( Katana.Flags.NoFlags ) + success = katana.isDetailedRoutingSuccess() + #Breakpoint.stop( 0, 'Block.route() done, success:{}.'.format(success) ) + katana.finalizeLayout() + katana.destroy() + return success + + def addBlockages ( self ): + with UpdateSession(): + net = self.state.cell.getNet( 'blockagenet' ) + ab = self.state.cell.getAbutmentBox() + rg = self.state.gaugeConf.routingGauge + for lg in rg.getLayerGauges(): + if lg.getType() == RoutingLayerGauge.PinOnly: continue + blockage = lg.getBlockageLayer() + if not blockage: continue + if lg.getDirection() == RoutingLayerGauge.Horizontal: + dxBorder = lg.getPitch() + lg.getWireWidth() + Horizontal.create( net + , blockage + , ab.getCenter().getY() + , ab.getHeight() + , ab.getXMin() + dxBorder + , ab.getXMax() - dxBorder + ) + else: + dyBorder = lg.getPitch() + lg.getWireWidth() + Vertical.create( net + , blockage + , ab.getCenter().getX() + , ab.getWidth() + , ab.getYMin() + dyBorder + , ab.getYMax() - dyBorder + ) + + def build ( self ): + """ + Perform all the steps required to build the layout of the block. + The first step is to build all the blockInstance it depends upon, + so they will appear as ``NetListTerminal`` and we can place them + in their parent cell. + """ + for blockInstance in self.blockInstances: + blockInstance.block.editor = self.state.editor + if not blockInstance.block.state.isBuilt: + blockInstance.block.build() + editor = self.state.editor + if editor: editor.setCell( self.state.cell ) + self.state.cfg.apply() + self.setupAb() + self.placeIoPins() + self.checkIoPins() + self.spares.build() + if editor: editor.fit() + if self.state.useClockTree: self.addClockTree() + self.place() + if self.state.useClockTree: self.splitClocks() + status = self.route() + self.addBlockages() + self.expandIoPins() + self.state.isBuilt = True + plugins.rsave.rsave( self.state.cell ) + return status + + def useBlockInstance ( self, instancePathName , transf ): + """ + Make a sub-block known to the top block. Can be trans-hierarchical. + + :param instancePathName: A string of instances names, separated by + dots ('.') pinpointing the instance of the + block. + :param transf: The position to place the instance of the + sub-block. + + Prior to calling this function, a block *must* have been created for + the master cell of the instance. But it need not to have been placed + and routed. + """ + iNames = instancePathName.split('.') + path = Path() + for iName in iNames: + if path.isEmpty(): parentCell = self.state.cell + else: parentCell = path.getTailInstance().getMasterCell() + instance = parentCell.getInstance( iName ) + if not instance: + raise ErrorMessage( 1, [ 'Block.useBlockInstance(): Cell "{}" has no instance "{}".' \ + .format(parentCell.getName(),iName) + , '(in path:"{})"' \ + .format(instancePathName) ] ) + path = Path( path, instance ) + + tailInstance = path.getTailInstance() + for blockIns in self.blockInstances: + if blockIns.instance == tailInstance: + print( ErrorMessage('Block.useBlockInstance(): In Cell "{}", duplicate call for "{}"' \ + .format(self.cell.getName(),tailInstance.getName()) )) + return + blockIns = BlockInstance( tailInstance, transf ) + self.blockInstances.append( blockIns ) + + +# ---------------------------------------------------------------------------- +# Class : "blockinstance.BlockInstance". + +class BlockInstance ( object ): + """ + Light helper class to hold a sub-block information. + """ + + def __init__ ( self, instance, transf ): + self.transf = transf + self.instance = instance + self.block = Block.lookup( instance.getMasterCell() ) + if not self.block: + raise ErrorMessage( 1, [ 'BlockInstance.__init__(): Instance "{}" of cell "{}" has no Block defined.' \ + .format( instance.getName(), instance.getMasterCell().getName() ) + ] ) + return + + def place ( self ): + self.instance.setTransformation( self.transf ) + self.instance.setPlacementStatus( Instance.PlacementStatus.PLACED ) + return + diff --git a/cumulus/src/plugins/alpha/block/clocktree.py b/cumulus/src/plugins/alpha/block/clocktree.py new file mode 100644 index 00000000..31f5249e --- /dev/null +++ b/cumulus/src/plugins/alpha/block/clocktree.py @@ -0,0 +1,152 @@ +# +# This file is part of the Coriolis Software. +# Copyright (c) SU 2020-2020, All Rights Reserved +# +# +-----------------------------------------------------------------+ +# | C O R I O L I S | +# | C u m u l u s - P y t h o n T o o l s | +# | | +# | Author : Jean-Paul CHAPUT | +# | E-mail : Jean-Paul.Chaput@lip6.fr | +# | =============================================================== | +# | Python : "./plugins/block/clocktree.py" | +# +-----------------------------------------------------------------+ + + +from __future__ import print_function +import sys +import os.path +import Cfg +from Hurricane import Breakpoint +from Hurricane import DbU +from Hurricane import Box +from Hurricane import Transformation +from Hurricane import Box +from Hurricane import Path +from Hurricane import Layer +from Hurricane import Occurrence +from Hurricane import Net +from Hurricane import HyperNet +from Hurricane import RoutingPad +from Hurricane import Horizontal +from Hurricane import Vertical +from Hurricane import Contact +from Hurricane import Pin +from Hurricane import Plug +from Hurricane import Instance +import CRL +from CRL import RoutingLayerGauge +from helpers import trace +from helpers.io import ErrorMessage +from helpers.io import WarningMessage +from helpers.io import catch +from helpers.overlay import UpdateSession +from plugins import getParameter +from plugins import utils +from plugins.alpha.block.configuration import GaugeConf + + +# ---------------------------------------------------------------------------- +# Class : "clocktree.ClockTree". + +class ClockTree ( object ): + """ + Build a clock tree on a block. + """ + + def __init__ ( self, spares, clockNet ): + self.spares = spares + self.clockNet = clockNet + + print( WarningMessage('ClockTree.__init__(): Net "{}" is not of CLOCK type.' \ + .format(self.clockNet.getName())) ) + + def _rconnectHTree ( self, quadTree ): + if quadTree.isLeaf(): return False + driverNet = quadTree.bOutputPlug.getNet() + for leaf in quadTree.leafs: + leaf.bInputPlug.setNet( driverNet ) + self._rconnectHTree( leaf ) + return True + + def _rrouteHTree ( self, quadT ): + """ + Recursively build one HTree branch for all non-terminal nodes of the QuadTree. + """ + trace( 550, ',+', '\tClockTree._rrouteHTree() {}\n'.format(quadT.bOutputPlug.getNet()) ) + if quadT.isLeaf(): + trace( 550, '-' ) + return False + + gaugeConf = self.spares.state.gaugeConf + bufferConf = self.spares.state.bufferConf + ckNet = quadT.bOutputPlug.getNet() + + leftSourceContact = gaugeConf.rpAccessByPlugName( quadT.buffer , bufferConf.output, ckNet , GaugeConf.HAccess|GaugeConf.OffsetBottom1 ) + rightSourceContact = gaugeConf.rpAccessByPlugName( quadT.buffer , bufferConf.output, ckNet , GaugeConf.HAccess|GaugeConf.OffsetBottom1 ) + blContact = gaugeConf.rpAccessByPlugName( quadT.bl.buffer, bufferConf.input , ckNet ) + brContact = gaugeConf.rpAccessByPlugName( quadT.br.buffer, bufferConf.input , ckNet ) + tlContact = gaugeConf.rpAccessByPlugName( quadT.tl.buffer, bufferConf.input , ckNet ) + trContact = gaugeConf.rpAccessByPlugName( quadT.tr.buffer, bufferConf.input , ckNet ) + leftContact = gaugeConf.createContact( ckNet, blContact.getX(), leftSourceContact.getY(), 0 ) + rightContact = gaugeConf.createContact( ckNet, brContact.getX(), rightSourceContact.getY(), 0 ) + + leftSourceX = gaugeConf.getNearestVerticalTrack ( quadT.root.area, leftSourceContact.getX(), 0 ) + leftSourceY = gaugeConf.getNearestHorizontalTrack( quadT.root.area, leftSourceContact.getY(), 0 ) + rightSourceX = gaugeConf.getNearestVerticalTrack ( quadT.root.area, rightSourceContact.getX(), 0 ) + rightSourceY = gaugeConf.getNearestHorizontalTrack( quadT.root.area, rightSourceContact.getY(), 0 ) + leftX = gaugeConf.getNearestVerticalTrack ( quadT.root.area, leftContact.getX(), 0 ) + rightX = gaugeConf.getNearestVerticalTrack ( quadT.root.area, rightContact.getX(), 0 ) + tlY = gaugeConf.getNearestHorizontalTrack( quadT.root.area, tlContact.getY(), 0 ) + blY = gaugeConf.getNearestHorizontalTrack( quadT.root.area, blContact.getY(), 0 ) + + gaugeConf.setStackPosition( leftSourceContact, leftSourceX, leftSourceY ) + gaugeConf.setStackPosition( rightSourceContact, rightSourceX, rightSourceY ) + gaugeConf.setStackPosition( tlContact, leftX, tlY ) + gaugeConf.setStackPosition( blContact, leftX, blY ) + gaugeConf.setStackPosition( trContact, rightX, tlY ) + gaugeConf.setStackPosition( brContact, rightX, blY ) + + leftContact .setX( leftX ) + leftContact .setY( leftSourceY ) + rightContact.setX( rightX ) + rightContact.setY( rightSourceY ) + + gaugeConf.createHorizontal( leftContact , leftSourceContact, leftSourceY , 0 ) + gaugeConf.createHorizontal( rightSourceContact, rightContact , rightSourceY, 0 ) + gaugeConf.createVertical ( leftContact , blContact , leftX , 0 ) + gaugeConf.createVertical ( tlContact , leftContact , leftX , 0 ) + gaugeConf.createVertical ( rightContact , brContact , rightX , 0 ) + gaugeConf.createVertical ( trContact , rightContact , rightX , 0 ) + + for leaf in quadT.leafs: + self._rrouteHTree( leaf ) + trace( 550, '-' ) + return True + + def buildHTree ( self ): + """ + Create the clock tree netlist in two steps: + 1. Connect the buffers of the spares QuadTree to form a H-Tree. + 2. Detach the all the clock sink point and reconnect them to the + buffers of the leafs of the QuadTree. + """ + quadTree = self.spares.quadTree + quadTree.bufferTag = self.clockNet.getName() + quadTree.ruseBuffer() + with UpdateSession(): + self._rconnectHTree( quadTree ) + self._rrouteHTree ( quadTree ) + + def splitClock ( self ): + """ + Disconnect the registers from the main clock and reconnect them to + the leaf buffers of the clock tree. + """ + quadTree = self.spares.quadTree + quadTree.bufferTag = self.clockNet.getName() + + hyperClock = HyperNet.create( Occurrence(self.clockNet) ) + for plugOccurrence in hyperClock.getTerminalNetlistPlugOccurrences(): + quadTree.attachToLeaf( plugOccurrence ) + quadTree.rsplitNetlist() diff --git a/cumulus/src/plugins/alpha/block/configuration.py b/cumulus/src/plugins/alpha/block/configuration.py new file mode 100644 index 00000000..1138800e --- /dev/null +++ b/cumulus/src/plugins/alpha/block/configuration.py @@ -0,0 +1,630 @@ +# +# This file is part of the Coriolis Software. +# Copyright (c) SU 2020-2020, All Rights Reserved +# +# +-----------------------------------------------------------------+ +# | C O R I O L I S | +# | C u m u l u s - P y t h o n T o o l s | +# | | +# | Author : Jean-Paul CHAPUT | +# | E-mail : Jean-Paul.Chaput@lip6.fr | +# | =============================================================== | +# | Python : "./plugins/block/configuration.py" | +# +-----------------------------------------------------------------+ + + +import sys +import os.path +import Cfg +from Hurricane import Breakpoint +from Hurricane import DbU +from Hurricane import Box +from Hurricane import Transformation +from Hurricane import Box +from Hurricane import Path +from Hurricane import Layer +from Hurricane import Occurrence +from Hurricane import Net +from Hurricane import NetExternalComponents +from Hurricane import RoutingPad +from Hurricane import Horizontal +from Hurricane import Vertical +from Hurricane import Contact +from Hurricane import Pin +from Hurricane import Plug +from Hurricane import Instance +import CRL +from CRL import RoutingLayerGauge +from helpers import trace, l, u, n +from helpers.io import ErrorMessage +from helpers.io import WarningMessage +from helpers.io import catch +from helpers.overlay import CfgCache +from plugins import getParameter +from plugins.alpha.utils import getPlugByName + + +# ---------------------------------------------------------------------------- +# Class : "configuration.GaugeConf". + +class GaugeConf ( object ): + """ + Wrapper around the Cell and I/O pads RoutingGauge, with additional + services. + + The Cell routing gauge is read from the configuration parameter + ``anabatic.routingGauge``. + """ + + HAccess = 0x0001 + OffsetRight1 = 0x0002 + OffsetTop1 = 0x0004 + OffsetBottom1 = 0x0008 + DeepDepth = 0x0010 + UseContactWidth = 0x0020 + ExpandWidth = 0x0040 + + def __init__ ( self ): + self._cellGauge = None + self._ioPadGauge = None + self._routingGauge = None + self._topLayerDepth = 0 + self._plugToRp = { } + self._rpToAccess = { } + self._loadRoutingGauge() + return + + @property + def routingGauge ( self ): return self._routingGauge + + @property + def sliceHeight ( self ): return self._cellGauge.getSliceHeight() + + @property + def sliceStep ( self ): return self._cellGauge.getSliceStep() + + @property + def ioPadHeight ( self ): return self._ioPadGauge.getSliceHeight() + + @property + def ioPadStep ( self ): return self._ioPadGauge.getSliceStep() + + @property + def ioPadPitch ( self ): return self._ioPadGauge.getPitch() + + @property + def ioPadGauge ( self ): return self._ioPadGauge + + @property + def hRoutingGauge ( self ): return self._routingGauge.getLayerGauge( self.horizontalDepth ) + + @property + def vRoutingGauge ( self ): return self._routingGauge.getLayerGauge( self.verticalDepth ) + + @property + def hDeepRG ( self ): return self._routingGauge.getLayerGauge( self.horizontalDeepDepth ) + + @property + def vDeepRG ( self ): return self._routingGauge.getLayerGauge( self.verticalDeepDepth ) + + def getPitch ( self, layer ): return self._routingGauge.getPitch( layer ) + + def _loadRoutingGauge ( self ): + trace( 550, ',+', '\tGaugeConf._loadRoutingGauge()\n' ) + gaugeName = Cfg.getParamString('anabatic.routingGauge').asString() + self._cellGauge = CRL.AllianceFramework.get().getCellGauge( gaugeName ) + self._routingGauge = CRL.AllianceFramework.get().getRoutingGauge( gaugeName ) + + if not self._routingGauge: + trace( 500, '-' ) + raise ErrorMessage( 1, [ 'RoutingGauge._loadRoutingGauge(): No routing gauge named "{}".'.format(gaugeName) + , 'Please check the "anabatic.routingGauge" configuration parameter." ' ]) + + topLayer = Cfg.getParamString('anabatic.topRoutingLayer').asString() + + self.topLayerDepth = 0 + for layerGauge in self._routingGauge.getLayerGauges(): + if layerGauge.getLayer().getName() == topLayer: + self.topLayerDepth = layerGauge.getDepth() + break + if not self.topLayerDepth: + print WarningMessage( 'Gauge top layer not defined, using top of gauge ({}).' \ + .format(self._routingGauge.getDepth()) ) + self._topLayerDepth = self._routingGauge.getDepth() - 1 + + self.horizontalDepth = -1 + self.verticalDepth = -1 + self.horizontalDeepDepth = -1 + self.verticalDeepDepth = -1 + for depth in range(0,self.topLayerDepth+1): + trace( 550, '\tdepth:{} {}\n'.format(depth,self._routingGauge.getLayerGauge(depth) )) + + if self._routingGauge.getLayerGauge(depth).getType() == RoutingLayerGauge.PinOnly: + continue + if self._routingGauge.getLayerGauge(depth).getDirection() == RoutingLayerGauge.Horizontal: + if self.horizontalDeepDepth < 0: + self.horizontalDeepDepth = depth + self.horizontalDepth = depth + if self._routingGauge.getLayerGauge(depth).getDirection() == RoutingLayerGauge.Vertical: + if self.verticalDeepDepth < 0: + self.verticalDeepDepth = depth + self.verticalDepth = depth + trace( 550, '\t| horizontalDepth :{}\n'.format(self.horizontalDepth) ) + trace( 550, '\t| verticalDepth :{}\n'.format(self.verticalDepth) ) + trace( 550, '\t| horizontalDeepDepth:{}\n'.format(self.horizontalDeepDepth) ) + trace( 550, '\t| verticalDeepDepth :{}\n'.format(self.verticalDeepDepth) ) + trace( 500, '-' ) + return + + def _loadIoPadGauge ( self, ioPadGaugeName ): + self._ioPadGauge = CRL.AllianceFramework.get().getCellGauge( ioPadGaugeName ) + if not self._ioPadGauge: + print WarningMessage( 'IO pad gauge "%s" not found.' % ioPadGaugeName ) + return + + def isHorizontal ( self, layer ): + mask = layer.getMask() + for lg in self._routingGauge.getLayerGauges(): + if lg.getLayer().getMask() == mask: + if lg.getDirection() == RoutingLayerGauge.Horizontal: return True + return False + + print ErrorMessage( 1, 'GaugeConf.isHorizontal(): Layer "%s" is not part of gauge "%s", cannot know preferred direction.' \ + % (layer.getName(), self._routingGauge.getName()) ) + return False + + def isVertical ( self, layer ): return not self.isHorizontal( layer ) + + def createContact ( self, net, x, y, flags ): + if flags & GaugeConf.DeepDepth: depth = self.horizontalDeepDepth + else: depth = self.horizontalDepth + + if self.horizontalDepth > self.verticalDepth: depth -= 1 + + trace( 550, '\t%s, horizontalDepth:%d, gaugeDepth:%d\n' + % (self._routingGauge,self.horizontalDepth,self._routingGauge.getDepth())) + return Contact.create( net + , self._routingGauge.getContactLayer(depth) + , x, y + , self._routingGauge.getLayerGauge(depth).getViaWidth() + , self._routingGauge.getLayerGauge(depth).getViaWidth() + ) + + def getNearestHorizontalTrack ( self, bb, y, flags ): + if flags & GaugeConf.DeepDepth: depth = self.horizontalDeepDepth + else: depth = self.horizontalDepth + + index = self._routingGauge.getLayerGauge(depth).getTrackIndex( bb.getYMin() + , bb.getYMax() + , y + , RoutingLayerGauge.Nearest ) + return self._routingGauge.getLayerGauge(depth).getTrackPosition( bb.getYMin(), index ) + + def getNearestVerticalTrack ( self, bb, x, flags ): + if flags & GaugeConf.DeepDepth: depth = self.verticalDeepDepth + else: depth = self.verticalDepth + + index = self._routingGauge.getLayerGauge(depth).getTrackIndex( bb.getXMin() + , bb.getXMax() + , x + , RoutingLayerGauge.Nearest ) + return self._routingGauge.getLayerGauge(depth).getTrackPosition( bb.getXMin(), index ) + + def createHorizontal ( self, source, target, y, flags ): + if flags & GaugeConf.DeepDepth: depth = self.horizontalDeepDepth + else: depth = self.horizontalDepth + + layer = self._routingGauge.getRoutingLayer(depth) + + if flags & GaugeConf.UseContactWidth: + width = source.getBoundingBox(layer.getBasicLayer()).getHeight() + else: + width = self._routingGauge.getLayerGauge(depth).getWireWidth() + if flags & GaugeConf.ExpandWidth: + width += DbU.fromLambda( 1.0 ) + + segment = Horizontal.create( source, target, layer, y, width ) + trace( 550, segment ) + return segment + + def createVertical ( self, source, target, x, flags ): + if flags & GaugeConf.DeepDepth: depth = self.verticalDeepDepth + else: depth = self.verticalDepth + + layer = self._routingGauge.getRoutingLayer(depth) + + if flags & GaugeConf.UseContactWidth: + width = source.getBoundingBox(layer.getBasicLayer()).getWidth() + else: + width = self._routingGauge.getLayerGauge(depth).getWireWidth() + if flags & GaugeConf.ExpandWidth: + width += DbU.fromLambda( 1.0 ) + + segment = Vertical.create( source, target, layer, x, width ) + trace( 550, segment ) + return segment + + def rpAccess ( self, rp, flags ): + trace( 550, ',+', '\t_rpAccess() %s\n' % str(rp) ) + + if self._rpToAccess.has_key(rp): + trace( 550, '-' ) + return self._rpToAccess[rp] + + if flags & GaugeConf.DeepDepth: + hdepth = self.horizontalDeepDepth + vdepth = self.verticalDeepDepth + else: + hdepth = self.horizontalDepth + vdepth = self.verticalDepth + + hpitch = self._routingGauge.getLayerGauge(hdepth).getPitch() + hoffset = self._routingGauge.getLayerGauge(hdepth).getOffset() + contact1 = Contact.create( rp, self._routingGauge.getContactLayer(0), 0, 0 ) + midSliceY = contact1.getY() - (contact1.getY() % self._cellGauge.getSliceHeight()) \ + + self._cellGauge.getSliceHeight() / 2 + midTrackY = midSliceY - ((midSliceY - hoffset) % hpitch) + dy = midSliceY - contact1.getY() + + if flags & GaugeConf.OffsetBottom1: dy += hpitch + if flags & GaugeConf.OffsetTop1: dy -= hpitch + contact1.setDy( dy ) + + trace( 550, contact1 ) + + if flags & GaugeConf.HAccess: stopDepth = hdepth + else: stopDepth = vdepth + trace( 550, '\tstopDepth:%d\n' % stopDepth ) + + for depth in range(1,stopDepth): + xoffset = 0 + if flags & GaugeConf.OffsetRight1 and depth == 1: + xoffset = self._routingGauge.getLayerGauge(depth+1).getPitch() + contact2 = Contact.create( rp.getNet() + , self._routingGauge.getContactLayer(depth) + , contact1.getX() + xoffset + , contact1.getY() + , self._routingGauge.getLayerGauge(depth).getViaWidth() + , self._routingGauge.getLayerGauge(depth).getViaWidth() + ) + trace( 550, contact2 ) + if self._routingGauge.getLayerGauge(depth).getDirection() == RoutingLayerGauge.Horizontal: + segment = Horizontal.create( contact1 + , contact2 + , self._routingGauge.getRoutingLayer(depth) + , contact1.getY() + , self._routingGauge.getLayerGauge(depth).getWireWidth() + ) + trace( 550, segment ) + else: + segment = Vertical.create( contact1 + , contact2 + , self._routingGauge.getRoutingLayer(depth) + , contact1.getX() + , self._routingGauge.getLayerGauge(depth).getWireWidth() + ) + trace( 550, segment ) + contact1 = contact2 + + self._rpToAccess[rp] = contact1 + + trace( 550, '-' ) + return contact1 + + def rpByOccurrence ( self, occurrence, net ): + plug = occurrence.getEntity() + if self._plugToRp.has_key(plug): + rp = self._plugToRp[plug] + else: + rp = RoutingPad.create( net, occurrence, RoutingPad.BiggestArea ) + self._plugToRp[plug] = rp + return rp + + def rpAccessByOccurrence ( self, occurrence, net, flags ): + plug = occurrence.getEntity() + if self._plugToRp.has_key(plug): + rp = self._plugToRp[plug] + else: + rp = RoutingPad.create( net, occurrence, RoutingPad.BiggestArea ) + self._plugToRp[plug] = rp + return self.rpAccess( self._rpByOccurrence(occurrence,net), flags ) + + def rpByPlug ( self, plug, net ): + if self._plugToRp.has_key(plug): + rp = self._plugToRp[plug] + else: + occurrence = Occurrence( plug, Path(net.getCell(),'') ) + rp = RoutingPad.create( net, occurrence, RoutingPad.BiggestArea ) + self._plugToRp[plug] = rp + return rp + + def rpByPlugName ( self, instance, plugName, net ): + return self.rpByPlug( getPlugByName(instance,plugName), net ) + + def rpAccessByPlug ( self, plug, net, flags ): + return self.rpAccess( self.rpByPlug(plug,net), flags ) + + def rpAccessByPlugName ( self, instance, plugName, net, flags=0 ): + return self.rpAccess( self.rpByPlugName(instance,plugName,net), flags ) + + def setStackPosition ( self, topContact, x, y ): + topContact.setX( x ) + topContact.setY( y ) + + count = 0 + for component in topContact.getSlaveComponents(): + segment = component + count += 1 + if count > 1: + raise ErrorMessage( 1, 'GaugeConf::_setStackPosition(): There must be exactly one segment connected to %s, not %d.' % (topContact,count) ) + + if count == 1: + if isinstance(segment,Horizontal): + segment.setY( y ) + segment.getOppositeAnchor( topContact ).setY( y ) + elif isinstance(segment,Vertical): + segment.setX( x ) + segment.getOppositeAnchor( topContact ).setX( x ) + return + + +# ---------------------------------------------------------------------------- +# Class : "configuration.BufferInterface". + +class BufferInterface ( object ): + + def __init__ ( self, framework ): + trace( 550, ',+', '\tBufferInterface.__init__()\n' ) + self.masterCell = framework.getCell( Cfg.getParamString('clockTree.buffer').asString() + , CRL.Catalog.State.Views ) + if not self.masterCell: + trace( 550, '-' ) + raise ErrorMessage( 3, [ 'ClockTree: Buffer cell "{}" not found in library,' \ + .format(Cfg.getParamString('clockTree.buffer').asString()) + , ' please check the "clockTree.buffer" configuration parameter in "plugins.conf".' ] ) + trace( 550, '\tmasterCell :<{}>\n'.format(self.masterCell) ) + + self.count = 0 + self.input = None + self.output = None + for net in self.masterCell.getNets(): + if not net.isExternal(): continue + if net.isGlobal(): continue + if net.getDirection() & Net.Direction.IN: self.input = net.getName() + elif net.getDirection() & Net.Direction.OUT: self.output = net.getName() + + trace( 550, '\tinput :<{}>\n'.format(self.input ) ) + trace( 550, '\toutput:<{}>\n'.format(self.output) ) + trace( 550, '-' ) + return + + @property + def width ( self ): return self.masterCell.getAbutmentBox().getWidth() + + @property + def height ( self ): return self.masterCell.getAbutmentBox().getHeight() + + def createBuffer ( self, cell ): + instance = Instance.create( cell, 'spare_buffer_{}'.format(self.count), self.masterCell ) + self.count += 1 + return instance + + +# ---------------------------------------------------------------------------- +# Class : "configuration.IoPin". + +class IoPin ( object ): + """ + Create an I/O Pin on a side of a block for one net or a vector of nets. + """ + + SOUTH = 0x0001 + NORTH = 0x0002 + EAST = 0x0004 + WEST = 0x0008 + A_BEGIN = 0x0010 + A_END = 0x0020 + A_MASK = A_BEGIN|A_END + + def __init__ ( self, flags, stem, upos, ustep=0, count=1 ): + """ + Create an I/O Pin(s) on the abutment box of a block. Could be for one + net or a vector of net. + + :param flags: On which side the pin is to be put (SOUTH, NORTH, EAST or WEST). + :param stem: A string giving the name of the net. In case of a vector, it + must be a string containing exactly one '{}' as placeholder + for the index. + :param upos: Offset from the bottom or left corner of the abutment box side. + :param ustep: Distance between two consecutive Pins of the vector. + :param count: Either an integer to be passed to ```range()``` or directly + a list of indexes. + + Examples: + + .. code-block:: python + + IoPin( IoPin.WEST , 'debug({})', l(50.0), l(100.0), 16 ) + IoPin( IoPin.EAST , 'debug(0)' , l(45.0), 0 , 1 ) + IoPin( IoPin.SOUTH, 'adrs({})' , l(50.0), l(100.0), [0, 2, 3, 6] ) + """ + if flags & IoPin.SOUTH: pass + elif flags & IoPin.NORTH: pass + elif flags & IoPin.EAST: pass + elif flags & IoPin.WEST: pass + else: + raise ErrorMessage( 1, [ 'IoPin.__init__(): Unsupported direction {} for "{}"'.format(flags,stem) + , '(must be NORTH, SOUTH, EAST or WEST)' ] ) + self.flags = flags + self.pins = [] + self.stem = stem + self.upos = upos + self.ustep = ustep + self.count = count + if self.upos == 0 and not (self.flags & IoPin.A_MASK): + raise ErrorMessage( 1, [ 'IoPin.__init__(): "upos" parameter cannot be zero, corners are forbidden.' + , 'For net "{}"'.format(stem) ] ) + if self.count > 1 and (self.ustep == 0) and not (self.flags & IoPin.A_MASK): + raise ErrorMessage( 1, [ 'IoPin.__init__(): "ustep" parameter cannot be zero when "count" more than 1.' + , 'For net "{}"'.format(stem) ] ) + + @property + def indexes ( self ): + if isinstance(self.count,int): + return range(self.count) + return self.count + +# def place ( self, block ): +# """ +# Performs the actual creation of the Pin. Should be called prior to any +# call to ```Etesian.place()```, so the Pin is taken into account when building +# the RoutingPads. Returns the number of *failed* pins, so zero means that all +# went well... Currently only check for out of bounds coordinates. +# """ +# +# if self.flags & IoPin.SOUTH: side = block.sides[IoPin.SOUTH] +# elif self.flags & IoPin.NORTH: side = block.sides[IoPin.NORTH] +# elif self.flags & IoPin.EAST: side = block.sides[IoPin.EAST ] +# elif self.flags & IoPin.WEST: side = block.sides[IoPin.WEST ] +# status = 0 +# if isinstance(self.count,int): +# indexes = range(self.count) +# else: +# indexes = self.count +# if self.flags & (IoPin.NORTH | IoPin.SOUTH): +# gauge = block.state.gaugeConf.vDeepRG +# for index in indexes: +# pinName = self.stem.format(index) +# net = block.state.cell.getNet( pinName ) +# if net is None: +# print( ErrorMessage( 1, [ 'IoPin.place(): No net named "{}".'.format(pinName) ] )) +# continue +# pinName += '.{}'.format(block.state.getIoPinsCounts(net)) +# pinPos = side.getNextPinPosition( self.flags, self.upos, self.ustep ) +# if pinPos.getX() > block.state.xMax or pinPos.getX() < block.state.xMin: +# print( ErrorMessage( 1, [ 'IoPin.place(): Pin "{}" is outside north or south abutment box side.'.format(pinName) +# , '(x:"{}", AB xMax:{})'.format(DbU.getValueString(pinPos.getX()),DbU.getValueString(block.state.xMax)) ] )) +# status += 1 +# trace( 550, '\tIoPin.place() N/S @{} "{}" of "{}".\n'.format(pinPos,pinName,net) ) +# pin = Pin.create( net +# , pinName +# , side.pinDirection +# , Pin.PlacementStatus.FIXED +# , gauge.getLayer() +# , pinPos.getX() +# , pinPos.getY() +# , gauge.getWireWidth() +# , gauge.getWireWidth() / 2 +# ) +# NetExternalComponents.setExternal( pin ) +# side.append( self.flags, pin ) +# block.state.incIoPinsCounts( net ) +# if self.upos: self.upos + self.ustep +# else: +# gauge = block.state.gaugeConf.hDeepRG +# for index in indexes: +# pinName = self.stem.format(index) +# net = block.state.cell.getNet( pinName ) +# if net is None: +# print( ErrorMessage( 1, [ 'IoPin.place(): No net named "{}".'.format(pinName) ] )) +# continue +# pinName += '.{}'.format(block.state.getIoPinsCounts(net)) +# pinPos = side.getNextPinPosition( self.flags, self.upos, self.ustep ) +# if pinPos.getY() > block.state.yMax or pinPos.getY() < block.state.yMin: +# print( ErrorMessage( 1, [ 'IoPin.place(): Pin "{}" is outside east or west abutment box side.'.format(pinName) +# , '(y:"{}", AB yMax:{})'.format(DbU.getValueString(pinPos.getY()),DbU.getValueString(block.state.yMax)) ] )) +# status += 1 +# trace( 550, '\tIoPin.place() E/W @{} "{}" of "{}".\n'.format(pinPos,pinName,net) ) +# pin = Pin.create( net +# , pinName +# , side.pinDirection +# , Pin.PlacementStatus.FIXED +# , gauge.getLayer() +# , pinPos.getX() +# , pinPos.getY() +# , gauge.getWireWidth() / 2 +# , gauge.getWireWidth() +# ) +# NetExternalComponents.setExternal( pin ) +# side.append( self.flags, pin ) +# block.state.incIoPinsCounts( net ) +# if self.upos: self.upos + self.ustep +# return status + + +# ---------------------------------------------------------------------------- +# Class : "configuration.BlockState". + +class BlockState ( object ): + """ + BlockState centralize all the configurations informations related to a + given block. + + It contains: + + ================= ======================================== + Attribute Information + ================= ======================================== + ``framework`` The Framework we are using. + ``gaugeConf`` The routing Gauge & Cell gauge. + ``bufferConf`` The interface of the buffer cell. + ``cell`` The block we are working on. + ================= ======================================== + """ + + def __init__ ( self, cell, ioPins=[] ): + self.editor = None + self.framework = CRL.AllianceFramework.get() + self.cfg = CfgCache('') + self.gaugeConf = GaugeConf() + self.bufferConf = BufferInterface( self.framework ) + self.cell = cell + self.fixedWidth = None + self.fixedHeight = None + self.deltaAb = [ 0, 0, 0, 0 ] + self.useClockTree = False + self.useSpares = True + self.isBuilt = False + self.ioPins = [] + self.ioPinsCounts = {} + for ioPinSpec in ioPins: + self.ioPins.append( IoPin( *ioPinSpec ) ) + + self.cfg.etesian.aspectRatio = None + self.cfg.etesian.spaceMargin = None + self.cfg.block.spareSide = None + + @property + def bufferWidth ( self ): return self.bufferConf.width + + @property + def bufferHeight ( self ): return self.bufferConf.height + + @property + def xMin ( self ): return self.cell.getAbutmentBox().getXMin() + + @property + def yMin ( self ): return self.cell.getAbutmentBox().getYMin() + + @property + def xMax ( self ): return self.cell.getAbutmentBox().getXMax() + + @property + def yMax ( self ): return self.cell.getAbutmentBox().getYMax() + + def setEditor ( self, editor ): self.editor = editor + + def createBuffer ( self ): + return self.bufferConf.createBuffer( self.cell ) + + def setDeltaAb ( self, dx1, dy1, dx2, dy2 ): + self.deltaAb = [ dx1, dy1, dx2, dy2 ] + + def incIoPinsCounts ( self, net ): + if not self.ioPinsCounts.has_key(net): + self.ioPinsCounts[net] = 0 + self.ioPinsCounts[net] += 1 + + def getIoPinsCounts ( self, net ): + if not self.ioPinsCounts.has_key(net): return 0 + return self.ioPinsCounts[net] diff --git a/cumulus/src/plugins/alpha/block/spares.py b/cumulus/src/plugins/alpha/block/spares.py new file mode 100644 index 00000000..628de69c --- /dev/null +++ b/cumulus/src/plugins/alpha/block/spares.py @@ -0,0 +1,481 @@ +# +# This file is part of the Coriolis Software. +# Copyright (c) SU 2020-2020, All Rights Reserved +# +# +-----------------------------------------------------------------+ +# | C O R I O L I S | +# | C u m u l u s - P y t h o n T o o l s | +# | | +# | Author : Jean-Paul CHAPUT | +# | E-mail : Jean-Paul.Chaput@lip6.fr | +# | =============================================================== | +# | Python : "./plugins/block/spares.py" | +# +-----------------------------------------------------------------+ + + +import sys +import os.path +import Cfg +from Hurricane import Breakpoint +from Hurricane import DbU +from Hurricane import Box +from Hurricane import Transformation +from Hurricane import Box +from Hurricane import Path +from Hurricane import Layer +from Hurricane import Occurrence +from Hurricane import Net +from Hurricane import RoutingPad +from Hurricane import Horizontal +from Hurricane import Vertical +from Hurricane import Contact +from Hurricane import Pin +from Hurricane import Plug +from Hurricane import Instance +import CRL +from CRL import RoutingLayerGauge +from helpers import trace +from helpers.io import ErrorMessage +from helpers.io import WarningMessage +from helpers.io import catch +from helpers.overlay import UpdateSession +from plugins import getParameter +from plugins.alpha import utils + + +framework = CRL.AllianceFramework.get() + + +# ---------------------------------------------------------------------------- +# Class : "spares.QuadTree". + +class QuadTree ( object ): + """ + Recursive quad-tree to partition the design area. If the aspect ratio + is not close to one, the first levels are only binary trees, either + horizontals or verticals. We resume quad-tree when the aspect ratio + is comprised between 1/2 (.5) and 2.0. + """ + + @staticmethod + def create ( spares ): + root = QuadTree( spares, None, spares.state.cell.getAbutmentBox() ) + root.rpartition() + root.rcreateBuffers() + return root + + def __init__ ( self, spares, parent, area, rtag='root' ): + self.spares = spares + self.area = area + self.xcut = None + self.ycut = None + self.parent = parent + self.bl = None + self.br = None + self.tl = None + self.tr = None + self.buseds = 0 + self.bufferTag = 'spare' + self.bufferNet = None + self.buffers = [] + self.plugs = [] + if self.parent and self.parent.rtag != '': + self.rtag = self.parent.rtag + '_' + rtag + else: + self.rtag = rtag + + @property + def leafs ( self ): + activeLeafs = [] + if self.bl: activeLeafs.append( self.bl ) + if self.br: activeLeafs.append( self.br ) + if self.tl: activeLeafs.append( self.tl ) + if self.tr: activeLeafs.append( self.tr ) + return activeLeafs + + @property + def root ( self ): + if self.parent is None: return self + return self.parent.root + + def isHBipart ( self ): return self.ycut is None + def isVBipart ( self ): return self.xcut is None + + def isLeaf ( self ): + for leaf in (self.bl, self.br, self.tl, self.tr): + if leaf is not None: return False + return True + + @property + def buffer ( self ): + """The the currently selected buffer instance in the pool.""" + return self.buffers[self.buseds] + + @property + def bInputPlug ( self ): + """The input Plug of the currently selected buffer in the pool.""" + return utils.getPlugByName( self.buffer, self.spares.state.bufferConf.input ) + + @property + def bOutputPlug ( self ): + """The output Plug of the currently selected buffer in the pool.""" + return utils.getPlugByName( self.buffer, self.spares.state.bufferConf.output ) + + def useBuffer ( self, doLeaf=False ): + """ + Create output nets for the currently selected buffer, if they do not + already exists. The nets are created in the top level cell, and their + names are derived from the `rtag` attribute. + """ + if self.isLeaf() and not doLeaf: return + + trace( 550, '\tQuadTree.useBuffer(): rtag:"{}"\n'.format(self.rtag) ) + plug = self.bOutputPlug + if not plug.getNet(): + outputNetBuff = Net.create( self.spares.state.cell,'{}_{}_{}' \ + .format(self.root.bufferTag,self.buseds,self.rtag) ) + plug.setNet( outputNetBuff ) + trace( 550, '\t| {}\n'.format(plug) ) + trace( 550, '\t| {}\n'.format(outputNetBuff) ) + + def ruseBuffer ( self ): + """Recursive call of useBuffer()""" + self.useBuffer() + for leaf in self.leafs: + leaf.ruseBuffer() + + def useNextBuffer ( self ): + """ + Reset the quadtree buffering cache in order to buffer the next net. + Flush the plugs list and increase the buffer counter (buseds). + """ + if self.plugs: + self.plugs = [] + self.buseds += 1 + if not self.isLeaf(): + for leaf in self.leafs: leaf.useNextBuffer() + + def partition ( self ): + trace( 550, ',+', '\tQuadTree.partition(): {}\n'.format(self.area) ) + + spareSide = self.spares.state.cfg.block.spareSide + sliceHeight = self.spares.state.gaugeConf.sliceHeight + side = float(spareSide * sliceHeight) + aspectRatio = float(self.area.getWidth()) / float(self.area.getHeight()) + + if self.area.getHeight() < side*2.0 or self.area.getWidth () < side*2.0: + trace( 550, '-' ) + return False + + if aspectRatio < 0.5: + self.ycut = self.spares.toYGCellGrid( self.area.getYMin() + self.area.getHeight()/2 ) + self.bl = QuadTree( self.spares + , self + , Box( self.area.getXMin() + , self.area.getYMin() + , self.area.getXMax() + , self.ycut ) + , 'bl' ) + self.tl = QuadTree( self.spares + , self + , Box( self.area.getXMin() + , self.ycut + , self.area.getXMax() + , self.area.getYMax() ) + , 'tl' ) + trace( 550, '\tVertical bi-partition @Y:{}\n'.format(DbU.getValueString(self.ycut)) ) + trace( 550, '-' ) + return True + elif aspectRatio > 2.0: + self.xcut = self.spares.toXGCellGrid( self.area.getXMin() + self.area.getWidth()/2 ) + self.bl = QuadTree( self.spares + , self + , Box( self.area.getXMin() + , self.area.getYMin() + , self.xcut + , self.area.getYMax() ) + , 'bl' ) + self.br = QuadTree( self.spares + , self + , Box( self.xcut + , self.area.getYMin() + , self.area.getXMax() + , self.area.getYMax() ) + , 'br' ) + trace( 550, '\tHorizontal bi-partition @X:{}\n'.format(DbU.getValueString(self.xcut)) ) + trace( 550, '-' ) + return True + + self.ycut = self.spares.toYGCellGrid( self.area.getYMin() + self.area.getHeight()/2 ) + self.xcut = self.spares.toXGCellGrid( self.area.getXMin() + self.area.getWidth ()/2 ) + self.bl = QuadTree( self.spares + , self + , Box( self.area.getXMin() + , self.area.getYMin() + , self.xcut + , self.ycut ) + , 'bl' ) + self.br = QuadTree( self.spares + , self + , Box( self.xcut + , self.area.getYMin() + , self.area.getXMax() + , self.ycut ) + , 'br' ) + self.tl = QuadTree( self.spares + , self + , Box( self.area.getXMin() + , self.ycut + , self.xcut + , self.area.getYMax() ) + , 'tl' ) + self.tr = QuadTree( self.spares + , self + , Box( self.xcut + , self.ycut + , self.area.getXMax() + , self.area.getYMax() ) + , 'tr' ) + + trace( 550, '\tQuadri-partition @X:{} + @Y:{}\n'\ + .format(DbU.getValueString(self.xcut),DbU.getValueString(self.ycut)) ) + trace( 550, '-' ) + return True + + def rpartition ( self ): + trace( 550, ',+', '\tQuadTree.rpartition(): {}\n'.format(self.area) ) + if self.partition(): + for leaf in self.leafs: + trace( 550, '\tLeaf rtag:"{}"\n'.format(leaf.rtag) ) + leaf.rpartition() + trace( 550, '-' ) + + def createBuffers ( self ): + trace( 550, ',+', '\tQuadTree.createBuffers()\n' ) + #if not self.isLeaf(): return + + state = self.spares.state + x = self.area.getXCenter() - state.bufferConf.width + y = self.area.getYCenter() - state.bufferConf.height + slice = y / state.gaugeConf.sliceHeight - 1 + + for slice in range(slice,slice+2): + orientation = Transformation.Orientation.ID + yadjust = 0 + if slice%2: + orientation = Transformation.Orientation.MY + yadjust = 2 * state.gaugeConf.sliceHeight + for i in range(2): + instance = state.createBuffer() + instance.setTransformation( Transformation( x + i*state.bufferConf.width + , y + yadjust + , orientation ) ) + instance.setPlacementStatus( Instance.PlacementStatus.FIXED ) + self.buffers.append( instance ) + trace( 550, '\tBuffer: {}\n'.format(self.buffers[-1]) ) + trace( 550, '-' ) + + def rcreateBuffers ( self ): + self.createBuffers() + trace( 550, ',+' ) + if self.bl: self.bl.rcreateBuffers() + if self.br: self.br.rcreateBuffers() + if self.tl: self.tl.rcreateBuffers() + if self.tr: self.tr.rcreateBuffers() + trace( 550, '-' ) + + def getLeafUnder ( self, position ): + """Find the QuadTree leaf under `position`.""" + if self.isLeaf(): return self + if self.isHBipart(): + if position.getX() < self.xcut: return self.bl.getLeafUnder(position) + return self.br.getLeafUnder(position) + if self.isVBipart(): + if position.getY() < self.ycut: return self.bl.getLeafUnder(position) + return self.tl.getLeafUnder(position) + if position.getX() < self.xcut: + if position.getY() < self.ycut: return self.bl.getLeafUnder(position) + return self.tl.getLeafUnder(position) + if position.getY() < self.ycut: return self.br.getLeafUnder(position) + return self.tr.getLeafUnder(position) + + def attachToLeaf ( self, plugOccurrence ): + position = plugOccurrence.getBoundingBox().getCenter() + self.getLeafUnder(position).plugs.append( plugOccurrence ) + + def splitNetlist ( self ): + """ + Reorganize the netlist by connecting all plugs to the output of the + currently selected buffer of this QuadTree. Use the following steps: + + 1. Create the output net of the currently selected buffer in the pool. + This net will be at the top level of the hierarchy. The name of + that net is created from the `rtag` and `buseds` attributes. + + 2. For each plug to connect: + + 2.a. Bore the buffer output net through all the levels of hierarchy + of the plug. This way, a new net, connected to the buffer output + will be created in the same hierarchical level as the plug. + + 2.b. Connect the plug to the buffer output net at the same level. + + No net/buffer will be used if the plug list is empty. + """ + if not self.plugs: return + + trace( 550, ',+', '\tQuadTree.spliNetlist()\n' ) + self.useBuffer( doLeaf=True ) + netBuff = self.bOutputPlug.getNet() + trace( 550, '\tBuffer: {}\n'.format(self.buffer) ) + trace( 550, '\tBuffer output: {}\n'.format(netBuff) ) + for plug in self.plugs: + trace( 550, '\t| Leaf: {}\n'.format(plug) ) + trace( 550, '\t| netBuff: {}\n'.format(netBuff) ) + deepPlug = self.spares.raddTransNet( netBuff, plug.getPath() ) + trace( 550, '\t| netBuff: {}\n'.format(netBuff) ) + trace( 550, '\t| Deep Plug: {}\n'.format(deepPlug) ) + deepNetBuff = deepPlug.getMasterNet() if deepPlug else netBuff + trace( 550, '\t| deepNetBuff: {} {}\n'.format(deepNetBuff,netBuff) ) + plug.getEntity().setNet( deepNetBuff ) + trace( 550, '-' ) + + def rsplitNetlist ( self ): + """Recursive call over splitNetlist().""" + self.splitNetlist() + for leaf in self.leafs: + leaf.rsplitNetlist() + + +# ---------------------------------------------------------------------------- +# Class : "spares.Spares". + +class Spares ( object ): + """ + Manages all the spare buffer over a Cell abutment box. Used for clock tree + synthesis (CTS) and high fanout net synthesis (HFS). 4 buffers are created + in each pool. Pools are set in a regular matrix over the whole abutment box. + Excess area is put in the topmost and rightmost pools. + """ + + def __init__ ( self, block ): + self.state = block.state + self.quadTree = None + self.cloneds = [] + + def getSpareSpaceMargin ( self ): + """ + Compute the percentage of margin space to compensate for the 4 spare + buffers. + """ + if not self.state.useSpares: return 0.0 + spareSide = self.state.cfg.block.spareSide + areaLength = spareSide * spareSide / self.state.gaugeConf.sliceHeight + bufferLength = self.state.bufferConf.width * 4 + return float(bufferLength) / float(areaLength) + + def toXGCellGrid ( self, x ): + """Find the nearest X (inferior) on the Cell gauge grid (sliceStep).""" + dx = x - self.state.xMin + return self.state.xMin + (dx - dx % self.state.gaugeConf.sliceStep) + + def toYGCellGrid ( self, y ): + """Find the nearest Y (inferior) on the Cell gauge grid (sliceHeight).""" + dy = y - self.state.yMin + return self.state.yMin + (dy - dy % self.state.gaugeConf.sliceHeight) + + def build ( self ): + if not self.state.useSpares: return + trace( 550, ',+', '\tSpares.build()\n' ) + with UpdateSession(): + self.quadTree = QuadTree.create( self ) + trace( 550, '-' ) + + def addClonedCell ( self, masterCell ): + if not masterCell in self.cloneds: self.cloneds.append( masterCell ) + return + + def raddTransNet ( self, topNet, path ): + """ + Add a net through a whole hierarchy of Instance/Cells. The master cells + of the instances may be modified in the process, if so, they are added + to the cloned cell list. + + :param topNet: The Net of the topmost cell which should be propagated + down the hierarchy. + :param path: The ordered list of instances into which the top net + must be connected. + :returns: The plug associated to the net in the *last* instance + (at the bottom of the hierarchy). + + This is a recursive function. For each instance level: + + 1. If the `path` is empty, it implies that no instance is involved, + no plug will be created, so return None. + + 2. Extract the *head* instance of the `path` and try to find an + already existing plug connected to the `topNet` + + 3. If no plug exists in the instance, then we must add a new external + net into the master cell of the *head instance*. The creation of + that external net will trigger the creation of the plug in all + the instances of it. The new net is created with the same net + name as the `topNet`, and copy it's type. The new plug is then + connected to the `topNet`. + + 4. If the tail path is empty, we are finished. Returns the current + plug. + + 5. The tail path is not empty, recursively call itself on the + *plug master net* and the *tail path*. + """ + + if path.isEmpty(): return None + + tailPath = path.getTailPath() + headInstance = path.getHeadInstance() + headPlug = utils.getPlugByNet(headInstance,topNet) + if not headPlug: + masterCell = headInstance.getMasterCell() + masterNet = Net.create( masterCell, topNet.getName() ) + masterNet.setExternal ( True ) + masterNet.setType ( topNet.getType() ) + masterNet.setDirection( Net.Direction.IN ) + headPlug = headInstance.getPlug( masterNet ) + if not headPlug: + raise ErrorMessage( 3, 'Plug not created for %s on instance %s of %s' \ + % (topNet.getName(),headInstance.getName(),masterCell.getName()) ) + headPlug.setNet( topNet ) + self.addClonedCell( masterCell ) + + if tailPath.isEmpty(): return headPlug + return self.raddTransNet( masterNet, tailPath ) + + def rsave ( self, cell ): + """ + Save the complete cell hierarchy. Saves only the physical view, except + for the ones that has been cloned (their names should end up by "_cts"), + for which logical and physical views are to be saved. They are completely + new cells. + """ + + flags = CRL.Catalog.State.Physical + if cell.getName().endswith('_cts'): + flags = flags | CRL.Catalog.State.Logical + framework.saveCell( cell, flags ) + + for instance in cell.getInstances(): + masterCell = instance.getMasterCell() + if not masterCell.isTerminal(): + self.rsave( masterCell ) + + def save ( self, topCell ): + """ + Frontend to Spares.rsave(). Append the "_cts" suffix to the cloned + cells, then call rsave(). + """ + for cell in self.cloneds: + cell.setName( cell.getName()+'_cts' ) + self.rsave( topCell ) + return + diff --git a/cumulus/src/plugins/alpha/utils.py b/cumulus/src/plugins/alpha/utils.py new file mode 100644 index 00000000..a84d9ea7 --- /dev/null +++ b/cumulus/src/plugins/alpha/utils.py @@ -0,0 +1,83 @@ +# +# This file is part of the Coriolis Software. +# Copyright (c) SU 2020-2020, All Rights Reserved +# +# +-----------------------------------------------------------------+ +# | C O R I O L I S | +# | C u m u l u s - P y t h o n T o o l s | +# | | +# | Author : Jean-Paul CHAPUT | +# | E-mail : Jean-Paul.Chaput@lip6.fr | +# | =============================================================== | +# | Python : "./plugins/utils.py" | +# +-----------------------------------------------------------------+ + + +from __future__ import print_function +from Hurricane import Breakpoint +from Hurricane import Box +from Hurricane import Vertical + + +def breakpoint ( editor, level, message ): + """ + Stop Coriolis when the stop level is at least equal to `level` + and display `message`. If there is an `editor`, perform a fit. + """ + if editor: + editor.fit() + editor.refresh() + Breakpoint.stop( level, message ) + + +def getPlugByName ( instance, netName ): + """ + Search in an Instance for a Plug whose *master net* is named `netName`. + """ + masterCell = instance.getMasterCell() + masterNet = masterCell.getNet( netName ) + if masterNet: + return instance.getPlug( masterNet ) + return None + + +def getPlugByNet ( instance, net ): + """ + Search in a Net for a Plug belonging to `instance`. Per Hurricane + build rules it is unique. + """ + for plug in net.getPlugs(): + if plug.getInstance() == instance: + return plug + return None + + +def getRpBb ( instance, netName ): + """ + Compute the bounding box of the Vertical external component of net `netName` + of the Instance `instance` with instance transformation applied. + + This function is not robust as it looks for the *first* external Vertical + segment it can find in the instance master cell. + """ + bb = Box() + for net in instance.getMasterCell().getNets(): + if net.isExternal() and net.getName() == netName: + for component in net.getExternalComponents(): + if isinstance(component,Vertical): + bb = component.getBoundingBox() + instance.getTransformation().applyOn( bb ) + return bb + + +def showNet ( cell, netName ): + """Display all the components of Net `netName`, along with their bounding boxes.""" + net = cell.getNet(netName) + if not net: + print( ErrorMessage( 3, 'Cell %s doesn\'t have net %s' % (cell.getName(),netName) ) ) + return + + print( 'Components of "{}":'.format(netName) ) + for component in net.getComponents(): + print( '| {} bb:{}'.format(component, component.getBoundingBox()) ) + return diff --git a/cumulus/src/plugins/block.py b/cumulus/src/plugins/block.py new file mode 100644 index 00000000..72934e30 --- /dev/null +++ b/cumulus/src/plugins/block.py @@ -0,0 +1,70 @@ +# +# This file is part of the Coriolis Software. +# Copyright (c) SU 2020-2020, All Rights Reserved +# +# +-----------------------------------------------------------------+ +# | C O R I O L I S | +# | C u m u l u s - P y t h o n T o o l s | +# | | +# | Author : Jean-Paul CHAPUT | +# | E-mail : Jean-Paul.Chaput@lip6.fr | +# | =============================================================== | +# | Python : "./plugins/block.py" | +# +-----------------------------------------------------------------+ + +""" +This script hook the Block plugin inside GCT/Unicorn. +""" + +from __future__ import print_function +import sys +import traceback +import helpers +from helpers.io import ErrorMessage +from helpers.io import WarningMessage +from helpers.overlay import UpdateSession +from helpers import trace +import plugins +from Hurricane import Breakpoint +from Hurricane import DbU +from Hurricane import Box +from Hurricane import Net +from Hurricane import Cell +from Hurricane import Instance +from Hurricane import Transformation +from plugins.alpha.block.block import Block + + +# -------------------------------------------------------------------- +# Plugin hook functions, unicornHook:menus, ScritMain:call + +def unicornHook ( **kw ): + kw['beforeAction'] = 'misc.alpha' + + plugins.kwUnicornHook( 'misc.alpha.block' + , 'Block P&&R' + , 'Perform block-level placement' + , sys.modules[__name__].__file__ + , **kw + ) + return + + +def scriptMain ( **kw ): + """The mandatory function that Coriolis CGT/Unicorn will look for.""" + rvalue = True + try: + helpers.setTraceLevel( 550 ) + cell, editor = plugins.kwParseMain( **kw ) + + block = Block.create( cell ) + if editor: block.setEditor( editor ) + rvalue = block.build() + except Exception, e: + helpers.io.catch( e ) + rvalue = False + + sys.stdout.flush() + sys.stderr.flush() + + return rvalue