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-byte-bus.ts

/** Joint byte escape exploration using the installed winding solver, never a new router. */
import{readFileSync,writeFileSync,mkdirSync}from'node:fs'
import{resolve}from'node:path'
import{verifyDdrCapture}from'./ddr-capture-provenance'
import{loadDdrSupportRoutingContext,supportCopperObstacles}from'./ddr-support-routing-context'
import{validateDeclaredDdrReplacements,objectHash}from'./improve-ddr-a9-power-launches'
import{verifyDdrSystemCopper}from'./check-ddr-system-copper'
export function facingByteSlots(entries:any[],pitch=.32){
 if(entries.length!==11||!Number.isFinite(pitch)||pitch<.32||entries.some(e=>!Number.isFinite(e.via.x)||!Number.isFinite(e.via.y))||new Set(entries.map(e=>e.connection.ramTerminal)).size!==11)throw Error('Expected eleven distinct byte members and pitch >= .32')
 const pair=entries.filter(e=>['DQS_P','DQS_N'].includes(e.connection.ramTerminal));if(pair.length!==2)throw Error('Missing DQS pair')
 const groups=entries.filter(e=>!pair.includes(e)).map(e=>[e]);groups.push(pair.sort((a,b)=>a.via.y-b.via.y||a.connection.ramTerminal.localeCompare(b.connection.ramTerminal)))
 groups.sort((a,b)=>a.reduce((s,e)=>s+e.via.y,0)/a.length-b.reduce((s,e)=>s+e.via.y,0)/b.length||a[0].connection.name.localeCompare(b[0].connection.name))
 const ordered=groups.flat(),center=entries.reduce((s,e)=>s+e.via.y,0)/11
 return ordered.map((e,i)=>({...e,target:{x:-6.040077,y:center+(i-5)*pitch,layer:'inner2'},slot:i}))
}
export function prepareFacingByteBus(circuit:any[],input:any,host:any,obstacles:any[]){
 const connections=input.connections.filter((c:any)=>c.ramByte===0&&/^(DM|DQ[0-7]|DQS_P|DQS_N)$/.test(c.ramTerminal))
 if(connections.length!==11)throw Error(`Expected byte0 eleven connections, got ${connections.length}`)
 const entries=connections.map((connection:any)=>{const ram=[...connection.pointsToConnect].sort((a,b)=>a.x-b.x)[0],exit=circuit.find(e=>e.type==='pcb_breakout_point'&&e.pcb_breakout_point_id===ram.pointId),old=circuit.find(e=>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||ram.layer!=='inner2')throw Error('Missing exact byte0 RAM escape');const viaIndex=old.route.findIndex((p:any)=>p.route_type==='via');if(viaIndex<1||old.route.filter((p:any)=>p.route_type==='via').length!==1)throw Error('Requires retained single via');return{connection,ram,exit,old,viaIndex,via:old.route[viaIndex],source:{...old.route[0],pointId:ram.pointId}}})
 const names=new Set(connections.map((c:any)=>c.name)),invalidated=host.traces.filter((t:any)=>names.has(t.connection_name)||names.has(t.source_trace_id)),retained=host.traces.filter((t:any)=>!invalidated.includes(t)),removedObstacleIds:string[]=[]
 if(invalidated.length!==6)throw Error(`Expected six explicitly invalidated byte0 host traces, got ${invalidated.length}`)
 const fixedObstacles=obstacles.filter((o:any)=>{for(const e of entries){const prefix=`fixed_trace_${e.old.pcb_trace_id}_`;if(o.obstacleId?.startsWith(prefix)){const index=Number(o.obstacleId.slice(prefix.length).split('_')[0]);if(Number.isInteger(index)&&index>e.viaIndex){removedObstacleIds.push(o.obstacleId);return false}}}return true})
 return{entries:facingByteSlots(entries),invalidated,retained,removedObstacleIds,fixedObstacles}
}
if(import.meta.main){
 const [capture,parentDir,supportDir,out]=process.argv.slice(2);if(!out)throw Error('Usage: capture parent-support child-support output')
 const read=(p:string)=>JSON.parse(readFileSync(p,'utf8')),write=(n:string,v:any)=>writeFileSync(resolve(out,n),JSON.stringify(v,null,2)+'\n'),{captureHash}=verifyDdrCapture(capture),original=read(resolve(capture,'unrouted.circuit.json')),input=read(resolve(capture,'routing-input.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 childEvidence=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),additions=manifest.replacements.filter((r:any)=>r.reason.includes('A9')&&r.after.type==='pcb_trace').map((r:any)=>r.after),p=prepareFacingByteBus(circuit,input,host,[...input.obstacles,...context.obstacles,...supportCopperObstacles(additions)])
 const bounds={minX:-18,maxX:-6.040077,minY:-13,maxY:-3},srj:any={...input,obstacles:p.fixedObstacles,traces:p.retained},prepared=p.entries.map((e:any,i:number)=>{const sourceObstacle=srj.obstacles.find((o:any)=>o.connectedTo.includes(e.connection.name)&&o.layers.includes('top')&&Math.abs(o.center.x-e.source.x)<=o.width/2+1e-7&&Math.abs(o.center.y-e.source.y)<=o.height/2+1e-7);if(!sourceObstacle)throw Error('Missing source-owned actual pad obstacle');if(e.target.y<bounds.minY||e.target.y>bounds.maxY)throw Error('Slot outside local boundary');return{connection:{...e.connection,pointsToConnect:[e.source,e.target]},connectionIndex:i,sourcePoint:e.source,sourcePointIndex:0,sourceLayer:'top',sourceObstacle,targetPoint:e.target}})
 const targetPreflight=p.entries.map(e=>({terminal:e.connection.ramTerminal,target:e.target,blockedBy:srj.obstacles.filter((o:any)=>o.layers.includes('inner2')&&!o.connectedTo.includes(e.connection.name)).map((o:any)=>{const a=(o.ccwRotationDegrees??0)*Math.PI/180,dx=e.target.x-o.center.x,dy=e.target.y-o.center.y,x=dx*Math.cos(a)+dy*Math.sin(a),y=-dx*Math.sin(a)+dy*Math.cos(a);return{obstacleId:o.obstacleId,gapMm:Math.hypot(Math.max(0,Math.abs(x)-o.width/2),Math.max(0,Math.abs(y)-o.height/2))-.06}}).filter((o:any)=>o.gapMm<.1016-1e-8)}))
 const sourceViaPreflight=p.entries.map(e=>({terminal:e.connection.ramTerminal,via:e.via,blockedBy:srj.obstacles.filter((o:any)=>o.layers.includes('inner2')&&!o.connectedTo.includes(e.connection.name)).map((o:any)=>{const a=(o.ccwRotationDegrees??0)*Math.PI/180,dx=e.via.x-o.center.x,dy=e.via.y-o.center.y,x=dx*Math.cos(a)+dy*Math.sin(a),y=-dx*Math.sin(a)+dy*Math.cos(a);return{obstacleId:o.obstacleId,gapMm:Math.hypot(Math.max(0,Math.abs(x)-o.width/2),Math.max(0,Math.abs(y)-o.height/2))-.06}}).filter((o:any)=>o.gapMm<.1016-1e-8)}))
 const bus:any={busId:'RAM0_CPU_facing_byte',direction:'right',exitEdge:'right',termination:{type:'boundary'},connections:prepared,componentId:'RAM0',componentObstacles:prepared.map(e=>e.sourceObstacle),componentBounds:bounds,sharedBoundary:bounds,xCoordinates:p.entries.map(e=>e.via.x),yCoordinates:p.entries.map(e=>e.via.y),pitchX:.8,pitchY:.8}
 const params:any={srj,bus,targetLayer:'inner2',terminals:prepared.map((connection,i)=>({connection,viaPoint:p.entries[i].via,exitPoint:p.entries[i].target})),acceptedPlans:[],layerNames:['top',...Array.from({length:8},(_,i)=>`inner${i+1}`),'bottom'],traceWidth:.12,viaDiameter:.4572,viaHoleDiameter:.254,clearance:.1016,allowBlindAndBuriedVias:false,allowSourceLayerRouting:true,gridStep:.1,maximumRouteOrderAttempts:1,maximumSearchStates:300000,heuristicWeight:1.2,sourceEscapePaths:new Map(p.entries.map((e,i)=>[i,e.old.route.slice(0,e.viaIndex).map((q:any)=>({x:q.x,y:q.y}))]))}
 mkdirSync(out,{recursive:true});write('preparation.json',{captureHash,parentContext:context.evidence,childEvidence,parentCircuitSha256:objectHash(circuit),bounds,pitchMm:.32,ordering:'Via-Y sorted atomic groups; DQS treated as adjacent pair at its mean via Y.',entries:p.entries,targetPreflight,sourceViaPreflight,invalidatedHostTraces:p.invalidated,retainedHostTraces:p.retained,removedObstacleIds:p.removedObstacleIds,scope:'All eleven old post-via paths removed only in exploration; preserved pad/via prefixes are incomplete until a complete candidate passes.'})
 const remove=new Set([...p.entries.map(e=>e.old.pcb_trace_id),...p.invalidated.map((e:any)=>e.pcb_trace_id)]),incomplete=circuit.filter((e:any)=>!remove.has(e.pcb_trace_id));write('incomplete-prefixes.json',p.entries.map(e=>({...e.old,route:e.old.route.slice(0,e.viaIndex+1)})))
 const {routeViaMinimalWindingAlternativesSteps}=await import(resolve('node_modules/@tscircuit/fanout-solver/lib/route-via-minimal-winding.ts')),steps=routeViaMinimalWindingAlternativesSteps(params,1),started=Date.now(),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]??[];write('raw-plans.json',raw);let accepted=false,physical:any,nominalFloorAudit:any,changed:any[]=[]
 if(raw.length===11){
  changed=p.entries.map((e:any)=>{const plan=raw.find((q:any)=>q.trace.connection_name===e.connection.name||q.connectionName===e.connection.name);if(!plan)throw Error('Missing member plan');const route=plan.trace.route,geometry=(p:any)=>p.route_type==='wire'?{type:'wire',x:p.x,y:p.y,layer:p.layer,width:p.width}:{type:'via',x:p.x,y:p.y,from:p.from_layer,to:p.to_layer,pad:p.via_diameter,drill:p.via_hole_diameter};if(objectHash(route.slice(0,e.viaIndex+1).map(geometry))!==objectHash(e.old.route.slice(0,e.viaIndex+1).map(geometry)))throw Error('Solver changed retained pad/via prefix');const last=route.at(-1),previous=[...route].reverse().find((q:any)=>q.route_type==='wire'&&Math.hypot(q.x-last.x,q.y-last.y)>1e-7);if(!previous||last.x<=previous.x||Math.abs(last.y-previous.y)>1e-7)throw Error('New exit does not face right');if(last.layer!=='inner2'||Math.hypot(last.x-e.target.x,last.y-e.target.y)>1e-7)throw Error('Wrong terminal');return{...e.old,route:[...e.old.route.slice(0,e.viaIndex+1),...route.slice(e.viaIndex+1)]}})
  const retainedIds=new Set(p.retained.map((t:any)=>t.pcb_trace_id)),fixed=incomplete.filter((e:any)=>!retainedIds.has(e.pcb_trace_id)).map((e:any)=>{const entry=p.entries.find(p=>p.exit===e);return entry?{...e,x:entry.target.x,y:entry.target.y}:e})
  physical=verifyDdrSystemCopper(fixed,[...p.retained,...changed],input.connections,{})
  const nominal=read(resolve(parentDir,'dqlm-audit.json')).bytes.find((b:any)=>b.byte===0),length=(r:any[])=>r.reduce((s:number,b:any,i:number)=>{const a=r[i-1];return s+(a?.route_type==='wire'&&b.route_type==='wire'&&a.layer===b.layer?Math.hypot(a.x-b.x,a.y-b.y):0)},0),octile=(a:any,b:any)=>Math.max(Math.abs(a.x-b.x),Math.abs(a.y-b.y))+(Math.SQRT2-1)*Math.min(Math.abs(a.x-b.x),Math.abs(a.y-b.y))
  nominalFloorAudit=p.entries.map((e,i)=>{const f=nominal.frozenExitFloors.find((f:any)=>f.terminal===e.connection.ramTerminal);if(!f)throw Error('Missing original nominal floor');const nominalMm=f.totalPlanarLowerBoundMm-f.excessOverNominalMm,ramFixedMm=length(changed[i].route),hostOctilinearLowerBoundMm=octile(e.target,f.cpuExit),totalPlanarLowerBoundMm=ramFixedMm+f.cpuFixedMm+hostOctilinearLowerBoundMm;return{terminal:e.connection.ramTerminal,ramFixedMm,cpuFixedMm:f.cpuFixedMm,hostOctilinearLowerBoundMm,totalPlanarLowerBoundMm,nominalMm,excessOverNominalMm:totalPlanarLowerBoundMm-nominalMm,passes:totalPlanarLowerBoundMm<=nominalMm+1e-8}})
  accepted=nominalFloorAudit.every((r:any)=>r.passes)&&!physical.errors.length&&!physical.violations.length&&physical.angles.valid&&!physical.joinedBends.length&&physical.connectivity.filter((c:any)=>c.connected).length===p.retained.length
  if(accepted){write('candidate.circuit.json',[...fixed,...p.retained,...changed]);write('replacement-manifest.json',{parentCircuitSha256:objectHash(circuit),replacements:p.entries.map((e,i)=>({before:e.old,after:changed[i]})),updatedExits:p.entries.map(e=>({before:e.exit,after:{...e.exit,x:e.target.x,y:e.target.y}})),invalidatedHostTraces:p.invalidated,scope:'New local profile candidate; requires recapture and all11 host routes plus timing closure.'})}
 }
 write('report.json',{captureHash,accepted,completeBytePaths:changed.length,alternatives:result.length,searchFinished:finished,elapsedSeconds:(Date.now()-started)/1000,targetPreflight,sourceViaPreflight,nominalFloorAudit,physical,invalidatedHostCount:p.invalidated.length,retainedHostCount:p.retained.length,scope:'Joint eleven-member local RAM profile attempt; incomplete paths never published as a valid board.'});console.log(JSON.stringify({accepted,completeBytePaths:changed.length,finished,progress:progress.at(-1),errors:physical?.errors.length,violations:physical?.violations.length}));if(!accepted)process.exitCode=1
}