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/refine-decoupling-globally.py

"""Globally assign the 90 identical capacitors to validated sites, respecting rail clearance."""
import ast,json,runpy,math,statistics
from pathlib import Path
from shapely.geometry import box
from shapely.ops import unary_union
import shapely
module=ast.parse(Path('scripts/refine-decoupling-assignment.py').read_text());exec(compile(ast.Module(body=[n for n in module.body if isinstance(n,ast.FunctionDef)],type_ignores=[]),'assignment','exec'))
x=runpy.run_path('scripts/audit-copper.py');c=x['c'];data=json.loads(Path('design/routed-copper.json').read_text());report=json.loads(Path('design/decoupling-report.json').read_text());rows=report['capacitors'];sites=[dict(r) for r in rows]
src={e['source_component_id']:e['name'] for e in c if e['type']=='source_component'};movable={e['pcb_component_id'] for e in c if e['type']=='pcb_component' and src[e['source_component_id']].startswith(('C_U1_','C_DDR'))};pads={e['pcb_smtpad_id'] for e in c if e['type']=='pcb_smtpad' and e['pcb_component_id'] in movable};powers={'GND','A3V3','DDR_1V5','RTC_1V8','V1V8','V3V3','VDD_CORE','VDD_MPU'}
remove={f'fr_{sid}_{i}' for sid,rs in data['nets'].items() for i,r in enumerate(rs) if x['netnames'].get(x['find'](sid)) in powers and all(p['route_type']=='wire' and p['layer']=='bottom' for p in r)}
fixed=[(g,m) for g,m in zip(x['shapes'],x['meta']) if m[0]=='bottom' and m[2] not in pads and m[2] not in remove and not m[2].startswith('pcb_copper_pour')]
obstacles={}
for net in powers:
 geom=unary_union([g for g,m in fixed if x['netnames'].get(m[1])!=net]).buffer(.105);shapely.prepare(geom);obstacles[net]=geom
points=[];shapes=[]
for s in sites:
 angle=s['rotation'];ux,uy=round(math.cos(math.radians(angle))),round(math.sin(math.radians(angle)));px,py=s['x']+.51*ux,s['y']+.51*uy;w,h=(.54,.64) if angle%180==0 else (.64,.54);points.append((px,py));shapes.append(box(px-w/2,py-h/2,px+w/2,py+h/2))
costs=[[math.hypot(r['targetX']-px,r['targetY']-py) if not obstacles[r['net']].intersects(shapes[j]) else 1e6 for j,(px,py) in enumerate(points)] for r in rows]
limits=sorted(set(d for row in costs for d in row if d<1e5));lo=0;hi=len(limits)-1
while lo<hi:
 mid=(lo+hi)//2
 if matching(costs,limits[mid]) is not None:hi=mid
 else:lo=mid+1
limit=limits[lo]+.02;assignment=hungarian([[d+.15*d*d if d<=limit else 1e6 for d in row] for row in costs]);assert all(costs[i][j]<limit for i,j in assignment.items())
vias={net:[(p['x'],p['y']) for sid,rs in data['nets'].items() if x['netnames'].get(x['find'](sid))==net for r in rs for p in r if p['route_type']=='via'] for net in powers}
for i,j in assignment.items():
 r=rows[i];site=sites[j]
 for k in ['x','y','rotation','nearestGroundViaMm']:r[k]=site[k]
 r['nearestPowerViaMm']=round(min(math.dist(points[j],p) for p in vias[r['net']]),4);r['padToBallMm']=round(costs[i][j],4)
report['assignmentRefinement']='Global minimum worst-case matching of identical 100 nF capacitors to the occupied sites, subject to fixed copper clearances for each assigned supply rail; weighted distance minimized within 0.02 mm of that bound.'
Path('design/decoupling-report.json').write_text(json.dumps(report,indent=2)+'\n');Path('design/decoupling-placement.json').write_text(json.dumps({r['name']:{k:r[k] for k in ['x','y','rotation']} for r in rows},indent=2)+'\n');print('Global assignment mean',round(statistics.mean(r['padToBallMm'] for r in rows),3),'max',max(r['padToBallMm'] for r in rows))