Design optimization of a bilayer SiN/Si grating coupler#
Silicon nitride has attracted increasing interest due to its superior passive properties. However, similar to silicon PICs, fiber-to-chip coupling remains a significant challenge for SiN platforms. Conventional SiN grating couplers exhibit low coupling efficiency due to their low refractive index contrast. To address this, a high-contrast grating reflector (GR) is employed as a bottom reflector to enhance coupling efficiency, rather than using distributed Bragg reflectors (DBR) or metal reflectors. Through design optimization, combining parameter sweeps and adjoint-based inverse design, a grating coupler with high coupling efficiency (< 1 dB) is achieved.
The workflow includes:
Periodic SiN Grating Coupler Design: Optimizing the SiN grating period, gap size, and fiber position.
Silicon Grating Reflector Design: Designing a bottom silicon grating to reflect leakage light upwards.
Interlayer Distance Optimization: Tuning the distance between the SiN and Si layers.
Inverse Design: Using adjoint optimization to apodize the grating for maximum efficiency.

The design is based on the following publication: Jinghui Zou, Yu Yu, Mengyuan Ye, Lei Liu, Shupeng Deng, and Xinliang Zhang, "Ultra efficient silicon nitride grating coupler with bottom grating reflector," Opt. Express 23, 26305-26312 (2015). DOI: 10.1364/OE.23.026305.
[1]:
import autograd as ag
import autograd.numpy as np
import matplotlib.pyplot as plt
import tidy3d as td
import tidy3d.plugins.design as tdd
import tidy3d.web as web
from tidy3d.plugins.autograd import adam, apply_updates
Simulation Setup#
We define the central wavelength, frequency, and bandwidth for the simulation. The simulation will target 1550 nm.
[2]:
lda0 = 1.55 # Central wavelength
freq0 = td.C_0 / lda0 # Central frequency
ldas = np.linspace(1.5, 1.6, 101) # Wavelength range
freqs = td.C_0 / ldas
fwidth = 0.5 * (np.max(freqs) - np.min(freqs)) # Frequency width of the source
We define the relevant materials as nondispersive mediums for simplicity.
[3]:
SiN = td.Medium.from_nk(n=1.97, k=0, freq=freq0)
Si = td.Medium.from_nk(n=3.47, k=0, freq=freq0)
SiO2 = td.Medium.from_nk(n=1.44, k=0, freq=freq0)
Defining the fixed geometric parameters.
[4]:
t_SiN = 0.4 # Thickness of SiN layer
t_Si = 0.22 # Thickness of Silicon waveguide
t_clad = 0.75 # Target cladding thickness
t_box = 2 # Thickness of Buried Oxide (BOX)
theta = np.deg2rad(8) # Fiber angle
mfd = 10.4 # Mode field diameter
n_gc = 14 # Number of SiN grating teeth
inf_eff = 1e3 # Effective infinity
Next we define some fixed simulation parameters. These will be used repeatedly in various following simulation setups.
[5]:
run_time = 3e-12 # Simulation run time
# Grid specification
grid_spec = td.GridSpec.auto(min_steps_per_wvl=20)
# Boundary condition specification
boundary_spec = td.BoundarySpec(
x=td.Boundary.absorber(num_layers=80),
y=td.Boundary.periodic(), # set the boundary to periodic in y since it's a 2D simulation
z=td.Boundary.pml(),
)
# Mode monitor for coupling efficiency measurement
mode_monitor = td.ModeMonitor(
center=(-lda0 / 2, 0, t_SiN / 2),
size=(0, td.inf, 5 * t_SiN),
freqs=freqs,
mode_spec=td.ModeSpec(num_modes=1, target_neff=1.97),
name="mode",
)
Periodic SiN Grating Coupler Design#
In the first part, we design and optimize a periodic SiN grating coupler by parameter sweeping the grating period, duty cycle, and fiber position.
Since we are optimizing the grating only, we will ignore the silicon layer as well as the substrate for now. They will be added and optimized later.

[6]:
def make_2D_SiN_grating(w_gc: float, p_gc: float) -> td.Structure:
"""
Creates a 2D SiN grating structure.
Parameters:
w_gc (float): Width of the etched region.
p_gc (float): Period of the grating.
Returns:
td.Structure: The resulting grating structure.
"""
gratings = 0
# Iterate to create each period of the grating
for i in range(n_gc):
# Add a box for each grating period
gratings += td.Box.from_bounds(
rmin=(w_gc + i * p_gc, -inf_eff, 0), rmax=((i + 1) * p_gc, inf_eff, t_SiN)
)
return td.Structure(geometry=gratings, medium=SiN)
waveguide = td.Structure(
geometry=td.Box.from_bounds(rmin=(-inf_eff, -inf_eff, 0), rmax=(0, inf_eff, t_SiN)),
medium=SiN,
)
oxide_layer = td.Structure(
geometry=td.Box.from_bounds(
rmin=(-inf_eff, -inf_eff, -inf_eff), rmax=(inf_eff, inf_eff, t_SiN + t_clad)
),
medium=SiO2,
)
def make_2D_SiN_grating_sim(w_gc: float, p_gc: float, x_fiber: float) -> td.Simulation:
"""
Creates a simulation for a 2D SiN grating coupler.
Parameters:
w_gc (float): Width of the etched region.
p_gc (float): Period of the grating.
x_fiber (float): x-position of the fiber center.
Returns:
td.Simulation: The Tidy3D simulation object.
"""
gratings = make_2D_SiN_grating(w_gc, p_gc) # Create the grating structure
# Create a Gaussian beam source representing the input fiber mode
gaussian_beam = td.GaussianBeam(
center=(x_fiber, 0, t_SiN + t_clad + lda0 / 4),
size=(2 * mfd, td.inf, 0),
source_time=td.GaussianPulse(freq0=freq0, fwidth=fwidth),
pol_angle=np.pi / 2,
angle_theta=theta,
angle_phi=0,
direction="-",
waist_radius=mfd / 2,
waist_distance=0,
)
# Simulation domain box
sim_box = td.Box.from_bounds(
rmin=(-lda0, 0, -lda0), rmax=(n_gc * p_gc + lda0, 0, t_SiN + t_clad + lda0)
)
# Create the simulation
sim = td.Simulation(
center=sim_box.center,
size=sim_box.size,
grid_spec=grid_spec,
run_time=run_time,
structures=[oxide_layer, gratings, waveguide],
sources=[gaussian_beam],
monitors=[mode_monitor],
boundary_spec=boundary_spec,
)
return sim
Create a single simulation to verify the setup and visualize the permittivity distribution.
[7]:
sim = make_2D_SiN_grating_sim(w_gc=0.5, p_gc=1.2, x_fiber=6)
sim.plot_eps(y=0)
plt.show()
[8]:
def coupling_efficiency(sim_data: td.SimulationData) -> dict:
"""
Calculates the coupling efficiency from simulation data.
Parameters
----------
sim_data : td.SimulationData
The simulation data containing mode amplitudes.
Returns
-------
dict
A dictionary containing the coupling efficiency in dB.
"""
# Extract the amplitude of the fundamental mode (mode_index=0) propagating in the backward direction ("-") at the central frequency (freq0)
amp = sim_data["mode"].amps.sel(mode_index=0, direction="-", f=freq0).values
return {"coupling efficiency": 20 * np.log10(np.abs(amp))}
Now we are ready to perform the parameter sweep (grid search) using Tidy3D’s Design plugin.
[9]:
# Define parameters and bounds
params = [
tdd.ParameterFloat(name="w_gc", span=(0.4, 0.5), num_points=6),
tdd.ParameterFloat(name="p_gc", span=(1.0, 1.2), num_points=6),
tdd.ParameterFloat(name="x_fiber", span=(4.8, 5.2), num_points=5),
]
# Design optimization method (grid search)
method = tdd.MethodGrid()
# Create a design space and run the sweep
design_space = tdd.DesignSpace(method=method, parameters=params, path_dir="./data")
results = design_space.run(make_2D_SiN_grating_sim, coupling_efficiency)
14:01:13 UTC Running 180 Simulations
After the sweep is done, we can pick the optimal design parameters.
[10]:
df = results.to_dataframe() # Convert the results to a pandas DataFrame
# Pick the best design
best_row = df.loc[df["coupling efficiency"].idxmax()]
best_w_gc, best_p_gc, best_x_fiber = best_row[["w_gc", "p_gc", "x_fiber"]]
df.sort_values(by="coupling efficiency", ascending=False)
180 rows × 4 columns