From e3803d28d7ad9f6a87e3c1b11ea45a2159d23a32 Mon Sep 17 00:00:00 2001 From: Jean-Paul Chaput Date: Sun, 27 Dec 2020 12:31:43 +0100 Subject: [PATCH] Simple HFNS (#4), break the net, regardless of placement. * New: In cumulus/plugins.block.hfns4.py, perform simple HFNS by breaking the net into sub-nets of at most 10 sinks (hard-coded for now). As this method is called *after* the netlist as been virtually flattened, we have to create the RoutingPad at the top level ourselves. Sub-nets are created at the Cell top-level (same approach as for clock synthesis, because there is no smart way to guess where they should be). * New: In cumulus/plugins.block.block.py, perform HFNS (#4) *before* doing placement. To see the real sink count on each net, we must perform the virtual net flattening first (Cell::flattenNets()). * Change: In cumulus/plugins.block.configuration, allow the creation of spare buffer in any cell (instead of only "self.cellPnR"). * Change: In cumulus/plugins.block.spares.Spares.raddTransNet(), Check if intermediate masterNet exists in Cells before trying to blindly re-create it. --- cumulus/src/CMakeLists.txt | 1 + cumulus/src/plugins/alpha/block/block.py | 52 ++- .../src/plugins/alpha/block/configuration.py | 9 +- cumulus/src/plugins/alpha/block/hfns4.py | 303 ++++++++++++++++++ cumulus/src/plugins/alpha/block/spares.py | 23 +- 5 files changed, 374 insertions(+), 14 deletions(-) create mode 100644 cumulus/src/plugins/alpha/block/hfns4.py diff --git a/cumulus/src/CMakeLists.txt b/cumulus/src/CMakeLists.txt index 0a8ad3db..d781d743 100644 --- a/cumulus/src/CMakeLists.txt +++ b/cumulus/src/CMakeLists.txt @@ -58,6 +58,7 @@ ${CMAKE_CURRENT_SOURCE_DIR}/plugins/alpha/block/hfns1.py ${CMAKE_CURRENT_SOURCE_DIR}/plugins/alpha/block/hfns2.py ${CMAKE_CURRENT_SOURCE_DIR}/plugins/alpha/block/hfns3.py + ${CMAKE_CURRENT_SOURCE_DIR}/plugins/alpha/block/hfns4.py ) set ( pyPluginAlphaC2C ${CMAKE_CURRENT_SOURCE_DIR}/plugins/alpha/core2chip/__init__.py ${CMAKE_CURRENT_SOURCE_DIR}/plugins/alpha/core2chip/core2chip.py diff --git a/cumulus/src/plugins/alpha/block/block.py b/cumulus/src/plugins/alpha/block/block.py index 6ad41aa9..2a485290 100644 --- a/cumulus/src/plugins/alpha/block/block.py +++ b/cumulus/src/plugins/alpha/block/block.py @@ -21,7 +21,7 @@ from Hurricane import Breakpoint, DbU, Box, Transformation, Point, \ Box, Path, Layer, Occurrence, Net, \ NetExternalComponents, RoutingPad, Pad, \ Horizontal, Vertical, Contact, Pin, Plug, \ - Instance + Cell, Instance import CRL from CRL import RoutingLayerGauge from helpers import trace, dots @@ -37,7 +37,8 @@ from alpha.block.spares import Spares from alpha.block.clocktree import ClockTree #from alpha.block.hfns1 import BufferTree #from alpha.block.hfns2 import BufferTree -from alpha.block.hfns3 import BufferTree +#from alpha.block.hfns3 import BufferTree +from alpha.block.hfns4 import BufferTree from alpha.block.configuration import IoPin, BlockConf, GaugeConf timing.staticInit() @@ -407,6 +408,44 @@ class Block ( object ): for clockTree in self.clockTrees: clockTree.splitClock() + def findHfnTrees4 ( self ): + """Perform simple HFNS, just break nets regardless of placement.""" + print( ' o Building high fanout nets trees.' ) + if self.spares: + if self.conf.isCoreBlock: + self.conf.corona.flattenNets( self.conf.icore, Cell.Flags_NoClockFlatten ) + else: + self.conf.cell.flattenNets( None, Cell.Flags_NoClockFlatten ) + beginCount = self.conf.bufferConf.count + maxSinks = 10 + dots( 82 + , ' - Max sinks for buffer "{}"'.format(self.conf.bufferConf.name) + , ' {}'.format(maxSinks) ) + nets = [] + block = self.conf.corona if self.conf.isCoreBlock else self.conf.cell + for net in block.getNets(): + sinksCount = 0 + for rp in net.getRoutingPads(): sinksCount += 1 + if sinksCount > maxSinks: + nets.append( (net,sinksCount) ) + with UpdateSession(): + for net,sinksCount in nets: + trace( 550, '\tBlock.addHfnTrees4(): 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() + Breakpoint.stop( 100, 'block.findHfnTrees4() done.' ) + else: + print( ' (No spares buffers, disabled)' ) + endCount = self.conf.bufferConf.count + dots( 82, ' - Added buffers', ' {}'.format(endCount-beginCount) ) + return len(self.hfnTrees) + def findHfnTrees ( self ): """Create the trunk of all the high fanout nets.""" print( ' o Building high fanout nets trees.' ) @@ -486,9 +525,13 @@ class Block ( object ): side.expand() def place ( self ): + editor = self.conf.editor if self.conf.isCoreBlock: etesian = Etesian.EtesianEngine.create( self.conf.corona ) etesian.setBlock( self.conf.icore ) + if editor: + editor.setCell( self.conf.cell ) + Breakpoint.stop( 100, 'Block.place(), corona loaded.') else: etesian = Etesian.EtesianEngine.create( self.conf.cell ) etesian.place() @@ -566,12 +609,13 @@ class Block ( object ): self.placeIoPins() self.checkIoPins() self.spares.build() + if self.conf.useHFNS: self.findHfnTrees4() if self.conf.useClockTree: self.addClockTrees() - if self.conf.useHFNS: self.addHfnBuffers() + #if self.conf.useHFNS: self.addHfnBuffers() if editor: editor.fit() #Breakpoint.stop( 0, 'Clock tree(s) done.' ) self.place() - if self.conf.useHFNS: self.findHfnTrees() + #if self.conf.useHFNS: self.findHfnTrees() break if self.conf.useClockTree: self.splitClocks() if self.conf.isCoreBlock: self.doConnectCore() diff --git a/cumulus/src/plugins/alpha/block/configuration.py b/cumulus/src/plugins/alpha/block/configuration.py index c256b172..60970d2a 100644 --- a/cumulus/src/plugins/alpha/block/configuration.py +++ b/cumulus/src/plugins/alpha/block/configuration.py @@ -719,6 +719,10 @@ class BufferConf ( object ): where ```` is an ever incrementing counter (self.count). """ instance = Instance.create( cell, 'spare_buffer_{}'.format(self.count), self.masterCell ) + trace( 550, '\tBufferConf.createBuffer(): cell={}, instance={}\n' \ + .format( cell, instance )) + trace( 550, '\tplug={}\n'.format( instance.getPlug( self.masterCell.getNet('q') ) )) + trace( 550, '\tplug.getCell()={}\n'.format( instance.getPlug( self.masterCell.getNet('q') ).getCell() )) self.count += 1 return instance @@ -1155,8 +1159,9 @@ class BlockConf ( GaugeConf ): self.editor.setCell( cell ) self.editor.fit() - def createBuffer ( self ): - return self.bufferConf.createBuffer( self.cellPnR ) + def createBuffer ( self, cell=None ): + if cell is None: cell = self.cellPnR + return self.bufferConf.createBuffer( cell ) def createFeed ( self ): return self.feedsConf.createFeed( self.cellPnR ) diff --git a/cumulus/src/plugins/alpha/block/hfns4.py b/cumulus/src/plugins/alpha/block/hfns4.py new file mode 100644 index 00000000..fbffc5b8 --- /dev/null +++ b/cumulus/src/plugins/alpha/block/hfns4.py @@ -0,0 +1,303 @@ +# +# 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). + +Fourth variant, quck and simple. Just break the net into subnets, +so each of them is under the fanout threshold. Basic method, do not +take into account the placement or the global wirelength. +""" + +from __future__ import print_function +import sys +import os.path +import re +from operator import itemgetter, attrgetter, methodcaller +import Cfg +from Hurricane import Breakpoint, DbU, Box, Transformation, Box, \ + Path, Layer, Occurrence, Net, HyperNet, \ + RoutingPad, Horizontal, Vertical, Contact, \ + Pin, Plug, Instance +import CRL +from CRL import RoutingLayerGauge +from helpers import trace, l, u, n +from helpers.io import ErrorMessage, WarningMessage, catch +from helpers.overlay import UpdateSession +from plugins import getParameter, utils +from plugins.alpha.block.configuration import GaugeConf +from plugins.alpha.block.spares import Spares + + +af = CRL.AllianceFramework.get() + + +# ---------------------------------------------------------------------------- +# Class : "hfns.Cluster". + +class Cluster ( object ): + """ + Cluster of routing pads, with one driver. + """ + + def __init__ ( self, bufferTree, depth ): + self.bufferTree = bufferTree + self.buffer = None + self.depth = depth + self.anchors = [ ] + self.childs = [ ] + self.parent = None + self.bInputRp = None + self.bOutputRp = None + trace( 550, '\tCluster.__init__(), depth={}\n'.format( self.depth )) + + @property + def conf ( self ): + return self.bufferTree.conf + + @property + def bInputPlug ( self ): + """The input Plug of the buffer.""" + return utils.getPlugByName( self.buffer, self.conf.bufferConf.input ) + + @property + def bOutputPlug ( self ): + """The output Plug of the buffer.""" + return utils.getPlugByName( self.buffer, self.conf.bufferConf.output ) + + @property + def size ( self ): + return len(self.anchors) + + def isRoot ( self ): return self.parent is None + + def addAnchor ( self, anchor ): + """Add an anchor. Can be RoutingPad (depth==0) or Cluster (depth>0).""" + if isinstance(anchor,Cluster) and self.depth == 0: + print( WarningMessage( 'Cluster.addAnchor(): Should not add a cluster at leaf level.' ) ) + elif isinstance(anchor,RoutingPad) and self.depth > 0: + print( WarningMessage( 'Cluster.addAnchor(): Should not add a RoutingPad at non-leaf level.' ) ) + self.anchors.append( anchor ) + trace( 550, '\tCluster.addAnchor(), size={} anchor={}\n'.format( self.size, anchor )) + + def createBufInputRp ( self, net ): + """Create a RoutingPad for the buffer input Plug (terminal).""" + blockNet = self.createTransNet( net, Path(self.conf.icore) ) + self.bInputPlug.setNet( blockNet ) + self.bInputRp = RoutingPad.create( net + , Occurrence( self.bInputPlug, Path(self.conf.icore) ) + , RoutingPad.BiggestArea ) + trace( 550, '\tCluster.createBufInputRp(): rp={}\n'.format(self.bInputRp) ) + return self.bInputRp + + def createBufOutputRp ( self, net ): + """Create a RoutingPad for the buffer output Plug (terminal).""" + blockNet = self.createTransNet( net, Path(self.conf.icore) ) + self.bOutputPlug.setNet( blockNet ) + self.bOutputRp = RoutingPad.create( net + , Occurrence( self.bOutputPlug, Path(self.conf.icore) ) + , RoutingPad.BiggestArea ) + trace( 550, '\tCluster.createBufOutputRp(): rp={}\n'.format(self.bOutputRp) ) + return self.bOutputRp + + 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 cluster's buffer.""" + self.buffer = self.conf.createBuffer( self.conf.cell ) + + def createTransNet ( self, topNet, path ): + trace( 550, '\tCluster.createTransNet(): topNet={}, path={}\n'.format(topNet,path) ) + bottomPlug = self.bufferTree.raddTransNet( topNet, path ) + bottomNet = bottomPlug.getMasterNet() if bottomPlug else topNet + trace( 550, '\tbottomNet: "{}"\n'.format(bottomNet) ) + return bottomNet + + def splitNet ( self ): + """ + Perform the actual splitting of the net into subnets. + """ + driverNet = self.bufferTree.createSubNet() + self.createBuffer() + self.createBufOutputRp( driverNet ) + trace( 550, ',+', '\tCluster.splitNet(), size:{} depth:{} driver:{}\n' \ + .format(self.size,self.depth,driverNet.getName()) ) + if len(self.anchors) > 30: + print( WarningMessage( 'Cluster.splitNet(): Top cluster of "{}" still has {} sinks.' \ + .format(driverNet.getName(),len(self.mergedAnchors)) )) + for anchor in self.anchors: + if isinstance(anchor,Cluster): + anchor.createBufInputRp( driverNet ) + else: + plug = anchor.getPlugOccurrence() + deepPlug = self.bufferTree.raddTransNet( driverNet, plug.getPath() ) + deepNetBuff = deepPlug.getMasterNet() if deepPlug else driverNet + 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() + rp = RoutingPad.create( driverNet, plug, RoutingPad.BiggestArea ) + for component in driverNet.getComponents(): + trace( 550, '\t| {}\n'.format(component) ) + trace( 550, ',-' ) + + def show ( self ): + """Select the RoutingPad of the cluster in the editor.""" + editor = self.bufferTree.spares.conf.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.BufferTree". + +class BufferTree ( object ): + """ + Recursively break down a Net with a huge fanout. Works at pure netlist + level, do not take placement or wirelength into account. + """ + + 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.bufName = self.conf.bufferConf.name + 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 conf ( self ): + return self.spares.conf + + @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] + + 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 ) + topCell = self.conf.cellPnR + net = Net.create( topCell, subNetName ) + self.netCount += 1 + return net + + def raddTransNet ( self, net, path ): + return self.spares.raddTransNet( net, path ) + + def rpartition ( self ): + """ + Recursively partition the net. + """ + trace( 550, ',+', '\tBufferTree.rpartition() 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: + if len(self.clusters[0]) == 0 or self.clusters[0][-1].size >= 10: + self.clusters[0].append( Cluster(self,self.clusterDepth) ) + self.clusters[0][-1].addAnchor( rp ) + else: + trace( 550, '\tDriver:{}.\n'.format(rp) ) + self.rpDriver = rp + if pinRp: + if self.rpDriver is None: + trace( 550, '\tDriver (external pin):{}.\n'.format(rp) ) + self.rpDriver = rp + else: + self.clusters[0][-1].addAnchor( pinRp ) + while len(self.clusters[-1]) > 1: + for cluster in self.clusters[self.clusterDepth]: + cluster.splitNet() + if len(self.clusters) < self.clusterDepth+2: + self.clusters.append( [] ) + self.clusters[-1].append( Cluster(self,self.clusterDepth+1) ) + if self.clusters[-1][-1].size >= 10: + self.clusters[-1].append( Cluster(self,self.clusterDepth+1) ) + self.clusters[-1][-1].addAnchor( cluster ) + self.clusterDepth += 1 + self.clusters[-1][-1].splitNet() + trace( 550, '-' ) + + def _splitNet ( self ): + """ + Perform the actual splitting of the net into sub-trees. Mostly calls + ``BufferTree.rpartition()`` then connect the top cluster root to the original + signal. + """ + self.rpartition() + if self.isDeepNet: + # Must convert from a DeepNet into a real top Net to be saved. + topCell = self.conf.corona if self.conf.isCoreBlock else self.conf.cell + topNetName = self.net.getName() + driverRpOcc = self.rpDriver.getPlugOccurrence() + self.net.destroy() + self.net = Net.create( topCell, topNetName ) + deepPlug = self.spares.raddTransNet( self.net, driverRpOcc.getPath() ) + deepDriverNet = deepPlug.getMasterNet() + driverRpOcc.getEntity().setNet( deepDriverNet ) + rp = RoutingPad.create( self.net, driverRpOcc, RoutingPad.BiggestArea ) + trace( 550, '\tBufferTree._splitNet(): rp={}\n'.format(rp) ) + self.root.setRootDriver( self.net ) + trace( 550, '\tRoot input: {}\n'.format(self.root.bInputPlug) ) + + def buildBTree ( self ): + self._splitNet() diff --git a/cumulus/src/plugins/alpha/block/spares.py b/cumulus/src/plugins/alpha/block/spares.py index 171cfeeb..2975a046 100644 --- a/cumulus/src/plugins/alpha/block/spares.py +++ b/cumulus/src/plugins/alpha/block/spares.py @@ -928,20 +928,25 @@ class Spares ( object ): *plug master net* and the *tail path*. """ - trace( 540, '\tSpares.raddTransNet() top:{} path:{}\n'.format(topNet,path) ) + trace( 540, ',+', '\tSpares.raddTransNet() top:{} path:{}\n'.format(topNet,path) ) if path.isEmpty(): self.conf.addClonedCell( topNet.getCell() ) + trace( 540, '-' ) return None tailPath = path.getTailPath() headInstance = path.getHeadInstance() headPlug = utils.getPlugByNet(headInstance,topNet) if not headPlug: masterCell = headInstance.getMasterCell() - trace( 540, '\tcreate Plug in {}\n'.format(headInstance) ) - masterNet = Net.create( masterCell, topNet.getName() ) - masterNet.setExternal ( True ) - masterNet.setType ( topNet.getType() ) - masterNet.setDirection( Net.Direction.IN ) + masterNet = masterCell.getNet( topNet.getName() ) + trace( 540, '\tmasterCell {}\n'.format(masterCell) ) + if masterNet is None: + trace( 540, '\tcreate Plug in {}\n'.format(headInstance) ) + masterNet = Net.create( masterCell, topNet.getName() ) + masterNet.setType ( topNet.getType() ) + masterNet.setDirection( Net.Direction.IN ) + trace( 540, '\tmasterNet {}\n'.format(masterNet) ) + masterNet.setExternal( True ) headPlug = headInstance.getPlug( masterNet ) if not headPlug: raise ErrorMessage( 3, 'Plug not created for %s on instance %s of %s' \ @@ -952,8 +957,10 @@ class Spares ( object ): else: masterNet = headPlug.getMasterNet() trace( 540, '\ttailPath {}\n'.format(tailPath) ) - if tailPath.isEmpty(): return headPlug - return self.raddTransNet( masterNet, tailPath ) + if not tailPath.isEmpty(): + headPlug = self.raddTransNet( masterNet, tailPath ) + trace( 540, '-' ) + return headPlug def removeUnusedBuffers ( self ): with UpdateSession():