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/conductor.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/rsave.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