feat(pdb): Add XSPdb, a GDB-like interactive debugger for XiangShan (#4906)
### 1. Purpose of this Pull Request XSPdb is a specialized Python pdb-based debugging tool for RISC-V IP cores, customized for Xiangshan's difftest interface. It provides GDB-like interactive debugging capabilities, integrating: Terminal command-line interface, RTL-level waveform toggling, Automated script replay, System snapshot save/restore, Register initialization configuration ### 2. Changes Made * **New Tool (`XSPdb`)**: The core Python scripts for XSPdb have been added under `scripts/xspdb/`. This tool provides an interactive debugging console with standard pdb commands (e.g., breakpoints, step, continue, register inspection). * **Build System Integration**: A new Makefile, `pdb.mk`, has been created to define the build targets and logic required for XSPdb. * **Makefile Modification**: The root Makefile has been updated to include `pdb.mk`, integrating the new `pdb` target into the main build system. ### 3. Usage The new tool can be built and used via a new `make` target. 1. **Build the XSPdb package**: ```bash make pdb NOOP_HOME=$(pwd) ``` 2. **Run a simulation with the XSPdb**: ```bash make pdb-run NOOP_HOME=$(pwd) ``` --------- Co-authored-by: Zhicheng Yao <yaozhicheng@ict.ac.cn>
This commit is contained in:
parent
ed04ad453a
commit
db3800c43f
|
@ -36,3 +36,6 @@ coupledL2/ @linjuanZ @Ivyfeather
|
|||
huancun/ @linjuanZ @Ivyfeather
|
||||
|
||||
src/main/scala/top/ @Tang-Haojin
|
||||
|
||||
scripts/pdb-run.py @yaozhicheng @forever043 @SFangYy @Tang-Haojin
|
||||
scripts/xspdb/ @yaozhicheng @forever043 @SFangYy @Tang-Haojin
|
||||
|
|
3
Makefile
3
Makefile
|
@ -21,6 +21,9 @@ RTL_DIR = $(BUILD_DIR)/rtl
|
|||
# import docker support
|
||||
include scripts/Makefile.docker
|
||||
|
||||
# import pdb support
|
||||
include scripts/Makefile.pdb
|
||||
|
||||
# if XSTopPrefix is specified in yaml, use it.
|
||||
ifneq ($(YAML_CONFIG),)
|
||||
HAS_PREFIX_FROM_YAML = $(shell grep 'XSTopPrefix *:' $(YAML_CONFIG))
|
||||
|
|
33
README.md
33
README.md
|
@ -121,6 +121,39 @@ make emu CONFIG=MinimalConfig EMU_THREADS=2 -j10
|
|||
./build/emu -b 0 -e 0 -i ./ready-to-run/coremark-2-iteration.bin --diff ./ready-to-run/riscv64-nemu-interpreter-so
|
||||
```
|
||||
|
||||
### Run with xspdb
|
||||
* Install [picker](https://github.com/XS-MLVP/picker), a verifaction tool that supports high-level languages.
|
||||
* Run `make pdb` to build XiangShan Python binaries.
|
||||
* Run `make pdb-run` to run XiangShan binaries.
|
||||
|
||||
Example output and interaction:
|
||||
|
||||
```bash
|
||||
$ make pdb-run
|
||||
[Info] Set PMEM_BASE to 0x80000000 (Current: 0x80000000)
|
||||
[Info] Set FIRST_INST_ADDRESS to 0x80000000 (Current: 0x80000000)
|
||||
Using simulated 32768B flash
|
||||
[Info] reset dut complete
|
||||
> XiangShan/scripts/pdb-run.py(13)run()
|
||||
-> while True:
|
||||
(XiangShan) xload ready-to-run/microbench.bin # Load binary (Tab-compatible)
|
||||
(XiangShan) xwatch_commit_pc 0x80000004 # set watch point,
|
||||
(XiangShan) xistep 3 # Step to next three instruction commit, it will stop at watch point
|
||||
[Info] Find break point (Inst commit), break (step 2107 cycles) at cycle: 2207 (0x89f)
|
||||
[Info] Find break point (Inst commit, Target commit), break (step 2108 cycles) at cycle: 2208 (0x8a0)
|
||||
(XiangShan) xpc # print pc info
|
||||
PC[0]: 0x80000000 Instr: 0x00000093
|
||||
PC[1]: 0x80000004 Instr: 0x00000113
|
||||
PC[2]: 0x0 Instr: 0x0
|
||||
...
|
||||
PC[7]: 0x0 Instr: 0x0
|
||||
(XiangShan) xistep 1000000 # Execute to binary end
|
||||
[Info] Find break point (Inst commit), break (step 2037 cycles) at cycle: 2207 (0x89f)
|
||||
[Info] Find break point (Inst commit), break (step 2180 cycles) at cycle: 2207 (0x89f)
|
||||
...
|
||||
HIT GOOD LOOP at pc = 0xf0001cb0
|
||||
```
|
||||
|
||||
## Troubleshooting Guide
|
||||
|
||||
[Troubleshooting Guide](https://github.com/OpenXiangShan/XiangShan/wiki/Troubleshooting-Guide)
|
||||
|
|
|
@ -115,6 +115,40 @@ make emu CONFIG=MinimalConfig EMU_THREADS=2 -j10
|
|||
./build/emu -b 0 -e 0 -i ./ready-to-run/coremark-2-iteration.bin --diff ./ready-to-run/riscv64-nemu-interpreter-so
|
||||
```
|
||||
|
||||
### 运行xspdb
|
||||
|
||||
* 安装多语言芯片验证辅助工具 [picker](https://github.com/XS-MLVP/picker)
|
||||
* 运行 `make pdb` 以利用 picker 构建香山的二进制版本
|
||||
* 运行 `make pdb-run` 来运行香山二进制版本
|
||||
|
||||
运行示例:
|
||||
|
||||
```bash
|
||||
$ make pdb-run
|
||||
[Info] Set PMEM_BASE to 0x80000000 (Current: 0x80000000)
|
||||
[Info] Set FIRST_INST_ADDRESS to 0x80000000 (Current: 0x80000000)
|
||||
Using simulated 32768B flash
|
||||
[Info] reset dut complete
|
||||
> XiangShan/scripts/pdb-run.py(13)run()
|
||||
-> while True:
|
||||
(XiangShan) xload ready-to-run/microbench.bin # 加载需要运行的bin文件
|
||||
(XiangShan) xwatch_commit_pc 0x80000004 # 设置观察点
|
||||
(XiangShan) xistep 3 # 执行到下三条指令提交,如设置观察点则执行到观察点
|
||||
[Info] Find break point (Inst commit), break (step 2107 cycles) at cycle: 2207 (0x89f)
|
||||
[Info] Find break point (Inst commit, Target commit), break (step 2108 cycles) at cycle: 2208 (0x8a0)
|
||||
(XiangShan) xpc # 打印pc信息
|
||||
PC[0]: 0x80000000 Instr: 0x00000093
|
||||
PC[1]: 0x80000004 Instr: 0x00000113
|
||||
PC[2]: 0x0 Instr: 0x0
|
||||
...
|
||||
PC[7]: 0x0 Instr: 0x0
|
||||
(XiangShan) xistep 1000000 # 执行到结束
|
||||
[Info] Find break point (Inst commit), break (step 2037 cycles) at cycle: 2207 (0x89f)
|
||||
[Info] Find break point (Inst commit), break (step 2180 cycles) at cycle: 2207 (0x89f)
|
||||
...
|
||||
HIT GOOD LOOP at pc = 0xf0001cb0
|
||||
```
|
||||
|
||||
## 错误排除指南
|
||||
|
||||
[Troubleshooting Guide](https://github.com/OpenXiangShan/XiangShan/wiki/Troubleshooting-Guide)
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
#***************************************************************************************
|
||||
# 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.
|
||||
#***************************************************************************************
|
||||
|
||||
PDB_HOME := $(abspath $(BUILD_DIR)/xspdb)
|
||||
RTL_HOME := $(abspath $(RTL_DIR))
|
||||
RTL_FILELIST := $(RTL_HOME)/filelist.f
|
||||
DIFFTEST_HOME := $(abspath $(PDB_HOME)/pydifftest)
|
||||
|
||||
DIFFTEST_INCLUDE = $(abspath difftest/src/test/vsrc/common)
|
||||
PICKER_INCLUDE = $(shell picker --show_xcom_lib_location_cpp | grep include | awk '{print $$2}')
|
||||
PYTHON_VERSION = $(shell python3 --version | awk '{print $$2}' | cut -d'.' -f1-2)
|
||||
SIM_LDFLAGS = $(shell cat $(DIFFTEST_HOME)/sim_ld_flags.txt)
|
||||
|
||||
PYTHON_DEP_FILE = scripts/xspdb/requirements.txt
|
||||
PDB_PYTHONPATH := $(PDB_HOME):script/xspdb
|
||||
PDB_LD_PRELOAD := $(PDB_HOME)/pyxscore/xspcomm/libxspcomm.so.0.0.1 \
|
||||
$(PDB_HOME)/pyxscore/libdifftest.so
|
||||
|
||||
.PHONY: pdb pdb-run check-deps
|
||||
|
||||
pdb: $(DIFFTEST_HOME)/_difftest.so $(PDB_HOME)/libUTSimTop.so
|
||||
|
||||
$(RTL_FILELIST): sim-verilog
|
||||
|
||||
$(DIFFTEST_HOME)/_difftest.so: $(RTL_FILELIST)
|
||||
$(MAKE) -C ./difftest pydifftest WITH_CONSTANTIN=0 WITH_CHISELDB=0
|
||||
ln -srf ../pydifftest/_difftest.so $(DIFFTEST_HOME)/libdifftest.so
|
||||
|
||||
$(PDB_HOME)/picker.f: $(DIFFTEST_HOME)/_difftest.so
|
||||
find $(realpath $(RTL_HOME)) -maxdepth 1 \( -name "*.sv" -o -name "*.v" -o -name "*.cpp" -o -name "*.so" \) -not -name "SimTop.sv" -printf "%p\n" > $(PDB_HOME)/picker.f
|
||||
find $(realpath $(DIFFTEST_INCLUDE)) -maxdepth 1 \( -name "*.v" \) -not -name "SimTop.sv" -printf "%p\n" >> $(PDB_HOME)/picker.f
|
||||
echo "$(DIFFTEST_HOME)/libdifftest.so" >> $(PDB_HOME)/picker.f
|
||||
|
||||
$(PDB_HOME)/libUTSimTop.so: $(PDB_HOME)/picker.f
|
||||
time picker export $(RTL_HOME)/SimTop.sv \
|
||||
--rw 1 \
|
||||
-w $(PDB_HOME)/xs.fst \
|
||||
--lang python \
|
||||
--tdir $(PDB_HOME)/pyxscore \
|
||||
--fs $(PDB_HOME)/picker.f \
|
||||
-V "--no-timing;--threads;128;+define+DIFFTEST;-I$(NOOP_HOME)/build/generated-src" \
|
||||
-C "-fPIC -lz -I$(PICKER_INCLUDE) -L$(PDB_HOME)/pyxscore -ldifftest -lpython$(PYTHON_VERSION) $(SIM_LDFLAGS)" \
|
||||
--autobuild=false
|
||||
ln -s ../pydifftest/_difftest.so $(PDB_HOME)/pyxscore/libdifftest.so
|
||||
$(MAKE) -C $(PDB_HOME)/pyxscore
|
||||
|
||||
pdb-run: check-deps
|
||||
LD_PRELOAD="$(PDB_LD_PRELOAD)" \
|
||||
PYTHONPATH="$(PDB_PYTHONPATH)" \
|
||||
python3 scripts/pdb-run.py
|
||||
|
||||
check-deps:
|
||||
@[ -f "$(PYTHON_DEP_FILE)" ] || { echo "Error: $(PYTHON_DEP_FILE) not found"; exit 1; }; \
|
||||
grep -v '^[#[:space:]]*$$' $(PYTHON_DEP_FILE) | while read -r req; do \
|
||||
ver=$$(pip show "$${req%%>=*}" 2>/dev/null | awk '/^Version:/{print $$2}') || exit 1; \
|
||||
[ -n "$$ver" ] && [ "$$(printf "$$ver\n$${req##*>=}" | sort -V | head -n1)" = "$${req##*>=}" ] || \
|
||||
{ echo "Error: Dependency requirement not satisfied, Please run 'pip install -r $(PYTHON_DEP_FILE)'"; exit 1; }; \
|
||||
done
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
#***************************************************************************************
|
||||
# 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.
|
||||
#***************************************************************************************
|
||||
|
||||
|
||||
from xspdb import XSPdb, DUTSimTop
|
||||
|
||||
if __name__ == "__main__":
|
||||
dut = DUTSimTop();
|
||||
XSPdb(dut).run();
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
#***************************************************************************************
|
||||
# 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.
|
||||
#***************************************************************************************
|
||||
|
||||
|
||||
from .xspdb import *
|
||||
from pyxscore import DUTSimTop
|
||||
|
||||
__version__ = "0.9.1"
|
|
@ -0,0 +1,2 @@
|
|||
urwid>=2.6.16
|
||||
capstone>=5.0.5
|
|
@ -0,0 +1,19 @@
|
|||
#***************************************************************************************
|
||||
# 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.
|
||||
#***************************************************************************************
|
||||
|
||||
|
||||
from xspdb.xscmd.util import message, error, warn, info, GREEN, RESET
|
||||
from xspdb.xscmd.util import get_completions, find_executable_in_dirs
|
|
@ -0,0 +1,328 @@
|
|||
#***************************************************************************************
|
||||
# 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
|
||||
from . import message, error, warn, info, GREEN, RESET
|
||||
|
||||
class CmdDiffTest:
|
||||
|
||||
def __init__(self):
|
||||
assert hasattr(self, "difftest_stat"), "difftest_stat not found"
|
||||
self.condition_watch_commit_pc = {}
|
||||
self.condition_instrunct_istep = {}
|
||||
self.difftest_ref_so = self.xsp.CString()
|
||||
self.difftest_ref_is_inited = False
|
||||
self.difftest_diff_checker = {}
|
||||
self.difftest_diff_is_run = False
|
||||
self.istep_last_commit_pc = []
|
||||
self.data_last_symbol_block = -1
|
||||
self.data_last_symbol_pc = -1
|
||||
|
||||
def api_load_ref_so(self, so_path):
|
||||
"""Load the difftest reference shared object
|
||||
|
||||
Args:
|
||||
so_path (string): Path to the shared object
|
||||
"""
|
||||
if not os.path.exists(so_path):
|
||||
error(f"file {so_path} not found")
|
||||
return False
|
||||
self.difftest_ref_so.Set(so_path)
|
||||
self.df.SetProxyRefSo(self.difftest_ref_so.CharAddress())
|
||||
info(f"load difftest ref so: {so_path} complete")
|
||||
return True
|
||||
|
||||
def api_get_ref_so_path(self):
|
||||
"""Get the path of the difftest reference shared object
|
||||
|
||||
Returns:
|
||||
string: Path to the shared object
|
||||
"""
|
||||
return self.difftest_ref_so.Get()
|
||||
|
||||
def api_init_ref(self, force=False):
|
||||
"""Initialize the difftest reference"""
|
||||
if self.difftest_ref_is_inited:
|
||||
if not force:
|
||||
error("difftest reference already inited")
|
||||
return False
|
||||
if self.difftest_ref_so.Get() == "":
|
||||
error("difftest reference so not loaded")
|
||||
return False
|
||||
if not self.mem_inited:
|
||||
error("mem not loaded, please load bin file to mem first")
|
||||
return False
|
||||
if force and self.difftest_ref_is_inited:
|
||||
self.df.finish_device()
|
||||
self.df.GoldenMemFinish()
|
||||
self.df.difftest_finish()
|
||||
self.df.difftest_init()
|
||||
self.difftest_stat = self.df.GetDifftest(0).dut
|
||||
self.df.init_device()
|
||||
self.df.GoldenMemInit()
|
||||
self.df.init_nemuproxy(0)
|
||||
self.difftest_ref_is_inited = True
|
||||
return True
|
||||
|
||||
def api_set_difftest_diff(self, turn_on):
|
||||
"""Initialize the difftest diff"""
|
||||
if not self.api_init_ref():
|
||||
return False
|
||||
checker = self.difftest_diff_checker.get("checker")
|
||||
if not checker:
|
||||
checker = self.xsp.ComUseCondCheck(self.dut.xclock)
|
||||
tmp_dat = self.xsp.ComUseDataArray(4)
|
||||
checker.SetCondition("diff_test_do_diff_check", tmp_dat.BaseAddr(), tmp_dat.BaseAddr(),
|
||||
self.xsp.ComUseCondCmp_NE, 4, 0, 0, 0,
|
||||
checker.AsPtrXFunc(self.df.GetFuncAddressOfDifftestStepAndCheck()),
|
||||
0)
|
||||
self.difftest_diff_checker["checker"] = checker
|
||||
key = "diff_test_do_diff_check"
|
||||
self.dut.xclock.RemoveStepRisCbByDesc(key)
|
||||
self.difftest_diff_is_run = False
|
||||
if turn_on:
|
||||
self.dut.xclock.StepRis(checker.GetCb(), checker.CSelf(), key)
|
||||
self.difftest_diff_is_run = True
|
||||
info("turn on difftest diff")
|
||||
else:
|
||||
info("turn off difftest diff")
|
||||
return True
|
||||
|
||||
def api_is_difftest_diff_exit(self, show_log=False):
|
||||
"""Check if the difftest diff has exited
|
||||
|
||||
Returns:
|
||||
bool: True if exited, False otherwise
|
||||
"""
|
||||
if not self.difftest_diff_is_run:
|
||||
return False
|
||||
stat = self.df.GetDifftestStat()
|
||||
if stat == -1:
|
||||
return False
|
||||
if show_log:
|
||||
message(f"{GREEN}Difftest run exit with code: {stat} {RESET}")
|
||||
return True
|
||||
|
||||
def api_is_difftest_diff_run(self):
|
||||
"""Check if the difftest diff is running"""
|
||||
return self.difftest_diff_is_run
|
||||
|
||||
def do_xload_difftest_ref_so(self, arg):
|
||||
"""Load the difftest reference shared object
|
||||
|
||||
Args:
|
||||
arg (string): Path to the shared object
|
||||
"""
|
||||
if not arg.strip():
|
||||
error("difftest ref so path not found\n usage: xload_difftest_ref_so <path>")
|
||||
return
|
||||
if not self.api_load_ref_so(arg):
|
||||
error(f"load difftest ref so {arg} failed")
|
||||
return
|
||||
|
||||
def complete_xload_difftest_ref_so(self, text, line, begidx, endidx):
|
||||
return self.api_complite_localfile(text)
|
||||
|
||||
def api_difftest_reset(self):
|
||||
"""Reset the difftest"""
|
||||
if self.difftest_ref_is_inited:
|
||||
if self.api_init_ref(force=True):
|
||||
info("difftest reset success")
|
||||
return True
|
||||
else:
|
||||
error("difftest reset failed")
|
||||
return False
|
||||
return True
|
||||
|
||||
def do_xdifftest_reset(self, arg):
|
||||
"""Reset the difftest
|
||||
|
||||
Args:
|
||||
arg (None): No arguments
|
||||
"""
|
||||
self.api_difftest_reset()
|
||||
|
||||
def api_commit_pc_list(self):
|
||||
"""Get the list of all commit PCs
|
||||
|
||||
Returns:
|
||||
list((pc, valid)): List of PCs
|
||||
"""
|
||||
index = 0
|
||||
pclist=[]
|
||||
while True:
|
||||
cmt = self.difftest_stat.get_commit(index)
|
||||
if cmt:
|
||||
pclist.append((cmt.pc, cmt.valid))
|
||||
index += 1
|
||||
else:
|
||||
break
|
||||
return pclist
|
||||
|
||||
def do_xpc(self, a):
|
||||
"""Print the current Commit PCs and instructions
|
||||
|
||||
Args:
|
||||
a (None): No arguments
|
||||
"""
|
||||
for i in range(8):
|
||||
cmt = self.difftest_stat.get_commit(i)
|
||||
message(f"PC[{i}]: 0x{cmt.pc:x} Instr: 0x{cmt.instr:x}")
|
||||
|
||||
def api_istep_update_commit_pc(self):
|
||||
old_p = self.condition_instrunct_istep.get("pc_old_list")
|
||||
new_P = self.condition_instrunct_istep.get("pc_lst_list")
|
||||
if not (old_p and new_P):
|
||||
self.istep_last_commit_pc = []
|
||||
else:
|
||||
self.istep_last_commit_pc = []
|
||||
for i in range(8):
|
||||
old_pc = int.from_bytes(old_p[i].AsBytes(), byteorder='little', signed=False)
|
||||
new_pc = int.from_bytes(new_P[i].AsBytes(), byteorder='little', signed=False)
|
||||
if old_pc != new_pc:
|
||||
self.istep_last_commit_pc.append(new_pc)
|
||||
|
||||
def comuse_checker_is_break(self, checker):
|
||||
if not checker:
|
||||
return False
|
||||
if {k: v for (k, v) in checker.ListCondition().items() if v}:
|
||||
return True
|
||||
return False
|
||||
|
||||
def api_break_is_instruction_commit(self):
|
||||
"""check break is instruction commit or not"""
|
||||
checker = self.condition_instrunct_istep.get("checker")
|
||||
return self.comuse_checker_is_break(checker)
|
||||
|
||||
def api_break_is_watch_commit_pc(self):
|
||||
"""check break is watch commit pc or not"""
|
||||
checker = self.condition_watch_commit_pc.get("checker")
|
||||
return self.comuse_checker_is_break(checker)
|
||||
|
||||
def api_get_istep_last_commit_pc(self):
|
||||
"""Get the last commit PC after instruction step
|
||||
|
||||
Returns:
|
||||
list: List of commit PCs
|
||||
"""
|
||||
return self.istep_last_commit_pc.copy()
|
||||
|
||||
def api_xistep(self, instr_count):
|
||||
"""Step through instructions, stop when find instruction commit
|
||||
|
||||
Args:
|
||||
step_count (int): Number of steps to take
|
||||
Returns:
|
||||
step_taken (int)
|
||||
"""
|
||||
self.api_xistep_break_on()
|
||||
update_pc_func = self.condition_instrunct_istep["pc_sync_list"]
|
||||
update_pc_func()
|
||||
step_taken = 0
|
||||
for i in range(instr_count):
|
||||
update_pc_func()
|
||||
v = self.api_step_dut(10000)
|
||||
if self.api_dut_is_step_exit():
|
||||
break
|
||||
if self.interrupt:
|
||||
break
|
||||
elif self.dut.xclock.IsDisable():
|
||||
self.api_istep_update_commit_pc()
|
||||
pc = max(self.api_get_istep_last_commit_pc() + [-1])
|
||||
self.data_last_symbol_block = self.api_echo_pc_symbol_block_change(pc,
|
||||
self.data_last_symbol_block,
|
||||
self.data_last_symbol_pc)
|
||||
self.data_last_symbol_pc = pc
|
||||
elif v == 10000:
|
||||
warn("step %d cycles complete, but no instruction commit find" % v)
|
||||
step_taken -= 1 # ignore record
|
||||
step_taken += 1
|
||||
# remove stepi_check
|
||||
self.api_xistep_break_off()
|
||||
return step_taken
|
||||
|
||||
def api_xistep_break_on(self):
|
||||
"""Set the instruction step break condition"""
|
||||
if not self.condition_instrunct_istep:
|
||||
checker = self.xsp.ComUseCondCheck(self.dut.xclock)
|
||||
self.condition_instrunct_istep["checker"] = checker
|
||||
pc_old_list = [self.xsp.ComUseDataArray(8) for i in range(8)]
|
||||
pc_lst_list = [self.xsp.ComUseDataArray(self.difftest_stat.get_commit(i).get_pc_address(), 8) for i in range(8)]
|
||||
# sync pc and add checker
|
||||
for i, opc in enumerate(pc_old_list):
|
||||
lpc = pc_lst_list[i]
|
||||
opc.SyncFrom(lpc.BaseAddr(), 8)
|
||||
checker.SetCondition("stepi_check_pc_%d" % i, lpc.BaseAddr(), opc.BaseAddr(), self.xsp.ComUseCondCmp_NE, 8)
|
||||
self.condition_instrunct_istep["pc_old_list"] = pc_old_list
|
||||
self.condition_instrunct_istep["pc_lst_list"] = pc_lst_list
|
||||
def _update_old_pc():
|
||||
for i, opc in enumerate(pc_old_list):
|
||||
lpc = pc_lst_list[i]
|
||||
opc.SyncFrom(lpc.BaseAddr(), 8)
|
||||
self.condition_instrunct_istep["pc_sync_list"] = _update_old_pc
|
||||
cb_key = "stepi_check"
|
||||
checker = self.condition_instrunct_istep["checker"]
|
||||
if cb_key in self.dut.xclock.ListSteRisCbDesc():
|
||||
return
|
||||
self.dut.xclock.StepRis(checker.GetCb(), checker.CSelf(), cb_key)
|
||||
|
||||
def api_xistep_break_off(self):
|
||||
"""Remove the instruction step break condition"""
|
||||
cb_key = "stepi_check"
|
||||
if cb_key in self.dut.xclock.ListSteRisCbDesc():
|
||||
self.dut.xclock.RemoveStepRisCbByDesc(cb_key)
|
||||
assert cb_key not in self.dut.xclock.ListSteRisCbDesc()
|
||||
|
||||
def do_xistep_break(self, arg):
|
||||
"""Set the instruction step break condition
|
||||
|
||||
Args:
|
||||
on_or_off (str): "on" or "off"
|
||||
"""
|
||||
if arg.strip() == "on":
|
||||
self.api_xistep_break_on()
|
||||
elif arg.strip() == "off":
|
||||
self.api_xistep_break_off()
|
||||
else:
|
||||
error("usage: xistep_break <on|off>")
|
||||
|
||||
def complete_xistep_break(self, text, line, begidx, endidx):
|
||||
return [x for x in ["on", "off"] if x.startswith(text)] if text else ["on", "off"]
|
||||
|
||||
def do_xistep(self, arg):
|
||||
"""Step through instructions, stop when find instruction commit
|
||||
|
||||
Args:
|
||||
step_count (int): Number of steps to take
|
||||
"""
|
||||
arg = arg.strip()
|
||||
instr_count = 1
|
||||
try:
|
||||
instr_count = 1 if not arg else int(arg)
|
||||
except Exception as e:
|
||||
error(f"convert {arg} to number fail: {str(e)}")
|
||||
return
|
||||
self.api_xistep(instr_count)
|
||||
|
||||
def api_difftest_get_instance(self, instance=0):
|
||||
"""Get the difftest instance
|
||||
|
||||
Args:
|
||||
instance (number): difftest instance to get, default is 0
|
||||
"""
|
||||
return self.df.GetDifftest(instance)
|
||||
|
|
@ -0,0 +1,269 @@
|
|||
#***************************************************************************************
|
||||
# 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 time
|
||||
from . import info, error, message, warn, get_completions
|
||||
|
||||
class CmdDut:
|
||||
|
||||
def __init__(self):
|
||||
assert hasattr(self, "dut"), "this class must be used in XSPdb, canot be used alone"
|
||||
self.interrupt = False
|
||||
self.xdut_signal_breaks = {}
|
||||
self.api_dut_reset()
|
||||
|
||||
def api_xbreak_list(self):
|
||||
"""List all breakpoints"""
|
||||
ret = []
|
||||
checker = self.xdut_signal_breaks.get("checker")
|
||||
if not checker:
|
||||
return ret
|
||||
checked = {k: v for (k, v) in checker.ListCondition().items()}
|
||||
for k, v in self.xdut_signal_breaks.items():
|
||||
if not k.startswith("xbreak-"):
|
||||
continue
|
||||
ret.append((k, v["sig"].value, v["cmp"], v["val"].value, checked[k]))
|
||||
ret.sort(key=lambda x: x[0])
|
||||
return ret
|
||||
|
||||
def call_break_callbacks(self):
|
||||
checker = self.xdut_signal_breaks.get("checker")
|
||||
cb_count = 0
|
||||
if not checker:
|
||||
return cb_count
|
||||
callbacks = {}
|
||||
for k, v in self.xdut_signal_breaks.items():
|
||||
if not k.startswith("xbreak-"):
|
||||
continue
|
||||
if not callable(v["cb"]):
|
||||
continue
|
||||
callbacks[k] = (v["cb"], v["cb_once"])
|
||||
if not callbacks:
|
||||
return cb_count
|
||||
checked = {k: v for (k, v) in checker.ListCondition().items() if v}
|
||||
for k, (cb, once) in callbacks.items():
|
||||
if k not in checked:
|
||||
continue
|
||||
cb(self, checker, k, self.dut.xclock.clk, self.xdut_signal_breaks[k]["sig"].value, self.xdut_signal_breaks[k]["val"].value)
|
||||
if once:
|
||||
checker.RemoveCondition(k)
|
||||
info(f"remove signal {k} break, because callback_once is True")
|
||||
del self.xdut_signal_breaks[k]
|
||||
cb_count += 1
|
||||
if not {k: v for (k, v) in checker.ListCondition().items()}:
|
||||
checker.ClearCondition()
|
||||
self.dut.xclock.RemoveStepRisCbByDesc("xdut_signal_break")
|
||||
assert "xdut_signal_break" not in self.dut.xclock.ListSteRisCbDesc()
|
||||
self.xdut_signal_breaks.clear()
|
||||
return cb_count
|
||||
|
||||
def api_is_xbreak_on(self):
|
||||
"""Check if the breakpoint is on"""
|
||||
checker = self.xdut_signal_breaks.get("checker")
|
||||
if checker:
|
||||
return True
|
||||
return False
|
||||
|
||||
def api_dut_is_step_exit(self):
|
||||
"""Check if the step is exit"""
|
||||
return any([self.api_is_difftest_diff_exit(show_log=False),
|
||||
self.api_is_hit_good_trap(show_log=False),
|
||||
self.api_is_hit_good_loop(show_log=False),
|
||||
])
|
||||
|
||||
def api_get_breaked_names(self):
|
||||
"""Get the names of the breaked names"""
|
||||
names = []
|
||||
# api_xbreak_list
|
||||
names += [v[0] for v in self.api_xbreak_list() if v[4]]
|
||||
# instrunct_istep
|
||||
if self.api_break_is_instruction_commit():
|
||||
names.append("Inst commit")
|
||||
# watch_commit_pc
|
||||
if self.api_break_is_watch_commit_pc():
|
||||
names.append("Target commit")
|
||||
return ",".join(names)
|
||||
|
||||
def api_dut_step_ready(self):
|
||||
"""Prepare the DUT for stepping"""
|
||||
self.dut.xclock.Enable()
|
||||
assert not self.dut.xclock.IsDisable(), "clock is disable"
|
||||
self.interrupt = False # interrupt from outside by user
|
||||
|
||||
def api_step_dut(self, cycle, batch_cycle=200):
|
||||
"""Step through the circuit
|
||||
|
||||
Args:
|
||||
cycle (int): Number of cycles
|
||||
batch_cycle (int): Number of cycles per run; after each run, check for interrupt signals
|
||||
"""
|
||||
if not self.mem_inited:
|
||||
warn("mem not inited, please load bin file first")
|
||||
def check_break():
|
||||
if self.dut.xclock.IsDisable():
|
||||
info("Find break point (%s), break (step %d cycles) at cycle: %d (%s)" % (
|
||||
self.api_get_breaked_names(),
|
||||
self.dut.xclock.clk - c_count,
|
||||
self.dut.xclock.clk, hex(self.dut.xclock.clk)))
|
||||
return True
|
||||
fc = getattr(self, "on_update_tstep", None)
|
||||
if fc:
|
||||
fc()
|
||||
if self.api_is_difftest_diff_exit(show_log=True):
|
||||
return True
|
||||
elif self.api_is_hit_good_trap(show_log=True):
|
||||
return True
|
||||
elif self.api_is_hit_good_loop(show_log=True):
|
||||
return True
|
||||
elif self.api_is_hit_trap_break(show_log=True):
|
||||
return True
|
||||
return False
|
||||
self.api_dut_step_ready()
|
||||
batch, offset = cycle//batch_cycle, cycle % batch_cycle
|
||||
c_count = self.dut.xclock.clk
|
||||
need_break = False
|
||||
for i in range(batch):
|
||||
if self.interrupt:
|
||||
break
|
||||
self.dut.Step(batch_cycle)
|
||||
if check_break():
|
||||
need_break = True
|
||||
break
|
||||
if not self.interrupt and not need_break:
|
||||
self.dut.Step(offset)
|
||||
check_break()
|
||||
if self.dut.xclock.IsDisable():
|
||||
self.call_break_callbacks()
|
||||
return self.dut.xclock.clk - c_count
|
||||
|
||||
def api_dut_reset(self):
|
||||
"""Reset the DUT"""
|
||||
for i in range(8):
|
||||
cmt = self.difftest_stat.get_commit(i)
|
||||
cmt.pc = 0x0
|
||||
cmt.instr = 0x0
|
||||
self.difftest_stat.trap.pc = 0x0
|
||||
self.difftest_stat.trap.code = 32
|
||||
self.difftest_stat.trap.hasTrap = 0
|
||||
self.dut.reset.AsImmWrite()
|
||||
self.dut.reset.value = 1
|
||||
self.dut.reset.AsRiseWrite()
|
||||
self.dut.reset.value = 1
|
||||
self.dut.Step(100)
|
||||
self.dut.reset.value = 0
|
||||
info("reset dut complete")
|
||||
|
||||
def api_dut_flash_load(self, flash_file):
|
||||
"""Load a bin file into Flash
|
||||
|
||||
Args:
|
||||
flash_file (string): Path to the bin file
|
||||
"""
|
||||
assert os.path.exists(flash_file)
|
||||
self.df.flash_finish()
|
||||
self.df.InitFlash(flash_file)
|
||||
self.flash_bin_file = flash_file
|
||||
self.info_cache_asm.clear()
|
||||
|
||||
def do_xreset(self, arg):
|
||||
"""Reset DUT
|
||||
|
||||
Args:
|
||||
arg (None): No arguments
|
||||
"""
|
||||
self.api_dut_reset()
|
||||
self.api_difftest_reset()
|
||||
|
||||
def do_xwatch(self, arg):
|
||||
"""Add a watch variable
|
||||
|
||||
Args:
|
||||
arg (string): Variable name
|
||||
"""
|
||||
key = arg.strip().split()
|
||||
if not key:
|
||||
for k, v in self.info_watch_list.items():
|
||||
message(f"{k}({v.W()}): 0x{v.value}")
|
||||
return
|
||||
arb = key[-1]
|
||||
sig = self.dut.GetInternalSignal(key[0])
|
||||
if sig:
|
||||
self.info_watch_list[arb] = sig
|
||||
|
||||
def do_xset(self, arg):
|
||||
"""Set the value of an internal signal
|
||||
|
||||
Args:
|
||||
name (string): Name of the internal signal
|
||||
value (int): Value of the internal signal
|
||||
"""
|
||||
args = arg.strip().split()
|
||||
if len(args) < 2:
|
||||
error("need args format: name value")
|
||||
return
|
||||
pin_name, pin_value = args[0], args[1]
|
||||
try:
|
||||
pin_value = int(pin_value)
|
||||
except Exception as e:
|
||||
error(f"convert {args[1]} to number fail: {str(e)}")
|
||||
return
|
||||
pin = self.dut.GetInternalSignal(pin_name)
|
||||
if pin:
|
||||
pin.AsImmWrite()
|
||||
pin.value = pin_value
|
||||
|
||||
def complete_xset(self, text, line, begidx, endidx):
|
||||
cmp = get_completions(self.get_dut_tree(), text)
|
||||
return cmp
|
||||
|
||||
def do_xstep(self, arg):
|
||||
"""Step through the circuit
|
||||
|
||||
Args:
|
||||
cycle (int): Number of cycles
|
||||
steps (int): Number of cycles per run; after each run, check for interrupt signals
|
||||
"""
|
||||
try:
|
||||
steps = 200
|
||||
cycle = arg.strip().split()
|
||||
if len(cycle) > 1:
|
||||
steps = int(cycle[1])
|
||||
if len(cycle) > 0:
|
||||
cycle = int(cycle[0])
|
||||
else:
|
||||
cycle = 1
|
||||
rcycles = self.api_step_dut(cycle, steps)
|
||||
info(f"step {rcycles} cycles complete" + (f" ({cycle - rcycles} cycle missed)" if rcycles != cycle else ""))
|
||||
except Exception as e:
|
||||
error(e)
|
||||
message("usage: xstep [cycle] [<steps>]")
|
||||
|
||||
def do_xprint(self, arg):
|
||||
"""Print the value and width of an internal signal
|
||||
|
||||
Args:
|
||||
arg (string): Name of the internal signal
|
||||
"""
|
||||
sig = self.dut.GetInternalSignal(arg)
|
||||
if sig:
|
||||
message(f"value: {hex(sig.value)} width: {sig.W()}")
|
||||
|
||||
def complete_xprint(self, text, line, begidx, endidx):
|
||||
cmp = get_completions(self.get_dut_tree(), text)
|
||||
return cmp
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
#***************************************************************************************
|
||||
# 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 bisect
|
||||
import subprocess
|
||||
from . import info, error, message, warn, find_executable_in_dirs
|
||||
|
||||
class CmdEfl:
|
||||
"""ELF command class for disassembling data"""
|
||||
|
||||
def __init__(self):
|
||||
self.elf_symbol_dict = None
|
||||
self.elf_current_exe_bin_is_efl = None
|
||||
self.flag_trace_pc_symbol_block_change = False
|
||||
|
||||
def api_get_elf_symbol_dict(self, elf_file, search_dirs=["./ready-to-run"]):
|
||||
"""Get the symbol dictionary from an ELF file
|
||||
|
||||
Args:
|
||||
elf_file (string): Path to the ELF file
|
||||
search_dirs (list): List of directories to search for CMD readelf
|
||||
"""
|
||||
if not os.path.exists(elf_file):
|
||||
error(f"{elf_file} not found")
|
||||
return None
|
||||
readelf = find_executable_in_dirs("readelf", search_dirs=search_dirs)
|
||||
cmd = [readelf, "-sW", elf_file]
|
||||
try:
|
||||
output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
|
||||
lines = output.decode().splitlines()
|
||||
symbol_value_dict = {}
|
||||
symbol_name_dict = {}
|
||||
symbol_type_dict = {}
|
||||
symbol_gnored = {}
|
||||
for line in lines[1:]:
|
||||
parts = line.split()
|
||||
if len(parts) != 8:
|
||||
continue
|
||||
if parts[1].lower() == "value":
|
||||
continue
|
||||
value = {"addr": int(parts[1], 16),
|
||||
"size": int(parts[2], 16),
|
||||
"type": parts[3],
|
||||
"bind": parts[4],
|
||||
"vis": parts[5],
|
||||
"ndx": parts[6],
|
||||
"name": parts[7],
|
||||
}
|
||||
if value["type"] in ("NOTYPE", "FILE"):
|
||||
if value["ndx"] not in ("1", "2", ".text", ".data"):
|
||||
if value["type"] not in symbol_gnored:
|
||||
symbol_gnored[value["type"]] = 1
|
||||
else:
|
||||
symbol_gnored[value["type"]] += 1
|
||||
continue
|
||||
symbol_name_dict[value["name"]] = value
|
||||
if value["type"] not in symbol_type_dict:
|
||||
symbol_type_dict[value["type"]] = 1
|
||||
else:
|
||||
symbol_type_dict[value["type"]] += 1
|
||||
if symbol_value_dict.get(value["addr"]) is None:
|
||||
symbol_value_dict[value["addr"]] = [value]
|
||||
else:
|
||||
symbol_value_dict[value["addr"]].append(value)
|
||||
info("Find symbol: %s" % symbol_type_dict)
|
||||
if len(symbol_gnored) > 0:
|
||||
warn("Ignored symbol: %s" % symbol_gnored)
|
||||
return {"addr": symbol_value_dict,
|
||||
"name": symbol_name_dict,
|
||||
"sorted_addr": sorted(symbol_value_dict.keys()),
|
||||
"sorted_name": sorted(symbol_name_dict.keys()),
|
||||
}
|
||||
except subprocess.CalledProcessError as e:
|
||||
error(f"Failed to read ELF file: {e.output.decode()}")
|
||||
return None
|
||||
|
||||
def api_echo_pc_symbol_block_change(self, current_pc, last_block_addr, last_pc):
|
||||
block_addr = last_block_addr
|
||||
if current_pc < 0:
|
||||
return block_addr
|
||||
if not self.flag_trace_pc_symbol_block_change:
|
||||
return block_addr
|
||||
if self.elf_current_exe_bin_is_efl is False:
|
||||
return block_addr
|
||||
if self.elf_symbol_dict is None:
|
||||
return block_addr
|
||||
symbol_index = bisect.bisect_left(self.elf_symbol_dict["sorted_addr"], current_pc) - 1
|
||||
if symbol_index < 0:
|
||||
return block_addr
|
||||
if symbol_index >= len(self.elf_symbol_dict["sorted_addr"]):
|
||||
return block_addr
|
||||
symbol_addr = self.elf_symbol_dict["sorted_addr"][symbol_index]
|
||||
if symbol_addr == last_block_addr:
|
||||
return block_addr
|
||||
# block address changed
|
||||
symbol = self.elf_symbol_dict.get("addr", {}).get(symbol_addr)
|
||||
if not symbol:
|
||||
return block_addr
|
||||
symbol_name = ','.join([s['name'] for s in symbol])
|
||||
symbol_pre = self.elf_symbol_dict.get("addr", {}).get(last_block_addr)
|
||||
symbol_pre_name = "None"
|
||||
if symbol_pre:
|
||||
symbol_pre_name = ','.join([s['name'] for s in symbol_pre])
|
||||
delta_last = last_pc-last_block_addr
|
||||
delta_curr = current_pc-symbol_addr
|
||||
message(f"PC block changed({hex(last_pc)} = > {hex(current_pc)}, " +
|
||||
f"cycle: {self.difftest_stat.trap.cycleCnt}): {symbol_pre_name}({hex(last_block_addr)})+{hex(delta_last)} " +
|
||||
f"-> {symbol_name}({hex(symbol_addr)})+{hex(delta_curr)}")
|
||||
return symbol_addr
|
||||
|
||||
def complete_xtrace_pc_symbol_block_change(self, text, line, begidx, endidx):
|
||||
"""Complete the command for tracing PC symbol block change
|
||||
|
||||
Args:
|
||||
text (string): Current text
|
||||
line (string): Current line
|
||||
begidx (int): Beginning index
|
||||
endidx (int): Ending index
|
||||
"""
|
||||
return [x for x in ["on", "off"] if x.startswith(text)] if text else ["on", "off"]
|
|
@ -0,0 +1,55 @@
|
|||
#***************************************************************************************
|
||||
# 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
|
||||
from . import info, error, message, warn
|
||||
|
||||
class CmdFiles:
|
||||
|
||||
def api_dut_bin_load(self, bin_file):
|
||||
"""Load a bin file into memory
|
||||
|
||||
Args:
|
||||
bin_file (string): Path to the bin file
|
||||
"""
|
||||
assert os.path.exists(bin_file), "file %s not found" % bin_file
|
||||
self.exec_bin_file = bin_file
|
||||
if self.mem_inited:
|
||||
self.df.overwrite_ram(bin_file, self.mem_size)
|
||||
else:
|
||||
self.api_init_mem()
|
||||
self.info_cache_asm.clear()
|
||||
|
||||
def complete_xflash(self, text, line, begidx, endidx):
|
||||
return self.api_complite_localfile(text)
|
||||
|
||||
def do_xload(self, arg):
|
||||
"""Load a binary file into memory
|
||||
|
||||
Args:
|
||||
arg (string): Path to the binary file
|
||||
"""
|
||||
if not arg:
|
||||
message("usage: xload <bin_file>")
|
||||
return
|
||||
if not os.path.exists(arg):
|
||||
error(f"{arg} not found")
|
||||
return
|
||||
self.api_dut_bin_load(arg)
|
||||
|
||||
def complete_xload(self, text, line, begidx, endidx):
|
||||
return self.api_complite_localfile(text)
|
|
@ -0,0 +1,154 @@
|
|||
#***************************************************************************************
|
||||
# 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
|
||||
from . import error, warn, message
|
||||
|
||||
class CmdFlash:
|
||||
|
||||
def api_get_flash_init_iregs(self):
|
||||
"""Get Flash internal registers
|
||||
|
||||
Returns:
|
||||
list(int): Register values
|
||||
"""
|
||||
if not self.api_check_if_xspdb_init_bin_loaded():
|
||||
return []
|
||||
base_offset = 8
|
||||
reg_index = self.mpc_iregs
|
||||
regs = []
|
||||
for i in range(len(reg_index)):
|
||||
regs.append((reg_index[i], self.df.FlashRead(base_offset + i*8)))
|
||||
return regs
|
||||
|
||||
def api_get_flash_init_fregs(self):
|
||||
"""Get Flash floating-point registers
|
||||
|
||||
Returns:
|
||||
list(int): Register values
|
||||
"""
|
||||
if not self.api_check_if_xspdb_init_bin_loaded():
|
||||
return []
|
||||
base_offset = 8 + 32*8
|
||||
regs = []
|
||||
for i in range(len(self.fregs)):
|
||||
regs.append((self.fregs[i], self.df.FlashRead(base_offset + i*8)))
|
||||
return regs
|
||||
|
||||
def api_set_flash_float_regs(self, regs):
|
||||
"""Set Flash floating-point registers
|
||||
|
||||
Args:
|
||||
regs (list(float), dict): Register values
|
||||
"""
|
||||
if not self.api_check_if_xspdb_init_bin_loaded():
|
||||
return
|
||||
base_offset = 8 + 32*8
|
||||
reg_map = {k: v for v, k in enumerate(self.fregs)}
|
||||
return self.api_set_flash_data_values(base_offset, self.fregs, reg_map, regs, "fregs")
|
||||
|
||||
def api_set_flash_int_regs(self, regs):
|
||||
"""Set Flash internal registers
|
||||
|
||||
Args:
|
||||
regs (list(int), dict): Register values
|
||||
"""
|
||||
if not self.api_check_if_xspdb_init_bin_loaded():
|
||||
return
|
||||
base_offset = 8
|
||||
reg_index = self.mpc_iregs
|
||||
reg_map = {k: v for v, k in enumerate(reg_index)}
|
||||
return self.api_set_flash_data_values(base_offset, reg_index, reg_map, regs, "iregs")
|
||||
|
||||
def api_check_if_xspdb_init_bin_loaded(self):
|
||||
"""Check if xspdb_flash_init.bin is loaded
|
||||
|
||||
Returns:
|
||||
bool: Whether it is loaded
|
||||
"""
|
||||
if not self.flash_bin_file or self.xspdb_init_bin not in self.flash_bin_file:
|
||||
error(f"{self.xspdb_init_bin} not loaded")
|
||||
return False
|
||||
return True
|
||||
|
||||
def api_set_flash_data_values(self, base_offset, reg_index, reg_map, kdata, kname):
|
||||
"""Set Flash register values
|
||||
|
||||
Args:
|
||||
base_offset (int): Base address of the registers
|
||||
reg_index (list(string)): List of register names
|
||||
reg_map (dict): Mapping of register names
|
||||
kdata (list(int), dict): Register values
|
||||
kname (string): Register name
|
||||
"""
|
||||
if isinstance(kdata, list):
|
||||
for i, r in enumerate(kdata):
|
||||
if isinstance(r, str):
|
||||
r = r.strip()
|
||||
if r == "-":
|
||||
continue
|
||||
r = int(r, 0)
|
||||
assert isinstance(r, int), f"{kname}[{i}] not number"
|
||||
self.df.FlashWrite(base_offset + i*8, r)
|
||||
elif isinstance(kdata, dict):
|
||||
if "*" in kdata:
|
||||
v = kdata["*"]
|
||||
for key in reg_index:
|
||||
if key in kdata:
|
||||
v = kdata[key]
|
||||
self.df.FlashWrite(base_offset + reg_map[key]*8, v)
|
||||
else:
|
||||
for key, v in kdata.items():
|
||||
if key in reg_map:
|
||||
self.df.FlashWrite(base_offset + reg_map[key]*8, v)
|
||||
else:
|
||||
warn(f"{kname}[{key}] not found")
|
||||
else:
|
||||
assert False, "regs type error"
|
||||
|
||||
# delete asm data in cache
|
||||
cache_index = self.flash_base - self.flash_base % self.info_cache_bsz
|
||||
if cache_index in self.info_cache_asm:
|
||||
del self.info_cache_asm[cache_index]
|
||||
|
||||
def api_dut_reset_flash(self):
|
||||
"""Reset the DUT Flash"""
|
||||
self.df.flash_finish()
|
||||
self.df.InitFlash("")
|
||||
self.flash_bin_file = None
|
||||
|
||||
def do_xflash(self, arg):
|
||||
"""Load a binary file into Flash
|
||||
|
||||
Args:
|
||||
arg (string): Path to the binary file
|
||||
"""
|
||||
if not arg:
|
||||
message("usage: xload <bin_file>")
|
||||
return
|
||||
if not os.path.exists(arg):
|
||||
error(f"{arg} not found")
|
||||
return
|
||||
self.api_dut_flash_load(arg)
|
||||
|
||||
def do_xreset_flash(self, arg):
|
||||
"""Reset Flash
|
||||
|
||||
Args:
|
||||
arg (None): No arguments
|
||||
"""
|
||||
self.api_reset_flash()
|
|
@ -0,0 +1,46 @@
|
|||
#***************************************************************************************
|
||||
# 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 bisect
|
||||
from collections import OrderedDict
|
||||
from . import error, info
|
||||
|
||||
class CmdInfo:
|
||||
"""Info command class
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
assert hasattr(self, "dut"), "this class must be used in XSPdb, canot be used alone"
|
||||
self.info_cache_asm = {}
|
||||
self.info_cache_bsz = 256
|
||||
self.info_cached_cmpclist = None
|
||||
self.info_watch_list = OrderedDict()
|
||||
self.info_force_address = None
|
||||
self.info_last_address = None
|
||||
|
||||
def api_info_get_last_commit_pc(self):
|
||||
"""Get the last commit PC
|
||||
|
||||
Returns:
|
||||
int: Address
|
||||
"""
|
||||
istep_pc = self.api_get_istep_last_commit_pc()
|
||||
if len(istep_pc) != 0:
|
||||
return max(istep_pc)
|
||||
valid_pc_list = [x[0] for x in self.api_commit_pc_list() if (x[1] or self.api_is_difftest_diff_run())]
|
||||
return max(valid_pc_list) if valid_pc_list else self.mem_base
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
#***************************************************************************************
|
||||
# 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.
|
||||
#***************************************************************************************
|
||||
|
||||
|
||||
from . import message, warn, GREEN, RESET, info
|
||||
|
||||
class CmdTrap:
|
||||
"""Trap command class
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
assert hasattr(self, "dut"), "this class must be used in XSPdb, canot be used alone"
|
||||
self.condition_good_trap = {}
|
||||
self.break_on_trap = {}
|
||||
|
||||
def api_is_hit_good_trap(self, show_log=False):
|
||||
"""Check if the good trap is hit
|
||||
|
||||
Returns:
|
||||
bool: Whether the good trap is hit
|
||||
"""
|
||||
trap = self.difftest_stat.trap
|
||||
if trap.hasTrap != 0 and trap.code == 0:
|
||||
if show_log:
|
||||
message(f"{GREEN}HIT GOOD TRAP at pc = 0x{trap.pc:x} cycle = 0x{trap.cycleCnt:x} {RESET}")
|
||||
return True
|
||||
return False
|
||||
|
||||
def api_is_hit_good_loop(self, show_log=False):
|
||||
"""Check if the good trap is hit
|
||||
|
||||
Args:
|
||||
show_log (bool): Whether to show the log
|
||||
Returns:
|
||||
bool: Whether the good trap is hit
|
||||
"""
|
||||
for i in range(8):
|
||||
cmt = self.difftest_stat.get_commit(i)
|
||||
if cmt and cmt.valid:
|
||||
if cmt.instr == 0x6f:
|
||||
if show_log:
|
||||
message(f"{GREEN}HIT GOOD LOOP at pc = 0x{cmt.pc:x}{RESET}")
|
||||
return True
|
||||
return False
|
||||
|
||||
def api_is_hit_trap_break(self, show_log=False):
|
||||
"""Check if the trap is break
|
||||
|
||||
Args:
|
||||
show_log (bool): Whether to show the log
|
||||
Returns:
|
||||
bool: Whether the trap is break
|
||||
"""
|
||||
trap = self.difftest_stat.trap
|
||||
if trap.hasTrap != 0 and self.api_is_trap_break_on():
|
||||
if show_log:
|
||||
message(f"{GREEN}HIT TRAP BREAK pc: 0x{trap.pc:x} code: 0x{trap.code:x} hasWFI: {trap.hasWFI}{RESET}")
|
||||
return True
|
||||
return False
|
|
@ -0,0 +1,100 @@
|
|||
#***************************************************************************************
|
||||
# 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
|
||||
from . import message, info, error
|
||||
|
||||
class CmdTrap:
|
||||
"""Trap command class
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
assert hasattr(self, "dut"), "this class must be used in XSPdb, canot be used alone"
|
||||
|
||||
def api_init_waveform(self):
|
||||
"""Initialize the waveform (close waveform at beginning)
|
||||
"""
|
||||
self.dut.RefreshComb()
|
||||
self.dut.CloseWaveform()
|
||||
self.waveform_on = False
|
||||
|
||||
def api_is_waveform_on(self):
|
||||
"""Check if waveform recording is on
|
||||
|
||||
Returns:
|
||||
bool: Whether the waveform is on
|
||||
"""
|
||||
return self.waveform_on
|
||||
|
||||
def api_waveform_on(self, wave_file=""):
|
||||
"""Start waveform recording
|
||||
|
||||
Args:
|
||||
wave_file (str): Waveform file path [optional]
|
||||
"""
|
||||
if self.waveform_on:
|
||||
info("waveform is already on")
|
||||
return True
|
||||
if wave_file:
|
||||
if not os.path.isabs(wave_file):
|
||||
error(f"waveform file[{wave_file}] name must be a ligal path")
|
||||
return False
|
||||
self.dut.SetWaveform(wave_file)
|
||||
self.dut.OpenWaveform()
|
||||
self.waveform_on = True
|
||||
return True
|
||||
|
||||
def api_waveform_off(self):
|
||||
"""Close waveform recording"""
|
||||
if not self.waveform_on:
|
||||
info("waveform is already off")
|
||||
return True
|
||||
self.dut.CloseWaveform()
|
||||
self.waveform_on = False
|
||||
return True
|
||||
|
||||
def do_xwave_on(self, arg):
|
||||
"""Start waveform recording
|
||||
|
||||
Args:
|
||||
name (str): Waveform file path [optional]
|
||||
"""
|
||||
if self.api_waveform_on(arg):
|
||||
info("waveform on")
|
||||
else:
|
||||
message("usage: xwave_on [waveform file path]")
|
||||
|
||||
def do_xwave_off(self, arg):
|
||||
"""Close waveform recording
|
||||
|
||||
Args:
|
||||
arg (None): No arguments
|
||||
"""
|
||||
self.api_waveform_off()
|
||||
info("waveform off")
|
||||
|
||||
def do_xwave_flush(self, arg):
|
||||
"""Flush waveform recording
|
||||
|
||||
Args:
|
||||
arg (None): No arguments
|
||||
"""
|
||||
if not self.waveform_on:
|
||||
error("waveform is not on")
|
||||
return
|
||||
self.dut.FlushWaveform()
|
||||
info("waveform flush complete")
|
|
@ -0,0 +1,313 @@
|
|||
#***************************************************************************************
|
||||
# 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 inspect
|
||||
import importlib.util
|
||||
import sys
|
||||
import logging
|
||||
|
||||
RESET = "\033[0m"
|
||||
GREEN = "\033[32m"
|
||||
RED = "\033[31m"
|
||||
YELLOW = "\033[33m"
|
||||
|
||||
_xspdb_enable_log = False
|
||||
_xspdb_logger = None
|
||||
|
||||
def xspdb_set_log(xspdb_enable_log:bool):
|
||||
global _xspdb_enable_log
|
||||
global _xspdb_logger
|
||||
|
||||
_xspdb_enable_log = xspdb_enable_log
|
||||
if _xspdb_enable_log and _xspdb_logger is None:
|
||||
#initialize logger
|
||||
_xspdb_logger = logging.getLogger("XSPdb")
|
||||
_xspdb_logger.setLevel(logging.DEBUG)
|
||||
ch = logging.FileHandler("XSPdb.log")
|
||||
ch.setLevel(logging.DEBUG)
|
||||
formatter = logging.Formatter('%(message)s')
|
||||
ch.setFormatter(formatter)
|
||||
_xspdb_logger.addHandler(ch)
|
||||
|
||||
def xspdb_set_log_file(log_file:str):
|
||||
global _xspdb_logger
|
||||
if _xspdb_logger is None:
|
||||
_xspdb_logger = logging.getLogger("XSPdb")
|
||||
_xspdb_logger.setLevel(logging.DEBUG)
|
||||
ch = logging.FileHandler(log_file)
|
||||
ch.setLevel(logging.DEBUG)
|
||||
formatter = logging.Formatter('%(message)s')
|
||||
ch.setFormatter(formatter)
|
||||
_xspdb_logger.addHandler(ch)
|
||||
else:
|
||||
for handler in _xspdb_logger.handlers:
|
||||
if isinstance(handler, logging.FileHandler):
|
||||
handler.close()
|
||||
_xspdb_logger.removeHandler(handler)
|
||||
ch = logging.FileHandler(log_file)
|
||||
ch.setLevel(logging.DEBUG)
|
||||
formatter = logging.Formatter('%(message)s')
|
||||
ch.setFormatter(formatter)
|
||||
_xspdb_logger.addHandler(ch)
|
||||
|
||||
_XSPDB_LOG_LEVEL = logging.DEBUG
|
||||
|
||||
def set_xspdb_log_level(level):
|
||||
"""
|
||||
Set the log level for XSPdb.
|
||||
|
||||
Args:
|
||||
level (int): Log level to set. Use logging.DEBUG, logging.INFO, etc.
|
||||
"""
|
||||
global _xspdb_logger
|
||||
if _xspdb_logger is not None:
|
||||
_xspdb_logger.setLevel(level)
|
||||
for handler in _xspdb_logger.handlers:
|
||||
handler.setLevel(level)
|
||||
|
||||
def set_xspdb_debug_level(level):
|
||||
global _XSPDB_LOG_LEVEL
|
||||
_XSPDB_LOG_LEVEL = level
|
||||
set_xspdb_log_level(level)
|
||||
|
||||
def log_message(*a, **k):
|
||||
"""Print a message to log"""
|
||||
if _xspdb_enable_log:
|
||||
old_level = _xspdb_logger.level
|
||||
set_xspdb_log_level(logging.INFO)
|
||||
_xspdb_logger.info(*a, **k)
|
||||
set_xspdb_log_level(old_level)
|
||||
|
||||
def message(*a, **k):
|
||||
"""Print a message"""
|
||||
k["flush"] = True
|
||||
print(*a, **k)
|
||||
del k["flush"]
|
||||
log_message(*a, **k)
|
||||
|
||||
def info(msg):
|
||||
"""Print information"""
|
||||
if _XSPDB_LOG_LEVEL <= logging.INFO:
|
||||
print(f"{GREEN}[Info] %s{RESET}" % msg, flush=True)
|
||||
if _xspdb_enable_log:
|
||||
_xspdb_logger.info("[Info] %s" % msg)
|
||||
|
||||
def debug(msg):
|
||||
"""Print debug information"""
|
||||
if _XSPDB_LOG_LEVEL <= logging.DEBUG:
|
||||
print("[Debug] %s" % msg, flush=True)
|
||||
if _xspdb_enable_log:
|
||||
_xspdb_logger.debug("[Debug] %s" % msg)
|
||||
|
||||
def error(msg):
|
||||
"""Print error information"""
|
||||
if _XSPDB_LOG_LEVEL <= logging.ERROR:
|
||||
print(f"{RED}[Error] %s{RESET}" % msg, flush=True)
|
||||
if _xspdb_enable_log:
|
||||
_xspdb_logger.error("[Error] %s" % msg)
|
||||
|
||||
def warn(msg):
|
||||
"""Print warning information"""
|
||||
if _XSPDB_LOG_LEVEL <= logging.WARNING:
|
||||
print(f"{YELLOW}[Warn] %s{RESET}" % msg, flush=True)
|
||||
if _xspdb_enable_log:
|
||||
_xspdb_logger.warning("[Warn] %s" % msg)
|
||||
|
||||
def build_prefix_tree(signals):
|
||||
tree = {}
|
||||
for signal in signals:
|
||||
current = tree
|
||||
parts = signal.split('.')
|
||||
for part in parts:
|
||||
if part not in current:
|
||||
current[part] = {}
|
||||
current = current[part]
|
||||
return tree
|
||||
|
||||
def get_completions(tree, prefix):
|
||||
parts = prefix.split('.')
|
||||
current_node = tree
|
||||
for i, part in enumerate(parts):
|
||||
if i == len(parts) - 1:
|
||||
break
|
||||
if part not in current_node:
|
||||
return []
|
||||
current_node = current_node[part]
|
||||
|
||||
if prefix.endswith('.'):
|
||||
return [prefix + v for v in current_node.keys()]
|
||||
elif part in current_node:
|
||||
return [prefix + "." + v for v in current_node[part].keys()]
|
||||
else:
|
||||
if "." in prefix:
|
||||
prefix = prefix.rsplit(".", 1)[0] + "."
|
||||
else:
|
||||
prefix = ""
|
||||
last_part = parts[-1] if parts else ''
|
||||
candidates = list(current_node.keys())
|
||||
completions = [prefix + c for c in candidates if c.startswith(last_part)]
|
||||
return completions
|
||||
|
||||
def find_executable_in_dirs(executable_name, search_dirs="./ready-to-run"):
|
||||
"""
|
||||
Search for an executable file in the specified directories. If not found, search in the system path.
|
||||
|
||||
Args:
|
||||
executable_name (str): Name of the executable file
|
||||
search_dirs (list): List of specified directories
|
||||
|
||||
Returns:
|
||||
str: Path to the executable file, or None if not found
|
||||
"""
|
||||
import shutil
|
||||
for directory in search_dirs:
|
||||
potential_path = os.path.join(directory, executable_name)
|
||||
if os.path.isfile(potential_path) and os.access(potential_path, os.X_OK):
|
||||
return os.path.abspath(potential_path)
|
||||
return shutil.which(executable_name)
|
||||
|
||||
spike_dasm_path = find_executable_in_dirs("spike-dasm", search_dirs=["./ready-to-run"])
|
||||
if not spike_dasm_path:
|
||||
info(f"spike-dasm found, use captone to disassemble, this may cannot work for some instructions")
|
||||
def dasm_bytes(bytes_data, address):
|
||||
"""Disassemble binary data
|
||||
|
||||
Args:
|
||||
bytes_data (bytes): Binary data
|
||||
address (int): Starting address
|
||||
|
||||
Returns:
|
||||
list: List of disassembled results
|
||||
"""
|
||||
if spike_dasm_path is not None:
|
||||
# iterate over bytes_data in chunks of 2 bytes (c.instr. 16 bits)
|
||||
instrs_todecode = []
|
||||
full_instr = None
|
||||
for i in range(0, len(bytes_data), 2):
|
||||
c_instr = bytes_data[i:i+2]
|
||||
if full_instr is not None:
|
||||
full_instr += c_instr
|
||||
instrs_todecode.append((int.from_bytes(full_instr, byteorder='little', signed=False),
|
||||
full_instr[::-1].hex(), i-2 + address))
|
||||
full_instr = None
|
||||
continue
|
||||
# Is full 32 bit instr
|
||||
if c_instr[0] & 0x3 == 0x3: # full instr
|
||||
full_instr = c_instr
|
||||
continue
|
||||
# Is compressed 16 instr
|
||||
instrs_todecode.append((int.from_bytes(c_instr, byteorder='little', signed=False),
|
||||
c_instr[::-1].hex(), i + address))
|
||||
import subprocess
|
||||
result_asm = []
|
||||
# For every 1000 instrs, call spike-dasm
|
||||
for i in range(0, len(instrs_todecode), 1000):
|
||||
instrs = instrs_todecode[i:i+1000]
|
||||
ins_dm = "\\n".join(["DASM(%016lx)" % i[0] for i in instrs])
|
||||
# Call spike-dasm
|
||||
bash_cmd = 'echo "%s"|%s' % (ins_dm, spike_dasm_path)
|
||||
result = subprocess.run(bash_cmd,
|
||||
shell=True,
|
||||
text=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
assert result.returncode == 0, f"Error({bash_cmd}): " + str(result.stderr)
|
||||
ins_dm = result.stdout.strip().split("\n")
|
||||
assert len(ins_dm) == len(instrs), "Error(%s): %d != %d\n%s vs %s" % (bash_cmd, len(ins_dm), len(instrs), ins_dm, instrs)
|
||||
for i, v in enumerate(instrs):
|
||||
result_asm.append((v[2], v[1], ins_dm[i] if "unknown" not in ins_dm[i] else "unknown.bytes %s" % v[1], ""))
|
||||
return result_asm
|
||||
try:
|
||||
import capstone
|
||||
except ImportError:
|
||||
raise ImportError("Please install capstone library: pip install capstone")
|
||||
md = capstone.Cs(capstone.CS_ARCH_RISCV, capstone.CS_MODE_RISCV32|capstone.CS_MODE_RISCV64|capstone.CS_MODE_RISCVC)
|
||||
md.detail = True
|
||||
md.skipdata = True
|
||||
md.skipdata_setup = (".byte", None, None)
|
||||
asm_data = []
|
||||
for instr in md.disasm(bytes_data, address):
|
||||
asm_data.append((instr.address, instr.bytes[::-1].hex(), instr.mnemonic, instr.op_str))
|
||||
return asm_data
|
||||
|
||||
def register_commands(src_class, dest_class, dest_instance):
|
||||
"""
|
||||
Register all commands from src_class to dest_class.
|
||||
"""
|
||||
reg_count = 0
|
||||
for cls_name in dir(src_class):
|
||||
if not cls_name.startswith("Cmd"):
|
||||
continue
|
||||
cmd_cls = getattr(src_class, cls_name)
|
||||
if not inspect.isclass(cmd_cls):
|
||||
continue
|
||||
for func in dir(cmd_cls):
|
||||
if func.startswith("__"):
|
||||
continue
|
||||
func_obj = getattr(cmd_cls, func)
|
||||
if not callable(func_obj):
|
||||
continue
|
||||
if not hasattr(dest_class, func):
|
||||
setattr(dest_class, func, func_obj)
|
||||
dest_instance.register_map[func] = src_class.__name__+"."+cls_name
|
||||
reg_count += 1
|
||||
elif has_override_tag(func_obj):
|
||||
# If the function is overridden, replace it
|
||||
old_func = getattr(dest_class, func)
|
||||
func_obj.__old_func__ = old_func
|
||||
setattr(dest_class, func, func_obj)
|
||||
warn(f"Command {func} overridden in {dest_class.__name__} from {src_class.__name__}.{cls_name}")
|
||||
else:
|
||||
warn(f"Command {func} already exists in {dest_class.__name__}, ignoring")
|
||||
continue
|
||||
init = getattr(cmd_cls, "__init__", None)
|
||||
if init:
|
||||
init(dest_instance)
|
||||
return reg_count
|
||||
|
||||
def load_module_from_file(filepath):
|
||||
module_name = os.path.splitext(os.path.basename(filepath))[0]
|
||||
spec = importlib.util.spec_from_file_location(module_name, filepath)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[module_name] = module
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
def load_package_from_dir(dirpath):
|
||||
module_name = os.path.basename(dirpath)
|
||||
init_path = os.path.join(dirpath, "__init__.py")
|
||||
assert os.path.exists(init_path), f"__init__.py not found in {dirpath}"
|
||||
spec = importlib.util.spec_from_file_location(module_name, init_path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[module_name] = module
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
# decorators
|
||||
def override(func):
|
||||
"""
|
||||
Decorator to override a function in a class.
|
||||
"""
|
||||
func.__is_xspdb_override__ = True
|
||||
return func
|
||||
|
||||
def has_override_tag(func):
|
||||
"""
|
||||
Check if a function is overridden.
|
||||
"""
|
||||
return getattr(func, "__is_xspdb_override__", False)
|
|
@ -0,0 +1,258 @@
|
|||
#***************************************************************************************
|
||||
# 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)
|
Loading…
Reference in New Issue