嵌入式


Jtag原理

<p><a href="https://liangkangnan.gitee.io/2020/03/21/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BARISC-V%E8%B0%83%E8%AF%95/">https://liangkangnan.gitee.io/2020/03/21/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BARISC-V%E8%B0%83%E8%AF%95/</a></p> <h4>JTAG主机和从机</h4> <p>在调试时需要用到一个工具,比如<code>JLink</code>或者<code>CMSIS-DAP</code>,对于这个工具,在这里称为<code>JTAG</code>主机(<code>JTAG host</code>),而嵌入在芯片内部的JTAG称为JTAG从机(<code>JTAG slave</code>),需要注意的是上面这些信号的输入输出方向是对于JTAG从机来说的。下文中如无特别说明,JTAG都是指<code>JTAG</code>从机。 一个JTAG主机可以同时对多个JTAG从机进行调试,这通过<code>JTAG</code>扫描链(<code>JTAG Scan Chain</code>)完成,如图1所示。 <img src="https://www.showdoc.cc/server/api/common/visitfile/sign/2c524e0d420269206f8629e26fb950e0?showdoc=.jpg" alt="" /></p> <p><code>JTAG</code>内部有一个<code>IR(instruction register)</code>寄存器和多个<code>DR(data register)</code>寄存器,<code>IR</code>寄存器决定要访问的是哪一个<code>DR</code>寄存器。<code>DR</code>寄存器有<code>IDCODE</code>、<code>BYPASS</code>等。在<code>Test-Logic-Reset</code>状态下<code>IR</code>寄存器默认选择的是<code>IDCODE</code>这个<code>DR</code>寄存器。</p> <p>JTAG主机通过<code>IR SCAN</code>设置<code>IR</code>寄存器的值,然后通过<code>DR SCAN</code>来读、写相应的<code>DR</code>寄存器。</p> <h4>RISC-V调试Spec</h4> <p>调试模块在<code>CPU</code>芯片设计里是最为不起眼的,但又是最为复杂的模块之一,大部分开源的处理器<code>IP</code>都没有调试模块。</p> <p>下面的内容基于<code>RISC-V debug spec 0.13</code>版本。</p> <p>目前<code>RISC-V</code>的官方调试上位机是<code>openocd</code>,调试工具可以是<code>JLink</code>或者<code>CMSIS-DAP</code>,<code>RISC-V</code>调试系统框架如图3所示。 <img src="https://www.showdoc.cc/server/api/common/visitfile/sign/7be152b9b28fb3e51a00853776c7cd19?showdoc=.jpg" alt="" /></p> <p>可以看到主要分为3个部分,分别是Debug Host,可以理解为PC;Debug Hardware,可以理解为JLink或者CMSIS-DAP这样的调试工具;第三部分就是嵌入在芯片内部的调试模块。在调试模块内部,与调试工具直接交互的是DTM模块,DTM模块通过DMI接口与DM模块交互。</p> <h4>DTM模块</h4> <p>在DTM模块里实现了一个TAP控制器(状态机),其中IR寄存器的长度最少为5位,当TAP控制器复位时,IR的值默认为5’b00001,即选择的是IDCODE寄存器。DTM模块的寄存器(DR寄存器)定义如图4所示。 <img src="https://www.showdoc.cc/server/api/common/visitfile/sign/ece22508f32ad68f25c31299986ca9a8?showdoc=.jpg" alt="" /></p> <p>其中红色框起来的寄存器是必须要实现的。下面简单介绍一下这几个寄存器。</p> <h5>IDCODE寄存器(0x01)</h5> <p>当TAP状态机复位时,IR寄存器的值默认为0x01,即选择的是IDCODE寄存器。IDCODE寄存器的每一位含义如图5所示。IDCODE是只读寄存器。 <img src="https://www.showdoc.cc/server/api/common/visitfile/sign/2ed2e673e6d7b056af2a6e7d3d8c200d?showdoc=.jpg" alt="" /></p> <ul> <li> <p>Version:只读,版本号,可为任意值。</p> </li> <li> <p>PartNumber:只读,可为任意值。</p> </li> <li>Manufld:只读,厂商号,遵循JEP106标准分配,实际中可为任意值,只要不与已分配的厂商号冲突即可。</li> </ul> <h5>DTM控制和状态寄存器(dtmcs,0x10)</h5> <p>dtmcs寄存器的每一位含义如图6所示。 <img src="https://www.showdoc.cc/server/api/common/visitfile/sign/29e1d6cf8e68957d2943ec182906305c?showdoc=.jpg" alt="" /></p> <ul> <li> <p>dmihardreset:DTM模块硬复位,写1有效。</p> </li> <li> <p>dmireset:清除出错,写1有效。</p> </li> <li> <p>idle:只读,JTAG 主机在Run-Test-Idle状态停留的时钟周期数,0表示不需要进入Run-Test-Idle状态,1表示进入Run-Test-Idle状态后可以马上进入下一个状态,以此类推。</p> </li> <li> <p>dmistat:只读,上一次操作的状态。0表示无出错,1或者2表示操作出错,3表示操作还未完成。</p> </li> <li> <p>abits:只读,dmi寄存器中address域的大小(位数)。</p> </li> <li>version:只读,实现所对应的spec版本,0表示0.11版本,1表示0.13版本。</li> </ul> <h5>DM模块接口访问寄存器(dmi,0x11)</h5> <p>dmi寄存器的每一位含义如图7所示。 <img src="https://www.showdoc.cc/server/api/common/visitfile/sign/29ef9c67a74f7ec2bb384bcf72649c12?showdoc=.jpg" alt="" /></p> <ul> <li> <p>address:可读可写,DM寄存器的长度(位数)。</p> </li> <li> <p>data:可读可写,往DM寄存器读、写的数据,固定为32位。</p> </li> <li>op:可读可写,读或者写这个域时有不同的含义。当写这个域时,写0表示忽略address和data的值,相当于nop操作;写1表示从address指定的寄存器读数据;写2表示把data的数据写到address指定的寄存器。写3为保留值。当读这个域时,0表示上一个操作正确完成;1为保留值;2表示上一个操作失败,这个状态是会被记住的,因此需要往dtmcs寄存器的dmireset域写1才能清除这个状态。3表示上一个操作还未完成。</li> </ul> <p>在Update-DR状态时,DTM开始执行op指定的操作。在Capture-DR状态时,DTM更新data域。</p> <h5>BYPASS寄存器(0x1f)</h5> <p>只读,长度为1,值固定为0。</p> <h3>DM模块</h3> <p>从图3可知,DM模块访问RISC-V Core有两种方式,一种是通过abstract command,另一种是通过system bus。abstract command方式是必须要实现的,system bus的方式是可选的。</p> <p>DM模块的寄存器都为32位,定义如图8所示。 <img src="https://www.showdoc.cc/server/api/common/visitfile/sign/a7839f6ba5a360a23101d0c6e0f906d4?showdoc=.jpg" alt="" /></p> <h4>data寄存器(data0~data11,0x04~0x0f)</h4> <p>这12个寄存器是用于abstract command的数据寄存器,长度为32位,可读可写。</p> <h4>DM控制寄存器(dmcontrol,0x10)</h4> <p>dmcontrol寄存器的每一位含义如图9所示。 <img src="https://www.showdoc.cc/server/api/common/visitfile/sign/06ed19186f29ab4765dec60190280017?showdoc=.jpg" alt="" /></p> <p>haltreq:只写,写1表示halt(暂停)当前hart(hart表示CPU核,存在多核的情况)。</p> <p>resumereq:只能写1,写1表示resume(恢复)当前hart,即go。</p> <p>hartreset:可读可写,写1表示复位DM模块,写0表示撤销复位,这是一个可选的位。</p> <p>ackhavereset:只能写1,写1表示清除当前hart的havereset状态。</p> <p>hasel:可读可写,0表示当前只有一个已经被选择了的hart,1表示当前可能有多个已经被选择了的hart。</p> <p>hartsello:可读可写,当前选择的hart的低10位。1位表示一个hart。</p> <p>hartselhi:可读可写,当前选择的hart的高10位。1位表示一个hart。如果只有一个hart,那么hasel的值为0,hartsello的值为1,hartselhi的值为0。</p> <p>setresethaltreq:只能写1,写1表示当前选择的hart复位后处于harted状态。</p> <p>clrresethaltreq:只能写1,写1表示清除setresethaltreq的值。</p> <p>ndmreset:可读可写,写1表示复位整个系统,写0表示撤销复位。</p> <p>dmactive:可读可写,写0表示复位DM模块,写1表示让DM模块正常工作。正常调试时,此位必须为1。</p> <h4>DM状态寄存器(dmstatus,0x11)</h4> <p>dmstatus寄存器是一个只读寄存器,每一位含义如图10所示。 <img src="https://www.showdoc.cc/server/api/common/visitfile/sign/f75d6ea881e7758f8c0c3c8802a30241?showdoc=.jpg" alt="" /> impebreak:1表示执行完progbuf的指令后自动插入一条ebreak指令,这样就可以节省一个progbuf。当progbufsize的值为1时,此值必须为1。</p> <p>allhavereset:1表示当前选择的hart已经复位。</p> <p>anyhavereset:1表示当前选择的hart至少有一个已经复位。</p> <p>allresumeack:1表示当前选择的所有hart已经应答上一次的resume请求。</p> <p>anyresumeack:1表示当前选择的hart至少有一个已经应答上一次的resume请求。</p> <p>allnonexistent:1表示当前选择的hart不存在于当前平台。</p> <p>anynonexistent:1表示至少有一个选择了的hart不存在于当前平台。</p> <p>allunavail:1表示当前选择的hart都不可用。 anyunavail:1表示至少有一个选择了的hart不可用。</p> <p>allrunning:1表示当前选择的hart都处于running状态。</p> <p>anyrunning:1表示至少有一个选择了的hart处于running状态。</p> <p>allhalted:1表示当前选择的hart都处于halted状态。</p> <p>anyhalted:1表示至少有一个选择了的hart处于halted状态。</p> <p>authenticated:0表示使用DM模块之前需要进行认证,1表示已经通过认证。</p> <p>authbusy:0表示可以进行正常的认证,1表示认证处于忙状态。</p> <p>hasresethaltreq:1表示DM模块支持复位后处于halted状态,0表示不支持。</p> <p>confstrptrvalid:1表示confstrptr0~3寄存器保存了配置字符串的地址。</p> <p>version:0表示DM模块不存在,1表示DM模块的版本为0.11,2表示DM模块的版本为0.13。</p> <h4>abstract控制和状态寄存器(abstractcs,0x16)</h4> <p>abstractcs寄存器定义如图11所示。 <img src="https://www.showdoc.cc/server/api/common/visitfile/sign/e3ab219a5ed37cc2c08869e9330673c6?showdoc=.jpg" alt="" /> progbufsize:只读,program buffer的个数,取值范围为0~16,每一个的大小为32位。</p> <p>busy:只读,1表示abstract命令正在执行,当写command寄存器后该位应该马上被置位直到命令执行完成。</p> <p>cmderr:可读、只能写1,cmderr的值仅当busy位为0时有效。0表示无错误,1表示正在操作command、abstractcs、data或者progbuf寄存器,2表示不支持当前命令,3表示执行命令时出现异常,4表示由于当前hart不可用,或者不是处于halted/running状态而不能被执行,5表示由于总线出错(对齐、访问大小、超时)导致的错误,7表示其他错误。写1清零cmderr。</p> <p>datacount:只读,所实现的data寄存器的个数。</p> <h4>abstract命令寄存器(command,0x17)</h4> <p>当写这个寄存器时,相应的操作就会被执行。command寄存器只能写,定义如图12所示。 <img src="https://www.showdoc.cc/server/api/common/visitfile/sign/cf70b96e1efb1aff08ba8080a9e6cd71?showdoc=.jpg" alt="" /> cmdtype:只写,命令类型,0为表示访问寄存器,1表示快速访问,2表示访问内存。</p> <p>control:只写,不同的命令类型有不同的含义,说明如下。</p> <p>当cmdtype为0时,control定义如图13所示。 <img src="https://www.showdoc.cc/server/api/common/visitfile/sign/7013905e64446b5ef48d6c3e8c46dd18?showdoc=.jpg" alt="" /></p> <p>cmdtype:值为0。</p> <p>aarsize:2表示访问寄存器的最低32位,3表示访问寄存器的最低64位,4表示访问寄存器的最低128位。如果大于实际寄存器的大小则此次访问是失败的。</p> <p>aarpostincrement:1表示成功访问寄存器后自动增加regno的值。</p> <p>postexec:1表示执行progbuf里的内容(指令)。</p> <p>transfer:0表示不执行write指定的操作,1表示执行write指定的操作。</p> <p>write:0表示从指定的寄存器拷贝数据到arg0指定的data寄存器。1表示从arg0指定的data寄存器拷贝数据到指定的寄存器。</p> <p>regno:要访问的寄存器。</p> <p>综上,可知:</p> <p>当write=0,transfer=1时,从regno指定的寄存器拷贝数据到arg0对应的data寄存器。</p> <p>当write=1,transfer=1时,从arg0对应的data寄存器拷贝数据到regno指定的寄存器。</p> <p>当aarpostincrement=1时,将regno的值加1。</p> <p>当postexec=1时,执行progbuf寄存器里的指令。</p> <p>arg对应的data寄存器如图14所示。 <img src="https://www.showdoc.cc/server/api/common/visitfile/sign/67f924860547f9d0c5063d0f53226caa?showdoc=.jpg" alt="" /></p> <p>即当访问的寄存器位数为32位时,arg0对应data0寄存器,arg1对应data1寄存器,arg2对应data2寄存器。</p> <p>当cmdtype为1时,control定义如图15所示。 <img src="https://www.showdoc.cc/server/api/common/visitfile/sign/20b0c671f0dd8b2955086c288b4090cd?showdoc=.jpg" alt="" /></p> <p>cmdtyte:值为1。 此命令会执行以下操作:</p> <p>halt住当前hart。</p> <p>执行progbuf寄存器里的指令。</p> <p>resume当前hart。</p> <p>当cmdtype为2时,control定义如图16所示。</p> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/cb365e48bc198ddae2858ccf27e41cb6?showdoc=.jpg" alt="" /></p> <p>cmdtype:值为2。</p> <p>aamvirtual:0表示访问的是物理地址,1表示访问的是虚拟地址。</p> <p>aamsize:0表示访问内存的低8位,1表示访问内存的低16位,2表示访问内存的低32位,3表示访问内存的低64位,4表示访问内存的低128位。</p> <p>aampostincrement:1表示访问成功后,将arg1对应的data寄存器的值加上aamsize对应的字节数。</p> <p>write:0表示从arg1指定的地址拷贝数据到arg0指定的data寄存器,1表示从arg0指定的data寄存器拷贝数据到arg1指定的地址。</p> <p>target-specific:保留。</p> <p>综上,可知:</p> <p>当write=0时,从arg1指定的地址拷贝数据到arg0指定的data寄存器。</p> <p>当write=1时,从arg0指定的data寄存器拷贝数据到arg1指定的地址。</p> <p>当aampostincrement=1时,增加arg1对应的data寄存器的值。</p> <h4>系统总线访问控制和状态寄存器(sbcs,0x38)</h4> <p>sbcs寄存器定义如图17所示。 <img src="https://www.showdoc.cc/server/api/common/visitfile/sign/8d2023abdc0d1cc99029e9bab100b230?showdoc=.jpg" alt="" /></p> <p>sbversion:只读,0表示system bus是2018.1.1之前的版本,1表示当前debug spec的版本,即0.13版本。</p> <p>sbbusyerror:只读,写1清零,当debugger要进行system bus访问操作时,如果上一次的system bus访问还在进行中,此时会置位该位。</p> <p>sbbusy:只读,1表示system bus正在忙。在进行system bus访问前必须确保该位为0。</p> <p>sbreadonaddr:可读可写,1表示每次往sbaddress0寄存器写数据时,将会自动触发system bus从新的地址读取数据。</p> <p>sbaccess:可读可写,访问的数据宽度,0表示8位,1表示16位,2表示32位,3表示64位,4表示128位。</p> <p>sbautoincrement:可读可写,1表示每次system bus访问后自动将sbaddress的值加上sbaccess的大小(字节)。</p> <p>sbreadondata:可读可写,1表示每次从sbdata0寄存器读数据后将自动触发system bus从新的地址读取数据。</p> <p>sberror:可读,写1清零,0表示无错误,1表示超时,2表示访问地址错误,3表示地址对齐错误,4表示访问大小错误,7表示其他错误。</p> <p>sbasize:只读,system bus地址宽度(位数),0表示不支持system bus访问。</p> <p>sbaccess128:只读,1表示system bus支持128位访问。</p> <p>sbaccess64:只读,1表示system bus支持64位访问。</p> <p>sbaccess32:只读,1表示system bus支持32位访问。</p> <p>sbaccess16:只读,1表示system bus支持16位访问。</p> <p>sbaccess8:只读,1表示system bus支持8位访问。</p> <h4>系统总线地址0寄存器(sbaddress0,0x39)</h4> <p>可读可写,如果sbcs寄存器中的sbasize的值为0,那么此寄存器可以不用实现。</p> <p>当写该寄存器时,会执行以下流程:</p> <p>设置sbcs.sbbusy的值为1。</p> <p>从新的sbaddress地址读取数据。</p> <p>如果读取成功并且sbcs.sbautoincrement的值为1,则增加sbaddress的值。</p> <p>设置sbcs.sbbusy的值为0。</p> <h4>系统总线数据0寄存器(sbdata0,0x3c)</h4> <p>可读可写,如果sbcs寄存器中的所有sbaccessxx的值都为0,那么此寄存器可以不用实现。</p> <p>当写该寄存器时,会执行以下流程:</p> <p>设置sbcs.sbbusy的值为1。</p> <p>将sbdata的值写到sbaddress指定的地址。</p> <p>如果写成功并且sbcs.sbautoincrement的值为1,则增加sbaddress的值。</p> <p>设置sbcs.sbbusy的值为0。</p> <p>当读该寄存器时,会执行以下流程:</p> <p>准备返回读取的数据。</p> <p>设置sbcs.sbbusy的值为1。</p> <p>如果sbcs.sbautoincrement的值为1,则增加sbaddress的值。</p> <p>如果sbcs.sbreadondata的值为1,则开始下一次读操作。</p> <p>设置sbcs.sbbusy的值为0。</p> <h4>RISC-V JTAG的实现</h4> <p>通过在STM32F103C8T6上实现(模拟)RISC-V调试标准,进一步加深对RISC-V JTAG调试的理解。</p> <p>使用STM32的四个GPIO作为JTAG信号的四根线,其中TCK所在的引脚设为外部中断,即上升沿和下降沿触发方式,实现了可以通过openocd以RISC-V的调试标准来访问STM32的寄存器和内存。程序流程如图19所示,完整的工程代码见[2]。verilog的实现见[3]。</p> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/bcfd9eb2650ebac473a185147ddff8d3?showdoc=.jpg" alt="" /></p> <h4>参考资料</h4> <p>RISC-V External Debug Support Version 0.13。</p> <p>在STM32上模拟RISC-V JTAG的实现:stm32_riscv_jtag_slave。<a href="https://gitee.com/liangkangnan/stm32_riscv_jtag_slave">https://gitee.com/liangkangnan/stm32_riscv_jtag_slave</a></p> <p>一个从零开始写的易懂的RISC-V处理器核:tinyriscv。</p> <p><a href="https://github.com/darklife/darkriscv">https://github.com/darklife/darkriscv</a> <a href="https://github.com/ultraembedded/biriscv">https://github.com/ultraembedded/biriscv</a> <a href="https://github.com/ultraembedded/riscv">https://github.com/ultraembedded/riscv</a> <a href="https://github.com/PulseRain/Reindeer">https://github.com/PulseRain/Reindeer</a> <img src="https://www.showdoc.cc/server/api/common/visitfile/sign/cea9d53c16715d51fc88d26d286cf955?showdoc=.jpg" alt="" /> <img src="https://www.showdoc.cc/server/api/common/visitfile/sign/17ae73863bd98eedc1bc97eca4c7defb?showdoc=.jpg" alt="" /> <img src="https://www.showdoc.cc/server/api/common/visitfile/sign/c52b1f6d2bb65fb9140cb43977881338?showdoc=.jpg" alt="" /> <img src="https://www.showdoc.cc/server/api/common/visitfile/sign/05aed68d0e750949b612cb48eabf9018?showdoc=.jpg" alt="" /></p>

页面列表

ITEM_HTML