debugging.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  1. import curses
  2. import math
  3. from debugging.quartus_tcl import QuartusTCL, INSYS_SPI_ERROR, INSYS_MEM_ERROR, QUATUS_TCL_TIMEOUT
  4. def render_list(w, items, offset=3):
  5. selected = 0
  6. while True:
  7. w.refresh()
  8. curses.doupdate()
  9. index = 0
  10. for item in items:
  11. mode = curses.A_REVERSE if index == selected else curses.A_NORMAL
  12. w.addstr(offset + index, 2, item, mode)
  13. index += 1
  14. key = w.getch()
  15. if key in [curses.KEY_ENTER, ord('\n')]:
  16. break
  17. elif key == curses.KEY_UP:
  18. selected -= 1
  19. if selected < 0:
  20. selected = len(items) - 1
  21. elif key == curses.KEY_DOWN:
  22. selected += 1
  23. if selected >= len(items):
  24. selected = 0
  25. elif key == curses.KEY_BACKSPACE or key == 27 or key == ord('q'): # esc | alt | q
  26. selected = -1
  27. break
  28. return selected
  29. def reprint_header(w, q=None, hw=None, dev=None):
  30. w.addstr(0, 0, "Processor debugging interface", curses.color_pair(1))
  31. if q is not None:
  32. w.addstr(1, 0, "Connected: ", curses.A_BOLD)
  33. w.addstr(q.version)
  34. w.addstr(2, 0, "")
  35. if hw is not None:
  36. w.addstr("Hardware: ", curses.A_BOLD)
  37. w.addstr(hw)
  38. if dev is not None:
  39. w.addstr(" Device: ", curses.A_BOLD)
  40. w.addstr(dev)
  41. w.clrtobot()
  42. def read_probes(ps, q, pren):
  43. res = []
  44. for i, src_width, prb_width, name in ps:
  45. value = []
  46. if pren and src_width > 0:
  47. try:
  48. value.append(q.read_source_data(i, True))
  49. except INSYS_SPI_ERROR:
  50. value.append('ERR')
  51. if pren and prb_width > 0:
  52. try:
  53. value.append(q.read_probe_data(i, True))
  54. except INSYS_SPI_ERROR:
  55. value.append('ERR')
  56. if len(value) == 0:
  57. value.append('??')
  58. res.append(value)
  59. return res
  60. def memeditor(w, q, hw, dev, mem):
  61. import traceback
  62. error = None
  63. try:
  64. memedit_window(w, q, hw, dev, mem)
  65. except INSYS_MEM_ERROR as e:
  66. error = e.args[0]
  67. except curses.error:
  68. trace = traceback.format_exc()
  69. error = 'Terminal window is too small'
  70. finally:
  71. try:
  72. q.end_memory_edit()
  73. except INSYS_MEM_ERROR:
  74. pass
  75. if error is not None:
  76. w.addstr(4, 0, "ERROR: ", curses.color_pair(4) | curses.A_BOLD)
  77. w.addstr(error, curses.color_pair(4))
  78. w.clrtobot()
  79. w.refresh()
  80. w.getch()
  81. def input_window(w, mode, x, y, length):
  82. curses.curs_set(1)
  83. index = 0
  84. new_val = [''] * length
  85. escape = False
  86. try:
  87. while True:
  88. w.refresh()
  89. curses.doupdate()
  90. for i, val in enumerate(new_val):
  91. if val == '':
  92. val = '_'
  93. w.addch(y, x + i, val, curses.A_BOLD)
  94. w.move(y, x + index)
  95. key = w.getch()
  96. if key == curses.KEY_LEFT:
  97. if index > 0:
  98. index -= 1
  99. elif key == curses.KEY_RIGHT:
  100. if index + 1 < length:
  101. index += 1
  102. elif key == curses.KEY_BACKSPACE:
  103. new_val[index] = ''
  104. if index > 0:
  105. index -= 1
  106. elif key == curses.KEY_ENTER or key == ord('\n'):
  107. break
  108. elif key == 27:
  109. escape = True
  110. break
  111. else:
  112. if (mode == 'd' and ord('0') <= key <= ord('9')) or \
  113. (mode == 'b' and ord('0') <= key <= ord('1')) or \
  114. (mode == 'h' and (ord('0') <= key <= ord('9') or ord('a') <= key <= ord('f'))) or \
  115. (mode == 'a' and 32 <= key <= 126):
  116. new_val[index] = chr(key)
  117. if index + 1 < length:
  118. index += 1
  119. except curses.error:
  120. pass
  121. curses.curs_set(0)
  122. if escape:
  123. return None
  124. return new_val
  125. def convert_to_ascii(value):
  126. """ converts binary string to ascii """
  127. r = ''
  128. for b in range(len(value) // 8):
  129. dec = int(value[b * 8:b * 8 + 8], 2)
  130. if 32 <= dec <= 126:
  131. r += chr(dec)
  132. else:
  133. r += '.'
  134. return r
  135. def read_mem(raw_mem, width, depth):
  136. data = list(reversed([raw_mem[i * width:i * width + width] for i in range(depth)]))
  137. data_hex = list(map(lambda x: format(int(x, 2), f'0{math.ceil(width / 4)}x'), data))
  138. data_ascii = data
  139. dec_format_len = len(str(2 ** width))
  140. dec_format = f'0{dec_format_len}d'
  141. if width % 8 == 0:
  142. data_ascii = list(map(lambda x: convert_to_ascii(x), data))
  143. data_dec = list(map(lambda x: format(int(x, 2), dec_format), data))
  144. return data, data_hex, data_ascii, data_dec, dec_format_len, dec_format
  145. def memedit_window(w, q, hw, dev, mem):
  146. mem_index, depth, width, mode, itype, name = mem
  147. w.addstr(3, 0, "Memory editor: ", curses.color_pair(2) | curses.A_BOLD)
  148. w.addstr(name, curses.A_BOLD)
  149. w.addstr(f" {mode} {itype} {depth}bit x {width}")
  150. iy, ix = w.getyx() # save to it can be edited later
  151. w.clrtobot()
  152. w.addstr(4, 0, "String the memory editing sequence..", curses.color_pair(1))
  153. w.refresh()
  154. q.begin_memory_edit(dev, hw)
  155. w.addstr(4, 0, "Reading memory..", curses.color_pair(1))
  156. w.clrtoeol()
  157. w.refresh()
  158. raw_mem = q.read_content_from_memory(mem_index, 0, depth)
  159. data, data_hex, data_ascii, data_dec, dec_format_len, dec_format = read_mem(raw_mem, width, depth)
  160. selected = 0
  161. offset = 0
  162. draw_mode = 'h'
  163. addr_len = math.ceil(math.log2(depth) / 4) + 2
  164. addr_format = f'0{addr_len - 2}x'
  165. moded = {}
  166. while True:
  167. w.refresh()
  168. curses.doupdate()
  169. if draw_mode == 'h':
  170. sdata = data_hex
  171. elif draw_mode == 'a':
  172. sdata = data_ascii
  173. elif draw_mode == 'd':
  174. sdata = data_dec
  175. else:
  176. sdata = data
  177. xmax, ymax = w.getmaxyx()
  178. dwidth = width
  179. if draw_mode == 'h':
  180. dwidth = math.ceil(width / 4)
  181. elif draw_mode == 'a':
  182. dwidth = width // 8
  183. elif draw_mode == 'd':
  184. dwidth = dec_format_len
  185. cols = (ymax - addr_len) // (dwidth + 1) - 1
  186. rows = xmax - 4
  187. # rows = 1
  188. info = [
  189. f'ADDR: {format(selected, addr_format)}',
  190. f'OFFSET: {offset}',
  191. f'BIN: {data[selected]}',
  192. f'HEX: {data_hex[selected]}',
  193. f'DEC: {data_dec[selected]}'
  194. ]
  195. if width % 8 == 0:
  196. info.append(f'ASCII: {convert_to_ascii(data[selected])}')
  197. w.addstr(iy, ix + 10, f"[{' '.join(info)}]", curses.color_pair(1))
  198. w.clrtoeol()
  199. for row in range(0, rows):
  200. row_off = row + offset
  201. w.addstr(4 + row, 0, format(row_off * cols, addr_format) + ": ", curses.A_BOLD)
  202. done = False
  203. for col in range(cols):
  204. index = row_off * cols + col
  205. if index + 1 > depth:
  206. w.clrtobot()
  207. done = True
  208. break
  209. smode = curses.A_REVERSE if index == selected else curses.A_NORMAL
  210. if index in moded:
  211. if draw_mode == 'h':
  212. val = format(moded[index], f'0{dwidth}x')
  213. elif draw_mode == 'a':
  214. val = convert_to_ascii(format(moded[index], f'0{width}b'))
  215. elif draw_mode == 'd':
  216. val = format(moded[index], f'0{dwidth}d')
  217. else:
  218. val = format(moded[index], f'0{width}b')
  219. w.addstr(val, curses.color_pair(3) | smode | curses.A_BOLD)
  220. else:
  221. w.addstr(sdata[index], smode)
  222. if col + 1 == cols:
  223. w.clrtoeol()
  224. else:
  225. w.addstr(' ')
  226. if done:
  227. break
  228. selected_row = selected // cols - offset
  229. if selected > cols and selected_row == 0:
  230. offset -= 1
  231. elif selected_row == rows - 2:
  232. offset += 1
  233. key = w.getch()
  234. if key == ord(' '):
  235. raw_mem = q.read_content_from_memory(mem_index, 0, depth)
  236. data, data_hex, data_ascii, data_dec, dec_format_len, dec_format = read_mem(raw_mem, width, depth)
  237. elif key == ord('h'):
  238. draw_mode = 'h'
  239. elif key == ord('b'):
  240. draw_mode = 'b'
  241. elif key == ord('d'):
  242. draw_mode = 'd'
  243. elif key == ord('a') and width % 8 == 0:
  244. draw_mode = 'a'
  245. elif key == ord('r'):
  246. if selected in moded:
  247. del moded[selected]
  248. elif key in {curses.KEY_ENTER, ord('\n')}:
  249. row = selected//cols
  250. col = selected - cols*row
  251. current_val = sdata[selected]
  252. res = input_window(w, draw_mode, col * (dwidth+1) + addr_len, row+4, dwidth)
  253. new_val = ''.join([res[i] if res[i] != '' else current_val[i] for i in range(len(current_val))])
  254. try:
  255. if draw_mode == 'h':
  256. moded[selected] = int(new_val, 16)
  257. elif draw_mode == 'a':
  258. moded[selected] = 0
  259. for i in range(len(new_val)):
  260. moded[selected] |= ord(new_val[i]) << ((len(new_val)-1-i)*8)
  261. elif draw_mode == 'd':
  262. moded[selected] = int(new_val)
  263. else:
  264. moded[selected] = int(new_val, 2)
  265. except ValueError:
  266. pass
  267. elif key == curses.KEY_LEFT:
  268. if selected > 0:
  269. selected -= 1
  270. elif key == curses.KEY_RIGHT:
  271. if selected + 1 < len(data):
  272. selected += 1
  273. elif key == curses.KEY_UP:
  274. if selected - cols >= 0:
  275. selected -= cols
  276. elif key == curses.KEY_DOWN:
  277. if selected + cols < len(data):
  278. selected += cols
  279. elif key == curses.KEY_BACKSPACE or key == 27 or key == ord('q'):
  280. return
  281. def debugging_window(w, q, hw, dev, ps, mem, pren):
  282. w.addstr(3, 0, " Probes:", curses.color_pair(2) | curses.A_BOLD)
  283. w.clrtoeol()
  284. w.addstr(3, 30, "Memory:", curses.color_pair(2) | curses.A_BOLD)
  285. w.clrtobot()
  286. selected = 0
  287. srow = 0
  288. profile = False
  289. ps_map = {name: (i, src_width, prb_width) for i, src_width, prb_width, name in ps}
  290. if pren and 'RST' in ps_map and 'CLKD' in ps_map and 'MCLK' in ps_map:
  291. profile = True
  292. q.write_source_data(ps_map['RST'][0], '1')
  293. q.write_source_data(ps_map['CLKD'][0], '1')
  294. q.write_source_data(ps_map['MCLK'][0], '1')
  295. q.write_source_data(ps_map['MCLK'][0], '0')
  296. q.write_source_data(ps_map['RST'][0], '0')
  297. values = read_probes(ps, q, pren)
  298. while True:
  299. try:
  300. w.refresh()
  301. curses.doupdate()
  302. index = 0
  303. # <index> <source width> <probe width> <instance name>
  304. for i, src_width, prb_width, name in ps:
  305. mode = curses.A_REVERSE if index == selected and srow == 0 else curses.A_NORMAL
  306. w.addstr(4 + index, 0, f" {name:>4} [{src_width:>2}|{prb_width:>2}]: ", curses.A_BOLD)
  307. data = values[i]
  308. for di, d in enumerate(data):
  309. if d == 'ERR':
  310. w.addstr(d, curses.color_pair(4) | mode)
  311. else:
  312. w.addstr(d, mode)
  313. if di < len(data) - 1:
  314. w.addstr('|', mode)
  315. w.clrtoeol()
  316. index += 1
  317. # <index> <depth> <width> <read/write mode> <instance type> <instance name>
  318. index = 0
  319. for i, depth, width, mode, itype, name in mem:
  320. smode = curses.A_REVERSE if index == selected and srow == 1 else curses.A_NORMAL
  321. w.addstr(4 + index, 31, f"{name:>5}: ", curses.A_BOLD)
  322. w.addstr(f"[{width}bit x {depth} {mode} {itype}]", smode)
  323. w.clrtoeol()
  324. index += 1
  325. key = w.getch()
  326. if key == ord(' '):
  327. if profile:
  328. q.write_source_data(ps_map['MCLK'][0], '0')
  329. q.write_source_data(ps_map['MCLK'][0], '1')
  330. values = read_probes(ps, q, pren)
  331. elif key in {curses.KEY_ENTER, ord('\n')}:
  332. if srow == 0 and pren:
  333. i, src_width, prb_width, name = ps[selected]
  334. # Flip single bit
  335. if src_width == 1 and values[selected][0].isdigit():
  336. new_val = 1 if values[selected][0] == '0' else 0
  337. q.write_source_data(i, new_val)
  338. values = read_probes(ps, q, pren)
  339. elif srow == 1:
  340. memeditor(w, q, hw, dev, mem[selected])
  341. w.addstr(3, 0, " Probes:", curses.color_pair(2) | curses.A_BOLD)
  342. w.clrtoeol()
  343. w.addstr(3, 30, "Memory:", curses.color_pair(2) | curses.A_BOLD)
  344. w.clrtobot()
  345. elif key == curses.KEY_LEFT or key == curses.KEY_RIGHT:
  346. srow = 0 if srow == 1 else 1
  347. if srow == 0 and selected >= len(ps):
  348. selected = len(ps) - 1
  349. elif srow == 1 and selected >= len(mem):
  350. selected = len(mem) - 1
  351. elif key == curses.KEY_UP:
  352. selected -= 1
  353. if selected < 0:
  354. selected = len(ps) - 1 if srow == 0 else len(mem) - 1
  355. elif key == curses.KEY_DOWN:
  356. selected += 1
  357. if srow == 0 and selected >= len(ps):
  358. selected = 0
  359. if srow == 1 and selected >= len(mem):
  360. selected = 0
  361. elif key == curses.KEY_BACKSPACE or key == 27 or key == ord('q'): # esc | alt | q
  362. break
  363. except curses.error:
  364. w.addstr(4, 2, "ERROR: ", curses.color_pair(4) | curses.A_BOLD)
  365. w.addstr("Terminal window is too small", curses.color_pair(4))
  366. key = w.getch()
  367. if key == curses.KEY_BACKSPACE or key == 27 or key == ord('q'): # esc | alt | q
  368. break
  369. def main(w):
  370. """ INIT """
  371. w.keypad(1)
  372. # screen.nodelay(1)
  373. curses.curs_set(0)
  374. curses.start_color()
  375. curses.use_default_colors()
  376. # panel = curses.panel.new_panel(w)
  377. # panel.hide()
  378. # curses.panel.update_panels()
  379. curses.init_pair(1, curses.COLOR_WHITE, -1)
  380. curses.init_pair(2, curses.COLOR_CYAN, -1)
  381. curses.init_pair(3, curses.COLOR_MAGENTA, -1)
  382. curses.init_pair(4, curses.COLOR_RED, -1)
  383. reprint_header(w)
  384. """ START """
  385. w.addstr(1, 0, "Connecting to Quartus Prime Signal Tap shell..")
  386. w.refresh()
  387. q = QuartusTCL()
  388. """ LIST Devices """
  389. # panel.top()
  390. # panel.show()
  391. while True:
  392. reprint_header(w, q, None, None)
  393. w.addstr(2, 1, "Loading list..")
  394. w.refresh()
  395. try:
  396. devices = {h: q.get_device_names(h) for h in q.get_hardware_names()}
  397. menu_map = [(h, d) for h, ds in devices.items() for d in ds]
  398. menu_list = list(map(lambda x: x[0] + ': ' + x[1], menu_map))
  399. w.addstr(2, 0, "")
  400. w.clrtoeol()
  401. except QUATUS_TCL_TIMEOUT:
  402. w.addstr(2, 1, "Timeout!", curses.color_pair(4) | curses.A_BOLD)
  403. w.clrtoeol()
  404. menu_list = []
  405. menu_list.append('Update list')
  406. selected = render_list(w, menu_list)
  407. if selected == len(menu_list) - 1:
  408. continue # update list
  409. if selected >= 0:
  410. w.clear()
  411. hw, dev = menu_map[selected]
  412. reprint_header(w, q, hw, dev)
  413. w.addstr(3, 2, "Checking device in-system sources and probes..")
  414. w.refresh()
  415. try:
  416. spis = q.get_insystem_source_probe_instance_info(dev, hw)
  417. w.addstr(3, 2, f"Found {len(spis)} source/probe instances..")
  418. w.clrtoeol()
  419. except INSYS_SPI_ERROR as e:
  420. w.addstr(3, 2, "ERROR: ", curses.color_pair(4) | curses.A_BOLD)
  421. w.addstr(e.message, curses.color_pair(4))
  422. spis = []
  423. w.refresh()
  424. w.addstr(4, 2, "Checking device in-system memory..")
  425. try:
  426. mems = q.get_editable_mem_instances(dev, hw)
  427. w.addstr(4, 2, f"Found {len(mems)} memory instances..")
  428. w.clrtoeol()
  429. except INSYS_MEM_ERROR as e:
  430. w.addstr(4, 2, "ERROR: ", curses.color_pair(4) | curses.A_BOLD)
  431. w.addstr(e.message, curses.color_pair(4))
  432. mems = []
  433. w.refresh()
  434. try:
  435. q.start_insystem_source_probe(dev, hw)
  436. w.addstr(5, 2, f"In-system transactions started..")
  437. pren = True
  438. except INSYS_SPI_ERROR as e:
  439. w.addstr(5, 2, "ERROR: ", curses.color_pair(4) | curses.A_BOLD)
  440. w.addstr("Transaction setup failed: " + e.message, curses.color_pair(4))
  441. pren = False
  442. w.refresh()
  443. w.addstr(6, 4, "Press any key to start..", curses.color_pair(1))
  444. w.getch()
  445. debugging_window(w, q, hw, dev, spis, mems, pren)
  446. try:
  447. if pren:
  448. q.end_insystem_source_probe()
  449. except INSYS_SPI_ERROR as e:
  450. w.addstr(3, 2, "ERROR: ", curses.color_pair(4) | curses.A_BOLD)
  451. w.addstr(e.message, curses.color_pair(4))
  452. w.clrtobot()
  453. w.refresh()
  454. w.getch()
  455. continue
  456. break
  457. q.disconnect()
  458. if __name__ == '__main__':
  459. print_last = ""
  460. try:
  461. curses.wrapper(main)
  462. # q = QuartusTCL()
  463. # q.list_devices()
  464. # q.disconnect()
  465. except KeyboardInterrupt:
  466. print('Interrupt!')
  467. except curses.error as e:
  468. print('Failed to start curses: ' + e.args[0])
  469. # except Exception as e:
  470. # print("Unexpected error:" + e.args[0])