# -*- coding: utf-8 -*-

import copy
import datetime
from   ..Hurricane  import DataBase, UpdateSession, DbU, Box, Net, \
                           Horizontal, Vertical, Contact, Pad,     \
                           NetExternalComponents
from   ..CRL        import AllianceFramework
from   ..           import Constant
from   ..helpers.io import ErrorMessage as Error
from   ..helpers    import setTraceLevel, trace
from   ..Analog     import Device
from   .            import getRules, rules, dtr, toUnity, adjustOnGrid

#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    = rules.getRealLayer( 'active' )
    metal1    = rules.getRealLayer( 'metal1' )
    metal2    = rules.getRealLayer( 'metal2' )
    metal3    = rules.getRealLayer( 'metal3' )
    cut0      = rules.getRealLayer( 'cut0' )
    cut1      = rules.getRealLayer( 'cut1' )
    cut2      = 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 = 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               = adjustOnGrid(device.getW() // device.getM())
    self.L               = 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 = rules.getRealLayer( 'nImplant' )
      self.bImplantLayer = rules.getRealLayer( 'pImplant' )
      self.wellLayer     = None
    else:
      self.tImplantLayer = rules.getRealLayer( 'pImplant' )
      self.bImplantLayer = rules.getRealLayer( 'nImplant' )
      self.wellLayer     = 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  = toUnity(   self.sideActiveWidth 
                          - self.L//2
                          - self.metal1ToGate
                          - self.eDiffMetal1Width//2 )
    self.DMCG  = toUnity( (self.gatePitch - self.L)//2 )
    self.DMCGT = 0
    self.DGG   = toUnity(  self.gatePitch - self.L )
    self.DGI   = 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  = rules.getRealLayer( 'metal2' )
      metal3  = 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      = 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    = rules.getRealLayer( 'poly' )
    cut0    = rules.getRealLayer( 'cut0' )
    cut1    = rules.getRealLayer( 'cut1' )
    cut2    = rules.getRealLayer( 'cut2' )
    metal1  = rules.getRealLayer( 'metal1' )
    metal2  = rules.getRealLayer( 'metal2' )
    metal3  = 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  = rules.getRealLayer( 'metal1' )
    metal2  = rules.getRealLayer( 'metal2' )
    metal3  = rules.getRealLayer( 'metal3' )
    cut0    = rules.getRealLayer( 'cut0' )
    cut1    = rules.getRealLayer( 'cut1' )
    cut2    = 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 = 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            = 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  = 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