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-ram-power-drops.ts

import{readFileSync,writeFileSync,mkdirSync,existsSync}from'node:fs'
import{resolve}from'node:path'
import{createHash}from'node:crypto'
import{verifyDdrCapture}from'./ddr-capture-provenance'
import{verifyDdrSystemCopper}from'./check-ddr-system-copper'
import{planDdrGroundAntipads}from'./ddr-ground-plane-antipads'
export function truncatePowerStubAtVia(route:any[],distanceMm:number,targetLayer:string){
 if(route.some(p=>p.route_type==='via'))throw Error('Existing via must not be replaced')
 const first=route[0],last=route.at(-1)
 if(!first||route.some(p=>p.route_type!=='wire'||p.layer!=='top'))throw Error('Requires straight top stub')
 const length=Math.hypot(last.x-first.x,last.y-first.y),dx=(last.x-first.x)/length,dy=(last.y-first.y)/length
 if(distanceMm<=0||distanceMm>1.524||distanceMm>=length)throw Error('Invalid early via distance')
 if(route.some(p=>Math.abs((p.x-first.x)*dy-(p.y-first.y)*dx)>1e-7))throw Error('Stub is not straight')
 const point={x:first.x+dx*distanceMm,y:first.y+dy*distanceMm}
 return [{...first},{route_type:'wire',...point,layer:'top',width:first.width},{route_type:'via',...point,from_layer:'top',to_layer:targetLayer,via_diameter:.4572,via_hole_diameter:.254}]
}
export function makePowerDropDogbone(source:any[],dx:number,dy:number,targetLayer:string,diagonalFirst=false){
 truncatePowerStubAtVia(source,.55,targetLayer)
 const magnitude=Math.abs(dy),h=dx-magnitude
 if(h<0||dx+(Math.SQRT2-1)*magnitude>1.524||Math.hypot(dx,dy)<.5302)throw Error('Dogbone exceeds launch budget or pad clearance')
 const start=source[0],sign=Math.sign(source.at(-1).x-start.x)
 if(!sign)throw Error('Requires horizontal outward source stub')
 const via={route_type:'via',x:start.x+sign*dx,y:start.y+dy,from_layer:'top',to_layer:targetLayer,via_diameter:.4572,via_hole_diameter:.254}
 const mid=diagonalFirst?{x:start.x+sign*magnitude,y:start.y+dy}:{x:start.x+sign*h,y:start.y}
 return [{...start},...(h>1e-8?[{route_type:'wire',...mid,layer:'top',width:start.width}]:[]),{route_type:'wire',x:via.x,y:via.y,layer:'top',width:start.width},via]
}
export function validatePowerDropResume(checkpoint:any,expected:Record<string,string>){
 if(!checkpoint||!Array.isArray(checkpoint.accepted)||!Array.isArray(checkpoint.planes)||!Array.isArray(checkpoint.dogboneSearch))throw Error('Incomplete power-drop checkpoint')
 for(const [key,value]of Object.entries(expected))if(checkpoint.provenance?.[key]!==value)throw Error(`Power-drop checkpoint provenance mismatch: ${key}`)
 if(new Set(checkpoint.accepted.map((r:any)=>r.pcb_trace_id)).size!==checkpoint.accepted.length)throw Error('Duplicate checkpoint replacement')
 return checkpoint
}
if(import.meta.main){
 const dir=resolve(process.argv[2]??''),out=resolve(process.argv[3]??`${dir}/power-drop-candidate`),{captureHash,snapshot}=verifyDdrCapture(dir),read=(name:string)=>JSON.parse(readFileSync(resolve(dir,name),'utf8'))
 const original=read('unrouted.circuit.json'),placements=read('capture-report.json').placements,rows=read('power-drop-audit.json').rows.filter((r:any)=>!r.localViaPresent)
 const replacements:any[]=[],changes:any[]=[],aliases:Record<string,string>={}
 for(const row of rows){
  const path=snapshot.pathsByByte[row.byte].find((p:any)=>p.connection===`U1.ball_${row.ball}`),placement=placements[row.byte],angle=placement.pcbRotation*Math.PI/180,first=path.route[0],xy={x:placement.pcbX+first.x*Math.cos(angle)-first.y*Math.sin(angle),y:placement.pcbY+first.x*Math.sin(angle)+first.y*Math.cos(angle)}
  const matches=original.filter((e:any)=>e.type==='pcb_trace'&&Math.hypot(e.route[0].x-xy.x,e.route[0].y-xy.y)<1e-6&&e.route[0].layer==='top')
  if(matches.length!==1)throw Error(`Source trace ambiguity ${row.byte}/${row.ball}: ${matches.length}`)
  const trace=matches[0],ground=row.terminal.startsWith('VSS'),targetLayer=ground?'inner1':'inner3',targetName=ground?`RAM${row.byte}_GND`:'VCC_DDR_1V5',targetNet=original.find((e:any)=>e.type==='source_net'&&e.name===targetName)?.source_net_id
  if(!targetNet||!trace.source_trace_id)throw Error('Missing intended explicit supply identity')
  aliases[trace.source_trace_id]=targetNet
  const distanceMm=4.2-Math.abs(first.x),route=truncatePowerStubAtVia(trace.route,distanceMm,targetLayer)
  replacements.push({...trace,connectsTo:trace.connectsTo?.filter((id:string)=>!id.startsWith('pcb_breakout_point_')),route,trace_length:distanceMm})
  changes.push({...row,pcbTraceId:trace.pcb_trace_id,sourceTraceId:trace.source_trace_id,targetName,targetNet,targetLayer,distanceMm,via:route.at(-1),retiredBreakoutIds:trace.connectsTo?.filter((id:string)=>id.startsWith('pcb_breakout_point_'))??[],removedOuterTailMm:row.lengthToViaOrUnterminatedEdgeMm-distanceMm})
 }
 // Explicit proposed support schematic joins, including existing local drops.
 // Equal supply labels alone are never used to relabel foreign physical copper.
 const supportConnections:any[]=[]
 for(const row of read('power-drop-audit.json').rows){
  const path=snapshot.pathsByByte[row.byte].find((p:any)=>p.connection===`U1.ball_${row.ball}`),placement=placements[row.byte],a=placement.pcbRotation*Math.PI/180,p=path.route[0],x=placement.pcbX+p.x*Math.cos(a)-p.y*Math.sin(a),y=placement.pcbY+p.x*Math.sin(a)+p.y*Math.cos(a)
  const pad=original.find((e:any)=>e.type==='pcb_smtpad'&&e.layer==='top'&&Math.hypot(e.x-x,e.y-y)<1e-6),port=original.find((e:any)=>e.type==='pcb_port'&&e.pcb_port_id===pad?.pcb_port_id)
  if(!port?.source_port_id)throw Error('Missing support source pad')
  const target=original.find((e:any)=>e.type==='source_net'&&e.name===(row.terminal.startsWith('VSS')?'GND':'VCC_DDR_1V5'))
  supportConnections.push({type:'source_trace',source_trace_id:`planned_ram${row.byte}_${row.ball}_supply`,connected_source_port_ids:[port.source_port_id],connected_source_net_ids:[target.source_net_id]})
 }
 const removedIds=new Set(replacements.map(r=>r.pcb_trace_id)),base=original.filter((e:any)=>!removedIds.has(e.pcb_trace_id)),dqs=(()=>{const available=['data-exact-width/candidate.physical.json','data-bounded-full-normalized/candidate.physical.json','data-d0-normalized/candidate.physical.json'].find(p=>existsSync(resolve(dir,p)));if(!available)return read('dqs-tuning-final/routes.json');const b=read(available);if(b.captureHash!==captureHash||b.layerSpace!=='physical')throw Error('Invalid reserved host bundle');return b.traces})(),report=verifyDdrSystemCopper(base,[...dqs,...replacements],read('merged-routing-input.json').connections,aliases)
 // Search only along the existing straight top launch. Reference-pour holes
 // are a separate required operation; omit pours for this conductor-only search.
 const accepted=new Map<string,any>(),search:any[]=[],planeRejections:any[]=[]
 const capDir=process.argv[4],capRaw=capDir?JSON.parse(readFileSync(resolve(capDir,'candidate.circuit.json'),'utf8')):[],idMap=new Map<string,string>()
 for(const e of capRaw)if(e[`${e.type}_id`])idMap.set(e[`${e.type}_id`],`power_plan_caps_${e[`${e.type}_id`]}`)
 const remap=(v:any):any=>Array.isArray(v)?v.map(remap):v&&typeof v==='object'?Object.fromEntries(Object.entries(v).map(([k,x])=>[k,remap(x)])):typeof v==='string'?idMap.get(v)??v:v
 const caps=remap(capRaw)
 const ground=original.find((e:any)=>e.type==='source_net'&&e.name==='GND').source_net_id,power=original.find((e:any)=>e.type==='source_net'&&e.name==='VCC_DDR_1V5').source_net_id
 for(const e of [...original,...caps])if(e.type==='source_net'&&['RAM0_GND','RAM1_GND','GND','DDR_1V5'].includes(e.name))aliases[e.source_net_id]=e.name==='DDR_1V5'?power:ground
 let planes=capDir?JSON.parse(readFileSync(resolve(capDir,'candidate-reference-planes.json'),'utf8')):original.filter((e:any)=>e.type==='pcb_copper_pour')
 const hash=(v:any)=>createHash('sha256').update(JSON.stringify(v)).digest('hex'),provenance={captureHash,hostTracesSha256:hash(dqs),capCircuitSha256:hash(capRaw),basePlanesSha256:hash(planes),searchContract:'outward-45-dogbone-v1'}
 let resume:any
 const checkpointPath=resolve(out,'dogbone-checkpoint.json')
 if(existsSync(checkpointPath)){
  resume=JSON.parse(readFileSync(checkpointPath,'utf8'))
  if(!resume.provenance){
   // Upgrade the previous write-only checkpoint only by matching its exact
   // copper to the independently verified accepted candidate and host hash.
   const previous=JSON.parse(readFileSync(resolve(out,'accepted-candidate.json'),'utf8')),previousReport=JSON.parse(readFileSync(resolve(out,'accepted.physical-report.json'),'utf8'))
   const geometry=(rs:any[])=>rs.map(r=>({id:r.pcb_trace_id,route:r.route})).sort((a,b)=>a.id.localeCompare(b.id))
   if(resume.captureHash!==captureHash||previous.captureHash!==captureHash||previous.reservedHostTracesSha256!==provenance.hostTracesSha256||previousReport.reservedHostTracesSha256!==provenance.hostTracesSha256||hash(geometry(resume.accepted))!==hash(geometry(previous.replacements)))throw Error('Legacy checkpoint lacks matching verified geometry/provenance')
   resume.provenance=provenance
   resume.dogboneSearch=resume.dogboneSearch.map((r:any,i:number)=>({...r,completed:!(resume.timeLimitReached&&i===resume.dogboneSearch.length-1)}))
  }
  validatePowerDropResume(resume,provenance)
  const seedIds=new Set(resume.accepted.map((r:any)=>r.pcb_trace_id)),seedBase=[...original.filter((e:any)=>e.type!=='pcb_copper_pour'&&!seedIds.has(e.pcb_trace_id)),...caps,...resume.planes,...supportConnections]
  const check=verifyDdrSystemCopper(seedBase,[...dqs,...resume.accepted],read('merged-routing-input.json').connections,aliases)
  if(check.errors.length||check.violations.length||!check.angles.valid||check.joinedBends.length)throw Error('Checkpoint fails current full copper verification')
  for(const r of resume.accepted)accepted.set(r.pcb_trace_id,r)
  planes=resume.planes
 }
 const withoutPlanes=[...base.filter((e:any)=>e.type!=='pcb_copper_pour'),...caps,...supportConnections]

 for(let step=0;step<=39;step++){
  const distanceMm=Math.min(1.524,.55+step*.025),candidates=replacements.filter(r=>!accepted.has(r.pcb_trace_id)).map(r=>({...r,route:truncatePowerStubAtVia(original.find((e:any)=>e.pcb_trace_id===r.pcb_trace_id).route,distanceMm,r.route.at(-1).to_layer)}))
  if(!candidates.length)break
  const audit=verifyDdrSystemCopper(withoutPlanes,[...dqs,...accepted.values(),...candidates],read('merged-routing-input.json').connections,aliases)
  const bad=new Set(audit.violations.flatMap((v:any)=>[v.aShape.traceId,v.bShape.traceId]))
  for(const r of candidates)if(!bad.has(r.pcb_trace_id)){
   try{const via=r.route.at(-1),p=planDdrGroundAntipads(planes,[{x:via.x,y:via.y,net:via.to_layer==='inner1'?'GND':'DDR_1V5'}]);planes=p.planes;accepted.set(r.pcb_trace_id,r)}catch(error){planeRejections.push({pcbTraceId:r.pcb_trace_id,distanceMm,error:String(error)})}
  }
  search.push({distanceMm,tested:candidates.length,acceptedTotal:accepted.size,violations:audit.violations.length})
 }
 const dogboneSearch:any[]=resume?.dogboneSearch??[],dogboneStarted=Date.now();let timeLimitReached=false
 mkdirSync(out,{recursive:true})
 for(const template of replacements.filter(r=>!accepted.has(r.pcb_trace_id)).sort((a,b)=>{const priority=resume?.searchPriority??[];return priority.indexOf(a.pcb_trace_id)-priority.indexOf(b.pcb_trace_id)})){
  const prior=dogboneSearch.find(r=>r.pcbTraceId===template.pcb_trace_id);if(prior?.completed)continue
  const source=original.find((e:any)=>e.pcb_trace_id===template.pcb_trace_id),start=source.route[0],end=source.route.at(-1),sign=Math.sign(end.x-start.x),trials:any[]=prior?.trials??[]
  searchDogbone:for(const dyMagnitude of [.2,.3,.4,.5,.6,.7,.8])for(const dySign of [-1,1])for(const dx of [.5,.6,.7,.8,.9,1,1.1,1.2])for(const diagonalFirst of [false,true]){
   if(Date.now()-dogboneStarted>60000){timeLimitReached=true;break searchDogbone}
   if(dx<dyMagnitude||dx+(Math.SQRT2-1)*dyMagnitude>1.524||Math.hypot(dx,dyMagnitude)<.5302)continue
   const dy=dyMagnitude*dySign
   if(trials.some(t=>t.dx===dx&&t.dy===dy&&t.diagonalFirst===diagonalFirst))continue
   const route=makePowerDropDogbone(source.route,dx,dy,template.route.at(-1).to_layer,diagonalFirst),via=route.at(-1)!
   const candidate={...template,route},currentBase=[...original.filter((e:any)=>e.type!=='pcb_copper_pour'&&!accepted.has(e.pcb_trace_id)&&e.pcb_trace_id!==template.pcb_trace_id),...caps,...supportConnections]
   const audit=verifyDdrSystemCopper(currentBase,[...dqs,...accepted.values(),candidate],read('merged-routing-input.json').connections,aliases)
   if(audit.errors.length||audit.violations.length||!audit.angles.valid||audit.joinedBends.length){trials.push({dx,dy,diagonalFirst,violations:audit.violations.filter((v:any)=>v.aShape.traceId===template.pcb_trace_id||v.bShape.traceId===template.pcb_trace_id).map((v:any)=>({aTraceId:v.aShape.traceId,bTraceId:v.bShape.traceId,aNet:v.aNet,bNet:v.bNet,layer:v.layer,gapMm:v.gapMm})).slice(0,3)});continue}
   try{const p=planDdrGroundAntipads(planes,[{x:via.x,y:via.y,net:via.to_layer==='inner1'?'GND':'DDR_1V5'}]);planes=p.planes;accepted.set(template.pcb_trace_id,candidate);trials.push({dx,dy,diagonalFirst,accepted:true});break searchDogbone}catch(error){trials.push({dx,dy,diagonalFirst,planeError:String(error)})}
  }
  const updated={pcbTraceId:template.pcb_trace_id,accepted:accepted.has(template.pcb_trace_id),trials,completed:!timeLimitReached};if(prior)dogboneSearch[dogboneSearch.indexOf(prior)]=updated;else dogboneSearch.push(updated)
  writeFileSync(`${out}/dogbone-checkpoint.json`,JSON.stringify({captureHash,provenance,accepted:[...accepted.values()],planes,dogboneSearch,searchPriority:resume?.searchPriority,previousHostSearch:resume?.previousHostSearch,timeLimitReached},null,2))
  if(timeLimitReached)break
 }
 const reservedHostTracesSha256=createHash('sha256').update(JSON.stringify(dqs)).digest('hex')
 const acceptedPaths=[...accepted.values()].map(r=>({...r,trace_length:r.route.reduce((sum:number,p:any,i:number)=>{const a=r.route[i-1];return sum+(a?.route_type==='wire'&&p.route_type==='wire'?Math.hypot(p.x-a.x,p.y-a.y):0)},0)})),rejected=changes.filter(c=>!accepted.has(c.pcbTraceId))
 const finalBase=[...original.filter((e:any)=>e.type!=='pcb_copper_pour'&&!accepted.has(e.pcb_trace_id)),...caps,...planes,...supportConnections],finalReport=verifyDdrSystemCopper(finalBase,[...dqs,...acceptedPaths],read('merged-routing-input.json').connections,aliases)

 mkdirSync(out,{recursive:true})
 writeFileSync(`${out}/straight-stub-search.json`,JSON.stringify({captureHash,scope:'Conductor-only search excludes reference pours pending antipads; not acceptance.',acceptedPaths,rejected,search,dogboneSearch,timeLimitReached,planeRejections},null,2))
 writeFileSync(`${out}/candidate.json`,JSON.stringify({captureHash,status:'unvalidated-power-drop-plan',scope:'Replaces specified outer RAM stubs with earlier through vias. Intended power/ground net assignments are explicit proposed aliases. Supply plane absent; no PDN continuity claim.',replacements,aliases,changes},null,2))
 writeFileSync(`${out}/physical-report.json`,JSON.stringify({captureHash,...report},null,2))
 writeFileSync(`${out}/accepted-reference-planes.json`,JSON.stringify(planes,null,2))
 writeFileSync(`${out}/accepted.physical-report.json`,JSON.stringify({captureHash,reservedHostTracesSha256,...finalReport},null,2))
 writeFileSync(`${out}/accepted-candidate.json`,JSON.stringify({captureHash,reservedHostTracesSha256,replacements:acceptedPaths,aliases,supportConnections,changes:changes.filter(c=>accepted.has(c.pcbTraceId)).map(c=>({...c,distanceMm:acceptedPaths.find(r=>r.pcb_trace_id===c.pcbTraceId).trace_length,via:acceptedPaths.find(r=>r.pcb_trace_id===c.pcbTraceId).route.at(-1),removedOuterTailMm:c.lengthToViaOrUnterminatedEdgeMm-acceptedPaths.find(r=>r.pcb_trace_id===c.pcbTraceId).trace_length})),rejected,planeRejections,scope:'Partial early RAM power drop candidate with local reference-plane antipads. Supply plane still absent; no PDN continuity claim.'},null,2))
 console.log(JSON.stringify({accepted:acceptedPaths.length,rejected:rejected.length,finalErrors:finalReport.errors.length,finalViolations:finalReport.violations.length,out,replacements:replacements.length,errors:report.errors.length,violations:report.violations.length,minClearanceMm:report.minClearanceMm}))
}