From 35f73ecec3898aa1e43272df9c0d478b9a73b64c Mon Sep 17 00:00:00 2001 From: Jean-Paul Chaput Date: Wed, 21 Sep 2022 17:48:26 +0200 Subject: [PATCH] Added plugin for placing Yosys generated SRAM (failed experiment). --- cumulus/src/CMakeLists.txt | 2 + cumulus/src/plugins/sramplacer1.py | 738 ++++++++++++++++++++++++ cumulus/src/plugins/sramplacer2.py | 863 +++++++++++++++++++++++++++++ 3 files changed, 1603 insertions(+) create mode 100644 cumulus/src/plugins/sramplacer1.py create mode 100644 cumulus/src/plugins/sramplacer2.py diff --git a/cumulus/src/CMakeLists.txt b/cumulus/src/CMakeLists.txt index 4f6fbfcd..80a46fb2 100644 --- a/cumulus/src/CMakeLists.txt +++ b/cumulus/src/CMakeLists.txt @@ -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 diff --git a/cumulus/src/plugins/sramplacer1.py b/cumulus/src/plugins/sramplacer1.py new file mode 100644 index 00000000..8ede4df6 --- /dev/null +++ b/cumulus/src/plugins/sramplacer1.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 ''.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 ''.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\d+)_(?P\d+)_(?P\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\d+)_rdreg_(?P\d+)_q_(?P\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 diff --git a/cumulus/src/plugins/sramplacer2.py b/cumulus/src/plugins/sramplacer2.py new file mode 100644 index 00000000..fd3827f2 --- /dev/null +++ b/cumulus/src/plugins/sramplacer2.py @@ -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 ''.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 ''.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\d+)_(?P\d+)_(?P\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\d+)_rdreg_(?P\d+)_q_(?P\d+)' ) + reMem2 = re.compile( r'^raddr\((?P\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