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/refresh-fixed-copper.ts

/** Refresh only fixed-copper polylines after a support-network geometry edit.
 * Refuses changed endpoints, widths, pads, vias, connectivity or saved escapes.
 * All four candidate fixtures must validate before any fixture is written. */
import assert from 'node:assert/strict'
import {readFileSync,writeFileSync,mkdirSync} from 'node:fs'
import {createHash} from 'node:crypto'
import {AM3352_PINS} from '../src/pin-map'
import {assertRouteAngles} from './check-route-angles'
import {validateDdrCopper} from './ddr-copper-check'
import {compactFanout} from './compact-fanout'
const profiles=['native','ddr_left','ddr_top','ddr_bottom'] as const
const layers=['top',...Array.from({length:8},(_,i)=>`inner${i+1}`),'bottom']
export const fixedDdrLayer=(layer:string):string=>{
 const target:Record<string,string>={top:'top',inner1:'inner1',inner2:'inner2',inner3:'inner4',inner4:'inner6',bottom:'bottom'}
 assert.ok(target[layer],`Unknown native physical layer ${layer}`)
 return target[layer]!
}
export function refreshFixedTraceRoute(old:any,fresh:any,ddr:boolean){
 const route=fresh.route.map(({start_pcb_port_id,end_pcb_port_id,...p}:any)=>{
  if(p.route_type==='wire')return {...p,layer:ddr?fixedDdrLayer(p.layer):p.layer}
  assert.equal(p.route_type,'via','Only wires/vias permitted')
  return {...p,from_layer:ddr?fixedDdrLayer(p.from_layer):p.from_layer,to_layer:ddr?fixedDdrLayer(p.to_layer):p.to_layer,...(ddr?{layers}: {})}
 })
 const signature=(p:any)=>({type:p.route_type,x:Number(p.x),y:Number(p.y),layer:p.layer,width:p.width,from_layer:p.from_layer,to_layer:p.to_layer,via_diameter:p.via_diameter,via_hole_diameter:p.via_hole_diameter})
 assert.deepEqual(signature(route[0]),signature(old.route[0]),`${old.pcb_trace_id}: first terminal changed`)
 assert.deepEqual(signature(route.at(-1)),signature(old.route.at(-1)),`${old.pcb_trace_id}: last terminal changed`)
 assert.deepEqual(route.filter((p:any)=>p.route_type==='via').map(signature),old.route.filter((p:any)=>p.route_type==='via').map(signature),`${old.pcb_trace_id}: physical transition changed`)
 const wireClasses=(r:any[])=>[...new Set(r.filter(p=>p.route_type==='wire').map(p=>`${p.layer}/${p.width}`))].sort()
 assert.deepEqual(wireClasses(route),wireClasses(old.route),`${old.pcb_trace_id}: changed wire layer/width`)
 return {...old,route}
}
if(import.meta.main){
 const placed=JSON.parse(readFileSync(process.argv.slice(2).find(a=>!a.startsWith('--'))??'dist/six-layer-placed.circuit.json','utf8'))
 const fresh=placed.filter((e:any)=>e.type==='pcb_trace')
 assert.equal(fresh.length,173,'Expected exactly the established fixed support traces')
 assertRouteAngles(fresh)
 const drafts:any[]=[],report:any[]=[]
 for(const profile of profiles){
  const filename=`scripts/fixtures/${profile}.input.json`,fixture=JSON.parse(readFileSync(filename,'utf8')),input=fixture.input
  const cachefile=`src/generated/${profile}.trace-paths.json`,cacheText=readFileSync(cachefile,'utf8'),saved=JSON.parse(cacheText)
  assert.equal(input.traces.length,173)
  const oldInput=structuredClone(input)
  const renderedVias=placed.filter((e:any)=>e.type==='pcb_via')
  for(const o of input.obstacles.filter((o:any)=>o.circuitJsonMetadata?.pcb_via_id)){
   const id=o.circuitJsonMetadata.pcb_via_id.replace(/^fixed_/,'')
   const via=renderedVias.find((v:any)=>v.pcb_via_id===id)
   assert.ok(via,`Missing fixed via ${id}`)
   assert.ok(Math.hypot(o.center.x-via.x,o.center.y-via.y)<1e-7&&Math.abs(o.width-via.outer_diameter)<1e-7&&Math.abs(o.height-via.outer_diameter)<1e-7,`${profile}: fixed via geometry changed`)
  }
  for(const o of input.obstacles.filter((o:any)=>/^fixed_C\d+_pad[01]$/.test(o.circuitJsonMetadata?.pcb_smtpad_id??''))){
   const [,name,index]=o.circuitJsonMetadata.pcb_smtpad_id.match(/^fixed_(C\d+)_pad([01])$/)
   const sc=placed.find((e:any)=>e.type==='source_component'&&e.name===name)
   const pc=placed.find((e:any)=>e.type==='pcb_component'&&e.source_component_id===sc.source_component_id)
   const pad=placed.filter((e:any)=>e.type==='pcb_smtpad'&&e.pcb_component_id===pc.pcb_component_id)[Number(index)]
   assert.ok(pad&&Math.hypot(o.center.x-pad.x,o.center.y-pad.y)<1e-7&&Math.abs(o.width-pad.width)<1e-7&&Math.abs(o.height-pad.height)<1e-7,`${profile}: fixed pad ${name}/${index} changed`)
  }
  let changed=0
  input.traces=input.traces.map((old:any)=>{
   const matches=fresh.filter((t:any)=>`fixed_${t.pcb_trace_id}`===old.pcb_trace_id)
   assert.equal(matches.length,1,`Missing or duplicated ${old.pcb_trace_id}`)
   const next=refreshFixedTraceRoute(old,matches[0],profile!=='native')
   if(JSON.stringify(next.route)!==JSON.stringify(old.route))changed++
   return next
  })
  const fixedAngles=assertRouteAngles(input.traces),savedAngles=assertRouteAngles(saved)
  const traces=saved.map((p:any)=>{
   const pin=AM3352_PINS.find(pin=>p.connection===`U1.${pin.ballName}`)!
   const connection=input.connections.find((c:any)=>c.pointsToConnect.some((q:any)=>Math.hypot(q.x-pin.x,q.y-pin.y)<1e-7))
   assert.ok(connection,`Missing cached connection ${p.connection}`)
   return {type:'pcb_trace',pcb_trace_id:`cache:${p.connection}`,connection_name:connection.name,route:p.route,pin}
  })
  const {drc,ddrDrc}=validateDdrCopper(input,traces,traces.filter((t:any)=>t.pin.name.startsWith('DDR_')))
  assert.ok(drc.valid,`${profile}: complete copper DRC failed ${JSON.stringify(drc.issues)}`)
  if(profile!=='native'){
   assert.ok(ddrDrc.valid,`${profile}: strict DDR DRC failed ${JSON.stringify(ddrDrc.issues)}`)
   assert.deepEqual(compactFanout(input,traces,input.bounds).bounds,input.bounds,'Fixed edit changed tight enclosure')
  }
  assert.deepEqual({...input,traces:oldInput.traces},oldInput,'Only fixed route geometry may change')
  drafts.push({filename,fixture,cachefile,cacheText})
  report.push({profile,fixedTraceCount:input.traces.length,changedFixedRoutes:changed,savedPathCount:saved.length,savedSha256:createHash('sha256').update(cacheText).digest('hex'),fixedAngles,savedAngles,allCopperDrc:drc,strictDdrDrc:profile==='native'?'not-applicable-to-native-legacy-geometry':ddrDrc})
 }
 // Validation above is deliberately complete before any mutation.
 for(const d of drafts)assert.equal(readFileSync(d.cachefile,'utf8'),d.cacheText,'Saved cache changed during validation')
 if(process.argv.includes('--write'))for(const d of drafts)writeFileSync(d.filename,JSON.stringify(d.fixture,null,2)+'\n')
 mkdirSync('dist',{recursive:true})
 writeFileSync('dist/fixed-copper-refresh.json',JSON.stringify({status:process.argv.includes('--write')?'updated-and-validated':'validated-dry-run',report},null,2)+'\n')
 console.log(JSON.stringify(report.map(r=>({profile:r.profile,changedFixedRoutes:r.changedFixedRoutes,fixedTraceCount:r.fixedTraceCount,savedPathCount:r.savedPathCount}))))
}