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/tune-ddr-dqs.ts

import{readFileSync,writeFileSync,mkdirSync}from'node:fs'
import{createHash}from'node:crypto'
import{resolve}from'node:path'
import{pathToFileURL}from'node:url'
import{verifyDdrSystemCopper}from'./check-ddr-system-copper'
type P={x:number;y:number}
/** Exact miter offsets of an octilinear centerline; no corner-rounding or
 * independently tuned pair members. Endpoint headings must be westward. */
export function offsetDqsCenterline(points:P[],offset:number):P[]{
 const normals=points.slice(1).map((b,i)=>{const a=points[i]!,l=Math.hypot(b.x-a.x,b.y-a.y);if(l<1e-8)throw Error('Zero length centerline segment');return{x:-(b.y-a.y)/l,y:(b.x-a.x)/l}})
 return points.map((p,i)=>{const a=normals[Math.max(0,i-1)]!,b=normals[Math.min(i,normals.length-1)]!,den=1+a.x*b.x+a.y*b.y;if(den<1e-6)throw Error('Reversing centerline');return{x:p.x+offset*(a.x+b.x)/den,y:p.y+offset*(a.y+b.y)/den}})
}
export function createTunedDqsPair(connections:any[],excursionY:number,entryX=1.5,exitX=-4.8,bevel=.2,diagonalReturn=false){
 const sorted=connections.map(c=>({...c,pointsToConnect:[...c.pointsToConnect].sort((a,b)=>a.x-b.x)}))
 const cpuY=sorted.reduce((s,c)=>s+c.pointsToConnect[1].y,0)/2,ramY=sorted.reduce((s,c)=>s+c.pointsToConnect[0].y,0)/2
 const direction=Math.sign(excursionY-cpuY),returnDirection=Math.sign(ramY-excursionY)
 if(direction===returnDirection||!direction||Math.abs(excursionY-ramY)<bevel*2||Math.abs(excursionY-cpuY)<bevel*2)throw Error('Excursion must extend beyond both endpoint rows')
 const cpu=sorted[0].pointsToConnect[1],ram=sorted[0].pointsToConnect[0]
 let center=[{x:cpu.x-.16,y:cpuY},{x:entryX+bevel,y:cpuY},{x:entryX,y:cpuY+direction*bevel},{x:entryX,y:excursionY-direction*bevel},{x:entryX-bevel,y:excursionY},{x:exitX+bevel,y:excursionY},{x:exitX,y:excursionY+returnDirection*bevel},{x:exitX,y:ramY-returnDirection*bevel},{x:exitX-bevel,y:ramY},{x:ram.x,y:ramY}]
 if(diagonalReturn){const finishX=ram.x+.25;center=[...center.slice(0,5),{x:finishX+Math.abs(ramY-excursionY),y:excursionY},{x:finishX,y:ramY},{x:ram.x,y:ramY}]}
 return sorted.map((c,i)=>{const cp=c.pointsToConnect[1],rp=c.pointsToConnect[0],p=i===0?1:-1,points=offsetDqsCenterline(center,p*.16);if(Math.hypot(points.at(-1)!.x-rp.x,points.at(-1)!.y-rp.y)>1e-6)throw Error('Pair polarity does not match template');return{type:'pcb_trace',pcb_trace_id:`tuned_dqs${c.ramByte}_${i}`,connection_name:c.name,source_trace_id:c.source_trace_id,route:[cp,{x:cp.x-.12,y:cp.y},...points].map(q=>({route_type:'wire',x:q.x,y:q.y,layer:cp.layer,width:.12}))}})
}
/** Fit the existing diagonal-return template, retaining its pair geometry. */
export function fitDqsPairMean(connections:any[],escapeLengths:number[],targetMm:number,entryX=1.9,initialExcursionY=-16.35){
 if(connections.length!==2||escapeLengths.length!==2||![targetMm,...escapeLengths].every(Number.isFinite)||targetMm<=0||escapeLengths.some(n=>n<0))throw Error('Invalid pair length budget')
 const planar=(route:any[])=>route.slice(1).reduce((n:number,p:any,i:number)=>n+Math.hypot(p.x-route[i].x,p.y-route[i].y),0)
 const lengths=(traces:any[])=>traces.map((t,i)=>planar(t.route)+escapeLengths[i]!)
 if(!Number.isFinite(initialExcursionY))throw Error('Invalid initial excursion')
 let excursionY=initialExcursionY
 const initial=createTunedDqsPair(connections,excursionY,entryX,-4.8,.2,true),mean=lengths(initial).reduce((a,b)=>a+b,0)/2,probeMean=lengths(createTunedDqsPair(connections,excursionY+.01,entryX,-4.8,.2,true)).reduce((a,b)=>a+b,0)/2,slope=(probeMean-mean)/.01
 if(!Number.isFinite(slope)||Math.abs(slope)<.1)throw Error('Unstable template slope')
 excursionY+=(targetMm-mean)/slope
 const traces=createTunedDqsPair(connections,excursionY,entryX,-4.8,.2,true),lengthsMm=lengths(traces),meanMm=lengthsMm.reduce((a,b)=>a+b,0)/2
 if(Math.abs(meanMm-targetMm)>1e-6)throw Error('Requested length crosses template topology boundary')
 return{traces,excursionY,lengthsMm,meanMm}
}
if(import.meta.main){
 const[captureArg,ramArg,outArg]=process.argv.slice(2);if(!captureArg||!ramArg)throw Error('Usage: tune-ddr-dqs.ts capture-directory RAM-directory [output-directory]')
 const capture=resolve(captureArg),out=resolve(outArg??`${capture}/dqs-tuning`),read=(n:string)=>JSON.parse(readFileSync(`${capture}/${n}`,'utf8'))
 const input=read('forced-trunk-routing-input.json'),fixed=read('unrouted.circuit.json'),{verifyHostDqsPair}=await import(pathToFileURL(resolve(ramArg,'scripts/audit-am3352-dqs-pair.ts')).href)
 mkdirSync(out,{recursive:true});const results:any[]=[],accepted:any[]=[]
 for(const byte of[0,1]){
  const connections=input.connections.filter((c:any)=>c.ramByte===byte&&['DQS_P','DQS_N'].includes(c.ramTerminal)),target=byte===0?34:31
  const base=byte===0?-16.35:9.6,attempts:any[]=[]
  search:for(const sign of[1,-1])for(const entryX of[1.9,1.7,1.5])for(const exitX of[-4.8]){
   const excursionY=sign*base
   let routes:any[];try{routes=createTunedDqsPair(connections,excursionY,entryX,exitX,.2,true)}catch{continue}
   const pair=verifyHostDqsPair({...input,connections},fixed,routes)
   const physical=verifyDdrSystemCopper(fixed,[...accepted,...routes],input.connections)
   const meetsTarget=pair.metrics.every((m:any)=>m.fullLengthMm>=target)
   const geometryPass=pair.status==='pair-geometry-pass'&&!physical.errors.length&&!physical.violations.length&&physical.angles.valid&&!physical.joinedBends.length&&physical.connectivity.filter(c=>connections.some((s:any)=>s.name===c.name)).every(c=>c.connected)
   const result={byte,excursionY,entryX,exitX,target,meetsTarget,geometryPass,pair,physical:{errors:physical.errors,violations:physical.violations,angles:physical.angles,joinedBends:physical.joinedBends,connectivity:physical.connectivity,minClearanceMm:physical.minClearanceMm}}
   attempts.push(result)
   if(geometryPass&&meetsTarget){accepted.push(...routes);writeFileSync(`${out}/byte${byte}.routes.json`,JSON.stringify(routes,null,2));break search}
  }
  results.push({byte,status:accepted.some(r=>r.pcb_trace_id.startsWith(`tuned_dqs${byte}_`))?'geometry-candidate':'no-accepted-candidate',attempts})
 }
 const bounds=accepted.length?{minX:Math.min(...accepted.flatMap(r=>r.route.map((p:any)=>p.x-.06))),maxX:Math.max(...accepted.flatMap(r=>r.route.map((p:any)=>p.x+.06))),minY:Math.min(...accepted.flatMap(r=>r.route.map((p:any)=>p.y-.06))),maxY:Math.max(...accepted.flatMap(r=>r.route.map((p:any)=>p.y+.06)))}:null
 const board=fixed.find((e:any)=>e.type==='pcb_board'),margin=board?.min_board_edge_clearance??.2
 if(bounds&&(!board||bounds.minX<board.center.x-board.width/2+margin||bounds.maxX>board.center.x+board.width/2-margin||bounds.minY<board.center.y-board.height/2+margin||bounds.maxY>board.center.y+board.height/2-margin))throw Error('Tuned copper exceeds captured board clearance envelope')
 const hashes={input:createHash('sha256').update(readFileSync(`${capture}/forced-trunk-routing-input.json`)).digest('hex'),fixed:createHash('sha256').update(readFileSync(`${capture}/unrouted.circuit.json`)).digest('hex')}
 writeFileSync(`${out}/report.json`,JSON.stringify({status:accepted.length===4?'both-tuned-pairs-geometry-pass':'incomplete-pair-tuning',hashes,copperBounds:bounds,boardMarginMm:margin,scope:'Candidate pair tuning only; not full byte timing or complete board routing.',results},null,2))
 writeFileSync(`${out}/routes.json`,JSON.stringify(accepted,null,2))
 const {captureHash}=read('provenance.json')
 writeFileSync(`${out}/candidate.physical.json`,JSON.stringify({captureHash,layerSpace:'physical',traces:accepted},null,2))
 writeFileSync(`${out}/physical-report.json`,JSON.stringify({captureHash,...verifyDdrSystemCopper(fixed,accepted,read('merged-routing-input.json').connections)},null,2))
 console.log(JSON.stringify(results.map(r=>({byte:r.byte,status:r.status,attempts:r.attempts.length,firstIssues:r.attempts[0]?.pair.issues.slice(-4)}))))
}