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()