Files
tcad-devsim_triac/device_pcad_config.py

341 lines
11 KiB
Python

# device_pcad_config.py
# Process-Step Oriented 2D Doping Configuration File
# This file coexists with device_config.py and keeps the original doping path intact.
import os
import sys
import numpy as np
import math
# All units in cm (1 um = 1e-4 cm)
um = 1e-4
# Import geometric and simulation parameters from the original device_config
from device_config import (
W_DEVICE, H_SI, T_OX, H_MOLD, W_SIDE_MOLD, W_SIM,
VIA_WIDTH, VIA_P11_X, VIA_P13_X,
MT1_FP1_X1, MT1_FP1_X2, MT1_FP2_X1, MT1_FP2_X2,
SIM_NAME
)
# Define mask openings (Horizontal coordinate ranges, positive X-axis. Mirroring is handled dynamically)
masks = {
'p_well_mask': [(75.0 * um, 100.0 * um)],
'p12_mask': [(120.0 * um, 130.0 * um)],
'p13_mask': [(150.0 * um, 255.0 * um)],
'nplus_mask': [(164.0 * um, 185.0 * um)],
'mring_mask': [(340.0 * um, W_DEVICE)],
}
# --- Process Database ---
# Implant Range & Straggle in Silicon (Energy in keV, Rp and dRp in microns)
IMPLANT_DB = {
'Boron': {
'energy': np.array([10.0, 20.0, 30.0, 50.0, 80.0, 100.0, 150.0, 200.0]),
'Rp': np.array([0.04, 0.07, 0.10, 0.16, 0.24, 0.30, 0.42, 0.55]),
'dRp': np.array([0.015, 0.025, 0.035, 0.050, 0.063, 0.070, 0.085, 0.100])
},
'Phosphorus': {
'energy': np.array([10.0, 20.0, 30.0, 50.0, 80.0, 100.0, 150.0, 200.0]),
'Rp': np.array([0.015, 0.028, 0.040, 0.065, 0.100, 0.120, 0.180, 0.240]),
'dRp': np.array([0.007, 0.012, 0.016, 0.025, 0.038, 0.045, 0.060, 0.075])
},
'Arsenic': {
'energy': np.array([10.0, 20.0, 30.0, 50.0, 80.0, 100.0, 150.0, 200.0]),
'Rp': np.array([0.010, 0.018, 0.025, 0.038, 0.058, 0.070, 0.100, 0.130]),
'dRp': np.array([0.004, 0.007, 0.009, 0.014, 0.020, 0.025, 0.035, 0.045])
}
}
# Diffusion Arrhenius parameters in Silicon (D0 in cm^2/s, Ea in eV)
DIFFUSION_DB = {
'Boron': {'D0': 1.0, 'Ea': 3.46},
'Phosphorus': {'D0': 10.5, 'Ea': 3.69},
'Arsenic': {'D0': 0.32, 'Ea': 3.56}
}
def get_implant_params(dopant, energy_kev):
"""Interpolate Rp and dRp from database for a given energy in keV. Returns values in cm."""
db = IMPLANT_DB.get(dopant, IMPLANT_DB['Boron'])
e = np.clip(energy_kev, db['energy'][0], db['energy'][-1])
Rp_um = np.interp(e, db['energy'], db['Rp'])
dRp_um = np.interp(e, db['energy'], db['dRp'])
return Rp_um * 1e-4, dRp_um * 1e-4
def get_diffusion_coefficient(dopant, temp_c):
"""Calculate the diffusion coefficient D in cm^2/s at temperature temp_c in Celsius."""
db = DIFFUSION_DB.get(dopant, DIFFUSION_DB['Boron'])
T_k = temp_c + 273.15
k_B = 8.617333262145e-5 # eV/K
D0 = db['D0']
Ea = db['Ea']
return D0 * np.exp(-Ea / (k_B * T_k))
# Define process steps for the 2D doping profile
process_steps = [
# Step 0: Substrate Wafer
{
'enabled': True,
'name': 'Substrate',
'method': 'Substrate',
'type': 'n',
'doping': 1.0e14, # N_SUB
},
# Step 1: P11 Well Implant & Diffusion
{
'enabled': True,
'name': 'P11_Well',
'method': 'Implant_and_Diffuse',
'type': 'p',
'dopant': 'Boron',
'energy': 80.0, # keV
'dose': 1.77245385091e12, # cm^-2
'mask': 'p_well_mask',
'temp': 1150.0, # Celsius
'time': 465.54227, # minutes
'lateral_ratio': 0.8,
},
# Step 2: P12 Well Implant & Diffusion
{
'enabled': True,
'name': 'P12_Well',
'method': 'Implant_and_Diffuse',
'type': 'p',
'dopant': 'Boron',
'energy': 80.0,
'dose': 6.6467019409e11,
'mask': 'p12_mask',
'temp': 1150.0,
'time': 465.54227,
'lateral_ratio': 0.8,
},
# Step 3: P13 Well Implant & Diffusion
{
'enabled': True,
'name': 'P13_Well',
'method': 'Implant_and_Diffuse',
'type': 'p',
'dopant': 'Boron',
'energy': 80.0,
'dose': 1.77245385091e12,
'mask': 'p13_mask',
'temp': 1150.0,
'time': 465.54227,
'lateral_ratio': 0.8,
},
# Step 4: N+ Source Active Region
{
'enabled': True,
'name': 'NPlus_Active',
'method': 'Implant_and_Diffuse',
'type': 'n',
'dopant': 'Phosphorus',
'energy': 50.0,
'dose': 2.65868077636e11,
'mask': 'nplus_mask',
'temp': 1000.0,
'time': 34.108586,
'lateral_ratio': 0.666666666667,
},
# Step 5: MRING Guard Ring Active Region
{
'enabled': True,
'name': 'MRing_Active',
'method': 'Implant_and_Diffuse',
'type': 'n',
'dopant': 'Phosphorus',
'energy': 50.0,
'dose': 2.65868077636e11,
'mask': 'mring_mask',
'temp': 1000.0,
'time': 34.108586,
'lateral_ratio': 0.666666666667,
}
]
def resolve_steps():
"""
Resolves the physical process steps into analytical model parameters
(peak, vdiff, hdiff, x_ranges) for profile evaluation.
"""
resolved = []
# 1. Resolve substrate first
sub_step = [s for s in process_steps if s['method'] == 'Substrate'][0]
if sub_step.get('enabled', True):
resolved.append({
'name': sub_step.get('name', 'Substrate'),
'method': 'Substrate',
'type': sub_step['type'],
'doping': sub_step['doping']
})
# 2. Resolve implant/diffusion steps
for step in process_steps:
if step['method'] == 'Substrate' or not step.get('enabled', True):
continue
name = step.get('name')
type_ = step['type']
dopant = step['dopant']
method = step['method']
# Look up mask coordinate ranges
mask_name = step['mask']
x_ranges = masks.get(mask_name, [])
# Get implant properties
energy = step.get('energy', 80.0)
dose = step.get('dose', 1.0e12)
Rp, dRp = get_implant_params(dopant, energy)
# Get thermal diffusion properties
temp = step.get('temp', 1000.0)
time_min = step.get('time', 30.0)
D = get_diffusion_coefficient(dopant, temp)
time_sec = time_min * 60.0
Dt = D * time_sec
# Standard deviation incorporating both implant straggle and thermal budget diffusion
vdiff = math.sqrt(2.0 * (dRp ** 2) + 4.0 * Dt)
# Horizontal standard deviation based on lateral ratio
lateral_ratio = step.get('lateral_ratio', 0.8)
hdiff = vdiff * lateral_ratio
# Calculate peak concentration matching the integrated dose for erfc profile:
# peak = dose / (vdiff * (sqrt(pi) / 2))
peak = dose / (vdiff * (math.sqrt(math.pi) / 2.0))
resolved.append({
'name': name,
'method': 'Implant_and_Diffuse',
'type': type_,
'peak': peak,
'vdiff': vdiff,
'hdiff': hdiff,
'x_ranges': x_ranges
})
return resolved
def apply_pcad_doping_2d(device, region="Silicon"):
"""
Parses the process steps list and registers the corresponding
2D analytical doping models in DEVSIM.
"""
import devsim
donor_terms = []
acceptor_terms = []
steps = resolve_steps()
# 1. Setup Substrate baseline first
sub_step = [s for s in steps if s['method'] == 'Substrate'][0]
sub_doping = sub_step['doping']
sub_type = sub_step['type']
devsim.node_model(device=device, region=region, name="nD_sub", equation=f"{sub_doping}")
if sub_type == 'n':
donor_terms.append("nD_sub")
else:
acceptor_terms.append("nD_sub")
# 2. Iterate and build expressions for Implant/Diffusion steps
idx = 1
for step in steps:
if step['method'] == 'Substrate':
continue
name = step['name']
type_ = step['type']
peak = step['peak']
x_ranges = step['x_ranges']
vdiff = step['vdiff']
hdiff = step['hdiff']
# Construct analytical 2D erfc expression for each window range, including mirroring
expr_terms = []
for x1, x2 in x_ranges:
# Right side (positive X)
expr_terms.append(f"erfc(y / {vdiff}) * 0.5 * (erf((x - ({x1})) / {hdiff}) - erf((x - ({x2})) / {hdiff}))")
# Left side (negative X, mirrored: x1 -> -x1 and x2 -> -x2, so window is [-x2, -x1])
expr_terms.append(f"erfc(y / {vdiff}) * 0.5 * (erf((x - ({-x2})) / {hdiff}) - erf((x - ({-x1})) / {hdiff}))")
# Combine terms and multiply by peak concentration
combined_expr = f"{peak} * (" + " + ".join(expr_terms) + ")"
model_name = f"nA_{name}" if type_ == 'p' else f"nD_{name}"
devsim.node_model(device=device, region=region, name=model_name, equation=combined_expr)
if type_ == 'n':
donor_terms.append(model_name)
else:
acceptor_terms.append(model_name)
idx += 1
# 3. Combine separate models into global Donors and Acceptors fields in DEVSIM
donor_equation = " + ".join(donor_terms)
acceptor_equation = "1e10 + " + " + ".join(acceptor_terms)
devsim.node_model(device=device, region=region, name="Donors", equation=donor_equation)
devsim.node_model(device=device, region=region, name="Acceptors", equation=acceptor_equation)
devsim.node_model(device=device, region=region, name="NetDoping", equation="Donors - Acceptors")
devsim.node_model(device=device, region=region, name="LogNetDoping", equation="asinh(NetDoping / 2.0) / log(10.0)")
def get_pcad_doping_val_2d(x, y):
"""
Evaluates the 2D process-step doping profile analytically on numpy arrays x and y.
"""
import numpy as np
import math
erf_vec = np.vectorize(math.erf)
erfc_vec = np.vectorize(math.erfc)
donors = np.zeros_like(x, dtype=float)
acceptors = np.zeros_like(x, dtype=float)
steps = resolve_steps()
# 1. Setup Substrate baseline first
sub_step = [s for s in steps if s['method'] == 'Substrate'][0]
sub_doping = sub_step['doping']
sub_type = sub_step['type']
if sub_type == 'n':
donors += sub_doping
else:
acceptors += sub_doping
# 2. Iterate and build expressions for Implant/Diffusion steps
for step in steps:
if step['method'] == 'Substrate':
continue
type_ = step['type']
peak = step['peak']
x_ranges = step['x_ranges']
vdiff = step['vdiff']
hdiff = step['hdiff']
prof = np.zeros_like(x, dtype=float)
for x1, x2 in x_ranges:
# Right side
prof += erfc_vec(y / vdiff) * 0.5 * (erf_vec((x - x1) / hdiff) - erf_vec((x - x2) / hdiff))
# Left side (mirrored)
prof += erfc_vec(y / vdiff) * 0.5 * (erf_vec((x - (-x2)) / hdiff) - erf_vec((x - (-x1)) / hdiff))
if type_ == 'n':
donors += peak * prof
else:
acceptors += peak * prof
# Acceptor base offset 1e10
acceptors += 1e10
return donors - acceptors