# JinNes **Repository Path**: chujin_w/jin-nes ## Basic Information - **Project Name**: JinNes - **Description**: 用Rust构建nes模拟器 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2025-04-11 - **Last Updated**: 2026-01-09 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## NES 模拟器开发 use rust ### CPU #### CPU的引脚 6502的真实引脚如下图: ``` text .--\/--. AD1 <- |01 40| -- +5V AD2 <- |02 39| -> OUT0 /RST -> |03 38| -> OUT1 A00 <- |04 37| -> OUT2 A01 <- |05 36| -> /OE1 A02 <- |06 35| -> /OE2 A03 <- |07 34| -> R/W A04 <- |08 33| <- /NMI A05 <- |09 32| <- /IRQ A06 <- |10 31| -> M2 A07 <- |11 30| <- TST (usually GND) A08 <- |12 29| <- CLK A09 <- |13 28| <> D0 A10 <- |14 27| <> D1 A11 <- |15 26| <> D2 A12 <- |16 25| <> D3 A13 <- |17 24| <> D4 A14 <- |18 23| <> D5 A15 <- |19 22| <> D6 GND -- |20 21| <> D7 `------' ``` 抽象话功能图为: ``` text ┌─────────────────────┐ │ │ ────reset────►│ ├──addr───► 16 bit │ cpu 6502 │ ────nmi──────►│ │ │ │◄─data───► 8 bit ────irq──────►│ │ │ │ ────clock────►│ ├──r/w────► │ │ └─────────────────────┘ ``` 我们需要实现的接口: - 地址,数据: 简单的读入和输出数据(fetch,dispatch)从总线读取,需考虑bus阻塞 - 时钟: 接入时钟脉冲 - irq/brk: 可屏蔽中断,和软中断 - reset: cpu跳转reset中断向量指向地址 - nmi: 不可屏蔽中断。nes中 屏幕刷新时(VBlank)时发生,NTSC-- 60次/秒,PAL -- 50次/秒 产生中断时,CPU 会将 PC 和 P 压栈,之后 CPU 读取中断向量表对应地址,赋给 PC,同时设置相应的标志位。当程序执行 RTI(中断返回) 后,CPU 将 P 和 PC 出栈,恢复 P 和 PC,从中断产生前的地址处继续执行 模拟引脚定义 ``` rust pub trait Bus { fn peek(&self, addr: u16) -> u8; fn poke(&mut self, addr: u16, value: u8); } pub trait Interface { // interrupt fn reset(&mut self); fn irq(&mut self); fn nmi(&mut self); } ``` #### 中断向量表: 中断向量表位于 0xFFFA ~ 0xFFFF,共 6 字节,分别对应 3 个中断: - 0xFFFA, 0xFFFB: NMI 中断地址 - 0xFFFC, 0xFFFD: RESET 中断地址 - 0xFFFE, 0xFFFF: IRQ 中断地址 #### 内部寄存器 6502有6个寄存器: A, X, Y, PC, SP, P,除了PC为16bits以外,其他均为8bits - A: 累加器 - X, Y: 循环计数器 - PC: 程序计数器,记录下一条指令地址 - SP: 堆栈寄存器,堆栈地址为0x100~0x1ff,即起始地址为0x0100 | SP - P: 状态寄存器 | BIT | 名称 | 含义 | | --- | --- | ------------------------ | | 0 | C | 进位标志,如果计算结果产生进位,则置 1 | | 1 | Z | 零标志,如果结算结果为 0,则置 1 | | 2 | I | 中断去使能标志,置 1 则可屏蔽掉 IRQ 中断 | | 3 | D | 十进制模式,未使用 | | 4 | B | BRK,后面解释 | | 5 | U | 未使用,后面解释 | | 6 | V | 溢出标志,如果结算结果产生了溢出,则置 1 | | 7 | N | 负标志,如果计算结果为负,则置 1 | B 和 U 并不是实际位,只不过某些指令执行后,标志位 push 到 stack 的时候,会附加上这两位以区分中断是由 BRK 触发还是 IRQ 触发,下面是详细解释 | 指令或中断 | U 和 B 的值 | push 之后对 P 的影响 | | ------ | -------- | -------------- | | PHP 指令 | 11 | 无 | | BRK 指令 | 11 | I 置 1 | | IRQ 中断 | 10 | I 置 1 | | MNI 中断 | 10 | I 置 1 | 寄存器实现 ``` rust struct chip6502 { reg_acc: u8, reg_x: u8, reg_y: u8, stack_pointer: u8, program_counter: u18, process_status: u8, } ``` #### 指令集 指令集参考地址 - https://www.oxyron.de/html/opcodes02.html - https://www.nesdev.org/obelisk-6502-guide/reference.html 指令编程注意 - 周期改变指令 - 分支指令 -> +1 or +2 - 中断指令,reset | nmi -> 8; irq -> 7 - abs x, abs y, izy 发生cross page -> cycles + 1 ### Tips - 对开发有用的测试文件 - 下载地址 https://www.qmtpro.com/~nes/misc/ - 下载文件名: nestest.log 和 nestest.nes - 设置PC为0xc000,再对照log - Bug - 间接寻址时的 BUG - 地址无法跨越Page - 执行Jmp(0x02ff)时,高位地址会从0x200读取,而非0x300,跳转至0x0103 ``` text Address: 0x0200 0x0201 ... 0x02FF 0x0300 Value: 0x01 0x02 ... 0x03 0x03 ``` #### refence - https://bugzmanov.github.io/nes_ebook/chapter_1.html