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

src/ddr.ts

import { AM3352_BALL_MAP } from './pin-map'
import { AM3352_POWER_PLANES } from './layout'

/** TI SPRS717L §§7.7.2.3.3–7.7.2.3.6, Tables 7-62, 7-68, 7-69.
 * Values apply to the complete PCB interface, not just this cached escape.
 * RESETn, VREF and VTP are intentionally outside the timed net classes.
 */
export const AM3352_DDR3_REQUIREMENTS = {
  source: 'https://www.ti.com/lit/ds/sprs717l/sprs717l.pdf',
  minimumTraceWidthMm: 0.1016,
  referenceEscapeViaPadMm: [0.4572, 0.508],
  referenceEscapeViaHoleMm: 0.254,
  singleEndedImpedanceOhms: [50, 75],
  impedanceToleranceOhms: 5,
  differentialImpedanceMultiplier: 2,
  dqWithinByteSkewMm: 0.635,
  dqsToDqSkewMm: 0.635,
  dqsPairSkewMm: 0.127,
  ckStubPairSkewMm: 0.127,
  clockAddressA1A2MaxMm: 63.5,
  clockAddressA1A2SkewMm: 0.635,
  nominalSameClassSpacingWidths: 3,
  nominalOtherClassSpacingWidths: 4,
  reducedSpacingMaximumLengthMm: 31.75,
  requiredReferenceNets: ['GND', 'VCC_DDR_1V5'],
  requiredReferencePlaneCuts: 0,
  interveningLayersToReference: 0,
  interfaceWidthBits: 16,
  supportedDeviceArrangements: ['one x16', 'two x8'],
  targetMemoryPart: 'MT41K512M8DA-107 IT:P',
  targetMemoryCount: 2,
  maximumControllerClockMHz: 400,
  selectedClockMHz: null,
} as const

export type DdrNetClass = 'CK' | 'ADDR_CTRL' | 'DQ0' | 'DQ1' | 'DQS0' | 'DQS1'
export function ddrNetClass(name: string): DdrNetClass | undefined {
  if (/^DDR_CKn?$/.test(name)) return 'CK'
  if (/^DDR_DQSn?0$/.test(name)) return 'DQS0'
  if (/^DDR_DQSn?1$/.test(name)) return 'DQS1'
  if (/^DDR_DQM[01]$/.test(name)) return name.endsWith('0') ? 'DQ0' : 'DQ1'
  const data = /^DDR_D(\d+)$/.exec(name)
  if (data && Number(data[1]) < 16) return Number(data[1]) < 8 ? 'DQ0' : 'DQ1'
  if (/^DDR_(A\d+|BA[012]|CSn0|CASn|RASn|WEn|CKE|ODT)$/.test(name)) return 'ADDR_CTRL'
}
export interface DdrRoutePoint {
  route_type: string; x: number; y: number; layer?: string; width?: number
  from_layer?: string; to_layer?: string; via_diameter?: number; via_hole_diameter?: number
}
export interface DdrPath { connection: string; route: DdrRoutePoint[] }
export interface DdrStackup {
  layers: readonly string[]
  planes: readonly { layer: string; netName: string }[]
  /** A declaration alone does not verify rendered copper continuity. */
  continuousReferenceLayers?: readonly string[]
}
export const AM3352_NATIVE_DDR_STACKUP: DdrStackup = {
  layers: ['top', 'inner1', 'inner2', 'inner3', 'inner4', 'bottom'],
  planes: AM3352_POWER_PLANES,
}
const round = (n: number) => Math.round(n * 1e6) / 1e6
export function auditAM3352Ddr(paths: readonly DdrPath[], stackup: DdrStackup = AM3352_NATIVE_DDR_STACKUP) {
  const failures: { code: string; signal?: string; message: string }[] = []
  const add = (code: string, message: string, signal?: string) => failures.push({ code, signal, message })
  const expected = Object.entries(AM3352_BALL_MAP).filter(([, name]) => ddrNetClass(name))
  const signals = expected.flatMap(([ball, signal]) => {
    const matches = paths.filter(p => p.connection === `U1.${ball}`)
    if (matches.length !== 1) { add('coverage', `Expected one saved path; found ${matches.length}`, signal); return [] }
    const p = matches[0]!
    const wires = p.route.filter(q => q.route_type === 'wire')
    if (wires.length < 2) add('empty-route', 'Route has fewer than two wire points', signal)
    if (p.route.some(q => !Number.isFinite(q.x) || !Number.isFinite(q.y))) add('invalid-geometry', 'Nonfinite route coordinate', signal)
    if (wires.some(q => !Number.isFinite(q.width) || q.width! < AM3352_DDR3_REQUIREMENTS.minimumTraceWidthMm - 1e-9)) add('trace-width', 'Timed DDR traces require at least 0.1016 mm width', signal)
    const lengthByLayerMm: Record<string, number> = {}
    for (let i = 1; i < p.route.length; i++) {
      const a = p.route[i - 1]!, b = p.route[i]!
      if (a.route_type === 'via' || b.route_type === 'via') {
        if (Math.hypot(b.x - a.x, b.y - a.y) > 1e-6) add('via-gap', 'Via transition is not colocated with its adjacent route point', signal)
      }
      if (a.route_type === 'wire' && b.route_type === 'wire') {
        if (a.layer !== b.layer) add('missing-via', 'Wire layer changes without an intervening via', signal)
        else if (a.layer) lengthByLayerMm[a.layer] = (lengthByLayerMm[a.layer] ?? 0) + Math.hypot(b.x - a.x, b.y - a.y)
      }
    }
    const layers = [...new Set(wires.map(q => q.layer).filter((q): q is string => !!q))]
    for (const layer of layers) {
      const index = stackup.layers.indexOf(layer)
      if (index < 0) add('unknown-layer', `Unknown signal layer ${layer}`, signal)
      const adjacent = stackup.planes.filter(plane => Math.abs(stackup.layers.indexOf(plane.layer) - index) === 1 && ['GND', 'VCC_DDR_1V5'].includes(plane.netName))
      if (!adjacent.length) add('reference-adjacency', `${layer} has no adjacent GND or VDDS_DDR reference`, signal)
      if (stackup.planes.some(plane => plane.layer === layer && plane.netName === 'VCC_DDR_1V5')) add('ddr-plane-signal-sharing', 'Signal clearance cuts the required VDDS_DDR reference plane', signal)
    }
    const vias = p.route.filter(q => q.route_type === 'via').map(v => ({
      x: v.x, y: v.y, entryLayer: v.from_layer, exitLayer: v.to_layer,
      physicalSpan: ['top', 'bottom'], // native profile uses plated through holes
      padDiameterMm: v.via_diameter, holeDiameterMm: v.via_hole_diameter,
    }))
    if (vias.some(v => (v.padDiameterMm ?? 0) < 0.4572 - 1e-9 || (v.holeDiameterMm ?? 0) < 0.254 - 1e-9)) add('escape-via-dimensions', 'Via is smaller than TI Table 7-62 escape geometry; requires redesign or documented SI deviation', signal)
    return [{ signal, ball, netClass: ddrNetClass(signal)!, planarLengthMm: round(Object.values(lengthByLayerMm).reduce((a, b) => a + b, 0)), lengthByLayerMm: Object.fromEntries(Object.entries(lengthByLayerMm).map(([l, n]) => [l, round(n)])), layers, vias }]
  })
  const pairs = [['DDR_CK', 'DDR_CKn'], ['DDR_DQS0', 'DDR_DQSn0'], ['DDR_DQS1', 'DDR_DQSn1']].flatMap(([a, b]) => {
    const pa = signals.find(s => s.signal === a), pb = signals.find(s => s.signal === b)
    if (!pa || !pb) return []
    const skewMm = round(Math.abs(pa.planarLengthMm - pb.planarLengthMm))
    // Conservative fanout allocation; CK full-route constraints are segment-specific in Table 7-68.
    if (skewMm > 0.127 + 1e-6) add('local-pair-skew', `${a}/${b} local planar skew ${skewMm} mm exceeds 0.127 mm allocation`)
    return [{ signals: [a, b], localPlanarSkewMm: skewMm, allocationMm: 0.127 }]
  })
  const byteLanes = [0, 1].map(byte => {
    const members = signals.filter(s => s.netClass === `DQ${byte}` || s.netClass === `DQS${byte}`)
    const longest = Math.max(0, ...members.map(s => s.planarLengthMm))
    return { byte, localPlanarSpreadMm: round(longest - Math.min(...members.map(s => s.planarLengthMm))), signals: members.map(s => ({ signal: s.signal, localPlanarLengthMm: s.planarLengthMm, localEqualizationContributionMm: round(longest - s.planarLengthMm) })), fullRouteMatchingVerified: false }
  })
  for (const net of AM3352_DDR3_REQUIREMENTS.requiredReferenceNets) {
    const planes = stackup.planes.filter(p => p.netName === net)
    if (!planes.length) add('missing-reference-plane', `No ${net} reference plane`)
  }
  const unverified = [
    'Complete processor-to-two-x8-memory connectivity, placement and selected clock',
    'Full CK/ADDR_CTRL segment timing and each DQ/DQS byte lane timing including memory escapes',
    'Rendered continuous reference copper, DDR keepout and reference transition return paths',
    'Fabricator stackup, dielectric thickness and material, controlled impedance and differential coupling',
    'DDR spacing including reduced-spacing length budget and physical barrel copper clearance',
    'Via vertical flight time and stubs; reported lengths are planar only',
    'Host DDR termination, VREF/VTT, ZQ, memory decoupling and controller configuration',
  ]
  return { requirements: AM3352_DDR3_REQUIREMENTS, scope: 'AM3352 cached fanout only', compliant: false, localGeometryChecksPass: failures.length === 0, failures, unverified, pairs, byteLanes, signals }
}