Remove unused buffers in Block & Core2Chip.

Note: The previous strategy was not fully coherent in chip mode.
      Everything added, net and components must be added at
      corona level and not separated between corona and core.

* New: In cumulus/plugins/block.configuration, new FeedsConf object
    to handle the feeds and provide a filling area helper.
* New: In cumulus/plugins/block.spares.removeUnusedbuffers() to
    remove unused buffers in the pools and replace them by feedthrough.
* Change: In cumulus/plugins.block.spares, unify coordinate/slice
    computation. If we are in chip mode, the coordinates are
    expressed in the corona *but* aligned on the slices of the
    *core* model.
* Change: In cumulus/plugins.block.Block.rsave(), add the '_r' suffix
    to the routed cells.
* Change: In cumulus/plugins.clocktree.ClockTree, when in chip mode
    create everything at corona level. Also forgot to set type of
    clock subnet as clock.
This commit is contained in:
Jean-Paul Chaput 2020-10-23 22:28:42 +02:00
parent e6e667c6c7
commit b9f2a5bf28
9 changed files with 206 additions and 65 deletions

View File

@ -401,7 +401,7 @@ class Block ( object ):
print( ' o Building clock tree(s).' )
af = CRL.AllianceFramework.get()
clockNets = []
for net in self.conf.cell.getNets():
for net in self.conf.cellPnR.getNets():
if af.isCLOCK(net.getName()): 'CLOCK: {}'.format(net)
if net.isClock():
trace( 550, '\tBlock.addClockTrees(): Found clock {}.\n'.format(net) )
@ -592,6 +592,7 @@ class Block ( object ):
break
if self.conf.useClockTree: self.splitClocks()
if self.conf.isCoreBlock: self.doConnectCore()
self.spares.removeUnusedBuffers()
status = self.route()
if not self.conf.isCoreBlock:
self.addBlockages()

View File

@ -82,6 +82,7 @@ class ClockTree ( object ):
if qt.isLeaf(): return False
qt.rconnectBuffer()
driverNet = qt.bOutputPlug.getNet()
driverNet.setType( Net.Type.CLOCK )
for leaf in qt.leafs:
leaf.bInputPlug.setNet( driverNet )
self._rconnectHTree( leaf )
@ -135,13 +136,13 @@ class ClockTree ( object ):
gaugeConf.createVertical ( trContact , rightContact , rightX , 0 )
if qt.isRoot():
ckNet = self.clockNet
trace( 550, '\tRemoving any previous pin...\n' )
pins = []
for pin in ckNet.getPins(): pins.append( pin )
for pin in pins:
print( WarningMessage('ClockTree._rrouteHTree(): Removing {}.'.format(pin)) )
pin.destroy()
if not self.spares.conf.isCoreBlock:
trace( 550, '\tRemoving any previous pin...\n' )
pins = []
for pin in ckNet.getPins(): pins.append( pin )
for pin in pins:
print( WarningMessage('ClockTree._rrouteHTree(): Removing {}.'.format(pin)) )
pin.destroy()
layerGauge = gaugeConf.vRoutingGauge
rootContact = gaugeConf.rpAccessByPlugName( qt.buffer, bufferConf.input, ckNet, 0 )
x = gaugeConf.getNearestVerticalTrack ( qt.area, rootContact.getX(), 0 )
@ -187,14 +188,19 @@ class ClockTree ( object ):
quadTree.bufferTag = self.clockNet.getName()
quadTree.rselectBuffer( self.clockIndex, self.clockIndex, 0 )
with UpdateSession():
hyperClock = HyperNet.create( Occurrence(self.clockNet) )
coronaPlugs = []
hyperClock = HyperNet.create( Occurrence(self.clockNet) )
for plugOccurrence in hyperClock.getTerminalNetlistPlugOccurrences():
quadTree.attachToLeaf( plugOccurrence )
if quadTree.isUnderArea(plugOccurrence):
quadTree.attachToLeaf( plugOccurrence )
else:
coronaPlugs.append( plugOccurrence )
quadTree.rsplitNetlist()
if self.spares.conf.isCoreBlock:
plug = utils.getPlugByName( quadTree.buffer, bufferConf.input )
plug.setNet( self.clockNet )
trace( 550, '\tCore mode, setting only root plug "{}"\n'.format(self.clockNet.getName()) )
trace( 550, '\tPlug of "{}"\n'.format(self.clockNet.getName()) )
trace( 550, '\tPlug of "{}" (Cell:{})\n'.format(self.clockNet.getName()
,self.clockNet.getCell()) )
for plug in self.clockNet.getPlugs():
trace( 550, '\t| {}\n'.format(plug) )

View File

@ -16,6 +16,7 @@ from __future__ import print_function
import sys
import re
import os.path
from operator import itemgetter
import Cfg
from Hurricane import Breakpoint
from Hurricane import DbU
@ -596,6 +597,67 @@ class BufferInterface ( object ):
self.count = 0
# ----------------------------------------------------------------------------
# Class : "configuration.FeedsConf".
class FeedsConf ( object ):
"""
Store informations about feed cells and how to fill a gap.
"""
def __init__ ( self, framework ):
trace( 550, ',+', '\tFeedsConf.__init__()\n' )
feeds = Cfg.getParamString('etesian.feedNames').asString().split(',')
self.count = 0
self.feeds = []
for feedName in feeds:
feedCell = framework.getCell( feedName, CRL.Catalog.State.Views )
if not feedCell:
print( WarningMessage( 'FeedConf.__init__(): Feed cell "{}" not found in library (skipped).' \
.format(feedName)) )
continue
feedWidth = feedCell.getAbutmentBox().getWidth()
self.feeds.append( (feedWidth,feedCell) )
self.feeds.sort( key=itemgetter(0) )
self.feeds.reverse()
print( self.feeds )
for i in range(len(self.feeds)):
trace( 550, '\t[{:>2}] {:>10} {}\n' \
.format(i,DbU.getValueString(self.feeds[i][0]),self.feeds[i][1]) )
trace( 550, '-' )
return
def fillAt ( self, cell, transf, gapWidth ):
"""
In ``cell``, fill a *one* row gap starting at ``transf`` position and
of length ``gapWidth``.
"""
x = transf.getTx()
while gapWidth > 0:
feedAdded = False
for i in range(len(self.feeds)):
if self.feeds[i][0] <= gapWidth:
instance = Instance.create( cell, 'spare_feed_{}'.format(self.count), self.feeds[i][1] )
instance.setTransformation( Transformation( x
, transf.getTy()
, transf.getOrientation() ))
instance.setPlacementStatus( Instance.PlacementStatus.FIXED )
gapWidth -= self.feeds[i][0]
x += self.feeds[i][0]
self.count += 1
feedAdded = True
if not feedAdded: break
if gapWidth > 0:
print( WarningMessage( [ 'FeedConf.fillAt(): Unable to fill row gap in "{}".' \
.format(cell.getName())
, ' (@{}, lenght:{})' \
.format(transf,DbU.getValueString(gapWidth))
] ))
def resetFeedCount ( self ):
self.count = 0
# ----------------------------------------------------------------------------
# Class : "configuration.IoPin".
@ -766,6 +828,7 @@ class BlockConf ( GaugeConf ):
self.framework = CRL.AllianceFramework.get()
self.cfg = CfgCache('',Cfg.Parameter.Priority.Interactive)
self.bufferConf = BufferInterface( self.framework )
self.feedsConf = FeedsConf( self.framework )
self.chipConf = ChipConf( self )
self.bColumns = 2
self.bRows = 2
@ -833,6 +896,11 @@ class BlockConf ( GaugeConf ):
@property
def core ( self ): return self.cell
@property
def cellPnR ( self ):
if self.icorona: return self.corona
return self.cell
def setEditor ( self, editor ): self.editor = editor
def refresh ( self, cell=None ):
@ -843,7 +911,7 @@ class BlockConf ( GaugeConf ):
self.editor.fit()
def createBuffer ( self ):
return self.bufferConf.createBuffer( self.cell )
return self.bufferConf.createBuffer( self.cellPnR )
def setDeltaAb ( self, dx1, dy1, dx2, dy2 ):
self.deltaAb = [ dx1, dy1, dx2, dy2 ]

View File

@ -144,20 +144,21 @@ class BufferPool ( object ):
def _createBuffers ( self ):
"""Create the matrix of instances buffer."""
trace( 540, ',+', '\tBufferPool.createBuffers()\n' )
yoffset = 0
if self.quadTree.spares.conf.isCoreBlock:
yoffset = self.quadTree.spares.conf.icore.getTransformation().getTy()
conf = self.quadTree.spares.conf
sliceHeight = conf.sliceHeight
x = self.quadTree.onXPitch( self.quadTree.area.getXCenter()
- (conf.bufferConf.width * self.columns)/2 )
y = self.quadTree.onYSlice( self.quadTree.area.getYCenter()
- (conf.bufferConf.height * self.rows)/2 )
slice = y / sliceHeight
x = self.quadTree.spares.toXPitch( self.quadTree.area.getXCenter()
- (conf.bufferConf.width * self.columns)/2 )
y = self.quadTree.spares.toYSlice( self.quadTree.area.getYCenter()
- (conf.bufferConf.height * self.rows)/2 )
slice = (y - yoffset) / sliceHeight
trace( 540, '\tSlice height: {}\n'.format(DbU.getValueString(sliceHeight)) )
trace( 540, '\tSlice #{} (y:{})\n'.format(slice,DbU.getValueString(y)) )
for row in range(self.rows):
orientation = Transformation.Orientation.ID
y = (slice+row) * sliceHeight
y = (slice+row) * sliceHeight + yoffset
if (slice+row)%2:
orientation = Transformation.Orientation.MY
y += sliceHeight
@ -175,10 +176,22 @@ class BufferPool ( object ):
, trBufAb.getXMax(), trBufAb.getYMax() )
trace( 540, '-' )
def _removeUnuseds ( self ):
conf = self.quadTree.spares.conf
for i in range(self.rows*self.columns):
if not (self.buffers[i][0] & Spares.USED):
trace( 540, '\tRemove Unused Buffer[{}]: {}\n'.format(i,self.buffers[i]) )
cell = self.buffers[i][1].getCell()
transformation = self.buffers[i][1].getTransformation()
gapWidth = self.buffers[i][1].getMasterCell().getAbutmentBox().getWidth()
self.buffers[i][1].destroy()
conf.feedsConf.fillAt( cell, transformation, gapWidth )
def _destroyBuffers ( self ):
"""Destroy all the buffer instances of the pool."""
for flags, buffer in self.buffers:
buffer.destroy()
if buffer is not None:
buffer.destroy()
def getUse ( self ):
"""Return the pool occupancy, a tuple ``(occupancy,capacity)``."""
@ -213,7 +226,11 @@ class QuadTree ( object ):
@staticmethod
def create ( spares ):
root = QuadTree( spares, None, spares.conf.cell.getAbutmentBox() )
area = spares.conf.cell.getAbutmentBox()
if spares.conf.isCoreBlock:
area = spares.conf.core.getAbutmentBox()
spares.conf.icore.getTransformation().applyOn( area )
root = QuadTree( spares, None, area )
root.rpartition()
return root
@ -261,6 +278,13 @@ class QuadTree ( object ):
def __eq__ ( self, other ):
return self.rtag == other.rtag
def removeUnusedBuffers ( self ):
if self.bl: self.bl.removeUnusedBuffers()
if self.br: self.br.removeUnusedBuffers()
if self.tl: self.tl.removeUnusedBuffers()
if self.tr: self.tr.removeUnusedBuffers()
self.pool._removeUnuseds()
def rshowPoolUse ( self ):
rused = 0
rtotal = 0
@ -354,16 +378,6 @@ class QuadTree ( object ):
"""The output Plug of the currently selected buffer in the pool."""
return utils.getPlugByName( self.buffer, self.spares.conf.bufferConf.output )
def onYSlice ( self, y ):
"""Returns the coordinate of the slice immediately inferior to Y."""
modulo = (y - self.area.getYMin()) % self.spares.conf.sliceHeight
return y - modulo
def onXPitch ( self, x ):
"""Returns the coordinate of the pitch immediately inferior to X."""
modulo = (x - self.area.getXMin()) % self.spares.conf.sliceStep
return x - modulo
def selectFree ( self ):
"""
Returns the first free buffer *instance* in the pool or None if
@ -382,7 +396,7 @@ class QuadTree ( object ):
trace( 540, '\tQuadTree.connectBuffer(): rtag:"{}"\n'.format(self.rtag) )
plug = self.bOutputPlug
if not plug.getNet():
outputNetBuff = Net.create( self.spares.conf.cell,'{}_{}' \
outputNetBuff = Net.create( self.spares.conf.cellPnR,'{}_{}' \
.format(self.root.bufferTag,self.rtag) )
plug.setNet( outputNetBuff )
trace( 540, '\t| {}\n'.format(plug) )
@ -429,7 +443,7 @@ class QuadTree ( object ):
return False
if aspectRatio < 0.5:
self.ycut = self.spares.toYGCellGrid( self.area.getYMin() + self.area.getHeight()/2 )
self.ycut = self.spares.toYSlice( self.area.getYMin() + self.area.getHeight()/2 )
self.bl = QuadTree( self.spares
, self
, Box( self.area.getXMin()
@ -448,7 +462,7 @@ class QuadTree ( object ):
trace( 540, '-' )
return True
elif aspectRatio > 2.0:
self.xcut = self.spares.toXGCellGrid( self.area.getXMin() + self.area.getWidth()/2 )
self.xcut = self.spares.toXPitch( self.area.getXMin() + self.area.getWidth()/2 )
self.bl = QuadTree( self.spares
, self
, Box( self.area.getXMin()
@ -467,8 +481,8 @@ class QuadTree ( object ):
trace( 540, '-' )
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.ycut = self.spares.toYSlice( self.area.getYMin() + self.area.getHeight()/2 )
self.xcut = self.spares.toXPitch( self.area.getXMin() + self.area.getWidth ()/2 )
self.bl = QuadTree( self.spares
, self
, Box( self.area.getXMin()
@ -648,6 +662,9 @@ class QuadTree ( object ):
position = plugOccurrence.getBoundingBox().getCenter()
self.getLeafUnder(position).plugs.append( plugOccurrence )
def isUnderArea ( self, plugOccurrence ):
return self.area.contains( plugOccurrence.getBoundingBox().getCenter() )
def splitNetlist ( self ):
"""
Reorganize the netlist by connecting all plugs to the output of the
@ -665,15 +682,15 @@ class QuadTree ( object ):
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 the plug list is empty, the buffer will still be used and it's
output net created.
"""
if not self.plugs: return
trace( 540, ',+', '\tQuadTree.spliNetlist()\n' )
self.connectBuffer( doLeaf=True )
netBuff = self.bOutputPlug.getNet()
trace( 540, '\tBuffer: {}\n'.format(self.buffer) )
trace( 540, '\tBuffer output: {}\n'.format(netBuff) )
if not self.plugs: return
for plug in self.plugs:
trace( 540, '\t| Leaf: {}\n'.format(plug) )
trace( 540, '\t| netBuff: {}\n'.format(netBuff) )
@ -743,15 +760,32 @@ class Spares ( object ):
raise ErrorMessage( 3, 'Spares.getSpareSpaceMargin(): Spare leaf area is zero.' )
return (float(bufferLength) * 1.4) / float(areaLength)
def toXGCellGrid ( self, x ):
"""Find the nearest X (inferior) on the Cell gauge grid (sliceStep)."""
dx = x - self.conf.xMin
return self.conf.xMin + (dx - dx % self.conf.sliceStep)
def toXPitch ( self, x ):
"""
Returns the coordinate of the pitch immediately inferior to X.
If we are in a chip, compute coordinate in the *corona* system.
"""
offset = 0
area = self.conf.coreAb
if self.conf.isCoreBlock:
offset = self.conf.icore.getTransformation().getTx()
self.conf.icore.getTransformation().applyOn( area )
modulo = (x - offset - area.getXMin()) % self.conf.sliceStep
return x - modulo
def toYGCellGrid ( self, y ):
"""Find the nearest Y (inferior) on the Cell gauge grid (sliceHeight)."""
dy = y - self.conf.yMin
return self.conf.yMin + (dy - dy % self.conf.sliceHeight)
def toYSlice ( self, y ):
"""
Returns the coordinate of the slice immediately inferior to Y.
If we are in a chip, compute coordinate in the *corona* system.
"""
offset = 0
area = self.conf.coreAb
if self.conf.isCoreBlock:
offset = self.conf.icore.getTransformation().getTy()
self.conf.icore.getTransformation().applyOn( area )
trace( 540, '\toffset:\n'.format(DbU.getValueString(offset)) )
modulo = (y - offset - area.getYMin()) % self.conf.sliceHeight
return y - modulo
def build ( self ):
if not self.conf.useSpares: return
@ -773,8 +807,8 @@ class Spares ( object ):
trace( 540, ',+', '\tSpares.addStrayBuffer()\n' )
sliceHeight = self.conf.sliceHeight
x = self.quadTree.onXPitch( position.getX() )
y = self.quadTree.onYSlice( position.getY() )
x = self.quadTree.toXPitch( position.getX() )
y = self.quadTree.toYSlice( position.getY() )
slice = y / sliceHeight
orientation = Transformation.Orientation.ID
y = slice * sliceHeight
@ -845,6 +879,7 @@ class Spares ( object ):
*plug master net* and the *tail path*.
"""
trace( 540, '\tSpares.raddTransNet() top:{} path:{}\n'.format(topNet,path) )
if path.isEmpty():
self.addClonedCell( topNet.getCell() )
return None
@ -853,6 +888,7 @@ class Spares ( object ):
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() )
@ -866,9 +902,14 @@ class Spares ( object ):
self.addClonedCell( headInstance.getCell() )
else:
masterNet = headPlug.getMasterNet()
trace( 540, '\ttailPath {}\n'.format(tailPath) )
if tailPath.isEmpty(): return headPlug
return self.raddTransNet( masterNet, tailPath )
def removeUnusedBuffers ( self ):
with UpdateSession():
self.quadTree.removeUnusedBuffers()
def rsave ( self, cell ):
"""
Save the complete cell hierarchy. Saves only the physical view, except
@ -896,5 +937,7 @@ class Spares ( object ):
for cell in self.cloneds:
trace( 550, '\tRenaming cloned cell: "{}"\n'.format(cell) )
cell.setName( cell.getName()+'_cts' )
if self.conf.chip is None:
self.conf.cell.setName( self.conf.cell.getName()+'_r' )
self.rsave( self.conf.cell )
return

View File

@ -148,6 +148,8 @@ class Chip ( Block ):
if not self.conf.validated:
raise ErrorMessage( 1, 'chip.save(): Chip is not valid, aborting.' )
super(Chip,self).save()
self.conf.corona.setName( self.conf.corona.getName()+'_r' )
self.conf.chip .setName( self.conf.chip .getName()+'_r' )
af = CRL.AllianceFramework.get()
af.saveCell( self.conf.corona, CRL.Catalog.State.Views )
af.saveCell( self.conf.chip , CRL.Catalog.State.Views )

View File

@ -158,7 +158,7 @@ class ChipConf ( BlockConf ):
#self.checkPads()
#self.checkCorona()
#self.computeChipSize()
#self.checkChipSize()
self.checkChipSize()
self.findPowerAndClockNets()
return
@ -577,6 +577,22 @@ class ChipConf ( BlockConf ):
return
def checkChipSize ( self ):
print( 'checkChipSize' )
if self.chipSize[0] % self.sliceStep:
print( WarningMessage( 'ChipConf.checkChipSize(): Width of "{}" ({})is not on sliceStep ({}), ajusted.' \
.format( self.chipConf.name
, DbU.getValueString(self.chipSize[0])
, DbU.getValueString(self.sliceStep))) )
adjust = self.sliceStep - self.chipSize[0] % self.sliceStep
self.chipSize = (self.chipSize[0] + adjust, self.chipSize[1])
if self.chipSize[1] % self.sliceStep:
print( WarningMessage( 'ChipConf.checkChipSize(): Height of "{}" ({})is not on sliceStep ({}), ajusted.' \
.format( self.chipConf.name
, DbU.getValueString(self.chipSize[1])
, DbU.getValueString(self.sliceStep))) )
adjust = self.sliceStep - self.chipSize[1] % self.sliceStep
self.chipSize = (self.chipSize[0], self.chipSize[1] + adjust)
#if self._coreSize.isEmpty(): return
#
#minWidth = self._coreSize.getWidth () + self._minCorona + 2*self._padHeight

View File

@ -488,6 +488,8 @@ class Builder ( object ):
def __init__ ( self, block ):
self.block = block
self.innerBb = self.block.bb
print( 'Builder.__init__(): innerBb: {}'.format(self.innerBb) )
print( self.block.path.getTransformation())
self.block.path.getTransformation().applyOn( self.innerBb )
self.innerBb.inflate( self.hRailSpace/2, self.vRailSpace/2 )
self.southSide = SouthSide( self )

View File

@ -180,7 +180,7 @@ class Builder ( object ):
def __init__ ( self, conf ):
self.conf = conf
self.path = Path( self.conf.icore )
self.block = self.path.getTailInstance().getMasterCell()
self.block = self.conf.icore.getMasterCell()
self.bb = self.block.getAbutmentBox()
self.planes = {}
self.activePlane = None
@ -211,27 +211,30 @@ class Builder ( object ):
raise ErrorMessage( 1, 'Cannot build clock terminal as ck is not known.' )
return
blockCk = None
for plug in self.path.getTailInstance().getPlugs():
for plug in self.conf.icore.getPlugs():
if plug.getNet() == self.conf.coronaCk:
blockCk = plug.getMasterNet()
if not blockCk:
raise ErrorMessage( 1, 'Block "{}" has no net connected to the clock "{}".' \
.format(self.path.getTailInstance().getName(),self.ck.getName()) )
.format(self.conf.icore.getName(),self.conf.coronaCk.getName()) )
return
htPlugs = []
ffPlugs = []
for plug in blockCk.getPlugs():
if not plug.getInstance().isTerminalNetlist(): continue
htPlugs.append( plug )
for plug in self.conf.coronaCk.getPlugs():
print( plug )
if plug.getInstance().isTerminalNetlist():
htPlugs.append( plug )
if len(htPlugs) != 1:
message = 'Clock "{}" of block "{}" is not organized as a H-Tree.' \
.format(blockCk.getName(),self.path.getTailInstance().getName())
message = [ 'Clock "{}" of block "{}" is not organized as a H-Tree ({} plugs).' \
.format( self.conf.coronaCk.getName()
, self.conf.icore.getName()
, len(htPlugs)) ]
print( self.conf.icore )
for plug in htPlugs:
message += '\n - {}'.format(plug)
message += [ '\n - {} {}'.format(plug,plug.getInstance()) ]
raise ErrorMessage( 1, message )
return
with UpdateSession():
bufferRp = self.conf.rpAccessByOccurrence( Occurrence(htPlugs[0], self.path)
bufferRp = self.conf.rpAccessByOccurrence( Occurrence(htPlugs[0], Path())
, self.conf.coronaCk
, 0 )
blockAb = self.block.getAbutmentBox()

View File

@ -248,8 +248,8 @@ class IoPad ( object ):
@property
def pads ( self ):
print( self.ioPadConf )
return self.ioPadConf.pads
print( self.ioPadConf )
return self.ioPadConf.pads
def __str__ ( self ):
s = '<IoPad "{}" '.format(self.padInstanceName)