一、红白机中APU操作方法
1、导言
本参考文献基于我在 1988 年型号的美国 NTSC NES 上进行的测试结果,该 NES 包含 G 版本的 2A03 CPU/APU 和 NES-CPU 07 版本的主板。一旦对 PAL 硬件进行了测试,我们将对其进行介绍。请随时将这些信息纳入参考资料和其他文档。
在实现 NES 声音模拟器时,即使阅读了现有的文档,仍有许多问题没有得到解答,因此制作了一个简单的开发盒,在真正的 NES 上进行测试。这次测试非常成功,并发现了许多新的细节。我的笔记包括与现有文档的不同之处,但这似乎不是发布我的发现的可靠方式,因此我决定编写一份简明的参考文献。
对于熟悉 NESSOUND.TXT 和 DMC.TXT 的用户,应特别注意以下差异:
更正:
- DMC 表项 $D 应为 $2A0 而不是 $2A8
- 帧序列发生器
- 方形占空比发生器
澄清:
- DMC
- 三角形线性计数器
- 长度计数器的操作和状态寄存器的行为
不言而喻,这里介绍的模型可能与 NES 中实际的逻辑门排列并不一致。只要其行为与此处描述的一致,硬件如何实现并无区别。
-
看看综合仿真器测试 ROM 是否切实可行。
-
探测完整的开机状态和复位状态。
-
测试 PAL 硬件,确定 APU 帧频、DMC 和噪声周期表。
-
检查每个通道的每个单元的完整行为,以确保所有通道的共用单元
所有通道的共同单元行为相同。 -
再次仔细检查 NES 硬件的细节。
-
确定输出前的后 DAC 滤波。
2、概述
APU 由五个通道组成:方波 1、方波 2、三角波、噪声、三角调制通道(DMC)。每个通道都有一个为波形发生器提供时钟的可变速率定时器,以及由帧序列器的低频时钟驱动的各种调制器。DMC 播放采样,而其他通道则播放波形。波形通道具有持续时间控制功能,有些还具有音量包络单元,还有几个具有频率扫描单元。
方形 1/方形 2
$4000/4 ddle nnnn 占空比、环路环境/禁用长度、环境禁用、体积/环境周期
$4001/5 EPPP NSSS 启用扫描、周期、负值、移位
$4002/6 PPPP PPPP 周期低电平
$4003/7 llll lppp 长度指数,周期高
三角形
$4008 CLLL LLLL 控制,线性计数器加载
$400A pppp pppp 周期低位
$400B llll lppp 长度指数,周期高
噪音
$400C --le nnnn 循环环境/禁用长度,环境禁用,音量/环境周期
$400E s— pppp 短模式,周期索引
$400F llll l— 长度指数
DMC
$4010 il–fffff IRQ 启用、循环、频率索引
$4011 -ddd dddd DAC
$4012 aaaa aaaa 采样地址
$4013 llll llll 采样长度
通用
$4015 —d nt21 长度控制器使能: DMC、噪声、三角形、脉冲 2、1
$4017 fd-- ---- 5 帧周期,禁用帧中断
状态(读)
$4015 if-d nt21 DMC IRQ、帧 IRQ、长度计数器状态
3、基础知识
十六进制值的前缀是 $,但某些单十六进制数序列除外,因为这些序列显然是十六进制。比特的编号从 0 到 7,与字节的最小有效比特到最大有效比特相对应;比特 n 的二进制权重为 2^n。
标志是一个双态变量,可以设置或清除。以位实现时,清 = 0,置 = 1。
分频器每隔 n 个输入时钟输出一个时钟,其中 n 是分频器的周期。它包含一个计数器,在每个时钟到来时递减。当计数器的周期为 0 时,它将被重新加载,并产生一个输出时钟。复位分频器可重新加载计数器,但不会产生输出时钟。改变分频器的周期不会影响其当前计数。
定序器根据从第一步开始的一系列步骤的重复,生成一系列数值或事件。当时钟计时时,序列的下一步就会产生。
在方框图中,三角形符号是一个控制门;如果控制为非零,则输入不变地传递到输出,否则输出为 0。
control
|
v
|\
in -->| >-- out
|/
除状态寄存器外,所有其他寄存器都只允许写入。寄存器的值 "是指最后写入寄存器的值。
NTSC NES 的主时钟基于 21.47727 MHz 晶体,除以 12 得到 ~1.79 MHz 时钟源。APU 使用这两个时钟。
CPU 的 IRQ 线路对电平敏感,因此一旦 CPU 的 IRQ 被确认,APU 的中断标志必须被清除,否则一旦其抑制标志被清除,CPU 将立即再次中断。
一般来说,APU 是许多独立单元的集合,它们总是并行运行。对通道参数的修改通常只影响一个子单元,直到该单元的下一个内部周期开始时才会生效。
每一节都以概述和可选框图开始,为后面的信息提供框架。为了减少歧义,很少有重复的信息陈述。
4、帧序列器
帧序列发生器包含一个分频器和一个序列发生器,用于为不同的单元提供时钟。
分频器的输出时钟频率略低于 240 Hz,似乎是通过将 21.47727 MHz 系统时钟除以 89490 得出的。定序器的时钟由分频器的输出提供。
在写入 $4017 时,分频器和定序器被复位,然后对定序器进行配置。有两个序列可供选择,帧 IRQ 生成可以禁用。
mi-- ---- mode, IRQ disable
如果模式标志清除,则选择 4 步序列,否则选择 5 步序列,并立即为定序器计时一次。
f = 设置中断标志
l = 时钟长度计数器和扫描单元
e = 时钟包络线和三角形线性计数器
模式 0:4 级有效速率(近似值)
- - - f 60 赫兹
- l - l 120 赫兹
e e e e 240 赫兹
模式 1:5 级有效速率(近似值)
------(中断标志永不设置)
l - l - - 96 Hz
e e e e - 192 Hz
在任何时候,如果中断标志被设置且 IRQ 禁用被清除,CPU 的 IRQ 线路都会被中断。
5、长度计数器
长度计数器可自动控制持续时间。通过清零状态寄存器中的相应位,可以停止计数并禁用计数器。
停止标志位于通道的第一个寄存器中。对于方波和噪声通道,是第 5 位,而对于三角形通道,是第 7 位:
--h- ---- 停止(噪声和方波通道)
h--- ---- halt(三角形通道)
请注意,"停止 "标志的位也映射到长度计数器(噪声和方波)或线性计数器(三角波)中的另一个标志。
除非禁用,否则写入通道的第四寄存器后,计数器将立即重新加载查找表中的值,查找表的索引由以下位构成 上 5 位构成的索引为基础,用查找表中的值重新加载计数器:
iiii i--- 长度索引
bits bit 3
7-4 0 1
-------
0 $0A $FE
1 $14 $02
2 $28 $04
3 $50 $06
4 $A0 $08
5 $3C $0A
6 $0E $0C
7 $1A $0E
8 $0C $10
9 $18 $12
A $30 $14
B $60 $16
C $C0 $18
D $48 $1A
E $10 $1C
F $20 $1E
关于表格左列数值的可能解释,请参阅说明部分。
由帧序列发生器计时时,如果停止标志清零且计数器非零,则计数器递减。
6、状态寄存器
位于 $4015 的状态寄存器可控制和查询通道长度计数器,以及查询 DMC 和帧中断。这是唯一一个可以读取的寄存器。
读取 $4015 时,将返回通道长度计数器的状态、当前 DMC 采样的剩余字节数以及中断标志。之后,帧序列器的帧中断标志将被清除。
if-d nt21
来自 DMC 的 IRQ
帧中断
DMC 样本剩余字节数 > 0
三角形长度计数器 > 0
方形 2 长度计数器 > 0
方形 1 长度计数器 > 0
当写入 $4015 时,通道长度计数器使能标志被设置,DMC 可能被启动或停止,DMC 的 IRQ 发生标志被清除。
---d nt21 DMC、噪声、三角形、方形 2、方形 1
如果 d 被置位,且 DMC 的 DMA 读取器没有更多的采样字节要获取,则重新启动 DMC 采样。如果 d 被清除,则 DMA 读取器的剩余采样字节数被设为 0。
7、包络生成器
包络发生器可生成恒定音量或锯形包络,并可选择循环。它包含一个分频器和一个计数器。
通道的第一个寄存器控制包络:
--ld nnnn loop, disable, n
请注意,循环标志的位位置也映射到长度计数器中的一个标志。
分频器的周期设置为 n + 1。
当帧序列发生器发出时钟时,会发生以下两种操作之一:如果上次时钟后第四通道寄存器被写入,则计数器被设置为 15,除法器被重置,否则除法器发出时钟。
当分频器输出时钟时,会发生以下两种操作之一:如果设置了循环且计数器为零,则将计数器设置为 15;否则,如果计数器不为零,则将计数器递减。
设置禁用时,通道音量为 n,否则为计数器中的值。除非被其他条件覆盖,否则通道的 DAC 将接收通道的音量值。
8、计时器
所有通道都使用一个由 ~1.79 MHz 时钟驱动的分频器。
噪声通道和 DMC 使用查找表来设置定时器的周期。对于方波和三角波通道,第三和第四寄存器构成一个 11 位值,分频器的周期设置为该值 * 加 1*。
llll llll 周期的低 8 位(第三寄存器)
---- -hhh 周期的高 3 位(第四寄存器)
9、扫描单元
扫描单元可定期调整方波通道的周期。它包含一个分频器和一个移位器。
通道的第二个寄存器配置扫描单元:
EPPP NSSS enable、period、negate、shift
除法器的周期设置为 p + 1。
移位器根据通道周期持续计算结果。通道周期(来自第三和第四寄存器)首先右移 s 位。如果设置了否定,移位值的位数将被反转,在第二个方波通道上,反转后的值将递增 1,所得值与通道的当前周期相加,得出最终结果。
当扫描单元进入时钟时,分频器先进入时钟,然后,如果自上次扫描时钟后扫描寄存器被写入,分频器将被重置。
当通道的周期小于 8 或移位器的结果大于 $7FF 时,通道的 DAC 接收 0,扫描单元不会改变通道的周期。否则,如果扫描单元已启用,且移位计数大于 0,则当分频器输出时钟时,第三和第四寄存器中的通道周期将根据移位器的结果进行更新。
10、方波生成器
+---------+ +---------+
| Sweep |--->|Timer / 2|
+---------+ +---------+
| |
| v
| +---------+ +---------+
| |Sequencer| | Length |
| +---------+ +---------+
| | |
v v v
+---------+ |\ |\ |\ +---------+
|Envelope |------->| >----------->| >----------->| >-------->| DAC |
+---------+ |/ |/ |/ +---------+
从寄存器 $4000 和 $4004 开始有两个方形通道。每个通道都包含以下内容 包络发生器、扫描单元、输出除以 2 的定时器、8 步音序器、长度计数器。
$4000 /$4004 :占空比、包络
$4001/$4005: 扫频单元
4002/$4006:低周期
$4003/$4007: 重载长度计数器,周期高
除包络外,第一个寄存器还控制方波的占空比,但不重置定序器的位置:
dd-- ---- 占空比选择
d 波形序列
---------------------
_ 1
0 - ------ 0 (12.5%)
__ 1
1 - ----- 0 (25%)
____ 1
2 - --- 0 (50%)
_ _____ 1
3 -- 0(25% 否定)
写入第四寄存器时,定序器重新启动。
定序器由分频定时器输出提供时钟。
当定序器输出为低电平时,DAC 接收 0。
11、线性计数器
线性计数器是三角形通道的第二个更精确的持续时间计数器。它包含一个计数器和一个内部停止标志。
寄存器 $4008 包含控制标志和重载值:
crrrr rrrr 控制标志,重载值
请注意,控制标志的位位置也映射到长度计数器中的一个标志。
当寄存器 $400B 被写入时,停止标志被置位。
当帧序列器发出时钟信号时,将依次执行以下操作:
1) 如果设置了停止标志,则将计数器设置为重载值,否则,如果计数器非零,则将其递减。
2) 如果控制标志清除,则清除停止标志。
12、三角波发生器
+---------+ +---------+
|LinearCtr| | Length |
+---------+ +---------+
| |
v v
+---------+ |\ |\ +---------+ +---------+
| Timer |------->| >----------->| >------->|Sequencer|--->| DAC |
+---------+ |/ |/ +---------+ +---------+
三角通道包含以下内容 定时器、32 步定序器、长度计数器、线性计数器、4 位 DAC。
$4008:长度计数器禁用,线性计数器
$400A:周期低电平
$400B:长度计数器重载,周期高电平
当定时器产生一个时钟,且长度计数器和线性计数器的计数均不为零时,定序器即进入时钟。
定序器向 DAC 输送以下重复的 32 步序列:
F E D C B A 9 8 7 6 5 4 3 2 1 0 0 1 2 3 4 5 6 7 8 9 A B C D E F
在最低的两个周期($400B = 0 和 $400A = 0 或 1),产生的频率非常高,以至于 DAC 实际上输出了介于 7 和 8 之间的数值。7 和 8 之间的中间值。
13、噪声发生器
+---------+ +---------+ +---------+
| Timer |--->| Random | | Length |
+---------+ +---------+ +---------+
| |
v v
+---------+ |\ |\ +---------+
|Envelope |------->| >----------->| >------->| DAC |
+---------+ |/ |/ +---------+
噪声通道从寄存器 $400C 开始,包含以下内容: 长度计数器、包络发生器、定时器、带反馈的 15 位右移寄存器、4 位 DAC。
$400C:包络
$400E:模式、周期
$400F:重载长度计数器
寄存器 $400E 根据周期表中的 4 位索引设置随机发生器模式和定时器周期:
m--- iiii 模式,周期索引
i timer period
----------------
0 $004
1 $008
2 $010
3 $020
4 $040
5 $060
6 $080
7 $0A0
8 $0CA
9 $0FE
A $17C
B $1FC
C $2FA
D $3F8
E $7F2
F $FE4
移位寄存器由定时器提供时钟,空出的第 14 位将由预移位的第 0 位和第 1 位(模式 = 0)或第 0 位和第 6 位(模式 = 1)的exclusive-OR 填满,分别产生 32767 位和 93 位序列。
当移位寄存器的第 0 位被设置时,DAC 接收 0。
上电时,移位寄存器的值为 1。
14、德尔塔调制通道(DMC)
+----------+ +---------+
|DMA Reader| | Timer |
+----------+ +---------+
| |
| v
+----------+ +---------+ +---------+ +---------+
| Buffer |----| Output |---->| Counter |---->| DAC |
+----------+ +---------+ +---------+ +---------+
DMC 可输出由 1 位三角积分组成的采样,其 DAC 可直接更改。它包含以下部分 DMA 读取器、中断标志、采样缓冲器、定时器、输出单元、与 7 位 DAC 绑定的 7 位计数器。
$4010:模式、频率
$4011: DAC
$4012:地址
$4013:长度
上电时,DAC 计数器为 0。
寄存器 4010 设置中断使能、循环和定时器周期。如果新的中断使能状态为清零,则中断标志将被清除。
il-- ffff interrupt enabled, loop, frequency index
f period
----------
0 $1AC
1 $17C
2 $154
3 $140
4 $11E
5 $0FE
6 $0E2
7 $0D6
8 $0BE
9 $0A0
A $08E
B $080
C $06A
D $054
E $048
F $036
写入 $4011 会将计数器和 DAC 设为新值:
-ddd dddd 新的 DAC 值
15、采样缓冲区
采样缓冲区可保存单个采样字节或为空。它由 DMA 读取器填充,只能由输出单元清空,因此一旦装入样本,最终将被输出。
16、DMA 读取器
DMA 读取器会在采样缓冲区变空时,用当前采样的连续字节填充采样缓冲区。它有一个地址计数器和一个字节剩余计数器。
重新启动 DMC 采样时,地址计数器被设置为寄存器 $4012 * $40 + $C000,字节计数器被设置为寄存器 $4013 * $10 + 1。
当采样缓冲器处于空状态且字节计数器不为零时,会发生以下情况: 采样缓冲区将被当前地址处从内存读取的下一个采样字节填满,这取决于现有的映射硬件(与 CPU 内存访问相同)。地址递增;如果地址超过 $8000 ,则绕到 $8000 。字节计数器递减;如果字节计数器变为零且循环标志被设置,则重新开始采样(见上文),否则,如果字节计数器变为零且中断启用标志被设置,则中断标志被设置。
当 DMA 读取器访问内存字节时,CPU 会暂停 4 个时钟周期。
17、输出单元
输出单元持续输出完整的采样字节或等长的静音。它包含一个 8 位右移寄存器、一个计数器和一个静音标志。
当一个输出周期开始时,计数器加载 8,如果采样缓冲器为空,则设置静音标志,否则清除静音标志并将采样缓冲器清空到移位寄存器中。
当定时器时钟到来时,将依次进行以下操作:
1. 如果静音标志清除,移位寄存器的第 0 位将应用于 DAC 计数器: 如果第 0 位清零且计数器大于 1,则计数器递减 2,否则如果第 0 位置位且计数器小于 126,则计数器递增 2。
1) 为移位寄存器计时。
2) 计数器递减。如果计数器为零,则开始一个新的周期。
18、DAC 输出
每个声道的数模转换器的实现方式会导致声道之间的非线性和相互作用,因此计算所产生的振幅有些复杂。
归一化音频输出电平是两组通道的总和:
output = square_out + tnd_out
95.88
square_out = -----------------------
8128
----------------- + 100
square1 + square2
159.79
tnd_out = ------------------------------
1
------------------------ + 100
triangle noise dmc
-------- + ----- + -----
8227 12241 22638
其中,三角形、噪声、dmc、square1 和 square2 是输入其 DAC 的值。dmc 的取值范围为 0 至 127,其他取值范围为 0 至 15。当某一组的次分母为 0 时,其输出为 0。
19、使用查找表实施
使用两个查找表可以有效地实现公式:两个方形通道使用一个 31 条目的查找表,其余通道使用一个 203 条目的查找表(由于对 tnd_out 进行了近似处理,分母略有调整,以保持归一化输出范围)。
square_table [n] = 95.52 / (8128.0 / n + 100)
square_out = square_table [square1 + square2]
通过使用与 DMC 的 DAC 接近的基本单位,可以对后一个表进行近似(4% 以内)。
tnd_table [n] = 163.67 / (24329.0 / n + 100)
tnd_out = tnd_table [3 * triangle + 2 * noise + DMC] = 163.67 / (24329.0 / n + 100)
20、线性近似
也可以使用线性近似,这将导致 DMC 采样音量稍大,但由于波形通道只使用了传输曲线的一小部分,因此操作相当精确。由于 DMC 近似所需的净空,整体音量会有所降低。
square_out = 0.00752 * (square1 + square2)
tnd_out = 0.00851 * triangle + 0.00494 * noise + 0.00335 * DMC
这一线性近似值忽略了 DMC 在其 DAC 处于高电平时的衰减效应。这个系数可以用主公式计算出一个比率,并预先计算到一个 128 项的查找表中。
tnd_out(triangle=15,dmc=d) - tnd_out(triangle=0,dmc=d)
attenuation(d) = ------------------------------------------------------
tnd_out(triangle=15,dmc=0)
21、不可靠的行为
(以下行为可能不需要模拟,因为它们不可靠,稳定的代码会避免调用,而且它们的行为很难精确预测)。
如果在读取寄存器 $4015 时设置了帧 IRQ,它似乎会被忽略(类似于轮询 $2002 以获取 vbl 标志)。
DMC 的 DMA 读取器似乎每隔几个 CPU 周期检查一次缓冲区是否为空,而不是每个周期或连续检查。
在播放采样时向 DAC 寄存器($4011)写入数据有时没有任何效果,这可能是因为 DMC 的输出单元在写入数据的同时为计数器计时。
22、说明
(以下内容只是对主要内容的重新陈述,而不是增加新内容)。
由于包络循环和长度计数器禁用标志被映射到同一个位上,因此当包络处于循环模式时,长度计数器无法使用。三角形通道也是如此,线性计数器和长度计数器由寄存器 $4008 中的相同位控制。
与其他波形通道不同的是,三角形通道的静音方式是将其波形停止在所处的任何相位,而不是将零发送到其 DAC。
长度计数表似乎是为 4/4 拍的标准音符长度设置的,速度为 160 bpm 和 180 bpm。如果第 3 位为 0,结果如下(Dn 为第四通道寄存器的第 n 位):
180bpm 160bpm
D6-D4 D7=0 D7=1 note
-------------------------------
$00 10 12 16th
$01 20 24 8th
$02 40 48 4th (one beat)
$03 80 96 half
$04 160 192 whole
$05 60 72 4th dotted
$06 14 16 8th triplet (*3 = a 4th)
$07 26 32 4th triplet (*3 = a half)