Files
tcad-devsim_triac/generate_mesh_2d.py
T
2026-06-15 07:06:26 +08:00

365 lines
17 KiB
Python

import gmsh
import numpy as np
import os
from device_config import *
def create_mesh(y_box_max=12.0*um, y_medium_max=20.0*um, mesh_out="device_2d.msh", bgmesh_pos="device_bgmesh.pos"):
gmsh.initialize()
gmsh.model.add("device_2d")
# Use OpenCASCADE kernel
occ = gmsh.model.occ
# 1. Create Silicon substrate: Y in [0, H_SI]
silicon = occ.addRectangle(-W_DEVICE, 0, 0, 2 * W_DEVICE, H_SI)
# 2. Create Oxide layer: Y in [-T_OX, 0]
oxide_base = occ.addRectangle(-W_DEVICE, -T_OX, 0, 2 * W_DEVICE, T_OX)
# Helper to create via rectangles (metal openings)
def create_vias(occ_kernel):
mring_l = occ_kernel.addRectangle(-W_DEVICE, -T_OX, 0, (W_DEVICE - MRING_X1), T_OX)
mt2_v1 = occ_kernel.addRectangle(-VIA_P13_X - 0.5 * VIA_WIDTH, -T_OX, 0, VIA_WIDTH, T_OX)
mt2_v3 = occ_kernel.addRectangle(-VIA_P11_X - 0.5 * VIA_WIDTH, -T_OX, 0, VIA_WIDTH, T_OX)
mt1_v1 = occ_kernel.addRectangle(VIA_P11_X - 0.5 * VIA_WIDTH, -T_OX, 0, VIA_WIDTH, T_OX)
mt1_v3 = occ_kernel.addRectangle(VIA_P13_X - 0.5 * VIA_WIDTH, -T_OX, 0, VIA_WIDTH, T_OX)
mring_r = occ_kernel.addRectangle(MRING_X1, -T_OX, 0, (W_DEVICE - MRING_X1), T_OX)
return [(2, mring_l), (2, mt2_v1), (2, mt2_v3), (2, mt1_v1), (2, mt1_v3), (2, mring_r)]
# 3. Subtract vias from oxide to create oxide regions
vias_for_oxide = create_vias(occ)
oxide_cut_list, _ = occ.cut([(2, oxide_base)], vias_for_oxide)
# 4. Create Molding layer that covers the entire simulation domain:
# X in [-W_SIM, W_SIM], Y in [-T_OX - H_MOLD, H_SI]
molding_base = occ.addRectangle(-W_SIM, -T_OX - H_MOLD, 0, 2 * W_SIM, H_SI + T_OX + H_MOLD)
# Subtract vias from molding_base to ensure vias are not filled with molding compound
vias_for_mold = create_vias(occ)
molding_cut_list, _ = occ.cut([(2, molding_base)], vias_for_mold)
# Add dummy points at Y=0 to force fragmentation of Silicon surface for P12 virtual contacts
p1 = occ.addPoint(-P12_X2, 0, 0)
p2 = occ.addPoint(-P12_X1, 0, 0)
p3 = occ.addPoint(P12_X1, 0, 0)
p4 = occ.addPoint(P12_X2, 0, 0)
dummy_points = [(0, p1), (0, p2), (0, p3), (0, p4)]
# Add dummy points at Y=-T_OX to force fragmentation of oxide-molding interface for field plates
fp_points = []
fp_x_list = [
-MT1_FP2_X2, -MT1_FP2_X1,
-MT1_FP1_X2, -MT1_FP1_X1,
MT1_FP1_X1, MT1_FP1_X2,
MT1_FP2_X1, MT1_FP2_X2
]
for i, x_val in enumerate(fp_x_list):
pt = occ.addPoint(x_val, -T_OX, 0)
fp_points.append((0, pt))
# Now fragment the silicon substrate, the remaining oxide, and the remaining molding layer, along with dummy points and field plate points
out, out_map = occ.fragment([(2, silicon)] + dummy_points + fp_points, oxide_cut_list + molding_cut_list)
occ.synchronize()
# Define physical groups for regions
silicon_tags = []
oxide_tags = []
molding_tags = []
for ent in gmsh.model.getEntities(dim=2):
tag = ent[1]
mass_center = occ.getCenterOfMass(2, tag)
x_c, y_c = mass_center[0], mass_center[1]
# Check if it is inside Silicon die boundaries
if y_c >= -1e-8 and abs(x_c) <= W_DEVICE + 1e-8:
silicon_tags.append(tag)
# Check if it is inside Oxide layer boundaries
elif y_c < -1e-8 and y_c >= -T_OX - 1e-8 and abs(x_c) <= W_DEVICE + 1e-8:
oxide_tags.append(tag)
# Otherwise it is molding compound (top or sides)
else:
molding_tags.append(tag)
gmsh.model.addPhysicalGroup(2, silicon_tags, tag=1, name="Silicon")
gmsh.model.addPhysicalGroup(2, oxide_tags, tag=2, name="Oxide")
gmsh.model.addPhysicalGroup(2, molding_tags, tag=3, name="Molding")
# Bounding box epsilon
eps = 0.01 * um
mt1_si_curves = []
mt2_si_curves = []
p12_l_si_curves = []
p12_r_si_curves = []
mring_l_si_curves = []
mring_r_si_curves = []
# Contacts for Oxide
mt1_ox_curves = []
mt2_ox_curves = []
mring_l_ox_curves = []
mring_r_ox_curves = []
# Contacts for Molding
mt1_mold_curves = []
mt2_mold_curves = []
mring_l_mold_curves = []
mring_r_mold_curves = []
silicon_oxide_interface_curves = []
substrate_bottom_si_curves = []
substrate_bottom_mold_curves = []
silicon_molding_side_curves = []
ox_mold_interface_curves = []
molding_top_curves = []
def is_in_via_opening(xmin, xmax):
via_ranges = [
(-VIA_P13_X - 0.5 * VIA_WIDTH, -VIA_P13_X + 0.5 * VIA_WIDTH),
(-VIA_P11_X - 0.5 * VIA_WIDTH, -VIA_P11_X + 0.5 * VIA_WIDTH),
(VIA_P11_X - 0.5 * VIA_WIDTH, VIA_P11_X + 0.5 * VIA_WIDTH),
(VIA_P13_X - 0.5 * VIA_WIDTH, VIA_P13_X + 0.5 * VIA_WIDTH)
]
for vl, vh in via_ranges:
if xmin >= vl - eps and xmax <= vh + eps:
return True
return False
curves = gmsh.model.getEntities(dim=1)
for c in curves:
c_tag = c[1]
xmin, ymin, zmin, xmax, ymax, zmax = gmsh.model.getBoundingBox(1, c_tag)
# Check if it lies on the substrate bottom boundary Y = H_SI
if abs(ymin - H_SI) < eps and abs(ymax - H_SI) < eps:
if abs(xmin) <= W_DEVICE + eps and abs(xmax) <= W_DEVICE + eps:
substrate_bottom_si_curves.append(c_tag)
else:
substrate_bottom_mold_curves.append(c_tag)
continue
# Check if it lies at Y = 0 (Silicon-Oxide interface or contacts at Y=0)
if abs(ymin) < eps and abs(ymax) < eps:
# MT2 Left Via contact
if xmin >= (-VIA_P13_X - 0.5*VIA_WIDTH) - eps and xmax <= (-VIA_P13_X + 0.5*VIA_WIDTH) + eps:
mt2_si_curves.append(c_tag)
# MT2 Right Via contact (p11_left)
elif xmin >= (-VIA_P11_X - 0.5*VIA_WIDTH) - eps and xmax <= (-VIA_P11_X + 0.5*VIA_WIDTH) + eps:
mt2_si_curves.append(c_tag)
# MT1 Left Via contact (p11_right)
elif xmin >= (VIA_P11_X - 0.5*VIA_WIDTH) - eps and xmax <= (VIA_P11_X + 0.5*VIA_WIDTH) + eps:
mt1_si_curves.append(c_tag)
# MT1 Right Via contact (p13_right N+)
elif xmin >= (VIA_P13_X - 0.5*VIA_WIDTH) - eps and xmax <= (VIA_P13_X + 0.5*VIA_WIDTH) + eps:
mt1_si_curves.append(c_tag)
# P12 Left virtual contact (connected to MT2)
elif xmin >= -P12_X2 - eps and xmax <= -P12_X1 + eps:
p12_l_si_curves.append(c_tag)
# P12 Right virtual contact (connected to MT1)
elif xmin >= P12_X1 - eps and xmax <= P12_X2 + eps:
p12_r_si_curves.append(c_tag)
# MRING Left contact at Y=0
elif xmin >= -W_DEVICE - eps and xmax <= -MRING_X1 + eps:
mring_l_si_curves.append(c_tag)
# MRING Right contact at Y=0
elif xmin >= MRING_X1 - eps and xmax <= W_DEVICE + eps:
mring_r_si_curves.append(c_tag)
else:
silicon_oxide_interface_curves.append(c_tag)
continue
# Check if it lies on the top boundary of Molding: Y = -T_OX - H_MOLD
if abs(ymin - (-T_OX - H_MOLD)) < eps and abs(ymax - (-T_OX - H_MOLD)) < eps:
molding_top_curves.append(c_tag)
continue
# Check if it lies at Y = -T_OX (oxide-molding interface and field plates)
if abs(ymin + T_OX) < eps and abs(ymax + T_OX) < eps:
# MT2 field plates: [-MT1_FP2_X2, -MT1_FP2_X1] and [-MT1_FP1_X2, -MT1_FP1_X1]
if (xmin >= -MT1_FP2_X2 - eps and xmax <= -MT1_FP2_X1 + eps) or \
(xmin >= -MT1_FP1_X2 - eps and xmax <= -MT1_FP1_X1 + eps):
mt2_mold_curves.append(c_tag)
if not is_in_via_opening(xmin, xmax):
mt2_ox_curves.append(c_tag)
# MT1 field plates: [MT1_FP1_X1, MT1_FP1_X2] and [MT1_FP2_X1, MT1_FP2_X2]
elif (xmin >= MT1_FP1_X1 - eps and xmax <= MT1_FP1_X2 + eps) or \
(xmin >= MT1_FP2_X1 - eps and xmax <= MT1_FP2_X2 + eps):
mt1_mold_curves.append(c_tag)
if not is_in_via_opening(xmin, xmax):
mt1_ox_curves.append(c_tag)
# MRING Left top: [-W_DEVICE, -MRING_X1]
elif xmin >= -W_DEVICE - eps and xmax <= -MRING_X1 + eps:
mring_l_mold_curves.append(c_tag)
# MRING Right top: [MRING_X1, W_DEVICE]
elif xmin >= MRING_X1 - eps and xmax <= W_DEVICE + eps:
mring_r_mold_curves.append(c_tag)
else:
ox_mold_interface_curves.append(c_tag)
continue
# Check for vertical curves: abs(xmin - xmax) < eps
if abs(xmin - xmax) < eps:
x_coord = (xmin + xmax) / 2.0
# Check for Silicon-Molding side boundaries: at X = +-W_DEVICE and Y in [0, H_SI]
if (abs(x_coord - W_DEVICE) < eps or abs(x_coord - (-W_DEVICE)) < eps) and ymin >= -eps and ymax <= H_SI + eps:
silicon_molding_side_curves.append(c_tag)
continue
# Check for vertical sidewalls of the vias (which are metal-oxide interfaces)
# These are vertical lines between Y = -T_OX and Y = 0
if ymin >= -T_OX - eps and ymax <= eps:
# Vias for MT2
if (abs(x_coord - (-VIA_P13_X - 0.5*VIA_WIDTH)) < eps or abs(x_coord - (-VIA_P13_X + 0.5*VIA_WIDTH)) < eps or
abs(x_coord - (-VIA_P11_X - 0.5*VIA_WIDTH)) < eps or abs(x_coord - (-VIA_P11_X + 0.5*VIA_WIDTH)) < eps):
mt2_ox_curves.append(c_tag)
# Vias for MT1
elif (abs(x_coord - (VIA_P11_X - 0.5*VIA_WIDTH)) < eps or abs(x_coord - (VIA_P11_X + 0.5*VIA_WIDTH)) < eps or
abs(x_coord - (VIA_P13_X - 0.5*VIA_WIDTH)) < eps or abs(x_coord - (VIA_P13_X + 0.5*VIA_WIDTH)) < eps):
mt1_ox_curves.append(c_tag)
# Vias/sidewalls for MRING (Oxide-MRING interface)
elif abs(x_coord - (-MRING_X1)) < eps:
mring_l_ox_curves.append(c_tag)
elif abs(x_coord - (MRING_X1)) < eps:
mring_r_ox_curves.append(c_tag)
# Outer side of MRING touching Molding (at X = +-W_DEVICE, Y in [-T_OX, 0])
elif abs(x_coord - (-W_DEVICE)) < eps:
mring_l_mold_curves.append(c_tag)
elif abs(x_coord - (W_DEVICE)) < eps:
mring_r_mold_curves.append(c_tag)
# Register the physical groups for boundaries
if mt1_si_curves:
gmsh.model.addPhysicalGroup(1, mt1_si_curves, name="MT1_Si")
if mt2_si_curves:
gmsh.model.addPhysicalGroup(1, mt2_si_curves, name="MT2_Si")
if p12_l_si_curves:
gmsh.model.addPhysicalGroup(1, p12_l_si_curves, name="MT2_P12_Si")
if p12_r_si_curves:
gmsh.model.addPhysicalGroup(1, p12_r_si_curves, name="MT1_P12_Si")
if mring_l_si_curves:
gmsh.model.addPhysicalGroup(1, mring_l_si_curves, name="MRING_L_Si")
if mring_r_si_curves:
gmsh.model.addPhysicalGroup(1, mring_r_si_curves, name="MRING_R_Si")
if mt1_ox_curves:
gmsh.model.addPhysicalGroup(1, mt1_ox_curves, name="MT1_Ox")
if mt1_mold_curves:
gmsh.model.addPhysicalGroup(1, mt1_mold_curves, name="MT1_Mold")
if mt2_ox_curves:
gmsh.model.addPhysicalGroup(1, mt2_ox_curves, name="MT2_Ox")
if mt2_mold_curves:
gmsh.model.addPhysicalGroup(1, mt2_mold_curves, name="MT2_Mold")
if mring_l_ox_curves:
gmsh.model.addPhysicalGroup(1, mring_l_ox_curves, name="MRING_L_Ox")
if mring_l_mold_curves:
gmsh.model.addPhysicalGroup(1, mring_l_mold_curves, name="MRING_L_Mold")
if mring_r_ox_curves:
gmsh.model.addPhysicalGroup(1, mring_r_ox_curves, name="MRING_R_Ox")
if mring_r_mold_curves:
gmsh.model.addPhysicalGroup(1, mring_r_mold_curves, name="MRING_R_Mold")
if silicon_oxide_interface_curves:
gmsh.model.addPhysicalGroup(1, silicon_oxide_interface_curves, name="Si_Ox_Interface")
if substrate_bottom_si_curves:
gmsh.model.addPhysicalGroup(1, substrate_bottom_si_curves, name="Substrate_Bottom")
if substrate_bottom_mold_curves:
gmsh.model.addPhysicalGroup(1, substrate_bottom_mold_curves, name="Substrate_Bottom_Mold")
if silicon_molding_side_curves:
gmsh.model.addPhysicalGroup(1, silicon_molding_side_curves, name="Si_Mold_Interface")
if ox_mold_interface_curves:
gmsh.model.addPhysicalGroup(1, ox_mold_interface_curves, name="Ox_Mold_Interface")
if molding_top_curves:
gmsh.model.addPhysicalGroup(1, molding_top_curves, name="Molding_Top")
# Set mesh size field for high resolution near all interfaces and electrode edges
gmsh.model.mesh.field.add("Distance", 1)
target_curves = (silicon_oxide_interface_curves + mt1_si_curves + mt2_si_curves +
ox_mold_interface_curves + mt1_ox_curves + mt2_ox_curves +
p12_l_si_curves + p12_r_si_curves +
mring_l_si_curves + mring_r_si_curves +
mring_l_ox_curves + mring_r_ox_curves +
mring_l_mold_curves + mring_r_mold_curves)
gmsh.model.mesh.field.setNumbers(1, "CurvesList", target_curves)
gmsh.model.mesh.field.add("Threshold", 2)
gmsh.model.mesh.field.setNumber(2, "IField", 1)
gmsh.model.mesh.field.setNumber(2, "LcMin", 0.15 * um) # 0.15 um near interfaces
gmsh.model.mesh.field.setNumber(2, "LcMax", 20.0 * um) #放寬至 20 um
gmsh.model.mesh.field.setNumber(2, "DistMin", 0.15 * um) # Concentrated near interfaces
gmsh.model.mesh.field.setNumber(2, "DistMax", 1.0 * um) # Coarsen rapidly at 1.0 um
# Box field to transition background mesh size in the active well region to 1.5 um, with 10 um transition zone to 20.0 um
gmsh.model.mesh.field.add("Box", 3)
gmsh.model.mesh.field.setNumber(3, "VIn", 1.5 * um) # Background surface mesh is 1.5 um
gmsh.model.mesh.field.setNumber(3, "VOut", 20.0 * um) # Outer mesh size (LcMax)
gmsh.model.mesh.field.setNumber(3, "Thickness", 10.0 * um) # 10 um transition zone
gmsh.model.mesh.field.setNumber(3, "XMin", -W_DEVICE)
gmsh.model.mesh.field.setNumber(3, "XMax", W_DEVICE)
gmsh.model.mesh.field.setNumber(3, "YMin", 0.0)
gmsh.model.mesh.field.setNumber(3, "YMax", y_box_max)
# Medium box field to transition background mesh size to 4.0 um, with 10 um transition zone to 20.0 um
gmsh.model.mesh.field.add("Box", 5)
gmsh.model.mesh.field.setNumber(5, "VIn", 4.0 * um) # Medium density region is 4.0 um
gmsh.model.mesh.field.setNumber(5, "VOut", 20.0 * um)
gmsh.model.mesh.field.setNumber(5, "Thickness", 10.0 * um)
gmsh.model.mesh.field.setNumber(5, "XMin", -W_DEVICE)
gmsh.model.mesh.field.setNumber(5, "XMax", W_DEVICE)
gmsh.model.mesh.field.setNumber(5, "YMin", 0.0)
gmsh.model.mesh.field.setNumber(5, "YMax", y_medium_max)
# Combine threshold field, box field, and medium box field using Min field
gmsh.model.mesh.field.add("Min", 4)
gmsh.model.mesh.field.setNumbers(4, "FieldsList", [2, 3, 5])
# Restrict the combined field to only Silicon and Oxide regions
restrict_field = gmsh.model.mesh.field.add("Restrict")
gmsh.model.mesh.field.setNumbers(restrict_field, "SurfacesList", silicon_tags + oxide_tags)
gmsh.model.mesh.field.setNumber(restrict_field, "IField", 4)
# If background mesh file exists, merge it and combine with restricted field using Min field
if bgmesh_pos and os.path.exists(bgmesh_pos):
gmsh.merge(bgmesh_pos)
bgm_field = gmsh.model.mesh.field.add("PostView")
gmsh.model.mesh.field.setNumber(bgm_field, "ViewIndex", 0)
# Restrict the bgm_field to Silicon and Oxide only
restrict_bgm = gmsh.model.mesh.field.add("Restrict")
gmsh.model.mesh.field.setNumbers(restrict_bgm, "SurfacesList", silicon_tags + oxide_tags)
gmsh.model.mesh.field.setNumber(restrict_bgm, "IField", bgm_field)
min_field = gmsh.model.mesh.field.add("Min")
gmsh.model.mesh.field.setNumbers(min_field, "FieldsList", [restrict_field, restrict_bgm])
gmsh.model.mesh.field.setAsBackgroundMesh(min_field)
print("Successfully merged and combined background mesh with restricted field using Min field.")
else:
gmsh.model.mesh.field.setAsBackgroundMesh(restrict_field)
print("Set restricted field as background mesh.")
# Force MSH 2.2 output format and set global size limits and gradation
gmsh.option.setNumber("Mesh.MshFileVersion", 2.2)
gmsh.option.setNumber("Mesh.MeshSizeMin", 0.15 * um)
gmsh.option.setNumber("Mesh.MeshSizeMax", 20.0 * um)
# Note: Mesh.CharacteristicLengthGradation is unsupported in Gmsh 4.12.1 and throws an exception.
# Mesh size gradation is managed via custom fields (Distance and Threshold) in Silicon.
# Generate 2D mesh
gmsh.model.mesh.generate(2)
gmsh.write(mesh_out)
gmsh.finalize()
print("Mesh generation complete! Saved as device_2d.msh.")
if __name__ == "__main__":
create_mesh()