Skip to content

BSI 2x2 Basic

A complete recipe for simulating a standard 2x2 Bayer BSI pixel unit cell and computing QE spectra.

Interactive BSI Pixel Stack Cross-Section

Select any layer to view its material properties and role in the pixel stack.

Incident lightAir(semi-infinite)Microlens0.4 - 0.8 umPlanarization0.3 - 1.0 umColor Filter0.4 - 0.8 umBARL0.05 - 0.12 umDTIDTISilicon2.0 - 4.0 umSubstrate / Metal(semi-infinite)
Select a layer in the stack diagram to see its properties.

Interactive Bayer Pattern Viewer

Explore different color filter array (CFA) patterns. Select a pixel to see its details.

R(0,0)G(0,1)G(1,0)B(1,1)R(0,2)G(0,3)G(1,2)B(1,3)R(2,0)G(2,1)G(3,0)B(3,1)R(2,2)G(2,3)G(3,2)B(3,3)Unit cell
Pattern
RGGB
Unit Cell Size
2x2
Green Ratio
50%
Description
Most common Bayer pattern. Two green pixels per unit cell provide higher luminance resolution, mimicking human vision sensitivity.

What you will learn

  • Setting up a full BSI pixel configuration from scratch
  • Running a wavelength sweep with RCWA
  • Plotting per-channel QE spectra
  • Validating energy balance

Prerequisites

bash
pip install -e ".[rcwa]"

Interactive Pixel Stack Builder

Adjust the thickness of each layer in a BSI pixel cross-section. Patterned layers show their z envelope separately from the actual microlens, color filter, grid, and DTI material.

Total stack height:4.58 um
0.01.02.03.04.0Microlens0.60 umn=1.00 / 1.56Planarization0.30 umn=1.46Color Filter0.60 umn=1.46 / 1.55n=1.8Silicon3.00 umn=3.5DTI

Configuration

Create a config dictionary (or load from YAML):

python
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,
                "material": "polymer_n1p56",
                "profile": {"type": "superellipse", "n": 2.5, "alpha": 1.0},
                "shift": {"mode": "none"},
            },
            "planarization": {"thickness": 0.3, "material": "sio2"},
            "color_filter": {
                "pattern": "bayer_rggb",
                "red": {"material": "cf_red", "thickness": 0.62, "contact_angle": 66.0},
                "green": {"material": "cf_green", "thickness": 0.60, "contact_angle": 72.0},
                "blue": {"material": "cf_blue", "thickness": 0.65, "contact_angle": 62.0},
                "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], "dtype": "complex64"},
        "stability": {
            "precision_strategy": "mixed",
            "allow_tf32": False,
            "fourier_factorization": "li_inverse",
        },
    },
    "source": {
        "type": "planewave",
        "wavelength": {
            "mode": "sweep",
            "sweep": {"start": 0.40, "stop": 0.70, "step": 0.01},
        },
        "angle": {"theta_deg": 0.0, "phi_deg": 0.0},
        "polarization": "unpolarized",
    },
    "compute": {"backend": "auto"},
}

Run the simulation

python
from compass.runners.single_run import SingleRunner

result = SingleRunner.run(config)

print(f"Wavelengths: {result.wavelengths.shape[0]} points "
      f"({result.wavelengths[0]*1000:.0f}-{result.wavelengths[-1]*1000:.0f} nm)")
print(f"Pixels: {list(result.qe_per_pixel.keys())}")

Plot QE spectrum

python
from compass.visualization.qe_plot import plot_qe_spectrum
import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(8, 5))
plot_qe_spectrum(result, ax=ax)
ax.set_title("BSI 2x2 Bayer (1 um pitch) - QE Spectrum")
plt.tight_layout()
plt.savefig("bsi_2x2_qe.png", dpi=150)
plt.show()

Check energy balance

python
from compass.analysis.energy_balance import EnergyBalance

check = EnergyBalance.check(result, tolerance=0.02)
print(f"Valid: {check['valid']}, max error: {check['max_error']:.4f}")

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)

Extract per-channel QE

python
from compass.analysis.qe_calculator import QECalculator

channel_qe = QECalculator.spectral_response(result.qe_per_pixel, result.wavelengths)

for color, (wl, qe) in channel_qe.items():
    peak_idx = qe.argmax()
    print(f"{color}: peak QE = {qe[peak_idx]:.1%} at {wl[peak_idx]*1000:.0f} nm")

Expected output (approximate):

B: peak QE = 62.3% at 460 nm
G: peak QE = 71.5% at 540 nm
R: peak QE = 59.8% at 620 nm

Interactive QE Spectrum Chart

Explore how silicon thickness, BARL quality, and metal grid width affect the quantum efficiency spectrum of Red, Green, and Blue channels.

Blue peak QE:46.7%
Green peak QE:54.5%
Red peak QE:42.8%
Average QE:48.0%
0%20%40%60%80%400450500550600650700750Wavelength (nm)QE (%)BlueGreenRed

Variations to try

Smaller pitch (0.8 um)

python
config["pixel"]["pitch"] = 0.8
config["pixel"]["layers"]["microlens"]["radius_x"] = 0.38
config["pixel"]["layers"]["microlens"]["radius_y"] = 0.38
config["pixel"]["layers"]["silicon"]["photodiode"]["size"] = [0.55, 0.55, 1.6]

Thicker silicon (4 um)

python
config["pixel"]["layers"]["silicon"]["thickness"] = 4.0
config["pixel"]["layers"]["silicon"]["dti"]["depth"] = 4.0
config["pixel"]["layers"]["silicon"]["photodiode"]["size"] = [0.7, 0.7, 3.0]

No microlens

python
config["pixel"]["layers"]["microlens"]["enabled"] = False

Compare QE with and without microlens to quantify its contribution.

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