seveibar/am3352-ram-dogbone-and-single-layer-route-test
AM3352BZCZ100 processor connected to a W631GG6MB-12 DDR3 memory chip through a routed, length-matched 16-bit DDR interface with clock/data-strobe pairs and control/address signals.
- Version
- 0.0.9
- License
- unset
- Stars
- 0
scripts/simplify-ordinary-turns.py
"""Offline, board-specific postprocessor: input.circuit.json output.circuit.json.
Replace only octilinear runs with fewer-turn, equal-length paths. Curved
meanders and differential-pair geometry stay untouched. Continuous trace/via/self clearance and sampled
bidirectional pairing gate every edit; run the independent audits afterward.
The published algorithmFn evaluates the resulting generated primitives.
"""
import json,math,numpy as np,importlib.util,sys
from shapely.geometry import LineString,Point
from shapely.ops import unary_union,substring
from shapely.prepared import prep
from shapely import line_interpolate_point,distance,get_x,get_y
from pathlib import Path
input_path=Path(sys.argv[1]);output_path=Path(sys.argv[2]);C=json.loads(input_path.read_text());trs=[t for t in C if t['type']=='pcb_trace'];V=[(t['source_trace_id'],(p['x'],p['y']))for t in trs for p in t['route']if p['route_type']=='via'];P={15:16,16:15,27:26,26:27,32:33,33:32};P={f'source_trace_{k}':f'source_trace_{v}'for k,v in P.items()}
def points(t):
p=np.array([(p['x'],p['y'])for p in t['route']if p['route_type']=='wire'and p.get('layer')!='top']);return p[np.r_[True,np.linalg.norm(np.diff(p,axis=0),axis=1)>1e-8]]
def heading(ps):
d=np.diff(ps,axis=0);return np.degrees(np.arctan2(d[:,1],d[:,0]))
def turns(ps):return abs((np.diff(heading(ps))+180)%360-180)
def axis(ps):return abs((heading(ps)+22.5)%45-22.5)<.2
def score(ps):
a=axis(ps);t=turns(ps);ordinary=a[:-1]&a[1:]&(t>.2);idx=np.where(ordinary)[0]+1;lengths=np.linalg.norm(np.diff(ps,axis=0),axis=1);return dict(ordinaryTurns=int(sum(ordinary)),shortJogSegments=int(sum(lengths[i]<.25 and i in idx and i+1 in idx for i in range(len(lengths)))))
def paths(a,b):
dx,dy=b-a;sx,sy=np.sign(dx),np.sign(dy);x,y=abs(dx),abs(dy)
for k in np.linspace(0,abs(x-y),9):
c=np.array([a,a+[sx*k,0],a+[sx*(k+y),sy*y],b])if x>=y else np.array([a,a+[0,sy*k],a+[sx*x,sy*(k+x)],b]);c=c[np.r_[True,np.linalg.norm(np.diff(c,axis=0),axis=1)>1e-8]];yield c
before={t['source_trace_id']:score(points(t))for t in trs};changes=[]
for repeat in range(3):
for t in trs:
if t['source_trace_id'] in P:continue
name=t['source_trace_id'];layer=next(p['layer']for p in t['route']if p['route_type']=='wire'and p.get('layer')!='top');ps=points(t);oldscore=score(ps);ob=prep(unary_union([LineString(points(z)).buffer(.19999)for z in trs if z is not t and any(p.get('layer')==layer for p in z['route'])]+[Point(p).buffer(.29999)for n,p in V if n!=name]));mate=LineString(points(next(z for z in trs if z['source_trace_id']==P[name])))if name in P else None
if mate:
mate_samples=line_interpolate_point(mate,np.arange(6.2,mate.length-6.2,.01));mate_x=get_x(mate_samples);mate_y=get_y(mate_samples)
for iteration in range(500):
ax=axis(ps);prefix=np.r_[0,np.cumsum(np.linalg.norm(np.diff(ps,axis=0),axis=1))];best=None
for i in range(len(ps)-3):
if not ax[i]:continue
for j in range(i+3,min(len(ps),i+70)):
if not all(ax[i:j]):break
old=ps[i:j+1];nt=score(old)['ordinaryTurns']
if nt<2:continue
for c in paths(ps[i],ps[j]):
gain=nt-score(c)['ordinaryTurns']
if best is not None and (gain,j-i)<=best[0]:continue
if gain<=0 or abs(LineString(c).length-(prefix[j]-prefix[i]))>1e-7:continue
check=np.vstack([ps[max(0,i-1):i],c,ps[j+1:j+2]])
if max(turns(check),default=0)>45.1:continue
g=LineString(c)
if ob.intersects(g):continue
line=LineString(ps);parts=[]
if prefix[i]>.3:parts.append(substring(line,0,prefix[i]-.3).buffer(.19999))
if prefix[j]<line.length-.3:parts.append(substring(line,prefix[j]+.3,line.length).buffer(.19999))
if any(q.intersects(g)for q in parts):continue
candidate=np.vstack([ps[:i],c,ps[j+1:]])
if not LineString(candidate).is_simple:continue
if mate:
ln=LineString(candidate);ss=np.arange(max(0,6.2-prefix[i]),min(g.length,ln.length-6.2-prefix[i]),.01)
if len(ss):
gaps=distance(line_interpolate_point(g,ss),mate)-.1
if np.any((gaps<.0999)|(gaps>.155)):continue
minx,miny,maxx,maxy=LineString(old).bounds;mp=mate_samples[(mate_x>minx-.256)&(mate_x<maxx+.256)&(mate_y>miny-.256)&(mate_y<maxy+.256)]
if len(mp):
gaps=distance(mp,ln)-.1
if np.any((gaps<.0999)|(gaps>.155)):continue
if score(candidate)['ordinaryTurns']>=score(ps)['ordinaryTurns']:continue
merit=(gain,j-i)
if best is None or merit>best[0]:best=(merit,i,j,c)
if best is None:break
_,i,j,c=best;ps=np.vstack([ps[:i],c,ps[j+1:]])
ids=[i for i,p in enumerate(t['route'])if p['route_type']=='wire'and p.get('layer')==layer];t['route'][ids[0]:ids[-1]+1]=[dict(route_type='wire',x=float(x),y=float(y),layer=layer,width=.1)for x,y in ps];newscore=score(ps)
json.dump(C,open(output_path,'w'))
if newscore!=oldscore:print(repeat,name,oldscore,newscore,flush=True);changes.append(name)
if not changes:break
json.dump(C,open(output_path,'w'))
after={t['source_trace_id']:score(points(t))for t in trs};json.dump(dict(before=before,after=after),open(str(output_path)+'.turn-scores.json','w'),indent=2)