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/generate-ddr-directions.ts

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 } from './compact-fanout'
import { assertRouteAngles } from './check-route-angles'
import { AM3352_PINS } from '../src/pin-map'

// Same CPU orientation, independently routed north/south escape corridors.
// The eight neighbouring west auxiliary signals move with DDR so their copper
// does not cut across the new corridor. Existing outer pair meanders are
// removed and replaced with broad 45-degree tuning in the vertical corridor.
const candidatePath = process.argv.find(a => a.startsWith('--candidate='))?.slice(12)
const baseline = JSON.parse(readFileSync(candidatePath ?? 'dist/solver/ddr_left-output.json', 'utf8'))
const fixture = candidatePath ? { input: baseline.input, options: baseline.seed.options } : JSON.parse(readFileSync('scripts/fixtures/ddr_left.input.json', 'utf8'))
const pitch = .4064
const signal = (t: any) => AM3352_PINS.find(p => Math.hypot(p.x - t.route[0].x, p.y - t.route[0].y) < 1e-6)!
const planarLength = (t: any): number => t.route.slice(1).reduce((sum: number, p: any, i: number) => sum + Math.hypot(p.x - t.route[i].x, p.y - t.route[i].y), 0)
const fixedLeft = Math.min(
  ...fixture.input.obstacles.map((o: any) => o.center.x - o.width / 2),
  ...fixture.input.traces.flatMap((t: any) => t.route.map((p: any) => p.x - (p.route_type === 'via' ? p.via_diameter : p.width) / 2)),
)
for (const side of ['top', 'bottom'] as const) {
  const profile = `ddr_${side}`, dir = side === 'top' ? 1 : -1
  let traces = structuredClone(baseline.output.fanoutTraces)
  const west = traces.filter((t: any) => Math.abs(t.route.at(-1).x - fixture.input.bounds.minX) < 1e-6)
  assert.equal(west.length, 60, 'Expected 52 DDR-related and eight adjacent west signals')
  for (const t of west) {
    let cut = false
    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' || a.layer !== b.layer || a.x <= fixedLeft || b.x > fixedLeft) continue
      assert.ok(!t.route.slice(i).some((p: any) => p.route_type === 'via'), 'Only external wire tails can be replaced')
      const point = { ...b, x: fixedLeft, y: a.y + (b.y - a.y) * (fixedLeft - a.x) / (b.x - a.x) }
      t.route = [...t.route.slice(0, i), point]
      cut = true
      break
    }
    assert.ok(cut, `No external west wire crossing for ${t.connection_name}`)
  }
  const columns: Record<string, number> = {}
  for (const layer of ['top', 'inner2', 'inner4', 'inner6']) {
    const group = west.filter((t: any) => t.route.at(-1).layer === layer).sort((a: any, b: any) => dir * (a.route.at(-1).y - b.route.at(-1).y))
    assert.ok(group.length)
    // First column is outside the actual fixed copper envelope. Quantization
    // is outward to 0.01 mm; independent DRC checks the resulting geometry.
    const nearestWidth = group.at(-1).route.at(-1).width
    columns[layer] = Math.floor((fixedLeft - nearestWidth / 2 - .1016) * 100) / 100
    group.forEach((t: any, i: number) => {
      const end = t.route.at(-1)
      end.x = columns[layer]! - (group.length - 1 - i) * pitch
      // Replace the horizontal-to-vertical right angle with two 45° turns.
      // End the horizontal run early, then join its assigned vertical column.
      const chamfer = .2032
      end.x += chamfer
      t.route.push({ ...end, x: end.x - chamfer, y: end.y + dir * chamfer }, { ...end, x: end.x - chamfer, y: dir * 30 })
    })
  }
  // Put every other external terminal on a temporary common rectangle so the
  // final fitter can shorten its collinear tail using actual retained copper.
  for (const t of traces.filter((t: any) => !west.includes(t))) {
    const end = t.route.at(-1), b = fixture.input.bounds
    if (end.route_type !== 'wire') continue
    if (Math.abs(end.x - b.maxX) < 1e-6) end.x = 30
    else if (Math.abs(end.y - b.maxY) < 1e-6) end.y = 30
    else if (Math.abs(end.y - b.minY) < 1e-6) end.y = -30
  }
  for (const [a, b] of [['DDR_CK', 'DDR_CKn'], ['DDR_DQS0', 'DDR_DQSn0'], ['DDR_DQS1', 'DDR_DQSn1']]) {
    const pair = [traces.find((t: any) => signal(t).name === a), traces.find((t: any) => signal(t).name === b)].sort((a: any, b: any) => planarLength(a) - planarLength(b))
    const extra = planarLength(pair[1]) - planarLength(pair[0])
    if (extra <= .127) continue
    const t = pair[0]
    // A trapezoid uses 45° diagonals separated by >=0.1016 mm straight
    // sections. Use available horizontal run first, then the exit column.
    // Each cell adds 0.1683 mm, leaving <=0.0842 mm quantized pair skew.
    const count = Math.round(extra / (.4064 * (Math.SQRT2 - 1)))
    const original = structuredClone(t.route)
    const horizontalEnd = original.length - 3
    const horizontalLength = original[horizontalEnd - 1].x - original[horizontalEnd].x
    const capacity = Math.max(0, Math.min(count, Math.floor((horizontalLength - .4064) / .6096)))
    let accepted = false
    for (let horizontalCount = capacity; horizontalCount >= 0 && !accepted; horizontalCount--) {
      for (const waveSign of [1, -1]) {
        t.route = structuredClone(original)
        if (horizontalCount) {
          const a = original[horizontalEnd - 1], b = original[horizontalEnd]
          assert.ok(Math.abs(a.y - b.y) < 1e-6)
          const wave = [{ ...a, x: a.x - .2032 }]
          for (let j = 0; j < horizontalCount; j++) {
            const x = a.x - .2032 - j * .6096
            wave.push(
              { ...a, x: x - .2032, y: a.y + waveSign * .2032 },
              { ...a, x: x - .3048, y: a.y + waveSign * .2032 },
              { ...a, x: x - .5080 },
              { ...a, x: x - .6096 },
            )
          }
          t.route.splice(horizontalEnd, 0, ...wave)
        }
        const end = t.route.pop(), start = dir * t.route.at(-1).y + .4064
        if (count > horizontalCount) t.route.push({ ...end, y: dir * start })
        for (let j = 0; j < count - horizontalCount; j++) {
          const y = dir * (start + j * .6096)
          t.route.push(
            { ...end, x: end.x + .2032, y: y + dir * .2032 },
            { ...end, x: end.x + .2032, y: y + dir * .3048 },
            { ...end, y: y + dir * .5080 },
            { ...end, y: y + dir * .6096 },
          )
        }
        t.route.push(end)
        const candidateDdr = traces.filter((trace: any) => signal(trace).name.startsWith('DDR_'))
        const check = validateDdrCopper(fixture.input, traces, candidateDdr)
        if (check.drc.valid && check.ddrDrc.valid) {
          console.log(`${profile} ${signal(t).name}: ${horizontalCount} horizontal, ${count - horizontalCount} vertical tuning cells`)
          accepted = true
          break
        }
      }
    }
    assert.ok(accepted, `${profile}: no clear 45° tuning for ${signal(t).name}`)
  }
  const fitted = compactFanout(fixture.input, traces, { minX: -30, maxX: 30, minY: -30, maxY: 30 })
  traces = fitted.traces
  assertRouteAngles(traces)
  const bounds = fitted.bounds
  const input = { ...fixture.input, bounds }
  const ddr = traces.filter((t: any) => signal(t).name.startsWith('DDR_'))
  const { drc, ddrDrc } = validateDdrCopper(input, traces, ddr)
  const pairs = [['DDR_CK', 'DDR_CKn'], ['DDR_DQS0', 'DDR_DQSn0'], ['DDR_DQS1', 'DDR_DQSn1']].map(names => {
    const lengths = names.map(name => planarLength(traces.find((t: any) => signal(t).name === name)))
    const skew = Math.abs(lengths[0]! - lengths[1]!)
    assert.ok(skew <= .127 + 1e-6, `${names.join('/')}: pair skew ${skew}`)
    return { signals: names, planarSkewMm: skew, planarLengthsMm: lengths }
  })
  writeFileSync(`dist/solver/${profile}-drc.json`, JSON.stringify({ drc, ddrDrc, pairs, bounds, columns }, null, 2) + '\n')
  assert.ok(drc.valid && ddrDrc.valid, JSON.stringify({ profile, drc, ddrDrc }))
  assert.equal(traces.length, 313)
  assert.equal(ddr.length, 52)
  assert.ok(ddr.every((t: any) => Math.abs(t.route.at(-1).y - (side === 'top' ? bounds.maxY : bounds.minY)) < 1e-6))
  const paths = traces.map((t: any) => fanoutTracePath.parse({ connection: `U1.${signal(t).ballName}`, route: t.route }))
  writeFileSync(`src/generated/${profile}.trace-paths.json`, JSON.stringify(paths, null, 2) + '\n')
  writeFileSync(`scripts/fixtures/${profile}.input.json`, JSON.stringify({ ...fixture, options: { ...fixture.options, sharedBoundary: bounds }, compaction: { marginMm: fitted.marginMm, gridMm: fitted.gridMm, fixedLeft, columnPitchMm: pitch, columns }, input, requestedExits: Object.fromEntries(ddr.map((t: any) => [t.connection_name, side])) }, null, 2) + '\n')
  const paddingMm = { left: -bounds.minX - 7.5, right: bounds.maxX - 7.5, top: bounds.maxY - 7.5, bottom: -bounds.minY - 7.5 }
  writeFileSync(`dist/solver/${profile}-output.json`, JSON.stringify({ output: { ...baseline.output, fanoutTraces: traces }, drc, ddrDrc, pairs, bounds, columns, paddingMm }, null, 2) + '\n')
  const allBounds = JSON.parse(readFileSync('src/generated/profile-bounds.json', 'utf8'))
  allBounds[profile] = bounds
  writeFileSync('src/generated/profile-bounds.json', JSON.stringify(allBounds, null, 2) + '\n')
  console.log(`${profile}: ${JSON.stringify(bounds)}, padding ${JSON.stringify(paddingMm)}; full copper and DDR DRC pass`)
}