astra/am3352-sbc

A 4-layer AM3352 single-board computer with 128‑MiB DDR3, PMIC power regulation, USB‑C 5‑V PD input, dual USB host ports, HDMI output with ESD protection, microSD/UART/JTAG interfaces, addressable RGB indicators, and a PWM-driven buzzer.

Version
0.1.19
License
unset
Stars
0

scripts/audit-power.ts

import {ramPoint,isRamRef} from "../design/ram-placement";
import { readFile, writeFile } from "node:fs/promises";
import { createHash } from "node:crypto";
import escapes from "../design/power-escapes.json";
const raw = await readFile(process.argv.find(a=>a.endsWith(".circuit.json")) ?? "output/board-ddr.circuit.json", "utf8");
const json: any[] = JSON.parse(raw);
const source = new Map(
  json.filter((e) => e.type === "source_trace").map((e) => [e.name, e]),
);
const traces = json.filter((e) => e.type === "pcb_trace");
const vias = json.filter((e) => e.type === "pcb_via");
const near = (a: any, b: any) => Math.hypot(a.x - b.x, a.y - b.y) < 1e-6;
const missing = escapes.traces
  .filter((e) => {
    const t = source.get(e.name);
    const endpoint=(e as {directPlaneEndpoint?:{x:number;y:number}|null}).directPlaneEndpoint;
    const rawVia = escapes.vias.find((v) => v.name === e.via);
    if (!rawVia && !endpoint) return true;
    const ramVias = new Set(escapes.traces.filter(e=>isRamRef(e.from.split(" > ")[0].slice(1))).map(e=>e.via));
    const rawPosition=endpoint ?? rawVia!;
    const transformed=endpoint ? isRamRef(e.from.split(" > ")[0].slice(1)) : ramVias.has(rawVia!.name);
    let v = transformed ? ramPoint(rawPosition.x,rawPosition.y) : rawPosition;
    if(endpoint){
      const sc=json.find(s=>s.type==="source_component"&&s.name===e.from.split(" > ")[0].slice(1));
      const c=json.find(c=>c.type==="pcb_component"&&c.source_component_id===sc.source_component_id);
      const angle=c.rotation*Math.PI/180;
      v={x:(c.display_offset_x??c.center.x)+endpoint.x*Math.cos(angle)-endpoint.y*Math.sin(angle),y:(c.display_offset_y??c.center.y)+endpoint.x*Math.sin(angle)+endpoint.y*Math.cos(angle)};
    }
    return (
      !traces.some(
        (p) =>
          p.source_trace_id === t?.source_trace_id &&
          p.route.some(
            (r: any) =>
              r.route_type === "wire" && r.layer === e.layer && near(r, v),
          ),
      ) ||
      (!endpoint && !vias.some(
        (p) =>
          near(p, v) &&
          ["top", "inner1", "inner2", "bottom"].every((l) =>
            p.layers.includes(l),
          ),
      ))
    );
  })
  .map((e) => e.name);
const errors = json.filter((e) => e.type.endsWith("_error"));
const incomplete = errors.filter(
  (e) =>
    ["pcb_port_not_connected_error", "pcb_trace_missing_error"].includes(
      e.type,
    ) ||
    (e.type === "pcb_trace_error" &&
      e.message.includes("has dangling endpoint")),
);
const timingErrors=errors.filter(e=>e.type==="pcb_bus_length_skew_error");
const blockers = errors.filter((e) => !incomplete.includes(e) && !timingErrors.includes(e));
const nets = new Map(
  json
    .filter((e) => e.type === "source_net")
    .map((e) => [e.source_net_id, e.name]),
);
const pours = json
  .filter((e) => e.type === "pcb_copper_pour")
  .map((e) => ({ layer: e.layer, net: nets.get(e.source_net_id) }));
const board = json.find((e) => e.type === "pcb_board");
const report = {
  stage:
    "DDR and power/ground breakout checkpoint; timing and peripheral routing incomplete",
  circuitSha256: createHash("sha256").update(raw).digest("hex"),
  layerCount: board.num_layers,
  pours,
  requestedEscapes: 375,
  generatedEscapes: escapes.traces.length,
  renderedEscapes: escapes.traces.length - missing.length,
  distinctEscapeVias: escapes.vias.length,
  missingEscapes: missing,
  nativeClearanceAndPlacementErrors: blockers,
  ddrTimingErrors:timingErrors,
  directTopPlaneConnections:escapes.traces.filter(e=>e.directPlaneEndpoint).length,
  incompleteRoutingErrors: incomplete.length,
  otherRailDistributionComplete: false,
  ddrRoutingComplete: json.filter(e=>e.type==="pcb_trace" && json.some(s=>s.type==="source_trace" && s.source_trace_id===e.source_trace_id && /^DDR_/.test(s.name))).length===47,
  fabricationReady: false,
};
await writeFile(
  "output/power-checkpoint-audit.json",
  JSON.stringify(report, null, 2),
);
console.log(report);
if (
  missing.length ||
  escapes.traces.length !== 375 ||
  blockers.length ||
  board.num_layers !== 4 ||
  !pours.some((p) => p.layer === "top" && p.net === "DDR_1V5") ||
  !pours.some((p) => p.layer === "bottom" && p.net === "GND")
)
  process.exitCode = 1;