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-winding-transition.ts
/** Existing winding solver routes an actual CPU escape to a separately checked full-depth landing. */
import{readFileSync,writeFileSync,mkdirSync}from'node:fs'
import{resolve}from'node:path'
import{createHash}from'node:crypto'
import{verifyDdrCapture}from'./ddr-capture-provenance'
import{verifyDdrSystemCopper,auditDdrTraceJunctions}from'./check-ddr-system-copper'
import{auditRouteAngles}from'./check-route-angles'
import{auditHostSelfContacts}from'./audit-ddr-host-self-contacts'
import{classifyHostFixedContact}from'./audit-ddr-host-fixed-contacts'
import{terminalOnlyFixedObstacles}from'./ddr-terminal-copper-obstacles'
import{supportCopperObstacles}from'./ddr-support-routing-context'
import{planDdrGroundAntipads}from'./ddr-ground-plane-antipads'
import{mergeDdrSignalAntipads}from'./merge-ddr-signal-antipads'
const flags=process.argv.slice(2).filter(arg=>arg.startsWith('--'));if(flags.some(arg=>arg!=='--merge-antipads'&&!arg.startsWith('--cpu-collar=')&&!arg.startsWith('--antipad-clearance=')))throw Error('Unknown transition-planning option')
const[capture,parentDir,signal,xArg,yArg,out,stateArg]=process.argv.slice(2).filter(arg=>!arg.startsWith('--')),via={x:Number(xArg),y:Number(yArg)},maximumSearchStates=stateArg===undefined?300000:Number(stateArg)
const cpuCollarMm=Number(process.argv.find(arg=>arg.startsWith('--cpu-collar='))?.split('=')[1]??.2)
if(!Number.isFinite(cpuCollarMm)||cpuCollarMm<.12||cpuCollarMm>1.2)throw Error('CPU collar must be .12–1.2mm')
if(!out||!Number.isFinite(via.x)||!Number.isFinite(via.y)||!Number.isInteger(maximumSearchStates)||maximumSearchStates<1000||maximumSearchStates>300000)throw Error('Usage: capture parent CPU-signal via-x via-y output [states<=300000] [--cpu-collar=mm] [--merge-antipads] [--antipad-clearance=mm]')
const hashes=new Map<string,string>(),fh=(p:string)=>createHash('sha256').update(readFileSync(p)).digest('hex'),read=(p:string)=>{p=resolve(p);hashes.set(p,fh(p));return JSON.parse(readFileSync(p,'utf8'))},hash=(x:any)=>createHash('sha256').update(JSON.stringify(x)).digest('hex'),{captureHash}=verifyDdrCapture(capture),circuit=read(resolve(parentDir,'candidate.circuit.json')),host=read(resolve(parentDir,'host-bundle.json')),report=read(resolve(parentDir,'report.json')),input=read(resolve(parentDir,'routing-input.json')),map=read(resolve(capture,'signal-map.json')),entry=map.find((m:any)=>m.cpuSignal===signal),connection=input.connections.find((c:any)=>c.source_trace_id===entry?.sourceTraceId)
if(report.captureHash!==captureHash||host.captureHash!==captureHash||host.layerSpace!=='physical'||report.candidateCircuitSha256!==hash(circuit)||!connection||connection.pointsToConnect.length!==2||host.traces.some((t:any)=>t.connection_name===connection.name))throw Error('Parent/connection provenance mismatch')
const[ram,cpu]=[...connection.pointsToConnect].sort((a:any,b:any)=>a.x-b.x);if(cpu.layer===ram.layer||!['inner2','inner4','inner6'].includes(ram.layer))throw Error('Expected explicit physical-layer transition')
const same=(a:any,b:any)=>Math.hypot(a.x-b.x,a.y-b.y)<1e-7,escapeTrace=(p:any)=>{const e=circuit.find((e:any)=>e.type==='pcb_breakout_point'&&e.pcb_breakout_point_id===p.pointId);if(!e||e.layer!==p.layer||!same(e,p))throw Error('Actual terminal differs');const ts=circuit.filter((t:any)=>t.type==='pcb_trace'&&t.source_trace_id===e.source_trace_id&&t.route.at(-1)?.layer===p.layer&&same(t.route.at(-1),p));if(ts.length!==1)throw Error('Ambiguous source escape');return ts[0]},cpuTrace=escapeTrace(cpu),ramTrace=escapeTrace(ram)
for(const t of host.traces)if(hash(circuit.find((e:any)=>e.type==='pcb_trace'&&e.pcb_trace_id===t.pcb_trace_id))!==hash(t))throw Error('Actual host copper differs')
const ids=new Set(host.traces.map((t:any)=>t.pcb_trace_id)),planes=circuit.filter((e:any)=>e.type==='pcb_copper_pour'),fixed=circuit.filter((e:any)=>e.type!=='pcb_copper_pour'&&!ids.has(e.pcb_trace_id)),landing={type:'pcb_trace',pcb_trace_id:`planned_transition_${signal}`,source_trace_id:connection.source_trace_id,connection_name:connection.name,route:[{route_type:'wire',...via,layer:cpu.layer,width:.12},{route_type:'via',...via,from_layer:cpu.layer,to_layer:ram.layer,via_diameter:.4572,via_hole_diameter:.254},{route_type:'wire',...via,layer:ram.layer,width:.12}]},planePlan=process.argv.includes('--merge-antipads')?(()=>{const p=mergeDdrSignalAntipads(planes,[via],Number(process.argv.find(a=>a.startsWith('--antipad-clearance='))?.split('=')[1]??.1016));return{...p,added:p.changes}})():planDdrGroundAntipads(planes,[{...via,net:signal}]),landingPhysical=verifyDdrSystemCopper([...fixed,...planePlan.planes],[...host.traces,landing],input.connections,{},50,{reportSameNetContacts:true}),landingContacts=(landingPhysical.sameNetContacts??[]).filter((v:any)=>v.aShape.traceId===landing.pcb_trace_id||v.bShape.traceId===landing.pcb_trace_id)
mkdirSync(out,{recursive:true});const write=(n:string,v:any)=>writeFileSync(resolve(out,n),JSON.stringify(v,null,2)+'\n'),assertUnchanged=()=>{verifyDdrCapture(capture);for(const[p,h]of hashes)if(fh(p)!==h)throw Error('Winding transition input changed')}
write('landing-report.json',{captureHash,via,landingPhysical,landingContacts});if(landingPhysical.errors.length||landingPhysical.violations.length||landingContacts.length)throw Error('Full-depth landing is not isolated and clear')
const previous=[...cpuTrace.route].reverse().find((p:any)=>p.route_type==='wire'&&p.layer===cpu.layer&&!same(p,cpu)),length=Math.hypot(cpu.x-previous.x,cpu.y-previous.y),collar={x:cpu.x+cpuCollarMm*(cpu.x-previous.x)/length,y:cpu.y+cpuCollarMm*(cpu.y-previous.y)/length},projection=terminalOnlyFixedObstacles(fixed,connection),target={...via,layer:cpu.layer},landingObstacles=supportCopperObstacles([landing]).flatMap((o:any)=>[{...o,shape:'circle',layers:o.layers.filter((l:string)=>l!==cpu.layer),connectedTo:['reserved:landing']},{...o,shape:'circle',layers:o.layers.filter((l:string)=>l===cpu.layer),connectedTo:[connection.name]}].filter(o=>o.layers.length)),srj:any={...input,traces:host.traces,obstacles:[...projection.obstacles,...supportCopperObstacles(host.traces).map((o:any)=>({...o,...(o.obstacleId.startsWith('fixed_via_')||o.obstacleId.endsWith('_cap')?{shape:'circle'}:{}),connectedTo:['reserved:host']})),...landingObstacles]},sourceObstacle=srj.obstacles.find((o:any)=>o.connectedTo.includes(connection.name)&&o.layers.includes(cpu.layer)&&same(o.center,cpu))
if(!sourceObstacle)throw Error('Missing actual CPU launch selector')
const prepared:any={connection,connectionIndex:0,sourcePoint:{...cpu,pcb_port_id:cpu.pointId},sourcePointIndex:connection.pointsToConnect.findIndex((p:any)=>p.pointId===cpu.pointId),sourceLayer:cpu.layer,sourceObstacle,targetPoint:target},bus:any={busId:connection.name,direction:'left',exitEdge:'left',termination:{type:'boundary'},connections:[prepared],componentId:'actual-CPU-DDR-exit',componentObstacles:[sourceObstacle],componentBounds:input.bounds,sharedBoundary:{...input.bounds,maxX:4},xCoordinates:[cpu.x],yCoordinates:[cpu.y],pitchX:.8,pitchY:.8}
const{routeViaMinimalWindingAlternativesSteps}=await import(resolve('node_modules/@tscircuit/fanout-solver/lib/route-via-minimal-winding.ts')),params:any={srj,bus,targetLayer:cpu.layer,terminals:[{connection:prepared,viaPoint:collar,exitPoint: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,heuristicWeight:1.2,sourceEscapePaths:new Map([[0,[cpu,collar]]])},started=Date.now(),steps=routeViaMinimalWindingAlternativesSteps(params,1),progress:any[]=[];let results:any[]=[],finished=false
while(Date.now()-started<55000){const s=steps.next();if(s.done){results=s.value;finished=true;break}progress.push({...s.value,visualization:undefined});write('progress.json',progress)}if(!finished)steps.return([])
const raw=results[0]??[];let chosen:any=null,diagnostic:any={}
if(raw.length===1){const prefix=raw[0].trace.route;if(prefix.some((p:any)=>p.route_type!=='wire'||p.layer!==cpu.layer)||!same(prefix[0],cpu)||!same(prefix.at(-1),via))throw Error('Solver changed layer or endpoints');const trace={...landing,route:[...prefix,...landing.route.slice(1)]},actual=[...fixed,...planePlan.planes,...host.traces,trace],physical=verifyDdrSystemCopper([...fixed,...planePlan.planes],[...host.traces,trace],input.connections,{},50,{reportSameNetContacts:true}),contacts=(physical.sameNetContacts??[]).filter((v:any)=>v.aShape.traceId===trace.pcb_trace_id||v.bShape.traceId===trace.pcb_trace_id).map((v:any)=>classifyHostFixedContact(v,trace)),terminalOnly=contacts.length===1&&contacts.every((v:any)=>v.classification!=='unintended-interior-contact'&&v.fixedTraceId===cpuTrace.pcb_trace_id&&v.hostShape.layer===cpu.layer&&[v.hostShape.a,v.hostShape.b].some((p:any)=>same(p,cpu))),selfContacts=auditHostSelfContacts([...host.traces,trace]),allAngles=auditRouteAngles(actual.filter((e:any)=>e.type==='pcb_trace')),allJunctions=auditDdrTraceJunctions(actual,input.connections)
diagnostic={physical,contacts,terminalOnly,selfContacts,allAngles,allJunctions}
if(terminalOnly&&selfContacts.valid&&allAngles.valid&&allJunctions.valid&&!physical.errors.length&&!physical.violations.length&&physical.connectivity.filter(c=>c.connected).length===host.traces.length){const planar=(r:any[])=>r.reduce((n:number,b:any,i:number)=>{const a=r[i-1];return n+(a?.route_type==='wire'&&b.route_type==='wire'&&a.layer===b.layer?Math.hypot(a.x-b.x,a.y-b.y):0)},0),dx=Math.abs(via.x-ram.x),dy=Math.abs(via.y-ram.y),nominal=read(resolve(capture,'../../ddr-integrated-ten/dqlm-audit.json'));if(nominal.captureHash!==captureHash)throw Error('Nominal capture mismatch');const strobeConnections=input.connections.filter((c:any)=>c.ramByte===connection.ramByte&&['DQS_P','DQS_N'].includes(c.ramTerminal));if(strobeConnections.length!==2)throw Error('Missing actual byte strobe pair');const strobeLengths=strobeConnections.map((c:any)=>{const trace=host.traces.find((t:any)=>t.connection_name===c.name);if(!trace)throw Error('Byte strobe host not routed');return planar(trace.route)+c.pointsToConnect.reduce((n:number,p:any)=>n+planar(escapeTrace(p).route),0)}),targetPlanarMm=strobeLengths.reduce((a:number,b:number)=>a+b,0)/2;const budget={targetPlanarMm,actualStrobeLengthsMm:strobeLengths,ramEscapeMm:planar(ramTrace.route),cpuEscapeMm:planar(cpuTrace.route),transitionMm:planar(prefix),remainingOctileMm:Math.max(dx,dy)+(Math.SQRT2-1)*Math.min(dx,dy),nominalMm:nominal.bytes.find((b:any)=>b.byte===connection.ramByte).dqlmPairedDataMaximumMm};chosen={trace,via,source:cpu,target:ram,sourceEscapePoints:prefix,budget:{...budget,optimisticTotalMm:budget.ramEscapeMm+budget.cpuEscapeMm+budget.transitionMm+budget.remainingOctileMm},terminalOnly,contacts,selfContacts,addedAntipads:planePlan.added}}
}
assertUnchanged();write('raw-prefix.json',raw);write('transition-plan.json',{captureHash,parentCircuitSha256:hash(circuit),hostTracesSha256:hash(host.traces),inputHashes:Object.fromEntries(hashes),connection,accepted:chosen?[chosen]:[],finished,maximumSearchStates,cpuCollarMm,elapsedSeconds:(Date.now()-started)/1000,diagnostic,scope:'Existing winding CPU prefix plus isolated full-depth landing. RAM continuation and full length matching remain unqualified.'});if(chosen){write('planes.json',planePlan.planes);write('physical-report.json',{captureHash,...diagnostic.physical})}console.log(JSON.stringify({accepted:!!chosen,alternatives:raw.length,budget:chosen?.budget,finished}));if(!chosen)process.exitCode=1