Added plugin for placing Yosys generated SRAM (failed experiment).

This commit is contained in:
Jean-Paul Chaput 2022-09-21 17:48:26 +02:00
parent 8980a01dae
commit 35f73ecec3
3 changed files with 1603 additions and 0 deletions

View File

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

View File

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

View File

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