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/assign-ddr-escapes.py

"""Assign BGA DDR fanout cells globally before routing the interconnect."""
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'];d=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'}
sids={p['source_net_id'] for p in c if p['type']=='source_net' and p['name'].startswith('DDR_') and p['name']!='DDR_1V5'};nets={x['find'](s) for s in sids};byroot={x['find'](s):s for s in sids}
hardVia=prep(unary_union([g for g,m in zip(shapes,meta) if m[3] or (m[2].endswith('_via') and m[1] not in nets)]).buffer(.249999))
hardTop=prep(unary_union([g for g,m in zip(shapes,meta) if m[0]=='top' and m[1] not in nets and not m[2].startswith('pcb_copper_pour')]).buffer(.149999))
requests=[];choices={};fixed=[(g,m) for g,m in zip(shapes,meta) if m[1] not in nets and not m[3] and not m[2].endswith('_via') and not m[2].startswith('pcb_copper_pour')]
for port in x['ports'].values():
 net=x['find'](port['source_port_id']);ref=sc[sp[port['source_port_id']]['source_component_id']]
 if net not in nets or ref not in ['U1','U3']:continue
 pid=port['pcb_port_id'];px,py=port['x'],port['y'];requests.append((pid,net,px,py,ref));choices[pid]={}
 for dx,dy in [(-.4,-.4),(-.4,.4),(.4,-.4),(.4,.4)]:
  key=(round(px+dx,3),round(py+dy,3));best=None
  for jx in [0,-.05,.05,-.1,.1]:
   for jy in [0,-.05,.05,-.1,.1]:
    vx,vy=round(key[0]+jx,6),round(key[1]+jy,6);v=Point(vx,vy)
    if hardVia.intersects(v):continue
    ax,ay=vx-px,vy-py;t=min(abs(ax),abs(ay));sx=1 if ax>=0 else -1;sy=1 if ay>=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={m[2] for g,m in fixed if g.distance(v)<.25};score=(len(blocked),line.length,abs(jx)+abs(jy))
     if best is None or score<best[0]:best=(score,vx,vy,pts,blocked)
  if best:choices[pid][key]=best
owner={};assigned={}
def visit(pid,seen):
 for key,b in sorted(choices[pid].items(),key=lambda z:z[1][0]):
  if key in seen:continue
  seen.add(key)
  if key not in owner or visit(owner[key],seen):owner[key]=pid;assigned[pid]=key;return True
 return False
failed=[]
for pid,net,px,py,ref in sorted(requests,key=lambda r:len(choices[r[0]])):
 if not visit(pid,set()):failed.append({'port':pid,'net':x['netnames'][net],'ref':ref,'x':px,'y':py,'candidates':len(choices[pid])})
Path('design/ddr-fanout-assignment.json').write_text(json.dumps({'pins':len(requests),'assigned':len(assigned),'unmatched':failed},indent=2))
print('DDR fanout matching:',len(assigned),'/',len(requests),'pins; unmatched:',failed,flush=True)
if failed:raise SystemExit(2)
removed=set()
for sid in sids:d['nets'][sid]=[];d['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',[]))
for pid,net,px,py,ref in requests:
 _,vx,vy,pts,blocked=choices[pid][assigned[pid]];removed.update(blocked);clean=[pts[0]]
 for q in pts[1:]:
  if math.dist(q,clean[-1])>1e-8:clean.append(q)
 sid=byroot[net];d['nets'][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'}]])
for sid,rs in d['nets'].items():
 if sid not in sids:d['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(d,separators=(',',':'))+'\n');print('Reserved all DDR escapes;',len(removed),'non-DDR inner trace records need rerouting',flush=True)