Each snippet below is one entry of insights.sweeps — a chart
definition passed to the renderer, not a test and not part of
buildWaferMap's input:
renderWaferGallery(container, items, {
insights: { enabled: true, defaultView: 'sweeps', sweeps: [ /* the five below */ ] },
});
In tsmap the same entries go in a
sweeps file of their own (Setup ▾ → Sweeps…, or --sweeps), separate
from the test definitions.
{ id: 'fmax-vdd',
title: 'Fmax vs VDD — 25 °C vs 125 °C',
series: [
{ label: '25 °C', tests: FMAX_COLD, xValues: VDD },
{ label: '125 °C', tests: FMAX_HOT, xValues: VDD },
],
separationAt: [1.2e9, 2.0e9],
xLabel: 'VDD (V)', yLabel: 'Fmax' }
At advanced nodes the hot corner is faster at low voltage: threshold voltage falls with temperature faster than mobility loss slows the transistor. The crossing is the temperature-inversion voltage — below it, 125 °C is no longer the worst case, which changes which corner a low-voltage sign-off has to be run at. The two widths are the extra VDD the slower corner needs to reach 1.2 GHz and 2.0 GHz; note that the slower corner swaps between them.
// One derived test per step: log10 of the measured drain current.
{ testNumber: 3200 + i, name: `log Id lin ${vg}`,
expression: `log10(t[${3100 + i}])` }
{ id: 'dibl',
title: 'NMOS transfer curve — Vds 0.05 V vs 0.8 V',
series: [
{ label: 'Vds = 0.05 V', tests: LOG_ID_LIN, xValues: VG },
{ label: 'Vds = 0.8 V', tests: LOG_ID_SAT, xValues: VG },
],
crossing: false,
separationAt: [-7],
xLabel: 'Vg (V)', yLabel: 'log10 Id (A)' }
A transfer curve spans decades of current, so it is only
readable with current on a log scale. That is the y axis, and a sweep
reads any test, including a derived one — so each step is swept
as log10(t[n]). (A log x axis is xScale; see
sweep 5.) The width at log10 Id = −7
(100 nA) is the gate-voltage shift between the two drain biases at constant
current — the constant-current threshold shift. Divided by the 0.75 V change
in Vds it is DIBL. The curves never cross, so crossing: false keeps
the card from reporting that.
The map opens on Subthreshold slope, a derived
test of two of those derived tests: 0.1 / (t[3202] - t[3200]), the
gate voltage per decade of current. The † in front of its name says it was not
measured.
const BAKE_H = [0, 24, 48, 96, 168, 336, 500, 750, 1000];
{ id: 'retention',
title: 'Retention — cell Vt through a 150 °C bake',
series: [
{ label: 'Programmed', tests: ['3300..3308'], xValues: BAKE_H },
{ label: 'Erased', tests: ['3310..3318'], xValues: BAKE_H },
],
xLabel: 'Bake time (h)', yLabel: 'Cell Vt' }
The two cell states drift towards each other as charge leaks
during an accelerated bake; the crossing is where the median read
window closes completely. The read points are not evenly spaced — they rarely are
in a qualification — which is what xValues is for: with it the
crossing is reported in hours, and without it the axis would be the ordinal
position of each read, with the crossing "between the 8th and 9th". Nothing is
interpolated along test numbers, which are identifiers, not a scale.
The tests are named as ranges, in the same
3300..3308 syntax a derived-test expression uses. A range picks up
only the tests that exist inside it, so a program numbered in steps of two needs
nothing extra; if one of them is missing from the data, the range comes up one
short of xValues and the card says so, rather than sliding every
later bake time onto the wrong read.
// In amps, the bare unit — xUnit prints them as mA.
const LOAD_A = [0, 2, 4, 6, 8, 10, 12, 14, 16].map(mA => mA / 1000);
{ id: 'io-drive',
title: 'Output driver — VOH vs VOL under load',
series: [
{ label: 'VOH (sourcing)', tests: ['3400..3408'], xValues: LOAD_A },
{ label: 'VOL (sinking)', tests: ['3410..3418'], xValues: LOAD_A },
],
xLabel: 'Load current', xUnit: 'A', yLabel: 'Output voltage' }
As load current rises, the high output sags and the low output
climbs. Where they cross, the pin can no longer present two
distinguishable logic levels: that current is the absolute ceiling on what the
driver can deliver, well beyond where it would already have left the VOH/VOL
spec. The p10–p90 bands show how much of the population gets there early.
xUnit makes the axis and the crossing read 8 mA rather
than a bare number beside "(mA)".
// Tests named "Normalized_LRS= LRS_STATS_12K / Total_LRS (…)": the
// threshold is only in the name, so read it from there.
{ id: 'lrs-cdf',
title: 'RRAM LRS distribution — before vs after bake',
series: [
{ label: 'Before bake', tests: ['3500..3509'], xFromName: 'LRS_STATS_{x}' },
{ label: 'After bake', tests: ['3510..3519'], xFromName: 'LRS_STATS_{x}' },
],
xScale: 'log', // thresholds grow by multiples: 1K … 1M
xUnit: 'Ω', xLabel: 'LRS threshold',
yLabel: 'Fraction of cells below',
separationAt: [0.5], // the median resistance shift
crossing: false }
Each test is one point of a cumulative distribution: the fraction
of RRAM cells whose low-resistance state reads below a threshold. The program
writes the threshold only into the test text, so xFromName reads it
from there — {x} takes the number with its SI prefix, so
12K is 12,000 Ω. It is a placeholder pattern, not a regular
expression, so a shared sweeps file cannot freeze the app.
The thresholds go up by multiples, so xScale: 'log'
gives each the same room. On a log axis the width at 0.5 is a
ratio — how many times higher the median cell resistance is after the
bake — with both ends shown. Two distributions of the same cells are not
expected to meet, so crossing: false.