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}`,
)