What’s the proper implementation for hardware emulation?

I mostly agree with WingsOfIcarus. I wrote a few emulators already so here is my insight:

  1. The use of function pointers is a good idea (for speed and clarity of code)
  2. OOP is not a problem

    Yes, member calls are a little bit slower, but if you are careful it will not affect performance too much. On the other hand, OOP emulation code is much better to manage/read/understand.

  3. Use an instruction database instead of fixed instruction decoding.

    I am using a single text file which consist of all the necessary information for all instructions. The emulator parses it during initialization (feeds the arrays of function pointers and operands…). In this architecture it is very easy to correct errors in the instruction set without any code change.

    Complex instruction sets documentation are almost always faulty to some point. The worst case is Z80 (I have never see a 100% error-free instruction set). So use more instruction sets, compare them and create an error-free set (if you can).

  4. Add sound, video, keyboard and mouse to your emulation

    This is usually not a problem. On Windows use WaveOut instead of DirectSound. It’s more stable, much faster (usable latencies of DSound are sometimes even > 400 ms). With WaveOut I was able to lover latency to 20-80 ms which is OK.

  5. Apply limit speed by T cycles of emulated CPU per second

    I am using machine cycle correct timings which is much slower, but allows me to correctly implement any hardware periphery emulations as (FDC, DMAC, sound chips,…without any hacks)

  6. Apply load/save of files for the emulated platform

For example, this is part of my instruction set (which is directly fed to CPU emulation:

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 is used for the address in an array of pointers
  • mnemonic is used to select the proper function pointer, and operands type
  • T0 and T1 are used for instructions timing (this is enough for rough emulations)
  • MC1++ are used for correct MC timings (to implement correct hardware emulation and contentions timing)

Here is my Zilog Z80A complete instruction set with machine cycle timing link for download. Feel free to use (just mention my nick somewhere). After porting to this I was finally able to 100% pass the ZEXALL test. For more info see Writing a graphical Z80 emulator in C or C++.

Leave a Comment