diff --git a/API/Z80.h b/API/Z80.h index b4f497f..89ef210 100644 --- a/API/Z80.h +++ b/API/Z80.h @@ -27,12 +27,12 @@ * @brief Zilog Z80 CPU emulator. * * @details The Z80 library implements a fast, small and accurate emulator - * of the Zilog Z80. It emulates all that is known to date about this CPU, + * of the Zilog Z80 that emulates all that is known to date about this CPU, * including the undocumented behaviors, MEMPTR, Q and the special RESET. * * @version 0.2 - * @date 2023-01 - * @author Manuel Sainz de Baranda y Goñi. */ + * @date 2023-07-xx + * @author Manuel Sainz de Baranda y Goñi */ #ifdef Z80_DEPENDENCIES_HEADER # define Z80_H @@ -72,12 +72,24 @@ #define Z80_MAXIMUM_CYCLES (Z_USIZE_MAXIMUM - Z_USIZE(30)) -/** @brief Maximum number of clock cycles that @ref z80_run emulates - * when instructed to execute 1 clock cycle. */ +/** @brief Maximum number of clock cycles that @ref z80_run will + * emulate if instructed to execute 1 clock cycle. + * + * This is the number of clock cycles consumed by the longest instruction, not + * counting opcode fetch M-cycles of @c 0xDD or @c 0xFD prefixes, plus the 2 + * wait clock cycles that the CPU automatically adds to the maskable interrupt + * acknowledge M-cycle. For @ref z80_execute, this value is + * 2 clock cycles less. */ #define Z80_MAXIMUM_CYCLES_PER_STEP 23 -/** @brief Opcode interpreted as a hook by the Z80 library. It corresponds to +/** @brief Minimum number of clock cycles that @ref z80_run or + * @ref z80_execute will emulate if instructed to execute 1 clock + * cycle. */ + +#define Z80_MINIMUM_CYCLES_PER_STEP 4 + +/** @brief Opcode interpreted as a trap by the Z80 library. It corresponds to * the ld h,h instruction of the Z80 ISA. */ #define Z80_HOOK 0x64 @@ -94,8 +106,7 @@ /** @brief Defines a pointer to a @ref Z80 callback function invoked to * perform a read operation. * - * @param context The value of the @ref Z80::context member of the - * calling object. + * @param context The @ref Z80::context of the calling object. * @param address The memory address or I/O port to read from. * @return The byte read. */ @@ -104,39 +115,31 @@ typedef zuint8 (* Z80Read)(void *context, zuint16 address); /** @brief Defines a pointer to a @ref Z80 callback function invoked to * perform a write operation. * - * @param context The value of the @ref Z80::context member of the - * calling object. + * @param context The @ref Z80::context of the calling object. * @param address The memory address or I/O port to write to. * @param value The byte to write. */ typedef void (* Z80Write)(void *context, zuint16 address, zuint8 value); /** @brief Defines a pointer to a @ref Z80 callback function invoked to - * notify signal changes on the HALT line. + * notify a signal change on the HALT line. * - * @param context The value of the @ref Z80::context member of the - * calling object. - * @param state @c TRUE if the HALT line goes low (the CPU enters the HALT - * state); @c FALSE if the HALT line goes high (the CPU exits the HALT state). - * If the library has been built with special RESET support, the values - * @ref Z80_HALT_CANCEL and @ref Z80_HALT_EARLY_EXIT are also - * possible. */ + * @param context The @ref Z80::context of the calling object. + * @param signal A code specifying the type of signal change. */ -typedef void (* Z80Halt)(void *context, zuint8 state); +typedef void (* Z80Halt)(void *context, zuint8 signal); /** @brief Defines a pointer to a @ref Z80 callback function invoked to * notify an event. * - * @param context The value of the @ref Z80::context member of the - * calling object. */ + * @param context The @ref Z80::context of the calling object. */ typedef void (* Z80Notify)(void *context); /** @brief Defines a pointer to a @ref Z80 callback function invoked to * delegate the emulation of an illegal instruction. * - * @param context The value of the @ref Z80::context member of the - * calling object. + * @param context The @ref Z80::context of the calling object. * @param opcode The illegal opcode. * @return The number of clock cycles consumed by the instruction. */ @@ -146,8 +149,14 @@ typedef zuint8 (* Z80Illegal)(void *context, zuint8 opcode); * * @brief A Z80 CPU emulator. * - * @details @c Z80 contains the state of an emulated Z80 CPU and the callback - * pointers needed to interconnect it with the external logic. */ + * A @c Z80 object contains the state of an emulated Z80 CPU, pointers to + * callback functions that interconnect the emulator with the external logic + * and a context that is passed to these functions. + * + * Because no constructor function is provided, it is mandatory to directly + * initialize all callback pointers and @ref Z80::options before using + * an object of this type. Optional callbacks must be set to @c Z_NULL when not + * in use. */ typedef struct { @@ -218,58 +227,71 @@ typedef struct { Z80Write out; - /** @brief Invoked to notify signal changes on the HALT line. + /** @brief Invoked to notify a signal change on the HALT line. + * + * This callback is optional and must be set to @c Z_NULL when not in + * use. Its invocation is always deferred until the next emulation step + * so that the emulator can abort the signal change if any invalidating + * condition occurs, such as the acceptance of an interrupt during the + * execution of a @c halt instruction. * - * This callback indicates that the CPU is entering or exiting the HALT - * state. It is invoked after updating the value of @ref - * Z80::halt_line, which is passed as the second parameter to the - * function. + * The second parameter of the function specifies the type of signal + * change and can only contain a boolean value if the Z80 library has + * not been built with special RESET support: * - * When exiting the HALT state, this callback is invoked before - * @ref Z80::nmia or @ref Z80::inta. */ + * - @c TRUE indicates that the HALT line is going low during the last + * clock cycle of a @c halt instruction, which means that the CPU is + * entering the HALT state. + * + * - @c FALSE indicates that the HALT line is going high during the + * last clock cycle of an internal NOP executed during the HALT + * state, i.e., the CPU is exiting the HALT state due to an interrupt + * or normal RESET. + * + * If the library has been built with special RESET support, the values + * @ref Z80_HALT_EXIT_EARLY and @ref Z80_HALT_CANCEL + * are also possible. */ Z80Halt halt; /** @brief Invoked to perform an opcode fetch that corresponds to an * internal NOP. * - * This callback indicates the beginning of an opcode fetch M-cycle - * of 4 clock cycles that is generated in the following cases: + * This callback indicates the beginning of an opcode fetch M-cycle of + * 4 clock cycles that is generated in the following two cases: * * - During the HALT state, the CPU repeatedly executes an internal NOP - * that fetches the next opcode after @c halt without incrementing - * the PC register. This opcode is read again and again until an exit - * condition occurs (i.e., NMI, INT or RESET). In this case, the - * opcode is ignored unless a special RESET signal is received, as - * this causes the CPU to exit the HALT state immediately and execute - * the full instruction with no delay. + * that fetches the next opcode after the @c halt instruction without + * incrementing the PC register. This opcode is read again and again + * until an exit condition occurs (i.e., NMI, INT or RESET). * * - After detecting a special RESET signal, the CPU completes the - * ongoing instruction or interrupt response and then zeroes PC - * during the the next M1 cycle. If no interrupt has been accepted at - * the end of the instruction or interrupt response, the CPU produces - * an internal NOP to allow for the fetch-execute overlap to take - * place, during which it fetches the next opcode and zeroes PC. + * ongoing instruction or interrupt response and then zeroes the PC + * register during the first clock cycle of the next M1 cycle. If no + * interrupt has been accepted at the end of the instruction or + * interrupt response, the CPU produces an internal NOP to allow for + * the fetch-execute overlap to take place, during which it fetches + * the next opcode and zeroes PC. * - * This callback is optional. Setting it to @c Z_NULL is equivalent to - * setting the @ref Z80_OPTION_HALT_SKIP option. */ + * This callback is optional but note that setting it to @c Z_NULL is + * equivalent to enabling @ref Z80_OPTION_HALT_SKIP. */ Z80Read nop; /** @brief Invoked to perform an opcode fetch that corresponds to a * non-maskable interrupt acknowledge M-cycle. * - * This callback is optional and must be set to @c Z_NULL when not - * used. It indicates the beginning of an NMI acknowledge M-cycle. - * The value returned by the function is ignored. */ + * This callback is optional and must be set to @c Z_NULL when not in + * use. It indicates the beginning of an NMI acknowledge M-cycle. The + * value returned by the function is ignored. */ Z80Read nmia; /** @brief Invoked to perform a data bus read that corresponds to a * maskable interrupt acknowledge M-cycle. * - * This callback is optional and must be set to @c Z_NULL when not - * used. It indicates the beginning of an INT acknowledge M-cycle. The + * This callback is optional and must be set to @c Z_NULL when not in + * use. It indicates the beginning of an INT acknowledge M-cycle. The * function must return the byte that the interrupting I/O device * supplies to the CPU via the data bus during this M-cycle. * @@ -285,65 +307,85 @@ typedef struct { * @ref Z80::fetch but it is specific to the INT response in * mode 0. Ideally, the function should return a byte of instruction * data that the interrupting I/O device supplies to the CPU via the - * data bus, but depending on the emulated hardware, the device may - * not be able to do this during a memory read M-cycle because the - * memory is addressed instead, in which case the function must return - * the byte located at the memory address specified by the second + * data bus, but depending on the emulated hardware, the device may not + * be able to do this during a memory read M-cycle because the memory + * is addressed instead, in which case the function must return the + * byte located at the memory address specified by the second * parameter. * - * This callback is only used when @ref Z80::inta is not @c - * Z_NULL and returns an opcode that implies subsequent memory read - * M-cycles to fetch the non-opcode bytes of the instruction, thus - * it is safe not to initialize it or set it to @c Z_NULL if such a - * scenario is not possible. */ + * This callback will only be invoked if @ref Z80::inta is not + * @c Z_NULL and returns an opcode that implies subsequent memory read + * M-cycles to fetch the non-opcode bytes of the instruction, so it is + * safe not to initialize it or set it to @c Z_NULL if such a scenario + * is not possible. */ Z80Read int_fetch; - /** @brief Invoked when an ld i,a instruction is fetched. + /** @brief Invoked to notify that an ld i,a instruction has + * been fetched. * - * This callback is optional and must be set to @c Z_NULL when not - * used. It is invoked before the instruction copies the A register - * into the I register. */ + * This callback is optional and must be set to @c Z_NULL when not in + * use. It is invoked before the instruction copies the A register into + * the I register. */ Z80Notify ld_i_a; - /** @brief Invoked when an ld r,a instruction is fetched. + /** @brief Invoked to notify that an ld r,a instruction has + * been fetched. * - * This callback is optional and must be set to @c Z_NULL when not - * used. It is invoked before the instruction copies the A register - * into the R register. */ + * This callback is optional and must be set to @c Z_NULL when not in + * use. It is invoked before the instruction copies the A register into + * the R register. */ Z80Notify ld_r_a; - /** @brief Invoked when a @c reti instruction is fetched. + /** @brief Invoked to notify that a @c reti instruction has been + * fetched. * - * This callback is optional and must be set to @c Z_NULL when not - * used. It is invoked before executing the instruction. */ + * This callback is optional and must be set to @c Z_NULL when not in + * use. It is invoked before executing the instruction. */ Z80Notify reti; - /** @brief Callback invoked when a @c retn instruction is fetched. + /** @brief Invoked to notify that a @c retn instruction has been + * fetched. * - * This callback is optional and must be set to @c Z_NULL when not - * used. It is invoked before executing the instruction. */ + * This callback is optional and must be set to @c Z_NULL when not in + * use. It is invoked before executing the instruction. */ Z80Notify retn; - /** @brief Invoked when a trap is fecthed. + /** @brief Invoked when a trap is fetched. * - * This callback is optional and must be set to @c Z_NULL when not - * used. */ + * This callback is optional and must be set to @c Z_NULL when not in + * use, in which case the opcode of the trap will be executed normally. + * The function receives the memory address of the trap as the second + * parameter and must return the opcode to be executed instead of the + * trap. If the function returns a trap (i.e., @ref Z80_HOOK), + * the emulator will do nothing, so the trap will be fetched again + * unless the function has modified the PC register or replaced the + * trap in memory with another opcode. Also note that returning a trap + * does not revert the increment of the R register performed before + * each opcode fetch. */ Z80Read hook; - /** @brief Invoked to delegate the emulation of an illegal opcode. + /** @brief Invoked to delegate the execution of an illegal instruction. * - * @attention This callback is optional and must be set to @c Z_NULL - * when not used. */ + * This callback is optional and must be set to @c Z_NULL when not in + * use. Only those instructions with @c 0xED prefix that behave as a + * double @c nop of 8 clock cycles are considered illegal. The function + * receives the illegal opcode as the second argument and must take + * care of invoking the corresponding callbacks according to the type + * of M-cycles produced by the instruction. Finally, it must return the + * total number of clock cycles consumed by the instruction. At the + * time of invoking the callback, the PC register contains the memory + * address of the prefix and R has been incremented twice since the + * start of the instruction. */ Z80Illegal illegal; - /** @brief Temporary storage used for instruction fetch. */ + /** @brief Temporary storage used for instruction fetch. */ /* TODO */ ZInt32 data; @@ -365,24 +407,24 @@ typedef struct { ZInt16 xy; - ZInt16 memptr; /**< @brief MEMPTR register. */ - ZInt16 af; /**< @brief AF register. */ - ZInt16 bc; /**< @brief BC register. */ - ZInt16 de; /**< @brief DE register. */ - ZInt16 hl; /**< @brief HL register. */ - ZInt16 af_; /**< @brief AF' register. */ - ZInt16 bc_; /**< @brief BC' register. */ - ZInt16 de_; /**< @brief DE' register. */ - ZInt16 hl_; /**< @brief HL' register. */ - zuint8 r; /**< @brief R register. */ - zuint8 i; /**< @brief I register. */ + ZInt16 memptr; /**< @brief MEMPTR register, also known as WZ. */ + ZInt16 af; /**< @brief AF register. */ + ZInt16 bc; /**< @brief BC register. */ + ZInt16 de; /**< @brief DE register. */ + ZInt16 hl; /**< @brief HL register. */ + ZInt16 af_; /**< @brief AF' register. */ + ZInt16 bc_; /**< @brief BC' register. */ + ZInt16 de_; /**< @brief DE' register. */ + ZInt16 hl_; /**< @brief HL' register. */ + zuint8 r; /**< @brief R register. */ + zuint8 i; /**< @brief I register. */ /** @brief The most significant bit of the R register. * * The Z80 CPU increments the R register during each M1 cycle without * altering its most significant bit, commonly known as R7. However, - * the Z80 library performs normal increments for speed reasons, which - * eventually corrupts R7. + * the Z80 library only performs normal increments for speed reasons, + * which eventually corrupts R7. * * Before entering the execution loop, @ref z80_execute and * @ref z80_run copy @ref Z80::r into this member to @@ -415,7 +457,7 @@ typedef struct { * * This member specifies the different emulation options that are * enabled. It is mandatory to initialize it before using the emulator. - * Setting it to `0` disables all options. */ + * Setting it to @c 0 disables all options. */ zuint8 options; @@ -429,8 +471,8 @@ typedef struct { /** @brief State of the HALT line. * * The value of this member is @c TRUE if the HALT line is low; - * otherwise, @c FALSE. The emulator always updates this member before - * invoking the @ref Z80::halt callback. */ + * otherwise, @c FALSE. The emulator updates this member before + * invoking @ref Z80::halt, not after. */ zuint8 halt_line; } Z80; @@ -458,7 +500,8 @@ typedef struct { #define Z80_OPTION_XQ 8 /** @brief @ref Z80::options bitmask that enables notifications for any -`reti` or `retn` instruction executed during the interrupt mode 0 response. */ + * @c reti or @c retn instruction executed during the interrupt mode 0 + * response. */ #define Z80_OPTION_IM0_RETX_NOTIFICATIONS 16 @@ -527,14 +570,16 @@ typedef struct { #define Z80_RESUME_IM0_XY 3 -/** @brief Value of the @p state parameter of @ref Z80::halt when the - * HALT line goes high due to a special RESET signal. */ +/** @brief Value of the second parameter of @ref Z80::halt when the + * HALT line goes high due to a special RESET signal. + * + * */ #define Z80_HALT_EXIT_EARLY 2 -/** @brief Value of the @p state paratemer of @ref Z80::halt when the +/** @brief Value of the second paratemer of @ref Z80::halt when the * HALT line goes low and then high due to a special RESET signal during the - * execution of the `halt` instruction. */ + * execution of a @c halt instruction. */ #define Z80_HALT_CANCEL 3 @@ -709,6 +754,10 @@ typedef struct { #define Z80_L_(object) (object).hl_.uint8_values.at_0 +#define Z80_WZ Z80_MEMPTR /**< @brief Same as @ref Z80_MEMPTR. */ +#define Z80_WZH Z80_MEMPTRH /**< @brief Same as @ref Z80_MEMPTRH. */ +#define Z80_WZL Z80_MEMPTRL /**< @brief Same as @ref Z80_MEMPTRL. */ + Z_EXTERN_C_BEGIN /** @brief Sets the power state of a @ref Z80.