Files
tcad-devsim_triac/generate_analytical_bgmesh.py
T

126 lines
5.9 KiB
Python

import devsim
import numpy as np
import math
import sys
import os
sys.path.append("/home/pchan/devsim2026")
from device_config import *
# Vectorize math functions for fast numpy operations
erf_vec = np.vectorize(math.erf)
erfc_vec = np.vectorize(math.erfc)
def erfc_doping(x, y, peak, x1, x2, hdiff, vdiff):
return peak * erfc_vec(y / vdiff) * 0.5 * (erf_vec((x - x1) / hdiff) - erf_vec((x - x2) / hdiff))
def get_doping_val(x, y):
# Donors
nD = N_SUB
nD += erfc_doping(x, y, NPLUS_PEAK, -NPLUS_X2, -NPLUS_X1, NPLUS_HDDIFF, NPLUS_VDDIFF)
nD += erfc_doping(x, y, NPLUS_PEAK, NPLUS_X1, NPLUS_X2, NPLUS_HDDIFF, NPLUS_VDDIFF)
nD += erfc_doping(x, y, NPLUS_PEAK, -W_DEVICE, -MRING_X1, NPLUS_HDDIFF, NPLUS_VDDIFF)
nD += erfc_doping(x, y, NPLUS_PEAK, MRING_X1, W_DEVICE, NPLUS_HDDIFF, NPLUS_VDDIFF)
# Acceptors
nA = 1e10
nA += erfc_doping(x, y, P11_PEAK, -P11_X2, -P11_X1, P_WELL_HDDIFF, P_WELL_VDDIFF)
nA += erfc_doping(x, y, P11_PEAK, P11_X1, P11_X2, P_WELL_HDDIFF, P_WELL_VDDIFF)
nA += erfc_doping(x, y, P12_PEAK, -P12_X2, -P12_X1, P_WELL_HDDIFF, P_WELL_VDDIFF)
nA += erfc_doping(x, y, P12_PEAK, P12_X1, P12_X2, P_WELL_HDDIFF, P_WELL_VDDIFF)
nA += erfc_doping(x, y, P13_PEAK, -P13_X2, -P13_X1, P_WELL_HDDIFF, P_WELL_VDDIFF)
nA += erfc_doping(x, y, P13_PEAK, P13_X1, P13_X2, P_WELL_HDDIFF, P_WELL_VDDIFF)
return nD - nA
def generate_analytical_bgmesh():
device = "device_2d"
# Load the mesh generated by first pass of generate_mesh_2d.py (coarse base mesh)
print("Loading base mesh: device_2d.msh...")
devsim.create_gmsh_mesh(mesh=device, file="device_2d.msh")
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")
devsim.finalize_mesh(mesh=device)
devsim.create_device(mesh=device, device=device)
print("Calculating background mesh sizes based on analytical doping profile...")
LcMin = 0.15 * um
LcMax = 20.0 * um
N_offset = 1.0e10 # Intrinsic concentration offset to avoid division by zero
G_ref = 0.2 / um # Reference relative gradient (0.2 um^-1) for transition smoothness
bgmesh_path = "device_bgmesh.pos"
with open(bgmesh_path, "w") as f:
f.write('View "background mesh" {\n')
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))
if reg == "Silicon":
# Evaluate analytical doping at Silicon nodes
doping = get_doping_val(x, y)
for tri in triangles:
n0, n1, n2 = tri[0], tri[1], tri[2]
x0, y0 = x[n0], y[n0]
x1, y1 = x[n1], y[n1]
x2, y2 = x[n2], y[n2]
# Check doping values at the 3 triangle nodes
d0, d1, d2 = doping[n0], doping[n1], doping[n2]
# Triangle center coordinate
x_c = (x0 + x1 + x2) / 3.0
y_c = (y0 + y1 + y2) / 3.0
d_c = get_doping_val(x_c, y_c)
# Numerical gradient by finite difference (delta = 50nm)
delta = 0.05 * um
d_cx = get_doping_val(x_c + delta, y_c)
d_cy = get_doping_val(x_c, y_c + delta)
grad_x = (d_cx - d_c) / delta
grad_y = (d_cy - d_c) / delta
grad_mag = math.sqrt(grad_x**2 + grad_y**2)
# Relative gradient with intrinsic concentration offset
rel_grad = grad_mag / (abs(d_c) + N_offset)
# Exponential relative gradient mesh refinement
lc_val = LcMin + (LcMax - LcMin) * math.exp(-rel_grad / G_ref)
# Force maximum refinement if the triangle directly crosses the PN junction (doping sign change)
if (d0 * d1 < 0.0) or (d1 * d2 < 0.0) or (d2 * d0 < 0.0):
lc_val = LcMin
# Write Gmsh post-processing view format (ST: Scalar Triangle)
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")
elif reg == "Oxide":
# For Oxide region, keep mesh size refined to 0.5 * um to prevent distorted triangles in this thin layer
for tri in triangles:
n0, n1, n2 = tri[0], tri[1], tri[2]
x0, y0 = x[n0], y[n0]
x1, y1 = x[n1], y[n1]
x2, y2 = x[n2], y[n2]
lc_val = 0.5 * um
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")
else:
# For Molding, use LcMax as default (interfaces are refined by curves threshold)
for tri in triangles:
n0, n1, n2 = tri[0], tri[1], tri[2]
x0, y0 = x[n0], y[n0]
x1, y1 = x[n1], y[n1]
x2, y2 = x[n2], y[n2]
lc_val = LcMax
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(f"Analytical background mesh successfully written to {bgmesh_path}.")
if __name__ == "__main__":
generate_analytical_bgmesh()