Support for gf180mcu native I/O pads.

* Bug: In CRL/technos.node180.gf180mcu_c4m.iolib.py, remove the VDD and
    VSS ring terminals in the pad as only *some* of the have it.
    Assume that it is a bug from GF. The power rail will still be ok
    as it connect by abutment (with the filler & other I/O pads).
* New: In cumulus.plugins.block.configuration.py, added support for
    iterable I/O pad specifications in ioPads argument.
* New: In cumulus.plugins.core2chip.core2chip.py, add support for
    any number of control signals on I/O pads. Not fully implemented
    yet, as we only allow to hard-wire them either to one or zero.
      Raise an error if _connect() fails to find a master net, so
    we don't fail strangely later...
This commit is contained in:
Jean-Paul Chaput 2023-09-25 23:50:20 +02:00
parent 4420da664e
commit 9274c21c14
9 changed files with 334 additions and 31 deletions

View File

@ -181,7 +181,7 @@ def _routing ():
cfg.katana.globalRipupLimit = 5
cfg.katana.globalRipupLimit = [1, None]
cfg.katana.longGlobalRipupLimit = 5
cfg.chip.padCoreSide = 'South'
cfg.chip.padCoreSide = 'North'
# Plugins setup
cfg.clockTree.minimumSide = u(5.04) * 6
cfg.clockTree.buffer = 'gf180mcu_fd_sc_mcu9t5v0__clkbuf_2'

View File

@ -99,6 +99,10 @@ def _routing():
)
af.addCellGauge(cg)
af.setCellGauge('StdCell3V3Lib')
lg5 = af.getRoutingGauge('StdCell3V3Lib').getLayerGauge( 5 )
lg5.setType( CRL.RoutingLayerGauge.PowerSupply )
env = af.getEnvironment()
env.setRegister( '.*sff.*' )
# Place & Route setup
with CfgCache(priority=Cfg.Parameter.Priority.ConfigurationFile) as cfg:
@ -162,17 +166,8 @@ def _routing():
cfg.katana.globalRipupLimit = 5
cfg.katana.globalRipupLimit = [1, None]
cfg.katana.longGlobalRipupLimit = 5
cfg.chip.padCoreSide = 'South'
# Plugins setup
with CfgCache(priority=Cfg.Parameter.Priority.ConfigurationFile) as cfg:
cfg.viewer.minimumSize = 500
cfg.viewer.pixelThreshold = 10
cfg.chip.block.rails.count = 5
cfg.chip.block.rails.hWidth = u(2.68)
cfg.chip.block.rails.vWidth = u(2.68)
cfg.chip.block.rails.hSpacing = u(0.7)
cfg.chip.block.rails.vSpacing = u(0.7)
cfg.clockTree.minimumSide = l(600)
cfg.clockTree.buffer = 'buf_x1'
cfg.clockTree.placerEngine = 'Etesian'

View File

@ -27,8 +27,9 @@ def _routing ():
cfg.chip.block.rails.vWidth = u(30.0)
cfg.chip.block.rails.hSpacing = u( 6.0)
cfg.chip.block.rails.vSpacing = u( 6.0)
cfg.chip.padCorner = 'gf180mcu_fd_io__cor_5lm'
cfg.chip.padSpacers = 'gf180mcu_fd_io__fill10_5lm,gf180mcu_fd_io__fill5_5lm,gf180mcu_fd_io__fill1_5lm'
#cfg.chip.padCorner = 'gf180mcu_fd_io__cor'
#cfg.chip.padSpacers = 'gf180mcu_fd_io__fill10,gf180mcu_fd_io__fill5,gf180mcu_fd_io__fill1'
cfg.chip.padCoreSide = 'North'
af = AllianceFramework.get()
cg = CellGauge.create( 'LEF.GF_IO_Site'
, 'Metal2' # pin layer name.
@ -57,7 +58,6 @@ def _loadIoLib ( pdkDir ):
print( ' o Setup GF180MCU I/O library in {}.'.format( ioLib.getName() ))
io.vprint( 1, ' o Setup GF180MCU I/O library in {}.'.format( ioLib.getName() ))
cellsDir = pdkDir / 'libraries' / 'gf180mcu_fd_io' / 'latest' / 'cells'
print( cellsDir )
for lefFile in cellsDir.glob( '*/*_5lm.lef' ):
print( lefFile )
gdsFile = lefFile.with_suffix( '.gds' )
@ -65,6 +65,12 @@ def _loadIoLib ( pdkDir ):
Gds.setTopCellName( gdsFile.stem[:-4] )
Gds.load( ioLib, gdsFile.as_posix(), Gds.Layer_0_IsBoundary|Gds.NoBlockages )
LefImport.load( lefFile.as_posix() )
# Demote the VDD/VSS nets until we understand how that works.
for cell in ioLib.getCells():
for net in cell.getNets():
if net.getName() in ('VDD', 'VSS'):
net.setExternal( False )
net.setGlobal( False )
af.wrapLibrary( ioLib, 1 )

View File

@ -65,6 +65,7 @@
${CMAKE_CURRENT_SOURCE_DIR}/plugins/core2chip/niolib.py
${CMAKE_CURRENT_SOURCE_DIR}/plugins/core2chip/libresocio.py
${CMAKE_CURRENT_SOURCE_DIR}/plugins/core2chip/sky130.py
${CMAKE_CURRENT_SOURCE_DIR}/plugins/core2chip/gf180mcu.py
)
set ( pyPluginChip ${CMAKE_CURRENT_SOURCE_DIR}/plugins/chip/__init__.py
${CMAKE_CURRENT_SOURCE_DIR}/plugins/chip/constants.py

View File

@ -284,7 +284,6 @@ def setupGf180mcu_c4m ( checkToolkit=None
cfg.misc.verboseLevel2 = True
cfg.etesian.graphics = 3
cfg.etesian.spaceMargin = 0.10
cfg.anabatic.topRoutingLayer = 'metal6'
cfg.katana.eventsLimit = 4000000
af = CRL.AllianceFramework.get()
lg5 = af.getRoutingGauge('StdCell3V3Lib').getLayerGauge( 5 )

View File

@ -15,6 +15,7 @@
import sys
import re
import os.path
import collections
from operator import itemgetter
from ... import Cfg
from ...Hurricane import DataBase, Breakpoint, DbU, Box, Transformation, \
@ -1449,7 +1450,22 @@ class BlockConf ( GaugeConf ):
for ioPinSpec in self.ioPinsArg:
self.ioPins.append( IoPin( *ioPinSpec ) )
for line in range(len(self.ioPadsArg)):
self.chipConf.addIoPad( self.ioPadsArg[line], line )
bits = []
if not isinstance(self.ioPadsArg[line][-1],str) \
and isinstance(self.ioPadsArg[line][-1],collections.Iterable):
bits = self.ioPadsArg[line][-1]
elif isinstance(self.ioPadsArg[line][-1],int):
bits = range( self.ioPadsArg[line][-1] )
if bits != []:
for bit in bits:
spec = [ self.ioPadsArg[line][0]
, self.ioPadsArg[line][1]
]
for i in range( 2, len(self.ioPadsArg[line])-1 ):
spec.append( self.ioPadsArg[line][i].format( bit ))
self.chipConf.addIoPad( spec, line )
else:
self.chipConf.addIoPad( self.ioPadsArg[line], line )
trace( 550, ',-' )
@property

View File

@ -693,11 +693,11 @@ class Corona ( object ):
if plug.getMasterNet().isGlobal():
net = self.conf.cell.getNet( plug.getMasterNet().getName() )
if not net:
raise ErrorMessage( 1, 'Corona._padAnalysis(): Ring net "%s" is not connected and there is no global net (in pad \"%s").' \
% plug.getMasterNet().getName(), padCell.getName() )
raise ErrorMessage( 1, 'Corona._padAnalysis(): Ring net "{}" is not connected and there is no global net (in pad "{}").' \
.format( plug.getMasterNet().getName(), padCell.getName() ))
else:
raise ErrorMessage( 1, 'Corona._padAnalysis(): Ring net "%s" is neither connected nor global (in pad \"%s").' \
% plug.getMasterNet().getName(), padCell.getName() )
raise ErrorMessage( 1, 'Corona._padAnalysis(): Ring net "{}" is neither connected nor global (in pad "{}").' \
.format( plug.getMasterNet().getName(), padCell.getName() ))
if net:
self.padRails.append( ( net
, component.getLayer()

View File

@ -375,6 +375,7 @@ class IoPad ( object ):
or self.nets[0].chipExtNetName.startswith('io_in') \
or self.nets[0].chipExtNetName.startswith('io_out')
if hasEnable:
trace( 550, '\tself.nets = {}\n'.format( self.nets ))
if len(self.nets) < 2:
enableNet = self.coreToChip.newEnableForNet( self.nets[0] )
self.nets.append( self.coreToChip.getIoNet( enableNet ) )
@ -387,6 +388,7 @@ class IoPad ( object ):
connexions.append( ( self.nets[0].chipIntNet , padInfo.inputNet ) )
connexions.append( ( self.coreToChip.newDummyNet(), padInfo.outputNet ) )
if hasEnable:
trace( 550, '\tenable Pad={} <-> {}\n'.format( padInfo.enableNet, self.nets[1].chipIntNet ))
connexions.append( ( self.nets[1].chipIntNet, padInfo.enableNet ) )
elif (self.direction == IoPad.TRI_OUT) and (len(self.nets) < 2):
self.nets[0].setFlags( IoNet.DoExtNet )
@ -415,6 +417,11 @@ class IoPad ( object ):
connexions.append( ( self.nets[0].chipIntNet, padInfo.inputNet ) )
connexions.append( ( self.nets[1].chipIntNet, padInfo.outputNet ) )
connexions.append( ( self.nets[2].chipIntNet, padInfo.enableNet ) )
for controlInfo in padInfo.controlNets:
controlNet = self.coreToChip.newControlForPad( self.ioPadConf, controlInfo )
self.nets.append( self.coreToChip.getIoNet( controlNet ) )
self.nets[-1].buildNets()
connexions.append( ( self.nets[-1].chipIntNet, controlInfo.name ) )
if not self.coreToChip.useHarness():
self.pads.append( Instance.create( self.coreToChip.chip
, self.padInstanceName
@ -448,13 +455,21 @@ class CoreToChip ( object ):
to the core actually bearing information.
"""
class IoControlInfo ( object ):
def __init__ ( self, name, defaultState ):
self.name = name
self.defaultState = defaultState
pass
class IoPadInfo ( object ):
def __init__ ( self, flags, padName, padNet, coreNets ):
self.flags = flags
self.name = padName
self.padNet = padNet
self.coreNets = coreNets
def __init__ ( self, flags, padName, padNet, coreNets, controlNets=[] ):
self.flags = flags
self.name = padName
self.padNet = padNet
self.coreNets = coreNets
self.controlNets = [ CoreToChip.IoControlInfo( net[0], net[1] ) for net in controlNets ]
return
@property
@ -508,6 +523,10 @@ class CoreToChip ( object ):
if not masterNetO: masterNet = instance.getMasterCell().getNet( chipNet.getName() )
elif isinstance(masterNetO,Net): masterNet = masterNetO
else: masterNet = instance.getMasterCell().getNet( masterNetO )
if not masterNet:
raise ErrorMessage( 1, [ 'CoreToChip._connect(): No net "{}" in cell "{}".' \
.format( masterNetO, instance.getMasterCell().getName() )
] )
instance.getPlug( masterNet ).setNet( chipNet )
return
@ -571,6 +590,22 @@ class CoreToChip ( object ):
self.dummyNetCount += 1
return dummy
def newControlNet ( self, controlName, constantType ):
"""
Create a new control signal, in *core* cell, to control the associated I/O pad.
The control signal is tied to a constant value, either zero or one.
:param controlName: The name of the control net *in the core cell*.
:param constantType: Whether the control signal is set to zero or one.
"""
instance = self.conf.constantsConf.createInstance( self.core, constantType )
control = Net.create( self.core, controlName )
control.setExternal ( True )
control.setDirection( Net.Direction.OUT )
getPlugByName( instance, self.conf.constantsConf.output(constantType) ).setNet( control )
self.conf.addClonedCell( self.conf.core )
return control
def newEnableForNet ( self, ioNet ):
"""
Create a new enable signal, in *core* cell, to control the associated I/O pad.
@ -582,13 +617,16 @@ class CoreToChip ( object ):
else:
raise ErrorMessage( 2, 'CoreToChip.newEnableForNet(): Net "{}" is neither IN nor OUT.' \
.format(ioNet.coreNet.getName()) )
instance = self.conf.constantsConf.createInstance( self.core, constantType )
enable = Net.create( self.core, ioNet.enableNetName )
enable.setExternal ( True )
enable.setDirection( Net.Direction.OUT )
getPlugByName( instance, self.conf.constantsConf.output(constantType) ).setNet( enable )
self.conf.addClonedCell( self.conf.core )
return enable
return self.newControlNet( ioNet.enableNetName, constantType )
def newControlForPad ( self, ioPadInfo, ioControlInfo ):
"""
Create a new control signal, in *core* cell, to control the associated I/O pad.
This is to be used for all I/O pads controls nets, save the "enable" signal.
"""
constantType = ConstantsConf.ONE if ioControlInfo.defaultState else ConstantsConf.ZERO
controlNetName = '{}_{}'.format( ioPadInfo.instanceName, ioControlInfo.name )
return self.newControlNet( controlNetName, constantType )
def getIoNet ( self, coreNet ):
"""

View File

@ -0,0 +1,248 @@
# -*- coding: utf-8 -*-
#
# This file is part of the Coriolis Software.
# Copyright (c) Sorbonne Université 2020-2023, 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/core2chip/libresocio.py" |
# +-----------------------------------------------------------------+
"""
Core2Chip configuration for the Global Foudries 180nm I/O pad library (GF180MCU).
"""
import sys
import re
from ...Hurricane import DbU, DataBase, UpdateSession, Breakpoint, \
Transformation , Instance , Net
from ...CRL import Catalog, AllianceFramework
from ...helpers import trace
from ...helpers.io import ErrorMessage, WarningMessage
from ...helpers.overlay import CfgCache
from .core2chip import CoreToChip as BaseCoreToChip, IoNet, IoPad
class CoreToChip ( BaseCoreToChip ):
"""
Provide pad-specific part for GF180MCU I/O pads (works in real mode).
"""
rePadType = re.compile(r'(?P<type>.+)_(?P<index>[\d]+)$')
def __init__ ( self, core ):
with CfgCache() as cfg:
cfg.chip.useAbstractPads = False
self.ioPadNames = { 'in' :'gf180mcu_fd_io__in_s'
, 'bidir' :'gf180mcu_fd_io__bi_t'
, 'analog' :'gf180mcu_fd_io__asig_5p0'
, 'vdd' :'gf180mcu_fd_io__dvdd'
, 'vss' :'gf180mcu_fd_io__dvss'
, 'corner' :'gf180mcu_fd_io__cor'
, 'spacer1' :'gf180mcu_fd_io__fill1'
, 'spacer5' :'gf180mcu_fd_io__fill5'
, 'spacer10' :'gf180mcu_fd_io__fill10'
}
BaseCoreToChip.__init__ ( self, core )
self.ringNetNames = { 'DVDD' : None
, 'DVSS' : None
#, 'VDD' : None
#, 'VSS' : None
}
self.ioPadInfos = [ BaseCoreToChip.IoPadInfo( IoPad.IN
, self.ioPadNames['in']
, 'PAD', ['Y'], [ ( 'PU' , False )
, ( 'PD' , False )
] )
, BaseCoreToChip.IoPadInfo( IoPad.BIDIR
, self.ioPadNames['bidir']
, 'PAD', ['A', 'Y', 'OE'], [ ( 'SL' , True )
, ( 'CS' , True )
, ( 'PU' , False )
, ( 'PD' , False )
, ( 'PDRV0', False )
, ( 'PDRV1', False )
, ( 'IE' , True )
] )
, BaseCoreToChip.IoPadInfo( IoPad.ANALOG
, self.ioPadNames['analog']
, 'ASIG5V', ['asig5v'] )
, BaseCoreToChip.IoPadInfo( IoPad.CORNER
, self.ioPadNames['corner']
, None, [] )
, BaseCoreToChip.IoPadInfo( IoPad.FILLER
, self.ioPadNames['spacer1']
, None, [] )
, BaseCoreToChip.IoPadInfo( IoPad.FILLER
, self.ioPadNames['spacer5']
, None, [] )
, BaseCoreToChip.IoPadInfo( IoPad.FILLER
, self.ioPadNames['spacer10']
, None, [] )
]
self.cornerCount = 0
self.spacerCount = 0
self.padSpacers = []
self._getPadLib()
return
def _getPadLib ( self ):
"""
Check that the I/O pad library is present and pre-load the spacer cells.
"""
def _cmpPad ( pad ):
"""Used to sort I/O pads by decreasing width."""
return pad.getAbutmentBox().getWidth()
self.padLib = AllianceFramework.get().getLibrary( "iolib" )
if not self.padLib:
message = [ 'CoreToChip.libresocio._getPadLib(): Unable to find Alliance "iolib" library' ]
raise ErrorMessage( 1, message )
for ioPadInfo in self.ioPadInfos:
if ioPadInfo.flags & IoPad.FILLER:
spacerCell = self.padLib.getCell( ioPadInfo.name )
if spacerCell: self.padSpacers.append( spacerCell )
else:
raise ErrorMessage( 1, 'CoreToChip.gf180mcu._getPadLib(): Missing spacer cell "{}"'.format(spacerName) )
self.padSpacers = sorted( self.padSpacers, key=_cmpPad, reverse=True )
def getNetType ( self, netName ):
if netName.lower().startswith('vss') or netName.lower().startswith('dvss'): return Net.Type.GROUND
if netName.lower().startswith('vdd') or netName.lower().startswith('dvdd'): return Net.Type.POWER
return Net.Type.LOGICAL
def isGlobal ( self, netName ):
if netName in self.ringNetNames: return True
return False
def getCell ( self, masterCellName ):
#cell = self.padLib.getCell( masterCellName )
cell = AllianceFramework.get().getCell( masterCellName, Catalog.State.Views )
if not cell:
raise ErrorMessage( 1, 'libresocio.getCell(): I/O pad library "%s" does not contain cell named "%s"' \
% (self.padLib.getName(),masterCellName) )
return cell
def _buildAllGroundPads ( self, ioPadConf ):
coreNet = self.core .getNet( ioPadConf.coreSupplyNetName )
coronaNet = self.corona.getNet( ioPadConf.coreSupplyNetName )
chipNet = self.chip .getNet( ioPadConf.coreSupplyNetName )
padNet = self.chip .getNet( ioPadConf.padSupplyNetName )
if not coronaNet:
coronaNet = Net.create( self.corona, ioPadConf.coreSupplyNetName )
coronaNet.setExternal( True )
coronaNet.setGlobal ( True )
coronaNet.setType ( Net.Type.GROUND )
self.icore.getPlug( coreNet ).setNet( coronaNet )
if not chipNet:
chipNet = Net.create( self.chip, ioPadConf.coreSupplyNetName )
chipNet.setExternal( True )
chipNet.setType ( Net.Type.GROUND )
if not padNet:
padNet = Net.create( self.chip, ioPadConf.padSupplyNetName )
padNet.setExternal( True )
padNet.setType ( Net.Type.GROUND )
coronaPlug = self.icorona.getPlug( coronaNet )
if not coronaPlug.getNet():
coronaPlug.setNet( chipNet )
self.ringNetNames['DVSS' ] = chipNet
#self.ringNetNames['VSS' ] = padNet
ioPadConf.pads.append( Instance.create( self.chip
, 'p_iovss_{}'.format(ioPadConf.index)
, self.getCell(self.ioPadNames['vss']) ) )
#self._connect( ioPadConf.pads[0], chipNet, 'VSS' )
self._connect( ioPadConf.pads[0], padNet , 'DVSS' )
self.groundPadCount += 1
self.chipPads += ioPadConf.pads
def _buildAllPowerPads ( self, ioPadConf ):
trace( 550, ',+', '\tgf180mcu.CoreToChip()\n' )
trace( 550, '\tcoreSupplyNetName="{}"\n'.format( ioPadConf.coreSupplyNetName ))
trace( 550, '\tpadSupplyNetName ="{}"\n'.format( ioPadConf.padSupplyNetName ))
coreNet = self.core .getNet( ioPadConf.coreSupplyNetName )
coronaNet = self.corona.getNet( ioPadConf.coreSupplyNetName )
chipNet = self.chip .getNet( ioPadConf.coreSupplyNetName )
padNet = self.chip .getNet( ioPadConf.padSupplyNetName )
if not coronaNet:
coronaNet = Net.create( self.corona, ioPadConf.coreSupplyNetName )
coronaNet.setExternal( True )
coronaNet.setGlobal ( True )
coronaNet.setType ( Net.Type.POWER )
self.icore.getPlug( coreNet ).setNet( coronaNet )
if not chipNet:
chipNet = Net.create( self.chip, ioPadConf.coreSupplyNetName )
chipNet.setExternal( True )
chipNet.setType ( Net.Type.POWER )
self.icorona.getPlug( coronaNet ).setNet( chipNet )
trace( 550, '\tchipNet ="{}"\n'.format( chipNet ))
if not padNet:
padNet = Net.create( self.chip, ioPadConf.padSupplyNetName )
padNet.setExternal( True )
padNet.setType ( Net.Type.POWER )
self.ringNetNames['DVDD'] = chipNet
#self.ringNetNames['VDD'] = padNet
trace( 550, '\tpadNet ="{}"\n'.format( padNet ))
ioPadConf.pads.append( Instance.create( self.chip
, 'p_iovdd_{}'.format(ioPadConf.index)
, self.getCell(self.ioPadNames['vdd']) ) )
#self._connect( ioPadConf.pads[0], chipNet, 'VDD' )
self._connect( ioPadConf.pads[0], padNet , 'DVDD' )
self.powerPadCount += 1
self.chipPads += ioPadConf.pads
trace( 550, '-,' )
def _buildClockPads ( self, ioPadConf ):
"""For "GF180MCU" there is no specialized clock I/O pad. So do nothing."""
pass
def _connectClocks ( self ):
"""For "GF180MCU" there is no pad internal clock ring. So do nothing."""
pass
def hasCornerCell ( self ):
"""Overload of CoreToChip, YES we have dedicated corner cells."""
return True
def hasFillerCells ( self ):
"""Overload of CoreToChip, YES we have dedicated filler cells."""
return True
def getCornerCell ( self ):
"""Return the model of corner cell."""
return self.getCell( self.ioPadNames['corner'] )
def createSpacer ( self, gapWidth ):
"""Return a new instance of spacer cell."""
spacerCell = None
for candidate in self.padSpacers:
if gapWidth >= candidate.getAbutmentBox().getWidth():
spacerCell = candidate
break
if not spacerCell:
return None
spacer = Instance.create( self.chip
, 'pad_spacer_{}'.format( self.spacerCount )
, spacerCell )
self.spacerCount += 1
#self._connect( spacer, self.ringNetNames['vddring'], 'vddring' )
self._connect( spacer, self.ringNetNames['DVDD'], 'DVDD' )
#self._connect( spacer, self.ringNetNames['gndring'], 'gndring' )
self._connect( spacer, self.ringNetNames['DVSS'], 'DVSS' )
return spacer
def createCorner ( self, instanceName=None ):
"""Return a new instance of corner cell."""
if instanceName is None:
instanceName = 'pad_corner_{}'.format( self.cornerCount )
corner = Instance.create( self.chip, instanceName, self.getCornerCell() )
self.cornerCount += 1
self._connect( corner, self.ringNetNames['DVDD'], 'DVDD' )
#self._connect( corner, self.ringNetNames['vddcore'], 'vddcore' )
self._connect( corner, self.ringNetNames['DVSS'], 'DVSS' )
#self._connect( corner, self.ringNetNames['gndcore'], 'gndcore' )
return corner