kosarev / z80

Fast and flexible Z80/i8080 emulator with C++ and Python APIs
MIT License
65 stars 10 forks source link

Support NMI #9

Closed simonowen closed 3 years ago

simonowen commented 3 years ago

Could I request the core support an initiate_nmi for NMI? Here's what I'm using at the moment, which seems to work for me:

    void initiate_nmi() {
        self().on_set_iff1(false);

        fast_u16 pc = self().on_get_pc();

        // Get past the HALT instruction, if halted. Note that
        // HALT instructions need to be executed at least once to
        // be skipped on an interrupt, so checking if the PC is
        // at a HALT instruction is not enough here.
        if(self().on_is_halted()) {
            pc = inc16(pc);
            self().on_set_pc(pc);
            self().on_set_is_halted(false);
        }

        self().on_inc_r_reg();
        self().on_tick(2);
        self().on_push(pc);

        self().on_jump(0x0066);
    }

It's mostly a stripped down version of initiate_int but it doesn't clear iff2 has a different fixed handler address, and reduced timing. It's probably worth checking the details!

kosarev commented 3 years ago

1057b95 tries to support it. I gave it 5 ticks to read the opcode as my sources say, e.g., at https://rk.nvg.ntnu.no/sinclair/faq/tech_z80.html:

When an NMI occurs, it takes 11 T states to get to #0066: a 5 T state M1 cycle to do an opcode read and decrement SP, a 3 T state M2 cycle to write the high byte of PC to the stack and decrement SP and finally a 3 T state M3 cycle to write the low byte of PC and jump to #0066.

The Sean's paper seems to agree with that saying that NMI takes 11 ticks.

simonowen commented 3 years ago

Thanks! I've tested it and it's working as expected. It's hard to measure timings from NMI as it's usually an asynchronous button press, but good to have it matching known sources.