Source code for riscv_debug_bfms.riscv_debug_bfm

#****************************************************************************
#* riscv_debug_bfm.py
#*
#****************************************************************************
from enum import Enum, auto, IntEnum

from elftools.elf.elffile import ELFFile
from elftools.elf.sections import SymbolTableSection
import pybfms


class RiscvDebugTraceLevel(IntEnum):
    Call = 0
    Jump = 1
    All = 2
    
[docs]@pybfms.bfm(hdl={ pybfms.BfmType.Verilog : pybfms.bfm_hdl_path(__file__, "hdl/riscv_debug_bfm.v"), pybfms.BfmType.SystemVerilog : pybfms.bfm_hdl_path(__file__, "hdl/riscv_debug_bfm.v"), }, has_init=True) class RiscvDebugBfm(): def __init__(self): self.busy = pybfms.lock() self.is_reset = False self.reset_ev = pybfms.event() self.en_disasm = True # Track the current values of the strings self.disasm_s = "" self.func_s = "" self.regs = [0]*32 self.addr2sym_m = {} self.sym2addr_m = {} self.instr_exec_f = set() self.elffile = None self.elffile_fp = None self.symtab = None self.callstack = [] self.frame_idx = 0 self.last_instr = 0 self.last_limit = 0 self.entry_exit_addr2cb_m = {} self.entry_exit_cb2addr_m = {} pass
[docs] async def on_entry(self, sym_or_addr): """ Waits for a function identified by name or symbol to be entered""" target_addr_s = set() if isinstance(sym_or_addr, str): # It's a symbol if sym_or_addr in self.sym2addr_m.keys(): target_addr_s.add(self.sym2addr_m[sym_or_addr]) else: raise Exception("Symbol \"" + sym_or_addr + "\" not found") elif isinstance(sym_or_addr, list): for e in sym_or_addr: if isinstance(e, str): # It's a symbol if e in self.sym2addr_m.keys(): target_addr_s.add(self.sym2addr_m[e]) else: raise Exception("Symbol \"" + e + "\" not found") else: # It's an address target_addr_s.add(sym_or_addr) ev = pybfms.event() def waiter(pc, sym, is_entry): if is_entry and pc in target_addr_s: ev.set() for a in target_addr_s: if a in self.entry_exit_addr2cb_m.keys(): self.entry_exit_addr2cb_m[a].append(waiter) else: self.entry_exit_addr2cb_m[a] = [waiter] await ev.wait() for a in target_addr_s: self.entry_exit_addr2cb_m[a].remove(waiter)
[docs] async def on_exit(self, sym_or_addr): """ Waits for a function identified by name or symbol to be entered""" target_addr_s = set() if isinstance(sym_or_addr, str): # It's a symbol if sym_or_addr in self.sym2addr_m.keys(): target_addr_s.add(self.sym2addr_m[sym_or_addr]) else: raise Exception("Symbol \"" + sym_or_addr + "\" not found") elif isinstance(sym_or_addr, list): for e in sym_or_addr: if isinstance(e, str): # It's a symbol if e in self.sym2addr_m.keys(): target_addr_s.add(self.sym2addr_m[e]) else: raise Exception("Symbol \"" + e + "\" not found") else: # It's an address target_addr_s.add(sym_or_addr) ev = pybfms.event() def waiter(pc, sym, is_entry): if is_entry and pc in target_addr_s: ev.set() for a in target_addr_s: if a in self.entry_exit_addr2cb_m.keys(): self.entry_exit_addr2cb_m[a].append(waiter) else: self.entry_exit_addr2cb_m[a] = [waiter] await ev.wait() for a in target_addr_s: self.entry_exit_addr2cb_m[a].remove(waiter)
[docs] def trace_level(self, l : RiscvDebugTraceLevel): self._set_trace_level(int(l))
[docs] def load_elf(self, elf_path): """ Specifies the software image running on the core this BFM monitors """ # Load ELF and extract symbols self.elffile_fp = open(elf_path, "rb") self.elffile = ELFFile(self.elffile_fp) self.symtab = self.elffile.get_section_by_name('.symtab') for i in range(self.symtab.num_symbols()): sym = self.symtab.get_symbol(i) if sym.name != "": self.addr2sym_m[sym["st_value"]] = sym.name self.sym2addr_m[sym.name] = sym["st_value"]
[docs] def add_sym_cb(self, name, f): if self.elffile is None: raise Exception("No ELF file loaded. Cannot set symbol callback") sym = self.symtab.get_symbol_by_name(name) if sym is None: raise Exception("Symbol \"" + name + "\" does not exist"); pass
[docs] def add_enter_exit_cb(self, f): self.enter_exit_cb.append(f)
[docs] def add_instr_exec_cb(self, f): self.instr_exec_f.add(f)
[docs] def del_instr_exec_cb(self, f): self.instr_exec_f.remove(f)
[docs] def reg(self, addr): """Gets the value of the specified register""" return self.regs[addr]
[docs] async def wait_exec(self, addrs, max): """ Wait until one of a set of addresses is executed or a maximum number of instructions have been executed """ ev = pybfms.event() result = [] addr_v = [] for a in addrs: if isinstance(a, str): if self.elffile is None: raise Exception("No ELF file loaded") sym = self.symtab.get_symbol_by_name(a) if sym is None: raise Exception("No symbol named \"" + a + "\"") addr_v.append(sym[0]["st_value"]) else: addr_v.append(a) def exec_f(pc, instr): if pc in addr_v: result.append(pc) ev.set() self.add_instr_exec_cb(exec_f) await ev.wait() # self.del_instr_exec_cb(exec_f) if len(result) > 0: return result[0] else: return -1
def _set_disasm_s(self, v): if len(v) > self.msg_sz: v = v[:-3] v += "..." for i,c in enumerate(v.encode()): self._set_disasm_c(i, c) # If the new string is shorter than the old string, # null out the leftover characters if len(self.disasm_s) > len(v): i=len(v) while i < len(self.disasm_s): self._set_disasm_c(i, 0) i+=1 self.disasm_s = v def _set_func_s(self, frame, v): self._clr_func(frame) # if len(v) > self.msg_sz: v = v[:-3] v += "..." for i,c in enumerate(v.encode()): self._set_func_c(frame, i, c) # If the new string is shorter than the old string, # null out the leftover characters # if len(self.func_s) > len(v): # i=len(v) # while i < len(self.func_s): # self._set_func_c(frame, i, 0) # i+=1 # self.func_s = v @pybfms.import_task(pybfms.uint8_t) def _clr_func(self, frame): pass @pybfms.export_task(pybfms.uint32_t) def _set_parameters(self, msg_sz): self.msg_sz = msg_sz @pybfms.export_task(pybfms.uint32_t,pybfms.uint32_t,pybfms.uint32_t,pybfms.uint32_t,pybfms.uint32_t,pybfms.uint8_t,pybfms.uint32_t) def _instr_exec(self, last_instr, pc, instr, mem_waddr, mem_wdata, mem_wmask, count): # if mem_wmask: # print("Write: " + hex(mem_waddr) + " = " + hex(mem_wmask)) # print("instr_exec: " + hex(pc)) for f in self.instr_exec_f: f(pc, instr) # Handle disassembly if self.en_disasm: self._set_disasm_s(self.disasm(pc, instr)) (last_is_push,last_is_pop) = self.is_pushpop(last_instr) if last_is_push: # Last was the push, so 'pc' is the target self.do_call(pc) elif last_is_pop: self.do_ret(pc) elif last_is_push and last_is_pop: print("TODO: both push/pop") else: # TODO: If this is a branch instruction, check # to see if we've landed on a symbol pass self.last_instr = instr
[docs] def is_pushpop(self, instr): is_push = False is_pop = False if (instr & 0x7f) == 0x67: # jalr rs1 = (instr >> 15) & 0x1f rd = (instr >> 7) & 0x1f rs1_islink = rs1 in [1,5] rd_islink = rd in [1,5] if not rd_islink and rs1_islink: is_pop = True elif rd_islink and not rs1_islink: is_push = True elif rd_islink and rs1_islink: if rd != rs1: is_push = True is_pop = True else: is_push = True elif (instr & 0x7f) == 0x6f: # jal rd = (instr >> 7) & 0x1f is_push = rd in [1,5] elif (instr & 0x3) == 2 and ((instr >> 13) & 0x7) == 4: # c.jal rd = (instr >> 7) & 0x1f is_push = rd in [1,5] elif (instr & 0x3) == 2 and ((instr >> 13) & 0x7) == 8: print("TODO: c.jalr") elif (instr & 0x3) == 1 and ((instr >> 13) & 0x7) == 1: print("TODO: c.jal") return (is_push,is_pop)
[docs] def do_call(self, pc): if pc in self.addr2sym_m.keys(): sym = self.addr2sym_m[pc] else: sym = "<unknown " + hex(pc) + ">" self.callstack.append((pc,sym)) if pc in self.entry_exit_addr2cb_m.keys(): # Ensure we don't get stuck modifying the list in-flight for cb in self.entry_exit_addr2cb_m[pc]: cb(pc, sym, True) self._set_func_s(self.frame_idx, sym) self.frame_idx += 1
[docs] def do_ret(self, pc): if len(self.callstack) > 0: (pc,sym) = self.callstack.pop() else: sym = "<unknown " + hex(pc) + ">" if pc in self.entry_exit_addr2cb_m.keys(): # Ensure we don't get stuck modifying the list in-flight for cb in self.entry_exit_addr2cb_m[pc]: cb(pc, sym, False) # TODO: handle swapping windows if self.frame_idx > 0: self._clr_func(self.frame_idx) self.frame_idx -= 1 else: self._clr_func(0)
@pybfms.export_task(pybfms.uint32_t,pybfms.uint32_t) def _write_reg(self, addr, data): self.regs[addr] = data @pybfms.import_task(pybfms.uint8_t,pybfms.uint8_t,pybfms.uint8_t) def _set_func_c(self, frame, idx, ch): pass @pybfms.import_task(pybfms.uint8_t,pybfms.uint8_t) def _set_disasm_c(self, idx, ch): pass @pybfms.import_task(pybfms.uint32_t) def _set_instr_limit(self, count): pass @pybfms.export_task() def _reset(self): self.is_reset = True self.reset_ev.set() @pybfms.import_task(pybfms.uint32_t) def _set_trace_level(self, l): pass
[docs] def disasm(self, pc, instr): if (instr & 0x3) == 0x3: return self.disasm_32(pc, instr) else: return self.disasm_16(pc, instr)
[docs] def disasm_32(self, pc, instr): ret = "" rd = ((instr >> 7) & 0x1F) rs1 = ((instr >> 15) & 0x1F) rs2 = ((instr >> 20) & 0x1F) rnm = [ "zero", "ra", "sp", "gp", "tp", "t0", "t1", "t2", "s0", "s1", "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11", "t3", "t4", "t5", "t6" ] if (instr & 0x7F) == 0x37: ret = "lui %s,0x%05x" % (rnm[rd], (instr >> 12) & 0xFFFFF) elif (instr & 0x7F) == 0x17: imm = (instr & 0xFFFFF000) if (imm & 0x80000000) != 0: imm = -(~imm + 1) ret = "auipc %s,0x%08x" % (rnm[rd],(pc+imm)) elif (instr & 0x7F) == 0x6f: imm = 0 imm |= (((instr >> 31) & 1) << 20) imm |= (((instr >> 21) & 0x3FF) << 1) imm |= (((instr >> 20) & 1) << 11) imm |= (((instr >> 12) & 0xFF) << 12) if (imm & (1 << 20)) != 0: imm = -(~imm + 1) if rd == 0: ret = "j 0x%08x" % (pc+imm) else: ret = "jal %s,0x%08x" % (rnm[rd],(pc+imm)) elif (instr & 0x7F) == 0x67 and ((instr & 0x7000) == 0): imm = (instr >> 20) & 0xFFF rs1_v = self.regs[rs1] if (imm & (1 << 11)): imm = -(~imm+1) target = pc + imm if rd != 0: if imm == 0: ret = "jalr %s,(%s)" % (rnm[rd], rnm[rs1]) else: ret = "jalr %s,%d(%s)" % (rnm[rd], imm, rnm[rs1]) else: if imm == 0: ret = "jalr (%s)" % (rnm[rs1],) else: ret = "jalr %d(%s)" % (imm,rnm[rs1]) elif (instr & 0x7F) == 0x63: op = [ "beq", "bne", "ill", "ill", "blt", "bge", "bltu", "bgeu" ][(instr >> 12) & 0x7] imm = 0 imm |= ((instr >> 8) & 0xF) << 1 imm |= ((instr >> 25) & 0x3F) << 5 imm |= ((instr >> 7) & 0x1) << 11 imm |= ((instr >> 31) & 0x1) << 12 if (imm & 0x1000) != 0: imm = -(~imm + 1) ret = "%s %s,%s,0x%04x" % (op, rnm[rs1], rnm[rs2], (pc+imm)) elif (instr & 0x7F) == 0x03: op = ["lb", "lh", "lw", "ill" "lbu", "lhb", "ill", "ill"][(instr >> 12) & 0x3] imm = (instr >> 20) & 0xFFF if imm & 0x800: # Actually a signed number imm = -(~imm + 1) ret = "%s %s,%d(%s)" % (op,rnm[rd],imm,rnm[rs1]) elif (instr & 0x7F) == 0x23: op = ["sb", "sh", "sw", "ill"][(instr >> 12) & 0x3] imm = (((instr >> 25) & 0x7F) << 5) | ((instr >> 7) & 0x1F) if imm & 0x800: # Actually a signed number imm = -(~imm + 1) ret = "%s %s,%d(%s)" % (op, rnm[rs2], imm,rnm[rs1]) elif (instr & 0x7F) == 0x13: f3 = (instr >> 12) & 0x7 op = ["addi", "slli", "slti", "sltiu", "xori", "srli", "ori", "andi"][f3] imm = (instr >> 20) & 0xFFF if f3 == 0: # addi if imm & 0x800: # Actually a signed number imm = -(~imm + 1) if f3 == 0 and rs1 == 0: if rd == 0: # nop ret = "nop" else: # Synthetic li ret = "li %s,%d" % (rnm[rd],imm) else: ret = "%s %s,%s,%d" % (op,rnm[rd],rnm[rs1],imm) elif (instr & 0x7F) == 0x33: op = "ill" if (instr & 0x40000000) == 0: op = ["add", "sll", "slt", "sltu", "xor", "srl", "or", "and"][(instr >> 12) & 0x3] else: op = ["sub", "ill", "ill", "ill", "ill", "sra", "ill", "ill"][(instr >> 12) & 0x3] ret = "%s %s,%s,%s" % (op, rnm[rd], rnm[rs1], rnm[rs2]) elif (instr & 0x7F) == 0x1F: ret = "fence" if (instr & 0x1000) == 0 else "fence.i" elif (instr & 0x73) == 0x1F: if ((instr >> 12) & 0x7) == 0: ret = "ecall" if (instr & 0x100000) == 0 else "ebreak" else: op = ["ill", "csrrw", "csrrs", "csrrc", "ill", "csrrwi", "csrrsi", "csrrci"][(instr >> 12) & 0x7] # TODO: CSR number ret = "%s %s,%s" % (op, rnm[rd], rnm[rs1]) else: ret = "ill " return ret
[docs] def disasm_16(self, pc, instr): ret = "ill" rnm = [ "zero", "ra", "sp", "gp", "tp", "t0", "t1", "t2", "s0", "s1", "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11", "t3", "t4", "t5", "t6" ] if (instr & 0x3) == 0: pass elif (instr & 0x3) == 1: op = (instr >> 13) & 0x7 rd = (instr >> 7) & 0x1f imm = (instr >> 2) & 0x1f imm |= ((instr >> 12) & 1) << 5 if op == 0: if rd == 0: ret = "c.nop" else: ret = "c.addi %s,%d" % (rnm[rd], imm) elif op == 1: pass elif op == 2: ret = "c.li %s,%d" % (rnm[rd], imm) elif (instr & 0x3) == 2: pass return ret;