0hmX/am62l-lpddr4-ti-pth-router

This code defines a hardware-oriented React/TypeScript setup for a PCB breakout circuit featuring an AM62L LPDDR4 interface, including detailed PCB component placement, routing algorithms, and validation scripts for DDR connections, via placement, and manufacturing constraints.

Version
1.0.1
License
unset
Stars
0

autorouting/am62l-middle-channel.ts

import type {
  AutorouterProgressEvent,
  GenericLocalAutorouter,
  SimpleRouteJson,
  SimplifiedPcbTrace,
} from "@tscircuit/core"
import {
  InProcessAutorouter,
  type InProcessAutorouterResult,
} from "./create-in-process-autorouter"

type Point = { x: number; y: number; layer: string; pointId?: string }
type Segment = { a: Point; b: Point; connectionName: string; width: number }

const EPS = 1e-6
const pointSegmentDistance = (
  point: { x: number; y: number },
  start: { x: number; y: number },
  end: { x: number; y: number },
) => {
  const dx = end.x - start.x
  const dy = end.y - start.y
  const lengthSquared = dx * dx + dy * dy
  const progress =
    lengthSquared <= EPS
      ? 0
      : Math.max(
          0,
          Math.min(
            1,
            ((point.x - start.x) * dx + (point.y - start.y) * dy) /
              lengthSquared,
          ),
        )
  return Math.hypot(
    point.x - start.x - progress * dx,
    point.y - start.y - progress * dy,
  )
}

const segmentDistance = (first: Segment, second: Segment) =>
  Math.min(
    pointSegmentDistance(first.a, second.a, second.b),
    pointSegmentDistance(first.b, second.a, second.b),
    pointSegmentDistance(second.a, first.a, first.b),
    pointSegmentDistance(second.b, first.a, first.b),
  )

const fail = (connectionName: string, message: string): never => {
  throw new Error(`[route_middle_channel] ${connectionName}: ${message}`)
}

/**
 * Route only the open channel between the two already-completed breakouts.
 * The coordinated breakout solver gives both ends of every signal the same
 * layer and ordered y coordinate, so this stage must not redo fanout work or
 * introduce vias: each connection is the exact horizontal channel segment.
 */
export const solveAm62lMiddleChannel = (
  input: SimpleRouteJson,
  reportProgress: (event: AutorouterProgressEvent) => void,
): InProcessAutorouterResult => {
  reportProgress({
    type: "progress",
    phase: "route_ordered_middle_channel",
    steps: 1,
    progress: 0.25,
  })

  const segments: Segment[] = input.connections.map((connection) => {
    if (connection.pointsToConnect.length !== 2) {
      fail(
        connection.name,
        `expected exactly two synchronized breakout endpoints, received ${connection.pointsToConnect.length}`,
      )
    }
    const [first, second] = connection.pointsToConnect as [Point, Point]
    if (first.layer !== second.layer) {
      fail(
        connection.name,
        `endpoint layers differ (${first.layer} -> ${second.layer})`,
      )
    }
    if (Math.abs(first.y - second.y) > EPS) {
      fail(
        connection.name,
        `coordinated endpoints are not horizontally aligned (${first.y} -> ${second.y})`,
      )
    }
    if (Math.abs(first.x - second.x) <= EPS) {
      fail(connection.name, "coordinated endpoints have no channel span")
    }
    return {
      a: first.x < second.x ? first : second,
      b: first.x < second.x ? second : first,
      connectionName: connection.name,
      width:
        connection.nominalTraceWidth ??
        connection.width ??
        input.nominalTraceWidth ??
        input.minTraceWidth,
    }
  })

  reportProgress({
    type: "progress",
    phase: "validate_ordered_middle_channel",
    steps: 2,
    progress: 0.75,
  })

  const clearance =
    input.minTraceToPadEdgeClearance ??
    fail("all", "SRJ is missing the minimum copper-edge clearance rule")
  for (let firstIndex = 0; firstIndex < segments.length; firstIndex++) {
    const first = segments[firstIndex]!
    for (
      let secondIndex = firstIndex + 1;
      secondIndex < segments.length;
      secondIndex++
    ) {
      const second = segments[secondIndex]!
      if (first.a.layer !== second.a.layer) continue
      const requiredCenterDistance =
        first.width / 2 + second.width / 2 + clearance
      if (segmentDistance(first, second) + EPS < requiredCenterDistance) {
        fail(
          `${first.connectionName}/${second.connectionName}`,
          `${first.a.layer} channel traces violate the SRJ-derived trace clearance`,
        )
      }
    }
  }

  const traces: SimplifiedPcbTrace[] = input.connections.map(
    (connection, connectionIndex) => {
      const segment = segments[connectionIndex]!
      const wire = (point: Point) => ({
        route_type: "wire" as const,
        x: point.x,
        y: point.y,
        width: segment.width,
        layer: point.layer,
      })
      return {
        type: "pcb_trace",
        pcb_trace_id: `am62l-middle:${connection.name}`,
        connection_name: connection.name,
        connectsTo: connection.pointsToConnect.flatMap((point) =>
          point.pointId ? [point.pointId] : [],
        ),
        route: [wire(segment.a), wire(segment.b)],
      }
    },
  )

  reportProgress({
    type: "progress",
    phase: "validate_ordered_middle_channel",
    steps: 3,
    progress: 1,
  })
  return {
    traces,
    outputSimpleRouteJson: {
      ...input,
      connections: input.connections.map((connection) => ({
        ...connection,
        pointsToConnect: connection.pointsToConnect.map((point) => ({
          ...point,
        })),
      })),
      buses: input.buses?.map((bus) => ({
        ...bus,
        connectionNames: [...bus.connectionNames],
      })),
      traces: [...(input.traces ?? []), ...traces],
    },
  }
}

export type Am62lMiddleChannelAlgorithmFn = (
  input: SimpleRouteJson,
) => Promise<GenericLocalAutorouter>

export const createAm62lMiddleChannelAlgorithm =
  (): Am62lMiddleChannelAlgorithmFn => async (input) =>
    new InProcessAutorouter(input, solveAm62lMiddleChannel)