code wiki / _hdl_build / nx_litho_stepper_test.nx

nx_litho_stepper_test.nx source

↩ module page · 85 lines · 5144 B

1// nx_litho_stepper_test.nx -- ACCEPTANCE GATE for the maskless-stepper exposure control. Exact KATs: 2// resist clearing window [80,120] mJ/cm^2, nominal 100, intensity 50 mW/cm^2 (so 2000ms = 100 mJ), 3// CD bias 5 nm/mJ, drawn CD 1000nm, CD tol 150nm, depth of focus 2000nm. 4import "nx_litho_stepper.nx" 5import "nx_syscalls.nx" 6 7func lt_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 8func lt_putn(v: i64) -> i64 { let bb: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m;sys_write(1,"-" as *u8,1)} let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48 as u8;k=1} while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} var i: i64=0; while i<k{bb[i]=t[k-1-i];i=i+1} sys_write(1,bb,k); return 0 } 9 10func main() -> i64 { 11 var pass: i64 = 0 12 var total: i64 = 0 13 let out: *i64 = sys_mmap(64) as *i64 14 15 // T1: exposure time for 100 mJ at 50 mW = 2000ms; actual dose at 50mW/2000ms = 100 mJ 16 total = total + 1 17 var t1: i64 = 1 18 if ls_exposure_time_ms(100, 50) != 2000 { t1 = 0 } 19 if ls_actual_dose_mJ(50, 2000) != 100 { t1 = 0 } 20 if t1 == 1 { pass = pass + 1 } else { lt_puts("T1 FAIL time=" as *u8); lt_putn(ls_exposure_time_ms(100,50)); lt_puts("\n" as *u8) } 21 22 // T2: dose verdict window [80,120] -- 100 OK, 60 UNDER, 140 OVER 23 total = total + 1 24 var t2: i64 = 1 25 if ls_dose_verdict(100, 80, 120) != LS_OK { t2 = 0 } 26 if ls_dose_verdict(60, 80, 120) != LS_UNDER_DOSE { t2 = 0 } 27 if ls_dose_verdict(140, 80, 120) != LS_OVER_DOSE { t2 = 0 } 28 if t2 == 1 { pass = pass + 1 } else { lt_puts("T2 FAIL dose-verdict\n" as *u8) } 29 30 // T3: CD bias -- at dose 120 (nominal 100, k=5) bias = +100nm -> actual CD 1100; in tol 150 31 total = total + 1 32 var t3: i64 = 1 33 if ls_cd_bias_nm(120, 100, 5) != 100 { t3 = 0 } 34 if ls_actual_cd_nm(1000, 120, 100, 5) != 1100 { t3 = 0 } 35 if ls_cd_in_tol(1000, 1100, 150) != 1 { t3 = 0 } 36 if ls_cd_in_tol(1000, 1300, 150) != 0 { t3 = 0 } // +300 out of +/-150 37 if t3 == 1 { pass = pass + 1 } else { lt_puts("T3 FAIL cd-bias\n" as *u8) } 38 39 // T4: focus -- z_error 500nm in 2000nm DOF (|500|*2=1000<=2000) ok; 1500nm out (3000>2000) 40 total = total + 1 41 var t4: i64 = 1 42 if ls_in_focus(500, 2000) != 1 { t4 = 0 } 43 if ls_in_focus(0 - 500, 2000) != 1 { t4 = 0 } 44 if ls_in_focus(1500, 2000) != 0 { t4 = 0 } 45 if t4 == 1 { pass = pass + 1 } else { lt_puts("T4 FAIL focus\n" as *u8) } 46 47 // T5: field stepping -- 100mm wafer / 20mm field = 5x5 = 25 fields; total time 48 total = total + 1 49 var t5: i64 = 1 50 if ls_field_count(100, 100, 20, 20) != 25 { t5 = 0 } 51 if ls_field_count(105, 100, 20, 20) != 30 { t5 = 0 } // ceil: 6 cols x 5 rows 52 if ls_total_exposure_ms(25, 2000, 500) != 62500 { t5 = 0 } // 25*(2000+500) 53 if t5 == 1 { pass = pass + 1 } else { lt_puts("T5 FAIL fields=" as *u8); lt_putn(ls_field_count(100,100,20,20)); lt_puts("\n" as *u8) } 54 55 // T6: RECIPE GATE -- good recipe (50mW, 2000ms->dose100, focus ok, CD ok) -> LS_OK 56 total = total + 1 57 let v6: i64 = ls_recipe_check(50, 2000, 80, 120, 100, 5, 1000, 150, 300, 2000, out) 58 var t6: i64 = 1 59 if v6 != LS_OK { t6 = 0 } 60 if out[1] != 100 { t6 = 0 } // dose 61 if out[2] != 1000 { t6 = 0 } // CD (no bias at nominal dose) 62 if t6 == 1 { pass = pass + 1 } else { lt_puts("T6 FAIL recipe-ok v=" as *u8); lt_putn(v6); lt_puts(" dose=" as *u8); lt_putn(out[1]); lt_puts("\n" as *u8) } 63 64 // T7: RECIPE names UNDER_DOSE -- 50mW for 1000ms -> dose 50 < 80 (resist won't clear = litho "too cold") 65 total = total + 1 66 if ls_recipe_check(50, 1000, 80, 120, 100, 5, 1000, 150, 300, 2000, out) == LS_UNDER_DOSE { pass = pass + 1 } else { lt_puts("T7 FAIL under v=" as *u8); lt_putn(out[0]); lt_puts("\n" as *u8) } 67 68 // T8: RECIPE names OVER_DOSE -- 50mW for 3000ms -> dose 150 > 120 (features bloom/short = litho "too hot") 69 total = total + 1 70 if ls_recipe_check(50, 3000, 80, 120, 100, 5, 1000, 150, 300, 2000, out) == LS_OVER_DOSE { pass = pass + 1 } else { lt_puts("T8 FAIL over v=" as *u8); lt_putn(out[0]); lt_puts("\n" as *u8) } 71 72 // T9: RECIPE names OUT_OF_FOCUS -- dose ok (2000ms) but z_error 1500nm beyond DOF 73 total = total + 1 74 if ls_recipe_check(50, 2000, 80, 120, 100, 5, 1000, 150, 1500, 2000, out) == LS_OUT_OF_FOCUS { pass = pass + 1 } else { lt_puts("T9 FAIL focus v=" as *u8); lt_putn(out[0]); lt_puts("\n" as *u8) } 75 76 // T10: RECIPE names CD_OUT -- dose 118 in window but CD bias = +90... raise k so bias exceeds tol: 77 // dose 118 (in [80,120]), nominal 100, k=10 -> bias +180 > 150 tol -> CD_OUT (in-window but wrong CD) 78 total = total + 1 79 if ls_recipe_check(50, 2360, 80, 120, 100, 10, 1000, 150, 300, 2000, out) == LS_CD_OUT { pass = pass + 1 } else { lt_puts("T10 FAIL cd-out v=" as *u8); lt_putn(out[0]); lt_puts(" dose=" as *u8); lt_putn(out[1]); lt_puts(" cd=" as *u8); lt_putn(out[2]); lt_puts("\n" as *u8) } 80 81 lt_puts("LITHO-STEPPER " as *u8); lt_putn(pass); lt_puts("/" as *u8); lt_putn(total); lt_puts("\n" as *u8) 82 if pass == total { lt_puts("LITHO-STEPPER ALL-PASS\n" as *u8); sys_exit(0) } 83 sys_exit(1) 84 return 1 85}