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/tune-ddr-lengths.py
"""Insert clearance-checked chamfered length-tuning detours into saved DDR routes."""
import json,runpy,math,os
from pathlib import Path
from shapely.geometry import Point,LineString,box
from shapely.ops import unary_union
import shapely
x=runpy.run_path('scripts/audit-copper.py');d=json.load(open('design/routed-copper.json'));c=x['c'];names={e['source_net_id']:e['name'] for e in c if e['type']=='source_net'};r=json.load(open('design/ddr-skew-check.json'));lengths={n:v['pathLengthMm'] for n,v in r['nets'].items()}
targets={}
for n in lengths:
if n.startswith(('DDR_DQS','DDR_DQM')):b=int(n[-1]);targets[n]=18.5 if b==0 else 12.1
elif n.startswith('DDR_D') and n[5:].isdigit():targets[n]=18.5 if int(n[5:])<8 else 12.1
elif n!='DDR_RESETn':targets[n]=35.02
sidby={n:s for s,n in names.items()};fixed={}
for layer in x['layers']:
fixed[layer]=[(g,m[1]) for g,m in zip(x['shapes'],x['meta']) if m[0]==layer and not m[2].startswith(('fr_','pcb_copper_pour'))]
def routesgeometry(exclude):
out={l:[] for l in x['layers']}
for sid,rs in d['nets'].items():
if sid==exclude:continue
for rt in rs:
for p in rt:
if p['route_type']=='via':
g=Point(p['x'],p['y']).buffer(p.get('via_diameter',.3)/2)
for l in out:out[l].append(g)
for a,b in zip(rt,rt[1:]):
if a['route_type']==b['route_type']=='wire' and a['layer']==b['layer'] and (a['x'],a['y'])!=(b['x'],b['y']):out[a['layer']].append(LineString([(a['x'],a['y']),(b['x'],b['y'])]).buffer(a['width']/2))
net=x['find'](exclude)
return {l:unary_union(out[l]+[g for g,n in fixed[l] if n!=net]).buffer(.155) for l in out}
overrides=json.loads(os.environ.get('DDR_TARGETS',Path('design/ddr-length-targets.json').read_text()))
if overrides:targets=overrides
report=[]
# Longer additions first, so they get the largest available pockets.
for name,target in sorted(targets.items(),key=lambda kv:-(kv[1]-lengths[kv[0]])):
sid=sidby[name];remaining=target-lengths[name];rs=d['nets'][sid];added=0
if remaining<.015:continue
obstacles=routesgeometry(sid)
for iteration in range(60):
best=None
segments=[]
for ri,rt in enumerate(rs):
if any(p['route_type']=='via' for p in rt):continue
for si,(a,b) in enumerate(zip(rt,rt[1:])):
le=math.hypot(b['x']-a['x'],b['y']-a['y'])
if le>.7 and a['layer'] not in ['top','bottom']:segments.append((le,ri,si,a,b))
for le,ri,si,a,b in sorted(segments,reverse=True,key=lambda v:v[0]):
layer=a['layer'];ux=(b['x']-a['x'])/le;uy=(b['y']-a['y'])/le
other=[]
for rj,rt in enumerate(rs):
for sj,(p,q) in enumerate(zip(rt,rt[1:])):
if rj==ri and sj==si:continue
if p['route_type']==q['route_type']=='wire' and p['layer']==q['layer']==layer:other.append(LineString([(p['x'],p['y']),(q['x'],q['y'])]))
others=unary_union(other)
for span in sorted(set([round(v,6) for v in [le-.4,min(le-.4,2.0),min(le-.4,1.0),.6] if .5<v<=le-.1]),reverse=True):
for t in [0.2,(le-span)/2,le-span-.2]:
for sign in [1,-1]:
# 45-degree corners replace four right-angle corners.
chamfer=min(.15,span/4,max(.025,remaining/4));heights=[(remaining+4*chamfer*(2-math.sqrt(2)))/2]+[v*.15 for v in range(min(80,int(remaining/2/.15)+2),1,-1)]
for h in heights:
if h<2*chamfer or h>12:continue
pts=[(0,0),(t-chamfer,0),(t,chamfer),(t,h-chamfer),(t+chamfer,h),(t+span-chamfer,h),(t+span,h-chamfer),(t+span,chamfer),(t+span+chamfer,0),(le,0)]
if t<chamfer or t+span+chamfer>le:continue
coords=[(a['x']+u*ux-v*uy*sign,a['y']+u*uy+v*ux*sign) for u,v in pts];line=LineString(coords);extra=line.length-le
if extra>remaining+.00001 or extra<.03:continue
if best and extra<best[0]-.0001:continue
if not box(-34.4,-29.4,34.4,29.4).covers(line) or obstacles[layer].intersects(line):continue
allowed=Point(a['x'],a['y']).buffer(.45).union(Point(b['x'],b['y']).buffer(.45))
if line.difference(allowed).distance(others)<.30:continue
if not line.is_simple:continue
best=(extra,ri,si,coords,layer)
break
if not best:break
extra,ri,si,coords,layer=best;rt=rs[ri];rs[ri]=rt[:si]+[dict(route_type='wire',x=round(px,6),y=round(py,6),width=.1,layer=layer) for px,py in coords]+rt[si+2:];remaining-=extra;added+=extra
if remaining<.015:break
print(name,'added',round(added,4),'remaining',round(remaining,4),flush=True);report.append(dict(net=name,targetMm=target,addedMm=added,remainingMm=remaining))
Path('design/routed-copper.json').write_text(json.dumps(d,separators=(',',':'))+'\n')
Path('design/ddr-pair-tuning-progress.json').write_text(json.dumps(report,indent=2)+'\n')