Project Brief 2A03 Music Player · Logisim Build Revised · 2026.05.16

在 Logisim 里搭一个
能放 NES 音乐的硬件播放器

用纯数字逻辑电路实现一个 8-bit 音乐播放器:自研五段 CPU + 基于 Logisim Buzzer 的 APU + NSF 文件离线转换工具链。最终能从 Logisim 内置 Buzzer 播放任意开源 NES 游戏音乐, 带完整的播放/暂停/切歌/计时控制。

01
Goal · 目标

项目目标

在 Logisim Evolution 里搭建一个能播放真实 NES 游戏音乐的"硬件"播放器。 输入是开源社区的 .nsf 文件,输出是从 Buzzer 真实发出的音乐声音。

满足的验收要求

我们想做到的效果

02
Architecture · 架构

整体架构一图流

整个系统分为三大块:转换脚本(在电脑上跑)、CPU(在 Logisim 里搭)、APU(在 Logisim 里搭)。 它们之间用简单的数据格式解耦,前期可以并行推进。

FIG. 2.1 · SYSTEM OVERVIEW
OFFLINE · 离线(在电脑上) .nsf 文件 开源 NES 音乐 Python 脚本 NSFPlay / VGM 工具 提取寄存器写入序列 ROM 映像 .txt Logisim 可加载格式 LOAD LOGISIM · 在仿真器里 ROM 乐谱数据 CPU 五段架构 IF · 取指 ID · 译码 EX · 执行 MEM · 访存 WB · 回写 指令 APU 4 路 Buzzer 🔊 Pulse 1 🔊 Pulse 2 🔊 Triangle 🔊 Noise — 系统声卡自动混音 — → OUTPUT MMIO 🎵 系统声音 从电脑扬声器输出 控制面板 播放 / 暂停 上下首 / 停止 计时器 Display 子电路(占位) 钢琴 / 计时显示 / 进度条 — 后期填充
FIG 2.1 · 三大块解耦:脚本管"曲谱",CPU 管"读谱",APU 管"发声"
03
Core Idea · 核心思路

最重要的两个想法

想法一:我们的 CPU 不需要真的运行 NES 的 6502 代码。 Python 脚本在电脑上离线把 NSF "跑一遍",录下它对音频芯片的所有寄存器写入, 变成一个简单的数据序列。Logisim 里的 CPU 只负责按时序回放这个序列就行。

这个区别非常关键,决定了整个项目的复杂度:

❌ 错误思路

在 Logisim 里完整实现一个 6502 CPU,加载真实的 NSF 文件, 让它像真 NES 一样运行 6502 程序。

→ 需要 56 条指令、bank switching、NMI 中断处理…
→ 工作量爆炸,作业要求只是"五段 CPU"

✅ 我们的思路

Python 脚本把 NSF 转成"等待 N 拍 → 写寄存器 R = V"这种超简单的指令流。 Logisim CPU 只需要 5-7 条自定义指令就能跑。

→ CPU 极简,几十个组件搞定
→ 五段流程清晰,验收时好讲

想法二:充分利用 Logisim Buzzer 已有的波形生成能力, 不自己搓波形。Buzzer 内置 square / triangle / noise 都能用, APU 部分只做"寄存器解析 + 频率换算 + 通道使能", 把工作量集中在真正有教学意义的地方(CPU 五段架构)。

关于 DMC 通道为什么砍掉

真实 2A03 有五个发声通道:Pulse×2、Triangle、Noise,以及 DMC(Delta Modulation Channel)。

DMC 不是发声器,而是1-bit DPCM 采样回放通道——它能播放预先录制好的任何声音: 鼓点、人声、采样乐器都行。NES 游戏里那些"语音喊叫"或"真实鼓声"都是用它做的。 但 NES 卡带空间极小,DMC 的采样率低、采样长度短,所以听起来很糙。

在 Logisim 里实现 DMC 需要:DPCM 解码器 + 独立的 ROM 访问通道 + 字节缓冲器, 工作量大约占整个 APU 的 30%,而且效果有限。

我们的决策:砍掉 DMC

转换脚本检测到 NSF 使用 DMC 寄存器($4010–$4013)时,直接跳过这些写入。 损失的是"鼓点和人声采样",保留的是"主旋律 + 和声 + 噪声鼓点"——大部分 NES 音乐听感损失有限。

关于 Triangle 通道为什么不搓 32 步阶梯

真实 2A03 的三角波是 32 步阶梯(4-bit 量化),听起来有"颗粒感",是 NES 经典音色之一。 很多教程会建议在 Logisim 里自己搓出这个阶梯。

但我们经过讨论决定不搓阶梯,直接用 Buzzer 的 triangle 波形,理由如下:

决策:Triangle/Noise 直接用 Buzzer 自带波形

代价:硬核 NES 玩家在低音区能听出"太干净了"。
收益:APU 工作量大幅压缩,时间投入到 CPU 和扩展功能上。
符合我们"不百分百还原也没关系,但不能割裂"的目标。

04
Buzzer Spec · 蜂鸣器规范

Logisim Buzzer 设计规范

这是整个系统的声音出口。我们的 APU 设计就是围绕 Buzzer 的能力展开的。

引脚定义

Logisim Evolution 的 Buzzer 组件(位于 I/O Extra 库)有以下输入引脚:

引脚 PIN
位宽
功能描述
Frequency
14-bit
无符号频率值,单位 Hz 或 dHz(属性可配)。
有效范围 20 Hz – 20,000 Hz
Volume
可配置
无符号音量值,位宽由属性 Volume Width 决定(常用 7-bit 或 8-bit)
Duty Cycle
8-bit
占空比,仅对方波有意义。0–255 映射到 0%–100%
Select (Enable)
1-bit
1 = 发声,0 = 静音。暂停/停止/通道禁用时拉低这一位

波形属性的选择

Square 方波

可配占空比。给 Pulse 1 + Pulse 2 用。

Triangle 三角波

数学三角波,比 2A03 阶梯更纯净。给 Triangle 用。

Noise 白噪声

Java Random 生成,与 2A03 LFSR 不同序列但听感都是噪声。给 Noise 用。

关键设计:4 路 Buzzer 并联

多通道并联自动混音

Logisim 的 Buzzer 输出到系统声卡,多个 Buzzer 同时响时,声卡会自动叠加它们的声音—— 这就是天然的混音器,不需要我们自己搭加法器。

所以我们的 APU 设计变得非常简洁:每个通道一个 Buzzer,4 个 Buzzer 并联,分别接收各自的频率/音量/使能信号

FIG. 4.1 · BUZZER ARRAY
APU 寄存器组 来自 CPU 的 MMIO 写入 $4000-$4003 P1 $4004-$4007 P2 $4008-$400B Tri $400C-$400F Noise $4015 通道使能 每组 = D 触发器 + 频率换算逻辑 + 通道使能门控 Buzzer 1 freq + duty + en ▸ Pulse 1 (square) waveform: square Buzzer 2 freq + duty + en ▸ Pulse 2 (square) waveform: square Buzzer 3 freq + en ▸ Triangle (triangle) waveform: triangle Buzzer 4 freq + en (随机基频) ▸ Noise (noise) waveform: noise 系统声卡 自动叠加四路声音 = 天然混音器 🎵 合成音乐
FIG 4.1 · 四路 Buzzer 并联,系统声卡自动混音——不需要自己搭加法器
05
Script · 转换脚本

NSF 转换脚本

用 Python 写,在电脑上运行,不进 Logisim。负责把 .nsf 文件转成 ROM 映像。

工作流程

.nsf 文件 输入 NSFPlay / VGM 工具 导出寄存器日志 寄存器日志 VGM / .reg 时间 + 写入对 Python 转换 编码 + 时钟缩放 DMC 跳过 ROM .txt Logisim v2.0 raw 每首一个文件

自定义指令格式

Opcode格式(字节)语义
0x0000 NN等待 NN 个时间单位(短等待,最长 255)
0x0101 RR VV写 APU 寄存器 RR = VV,暂时不带 KK 字段
0x0202 RR VV KK同上 + 附 MIDI 音符号 KK(可视化用,预留)
0x0303 NN NN长等待(16-bit 时间,最长 65535)
0xFFFF曲目结束 / 循环点
# ROM 内容示例(一段假想的曲子)
# 假设时间单位 = 1 个 CPU 周期
01 00 9F      # 写 Pulse1 控制 = 0x9F(音量+占空比)
01 02 AA      # 写 Pulse1 频率低 = 0xAA
01 03 00      # 写 Pulse1 频率高 = 0x00 → 触发发声
03 04 00      # 等待 1024 个周期(约 1/4 拍)
01 02 BB      # 改变频率
00 80         # 等待 128 个周期
...
FF            # 曲目结束,触发循环或下一首
时钟速度的坑(必须处理!)

Logisim 实际时钟跑得很慢(最快也就几 kHz),无法跑到 NES 的 1.79 MHz。 脚本里必须按比例缩放

时间缩放系数 = Logisim 实测频率 / NES_CPU_FREQ (1.79 MHz)
频率寄存器值 = 原值 × 时间缩放系数

时间值和频率值要同时缩放,否则会"节奏对了音高错"或反过来。 调试时先做"音高正确但慢放"版本验证电路,再调到合适速度。

06
CPU · 详细设计

CPU 详细设计

自研单周期五段 CPU。不用开源 6502——作业要求是"五段架构"而不是"能跑 NES 的 CPU", 自己搓的极简版更符合验收要求。

指令编码(16-bit 等长指令)

为了简化译码电路,统一用 16-bit 等长指令。高 4 位是 opcode,低 12 位是操作数。

FIG. 6.1 · INSTRUCTION ENCODING
bit: 15 14 13 12 11 0 WAIT 0000 (op) imm12 — 12-bit 等待计数(0-4095) WRITE 0001 (op) reg_id (4-bit) value (8-bit) LWAIT 0010 (op) unused imm_long (拼接下条指令为 16-bit) WKEY 0011 (op) reg(4) value (8) MIDI key (8) — 可视化 END 1111 (op) unused — 触发"曲目结束"信号
FIG 6.1 · 5 条指令足够回放任意 NES 音乐

五段数据通路(详细)

FIG. 6.2 · CPU DATAPATH
IF · 取指 ID · 译码 EX · 执行 MEM · 访存 WB · 回写 PC 12-bit 指令 ROM addr → instr PC + 1 instr opcode 提取 高 4 位 → ctrl 操作数拆分 reg / val / imm 控制信号生成 is_wait / is_write 等待计数器 12-bit 减法器 每周期 -1 地址生成 reg_id → $4000+id stall 信号 wait_cnt > 0? MMIO 地址译码 $4000-$400F → APU 寄存器 we → APU 寄存器组 这里"出声" END → 切歌信号 触发外部控制 PC 更新逻辑 stall? 保持 : PC+1 复位逻辑 END/切歌 → PC=0 等待寄存器写回 下次仍 stall ↑ 新 PC 反馈到取指阶段(下一个时钟周期) CLK · 系统时钟(受暂停信号门控)
FIG 6.2 · 五段数据通路,关键反馈:stall 让 PC 在等待期间不递增

关键设计点

WAIT 指令如何"占用多个周期"

问题:WAIT 指令需要让 CPU 停在原地等 N 个周期,怎么实现?
方案:EX 阶段维护一个 12-bit"等待计数器"寄存器。 WAIT 指令到 EX 时把计数器装载为 N,之后每个时钟 -1,直到归零才发出 stall=0。 WB 阶段看到 stall=1不更新 PC,IF 阶段于是反复取同一条 WAIT 指令—— 但因为计数器在递减,实际上是在"原地走时间"。

MMIO 地址译码(CPU↔APU 接口)

约定 APU 寄存器映射到地址 $4000–$400F(共 16 个 8-bit 寄存器,模仿真实 NES 布局)。
MEM 阶段拿到 reg_id 后,根据 opcode 判断是不是 WRITE 类指令:
apu_we[reg_id] = is_write_op && stage==MEM
APU 寄存器组就是 16 组 8-bit D 触发器,每组有自己的写使能。这是 CPU 跟 APU 之间唯一的通信。

为什么单周期而不是流水线?

真流水化需要处理"数据冒险"和"控制冒险",工作量翻倍。 作业只要求"有五个阶段"——单周期五段满足要求。组件数估计:CPU 整体约 60-80 个元件。

07
APU · 详细设计

APU 详细设计

APU 的工作是:解析 CPU 写入的寄存器值 → 算出频率/音量/使能 → 喂给对应的 Buzzer。 没有波形生成器,没有混音器——Buzzer 已经搞定了。

寄存器地址映射(对齐真实 NES)

地址归属bit 7..0用途
$4000Pulse 1DDLC NNNN占空比(2)/长度禁(1)/包络禁(1)/音量(4)
$4001Pulse 1EPPP NSSS扫频(我们忽略,写入但不用)
$4002Pulse 1TTTT TTTT频率低 8 位
$4003Pulse 1LLLL LTTT长度计数 + 频率高 3 位
$4004-7Pulse 2同 Pulse 1
$4008TriangleCRRR RRRR线性计数控制
$400ATriangleTTTT TTTT频率低 8 位
$400BTriangleLLLL LTTT长度计数 + 频率高 3 位
$400CNoise--LC NNNN长度禁 + 音量
$400ENoiseM--- PPPP模式 + 周期索引
$400FNoiseLLLL L---长度计数
$4015状态---D NT21通道使能(noise/triangle/pulse2/pulse1)

每个通道做什么(统一架构)

所有 4 个通道结构一致

每个通道都是这三件事:① 从寄存器读出 timer 值 → ② 用公式换算成 Hz → ③ 接到 Buzzer 的 Frequency 引脚。 加上音量、使能、占空比的接线。不写任何波形生成逻辑。

FIG. 7.1 · CHANNEL DATAPATH (UNIFIED)
通道寄存器 CPU 写入 timer (11-bit) 频率低+高位拼接 vol (4-bit) $4000 / $400C bit3-0 duty (2-bit) $4000 bit7-6 (Pulse 用) 频率换算 f_Hz = K / (timer+1) K = 缩放常数 实现:除法器 或 查找 ROM 2048 项 ROM 最简单 通道使能 ($4015) AND vol → 静音 → Freq → Vol / En → Duty (Pulse 用) Buzzer 组件 waveform 属性已设定 ▸ Pulse: square ▸ Triangle: triangle ▸ Noise: noise 直接生成对应波形 不用我们搓 🔊 声音
FIG 7.1 · 通道统一架构:寄存器 → 频率换算 → Buzzer。波形生成完全交给 Buzzer 组件

每个通道的特殊处理

Pulse 1 / Pulse 2 — 用 Buzzer 的 Duty Cycle

NES 的 4 种占空比(12.5%/25%/50%/75%)映射到 Buzzer 的 8-bit Duty Cycle 输入:
$4000 bit7-6 → MUX 选择 → {32, 64, 128, 192}
(Buzzer Duty 是 0-255 满量程,所以 NES 50% = Buzzer 128)
Pulse 通道 = 频率换算 + 占空比查表 + 音量。这是工作量最大的通道

Triangle — 最简单的通道

Buzzer 的 triangle 波形不接受 Duty Cycle 输入。
所以 Triangle 通道只需要:11-bit timer → 频率换算 → Buzzer.Frequency
音量?真实 2A03 的 Triangle 通道没有音量寄存器——它只有"响"或"不响"两态。 我们也照做:Buzzer.Volume 接一个固定值(比如最大值),用 $4015 使能位控制响不响。
整个 Triangle 通道大约 10 个组件就够了。

Noise — 周期索引查表

Noise 通道的频率不是 11-bit timer,而是 $400E bit3-0 这 4 位作为周期表索引。 用一个 16 项的 ROM 查找表把索引映射到频率:

# NES Noise 周期表(NTSC,单位 CPU 周期)
4, 8, 16, 32, 64, 96, 128, 160,
202, 254, 380, 508, 762, 1016, 2034, 4068
# 缩放后写入查找 ROM,输出接 Buzzer.Frequency

Buzzer 的 noise 波形是 Java Random,不是真 NES 的 LFSR—— 但听感上都是噪声,对作业来说够用了。

$4015 通道使能

$4015 寄存器的低 4 位分别控制 4 个通道的使能。 每个通道把这一位 AND 到自己的 Buzzer.Select 引脚上。
切歌/停止时把 $4015 写成 0 就能瞬间静音所有通道

混音器?不需要!

4 个 Buzzer 都连到 Logisim 仿真器的音频输出,系统声卡自动叠加它们的声音。 这就是天然的线性混音——不需要我们自己搭加法器。
这是把工作量交给 Buzzer 组件最大的好处。

08
UX · 控制 & 计时

控制面板 & 计时器

这部分是纯逻辑,不经过 CPU。用按钮 + 几个触发器搞定。

按钮输入 ▶ 播放 ⏸ 暂停 ⏹ 停止 ⏮ 上一首 ⏭ 下一首 暂停标志 D-FF 控制 CPU 时钟使能 暂停 = 冻结状态 CPU 复位脉冲 停止时清零 PC 写 $4015 = 0 静音 曲目计数器 4-bit 上下计数 选 ROM (MUX) 计时器 1 Hz 慢时钟 秒 (0-59) → 分 切歌/停止时复位 MM:SS 时长上限比较 ≥ 5 分钟? → 边沿检测 → 自动下一首 → 显示
为什么用"时长上限"而不是真实曲长?

NSF 格式本身没有曲长字段——它是 6502 程序,理论上能无限循环。 连 NSFPlay 这种大软件也是用"默认播放 N 分钟然后淡出"处理的。
所以我们也这么做:用户可以设置上限(DIP 开关 4/5/8 分钟),到了自动切下一首。 ROM 里只存一个完整循环,硬件自动 PC 清零无缝循环播放。

09
Extension · 可视化

可视化扩展位(预留)

为后期想加的钢琴键盘可视化计时条提前预留接口。 现阶段建一个空的 Display 子电路占位,主电路布线一次到位。

预留信号接口

信号名位宽来源用途
elapsed_min8-bit计时器分钟显示
elapsed_sec6-bit计时器秒数显示
limit_min4-bitDIP 开关用户设定上限
track_id4-bit曲目计数器当前曲目编号
freq_ch1..411-bit ×4APU 寄存器音高 → 钢琴键
vol_ch1..44-bit ×4APU 寄存器判断是否在响
midi_key_ch1..47-bit ×4WKEY 指令直接读 MIDI 音符(脚本预编码)
建议:在 Display 子电路里加帧采样器

可视化更新频率不需要和 CPU 时钟同步(人眼 30 Hz 就够)。 子电路入口加一组采样寄存器 + 30 Hz 慢时钟,CPU 跑得再快也只采样到稳定帧。 这相当于给显示加了"帧缓冲",能避免画面抖动且节省组件资源。

10
Team · 四人分工

四人分工方案

四个角色,组长负责统筹和补位,其他三人各主攻一块。 三大块工作可并行推进,前期不互相阻塞。

LEAD
ROLE A · Team Lead
组长 / 系统集成

统筹三方进度、维护接口约定、负责系统联调。技术上每块都参与一点,是最了解全局的人。

  • 制定并维护 ROM 格式、MMIO 地址映射、寄存器位定义文档
  • 搭建顶层电路框架(连接 CPU/APU/控制面板/Display 子电路)
  • 负责控制面板和计时器整块(这部分相对独立)
  • 负责系统联调,三方接口出问题时第一个跳出来
  • 遇到队友卡住时补位帮忙——哪里弱往哪里去
  • 准备验收 demo 和答辩材料
ROLE B · Software
转换脚本组

负责 NSF → ROM 映像的离线工具链。纯软件工作,前期可独立推进,不依赖 Logisim 部分。

  • 调研 NSFPlay / VGM 工具的日志导出
  • 用 Python 解析寄存器写入序列
  • 实现时钟/频率缩放算法(关键难点)
  • 检测 DMC 写入并跳过
  • 编码成自定义指令格式输出 .txt
  • 批量处理多首曲库 + 检测循环点
  • 预留 MIDI 音符号字段(可视化用)
ROLE C · CPU
CPU 设计组

负责 Logisim 里的五段 CPU。验收时这块最受关注,要做扎实。

  • 设计自定义指令集(5 条 + END)
  • 实现 IF/ID/EX/MEM/WB 五个阶段(单周期)
  • WAIT 指令的 stall 机制(关键难点)
  • MMIO 地址译码(CPU→APU 接口)
  • PC 控制(顺序 +1 / 复位 / 保持)
  • END 信号 + 切歌响应
  • 跟组长一起准备验收答辩
ROLE D · APU
APU 设计组

负责音频合成 + 输出。这块最有"出活"的成就感——一旦做完就能听到声音。

  • 设计寄存器组结构(16 组 D 触发器 + 写使能)
  • 实现频率换算逻辑(timer → Hz)
  • Pulse 通道占空比 MUX
  • Noise 通道周期索引查找表
  • $4015 通道使能门控
  • 4 路 Buzzer 接线 + 波形属性配置
  • 调试不同 NES 曲目的音色效果

四方接口约定(必须提前对齐!)

接口文档由组长(A)维护,三方共同遵守
  • A ↔ B:ROM 格式约定(指令编码表,见第 5 节)。脚本输出 = CPU 输入。
  • A ↔ C:CPU 提供的对外信号(PC、END 信号、stall 状态)。
  • A ↔ D:APU 寄存器组的写使能接口。$4015 通道使能位定义。
  • C ↔ D:MMIO 地址映射。CPU 写 $4000–$400F 落到 APU 哪个寄存器。
  • B ↔ D:APU 寄存器位定义跟 nesdev wiki 走,三方共同参考同一份文档。
第一周建议先做的事

所有人:通读 nesdev wiki 的 APU 章节
A:起草接口文档第一版,开 git 仓库
B:搭 Python 环境,跑通 NSFPlay 导出日志
C:在 Logisim 里搭一个最简单的"PC + ROM + 译码"看效果
D:在 Logisim 里玩 Buzzer 组件,测试不同 waveform 属性下的声音
一周后碰一次,对齐接口、调整方案。

11
Milestone · 里程碑规划

30 天到验收

今天是 5 月 16 日(周六),验收 6 月 15 日(周日),共 30 天。 划分成 4 个周期,每个周期有明确的 demo 标准——不是"做完了",而是"能演示什么、能听到什么"。 最后留 3 天 buffer 应对 bug 和验收准备。

甘特图概览

任务 / 负责人
5/16 ←————————————————————————————————→ 6/15
通读文档 · 全员
调研
接口文档 · A
起草 + 维护
脚本工具链 · B
脚本核心
CPU 五段 · C
CPU 实现
APU 通道 · D
4 通道
控制面板 · A
面板 + 计时
系统联调 · 全员
CPU+APU+面板集成
真实 NSF 测试 · D+B
音色调参
可视化扩展 · 选做
钢琴/进度条
验收准备 · 全员
Buffer + 答辩
M1
17
18
19
20
21
M2
23
24
25
26
27
28
29
30
31
M3
2
3
4
5
6
7
M4
9
10
11
12
13
14

4 个里程碑详细

M1 调研 & 接口确定
5/16 — 5/22 · 第 1 周
本周完成
  • 全员:通读 nesdev wiki APU 章节、Logisim Evolution 用户手册
  • A:建 git 仓库,起草接口文档(指令编码、MMIO 地址表、寄存器位定义)
  • B:调研 NSFPlay / VGM 工具,能从一个 .nsf 文件 dump 出寄存器写入日志
  • C:在 Logisim 里搭"PC + ROM"原型(PC 自动递增,从 ROM 取出指令字显示在 LED)
  • D:在 Logisim 里手动调 Buzzer 属性,测试 square / triangle / noise 三种波形听感
M1 Demo 标准(5/22 晚碰头)
能展示:① B 演示一个 .nsf 文件被 dump 成寄存器写入文本日志; ② C 演示一个 PC 在 ROM 上自动跑、LED 显示当前指令; ③ D 演示 Buzzer 接固定值发出 square/triangle/noise 三种声音; ④ A 提交一份所有人都看过且认可的接口文档第一版。
M2 核心模块独立完成
5/23 — 6/1 · 第 2-3 周
10 天内完成
  • B:脚本能输出符合 v1 指令格式的 ROM 映像 .txt;时钟/频率缩放算法跑通;DMC 跳过逻辑完成
  • C:CPU 五段全部完成(IF/ID/EX/MEM/WB);WAIT 指令 stall 机制正确;END 信号能输出
  • D:4 个 APU 通道全部独立可发声(手动写寄存器值能听到对应通道响);$4015 通道使能正确
  • A:跟进各组进度,发现接口偏差时及时召集对齐
M2 Demo 标准(6/1 晚碰头)
能展示:① B 把一首真实 NSF 转成 ROM 映像(即便没法运行,文件本身格式正确); ② C 用手工写的测试 ROM 跑 CPU,PC 按预期递增、WAIT 能正确停 N 个周期、END 触发复位; ③ D 手动写 4 个通道的寄存器,能同时听到 4 个 Buzzer 发声(音高音量各自独立); ④ 全员确认接口对齐没有偏差。
⚠ 风险预警
  • 这是最长的一个里程碑(10 天),中间容易松懈。5/27 设一个 mini-check:各组同步进度,未达 50% 的紧急对齐
  • 如果 C 卡在 WAIT 指令实现,A 立即介入帮忙——这是验收最重要的部分
M3 联调成功 · 第一次出声
6/2 — 6/8 · 第 4 周
本周完成
  • 把 CPU 和 APU 通过 MMIO 接起来(A 主导,C 和 D 配合)
  • 用 B 生成的测试曲(先用手写音阶,再用真实 NSF)跑通完整链路
  • A 完成控制面板:播放/暂停/停止/上下首按钮 + MM:SS 计时器 + 自动切歌
  • 调整时钟缩放参数,找到"速度和音高都能接受"的折中点
  • 主电路里建空的 Display 子电路占位(接口预留好)
M3 Demo 标准(6/8 晚)
能展示:① 整个系统跑起来——点"播放"按钮,Logisim 输出真实可识别的 NES 音乐; ② 演示暂停/继续/停止/上下首功能; ③ 计时器 MM:SS 正常走动,到上限自动切下一首; ④ 至少能成功播放 3 首不同的 NSF 曲目(证明不是"针对单首歌调出来的")。
这是项目的"能交差"节点
  • 到 M3 即便后面全员摆烂,也能保证拿一个能交的作业去验收
  • 剩下 7 天就是优化和加分项,心态会从容很多
M4 打磨 · 扩展 · 验收准备
6/9 — 6/15 · 最后一周
本周完成
  • 批量处理 NSF 曲库——准备 8-16 首歌烧进多个 ROM
  • 音色调优(调时钟缩放、频率换算精度、音量比例)
  • 选做:填充 Display 子电路,加钢琴键盘可视化 + 计时条
  • 整理 git 仓库结构、写 README、注释关键电路
  • 准备验收 PPT / Demo 流程脚本
  • 预演 1-2 次答辩,互相提问检查薄弱点
最后 3 天(6/13-6/15)严格 buffer
  • 不再写新功能,只修 bug 和润色
  • 把 .circ 文件备份多份,避免 Logisim 损坏文件的悲剧
  • 提前准备好"如果 X 坏了用 Y 演示"的 Plan B
M4 Demo 标准(6/15 验收)
验收当天演示:① 五段 CPU 数据通路讲解清晰,能回答"WAIT 指令怎么 stall 的"; ② 演示 ≥ 5 首不同 NSF 音乐播放;③ 控制面板所有功能正常; ④ 能解释为什么砍 DMC、为什么 Triangle 用平滑波形——展示"做了工程取舍"而不是"做不出来"。

每周固定碰头

建议每周日晚 9 点(30 分钟)

不要拖到最后才发现问题。每周日固定开会,每人说三件事:
1) 这周做完了什么(拿 demo 出来)
2) 下周准备做什么
3) 卡住的地方 / 需要别人配合的事
开会时长不超过 30 分钟。超时说明问题太多,应该拆成 1v1 单独解决。

12
Reference · 参考资料

官方文档 & 参考链接

遇到具体组件不会用、属性不知道怎么配、寄存器位定义记不清的时候——看官方文档,不要凭印象。

Logisim Evolution 官方资源

组件库手册(按角色分)

遇到具体组件不会用时直接查这些页面:

A · 组长 / 控制面板 / 顶层集成

B · 转换脚本组

C · CPU 组

D · APU 组

额外的参考

在 Logisim 里查文档的小技巧

Logisim Evolution 内置帮助系统:选中任意组件 → 右键 → Show Component Help → 直接打开该组件的文档页(其实就是 mbaillif 那个站的镜像)。不用搜索,最快