Teakra 全解析:melonDS 中 DSi/3DS 的 XpertTeak DSP 模拟器、工具链与硬件文档

原创2026-10-03 18:17:08440 阅读
文章标签:虚拟化图形学桌面应用

Teakra 全解析:melonDS 中 DSi/3DS 的 XpertTeak DSP 模拟器、工具链与硬件文档

Teakra 是一套面向 XpertTeak DSP(DSi/3DS 内置的音频数字信号处理器)的开源模拟器、反汇编器、汇编器、测试工具与硬件文档合集,被 melonDS 以子模块形式集成以模拟 DSi 的 DSP 子系统。本文以 src/teakra/README.md 与 src/teakra/src/README.md 为骨架,结合 Teakra 源码 与 DSi_DSP 集成层,完整梳理其处理器架构、寄存器模型、MMIO 外设布局、CPU↔DSP 通信机制、DMA 引擎以及配套工具链,帮助你理解如何模拟一颗真实存在的 DSP 芯片,以及如何在模拟器中接入这颗 DSP。

一、Teakra 是什么:定位与背景

Teakra 提供四类内容:Emulator(模拟器)、(dis-)assembler(反汇编器/汇编器)、tools(工具)和 documentation(文档),对象是 XpertTeak——由 DSP Group 设计、被任天堂用于 DSi/3DS 的 DSP 芯片。项目明确感谢 Martin Korth 的 GBATEK 文档以及多位贡献者在硬件逆向上的帮助。

  • 主源码位于 src/teakra/src,编译为可移植的库/工具;架构与外设的详细硬件文档同样位于该目录(如 processor_general.md、register.md、mmio.md)。
  • 公共头文件位于 src/teakra/include/teakra,对外暴露模拟器与反汇编器库的 C++/C 接口(teakra.h、teakra_c.h)。
  • 另外还有两个 3DS 实机测试工具源码(dsptester、dspmemorytester),用于在真机上验证指令/寄存器以及内存读写、MMIO 与 DMA 的行为,是模拟器精度的重要校验来源。

XpertTeak 的架构归属:Teak 还是 TeakLite II?

XpertTeak 由 Teak 家族处理器内核与外设组成,外设包括 DMA、中断控制器(ICU)、音频输入/输出端口(BTDMP) 等。关于其确切架构存在一段有趣的争议:

  • GBATEK 依据 RealView Developer Suite 中附带的 TeakLite II 反汇编器,认为它是 TeakLite II(TeakLite 的后继);
  • 但 Lauterbach 的 Teak 家族调试器显示,名为 "TEAK(-REVA, -REVB, DEV-A0, -RTL2_0)" 的部件包含与 GBATEK 描述的寄存器与指令集高度一致的实现,而 "TeakLite-II" 的寄存器与指令集则完全不同。

由此可以推断,XpertTeak 更可能是 最初的 Teak 架构——即作为 TeakLite 的 "non-Lite" 扩展版本而引入。这是理解后续所有寄存器与指令细节的前提。

DSi 与 3DS 中用途的差异

DSi 和 3DS 都搭载 XpertTeak,但使用方式截然不同(原文档事实):

  • DSi:绝大多数游戏完全不用它;仅 "Nintendo DSi Sound"、"Nintendo Zone" 系统应用以及 "Cooking Coach" 卡带使用,用途看起来是音频/视频解码。
  • 3DS:所有 3DS 游戏都使用 XpertTeak 进行音频解码与输出。

这一差异也解释了 melonDS 中 Teakra 的接入方式:在 DSi 模式下通过 DSi_DSP.cpp / DSi_DSP.h 的 DSPInterface 抽象接口挂载,支持 HLE/LLE 切换。

二、处理器架构总览:以 16-bit "word" 为基本单位

processor_general.md 给出了处理器层面的总体特征,这些特征决定了下文所有寄存器与内存模型的形状:

  • 字长:处理器绝大多数东西是 16-bit 的,没有 8-bit 字节;最小可寻址内存单元就是 16-bit word。多数寄存器与数据总线也是 16-bit。例外是:40-bit 累加器、33-bit 乘法结果、18-bit 程序计数器。文中约定用 "word" 指代 16-bit。
  • 分离的指令/数据地址空间:
    • 指令地址为 (18+4) bit:低 18 bit 由 PC 直接寻址,高 4 bit 由 prpage 寄存器指定,即程序内存最多 16 页、每页最多 0x40000 words。但 DSi/3DS 似乎只支持半页(0x20000 words),prpage 恒为 0;
    • 数据地址为 16 bit,即数据内存空间为 0x10000 words;但 DSi/3DS 实际提供了 0x20000 words 的数据内存,超出部分通过内存 bank 切换访问,详见 miu.md。
  • 字节序:从 Teak 处理器视角看,程序/数据内存是没有字节序概念的 word 数组;但从 CPU(ARM)侧以字节数组访问时,word 呈现 little-endian。
  • 指令编码:每条指令为 1 word 或 2 words,指令集编码"非常混乱(very messy)",细节见 decoder.md。
  • 时钟频率:不确定,但 3DS DSP 二进制的反汇编显示很可能约为 134MHz(代码中出现的 134,060,000),这也与 3dbrew 声称的采样率吻合(32728Hz ≈ 134MHz / 4100,4100 正是反汇编中配置音频输出定时器所用的数)。大多数指令可在单周期完成,例外是双字指令、多数据读写指令与流控指令。

三、寄存器模型:从基础寄存器到影子寄存器与伪寄存器

register.md 详细描述了 RegisterState 类(对应 register.h)所建模的寄存器体系。核心设计理念:硬件上不同位宽的寄存器尽量统一用 u16 表示(多余位补零),以便定义伪寄存器;少数例外单独处理。

3.1 基础寄存器

  • 程序控制寄存器:
    • pc(程序计数器):18-bit,每条指令后 +1 或 +2(取决于指令长度),对指令而言是后自增语义——指令中引用 pc 时拿到的是下一条指令的地址。大部分接受 Register 操作数的指令走 16-bit 数据总线,因此 18-bit 的传输语义尚不明确,只有 mov pc a0/a1 被充分测试并实现为完整 18-bit 传输,其它代码路径被标记为 unreachable;pc 也可作为两个 word 压栈/弹栈,word 顺序由 cpc 寄存器指定。pc 还会被 call/ret/br 家族、movpdw [a0/a1] pc、bkrep 循环结束、rep 重复指令以及中断所修改;
    • prpage:4-bit 程序页寄存器,将程序空间扩展到 22-bit;XpertTeak/DSi/3DS 只支持单页,应保持 0;
    • repc:16-bit 重复计数器,rep 指令将其设为"重复次数 - 1",每重复一次递减至 0;循环外可作通用寄存器,可压栈/弹栈,若配置寄存器 crep 清零则会在上下文存取时与影子寄存器 repcs 交换。
  • 算术寄存器:
    • a0/a1/b0/b1(40-bit 累加器):Teakra 中以符号扩展的 64-bit 整数存储,分为 e:h:l(extension:high:low,8:16:16)三段;h/l 对多数运算可见,e 段只能通过 push/pop Abe 访问,其中 a0/a1 的 e 段低 4 位暴露在伪寄存器 st0/st1 中;
    • vtr0/vtr1(Viterbi 寄存器):两个 16-bit 寄存器,专用于 Viterbi 解码 的路径恢复;含 vtrshr 部分的指令会把状态位 fc0/fc1 压入其中,vtrmov 可将其搬到累加器,vtrclr 清零,无直接访问途径。
  • 乘法寄存器:
    • x0/x1/y0/y1(因子寄存器):各 16-bit,作为乘法因子,也可作通用用途;
    • p0/p1(乘积寄存器):各 33-bit,保存乘法结果,p0 存 x0*y0、p1 存 x1*y1;代码中拆为 32-bit 的 p[*] 与 1-bit 的 pe[*]。
  • 地址寄存器:
    • r0~r7(通用地址寄存器):16-bit,可作通用寄存器或内存指针,多数间接寻址操作数(如 [r0])使用它们;r7 在一些指令中还支持额外的立即数地址偏移,常被用作栈帧指针;
    • sp(栈指针):16-bit,push/call/bksto [sp] 递减,pop/ret/bkrst [sp] 递增,也可经接受 Register 操作数的指令读写;
    • page:为使用 8-bit 立即数地址的指令提供高 8 bit;
    • pcmhi:为使用 16-bit 程序地址的指令提供高 2 bit。

另一个值得注意的实现事实:大量寄存器具有二进制补码的有符号语义,但 Teakra 仍以无符号整数存储,这是为了配合解释器的策略——避免有符号整数运算,从而规避未定义/实现定义行为。

3.2 影子寄存器(Shadow Registers)

很多寄存器带有不可直接访问的"影子"副本,可在上下文切换时与主寄存器交换,分为 4 组:

  • bank exchange 寄存器:由 banke 指令交换,指令可指定交换哪些寄存器;在 RegisterState 中以 b 后缀命名;
  • Batch one-side 影子寄存器:由 cntx r/s 指令整体交换。cntx s 只发生 主→影子 传输,cntx r 只发生 影子→主;
  • Batch two-side 影子寄存器:同样由 cntx r/s 触发,但两个方向都做完整的主↔影子互换。实现上用模板 Shadow(Swap)(Array)Register 创建,再汇总进 Shadow(Swap)RegisterList 统一处理交换;
  • shadow ar/arp:行为类似 two-side 寄存器,但还能被 bankr 指令按更小批量交换,因此单独定义为 ShadowSwapAr(p)。

3.3 Block Repeat 栈

Teak 支持最多 4 层嵌套的 block repeat,为此有一个 4 帧小栈,每帧保存循环计数器、起始地址和结束地址。对程序可见的 lc(loop counter)寄存器始终是栈顶值,因此实现上通过函数 Lc() 返回栈顶,而非普通的 u16 lc 字段。

3.4 伪寄存器(Pseudo-registers)

伪寄存器表现为位域,是多个基础寄存器的组合:程序可按 16-bit word 读写,每个 bit 作用于对应的基础寄存器。它们不直接用 C++ 位域实现,原因有二:

  1. 存在两套控制/状态伪寄存器(TeakLite 兼容集 与 包含全部 Teak 专属寄存器的集合),很多字段映射到同一基础寄存器但位布局不同;
  2. 部分 bit 有存储之外的特殊行为。

实现上,RegisterState 提供模板函数 u16 Get<T>() 与 void Set<T>(u16),T 是诸如 cfgi、stt0、arp2 的预定义类型;这些类型内部通过模板保存指向基础寄存器的指针及其位位置与位长。用法示例:

u16 stt1_value = registers.Get<stt1>();

四、MMIO 布局:处理器与外界通信的中枢

mmio.md 给出了 MMIO 的整体视图:处理器通过 MMIO 与外设(并间接与 CPU 等外部硬件)通信。要点:

  • MMIO 区域占据 16-bit 地址空间中 0x0800-word 的区域,初始位于 0x8000;
  • MMIO 区域的位置可由 MIU 配置(没错,就是通过 MMIO 本身);
  • MMIO 寄存器通常只用偶地址,这看起来是一种硬件优化——MIU 中存在一个可强制此规则的寄存器;
  • 每个外设对应 MMIO 中的一个子区域,文档中各寄存器地址均为相对 MMIO 区域起始位置的偏移:
偏移 外设 功能
+0x0000 ? 未知
+0x0004 JAM —
+0x0010 GLUE —
+0x0020 Timer 定时器
+0x0050 SIO 串行 IO
+0x0060 OCEM 片上仿真模块
+0x0080 PMU 电源管理单元
+0x00C0 APBP 高级外设总线端口(CPU 通信)
+0x00E0 AHBM 高级高性能总线主控(外部内存访问)
+0x0100 MIU 内存接口单元
+0x0140 CRU 代码替换单元
+0x0180 DMA 直接内存访问
+0x0200 ICU 中断控制单元
+0x0280 BTDMP 缓冲时分复用端口(音频)

在模拟器实现中,这一布局直接体现在 teakra.cpp 的 Impl 结构里:MMIORegion mmio{miu, icu, apbp_from_cpu, apbp_from_dsp, timer, dma, ahbm, btdmp} 将全部外设串联,MemoryInterface 再把 mmio 挂到处理器的地址空间上。外设之间的中断连接也在构造函数中完成(见下文 ICU 一节)。

五、外设逐一拆解

5.1 APBP:CPU ↔ DSP 通信端口

apbp.md 描述了 DSP 侧(offset +0x00C0 起)与 CPU 侧(offset +0x0000 起)的对称 MMIO。它是 DSP 与 ARM CPU 通信的最重要端口,包含:

  • 3 对对称数据通道(CMD0..2 / REPLY0..2):一方写入数据通道会置位对应的 "data ready" 位(DSP 侧看 R0..R2 或 C0..C2),若中断使能则向对方触发中断;对方读取通道寄存器会自动清除该位。若前一条数据尚未被读就写入新数据,新数据会覆盖旧数据并再次触发中断;
  • 一对 16-bit 对称信号量通道(CPU→DSP 与 DSP→CPU):A 侧写 SET_SEMAPHORE 或 B 侧写 ACK_SEMAPHORE 会改变对应方向的信号量值,从而改变 S 位(S = (GET_SEMAPHORE & ~MASK_SEMAPHORE) != 0);掩码变化也立即反映到 S 位;每当 S 出现 0→1 跳变,就在 B 侧触发中断;
  • 状态寄存器:END(CPU 侧寄存器字节序,置 1 交换字节序)、R0..R2/C0..C2(数据就绪)、CI0..CI2(禁止 CPU 写 CMD 时的中断)、RTU/WTU(CPU 侧读写进行中)、PRS(外设复位)、RFL/RNE/WFL/WEM(CPU 侧 FIFO 满/非空等)。注意 +0x00D8 是 CPU 侧寄存器 DSP_PSTS 的镜像。

在 melonDS 集成层,APBP 的全部交互被抽象为 teakra.h 中的 SendData/RecvData/PeekRecvData/SetRecvDataHandler/SetSemaphore/ClearSemaphore/MaskSemaphore/GetSemaphore/SetSemaphoreHandler 等接口,并由 DSi_DSP.h 的 DSPInterface 以虚函数形式暴露给 CPU 侧模拟代码。

5.2 AHBM:访问外部内存(DSi/3DS 主内存)的桥

ahbm.md 描述了 AHBM(Advanced High-performance Bus Master)的 MMIO(offset +0x00E0 起),用于访问 DSP 之外的内存:

  • 状态寄存器(+0x00E0):应用在把 AHBM 接到 DMA 前会等待所有位为 0;其中 RNE 为 1 表示 burst 队列非空;
  • 通道配置(+0x00E2+N*6,N = 0..2):BURST(0:x1、1:x4、2:x8、3:?)、TYPE(0:8-bit、1:16-bit、2:32-bit、3:?)、W(0:从外部内存读、1:写外部内存)、E(应用总置位)、R(BURST 非零时置 1)、D0..D7(置位表示连接到 DMA 通道 0..7)。

melonDS 集成时,AHBM 回调通过 teakra.h 的 AHBMCallback(read8/write8/read16/write16/read32/write32)接入外部内存,另有 AHBMRead16/AHBMWrite16/AHBMRead32/AHBMWrite32 用于处理非对齐访问的怪异行为(源码注释:AHBM 在非对齐内部访问时"做了一些奇怪的事")。

5.3 DMA:三维步进的数据搬运引擎

dma.md 描述 DMA 引擎(MMIO offset +0x0180 起),用于在 DSP 内存与外部内存之间搬运大块数据。寄存器要点:

  • +0x0184 C0..C7:启用通道 0..7(?);+0x018C E0..E7:通道结束传输标志(?);+0x018E/0x0190 S0..S7:某种 slot,初始化为 0..7;
  • +0x01BE CHANNEL:选择与 +0x01C0..+0x01DE 寄存器组绑定的通道;
  • +0x01C0..:SRC_ADDR_LOW/HIGH、DST_ADDR_LOW/HIGH、SIZE0..2、SRC_STEP0..2、DST_STEP0..2;
  • +0x01DA:SRC_SPACE/DST_SPACE 选择内存空间:0 DSP 主内存、1 MMIO、5 程序内存(仅 DST_SPACE,未测试)、7 连接 AHBM/外部内存;
  • +0x01DE:RST、STR 等控制位。

数据搬运模型:一次传输如同"复制一个带可配置步长的三维数组"。SIZE0/1/2 指定三个维度的元素数(SIZE0 是线性内存空间中最细的维度),STEP0/1/2 指定每个维度的地址步长。文档给出的例子:

SRC_ADDR = 0
SIZE0 = 3, SIZE1 = 5, SIZE2 = 2
SRC_STEP0 = 2, SRC_STEP1 = 1, SRC_STEP2 = 7

将依次从以下地址拷贝数据(, 表示维度 0 步进,/ 维度 1,|| 维度 2):

 0,  2,  4/  5,  7,  9/ 10, 12, 14/ 15, 17, 19/ 20, 22, 24||
31, 33, 35/ 36, 38, 40/ 41, 43, 45/ 46, 48, 50/ 51, 53, 55

目标地址遵循同样规则。注意 SIZEx 可为 0,效果等同于 1。

元素宽度:DWM = 0 时元素为 16-bit word,DWM = 1 时为 32-bit 双字。双字模式下维度 0 计数器把每个 32-bit 元素计为 2(即 SIZE0 值实际是所拷贝 32-bit 元素数的两倍),且地址会自动向下对齐到 32-bit 边界。

外部内存(space 7 / AHBM):以 3DS 为例,外部内存即 FCRAM,地址直接使用 FCRAM 物理地址。由于外部内存是 8-bit 寻址,而 DSP 内存是 16-bit 寻址,地址换算容易混淆——关键是 STEP 值原样加到地址上:STEP = 1 对 FCRAM 意味着步进 8-bit,对 DSP 内存则步进 16-bit。外部内存地址还有更多对齐要求,由 AHBM 处理(见 ahbm.md)。

5.4 ICU:中断控制单元

icu.md 描述 ICU(MMIO offset +0x0200 起)。核心职责是把 16 路 IRQ 信号翻译成 4 个处理器中断信号(int0、int1、int2 与 vectored interrupt vint):

  • 寄存器组:IP0..IPF(IRQ pending 标志)、IA0..IAF(IRQ acknowledge,写 1 清除 pending)、IT0..ITF(手动触发 IRQ)、I00..I0F/I10..I1F/I20..I2F(把 IRQ 连接到 core interrupt 0/1/2)、IV0..IVF(连接到 vectored interrupt)、TP0..TPF(触发模式:0 pulse / 1 sticky)、PL0..PLF(信号极性,文档作者自注"可能把 PLx 和 TPx 搞反了")、VADDR_H/L(vectored interrupt 处理函数地址)、VIC(vectored interrupt 时启用上下文切换);
  • 中断处理流程:IRQ 被触发后,若对应 core interrupt 已使能,处理器开始执行中断处理过程;处理程序应读取 IPx 得知具体 IRQ 索引,并写 IAx 清除之。

已知 IRQ 信号连线(可能有更多未发现的组件也关联了 IRQ):

DMA   -> *0
APBP  -> *1
ICU?  -> *2
BTDMP -> *4
TIMER0-> *5
TIMER1-> *6

这组连线在模拟器中一一落实于 teakra.cpp:timer[0] 触发 IRQ 0xA、timer[1] 触发 0x9、APBP 数据/信号量触发 0xE、BTDMP[0] 触发 0xB、BTDMP[1] 触发 0xC、DMA 触发 0xF。

软件中断用例:处理器可写 ITx 手动产生软件中断。许多 3DS 游戏用 IRQ 0 作为多线程的重新调度过程——线程要让出 CPU 时触发 IRQ 0,由中断处理切换线程上下文并恢复另一个线程。

5.5 BTDMP:音频输入/输出端口

btdmp.md 描述 BTDMP(Buffered Time Division Multiplexing Port,MMIO offset +0x0280 起),是音频输入输出的通道:

  • 接收侧(+0x0280 起):RIR(置 1 使能接收中断)、RE(置 1 使能接收),其余大量寄存器标为 ?/疑似固定值;
  • 发送侧(+0x02A0 起):TIR(使能发送中断)、TE(使能发送),+0x02A2 标注为时钟相关("1004");
  • 状态与数据(+0x02C0 起):RF(接收缓冲满)、TF(发送缓冲满)、TE(发送缓冲空)、FL(应用自旋等待的标志)、FIFO_RECEIVE/FIFO_TRANSMIT 数据寄存器。

模拟器中 BTDMP 与核心时序耦合:Teakra::SampleClock(s16 output<a href="https://link.gitcode.com/i/b42b1075ce970408deb540ce0e7eee3f" target="_blank">2], s16 input) 直接驱动 btdmp[0] 的采样时钟(见 [teakra.cpp),音频回调通过 teakra.h 的 SetAudioCallback 输出立体声样本。

5.6 Timer:定时器

timer.md 的 MMIO 定义提取自 Lauterbach 的 Teak 调试器,部分未测试。Timer 0 位于 +0x0020,Timer 1 位于 +0x0030(布局相同)。控制寄存器 +0x0020 的字段:

  • TS 时间刻度:0:/1、1:/2、2:/4、3:/16;
  • CM 计数模式:0 单次计数、1 自动重启、2 自由运行、3 事件计数、4/5/6 三种看门狗模式;
  • TP 输出信号极性;CT 置 1 清除输出信号;PC 暂停计数器;MU 使能 COUNTER 寄存器更新;RES 重启计数器;BP 使能断点请求;CS 时钟源(0 内部 / 1 外部);GP ?;TM 输出信号清除方式(0 手动 CT、1/2/3 两/四/八周期后);
  • +0x0022 EW:事件计数模式下递减计数器 / 看门狗模式下重载看门狗;
  • 数据寄存器:START_COUNT_L/H(重启时载入计数器的值)、COUNTER_L/H(当前计数值)、PWM_COUNTER_L/H(PWM 计数器重启值)。

5.7 MIU:内存接口单元

miu.md 的 MMIO 定义同样提取自 Lauterbach 调试器(部分未测试),位于 +0x0100。它管理处理器看到的内存空间:

  • 等待状态:Z0WS..Z3WS(4 个片外 Z 块的等待周期数)、ZDEFAULTWS(Z 片外传输默认等待周期)、XYWS(X/Y 片外内存等待周期)、PRMWS(片外程序内存等待周期);块地址由 ZxWSSA(起始,1K-word 分辨率)、ZxWSEA(结束)、ZxWSPAGE(页低 4 位)指定;
  • 分页:XPAGE/YPAGE/ZPAGE(分页模式 1 的 X/Y 页与 Z 页,模式 0 下 ZPAGE 为绝对数据页);XPAGE0CFG/YPAGE0CFG/XPAGE1CFG/YPAGE1CFG/XOFFPAGECFG/YOFFPAGECFG 配置各页的 X/Y 内存大小;
  • 程序保护:PP(启用程序保护)、TSP("Test Program",复位时锁存 strap 引脚)、INP(断点中断后跳转页选择)、PDLPAGE/PDP(程序读写 movd/movp 的替代页寄存器);
  • 其它:ZSP(Z 空间单访问模式)、PGM(分页模式)、DLP/SDL(下载内存选择及其 sticky 版本)、MMIOBASE(MMIO 基地址,512-word 分辨率——印证了上文 MMIO 区域位置可由 MIU 配置)、OBS/OBSMOD(可观测性模式及总线选择)、PRD/PZS/PXS/PXT/PZT/PZW/PZR(各类信号极性位)。

正是通过 MIU 的 bank 切换与分页机制,DSi/3DS 提供的 0x20000 words 数据内存才能够在 16-bit 数据地址空间之上被访问。

六、处理器解释器与指令流水线组件

src/README.md 将主库划分为两个层面:

  • 处理器相关:operand(基本操作数类型)、matcher/decoder(把二进制指令解码为 opcode 与操作数)、disassembler(二进制 → 伪汇编)、parser(伪汇编 → 二进制)、interpreter(执行指令)、register(register.md)、processor(解释器 + 寄存器的封装)、test_generator(为指令集生成测试用例信息);
  • 外设相关:AHBM、APBP、BTDMP、DMA、ICU、timer、MMIO、shared_memory(DSP 工作内存)、memory_interface。

在模拟器顶层,teakra.cpp 的 Impl 把这些组件按依赖顺序实例化:CoreTiming → SharedMemory → MIU → ICU → 两组 Apbp(apbp_from_cpu{0} 与 apbp_from_dsp{1},索引区分方向)→ 两个 Timer → Ahbm → Dma(依赖 shared_memory 与 ahbm)→ 两个 Btdmp → MMIORegion → MemoryInterface → Processor。中断处理器与外设回调的绑定也在此完成。Teakra::Run(cycle) 即驱动 processor.Run(cycle) 的入口。

七、工具链:COFF 反汇编、DSP1 汇编/反汇编与指令测试

src/README.md 列出的工具各自服务于"逆向 3DS/DSi DSP 固件"或"验证模拟器精度":

工具 源码位置 用途
coff_reader src/teakra/src/coff_reader 反汇编并解析某些 DSi 应用泄露的 COFF 文件的符号
dsp1_reader src/teakra/src/dsp1_reader 反汇编 DSP1 文件(3DS 应用的 DSP 二进制)
makedsp1 src/teakra/src/makedsp1 汇编 DSP1 文件
test_generator src/teakra/src/test_generator 为处理器指令生成随机测试用例
mod_test_generator / step2_test_generator src/teakra/src/mod_test_generator、src/teakra/src/step2_test_generator 类似 test_generator,但专用于 mod / step2 相关指令
test_verifier src/teakra/src/test_verifier 在解释器上验证测试用例,对照 3DS 实机生成的结果

这条工具链构成了 Teakra 的"验证闭环":test_generator 系列在宿主机随机生成指令用例,test_verifier 用这些用例比对解释器与 3DS 实机(dsptester / dspmemorytester)的运行结果,从而持续校正模拟精度。工程化方面,项目根 CMakeLists.txt 负责构建这些可移植库与工具,appveyor.yml 提供 CI 构建配置。

八、在 melonDS 中的集成方式

Teakra 在 melonDS 中的角色是 DSi 音频 DSP 的 LLE 实现,集成方式如下:

  1. 抽象接口:DSi_DSP.h 定义纯虚类 DSPInterface,覆盖 APBP 数据/信号量、程序/数据内存读写(ProgramRead/ProgramWrite、DataReadA32/DataWriteA32)、MMIO 读写、DMA 高位地址(DMAChan0GetSrcHigh/DstHigh)、AHBM 单元大小/方向/DMA 通道查询与非对齐读写、Run(cycle) 与 SampleClock(s16 output[2], s16 input);
  2. Teakra 实现:teakra.h 的 class Teakra : public melonDS::DSPInterface 实现该接口,并补充 SetSharedMemoryCallback、SetAHBMCallback、SetAudioCallback、SetMicEnableCallback 等回调注册入口,以及 DoSavestate(状态存档)与 Reset;
  3. CPU 侧接入:DSi_DSP 类(DSi_DSP.h 起)封装 HLE/LLE 切换(SetDSPHLE、StartDSPLLE/StartDSPHLE/StopDSP),并通过 DSP_* 寄存器(0x040043xx)的 Read8/16/32、Write8/16/32 把 ARM 侧访问转发给 Teakra。DSP 的复位线由 SCFG_RST bit0 控制(SetRstLine),这与硬件文档中 APBP 的 PRS 复位标志、MIU 的 strap 锁存等细节相呼应。

九、延伸阅读与源码导航

以当前仓库为根,可继续深挖以下文件:

综上,Teakra 的价值不仅在于给 melonDS 提供了一颗真实 DSP 的高保真 LLE 实现,更在于它把 XpertTeak 的逆向成果沉淀为一套结构清晰的文档(每个外设一篇 MMIO 手册)和一条"实机对照验证"的工具流水线——对任何想研究 DSP 模拟、嵌入式 SoC 外设建模或参与模拟器精度打磨的开发者,都是极佳的参考资料。

登录后查看全文
melonDS