You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

308 lines
8.3 KiB

  1. #!/usr/bin/env python2
  2. # Author: Dick Hollenbeck
  3. # Report any length problems pertaining to a SDRAM DDR3 T topology using
  4. # 4 memory chips: a T into 2 Ts routing strategy from the CPU.
  5. # Designed to be run from the command line in a process separate from pcbnew.
  6. # It can monitor writes to disk which will trigger updates to its output, or
  7. # it can be run with --once option.
  8. from __future__ import print_function
  9. import pcbnew
  10. import os.path
  11. import sys
  12. import time
  13. CPU_REF = 'U7' # CPU reference designator
  14. # four SDRAM memory chips:
  15. DDR_LF = 'U15' # left front DRAM
  16. DDR_RF = 'U17' # right front DRAM
  17. DDR_LB = 'U16' # left back DRAM
  18. DDR_RB = 'U18' # right back DRAM
  19. # Length of SDRAM clock, it sets the maximum or equal needed for other traces
  20. CLOCK_LEN = pcbnew.FromMils( 2.25 * 1000 )
  21. def addr_line_netname(line_no):
  22. """From an address line number, return the netname"""
  23. netname = '/DDR3/DRAM_A' + str(line_no)
  24. return netname
  25. # Establish GOALS which are LENs, TOLERANCEs and NETs for each group of nets.
  26. # Net Group: ADDR_AND_CMD
  27. ADDR_AND_CMD_LEN = pcbnew.FromMils( 2.22 * 1000 )
  28. ADDR_AND_CMD_TOLERANCE = pcbnew.FromMils( 25 ) / 2
  29. ADDR_AND_CMD_NETS = [addr_line_netname(a) for a in range(0,16)]
  30. ADDR_AND_CMD_NETS += [
  31. '/DDR3/DRAM_SDBA0',
  32. '/DDR3/DRAM_SDBA1',
  33. '/DDR3/DRAM_SDBA2',
  34. '/DDR3/DRAM_RAS_B',
  35. '/DDR3/DRAM_CAS_B',
  36. '/DDR3/DRAM_WE_B'
  37. ]
  38. # Net Group: CONTROL
  39. CONTROL_LEN = pcbnew.FromMils( 2.10 * 1000 )
  40. CONTROL_TOLERANCE = pcbnew.FromMils( 50 ) / 2
  41. CONTROL_NETS = [
  42. '/DDR3/DRAM_SDODT0',
  43. #'/DDR3/DRAM_SDODT1',
  44. '/DDR3/DRAM_CS0_B',
  45. #'/DDR3/DRAM_CS1_B',
  46. '/DDR3/DRAM_SDCKE0',
  47. #'/DDR3/DRAM_SDCKE1',
  48. ]
  49. BRIGHTGREEN = '\033[92;1m'
  50. GREEN = '\033[92m'
  51. BRIGHTRED = '\033[91;1m'
  52. RED = '\033[91m'
  53. ENDC = '\033[0m'
  54. pcb = None
  55. nets = None
  56. dbg_conn = False # when true prints out reason for track discontinuity
  57. def print_color(color, s):
  58. print(color + s + ENDC)
  59. def addr_line_netname(line_no):
  60. netname = '/DDR3/DRAM_A' + str(line_no)
  61. return netname
  62. def pad_name(pad):
  63. return str( pad.GetParent().Reference().GetShownText() ) + '/' + pad.GetPadName()
  64. def pad_pos(pad):
  65. return str(pad.GetPosition())
  66. def pads_in_net(netname):
  67. byname = {}
  68. pads = nets[netname].Pads()
  69. for pad in pads:
  70. byname[pad_name(pad)] = pad
  71. return byname
  72. def track_ends(track):
  73. """return a string showing both ends of a track"""
  74. return str(track.GetStart()) + ' ' + str(track.GetEnd())
  75. def print_tracks(net_name,tracks):
  76. print('net:', net_name)
  77. for track in tracks:
  78. print(' track:', track_ends(track))
  79. def sum_track_lengths(point1,point2,netcode):
  80. tracks = pcb.TracksInNetBetweenPoints(point1, point2, netcode)
  81. sum = 0
  82. for t in tracks:
  83. sum += t.GetLength()
  84. return sum
  85. def tracks_in_net(netname):
  86. nc = pcb.GetNetcodeFromNetname(netname)
  87. tracks_and_vias = pcb.TracksInNet(nc)
  88. # remove vias while making new non-owning list
  89. tracks = [t for t in tracks_and_vias if not t.Type() == pcbnew.PCB_VIA_T]
  90. return tracks
  91. def print_pad(pad):
  92. print( " pad name:'%s' pos:%s" % ( pad_name(pad), pad_pos(pad) ) )
  93. def print_pads(prompt,pads):
  94. print(prompt)
  95. for pad in pads:
  96. print_pad(pad)
  97. def is_connected(start_pad, end_pad):
  98. """
  99. Return True if the two pads are copper connected only with vias and tracks
  100. directly and with no intervening pads, else False.
  101. """
  102. netcode = start_pad.GetNet().GetNet()
  103. try:
  104. tracks = pcb.TracksInNetBetweenPoints(start_pad.GetPosition(), end_pad.GetPosition(), netcode)
  105. except IOError as ioe:
  106. if dbg_conn: # can be True when wanting details on discontinuity
  107. print(ioe)
  108. return False
  109. return True
  110. def find_connected_pad(start_pad, pads):
  111. for p in pads:
  112. if p == start_pad:
  113. continue
  114. if is_connected(start_pad,p):
  115. return p
  116. raise IOError( 'no connection to pad %s' % pad_name(start_pad) )
  117. def find_cpu_pad(pads):
  118. for p in pads:
  119. if CPU_REF in pad_name(p):
  120. return p
  121. raise IOError( 'no cpu pad' )
  122. def report_teed_lengths(groupname, netname, target_length, tolerance):
  123. global dbg_conn
  124. print(groupname, netname)
  125. nc = pcb.GetNetcodeFromNetname(netname)
  126. #print("nc", nc)
  127. pads = nets[netname].Pads()
  128. # convert from std::vector<> to python list
  129. pads = list(pads)
  130. #print_pads(netname, pads )
  131. cpu_pad = find_cpu_pad(pads)
  132. pads.remove(cpu_pad)
  133. # a trap for a troublesome net that appears to be disconnected or has stray segments.
  134. if netname == None:
  135. #if netname == '/DDR3/DRAM_SDCKE0':
  136. dbg_conn = True
  137. # find the first T
  138. #print_pads(netname + ' without cpu pad', pads )
  139. t1 = find_connected_pad(cpu_pad, pads)
  140. pads.remove(t1)
  141. # find 2 second tier T pads
  142. t2_1 = find_connected_pad(t1, pads)
  143. pads.remove(t2_1)
  144. t2_2 = find_connected_pad(t1, pads)
  145. pads.remove(t2_2)
  146. cpad = [0] * 4
  147. # find 4 memory pads off of each 2nd tier T
  148. cpad[0] = find_connected_pad(t2_1, pads)
  149. pads.remove(cpad[0])
  150. cpad[1] = find_connected_pad(t2_1, pads)
  151. pads.remove(cpad[1])
  152. cpad[2] = find_connected_pad(t2_2, pads)
  153. pads.remove(cpad[2])
  154. cpad[3] = find_connected_pad(t2_2, pads)
  155. pads.remove(cpad[3])
  156. len_t1 = sum_track_lengths(cpu_pad.GetPosition(),t1.GetPosition(),nc)
  157. #print("len_t1 %.0f" % len_t1)
  158. len_t2_1 = sum_track_lengths(t1.GetPosition(),t2_1.GetPosition(),nc)
  159. len_t2_2 = sum_track_lengths(t1.GetPosition(),t2_2.GetPosition(),nc)
  160. #print("len_t2_1 %.0f" % len_t2_1)
  161. #print("len_t2_2 %.0f" % len_t2_2)
  162. lens = [0] * 4
  163. lens[0] = sum_track_lengths(t2_1.GetPosition(),cpad[0].GetPosition(),nc)
  164. lens[1] = sum_track_lengths(t2_1.GetPosition(),cpad[1].GetPosition(),nc)
  165. lens[2] = sum_track_lengths(t2_2.GetPosition(),cpad[2].GetPosition(),nc)
  166. lens[3] = sum_track_lengths(t2_2.GetPosition(),cpad[3].GetPosition(),nc)
  167. """
  168. for index, total_len in enumerate(lens):
  169. print( "%s: %.0f" % (pad_name(cpad[index]), lens[index]))
  170. """
  171. # Each net goes from CPU to four memory chips, these are the 4 lengths from
  172. # CPU to each of the for memory chip balls/pads, some of these journies are
  173. # common with one another but branch off at each T.
  174. lens[0] += len_t1 + len_t2_1
  175. lens[1] += len_t1 + len_t2_1
  176. lens[2] += len_t1 + len_t2_2
  177. lens[3] += len_t1 + len_t2_2
  178. for index, total_len in enumerate(lens):
  179. delta = total_len - target_length
  180. if delta > tolerance:
  181. print_color( BRIGHTRED, "%s %s len:%.0f long by %.0f mils" %
  182. (netname, pad_name(cpad[index]), pcbnew.ToMils(total_len), pcbnew.ToMils(delta - tolerance) ))
  183. elif delta < -tolerance:
  184. print_color( BRIGHTRED, "%s %s len:%.0f short by %.0f mils" %
  185. (netname, pad_name(cpad[index]), pcbnew.ToMils(total_len), pcbnew.ToMils(tolerance - delta) ))
  186. def load_board_and_report_lengths(filename):
  187. global pcb
  188. pcb = pcbnew.LoadBoard(filename)
  189. pcb.BuildListOfNets() # required so 'pcb' contains valid netclass data
  190. global nets
  191. nets = pcb.GetNetsByName()
  192. for netname in ADDR_AND_CMD_NETS:
  193. report_teed_lengths("addr_and_cmd", netname, ADDR_AND_CMD_LEN, ADDR_AND_CMD_TOLERANCE)
  194. for netname in CONTROL_NETS:
  195. report_teed_lengths("control", netname, CONTROL_LEN, CONTROL_TOLERANCE)
  196. if __name__ == "__main__":
  197. try:
  198. boardfile = sys.argv[1]
  199. except IndexError:
  200. print("Usage: %s <boardname.kicad_pcb> [--once]" % sys.argv[0])
  201. sys.exit(1)
  202. first = True
  203. while True:
  204. # wait for the file contents to change
  205. lastmtime = os.path.getmtime(boardfile)
  206. mtime = lastmtime
  207. while mtime == lastmtime and not first:
  208. try:
  209. mtime = os.path.getmtime(boardfile)
  210. except OSError:
  211. pass # kicad save process seems to momentarily delete file, so there's a race here with "No such file.."
  212. time.sleep(0.5)
  213. # The "Debug" build of pcbnew writes to disk slowy, new file takes time to get to disk.
  214. time.sleep(1)
  215. first = False
  216. print( '\033[2J' ) # clear screen, maybe
  217. load_board_and_report_lengths(boardfile)
  218. if "--once" in sys.argv:
  219. sys.exit(0)