Debug Module(DM)的核心功能
DM 就像一个翻译官,负责把调试器的抽象指令(比如 "暂停处理器")转换成硬件能听懂的具体操作。它必须实现以下基本功能:
- 必要功能 (必须实现):
- 暂停 / 恢复任意处理器核心(hart)。
- 告诉调试器哪些核心已经暂停。
- 读写暂停状态下的核心寄存器(比如通用寄存器 GPR)。
- 提供复位信号,确保能从启动第一条指令开始调试。
- 可选功能 (根据设计选择):
- 直接读写非通用寄存器(如 CSR)。
- 用 "程序缓冲区" 让核心执行任意指令(比如访问内存)。
- 同时控制多个核心暂停 / 恢复。
- 直接访问系统总线(无需通过核心)。
1. DM 的接口(DMI 总线)
DM 通过DMI 总线与调试主机通信,就像电脑通过 USB 连接外设一样:
- 主设备是调试主机(如运行 GDB 的电脑)。
- 从设备是 DM 和其他调试模块(如多个 DM 或自定义设备)。
- 地址空间:DM 的地址从最低位开始,额外地址可分配给其他设备。例如:

2. Reset control
DM 负责管理系统的全局复位信号(ndmreset / non debug module reset):
-
作用:除 DM 和Debug Transport模块之外的所有组件会被复位或保持在复位状态。具体哪些部件被复位由芯片厂商决定,但必须保证调试器能从程序第一条指令开始调试。
-
规则 :
- DM模块自己的状态和寄存器只有两种情况会被复位:
a. 系统刚上电时
b. 当 dmcontrol 寄存器中的 dmactive 位为 0 时(即调试功能未激活) - 如果调试功能激活(dmactive=1),系统复位时 HART 的暂停状态会被保留(比如断点位置),但触发相关的 CSR 寄存器可能会被清除
- 由于时钟和电源域的设计问题,系统复位时不能随意进行 DMI 访问
- 此时唯一允许的操作是读写 dmcontrol 寄存器,其他 DMI 操作会导致不可预测的结果
- DM模块自己的状态和寄存器只有两种情况会被复位:
-
ndmreset 的操作规则
- 复位信号的持续时间没有强制要求,但必须满足:
a. 先写 1 使能复位
b. 再写 0 触发实际复位 - 系统恢复时间不确定,通过 allunavail/anyunavail 寄存器报告
- 复位信号的持续时间没有强制要求,但必须满足:
-
单个 HART 的复位方法
- 通过选择目标 HART,设置并清除 hartreset 位实现复位
- 实际复位可能包括未被选中的 HART(厂商实现差异)
-
复位状态检测
- 调试器可通过以下方式确认复位情况:
a. anyhavereset:是否有至少一个选中 HART 处于复位状态
b. allhavereset:是否所有选中 HART 都处于复位状态
- 调试器可通过以下方式确认复位情况:
-
复位标志的清除规则
- 复位后 HART 会设置粘性标志(必须手动清除)
- 清除方法:向 dmcontrol 的 ackhavereset 位写 1
- 特殊情况:当 dmactive=0 时,标志可能自动清除
-
复位后的 HART 行为
- 如果设置了 haltreq 或 resethaltreq:
- 复位后立即进入调试模式
- 否则:
- 正常执行程序
- 如果设置了 haltreq 或 resethaltreq:

3. selecting Harts
- 一个调试模块(DM)最多可以连接 2^20 个硬件线程(hart)。调试器需要先选择一个特定的 hart,之后对这个 hart 发出的暂停、恢复、复位和调试等命令就都只针对这个被选中的 hart 起作用。
- 调试器如果要列出所有的 hart,需要先确定
HARTSELLEN
的值。
- 把
hartsel
寄存器里的所有位都设为 1(假定hartsel
有最大的位宽),接着读取hartsel
的值,这样就能知道哪些位实际上被设置了,从而确定HARTSELLEN
(即hartsel
寄存器里有效位的数量)。 - 从索引为 0 的 hart 开始,逐个选择 hart,直到
dmstatus
寄存器里的anynonexistent
位变为 1(这表明后面没有可用的 hart 了),或者达到了HARTSELLEN
所限制的最大索引。
- 调试器可以通过接口读取
mhartid
寄存器,或者读取系统的配置字符串,来了解 hart 索引和mhartid
之间的映射关系。
3.1 选择单个hart
- 所有的调试模块都必须支持选择单个 hart。调试器选择单个 hart 的方法很简单,就是把要选的 hart 的索引值写到
hartsel
寄存器里。hart 的索引是从 0 开始的,而且是连续编号的,一直到最后一个索引。
举个例子,要是你想选择第 3 个 hart(索引为 2,因为索引从 0 开始),就把数值 2 写入 hartsel
寄存器。之后执行的暂停、恢复、复位和调试命令都只会对这个被选中的 hart 起作用。
3.2 选择多个hart
- 调试模块可以实现一个 Hart 数组掩码寄存器,这样就能一次选择多个 hart 了。这个 Hart 数组掩码寄存器的第 n 位对应索引为 n 的 hart,如果这一位是 1,就表示选中了这个 hart。通常,一个 DM 的 Hart 数组掩码寄存器的位宽刚好能覆盖它所支持的所有 hart,但也允许把其中某些位固定设为 0。
- 调试器可以通过
hawindowsel
和hawindow
来设置 Hart 数组掩码寄存器里的位,然后设置hasel
来对所有被选中的 hart 执行操作。如果支持这个功能,就可以同时对多个 hart 进行暂停、恢复和复位操作。而且,设置或清除hasel
不会影响 Hart 数组掩码寄存器的状态。 - 只有通过
dmcontrol
发起的操作才能同时作用于多个 hart,而抽象命令(Abstract Commands)只对通过hartsel
选中的单个 hart 起作用。
4. Hart States
每个可被选中的 Hart(硬件线程)必定处于以下四种状态之一:
- 不存在(Nonexistent)
- 不可用(Unavailable)
- 运行中(Running)
- 暂停(Halted)
状态详解
a. 不存在(Nonexistent)
- 含义:这个 Hart 在系统中根本不存在,无论等多久都不会出现。
- 例子 :
假设系统只有 1 个 Hart(索引 0),那么索引 1 及以上的 Hart 都是 "不存在" 的。 - 调试器行为 :
调试器遇到第一个不存在的 Hart 后,会认为后面所有索引的 Hart 都不存在。
b. 不可用(Unavailable)
- 含义:Hart 可能暂时无法使用,或者被系统永久禁用(比如工厂关闭)。
- 可能原因 :
- 正在复位(Reset)
- 暂时断电
- 未插入系统(如扩展卡未安装)
- 制造时被永久禁用(导致 Hart 索引不连续)
- 调试器行为 :
即使 Hart 永远不会可用,调试器也必须将其视为 "不可用",以便正确枚举所有可能的 Hart。
c. 运行中(Running)
- 含义:Hart 正在正常执行程序,就像没有调试器连接一样。
- 特点 :
- 可能处于低功耗模式或等待中断,但调试器可以通过暂停请求(
haltreq
)让它进入暂停状态。 - 例如:手机 CPU 在省电模式下运行,但仍能被调试器暂停。
- 可能处于低功耗模式或等待中断,但调试器可以通过暂停请求(
d. 暂停(Halted)
- 含义:Hart 被调试器暂停,只能执行调试器的命令(如单步执行、读写寄存器)。
- 特点 :
- 处于调试模式,无法自主运行程序。
- 例如:调试器设置断点后,Hart 会暂停在此处等待调试指令。
复位后的状态变化
- 复位期间:Hart 可能处于 "不可用" 状态。
- 复位结束后 :
- Hart 可能短暂进入 "运行中" 状态(比如执行初始化代码)。
- 最终根据调试器的配置(
haltreq
和resethaltreq
)决定是继续运行还是暂停。
状态判断工具
调试器通过dmstatus
寄存器的以下标志位判断状态:
allnonexistent
:所有 Hart 都不存在(罕见)。anynonexistent
:存在至少一个不存在的 Hart。allunavail
:所有 Hart 都不可用。anyunavail
:存在至少一个不可用的 Hart。allrunning
:所有 Hart 都在运行。anyrunning
:存在至少一个运行中的 Hart。allhalted
:所有 Hart 都被暂停。anyhalted
:存在至少一个被暂停的 Hart。
总结
- 不存在:系统中没有这个 Hart。
- 不可用:Hart 可能存在,但暂时或永久无法使用。
- 运行中:Hart 正常执行程序,可被调试器暂停。
- 暂停:Hart 被调试器控制,只能执行调试命令。
通过这些状态,调试器可以精准控制和监控每个 Hart 的行为。
5. Run Control
基本概念
调试模块(DM)会为每个 hart 记录 4 个概念上的状态位:
- 暂停请求(halt request):就像老师让学生停下来别写作业了。
- 恢复确认(resume ack):好比学生收到老师让继续写作业的通知后,给老师一个确认的回复。
- 复位时暂停请求(halt - on - reset request):可以想象成在重新开始考试前,老师要求学生先别动笔。
- 硬件线程复位(hart reset):类似于把电脑重启一下。
除了恢复确认位可能初始化为 0 或者 1,其他 3 个位初始都为 0。
状态信号
DM 会从每个 hart 接收 "已暂停(halted)""正在运行(running)" 和 "已复位(havereset)" 这些信号。调试器能通过一些寄存器看到恢复确认位的状态,以及 "已暂停""正在运行""已复位" 信号的状态,但其他位的状态不能直接看到。
暂停请求操作
当调试器把 haltreq
设为 1 时,就好像老师喊了一声 "都别写了",每个被选中的 hart 的暂停请求位就会被设置。如果一个 hart 正在运行,或者刚从复位状态恢复,看到自己的暂停请求位被设置了,它就会停下来,就像学生听到老师的话停下手中的笔。同时,它会取消 "正在运行" 信号,发出 "已暂停" 信号。要是 hart 本来就已经暂停了,就会忽略这个暂停请求位,就像已经停下笔的学生不会再因为老师重复 "别写了" 而有额外反应。
恢复请求操作
当调试器把 resumereq
设为 1 时,好比老师说 "可以继续写作业了"。这时,每个被选中的 hart 的恢复确认位会被清除,并且每个被选中且处于暂停状态的 hart 会收到恢复请求。hart 收到请求后,就会继续工作,就像学生继续写作业,同时取消 "已暂停" 信号,发出 "正在运行" 信号。最后,恢复确认位会被设置,表示已经确认恢复。正在运行的 hart 会忽略恢复请求,就像一直在写作业的学生不会因为老师重复 "继续写" 而有额外反应。
响应时间要求
当请求暂停或者恢复时,除非 hart 不可用,否则它必须在 1 秒内做出响应。不过一般来说,可能几个时钟周期就会有反应,就像学生听到老师的话马上就会做出动作。
复位时暂停请求操作
DM 可以为每个 hart 实现可选的 "复位时暂停请求" 位。当 DM 把 hasresethaltreq
设为 1 时,就表示它支持这个功能。调试器把 setresethaltreq
设为 1,就像老师在重新开始考试前说 "大家先别动笔",每个被选中的 hart 的 "复位时暂停请求" 位就会被设置。当这个位被设置后,下次 hart 复位结束,它会马上进入调试模式,不管复位是什么原因引起的。这个位会一直保持设置状态,直到调试器把 clrresethaltreq
设为 1 来清除它,或者 DM 进行复位,就像老师说 "可以开始考试了",学生才会开始动笔。
总结
调试模块通过设置不同的控制位,就像老师给学生发指令一样,来控制 hart 的运行、暂停和复位等状态,并且能根据 hart 反馈的信号了解它的状态。同时,对 hart 的响应时间也有一定要求。
6. Abstract Command
抽象命令概述
调试模块(DM)支持一组抽象命令,不过大部分命令是可选的。这就好比一个工具包,里面有很多工具,但不是每个工具都一定会被用到。
有时候,就算被选中的硬件线程(hart)没有暂停,调试器也能执行某些抽象命令。就好像你在一台电脑运行程序的时候,也能对它进行一些简单的设置操作。
调试器要知道某个 hart 在特定状态下支持哪些抽象命令,只能通过实际去尝试执行这些命令,然后查看 abstractcs
寄存器里的 cmderr
值,看看命令是否执行成功。就像你想知道一个新玩具能做哪些动作,只能亲自去操作一下,看看它能不能按你的要求做出反应。
而且有些命令在设置了某些选项时能支持,设置其他选项时就不支持了。如果设置了不支持的选项,DM 会把 cmderr
设置为 2,表示 "不支持"。比如一个游戏,有些难度级别是支持的,有些难度级别是不支持的,你选了不支持的难度,游戏就会提示你不行。
执行抽象命令的过程
调试器执行抽象命令时,就像给 DM 下指令,要把命令写到 command
寄存器里。然后,通过读取 abstractcs
寄存器里的 busy
位,就能知道命令是否执行完了。命令执行完后,cmderr
会告诉你命令是否成功。命令可能会失败,原因可能是 hart 没暂停、没运行、不可用,或者执行过程中遇到了错误。这就好比你让一个工人完成一项任务,你可以通过看他是不是还在忙(busy
)来知道任务有没有完成,最后通过任务的结果(cmderr
)来判断是否成功。
如果命令需要参数,调试器得在写命令到 command
寄存器之前,先把参数写到数据寄存器里。要是命令有返回结果,DM 得在 busy
位清零之前,把结果放到数据寄存器里。具体用哪些数据寄存器存参数在表格 3.1 里有说明,而且最低有效字要放在编号最小的数据寄存器里。参数的宽度取决于执行的命令,没明确指定时就是 DXLEN
。这就好比你给工人布置任务时,要先把任务所需的材料(参数)准备好放在指定的地方,等任务完成后,工人要把结果也放在指定的地方。
抽象命令接口的设计目的
抽象命令接口的设计是为了让调试器能尽快地发出命令,之后再去检查命令是否无错完成。一般情况下,调试器的速度比目标硬件慢很多,而且命令通常能成功执行,这样就能实现最大的吞吐量。要是有命令失败了,这个接口能保证失败的命令之后不会再执行其他命令。调试器要找出是哪个命令失败了,就得查看 DM 的状态(比如 data0
寄存器的内容)或者 hart 的状态(比如被程序缓冲区程序修改的寄存器内容)。这就好比你给一群工人依次布置任务,先快速把任务都布置下去,之后再检查任务完成情况。如果有一个任务失败了,后面的任务就先不执行了,然后通过查看相关的工作记录(DM 或 hart 的状态)来找出是哪个任务出了问题。
执行抽象命令的限制条件
在开始执行抽象命令之前,调试器得确保 haltreq
、resumereq
和 ackhavereset
这几个位都是 0。这就好比你要让工人开始一项新任务,得先确保之前的暂停、恢复和复位相关的事情都处理好了。
当抽象命令在执行时(abstractcs
里的 busy
位为高),调试器不能改变 hartsel
(选择的 hart),也不能把 haltreq
、resumereq
、ackhavereset
、setresethaltreq
或 clrresethaltreq
这些位设置为 1。这就好比工人在干活的时候,你不能随便换工人,也不能突然让他暂停、恢复或者复位。
处理命令执行异常的情况
如果一个抽象命令没在预期时间内完成,看起来像是卡住了,调试器可以尝试下面的步骤来中止这个命令:先复位 hart(用 hartreset
或 ndmreset
),然后复位 DM(用 dmactive
)。这就好比工人干活卡住了,你先把他 "重启" 一下,再把工作环境 "重启" 一下。
如果在所选的 hart 不可用的时候启动抽象命令,或者在执行抽象命令时 hart 变得不可用了,DM 可能会终止这个抽象命令,把 busy
位清零,把 cmderr
设置为 4(表示 "暂停 / 恢复" 问题)。也有可能命令看起来就像卡住了(busy
位一直不清零)。这就好比工人突然生病了(hart 不可用),正在做的任务可能就做不下去了,要么直接停止,要么就一直卡在那里。
6.1 Abstract command list

- 0x0000 - 0x0fff :放的是 CSRs(控制状态寄存器),这里面可以通过
dpc
这个 "快捷方式" 访问 "PC"(程序计数器,类似记录你正在看哪一页书的标记)。 - 0x1000 - 0x101f:放的是 GPRs(通用寄存器,像通用抽屉,啥都能临时放一点)。
- 0x1020 - 0x103f:放的是浮点寄存器(专门放小数相关数据的抽屉)。
- 0xc000 - 0xffff:留着以后给特殊扩展功能或内部用的,现在先空着。

- cmdtype:等于 0 时,代表这个命令是 "Access Register"(访问寄存器),就像给调试器一个 "访问寄存器" 的任务标签。
- aarsize:决定访问寄存器时拿多少数据。2 是拿 32 位(一小份),3 是拿 64 位(中等份),4 是拿 128 位(一大份)。如果要拿的量比寄存器实际能给的多,访问就会失败。
- aarpostincrement:如果是 1,比如你访问了 0 号寄存器,访问完后,下次自动变成访问 1 号寄存器(像自动翻页)。0 就是没这功能。
- postexec:如果是 1,在完成数据转移(读或写寄存器)后,去执行 "程序缓冲区" 里的程序(像做完一件事,再顺手做另一件事)。0 就是不做。
- transfer :配合
write
用。1 表示要按write
的指示做操作(读或写寄存器)。 - write :当
transfer
是 1 时,0 表示从寄存器读数据到arg0
(从抽屉拿东西放篮子);1 表示从arg0
写数据到寄存器(把篮子东西放抽屉)。 - regno:要访问的寄存器编号,像抽屉号码。
"Access Register" 命令就像调试器当 "寄存器管理员",对 CPU 寄存器进行操作,还能执行一个小任务(程序缓冲区)。举例:
-
读寄存器 :比如你想看看 3 号寄存器(
regno = 3
)里的内容。设置write = 0
(不写),transfer = 1
(要操作)。调试器就像从 3 号抽屉里把东西拿出来,放到arg0
这个篮子里。 -
写寄存器 :如果想把
arg0
篮子里的东西放到 5 号寄存器(regno = 5
)。设置write = 1
(写),transfer = 1
(要操作)。调试器就把篮子东西放进 5 号抽屉。 -
自动递增编号 :如果
aarpostincrement = 1
,假设先访问 2 号寄存器,访问完后,下次regno
自动变成 3 号(像自动从第 2 页翻到第 3 页)。 -
执行程序缓冲区 :如果
postexec = 1
,在完成读或写寄存器后,调试器会去执行 "程序缓冲区" 里的程序(像做完作业,再去做手工)。 -
调试模块必须实现这个命令。而且当硬件线程(hart)"暂停"(像上课喊停,学生不动)时,必须支持读写所有 GPRs(通用寄存器,那些通用抽屉)。
-
对于其他寄存器(比如浮点寄存器),支持情况不一定。可能有的寄存器只能读不能写,或者在 hart 运行时(像学生正常上课)也能访问,也可能不行。每个寄存器在 "读、写、hart 状态(暂停或运行)" 下的支持情况都可能不同。
-
如果操作中任何一步失败(比如要访问的寄存器根本不存在),
cmderr
会被设置(像报错),后面步骤也不做了。比如要开一个不存在的抽屉,直接报错,不再继续开其他抽屉。
通过这些操作,调试器就能灵活地和 CPU 寄存器 "打交道",就像管理员管理抽屉一样,按需读写,还能在适当时候执行小任务。
6.2 快速访问
当 cmdtype
为 1 时,表示这是一个 "Quick Access command(快速访问命令)

命令操作通俗解释
- 检查 hart 是否已暂停 :
假如把 hart 想象成一个正在工作的小机器人。如果这个小机器人已经停下来(暂停)了,就像机器人正在休息,这时这个命令会给cmderr
设置为 "halt/resume"(暂停 / 恢复相关的错误标记),然后不再继续下面的操作。就好比你看到一个人已经坐下休息了,还非要让他坐下,那就会报错说 "别重复啦"。 - 尝试让 hart 暂停 :
现在要让这个小机器人停下来。但如果小机器人因为其他原因(比如遇到了一个 "小陷阱"------ 断点)自己停下来了,那这个命令也会给cmderr
设置为 "halt/resume",不再继续下面的操作。这就像你让朋友帮忙去买东西,结果朋友因为路上看到喜欢的店自己先停下了,那你的任务就没法按计划进行,得报错。 - 执行程序缓冲区 :
让小机器人去执行一个 "小任务包"(程序缓冲区)。如果在执行这个 "小任务包" 时出了问题(异常),就像小机器人在执行任务时摔了一跤,这时会给cmderr
设置为 "exception"(异常标记),"小任务包" 的执行结束,但这个快速访问命令还会继续往下走。就好比你让朋友去取快递,朋友取快递时遇到点问题,但你还是让朋友继续后面的行程。 - 恢复 hart 运行 :
最后让小机器人重新开始工作(恢复运行)。
总结
这个 "Quick Access command" 是一个可选的命令(不是必须有的功能),而且在整个过程中,它不会去动数据寄存器(就像一个人只负责发号施令让机器人动,但不碰机器人携带的包裹 ------ 数据寄存器)。通过这几个步骤,调试器可以在一定条件下控制 hart 的暂停、执行特定任务包,然后再恢复运行,但每一步都有相应的规则和错误处理机制。
6.3 内存访问
cmdtype
:值为 2,代表这是 "访问内存命令(Access Memory Command)"
aamvirtual
:- 0:表示访问的是物理地址(就像直接按实际房间号找房间),操作直接在硬件线程(hart)对应的物理内存上进行。
- 1:表示访问的是虚拟地址,且会像在机器模式(M - mode)下设置了
MPRV
那样进行地址转换(类似通过门牌号对应的虚拟映射找房间)。
aamsize
:决定访问内存区域的大小。- 0:访问内存位置的最低 8 位(1 字节,像从一个大箱子里拿 1 小格东西)。
- 1:访问最低 16 位(2 字节,拿 2 小格)。
- 2:访问最低 32 位(4 字节,拿 4 小格)。
- 3:访问最低 64 位(8 字节,拿 8 小格)。
- 4:访问最低 128 位(16 字节,拿 16 小格)。
aampostincrement
:- 1:内存访问完成后,
arg1
(存地址的地方)会按aamsize
编码的字节数增加(比如aamsize
是 2,访问 4 字节,arg1
地址就 +4,像自动翻到下一页地址)。 - 0:无此效果。
- 1:内存访问完成后,
write
:- 0:从
arg1
指定的内存位置读数据到arg0
(像从书架上拿书到自己手里)。 - 1:把
arg0
的数据写到arg1
指定的内存位置(像把手里的书放回书架)。
- 0:从
target - specific
:留给特定目标用的保留位(暂时不用管,像预留的特殊通道)。
命令操作通俗解释(结合例子)
"访问内存命令(Access Memory)" 让调试器能像被选中的硬件线程(hart)一样访问内存,包括 hart 本地的内存映射寄存器等(好比调试器 "模仿" hart 去开它能开的抽屉)。
- 读操作示例 :
假设aamsize = 2
(访问 32 位,4 字节),write = 0
(读),arg1
里存的地址是0x1000
。调试器就从内存地址0x1000
处读 4 字节数据到arg0
(像从0x1000
这个抽屉里拿 4 件东西放到篮子arg0
里)。如果aampostincrement = 1
,读完后arg1
会变成0x1004
(地址自动加 4,准备好下次读下一个抽屉)。 - 写操作示例 :
若write = 1
(写),arg0
里有数据,arg1
地址是0x2000
,aamsize = 3
(访问 64 位,8 字节)。调试器会把arg0
的数据写到内存地址0x2000
处(像把篮子arg0
里的东西放到0x2000
这个抽屉里)。 - 失败处理 :
如果这些操作中任何一步失败(比如 hart 在机器模式下执行相同访问也会失败的情况),就设置cmderr
(报错),后面步骤不再执行(像发现抽屉坏了,马上停止操作并标记问题)。
命令支持情况
调试模块可以选择实现这个命令,并且可以支持在 hart 运行或暂停时访问内存位置。如果支持 hart 运行时的内存访问,那也必须支持 hart 暂停时的内存访问(好比如果允许在白天用某工具,那晚上也得允许用)。这个命令只有在读内存时会修改 arg0
,只有设置了 aampostincrement
才会修改 arg1
,其他数据寄存器不受影响(像只动特定的篮子和抽屉编号,其他篮子不动)。
