前面我们介绍了JTAG的基本知识,知道了JTAG的是如何通过操作IR,DR和TAP进行数据交互的。这一篇就来继续分享RISCV的DEBUG是如何使用JTAG进行数据交互的。
我们参考官方的规格书《RISC-V Debug Specification Version 1.0-STABLE》,可以直接从github上下载https://github.com/riscv/riscv-debug-spec.
RISCV的整个DEBUG框架如下:
我们先来看几个主要的组成部分。
其中(1)(2)是运行在主机上的程序
(1)是调试程序,最常见的就是我们经常使用的GDB,当然很多是集成在IDE中的比如IAR的C-SPY 调试器。
(2)是调试服务程序。gdb等调试程序定义了一些基本的调试功能,需要将其翻译成对具体仿真器硬件的操作。(2)就是做这个作用的。最常见的就是开源的OpenOCD,当然一些芯片设计厂商可能会提供自己的服务程序做一些功能扩展等,比如玄铁的DebugServer,都是一样的,一般都是基于开源的实现做的修改。
该程序会提供一组tcpi/ip服务,Debugger就可以通过网络连接到该服务,两者之间通过socket通讯,具体通讯内容是gdb等规范的,比如设置断点,读内存等。
(3)是仿真器硬件部分,(2)和(3)一般通过USB通讯,很早期的仿真器是那种很大块头的并口的接口。(2)需要将(1)的需求转为对(3)的硬件操作,(2)会支持很多种硬件仿真器,所以一般(2)会定义基本的需求,由(3)开发商提供DLL或者驱动,供(2)使用,这样(2)就可以做到支持多种硬件。而(3)最终需要通过调试接口这里是JTAG来和TARGET的DEBUG模块进行交互。
(4)是TARGET中的DTM:Debug Transport Module调试传输模块。即用于和(3)通讯,这是基于JTAG的TAP,Test Access Port测试访问端口。即提供我们前面一篇文章说的IR,DR操作的能力。即(3)就是操作DTM的IR和DR来和DTM交互。
(5)DMI即Debug Module Interface调试模块接口。(5)是(4)和(6)的接口桥梁,
因为(4)提供的操作能力有限,只能先选择IR然对应的操作DR,即前面介绍的IR选择0x10和0x11,在选择IR为0x11之后DR即对应dmi的操作。然后进一步的定义dmi操作的数据不同对应不同功能,这样就可以进行更多复杂功能的扩展。后面会继续讲到DMI的格式,以及DM定义一组寄存器空间用于通过DMI访问。
(6)DM:Debug Module。这里才是TARGET的真正的调试模块,DM模块不能被DTM直接访问,只能通过DMI,访问DM的寄存器空间间接访问。
(7)是RISC-V的core,DM对RISC-V core有复位/halt,读写寄存器,内存等操作。
而读写寄存器,读写内存有
Abstract Memory Access,Program Buffer,System Bus Access几种方式,后文再讲。
这一部分需要参考规格书的《Chapter 6 Debug Transport Module (DTM), non-ISA》章节。
从下表可看出支持JTAG的IR为0x01,0x10,0x11,其他都是BYPASS。
其中0x01是操作IDCODE获取厂商版本信息。
0x10是直接控制dtm, 即dtm的控制和状态寄存器。
0x11是操作dmi,通过dmi来间接操作dm。
从上面框图可以看到,和DM交互,最重要的是DTM通过DMI访问DM的过程,其中我们现在JTAG具备的直接能力是只能够操作DTM的IR和DR。而通过DMI操作DM,需要指定IR=0x11,DR解析为dmi,不同的内容代表不同的操作dm的寄存器的含义。
IR=0x00,0x12~0x17,0x1F时,DR为1位寄存器值为0
用于表示DEBUGGER不与本TAP交互
IR=0x01,此时DR的值含义对应为IDCODE,RISCV对IDCODE的定义如下,只能读
IR=0x10,此时DR的值含义对应为dtmcs, 定义如下
位域 | 描述 | 访问 | 复位值 |
dmihardreset | 写1对DTM进行硬复位,DTM丢弃所有未完成DMI事务,并将所有寄存器和内部状态恢复到其复位值。 | W1 | - |
dmireset | 写1清除错误状态,不影响DMI事务 | W1 | - |
idle | 告诉调试器在每次DMI扫描后应在RunTest/IIdle状态等待的最小周期数,以避免dmistat返回3busy状态。 以下条件时调试器仍须检查dmistat: 0:无需进入Run-Test/Idle状态时。1:进入Run-Test/Idle且立即离开时。2:进入Run-Test/Idle只停留1个cycle时。 | R | Preset |
dmistat | 只读,对应IR=0x11时的dmi.op | R | 0 |
abits | dmi.address.的位数 | R | Preset |
version | DTM版本信息 0 (0.11):0.11. 1 (1.0):0.13 1.0. 15 (custom): 暂时未定义. | R | 1 |
IR=0x11,此时DR的值含义对应为dmi, 定义如下
在Update DR时,DTM将启动op中指定的操作,除非op中报告的当前状态是粘滞的。在Capture DR中,DTM使用该操作的结果更新数据,如果当前操作不具有粘性,则更新操作。
位域 | 描述 | 访问 | 复位值 |
address | DMI访问的地址空间地址. Update-DR 时通过DMI访问DM。 | R/W | 0 |
data | Update-DR时通过DMI写入DM的值, 通时返回上衣此操作DM的返回值. | R/W | 0 |
op | 操作码,区分不同的操作 0(nop),忽略address和data 1 (read):从address处读数据,数据返回到data 2 (write):往address处写数据data 3 (reserved):保留,debugger读该位表示上一次操作的状态,0 表示(success),1 表示(reserved),2 表示(failed)。该状态是迟滞的即没有新的状态覆盖则一直在,可以通过写 dtmcs的dmireset清除。 3表示(busy),如果出现该状态则需要UpdateDR和Capture-DR之间增加Run-Test/Idle状态的时间,以等待。清除该状态和failed一样。 | R/W | 0 |
上一节可以看到,对DM的寄存器的操作转换为了,IR=0x11,DR映射为dmi的基本操作。
DR只要按照dmi如下定义指定address,data和op即可操作对应的DM寄存器。
支持的DM寄存器有哪些,参考规格书的3.15 Debug Module Registers章节。
现在就以读写Abstract Command为例,用逻辑分析仪抓取信号,来看下整个过程
首先是IR操作
然后中间是RUN-TEST/IDLE等待过程
然后是DR过程,对应的DR(TDI)值为0x5C00000001,按照dmi解析
Address=0x17即Abstract Command
Data=0x00
Op=0x01即read
此时的TDO输出的是上一次操作的值,所以还需要继续来一次IR和DR。第二次的tdi即DR的值为0x00,按照dmi解析是nop,这次操作的目的是读出上一次的操作的返回值,此时的tdo,dr就是返回值。
整个过程由IR-DR-IR-DR组成
这里假设写值为0x55.
第一次IR-DR的dr(tdi)对应dmi为0x5c00000156即
Address=0x17
Data= 0x55
Op=10 即write
第二次IR-DR的dr(tdi)对应dmi为x0即
Nop操作。
然后后面工具自动跟了一次回读操作IR-DR-IR-DR(见前面读Abstract Command)
所以以上写实际过程是
IR-DR-IR-DR写
IR-DR-IR-DR回读(非必须,调试工具自动加的)。
更复杂的操作无非也就是上述DM寄存器的读写操作而已,可以参考规格书的
B.2 External Debugger Implementation和3.15 Debug Module Registers
后文再分享一些关键操作。
以上分享了RISCV的DEBUG基于JTAG的数据流,以实例抓波形的方式介绍了DM寄存器的读写过程,至此可以根据波形分析DEBUGGER到底读写了哪些DM寄存器,具备调试分析DEBUG模块的能力了。至于更细节一些的具体操作,我们后文再分享,也可以直接参考规格书。