use std::f64::consts::PI;
struct Lcg {
state: u64,
}
impl Lcg {
fn new(seed: u64) -> Self {
Self {
state: seed.wrapping_add(1),
}
}
fn next_u64(&mut self) -> u64 {
self.state = self
.state
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
self.state
}
fn next_f64(&mut self) -> f64 {
(self.next_u64() >> 11) as f64 / (1u64 << 53) as f64
}
fn next_normal(&mut self) -> f64 {
let u1 = (self.next_f64() + 1.0e-15).min(1.0 - 1.0e-15);
let u2 = self.next_f64();
(-2.0 * u1.ln()).sqrt() * (2.0 * PI * u2).cos()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DrcSeverity {
Error,
Warning,
Info,
}
impl std::fmt::Display for DrcSeverity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Error => write!(f, "ERROR"),
Self::Warning => write!(f, "WARNING"),
Self::Info => write!(f, "INFO"),
}
}
}
#[derive(Debug, Clone)]
pub struct DrcViolation {
pub rule: String,
pub location: [f64; 2],
pub severity: DrcSeverity,
pub actual_value: f64,
pub min_allowed: f64,
}
impl DrcViolation {
fn new_error(rule: &str, location: [f64; 2], actual: f64, min: f64) -> Self {
Self {
rule: rule.to_owned(),
location,
severity: DrcSeverity::Error,
actual_value: actual,
min_allowed: min,
}
}
fn new_warning(rule: &str, location: [f64; 2], actual: f64, min: f64) -> Self {
Self {
rule: rule.to_owned(),
location,
severity: DrcSeverity::Warning,
actual_value: actual,
min_allowed: min,
}
}
}
#[derive(Debug, Clone)]
pub struct DesignRuleChecker {
pub min_width_nm: f64,
pub min_spacing_nm: f64,
pub min_bend_radius_um: f64,
pub min_coupling_gap_nm: f64,
pub max_waveguide_length_mm: f64,
}
impl DesignRuleChecker {
pub fn new_soi_220nm() -> Self {
Self {
min_width_nm: 300.0,
min_spacing_nm: 200.0,
min_bend_radius_um: 5.0,
min_coupling_gap_nm: 100.0,
max_waveguide_length_mm: 50.0,
}
}
pub fn new_sin_400nm() -> Self {
Self {
min_width_nm: 500.0,
min_spacing_nm: 400.0,
min_bend_radius_um: 50.0,
min_coupling_gap_nm: 200.0,
max_waveguide_length_mm: 100.0,
}
}
pub fn check_width(&self, width_nm: f64, location: [f64; 2]) -> Option<DrcViolation> {
if width_nm < self.min_width_nm {
Some(DrcViolation::new_error(
"MIN_WIDTH",
location,
width_nm,
self.min_width_nm,
))
} else if width_nm < self.min_width_nm * 1.1 {
Some(DrcViolation::new_warning(
"MIN_WIDTH_MARGIN",
location,
width_nm,
self.min_width_nm * 1.1,
))
} else {
None
}
}
pub fn check_spacing(&self, spacing_nm: f64, location: [f64; 2]) -> Option<DrcViolation> {
if spacing_nm < self.min_spacing_nm {
Some(DrcViolation::new_error(
"MIN_SPACING",
location,
spacing_nm,
self.min_spacing_nm,
))
} else if spacing_nm < self.min_spacing_nm * 1.2 {
Some(DrcViolation::new_warning(
"MIN_SPACING_MARGIN",
location,
spacing_nm,
self.min_spacing_nm * 1.2,
))
} else {
None
}
}
pub fn check_bend_radius(&self, radius_um: f64, location: [f64; 2]) -> Option<DrcViolation> {
if radius_um < self.min_bend_radius_um {
Some(DrcViolation::new_error(
"MIN_BEND_RADIUS",
location,
radius_um,
self.min_bend_radius_um,
))
} else if radius_um < self.min_bend_radius_um * 1.5 {
Some(DrcViolation::new_warning(
"MIN_BEND_RADIUS_MARGIN",
location,
radius_um,
self.min_bend_radius_um * 1.5,
))
} else {
None
}
}
pub fn check_coupling_gap(&self, gap_nm: f64, location: [f64; 2]) -> Option<DrcViolation> {
if gap_nm < self.min_coupling_gap_nm {
Some(DrcViolation::new_error(
"MIN_COUPLING_GAP",
location,
gap_nm,
self.min_coupling_gap_nm,
))
} else if gap_nm < self.min_coupling_gap_nm * 1.3 {
Some(DrcViolation::new_warning(
"MIN_COUPLING_GAP_MARGIN",
location,
gap_nm,
self.min_coupling_gap_nm * 1.3,
))
} else {
None
}
}
pub fn check_all(
&self,
width_nm: f64,
bend_radius: f64,
gap_nm: f64,
location: [f64; 2],
) -> Vec<DrcViolation> {
let mut violations = Vec::new();
if let Some(v) = self.check_width(width_nm, location) {
violations.push(v);
}
if bend_radius.is_finite() {
if let Some(v) = self.check_bend_radius(bend_radius, location) {
violations.push(v);
}
}
if gap_nm.is_finite() {
if let Some(v) = self.check_coupling_gap(gap_nm, location) {
violations.push(v);
}
}
violations
}
}
#[derive(Debug, Clone)]
pub struct CircuitVerifier {
pub wavelengths: Vec<f64>,
}
impl CircuitVerifier {
pub fn new(start_nm: f64, stop_nm: f64, n_points: usize) -> Self {
let n = n_points.max(2);
let wavelengths: Vec<f64> = (0..n)
.map(|i| {
let t = i as f64 / (n - 1) as f64;
(start_nm + (stop_nm - start_nm) * t) * 1.0e-9
})
.collect();
Self { wavelengths }
}
pub fn check_loss_budget(&self, component_losses: &[f64], budget_db: f64) -> bool {
let total: f64 = component_losses.iter().sum();
total <= budget_db
}
pub fn check_bandwidth(&self, component_bw_nm: &[f64], required_nm: f64) -> bool {
component_bw_nm.iter().all(|&bw| bw >= required_nm)
}
pub fn check_power_handling(&self, powers_mw: &[f64], threshold_mw: f64) -> bool {
powers_mw.iter().all(|&p| p <= threshold_mw)
}
pub fn yield_estimate(
&self,
mean_performances: &[f64],
std_devs: &[f64],
specs: &[f64],
) -> f64 {
let n = mean_performances.len().min(std_devs.len()).min(specs.len());
if n == 0 {
return 1.0;
}
(0..n)
.map(|i| {
let sigma = std_devs[i].max(1.0e-15);
let z = (mean_performances[i] - specs[i]) / sigma;
normal_cdf(z)
})
.product()
}
pub fn monte_carlo_yield(
&self,
n_samples: usize,
means: &[f64],
stds: &[f64],
specs: &[f64],
) -> f64 {
if n_samples == 0 || means.is_empty() {
return 1.0;
}
let n_comp = means.len().min(stds.len()).min(specs.len());
let mut rng = Lcg::new(0xDEAD_BEEF_CAFE_1234);
let mut pass = 0usize;
for _ in 0..n_samples {
let all_pass = (0..n_comp).all(|i| {
let z = rng.next_normal();
let val = means[i] + stds[i] * z;
val >= specs[i]
});
if all_pass {
pass += 1;
}
}
pass as f64 / n_samples as f64
}
}
fn normal_cdf(x: f64) -> f64 {
0.5 * (1.0 + erf(x / 2.0_f64.sqrt()))
}
fn erf(x: f64) -> f64 {
let t = 1.0 / (1.0 + 0.3275911 * x.abs());
let poly = t
* (0.254_829_592
+ t * (-0.284_496_736
+ t * (1.421_413_741 + t * (-1.453_152_027 + t * 1.061_405_429))));
let sign = if x < 0.0 { -1.0 } else { 1.0 };
sign * (1.0 - poly * (-x * x).exp())
}
#[derive(Debug, Clone)]
pub struct ThermalAnalyzer {
pub substrate_thermal_conductivity: f64,
pub heater_resistance: f64,
pub heater_length_um: f64,
}
impl ThermalAnalyzer {
pub fn new_soi() -> Self {
Self {
substrate_thermal_conductivity: 148.0,
heater_resistance: 10_000.0,
heater_length_um: 100.0,
}
}
pub fn temperature_rise_k(&self, power_mw: f64) -> f64 {
let p_w = power_mw * 1.0e-3;
let l_m = self.heater_length_um * 1.0e-6;
let r_th = 1.0 / (2.0 * PI * self.substrate_thermal_conductivity * l_m);
p_w * r_th
}
pub fn phase_shift_rad(
&self,
power_mw: f64,
dn_dt: f64,
length_um: f64,
wavelength: f64,
) -> f64 {
let delta_t = self.temperature_rise_k(power_mw);
let l_m = length_um * 1.0e-6;
2.0 * PI / wavelength * dn_dt * delta_t * l_m
}
pub fn vpi_equivalent_power_mw(&self, dn_dt: f64, length_um: f64, wavelength: f64) -> f64 {
let dphi_dp = self.phase_shift_rad(1.0, dn_dt, length_um, wavelength);
if dphi_dp.abs() < 1.0e-30 {
return f64::INFINITY;
}
PI / dphi_dp
}
pub fn thermo_optic_bandwidth_hz(&self) -> f64 {
let rho_cp = 1.63e6_f64; let d_m = 2.0e-6_f64; self.substrate_thermal_conductivity / (rho_cp * d_m * d_m)
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
#[test]
fn test_drc_width_ok() {
let drc = DesignRuleChecker::new_soi_220nm();
let v = drc.check_width(500.0, [0.0, 0.0]);
assert!(v.is_none(), "500 nm should pass DRC for SOI 220 nm");
}
#[test]
fn test_drc_width_violation() {
let drc = DesignRuleChecker::new_soi_220nm();
let v = drc.check_width(200.0, [10.0, 5.0]);
assert!(v.is_some());
let viol = v.unwrap();
assert_eq!(viol.severity, DrcSeverity::Error);
}
#[test]
fn test_drc_bend_radius_warning() {
let drc = DesignRuleChecker::new_soi_220nm();
let v = drc.check_bend_radius(6.0, [20.0, 20.0]);
assert!(v.is_some());
assert_eq!(v.unwrap().severity, DrcSeverity::Warning);
}
#[test]
fn test_drc_coupling_gap_error() {
let drc = DesignRuleChecker::new_soi_220nm();
let v = drc.check_coupling_gap(50.0, [100.0, 50.0]);
assert!(v.is_some());
assert_eq!(v.unwrap().severity, DrcSeverity::Error);
}
#[test]
fn test_drc_check_all_no_violations() {
let drc = DesignRuleChecker::new_soi_220nm();
let violations = drc.check_all(450.0, 10.0, 200.0, [0.0, 0.0]);
assert!(
violations.is_empty(),
"No violations expected for valid parameters"
);
}
#[test]
fn test_loss_budget_pass() {
let cv = CircuitVerifier::new(1530.0, 1570.0, 41);
let losses = vec![1.0, 0.5, 0.3, 0.2];
assert!(cv.check_loss_budget(&losses, 3.0));
}
#[test]
fn test_loss_budget_fail() {
let cv = CircuitVerifier::new(1530.0, 1570.0, 41);
let losses = vec![2.0, 1.5, 0.5];
assert!(!cv.check_loss_budget(&losses, 3.0));
}
#[test]
fn test_bandwidth_check() {
let cv = CircuitVerifier::new(1530.0, 1570.0, 41);
assert!(cv.check_bandwidth(&[50.0, 60.0, 80.0], 40.0));
assert!(!cv.check_bandwidth(&[50.0, 30.0, 80.0], 40.0));
}
#[test]
fn test_yield_estimate_high_margin() {
let cv = CircuitVerifier::new(1530.0, 1570.0, 41);
let yield_val = cv.yield_estimate(&[10.0, 20.0], &[0.1, 0.1], &[1.0, 2.0]);
assert!(
yield_val > 0.99,
"Yield should be near 1 with high margin: {yield_val}"
);
}
#[test]
fn test_monte_carlo_yield_deterministic() {
let cv = CircuitVerifier::new(1530.0, 1570.0, 41);
let y1 = cv.monte_carlo_yield(1000, &[5.0], &[1.0], &[3.0]);
let y2 = cv.monte_carlo_yield(1000, &[5.0], &[1.0], &[3.0]);
assert_abs_diff_eq!(y1, y2, epsilon = 1.0e-10);
}
#[test]
fn test_monte_carlo_yield_ordering() {
let cv = CircuitVerifier::new(1530.0, 1570.0, 41);
let y_tight = cv.monte_carlo_yield(2000, &[5.0], &[1.0], &[5.5]);
let y_relaxed = cv.monte_carlo_yield(2000, &[5.0], &[1.0], &[3.0]);
assert!(y_tight < y_relaxed, "Tighter spec should give lower yield");
}
#[test]
fn test_temperature_rise_positive() {
let ta = ThermalAnalyzer::new_soi();
let dt = ta.temperature_rise_k(10.0);
assert!(
dt > 0.0 && dt.is_finite(),
"ΔT should be positive and finite: {dt}"
);
}
#[test]
fn test_phase_shift_scales_with_power() {
let ta = ThermalAnalyzer::new_soi();
let phi_1 = ta.phase_shift_rad(5.0, 1.86e-4, 100.0, 1.55e-6);
let phi_2 = ta.phase_shift_rad(10.0, 1.86e-4, 100.0, 1.55e-6);
assert_abs_diff_eq!(phi_2, 2.0 * phi_1, epsilon = 1.0e-6);
}
#[test]
fn test_vpi_power_finite() {
let ta = ThermalAnalyzer::new_soi();
let p_pi = ta.vpi_equivalent_power_mw(1.86e-4, 100.0, 1.55e-6);
assert!(
p_pi > 0.0 && p_pi.is_finite(),
"Pπ should be finite positive: {p_pi}"
);
}
#[test]
fn test_thermo_optic_bandwidth() {
let ta = ThermalAnalyzer::new_soi();
let f = ta.thermo_optic_bandwidth_hz();
assert!(
f > 1.0e3 && f < 1.0e9,
"Thermal bandwidth out of range: {f} Hz"
);
}
#[test]
fn test_erf_at_zero() {
assert_abs_diff_eq!(erf(0.0), 0.0, epsilon = 1.0e-7);
}
#[test]
fn test_erf_large_positive() {
assert_abs_diff_eq!(erf(4.0), 1.0, epsilon = 1.0e-5);
}
#[test]
fn test_normal_cdf_symmetry() {
assert_abs_diff_eq!(normal_cdf(0.0), 0.5, epsilon = 1.0e-7);
let lo = normal_cdf(-2.0);
let hi = normal_cdf(2.0);
assert_abs_diff_eq!(lo + hi, 1.0, epsilon = 1.0e-6);
}
}