interface.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. import curses
  2. import math
  3. from quartus_tcl import INSYS_SPI_ERROR, INSYS_MEM_ERROR, QuartusTCL, QUATUS_TCL_TIMEOUT
  4. import oisc8asm
  5. def render_list(w, items, offset=3):
  6. selected = 0
  7. while True:
  8. w.refresh()
  9. curses.doupdate()
  10. index = 0
  11. for item in items:
  12. mode = curses.A_REVERSE if index == selected else curses.A_NORMAL
  13. w.addstr(offset + index, 2, item, mode)
  14. index += 1
  15. key = w.getch()
  16. if key in [curses.KEY_ENTER, ord('\n')]:
  17. break
  18. elif key == curses.KEY_UP:
  19. selected -= 1
  20. if selected < 0:
  21. selected = len(items) - 1
  22. elif key == curses.KEY_DOWN:
  23. selected += 1
  24. if selected >= len(items):
  25. selected = 0
  26. elif key == curses.KEY_BACKSPACE or key == 27 or key == ord('q'): # esc | alt | q
  27. selected = -1
  28. break
  29. return selected
  30. def reprint_header(w, q=None, hw=None, dev=None):
  31. w.addstr(0, 0, "Processor debugging interface", curses.color_pair(1))
  32. if q is not None:
  33. w.addstr(1, 0, "Connected: ", curses.A_BOLD)
  34. w.addstr(q.version)
  35. w.addstr(2, 0, "")
  36. if hw is not None:
  37. w.addstr("Hardware: ", curses.A_BOLD)
  38. w.addstr(hw)
  39. if dev is not None:
  40. w.addstr(" Device: ", curses.A_BOLD)
  41. w.addstr(dev)
  42. w.clrtobot()
  43. def read_probes(ps, q, pren):
  44. res = []
  45. for i, src_width, prb_width, name in ps:
  46. value = []
  47. if pren and src_width > 0:
  48. try:
  49. value.append(q.read_source_data(i, True))
  50. except INSYS_SPI_ERROR:
  51. value.append('ERR')
  52. if pren and prb_width > 0:
  53. try:
  54. value.append(q.read_probe_data(i, True))
  55. except INSYS_SPI_ERROR:
  56. value.append('ERR')
  57. if len(value) == 0:
  58. value.append('??')
  59. res.append(value)
  60. return res
  61. def memeditor(w, q, hw, dev, mem):
  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] = str(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 oisc_comments(probe, value):
  286. try:
  287. hexdata = value[0]
  288. if len(hexdata) % 2 == 1:
  289. hexdata = "0" + hexdata
  290. data = bytes.fromhex(hexdata)
  291. except ValueError:
  292. return None
  293. except IndexError:
  294. return None
  295. if probe == "INST":
  296. val = oisc8asm.asmc.decompile(data).split('\n')[0]
  297. val = ' '.join(list(filter(None, val.split(' ')))[1:3])
  298. return val
  299. return None
  300. def debugging_window(w, q, hw, dev, ps, mem, pren):
  301. w.addstr(3, 0, " Probes:", curses.color_pair(2) | curses.A_BOLD)
  302. w.clrtoeol()
  303. w.addstr(3, 30, "Memory:", curses.color_pair(2) | curses.A_BOLD)
  304. w.clrtobot()
  305. selected = 0
  306. srow = 0
  307. profile = False
  308. ps_map = {name: (i, src_width, prb_width) for i, src_width, prb_width, name in ps}
  309. if pren and 'RST' in ps_map and 'CLKD' in ps_map and 'MCLK' in ps_map:
  310. ## This is oisc
  311. profile = True
  312. q.write_source_data(ps_map['RST'][0], '1')
  313. q.write_source_data(ps_map['CLKD'][0], '1')
  314. q.write_source_data(ps_map['MCLK'][0], '1')
  315. q.write_source_data(ps_map['MCLK'][0], '0')
  316. q.write_source_data(ps_map['RST'][0], '0')
  317. values = read_probes(ps, q, pren)
  318. while True:
  319. try:
  320. w.refresh()
  321. curses.doupdate()
  322. index = 0
  323. # <index> <source width> <probe width> <instance name>
  324. for i, src_width, prb_width, name in ps:
  325. mode = curses.A_REVERSE if index == selected and srow == 0 else curses.A_NORMAL
  326. w.addstr(4 + index, 0, f" {name:>4} [{src_width:>2}|{prb_width:>2}]: ", curses.A_BOLD)
  327. data = values[i]
  328. for di, d in enumerate(data):
  329. if d == 'ERR':
  330. w.addstr(d, curses.color_pair(4) | mode)
  331. else:
  332. w.addstr(d, mode)
  333. if di < len(data) - 1:
  334. w.addstr('|', mode)
  335. if profile:
  336. comment = oisc_comments(name, data)
  337. if comment is not None:
  338. w.addstr(' ' + comment, curses.color_pair(1))
  339. w.clrtoeol()
  340. index += 1
  341. # <index> <depth> <width> <read/write mode> <instance type> <instance name>
  342. index = 0
  343. for i, depth, width, mode, itype, name in mem:
  344. smode = curses.A_REVERSE if index == selected and srow == 1 else curses.A_NORMAL
  345. w.addstr(4 + index, 31, f"{name:>5}: ", curses.A_BOLD)
  346. w.addstr(f"[{width}bit x {depth} {mode} {itype}]", smode)
  347. w.clrtoeol()
  348. index += 1
  349. key = w.getch()
  350. if key == ord(' '):
  351. if profile:
  352. q.write_source_data(ps_map['MCLK'][0], '0')
  353. q.write_source_data(ps_map['MCLK'][0], '1')
  354. values = read_probes(ps, q, pren)
  355. elif key in {curses.KEY_ENTER, ord('\n')}:
  356. if srow == 0 and pren:
  357. i, src_width, prb_width, name = ps[selected]
  358. # Flip single bit
  359. if src_width == 1 and values[selected][0].isdigit():
  360. new_val = 1 if values[selected][0] == '0' else 0
  361. q.write_source_data(i, new_val)
  362. values = read_probes(ps, q, pren)
  363. elif srow == 1:
  364. memeditor(w, q, hw, dev, mem[selected])
  365. w.addstr(3, 0, " Probes:", curses.color_pair(2) | curses.A_BOLD)
  366. w.clrtoeol()
  367. w.addstr(3, 30, "Memory:", curses.color_pair(2) | curses.A_BOLD)
  368. w.clrtobot()
  369. elif key == curses.KEY_LEFT or key == curses.KEY_RIGHT:
  370. srow = 0 if srow == 1 else 1
  371. if srow == 0 and selected >= len(ps):
  372. selected = len(ps) - 1
  373. elif srow == 1 and selected >= len(mem):
  374. selected = len(mem) - 1
  375. elif key == curses.KEY_UP:
  376. selected -= 1
  377. if selected < 0:
  378. selected = len(ps) - 1 if srow == 0 else len(mem) - 1
  379. elif key == curses.KEY_DOWN:
  380. selected += 1
  381. if srow == 0 and selected >= len(ps):
  382. selected = 0
  383. if srow == 1 and selected >= len(mem):
  384. selected = 0
  385. elif key == curses.KEY_BACKSPACE or key == 27 or key == ord('q'): # esc | alt | q
  386. break
  387. except curses.error:
  388. w.addstr(4, 2, "ERROR: ", curses.color_pair(4) | curses.A_BOLD)
  389. w.addstr("Terminal window is too small", curses.color_pair(4))
  390. key = w.getch()
  391. if key == curses.KEY_BACKSPACE or key == 27 or key == ord('q'): # esc | alt | q
  392. break
  393. def main(w):
  394. """ INIT """
  395. w.keypad(1)
  396. # screen.nodelay(1)
  397. curses.curs_set(0)
  398. curses.start_color()
  399. curses.use_default_colors()
  400. # panel = curses.panel.new_panel(w)
  401. # panel.hide()
  402. # curses.panel.update_panels()
  403. curses.init_pair(1, curses.COLOR_WHITE, -1)
  404. curses.init_pair(2, curses.COLOR_CYAN, -1)
  405. curses.init_pair(3, curses.COLOR_MAGENTA, -1)
  406. curses.init_pair(4, curses.COLOR_RED, -1)
  407. reprint_header(w)
  408. """ START """
  409. w.addstr(1, 0, "Connecting to Quartus Prime Signal Tap shell..")
  410. w.refresh()
  411. q = QuartusTCL()
  412. """ LIST Devices """
  413. # panel.top()
  414. # panel.show()
  415. while True:
  416. reprint_header(w, q, None, None)
  417. w.addstr(2, 1, "Loading list..")
  418. w.refresh()
  419. try:
  420. devices = {h: q.get_device_names(h) for h in q.get_hardware_names()}
  421. menu_map = [(h, d) for h, ds in devices.items() for d in ds]
  422. menu_list = list(map(lambda x: x[0] + ': ' + x[1], menu_map))
  423. w.addstr(2, 0, "")
  424. w.clrtoeol()
  425. except QUATUS_TCL_TIMEOUT:
  426. w.addstr(2, 1, "Timeout!", curses.color_pair(4) | curses.A_BOLD)
  427. w.clrtoeol()
  428. menu_list = []
  429. menu_list.append('Update list')
  430. selected = render_list(w, menu_list)
  431. if selected == len(menu_list) - 1:
  432. continue # update list
  433. if selected >= 0:
  434. w.clear()
  435. hw, dev = menu_map[selected]
  436. reprint_header(w, q, hw, dev)
  437. w.addstr(3, 2, "Checking device in-system sources and probes..")
  438. w.refresh()
  439. cont_flag = False
  440. try:
  441. spis = q.get_insystem_source_probe_instance_info(dev, hw)
  442. w.addstr(3, 2, f"Found {len(spis)} source/probe instances..")
  443. w.clrtoeol()
  444. except INSYS_SPI_ERROR as e:
  445. w.addstr(3, 2, "ERROR: ", curses.color_pair(4) | curses.A_BOLD)
  446. w.addstr(e.message, curses.color_pair(4))
  447. cont_flag = True
  448. spis = []
  449. w.refresh()
  450. w.addstr(4, 2, "Checking device in-system memory..")
  451. # Attemt to close previous connections
  452. try:
  453. q.end_memory_edit()
  454. except INSYS_MEM_ERROR:
  455. pass
  456. try:
  457. mems = q.get_editable_mem_instances(dev, hw)
  458. w.addstr(4, 2, f"Found {len(mems)} memory instances..")
  459. w.clrtoeol()
  460. except INSYS_MEM_ERROR as e:
  461. w.addstr(4, 2, "ERROR: ", curses.color_pair(4) | curses.A_BOLD)
  462. w.addstr(e.message, curses.color_pair(4))
  463. cont_flag = True
  464. mems = []
  465. w.refresh()
  466. try:
  467. q.start_insystem_source_probe(dev, hw)
  468. w.addstr(5, 2, f"In-system transactions started..")
  469. pren = True
  470. except INSYS_SPI_ERROR as e:
  471. w.addstr(5, 2, "ERROR: ", curses.color_pair(4) | curses.A_BOLD)
  472. w.addstr("Transaction setup failed: " + e.message, curses.color_pair(4))
  473. cont_flag = True
  474. pren = False
  475. w.refresh()
  476. if cont_flag:
  477. w.addstr(6, 4, "Press any key to start..", curses.color_pair(1))
  478. w.refresh()
  479. w.getch()
  480. debugging_window(w, q, hw, dev, spis, mems, pren)
  481. try:
  482. if pren:
  483. q.end_insystem_source_probe()
  484. except INSYS_SPI_ERROR as e:
  485. w.addstr(3, 2, "ERROR: ", curses.color_pair(4) | curses.A_BOLD)
  486. w.addstr(e.message, curses.color_pair(4))
  487. w.clrtobot()
  488. w.refresh()
  489. w.getch()
  490. continue
  491. break
  492. q.disconnect()
  493. # except Exception as e:
  494. # print("Unexpected error:" + e.args[0])