import curses import math from quartus_tcl import INSYS_SPI_ERROR, INSYS_MEM_ERROR, QuartusTCL, QUATUS_TCL_TIMEOUT import oisc8asm def render_list(w, items, offset=3): selected = 0 while True: w.refresh() curses.doupdate() index = 0 for item in items: mode = curses.A_REVERSE if index == selected else curses.A_NORMAL w.addstr(offset + index, 2, item, mode) index += 1 key = w.getch() if key in [curses.KEY_ENTER, ord('\n')]: break elif key == curses.KEY_UP: selected -= 1 if selected < 0: selected = len(items) - 1 elif key == curses.KEY_DOWN: selected += 1 if selected >= len(items): selected = 0 elif key == curses.KEY_BACKSPACE or key == 27 or key == ord('q'): # esc | alt | q selected = -1 break return selected def reprint_header(w, q=None, hw=None, dev=None): w.addstr(0, 0, "Processor debugging interface", curses.color_pair(1)) if q is not None: w.addstr(1, 0, "Connected: ", curses.A_BOLD) w.addstr(q.version) w.addstr(2, 0, "") if hw is not None: w.addstr("Hardware: ", curses.A_BOLD) w.addstr(hw) if dev is not None: w.addstr(" Device: ", curses.A_BOLD) w.addstr(dev) w.clrtobot() def read_probes(ps, q, pren): res = [] for i, src_width, prb_width, name in ps: value = [] if pren and src_width > 0: try: value.append(q.read_source_data(i, True)) except INSYS_SPI_ERROR: value.append('ERR') if pren and prb_width > 0: try: value.append(q.read_probe_data(i, True)) except INSYS_SPI_ERROR: value.append('ERR') if len(value) == 0: value.append('??') res.append(value) return res def memeditor(w, q, hw, dev, mem): error = None try: memedit_window(w, q, hw, dev, mem) except INSYS_MEM_ERROR as e: error = e.args[0] except curses.error: error = 'Terminal window is too small' finally: try: q.end_memory_edit() except INSYS_MEM_ERROR: pass if error is not None: w.addstr(4, 0, "ERROR: ", curses.color_pair(4) | curses.A_BOLD) w.addstr(error, curses.color_pair(4)) w.clrtobot() w.refresh() w.getch() def input_window(w, mode, x, y, length): curses.curs_set(1) index = 0 new_val = [''] * length escape = False try: while True: w.refresh() curses.doupdate() for i, val in enumerate(new_val): if val == '': val = '_' w.addch(y, x + i, val, curses.A_BOLD) w.move(y, x + index) key = w.getch() if key == curses.KEY_LEFT: if index > 0: index -= 1 elif key == curses.KEY_RIGHT: if index + 1 < length: index += 1 elif key == curses.KEY_BACKSPACE: new_val[index] = '' if index > 0: index -= 1 elif key == curses.KEY_ENTER or key == ord('\n'): break elif key == 27: escape = True break else: if (mode == 'd' and ord('0') <= key <= ord('9')) or \ (mode == 'b' and ord('0') <= key <= ord('1')) or \ (mode == 'h' and (ord('0') <= key <= ord('9') or ord('a') <= key <= ord('f'))) or \ (mode == 'a' and 32 <= key <= 126): new_val[index] = chr(key) if index + 1 < length: index += 1 except curses.error: pass curses.curs_set(0) if escape: return None return new_val def convert_to_ascii(value): """ converts binary string to ascii """ r = '' for b in range(len(value) // 8): dec = int(value[b * 8:b * 8 + 8], 2) if 32 <= dec <= 126: r += chr(dec) else: r += '.' return r def read_mem(raw_mem, width, depth): data = list(reversed([raw_mem[i * width:i * width + width] for i in range(depth)])) data_hex = list(map(lambda x: format(int(x, 2), f'0{math.ceil(width / 4)}x'), data)) data_ascii = data dec_format_len = len(str(2 ** width)) dec_format = f'0{dec_format_len}d' if width % 8 == 0: data_ascii = list(map(lambda x: convert_to_ascii(x), data)) data_dec = list(map(lambda x: format(int(x, 2), dec_format), data)) return data, data_hex, data_ascii, data_dec, dec_format_len, dec_format def memedit_window(w, q, hw, dev, mem): mem_index, depth, width, mode, itype, name = mem w.addstr(3, 0, "Memory editor: ", curses.color_pair(2) | curses.A_BOLD) w.addstr(name, curses.A_BOLD) w.addstr(f" {mode} {itype} {depth}bit x {width}") iy, ix = w.getyx() # save to it can be edited later w.clrtobot() w.addstr(4, 0, "String the memory editing sequence..", curses.color_pair(1)) w.refresh() q.begin_memory_edit(dev, hw) w.addstr(4, 0, "Reading memory..", curses.color_pair(1)) w.clrtoeol() w.refresh() raw_mem = q.read_content_from_memory(mem_index, 0, depth) data, data_hex, data_ascii, data_dec, dec_format_len, dec_format = read_mem(raw_mem, width, depth) selected = 0 offset = 0 draw_mode = 'h' addr_len = math.ceil(math.log2(depth) / 4) + 2 addr_format = f'0{addr_len - 2}x' moded = {} while True: w.refresh() curses.doupdate() if draw_mode == 'h': sdata = data_hex elif draw_mode == 'a': sdata = data_ascii elif draw_mode == 'd': sdata = data_dec else: sdata = data xmax, ymax = w.getmaxyx() dwidth = width if draw_mode == 'h': dwidth = math.ceil(width / 4) elif draw_mode == 'a': dwidth = width // 8 elif draw_mode == 'd': dwidth = dec_format_len cols = (ymax - addr_len) // (dwidth + 1) - 1 rows = xmax - 4 # rows = 1 info = [ f'ADDR: {format(selected, addr_format)}', f'OFFSET: {offset}', f'BIN: {data[selected]}', f'HEX: {data_hex[selected]}', f'DEC: {data_dec[selected]}' ] if width % 8 == 0: info.append(f'ASCII: {convert_to_ascii(data[selected])}') w.addstr(iy, ix + 10, f"[{' '.join(info)}]", curses.color_pair(1)) w.clrtoeol() for row in range(0, rows): row_off = row + offset w.addstr(4 + row, 0, format(row_off * cols, addr_format) + ": ", curses.A_BOLD) done = False for col in range(cols): index = row_off * cols + col if index + 1 > depth: w.clrtobot() done = True break smode = curses.A_REVERSE if index == selected else curses.A_NORMAL if index in moded: if draw_mode == 'h': val = format(moded[index], f'0{dwidth}x') elif draw_mode == 'a': val = convert_to_ascii(format(moded[index], f'0{width}b')) elif draw_mode == 'd': val = format(moded[index], f'0{dwidth}d') else: val = format(moded[index], f'0{width}b') w.addstr(val, curses.color_pair(3) | smode | curses.A_BOLD) else: w.addstr(sdata[index], smode) if col + 1 == cols: w.clrtoeol() else: w.addstr(' ') if done: break selected_row = selected // cols - offset if selected > cols and selected_row == 0: offset -= 1 elif selected_row == rows - 2: offset += 1 key = w.getch() if key == ord(' '): raw_mem = q.read_content_from_memory(mem_index, 0, depth) data, data_hex, data_ascii, data_dec, dec_format_len, dec_format = read_mem(raw_mem, width, depth) elif key == ord('h'): draw_mode = 'h' elif key == ord('b'): draw_mode = 'b' elif key == ord('d'): draw_mode = 'd' elif key == ord('a') and width % 8 == 0: draw_mode = 'a' elif key == ord('r'): if selected in moded: del moded[selected] elif key == ord('s'): for i, val in moded.items(): data[i] = str(val) content = ''.join(list(reversed(data))) q.write_content_to_memory(mem_index, 0, depth, content) elif key in {curses.KEY_ENTER, ord('\n')}: row = selected//cols col = selected - cols*row current_val = sdata[selected] res = input_window(w, draw_mode, col * (dwidth+1) + addr_len, row+4, dwidth) new_val = ''.join([res[i] if res[i] != '' else current_val[i] for i in range(len(current_val))]) try: if draw_mode == 'h': moded[selected] = int(new_val, 16) elif draw_mode == 'a': moded[selected] = 0 for i in range(len(new_val)): moded[selected] |= ord(new_val[i]) << ((len(new_val)-1-i)*8) elif draw_mode == 'd': moded[selected] = int(new_val) else: moded[selected] = int(new_val, 2) except ValueError: pass elif key == curses.KEY_LEFT: if selected > 0: selected -= 1 elif key == curses.KEY_RIGHT: if selected + 1 < len(data): selected += 1 elif key == curses.KEY_UP: if selected - cols >= 0: selected -= cols elif key == curses.KEY_DOWN: if selected + cols < len(data): selected += cols elif key == curses.KEY_BACKSPACE or key == 27 or key == ord('q'): return def oisc_comments(probe, value): try: hexdata = value[0] if len(hexdata) % 2 == 1: hexdata = "0" + hexdata data = bytes.fromhex(hexdata) except ValueError: return None except IndexError: return None if probe == "INST": val = oisc8asm.asmc.decompile(data).split('\n')[0] val = ' '.join(list(filter(None, val.split(' ')))[1:3]) return val return None def debugging_window(w, q, hw, dev, ps, mem, pren): w.addstr(3, 0, " Probes:", curses.color_pair(2) | curses.A_BOLD) w.clrtoeol() w.addstr(3, 30, "Memory:", curses.color_pair(2) | curses.A_BOLD) w.clrtobot() selected = 0 srow = 0 profile = False ps_map = {name: (i, src_width, prb_width) for i, src_width, prb_width, name in ps} if pren and 'RST' in ps_map and 'CLKD' in ps_map and 'MCLK' in ps_map: ## This is oisc profile = True q.write_source_data(ps_map['RST'][0], '1') q.write_source_data(ps_map['CLKD'][0], '1') q.write_source_data(ps_map['MCLK'][0], '1') q.write_source_data(ps_map['MCLK'][0], '0') q.write_source_data(ps_map['RST'][0], '0') values = read_probes(ps, q, pren) while True: try: w.refresh() curses.doupdate() index = 0 # for i, src_width, prb_width, name in ps: mode = curses.A_REVERSE if index == selected and srow == 0 else curses.A_NORMAL w.addstr(4 + index, 0, f" {name:>4} [{src_width:>2}|{prb_width:>2}]: ", curses.A_BOLD) data = values[i] for di, d in enumerate(data): if d == 'ERR': w.addstr(d, curses.color_pair(4) | mode) else: w.addstr(d, mode) if di < len(data) - 1: w.addstr('|', mode) if profile: comment = oisc_comments(name, data) if comment is not None: w.addstr(' ' + comment, curses.color_pair(1)) w.clrtoeol() index += 1 # index = 0 for i, depth, width, mode, itype, name in mem: smode = curses.A_REVERSE if index == selected and srow == 1 else curses.A_NORMAL w.addstr(4 + index, 31, f"{name:>5}: ", curses.A_BOLD) w.addstr(f"[{width}bit x {depth} {mode} {itype}]", smode) w.clrtoeol() index += 1 key = w.getch() if key == ord(' '): if profile: q.write_source_data(ps_map['MCLK'][0], '0') q.write_source_data(ps_map['MCLK'][0], '1') values = read_probes(ps, q, pren) elif key in {curses.KEY_ENTER, ord('\n')}: if srow == 0 and pren: i, src_width, prb_width, name = ps[selected] # Flip single bit if src_width == 1 and values[selected][0].isdigit(): new_val = 1 if values[selected][0] == '0' else 0 q.write_source_data(i, new_val) values = read_probes(ps, q, pren) elif srow == 1: memeditor(w, q, hw, dev, mem[selected]) w.addstr(3, 0, " Probes:", curses.color_pair(2) | curses.A_BOLD) w.clrtoeol() w.addstr(3, 30, "Memory:", curses.color_pair(2) | curses.A_BOLD) w.clrtobot() elif key == curses.KEY_LEFT or key == curses.KEY_RIGHT: srow = 0 if srow == 1 else 1 if srow == 0 and selected >= len(ps): selected = len(ps) - 1 elif srow == 1 and selected >= len(mem): selected = len(mem) - 1 elif key == curses.KEY_UP: selected -= 1 if selected < 0: selected = len(ps) - 1 if srow == 0 else len(mem) - 1 elif key == curses.KEY_DOWN: selected += 1 if srow == 0 and selected >= len(ps): selected = 0 if srow == 1 and selected >= len(mem): selected = 0 elif key == curses.KEY_BACKSPACE or key == 27 or key == ord('q'): # esc | alt | q break except curses.error: w.addstr(4, 2, "ERROR: ", curses.color_pair(4) | curses.A_BOLD) w.addstr("Terminal window is too small", curses.color_pair(4)) key = w.getch() if key == curses.KEY_BACKSPACE or key == 27 or key == ord('q'): # esc | alt | q break def main(w): """ INIT """ w.keypad(1) # screen.nodelay(1) curses.curs_set(0) curses.start_color() curses.use_default_colors() # panel = curses.panel.new_panel(w) # panel.hide() # curses.panel.update_panels() curses.init_pair(1, curses.COLOR_WHITE, -1) curses.init_pair(2, curses.COLOR_CYAN, -1) curses.init_pair(3, curses.COLOR_MAGENTA, -1) curses.init_pair(4, curses.COLOR_RED, -1) reprint_header(w) """ START """ w.addstr(1, 0, "Connecting to Quartus Prime Signal Tap shell..") w.refresh() q = QuartusTCL() """ LIST Devices """ # panel.top() # panel.show() while True: reprint_header(w, q, None, None) w.addstr(2, 1, "Loading list..") w.refresh() try: devices = {h: q.get_device_names(h) for h in q.get_hardware_names()} menu_map = [(h, d) for h, ds in devices.items() for d in ds] menu_list = list(map(lambda x: x[0] + ': ' + x[1], menu_map)) w.addstr(2, 0, "") w.clrtoeol() except QUATUS_TCL_TIMEOUT: w.addstr(2, 1, "Timeout!", curses.color_pair(4) | curses.A_BOLD) w.clrtoeol() menu_list = [] menu_list.append('Update list') selected = render_list(w, menu_list) if selected == len(menu_list) - 1: continue # update list if selected >= 0: w.clear() hw, dev = menu_map[selected] reprint_header(w, q, hw, dev) w.addstr(3, 2, "Checking device in-system sources and probes..") w.refresh() cont_flag = False try: spis = q.get_insystem_source_probe_instance_info(dev, hw) w.addstr(3, 2, f"Found {len(spis)} source/probe instances..") w.clrtoeol() except INSYS_SPI_ERROR as e: w.addstr(3, 2, "ERROR: ", curses.color_pair(4) | curses.A_BOLD) w.addstr(e.message, curses.color_pair(4)) cont_flag = True spis = [] w.refresh() w.addstr(4, 2, "Checking device in-system memory..") # Attemt to close previous connections try: q.end_memory_edit() except INSYS_MEM_ERROR: pass try: mems = q.get_editable_mem_instances(dev, hw) w.addstr(4, 2, f"Found {len(mems)} memory instances..") w.clrtoeol() except INSYS_MEM_ERROR as e: w.addstr(4, 2, "ERROR: ", curses.color_pair(4) | curses.A_BOLD) w.addstr(e.message, curses.color_pair(4)) cont_flag = True mems = [] w.refresh() try: q.start_insystem_source_probe(dev, hw) w.addstr(5, 2, f"In-system transactions started..") pren = True except INSYS_SPI_ERROR as e: w.addstr(5, 2, "ERROR: ", curses.color_pair(4) | curses.A_BOLD) w.addstr("Transaction setup failed: " + e.message, curses.color_pair(4)) cont_flag = True pren = False w.refresh() if cont_flag: w.addstr(6, 4, "Press any key to start..", curses.color_pair(1)) w.refresh() w.getch() debugging_window(w, q, hw, dev, spis, mems, pren) try: if pren: q.end_insystem_source_probe() except INSYS_SPI_ERROR as e: w.addstr(3, 2, "ERROR: ", curses.color_pair(4) | curses.A_BOLD) w.addstr(e.message, curses.color_pair(4)) w.clrtobot() w.refresh() w.getch() continue break q.disconnect() # except Exception as e: # print("Unexpected error:" + e.args[0])