debugging.py 18 KB

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