1576 lines
68 KiB
Python
1576 lines
68 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
import copy
|
|
import datetime
|
|
from Hurricane import DataBase
|
|
from Hurricane import UpdateSession
|
|
from Hurricane import DbU
|
|
from Hurricane import Box
|
|
from Hurricane import Net
|
|
from Hurricane import Horizontal
|
|
from Hurricane import Vertical
|
|
from Hurricane import Contact
|
|
from Hurricane import Pad
|
|
from Hurricane import NetExternalComponents
|
|
from CRL import AllianceFramework
|
|
import Constant
|
|
import helpers
|
|
from helpers.io import ErrorMessage as Error
|
|
from helpers import trace
|
|
from Analog import Device
|
|
import oroshi
|
|
import oroshi.dtr
|
|
|
|
#helpers.setTraceLevel( 100 )
|
|
|
|
def traceMT ( mt ):
|
|
trace( 100, '+', '\tMeta-Transistor Topological Datas\n' )
|
|
trace( 100, '\t +-------------------------------------------------+\n' )
|
|
trace( 100, '\t | Gate name | %20s |\n' % mt['gate'] )
|
|
trace( 100, '\t +------------+------------------------------------+\n' )
|
|
trace( 100, '\t | NF | %10d |\n' % mt['NF'] )
|
|
trace( 100, '\t | MIN | %10r |\n' % mt['MIN'] )
|
|
trace( 100, '\t +------------+-----------------------+------------+\n' )
|
|
trace( 100, '\t | Style | NSint | %10d |\n' % mt['style.NSint'] )
|
|
trace( 100, '\t | | DSint | %10d |\n' % mt['style.NDint'] )
|
|
trace( 100, '\t | | NSend | %10d |\n' % mt['style.NSend'] )
|
|
trace( 100, '\t | | DSend | %10d |\n' % mt['style.NDend'] )
|
|
trace( 100, '\t +------------+---------+-------------+------------+\n' )
|
|
trace( 100, '\t | Stress | BSIM4 | SAeff | %10.2g |\n' % mt['stress.SAeff_Bsim4' ] )
|
|
trace( 100, '\t | | | SBeff | %10.2g |\n' % mt['stress.SBeff_Bsim4' ] )
|
|
trace( 100, '\t | | | SAinv | %10.2g |\n' % mt['stress.SAinv_Bsim4' ] )
|
|
trace( 100, '\t | | | SBinv | %10.2g |\n' % mt['stress.SBinv_Bsim4' ] )
|
|
trace( 100, '\t | | | SBinv | %10.2g |\n' % mt['stress.SBinv_Bsim4' ] )
|
|
trace( 100, '\t | | | alpha | %10.2g |\n' % mt['stress.alpha' ] )
|
|
trace( 100, '\t | | | alphaInv | %10.2g |\n' % mt['stress.alphaInv' ] )
|
|
trace( 100, '\t | | | LODeffect | %10.2g |\n' % mt['stress.LODeffect' ] )
|
|
trace( 100, '\t | +---------+-------------+------------+\n' )
|
|
trace( 100, '\t | | Crolles | SAeff | %10.2g |\n' % mt['stress.SAeff_Crolles'] )
|
|
trace( 100, '\t | | | SBeff | %10.2g |\n' % mt['stress.SBeff_Crolles'] )
|
|
trace( 100, '\t | | | SAinv | %10.2g |\n' % mt['stress.SAinv_Crolles'] )
|
|
trace( 100, '\t | | | SBinv | %10.2g |\n' % mt['stress.SBinv_Crolles'] )
|
|
trace( 100, '\t | | | po2actEff | %10.2g |\n' % mt['stress.po2actEff_Crolles'] )
|
|
trace( 100, '\t +------------+---------+-------------+------------+\n' )
|
|
trace( 100, '\t | Parasitics | ASeff %10.2g m2 |\n' % mt['parasitics.ASeff'] )
|
|
trace( 100, '\t | | PSeff %10.2g m |\n' % mt['parasitics.PSeff'] )
|
|
trace( 100, '\t | | ADeff %10.2g m2 |\n' % mt['parasitics.ADeff'] )
|
|
trace( 100, '\t | | PDeff %10.2g m |\n' % mt['parasitics.PDeff'] )
|
|
trace( 100, '\t +------------+------------------------------------+\n' )
|
|
trace( 100, '-' )
|
|
return
|
|
|
|
|
|
class Wiring ( object ):
|
|
|
|
def __init__ ( self, device, wiring ):
|
|
chain = wiring.split('.')
|
|
net = chain[0]
|
|
side = chain[1]
|
|
if net == 'B' and device.isBulkConnected(): net = 'S'
|
|
|
|
self.net = device.getNet( net )
|
|
self.topTrack = -1
|
|
self.botTrack = -1
|
|
self.wTrack = 1
|
|
if len(chain) >= 3: self.wTrack = int(chain[2])
|
|
length = len(side)
|
|
for i in range( 0, length//2, 2 ):
|
|
track = -2
|
|
if side[i+1] != 'X': track = int( side[i+1] )
|
|
|
|
if side[i] == 't': self.topTrack = track
|
|
if side[i] == 'b': self.botTrack = track
|
|
if side[i] == 'z': pass
|
|
return
|
|
|
|
def isDrain ( self ): return self.net.getName()[0] == 'D'
|
|
def isSource ( self ): return self.net.getName()[0] == 'S'
|
|
def isGate ( self ): return self.net.getName()[0] == 'G'
|
|
def isBulk ( self ): return self.net.getName()[0] == 'B'
|
|
|
|
def isTop ( self ):
|
|
if self.topTrack != -1: return Stack.NorthBulk
|
|
return None
|
|
|
|
def isBot ( self ):
|
|
if self.botTrack != -1: return Stack.SouthBulk
|
|
return None
|
|
|
|
def getTop ( self ): return self.topTrack
|
|
def getBot ( self ): return self.botTrack
|
|
def setTop ( self, track ): self.topTrack = track
|
|
def setBot ( self, track ): self.botTrack = track
|
|
def getWTrack ( self ): return self.wTrack
|
|
|
|
def __str__ ( self ):
|
|
s = self.net.getName()
|
|
if not self.isTop() and not self.isBot(): s += 'z'
|
|
else:
|
|
if self.isBot():
|
|
s += '.b%d' % self.botTrack
|
|
s += '.%d' % self.wTrack
|
|
if self.isTop():
|
|
s += '.t%d' % self.topTrack
|
|
s += '.%d' % self.wTrack
|
|
return s
|
|
|
|
|
|
class Bulk ( object ):
|
|
|
|
def __init__ ( self, stack, axis, usource, utarget, flags ):
|
|
self.stack = stack
|
|
self.axis = axis
|
|
self.usource = usource
|
|
self.utarget = utarget
|
|
self.flags = flags
|
|
self.ucontacts = []
|
|
self.ucontactsBulk = []
|
|
return
|
|
|
|
|
|
def addContact ( self, upos, enabled=True ):
|
|
#if self.ucontacts:
|
|
# trace( 100, '\taddContact [ ' )
|
|
# for pos, en in self.ucontacts: trace( 100, "%s+%s " % (DbU.getValueString(pos),en) )
|
|
# trace( 100, '] inserting: %s+%s\n' % (DbU.getValueString(upos),enabled) )
|
|
|
|
if len(self.ucontacts) == 0 or self.ucontacts[-1][0] < upos:
|
|
self.ucontacts.append( (upos,enabled) )
|
|
else:
|
|
for i in range(len(self.ucontacts)):
|
|
if self.ucontacts[i][0] > upos:
|
|
self.ucontacts.insert( i, (upos,enabled) )
|
|
return
|
|
if self.ucontacts[i][0] == upos:
|
|
return
|
|
self.ucontacts.append( (upos,enabled) )
|
|
return
|
|
|
|
|
|
def _computeContacts ( self, ucontacts, pitch ):
|
|
#trace( 100, ',+', '\tBulk._computeContacts() @%s\n' % DbU.getValueString(self.axis) )
|
|
#trace( 100, '\thorPitch: %s\n' % DbU.getValueString(self.stack.horPitch) )
|
|
#trace( 100, '\tbulk pitch: %s\n' % DbU.getValueString(pitch) )
|
|
#trace( 100, '\tusource: %s\n' % DbU.getValueString(self.usource) )
|
|
#trace( 100, '\tutarget: %s\n' % DbU.getValueString(self.utarget) )
|
|
|
|
icontact = 0
|
|
ucontact = self.usource
|
|
while ucontact <= self.utarget:
|
|
#trace( 100, '\tucontact: %s\n' % DbU.getValueString(ucontact) )
|
|
|
|
if len(ucontacts) == 0:
|
|
ucontacts.insert( 0, (ucontact,True) )
|
|
else:
|
|
if icontact == 0 and ucontact < ucontacts[0][0]:
|
|
if ucontacts[0][0] - ucontact >= pitch:
|
|
ucontacts.insert( 0, (ucontact,True) )
|
|
else:
|
|
while icontact+1 < len(ucontacts) and ucontacts[icontact+1][0] < ucontact:
|
|
if icontact+1 == len(ucontacts): break
|
|
icontact += 1
|
|
|
|
if ucontact - ucontacts[icontact][0] >= pitch:
|
|
if icontact + 1 >= len(ucontacts) \
|
|
or ucontacts[icontact+1][0] - ucontact >= pitch:
|
|
ucontacts.insert( icontact+1, (ucontact,True) )
|
|
icontact += 1
|
|
else:
|
|
ucontact = ucontacts[icontact][0]
|
|
|
|
ucontact += pitch
|
|
|
|
trace( 100, '-' )
|
|
return
|
|
|
|
|
|
def computeContacts ( self ):
|
|
#self.ucontactsBulk = copy.deepcopy( self.ucontacts )
|
|
|
|
pitch = self.stack.minWidth_metal1 + self.stack.minSpacing_metal1
|
|
pitch = max( pitch, self.stack.minWidth_cut0 + self.stack.minEnclosure_metal1_cut0*2 + self.stack.minSpacing_metal1 )
|
|
|
|
if self.stack.isVH:
|
|
self._computeContacts( self.ucontactsBulk, pitch )
|
|
|
|
pitch = max( pitch, self.stack.minWidth_cut1 + self.stack.minEnclosure_metal1_cut1*2 + self.stack.minSpacing_metal1 )
|
|
pitch = max( pitch, self.stack.minWidth_metal2 + self.stack.minSpacing_metal2 )
|
|
pitch = max( pitch, self.stack.minWidth_cut1 + self.stack.minEnclosure_metal2_cut1*2 + self.stack.minSpacing_metal2 )
|
|
pitch = max( pitch, self.stack.minWidth_cut2 + self.stack.minEnclosure_metal2_cut2*2 + self.stack.minSpacing_metal2 )
|
|
if self.stack.isVH:
|
|
pitch = max( pitch, self.stack.minWidth_metal3 + self.stack.minSpacing_metal3 )
|
|
pitch = max( pitch, self.stack.minWidth_cut2 + self.stack.minEnclosure_metal3_cut2*2 + self.stack.minSpacing_metal3 )
|
|
|
|
#trace( 100, '\tOriginal ucontacts[]\n' )
|
|
#for ucontact, enabled in self.ucontacts:
|
|
# trace( 100, '\tucontact: %s enabled:%d\n' % (DbU.getValueString(ucontact),enabled) )
|
|
#trace( 100, '\n' )
|
|
|
|
self._computeContacts( self.ucontacts, pitch )
|
|
if not self.stack.isVH:
|
|
self.ucontactsBulk = self.ucontacts
|
|
return
|
|
|
|
|
|
def doLayout ( self ):
|
|
active = oroshi.rules.getRealLayer( 'active' )
|
|
metal1 = oroshi.rules.getRealLayer( 'metal1' )
|
|
metal2 = oroshi.rules.getRealLayer( 'metal2' )
|
|
metal3 = oroshi.rules.getRealLayer( 'metal3' )
|
|
cut0 = oroshi.rules.getRealLayer( 'cut0' )
|
|
cut1 = oroshi.rules.getRealLayer( 'cut1' )
|
|
cut2 = oroshi.rules.getRealLayer( 'cut2' )
|
|
bulkNet = self.stack.bulkNet
|
|
|
|
self.computeContacts()
|
|
|
|
#trace( 100, '\tBulk.doLayout() @%s\n' % DbU.getValueString(self.axis) )
|
|
|
|
if self.flags & (Stack.NorthBulk | Stack.SouthBulk):
|
|
xsource = self.usource
|
|
xtarget = self.utarget
|
|
if not self.stack.hasWestBulk(): xsource = self.ucontacts[ 0][0]
|
|
if not self.stack.hasEastBulk(): xtarget = self.ucontacts[-1][0]
|
|
|
|
width = self.stack.minWidth_metal1
|
|
width = max( width, self.stack.minEnclosure_metal1_cut0*2 + self.stack.minWidth_cut0 )
|
|
Horizontal.create( bulkNet
|
|
, metal1
|
|
, self.axis
|
|
, width
|
|
, xsource-self.stack.wire1Width//2
|
|
, xtarget+self.stack.wire1Width//2 )
|
|
width = self.stack.minWidth_cut0 + 2* self.stack.minEnclosure_active_cut0
|
|
Horizontal.create( bulkNet
|
|
, active
|
|
, self.axis
|
|
, width
|
|
, self.usource-width//2
|
|
, self.utarget+width//2 )
|
|
width += 2* self.stack.minEnclosure_bImplant_active
|
|
Horizontal.create( bulkNet
|
|
, self.stack.bImplantLayer
|
|
, self.axis
|
|
, width
|
|
, self.usource-width//2
|
|
, self.utarget+width//2 )
|
|
|
|
for xcontact, enabled in self.ucontactsBulk:
|
|
if enabled:
|
|
Contact.create( bulkNet
|
|
, cut0
|
|
, xcontact
|
|
, self.axis
|
|
, self.stack.minWidth_cut0
|
|
, self.stack.minWidth_cut0 )
|
|
|
|
cutBb = Box( xcontact, self.axis, xcontact, self.axis )
|
|
|
|
#metal1EnclBb = Box( cutBb ).inflate( self.stack.minEnclosure_metal1_cut0 + self.stack.minWidth_cut0/2 )
|
|
#Pad.create( bulkNet, metal1, metal1EnclBb )
|
|
|
|
for xcontact, enabled in self.ucontacts:
|
|
trace( 100, '\tucontact: %s enabled:%d\n' % (DbU.getValueString(xcontact),enabled) )
|
|
|
|
if enabled:
|
|
Contact.create( bulkNet
|
|
, cut1
|
|
, xcontact
|
|
, self.axis
|
|
, self.stack.minWidth_cut1
|
|
, self.stack.minWidth_cut1 )
|
|
if self.stack.isVH:
|
|
Contact.create( bulkNet
|
|
, cut2
|
|
, xcontact
|
|
, self.axis
|
|
, self.stack.minWidth_cut2
|
|
, self.stack.minWidth_cut2 )
|
|
|
|
cutBb = Box( xcontact, self.axis, xcontact, self.axis )
|
|
|
|
metal1EnclBb = Box( cutBb ).inflate( self.stack.minEnclosure_metal1_cut1 + self.stack.minWidth_cut1//2 )
|
|
Pad.create( bulkNet, metal1, metal1EnclBb )
|
|
|
|
metal2EnclBb = Box( cutBb ).inflate( self.stack.minEnclosure_metal2_cut1 + self.stack.minWidth_cut1//2 )
|
|
if self.stack.isVH:
|
|
metal2EnclBb.merge( Box( cutBb ).inflate( self.stack.minEnclosure_metal2_cut2 + self.stack.minWidth_cut2//2 ) )
|
|
|
|
Pad.create( bulkNet, metal2, metal2EnclBb )
|
|
|
|
if self.stack.isVH:
|
|
metal3EnclBb = Box( cutBb ).inflate( self.stack.minEnclosure_metal3_cut2 + self.stack.minWidth_cut2//2 )
|
|
Pad.create( bulkNet, metal3, metal3EnclBb )
|
|
|
|
if self.flags & (Stack.EastBulk | Stack.WestBulk):
|
|
metal1Width = self.stack.minWidth_metal1
|
|
metal1Width = max( metal1Width, self.stack.minWidth_cut0 + 2*self.stack.minEnclosure_metal1_cut0 )
|
|
metal1Width = max( metal1Width, self.stack.minWidth_cut1 + 2*self.stack.minEnclosure_metal1_cut1 )
|
|
|
|
Vertical.create( bulkNet
|
|
, metal1
|
|
, self.axis
|
|
, metal1Width
|
|
, self.usource-self.stack.wire1Width//2
|
|
, self.utarget+self.stack.wire1Width//2 )
|
|
width = self.stack.minWidth_cut0 + 2*self.stack.minEnclosure_active_cut0
|
|
Vertical.create( bulkNet
|
|
, active
|
|
, self.axis
|
|
, width
|
|
, self.usource-width//2
|
|
, self.utarget+width//2 )
|
|
width += 2*self.stack.minEnclosure_bImplant_active
|
|
Vertical.create( bulkNet
|
|
, self.stack.bImplantLayer
|
|
, self.axis
|
|
, width
|
|
, self.usource-width//2
|
|
, self.utarget+width//2 )
|
|
|
|
for icontact in range(len(self.ucontacts)):
|
|
if (icontact == 0 and self.stack.hasSouthBulk()) \
|
|
or (icontact+1 == len(self.ucontacts) and self.stack.hasNorthBulk()): continue
|
|
Contact.create( bulkNet
|
|
, cut0
|
|
, self.axis
|
|
, self.ucontacts[icontact][0]
|
|
, self.stack.minWidth_cut0
|
|
, self.stack.minWidth_cut0 )
|
|
return
|
|
|
|
|
|
## Draw a Stack of Transistors.
|
|
#
|
|
# A Stack of Transistors is a set of transistor put into a regular
|
|
# band and connected through their sources/drains. All share the
|
|
# exact same W & L. The way they are connecteds defines what functionnality
|
|
# the Stack implement.
|
|
#
|
|
# The abutment box of the stack is adjusted so that both height and width
|
|
# are even multiples of the track pitches, so the device can be easily
|
|
# placed and handled by the mixed router. The extra space needed for
|
|
# padding is added around the active area. Due to the presence of tracks
|
|
# at the top and bottom of the stack, the active area will be horizontally
|
|
# centered but \b not vertically.
|
|
#
|
|
# The drawing of the stack is controlled through a set of variables
|
|
# (attributes) that allows to create it regardless of the technology.
|
|
# The technology is taken into account in the way those variables are
|
|
# computed and, obviously, their values. The following schematics details
|
|
# the main stack drawing variables along with their computations.
|
|
#
|
|
# \section secStackLayout Stack Layout
|
|
#
|
|
# \subsection secGatePitch Gate pitch
|
|
#
|
|
# - \c self.gatePitch : the pitch of transistors gates, inside the stack.
|
|
# It also applies to dummy transistors.
|
|
#
|
|
# \image html gate-pitch-1.png "Gate Pitch"
|
|
# \image latex gate-pitch-1.pdf "Gate Pitch" width=.9\linewidth
|
|
#
|
|
#
|
|
# \subsection secActiveSideWidth Active Side Width
|
|
#
|
|
# - \c self.activeSideWidth : the distance between the axis of the last
|
|
# transistor gate (on the left or right) and the edge of the active
|
|
# area (\e not the diffusion area).
|
|
#
|
|
# \image html active-side-width-1.png "Active Side Width"
|
|
# \image latex active-side-width-1.pdf "Active Side Width" width=.9\linewidth
|
|
#
|
|
#
|
|
# \subsection secHTrackDistance H-Track Distance
|
|
#
|
|
# - \c self.hTrackDistance : the minimal distance between either the top or
|
|
# bottom edge of the active area and the \e axis of the first track.
|
|
#
|
|
# \image html htrack-distance-1.png "H-Track distance"
|
|
# \image latex htrack-distance-1.pdf "H-Track distance" width=.9\linewidth
|
|
#
|
|
#
|
|
# \subsection secOverallVariables BoundingBox & Overall Variables
|
|
#
|
|
# - \c self.xpitches : the number of vertical track pitches needed to fully
|
|
# enclose the active area.
|
|
# - \c self.ypitches : the number of horizontal track pitches needed to fully
|
|
# enclose the active area.
|
|
# - \c self.activeOffsetX & \c self.activeOffsetY : the offsets of the active area
|
|
# from the bottom left corner of the abutment box.
|
|
# - \c self.diffusionWidth & \c self.diffusionHeight are the minimun dimensions
|
|
# required to fit the active area.
|
|
# - \c self.topTracksNb() : the number of tracks at the top of the stack.
|
|
# - \c self.botTracksNb() : the number of tracks at the bottom of the stack.
|
|
#
|
|
# \image html stack-layout-3.png "General Stack Layout"
|
|
# \image latex stack-layout-3.pdf "General Stack Layout" width=.9\linewidth
|
|
#
|
|
#
|
|
# \section secWiringSpecs Wiring Specifications
|
|
#
|
|
# Stack routing is done through vertical \c metal1 wires coming from the
|
|
# gates and diffusions areas and \c metal2 horizontal wires that can be
|
|
# either above or below the active area. \c metal2 wires (or track) goes
|
|
# through the whole stack and are assigned to one net only. A net will
|
|
# have at least one track above or below and may have both.
|
|
#
|
|
# The connections to the diffusions areas and gates of the various
|
|
# fingers are specified through a list. The stack is made of a regular
|
|
# alternation of diffusions and gates. The list tells, for each one
|
|
# starting from the left, to which net and track they are connected.
|
|
# For a stack of \f$NFs\f$ transistor fingers, the must wiring specification
|
|
# must contains \f$ 3 + (NFs-1) \times 2\f$ elements. The list is given
|
|
# through one \e string with each elements separated by one or more
|
|
# whitespace. The syntax for \e one element is detailed \ref secAtomicWiring.
|
|
#
|
|
# <b>Track numbering scheme</b>
|
|
#
|
|
# Tracks above (top) the active area and below (bottom) each have their
|
|
# own numbering. In both case, the count start \e from the active area.
|
|
# This, the top tracks will be numbered by increasing Y and the bottom
|
|
# tracks by \e decreasing Y.
|
|
#
|
|
# <b>Track/Net assignement</b>
|
|
#
|
|
# The track/net assignement is deduced from the atomic wiring specifications.
|
|
# It also allows to compute the total number of tracks needed above and
|
|
# below the active area.
|
|
#
|
|
# \image html wiring-spec-2.png "Wiring Specification"
|
|
# \image latex wiring-spec-2.pdf "Wiring Specification" width=.9\linewidth
|
|
#
|
|
# \subsection secAtomicWiring Atomic Wiring Specification
|
|
#
|
|
# An atomic wiring specification has the same syntax for either diffusions
|
|
# or gates. It \e must not comprise any whitespaces. it is made of the
|
|
# following parts:
|
|
# - The net name to connect to.
|
|
# - Whether the track is above the active area (\c "t") or below (\c "b").
|
|
# The special case (\c "z") means that this element must be left
|
|
# unconnected (is such case possible?).
|
|
# - The number of the track.
|
|
#
|
|
# \image html wiring-spec-1.png "Atomic Wiring Specification"
|
|
# \image latex wiring-spec-1.pdf "Atomic Wiring Specification" width=.4\linewidth
|
|
#
|
|
#
|
|
# \section secStackImplDetails Stack Implementation Details
|
|
#
|
|
# The \c __setattr__() and \c __getattr__ functions have been redefined
|
|
# so that the technological values (rules) can be accessed has normal
|
|
# attributes of the Stack class, in addition to the regular ones.
|
|
|
|
|
|
class Stack ( object ):
|
|
|
|
NorthBulk = 0x0004
|
|
SouthBulk = 0x0008
|
|
EastBulk = 0x0010
|
|
WestBulk = 0x0020
|
|
RingBulk = NorthBulk|SouthBulk|EastBulk|WestBulk
|
|
SourceIsolated = 0x0040
|
|
SourceShared = 0x0080
|
|
SourceMerged = 0x0100
|
|
DrainIsolated = 0x0200
|
|
DrainShared = 0x0400
|
|
DrainMerged = 0x0800
|
|
|
|
rules = oroshi.getRules()
|
|
|
|
|
|
def __setattr__ ( self, attribute, value ):
|
|
if hasattr(Stack.rules,attribute):
|
|
print( '[ERROR] Stack.{} attribute is read-only (ignored).'.format(attribute) )
|
|
return
|
|
self.__dict__[attribute] = value
|
|
return
|
|
|
|
|
|
def __getattr__ ( self, attribute ):
|
|
if attribute.find('_') != -1:
|
|
if attribute in [ 'minEnclosure_nImplant_active'
|
|
, 'minEnclosure_pImplant_active'
|
|
]:
|
|
raise Error( 3, 'Stack.__getattr__(): Do not access directly rule "%s".' % attribute )
|
|
|
|
if self.isNmos():
|
|
if attribute == 'minEnclosure_tImplant_active': return getattr(Stack.rules,'minEnclosure_nImplant_active')
|
|
if attribute == 'minEnclosure_bImplant_active': return getattr(Stack.rules,'minEnclosure_pImplant_active')
|
|
else:
|
|
if attribute == 'minEnclosure_tImplant_active': return getattr(Stack.rules,'minEnclosure_pImplant_active')
|
|
if attribute == 'minEnclosure_bImplant_active': return getattr(Stack.rules,'minEnclosure_nImplant_active')
|
|
|
|
return getattr(Stack.rules,attribute)
|
|
|
|
if attribute in self.__dict__: return self.__dict__[attribute]
|
|
return None
|
|
|
|
|
|
@staticmethod
|
|
def _addToTracks ( tracks, trackNb, net ):
|
|
if trackNb < len(tracks):
|
|
if tracks[trackNb] and tracks[trackNb] != net:
|
|
raise Error( 3, 'Stack._addToTracks(): Track %d is already assigned to net "%s" (requested by net "%s")'
|
|
% ( trackNb, tracks[trackNb].getName(), net.getName() ) )
|
|
tracks[ trackNb ] = net
|
|
else:
|
|
for i in range( len(tracks), trackNb ): tracks.append( None )
|
|
tracks.append( net )
|
|
return
|
|
|
|
@staticmethod
|
|
def _addToWTracks ( tracks, trackNb, net ):
|
|
if trackNb < len(tracks):
|
|
if tracks[trackNb] and tracks[trackNb] != net:
|
|
raise Error( 3, 'Stack._addToTracks(): Track %d is already assigned to net "%s" (requested by net "%s")'
|
|
% ( trackNb, tracks[trackNb].getName(), net.getName() ) )
|
|
tracks[ trackNb ] = net
|
|
else:
|
|
for i in range( len(tracks), trackNb ): tracks.append( 0 )
|
|
tracks.append( net )
|
|
return
|
|
|
|
|
|
@staticmethod
|
|
def toGeomod ( geoFlags, nf ):
|
|
geomod = 0
|
|
|
|
# Check for geomod 9 & 10 first, because they require the additionnal
|
|
# condition that "nf" must be even. They lead to simpler equations in
|
|
# The BSIM4 model.
|
|
#if nf%2 == 0:
|
|
|
|
if geoFlags == (Stack.SourceIsolated | Stack.DrainIsolated): geomod = 0
|
|
elif geoFlags == (Stack.SourceIsolated | Stack.DrainShared ): geomod = 1
|
|
elif geoFlags == Stack.SourceIsolated: geomod = 1
|
|
elif geoFlags == (Stack.SourceShared | Stack.DrainIsolated): geomod = 2
|
|
elif geoFlags == Stack.DrainIsolated : geomod = 2
|
|
elif geoFlags == (Stack.SourceShared | Stack.DrainShared ): geomod = 3
|
|
elif geoFlags == Stack.SourceShared: geomod = 3
|
|
elif geoFlags == Stack.DrainShared : geomod = 3
|
|
elif geoFlags == 0: geomod = 3
|
|
elif geoFlags == (Stack.SourceIsolated | Stack.DrainMerged ): geomod = 4
|
|
elif geoFlags == (Stack.SourceShared | Stack.DrainMerged ): geomod = 5
|
|
elif geoFlags == Stack.DrainMerged : geomod = 5
|
|
elif geoFlags == (Stack.SourceMerged | Stack.DrainIsolated): geomod = 6
|
|
elif geoFlags == (Stack.SourceMerged | Stack.DrainShared ): geomod = 7
|
|
elif geoFlags == Stack.SourceMerged: geomod = 7
|
|
elif geoFlags == (Stack.SourceMerged | Stack.DrainMerged ): geomod = 8
|
|
# Modes 9 & 10 are not clear to me, but we shouldn't need it ever.
|
|
|
|
if geomod in [0, 4, 6, 8] and nf%2 == 0:
|
|
print( '[WARNING] Stack.toGeomod(): In geomod {}, NF must be odd ({})'.format(geomod,nf))
|
|
|
|
return geomod
|
|
|
|
|
|
## <b>[API]</b> Constructor
|
|
#
|
|
# param rules The physical rule set.
|
|
# \param device The Hurricane AMS device into which the layout will be drawn.
|
|
# \param NERC Number of contact rows in external (first & last) diffusion connectors.
|
|
# \param NIRC Number of contact rows in middle diffusion connectors.
|
|
# param w The \b width of every transistor of the stack (aka \e fingers).
|
|
# param L The \b length of every transistor.
|
|
# param NFs The total number of fingers (dummies includeds).
|
|
# param NDs The number of dummies to put on each side of the stack.
|
|
|
|
def __init__ ( self, device, NERC, NIRC ):
|
|
self.dimensioned = False
|
|
self.device = device
|
|
self.w = oroshi.adjustOnGrid(device.getW() // device.getM())
|
|
self.L = oroshi.adjustOnGrid(device.getL())
|
|
self.NDs = device.getExternalDummy() # Number of Dummies at each end of the stack.
|
|
self.NFs = device.getM() * self.metaTnb() + self.NDs*2 # Total number of Fingers (including dummies).
|
|
self.NERC = NERC
|
|
self.NIRC = NIRC
|
|
self.wirings = []
|
|
self.topTracks = []
|
|
self.botTracks = []
|
|
self.flags = 0
|
|
self.bulks = [ None, None, None, None ] # 0:North, 1:South, 2:East, 3:West.
|
|
self.metaTransistors = {}
|
|
|
|
self.topWTracks = [] # widths top tracks
|
|
self.botWTracks = [] # widths bot tracks
|
|
|
|
if device.isBulkConnected(): self.bulkNet = device.getNet( 'S' )
|
|
else: self.bulkNet = device.getNet( 'B' )
|
|
|
|
bulkType = self.device.getBulkType()
|
|
if bulkType & 0x0001: self.flags |= Stack.NorthBulk
|
|
if bulkType & 0x0002: self.flags |= Stack.SouthBulk
|
|
if bulkType & 0x0004: self.flags |= Stack.EastBulk
|
|
if bulkType & 0x0008: self.flags |= Stack.WestBulk
|
|
|
|
if self.isNmos():
|
|
self.tImplantLayer = oroshi.rules.getRealLayer( 'nImplant' )
|
|
self.bImplantLayer = oroshi.rules.getRealLayer( 'pImplant' )
|
|
self.wellLayer = None
|
|
else:
|
|
self.tImplantLayer = oroshi.rules.getRealLayer( 'pImplant' )
|
|
self.bImplantLayer = oroshi.rules.getRealLayer( 'nImplant' )
|
|
self.wellLayer = oroshi.rules.getRealLayer( 'pWell' )
|
|
|
|
return
|
|
|
|
|
|
def metaTnb ( self ):
|
|
metaT = 0
|
|
for i in self.device.getInstances(): metaT += 1
|
|
return metaT
|
|
|
|
|
|
## <b>[API]</b> Set the Stack wiring specification.
|
|
#
|
|
# \param wiringSpec A string defining the connections for the
|
|
# gates and diffusion areas.
|
|
#
|
|
# For a comprehensive explanation of the wiring specification, refers
|
|
# to \ref secWiringSpecs .
|
|
|
|
def setWirings ( self, wiringSpec ):
|
|
|
|
trace( 100, 'SetWirings \n' )
|
|
|
|
restrictions = {}
|
|
for net in self.device.getNets():
|
|
if net.getName() == 'anonymous':
|
|
restrictions[net] = Device.AllBlocked
|
|
else:
|
|
restrictions[net] = Device.NorthBlocked|Device.SouthBlocked
|
|
|
|
hasTopBulkTrack = False
|
|
hasBotBulkTrack = False
|
|
specs = wiringSpec.split()
|
|
i = 0
|
|
topBulkWiring = None
|
|
botBulkWiring = None
|
|
|
|
trace( 100, specs )
|
|
|
|
for spec in specs:
|
|
wiring = Wiring( self.device, spec )
|
|
self.wirings.append( wiring )
|
|
|
|
if wiring.isTop():
|
|
#restrictions[wiring.net] = restrictions[wiring.net] & ~Device.NorthBlocked
|
|
if wiring.net == self.bulkNet:
|
|
hasTopBulkTrack = True
|
|
topBulkWiring = wiring
|
|
continue
|
|
Stack._addToTracks ( self.topTracks , wiring.topTrack, wiring.net )
|
|
Stack._addToWTracks( self.topWTracks, wiring.topTrack, wiring.wTrack )
|
|
restrictions[wiring.net] = restrictions[wiring.net] & ~Device.NorthBlocked
|
|
|
|
if wiring.isBot():
|
|
#restrictions[wiring.net] = restrictions[wiring.net] & ~Device.SouthBlocked
|
|
if wiring.net == self.bulkNet:
|
|
hasBotBulkTrack = True
|
|
botBulkWiring = wiring
|
|
continue
|
|
Stack._addToTracks ( self.botTracks , wiring.botTrack, wiring.net )
|
|
Stack._addToWTracks( self.botWTracks, wiring.botTrack, wiring.wTrack )
|
|
restrictions[wiring.net] = restrictions[wiring.net] & ~Device.SouthBlocked
|
|
|
|
for net, flags in restrictions.items():
|
|
self.device.setRestrictions( net, flags )
|
|
|
|
if self.flags & Stack.NorthBulk or hasTopBulkTrack:
|
|
if not self.topTracks or self.topTracks[-1] != self.bulkNet:
|
|
for net in self.topTracks:
|
|
if net == self.bulkNet:
|
|
raise Error( 3, 'Stack.setWirings(): Bulk track (net:"%s") must be topmost.' \
|
|
% self.bulkNet.getName() )
|
|
index = len(self.topTracks)
|
|
Stack._addToTracks( self.topTracks , index, self.bulkNet )
|
|
if (topBulkWiring != None): Stack._addToWTracks( self.topWTracks, index, topBulkWiring.wTrack )
|
|
elif (botBulkWiring != None): Stack._addToWTracks( self.topWTracks, index, botBulkWiring.wTrack )
|
|
else: Stack._addToWTracks( self.topWTracks, index, self.device.getParameter('B.w').getValue() )
|
|
|
|
bulkIndex = len(self.topTracks)-1
|
|
for wiring in self.wirings:
|
|
if wiring.net == self.bulkNet: wiring.setTop( bulkIndex )
|
|
|
|
Stack._addToTracks ( self.topTracks , len(self.topTracks) , None )
|
|
Stack._addToWTracks( self.topWTracks, len(self.topWTracks), self.device.getParameter('B.w').getValue() )
|
|
|
|
if self.flags & Stack.SouthBulk or hasBotBulkTrack:
|
|
if not self.botTracks or self.botTracks[-1] != self.bulkNet:
|
|
for net in self.botTracks:
|
|
if net == self.bulkNet:
|
|
raise Error( 3, 'Stack.setWirings(): Bulk track (net:"%s") must be bottommost.' \
|
|
% self.bulkNet.getName() )
|
|
index = len(self.botTracks)
|
|
Stack._addToTracks( self.botTracks , index, self.bulkNet )
|
|
if (botBulkWiring != None): Stack._addToWTracks( self.botWTracks, index, botBulkWiring.wTrack )
|
|
elif (topBulkWiring != None): Stack._addToWTracks( self.botWTracks, index, topBulkWiring.wTrack )
|
|
else: Stack._addToWTracks( self.botWTracks, index, self.device.getParameter('B.w').getValue() )
|
|
bulkIndex = len(self.botTracks)-1
|
|
for wiring in self.wirings:
|
|
if wiring.net == self.bulkNet: wiring.setBot( bulkIndex )
|
|
|
|
Stack._addToTracks ( self.botTracks , len(self.botTracks) , None )
|
|
Stack._addToWTracks( self.botWTracks, len(self.botWTracks), self.device.getParameter('B.w').getValue() )
|
|
|
|
trace( 100, '\tbotTracks:%d topTracks:%d\n' % (len(self.topTracks),len(self.botTracks)) )
|
|
for i in range(len(self.wirings)):
|
|
trace( 100, '\t| wirings[%d]: %s\n' % (i,str(self.wirings[i])) )
|
|
if self.wirings[i].net == self.bulkNet:
|
|
if hasBotBulkTrack: self.wirings[i].setBot( len(self.botTracks)-2 )
|
|
if hasTopBulkTrack: self.wirings[i].setTop( len(self.topTracks)-2 )
|
|
trace( 100, '\t+ wirings[%d]: %s\n' % (i,str(self.wirings[i])) )
|
|
|
|
for i in range(len(self.wirings)):
|
|
gateName = self.wirings[i].net.getName()
|
|
if gateName[0] != 'G': continue
|
|
|
|
leftMost = False
|
|
rightMost = False
|
|
if i == 1: leftMost = True
|
|
if i == len(self.wirings)-1: rightMost = True
|
|
|
|
# MIN means "minimize the number of sources", so according to the
|
|
# number of transistor fingers, it is almost equivalent to
|
|
# "drain first".
|
|
if not gateName in self.metaTransistors:
|
|
MIN = 0
|
|
if self.wirings[i-1].isDrain(): MIN = 1
|
|
|
|
self.metaTransistors[gateName] = { 'gate':self.wirings[i].net, 'MIN':MIN, 'NF':1
|
|
, 'leftMost':leftMost, 'rightMost':rightMost
|
|
}
|
|
else:
|
|
self.metaTransistors[gateName]['NF'] += 1
|
|
self.metaTransistors[gateName]['rightMost'] = rightMost
|
|
|
|
# Compute style parameters for use in geomod equations.
|
|
for mt in self.metaTransistors.values():
|
|
geoFlags = 0
|
|
nf = mt['NF']
|
|
if nf % 2:
|
|
mt['style.NDend'] = 1
|
|
mt['style.NSend'] = 1
|
|
mt['style.NDint'] = nf-1
|
|
mt['style.NSint'] = nf-1
|
|
if mt['leftMost']:
|
|
if mt['MIN'] == 1: geoFlags |= Stack.DrainIsolated
|
|
else: geoFlags |= Stack.SourceIsolated
|
|
else:
|
|
if mt['MIN'] == 1: geoFlags |= Stack.DrainShared
|
|
else: geoFlags |= Stack.SourceShared
|
|
if mt['leftMost']:
|
|
if mt['MIN'] == 1: geoFlags |= Stack.SourceIsolated
|
|
else: geoFlags |= Stack.DrainIsolated
|
|
else:
|
|
if mt['MIN'] == 1: geoFlags |= Stack.SourceShared
|
|
else: geoFlags |= Stack.DrainShared
|
|
else:
|
|
if mt['MIN']:
|
|
mt['style.NDend'] = 2
|
|
mt['style.NSend'] = 0
|
|
mt['style.NDint'] = (nf//2 - 1)*2
|
|
mt['style.NSint'] = nf
|
|
if mt['leftMost'] or mt['rightMost']: geoFlags |= Stack.DrainIsolated
|
|
else: geoFlags |= Stack.DrainShared
|
|
else:
|
|
mt['style.NDend'] = 0
|
|
mt['style.NSend'] = 2
|
|
mt['style.NSint'] = (nf//2 - 1)*2
|
|
mt['style.NDint'] = nf
|
|
if mt['leftMost'] or mt['rightMost']: geoFlags |= Stack.SourceIsolated
|
|
else: geoFlags |= Stack.SourceShared
|
|
|
|
mt['style.geomod'] = Stack.toGeomod( geoFlags, mt['NF'] )
|
|
return
|
|
|
|
|
|
def isNmos ( self ): return self.device.isNMOS()
|
|
def isPmos ( self ): return self.device.isPMOS()
|
|
def hasNorthBulk ( self ): return self.flags & Stack.NorthBulk
|
|
def hasSouthBulk ( self ): return self.flags & Stack.SouthBulk
|
|
def hasEastBulk ( self ): return self.flags & Stack.EastBulk
|
|
def hasWestBulk ( self ): return self.flags & Stack.WestBulk
|
|
def wiring ( self, i ): return self.wirings[i]
|
|
def tracksNb ( self ): return len(self.topTracks) + len(self.botTracks)
|
|
def topTracksNb ( self ): return len(self.topTracks)
|
|
def botTracksNb ( self ): return len(self.botTracks)
|
|
|
|
def isTopTrack ( self, net ):
|
|
if net == self.bulkNet: return False
|
|
|
|
trackIndex = 2
|
|
if self.flags & Stack.NorthBulk: trackIndex = 3
|
|
if trackIndex > len(self.topTracks): return False
|
|
if self.topTracks[-trackIndex] != net: return False
|
|
return True
|
|
|
|
def isBotTrack ( self, net ):
|
|
if net == self.bulkNet: return False
|
|
|
|
trackIndex = 2
|
|
if self.flags & Stack.SouthBulk: trackIndex = 3
|
|
if trackIndex > len(self.botTracks): return False
|
|
if self.botTracks[-trackIndex] != net: return False
|
|
return True
|
|
|
|
######################################
|
|
def tracksNbPitch ( self ):
|
|
nb = 0
|
|
for i in range (self.topTracksNb()-1):
|
|
nb += self.topWTracks[i]
|
|
for i in range (self.botTracksNb()-1):
|
|
nb += self.botWTracks[i]
|
|
return nb
|
|
|
|
def getBotTrackY ( self, i ):
|
|
nb = self.horPitch
|
|
for j in range(self.botTracksNb()-2-i):
|
|
nb += self.horPitch*self.botWTracks[self.botTracksNb()-2-j]
|
|
return nb
|
|
|
|
def getTopTrackY ( self, i ):
|
|
y = (self.ypitches)*self.horPitch + self.getBotTrackY(0) + self.getHorizontalWidth(self.botWTracks[0])
|
|
#y = self.activeOffsetY + (self.ypitches-1)*self.horPitch #- self.hTrackDistance
|
|
for j in range(i):
|
|
y += self.horPitch*self.topWTracks[j]
|
|
return y
|
|
|
|
def getLastTopTrackY ( self ): return self.getTopTrackY(self.topTracksNb()-1)
|
|
def getLastTopWTracks ( self ): return self.topWTracks[self.topTracksNb()-2]
|
|
def getLastBotWTracks ( self ): return self.botWTracks[self.botTracksNb()-2]
|
|
def getHorizontalWidth ( self, trackSpan ): return (self.horPitch * (trackSpan - 1))
|
|
def getHorizontalAxis ( self, trackSpan ): return self.getHorizontalWidth(trackSpan)//2
|
|
|
|
def getWiringWidth ( self, wiring, isTopConnect ):
|
|
if isTopConnect: return self.horPitch * (self.topWTracks[wiring.topTrack] - 1)
|
|
return self.horPitch * (self.botWTracks[wiring.botTrack] - 1)
|
|
|
|
######################################
|
|
def DMCI ( self ):
|
|
if not self.dimensioned: self.computeDimensions()
|
|
return self.sideActiveWidth \
|
|
- self.L//2 \
|
|
- self.metal1ToGate \
|
|
- self.eDiffMetal1Width//2
|
|
|
|
def DMCG ( self ):
|
|
if not self.dimensioned: self.computeDimensions()
|
|
return (self.gatePitch - self.L)//2
|
|
|
|
def DMCGT ( self ): return 0.0
|
|
|
|
def DGG ( self ):
|
|
if not self.dimensioned: self.computeDimensions()
|
|
return self.gatePitch - self.L
|
|
|
|
def DGI ( self ):
|
|
if not self.dimensioned: self.computeDimensions()
|
|
return self.sideActiveWidth - self.L//2
|
|
|
|
|
|
## <b>[internal]</b> Compute Stack dimensions from the technological rules.
|
|
#
|
|
# <b>Internal function.</b> Perform the computation of:
|
|
# - \c self.metal1Pitch
|
|
# - \c self.minWidth_metal1
|
|
# - \c self.metal2Pitch
|
|
# - \c self.minWidth_metal2
|
|
# - \c self.gatePitch
|
|
# - \c self.sideActiveWidth
|
|
# - \c self.hTrackDistance
|
|
# - \c self.xpitches
|
|
# - \c self.ypitches
|
|
# - \c self.activeOffsetX
|
|
# - \c self.activeOffsetY
|
|
# - \c self.boundingBox
|
|
|
|
def computeDimensions ( self ):
|
|
if self.dimensioned: return
|
|
self.dimensioned = True
|
|
|
|
trace( 100, '\tStack.computeDimensions(): Start time %s.\n' % str(datetime.datetime.now()) )
|
|
|
|
specsNb = 3 + (self.NFs - 1)*2
|
|
if len(self.wirings) != specsNb:
|
|
raise Error( 3, [ 'Stack._computeDimension(): Wiring inconsistency,' \
|
|
' have %d specs but should be %d for a NFs of %d.' \
|
|
% (len(self.wirings), specsNb, self.NFs)
|
|
] )
|
|
|
|
rg = AllianceFramework.get().getRoutingGauge()
|
|
self.metal2Pitch = rg.getHorizontalPitch()
|
|
self.metal3Pitch = rg.getVerticalPitch()
|
|
self.isVH = rg.isVH()
|
|
|
|
foundHor = False
|
|
foundVer = False
|
|
for depth in range(rg.getDepth()):
|
|
rlg = rg.getLayerGauge(depth)
|
|
if rlg.getType() == Constant.PinOnly: continue
|
|
if rlg.getDirection() == Constant.Horizontal and not foundHor:
|
|
self.horPitch = rlg.getPitch()
|
|
foundHor = True
|
|
if rlg.getDirection() == Constant.Vertical and not foundVer:
|
|
self.verPitch = rlg.getPitch()
|
|
foundVer = True
|
|
|
|
width1 = self.minWidth_cut0 + self.minEnclosure_metal1_cut0*2
|
|
width2 = self.minWidth_cut1 + self.minEnclosure_metal1_cut1*2
|
|
self.wire1Width = max( self.minWidth_metal1, width1, width2 )
|
|
|
|
pitch1 = self.minWidth_metal2 + self.minSpacing_metal2
|
|
pitch2 = self.minWidth_cut1 + self.minEnclosure_metal2_cut1*2 + self.minSpacing_metal2
|
|
self.metal2TechnoPitch = max( pitch1, pitch2 )
|
|
|
|
if self.metal2Pitch == 0: self.metal2Pitch = self.metal2TechnoPitch
|
|
if self.metal2Pitch < self.metal2TechnoPitch:
|
|
raise Error( 3, 'Stack.computeDimensions(): Routing gauge pitch %s is inferior to technology minimum of %s.'
|
|
% ( DbU.getValueString(self.metal2Pitch)
|
|
, DbU.getValueString(self.metal2TechnoPitch) ) )
|
|
|
|
width1 = self.minWidth_cut1 + self.minEnclosure_metal2_cut1*2
|
|
if self.isVH:
|
|
self.wire2Width = self.minWidth_metal2
|
|
self.wire3Width = self.minWidth_metal3
|
|
else:
|
|
self.wire2Width = max( self.minWidth_metal2, width1 )
|
|
|
|
pitch1 = self.minGateSpacing_poly + self.minWidth_cut0
|
|
pitch2 = self.minSpacing_cut1 + self.minWidth_cut1
|
|
self.gateVia1Pitch = max( pitch1, pitch2 )
|
|
|
|
pitch1 = self.minWidth_cut0 + self.minSpacing_cut0
|
|
pitch2 = self.minWidth_cut1 + self.minSpacing_cut1
|
|
self.contactDiffPitch = max( pitch1, pitch2 )
|
|
self.contactDiffSide = max( self.minWidth_cut1, self.minWidth_cut0 )
|
|
|
|
iDiffContactWidth = self.contactDiffSide + (self.NIRC - 1)*self.contactDiffPitch
|
|
eDiffContactWidth = self.contactDiffSide + (self.NERC - 1)*self.contactDiffPitch
|
|
overlap = max( self.minEnclosure_metal1_cut0, self.minEnclosure_metal1_cut1 )
|
|
gateVia1Side = max( self.minWidth_cut1, self.minWidth_cut0 )
|
|
self.iDiffMetal1Width = iDiffContactWidth + overlap*2
|
|
self.eDiffMetal1Width = eDiffContactWidth + overlap*2
|
|
|
|
pitch1 = self.L \
|
|
+ iDiffContactWidth \
|
|
+ self.minSpacing_cut0_poly*2
|
|
pitch2 = self.minWidth_cut0 \
|
|
+ self.minEnclosure_poly_cut0*2 \
|
|
+ self.minGateSpacing_poly
|
|
pitch3 = self.minSpacing_metal1*2 \
|
|
+ self.iDiffMetal1Width \
|
|
+ max( self.L, gateVia1Side+2*overlap )
|
|
self.gatePitch = max( pitch1, pitch2, pitch3 )
|
|
self.metal1ToGate = (self.gatePitch - self.L - self.iDiffMetal1Width) // 2
|
|
|
|
self.sideActiveWidth = self.minEnclosure_active_cut0 \
|
|
- self.minEnclosure_metal1_cut0 \
|
|
+ self.eDiffMetal1Width \
|
|
+ self.metal1ToGate \
|
|
+ self.L//2
|
|
|
|
hTrackDistance1 = self.minWidth_cut0//2 + self.minSpacing_cut0_active
|
|
hTrackDistance2 = self.minWidth_cut0//2 + self.minEnclosure_poly_cut0 + self.minSpacing_poly_active
|
|
self.hTrackDistance = max( hTrackDistance1, hTrackDistance2 )
|
|
|
|
vBulkDistance1 = self.minWidth_cut0//2 \
|
|
+ self.minEnclosure_active_cut0 \
|
|
+ self.minEnclosure_tImplant_active \
|
|
+ self.minEnclosure_bImplant_active
|
|
vBulkDistance2 = self.minWidth_cut0//2 \
|
|
+ self.minEnclosure_active_cut0 \
|
|
+ self.minSpacing_nImplant_pImplant
|
|
self.vBulkDistance = max( vBulkDistance1, vBulkDistance2 )
|
|
|
|
activeHeight = self.w + 2*self.hTrackDistance
|
|
self.ypitches = activeHeight // self.horPitch
|
|
if activeHeight % self.horPitch: self.ypitches += 1
|
|
if (self.ypitches + self.tracksNbPitch()) % 2: self.ypitches += 1
|
|
|
|
diffusionWidth = (self.sideActiveWidth + self.minEnclosure_tImplant_active)*2 + (self.NFs-1)*self.gatePitch
|
|
deviceMinWidth = diffusionWidth
|
|
if self.flags & Stack.WestBulk: deviceMinWidth += self.vBulkDistance + self.verPitch
|
|
if self.flags & Stack.EastBulk: deviceMinWidth += self.vBulkDistance + self.verPitch
|
|
|
|
self.xpitches = deviceMinWidth // self.verPitch
|
|
if self.xpitches % 2:
|
|
self.xpitches += 1
|
|
else:
|
|
if diffusionWidth % self.verPitch:
|
|
self.xpitches += 2
|
|
|
|
self.activeOffsetY = self.getBotTrackY(0) + self.getHorizontalWidth(self.botWTracks[0]) \
|
|
+ self.hTrackDistance \
|
|
+ (self.ypitches*self.horPitch - activeHeight)//2 \
|
|
|
|
self.bbHeight = self.getLastTopTrackY()
|
|
|
|
self.bbWidth = self.xpitches * self.verPitch
|
|
diffusionRealWidth = self.bbWidth
|
|
if self.flags & Stack.WestBulk: diffusionRealWidth -= self.vBulkDistance + self.verPitch
|
|
if self.flags & Stack.EastBulk: diffusionRealWidth -= self.vBulkDistance + self.verPitch
|
|
self.activeOffsetX = self.minEnclosure_tImplant_active + (diffusionRealWidth - diffusionWidth)//2
|
|
if self.flags & Stack.WestBulk: self.activeOffsetX += self.vBulkDistance + self.verPitch
|
|
|
|
self.boundingBox = Box( 0, 0, self.bbWidth, self.bbHeight )
|
|
self.activeBox = Box( self.activeOffsetX
|
|
, self.activeOffsetY
|
|
, self.activeOffsetX + (self.NFs - 1) * self.gatePitch + 2*self.sideActiveWidth
|
|
, self.activeOffsetY + self.w
|
|
)
|
|
|
|
westBulkX = self.activeOffsetX
|
|
eastBulkX = self.activeOffsetX + diffusionWidth - self.minEnclosure_tImplant_active*2
|
|
southBulkY = self.boundingBox.getYMin() + self.horPitch
|
|
northBulkY = self.boundingBox.getYMax() - self.horPitch
|
|
|
|
|
|
self.bulkWidth = self.minWidth_cut0 + 2*self.minEnclosure_active_cut0
|
|
|
|
if self.flags & Stack.EastBulk:
|
|
eastBulkX = self.boundingBox.getXMax() - self.horPitch
|
|
self.bulks[2] = Bulk( self, eastBulkX, southBulkY, northBulkY, Stack.EastBulk )
|
|
|
|
if self.flags & Stack.WestBulk:
|
|
westBulkX = self.horPitch
|
|
self.bulks[3] = Bulk( self, westBulkX, southBulkY, northBulkY, Stack.WestBulk )
|
|
|
|
if self.flags & Stack.NorthBulk:
|
|
self.bulks[0] = Bulk( self, northBulkY, westBulkX, eastBulkX, Stack.NorthBulk )
|
|
|
|
if self.flags & Stack.SouthBulk:
|
|
self.bulks[1] = Bulk( self, southBulkY, westBulkX, eastBulkX, Stack.SouthBulk )
|
|
|
|
self.DMCI = oroshi.toUnity( self.sideActiveWidth
|
|
- self.L//2
|
|
- self.metal1ToGate
|
|
- self.eDiffMetal1Width//2 )
|
|
self.DMCG = oroshi.toUnity( (self.gatePitch - self.L)//2 )
|
|
self.DMCGT = 0
|
|
self.DGG = oroshi.toUnity( self.gatePitch - self.L )
|
|
self.DGI = oroshi.toUnity( self.sideActiveWidth - self.L//2 )
|
|
|
|
trace( 100, '+' )
|
|
trace( 100, '\t +----------------------------------+\n' )
|
|
trace( 100, '\t | Parameters |\n' )
|
|
trace( 100, '\t +=====================+============+\n' )
|
|
trace( 100, '\t | w (finger) | %10s |\n' % DbU.getValueString(self.w) )
|
|
trace( 100, '\t | L | %10s |\n' % DbU.getValueString(self.L) )
|
|
trace( 100, '\t | NFs | %10d |\n' % self.NFs )
|
|
trace( 100, '\t +=====================+============+\n' )
|
|
trace( 100, '\t | Computed |\n' )
|
|
trace( 100, '\t +=====================+============+\n' )
|
|
trace( 100, '\t | metal2 pitch | %10s |\n' % DbU.getValueString(self.metal2Pitch) )
|
|
trace( 100, '\t | metal2 Techno pitch | %10s |\n' % DbU.getValueString(self.metal2TechnoPitch) )
|
|
trace( 100, '\t | gatePitch | %10s |\n' % DbU.getValueString(self.gatePitch) )
|
|
trace( 100, '\t | sideActiveWidth | %10s |\n' % DbU.getValueString(self.sideActiveWidth) )
|
|
trace( 100, '\t | contactDiffPitch | %10s |\n' % DbU.getValueString(self.contactDiffPitch) )
|
|
trace( 100, '\t | hTrackDistance | %10s |\n' % DbU.getValueString(self.hTrackDistance) )
|
|
trace( 100, '\t | vBulkDistance | %10s |\n' % DbU.getValueString(self.vBulkDistance) )
|
|
trace( 100, '\t | activeOffsetX | %10s |\n' % DbU.getValueString(self.activeOffsetX) )
|
|
trace( 100, '\t | activeOffsetY | %10s |\n' % DbU.getValueString(self.activeOffsetY) )
|
|
trace( 100, '\t | active pitches | %10d |\n' % self.ypitches )
|
|
trace( 100, '\t +---------------------+------------+\n' )
|
|
trace( 100, '\n' )
|
|
trace( 100, '\tStack.computeDimensions(): End time %s.\n' % str(datetime.datetime.now()) )
|
|
trace( 100, '-' )
|
|
|
|
for mt in self.metaTransistors.values():
|
|
self.computeStressEffect( mt )
|
|
self.computeLayoutParasitics( mt )
|
|
traceMT( mt )
|
|
return
|
|
|
|
## <b>[API]</b> Draw the complete layout.
|
|
#
|
|
# Draw the commplete layout of the Stack.
|
|
|
|
def doLayout ( self, bbMode ):
|
|
trace( 100, '+', '\tStack.doLayout().\n' )
|
|
|
|
self.computeDimensions()
|
|
|
|
if not bbMode:
|
|
UpdateSession.open()
|
|
|
|
self.drawActive()
|
|
self.drawWell()
|
|
|
|
xoffset = self.activeOffsetX + self.sideActiveWidth
|
|
for i in range(self.NFs):
|
|
self.drawGate( xoffset + i*self.gatePitch, self.wirings[1+2*i] )
|
|
|
|
for i in range(self.NFs+1):
|
|
if i == 0: # Leftmost diffusion area.
|
|
NRC = self.NERC
|
|
width = self.eDiffMetal1Width
|
|
axis = self.activeOffsetX \
|
|
+ self.sideActiveWidth \
|
|
- self.L//2 \
|
|
- self.metal1ToGate \
|
|
- width//2
|
|
elif i == self.NFs: # Rightmost diffusion area.
|
|
NRC = self.NERC
|
|
width = self.eDiffMetal1Width
|
|
axis = self.activeOffsetX \
|
|
+ self.sideActiveWidth \
|
|
+ self.gatePitch*(self.NFs - 1) \
|
|
+ self.L//2 \
|
|
+ self.metal1ToGate \
|
|
+ width//2
|
|
else: # Middle diffusion areas.
|
|
NRC = self.NIRC
|
|
width = self.iDiffMetal1Width
|
|
axis = self.activeOffsetX \
|
|
+ self.sideActiveWidth \
|
|
- self.gatePitch//2 \
|
|
+ self.gatePitch*i
|
|
|
|
self.drawSourceDrain( axis, self.wirings[2*i], width, NRC )
|
|
|
|
capSpacing = self.minSpacing_metal2 + self.minWidth_metal2//2
|
|
capSpacing = max( capSpacing, self.minSpacing_metal3 + self.minWidth_metal3//2 )
|
|
|
|
metal2 = oroshi.rules.getRealLayer( 'metal2' )
|
|
metal3 = oroshi.rules.getRealLayer( 'metal3' )
|
|
trackNb = 0
|
|
if self.topTracks: trackNb = len(self.topTracks)
|
|
for i in range(trackNb):
|
|
if not self.topTracks[i]: continue
|
|
h = Horizontal.create( self.topTracks[i]
|
|
, metal2
|
|
, self.getTopTrackY(i) + self.getHorizontalAxis (self.topWTracks[i])
|
|
, self.wire2Width + self.getHorizontalWidth(self.topWTracks[i])
|
|
, capSpacing
|
|
, self.bbWidth - capSpacing )
|
|
if self.isVH:
|
|
h = Horizontal.create( self.topTracks[i]
|
|
, metal3
|
|
, self.getTopTrackY(i) + self.getHorizontalAxis (self.topWTracks[i])
|
|
, self.wire3Width + self.getHorizontalWidth(self.topWTracks[i])
|
|
, capSpacing
|
|
, self.bbWidth - capSpacing )
|
|
NetExternalComponents.setExternal( h )
|
|
|
|
trackNb = 0
|
|
if self.botTracks: trackNb = len(self.botTracks)
|
|
for i in range(trackNb):
|
|
if not self.botTracks[i]: continue
|
|
h = Horizontal.create( self.botTracks[i]
|
|
, metal2
|
|
, self.getBotTrackY(i) + self.getHorizontalAxis (self.botWTracks[i])
|
|
, self.wire2Width + self.getHorizontalWidth(self.botWTracks[i])
|
|
, capSpacing
|
|
, self.bbWidth - capSpacing )
|
|
if self.isVH:
|
|
h = Horizontal.create( self.botTracks[i]
|
|
, metal3
|
|
, self.getBotTrackY(i) + self.getHorizontalAxis (self.botWTracks[i])
|
|
, self.wire3Width + self.getHorizontalWidth(self.botWTracks[i])
|
|
, capSpacing
|
|
, self.bbWidth - capSpacing )
|
|
NetExternalComponents.setExternal( h )
|
|
|
|
|
|
for bulk in self.bulks:
|
|
if bulk: bulk.doLayout()
|
|
|
|
UpdateSession.close()
|
|
|
|
trace( 100, '\tStack.doLayout(): End time %s\n' %str(datetime.datetime.now()) )
|
|
trace( 100, '-' )
|
|
return self.boundingBox
|
|
|
|
|
|
def drawActive ( self ):
|
|
trace( 100, '+', '\tDrawActive().\n' )
|
|
activeNet = self.device.getNet( 'active' )
|
|
if not activeNet: activeNet = Net.create( self.device, 'active' )
|
|
activeNet.setAutomatic( True )
|
|
|
|
tImplantNet = self.device.getNet( 'nImplant' )
|
|
if not tImplantNet: tImplantNet = Net.create( self.device, 'nImplant' )
|
|
tImplantNet.setAutomatic( True )
|
|
|
|
active = oroshi.rules.getRealLayer( 'active' )
|
|
width = self.w
|
|
length = (self.NFs - 1) * self.gatePitch + 2*self.sideActiveWidth
|
|
axis = width // 2
|
|
xoffset = self.activeOffsetX
|
|
yoffset = self.activeOffsetY
|
|
segment = Horizontal.create( activeNet, active, yoffset+axis, width, xoffset, xoffset+length )
|
|
|
|
width = width + 2*self.minEnclosure_tImplant_active
|
|
length = length + 2*self.minEnclosure_tImplant_active
|
|
axis = width // 2
|
|
xoffset = self.activeOffsetX - self.minEnclosure_tImplant_active
|
|
yoffset = self.activeOffsetY - self.minEnclosure_tImplant_active
|
|
segment = Horizontal.create( tImplantNet
|
|
, self.tImplantLayer
|
|
, yoffset+axis
|
|
, width
|
|
, xoffset
|
|
, xoffset+length )
|
|
return
|
|
|
|
|
|
def drawWell ( self ):
|
|
if self.wellLayer:
|
|
trace( 100, '+', '\tDrawWell().\n' )
|
|
Pad.create( self.device.getNet('anonymous'), self.wellLayer, self.boundingBox )
|
|
return
|
|
|
|
|
|
def drawGate ( self, axis, wiring ):
|
|
trace( 100, '\tStack.drawGate(): %s\n' % wiring )
|
|
|
|
gate = oroshi.rules.getRealLayer( 'poly' )
|
|
cut0 = oroshi.rules.getRealLayer( 'cut0' )
|
|
cut1 = oroshi.rules.getRealLayer( 'cut1' )
|
|
cut2 = oroshi.rules.getRealLayer( 'cut2' )
|
|
metal1 = oroshi.rules.getRealLayer( 'metal1' )
|
|
metal2 = oroshi.rules.getRealLayer( 'metal2' )
|
|
metal3 = oroshi.rules.getRealLayer( 'metal3' )
|
|
width = self.L
|
|
|
|
if wiring.isTop(): ytarget = self.getTopTrackY( wiring.topTrack )
|
|
else: ytarget = self.activeOffsetY + self.minExtension_poly_active + self.w
|
|
|
|
if wiring.isBot(): ysource = self.getBotTrackY( wiring.botTrack )
|
|
else: ysource = self.activeOffsetY - self.minExtension_poly_active
|
|
|
|
segment = Vertical.create( wiring.net, gate, axis, width, ysource, ytarget )
|
|
|
|
contactHeight = self.minWidth_cut0 + 2*self.minEnclosure_poly_cut0
|
|
contactWidth = max( contactHeight, self.L )
|
|
contactsNb = (contactWidth - 2*self.minEnclosure_poly_cut0) // self.gateVia1Pitch
|
|
if contactsNb:
|
|
contactPitch = contactWidth // contactsNb
|
|
else:
|
|
contactsNb = 1
|
|
contactPitch = self.L
|
|
|
|
gateVia1Overlap = max( self.minEnclosure_metal1_cut0, self.minEnclosure_metal1_cut1 )
|
|
gateVia1Side = max( self.minWidth_cut1, self.minWidth_cut0 )
|
|
|
|
for connector in ( (wiring.isTop(),ytarget,True), (wiring.isBot(),ysource,False) ):
|
|
if not connector[0]: continue
|
|
|
|
isTopConnect = connector[2]
|
|
yoffset = connector[1]
|
|
xcontact = axis - self.L//2 + contactPitch//2
|
|
contactBb = Box( axis, yoffset ).inflate( contactWidth//2, contactHeight//2)
|
|
width = gateVia1Side + 2*gateVia1Overlap + self.getWiringWidth(wiring, isTopConnect)
|
|
y = yoffset + self.getWiringWidth(wiring, isTopConnect)//2
|
|
rowHeight = self.horPitch
|
|
|
|
if isTopConnect:
|
|
contactBb = Box( axis, yoffset ).inflate( contactWidth//2, contactHeight//2+self.getWiringWidth(wiring, isTopConnect))
|
|
|
|
Pad.create( wiring.net, gate, contactBb ) # GateExtension Contact
|
|
Horizontal.create( wiring.net
|
|
, metal1
|
|
, y
|
|
, width
|
|
, xcontact - gateVia1Side//2 - gateVia1Overlap
|
|
, xcontact + (contactsNb-1)*contactPitch + gateVia1Side//2 + gateVia1Overlap )# M1 area
|
|
|
|
|
|
cut1Bb = Box()
|
|
cut2Bb = Box()
|
|
for i in range(contactsNb):
|
|
if isTopConnect: rangeWidth = range(self.topWTracks[wiring.topTrack])
|
|
else: rangeWidth = range(self.botWTracks[wiring.botTrack])
|
|
|
|
for j in rangeWidth:
|
|
contactBb = Box( xcontact, yoffset ).inflate( self.minWidth_cut0//2 )
|
|
Pad.create( wiring.net, cut0, contactBb )
|
|
contactBb = Box( xcontact, yoffset ).inflate( self.minWidth_cut1//2 )
|
|
Pad.create( wiring.net, cut1, contactBb )
|
|
cut1Bb.merge( contactBb )
|
|
if self.isVH:
|
|
contactBb = Box( xcontact, yoffset ).inflate( self.minWidth_cut2//2 )
|
|
Pad.create( wiring.net, cut2, contactBb )
|
|
cut2Bb.merge( contactBb )
|
|
yoffset += rowHeight
|
|
yoffset -= rowHeight*self.topWTracks[wiring.topTrack]
|
|
|
|
if self.isVH:
|
|
metal2EnclBb = Box( cut1Bb ).inflate( self.minEnclosure_metal2_cut1 )
|
|
metal2EnclBb.merge( Box( cut2Bb ).inflate( self.minEnclosure_metal2_cut2 ) )
|
|
Pad.create( wiring.net, metal2, metal2EnclBb )
|
|
|
|
metal3EnclBb = Box( cut2Bb ).inflate( self.minEnclosure_metal3_cut2 )
|
|
Pad.create( wiring.net, metal3, metal3EnclBb )
|
|
|
|
enable = False
|
|
if wiring.net == self.bulkNet: enable = True
|
|
if connector[0] & Stack.NorthBulk and self.bulks[0]: self.bulks[0].addContact( xcontact, enable )
|
|
if connector[0] & Stack.SouthBulk and self.bulks[1]: self.bulks[1].addContact( xcontact, enable )
|
|
|
|
xcontact += contactPitch
|
|
|
|
return
|
|
|
|
|
|
def drawSourceDrain ( self, axis, wiring, width, cols ):
|
|
trace( 100, '\tStack.drawSourceDrain(): %s @%s width:%s NRC=%d\n' \
|
|
% (wiring, DbU.getValueString(axis), DbU.getValueString(width), cols ) )
|
|
|
|
metal1 = oroshi.rules.getRealLayer( 'metal1' )
|
|
metal2 = oroshi.rules.getRealLayer( 'metal2' )
|
|
metal3 = oroshi.rules.getRealLayer( 'metal3' )
|
|
cut0 = oroshi.rules.getRealLayer( 'cut0' )
|
|
cut1 = oroshi.rules.getRealLayer( 'cut1' )
|
|
cut2 = oroshi.rules.getRealLayer( 'cut2' )
|
|
rows = max( 1, (self.w - 2*self.minEnclosure_active_cut0) // self.contactDiffPitch )
|
|
ypitch = self.w // rows
|
|
yoffset = self.activeOffsetY + ypitch//2
|
|
xpitch = self.contactDiffPitch
|
|
ypitch2 = self.horPitch
|
|
xoffset = axis - (self.contactDiffPitch * (cols - 1))//2
|
|
|
|
if self.w < 2*self.minEnclosure_active_cut0 + self.minWidth_cut0:
|
|
active = oroshi.rules.getRealLayer( 'active' )
|
|
|
|
box = Box( xoffset, yoffset, xoffset + (cols-1)*xpitch, yoffset )
|
|
box.inflate( self.minWidth_cut0 + self.minEnclosure_active_cut0 )
|
|
Pad.create( wiring.net, active, box )
|
|
|
|
box.inflate( self.minEnclosure_tImplant_active )
|
|
Pad.create( wiring.net, self.tImplantLayer, box )
|
|
|
|
for j in range(rows):
|
|
for i in range(cols):
|
|
contact = Contact.create( wiring.net
|
|
, cut0
|
|
, xoffset + i*xpitch
|
|
, yoffset + j*ypitch
|
|
, self.minWidth_cut0
|
|
, self.minWidth_cut0 )
|
|
|
|
if wiring.isTop():
|
|
ytarget = self.getTopTrackY( wiring.topTrack )
|
|
for i in range(cols):
|
|
for j in range(self.topWTracks[wiring.topTrack]):
|
|
Contact.create( wiring.net
|
|
, cut1
|
|
, xoffset + i*xpitch
|
|
, ytarget + j*ypitch2
|
|
, self.minWidth_cut1
|
|
, self.minWidth_cut1 )
|
|
Contact.create( wiring.net
|
|
, cut2
|
|
, xoffset + i*xpitch
|
|
, ytarget + j*ypitch2
|
|
, self.minWidth_cut2
|
|
, self.minWidth_cut2 )
|
|
|
|
enable = False
|
|
if wiring.net == self.bulkNet: enable = True
|
|
if self.bulks[0]: self.bulks[0].addContact( xoffset + i*xpitch, enable )
|
|
|
|
if self.isVH:
|
|
cutBb = Box( xoffset
|
|
, ytarget
|
|
, xoffset + xpitch *(cols - 1)
|
|
, ytarget + ypitch2*(self.topWTracks[wiring.topTrack] - 1) )
|
|
|
|
metal2EnclBb = Box( cutBb ).inflate( self.minEnclosure_metal2_cut1 + self.minWidth_cut1//2 )
|
|
metal2EnclBb.merge( Box( cutBb ).inflate( self.minEnclosure_metal2_cut2 + self.minWidth_cut2//2 ) )
|
|
Pad.create( wiring.net, metal2, metal2EnclBb )
|
|
|
|
metal3EnclBb = Box( cutBb ).inflate( self.minEnclosure_metal3_cut2 + self.minWidth_cut2//2 )
|
|
Pad.create( wiring.net, metal3, metal3EnclBb )
|
|
|
|
ytarget += ypitch2*(self.topWTracks[wiring.topTrack]-1)
|
|
else:
|
|
ytarget = yoffset + ypitch*(rows - 1)
|
|
|
|
if wiring.isBot():
|
|
ysource = self.getBotTrackY( wiring.botTrack )
|
|
for i in range(cols):
|
|
for j in range(self.botWTracks[wiring.botTrack]):
|
|
Contact.create( wiring.net
|
|
, cut1
|
|
, xoffset + i*xpitch
|
|
, ysource + j*ypitch2
|
|
, self.minWidth_cut1
|
|
, self.minWidth_cut1 )
|
|
Contact.create( wiring.net
|
|
, cut2
|
|
, xoffset + i*xpitch
|
|
, ysource + j*ypitch2
|
|
, self.minWidth_cut2
|
|
, self.minWidth_cut2 )
|
|
|
|
if self.bulks[1]:
|
|
if self.isBotTrack(wiring.net) or wiring.net == self.bulkNet:
|
|
enable = False
|
|
if wiring.net == self.bulkNet: enable = True
|
|
self.bulks[1].addContact( xoffset + i*xpitch, enable )
|
|
|
|
if self.isVH:
|
|
cutBb = Box( xoffset
|
|
, ysource
|
|
, xoffset + xpitch *(cols - 1)
|
|
, ysource + ypitch2*(self.botWTracks[wiring.botTrack] - 1) )
|
|
|
|
metal2EnclBb = Box( cutBb ).inflate( self.minEnclosure_metal2_cut1 + self.minWidth_cut1//2 )
|
|
metal2EnclBb.merge( Box( cutBb ).inflate( self.minEnclosure_metal2_cut2 + self.minWidth_cut2//2 ) )
|
|
Pad.create( wiring.net, metal2, metal2EnclBb )
|
|
|
|
metal3EnclBb = Box( cutBb ).inflate( self.minEnclosure_metal3_cut2 + self.minWidth_cut2//2 )
|
|
Pad.create( wiring.net, metal3, metal3EnclBb )
|
|
else:
|
|
ysource = yoffset
|
|
|
|
bcontact = Contact.create( wiring.net
|
|
, metal1
|
|
, axis
|
|
, ysource
|
|
, width
|
|
, self.wire1Width )
|
|
tcontact = Contact.create( wiring.net
|
|
, metal1
|
|
, axis
|
|
, ytarget
|
|
, width
|
|
, self.wire1Width )
|
|
segment = Vertical.create( bcontact, tcontact, metal1, axis, width )
|
|
|
|
return
|
|
|
|
|
|
def computeStressEffect ( self, mt ):
|
|
# Stress effect computation as specified in S. Youssef thesis, p.59.
|
|
eastBulkWidth = 0
|
|
westBulkWidth = 0
|
|
if self.flags & Stack.WestBulk: westBulkWidth = self.bulkWidth
|
|
if self.flags & Stack.EastBulk: eastBulkWidth = self.bulkWidth
|
|
|
|
L = oroshi.toUnity( self.L )
|
|
sumA_Bsim4 = 0.0
|
|
sumB_Bsim4 = 0.0
|
|
sumA_Crolles = 0.0
|
|
sumB_Crolles = 0.0
|
|
|
|
for i in range(self.NFs):
|
|
if not self.wirings[i].net != mt['gate']: continue
|
|
|
|
sa = self.DGI + westBulkWidth + i *(L + self.DGG)
|
|
sb = self.DGI + eastBulkWidth + (self.NFs - i - 1)*(L + self.DGG)
|
|
|
|
sumA_Bsim4 += 1 / (sa + 0.5*self.DGG)
|
|
sumB_Bsim4 += 1 / (sb + 0.5*self.DGG)
|
|
sumA_Crolles += 1 / sa
|
|
sumB_Crolles += 1 / sb
|
|
|
|
SAinv_Bsim4 = sumA_Bsim4 / mt['NF']
|
|
SBinv_Bsim4 = sumB_Bsim4 / mt['NF']
|
|
SAinv_Crolles = sumA_Crolles / mt['NF']
|
|
SBinv_Crolles = sumB_Crolles / mt['NF']
|
|
|
|
mt['stress.SAinv_Bsim4'] = SAinv_Bsim4
|
|
mt['stress.SBinv_Bsim4'] = SBinv_Bsim4
|
|
mt['stress.SAeff_Bsim4'] = 1 / SAinv_Bsim4
|
|
mt['stress.SBeff_Bsim4'] = 1 / SBinv_Bsim4
|
|
mt['stress.LODeffect' ] = 1 / SAinv_Bsim4 + 1 / SBinv_Bsim4
|
|
mt['stress.alpha' ] = 2 / ( SAinv_Bsim4 + SBinv_Bsim4 )
|
|
mt['stress.alphaInv' ] = 1 / mt['stress.alpha']
|
|
|
|
mt['stress.SAinv_Crolles' ] = SAinv_Crolles
|
|
mt['stress.SBinv_Crolles' ] = SBinv_Crolles
|
|
mt['stress.SAeff_Crolles' ] = 1 / SAinv_Crolles
|
|
mt['stress.SBeff_Crolles' ] = 1 / SBinv_Crolles
|
|
mt['stress.po2actEff_Crolles'] = 2 / ( SAinv_Crolles + SBinv_Crolles )
|
|
|
|
return
|
|
|
|
|
|
def computeLayoutParasitics ( self, mt ):
|
|
#trace( 100, '\tStack.computeLayoutParasitics(): %s\n' % str(mt) )
|
|
|
|
NSend = mt['style.NSend']
|
|
NDend = mt['style.NDend']
|
|
NSint = mt['style.NSint']
|
|
NDint = mt['style.NDint']
|
|
NF = mt['NF']
|
|
Weff = oroshi.toUnity( self.w )
|
|
DMCI = self.DMCI
|
|
DMCG = self.DMCG
|
|
DMCGT = self.DMCGT
|
|
|
|
if mt['style.geomod'] == 0:
|
|
ASeff = NSend * ( DMCG + DMCI - DMCGT) * Weff + NSint *(DMCG - DMCGT)*Weff
|
|
PSeff = NSend * ( 2*(DMCG + DMCI - DMCGT) + Weff) + NSint * 2*(DMCG - DMCGT)
|
|
ADeff = NDend * ( DMCG + DMCI - DMCGT) * Weff + NDint *(DMCG - DMCGT)*Weff
|
|
PDeff = NDend * ( 2*(DMCG + DMCI - DMCGT) + Weff) + NDint * 2*(DMCG - DMCGT)
|
|
elif mt['style.geomod'] == 1:
|
|
ASeff = NSend * (DMCG + DMCI - DMCGT) * Weff + NSint *(DMCG - DMCGT)*Weff
|
|
PSeff = NSend * ( 2*(DMCG + DMCI - DMCGT) + Weff) + NSint *2*(DMCG - DMCGT)
|
|
ADeff = NDend * (DMCG - DMCGT)*Weff + NDint *(DMCG - DMCGT)*Weff
|
|
PDeff = NDend * 2*(DMCG - DMCGT) + NDint *2*(DMCG - DMCGT)
|
|
elif mt['style.geomod'] == 2:
|
|
ASeff = NSend * (DMCG - DMCGT) * Weff + NSint *(DMCG - DMCGT)*Weff
|
|
PSeff = NSend * 2 * (DMCG - DMCGT) + NSint *2*(DMCG - DMCGT)
|
|
ADeff = NDend * (DMCG + DMCI - DMCGT) * Weff + NDint *(DMCG - DMCGT)*Weff
|
|
PDeff = NDend * ( 2*(DMCG + DMCI - DMCGT) + Weff) + NDint *2*(DMCG - DMCGT)
|
|
elif mt['style.geomod'] == 3:
|
|
ASeff = NSend *(DMCG - DMCGT) * Weff + NSint *(DMCG - DMCGT)*Weff
|
|
PSeff = NSend *2*(DMCG - DMCGT) + NSint *2*(DMCG - DMCGT)
|
|
ADeff = NDend *(DMCG + DMCI - DMCGT) * Weff + NDint *(DMCG - DMCGT)*Weff
|
|
PDeff = NDend * ( 2*(DMCG + DMCI - DMCGT) + Weff) + NDint *2*(DMCG - DMCGT)
|
|
elif mt['style.geomod'] == 4:
|
|
ASeff = NSend * (DMCG + DMCI - DMCGT) * Weff + NSint *(DMCG - DMCGT)*Weff
|
|
PSeff = NSend * ( 2*(DMCG + DMCI - DMCGT) * Weff) + NSint *2*(DMCG - DMCGT)
|
|
ADeff = NDend * (DMDG - DMCGT)*Weff + NDint *(DMCG - DMCGT)*Weff
|
|
PDeff = NDend * 2*(DMDG - DMCGT) + NDint *2*(DMCG - DMCGT)
|
|
elif mt['style.geomod'] == 5:
|
|
ASeff = NSend *(DMCG - DMCGT)*Weff + NSint *(DMCG - DMCGT)*Weff
|
|
PSeff = NSend * 2 *(DMCG - DMCGT) + NSint*2*(DMCG - DMCGT)
|
|
ADeff = NDend *(DMDG - DMCGT) * Weff + NDint *(DMCG - DMCGT)*Weff
|
|
PDeff = NDend * ( 2*(DMDG - DMCGT) * Weff) + NDint*2*(DMCG - DMCGT)
|
|
elif mt['style.geomod'] == 5:
|
|
ASeff = NSend *(DMDG - DMCGT)*Weff + NSint *(DMDG - DMCGT)*Weff
|
|
PSeff = NSend *2*(DMCG - DMCGT) + NSint*2*(DMCG - DMCGT)
|
|
ADeff = NDend *(DMCG + DMCI - DMCGT) * Weff + NDint *(DMCG - DMCGT)*Weff
|
|
PDeff = NDend * ( 2*(DMCG + DMCI - DMCGT) * Weff) + NDint*2*(DMCG - DMCGT)
|
|
elif mt['style.geomod'] == 6:
|
|
ASeff = NSend *(DMDG - DMCGT)*Weff + NSint *(DMDG - DMCGT)*Weff
|
|
PSeff = NSend *2*(DMCG - DMCGT) + NSint*2*(DMCG - DMCGT)
|
|
ADeff = NDend *(DMCG + DMCI - DMCGT) * Weff + NDint *(DMCG - DMCGT)*Weff
|
|
PDeff = NDend * ( 2*(DMCG + DMCI - DMCGT) * Weff) + NDint*2*(DMCG - DMCGT)
|
|
elif mt['style.geomod'] == 7:
|
|
ASeff = NSend *(DMDG - DMCGT)*Weff + NSint *(DMCG - DMCGT)*Weff
|
|
PSeff = NSend *2*(DMDG - DMCGT) + NSint*2*(DMCG - DMCGT)
|
|
ADeff = NDend *(DMCG - DMCGT)*Weff + NDint *(DMCG - DMCGT)*Weff
|
|
PDeff = NDend *2*(DMCG - DMCGT) + NDint*2*(DMCG - DMCGT)
|
|
elif mt['style.geomod'] == 8:
|
|
ASeff = NSend *(DMDG - DMCGT)*Weff + NSint *(DMCG - DMCGT)*Weff
|
|
PSeff = NSend *2*(DMDG - DMCGT) + NSint*2*(DMCG - DMCGT)
|
|
ADeff = NDend *(DMDG - DMCGT)*Weff + NDint *(DMCG - DMCGT)*Weff
|
|
PDeff = NDend *2*(DMDG - DMCGT) + NDint*2*(DMCG - DMCGT)
|
|
elif mt['style.geomod'] == 9:
|
|
ASeff = (DMCG + DMCI - DMCGT) * Weff + (NF-1) *(DMCG - DMCGT)*Weff
|
|
PSeff = (2*(DMCG + DMCI - DMCGT) + Weff) + (NF-1)*2*(DMCG - DMCGT)
|
|
ADeff = NF *(DMCG - DMCGT)*Weff
|
|
PDeff = NF* 2*(DMCG - DMCGT)
|
|
elif mt['style.geomod'] == 10:
|
|
ASeff = NF *(DMCG - DMCGT)*Weff
|
|
PSeff = NF* 2*(DMCG - DMCGT)
|
|
ADeff = (DMCG + DMCI - DMCGT) * Weff + (NF-1) *(DMCG - DMCGT)*Weff
|
|
PDeff = (2*(DMCG + DMCI - DMCGT) + Weff) + (NF-1)*2*(DMCG - DMCGT)
|
|
else:
|
|
raise Error( 3, 'Stack.computeLayoutParasitics(): Unknown GEOMOD %s for meta-transistor \"%s\".'
|
|
% ( mt['style.geomod'], mt['gate'].getName() ) )
|
|
|
|
mt['parasitics.ASeff'] = ASeff
|
|
mt['parasitics.PSeff'] = PSeff
|
|
mt['parasitics.ADeff'] = ADeff
|
|
mt['parasitics.PDeff'] = PDeff
|
|
|
|
return
|