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.
This commit is contained in:
Jean-Paul Chaput 2020-12-27 12:31:43 +01:00
parent b0ae6be652
commit e3803d28d7
5 changed files with 374 additions and 14 deletions

View File

@ -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

View File

@ -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()

View File

@ -719,6 +719,10 @@ class BufferConf ( object ):
where ``<Nb>`` 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 )

View File

@ -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<name>.*)\((?P<index>\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()

View File

@ -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()
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.setExternal ( True )
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():