0hmX/am3352-ddr-track-a-local-dfm-wip-20261007

Experimental RJ45-corrected board with isolated PG indication. 1005/1005 source joins pass; 15 expanded-guard physical errors, sourcing and qualification gates remain. Not fabrication-ready.

Version
0.0.14
License
unset
Stars
0

simulations-next/ibis-models.tsx

import type { IbisModelDefinition } from "@tscircuit/props"

export type IbisCorner = "typical" | "minimum" | "maximum"
export type TiWriteDrive = "slow9mA" | "fast9mA" | "fast8mA" | "fast12mA"
export type DdrDirection = "write" | "read"

// TI sprm552c, industrial, 1.5 V, 5% supply. Selector membership is
// checked again by the converter. These settings must match the DDR PHY.
export const tiWriteModels = {
  slow9mA: ["Model_343", "Model_655", "Model_847"],
  fast9mA: ["Model_339", "Model_651", "Model_843"],
  fast8mA: ["Model_348", "Model_660", "Model_852"],
  fast12mA: ["Model_351", "Model_663", "Model_855"],
} as const

export const modelCornerSupplyV = { typical: 1.5, minimum: 1.425, maximum: 1.575 }
export const bytePins = [
  { cpu: ["M3", "P1", "P2"], ram: ["E3", "F3", "G3"] },
  { cpu: ["J1", "L1", "L2"], ram: ["D7", "C7", "B7"] },
] as const
const ramDataPins = [
  ["E3", "F7", "F2", "F8", "H3", "H8", "G2", "H7"],
  ["D7", "C3", "C8", "C2", "A7", "A2", "B8", "A3"],
]
const cpuDataPins = [
  ["M3", "M4", "N1", "N2", "N3", "N4", "P3", "P4"],
  ["J1", "K1", "K2", "K3", "K4", "L3", "L4", "M1"],
]

const references = (pins: string[], power: string, ground: string) =>
  Object.fromEntries(pins.map((pin) => [pin, {
    pullup: power, pulldown: ground, powerClamp: power, groundClamp: ground,
  }]))

/**
 * Bind one physical DQ and its unchanged DQS pair. If the board permutes DQ
 * balls within a byte, pass ramDataPin derived from that board's actual net.
 * A frozen E3/D7 assumption would simulate the wrong routed copper.
 */
export function createDdrIbisModels({
  byte, direction, corner, ramDataPin, cpuDataPin = bytePins[byte].cpu[0],
  drive = "slow9mA", ramOdt = "ODT_60", ramReadDrive = "DATA_40",
  tiReadTermination = "full9mA",
}: {
  byte: 0 | 1
  direction: DdrDirection
  corner: IbisCorner
  ramDataPin: string
  cpuDataPin?: string
  drive?: TiWriteDrive
  ramOdt?: "ODT_20" | "ODT_30" | "ODT_40" | "ODT_60" | "ODT_120" | "ODT_off"
  ramReadDrive?: "DATA_34" | "DATA_40"
  tiReadTermination?: "full9mA" | "off"
}): { cpu: IbisModelDefinition; ram: IbisModelDefinition } {
  if (!ramDataPins[byte].includes(ramDataPin))
    throw new Error(`RAM ball ${ramDataPin} is not a DQ ball in byte ${byte}`)
  if (!cpuDataPins[byte].includes(cpuDataPin))
    throw new Error(`CPU ball ${cpuDataPin} is not a DQ ball in byte ${byte}`)
  const cpuPins = [cpuDataPin, ...bytePins[byte].cpu.slice(1)]
  const ramPins = [ramDataPin, ...bytePins[byte].ram.slice(1)]
  const cpuModels = direction === "write" ? tiWriteModels[drive]
    : tiReadTermination === "full9mA"
      ? ["Model_498", "Model_1002", "Model_1002"]
      : ["Model_490", "Model_994", "Model_994"]
  const ramModel = direction === "write" ? ramOdt : ramReadDrive
  const definition = (
    projectPath: string, component: string, pins: string[], models: readonly string[],
    power: string, ground: string,
  ): IbisModelDefinition => ({
    ibisModelAsset: {
      project_relative_path: projectPath, url: `./${projectPath}`, mimetype: "text/plain",
    },
    ibisComponentName: component,
    ibisPinNameToPortLabel: Object.fromEntries([...pins, power, ground].map(p => [p, p])),
    ibisPinNameToModelName: Object.fromEntries(pins.map((p, i) => [p, models[i]])),
    ibisPinNameToReferencePortLabels: references(pins, power, ground),
    modelValueCorner: corner,
    // The converter refuses TI's coupled package and nonzero supply-pin
    // package R/L/C. Do not claim these are included or replace them silently.
    includePackageParasitics: false,
  })
  return {
    cpu: definition("simulations-next/model-review/sprm552c-candidate-models.ibs", "am335x_zcz",
      cpuPins, cpuModels, "E5", "A1"),
    ram: definition("simulations-next/model-review/W631GG6MB-12.ibs", "W631GG6MB-12",
      ramPins, ramPins.map(() => ramModel), "A1", "B1"),
  }
}

// Winbond W631GG6MB-12 (DDR3-1600): Tables 18, 19, 54, 55, 57.
// These screening limits conservatively cover the maximum listed AC150
// setup/hold derating (75/50 ps), but do not establish that a measured slew
// pair belongs to a supported Table 55 cell. Keep the pipeline result
// illustrative until actual slew, tangent-line rules and tVAC are checked.
export function winbondWriteScreen(corner: IbisCorner) {
  const vrefV = modelCornerSupplyV[corner] / 2
  return {
    vrefV,
    thresholds: {
      vilAcV: vrefV - 0.15, vilDcV: vrefV - 0.1,
      vihDcV: vrefV + 0.1, vihAcV: vrefV + 0.15,
    },
    setupSeconds: 85e-12,
    holdSeconds: 95e-12,
    minimumStrobeDifferentialV: 0.3,
  }
}

export const modelingOmissions = [
  "Vendor package parasitics excluded: coupled TI package and nonzero reference/supply package impedance unsupported by this converter.",
  "Static IBIS ODT/drive selections assume matching actual PHY, MR1/MR2 and ODT control during a burst.",
  "Minimum/maximum IBIS tables are vendor correlated corners; all corner combinations and VREF noise are not covered.",
  "Write DQS is launched half a UI after DQ; read DQS is edge aligned and needs an explicit receiver sampling shift.",
  "TI publishes rule-based DDR3 PCB timing limits; its public datasheet does not provide a complete read receiver AC setup/hold mask.",
]