有人这样评价RISCV Debug Module——默默无闻,但异常重要,且难以理解。学习完Debug Module,有一种轻舟已过万重山的感觉。
RISCV Debug Module通过JTAG接口与外界调试器产生交互,外界调试器首先通过JTAG访问Debug Module寄存器,进而访问RSICV内部通用寄存器、CSR、内存数据等。
关于JTAG,网上有很多资料,简言之,JTAG协议有4个必选信号TMS,TDI,TDO,TCLK,每个信号都是1bit,因此是串口协议。向这4个信号打数据,经过TAP状态机,访问指令寄存器IR和数据寄存器DR(IR和DR的解码逻辑取决于具体芯片,代表着这块芯片支持的调试功能)。经过以上步骤,即可将0101这样的单比特数据转换成丰富多样的调试操作。
此时你会想,给JTAG打的数据是0101这样的格式,岂不是很难理解,毕竟它不像AMBA这样的总线协议,有addr、data这样的信号。其实在实际使用时,调试软件都会将底层协议转换成方便人类阅读的函数,比如通过JTAG访问某个Debug Module寄存器,只需要直接调用WriteDMI(addr, data)这样的函数。但是理解底层数据的流转,能帮助我们理解JTAG协议。
表6.1摘自riscv debug spec. RISCV Debug Module中的JTAG IR必须至少为5bits(标准JTAG协议规定IR至少为2bits),复位后IR指向IDCODE设备识别寄存器。笔者认为,最常用的IR是位于0x11的Debug Module Interface Access(dmi)寄存器,dmi即Debug总线,几乎所有的Debug操作都需要访问dmi寄存器,比如访问RISCV通用寄存器、CSR、内存等操作。
可以理解为dmi寄存器是Debug Module的大门,通过这个入口,可以访问到Debug Module的内部寄存器。dmi寄存器与Debug Module内的众多寄存器,对应关系详见表3.8。这里需要注意,不管你要读还是写Debug Module内部寄存器,都需要写dmi寄存器。dmi[1:0]代表操作类型,1为读,2为写,dmi[33:2]代表读写数据,dmi[abits+33:34]代表地址,一般abits是7。
下面通过一个例子来说明通过JTAG访问Debug Module寄存器,信号是如何流转的。
首先贴一个TAP状态机在这里,方便对照着理解。TMS,TDI,TDO,TCLK四个信号中,TMS控制状态机的流转,TDI是数据输入,只需要在Shift状态下给值,TDO是数据输出,只需要在Capture状态下取值。
TMS=0, TAP => Run-Test-Idle
TMS=1, TAP => Select-IR-Scan
TMS=0, TAP => Capture-IR
TMS=0, TAP => Shift-IR, TDI= IR(要选择的IR,比如要选择DMI,这里是5'h11,每个TCK给1个bit数据,这个过程保持TMS为0)
TMS=1, TAP => Exit-1-IR
TMS=1, TAP => Update-IR
TMS=0, TAP => Run-Test-Idle
以上步骤为Scan IR,即可选中Dmi寄存器,接下来为Scan DR。
TMS=0, TAP => Run-Test-Idle
TMS=1, TAP => Select-DR-Scan
TMS=0, TAP => Capture-DR
TMS=0, TAP => Shift-DR, TDI=DR_data(即给对应的IR DMi要灌的数据,例如[41:0]={7'h10, 32'h1, 2'h10},只不过每个TCK cycle只能灌1bitDR_data, 这个过程中保持TMS为0)
TMS=1, TAP => Exit-1-DR
TMS=1, TAP => Update-DR
TMS=0, TAP => Run-Test-Idle
以上,先选IR,再给所选IR灌数据,即可将[41:0]={7'h10, 32'h1, 2'h10}这个数据给到DMI寄存器,这个数据代表的含义是把32‘h1写给位于Debug总线地址为7'h10的dmcontrol寄存器。