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
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)
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))
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
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)
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 __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()
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))
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)})
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())
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
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
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)
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
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)))
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)
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__()')
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)
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()
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
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
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)
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' # ;×μ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))
""" 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()
# 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)
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)
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)
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()
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