0hmX/simplified-am62l-computer

This code defines components and scripts for physically representing and routing high-speed DDR memory signals, power lines, and ground planes on an 8-layer PCB with detailed pin assignments, copper pours, and precise via and trace layouts based on TI EVM reference data.

Version
1.0.16
License
unset
Stars
0

scripts/extract-ti-ddr-reference-routes.ts

// @ts-nocheck -- standalone Bun extractor; the circuit tsconfig intentionally omits host types.
import { basename, resolve } from "node:path"

type RecordMap = Record<string, string>
type GraphEdge = {
  to: string
  weight: number
  kind: "wire" | "via"
  widthMil?: number
}

const inputPath = process.argv[2]
const outputPath = process.argv[3]
if (!inputPath || !outputPath) {
  throw new Error(
    "Usage: bun scripts/extract-ti-ddr-reference-routes.ts <TI .alg> <output.ts>",
  )
}

const signalToTiNet: Record<string, string> = {
  ...Object.fromEntries(
    Array.from({ length: 16 }, (_, bit) => [
      `DDR0_DQ${bit}`,
      `LPDDR4_DQ${bit}`,
    ]),
  ),
  DDR0_DM0: "LPDDR4_DMI0",
  DDR0_DM1: "LPDDR4_DMI1",
  DDR0_DQS0: "LPDDR4_DQS0_P",
  DDR0_DQS0_n: "LPDDR4_DQS0_N",
  DDR0_DQS1: "LPDDR4_DQS1_P",
  DDR0_DQS1_n: "LPDDR4_DQS1_N",
  ...Object.fromEntries(
    Array.from({ length: 6 }, (_, bit) => [
      `DDR0_A${bit}`,
      `LPDDR4_CA${bit}`,
    ]),
  ),
  DDR0_CS0_n: "LPDDR4_CS0",
  DDR0_CKE0: "LPDDR4_CKE0",
  DDR0_CK0: "LPDDR4_CK_P",
  DDR0_CK0_n: "LPDDR4_CK_N",
  DDR0_RESET0_n: "LPDDR4_RESET_N|N29950535",
}

const relevantNets = new Set(
  Object.values(signalToTiNet).flatMap((netName) => netName.split("|")),
)
const records: RecordMap[] = []
let headers: string[] = []
for (const rawLine of (await Bun.file(inputPath).text()).split(/\r?\n/)) {
  if (rawLine.startsWith("A!")) {
    headers = rawLine.slice(2).split("!")
    continue
  }
  if (!rawLine.startsWith("S!") || headers.length === 0) continue
  const values = rawLine.slice(2).split("!")
  const record = Object.fromEntries(
    headers.map((header, index) => [header, values[index] ?? ""]),
  )
  records.push(record)
}

const milToMm = (value: number) => Number((value * 0.0254).toFixed(6))
const coordinateKey = (x: number, y: number, layer: string) =>
  `${x.toFixed(4)},${y.toFixed(4)},${layer}`
const parseNode = (key: string) => {
  const [x, y, layer] = key.split(",")
  return { x: Number(x), y: Number(y), layer }
}

const componentCenters = new Map<string, { x: number; y: number }>()
for (const record of records) {
  if (record.SYM_TYPE !== "PACKAGE") continue
  if (record.REFDES !== "U28" && record.REFDES !== "U29") continue
  componentCenters.set(record.REFDES, {
    x: Number(record.SYM_X),
    y: Number(record.SYM_Y),
  })
}
const socCenter = componentCenters.get("U28")
const memoryCenter = componentCenters.get("U29")
if (!socCenter || !memoryCenter) {
  throw new Error("Could not find U28/U29 component centers in TI ALG")
}

const pinByRefAndNet = new Map<string, { x: number; y: number; pin: string }>()
for (const record of records) {
  if (record.CLASS !== "PIN" || record.SUBCLASS !== "TOP") continue
  if (record.REFDES !== "U28" && record.REFDES !== "U29") continue
  if (!relevantNets.has(record.NET_NAME)) continue
  pinByRefAndNet.set(`${record.REFDES}:${record.NET_NAME}`, {
    x: Number(record.PIN_X),
    y: Number(record.PIN_Y),
    pin: record.PIN_NUMBER,
  })
}

const layerMap: Record<string, string> = {
  TOP: "top",
  "L3-SIG": "inner2",
  BOTTOM: "bottom",
}

function buildGraph(netNames: string[]) {
  const graph = new Map<string, GraphEdge[]>()
  const addEdge = (from: string, edge: GraphEdge) => {
    const edges = graph.get(from) ?? []
    edges.push(edge)
    graph.set(from, edges)
  }
  const viaLayersAt = new Map<string, Set<string>>()

  for (const record of records) {
    if (!netNames.includes(record.NET_NAME)) continue
    if (record.CLASS === "ETCH" && record.GRAPHIC_DATA_NAME === "LINE") {
      const layer = layerMap[record.SUBCLASS]
      if (!layer) continue
      const x1 = Number(record.GRAPHIC_DATA_1)
      const y1 = Number(record.GRAPHIC_DATA_2)
      const x2 = Number(record.GRAPHIC_DATA_3)
      const y2 = Number(record.GRAPHIC_DATA_4)
      const widthMil = Number(record.GRAPHIC_DATA_5)
      const first = coordinateKey(x1, y1, layer)
      const second = coordinateKey(x2, y2, layer)
      const length = Math.hypot(x2 - x1, y2 - y1)
      addEdge(first, { to: second, weight: length, kind: "wire", widthMil })
      addEdge(second, { to: first, weight: length, kind: "wire", widthMil })
    }
    if (record.CLASS === "VIA CLASS") {
      const mappedLayer = layerMap[record.SUBCLASS]
      if (!mappedLayer) continue
      const x = Number(record.VIA_X)
      const y = Number(record.VIA_Y)
      const position = `${x.toFixed(4)},${y.toFixed(4)}`
      const layers = viaLayersAt.get(position) ?? new Set<string>()
      layers.add(mappedLayer)
      viaLayersAt.set(position, layers)
    }
  }

  for (const [position, layerSet] of viaLayersAt) {
    const [x, y] = position.split(",").map(Number)
    const layers = [...layerSet]
    for (const from of layers) {
      for (const to of layers) {
        if (from === to) continue
        addEdge(coordinateKey(x, y, from), {
          to: coordinateKey(x, y, to),
          weight: 0.01,
          kind: "via",
        })
      }
    }
  }
  if (netNames.length > 1) {
    const bridgePins = records
      .filter(
        (record) =>
          record.REFDES === "R132" &&
          record.CLASS === "PIN" &&
          record.SUBCLASS === "TOP" &&
          netNames.includes(record.NET_NAME),
      )
      .map((record) => ({
        x: Number(record.PIN_X),
        y: Number(record.PIN_Y),
      }))
    if (bridgePins.length !== 2) {
      throw new Error("Could not identify the TI EVM RESET 0R bridge R132")
    }
    const first = coordinateKey(bridgePins[0].x, bridgePins[0].y, "top")
    const second = coordinateKey(bridgePins[1].x, bridgePins[1].y, "top")
    const length = Math.hypot(
      bridgePins[1].x - bridgePins[0].x,
      bridgePins[1].y - bridgePins[0].y,
    )
    addEdge(first, { to: second, weight: length, kind: "wire", widthMil: 5.2 })
    addEdge(second, { to: first, weight: length, kind: "wire", widthMil: 5.2 })
  }
  return graph
}

function shortestPath(
  graph: Map<string, GraphEdge[]>,
  start: string,
  target: string,
) {
  const distances = new Map<string, number>([[start, 0]])
  const previous = new Map<string, { node: string; edge: GraphEdge }>()
  const pending = new Set(graph.keys())
  pending.add(start)
  pending.add(target)

  while (pending.size > 0) {
    let current: string | undefined
    let best = Infinity
    for (const node of pending) {
      const distance = distances.get(node) ?? Infinity
      if (distance < best) {
        best = distance
        current = node
      }
    }
    if (!current || best === Infinity) break
    pending.delete(current)
    if (current === target) break
    for (const edge of graph.get(current) ?? []) {
      const candidate = best + edge.weight
      if (candidate >= (distances.get(edge.to) ?? Infinity)) continue
      distances.set(edge.to, candidate)
      previous.set(edge.to, { node: current, edge })
      pending.add(edge.to)
    }
  }
  if (!previous.has(target)) throw new Error(`No TI route from ${start} to ${target}`)

  const nodes = [target]
  const edges: GraphEdge[] = []
  while (nodes.at(-1) !== start) {
    const step = previous.get(nodes.at(-1)!)
    if (!step) throw new Error(`Broken path from ${start} to ${target}`)
    edges.push(step.edge)
    nodes.push(step.node)
  }
  return { nodes: nodes.reverse(), edges: edges.reverse() }
}

const routes: Record<string, unknown> = {}
for (const [signal, tiNet] of Object.entries(signalToTiNet)) {
  const tiNets = tiNet.split("|")
  const socPin = pinByRefAndNet.get(`U28:${tiNets[0]}`)
  const memoryPin = pinByRefAndNet.get(`U29:${tiNets.at(-1)}`)
  if (!socPin || !memoryPin) {
    throw new Error(`Missing U28/U29 endpoint for ${signal} (${tiNet})`)
  }
  const graph = buildGraph(tiNets)
  const start = coordinateKey(socPin.x, socPin.y, "top")
  const target = coordinateKey(memoryPin.x, memoryPin.y, "top")
  const path = shortestPath(graph, start, target)
  const route: Array<Record<string, unknown>> = []
  for (let index = 0; index < path.nodes.length; index++) {
    const node = parseNode(path.nodes[index])
    const previousEdge = path.edges[Math.max(0, index - 1)]
    const nextEdge = path.edges[index]
    const wireEdge = [previousEdge, nextEdge].find((edge) => edge?.kind === "wire")
    if (index > 0 && previousEdge?.kind === "via") {
      const previousNode = parseNode(path.nodes[index - 1])
      route.push({
        route_type: "via",
        x: milToMm(node.x - socCenter.x),
        y: milToMm(node.y - socCenter.y),
        from_layer: previousNode.layer,
        to_layer: node.layer,
        via_diameter: 0.4572,
        via_hole_diameter: 0.2032,
      })
    }
    if (wireEdge) {
      route.push({
        route_type: "wire",
        x: milToMm(node.x - socCenter.x),
        y: milToMm(node.y - socCenter.y),
        width: milToMm(wireEdge.widthMil ?? 3.2),
        layer: node.layer,
      })
    }
  }
  routes[signal] = {
    tiNet,
    socBall: socPin.pin,
    memoryBall: memoryPin.pin,
    route,
  }
}

const generated = `// Generated from TI TMDS62LEVM SPRCAL6D Altium ASCII board data.\n` +
  `// Coordinates are millimetres relative to U28 (AM62L) centre.\n` +
  `export const TI_AM62L_EVM_MEMORY_OFFSET = ${JSON.stringify({
    x: milToMm(memoryCenter.x - socCenter.x),
    y: milToMm(memoryCenter.y - socCenter.y),
  })} as const\n\n` +
  `export const TI_AM62L_EVM_DDR_ROUTES = ${JSON.stringify(routes, null, 2)} as const\n`

await Bun.write(resolve(outputPath), generated)
console.log(
  `Extracted ${Object.keys(routes).length} DDR routes from ${basename(inputPath)} to ${outputPath}`,
)