@wafertools/wafermap 0.30.2 → 0.30.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +16 -5
- package/CHANGELOG.md +432 -0
- package/README.md +6 -3
- package/dist/packages/canvas-adapter/chartPopulation.d.ts +48 -0
- package/dist/packages/canvas-adapter/chartPopulation.js +1 -0
- package/dist/packages/canvas-adapter/charts/barPanel.d.ts +4 -1
- package/dist/packages/canvas-adapter/charts/barPanel.js +1 -1
- package/dist/packages/canvas-adapter/charts/boxplot.d.ts +4 -1
- package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
- package/dist/packages/canvas-adapter/charts/capability.js +1 -1
- package/dist/packages/canvas-adapter/charts/chartShell.d.ts +82 -8
- package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
- package/dist/packages/canvas-adapter/charts/correlation.js +2 -2
- package/dist/packages/canvas-adapter/charts/groupedBarPlot.js +1 -1
- package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
- package/dist/packages/canvas-adapter/charts/regionYieldDiagram.js +1 -1
- package/dist/packages/canvas-adapter/charts/scatter.d.ts +5 -1
- package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
- package/dist/packages/canvas-adapter/charts/sweep.d.ts +31 -0
- package/dist/packages/canvas-adapter/charts/sweep.js +1 -0
- package/dist/packages/canvas-adapter/charts/testPassRate.js +1 -1
- package/dist/packages/canvas-adapter/charts/trend.d.ts +3 -1
- package/dist/packages/canvas-adapter/charts/trend.js +1 -1
- package/dist/packages/canvas-adapter/dieList.js +2 -2
- package/dist/packages/canvas-adapter/drilldown.d.ts +23 -0
- package/dist/packages/canvas-adapter/drilldown.js +1 -0
- package/dist/packages/canvas-adapter/icons.js +1 -1
- package/dist/packages/canvas-adapter/insightsTab.d.ts +65 -3
- package/dist/packages/canvas-adapter/insightsTab.js +1 -1
- package/dist/packages/canvas-adapter/maplessSummary.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
- package/dist/packages/canvas-adapter/toCanvas.js +1 -1
- package/dist/packages/canvas-adapter/toolbar.d.ts +30 -2
- package/dist/packages/canvas-adapter/toolbar.js +2 -2
- package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
- package/dist/packages/canvas-adapter/userGuideHtml.js +162 -37
- package/dist/packages/canvas-adapter/version.d.ts +2 -2
- package/dist/packages/canvas-adapter/version.js +1 -1
- package/dist/packages/canvas-adapter/warnings.js +1 -1
- package/dist/packages/core/dies.d.ts +1 -1
- package/dist/packages/renderer/axisTicks.d.ts +32 -0
- package/dist/packages/renderer/axisTicks.js +1 -0
- package/dist/packages/renderer/binColors.js +1 -1
- package/dist/packages/renderer/buildView.d.ts +11 -37
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.d.ts +97 -12
- package/dist/packages/renderer/buildWaferMap.js +1 -1
- package/dist/packages/renderer/deprecate.d.ts +13 -5
- package/dist/packages/renderer/deprecate.js +1 -1
- package/dist/packages/renderer/derivedTests/apply.d.ts +85 -0
- package/dist/packages/renderer/derivedTests/apply.js +1 -0
- package/dist/packages/renderer/derivedTests/evaluate.d.ts +48 -0
- package/dist/packages/renderer/derivedTests/evaluate.js +1 -0
- package/dist/packages/renderer/derivedTests/parser.d.ts +148 -0
- package/dist/packages/renderer/derivedTests/parser.js +2 -0
- package/dist/packages/renderer/fmt.d.ts +24 -2
- package/dist/packages/renderer/fmt.js +1 -1
- package/dist/packages/renderer/index.d.ts +1 -0
- package/dist/packages/renderer/index.js +1 -1
- package/dist/packages/renderer/spec.d.ts +66 -0
- package/dist/packages/renderer/spec.js +1 -0
- package/dist/packages/renderer/testLabel.d.ts +154 -0
- package/dist/packages/renderer/testLabel.js +1 -0
- package/dist/packages/stats/analyzeWaferMap.js +1 -1
- package/dist/packages/stats/capability.d.ts +8 -2
- package/dist/packages/stats/capability.js +1 -1
- package/dist/packages/stats/correlation.d.ts +4 -1
- package/dist/packages/stats/correlation.js +1 -1
- package/dist/packages/stats/deprecated.d.ts +4 -1
- package/dist/packages/stats/deprecated.js +1 -1
- package/dist/packages/stats/index.d.ts +2 -2
- package/dist/packages/stats/index.js +1 -1
- package/dist/packages/stats/mergeTestDefs.d.ts +1 -1
- package/dist/packages/stats/mergeTestDefs.js +1 -1
- package/dist/packages/stats/renderFindingsReport.js +8 -28
- package/dist/packages/stats/renderSummaryReport.js +19 -28
- package/dist/packages/stats/reportHtml.d.ts +27 -0
- package/dist/packages/stats/reportHtml.js +35 -14
- package/dist/packages/stats/sweep.d.ts +186 -0
- package/dist/packages/stats/sweep.js +1 -0
- package/dist/packages/stats/sweepXFromName.d.ts +27 -0
- package/dist/packages/stats/sweepXFromName.js +1 -0
- package/dist/packages/stats/testPassRate.js +1 -1
- package/dist/packages/stats/types.d.ts +37 -4
- package/llms.txt +3 -2
- package/package.json +1 -1
|
@@ -5,6 +5,9 @@ import type { DieMetadata, WaferMetadata } from '../core/metadata.js';
|
|
|
5
5
|
import { type Affine } from '../core/transforms.js';
|
|
6
6
|
import { type BinColors } from './binColors.js';
|
|
7
7
|
import type { TestDef, BinDef, MetadataFieldDef, ReticleConfig } from './buildWaferMap.js';
|
|
8
|
+
import { classifySpec, type SpecCategory } from './spec.js';
|
|
9
|
+
export { classifySpec };
|
|
10
|
+
export type { SpecCategory };
|
|
8
11
|
/** Resolve a toolbar activeTest cursor to the canonical test number for getDieTestValue. */
|
|
9
12
|
export declare function resolveTestNumber(activeTest: number, testDefs?: TestDef[]): {
|
|
10
13
|
testNumber: number;
|
|
@@ -447,26 +450,6 @@ interface ColorFns {
|
|
|
447
450
|
forValue: (t: number) => string;
|
|
448
451
|
forBin: (bin: number) => string;
|
|
449
452
|
}
|
|
450
|
-
/** A die's spec classification for `value` mode against the active test's limits. */
|
|
451
|
-
export type SpecCategory = 'pass' | 'failHigh' | 'failLow';
|
|
452
|
-
/**
|
|
453
|
-
* Classify a value against the active test's spec limits, using the SAME rules as the value-mode
|
|
454
|
-
* die colouring in `pushDieRectangles`. Returns null when there is no value. Shared by the colour
|
|
455
|
-
* branch and the spec-count tally so the two never diverge.
|
|
456
|
-
*
|
|
457
|
-
* Out-of-spec *classification* depends ONLY on whether limits are defined — never on
|
|
458
|
-
* `colorbarRangeMode`. An out-of-spec die is always flagged when limits exist, regardless of
|
|
459
|
-
* how the colorbar is scaled. The *form* of the indication, decided in `pushDieRectangles`,
|
|
460
|
-
* depends only on the effective `passFailDisplay`: under `'spec'` (pass/fail mode) the die gets a solid
|
|
461
|
-
* green/blue/red categorical fill; in normal value/gradient mode it keeps the value gradient
|
|
462
|
-
* fill (like every other die, so the distribution stays readable and out-of-spec colours don't
|
|
463
|
-
* collide with the scheme) and is flagged with a ▽/△ marker (`ViewRect.specMark`). Either way
|
|
464
|
-
* the die is never drawn as plain in-spec — the silent correctness bug this guards against.
|
|
465
|
-
*/
|
|
466
|
-
export declare function classifySpec(value: number | undefined, activeTestDef: {
|
|
467
|
-
limitLow?: number;
|
|
468
|
-
limitHigh?: number;
|
|
469
|
-
} | undefined): SpecCategory | null;
|
|
470
453
|
/** Options for {@link buildHoverText}. All fields are optional. */
|
|
471
454
|
export interface HoverTextOptions {
|
|
472
455
|
testDefs?: TestDef[];
|
|
@@ -499,24 +482,15 @@ export declare function buildHoverText(die: Die, plotMode: PlotMode, opts?: Hove
|
|
|
499
482
|
export interface MapTitleParts {
|
|
500
483
|
primary: string;
|
|
501
484
|
secondary: string;
|
|
485
|
+
/**
|
|
486
|
+
* The derived-test key, `"† Derived, not measured"`, when the test the
|
|
487
|
+
* map shows is derived — drawn on its own line below `secondary`, because
|
|
488
|
+
* `primary` then carries the `†` and a glyph must never appear without its
|
|
489
|
+
* key. Absent for a measured test. Deliberately NOT part of the export file
|
|
490
|
+
* name, which reads `primary` and `secondary` only.
|
|
491
|
+
*/
|
|
492
|
+
note?: string;
|
|
502
493
|
}
|
|
503
|
-
/**
|
|
504
|
-
* Build the on-canvas map title for any plot mode, derived from the View (+ optional bin defs so a
|
|
505
|
-
* single-bin stacked card can name its bin). Returns a primary/secondary split so the renderer can
|
|
506
|
-
* place the key identifier above the scale and the supporting context below it — keeping each line
|
|
507
|
-
* short and never obscured.
|
|
508
|
-
*
|
|
509
|
-
* Primary / secondary per mode:
|
|
510
|
-
* - value → "{name} ({unit})" / —
|
|
511
|
-
* - stackedValues → "{name} ({unit}) · {method}" / "stacked ({n} wafers)"
|
|
512
|
-
* - hardBin/softBin → "Hard Bin" / "Soft Bin" / —
|
|
513
|
-
* - stackedBins → "Hard Bin {bin}" (or "Hard Bin") / "stacked ({n} wafers)"
|
|
514
|
-
* - stackedSoftBins → "Soft Bin {bin}" (or "Soft Bin") / "stacked ({n} wafers)"
|
|
515
|
-
*
|
|
516
|
-
* @param fallbackFormat unitless value formatting — matches the colorbar tick formatting.
|
|
517
|
-
* @param binDefs the active bin defs (hbinDefs for hard modes, sbinDefs for soft) — used to name
|
|
518
|
-
* the bin on single-bin stacked cards.
|
|
519
|
-
*/
|
|
520
494
|
export declare function buildMapTitle(view: View, fallbackFormat?: 'si' | 'engineering', binDefs?: BinDef[]): MapTitleParts;
|
|
521
495
|
export declare function generateTextOverlay(dies: PositionedDie[], txCoords: Float64Array | null, options: {
|
|
522
496
|
plotMode: PlotMode;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{hasPosition as oe}from"../core/dies.js";import{getReticleCell as re}from"../core/reticle.js";import{metadataCategoricalValue as le,metadataDisplayValue as ce}from"../core/metadata.js";import{affineRotation as pt,affineMirror as Rt,affineCompose as st,affinePoint as G,affineVector as yt,affineSwapsAxes as ue}from"../core/transforms.js";import{contrastTextColor as Q,SPEC_PASS_FILL as gt,SPEC_FAIL_LOW as fe,SPEC_FAIL_HIGH as bt}from"./colorMap.js";import{resolveValueColorFn as de}from"./colorSchemes.js";import{resolveBinColors as he,binColorsCover as me}from"./binColors.js";import{NO_DATA_FILL as z}from"./colorMap.js";import{diePassStatus as pe}from"../core/dies.js";import{getDieTestValue as P,getTestPassStatus as U,isParametricTest as xt}from"./buildWaferMap.js";import{fmt as Mt,fmtColorbarAxis as St,fmtAggregationMethod as Nt}from"./fmt.js";import{metadataValueColor as ye}from"./colorMap.js";import{clamp01 as Ot,compareNatural as ge,escHtml as H,minOf as be}from"../core/utils.js";import{prettyKey as Ht}from"../core/utils.js";export function resolveTestNumber(t,e){return e?.length?{testNumber:(findTestDef(e,t)??e[0]).testNumber}:{testNumber:t}}export function collectMetadataValues(t,e){const a=new Set;for(const n of t){if(n.partial||n.edgeExcluded)continue;const s=ot(n,e);s!==void 0&&a.add(s)}return[...a].sort(ge)}export function findTestDef(t,e){return t?.find(a=>a.testNumber===e)}export function getUniqueTestNumbers(t){return[...new Set(t.flatMap(e=>e.testValues?Object.keys(e.testValues).map(Number):[]))].sort((e,a)=>e-a)}const xe="#d3d6db",At="#e8e9ea",Me="#aab0ba";function ot(t,e){return e?le(t.metadata?.[e]):void 0}export function classifySpec(t,e){return t===void 0?null:e?.limitLow!==void 0&&t<e.limitLow?"failLow":e?.limitHigh!==void 0&&t>e.limitHigh?"failHigh":"pass"}function Se(t,e,a){const{x:n,y:s}=t.center,i=pt(t.orientation,n,s),o=st(Rt(e?.x??!1,e?.y??!1,n,s),pt(t.orientation,n,s)),l=st(Rt(a?.flipX??!1,a?.flipY??!1,n,s),pt(a?.rotation??0,n,s));return{bakedToScreen:l,physicalToScreen:st(l,i),gridToScreen:st(l,o)}}function K(t,e=!1){return{points:[t],closed:e}}function ke(t,e){const{center:a,radius:n,notch:s}=t;let i=a.x+n*Math.cos(e),o=a.y+n*Math.sin(e);if(!s)return{x:i,y:o};const l=Math.sqrt(n**2-(s.length/2)**2),d=s.length/2,g=i-a.x,p=o-a.y;return s.type==="bottom"&&p<-l?(o=a.y-l,i=a.x+Math.max(-d,Math.min(d,g))):s.type==="top"&&p>l?(o=a.y+l,i=a.x+Math.max(-d,Math.min(d,g))):s.type==="left"&&g<-l?(i=a.x-l,o=a.y+Math.max(-d,Math.min(d,p))):s.type==="right"&&g>l&&(i=a.x+l,o=a.y+Math.max(-d,Math.min(d,p))),{x:i,y:o}}function Et(t,e){return t.map(e).join(" / ")}function ve(t,e=256){const a=Array.from({length:e},(n,s)=>t(s/(e-1)));return n=>a[Math.min(e-1,Math.max(0,Math.round(n*(e-1))))]}function $e(t,e){const a=Math.max(1,Math.min(t.width,t.height)),n=t.width/Math.max(e.length,1);return Math.max(8,Math.min(16,Math.round(Math.min(a*.55,n*1.8))))}function Be(t,e,a){if(e?.length){const n=e.flatMap(s=>{const i=s.testNumber;if(!xt(s)){const l=U(t,i,s);return l===void 0?[]:[{key:i,label:s.name,value:l?"Pass":"Fail"}]}const o=P(t,i);return o===void 0?[]:[{key:i,label:s.name,value:Mt(o,s.unit,a),recordedFail:t.testPass?.[i]===!1}]});if(n.length)return n}return t.testValues&&Object.keys(t.testValues).length>0?Object.entries(t.testValues).map(([n,s])=>({key:Number(n),label:`Test ${n}`,value:Mt(s,void 0,a)})):t.testPass&&Object.keys(t.testPass).length>0?Object.entries(t.testPass).map(([n,s])=>({key:Number(n),label:`Test ${n}`,value:s?"Pass":"Fail"})):[]}export function buildHoverText(t,e,a={}){const{testDefs:n,hbinDefs:s,sbinDefs:i,fallbackFormat:o,aggrMethod:l,lotSize:d,activeTest:g,reticleConfig:p,metadataFields:B}=a,D=s?new Map(s.map(u=>[u.bin,u])):null,T=i?new Map(i.map(u=>[u.bin,u])):null,b=[H(`Die (${t.x}, ${t.y})`)];if(p&&oe(t)){const u=re(t,p);b.push(H(`Reticle (${u.column}, ${u.row})`))}if(e==="stackedValues"){const u=P(t,0);if(u!==void 0){const f=n?.[0],h=f?.testNumber,k=f?.name??(h!=null?`Test ${h}`:"Value"),m=l?` (${l})`:"";b.push(H(`${k}${m}: ${Mt(u,f?.unit,o)}`))}}else if(e==="stackedBins"||e==="stackedSoftBins"){const u=P(t,0),f=e==="stackedSoftBins"?t.sbin:t.hbin;if(u!==void 0){const h=e==="stackedSoftBins"?T:D,k=f!==void 0?h?.get(f)?.name?`${f} \xB7 ${h.get(f).name}`:`Bin ${f}`:"Bin";let m;if(l==="percent")m=`${u.toFixed(0)}%`;else{const C=d?` (${(u/d*100).toFixed(0)}% of stacked wafers)`:"";m=`${u}${C}`}const x=l?` [${Nt(l)}]`:"";b.push(H(`${k}: ${m}${x}`))}}else{const u=Be(t,n,o);if(e==="value"&&u.length){const{testNumber:f}=resolveTestNumber(g??0,n),h=findTestDef(n,f),k=P(t,f);let m=u.findIndex(y=>y.key===f);m<0&&(m=0);const x=u[m];let C=`<b>${H(`${x.label}: ${x.value}`)}${x.recordedFail?" <i>(recorded fail)</i>":""}</b>`;if(x.key===f&&h){const y=classifySpec(k,h);(y==="failLow"||y==="failHigh")&&(C+=" <i>(out of spec)</i>")}b.push(C);const R=u.length-1;R>0&&b.push(`<i>+${R} more test${R===1?"":"s"}</i>`)}else if(u.length){const f=u.length;b.push(`<i>${f} test result${f===1?"":"s"} recorded</i>`)}if(t.hbin!==void 0||t.sbin!==void 0){const f=[];if(t.hbin!==void 0){const h=D?.get(t.hbin)?.name,k=h?`${t.hbin} \xB7 ${h}`:String(t.hbin);f.push(`HBin: ${k}`)}if(t.sbin!==void 0){const h=T?.get(t.sbin)?.name,k=h?`${t.sbin} \xB7 ${h}`:String(t.sbin);f.push(`SBin: ${k}`)}b.push(f.map(H).join(" \xB7 "))}}t.retestCount!==void 0&&b.push(H(`Retests: ${t.retestCount}`)),t.siteNum!==void 0&&b.push(H(`Site: ${t.siteNum}`)),t.partId!==void 0&&b.push(H(`Part ID: ${t.partId}`)),t.partial&&b.push("<i>partial die</i>"),t.probeIndex!==void 0&&b.push(H(`Probe: #${t.probeIndex}`));const $={...t.metadata??{}};for(const[u,f]of Object.entries($)){const h=ce(f);if(h===void 0)continue;const k=B?.find(m=>m.key===u)?.label??Ht(u);b.push(H(`${k}: ${h}`))}return b.join("<br>")}export function buildMapTitle(t,e="engineering",a){const n=t.isLotStack&&t.lotSize!==void 0?`stacked (${t.lotSize} ${t.lotSize===1?"wafer":"wafers"})`:"stacked",s=i=>{const o=a?.length===1?a[0]:void 0;return o===void 0?`${i} Bin`:o.name?`${i} Bin ${o.bin} \xB7 ${o.name}`:`${i} Bin ${o.bin}`};switch(t.plotMode){case"hardBin":return{primary:"Hard Bin",secondary:""};case"softBin":return{primary:"Soft Bin",secondary:""};case"stackedBins":return{primary:s("Hard"),secondary:n};case"stackedSoftBins":return{primary:s("Soft"),secondary:n};case"metadata":{const i=t.metadataFields?.find(l=>l.key===t.activeMetadataKey),o=t.activeMetadataKey?Ht(t.activeMetadataKey):"Metadata";return{primary:i?.label??o,secondary:""}}case"stackedValues":{const i=t.testDefs?.[0],o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:l}=St(o,i?.name,i?.unit,e),d=l||"Value",g=t.aggrMethod?` \xB7 ${Nt(t.aggrMethod)}`:"";return{primary:`${d}${g}`,secondary:n}}default:{const i=findTestDef(t.testDefs,t.activeTest),o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:l}=St(o,i?.name,i?.unit,e),d=l||i?.name;if(t.passFailDisplay!=="off"){const g=i?.testNumber??t.activeTest,p=d?`${d} \xB7 #${g}`:`Test ${g}`,B=t.passFailDisplay==="spec"?"Spec pass/fail":i!==void 0&&!xt(i)?"Functional pass/fail":"Tester pass/fail";return{primary:p,secondary:B}}return{primary:d??`Test ${t.activeTest}`,secondary:""}}}}export function generateTextOverlay(t,e,a){const{plotMode:n,colorFns:s,normalize:i,activeTest:o,valueRange:l,testDefs:d,fallbackFormat:g,passFailDisplay:p="off",activeMetadataKey:B,metadataColorMap:D,allIntegerValues:T}=a,b=findTestDef(d,o),{tickFmt:$}=St(l[1],b?.name,b?.unit,g),u=T?h=>String(Math.round(h)):$,{testNumber:f}=resolveTestNumber(o,d);return t.flatMap((h,k)=>{let m="",x="#111111";if(n==="value"&&p==="test"){const y=U(h,f,b);if(y===void 0)return[];m=y?"P":"F",x=Q(y?gt:bt)}else if(n==="value"){const y=P(h,f);if(y===void 0)return[];m=Et([y],u),x=Q(s.forValue(i(y)))}else if(n==="hardBin"||n==="softBin"){const y=n==="softBin"?h.sbin:h.hbin;if(y===void 0)return[];m=String(y),x=Q(s.forBin(y))}else if(n==="metadata"){const y=ot(h,B);if(y===void 0)return[];m=y,x=Q(D?.get(m)??z)}else{const y=P(h,0);if(y===void 0)return[];m=Et([y],u),x=Q(s.forValue(i(y)))}const C=e?e[k*2]:h.physX,R=e?e[k*2+1]:h.physY;return[{x:C,y:R,text:m,fontSize:$e(h,m),color:x,align:"center"}]})}function _t(t){return t==="top"?Math.PI/2:t==="bottom"?-Math.PI/2:t==="left"?Math.PI:0}function Te(t,e,a=720){const{center:n,radius:s,notch:i}=t,o=$=>G(e,$.x,$.y);if(!(i!==void 0&&t.diameter>150)){const $=[];for(let u=0;u<=a;u++){const f=2*Math.PI*u/a;$.push(o(ke(t,f)))}return[{kind:"wafer-boundary",...K($,!0),lineColor:"#888888",lineWidth:1}]}const d=1.25,g=i.length,p=_t(i.type),B=Math.atan2(g,s),D=s-d,T=[];let b=!1;for(let $=0;$<=a;$++){const u=2*Math.PI*$/a;let f=u-p;for(;f>Math.PI;)f-=2*Math.PI;for(;f<-Math.PI;)f+=2*Math.PI;if(Math.abs(f)<=B){if(!b){b=!0;const h={x:n.x+s*Math.cos(p-B),y:n.y+s*Math.sin(p-B)},k={x:n.x+D*Math.cos(p),y:n.y+D*Math.sin(p)},m={x:n.x+s*Math.cos(p+B),y:n.y+s*Math.sin(p+B)};T.push(o(h),o(k),o(m))}continue}T.push(o({x:n.x+s*Math.cos(u),y:n.y+s*Math.sin(u)}))}return[{kind:"wafer-boundary",...K(T,!0),lineColor:"#888888",lineWidth:1}]}function Ie(t,e,a,n=360){const s=[],i=Math.max(1,Math.floor(a));for(let o=1;o<i;o++){const l=t.radius*o/i,d=[];for(let g=0;g<=n;g++){const p=2*Math.PI*g/n,B={x:t.center.x+l*Math.cos(p),y:t.center.y+l*Math.sin(p)};d.push(G(e,B.x,B.y))}s.push({kind:"ring-boundary",...K(d,!0),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5})}return s}function Ve(t,e,a,n){const{x:s,y:i}=t.center,o=t.radius,l=(u,f)=>G(e,u,f),d=a-s,g=Math.sqrt(Math.max(0,o*o-d*d)),p=l(a,i-g),B=l(a,i+g),D=n-i,T=Math.sqrt(Math.max(0,o*o-D*D)),b=l(s-T,n),$=l(s+T,n);return[{kind:"quadrant-boundary",...K([p,B]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5},{kind:"quadrant-boundary",...K([b,$]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5}]}function Ce(t,e){return t.map(a=>{const n=a.width/2,s=a.height/2;return{kind:"reticle",points:[[[a.x-n,a.y-s],[a.x+n,a.y-s],[a.x+n,a.y+s],[a.x-n,a.y+s]].map(([o,l])=>G(e,o,l))],closed:!0,lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5,fill:"rgba(0,0,0,0)"}})}function Fe(t){const e=t.filter(a=>a.probeIndex!==void 0).sort((a,n)=>(a.probeIndex??0)-(n.probeIndex??0));return e.length?[{kind:"probe-path",...K(e.map(a=>({x:a.physX,y:a.physY}))),lineColor:"rgba(220,80,0,0.55)",lineWidth:1}]:[]}function De(t,e,a,n,s,i,o,l,d,g,p,B,D,T,b="off",$,u,f){const h=e.width-o,k=e.height-o,m=i?k:h,x=i?h:k,C=S=>s==="softBin"?S.sbin:S.hbin,R=S=>ot(S,$);if(e.partial){t.push({x:a,y:n,width:m,height:x,fill:xe,type:"stacked",metadata:e.metadata});return}if(e.edgeExcluded){t.push({x:a,y:n,width:m,height:x,fill:Me,type:"stacked",metadata:e.metadata});return}if(d!==void 0&&(s==="hardBin"||s==="softBin")&&C(e)!==d){t.push({x:a,y:n,width:m,height:x,fill:At,type:"hardBin",metadata:e.metadata});return}if(f!==void 0&&s==="metadata"&&R(e)!==f){t.push({x:a,y:n,width:m,height:x,fill:At,type:"metadata",metadata:e.metadata});return}if(s==="value"){if(b==="test"){const W=U(e,p,T),rt=W===void 0?z:W?gt:bt;t.push({x:a,y:n,width:m,height:x,fill:rt,type:"value",metadata:e.metadata});return}const S=P(e,p),N=classifySpec(S,T);let w,J;S===void 0?w=z:b==="spec"?w=N==="failLow"?fe:N==="failHigh"?bt:gt:(w=l.forValue(g(S)),(N==="failLow"||N==="failHigh")&&(J=N)),t.push({x:a,y:n,width:m,height:x,fill:w,type:"value",specMark:J,metadata:e.metadata});return}if(s==="hardBin"||s==="softBin"){const S=C(e),N=S!=null?l.forBin(S):z,w=S!=null&&pe(e,D)===!1?!0:void 0;t.push({x:a,y:n,width:m,height:x,fill:N,type:s,binFail:w,metadata:e.metadata});return}if(s==="metadata"){const S=R(e),N=S!==void 0?u?.get(S)??z:z;t.push({x:a,y:n,width:m,height:x,fill:N,type:"metadata",metadata:e.metadata});return}const y=P(e,0),Y=y!==void 0?l.forValue(g(y)):z;t.push({x:a,y:n,width:m,height:x,fill:Y,type:"value",metadata:e.metadata})}function Pe(t,e,a){const n=t.radius*.15,s=yt(e,n,0),i=yt(e,0,n),o=s.x+i.x>=0?-1:1,l=s.y+i.y>=0?-1:1,d={x:t.center.x+o*t.radius*.9,y:t.center.y+l*t.radius*.9},g={x:d.x+s.x,y:d.y+s.y},p={x:d.x+i.x,y:d.y+i.y};return a.push({x:g.x+s.x*.35,y:g.y+s.y*.35,text:"+X",fontSize:10,color:"#cc3300",align:"center",role:"indicator"},{x:p.x+i.x*.35,y:p.y+i.y*.35,text:"+Y",fontSize:10,color:"#0044cc",align:"center",role:"indicator"}),[{kind:"xy-indicator",...K([d,g]),lineColor:"#cc3300",lineWidth:2},{kind:"xy-indicator",...K([d,p]),lineColor:"#0044cc",lineWidth:2}]}export function buildView(t,e,a={},n){const s=a.reticles??[],{plotMode:i="value",showDieLabels:o=!1,showReticle:l=!1,showProbePath:d=!1,ringCount:g=4,showRingBoundaries:p=!1,showQuadrantBoundaries:B=!1,showXYIndicator:D=!1,binColorScheme:T="default",valueColorScheme:b="default",reverseValueScheme:$=!1,useDefinedBinColors:u=!0,binColors:f,passBins:h=[1],highlightBin:k,highlightMetadataValue:m,interactiveTransform:x,valueRange:C,metadataValueOrder:R,testDefs:y,activeTest:Y=0,activeMetadataKey:S,fallbackFormat:N="engineering",aggregationMethod:w,lotSize:J,dataAxisFlip:W,isLotStack:rt=!1,logScale:wt,colorbarRangeMode:Wt="spec",passFailDisplay:jt,showPartialDies:zt=!0}=a,kt=jt??"off",lt=n?.hbinDefs,ct=n?.sbinDefs,vt=n?.metadataFields,Kt=vt?.find(c=>c.key===S),qt={x:(W?.x??!1)!==(x?.flipX??!1),y:(W?.y??!1)!==(x?.flipY??!1)},Xt=lt?new Map(lt.map(c=>[c.bin,c])):null,Yt=ct?new Map(ct.map(c=>[c.bin,c])):null,Gt=i==="softBin"?Yt:Xt,Qt=new Set(h),ut=f&&me(f,e)?f:he(e,{passBins:h,binColorScheme:T,hbinDefs:lt,sbinDefs:ct,useDefinedBinColors:u}),Ut=i==="softBin"?ut.soft:ut.hard,$t={forValue:ve(de(b,$)),forBin:c=>Ut.get(c)??z};let{testNumber:A}=resolveTestNumber(Y,y);if(!y?.length&&i==="value"&&!e.some(r=>r.testValues&&A in r.testValues)){const r=e.reduce((M,V)=>{if(!V.testValues)return M;const v=Object.keys(V.testValues).map(Number),L=v.length?be(v):void 0;return L!==void 0&&(M===void 0||L<M)?L:M},void 0);r!==void 0&&(A=r)}let Z;if(Array.isArray(C))Z=C;else if(C){const{testNumber:c}=resolveTestNumber(C.test,y);Z=c===A?C.range:void 0}const I=findTestDef(y,A),Bt=i==="value"&&I!==void 0&&!xt(I),Jt=I!==void 0&&(I.limitLow!==void 0||I.limitHigh!==void 0);let _="off";i==="value"&&(Bt?_="test":kt==="spec"&&Jt?_="spec":kt==="test"&&e.some(c=>U(c,A,I)!==void 0)&&(_="test"));const Tt=_==="spec"?"spec":Wt,ft=i==="stackedValues"||i==="stackedBins"||i==="stackedSoftBins";let O,E,tt=!1;if(Z){[O,E]=Z;let c=!0,r=!1;for(const M of e){const V=ft?P(M,0):P(M,A);if(V!==void 0&&(r=!0,!Number.isInteger(V))){c=!1;break}}tt=r&&c}else{const c=!ft&&i==="value"&&Tt==="spec"&&I&&(I.limitLow!==void 0||I.limitHigh!==void 0);let r=1/0,M=-1/0,V=!0;for(const v of e){const L=ft?P(v,0):P(v,A);L!==void 0&&(L<r&&(r=L),L>M&&(M=L),Number.isInteger(L)||(V=!1))}isFinite(r)||(V=!1),tt=V,c?(O=I.limitLow!==void 0?I.limitLow:isFinite(r)?r:0,E=I.limitHigh!==void 0?I.limitHigh:isFinite(M)?M:1):(O=isFinite(r)?r:0,E=isFinite(M)?M:1)}O===E&&(E=O+1);const It=Bt?!1:wt??I?.logScale??!1,Vt=It&&O>0&&E>0,Zt=Vt;let et;if(Vt){const c=Math.log10(O),r=Math.log10(E)-c;et=M=>M<=0?0:Ot((Math.log10(M)-c)/r)}else et=c=>Ot((c-O)/(E-O));const F=Se(t,W,x);let Ct=null;if(t.notch){const c=_t(t.notch.type),r=yt(F.physicalToScreen,Math.cos(c)*t.radius,Math.sin(c)*t.radius),M=Math.hypot(r.x,r.y);M>0&&(Ct={x:r.x/M,y:r.y/M})}let q=1/0;for(let c=0;c<e.length;c++){const r=e[c];r.width<q&&(q=r.width),r.height<q&&(q=r.height)}const Ft=1,te=q!==1/0?Math.min(Ft,q*.12):Ft,Dt=[],dt=[],ee=F.bakedToScreen.a===1&&F.bakedToScreen.b===0&&F.bakedToScreen.c===0&&F.bakedToScreen.d===1&&F.bakedToScreen.e===0&&F.bakedToScreen.f===0;let j=null;if(!ee){j=new Float64Array(e.length*2);for(let c=0;c<e.length;c++){const r=G(F.bakedToScreen,e[c].physX,e[c].physY);j[c*2]=r.x,j[c*2+1]=r.y}}const ne=ue(F.gridToScreen),ae=i==="hardBin"||i==="softBin"||i==="stackedBins"||i==="stackedSoftBins",ie=i==="softBin"||i==="stackedSoftBins";let nt=ae?new Map:void 0;const ht=i==="value"&&_==="spec"?{pass:0,failHigh:0,failLow:0}:void 0,mt=i==="value"&&_==="test"?{pass:0,fail:0}:void 0,Pt=i==="metadata";let at=Pt?new Map:void 0,it=null;if(Pt&&S){const c=collectMetadataValues(e,S),r=R?.key===S?R.values:void 0,M=r?[...r,...c.filter(V=>!r.includes(V))]:c;it=new Map(M.map((V,v)=>[V,Kt?.values?.find(L=>L.value===V)?.color??ye(v)]))}for(let c=0;c<e.length;c++){const r=e[c],M=j?j[c*2]:r.physX,V=j?j[c*2+1]:r.physY;if(dt.push({x:M,y:V}),nt&&!r.partial&&!r.edgeExcluded){const v=ie?r.sbin:r.hbin;v!=null&&nt.set(v,(nt.get(v)??0)+1)}if(ht&&!r.partial&&!r.edgeExcluded){const v=classifySpec(P(r,A),I);v&&ht[v]++}if(mt&&!r.partial&&!r.edgeExcluded){const v=U(r,A,I);v!==void 0&&mt[v?"pass":"fail"]++}if(at&&!r.partial&&!r.edgeExcluded){const v=ot(r,S);v!==void 0&&at.set(v,(at.get(v)??0)+1)}r.partial&&!zt||De(Dt,r,M,V,i,ne,te,$t,k,et,A,Gt,Qt,I,_,S,it,m)}const Lt=o?generateTextOverlay(e,j,{plotMode:i,colorFns:$t,normalize:et,activeTest:Y,valueRange:[O,E],testDefs:y,fallbackFormat:N,passFailDisplay:_,activeMetadataKey:S,metadataColorMap:it,allIntegerValues:tt}):[],X=Te(t,F.physicalToScreen);p&&X.push(...Ie(t,F.physicalToScreen,g)),B&&X.push(...Ve(t,F.physicalToScreen,t.center.x,t.center.y)),l&&X.push(...Ce(s,F.gridToScreen)),d&&X.push(...Fe(e)),D&&X.push(...Pe(t,F.gridToScreen,Lt));const se=dt.length>0?{minX:t.center.x-t.radius,maxX:t.center.x+t.radius,minY:t.center.y-t.radius,maxY:t.center.y+t.radius}:null;return{rectangles:Dt,hoverPoints:dt,texts:Lt,overlays:X,plotMode:i,binColorScheme:T,valueColorScheme:b,reverseValueScheme:$,binColors:ut,metadata:t.metadata??null,dies:e,valueRange:[O,E],allIntegerValues:tt,testDefs:y,activeTest:Y,logScale:Zt,logScaleRequested:It,aggrMethod:w,lotSize:J,axisFlip:qt,dataAxisFlip:{x:W?.x??!1,y:W?.y??!1},gridToScreen:F.gridToScreen,rotation:((t.orientation+(x?.rotation??0))%360+360)%360,hasReticle:s.length>0,isLotStack:rt,passFailDisplay:_,colorbarRangeMode:Tt,waferCenter:t.center,waferRadius:t.radius,notchDir:Ct,binCounts:nt,metadataCounts:at,metadataColorMap:it,activeMetadataKey:S,metadataFields:vt,specCounts:ht,passFailCounts:mt,dieBounds:se}}export{getDieKey}from"../core/dies.js";
|
|
1
|
+
import{hasPosition as de}from"../core/dies.js";import{getReticleCell as he}from"../core/reticle.js";import{metadataCategoricalValue as me,metadataDisplayValue as pe}from"../core/metadata.js";import{affineRotation as bt,affineMirror as Ht,affineCompose as st,affinePoint as G,affineVector as xt,affineSwapsAxes as ye}from"../core/transforms.js";import{contrastTextColor as Q,SPEC_PASS_FILL as Mt,SPEC_FAIL_LOW as ge,SPEC_FAIL_HIGH as St}from"./colorMap.js";import{resolveValueColorFn as be}from"./colorSchemes.js";import{resolveBinColors as xe,binColorsCover as Me}from"./binColors.js";import{NO_DATA_FILL as z}from"./colorMap.js";import{diePassStatus as Se}from"../core/dies.js";import{getDieTestValue as P,getTestPassStatus as U,isParametricTest as vt}from"./buildWaferMap.js";import{classifySpec as ot}from"./spec.js";export{ot as classifySpec};import{fmt as kt,fmtColorbarAxis as $t,fmtAggregationMethod as _t}from"./fmt.js";import{metadataValueColor as ve}from"./colorMap.js";import{clamp01 as Kt,compareNatural as ke,escHtml as L,minOf as $e}from"../core/utils.js";import{prettyKey as Wt}from"../core/utils.js";import{testLabel as rt,markedTestLabel as lt,derivedTestNote as jt,isDerivedTest as Be,DERIVED_MARK as Te,DERIVED_KEY as Ie}from"./testLabel.js";export function resolveTestNumber(t,e){return e?.length?{testNumber:(findTestDef(e,t)??e[0]).testNumber}:{testNumber:t}}export function collectMetadataValues(t,e){const i=new Set;for(const a of t){if(a.partial||a.edgeExcluded)continue;const s=ct(a,e);s!==void 0&&i.add(s)}return[...i].sort(ke)}export function findTestDef(t,e){return t?.find(i=>i.testNumber===e)}export function getUniqueTestNumbers(t){return[...new Set(t.flatMap(e=>e.testValues?Object.keys(e.testValues).map(Number):[]))].sort((e,i)=>e-i)}const De="#d3d6db",wt="#e8e9ea",Ve="#aab0ba";function ct(t,e){return e?me(t.metadata?.[e]):void 0}function Ce(t,e,i){const{x:a,y:s}=t.center,n=bt(t.orientation,a,s),o=st(Ht(e?.x??!1,e?.y??!1,a,s),bt(t.orientation,a,s)),l=st(Ht(i?.flipX??!1,i?.flipY??!1,a,s),bt(i?.rotation??0,a,s));return{bakedToScreen:l,physicalToScreen:st(l,n),gridToScreen:st(l,o)}}function q(t,e=!1){return{points:[t],closed:e}}function Fe(t,e){const{center:i,radius:a,notch:s}=t;let n=i.x+a*Math.cos(e),o=i.y+a*Math.sin(e);if(!s)return{x:n,y:o};const l=Math.sqrt(a**2-(s.length/2)**2),d=s.length/2,g=n-i.x,p=o-i.y;return s.type==="bottom"&&p<-l?(o=i.y-l,n=i.x+Math.max(-d,Math.min(d,g))):s.type==="top"&&p>l?(o=i.y+l,n=i.x+Math.max(-d,Math.min(d,g))):s.type==="left"&&g<-l?(n=i.x-l,o=i.y+Math.max(-d,Math.min(d,p))):s.type==="right"&&g>l&&(n=i.x+l,o=i.y+Math.max(-d,Math.min(d,p))),{x:n,y:o}}function zt(t,e){return t.map(e).join(" / ")}function Pe(t,e=256){const i=Array.from({length:e},(a,s)=>t(s/(e-1)));return a=>i[Math.min(e-1,Math.max(0,Math.round(a*(e-1))))]}function Le(t,e){const i=Math.max(1,Math.min(t.width,t.height)),a=t.width/Math.max(e.length,1);return Math.max(8,Math.min(16,Math.round(Math.min(i*.55,a*1.8))))}function Re(t,e,i){if(e?.length){const a=e.flatMap(s=>{const n=s.testNumber;if(!vt(s)){const l=U(t,n,s);return l===void 0?[]:[{key:n,label:lt(s,n),value:l?"Pass":"Fail"}]}const o=P(t,n);return o===void 0?[]:[{key:n,label:lt(s,n),value:kt(o,s.unit,i),recordedFail:t.testPass?.[n]===!1}]});if(a.length)return a}return t.testValues&&Object.keys(t.testValues).length>0?Object.entries(t.testValues).map(([a,s])=>({key:Number(a),label:rt(void 0,Number(a)),value:kt(s,void 0,i)})):t.testPass&&Object.keys(t.testPass).length>0?Object.entries(t.testPass).map(([a,s])=>({key:Number(a),label:rt(void 0,Number(a)),value:s?"Pass":"Fail"})):[]}export function buildHoverText(t,e,i={}){const{testDefs:a,hbinDefs:s,sbinDefs:n,fallbackFormat:o,aggrMethod:l,lotSize:d,activeTest:g,reticleConfig:p,metadataFields:B}=i,F=s?new Map(s.map(f=>[f.bin,f])):null,T=n?new Map(n.map(f=>[f.bin,f])):null,b=[L(`Die (${t.x}, ${t.y})`)];if(p&&de(t)){const f=he(t,p);b.push(L(`Reticle (${f.column}, ${f.row})`))}if(e==="stackedValues"){const f=P(t,0);if(f!==void 0){const u=a?.[0],h=u?lt(u,u.testNumber):"Value",v=l?` (${l})`:"";b.push(L(`${h}${v}: ${kt(f,u?.unit,o)}`));const m=jt(u);m&&b.push(`<i>${L(m)}</i>`)}}else if(e==="stackedBins"||e==="stackedSoftBins"){const f=P(t,0),u=e==="stackedSoftBins"?t.sbin:t.hbin;if(f!==void 0){const h=e==="stackedSoftBins"?T:F,v=u!==void 0?h?.get(u)?.name?`${u} \xB7 ${h.get(u).name}`:`Bin ${u}`:"Bin";let m;if(l==="percent")m=`${f.toFixed(0)}%`;else{const V=d?` (${(f/d*100).toFixed(0)}% of stacked wafers)`:"";m=`${f}${V}`}const x=l?` [${_t(l)}]`:"";b.push(L(`${v}: ${m}${x}`))}}else{const f=Re(t,a,o);if(e==="value"&&f.length){const{testNumber:u}=resolveTestNumber(g??0,a),h=findTestDef(a,u),v=P(t,u);let m=f.findIndex(A=>A.key===u);m<0&&(m=0);const x=f[m];let V=`<b>${L(`${x.label}: ${x.value}`)}${x.recordedFail?" <i>(recorded fail)</i>":""}</b>`;if(x.key===u&&h){const A=ot(v,h);(A==="failLow"||A==="failHigh")&&(V+=" <i>(outside test limits)</i>")}b.push(V);const E=jt(findTestDef(a,x.key));E&&b.push(`<i>${L(E)}</i>`);const y=f.length-1;y>0&&b.push(`<i>+${y} more test${y===1?"":"s"}</i>`)}else if(f.length){const u=f.length;b.push(`<i>${u} test result${u===1?"":"s"} recorded</i>`)}if(t.hbin!==void 0||t.sbin!==void 0){const u=[];if(t.hbin!==void 0){const h=F?.get(t.hbin)?.name,v=h?`${t.hbin} \xB7 ${h}`:String(t.hbin);u.push(`HBin: ${v}`)}if(t.sbin!==void 0){const h=T?.get(t.sbin)?.name,v=h?`${t.sbin} \xB7 ${h}`:String(t.sbin);u.push(`SBin: ${v}`)}b.push(u.map(L).join(" \xB7 "))}}t.retestCount!==void 0&&b.push(L(`Retests: ${t.retestCount}`)),t.siteNum!==void 0&&b.push(L(`Site: ${t.siteNum}`)),t.partId!==void 0&&b.push(L(`Part ID: ${t.partId}`)),t.partial&&b.push("<i>partial die</i>"),t.probeIndex!==void 0&&b.push(L(`Probe: #${t.probeIndex}`));const $={...t.metadata??{}};for(const[f,u]of Object.entries($)){const h=pe(u);if(h===void 0)continue;const v=B?.find(m=>m.key===f)?.label??Wt(f);b.push(L(`${v}: ${h}`))}return b.join("<br>")}function Bt(t){return t?.name?lt(t,t.testNumber):void 0}function Tt(t){return Be(t)?{note:`${Te} ${Ie}`}:{}}export function buildMapTitle(t,e="engineering",i){const a=t.isLotStack&&t.lotSize!==void 0?`stacked (${t.lotSize} ${t.lotSize===1?"wafer":"wafers"})`:"stacked",s=n=>{const o=i?.length===1?i[0]:void 0;return o===void 0?`${n} Bin`:o.name?`${n} Bin ${o.bin} \xB7 ${o.name}`:`${n} Bin ${o.bin}`};switch(t.plotMode){case"hardBin":return{primary:"Hard Bin",secondary:""};case"softBin":return{primary:"Soft Bin",secondary:""};case"stackedBins":return{primary:s("Hard"),secondary:a};case"stackedSoftBins":return{primary:s("Soft"),secondary:a};case"metadata":{const n=t.metadataFields?.find(l=>l.key===t.activeMetadataKey),o=t.activeMetadataKey?Wt(t.activeMetadataKey):"Metadata";return{primary:n?.label??o,secondary:""}}case"stackedValues":{const n=t.testDefs?.[0],o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:l}=$t(o,Bt(n),n?.unit,e),d=l||"Value",g=t.aggrMethod?` \xB7 ${_t(t.aggrMethod)}`:"";return{primary:`${d}${g}`,secondary:a,...Tt(n)}}default:{const n=findTestDef(t.testDefs,t.activeTest),o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:l}=$t(o,Bt(n),n?.unit,e),d=l||Bt(n);if(t.passFailDisplay!=="off"){const g=n?.testNumber??t.activeTest,p=d?`${d} \xB7 #${g}`:rt(void 0,g),B=t.passFailDisplay==="spec"?"Limit pass/fail":n!==void 0&&!vt(n)?"Functional pass/fail":"Tester pass/fail";return{primary:p,secondary:B,...Tt(n)}}return{primary:d??rt(void 0,t.activeTest),secondary:"",...Tt(n)}}}}export function generateTextOverlay(t,e,i){const{plotMode:a,colorFns:s,normalize:n,activeTest:o,valueRange:l,testDefs:d,fallbackFormat:g,passFailDisplay:p="off",activeMetadataKey:B,metadataColorMap:F,allIntegerValues:T}=i,b=findTestDef(d,o),{tickFmt:$}=$t(l[1],b?.name,b?.unit,g),f=T?h=>String(Math.round(h)):$,{testNumber:u}=resolveTestNumber(o,d);return t.flatMap((h,v)=>{let m="",x="#111111";if(a==="value"&&p==="test"){const y=U(h,u,b);if(y===void 0)return[];m=y?"P":"F",x=Q(y?Mt:St)}else if(a==="value"){const y=P(h,u);if(y===void 0)return[];m=zt([y],f),x=Q(s.forValue(n(y)))}else if(a==="hardBin"||a==="softBin"){const y=a==="softBin"?h.sbin:h.hbin;if(y===void 0)return[];m=String(y),x=Q(s.forBin(y))}else if(a==="metadata"){const y=ct(h,B);if(y===void 0)return[];m=y,x=Q(F?.get(m)??z)}else{const y=P(h,0);if(y===void 0)return[];m=zt([y],f),x=Q(s.forValue(n(y)))}const V=e?e[v*2]:h.physX,E=e?e[v*2+1]:h.physY;return[{x:V,y:E,text:m,fontSize:Le(h,m),color:x,align:"center"}]})}function qt(t){return t==="top"?Math.PI/2:t==="bottom"?-Math.PI/2:t==="left"?Math.PI:0}function Ne(t,e,i=720){const{center:a,radius:s,notch:n}=t,o=$=>G(e,$.x,$.y);if(!(n!==void 0&&t.diameter>150)){const $=[];for(let f=0;f<=i;f++){const u=2*Math.PI*f/i;$.push(o(Fe(t,u)))}return[{kind:"wafer-boundary",...q($,!0),lineColor:"#888888",lineWidth:1}]}const d=1.25,g=n.length,p=qt(n.type),B=Math.atan2(g,s),F=s-d,T=[];let b=!1;for(let $=0;$<=i;$++){const f=2*Math.PI*$/i;let u=f-p;for(;u>Math.PI;)u-=2*Math.PI;for(;u<-Math.PI;)u+=2*Math.PI;if(Math.abs(u)<=B){if(!b){b=!0;const h={x:a.x+s*Math.cos(p-B),y:a.y+s*Math.sin(p-B)},v={x:a.x+F*Math.cos(p),y:a.y+F*Math.sin(p)},m={x:a.x+s*Math.cos(p+B),y:a.y+s*Math.sin(p+B)};T.push(o(h),o(v),o(m))}continue}T.push(o({x:a.x+s*Math.cos(f),y:a.y+s*Math.sin(f)}))}return[{kind:"wafer-boundary",...q(T,!0),lineColor:"#888888",lineWidth:1}]}function Oe(t,e,i,a=360){const s=[],n=Math.max(1,Math.floor(i));for(let o=1;o<n;o++){const l=t.radius*o/n,d=[];for(let g=0;g<=a;g++){const p=2*Math.PI*g/a,B={x:t.center.x+l*Math.cos(p),y:t.center.y+l*Math.sin(p)};d.push(G(e,B.x,B.y))}s.push({kind:"ring-boundary",...q(d,!0),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5})}return s}function Ee(t,e,i,a){const{x:s,y:n}=t.center,o=t.radius,l=(f,u)=>G(e,f,u),d=i-s,g=Math.sqrt(Math.max(0,o*o-d*d)),p=l(i,n-g),B=l(i,n+g),F=a-n,T=Math.sqrt(Math.max(0,o*o-F*F)),b=l(s-T,a),$=l(s+T,a);return[{kind:"quadrant-boundary",...q([p,B]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5},{kind:"quadrant-boundary",...q([b,$]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5}]}function Ae(t,e){return t.map(i=>{const a=i.width/2,s=i.height/2;return{kind:"reticle",points:[[[i.x-a,i.y-s],[i.x+a,i.y-s],[i.x+a,i.y+s],[i.x-a,i.y+s]].map(([o,l])=>G(e,o,l))],closed:!0,lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5,fill:"rgba(0,0,0,0)"}})}function He(t){const e=t.filter(i=>i.probeIndex!==void 0).sort((i,a)=>(i.probeIndex??0)-(a.probeIndex??0));return e.length?[{kind:"probe-path",...q(e.map(i=>({x:i.physX,y:i.physY}))),lineColor:"rgba(220,80,0,0.55)",lineWidth:1}]:[]}function _e(t,e,i,a,s,n,o,l,d,g,p,B,F,T,b="off",$,f,u){const h=e.width-o,v=e.height-o,m=n?v:h,x=n?h:v,V=S=>s==="softBin"?S.sbin:S.hbin,E=S=>ct(S,$);if(e.partial){t.push({x:i,y:a,width:m,height:x,fill:De,type:"stacked",metadata:e.metadata});return}if(e.edgeExcluded){t.push({x:i,y:a,width:m,height:x,fill:Ve,type:"stacked",metadata:e.metadata});return}if(d!==void 0&&(s==="hardBin"||s==="softBin")&&V(e)!==d){t.push({x:i,y:a,width:m,height:x,fill:wt,type:"hardBin",metadata:e.metadata});return}if(u!==void 0&&s==="metadata"&&E(e)!==u){t.push({x:i,y:a,width:m,height:x,fill:wt,type:"metadata",metadata:e.metadata});return}if(s==="value"){if(b==="test"){const j=U(e,p,T),ut=j===void 0?z:j?Mt:St;t.push({x:i,y:a,width:m,height:x,fill:ut,type:"value",metadata:e.metadata});return}const S=P(e,p),N=ot(S,T);let W,J;S===void 0?W=z:b==="spec"?W=N==="failLow"?ge:N==="failHigh"?St:Mt:(W=l.forValue(g(S)),(N==="failLow"||N==="failHigh")&&(J=N)),t.push({x:i,y:a,width:m,height:x,fill:W,type:"value",specMark:J,metadata:e.metadata});return}if(s==="hardBin"||s==="softBin"){const S=V(e),N=S!=null?l.forBin(S):z,W=S!=null&&Se(e,F)===!1?!0:void 0;t.push({x:i,y:a,width:m,height:x,fill:N,type:s,binFail:W,metadata:e.metadata});return}if(s==="metadata"){const S=E(e),N=S!==void 0?f?.get(S)??z:z;t.push({x:i,y:a,width:m,height:x,fill:N,type:"metadata",metadata:e.metadata});return}const y=P(e,0),A=y!==void 0?l.forValue(g(y)):z;t.push({x:i,y:a,width:m,height:x,fill:A,type:"value",metadata:e.metadata})}function Ke(t,e,i){const a=t.radius*.15,s=xt(e,a,0),n=xt(e,0,a),o=s.x+n.x>=0?-1:1,l=s.y+n.y>=0?-1:1,d={x:t.center.x+o*t.radius*.9,y:t.center.y+l*t.radius*.9},g={x:d.x+s.x,y:d.y+s.y},p={x:d.x+n.x,y:d.y+n.y};return i.push({x:g.x+s.x*.35,y:g.y+s.y*.35,text:"+X",fontSize:10,color:"#cc3300",align:"center",role:"indicator"},{x:p.x+n.x*.35,y:p.y+n.y*.35,text:"+Y",fontSize:10,color:"#0044cc",align:"center",role:"indicator"}),[{kind:"xy-indicator",...q([d,g]),lineColor:"#cc3300",lineWidth:2},{kind:"xy-indicator",...q([d,p]),lineColor:"#0044cc",lineWidth:2}]}export function buildView(t,e,i={},a){const s=i.reticles??[],{plotMode:n="value",showDieLabels:o=!1,showReticle:l=!1,showProbePath:d=!1,ringCount:g=4,showRingBoundaries:p=!1,showQuadrantBoundaries:B=!1,showXYIndicator:F=!1,binColorScheme:T="default",valueColorScheme:b="default",reverseValueScheme:$=!1,useDefinedBinColors:f=!0,binColors:u,passBins:h=[1],highlightBin:v,highlightMetadataValue:m,interactiveTransform:x,valueRange:V,metadataValueOrder:E,testDefs:y,activeTest:A=0,activeMetadataKey:S,fallbackFormat:N="engineering",aggregationMethod:W,lotSize:J,dataAxisFlip:j,isLotStack:ut=!1,logScale:Xt,colorbarRangeMode:Yt="spec",passFailDisplay:Gt,showPartialDies:Qt=!0}=i,It=Gt??"off",ft=a?.hbinDefs,dt=a?.sbinDefs,Dt=a?.metadataFields,Ut=Dt?.find(c=>c.key===S),Jt={x:(j?.x??!1)!==(x?.flipX??!1),y:(j?.y??!1)!==(x?.flipY??!1)},Zt=ft?new Map(ft.map(c=>[c.bin,c])):null,te=dt?new Map(dt.map(c=>[c.bin,c])):null,ee=n==="softBin"?te:Zt,ne=new Set(h),ht=u&&Me(u,e)?u:xe(e,{passBins:h,binColorScheme:T,hbinDefs:ft,sbinDefs:dt,useDefinedBinColors:f}),ae=n==="softBin"?ht.soft:ht.hard,Vt={forValue:Pe(be(b,$)),forBin:c=>ae.get(c)??z};let{testNumber:H}=resolveTestNumber(A,y);if(!y?.length&&n==="value"&&!e.some(r=>r.testValues&&H in r.testValues)){const r=e.reduce((M,D)=>{if(!D.testValues)return M;const k=Object.keys(D.testValues).map(Number),R=k.length?$e(k):void 0;return R!==void 0&&(M===void 0||R<M)?R:M},void 0);r!==void 0&&(H=r)}let Z;if(Array.isArray(V))Z=V;else if(V){const{testNumber:c}=resolveTestNumber(V.test,y);Z=c===H?V.range:void 0}const I=findTestDef(y,H),Ct=n==="value"&&I!==void 0&&!vt(I),ie=I!==void 0&&(I.limitLow!==void 0||I.limitHigh!==void 0);let K="off";n==="value"&&(Ct?K="test":It==="spec"&&ie?K="spec":It==="test"&&e.some(c=>U(c,H,I)!==void 0)&&(K="test"));const Ft=K==="spec"?"spec":Yt,mt=n==="stackedValues"||n==="stackedBins"||n==="stackedSoftBins";let O,_,tt=!1;if(Z){[O,_]=Z;let c=!0,r=!1;for(const M of e){const D=mt?P(M,0):P(M,H);if(D!==void 0&&(r=!0,!Number.isInteger(D))){c=!1;break}}tt=r&&c}else{const c=!mt&&n==="value"&&Ft==="spec"&&I&&(I.limitLow!==void 0||I.limitHigh!==void 0);let r=1/0,M=-1/0,D=!0;for(const k of e){const R=mt?P(k,0):P(k,H);R!==void 0&&(R<r&&(r=R),R>M&&(M=R),Number.isInteger(R)||(D=!1))}isFinite(r)||(D=!1),tt=D,c?(O=I.limitLow!==void 0?I.limitLow:isFinite(r)?r:0,_=I.limitHigh!==void 0?I.limitHigh:isFinite(M)?M:1):(O=isFinite(r)?r:0,_=isFinite(M)?M:1)}O===_&&(_=O+1);const Pt=Ct?!1:Xt??I?.logScale??!1,Lt=Pt&&O>0&&_>0,se=Lt;let et;if(Lt){const c=Math.log10(O),r=Math.log10(_)-c;et=M=>M<=0?0:Kt((Math.log10(M)-c)/r)}else et=c=>Kt((c-O)/(_-O));const C=Ce(t,j,x);let Rt=null;if(t.notch){const c=qt(t.notch.type),r=xt(C.physicalToScreen,Math.cos(c)*t.radius,Math.sin(c)*t.radius),M=Math.hypot(r.x,r.y);M>0&&(Rt={x:r.x/M,y:r.y/M})}let X=1/0;for(let c=0;c<e.length;c++){const r=e[c];r.width<X&&(X=r.width),r.height<X&&(X=r.height)}const Nt=1,oe=X!==1/0?Math.min(Nt,X*.12):Nt,Ot=[],pt=[],re=C.bakedToScreen.a===1&&C.bakedToScreen.b===0&&C.bakedToScreen.c===0&&C.bakedToScreen.d===1&&C.bakedToScreen.e===0&&C.bakedToScreen.f===0;let w=null;if(!re){w=new Float64Array(e.length*2);for(let c=0;c<e.length;c++){const r=G(C.bakedToScreen,e[c].physX,e[c].physY);w[c*2]=r.x,w[c*2+1]=r.y}}const le=ye(C.gridToScreen),ce=n==="hardBin"||n==="softBin"||n==="stackedBins"||n==="stackedSoftBins",ue=n==="softBin"||n==="stackedSoftBins";let nt=ce?new Map:void 0;const yt=n==="value"&&K==="spec"?{pass:0,failHigh:0,failLow:0}:void 0,gt=n==="value"&&K==="test"?{pass:0,fail:0}:void 0,Et=n==="metadata";let at=Et?new Map:void 0,it=null;if(Et&&S){const c=collectMetadataValues(e,S),r=E?.key===S?E.values:void 0,M=r?[...r,...c.filter(D=>!r.includes(D))]:c;it=new Map(M.map((D,k)=>[D,Ut?.values?.find(R=>R.value===D)?.color??ve(k)]))}for(let c=0;c<e.length;c++){const r=e[c],M=w?w[c*2]:r.physX,D=w?w[c*2+1]:r.physY;if(pt.push({x:M,y:D}),nt&&!r.partial&&!r.edgeExcluded){const k=ue?r.sbin:r.hbin;k!=null&&nt.set(k,(nt.get(k)??0)+1)}if(yt&&!r.partial&&!r.edgeExcluded){const k=ot(P(r,H),I);k&&yt[k]++}if(gt&&!r.partial&&!r.edgeExcluded){const k=U(r,H,I);k!==void 0&>[k?"pass":"fail"]++}if(at&&!r.partial&&!r.edgeExcluded){const k=ct(r,S);k!==void 0&&at.set(k,(at.get(k)??0)+1)}r.partial&&!Qt||_e(Ot,r,M,D,n,le,oe,Vt,v,et,H,ee,ne,I,K,S,it,m)}const At=o?generateTextOverlay(e,w,{plotMode:n,colorFns:Vt,normalize:et,activeTest:A,valueRange:[O,_],testDefs:y,fallbackFormat:N,passFailDisplay:K,activeMetadataKey:S,metadataColorMap:it,allIntegerValues:tt}):[],Y=Ne(t,C.physicalToScreen);p&&Y.push(...Oe(t,C.physicalToScreen,g)),B&&Y.push(...Ee(t,C.physicalToScreen,t.center.x,t.center.y)),l&&Y.push(...Ae(s,C.gridToScreen)),d&&Y.push(...He(e)),F&&Y.push(...Ke(t,C.gridToScreen,At));const fe=pt.length>0?{minX:t.center.x-t.radius,maxX:t.center.x+t.radius,minY:t.center.y-t.radius,maxY:t.center.y+t.radius}:null;return{rectangles:Ot,hoverPoints:pt,texts:At,overlays:Y,plotMode:n,binColorScheme:T,valueColorScheme:b,reverseValueScheme:$,binColors:ht,metadata:t.metadata??null,dies:e,valueRange:[O,_],allIntegerValues:tt,testDefs:y,activeTest:A,logScale:se,logScaleRequested:Pt,aggrMethod:W,lotSize:J,axisFlip:Jt,dataAxisFlip:{x:j?.x??!1,y:j?.y??!1},gridToScreen:C.gridToScreen,rotation:((t.orientation+(x?.rotation??0))%360+360)%360,hasReticle:s.length>0,isLotStack:ut,passFailDisplay:K,colorbarRangeMode:Ft,waferCenter:t.center,waferRadius:t.radius,notchDir:Rt,binCounts:nt,metadataCounts:at,metadataColorMap:it,activeMetadataKey:S,metadataFields:Dt,specCounts:yt,passFailCounts:gt,dieBounds:fe}}export{getDieKey}from"../core/dies.js";
|
|
@@ -2,6 +2,8 @@ import type { Die } from '../core/dies.js';
|
|
|
2
2
|
import type { DieMetadata, WaferMetadata } from '../core/metadata.js';
|
|
3
3
|
import type { Wafer } from '../core/wafer.js';
|
|
4
4
|
import type { Reticle } from '../core/reticle.js';
|
|
5
|
+
import { type DerivedTestDef } from './derivedTests/apply.js';
|
|
6
|
+
export type { DerivedTestDef } from './derivedTests/apply.js';
|
|
5
7
|
import { type View, type ViewOptions, type PlotMode } from './buildView.js';
|
|
6
8
|
/**
|
|
7
9
|
* Test result for a single die position, as output by the prober.
|
|
@@ -55,12 +57,19 @@ export interface DieResult {
|
|
|
55
57
|
*/
|
|
56
58
|
siteNum?: number;
|
|
57
59
|
/**
|
|
58
|
-
* STDF `
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
60
|
+
* STDF PRR `PART_ID` — the tester's part identification, text in STDF (a
|
|
61
|
+
* number is accepted too). Data about the part, not an identifier: a die's
|
|
62
|
+
* identity is its position. Its meaning is fab-specific; at many fabs it
|
|
63
|
+
* encodes the probe sequence.
|
|
62
64
|
*/
|
|
63
|
-
partId?: number;
|
|
65
|
+
partId?: number | string;
|
|
66
|
+
/**
|
|
67
|
+
* The tester marked this record as replacing an earlier one (STDF PRR
|
|
68
|
+
* `PART_FLG` bit 0/1): `'partId'` — the earlier record with the same part ID;
|
|
69
|
+
* `'position'` — the earlier record at the same position. Such a record always
|
|
70
|
+
* wins, whatever `retestPolicy` says.
|
|
71
|
+
*/
|
|
72
|
+
supersedes?: 'partId' | 'position';
|
|
64
73
|
/** Per-die metadata — all fields appear automatically in hover tooltips. See `DieMetadata → §12.4`. */
|
|
65
74
|
metadata?: DieMetadata;
|
|
66
75
|
}
|
|
@@ -212,16 +221,28 @@ export interface TestDef {
|
|
|
212
221
|
*/
|
|
213
222
|
logScale?: boolean;
|
|
214
223
|
/**
|
|
215
|
-
* Lower
|
|
216
|
-
*
|
|
217
|
-
*
|
|
224
|
+
* Lower **test limit** (STDF `LO_LIMIT`) — the pass/fail limit, in the test
|
|
225
|
+
* value's units. A value below it is outside the test limits. Both limits are
|
|
226
|
+
* optional independently — some tests have one-sided limits.
|
|
218
227
|
*/
|
|
219
228
|
limitLow?: number;
|
|
229
|
+
/** Upper test limit (STDF `HI_LIMIT`). A value above it is outside the test limits. */
|
|
230
|
+
limitHigh?: number;
|
|
220
231
|
/**
|
|
221
|
-
*
|
|
222
|
-
*
|
|
232
|
+
* Whether a value exactly equal to `limitLow` passes. Default `true`. STDF
|
|
233
|
+
* states it per test in `PARM_FLG` bit 6, ATDF in the Limit Compare field.
|
|
223
234
|
*/
|
|
224
|
-
|
|
235
|
+
limitLowInclusive?: boolean;
|
|
236
|
+
/** Whether a value exactly equal to `limitHigh` passes. Default `true`; STDF `PARM_FLG` bit 7. */
|
|
237
|
+
limitHighInclusive?: boolean;
|
|
238
|
+
/**
|
|
239
|
+
* Lower **specification limit** (STDF `LO_SPEC`) — separate from the test limits,
|
|
240
|
+
* and what process capability (Cp/Cpk/Pp/Ppk) is judged against when both spec
|
|
241
|
+
* limits are given. Without them, capability uses the test limits and says so.
|
|
242
|
+
*/
|
|
243
|
+
specLow?: number;
|
|
244
|
+
/** Upper specification limit (STDF `HI_SPEC`). See `specLow`. */
|
|
245
|
+
specHigh?: number;
|
|
225
246
|
/**
|
|
226
247
|
* Test kind: `'P'` (parametric — a continuous measured value) or `'F'`
|
|
227
248
|
* (functional — a pass/fail outcome, conventionally recorded as 1 = pass,
|
|
@@ -232,6 +253,24 @@ export interface TestDef {
|
|
|
232
253
|
* would be meaningless. Default: `'P'`.
|
|
233
254
|
*/
|
|
234
255
|
testType?: 'P' | 'F';
|
|
256
|
+
/**
|
|
257
|
+
* Set by `buildWaferMap` on a test computed from `derivedTests`, never by the
|
|
258
|
+
* caller. It lives on `TestDef` rather than only on `DerivedTestDef` because
|
|
259
|
+
* `result.testDefs` is one homogeneous `TestDef[]`: every display surface
|
|
260
|
+
* reads its test names from there, and a derived value must be
|
|
261
|
+
* distinguishable from a measured one without a cast — an engineer reading a
|
|
262
|
+
* Cpk table cannot be left to assume a number came off the tester.
|
|
263
|
+
*/
|
|
264
|
+
readonly derived?: true;
|
|
265
|
+
/**
|
|
266
|
+
* The expression a `derived` test was computed from, carried through verbatim
|
|
267
|
+
* so a display surface can answer "where did this number come from" without
|
|
268
|
+
* the caller having to hold the definition alongside the result. Absent on a
|
|
269
|
+
* measured test.
|
|
270
|
+
*/
|
|
271
|
+
readonly expression?: string;
|
|
272
|
+
/** Named constants the `expression` resolved, for the same reason. */
|
|
273
|
+
readonly constants?: Record<string, number>;
|
|
235
274
|
}
|
|
236
275
|
/**
|
|
237
276
|
* True when `def` describes a parametric (continuous-value) test — i.e. its
|
|
@@ -368,6 +407,34 @@ export interface WaferMapInputBase {
|
|
|
368
407
|
* Both spaces range 0–32767 and may overlap — define them separately.
|
|
369
408
|
*/
|
|
370
409
|
sbinDefs?: BinDef[];
|
|
410
|
+
/**
|
|
411
|
+
* Tests computed from other tests on the same die, rather than measured.
|
|
412
|
+
*
|
|
413
|
+
* Each entry is a `TestDef` plus an `expression`. They are evaluated per probe
|
|
414
|
+
* record at build time — before lot stacking and before retest resolution, so
|
|
415
|
+
* every value is derived from one real touchdown — and then behave as ordinary
|
|
416
|
+
* tests: they appear in `testDefs`, in the value plot modes, the colorbar,
|
|
417
|
+
* tooltips, `analyzeWaferMap`, Insights and the report.
|
|
418
|
+
*
|
|
419
|
+
* ```ts
|
|
420
|
+
* derivedTests: [
|
|
421
|
+
* { testNumber: 900001, name: 'Leakage Shift', unit: 'uA',
|
|
422
|
+
* expression: 'abs(t[1020] - t[1010])', limitHigh: 5 },
|
|
423
|
+
* { testNumber: 900002, name: 'Sweep All Pass', testType: 'F',
|
|
424
|
+
* expression: 'all(testPass[1010..1025])' },
|
|
425
|
+
* ]
|
|
426
|
+
* ```
|
|
427
|
+
*
|
|
428
|
+
* Expressions are parsed to a typed tree and walked — there is no `eval`, no
|
|
429
|
+
* `Function`, and no way to reach a host object — so a spec can be shared as
|
|
430
|
+
* plain JSON between teams. An entry that cannot be compiled is DROPPED with a
|
|
431
|
+
* `derived-test-invalid` warning naming the position; it is never partially
|
|
432
|
+
* applied.
|
|
433
|
+
*
|
|
434
|
+
* A boolean expression is a verdict: declare `testType: 'F'` and the result
|
|
435
|
+
* lands in `die.testPass`, never as a 1/0 in `testValues`.
|
|
436
|
+
*/
|
|
437
|
+
derivedTests?: DerivedTestDef[];
|
|
371
438
|
/**
|
|
372
439
|
* Named definitions for `die.metadata` keys that should be selectable as the
|
|
373
440
|
* `'metadata'` plot mode — a generic categorical view driven by whatever
|
|
@@ -519,6 +586,24 @@ export interface WaferWarning {
|
|
|
519
586
|
* were given as text (or verdicts as something other than true/false), as a
|
|
520
587
|
* CSV parser produces. They are not converted, so those dies are judged and
|
|
521
588
|
* plotted wrongly — a bin of "1" is not pass bin 1. The message counts them.
|
|
589
|
+
* - `'input-values-outside-stdf'` — raised by `buildWaferMap`: bins, coordinates,
|
|
590
|
+
* test numbers, site numbers or `waferConfig.orientation` outside the STDF V4
|
|
591
|
+
* ranges, or test values that are not finite. Used as given for now; a future
|
|
592
|
+
* release treats them as missing. A `NaN` bin is already treated as no bin, and
|
|
593
|
+
* is counted here too.
|
|
594
|
+
* - `'retests-by-part-id'` — raised by `buildWaferMap` (severity `'info'`): dies
|
|
595
|
+
* with no position that share a part ID were treated as retests of one die.
|
|
596
|
+
* Blank part IDs never match, and part IDs are not used on a wafer where one
|
|
597
|
+
* value covers more than 20% of the unpositioned records (a default, not an ID).
|
|
598
|
+
* - `'derived-test-invalid'` — raised by `buildWaferMap`: a `derivedTests` entry
|
|
599
|
+
* could not be compiled — a parse or type error in its `expression`, a
|
|
600
|
+
* `testNumber` that collides with a measured test, a `testType` that
|
|
601
|
+
* disagrees with what the expression produces, or an accessor that is
|
|
602
|
+
* statically impossible (`t[n]` on a functional test, `specPass[n]` on a test
|
|
603
|
+
* with no limits). The message names the test and the position in the
|
|
604
|
+
* expression. That derived test is DROPPED — never partially applied, since a
|
|
605
|
+
* half-working expression plots wrong numbers rather than no numbers. Also
|
|
606
|
+
* used for the non-fatal unit-mismatch advisory, where the value IS computed.
|
|
522
607
|
* - `'input-field-removed'` — raised by `buildWaferMap`: the input used a name
|
|
523
608
|
* removed in an earlier release (`data`, `die`, `stack`, `values`,
|
|
524
609
|
* `TestDef.index`, `dieConfig.origin`, `waferConfig.flat`,
|
|
@@ -529,7 +614,7 @@ export interface WaferWarning {
|
|
|
529
614
|
* The union is intentionally open to string so future advisory codes can be
|
|
530
615
|
* added without a breaking change; switch with a `default` branch.
|
|
531
616
|
*/
|
|
532
|
-
code: 'partial-coverage' | 'geometry-conflict' | 'non-standard-diameter' | 'diameter-exceeds-die-extent' | 'test-count-capped' | 'edge-exclusion-exceeds-radius' | 'analysis-option-corrected' | 'bin-colors-shared' | 'pass-bins-mixed' | 'ring-count-mixed' | 'input-field-removed' | 'input-values-not-numbers' | (string & {});
|
|
617
|
+
code: 'partial-coverage' | 'geometry-conflict' | 'non-standard-diameter' | 'diameter-exceeds-die-extent' | 'test-count-capped' | 'edge-exclusion-exceeds-radius' | 'analysis-option-corrected' | 'bin-colors-shared' | 'pass-bins-mixed' | 'ring-count-mixed' | 'input-field-removed' | 'input-values-not-numbers' | 'input-values-outside-stdf' | 'retests-by-part-id' | 'derived-test-invalid' | (string & {});
|
|
533
618
|
/** Human-readable explanation, suitable for direct display. */
|
|
534
619
|
message: string;
|
|
535
620
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createWafer as j}from"../core/wafer.js";import{isYieldEligibleDie as he,getDieKey as V,hasPosition as A}from"../core/dies.js";import{applyOrientation as U,transformDies as me,clipDiesToWafer as ge}from"../core/transforms.js";import{generateDies as pe}from"../core/dies.js";import{affineRotation as ye,affineMirror as be,affineCompose as we,affinePoint as xe}from"../core/transforms.js";import{inferWaferFromXY as De}from"../core/inference/wafer.js";import{resolveGridPitch as Me}from"../core/inference/pitch.js";import{assignGridIndices as Ce}from"../core/inference/grid.js";import{generateReticleGrid as ve}from"../core/reticle.js";import{buildView as J}from"./buildView.js";import{maxOf as K,minOf as Q,modeOf as Oe}from"../core/utils.js";import{aggregateValues as Z,aggregateBinCounts as Se}from"../core/aggregates.js";export function isParametricTest(t){return t?.testType!=="F"}function ke(t){const n=(l,s)=>({ringCount:l,ringCountWarning:{code:"analysis-option-corrected",severity:"warning",message:`ringCount=${String(t)} ${s} (using ${l}).`}});if(t===void 0)return{ringCount:4,ringCountWarning:void 0};if(typeof t!="number"||!Number.isFinite(t))return n(4,"is not a finite number");const e=Math.max(1,Math.round(t));return e===t?{ringCount:t,ringCountWarning:void 0}:n(e,"is not a whole number of at least 1")}function ee(t){const n=Array.isArray(t)?{}:t,e=Array.isArray(n.lotStack?.results)?n.lotStack.results:[],l=(Array.isArray(t)?[t]:[n.results,...e]).filter(a=>Array.isArray(a)),s={hbin:0,sbin:0,testValues:0,testPass:0};let r;const h=(a,i)=>{s[a]++,r??=`${a} ${JSON.stringify(i)}`};for(const a of l){const i=new Set,f=new Set;for(let p=0;p<a.length;p+=p<50?1:100){const m=a[p];if(!(m===null||typeof m!="object")){if(m.testValues&&typeof m.testValues=="object")for(const D of Object.keys(m.testValues))i.add(D);if(m.testPass&&typeof m.testPass=="object")for(const D of Object.keys(m.testPass))f.add(D)}}const u=[...i],d=[...f];for(const p of a){if(p===null||typeof p!="object")continue;const m=p;m.hbin!=null&&typeof m.hbin!="number"&&h("hbin",m.hbin),m.sbin!=null&&typeof m.sbin!="number"&&h("sbin",m.sbin);const D=m.testValues;if(D&&typeof D=="object")for(let y=0;y<u.length;y++){const b=D[u[y]];b!=null&&typeof b!="number"&&h("testValues",b)}const C=m.testPass;if(C&&typeof C=="object")for(let y=0;y<d.length;y++){const b=C[d[y]];b!=null&&typeof b!="boolean"&&h("testPass",b)}}}const o=[];if(s.hbin&&o.push(`${s.hbin} hard bin${s.hbin===1?"":"s"}`),s.sbin&&o.push(`${s.sbin} soft bin${s.sbin===1?"":"s"}`),s.testValues&&o.push(`${s.testValues} test value${s.testValues===1?"":"s"}`),s.testPass&&o.push(`${s.testPass} pass/fail verdict${s.testPass===1?"":"s"}`),!o.length)return;const g=`buildWaferMap received ${o.join(", ")} of the wrong type (for example ${r}). Bins and test values must be numbers and verdicts true/false \u2014 a bin of "1" is not pass bin 1, so those dies count as fails and yield is wrong, and a test value given as text is not plotted or analysed correctly. Convert them with Number() (and verdicts to booleans) and rebuild.`;return console.warn(`[wafermap] ${g}`),{code:"input-values-not-numbers",message:g,severity:"error"}}function te(t){const n=(a,i)=>a!==null&&typeof a=="object"&&a[i]!==void 0,e=Array.isArray(t)?{}:t,l=[],s=(a,i,f)=>{a&&l.push(`\`${i}\` (now \`${f}\`)`)};s(n(e,"data"),"data","results"),s(n(e,"die"),"die","dieConfig"),s(n(e,"stack"),"stack","lotStack"),s(n(e.dieConfig,"origin"),"dieConfig.origin","dieConfig.coordinateOrigin"),s(n(e.waferConfig,"flat"),"waferConfig.flat","waferConfig.notch"),s(n(e.reticleConfig,"anchor"),"reticleConfig.anchor","reticleConfig.anchorDie"),s(n(e.lotStack,"aggr"),"lotStack.aggr","lotStack.method"),s(Array.isArray(e.testDefs)&&e.testDefs.some(a=>n(a,"index")&&!n(a,"testNumber")),"TestDef.index","TestDef.testNumber");const r=n(e.lotStack,"results")?e.lotStack.results:[],h=(Array.isArray(t)?[t]:[e.results,...Array.isArray(r)?r:[]]).filter(a=>Array.isArray(a));if(s(h.some(a=>a.slice(0,100).some(i=>n(i,"values"))),"values","testValues"),!l.length)return;const o=l.length>1,g=`buildWaferMap ignored ${l.join(", ")}: ${o?"these names were":"that name was"} removed in an earlier release, so what ${o?"they":"it"} described is not on this map. Rename and rebuild.`;return console.warn(`[wafermap] ${g}`),{code:"input-field-removed",message:g,severity:"error"}}function Ae(t){if(t.results!==void 0||t.lotStack!==void 0)throw new Error("buildWaferMap: pass `layout`, `results` or `lotStack` \u2014 only one.");const n=t.waferConfig?.diameter,e=t.dieConfig?.width,l=t.dieConfig?.height;if(!(n>0&&e>0&&l>0))throw new Error("buildWaferMap: `layout` needs waferConfig.diameter and dieConfig.width and height, all positive.");const s=j({diameter:n,notch:t.waferConfig?.notch,orientation:0}),r=ge(pe(s,{width:e,height:l}),s,{width:e,height:l}).filter(i=>!i.partial),{flipX:h,flipY:o}=ne(t.dieConfig,{type:"center"}),{layout:g,...a}=t;return{...a,results:r.map(i=>({x:h?-i.x:i.x,y:o?-i.y:i.y})),waferConfig:{...t.waferConfig,center:{x:0,y:0}},dieConfig:{...t.dieConfig,coordinateOrigin:{type:"center"}}}}function Re(t){if(!Array.isArray(t)&&"results"in t&&t.results!==void 0&&"lotStack"in t&&t.lotStack!==void 0)throw new Error("buildWaferMap: pass either `results` or `lotStack`, not both.");return Array.isArray(t)?{results:t,waferOpts:void 0,dieOpts:void 0,explicitDies:void 0,reticleOpts:void 0,lotStackOpts:void 0,passBins:[1],ringCount:4,ringCountWarning:void 0,removedFieldWarning:te(t),nonNumericWarning:ee(t),standardDiameters:void 0,testDefs:void 0,hbinDefs:void 0,sbinDefs:void 0,metadataFields:void 0,retestPolicy:"last",edgeDieYieldMode:"exclude"}:{results:t.results??[],waferOpts:t.waferConfig,dieOpts:t.dieConfig,explicitDies:t.dies,reticleOpts:t.reticleConfig,lotStackOpts:t.lotStack,passBins:t.passBins??[1],...ke(t.ringCount),removedFieldWarning:te(t),nonNumericWarning:ee(t),standardDiameters:t.standardDiameters,testDefs:t.testDefs?.length?t.testDefs:void 0,hbinDefs:t.hbinDefs,sbinDefs:t.sbinDefs,metadataFields:t.metadataFields,retestPolicy:t.retestPolicy??"last",edgeDieYieldMode:t.edgeDieYieldMode??"exclude"}}function $e(t,n){return n?.coordinateOrigin?n.coordinateOrigin:{type:"center"}}function We(t,n,e){if(n.type==="custom"&&n.offset)return{offsetX:n.offset.x,offsetY:n.offset.y};if(n.type!=="center"){let l=1/0,s=-1/0,r=1/0,h=-1/0;for(const o of t)o.x<l&&(l=o.x),o.x>s&&(s=o.x),o.y<r&&(r=o.y),o.y>h&&(h=o.y);return{offsetX:Math.round((s+l)/2),offsetY:Math.round((h+r)/2)}}return{offsetX:e.offsetX,offsetY:e.offsetY}}function Ee(t,n,e,l,s,r){if(r)return{colMidX:(Math.round(r.x)-n)*l,colMidY:(Math.round(r.y)-e)*s,anchored:!0};if(t.length===0)return{colMidX:0,colMidY:0,anchored:!1};let h=1/0,o=-1/0,g=1/0,a=-1/0;for(const i of t){const f=Math.round(i.x)-n,u=Math.round(i.y)-e;f<h&&(h=f),f>o&&(o=f),u<g&&(g=u),u>a&&(a=u)}return{colMidX:(h+o)/2*l,colMidY:(g+a)/2*s,anchored:!1}}function Fe(t,n,e){let l=1/0,s=-1/0,r=1/0,h=-1/0,o=0,g=0;for(const f of t){const u=Math.round(f.x)-n,d=Math.round(f.y)-e;o+=u,g+=d,u<l&&(l=u),u>s&&(s=u),d<r&&(r=d),d>h&&(h=d)}const a=t.length,i=(f,u,d)=>{const p=(d-u)/2;return p<=0?0:Math.abs(f/a-(u+d)/2)/p};return i(o,l,s)>.11||i(g,r,h)>.11}function ne(t,n){let e=t?.xAxisDirection==="left",l=t?.yAxisDirection==="down";return(n.type==="UL"||n.type==="UR")&&(l=!0),(n.type==="LR"||n.type==="UR")&&(e=!0),{flipX:e,flipY:l}}function Pe(t,n){const{method:e,targetBin:l}=t,s=t.results.map(r=>r.filter(A));if(e==="mean"||e==="median"||e==="stddev"||e==="min"||e==="max"||e==="count"){const r=new Set((n??[]).filter(i=>!isParametricTest(i)).map(i=>i.testNumber)),h=new Set;for(const i of s)for(const f of i)if(f.testValues)for(const u of Object.keys(f.testValues)){const d=Number(u);r.has(d)||h.add(d)}if(h.size===0)return Z(s,e);const o=[...h],g=o.map(i=>Z(s,e,i)),a=new Map;for(let i=0;i<o.length;i++){const f=o[i];for(const u of g[i]){const d=V(u);a.has(d)||a.set(d,{template:u,testValues:{}});const p=a.get(d),m=u.testValues?.[0];m!==void 0&&(p.testValues[f]=m)}}return[...a.values()].map(({template:i,testValues:f})=>({...i,testValues:Object.keys(f).length>0?f:void 0}))}if(e==="countBin"||e==="percent"){if(l===void 0)return[];const r=Se(s,l,"hard");if(e==="countBin")return r;const h=s.length;return r.map(o=>({...o,testValues:{0:h>0?(o.testValues?.[0]??0)/h*100:0}}))}if(e==="mode"){const r=new Map;for(const o of s)for(const g of o){const a=V(g);r.has(a)||r.set(a,[]),r.get(a).push(g)}const h=[];for(const[o,g]of r){const a=o.split(","),i=Number(a[0]),f=Number(a[1]),u=g.map(p=>p.hbin).filter(p=>p!==void 0),d=Oe(u);d!==null&&h.push({x:i,y:f,hbin:d})}return h}return[]}function se(t){const n=t.filter(A),e=n.length,l=n.filter(r=>r.edgeExcluded).length,s=n.filter(r=>dieHasTestData(r)||r.hbin!==void 0||r.sbin!==void 0).length;return{filledDies:s,totalDies:e,edgeExcludedDies:l,unpositionedDies:t.length-n.length,ratio:e>0?s/e:0}}function ie(t,n,e="exclude"){const l=new Set(n),r=t.filter(d=>!d.partial).filter(d=>d.edgeExcluded).length,h=t.filter(d=>d.partial).length;let o=0,g=0,a=!1;for(const d of t){if(!he(d))continue;const p=d.hbin??d.sbin;p!==void 0&&(a=!0,l.has(p)?o++:g++)}const i=o+g,f=a&&i>0?o/i*100:null;let u=null;if(e==="denominator-only"){const d=i+r;u=a&&d>0?o/d*100:null}return{passDies:o,failDies:g,edgeExcludedDies:r,partialDies:h,totalDies:i,yieldPercent:f,yieldPercentGross:u}}function oe(t,n,e,l,s,r=0,h=0,o=0,g=0,a=0,i=!1,f=!1){if(!t)return[];const u=t.anchorDie??{x:0,y:0},d=ve(n,{width:t.width,height:t.height,diePitchX:l,diePitchY:s,anchorDie:{x:u.x-r,y:u.y-h},gridOrigin:{x:-o,y:-g}}),p=we(be(i,f,n.center.x,n.center.y),ye(a,n.center.x,n.center.y));return d.filter(m=>{const D=m.width/2,C=m.height/2,y=[[m.x-D,m.y-C],[m.x+D,m.y-C],[m.x+D,m.y+C],[m.x-D,m.y+C]].map(([w,B])=>xe(p,w,B)),b=Q(y.map(w=>w.x)),R=K(y.map(w=>w.x)),$=Q(y.map(w=>w.y)),F=K(y.map(w=>w.y));return e.some(w=>w.physX>=b&&w.physX<R&&w.physY>=$&&w.physY<F)})}function Ie(t,n,e){const s=Math.max(0,n.radius-e)**2;return t.map(r=>{const h=r.physX-n.center.x,o=r.physY-n.center.y;return h*h+o*o>s?{...r,edgeExcluded:!0}:r})}function Ne(t,n,e){const l=t.filter(A),s=t.filter(i=>!A(i)),r=new Map;for(const i of l){let f=r.get(i.x);f||(f=new Map,r.set(i.x,f)),f.set(i.y,(f.get(i.y)??0)+1)}const h=new Set(e);function o(i,f){const u=i.hbin,d=f.hbin;if(u===void 0||d===void 0)return!1;const p=h.has(u),m=h.has(d);return n==="best"?m!==p?m:d<u:m!==p?p:d>u}const g=new Map;for(const i of l){const f=V(i),u=g.get(f);n==="first"&&u||(n==="best"||n==="worst")&&u&&!o(u,i)||g.set(f,i)}return[...Array.from(g.values()).map(i=>{const u=r.get(i.x)?.get(i.y)??1;return u>1?{...i,retestCount:u}:i}),...s]}export function getDieTestValue(t,n){return t.testValues?.[n]}export function getTestPassStatus(t,n,e){const l=t.testPass?.[n];if(l!==void 0)return l;if(e!==void 0&&!isParametricTest(e)){const s=t.testValues?.[n];if(s===0||s===1)return s===1}}export function dieHasTestData(t){return t.testValues!==void 0&&Object.keys(t.testValues).length>0||t.testPass!==void 0&&Object.keys(t.testPass).length>0}function _(t,n){const e={};return n.hbin!==void 0&&(e.hbin=n.hbin),n.sbin!==void 0&&(e.sbin=n.sbin),n.retestCount!==void 0&&(e.retestCount=n.retestCount),n.siteNum!==void 0&&(e.siteNum=n.siteNum),n.partId!==void 0&&(e.partId=n.partId),n.metadata!==void 0&&(e.metadata=n.metadata),n.testPass!==void 0&&(e.testPass=n.testPass),n.testValues!==void 0&&(e.testValues=n.testValues),{...t,...e}}function re(t,n){return n.plotMode?n.plotMode:t.some(dieHasTestData)?"value":"hardBin"}function ae(t,n){return t.map(({code:e,message:l})=>({code:e,message:l,severity:e==="non-standard-diameter"||e==="diameter-exceeds-die-extent"?"warning":"error",confidence:n.wafer.confidence}))}const Ve="do not fit inside the supplied",Te="Inferred wafer diameter",_e="The probed dies reach only",Be=.75,Ye=20;export const STANDARD_WAFER_DIAMETERS_MM=[100,125,150,200,300];const Le=.02;function de(t,n){return n.reduce((e,l)=>Math.abs(l-t)<Math.abs(e-t)?l:e)}function Xe(t,n){if(!n.length)return!0;const e=de(t,n);return Math.abs(e-t)/e<=Le}function je(t){return t===void 0?STANDARD_WAFER_DIAMETERS_MM:t.filter(n=>Number.isFinite(n)&&n>0)}export function buildWaferMap(t,n){const e=Re(!Array.isArray(t)&&t.layout?Ae(t):t),{debug:l,...s}=n??{},r=e.lotStackOpts?Pe(e.lotStackOpts,e.testDefs):e.results;for(let c=0;c<Math.min(5,r.length);c++){const x=r[c];if(typeof x.x=="string"||typeof x.y=="string")throw new TypeError("buildWaferMap: x and y must be numbers, received strings. Did you forget to cast CSV values? e.g. { x: +row.x, y: +row.y }")}for(const c of r)if(c.x==null!=(c.y==null))throw new TypeError(`buildWaferMap: die has only one of x/y set (x=${String(c.x)}, y=${String(c.y)}). A die must be either fully positioned (both x and y) or fully unpositioned (neither) \u2014 omit both to mark a die as having no reported position.`);const h=Ne(r,e.retestPolicy,e.passBins),o={wafer:{confidence:1,method:"provided"},diePitch:{confidence:1,units:"mm"},grid:{confidence:1}},g=[];if(e.explicitDies){let c=e.explicitDies;const x=h.filter(A),S=h.filter(N=>!A(N));if(x.length>0){const N=new Map(x.map(k=>[V(k),k]));c=c.map(k=>{const H=N.get(V(k));return H?_(k,H):k})}const I=e.waferOpts?.diameter??300,E=j({diameter:I,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});E.orientation!==0&&(c=U(c,E));const q=oe(e.reticleOpts,E,c,1,1,0,0,0,0,E.orientation),le=s.showReticle??e.reticleOpts!==void 0,L=J(E,c,{...s,reticles:q,showReticle:le,plotMode:re(h,s),testDefs:e.testDefs,passBins:e.passBins,ringCount:e.ringCount,isLotStack:!1},{hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,metadataFields:e.metadataFields}),ue=S.map((N,k)=>_({id:`unpositioned_${k}`,width:c[0]?.width??1,height:c[0]?.height??1},N)),X=[...c,...ue];return{wafer:E,dies:X,view:L,reticleConfig:e.reticleOpts,units:"mm",inference:o,warnings:[...e.removedFieldWarning?[e.removedFieldWarning]:[],...e.nonNumericWarning?[e.nonNumericWarning]:[],...ae(g,o),...e.ringCountWarning?[e.ringCountWarning]:[]],plotMode:L.plotMode,metadata:L.metadata,isLotStack:!1,dataCoverage:se(X),passBins:e.passBins,ringCount:e.ringCount,yield:ie(X,e.passBins,e.edgeDieYieldMode),reticles:q,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields}}const a=h.filter(A),i=h.filter(c=>!A(c)),f=a.map(c=>({x:c.x,y:c.y})),u=Me(f,e.dieOpts,e.waferOpts?.diameter);o.diePitch={confidence:u.confidence,units:u.units};const{pitchX:d,pitchY:p}=u,m=u.units,D=$e(a,e.dieOpts),C=Ce(f);o.grid={confidence:C.confidence};const{offsetX:y,offsetY:b}=We(f,D,C),{flipX:R,flipY:$}=ne(e.dieOpts,D),{colMidX:F,colMidY:w,anchored:B}=Ee(f,y,b,d,p,e.waferOpts?.center);let M=e.waferOpts?.diameter;const T=f.map(c=>({x:(Math.round(c.x)-y)*d-F,y:(Math.round(c.y)-b)*p-w})),W=K(T.map(({x:c,y:x})=>Math.hypot(Math.abs(c)+d/2,Math.abs(x)+p/2)));if(M===void 0)if(f.length>0)if(m==="mm"){const c=De(T,{minRadius:W});M=c.diameter,o.wafer={confidence:c.confidence,method:c.method};const x=je(e.standardDiameters);if(!Xe(M,x)){const S=de(M,x);g.push({code:"non-standard-diameter",message:`${Te} ${M.toFixed(1)} mm is not a standard wafer size (nearest is ${S} mm). The diameter was derived from the die extent, which assumes the probed grid reaches the wafer edge \u2014 a non-standard result usually means it does not, because the outer dies were never probed. Every die is then placed against a wafer that is too small, which moves dies between rings and changes ring and edge findings. Supply waferConfig.diameter.`})}}else M=W*2,o.wafer={confidence:u.confidence*.8,method:"extent"};else M=m==="mm"?300:30,o.wafer={confidence:0,method:"default"};if(!B&&f.length>0&&Fe(f,y,b)&&(o.wafer.method="inferred-partial",g.push({code:"partial-coverage",message:"Wafer geometry was inferred from die positions alone. The data does not span a full symmetric wafer, so the inferred diameter and centre may be wrong and dies may be mis-positioned relative to the true wafer boundary. Supply waferConfig.diameter and waferConfig.center (the prober coordinate of the wafer centre) to position partial data correctly."})),e.waferOpts?.diameter!==void 0&&e.waferOpts.center===void 0&&W>0&&f.length>=Ye){const c=W/(M/2);c<Be&&g.push({code:"diameter-exceeds-die-extent",message:`${_e} ${(c*100).toFixed(0)}% of the ${M} mm wafer's radius. Either the diameter is larger than the real one, or this is a partial map covering only part of the wafer. Ring bands are equal-radius, so the outer rings are mostly or entirely empty and ring, quadrant and edge findings describe the assumed wafer rather than the probed area. Check waferConfig.diameter, or drop it and let the diameter be inferred from the die extent.`})}if(e.waferOpts?.diameter!==void 0){const c=e.dieOpts?.width!==void 0&&e.dieOpts?.height!==void 0;if(c&&W>M/2+1e-9){const x=T.filter(({x:S,y:I})=>Math.hypot(Math.abs(S)+d/2,Math.abs(I)+p/2)>M/2+1e-9).length;g.push({code:"geometry-conflict",message:`${x} of ${T.length} probed die positions ${Ve} ${M} mm wafer at the supplied die pitch of ${d} \xD7 ${p} mm. A die with test results is a real prober position and is always fully on the wafer, so one of the two supplied values must be wrong: containing these dies at this pitch would need a diameter of at least ${(W*2).toFixed(1)} mm. Check waferConfig.diameter and dieConfig.width/height against the real device.`})}}const O=j({diameter:M,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});let v=a.map(c=>{const x=Math.round(c.x)-y,S=Math.round(c.y)-b,I={id:`${x}_${S}`,x,y:S,physX:x*d-F,physY:S*p-w,width:d,height:p,insideWafer:!0,partial:!1};return _(I,c)});(y!==0||b!==0)&&(v=v.map(c=>({...c,x:c.x+y,y:c.y+b,id:`${c.x+y}_${c.y+b}`}))),v=U(v,O),(R||$)&&(v=me(v,{flipX:R,flipY:$},O.center));const z=[];e.waferOpts?.edgeExclusion&&e.waferOpts.edgeExclusion>0&&(v=Ie(v,O,e.waferOpts.edgeExclusion),e.waferOpts.edgeExclusion>O.radius&&z.push({code:"edge-exclusion-exceeds-radius",message:`edgeExclusion (${e.waferOpts.edgeExclusion}mm) exceeds the resolved wafer radius (${O.radius}mm) \u2014 the entire wafer is excluded.`,severity:"warning"}));const G=oe(e.reticleOpts,O,v,d,p,y,b,F,w,O.orientation,R,$),fe=s.showReticle??e.reticleOpts!==void 0,P=J(O,v,{...s,reticles:G,showReticle:fe,plotMode:re(h,s),testDefs:e.testDefs,passBins:e.passBins,ringCount:e.ringCount,dataAxisFlip:{x:R,y:$},isLotStack:e.lotStackOpts!==void 0,aggregationMethod:e.lotStackOpts?.method,lotSize:e.lotStackOpts?.results.length},{hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,metadataFields:e.metadataFields}),ce=i.map((c,x)=>_({id:`unpositioned_${x}`,width:d,height:p},c)),Y=[...v,...ce];return{wafer:O,dies:Y,view:P,reticleConfig:e.reticleOpts,units:m,inference:o,warnings:[...e.removedFieldWarning?[e.removedFieldWarning]:[],...e.nonNumericWarning?[e.nonNumericWarning]:[],...ae(g,o),...z,...e.ringCountWarning?[e.ringCountWarning]:[]],plotMode:P.plotMode,metadata:P.metadata,isLotStack:e.lotStackOpts!==void 0,dataCoverage:se(Y),passBins:e.passBins,ringCount:e.ringCount,yield:ie(Y,e.passBins,e.edgeDieYieldMode),reticles:G,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields,aggrMethod:P.aggrMethod,lotSize:P.lotSize}}
|
|
1
|
+
import{createWafer as z}from"../core/wafer.js";import{isYieldEligibleDie as xe,getDieKey as B,hasPosition as E}from"../core/dies.js";import{applyOrientation as ee,transformDies as De,clipDiesToWafer as Me}from"../core/transforms.js";import{generateDies as ve}from"../core/dies.js";import{affineRotation as Ce,affineMirror as Oe,affineCompose as Se,affinePoint as $e}from"../core/transforms.js";import{inferWaferFromXY as ke}from"../core/inference/wafer.js";import{resolveGridPitch as Ae}from"../core/inference/pitch.js";import{assignGridIndices as Ne}from"../core/inference/grid.js";import{generateReticleGrid as Re}from"../core/reticle.js";import{applyDerivedTests as te}from"./derivedTests/apply.js";import{buildView as ne}from"./buildView.js";import{maxOf as G,minOf as se,modeOf as We}from"../core/utils.js";import{aggregateValues as ie,aggregateBinCounts as Ee}from"../core/aggregates.js";export function isParametricTest(t){return t?.testType!=="F"}function Ie(t){const n=(f,i)=>({ringCount:f,ringCountWarning:{code:"analysis-option-corrected",severity:"warning",message:`ringCount=${String(t)} ${i} (using ${f}).`}});if(t===void 0)return{ringCount:4,ringCountWarning:void 0};if(typeof t!="number"||!Number.isFinite(t))return n(4,"is not a finite number");const e=Math.max(1,Math.round(t));return e===t?{ringCount:t,ringCountWarning:void 0}:n(e,"is not a whole number of at least 1")}function re(t){const n=Array.isArray(t)?{}:t,e=Array.isArray(n.lotStack?.results)?n.lotStack.results:[],f=(Array.isArray(t)?[t]:[n.results,...e]).filter(h=>Array.isArray(h)),i={hbin:0,sbin:0,testValues:0,testPass:0};let o;const u=(h,S)=>{i[h]++,o??=`${h} ${JSON.stringify(S)}`},s={bins:0,positions:0,testNumbers:0,values:0,sites:0,orientation:0};let m=0;const c=h=>Number.isInteger(h)&&h>=0&&h<=oe,a=h=>{typeof h=="number"&&(Number.isNaN(h)?m++:c(h)||s.bins++)},g=h=>{typeof h=="number"&&!(Number.isInteger(h)&&Math.abs(h)<=H)&&s.positions++};for(const h of f){const S=new Set,w=new Set;for(let v=0;v<h.length;v+=v<50?1:100){const r=h[v];if(!(r===null||typeof r!="object")){if(r.testValues&&typeof r.testValues=="object")for(const $ of Object.keys(r.testValues))S.add($);if(r.testPass&&typeof r.testPass=="object")for(const $ of Object.keys(r.testPass))w.add($)}}const p=[...S],D=[...w];for(const v of new Set([...p,...D])){const r=Number(v);Number.isInteger(r)&&r>=0&&r<=ae||s.testNumbers++}for(const v of h){if(v===null||typeof v!="object")continue;const r=v;r.hbin!=null&&typeof r.hbin!="number"&&u("hbin",r.hbin),r.sbin!=null&&typeof r.sbin!="number"&&u("sbin",r.sbin),a(r.hbin),a(r.sbin),g(r.x),g(r.y),typeof r.siteNum=="number"&&!(Number.isInteger(r.siteNum)&&r.siteNum>=0&&r.siteNum<=255)&&s.sites++;const $=r.testValues;if($&&typeof $=="object")for(let N=0;N<p.length;N++){const O=$[p[N]];O!=null&&(typeof O!="number"?u("testValues",O):Number.isFinite(O)||s.values++)}const I=r.testPass;if(I&&typeof I=="object")for(let N=0;N<D.length;N++){const O=I[D[N]];O!=null&&typeof O!="boolean"&&u("testPass",O)}}}const y=n.waferConfig?.orientation;typeof y=="number"&&![0,90,180,270].includes((y%360+360)%360)&&s.orientation++;const l=[],b=(h,S,w=`${S}s`)=>`${h} ${h===1?S:w}`,x=[];if(i.hbin&&x.push(b(i.hbin,"hard bin")),i.sbin&&x.push(b(i.sbin,"soft bin")),i.testValues&&x.push(b(i.testValues,"test value")),i.testPass&&x.push(b(i.testPass,"pass/fail verdict")),x.length){const h=`buildWaferMap received ${x.join(", ")} of the wrong type (for example ${o}). Bins and test values must be numbers and verdicts true/false \u2014 a bin of "1" is not pass bin 1, so those dies count as fails and yield is wrong, and a test value given as text is not plotted or analysed correctly. Convert them with Number() (and verdicts to booleans) and rebuild.`;console.warn(`[wafermap] ${h}`),l.push({code:"input-values-not-numbers",message:h,severity:"error"})}const M=[];if(s.bins&&M.push(`${b(s.bins,"bin")} (legal: whole numbers 0\u2013${oe})`),s.positions&&M.push(`${b(s.positions,"coordinate")} (legal: whole numbers \u2212${H} to ${H})`),s.testNumbers&&M.push(`${b(s.testNumbers,"test number")} (legal: whole numbers 0\u2013${ae})`),s.values&&M.push(`${b(s.values,"test value")} that ${s.values===1?"is":"are"} not finite`),s.sites&&M.push(`${b(s.sites,"site number")} (legal: 0\u2013255)`),s.orientation&&M.push("a waferConfig.orientation other than 0, 90, 180 or 270"),M.length||m){const h=[];M.length&&h.push(`buildWaferMap received values outside the STDF V4 ranges: ${M.join("; ")}. They are used as given for now; a future release treats them as missing.`),m&&h.push(`${b(m,"bin was","bins were")} NaN and ${m===1?"is":"are"} treated as no bin.`);const S=h.join(" ");console.warn(`[wafermap] ${S}`),l.push({code:"input-values-outside-stdf",message:S,severity:"warning"})}return l}const oe=32767,H=32767,ae=4294967295;function de(t){const n=(c,a)=>c!==null&&typeof c=="object"&&c[a]!==void 0,e=Array.isArray(t)?{}:t,f=[],i=(c,a,g)=>{c&&f.push(`\`${a}\` (now \`${g}\`)`)};i(n(e,"data"),"data","results"),i(n(e,"die"),"die","dieConfig"),i(n(e,"stack"),"stack","lotStack"),i(n(e.dieConfig,"origin"),"dieConfig.origin","dieConfig.coordinateOrigin"),i(n(e.waferConfig,"flat"),"waferConfig.flat","waferConfig.notch"),i(n(e.reticleConfig,"anchor"),"reticleConfig.anchor","reticleConfig.anchorDie"),i(n(e.lotStack,"aggr"),"lotStack.aggr","lotStack.method"),i(Array.isArray(e.testDefs)&&e.testDefs.some(c=>n(c,"index")&&!n(c,"testNumber")),"TestDef.index","TestDef.testNumber");const o=n(e.lotStack,"results")?e.lotStack.results:[],u=(Array.isArray(t)?[t]:[e.results,...Array.isArray(o)?o:[]]).filter(c=>Array.isArray(c));if(i(u.some(c=>c.slice(0,100).some(a=>n(a,"values"))),"values","testValues"),!f.length)return;const s=f.length>1,m=`buildWaferMap ignored ${f.join(", ")}: ${s?"these names were":"that name was"} removed in an earlier release, so what ${s?"they":"it"} described is not on this map. Rename and rebuild.`;return console.warn(`[wafermap] ${m}`),{code:"input-field-removed",message:m,severity:"error"}}function Te(t){if(t.results!==void 0||t.lotStack!==void 0)throw new Error("buildWaferMap: pass `layout`, `results` or `lotStack` \u2014 only one.");const n=t.waferConfig?.diameter,e=t.dieConfig?.width,f=t.dieConfig?.height;if(!(n>0&&e>0&&f>0))throw new Error("buildWaferMap: `layout` needs waferConfig.diameter and dieConfig.width and height, all positive.");const i=z({diameter:n,notch:t.waferConfig?.notch,orientation:0}),o=Me(ve(i,{width:e,height:f}),i,{width:e,height:f}).filter(a=>!a.partial),{flipX:u,flipY:s}=fe(t.dieConfig,{type:"center"}),{layout:m,...c}=t;return{...c,results:o.map(a=>({x:u?-a.x:a.x,y:s?-a.y:a.y})),waferConfig:{...t.waferConfig,center:{x:0,y:0}},dieConfig:{...t.dieConfig,coordinateOrigin:{type:"center"}}}}function Fe(t){if(!Array.isArray(t)&&"results"in t&&t.results!==void 0&&"lotStack"in t&&t.lotStack!==void 0)throw new Error("buildWaferMap: pass either `results` or `lotStack`, not both.");return Array.isArray(t)?{results:t,waferOpts:void 0,dieOpts:void 0,explicitDies:void 0,reticleOpts:void 0,lotStackOpts:void 0,passBins:[1],ringCount:4,ringCountWarning:void 0,removedFieldWarning:de(t),inputValueWarnings:re(t),standardDiameters:void 0,testDefs:void 0,hbinDefs:void 0,sbinDefs:void 0,metadataFields:void 0,derivedTests:void 0,retestPolicy:"last",edgeDieYieldMode:"exclude"}:{results:t.results??[],waferOpts:t.waferConfig,dieOpts:t.dieConfig,explicitDies:t.dies,reticleOpts:t.reticleConfig,lotStackOpts:t.lotStack,passBins:t.passBins??[1],...Ie(t.ringCount),removedFieldWarning:de(t),inputValueWarnings:re(t),standardDiameters:t.standardDiameters,testDefs:t.testDefs?.length?t.testDefs:void 0,hbinDefs:t.hbinDefs,sbinDefs:t.sbinDefs,metadataFields:t.metadataFields,derivedTests:t.derivedTests?.length?t.derivedTests:void 0,retestPolicy:t.retestPolicy??"last",edgeDieYieldMode:t.edgeDieYieldMode??"exclude"}}function _e(t,n){return n?.coordinateOrigin?n.coordinateOrigin:{type:"center"}}function Pe(t,n,e){if(n.type==="custom"&&n.offset)return{offsetX:n.offset.x,offsetY:n.offset.y};if(n.type!=="center"){let f=1/0,i=-1/0,o=1/0,u=-1/0;for(const s of t)s.x<f&&(f=s.x),s.x>i&&(i=s.x),s.y<o&&(o=s.y),s.y>u&&(u=s.y);return{offsetX:Math.round((i+f)/2),offsetY:Math.round((u+o)/2)}}return{offsetX:e.offsetX,offsetY:e.offsetY}}function Ve(t,n,e,f,i,o){if(o)return{colMidX:(Math.round(o.x)-n)*f,colMidY:(Math.round(o.y)-e)*i,anchored:!0};if(t.length===0)return{colMidX:0,colMidY:0,anchored:!1};let u=1/0,s=-1/0,m=1/0,c=-1/0;for(const a of t){const g=Math.round(a.x)-n,y=Math.round(a.y)-e;g<u&&(u=g),g>s&&(s=g),y<m&&(m=y),y>c&&(c=y)}return{colMidX:(u+s)/2*f,colMidY:(m+c)/2*i,anchored:!1}}function Be(t,n,e){let f=1/0,i=-1/0,o=1/0,u=-1/0,s=0,m=0;for(const g of t){const y=Math.round(g.x)-n,l=Math.round(g.y)-e;s+=y,m+=l,y<f&&(f=y),y>i&&(i=y),l<o&&(o=l),l>u&&(u=l)}const c=t.length,a=(g,y,l)=>{const b=(l-y)/2;return b<=0?0:Math.abs(g/c-(y+l)/2)/b};return a(s,f,i)>.11||a(m,o,u)>.11}function fe(t,n){let e=t?.xAxisDirection==="left",f=t?.yAxisDirection==="down";return(n.type==="UL"||n.type==="UR")&&(f=!0),(n.type==="LR"||n.type==="UR")&&(e=!0),{flipX:e,flipY:f}}function Ye(t,n){const{method:e,targetBin:f}=t,i=t.results.map(o=>o.filter(E));if(e==="mean"||e==="median"||e==="stddev"||e==="min"||e==="max"||e==="count"){const o=new Set((n??[]).filter(a=>!isParametricTest(a)).map(a=>a.testNumber)),u=new Set;for(const a of i)for(const g of a)if(g.testValues)for(const y of Object.keys(g.testValues)){const l=Number(y);o.has(l)||u.add(l)}if(u.size===0)return ie(i,e);const s=[...u],m=s.map(a=>ie(i,e,a)),c=new Map;for(let a=0;a<s.length;a++){const g=s[a];for(const y of m[a]){const l=B(y);c.has(l)||c.set(l,{template:y,testValues:{}});const b=c.get(l),x=y.testValues?.[0];x!==void 0&&(b.testValues[g]=x)}}return[...c.values()].map(({template:a,testValues:g})=>({...a,testValues:Object.keys(g).length>0?g:void 0}))}if(e==="countBin"||e==="percent"){if(f===void 0)return[];const o=Ee(i,f,"hard");if(e==="countBin")return o;const u=i.length;return o.map(s=>({...s,testValues:{0:u>0?(s.testValues?.[0]??0)/u*100:0}}))}if(e==="mode"){const o=new Map;for(const s of i)for(const m of s){const c=B(m);o.has(c)||o.set(c,[]),o.get(c).push(m)}const u=[];for(const[s,m]of o){const c=s.split(","),a=Number(c[0]),g=Number(c[1]),y=m.map(b=>b.hbin).filter(b=>b!==void 0),l=We(y);l!==null&&u.push({x:a,y:g,hbin:l})}return u}return[]}function ce(t){const n=t.filter(E),e=n.length,f=n.filter(o=>o.edgeExcluded).length,i=n.filter(o=>dieHasTestData(o)||o.hbin!==void 0||o.sbin!==void 0).length;return{filledDies:i,totalDies:e,edgeExcludedDies:f,unpositionedDies:t.length-n.length,ratio:e>0?i/e:0}}function le(t,n,e="exclude"){const f=new Set(n),o=t.filter(l=>!l.partial).filter(l=>l.edgeExcluded).length,u=t.filter(l=>l.partial).length;let s=0,m=0,c=!1;for(const l of t){if(!xe(l))continue;const b=l.hbin??l.sbin;b!==void 0&&(c=!0,f.has(b)?s++:m++)}const a=s+m,g=c&&a>0?s/a*100:null;let y=null;if(e==="denominator-only"){const l=a+o;y=c&&l>0?s/l*100:null}return{passDies:s,failDies:m,edgeExcludedDies:o,partialDies:u,totalDies:a,yieldPercent:g,yieldPercentGross:y}}function ue(t,n,e,f,i,o=0,u=0,s=0,m=0,c=0,a=!1,g=!1){if(!t)return[];const y=t.anchorDie??{x:0,y:0},l=Re(n,{width:t.width,height:t.height,diePitchX:f,diePitchY:i,anchorDie:{x:y.x-o,y:y.y-u},gridOrigin:{x:-s,y:-m}}),b=Se(Oe(a,g,n.center.x,n.center.y),Ce(c,n.center.x,n.center.y));return l.filter(x=>{const M=x.width/2,h=x.height/2,S=[[x.x-M,x.y-h],[x.x+M,x.y-h],[x.x+M,x.y+h],[x.x-M,x.y+h]].map(([r,$])=>$e(b,r,$)),w=se(S.map(r=>r.x)),p=G(S.map(r=>r.x)),D=se(S.map(r=>r.y)),v=G(S.map(r=>r.y));return e.some(r=>r.physX>=w&&r.physX<p&&r.physY>=D&&r.physY<v)})}function Le(t,n,e){const i=Math.max(0,n.radius-e)**2;return t.map(o=>{const u=o.physX-n.center.x,s=o.physY-n.center.y;return u*u+s*s>i?{...o,edgeExcluded:!0}:o})}const Xe=.2;function je(t,n,e){const f=new Set(e);function i(w,p){if(n==="last")return!0;if(n==="first")return!1;const D=L(w.hbin),v=L(p.hbin);if(D===void 0||v===void 0)return!1;const r=f.has(D),$=f.has(v);return n==="best"?$!==r?$:v<D:$!==r?r:v>D}const o=(w,p)=>p.supersedes!==void 0||i(w,p),u=w=>{const p=w.partId===void 0?"":String(w.partId).trim();return p===""?void 0:p},s=t.filter(w=>!E(w)),m=new Map;for(const w of s){const p=u(w);p!==void 0&&m.set(p,(m.get(p)??0)+1)}const a=(s.length===0?0:Math.max(0,...m.values())/s.length)<=Xe,g=new Map,y=[],l=new Map;let b=0,x=0;for(const w of t){const p=u(w);let D;if(E(w)?D=`p:${B(w)}`:a&&p!==void 0?D=`i:${p}`:D=`u:${x++}`,w.supersedes==="partId"&&p!==void 0){const r=l.get(p);r!==void 0&&r!==D&&g.has(r)&&g.delete(r)}const v=g.get(D);v===void 0?(g.set(D,{winner:w,count:1}),y.push(D)):(v.count++,D.startsWith("i:")&&b++,o(v.winner,w)&&(v.winner=w)),p!==void 0&&l.set(p,D)}const M=[],h=[...y.filter(w=>w.startsWith("p:")),...y.filter(w=>!w.startsWith("p:"))];for(const w of h){const p=g.get(w);p!==void 0&&M.push(p.count>1?{...p.winner,retestCount:p.count}:p.winner)}const S=b>0?{code:"retests-by-part-id",severity:"info",message:`${b} record${b===1?"":"s"} with no position shared a part ID with an earlier record and ${b===1?"was":"were"} treated as a retest of it.`}:void 0;return{results:M,warning:S}}export function getDieTestValue(t,n){return t.testValues?.[n]}export function getTestPassStatus(t,n,e){const f=t.testPass?.[n];if(f!==void 0)return f;if(e!==void 0&&!isParametricTest(e)){const i=t.testValues?.[n];if(i===0||i===1)return i===1}}export function dieHasTestData(t){return t.testValues!==void 0&&Object.keys(t.testValues).length>0||t.testPass!==void 0&&Object.keys(t.testPass).length>0}function L(t){return t===void 0||Number.isNaN(t)?void 0:t}function X(t,n){const e={},f=L(n.hbin),i=L(n.sbin);return f!==void 0&&(e.hbin=f),i!==void 0&&(e.sbin=i),n.retestCount!==void 0&&(e.retestCount=n.retestCount),n.siteNum!==void 0&&(e.siteNum=n.siteNum),n.partId!==void 0&&(e.partId=n.partId),n.metadata!==void 0&&(e.metadata=n.metadata),n.testPass!==void 0&&(e.testPass=n.testPass),n.testValues!==void 0&&(e.testValues=n.testValues),{...t,...e}}function he(t,n){return n.plotMode?n.plotMode:t.some(dieHasTestData)?"value":"hardBin"}function pe(t,n){return t.map(({code:e,message:f})=>({code:e,message:f,severity:e==="non-standard-diameter"||e==="diameter-exceeds-die-extent"?"warning":"error",confidence:n.wafer.confidence}))}const Ke="do not fit inside the supplied",Ue="Inferred wafer diameter",ze="The probed dies reach only",Ge=.75,He=20;export const STANDARD_WAFER_DIAMETERS_MM=[100,125,150,200,300];const qe=.02;function me(t,n){return n.reduce((e,f)=>Math.abs(f-t)<Math.abs(e-t)?f:e)}function Je(t,n){if(!n.length)return!0;const e=me(t,n);return Math.abs(e-t)/e<=qe}function Qe(t){return t===void 0?STANDARD_WAFER_DIAMETERS_MM:t.filter(n=>Number.isFinite(n)&&n>0)}export function buildWaferMap(t,n){const e=Fe(!Array.isArray(t)&&t.layout?Te(t):t),{debug:f,...i}=n??{},o=[];if(e.derivedTests){const d=new Set(e.passBins);if(e.lotStackOpts){const C=e.lotStackOpts.results.map(k=>te(k,e.testDefs,e.derivedTests,d));e.lotStackOpts={...e.lotStackOpts,results:C.map(k=>k.results)},e.testDefs=C[0]?.testDefs??e.testDefs,o.push(...C[0]?.warnings??[])}else{const C=te(e.results,e.testDefs,e.derivedTests,d);e.results=C.results,e.testDefs=C.testDefs,o.push(...C.warnings)}}const u=e.lotStackOpts?Ye(e.lotStackOpts,e.testDefs):e.results;for(let d=0;d<Math.min(5,u.length);d++){const C=u[d];if(typeof C.x=="string"||typeof C.y=="string")throw new TypeError("buildWaferMap: x and y must be numbers, received strings. Did you forget to cast CSV values? e.g. { x: +row.x, y: +row.y }")}for(const d of u)if(d.x==null!=(d.y==null))throw new TypeError(`buildWaferMap: die has only one of x/y set (x=${String(d.x)}, y=${String(d.y)}). A die must be either fully positioned (both x and y) or fully unpositioned (neither) \u2014 omit both to mark a die as having no reported position.`);const{results:s,warning:m}=je(u,e.retestPolicy,e.passBins),c={wafer:{confidence:1,method:"provided"},diePitch:{confidence:1,units:"mm"},grid:{confidence:1}},a=[];if(e.explicitDies){let d=e.explicitDies;const C=s.filter(E),k=s.filter(V=>!E(V));if(C.length>0){const V=new Map(C.map(W=>[B(W),W]));d=d.map(W=>{const Z=V.get(B(W));return Z?X(W,Z):W})}const P=e.waferOpts?.diameter??300,F=z({diameter:P,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});F.orientation!==0&&(d=ee(d,F));const Q=ue(e.reticleOpts,F,d,1,1,0,0,0,0,F.orientation),be=i.showReticle??e.reticleOpts!==void 0,K=ne(F,d,{...i,reticles:Q,showReticle:be,plotMode:he(s,i),testDefs:e.testDefs,passBins:e.passBins,ringCount:e.ringCount,isLotStack:!1},{hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,metadataFields:e.metadataFields}),we=k.map((V,W)=>X({id:`unpositioned_${W}`,width:d[0]?.width??1,height:d[0]?.height??1},V)),U=[...d,...we];return{wafer:F,dies:U,view:K,reticleConfig:e.reticleOpts,units:"mm",inference:c,warnings:[...e.removedFieldWarning?[e.removedFieldWarning]:[],...e.inputValueWarnings,...m?[m]:[],...pe(a,c),...o,...e.ringCountWarning?[e.ringCountWarning]:[]],plotMode:K.plotMode,metadata:K.metadata,isLotStack:!1,dataCoverage:ce(U),passBins:e.passBins,ringCount:e.ringCount,yield:le(U,e.passBins,e.edgeDieYieldMode),reticles:Q,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields}}const g=s.filter(E),y=s.filter(d=>!E(d)),l=g.map(d=>({x:d.x,y:d.y})),b=Ae(l,e.dieOpts,e.waferOpts?.diameter);c.diePitch={confidence:b.confidence,units:b.units};const{pitchX:x,pitchY:M}=b,h=b.units,S=_e(g,e.dieOpts),w=Ne(l);c.grid={confidence:w.confidence};const{offsetX:p,offsetY:D}=Pe(l,S,w),{flipX:v,flipY:r}=fe(e.dieOpts,S),{colMidX:$,colMidY:I,anchored:N}=Ve(l,p,D,x,M,e.waferOpts?.center);let O=e.waferOpts?.diameter;const Y=l.map(d=>({x:(Math.round(d.x)-p)*x-$,y:(Math.round(d.y)-D)*M-I})),T=G(Y.map(({x:d,y:C})=>Math.hypot(Math.abs(d)+x/2,Math.abs(C)+M/2)));if(O===void 0)if(l.length>0)if(h==="mm"){const d=ke(Y,{minRadius:T});O=d.diameter,c.wafer={confidence:d.confidence,method:d.method};const C=Qe(e.standardDiameters);if(!Je(O,C)){const k=me(O,C);a.push({code:"non-standard-diameter",message:`${Ue} ${O.toFixed(1)} mm is not a standard wafer size (nearest is ${k} mm). The diameter was derived from the die extent, which assumes the probed grid reaches the wafer edge \u2014 a non-standard result usually means it does not, because the outer dies were never probed. Every die is then placed against a wafer that is too small, which moves dies between rings and changes ring and edge findings. Supply waferConfig.diameter.`})}}else O=T*2,c.wafer={confidence:b.confidence*.8,method:"extent"};else O=h==="mm"?300:30,c.wafer={confidence:0,method:"default"};if(!N&&l.length>0&&Be(l,p,D)&&(c.wafer.method="inferred-partial",a.push({code:"partial-coverage",message:"Wafer geometry was inferred from die positions alone. The data does not span a full symmetric wafer, so the inferred diameter and centre may be wrong and dies may be mis-positioned relative to the true wafer boundary. Supply waferConfig.diameter and waferConfig.center (the prober coordinate of the wafer centre) to position partial data correctly."})),e.waferOpts?.diameter!==void 0&&e.waferOpts.center===void 0&&T>0&&l.length>=He){const d=T/(O/2);d<Ge&&a.push({code:"diameter-exceeds-die-extent",message:`${ze} ${(d*100).toFixed(0)}% of the ${O} mm wafer's radius. Either the diameter is larger than the real one, or this is a partial map covering only part of the wafer. Ring bands are equal-radius, so the outer rings are mostly or entirely empty and ring, quadrant and edge findings describe the assumed wafer rather than the probed area. Check waferConfig.diameter, or drop it and let the diameter be inferred from the die extent.`})}if(e.waferOpts?.diameter!==void 0){const d=e.dieOpts?.width!==void 0&&e.dieOpts?.height!==void 0;if(d&&T>O/2+1e-9){const C=Y.filter(({x:k,y:P})=>Math.hypot(Math.abs(k)+x/2,Math.abs(P)+M/2)>O/2+1e-9).length;a.push({code:"geometry-conflict",message:`${C} of ${Y.length} probed die positions ${Ke} ${O} mm wafer at the supplied die pitch of ${x} \xD7 ${M} mm. A die with test results is a real prober position and is always fully on the wafer, so one of the two supplied values must be wrong: containing these dies at this pitch would need a diameter of at least ${(T*2).toFixed(1)} mm. Check waferConfig.diameter and dieConfig.width/height against the real device.`})}}const R=z({diameter:O,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});let A=g.map(d=>{const C=Math.round(d.x)-p,k=Math.round(d.y)-D,P={id:`${C}_${k}`,x:C,y:k,physX:C*x-$,physY:k*M-I,width:x,height:M,insideWafer:!0,partial:!1};return X(P,d)});(p!==0||D!==0)&&(A=A.map(d=>({...d,x:d.x+p,y:d.y+D,id:`${d.x+p}_${d.y+D}`}))),A=ee(A,R),(v||r)&&(A=De(A,{flipX:v,flipY:r},R.center));const q=[];e.waferOpts?.edgeExclusion&&e.waferOpts.edgeExclusion>0&&(A=Le(A,R,e.waferOpts.edgeExclusion),e.waferOpts.edgeExclusion>R.radius&&q.push({code:"edge-exclusion-exceeds-radius",message:`edgeExclusion (${e.waferOpts.edgeExclusion}mm) exceeds the resolved wafer radius (${R.radius}mm) \u2014 the entire wafer is excluded.`,severity:"warning"}));const J=ue(e.reticleOpts,R,A,x,M,p,D,$,I,R.orientation,v,r),ge=i.showReticle??e.reticleOpts!==void 0,_=ne(R,A,{...i,reticles:J,showReticle:ge,plotMode:he(s,i),testDefs:e.testDefs,passBins:e.passBins,ringCount:e.ringCount,dataAxisFlip:{x:v,y:r},isLotStack:e.lotStackOpts!==void 0,aggregationMethod:e.lotStackOpts?.method,lotSize:e.lotStackOpts?.results.length},{hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,metadataFields:e.metadataFields}),ye=y.map((d,C)=>X({id:`unpositioned_${C}`,width:x,height:M},d)),j=[...A,...ye];return{wafer:R,dies:j,view:_,reticleConfig:e.reticleOpts,units:h,inference:c,warnings:[...e.removedFieldWarning?[e.removedFieldWarning]:[],...e.inputValueWarnings,...m?[m]:[],...pe(a,c),...q,...o,...e.ringCountWarning?[e.ringCountWarning]:[]],plotMode:_.plotMode,metadata:_.metadata,isLotStack:e.lotStackOpts!==void 0,dataCoverage:ce(j),passBins:e.passBins,ringCount:e.ringCount,yield:le(j,e.passBins,e.edgeDieYieldMode),reticles:J,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields,aggrMethod:_.aggrMethod,lotSize:_.lotSize}}
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The release that deletes
|
|
3
|
-
* each notice and each `@deprecated` tag,
|
|
4
|
-
* which fails once the changelog or
|
|
2
|
+
* The release that deletes an export deprecated through this helper, unless its
|
|
3
|
+
* deprecation names a later one. Named in each notice and each `@deprecated` tag,
|
|
4
|
+
* and held to by tests/deprecations.test.mjs, which fails once the changelog or
|
|
5
|
+
* package.json reaches an export's removal release with it still here.
|
|
5
6
|
*/
|
|
6
7
|
export declare const DEPRECATED_REMOVAL_VERSION = "0.31.0";
|
|
8
|
+
/**
|
|
9
|
+
* @internal The release that removes each deprecated name. A name deprecated
|
|
10
|
+
* after the default removal release was scheduled gets a later one — a removal
|
|
11
|
+
* must follow a release in which the name shipped deprecated, never coincide
|
|
12
|
+
* with it.
|
|
13
|
+
*/
|
|
14
|
+
export declare const DEPRECATED_REMOVALS: Map<string, string>;
|
|
7
15
|
/**
|
|
8
16
|
* @internal Every name deprecated through this module, and whether it is a function
|
|
9
17
|
* (which logs a notice) or a value (which cannot). tests/deprecations.test.mjs pins
|
|
@@ -22,10 +30,10 @@ export declare function noticeOnce(key: string, message: string): void;
|
|
|
22
30
|
* The wrapper has exactly `fn`'s type — generics and overloads included — so a
|
|
23
31
|
* deprecated export's signature does not change until it is removed.
|
|
24
32
|
*/
|
|
25
|
-
export declare function deprecated<F extends (...args: never[]) => unknown>(fn: F, name: string, advice: string): F;
|
|
33
|
+
export declare function deprecated<F extends (...args: never[]) => unknown>(fn: F, name: string, advice: string, removal?: string): F;
|
|
26
34
|
/**
|
|
27
35
|
* @internal Register a deprecated constant. A value cannot log on use, so its
|
|
28
36
|
* `@deprecated` tag and the changelog are the only announcement.
|
|
29
37
|
*/
|
|
30
|
-
export declare function deprecatedValue<T>(value: T, name: string): T;
|
|
38
|
+
export declare function deprecatedValue<T>(value: T, name: string, removal?: string): T;
|
|
31
39
|
//# sourceMappingURL=deprecate.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const DEPRECATED_REMOVAL_VERSION="0.31.0",DEPRECATED_EXPORTS=new Map;const
|
|
1
|
+
export const DEPRECATED_REMOVAL_VERSION="0.31.0",DEPRECATED_REMOVALS=new Map,DEPRECATED_EXPORTS=new Map;const r=new Set;export function noticeOnce(t,e){r.has(t)||(r.add(t),console.warn(`[wafermap] ${e}`))}export function deprecated(t,e,n,o=DEPRECATED_REMOVAL_VERSION){DEPRECATED_EXPORTS.set(e,"function"),DEPRECATED_REMOVALS.set(e,o);const c=t;return((...E)=>(noticeOnce(e,`${e} is deprecated and will be removed in ${o}. ${n}`),c(...E)))}export function deprecatedValue(t,e,n=DEPRECATED_REMOVAL_VERSION){return DEPRECATED_EXPORTS.set(e,"value"),DEPRECATED_REMOVALS.set(e,n),t}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build-time application of derived tests.
|
|
3
|
+
*
|
|
4
|
+
* Runs over raw `DieResult` records **before** `collapseLotStack` and before
|
|
5
|
+
* retest resolution, and that ordering is load-bearing:
|
|
6
|
+
*
|
|
7
|
+
* - **Before stacking.** `abs(t[1020] - t[1010])` computed from the two raw
|
|
8
|
+
* values on one probe record is a real measurement of one die. The same
|
|
9
|
+
* expression computed from two independently aggregated stacks is a difference
|
|
10
|
+
* of two averages over possibly different die populations — a number with no
|
|
11
|
+
* physical meaning that would nonetheless plot happily.
|
|
12
|
+
* - **Before retest collapse.** Every accessor then reads the same probe record,
|
|
13
|
+
* so a derived value can never mix a value from one touchdown with a verdict
|
|
14
|
+
* from another. `retestPolicy` afterwards selects among records that already
|
|
15
|
+
* carry their own derived value.
|
|
16
|
+
*
|
|
17
|
+
* A derived test is an ordinary test from here on: it lands in `testValues` (or
|
|
18
|
+
* `testPass`, if its expression is boolean) and its def joins `testDefs`, so
|
|
19
|
+
* every downstream surface — value plot modes, the colorbar, tooltips,
|
|
20
|
+
* `analyzeWaferMap`, Insights, the report, gallery reconciliation — picks it up
|
|
21
|
+
* with no further change.
|
|
22
|
+
*/
|
|
23
|
+
import type { DieResult, TestDef, WaferWarning } from '../buildWaferMap.js';
|
|
24
|
+
/**
|
|
25
|
+
* A test whose values are computed from other tests on the same die, rather than
|
|
26
|
+
* measured by the tester.
|
|
27
|
+
*
|
|
28
|
+
* It is a `TestDef` with an expression, not a parallel concept: `unit`,
|
|
29
|
+
* `limitLow`/`limitHigh` and `logScale` already mean exactly the right thing for
|
|
30
|
+
* a derived quantity, and `result.testDefs` stays one homogeneous array.
|
|
31
|
+
*
|
|
32
|
+
* ```ts
|
|
33
|
+
* { testNumber: 900001, name: 'Leakage Shift', unit: 'uA',
|
|
34
|
+
* expression: 'abs(t[1020] - t[1010])', limitHigh: 5 }
|
|
35
|
+
*
|
|
36
|
+
* { testNumber: 900002, name: 'Sweep All Pass', testType: 'F',
|
|
37
|
+
* expression: 'all(testPass[1010..1025])' }
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export interface DerivedTestDef extends TestDef {
|
|
41
|
+
/**
|
|
42
|
+
* Expression over other tests on the same die.
|
|
43
|
+
*
|
|
44
|
+
* Reads: `t[n]` (measured value, parametric tests only), `testPass[n]` (the
|
|
45
|
+
* tester's recorded verdict), `specPass[n]` (spec-limit judgement, needs
|
|
46
|
+
* limits), `diePass()` (the die's bin verdict). A range — `t[1010..1015]` —
|
|
47
|
+
* produces a set that must be reduced: `mean`, `sum`, `min`, `max`, `all`,
|
|
48
|
+
* `any`, `none`, `countTrue`, `countFalse`, `countKnown`.
|
|
49
|
+
*
|
|
50
|
+
* Operators `+ - * / % ^`, comparisons, `and`/`or`/`not`, and
|
|
51
|
+
* `if(cond, a, b)`. Functions `abs sqrt ln log10 exp floor ceil round sign
|
|
52
|
+
* pow min max`.
|
|
53
|
+
*
|
|
54
|
+
* Evaluated with no JavaScript engine of any kind: it is parsed to a typed
|
|
55
|
+
* tree at build time and walked per die. There is no `eval`, no `Function`,
|
|
56
|
+
* no member access and no way to name a host object.
|
|
57
|
+
*/
|
|
58
|
+
expression: string;
|
|
59
|
+
/** Named constants usable in `expression`. The only resolvable identifiers
|
|
60
|
+
* besides function names. */
|
|
61
|
+
constants?: Record<string, number>;
|
|
62
|
+
/**
|
|
63
|
+
* Marks this def as derived. Set by `buildWaferMap`, never by the caller.
|
|
64
|
+
*
|
|
65
|
+
* It is a display flag: every surface that names the test reads it, through
|
|
66
|
+
* `renderer/testLabel.ts`, to show that the value was not measured. It does
|
|
67
|
+
* NOT exclude derived tests from anything — a derived test is an ordinary test
|
|
68
|
+
* from the build onwards, and appears in `functionalYield`, the per-test
|
|
69
|
+
* statistics, capability, findings and Insights exactly like a measured one.
|
|
70
|
+
*/
|
|
71
|
+
readonly derived?: true;
|
|
72
|
+
}
|
|
73
|
+
export interface ApplyResult {
|
|
74
|
+
results: DieResult[];
|
|
75
|
+
testDefs: TestDef[] | undefined;
|
|
76
|
+
warnings: WaferWarning[];
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Apply `derivedTests` to `results`, returning new records that carry the
|
|
80
|
+
* derived values and a `testDefs` array extended with the derived defs.
|
|
81
|
+
*
|
|
82
|
+
* Input records are not mutated — the caller's array is its own.
|
|
83
|
+
*/
|
|
84
|
+
export declare function applyDerivedTests(results: DieResult[], testDefs: TestDef[] | undefined, derivedTests: DerivedTestDef[] | undefined, passBins: ReadonlySet<number>): ApplyResult;
|
|
85
|
+
//# sourceMappingURL=apply.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{diePassStatus as P}from"../../core/dies.js";import{getTestPassStatus as D,isParametricTest as S}from"../buildWaferMap.js";import{classifySpec as V}from"../spec.js";import{parseExpression as k}from"./parser.js";import{evaluate as O}from"./evaluate.js";function p(m){return{code:"derived-test-invalid",message:m,severity:"warning"}}function M(m,l){const w=[],o=n=>{switch(n.k){case"acc":{if(n.kind!=="t")return;const i=l.get(n.test)?.unit;return i?new Set([i]):void 0}case"unary":return n.op==="-"?o(n.arg):void 0;case"binary":{const i=o(n.left),a=o(n.right);if(n.op==="+"||n.op==="-"){if(i&&a){const d=new Set([...i,...a]);return d.size>1&&w.push(`${[...d].join(" and ")} are combined by "${n.op}"`),d}return i??a}return}case"call":{if(n.name==="min"||n.name==="max"||n.name==="mean"||n.name==="sum"){const i=n.args[0];if(i?.k==="vec"&&i.kind==="t"){const a=new Set(i.tests.map(d=>l.get(d)?.unit).filter(d=>!!d));return a.size>1&&w.push(`${[...a].join(" and ")} are combined by "${n.name}"`),a}}return}default:return}};return o(m),w}function T(m,l,w){const o=[],n=new Map((l??[]).map(e=>[e.testNumber,e])),i=e=>`${JSON.stringify(e.name??"")} (test ${e.testNumber})`,a=[],d=new Set;for(const e of m){const t=i(e);if(!Number.isInteger(e.testNumber)){o.push(p(`Derived test ${t} was dropped: \`testNumber\` must be an integer.`));continue}if(w.has(e.testNumber)||n.has(e.testNumber)){o.push(p(`Derived test ${t} was dropped: test number ${e.testNumber} already exists in the data. Measured values are never replaced by a derived test \u2014 give it an unused number.`));continue}if(d.has(e.testNumber)){o.push(p(`Derived test ${t} was dropped: test number ${e.testNumber} is used by an earlier derived test.`));continue}if(typeof e.expression!="string"||e.expression.trim()===""){o.push(p(`Derived test ${t} was dropped: \`expression\` is empty.`));continue}d.add(e.testNumber),a.push(e)}const y=new Map(n);for(const e of a){const{expression:t,constants:u,...c}=e;y.set(e.testNumber,{...c,testType:e.testType??"P"})}const s=new Map,r=new Set;for(const e of a){const t=i(e),u={testDefs:y,constants:e.constants??{}},c=k(e.expression,u);if(!c.ok){o.push(p(`Derived test ${t} was dropped: ${c.message}`)),r.add(e.testNumber);continue}const f=c.type==="boolean",g=!S(e);if(f!==g){o.push(p(f?`Derived test ${t} was dropped: its expression produces a true/false verdict, so it must declare \`testType: 'F'\`.`:`Derived test ${t} was dropped: it declares \`testType: 'F'\` but its expression produces a number. Functional tests carry a verdict, not a measurement.`)),r.add(e.testNumber);continue}if(f&&(e.limitLow!==void 0||e.limitHigh!==void 0)&&o.push(p(`Derived test ${t}: test limits are ignored on a true/false derived test.`)),e.testNumber===void 0)continue;const N=e.testNumber,x=[...new Set(c.reads.map(h=>h.test))].filter(h=>h!==N&&d.has(h));if(c.reads.some(h=>h.test===N)){o.push(p(`Derived test ${t} was dropped: its expression reads itself.`)),r.add(N);continue}for(const h of M(c.ast,y))o.push(p(`Derived test ${t} combines different units \u2014 ${h}. The result is computed as written; set \`unit\` on the derived test to say what it is.`));s.set(N,{raw:e,compiled:{def:{...e,testType:f?"F":"P",derived:!0},ast:c.ast,boolean:f},deps:x})}const b=[],v=new Map,$=(e,t)=>{if(v.get(e)==="done")return!r.has(e);if(v.get(e)==="visiting"){const f=[...t.slice(t.indexOf(e)),e].join(" \u2192 ");for(const g of t.slice(t.indexOf(e)))r.has(g)||(r.add(g),o.push(p(`Derived test ${i(s.get(g).raw)} was dropped: its expression depends on itself through ${f}.`)));return!1}const u=s.get(e);if(u===void 0)return!1;v.set(e,"visiting");let c=!0;for(const f of u.deps)if(!$(f,[...t,e])){if(!r.has(e)){r.add(e);const g=s.get(f)?.raw.name??`test ${f}`;o.push(p(`Derived test ${i(u.raw)} was dropped: it reads ${JSON.stringify(g)} (test ${f}), which was itself dropped.`))}c=!1;break}return v.set(e,"done"),!c||r.has(e)?!1:(b.push(u.compiled),!0)};for(const e of a)$(e.testNumber,[]);return{compiled:b,warnings:o}}export function applyDerivedTests(m,l,w,o){if(w===void 0||w.length===0)return{results:m,testDefs:l,warnings:[]};const n=new Set;for(const s of m){for(const r of Object.keys(s.testValues??{}))n.add(Number(r));for(const r of Object.keys(s.testPass??{}))n.add(Number(r))}const{compiled:i,warnings:a}=T(w,l,n);if(i.length===0)return{results:m,testDefs:l,warnings:a};const d=new Map((l??[]).map(s=>[s.testNumber,s]));for(const s of i)d.set(s.def.testNumber,s.def);return{results:m.map(s=>{let r,b;const v=t=>r?.[t]??s.testValues?.[t],e={value:v,testPass:t=>b?.[t]??D({testValues:r??s.testValues,testPass:b??s.testPass},t,d.get(t)),specPass:t=>{const u=V(v(t),d.get(t));return u===null?void 0:u==="pass"},diePass:()=>P(s,o)};for(const t of i){const u=O(t.ast,e);u!==void 0&&(t.boolean?(b??={...s.testPass},b[t.def.testNumber]=u):(r??={...s.testValues},r[t.def.testNumber]=u))}return r===void 0&&b===void 0?s:{...s,...r!==void 0?{testValues:r}:{},...b!==void 0?{testPass:b}:{}}}),testDefs:[...l??[],...i.map(s=>s.def)],warnings:a}}
|