Add python script for DDR3 length matching using T topology.
This commit is contained in:
parent
92216b7351
commit
904ae200e8
|
@ -0,0 +1,308 @@
|
|||
#!/usr/bin/env python2
|
||||
|
||||
|
||||
# Author: Dick Hollenbeck
|
||||
# Report any length problems pertaining to a SDRAM DDR3 T topology using
|
||||
# 4 memory chips: a T into 2 Ts routing strategy from the CPU.
|
||||
|
||||
# Designed to be run from the command line in a process separate from pcbnew.
|
||||
# It can monitor writes to disk which will trigger updates to its output, or
|
||||
# it can be run with --once option.
|
||||
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import pcbnew
|
||||
import os.path
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
CPU_REF = 'U7' # CPU reference designator
|
||||
|
||||
# four SDRAM memory chips:
|
||||
DDR_LF = 'U15' # left front DRAM
|
||||
DDR_RF = 'U17' # right front DRAM
|
||||
DDR_LB = 'U16' # left back DRAM
|
||||
DDR_RB = 'U18' # right back DRAM
|
||||
|
||||
|
||||
# Length of SDRAM clock, it sets the maximum or equal needed for other traces
|
||||
CLOCK_LEN = pcbnew.FromMils( 2.25 * 1000 )
|
||||
|
||||
def addr_line_netname(line_no):
|
||||
"""From an address line number, return the netname"""
|
||||
netname = '/DDR3/DRAM_A' + str(line_no)
|
||||
return netname
|
||||
|
||||
# Establish GOALS which are LENs, TOLERANCEs and NETs for each group of nets.
|
||||
|
||||
# Net Group: ADDR_AND_CMD
|
||||
ADDR_AND_CMD_LEN = pcbnew.FromMils( 2.22 * 1000 )
|
||||
ADDR_AND_CMD_TOLERANCE = pcbnew.FromMils( 25 ) / 2
|
||||
ADDR_AND_CMD_NETS = [addr_line_netname(a) for a in range(0,16)]
|
||||
ADDR_AND_CMD_NETS += [
|
||||
'/DDR3/DRAM_SDBA0',
|
||||
'/DDR3/DRAM_SDBA1',
|
||||
'/DDR3/DRAM_SDBA2',
|
||||
'/DDR3/DRAM_RAS_B',
|
||||
'/DDR3/DRAM_CAS_B',
|
||||
'/DDR3/DRAM_WE_B'
|
||||
]
|
||||
|
||||
|
||||
# Net Group: CONTROL
|
||||
CONTROL_LEN = pcbnew.FromMils( 2.10 * 1000 )
|
||||
CONTROL_TOLERANCE = pcbnew.FromMils( 50 ) / 2
|
||||
CONTROL_NETS = [
|
||||
'/DDR3/DRAM_SDODT0',
|
||||
#'/DDR3/DRAM_SDODT1',
|
||||
'/DDR3/DRAM_CS0_B',
|
||||
#'/DDR3/DRAM_CS1_B',
|
||||
'/DDR3/DRAM_SDCKE0',
|
||||
#'/DDR3/DRAM_SDCKE1',
|
||||
]
|
||||
|
||||
|
||||
|
||||
BRIGHTGREEN = '\033[92;1m'
|
||||
GREEN = '\033[92m'
|
||||
BRIGHTRED = '\033[91;1m'
|
||||
RED = '\033[91m'
|
||||
ENDC = '\033[0m'
|
||||
|
||||
|
||||
pcb = None
|
||||
nets = None
|
||||
|
||||
dbg_conn = False # when true prints out reason for track discontinuity
|
||||
|
||||
def print_color(color, s):
|
||||
print(color + s + ENDC)
|
||||
|
||||
|
||||
def addr_line_netname(line_no):
|
||||
netname = '/DDR3/DRAM_A' + str(line_no)
|
||||
return netname
|
||||
|
||||
|
||||
def pad_name(pad):
|
||||
return str( pad.GetParent().Reference().GetShownText() ) + '/' + pad.GetPadName()
|
||||
|
||||
|
||||
def pad_pos(pad):
|
||||
return str(pad.GetPosition())
|
||||
|
||||
|
||||
def pads_in_net(netname):
|
||||
byname = {}
|
||||
pads = nets[netname].Pads()
|
||||
for pad in pads:
|
||||
byname[pad_name(pad)] = pad
|
||||
return byname
|
||||
|
||||
|
||||
def track_ends(track):
|
||||
"""return a string showing both ends of a track"""
|
||||
return str(track.GetStart()) + ' ' + str(track.GetEnd())
|
||||
|
||||
|
||||
def print_tracks(net_name,tracks):
|
||||
print('net:', net_name)
|
||||
for track in tracks:
|
||||
print(' track:', track_ends(track))
|
||||
|
||||
|
||||
def sum_track_lengths(point1,point2,netcode):
|
||||
tracks = pcb.TracksInNetBetweenPoints(point1, point2, netcode)
|
||||
sum = 0
|
||||
for t in tracks:
|
||||
sum += t.GetLength()
|
||||
return sum
|
||||
|
||||
|
||||
def tracks_in_net(netname):
|
||||
nc = pcb.GetNetcodeFromNetname(netname)
|
||||
tracks_and_vias = pcb.TracksInNet(nc)
|
||||
# remove vias while making new non-owning list
|
||||
tracks = [t for t in tracks_and_vias if not t.Type() == pcbnew.PCB_VIA_T]
|
||||
return tracks
|
||||
|
||||
|
||||
def print_pad(pad):
|
||||
print( " pad name:'%s' pos:%s" % ( pad_name(pad), pad_pos(pad) ) )
|
||||
|
||||
|
||||
def print_pads(prompt,pads):
|
||||
print(prompt)
|
||||
for pad in pads:
|
||||
print_pad(pad)
|
||||
|
||||
|
||||
def is_connected(start_pad, end_pad):
|
||||
"""
|
||||
Return True if the two pads are copper connected only with vias and tracks
|
||||
directly and with no intervening pads, else False.
|
||||
"""
|
||||
netcode = start_pad.GetNet().GetNet()
|
||||
try:
|
||||
tracks = pcb.TracksInNetBetweenPoints(start_pad.GetPosition(), end_pad.GetPosition(), netcode)
|
||||
except IOError as ioe:
|
||||
if dbg_conn: # can be True when wanting details on discontinuity
|
||||
print(ioe)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def find_connected_pad(start_pad, pads):
|
||||
for p in pads:
|
||||
if p == start_pad:
|
||||
continue
|
||||
if is_connected(start_pad,p):
|
||||
return p
|
||||
raise IOError( 'no connection to pad %s' % pad_name(start_pad) )
|
||||
|
||||
|
||||
def find_cpu_pad(pads):
|
||||
for p in pads:
|
||||
if CPU_REF in pad_name(p):
|
||||
return p
|
||||
raise IOError( 'no cpu pad' )
|
||||
|
||||
|
||||
def report_teed_lengths(groupname, netname, target_length, tolerance):
|
||||
global dbg_conn
|
||||
|
||||
print(groupname, netname)
|
||||
nc = pcb.GetNetcodeFromNetname(netname)
|
||||
#print("nc", nc)
|
||||
|
||||
pads = nets[netname].Pads()
|
||||
|
||||
# convert from std::vector<> to python list
|
||||
pads = list(pads)
|
||||
#print_pads(netname, pads )
|
||||
|
||||
cpu_pad = find_cpu_pad(pads)
|
||||
pads.remove(cpu_pad)
|
||||
|
||||
# a trap for a troublesome net that appears to be disconnected or has stray segments.
|
||||
if netname == None:
|
||||
#if netname == '/DDR3/DRAM_SDCKE0':
|
||||
dbg_conn = True
|
||||
|
||||
# find the first T
|
||||
#print_pads(netname + ' without cpu pad', pads )
|
||||
t1 = find_connected_pad(cpu_pad, pads)
|
||||
pads.remove(t1)
|
||||
|
||||
# find 2 second tier T pads
|
||||
t2_1 = find_connected_pad(t1, pads)
|
||||
pads.remove(t2_1)
|
||||
|
||||
t2_2 = find_connected_pad(t1, pads)
|
||||
pads.remove(t2_2)
|
||||
|
||||
cpad = [0] * 4
|
||||
|
||||
# find 4 memory pads off of each 2nd tier T
|
||||
cpad[0] = find_connected_pad(t2_1, pads)
|
||||
pads.remove(cpad[0])
|
||||
|
||||
cpad[1] = find_connected_pad(t2_1, pads)
|
||||
pads.remove(cpad[1])
|
||||
|
||||
cpad[2] = find_connected_pad(t2_2, pads)
|
||||
pads.remove(cpad[2])
|
||||
|
||||
cpad[3] = find_connected_pad(t2_2, pads)
|
||||
pads.remove(cpad[3])
|
||||
|
||||
len_t1 = sum_track_lengths(cpu_pad.GetPosition(),t1.GetPosition(),nc)
|
||||
#print("len_t1 %.0f" % len_t1)
|
||||
|
||||
len_t2_1 = sum_track_lengths(t1.GetPosition(),t2_1.GetPosition(),nc)
|
||||
len_t2_2 = sum_track_lengths(t1.GetPosition(),t2_2.GetPosition(),nc)
|
||||
#print("len_t2_1 %.0f" % len_t2_1)
|
||||
#print("len_t2_2 %.0f" % len_t2_2)
|
||||
|
||||
lens = [0] * 4
|
||||
|
||||
lens[0] = sum_track_lengths(t2_1.GetPosition(),cpad[0].GetPosition(),nc)
|
||||
lens[1] = sum_track_lengths(t2_1.GetPosition(),cpad[1].GetPosition(),nc)
|
||||
lens[2] = sum_track_lengths(t2_2.GetPosition(),cpad[2].GetPosition(),nc)
|
||||
lens[3] = sum_track_lengths(t2_2.GetPosition(),cpad[3].GetPosition(),nc)
|
||||
|
||||
"""
|
||||
for index, total_len in enumerate(lens):
|
||||
print( "%s: %.0f" % (pad_name(cpad[index]), lens[index]))
|
||||
"""
|
||||
|
||||
# Each net goes from CPU to four memory chips, these are the 4 lengths from
|
||||
# CPU to each of the for memory chip balls/pads, some of these journies are
|
||||
# common with one another but branch off at each T.
|
||||
lens[0] += len_t1 + len_t2_1
|
||||
lens[1] += len_t1 + len_t2_1
|
||||
lens[2] += len_t1 + len_t2_2
|
||||
lens[3] += len_t1 + len_t2_2
|
||||
|
||||
for index, total_len in enumerate(lens):
|
||||
delta = total_len - target_length
|
||||
if delta > tolerance:
|
||||
print_color( BRIGHTRED, "%s %s len:%.0f long by %.0f mils" %
|
||||
(netname, pad_name(cpad[index]), pcbnew.ToMils(total_len), pcbnew.ToMils(delta - tolerance) ))
|
||||
elif delta < -tolerance:
|
||||
print_color( BRIGHTRED, "%s %s len:%.0f short by %.0f mils" %
|
||||
(netname, pad_name(cpad[index]), pcbnew.ToMils(total_len), pcbnew.ToMils(tolerance - delta) ))
|
||||
|
||||
|
||||
|
||||
def load_board_and_report_lengths(filename):
|
||||
|
||||
global pcb
|
||||
pcb = pcbnew.LoadBoard(filename)
|
||||
pcb.BuildListOfNets() # required so 'pcb' contains valid netclass data
|
||||
|
||||
global nets
|
||||
nets = pcb.GetNetsByName()
|
||||
|
||||
for netname in ADDR_AND_CMD_NETS:
|
||||
report_teed_lengths("addr_and_cmd", netname, ADDR_AND_CMD_LEN, ADDR_AND_CMD_TOLERANCE)
|
||||
|
||||
for netname in CONTROL_NETS:
|
||||
report_teed_lengths("control", netname, CONTROL_LEN, CONTROL_TOLERANCE)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
boardfile = sys.argv[1]
|
||||
except IndexError:
|
||||
print("Usage: %s <boardname.kicad_pcb> [--once]" % sys.argv[0])
|
||||
sys.exit(1)
|
||||
|
||||
first = True
|
||||
|
||||
while True:
|
||||
|
||||
# wait for the file contents to change
|
||||
lastmtime = os.path.getmtime(boardfile)
|
||||
mtime = lastmtime
|
||||
while mtime == lastmtime and not first:
|
||||
try:
|
||||
mtime = os.path.getmtime(boardfile)
|
||||
except OSError:
|
||||
pass # kicad save process seems to momentarily delete file, so there's a race here with "No such file.."
|
||||
time.sleep(0.5)
|
||||
|
||||
# The "Debug" build of pcbnew writes to disk slowy, new file takes time to get to disk.
|
||||
time.sleep(1)
|
||||
|
||||
first = False
|
||||
|
||||
print( '\033[2J' ) # clear screen, maybe
|
||||
|
||||
load_board_and_report_lengths(boardfile)
|
||||
|
||||
if "--once" in sys.argv:
|
||||
sys.exit(0)
|
Loading…
Reference in New Issue