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/plan-ddr-facing-ram-exit.ts
/** Local RAM escape replacement against a verified frozen board; external RAM source is read-only. */
import {readFileSync,writeFileSync,mkdirSync} from 'node:fs'
import {resolve} from 'node:path'
import {verifyDdrCapture} from './ddr-capture-provenance'
import {prepareDdrHostSignalProjection,projectHostObstacles} from './prepare-ddr-host-signal-projection'
import {loadDdrSupportRoutingContext,supportCopperObstacles} from './ddr-support-routing-context'
import {validateDeclaredDdrReplacements,objectHash} from './improve-ddr-a9-power-launches'
import {verifyDdrSystemCopper} from './check-ddr-system-copper'
import {retainViaLaunch} from './ddr-retained-via-route'
const [capture,parentDir,supportDir,signal,out,yArg,gridArg,layerArg]=process.argv.slice(2)
if(!capture||!parentDir||!supportDir||!signal||!out||!Number.isFinite(Number(yArg)))throw Error('Usage: capture parent-support child-support CPU-signal output target-y')
const gridStep=gridArg===undefined?.1:Number(gridArg)
if(![.05,.1].includes(gridStep))throw Error('Grid step must be .05 or .1 mm')
const read=(p:string)=>JSON.parse(readFileSync(p,'utf8')),write=(p:string,x:any)=>writeFileSync(resolve(out,p),JSON.stringify(x,null,2)+'\n')
const {captureHash}=verifyDdrCapture(capture),original=read(resolve(capture,'unrouted.circuit.json')),input=read(resolve(capture,'routing-input.json')),map=read(resolve(capture,'signal-map.json')),parent=read(resolve(parentDir,'candidate.circuit.json')),circuit=read(resolve(supportDir,'candidate.circuit.json')),manifest=read(resolve(supportDir,'original-copper-replacements.json')),host=read(resolve(supportDir,'host-bundle.json'))
const evidence=validateDeclaredDdrReplacements(parent,circuit,manifest)
if(host.captureHash!==captureHash)throw Error('Capture mismatch')
const context=await loadDdrSupportRoutingContext(capture,resolve(parentDir,'candidate.circuit.json'),original,host.traces,captureHash)
const entry=map.find((m:any)=>m.cpuSignal===signal),connection=input.connections.find((c:any)=>c.source_trace_id===entry?.sourceTraceId)
if(!connection||!['DM','DQ1','DQ3','DQ5','DQ7'].includes(connection.ramTerminal))throw Error('Expected an opposite-facing data/mask escape')
if(host.traces.some((t:any)=>t.connection_name===connection.name))throw Error('Existing affected host must be explicitly invalidated before changing its exit')
const ram=[...connection.pointsToConnect].sort((a:any,b:any)=>a.x-b.x)[0],exit=circuit.find((e:any)=>e.pcb_breakout_point_id===ram.pointId)
const old=circuit.find((e:any)=>e.type==='pcb_trace'&&e.source_trace_id===exit?.source_trace_id&&e.route.at(-1)?.layer===ram.layer&&Math.hypot(e.route.at(-1).x-ram.x,e.route.at(-1).y-ram.y)<1e-7)
if(!old)throw Error('Missing exact original RAM escape')
const viaIndex=old.route.findIndex((p:any)=>p.route_type==='via')
if(viaIndex<0||old.route.filter((p:any)=>p.route_type==='via').length!==1)throw Error('Requires one retained physical via')
const targetLayer=layerArg??ram.layer
if(!['inner2','inner4'].includes(targetLayer))throw Error('Facing escape requires a referenced data signal layer')
const via={...old.route[viaIndex],to_layer:targetLayer},source={...old.route[0],pointId:ram.pointId},target={x:-6.040077,y:Number(yArg),layer:targetLayer},sourceEscapePoints=old.route.slice(0,viaIndex).map((p:any)=>({x:p.x,y:p.y}))
const projection=prepareDdrHostSignalProjection(capture),inverse:Record<string,string>={top:'top',inner1:'inner2',inner2:'inner4',bottom:'inner6'}
// Remove only the old post-via wire obstacles; its physical barrel/top launch stay reserved.
const removedObstacleIds:string[]=[]
const baseObstacles=input.obstacles.filter((o:any)=>{
const prefix=`fixed_trace_${old.pcb_trace_id}_`
if(!o.obstacleId?.startsWith(prefix))return true
const index=Number(o.obstacleId.slice(prefix.length).split('_')[0])
const remove=Number.isInteger(index)&&index>viaIndex
if(remove)removedObstacleIds.push(o.obstacleId)
return !remove
})
const supportAdditions=manifest.replacements.filter((r:any)=>r.reason.includes('A9')&&r.after.type==='pcb_trace').map((r:any)=>r.after)
const added=projectHostObstacles(supportCopperObstacles(supportAdditions)).projected.map((o:any)=>({...o,layers:o.layers.map((l:string)=>inverse[l])}))
const srj:any={...input,obstacles:[...baseObstacles,...context.obstacles,...added],traces:host.traces}
const sourceObstacle=srj.obstacles.find((o:any)=>o.connectedTo.includes(connection.name)&&o.layers.includes('top')&&Math.abs(o.center.x-source.x)<=o.width/2+1e-7&&Math.abs(o.center.y-source.y)<=o.height/2+1e-7)
if(!sourceObstacle)throw Error('Missing source-owned RAM pad obstacle')
const localConnection={...connection,pointsToConnect:[source,target]},prepared:any={connection:localConnection,connectionIndex:0,sourcePoint:source,sourcePointIndex:0,sourceLayer:'top',sourceObstacle,targetPoint:target}
const bounds={minX:-18,maxX:target.x,minY:connection.ramByte===0?-13:0,maxY:connection.ramByte===0?-3:10}
if(target.y<bounds.minY||target.y>bounds.maxY)throw Error('Target outside local search region')
const bus:any={busId:signal,direction:'right',exitEdge:'right',termination:{type:'boundary'},connections:[prepared],componentId:'RAM-facing-escape',componentObstacles:[sourceObstacle],componentBounds:bounds,sharedBoundary:bounds,xCoordinates:[via.x],yCoordinates:[via.y],pitchX:.8,pitchY:.8}
const {routeViaMinimalWindingAlternativesSteps}=await import(resolve('node_modules/@tscircuit/fanout-solver/lib/route-via-minimal-winding.ts'))
const params:any={srj,bus,targetLayer,terminals:[{connection:prepared,viaPoint:via,exitPoint:target}],acceptedPlans:[],layerNames:['top',...Array.from({length:8},(_,i)=>`inner${i+1}`),'bottom'],traceWidth:.12,viaDiameter:via.via_diameter,viaHoleDiameter:via.via_hole_diameter,clearance:.1016,allowBlindAndBuriedVias:false,allowSourceLayerRouting:true,gridStep,maximumRouteOrderAttempts:1,maximumSearchStates:300000,heuristicWeight:1.2,sourceEscapePaths:new Map([[0,sourceEscapePoints]])}
mkdirSync(out,{recursive:true});write('input-evidence.json',{captureHash,parentContext:context.evidence,childReplacements:evidence,signal,connection,source,target,via,removedObstacleIds,sourceEscapePoints,bounds,gridStep,obstacleMode:'native rotated rectangles and actual circular barrels'})
const started=Date.now(),steps=routeViaMinimalWindingAlternativesSteps(params,1),progress:any[]=[];let result:any=[],finished=false
while(Date.now()-started<55000){const step=steps.next();if(step.done){result=step.value;finished=true;break}progress.push({...step.value,visualization:undefined});write('progress.json',progress)}
if(!finished)steps.return([])
const raw=result[0]?.[0]?.trace
let accepted=false,physical:any,changed:any,updatedExit:any,lengthBudget:any
if(raw){
const retainedPrefix=old.route.slice(0,viaIndex+1).map((p:any,i:number)=>i===viaIndex?{...p,to_layer:targetLayer}:p)
changed={...old,route:retainViaLaunch(raw.route,retainedPrefix)}
// A profile replacement retains the complete actual pad-to-via prefix.
if(objectHash(changed.route.slice(0,viaIndex+1))!==objectHash(retainedPrefix))throw Error('Solver changed existing pad-to-via prefix')
const last=changed.route.at(-1)
if(last.route_type!=='wire'||last.layer!==target.layer||Math.hypot(last.x-target.x,last.y-target.y)>1e-7)throw Error('Wrong exit endpoint')
const previous=[...changed.route].reverse().find((p:any)=>p.route_type==='wire'&&p.layer===target.layer&&Math.hypot(p.x-target.x,p.y-target.y)>1e-7)
if(!previous||target.x-previous.x<=0)throw Error('CPU-facing exit must have an outward rightward tangent')
const planar=(route:any[])=>route.reduce((sum:number,p:any,i:number)=>{const q=route[i-1];return sum+(p.route_type==='wire'&&q?.route_type==='wire'&&p.layer===q.layer?Math.hypot(p.x-q.x,p.y-q.y):0)},0)
const cpu=connection.pointsToConnect.find((p:any)=>p.pointId!==ram.pointId),cpuExit=circuit.find((e:any)=>e.pcb_breakout_point_id===cpu.pointId),cpuTrace=circuit.find((e:any)=>e.type==='pcb_trace'&&e.source_trace_id===cpuExit?.source_trace_id&&e.route.some((p:any)=>p.route_type==='wire'&&p.layer===cpu.layer&&Math.hypot(p.x-cpu.x,p.y-cpu.y)<1e-7))
if(!cpuTrace)throw Error('Missing CPU escape for nominal budget')
const dx=Math.abs(cpu.x-target.x),dy=Math.abs(cpu.y-target.y),newRamMm=planar(changed.route),hostFloorMm=Math.max(dx,dy)+(Math.SQRT2-1)*Math.min(dx,dy)
const dqlm=read(resolve(parentDir,'dqlm-audit.json'))
if(dqlm.captureHash!==captureHash)throw Error('Nominal budget capture mismatch')
const nominalMm=dqlm.bytes.find((b:any)=>b.byte===connection.ramByte)?.dqlmPairedDataMaximumMm
if(!Number.isFinite(nominalMm))throw Error('Missing measured DQLM')
lengthBudget={ramEscapeMm:newRamMm,cpuEscapeMm:planar(cpuTrace.route),hostFloorMm,nominalMm,optimisticTotalMm:newRamMm+planar(cpuTrace.route)+hostFloorMm}
lengthBudget.nominalFloorPass=lengthBudget.optimisticTotalMm<=nominalMm+1e-7
updatedExit={...exit,x:target.x,y:target.y,layer:targetLayer}
const candidate=circuit.map((e:any)=>e===old?changed:e===exit?updatedExit:e)
const hostIds=new Set(host.traces.map((t:any)=>t.pcb_trace_id)),fixed=candidate.filter((e:any)=>!hostIds.has(e.pcb_trace_id))
physical=verifyDdrSystemCopper(fixed,host.traces,input.connections,{})
accepted=lengthBudget.nominalFloorPass&&!physical.errors.length&&!physical.violations.length&&physical.angles.valid&&!physical.joinedBends.length&&physical.connectivity.filter((c:any)=>c.connected).length===host.traces.length
write('raw-replacement.json',changed)
if(accepted){write('candidate.circuit.json',candidate);write('replacements.json',{parentCircuitSha256:objectHash(circuit),candidateCircuitSha256:objectHash(candidate),traces:[{before:old,after:changed}],breakoutPoints:[{before:exit,after:updatedExit}],scope:'One local RAM escape replacement; requires refreshed capture/exit contract before host routing.'})}
}
write('report.json',{captureHash,accepted,alternatives:result.length,searchFinished:finished,physical,lengthBudget,elapsedSeconds:(Date.now()-started)/1000,scope:'Local RAM profile exploration; no complete DDR or timing claim.'})
console.log(JSON.stringify({accepted,alternatives:result.length,errors:physical?.errors.length,violations:physical?.violations.length,angles:physical?.angles.violations.length,joins:physical?.joinedBends.length}));if(!accepted)process.exitCode=1