Analytical process simulation#

The simplest (and quickest) process simulation involves analytical implant tables, followed by broadening of the profile according to a tabulated diffusion coefficient.

The implant module of gdsfactory tabulates these expressions for silicon and common implants, and allows you to import them to TCAD solvers.

For more exact implant simulation, see the numerical implantation and numerical diffusion modules.

References:

[1] Selberherr, S. (1984). Process Modeling. In: Analysis and Simulation of Semiconductor Devices. Springer, Vienna. https://doi.org/10.1007/978-3-7091-8752-4_3

import matplotlib.pyplot as plt
import numpy as np

from gplugins.process.diffusion import D, silicon_diffused_gaussian_profile
from gplugins.process.implant_tables import (
    depth_in_silicon,
    silicon_gaussian_profile,
    silicon_skewed_gaussian_profile,
    skew_in_silicon,
    straggle_in_silicon,
)

Gaussian profile#

The simplest approximation to an implantation profile only considers the range (mean) and straggle (standard deviation) of the ion distribution:

energies = np.linspace(0, 300, 1000)  # keV

for dopant in ["boron", "phosphorus", "arsenic", "antimony"]:
    plt.plot(energies, depth_in_silicon[dopant](energies), label=dopant)

plt.xlabel("Energy (keV)")
plt.ylabel("range (um)")
plt.legend(loc="best")
plt.title("Ion implantation in silicon, range")
Text(0.5, 1.0, 'Ion implantation in silicon, range')
../_images/9f9a95b43c1c9a1ef94308fa2667b8edbecd3106f7f56fa83cfbf963c15d31c8.png
for dopant in ["boron", "phosphorus", "arsenic", "antimony"]:
    plt.plot(energies, straggle_in_silicon[dopant](energies), label=dopant)

plt.xlabel("Energy (keV)")
plt.ylabel("straggle (um)")
plt.legend(loc="best")
plt.title("Ion implantation in silicon, straggle")
Text(0.5, 1.0, 'Ion implantation in silicon, straggle')
../_images/92f83f98e2d0a0a44761573b73c0450ba12e8d2883c6931d7251667bef446dc0.png
energies = [20, 40, 60, 80, 100, 120, 140, 160]
z = np.linspace(0, 0.25, 1000)


lower_lim = 0
for E in energies:
    c = silicon_gaussian_profile("arsenic", dose=1e15, E=E, z=z)
    plt.semilogy(z, c, label=E)
    if c[0] > lower_lim:
        lower_lim = c[0]

plt.xlabel("depth (um)")
plt.ylabel("ion concentration (cm-3)")
plt.ylim([lower_lim, 1e21])
plt.xlim([0, 0.2])
plt.title("Gaussian implantation profile (1E15 cm-2 arsenic)")
plt.legend(title="Energy (keV)")
<matplotlib.legend.Legend at 0x7f9c5a256210>
../_images/65b60326e9881bffef669f4327262d0fca2a08ede616fd84cba61def00952bff.png

Skewed Gaussian profile#

For more realistic results with nonzero skewness (we catalog the skews of [1], but your mileage may vary), two half-gaussians can better approximate the distribution:

for dopant in ["boron", "phosphorus", "arsenic", "antimony"]:
    plt.plot(energies, skew_in_silicon[dopant](energies), label=dopant)

plt.xlabel("Energy (keV)")
plt.ylabel("skewness (unitless)")
plt.legend(loc="best")
plt.title("Ion implantation in silicon, skewness")
Text(0.5, 1.0, 'Ion implantation in silicon, skewness')
../_images/79998d8ada211c0799d56b8cfa82b0b985e90ebe78e7dcf852c3748bf014e584.png
energies = [20, 40, 60, 80, 100, 120, 140, 160]
z = np.linspace(0, 0.25, 1000)


lower_lim = 0
for E in energies:
    c = silicon_skewed_gaussian_profile("arsenic", dose=1e11, E=E, z=z)
    plt.semilogy(z, c, label=E)
    if c[0] > lower_lim:
        lower_lim = c[0]

plt.xlabel("depth (um)")
plt.ylabel("ion concentration (cm-2)")
plt.ylim([lower_lim, 1e18])
plt.xlim([0, 0.2])
plt.title("Joined half Gaussian implantation profile (|skew| <~ 1)")
plt.legend(title="Energy (keV)")
<matplotlib.legend.Legend at 0x7f9c58ec1cd0>
../_images/1907b5efb908729ba8f82dacd97597814b029e6176ec4188af950494834fffcf.png

The above can be further improved by considering a Pearson IV distribution, or performing Monte-Carlo simulations of implantation.

Diffusion#

Thermal treatment of the wafer to anneal implantation defects and activate the dopants results in rearrangement of the doping ions, and hence a change in doping profile. This is governed by the diffusivity of the species in the crystal:

Ts = np.linspace(800, 1100, 100)

for dopant in ["boron", "phosphorus", "arsenic", "antimony"]:
    plt.semilogy(Ts, D(dopant, Ts), label=dopant)

plt.xlabel("Temperature (C)")
plt.ylabel("Diffusivity (cm2 s-1)")
plt.title("Intrinsic diffusivity (n=p=ni)")
plt.legend()
<matplotlib.legend.Legend at 0x7f9c58cf22d0>
../_images/56d5ef555c53154bfed783a69860a2b9ead72e2fe52983c24e4502b44048afe0.png

In this low-doping regime, the diffusivity can be taken as constant, and there exist an analytical solution for initially Gaussian doping profiles:

T = 1000  # C
dopant = "phosphorus"
dose = 1e12  # ions/cm2
E = 100  # keV
z = np.linspace(0, 0.6, 1000)

for t in [0, 60, 5 * 60, 10 * 60]:
    conc = silicon_diffused_gaussian_profile(
        dopant=dopant,
        dose=dose,
        E=E,
        t=t,
        T=T,
        z=z,
    )
    plt.plot(conc, label=t)
plt.title(f"{dopant} dose={dose:1.0e} ions/cm2, E={E}keV")
plt.xlabel("depth (um)")
plt.ylabel("ion concentration (cm-3)")
plt.legend(title=f"Diffusion time @ {T}C (s)")
plt.show()
../_images/1d80893437315b584624847bc917fc32f998f8b7b7af73ce5c92b42b800e07ed.png

This can be extended to many dimensions.

Concentration-dependent diffusion#

In general, especially at higher concentrations, diffusivity depends on concentration:

Ts = [800, 850, 900, 950, 1000, 1050]
conc = np.logspace(18, 21, 100)

for T in Ts:
    plt.loglog(conc, D("boron", T, n=conc, p=conc), label=T)

plt.xlabel("Acceptor concentration (cm-3)")
plt.ylabel("Diffusivity (cm2 s-1)")
plt.title("Diffusivity of boron (n=p=ni)")
plt.legend()
<matplotlib.legend.Legend at 0x7f9c58d49150>
../_images/0cbfb9d1acecb41ac2e093d6b14ad6c2f4082cf49b70a96585979f844b3a7c95.png
for T in Ts:
    plt.loglog(conc, D("phosphorus", T, n=conc, p=conc), label=T)

plt.xlabel("Donor concentration (cm-3)")
plt.ylabel("Diffusivity (cm2 s-1)")
plt.title("Diffusivity of phosphorus (n=p=ni)")
plt.legend()
<matplotlib.legend.Legend at 0x7f9c58a32d10>
../_images/c007e54ef708b8fd11133c22f1fd164e3fe75a34d0aaee1a0a542ff39a848832.png
for T in Ts:
    plt.loglog(conc, D("antimony", T, n=conc, p=conc), label=T)

plt.xlabel("Donor concentration (cm-3)")
plt.ylabel("Diffusivity (cm2 s-1)")
plt.title("Diffusivity of antimony (n=p=ni)")
plt.legend()
<matplotlib.legend.Legend at 0x7f9c587e9150>
../_images/fbf4255e962fd803584a8f80b06750b11f9105889f8bb3165e8ce544405a2c89.png
for T in Ts:
    plt.loglog(conc, D("arsenic", T, n=conc, p=conc), label=T)

plt.xlabel("Donor concentration (cm-3)")
plt.ylabel("Diffusivity (cm2 s-1)")
plt.title("Diffusivity of arsenic (n=p=ni)")
plt.legend()
<matplotlib.legend.Legend at 0x7f9c5856cc50>
../_images/5b6b1e6403ab9f0950e379aafba837a34bededc4fe8b851b5ba49bfdc4b29adf.png

The most generic solution considers the local forms of these concentration-dependent diffusivities for the diffusion equation of each dopant, as well as the electrostatic potential, in a finite-element scheme.