Added plugin for placing Yosys generated SRAM (failed experiment).
This commit is contained in:
parent
8980a01dae
commit
35f73ecec3
|
@ -13,6 +13,8 @@
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/plugins/chiproute.py
|
${CMAKE_CURRENT_SOURCE_DIR}/plugins/chiproute.py
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/plugins/conductor.py
|
${CMAKE_CURRENT_SOURCE_DIR}/plugins/conductor.py
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/plugins/matrixplacer.py
|
${CMAKE_CURRENT_SOURCE_DIR}/plugins/matrixplacer.py
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/plugins/sramplacer1.py
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/plugins/sramplacer2.py
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/plugins/block.py
|
${CMAKE_CURRENT_SOURCE_DIR}/plugins/block.py
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/plugins/rsave.py
|
${CMAKE_CURRENT_SOURCE_DIR}/plugins/rsave.py
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/plugins/rsaveall.py
|
${CMAKE_CURRENT_SOURCE_DIR}/plugins/rsaveall.py
|
||||||
|
|
|
@ -0,0 +1,738 @@
|
||||||
|
|
||||||
|
# This file is part of the Coriolis Software.
|
||||||
|
# Copyright (c) Sorbonne Université 2022-2022, 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/sramplacer1.py" |
|
||||||
|
# +-----------------------------------------------------------------+
|
||||||
|
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
import traceback
|
||||||
|
import helpers
|
||||||
|
from helpers.io import ErrorMessage, WarningMessage
|
||||||
|
from helpers.overlay import UpdateSession
|
||||||
|
from helpers import trace, l, u, n
|
||||||
|
import plugins
|
||||||
|
from Hurricane import Breakpoint, DbU, Box, Net, Cell, Instance, \
|
||||||
|
Transformation, PythonAttributes
|
||||||
|
from Foehn import FoehnEngine, DagExtension
|
||||||
|
from plugins.chip.configuration import GaugeConf
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Automatic placement of a Yosys generated SRAM
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
2-D matrix like flavor.
|
||||||
|
|
||||||
|
For the documentation of this trial, please refer to sramplacer2.py.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# Class : StdCellConf.
|
||||||
|
|
||||||
|
class StdCellConf ( object ):
|
||||||
|
"""
|
||||||
|
Gives meta informations about the standard cell library.
|
||||||
|
"""
|
||||||
|
|
||||||
|
reDFF = re.compile( r'^sff' )
|
||||||
|
reMux = re.compile( r'^n?mx[0-9]_' )
|
||||||
|
reDataIn = re.compile( r'^i[0-9]?' )
|
||||||
|
reDataOut = re.compile( r'^n?q' )
|
||||||
|
|
||||||
|
def isRegister ( self, cell ):
|
||||||
|
"""Returns True if the cell is a register."""
|
||||||
|
m = StdCellConf.reDFF.match( cell.getName() )
|
||||||
|
if m: return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def isMux ( self, cell ):
|
||||||
|
"""Returns True if the cell is a multiplexer."""
|
||||||
|
m = StdCellConf.reMux.match( cell.getName() )
|
||||||
|
if m: return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def isDataIn ( self, net ):
|
||||||
|
"""Returns True if the net is a data input."""
|
||||||
|
m = StdCellConf.reDataIn.match( net.getName() )
|
||||||
|
if m:
|
||||||
|
if not (net.getDirection() & Net.Direction.DirIn):
|
||||||
|
print( Warning( [ 'StdCellConf.isDataIn(): Match input pattern "^i[0_9]" but not in input direction.'
|
||||||
|
, 'On {}'.format(net) ] ))
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def isDataOut ( self, net ):
|
||||||
|
"""Returns True if the net is a data output."""
|
||||||
|
m = StdCellConf.reDataOut.match( net.getName() )
|
||||||
|
if m:
|
||||||
|
if not (net.getDirection() & Net.Direction.DirOut):
|
||||||
|
print( Warning( [ 'StdCellConf.isDataOut(): Match output pattern "^i[0_9]" but not in output direction.'
|
||||||
|
, 'On {}'.format(net) ] ))
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def isData ( self, net ):
|
||||||
|
"""Returns True if the net is a data flow (i.e. not a control)."""
|
||||||
|
return self.isDataIn(net) or self.isDataOut(net)
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# Class : DAG.
|
||||||
|
|
||||||
|
class DAG ( object ):
|
||||||
|
|
||||||
|
def __init__ ( self, rootInst ):
|
||||||
|
self.rootInst = rootInst
|
||||||
|
self.reacheds = [ rootInst ]
|
||||||
|
self.processeds = []
|
||||||
|
self.tree = []
|
||||||
|
self.propagDir = Net.Direction.DirIn
|
||||||
|
self.externalNets = []
|
||||||
|
self.libConf = StdCellConf()
|
||||||
|
|
||||||
|
def _isInPropagationDir ( self, plug ):
|
||||||
|
trace( 600, ',+', '\tDAG._isInPropagationDir() on {}\n'.format( plug ))
|
||||||
|
masterNet = plug.getMasterNet()
|
||||||
|
if masterNet.isSupply():
|
||||||
|
trace( 600, ',-', '\tFalse (Supply) {}\n'.format( masterNet ))
|
||||||
|
return False
|
||||||
|
if masterNet.isClock():
|
||||||
|
trace( 600, ',-', '\tFalse (Clock) {}\n'.format( masterNet ))
|
||||||
|
return False
|
||||||
|
if (masterNet.getDirection() & self.propagDir) ^ self.propagDir:
|
||||||
|
trace( 600, ',-', '\tFalse {}\n'.format( masterNet ))
|
||||||
|
return False
|
||||||
|
if not self.libConf.isData(masterNet):
|
||||||
|
trace( 600, ',-', '\tFalse (not data-flow) {}\n'.format( masterNet ))
|
||||||
|
return False
|
||||||
|
trace( 600, ',-', '\tTrue {}\n'.format( masterNet ))
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _processInst ( self ):
|
||||||
|
rvalue = False
|
||||||
|
if not self.reacheds:
|
||||||
|
return rvalue
|
||||||
|
current = self.reacheds[ 0 ]
|
||||||
|
trace( 600, ',+', '\tDAG._processInst() on {}\n'.format(current) )
|
||||||
|
self.reacheds.remove( current )
|
||||||
|
self.processeds.append( current )
|
||||||
|
for currentPlug in current.getPlugs():
|
||||||
|
trace( 600, ',+', '\tLooking for {}\n'.format( currentPlug ))
|
||||||
|
if not self._isInPropagationDir(currentPlug):
|
||||||
|
trace( 600, ',-', '\tMaster net not in propag dir {}\n'.format( currentPlug.getMasterNet() ))
|
||||||
|
continue
|
||||||
|
net = currentPlug.getNet()
|
||||||
|
trace( 600, ',+', '\tReached {}\n'.format( net ))
|
||||||
|
if net.isExternal():
|
||||||
|
trace( 600, ',--', '\tExternal net reached: {}\n'.format( net ))
|
||||||
|
self.externalNets.append( net )
|
||||||
|
continue
|
||||||
|
for plug in net.getPlugs():
|
||||||
|
if plug == currentPlug:
|
||||||
|
continue
|
||||||
|
if self._isInPropagationDir(plug):
|
||||||
|
continue
|
||||||
|
reachedInst = plug.getInstance()
|
||||||
|
if not reachedInst in self.reacheds and not reachedInst in self.processeds:
|
||||||
|
trace( 600, '\tInstance reached: {}\n'.format( reachedInst ))
|
||||||
|
self.reacheds.append( reachedInst )
|
||||||
|
rvalue = True
|
||||||
|
trace( 600, ',--' )
|
||||||
|
trace( 600, ',-', '\treturn {}\n'.format( rvalue ))
|
||||||
|
return rvalue
|
||||||
|
|
||||||
|
def _backtrackNet ( self, net ):
|
||||||
|
trace( 600, ',+', '\tDAG._backtraceNet() {}\n'.format( net ))
|
||||||
|
for plug in net.getPlugs():
|
||||||
|
inst = plug.getInstance()
|
||||||
|
if inst in self.processeds:
|
||||||
|
trace( 600, '\tbacktrack: {}\n'.format( inst ))
|
||||||
|
self.processeds.remove( inst )
|
||||||
|
if inst == self.rootInst:
|
||||||
|
continue
|
||||||
|
self.tree.append( inst )
|
||||||
|
for instPlug in inst.getPlugs():
|
||||||
|
if instPlug.getNet() == net:
|
||||||
|
continue
|
||||||
|
if not self._isInPropagationDir(instPlug):
|
||||||
|
continue
|
||||||
|
self._backtrackNet( instPlug.getNet() )
|
||||||
|
trace( 600, ',-', '\tDone\n' )
|
||||||
|
|
||||||
|
def _backtrack ( self ):
|
||||||
|
for net in self.externalNets:
|
||||||
|
self._backtrackNet( net )
|
||||||
|
|
||||||
|
def build ( self ):
|
||||||
|
while self._processInst():
|
||||||
|
pass
|
||||||
|
self._backtrack()
|
||||||
|
|
||||||
|
def show ( self ):
|
||||||
|
print( 'DAG Tree:' )
|
||||||
|
for inst in self.tree:
|
||||||
|
print( '| {}'.format( inst ))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# Class : Column.
|
||||||
|
|
||||||
|
class Column ( object ):
|
||||||
|
"""
|
||||||
|
Manage one column in the SRAM matrix.
|
||||||
|
"""
|
||||||
|
KIND_WORD = 0x0001
|
||||||
|
KIND_INPUT_MUX = 0x0002
|
||||||
|
KIND_OUTPUT_MUX = 0x0004
|
||||||
|
KIND_GROUP = 0x0008
|
||||||
|
|
||||||
|
def __init__ ( self, kind, tag=None ):
|
||||||
|
self.tag = tag
|
||||||
|
self.parent = None
|
||||||
|
self.kind = kind
|
||||||
|
self.bits = []
|
||||||
|
self.width = 0
|
||||||
|
self.depth = 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def root ( self ):
|
||||||
|
return self.parent
|
||||||
|
|
||||||
|
@property
|
||||||
|
def busWidth ( self ):
|
||||||
|
return len( self.bits )
|
||||||
|
|
||||||
|
def __str__ ( self ):
|
||||||
|
return '<Column d={} {} {} {}>'.format( self.depth, self.tag, len(self.bits), self.parent )
|
||||||
|
|
||||||
|
def getBit ( self, bit ):
|
||||||
|
if bit >= len(self.bits): return None
|
||||||
|
return self.bits[bit]
|
||||||
|
|
||||||
|
def addToEmptyRow ( self, bit, inst ):
|
||||||
|
print( 'self.width={} ABwidth={}'.format(
|
||||||
|
DbU.getValueString(self.width), DbU.getValueString(inst.getMasterCell().getAbutmentBox().getWidth()) ))
|
||||||
|
if inst.getMasterCell().getAbutmentBox().getWidth() != self.width:
|
||||||
|
print( 'Rejected' )
|
||||||
|
return False
|
||||||
|
if self.getBit(bit):
|
||||||
|
return False
|
||||||
|
self.addRow( bit, inst )
|
||||||
|
return True
|
||||||
|
|
||||||
|
def addRow ( self, bit, inst ):
|
||||||
|
if len(self.bits) > bit:
|
||||||
|
self.bits[ bit ] = inst
|
||||||
|
else:
|
||||||
|
while len(self.bits) < bit:
|
||||||
|
self.bits.append( None )
|
||||||
|
self.bits.append( inst )
|
||||||
|
self.width = max( self.width, inst.getMasterCell().getAbutmentBox().getWidth() )
|
||||||
|
|
||||||
|
def getDriver ( self, bit ):
|
||||||
|
for plug in self.bits[bit].getPlugs():
|
||||||
|
if plug.getMasterNet().getDirection() & Net.Direction.DirOut:
|
||||||
|
return plug.getNet()
|
||||||
|
return None
|
||||||
|
|
||||||
|
def findChild ( self, tag ):
|
||||||
|
if self.tag == tag: return self
|
||||||
|
return None
|
||||||
|
|
||||||
|
def placeAt ( self, colTransf ):
|
||||||
|
for bit in range(len(self.bits)):
|
||||||
|
if self.bits[bit] is not None:
|
||||||
|
sliceHeight = self.bits[bit].getMasterCell().getAbutmentBox().getHeight()
|
||||||
|
bb = Box()
|
||||||
|
for bit in range(len(self.bits)):
|
||||||
|
if self.bits[bit] is None:
|
||||||
|
continue
|
||||||
|
y = bit*sliceHeight
|
||||||
|
orient = Transformation.Orientation.ID
|
||||||
|
if bit % 2:
|
||||||
|
y += sliceHeight
|
||||||
|
orient = Transformation.Orientation.MY
|
||||||
|
transf = Transformation( 0, y, orient )
|
||||||
|
colTransf.applyOn( transf )
|
||||||
|
self.bits[bit].setTransformation( transf )
|
||||||
|
self.bits[bit].setPlacementStatus( Instance.PlacementStatus.PLACED )
|
||||||
|
bb.merge( self.bits[bit].getAbutmentBox() )
|
||||||
|
return bb
|
||||||
|
|
||||||
|
def showTree ( self, depth ):
|
||||||
|
print( '{}| {}'.format( ' '*depth, self ))
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# Class : ColGroup.
|
||||||
|
|
||||||
|
class ColGroup ( object ):
|
||||||
|
"""
|
||||||
|
Manage a group of Column and/or ColGroup (recursive).
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__ ( self, tag ):
|
||||||
|
"""
|
||||||
|
Initialize an *empty* column group. Sub-group or columns must be
|
||||||
|
added afterwards.
|
||||||
|
"""
|
||||||
|
self.tag = tag
|
||||||
|
self.parent = None
|
||||||
|
self.depth = 0
|
||||||
|
self.childs = []
|
||||||
|
|
||||||
|
def __iter__ ( self ):
|
||||||
|
return ColGroupIterator( self )
|
||||||
|
|
||||||
|
@property
|
||||||
|
def kind ( self ): return Column.KIND_GROUP
|
||||||
|
|
||||||
|
@property
|
||||||
|
def width ( self ):
|
||||||
|
width = 0
|
||||||
|
for child in self.childs:
|
||||||
|
width += child.width
|
||||||
|
return width
|
||||||
|
|
||||||
|
@property
|
||||||
|
def root ( self ):
|
||||||
|
if self.parent:
|
||||||
|
return self.parent.root
|
||||||
|
return self
|
||||||
|
|
||||||
|
@property
|
||||||
|
def busWidth ( self ):
|
||||||
|
busWidth = 0
|
||||||
|
for child in self.childs:
|
||||||
|
busWidth = max( busWidth, child.busWidth )
|
||||||
|
return busWidth
|
||||||
|
|
||||||
|
def __str__ ( self ):
|
||||||
|
return '<ColGroup d={} {} {}>'.format( self.depth, self.tag, self.parent )
|
||||||
|
|
||||||
|
def group ( self, child ):
|
||||||
|
""" Add a new child to the group. """
|
||||||
|
self.childs.append( child )
|
||||||
|
child.parent = self
|
||||||
|
self.depth = max( self.depth, child.depth+1 )
|
||||||
|
|
||||||
|
def unGroup ( self, child ):
|
||||||
|
""" Remove a child from the group (the child is *not* deleted). """
|
||||||
|
if child in self.childs:
|
||||||
|
self.childs.remove( child )
|
||||||
|
child.parent = None
|
||||||
|
self.depth = 0
|
||||||
|
for child in self.childs:
|
||||||
|
self.depth = max( self.depth, child.depth )
|
||||||
|
self.depth += 1
|
||||||
|
|
||||||
|
def findChild ( self, tag ):
|
||||||
|
""" Recursively find a child in a goup by it's tag name. """
|
||||||
|
if self.tag == tag: return self
|
||||||
|
for child in self.childs:
|
||||||
|
rchild = child.findChild( tag )
|
||||||
|
if rchild: return rchild
|
||||||
|
return None
|
||||||
|
|
||||||
|
def placeAt ( self, groupTransf ):
|
||||||
|
"""
|
||||||
|
Place childs/colums from left to rigth.
|
||||||
|
"""
|
||||||
|
width = 0
|
||||||
|
bb = Box()
|
||||||
|
for child in self.childs:
|
||||||
|
bb.merge( child.placeAt( Transformation( groupTransf.getTx()+width
|
||||||
|
, groupTransf.getTy()
|
||||||
|
, groupTransf.getOrientation() )))
|
||||||
|
width += child.width
|
||||||
|
return bb
|
||||||
|
|
||||||
|
def showTree ( self, depth=0 ):
|
||||||
|
print( '{}+ {}'.format( ' '*depth, self ))
|
||||||
|
for child in self.childs:
|
||||||
|
child.showTree( depth+1 )
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# Class : ColGroupIterator.
|
||||||
|
|
||||||
|
class ColGroupIterator ( object ):
|
||||||
|
|
||||||
|
def __init__ ( self, colGroup ):
|
||||||
|
self._colGroup = colGroup
|
||||||
|
self._index = 0
|
||||||
|
self._subIter = None
|
||||||
|
|
||||||
|
def __next__ ( self ):
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
if self._subIter:
|
||||||
|
column = next( self._subIter )
|
||||||
|
return column
|
||||||
|
except StopIteration:
|
||||||
|
self._columnIter = None
|
||||||
|
if self._index < len(self._colGroup.childs):
|
||||||
|
column = self._colGroup.childs[self._index]
|
||||||
|
self._index += 1
|
||||||
|
if isinstance(column,ColGroup):
|
||||||
|
self._subIter = iter( column )
|
||||||
|
continue
|
||||||
|
return column
|
||||||
|
else:
|
||||||
|
raise StopIteration
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# Class : SRAMPlacer.
|
||||||
|
|
||||||
|
class SRAMPlacer ( object ):
|
||||||
|
"""
|
||||||
|
Takes a Yosys generated SRAM and place it on a regular matrix.
|
||||||
|
"""
|
||||||
|
|
||||||
|
patIgnoreMasterNet = '^cmd[0-9]?$'
|
||||||
|
reIgnoreMasterNet = None
|
||||||
|
CREATE_COLUMNS = 1
|
||||||
|
REUSE_COLUMNS = 2
|
||||||
|
|
||||||
|
def __init__ ( self, cell ):
|
||||||
|
"""
|
||||||
|
Initialize a completely empty SRAM placer. Build functions must
|
||||||
|
be called separately.
|
||||||
|
"""
|
||||||
|
self.gaugeConf = GaugeConf()
|
||||||
|
self.libConf = StdCellConf()
|
||||||
|
self.cell = cell
|
||||||
|
self.matrix = ColGroup( 'root' )
|
||||||
|
self.totalInsts = 0
|
||||||
|
self.placedInsts = 0
|
||||||
|
self.totalLength = 0
|
||||||
|
self.placedLength = 0
|
||||||
|
self.decodDff = []
|
||||||
|
if not SRAMPlacer.reIgnoreMasterNet:
|
||||||
|
SRAMPlacer.reIgnoreMasterNet = re.compile( SRAMPlacer.patIgnoreMasterNet )
|
||||||
|
|
||||||
|
def _groupInputColumns ( self, instance, mode ):
|
||||||
|
"""
|
||||||
|
Group this instance with all it's drivers input into a single new group.
|
||||||
|
"""
|
||||||
|
if hasattr(instance,'column'):
|
||||||
|
print( ' | Already grouped in {}'.format( instance.column ))
|
||||||
|
return
|
||||||
|
|
||||||
|
drivers = []
|
||||||
|
for plug in instance.getPlugs():
|
||||||
|
if not plug.getNet():
|
||||||
|
continue
|
||||||
|
if not (plug.getMasterNet().getDirection() & Net.Direction.DirIn):
|
||||||
|
continue
|
||||||
|
m = SRAMPlacer.reIgnoreMasterNet.match( plug.getMasterNet().getName() )
|
||||||
|
if m:
|
||||||
|
continue
|
||||||
|
driverInst = DagExtension.getDriver( plug.getNet() )
|
||||||
|
if not driverInst:
|
||||||
|
continue
|
||||||
|
if not hasattr(driverInst,'column'):
|
||||||
|
continue
|
||||||
|
drivers.append( driverInst )
|
||||||
|
print( ' | driver {}'.format( driverInst.column ))
|
||||||
|
if not len(drivers):
|
||||||
|
return
|
||||||
|
bits = []
|
||||||
|
for driver in drivers:
|
||||||
|
if driver.column.tag.startswith('word'):
|
||||||
|
bits.append( int( driver.column.tag[4:] ))
|
||||||
|
elif driver.column.tag.startswith('mux'):
|
||||||
|
bits += [ int(bit) for bit in driver.column.tag[3:].split( '_' ) ]
|
||||||
|
bits.sort()
|
||||||
|
muxTag = 'mux'
|
||||||
|
for i in range(len(bits)):
|
||||||
|
if i: muxTag += '_'
|
||||||
|
muxTag += str( bits[i] )
|
||||||
|
if mode == SRAMPlacer.CREATE_COLUMNS:
|
||||||
|
muxGroup = ColGroup( muxTag+'_g' )
|
||||||
|
muxCol = Column( Column.KIND_OUTPUT_MUX, muxTag )
|
||||||
|
muxCol.addRow( drivers[0].bit, instance )
|
||||||
|
instance.column = muxCol
|
||||||
|
instance.bit = drivers[0].bit
|
||||||
|
midRange = len(drivers) // 2
|
||||||
|
for i in range(len(drivers)):
|
||||||
|
if i == midRange:
|
||||||
|
muxGroup.group( muxCol )
|
||||||
|
self.matrix.unGroup( drivers[i].column.parent )
|
||||||
|
muxGroup.group( drivers[i].column.parent )
|
||||||
|
self.matrix.group( muxGroup )
|
||||||
|
print( ' | Grouped in {}'.format( muxTag ))
|
||||||
|
else:
|
||||||
|
muxCol = self.matrix.findChild( muxTag )
|
||||||
|
if not muxCol:
|
||||||
|
maxDepth = len(bits) + 1
|
||||||
|
bitTag = 'word{}'.format( bits[0] )
|
||||||
|
groupCol = self.matrix.findChild( bitTag ).parent
|
||||||
|
while groupCol.depth <= maxDepth:
|
||||||
|
groupCol = groupCol.parent
|
||||||
|
bits = [ int(bit) for bit in groupCol.tag[3:-2].split( '_' ) ]
|
||||||
|
added = False
|
||||||
|
print( ' | mismatched maxDepth={} muxTag="{}"'.format( maxDepth, muxTag ))
|
||||||
|
for bit in bits:
|
||||||
|
print( ' | bit {}'.format( bit ))
|
||||||
|
bitTag = 'word{}'.format( bit )
|
||||||
|
groupCol = self.matrix.findChild( bitTag ).parent
|
||||||
|
print( ' | Look in childs of {}'.format( groupCol ))
|
||||||
|
while groupCol.depth <= maxDepth:
|
||||||
|
for child in groupCol.childs:
|
||||||
|
print( ' | try {}'.format( child ))
|
||||||
|
if child.tag.startswith('mux') and not child.tag.endswith('_g'):
|
||||||
|
if child.addToEmptyRow( drivers[0].bit, instance ):
|
||||||
|
print( ' | added in="{}"'.format( child.tag ))
|
||||||
|
added = True
|
||||||
|
break
|
||||||
|
if added: break
|
||||||
|
groupCol = groupCol.parent
|
||||||
|
print( ' | Look in childs of {}'.format( groupCol ))
|
||||||
|
if not groupCol: break
|
||||||
|
if added: break
|
||||||
|
else:
|
||||||
|
print( ' | matched muxTag="{}"'.format( muxTag ))
|
||||||
|
muxCol.addRow( drivers[0].bit, instance )
|
||||||
|
|
||||||
|
def findChild ( self, tag ):
|
||||||
|
""" Search for a leaf column named ``tag`` """
|
||||||
|
return self.matrix.findChild( tag )
|
||||||
|
|
||||||
|
def addMemBit ( self, addr, bit, inst ):
|
||||||
|
"""
|
||||||
|
Add a *memory element* in the matrix.
|
||||||
|
|
||||||
|
:param addr: The address of the word the bit is part of (X).
|
||||||
|
:param bit: The bit in the word (Y).
|
||||||
|
:param inst: The register (DFF) instance.
|
||||||
|
"""
|
||||||
|
wordTag = 'word{:d}'.format( addr )
|
||||||
|
column = self.findChild( wordTag )
|
||||||
|
if not column:
|
||||||
|
wordGroup = ColGroup( wordTag+'_g' )
|
||||||
|
column = Column( Column.KIND_WORD, wordTag )
|
||||||
|
wordGroup.group( column )
|
||||||
|
self.matrix.group( wordGroup )
|
||||||
|
column.addRow( bit, inst )
|
||||||
|
inst.column = column
|
||||||
|
inst.bit = bit
|
||||||
|
|
||||||
|
def findMemBits ( self ):
|
||||||
|
"""
|
||||||
|
Match the memory bits (DFF) into the matrix. Match is as follow:
|
||||||
|
|
||||||
|
* ``byte`` : the byte number. This is 4 for words of 32 bits size.
|
||||||
|
* ``bit`` : the bit inside a byte (should always be 8).
|
||||||
|
* ``word`` : the word identifier, this is the numerical value of
|
||||||
|
the address.
|
||||||
|
|
||||||
|
Yosys seems to split 32 bits value in a per byte fashion.
|
||||||
|
"""
|
||||||
|
reMem = re.compile( r'^mem(?P<byte>\d+)_(?P<word>\d+)_(?P<bit>\d+)' )
|
||||||
|
for instance in self.cell.getInstances():
|
||||||
|
PythonAttributes.enable( instance )
|
||||||
|
cellLength = instance.getAbutmentBox().getWidth()
|
||||||
|
self.totalInsts += 1
|
||||||
|
self.totalLength += cellLength
|
||||||
|
for plug in instance.getPlugs():
|
||||||
|
if plug.getMasterNet().getDirection() & Net.Direction.DirOut:
|
||||||
|
m = reMem.match( plug.getNet().getName() )
|
||||||
|
if m:
|
||||||
|
self.addMemBit( int(m.group('word'))
|
||||||
|
, int(m.group('byte'))*8 + int(m.group('bit'))
|
||||||
|
, instance )
|
||||||
|
self.placedLength += cellLength
|
||||||
|
self.placedInsts += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
def findDecodDff ( self ):
|
||||||
|
"""
|
||||||
|
Match the DFF holding the decoded read address. Match is as follow:
|
||||||
|
|
||||||
|
Yosys seems to split 32 bits value in a per byte fashion.
|
||||||
|
"""
|
||||||
|
reMem = re.compile( r'^mem(?P<byte>\d+)_rdreg_(?P<word>\d+)_q_(?P<bit>\d+)' )
|
||||||
|
for instance in self.cell.getInstances():
|
||||||
|
cellLength = instance.getAbutmentBox().getWidth()
|
||||||
|
for plug in instance.getPlugs():
|
||||||
|
if plug.getMasterNet().getDirection() & Net.Direction.DirOut:
|
||||||
|
m = reMem.match( plug.getNet().getName() )
|
||||||
|
if m:
|
||||||
|
self.decodDff.append( instance )
|
||||||
|
continue
|
||||||
|
print( 'Decoder DFFs:' )
|
||||||
|
for instance in self.decodDff:
|
||||||
|
print( '| ', instance )
|
||||||
|
|
||||||
|
def findInputMuxes ( self ):
|
||||||
|
"""
|
||||||
|
Perform a DAG propagation to find the input muxes attached to each
|
||||||
|
memory bits.
|
||||||
|
"""
|
||||||
|
placeds = []
|
||||||
|
for inst in self.cell.getInstances():
|
||||||
|
if not hasattr(inst,'column'):
|
||||||
|
continue
|
||||||
|
dag = DAG( inst )
|
||||||
|
dag.build()
|
||||||
|
#dag.show()
|
||||||
|
muxTag = 'mux_{}'.format(inst.column.tag)
|
||||||
|
rootCol = inst.column.root
|
||||||
|
muxCol = rootCol.findChild( muxTag )
|
||||||
|
if not muxCol:
|
||||||
|
muxCol = Column( Column.KIND_INPUT_MUX, muxTag )
|
||||||
|
inst.column.parent.group( muxCol )
|
||||||
|
muxCol.addRow( inst.bit, dag.tree[0] )
|
||||||
|
placeds.append( (muxCol, dag.tree[0], inst.bit) )
|
||||||
|
for column, inst, bit in placeds:
|
||||||
|
cellLength = inst.getAbutmentBox().getWidth()
|
||||||
|
self.placedLength += cellLength
|
||||||
|
self.placedInsts += 1
|
||||||
|
inst.column = column
|
||||||
|
inst.bit = bit
|
||||||
|
|
||||||
|
def findOutputMuxes ( self ):
|
||||||
|
"""
|
||||||
|
Perform a DAG propagation to find the output muxes attached to each
|
||||||
|
memory bits.
|
||||||
|
"""
|
||||||
|
foehn = FoehnEngine.create( self.cell )
|
||||||
|
dagDecod = foehn.newDag( 'decoder' )
|
||||||
|
dagDecod.addDStart( self.cell.getNet( 'rst' ))
|
||||||
|
for inst in self.decodDff:
|
||||||
|
dagDecod.addDStart( inst )
|
||||||
|
dagDecod.dpropagate()
|
||||||
|
dagDecod.resetDepths()
|
||||||
|
for index in range(256):
|
||||||
|
print( 'PROCESSING BIT {}'.format( index ))
|
||||||
|
mode = SRAMPlacer.REUSE_COLUMNS
|
||||||
|
if index == 0:
|
||||||
|
mode = SRAMPlacer.CREATE_COLUMNS
|
||||||
|
dagOMux = foehn.newDag( 'outputMuxes_{}'.format(index) )
|
||||||
|
dagOMux.setIgnoredMasterNetRe( SRAMPlacer.patIgnoreMasterNet )
|
||||||
|
for inst in self.cell.getInstances():
|
||||||
|
if not hasattr(inst,'column'):
|
||||||
|
continue
|
||||||
|
if inst.bit != index:
|
||||||
|
continue
|
||||||
|
if not self.libConf.isRegister(inst.getMasterCell()):
|
||||||
|
continue
|
||||||
|
dagOMux.addDStart( inst )
|
||||||
|
dagOMux.dpropagate()
|
||||||
|
order = 0
|
||||||
|
depth = 0
|
||||||
|
depthSize = 0
|
||||||
|
for dbo in dagOMux.getDOrder():
|
||||||
|
minDepth = DagExtension.getMinDepth( dbo )
|
||||||
|
if minDepth != depth:
|
||||||
|
print( 'Depth {} has {} gates.'.format( depth, depthSize ))
|
||||||
|
depth = minDepth
|
||||||
|
depthSize = 0
|
||||||
|
print( '{:03d} {:02d} | {}'.format( order, minDepth, dbo ))
|
||||||
|
if hasattr(dbo,'column'):
|
||||||
|
print( ' | {}'.format( dbo.column ))
|
||||||
|
if isinstance(dbo,Net):
|
||||||
|
print( ' | driver={}'.format( DagExtension.getDriver( dbo )))
|
||||||
|
if isinstance(dbo,Instance):
|
||||||
|
self._groupInputColumns( dbo, mode )
|
||||||
|
depthSize += 1
|
||||||
|
order += 1
|
||||||
|
dagOMux.resetDepths()
|
||||||
|
foehn.destroy()
|
||||||
|
|
||||||
|
def show ( self ):
|
||||||
|
"""
|
||||||
|
Display the matrix contents.
|
||||||
|
"""
|
||||||
|
print( 'SRAM Tree of {}'.format(self.cell) )
|
||||||
|
self.matrix.showTree()
|
||||||
|
print( 'SRAM Matrix of {}'.format(self.cell) )
|
||||||
|
print( ' Cells: {}, placed: {:.1%}, area placed: {:.1%}' \
|
||||||
|
.format( self.totalInsts
|
||||||
|
, self.placedInsts /self.totalInsts
|
||||||
|
, self.placedLength/self.totalLength ))
|
||||||
|
for column in self.matrix:
|
||||||
|
print( ' Column {}'.format( column ))
|
||||||
|
sys.stdout.flush()
|
||||||
|
sys.stderr.flush()
|
||||||
|
for yindex in range(len(column.bits)):
|
||||||
|
if not column.bits[yindex]: continue
|
||||||
|
print( ' Row [{},{}] {} -> {} @{}' \
|
||||||
|
.format( 'X'
|
||||||
|
, yindex
|
||||||
|
, column.bits[yindex]
|
||||||
|
, column.getDriver(yindex).getName()
|
||||||
|
, column.bits[yindex].getTransformation()
|
||||||
|
))
|
||||||
|
|
||||||
|
def placeAt ( self, matrixTransf=Transformation() ):
|
||||||
|
"""
|
||||||
|
Place the matrix (regularly).
|
||||||
|
"""
|
||||||
|
with UpdateSession():
|
||||||
|
bb = Box()
|
||||||
|
x = 0
|
||||||
|
for column in self.matrix:
|
||||||
|
print( 'place {}'.format( column ))
|
||||||
|
sys.stdout.flush()
|
||||||
|
sys.stderr.flush()
|
||||||
|
colTransf = Transformation( x, 0, Transformation.Orientation.ID )
|
||||||
|
matrixTransf.applyOn( colTransf )
|
||||||
|
bb.merge( column.placeAt( colTransf ))
|
||||||
|
x += column.width
|
||||||
|
self.cell.setAbutmentBox( bb )
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# Plugin hook functions, unicornHook:menus, ScritMain:call
|
||||||
|
|
||||||
|
def unicornHook ( **kw ):
|
||||||
|
"""
|
||||||
|
The mandatory function to make the plugin appears in the menus.
|
||||||
|
"""
|
||||||
|
plugins.kwUnicornHook( 'tools.SRAMPlacer'
|
||||||
|
, 'Soft SRAM Placer'
|
||||||
|
, 'Placer dedicated to Yosys generated SRAM'
|
||||||
|
, sys.modules[__name__].__file__
|
||||||
|
, **kw
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def scriptMain ( **kw ):
|
||||||
|
"""
|
||||||
|
The mandatory function from which a plugin will be called by Coriolis CGT/Unicorn.
|
||||||
|
"""
|
||||||
|
|
||||||
|
rvalue = True
|
||||||
|
try:
|
||||||
|
DbU.setStringMode( DbU.StringModeReal, DbU.UnitPowerMicro )
|
||||||
|
#helpers.setTraceLevel( 500 )
|
||||||
|
cell, editor = plugins.kwParseMain( **kw )
|
||||||
|
placer = SRAMPlacer( cell )
|
||||||
|
placer.findMemBits()
|
||||||
|
placer.placeAt()
|
||||||
|
placer.show()
|
||||||
|
if editor: editor.fit()
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
helpers.io.catch( e )
|
||||||
|
rvalue = False
|
||||||
|
sys.stdout.flush()
|
||||||
|
sys.stderr.flush()
|
||||||
|
return rvalue
|
|
@ -0,0 +1,863 @@
|
||||||
|
|
||||||
|
# This file is part of the Coriolis Software.
|
||||||
|
# Copyright (c) Sorbonne Université 2022-2022, 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/sramplacer.py" |
|
||||||
|
# +-----------------------------------------------------------------+
|
||||||
|
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
import traceback
|
||||||
|
import helpers
|
||||||
|
from helpers.io import ErrorMessage, WarningMessage
|
||||||
|
from helpers.overlay import UpdateSession
|
||||||
|
from helpers import trace, l, u, n
|
||||||
|
import plugins
|
||||||
|
from Hurricane import Breakpoint, DbU, Box, Net, Cell, Instance, \
|
||||||
|
Transformation, PythonAttributes
|
||||||
|
from Foehn import FoehnEngine, DagExtension
|
||||||
|
from plugins.chip.configuration import GaugeConf
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Automatic placement of a Yosys generated SRAM
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
* We were expecting the output decoder to be the same for each bit
|
||||||
|
line, allowing us to rebuild a matrix-like placement. This is not so.
|
||||||
|
Each output mux equation is synthetised differently. Knowing that
|
||||||
|
we did create a row-based placement, with reordering capabilities
|
||||||
|
so we can optimize the mux placement.
|
||||||
|
|
||||||
|
* Alas, the previous effort was doomed from the start. If you have
|
||||||
|
the same multiplexing function for all the bits, the command signals
|
||||||
|
from the decoder are the same. For example, to mux 256 words,
|
||||||
|
assuming we use only mux2, we need 8 bits (control lines).
|
||||||
|
Given that we have also to take into account "ce", "we", "rst"
|
||||||
|
and "oe", there are more of them, but not so much. Let's say 20.
|
||||||
|
|
||||||
|
When running placeSRAM and looking at the last level (5) of the
|
||||||
|
DAG's decoder, we see that it contains 832 gates, which means as
|
||||||
|
much command signals. That is 26 control signals *per* bit.
|
||||||
|
This is the direct consequence that *each* multiplexer has it's
|
||||||
|
own structure. 26 signals takes up more than half the horizontal
|
||||||
|
routing capacity of a slice (40), this result in an unroutable
|
||||||
|
design, the bits are kept into one row each.
|
||||||
|
|
||||||
|
Conclusions
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
1. A Yosys generated SRAM cannot be regularly placed, neither in
|
||||||
|
2-D matrix fashion nor in simple bit-line organization.
|
||||||
|
|
||||||
|
2. Worse, a thorough analysis of the generated netlist shows it is
|
||||||
|
highly sub-optimal. Yosys generate *way* too much signals to
|
||||||
|
achieve it, resulting in a bloated design.
|
||||||
|
|
||||||
|
3. Creating a small generator of SRAM, even based on standard cells
|
||||||
|
would be a great improvement over the Yosys generated one.
|
||||||
|
(the simpler OpenRAM approach)
|
||||||
|
|
||||||
|
Looking backward, as we were using Yosys generated SRAM in the LibreSOC,
|
||||||
|
that explain lot of the observed congestion.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# Class : StdCellConf.
|
||||||
|
|
||||||
|
class StdCellConf ( object ):
|
||||||
|
"""
|
||||||
|
Gives meta informations about the standard cell library.
|
||||||
|
"""
|
||||||
|
|
||||||
|
reDFF = re.compile( r'^sff' )
|
||||||
|
reMux = re.compile( r'^n?mx[0-9]_' )
|
||||||
|
reDataIn = re.compile( r'^i[0-9]?' )
|
||||||
|
reDataOut = re.compile( r'^n?q' )
|
||||||
|
|
||||||
|
def isRegister ( self, cell ):
|
||||||
|
"""Returns True if the cell is a register."""
|
||||||
|
m = StdCellConf.reDFF.match( cell.getName() )
|
||||||
|
if m: return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def isMux ( self, cell ):
|
||||||
|
"""Returns True if the cell is a multiplexer."""
|
||||||
|
m = StdCellConf.reMux.match( cell.getName() )
|
||||||
|
if m: return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def isDataIn ( self, net ):
|
||||||
|
"""Returns True if the net is a data input."""
|
||||||
|
m = StdCellConf.reDataIn.match( net.getName() )
|
||||||
|
if m:
|
||||||
|
if not (net.getDirection() & Net.Direction.DirIn):
|
||||||
|
print( Warning( [ 'StdCellConf.isDataIn(): Match input pattern "^i[0_9]" but not in input direction.'
|
||||||
|
, 'On {}'.format(net) ] ))
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def isDataOut ( self, net ):
|
||||||
|
"""Returns True if the net is a data output."""
|
||||||
|
m = StdCellConf.reDataOut.match( net.getName() )
|
||||||
|
if m:
|
||||||
|
if not (net.getDirection() & Net.Direction.DirOut):
|
||||||
|
print( Warning( [ 'StdCellConf.isDataOut(): Match output pattern "^i[0_9]" but not in output direction.'
|
||||||
|
, 'On {}'.format(net) ] ))
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def isData ( self, net ):
|
||||||
|
"""Returns True if the net is a data flow (i.e. not a control)."""
|
||||||
|
return self.isDataIn(net) or self.isDataOut(net)
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# Class : DAG.
|
||||||
|
|
||||||
|
class DAG ( object ):
|
||||||
|
|
||||||
|
def __init__ ( self, rootInst ):
|
||||||
|
self.rootInst = rootInst
|
||||||
|
self.reacheds = [ rootInst ]
|
||||||
|
self.processeds = []
|
||||||
|
self.tree = []
|
||||||
|
self.propagDir = Net.Direction.DirIn
|
||||||
|
self.externalNets = []
|
||||||
|
self.libConf = StdCellConf()
|
||||||
|
|
||||||
|
def _isInPropagationDir ( self, plug ):
|
||||||
|
trace( 600, ',+', '\tDAG._isInPropagationDir() on {}\n'.format( plug ))
|
||||||
|
masterNet = plug.getMasterNet()
|
||||||
|
if masterNet.isSupply():
|
||||||
|
trace( 600, ',-', '\tFalse (Supply) {}\n'.format( masterNet ))
|
||||||
|
return False
|
||||||
|
if masterNet.isClock():
|
||||||
|
trace( 600, ',-', '\tFalse (Clock) {}\n'.format( masterNet ))
|
||||||
|
return False
|
||||||
|
if (masterNet.getDirection() & self.propagDir) ^ self.propagDir:
|
||||||
|
trace( 600, ',-', '\tFalse {}\n'.format( masterNet ))
|
||||||
|
return False
|
||||||
|
if not self.libConf.isData(masterNet):
|
||||||
|
trace( 600, ',-', '\tFalse (not data-flow) {}\n'.format( masterNet ))
|
||||||
|
return False
|
||||||
|
trace( 600, ',-', '\tTrue {}\n'.format( masterNet ))
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _processInst ( self ):
|
||||||
|
rvalue = False
|
||||||
|
if not self.reacheds:
|
||||||
|
return rvalue
|
||||||
|
current = self.reacheds[ 0 ]
|
||||||
|
trace( 600, ',+', '\tDAG._processInst() on {}\n'.format(current) )
|
||||||
|
self.reacheds.remove( current )
|
||||||
|
self.processeds.append( current )
|
||||||
|
for currentPlug in current.getPlugs():
|
||||||
|
trace( 600, ',+', '\tLooking for {}\n'.format( currentPlug ))
|
||||||
|
if not self._isInPropagationDir(currentPlug):
|
||||||
|
trace( 600, ',-', '\tMaster net not in propag dir {}\n'.format( currentPlug.getMasterNet() ))
|
||||||
|
continue
|
||||||
|
net = currentPlug.getNet()
|
||||||
|
trace( 600, ',+', '\tReached {}\n'.format( net ))
|
||||||
|
if net.isExternal():
|
||||||
|
trace( 600, ',--', '\tExternal net reached: {}\n'.format( net ))
|
||||||
|
self.externalNets.append( net )
|
||||||
|
continue
|
||||||
|
for plug in net.getPlugs():
|
||||||
|
if plug == currentPlug:
|
||||||
|
continue
|
||||||
|
if self._isInPropagationDir(plug):
|
||||||
|
continue
|
||||||
|
reachedInst = plug.getInstance()
|
||||||
|
if not reachedInst in self.reacheds and not reachedInst in self.processeds:
|
||||||
|
trace( 600, '\tInstance reached: {}\n'.format( reachedInst ))
|
||||||
|
self.reacheds.append( reachedInst )
|
||||||
|
rvalue = True
|
||||||
|
trace( 600, ',--' )
|
||||||
|
trace( 600, ',-', '\treturn {}\n'.format( rvalue ))
|
||||||
|
return rvalue
|
||||||
|
|
||||||
|
def _backtrackNet ( self, net ):
|
||||||
|
trace( 600, ',+', '\tDAG._backtraceNet() {}\n'.format( net ))
|
||||||
|
for plug in net.getPlugs():
|
||||||
|
inst = plug.getInstance()
|
||||||
|
if inst in self.processeds:
|
||||||
|
trace( 600, '\tbacktrack: {}\n'.format( inst ))
|
||||||
|
self.processeds.remove( inst )
|
||||||
|
if inst == self.rootInst:
|
||||||
|
continue
|
||||||
|
self.tree.append( inst )
|
||||||
|
for instPlug in inst.getPlugs():
|
||||||
|
if instPlug.getNet() == net:
|
||||||
|
continue
|
||||||
|
if not self._isInPropagationDir(instPlug):
|
||||||
|
continue
|
||||||
|
self._backtrackNet( instPlug.getNet() )
|
||||||
|
trace( 600, ',-', '\tDone\n' )
|
||||||
|
|
||||||
|
def _backtrack ( self ):
|
||||||
|
for net in self.externalNets:
|
||||||
|
self._backtrackNet( net )
|
||||||
|
|
||||||
|
def build ( self ):
|
||||||
|
while self._processInst():
|
||||||
|
pass
|
||||||
|
self._backtrack()
|
||||||
|
|
||||||
|
def show ( self ):
|
||||||
|
print( 'DAG Tree:' )
|
||||||
|
for inst in self.tree:
|
||||||
|
print( '| {}'.format( inst ))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# Class : RowLeaf.
|
||||||
|
|
||||||
|
class RowLeaf ( object ):
|
||||||
|
"""
|
||||||
|
Manage one leaf (atomic Instance) in a whole bit row.
|
||||||
|
"""
|
||||||
|
KIND_WORD = 0x0001
|
||||||
|
KIND_INPUT_MUX = 0x0002
|
||||||
|
KIND_OUTPUT_MUX = 0x0004
|
||||||
|
KIND_GROUP = 0x0008
|
||||||
|
|
||||||
|
def __init__ ( self, instance, kind, tag, bit ):
|
||||||
|
self.tag = tag
|
||||||
|
self.parent = None
|
||||||
|
self.kind = kind
|
||||||
|
self.instance = instance
|
||||||
|
self.width = instance.getMasterCell().getAbutmentBox().getWidth()
|
||||||
|
self.depth = 0
|
||||||
|
PythonAttributes.enable( self.instance )
|
||||||
|
self.instance.row = self
|
||||||
|
self.instance.bit = bit
|
||||||
|
|
||||||
|
@property
|
||||||
|
def root ( self ):
|
||||||
|
return self.parent
|
||||||
|
|
||||||
|
@property
|
||||||
|
def bit ( self ):
|
||||||
|
return self.instance.bit
|
||||||
|
|
||||||
|
def __str__ ( self ):
|
||||||
|
return '<RowLeaf d={} {} {}>'.format( self.depth, self.tag, self.parent )
|
||||||
|
|
||||||
|
def getDriver ( self ):
|
||||||
|
for plug in self.instance.getPlugs():
|
||||||
|
if plug.getMasterNet().getDirection() & Net.Direction.DirOut:
|
||||||
|
return plug.getNet()
|
||||||
|
return None
|
||||||
|
|
||||||
|
def findChild ( self, tag ):
|
||||||
|
if self.tag == tag: return self
|
||||||
|
return None
|
||||||
|
|
||||||
|
def placeAt ( self, transf ):
|
||||||
|
self.instance.setTransformation( transf )
|
||||||
|
self.instance.setPlacementStatus( Instance.PlacementStatus.PLACED )
|
||||||
|
return self.instance.getAbutmentBox()
|
||||||
|
|
||||||
|
def showTree ( self, depth ):
|
||||||
|
trace( 610, '\t{}| {}\n'.format( ' '*depth, self ))
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# Class : Row.
|
||||||
|
|
||||||
|
class Row ( object ):
|
||||||
|
"""
|
||||||
|
Manage a group of RowLeaf and/or Row (recursive).
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__ ( self, tag ):
|
||||||
|
"""
|
||||||
|
Initialize an *empty* column group. Sub-group or columns must be
|
||||||
|
added afterwards.
|
||||||
|
"""
|
||||||
|
self.tag = tag
|
||||||
|
self.parent = None
|
||||||
|
self.depth = 0
|
||||||
|
self.childs = []
|
||||||
|
|
||||||
|
def __iter__ ( self ):
|
||||||
|
return RowIterator( self )
|
||||||
|
|
||||||
|
@property
|
||||||
|
def kind ( self ): return RowLeaf.KIND_GROUP
|
||||||
|
|
||||||
|
@property
|
||||||
|
def width ( self ):
|
||||||
|
width = 0
|
||||||
|
for child in self.childs:
|
||||||
|
width += child.width
|
||||||
|
return width
|
||||||
|
|
||||||
|
@property
|
||||||
|
def root ( self ):
|
||||||
|
if self.parent:
|
||||||
|
return self.parent.root
|
||||||
|
return self
|
||||||
|
|
||||||
|
@property
|
||||||
|
def bit ( self ):
|
||||||
|
next(iter( self )).bit
|
||||||
|
|
||||||
|
def __str__ ( self ):
|
||||||
|
return '<Row d={} {} {}>'.format( self.depth, self.tag, self.parent )
|
||||||
|
|
||||||
|
def group ( self, child ):
|
||||||
|
""" Add a new child to the group. """
|
||||||
|
self.childs.append( child )
|
||||||
|
child.parent = self
|
||||||
|
self.depth = max( self.depth, child.depth+1 )
|
||||||
|
|
||||||
|
def unGroup ( self, child ):
|
||||||
|
""" Remove a child from the group (the child is *not* deleted). """
|
||||||
|
if child in self.childs:
|
||||||
|
self.childs.remove( child )
|
||||||
|
child.parent = None
|
||||||
|
self.depth = 0
|
||||||
|
for child in self.childs:
|
||||||
|
self.depth = max( self.depth, child.depth )
|
||||||
|
self.depth += 1
|
||||||
|
|
||||||
|
def findChild ( self, tag ):
|
||||||
|
""" Recursively find a child in a goup by it's tag name. """
|
||||||
|
if self.tag == tag: return self
|
||||||
|
for child in self.childs:
|
||||||
|
rchild = child.findChild( tag )
|
||||||
|
if rchild: return rchild
|
||||||
|
return None
|
||||||
|
|
||||||
|
def placeAt ( self, groupTransf ):
|
||||||
|
"""
|
||||||
|
Place childs/colums from left to rigth.
|
||||||
|
"""
|
||||||
|
width = 0
|
||||||
|
bb = Box()
|
||||||
|
for child in self.childs:
|
||||||
|
bb.merge( child.placeAt( Transformation( groupTransf.getTx()+width
|
||||||
|
, groupTransf.getTy()
|
||||||
|
, groupTransf.getOrientation() )))
|
||||||
|
width += child.width
|
||||||
|
return bb
|
||||||
|
|
||||||
|
def showTree ( self, depth=0 ):
|
||||||
|
trace( 610, '\t{}+ {}\n'.format( ' '*depth, self ))
|
||||||
|
for child in self.childs:
|
||||||
|
child.showTree( depth+1 )
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# Class : RowIterator.
|
||||||
|
|
||||||
|
class RowIterator ( object ):
|
||||||
|
"""
|
||||||
|
Iterator over a Row. The iterator returns *only* the RowLeafs,
|
||||||
|
not the intermediate Row groups.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__ ( self, row ):
|
||||||
|
self.row = row
|
||||||
|
self.index = 0
|
||||||
|
self.childIter = None
|
||||||
|
|
||||||
|
def __next__ ( self ):
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
if self.childIter:
|
||||||
|
return next( self.childIter )
|
||||||
|
except StopIteration:
|
||||||
|
self.childIter = None
|
||||||
|
if self.index < len(self.row.childs):
|
||||||
|
child = self.row.childs[ self.index ]
|
||||||
|
self.index += 1
|
||||||
|
if isinstance(child,Row):
|
||||||
|
self.childIter = iter( child )
|
||||||
|
continue
|
||||||
|
return child
|
||||||
|
else:
|
||||||
|
raise StopIteration
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# Class : SRAMPlacer.
|
||||||
|
|
||||||
|
class SRAMPlacer ( object ):
|
||||||
|
"""
|
||||||
|
Takes a Yosys generated SRAM and place it on a regular matrix.
|
||||||
|
"""
|
||||||
|
|
||||||
|
patIgnoreMasterNet = '^cmd[0-9]?$'
|
||||||
|
reIgnoreMasterNet = None
|
||||||
|
|
||||||
|
def __init__ ( self, cell ):
|
||||||
|
"""
|
||||||
|
Initialize a completely empty SRAM placer. Build functions must
|
||||||
|
be called separately.
|
||||||
|
"""
|
||||||
|
self.gaugeConf = GaugeConf()
|
||||||
|
self.libConf = StdCellConf()
|
||||||
|
self.cell = cell
|
||||||
|
self.rows = []
|
||||||
|
self.totalInsts = 0
|
||||||
|
self.placedInsts = 0
|
||||||
|
self.totalLength = 0
|
||||||
|
self.placedLength = 0
|
||||||
|
self.decodDff = []
|
||||||
|
self.foehn = FoehnEngine.create( self.cell )
|
||||||
|
if not SRAMPlacer.reIgnoreMasterNet:
|
||||||
|
SRAMPlacer.reIgnoreMasterNet = re.compile( SRAMPlacer.patIgnoreMasterNet )
|
||||||
|
|
||||||
|
def __del__ ( self ):
|
||||||
|
if self.foehn: self.foehn.destroy()
|
||||||
|
|
||||||
|
def _groupInputRows ( self, instance ):
|
||||||
|
"""
|
||||||
|
Group this instance with all it's drivers input into a single new group.
|
||||||
|
"""
|
||||||
|
if not isinstance(instance,Instance):
|
||||||
|
return
|
||||||
|
if hasattr(instance,'row'):
|
||||||
|
trace( 610, '\t | Already grouped in {}\n'.format( instance.row ))
|
||||||
|
return
|
||||||
|
|
||||||
|
drivers = []
|
||||||
|
for plug in instance.getPlugs():
|
||||||
|
if not plug.getNet():
|
||||||
|
continue
|
||||||
|
if not (plug.getMasterNet().getDirection() & Net.Direction.DirIn):
|
||||||
|
continue
|
||||||
|
m = SRAMPlacer.reIgnoreMasterNet.match( plug.getMasterNet().getName() )
|
||||||
|
if m:
|
||||||
|
continue
|
||||||
|
driverInst = DagExtension.getDriver( plug.getNet() )
|
||||||
|
if not driverInst:
|
||||||
|
continue
|
||||||
|
if not hasattr(driverInst,'row') or driverInst.row is None:
|
||||||
|
continue
|
||||||
|
drivers.append( driverInst )
|
||||||
|
trace( 610, '\t | driver {}\n'.format( driverInst.row ))
|
||||||
|
if not len(drivers):
|
||||||
|
return
|
||||||
|
bit = drivers[0].bit
|
||||||
|
words = []
|
||||||
|
for driver in drivers:
|
||||||
|
if driver.row.tag.startswith('word'):
|
||||||
|
words.append( int( driver.row.tag[4:] ))
|
||||||
|
elif driver.row.tag.startswith('mux'):
|
||||||
|
words += [ int(words) for words in driver.row.tag[3:].split( '_' ) ]
|
||||||
|
words.sort()
|
||||||
|
muxTag = 'mux'
|
||||||
|
for i in range(len(words)):
|
||||||
|
if i: muxTag += '_'
|
||||||
|
muxTag += str( words[i] )
|
||||||
|
muxGroup = Row( muxTag+'_g' )
|
||||||
|
muxRow = RowLeaf( instance, RowLeaf.KIND_OUTPUT_MUX, muxTag, bit )
|
||||||
|
midRange = len(drivers) // 2
|
||||||
|
for i in range(len(drivers)):
|
||||||
|
if i == midRange:
|
||||||
|
muxGroup.group( muxRow )
|
||||||
|
self.rows[bit].unGroup( drivers[i].row.parent )
|
||||||
|
muxGroup.group( drivers[i].row.parent )
|
||||||
|
self.rows[bit].group( muxGroup )
|
||||||
|
trace( 610, '\t | Grouped in {}\n'.format( muxTag ))
|
||||||
|
|
||||||
|
def findChild ( self, bit, tag ):
|
||||||
|
""" Search for a leaf row named ``tag`` in row ``bit`` """
|
||||||
|
return self.createRow( bit ).findChild( tag )
|
||||||
|
|
||||||
|
def createRow ( self, bit ):
|
||||||
|
"""
|
||||||
|
Returns the row at index ``bit``, expand the list and create it
|
||||||
|
if needed. All intermediate missing rows are also createds.
|
||||||
|
"""
|
||||||
|
if len(self.rows) > bit:
|
||||||
|
return self.rows[ bit ]
|
||||||
|
while len(self.rows) <= bit:
|
||||||
|
self.rows.append( Row( 'root{}'.format(bit) ))
|
||||||
|
return self.rows[-1]
|
||||||
|
|
||||||
|
def addMemBit ( self, addr, bit, inst ):
|
||||||
|
"""
|
||||||
|
Add a *memory element* in the matrix.
|
||||||
|
|
||||||
|
:param addr: The address of the word the bit is part of (X).
|
||||||
|
:param bit: The bit in the word (Y).
|
||||||
|
:param inst: The register (DFF) instance.
|
||||||
|
"""
|
||||||
|
row = self.createRow( bit )
|
||||||
|
wordTag = 'word{:d}'.format( addr )
|
||||||
|
rowLeaf = row.findChild( wordTag )
|
||||||
|
if not rowLeaf:
|
||||||
|
word = Row( wordTag+'_g' )
|
||||||
|
rowLeaf = RowLeaf( inst, RowLeaf.KIND_WORD, wordTag, bit )
|
||||||
|
word.group( rowLeaf )
|
||||||
|
row .group( word )
|
||||||
|
else:
|
||||||
|
print( ErrorMessage( 1, 'SRAMPlacer.addMemBit(): duplicated bit at ({},{})' \
|
||||||
|
.format( addr, bit )))
|
||||||
|
|
||||||
|
def findHighFanout ( self ):
|
||||||
|
"""
|
||||||
|
Find high-fanout nets (more than 20 sinks).
|
||||||
|
"""
|
||||||
|
trace( 610, '+,', '\tFind high fanout nets (above 20 sinks).\n' )
|
||||||
|
for net in self.cell.getNets():
|
||||||
|
if net.isSupply(): continue
|
||||||
|
if net.isClock(): continue
|
||||||
|
sinks = 0
|
||||||
|
for plug in net.getPlugs():
|
||||||
|
if plug.getMasterNet().getDirection() & Net.Direction.DirIn:
|
||||||
|
sinks += 1
|
||||||
|
if sinks > 20:
|
||||||
|
trace( 610, '\t- {} sinks on {}\n'.format( sinks, net ))
|
||||||
|
trace( 610, '-,' )
|
||||||
|
|
||||||
|
def findMemBits ( self ):
|
||||||
|
"""
|
||||||
|
Match the memory bits (DFF) into the matrix. Match is as follow:
|
||||||
|
|
||||||
|
* ``byte`` : the byte number. This is 4 for words of 32 bits size.
|
||||||
|
* ``bit`` : the bit inside a byte (should always be 8).
|
||||||
|
* ``word`` : the word identifier, this is the numerical value of
|
||||||
|
the address.
|
||||||
|
|
||||||
|
Yosys seems to split 32 bits value in a per byte fashion.
|
||||||
|
"""
|
||||||
|
reMem = re.compile( r'^mem(?P<byte>\d+)_(?P<word>\d+)_(?P<bit>\d+)' )
|
||||||
|
for instance in self.cell.getInstances():
|
||||||
|
PythonAttributes.enable( instance )
|
||||||
|
cellLength = instance.getAbutmentBox().getWidth()
|
||||||
|
self.totalInsts += 1
|
||||||
|
self.totalLength += cellLength
|
||||||
|
for plug in instance.getPlugs():
|
||||||
|
if plug.getMasterNet().getDirection() & Net.Direction.DirOut:
|
||||||
|
m = reMem.match( plug.getNet().getName() )
|
||||||
|
if m:
|
||||||
|
self.addMemBit( int(m.group('word'))
|
||||||
|
, int(m.group('byte'))*8 + int(m.group('bit'))
|
||||||
|
, instance )
|
||||||
|
continue
|
||||||
|
|
||||||
|
def findDecodDff ( self ):
|
||||||
|
"""
|
||||||
|
Match the DFF holding the decoded read address. Match is as follow:
|
||||||
|
|
||||||
|
Yosys seems to split 32 bits value in a per byte fashion.
|
||||||
|
"""
|
||||||
|
trace( 610, '+,', '\tFind decoder related DFFs.\n' )
|
||||||
|
reMem1 = re.compile( r'^mem(?P<byte>\d+)_rdreg_(?P<word>\d+)_q_(?P<bit>\d+)' )
|
||||||
|
reMem2 = re.compile( r'^raddr\((?P<byte>\d+)\)' )
|
||||||
|
for instance in self.cell.getInstances():
|
||||||
|
cellLength = instance.getAbutmentBox().getWidth()
|
||||||
|
for plug in instance.getPlugs():
|
||||||
|
if plug.getMasterNet().getDirection() & Net.Direction.DirOut:
|
||||||
|
m1 = reMem1.match( plug.getNet().getName() )
|
||||||
|
if not m1:
|
||||||
|
m2 = reMem2.match( plug.getNet().getName() )
|
||||||
|
if not m2:
|
||||||
|
continue
|
||||||
|
self.decodDff.append( instance )
|
||||||
|
for instance in self.decodDff:
|
||||||
|
trace( 610, '\t| {}\n'.format( instance ))
|
||||||
|
trace( 610, '-,' )
|
||||||
|
|
||||||
|
def findInputMuxes ( self ):
|
||||||
|
"""
|
||||||
|
Perform a DAG propagation to find the input muxes attached to each
|
||||||
|
memory bits.
|
||||||
|
"""
|
||||||
|
placeds = []
|
||||||
|
for inst in self.cell.getInstances():
|
||||||
|
if not hasattr(inst,'row') or inst.row is None:
|
||||||
|
continue
|
||||||
|
if inst.row.kind != RowLeaf.KIND_WORD:
|
||||||
|
continue
|
||||||
|
dag = DAG( inst )
|
||||||
|
dag.build()
|
||||||
|
#dag.show()
|
||||||
|
muxTag = 'mux_{}'.format(inst.row.tag)
|
||||||
|
rootRow = inst.row.root
|
||||||
|
muxRow = rootRow.findChild( muxTag )
|
||||||
|
if not muxRow:
|
||||||
|
muxRow = RowLeaf( dag.tree[0], RowLeaf.KIND_INPUT_MUX, muxTag, inst.bit )
|
||||||
|
inst.row.parent.group( muxRow )
|
||||||
|
placeds.append( (muxRow, dag.tree[0]) )
|
||||||
|
|
||||||
|
def findDecod ( self ):
|
||||||
|
"""
|
||||||
|
Perform a DAG propagation to find the output muxes attached to each
|
||||||
|
memory bits.
|
||||||
|
"""
|
||||||
|
trace( 610, '+,', '\tDecoder DAG\n' )
|
||||||
|
dagDecoder = self.foehn.newDag( 'decoder' )
|
||||||
|
dagDecoder.addDStart( self.cell.getNet( 'rst' ))
|
||||||
|
dagDecoder.addDStart( self.cell.getNet( 'ce' ))
|
||||||
|
dagDecoder.addDStart( self.cell.getNet( 'oe' ))
|
||||||
|
dagDecoder.addDStart( self.cell.getNet( 'we(0)' ))
|
||||||
|
dagDecoder.addDStart( self.cell.getNet( 'we(1)' ))
|
||||||
|
dagDecoder.addDStart( self.cell.getNet( 'we(2)' ))
|
||||||
|
dagDecoder.addDStart( self.cell.getNet( 'we(3)' ))
|
||||||
|
for net in self.cell.getNets():
|
||||||
|
if not net.isExternal(): continue
|
||||||
|
if not (net.getDirection() & Net.Direction.DirIn): continue
|
||||||
|
if not net.getName().startswith('addr'): continue
|
||||||
|
dagDecoder.addDStart( net )
|
||||||
|
for inst in self.decodDff:
|
||||||
|
dagDecoder.addDStart( inst )
|
||||||
|
dagDecoder.dpropagate()
|
||||||
|
self.runDag( dagDecoder )
|
||||||
|
order = 0
|
||||||
|
for dbo in dagDecoder.getDOrder():
|
||||||
|
if isinstance(dbo,Instance):
|
||||||
|
PythonAttributes.enable( dbo )
|
||||||
|
dbo.row = None
|
||||||
|
order += 1
|
||||||
|
if order > 2600:
|
||||||
|
if isinstance(dbo,Instance):
|
||||||
|
self.getSupportNets( dbo )
|
||||||
|
dagDecoder.resetDepths()
|
||||||
|
self.dagDecoder = dagDecoder
|
||||||
|
trace( 610, '-,' )
|
||||||
|
|
||||||
|
def findOutputMuxes ( self ):
|
||||||
|
"""
|
||||||
|
Perform a direct DAG propagation to find the output muxes attached
|
||||||
|
to each memory bits.
|
||||||
|
"""
|
||||||
|
trace( 610, '+,', '\tFinding output muxes\n' )
|
||||||
|
dagDecoded = self.foehn.newDag( 'decoded' )
|
||||||
|
dagDecoded.addDStart( self.cell.getNet( 'rst' ))
|
||||||
|
for inst in self.decodDff:
|
||||||
|
dagDecoded.addDStart( inst )
|
||||||
|
dagDecoded.dpropagate()
|
||||||
|
dagDecoded.resetDepths()
|
||||||
|
for index in range(32):
|
||||||
|
trace( 610, '+,', '\tProcessing bit {}\n'.format( index ))
|
||||||
|
dagOMux = self.foehn.newDag( 'outputMuxes_{}'.format(index) )
|
||||||
|
dagOMux.setIgnoredMasterNetRe( SRAMPlacer.patIgnoreMasterNet )
|
||||||
|
for inst in self.cell.getInstances():
|
||||||
|
if not hasattr(inst,'row') or inst.row is None:
|
||||||
|
continue
|
||||||
|
if inst.bit != index:
|
||||||
|
continue
|
||||||
|
if not self.libConf.isRegister(inst.getMasterCell()):
|
||||||
|
continue
|
||||||
|
dagOMux.addDStart( inst )
|
||||||
|
dagOMux.dpropagate()
|
||||||
|
self.runDag( dagOMux, self._groupInputRows )
|
||||||
|
dagOMux.resetDepths()
|
||||||
|
trace( 610, '-,' )
|
||||||
|
trace( 610, '-,' )
|
||||||
|
|
||||||
|
def runDag ( self, dag, callback=None ):
|
||||||
|
"""
|
||||||
|
Run ``callback()`` on every Instance element of the DAG
|
||||||
|
in topological order. Can also additionnaly display the
|
||||||
|
order of the DAG.
|
||||||
|
"""
|
||||||
|
order = 0
|
||||||
|
depth = 0
|
||||||
|
depthSize = 0
|
||||||
|
trace( 610, '+,' )
|
||||||
|
for dbo in dag.getDOrder():
|
||||||
|
minDepth = DagExtension.getMinDepth( dbo )
|
||||||
|
if minDepth != depth:
|
||||||
|
trace( 610, '\tDepth {} has {} gates.\n'.format( depth, depthSize ))
|
||||||
|
depth = minDepth
|
||||||
|
depthSize = 0
|
||||||
|
trace( 610, '\t{:05d} {:02d} | {}\n'.format( order, minDepth, dbo ))
|
||||||
|
if hasattr(dbo,'row'):
|
||||||
|
trace( 610, '\t | {}\n'.format( dbo.row ))
|
||||||
|
if isinstance(dbo,Net):
|
||||||
|
trace( 610, '\t | driver={}\n'.format( DagExtension.getDriver( dbo )))
|
||||||
|
if isinstance(dbo,Instance):
|
||||||
|
if callback: callback( dbo )
|
||||||
|
depthSize += 1
|
||||||
|
order += 1
|
||||||
|
trace( 610, '\tDepth {} has {} gates.\n'.format( depth, depthSize ))
|
||||||
|
trace( 610, '-,' )
|
||||||
|
|
||||||
|
def getSupportNets ( self, rootInst ):
|
||||||
|
"""
|
||||||
|
Find the set of *entry* nets (of the DAG) the ``rootInst`` is depending upon.
|
||||||
|
"""
|
||||||
|
support = set()
|
||||||
|
stack = [ rootInst ]
|
||||||
|
istack = 0
|
||||||
|
while istack < len(stack):
|
||||||
|
#print( 'istack={} on {}'.format( istack, stack[istack], flush=True ))
|
||||||
|
instance = stack[ istack ]
|
||||||
|
istack += 1
|
||||||
|
for plug in instance.getPlugs():
|
||||||
|
if not plug.getNet(): continue
|
||||||
|
if not (plug.getMasterNet().getDirection() & Net.Direction.DirIn): continue
|
||||||
|
if not DagExtension.isPresent(plug.getNet()):
|
||||||
|
support.add( plug.getNet() )
|
||||||
|
continue
|
||||||
|
driver = DagExtension.getDriver( plug.getNet() )
|
||||||
|
if not driver:
|
||||||
|
support.add( plug.getNet() )
|
||||||
|
continue
|
||||||
|
stack.append( driver )
|
||||||
|
trace( 610, '\tSupport of {}\n'.format( rootInst ))
|
||||||
|
trace( 610, '\t => ' )
|
||||||
|
for net in support:
|
||||||
|
trace( 610, '{} '.format(net.getName() ))
|
||||||
|
trace( 610, '\n' )
|
||||||
|
|
||||||
|
def show ( self ):
|
||||||
|
"""
|
||||||
|
Display the matrix contents.
|
||||||
|
"""
|
||||||
|
trace( 610, '+,', '\tSRAM Tree of {}\n'.format(self.cell) )
|
||||||
|
self.rows[0].showTree()
|
||||||
|
trace( 610, '-,' )
|
||||||
|
trace( 610, '+', '\tSRAM Matrix of {}\n'.format(self.cell) )
|
||||||
|
trace( 610, '\tCells: {}, placed: {} {:.1%}, remains {}, area placed: {:.1%}\n' \
|
||||||
|
.format( self.totalInsts
|
||||||
|
, self.placedInsts
|
||||||
|
, self.placedInsts / self.totalInsts
|
||||||
|
, self.totalInsts - self.placedInsts
|
||||||
|
, self.placedLength/self.totalLength ))
|
||||||
|
for bit in range(len(self.rows)):
|
||||||
|
trace( 610, ',+', '\tRow/bit {}\n'.format( bit ))
|
||||||
|
for leaf in self.rows[bit]:
|
||||||
|
trace( 610, '\tLeaf [{},{}] -> {} @{}\n' \
|
||||||
|
.format( 'X'
|
||||||
|
, bit
|
||||||
|
, leaf.getDriver().getName()
|
||||||
|
, leaf.instance.getTransformation()
|
||||||
|
))
|
||||||
|
trace( 610, '-,' )
|
||||||
|
trace( 610, '\tSRAM non placed instances:\n' )
|
||||||
|
for instance in self.cell.getInstances():
|
||||||
|
if not DagExtension.isPresent(instance): continue
|
||||||
|
if hasattr(instance,'row'): continue
|
||||||
|
trace( 610, '\t| {}\n'.format( instance ))
|
||||||
|
if self.libConf.isRegister(instance.getMasterCell()):
|
||||||
|
trace( 610, '\t| REGISTER\n' )
|
||||||
|
trace( 610, '\tSRAM non matched instances:\n' )
|
||||||
|
for instance in self.cell.getInstances():
|
||||||
|
if DagExtension.isPresent(instance): continue
|
||||||
|
if hasattr(instance,'row'): continue
|
||||||
|
trace( 610, '\t| {}\n'.format(instance) )
|
||||||
|
if self.libConf.isRegister(instance.getMasterCell()):
|
||||||
|
trace( 610, '\t| REGISTER\n' )
|
||||||
|
|
||||||
|
def placeAt ( self, matrixTransf=Transformation() ):
|
||||||
|
"""
|
||||||
|
Place the matrix. Each row on top of each other.
|
||||||
|
"""
|
||||||
|
bb = self.placeDecoder( matrixTransf )
|
||||||
|
matrixTransf = Transformation( matrixTransf.getTx() + bb.getWidth()
|
||||||
|
, matrixTransf.getTy()
|
||||||
|
, matrixTransf.getOrientation() )
|
||||||
|
sliceHeight = next(iter( self.rows[0] )).instance.getMasterCell().getAbutmentBox().getHeight()
|
||||||
|
with UpdateSession():
|
||||||
|
for bit in range(len(self.rows)):
|
||||||
|
y = bit*sliceHeight
|
||||||
|
orient = Transformation.Orientation.ID
|
||||||
|
if bit % 2:
|
||||||
|
y += sliceHeight
|
||||||
|
orient = Transformation.Orientation.MY
|
||||||
|
transf = Transformation( 0, y, orient )
|
||||||
|
matrixTransf.applyOn( transf )
|
||||||
|
bb.merge( self.rows[bit].placeAt( transf ))
|
||||||
|
self.cell.setAbutmentBox( bb )
|
||||||
|
for row in self.rows:
|
||||||
|
for leaf in row:
|
||||||
|
self.placedLength += leaf.instance.getMasterCell().getAbutmentBox().getWidth()
|
||||||
|
self.placedInsts += 1
|
||||||
|
|
||||||
|
def placeDecoder ( self, matrixTransf=Transformation() ):
|
||||||
|
"""
|
||||||
|
Place the decoder part of the SRAM. Simple layered implementation
|
||||||
|
for now.
|
||||||
|
"""
|
||||||
|
sliceHeight = next(iter( self.rows[0] )).instance.getMasterCell().getAbutmentBox().getHeight()
|
||||||
|
nbRows = len( self.rows )
|
||||||
|
decRows = []
|
||||||
|
for bit in range(nbRows):
|
||||||
|
y = bit*sliceHeight
|
||||||
|
orient = Transformation.Orientation.ID
|
||||||
|
if bit % 2:
|
||||||
|
y += sliceHeight
|
||||||
|
orient = Transformation.Orientation.MY
|
||||||
|
transf = Transformation( 0, y, orient )
|
||||||
|
matrixTransf.applyOn( transf )
|
||||||
|
decRows.append( [ transf, 0, [] ] )
|
||||||
|
order = 0
|
||||||
|
bb = Box()
|
||||||
|
with UpdateSession():
|
||||||
|
for dbo in self.dagDecoder.getDOrder():
|
||||||
|
if not isinstance(dbo,Instance):
|
||||||
|
continue
|
||||||
|
irow = order % nbRows
|
||||||
|
rowTransf = decRows[ irow ][ 0 ]
|
||||||
|
rowWidth = decRows[ irow ][ 1 ]
|
||||||
|
transf = Transformation( rowTransf.getTx() + rowWidth
|
||||||
|
, rowTransf.getTy()
|
||||||
|
, rowTransf.getOrientation() )
|
||||||
|
dbo.setTransformation( transf )
|
||||||
|
dbo.setPlacementStatus( Instance.PlacementStatus.PLACED )
|
||||||
|
decRows[ irow ][ 1 ] += dbo.getAbutmentBox().getWidth()
|
||||||
|
bb.merge( dbo.getAbutmentBox() )
|
||||||
|
self.placedLength += dbo.getMasterCell().getAbutmentBox().getWidth()
|
||||||
|
self.placedInsts += 1
|
||||||
|
order += 1
|
||||||
|
return bb
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# Plugin hook functions, unicornHook:menus, ScritMain:call
|
||||||
|
|
||||||
|
def unicornHook ( **kw ):
|
||||||
|
"""
|
||||||
|
The mandatory function to make the plugin appears in the menus.
|
||||||
|
"""
|
||||||
|
plugins.kwUnicornHook( 'tools.SRAMPlacer'
|
||||||
|
, 'Soft SRAM Placer'
|
||||||
|
, 'Placer dedicated to Yosys generated SRAM'
|
||||||
|
, sys.modules[__name__].__file__
|
||||||
|
, **kw
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def scriptMain ( **kw ):
|
||||||
|
"""
|
||||||
|
The mandatory function from which a plugin will be called by Coriolis CGT/Unicorn.
|
||||||
|
"""
|
||||||
|
|
||||||
|
rvalue = True
|
||||||
|
try:
|
||||||
|
DbU.setStringMode( DbU.StringModeReal, DbU.UnitPowerMicro )
|
||||||
|
#helpers.setTraceLevel( 500 )
|
||||||
|
cell, editor = plugins.kwParseMain( **kw )
|
||||||
|
placer = SRAMPlacer( cell )
|
||||||
|
placer.findMemBits()
|
||||||
|
placer.placeAt()
|
||||||
|
placer.show()
|
||||||
|
if editor: editor.fit()
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
helpers.io.catch( e )
|
||||||
|
rvalue = False
|
||||||
|
sys.stdout.flush()
|
||||||
|
sys.stderr.flush()
|
||||||
|
return rvalue
|
Loading…
Reference in New Issue