Resources Numerical accuracy Calculation policy

GDSII layout read and placement

Reads an IC layout stream file and places every cell reference and array so the layout can be viewed and measured as drawing objects.

How the precision is kept

Coordinates in the file are 32-bit integers and are read with getInt32, so a 64-bit number holds each one exactly. The file's 8-byte base-16 reals are converted to 64-bit numbers. Placement composes 2 × 3 matrices in database units and multiplies by the user unit only at the end.

Calculations under this policy

3 calculations in 1 source file. The code is read from the released app when this page loads. It is the calculation people run, not a copy kept for this page.

gds.js CadCraft 0.8.2 64-bit 3 calculations Reviewed

Tests bin/test-cadcraft.mjs

8-byte base-16 real

64-bit

Converts the stream format's excess-64 base-16 real (units, magnification, angle) to a number.

value = sign × (m ÷ 2⁵⁶) × 16^(e − 64), m = the 7 mantissa bytes as one integer

The mantissa is built as m × 256 + byte over 7 bytes, which is exact for the first 6 bytes and rounds once on the 7th when the 56-bit value needs more than 53 bits. The division by 2⁵⁶ (written as 72057594037927936) only changes the exponent, and the power of 16 comes from Math.pow.

  • gds.js
  • lines 21 to 26
  • CadCraft 0.8.2
function real8(dv, p) {
  const b0 = dv.getUint8(p), sign = b0 & 0x80 ? -1 : 1, exp = (b0 & 0x7f) - 64;
  let m = 0;
  for (let i = 1; i < 8; i++) m = m * 256 + dv.getUint8(p + i);
  return sign * (m / 72057594037927936) * Math.pow(16, exp);   // 2^56
}

Reference transform

64-bit

The matrix that places one cell reference: reflect, magnify, rotate, move, and its product with the parent's matrix.

r = angle × π ÷ 180; M = [mag·cos r, mag·sin r, −f·mag·sin r, f·mag·cos r, x, y], f = −1 when reflected

The angle in degrees is converted with Math.PI and passed to cos and sin; right angles are not special-cased, so cos of 90° evaluates to about 6.1 × 10⁻¹⁷ instead of 0. Matrix products are plain sums of products with no tolerance.

  • gds.js
  • lines 82 to 87
  • CadCraft 0.8.2
const mul = (m, n) => [m[0] * n[0] + m[2] * n[1], m[1] * n[0] + m[3] * n[1], m[0] * n[2] + m[2] * n[3], m[1] * n[2] + m[3] * n[3], m[0] * n[4] + m[2] * n[5] + m[4], m[1] * n[4] + m[3] * n[5] + m[5]];
const place = (x, y, reflect, mag, angle) => {
  const r = (angle || 0) * Math.PI / 180, c = Math.cos(r) * mag, s = Math.sin(r) * mag, f = reflect ? -1 : 1;
  return [c, s, -s * f, c * f, x, y];   // reflect about the X axis first (y → −y), then magnify, rotate and move
};
const apply = (m, x, y) => [m[0] * x + m[2] * y + m[4], m[1] * x + m[3] * y + m[5]];

Flattened coordinates, array steps and path width

64-bit

Places every element of every referenced cell and array and converts its points and width to user units.

array step = (corner − origin) ÷ count; point = (M × p) × userUnit; width = w × √(M₀² + M₁²) × userUnit

Points are transformed in integer database units and multiplied by the user unit last; a user unit such as 0.001 is not exact in binary, so each coordinate is a rounded product. Array positions are origin + column × step + row × step, with the step from a division by the column or row count. A user unit that is zero or negative is replaced by 0.001.

  • gds.js
  • lines 89 to 120
  • CadCraft 0.8.2
export function flattenGds(gds, topName, { max = 400000, depth = 16 } = {}) {
  const items = [];
  let count = 0, truncated = false;
  const u = gds.lib.userUnit > 0 ? gds.lib.userUnit : 1e-3;   // user units per database unit
  const walk = (name, m, d) => {
    const els = gds.cells.get(name);
    if (!els || d > depth || truncated) return;
    for (const e of els) {
      if (truncated) return;
      if (e.k === 'sref' || e.k === 'aref') {
        const base = e.pts.length >= 2 ? e.pts : [0, 0];
        if (e.k === 'sref') { walk(e.name, mul(m, place(base[0], base[1], e.reflect, e.mag || 1, e.angle)), d + 1); continue; }
        const cols = e.cols || 1, rows = e.rows || 1;
        if (e.pts.length < 6) continue;
        const [x0, y0, xc, yc, xr, yr] = e.pts;
        const dxc = (xc - x0) / cols, dyc = (yc - y0) / cols, dxr = (xr - x0) / rows, dyr = (yr - y0) / rows;
        for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) {
          if (truncated) return;
          walk(e.name, mul(m, place(x0 + c * dxc + r * dxr, y0 + c * dyc + r * dyr, e.reflect, e.mag || 1, e.angle)), d + 1);
        }
        continue;
      }
      if (++count > max) { truncated = true; return; }
      const pts = [];
      for (let i = 0; i + 1 < e.pts.length; i += 2) { const [x, y] = apply(m, e.pts[i], e.pts[i + 1]); pts.push(x * u, y * u); }
      const k = Math.hypot(m[0], m[1]);
      items.push({ k: e.k === 'box' ? 'boundary' : e.k, layer: e.layer, datatype: e.datatype, pts, width: (e.width || 0) * Math.abs(k) * u, pathtype: e.pathtype || 0, string: e.string, h: 1 * Math.abs(k), angle: Math.atan2(m[1], m[0]) });
    }
  };
  walk(topName, [1, 0, 0, 1, 0, 0], 0);
  return { items, count: Math.min(count, max), truncated };
}

In detail

Method
Stream records are read as the file header comment describes them: 2-byte length, record type, data type, big-endian integers, 8-byte excess-64 base-16 reals. References are placed by reflecting about the X axis, then magnifying, rotating and moving.
Number types
64-bit numbers; coordinates come from 32-bit integers in the file, reals from a 56-bit base-16 mantissa.
Limits
A 56-bit mantissa has 3 more bits than a 64-bit number keeps, so a real with those bits set is rounded once. Files over 256 MB or 5,000,000 elements are refused; flattening stops at 400,000 items and 16 levels by default.

Approach: 64-bit, direct

Every value is a 64-bit number from input to result. Formulas are closed form or a direct solve, and every comparison uses a stated tolerance.

Every policy that takes this approach