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
|
huancun/ @linjuanZ @Ivyfeather
|
||||||
|
|
||||||
src/main/scala/top/ @Tang-Haojin
|
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
|
# import docker support
|
||||||
include scripts/Makefile.docker
|
include scripts/Makefile.docker
|
||||||
|
|
||||||
|
# import pdb support
|
||||||
|
include scripts/Makefile.pdb
|
||||||
|
|
||||||
# if XSTopPrefix is specified in yaml, use it.
|
# if XSTopPrefix is specified in yaml, use it.
|
||||||
ifneq ($(YAML_CONFIG),)
|
ifneq ($(YAML_CONFIG),)
|
||||||
HAS_PREFIX_FROM_YAML = $(shell grep 'XSTopPrefix *:' $(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
|
./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
|
||||||
|
|
||||||
[Troubleshooting Guide](https://github.com/OpenXiangShan/XiangShan/wiki/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
|
./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)
|
[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