用telnet+openocd+jtag_dpi+vcs仿真调试RISCV的cpu

江阳冰
2023-12-01

目录

背景:

需要了解的基础知识,此处不做介绍:

如何连结和调试

一些中间过程需要关心,记录

背景:

  1. Server    :tcl+telnet
  2. SW         :openocd+JTAG_DPI+VCS仿真(riscv base+1core+multi_harts)

用tcl,通过telnet连接openocd,与JTAG_DPI连接,JTAG_DPI的verilog model 例化在testbench中,DUT是riscv的core,具有JTAG的调试接口,遵循riscv-debug-spec

这个文章的介绍只是我操作过程中的一些理解和整理,也有很多不涉及的或者有偏差的地方,如果大家又发现问题,可以随时私信沟通交流,多谢。

需要了解的基础知识,此处不做介绍:

  1. Riscv-spec
  2. Riscv-spec-privileged
  3. Riscv-debug-stable
  4. JTAG spec
  5. Openocd user guide
  6. Openocd如何通过JTAG_DPI和VCS 仿真连接,网络有教程

我是用的下边的JTAG_DPI

GitHub - yaozhaosh/e200_opensource: The Ultra-Low Power RISC Core

About (OpenOCD User’s Guide)

如何连结和调试

配置target.cfg 文件

       ##配置interface,bitbang和jtag_dpi相连,telnet_port是telnet和openocd相连

interface remote_bitbang

    ##注意,bitbanghostport要和jtag_dpi设置为相同的配置

remote_bitbang_port 44853

remote_bitbang_host t01n70

tcl_port 6667                  #自行更改

telnet_port 44441              #自行更改,如果是window界面,可以在windows打开telnet服务

    ##以下是配置chipnametap_ID

if { [info exists CHIPNAME] } {

   set  _CHIPNAME $CHIPNAME

} else {

   set  _CHIPNAME riscv

}

if { [info exists CPUTAPID ] } {

   set _CPUTAPID $CPUTAPID

} else {

    ## 以下设置jtag tapIDCODE

   set _CPUTAPID 0x****

}

   

    ## 设置jtagnew_tap

jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id $_CPUTAPID

set _TARGETNAME $_CHIPNAME.cpu

    ## 根据该coreharts的情况配置target,认为每一个hart 都是一个target

    ## 为何除了hart5,其他都是defer-examine,因为本方案每次调试只会启动一个hart,其他的都是sleep状态,所以不需要其他core examine

target create $_TARGETNAME.hart0 riscv -chain-position $_TARGETNAME -coreid 0 -rtos hwthread  -defer-examine

target create $_TARGETNAME.hart1 riscv -chain-position $_TARGETNAME -coreid 1 -rtos hwthread  -defer-examine 

target create $_TARGETNAME.hart2 riscv -chain-position $_TARGETNAME -coreid 2 -rtos hwthread  -defer-examine

target create $_TARGETNAME.hart3 riscv -chain-position $_TARGETNAME -coreid 3 -rtos hwthread  -defer-examine

target create $_TARGETNAME.hart4 riscv -chain-position $_TARGETNAME -coreid 4 -rtos hwthread  -defer-examine

target create $_TARGETNAME.hart5 riscv -chain-position $_TARGETNAME -coreid 5 -rtos hwthread  -defer-examine

target create $_TARGETNAME.hart6 riscv -chain-position $_TARGETNAME -coreid 6 -rtos hwthread  -defer-examine

target create $_TARGETNAME.hart7 riscv -chain-position $_TARGETNAME -coreid 7 -rtos hwthread  -defer-examine

    ## 一些其他配置

gdb_report_data_abort enable

gdb_report_register_access_error enable

riscv set_command_timeout_sec 120

    ## hart5 examine结束之后,立刻halthart

$_TARGETNAME.hart5 configure -event examine-end {

    halt

}

############  target.cfg 配置完成。

跑起vcs仿真,(仿真截止时间一定要长,应为jtag 输入实在太慢了)

    ## -d 可以打出很多debug的信息,感觉非常有用,打出log文件,可以不用trace屏幕了

openocd -f target.cfg -d -l my.log 

    ## telnet连接

telnet  host  port_number

    ##  连接成功之后,可以在命令行调试;

我做的调试命令是

   

    ##打印出当前的targets都有哪些

targets

    ##选中要调试的hart,不然会处于hart7

targets riscv.cpu.hart5

    ##可以看到current target是不是hart5,如果是可以做后续调试

target current

    ##查看hart5是不是处于halted状态,或者,当前是处于什么状态;

riscv.cpu.hart5 curstate

    ## 可以做hartreset,这个需要根据自己的需求自定义

## 这个proc是设置起始地址和hartresethaltreqhartreset之后会进入到debug mode

## proc完成后,可以做step,或者是设置硬件断点

proc halted_hartreset_halt {target data0 data1 dmcontrol} {

    set CurS [$target curstate]

    if { $CurS eq "halted" } {

        set dmctrl_ori [riscv dmi_read 0x10]

        set dmctrl_new [ expr $dmctrl_ori | $dmcontrol ]

        riscv dmi_write 0x05 $data1

        riscv dmi_write 0x04 $data0

        riscv dmi_write 0x10 $dmctrl_new

        riscv dmi_read 0x11

        riscv dmi_read 0x10

    }        

}

    ##hartreset之后

    ## 设置单步执行,该命令会设置dcsrstep位域,然后resume,执行一条指令,理论上会再次进入debug mode

step

    ## 查看该hart 的状态,一般是halted

riscv.cpu.hart5 curstate

    ## reg 可以通过abstract command的register access的方式读取hart内部的寄存器

    ## 读取dcsr

reg dcsr

    ## x1 写入0x20 (一般不会这么用)我的工程主要是read

reg x1  0x20

    ##  设置硬件断点,地址,asid,字节数,hw4option

bp 0x000000000010001c 0 4 hw

    ## 断点设置成功之后,resume,然后再断点进入debug mode

resume

    ## 如果想要设置第二个断点,先要remove所有的断点,然后再设置

    ## remove bp,不会对tdata2 清零,要注意;

rbp all

    ## exit,调试结束

exit

一些中间过程需要关心,记录

调试之前会做target examine,只有完成target examine之后,后续的调试命令才能被识别,否则openocd会报告当前的target还没有被examine

通常target的examine主要做的流程是:(假如只启动某一个调试hart的examine)

  1. 读取dtmcs这个DR,获取DM集成的一些信息;
  2. Allocate new DM,deassert dmactive,然后assert dmactive
  3. 枚举hartsel,写入hasel和hartse*,hartse*为最大值,检查当前DM支持的最大hart个数;如果写入的hartse*不支持,会汇报 noexistense的dmstatus,随后读取dmcontrol,观察真实写入的hartse*是多少;
  4. 随后分别读取hartinfo,sbcs,abstractcs这些DMR,看是否支持,如果支持,获取这些DMR的各个域段的值;以观察DM支持的feature;
  5. 如果发现有havereset位域置位,则通过ackhavereset,进行握手;
  6. 从0开始一直遍历到最大的hartsel*,看当前的DM支持的hart的范围;
  7. 因为当前启动的是某个线程,所以后续针对这个线程开始做后续步骤;
  8. 如果当前hart 是running状态,设置haltreq,挂起该hart;
  9. Polling该hart的dmstatus,直到有anyhalted的状态,可以认为hart已经进入debug_mode;如果已经进入,则deassert haltreq;
  10. 然后通过abstract command的方式读取GPR x8,
  11. 通过abstract command的方式读取CSR_misa,观察当前支持的ISA如何;
  12. Create register cache;
  13. 根据misa不同可能后续还会有其他的操作,我的调试支持的只有64I,之后不需要读取其他信息了;
  14. 发送resume,等polling dmstatus,收集到anyrunning和anyresumeack之后认为resume成功,deassert resume,完成examine的过程;

   

Step这个操作会

  1. 如果还未设置过硬件断点,step首先会检测trigger情况,通过遍历tselect,看写入的和读出的是否一致,如过不一致,可以列举出支持的trigger的个数;
  2. 随后读取tdata1,看当前tdata1支持的control type,决定后续如何加断点trigger的控制
  3. 随后读取dcsr看当前的状态
  4. 如果step位域不是1,则设置该位域;
  5. 发送resume,唤起hart
  6. 通常执行完一条指令,则会再次进入到debug mode

bp 这个命令

通过访问tselect,tdata1和tdata2 做相关的trigger的配置

我的方案是支持1个硬件指令断点,tdata1的type位域是2;

resume

该操作会清除dcsr的step位域,解除单步的设置,直到响应了断点,或者haltreq

rbp all

清除所有的trigger设置,对tdata1 清零,但不会改变tdata2的值

 类似资料: