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-ddr-host.tsx

import { readFileSync, writeFileSync, mkdirSync } from 'node:fs'
import { resolve } from 'node:path'
import { routeHostSignals } from './check-ddr-host-router'
import { Fragment } from 'react'
import { RootCircuit } from '@tscircuit/core'
import { strict as assert } from 'node:assert'
import { getAM3352TracePaths } from '../src/saved-paths'
import { adaptDdrMemoryPaths } from '../src/ddr-memory'
import { getAM3352PowerPlanes } from '../src/profiles'
import { AM3352Module } from '../src/index'
import { DdrMemoryAdapter, type DdrMemoryPin } from '../src/ddr-memory'
const base=process.argv[2]
if(!base)throw new Error('Usage: bun scripts/check-ddr-host.tsx /path/to/memory-fanout')
const pins=JSON.parse(readFileSync(resolve(base,'src/pin-map.json'),'utf8')) as Array<DdrMemoryPin & {x:number;y:number;radius:number}>
const paths=JSON.parse(readFileSync(resolve(base,'src/generated/outward_north.trace-paths.json'),'utf8'))
const cpuX=10
const cpuExit=getAM3352TracePaths('ddr_left').find(p=>p.connection==='U1.M3')!.route.at(-1)!
const ramExit=adaptDdrMemoryPaths(paths).find(p=>p.connection==='U1.ball_B3')!.route.at(-1)!
// Keep a real 7.5 x 2 mm routing gap as the CPU's asymmetric boundary changes.
const ramX=cpuX+Number(cpuExit.x)-7.5+Number(ramExit.x)
const ramY=Number(cpuExit.y)-2+Number(ramExit.y)
const runDir=resolve('dist/ddr-host',process.argv.includes('--direct')?'direct':'capacity')
const c=new RootCircuit()
mkdirSync(runDir,{recursive:true})
c.on('autorouting:start', event=>{if(event.simpleRouteJson)writeFileSync(resolve(runDir,`router-input-${event.phaseName??'default'}.json`),JSON.stringify(event.simpleRouteJson,null,2))})
c.add(<board width={70} height={45} layers={10} schematicDisabled autorouter={{algorithmFn:routeHostSignals}} minPadEdgeToPadEdgeClearance={.0762} minTraceToPadEdgeClearance={.0762} minViaEdgeToPadEdgeClearance={.0762} allowBlindAndBuriedVias={false}
 minTraceWidth={.1016} defaultTraceWidth={.12} minViaPadDiameter={.4572} minViaHoleDiameter={.254}>
 {[...new Set(getAM3352PowerPlanes('ddr_left').map(p=>p.netName))].map(name=><Fragment key={name}><net name={name}/></Fragment>)}
 {getAM3352PowerPlanes('ddr_left').map(p=><Fragment key={`${p.netName}:${p.layer}`}><copperpour layer={p.layer} connectsTo={`net.${p.netName}`} clearance={.1016} boardEdgeMargin={.2}/></Fragment>)}
 <AM3352Module name="CPU" layoutProfile="ddr_left" pcbX={cpuX}/>
 <DdrMemoryAdapter name="RAM0" paths={paths} pins={pins} pcbX={ramX} pcbY={ramY} pcbRotation={180}
  connections={{DQ0:'.CPU .U1 > .DDR_D0'}}
  footprint={<footprint>{pins.map(p=><Fragment key={p.ball}><smtpad portHints={[`pin${p.pin}`]} pcbX={p.x} pcbY={p.y} radius={p.radius} shape="circle"/></Fragment>)}</footprint>}/>
</board>)
await c.renderUntilSettled()
const json=c.getCircuitJson()
mkdirSync(runDir,{recursive:true})
writeFileSync(resolve(runDir,'circuit.json'),JSON.stringify(json,null,2)+'\n')
const routingFailures=json.filter(e=>e.type==='pcb_autorouting_error')
if(routingFailures.length)writeFileSync(resolve(runDir,'failure.json'),JSON.stringify({mode:'capacity-autorouter',errors:routingFailures},null,2)+'\n')
console.log(JSON.stringify({errors:json.filter(e=>e.type.endsWith('_error')).map(e=>({type:e.type,message:"message" in e?e.message:undefined})),traces:json.filter(e=>e.type==='pcb_trace').length},null,2))

assert.deepEqual(json.filter(e=>e.type==='pcb_autorouting_error'),[], 'Host routing failed')
const target=json.find(e=>e.type==='source_trace'&&e.name==='RAM0_DQ0_external')!
assert(target?.type==='source_trace')
assert(json.some(e=>e.type==='pcb_trace'&&e.source_trace_id===target.source_trace_id),'Target DDR host trace missing')

const rendered=json.filter(e=>e.type==='pcb_trace')
const expected=[...getAM3352TracePaths('ddr_left').map(p=>({...p,route:p.route.map(q=>({...q,x:Number(q.x)+cpuX,y:Number(q.y)}))})),...adaptDdrMemoryPaths(paths).map(p=>({...p,route:p.route.map(q=>({...q,x:ramX-Number(q.x),y:ramY-Number(q.y)}))}))]
for(const path of expected) {
 assert(rendered.some(t=>t.route.length===path.route.length&&t.route.every((q:any,i:number)=>{
  const p:any=path.route[i]!
  return q.route_type===p.route_type&&Math.hypot(q.x-p.x,q.y-p.y)<1e-7&&(q.route_type==='wire'?q.layer===p.layer&&Math.abs(q.width-Number(p.width))<1e-8:q.from_layer===p.from_layer&&q.to_layer===p.to_layer)
 })),`Cached geometry changed or disappeared: ${path.connection}`)
}
console.log(`Preserved all ${expected.length} saved CPU and RAM paths`)

const cpuD0=expected.find(p=>p.connection==='U1.M3')!
const ramD0=expected.find(p=>p.connection==='U1.ball_B3')!
const host=rendered.find(t=>t.source_trace_id===target.source_trace_id)!
const near=(a:any,b:any)=>Math.hypot(a.x-b.x,a.y-b.y)<1e-7
const hostRoute=host.route.map(p=>{assert(p.route_type==='wire');return p})
const hostEnds=[hostRoute[0]!,hostRoute.at(-1)!]
for(const escape of [cpuD0,ramD0]) {
 const first=escape.route[0]!,last=escape.route.at(-1)!
 assert(json.some(e=>e.type==='pcb_smtpad'&&near(e,first)),`Source pad missing for ${escape.connection}`)
 assert(hostEnds.some(end=>near(end,last)&&end.route_type==='wire'&&last.route_type==='wire'&&end.layer===last.layer),`Host does not touch saved exit ${escape.connection}`)
}
assert(Math.hypot(hostEnds[0]!.x-hostEnds[1]!.x,hostEnds[0]!.y-hostEnds[1]!.y)>1,'Coincident host contacts are not integration proof')
assert.deepEqual(json.filter(e=>e.type.endsWith('_error')),[])
// Independently check host inner-layer copper against all foreign tracks and via barrels.
const distance=(a:any,b:any)=>Math.hypot(a.x-b.x,a.y-b.y)
const pointSegment=(p:any,a:any,b:any)=>{const dx=b.x-a.x,dy=b.y-a.y,t=Math.max(0,Math.min(1,((p.x-a.x)*dx+(p.y-a.y)*dy)/(dx*dx+dy*dy)||0));return distance(p,{x:a.x+t*dx,y:a.y+t*dy})}
const segmentDistance=(a:any,b:any,c:any,d:any)=>{const cross=(p:any,q:any,r:any)=>(q.x-p.x)*(r.y-p.y)-(q.y-p.y)*(r.x-p.x);return cross(a,b,c)*cross(a,b,d)<0&&cross(c,d,a)*cross(c,d,b)<0?0:Math.min(pointSegment(a,c,d),pointSegment(b,c,d),pointSegment(c,a,b),pointSegment(d,a,b))}
const sameNetTraceIds=new Set([host.pcb_trace_id])
for(const path of [cpuD0,ramD0])for(const trace of rendered)if(trace.route.length===path.route.length&&trace.route.every((p:any,i)=>near(p,path.route[i])))sameNetTraceIds.add(trace.pcb_trace_id)
let hostMinClearanceMm=Infinity
for(let i=1;i<hostRoute.length;i++){
 const a=hostRoute[i-1]!,b=hostRoute[i]!
 for(const trace of rendered){
  if(sameNetTraceIds.has(trace.pcb_trace_id))continue
  for(let k=1;k<trace.route.length;k++){
   const c=trace.route[k-1]!,d=trace.route[k]!
   if(c.route_type!=='wire'||d.route_type!=='wire'||c.layer!==a.layer||d.layer!==a.layer)continue
   hostMinClearanceMm=Math.min(hostMinClearanceMm,segmentDistance(a,b,c,d)-(a.width+Math.max(c.width,d.width))/2)
  }
 }
 for(const via of json.filter(e=>e.type==='pcb_via')){
  if(sameNetTraceIds.has(via.pcb_trace_id??'')||!via.layers.includes(a.layer))continue
  hostMinClearanceMm=Math.min(hostMinClearanceMm,pointSegment(via,a,b)-(a.width+via.outer_diameter)/2)
 }
}
assert(hostMinClearanceMm>=.1016-1e-8,`Host trace clearance ${hostMinClearanceMm} below 4 mil`)
const lengthMm=hostRoute.slice(1).reduce((sum,p,i)=>sum+Math.hypot(p.x-hostRoute[i]!.x,p.y-hostRoute[i]!.y),0)
writeFileSync(resolve(runDir,'report.json'),JSON.stringify({mode:process.argv.includes('--direct')?'explicit-45-degree-host-continuation':'capacity-autorouter',cachedPathsPreserved:expected.length,hostRouteLengthMm:lengthMm,hostMinClearanceMm,source:'CPU.M3 DDR_D0',destination:'RAM0.B3 DQ0',hostExitLayer:'inner2',emittedErrors:0,scope:'One physical DQ connection; does not validate complete DDR bus timing, impedance or default autorouter reliability.'},null,2)+'\n')
console.log(`CPU M3 -> saved exit -> ${lengthMm.toFixed(6)} mm host copper -> saved exit -> RAM B3: continuous`)