什么是硬件仿真的正确实现?

我打算编写一个Game Boy模拟器( Z80是CPU,以防有人不熟悉它),在我做研究的时候,我发现了一些我不太确定的东西。

第一个是C是这里选择的编程语言。 这不是一个问题,但我想从今天的观点听取你的意见。 甚至不建议使用C ++。

我发现的第二件事是每个人都在使用每个操作码一个函数。 这似乎是合乎逻辑的,因为它只是一个函数调用,可能比为“ADD”指令设置一个函数更好地优化,然后你必须找出这里使用的寄存器。 但今天有多必要? 这是我应该坚持的东西,还是我应该改写我的模拟器,如果我注意到另一种可能更方便的方式就是不切割它(或多或少现代游戏机现在流入我的脑海)?

此外,一遍又一遍地编写一个“将该寄存器添加到该寄存器”的function,这种情况有点令人失望。 有没有办法从操作码映射或类似的东西自动化?

第一个建议,你不应该使用嵌套的switch语句,你应该使用函数指针数组,更快 – >更好的模拟,更好的代码,嵌套的switch-es也会有点乱,这里有一些链接,你可以详细了解这些数组
http://www.newty.de/fpt/fpt.html
http://www.multigesture.net/wp-content/uploads/mirror/zenogais/FunctionPointers.htm

第二个建议,是的,你可以用C#,Java,C ++来做,但是因为你想要你的每一个CPU周期,所以你可以尽可能接近仿真 – 模拟目标架构的一个CPU周期,CPU周期最少当前的架构,在这种情况下OOP并不像我从人们那里听到/读到的那样好。 其中一个是性能,第二个是非常明显的,仿真,正如你可能已经注意到的,真正复杂的任务和在OOP中包装它可能是不必要的痛苦。

我大多同意WingsOfIcarus。 我已经写了一些模拟器,所以这是我的见解:

  1. 使用函数指针是一个好主意 (为了代码的速度和清晰度)
  2. OOP不是问题

    是的,成员调用有点慢,但如果你小心,它不会对性能产生太大影响。 另一方面,OOP仿真代码更易于管理/读取/理解。

  3. 使用指令数据库而不是固定指令解码。

    我正在使用单个文本文件,其中包含所有指令的所有必要信息。 模拟器在初始化期间解析它(提供函数指针和操作数的数组……)。 在这种架构中,很容易在没有任何代码更改的情况下纠正指令集中的错误。

    复杂的指令集文档在某些方面几乎总是有缺陷的。 最糟糕的情况是Z80 (我从未看到100%无错误的指令集)。 因此,使用更多指令集,比较它们并创建无错误集(如果可以)。

  4. 为您的仿真添加声音,video,键盘和鼠标

    这通常不是问题。 在Windows上使用WaveOut而不是DirectSound 。 它更稳定,更快(DSound的可用延迟有时甚至> 400 ms)。 使用WaveOut,我能够将潜伏期延迟到20-80毫秒,这是可以的。

  5. 将限制速度应用于每秒模拟CPU的T个周期

    我使用的机器周期校正时间要慢得多,但允许我正确地实现任何硬件外围仿真(FDC,DMAC,声音芯片,……没有任何黑客攻击)

  6. 为模拟平台应用加载/保存文件

例如,这是我的指令集的一部分(直接输入到CPU仿真:

 opc T0 T1 MC1 MC2 MC3 MC4 MC5 MC6 MC7 mnemonic B8 04 00 M1R 4 ... 0 ... 0 ... 0 ... 0 ... 0 ... 0 CP A,B B9 04 00 M1R 4 ... 0 ... 0 ... 0 ... 0 ... 0 ... 0 CP A,C BA 04 00 M1R 4 ... 0 ... 0 ... 0 ... 0 ... 0 ... 0 CP A,D BB 04 00 M1R 4 ... 0 ... 0 ... 0 ... 0 ... 0 ... 0 CP A,E BC 04 00 M1R 4 ... 0 ... 0 ... 0 ... 0 ... 0 ... 0 CP A,H BD 04 00 M1R 4 ... 0 ... 0 ... 0 ... 0 ... 0 ... 0 CP A,L BE 07 00 M1R 4 MRD 3 ... 0 ... 0 ... 0 ... 0 ... 0 CP A,(HL) BF 04 00 M1R 4 ... 0 ... 0 ... 0 ... 0 ... 0 ... 0 CP A,A C0 11 05 M1R 5 MRD 3 MRD 3 ... 0 ... 0 ... 0 ... 0 RET NZ C1 10 00 M1R 4 MRD 3 MRD 3 ... 0 ... 0 ... 0 ... 0 POP BC C2L2H2 10 10 M1R 4 MRD 3 MRD 3 ... 0 ... 0 ... 0 ... 0 JP NZ,U16 C3L1H1 10 00 M1R 4 MRD 3 MRD 3 ... 0 ... 0 ... 0 ... 0 JP U16 C4L2H2 17 10 M1R 4 MRD 3 MRD 4 MWR 3 MWR 3 ... 0 ... 0 CALL NZ,U16 C5 11 00 M1R 5 MWR 3 MWR 3 ... 0 ... 0 ... 0 ... 0 PUSH BC C6U2 07 00 M1R 4 MRD 3 ... 0 ... 0 ... 0 ... 0 ... 0 ADD A,U8 C7 11 00 M1R 5 MWR 3 MWR 3 ... 0 ... 0 ... 0 ... 0 RST 00H C8 11 05 M1R 5 MRD 3 MRD 3 ... 0 ... 0 ... 0 ... 0 RET Z C9 10 00 M1R 4 MRD 3 MRD 3 ... 0 ... 0 ... 0 ... 0 RET CAL2H2 10 10 M1R 4 MRD 3 MRD 3 ... 0 ... 0 ... 0 ... 0 JP Z,U16 opc: operation code [hex] L1,H1,U1,S1 means first operand direct number or address L2,H2,U2,S2 means second operand direct number or address L3,H3,U3,S3 means third operand direct number or address H,L ... U16 high and low byte U ... U8 unsigned byte S ... S8 signed byte T0 normal instruction duration [T] always 2 decimal digits T1 instruction duration if condition not met [T] always 2 decimal digits MC1++ Machine cycle first is type,second is duration [T] always 1 decimal digit ... unused M1R M1 cycle MRD memory read MWR memory write IOR IO read IOW IO write NON no external operation (internal computation) INT interrupt cycle mnem instruction text (mnemonic) 
  • opc用于指针数组中的地址
  • mnemonic用于选择正确的函数指针和操作数类型
  • T0T1用于指令时序(这对于粗略仿真来说已经足够了)
  • MC1++用于正确的MC时序(实现正确的硬件仿真和争用时序)

这是我的Zilog Z80A完整指令集,带有机器周期定时链接供下载。 随意使用(只是在某处提到我的昵称)。 移植到此后,我终于能够100%通过ZEXALL测试。 有关更多信息,请参阅使用C或C ++编写图形化Z80仿真器

这是一个非常酷的实现,使用NES模拟器的一些操作码:

http://bisqwit.iki.fi/jutut/kuvat/programming_examples/nesemu1/

这是随附的YouTubevideo,它们对正在发生的事情有更多的解释

http://www.youtube.com/watch?v=y71lli8MS8s

它使用C ++模板和一些额外的C ++ 11function。 至于你是否选择了C ++或C,这对你来说很重要,但它并不重要。 如果你只是模仿一个游戏男孩,我怀疑速度将成为现代处理器的一个问题,所以尽量使用你喜欢的任何东西。