259 lines
9.6 KiB
Python
259 lines
9.6 KiB
Python
#***************************************************************************************
|
|
# Copyright (c) 2025 Beijing Institute of Open Source Chip (BOSC)
|
|
# Copyright (c) 2025 Institute of Computing Technology, Chinese Academy of Sciences
|
|
#
|
|
# XiangShan is licensed under Mulan PSL v2.
|
|
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
|
# You may obtain a copy of Mulan PSL v2 at:
|
|
# http://license.coscl.org.cn/MulanPSL2
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
|
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
|
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
|
#
|
|
# See the Mulan PSL v2 for more details.
|
|
#***************************************************************************************
|
|
|
|
import os
|
|
import sys
|
|
import inspect
|
|
import pkgutil
|
|
import signal
|
|
import time
|
|
import pdb
|
|
|
|
from bdb import BdbQuit
|
|
from pyxscore import DUTSimTop, xsp
|
|
from pydifftest import difftest as df
|
|
from collections import OrderedDict
|
|
from logging import DEBUG, INFO, WARNING, ERROR
|
|
from xspdb.xscmd.util import load_module_from_file, load_package_from_dir, set_xspdb_log_level
|
|
from xspdb.xscmd.util import message, info, error, warn, build_prefix_tree, register_commands, YELLOW, RESET, xspdb_set_log, xspdb_set_log_file, log_message
|
|
|
|
class XSPdb(pdb.Pdb):
|
|
def __init__(self, dut, default_file=None,
|
|
mem_base=0x80000000,
|
|
flash_base=0x10000000,
|
|
finstr_addr=None,
|
|
default_mem_size=1024*1024*1024, # 1GB
|
|
default_flash_size=0x10000000,
|
|
no_interact=False,
|
|
):
|
|
"""Create a PDB debugger for XiangShan
|
|
Args:
|
|
dut (DUT): DUT exported by picker
|
|
df (difftest): Difftest exported from DUT Python library
|
|
xsp (xspcomm): xspcomm exported from DUT Python library
|
|
default_file (string): Default bin file to load
|
|
mem_base (int): Memory base address
|
|
finstr_addr (int): First instruction address
|
|
flash_base (int): Flash base address
|
|
default_mem_size (int): Default memory size
|
|
default_flash_size (int): Default flash size
|
|
no_interact (bool): No interact mode, default is False
|
|
"""
|
|
super().__init__()
|
|
self.dut = dut
|
|
self.df = df
|
|
self.xsp = xsp
|
|
self.mem_base = mem_base
|
|
self.finstr_addr = mem_base if finstr_addr is None else finstr_addr
|
|
self.flash_base = flash_base
|
|
self.flash_ends = flash_base + default_flash_size
|
|
self.no_interact = no_interact
|
|
self.interrupt_count = 0
|
|
signal.signal(signal.SIGINT, self._sigint_handler)
|
|
if no_interact:
|
|
info("Start XSPdb whit no_interact config, press Ctrl+C will interrupt the program")
|
|
self.dut_tree = None
|
|
self.prompt = "(XiangShan) "
|
|
self.in_tui = False
|
|
# Init dut uart echo
|
|
self.dut.InitClock("clock")
|
|
self.c_stderr_echo = xsp.ComUseEcho(dut.difftest_uart_out_valid.CSelf(), dut.difftest_uart_out_ch.CSelf())
|
|
self.dut.StepRis(self.c_stderr_echo.GetCb(), self.c_stderr_echo.CSelf(), "uart_echo")
|
|
# Init difftest
|
|
self.exec_bin_file = default_file
|
|
self.mem_size = default_mem_size
|
|
self.mem_inited = False
|
|
self.api_update_pmem_base_and_first_inst_addr(self.mem_base, self.finstr_addr)
|
|
if self.exec_bin_file:
|
|
assert os.path.exists(self.exec_bin_file), "file %s not found" % self.exec_bin_file
|
|
info("load: %s" % self.exec_bin_file)
|
|
self.api_init_mem()
|
|
self.df.InitFlash("")
|
|
self.xspdb_init_bin = "xspdb_flash_init.bin"
|
|
self.flash_bin_file = None
|
|
self.df.difftest_init()
|
|
self.difftest_stat = df.GetDifftest(0).dut
|
|
self.difftest_flash = df.GetFlash()
|
|
self.register_map = OrderedDict()
|
|
self.load_cmds()
|
|
self.api_init_waveform()
|
|
self.init_cmds = []
|
|
self.sigint_original_handler = signal.getsignal(signal.SIGINT)
|
|
self.sigint_callback = []
|
|
self.log_cmd_prefix = "@cmd{"
|
|
self.log_cmd_suffix = "}"
|
|
|
|
def run(self):
|
|
try:
|
|
self.set_trace()
|
|
while True:
|
|
dut.Step(1000)
|
|
except BdbQuit:
|
|
pass
|
|
|
|
def _sigint_handler(self, s, f):
|
|
self.interrupt = True
|
|
warn("[Ctrl+C] Interrupted")
|
|
for f in self.sigint_callback:
|
|
f(self)
|
|
if self.no_interact:
|
|
warn("No interact mode, exit(-1)")
|
|
raise sys.exit(-1)
|
|
self.interrupt_count += 1
|
|
if self.interrupt_count > 3:
|
|
warn("Too many interrupts, force entering pdb")
|
|
self.set_trace()
|
|
self.interrupt_count = 0
|
|
|
|
def __del__(self):
|
|
"""Destructor"""
|
|
if self.sigint_original_handler:
|
|
signal.signal(signal.SIGINT, self.sigint_original_handler)
|
|
|
|
def is_no_interact(self):
|
|
"""Check if no interact mode"""
|
|
return self.no_interact
|
|
|
|
def get_dut_tree(self):
|
|
"""Get the DUT tree"""
|
|
if self.dut_tree is None:
|
|
info("First time to use DUTree, build it ...")
|
|
self.dut_tree = build_prefix_tree(self.dut.GetInternalSignalList())
|
|
return self.dut_tree
|
|
|
|
def api_init_mem(self):
|
|
"""Initialize memory"""
|
|
if self.mem_inited:
|
|
return
|
|
self.df.InitRam(self.exec_bin_file, self.mem_size)
|
|
self.mem_inited = True
|
|
|
|
def api_update_pmem_base_and_first_inst_addr(self, a, b):
|
|
"""Set diftest PMEM_BASE and FIRST_INST_ADDRESS
|
|
|
|
Args:
|
|
a (int): PMEM_BASE value
|
|
b (int): FIRST_INST_ADDRESS value
|
|
Returns:
|
|
(PMEM_BASE, FIRST_INST_ADDRESS): current value of PMEM_BASE and FIRST_INST_ADDRESS
|
|
"""
|
|
if not hasattr(self.df, "Set_PMEM_BASE"):
|
|
warn("difftest.Set_PMEM_BASE not found, update your difftest")
|
|
warn("ignore memory PMEM_BASE set")
|
|
return None, None
|
|
if a is not None:
|
|
self.df.Set_PMEM_BASE(a)
|
|
if b is not None:
|
|
self.df.Set_FIRST_INST_ADDRESS(b)
|
|
x, y = self.df.Get_PMEM_BASE(), self.df.Get_FIRST_INST_ADDRESS()
|
|
if a is not None:
|
|
info("Set PMEM_BASE to %s (Current: %s)" % (hex(a), hex(x)))
|
|
if b is not None:
|
|
info("Set FIRST_INST_ADDRESS to %s (Current: %s)" % (hex(b), hex(y)))
|
|
return x, y
|
|
|
|
def load_cmds(self):
|
|
import xspdb.xscmd
|
|
cmd_count = self.api_load_custom_pdb_cmds(xspdb.xscmd)
|
|
info(f"Loaded {cmd_count} functions from XSPdb.cmd")
|
|
|
|
def api_load_custom_pdb_cmds(self, path_or_module):
|
|
"""Load custom command
|
|
|
|
Args:
|
|
path_or_module (string/Module): Command file path or directory (or python module)
|
|
"""
|
|
if isinstance(path_or_module, str):
|
|
if path_or_module.strip().endswith("/"):
|
|
path_or_module = path_or_module.strip()[:-1]
|
|
mod = path_or_module
|
|
if not inspect.ismodule(path_or_module):
|
|
if os.path.isdir(path_or_module):
|
|
mod = load_package_from_dir(path_or_module)
|
|
elif os.path.isfile(path_or_module):
|
|
mod = load_module_from_file(path_or_module)
|
|
return register_commands(mod, self.__class__, self)
|
|
else:
|
|
error(f"Invalid path or module: {path_or_module}")
|
|
return -1
|
|
# module
|
|
cmd_count = 0
|
|
for _, modname, _ in pkgutil.iter_modules(mod.__path__):
|
|
if not modname.startswith("cmd_"):
|
|
continue
|
|
submod = __import__(f"{mod.__name__}.{modname}", fromlist=[modname])
|
|
cmd_count += register_commands(submod, self.__class__, self)
|
|
return cmd_count
|
|
|
|
# override the default PDB function to avoid None cmd error
|
|
def parseline(self, line):
|
|
cmd, arg, line = super().parseline(line)
|
|
return cmd or "", arg, line
|
|
|
|
def do_xcmds(self, arg):
|
|
"""Print all xcmds
|
|
|
|
Args:
|
|
arg (None): No arguments
|
|
"""
|
|
cmd_count = 0
|
|
max_cmd_len = 0
|
|
cmds = []
|
|
for cmd in dir(self):
|
|
if not cmd.startswith("do_x"):
|
|
continue
|
|
cmd_name = cmd[3:]
|
|
max_cmd_len = max(max_cmd_len, len(cmd_name))
|
|
cmd_desc = f"{YELLOW}Description not found{RESET}"
|
|
try:
|
|
cmd_desc = getattr(self, cmd).__doc__.split("\n")[0]
|
|
except Exception as e:
|
|
pass
|
|
cmds.append((cmd, cmd_name, cmd_desc))
|
|
cmd_count += 1
|
|
cmds.sort(key=lambda x: x[0])
|
|
for c in cmds:
|
|
message(("%-"+str(max_cmd_len+2)+"s: %s (from %s)") % (c[1], c[2], self.register_map.get(c[0], self.__class__.__name__)))
|
|
info(f"Total {cmd_count} xcommands")
|
|
|
|
@staticmethod
|
|
def api_log_enable_log(enable):
|
|
xspdb_set_log(enable)
|
|
|
|
@staticmethod
|
|
def api_log_set_log_file(log_file):
|
|
xspdb_set_log(True)
|
|
xspdb_set_log_file(log_file)
|
|
|
|
def api_busy_sleep(self, data, delta=0.1):
|
|
for i in range(int(data//delta)):
|
|
time.sleep(delta)
|
|
if self.interrupt:
|
|
return (i+1)*delta
|
|
return data
|
|
|
|
def complete_xset_log_file(self, text, line, begidx, endidx):
|
|
return self.api_complite_localfile(text)
|
|
|
|
def record_cmd(self, cmd):
|
|
log_message(self.log_cmd_prefix + cmd + self.log_cmd_suffix)
|
|
|
|
def onecmd(self, line, log_cmd=True):
|
|
"""Override the onecmd to log the command"""
|
|
if log_cmd:
|
|
self.record_cmd(line)
|
|
return super().onecmd(line)
|