Added Standard Cells based SRAM generator (Python).
The generator as been build in two parts: 1. A genereric sram.BaseSRAM class to provides support for all kind of SRAM (grouping column tree, headers, folding). 2. The specific SRAM_256x32 (256 words of 32 bits) suited for the ethmac. The sram has been simulated with genpat+asimut and gives identical results to the Yosys one (at gate level). No timing though.
This commit is contained in:
parent
df1ba66c09
commit
d294a770c4
|
@ -13,8 +13,6 @@
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/plugins/chiproute.py
|
${CMAKE_CURRENT_SOURCE_DIR}/plugins/chiproute.py
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/plugins/conductor.py
|
${CMAKE_CURRENT_SOURCE_DIR}/plugins/conductor.py
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/plugins/matrixplacer.py
|
${CMAKE_CURRENT_SOURCE_DIR}/plugins/matrixplacer.py
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/plugins/sramplacer1.py
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/plugins/sramplacer2.py
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/plugins/block.py
|
${CMAKE_CURRENT_SOURCE_DIR}/plugins/block.py
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/plugins/rsave.py
|
${CMAKE_CURRENT_SOURCE_DIR}/plugins/rsave.py
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/plugins/rsaveall.py
|
${CMAKE_CURRENT_SOURCE_DIR}/plugins/rsaveall.py
|
||||||
|
@ -45,6 +43,12 @@
|
||||||
set ( pyTools ${CMAKE_CURRENT_SOURCE_DIR}/tools/blif2vst.py
|
set ( pyTools ${CMAKE_CURRENT_SOURCE_DIR}/tools/blif2vst.py
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/tools/yosys.py
|
${CMAKE_CURRENT_SOURCE_DIR}/tools/yosys.py
|
||||||
)
|
)
|
||||||
|
set ( pyPluginSRAM ${CMAKE_CURRENT_SOURCE_DIR}/plugins/sram/__init__.py
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/plugins/sram/sram.py
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/plugins/sram/sram_256x32.py
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/plugins/sram/sramplacer1.py
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/plugins/sram/sramplacer2.py
|
||||||
|
)
|
||||||
set ( pyPluginAlpha ${CMAKE_CURRENT_SOURCE_DIR}/plugins/alpha/__init__.py
|
set ( pyPluginAlpha ${CMAKE_CURRENT_SOURCE_DIR}/plugins/alpha/__init__.py
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/plugins/alpha/utils.py
|
${CMAKE_CURRENT_SOURCE_DIR}/plugins/alpha/utils.py
|
||||||
)
|
)
|
||||||
|
@ -92,6 +96,7 @@
|
||||||
install ( FILES ${pyPluginC2C} DESTINATION ${Python_CORIOLISLIB}/cumulus/plugins/core2chip )
|
install ( FILES ${pyPluginC2C} DESTINATION ${Python_CORIOLISLIB}/cumulus/plugins/core2chip )
|
||||||
install ( FILES ${pyPluginC2C} DESTINATION ${Python_CORIOLISLIB}/cumulus/plugins/core2chip )
|
install ( FILES ${pyPluginC2C} DESTINATION ${Python_CORIOLISLIB}/cumulus/plugins/core2chip )
|
||||||
install ( FILES ${pyPluginChip} DESTINATION ${Python_CORIOLISLIB}/cumulus/plugins/chip )
|
install ( FILES ${pyPluginChip} DESTINATION ${Python_CORIOLISLIB}/cumulus/plugins/chip )
|
||||||
|
install ( FILES ${pyPluginSRAM} DESTINATION ${Python_CORIOLISLIB}/cumulus/plugins/sram )
|
||||||
install ( FILES ${pyPluginAlpha} DESTINATION ${Python_CORIOLISLIB}/cumulus/plugins/alpha )
|
install ( FILES ${pyPluginAlpha} DESTINATION ${Python_CORIOLISLIB}/cumulus/plugins/alpha )
|
||||||
install ( FILES ${pyPluginAlphaBlock} DESTINATION ${Python_CORIOLISLIB}/cumulus/plugins/alpha/block )
|
install ( FILES ${pyPluginAlphaBlock} DESTINATION ${Python_CORIOLISLIB}/cumulus/plugins/alpha/block )
|
||||||
install ( FILES ${pyPluginAlphaC2C} DESTINATION ${Python_CORIOLISLIB}/cumulus/plugins/alpha/core2chip )
|
install ( FILES ${pyPluginAlphaC2C} DESTINATION ${Python_CORIOLISLIB}/cumulus/plugins/alpha/core2chip )
|
||||||
|
|
|
@ -0,0 +1,819 @@
|
||||||
|
|
||||||
|
# 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/sram.py" |
|
||||||
|
# +-----------------------------------------------------------------+
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
The ``sram`` module provide base classes for SRAM assemby.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
import CRL
|
||||||
|
from Foehn import FoehnEngine, DagExtension
|
||||||
|
from plugins.chip.configuration import GaugeConf
|
||||||
|
|
||||||
|
|
||||||
|
af = CRL.AllianceFramework.get()
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# 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 : Bus.
|
||||||
|
|
||||||
|
class Bus ( object ):
|
||||||
|
"""
|
||||||
|
A set of Net to be manipulated as a bus.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__ ( self, sram, netFmt, bits ):
|
||||||
|
self.sram = sram
|
||||||
|
self.nets = []
|
||||||
|
if isinstance(bits,int):
|
||||||
|
bits = range( bits )
|
||||||
|
for bit in bits:
|
||||||
|
self.nets.append( self.sram.getNet( netFmt.format( bit )))
|
||||||
|
self.sram.busses[ netFmt ] = self
|
||||||
|
|
||||||
|
@property
|
||||||
|
def busWidth ( self ):
|
||||||
|
return len( self.nets )
|
||||||
|
|
||||||
|
def __getitem__ ( self, i ):
|
||||||
|
return self.nets[ i ]
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# Class : Column.
|
||||||
|
|
||||||
|
class Column ( object ):
|
||||||
|
"""
|
||||||
|
Build & manage a column of identical master cells.
|
||||||
|
"""
|
||||||
|
KIND_COLUMN = 0x0001
|
||||||
|
KIND_BLOCK = 0x0002
|
||||||
|
KIND_GROUP = 0x0004
|
||||||
|
|
||||||
|
def __init__ ( self, sram, masterCell, tag, bitNaming, busWidth ):
|
||||||
|
"""
|
||||||
|
Build (instanciate) a column of identical insts of ``masterCell``.
|
||||||
|
Instances names are generated through the ``instNaming`` callable.
|
||||||
|
"""
|
||||||
|
self.sram = sram
|
||||||
|
self.tag = tag
|
||||||
|
self.parent = None
|
||||||
|
self.order = None
|
||||||
|
self.bitNaming = bitNaming
|
||||||
|
self.busWidth = busWidth
|
||||||
|
self.insts = []
|
||||||
|
self.busPlugs = {}
|
||||||
|
self.revert = False
|
||||||
|
naming = ColNaming( self.tag + self.bitNaming )
|
||||||
|
for bit in range(busWidth):
|
||||||
|
self.insts.append( Instance.create( self.sram.cell, naming(bit), masterCell ))
|
||||||
|
for net in masterCell.getNets():
|
||||||
|
if self.sram.confLib.isDataIn(net) or self.sram.confLib.isDataOut(net):
|
||||||
|
busPlug = []
|
||||||
|
for inst in self.insts:
|
||||||
|
busPlug.append( inst.getPlug( net ))
|
||||||
|
self.busPlugs[ net.getName() ] = busPlug
|
||||||
|
|
||||||
|
def __del__ ( self ):
|
||||||
|
""" Needed to disable Python attribute uses on reference instance. """
|
||||||
|
PythonAttributes.disable( self.insts[0] )
|
||||||
|
|
||||||
|
@property
|
||||||
|
def kind ( self ): return Column.KIND_COLUMN
|
||||||
|
|
||||||
|
@property
|
||||||
|
def depth ( self ): return 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def width ( self ):
|
||||||
|
""" Width of the Column. """
|
||||||
|
return self.insts[0].getMasterCell().getAbutmentBox().getWidth()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def root ( self ):
|
||||||
|
""" Return the root of the tree. """
|
||||||
|
if self.parent:
|
||||||
|
return self.parent.root
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __repr__ ( self ):
|
||||||
|
return '<Column "{}" d={} {} {}>'.format( self.insts[0].getMasterCell().getName()
|
||||||
|
, self.depth
|
||||||
|
, self.tag
|
||||||
|
, self.busWidth )
|
||||||
|
|
||||||
|
def findChild ( self, tag ):
|
||||||
|
"""
|
||||||
|
Terminal function for ColGroup.findChild(), display itsef if ``tag`` match.
|
||||||
|
"""
|
||||||
|
if self.tag == tag: return self
|
||||||
|
return None
|
||||||
|
|
||||||
|
def setBusNet ( self, busName, busNet ):
|
||||||
|
"""
|
||||||
|
Connect a bus to the column. ``busName`` is the name of the master net
|
||||||
|
in the reference cell of the column.
|
||||||
|
"""
|
||||||
|
busPlug = self.busPlugs[ busName ]
|
||||||
|
if busPlug[0].getNet() and busPlug[0].getNet() != busNet[0]:
|
||||||
|
print( Warning( 'Column.setBusNet(): Overrode {} {} -> {} with {}' \
|
||||||
|
.format( busPlug[0].getInstance()
|
||||||
|
, busName
|
||||||
|
, busPlug[0].getNet()
|
||||||
|
, busNet[0] )))
|
||||||
|
for i in range(self.busWidth):
|
||||||
|
busPlug[ i ].setNet( busNet[i] )
|
||||||
|
trace( 605, '\tsetBusNet {} {} -> {}\n'.format( busPlug[0].getInstance(), busName, busNet[0] ))
|
||||||
|
|
||||||
|
def setCmdNet ( self, cmd, net ):
|
||||||
|
masterNet = self.insts[0].getMasterCell().getNet( cmd )
|
||||||
|
for bus in range(self.busWidth):
|
||||||
|
self.insts[ bus ].getPlug( masterNet ).setNet( net )
|
||||||
|
|
||||||
|
def setCutCost ( self, order ):
|
||||||
|
self.order = order + 1
|
||||||
|
return self.order
|
||||||
|
|
||||||
|
def reverse ( self ):
|
||||||
|
""" reverse() toggle a MX symmetry to the column. """
|
||||||
|
self.revert = not self.revert
|
||||||
|
|
||||||
|
def place ( self ):
|
||||||
|
""" Perform the placement of the column at ``x``. """
|
||||||
|
trace( 610, ',+', "\tColumn.place() tag={}\n".format( self.tag ))
|
||||||
|
bb = Box()
|
||||||
|
self.sram.doFoldAtColumn( self )
|
||||||
|
x, irow = self.sram.foldState.getPosition()
|
||||||
|
for bit in range(self.busWidth):
|
||||||
|
if bit == 0:
|
||||||
|
PythonAttributes.enable( self.insts[0] )
|
||||||
|
self.insts[0].fold = self.sram.foldState.fold
|
||||||
|
trace( 610, "\tx={} irow={} tag={}\n" \
|
||||||
|
.format( DbU.getValueString(x), irow+bit, self.tag ))
|
||||||
|
self.sram.placeInstance( self.insts[ bit ], x, irow+bit, self.sram.foldState.direction, self.revert )
|
||||||
|
bb.merge( self.insts[bit].getAbutmentBox() )
|
||||||
|
self.sram.foldState.addWidth( self.width )
|
||||||
|
trace( 610, '-,' )
|
||||||
|
return bb
|
||||||
|
|
||||||
|
def showTree ( self, depth ):
|
||||||
|
""" Terminal function for ColGroup.showTree(), display itsef. """
|
||||||
|
print( '{}| {}'.format( ' '*depth, self ))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# Class : ColBlock.
|
||||||
|
|
||||||
|
class ColBlock ( object ):
|
||||||
|
"""
|
||||||
|
Manage an column made of irregular instances, more than one instance
|
||||||
|
can be put on each slice.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__ ( self, sram, tag, busWidth ):
|
||||||
|
"""
|
||||||
|
Manage an column made of irregular instances, more than one instance
|
||||||
|
can be put on each slice.
|
||||||
|
"""
|
||||||
|
self.sram = sram
|
||||||
|
self.tag = tag
|
||||||
|
self.parent = None
|
||||||
|
self.busWidth = busWidth
|
||||||
|
self.width = 0
|
||||||
|
self.widths = []
|
||||||
|
self.rows = []
|
||||||
|
for i in range(self.busWidth):
|
||||||
|
self.rows.append( [] )
|
||||||
|
self.widths.append( 0 )
|
||||||
|
|
||||||
|
@property
|
||||||
|
def kind ( self ): return Column.KIND_BLOCK
|
||||||
|
|
||||||
|
@property
|
||||||
|
def depth ( self ): return 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def root ( self ):
|
||||||
|
""" Return the root of the tree. """
|
||||||
|
if self.parent:
|
||||||
|
return self.parent.root
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __repr__ ( self ):
|
||||||
|
return '<ColBlock d={} {} {}>'.format( self.depth, self.tag, self.busWidth )
|
||||||
|
|
||||||
|
def showTree ( self, depth ):
|
||||||
|
""" Terminal function for ColGroup.showTree(), display itsef. """
|
||||||
|
print( '{}| {}'.format( ' '*depth, self ))
|
||||||
|
|
||||||
|
def addInstance ( self, irow, inst ):
|
||||||
|
"""
|
||||||
|
Add an instance to the column block. Try to add the instance in
|
||||||
|
rows ``[irow,irow+3]`` so the width increase will be minimized.
|
||||||
|
"""
|
||||||
|
inarrower = irow
|
||||||
|
for i in range( irow+1, min( irow+4, len(self.rows))):
|
||||||
|
if self.widths[ i ] < self.widths[ inarrower ]:
|
||||||
|
inarrower = i
|
||||||
|
self.rows [ inarrower ].append( inst )
|
||||||
|
self.widths[ inarrower ] += inst.getMasterCell().getAbutmentBox().getWidth()
|
||||||
|
if self.widths[ inarrower ] > self.width:
|
||||||
|
self.width = self.widths[ inarrower ]
|
||||||
|
|
||||||
|
def place ( self, x ):
|
||||||
|
""" Perform the placement of the column block at ``x``. """
|
||||||
|
bb = Box()
|
||||||
|
for irow in range(self.busWidth):
|
||||||
|
xrow = x
|
||||||
|
for inst in self.rows[irow]:
|
||||||
|
self.sram.placeInstance( inst, xrow, irow )
|
||||||
|
xrow += inst.getMasterCell().getAbutmentBox().getWidth()
|
||||||
|
bb.merge( inst.getAbutmentBox() )
|
||||||
|
return bb
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# 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.order = None
|
||||||
|
self.depth = 0
|
||||||
|
self.childs = []
|
||||||
|
|
||||||
|
def __iter__ ( self ):
|
||||||
|
return ColGroupIterator( self )
|
||||||
|
|
||||||
|
def __repr__ ( self ):
|
||||||
|
return '<ColGroup d={} {} childs={}>'.format( self.depth, self.tag, len(self.childs ))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def kind ( self ): return Column.KIND_GROUP
|
||||||
|
|
||||||
|
@property
|
||||||
|
def width ( self ):
|
||||||
|
""" Width of the whole ColGroup (sum of all it's children's width). """
|
||||||
|
width = 0
|
||||||
|
for child in self.childs:
|
||||||
|
width += child.width
|
||||||
|
return width
|
||||||
|
|
||||||
|
@property
|
||||||
|
def root ( self ):
|
||||||
|
""" Return the root of the tree. """
|
||||||
|
if self.parent:
|
||||||
|
return self.parent.root
|
||||||
|
return self
|
||||||
|
|
||||||
|
@property
|
||||||
|
def busWidth ( self ):
|
||||||
|
"""
|
||||||
|
Return the width of the group. This the widest bus width of all the childs.
|
||||||
|
"""
|
||||||
|
busWidth = 0
|
||||||
|
for child in self.childs:
|
||||||
|
busWidth = max( busWidth, child.busWidth )
|
||||||
|
return busWidth
|
||||||
|
|
||||||
|
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=None ):
|
||||||
|
""" Remove a child from the group (the child is *not* deleted). """
|
||||||
|
if child is None:
|
||||||
|
if not self.parent:
|
||||||
|
return
|
||||||
|
self.parent.unGroup( self )
|
||||||
|
return
|
||||||
|
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 setCutCost ( self, order ):
|
||||||
|
order += 1
|
||||||
|
self.order = order
|
||||||
|
for child in self.childs:
|
||||||
|
order = child.setCutCost( order )
|
||||||
|
order += 1
|
||||||
|
return order
|
||||||
|
|
||||||
|
def reverse (self ):
|
||||||
|
"""
|
||||||
|
Reverse the order of the childs of the group *and* perform it recursively on each
|
||||||
|
child itself.
|
||||||
|
"""
|
||||||
|
for child in self.childs:
|
||||||
|
child.reverse()
|
||||||
|
self.childs.reverse()
|
||||||
|
|
||||||
|
def place ( self ):
|
||||||
|
""" Place childs/colums from left to rigth. """
|
||||||
|
bb = Box()
|
||||||
|
for child in self.childs:
|
||||||
|
bb.merge( child.place() )
|
||||||
|
return bb
|
||||||
|
|
||||||
|
def showTree ( self, depth=0 ):
|
||||||
|
""" Display the tree rooted at this ColGroup. """
|
||||||
|
print( '{}+ {}'.format( ' '*depth, self ))
|
||||||
|
for child in self.childs:
|
||||||
|
child.showTree( depth+1 )
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# Class : ColGroupIterator.
|
||||||
|
|
||||||
|
class ColGroupIterator ( object ):
|
||||||
|
"""
|
||||||
|
Provide an iterator over all the *leaf* instances of a ColGroup tree.
|
||||||
|
(all intermediate ColGroup are ignored)
|
||||||
|
"""
|
||||||
|
|
||||||
|
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._subIter = 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 : ColNaming.
|
||||||
|
|
||||||
|
class ColNaming ( object ):
|
||||||
|
"""
|
||||||
|
Callable to generate the individual instances name of each bit
|
||||||
|
in a regular column.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__ ( self, instFormat ):
|
||||||
|
self.instFormat = instFormat
|
||||||
|
|
||||||
|
def __call__ ( self, bit ):
|
||||||
|
return self.instFormat.format( wbit=bit, bbit=(bit%8), byte=(bit//8) )
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# Class : HeaderRow.
|
||||||
|
|
||||||
|
class HeaderRow ( object ):
|
||||||
|
"""
|
||||||
|
Build the top row of the memory area datapath. Usually containing
|
||||||
|
the buffers for the local command drivers.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__ ( self, sram ):
|
||||||
|
self.sram = sram
|
||||||
|
self.row = {}
|
||||||
|
|
||||||
|
def addInstanceAt ( self, inst, refInst ):
|
||||||
|
"""
|
||||||
|
Add an Instance in the header with it's reference instance, taken
|
||||||
|
from the to be associated Column. The reference instance gives a
|
||||||
|
hint about the X position the cell must be placed.
|
||||||
|
"""
|
||||||
|
if refInst not in self.row:
|
||||||
|
self.row[ refInst ] = []
|
||||||
|
self.row[ refInst ].append( inst )
|
||||||
|
|
||||||
|
def place ( self, xstart, irow, direction ):
|
||||||
|
"""
|
||||||
|
Perform the placement of the header at ``(x,irow)``. Instances
|
||||||
|
in the row are placed as close as possible to the X position of
|
||||||
|
their reference instance (part of a Column).
|
||||||
|
"""
|
||||||
|
def getXMin ( inst ):
|
||||||
|
return inst.getAbutmentBox().getXMin()
|
||||||
|
|
||||||
|
def getXMax ( inst ):
|
||||||
|
return inst.getAbutmentBox().getXMax()
|
||||||
|
|
||||||
|
|
||||||
|
trace( 610, ',+', '\tHeaderRow.place() {}\n'.format( DbU.getValueString(xstart) ))
|
||||||
|
xsorteds = []
|
||||||
|
reverse = (direction != BaseSRAM.TO_RIGHT)
|
||||||
|
for refInst in self.row.keys():
|
||||||
|
trace( 610, '\trefInst.getXMin() {}\n'.format( DbU.getValueString(refInst.getAbutmentBox().getXMin()) ))
|
||||||
|
xsorteds.append( refInst )
|
||||||
|
bb = Box()
|
||||||
|
xsorteds.sort( key=getXMin, reverse=reverse )
|
||||||
|
if direction == BaseSRAM.TO_RIGHT:
|
||||||
|
x = xstart
|
||||||
|
for refInst in xsorteds:
|
||||||
|
xmin = getXMin( refInst )
|
||||||
|
if xmin > x:
|
||||||
|
x = xmin
|
||||||
|
for inst in self.row[ refInst ]:
|
||||||
|
self.sram.placeInstance( inst, x, irow, direction )
|
||||||
|
bb.merge( inst.getAbutmentBox() )
|
||||||
|
x += inst.getMasterCell().getAbutmentBox().getWidth()
|
||||||
|
else:
|
||||||
|
x = xstart
|
||||||
|
for refInst in xsorteds:
|
||||||
|
xmin = getXMax( refInst )
|
||||||
|
if xmin < x:
|
||||||
|
x = xmin
|
||||||
|
for inst in self.row[ refInst ]:
|
||||||
|
self.sram.placeInstance( inst, x, irow, direction )
|
||||||
|
bb.merge( inst.getAbutmentBox() )
|
||||||
|
x -= inst.getMasterCell().getAbutmentBox().getWidth()
|
||||||
|
trace( 610, '-,' )
|
||||||
|
return bb
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# Class : FoldState.
|
||||||
|
|
||||||
|
class FoldState ( object ):
|
||||||
|
"""
|
||||||
|
Manage information about how to fold the group tree and the
|
||||||
|
current information during the placement process.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__ ( self, sram, fold ):
|
||||||
|
self.sram = sram
|
||||||
|
self.foldNb = fold
|
||||||
|
self.fold = 0
|
||||||
|
|
||||||
|
def setupDimensions ( self ):
|
||||||
|
"""
|
||||||
|
Compute folding coordinates. Must be called at the beginning
|
||||||
|
of the placement stage, after all instances have been created.
|
||||||
|
"""
|
||||||
|
foldedWidth = self.sram.rootGroup.width // self.foldNb
|
||||||
|
self.xmin = self.sram.decoder.width
|
||||||
|
self.xmax = self.sram.decoder.width + foldedWidth
|
||||||
|
self.x = self.xmin
|
||||||
|
self.irow = 0
|
||||||
|
self.direction = BaseSRAM.TO_RIGHT
|
||||||
|
|
||||||
|
def getPosition ( self ):
|
||||||
|
""" Return the position to put the next column. """
|
||||||
|
return self.x, self.irow
|
||||||
|
|
||||||
|
def forceFold ( self ):
|
||||||
|
if self.direction == BaseSRAM.TO_RIGHT:
|
||||||
|
self.direction = BaseSRAM.TO_LEFT
|
||||||
|
self.x = self.xmax
|
||||||
|
else:
|
||||||
|
self.direction = BaseSRAM.TO_RIGHT
|
||||||
|
self.x = self.xmin
|
||||||
|
self.irow += self.sram.rootGroup.busWidth + 1
|
||||||
|
self.fold += 1
|
||||||
|
|
||||||
|
def addWidth ( self, width ):
|
||||||
|
""" Increment the currently placed width and update ``(x,irow)``. """
|
||||||
|
if self.direction == BaseSRAM.TO_RIGHT:
|
||||||
|
self.x += width
|
||||||
|
else:
|
||||||
|
self.x -= width
|
||||||
|
#if self.direction == BaseSRAM.TO_RIGHT:
|
||||||
|
# if self.x + width <= self.xmax:
|
||||||
|
# self.x += width
|
||||||
|
# return
|
||||||
|
# self.direction = BaseSRAM.TO_LEFT
|
||||||
|
# self.x = self.xmax
|
||||||
|
#else:
|
||||||
|
# if self.x - width >= self.xmin:
|
||||||
|
# self.x -= width
|
||||||
|
# return
|
||||||
|
# self.direction = BaseSRAM.TO_RIGHT
|
||||||
|
# self.x = self.xmin
|
||||||
|
#self.irow += self.sram.rootGroup.busWidth + 1
|
||||||
|
#self.fold += 1
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# Class : BaseSRAM.
|
||||||
|
|
||||||
|
class BaseSRAM ( object ):
|
||||||
|
"""
|
||||||
|
Base class for SRAM builder. Provides utility functions.
|
||||||
|
"""
|
||||||
|
TO_RIGHT = 1
|
||||||
|
TO_LEFT = 2
|
||||||
|
|
||||||
|
def __init__ ( self, fold ):
|
||||||
|
"""
|
||||||
|
Create the base structure of a SRAM, which contains :
|
||||||
|
|
||||||
|
* ``self.dffCell`` : The DFF standard cell which provides the single
|
||||||
|
bit storing.
|
||||||
|
* ``self.rootGroup`` : A root column group named ``rootSRAM``, for the
|
||||||
|
matrix part (datapath).
|
||||||
|
* ``self.decoder`` : The soft block to store decoder cells.
|
||||||
|
* ``self.headers`` : The headers for the columns command and buffer
|
||||||
|
lines.
|
||||||
|
|
||||||
|
The overall relative placement is organized as follow : ::
|
||||||
|
|
||||||
|
+---------+-------------------------------------------+
|
||||||
|
| | headers[1] (1 row) |
|
||||||
|
| +-------------------------------------------+
|
||||||
|
| | |
|
||||||
|
| | Column area, fold 1 (N rows) |
|
||||||
|
| | |
|
||||||
|
| decoder +-------------------------------------------+
|
||||||
|
| | headers[0] (1 row) |
|
||||||
|
| +-------------------------------------------+
|
||||||
|
| | |
|
||||||
|
| | Column area, fold 0 (N rows) |
|
||||||
|
| | |
|
||||||
|
+---------+-------------------------------------------+
|
||||||
|
"""
|
||||||
|
self.confLib = StdCellConf()
|
||||||
|
self.foldState = FoldState( self, fold )
|
||||||
|
self.cell = None
|
||||||
|
self.foldTags = []
|
||||||
|
self.dffCell = af.getCell( 'sff1_x4', CRL.Catalog.State.Views )
|
||||||
|
self.position = Transformation()
|
||||||
|
self.sliceHeight = self.dffCell.getAbutmentBox().getHeight()
|
||||||
|
self.rootGroup = ColGroup( 'rootSRAM' )
|
||||||
|
self.busses = {}
|
||||||
|
self.decoder = None
|
||||||
|
self.toHeaders = []
|
||||||
|
self.headers = [ HeaderRow( self ) for row in range(fold) ]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fold ( self ):
|
||||||
|
return self.foldState.fold
|
||||||
|
|
||||||
|
def doFoldAtColumn ( self, column ):
|
||||||
|
if column.tag in self.foldTags:
|
||||||
|
self.foldState.forceFold()
|
||||||
|
|
||||||
|
def getNet ( self, name, create=True ):
|
||||||
|
"""
|
||||||
|
Find a Net by name. If it doesn't exists and ``create`` is set to ``True``,
|
||||||
|
add it to the netlist.
|
||||||
|
"""
|
||||||
|
net = self.cell.getNet( name )
|
||||||
|
if net:
|
||||||
|
return net
|
||||||
|
if not create:
|
||||||
|
return None
|
||||||
|
return Net.create( self.cell, name )
|
||||||
|
|
||||||
|
def addExternalNet ( self, name, direction, kind=Net.Type.LOGICAL ):
|
||||||
|
"""
|
||||||
|
Add a Net to the cell interface.
|
||||||
|
"""
|
||||||
|
net = self.getNet( name )
|
||||||
|
net.setExternal ( True )
|
||||||
|
net.setDirection( direction )
|
||||||
|
net.setType ( kind )
|
||||||
|
if kind == Net.Type.POWER or kind == Net.Type.GROUND:
|
||||||
|
net.setGlobal( True )
|
||||||
|
return net
|
||||||
|
|
||||||
|
def addInstance ( self, masterName, instName, netMapNames ):
|
||||||
|
"""
|
||||||
|
Create an Instance named ``instName`` of model ``masterName`` and
|
||||||
|
connect it's Plugs according to the dictionary ``netMapNames``.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
self.addInstance( 'a2_x2'
|
||||||
|
, 'decod_a2_1'
|
||||||
|
, { 'i0' : 'net_input_0'
|
||||||
|
, 'i1' : 'net_input_1'
|
||||||
|
, 'q' : 'net_output_X' } )
|
||||||
|
"""
|
||||||
|
masterCell = af.getCell( masterName, CRL.Catalog.State.Views )
|
||||||
|
inst = Instance.create( self.cell, instName, masterCell )
|
||||||
|
for masterNetName, netName in netMapNames.items():
|
||||||
|
masterNet = masterCell.getNet( masterNetName )
|
||||||
|
net = self.getNet( netName )
|
||||||
|
plug = inst.getPlug( masterNet )
|
||||||
|
plug.setNet( net )
|
||||||
|
return inst
|
||||||
|
|
||||||
|
def connect ( self, instName, masterNetName, netName ):
|
||||||
|
"""
|
||||||
|
Connect the Plug ``masterNetName`` of instance ``instName`` to the
|
||||||
|
net ``netName``.
|
||||||
|
"""
|
||||||
|
inst = self.cell.getInstance( instName )
|
||||||
|
masterNet = inst.getMasterCell().getNet( masterNetName )
|
||||||
|
net = self.getNet( netName )
|
||||||
|
inst.getPlug( masterNet ).setNet( net )
|
||||||
|
|
||||||
|
def placeInstance ( self, inst, x, irow, direction=TO_RIGHT, reverse=False ):
|
||||||
|
"""
|
||||||
|
Place an instance at a position defined by ``(x,irow)`` where :
|
||||||
|
|
||||||
|
* ``x`` : the usual X coordinate.
|
||||||
|
* ``irow`` : the row into which to put the cell, the Y coordinate
|
||||||
|
is computed from it, accounting for the X axis
|
||||||
|
flipping that occurs on one row over two.
|
||||||
|
|
||||||
|
The position is relative to the bottom left corner of the design
|
||||||
|
given by ``self.position``.
|
||||||
|
|
||||||
|
.. note:: ``self.position`` should not contains rotations, unmanaged
|
||||||
|
for now.
|
||||||
|
"""
|
||||||
|
orients = { BaseSRAM.TO_RIGHT : [ Transformation.Orientation.ID
|
||||||
|
, Transformation.Orientation.MY ]
|
||||||
|
, BaseSRAM.TO_LEFT : [ Transformation.Orientation.MX
|
||||||
|
, Transformation.Orientation.R2 ]
|
||||||
|
}
|
||||||
|
if reverse:
|
||||||
|
if direction == BaseSRAM.TO_RIGHT:
|
||||||
|
x += inst.getMasterCell().getAbutmentBox().getWidth()
|
||||||
|
direction = BaseSRAM.TO_LEFT
|
||||||
|
else:
|
||||||
|
x -= inst.getMasterCell().getAbutmentBox().getWidth()
|
||||||
|
direction = BaseSRAM.TO_RIGHT
|
||||||
|
y = irow*self.sliceHeight
|
||||||
|
orient = orients[ direction ][ 0 ]
|
||||||
|
if irow % 2:
|
||||||
|
y += self.sliceHeight
|
||||||
|
orient = orients[ direction ][ 1 ]
|
||||||
|
trace( 610, '\tBaseSRAM.placeInstance() x={} y={} orient={} {}\n' \
|
||||||
|
.format( DbU.getValueString(x), DbU.getValueString(y), orient, inst ))
|
||||||
|
transf = Transformation( x, y, orient )
|
||||||
|
self.position.applyOn( transf )
|
||||||
|
inst.setTransformation( transf )
|
||||||
|
inst.setPlacementStatus( Instance.PlacementStatus.PLACED )
|
||||||
|
return inst.getAbutmentBox()
|
||||||
|
|
||||||
|
def findFoldColumns ( self ):
|
||||||
|
"""
|
||||||
|
Find the cuts between columns where the wiring is minimal.
|
||||||
|
Based on the ColGroup tree, assuming that the deeper a column is
|
||||||
|
in the tree the more they are closely connected and must not be
|
||||||
|
separateds.
|
||||||
|
"""
|
||||||
|
trace( 610, ',+', '\tBaseSRAM.findFoldcolumns()\n' )
|
||||||
|
self.rootGroup.setCutCost( 0 )
|
||||||
|
prevOrder = 0
|
||||||
|
cutCosts = {}
|
||||||
|
count = 0
|
||||||
|
for column in self.rootGroup:
|
||||||
|
cutCost = column.order-prevOrder
|
||||||
|
if cutCost in cutCosts:
|
||||||
|
cutCosts[ cutCost ].append(( count, column.tag ))
|
||||||
|
else:
|
||||||
|
cutCosts[ cutCost ] = [( count, column.tag )]
|
||||||
|
trace( 610, '\t{:>4} {:>4} {:>4} {}\n'.format( count
|
||||||
|
, column.order
|
||||||
|
, column.order-prevOrder
|
||||||
|
, column ))
|
||||||
|
prevOrder = column.order
|
||||||
|
count += 1
|
||||||
|
keys = list( cutCosts.keys() )
|
||||||
|
keys.sort()
|
||||||
|
for key in keys:
|
||||||
|
trace( 610, '\tcutCost[ {} ] = \n'.format( key ))
|
||||||
|
for order, tag in cutCosts[key]:
|
||||||
|
trace( 610, '\t | {:>3d} {}\n'.format( order, tag ))
|
||||||
|
trace( 610, '-,' )
|
||||||
|
|
||||||
|
def placeAt ( self, position=Transformation() ):
|
||||||
|
"""
|
||||||
|
Perform the placement of all the various area of the SRAM.
|
||||||
|
For the overall layout, see ``__init__()``.
|
||||||
|
"""
|
||||||
|
self.findFoldColumns()
|
||||||
|
self.position = position
|
||||||
|
self.foldState.setupDimensions()
|
||||||
|
with UpdateSession():
|
||||||
|
bb = Box()
|
||||||
|
bb.merge( self.decoder.place( 0 ) )
|
||||||
|
bb.merge( self.rootGroup.place() )
|
||||||
|
for inst, refInst in self.toHeaders:
|
||||||
|
self.headers[ refInst.fold ].addInstanceAt( inst, refInst )
|
||||||
|
for i in range(len(self.headers)):
|
||||||
|
trace( 610, ',+', 'Place row header {} {}\n'.format( i, self.headers[i].row ))
|
||||||
|
if i % 2:
|
||||||
|
xstart = bb.getXMax()
|
||||||
|
direction = BaseSRAM.TO_LEFT
|
||||||
|
else:
|
||||||
|
xstart = self.decoder.width
|
||||||
|
direction = BaseSRAM.TO_RIGHT
|
||||||
|
bb.merge( self.headers[i].place( xstart
|
||||||
|
, self.rootGroup.busWidth*(i + 1) + i
|
||||||
|
, direction ))
|
||||||
|
trace( 610, '-,' )
|
||||||
|
self.cell.setAbutmentBox( bb )
|
||||||
|
|
||||||
|
def showTree ( self ):
|
||||||
|
"""
|
||||||
|
Display the Column tree of the SRAM.
|
||||||
|
"""
|
||||||
|
self.rootGroup.showTree()
|
|
@ -0,0 +1,493 @@
|
||||||
|
|
||||||
|
# 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/sram_256x32.py" |
|
||||||
|
# +-----------------------------------------------------------------+
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Verilog description of ``spram_256x32`` (256 words of 32 bits).
|
||||||
|
This descripion is the ASIC part of the SPRAM exctracted from
|
||||||
|
FreeCores : ::
|
||||||
|
|
||||||
|
https://github.com/freecores/ethmac.git
|
||||||
|
|
||||||
|
.. code:: Verilog
|
||||||
|
|
||||||
|
module eth_spram_256x32(
|
||||||
|
// Generic synchronous single-port RAM interface
|
||||||
|
clk, rst, ce, we, oe, addr, di, dato
|
||||||
|
|
||||||
|
// Generic synchronous single-port RAM interface
|
||||||
|
input clk; // Clock, rising edge
|
||||||
|
input rst; // Reset, active high
|
||||||
|
input ce; // Chip enable input, active high
|
||||||
|
input [ 3: 0] we; // Write enable input, active high
|
||||||
|
input oe; // Output enable input, active high
|
||||||
|
input [ 7: 0] addr; // address bus inputs
|
||||||
|
input [31: 0] di; // input data bus
|
||||||
|
output [31: 0] dato; // output data bus
|
||||||
|
|
||||||
|
reg [ 7: 0] mem0 [255:0];
|
||||||
|
reg [15: 8] mem1 [255:0];
|
||||||
|
reg [23:16] mem2 [255:0];
|
||||||
|
reg [31:24] mem3 [255:0];
|
||||||
|
wire [31: 0] q;
|
||||||
|
reg [ 7: 0] raddr;
|
||||||
|
|
||||||
|
// Data output drivers
|
||||||
|
assign dato = (oe & ce) ? q : {32{1'bz}};
|
||||||
|
|
||||||
|
// RAM read and write
|
||||||
|
// read operation
|
||||||
|
always@(posedge clk)
|
||||||
|
if (ce)
|
||||||
|
raddr <= addr; // read address needs to be registered to read clock
|
||||||
|
|
||||||
|
assign q = rst ? {32{1'b0}} : { mem3[raddr]
|
||||||
|
, mem2[raddr]
|
||||||
|
, mem1[raddr]
|
||||||
|
, mem0[raddr] };
|
||||||
|
|
||||||
|
// write operation
|
||||||
|
always@(posedge clk)
|
||||||
|
begin
|
||||||
|
if (ce && we[3]) mem3[addr] <= di[31:24];
|
||||||
|
if (ce && we[2]) mem2[addr] <= di[23:16];
|
||||||
|
if (ce && we[1]) mem1[addr] <= di[15: 8];
|
||||||
|
if (ce && we[0]) mem0[addr] <= di[ 7: 0];
|
||||||
|
end
|
||||||
|
endmodule
|
||||||
|
|
||||||
|
|
||||||
|
Provisional results
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. note:: All length are in micro-meters.
|
||||||
|
|
||||||
|
+--------------+-----------------------------+-----------------------------+
|
||||||
|
| Kind | Generator | Yosys |
|
||||||
|
+==============+=============================+=============================+
|
||||||
|
| # Gates | 23209 (-25.4%) | 32121 |
|
||||||
|
+--------------+-----------------------------+-----------------------------+
|
||||||
|
| 1 Fold |
|
||||||
|
+--------------+-----------------------------+-----------------------------+
|
||||||
|
| Area | 7182 x 330 (-5.5%) | 7380 x 340 |
|
||||||
|
+--------------+-----------------------------+-----------------------------+
|
||||||
|
| Wirelength | 1841036 (-4.3%) | 1924153 |
|
||||||
|
+--------------+-----------------------------+-----------------------------+
|
||||||
|
| 2 Fold |
|
||||||
|
+--------------+-----------------------------+-----------------------------+
|
||||||
|
| Area | 3599 x 660 (-5.3%) | 3690 x 680 |
|
||||||
|
+--------------+-----------------------------+-----------------------------+
|
||||||
|
| Wirelength | 1670455 (-6.3%) | 1782558 |
|
||||||
|
+--------------+-----------------------------+-----------------------------+
|
||||||
|
| 4 Fold |
|
||||||
|
+--------------+-----------------------------+-----------------------------+
|
||||||
|
| Area | 1812 x 1320 (-4.6%) | 1900 x 1320 |
|
||||||
|
+--------------+-----------------------------+-----------------------------+
|
||||||
|
| Wirelength | 1699810 (-1.5%) | 1726436 |
|
||||||
|
+--------------+-----------------------------+-----------------------------+
|
||||||
|
|
||||||
|
Conclusions that we can draw from those results are :
|
||||||
|
|
||||||
|
1. The generator version uses subtantially less gates than the Yosys one.
|
||||||
|
As the both SRAM uses the exact same number of SFFs, the difference is
|
||||||
|
only due to the decoder for the control of input and output muxes.
|
||||||
|
|
||||||
|
2. Notwithanding having less gates the generator version uses similar areas,
|
||||||
|
which means that we use fewer but significantly *bigger* cells.
|
||||||
|
|
||||||
|
3. The FlexLib library supplied for SkyWater 130nm do not contains all
|
||||||
|
SxLib one, effectively restricting our choices.
|
||||||
|
|
||||||
|
In particular, to build the output multiplexer we only have mx2 and
|
||||||
|
mx3 cells, which are large. The density of the SRAM could be much
|
||||||
|
increased if we did have nmx2 and nmx3. We could also try to synthesise
|
||||||
|
the tree using nandX and norX but we are short of time.
|
||||||
|
|
||||||
|
Furthermore for the output multiplexers, as it is a controlled case,
|
||||||
|
we may also uses three-state drivers cells (which have not been
|
||||||
|
ported either).
|
||||||
|
|
||||||
|
.. note:: Cell width in the SkyWater 130 port of FlexLib:
|
||||||
|
|
||||||
|
============== =====
|
||||||
|
Cell Width
|
||||||
|
============== =====
|
||||||
|
mx2_x2 7
|
||||||
|
mx3_x2 11
|
||||||
|
nand2_x0 2
|
||||||
|
nand3_x0 3
|
||||||
|
nand4_x0 4
|
||||||
|
nor2_x0 2
|
||||||
|
============== =====
|
||||||
|
|
||||||
|
1. mx2_x2 + mx3_x2 = 18
|
||||||
|
2. 9 * nand2_x0 = 18
|
||||||
|
3. 4 * nand3_x0 + nand4_x0 = 16
|
||||||
|
4. 6 * nand2_x0 + nor2_x0 = 14
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
import CRL
|
||||||
|
from Foehn import FoehnEngine, DagExtension
|
||||||
|
from plugins.chip.configuration import GaugeConf
|
||||||
|
from plugins.sram.sram import Bus, Column, ColBlock, ColGroup, \
|
||||||
|
HeaderRow, BaseSRAM
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Simple Standard cells based SRAM generator.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
af = CRL.AllianceFramework.get()
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# Class : SRAM_256x32.
|
||||||
|
|
||||||
|
class SRAM_256x32 ( BaseSRAM ):
|
||||||
|
"""
|
||||||
|
Build & place a SRAM of 256 words of 32 bits.
|
||||||
|
"""
|
||||||
|
BIT_GROUP_FMT = 'bit_addr{:04d}_g'
|
||||||
|
MUX_GROUP_FMT = 'bits_{}_g'
|
||||||
|
|
||||||
|
def __init__ ( self, fold ):
|
||||||
|
BaseSRAM.__init__( self, fold )
|
||||||
|
if fold == 1:
|
||||||
|
pass
|
||||||
|
elif fold == 2:
|
||||||
|
self.foldTags = [ 'imux_addr0128' ]
|
||||||
|
elif fold == 4:
|
||||||
|
self.foldTags = [ 'omux_0_to_127', 'imux_addr0128', 'imux_addr0240' ]
|
||||||
|
#self.foldTags = [ 'imux_addr0064', 'imux_addr0128', 'imux_addr0192' ]
|
||||||
|
else:
|
||||||
|
raise ErrorMessage( 1, 'SRAM_256x32.__init__(): Unsupported fold {}, valid values are 1, 2, 4.'.format( fold ))
|
||||||
|
self.cell = af.createCell( 'spram_256x32' )
|
||||||
|
self.mx2Cell = af.getCell( 'mx2_x2', CRL.Catalog.State.Views )
|
||||||
|
self.mx3Cell = af.getCell( 'mx3_x2', CRL.Catalog.State.Views )
|
||||||
|
with UpdateSession():
|
||||||
|
self.buildInterface()
|
||||||
|
self.decoder = ColBlock( self, 'decod', 33 )
|
||||||
|
for addr in range(256):
|
||||||
|
bitGroup = ColGroup( SRAM_256x32.BIT_GROUP_FMT.format( addr ))
|
||||||
|
self.rootGroup.group( bitGroup )
|
||||||
|
bitGroup.group( Column( self
|
||||||
|
, self.mx2Cell
|
||||||
|
, 'imux_addr{:04d}'.format( addr )
|
||||||
|
, '_byte{byte}_{bbit}'
|
||||||
|
, 32 ))
|
||||||
|
bitGroup.group( Column( self
|
||||||
|
, self.dffCell
|
||||||
|
, 'bit_addr{:04d}'.format( addr )
|
||||||
|
, '_byte{byte}_{bbit}'
|
||||||
|
, 32 ))
|
||||||
|
bus = Bus( self, 'imux_addr{:04d}_b_q({{}})'.format(addr), 32 )
|
||||||
|
bitGroup.childs[0].setBusNet( 'q', bus )
|
||||||
|
bitGroup.childs[1].setBusNet( 'i', bus )
|
||||||
|
bus = Bus( self, 'bit_addr{:04d}_b_q({{}})'.format(addr), 32 )
|
||||||
|
bitGroup.childs[0].setBusNet( 'i0', bus )
|
||||||
|
bitGroup.childs[1].setBusNet( 'q', bus )
|
||||||
|
bus = Bus( self, 'di({})', 32 )
|
||||||
|
bitGroup.childs[0].setBusNet( 'i1', bus )
|
||||||
|
bitGroup.childs[1].setCmdNet( 'ck', self.getNet( 'clk' ))
|
||||||
|
omuxGroupsCurr = []
|
||||||
|
omuxGroupsNext = []
|
||||||
|
muxDepth = 0
|
||||||
|
for i in range(256//4):
|
||||||
|
childs = []
|
||||||
|
for addr in range(i*4, (i+1)*4):
|
||||||
|
tag = SRAM_256x32.BIT_GROUP_FMT.format( addr )
|
||||||
|
childs.append( self.rootGroup.findChild( tag ))
|
||||||
|
childs[-1].unGroup()
|
||||||
|
omuxGroupsCurr.append( self._doMux4( childs, muxDepth ))
|
||||||
|
while len(omuxGroupsCurr) >= 4:
|
||||||
|
trace( 610, '\tGrouping {} elements.\n'.format( len(omuxGroupsCurr )))
|
||||||
|
muxDepth += 1
|
||||||
|
for i in range(len(omuxGroupsCurr)//4):
|
||||||
|
omuxGroupsNext.append( self._doMux4( omuxGroupsCurr[i*4:(i+1)*4], muxDepth ))
|
||||||
|
omuxGroupsCurr = omuxGroupsNext
|
||||||
|
omuxGroupsNext = []
|
||||||
|
for group in omuxGroupsCurr:
|
||||||
|
self.rootGroup.group( group )
|
||||||
|
inst = self.addInstance( 'inv_x2'
|
||||||
|
, 'nrst_inv'
|
||||||
|
, { 'i' : 'rst'
|
||||||
|
, 'nq' : 'nrst'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.decoder.addInstance( 0, inst )
|
||||||
|
for child in self.rootGroup.childs[0].childs:
|
||||||
|
if child.kind == Column.KIND_COLUMN:
|
||||||
|
if child.insts[0].getMasterCell() != self.mx3Cell:
|
||||||
|
continue
|
||||||
|
rstCol = Column( self
|
||||||
|
, af.getCell( 'a2_x2', CRL.Catalog.State.Views )
|
||||||
|
, 'omux_nrst'
|
||||||
|
, '_byte{byte}_{bbit}'
|
||||||
|
, 32 )
|
||||||
|
busOMux = Bus( self, child.tag+'_b_q({})', 32 )
|
||||||
|
busDato = Bus( self, 'dato({})', 32 )
|
||||||
|
child .setBusNet( 'q' , busOMux )
|
||||||
|
rstCol.setBusNet( 'i0', busOMux )
|
||||||
|
rstCol.setCmdNet( 'i1', self.getNet('nrst') )
|
||||||
|
rstCol.setBusNet( 'q' , busDato )
|
||||||
|
self.rootGroup.group( rstCol )
|
||||||
|
self.buildDecoder()
|
||||||
|
af.saveCell( self.cell, CRL.Catalog.State.Logical )
|
||||||
|
|
||||||
|
def _doMux4 ( self, childs, muxDepth ):
|
||||||
|
"""
|
||||||
|
Build a 4 entry mux. It uses a mux2 / mux3 combination.
|
||||||
|
Returns a newly build group.
|
||||||
|
Entry selection given (cmd0,cmd1) : ::
|
||||||
|
|
||||||
|
00 ==> i0 (mux2.i0)
|
||||||
|
01 ==> i1 (mux2.i1)
|
||||||
|
10 ==> i2 (mux3.i2)
|
||||||
|
11 ==> i3 (mux3.i1)
|
||||||
|
"""
|
||||||
|
tags = []
|
||||||
|
for child in childs:
|
||||||
|
tags.append( child.tag )
|
||||||
|
childIndex = 1 if muxDepth == 0 else 4
|
||||||
|
muxTag = SRAM_256x32._mergeOMuxTags( tags )
|
||||||
|
mux2Tag = SRAM_256x32._mergeOMuxTags( tags[0:2] )
|
||||||
|
mux3Tag = SRAM_256x32._mergeOMuxTags( tags )
|
||||||
|
muxGroup = ColGroup( muxTag+'_g' )
|
||||||
|
trace( 610, ',+', '\tSRAM_256x32._doMux4() {} + {} -> {}\n' \
|
||||||
|
.format( mux2Tag, mux3Tag, muxTag ))
|
||||||
|
mux2Col = Column( self
|
||||||
|
, self.mx2Cell
|
||||||
|
, mux2Tag
|
||||||
|
, '_byte{byte}_{bbit}'
|
||||||
|
, 32 )
|
||||||
|
mux2Col.setCmdNet( 'cmd', self.getNet( 'raddr({})'.format(muxDepth*2) ))
|
||||||
|
mux3Col = Column( self
|
||||||
|
, self.mx3Cell
|
||||||
|
, mux3Tag
|
||||||
|
, '_byte{byte}_{bbit}'
|
||||||
|
, 32 )
|
||||||
|
mux3Col.setCmdNet( 'cmd0', self.getNet( 'raddr({})'.format(muxDepth*2 + 1) ))
|
||||||
|
mux3Col.setCmdNet( 'cmd1', self.getNet( 'raddr({})'.format(muxDepth*2 ) ))
|
||||||
|
muxGroup.group( childs[0] )
|
||||||
|
muxGroup.group( mux2Col )
|
||||||
|
muxGroup.group( childs[1] )
|
||||||
|
muxGroup.group( childs[2] )
|
||||||
|
muxGroup.group( mux3Col )
|
||||||
|
muxGroup.group( childs[3] )
|
||||||
|
bus0 = Bus( self, tags[0][:-2]+'_b_q({})', 32 )
|
||||||
|
bus1 = Bus( self, tags[1][:-2]+'_b_q({})', 32 )
|
||||||
|
bus2 = Bus( self, tags[2][:-2]+'_b_q({})', 32 )
|
||||||
|
bus3 = Bus( self, tags[3][:-2]+'_b_q({})', 32 )
|
||||||
|
busMx2 = Bus( self, mux2Tag+'_b_q({})', 32 )
|
||||||
|
childs[0].childs[ childIndex ].setBusNet( 'q', bus0 )
|
||||||
|
childs[1].childs[ childIndex ].setBusNet( 'q', bus1 )
|
||||||
|
childs[2].childs[ childIndex ].setBusNet( 'q', bus2 )
|
||||||
|
childs[3].childs[ childIndex ].setBusNet( 'q', bus3 )
|
||||||
|
mux2Col.setBusNet( 'i0', bus0 )
|
||||||
|
mux2Col.setBusNet( 'i1', bus1 )
|
||||||
|
mux2Col.setBusNet( 'q' , busMx2 )
|
||||||
|
mux3Col.setBusNet( 'i0', busMx2 )
|
||||||
|
mux3Col.setBusNet( 'i2', bus2 )
|
||||||
|
mux3Col.setBusNet( 'i1', bus3 )
|
||||||
|
childs[1].reverse()
|
||||||
|
childs[3].reverse()
|
||||||
|
trace( 610, '-,' )
|
||||||
|
return muxGroup
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _mergeOMuxTags ( tags ):
|
||||||
|
"""
|
||||||
|
Merge two output mux column tags. We assume that we merge only
|
||||||
|
contiguous tags.
|
||||||
|
|
||||||
|
Example: ::
|
||||||
|
|
||||||
|
'omux_0_to_1' + 'omux_2_to_3' ==> 'omux_0_to_3'
|
||||||
|
"""
|
||||||
|
vectorRe = re.compile( '^omux_(?P<lsb>\d+)_to_(?P<msb>\d+)' )
|
||||||
|
addrs = []
|
||||||
|
for tag in tags:
|
||||||
|
end = -2 if tag.endswith('_g') else 0
|
||||||
|
if tag.startswith('bit'):
|
||||||
|
addrs.append( int( tag[8:end] ))
|
||||||
|
elif tag.startswith('omux'):
|
||||||
|
m = vectorRe.match( tag )
|
||||||
|
addrs += [ int(m.group('lsb')), int(m.group('msb')) ]
|
||||||
|
addrs.sort()
|
||||||
|
omuxTag = 'omux'
|
||||||
|
omuxTag = 'omux_{}_to_{}'.format( addrs[0], addrs[-1] )
|
||||||
|
return omuxTag
|
||||||
|
|
||||||
|
def buildInterface ( self ):
|
||||||
|
""" Build the interface of the SRAM. """
|
||||||
|
self.addExternalNet( 'clk', Net.Direction.DirIn, Net.Type.CLOCK )
|
||||||
|
self.addExternalNet( 'rst', Net.Direction.DirIn )
|
||||||
|
self.addExternalNet( 'ce' , Net.Direction.DirIn )
|
||||||
|
for bit in range(4):
|
||||||
|
self.addExternalNet( 'we({})'.format(bit) , Net.Direction.DirIn )
|
||||||
|
self.addExternalNet( 'oe' , Net.Direction.DirIn )
|
||||||
|
for bit in range(8):
|
||||||
|
self.addExternalNet( 'addr({})'.format(bit) , Net.Direction.DirIn )
|
||||||
|
for bit in range(32):
|
||||||
|
self.addExternalNet( 'di({})'.format(bit) , Net.Direction.DirIn )
|
||||||
|
for bit in range(32):
|
||||||
|
self.addExternalNet( 'dato({})'.format(bit) , Net.Direction.DirOut )
|
||||||
|
self.addExternalNet( 'vdd' , Net.Direction.DirIn, Net.Type.POWER )
|
||||||
|
self.addExternalNet( 'vss' , Net.Direction.DirIn, Net.Type.GROUND )
|
||||||
|
|
||||||
|
def _getDecodNetName ( self, oneHot, addrWidth ):
|
||||||
|
"""
|
||||||
|
Build a net name for a particular oneHot bit in the range covered by addrWidth.
|
||||||
|
The first part is the address lines and the second the value they decod.
|
||||||
|
If the oneHot value exceed 2^addrWidth, we uses the *next* address lines.
|
||||||
|
|
||||||
|
======== =========== ========================
|
||||||
|
oneHot addrWidth net name
|
||||||
|
======== =========== ========================
|
||||||
|
0 4 'decod_3_2_1_0_0000'
|
||||||
|
1 4 'decod_3_2_1_0_0001'
|
||||||
|
2 4 'decod_3_2_1_0_0010'
|
||||||
|
3 4 'decod_3_2_1_0_0011'
|
||||||
|
4 4 'decod_3_2_1_0_0100'
|
||||||
|
15 4 'decod_3_2_1_0_1111'
|
||||||
|
16 4 'decod_7_6_5_4_0000'
|
||||||
|
17 4 'decod_7_6_5_4_0001'
|
||||||
|
======== =========== ========================
|
||||||
|
"""
|
||||||
|
netName = ''
|
||||||
|
indexFirstBit = (oneHot >> addrWidth) * addrWidth
|
||||||
|
for bit in range(indexFirstBit, indexFirstBit + addrWidth):
|
||||||
|
netName = '{}_'.format(str( bit )) + netName
|
||||||
|
divider = 1 << addrWidth
|
||||||
|
netName = '{}{:0{width}b}'.format( netName, oneHot % divider, width=addrWidth )
|
||||||
|
return netName
|
||||||
|
|
||||||
|
def _getDecodInstConf ( self, oneHot, addrWidth ):
|
||||||
|
"""
|
||||||
|
Compute the informations needed to instanciate one cell of one level of
|
||||||
|
the decoder. For the first level of one hot (addrWidth == 2), the inputs
|
||||||
|
are just direct or inverted addresses bits. For the upper level we
|
||||||
|
combine the outputs of the previous one hot level, that is the one with
|
||||||
|
addrWidth/2 to generate the current one.
|
||||||
|
"""
|
||||||
|
instConf = []
|
||||||
|
if addrWidth == 2:
|
||||||
|
indexFirstBit = (oneHot >> addrWidth) * addrWidth
|
||||||
|
valueAddr = oneHot % (1 << addrWidth)
|
||||||
|
trunkName = self._getDecodNetName( oneHot, addrWidth )
|
||||||
|
instConf.append( 'a2_x2' )
|
||||||
|
instConf.append( 'decod_a2_{}'.format( trunkName ))
|
||||||
|
instConf.append( {} )
|
||||||
|
for i in range(2):
|
||||||
|
inv = '' if (valueAddr & (1 << i)) else 'n_'
|
||||||
|
instConf[2][ 'i{}'.format(i) ] = '{}addr({})'.format( inv, indexFirstBit+i )
|
||||||
|
instConf[2][ 'q' ] = 'decod_'+trunkName
|
||||||
|
elif addrWidth == 4 or addrWidth == 8:
|
||||||
|
halfWidth = addrWidth>>1
|
||||||
|
halfMask = 0
|
||||||
|
for i in range(halfWidth):
|
||||||
|
halfMask |= 1 << i
|
||||||
|
indexFirstBit = (oneHot >> addrWidth) * addrWidth
|
||||||
|
valueAddr = oneHot % (1 << addrWidth)
|
||||||
|
trunkName = self._getDecodNetName( oneHot, addrWidth )
|
||||||
|
instConf.append( 'a2_x2' )
|
||||||
|
instConf.append( 'decod_a2_{}'.format( trunkName ))
|
||||||
|
instConf.append( {} )
|
||||||
|
offset = (oneHot >> addrWidth) << (halfWidth+1)
|
||||||
|
oneHot0 = (oneHot & halfMask) + offset
|
||||||
|
instConf[2][ 'i0' ] = 'decod_'+self._getDecodNetName( oneHot0, halfWidth )
|
||||||
|
oneHot1 = ((oneHot >> halfWidth) & halfMask) + (1<<(halfWidth)) + offset
|
||||||
|
instConf[2][ 'i1' ] = 'decod_'+self._getDecodNetName( oneHot1, halfWidth )
|
||||||
|
instConf[2][ 'q' ] = 'decod_'+trunkName
|
||||||
|
trace( 610, '\t{:08b} {:3d}:{} + {:3d}:{} => {:3d}::{:08b}:{}\n' \
|
||||||
|
.format( halfMask
|
||||||
|
, oneHot0, self._getDecodNetName( oneHot0, halfWidth )
|
||||||
|
, oneHot1, self._getDecodNetName( oneHot1, halfWidth )
|
||||||
|
, oneHot , oneHot, trunkName ))
|
||||||
|
return instConf
|
||||||
|
|
||||||
|
def buildDecoder ( self ):
|
||||||
|
trace( 610, ',+', '\tSRAM_256x32.buildDecoder()\n' )
|
||||||
|
for bit in range(8):
|
||||||
|
inst = self.addInstance( 'mx2_x2'
|
||||||
|
, 'raddr_imux_{}'.format(bit)
|
||||||
|
, { 'cmd' : 'ce'
|
||||||
|
, 'i0' : 'raddr({})'.format(bit)
|
||||||
|
, 'i1' : 'addr({})'.format(bit)
|
||||||
|
, 'q' : 'raddr_imux_q({})'.format(bit)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.decoder.addInstance( bit * 4 + 1, inst )
|
||||||
|
inst = self.addInstance( 'sff1_x4'
|
||||||
|
, 'raddr_sff_{}'.format(bit)
|
||||||
|
, { 'i' : 'raddr_imux_q({})'.format(bit)
|
||||||
|
, 'q' : 'raddr({})'.format(bit)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.decoder.addInstance( bit * 4, inst )
|
||||||
|
self.connect( 'raddr_sff_{}'.format(bit), 'ck', 'clk' )
|
||||||
|
for bit in range(8):
|
||||||
|
inst = self.addInstance( 'inv_x1'
|
||||||
|
, 'decod_inv_{}'.format(bit)
|
||||||
|
, { 'i' : 'addr({})'.format(bit)
|
||||||
|
, 'nq' : 'n_addr({})'.format(bit)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.decoder.addInstance( bit*4 + 1, inst )
|
||||||
|
for oneHot in range(16):
|
||||||
|
trace( 610, '\t{}\n'.format( self._getDecodNetName(oneHot,2) ))
|
||||||
|
instDatas = self._getDecodInstConf( oneHot, 2 )
|
||||||
|
inst = self.addInstance( instDatas[0], instDatas[1], instDatas[2] )
|
||||||
|
self.decoder.addInstance( oneHot*2 + 1, inst )
|
||||||
|
for oneHot in range(32):
|
||||||
|
instDatas = self._getDecodInstConf( oneHot, 4 )
|
||||||
|
inst = self.addInstance( instDatas[0], instDatas[1], instDatas[2] )
|
||||||
|
self.decoder.addInstance( oneHot + (oneHot+1)%2, inst )
|
||||||
|
for oneHot in range(256):
|
||||||
|
bitTag = 'bit_addr{:04d}'.format( oneHot )
|
||||||
|
imuxTag = 'imux_addr{:04d}'.format( oneHot )
|
||||||
|
instDatas = self._getDecodInstConf( oneHot, 8 )
|
||||||
|
inst = self.addInstance( instDatas[0], instDatas[1], instDatas[2] )
|
||||||
|
dffCol = self.rootGroup.findChild( bitTag )
|
||||||
|
imuxCol = self.rootGroup.findChild( imuxTag )
|
||||||
|
self.toHeaders.append(( inst, imuxCol.insts[0] ))
|
||||||
|
for we in range(4):
|
||||||
|
cmdNetName = 'decod_addr{:04d}_we({})'.format( oneHot, we )
|
||||||
|
inst = self.addInstance( 'a3_x2'
|
||||||
|
, 'decod_a3_we_{}_{}'.format(we,oneHot)
|
||||||
|
, { 'i0' : instDatas[2]['q']
|
||||||
|
, 'i1' : 'ce'
|
||||||
|
, 'i2' : 'we({})'.format(we)
|
||||||
|
, 'q' : cmdNetName
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.toHeaders.append(( inst, dffCol.insts[0] ))
|
||||||
|
for bit in range(8):
|
||||||
|
self.connect( 'imux_addr{:04d}_byte{byte}_{bbit}'.format( oneHot, byte=we, bbit=bit )
|
||||||
|
, 'cmd'
|
||||||
|
, cmdNetName
|
||||||
|
)
|
||||||
|
trace( 610, '-,' )
|
|
@ -51,6 +51,8 @@ Automatic placement of a Yosys generated SRAM
|
||||||
own structure. 26 signals takes up more than half the horizontal
|
own structure. 26 signals takes up more than half the horizontal
|
||||||
routing capacity of a slice (40), this result in an unroutable
|
routing capacity of a slice (40), this result in an unroutable
|
||||||
design, the bits are kept into one row each.
|
design, the bits are kept into one row each.
|
||||||
|
832 gates is for the TSMC 180nm, for SkyWater 130nm we got
|
||||||
|
976 gates on the third level.
|
||||||
|
|
||||||
Conclusions
|
Conclusions
|
||||||
~~~~~~~~~~~
|
~~~~~~~~~~~
|
||||||
|
|
Loading…
Reference in New Issue