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/validate-ddr-profiles.ts

import { assertRouteAngles } from './check-route-angles'
import assert from 'node:assert/strict'
import { readFileSync, writeFileSync } from 'node:fs'
import { fanoutTracePath } from '@tscircuit/props'
import { validateDdrCopper } from './ddr-copper-check'
import { compactFanout, type FanoutBounds } from './compact-fanout'
import { AM3352_PINS } from '../src/pin-map'
import { auditAM3352Ddr } from '../src/ddr'

export const DDR_PROFILES = ['ddr_left', 'ddr_top', 'ddr_bottom'] as const
export function assertTightDdrBounds(input: any, traces: any[], bounds: FanoutBounds) {
  const fitted = compactFanout(input, traces, bounds)
  assert.deepEqual(fitted.bounds, bounds, 'Published DDR bounds must be the tight 0.01 mm enclosure with 0.1016 mm margin')
}
export function validateDdrProfile(profile: typeof DDR_PROFILES[number], overridePaths?: any[]) {
  const paths = (overridePaths ?? JSON.parse(readFileSync(`src/generated/${profile}.trace-paths.json`, 'utf8'))).map((p: any) => fanoutTracePath.parse(p))
  const input = JSON.parse(readFileSync(`scripts/fixtures/${profile}.input.json`, 'utf8')).input
  const expected = JSON.parse(readFileSync('src/generated/native.trace-paths.json', 'utf8')).map((p: any) => p.connection).sort()
  assert.deepEqual(paths.map((p: any) => p.connection).sort(), expected, 'All 313 saved connections must occur exactly once')
  const traces = paths.map((p: any) => {
    const pin = AM3352_PINS.find(pin => p.connection === `U1.${pin.ballName}`)!
    assert.ok(pin)
    const first = p.route[0]
    assert.ok(first.route_type === 'wire' && first.layer === 'top' && Math.hypot(first.x - pin.x, first.y - pin.y) < 1e-6, `${p.connection}: source must remain on its unrotated ball`)
    const connection = input.connections.find((c: any) => c.pointsToConnect.some((q: any) => Math.hypot(q.x - pin.x, q.y - pin.y) < 1e-6))
    assert.ok(connection, `Missing source connection ${p.connection}`)
    return { type: 'pcb_trace', pcb_trace_id: `cache:${p.connection}`, connection_name: connection.name, route: p.route, pin }
  })
  // Eight local CAP_* capacitor connections have fixed copper, no fanout exit.
  // Reintroduce their original electrical tokens before auditing them together
  // with cached copper. Merely putting them in input.traces skips pairwise DRC.
  for (const t of input.traces) {
    if (input.connections.some((c: any) => c.name === t.connection_name)) continue
    const wires = t.route.filter((p: any) => p.route_type === 'wire')
    assert.ok(wires.length && t.connectsTo?.length, `Fixed trace ${t.connection_name} needs electrical identity`)
    input.connections.push({ name: t.connection_name, pointsToConnect: [{ ...wires[0], pointId: t.connectsTo[0] }, wires.at(-1)] })
  }
  const ddr = traces.filter((t: any) => t.pin.name.startsWith('DDR_'))
  assert.equal(ddr.length, 52)
  const bounds: FanoutBounds = JSON.parse(readFileSync('src/generated/profile-bounds.json', 'utf8'))[profile]
  assert.deepEqual(input.bounds, bounds, 'Fixture and public profile bounds must agree')
  assertTightDdrBounds(input, traces, bounds)
  const fixedLeft = Math.min(...input.obstacles.map((o: any) => o.center.x - o.width / 2))
  const side = profile.slice(4)
  for (const t of ddr) {
    const end = t.route.at(-1)
    assert.ok(end.route_type === 'wire')
    assert.ok(side === 'left' ? Math.abs(end.x - bounds.minX) < 1e-6 : Math.abs(end.y - (side === 'top' ? bounds.maxY : bounds.minY)) < 1e-6, `${t.pin.name}: incorrect ${side} exit`)
    for (const q of t.route) {
      if (q.route_type === 'wire') assert.ok(q.width >= .1016 - 1e-9, `${t.pin.name}: narrow DDR copper`)
      else assert.ok(q.via_diameter >= .4572 - 1e-9 && q.via_hole_diameter >= .254 - 1e-9, `${t.pin.name}: undersized DDR via`)
    }
    // All newly generated border tuning must use visible, manufacturable spans.
    for (let i = 1; i < t.route.length; i++) {
      const a = t.route[i - 1], b = t.route[i]
      if (a.route_type === 'wire' && b.route_type === 'wire' && side !== 'left' && a.x <= fixedLeft + 1e-6 && b.x <= fixedLeft + 1e-6) {
        const length = Math.hypot(a.x - b.x, a.y - b.y)
        assert.ok(length >= .1016 - 1e-6, `${t.pin.name}: sub-width tuning segment`)
      }
    }
  }
  const routeAngles = { saved: assertRouteAngles(traces), fixed: assertRouteAngles(input.traces) }
  const { drc, ddrDrc } = validateDdrCopper(input, traces, ddr)
  assert.ok(drc.valid && ddrDrc.valid, JSON.stringify({ drc, ddrDrc }))
  const audit = auditAM3352Ddr(paths, { layers: ['top', ...Array.from({ length: 8 }, (_, i) => `inner${i + 1}`), 'bottom'], planes: [{ layer: 'inner1', netName: 'GND' }, { layer: 'inner3', netName: 'VCC_DDR_1V5' }, { layer: 'inner5', netName: 'GND' }] })
  assert.ok(audit.localGeometryChecksPass, JSON.stringify(audit.failures))
  return { profile, routeAngles, bounds, widthMm: bounds.maxX - bounds.minX, heightMm: bounds.maxY - bounds.minY, paddingMm: { left: -bounds.minX - 7.5, right: bounds.maxX - 7.5, top: bounds.maxY - 7.5, bottom: -bounds.minY - 7.5 }, tightEnclosureVerified: true, enclosureMarginMm: .1016, enclosureGridMm: .01, savedPathCount: paths.length, fixedTraceCount: input.traces.length, ddrRelatedSignalCount: ddr.length, allCopperDrc: drc, ddrCopperDrc: ddrDrc, sourceOrientationPreserved: true, requestedDdrExitSide: side, ...audit }
}
if (import.meta.main) {
  for (const profile of DDR_PROFILES) {
    const report = validateDdrProfile(profile)
    writeFileSync(`src/generated/${profile}.metrics.json`, JSON.stringify(report, null, 2) + '\n')
    console.log(`${profile}: 313 saved + ${report.fixedTraceCount} fixed traces, complete copper DRC and 52 DDR exits pass; full host DDR compliance unverified`)
  }
}