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/prepare-ddr-host-corridors.ts
/** Conservative planning abstraction: module copper is replaced by solid all-layer
* boxes. Original copper is still mandatory in final physical validation. */
import {readFileSync,writeFileSync} from 'node:fs'
import {resolve} from 'node:path'
import {getAM3352Bounds,getAM3352Layers} from '../src/profiles'
import {getAM3352TracePaths} from '../src/saved-paths'
import {adaptDdrMemoryPaths,DDR_MEMORY_SIX_LAYER_MAP} from '../src/ddr-memory'
export interface Bounds { minX:number;maxX:number;minY:number;maxY:number }
export function inflateBounds(b:Bounds,padding:number):Bounds {
if(!Number.isFinite(padding)||padding<0)throw Error('Invalid padding')
return {minX:b.minX-padding,maxX:b.maxX+padding,minY:b.minY-padding,maxY:b.maxY+padding}
}
export function containsBounds(outer:Bounds,inner:Bounds,tolerance=1e-7) {
return inner.minX>=outer.minX-tolerance&&inner.maxX<=outer.maxX+tolerance&&inner.minY>=outer.minY-tolerance&&inner.maxY<=outer.maxY+tolerance
}
export function outwardPort(point:{x:number;y:number;layer:string},bounds:Bounds,distance=.5) {
const sides=[{d:Math.abs(point.x-bounds.minX),dx:-1,dy:0},{d:Math.abs(point.x-bounds.maxX),dx:1,dy:0},{d:Math.abs(point.y-bounds.minY),dx:0,dy:-1},{d:Math.abs(point.y-bounds.maxY),dx:0,dy:1}].filter(s=>s.d<1e-6)
if(sides.length!==1)throw Error('Port must lie on exactly one boundary side')
if(!Number.isFinite(distance)||distance<=0)throw Error('Invalid lead distance')
return {...point,x:point.x+sides[0]!.dx*distance,y:point.y+sides[0]!.dy*distance}
}
const pointBox=(p:any,r:number):Bounds=>({minX:p.x-r,maxX:p.x+r,minY:p.y-r,maxY:p.y+r})
export function prepareDdrHostCorridors(input:any,snapshot:any,rendered:any[]) {
if(snapshot.hostProfiles)throw Error('AM3352 RAM host profiles have interior trunk taps; solid-module edge-corridor abstraction is not applicable')
if(snapshot.profile!=='bus_grouped'||!snapshot.boundary)throw Error('Requires captured bus_grouped memory and its boundary')
const layers=getAM3352Layers('ddr_left'),padding=.1016,leadLength=.5,width=.1016
const cpu=getAM3352Bounds('ddr_left'),ram=snapshot.boundary as Bounds
const ramYs=snapshot.placements?.map((p:any)=>{if(p.x!==-12||p.rotation!==180)throw Error('Unsupported captured RAM placement');return p.y})??[8,-8]
const modules=[
{name:'CPU',bounds:{minX:cpu.minX+12,maxX:cpu.maxX+12,minY:cpu.minY,maxY:cpu.maxY},paths:getAM3352TracePaths('ddr_left').map(p=>({...p,route:p.route.map(q=>({...q,x:Number(q.x)+12,y:Number(q.y)}))}))},
...[0,1].map(byte=>({name:`RAM${byte}`,bounds:{minX:-12-ram.maxX,maxX:-12-ram.minX,minY:ramYs[byte]-ram.maxY,maxY:ramYs[byte]-ram.minY},paths:adaptDdrMemoryPaths(snapshot.paths,DDR_MEMORY_SIX_LAYER_MAP,.1168).map(p=>({...p,route:p.route.map(q=>({...q,x:-12-Number(q.x),y:ramYs[byte]-Number(q.y)}))}))})),
].map(m=>({...m,keepout:inflateBounds(m.bounds,padding)}))
const ownerCounts=Object.fromEntries(modules.map(m=>[m.name,0]))
const assertEnclosed=(bounds:Bounds,label:string)=>{
const owners=modules.filter(m=>containsBounds(m.keepout,bounds))
if(owners.length!==1)throw Error(`${label} not enclosed by exactly one solid module keepout: ${JSON.stringify(bounds)}`)
ownerCounts[owners[0]!.name]++
}
input.obstacles.forEach((o:any,i:number)=>{
if(o.type!=='rect'||!Number.isFinite(o.width)||!Number.isFinite(o.height)||o.width<0||o.height<0)throw Error(`Unknown obstacle geometry ${i}`)
const angle=(o.ccwRotationDegrees??0)*Math.PI/180
if(!Number.isFinite(angle))throw Error(`Invalid obstacle rotation ${i}`)
const w=Math.abs(Math.cos(angle))*o.width+Math.abs(Math.sin(angle))*o.height,h=Math.abs(Math.sin(angle))*o.width+Math.abs(Math.cos(angle))*o.height
assertEnclosed({minX:o.center.x-w/2,maxX:o.center.x+w/2,minY:o.center.y-h/2,maxY:o.center.y+h/2},`obstacle ${i}`)
})
for(const [i,t] of input.traces.entries())for(const q of t.route)assertEnclosed(pointBox(q,q.route_type==='via'?Number(q.via_diameter??.4572)/2:Number(q.width??width)/2),`fixed trace ${i}`)
for(const m of modules)for(const path of m.paths)for(const q of path.route){
const r=q.route_type==='via'?Number(q.via_diameter??.4572)/2:Number(q.width??width)/2
if(!containsBounds(m.keepout,pointBox(q,r)))throw Error(`${m.name} cached copper outside conservative box: ${path.connection}`)
}
let renderedCopperPoints=0
for(const trace of rendered.filter(e=>e.type==='pcb_trace'))for(const q of trace.route){
assertEnclosed(pointBox(q,q.route_type==='via'?Number(q.via_diameter??.4572)/2:Number(q.width??width)/2),`rendered trace ${trace.pcb_trace_id}`)
renderedCopperPoints++
}
if(!renderedCopperPoints)throw Error('Actual rendered copper is required for enclosure validation')
const portMap=new Map<string,any>()
const leads=new Map<string,any>()
const connections=input.connections.map((c:any)=>({...c,width,nominalTraceWidth:width,pointsToConnect:c.pointsToConnect.map((point:any)=>{
const key=point.pointId??`${point.x},${point.y},${point.layer}`
const previous=portMap.get(key)
if(previous){
if(Math.hypot(previous.original.x-point.x,previous.original.y-point.y)>1e-7||previous.original.layer!==point.layer)throw Error(`Inconsistent shared port ${key}`)
leads.get(key).connectionNames.push(c.name)
return previous.virtual
}
const found=modules.flatMap(m=>m.paths.filter(p=>{const q=p.route.at(-1);return q?.route_type==='wire'&&q.layer===point.layer&&Math.hypot(q.x-point.x,q.y-point.y)<1e-6}).map(path=>({module:m,path})))
if(found.length!==1)throw Error(`Expected one cached exit for ${key}; found ${found.length}`)
const {module:m,path}=found[0]!
const virtual=outwardPort(point,m.bounds,leadLength)
const incoming=[...path.route].reverse().find(q=>q.route_type==='wire'&&q.layer===point.layer&&Math.hypot(q.x-point.x,q.y-point.y)>1e-7)
if(!incoming)throw Error(`No incoming cache segment for ${key}`)
const ix=point.x-incoming.x,iy=point.y-incoming.y,ox=virtual.x-point.x,oy=virtual.y-point.y
const cosine=(ix*ox+iy*oy)/(Math.hypot(ix,iy)*leadLength)
if(cosine<Math.SQRT1_2-1e-6)throw Error(`Lead introduces a sharper than45degree bend at ${key}`)
for(const box of modules)if(containsBounds(box.keepout,pointBox(virtual,width/2+padding)))throw Error(`Virtual port ${key} inside ${box.name} keepout`)
// The virtual point must be separated from its own rectangle by clearance + copper radius.
const sep=Math.max(m.keepout.minX-virtual.x,virtual.x-m.keepout.maxX,m.keepout.minY-virtual.y,virtual.y-m.keepout.maxY)
if(sep<width/2+padding-1e-7)throw Error(`Virtual port ${key} lacks external clearance`)
portMap.set(key,{pointId:key,module:m.name,cacheConnection:path.connection,original:{...point},virtual})
leads.set(key,{type:'pcb_trace',pcb_trace_id:`ddr_host_lead_${leads.size}`,connection_name:c.name,connectionNames:[c.name],source_trace_id:c.source_trace_id,pointId:key,module:m.name,route:[{route_type:'wire',x:point.x,y:point.y,layer:point.layer,width},{route_type:'wire',x:virtual.x,y:virtual.y,layer:point.layer,width}]})
return virtual
})}))
const moduleObstacles=modules.map(m=>({type:'rect',shape:'rect',center:{x:(m.keepout.minX+m.keepout.maxX)/2,y:(m.keepout.minY+m.keepout.maxY)/2},width:m.keepout.maxX-m.keepout.minX,height:m.keepout.maxY-m.keepout.minY,layers,connectedTo:[`solid_module_${m.name}`],offBoardConnectsTo:[],netIsAssignable:false}))
const leadObstacles=[...leads.values()].map(lead=>{
const [a,b]=lead.route
return {type:'rect',shape:'rect',center:{x:(a.x+b.x)/2,y:(a.y+b.y)/2},width:Math.abs(b.x-a.x)+width,height:Math.abs(b.y-a.y)+width,layers:[a.layer],connectedTo:[...lead.connectionNames],offBoardConnectsTo:[],netIsAssignable:false}
})
const obstacles=[...moduleObstacles,...leadObstacles]
return {input:{...input,__ddrCaptureHash:snapshot.captureHash??snapshot.sha256,connections,obstacles,traces:[],minTraceWidth:width,nominalTraceWidth:width},leads:[...leads.values()],portMap:[...portMap.values()],report:{status:'planning-abstraction-unvalidated',originalObstacleCount:input.obstacles.length,originalFixedTraceCount:input.traces.length,conservativeModuleObstacleCount:moduleObstacles.length,leadObstacleCount:leadObstacles.length,conservativeObstacleCount:obstacles.length,renderedCopperPointsVerified:renderedCopperPoints,layerCount:layers.length,connectionBranches:connections.length,uniquePorts:portMap.size,leadCount:leads.size,leadLengthMm:leadLength,paddingMm:padding,modules:modules.map(m=>({name:m.name,bounds:m.bounds,keepout:m.keepout,cachedPaths:m.paths.length,enclosedPrimitiveCount:ownerCounts[m.name]})),requires:'Reinsert leads and preserve every original cached/fixed copper primitive; run final physical connectivity, all-layer DRC, angle and full DDR timing checks.'}}
}
if(import.meta.main){
const dir=resolve(process.argv[2]??'dist/ddr-system/bus_grouped')
const result=prepareDdrHostCorridors(JSON.parse(readFileSync(resolve(dir,'routing-input.json'),'utf8')),JSON.parse(readFileSync(resolve(dir,'memory-source-snapshot.json'),'utf8')),JSON.parse(readFileSync(resolve(dir,'unrouted.circuit.json'),'utf8')))
for(const [file,data] of Object.entries({'corridor-routing-input.json':result.input,'corridor-leads.json':result.leads,'corridor-port-map.json':result.portMap,'corridor-report.json':result.report}))writeFileSync(resolve(dir,file),JSON.stringify(data,null,2)+'\n')
console.log(JSON.stringify(result.report,null,2))
}