astra/am3352-sbc
AM3352 single-board computer with DDR3, PMIC/USB‑C 5 V power, microSD, dual USB host, HDMI video, RGB indicators, and a PWM-driven buzzer.
- Version
- 0.1.18
- License
- unset
- Stars
- 0
scripts/audit-ddr.ts
import { readFile, writeFile } from "node:fs/promises";
import { createHash } from "node:crypto";
import type { SimpleRouteJson, Trace } from "@tscircuit/bus-lanes-solver";
import { sharedNets } from "../design/ddr-netlist";
import { DDR_RULES, ddrPairs, ddrTraceName } from "../design/ddr-rules";
import { auditDdrGeometry } from "../design/ddr-compliance";
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 expected=new Set(sharedNets.map(ddrTraceName));
const source=json.filter(e=>e.type==="source_trace"&&expected.has(e.name));
const ids=new Set(source.map(s=>s.source_trace_id));
const names=Object.fromEntries(source.map(s=>[s.source_trace_id,s.name]));
const ports=new Map(json.filter(e=>e.type==="pcb_port").map(e=>[e.source_port_id,e]));
const traces=json.filter(e=>e.type==="pcb_trace"&&ids.has(e.source_trace_id)).map(t=>({...t,connection_name:t.source_trace_id})) as Trace[];
const buses=json.filter(e=>e.type==="source_bus"&&e.source_trace_ids.every((id:string)=>ids.has(id)));
const board=json.find(e=>e.type==="pcb_board");
// The native source buses are independently checked against the required groups.
// Signal-layer conformance is measured from copper, because source_bus does not
// serialize pcbAllowedLayers. The router-input test separately checks that field.
const input:SimpleRouteJson={
layerCount:board.num_layers, allowedLayers:[...DDR_RULES.signalLayers],minTraceWidth:.1,
bounds:{minX:-50,maxX:50,minY:-40,maxY:40},obstacles:[],
connections:source.map(s=>({name:s.source_trace_id,source_trace_id:s.source_trace_id,pointsToConnect:s.connected_source_port_ids.map((id:string)=>{
const p=ports.get(id);if(!p)throw Error(`Missing PCB terminal: ${s.name}`);return {x:p.x,y:p.y,layer:p.layers[0],pcb_port_id:p.pcb_port_id};
})})),
buses:buses.map(b=>({busId:b.name,name:b.name,connectionNames:b.source_trace_ids,maxLengthSkew:b.max_length_skew,allowedLayers:[...DDR_RULES.signalLayers]})),
differentialPairs:ddrPairs.flatMap(pair=>{
const bus=buses.find(b=>b.name===pair.name);
return bus?[{connectionNames:bus.source_trace_ids as [string,string],lengthTolerance:bus.max_length_skew}]:[];
}),
};
const geometry=auditDdrGeometry(input,traces,names);
const errors=json.filter(e=>e.type.endsWith("_error"));
const sourcePorts=new Set(source.flatMap(s=>s.connected_source_port_ids));
const pcbPorts=new Set(json.filter(e=>e.type==="pcb_port"&&sourcePorts.has(e.source_port_id)).map(e=>e.pcb_port_id));
const connectivityErrors=errors.filter(e=>(e.type==="pcb_port_not_connected_error"&&e.pcb_port_ids?.some((p:string)=>pcbPorts.has(p)))||(e.type==="pcb_trace_missing_error"&&ids.has(e.source_trace_id)));
const copperErrors=errors.filter(e=>!["pcb_port_not_connected_error","pcb_trace_missing_error","pcb_bus_length_skew_error"].includes(e.type)&&!(e.type==="pcb_trace_error"&&e.message.includes("has dangling endpoint")));
const nativeTimingErrors=errors.filter(e=>e.type==="pcb_bus_length_skew_error");
const configuredSolverVersion=JSON.parse(await readFile("node_modules/@tscircuit/bus-lanes-solver/package.json","utf8")).version;
const report={
circuitSha256:createHash("sha256").update(raw).digest("hex"), reference:DDR_RULES.source,revision:DDR_RULES.revision,
router:"bus_lanes",configuredSolverVersion,copperProvenance:"Precomputed DDR copper; audit does not reroute it",routed:traces.length,expected:47,ddrEndpointCount:pcbPorts.size,
geometricTimingPassed:geometry.pass, nativeCopperErrors:copperErrors,ddrConnectivityErrors:connectivityErrors,nativeTimingErrors,
...geometry,
// This pass is only the explicitly measured geometric checkpoint.
pass:geometry.pass&&input.layerCount===4&&connectivityErrors.length===0&&copperErrors.length===0&&nativeTimingErrors.length===0,
signoffBlockers:[
"JLC04161H-3313 stackup selected and calculated; existing DDR copper still needs migration to the calculated widths/gaps and manufacturer coupon confirmation",
"Top DDR_1V5 has bridged islands; electrical connectivity does not prove continuous high-frequency reference planes",
"Via/package flight-time correction and reference return-current stitching are not signed off",
"Unterminated single-x16 CA/CK topology needs SI analysis (TI Figure 7-48 and section 7.7.2.3.3.9)",
"TI spacing exceptions, pair coupling, decoupling and complete supply distribution are not signed off",
],fabricationReady:false,
};
await writeFile("output/ddr-audit.json",JSON.stringify(report,null,2)+"\n");
console.log(JSON.stringify({...report,measurements:undefined,nativeTimingErrors:nativeTimingErrors.map(e=>({type:e.type,message:e.message}))},null,2));
// Absolute length failures are always fatal, not an optional --strict check.
if(!report.pass||process.argv.includes("--strict")&&report.signoffBlockers.length)process.exitCode=1;