365 lines
17 KiB
Python
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()
|