278 lines
15 KiB
Python
278 lines
15 KiB
Python
|
|
import devsim
|
||
|
|
import numpy as np
|
||
|
|
import matplotlib.pyplot as plt
|
||
|
|
import os
|
||
|
|
import sys
|
||
|
|
sys.path.append("/home/pchan/devsim2026")
|
||
|
|
from device_config import *
|
||
|
|
from physics.model_create import *
|
||
|
|
from physics.new_physics import *
|
||
|
|
|
||
|
|
def run_simulation(mesh_file="device_2d.msh", tec_file="static_preview.tec", png_file="static_potential_2d.png", suffix=""):
|
||
|
|
device = "device_2d"
|
||
|
|
|
||
|
|
# 1. Load the mesh
|
||
|
|
print(f"Loading mesh: {mesh_file}")
|
||
|
|
devsim.create_gmsh_mesh(mesh=device, file=mesh_file)
|
||
|
|
devsim.add_gmsh_region(mesh=device, gmsh_name="Silicon", region="Silicon", material="Silicon")
|
||
|
|
devsim.add_gmsh_region(mesh=device, gmsh_name="Oxide", region="Oxide", material="Oxide")
|
||
|
|
devsim.add_gmsh_region(mesh=device, gmsh_name="Molding", region="Molding", material="Molding")
|
||
|
|
|
||
|
|
# Add contacts
|
||
|
|
devsim.add_gmsh_contact(mesh=device, gmsh_name="MT1_Si", name="MT1_Si", region="Silicon", material="metal")
|
||
|
|
devsim.add_gmsh_contact(mesh=device, gmsh_name="MT2_Si", name="MT2_Si", region="Silicon", material="metal")
|
||
|
|
devsim.add_gmsh_contact(mesh=device, gmsh_name="MT1_P12_Si", name="MT1_P12_Si", region="Silicon", material="metal")
|
||
|
|
devsim.add_gmsh_contact(mesh=device, gmsh_name="MT2_P12_Si", name="MT2_P12_Si", region="Silicon", material="metal")
|
||
|
|
|
||
|
|
devsim.add_gmsh_contact(mesh=device, gmsh_name="MT1_Ox", name="MT1_Ox", region="Oxide", material="metal")
|
||
|
|
devsim.add_gmsh_contact(mesh=device, gmsh_name="MT2_Ox", name="MT2_Ox", region="Oxide", material="metal")
|
||
|
|
|
||
|
|
devsim.add_gmsh_contact(mesh=device, gmsh_name="MT1_Mold", name="MT1_Mold", region="Molding", material="metal")
|
||
|
|
devsim.add_gmsh_contact(mesh=device, gmsh_name="MT2_Mold", name="MT2_Mold", region="Molding", material="metal")
|
||
|
|
|
||
|
|
# Add interfaces
|
||
|
|
devsim.add_gmsh_interface(mesh=device, gmsh_name="Si_Ox_Interface", name="Si_Ox", region0="Silicon", region1="Oxide")
|
||
|
|
devsim.add_gmsh_interface(mesh=device, gmsh_name="Ox_Mold_Interface", name="Ox_Mold", region0="Oxide", region1="Molding")
|
||
|
|
devsim.add_gmsh_interface(mesh=device, gmsh_name="Si_Mold_Interface", name="Si_Mold", region0="Silicon", region1="Molding")
|
||
|
|
|
||
|
|
devsim.finalize_mesh(mesh=device)
|
||
|
|
devsim.create_device(mesh=device, device=device)
|
||
|
|
|
||
|
|
# 2. Set up doping in Silicon region
|
||
|
|
devsim.node_model(device=device, region="Silicon", name="nD_sub", equation=f"{N_SUB}")
|
||
|
|
|
||
|
|
def get_erfc_expr(peak, x1, x2, hdiff, vdiff):
|
||
|
|
return f"{peak} * erfc(y / {vdiff}) * 0.5 * (erf((x - ({x1})) / {hdiff}) - erf((x - ({x2})) / {hdiff}))"
|
||
|
|
|
||
|
|
p11_left_expr = get_erfc_expr(P11_PEAK, -P11_X2, -P11_X1, P_WELL_HDDIFF, P_WELL_VDDIFF)
|
||
|
|
p11_right_expr = get_erfc_expr(P11_PEAK, P11_X1, P11_X2, P_WELL_HDDIFF, P_WELL_VDDIFF)
|
||
|
|
devsim.node_model(device=device, region="Silicon", name="nA_p11_l", equation=p11_left_expr)
|
||
|
|
devsim.node_model(device=device, region="Silicon", name="nA_p11_r", equation=p11_right_expr)
|
||
|
|
|
||
|
|
p12_left_expr = get_erfc_expr(P12_PEAK, -P12_X2, -P12_X1, P_WELL_HDDIFF, P_WELL_VDDIFF)
|
||
|
|
p12_right_expr = get_erfc_expr(P12_PEAK, P12_X1, P12_X2, P_WELL_HDDIFF, P_WELL_VDDIFF)
|
||
|
|
devsim.node_model(device=device, region="Silicon", name="nA_p12_l", equation=p12_left_expr)
|
||
|
|
devsim.node_model(device=device, region="Silicon", name="nA_p12_r", equation=p12_right_expr)
|
||
|
|
|
||
|
|
p13_left_expr = get_erfc_expr(P13_PEAK, -P13_X2, -P13_X1, P_WELL_HDDIFF, P_WELL_VDDIFF)
|
||
|
|
p13_right_expr = get_erfc_expr(P13_PEAK, P13_X1, P13_X2, P_WELL_HDDIFF, P_WELL_VDDIFF)
|
||
|
|
devsim.node_model(device=device, region="Silicon", name="nA_p13_l", equation=p13_left_expr)
|
||
|
|
devsim.node_model(device=device, region="Silicon", name="nA_p13_r", equation=p13_right_expr)
|
||
|
|
|
||
|
|
nplus_left_expr = get_erfc_expr(NPLUS_PEAK, -NPLUS_X2, -NPLUS_X1, NPLUS_HDDIFF, NPLUS_VDDIFF)
|
||
|
|
nplus_right_expr = get_erfc_expr(NPLUS_PEAK, NPLUS_X1, NPLUS_X2, NPLUS_HDDIFF, NPLUS_VDDIFF)
|
||
|
|
devsim.node_model(device=device, region="Silicon", name="nD_nplus_l", equation=nplus_left_expr)
|
||
|
|
devsim.node_model(device=device, region="Silicon", name="nD_nplus_r", equation=nplus_right_expr)
|
||
|
|
|
||
|
|
mring_l_expr = get_erfc_expr(NPLUS_PEAK, -W_DEVICE, -MRING_X1, NPLUS_HDDIFF, NPLUS_VDDIFF)
|
||
|
|
mring_r_expr = get_erfc_expr(NPLUS_PEAK, MRING_X1, W_DEVICE, NPLUS_HDDIFF, NPLUS_VDDIFF)
|
||
|
|
devsim.node_model(device=device, region="Silicon", name="nD_mring_l", equation=mring_l_expr)
|
||
|
|
devsim.node_model(device=device, region="Silicon", name="nD_mring_r", equation=mring_r_expr)
|
||
|
|
|
||
|
|
devsim.node_model(device=device, region="Silicon", name="Donors",
|
||
|
|
equation="nD_sub + nD_nplus_l + nD_nplus_r + nD_mring_l + nD_mring_r")
|
||
|
|
devsim.node_model(device=device, region="Silicon", name="Acceptors",
|
||
|
|
equation="1e10 + nA_p11_l + nA_p11_r + nA_p12_l + nA_p12_r + nA_p13_l + nA_p13_r")
|
||
|
|
devsim.node_model(device=device, region="Silicon", name="NetDoping", equation="Donors - Acceptors")
|
||
|
|
|
||
|
|
# 3. Solutions and Physics
|
||
|
|
CreateSolution(device, "Silicon", "Potential")
|
||
|
|
devsim.set_parameter(device=device, name="T", value="300")
|
||
|
|
CreateSiliconPotentialOnly(device, "Silicon")
|
||
|
|
|
||
|
|
# Oxide
|
||
|
|
if not InNodeModelList(device, "Oxide", "Potential"):
|
||
|
|
CreateSolution(device, "Oxide", "Potential")
|
||
|
|
devsim.set_parameter(device=device, region="Oxide", name="Permittivity", value=3.9 * 8.85e-14)
|
||
|
|
efield = "(Potential@n0 - Potential@n1)*EdgeInverseLength"
|
||
|
|
CreateEdgeModel(device, "Oxide", "EField", efield)
|
||
|
|
CreateEdgeModelDerivatives(device, "Oxide", "EField", efield, "Potential")
|
||
|
|
dfield = "Permittivity*EField"
|
||
|
|
CreateEdgeModel(device, "Oxide", "PotentialEdgeFlux", dfield)
|
||
|
|
CreateEdgeModelDerivatives(device, "Oxide", "PotentialEdgeFlux", dfield, "Potential")
|
||
|
|
devsim.equation(device=device, region="Oxide", name="PotentialEquation", variable_name="Potential",
|
||
|
|
edge_model="PotentialEdgeFlux", variable_update="default")
|
||
|
|
|
||
|
|
# Molding
|
||
|
|
if not InNodeModelList(device, "Molding", "Potential"):
|
||
|
|
CreateSolution(device, "Molding", "Potential")
|
||
|
|
devsim.set_parameter(device=device, region="Molding", name="Permittivity", value=4.0 * 8.85e-14)
|
||
|
|
efield = "(Potential@n0 - Potential@n1)*EdgeInverseLength"
|
||
|
|
CreateEdgeModel(device, "Molding", "EField", efield)
|
||
|
|
CreateEdgeModelDerivatives(device, "Molding", "EField", efield, "Potential")
|
||
|
|
dfield = "Permittivity*EField"
|
||
|
|
CreateEdgeModel(device, "Molding", "PotentialEdgeFlux", dfield)
|
||
|
|
CreateEdgeModelDerivatives(device, "Molding", "PotentialEdgeFlux", dfield, "Potential")
|
||
|
|
devsim.equation(device=device, region="Molding", name="PotentialEquation", variable_name="Potential",
|
||
|
|
edge_model="PotentialEdgeFlux", variable_update="default")
|
||
|
|
|
||
|
|
# Interfaces
|
||
|
|
def CreateContinuousPotentialInterface(device, interface):
|
||
|
|
model_name = CreateContinuousInterfaceModel(device, interface, "Potential")
|
||
|
|
devsim.interface_equation(device=device, interface=interface, name="PotentialEquation",
|
||
|
|
interface_model=model_name, type="continuous")
|
||
|
|
CreateContinuousPotentialInterface(device, "Si_Ox")
|
||
|
|
CreateContinuousPotentialInterface(device, "Ox_Mold")
|
||
|
|
CreateContinuousPotentialInterface(device, "Si_Mold")
|
||
|
|
|
||
|
|
# Silicon contacts
|
||
|
|
silicon_contacts = ["MT1_Si", "MT2_Si"]
|
||
|
|
for c in silicon_contacts:
|
||
|
|
devsim.set_parameter(device=device, name=GetContactBiasName(c), value=0.0)
|
||
|
|
CreateSiliconPotentialOnlyContact(device, "Silicon", c)
|
||
|
|
|
||
|
|
devsim.set_parameter(device=device, name="MT1_P12_Si_bias", value=0.0)
|
||
|
|
CreateSiliconPotentialOnlyContact(device, "Silicon", "MT1_P12_Si")
|
||
|
|
devsim.set_parameter(device=device, name="MT2_P12_Si_bias", value=0.0)
|
||
|
|
CreateSiliconPotentialOnlyContact(device, "Silicon", "MT2_P12_Si")
|
||
|
|
|
||
|
|
# Oxide contacts
|
||
|
|
def CreateOxidePotentialOnlyContact(device, region, contact):
|
||
|
|
contact_bias = GetContactBiasName(contact)
|
||
|
|
contact_model = f"Potential - {contact_bias}"
|
||
|
|
contact_model_name = f"{contact}_bc"
|
||
|
|
CreateContactNodeModel(device, contact, contact_model_name, contact_model)
|
||
|
|
CreateContactNodeModelDerivative(device, contact, contact_model_name, contact_model, "Potential")
|
||
|
|
devsim.contact_equation(device=device, contact=contact, name="PotentialEquation",
|
||
|
|
node_model=contact_model_name, edge_charge_model="PotentialEdgeFlux")
|
||
|
|
|
||
|
|
oxide_contacts = ["MT1_Ox", "MT2_Ox"]
|
||
|
|
for c in oxide_contacts:
|
||
|
|
devsim.set_parameter(device=device, name=GetContactBiasName(c), value=0.0)
|
||
|
|
CreateOxidePotentialOnlyContact(device, "Oxide", c)
|
||
|
|
|
||
|
|
# Molding contacts
|
||
|
|
def CreateMoldingPotentialOnlyContact(device, region, contact):
|
||
|
|
contact_bias = GetContactBiasName(contact)
|
||
|
|
contact_model = f"Potential - {contact_bias}"
|
||
|
|
contact_model_name = f"{contact}_bc"
|
||
|
|
CreateContactNodeModel(device, contact, contact_model_name, contact_model)
|
||
|
|
CreateContactNodeModelDerivative(device, contact, contact_model_name, contact_model, "Potential")
|
||
|
|
devsim.contact_equation(device=device, contact=contact, name="PotentialEquation",
|
||
|
|
node_model=contact_model_name, edge_charge_model="PotentialEdgeFlux")
|
||
|
|
|
||
|
|
molding_contacts = ["MT1_Mold", "MT2_Mold"]
|
||
|
|
for c in molding_contacts:
|
||
|
|
devsim.set_parameter(device=device, name=GetContactBiasName(c), value=0.0)
|
||
|
|
CreateMoldingPotentialOnlyContact(device, "Molding", c)
|
||
|
|
|
||
|
|
# Solve
|
||
|
|
print("Solving Poisson/Laplace equations...")
|
||
|
|
devsim.solve(type="dc", absolute_error=1.0, relative_error=1e-10, maximum_iterations=50)
|
||
|
|
print("Solution converged!")
|
||
|
|
|
||
|
|
# Compute electric field magnitude (Emag) on elements
|
||
|
|
for reg in ["Silicon", "Oxide", "Molding"]:
|
||
|
|
devsim.element_from_edge_model(edge_model="EField", device=device, region=reg)
|
||
|
|
devsim.element_model(device=device, region=reg, name="Emag", equation="(EField_x^2 + EField_y^2)^(0.5)")
|
||
|
|
|
||
|
|
devsim.write_devices(file=tec_file, type="tecplot")
|
||
|
|
print(f"Saved {tec_file}.")
|
||
|
|
|
||
|
|
# Extract data for plotting
|
||
|
|
x_si = np.array(devsim.get_node_model_values(device=device, region="Silicon", name="x")) / um
|
||
|
|
y_si = np.array(devsim.get_node_model_values(device=device, region="Silicon", name="y")) / um
|
||
|
|
pot_si = np.array(devsim.get_node_model_values(device=device, region="Silicon", name="Potential"))
|
||
|
|
tri_si = np.array(devsim.get_element_node_list(device=device, region="Silicon"))
|
||
|
|
emag_si = np.array(devsim.get_element_model_values(device=device, region="Silicon", name="Emag"))[::3]
|
||
|
|
|
||
|
|
x_ox = np.array(devsim.get_node_model_values(device=device, region="Oxide", name="x")) / um
|
||
|
|
y_ox = np.array(devsim.get_node_model_values(device=device, region="Oxide", name="y")) / um
|
||
|
|
pot_ox = np.array(devsim.get_node_model_values(device=device, region="Oxide", name="Potential"))
|
||
|
|
tri_ox = np.array(devsim.get_element_node_list(device=device, region="Oxide"))
|
||
|
|
emag_ox = np.array(devsim.get_element_model_values(device=device, region="Oxide", name="Emag"))[::3]
|
||
|
|
|
||
|
|
x_mold = np.array(devsim.get_node_model_values(device=device, region="Molding", name="x")) / um
|
||
|
|
y_mold = np.array(devsim.get_node_model_values(device=device, region="Molding", name="y")) / um
|
||
|
|
pot_mold = np.array(devsim.get_node_model_values(device=device, region="Molding", name="Potential"))
|
||
|
|
tri_mold = np.array(devsim.get_element_node_list(device=device, region="Molding"))
|
||
|
|
emag_mold = np.array(devsim.get_element_model_values(device=device, region="Molding", name="Emag"))[::3]
|
||
|
|
|
||
|
|
def draw_device_boundaries(ax):
|
||
|
|
ax.plot([-W_DEVICE/um, W_DEVICE/um], [-T_OX/um, -T_OX/um], color='black', linestyle='--', linewidth=0.8)
|
||
|
|
ax.plot([-W_DEVICE/um, W_DEVICE/um], [0, 0], color='black', linestyle='-', linewidth=0.8)
|
||
|
|
ax.plot([-W_DEVICE/um, -W_DEVICE/um], [0, H_SI/um], color='black', linestyle='-', linewidth=0.8)
|
||
|
|
ax.plot([W_DEVICE/um, W_DEVICE/um], [0, H_SI/um], color='black', linestyle='-', linewidth=0.8)
|
||
|
|
ax.plot([-W_SIM/um, W_SIM/um], [H_SI/um, H_SI/um], color='black', linestyle='-', linewidth=1.2)
|
||
|
|
|
||
|
|
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 14))
|
||
|
|
|
||
|
|
tcf1_si = ax1.tripcolor(x_si, y_si, tri_si, pot_si, cmap='RdYlBu_r', shading='gouraud')
|
||
|
|
tcf1_ox = ax1.tripcolor(x_ox, y_ox, tri_ox, pot_ox, cmap='RdYlBu_r', shading='gouraud')
|
||
|
|
tcf1_mold = ax1.tripcolor(x_mold, y_mold, tri_mold, pot_mold, cmap='RdYlBu_r', shading='gouraud')
|
||
|
|
fig.colorbar(tcf1_si, ax=ax1, label='Electrostatic Potential (V)')
|
||
|
|
draw_device_boundaries(ax1)
|
||
|
|
ax1.set_xlabel('X (μm)')
|
||
|
|
ax1.set_ylabel('Y (μm)')
|
||
|
|
ax1.set_title(f'2D Electrostatic Potential at Zero Bias (Floating Bottom & MRING) {suffix}')
|
||
|
|
ax1.set_xlim(-W_SIM / um, W_SIM / um)
|
||
|
|
ax1.set_ylim(H_SI/um + 15.0, -110.0)
|
||
|
|
|
||
|
|
tcf2_si = ax2.tripcolor(x_si, y_si, tri_si, facecolors=emag_si, cmap='inferno', shading='flat')
|
||
|
|
tcf2_ox = ax2.tripcolor(x_ox, y_ox, tri_ox, facecolors=emag_ox, cmap='inferno', shading='flat')
|
||
|
|
tcf2_mold = ax2.tripcolor(x_mold, y_mold, tri_mold, facecolors=emag_mold, cmap='inferno', shading='flat')
|
||
|
|
fig.colorbar(tcf2_si, ax=ax2, label='Electric Field Magnitude (V/cm)')
|
||
|
|
draw_device_boundaries(ax2)
|
||
|
|
ax2.set_xlabel('X (μm)')
|
||
|
|
ax2.set_ylabel('Y (μm)')
|
||
|
|
ax2.set_title(f'2D Electric Field Magnitude at Zero Bias (Floating Bottom & MRING) {suffix}')
|
||
|
|
ax2.set_xlim(-W_SIM / um, W_SIM / um)
|
||
|
|
ax2.set_ylim(H_SI/um + 15.0, -110.0)
|
||
|
|
|
||
|
|
plt.tight_layout()
|
||
|
|
plt.savefig(png_file, dpi=300)
|
||
|
|
plt.close()
|
||
|
|
print(f"Plot saved to {png_file}")
|
||
|
|
|
||
|
|
return device
|
||
|
|
|
||
|
|
def generate_background_mesh():
|
||
|
|
# 1. Run simulation on current mesh to get Emag
|
||
|
|
device = run_simulation("device_2d.msh", "static_preview.tec", "static_potential_2d.png", suffix="(Coarse Mesh)")
|
||
|
|
|
||
|
|
# 2. Extract elements and Emag
|
||
|
|
print("Generating background mesh...")
|
||
|
|
|
||
|
|
# Refinement parameters
|
||
|
|
LcMin = 0.15 * um # 0.15 um min mesh size in cm
|
||
|
|
LcMax = 20.0 * um # 20 um max mesh size in cm
|
||
|
|
alpha = 1.0e-3 # Scaling coefficient for Emag
|
||
|
|
|
||
|
|
# We will write to device_bgmesh.pos
|
||
|
|
with open("device_bgmesh.pos", "w") as f:
|
||
|
|
f.write('View "background mesh" {\n')
|
||
|
|
|
||
|
|
# Write for Silicon, Oxide, Molding regions
|
||
|
|
for reg in ["Silicon", "Oxide", "Molding"]:
|
||
|
|
x = np.array(devsim.get_node_model_values(device=device, region=reg, name="x"))
|
||
|
|
y = np.array(devsim.get_node_model_values(device=device, region=reg, name="y"))
|
||
|
|
triangles = np.array(devsim.get_element_node_list(device=device, region=reg))
|
||
|
|
emag = np.array(devsim.get_element_model_values(device=device, region=reg, name="Emag"))[::3]
|
||
|
|
|
||
|
|
for i, tri in enumerate(triangles):
|
||
|
|
# get nodes
|
||
|
|
n0, n1, n2 = tri[0], tri[1], tri[2]
|
||
|
|
|
||
|
|
# get coordinates
|
||
|
|
x0, y0 = x[n0], y[n0]
|
||
|
|
x1, y1 = x[n1], y[n1]
|
||
|
|
x2, y2 = x[n2], y[n2]
|
||
|
|
|
||
|
|
# get Emag of the element
|
||
|
|
e_val = emag[i]
|
||
|
|
|
||
|
|
# Calculate target lc at this element based on Emag
|
||
|
|
lc_val = LcMax / (1.0 + alpha * e_val)
|
||
|
|
if lc_val < LcMin:
|
||
|
|
lc_val = LcMin
|
||
|
|
|
||
|
|
# Write a Scalar Triangle (ST)
|
||
|
|
f.write(f"ST({x0:.8e},{y0:.8e},0,{x1:.8e},{y1:.8e},0,{x2:.8e},{y2:.8e},0){{{lc_val:.8e},{lc_val:.8e},{lc_val:.8e}}};\n")
|
||
|
|
|
||
|
|
f.write("};\n")
|
||
|
|
|
||
|
|
print("Background mesh file written to device_bgmesh.pos successfully.")
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
generate_background_mesh()
|