From 0c1a6def569cc4ae5ce079ccdae98695bdfad4c5 Mon Sep 17 00:00:00 2001 From: Jean-Paul Chaput Date: Mon, 14 Sep 2020 15:14:23 +0200 Subject: [PATCH] Basic implementation of High Fanout Net Synthesis. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To perform HFNS, recursively makes clusters of closest sinks, less than 30 sinks and with a control of the half-perimeter at all the clusters levels. Clearly needs lots of improvements but the backbone of the feature works. Make use of the pool buffers as do the clock tree. Clustering is done with a degenerated Kruskal algorithm. This is rougly based on the article: Buffered Steiner Trees for Difficult Instances Charles J. Alpert, Member, IEEE IEEE TCAD, Vol. 21, No. 1, January 2002 0278–0070/02$17.00 (c) 2002 IEEE --- cumulus/src/CMakeLists.txt | 1 + cumulus/src/plugins/alpha/block/block.py | 63 +- cumulus/src/plugins/alpha/block/clocktree.py | 18 +- .../src/plugins/alpha/block/configuration.py | 6 + cumulus/src/plugins/alpha/block/hfns.py | 718 ++++++++++++++++++ cumulus/src/plugins/alpha/block/spares.py | 175 ++++- cumulus/src/plugins/alpha/utils.py | 15 + 7 files changed, 979 insertions(+), 17 deletions(-) create mode 100644 cumulus/src/plugins/alpha/block/hfns.py diff --git a/cumulus/src/CMakeLists.txt b/cumulus/src/CMakeLists.txt index 99b672fa..f9181bad 100644 --- a/cumulus/src/CMakeLists.txt +++ b/cumulus/src/CMakeLists.txt @@ -51,6 +51,7 @@ ${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 + ${CMAKE_CURRENT_SOURCE_DIR}/plugins/alpha/block/hfns.py ) install ( FILES ${pySources} DESTINATION ${PYTHON_SITE_PACKAGES}/cumulus ) diff --git a/cumulus/src/plugins/alpha/block/block.py b/cumulus/src/plugins/alpha/block/block.py index ffe62b77..7798f31a 100644 --- a/cumulus/src/plugins/alpha/block/block.py +++ b/cumulus/src/plugins/alpha/block/block.py @@ -50,6 +50,7 @@ import plugins.rsave from plugins import getParameter from alpha.block.spares import Spares from alpha.block.clocktree import ClockTree +from alpha.block.hfns import BufferTree from alpha.block.configuration import IoPin from alpha.block.configuration import BlockState @@ -78,6 +79,13 @@ class Side ( object ): elif self.side & IoPin.SOUTH: return Pin.Direction.SOUTH else: return Pin.Direction.NORTH + def destroy ( self ): + #with UpdateSession(): + # for pinsAtPos in self.pins.values(): + # for pin in pinsAtPos: + # pin.destroy() + self.pins = {} + def setupAb ( self ): """ Initialise the side coordinate from the block abutmeent box. @@ -307,6 +315,7 @@ class Block ( object ): self.state = state self.spares = Spares( self ) self.clockTrees = [] + self.hfnTrees = [] self.blockInstances = [] self.sides = { IoPin.WEST : Side( self, IoPin.WEST ) , IoPin.EAST : Side( self, IoPin.EAST ) @@ -376,7 +385,7 @@ class Block ( object ): def addClockTrees ( self ): """Create the trunk of all the clock trees (recursive H-Tree).""" - print( ' o Buildding clock tree(s).' ) + print( ' o Building clock tree(s).' ) af = CRL.AllianceFramework.get() clockNets = [] for net in self.state.cell.getNets(): @@ -402,6 +411,35 @@ class Block ( object ): for clockTree in self.clockTrees: clockTree.splitClock() + def findHfnTrees ( self ): + """Create the trunk of all the high fanout nets.""" + print( ' o Building high fanout nets trees.' ) + if self.spares: + with UpdateSession(): + for net in self.state.cell.getNets(): + sinksCount = 0 + for rp in net.getRoutingPads(): sinksCount += 1 + if sinksCount > 30: + trace( 550, '\tBlock.addHfnTrees(): Found high fanout net "{}" ({} sinks).\n' \ + .format(net.getName(),sinksCount) ) + #if not net.getName().startswith('alu_m_muls_b(1)'): continue + #if not net.getName().startswith('abc_75177_new_n12236'): continue + sys.stderr.flush() + print( ' - "{}", {} sinks.'.format(net.getName(),sinksCount) ) + sys.stdout.flush() + self.hfnTrees.append( BufferTree( self.spares, net ) ) + self.hfnTrees[-1].buildBTree() + self.hfnTrees[-1].rcreateBuffer() + self.hfnTrees[-1].splitNet() + self.spares.rshowPoolUse() + else: + print( ' (No spares buffers, disabled)' ) + return len(self.hfnTrees) + + def addHfnBuffers ( self ): + for hfnTree in self.hfnTrees: + hfnTree.rcreateBuffer() + def placeIoPins ( self ): """ Place the Pins on all the sides. Raise an exception in case of failure. @@ -510,14 +548,21 @@ class Block ( object ): blockInstance.block.build() 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.addClockTrees() - #Breakpoint.stop( 0, 'Clock tree(s) done.' ) - self.place() + iteration = -1 + while True: + iteration += 1 + if iteration > 0: break + self.setupAb() + self.placeIoPins() + self.checkIoPins() + self.spares.build() + if self.state.useClockTree: self.addClockTrees() + self.addHfnBuffers() + if editor: editor.fit() + #Breakpoint.stop( 0, 'Clock tree(s) done.' ) + self.place() + self.findHfnTrees() + break if self.state.useClockTree: self.splitClocks() status = self.route() self.addBlockages() diff --git a/cumulus/src/plugins/alpha/block/clocktree.py b/cumulus/src/plugins/alpha/block/clocktree.py index 65abf42a..af256cf7 100644 --- a/cumulus/src/plugins/alpha/block/clocktree.py +++ b/cumulus/src/plugins/alpha/block/clocktree.py @@ -59,10 +59,25 @@ class ClockTree ( object ): self.spares = spares self.clockNet = clockNet self.clockIndex = index + self.subNets = [] if not self.clockNet.isClock(): print( WarningMessage( 'ClockTree.__init__(): Net "{}" is not of CLOCK type.' \ .format(self.clockNet.getName()) )) + def destroy ( self ): + trace( 550, ',+', '\tClockTree.destroy() "{}"\n'.format(self.clockNet.getName()) ) + with UpdateSession(): + for subNet in self.subNets + [ self.clockNet ]: + components = [] + for comp in subNet.getComponents(): + if isinstance(comp,RoutingPad): components.append( comp ) + if isinstance(comp,Pin ): components.append( comp ) + for comp in components: + comp.destroy() + if subNet != self.clockNet: + subNet.destroy() + trace( 550, '-' ) + def _rconnectHTree ( self, qt ): if qt.isLeaf(): return False qt.rconnectBuffer() @@ -85,6 +100,7 @@ class ClockTree ( object ): gaugeConf = self.spares.state.gaugeConf bufferConf = self.spares.state.bufferConf ckNet = qt.bOutputPlug.getNet() + self.subNets.append( ckNet ) leftSourceContact = gaugeConf.rpAccessByPlugName( qt.buffer , bufferConf.output, ckNet , GaugeConf.HAccess|GaugeConf.OffsetBottom1 ) rightSourceContact = gaugeConf.rpAccessByPlugName( qt.buffer , bufferConf.output, ckNet , GaugeConf.HAccess|GaugeConf.OffsetBottom1 ) @@ -175,7 +191,7 @@ class ClockTree ( object ): """ quadTree = self.spares.quadTree quadTree.bufferTag = self.clockNet.getName() - + quadTree.rselectBuffer( self.clockIndex, self.clockIndex, 0 ) with UpdateSession(): hyperClock = HyperNet.create( Occurrence(self.clockNet) ) for plugOccurrence in hyperClock.getTerminalNetlistPlugOccurrences(): diff --git a/cumulus/src/plugins/alpha/block/configuration.py b/cumulus/src/plugins/alpha/block/configuration.py index 75a3af5b..429000b4 100644 --- a/cumulus/src/plugins/alpha/block/configuration.py +++ b/cumulus/src/plugins/alpha/block/configuration.py @@ -415,6 +415,9 @@ class BufferInterface ( object ): self.count += 1 return instance + def resetBufferCount ( self ): + self.count = 0 + # ---------------------------------------------------------------------------- # Class : "configuration.IoPin". @@ -636,3 +639,6 @@ class BlockState ( object ): def getIoPinsCounts ( self, net ): if not self.ioPinsCounts.has_key(net): return 0 return self.ioPinsCounts[net] + + def resetBufferCount ( self ): + self.bufferConf.resetBufferCount() diff --git a/cumulus/src/plugins/alpha/block/hfns.py b/cumulus/src/plugins/alpha/block/hfns.py new file mode 100644 index 00000000..7c4b6eb2 --- /dev/null +++ b/cumulus/src/plugins/alpha/block/hfns.py @@ -0,0 +1,718 @@ +# +# 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/hfns.py" | +# +-----------------------------------------------------------------+ + +""" +Manage High Fanout Net Synthesis (HFNS). +""" + +from __future__ import print_function +import sys +import os.path +import re +from operator import itemgetter, attrgetter, methodcaller +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, l, u, n +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 +from plugins.alpha.block.spares import Spares + + +af = CRL.AllianceFramework.get() + + +# ---------------------------------------------------------------------------- +# Class : "hfns.SlicedArea". + +class SlicedArea ( object ): + """ + Perform the buffer creation and insertion for a Cluster. It can request + a free buffer from the spare set or insert a new one directly in the + design. The second option is still available but now unused, kept as + code example (may be needed in the future). + """ + + REPLACE = 0x0001 + WEDGE = 0x0002 + + def __init__ ( self, cluster ): + """ + Create the sliced area and perform an immediate buffer allocation + from the spare set. Hint for a position indide of the cluster's area + but closest to the parent's center area (so, ideally, on the cluster's + edge). + """ + state = cluster.bufferTree.spares.state + self.cluster = cluster + self.rows = {} + self.iposition = None + if cluster.parent is None: + attractor = cluster.getCenter() + else: + attractor = cluster.parent.area.getCenter() + self.instBuffer = cluster.bufferTree.spares.getFreeBufferUnder( cluster.area, attractor ) + + @property + def buffer ( self ): + """The buffer instance.""" + return self.instBuffer + + @property + def bInputPlug ( self ): + """The input Plug of the buffer.""" + return utils.getPlugByName( self.buffer, self.cluster.bufferTree.spares.state.bufferConf.input ) + + @property + def bOutputPlug ( self ): + """The output Plug of the buffer.""" + return utils.getPlugByName( self.buffer, self.cluster.bufferTree.spares.state.bufferConf.output ) + + def buildSlicesUnder ( self ): + """ + UNUSED. Kept as reference example. + Rebuild slices structure under a specific area (must be small). + """ + state = self.cluster.bufferTree.spares.state + sliceHeight = state.gaugeConf.sliceHeight + cell = state.cell + cellAb = cell.getAbutmentBox() + insertArea = Box( self.cluster.getCenter() ) + insertArea.inflate( l(50.0*3), 2*l(50.0) ) + for occurrence in cell.getTerminalNetlistInstanceOccurrencesUnder( insertArea ): + instance = occurrence.getEntity() + masterCell = instance.getMasterCell() + ab = masterCell.getAbutmentBox() + transf = instance.getTransformation() + occurrence.getPath().getTransformation().applyOn( transf ) + transf.applyOn( ab ) + y = (ab.getYMin() - cellAb.getYMin()) / sliceHeight + if (ab.getYMin() - cellAb.getYMin()) % sliceHeight: + print( ErrorMessage( 1, 'SlicedArea.__init__(): Misaligned {}.'.format(occurrence) )) + continue + if not self.rows.has_key(y): + self.rows[y] = [] + row = self.rows[ y ] + row.append( (occurrence,ab) ) + for row in self.rows.values(): + row.sort( key=lambda v: v[1].getXMin() ) + + def findBufferSite ( self ): + """ + UNUSED. Kept as reference example. + Analyse the slices for spaces and holes into which insert a buffer. + Look for hole big enough (REPLACE case) or if we do need to shift + the cells to create one (WEDGE case). + """ + global af + catalog = af.getCatalog() + bufferLength = self.cluster.bufferTree.spares.state.bufferConf.width + for key in self.rows.keys(): + row = self.rows[ key ] + trace( 550, '\t+ Row:\n' ) + tieLength = 0 + holeLength = 0 + biggestHoleLength = 0 + contiguousTie = False + ihole = 0 + for i in range(len(row)): + occurrence, ab = row[i] + masterCell = occurrence.getEntity().getMasterCell() + catalogState = catalog.getState( masterCell.getName() ) + if catalogState.isFeed(): + trace( 550, '\t| Feed:{}\n'.format(occurrence) ) + cellLength = masterCell.getAbutmentBox().getWidth() + tieLength += cellLength + holeLength += cellLength + contiguousTie = True + if holeLength > biggestHoleLength: + biggestHoleLength = holeLength + if holeLength == cellLength: + ihole = i + else: + holeLength = 0 + contiguousTie = False + if bufferLength <= tieLength: + trace( 550, '\tbufferLength:{} tieLength:{} biggestHole:{}\n' \ + .format( DbU.getValueString(bufferLength) + , DbU.getValueString(tieLength) + , DbU.getValueString(biggestHoleLength) )) + if bufferLength <= biggestHoleLength: + trace( 550, '\tHole is big enough, REPLACE\n' ) + self.iposition = (key, ihole, SlicedArea.REPLACE) + else: + trace( 550, '\tNeeds wedging, WEDGE\n' ) + self.iposition = (key, ihole, SlicedArea.WEDGE) + return True + return False + + def insertBuffer ( self ): + """ + UNUSED. Kept as reference example. + Insert a new buffer instance inside the slice area. Uses the informations + gathered by ``findBufferSite()`` (where to REPLACE or WEDGE). + """ + if self.iposition is None: + raise ErrorMessage( 2, 'SlicedArea.insertBuffer(): No position defined to wedge the buffer.' ) + state = self.cluster.bufferTree.spares.state + catalog = af.getCatalog() + bufferLength = self.cluster.bufferTree.spares.state.bufferConf.width + tieLength = 0 + transf = None + if self.iposition[2] & SlicedArea.REPLACE: + row = self.rows[ self.iposition[0] ] + for i in range(self.iposition[1],len(row)): + occurrence, ab = row[i] + tieLength += ab.getWidth() + tieInstance = occurrence.getEntity() + masterCell = tieInstance.getMasterCell() + catalogState = catalog.getState( masterCell.getName() ) + if not catalogState.isFeed(): + raise ErrorMessage( 2, 'SlicedArea.insertBuffer(): Not a feed cell under wedge position.' ) + if transf is None: + transf = tieInstance.getTransformation() + tieInstance.destroy() + if tieLength >= bufferLength: + break + self.instBuffer = state.createBuffer() + self.instBuffer.setTransformation( transf ) + self.instBuffer.setPlacementStatus( Instance.PlacementStatus.FIXED ) + trace( 550, '\tWedged: {} @{}\n'.format(self.instBuffer,transf) ) + + def display ( self ): + """Display the orderded instances under the sliced area.""" + for key in self.rows.keys(): + print( 'Row @{}:'.format(key) ) + row = self.rows[ key ] + for occurrence, ab in row: + print( '| {} <- {}'.format(ab,occurrence) ) + + +# ---------------------------------------------------------------------------- +# Class : "hfns.Cluster". + +class Cluster ( object ): + """ + Implementation of cluster of RoutingPads. This is a disjoint-set data + structure (ref. https://en.wikipedia.org/wiki/Disjoint-set_data_structure). + + We manage two kind of trees, do not mistake them: + + 1. The cluster's own tree, that is, the union set. ``self.root`` and + ``self.parent`` belongs to that structure. + 2. The tree *of* Clusters. Recursive functions like ``rsplitNet()`` + and ``rcreateBuffer()`` belongs to that super-tree. + + The ``snapshot()`` and ``rrebindRp()`` are kept just in case. They allow + to keep the cluster partitionning between iterations of the placer by + replacing RoutingPad (which get erased) by Plug occurrences which are + stables. + """ + + def __init__ ( self, bufferTree, anchor, depth ): + self.depth = depth + self.bufferTree = bufferTree + self.anchor = anchor + self.mergedAnchors = [ anchor ] + self.parent = None + self.rank = 0 + self.size = 1 + self.area = Box( anchor.getCenter() ) + + def __str__ ( self ): + parentId = 'None' if self.parent is None else str(self.parent.id) + s = '' \ + .format( self.depth + , parentId + , self.id + , self.size + , DbU.getValueString(self.area.getWidth()) + , DbU.getValueString(self.area.getHeight()) ) + return s + + def __cmp__ ( self, other ): + if other is None: return 1 + if self.id < other.id: return -1 + if self.id > other.id: return 1 + return 0 + + @property + def buffer ( self ): + """The buffer instance (proxy to slicedArea).""" + return self.slicedArea.buffer + + @property + def bInputPlug ( self ): + """The input Plug of the buffer (proxy to slicedArea).""" + return self.slicedArea.bInputPlug + + @property + def bOutputPlug ( self ): + """The output Plug of the buffer (proxy to slicedArea).""" + return self.slicedArea.bOutputPlug + + @property + def id ( self ): + if self.anchor is None: return 0 + return self.anchor.getId() + + def getId ( self ): + return self.id + + def isRoot ( self ): return self.parent is None + + def getCenter ( self ): + return self.area.getCenter() + + def getRoot ( self ): + """Find the root, performing simple path compression as it goes.""" + #trace( 550, ',+', '\tCluster.getRoot() of id:{}\n'.format(self.id) ) + root = self + #trace( 550, '\t+ Finding root:\n' ) + while root.parent is not None: + root = root.parent + #trace( 550, '\t| id:{}\n'.format(root.id) ) + node = self + #trace( 550, '\t+ Compressing path:\n' ) + while node.parent is not None: + pnode = node.parent + node.parent = root + node = pnode + #trace( 550, '\t| id:{}\n'.format(node.id) ) + #trace( 550, ',-', '\t> Root of id:{} is id:{}\n'.format(self.id,root.id) ) + return root + + def merge ( self, other ): + """Union by rank.""" + #trace( 550, ',+', '\tCluster.merge() id:{} with id:{}\n' \ + # .format(self.id,other.id) ) + root1 = self.getRoot() + root2 = other.getRoot() + if root1 != root2: + if root1.rank < root2.rank: + root1, root2 = root2, root1 + if root1.rank != root2.rank: + root1.rank += 1 + root1.area.merge( root2.area ) + root1.size += root2.size + root1.mergedAnchors += root2.mergedAnchors + root2.parent = root1 + #trace( 550, ',-', '\tMerge id:{} <= id:{} done\n' \ + # .format(root1.id,root2.id) ) + else: + pass + #trace( 550, ',-', '\tMerge id:{} and id:{} already done\n' \ + # .format(root1.id,root2.id) ) + return root1 + + def snapshot ( self ): + """ + UNUSED. Kept as reference example. + Replace the RoutingPad by their occurrences (in place). + This operation is needed to save the cluster information between + two runs as RoutingPad will get destroyed/re-created. However, + the Plug occurrence will remains valid (and stable). + """ + if isinstance(self.anchor,RoutingPad): + self.anchor = self.anchor.getPlugOccurrence() + mergedAnchors = [] + for anchor in self.mergedAnchors: + if isinstance(anchor,RoutingPad): + mergedAnchors.append( anchor.getPlugOccurrence() ) + trace( 550, '\t| snapshot:{}\n'.format(anchor.getPlugOccurrence()) ) + else: + mergedAnchors.append( anchor ) + anchor.snapshot() + self.mergedAnchors = mergedAnchors + + def rrebindRp ( self, rp ): + """ + UNUSED. Kept as reference example. + Associate a RoutingPad ``rp`` to a cluster. This is done by + matching the plug occurrence of the RoutingPad. The ``snapshot()`` + method must have been called before. + """ + trace( 550, ',+', '\tCluster.rrebindRp() {}\n'.format(rp.getPlugOccurrence()) ) + bound = False + plugOcc = rp.getPlugOccurrence() + if plugOcc == self.anchor: + bound = True + self.anchor = rp + self.mergedAnchors[0] = rp + trace( 550, '\t> Bound to: {}\n'.format(self) ) + else: + trace( 550, '\t+ mergedAnchor: {}\n'.format(len(self.mergedAnchors)) ) + if len(self.mergedAnchors): + for i in range(len(self.mergedAnchors)): + trace( 550, '\t| compare:[{:2}] {}\n'.format(i,self.mergeAnchors[i]) ) + if plugOcc == self.mergeAnchors[i]: + self.mergedAnchors[i] = rp + bound = True + trace( 550, '\t> Bound to: {}\n'.format(self) ) + break + if not bound and self.mergedAnchors is not None: + for cluster in self.mergedAnchors: + if isinstance(cluster,Cluster): + bound = cluster.rrebindRp(rp) + if bound: break + trace( 550, '-' ) + return bound + + def createBufInputRp ( self, net ): + """Create a RoutingPad for the buffer input Plug (terminal).""" + return RoutingPad.create( net, Occurrence(self.bInputPlug), RoutingPad.BiggestArea ) + + def createBufOutputRp ( self, net ): + """Create a RoutingPad for the buffer output Plug (terminal).""" + return RoutingPad.create( net, Occurrence(self.bOutputPlug), RoutingPad.BiggestArea ) + + def setRootDriver ( self, net ): + """Connect the top-level buffer input to the original signal.""" + if not self.isRoot(): + raise ErrorMessage( 2, 'Cluster.setRootDriver(): Must be called only on the top root cluster.' ) + self.createBufInputRp( net ) + + def createBuffer ( self ): + """Create the SlicedArea which will create/insert the buffer of the cluster.""" + if not self.isRoot(): + raise ErrorMessage( 2, 'Cluster.createBuffer(): Only root cluster should have buffer.' ) + self.slicedArea = SlicedArea( self ) + + def rcreateBuffer ( self ): + """Recursively call ``createBuffer()`` on the whole cluster hierarchy.""" + self.createBuffer() + for anchor in self.mergedAnchors: + if isinstance(anchor,Cluster): + anchor.rcreateBuffer() + + def rsplitNet ( self ): + """ + Perform the actual splitting of the net into subnets. This is a + recursive function. One driver net will be created by cluster. + """ + if not self.isRoot(): + raise ErrorMessage( 2, 'Cluster.connect(): Only root cluster should be connecteds.' ) + spares = self.bufferTree.spares + netBuff = self.bufferTree.createSubNet() + self.bOutputPlug.setNet( netBuff ) + trace( 550, ',+', '\tCluster.rsplitNet(), size:{} depth:{} driver:{}\n' \ + .format(self.size,self.depth,netBuff.getName()) ) + if len(self.mergedAnchors) > 30: + print( 'Top cluster of "{}" still has {} sinks.' \ + .format(netBuff.getName(),len(self.mergedAnchors)) ) + for anchor in self.mergedAnchors: + if isinstance(anchor,Cluster): + trace( 550, '\tcluster input: "{}"\n'.format(netBuff) ) + anchor.bInputPlug.setNet( netBuff ) + anchor.rsplitNet() + else: + plug = anchor.getPlugOccurrence() + deepPlug = spares.raddTransNet( netBuff, plug.getPath() ) + deepNetBuff = deepPlug.getMasterNet() if deepPlug else netBuff + trace( 550, '\tdeepNetBuf: "{}"\n'.format(deepNetBuff) ) + if isinstance(plug.getEntity(),Pin): + print( 'PIN, SKIPPED for {}'.format(deepNetBuff.getName()) ) + continue + plug.getEntity().setNet( deepNetBuff ) + anchor.destroy() + if netBuff.getName().startswith('abc_75177_new_n3292'): + trace( 550, '\tNet {}:\n'.format(netBuff.getName()) ) + count = 0 + for component in netBuff.getComponents(): + trace( 550, '\t[{:2}] {}\n'.format(count,component) ) + count += 1 + trace( 550, ',-' ) + + def show ( self ): + """Select the RoutingPad of the cluster in the editor.""" + editor = self.bufferTree.spares.state.editor + if not editor: return False + editor.unselectAll() + editor.setCumulativeSelection( True ) + editor.setShowSelection( True ) + area = Box( self.area ) + area.inflate( l(10.0) ) + editor.reframe( area, False ) + #editor.select( self.anchor.getOccurrence() ) + for anchor in self.mergedAnchors: + if isinstance(anchor,Cluster): + continue + else: + editor.select( anchor.getOccurrence() ) + return True + +# ---------------------------------------------------------------------------- +# Class : "hfns.Edge". + +class Edge ( object ): + """ + Define an Edge between two Clusters. The lenght of the Edge is the + Manhattan distance of the two centers of the cluster's areas. + So, as Clusters grows, so does the area and the length of the + edge change over time. To work on a stable value, the initial + distance is cached in the ``length`` attribute. + """ + + def __init__ ( self, source, target ): + self.source = source + self.target = target + self.length = self.clusterLength + + @property + def clusterLength ( self ): + """ + Manhattan distance, cluster center to cluster center. + The actual one, not the ``length`` initial cached value. + """ + sourceCenter = self.source.getCenter() + targetCenter = self.target.getCenter() + return targetCenter.manhattanDistance( sourceCenter ) + + def __cmp__ ( self, other ): + """Comparison over the cached initial length value.""" + if self.length < other.length: return -1 + if self.length > other.length: return 1 + return 0 + + +# ---------------------------------------------------------------------------- +# Class : "hfns.BufferTree". + +class BufferTree ( object ): + """ + Build a buffer tree for a high fanout net. Recursively build clusters + using Kruskal algorithm (https://en.wikipedia.org/wiki/Kruskal's_algorithm). + All subnet are created at the design top level (like for clock tree) + so they are not ``DeepNet``, the driver itself is pulled up to the top + level if needs be. + """ + + patVhdlVector = re.compile( r'(?P.*)\((?P\d+)\)' ) + + def __init__ ( self, spares, net ): + trace( 550, '\tBufferTree.__init__() on "{}".\n'.format(net.getName()) ) + self.spares = spares + self.net = net + self.isDeepNet = True + self.clusterDepth = 0 + self.clusters = [ [] ] + self.netCount = 0 + self.netName = self.net.getName() + self.netIndex = None + m = BufferTree.patVhdlVector.match( self.net.getName() ) + if m: + self.netName = m.group('name') + self.netIndex = m.group('index') + + @property + def root ( self ): + """The root cluster of the tree (must be unique...)""" + if len(self.clusters[-1]) != 1: + raise ErrorMessage( 2, 'BufferTree.root: No, or multiple root for "{}".' \ + .format(self.net.getName()) ) + return self.clusters[-1][0] + + @property + def edgeLimit ( self ): + """ + Maximum length of Edge to consider. Edges above this threshold will be + pruned from the set given to Kruskal. + """ + levelFactor = 1 + if self.clusterDepth == 0: pass + else: levelFactor = 4*self.clusterDepth + return levelFactor*l(700) + + def createSubNet ( self ): + """ + Create a new sub-net for a buffer driver. If the signal is a bit + from a vector, unvectorize but keep a ``bitX`` tag in it. For example, + the third (i.e. index 2) auxiliary signal for ``my_vector(3)`` will give + ``my_vector_bit3_2``. + """ + if self.netIndex is None: + subNetName = '{}_hfns_{}'.format( self.netName, self.netCount ) + else: + subNetName = '{}_bit{}_hfns_{}'.format( self.netName, self.netIndex, self.netCount ) + net = Net.create( self.spares.state.cell, subNetName ) + self.netCount += 1 + return net + + def canMerge ( self, clusterA, clusterB ): + """ + Control the merge criterion between two clusters. For now we check + that the number of sinks is below 30 and the half-perimeter is not + too great (see ``edgeLimit``). + """ + if clusterA.size + clusterB.size > 30: + trace( 550, '\t> Reject merge, over size threshold of 30.\n' ) + return False + area = Box( clusterA.area ) + area.merge( clusterB.area ) + hpwl = (area.getWidth() + area.getHeight()) / 2 + if hpwl > 2*self.edgeLimit: + trace( 550, '\t> Reject merge, over HPWL threshold of 2*{}.\n' \ + .format(DbU.getValueString(self.edgeLimit))) + return False + else: + trace( 550, '\t> Accepted merge, future area is {}x{}.\n' \ + .format( DbU.getValueString(area.getWidth ()) + , DbU.getValueString(area.getHeight()) )) + return True + + def doKruskal ( self ): + """ + Do Kruskal algorithm. We do not perform a complete Krukal as + *too long* edges are pruned and we do not keep tracks of edges, + we just want a cluster of close RoutingPad, not a minimum + spanning tree. + """ + trace( 550, ',+', '\tBufferTree.doKruskal()\n' ) + trace( 550, '\tBuilding edges, max length:{} ...\n'.format(DbU.getValueString(self.edgeLimit)) ) + clusters = self.clusters[-1] + edges = [] + for i in range(len(clusters)): + for j in range(i+1,len(clusters)): + edge = Edge( clusters[i], clusters[j] ) + if edge.length < self.edgeLimit: + edges.append( edge ) + trace( 550, '\tSorting {} edges ...\n'.format(len(edges)) ) + edges.sort( key=attrgetter('length') ) + trace( 550, '\tProcessing edges ...\n' ) + clustersCount = len(clusters) + for i in range(len(edges)): + edge = edges[i] + trace( 550, '\t| Process [{:3d}], length:{} clusterLength:{}\n' \ + .format( i, DbU.getValueString(edge.length) + , DbU.getValueString(edge.clusterLength)) ) + sourceRoot = edge.source.getRoot() + targetRoot = edge.target.getRoot() + if sourceRoot == targetRoot: + continue + if not self.canMerge(sourceRoot,targetRoot): + continue + sourceRoot.merge( targetRoot ) + trace( 550, '\t> Merged cluster: {}\n'.format(sourceRoot.getRoot()) ) + clustersCount -= 1 + trace( 550, '\tClusters count: {}\n'.format(clustersCount) ) + for cluster in clusters: + if cluster.isRoot(): + trace( 550, '\t | Cluster, size:{}, area:{} x {} {}\n' \ + .format( cluster.size + , DbU.getValueString(cluster.area.getWidth()) + , DbU.getValueString(cluster.area.getHeight()) + , cluster.area ) ) + trace( 550, '-' ) + return clustersCount + + def buildBTree ( self ): + """ + Recursively performs the Kruskal algorithm until only *one* root + cluster remains. First level is clusters of RoutingPad, then + clusters of clusters. + """ + trace( 550, ',+', '\tBufferTree.buildBTree() on "{}" ...\n'.format(self.net.getName()) ) + self.rpDriver = None + pinRp = None + for rp in self.net.getRoutingPads(): + rpOccurrence = rp.getPlugOccurrence() + entity = rpOccurrence.getEntity() + if rpOccurrence.getPath().isEmpty(): + self.isDeepNet = False + if isinstance(entity,Pin): + pinRp = rp + continue + masterNet = entity.getMasterNet() + if masterNet.getDirection() & Net.Direction.DirIn: + self.clusters[0].append( Cluster(self,rp,self.clusterDepth) ) + else: + trace( 550, '\tDriver:{}.\n'.format(rp) ) + self.rpDriver = rp + if pinRp: + if self.rpDriver is None: + trace( 550, '\tDriver (externa pin):{}.\n'.format(rp) ) + self.rpDriver = rp + else: + self.clusters[0].append( Cluster(self,pinRp,self.clusterDepth) ) + while len(self.clusters[-1]) > 1: + self.doKruskal() + self.clusters.append( [] ) + for cluster in self.clusters[-2]: + if cluster.isRoot(): + self.clusters[-1].append( Cluster(self,cluster,self.clusterDepth+1) ) + #if cluster.show(): + # Breakpoint.stop( 0, 'Showing cluster of {} RoutingPads'.format(cluster.size) ) + editor = self.spares.state.editor + if editor: + editor.unselectAll() + editor.setCumulativeSelection( False ) + editor.setShowSelection( False ) + self.clusterDepth += 1 + trace( 550, '-' ) + + def snapshot ( self ): + """UNUSED. Kept as reference example. See ``Cluster.snapshot()``.""" + if not self.root: + raise ErrorMessage( 2, 'BufferTree.snapshot(): Clusters must be built first.' ) + self.root.snapshot() + + def rcreateBuffer ( self ): + """Proxy to ``Cluster.rcreateBuffer()``.""" + if not self.root: + raise ErrorMessage( 2, 'BufferTree.rcreateBuffer(): Clusters must be built first.' ) + self.root.rcreateBuffer() + + def splitNet ( self ): + """ + Perform the actual splitting of the net into sub-trees. Mostly calls + ``Cluster.rsplitNet()`` then connect the top cluster root to the original + signal. + """ + if not self.root: + raise ErrorMessage( 2, 'BufferTree.splitNet(): Clusters must be built first.' ) + self.root.rsplitNet() + if self.isDeepNet: + # Must convert from a DeepNet into a real top Net to be saved. + driverRpOcc = self.rpDriver.getPlugOccurrence() + topNetName = self.net.getName() + self.net.destroy() + self.net = Net.create( self.spares.state.cell, topNetName ) + deepPlug = self.spares.raddTransNet( self.net, driverRpOcc.getPath() ) + deepDriverNet = deepPlug.getMasterNet() + driverRpOcc.getEntity().setNet( deepDriverNet ) + RoutingPad.create( self.net, driverRpOcc, RoutingPad.BiggestArea ) + self.root.setRootDriver( self.net ) + trace( 550, '\tRoot input: {}\n'.format(self.root.bInputPlug) ) diff --git a/cumulus/src/plugins/alpha/block/spares.py b/cumulus/src/plugins/alpha/block/spares.py index c03e2a5c..c8442ecf 100644 --- a/cumulus/src/plugins/alpha/block/spares.py +++ b/cumulus/src/plugins/alpha/block/spares.py @@ -16,6 +16,7 @@ import sys import os.path import Cfg +from operator import itemgetter from Hurricane import Breakpoint from Hurricane import DbU from Hurricane import Box @@ -59,6 +60,7 @@ class BufferPool ( object ): self.quadTree = quadTree self.columns = quadTree.spares.state.bColumns self.rows = quadTree.spares.state.bRows + self.area = Box() self.buffers = [] self.selectedIndex = None for i in range(self.rows*self.columns): @@ -119,10 +121,24 @@ class BufferPool ( object ): selectedBuffer[0] |= Spares.USED def selectFree ( self ): - """Select the first free buffer available.""" + """ + Select the first free buffer available. Marks the buffer as used + and return it's instance. If all buffer in the pool are taken, + return ``None``. + """ for i in range(self.rows*self.columns): if not (self.buffers[i][0] & Spares.USED): self._select( i, Spares.MARK_USED ) + trace( 550, '\tUse buffer from pool {}\n'.format(self.quadTree) ) + return self.buffers[i][1] + return None + + def hasFree ( self ): + """Tells if the pool has a free buffer available.""" + for i in range(self.rows*self.columns): + if not (self.buffers[i][0] & Spares.USED): + return True + return False def _createBuffers ( self ): """Create the matrix of instances buffer.""" @@ -152,8 +168,30 @@ class BufferPool ( object ): instance.setPlacementStatus( Instance.PlacementStatus.FIXED ) self.buffers[ index ][1] = instance trace( 550, '\tBuffer[{}]: {} @{}\n'.format(index,self.buffers[index],transf) ) + blBufAb = self.buffers[ 0][1].getAbutmentBox() + trBufAb = self.buffers[-1][1].getAbutmentBox() + self.area = Box( blBufAb.getXMin(), blBufAb.getYMin() + , trBufAb.getXMax(), trBufAb.getYMax() ) trace( 550, '-' ) - + + def _destroyBuffers ( self ): + """Destroy all the buffer instances of the pool.""" + for flags, buffer in self.buffers: + buffer.destroy() + + def showUse ( self, depth ): + """Display the pool occupancy.""" + count = 0 + for i in range(self.rows*self.columns): + if self.buffers[i][0] & Spares.USED: + count += 1 + #header = '| ' if self.quadTree.isLeaf() else '+ ' + #print( ' {}{}Pool {}, usage:{}/{}.'.format( ' '*depth + # , header + # , self.quadTree + # , count + # , self.rows*self.columns) ) + return count, self.rows*self.columns # ---------------------------------------------------------------------------- @@ -179,6 +217,7 @@ class QuadTree ( object ): self.xcut = None self.ycut = None self.parent = parent + self.depth = parent.depth+1 if parent else 0 self.bl = None self.br = None self.tl = None @@ -192,6 +231,13 @@ class QuadTree ( object ): else: self.rtag = rtag + def destroy ( self ): + if self.bl: self.bl.destroy() + if self.br: self.br.destroy() + if self.tl: self.tl.destroy() + if self.tr: self.tr.destroy() + self.pool._destroyBuffers() + def __str__ ( self ): s = ''.format( DbU.getValueString(self.area.getXMin()) , DbU.getValueString(self.area.getYMin()) @@ -200,6 +246,27 @@ class QuadTree ( object ): , self.rtag ) return s + def __eq__ ( self, other ): + return self.rtag == other.rtag + + def rshowPoolUse ( self ): + rused = 0 + rtotal = 0 + if not self.depth: + print( ' o Detailed use of spare buffers.' ) + used, total = self.pool.showUse( self.depth ) + rused += used + rtotal += total + for leaf in self.leafs: + used, total = leaf.rshowPoolUse() + rused += used + rtotal += total + if not self.depth: + if rtotal: + print( ' - Useds: {}, total: {} ({:.1%}).' \ + .format(rused,rtotal,float(rused)/float(rtotal)) ) + return rused, rtotal + @property def leafs ( self ): activeLeafs = [] @@ -225,6 +292,20 @@ class QuadTree ( object ): if leaf is not None: return False return True + def getParentAt ( self, depth ): + """ + Return the parent at the given ``depth``. The depth increase starting + from the root which is labeled 0. So requesting the parent at a depth + superior to the one of the node is an error... + """ + if self.depth <= depth: + raise ErrorMessage( 2, 'QuadTree.getParentAt(): Parent depth must be lower than current depth ({} vs. {})' \ + .format(depth,self.depth) ) + parent = self.parent + while parent.depth > depth: + parent = parent.parent + return parent + @property def buffer ( self ): """The the currently selected buffer instance in the pool.""" @@ -250,6 +331,13 @@ class QuadTree ( object ): modulo = (x - self.area.getXMin()) % self.spares.state.gaugeConf.sliceStep return x - modulo + def selectFree ( self ): + """ + Returns the first free buffer *instance* in the pool or None if + there isn't any left. + """ + return self.pool.selectFree() + def connectBuffer ( self, doLeaf=False ): """ Create output nets for the currently selected buffer, if they do not @@ -392,7 +480,7 @@ class QuadTree ( object ): trace( 550, '-' ) def getLeafUnder ( self, position ): - """Find the QuadTree leaf under `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) @@ -406,6 +494,29 @@ class QuadTree ( object ): if position.getY() < self.ycut: return self.br.getLeafUnder(position) return self.tr.getLeafUnder(position) + def getFreeLeafUnder ( self, area, attractor=None ): + """ + Find a free buffer under the given ``area`` the candidates are sorted + according to their distance to the ``attractor`` point, the closest is + returned. If no ``attractor`` is supplied, the center of the ``area`` + id used. This function is a frontend to ``_getFreeLeafUnder()``. + """ + candidates = [] + self._getFreeLeafUnder( area, candidates, attractor ) + if not len(candidates): + return None + candidates.sort( key=itemgetter(1) ) + return candidates[0][0] + + def _getFreeLeafUnder ( self, area, candidates, attractor ): + """Find a free buffer under the given ``area``. See ``getFreeLeafUnder()``.""" + if self.pool.hasFree(): + point = area.getCenter() if attractor is None else attractor + candidates.append( [ self, self.area.getCenter().manhattanDistance(point) ] ) + for leaf in self.leafs: + if leaf.area.intersect(area): + leaf._getFreeLeafUnder( area, candidates, attractor ) + def attachToLeaf ( self, plugOccurrence ): """Assign the plug occurrence to the QuadTree leaf it is under.""" position = plugOccurrence.getBoundingBox().getCenter() @@ -476,9 +587,19 @@ class Spares ( object ): MARK_USED = 0x00020000 def __init__ ( self, block ): - self.state = block.state - self.quadTree = None - self.cloneds = [] + self.state = block.state + self.quadTree = None + self.cloneds = [] + self.strayBuffers = [] + + def reset ( self ): + self.quadTree.destroy() + for buffer in self.strayBuffers: + buffer.destroy() + self.quadTree = None + self.cloneds = [] + self.strayBuffers = [] + self.state.resetBufferCount() def getSpareSpaceMargin ( self ): """ @@ -494,7 +615,7 @@ class Spares ( object ): bufferLength = self.state.bufferConf.width * self.state.bColumns * self.state.bRows if not areaLength: raise ErrorMessage( 3, 'Spares.getSpareSpaceMargin(): Spare leaf area is zero.' ) - return float(bufferLength) / float(areaLength) + return (float(bufferLength) * 1.3) / float(areaLength) def toXGCellGrid ( self, x ): """Find the nearest X (inferior) on the Cell gauge grid (sliceStep).""" @@ -513,6 +634,46 @@ class Spares ( object ): self.quadTree = QuadTree.create( self ) trace( 550, '-' ) + def rshowPoolUse ( self ): + if self.quadTree: + self.quadTree.rshowPoolUse() + + def addStrayBuffer ( self, position ): + """Add a new stray buffer at ``position``.""" + trace( 550, ',+', '\tSpares.addStrayBuffer()\n' ) + + sliceHeight = self.state.gaugeConf.sliceHeight + x = self.quadTree.onXPitch( position.getX() ) + y = self.quadTree.onYSlice( position.getY() ) + slice = y / sliceHeight + orientation = Transformation.Orientation.ID + y = slice * sliceHeight + if slice % 2: + orientation = Transformation.Orientation.MY + y += sliceHeight + transf = Transformation( x, y, orientation ) + instance = self.state.createBuffer() + instance.setTransformation( transf ) + instance.setPlacementStatus( Instance.PlacementStatus.FIXED ) + unoverlapDx = self.quadTree.getUnoverlapDx( instance.getAbutmentBox() ) + if unoverlapDx: + transf = Transformation( x+unoverlapDx, y, orientation ) + instance.setTransformation( transf ) + self.strayBuffers.append( instance ) + trace( 550, '\tBuffer: {} @{}\n'.format(self.strayBuffers[-1],transf) ) + trace( 550, '-' ) + return instance + + def getFreeBufferNear ( self, position ): + leaf = self.quadTree.getLeafUnder( position ) + return leaf.selectFree() + + def getFreeBufferUnder ( self, area, attractor=None ): + leaf = self.quadTree.getFreeLeafUnder( area, attractor ) + if leaf is None: + raise ErrorMessage( 2, 'Spares.getFreeBufferUnder(): No more free buffers under {}.'.format(area) ) + return leaf.selectFree() + def addClonedCell ( self, masterCell ): if not masterCell in self.cloneds: self.cloneds.append( masterCell ) return diff --git a/cumulus/src/plugins/alpha/utils.py b/cumulus/src/plugins/alpha/utils.py index a84d9ea7..0382f612 100644 --- a/cumulus/src/plugins/alpha/utils.py +++ b/cumulus/src/plugins/alpha/utils.py @@ -17,6 +17,7 @@ from __future__ import print_function from Hurricane import Breakpoint from Hurricane import Box from Hurricane import Vertical +from Hurricane import RoutingPad def breakpoint ( editor, level, message ): @@ -81,3 +82,17 @@ def showNet ( cell, netName ): for component in net.getComponents(): print( '| {} bb:{}'.format(component, component.getBoundingBox()) ) return + + +def hpathToName ( path ): + """ + Translate a hierarchical path into a string. This function is to be used + when a flattend name is required. Should be VHDL compliant. + """ + s = '' + while not path.isEmpty(): + head = path.getHeadInstance() + path = path.getTailPath() + if len(s): s += '_' + s += head.getName() + return s