Commodore 64 模拟器






4.99/5 (107投票s)
用 C# 编写的 Commodore 64 模拟器的另一个实现
背景
本文详细介绍了用 C# 编写的 Commodore 64 模拟器的实现。
由于多种原因,例如基于周期的实时驱动器模拟以及源代码中某些事物的实现方式,性能并不理想。我出于其他目的用 C++ 重新实现了该模拟器,实现后性能有了显著提升。鉴于这些结果,我放弃了 C# 实现。由于不以某种方式利用现有代码是时间和精力的浪费,我决定撰写一篇关于该主题的文章。
不幸的是,有些内容仍不完整、缺失或实现不正确,但它仍然是解释模拟基本概念的良好基础。
在本文中,我不会试图解释实际硬件的工作原理。有大量出色的资源涵盖这些主题比我做得更好。相反,我将把注意力集中在模拟上——应该做什么以及在此实现中是如何做的。
在尝试构建解决方案或运行模拟器之前,您应该阅读有关 ROM 文件的部分。
引言
Commodore 64 对程序员可见并可使用的部分是
- 6510 芯片 - CPU(与 6502 芯片相同,但多一个 IO 端口)
- VIC-II 芯片 - 负责图形
- SID 芯片 - 负责音频
- 2xCIA 芯片 - 负责定时器和 IO(例如串行总线、键盘、操纵杆...)
- 彩色 RAM - VIC-II 芯片用于生成显示图形颜色的 4 位内存
- RAM 芯片 - 可用于编程的 64 千字节
- 3xROM 芯片 - 存储 Basic 解释器、OS (KERNAL) 和 VIC-II 芯片使用的字符生成器
此实现涵盖了运行游戏所需的大部分硬件。缺少的主要功能是 SID 芯片模拟。REU 和磁带模拟也不可用。使用适当的工具,应该可以将 T64 格式的游戏转换为 D64(磁盘)格式,因此这些游戏也应该可以玩。
模拟器是基于周期的,这允许完美模拟芯片时序和系统内不同芯片之间的同步。它还实现了 1541-II 驱动器的实时驱动器模拟。所有这些技术都提供了更好的软件兼容性,但它们也带来了性能成本。
Commodore 64 方框图
项目结构
模拟代码分离在多个程序集中,因此 CPU、内存和 IO 端口模拟等一些功能可以用于其他项目,例如 VIC、PET 或 NES 模拟器。
c64_common
- 时钟、内存、IO 端口和中断线模拟c64_cpu
- 6502 和 6510 模拟c64_av
- VIC-II 和 SID 芯片模拟c64_io
- CIA 芯片和 IEC(串行)总线模拟c64_system
- 连接所有模拟硬件以及键盘/操纵杆模拟的 C64 主板c64_1541
- 1541-II 驱动器模拟c64_environment
- 模拟器与外部世界交互(例如输出图形和音频、读取文件...)的接口定义c64_win_gdi
- 实现 GUI 并将所有内容连接在一起(C64 主板、1541-II 驱动器和环境)
时钟
系统中的一切都由 CPU 时钟同步。时钟周期分为多个阶段。这些阶段定义了每个周期中芯片模拟的顺序,每个芯片占用周期的一个阶段。在每个阶段,模拟器执行为芯片安排的单个时钟操作。一个周期中可以有任意数量的阶段,具体取决于所模拟的芯片数量。
系统时钟方框图
ClockOp
定义了时钟操作的接口,其中 Execute
应包含在时钟周期中执行的逻辑。
Clock
类表示系统的时钟,它保留了每个阶段应执行的时钟操作队列。Clock
不直接使用 ClockOp
,而是通过 ClockEntry
和 ClockEntryRep
类。这些类为 ClockOp
提供了额外的上下文,例如下一个应该执行的操作是什么,以及该指令是否应该在同一周期中执行。ClockEntryRep
类用于需要多个周期才能完成的操作,并包含有关其长度的信息 - 执行所需的周期数。
QueueOpsStart
和 QueueOps
方法用于为时钟的指定阶段排队操作。QueueOpsStart
方法用于模拟启动后首次排队操作,对于每个后续请求应使用 QueueOps
。对于大多数芯片,在开始时调用一次 QueueOpsStart
,并在循环列表中指定所有时钟操作通常就足够了。只有 CPU 使用 QueueOps
方法在解码指令后排队要执行的操作。
Stall
方法可用于暂停某个阶段中排队操作的执行(即当 VIC-II 芯片访问 RAM 时暂停 CPU),或者如果需要额外周期才能完成操作(即执行导致跨页跳转的分支指令)则使用 Prolong
。
一旦一切准备就绪可以启动/恢复模拟,就应该调用 Clock
的 Run
方法。Halt
方法可用于停止时钟中排队操作的执行。这用于在帧完成后和渲染新帧之前暂停模拟,或者在驱动器不活动时停止驱动器模拟以节省资源。
当前周期的最后一个阶段完成后,会触发 OnPhaseEnd
事件。此事件可用于链接其他主板(如 1541-II 驱动器)的额外时钟。
时钟模拟类图
内存模型
本章将描述内存总线、ROM 和 RAM 的模拟是如何实现的。它还将解释什么是内存映射设备以及在此实现中内存映射是如何工作的。
内存映射设备
每个可以通过内存总线访问的设备都是内存映射设备(包括 RAM 和 ROM)。它们映射到由起始地址和大小(内存位置数)指定的某个地址范围。
MemoryMappedDevice
类是所有内存映射设备的基类。这些设备应实现并支持 Read
和 Write
方法,当 CPU 或其他芯片使用内存总线读取或写入设备映射到的地址内容时,会调用这些方法。
内存映射
内存映射表示一种内存配置。此配置定义了哪些设备映射到何处以及它们在系统地址空间中的映射位置。需要注意的重要一点是,即使使用相同的内存映射,对于相同的内存位置,写入操作也可能触发与读取操作不同的芯片。由于此要求,此实现有两个单独的映射,一个用于写入,一个用于读取。
内存映射方框图
Commodore 64 可以有不同的内存映射,程序员可以选择。为了实现这一点,模拟器将创建多个内存映射,这些映射已针对每种可能的配置进行了预配置。
每个位置由 MemoryMapEntry
类表示,它有两个条目——用于写入的设备和用于读取的设备。
MemoryMap
类处理内存映射,它只是一个内存映射条目数组。它具有 Map
和 Unmap
方法,可以将设备映射到指定的地址空间并从中移除。另一对重要方法是 Read
和 Write
。这些方法将找到在指定位置针对请求的内存操作映射了哪个设备,并调用设备上的相应方法。
内存模拟类图
RAM
RAM 模拟非常简单。内存只是一个字节数组,可以由 CPU 或其他芯片(如 VIC-II)读取或写入。
RAM
类实现了 RAM 的模拟。由于 RAM 显然映射到地址空间中,因此该类基于 MemoryMappedDevice
类。Read
和 Write
方法非常直接,并按它们所说的做——读取或写入内存位置。
ROM
ROM 模拟甚至更简单,实现只提供了从预定义文件加载字节的读取。
由于 Commodore 64 中内存总线的硬件实现,对当前映射到 ROM 的位置进行存储将导致将值写入这些位置的 RAM。此功能由模拟器的内存映射而不是 ROM 模拟实现,ROM 模拟只是忽略写入。
ROM 模拟由 ROM
类实现,该类类似于 RAM
类。它还提供了一个额外的方法 Patch
,允许更改 ROM 的内容,这对于跳过固件执行的各种检查很有用。
彩色 RAM
Commodore 64 具有额外的 RAM,专门用于存储当前显示的所有字符的颜色,并由 VIC-II 芯片用于生成视频输出。VIC-II 仅使用每个字节的低 4 位。
彩色 RAM 的模拟在 ColorRAM
类中实现,它基于 RAM
类。
隐藏
在某些情况下,单个设备可以映射到多个位置,但当前实现不支持此功能。此外,芯片通常映射到比其寄存器大得多的空间。例如,VIC-II 地址大小为 1024 字节,但它只有 64 个寄存器。在这些情况下,由芯片决定如何处理超出可用寄存器范围的读/写,但通常只有地址的前几位是有效的,其他位被假定为 0。这使得内存映射看起来这些 64 个寄存器在分配的地址空间中被多次映射,以填充整个 1024 字节的地址空间。
未映射的地址空间
某些内存配置允许地址范围没有映射任何设备。在实际硬件中,对这些位置的写入将被忽略,读取将返回预定义的值。在此实现中,将发生异常,这可能会导致某些现有软件出现问题。
IO 端口
IO 端口存在于多个芯片中:VIA、CIA 和 6510 CPU。端口上的引脚可以是输入或输出。引脚的方向可以通过编程控制。这些端口用于连接未直接连接到内存总线的硬件——它们不是内存映射的。这些是 Commodore 64 中的串行总线、键盘和操纵杆,或 1541-II 磁盘驱动器中的驱动头控制器等设备...
控制 IO 端口的逻辑由 IOPort
类提供,因为它对所有芯片都相同。
属性 Input
、Output
和 Direction
提供了对 IO 端口引脚的访问,并允许控制它们的方向和状态。这些属性将所有引脚的状态设置为指定值,这并非总是理想的。SetSingleInputFast
方法可用于需要更改单个输入引脚状态的情况。
除了可以读取或设置引脚状态或更改其方向的方法外,此类别还会在输出引脚状态更改时公开事件 - OnPortOut
。
这些端口如何被控制并暴露给系统其余部分取决于它们所属的芯片逻辑。
IOPort 类
CPU
Commodore 64 计算机由 MOS6510 CPU 供电。此版本的 CPU 与 MOS6502 芯片完全相同,但带有一个额外的通用 IO 端口,该端口映射到地址空间。此 IO 端口用于控制磁带驱动器和系统的内存配置。
模拟器中仅实现已记录的操作码。执行非法操作码(MOS 未记录)将导致模拟器崩溃。
MOS6502
类连接 CPU 模拟的所有单个组件。MOS6510
类扩展了 MOS6502
,并且它只实现了该芯片上可用的附加 IO 端口的模拟。
CPU 寄存器
6502 有一组 6 个对程序员可见的寄存器。这组寄存器由 CPUState
类表示。
每个寄存器都实现了 IRegister
泛型接口,其中接口的基础类型取决于寄存器的大小。
累加器 (A) 和索引 (X 和 Y) 寄存器由 GpRegister
类实现。堆栈寄存器和程序计数器分别由 StackRegister
和 ProgramCounter
类实现。
处理器状态寄存器由 StatusRegister
类实现,其中每个状态标志都作为属性公开。
CPU 寄存器类图
解码
获取和解码操作码是指令执行的第一步。在 6502 CPU 的情况下,所有操作码都是一个字节长,足以解码整个指令——这样我们就知道它是哪条指令以及它使用哪种寻址模式。
解码过程使用预先创建的查找表,该表将操作码映射到指示应调用哪种寻址模式和应执行哪条指令以及它们的时序(寻址、执行和存储结果所需的周期数)的结构。该表由 DecodingTable
类实现。
解码器将根据从解码表获得的信息创建时钟操作列表,并将其排队等待执行。
解码器本身作为时钟操作(DecodeOpcodeOp
类)实现,并附加到每个解码指令的时钟操作列表的末尾。
寻址
指令解码后的下一步是确定操作数。操作数指定指令的源和/或目的地。
6502/6510 CPU 有几种寻址模式,这里不作讨论,因为有更好的相关资料,包括官方数据手册。
每种寻址模式都实现为一个单独的类,该类实现了 AddressingMode
接口。唯一定义的方法是 Decode
,它负责根据处理器当前状态计算目标地址。
寻址模式类图
地址解码过程将根据寻址创建和设置 CPU 的活动目标。目标可以是可写的或只读的;它可以是内存或寄存器。目标由 AddressedTarget
接口定义,它应实现用于检索和存储目标位置值的逻辑,并且每种寻址模式都有其自己的类型。
ReadableTarget
类由仅需要读取内存位置的寻址模式使用,而 WritableTarget
类由也需要将值存储到目标位置的寻址模式使用。IndirectTarget
由不直接引用目标而是通过另一个中间内存位置(存储实际目标位置)引用目标的寻址模式使用。
还有针对某些寻址模式的特定目标,例如累加器寻址,它以 CPU 的累加器寄存器为目标,以及立即寻址,它指定目标是解码指令的一部分。
需要注意的是,某些寻址模式需要从程序计数器引用的内存中获取额外的字节来解码目标位置,并且每次获取都会导致程序计数器移动到下一个地址。
寻址目标类图
寻址模式 | 寻址类名 | 目标类名 |
累加器 | AccAddressing |
特定目标(WritableTarget 为基类) |
立即数 | ImmAddressing |
特定目标(ReadableTarget 为基类) |
绝对寻址 | AbsAddressing |
WritableTarget |
零页寻址 | ZeroAddressing |
WritableTarget |
索引零页寻址(使用 X 寄存器) | ZeroXAddressing |
WritableTarget |
索引零页寻址(使用 Y 寄存器) | ZeroYAddressing |
WritableTarget |
索引绝对寻址(使用 X 寄存器) | AbsXAddressing |
WritableTarget |
索引绝对寻址(使用 Y 寄存器) | AbsYAddressing |
WritableTarget |
隐含寻址 | ImpAddressing |
none |
相对寻址 | RelAddressing |
WritableTarget |
索引间接寻址 | IndAddressing |
IndirectTarget |
绝对间接寻址(使用 X 寄存器) | IndXAddressing |
IndirectTarget |
绝对间接寻址(使用 X 寄存器) | IndYAddressing |
IndirectTarget |
地址解码被封装在 DecodeAddressOp
时钟操作类中,以便它可以排队执行。
每种寻址模式的工作原理在 6502 的数据手册中都有描述。
说明
每个表示指令的类都需要实现 Instruction
接口,该接口公开 Execute
方法。这是负责执行指令逻辑的方法。
指令的执行阶段封装在 DecodeAddressOp
类中,该类表示可排队的时钟操作。此包装器向 Execute
方法提供了所有必要信息,例如执行指令的 CPU、指令所需的当前周期(对于需要多个周期的指令)等等。
中断
6502/6510 CPU 有两条中断线——一条是可屏蔽的(IRQ),一条是不可屏蔽的(NMI)。IRQ 线是电平触发的——这意味着 CPU 每次电平升高时都会检测到中断,而 NMI 线是边沿触发的——这意味着 CPU 只有在电平从低到高变化时才会识别。
中断线模拟通过 IrqLine
和 NmiLine
类实现。不幸的是,NmiLine
未能正确处理多个中断源。
CPU 是这些线路的所有者,但连接到它们的芯片可以获取引用并使用它们来升高或降低线路的电平。
负责指令解码(DecodeOpcodeOp
)的时钟操作在获取下一条指令之前检查中断线。如果任何一条线处于活动状态,它将把负责中断处理的时钟操作排队。在可屏蔽中断(IRQ)的情况下,此操作将在开始中断处理之前检查进程状态寄存器。
中断处理时钟操作由 InterruptOp
类实现。
CPU 不同时钟操作的类图
6510 IO 端口
6510 的唯一新增功能是 IO 端口。该端口始终映射到地址 $0000
(引脚方向寄存器)和 $0001
(引脚状态寄存器)。每个引脚的精确功能在参考文献中提供。
MOS 6502/6510 仿真类图
Commodore 64
以下部分讨论了 Commodore 64 主板上存在的单个芯片和硬件组件的仿真。
VIC-II 芯片
此芯片负责在 Commodore 64 中生成图形。虽然没有 VIC-II 芯片的官方数据手册,但有一篇详细描述该芯片内部工作原理的文章,它是逆向工程的产物。文章的链接在参考文献部分提供。读者最好在深入了解实际实现的细节之前熟悉该主题。
VIC-II 仿真由 VIC
类实现。由于芯片映射在地址空间中并通过内存总线访问,因此 VIC
类必须扩展 MemoryMappedDevice
。Read
和 Write
方法负责检索芯片寄存器的状态并更新它们。
RasterLine
类负责管理芯片在每个周期中执行的定义良好的操作序列。
每个时钟周期分为两个步骤——内存访问操作和图形生成操作。有些周期同时定义了两种操作,有些只有一种或没有。为了涵盖所有情况,VIC-II 芯片有几种时钟操作实现
VicNop
- 整个芯片空闲的周期VicGraphOp
- 只有图形生成器处于活动状态,但芯片不需要内存访问的周期VicReadOp
- 图形生成器空闲,但芯片需要内存访问的周期VicGraphReadOp
- 需要内存访问且图形生成器处于活动状态的周期VicReadOp62
-VicReadOp
的特殊情况,仅用于栅格线的最后一个周期
光栅线有两个预定义的时钟操作列表。一个列表表示在图形输出处于活动状态时在光栅线中执行的操作,另一个列表包含在图形输出空闲时(在渲染边框期间或被程序员以编程方式禁用时)由 VIC-II 芯片执行的操作。
光栅线仿真类图
如前所述,每个周期都有严格定义的一组操作。每个周期用于内存访问和图形生成操作的逻辑都作为 RasterLine
类的不同方法实现。这些方法负责更新芯片状态、执行内存读取和调用活动图形模式以生成图形。
图形模式负责生成除精灵图形外的所有图形输出。每种图形模式都有自己的类,该类实现了 GraphiceMode
接口。接口的唯一方法是渲染,它将根据芯片的当前状态生成当前位置的像素。
VIC-II 图形模式类图
RasterLine
类还负责生成精灵的图形输出。
当芯片需要将像素输出到屏幕时,会调用 IVideoOutput
接口的 Output
方法。
VIC-II 对象还保留碰撞矩阵,以实现芯片的碰撞检测功能。该矩阵存储每个像素的信息,并定义该像素是否可能与其他精灵发生碰撞。此信息在输出背景图形像素时存储,并在输出精灵图形时检查。
VIC-II 芯片仿真类图
CIA 芯片
CIA 芯片有两个 IO 端口(A 和 B)、两个定时器(A 和 B)、一个时钟(日期时间或 TOD)和一个在 Commodore 64 中未使用的串行引脚。这些芯片负责与 IO 设备(例如直接连接的键盘和操纵杆,以及通过 IEC 总线连接的磁盘驱动器和打印机)的连接。
这些芯片连接到中断线,以便在需要时(例如,当输入引脚状态改变、定时器达到 0 等)可以触发中断。CIA #1 连接到 IRQ 线,而 CIA #2 连接到 NMI 线,但在本次实现中,它也错误地连接到 IRQ 线。
两个 CIA 都映射到 Commodore 64 的地址空间中,因此可以通过内存总线访问它们。
有关 CIA 芯片每个组件的详细信息,您可以在参考文献中找到 CIA 数据手册的链接。
芯片的仿真由 CIA
类实现。该类扩展了 MemmoryMappedDevice
,因为 CIA 映射到内存空间中。Read
和 Write
方法读取并更新芯片的寄存器。
CIA
类还实现了 ClockOp
接口,该接口负责更新定时器计数器。在某些情况下,当定时器重新加载时,CIA 芯片会空闲几个周期,但此实现未涵盖这些情况。
定时器仿真由 TimerState
类实现。
负责更新日期时间的 IncrementTod
方法在每帧结束时调用。
CIA 芯片仿真类图
SID 芯片
遗憾的是,SID 芯片仿真未实现,这里将不予讨论。SID 数据手册的链接在文章的参考文献部分提供。
内存配置
Commodore 64 有几种内存配置,可以通过设置 6510 IO 端口的前 3 位来选择合适的配置。有关每种配置的详细信息可以在引用的文章中找到。8 种可能配置中的每一种都有自己的 MemoryMap
实例,该实例在启动时预先配置。这提高了性能,因为当通过写入 IO 端口更改内存配置时,只需设置对适当的 MemoryMap
实例的引用即可。
键盘和操纵杆
在 Commodore 64 中,键盘和操纵杆连接到 CIA #1 芯片的 IO 端口(A 和 B)。键盘形成一个矩阵,可以根据输出端口 A 的状态和输入端口 B 的状态进行解码。Restore 直接连接到 NMI 线(此功能未实现)。Keyboard
类负责从系统接收按键,根据矩阵和输出端口 A 进行转换,并设置输入端口 B 的状态。
IEC 总线
IEC 总线用于与磁盘驱动器(如 1541-II)和打印机等设备进行通信。它有三条线:DATA、CLOCK 和 ATN。这些线连接到 CIA #2 的 IO 端口 B。尽管 CIA 芯片具有串行通信的硬件支持,但此功能未使用,而是通过软件实现了协议。SerialPort
类实现了串行总线模拟,其中每条线由 BusLine
类表示。线的状态可以通过 BusLine
类的 State
属性进行控制。该类跟踪有多少设备将线保持低电平,并在线状态更改时触发事件。设备不直接连接到线,而是通过 BusLineConnection
类的对象连接,该对象负责跟踪本地状态——由该设备设置的线状态。
IEC 总线仿真类图
1541-II 驱动器
该模拟器还实现了 1541-II 磁盘驱动器的真实驱动器模拟。这意味着模拟不在 IEC 总线协议级别,而是模拟驱动器的真实硬件。这种类型的模拟需要更多资源,但提供了更好的兼容性。
1541-II 驱动器方框图
GCR 编码和 D64 格式
GCR 是 Commodore 用于在磁盘上存储信息的格式。它定义了数据如何在磁盘上布局:磁道大小、数据同步、扇区头的外观以及扇区数据的编码。参考文献部分提供了包含该格式详细解释的链接。
最广泛使用的磁盘映像存储格式是 D64,因此这里只实现了这一种。D64 格式的磁盘映像包含实际磁盘的扇区数据。这些是 Commodore 64 从磁盘驱动器接收的扇区数据,它们不是 GCR 编码的。除了扇区数据之外,D64 格式还有一些版本提供了额外的坏扇区模拟数据,这提高了与某些版权保护机制的兼容性。不幸的是,这些扩展在此处未实现。D64 格式也包含在参考文献中。
GCRImage
类负责从 D64 格式加载磁盘映像,并创建可供 1541-II 模拟器其余部分使用的内存中 GCR 编码映像。
GCRImage 类
VIA 芯片
VIA 是一种 IO 端口控制器,就像它的后继者——CIA 芯片一样。它提供了两个通用 IO 端口以及两个可编程定时器。除了 8 个 IO 引脚之外,每个端口还有两条额外的控制线,可用作中断输入或握手输出。
驱动头和电机以及 IEC 总线都连接到驱动器中安装的两个 VIA 芯片的端口。
VIA
类实现了芯片的模拟。它扩展了 MemmoryMappedDevice
,因为 VIA 映射到驱动器 CPU 的内存空间中。Read
和 Write
方法读取并更新芯片的寄存器。
此类别还实现了 ClockOp
接口,负责更新计时器计数器以及端口控制线。
该类的 PortA
和 PortB
属性公开了 VIA 芯片的两个 IO 端口。CA1
、CA2
、CB1
和 CB2
属性提供了对 IO 端口控制线的访问。
VIA 和驱动头仿真类图
驱动头
驱动头是一个机电子系统,它控制驱动器的电机、磁头的位置以及从/向磁盘读取/写入数据的磁头本身。它连接到 VIA#2 的 IO 端口,其中端口 B 负责磁头控制,端口 A 负责数据传输。
磁头还连接到 VIA 芯片的控制线以及 CPU 的 SO 引脚,SO 引脚设置溢出标志,以通知 CPU 数据已准备好读取或已写入磁盘。
DriveHead
类,顾名思义,实现了驱动器磁头的模拟。该类实现了 ClockOp
接口,因此在每个时钟周期中都将执行读取或写入数据的逻辑。如果磁头处于活动状态(由 VIA 芯片控制),在一定数量的周期后,数据将可用于读取操作,或存储到磁盘以进行写入操作。为了增加磁盘容量,磁道具有不同的密度,具体取决于它们与磁盘中心的距离。因此,等待读取/写入操作完成所需的周期数取决于磁头当前所在的磁盘磁道。
持久化模拟器状态
每个模拟设备都有一定的状态,如果我们希望能够保存或加载模拟会话(这是模拟器非常有用的功能,并使旧游戏更容易玩),这些状态应该持久化到文件中。
模拟器只会在发出保存命令的帧结束时保存状态。这样做的原因是简化了负责处理 VIC-II 芯片状态的代码。
模拟器状态的保存和加载通过 IDeviceState
接口完成。每个需要保存其状态的模拟组件类都应实现此接口。当需要持久化或恢复状态时,会调用接口的 ReadDeviceState
和 WriteDeviceState
方法。模拟组件可以使用作为这两个方法参数提供的文件对象的引用来读取或写入状态文件。
如果它是复合的并包含实现 IDeviceState
接口的其他组件,则父组件应在其所有子组件上调用相应的方法。例如,C64 主板将调用表示内存、CPU 和所有其他芯片的组件上的存储/加载方法。
IDeviceState 接口
模拟器状态结构
环境
环境项目包含旨在将外部系统与模拟器隔离的接口。目前定义了两个接口:IVideoOutput
接口抽象了视频输出,IFile
抽象了文件操作。
ROM 文件
由于法律原因,ROM 文件未包含在源代码中,但它们应该可以在互联网上找到。
模拟器所需的文件是
kernal.rom
- 承载操作系统的 ROM 芯片内容basic.rom
- 承载 Basic 解释器的 ROM 芯片内容chargen.rom
- 字符 ROMd1541.rom
- CBM 1541-II 驱动器固件
它们应复制到项目的 .\c64_roms
文件夹。如果您只是尝试运行模拟器二进制文件,请将 ROM 文件复制到与可执行文件相同的文件夹中。
参考文献
本节提供官方硬件文档和数据手册的链接。它还包含指向非常有用的资源的链接,这些资源是 Commodore 64 硬件和软件逆向工程的产物,例如内存映射、非法操作码、ROM 反汇编等。
硬件数据手册和文档
- MOS 6510 数据手册 - CPU [PDF]
- MOS 6502 非法操作码 - CPU [TXT]
- MOS 6526 数据手册 - CIA [PDF]
- MOS 6522 数据手册 - VIA [PDF]
- MOS 6581 数据手册 - SID [PDF]
- MOS 6567 描述 - (VIC-II) [TXT]
内存映射
ROM 反汇编
硬件原理图
其他
历史
- 2014 年 7 月 23 日 - 原始文章
- 2014 年 7 月 28 日 - 添加了关于 1541-II 驱动器仿真的信息