seveibar/am3352-dev-board

A 324-ball TI AM3352 ARM processor with DDR3, LCD, USB, Ethernet, storage, serial, analog, clock, reset, power, and JTAG interfaces, paired with configurable JST-GH board connectors and constrained PCB routing.

Version
1.0.2
License
unset
Stars
0

scripts/force-bga-escape.py

"""Reserve clear BGA escape vias, locally removing obstructing signal traces for rerouting."""
import json,runpy,math
from pathlib import Path
from shapely.geometry import Point,LineString
from shapely.ops import unary_union
from shapely.prepared import prep
x=runpy.run_path('scripts/audit-copper.py');c=x['c'];data=json.loads(Path('design/routed-copper.json').read_text());meta=x['meta'];shapes=x['shapes'];sc={p['source_component_id']:p['name'] for p in c if p['type']=='source_component'};sp={p['source_port_id']:p for p in c if p['type']=='source_port'}
removed=set();extras=[];added=0;fail=[];planes=['GND','V1V8','V3V3','VDD_CORE','VDD_MPU','DDR_1V5','A3V3']
hardvia=prep(unary_union([g for g,m in zip(shapes,meta) if m[3] or m[2].endswith('_via')]).buffer(.25))
for net,ps in x['netports'].items():
 name=x['netnames'].get(net,net)
 if name in planes:continue
 groups={x['component'](x['padnodes'][p][0]) for p in ps}
 if len(groups)<2:continue
 for p in set(ps):
  port=x['ports'][p]
  if sc[sp[port['source_port_id']]['source_component_id']]not in ['U1','U3']:continue
  k=x['component'](x['padnodes'][p][0]);vs=[i for i,m in enumerate(meta) if m[2].endswith('_via') and x['component'](i)==k]
  if vs:continue
  px,py=port['x'],port['y'];hardtop=prep(unary_union([g for g,m in zip(shapes,meta) if m[0]=='top' and m[1]!=net and (m[3] or m[2].endswith('_via'))]+[g for g,n,l in extras if n!=net and l=='top']).buffer(.15));best=None
  for dx in sorted({a+b for a in [-1.2,-.4,.4,1.2] for b in [-.1,-.05,0,.05,.1]},key=abs):
   for dy in sorted({a+b for a in [-1.2,-.4,.4,1.2] for b in [-.1,-.05,0,.05,.1]},key=abs):
    vx,vy=round(px+dx,6),round(py+dy,6);v=Point(vx,vy)
    if hardvia.intersects(v) or any(g.distance(v)<.25 for g,n,l in extras):continue
    t=min(abs(dx),abs(dy));sx=1 if dx>0 else -1;sy=1 if dy>0 else -1
    for pts in [[(px,py),(px+sx*t,py+sy*t),(vx,vy)],[(px,py),(vx-sx*t,vy-sy*t),(vx,vy)]]:
     line=LineString(pts)
     if hardtop.intersects(line):continue
     blocked=set();cost=0
     for g,m in zip(shapes,meta):
      if m[1]==net or m[3] or m[2].endswith('_via') or m[2].startswith('pcb_copper_pour') or m[2] in removed:continue
      if g.distance(v)<.25 or (m[0]=='top' and g.distance(line)<.15):
       if m[2] not in blocked:cost+=10 if m[0]=='top' else 1
       blocked.add(m[2])
     score=(cost,len(blocked),line.length)
     if best is None or score<best[0]:best=(score,vx,vy,pts,blocked)
  if best is None:fail.append(name);print('No pad-safe escape for',name,flush=True);continue
  _,vx,vy,pts,blocked=best;removed.update(blocked);sid=next(t['source_net_id'] for t in c if t['type']=='source_net' and x['find'](t['source_net_id'])==net);clean=[pts[0]]
  for q in pts[1:]:
   if math.dist(q,clean[-1])>1e-8:clean.append(q)
  data['nets'].setdefault(sid,[]).extend([[{'route_type':'wire','x':a,'y':b,'width':.1,'layer':'top'} for a,b in clean],[{'route_type':'wire','x':vx,'y':vy,'width':.1,'layer':'top'},{'route_type':'via','x':vx,'y':vy,'from_layer':'top','to_layer':'bottom','via_diameter':.3,'via_hole_diameter':.15},{'route_type':'wire','x':vx,'y':vy,'width':.1,'layer':'bottom'}]])
  data['sourceTraceIds'][sid]=next(t['source_trace_id'] for t in c if t['type']=='source_trace' and sid in t.get('connected_source_net_ids',[]));extras.append((LineString(clean).buffer(.05),net,'top'));extras.extend((Point(vx,vy).buffer(.15),net,l) for l in x['layers']);added+=1;print('Escaped',name,'via',vx,vy,'reroute',len(blocked),'trace records',flush=True)
# Original route IDs match their saved-data indexes; appended escape records have larger indexes.
for sid,rs in data['nets'].items():data['nets'][sid]=[r for i,r in enumerate(rs) if f'fr_{sid}_{i}' not in removed]
Path('design/routed-copper.json').write_text(json.dumps(data,separators=(',',':'))+'\n');Path('design/bga-escape-report.json').write_text(json.dumps({'escaped':added,'removedTraces':list(removed),'remaining':fail},indent=2));print('Reserved',added,'escapes;',len(removed),'trace fragments need rerouting;',len(fail),'blocked')