Library / Teardown

Put a workload
on the die.

ARCLITH One is a processor that does not exist. Its die does something real: sixty times a second a 128 × 128 grid of heat is stepped forward by the heat equation, and the workload you pick decides where the heat comes from. Here is the whole mechanism, what it costs, and the readout that could not tell two workloads apart.

9.8 KBOf simulation
0.56 msPer frame
16 384Cells

The page is a launch site for a chip nobody makes: scroll from the module down to a transistor, take the package apart in 3D, and then — section three — put it to work. Pick Idle, Inference or Training and the floorplan starts to glow where that work would land.

The glow is not an animation. It is a grid of numbers stepped forward by the heat equation, and the temperature and power printed beside it are read out of the same grid.

Run it yourself

This is the same floorplan, the same loads and the same constants. The one thing added is a slider for the spread constant α, so you can see what it does — including past the point where the maths stops working.

The same model, liveStable
0.22
71 °CHotspot
230 WPower
NeuralBusiest
0.638Peak heat
0.7 %At top colour
1.78 ≤ 28α + c

It starts on Inference. Switch to Idle and let it cool, then to Training, and count: the blocks reach their new load in under two seconds, the heat takes nearly six. Then switch between Inference and Training and watch the hotspot: 79 °C against 92. Until this page was written, both read 92 — the last section is about why.

1 · The floorplan is data

Eight rectangles, each given as fractions of the die, each split into a few cells that heat independently, each with a load for the three workloads. Everything else is computed from this list.

the die, as a list
// rect = [x, y, width, height] as fractions of the die; cells = columns × rows of heaters
const BLOCKS = [
  { name: 'Performance cores',  rect: [0.05, 0.05, 0.36, 0.34], cells: [4, 2],
    load: { idle: 0.12, inference: 0.35, training: 0.55 } },
  { name: 'Neural accelerator', rect: [0.44, 0.05, 0.51, 0.40], cells: [2, 2],
    load: { idle: 0.04, inference: 0.78, training: 1.0 } },
  // … six more: efficiency cores, cache, memory controller, graphics, secure enclave, I/O
];

The die is drawn as 26 mm square, so one grid cell stands for about 0.2 mm. The neural block covers 20.4 % of the area and carries the heaviest load, which is why the eye goes there first. The loads are concept values — the page says so under the canvas — but they are consistent with each other, and that is what the picture needs.

2 · A workload is a target

Choosing a workload does not set the loads. It sets what each block is heading for, and every block eases toward it at a fixed rate.

target → level
const STEP = 1 / 60;                                   // one simulation step

// each block's level moves a fixed share of the way to its target every step
level[i] += (BLOCKS[i].load[target] - level[i]) * (1 - Math.exp(-STEP / 0.6));

A 0.6 s time constant puts a block 95 % of the way there in 1.8 s. The heat lags behind the load: from Idle to Training the hottest cell needs 5.6 s to reach 98 % of its final value. That second delay is what reads as thermal mass — the difference between a die warming up and a picture changing colour.

3 · Every cell is a heater

The 64 cells each inject heat into the grid in proportion to their block's level, with a slow flicker so a busy die never freezes into a still.

level → heat source
const rnd = mulberry32(2026);                          // seeded: the live page and a 4K render agree
const phases = BLOCKS.map((b) => Array.from({ length: b.cells[0] * b.cells[1] },
  () => [rnd() * 6.283, 0.6 + rnd() * 1.8]));           // phase, speed per cell

// inside one step, for cell (i, j) of block b
const [ph, sp] = phases[b][j * cx + i];
const q = level[b] * (0.7 + 0.3 * Math.sin(t * sp + ph)) * 0.022;
const x0 = Math.floor((x + w * i / cx) * N) + 1;         // +1 and −1 leave a one-cell cold seam
const x1 = Math.floor((x + w * (i + 1) / cx) * N) - 1;   // between neighbouring heaters
for (let yy = y0; yy <= y1; yy++) for (let xx = x0; xx <= x1; xx++) src[yy * N + xx] += q;

The flicker swings each cell ±30 % with a period between 2.6 and 10.5 s. Slow enough that nobody calls it flashing; fast enough that the die is visibly alive. Because the phases come from a seed, the same moment always looks the same — which is what lets the page render a frame-exact video of itself.

4 · Heat spreads, the cooler takes it away

This is the whole physics: one line inside two loops. The explicit five-point scheme for the heat equation, with a loss term for the heatsink.

one step of the heat equation
for (let y = 1; y < N - 1; y++) for (let x = 1; x < N - 1; x++) {
  const k = y * N + x;
  const lap = heat[k - 1] + heat[k + 1] + heat[k - N] + heat[k + N] - 4 * heat[k];
  next[k] = heat[k] + 0.22 * lap + src[k] - 0.02 * heat[k];
  //                  spread       made     taken away
}
heat.set(next);                                         // the rim is never written: it stays at 0

Two constants decide the look. Their ratio sets how far a hotspot glows before the cooler wins: √(α / c) = √(0.22 / 0.02) ≈ 3.3 cells, about 0.67 mm of silicon. Lower α and the hotspots stay sharp; raise the cooling and the die never lights up.

α also has a ceiling, and it is not a matter of taste. The textbook bound for this scheme is 8α + c ≤ 2 — α just under ¼. Measured with the training load: at 0.24 the die settles normally. At 0.25 the peak reads 6 870 after 20 seconds and 3.4 × 10²³ after a minute, as a checkerboard that grows about 2 % every step. Take the slider in the toy to 0.26 and you will see it; the toy wipes the grid when it runs away.

5 · The numbers come out of the same grid

The readouts beside the die are not a second model. They scan the grid the picture is drawn from.

grid → °C, W and colour
function stats() {
  let max = 0;
  for (let k = 0; k < N * N; k++) if (heat[k] > max) max = heat[k];
  const areaPower = BLOCKS.reduce((s, b, i) => s + level[i] * b.rect[2] * b.rect[3], 0);
  return {
    hot:   34 + Math.min(1, max / HOT_PEAK) * 58,        // °C, concept scale; HOT_PEAK = 0.992
    power: Math.min(350, 22 + areaPower * 640),          // W, concept scale
  };
}
// the picture has its own gain: colour(heat[k] * 1.6), through
// transparent → deep copper → copper → amber → ice white

Settled, the three workloads read 40 °C · 50 W, 79 °C · 230 W and 92 °C · 322 W. The 128 × 128 image is then stretched over the floorplan with a screen blend and a blur of 0.6 of a cell, so the grid reads as light on silicon rather than as squares.

Fixed step, any screen

The page accumulates real time and steps the grid in exact sixtieths of a second, so a 120 Hz display heats the die at the same rate as a 60 Hz one. The elapsed time is capped at 0.1 s, so a tab that stalls catches up with at most six steps instead of a burst of hundreds.

What it costs

A live simulation sounds expensive. This one is smaller than most hero images.

PartSizeNote
The simulation (js/diemap.js)9.8 KBfloorplan, model, colour and drawing; no dependencies
The grid192 KBthree 128 × 128 Float32Arrays: heat, next, source
One step0.069 msthe physics alone, M1 Pro
One step and one draw0.56 msin Chrome, on a 1120 × 1120 canvas, M1 Pro

At sixty steps a second that is about 34 ms of main thread per second — 3 % of it. No worker, no WebGL, no shader: a typed array, two loops and a 2D canvas.

What the measurement found

Measuring the model for this page turned up something no screenshot had: two of the three workloads printed the same temperature.

After twenty seconds the grid's peak is 0.774 under Inference and 0.992 under Training. Both are multiplied by the gain of 1.6, both come out above 1, and Math.min(1, …) clamps them to the same place: 92 °C either way. The picture clamps with it — the top colour covers 10.6 % of the die under Inference and 17.7 % under Training, and the neural block is the same white in both. Only the power readout tells them apart.

The gain had been chosen so the heaviest workload reaches the top of the scale. Nothing checked that the second-heaviest stays below it. The fix separates the two jobs the gain was doing: the picture keeps its 1.6, because the white-hot neural block is the look of the section, and the temperature gets a scale of its own, taken from the measured peak.

measure the scale, then set it
const HOT_PEAK = 0.992;                                // training, measured after 20 s

hot = 34 + Math.min(1, max / HOT_PEAK) * 58;            // idle 40 °C · inference 79 °C · training 92 °C

The live page runs this now. Clicked through on the real site: Inference settles at 79 °C, Training at 92 °C, and the two can be told apart again without reading the power.

The lesson worth keeping

A readout that clamps looks exactly like one that works. Put every state through it once and read the numbers side by side: if two different inputs print the same value, the scale is wrong, not the model.

See it running

The die is the third section of the page. Scroll there, pick a workload, and point at a block to read what it does.

Open ARCLITH ↗

Sources

The explicit (forward-time, centred-space) scheme for the heat equation and its stability bound: Press et al., Numerical Recipes, the chapter on diffusive initial-value problems. Seeded random numbers: mulberry32, public domain. The floorplan, loads, temperatures and power figures are concept values; ARCLITH One is fictional.