debug命令_浅谈RISC-V的DEBUG系统及其仿真

0fd2a9ede542a6e445a958fb51346471.png
横看成岭侧成峰,远近高低各不同。 不识庐山真面目,只缘身在此山中。 —— 苏轼《题西林壁》

研究RISC-V系统的debug有很多角度、很多内容,涉及很多软硬件工具如GDB、OpenOCD、adapter,芯片里的JTAG、DM模块、处理器的支持以及RISC-V external debug协议等等。如果单陷入某个方面,则如盲人摸象,很难了解全局的运作原理,我们有必要跳到系统外面来,捋一捋整个流程到底是怎么运转起来的。值得一提的是,在没有芯片、没有开发板的情况下,我们可以通过仿真的方式(各种仿真工具包括verilator)来接入GDB。

1. RISC-V Debug系统组件

在了解Verilated RISC-V系统如何debug之前,我们先来看一下真实芯片是如何debug的。假设我们使用PC上的Linux里的OpenOCD和GDB对一颗RISC-V SoC芯片进行debug,这颗RISC-V芯片在一块开发板上,开发板和PC之间使用某型号的下载器,这个下载器一端连接开发板上连出的JTAG端口,一端连接PC上的USB接口。我们来看看这其中的每个部件都负责什么工作。GDB、OpenOCD、下载器(adapter)和target端的连接关系可以参考下图。

29c28227013a30804037051fe665947f.png
  • RISC-V SoC芯片:里面有RISC-V core,和SoC中的debug module相连,这个debug module对内控制RISC-V core、读取寄存器、访问memory,对外和JTAG控制器连接,JTAG控制器的接口连到芯片的PAD。
  • Adapter:负责协议转换:把USB的JTAG控制信息按JTAG协议转换输出,满足协议定义的电气特性
  • Linux系统:负责提供USB驱动,由OpenOCD所调用。
  • OpenOCD:负责把GDB的高级别命令转换成JTAG命令,并通过特定下载器的要求进行打包,准备调用OS提供的USB驱动由USB发送出去。GDB和OpenOCD之间使用TCP协议进行连接。
  • GDB:这个GDB并非是Linux系统下调试host系统(可能是x86,可能是ARM或者其他)的GDB,而是交叉编译工具提供的调试非host系统的RISC-V设备的GDB。顺便提一句,telnet也可以用来连接OpenOCD,不过既然是简单介绍,就提GDB一个好了。

假设GDB准备把一段risc-v的代码Load到开发板上的risc-v soc上去执行。GDB通过TCP连到OpenOCD的GDB server。OpenOCD这边收到命令后进行解析,根据target类型、adapter型号、使用的协议等等翻译成符合target debug协议的命令,调用底层的驱动(USB之类)发送出去。USB adapter可能还要再转成JTAG格式传给板子上的设备。

传到板子上的risc-v设备后,芯片里的几个伙计要忙活了。对于符合SIFIVE的debug spec的设备来讲,分成DTM、DMI和DM几个部分。这里先祭上事实上的RISC-V标准中的external debug连接关系图,这张图信息量很大。里面的每个部分负责什么工作?如何实现呢?这里的细节不细说了,又可以单独成文了,感兴趣的同学可以自行研读spec。

ecec2b64c1791c488d801fb543cb13f2.png

最终,DM(Debug module)依靠前面各位朋友的帮助把抽象的GDB命令翻译成一个个的访存、执行或者读CPU寄存器的操作,拿到数据后原路返回。

2. 把GDB接入仿真

还是再来看一下这张block diagram。

29c28227013a30804037051fe665947f.png

test 在仿真时risc-v platform里面的实际都是仿真器在解释RTL代码,PC部分也是和仿真器在一个系统里运行(比如服务器或者虚拟机里的Linux),我们留意到这张图里adapter本来是个硬件部分,我们只要把它做成个model便可以在仿真里把PC和RISC-V platform连接起来了。当然了这时候我们不用USB接口了,直接使用操作系统的TCP服务,至于OpenOCD的运行在TCP上的debug协议我们使用简单的 remote bitbang协议。顺便提一下,如果DTM部分RTL没有也是可以的,直接做在这个model里就可以,直接drive DMI,PULP平台就是这么做的。值得一提的是,用GDB调试Spike上跑的程序也是这个套路,即使Spike是个纯C的model,连RTL也不是。

至于仿真器是什么并不重要,只要支持DPI即可,毕竟这个model除了接口之外我们是准备用C写的。当然了,Ibex的PULP平台里有现成的实现,除了接口部分需要适配一下,其他的直接可以拿过来用了。感谢LowRISC的开发者。OpenTitan项目顺理成章也是使用PULP平台上的这套实现机制。它的RISC-V platform这部分的microarchitecture如图。

a69daa414a0901d4830784b266d95281.png

就拿OpenTitan作为例子,用verilator仿真的时候可以看到下面的打印信息:

$build/lowrisc_systems_top_earlgrey_verilator_0.1/sim-verilator/Vtop_earlgrey_verilator   --meminit=rom,build-bin/sw/device/boot_rom/boot_rom_sim_verilator.elf   --meminit=flash,build-bin/sw/device/examples/hello_world/hello_world_sim_verilator.elf

Simulation of OpenTitan Earl Grey =================================
JTAG: Virtual JTAG interface dmi0 is listening on port 44853. Use OpenOCD and the following configuration to connect:   interface remote_bitbang   remote_bitbang_host localhost   remote_bitbang_port 44853

这是adapter+DTM model里的DPI调用TCP server在44853端口上开了个服务,等待host的到来。

这时候我们另起个中断运行OpenOCD,当然要选verilator适配的config。

$ /tools/openocd/bin/openocd -s util/openocd -f board/lowrisc-earlgrey-verilator.cfg 

Info : Listening on port 6666 for tcl connections 
Info : Listening on port 4444 for telnet connections 
Info : Initializing remote_bitbang driver 
Info : Connecting to localhost:44853 
Info : remote_bitbang driver initialized 
Info : This adapter doesn't support configurable speed 
Info : JTAG tap: riscv.tap tap/device found: 0x04f5484d (mfg: 0x426 (Google Inc), part: 0x4f54, ver: 0x0) 
Info : datacount=2 progbufsize=8 
Info : Examined RISC-V core; found 1 harts 
Info :  hart 0: XLEN=32, misa=0x40101104 
Info : Listening on port 3333 for gdb connections

我们可以看到OpenOCD顺利连上了44853端口,和仿真中的DMI接洽上了。它自己监听3333端口看看有否GDB连接的请求。要是我们在另一个终端打开GDB:

$ riscv32-unknown-elf-gdb -ex "target extended-remote :3333" -ex "info reg"   build-bin/sw/device/examples/hello_world/hello_world_sim_verilator.elf

这时候OpenOCD这边会显示:

Info : accepting 'gdb' connection on tcp/3333

GDB那边的输出:

Reading symbols from build-bin/sw/device/examples/hello_world/hello_world_sim_verilator.elf...done. Remote debugging using :3333

我们来看一眼OpenOCD把DM启动起来的瞬间是啥样:

c5feedd0f0a27097bf1f8df75fdd0fc8.png

我们看到DMI上先向0x40(dmi_req_addr=0x10)写了0x0来reset,随即写了0x1把DM启动起来,接着还是往0x40写了0x7FFFFC1然后随即读这个地址看看它到底实现了哪些寄存器位。我们虽然从waveform看不出OpenOCD到底发了什么过来,但是我们可以知道它想让DM做什么事情。

OK,下面就是使用GDB debug的时间了,略去不表。比如我们想看看0x2000034a地址的值:

(gdb) x 0x2000034a 0x2000034a <ibex_mcycle_read+12>:    0xb80027f3

当然,跳过OpenOCD理论上也是可行的,我们可以直接解析GDB的命令,并转换成DMI上的请求。理论上这种方式还可以提高仿真速度,但使用OpenOCD更贴近实际硬件运行状况,并且OpenOCD已经帮我们做了很多命令解析的工作,大大减少了开发的负担。

3. 关键代码解析

毫无疑问,GDB或者说OpenOCD接入仿真最关键的部分是adapter和DTM的model,OpenOCD里面使用PULP的solution,使用DPI C程序来模拟jtag到DMI的数据传输。看一下模块间的连接:

e16cf6051eb8b27dc39f236985d37c22.png

dmidpi表面上只有和DM之间的DMI接口,实则还有和OpenOCD之间的TCP数据交换。这是因为dmidpi里面开了一个TCP server。在dmidpi.sv里面initial时便调用DPI建立TCP server,仿真结束时关掉:

  initial begin
    ctx = dmidpi_create(Name, ListenPort);
  end

  final begin
    dmidpi_close(ctx);
    ctx = 0;
  end

那DPI里面是怎么实现的呢?我们看dmidpi.c

struct dmidpi_ctx {
  struct tcp_server_ctx *sock;
  struct jtag_ctx jtag;
  struct dmi_sig_values sig;
};

void *dmidpi_create(const char *display_name, int listen_port) {
  // Create context
  struct dmidpi_ctx *ctx =
      (struct dmidpi_ctx *)calloc(1, sizeof(struct dmidpi_ctx));
  assert(ctx);

  // Set up socket details
  ctx->sock = tcp_server_create(display_name, listen_port);

  printf(
      "n"
      "JTAG: Virtual JTAG interface %s is listening on port %d. Usen"
      "OpenOCD and the following configuration to connect:n"
      "  interface remote_bitbangn"
      "  remote_bitbang_host localhostn"
      "  remote_bitbang_port %dn",
      display_name, listen_port, listen_port);

  return (void *)ctx;
}

TCP送来了remote bitbang协议的JTAG command,一系列的函数对其进行解析,并转换成DMI上的数据读写请求。在dmidpi.sv里面每个cycle对DMI interface上的信号值进行更新,包括从DM收过来以及送到DM去。

  always_ff @(posedge clk_i, negedge rst_ni) begin
    dmidpi_tick(ctx, dmi_req_valid, dmi_req_ready, dmi_req_addr, dmi_req_op,
                dmi_req_data, dmi_rsp_valid, dmi_rsp_ready, dmi_rsp_data,
                dmi_rsp_resp, dmi_rst_n);
  end

使用了dmidpi模块,就意味着我们放弃了真实的DTM,在verilator仿真顶层top_earlgrey_verilator里面dmidpi直接bind到rv_dm模块里面。

  bind rv_dm dmidpi u_dmidpi (
    .clk_i,
    .rst_ni,
    //......

4. 小结

Debug系统是SoC中非常重要的一个部分,也是开发中非常费时的一个部分。Debug系统能在仿真阶段来debug,本身也是很有意义也很挑战的工作。这篇小文里也只是粗略解析了系统大体的运作过程以及仿真阶段的处理。其实还有很多debug相关的知识点没有包括,比如:

  • hardware breakpoint和software breakpoint各是如何工作的?
  • 如何对Flash及ROM里的代码设置breakpoint?
  • JTAG标准是怎么样的?如何应用在debug系统里的?
  • EBREAK的exception handler都做了什么事情?
RISC-V是一种基于开放架构的指令集架构(ISA),其具有丰富的调试功能,被称为RISC-V DebugRISC-V Debug为软件和硬件提供了一种统一的调试接口,以实现系统级的调试操作。 在RISC-V Debug中,存在一个特殊的调试模块,称为Debug Module(DM)。DM是连接CPU核心和调试工具的桥梁,它通过一系列调试命令和寄存器来实现针对运行中的处理器进行监控和调试的功能。 在RISC-V Debug中,有两种调试接口:Debug Transport Module(DTM)和Debug Abstract Rachine Interface(DARI)。DTM通常用于通过调试接口连接硬件开发板和调试工具,而DARI用于连接软件仿真器和调试工具。 RISC-V Debug支持多种调试操作,包括读写寄存器、设置断点、单步执行、观察内存等。通过这些操作,调试工具可以实现对RISC-V处理器的控制和观察。 RISC-V Debug还支持硬件断点和软件断点功能。硬件断点可以在特定的内存地址上设置断点,一旦处理器运行到该地址,就会暂停执行并跳转到调试工具。软件断点则是在程序执行过程中插入的特殊指令,一旦程序执行到该指令,也会触发断点。 除了断点功能,RISC-V Debug还提供了观察寄存器和内存的能力。调试工具可以读取和写入处理器的寄存器和内存内容,以便分析程序的状态和数据。 总体而言,RISC-V Debug是一种功能强大的调试工具,它为软件和硬件开发人员提供了多种调试操作,以帮助他们更好地分析和调试RISC-V处理器的程序
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值