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/import-router.py

"""Convert routed Specctra session copper to tscircuit phase-replay data."""
import json,re,sys
from pathlib import Path
session=Path(sys.argv[1] if len(sys.argv)>1 else '../../work/compact-clean.ses')
text=session.read_text().replace('(string_quote ")','')
tokens=re.findall(r'"(?:\\.|[^"\\])*"|[()]|[^\s()]+',text)
stack=[];tree=None
for t in tokens:
 if t=='(':
  a=[]
  if stack:stack[-1].append(a)
  stack.append(a)
 elif t==')':tree=stack.pop()
 else:stack[-1].append(json.loads(t) if t.startswith('"') else t)
def children(node,key):return [x for x in node if isinstance(x,list) and x and x[0]==key]
def walk(node,key):
 if isinstance(node,list):
  if node and node[0]==key:yield node
  for x in node:
   if isinstance(x,list):yield from walk(x,key)
resolution=next(walk(tree,'resolution'));scale=float(resolution[2])*({'um':1000,'mm':1}[resolution[1]])
source=json.loads(Path('dist/index/circuit.json').read_text())
byNet={e['name']:e['source_net_id'] for e in source if e['type']=='source_net'}
ports={e['source_port_id']:e for e in source if e['type']=='source_port'}
traces=[e for e in source if e['type']=='source_trace']
# Match direct port-to-port nets by their physical DSN pins, independently of source ID renumbering.
dsnPath=Path(next((a for a in sys.argv[2:] if a.endswith('.dsn')),'../../work/compact-ortho.dsn'))
dsn=dsnPath.read_text()
placements={p:(float(x)/1000,float(y)/1000) for p,x,y in re.findall(r'\(place (P\d+) ([-\d.]+) ([-\d.]+) front 0\)',dsn)}
pinNets={pin:(json.loads(name) if name.startswith('"') else name) for name,pins in re.findall(r'\(net ("[^"\n]+"|[^\s()]+) \(pins ([^)]*)\)\)',dsn) for pin in pins.split()}
for t in traces:
 if not t.get('connected_source_net_ids'):
  p=t['connected_source_port_ids'][0]
  point=next(v for v in source if v['type']=='pcb_port' and v['source_port_id']==p)
  pin=next(k for k,(x,y) in placements.items() if abs(x-point['x'])<.002 and abs(y-point['y'])<.002)
  name=pinNets[pin+'-1']
  byNet[name]=t['source_trace_id']
viaDiameters={(json.loads(name) if name.startswith('"') else name):float(diameter)/1000 for name,diameter in re.findall(r'\(padstack ("[^"\n]+"|[^\s()]+)\s+\(shape\s+\(circle\s+[^\s()]+\s+([\d.]+)',dsn)}
viaDiameters.update({p[1]:float(next(walk(p,'circle'))[2])/scale for p in walk(tree,'padstack')})
result={};count=0
for net in walk(tree,'net'):
 if len(net)<2 or not isinstance(net[1],str) or net[1] not in byNet:continue
 routes=[]
 for wire in children(net,'wire'):
  for path in children(wire,'path'):
   layer=path[1];width=float(path[2])/scale;coords=list(map(float,path[3:]));route=[{'route_type':'wire','x':coords[i]/scale,'y':coords[i+1]/scale,'width':width,'layer':layer} for i in range(0,len(coords),2)]
   routes.append(route)
 for via in children(net,'via'):
  x,y=float(via[2])/scale,float(via[3])/scale
  diameter=viaDiameters[via[1]]
  assert diameter>=.299,f'Router produced an undersized via: {diameter}'
  routes.append([{'route_type':'wire','x':x,'y':y,'width':.12,'layer':'top'},{'route_type':'via','x':x,'y':y,'from_layer':'top','to_layer':'bottom','via_diameter':diameter,'via_hole_diameter':(.15 if diameter<.35 else .2)},{'route_type':'wire','x':x,'y':y,'width':.12,'layer':'bottom'}])
 if routes:result[byNet[net[1]]]=routes;count+=len(routes)
# Bind snapshots to actual endpoint coordinates so later placement edits cannot silently replay stale copper.
endpoints={}
for net in source:
 if net['type']!='source_net':continue
 ids={p for t in traces if net['source_net_id'] in t.get('connected_source_net_ids',[]) for p in t['connected_source_port_ids']}
 endpoints[net['source_net_id']]=[[p['x'],p['y']] for p in source if p['type']=='pcb_port' and p['source_port_id'] in ids]
for t in traces:
 if not t.get('connected_source_net_ids'):endpoints[t['source_trace_id']]=[[p['x'],p['y']] for p in source if p['type']=='pcb_port' and p['source_port_id'] in t['connected_source_port_ids']]
sourceTraceIds={n:next((t['source_trace_id'] for t in traces if n in t.get('connected_source_net_ids',[])),n) for n in result}
if '--partial' not in sys.argv:assert len(result)==len(byNet),f'Missing routed nets: {set(byNet.values())-set(result)}'
Path('design/routed-copper.json').write_text(json.dumps({'scale':scale,'nets':result,'endpoints':endpoints,'sourceTraceIds':sourceTraceIds},separators=(',',':'))+'\n')
print('Imported',len(result),'nets;',count,'wire/via items; SES scale:',scale)