Technology

Every IPKISS script starts with the import of a TECHNOLOGY file. The goal of the technology file is to describe very general settings that are stable across several designs that use the same fab. Although you are free to add whatever you want to a TECHNOLOGY file the following aspects of your design process are usually described by it.

  • Metrics: Contains the general settings for the grid on which shapes are discretized.
  • Process settings: Contains all the available process layers, drawing pattern purposes, and combinations thereof: ProcessPurposeLayers
  • GDSII settings: Contains GDSII import and export settings.
  • Display settings: Contains settings that are used in visualization routines of IPKISS.
  • Design rules: Contains default design values for different process layers, which are used throughout IPKISS such as, minimum width, default width, default bend radius.
  • Materials: Materials and materials stacks
  • Virtual fabrication settings: Contains the description of the virtual fabrication processes that can transform a GDSII mask in a virtually fabricated structure.
  • Traces: Contains the description of the default waveguides that are used.
  • Grating Couplers: Contains the description of the default grating couplers that are used.

In this guide we will illustrate these concepts so that you can build you own technology file.

General structure of a TECH-tree

A technology in IPKISS is built as a TREE of objects of TechnologyTree

TechnologyTree

Fig. 79 TechnologyTree

As depicted in the figure above each purple box is object of the TechnologyTree that can contain settings of any kind (depicted in green). Creating your own TECH amounts to building up such a tree and to place the relevant settings in the leaves.

The code excerpt below illustrates this:

from ipkiss.technology import get_technology
from ipkiss.technology.technology import TechnologyTree

TECH = get_technology()
TECH.MYSETTINGS = TechnologyTree()
TECH.MYSETTINGS.SET1 = 10.0
TECH.MYSETTINGS.MYFIRSTSUBJECT = TechnologyTree()
TECH.MYSETTINGS.MYFIRSTSUBJECT.SET1 = "Hello"
TECH.MYSETTINGS.MYFIRSTSUBJECT.SET2 = 300.0


print TECH.MYSETTINGS.MYFIRSTSUBJECT.SET1

Tech settings can be any python object. In a lot of cases, you will want to store Ipkiss objects in the tech tree, e.g. default waveguides. For that purpose, you will import a module, defining some Ipkiss class, which in its turn uses default values from the tech tree, creating a circular dependency. These circular dependencies can be managed by using a DelayedInitTechnologyTree.

from ipkiss.technology import get_technology
from ipkiss.technology.technology import DelayedInitTechnologyTree

TECH = get_technology()

class MySettingsTree(DelayedInitTechnologyTree):
    def initialize(self):
        from mymodule import MyClass
        self.MYOBJ = MyClass()
        # now the technology tree can be fully imported at the start
        # of the main script, even if mymodule uses TECH settings

TECH.MYSETTINGS = MySettingsTree()

The initialize routine will only be called when TECH.MYSETTINGS.MYOBJ will be called. This mechanism can avoid many cases of circular dependencies.

Name of the technology

The first required step is to import the base TECH object from ipkiss.techonolgy and give it a name.


from ipkiss.technology import TECHNOLOGY as TECH # Import the base technology from IPKISS. 

__all__ = ["TECH"]

TECH.name = "SILICON_PHOTONICS" # Give a name to the technology. (Required)

# Import all the other files. Each of those imports will expand the TECH tree.

from ..base_ipkiss.admin import *         # PCell name generators (Required)
from ..base_ipkiss.mask import *          # Basic mask definitions (Required)

Also the PCell name generators and basic mask definition need to be imported. It is unlikely you will need to modify those unless you want to do truly specialized things.

Metrics

Metrics are setting related to standard distance units and discretization settings. Changes here are rare, so you can perfectly copy the existing file from the silicon_photonics tech.

################
# Basic Metrics 
################

from ipkiss.technology import get_technology
from ipkiss.technology.technology import TechnologyTree

TECH = get_technology()


TECH.METRICS = TechnologyTree()
TECH.METRICS.GRID = 5E-9           # Drawing grid [m]: 5nm
TECH.METRICS.UNIT = 1E-6           # User unit [m]: um
TECH.METRICS.ANGLE_STEP = 1.0      # Angle step for curve discretization [degrees]: 1 degree
TECH.METRICS.overwrite_allowed = ["UNIT","GRID","ANGLE_STEP"]

Process

Process layers and drawing purposes define the available ProcessLayers, PatternPurpose, and combinations thereof ProcessPurposeLayer

Process Layers

A ProcessLayer describes a process module with a specific function: waveguide, fiber coupler, rib etch, implants, metalization, …

  • ProcessLayer (e.g. “waveguide patterning”) will typically consist of several process steps (e.g. “waveguide litho”, “waveguide etch”, “resist strip”, “metrology”). These steps are not defined here, only the overall process module.
  • A ProcessLayer is not the same as a drawing layer: there can be multiple drawing layers associated with one ProcessLayer, through the use of drawing pattern purposes. E.g. Process Layer WG (Waveguides) can have drawing layers such as “WG.CORE” and “WG.CLADDING”.
  • Each ProcessLayer has a name (e.g. “Waveguides”) and an mnemonic (e.g. “WG”)

The process layers are gathered in the TECH.PROCESS tree.

This is how it is done in the silicon photonics technology.

TECH.PROCESS = ProcessTechnologyTree()
TECH.PROCESS.overwrite_allowed += ['M1']
TECH.PROCESS.WG = ProcessLayer("Waveguides", "WG")
TECH.PROCESS.FC = ProcessLayer("Fiber Couplers", "FC")
TECH.PROCESS.RWG = ProcessLayer("Rib waveguides", "RWG")
TECH.PROCESS.SK = ProcessLayer("Socket (deep rib) waveguides", "SK")
TECH.PROCESS.N = ProcessLayer("N+ implant", "N")
TECH.PROCESS.P = ProcessLayer("P+ implant", "P")
TECH.PROCESS.NPLUS = ProcessLayer("N++ implant", "NPLUS")
TECH.PROCESS.PPLUS = ProcessLayer("P++ implant", "PPLUS")
TECH.PROCESS.M1 = ProcessLayer("Metal1", "M1")
TECH.PROCESS.M2 = ProcessLayer("Metal2", "M2")
TECH.PROCESS.CON = ProcessLayer("Contact", "CON") # Process used to contact the SIL layer.
TECH.PROCESS.SIL = ProcessLayer("Silicide", "SIL") # Process layer used for the silicide. 
TECH.PROCESS.V12 = ProcessLayer("Via between M1 and M2", "V12")
TECH.PROCESS.HFW = ProcessLayer("HF Windows", "HFW") # used in picazzo3.phc
TECH.PROCESS.NONE = ProcessLayer("No specific process", "NONE")

TECH.PROCESS.overwrite_allowed.remove('M1')


###################

Pattern Purposes

PatternPurpose describe different purposes for drawing on a mask, to discriminate different types of drawing that will end up on the actual mask (e.g. trenches vs holes vs lines) or different types of data which does not or not directly end up on the mask (e.g. exclusion zones, DRC waiver zones, pin recognition, device recognition, …). As a result, pattern purposes are really described with the process engineer in mind, not the designer who starts from functional devices.

  • The PatternPurpose are gathered in the TECH.PURPOSE tree
  • We define PatternPurposes in two ways: either they have a meaning related to the process, more specifically mask polarity and feature type, or they have a more functional meaning.

The default mask drawing related purposes in Ipkiss are divided according to mask polarity: dark field or light field. How that translates into the specific process depends on the lithographic resist tone (positive or negative), which is not considered here. The process engineer who defines the possible combinations between process layers and pattern purposes knows and the designer should not “care”.

  • Every PatternPurpose has a name (e.g. “Dark-field Line”) and an extension (e.g. “DFLIN”).

This is how we define for Light Field pattern in the silicon photonics tech.

TECH.PURPOSE.LF_AREA = PatternPurpose(name = "Light-Field area in Dark-Field mask", extension = "LFAREA")

TECH.PURPOSE.LF = TechnologyTree() # Subtree for Light-Field pattern purposes
TECH.PURPOSE.LF.LINE = PatternPurpose(name = "Light-Field Lines", extension = "LFLIN") # with PR: lines in the resist
TECH.PURPOSE.LF.MARKER = PatternPurpose(name = "Light-Field Markers", extension = "LFMARK") # with PR: marker lines in resist
TECH.PURPOSE.LF.POLYGON = PatternPurpose(name = "Light-field Polygons", extension = "LFPOL") # with PR: polygon pillars/islands
TECH.PURPOSE.LF.SQUARE = PatternPurpose(name = "Light-field Squares", extension = "LFSQU") # with PR: square pillar/islands
TECH.PURPOSE.LF.TEXT = PatternPurpose(name = "Light-field Text", extension = "LFTXT") # with PR: standing text in resist
TECH.PURPOSE.LF.DUMMY = PatternPurpose(name = "Light-Field Dummies", extension = "LFDUM") # with PR: dummy features in resist

Process-purpose layers

The process engineer defines the possible combinations between ProcessLayer and PatternPurposes. Some ProcessLayers may only have drawings in one PatternPurpose, whereas other ProcessLayer may have many PatternPurpose associated. Each (ProcessLayer, PatternPurpose) pair is defined as a ProcessPurposeLayer (PPLayer).

We make the translation between the process engineer and the designer using these ProcessPurposeLayer: whereas the ProcessLayers and specifically the PatternPurpose make more sense to the process engineer, we give the ProcessPurposeLayer names and descriptions which make functional sense to the designer (e.g. “waveguide core”, “fiber coupler trenches”, “exclusion zone”, “implanted area”)

TECH.PURPOSE.ERROR = PatternPurpose(name="Error", extension="ERR", doc="Errors")

##############################################################################################################
# Process-Purpose layer pairs
# These are the layers the designer should specify when using library components or drawing his own components
##############################################################################################################

TECH.PPLAYER = TechnologyTree()

# WG PPLayers: several drawing purposes for TECH.PROCESS.WG => several PPLayers
TECH.PPLAYER.WG = TechnologyTree() # we assume a DF mask
TECH.PPLAYER.WG.CORE = PPLayer(TECH.PROCESS.WG, TECH.PURPOSE.LF.LINE, name="WG_COR")  # core of the waveguide
TECH.PPLAYER.WG.CLADDING = PPLayer(TECH.PROCESS.WG, TECH.PURPOSE.LF_AREA, name="WG_CLAD") # cladding of the waveguide (etched Silicon)
TECH.PPLAYER.WG.HOLE = PPLayer(TECH.PROCESS.WG, TECH.PURPOSE.DF.POLYGON, name="WG_HOL") # hole in silicon, for e.g. photonic crystals
TECH.PPLAYER.WG.TRENCH = PPLayer(TECH.PROCESS.WG, TECH.PURPOSE.DF.LINE, name="WG_TRE") # trench in silicon, for e.g. gratings
TECH.PPLAYER.WG.TEXT = PPLayer(TECH.PROCESS.WG, TECH.PURPOSE.DF.TEXT, name="WG_TXT") # text etched in silicon

Note

This is not the same as GDSII layers and datatypes. The mapping between (ProcessLayer, PatternPurpose) pairs and GDSII (Layer, Datatype) pairs is defined in gdsii.py

Note

It is important to add all the PPlayers that are associated with a single process in the same tree. For example TECH.PPLAYER.WG should contain all the PPlayers with TECH.PROCESS.WG as process. The is required for virtual fabrication to work.

GDSII

The TECH tree contains GDSII import and export settings from IPKISS to GDSII. The settings make sure that there is a mapping between each PPlayer (ProcessLayer, PatternPurpose) in IPKISS and (Layer, Datatype) in GDSII.

Standard GDSII settings

There are some standard settings for the GDSII import. They are all represented in the silicon_photonics tech. It is usually better to keep these setting as they are.



##################################
# SETTINGS
##################################
TECH.GDSII.STRNAME_CHARACTER_DICT = {" -./" : "_"} # Dict of charactrer combinations that are substituted
TECH.GDSII.STRNAME_ALLOWED_CHARACTERS = string.ascii_letters + string.digits + '_$' # list of allowed characters

TECH.GDSII.MAX_COORDINATES = 200
TECH.GDSII.MAX_PATH_LENGTH = 100
TECH.GDSII.MAX_VERTEX_COUNT = 4000
TECH.GDSII.MAX_NAME_LENGTH = 255 

##################################

GDSII layer tables

TECH.GDSII.LAYERTABLE: maps (ProcessLayer, PatternPurpose) and GDSII (Layer, Datatype) pairs Only these layers are exported/imported. Others are ignored upon export, or an error is raised upon import.

GenericGdsiiPPLayerOutputMap and GenericGdsiiPPLayerInputMap are used to achieve this mapping (usually the same map is used).

In the silicon photonics tech this is done like so:


##################################
# LAYER MAP
##################################

TECH.GDSII.LAYERTABLE = {
    # (ProcessLayer, PatternPurpose) : (GDSIILayer, GDSIIDatatype)
    # WG
    (TECH.PROCESS.WG    , TECH.PURPOSE.LF.LINE    ) : (4, 2),    # core
    (TECH.PROCESS.WG    , TECH.PURPOSE.LF_AREA    ) : (4, 9),    # cladding
    (TECH.PROCESS.WG    , TECH.PURPOSE.DF_AREA    ) : (4, 10),   # DF area (LF mask compatibility) 
    (TECH.PROCESS.WG    , TECH.PURPOSE.DF.POLYGON ) : (4, 12),   # holes
    (TECH.PROCESS.WG    , TECH.PURPOSE.DF.LINE    ) : (4, 13),   # trenches
    (TECH.PROCESS.WG    , TECH.PURPOSE.DF.TEXT    ) : (4, 15),

filters

Filters are functions that are that are applied upon conversion from IPKISS to GDSII. There is a wide range of filters and you can define your own. This is advanced functionality - please contact us if you think you would need them we would be happy to assist.

Display Settings

IPKISS contains several visualization functions such as visualize. The display settings and their association with the ProcessPurposeLayer for visualization is defined in the TECH.DISPLAY tree. The display settings are set in an object of the DisplayStyle. Each ProcessPurposeLayer that needs to be visualized can be then be associated with a DisplayStyle to form a DisplayStyleSet.

The DisplayStyleSet is then added to TECH.DISPLAY.DEFAULT_DISPLAY_STYLE_SET

Here is a simplified example - the same technique is used in the silicon photonics technology.

from ipkiss.technology import get_technology
from ipkiss.technology.technology import DelayedInitTechnologyTree, TechnologyTree
from ipkiss.visualisation.display_style.display_style import DisplayStyle, DisplayStyleSet
from ipkiss.visualisation.display_style import color

TECH = get_technology()

class TechDisplayTree(DelayedInitTechnologyTree):
    def initialize(self):
        from ipkiss.process import PPLayer

        self.PREDEFINED_STYLE_SETS = TechnologyTree()

        # colorful purpose map
        DISPLAY_WG = DisplayStyle(color = color.COLOR_GREEN, alpha = 0.5, edgewidth = 1.0)
        DISPLAY_WHITE = DisplayStyle(color=color.COLOR_WHITE, alpha=1.0, edgewidth=1.0)
        DISPLAY_IGNORE = DisplayStyle(color=color.COLOR_WHITE, alpha=0.0, edgewidth=0.0)
        DISPLAY_DOC = DisplayStyle(color=color.COLOR_RED, edgecolor=color.COLOR_RED, alpha=0.2, edgewidth=1.0)

        style_set = DisplayStyleSet()
        style_set.background = DISPLAY_WHITE

        style_set += [(TECH.PPLAYER.WG.CORE, DISPLAY_WG),
                      (TECH.PPLAYER.WG.CLADDING, DISPLAY_IGNORE),
                      (TECH.PPLAYER.NONE.DOC, DISPLAY_DOC),
                     ]

        self.PREDEFINED_STYLE_SETS.PURPOSE_HIGHLIGHT  = style_set
        self.DEFAULT_DISPLAY_STYLE_SET = style_set

TECH.DISPLAY = TechDisplayTree()

Design Rules

Many components in PICAZZO are built based on high level design rules. These used as defaults as to ensure sensible defaults in PICAZZO but could be useful for your own components as well.

These are the rules present in the silicon_photonics technology. They are needed for PICAZZO.

############################################################################################
# RULES
# 
# This file contains default settings which are used throughout Ipkiss and Picazzo
# - Some global settings
# - Per process layer settings, like minimum width, default width, default bend radius, ....
############################################################################################

from ipkiss.technology import get_technology
from ipkiss.technology.technology import TechnologyTree, DelayedInitTechnologyTree
from ipkiss.geometry.shapes.basic import ShapeRectangle

TECH = get_technology()

# Overall rules
# At the moment, minimum line and space are specified on a global level, not per layer.
TECH.TECH = TechnologyTree()
TECH.TECH.MINIMUM_LINE = 0.120 # Shortest possible line
TECH.TECH.MINIMUM_SPACE = 0.120 # Shortest space possible 


# WG SETTINGS
#############
TECH.WG = TechnologyTree()
TECH.WG.WIRE_WIDTH = 0.45 # default Wire waveguide width (core)
TECH.WG.TRENCH_WIDTH = 2.0 # default Trench waveguide width (from side core to end cladding)
TECH.WG.CLADDING_WIDTH = TECH.WG.WIRE_WIDTH + 2 * TECH.WG.TRENCH_WIDTH # default cladding width

TECH.WG.BEND_RADIUS = 5.0 # Default bend radius
TECH.WG.SPACING = 2.0 # Default wg spacing (core-center to core-center) 
TECH.WG.DC_SPACING = TECH.WG.WIRE_WIDTH + 0.18 # Default wg spacing (core-center to core-center) for directional couplers
TECH.WG.SHORT_STRAIGHT = 2.0 # Default length of a short straight (used in PICAZZO)
TECH.WG.SHORT_TRANSITION_LENGTH = 5.0 # Default length of a short transition
TECH.WG.OVERLAP_EXTENSION = 0.020 # Compatibility with Picazzo in Ipkiss 2 - overlaps between waveguides, used in some places
TECH.WG.OVERLAP_TRENCH = 0.010 # # Compatibility with Picazzo in Ipkiss 2 - overlaps between waveguide trenches, used in some places
TECH.WG.EXPANDED_WIDTH = 0.8 # Standard width of expanded waveguides (used in AWGS and IOFibcoup)
TECH.WG.EXPANDED_TAPER_LENGTH = 3.0 # Standard taper length of expanded waveguides (used in AWGS and IOFibcoup)
TECH.WG.EXPANDED_STRAIGHT = 5.0 #Default length of a straight for an expanded waveguide (used in PICAZZO) 
TECH.WG.ANGLE_STEP = 1.0 # Angle step for bends
TECH.WG.SLOT_WIDTH = 0.15 # Width of the slot for slotted waveguides
TECH.WG.SLOTTED_WIRE_WIDTH = 0.7 # Width of the wire fore slot waveguides
# backward compatibility
TECH.WG.SHORT_TAPER_LENGTH = TECH.WG.SHORT_TRANSITION_LENGTH

# Properties for socket waveguide
TECH.SK = TechnologyTree()
TECH.SK.WIRE_WIDTH = 0.45
TECH.SK.TRENCH_WIDTH = 3.0
TECH.SK.CLADDING_WIDTH = 2 * TECH.SK.TRENCH_WIDTH + TECH.SK.WIRE_WIDTH

# Properties for rib waveguides

TECH.RWG = TechnologyTree()
TECH.RWG.RIB_WIDTH = 0.45
TECH.RWG.STRIP_WIDTH = 1.0
TECH.RWG.CLADDING_WIDTH = 2 * 4.0 + TECH.RWG.STRIP_WIDTH
TECH.RWG.RIBWG_TO_WIREWG_AUTO_TAPER_LENGTH = 20.0
TECH.RWG.RIBWG_TO_WIREWG_AUTO_TRANSITION_LENGTH = TECH.RWG.RIBWG_TO_WIREWG_AUTO_TAPER_LENGTH # Used in picazzo3.

# Default metals properties

TECH.METAL = TechnologyTree()
TECH.METAL.LINE_WIDTH = 0.5 # line width of metal wire
TECH.METAL.DEFAULT_PROCESS = TECH.PROCESS.M1 # default process for metals

# Default via settings (Required for using picazzo VIAs.)

TECH.METAL.VIA = TechnologyTree()
TECH.METAL.VIA.SHAPE = ShapeRectangle(box_size=(10,10)) 

TECH.VIAS = TechnologyTree()



class ViaPCellTree(DelayedInitTechnologyTree):
    def initialize(self):
        self._set_pcell()        
        
    def __init__(self,via_tree, **kwargs):
        super(ViaPCellTree, self).__init__(**kwargs)
        self._via_tree = via_tree

    def _get_via_class(self):
        import importlib
        module = importlib.import_module(self._via_tree._get_via_via_lib_cell()[0])
        MyClass = getattr(module, self._via_tree._get_via_via_lib_cell()[1])        
        
        return MyClass
    
    def _set_pcell(self):
        
        cell = self._get_via_class()()
        via_tree = self.__getattr__(key="_via_tree", no_init=True)

        layout = cell.Layout()
    
        self.DEFAULT_VIA = cell 
    

TECH.VIAS.DEFAULT = TechnologyTree()    
TECH.VIAS.DEFAULT.TOP_SHAPE = ShapeRectangle(box_size=(0.6,0.6)) # Default shape for the generic via shapes. 
TECH.VIAS.DEFAULT.VIA_SHAPE = ShapeRectangle(box_size=(0.5,0.5)) # Default shape for the generic via shapes. 
TECH.VIAS.DEFAULT.BOTTOM_SHAPE = ShapeRectangle(box_size=(0.6,0.6)) # Default shape for the generic via shapes.

##### V12 (From M1 to M2)  #######

TECH.VIAS.V12 = TechnologyTree()
TECH.VIAS.V12.N_O_SIDES = 4        
TECH.VIAS.V12.M1_INCLUSION = 0.5 #M1
TECH.VIAS.V12.M2_INCLUSION = TECH.VIAS.V12.M1_INCLUSION #M2
       
TECH.VIAS.V12.VIA_WIDTH = 0.5
TECH.VIAS.V12.M1_WIDTH =  TECH.VIAS.V12.VIA_WIDTH + 2 * TECH.VIAS.V12.M1_INCLUSION #M1
TECH.VIAS.V12.M2_WIDTH =  TECH.VIAS.V12.VIA_WIDTH + 2 * TECH.VIAS.V12.M2_INCLUSION #M2

class V12PCellTree(DelayedInitTechnologyTree):
    
    def initialize(self):
        
        from picazzo3.electrical.contact import Via12
        cell = Via12()
        cell.Layout()
        self.DEFAULT_VIA = cell
        
TECH.VIAS.V12.PCELLS = V12PCellTree()


##### ContactHole (From SIL to M1)  #######

TECH.VIAS.CONTACT_HOLE  = TechnologyTree()
TECH.VIAS.CONTACT_HOLE.N_O_SIDES = 4        
TECH.VIAS.CONTACT_HOLE.SIL_INCLUSION = 0.2 # SIL
TECH.VIAS.CONTACT_HOLE.M1_INCLUSION = TECH.VIAS.CONTACT_HOLE.SIL_INCLUSION #M1
       
TECH.VIAS.CONTACT_HOLE.VIA_WIDTH = 0.2
TECH.VIAS.CONTACT_HOLE.SIL_WIDTH =  TECH.VIAS.CONTACT_HOLE.VIA_WIDTH + 2 * TECH.VIAS.CONTACT_HOLE.SIL_INCLUSION #SIL
TECH.VIAS.CONTACT_HOLE.M1_WIDTH =  TECH.VIAS.CONTACT_HOLE.VIA_WIDTH + 2 * TECH.VIAS.CONTACT_HOLE.M1_INCLUSION #M1

class ContactHoleTree(DelayedInitTechnologyTree):
    
    def initialize(self):
        
        from picazzo3.electrical.contact import ContactHole
        cell = ContactHole()
        cell.Layout()
        self.DEFAULT_VIA = cell
        
TECH.VIAS.CONTACT_HOLE.PCELLS = ContactHoleTree()   
        

Materials

Materials and their settings are added using two trees in the TECH tree.

TECH.MATERIALS

Here actual physical materials are defined. This is done using the class Material that are added to a MaterialFactory. Each material is associate with a DisplayStyle and with a boolean that determines if it is a solid or not.

from .colors import *

TECH.overwrite_allowed.append('MATERIALS')
TECH.MATERIALS = MaterialFactory()

TECH.MATERIALS.AIR = Material(name="air", display_style=DisplayStyle(color=COLOR_BABY_BLUE), solid=False)
TECH.MATERIALS.SILICON = Material(name="silicon", display_style=DisplayStyle(color=COLOR_UGLY_PINK))
TECH.MATERIALS.SILICON_OXIDE = Material(name="silicon oxide", display_style=DisplayStyle(color=COLOR_DIAPER_BLUE))
TECH.MATERIALS.GERMANIUM = Material(name="germanium", display_style=DisplayStyle(color=COLOR_DARK_GREEN))

TECH.MATERIAL_STACKS

Here actual stack of materials are defined. Material stacks are build by a sequence of a material with a height. These stacks are then used for virtual fabrication. In analogy with Materials, you create MaterialStack that are added to a MaterialStackFactory. Each MaterialStack is associate with a DisplayStyle.

Note

All the material stacks in a single factory must have the same height.


from pysics.basics.material.material_stack import MaterialStack, MaterialStackFactory

TECH.overwrite_allowed.append('MATERIAL_STACKS')
TECH.MATERIAL_STACKS = MaterialStackFactory()

MSTACK_SOI_SILICON_OXIDE_HEIGHT = 0.500

TECH.MATERIAL_STACKS.MSTACK_SOI_AIR = MaterialStack(name="Air", 
                                                    materials_heights=[(TECH.MATERIALS.SILICON_OXIDE, MSTACK_SOI_SILICON_OXIDE_HEIGHT),
                                                                       (TECH.MATERIALS.AIR, 1.220)], 
                                                    display_style=DisplayStyle(color=COLOR_DIAPER_BLUE))

TECH.MATERIAL_STACKS.MSTACK_SOI_SI_100nm = MaterialStack(name="100nm Si", 
                                                         materials_heights=[(TECH.MATERIALS.SILICON_OXIDE,MSTACK_SOI_SILICON_OXIDE_HEIGHT),
                                                                            (TECH.MATERIALS.SILICON, 0.100),
                                                                            (TECH.MATERIALS.AIR, 1.120)], 
                                                        display_style = DisplayStyle(color=COLOR_LIGHT_UGLY_PINK))

TECH.MATERIAL_STACKS.MSTACK_SOI_SI_150nm = MaterialStack(name="150nm Si", 
                                                         materials_heights=[(TECH.MATERIALS.SILICON_OXIDE, MSTACK_SOI_SILICON_OXIDE_HEIGHT),
                                                                            (TECH.MATERIALS.SILICON, 0.150),
                                                                            (TECH.MATERIALS.AIR, 1.070)], 
                                                         display_style=DisplayStyle(color=COLOR_UGLY_PINK))

TECH.MATERIAL_STACKS.MSTACK_SOI_SI_220nm = MaterialStack(name="220nm Si", 
                                                         materials_heights=[(TECH.MATERIALS.SILICON_OXIDE, MSTACK_SOI_SILICON_OXIDE_HEIGHT),
                                                                            (TECH.MATERIALS.SILICON, 0.220),
                                                                            (TECH.MATERIALS.AIR, 1.0)], 
                                                         display_style=DisplayStyle(color=COLOR_DARK_UGLY_PINK))

# Defining attributes for materials and material stacks

Defining attribute of Materials and Materials Stacks.

To be used in physical engines other attributes can be added to material and material stacks. By default only the permittivity is added to each materials and the effective permittivity to each material stack. You are free to add your own attributes to use them in your own engines.


TECH.MATERIALS.SILICON.epsilon = 12

TECH.MATERIALS.SILICON_OXIDE.epsilon = 2.3104

TECH.MATERIALS.AIR.epsilon = 1

TECH.MATERIAL_STACKS.MSTACK_SOI_SI_100nm.effective_index_epsilon = 1.936**2 

TECH.MATERIAL_STACKS.MSTACK_SOI_SI_150nm.effective_index_epsilon = 2.539**2

TECH.MATERIAL_STACKS.MSTACK_SOI_SI_220nm.effective_index_epsilon =  2.844**2

TECH.MATERIAL_STACKS.MSTACK_SOI_AIR.effective_index_epsilon = 1.0

Virtual Fabrication

Virtual fabrication generates 3D representation of a component starting from a Layout. This is done by transforming a Layout constituted of elements (ProcessPurposeLayer on a shape) to a series of non-overlapping shapes that are associated with a material stack.

Two settings in the TECH tree determine how this transformation in done.

TECH.PPLAYER.(#PROCESS_NAME#).ALL

All the PPlayers associated with a process need to be combined to a mask that tells if a process is either 1 or 0 at a specific point. PPlayers are combined to generate this mask through a set of boolean operations between the PPlayers in the process. Assume for instance that we have a layout made of 3 PPlayers two of which are associated with Process2

PPLayers

Fig. 81 Layout of the structure to be virtually fabricated

For each process (#PROCESS_NAME#).ALL must be added to the PPLAYER tree.

TECH.PPLAYER.P1.ALL = TECH.PPLAYER.P1.PO1
TECH.PPLAYER.P1.ALL.name = "P1_ALL"
TECH.PPLAYER.P2.ALL = TECH.PPLAYER.P2.PO1 ^ TECH.PPLAYER.P2.PO2  # (XOR)
TECH.PPLAYER.P2.ALL.name = "P2_ALL"

The boolean operations defined there will be used to create a virtual mask for each process. In this particular case a XOR operation is done between P01 and P02

VFab

Fig. 82 Mechanism used for the virtual fabrication of the masks.

TECH.VFABRICATION.PROCESS_FLOW

The masks of each process are then combined to create material stacks. In a VFabricationProcessFlow the different possible combinations of processes must be associated with a MaterialStack.

TECH.VFABRICATION = TechnologyTree()
TECH.VFABRICATION.PROCESS_FLOW = VFabricationProcessFlow(active_processes = [TECH.PPLAYER.P1, TECH.PPLAYER.P2],
                                                         process_to_material_stack_map = [
                                                          ((0, 0), TECH.MATERIAL_STACKS.STACK_0),
                                                          ((0, 1), TECH.MATERIAL_STACKS.STACK_1),
                                                          ((1, 0), TECH.MATERIAL_STACKS.STACK_1),
                                                          ((1, 1), TECH.MATERIAL_STACKS.STACK_2),
                                                          ])

As illustrated below the masks are then combined to create virtually fabricated structure.

VFab

Fig. 83 Mechanism used for the virtual fabrication.

Our samples contain an executable example on how to define a virtual fabrication and how visualize it or use it for cross sections. It’s a good start to experiment with it to get a better understanding about its inner behavior. You can find it at samples/technology/virtual_fabrication_example.py or download it here

# This is an advanced example on how to build Virtual Fabrication Process Flows.
# It's meant for PDK and library builders who want to include this process information
# in their technology. This way users of the PDK can with little effort export their
# components to 3rd party solvers.

import ipkiss3.all as i3
from pysics.basics.material.material import Material
from pysics.basics.material.material_stack import MaterialStack
from ipkiss.plugins.vfabrication.process_flow import VFabricationProcessFlow
from ipkiss.visualisation.display_style import DisplayStyle
from ipkiss.visualisation import color

dummy_mat = Material(
    name="dummy0_mat",
    epsilon=1,
    display_style=DisplayStyle(
        color=color.COLOR_SANGRIA
    )
)

# We'll have 4 material stacks
dummy_mstack0 = MaterialStack(
    name="dummy0",
    materials_heights=[
        (dummy_mat, 1.)
    ],
    display_style=DisplayStyle(
        color=color.COLOR_CHERRY
    )
)

dummy_mstack1 = MaterialStack(
    name="dummy1",
    materials_heights=[
        (dummy_mat, 2.)
    ],
    display_style=DisplayStyle(
        color=color.COLOR_DARK_GREEN
    )
)

dummy_mstack2 = MaterialStack(
    name="dummy2",
    materials_heights=[
        (dummy_mat, 3.)
    ],
    display_style=DisplayStyle(
        color=color.COLOR_GRAY
    )
)

dummy_mstack3 = MaterialStack(
    name="dummy3",
    materials_heights=[
        (dummy_mat, 0.5)
    ],
    display_style=DisplayStyle(
        color=color.COLOR_MAGENTA
    )
)


# We'll use 3 Layers in our test structure
LAY0 = i3.Layer(0)
LAY1 = i3.Layer(1)
LAY2 = i3.Layer(2)

# and our technology uses 3 processes
PROCESS1 = i3.ProcessLayer(extension='ext1', name='dummyproc1')
PROCESS2 = i3.ProcessLayer(extension='ext2', name='dummyproc2')
PROCESS3 = i3.ProcessLayer(extension='ext3', name='dummyproc3')

vfab_flow = VFabricationProcessFlow(
    active_processes=[PROCESS1, PROCESS2, PROCESS3],
    process_layer_map={
        # any of LAY0, LAY1 or LAY2 :
        PROCESS1: LAY0 | LAY1 | LAY2,
        # everwhere LAY1 or LAY2 :
        PROCESS2: (LAY1 & LAY2) | (LAY1  & LAY0) | (LAY2 & LAY0),
        # only when all layers are present:
        PROCESS3: LAY0 & LAY1 & LAY2,
    },
    process_to_material_stack_map=[
        ((0, 0, 0), dummy_mstack2),
        ((1, 0, 0), dummy_mstack0), # mstack 0 where only 1 layer is present
        ((1, 1, 0), dummy_mstack1), # mstack 1 where any combination of 2 layers is present
        ((1, 1, 1), dummy_mstack3), # mstack 3 where all layers are present
    ],
    is_lf_fabrication={
        PROCESS1: False,
        PROCESS2: False,
        PROCESS3: False,
    }
)

# We make a Venn - Diagram like teststructure
lay = i3.LayoutCell().Layout(
    elements=[
        i3.Circle(layer=LAY0, center=(-3, 0), radius=5.),
        i3.Circle(layer=LAY1, center=(3, 0), radius=5.),
        i3.Circle(layer=LAY2, center=(0, 5), radius=5.),
    ]
)

# We visualize the layers
lay.visualize()

# visualize_2d displays a top down view of the fabricated layout
lay.visualize_2d(vfabrication_process_flow=vfab_flow)

# we can also calculate and visualize the cross_section
lay.cross_section(i3.Shape([(-9, 3), (9, 3)]), process_flow=vfab_flow).visualize()
../../_images/virtual_fabrication_example_00.png
../../_images/virtual_fabrication_example_01.png
../../_images/virtual_fabrication_example_02.png

Traces

Contains the default settings for drawing traces such as waveguides and electrical traces. Three different trees need to be built.

TECH.TRACE

Contains the default settings for traces at the highest level. Those are often overridden by more specific settings.


# TRACES (Default settings for a default trace)
class TechTraceTree(DelayedInitTechnologyTree):
    def initialize(self):
        from ipkiss.primitives.layer import Layer
        self.DEFAULT_LAYER = Layer(0)
        self.CONTROL_SHAPE_LAYER = PPLayer(TECH.PROCESS.NONE, TECH.PURPOSE.TRACE)
        self.BEND_RADIUS = 5.0
        self.DRAW_CONTROL_SHAPE = False

TECH.TRACE = TechTraceTree()

TECH.PCELLS.WG

Contains the default waveguide template.


# Default waveguide template

class TechWgTree(DelayedInitTechnologyTree):
    def initialize(self):
        from picazzo3.traces.wire_wg.trace import WireWaveguideTemplate

        self.WIRE = WireWaveguideTemplate(name="WIRE_WG_TEMPLATE",
                                          library=TECH.PCELLS.LIB)
        self.WIRE.Layout(core_width=TECH.WG.WIRE_WIDTH,
                         cladding_width=TECH.WG.CLADDING_WIDTH,
                         core_process=TECH.PROCESS.WG
                         )

        self.WIRE.CircuitModel(n_eff=2.86,
                               n_g=2.86,
                               center_wavelength=1.55,
                               loss_dB_m=0.0) # Default values


        from picazzo3.traces.rib_wg.trace import RibWaveguideTemplate, RibWireWaveguideTemplate

        self.RIB = RibWaveguideTemplate(name="RIB_WG_TEMPLATE",
                                        library=TECH.PCELLS.LIB)
        self.RIB.Layout(core_width=TECH.RWG.RIB_WIDTH,
                        cladding_width=TECH.RWG.CLADDING_WIDTH,
                        core_process=TECH.PROCESS.RWG
                         )

        self.RIB.CircuitModel(n_eff=2.86,
                              n_g=2.86,
                              center_wavelength=1.55,
                              loss_dB_m=0.0) # Default values


        self.RIBWIRE = RibWireWaveguideTemplate(name="RIBWIRE_WG_TEMPLATE",
                                                library=TECH.PCELLS.LIB)
        self.RIBWIRE.Layout(core_width=TECH.RWG.RIB_WIDTH,
                            strip_width=TECH.RWG.STRIP_WIDTH,
                            cladding_width=TECH.RWG.CLADDING_WIDTH,
                            core_process=TECH.PROCESS.RWG,
                            strip_process=TECH.PROCESS.WG
                            )

        self.RIBWIRE.CircuitModel(n_eff=2.86,
                                  n_g=2.86,
                                  center_wavelength=1.55,
                                  loss_dB_m=0.0) # Default values


        self.DEFAULT = self.WIRE

TECH.PCELLS.WG = TechWgTree()

TECH.PCELLS.METAL

Contains the default electrical wire template.


# Default templates for electrical wires.

class TechWireTree(DelayedInitTechnologyTree):
    def initialize(self):
        from picazzo3.traces.electrical_wire import ElectricalWireTemplate
        tpl = ElectricalWireTemplate(name="DEFAULT_WIRE_TEMPLATE",
                                     library=TECH.PCELLS.LIB)
        self.WIRE = tpl
        self.DEFAULT = self.WIRE


TECH.PCELLS.METAL = TechWireTree()

Grating Couplers

Here the grating coupler that are used in PICAZZO are defined. This is required to adapt the default fiber couplers used by IOFibcoup.

####################################################################
# DEFAULT FIBER COUPLER
####################################################################


from ipkiss.technology import get_technology
from ipkiss.technology.technology import TechnologyTree, DelayedInitTechnologyTree
from ipkiss.log import IPKISS_LOG as LOG
from numpy import floor

TECH = get_technology()
TECH.IO = TechnologyTree()
TECH.IO.DEFAULT_FIBCOUP_SPACING=25.0 # Default spacing between fibercouplers for IOFibCoup



###########################################################
# Straight Gratings
###########################################################

class TechFibCoupTreeStraight(DelayedInitTechnologyTree):
    
    def initialize(self):
        self.SOCKET = TechnologyTree()
        self.SOCKET.LENGTH = 50.0
        self.SOCKET.WIDTH = 10.0

        from picazzo3.traces.wire_wg import WireWaveguideTemplate
        wide_trace_template = WireWaveguideTemplate()
        wide_trace_template.Layout(core_width=10.0, cladding_width=14.0) 
        
        self.SOCKET.TRACE_TEMPLATE = wide_trace_template # Wide trace template.
        
        #TE Grating
        self.GRATING_TE = TechnologyTree()
        self.GRATING_TE.N_O_LINES = 25 # Default number of lines used.
        self.GRATING_TE.PERIOD = 0.63 # Default period used in the grating.
        self.GRATING_TE.LINE_WIDTH = 0.315 # Default linewidth used in the grating.
        self.GRATING_TE.BOX_WIDTH = 14.0 # Default box width 
        
        #TM Grating
        self.GRATING_TM = TechnologyTree()
        self.GRATING_TM.N_O_LINES = 16 # Default number of lines used.
        self.GRATING_TM.PERIOD = 1.080 # Default period used in the grating.
        self.GRATING_TM.LINE_WIDTH = 0.540 # Default linewidth used in the grating.
        self.GRATING_TM.BOX_WIDTH = 14.0   # Default box width 
        
        
        # default
        self.GRATING = self.GRATING_TE
       
        
class TechFibcoupStraightCellsTree(DelayedInitTechnologyTree):
    def initialize(self):
        ## standard gratings 1550nm
        def STANDARD_GRATING_1550_TE():
            from picazzo3.fibcoup.uniform import UniformLineGrating as _ULG
            from picazzo3.traces.wire_wg import WireWaveguideTemplate
            t_tree = TECH.IO.FIBCOUP.STRAIGHT
            wg_t = WireWaveguideTemplate(name="STD_FIBCOUP_SOCKET_WG_TE_T")
            G = _ULG(name="std_grating_1550",
                     trace_template=t_tree.SOCKET.TRACE_TEMPLATE, 
                     library=TECH.PCELLS.LIB
                     )
            G.Layout(origin=(0.0,0.0),
                     period=t_tree.GRATING.PERIOD, 
                     line_width=t_tree.GRATING_TE.LINE_WIDTH,
                     line_length=t_tree.GRATING_TE.BOX_WIDTH,
                     n_o_periods=t_tree.GRATING_TE.N_O_LINES)   
            TECH.PCELLS.LIB.add(G.dependencies()) #make sure that the child cells are also added to this lib
            return G

        ### standard gratings 1550nm TM polarization
        def STANDARD_GRATING_1550_TM():
            from picazzo3.fibcoup.uniform import UniformLineGrating as _ULG
            from picazzo3.traces.wire_wg import WireWaveguideTemplate
            std1550_grating_trench = 0.540
            std1550_grating_period = 1.080
            std1550_grating_n_o_periods = 16
            wg_t = WireWaveguideTemplate(name="STD_FIBCOUP_SOCKET_WG_TM_T")
            wg_t.Layout(core_width=10.0,
                        cladding_width=14.0)
            G = _ULG(name="std_grating_1550_tm",
                     trace_template=wg_t, 
                     library=TECH.PCELLS.LIB
                     )
            G.Layout(origin=(0.0,0.0),
                     period=std1550_grating_period, 
                     line_width=std1550_grating_trench, 
                     n_o_periods=std1550_grating_n_o_periods)   
            TECH.PCELLS.LIB.add(G.dependencies()) #make sure that the child cells are also added to this lib
            return G

        
        self.DEFAULT_GRATING_TE = STANDARD_GRATING_1550_TE()
        self.DEFAULT_GRATING_TM = STANDARD_GRATING_1550_TM()
        self.DEFAULT_GRATING = self.DEFAULT_GRATING_TE


###########################################################
# Curved Gratings
###########################################################

class TechFibcoupTreeCurved(DelayedInitTechnologyTree):
    
    def initialize(self):
        # Socket 
        self.SOCKET=TechnologyTree()
        self.SOCKET.LENGTH = 20.0
        self.SOCKET.STRAIGHT_EXTENSION = (0.0, 0.05) # Default straight extentions used for the socket taper. 
        self.SOCKET.MARGIN_FROM_GRATING = TECH.WG.SHORT_STRAIGHT # Distance between the last grating line and the end of the socket by default
        self.SOCKET.START_TRACE_TEMPLATE = TECH.PCELLS.WG.DEFAULT 
        self.SOCKET.TRANSITION_LENGTH = 5.0
        
        from picazzo3.traces.wire_wg import WireWaveguideTemplate
        wide_trace_template = WireWaveguideTemplate(name="LinearTransitionSocket_WGT")
        wide_trace_template.Layout(core_width=17.0, cladding_width=2 * TECH.WG.TRENCH_WIDTH + 17.0) 
        
        self.SOCKET.WIDE_TRACE_TEMPLATE = wide_trace_template # Wide trace template.
        
        # Grating
        
        self.GRATING = TechnologyTree()
        self.GRATING.PERIOD = 0.63 # Default period used in the grating.
        self.GRATING.FOCAL_DISTANCE = 20.0 # Default focal distance of curved gratings. 
        self.GRATING.BOX_WIDTH = 15.5 # Default box width        
        self.GRATING.N_O_LINES = int(floor(self.GRATING.BOX_WIDTH  / self.GRATING.PERIOD ))
        self.GRATING.START_RADIUS = self.GRATING.FOCAL_DISTANCE  - self.GRATING.BOX_WIDTH / 2.0  # Default first radius of the grating.
       
        self.GRATING.ANGLE_SPAN = 90 # Default angle span of a curved grating when it is not boxed.
        
        
class TechFibcoupCurvedCellsTree(DelayedInitTechnologyTree):
    
    def initialize(self):
        
        from picazzo3.fibcoup.curved import FiberCouplerCurvedGrating
        from picazzo3.traces.wire_wg import WireWaveguideTemplate

        # Creating the wire templates for the socket.
        t_tree = TECH.IO.FIBCOUP.CURVED
        fc_cell = FiberCouplerCurvedGrating(start_trace_template=t_tree.SOCKET.START_TRACE_TEMPLATE,
                                            wide_trace_template=t_tree.SOCKET.WIDE_TRACE_TEMPLATE,
                                            library=TECH.PCELLS.LIB)
        fc_cell.Layout(period_x=t_tree.GRATING.PERIOD, # We with a period of 0.8
                       focal_distance_x=t_tree.GRATING.FOCAL_DISTANCE,# We use a focal distance of 20.0
                       #n_o_lines=t_tree.GRATING.N_O_LINES,# We set the number of lines to 30
                       #min_x=t_tree.GRATING.START_RADIUS,
                       box_width=t_tree.GRATING.BOX_WIDTH,# All the lines will be contained between -20 and 20
                       socket_straight_extension=t_tree.SOCKET.STRAIGHT_EXTENSION)
        TECH.PCELLS.LIB.add(fc_cell.dependencies()) #make sure that the child cells are also added to this lib
        
        self.DEFAULT_GRATING = fc_cell




TECH.IO.FIBCOUP = TechnologyTree()
TECH.IO.FIBCOUP.CURVED = TechFibcoupTreeCurved()
TECH.IO.FIBCOUP.CURVED.PCELLS = TechFibcoupCurvedCellsTree()
TECH.IO.FIBCOUP.STRAIGHT = TechFibCoupTreeStraight()
TECH.IO.FIBCOUP.STRAIGHT.PCELLS = TechFibcoupStraightCellsTree()
TECH.IO.FIBCOUP.DEFAULT = TECH.IO.FIBCOUP.STRAIGHT


class TechAdapterTree(DelayedInitTechnologyTree):
    def initialize(self):
        self.IOFIBCOUP = TechnologyTree()
        self.IOFIBCOUP.FANOUT_LENGTH = 40.0
        self.IOFIBCOUP.S_BEND_ANGLE = 60.0
        self.IOFIBCOUP.CONNECT_TRANSITION_LENGTH = None # automatic
        self.IOFIBCOUP.FIBER_COUPLER_TRANSITION_LENGTH = None # automatic
        
        self.DEFAULT_ADAPTER = self.IOFIBCOUP

class TechIoFibcoupAdapterPCellTree(DelayedInitTechnologyTree):
    def initialize(self):
        from picazzo3.container.iofibcoup import IoFibcoup
        self.ADAPTER = IoFibcoup



TECH.IO.ADAPTER = TechAdapterTree()
TECH.IO.ADAPTER.IOFIBCOUP.PCELLS = TechIoFibcoupAdapterPCellTree()