0hmX/am3352

This code suite comprises TypeScript scripts that analyze, verify, and assemble complex DDR memory interface hardware, focusing on physical routing, via and pad placement, electrical clearance, and physical constraints, often involving precise geometric calculations and consistent provenance tracking.

Version
1.0.5
License
unset
Stars
0

scripts/document-exits.ts

import { mkdirSync, writeFileSync } from 'node:fs'
import { getAM3352Exits, type ExitVector } from '../src/exits'
import { LAYOUT_PROFILES, getAM3352Bounds, getAM3352Layers, type LayoutProfile } from '../src/profiles'
const n = (value: number) => (Math.abs(value) < .0000005 ? 0 : value).toFixed(6).replace(/\.?0+$/, '')
const vector = (v: ExitVector) => `(${n(v.x)}, ${n(v.y)})`
const sections = [
  '# Saved signal exit contract',
  '',
  'Generated by `bun scripts/document-exits.ts` directly from saved trace paths and the AM3352 ball map. Regenerate after changing any profile. Requested solver exit preferences are not used as evidence of actual exits.',
  '',
  'Coordinates and lengths are millimeters in the module-local PCB frame: origin at chip center, +X right, +Y up. The boundary is the profile fanout size listed below, not the 15 × 15 mm package body or the host board edge. “Top side” means +Y boundary; “top layer” means outer copper. These are independent.',
  '',
  'Outward direction is the boundary normal toward the host board. Final tangent is the final nonzero copper segment, oriented from the source ball toward the exit. Source escape is the first nonzero segment away from the ball. The final tangent can differ from the outward direction; host routing must meet the actual endpoint/layer and respect approach clearance. None of these directions specifies electrical input/output direction.',
  '',
  'Planar length includes all saved XY copper segments from ball center to exit. It excludes package delay, vertical via length, and downstream host copper. Logical via spans describe the connected signal layers only. Every physical via barrel spans all copper layers of its profile, from top through every inner layer to bottom; a logical top→inner2 transition is not a blind via. Unused barrel sections can affect DDR signal integrity.',
  '',
  'The JSON contract also records per-layer planar lengths and logical via XY coordinates. Exit metadata is a geometric integration contract, not proof of DDR timing, impedance, reference-plane continuity, or full memory connectivity.',
  '',
  'All CPU profiles use only horizontal, vertical and 45° diagonal saved segments in this local frame. Same-layer bends change direction by at most 45° (135° inside corner), including fixed support copper. Fixed capacitor-to-via routes use the same permitted headings. A 90° directional turn must use two separated 45° bends. Rotating the whole module preserves bend angles while rotating segment headings; host continuation routing must enforce its own bend rule.',
  '',
  'Run `bun run check:angles` after rebuilding profiles. It checks every saved and fixed path, then all rendered traces (including the 201 edge contacts per profile), with octilinear headings and maximum 45° turns. The rendered audit also checks cross-trace and interior junctions: Y splits must admit a valid incoming direction; T/X or sharp joins fail. Only a coincident same-net via terminates a planar run. The public profile registry drives rendered coverage, so a new profile cannot silently omit this check.',
  '',
  '## Placement API',
  '',
  '```ts',
  "import { getAM3352Exits, transformAM3352Exit } from '@tsci/0hmX.am3352'",
  "const localExits = getAM3352Exits('native')",
  'const placedExits = localExits.map(exit => transformAM3352Exit(exit, {',
  '  pcbX: 25, pcbY: 10, pcbRotation: 90,',
  '}))',
  '```',
  '',
  'The public pcbX/pcbY placement remains the CPU center even when the boundary has unequal side padding. getAM3352Bounds(profile) returns the actual CPU-centered min/max bounds; fanoutWidthMm/fanoutHeightMm and padding.left/right/top/bottom give dimensions. Legacy fanoutSizeMm/paddingMm are maximum dimensions only and must not be used to infer symmetric bounds. Transform uses counterclockwise degrees in PCB XY, then translation. Exit coordinates, via coordinates and all three direction vectors transform. Copper layers, lengths and the `side` label remain in the local profile frame; use transformed `outwardDirection` to determine the world-facing side, including non-cardinal rotations. Apply a transform to a fresh local exit, not repeatedly to an already placed result. No layer mirroring is supported.',
]
mkdirSync('docs', { recursive: true })
for (const profile of Object.keys(LAYOUT_PROFILES) as LayoutProfile[]) {
  const exits = getAM3352Exits(profile)
  const settings=LAYOUT_PROFILES[profile],bounds=getAM3352Bounds(profile)
  writeFileSync(`src/generated/${profile}.exits.json`, JSON.stringify({
    schemaVersion: 1, profile, bounds, padding:settings.padding, units: 'mm', coordinateFrame: 'module-local, +X right, +Y up',
    physicalViaTechnology: `through-hole, all ${LAYOUT_PROFILES[profile].layerCount} layers`, exits,
  }, null, 2) + '\n')
  sections.push('', `## ${profile}`, '', `${settings.fanoutWidthMm} × ${settings.fanoutHeightMm} mm local boundary, x=[${bounds.minX}, ${bounds.maxX}], y=[${bounds.minY}, ${bounds.maxY}]. Package padding (left/right/top/bottom): ${settings.padding.left}/${settings.padding.right}/${settings.padding.top}/${settings.padding.bottom} mm; ${LAYOUT_PROFILES[profile].layerCount} layers (${getAM3352Layers(profile).join(' → ')}). ${exits.length} external signal exits. Fixed local supply and capacitor routes do not terminate at the fanout boundary and are excluded.`, '',
    '### DDR exit distribution', '', '| Side | Copper layer | Signals |', '| --- | --- | --- |')
  const groups = new Map<string, string[]>()
  for (const exit of exits.filter(exit => exit.signal.startsWith('DDR_'))) {
    const key = `${exit.side} | ${exit.layer}`
    groups.set(key, [...(groups.get(key) ?? []), exit.signal])
  }
  for (const [group, signals] of groups) sections.push(`| ${group} | ${signals.join(', ')} |`)
  sections.push('', '### All signal exits', '',
    '| Signal | Ball | Side | Outward (dx, dy) | Final tangent (dx, dy) | Source escape (dx, dy) | Layer | Exit (x, y) mm | Planar mm | Logical vias |',
    '| --- | --- | --- | --- | --- | --- | --- | --- | ---: | --- |')
  for (const exit of exits) sections.push(`| ${exit.signal} | ${exit.ball} | ${exit.side} | ${vector(exit.outwardDirection)} | ${vector(exit.finalSegmentDirection)} | ${vector(exit.sourceEscapeDirection)} | ${exit.layer} | ${vector(exit)} | ${n(exit.planarLengthMm)} | ${exit.logicalVias.map(via => `${via.fromLayer}→${via.toLayer}`).join(', ') || 'none'} |`)
}
writeFileSync('docs/signal-exits.md', sections.join('\n') + '\n')
console.log(`Documented exits for ${Object.keys(LAYOUT_PROFILES).join(', ')}`)