Пример #1
0
    def test_tlmextraction(self):

        # path = '/home/connor/Documents/Stanford_Projects/Extractions/src/SampleData/FETExampleData/nano_patterning.csv'
        # idvg_path = '/home/connor/Documents/Stanford_Projects/Extractions/fetextraction/SemiPy/SampleData/TLMExampleData'
        idvg_path = '/home/connor/Documents/Stanford_Projects/Extractions/SemiPy/SampleData/TLMExampleDataShort'
        # idvg_path = '/home/connor/Documents/Stanford_Projects/Extractions/fetextraction/SemiPy/SampleData/TLMExampleDataShort'

        # idvd_path = '/home/connor/Documents/Stanford_Projects/Extractions/fetextraction/SemiPy/SampleData/FETExampleData/WSe2_Sample_4_Id_Vd.txt'
        # idvg_path = '/home/connor/Documents/Stanford_Projects/Extractions/fetextraction/SemiPy/SampleData/FETExampleData/WSe2_Sample_4_Id_Vg.txt'
        widths = Value(4.0, ureg.micrometer)
        # lengths = Value.array_like(np.array([0.5, 1.0, 2.0, 2.5, 3.0, 3.5]), unit=ureg.micrometer)
        lengths = Value.array_like(np.array([1.0, 0.5, 2.0]),
                                   unit=ureg.micrometer)
        tox = Value(90, ureg.nanometer)

        result = TLMExtractor(widths=widths,
                              lengths=lengths,
                              tox=tox,
                              epiox=3.9,
                              device_polarity='n',
                              idvg_path=idvg_path,
                              vd_values=[1.0, 2.0])

        result.save_tlm_plots()

        a = 5
Пример #2
0
    def test_ambiFET(self):
        idvd_path = get_abs_semipy_path(
            'SampleData/FETExampleData/WSe2_Sample_4_Id_Vd.txt')
        idvg_path = get_abs_semipy_path(
            'SampleData/FETExampleData/WSe2_Sample_4_Id_Vg.txt')

        gate_oxide = SiO2(thickness=Value(30, ureg.nanometer))
        channel = MoS2(layer_number=1)
        substrate = Silicon()

        fet = ambiTFT(gate_oxide=gate_oxide,
                      channel=channel,
                      width=Value(1, ureg.micrometer),
                      substrate=substrate,
                      length=Value(1, ureg.micrometer))

        result = FETExtractor(FET=fet,
                              idvg_path=idvg_path,
                              idvd_path=idvd_path)
        print("~~~~~~TFT Extracted Values~~~~~~")
        print("~~~~~~n-type~~~~~~")
        print(result.FET.NBranch.Vt_avg)
        print(result.FET.NBranch.Vt_fwd)
        print(result.FET.NBranch.Vt_bwd)
        print(result.FET.NBranch.min_ss)
        print("~~~~~~p-type~~~~~~")
        print(result.FET.PBranch.Vt_avg)
        print(result.FET.PBranch.Vt_fwd)
        print(result.FET.PBranch.Vt_bwd)
        print(result.FET.PBranch.min_ss)
Пример #3
0
class Al2O3(MetalOxide):

    bandgap = matprop.Bulk.Electrical.BandGap(
        value=Value(7.5, unit=ureg.electron_volt))

    relative_permittivity = matprop.Bulk.Electrical.RelativePermittivity(
        value=Value(8.5, ureg.dimensionless))
Пример #4
0
    def test_value(self):

        a = np.array([1, 2, 3, 4])
        b = Value.array_like(array=a, unit=ureg.amp)
        print(b[0])
        a = np.array([[1, 2, 3, 4], [1, 2, 3, 4]])
        b = Value.array_like(array=a, unit=ureg.amp)
        print(b[0][0])
        f = Field(field='voltage', name='nothing')
        print(f)
        a = Value(value=3.0,
                  unit=ureg.amp,
                  name='input_voltage',
                  tf_shape=(None, 1))
        c = 1 / a
        array = np.array([a, a, a], dtype=object)
        b = Value(value=2.0, unit=ureg.volt * ureg.volt, name='input_current')
        d = ureg.amp * array
        r = array * b
        f = b * array
        boo = r == f
        tmep = 1.0 * ureg.volt
        f = 2.0 * ureg.microvolt
        temp = tmep + f
        temp = tmep.to_reduced_units()
        d = a * 2.0
        f = 2.0 * a
        c = 1.0
        r = 1.0 * ureg.volt
        t = ureg.volt * 1.0
        d = a == b
        r = a.tensor
        d = 5
Пример #5
0
    def test_igzo(self):
        # This test models an a-IGZO device with 20nm IGZO, 100nm SiO2 on Si substrate.
        # Documentation notes:
        # 1. Add material and copy material properties
        # 2. Set model in unittest
        # 3. Files

        # TODO: Change filepaths to point correctly
        idvd_path = get_abs_semipy_path(
            'SampleData/FETExampleData/WSe2_Sample_4_Id_Vd.txt')
        idvg_path = get_abs_semipy_path(
            'SampleData/FETExampleData/WSe2_Sample_4_Id_Vg.txt')

        gate_oxide = SiO2(thickness=Value(100, ureg.nanometer))
        channel = aIGZO(thickness=Value(20, ureg.nanometer))
        substrate = Silicon()

        fet = pTFT(gate_oxide=gate_oxide,
                   channel=channel,
                   width=Value(180, ureg.micrometer),
                   substrate=substrate,
                   length=Value(30, ureg.micrometer))

        result = FETExtractor(FET=fet,
                              idvg_path=idvg_path,
                              idvd_path=idvd_path)
        print(result.FET.max_gm)
        print(result.FET.max_mobility)
        print(result.FET.Vt_avg)
        print(result.FET.min_ss)
Пример #6
0
    def compute_diffusion_current(self, ambient_temp, vgs, vd):
        # Capacitance Values
        c_Q = self.compute_quantum_cap(ambient_temp, vgs)
        # self.c_Q = Value(0, ureg.meter ** -2 * ureg.coulomb / ureg.volt)
        c_i = self.cox_t
        # c_it = electron_charge_C * Value(1e16, ureg.meter ** -2 / ureg.volt)
        c_it = Value(0, ureg.meter**-2 * ureg.coulomb / ureg.volt)
        cap_r = 1 + (c_Q + c_it) / c_i

        #Mobility
        mobility_temp = Value(295, ureg.kelvin)
        T = ambient_temp
        mobility = self.compute_mobility_temperature(
            self.FET.max_mobility, mobility_temp,
            T).adjust_unit(ureg.meter**2 / ureg.second / ureg.volt)

        #Compute Diffusion Current
        # i_diff = (mobility * (self.c_Q + self.c_it) * (self.FET.width / self.FET.length) * (self.vth ** 2) * (1-math.exp(-vd/self.vth)) * math.exp((vgs-self.FET.Vt_avg)/(self.vth * self.cap_r))).adjust_unit(ureg.ampere)

        I_diff_term_1 = math.log(
            math.exp((vgs - self.FET.Vt_avg) / (self.vth * cap_r)) + 1)
        I_diff_term_2 = math.log(
            math.exp((vgs - self.FET.Vt_avg - vd) / (self.vth * cap_r)) + 1)

        i_diff = (self.vth * electron_charge_C * mobility *
                  (self.FET.width * self.NDOS / self.FET.length) *
                  (I_diff_term_1 - I_diff_term_2)).base_units()

        print("Diffusion Current is {0} at Vg = {1}".format(i_diff, vgs))

        return i_diff
Пример #7
0
    def __check_properties(length, width):
        # make sure given properties are values
        if not isinstance(length, Value):
            warnings.warn(
                'Given length is not a value. Assuming units are micrometers.')
            length = Value(value=length, unit=ureg.micrometer)
        else:
            assert length.unit.dimensionality == ureg.meter.dimensionality, 'Your length is not given in meters, but {0}.'.format(
                length.unit.dimensionality)

        if not isinstance(width, Value):
            warnings.warn(
                'Given width is not a value. Assuming units are micrometers.')
            width = Value(value=width, unit=ureg.micrometer)
        else:
            assert width.unit.dimensionality == ureg.meter.dimensionality, 'Your length is not given in meters, but {0}.'.format(
                width.unit.dimensionality)

        # if not isinstance(tox, Value):
        #     warnings.warn('Given Tox is not a value. Assuming units are nanometers.')
        #     tox = Value(value=tox, unit=ureg.nanometer)
        # else:
        #     assert tox.unit.dimensionality == ureg.meter.dimensionality, 'Your length is not given in meters, but {0}.'.format(tox.unit.dimensionality)
        #
        # if not isinstance(epiox, Value):
        #     epiox = Value(value=epiox)

        # now return all the properties
        return length, width
    def test_2DFETExtraction_and_stanford2dsmodel(self):

        idvd_path = get_abs_semipy_path(
            'SampleData/FETExampleData/MoS2AlOx/MoS2_15AlOxALD_Id_Vd.csv')
        idvg_path = get_abs_semipy_path(
            'SampleData/FETExampleData/MoS2AlOx/MoS2_15AlOxALD_Id_Vg.txt')

        gate_oxide = SiO2(thickness=Value(30.0, ureg.nanometer))
        channel = MoS2(layer_number=1)
        substrate = Silicon()

        fet = nTFT(channel=channel,
                   gate_oxide=gate_oxide,
                   length=Value(400, ureg.nanometer),
                   width=Value(2.2, ureg.micrometer),
                   substrate=substrate)

        result = FETExtractor(FET=fet,
                              idvg_path=idvg_path,
                              idvd_path=idvd_path)

        fet = result.FET

        fet.Rc.set(Value(480.0, ureg.ohm * ureg.micrometer),
                   input_values={'n': Value(1e13, ureg.centimeter**-2)})

        fet.mobility_temperature_exponent.set(Value(0.85, ureg.dimensionless))

        fet.max_mobility.set(Value(
            35, ureg.centimeter**2 / (ureg.volt * ureg.second)),
                             input_values={
                                 'Vd': Value(1, ureg.volt),
                                 'Vg': Value(1, ureg.volt)
                             })

        S2DModel = Stanford2DSModel(FET=fet)

        Vds = ModelInput(0.0, 5.0, num=40, unit=ureg.volt)
        Vgs = ModelInput(0.0, 30.0, num=4, unit=ureg.volt)

        ambient_temperature = Value(300, ureg.kelvin)

        id = S2DModel.model_output(Vds,
                                   Vgs,
                                   heating=True,
                                   vsat=True,
                                   diffusion=False,
                                   drift=True,
                                   ambient_temperature=ambient_temperature)

        plot = IdVdPlot('IdVd')

        plot.add_idvd_dataset(result.idvd, marker='o')

        for key in id.keys():
            if 'Id' in key:
                plot.add_data(Vds.range, id[key], linewidth=4.0)

        plot.show_plot()
Пример #9
0
    def compute_metal_thermal_resistance(self):
        #using 1 for now to account for the contact resistance
        km = Value(1, ureg.watt / (ureg.meter * ureg.kelvin))
        tm = Value(50, ureg.nanometer)
        Lhm = (tm * self.FET.gate_oxide.thickness * km /
               self.FET.gate_oxide.thermal_conductivity)**.5

        return Lhm / (km * tm * (self.FET.width.base_units() + 2 * Lhm))
Пример #10
0
class aIGZO(MetalOxide):
    bandgap = matprop.Bulk.Electrical.BandGap(
        value=Value(3.2, unit=ureg.electron_volt))

    relative_permittivity = matprop.Bulk.Electrical.RelativePermittivity(
        value=Value(3.9, ureg.dimensionless))

    thermal_conductivity = matprop.Bulk.Thermal.ThermalConductivity(
        value=Value(1.4, ureg.watt / (ureg.kelvin * ureg.meter)),
        input_values={'temperature': Value(300, ureg.kelvin)})
Пример #11
0
class MoS2SiO2(BaseInterface):
    """
    The MoS2 SiO2 interface.
    """
    material1 = MoS2
    material2 = SiO2

    thermal_boundary_conductance = matprop.Interfaces.Thermal.ThermalBoundaryConductance(
        value=Value(15e6, ureg.watt / (ureg.kelvin * ureg.meter**2)),
        cited=True,
        input_values={'temperature': Value(300, ureg.kelvin)},
        citation=MoS2SiO2AlNThermalBoundaryResistance())
Пример #12
0
    def vg_to_n(self, vg):
        # adding extra values to make the units be centimeter ** -2
        try:
            # replace any Vg < Vt_avg with Vt_avg
            vg[vg < self.Vt_avg.value] = self.Vt_avg.value
            n = Value(value=1.0, unit=ureg.coulomb) * self.gate_oxide.capacitance * (vg - self.Vt_avg.value)\
                / (electron_charge_C * Value(value=1.0, unit=ureg.volt * ureg.farad))

        except Exception as e:
            assert self.Vt_avg.value is not None, \
                'You must calculate the average Vt by running FET.compute_properties() before computing the carrier density'
            raise e
        return n
Пример #13
0
    def test_fetextraction(self):

        # path = '/home/connor/Documents/Stanford_Projects/Extractions/src/SampleData/FETExampleData/nano_patterning.csv'
        idvd_path = '/home/connor/Documents/Stanford_Projects/Extractions/SemiPy/SampleData/FETExampleData/WSe2_Sample_4_Id_Vd.txt'
        idvg_path = '/home/connor/Documents/Stanford_Projects/Extractions/SemiPy/SampleData/FETExampleData/WSe2_Sample_4_Id_Vg.txt'
        #
        # idvd_path = '/home/connor/Documents/Stanford_Projects/Extractions/fetextraction/SemiPy/SampleData/FETExampleData/WSe2_Sample_4_Id_Vd.txt'
        # idvg_path = '/home/connor/Documents/Stanford_Projects/Extractions/fetextraction/SemiPy/SampleData/FETExampleData/WSe2_Sample_4_Id_Vg.txt'

        gate_oxide = SiO2(thickness=Value(30, ureg.nanometer))
        channel = MoS2(layer_number=1)

        result = FETExtractor(width=1,
                              length=1,
                              gate_oxide=gate_oxide,
                              channel=channel,
                              device_polarity='n',
                              idvg_path=idvg_path,
                              idvd_path=idvd_path)

        result.FET.publish_csv('.')

        result.save_plots()
        print(result.FET.max_gm)
        print(result.FET.min_ss)
        a = 5
Пример #14
0
class WS2(TwoDMaterial, Semiconductor):
    """
    The material MoS2.  Single layer thickness is 0.615 nm taken from <citation>.
    """
    layer_thickness = matprop.Bulk.Basic.Thickness(value=Value(0.615, ureg.nanometer))

    def __init__(self, *args, **kwargs):
        super(WS2, self).__init__(*args, **kwargs)
Пример #15
0
 def min_ss(self, _in):
     if isinstance(_in, np.ndarray):
         # remove any zeros
         _in = _in.flatten()
         zero_val = Value(0.0, _in[0].unit)
         zero_i = [i for i in range(len(_in)) if _in[i] == zero_val]
         _in = np.delete(_in, zero_i)
         _in = self.min_slope_value(_in)
     _in = _in.adjust_unit(ureg.meter * ureg.millivolt / ureg.amp)
     self._min_ss = _in
Пример #16
0
    def test_idvgdataset(self):

        path = get_abs_semipy_path(
            'SampleData/FETExampleData/WSe2_Sample_4_Id_Vg.txt')

        dataset = IdVgDataSet(data_path=path)

        result = dataset.get_column(column_name='vg_fwd',
                                    master_independent_value_range=[0, 10.0])

        self.assertEqual(
            result[0][0], Value(0.0, ureg.volt),
            'Error in the IdVgDataSet object get column function when requesting master'
            ' independent value range.  Lowest Vg value should be 0.0 volt but is {0}'
            .format(result[0][0]))

        self.assertEqual(
            result[0][-1], Value(9.5, ureg.volt),
            'Error in the IdVgDataSet object get column function when requesting master'
            ' independent value range.  Highest Vg value should be 9.5 volt but is {0}'
            .format(result[0][-1]))

        result, vd = dataset.get_column(column_name='id',
                                        return_set_values=True)

        self.assertEqual(
            result[0][-1], Value(1.921e-6, ureg.amp),
            'Error in the IdVgDataSet object get column function. Highest Id value should '
            'be 1.921e-6 amps but is {0}'.format(result[0][-1]))

        self.assertEqual(
            vd[-1], Value(2.0, ureg.volt),
            'Error in the IdVgDataSet object get column function. Highest Vd value should '
            'be 2.0 volts but is {0}'.format(result[0][-1]))

        result = dataset.get_column_set(column_name='id',
                                        secondary_value=Value(1.0, ureg.volt))

        self.assertEqual(
            len(result.shape), 1,
            'Error in the IdVgDataSet object get column set function. Result should only have '
            '1 dimension but it has {0}'.format(len(result.shape)))
Пример #17
0
class MoS2(TwoDMaterial, Semiconductor):
    """
    The material MoS2.  Single layer thickness is 0.615 nm taken from <citation>.
    """
    relative_permittivity = matprop.Bulk.Electrical.RelativePermittivity(value=Value(4.2, ureg.dimensionless))

    thermal_conductivity = matprop.Bulk.Thermal.ThermalConductivity(value=Value(90, ureg.watt/(ureg.kelvin*ureg.meter)),
                                                                    input_values={'temperature': Value(300, ureg.kelvin)},
                                                                    citation=MonolayerMoS2ThermalConductivityYan)

    saturation_velocity = matprop.Bulk.Electrical.SaturationVelocity(value=Value(3.2e6, ureg.centimeter/ureg.seconds),
                                                                     input_values={'temperature': Value(300, ureg.kelvin)})

    layer_thickness = matprop.Bulk.Basic.Thickness(value=Value(0.615, ureg.nanometer),
                                                   citation=MonolayerMoS2ThicknessDickinson)

    #ADDED
    bandgap = matprop.Bulk.Electrical.BandGap(value=Value(2.2, ureg.electron_volt))

    def __init__(self, *args, **kwargs):
        super(MoS2, self).__init__(*args, **kwargs)
Пример #18
0
    def test_metavalue(self):
        class MetaValueTest(MetaValue):
            def __init__(self, value):
                self.value = value

        # test math operations
        a = Value(1, ureg.amp)
        a_meta = MetaValueTest(Value(1, ureg.amp))
        a_n = 1
        b = Value(2, ureg.amp)
        b_meta = MetaValueTest(b)
        c = Value(3, ureg.amp)
        c_meta = MetaValueTest(c)
        d = Value(4, ureg.volt)
        d_meta = MetaValueTest(d)
        e = Value(2, ureg.ohm)
        e_meta = MetaValueTest(e)

        # test the add function
        self.assertEqual(c, b + a_meta, 'Error in MetaValue.__add__()')
        self.assertEqual(c, a_meta + b_meta, 'Error in MetaValue.__add__()')

        # test the subtract function
        self.assertEqual(c - b, a_meta, 'Error in MetaValue.__sub__()')
        self.assertEqual(c - b_meta, a_meta, 'Error in MetaValue.__sub__()')

        # test the multiply function
        self.assertEqual(d, b * e_meta, 'Error in MetaValue.__mul__()')
        self.assertEqual(a, a * a_n, 'Error in MetaValue.__mul__()')

        # test the divide function
        self.assertEqual(d / e_meta, b, 'Error in MetaValue.__truediv__()')
        self.assertEqual(d_meta / e_meta, b,
                         'Error in MetaValue.__truediv__()')
        self.assertEqual(a, a / a_n, 'Error in MetaValue.__truediv__()')
Пример #19
0
class Silicon(Semiconductor):
    """
    The semiconductor material Silicon
    """
    # relative_permittivity = matprop.Bulk.Electrical.RelativePermittivity(value=Value(4.2, ureg.dimensionless))

    thermal_conductivity = matprop.Bulk.Thermal.ThermalConductivity(value=Value(140, ureg.watt/(ureg.kelvin*ureg.meter)),
                                                                    input_values={'temperature': Value(300, ureg.kelvin)})

    # saturation_velocity = matprop.Bulk.Electrical.SaturationVelocity(value=Value(3.4e6, ureg.centimeter/ureg.seconds),
    #                                                                  input_values={'temperature': Value(300, ureg.kelvin)})

    def __init__(self, *args, **kwargs):
        super(Semiconductor, self).__init__(*args, **kwargs)
Пример #20
0
    def test_tlmextraction(self):

        # Change these filepaths to match TLM data
        idvd_path = get_abs_semipy_path(
            'SampleData/TLMExampleData/WSe2_Sample_4_Id_Vd.txt')  # unused
        idvg_path = get_abs_semipy_path('SampleData/TLMExampleData')
        widths = Value(4.0, ureg.micrometer)
        lengths = Value.array_like(np.array([0.5, 1.0, 2.0, 2.5, 3.0, 3.5]),
                                   unit=ureg.micrometer)
        #lengths = Value.array_like(np.array([1.0, 2.0, 0.5]), unit=ureg.micrometer)
        #lengths = Value.array_like(np.array([2.0, 0.5, 1.0]), unit=ureg.micrometer)

        gate_oxide = SiO2(thickness=Value(30, ureg.nanometer))
        channel = MoS2(layer_number=1)
        result = TLMExtractor(widths=widths,
                              lengths=lengths,
                              gate_oxide=gate_oxide,
                              channel=channel,
                              FET_class=nTFT,
                              idvg_path=idvg_path,
                              vd_values=[1.0, 2.0],
                              substrate=Silicon())

        result.save_tlm_plots()
Пример #21
0
    def test_transistor(self):

        # path = '/home/connor/Documents/Stanford_Projects/Extractions/src/SampleData/FETExampleData/nano_patterning.csv'
        # path = '/home/connor/Documents/Stanford_Projects/Extractions/src/SampleData/FETExampleData/WSe2_Sample_4_Id_Vd.txt'
        # path = '/home/connor/Documents/Stanford_Projects/Extractions/fetextraction/src/SampleData/FETExampleData/WSe2_Sample_4_Id_Vd.txt'
        path = '/home/connor/Documents/Stanford_Projects/Extractions/fetextraction/SemiPy/SampleData/FETExampleData/WSe2_Sample_4_Id_Vg.txt'

        dataset = IdVgDataSet(data_path=path)

        result = dataset.get_column(column_name='vg_fwd', master_independent_value_range=[0, 18.0])

        result, vd = dataset.get_column(column_name='id', return_set_values=True)

        result = dataset.get_column_set(column_name='id', secondary_value=Value(1.0, ureg.volt))

        a = 5
Пример #22
0
    def set(self, value, input_values=None):
        """
        Set the property and input values for this Device Property.
        Args:
            value (physics.Value): The Value to be set for this property
            input_values (dict): A dictionary of the input values, using the convention {'input_name': input_value}

        Returns:
            None
        """
        # save the prop value
        assert_value(value)
        assert value.unit.dimensionality == self.prop_dimensionality.dimensionality, 'Your dimensionality is incorrect for {0}. Yours is {1}, but it should be' \
                ' {2}'.format(self.prop_name, value.unit.dimensionality, self.prop_dimensionality.dimensionality)
        self.value = value

        # now if the standard units were given, then adjust the value units
        if self.prop_standard_units is not None:
            self.value = self.value.adjust_unit(self.prop_standard_units)
            # now round the value to 2 decimal places
            self.value = Value(value=round(self.value.value * 100) / 100,
                               unit=self.value.unit)

        # save the input values, making sure that they match the given names
        for i, input_name in enumerate(self.input_value_names):
            input_value = input_values.get(input_name, None)
            assert input_value is not None, 'You are missing the required {0} input for this property'.format(
                input_name)
            assert_value(input_value)
            assert input_value.unit.dimensionality == self.input_dimensionalities[i].dimensionality,\
                'Your dimensionality is incorrect for input {0}.  Yours is {1}, but it should be' \
                ' {2}'.format(input_name, input_value.unit.dimensionality, self.input_dimensionalities[i].dimensionality)

            self.input_values[input_name] = input_value

        # now go through the optional input values if there a given input values
        if input_values is not None:
            for i, optional_name in enumerate(self.optional_input_value_names):
                input_value = input_values.get(optional_name, None)
                if input_value is not None:
                    assert_value(input_value)
                    assert input_value.unit.dimensionality == self.input_dimensionalities[i].dimensionality, \
                        'Your dimensionality is incorrect for input {0}.  Yours is {1}, but it should be' \
                        ' {2}'.format(optional_name, input_value.unit.dimensionality, self.input_dimensionalities[i].dimensionality)

                    self.input_values[optional_name] = input_value
Пример #23
0
    def __init__(self,
                 min_value,
                 max_value,
                 unit,
                 num=None,
                 step=None,
                 *args,
                 **kwargs):

        assert num is not None or step is not None, 'You must give a step or num value'

        if num is not None:
            self.range = np.linspace(min_value, max_value, num)

        elif step is not None:
            self.range = np.arange(min_value, max_value, step)

        self.range = Value.array_like(self.range, unit=unit)
Пример #24
0
class TLMExtractor(Extractor):
    """
    An extractor object for Transfer Length Method (TLM) measurements.

    Args:
        length (list):  The list of the physical FET length.  Should be Values with correct units for floats in micrometers
        width (Value, float, or list): Physical width of the FET.  Should be a Value with correct units or float in micrometers.
        tox (Value or float): Physical thickness of the FET oxide.  Should be a Value with correct units or float in nanometers.
        epiox (Value or float): Dielectric constant of the oxide.  Should be a Value or float (unitless).
        device_polarity (str): The polarity of the device, either 'n' or 'p' for electron or hole, respectively.
        idvg_path (str): Path to the folder with all the IdVg data.

    Attributes:
        tlm_datasets: A dict of SemiPy.Datasets.IVDataset.TLMDataSet indexed by Vd values
        FETs: A list of all the SemiPy.Devices.FET.Transistor.FETs analyzed from the IdVg data

    Example:
        Example of how to extract TLM data from IdVg data

        >>> from physics.value import Value, ureg
        # path points to a folder with all the IdVg data
        >>> widths = Value(4.0, ureg.micrometer)
        >>> lengths = Value.array_like(np.array([1.0, 0.5, 2.0]), unit=ureg.micrometer)
        >>> tox = Value(90, ureg.nanometer)
        >>> tlm = TLMExtractor(widths=widths, lengths=lengths, tox=tox, epiox=3.9, device_polarity='n', idvg_path=path, vd_values=[1.0, 2.0])
        # save all the plots of the TLM
        >>> tlm.save_tlm_plots()
    """

    maximum_n_varience = Value(8e11, ureg.centimeter**-2)

    def __init__(self,
                 lengths,
                 widths,
                 channel,
                 gate_oxide,
                 FET_class,
                 vd_values=None,
                 idvg_path=None,
                 *args,
                 **kwargs):

        super(TLMExtractor, self).__init__()

        # now create FETExtractors for every set of IdVg data
        self.FETs = []
        self.data_dict = {'n': [], 'r': [], 'l': []}
        self.n_max = []

        self.vd_values = vd_values

        for root, dirs, idvg_data in os.walk(idvg_path):
            # make sure there are lengths for each data file
            assert len(lengths) == len(idvg_data), 'There are too many or too few channel' \
                                                   ' lengths given for the data. ({0})'.format(idvg_data)
            for i, idvg in enumerate(idvg_data):
                path = os.path.join(root, idvg)

                fet = FET_class(gate_oxide=gate_oxide,
                                channel=channel,
                                width=widths,
                                length=lengths[i],
                                *args,
                                **kwargs)
                self.FETs.append(
                    FETExtractor(fet, idvg_path=path, vd_values=vd_values))
                # self.FETs.append(FETExtractor(width=widths, length=lengths[i], epiox=epiox, tox=tox,
                #                               device_polarity=device_polarity, idvg_path=path,
                #                               vd_values=vd_values))

                new_fet_vd_values = self.FETs[
                    -1].idvg.get_secondary_indep_values()

                if self.vd_values is None:
                    self.vd_values = new_fet_vd_values

                assert new_fet_vd_values == self.vd_values,\
                    'The IdVg data at {0} for this TLM does not have consistent Vd values.' \
                    '  Make sure that all the Vd values are the same for every dataset'.format(idvg_path)

                # grab the n and resistances and create the l column
                self.data_dict['n'].append(self.FETs[-1].idvg.get_column('n'))
                self.n_max.append(np.max(self.data_dict['n'][-1], axis=-1))
                self.data_dict['r'].append(
                    self.FETs[-1].idvg.get_column('resistance'))
                self.data_dict['l'].append(
                    np.ones_like(self.data_dict['n'][-1]) *
                    self.FETs[-1].FET.length)

                # create_scatter_plot(self.FETs[-1].idvg.get_column('n')[0], self.FETs[-1].idvg.get_column('id')[-1], scale='lin', show=True, autoscale=True)

        # now lets start processing the data, first creating a TLMDataSet for each Vd value
        self.tlm_datasets = {}
        # convert to np arrays for easy indexing
        self.data_dict = {
            key: np.array(data)
            for key, data in self.data_dict.items()
        }

        # make sure there is actually data in the data_dict
        assert len(
            self.data_dict['n']
        ) != 0, 'There is no data in the file path you gave.  Make sure the path is correct.'

        for i, vd in enumerate(self.vd_values):
            new_dataset = TLMDataSet(data_path=pd.DataFrame.from_dict({
                key + '_' + str(j): self.data_dict[key][j, i, :]
                for j in range(len(lengths)) for key in ('n', 'r', 'l')
            }))
            self.tlm_datasets[vd] = new_dataset

    def save_tlm_plots(self):
        """
        Saves TLM plots for this TLM instance at all Vd values. This includes R vs. Length, Rc vs. n, Rsheet vs. n

        """
        for i, vd in enumerate(self.vd_values):

            new_dataset = self.tlm_datasets[vd]

            # now we can start computing TLM properties
            n_full = new_dataset.get_column('n')

            # get the column with the lowest max min
            min_max_n_col = np.argmin(np.max(n_full, axis=1))
            # get the min_max n value
            min_max_n = np.min(np.max(n_full, axis=1))
            # now get the min n value from the column with the min_max_n.  This is important to ensure we get a rectangular n array
            max_min_n = np.partition(
                n_full[min_max_n_col][np.where(
                    n_full[min_max_n_col, :] >= 1.0)[0]], 2)[2]
            # value = 989429999999.9993 = 9.89e+11

            # expr: np.where((n_full[1]>max_min_n) & (n_full[1]<min_max_n))
            # looks like for n_full[1] range is 29-122 with gap 68-83 for total of 78 values
            # n_full[0]: 37-114, missing 75-76 for total 76 values
            # n_full[2]: 31-120, missing 68-83 for total 76 values
            n = new_dataset.get_column('n',
                                       master_independent_value_range=[
                                           max_min_n, min_max_n.magnitude
                                       ])

            # check to make sure the n values are close enough.  If the Vg step is not fine enough and the device Vts are hihgly varied, the n values may not be close enough
            # to extract TLM data at a specific n
            max_n_varience = np.max(n[:, 1]) - np.min(n[:, 1])
            assert max_n_varience < self.maximum_n_varience, 'Varience in carrier density between the devices is {0}, which is greater than {1}.  As such,' \
                                                             'accurate TLM extraction is not possible.  This could be the result of too small a gate' \
                                                             'voltage step and high varience between device threshold voltages.  You can try to remove ' \
                                                             'device data with high varience, or lower the maximum_n_varience value in the' \
                                                             'TLMExtractor object.'.format(max_n_varience, self.maximum_n_varience)

            n_r = np.round(np.array(n, dtype=float) * 1e-12)
            r = new_dataset.get_column('r',
                                       master_independent_value_range=[
                                           max_min_n, min_max_n.magnitude
                                       ])
            l = new_dataset.get_column('l',
                                       master_independent_value_range=[
                                           max_min_n, min_max_n.magnitude
                                       ])

            n_units = '10<sup>12</sup> cm<sup>-2</sup>'
            r_units = '\u03A9\u2022\u03BCm'  # ;&times;&mu;m'
            l_units = '\u03BCm'
            # create_scatter_plot(l[:, 0], r[:, 0], scale='lin', show=True, autoscale=True)

            r_sheet, r_sheet_error, rc, rc_error = self.linear_regression(l, r)

            # now add r_sheet and rc to the dataset
            # new_dataset.add_column()
            max_l = float(max(l[:, 0]))

            r_plot = BasicPlot(x_label='length {0}'.format(l_units),
                               y_label='total resistance {0}'.format(r_units),
                               marker_size=8.0,
                               x_min=0.0,
                               x_max=max_l * 1.2)

            for i in range(0, len(n[0]), round(len(n[0]) / 5)):
                r_plot.add_data(x_data=l[:, i],
                                y_data=r[:, i],
                                mode='markers',
                                name='n = {0} {1}'.format(n_r[0][i], n_units),
                                text='n')
                r_plot.add_line(x_data=[0.0, max_l],
                                y_data=[float(rc[i]),
                                        float(r[-1, i])],
                                name=None)

            r_plot.save_plot(name='r_at_vd_{0}'.format(vd))

            rc_plot = BasicPlot(
                x_label='carrier density {0}'.format(n_units),
                y_label='contact resistance {0}'.format(r_units),
                marker_size=8.0)

            rc_plot.add_data(x_data=n[0] * 1e-12,
                             y_data=rc,
                             error_y={
                                 'type': 'data',
                                 'array': np.array(rc_error, dtype=float),
                                 'visible': True
                             },
                             mode='markers',
                             name='n',
                             text='n')

            rc_plot.save_plot(name='rc_at_vd_{0}'.format(vd))

            rsheet_plot = BasicPlot(
                x_label='carrier density {0}'.format(n_units),
                y_label='sheet resistance',
                marker_size=8.0)

            rsheet_plot.add_data(x_data=n[0] * 1e-12,
                                 y_data=r_sheet,
                                 error_y={
                                     'type': 'data',
                                     'array': np.array(r_sheet_error,
                                                       dtype=float),
                                     'visible': True
                                 },
                                 mode='markers',
                                 name='n',
                                 text='n')

            rsheet_plot.save_plot(name='rsheet_at_vd_{0}'.format(vd))
Пример #25
0
"""
import unittest
from SemiPy.Extractors.TLM.TLMExtractor import TLMExtractor
from SemiPy.Devices.Devices.FET.ThinFilmFET import nTFT
from SemiPy.Devices.Materials.Oxides.MetalOxides import SiO2
from SemiPy.Devices.Materials.Semiconductors.BulkSemiconductors import Silicon
from SemiPy.Devices.Materials.TwoDMaterials.TMD import MoS2
from SemiPy.helper.paths import get_abs_semipy_path
from physics.value import Value, ureg
import numpy as np

idvd_path = get_abs_semipy_path(
    'SampleData/TLMExampleData/WSe2_Sample_4_Id_Vd.txt')  # unused
idvg_path = get_abs_semipy_path('SampleData/TLMExampleData')

widths = Value(4.0, ureg.micrometer)
lengths = Value.array_like(np.array([0.5, 1.0, 2.0, 2.5, 3.0, 3.5]),
                           unit=ureg.micrometer)

gate_oxide = SiO2(thickness=Value(30, ureg.nanometer))
channel = MoS2(layer_number=1)
result = TLMExtractor(widths=widths,
                      lengths=lengths,
                      gate_oxide=gate_oxide,
                      channel=channel,
                      FET_class=nTFT,
                      idvg_path=idvg_path,
                      vd_values=[1.0, 2.0],
                      substrate=Silicon())

result.save_tlm_plots()
Пример #26
0
# just give fundamental constants
from physics.value import ureg, Value

free_space_permittivity_F_div_m = Value(value=8.85e-12,
                                        unit=ureg.farad / ureg.meter)
free_space_permittivity_F_div_cm = Value(value=8.85e-14,
                                         unit=ureg.farad / ureg.centimeter)

electron_charge_C = Value(value=1.602176634e-19, unit=ureg.coulomb)
Пример #27
0
    def model_output(self,
                     Vds,
                     Vgs,
                     heating=True,
                     vsat=True,
                     diffusion=True,
                     drift=True,
                     ambient_temperature=None,
                     iterations=2,
                     linestyle='-',
                     IdVd=True):
        mobility_temp = Value(295, ureg.kelvin)
        if ambient_temperature is None:
            ambient_temperature = Value(295, ureg.kelvin)
        # colors = ['C0', 'C2', 'C3']
        assert isinstance(Vds, ModelInput)
        assert isinstance(Vgs, ModelInput)

        # first calculate an initial Id value assuming room temperature (at low Vds this is a good assumption)
        # make sure the starting field is less than 0.25 V / um
        # if Vds.range[0] == 0.0:
        #     Vds.range[0] = Vds.range[1]
        # make sure the Vgs is above threshold
        # assert Vgs.range[0] > self.FET.Vt_avg, 'The Vgs values should be above the average threshold voltage {0},' \
        #                                        ' your min is {1}'.format(self.FET.Vt_avg, Vgs.range[0])
        # assert Vds.range[0] / self.FET.length <= Value(0.25, ureg.volt / ureg.micrometer),\
        #     'Your starting field should be less than 0.25 V /um, yours is {0}'.format(Vds.range[0] / self.FET.length)

        # create a holder for the data
        if IdVd:
            dataset = IdVdDataSet
            master_independent = Vds
            secondary_independent = Vgs
        else:
            dataset = IdVgDataSet
            master_independent = Vgs
            secondary_independent = Vds

        id_data = {}
        for i_vg in range(len(secondary_independent.range)):
            id_data['id({0})'.format(i_vg)] = []
            id_data['T({0})'.format(i_vg)] = []
            id_data['vsat({0})'.format(i_vg)] = []
            id_data['vgs({0})'.format(i_vg)] = []
            id_data['n({0})'.format(i_vg)] = []
            id_data['vds({0})'.format(i_vg)] = []

        # now loop through the Vgs values
        for i_vgs, vgs in enumerate(Vgs):
            # first calculate an initial Id
            prev_Id = Value(0.0, ureg.amp)
            # prev_Id = self.compute_drift_current(Vds.range[0], self.FET.max_mobility, Vgs.range[0], prev_Id)
            effective_mobility = self.FET.max_mobility
            # now loop through each Vds value
            for i_vds, vds in enumerate(Vds):

                if IdVd:
                    i_master = i_vgs
                else:
                    i_master = i_vds

                # for i in range(iterations):
                # vds = self.compute_vds_rc(vds, prev_Id)
                power = self.compute_power(vds, vgs, effective_mobility,
                                           prev_Id)
                # If self heating is turned off, then just use the ambient tempeature
                if not heating:
                    temperature = ambient_temperature
                else:
                    temperature = self.compute_temperature(
                        power, ambient_temperature)

                # compute the new mobility at this temperature
                effective_mobility = self.compute_mobility_temperature(
                    self.FET.max_mobility, mobility_temp, temperature)
                # now compute the mobility at this field

                if vsat:
                    effective_mobility, vsat = self.compute_mobility_velocity_saturation(
                        effective_mobility, vds, temperature)
                    id_data['vsat({0})'.format(i_master)].append(vsat)

                # now calculate the new current
                new_Id = Value(0.0, ureg.amp)

                if drift and vgs > self.FET.Vt_avg:
                    new_Id += self.compute_drift_current(
                        vds, effective_mobility, vgs, prev_Id).base_units()

                if diffusion:
                    new_Id += self.compute_diffusion_current(
                        temperature, vgs, vds)

                # idvd_data['Vg = {0}'.format(vgs)].append([])
                id_data['id({0})'.format(i_master)].append(new_Id /
                                                           self.FET.width)

                prev_Id = new_Id
                id_data['T({0})'.format(i_master)].append(temperature)
                id_data['n({0})'.format(i_master)].append(
                    self.FET.vg_to_n(vgs))
                id_data['vgs({0})'.format(i_master)].append(vgs)
                id_data['vds({0})'.format(i_master)].append(vds)

        return dataset(data_path=id_data)
Пример #28
0
class Stanford2DSModel(BaseModel):
    """
    Compact model for modeling traps, parasitic capacitances, velocity saturation, self-heating, and field effects of 2D FETs. The
    reference paper is <citation>, which gives full physical details on the model.
    """

    # Rtbr = Value(1e-7, ureg.meters**2*ureg.kelvin/(ureg.watt))

    # ksub = Value(150, ureg.watt/(ureg.kelvin*ureg.meter))

    eta = 5
    hwop = Value(35e-3, ureg.eV)
    k = Value(
        scipy.constants.physical_constants["Boltzmann constant in eV/K"][0],
        ureg.eV / ureg.kelvin)
    k_J = Value(scipy.constants.physical_constants["Boltzmann constant"][0],
                ureg.J / ureg.kelvin)
    hcross = Value(
        scipy.constants.physical_constants["reduced Planck constant"][0],
        ureg.J * ureg.seconds)

    # Constants for Quantum Capacitance Calculation that need to be defined
    WFTG = Value(4.3, ureg.volt)  # Top Gate Work Function [V]
    WFBG = Value(4.3, ureg.volt)  # Bottom Gate Work Function [V]
    VFBT = Value(0.305, ureg.volt)  # Top Gate Cutoff voltage[V]
    VFBB = Value(0.305, ureg.volt)  # Bottom Gate Cutoff voltage[V]

    # WFTG = 4.3
    # WFBG = 4.3
    # VFBT = 0.305
    # VFBB =0.305

    def __init__(self, FET, *args, **kwargs):

        super(Stanford2DSModel, self).__init__(*args, **kwargs)

        assert isinstance(
            FET, TFT
        ), 'The Stanford 2DS Model only works with Thin Film transistors.  Yours is {0}'.format(
            type(FET))

        self.FET = FET

        # get the gate_oxide-channel interface
        self.gate_oxide_channel_interface = import_interface(
            self.FET.gate_oxide, self.FET.channel)

        self.Rtbr = 1 / self.gate_oxide_channel_interface.thermal_boundary_conductance

        self.thermal_conductance = self.compute_device_thermal_conductance()

        self.thermal_healing_length = self.compute_thermal_healing_length()

        self.vO = self.compute_v0()

    def compute_metal_thermal_resistance(self):
        #using 1 for now to account for the contact resistance
        km = Value(1, ureg.watt / (ureg.meter * ureg.kelvin))
        tm = Value(50, ureg.nanometer)
        Lhm = (tm * self.FET.gate_oxide.thickness * km /
               self.FET.gate_oxide.thermal_conductivity)**.5

        return Lhm / (km * tm * (self.FET.width.base_units() + 2 * Lhm))

    def compute_thermal_healing_length(self):

        lh = (self.FET.channel.thermal_conductivity *
              self.FET.channel.thickness *
              (self.FET.width / self.thermal_conductance + self.Rtbr))**0.5
        lh_compact = lh.compact_units()
        return lh.compact_units()

        # return Value(100, ureg.nanometers)

    def compute_power(self, Vds, Vgs, mobility, Id):
        Vds = self.compute_vds_rc(Vds, Vgs, mobility)

        return Id * Vds

    def compute_device_thermal_conductance(self):
        weff = self.FET.width + 2 * self.FET.gate_oxide.thickness

        # computing the inverse of the thermal conductance
        inv_g_1 = self.Rtbr / self.FET.width.base_units()
        inv_g_2 = ((pi * self.FET.gate_oxide.thermal_conductivity) /
                   log(6 * (self.FET.gate_oxide.thickness.base_units() /
                            self.FET.width.base_units() + 1)) +
                   self.FET.gate_oxide.thermal_conductivity *
                   self.FET.width.base_units() /
                   self.FET.gate_oxide.thickness.base_units())**-1
        inv_g_3 = (1 / (self.FET.substrate.thermal_conductivity * 2) *
                   (self.FET.length / weff)**0.5)

        g = (inv_g_1 + inv_g_2 + inv_g_3)**(-1)

        return g

    def compute_vds_rc(self, Vds, Vgs, mobility):
        if self.FET.Rc is not None:
            # Vds = Vds - 2 * previous_Id * self.FET.Rc / self.FET.width
            rch = self.compute_channel_resistance(self.FET.vg_to_n(Vgs),
                                                  mobility)

            Vds = Vds * rch / (rch + 2 * self.FET.Rc)

        return Vds

    def compute_channel_resistance(self, n, mobility):
        rsh = compute_sheet_resistance(n, mobility)
        rch = rsh * self.FET.length
        return rch

    def compute_drift_current(self, Vds, mobility, Vgs, previous_Id):

        # Vds = self.compute_vds_rc(Vds, previous_Id)
        n = self.FET.vg_to_n(Vgs)
        rch = self.compute_channel_resistance(n, mobility)

        Vds = Vds * rch / (rch + 2 * self.FET.Rc)

        return electron_charge_C * n * mobility * (
            Vds / self.FET.length) * self.FET.width

    def compute_mobility_temperature(self, ambient_mobility,
                                     ambient_temperature, temperature):
        return ambient_mobility * (temperature / ambient_temperature)**(
            self.FET.mobility_temperature_exponent * -1)

    def compute_v0(self):
        Nop = self.compute_nop(Value(295, ureg.kelvin))
        # Nop = 1 / (math.exp(self.hwop / (self.k * Value(295, ureg.kelvin))))
        return self.FET.channel.saturation_velocity * (Nop + 1)

    def compute_nop(self, temp):
        return 1 / (math.exp(self.hwop / (self.k * temp)) - 1)

    def compute_saturation_velocity(self, Temp):
        Nop = self.compute_nop(Temp)
        return self.vO / (Nop + 1)

    def compute_mobility_velocity_saturation(self, effective_mobility, Vds,
                                             Temp):
        #return effective_mobility
        field = Vds / self.FET.length.adjust_unit(ureg.centimeter)
        vsat = self.compute_saturation_velocity(Temp)
        #return effective_mobility / (1 + (effective_mobility * field / self.FET.channel.saturation_velocity)**self.eta)**(1/self.eta)
        return effective_mobility / (1 + (effective_mobility * field / vsat)**
                                     self.eta)**(1 / self.eta), vsat

    def compute_temperature(self,
                            power,
                            ambient_temperature,
                            metal_thermal_resistance=None):
        if metal_thermal_resistance is None:
            #metal_thermal_resistance = Value(0.0, unit=ureg.kelvin / ureg.watt)

            metal_thermal_resistance = self.compute_metal_thermal_resistance()

        x = tanh(self.FET.length.base_units() /
                 (2 * self.thermal_healing_length.base_units()))

        t_part = self.thermal_conductance * self.thermal_healing_length.base_units(
        ) * metal_thermal_resistance * x

        t_1 = (1 + t_part - 2 * x * self.thermal_healing_length.base_units() /
               self.FET.length.base_units()) / (1 + t_part)

        average_temperature = ambient_temperature + power * (
            1 /
            (self.thermal_conductance * self.FET.length.base_units())) * t_1

        return average_temperature

    def compute_quantum_cap(self, ambient_temperature, Vgs):

        T = ambient_temperature
        # Material Parameters
        self.g = 2  # Spin Degenracy
        self.gv1 = 1  # Degenracy of first valley
        self.gv2 = 1  # Degeneracy of second valley
        self.me1_eff = Value(0.45 * scipy.constants.electron_mass,
                             ureg.kilograms)  # Effective mass of first valley
        self.me2_eff = Value(0.45 * scipy.constants.electron_mass,
                             ureg.kilograms)  # Effective mass of second valley
        self.vth = self.k_J * T / electron_charge_C
        self.delEC = 3 * self.vth  # Energy difference from the first valley(Set high to ignore this valley in charge calculations)
        self.epsilon_channel = self.FET.channel.relative_permittivity * free_space_permittivity_F_div_cm
        self.d = self.FET.channel.thickness.base_units()  # channel thickness

        self.epsilon_ox = self.FET.gate_oxide.relative_permittivity * free_space_permittivity_F_div_cm
        self.EOT = self.FET.gate_oxide.thickness
        # self.epsilon_ox_b =
        # self.EOTB =
        self.cox_t = (self.epsilon_ox / self.EOT).adjust_unit(
            ureg.coulombs / (ureg.volts * ureg.meter**2))
        self.cox_b = (self.epsilon_ox / Value(1e10, ureg.meter)).adjust_unit(
            ureg.coulombs / (ureg.volts * ureg.meter**2))

        # Calculate electrostatic screening lengths
        self.lambdaT = (self.epsilon_channel * self.d / self.cox_t)**(1 / 2)
        self.lambdaB = (self.epsilon_channel * self.d / self.cox_b)**(1 / 2)
        self.A = (self.lambdaT**(-2) + self.lambdaB**(-2))**(1 / 2)

        # Calculate Effective density of states for each valley
        self.NDOS1 = (self.gv1 * self.me1_eff * self.k_J *
                      T) / (pi * self.hcross**2)
        self.NDOS2 = (self.gv2 * self.me2_eff * self.k_J *
                      T) / (pi * self.hcross**2)
        self.NDOS = self.NDOS2
        self.alpha = self.NDOS1 / self.NDOS
        self.beta = self.NDOS2 / self.NDOS
        self.Nimp = Value(3.5e11, ureg.meters**-2)

        VDS = Value(0.0, ureg.volts)
        VS = Value(0.0, ureg.volts)
        VD = VS + VDS
        VG = [Vgs, Vgs + .01]
        xg_length = len(VG)

        array_size = 10000

        self.phi = np.zeros(array_size)
        self.f = np.zeros(array_size)
        self.fd = np.zeros(array_size)
        self.phis = np.zeros(array_size)
        self.n2d = np.zeros(len(VG))
        Es = np.zeros(len(VG))
        Eox = np.zeros(len(VG))
        self.Cg = np.zeros(len(VG))
        self.Cq = np.zeros(len(VG))

        for j in range(xg_length):
            # VBG = Value(VG[j], ureg.volts)
            VBG = VG[j]
            # VBG = VBG
            B = ((VBG - self.VFBT) / (self.lambdaT**2)) + ((VBG - self.VFBB) /
                                                           (self.lambdaB**2))
            i = 1
            self.phi[i] = VBG
            self.p = Value(self.phi[i], ureg.volts)

            self.f[i] = (1 + math.exp(
                (self.p - VS) / self.vth))**self.alpha * (1 + math.exp(
                    (self.p - VS) / self.vth) * math.exp(
                        -self.delEC / self.vth))**self.beta - math.exp(
                            (self.epsilon_channel * self.d /
                             (electron_charge_C * self.NDOS)) *
                            (B -
                             (self.A**2) * self.p) + (self.Nimp / self.NDOS))

            self.fd[i] = (1 + math.exp(
                (self.p - VS) / self.vth))**self.alpha * (1 + math.exp(
                    (self.p - VS) / self.vth
                ) * math.exp(-self.delEC / self.vth))**self.beta * (
                    ((self.beta * math.exp((self.p - VS) / self.vth) *
                      math.exp(-self.delEC / self.vth)) /
                     (self.vth *
                      (1 + math.exp((self.p - VS) / self.vth) *
                       math.exp(-self.delEC / self.vth)))) +
                    ((self.alpha * math.exp(
                        (self.p - VS) / self.vth)) / (self.vth * (1 + math.exp(
                            (self.p - VS) / self.vth))))) + (
                                (self.A**2) *
                                (self.epsilon_channel * self.d /
                                 (electron_charge_C * self.NDOS))) * math.exp(
                                     (self.epsilon_channel * self.d /
                                      (electron_charge_C * self.NDOS)) *
                                     (B - (self.A**2) * self.p) +
                                     (self.Nimp / self.NDOS))
            iter = 0
            while abs(self.f[i]) > 1e-06:  # termination condition
                iter = iter + 1
                self.phi[i + 1] = (self.phi[i] - (self.f[i]) / (self.fd[i]))
                p_1 = Value(self.phi[i + 1], ureg.volts)
                self.f[i + 1] = (1 + math.exp(
                    (p_1 - VS) / self.vth))**self.alpha * (1 + math.exp(
                        (p_1 - VS) / self.vth) * math.exp(
                            -self.delEC / self.vth))**self.beta - math.exp(
                                (self.epsilon_channel * self.d /
                                 (electron_charge_C * self.NDOS)) *
                                (B -
                                 (self.A**2) * p_1) + (self.Nimp / self.NDOS))

                self.fd[i + 1] = (1 + math.exp(
                    (p_1 - VS) / self.vth))**self.alpha * (1 + math.exp(
                        (p_1 - VS) / self.vth
                    ) * math.exp(-self.delEC / self.vth))**self.beta * (
                        ((self.beta * math.exp((p_1 - VS) / self.vth) *
                          math.exp(-self.delEC / self.vth)) /
                         (self.vth *
                          (1 + math.exp((p_1 - VS) / self.vth) *
                           math.exp(-self.delEC / self.vth)))) +
                        ((self.alpha * math.exp((p_1 - VS) / self.vth)) /
                         (self.vth * (1 + math.exp(
                             (p_1 - VS) / self.vth))))) + (
                                 (self.A**2) *
                                 (self.epsilon_channel * self.d /
                                  (electron_charge_C * self.NDOS))) * math.exp(
                                      (self.epsilon_channel * self.d /
                                       (electron_charge_C * self.NDOS)) *
                                      (B - (self.A**2) * p_1) +
                                      (self.Nimp / self.NDOS))
                i = i + 1

            self.phis[j] = self.phi[i]
            self.ph = Value(self.phis[j], ureg.volts)
            # Charge density in the 2D layer(m ^ -2)
            self.n2d[j] = (self.epsilon_channel * self.d / electron_charge_C
                           ) * (B - (self.A**2) * self.ph) + self.Nimp
            # Electic field at the surface (V/m)
            Es[j] = electron_charge_C * self.n2d[j] / self.epsilon_channel
            # Electric field in the oxide
            Eox[j] = (self.epsilon_ox / self.epsilon_channel) * (
                (VG[j] - self.VFBT - self.phis[j]) / self.EOT)

        self.n2d = np.trim_zeros(self.n2d, 'b')
        self.phis = np.trim_zeros(self.phis, 'b')

        # Capacitance Calculation
        Cg = Value(
            np.diff(electron_charge_C * self.n2d * 1e-4) / np.diff(VG)[0],
            ureg.coulomb / ureg.meter**2 / ureg.volt)
        CQ = Value(
            np.diff(electron_charge_C * self.n2d * 1e-4) /
            np.diff(self.phis)[0], ureg.coulomb / ureg.meter**2 / ureg.volt)

        #print("Vgs is ", VG)
        print("CQ is ", CQ.adjust_unit(ureg.microfarad / (ureg.centimeter**2)))

        return CQ

    def compute_diffusion_current(self, ambient_temp, vgs, vd):
        # Capacitance Values
        c_Q = self.compute_quantum_cap(ambient_temp, vgs)
        # self.c_Q = Value(0, ureg.meter ** -2 * ureg.coulomb / ureg.volt)
        c_i = self.cox_t
        # c_it = electron_charge_C * Value(1e16, ureg.meter ** -2 / ureg.volt)
        c_it = Value(0, ureg.meter**-2 * ureg.coulomb / ureg.volt)
        cap_r = 1 + (c_Q + c_it) / c_i

        #Mobility
        mobility_temp = Value(295, ureg.kelvin)
        T = ambient_temp
        mobility = self.compute_mobility_temperature(
            self.FET.max_mobility, mobility_temp,
            T).adjust_unit(ureg.meter**2 / ureg.second / ureg.volt)

        #Compute Diffusion Current
        # i_diff = (mobility * (self.c_Q + self.c_it) * (self.FET.width / self.FET.length) * (self.vth ** 2) * (1-math.exp(-vd/self.vth)) * math.exp((vgs-self.FET.Vt_avg)/(self.vth * self.cap_r))).adjust_unit(ureg.ampere)

        I_diff_term_1 = math.log(
            math.exp((vgs - self.FET.Vt_avg) / (self.vth * cap_r)) + 1)
        I_diff_term_2 = math.log(
            math.exp((vgs - self.FET.Vt_avg - vd) / (self.vth * cap_r)) + 1)

        i_diff = (self.vth * electron_charge_C * mobility *
                  (self.FET.width * self.NDOS / self.FET.length) *
                  (I_diff_term_1 - I_diff_term_2)).base_units()

        print("Diffusion Current is {0} at Vg = {1}".format(i_diff, vgs))

        return i_diff

    def plot_diffusion_current(self, ambient_temp, vgs_array, vd_array):
        idiff_vgs_dict = {"vgs": vgs_array}
        length = len(vgs_array)

        for d in vd_array:
            i_diff = []
            cq = []
            for i in range(length):
                i_diff.append(
                    self.compute_diffusion_current(ambient_temp, vgs_array[i],
                                                   d))
                cq.append(self.Cq)
            idiff_vgs_dict["vds = " + str(d)] = np.array(i_diff)
            idiff_vgs_dict["cq = " + str(d)] = np.array(cq)

        plt.plot(vgs_array, idiff_vgs_dict["vds = " + str(vd_array[0])])
        plt.yscale('log')
        plt.show()

        # plt.plot(vgs_array, idiff_vgs_dict["cq = " + str(vd_array[1])])
        # plt.yscale('log')
        # plt.show()

    def model_output(self,
                     Vds,
                     Vgs,
                     heating=True,
                     vsat=True,
                     diffusion=True,
                     drift=True,
                     ambient_temperature=None,
                     iterations=2,
                     linestyle='-',
                     IdVd=True):
        mobility_temp = Value(295, ureg.kelvin)
        if ambient_temperature is None:
            ambient_temperature = Value(295, ureg.kelvin)
        # colors = ['C0', 'C2', 'C3']
        assert isinstance(Vds, ModelInput)
        assert isinstance(Vgs, ModelInput)

        # first calculate an initial Id value assuming room temperature (at low Vds this is a good assumption)
        # make sure the starting field is less than 0.25 V / um
        # if Vds.range[0] == 0.0:
        #     Vds.range[0] = Vds.range[1]
        # make sure the Vgs is above threshold
        # assert Vgs.range[0] > self.FET.Vt_avg, 'The Vgs values should be above the average threshold voltage {0},' \
        #                                        ' your min is {1}'.format(self.FET.Vt_avg, Vgs.range[0])
        # assert Vds.range[0] / self.FET.length <= Value(0.25, ureg.volt / ureg.micrometer),\
        #     'Your starting field should be less than 0.25 V /um, yours is {0}'.format(Vds.range[0] / self.FET.length)

        # create a holder for the data
        if IdVd:
            dataset = IdVdDataSet
            master_independent = Vds
            secondary_independent = Vgs
        else:
            dataset = IdVgDataSet
            master_independent = Vgs
            secondary_independent = Vds

        id_data = {}
        for i_vg in range(len(secondary_independent.range)):
            id_data['id({0})'.format(i_vg)] = []
            id_data['T({0})'.format(i_vg)] = []
            id_data['vsat({0})'.format(i_vg)] = []
            id_data['vgs({0})'.format(i_vg)] = []
            id_data['n({0})'.format(i_vg)] = []
            id_data['vds({0})'.format(i_vg)] = []

        # now loop through the Vgs values
        for i_vgs, vgs in enumerate(Vgs):
            # first calculate an initial Id
            prev_Id = Value(0.0, ureg.amp)
            # prev_Id = self.compute_drift_current(Vds.range[0], self.FET.max_mobility, Vgs.range[0], prev_Id)
            effective_mobility = self.FET.max_mobility
            # now loop through each Vds value
            for i_vds, vds in enumerate(Vds):

                if IdVd:
                    i_master = i_vgs
                else:
                    i_master = i_vds

                # for i in range(iterations):
                # vds = self.compute_vds_rc(vds, prev_Id)
                power = self.compute_power(vds, vgs, effective_mobility,
                                           prev_Id)
                # If self heating is turned off, then just use the ambient tempeature
                if not heating:
                    temperature = ambient_temperature
                else:
                    temperature = self.compute_temperature(
                        power, ambient_temperature)

                # compute the new mobility at this temperature
                effective_mobility = self.compute_mobility_temperature(
                    self.FET.max_mobility, mobility_temp, temperature)
                # now compute the mobility at this field

                if vsat:
                    effective_mobility, vsat = self.compute_mobility_velocity_saturation(
                        effective_mobility, vds, temperature)
                    id_data['vsat({0})'.format(i_master)].append(vsat)

                # now calculate the new current
                new_Id = Value(0.0, ureg.amp)

                if drift and vgs > self.FET.Vt_avg:
                    new_Id += self.compute_drift_current(
                        vds, effective_mobility, vgs, prev_Id).base_units()

                if diffusion:
                    new_Id += self.compute_diffusion_current(
                        temperature, vgs, vds)

                # idvd_data['Vg = {0}'.format(vgs)].append([])
                id_data['id({0})'.format(i_master)].append(new_Id /
                                                           self.FET.width)

                prev_Id = new_Id
                id_data['T({0})'.format(i_master)].append(temperature)
                id_data['n({0})'.format(i_master)].append(
                    self.FET.vg_to_n(vgs))
                id_data['vgs({0})'.format(i_master)].append(vgs)
                id_data['vds({0})'.format(i_master)].append(vds)

        return dataset(data_path=id_data)
Пример #29
0
    def test_stanford2dsmodel(self):

        gate_oxide = SiO2(thickness=Value(30.0, ureg.nanometer))
        channel = MoS2(layer_number=1, thickness=Value(0.6, ureg.nanometer))
        # set the channel vsat to 7e6 cm/s
        channel.saturation_velocity.set(
            Value(3e6, ureg.centimeter / ureg.seconds),
            input_values={'temperature': Value(300, ureg.kelvin)})
        substrate = Silicon()

        fet = TFT(channel=channel,
                  gate_oxide=gate_oxide,
                  length=Value(400, ureg.nanometer),
                  width=Value(2000, ureg.nanometer),
                  substrate=substrate)

        fet.Vt_avg.set(Value(-10.0, ureg.volt))

        fet.Rc.set(Value(2000.0, ureg.ohm * ureg.micrometer),
                   input_values={'n': Value(1e13, ureg.centimeter**-2)})

        fet.max_mobility.set(Value(
            30, ureg.centimeter**2 / (ureg.volt * ureg.second)),
                             input_values={
                                 'Vd': Value(1, ureg.volt),
                                 'Vg': Value(1, ureg.volt)
                             })

        fet.mobility_temperature_exponent.set(Value(1.15, ureg.dimensionless))

        S2DModel = Stanford2DSModel(FET=fet)

        Vds = ModelInput(0.0, 10.0, num=5, unit=ureg.volt)

        Vgs = ModelInput(-12.0, 30.0, num=40, unit=ureg.volt)

        ambient_temperature = Value(300, ureg.kelvin)

        # plot = IdVdPlot('IdVg')
        # fig = plt.figure(dpi=160)
        # ax = fig.gca()
        # I_units = '\u03BCA/\u03BCm'
        # colors = ['C0', 'C2', 'C3', 'C4', 'C5', 'C6']
        # linestyle = '-'
        # plt.rc('xtick', labelsize=12)  # fontsize of the tick labels
        # plt.rc('ytick', labelsize=12)
        # ax.axes.get_xaxis().set_ticks([])
        # ax.axes.get_yaxis().set_ticks([])

        # now compute the model output with self-heating
        idvd_data = S2DModel.model_output(
            Vds,
            Vgs,
            heating=True,
            vsat=True,
            diffusion=False,
            drift=True,
            ambient_temperature=ambient_temperature,
            IdVd=False)

        idvd_plot = IdVgPlot(name='IdVd', dpi=160)
        idvd_plot.add_idvg_dataset(idvd_data)
        idvd_plot.show_plot()
Пример #30
0
    def compute_quantum_cap(self, ambient_temperature, Vgs):

        T = ambient_temperature
        # Material Parameters
        self.g = 2  # Spin Degenracy
        self.gv1 = 1  # Degenracy of first valley
        self.gv2 = 1  # Degeneracy of second valley
        self.me1_eff = Value(0.45 * scipy.constants.electron_mass,
                             ureg.kilograms)  # Effective mass of first valley
        self.me2_eff = Value(0.45 * scipy.constants.electron_mass,
                             ureg.kilograms)  # Effective mass of second valley
        self.vth = self.k_J * T / electron_charge_C
        self.delEC = 3 * self.vth  # Energy difference from the first valley(Set high to ignore this valley in charge calculations)
        self.epsilon_channel = self.FET.channel.relative_permittivity * free_space_permittivity_F_div_cm
        self.d = self.FET.channel.thickness.base_units()  # channel thickness

        self.epsilon_ox = self.FET.gate_oxide.relative_permittivity * free_space_permittivity_F_div_cm
        self.EOT = self.FET.gate_oxide.thickness
        # self.epsilon_ox_b =
        # self.EOTB =
        self.cox_t = (self.epsilon_ox / self.EOT).adjust_unit(
            ureg.coulombs / (ureg.volts * ureg.meter**2))
        self.cox_b = (self.epsilon_ox / Value(1e10, ureg.meter)).adjust_unit(
            ureg.coulombs / (ureg.volts * ureg.meter**2))

        # Calculate electrostatic screening lengths
        self.lambdaT = (self.epsilon_channel * self.d / self.cox_t)**(1 / 2)
        self.lambdaB = (self.epsilon_channel * self.d / self.cox_b)**(1 / 2)
        self.A = (self.lambdaT**(-2) + self.lambdaB**(-2))**(1 / 2)

        # Calculate Effective density of states for each valley
        self.NDOS1 = (self.gv1 * self.me1_eff * self.k_J *
                      T) / (pi * self.hcross**2)
        self.NDOS2 = (self.gv2 * self.me2_eff * self.k_J *
                      T) / (pi * self.hcross**2)
        self.NDOS = self.NDOS2
        self.alpha = self.NDOS1 / self.NDOS
        self.beta = self.NDOS2 / self.NDOS
        self.Nimp = Value(3.5e11, ureg.meters**-2)

        VDS = Value(0.0, ureg.volts)
        VS = Value(0.0, ureg.volts)
        VD = VS + VDS
        VG = [Vgs, Vgs + .01]
        xg_length = len(VG)

        array_size = 10000

        self.phi = np.zeros(array_size)
        self.f = np.zeros(array_size)
        self.fd = np.zeros(array_size)
        self.phis = np.zeros(array_size)
        self.n2d = np.zeros(len(VG))
        Es = np.zeros(len(VG))
        Eox = np.zeros(len(VG))
        self.Cg = np.zeros(len(VG))
        self.Cq = np.zeros(len(VG))

        for j in range(xg_length):
            # VBG = Value(VG[j], ureg.volts)
            VBG = VG[j]
            # VBG = VBG
            B = ((VBG - self.VFBT) / (self.lambdaT**2)) + ((VBG - self.VFBB) /
                                                           (self.lambdaB**2))
            i = 1
            self.phi[i] = VBG
            self.p = Value(self.phi[i], ureg.volts)

            self.f[i] = (1 + math.exp(
                (self.p - VS) / self.vth))**self.alpha * (1 + math.exp(
                    (self.p - VS) / self.vth) * math.exp(
                        -self.delEC / self.vth))**self.beta - math.exp(
                            (self.epsilon_channel * self.d /
                             (electron_charge_C * self.NDOS)) *
                            (B -
                             (self.A**2) * self.p) + (self.Nimp / self.NDOS))

            self.fd[i] = (1 + math.exp(
                (self.p - VS) / self.vth))**self.alpha * (1 + math.exp(
                    (self.p - VS) / self.vth
                ) * math.exp(-self.delEC / self.vth))**self.beta * (
                    ((self.beta * math.exp((self.p - VS) / self.vth) *
                      math.exp(-self.delEC / self.vth)) /
                     (self.vth *
                      (1 + math.exp((self.p - VS) / self.vth) *
                       math.exp(-self.delEC / self.vth)))) +
                    ((self.alpha * math.exp(
                        (self.p - VS) / self.vth)) / (self.vth * (1 + math.exp(
                            (self.p - VS) / self.vth))))) + (
                                (self.A**2) *
                                (self.epsilon_channel * self.d /
                                 (electron_charge_C * self.NDOS))) * math.exp(
                                     (self.epsilon_channel * self.d /
                                      (electron_charge_C * self.NDOS)) *
                                     (B - (self.A**2) * self.p) +
                                     (self.Nimp / self.NDOS))
            iter = 0
            while abs(self.f[i]) > 1e-06:  # termination condition
                iter = iter + 1
                self.phi[i + 1] = (self.phi[i] - (self.f[i]) / (self.fd[i]))
                p_1 = Value(self.phi[i + 1], ureg.volts)
                self.f[i + 1] = (1 + math.exp(
                    (p_1 - VS) / self.vth))**self.alpha * (1 + math.exp(
                        (p_1 - VS) / self.vth) * math.exp(
                            -self.delEC / self.vth))**self.beta - math.exp(
                                (self.epsilon_channel * self.d /
                                 (electron_charge_C * self.NDOS)) *
                                (B -
                                 (self.A**2) * p_1) + (self.Nimp / self.NDOS))

                self.fd[i + 1] = (1 + math.exp(
                    (p_1 - VS) / self.vth))**self.alpha * (1 + math.exp(
                        (p_1 - VS) / self.vth
                    ) * math.exp(-self.delEC / self.vth))**self.beta * (
                        ((self.beta * math.exp((p_1 - VS) / self.vth) *
                          math.exp(-self.delEC / self.vth)) /
                         (self.vth *
                          (1 + math.exp((p_1 - VS) / self.vth) *
                           math.exp(-self.delEC / self.vth)))) +
                        ((self.alpha * math.exp((p_1 - VS) / self.vth)) /
                         (self.vth * (1 + math.exp(
                             (p_1 - VS) / self.vth))))) + (
                                 (self.A**2) *
                                 (self.epsilon_channel * self.d /
                                  (electron_charge_C * self.NDOS))) * math.exp(
                                      (self.epsilon_channel * self.d /
                                       (electron_charge_C * self.NDOS)) *
                                      (B - (self.A**2) * p_1) +
                                      (self.Nimp / self.NDOS))
                i = i + 1

            self.phis[j] = self.phi[i]
            self.ph = Value(self.phis[j], ureg.volts)
            # Charge density in the 2D layer(m ^ -2)
            self.n2d[j] = (self.epsilon_channel * self.d / electron_charge_C
                           ) * (B - (self.A**2) * self.ph) + self.Nimp
            # Electic field at the surface (V/m)
            Es[j] = electron_charge_C * self.n2d[j] / self.epsilon_channel
            # Electric field in the oxide
            Eox[j] = (self.epsilon_ox / self.epsilon_channel) * (
                (VG[j] - self.VFBT - self.phis[j]) / self.EOT)

        self.n2d = np.trim_zeros(self.n2d, 'b')
        self.phis = np.trim_zeros(self.phis, 'b')

        # Capacitance Calculation
        Cg = Value(
            np.diff(electron_charge_C * self.n2d * 1e-4) / np.diff(VG)[0],
            ureg.coulomb / ureg.meter**2 / ureg.volt)
        CQ = Value(
            np.diff(electron_charge_C * self.n2d * 1e-4) /
            np.diff(self.phis)[0], ureg.coulomb / ureg.meter**2 / ureg.volt)

        #print("Vgs is ", VG)
        print("CQ is ", CQ.adjust_unit(ureg.microfarad / (ureg.centimeter**2)))

        return CQ