seveibar/am3352-ram-dogbone-and-single-layer-route-test

AM3352BZCZ100 processor connected to a W631GG6MB-12 DDR3 memory chip through a routed, length-matched 16-bit DDR interface with clock/data-strobe pairs and control/address signals.

Version
0.0.9
License
unset
Stars
0

routing/round.ts

import {length,segmentDist} from './single-layer'
type P={x:number,y:number}
const dist=(a:P,b:P)=>Math.hypot(a.x-b.x,a.y-b.y)
const turn=(a:P,b:P,c:P)=>Math.atan2((b.x-a.x)*(c.y-b.y)-(b.y-a.y)*(c.x-b.x),(b.x-a.x)*(c.x-b.x)+(b.y-a.y)*(c.y-b.y))
const violation=(a:P,b:P,c:P,d:P,margin:number)=>{
 if(Math.max(a.x,b.x)+margin<Math.min(c.x,d.x)||Math.max(c.x,d.x)+margin<Math.min(a.x,b.x)||Math.max(a.y,b.y)+margin<Math.min(c.y,d.y)||Math.max(c.y,d.y)+margin<Math.min(a.y,b.y))return false
 return segmentDist(a,b,c,d)<margin-0.00005
}

function arc(a:P,b:P,c:P,trim:number){
 const da=dist(a,b),dc=dist(b,c),u={x:(b.x-a.x)/da,y:(b.y-a.y)/da},v={x:(c.x-b.x)/dc,y:(c.y-b.y)/dc},theta=turn(a,b,c),sign=Math.sign(theta),radius=trim/Math.tan(Math.abs(theta)/2)
 const start={x:b.x-trim*u.x,y:b.y-trim*u.y},end={x:b.x+trim*v.x,y:b.y+trim*v.y},center={x:start.x-u.y*radius*sign,y:start.y+u.x*radius*sign},angle=Math.atan2(start.y-center.y,start.x-center.x),n=Math.max(2,Math.ceil(Math.abs(theta)/(5*Math.PI/180)))
 return Array.from({length:n+1},(_,i)=>i===0?start:i===n?end:{x:center.x+radius*Math.cos(angle+theta*i/n),y:center.y+radius*Math.sin(angle+theta*i/n)})
}
export function roundTraces(traces:any[],input:any){
 const output=structuredClone(traces),routes=output.map((t:any)=>({name:t.source_trace_id,layer:t.route.find((p:any)=>p.route_type==='wire'&&p.layer!=='top').layer,points:t.route.filter((p:any)=>p.route_type==='wire'&&p.layer!=='top').map((p:any)=>({x:p.x,y:p.y}))}))
 const vias=output.flatMap((t:any)=>t.route.filter((p:any)=>p.route_type==='via').map((p:any)=>({...p,net:t.source_trace_id})))
 const plans:any[]=[];let blocked:any[]=[]
 for(const r of routes){const original=r.points, trims=new Array(original.length).fill(0);let loss=0
  for(let i=1;i<original.length-1;i++){
   const a=original[i-1],b=original[i],c=original[i+1],angle=Math.abs(turn(a,b,c));if(angle<5*Math.PI/180)continue
   const limit=Math.min(.5,dist(a,b)*.45,dist(b,c)*.45);let accepted=false
   for(const scale of [1,.7,.4,.2,.1,.04,.01,.001]){
    const trim=limit*scale;if(trim<.000001)break;const ps=arc(a,b,c,trim);let valid=true
    for(let k=1;k<ps.length&&valid;k++){
     for(const v of vias)if(v.net!==r.name&&violation(ps[k-1],ps[k],v,v,.3)){valid=false;break}
     for(const other of routes)if(other!==r&&other.layer===r.layer)for(let j=1;j<other.points.length;j++)if(violation(ps[k-1],ps[k],other.points[j-1],other.points[j],.2)){valid=false;break}
    }
    if(valid){trims[i]=trim;loss+=2*trim-length(ps);accepted=true;break}
   }
   if(!accepted)blocked.push({net:r.name,index:i,angle:angle*180/Math.PI,limit})
  }
  plans.push({r,trims,loss,desiredLoss:loss,originalLength:length(r.points)})
 }
 for(const group of (input.buses??[])){
  const members=plans.filter(p=>group.connectionNames.includes(p.r.name));if(members.length<2)continue
  const longestMinimum=Math.max(...members.map(p=>p.originalLength-p.loss)),floor=longestMinimum-(group.maxLengthSkew??.635)*.8
  for(const p of members)p.desiredLoss=Math.min(p.loss,Math.max(0,p.originalLength-floor))
 }
 for(const group of (input.differentialPairs??[])){
  const members=plans.filter(p=>group.connectionNames.includes(p.r.name));if(members.length<2)continue
  const floor=Math.max(...members.map(p=>p.originalLength-p.desiredLoss))-(group.lengthTolerance??.127)*.6
  for(const p of members)p.desiredLoss=Math.min(p.desiredLoss,Math.max(0,p.originalLength-floor))
 }
 if(blocked.length)throw new Error(`Cannot round ${blocked.length} corners without violating clearance`)
 const stats:any[]=[]
 for(const plan of plans){const {r,trims,loss,desiredLoss}=plan,ps=r.points,result:P[]=[ps[0]],scale=loss>0?desiredLoss/loss:1;let minRadius=Infinity
  for(let i=1;i<ps.length-1;i++){if(trims[i]){const trim=trims[i]*scale;result.push(...arc(ps[i-1],ps[i],ps[i+1],trim));minRadius=Math.min(minRadius,trim/Math.tan(Math.abs(turn(ps[i-1],ps[i],ps[i+1]))/2))}else result.push(ps[i])}
  result.push(ps.at(-1)!);stats.push({name:r.name,availableLoss:loss,loss:desiredLoss,scale,minRadius});r.points=result
 }
 output.forEach((t:any,i:number)=>{const first=t.route.findIndex((p:any)=>p.route_type==='via'),last=t.route.findLastIndex((p:any)=>p.route_type==='via');t.route=[...t.route.slice(0,first+1),...routes[i].points.map((p:P)=>({route_type:'wire',...p,layer:routes[i].layer,width:.1})),...t.route.slice(last)]})
 for(const r of routes)for(let i=1;i<r.points.length-1;i++)if(Math.abs(turn(r.points[i-1],r.points[i],r.points[i+1]))>5.001*Math.PI/180)throw new Error(`Unrounded corner on ${r.name}`)
 const report={blocked,stats};console.log('ROUNDING_REPORT '+JSON.stringify(report));return{traces:output,report}
}