code wiki / _hdl_build / nx_litho_stepper.nx

nx_litho_stepper.nx source

↩ module page · 102 lines · 5133 B

1// nx_litho_stepper.nx -- Arc D1: MASKLESS-STEPPER CONTROL (the chip-fab exposure step; the assignment 2// generator queued this). A maskless stepper is an XY motion stage + a UV source that EXPOSES the mask 3// pattern (nx_mask_geom) onto photoresist, field by field. The control loop is EXPOSURE DOSE -- the 4// direct analog of the jam-physics gate: 5// dose (mJ/cm^2) = intensity (mW/cm^2) * time (s). The resist clears only inside a DOSE WINDOW: 6// UNDER-dose -> resist never clears -> MISSING features (the litho "too cold") 7// OVER-dose -> features BLOOM, the critical dimension blows out, neighbors SHORT (litho "too hot") 8// Over/under exposure also BIASES the critical dimension (CD): actual_CD = drawn_CD + dose-bias; a 9// layout that passed DRC can still fail in resist if the dose is wrong. 10// FOCUS: the stage Z must hold the pattern within the depth of focus or features blur. 11// The S-class RECIPE GATE names the failing cause (UNDER_DOSE / OVER_DOSE / OUT_OF_FOCUS / CD_OUT) -- 12// the same "tell me WHERE the hardware is failing" discipline as nx_jam_check, now for lithography. 13// Composes nx_mask_geom (pattern + CD) + nx_motion_plan (field stepping) + nx_metrology. Units: 14// dose mJ/cm^2, intensity mW/cm^2, time ms, lengths nm. LAWS: struct-free, integer-only. 15// license_tier: ORIGINAL 16import "nx_mask_geom.nx" 17import "nx_motion_plan.nx" 18import "nx_syscalls.nx" 19 20const LS_OK: i64 = 0 21const LS_UNDER_DOSE: i64 = 1 // resist won't clear -> missing features 22const LS_OVER_DOSE: i64 = 2 // features bloom / short -> CD blowout 23const LS_OUT_OF_FOCUS: i64 = 3 // Z beyond depth of focus -> blur 24const LS_CD_OUT: i64 = 4 // critical dimension out of tolerance after dose bias 25 26// exposure TIME (ms) to deliver target_dose at a given intensity. dose_mJ = intensity_mW * time_s -> 27// time_s = dose/intensity ; time_ms = dose*1000/intensity. 28func ls_exposure_time_ms(target_dose_mJ: i64, intensity_mW: i64) -> i64 { 29 if intensity_mW <= 0 { return 0 } 30 return target_dose_mJ * 1000 / intensity_mW 31} 32 33// the dose actually delivered for a given intensity + time 34func ls_actual_dose_mJ(intensity_mW: i64, time_ms: i64) -> i64 { 35 return intensity_mW * time_ms / 1000 36} 37 38// dose verdict against the resist's clearing window [dose_min, dose_max] 39func ls_dose_verdict(dose_mJ: i64, dose_min: i64, dose_max: i64) -> i64 { 40 if dose_mJ < dose_min { return LS_UNDER_DOSE } 41 if dose_mJ > dose_max { return LS_OVER_DOSE } 42 return LS_OK 43} 44 45// CD bias (nm) from dose error: over-dose widens, under-dose narrows. bias = (dose - nominal) * k_nm_per_mJ. 46func ls_cd_bias_nm(dose_mJ: i64, nominal_dose_mJ: i64, k_nm_per_mJ: i64) -> i64 { 47 return (dose_mJ - nominal_dose_mJ) * k_nm_per_mJ 48} 49 50// actual critical dimension after dose bias (drawn CD shifted by the bias) 51func ls_actual_cd_nm(drawn_cd_nm: i64, dose_mJ: i64, nominal_dose_mJ: i64, k_nm_per_mJ: i64) -> i64 { 52 return drawn_cd_nm + ls_cd_bias_nm(dose_mJ, nominal_dose_mJ, k_nm_per_mJ) 53} 54 55// is the actual CD within +/- tol of the drawn CD? 56func ls_cd_in_tol(drawn_cd_nm: i64, actual_cd_nm: i64, tol_nm: i64) -> i64 { 57 var d: i64 = actual_cd_nm - drawn_cd_nm 58 if d < 0 { d = 0 - d } 59 if d <= tol_nm { return 1 } 60 return 0 61} 62 63// focus: |z_error| must be within half the depth of focus 64func ls_in_focus(z_error_nm: i64, depth_of_focus_nm: i64) -> i64 { 65 var e: i64 = z_error_nm 66 if e < 0 { e = 0 - e } 67 if e * 2 <= depth_of_focus_nm { return 1 } 68 return 0 69} 70 71// step-and-repeat field grid: how many exposure fields tile a wafer of (ww x wh) with field (fw x fh). 72// ceil division so a partial edge field still counts. 73func ls_field_count(wafer_w: i64, wafer_h: i64, field_w: i64, field_h: i64) -> i64 { 74 if field_w <= 0 { return 0 } 75 if field_h <= 0 { return 0 } 76 let cols: i64 = (wafer_w + field_w - 1) / field_w 77 let rows: i64 = (wafer_h + field_h - 1) / field_h 78 return cols * rows 79} 80 81// total wafer exposure time (ms): per-field (expose + stage step-and-settle) over all fields. 82func ls_total_exposure_ms(n_fields: i64, expose_ms: i64, step_settle_ms: i64) -> i64 { 83 return n_fields * (expose_ms + step_settle_ms) 84} 85 86// the RECIPE GATE: validate one exposure recipe and NAME the failure (UNDER/OVER/FOCUS/CD/OK). 87// out[0]=verdict out[1]=actual_dose out[2]=actual_cd out[3]=exposure_time_ms 88func ls_recipe_check(intensity_mW: i64, time_ms: i64, dose_min: i64, dose_max: i64, nominal_dose: i64, 89 k_nm_per_mJ: i64, drawn_cd_nm: i64, cd_tol_nm: i64, z_error_nm: i64, dof_nm: i64, 90 out: *i64) -> i64 { 91 let dose: i64 = ls_actual_dose_mJ(intensity_mW, time_ms) 92 let acd: i64 = ls_actual_cd_nm(drawn_cd_nm, dose, nominal_dose, k_nm_per_mJ) 93 out[1] = dose 94 out[2] = acd 95 out[3] = time_ms 96 let dv: i64 = ls_dose_verdict(dose, dose_min, dose_max) 97 if dv != LS_OK { out[0] = dv; return dv } 98 if ls_in_focus(z_error_nm, dof_nm) == 0 { out[0] = LS_OUT_OF_FOCUS; return LS_OUT_OF_FOCUS } 99 if ls_cd_in_tol(drawn_cd_nm, acd, cd_tol_nm) == 0 { out[0] = LS_CD_OUT; return LS_CD_OUT } 100 out[0] = LS_OK 101 return LS_OK 102}