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
scripts/check-fixed-envelope.ts
import { readFileSync, mkdirSync, writeFileSync } from 'node:fs'
import assert from 'node:assert/strict'
const profile=process.argv[2]??'native'
const clearanceMm=Number(process.argv[3]??.1016)
assert(Number.isFinite(clearanceMm)&&clearanceMm>=0)
const json:any[]=JSON.parse(readFileSync(`src/generated/${profile}.circuit.json`,'utf8'))
const sourceNames=new Map(json.filter(e=>e.type==='source_component').map(e=>[e.source_component_id,e.name]))
const componentNames=new Map(json.filter(e=>e.type==='pcb_component').map(e=>[e.pcb_component_id,sourceNames.get(e.source_component_id)]))
const traceNames=new Map(json.filter(e=>e.type==='source_trace').map(e=>[e.source_trace_id,e.name??'']))
type Extent={minX:number;maxX:number;minY:number;maxY:number;witness:string}
const extents:Extent[]=[]
const add=(x:number,y:number,rx:number,ry:number,witness:string)=>extents.push({minX:x-rx,maxX:x+rx,minY:y-ry,maxY:y+ry,witness})
let padCount=0,viaCount=0,traceCount=0
for(const e of json){
if(e.type==='pcb_smtpad'&&(componentNames.get(e.pcb_component_id)==='U1'||/^C\d+$/.test(componentNames.get(e.pcb_component_id)??''))){
const angle=(e.ccw_rotation??0)*Math.PI/180
const rx=e.shape==='circle'?e.radius:(Math.abs(Math.cos(angle))*e.width+Math.abs(Math.sin(angle))*e.height)/2
const ry=e.shape==='circle'?e.radius:(Math.abs(Math.sin(angle))*e.width+Math.abs(Math.cos(angle))*e.height)/2
assert(Number.isFinite(rx)&&Number.isFinite(ry),`Unsupported real pad geometry ${e.shape}`)
add(e.x,e.y,rx,ry,`${componentNames.get(e.pcb_component_id)} pad ${e.port_hints?.join('/')} at (${e.x},${e.y})`);padCount++
}
// Standalone supply/decoupling vias have no saved-path pcb_trace_id.
if(e.type==='pcb_via'&&!e.pcb_trace_id){add(e.x,e.y,e.outer_diameter/2,e.outer_diameter/2,`fixed via at (${e.x},${e.y}), diameter ${e.outer_diameter}`);viaCount++}
if(e.type==='pcb_trace'&&/^(C\d+_(POWER|GROUND|LDO)|FIXED_DROP_)/.test(traceNames.get(e.source_trace_id)??'')){
traceCount++
for(const p of e.route)if(p.route_type==='wire')add(p.x,p.y,p.width/2,p.width/2,`fixed trace ${traceNames.get(e.source_trace_id)}`)
}
}
assert.equal(padCount,488,'Expected 324 BGA pads and 164 capacitor pads')
assert.equal(traceCount,173)
const copperBounds={minX:Math.min(...extents.map(e=>e.minX)),maxX:Math.max(...extents.map(e=>e.maxX)),minY:Math.min(...extents.map(e=>e.minY)),maxY:Math.max(...extents.map(e=>e.maxY))}
const reservedBounds={minX:Math.min(-7.5,copperBounds.minX-clearanceMm),maxX:Math.max(7.5,copperBounds.maxX+clearanceMm),minY:Math.min(-7.5,copperBounds.minY-clearanceMm),maxY:Math.max(7.5,copperBounds.maxY+clearanceMm)}
const witnesses=Object.fromEntries((['minX','maxX','minY','maxY'] as const).map(key=>[key,extents.filter(e=>Math.abs(e[key]-copperBounds[key])<1e-9).map(e=>e.witness)]))
const report={profile,padCount,viaCount,traceCount,copperBounds,reservationClearanceMm:clearanceMm,reservedBounds,paddingFrom15MmBody:{left:-7.5-reservedBounds.minX,right:reservedBounds.maxX-7.5,bottom:-7.5-reservedBounds.minY,top:reservedBounds.maxY-7.5},witnesses,scope:'Lower bound from fixed copper only, including circular radii and track half-widths. Excludes saved signal routing, preview receiving pads, board-wide pours, soldermask and mechanical courtyard. Route-terminal center bounds are separate; endpoint round caps extend by half the trace width.'}
mkdirSync('dist/envelopes',{recursive:true})
writeFileSync(`dist/envelopes/${profile}.fixed.json`,JSON.stringify(report,null,2)+'\n')
console.log(JSON.stringify({profile,copperBounds,reservedBounds,padding:report.paddingFrom15MmBody},null,2))