Creating a Cell from an Existing GDSII file

Result

There are cases when you want to integrate an existing old design into a new Ipkiss design, but where only the GDSII output is available. Ipkiss can wrap this GDSII file into an Ipkiss cell, and then you can manually add the ports, netlist and model so you can use it into a larger circuit.

This sample illustrates how to import an existing GDSII file into a PCell, and assign ports and a circuit model to it.

Note that all the layers used in the GDSII must first be defined in the Technology file. This may not always be the case. To solve this we create a new technology file based on an existing tech file and add the missing layers.

Import GDSII

Fig. 62 Importing a from GDSII

Illustrates

  1. Import a GDSII file into an IPKISS component
  2. Assign a CapheModel to the imported component
  3. Create a new technology and add the missing layers so you can import them.

Files (see: samples/ipkiss3/samples/gdsii_import)

There is file contained in this sample.

  • execute.py : Creates a component writes it to gdsii and imports it back in
  • __init___.py : A custom technology file.
  • execute2.py : Imports a component with layers that are not in the standard technology

How to run this example

To run the first example, run ‘execute.py’. To run the second example, run ‘execute2.py’.

Importing a cell and adding a Caphemodel to it.

# This sample illustrates how to import a Cell from an existing GDSII file and assign ports and a circuit model to it. 
# We first write a fiber coupler to gdsii an import it again. 
# Note that all the layers used in the gdsii must be defined in the Technology file. 

# 1. Import the technology

from technologies import silicon_photonics
from ipkiss3 import all as i3
from ipkiss3.pcell.gdscell import GDSCell
import numpy as np
import pylab as plt

# 2. Create a GDSII file with a component (fiber coupler)
from picazzo3.fibcoup.curved.cell import FiberCouplerCurvedGrating # Here we use a fiber_coupler from Picazzo
my_grating = FiberCouplerCurvedGrating(name="unique_grating_name_used_in_GDSII")  # We give a unique name to the cell that will be used in the gdsii file.
my_grating_layout = my_grating.Layout()
my_grating_layout_ports = my_grating_layout.ports
my_grating_layout.visualize()
my_grating_layout.write_gdsii("my_grating.gds")

# 3. Define a cell that imports the written GDSII, and:
#  - Assign it ports
#  - Assign it a Caphe S-matrix model

class ImportedGrating(GDSCell):

    def _default_filename(self):
        return 'my_grating.gds' # path to the gdsii file that contains the cell to be imported

    def _default_cell_name(self):
        return 'unique_grating_name_used_in_GDSII' # name of the cell to be imported inside the gdsii file. 

    class Layout(GDSCell.Layout):

        def _generate_ports(self, ports):
            ports += i3.OpticalPort(name='in', position=(20.0, 0.0), angle=0.0) # We have to manually set the ports as this info is not in the gdsii file yet
            ports += i3.VerticalOpticalPort(name="vertical_in",
                                            position=(0.0,0.0),
                                            inclination=90.0, 
                                            angle=0.0)  # For the fiber a vertical port is used. 

            return ports


    # Here we write a text book caphemodel. The netlist is automatically inferred from the layout. 
    class CapheModel(i3.CapheSModelView):
        
        center_wavelength = i3.PositiveNumberProperty(default=1.55, doc="center wavelength [um]")
        bandwidth_3dB = i3.PositiveNumberProperty(default=0.060, doc="3dB bandwidth [um]")
        peak_transmission = i3.NonNegativeNumberProperty(default=1.0, doc="peak transmission (0 to 1)")
        reflection = i3.ComplexFractionProperty(default=0.0, doc="Complex reflection back into the waveguide")
        
        
        def _calculate_S(self, environment, term1, term2, mode1, mode2):        
            if term1.name=="vertical_in" and term2.name=="in" or term2.name=="vertical_in" and term1.name=="in":
                sigma = self.bandwidth_3dB / 2.35482 # fwhm => sigma
                power_S = self.peak_transmission * np.exp(-(environment.wavelength - self.center_wavelength)**2.0 / (2.0 * (sigma**2.0)))
                return np.sqrt(power_S) # power=>amplitude 

            elif term1.name=="in" and term2.name=="in":
                return self.reflection

            else:                
                return 0.0        



# Instantiate the imported cell with its Layout view
im_cell = ImportedGrating()
im_layout = im_cell.Layout()
im_layout.visualize()

# Instantiate the CapheModel view and do a simulation
wavelengths = np.arange(1.5, 1.6, 0.001)
im_caphemodel = im_cell.CapheModel(bandwidth_3dB=0.1, peak_transmission=0.6)
S = im_caphemodel.get_smatrix(wavelengths=wavelengths)
plt.plot(wavelengths, S['in','vertical_in']**2)
plt.show()

Adding the missing layers to a tech file

# technology example which starts from the si_photonics.picazzo.default technology in ipkiss.
# As the layers existing in your GDSII files may not be in the silicon_photonics TECH - we will add
# them to the technology here. Each layer needs to be a ProcessPurpose layer. 
# Here we will add two layers that use the 1 process. 
# In the long run it is best to create your own TECH file for your process. silicon_photonics is a good example but Luceda can do it for you as service.

from technologies.silicon_photonics import TECH

TECH.name = "CUSTOMIZED TECHNOLOGY SAMPLE"


from ipkiss.process import ProcessLayer, ProcessPurposeLayer, PatternPurpose
from ipkiss.technology.technology import TechnologyTree
from ipkiss.visualisation.display_style import DisplayStyle, DisplayStyleSet
from ipkiss.visualisation import color


# We create 1 new process and 2 new purposes and add them to the TECH tree
new_process = ProcessLayer(name="NEW_PROCESS", extension="NEWP")
new_purpose_1 = PatternPurpose(name="PATTERN_1", extension="PAT_1")
new_purpose_2 = PatternPurpose(name="PATTERN_2", extension="PAT_2")


TECH.PROCESS.NEW_PROCESS = new_process
TECH.PPLAYER.NEW_PROCESS = TechnologyTree()

# We combine the process and purposes into PPlayers and add them to the TECH tree. 
TECH.PPLAYER.NEW_PROCESS.PP1 = ProcessPurposeLayer(process=new_process,
                                                   purpose=new_purpose_1, 
                                                   name="PP1")


TECH.PPLAYER.NEW_PROCESS.PP2 = ProcessPurposeLayer(process=new_process,
                                                   purpose=new_purpose_2, 
                                                   name="PP2")

# add the layer to the GDSII import/export rules. These are the GDSII process and purpose numbers used in your existing gdsii files. 
gdsii_maps = {(new_process, new_purpose_1) : (99, 1),
              (new_process, new_purpose_2) : (99, 2)}


TECH.GDSII.LAYERTABLE.update(gdsii_maps)


# add the layer to the visualization styleset
DISPLAY_PP1 = DisplayStyle(color = color.COLOR_YELLOW, alpha = 0.5, edgewidth = 1.0)
DISPLAY_PP2 = DisplayStyle(color = color.COLOR_YELLOW, alpha = 0.2, edgewidth = 1.0)

TECH.DISPLAY.DEFAULT_DISPLAY_STYLE_SET.append((TECH.PPLAYER.NEW_PROCESS.PP1, DISPLAY_PP1))
TECH.DISPLAY.DEFAULT_DISPLAY_STYLE_SET.append((TECH.PPLAYER.NEW_PROCESS.PP2, DISPLAY_PP2))


                                                                              

Importing an cell with gdsii layers in the new tech file

# This sample illustrates how to import a Cell from an existing GDSII file
# Since all of the layers used in the GDSII file are present in silicon_photonics TECH.
# we append it the technology with the layers we want. 
# Appending an existing TECH is easier than creating TECH file from nothing. Nevertheless you may want to do that 
# as this is the most stable option in the long run. 

# 1. Import the appended technology

import mytech
from ipkiss3 import all as i3
from ipkiss3.pcell.gdscell import GDSCell
import numpy as np
import pylab as plt


# 2. Define a cell that imports the written GDSII, and:
#  - add ports

class ImportedGrating(GDSCell):

    def _default_filename(self):
        return 'my_grating_with_new_layers.gds' # path to the gdsii file that contains the cell to be imported

    def _default_cell_name(self):
        return 'unique_grating_name_used_in_GDSII' # name of the cell to be imported inside the gdsii file. 

    class Layout(GDSCell.Layout):

        def _generate_ports(self, ports):
            ports += i3.OpticalPort(name='in', 
                                    position=(20.0, 0.0), 
                                    angle=0.0) # We have to manually set the ports as this info is not in the gdsii file yet
            ports += i3.VerticalOpticalPort(name="vertical_in",
                                            position=(0.0,0.0),
                                            inclination=90.0, 
                                            angle=0.0)  # For the fiber a vertical port is used. 

            return ports

       

# Instantiate the imported cell with its Layout view and write it back to GDSII.
im_cell = ImportedGrating(name="my_grating")
im_layout = im_cell.Layout()
im_layout.visualize()
im_layout.write_gdsii("my_grating_with_new_layers_rewritten.gds")