Align power lines on QuadTree leaf area centers (X).
* Bug: In cumulus/plugins.block.block.py, always import Python modules using the exact same path. Otherwise the module may get imported twice and static variables are duplicated, generating a big mess. This was causing problem for the LUT in macro.py, and got SRAMs blocks encapsulated twice. * New: In cumulus/plugins.block.block.Block.addPlaceHolder(), create a "place holder" instance over a given area to prevent the placer from using it. Allow to make space reservation. * New: In cumulus/plugins.block.configuration.BlockConf, copy the toXPitch() and toYSlice() methods from spare in order to share them between modules. Still have to remove some other local copies. * New: In cumulus/plugins.block.spare.QuadTree, keep a list of all the X centers of the partitionned areas. For yse by the power lines. * New: In cumulus/plugins.chip.chip, move doPowerLayout() call from doChipFloorplan() to doConnectCore(), this is to delay the call until *after* the spare QuadTree has been created and we can align the power lines to the centers of the QuadTree. * New: In cumulus/plugins.chip.pads.Corona.doPowerLayout(), if a spare QuadTree has been created, align the power lines on the X center of the leaf areas. This is a cheap way to avoid DRC errors between the power BigVias and the wires from the various clock trees (on METAL5). * New: In cumulus/plugins.block.macro, add an ad-hoc patch for Staf's SRAMs. The blockage areas are slightly too narrow. We enlarge them by one pitch.
This commit is contained in:
parent
dd28bbba7a
commit
95713ac66b
|
@ -24,7 +24,7 @@ from Hurricane import Breakpoint, DbU, Box, Transformation, Point, \
|
|||
Cell, Instance
|
||||
import CRL
|
||||
from CRL import RoutingLayerGauge
|
||||
from helpers import trace, dots
|
||||
from helpers import trace, dots, l, u, n
|
||||
from helpers.io import ErrorMessage, WarningMessage, catch
|
||||
from helpers.overlay import UpdateSession
|
||||
import Etesian
|
||||
|
@ -32,15 +32,15 @@ import Anabatic
|
|||
import Katana
|
||||
import plugins.rsave
|
||||
from plugins import getParameter
|
||||
from alpha.macro.macro import Macro
|
||||
from alpha.block import timing
|
||||
from alpha.block.spares import Spares
|
||||
from alpha.block.clocktree import ClockTree
|
||||
#from alpha.block.hfns1 import BufferTree
|
||||
#from alpha.block.hfns2 import BufferTree
|
||||
#from alpha.block.hfns3 import BufferTree
|
||||
from alpha.block.hfns4 import BufferTree
|
||||
from alpha.block.configuration import IoPin, BlockConf, GaugeConf
|
||||
from plugins.alpha.macro.macro import Macro
|
||||
from plugins.alpha.block import timing
|
||||
from plugins.alpha.block.spares import Spares
|
||||
from plugins.alpha.block.clocktree import ClockTree
|
||||
#from plugins.alpha.block.hfns1 import BufferTree
|
||||
#from plugins.alpha.block.hfns2 import BufferTree
|
||||
#from plugins.alpha.block.hfns3 import BufferTree
|
||||
from plugins.alpha.block.hfns4 import BufferTree
|
||||
from plugins.alpha.block.configuration import IoPin, BlockConf, GaugeConf
|
||||
|
||||
timing.staticInit()
|
||||
|
||||
|
@ -297,17 +297,18 @@ class Block ( object ):
|
|||
Create a Block object. The only parameter ``conf`` must be a BlockConf
|
||||
object which contains the complete block configuration.
|
||||
"""
|
||||
self.flags = 0
|
||||
self.conf = conf
|
||||
self.spares = Spares( self )
|
||||
self.clockTrees = []
|
||||
self.hfnTrees = []
|
||||
self.blockInstances = []
|
||||
self.sides = { IoPin.WEST : Side( self.conf, IoPin.WEST )
|
||||
, IoPin.EAST : Side( self.conf, IoPin.EAST )
|
||||
, IoPin.SOUTH : Side( self.conf, IoPin.SOUTH )
|
||||
, IoPin.NORTH : Side( self.conf, IoPin.NORTH )
|
||||
}
|
||||
self.flags = 0
|
||||
self.conf = conf
|
||||
self.spares = Spares( self )
|
||||
self.clockTrees = []
|
||||
self.hfnTrees = []
|
||||
self.blockInstances = []
|
||||
self.placeHolderCount = 0
|
||||
self.sides = { IoPin.WEST : Side( self.conf, IoPin.WEST )
|
||||
, IoPin.EAST : Side( self.conf, IoPin.EAST )
|
||||
, IoPin.SOUTH : Side( self.conf, IoPin.SOUTH )
|
||||
, IoPin.NORTH : Side( self.conf, IoPin.NORTH )
|
||||
}
|
||||
if not self.conf.cell.getAbutmentBox().isEmpty():
|
||||
isBuilt = True
|
||||
for instance in self.conf.cell.getInstances():
|
||||
|
@ -604,6 +605,7 @@ class Block ( object ):
|
|||
coreTransf.applyOn( macroPosition )
|
||||
xoffset = macroPosition.getX() - pnrAb.getXMin()
|
||||
xpitch = self.conf.vDeepRG.getPitch()
|
||||
print( 'X placement pitch: {}'.format(DbU.getValueString(xpitch)) )
|
||||
print( 'Original X offset: {}'.format(DbU.getValueString(xoffset)) )
|
||||
if xoffset % xpitch:
|
||||
xoffset += xpitch - (xoffset % xpitch)
|
||||
|
@ -627,6 +629,33 @@ class Block ( object ):
|
|||
, Transformation.Orientation.ID )
|
||||
, Instance.PlacementStatus.FIXED )
|
||||
|
||||
def addPlaceHolder ( self, area, inCore=False ):
|
||||
if area.isEmpty():
|
||||
print( ErrorMessage( 1, 'Block.addPlaceHolder(): Request for an empty place holder area.' ))
|
||||
return
|
||||
af = CRL.AllianceFramework.get()
|
||||
phCellName = 'placeholder_{}'.format(self.placeHolderCount)
|
||||
placeHolder = Cell.create( af.getLibrary(0), phCellName )
|
||||
if inCore:
|
||||
self.conf.icore.getTransformation().applyOn( area )
|
||||
pitchedArea = Box( self.conf.toXPitch(area.getXMin(),True)
|
||||
, self.conf.toYSlice(area.getYMin(),True)
|
||||
, self.conf.toXPitch(area.getXMax())
|
||||
, self.conf.toYSlice(area.getYMax()) )
|
||||
placeHolder.setAbutmentBox( Box( u(0.0)
|
||||
, u(0.0)
|
||||
, pitchedArea.getWidth()
|
||||
, pitchedArea.getHeight() ))
|
||||
print( 'pitchedArea={}'.format(pitchedArea) )
|
||||
placeHolder.setTerminalNetlist( True )
|
||||
instance = Instance.create( self.conf.cellPnR, phCellName, placeHolder )
|
||||
instance.setTransformation( Transformation( pitchedArea.getXMin()
|
||||
, pitchedArea.getYMin()
|
||||
, Transformation.Orientation.ID ) )
|
||||
instance.setPlacementStatus( Instance.PlacementStatus.FIXED )
|
||||
self.placeHolderCount += 1
|
||||
return instance
|
||||
|
||||
def route ( self ):
|
||||
routedCell = self.conf.corona if self.conf.isCoreBlock else self.conf.cell
|
||||
self.katana = Katana.KatanaEngine.create( routedCell )
|
||||
|
|
|
@ -1239,3 +1239,27 @@ class BlockConf ( GaugeConf ):
|
|||
self.cell.setName( self.cell.getName()+'_r' )
|
||||
rsave( self.cell, CRL.Catalog.State.Physical|flags )
|
||||
return
|
||||
|
||||
def toXPitch ( self, x, superior=False ):
|
||||
"""
|
||||
Returns the coordinate of the pitch immediately inferior to X.
|
||||
Compute in the "P&R cell" coordinate system which may be core
|
||||
or corona.
|
||||
"""
|
||||
area = self.cellPnR.getAbutmentBox()
|
||||
modulo = (x - area.getXMin()) % self.sliceStep
|
||||
if modulo and superior:
|
||||
x += self.sliceStep
|
||||
return x - modulo
|
||||
|
||||
def toYSlice ( self, y, superior=False ):
|
||||
"""
|
||||
Returns the coordinate of the slice immediately inferior to Y.
|
||||
Compute in the "P&R cell" coordinate system which may be core
|
||||
or corona.
|
||||
"""
|
||||
area = self.cellPnR.getAbutmentBox()
|
||||
modulo = (y - area.getYMin()) % self.sliceHeight
|
||||
if modulo and superior:
|
||||
y += self.sliceHeight
|
||||
return y - modulo
|
||||
|
|
|
@ -308,6 +308,7 @@ class QuadTree ( object ):
|
|||
self.pool = BufferPool( self )
|
||||
self.plugs = []
|
||||
self.rtag = rtag
|
||||
self.rleafX = [ area.getXCenter() ]
|
||||
|
||||
def destroy ( self ):
|
||||
if self.bl: self.bl.destroy()
|
||||
|
@ -481,6 +482,17 @@ class QuadTree ( object ):
|
|||
for leaf in self.leafs: leaf.rselectBuffer( column, row, flags )
|
||||
trace( 540, '-' )
|
||||
|
||||
def _mergeLeafX ( self, leafX ):
|
||||
for x in leafX:
|
||||
if not (x in self.rleafX):
|
||||
self.rleafX.append( x )
|
||||
self.rleafX.sort()
|
||||
|
||||
def _upMergeLeafX ( self ):
|
||||
if self.parent:
|
||||
self.parent._mergeLeafX( self.rleafX )
|
||||
self.parent._upMergeLeafX()
|
||||
|
||||
def partition ( self ):
|
||||
"""
|
||||
Build one level of the quad-tree, if the side of the area is bigger than
|
||||
|
@ -584,6 +596,8 @@ class QuadTree ( object ):
|
|||
for leaf in self.leafs:
|
||||
trace( 540, '\tLeaf rtag:"{}"\n'.format(leaf.rtag) )
|
||||
leaf.rpartition()
|
||||
else:
|
||||
self._upMergeLeafX()
|
||||
trace( 540, '-' )
|
||||
if self.isRoot():
|
||||
self._rsetMaxDepth()
|
||||
|
@ -804,6 +818,11 @@ class Spares ( object ):
|
|||
self.quadTree = None
|
||||
self.strayBuffers = []
|
||||
|
||||
@property
|
||||
def rleafX ( self ):
|
||||
if self.quadTree: return self.quadTree.rleafX
|
||||
return []
|
||||
|
||||
def reset ( self ):
|
||||
self.quadTree.destroy()
|
||||
for buffer in self.strayBuffers:
|
||||
|
@ -908,6 +927,9 @@ class Spares ( object ):
|
|||
with UpdateSession():
|
||||
self.quadTree = QuadTree.create( self )
|
||||
#self._addCapTies()
|
||||
print( "X Centers of the QuadTree leaf" )
|
||||
for x in self.quadTree.rleafX:
|
||||
print( '| {}'.format(DbU.getValueString(x) ))
|
||||
trace( 540, '-' )
|
||||
|
||||
def rshowPoolUse ( self ):
|
||||
|
|
|
@ -82,7 +82,7 @@ class Chip ( Block ):
|
|||
self.conf.chip.setAbutmentBox( self.conf.chipAb )
|
||||
trace( 550, '\tSet chip ab:{}\n'.format(self.conf.chip.getAbutmentBox()) )
|
||||
trace( 550, '\tUsing core ab:{}\n'.format(self.conf.core.getAbutmentBox()) )
|
||||
self.padsCorona = plugins.alpha.chip.pads.Corona( self.conf )
|
||||
self.padsCorona = plugins.alpha.chip.pads.Corona( self )
|
||||
self.conf.validated = self.padsCorona.validate()
|
||||
if not self.conf.validated:
|
||||
return False
|
||||
|
@ -112,10 +112,11 @@ class Chip ( Block ):
|
|||
y = y - (y % self.conf.sliceHeight)
|
||||
self.conf.icore.setTransformation ( Transformation(x,y,Transformation.Orientation.ID) )
|
||||
self.conf.icore.setPlacementStatus( Instance.PlacementStatus.FIXED )
|
||||
self.padsCorona.doPowerLayout()
|
||||
self.conf.refresh()
|
||||
|
||||
def doConnectCore ( self ):
|
||||
if self.padsCorona:
|
||||
self.padsCorona.doPowerLayout()
|
||||
if self.conf.routingGauge.hasPowerSupply():
|
||||
power = plugins.alpha.chip.powerplane.Builder( self.conf )
|
||||
power.connectPower()
|
||||
|
|
|
@ -780,7 +780,7 @@ class CoreWire ( object ):
|
|||
|
||||
class Corona ( object ):
|
||||
|
||||
def __init__ ( self, conf ):
|
||||
def __init__ ( self, chip ):
|
||||
|
||||
def _cmpPad ( pad1, pad2):
|
||||
width1 = pad1.getAbutmentBox().getWidth()
|
||||
|
@ -797,7 +797,7 @@ class Corona ( object ):
|
|||
duplicateds.append( [ position, padInstance ] )
|
||||
return duplicateds
|
||||
|
||||
self.conf = conf
|
||||
self.chip = chip
|
||||
self.conf.validated = False
|
||||
self.northPads = _dupPads( self.conf.chipConf.northPads )
|
||||
self.southPads = _dupPads( self.conf.chipConf.southPads )
|
||||
|
@ -838,6 +838,9 @@ class Corona ( object ):
|
|||
if self.conf.cfg.chip.minPadSpacing is None:
|
||||
self.conf.cfg.chip.minPadSpacing = 0
|
||||
|
||||
@property
|
||||
def conf ( self ): return self.chip.conf
|
||||
|
||||
@property
|
||||
def supplyRailWidth ( self ): return self.conf.cfg.chip.supplyRailWidth
|
||||
|
||||
|
@ -1212,9 +1215,6 @@ class Corona ( object ):
|
|||
with UpdateSession():
|
||||
capViaWidth = self.conf.vDeepRG.getPitch()*3
|
||||
coreAb = self.conf.coreAb
|
||||
stripesNb = int( (coreAb.getWidth() - 8*capViaWidth + self.supplyRailWidth) \
|
||||
/ self.supplyRailPitch - 1 )
|
||||
offset = (coreAb.getWidth() - self.supplyRailPitch*(stripesNb-1)) / 2
|
||||
powerNet = None
|
||||
groundNet = None
|
||||
chipPowerNet = None
|
||||
|
@ -1251,31 +1251,59 @@ class Corona ( object ):
|
|||
else:
|
||||
raise ErrorMessage( 1, 'pads.Corona.doPowerLayout(): No ground net found in "{}"' \
|
||||
.format(corona.getName()) )
|
||||
|
||||
icore = self.conf.icore
|
||||
xcore = icore.getTransformation().getTx()
|
||||
trace( 550, '\ticoreAb={}\n'.format(icore.getAbutmentBox()) )
|
||||
print( 'capViaWidth={}'.format(DbU.getValueString(capViaWidth)))
|
||||
stripeSpecs = []
|
||||
for i in range(stripesNb+4):
|
||||
stripesNb = int( (coreAb.getWidth() - 8*capViaWidth + self.supplyRailWidth) \
|
||||
/ self.supplyRailPitch - 1 )
|
||||
offset = (coreAb.getWidth() - self.supplyRailPitch*(stripesNb-1)) / 2
|
||||
stripeSpecs.append( [ xcore + capViaWidth/2 , capViaWidth ] )
|
||||
stripeSpecs.append( [ xcore + 2*capViaWidth + capViaWidth/2 , capViaWidth ] )
|
||||
if self.chip.spares:
|
||||
rleafX = self.chip.spares.rleafX
|
||||
spacing = (rleafX[1] - rleafX[0]) / 2
|
||||
stepOffset = 0
|
||||
step = 1
|
||||
trace( 550, '\trleafX\n' )
|
||||
for i in range(len(rleafX)):
|
||||
trace( 550, '\t| rleafX[{}] = {}\n'.format(i,DbU.getValueString(rleafX[i])))
|
||||
if spacing < self.supplyRailPitch:
|
||||
stepOffset = 1
|
||||
step = 2
|
||||
spacing = (rleafX[2] - rleafX[0]) / 2
|
||||
if step == 1:
|
||||
stripeSpecs.append( [ rleafX[0] - spacing, self.supplyRailWidth ] )
|
||||
trace( 550, '\tstripe[N/A] @{}\n'.format(DbU.getValueString(stripeSpecs[-1][0])))
|
||||
else:
|
||||
stripeSpecs.append( [ rleafX[0], self.supplyRailWidth ] )
|
||||
trace( 550, '\tstripe[N/A] @{}\n'.format(DbU.getValueString(stripeSpecs[-1][0])))
|
||||
for i in range( stepOffset, len(rleafX)-stepOffset, step ):
|
||||
if step == 1:
|
||||
stripeSpecs.append( [ rleafX[i], self.supplyRailWidth ] )
|
||||
trace( 550, '\tstripe[{}] @{}\n'.format(i,DbU.getValueString(stripeSpecs[-1][0])))
|
||||
stripeSpecs.append( [ rleafX[i] + spacing, self.supplyRailWidth ] )
|
||||
trace( 550, '\tstripe[{}] @{}\n'.format(i,DbU.getValueString(stripeSpecs[-1][0])))
|
||||
else:
|
||||
for i in range(stripesNb):
|
||||
stripeSpecs.append( [ xcore + offset + i*self.supplyRailPitch
|
||||
, self.supplyRailWidth
|
||||
] )
|
||||
stripeSpecs.append( [ xcore + coreAb.getWidth() - 2*capViaWidth + capViaWidth/2 , capViaWidth ] )
|
||||
stripeSpecs.append( [ xcore + coreAb.getWidth() - capViaWidth/2 , capViaWidth ] )
|
||||
|
||||
trace( 550, '\ticoreAb={}\n'.format(icore.getAbutmentBox()) )
|
||||
trace( 550, '\tcapViaWidth={}\n'.format(DbU.getValueString(capViaWidth)))
|
||||
for i in range(len(stripeSpecs)):
|
||||
if i % 2:
|
||||
coronaNet = groundNet
|
||||
chipNet = chipGroundNet
|
||||
else:
|
||||
coronaNet = powerNet
|
||||
chipNet = chipPowerNet
|
||||
if i < 2:
|
||||
axis = xcore + 2*i*capViaWidth + capViaWidth/2
|
||||
width = capViaWidth
|
||||
elif i >= stripesNb+2:
|
||||
axis = xcore + coreAb.getWidth() - 2*(i-stripesNb-1)*capViaWidth + capViaWidth/2
|
||||
width = capViaWidth
|
||||
coronaNet = powerNet
|
||||
else:
|
||||
axis = xcore + offset + (i-2)*self.supplyRailPitch
|
||||
width = self.supplyRailWidth
|
||||
stripeSpecs.append( [ chipNet, coronaNet, axis, width ] )
|
||||
for chipNet, coronaNet, axis, width in stripeSpecs:
|
||||
self._supplyToPad( chipNet, coronaNet, axis, width, North )
|
||||
self._supplyToPad( chipNet, coronaNet, axis, width, South )
|
||||
chipNet = chipGroundNet
|
||||
coronaNet = groundNet
|
||||
trace( 550, '\tstripe[{}] @{}\n'.format(i,DbU.getValueString(stripeSpecs[i][0])))
|
||||
self._supplyToPad( chipNet, coronaNet, stripeSpecs[i][0], stripeSpecs[i][1], North )
|
||||
self._supplyToPad( chipNet, coronaNet, stripeSpecs[i][0], stripeSpecs[i][1], South )
|
||||
#for istripe in range(stripesNb):
|
||||
# trace( 550, '\tistripe={}\n'.format(istripe) )
|
||||
# axis = xcore + offset + istripe*self.supplyRailPitch
|
||||
|
|
|
@ -448,7 +448,7 @@ class Builder ( object ):
|
|||
if layer.isSymbolic():
|
||||
layer = layer.getBasicLayer()
|
||||
query.setBasicLayer( layer )
|
||||
trace( 550, ',+', 'query.doQuery() {}'.format(layer) )
|
||||
trace( 550, ',+', '\tquery.doQuery() {}\n'.format(layer) )
|
||||
query.doQuery()
|
||||
trace( 550, '-' )
|
||||
self.activePlane = None
|
||||
|
|
|
@ -22,7 +22,7 @@ from Hurricane import Breakpoint, DbU, Box, Transformation, Point, \
|
|||
Box, Path, Layer, Occurrence, Net, \
|
||||
NetExternalComponents, RoutingPad, Pad, \
|
||||
Horizontal, Vertical, Contact, Pin, Plug, \
|
||||
Cell, Instance
|
||||
Cell, Instance, Rectilinear
|
||||
import CRL
|
||||
from CRL import RoutingLayerGauge
|
||||
from helpers import trace, dots
|
||||
|
@ -40,10 +40,12 @@ class Macro ( object ):
|
|||
processeds macros so they are modified only once.
|
||||
"""
|
||||
|
||||
trace( 550, '\tStatic init of Macro\n' )
|
||||
LUT = {}
|
||||
|
||||
@staticmethod
|
||||
def lookup ( macroCell ):
|
||||
trace( 550, '\tMacro.lookup() on {}\n'.format(macroCell) )
|
||||
if Macro.LUT.has_key(macroCell): return Macro.LUT[ macroCell ]
|
||||
return None
|
||||
|
||||
|
@ -68,6 +70,7 @@ class Macro ( object ):
|
|||
|
||||
macro = Macro.lookup( macroCell )
|
||||
if macro is not None:
|
||||
trace( 550, '\tReusing macro wrapper {}\n'.format(macroCell) )
|
||||
return macro
|
||||
return Macro( macroCell, gaugeName, hMargin, vMargin )
|
||||
|
||||
|
@ -112,12 +115,53 @@ class Macro ( object ):
|
|||
that are half free and half occluded by the block itself may
|
||||
cause (stupid) deadlock to appear.
|
||||
"""
|
||||
trace( 550, '\tMacro.__init__() {}\n'.format(macroCell) )
|
||||
self.cell = macroCell
|
||||
Macro.LUT[ self.cell ] = self
|
||||
|
||||
af = CRL.AllianceFramework.get()
|
||||
ab = self.cell.getAbutmentBox()
|
||||
self.rg = af.getRoutingGauge( gaugeName )
|
||||
self.rg = af.getRoutingGauge( gaugeName )
|
||||
gaugeMetal2 = self.rg.getLayerGauge( 1 )
|
||||
gaugeMetal3 = self.rg.getLayerGauge( 2 )
|
||||
gaugeMetal4 = self.rg.getLayerGauge( 3 )
|
||||
blockageMetal2 = gaugeMetal2.getBlockageLayer()
|
||||
blockageMetal3 = gaugeMetal3.getBlockageLayer()
|
||||
blockageMetal4 = gaugeMetal4.getBlockageLayer()
|
||||
minSpacingMetal2 = gaugeMetal2.getLayer().getMinimalSpacing()
|
||||
minSpacingMetal3 = gaugeMetal3.getLayer().getMinimalSpacing()
|
||||
minSpacingMetal4 = gaugeMetal4.getLayer().getMinimalSpacing()
|
||||
if self.cell.getName() == 'SPBlock_512W64B8W':
|
||||
print( ' o Ad-hoc blockage patch for "{}".'.format(self.cell.getName()) )
|
||||
for net in self.cell.getNets():
|
||||
for component in net.getComponents():
|
||||
if isinstance(component,Rectilinear) and component.getLayer() == blockageMetal2:
|
||||
bb = component.getBoundingBox()
|
||||
bb.inflate( minSpacingMetal2/2 )
|
||||
Horizontal.create( component.getNet()
|
||||
, blockageMetal2
|
||||
, bb.getYCenter()
|
||||
, bb.getHeight()
|
||||
, bb.getXMin()
|
||||
, bb.getXMax() )
|
||||
elif isinstance(component,Rectilinear) and component.getLayer() == blockageMetal3:
|
||||
bb = component.getBoundingBox()
|
||||
bb.inflate( 2*minSpacingMetal3, minSpacingMetal3/2 )
|
||||
Vertical.create( component.getNet()
|
||||
, blockageMetal3
|
||||
, bb.getXCenter()
|
||||
, bb.getWidth()
|
||||
, bb.getYMin()
|
||||
, bb.getYMax() )
|
||||
elif isinstance(component,Rectilinear) and component.getLayer() == blockageMetal4:
|
||||
bb = component.getBoundingBox()
|
||||
bb.inflate( minSpacingMetal4/2 )
|
||||
Horizontal.create( component.getNet()
|
||||
, blockageMetal4
|
||||
, bb.getYCenter()
|
||||
, bb.getHeight()
|
||||
, bb.getXMin()
|
||||
, bb.getXMax() )
|
||||
self.innerAb = ab
|
||||
sliceHeight = af.getCellGauge( gaugeName ).getSliceHeight()
|
||||
westPins = []
|
||||
|
|
Loading…
Reference in New Issue