0hmX/mt41k512m8da-107-it-p-fanout
This code defines the physical footprint, routing, and layout configuration for a DDR3 memory module using surface-mount pads, vias, and copper traces on a printed circuit board (PCB).
- Version
- 0.2.3
- License
- unset
- Stars
- 0
scripts/generate.ts
import { PIN_MAP } from "../src/pin-map"
import { LAYOUT_PROFILES, PROFILE_LAYOUTS, RULES, BREAKOUT, getExitLayer } from "../src/profiles"
import { PAIRS, MATCH_GROUPS, TUNING, getEdgeY } from "../src/timing"
import { fanoutTracePath, type FanoutTracePath } from "@tscircuit/props"
const planarLength=(route:FanoutTracePath['route'])=>route.reduce((sum,b,i)=>{
const a=route[i-1]
return sum+(a?.route_type==='wire'&&b.route_type==='wire'&&a.layer===b.layer?Math.hypot(Number(a.x)-Number(b.x),Number(a.y)-Number(b.y)):0)
},0)
for (const profile of LAYOUT_PROFILES) {
const layout=PROFILE_LAYOUTS[profile]
const paths=PIN_MAP.filter(p=>p.routed).map(p=>{
const sign=Math.sign(p.x)
const dx=sign*(layout.xDirection==="outward"?1:-1)*0.4
const dy=layout.yDirection==="north"?0.4:-0.4
const x=p.x+dx,y=p.y+dy,layer=getExitLayer(p.ball)
const wire=(x:number,y:number)=>({route_type:'wire' as const,x,y,width:RULES.traceWidth,layer})
const route:FanoutTracePath['route']=layer==='top'?[wire(p.x,p.y),wire(sign*TUNING.topTuningStartX,p.y)]:[
{route_type:"wire",x:p.x,y:p.y,width:RULES.traceWidth,layer:"top"},
{route_type:"wire",x,y,width:RULES.traceWidth,layer:"top"},
{route_type:"via",x,y,from_layer:"top",to_layer:layer,via_diameter:RULES.viaDiameter,via_hole_diameter:RULES.viaDrill},
// Exit diagonally into the inter-row channel instead of a vertical/horizontal L.
wire(x,y),wire(x+sign*Math.abs(dy),p.y),
]
if(layer!=='top'&&Math.abs(Number(route.at(-1)!.x)-sign*TUNING.startX)>.001)route.push(wire(sign*TUNING.startX,p.y))
const edgeY=getEdgeY(p)
if(edgeY!==p.y) {
route.push(wire(sign*TUNING.pairConvergenceStartX,p.y))
route.push(wire(sign*(TUNING.pairConvergenceStartX+Math.abs(edgeY-p.y)),edgeY))
}
route.push(wire(sign*BREAKOUT.halfWidth,edgeY))
return fanoutTracePath.parse({connection:`U1.ball_${p.ball}`,route})
})
const pathFor=(terminal:string)=>paths.find(path=>path.connection===`U1.ball_${PIN_MAP.find(p=>p.terminal===terminal)!.ball}`)!
for(const group of Object.values(MATCH_GROUPS)) {
const target=Math.max(...group.map(n=>planarLength(pathFor(n).route)))
for(const terminal of group) {
// Keep paired tails straight and symmetric. Their nominal fabrication-coordinate
// rounding mismatch is below the explicit 0.005 mm acceptance threshold.
if(PAIRS.some(pair=>pair.includes(terminal))) continue
const path=pathFor(terminal),pin=PIN_MAP.find(p=>p.terminal===terminal)!
const extra=target-planarLength(path.route)
if(extra<1e-8)continue
const sign=Math.sign(pin.x),layer=getExitLayer(pin.ball)
const wire=(x:number,y:number)=>({route_type:'wire' as const,x:sign*x,y,width:RULES.traceWidth,layer})
// Fit broad tuning cells inside the 1 mm body padding.
// Top ODT/CKE use the vacant adjacent NC rows for vertical tuning room.
// A small correction gets one 45-degree trapezoid, never a train of
// sub-trace-width ripples. Larger corrections use 1–2 chamfered trombones.
const count=layer==='top'?2:Math.ceil(extra/.8),perCell=extra/count
const trapezoid=perCell<.25
const c=.12
const h=trapezoid?perCell/(2*(Math.SQRT2-1)):perCell/2+2*c*(2-Math.SQRT2)
if(h>(layer==='top'?1.1:.55)||count>2)throw Error(`Tuning corridor exceeded for ${terminal}: ${h}`)
const end=path.route.pop()!
for(let i=0;i<count;i++){
const a=(layer==='top'?TUNING.topTuningStartX:TUNING.startX)+.15+i*(layer==='top'?.72:.8),b=a+(layer==='top'?.6:.65),y=pin.y
if(trapezoid){
path.route.push(wire(a,y),wire(a+h,y+h),wire(b-h,y+h),wire(b,y))
}else{
path.route.push(wire(a,y),wire(a+c,y+c),wire(a+c,y+h-c),wire(a+2*c,y+h),wire(b-2*c,y+h),wire(b-c,y+h-c),wire(b-c,y+c),wire(b,y))
}
}
path.route.push(end)
if(Math.abs(planarLength(path.route)-target)>1e-7)throw Error(`Length tuning failed: ${terminal}`)
}
}
await Bun.write(`src/generated/${profile}.trace-paths.json`,JSON.stringify(paths,null,2)+"\n")
}