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/compact-fanout.ts

import assert from 'node:assert/strict'
export interface FanoutBounds { minX:number; maxX:number; minY:number; maxY:number }
type Side = 'left'|'right'|'top'|'bottom'
/** Remove redundant collinear tail waypoints before fitting the exit rectangle. */
export function simplifyExitTail(route:any[]) {
  const result=structuredClone(route)
  while(result.length>2){
    const a=result.at(-3),b=result.at(-2),c=result.at(-1)
    if(a.route_type!=='wire'||b.route_type!=='wire'||c.route_type!=='wire'||a.layer!==b.layer||b.layer!==c.layer)break
    const cross=(b.x-a.x)*(c.y-b.y)-(b.y-a.y)*(c.x-b.x)
    const dot=(b.x-a.x)*(c.x-b.x)+(b.y-a.y)*(c.y-b.y)
    if(Math.abs(cross)>1e-7||dot < -1e-7)break
    result.splice(result.length-2,1)
  }
  return result
}
/** Tight CPU-centered rectangle for existing copper on a 0.01 mm outward grid.
 * Only final outward trace tails are shortened. Internal copper remains fixed.
 * Normal-direction terminal end caps intentionally join host copper at the boundary.
 */
export function compactFanout(input:any,traces:any[],oldBounds:FanoutBounds,margin=.1016){
  const bounds:FanoutBounds={minX:-7.5,maxX:7.5,minY:-7.5,maxY:7.5}
  const include=(x:number,y:number,rx:number,ry=rx)=>{
    bounds.minX=Math.min(bounds.minX,x-rx-margin);bounds.maxX=Math.max(bounds.maxX,x+rx+margin)
    bounds.minY=Math.min(bounds.minY,y-ry-margin);bounds.maxY=Math.max(bounds.maxY,y+ry+margin)
  }
  for(const o of input.obstacles)include(o.center.x,o.center.y,o.width/2,o.height/2)
  for(const t of input.traces??[])for(const q of t.route)include(q.x,q.y,q.route_type==='via'?q.via_diameter/2:q.width/2)
  const sides=new Map<string,Side>()
  const compacted=traces.map(t=>{
    const end=t.route.at(-1)
    const matches:Side[]=[]
    if(end.route_type==='wire'){
      if(Math.abs(end.x-oldBounds.minX)<1e-6)matches.push('left')
      if(Math.abs(end.x-oldBounds.maxX)<1e-6)matches.push('right')
      if(Math.abs(end.y-oldBounds.minY)<1e-6)matches.push('bottom')
      if(Math.abs(end.y-oldBounds.maxY)<1e-6)matches.push('top')
    }
    assert.ok(matches.length<=1,`Ambiguous old exit ${t.connection_name}`)
    const side=matches[0]
    const route=side?simplifyExitTail(t.route):structuredClone(t.route)
    if(side)sides.set(t.connection_name,side)
    const core=side?route.slice(0,-1):route
    for(const q of core)include(q.x,q.y,q.route_type==='via'?q.via_diameter/2:q.width/2)
    if(side){ // Endpoint's transverse copper also belongs inside the region.
      if(side==='left'||side==='right'){
        bounds.minY=Math.min(bounds.minY,end.y-end.width/2-margin);bounds.maxY=Math.max(bounds.maxY,end.y+end.width/2+margin)
      }else{
        bounds.minX=Math.min(bounds.minX,end.x-end.width/2-margin);bounds.maxX=Math.max(bounds.maxX,end.x+end.width/2+margin)
      }
    }
    return {...t,route}
  })
  bounds.minX=Math.floor((bounds.minX+1e-9)*100)/100;bounds.minY=Math.floor((bounds.minY+1e-9)*100)/100
  bounds.maxX=Math.ceil((bounds.maxX-1e-9)*100)/100;bounds.maxY=Math.ceil((bounds.maxY-1e-9)*100)/100
  for(const t of compacted){
    const side=sides.get(t.connection_name);if(!side)continue
    const end=t.route.at(-1),before=t.route.at(-2)
    assert.equal(before.route_type,'wire')
    assert.equal(end.layer,before.layer)
    if(side==='left'||side==='right'){
      assert.ok(Math.abs(end.y-before.y)<1e-6,`${t.connection_name}: exit tail is not horizontal`)
      end.x=side==='left'?bounds.minX:bounds.maxX
      assert.ok(side==='left'?end.x<before.x:end.x>before.x)
    }else{
      assert.ok(Math.abs(end.x-before.x)<1e-6,`${t.connection_name}: exit tail is not vertical`)
      end.y=side==='bottom'?bounds.minY:bounds.maxY
      assert.ok(side==='bottom'?end.y<before.y:end.y>before.y)
    }
  }
  return {bounds,traces:compacted,exitSides:Object.fromEntries(sides),marginMm:margin,gridMm:.01}
}