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}