Skip to content

BARL Design

This recipe demonstrates multi-layer Bottom Anti-Reflective Layer (BARL) optimization, including the quarter-wave condition, thickness sweeps, and broadband ARC design for BSI pixels.

Background

The BARL sits between the color filter and the silicon photodiode. Without an anti-reflective coating, the air/SiO2/silicon interface reflects 15--30% of incident light, significantly reducing QE. A well-designed BARL stack reduces this reflection to under 5% across the visible spectrum. Real BARL recipes — the choice of materials, the number of layers, and their stacking order — vary widely between vendors and are usually proprietary; the stacks used below are illustrative starting points for a design sweep, not a canonical recipe.

The quarter-wave condition for a single-layer ARC is:

nARC=n1n2,tARC=λ04nARC

For multi-layer stacks, numerical optimization with COMPASS replaces analytical design rules.

Setup

python
import numpy as np
import copy
import matplotlib.pyplot as plt
from compass.runners.single_run import SingleRunner

base_config = {
    "pixel": {
        "pitch": 1.0,
        "unit_cell": [2, 2],
        "bayer_map": [["R", "G"], ["G", "B"]],
        "layers": {
            "air": {"thickness": 1.0, "material": "air"},
            "microlens": {
                "enabled": True, "height": 0.6,
                "radius_x": 0.48, "radius_y": 0.48,
            },
            "planarization": {"thickness": 0.3, "material": "sio2"},
            "color_filter": {
                "red": {"material": "cf_red", "thickness": 0.62},
                "green": {"material": "cf_green", "thickness": 0.60},
                "blue": {"material": "cf_blue", "thickness": 0.65},
                "grid": {"enabled": True, "width": 0.05, "thickness": 0.47, "material": "tungsten"},
            },
            "barl": {
                "layers": [
                    {"thickness": 0.010, "material": "sio2"},
                    {"thickness": 0.025, "material": "hfo2"},
                    {"thickness": 0.015, "material": "sio2"},
                    {"thickness": 0.030, "material": "si3n4"},
                ]
            },
            "silicon": {
                "thickness": 3.0, "material": "silicon",
                "photodiode": {"position": [0, 0, 0.5], "size": [0.7, 0.7, 2.0]},
                "dti": {"enabled": True, "width": 0.1, "material": "sio2"},
            },
        },
    },
    "solver": {
        "name": "torcwa", "type": "rcwa",
        "params": {"fourier_order": [9, 9]},
        "stability": {"precision_strategy": "mixed", "fourier_factorization": "li_inverse"},
    },
    "source": {
        "wavelength": {"mode": "sweep", "sweep": {"start": 0.40, "stop": 0.70, "step": 0.01}},
        "polarization": "unpolarized",
    },
    "compute": {"backend": "auto"},
}

Baseline: no BARL vs with BARL

Compare QE with and without the anti-reflective layers:

python
# With BARL (baseline config)
result_with = SingleRunner.run(base_config)

# Without BARL (remove BARL layers)
config_no_barl = copy.deepcopy(base_config)
config_no_barl["pixel"]["layers"]["barl"]["layers"] = []
result_without = SingleRunner.run(config_no_barl)

from compass.visualization.qe_plot import plot_qe_comparison

fig, ax = plt.subplots(figsize=(10, 6))
plot_qe_comparison(
    results=[result_with, result_without],
    labels=["With BARL", "No BARL"],
    ax=ax,
)
ax.set_title("BARL Effect on QE")
plt.tight_layout()
plt.savefig("barl_vs_no_barl.png", dpi=150)

Interactive Thin Film Reflectance Calculator

Compute reflectance spectra using the transfer matrix method for common anti-reflection coating configurations.

Layer 1Si3N4 (n=2.00)
Min Reflectance
0.00%
At Wavelength
552 nm
Substrate
Si (n=4.0)
Incident Medium
Air (n=1.0)
Reflectance (%)05101520400450500550600650700750Wavelength (nm)

Quarter-wave single-layer design

Design a single-layer ARC optimized for 550 nm (green peak):

python
# Ideal ARC refractive index for SiO2 (n~1.46) to Si (n~4.08) interface
n_sio2 = 1.46
n_si = 4.08
n_ideal = np.sqrt(n_sio2 * n_si)
print(f"Ideal ARC index: {n_ideal:.2f}")  # ~2.44

# Quarter-wave thickness at 550 nm
wl_design = 0.55  # um
t_qw = wl_design / (4 * n_ideal)
print(f"Quarter-wave thickness: {t_qw*1000:.1f} nm")  # ~56 nm

# Si3N4 (n~2.0) is the closest standard material
t_si3n4_qw = wl_design / (4 * 2.0)
print(f"Si3N4 quarter-wave: {t_si3n4_qw*1000:.1f} nm")  # ~69 nm

Test this single-layer design:

python
config_single = copy.deepcopy(base_config)
config_single["pixel"]["layers"]["barl"]["layers"] = [
    {"thickness": 0.069, "material": "si3n4"},
]
result_single = SingleRunner.run(config_single)

Thickness sweep for single-layer BARL

Sweep the Si3N4 thickness to find the optimum:

python
thicknesses = np.arange(0.020, 0.151, 0.005)  # 20 to 150 nm
avg_green_qe = []

for t in thicknesses:
    cfg = copy.deepcopy(base_config)
    cfg["pixel"]["layers"]["barl"]["layers"] = [
        {"thickness": float(t), "material": "si3n4"},
    ]
    result = SingleRunner.run(cfg)
    green_qe = np.mean([
        qe for name, qe in result.qe_per_pixel.items() if name.startswith("G")
    ], axis=0)
    # Average across wavelengths
    avg_green_qe.append(float(np.mean(green_qe)))

plt.figure(figsize=(8, 5))
plt.plot(thicknesses * 1000, avg_green_qe, "o-", linewidth=2)
plt.axvline(69, color="red", linestyle="--", alpha=0.5, label="Quarter-wave (69 nm)")
plt.xlabel("Si3N4 Thickness (nm)")
plt.ylabel("Average Green QE (400-700 nm)")
plt.title("Single-Layer BARL Thickness Sweep")
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig("barl_thickness_sweep.png", dpi=150)

Multi-layer broadband BARL design

A single layer is optimized for one wavelength. For broadband performance, use alternating high-index and low-index layers:

python
# Two-layer: HfO2 (high-n) + SiO2 (low-n)
designs = {
    "2-layer HfO2/SiO2": [
        {"thickness": 0.025, "material": "hfo2"},
        {"thickness": 0.035, "material": "sio2"},
    ],
    "3-layer SiO2/HfO2/SiO2": [
        {"thickness": 0.010, "material": "sio2"},
        {"thickness": 0.030, "material": "hfo2"},
        {"thickness": 0.020, "material": "sio2"},
    ],
    "4-layer (baseline)": [
        {"thickness": 0.010, "material": "sio2"},
        {"thickness": 0.025, "material": "hfo2"},
        {"thickness": 0.015, "material": "sio2"},
        {"thickness": 0.030, "material": "si3n4"},
    ],
}

fig, ax = plt.subplots(figsize=(10, 6))

for name, layers in designs.items():
    cfg = copy.deepcopy(base_config)
    cfg["pixel"]["layers"]["barl"]["layers"] = layers
    result = SingleRunner.run(cfg)

    green_qe = np.mean([
        qe for pname, qe in result.qe_per_pixel.items() if pname.startswith("G")
    ], axis=0)

    total_t = sum(l["thickness"] for l in layers) * 1000
    ax.plot(result.wavelengths * 1000, green_qe,
            label=f"{name} ({total_t:.0f} nm total)", linewidth=2)

ax.set_xlabel("Wavelength (nm)")
ax.set_ylabel("Green QE")
ax.set_title("Multi-Layer BARL Design Comparison")
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig("barl_multilayer_comparison.png", dpi=150)

HfO2 thickness sweep in a 2-layer stack

Fix SiO2 at 15 nm and sweep HfO2 thickness:

python
hfo2_thicknesses = np.arange(0.010, 0.061, 0.005)
sio2_thickness = 0.015

broadband_qe = []

for t_hfo2 in hfo2_thicknesses:
    cfg = copy.deepcopy(base_config)
    cfg["pixel"]["layers"]["barl"]["layers"] = [
        {"thickness": float(sio2_thickness), "material": "sio2"},
        {"thickness": float(t_hfo2), "material": "hfo2"},
    ]
    result = SingleRunner.run(cfg)

    # Broadband average QE across all channels
    all_qe = np.mean([qe for qe in result.qe_per_pixel.values()], axis=0)
    broadband_qe.append(float(np.mean(all_qe)))

plt.figure(figsize=(8, 5))
plt.plot(hfo2_thicknesses * 1000, broadband_qe, "s-", linewidth=2, color="tab:orange")
plt.xlabel("HfO2 Thickness (nm)")
plt.ylabel("Broadband Average QE")
plt.title("HfO2 Thickness Sweep (SiO2 = 15 nm)")
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig("barl_hfo2_sweep.png", dpi=150)

Reflectance analysis

Examine the reflectance spectrum to understand BARL effectiveness:

python
fig, ax = plt.subplots(figsize=(10, 5))

for name, layers in designs.items():
    cfg = copy.deepcopy(base_config)
    cfg["pixel"]["layers"]["barl"]["layers"] = layers
    result = SingleRunner.run(cfg)
    if result.reflection is not None:
        ax.plot(result.wavelengths * 1000, result.reflection,
                label=name, linewidth=2)

ax.set_xlabel("Wavelength (nm)")
ax.set_ylabel("Reflectance")
ax.set_title("BARL Stack Reflectance Comparison")
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig("barl_reflectance.png", dpi=150)

Energy Balance: R + T + A = 1

Adjust silicon thickness and BARL quality to see how reflection, transmission, and absorption partition the incident light across the visible spectrum.

Max error:0.00e+0
Mean error:0.00e+0
Validation:✓ Pass
Peak absorption:380 nm
0.00.20.40.60.81.0400450500550600650700750Wavelength (nm)FractionReflection (R)Transmission (T)Absorption (A)

Design guidelines

ParameterGuideline
Number of layers2--4 layers for broadband visible ARC
Material choicesSiO2 (low-n), Si3N4 (mid-n), HfO2/TiO2 (high-n)
Total BARL thickness40--120 nm typical
Target reflectance< 5% across 400--700 nm
Optimization metricBroadband average QE across all channels

For production designs, combine BARL optimization with microlens and color filter optimization in a joint parameter sweep.

Reproducibility

Numbers shown above are illustrative outputs from one specific run. They depend on solver version, materials, hardware (GPU vs CPU, fp32 vs fp64), and config. Always re-run the recipe in your environment to validate before drawing conclusions.

See also