Files

464 lines
15 KiB
Python
Raw Permalink Normal View History

2025-12-08 10:11:12 +08:00
# Copyright 2013 DEVSIM LLC
#
# SPDX-License-Identifier: Apache-2.0
from .simple_dd import CreateBernoulli, CreateElectronCurrent, CreateHoleCurrent
from .model_create import (
CreateSolution,
CreateNodeModel,
CreateNodeModelDerivative,
CreateContactNodeModel,
CreateContactNodeModelDerivative,
CreateEdgeModel,
CreateEdgeModelDerivatives,
CreateContinuousInterfaceModel,
InEdgeModelList,
InNodeModelList,
)
from devsim import (
contact_equation,
equation,
get_contact_current,
get_parameter,
interface_equation,
set_parameter,
)
contactcharge_edge = "contactcharge_edge"
ece_name = "ElectronContinuityEquation"
hce_name = "HoleContinuityEquation"
celec_model = "(1e-10 + 0.5*abs(NetDoping+(NetDoping^2 + 4 * n_i^2)^(0.5)))"
chole_model = "(1e-10 + 0.5*abs(-NetDoping+(NetDoping^2 + 4 * n_i^2)^(0.5)))"
q = 1.6e-19 # coul
k = 1.3806503e-23 # J/K
eps_0 = 8.85e-14 # F/cm^2
# T = 300 # K
eps_si = 11.1
eps_ox = 3.9
# TODO: make this temperature dependent
n_i = 1.0e10 # #/cm^3
# constant in our approximation
mu_n = 400
mu_p = 200
def GetContactBiasName(contact):
return "{0}_bias".format(contact)
def GetContactNodeModelName(contact):
return "{0}nodemodel".format(contact)
def PrintCurrents(device, contact):
"""
print out contact currents
"""
# TODO add charge
_contact_bias_name = GetContactBiasName(contact)
electron_current = get_contact_current(
device=device, contact=contact, equation=ece_name
)
hole_current = get_contact_current(
device=device, contact=contact, equation=hce_name
)
total_current = electron_current + hole_current
voltage = get_parameter(device=device, name=GetContactBiasName(contact))
print(
"{0}\t{1}\t{2}\t{3}\t{4}".format(
contact, voltage, electron_current, hole_current, total_current
)
)
# in the future, worry about workfunction
def CreateOxideContact(device, region, contact):
_conteq = "Permittivity*ElectricField"
contact_bias_name = GetContactBiasName(contact)
contact_model_name = GetContactNodeModelName(contact)
eq = "Potential - {0}".format(contact_bias_name)
CreateContactNodeModel(device, contact, contact_model_name, eq)
CreateContactNodeModelDerivative(
device, contact, contact_model_name, eq, "Potential"
)
# TODO: make everyone use dfield
if not InEdgeModelList(device, region, contactcharge_edge):
CreateEdgeModel(
device, region, contactcharge_edge, "Permittivity*ElectricField"
)
CreateEdgeModelDerivatives(
device,
region,
contactcharge_edge,
"Permittivity*ElectricField",
"Potential",
)
contact_equation(
device=device,
contact=contact,
name="PotentialEquation",
node_model=contact_model_name,
edge_charge_model=contactcharge_edge,
)
#####
##### Constants
#####
def SetOxideParameters(device, region, T):
"""
Sets physical parameters
"""
set_parameter(
device=device, region=region, name="Permittivity", value=eps_ox * eps_0
)
set_parameter(device=device, region=region, name="ElectronCharge", value=q)
def SetSiliconParameters(device, region, T):
"""
Sets physical parameters assuming constants
"""
#### TODO: make T a free parameter and T dependent parameters as models
set_parameter(
device=device, region=region, name="Permittivity", value=eps_si * eps_0
)
set_parameter(device=device, region=region, name="ElectronCharge", value=q)
set_parameter(device=device, region=region, name="n_i", value=n_i)
set_parameter(device=device, region=region, name="T", value=T)
set_parameter(device=device, region=region, name="kT", value=k * T)
set_parameter(device=device, region=region, name="V_t", value=k * T / q)
set_parameter(device=device, region=region, name="mu_n", value=mu_n)
set_parameter(device=device, region=region, name="mu_p", value=mu_p)
# default SRH parameters
set_parameter(device=device, region=region, name="n1", value=n_i)
set_parameter(device=device, region=region, name="p1", value=n_i)
set_parameter(device=device, region=region, name="taun", value=1e-5)
set_parameter(device=device, region=region, name="taup", value=1e-5)
def CreateSiliconPotentialOnly(device, region):
"""
Creates the physical models for a Silicon region
"""
if not InNodeModelList(device, region, "Potential"):
print("Creating Node Solution Potential")
CreateSolution(device, region, "Potential")
elec_i = "n_i*exp(Potential/V_t)"
hole_i = "n_i^2/IntrinsicElectrons"
charge_i = "kahan3(IntrinsicHoles, -IntrinsicElectrons, NetDoping)"
pcharge_i = "-ElectronCharge * IntrinsicCharge"
# require NetDoping
for i in (
("IntrinsicElectrons", elec_i),
("IntrinsicHoles", hole_i),
("IntrinsicCharge", charge_i),
("PotentialIntrinsicCharge", pcharge_i),
):
n = i[0]
e = i[1]
CreateNodeModel(device, region, n, e)
CreateNodeModelDerivative(device, region, n, e, "Potential")
### TODO: Edge Average Model
for i in (
("ElectricField", "(Potential@n0-Potential@n1)*EdgeInverseLength"),
("PotentialEdgeFlux", "Permittivity * ElectricField"),
):
n = i[0]
e = i[1]
CreateEdgeModel(device, region, n, e)
CreateEdgeModelDerivatives(device, region, n, e, "Potential")
equation(
device=device,
region=region,
name="PotentialEquation",
variable_name="Potential",
node_model="PotentialIntrinsicCharge",
edge_model="PotentialEdgeFlux",
variable_update="log_damp",
)
def CreateSiliconPotentialOnlyContact(device, region, contact, is_circuit=False):
"""
Creates the potential equation at the contact
if is_circuit is true, than use node given by GetContactBiasName
"""
# Means of determining contact charge
# Same for all contacts
#### TODO: This is the same as D-Field
if not InEdgeModelList(device, region, "contactcharge_edge"):
CreateEdgeModel(
device, region, "contactcharge_edge", "Permittivity*ElectricField"
)
CreateEdgeModelDerivatives(
device,
region,
"contactcharge_edge",
"Permittivity*ElectricField",
"Potential",
)
# set_parameter(device=device, region=region, name=GetContactBiasName(contact), value=0.0)
contact_model = "Potential -{0} + ifelse(NetDoping > 0, \
-V_t*log({1}/n_i), \
V_t*log({2}/n_i))".format(GetContactBiasName(contact), celec_model, chole_model)
contact_model_name = GetContactNodeModelName(contact)
CreateContactNodeModel(device, contact, contact_model_name, contact_model)
# Simplify it too complicated
CreateContactNodeModel(
device, contact, "{0}:{1}".format(contact_model_name, "Potential"), "1"
)
if is_circuit:
CreateContactNodeModel(
device,
contact,
"{0}:{1}".format(contact_model_name, GetContactBiasName(contact)),
"-1",
)
if is_circuit:
contact_equation(
device=device,
contact=contact,
name="PotentialEquation",
node_model=contact_model_name,
edge_model="",
node_charge_model="",
edge_charge_model="contactcharge_edge",
node_current_model="",
edge_current_model="",
circuit_node=GetContactBiasName(contact),
)
else:
contact_equation(
device=device,
contact=contact,
name="PotentialEquation",
node_model=contact_model_name,
edge_model="",
node_charge_model="",
edge_charge_model="contactcharge_edge",
node_current_model="",
edge_current_model="",
)
def CreateSRH(device, region):
USRH = "(Electrons*Holes - n_i^2)/(taup*(Electrons + n1) + taun*(Holes + p1))"
Gn = "-ElectronCharge * USRH"
Gp = "+ElectronCharge * USRH"
CreateNodeModel(device, region, "USRH", USRH)
CreateNodeModel(device, region, "ElectronGeneration", Gn)
CreateNodeModel(device, region, "HoleGeneration", Gp)
for i in ("Electrons", "Holes"):
CreateNodeModelDerivative(device, region, "USRH", USRH, i)
CreateNodeModelDerivative(device, region, "ElectronGeneration", Gn, i)
CreateNodeModelDerivative(device, region, "HoleGeneration", Gp, i)
def CreateECE(device, region, mu_n):
CreateElectronCurrent(device, region, mu_n)
NCharge = "-ElectronCharge * Electrons"
CreateNodeModel(device, region, "NCharge", NCharge)
CreateNodeModelDerivative(device, region, "NCharge", NCharge, "Electrons")
equation(
device=device,
region=region,
name="ElectronContinuityEquation",
variable_name="Electrons",
time_node_model="NCharge",
edge_model="ElectronCurrent",
variable_update="positive",
node_model="ElectronGeneration",
)
def CreateHCE(device, region, mu_p):
CreateHoleCurrent(device, region, mu_p)
PCharge = "ElectronCharge * Holes"
CreateNodeModel(device, region, "PCharge", PCharge)
CreateNodeModelDerivative(device, region, "PCharge", PCharge, "Holes")
equation(
device=device,
region=region,
name="HoleContinuityEquation",
variable_name="Holes",
time_node_model="PCharge",
edge_model="HoleCurrent",
variable_update="positive",
node_model="HoleGeneration",
)
def CreatePE(device, region):
pne = "-ElectronCharge*kahan3(Holes, -Electrons, NetDoping)"
CreateNodeModel(device, region, "PotentialNodeCharge", pne)
CreateNodeModelDerivative(device, region, "PotentialNodeCharge", pne, "Electrons")
CreateNodeModelDerivative(device, region, "PotentialNodeCharge", pne, "Holes")
equation(
device=device,
region=region,
name="PotentialEquation",
variable_name="Potential",
node_model="PotentialNodeCharge",
edge_model="PotentialEdgeFlux",
time_node_model="",
variable_update="log_damp",
)
def CreateSiliconDriftDiffusion(device, region, mu_n="mu_n", mu_p="mu_p"):
CreatePE(device, region)
CreateBernoulli(device, region)
CreateSRH(device, region)
CreateECE(device, region, mu_n)
CreateHCE(device, region, mu_p)
def CreateSiliconDriftDiffusionAtContact(device, region, contact, is_circuit=False):
"""
Restrict electrons and holes to their equilibrium values
Integrates current into circuit
"""
contact_electrons_model = (
"Electrons - ifelse(NetDoping > 0, {0}, n_i^2/{1})".format(
celec_model, chole_model
)
)
contact_holes_model = "Holes - ifelse(NetDoping < 0, +{1}, +n_i^2/{0})".format(
celec_model, chole_model
)
contact_electrons_name = "{0}nodeelectrons".format(contact)
contact_holes_name = "{0}nodeholes".format(contact)
CreateContactNodeModel(
device, contact, contact_electrons_name, contact_electrons_model
)
# TODO: The simplification of the ifelse statement is time consuming
# CreateContactNodeModelDerivative(device, contact, contact_electrons_name, contact_electrons_model, "Electrons")
CreateContactNodeModel(
device, contact, "{0}:{1}".format(contact_electrons_name, "Electrons"), "1"
)
CreateContactNodeModel(device, contact, contact_holes_name, contact_holes_model)
CreateContactNodeModel(
device, contact, "{0}:{1}".format(contact_holes_name, "Holes"), "1"
)
# TODO: keyword args
if is_circuit:
contact_equation(
device=device,
contact=contact,
name="ElectronContinuityEquation",
node_model=contact_electrons_name,
edge_current_model="ElectronCurrent",
circuit_node=GetContactBiasName(contact),
)
contact_equation(
device=device,
contact=contact,
name="HoleContinuityEquation",
node_model=contact_holes_name,
edge_current_model="HoleCurrent",
circuit_node=GetContactBiasName(contact),
)
else:
contact_equation(
device=device,
contact=contact,
name="ElectronContinuityEquation",
node_model=contact_electrons_name,
edge_current_model="ElectronCurrent",
)
contact_equation(
device=device,
contact=contact,
name="HoleContinuityEquation",
node_model=contact_holes_name,
edge_current_model="HoleCurrent",
)
def CreateOxidePotentialOnly(device, region, update_type="default"):
"""
Create electric field model in oxide
Creates Potential solution variable if not available
"""
if not InNodeModelList(device, region, "Potential"):
print("Creating Node Solution Potential")
CreateSolution(device, region, "Potential")
efield = "(Potential@n0 - Potential@n1)*EdgeInverseLength"
# this needs to remove derivatives w.r.t. independents
CreateEdgeModel(device, region, "ElectricField", efield)
CreateEdgeModelDerivatives(device, region, "ElectricField", efield, "Potential")
dfield = "Permittivity*ElectricField"
CreateEdgeModel(device, region, "PotentialEdgeFlux", dfield)
CreateEdgeModelDerivatives(device, region, "PotentialEdgeFlux", dfield, "Potential")
equation(
device=device,
region=region,
name="PotentialEquation",
variable_name="Potential",
edge_model="PotentialEdgeFlux",
variable_update=update_type,
)
def CreateSiliconOxideInterface(device, interface):
"""
continuous potential at interface
"""
model_name = CreateContinuousInterfaceModel(device, interface, "Potential")
interface_equation(
device=device,
interface=interface,
name="PotentialEquation",
interface_model=model_name,
type="continuous",
)
#
##TODO: similar model for silicon/silicon interface
## should use quasi-fermi potential
def CreateSiliconSiliconInterface(device, interface):
"""
Enforces potential, electron, and hole continuity across the interface
"""
CreateSiliconOxideInterface(device, interface)
ename = CreateContinuousInterfaceModel(device, interface, "Electrons")
interface_equation(
device=device,
interface=interface,
name="ElectronContinuityEquation",
interface_model=ename,
type="continuous",
)
hname = CreateContinuousInterfaceModel(device, interface, "Holes")
interface_equation(
device=device,
interface=interface,
name="HoleContinuityEquation",
interface_model=hname,
type="continuous",
)