Coder Social home page Coder Social logo

z80's Introduction

SUZUKI PLAN - Z80 Emulator

suzukiplan

The Z80 is an 8-bit CPU developed by Zilog corporation, released in 1976, and widely used in computers and game consoles in the 1980s. It is not just a relic of the past, but continues to be used in embedded systems that require accuracy in processing execution time, such as real-time systems.

SUZUKI PLAN - Z80 Emulator is an emulator under development based on the following design guidelines to support the development of programs and/or emulators using Z80, 8080:

(FOUR EASY GUIDELINES FOR EASILY)

  1. Make emulator implementation EASY & simple (Realized by providing single header: z80.hpp)
  2. EASILY debugging the Z80 programs (Realized by having dynamic disassemble feature)
  3. Highly readable and EASILY customizable (priority for readability over performance)
  4. Provide under the license that EASY to adopt in various programs (MIT)

Since I do not have deep knowledge about Z80 myself, I'm implementing it with reference to the information on the following web sites:

z80.hpp passes all zexdoc and zexall tests. (See the detail)

How to test

Prerequests

You can test on the any 32bit/64bit platform/OS (UNIX, Linux, macOS...etc) but needs following middlewares:

  • Git
  • GNU Make
  • clang
  • clang-format

Build (UNIX, Linux, macOS)

git clone https://github.com/suzukiplan/z80.git
cd z80
make

Minimum usage

1. Include

#include "z80.hpp"

2. Implement MMU & access callback

class MMU
{
  public:
    unsigned char RAM[0x10000]; // 64KB memory (minimum)
    unsigned char IO[0x100]; // 256bytes port

    MMU()
    {
        memset(&RAM, 0, sizeof(RAM));
        memset(&IO, 0, sizeof(IO));
    }
};

// memory read request per 1 byte from CPU
unsigned char readByte(void* arg, unsigned short addr)
{
    // NOTE: implement switching procedure here if your MMU has bank switch feature
    return ((MMU*)arg)->RAM[addr];
}

// memory write request per 1 byte from CPU
void writeByte(void* arg, unsigned short addr, unsigned char value)
{
    // NOTE: implement switching procedure here if your MMU has bank switch feature
    ((MMU*)arg)->RAM[addr] = value;
}

// IN operand request from CPU
unsigned char inPort(void* arg, unsigned short port)
{
    return ((MMU*)arg)->IO[port];
}

// OUT operand request from CPU
void outPort(void* arg, unsigned short port, unsigned char value)
{
    ((MMU*)arg)->IO[port] = value;
}

3. Make Z80 instance

    MMU mmu;
    /**
     * readByte: callback of memory read request
     * writeByte: callback of memory write request
     * inPort: callback of input request
     * outPort: callback of output request
     * &mmu: 1st argument of the callbacks
     */
    Z80 z80(readByte, writeByte, inPort, outPort, &mmu);

3-1. Cases when want to use 16bit port

Note that by default, only the lower 8 bits of the port number can be obtained in the callback argument, and the upper 8 bits must be referenced from register B.

If you want to get it in 16 bits from the beginning, please initialize with returnPortAs16Bits (6th argument) to true as follows:

    Z80 z80(readByte, writeByte, inPort, outPort, &mmu, true);

3-2. Cases when performance-sensitive

Normally, std::function is used for callbacks, but in more performance-sensitive cases or std::function is not exist environment (ex: RaspberryPi Baremetal), all can be replaced with function pointers by specifying the compile option -DZ80_NO_FUNCTIONAL.

The following article (in Japanese) provides a performance comparison between function pointers and std::function:

https://qiita.com/suzukiplan/items/e459bf47f6c659acc74d

The above article gives an example of the time it took to execute 100 million times call of function-pointer and three patterns of std::function calls (bind, direct, lambda) as following:

Optimization Option function pointer bind direct lambda
none 195,589μs 5,574,856μs 2,570,016μs 2,417,802μs
-O 184,692μs 2,293,151μs 827,113μs 580,442μs
-O2 154,206μs 197,683μs 209,626μs 167,703μs
-Ofast 154,332μs 250,490μs 255,401μs 164,773μs

4. Execute

    // when executing about 1234Hz
    int actualExecutedClocks = z80.execute(1234);

4-1. Actual executed clocks

  • The execute method repeats the execution of an instruction until the total number of clocks from the time of the call is greater than or equal to the value specified in the "clocks" argument.
  • If a value less than or equal to 0 is specified, no instruction is executed at all.
  • If you want single operand execution, you can specify 1.

4-2. Interruption of execution

Execution of the requestBreak method can abort the execute at an arbitrary time.

A typical 8-bit game console emulator implementation that I envision:

  • Implement synchronization with Video Display Processor (VDP) and other devices (sound modules, etc.) in consumeClock callback.
  • Call requestBreak when V-SYNC signal is received from VDP.
  • Call execute with a large value such as INT_MAX.

4-3. Example

Code: test/test-execute.cpp

#include "z80.hpp"

int main()
{
    unsigned char rom[256] = {
        0x01, 0x34, 0x12, // LD BC, $1234
        0x3E, 0x01,       // LD A, $01
        0xED, 0x79,       // OUT (C), A
        0xED, 0x78,       // IN A, (C)
        0xc3, 0x09, 0x00, // JMP $0009
    };
    Z80 z80([=](void* arg, unsigned short addr) { return rom[addr & 0xFF]; },
            [](void* arg, unsigned short addr, unsigned char value) {},
            [](void* arg, unsigned short port) { return 0x00; },
            [](void* arg, unsigned short port, unsigned char value) {
                // request break the execute function after output port operand has executed.
                ((Z80*)arg)->requestBreak();
            }, &z80);
    z80.setDebugMessage([](void* arg, const char* msg) { puts(msg); });
    z80.setConsumeClockCallback([](void* arg, int clocks) { printf("consume %dHz\n", clocks); });
    puts("===== execute(0) =====");
    printf("actualExecuteClocks = %dHz\n", z80.execute(0));
    puts("===== execute(1) =====");
    printf("actualExecuteClocks = %dHz\n", z80.execute(1));
    puts("===== execute(0x7FFFFFFF) =====");
    printf("actualExecuteClocks = %dHz\n", z80.execute(0x7FFFFFFF));
    return 0;
}

Result is following:

===== execute(0) =====
actualExecuteClocks = 0Hz ... 0 is specified, no instruction is executed at all
===== execute(1) =====
consume 2Hz
consume 2Hz
consume 3Hz
consume 3Hz
[0000] LD BC<$0000>, $1234
actualExecuteClocks = 10Hz ... specify 1 to single step execution
===== execute(0x7FFFFFFF) =====
consume 2Hz
consume 2Hz
consume 3Hz
[0003] LD A<$FF>, $01
consume 2Hz
consume 2Hz
consume 4Hz
[0005] OUT (C<$34>), A<$01>
consume 4Hz
actualExecuteClocks = 19Hz ... 2147483647Hz+ is not executed but interrupted after completion of OUT due to requestBreak during OUT execution

5. Generate interrupt

IRQ; Interrupt Request

    z80.generateIRQ(vector);

NMI; Non Maskable Interrupt

    z80.generateNMI(address);

Optional features

Dynamic disassemble (for debug)

You can acquire the debug messages with setDebugMessage. Debug message contains dynamic disassembly results step by step.

    z80.setDebugMessage([](void* arg, const char* message) -> void {
        time_t t1 = time(NULL);
        struct tm* t2 = localtime(&t1);
        printf("%04d.%02d.%02d %02d:%02d:%02d %s\n",
               t2->tm_year + 1900, t2->tm_mon, t2->tm_mday, t2->tm_hour,
               t2->tm_min, t2->tm_sec, message);
    });
  • call resetDebugMessage if you want to remove the detector.
  • call setDebugMessageFP if you want to use the function pointer.

Use break point

If you want to execute processing just before executing an instruction of specific program counter value (in this ex: $008E), you can set a breakpoint as follows:

    z80.addBreakPoint(0x008E, [](void* arg) -> void {
        printf("Detect break point! (PUSH ENTER TO CONTINUE)");
        char buf[80];
        fgets(buf, sizeof(buf), stdin);
    });
  • addBreakPoint can set multiple breakpoints for the same address.
  • call removeBreakPoint or removeAllBreakPoints if you want to remove the break point(s).
  • call addBreakPointFP if you want to use the function pointer.

Use break operand

If you want to execute processing just before executing an instruction of specific operand number, you can set a breakpoint as follows:

    // break when NOP ... $00
    z80.addBreakOperand(0x00, [](void* arg, unsigned char* opcode, int opcodeLength) -> void {
        printf("Detect break operand! (PUSH ENTER TO CONTINUE)");
        char buf[80];
        fgets(buf, sizeof(buf), stdin);
    });

    // break when RLC B ... $CB $00
    z80.addBreakOperand(0xCB, 0x00, [](void* arg, unsigned char* opcode, int opcodeLength) -> void {
        printf("Detect break operand! (PUSH ENTER TO CONTINUE)");
        char buf[80];
        fgets(buf, sizeof(buf), stdin);
    });

    // break when RLC (IX+d) ... $DD $CB, $06
    z80.addBreakOperand(0xDD, 0xCB, 0x06, [](void* arg, unsigned char* opcode, int opcodeLength) -> void {
        printf("Detect break operand! (PUSH ENTER TO CONTINUE)");
        char buf[80];
        fgets(buf, sizeof(buf), stdin);
    });
  • the opcode and length at break are stored in opcode and opcodeLength when the callback is made.
  • addBreakOperand can set multiple breakpoints for the same operand.
  • call removeBreakOperand or removeAllBreakOperands if you want to remove the break operand(s).
  • call addBreakOperandFP if you want to use the function pointer.

Detect clock consuming

If you want to implement stricter synchronization, you can capture the CPU clock consumption timing as follows:

    z80.setConsumeClockCallback([](void* arg, int clock) -> void {
        printf("consumed: %dHz\n", clock);
    });
  • call resetConsumeClockCallback if you want to remove the detector.
  • call setConsumeClockCallbackFP if you want to use the function pointer.

With this callback, the CPU cycle (clock) can be synchronized in units of 3 to 4 Hz, and while the execution of a single Z80 instruction requires approximately 10 to 20 Hz of CPU cycle (time), the SUZUKI PLAN - Z80 Emulator can synchronize the CPU cycle (time) for fetch, execution, write back, etc. However, the SUZUKI PLAN - Z80 Emulator can synchronize fetches, executions, writes, backs, etc. in smaller units. This makes it easy to implement severe timing emulation.

If implement quick save/load

Save the member variable reg when quick saving:

    fwrite(&z80.reg, sizeof(z80.reg), 1, fp);

Extract to the member variable reg when quick loading:

    fread(&z80.reg, sizeof(z80.reg), 1, fp);

Handling of CALL instructions

The occurrence of the branches by the CALL instructions can be captured by the CallHandler. CallHandler will be called back immediately after a branch by a CALL instruction occurs.

CallHandle will called back after the return address is stacked in the RAM.

    z80.addCallHandler([](void* arg) -> void {
        printf("Executed a CALL instruction:\n");
        printf("- Branched to: $%04X\n", ((Z80*)arg)->reg.PC);
        unsigned short sp = ((Z80*)arg)->reg.SP;
        unsigned short returnAddr = ((Z80*)arg)->readByte(sp + 1);
        returnAddr <<= 8;
        returnAddr |= ((Z80*)arg)->readByte(sp);
        printf("- Return to: $%04X\n", returnAddr);
    });
  • addCallHandler can set multiple CallHandlers.
  • call removeAllCallHandlers if you want to remove the CallHandler(s).
  • call addCallHandlerFP if you want to use the function pointer.
  • CallHandler also catches branches caused by interrupts.
  • In the case of a condition-specified branch instruction, only the case where the branch is executed is callbacked.

Handling of RET instructions

The occurrence of the branches by the RET instructions can be captured by the ReturnHandler. ReturnHandler will be called back immediately before a branch by a RET instruction occurs.

ReturnHandle will called back while the return address is stacked in the RAM.

    z80.addReturnHandler([](void* arg) -> void {
        printf("Detected a RET instruction:\n");
        printf("- Branch from: $%04X\n", ((Z80*)arg)->reg.PC);
        unsigned short sp = ((Z80*)arg)->reg.SP;
        unsigned short returnAddr = ((Z80*)arg)->readByte(sp + 1);
        returnAddr <<= 8;
        returnAddr |= ((Z80*)arg)->readByte(sp);
        printf("- Return to: $%04X\n", returnAddr);
    });
  • addReturnHandler can set multiple ReturnHandlers.
  • call removeAllReturnHandlers if you want to remove the ReturnHandler(s).
  • call addReturnHandlerFP if you want to use the function pointer.
  • In the case of a condition-specified branch instruction, only the case where the branch is executed is callbacked.

Advanced Compile Flags

There is a compile flag that disables certain features in order to adapt to environments with poor performance environments, i.e: Arduino or ESP32:

Compile Flag Feature
-DZ80_DISABLE_DEBUG disable setDebugMessage method
-DZ80_DISABLE_BREAKPOINT disable addBreakPoint and addBreakOperand methods
-DZ80_DISABLE_NESTCHECK disable addCallHandler and addReturnHandler methods
-DZ80_CALLBACK_WITHOUT_CHECK Omit the check process when calling consumeClock callback (NOTE: Crashes if setConsumeClock is not done)
-DZ80_CALLBACK_PER_INSTRUCTION Calls consumeClock callback on an instruction-by-instruction basis (NOTE: two or more instructions when interrupting)
-DZ80_UNSUPPORT_16BIT_PORT Reduces extra branches by always assuming the port number to be 8 bits
-DZ80_NO_FUNCTIONAL Do not use std::function in the callbacks (use function pointer)
-DZ80_NO_EXCEPTION Do not throw exceptions

License

MIT

z80's People

Contributors

bradgrantham avatar suzukiplan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

z80's Issues

MMU out callback missing the MSB of the port

When using the "out" callback of the MMU on the Z80 initialization, only the Less Significant Byte of the port is sent, so if using:

ld bc,#1234
ld a,#1
out (c),a

only "#34" is set in the port callback. It should be a 16 bits value.

Callback on each instruction

One feature I'd like would be a callback called every time an instruction is read, with a least the opcode. If the values could be shown too (if relevant), the better.

My use-case is as follows:

  • On Amstrad CPC, the Z80 timing is different and I'd like to count precisely how many cycles are spent after my code is executed. What I am doing right now is hacking a counter which increases on every instruction. This forces me to modify the emulator code. Such callback would externalize my code.

One problem I see in the current implementation is that, for IX/IY instructions, only the prefix (0xdd, 0xed, 0xfd...) is transmitted (in the BreakOperand callback for example), the rest of the opcode is "lost", so this would have to be corrected first.

Optimize performance of breakPoints and breakOperands

Currently, breakPoints and breakOperands are managed by a simple std::vector, which performs a linear search each time, resulting in a significant performance degradation proportional to the number of settings.

z80/z80.hpp

Lines 202 to 209 in f1e4def

std::vector<BreakPoint*> breakPoints;
std::vector<BreakOperand*> breakOperands;
std::vector<BreakOperand*> breakOperandsCB;
std::vector<BreakOperand*> breakOperandsED;
std::vector<BreakOperand*> breakOperandsIX;
std::vector<BreakOperand*> breakOperandsIX4;
std::vector<BreakOperand*> breakOperandsIY;
std::vector<BreakOperand*> breakOperandsIY4;

NOTE: callHandlers and returnHandler are fine as they are.

breakPoints should be a map with addresses as keys, and breakOperands should be a map with opcode prefix values as keys.

LD A, I Incorrect P/V flag after execution

I got the point from a comment on Qiita.

Specification

image

Actual

z80/z80.hpp

Lines 459 to 466 in f3cb12d

inline int LD_A_I()
{
if (isDebug()) log("[%04X] LD A<$%02X>, I<$%02X>", reg.PC, reg.pair.A, reg.I);
reg.pair.A = reg.I;
setFlagPV(reg.IFF & IFF1() ? true : false);
reg.PC += 2;
return consumeClock(1);
}

P/V flag is set to IFF1 instead of IFF2

Make strict the registers conditions at callback time

Currently, some register conditions when callbacks are invoked are different from expected values.

For example...

#include "z80.hpp"

int main()
{
    unsigned short expectIndex = 0;
    int expectPC[] = {0x0000, 0x0001, 0x0002, -1 };
    int expectSP[] = {0xFFFF, 0xFFFF, 0xFF34, -1 };
    unsigned char rom[256] = {
        0x31, 0x34, 0x12, // LD SP, $1234
    };
    Z80 z80([=, &expectIndex](void* arg, unsigned short addr) {
        if (addr < 0x100) {
            unsigned short sp = ((Z80*)arg)->reg.SP;
            unsigned short pc = ((Z80*)arg)->reg.PC;
            printf("Read memory ... $%04X SP=%04X, PC=%04X <expect: SP=%04X, PC=%04X>\n", addr, sp, pc, expectSP[expectIndex], expectPC[expectIndex]);
            expectIndex++;
            return rom[addr];
        } 
        return (unsigned char)0xFF;
    }, [](void* arg, unsigned char addr, unsigned char value) {
    }, [](void* arg, unsigned short port) {
        return 0x00;
    }, [](void* arg, unsigned short port, unsigned char value) {
    }, &z80);
    z80.setDebugMessage([](void* arg, const char* msg) { puts(msg); });
    z80.execute(1);
    printf("SP=%04X, PC=%04X\n", z80.reg.SP, z80.reg.PC);
    return 0;
}

Current behavior:

Read memory ... $0000 SP=FFFF, PC=0000 <expect: SP=FFFF, PC=0000>
Read memory ... $0001 SP=FFFF, PC=0000 <expect: SP=FFFF, PC=0001>
Read memory ... $0002 SP=FFFF, PC=0000 <expect: SP=FF34, PC=0002>
[0000] LD SP<$FFFF>, $1234
SP=1234, PC=0003

Correct warnings

I use CLion as an IDE, and it shows many many warnings. On Linux, I compile with GCC 10 which reports a lot of errors. As I like to use the "consider warning as error" option, I have to rely on following pragmas to be able to compile:

#pragma GCC diagnostic ignored "-Wunused-variable"
#pragma GCC diagnostic ignored "-Wshadow"
#pragma GCC diagnostic ignored "-Wsign-conversion"
#pragma GCC diagnostic ignored "-Wclass-memaccess"
#pragma GCC diagnostic ignored "-Wtype-limits"

I can provide a list of warnings if you want, but the main one is the use of NULL instead of nullptr. By doing this, you would correct 80% of the warnings :).

zexdoc `aluop` test fails except for immediate value.

Condition

zexdoc aluop test fails except for immediate value.

I think aluop means something like ADD, SUB, CP, ADC, SBC, AND, OR, XOR? 🤔

Test Result of zexdoc (Partial)

aluop a,nn....................  OK
aluop a,<b,c,d,e,h,l,(hl),a>..  ERROR **** crc expected:fe43b016 found:9e17dfdc
aluop a,<ixh,ixl,iyh,iyl>.....  ERROR **** crc expected:a4026d5a found:88a56864
aluop a,(<ix,iy>+1)...........  ERROR **** crc expected:e849676e found:14c405a8

RaspberryPiのベアメタル環境でCircleを用いてコンパイルできない

functional が使えないためエラーになる。

functional は関数ポインタに置換可能なのでコンパイルオプションで functional を使わないようにできた方が良いかもしれない。

msx2-rpizero % make
cp ../src/* .
make -f Build.mak
  CPP   kernel.o
In file included from kernel.cpp:29:
z80.hpp:29:10: fatal error: functional: No such file or directory
   29 | #include <functional>
      |          ^~~~~~~~~~~~
compilation terminated.
make[1]: *** [kernel.o] Error 1
make: *** [all] Error 2

RST実行時のデバッグログが正しくない

プログラムカウンタが+1された状態でログ出力されてしまう。

[127][024E] LDIR ... BC<$0002>, DE<$DEFE>, HL<$DEFD>
[127][024E] LDIR ... BC<$0001>, DE<$DEFF>, HL<$DEFE>
[127][0250] LD ($C006), A<$00>
[127][0253] LD A<$00>, $12
[127][0256] RST $0020 (SP<$DFEF>) ← ★expected [0255]
[127][0020] LD ($C038), A<$12>
[127][0023] LD ($FFFF), A<$12>
[127][0026] RET to $0256 (SP<$DFED>)

ログ表示上のみの問題で動作には支障はない。

Consider system test

完成は問題なくできる。
ユニットテストを作りながら実装しているので、ユニットテストは(カバレッジはさておき)概ね問題ない。
システムテストをどうやってやるかが悩ましい。

ゲームボーイエミュレータを作ってみて検証してみるとか。
何番煎じか分からないけど知見が沢山あるので割と簡単かもしれないので、資料を収集して検討予定。

Error Case Exceptions

Currently, it expects a crash when it tries to execute an existence instruction, but this is not very desirable and should be modified to throw an exception.

Callbacks allowing variable capture

First of all, thanks for your great emulator. I'm trying to integrate it, but I had to modify the code to make it fit to my need. I simply want the emulator to stop whenever a special instruction is met:

    z80.addBreakOperand(0xff, [&]() {
        z80.requestBreak();
    });

However, the way the callback is declared does not allow the capture ([&]), making the inner method unable to reach the z80 object.

My solution is as follow:

Instead of:
void addBreakOperand(unsigned char operandNumber, void (*callback)(void*) = NULL)
I did:
void addBreakOperand(unsigned char operandNumber, const std::function<void()>& callback)
and so on.

Does something like that makes sense to you? It could be applied to all the callbacks.

OUTI命令のポートについて

Z80エミュ使わせていただいております。
物凄く使いやすく組み込むのも簡単にできました。ありがとうございます。

使っていて気になったのですが、
OUTI命令ではじめにポートアドレスのBレジスタを引いてから出力するのが正しい挙動だと思います。

下記が詳しいです。
入出力命令のバグ?

ご確認の程よろしくお願いいたします。

incorrect ADD IX,IX and ADD IY,IY

Condition

When ADD IX,IX or ADD IY,IY are executed, the result of HL addition is set to IX or IY.

Expected

ADD IX, IX ... IX = IX + IX
ADD IY, IY ... IY = IY + IY

Actual

ADD IX, IX ... IX = IX + HL
ADD IY, IY ... IY = IY + HL

Note

Detected by zexdoc

unexpected break operand

Unexpected BreakOperand invoke when executing the following test program:

#include "z80.hpp"

int main()
{
    unsigned char rom[256] = {
        0x01, 0x34, 0x12, // LD BC, $1234
        0x3E, 0x01,       // LD A, $01
        0xED, 0x79,       // OUT (C), A
        0xED, 0x78,       // IN A, (C)
        0xc3, 0x00, 0x00, // JMP $0000
    };

    Z80 z80([&rom](void* arg, unsigned short addr) {
        return rom[addr & 0xFF];
    }, [](void* arg, unsigned char addr, unsigned char value) {
        ((Z80*)arg)->requestBreak();
    }, [](void* arg, unsigned short port) {
        return 0x00;
    }, [](void* arg, unsigned short port, unsigned char value) {
        putc(value, stdout);
    }, &z80);
    z80.setDebugMessage([](void* arg, const char* msg) { puts(msg); });
    for (int operand = 0; operand < 65536; operand++) {
        unsigned char op1 = (unsigned char)((operand & 0xFF00) >> 8);
        unsigned char op2 = (unsigned char)(operand & 0xFF);
        z80.addBreakOperand((unsigned char)operand, [=](void* arg, const unsigned char* opcode, int opcodeLength) {
            char buf[1024];
            buf[0] = '\0';
            for (int j = 0; j < opcodeLength; j++) {
                char hex[8];
                if (j) {
                    snprintf(hex, sizeof(hex), ",%02X", opcode[j]);
                } else {
                    snprintf(hex, sizeof(hex), "%02X", opcode[j]);
                }
                strcat(buf, hex);
            }
            printf("break op1=%02X, op2=%02X (len=%d) ... opcode=%s\n", op1, op2, opcodeLength, buf);
        });
    }
    z80.execute(50);
    return 0;
}

unexpected 1

break op1=01~FF, op2=01 (len=3) ... opcode=01,34,12
[0000] LD BC<$1234>, $1234

unexpected 2

break op1=01~FF, op2=3E (len=2) ... opcode=3E,01
[0003] LD A<$00>, $01

unexpected 3

break op1=00~FF, op2=ED (len=0) ... opcode=
[0005] OUT (C<$34>), A<$01>

unexpected 4

break op1=00~FF, op2=ED (len=0) ... opcode=
[0007] IN A<$01>, (C<$34>) = $00

unexpected 5

break op1=01~FF, op2=C3 (len=3) ... opcode=C3,00,00
[0009] JP $0000

IN A,(n)、OUT (n),Aのポート番号について

お疲れ様です。(度々すみません。)
IN A,(n)、OUT (n),A命令のポート番号ですが、Bレジスタではなく、Aレジスタがポートの上位8ビットになります。

例えば
LD A,$1A
IN A,($01)
とすると、16ビットのポート番号は$1A01となります。

mameのZ80より
https://github.com/mamedev/mame/blob/master/src/devices/cpu/z80/z80.cpp
3183行目

OP(op,db) { unsigned n = arg() | (A << 8); A = in(n); WZ = n + 1; } /* IN A,(n) */

3174行目

OP(op,d3) { unsigned n = arg() | (A << 8); out(n, A); WZ_L = ((n & 0xff) + 1) & 0xff; WZ_H = A; } /* OUT (n),A */

Performance issue in version 1.6.0 or later

When I tested my MSX emulator on an actual iOS (iPhone SE1) device by replacing z80.hpp with version 1.6.0 or later, the sound skipping that did not occur before version 1.5.0 is now constantly occurring.

The read/write/in/out callback calls appear to be the bottleneck.

It is possible that the performance of std::function is worse than expected compared to function pointer calls.

Interrupt mode 2 jumps to the incorrect address

The code for interrupt mode 2 contains the following:

unsigned short pc = readByte(addr);
pc += ((unsigned short)readByte(addr)) << 8;

This sets the program counter to the incorrect address since it reads the same byte (addr) twice. The second line should read the next byte:

pc += ((unsigned short)readByte(addr + 1)) << 8;

I noticed this since Pacman uses interrupt mode 2 and it was jumping to the incorrect address.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.