From 547a9f5f6867b2ed7d8e2474e26f9372631e0a55 Mon Sep 17 00:00:00 2001 From: Jose Hernandez Date: Mon, 29 Dec 2025 22:55:15 +0000 Subject: [PATCH 01/12] Updated CMake configuration for improved compatibility and added numerous optimization changes to speedup runtime performance. --- .clang-format | 82 + .clang-tidy | 45 + .github/workflows/code-quality.yml | 68 + .gitignore | 4 +- CMakeLists.txt | 273 +- README.md | 310 +- cmake/z80cppConfig.cmake.in | 9 + example/README.md | 8 - example/z80sim.cpp | 158 - example/z80sim.h | 46 - include/z80.h | 5393 ++++++++++++++++++++++++++- include/z80_bus_interface.h | 98 + include/z80_types.h | 50 + include/z80operations.h | 47 - src/z80.cpp | 5462 ---------------------------- tests/CMakeLists.txt | 81 + tests/benchmark_shared.h | 210 ++ tests/benchmark_shared.hpp | 210 ++ tests/roms/.gitkeep | 0 tests/z80_benchmark_test.cpp | 271 ++ tests/z80_game_test.cpp | 165 + tests/z80_sim_test.cpp | 218 ++ tests/z80_sim_test.h | 61 + {example => tests}/zexall.bin | Bin 24 files changed, 7313 insertions(+), 5956 deletions(-) create mode 100644 .clang-format create mode 100644 .clang-tidy create mode 100644 .github/workflows/code-quality.yml create mode 100644 cmake/z80cppConfig.cmake.in delete mode 100644 example/README.md delete mode 100644 example/z80sim.cpp delete mode 100644 example/z80sim.h create mode 100644 include/z80_bus_interface.h create mode 100644 include/z80_types.h delete mode 100644 include/z80operations.h delete mode 100644 src/z80.cpp create mode 100644 tests/CMakeLists.txt create mode 100644 tests/benchmark_shared.h create mode 100644 tests/benchmark_shared.hpp create mode 100644 tests/roms/.gitkeep create mode 100644 tests/z80_benchmark_test.cpp create mode 100644 tests/z80_game_test.cpp create mode 100644 tests/z80_sim_test.cpp create mode 100644 tests/z80_sim_test.h rename {example => tests}/zexall.bin (100%) diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..55db36c --- /dev/null +++ b/.clang-format @@ -0,0 +1,82 @@ +--- +Language: Cpp +Standard: c++17 +BasedOnStyle: LLVM + +# Line length +ColumnLimit: 120 + +# Indentation +IndentWidth: 4 +UseTab: Never +IndentCaseLabels: true +IndentPPDirectives: AfterHash + +# Spacing +SpaceBeforeParens: ControlStatements +SpaceAfterCStyleCast: false +SpacesInAngles: false +SpaceInEmptyParentheses: false +SpaceBeforeAssignmentOperators: true + +# Pointers and references +PointerAlignment: Left + +# Braces +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false + +# Function definitions +AllowShortFunctionsOnASingleLine: None +SeparateDefinitionBlocks: Always +AllowShortBlocksOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true + +# Constructor/Destructor +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +Cpp11BracedListStyle: true + +# Operators +BreakBeforeBinaryOperators: NonAssignment +BreakBeforeTernaryOperators: true + +# Includes +SortIncludes: true +IncludeBlocks: Preserve + +# Comments +ReflowComments: true + +# Other +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: None +AlignConsecutiveDeclarations: None +AlignConsecutiveMacros: true +AlignEscapedNewlines: Left +AlignTrailingComments: true + +AccessModifierOffset: -2 +CompactNamespaces: false +FixNamespaceComments: true +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..5c45a3b --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,45 @@ +--- +# Clang-tidy configuration for Z80 emulator project +Checks: | + -*, + readability-*, + performance-*, + modernize-*, + bugprone-*, + clang-diagnostic-*, + cppcoreguidelines-*, + google-*, + -readability-magic-numbers, + -readability-uppercase-literal-suffix, + -cppcoreguidelines-avoid-magic-numbers, + -cppcoreguidelines-pro-bounds-array-to-pointer-decay, + -cppcoreguidelines-pro-type-cstyle-cast, + -cppcoreguidelines-pro-type-reinterpret-cast, + -google-readability-function-size, + -google-runtime-references, + -modernize-use-trailing-return-type, + -cppcoreguidelines-pro-bounds-constant-array-index, + -readability-function-cognitive-complexity, + -readability-function-size + +WarningsAsErrors: '' + +# Configure individual checks +CheckOptions: + - key: 'readability-function-cognitive-complexity.Threshold' + value: '25' + - key: 'readability-function-size.StatementThreshold' + value: '800' + - key: 'readability-identifier-length.MinimumVariableNameLength' + value: '2' + - key: 'readability-identifier-length.MinimumParameterNameLength' + value: '2' + - key: 'readability-identifier-length.MinimumLoopCounterNameLength' + value: '1' + - key: 'readability-line-length.LineLength' + value: '120' + - key: 'bugprone-easily-swappable-parameters.MinimumLength' + value: '3' + +HeaderFilterRegex: '.*' +InheritParentConfig: true diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml new file mode 100644 index 0000000..664d209 --- /dev/null +++ b/.github/workflows/code-quality.yml @@ -0,0 +1,68 @@ +name: Code Quality + +on: [push, pull_request] + +jobs: + formatting: + name: Check Code Formatting + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install clang-format + run: sudo apt-get install -y clang-format + + - name: Check formatting + run: | + files=$(find include tests -type f \( -name "*.cpp" -o -name "*.h" -o -name "*.tpp" \)) + for file in $files; do + clang-format --dry-run -Werror "$file" || exit 1 + done + echo "All files are properly formatted!" + + static-analysis: + name: Static Analysis with clang-tidy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y clang-tidy cmake build-essential + + - name: Build with compile_commands.json + run: | + mkdir -p build + cd build + cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON .. + make + + - name: Run clang-tidy + run: | + cd build + files=$(find ../tests -type f -name "*.cpp") + clang-tidy -p . $files + + build: + name: Build Project + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y cmake build-essential + + - name: Build + run: | + mkdir -p build + cd build + cmake .. + make -j4 + + - name: Run tests + run: | + cd build + ctest --output-on-failure diff --git a/.gitignore b/.gitignore index 15c6001..c0f7fda 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ .cache/ .idea/ .vscode/ +.DS_Store build*/ -cmake-build*/ -example/z80sim +tests/roms/* diff --git a/CMakeLists.txt b/CMakeLists.txt index d60ed4a..ae775f0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,69 +1,240 @@ -# By default, CMake generates standard UNIX makefiles. To generate an XCode -# project on MacOS, invoke CMake from the build directory as shown below: -# -# cmake -G Xcode .. -# - cmake_minimum_required (VERSION 3.25.1) -project (z80cpp) -# Set the rpath policy and C++ version for MacOS builds +project( + z80cpp + VERSION 1.0.0 + DESCRIPTION "Z80 emulator core" + LANGUAGES CXX +) + +# Prevent in-source builds +if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) + message(FATAL_ERROR "In-source builds not allowed. Please run cmake from a separate build directory.") +endif() + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Standard output directories +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + +include(GNUInstallDirs) +include(CMakePackageConfigHelpers) +include(CTest) +include(CheckIPOSupported) + +# Set default build type to Release if not specified +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Debug Release RelWithDebInfo MinSizeRel) +endif() + +# Set the rpath policy for MacOS builds if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - # set the RPATH policy set(CMAKE_MACOSX_RPATH 1) +endif() + +# Option for native architecture optimization (disable for distributable binaries) +option(Z80CPP_NATIVE_ARCH "Use -march=native for local optimization" ON) - # specify the C++ standard - set(CMAKE_CXX_STANDARD 14) - set(CMAKE_CXX_STANDARD_REQUIRED True) +# Option for Link-Time Optimization (LTO) +option(Z80CPP_ENABLE_LTO "Enable Link-Time Optimization" ON) + +# Check LTO support and enable if requested +if(Z80CPP_ENABLE_LTO) + check_ipo_supported(RESULT Z80CPP_LTO_SUPPORTED OUTPUT Z80CPP_LTO_OUTPUT) + if(Z80CPP_LTO_SUPPORTED) + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) + message(STATUS "Link-Time Optimization (LTO): Enabled") + else() + message(WARNING "LTO requested but not supported by compiler: ${Z80CPP_LTO_OUTPUT}") + endif() +else() + message(STATUS "Link-Time Optimization (LTO): Disabled") endif() -set (API_REVISION 1) -set (VERSION_MAJOR 0) -set (VERSION_MINOR 0) -set (RELEASE_TYPE "") -set (VERSION_STR "${API_REVISION}.${VERSION_MAJOR}.${VERSION_MINOR}${RELEASE_TYPE}") +# Option for ccache (compiler cache for faster rebuilds) +option(Z80CPP_ENABLE_CCACHE "Enable ccache for faster rebuilds" ON) -if (CMAKE_COMPILER_IS_GNUCXX) - set (CMAKE_CXX_FLAGS "-Wall -O3 -std=c++14") -endif () +# Check for ccache and enable if available +if(Z80CPP_ENABLE_CCACHE) + find_program(CCACHE_EXECUTABLE ccache) + if(CCACHE_EXECUTABLE) + message(STATUS "ccache found: ${CCACHE_EXECUTABLE}") + set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE_EXECUTABLE}) + set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE_EXECUTABLE}) + message(STATUS "ccache: Enabled for faster rebuilds") + else() + message(STATUS "ccache: Disabled") + endif() +endif() -include_directories(BEFORE . include) +# Option for parallel builds (default: use all available CPU cores) +option(Z80CPP_PARALLEL_BUILD "Enable parallel builds using all CPU cores" ON) -set (z80cpp_sources src/z80.cpp include/z80.h include/z80operations.h ) -add_library (z80cpp-static STATIC ${z80cpp_sources}) -set_target_properties (z80cpp-static PROPERTIES OUTPUT_NAME z80cpp) -if (NOT DEFINED Z80CPP_STATIC_ONLY) - add_library (z80cpp SHARED ${z80cpp_sources}) -# Affects Win32 only: avoid dynamic/static *.lib files naming conflict - set_target_properties (z80cpp-static PROPERTIES PREFIX "lib") +if(Z80CPP_PARALLEL_BUILD) + include(ProcessorCount) + ProcessorCount(N) + if(NOT N EQUAL 0) + set(CMAKE_BUILD_PARALLEL_LEVEL ${N}) + message(STATUS "Parallel Build: Enabled (${N} CPU cores)") + else() + message(STATUS "Parallel Build: Could not detect CPU cores, using system default") + endif() +endif() + +# Set optimization flags for all compilers +if (CMAKE_CXX_COMPILER_ID MATCHES "GNU") + # GCC + message(STATUS "Configuring for GCC") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") + if (CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -fomit-frame-pointer -funroll-loops") + if(Z80CPP_NATIVE_ARCH) + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native -mtune=native") + endif() + endif() +elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "AppleClang") + # Clang and AppleClang + message(STATUS "Configuring for Clang/AppleClang") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") + if (CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -fomit-frame-pointer -funroll-loops") + if(Z80CPP_NATIVE_ARCH) + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native -mtune=native") + endif() + endif() +elseif (MSVC) + # MSVC + message(STATUS "Configuring for MSVC") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W3") + if (CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /O2 /Ob2 /Oi /Ot /GL") + endif() endif () -if (NOT DEFINED Z80CPP_STATIC_ONLY) - set_target_properties(z80cpp - PROPERTIES VERSION ${VERSION_STR} SOVERSION ${API_REVISION} +message(STATUS "Final CXX flags: ${CMAKE_CXX_FLAGS}") + +option(Z80CPP_ENABLE_TESTING "Build test simulator target" ${BUILD_TESTING}) + +if(Z80CPP_ENABLE_TESTING) + set(CMAKE_CTEST_ARGUMENTS --verbose --output-on-failure) +endif() + + +set(z80cpp_headers + include/z80.h + include/z80_bus_interface.h + include/z80_types.h +) + +# Helper function to configure common properties for header-only library +function(z80cpp_configure_target target_name) + target_compile_features(${target_name} INTERFACE cxx_std_14) + target_include_directories(${target_name} + INTERFACE + $ + $ ) -endif () -set_target_properties(z80cpp-static - PROPERTIES VERSION ${VERSION_STR} SOVERSION ${API_REVISION} +endfunction() + +# Create header-only library +add_library(z80cpp INTERFACE) +add_library(z80cpp::z80cpp ALIAS z80cpp) +z80cpp_configure_target(z80cpp) + +# For backward compatibility, also create z80cpp-static as an alias +add_library(z80cpp-static INTERFACE) +add_library(z80cpp::z80cpp-static ALIAS z80cpp-static) +z80cpp_configure_target(z80cpp-static) + +# Install targets +install( + TARGETS z80cpp z80cpp-static + EXPORT z80cppTargets + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/z80cpp ) -if ("${CMAKE_CPP_IMPLICIT_LINK_DIRECTORIES}" MATCHES "lib64") - set (LIB_DIR "lib64") -else () - set (LIB_DIR "lib") -endif () -if (NOT DEFINED Z80CPP_STATIC_ONLY) - install (TARGETS z80cpp LIBRARY DESTINATION ${LIB_DIR} ARCHIVE DESTINATION ${LIB_DIR}) -endif () +if(Z80CPP_ENABLE_TESTING AND BUILD_TESTING) + enable_testing() + add_subdirectory(tests) +endif() + +# Installation of headers (project level) +install( + DIRECTORY include/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/z80cpp + FILES_MATCHING + PATTERN "*.h" + PATTERN "*.tpp" +) -# Enable running the test simulator as part the `make test` target -set( TEST_SOURCES example/z80sim.cpp example/z80sim.h ) -add_executable( z80sim ${TEST_SOURCES} ) -target_link_libraries( z80sim z80cpp-static ) -configure_file( example/zexall.bin zexall.bin COPYONLY ) +# Package Configuration +install( + EXPORT z80cppTargets + NAMESPACE z80cpp:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/z80cpp +) -enable_testing( ) -add_test( NAME z80sim COMMAND z80sim ) +configure_package_config_file( + ${CMAKE_CURRENT_LIST_DIR}/cmake/z80cppConfig.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/z80cppConfig.cmake + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/z80cpp +) -install( TARGETS z80cpp-static LIBRARY DESTINATION ${LIB_DIR} ARCHIVE DESTINATION ${LIB_DIR} ) -install( DIRECTORY include/ DESTINATION include/z80cpp PATTERN "*.h" ) +write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/z80cppConfigVersion.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion +) + +install( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/z80cppConfig.cmake + ${CMAKE_CURRENT_BINARY_DIR}/z80cppConfigVersion.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/z80cpp +) + +# Clang-format support +find_program(CLANG_FORMAT_EXECUTABLE clang-format) +if(CLANG_FORMAT_EXECUTABLE) + file(GLOB_RECURSE ALL_SOURCE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/*.tpp + ${CMAKE_CURRENT_SOURCE_DIR}/tests/*.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tests/*.h + ) + + add_custom_target( + format + COMMENT "Running clang-format to format source files..." + COMMAND ${CLANG_FORMAT_EXECUTABLE} -i ${ALL_SOURCE_FILES} + ) + + add_custom_target( + format-check + COMMENT "Checking code formatting with clang-format..." + COMMAND ${CLANG_FORMAT_EXECUTABLE} --dry-run -Werror ${ALL_SOURCE_FILES} + ) +endif() + +# Clang-tidy support +find_program(CLANG_TIDY_EXECUTABLE NAMES clang-tidy clang-tidy-17 clang-tidy-16) +if(CLANG_TIDY_EXECUTABLE) + file(GLOB_RECURSE ALL_SOURCE_FILES_CPP + ${CMAKE_CURRENT_SOURCE_DIR}/tests/*.cpp + ) + + add_custom_target(lint + COMMENT "Running clang-tidy..." + ) + + foreach(_file IN LISTS ALL_SOURCE_FILES_CPP) + add_custom_command(TARGET lint POST_BUILD + COMMAND ${CLANG_TIDY_EXECUTABLE} -quiet "${_file}" -- -std=c++17 + COMMENT "Linting ${_file}" + ) + endforeach() +endif() diff --git a/README.md b/README.md index fc1b67b..b8db352 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,305 @@ -## z80cpp -#### Z80 core in C++ +# z80cpp - Z80 Core in C++ -That's a port from Java to C++ of my [Z80Core](https://github.com/jsanchezv/Z80Core). +This project is a C++ port of the Java-based [Z80Core](https://github.com/jsanchezv/Z80Core). It provides a highly optimized, standards-compliant Z80 emulator core. -To build: -``` +## Features + +* **Complete Instruction Set**: + * Full emulation of the Z80 instruction set. + * Emulates the undocumented bits 3 & 5 from flags register + * Emulates the MEMPTR register (known as WZ in official Zilog documentation) + * Strict execution order for every instruction + * Precise timing for all instructions, totally decoupled from the core +* **High Performance**: Optimized for modern C++ compilers with specific performance tuning. +* **Code Quality**: Enforced via clang-format, clang-tidy, and continuous integration. +* **Build System**: Modern CMake build system with support for caching and parallel builds. + + +## Usage Example + +See the *test* directory for an example use case. + +*jspeccy at gmail dot com* + + +--- + +## 🚀 Quick Start + +### Prerequisites + +* **CMake** (3.25+) +* **C++ Compiler** (GCC, Clang, or MSVC supporting C++17) +* **Tools** (Optional but recommended): `ccache`, `clang-format`, `clang-tidy`, `cppcheck` + +### Build and Run + +```bash mkdir build cd build cmake .. -make +make -j$(nproc) ``` -Then, you have an use case at dir *example*. -The core have the same features of [Z80Core](https://github.com/jsanchezv/Z80Core): +### Run Tests -* Complete instruction set emulation -* Emulates the undocumented bits 3 & 5 from flags register -* Emulates the MEMPTR register (known as WZ in official Zilog documentation) -* Strict execution order for every instruction -* Precise timing for all instructions, totally decoupled from the core +```bash +make test +``` -*jspeccy at gmail dot com* +--- + +## 🛠️ Building the Project + +### Build Options + +You can configure the build using the following CMake options: + +| Option | Default | Description | +|-------------------------|---------|-------------------------------------------------| +| `Z80CPP_NATIVE_ARCH` | `ON` | Use `-march=native` for local CPU optimization. | +| `Z80CPP_ENABLE_LTO` | `ON` | Enable Link-Time Optimization. | +| `Z80CPP_BUILD_SHARED` | `ON` | Build shared library. | +| `Z80CPP_BUILD_STATIC` | `ON` | Build static library. | +| `Z80CPP_ENABLE_TESTING` | `ON` | Build test suite. | +| `Z80CPP_ENABLE_CCACHE` | `ON` | Enable ccache for faster rebuilds. | +| `Z80CPP_PARALLEL_BUILD` | `ON` | Enable parallel builds using all CPU cores. | + +### Build Configurations + +#### Maximum Performance (Local Use) +Optimized for the machine you are building on. +```bash +cmake -DCMAKE_BUILD_TYPE=Release \ + -DZ80CPP_NATIVE_ARCH=ON \ + -DZ80CPP_ENABLE_LTO=ON \ + .. +make -j$(nproc) +``` + +#### Distribution Build +Portable binary without architecture-specific instructions. +```bash +cmake -DCMAKE_BUILD_TYPE=Release \ + -DZ80CPP_NATIVE_ARCH=OFF \ + -DZ80CPP_ENABLE_LTO=ON \ + .. +make -j$(nproc) +``` + +### Build Performance + +The project automatically optimizes build times using: +* **ccache**: Compiler cache for reusing compiled objects (auto-enabled if installed). +* **Parallel Builds**: Uses all available CPU cores by default. + +To install `ccache`: +* **macOS**: `brew install ccache` +* **Ubuntu/Debian**: `sudo apt-get install ccache` +* **Fedora/RHEL**: `sudo dnf install ccache` + +--- + +## ⚡ Performance Optimizations + +This fork implements several optimizations to improve emulation speed. + +### Optimization Summary + +| Priority | Optimization | Expected Gain | Status | +|----------|-----------------------------------|---------------|---------------------------| +| 1 | Jump table dispatch | 15-30% | Not implemented | +| 2 | Force inline bus calls | 10-20% | ✅ Implemented | +| 3 | Combined flag table | 5-10% | Not implemented | +| 4 | Inline carry flag | 5-8% | Not implemented | +| 5 | Branch hints | 3-5% | ✅ Implemented | +| 6 | Profile-Guided Optimization (PGO) | 5-15% | Not implemented | +| 7 | Memory prefetch | 2-4% | Not implemented | +| 8 | Restrict pointers | 2-3% | ✅ Implemented | +| 9 | Reduce flagQ writes | 1-2% | ✅ Implemented | +| 10 | Full Link-Time Optimization (LTO) | 2-5% | ✅ Implemented | +| 11 | Prefix shadowing / register swap | 1-3% | Not implemented | +| 12 | Cache-friendly member layout | 2-5% | ⚠️ Partial (alignas only) | + +### Detailed Descriptions + +#### 1. Jump Table / Computed Goto +Replaces large `switch` statements in opcode decoding with function pointer tables or computed gotos (GCC/Clang) to reduce branch misprediction. + +#### 2. Inline Bus Interface Calls +Uses compiler attributes (`__attribute__((always_inline))`, `__forceinline`) to ensure critical memory access methods (`fetchOpcode`, `peek8`, `poke8`) are fully inlined. + +#### 3. Combined Flag Tables +Combines four separate 256-byte flag tables into a single structure array to improve cache locality and reduce cache pressure. + +--- + +## 🧹 Code Quality & Tools + +This project uses **clang-format** and **clang-tidy** to maintain consistent code style and detect issues. + +### Installation + +* **macOS**: `brew install clang-format llvm` +* **Linux (Ubuntu/Debian)**: `sudo apt install clang-format clang-tools` +* **Linux (Fedora/RHEL)**: `sudo dnf install clang-tools-extra` + +### Usage + +From the `build/` directory: + +| Command | Description | +|---------------------|--------------------------------------------------------------------| +| `make format` | Format all source files. | +| `make format-check` | Verify formatting without modifying files. | +| `make lint` | Run clang-tidy static analysis (requires `compile_commands.json`). | +| `make lint-fix` | Run clang-tidy and automatically fix issues. | + +### IDE Integration + +* **VS Code**: Install the C/C++ extension. It will automatically use the `.clang-format` file. +* **CLion**: Go to Settings → Editor → Code Style → C/C++ and enable clang-format. +* **Xcode**: Use the CMake targets (`make format`) via build settings. + +--- + +## 🧪 Testing + +The project includes a test suite to verify the emulator's correctness. + +### Running Tests + +1. Build the project with testing enabled (default). +2. Run the tests using `make test` or `ctest`. + +```bash +cd build +make test +``` + +To run the simulator manually: +```bash +./bin/z80_sim_test +``` + +--- + +## ❓ Troubleshooting + +| Issue | Solution | +|-----------------------------------|----------------------------------------------------| +| "clang-format not found" | Install using package manager (brew/apt/dnf). | +| "CMake targets not found" | Run `cmake ..` from the `build/` directory. | +| "compile_commands.json not found" | Run `cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON ..`. | + +## Performance Benchmarks + +### Z80all instruction exerciser timings + +This table presents detailed timing measurements from the Z80all instruction exerciser, which systematically tests individual Z80 instruction groups and their variants. + +**Column descriptions:** +- **Test**: Instruction or instruction group being tested +- **Time (sec)**: Execution time in seconds for the test (baseline) +- **Optimized (sec)**: Execution time in seconds after optimizations + +| # | Instruction group | Baseline (sec) | Optimized (sec) | Speedup (%) | +|-------------|------------------------------|---------------:|----------------:|------------:| +| 1 | hl, | 4.290 | 0.870 | 79.7% | +| 2 | add hl, | 2.098 | 0.420 | 80.0% | +| 3 | add ix, | 2.125 | 0.420 | 80.2% | +| 4 | add iy, | 2.120 | 0.426 | 79.9% | +| 5 | aluop a,nn | 1.063 | 0.213 | 79.9% | +| 6 | aluop a, | 37.504 | 7.422 | 80.1% | +| 7 | aluop a, | 19.405 | 3.792 | 80.5% | +| 8 | aluop a,(+1) | 9.732 | 1.922 | 80.3% | +| 9 | bit n,(+1) | 0.076 | 0.014 | 81.6% | +| 10 | bit n, | 2.462 | 0.481 | 80.5% | +| 11 | cpd | 0.475 | 0.094 | 80.2% | +| 12 | cpi | 0.475 | 0.093 | 80.4% | +| 13 | | 2.073 | 0.410 | 80.2% | +| 14 | a | 0.111 | 0.022 | 80.2% | +| 15 | b | 0.111 | 0.021 | 81.1% | +| 16 | bc | 0.058 | 0.011 | 81.0% | +| 17 | c | 0.112 | 0.022 | 80.4% | +| 18 | d | 0.112 | 0.022 | 80.4% | +| 19 | de | 0.056 | 0.011 | 80.4% | +| 20 | e | 0.112 | 0.021 | 81.3% | +| 21 | h | 0.111 | 0.022 | 80.2% | +| 22 | hl | 0.057 | 0.011 | 80.7% | +| 23 | ix | 0.057 | 0.011 | 80.7% | +| 24 | iy | 0.057 | 0.011 | 80.7% | +| 25 | l | 0.112 | 0.022 | 80.4% | +| 26 | (hl) | 0.111 | 0.022 | 80.2% | +| 27 | sp | 0.057 | 0.011 | 80.7% | +| 28 | (+1) | 0.235 | 0.046 | 80.4% | +| 29 | ixh | 0.112 | 0.022 | 80.4% | +| 30 | ixl | 0.114 | 0.022 | 80.7% | +| 31 | iyh | 0.112 | 0.022 | 80.4% | +| 32 | iyl | 0.112 | 0.022 | 80.4% | +| 33 | ld ,(nnnn) | 0.001 | 0.000 | 100.0% | +| 34 | ld hl,(nnnn) | 0.000 | 0.000 | N/A | +| 35 | ld sp,(nnnn) | 0.000 | 0.000 | N/A | +| 36 | ld ,(nnnn) | 0.001 | 0.000 | 100.0% | +| 37 | ld (nnnn), | 0.002 | 0.000 | 100.0% | +| 38 | ld (nnnn),hl | 0.000 | 0.000 | N/A | +| 39 | ld (nnnn),sp | 0.000 | 0.000 | N/A | +| 40 | ld (nnnn), | 0.002 | 0.000 | 100.0% | +| 41 | ld ,nnnn | 0.002 | 0.000 | 100.0% | +| 42 | ld ,nnnn | 0.001 | 0.000 | 100.0% | +| 43 | ld a,<(bc),(de)> | 0.001 | 0.000 | 100.0% | +| 44 | ld ,nn | 0.002 | 0.000 | 100.0% | +| 45 | ld (+1),nn | 0.001 | 0.000 | 100.0% | +| 46 | ld ,(+1) | 0.019 | 0.004 | 78.9% | +| 47 | ld ,(+1) | 0.009 | 0.001 | 88.9% | +| 48 | ld a,(+1) | 0.004 | 0.000 | 100.0% | +| 49 | ld ,nn | 0.001 | 0.000 | 100.0% | +| 50 | ld , | 0.177 | 0.033 | 81.4% | +| 51 | ld , | 0.355 | 0.069 | 80.6% | +| 52 | ld a,(nnnn) / ld (nnnn),a | 0.001 | 0.000 | 100.0% | +| 53 | ldd (1) | 0.001 | 0.000 | 100.0% | +| 54 | ldd (2) | 0.001 | 0.000 | 100.0% | +| 55 | ldi (1) | 0.001 | 0.000 | 100.0% | +| 56 | ldi (2) | 0.001 | 0.000 | 100.0% | +| 57 | neg | 0.485 | 0.096 | 80.2% | +| 58 | | 0.264 | 0.052 | 80.3% | +| 59 | | 0.226 | 0.045 | 80.1% | +| 60 | shf/rot (+1) | 0.015 | 0.003 | 80.0% | +| 61 | shf/rot | 0.348 | 0.069 | 80.2% | +| 62 | n, | 0.346 | 0.068 | 80.3% | +| 63 | n,(+1) | 0.016 | 0.003 | 81.3% | +| 64 | ld (+1), | 0.044 | 0.009 | 79.5% | +| 65 | ld (+1), | 0.009 | 0.001 | 88.9% | +| 66 | ld (+1),a | 0.002 | 0.000 | 100.0% | +| 67 | ld (),a | 0.003 | 0.000 | 100.0% | +| **Total** | | **88.158** | **17.404** | **80.3%** | +| **Average** | | **1.316** | **0.260** | **80.3%** | + + +### Z80 Performance Benchmark Test Suite + +This test suite measures the performance of the Z80 emulator core across various instruction patterns and workloads, demonstrating both throughput (MT/s) and instruction execution rate (MIPS). + +**Column descriptions:** +- **Test**: Name of the benchmark test +- **Elapsed (sec)**: Total execution time in seconds +- **T-States**: Total CPU clock cycles executed +- **MT/s**: Million T-States per second (throughput) +- **MIPS**: Million Instructions Per Second + +| Test | Elapsed (sec) | T-States | MT/s | MIPS | +|------------------|--------------:|--------------:|--------:|------:| +| ZEXALL | 13.241 | 8,099,612,806 | 611.700 | 0.755 | +| Instruction Mix | 11.137 | 6,812,499,998 | 611.676 | 0.449 | +| Memory Intensive | 4.136 | 4,000,039,014 | 967.207 | 0.484 | +| Arithmetic Heavy | 10.093 | 4,781,818,189 | 473.791 | 0.495 | +| Branch Heavy | 4.114 | 4,000,410,001 | 972.331 | 0.729 | + + +| Test | Elapsed (sec) | Performance (MIPS) | Speedup (x) | +|------------------|--------------:|-------------------:|------------:| +| ZEXALL | 0.04 | 240.30 | 555.15 | +| Instruction Mix | 0.02 | 332.64 | 647.47 | +| Memory Intensive | 0.00 | 48096.58 | 680.46 | +| Arithmetic Heavy | 0.01 | 365.40 | 499.23 | +| Branch Heavy | 0.00 | 6954.51 | 589.48 | diff --git a/cmake/z80cppConfig.cmake.in b/cmake/z80cppConfig.cmake.in new file mode 100644 index 0000000..9fa738d --- /dev/null +++ b/cmake/z80cppConfig.cmake.in @@ -0,0 +1,9 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +if(NOT TARGET z80cpp::z80cpp AND NOT TARGET z80cpp::z80cpp-static) + include("${CMAKE_CURRENT_LIST_DIR}/z80cppTargets.cmake") +endif() + +check_required_components(z80cpp) diff --git a/example/README.md b/example/README.md deleted file mode 100644 index 2e06af4..0000000 --- a/example/README.md +++ /dev/null @@ -1,8 +0,0 @@ -* That's an use case for z80cpp library. Compile with: - -g++ -Wall -O3 -std=c++14 -I../include z80sim.cpp -o z80sim -L../build -lz80cpp -Wl,-rpath=../build/ - -after build the library (of course). - -Alternatively run `make test` (or `ctest --verbose` to see the test output) -from within the build directory to run the test simulator. diff --git a/example/z80sim.cpp b/example/z80sim.cpp deleted file mode 100644 index bab3a9b..0000000 --- a/example/z80sim.cpp +++ /dev/null @@ -1,158 +0,0 @@ -#include "z80sim.h" - -using namespace std; - -Z80sim::Z80sim() : cpu(this) -{ - -} - -Z80sim::~Z80sim() = default; - -uint8_t Z80sim::fetchOpcode(uint16_t address) { - // 3 clocks to fetch opcode from RAM and 1 execution clock - tstates += 4; - -#ifdef WITH_BREAKPOINT_SUPPORT - return z80Ram[address]; -#else - uint8_t opcode = z80Ram[address]; - return (address != 0x0005 ? opcode : breakpoint(address, opcode)); -#endif - -} - -uint8_t Z80sim::peek8(uint16_t address) { - // 3 clocks for read byte from RAM - tstates += 3; - return z80Ram[address]; -} - -void Z80sim::poke8(uint16_t address, uint8_t value) { - // 3 clocks for write byte to RAM - tstates += 3; - z80Ram[address] = value; -} - -uint16_t Z80sim::peek16(uint16_t address) { - // Order matters, first read lsb, then read msb, don't "optimize" - uint8_t lsb = peek8(address); - uint8_t msb = peek8(address + 1); - return (msb << 8) | lsb; -} - -void Z80sim::poke16(uint16_t address, RegisterPair word) { - // Order matters, first write lsb, then write msb, don't "optimize" - poke8(address, word.byte8.lo); - poke8(address + 1, word.byte8.hi); -} - -uint8_t Z80sim::inPort(uint16_t port) { - // 4 clocks for read byte from bus - tstates += 3; - return z80Ports[port]; -} - -void Z80sim::outPort(uint16_t port, uint8_t value) { - // 4 clocks for write byte to bus - tstates += 4; - z80Ports[port] = value; -} - -void Z80sim::addressOnBus(uint16_t address, int32_t tstates) { - // Additional clocks to be added on some instructions - this->tstates += tstates; -} - -void Z80sim::interruptHandlingTime(int32_t tstates) { - this->tstates += tstates; -} - -bool Z80sim::isActiveINT() { - // Put here the needed logic to trigger an INT - return false; -} - -#ifdef WITH_EXEC_DONE -void Z80sim::execDone(void) {} -#endif - -uint8_t Z80sim::breakpoint(uint16_t address, uint8_t opcode) { - // Emulate CP/M Syscall at address 5 - -#ifdef WITH_BREAKPOINT_SUPPORT - if (address != 0x0005) - return opcode; -#endif - - switch (cpu.getRegC()) { - case 0: // BDOS 0 System Reset - { - cout << endl << "Z80 reset after " << tstates << " t-states" << endl; - finish = true; - break; - } - case 2: // BDOS 2 console char output - { - cout << static_cast(cpu.getRegE()); - break; - } - case 9: // BDOS 9 console string output (string terminated by "$") - { - uint16_t strAddr = cpu.getRegDE(); - while (z80Ram[strAddr] != '$') { - cout << static_cast(z80Ram[strAddr++]); - } - cout.flush(); - break; - } - default: - { - cout << "BDOS Call " << cpu.getRegC() << endl; - finish = true; - cout << finish << endl; - } - } - // opcode would be modified before the decodeOpcode method - return opcode; -} - -void Z80sim::runTest(std::ifstream* f) { - streampos size; - if (!f->is_open()) { - cout << "file NOT OPEN" << endl; - return; - } else cout << "file open" << endl; - - size = f->tellg(); - cout << "Test size: " << size << endl; - f->seekg(0, ios::beg); - f->read(reinterpret_cast(&z80Ram[0x100]), size); - f->close(); - -#ifdef WITH_BREAKPOINT_SUPPORT - cpu.setBreakpoint(true); -#endif - - cpu.reset(); - finish = false; - - z80Ram[0] = static_cast(0xC3); - z80Ram[1] = 0x00; - z80Ram[2] = 0x01; // JP 0x100 CP/M TPA - z80Ram[5] = static_cast(0xC9); // Return from BDOS call - - while (!finish) { - cpu.execute(); - } -} - -int main() { - - Z80sim sim = Z80sim(); - - ifstream f1("zexall.bin", ios::in | ios::binary | ios::ate); - sim.runTest(&f1); - f1.close(); - -} diff --git a/example/z80sim.h b/example/z80sim.h deleted file mode 100644 index e540a28..0000000 --- a/example/z80sim.h +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef Z80SIM_H -#define Z80SIM_H - -#include -#include - -#include "../include/z80.h" -#include "../include/z80operations.h" - -class Z80sim : public Z80operations -{ -private: - uint64_t tstates; - Z80 cpu; - uint8_t z80Ram[0x10000]; - uint8_t z80Ports[0x10000]; - volatile bool finish; - -public: - Z80sim(); - ~Z80sim() override; - - uint8_t fetchOpcode(uint16_t address) override; - uint8_t peek8(uint16_t address) override; - void poke8(uint16_t address, uint8_t value) override; - uint16_t peek16(uint16_t address) override; - void poke16(uint16_t address, RegisterPair word) override; - uint8_t inPort(uint16_t port) override; - void outPort(uint16_t port, uint8_t value) override; - void addressOnBus(uint16_t address, int32_t tstates) override; - void interruptHandlingTime(int32_t tstates) override; - bool isActiveINT() override; - -#ifdef WITH_BREAKPOINT_SUPPORT - uint8_t breakpoint(uint16_t address, uint8_t opcode) override; -#else - uint8_t breakpoint(uint16_t address, uint8_t opcode); -#endif - -#ifdef WITH_EXEC_DONE - void execDone(void) override; -#endif - - void runTest(std::ifstream* f); -}; -#endif // Z80SIM_H diff --git a/include/z80.h b/include/z80.h index 0084aa3..b2cbb7d 100644 --- a/include/z80.h +++ b/include/z80.h @@ -5,28 +5,73 @@ //... v1.0.0 (13/02/2017) // quick & dirty conversion by dddddd (AKA deesix) -//... compile with $ g++ -m32 -std=c++14 +//... compile with $ g++ -m32 -std=c++17 //... put the zen*bin files in the same directory. -#ifndef Z80CPP_H -#define Z80CPP_H +#ifndef Z80_HPP +#define Z80_HPP +#include #include -/* Union allowing a register pair to be accessed as bytes or as a word */ -typedef union { -#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ - struct { - uint8_t hi, lo; - } byte8; -#else - struct { - uint8_t lo, hi; - } byte8; -#endif - uint16_t word; -} RegisterPair; +#include "z80_types.h" + +struct alignas(64) FlagTables { + alignas(64) std::array sz53n_add; + alignas(64) std::array sz53pn_add; + alignas(64) std::array sz53n_sub; + alignas(64) std::array sz53pn_sub; +}; + +// Build flag lookup tables at compile time +constexpr FlagTables makeFlagTables() { + constexpr uint8_t kSignMask = 0x80; + constexpr uint8_t kZeroMask = 0x40; + constexpr uint8_t kParityMask = 0x04; + constexpr uint8_t kAddSubMask = 0x02; + constexpr uint8_t kFlag53Mask = 0x28; // bit5 | bit3 + + FlagTables tables{}; + + for (uint32_t idx = 0; idx < 256; idx++) { + uint8_t addFlags = 0; + + if (idx > 0x7f) { + addFlags |= kSignMask; + } + + bool evenBits = true; + for (uint8_t mask = 0x01; mask != 0; mask <<= 1) { + if ((idx & mask) != 0) { + evenBits = !evenBits; + } + } -#include "z80operations.h" + addFlags |= static_cast(idx & kFlag53Mask); + + auto subFlags = static_cast(addFlags | kAddSubMask); + + tables.sz53n_add[idx] = addFlags; + tables.sz53n_sub[idx] = subFlags; + tables.sz53pn_add[idx] = evenBits ? static_cast(addFlags | kParityMask) : addFlags; + tables.sz53pn_sub[idx] = evenBits ? static_cast(subFlags | kParityMask) : subFlags; + } + + tables.sz53n_add[0] |= kZeroMask; + tables.sz53pn_add[0] |= kZeroMask; + tables.sz53n_sub[0] |= kZeroMask; + tables.sz53pn_sub[0] |= kZeroMask; + + return tables; +} + +// Pre-computed flag tables, initialized at compile time +inline constexpr FlagTables kFlagTables = makeFlagTables(); + +inline const FlagTables& getFlagTables() { + return kFlagTables; +} + +#include "z80_bus_interface.h" #define REG_B regBC.byte8.hi #define REG_C regBC.byte8.lo @@ -65,34 +110,38 @@ typedef union { #define REG_PCl regPC.byte8.lo #define REG_PC regPC.word -#define REG_S regSP.byte8.hi -#define REG_P regSP.byte8.lo -#define REG_SP regSP.word +#define REG_S regSP.byte8.hi +#define REG_P regSP.byte8.lo +#define REG_SP regSP.word + +#define REG_W memptr.byte8.hi +#define REG_Z memptr.byte8.lo +#define REG_WZ memptr.word -#define REG_W memptr.byte8.hi -#define REG_Z memptr.byte8.lo -#define REG_WZ memptr.word +template class Z80 { + public: + Z80(const Z80&) = delete; + Z80& operator=(const Z80&) = delete; + Z80(Z80&&) = delete; + Z80& operator=(Z80&&) = delete; -class Z80 { -public: // Modos de interrupción - enum IntMode { - IM0, IM1, IM2 - }; -private: - Z80operations *Z80opsImpl; + enum class IntMode : uint8_t { IM0, IM1, IM2 }; + + private: + TBusInterface& m_busInterface; // Código de instrucción a ejecutar // Poner esta variable como local produce peor rendimiento // ZEXALL test: (local) 1:54 vs 1:47 (visitante) - uint8_t m_opCode; + uint8_t m_opCode{}; // Se está ejecutando una instrucción prefijada con DD, ED o FD // Los valores permitidos son [0x00, 0xDD, 0xED, 0xFD] // El prefijo 0xCB queda al margen porque, detrás de 0xCB, siempre // viene un código de instrucción válido, tanto si delante va un // 0xDD o 0xFD como si no. - uint8_t prefixOpcode = { 0x00 }; + uint8_t prefixOpcode = {0x00}; // Subsistema de notificaciones - bool execDone; + bool execDone{false}; // Posiciones de los flags const static uint8_t CARRY_MASK = 0x01; const static uint8_t ADDSUB_MASK = 0x02; @@ -110,13 +159,13 @@ class Z80 { const static uint8_t FLAG_SZP_MASK = FLAG_SZ_MASK | PARITY_MASK; const static uint8_t FLAG_SZHP_MASK = FLAG_SZP_MASK | HALFCARRY_MASK; // Acumulador y resto de registros de 8 bits - uint8_t regA; + uint8_t regA{}; // Flags sIGN, zERO, 5, hALFCARRY, 3, pARITY y ADDSUB (n) - uint8_t sz5h3pnFlags; + uint8_t sz5h3pnFlags{}; // El flag Carry es el único que se trata aparte - bool carryFlag; + bool carryFlag{}; // Registros principales y alternativos - RegisterPair regBC, regBCx, regDE, regDEx, regHL, regHLx; + RegisterPair regBC{}, regBCx{}, regDE{}, regDEx{}, regHL{}, regHLx{}; /* Flags para indicar la modificación del registro F en la instrucción actual * y en la anterior. * Son necesarios para emular el comportamiento de los bits 3 y 5 del @@ -127,27 +176,27 @@ class Z80 { * * Thanks to Patrik Rak for his tests and investigations. */ - bool flagQ, lastFlagQ; + bool flagQ{}, lastFlagQ{}; // Acumulador alternativo y flags -- 8 bits - RegisterPair regAFx; + RegisterPair regAFx{}; // Registros de propósito específico // *PC -- Program Counter -- 16 bits* - RegisterPair regPC; + RegisterPair regPC{}; // *IX -- Registro de índice -- 16 bits* - RegisterPair regIX; + RegisterPair regIX{}; // *IY -- Registro de índice -- 16 bits* - RegisterPair regIY; + RegisterPair regIY{}; // *SP -- Stack Pointer -- 16 bits* - RegisterPair regSP; + RegisterPair regSP{}; // *I -- Vector de interrupción -- 8 bits* - uint8_t regI; + uint8_t regI{}; // *R -- Refresco de memoria -- 7 bits* - uint8_t regR; + uint8_t regR{}; // *R7 -- Refresco de memoria -- 1 bit* (bit superior de R) - bool regRbit7; - //Flip-flops de interrupción + bool regRbit7{}; + // Flip-flops de interrupción bool ffIFF1 = false; bool ffIFF2 = false; // EI solo habilita las interrupciones DESPUES de ejecutar la @@ -182,9 +231,9 @@ class Z80 { * Shit yourself, little parrot. */ - RegisterPair memptr; + RegisterPair memptr{}; // I and R registers - inline RegisterPair getPairIR() const; + [[nodiscard]] inline RegisterPair getPairIR() const; /* Algunos flags se precalculan para un tratamiento más rápido * Concretamente, SIGN, ZERO, los bits 3, 5, PARITY y ADDSUB: @@ -197,184 +246,403 @@ class Z80 { * decreto. Si lo ponen a 1 por el mismo método basta con hacer un OR con * la máscara correspondiente. */ - uint8_t sz53n_addTable[256] = {}; - uint8_t sz53pn_addTable[256] = {}; - uint8_t sz53n_subTable[256] = {}; - uint8_t sz53pn_subTable[256] = {}; + const std::array& sz53n_addTable; + const std::array& sz53pn_addTable; + const std::array& sz53n_subTable; + const std::array& sz53pn_subTable; - // Un true en una dirección indica que se debe notificar que se va a - // ejecutar la instrucción que está en esa direción. +// Un true en una dirección indica que se debe notificar que se va a +// ejecutar la instrucción que está en esa direción. #ifdef WITH_BREAKPOINT_SUPPORT - bool breakpointEnabled {false}; -#endif + bool breakpointEnabled{false}; +#endif // Z80_HPP void copyToRegister(uint8_t opCode, uint8_t value); void adjustINxROUTxRFlags(); -public: + public: // Constructor de la clase - explicit Z80(Z80operations *ops); + explicit Z80(TBusInterface& ops); ~Z80(); // Acceso a registros de 8 bits // Access to 8-bit registers - uint8_t getRegA() const { return regA; } - void setRegA(uint8_t value) { regA = value; } + [[nodiscard]] uint8_t getRegA() const { + return regA; + } + + void setRegA(uint8_t value) { + regA = value; + } + + [[nodiscard]] uint8_t getRegB() const { + return REG_B; + } + + void setRegB(uint8_t value) { + REG_B = value; + } + + [[nodiscard]] uint8_t getRegC() const { + return REG_C; + } + + void setRegC(uint8_t value) { + REG_C = value; + } + + [[nodiscard]] uint8_t getRegD() const { + return REG_D; + } - uint8_t getRegB() const { return REG_B; } - void setRegB(uint8_t value) { REG_B = value; } + void setRegD(uint8_t value) { + REG_D = value; + } - uint8_t getRegC() const { return REG_C; } - void setRegC(uint8_t value) { REG_C = value; } + [[nodiscard]] uint8_t getRegE() const { + return REG_E; + } - uint8_t getRegD() const { return REG_D; } - void setRegD(uint8_t value) { REG_D = value; } + void setRegE(uint8_t value) { + REG_E = value; + } - uint8_t getRegE() const { return REG_E; } - void setRegE(uint8_t value) { REG_E = value; } + [[nodiscard]] uint8_t getRegH() const { + return REG_H; + } - uint8_t getRegH() const { return REG_H; } - void setRegH(uint8_t value) { REG_H = value; } + void setRegH(uint8_t value) { + REG_H = value; + } - uint8_t getRegL() const { return REG_L; } - void setRegL(uint8_t value) { REG_L = value; } + [[nodiscard]] uint8_t getRegL() const { + return REG_L; + } + + void setRegL(uint8_t value) { + REG_L = value; + } // Acceso a registros alternativos de 8 bits // Access to alternate 8-bit registers - uint8_t getRegAx() const { return REG_Ax; } - void setRegAx(uint8_t value) { REG_Ax = value; } + [[nodiscard]] uint8_t getRegAx() const { + return REG_Ax; + } + + void setRegAx(uint8_t value) { + REG_Ax = value; + } + + [[nodiscard]] uint8_t getRegFx() const { + return REG_Fx; + } + + void setRegFx(uint8_t value) { + REG_Fx = value; + } + + [[nodiscard]] uint8_t getRegBx() const { + return REG_Bx; + } + + void setRegBx(uint8_t value) { + REG_Bx = value; + } + + [[nodiscard]] uint8_t getRegCx() const { + return REG_Cx; + } + + void setRegCx(uint8_t value) { + REG_Cx = value; + } - uint8_t getRegFx() const { return REG_Fx; } - void setRegFx(uint8_t value) { REG_Fx = value; } + [[nodiscard]] uint8_t getRegDx() const { + return REG_Dx; + } - uint8_t getRegBx() const { return REG_Bx; } - void setRegBx(uint8_t value) { REG_Bx = value; } + void setRegDx(uint8_t value) { + REG_Dx = value; + } - uint8_t getRegCx() const { return REG_Cx; } - void setRegCx(uint8_t value) { REG_Cx = value; } + [[nodiscard]] uint8_t getRegEx() const { + return REG_Ex; + } - uint8_t getRegDx() const { return REG_Dx; } - void setRegDx(uint8_t value) { REG_Dx = value; } + void setRegEx(uint8_t value) { + REG_Ex = value; + } - uint8_t getRegEx() const { return REG_Ex; } - void setRegEx(uint8_t value) { REG_Ex = value; } + [[nodiscard]] uint8_t getRegHx() const { + return REG_Hx; + } - uint8_t getRegHx() const { return REG_Hx; } - void setRegHx(uint8_t value) { REG_Hx = value; } + void setRegHx(uint8_t value) { + REG_Hx = value; + } - uint8_t getRegLx() const { return REG_Lx; } - void setRegLx(uint8_t value) { REG_Lx = value; } + [[nodiscard]] uint8_t getRegLx() const { + return REG_Lx; + } + + void setRegLx(uint8_t value) { + REG_Lx = value; + } // Acceso a registros de 16 bits // Access to registers pairs - uint16_t getRegAF() const { return (regA << 8) | (carryFlag ? sz5h3pnFlags | CARRY_MASK : sz5h3pnFlags); } - void setRegAF(uint16_t word) { regA = word >> 8; sz5h3pnFlags = word & 0xfe; carryFlag = (word & CARRY_MASK) != 0; } + [[nodiscard]] uint16_t getRegAF() const { + return (regA << 8) | (carryFlag ? sz5h3pnFlags | CARRY_MASK : sz5h3pnFlags); + } + + void setRegAF(uint16_t word) { + regA = word >> 8; + sz5h3pnFlags = word & 0xfe; + carryFlag = (word & CARRY_MASK) != 0; + } + + [[nodiscard]] uint16_t getRegAFx() const { + return REG_AFx; + } + + void setRegAFx(uint16_t word) { + REG_AFx = word; + } + + [[nodiscard]] uint16_t getRegBC() const { + return REG_BC; + } + + void setRegBC(uint16_t word) { + REG_BC = word; + } + + [[nodiscard]] uint16_t getRegBCx() const { + return REG_BCx; + } + + void setRegBCx(uint16_t word) { + REG_BCx = word; + } - uint16_t getRegAFx() const { return REG_AFx; } - void setRegAFx(uint16_t word) { REG_AFx = word; } + [[nodiscard]] uint16_t getRegDE() const { + return REG_DE; + } - uint16_t getRegBC() const { return REG_BC; } - void setRegBC(uint16_t word) { REG_BC = word; } + void setRegDE(uint16_t word) { + REG_DE = word; + } - uint16_t getRegBCx() const { return REG_BCx; } - void setRegBCx(uint16_t word) { REG_BCx = word; } + [[nodiscard]] uint16_t getRegDEx() const { + return REG_DEx; + } - uint16_t getRegDE() const { return REG_DE; } - void setRegDE(uint16_t word) { REG_DE = word; } + void setRegDEx(uint16_t word) { + REG_DEx = word; + } - uint16_t getRegDEx() const { return REG_DEx; } - void setRegDEx(uint16_t word) { REG_DEx = word; } + [[nodiscard]] uint16_t getRegHL() const { + return REG_HL; + } - uint16_t getRegHL() const { return REG_HL; } - void setRegHL(uint16_t word) { REG_HL = word; } + void setRegHL(uint16_t word) { + REG_HL = word; + } - uint16_t getRegHLx() const { return REG_HLx; } - void setRegHLx(uint16_t word) { REG_HLx = word; } + [[nodiscard]] uint16_t getRegHLx() const { + return REG_HLx; + } + + void setRegHLx(uint16_t word) { + REG_HLx = word; + } // Acceso a registros de propósito específico // Access to special purpose registers - uint16_t getRegPC() const { return REG_PC; } - void setRegPC(uint16_t address) { REG_PC = address; } + [[nodiscard]] uint16_t getRegPC() const { + return REG_PC; + } + + void setRegPC(uint16_t address) { + REG_PC = address; + } + + [[nodiscard]] uint16_t getRegSP() const { + return REG_SP; + } + + void setRegSP(uint16_t word) { + REG_SP = word; + } - uint16_t getRegSP() const { return REG_SP; } - void setRegSP(uint16_t word) { REG_SP = word; } + [[nodiscard]] uint16_t getRegIX() const { + return REG_IX; + } - uint16_t getRegIX() const { return REG_IX; } - void setRegIX(uint16_t word) { REG_IX = word; } + void setRegIX(uint16_t word) { + REG_IX = word; + } - uint16_t getRegIY() const { return REG_IY; } - void setRegIY(uint16_t word) { REG_IY = word; } + [[nodiscard]] uint16_t getRegIY() const { + return REG_IY; + } - uint8_t getRegI() const { return regI; } - void setRegI(uint8_t value) { regI = value; } + void setRegIY(uint16_t word) { + REG_IY = word; + } - uint8_t getRegR() const { return regRbit7 ? regR | SIGN_MASK : regR & 0x7f; } - void setRegR(uint8_t value) { regR = value & 0x7f; regRbit7 = (value > 0x7f); } + [[nodiscard]] uint8_t getRegI() const { + return regI; + } + + void setRegI(uint8_t value) { + regI = value; + } + + [[nodiscard]] uint8_t getRegR() const { + return regRbit7 ? regR | SIGN_MASK : regR & 0x7f; + } + + void setRegR(uint8_t value) { + regR = value & 0x7f; + regRbit7 = (value > 0x7f); + } // Acceso al registro oculto MEMPTR // Hidden register MEMPTR (known as WZ at Zilog doc?) - uint16_t getMemPtr() const { return REG_WZ; } - void setMemPtr(uint16_t word) { REG_WZ = word; } + [[nodiscard]] uint16_t getMemPtr() const { + return REG_WZ; + } + + void setMemPtr(uint16_t word) { + REG_WZ = word; + } // Acceso a los flags uno a uno // Access to single flags from F register - bool isCarryFlag() const { return carryFlag; } - void setCarryFlag(bool state) { carryFlag = state; } + [[nodiscard]] bool isCarryFlag() const { + return carryFlag; + } + + void setCarryFlag(bool state) { + carryFlag = state; + } + + [[nodiscard]] bool isAddSubFlag() const { + return (sz5h3pnFlags & ADDSUB_MASK) != 0; + } - bool isAddSubFlag() const { return (sz5h3pnFlags & ADDSUB_MASK) != 0; } void setAddSubFlag(bool state); - bool isParOverFlag() const { return (sz5h3pnFlags & PARITY_MASK) != 0; } + [[nodiscard]] bool isParOverFlag() const { + return (sz5h3pnFlags & PARITY_MASK) != 0; + } + void setParOverFlag(bool state); /* Undocumented flag */ - bool isBit3Flag() const { return (sz5h3pnFlags & BIT3_MASK) != 0; } + [[nodiscard]] bool isBit3Flag() const { + return (sz5h3pnFlags & BIT3_MASK) != 0; + } + void setBit3Flag(bool state); - bool isHalfCarryFlag() const { return (sz5h3pnFlags & HALFCARRY_MASK) != 0; } + [[nodiscard]] bool isHalfCarryFlag() const { + return (sz5h3pnFlags & HALFCARRY_MASK) != 0; + } + void setHalfCarryFlag(bool state); /* Undocumented flag */ - bool isBit5Flag() const { return (sz5h3pnFlags & BIT5_MASK) != 0; } + [[nodiscard]] bool isBit5Flag() const { + return (sz5h3pnFlags & BIT5_MASK) != 0; + } + void setBit5Flag(bool state); - bool isZeroFlag() const { return (sz5h3pnFlags & ZERO_MASK) != 0; } + [[nodiscard]] bool isZeroFlag() const { + return (sz5h3pnFlags & ZERO_MASK) != 0; + } + void setZeroFlag(bool state); - bool isSignFlag() const { return sz5h3pnFlags >= SIGN_MASK; } + [[nodiscard]] bool isSignFlag() const { + return sz5h3pnFlags >= SIGN_MASK; + } + void setSignFlag(bool state); // Acceso a los flags F // Access to F register - uint8_t getFlags() const { return carryFlag ? sz5h3pnFlags | CARRY_MASK : sz5h3pnFlags; } - void setFlags(uint8_t regF) { sz5h3pnFlags = regF & 0xfe; carryFlag = (regF & CARRY_MASK) != 0; } + [[nodiscard]] uint8_t getFlags() const { + return carryFlag ? sz5h3pnFlags | CARRY_MASK : sz5h3pnFlags; + } + + void setFlags(uint8_t regF) { + sz5h3pnFlags = regF & 0xfe; + carryFlag = (regF & CARRY_MASK) != 0; + } // Acceso a los flip-flops de interrupción // Interrupt flip-flops - bool isIFF1() const { return ffIFF1; } - void setIFF1(bool state) { ffIFF1 = state; } + [[nodiscard]] bool isIFF1() const { + return ffIFF1; + } + + void setIFF1(bool state) { + ffIFF1 = state; + } + + [[nodiscard]] bool isIFF2() const { + return ffIFF2; + } + + void setIFF2(bool state) { + ffIFF2 = state; + } - bool isIFF2() const { return ffIFF2; } - void setIFF2(bool state) { ffIFF2 = state; } + [[nodiscard]] bool isNMI() const { + return activeNMI; + } - bool isNMI() const { return activeNMI; } - void setNMI(bool nmi) { activeNMI = nmi; } + void setNMI(bool nmi) { + activeNMI = nmi; + } // /NMI is negative level triggered. - void triggerNMI() { activeNMI = true; } + void triggerNMI() { + activeNMI = true; + } - //Acceso al modo de interrupción - // Maskable interrupt mode - IntMode getIM() const { return modeINT; } - void setIM(IntMode mode) { modeINT = mode; } + // Acceso al modo de interrupción + // Maskable interrupt mode + [[nodiscard]] IntMode getIM() const { + return modeINT; + } - bool isHalted() const { return halted; } - void setHalted(bool state) { halted = state; } + void setIM(IntMode mode) { + modeINT = mode; + } + + [[nodiscard]] bool isHalted() const { + return halted; + } + + void setHalted(bool state) { + halted = state; + } // Reset requested by /RESET signal (not power-on) - void setPinReset() { pinReset = true; } + void setPinReset() { + pinReset = true; + } - bool isPendingEI() const { return pendingEI; } - void setPendingEI(bool state) { pendingEI = state; } + [[nodiscard]] bool isPendingEI() const { + return pendingEI; + } + + void setPendingEI(bool state) { + pendingEI = state; + } // Reset void reset(); @@ -383,44 +651,51 @@ class Z80 { void execute(); #ifdef WITH_BREAKPOINT_SUPPORT - bool isBreakpoint() { return breakpointEnabled; } - void setBreakpoint(bool state) { breakpointEnabled = state; } + [[nodiscard]] bool isBreakpoint() { + return breakpointEnabled; + } + + void setBreakpoint(bool state) { + breakpointEnabled = state; + } #endif #ifdef WITH_EXEC_DONE - void setExecDone(bool status) { execDone = status; } + void setExecDone(bool status) { + execDone = status; + } #endif -private: + private: // Rota a la izquierda el valor del argumento - inline void rlc(uint8_t &oper8); + inline void rlc(uint8_t& oper8); // Rota a la izquierda el valor del argumento - inline void rl(uint8_t &oper8); + inline void rl(uint8_t& oper8); // Rota a la izquierda el valor del argumento - inline void sla(uint8_t &oper8); + inline void sla(uint8_t& oper8); // Rota a la izquierda el valor del argumento (como sla salvo por el bit 0) - inline void sll(uint8_t &oper8); + inline void sll(uint8_t& oper8); // Rota a la derecha el valor del argumento - inline void rrc(uint8_t &oper8); + inline void rrc(uint8_t& oper8); // Rota a la derecha el valor del argumento - inline void rr(uint8_t &oper8); + inline void rr(uint8_t& oper8); // Rota a la derecha 1 bit el valor del argumento - inline void sra(uint8_t &oper8); + inline void sra(uint8_t& oper8); // Rota a la derecha 1 bit el valor del argumento - inline void srl(uint8_t &oper8); + inline void srl(uint8_t& oper8); // Incrementa un valor de 8 bits modificando los flags oportunos - inline void inc8(uint8_t &oper8); + inline void inc8(uint8_t& oper8); // Decrementa un valor de 8 bits modificando los flags oportunos - inline void dec8(uint8_t &oper8); + inline void dec8(uint8_t& oper8); // Suma de 8 bits afectando a los flags inline void add(uint8_t oper8); @@ -429,7 +704,7 @@ class Z80 { inline void adc(uint8_t oper8); // Suma dos operandos de 16 bits sin carry afectando a los flags - inline void add16(RegisterPair ®16, uint16_t oper16); + inline void add16(RegisterPair& reg16, uint16_t oper16); // Suma con acarreo de 16 bits inline void adc16(uint16_t reg16); @@ -497,10 +772,10 @@ class Z80 { // BIT n,r inline void bitTest(uint8_t mask, uint8_t reg); - //Interrupción + // Interrupción void interrupt(); - //Interrupción NMI + // Interrupción NMI void nmi(); // Decode main opcodes @@ -510,16 +785,4798 @@ class Z80 { // decode CBXX opcodes void decodeCB(); - //Subconjunto de instrucciones 0xDD / 0xFD - // Decode DD/FD opcodes + // Subconjunto de instrucciones 0xDD / 0xFD + // Decode DD/FD opcodes void decodeDDFD(uint8_t opCode, RegisterPair& regIXY); // Subconjunto de instrucciones 0xDD / 0xFD 0xCB // Decode DD / FD CB opcodes void decodeDDFDCB(uint8_t opCode, uint16_t address); - //Subconjunto de instrucciones 0xED - // Decode EDXX opcodes + // Subconjunto de instrucciones 0xED + // Decode EDXX opcodes void decodeED(uint8_t opCode); }; -#endif // Z80CPP_H + +// Constructor de la clase +template +Z80::Z80(TBusInterface& ops) + : m_busInterface(ops), + sz53n_addTable(getFlagTables().sz53n_add), + sz53pn_addTable(getFlagTables().sz53pn_add), + sz53n_subTable(getFlagTables().sz53n_sub), + sz53pn_subTable(getFlagTables().sz53pn_sub) { + + reset(); +} + +template Z80::~Z80() = default; + +template RegisterPair Z80::getPairIR() const { + RegisterPair IR; + IR.byte8.hi = regI; + IR.byte8.lo = regR & 0x7f; + if (regRbit7) { + IR.byte8.lo |= SIGN_MASK; + } + return IR; +} + +template void Z80::setAddSubFlag(bool state) { + // Branchless flag update to reduce mispredictions in hot paths + const uint8_t mask = state ? 0xFF : 0x00; + sz5h3pnFlags = static_cast((sz5h3pnFlags & static_cast(~ADDSUB_MASK)) | (mask & ADDSUB_MASK)); +} + +template void Z80::setParOverFlag(bool state) { + const uint8_t mask = state ? 0xFF : 0x00; + sz5h3pnFlags = static_cast((sz5h3pnFlags & static_cast(~PARITY_MASK)) | (mask & PARITY_MASK)); +} + +template void Z80::setBit3Flag(bool state) { + const uint8_t mask = state ? 0xFF : 0x00; + sz5h3pnFlags = static_cast((sz5h3pnFlags & static_cast(~BIT3_MASK)) | (mask & BIT3_MASK)); +} + +template void Z80::setHalfCarryFlag(bool state) { + const uint8_t mask = state ? 0xFF : 0x00; + sz5h3pnFlags = + static_cast((sz5h3pnFlags & static_cast(~HALFCARRY_MASK)) | (mask & HALFCARRY_MASK)); +} + +template void Z80::setBit5Flag(bool state) { + const uint8_t mask = state ? 0xFF : 0x00; + sz5h3pnFlags = static_cast((sz5h3pnFlags & static_cast(~BIT5_MASK)) | (mask & BIT5_MASK)); +} + +template void Z80::setZeroFlag(bool state) { + const uint8_t mask = state ? 0xFF : 0x00; + sz5h3pnFlags = static_cast((sz5h3pnFlags & static_cast(~ZERO_MASK)) | (mask & ZERO_MASK)); +} + +template void Z80::setSignFlag(bool state) { + const uint8_t mask = state ? 0xFF : 0x00; + sz5h3pnFlags = static_cast((sz5h3pnFlags & static_cast(~SIGN_MASK)) | (mask & SIGN_MASK)); +} + +// Reset +/* Según el documento de Sean Young, que se encuentra en + * [http://www.myquest.com/z80undocumented], la mejor manera de emular el + * reset es poniendo PC, IFF1, IFF2, R e IM0 a 0 y todos los demás registros + * a 0xFFFF. + * + * 29/05/2011: cuando la CPU recibe alimentación por primera vez, los + * registros PC e IR se inicializan a cero y el resto a 0xFF. + * Si se produce un reset a través de la patilla correspondiente, + * los registros PC e IR se inicializan a 0 y el resto se preservan. + * En cualquier caso, todo parece depender bastante del modelo + * concreto de Z80, así que se escoge el comportamiento del + * modelo Zilog Z8400APS. Z80A CPU. + * http://www.worldofspectrum.org/forums/showthread.php?t=34574 + */ +template void Z80::reset() { + if (pinReset) { + pinReset = false; + } else { + regA = 0xff; + + setFlags(0xfd); // The only one flag reset at cold start is the add/sub flag + + REG_AFx = 0xffff; + REG_BC = REG_BCx = 0xffff; + REG_DE = REG_DEx = 0xffff; + REG_HL = REG_HLx = 0xffff; + + REG_IX = REG_IY = 0xffff; + + REG_SP = 0xffff; + + REG_WZ = 0xffff; + } + + REG_PC = 0; + regI = regR = 0; + regRbit7 = false; + ffIFF1 = false; + ffIFF2 = false; + pendingEI = false; + activeNMI = false; + halted = false; + setIM(IntMode::IM0); + lastFlagQ = false; + prefixOpcode = 0x00; +} + +// Rota a la izquierda el valor del argumento +// El bit 0 y el flag C toman el valor del bit 7 antes de la operación +template void Z80::rlc(uint8_t& oper8) { + carryFlag = (oper8 > 0x7f); + oper8 <<= 1; + if (carryFlag) { + oper8 |= CARRY_MASK; + } + sz5h3pnFlags = sz53pn_addTable[oper8]; +} + +// Rota a la izquierda el valor del argumento +// El bit 7 va al carry flag +// El bit 0 toma el valor del flag C antes de la operación +template void Z80::rl(uint8_t& oper8) { + bool carry = carryFlag; + carryFlag = (oper8 > 0x7f); + oper8 <<= 1; + if (carry) { + oper8 |= CARRY_MASK; + } + sz5h3pnFlags = sz53pn_addTable[oper8]; +} + +// Rota a la izquierda el valor del argumento +// El bit 7 va al carry flag +// El bit 0 toma el valor 0 +template void Z80::sla(uint8_t& oper8) { + carryFlag = (oper8 > 0x7f); + oper8 <<= 1; + sz5h3pnFlags = sz53pn_addTable[oper8]; +} + +// Rota a la izquierda el valor del argumento (como sla salvo por el bit 0) +// El bit 7 va al carry flag +// El bit 0 toma el valor 1 +// Instrucción indocumentada +template void Z80::sll(uint8_t& oper8) { + carryFlag = (oper8 > 0x7f); + oper8 <<= 1; + oper8 |= CARRY_MASK; + sz5h3pnFlags = sz53pn_addTable[oper8]; +} + +// Rota a la derecha el valor del argumento +// El bit 7 y el flag C toman el valor del bit 0 antes de la operación +template void Z80::rrc(uint8_t& oper8) { + carryFlag = (oper8 & CARRY_MASK) != 0; + oper8 >>= 1; + if (carryFlag) { + oper8 |= SIGN_MASK; + } + sz5h3pnFlags = sz53pn_addTable[oper8]; +} + +// Rota a la derecha el valor del argumento +// El bit 0 va al carry flag +// El bit 7 toma el valor del flag C antes de la operación +template void Z80::rr(uint8_t& oper8) { + bool carry = carryFlag; + carryFlag = (oper8 & CARRY_MASK) != 0; + oper8 >>= 1; + if (carry) { + oper8 |= SIGN_MASK; + } + sz5h3pnFlags = sz53pn_addTable[oper8]; +} + +// Rota a la derecha 1 bit el valor del argumento +// El bit 0 pasa al carry. +// El bit 7 conserva el valor que tenga +template void Z80::sra(uint8_t& oper8) { + uint8_t sign = oper8 & SIGN_MASK; + carryFlag = (oper8 & CARRY_MASK) != 0; + oper8 = (oper8 >> 1) | sign; + sz5h3pnFlags = sz53pn_addTable[oper8]; +} + +// Rota a la derecha 1 bit el valor del argumento +// El bit 0 pasa al carry. +// El bit 7 toma el valor 0 +template void Z80::srl(uint8_t& oper8) { + carryFlag = (oper8 & CARRY_MASK) != 0; + oper8 >>= 1; + sz5h3pnFlags = sz53pn_addTable[oper8]; +} + +/* + * Half-carry flag: + * + * FLAG = (A ^ B ^ RESULT) & 0x10 for any operation + * + * Overflow flag: + * + * FLAG = ~(A ^ B) & (B ^ RESULT) & 0x80 for addition [ADD/ADC] + * FLAG = (A ^ B) & (A ^ RESULT) &0x80 for subtraction [SUB/SBC] + * + * For INC/DEC, you can use following simplifications: + * + * INC: + * H_FLAG = (RESULT & 0x0F) == 0x00 + * V_FLAG = RESULT == 0x80 + * + * DEC: + * H_FLAG = (RESULT & 0x0F) == 0x0F + * V_FLAG = RESULT == 0x7F + */ +// Incrementa un valor de 8 bits modificando los flags oportunos +template void Z80::inc8(uint8_t& oper8) { + oper8++; + + sz5h3pnFlags = sz53n_addTable[oper8]; + + if ((oper8 & 0x0f) == 0) { + sz5h3pnFlags |= HALFCARRY_MASK; + } + + if (oper8 == 0x80) { + sz5h3pnFlags |= OVERFLOW_MASK; + } +} + +// Decrementa un valor de 8 bits modificando los flags oportunos +template void Z80::dec8(uint8_t& oper8) { + oper8--; + + sz5h3pnFlags = sz53n_subTable[oper8]; + + if ((oper8 & 0x0f) == 0x0f) { + sz5h3pnFlags |= HALFCARRY_MASK; + } + + if (oper8 == 0x7f) { + sz5h3pnFlags |= OVERFLOW_MASK; + } +} + +// Suma de 8 bits afectando a los flags +template void Z80::add(uint8_t oper8) { + uint16_t res = regA + oper8; + + carryFlag = res > 0xff; + res &= 0xff; + sz5h3pnFlags = sz53n_addTable[res]; + + /* El módulo 16 del resultado será menor que el módulo 16 del registro A + * si ha habido HalfCarry. Sucede lo mismo para todos los métodos suma + * SIN carry */ + if ((res & 0x0f) < (regA & 0x0f)) { + sz5h3pnFlags |= HALFCARRY_MASK; + } + + if (((regA ^ ~oper8) & (regA ^ res)) > 0x7f) { + sz5h3pnFlags |= OVERFLOW_MASK; + } + + regA = res; +} + +// Suma con acarreo de 8 bits +template void Z80::adc(uint8_t oper8) { + uint16_t res = regA + oper8; + + if (carryFlag) { + res++; + } + + carryFlag = res > 0xff; + res &= 0xff; + sz5h3pnFlags = sz53n_addTable[res]; + + if (((regA ^ oper8 ^ res) & 0x10) != 0) { + sz5h3pnFlags |= HALFCARRY_MASK; + } + + if (((regA ^ ~oper8) & (regA ^ res)) > 0x7f) { + sz5h3pnFlags |= OVERFLOW_MASK; + } + + regA = res; +} + +// Suma dos operandos de 16 bits sin carry afectando a los flags +template void Z80::add16(RegisterPair& reg16, uint16_t oper16) { + uint32_t tmp = oper16 + reg16.word; + + REG_WZ = reg16.word + 1; + carryFlag = tmp > 0xffff; + reg16.word = tmp; + sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZP_MASK) | ((reg16.word >> 8) & FLAG_53_MASK); + + if ((reg16.word & 0x0fff) < (oper16 & 0x0fff)) { + sz5h3pnFlags |= HALFCARRY_MASK; + } +} + +// Suma con acarreo de 16 bits +template void Z80::adc16(uint16_t reg16) { + uint16_t tmpHL = REG_HL; + REG_WZ = REG_HL + 1; + + uint32_t res = REG_HL + reg16; + if (carryFlag) { + res++; + } + + carryFlag = res > 0xffff; + res &= 0xffff; + REG_HL = static_cast(res); + + sz5h3pnFlags = sz53n_addTable[REG_H]; + if (res != 0) { + sz5h3pnFlags &= ~ZERO_MASK; + } + + if (((res ^ tmpHL ^ reg16) & 0x1000) != 0) { + sz5h3pnFlags |= HALFCARRY_MASK; + } + + if (((tmpHL ^ ~reg16) & (tmpHL ^ res)) > 0x7fff) { + sz5h3pnFlags |= OVERFLOW_MASK; + } +} + +// Resta de 8 bits +template void Z80::sub(uint8_t oper8) { + auto res = static_cast(regA - oper8); + + carryFlag = res < 0; + res &= 0xff; + sz5h3pnFlags = sz53n_subTable[res]; + + /* El módulo 16 del resultado será mayor que el módulo 16 del registro A + * si ha habido HalfCarry. Sucede lo mismo para todos los métodos resta + * SIN carry, incluido cp */ + if ((res & 0x0f) > (regA & 0x0f)) { + sz5h3pnFlags |= HALFCARRY_MASK; + } + + if (((regA ^ oper8) & (regA ^ res)) > 0x7f) { + sz5h3pnFlags |= OVERFLOW_MASK; + } + + regA = res; +} + +// Resta con acarreo de 8 bits +template void Z80::sbc(uint8_t oper8) { + auto res = static_cast(regA - oper8); + + if (carryFlag) { + res--; + } + + carryFlag = res < 0; + res &= 0xff; + sz5h3pnFlags = sz53n_subTable[res]; + + if (((regA ^ oper8 ^ res) & 0x10) != 0) { + sz5h3pnFlags |= HALFCARRY_MASK; + } + + if (((regA ^ oper8) & (regA ^ res)) > 0x7f) { + sz5h3pnFlags |= OVERFLOW_MASK; + } + + regA = res; +} + +// Resta con acarreo de 16 bits +template void Z80::sbc16(uint16_t reg16) { + uint16_t tmpHL = REG_HL; + REG_WZ = REG_HL + 1; + + int32_t res = REG_HL - reg16; + if (carryFlag) { + res--; + } + + carryFlag = res < 0; + res &= 0xffff; + REG_HL = static_cast(res); + + sz5h3pnFlags = sz53n_subTable[REG_H]; + if (res != 0) { + sz5h3pnFlags &= ~ZERO_MASK; + } + + if (((res ^ tmpHL ^ reg16) & 0x1000) != 0) { + sz5h3pnFlags |= HALFCARRY_MASK; + } + + if (((tmpHL ^ reg16) & (tmpHL ^ res)) > 0x7fff) { + sz5h3pnFlags |= OVERFLOW_MASK; + } +} + +// Operación AND lógica +template void Z80::and_(uint8_t oper8) { + regA &= oper8; + carryFlag = false; + sz5h3pnFlags = sz53pn_addTable[regA] | HALFCARRY_MASK; +} + +// Operación XOR lógica +template void Z80::xor_(uint8_t oper8) { + regA ^= oper8; + carryFlag = false; + sz5h3pnFlags = sz53pn_addTable[regA]; +} + +// Operación OR lógica +template void Z80::or_(uint8_t oper8) { + regA |= oper8; + carryFlag = false; + sz5h3pnFlags = sz53pn_addTable[regA]; +} + +// Operación de comparación con el registro A +// es como SUB, pero solo afecta a los flags +// Los flags SIGN y ZERO se calculan a partir del resultado +// Los flags 3 y 5 se copian desde el operando (sigh!) +template void Z80::cp(uint8_t oper8) { + auto res = static_cast(regA - oper8); + + carryFlag = res < 0; + res &= 0xff; + + sz5h3pnFlags = (sz53n_addTable[oper8] & FLAG_53_MASK) + | // No necesito preservar H, pero está a 0 en la tabla de todas formas + (sz53n_subTable[res] & FLAG_SZHN_MASK); + + if ((res & 0x0f) > (regA & 0x0f)) { + sz5h3pnFlags |= HALFCARRY_MASK; + } + + if (((regA ^ oper8) & (regA ^ res)) > 0x7f) { + sz5h3pnFlags |= OVERFLOW_MASK; + } +} + +// DAA +template void Z80::daa() { + uint8_t suma = 0; + bool carry = carryFlag; + + if ((sz5h3pnFlags & HALFCARRY_MASK) != 0 || (regA & 0x0f) > 0x09) { + suma = 6; + } + + if (carry || (regA > 0x99)) { + suma |= 0x60; + } + + if (regA > 0x99) { + carry = true; + } + + if ((sz5h3pnFlags & ADDSUB_MASK) != 0) { + sub(suma); + sz5h3pnFlags = (sz5h3pnFlags & HALFCARRY_MASK) | sz53pn_subTable[regA]; + } else { + add(suma); + sz5h3pnFlags = (sz5h3pnFlags & HALFCARRY_MASK) | sz53pn_addTable[regA]; + } + + carryFlag = carry; + // Los add/sub ya ponen el resto de los flags +} + +// POP +template uint16_t Z80::pop() { + uint16_t word = m_busInterface.peek16(REG_SP); + REG_SP = REG_SP + 2; + return word; +} + +// PUSH +template void Z80::push(uint16_t word) { + m_busInterface.poke8(--REG_SP, word >> 8); + m_busInterface.poke8(--REG_SP, word); +} + +// LDI +template void Z80::ldi() { + uint8_t work8 = m_busInterface.peek8(REG_HL); + m_busInterface.poke8(REG_DE, work8); + m_busInterface.addressOnBus(REG_DE, 2); + REG_HL++; + REG_DE++; + REG_BC--; + work8 += regA; + + sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZ_MASK) | (work8 & BIT3_MASK); + + if ((work8 & ADDSUB_MASK) != 0) { + sz5h3pnFlags |= BIT5_MASK; + } + + if (REG_BC != 0) { + sz5h3pnFlags |= PARITY_MASK; + } +} + +// LDD +template void Z80::ldd() { + uint8_t work8 = m_busInterface.peek8(REG_HL); + m_busInterface.poke8(REG_DE, work8); + m_busInterface.addressOnBus(REG_DE, 2); + REG_HL--; + REG_DE--; + REG_BC--; + work8 += regA; + + sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZ_MASK) | (work8 & BIT3_MASK); + + if ((work8 & ADDSUB_MASK) != 0) { + sz5h3pnFlags |= BIT5_MASK; + } + + if (REG_BC != 0) { + sz5h3pnFlags |= PARITY_MASK; + } +} + +// CPI +template void Z80::cpi() { + uint8_t memHL = m_busInterface.peek8(REG_HL); + bool carry = carryFlag; // lo guardo porque cp lo toca + cp(memHL); + carryFlag = carry; + m_busInterface.addressOnBus(REG_HL, 5); + REG_HL++; + REG_BC--; + memHL = regA - memHL - ((sz5h3pnFlags & HALFCARRY_MASK) != 0 ? 1 : 0); + sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZHN_MASK) | (memHL & BIT3_MASK); + + if ((memHL & ADDSUB_MASK) != 0) { + sz5h3pnFlags |= BIT5_MASK; + } + + if (REG_BC != 0) { + sz5h3pnFlags |= PARITY_MASK; + } + + REG_WZ++; +} + +// CPD +template void Z80::cpd() { + uint8_t memHL = m_busInterface.peek8(REG_HL); + bool carry = carryFlag; // lo guardo porque cp lo toca + cp(memHL); + carryFlag = carry; + m_busInterface.addressOnBus(REG_HL, 5); + REG_HL--; + REG_BC--; + memHL = regA - memHL - ((sz5h3pnFlags & HALFCARRY_MASK) != 0 ? 1 : 0); + sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZHN_MASK) | (memHL & BIT3_MASK); + + if ((memHL & ADDSUB_MASK) != 0) { + sz5h3pnFlags |= BIT5_MASK; + } + + if (REG_BC != 0) { + sz5h3pnFlags |= PARITY_MASK; + } + + REG_WZ--; +} + +// INI +template void Z80::ini() { + REG_WZ = REG_BC; + m_busInterface.addressOnBus(getPairIR().word, 1); + uint8_t work8 = m_busInterface.inPort(REG_WZ++); + m_busInterface.poke8(REG_HL, work8); + + REG_B--; + REG_HL++; + + sz5h3pnFlags = sz53pn_addTable[REG_B]; + if (work8 > 0x7f) { + sz5h3pnFlags |= ADDSUB_MASK; + } + + carryFlag = false; + uint16_t tmp = work8 + ((REG_C + 1) & 0xff); + if (tmp > 0xff) { + sz5h3pnFlags |= HALFCARRY_MASK; + carryFlag = true; + } + + if ((sz53pn_addTable[((tmp & 0x07) ^ REG_B)] & PARITY_MASK) == PARITY_MASK) { + sz5h3pnFlags |= PARITY_MASK; + } else { + sz5h3pnFlags &= ~PARITY_MASK; + } +} + +// IND +template void Z80::ind() { + REG_WZ = REG_BC; + m_busInterface.addressOnBus(getPairIR().word, 1); + uint8_t work8 = m_busInterface.inPort(REG_WZ--); + m_busInterface.poke8(REG_HL, work8); + + REG_B--; + REG_HL--; + + sz5h3pnFlags = sz53pn_addTable[REG_B]; + if (work8 > 0x7f) { + sz5h3pnFlags |= ADDSUB_MASK; + } + + carryFlag = false; + uint16_t tmp = work8 + ((REG_C - 1) & 0xff); + if (tmp > 0xff) { + sz5h3pnFlags |= HALFCARRY_MASK; + carryFlag = true; + } + + if ((sz53pn_addTable[((tmp & 0x07) ^ REG_B)] & PARITY_MASK) == PARITY_MASK) { + sz5h3pnFlags |= PARITY_MASK; + } else { + sz5h3pnFlags &= ~PARITY_MASK; + } +} + +// OUTI +template void Z80::outi() { + + m_busInterface.addressOnBus(getPairIR().word, 1); + + REG_B--; + REG_WZ = REG_BC; + + uint8_t work8 = m_busInterface.peek8(REG_HL); + m_busInterface.outPort(REG_WZ++, work8); + + REG_HL++; + + carryFlag = false; + if (work8 > 0x7f) { + sz5h3pnFlags = sz53n_subTable[REG_B]; + } else { + sz5h3pnFlags = sz53n_addTable[REG_B]; + } + + if ((REG_L + work8) > 0xff) { + sz5h3pnFlags |= HALFCARRY_MASK; + carryFlag = true; + } + + if ((sz53pn_addTable[(((REG_L + work8) & 0x07) ^ REG_B)] & PARITY_MASK) == PARITY_MASK) { + sz5h3pnFlags |= PARITY_MASK; + } +} + +// OUTD +template void Z80::outd() { + + m_busInterface.addressOnBus(getPairIR().word, 1); + + REG_B--; + REG_WZ = REG_BC; + + uint8_t work8 = m_busInterface.peek8(REG_HL); + m_busInterface.outPort(REG_WZ--, work8); + + REG_HL--; + + carryFlag = false; + if (work8 > 0x7f) { + sz5h3pnFlags = sz53n_subTable[REG_B]; + } else { + sz5h3pnFlags = sz53n_addTable[REG_B]; + } + + if ((REG_L + work8) > 0xff) { + sz5h3pnFlags |= HALFCARRY_MASK; + carryFlag = true; + } + + if ((sz53pn_addTable[(((REG_L + work8) & 0x07) ^ REG_B)] & PARITY_MASK) == PARITY_MASK) { + sz5h3pnFlags |= PARITY_MASK; + } +} + +// Pone a 1 el Flag Z si el bit b del registro +// r es igual a 0 +/* + * En contra de lo que afirma el Z80-Undocumented, los bits 3 y 5 toman + * SIEMPRE el valor de los bits correspondientes del valor a comparar para + * las instrucciones BIT n,r. Para BIT n,(HL) toman el valor del registro + * escondido (REG_WZ), y para las BIT n, (IX/IY+n) toman el valor de los + * bits superiores de la dirección indicada por IX/IY+n. + * + * 04/12/08 Confirmado el comentario anterior: + * http://scratchpad.wikia.com/wiki/Z80 + */ +template void Z80::bitTest(uint8_t mask, uint8_t reg) { + bool zeroFlag = (mask & reg) == 0; + + sz5h3pnFlags = (sz53n_addTable[reg] & ~FLAG_SZP_MASK) | HALFCARRY_MASK; + + if (zeroFlag) { + sz5h3pnFlags |= (PARITY_MASK | ZERO_MASK); + } + + if (mask == SIGN_MASK && !zeroFlag) { + sz5h3pnFlags |= SIGN_MASK; + } +} + +// Interrupción +/* Desglose de la interrupción, según el modo: + * IM0: + * M1: 7 T-Estados -> reconocer INT y decSP + * M2: 3 T-Estados -> escribir byte alto y decSP + * M3: 3 T-Estados -> escribir byte bajo y salto a N + * IM1: + * M1: 7 T-Estados -> reconocer INT y decSP + * M2: 3 T-Estados -> escribir byte alto PC y decSP + * M3: 3 T-Estados -> escribir byte bajo PC y PC=0x0038 + * IM2: + * M1: 7 T-Estados -> reconocer INT y decSP + * M2: 3 T-Estados -> escribir byte alto y decSP + * M3: 3 T-Estados -> escribir byte bajo + * M4: 3 T-Estados -> leer byte bajo del vector de INT + * M5: 3 T-Estados -> leer byte alto y saltar a la rutina de INT + */ +template void Z80::interrupt() { + // Si estaba en un HALT esperando una INT, lo saca de la espera + halted = false; + + m_busInterface.interruptHandlingTime(7); + + regR++; + ffIFF1 = ffIFF2 = false; + push(REG_PC); // el push añadirá 6 t-estados (+contended si toca) + if (modeINT == IntMode::IM2) { + REG_PC = m_busInterface.peek16((regI << 8) | 0xff); // +6 t-estados + } else { + REG_PC = 0x0038; + } + REG_WZ = REG_PC; +} + +// Interrupción NMI, no utilizado por ahora +/* Desglose de ciclos de máquina y T-Estados + * M1: 5 T-Estados -> extraer opcode (pá ná, es tontería) y decSP + * M2: 3 T-Estados -> escribe byte alto de PC y decSP + * M3: 3 T-Estados -> escrib e byte bajo de PC y PC=0x0066 + */ +template void Z80::nmi() { + halted = false; + // Esta lectura consigue dos cosas: + // 1.- La lectura del opcode del M1 que se descarta + // 2.- Si estaba en un HALT esperando una INT, lo saca de la espera + m_busInterface.fetchOpcode(REG_PC); + m_busInterface.interruptHandlingTime(1); + regR++; + ffIFF1 = false; + push(REG_PC); // 3+3 t-estados + contended si procede + REG_PC = REG_WZ = 0x0066; +} + +template void Z80::execute() { + prefixOpcode = 0; + + if (halted) { + m_opCode = m_busInterface.fetchOpcode(REG_PC); + regR++; + } else { + uint8_t currentPrefix = 0; + bool firstByteOfInstruction = true; + + while (true) { + m_opCode = m_busInterface.fetchOpcode(REG_PC); + regR++; + +#ifdef WITH_BREAKPOINT_SUPPORT + if (breakpointEnabled && currentPrefix == 0) { + m_opCode = m_busInterface.breakpoint(REG_PC, m_opCode); + } +#endif + + REG_PC++; + + if (firstByteOfInstruction && currentPrefix == 0) { + // Optimization: Most instructions modify flags, so set flagQ = true by default. + // Only instructions that do NOT modify flags need to set flagQ = false. + flagQ = true; + pendingEI = false; + } + + switch (currentPrefix) { + case 0x00: + decodeOpcode(m_opCode); + break; + case 0xDD: + decodeDDFD(m_opCode, regIX); + break; + case 0xED: + decodeED(m_opCode); + break; + case 0xFD: + decodeDDFD(m_opCode, regIY); + break; + default: + return; + } + + if (Z80_UNLIKELY(prefixOpcode != 0)) { + currentPrefix = prefixOpcode; + prefixOpcode = 0; + firstByteOfInstruction = false; + continue; + } + + break; + } + + lastFlagQ = flagQ; + +#ifdef WITH_EXEC_DONE + if (Z80_UNLIKELY(execDone)) { + m_busInterface.execDone(); + } +#endif + } + + // Primero se comprueba NMI + // Si se activa NMI no se comprueba INT porque la siguiente + // instrucción debe ser la de 0x0066. + if (Z80_UNLIKELY(activeNMI)) { + activeNMI = false; + lastFlagQ = false; + nmi(); + return; + } + + // Ahora se comprueba si está activada la señal INT + if (Z80_UNLIKELY(ffIFF1 && !pendingEI && m_busInterface.isActiveINT())) { + lastFlagQ = false; + interrupt(); + } +} + +template void Z80::decodeOpcode(uint8_t opCode) { + + switch (opCode) { + default: + case 0x00: /* NOP */ + break; + + case 0x01: /* LD BC,nn */ + REG_BC = m_busInterface.peek16(REG_PC); + REG_PC = REG_PC + 2; + break; + + case 0x02: /* LD (BC),A */ + m_busInterface.poke8(REG_BC, regA); + REG_W = regA; + REG_Z = REG_C + 1; + // REG_WZ = (regA << 8) | (REG_C + 1); + break; + + case 0x03: /* INC BC */ + m_busInterface.addressOnBus(getPairIR().word, 2); + REG_BC++; + break; + + case 0x04: /* INC B */ + inc8(REG_B); + break; + + case 0x05: /* DEC B */ + dec8(REG_B); + break; + + case 0x06: /* LD B,n */ + REG_B = m_busInterface.peek8(REG_PC); + REG_PC++; + break; + + case 0x07: { /* RLCA */ + carryFlag = (regA > 0x7f); + regA <<= 1; + if (carryFlag) { + regA |= CARRY_MASK; + } + sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZP_MASK) | (regA & FLAG_53_MASK); + break; + } + case 0x08: /* EX AF,AF' */ { + uint8_t work8 = regA; + regA = REG_Ax; + REG_Ax = work8; + + work8 = getFlags(); + setFlags(REG_Fx); + REG_Fx = work8; + break; + } + + case 0x09: /* ADD HL,BC */ + m_busInterface.addressOnBus(getPairIR().word, 7); + add16(regHL, REG_BC); + break; + + case 0x0A: /* LD A,(BC) */ + regA = m_busInterface.peek8(REG_BC); + REG_WZ = REG_BC + 1; + break; + + case 0x0B: /* DEC BC */ + m_busInterface.addressOnBus(getPairIR().word, 2); + REG_BC--; + break; + + case 0x0C: /* INC C */ + inc8(REG_C); + break; + + case 0x0D: /* DEC C */ + dec8(REG_C); + break; + + case 0x0E: /* LD C,n */ + REG_C = m_busInterface.peek8(REG_PC); + REG_PC++; + break; + + case 0x0F: { /* RRCA */ + carryFlag = (regA & CARRY_MASK) != 0; + regA >>= 1; + if (carryFlag) { + regA |= SIGN_MASK; + } + sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZP_MASK) | (regA & FLAG_53_MASK); + break; + } + case 0x10: { /* DJNZ e */ + m_busInterface.addressOnBus(getPairIR().word, 1); + auto offset = static_cast(m_busInterface.peek8(REG_PC)); + if (--REG_B != 0) { + m_busInterface.addressOnBus(REG_PC, 5); + REG_PC = REG_WZ = REG_PC + offset + 1; + } else { + REG_PC++; + } + break; + } + case 0x11: /* LD DE,nn */ + REG_DE = m_busInterface.peek16(REG_PC); + REG_PC = REG_PC + 2; + break; + + case 0x12: /* LD (DE),A */ + m_busInterface.poke8(REG_DE, regA); + REG_W = regA; + REG_Z = REG_E + 1; + // REG_WZ = (regA << 8) | (REG_E + 1); + break; + + case 0x13: /* INC DE */ + m_busInterface.addressOnBus(getPairIR().word, 2); + REG_DE++; + break; + + case 0x14: /* INC D */ + inc8(REG_D); + break; + + case 0x15: /* DEC D */ + dec8(REG_D); + break; + + case 0x16: /* LD D,n */ + REG_D = m_busInterface.peek8(REG_PC); + REG_PC++; + break; + + case 0x17: { /* RLA */ + bool oldCarry = carryFlag; + carryFlag = regA > 0x7f; + regA <<= 1; + if (oldCarry) { + regA |= CARRY_MASK; + } + sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZP_MASK) | (regA & FLAG_53_MASK); + break; + } + case 0x18: { /* JR e */ + auto offset = static_cast(m_busInterface.peek8(REG_PC)); + m_busInterface.addressOnBus(REG_PC, 5); + REG_PC = REG_WZ = REG_PC + offset + 1; + break; + } + case 0x19: /* ADD HL,DE */ + m_busInterface.addressOnBus(getPairIR().word, 7); + add16(regHL, REG_DE); + break; + + case 0x1A: /* LD A,(DE) */ + regA = m_busInterface.peek8(REG_DE); + REG_WZ = REG_DE + 1; + break; + + case 0x1B: /* DEC DE */ + m_busInterface.addressOnBus(getPairIR().word, 2); + REG_DE--; + break; + + case 0x1C: /* INC E */ + inc8(REG_E); + break; + + case 0x1D: /* DEC E */ + dec8(REG_E); + break; + + case 0x1E: /* LD E,n */ + REG_E = m_busInterface.peek8(REG_PC); + REG_PC++; + break; + + case 0x1F: { /* RRA */ + bool oldCarry = carryFlag; + carryFlag = (regA & CARRY_MASK) != 0; + regA >>= 1; + if (oldCarry) { + regA |= SIGN_MASK; + } + sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZP_MASK) | (regA & FLAG_53_MASK); + break; + } + case 0x20: { /* JR NZ,e */ + auto offset = static_cast(m_busInterface.peek8(REG_PC)); + if ((sz5h3pnFlags & ZERO_MASK) == 0) { + m_busInterface.addressOnBus(REG_PC, 5); + REG_PC += offset; + REG_WZ = REG_PC + 1; + } + REG_PC++; + break; + } + case 0x21: /* LD HL,nn */ + REG_HL = m_busInterface.peek16(REG_PC); + REG_PC = REG_PC + 2; + break; + + case 0x22: /* LD (nn),HL */ + REG_WZ = m_busInterface.peek16(REG_PC); + m_busInterface.poke16(REG_WZ, regHL); + REG_WZ++; + REG_PC = REG_PC + 2; + break; + + case 0x23: /* INC HL */ + m_busInterface.addressOnBus(getPairIR().word, 2); + REG_HL++; + break; + + case 0x24: /* INC H */ + inc8(REG_H); + break; + + case 0x25: /* DEC H */ + dec8(REG_H); + break; + + case 0x26: /* LD H,n */ + REG_H = m_busInterface.peek8(REG_PC); + REG_PC++; + break; + + case 0x27: /* DAA */ + daa(); + break; + + case 0x28: { /* JR Z,e */ + auto offset = static_cast(m_busInterface.peek8(REG_PC)); + if ((sz5h3pnFlags & ZERO_MASK) != 0) { + m_busInterface.addressOnBus(REG_PC, 5); + REG_PC += offset; + REG_WZ = REG_PC + 1; + } + REG_PC++; + break; + } + case 0x29: /* ADD HL,HL */ + m_busInterface.addressOnBus(getPairIR().word, 7); + add16(regHL, REG_HL); + break; + + case 0x2A: /* LD HL,(nn) */ + REG_WZ = m_busInterface.peek16(REG_PC); + REG_HL = m_busInterface.peek16(REG_WZ); + REG_WZ++; + REG_PC = REG_PC + 2; + break; + + case 0x2B: /* DEC HL */ + m_busInterface.addressOnBus(getPairIR().word, 2); + REG_HL--; + break; + + case 0x2C: /* INC L */ + inc8(REG_L); + break; + + case 0x2D: /* DEC L */ + dec8(REG_L); + break; + + case 0x2E: /* LD L,n */ + REG_L = m_busInterface.peek8(REG_PC); + REG_PC++; + break; + + case 0x2F: /* CPL */ + regA ^= 0xff; + sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZP_MASK) | HALFCARRY_MASK | (regA & FLAG_53_MASK) | ADDSUB_MASK; + break; + + case 0x30: { /* JR NC,e */ + auto offset = static_cast(m_busInterface.peek8(REG_PC)); + if (!carryFlag) { + m_busInterface.addressOnBus(REG_PC, 5); + REG_PC += offset; + REG_WZ = REG_PC + 1; + } + REG_PC++; + break; + } + case 0x31: /* LD SP,nn */ + REG_SP = m_busInterface.peek16(REG_PC); + REG_PC = REG_PC + 2; + break; + + case 0x32: /* LD (nn),A */ + REG_WZ = m_busInterface.peek16(REG_PC); + m_busInterface.poke8(REG_WZ, regA); + REG_WZ = (regA << 8) | ((REG_WZ + 1) & 0xff); + REG_PC = REG_PC + 2; + break; + + case 0x33: /* INC SP */ + m_busInterface.addressOnBus(getPairIR().word, 2); + REG_SP++; + break; + + case 0x34: /* INC (HL) */ { + uint8_t work8 = m_busInterface.peek8(REG_HL); + inc8(work8); + m_busInterface.addressOnBus(REG_HL, 1); + m_busInterface.poke8(REG_HL, work8); + break; + } + + case 0x35: /* DEC (HL) */ { + uint8_t work8 = m_busInterface.peek8(REG_HL); + dec8(work8); + m_busInterface.addressOnBus(REG_HL, 1); + m_busInterface.poke8(REG_HL, work8); + break; + } + + case 0x36: /* LD (HL),n */ + m_busInterface.poke8(REG_HL, m_busInterface.peek8(REG_PC)); + REG_PC++; + break; + + case 0x37: /* SCF */ { + uint8_t regQ = lastFlagQ ? sz5h3pnFlags : 0; + carryFlag = true; + sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZP_MASK) | (((regQ ^ sz5h3pnFlags) | regA) & FLAG_53_MASK); + break; + } + + case 0x38: { /* JR C,e */ + auto offset = static_cast(m_busInterface.peek8(REG_PC)); + if (carryFlag) { + m_busInterface.addressOnBus(REG_PC, 5); + REG_PC += offset; + REG_WZ = REG_PC + 1; + } + REG_PC++; + break; + } + case 0x39: /* ADD HL,SP */ + m_busInterface.addressOnBus(getPairIR().word, 7); + add16(regHL, REG_SP); + break; + + case 0x3A: /* LD A,(nn) */ + REG_WZ = m_busInterface.peek16(REG_PC); + regA = m_busInterface.peek8(REG_WZ); + REG_WZ++; + REG_PC = REG_PC + 2; + break; + + case 0x3B: /* DEC SP */ + m_busInterface.addressOnBus(getPairIR().word, 2); + REG_SP--; + break; + + case 0x3C: /* INC A */ + inc8(regA); + break; + + case 0x3D: /* DEC A */ + dec8(regA); + break; + + case 0x3E: /* LD A,n */ + regA = m_busInterface.peek8(REG_PC); + REG_PC++; + break; + + case 0x3F: { /* CCF */ + uint8_t regQ = lastFlagQ ? sz5h3pnFlags : 0; + sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZP_MASK) | (((regQ ^ sz5h3pnFlags) | regA) & FLAG_53_MASK); + if (carryFlag) { + sz5h3pnFlags |= HALFCARRY_MASK; + } + carryFlag = !carryFlag; + break; + } + case 0x40: /* LD B,B */ + break; + + case 0x41: /* LD B,C */ + REG_B = REG_C; + break; + + case 0x42: /* LD B,D */ + REG_B = REG_D; + break; + + case 0x43: /* LD B,E */ + REG_B = REG_E; + break; + + case 0x44: /* LD B,H */ + REG_B = REG_H; + break; + + case 0x45: /* LD B,L */ + REG_B = REG_L; + break; + + case 0x46: /* LD B,(HL) */ + REG_B = m_busInterface.peek8(REG_HL); + break; + + case 0x47: /* LD B,A */ + REG_B = regA; + break; + + case 0x48: /* LD C,B */ + REG_C = REG_B; + break; + + case 0x49: /* LD C,C */ + break; + + case 0x4A: /* LD C,D */ + REG_C = REG_D; + break; + + case 0x4B: /* LD C,E */ + REG_C = REG_E; + break; + + case 0x4C: /* LD C,H */ + REG_C = REG_H; + break; + + case 0x4D: /* LD C,L */ + REG_C = REG_L; + break; + + case 0x4E: /* LD C,(HL) */ + REG_C = m_busInterface.peek8(REG_HL); + break; + + case 0x4F: /* LD C,A */ + REG_C = regA; + break; + + case 0x50: /* LD D,B */ + REG_D = REG_B; + break; + + case 0x51: /* LD D,C */ + REG_D = REG_C; + break; + + case 0x52: /* LD D,D */ + break; + + case 0x53: /* LD D,E */ + REG_D = REG_E; + break; + + case 0x54: /* LD D,H */ + REG_D = REG_H; + break; + + case 0x55: /* LD D,L */ + REG_D = REG_L; + break; + + case 0x56: /* LD D,(HL) */ + REG_D = m_busInterface.peek8(REG_HL); + break; + + case 0x57: /* LD D,A */ + REG_D = regA; + break; + + case 0x58: /* LD E,B */ + REG_E = REG_B; + break; + + case 0x59: /* LD E,C */ + REG_E = REG_C; + break; + + case 0x5A: /* LD E,D */ + REG_E = REG_D; + break; + + case 0x5B: /* LD E,E */ + break; + + case 0x5C: /* LD E,H */ + REG_E = REG_H; + break; + + case 0x5D: /* LD E,L */ + REG_E = REG_L; + break; + + case 0x5E: /* LD E,(HL) */ + REG_E = m_busInterface.peek8(REG_HL); + break; + + case 0x5F: /* LD E,A */ + REG_E = regA; + break; + + case 0x60: /* LD H,B */ + REG_H = REG_B; + break; + + case 0x61: /* LD H,C */ + REG_H = REG_C; + break; + + case 0x62: /* LD H,D */ + REG_H = REG_D; + break; + + case 0x63: /* LD H,E */ + REG_H = REG_E; + break; + + case 0x64: /* LD H,H */ + break; + + case 0x65: /* LD H,L */ + REG_H = REG_L; + break; + + case 0x66: /* LD H,(HL) */ + REG_H = m_busInterface.peek8(REG_HL); + break; + + case 0x67: /* LD H,A */ + REG_H = regA; + break; + + case 0x68: /* LD L,B */ + REG_L = REG_B; + break; + + case 0x69: /* LD L,C */ + REG_L = REG_C; + break; + + case 0x6A: /* LD L,D */ + REG_L = REG_D; + break; + + case 0x6B: /* LD L,E */ + REG_L = REG_E; + break; + + case 0x6C: /* LD L,H */ + REG_L = REG_H; + break; + + case 0x6D: /* LD L,L */ + break; + + case 0x6E: /* LD L,(HL) */ + REG_L = m_busInterface.peek8(REG_HL); + break; + + case 0x6F: /* LD L,A */ + REG_L = regA; + break; + + case 0x70: /* LD (HL),B */ + m_busInterface.poke8(REG_HL, REG_B); + break; + + case 0x71: /* LD (HL),C */ + m_busInterface.poke8(REG_HL, REG_C); + break; + + case 0x72: /* LD (HL),D */ + m_busInterface.poke8(REG_HL, REG_D); + break; + + case 0x73: /* LD (HL),E */ + m_busInterface.poke8(REG_HL, REG_E); + break; + + case 0x74: /* LD (HL),H */ + m_busInterface.poke8(REG_HL, REG_H); + break; + + case 0x75: /* LD (HL),L */ + m_busInterface.poke8(REG_HL, REG_L); + break; + + case 0x76: /* HALT */ + halted = true; + break; + + case 0x77: /* LD (HL),A */ + m_busInterface.poke8(REG_HL, regA); + break; + + case 0x78: /* LD A,B */ + regA = REG_B; + break; + + case 0x79: /* LD A,C */ + regA = REG_C; + break; + + case 0x7A: /* LD A,D */ + regA = REG_D; + break; + + case 0x7B: /* LD A,E */ + regA = REG_E; + break; + + case 0x7C: /* LD A,H */ + regA = REG_H; + break; + + case 0x7D: /* LD A,L */ + regA = REG_L; + break; + + case 0x7E: /* LD A,(HL) */ + regA = m_busInterface.peek8(REG_HL); + break; + + case 0x7F: /* LD A,A */ + break; + + case 0x80: /* ADD A,B */ + add(REG_B); + break; + + case 0x81: /* ADD A,C */ + add(REG_C); + break; + + case 0x82: /* ADD A,D */ + add(REG_D); + break; + + case 0x83: /* ADD A,E */ + add(REG_E); + break; + + case 0x84: /* ADD A,H */ + add(REG_H); + break; + + case 0x85: /* ADD A,L */ + add(REG_L); + break; + + case 0x86: /* ADD A,(HL) */ + add(m_busInterface.peek8(REG_HL)); + break; + + case 0x87: /* ADD A,A */ + add(regA); + break; + + case 0x88: /* ADC A,B */ + adc(REG_B); + break; + + case 0x89: /* ADC A,C */ + adc(REG_C); + break; + + case 0x8A: /* ADC A,D */ + adc(REG_D); + break; + + case 0x8B: /* ADC A,E */ + adc(REG_E); + break; + + case 0x8C: /* ADC A,H */ + adc(REG_H); + break; + + case 0x8D: /* ADC A,L */ + adc(REG_L); + break; + + case 0x8E: /* ADC A,(HL) */ + adc(m_busInterface.peek8(REG_HL)); + break; + + case 0x8F: /* ADC A,A */ + adc(regA); + break; + + case 0x90: /* SUB B */ + sub(REG_B); + break; + + case 0x91: /* SUB C */ + sub(REG_C); + break; + + case 0x92: /* SUB D */ + sub(REG_D); + break; + + case 0x93: /* SUB E */ + sub(REG_E); + break; + + case 0x94: /* SUB H */ + sub(REG_H); + break; + + case 0x95: /* SUB L */ + sub(REG_L); + break; + + case 0x96: /* SUB (HL) */ + sub(m_busInterface.peek8(REG_HL)); + break; + + case 0x97: /* SUB A */ + sub(regA); + break; + + case 0x98: /* SBC A,B */ + sbc(REG_B); + break; + + case 0x99: /* SBC A,C */ + sbc(REG_C); + break; + + case 0x9A: /* SBC A,D */ + sbc(REG_D); + break; + + case 0x9B: /* SBC A,E */ + sbc(REG_E); + break; + + case 0x9C: /* SBC A,H */ + sbc(REG_H); + break; + + case 0x9D: /* SBC A,L */ + sbc(REG_L); + break; + + case 0x9E: /* SBC A,(HL) */ + sbc(m_busInterface.peek8(REG_HL)); + break; + + case 0x9F: /* SBC A,A */ + sbc(regA); + break; + + case 0xA0: /* AND B */ + and_(REG_B); + break; + + case 0xA1: /* AND C */ + and_(REG_C); + break; + + case 0xA2: /* AND D */ + and_(REG_D); + break; + + case 0xA3: /* AND E */ + and_(REG_E); + break; + + case 0xA4: /* AND H */ + and_(REG_H); + break; + + case 0xA5: /* AND L */ + and_(REG_L); + break; + + case 0xA6: /* AND (HL) */ + and_(m_busInterface.peek8(REG_HL)); + break; + + case 0xA7: /* AND A */ + and_(regA); + break; + + case 0xA8: /* XOR B */ + xor_(REG_B); + break; + + case 0xA9: /* XOR C */ + xor_(REG_C); + break; + + case 0xAA: /* XOR D */ + xor_(REG_D); + break; + + case 0xAB: /* XOR E */ + xor_(REG_E); + break; + + case 0xAC: /* XOR H */ + xor_(REG_H); + break; + + case 0xAD: /* XOR L */ + xor_(REG_L); + break; + + case 0xAE: /* XOR (HL) */ + xor_(m_busInterface.peek8(REG_HL)); + break; + + case 0xAF: /* XOR A */ + xor_(regA); + break; + + case 0xB0: /* OR B */ + or_(REG_B); + break; + + case 0xB1: /* OR C */ + or_(REG_C); + break; + + case 0xB2: /* OR D */ + or_(REG_D); + break; + + case 0xB3: /* OR E */ + or_(REG_E); + break; + + case 0xB4: /* OR H */ + or_(REG_H); + break; + + case 0xB5: /* OR L */ + or_(REG_L); + break; + + case 0xB6: /* OR (HL) */ + or_(m_busInterface.peek8(REG_HL)); + break; + + case 0xB7: /* OR A */ + or_(regA); + break; + + case 0xB8: /* CP B */ + cp(REG_B); + break; + + case 0xB9: /* CP C */ + cp(REG_C); + break; + + case 0xBA: /* CP D */ + cp(REG_D); + break; + + case 0xBB: /* CP E */ + cp(REG_E); + break; + + case 0xBC: /* CP H */ + cp(REG_H); + break; + + case 0xBD: /* CP L */ + cp(REG_L); + break; + + case 0xBE: /* CP (HL) */ + cp(m_busInterface.peek8(REG_HL)); + break; + + case 0xBF: /* CP A */ + cp(regA); + break; + + case 0xC0: { /* RET NZ */ + m_busInterface.addressOnBus(getPairIR().word, 1); + if ((sz5h3pnFlags & ZERO_MASK) == 0) { + REG_PC = REG_WZ = pop(); + } + break; + } + case 0xC1: /* POP BC */ + REG_BC = pop(); + break; + + case 0xC2: { /* JP NZ,nn */ + REG_WZ = m_busInterface.peek16(REG_PC); + if ((sz5h3pnFlags & ZERO_MASK) == 0) { + REG_PC = REG_WZ; + break; + } + REG_PC = REG_PC + 2; + break; + } + case 0xC3: /* JP nn */ + REG_WZ = REG_PC = m_busInterface.peek16(REG_PC); + break; + + case 0xC4: { /* CALL NZ,nn */ + REG_WZ = m_busInterface.peek16(REG_PC); + if ((sz5h3pnFlags & ZERO_MASK) == 0) { + m_busInterface.addressOnBus(REG_PC + 1, 1); + push(REG_PC + 2); + REG_PC = REG_WZ; + break; + } + REG_PC = REG_PC + 2; + break; + } + case 0xC5: /* PUSH BC */ + m_busInterface.addressOnBus(getPairIR().word, 1); + push(REG_BC); + break; + + case 0xC6: /* ADD A,n */ + add(m_busInterface.peek8(REG_PC)); + REG_PC++; + break; + + case 0xC7: /* RST 00H */ + m_busInterface.addressOnBus(getPairIR().word, 1); + push(REG_PC); + REG_PC = REG_WZ = 0x00; + break; + + case 0xC8: { /* RET Z */ + m_busInterface.addressOnBus(getPairIR().word, 1); + if ((sz5h3pnFlags & ZERO_MASK) != 0) { + REG_PC = REG_WZ = pop(); + } + break; + } + case 0xC9: /* RET */ + REG_PC = REG_WZ = pop(); + break; + + case 0xCA: { /* JP Z,nn */ + REG_WZ = m_busInterface.peek16(REG_PC); + if ((sz5h3pnFlags & ZERO_MASK) != 0) { + REG_PC = REG_WZ; + break; + } + REG_PC = REG_PC + 2; + break; + } + case 0xCB: /* Subconjunto de instrucciones */ + decodeCB(); + break; + + case 0xCC: { /* CALL Z,nn */ + REG_WZ = m_busInterface.peek16(REG_PC); + if ((sz5h3pnFlags & ZERO_MASK) != 0) { + m_busInterface.addressOnBus(REG_PC + 1, 1); + push(REG_PC + 2); + REG_PC = REG_WZ; + break; + } + REG_PC = REG_PC + 2; + break; + } + case 0xCD: /* CALL nn */ + REG_WZ = m_busInterface.peek16(REG_PC); + m_busInterface.addressOnBus(REG_PC + 1, 1); + push(REG_PC + 2); + REG_PC = REG_WZ; + break; + + case 0xCE: /* ADC A,n */ + adc(m_busInterface.peek8(REG_PC)); + REG_PC++; + break; + + case 0xCF: /* RST 08H */ + m_busInterface.addressOnBus(getPairIR().word, 1); + push(REG_PC); + REG_PC = REG_WZ = 0x08; + break; + + case 0xD0: { /* RET NC */ + m_busInterface.addressOnBus(getPairIR().word, 1); + if (!carryFlag) { + REG_PC = REG_WZ = pop(); + } + break; + } + case 0xD1: /* POP DE */ + REG_DE = pop(); + break; + + case 0xD2: { /* JP NC,nn */ + REG_WZ = m_busInterface.peek16(REG_PC); + if (!carryFlag) { + REG_PC = REG_WZ; + break; + } + REG_PC = REG_PC + 2; + break; + } + case 0xD3: /* OUT (n),A */ { + uint8_t work8 = m_busInterface.peek8(REG_PC); + REG_PC++; + REG_WZ = regA << 8; + m_busInterface.outPort(REG_WZ | work8, regA); + REG_WZ |= (work8 + 1); + break; + } + + case 0xD4: { /* CALL NC,nn */ + REG_WZ = m_busInterface.peek16(REG_PC); + if (!carryFlag) { + m_busInterface.addressOnBus(REG_PC + 1, 1); + push(REG_PC + 2); + REG_PC = REG_WZ; + break; + } + REG_PC = REG_PC + 2; + break; + } + case 0xD5: /* PUSH DE */ + m_busInterface.addressOnBus(getPairIR().word, 1); + push(REG_DE); + break; + + case 0xD6: /* SUB n */ + sub(m_busInterface.peek8(REG_PC)); + REG_PC++; + break; + + case 0xD7: /* RST 10H */ + m_busInterface.addressOnBus(getPairIR().word, 1); + push(REG_PC); + REG_PC = REG_WZ = 0x10; + break; + + case 0xD8: { /* RET C */ + m_busInterface.addressOnBus(getPairIR().word, 1); + if (carryFlag) { + REG_PC = REG_WZ = pop(); + } + break; + } + case 0xD9: /* EXX */ { + uint16_t tmp = 0; + tmp = REG_BC; + REG_BC = REG_BCx; + REG_BCx = tmp; + + tmp = REG_DE; + REG_DE = REG_DEx; + REG_DEx = tmp; + + tmp = REG_HL; + REG_HL = REG_HLx; + REG_HLx = tmp; + break; + } + + case 0xDA: { /* JP C,nn */ + REG_WZ = m_busInterface.peek16(REG_PC); + if (carryFlag) { + REG_PC = REG_WZ; + break; + } + REG_PC = REG_PC + 2; + break; + } + case 0xDB: /* IN A,(n) */ + REG_W = regA; + REG_Z = m_busInterface.peek8(REG_PC); + // REG_WZ = (regA << 8) | m_busInterface.peek8(REG_PC); + REG_PC++; + regA = m_busInterface.inPort(REG_WZ); + REG_WZ++; + break; + + case 0xDC: { /* CALL C,nn */ + REG_WZ = m_busInterface.peek16(REG_PC); + if (carryFlag) { + m_busInterface.addressOnBus(REG_PC + 1, 1); + push(REG_PC + 2); + REG_PC = REG_WZ; + break; + } + REG_PC = REG_PC + 2; + break; + } + case 0xDD: /* Subconjunto de instrucciones */ + opCode = m_busInterface.fetchOpcode(REG_PC++); + regR++; + decodeDDFD(opCode, regIX); + break; + + case 0xDE: /* SBC A,n */ + sbc(m_busInterface.peek8(REG_PC)); + REG_PC++; + break; + + case 0xDF: /* RST 18H */ + m_busInterface.addressOnBus(getPairIR().word, 1); + push(REG_PC); + REG_PC = REG_WZ = 0x18; + break; + + case 0xE0: /* RET PO */ + m_busInterface.addressOnBus(getPairIR().word, 1); + if ((sz5h3pnFlags & PARITY_MASK) == 0) { + REG_PC = REG_WZ = pop(); + } + break; + case 0xE1: /* POP HL */ + REG_HL = pop(); + break; + case 0xE2: /* JP PO,nn */ + REG_WZ = m_busInterface.peek16(REG_PC); + if ((sz5h3pnFlags & PARITY_MASK) == 0) { + REG_PC = REG_WZ; + break; + } + REG_PC = REG_PC + 2; + break; + case 0xE3: /* EX (SP),HL */ { + // Instrucción de ejecución sutil. + RegisterPair work = regHL; + REG_HL = m_busInterface.peek16(REG_SP); + m_busInterface.addressOnBus(REG_SP + 1, 1); + // No se usa poke16 porque el Z80 escribe los bytes AL REVES + m_busInterface.poke8(REG_SP + 1, work.byte8.hi); + m_busInterface.poke8(REG_SP, work.byte8.lo); + m_busInterface.addressOnBus(REG_SP, 2); + REG_WZ = REG_HL; + break; + } + + case 0xE4: /* CALL PO,nn */ + REG_WZ = m_busInterface.peek16(REG_PC); + if ((sz5h3pnFlags & PARITY_MASK) == 0) { + m_busInterface.addressOnBus(REG_PC + 1, 1); + push(REG_PC + 2); + REG_PC = REG_WZ; + break; + } + REG_PC = REG_PC + 2; + break; + case 0xE5: /* PUSH HL */ + m_busInterface.addressOnBus(getPairIR().word, 1); + push(REG_HL); + break; + case 0xE6: /* AND n */ + and_(m_busInterface.peek8(REG_PC)); + REG_PC++; + break; + case 0xE7: /* RST 20H */ + m_busInterface.addressOnBus(getPairIR().word, 1); + push(REG_PC); + REG_PC = REG_WZ = 0x20; + break; + case 0xE8: /* RET PE */ + m_busInterface.addressOnBus(getPairIR().word, 1); + if ((sz5h3pnFlags & PARITY_MASK) != 0) { + REG_PC = REG_WZ = pop(); + } + break; + case 0xE9: /* JP (HL) */ + REG_PC = REG_HL; + break; + case 0xEA: /* JP PE,nn */ + REG_WZ = m_busInterface.peek16(REG_PC); + if ((sz5h3pnFlags & PARITY_MASK) != 0) { + REG_PC = REG_WZ; + break; + } + REG_PC = REG_PC + 2; + break; + case 0xEB: /* EX DE,HL */ { + uint16_t tmp = REG_HL; + REG_HL = REG_DE; + REG_DE = tmp; + break; + } + + case 0xEC: /* CALL PE,nn */ + REG_WZ = m_busInterface.peek16(REG_PC); + if ((sz5h3pnFlags & PARITY_MASK) != 0) { + m_busInterface.addressOnBus(REG_PC + 1, 1); + push(REG_PC + 2); + REG_PC = REG_WZ; + break; + } + REG_PC = REG_PC + 2; + break; + case 0xED: /*Subconjunto de instrucciones*/ + opCode = m_busInterface.fetchOpcode(REG_PC++); + regR++; + decodeED(opCode); + break; + case 0xEE: /* XOR n */ + xor_(m_busInterface.peek8(REG_PC)); + REG_PC++; + break; + case 0xEF: /* RST 28H */ + m_busInterface.addressOnBus(getPairIR().word, 1); + push(REG_PC); + REG_PC = REG_WZ = 0x28; + break; + case 0xF0: /* RET P */ + m_busInterface.addressOnBus(getPairIR().word, 1); + if (sz5h3pnFlags < SIGN_MASK) { + REG_PC = REG_WZ = pop(); + } + break; + case 0xF1: /* POP AF */ + setRegAF(pop()); + break; + case 0xF2: /* JP P,nn */ + REG_WZ = m_busInterface.peek16(REG_PC); + if (sz5h3pnFlags < SIGN_MASK) { + REG_PC = REG_WZ; + break; + } + REG_PC = REG_PC + 2; + break; + case 0xF3: /* DI */ + ffIFF1 = ffIFF2 = false; + break; + case 0xF4: /* CALL P,nn */ + REG_WZ = m_busInterface.peek16(REG_PC); + if (sz5h3pnFlags < SIGN_MASK) { + m_busInterface.addressOnBus(REG_PC + 1, 1); + push(REG_PC + 2); + REG_PC = REG_WZ; + break; + } + REG_PC = REG_PC + 2; + break; + case 0xF5: /* PUSH AF */ + m_busInterface.addressOnBus(getPairIR().word, 1); + push(getRegAF()); + break; + case 0xF6: /* OR n */ + or_(m_busInterface.peek8(REG_PC)); + REG_PC++; + break; + case 0xF7: /* RST 30H */ + m_busInterface.addressOnBus(getPairIR().word, 1); + push(REG_PC); + REG_PC = REG_WZ = 0x30; + break; + case 0xF8: /* RET M */ + m_busInterface.addressOnBus(getPairIR().word, 1); + if (sz5h3pnFlags > 0x7f) { + REG_PC = REG_WZ = pop(); + } + break; + case 0xF9: /* LD SP,HL */ + m_busInterface.addressOnBus(getPairIR().word, 2); + REG_SP = REG_HL; + break; + case 0xFA: /* JP M,nn */ + REG_WZ = m_busInterface.peek16(REG_PC); + if (sz5h3pnFlags > 0x7f) { + REG_PC = REG_WZ; + break; + } + REG_PC = REG_PC + 2; + break; + case 0xFB: /* EI */ + ffIFF1 = ffIFF2 = true; + pendingEI = true; + break; + case 0xFC: /* CALL M,nn */ + REG_WZ = m_busInterface.peek16(REG_PC); + if (sz5h3pnFlags > 0x7f) { + m_busInterface.addressOnBus(REG_PC + 1, 1); + push(REG_PC + 2); + REG_PC = REG_WZ; + break; + } + REG_PC = REG_PC + 2; + break; + case 0xFD: /* Subconjunto de instrucciones */ + opCode = m_busInterface.fetchOpcode(REG_PC++); + regR++; + decodeDDFD(opCode, regIY); + break; + case 0xFE: /* CP n */ + cp(m_busInterface.peek8(REG_PC)); + REG_PC++; + break; + case 0xFF: /* RST 38H */ + m_busInterface.addressOnBus(getPairIR().word, 1); + push(REG_PC); + REG_PC = REG_WZ = 0x38; + } /* del switch( codigo ) */ +} + +// Subconjunto de instrucciones 0xCB + +template void Z80::decodeCB() { + uint8_t opCode = m_busInterface.fetchOpcode(REG_PC++); + regR++; + + switch (opCode) { + case 0x00: /* RLC B */ + rlc(REG_B); + break; + + case 0x01: /* RLC C */ + rlc(REG_C); + break; + + case 0x02: /* RLC D */ + rlc(REG_D); + break; + + case 0x03: /* RLC E */ + rlc(REG_E); + break; + + case 0x04: /* RLC H */ + rlc(REG_H); + break; + + case 0x05: /* RLC L */ + rlc(REG_L); + break; + + case 0x06: /* RLC (HL) */ { + uint8_t work8 = m_busInterface.peek8(REG_HL); + rlc(work8); + m_busInterface.addressOnBus(REG_HL, 1); + m_busInterface.poke8(REG_HL, work8); + break; + } + + case 0x07: /* RLC A */ + rlc(regA); + break; + + case 0x08: /* RRC B */ + rrc(REG_B); + break; + + case 0x09: /* RRC C */ + rrc(REG_C); + break; + + case 0x0A: /* RRC D */ + rrc(REG_D); + break; + + case 0x0B: /* RRC E */ + rrc(REG_E); + break; + + case 0x0C: /* RRC H */ + rrc(REG_H); + break; + + case 0x0D: /* RRC L */ + rrc(REG_L); + break; + + case 0x0E: /* RRC (HL) */ { + uint8_t work8 = m_busInterface.peek8(REG_HL); + rrc(work8); + m_busInterface.addressOnBus(REG_HL, 1); + m_busInterface.poke8(REG_HL, work8); + break; + } + + case 0x0F: /* RRC A */ + rrc(regA); + break; + + case 0x10: /* RL B */ + rl(REG_B); + break; + + case 0x11: /* RL C */ + rl(REG_C); + break; + + case 0x12: /* RL D */ + rl(REG_D); + break; + + case 0x13: /* RL E */ + rl(REG_E); + break; + + case 0x14: /* RL H */ + rl(REG_H); + break; + + case 0x15: /* RL L */ + rl(REG_L); + break; + + case 0x16: /* RL (HL) */ { + uint8_t work8 = m_busInterface.peek8(REG_HL); + rl(work8); + m_busInterface.addressOnBus(REG_HL, 1); + m_busInterface.poke8(REG_HL, work8); + break; + } + + case 0x17: /* RL A */ + rl(regA); + break; + + case 0x18: /* RR B */ + rr(REG_B); + break; + + case 0x19: /* RR C */ + rr(REG_C); + break; + + case 0x1A: /* RR D */ + rr(REG_D); + break; + + case 0x1B: /* RR E */ + rr(REG_E); + break; + + case 0x1C: /*RR H*/ + rr(REG_H); + break; + + case 0x1D: /* RR L */ + rr(REG_L); + break; + + case 0x1E: /* RR (HL) */ { + uint8_t work8 = m_busInterface.peek8(REG_HL); + rr(work8); + m_busInterface.addressOnBus(REG_HL, 1); + m_busInterface.poke8(REG_HL, work8); + break; + } + + case 0x1F: /* RR A */ + rr(regA); + break; + + case 0x20: /* SLA B */ + sla(REG_B); + break; + + case 0x21: /* SLA C */ + sla(REG_C); + break; + + case 0x22: /* SLA D */ + sla(REG_D); + break; + + case 0x23: /* SLA E */ + sla(REG_E); + break; + + case 0x24: /* SLA H */ + sla(REG_H); + break; + + case 0x25: /* SLA L */ + sla(REG_L); + break; + + case 0x26: /* SLA (HL) */ { + uint8_t work8 = m_busInterface.peek8(REG_HL); + sla(work8); + m_busInterface.addressOnBus(REG_HL, 1); + m_busInterface.poke8(REG_HL, work8); + break; + } + + case 0x27: /* SLA A */ + sla(regA); + break; + + case 0x28: /* SRA B */ + sra(REG_B); + break; + + case 0x29: /* SRA C */ + sra(REG_C); + break; + + case 0x2A: /* SRA D */ + sra(REG_D); + break; + + case 0x2B: /* SRA E */ + sra(REG_E); + break; + + case 0x2C: /* SRA H */ + sra(REG_H); + break; + + case 0x2D: /* SRA L */ + sra(REG_L); + break; + + case 0x2E: /* SRA (HL) */ { + uint8_t work8 = m_busInterface.peek8(REG_HL); + sra(work8); + m_busInterface.addressOnBus(REG_HL, 1); + m_busInterface.poke8(REG_HL, work8); + break; + } + + case 0x2F: /* SRA A */ + sra(regA); + break; + + case 0x30: /* SLL B */ + sll(REG_B); + break; + + case 0x31: /* SLL C */ + sll(REG_C); + break; + + case 0x32: /* SLL D */ + sll(REG_D); + break; + + case 0x33: /* SLL E */ + sll(REG_E); + break; + + case 0x34: /* SLL H */ + sll(REG_H); + break; + + case 0x35: /* SLL L */ + sll(REG_L); + break; + + case 0x36: /* SLL (HL) */ { + uint8_t work8 = m_busInterface.peek8(REG_HL); + sll(work8); + m_busInterface.addressOnBus(REG_HL, 1); + m_busInterface.poke8(REG_HL, work8); + break; + } + + case 0x37: /* SLL A */ + sll(regA); + break; + + case 0x38: /* SRL B */ + srl(REG_B); + break; + + case 0x39: /* SRL C */ + srl(REG_C); + break; + + case 0x3A: /* SRL D */ + srl(REG_D); + break; + + case 0x3B: /* SRL E */ + srl(REG_E); + break; + + case 0x3C: /* SRL H */ + srl(REG_H); + break; + + case 0x3D: /* SRL L */ + srl(REG_L); + break; + + case 0x3E: /* SRL (HL) */ { + uint8_t work8 = m_busInterface.peek8(REG_HL); + srl(work8); + m_busInterface.addressOnBus(REG_HL, 1); + m_busInterface.poke8(REG_HL, work8); + break; + } + + case 0x3F: /* SRL A */ + srl(regA); + break; + + case 0x40: /* BIT 0,B */ + bitTest(0x01, REG_B); + break; + + case 0x41: /* BIT 0,C */ + bitTest(0x01, REG_C); + break; + + case 0x42: /* BIT 0,D */ + bitTest(0x01, REG_D); + break; + + case 0x43: /* BIT 0,E */ + bitTest(0x01, REG_E); + break; + + case 0x44: /* BIT 0,H */ + bitTest(0x01, REG_H); + break; + + case 0x45: /* BIT 0,L */ + bitTest(0x01, REG_L); + break; + + case 0x46: /* BIT 0,(HL) */ + bitTest(0x01, m_busInterface.peek8(REG_HL)); + sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZHP_MASK) | (REG_W & FLAG_53_MASK); + m_busInterface.addressOnBus(REG_HL, 1); + break; + + case 0x47: /* BIT 0,A */ + bitTest(0x01, regA); + break; + + case 0x48: /* BIT 1,B */ + bitTest(0x02, REG_B); + break; + + case 0x49: /* BIT 1,C */ + bitTest(0x02, REG_C); + break; + + case 0x4A: /* BIT 1,D */ + bitTest(0x02, REG_D); + break; + + case 0x4B: /* BIT 1,E */ + bitTest(0x02, REG_E); + break; + + case 0x4C: /* BIT 1,H */ + bitTest(0x02, REG_H); + break; + + case 0x4D: /* BIT 1,L */ + bitTest(0x02, REG_L); + break; + + case 0x4E: /* BIT 1,(HL) */ + bitTest(0x02, m_busInterface.peek8(REG_HL)); + sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZHP_MASK) | (REG_W & FLAG_53_MASK); + m_busInterface.addressOnBus(REG_HL, 1); + break; + + case 0x4F: /* BIT 1,A */ + bitTest(0x02, regA); + break; + + case 0x50: /* BIT 2,B */ + bitTest(0x04, REG_B); + break; + + case 0x51: /* BIT 2,C */ + bitTest(0x04, REG_C); + break; + + case 0x52: /* BIT 2,D */ + bitTest(0x04, REG_D); + break; + + case 0x53: /* BIT 2,E */ + bitTest(0x04, REG_E); + break; + + case 0x54: /* BIT 2,H */ + bitTest(0x04, REG_H); + break; + + case 0x55: /* BIT 2,L */ + bitTest(0x04, REG_L); + break; + + case 0x56: /* BIT 2,(HL) */ + bitTest(0x04, m_busInterface.peek8(REG_HL)); + sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZHP_MASK) | (REG_W & FLAG_53_MASK); + m_busInterface.addressOnBus(REG_HL, 1); + break; + + case 0x57: /* BIT 2,A */ + bitTest(0x04, regA); + break; + + case 0x58: /* BIT 3,B */ + bitTest(0x08, REG_B); + break; + + case 0x59: /* BIT 3,C */ + bitTest(0x08, REG_C); + break; + + case 0x5A: /* BIT 3,D */ + bitTest(0x08, REG_D); + break; + + case 0x5B: /* BIT 3,E */ + bitTest(0x08, REG_E); + break; + + case 0x5C: /* BIT 3,H */ + bitTest(0x08, REG_H); + break; + + case 0x5D: /* BIT 3,L */ + bitTest(0x08, REG_L); + break; + + case 0x5E: /* BIT 3,(HL) */ + bitTest(0x08, m_busInterface.peek8(REG_HL)); + sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZHP_MASK) | (REG_W & FLAG_53_MASK); + m_busInterface.addressOnBus(REG_HL, 1); + break; + + case 0x5F: /* BIT 3,A */ + bitTest(0x08, regA); + break; + + case 0x60: /* BIT 4,B */ + bitTest(0x10, REG_B); + break; + + case 0x61: /* BIT 4,C */ + bitTest(0x10, REG_C); + break; + + case 0x62: /* BIT 4,D */ + bitTest(0x10, REG_D); + break; + + case 0x63: /* BIT 4,E */ + bitTest(0x10, REG_E); + break; + + case 0x64: /* BIT 4,H */ + bitTest(0x10, REG_H); + break; + + case 0x65: /* BIT 4,L */ + bitTest(0x10, REG_L); + break; + + case 0x66: /* BIT 4,(HL) */ + bitTest(0x10, m_busInterface.peek8(REG_HL)); + sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZHP_MASK) | (REG_W & FLAG_53_MASK); + m_busInterface.addressOnBus(REG_HL, 1); + break; + + case 0x67: /* BIT 4,A */ + bitTest(0x10, regA); + break; + + case 0x68: /* BIT 5,B */ + bitTest(0x20, REG_B); + break; + + case 0x69: /* BIT 5,C */ + bitTest(0x20, REG_C); + break; + + case 0x6A: /* BIT 5,D */ + bitTest(0x20, REG_D); + break; + + case 0x6B: /* BIT 5,E */ + bitTest(0x20, REG_E); + break; + + case 0x6C: /* BIT 5,H */ + bitTest(0x20, REG_H); + break; + + case 0x6D: /* BIT 5,L */ + bitTest(0x20, REG_L); + break; + + case 0x6E: /* BIT 5,(HL) */ + bitTest(0x20, m_busInterface.peek8(REG_HL)); + sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZHP_MASK) | (REG_W & FLAG_53_MASK); + m_busInterface.addressOnBus(REG_HL, 1); + break; + + case 0x6F: /* BIT 5,A */ + bitTest(0x20, regA); + break; + + case 0x70: /* BIT 6,B */ + bitTest(0x40, REG_B); + break; + + case 0x71: /* BIT 6,C */ + bitTest(0x40, REG_C); + break; + + case 0x72: /* BIT 6,D */ + bitTest(0x40, REG_D); + break; + + case 0x73: /* BIT 6,E */ + bitTest(0x40, REG_E); + break; + + case 0x74: /* BIT 6,H */ + bitTest(0x40, REG_H); + break; + + case 0x75: /* BIT 6,L */ + bitTest(0x40, REG_L); + break; + + case 0x76: /* BIT 6,(HL) */ + bitTest(0x40, m_busInterface.peek8(REG_HL)); + sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZHP_MASK) | (REG_W & FLAG_53_MASK); + m_busInterface.addressOnBus(REG_HL, 1); + break; + + case 0x77: /* BIT 6,A */ + bitTest(0x40, regA); + break; + + case 0x78: /* BIT 7,B */ + bitTest(0x80, REG_B); + break; + + case 0x79: /* BIT 7,C */ + bitTest(0x80, REG_C); + break; + + case 0x7A: /* BIT 7,D */ + bitTest(0x80, REG_D); + break; + + case 0x7B: /* BIT 7,E */ + bitTest(0x80, REG_E); + break; + + case 0x7C: /* BIT 7,H */ + bitTest(0x80, REG_H); + break; + + case 0x7D: /* BIT 7,L */ + bitTest(0x80, REG_L); + break; + + case 0x7E: /* BIT 7,(HL) */ + bitTest(0x80, m_busInterface.peek8(REG_HL)); + sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZHP_MASK) | (REG_W & FLAG_53_MASK); + m_busInterface.addressOnBus(REG_HL, 1); + break; + + case 0x7F: /* BIT 7,A */ + bitTest(0x80, regA); + break; + + case 0x80: /* RES 0,B */ + REG_B &= 0xFE; + break; + + case 0x81: /* RES 0,C */ + REG_C &= 0xFE; + break; + + case 0x82: /* RES 0,D */ + REG_D &= 0xFE; + break; + + case 0x83: /* RES 0,E */ + REG_E &= 0xFE; + break; + + case 0x84: /* RES 0,H */ + REG_H &= 0xFE; + break; + + case 0x85: /* RES 0,L */ + REG_L &= 0xFE; + break; + + case 0x86: /* RES 0,(HL) */ { + uint8_t work8 = m_busInterface.peek8(REG_HL) & 0xFE; + m_busInterface.addressOnBus(REG_HL, 1); + m_busInterface.poke8(REG_HL, work8); + break; + } + + case 0x87: /* RES 0,A */ + regA &= 0xFE; + break; + + case 0x88: /* RES 1,B */ + REG_B &= 0xFD; + break; + + case 0x89: /* RES 1,C */ + REG_C &= 0xFD; + break; + + case 0x8A: /* RES 1,D */ + REG_D &= 0xFD; + break; + + case 0x8B: /* RES 1,E */ + REG_E &= 0xFD; + break; + + case 0x8C: /* RES 1,H */ + REG_H &= 0xFD; + break; + + case 0x8D: /* RES 1,L */ + REG_L &= 0xFD; + break; + + case 0x8E: /* RES 1,(HL) */ { + uint8_t work8 = m_busInterface.peek8(REG_HL) & 0xFD; + m_busInterface.addressOnBus(REG_HL, 1); + m_busInterface.poke8(REG_HL, work8); + break; + } + + case 0x8F: /* RES 1,A */ + regA &= 0xFD; + break; + + case 0x90: /* RES 2,B */ + REG_B &= 0xFB; + break; + + case 0x91: /* RES 2,C */ + REG_C &= 0xFB; + break; + + case 0x92: /* RES 2,D */ + REG_D &= 0xFB; + break; + + case 0x93: /* RES 2,E */ + REG_E &= 0xFB; + break; + + case 0x94: /* RES 2,H */ + REG_H &= 0xFB; + break; + + case 0x95: /* RES 2,L */ + REG_L &= 0xFB; + break; + + case 0x96: /* RES 2,(HL) */ { + uint8_t work8 = m_busInterface.peek8(REG_HL) & 0xFB; + m_busInterface.addressOnBus(REG_HL, 1); + m_busInterface.poke8(REG_HL, work8); + break; + } + + case 0x97: /* RES 2,A */ + regA &= 0xFB; + break; + + case 0x98: /* RES 3,B */ + REG_B &= 0xF7; + break; + + case 0x99: /* RES 3,C */ + REG_C &= 0xF7; + break; + + case 0x9A: /* RES 3,D */ + REG_D &= 0xF7; + break; + + case 0x9B: /* RES 3,E */ + REG_E &= 0xF7; + break; + + case 0x9C: /* RES 3,H */ + REG_H &= 0xF7; + break; + + case 0x9D: /* RES 3,L */ + REG_L &= 0xF7; + break; + + case 0x9E: /* RES 3,(HL) */ { + uint8_t work8 = m_busInterface.peek8(REG_HL) & 0xF7; + m_busInterface.addressOnBus(REG_HL, 1); + m_busInterface.poke8(REG_HL, work8); + break; + } + + case 0x9F: /* RES 3,A */ + regA &= 0xF7; + break; + + case 0xA0: /* RES 4,B */ + REG_B &= 0xEF; + break; + + case 0xA1: /* RES 4,C */ + REG_C &= 0xEF; + break; + + case 0xA2: /* RES 4,D */ + REG_D &= 0xEF; + break; + + case 0xA3: /* RES 4,E */ + REG_E &= 0xEF; + break; + + case 0xA4: /* RES 4,H */ + REG_H &= 0xEF; + break; + + case 0xA5: /* RES 4,L */ + REG_L &= 0xEF; + break; + + case 0xA6: /* RES 4,(HL) */ { + uint8_t work8 = m_busInterface.peek8(REG_HL) & 0xEF; + m_busInterface.addressOnBus(REG_HL, 1); + m_busInterface.poke8(REG_HL, work8); + break; + } + + case 0xA7: /* RES 4,A */ + regA &= 0xEF; + break; + + case 0xA8: /* RES 5,B */ + REG_B &= 0xDF; + break; + + case 0xA9: /* RES 5,C */ + REG_C &= 0xDF; + break; + + case 0xAA: /* RES 5,D */ + REG_D &= 0xDF; + break; + + case 0xAB: /* RES 5,E */ + REG_E &= 0xDF; + break; + + case 0xAC: /* RES 5,H */ + REG_H &= 0xDF; + break; + + case 0xAD: /* RES 5,L */ + REG_L &= 0xDF; + break; + + case 0xAE: /* RES 5,(HL) */ { + uint8_t work8 = m_busInterface.peek8(REG_HL) & 0xDF; + m_busInterface.addressOnBus(REG_HL, 1); + m_busInterface.poke8(REG_HL, work8); + break; + } + + case 0xAF: /* RES 5,A */ + regA &= 0xDF; + break; + + case 0xB0: /* RES 6,B */ + REG_B &= 0xBF; + break; + + case 0xB1: /* RES 6,C */ + REG_C &= 0xBF; + break; + + case 0xB2: /* RES 6,D */ + REG_D &= 0xBF; + break; + + case 0xB3: /* RES 6,E */ + REG_E &= 0xBF; + break; + + case 0xB4: /* RES 6,H */ + REG_H &= 0xBF; + break; + + case 0xB5: /* RES 6,L */ + REG_L &= 0xBF; + break; + + case 0xB6: /* RES 6,(HL) */ { + uint8_t work8 = m_busInterface.peek8(REG_HL) & 0xBF; + m_busInterface.addressOnBus(REG_HL, 1); + m_busInterface.poke8(REG_HL, work8); + break; + } + + case 0xB7: /* RES 6,A */ + regA &= 0xBF; + break; + + case 0xB8: /* RES 7,B */ + REG_B &= 0x7F; + break; + + case 0xB9: /* RES 7,C */ + REG_C &= 0x7F; + break; + + case 0xBA: /* RES 7,D */ + REG_D &= 0x7F; + break; + + case 0xBB: /* RES 7,E */ + REG_E &= 0x7F; + break; + + case 0xBC: /* RES 7,H */ + REG_H &= 0x7F; + break; + + case 0xBD: /* RES 7,L */ + REG_L &= 0x7F; + break; + + case 0xBE: /* RES 7,(HL) */ { + uint8_t work8 = m_busInterface.peek8(REG_HL) & 0x7F; + m_busInterface.addressOnBus(REG_HL, 1); + m_busInterface.poke8(REG_HL, work8); + break; + } + + case 0xBF: /* RES 7,A */ + regA &= 0x7F; + break; + + case 0xC0: /* SET 0,B */ + REG_B |= 0x01; + break; + + case 0xC1: /* SET 0,C */ + REG_C |= 0x01; + break; + + case 0xC2: /* SET 0,D */ + REG_D |= 0x01; + break; + + case 0xC3: /* SET 0,E */ + REG_E |= 0x01; + break; + + case 0xC4: /* SET 0,H */ + REG_H |= 0x01; + break; + + case 0xC5: /* SET 0,L */ + REG_L |= 0x01; + break; + + case 0xC6: /* SET 0,(HL) */ { + uint8_t work8 = m_busInterface.peek8(REG_HL) | 0x01; + m_busInterface.addressOnBus(REG_HL, 1); + m_busInterface.poke8(REG_HL, work8); + break; + } + + case 0xC7: /* SET 0,A */ + regA |= 0x01; + break; + + case 0xC8: /* SET 1,B */ + REG_B |= 0x02; + break; + + case 0xC9: /* SET 1,C */ + REG_C |= 0x02; + break; + + case 0xCA: /* SET 1,D */ + REG_D |= 0x02; + break; + + case 0xCB: /* SET 1,E */ + REG_E |= 0x02; + break; + + case 0xCC: /* SET 1,H */ + REG_H |= 0x02; + break; + + case 0xCD: /* SET 1,L */ + REG_L |= 0x02; + break; + + case 0xCE: /* SET 1,(HL) */ { + uint8_t work8 = m_busInterface.peek8(REG_HL) | 0x02; + m_busInterface.addressOnBus(REG_HL, 1); + m_busInterface.poke8(REG_HL, work8); + break; + } + + case 0xCF: /* SET 1,A */ + regA |= 0x02; + break; + + case 0xD0: /* SET 2,B */ + REG_B |= 0x04; + break; + + case 0xD1: /* SET 2,C */ + REG_C |= 0x04; + break; + + case 0xD2: /* SET 2,D */ + REG_D |= 0x04; + break; + + case 0xD3: /* SET 2,E */ + REG_E |= 0x04; + break; + + case 0xD4: /* SET 2,H */ + REG_H |= 0x04; + break; + + case 0xD5: /* SET 2,L */ + REG_L |= 0x04; + break; + + case 0xD6: /* SET 2,(HL) */ { + uint8_t work8 = m_busInterface.peek8(REG_HL) | 0x04; + m_busInterface.addressOnBus(REG_HL, 1); + m_busInterface.poke8(REG_HL, work8); + break; + } + + case 0xD7: /* SET 2,A */ + regA |= 0x04; + break; + + case 0xD8: /* SET 3,B */ + REG_B |= 0x08; + break; + + case 0xD9: /* SET 3,C */ + REG_C |= 0x08; + break; + + case 0xDA: /* SET 3,D */ + REG_D |= 0x08; + break; + + case 0xDB: /* SET 3,E */ + REG_E |= 0x08; + break; + + case 0xDC: /* SET 3,H */ + REG_H |= 0x08; + break; + + case 0xDD: /* SET 3,L */ + REG_L |= 0x08; + break; + + case 0xDE: /* SET 3,(HL) */ { + uint8_t work8 = m_busInterface.peek8(REG_HL) | 0x08; + m_busInterface.addressOnBus(REG_HL, 1); + m_busInterface.poke8(REG_HL, work8); + break; + } + + case 0xDF: /* SET 3,A */ + regA |= 0x08; + break; + + case 0xE0: /* SET 4,B */ + REG_B |= 0x10; + break; + + case 0xE1: /* SET 4,C */ + REG_C |= 0x10; + break; + + case 0xE2: /* SET 4,D */ + REG_D |= 0x10; + break; + + case 0xE3: /* SET 4,E */ + REG_E |= 0x10; + break; + + case 0xE4: /* SET 4,H */ + REG_H |= 0x10; + break; + + case 0xE5: /* SET 4,L */ + REG_L |= 0x10; + break; + + case 0xE6: /* SET 4,(HL) */ { + uint8_t work8 = m_busInterface.peek8(REG_HL) | 0x10; + m_busInterface.addressOnBus(REG_HL, 1); + m_busInterface.poke8(REG_HL, work8); + break; + } + + case 0xE7: /* SET 4,A */ + regA |= 0x10; + break; + + case 0xE8: /* SET 5,B */ + REG_B |= 0x20; + break; + + case 0xE9: /* SET 5,C */ + REG_C |= 0x20; + break; + + case 0xEA: /* SET 5,D */ + REG_D |= 0x20; + break; + + case 0xEB: /* SET 5,E */ + REG_E |= 0x20; + break; + + case 0xEC: /* SET 5,H */ + REG_H |= 0x20; + break; + + case 0xED: /* SET 5,L */ + REG_L |= 0x20; + break; + + case 0xEE: /* SET 5,(HL) */ { + uint8_t work8 = m_busInterface.peek8(REG_HL) | 0x20; + m_busInterface.addressOnBus(REG_HL, 1); + m_busInterface.poke8(REG_HL, work8); + break; + } + + case 0xEF: /* SET 5,A */ + regA |= 0x20; + break; + + case 0xF0: /* SET 6,B */ + REG_B |= 0x40; + break; + + case 0xF1: /* SET 6,C */ + REG_C |= 0x40; + break; + + case 0xF2: /* SET 6,D */ + REG_D |= 0x40; + break; + + case 0xF3: /* SET 6,E */ + REG_E |= 0x40; + break; + + case 0xF4: /* SET 6,H */ + REG_H |= 0x40; + break; + + case 0xF5: /* SET 6,L */ + REG_L |= 0x40; + break; + + case 0xF6: /* SET 6,(HL) */ { + uint8_t work8 = m_busInterface.peek8(REG_HL) | 0x40; + m_busInterface.addressOnBus(REG_HL, 1); + m_busInterface.poke8(REG_HL, work8); + break; + } + + case 0xF7: /* SET 6,A */ + regA |= 0x40; + break; + + case 0xF8: /* SET 7,B */ + REG_B |= 0x80; + break; + + case 0xF9: /* SET 7,C */ + REG_C |= 0x80; + break; + + case 0xFA: /* SET 7,D */ + REG_D |= 0x80; + break; + + case 0xFB: /* SET 7,E */ + REG_E |= 0x80; + break; + + case 0xFC: /* SET 7,H */ + REG_H |= 0x80; + break; + + case 0xFD: /* SET 7,L */ + REG_L |= 0x80; + break; + + case 0xFE: /* SET 7,(HL) */ { + uint8_t work8 = m_busInterface.peek8(REG_HL) | 0x80; + m_busInterface.addressOnBus(REG_HL, 1); + m_busInterface.poke8(REG_HL, work8); + break; + } + + case 0xFF: /* SET 7,A */ + regA |= 0x80; + break; + + default: + break; + } +} + +// Subconjunto de instrucciones 0xDD / 0xFD +/* + * Hay que tener en cuenta el manejo de secuencias códigos DD/FD que no + * hacen nada. Según el apartado 3.7 del documento + * [http://www.myquest.nl/z80undocumented/z80-documented-v0.91.pdf] + * secuencias de códigos como FD DD 00 21 00 10 NOP NOP NOP LD HL,1000h + * activan IY con el primer FD, IX con el segundo DD y vuelven al + * registro HL con el código NOP. Es decir, si detrás del código DD/FD no + * viene una instrucción que maneje el registro HL, el código DD/FD + * "se olvida" y hay que procesar la instrucción como si nunca se + * hubiera visto el prefijo (salvo por los 4 t-estados que ha costado). + * Naturalmente, en una serie repetida de DDFD no hay que comprobar las + * interrupciones entre cada prefijo. + */ +template void Z80::decodeDDFD(uint8_t opCode, RegisterPair& regIXY) { + switch (opCode) { + case 0x09: /* ADD IX,BC */ + m_busInterface.addressOnBus(getPairIR().word, 7); + add16(regIXY, REG_BC); + break; + + case 0x19: /* ADD IX,DE */ + m_busInterface.addressOnBus(getPairIR().word, 7); + add16(regIXY, REG_DE); + break; + + case 0x21: /* LD IX,nn */ + regIXY.word = m_busInterface.peek16(REG_PC); + REG_PC = REG_PC + 2; + break; + + case 0x22: /* LD (nn),IX */ + REG_WZ = m_busInterface.peek16(REG_PC); + m_busInterface.poke16(REG_WZ++, regIXY); + REG_PC = REG_PC + 2; + break; + + case 0x23: /* INC IX */ + m_busInterface.addressOnBus(getPairIR().word, 2); + regIXY.word++; + break; + + case 0x24: /* INC IXh */ + inc8(regIXY.byte8.hi); + break; + + case 0x25: /* DEC IXh */ + dec8(regIXY.byte8.hi); + break; + + case 0x26: /* LD IXh,n */ + regIXY.byte8.hi = m_busInterface.peek8(REG_PC); + REG_PC++; + break; + + case 0x29: /* ADD IX,IX */ + m_busInterface.addressOnBus(getPairIR().word, 7); + add16(regIXY, regIXY.word); + break; + + case 0x2A: /* LD IX,(nn) */ + REG_WZ = m_busInterface.peek16(REG_PC); + regIXY.word = m_busInterface.peek16(REG_WZ++); + REG_PC = REG_PC + 2; + break; + + case 0x2B: /* DEC IX */ + m_busInterface.addressOnBus(getPairIR().word, 2); + regIXY.word--; + break; + + case 0x2C: /* INC IXl */ + inc8(regIXY.byte8.lo); + break; + + case 0x2D: /* DEC IXl */ + dec8(regIXY.byte8.lo); + break; + + case 0x2E: /* LD IXl,n */ + regIXY.byte8.lo = m_busInterface.peek8(REG_PC); + REG_PC++; + break; + + case 0x34: /* INC (IX+d) */ { + REG_WZ = regIXY.word + static_cast(m_busInterface.peek8(REG_PC)); + m_busInterface.addressOnBus(REG_PC, 5); + REG_PC++; + uint8_t work8 = m_busInterface.peek8(REG_WZ); + m_busInterface.addressOnBus(REG_WZ, 1); + inc8(work8); + m_busInterface.poke8(REG_WZ, work8); + break; + } + + case 0x35: /* DEC (IX+d) */ { + REG_WZ = regIXY.word + static_cast(m_busInterface.peek8(REG_PC)); + m_busInterface.addressOnBus(REG_PC, 5); + REG_PC++; + uint8_t work8 = m_busInterface.peek8(REG_WZ); + m_busInterface.addressOnBus(REG_WZ, 1); + dec8(work8); + m_busInterface.poke8(REG_WZ, work8); + break; + } + + case 0x36: /* LD (IX+d),n */ { + REG_WZ = regIXY.word + static_cast(m_busInterface.peek8(REG_PC)); + REG_PC++; + uint8_t work8 = m_busInterface.peek8(REG_PC); + m_busInterface.addressOnBus(REG_PC, 2); + REG_PC++; + m_busInterface.poke8(REG_WZ, work8); + break; + } + + case 0x39: /* ADD IX,SP */ + m_busInterface.addressOnBus(getPairIR().word, 7); + add16(regIXY, REG_SP); + break; + + case 0x44: /* LD B,IXh */ + REG_B = regIXY.byte8.hi; + break; + + case 0x45: /* LD B,IXl */ + REG_B = regIXY.byte8.lo; + break; + + case 0x46: /* LD B,(IX+d) */ + REG_WZ = regIXY.word + static_cast(m_busInterface.peek8(REG_PC)); + m_busInterface.addressOnBus(REG_PC, 5); + REG_PC++; + REG_B = m_busInterface.peek8(REG_WZ); + break; + + case 0x4C: /* LD C,IXh */ + REG_C = regIXY.byte8.hi; + break; + + case 0x4D: /* LD C,IXl */ + REG_C = regIXY.byte8.lo; + break; + + case 0x4E: /* LD C,(IX+d) */ + REG_WZ = regIXY.word + static_cast(m_busInterface.peek8(REG_PC)); + m_busInterface.addressOnBus(REG_PC, 5); + REG_PC++; + REG_C = m_busInterface.peek8(REG_WZ); + break; + + case 0x54: /* LD D,IXh */ + REG_D = regIXY.byte8.hi; + break; + + case 0x55: /* LD D,IXl */ + REG_D = regIXY.byte8.lo; + break; + + case 0x56: /* LD D,(IX+d) */ + REG_WZ = regIXY.word + static_cast(m_busInterface.peek8(REG_PC)); + m_busInterface.addressOnBus(REG_PC, 5); + REG_PC++; + REG_D = m_busInterface.peek8(REG_WZ); + break; + + case 0x5C: /* LD E,IXh */ + REG_E = regIXY.byte8.hi; + break; + + case 0x5D: /* LD E,IXl */ + REG_E = regIXY.byte8.lo; + break; + + case 0x5E: /* LD E,(IX+d) */ + REG_WZ = regIXY.word + static_cast(m_busInterface.peek8(REG_PC)); + m_busInterface.addressOnBus(REG_PC, 5); + REG_PC++; + REG_E = m_busInterface.peek8(REG_WZ); + break; + + case 0x60: /* LD IXh,B */ + regIXY.byte8.hi = REG_B; + break; + + case 0x61: /* LD IXh,C */ + regIXY.byte8.hi = REG_C; + break; + + case 0x62: /* LD IXh,D */ + regIXY.byte8.hi = REG_D; + break; + + case 0x63: /* LD IXh,E */ + regIXY.byte8.hi = REG_E; + break; + + case 0x64: /* LD IXh,IXh */ + break; + + case 0x65: /* LD IXh,IXl */ + regIXY.byte8.hi = regIXY.byte8.lo; + break; + + case 0x66: /* LD H,(IX+d) */ + REG_WZ = regIXY.word + static_cast(m_busInterface.peek8(REG_PC)); + m_busInterface.addressOnBus(REG_PC, 5); + REG_PC++; + REG_H = m_busInterface.peek8(REG_WZ); + break; + + case 0x67: /* LD IXh,A */ + regIXY.byte8.hi = regA; + break; + + case 0x68: /* LD IXl,B */ + regIXY.byte8.lo = REG_B; + break; + + case 0x69: /* LD IXl,C */ + regIXY.byte8.lo = REG_C; + break; + + case 0x6A: /* LD IXl,D */ + regIXY.byte8.lo = REG_D; + break; + + case 0x6B: /* LD IXl,E */ + regIXY.byte8.lo = REG_E; + break; + + case 0x6C: /* LD IXl,IXh */ + regIXY.byte8.lo = regIXY.byte8.hi; + break; + + case 0x6D: /* LD IXl,IXl */ + break; + + case 0x6E: /* LD L,(IX+d) */ + REG_WZ = regIXY.word + static_cast(m_busInterface.peek8(REG_PC)); + m_busInterface.addressOnBus(REG_PC, 5); + REG_PC++; + REG_L = m_busInterface.peek8(REG_WZ); + break; + + case 0x6F: /* LD IXl,A */ + regIXY.byte8.lo = regA; + break; + + case 0x70: /* LD (IX+d),B */ + REG_WZ = regIXY.word + static_cast(m_busInterface.peek8(REG_PC)); + m_busInterface.addressOnBus(REG_PC, 5); + REG_PC++; + m_busInterface.poke8(REG_WZ, REG_B); + break; + + case 0x71: /* LD (IX+d),C */ + REG_WZ = regIXY.word + static_cast(m_busInterface.peek8(REG_PC)); + m_busInterface.addressOnBus(REG_PC, 5); + REG_PC++; + m_busInterface.poke8(REG_WZ, REG_C); + break; + + case 0x72: /* LD (IX+d),D */ + REG_WZ = regIXY.word + static_cast(m_busInterface.peek8(REG_PC)); + m_busInterface.addressOnBus(REG_PC, 5); + REG_PC++; + m_busInterface.poke8(REG_WZ, REG_D); + break; + + case 0x73: /* LD (IX+d),E */ + REG_WZ = regIXY.word + static_cast(m_busInterface.peek8(REG_PC)); + m_busInterface.addressOnBus(REG_PC, 5); + REG_PC++; + m_busInterface.poke8(REG_WZ, REG_E); + break; + + case 0x74: /* LD (IX+d),H */ + REG_WZ = regIXY.word + static_cast(m_busInterface.peek8(REG_PC)); + m_busInterface.addressOnBus(REG_PC, 5); + REG_PC++; + m_busInterface.poke8(REG_WZ, REG_H); + break; + + case 0x75: /* LD (IX+d),L */ + REG_WZ = regIXY.word + static_cast(m_busInterface.peek8(REG_PC)); + m_busInterface.addressOnBus(REG_PC, 5); + REG_PC++; + m_busInterface.poke8(REG_WZ, REG_L); + break; + + case 0x77: /* LD (IX+d),A */ + REG_WZ = regIXY.word + static_cast(m_busInterface.peek8(REG_PC)); + m_busInterface.addressOnBus(REG_PC, 5); + REG_PC++; + m_busInterface.poke8(REG_WZ, regA); + break; + + case 0x7C: /* LD A,IXh */ + regA = regIXY.byte8.hi; + break; + + case 0x7D: /* LD A,IXl */ + regA = regIXY.byte8.lo; + break; + + case 0x7E: /* LD A,(IX+d) */ + REG_WZ = regIXY.word + static_cast(m_busInterface.peek8(REG_PC)); + m_busInterface.addressOnBus(REG_PC, 5); + REG_PC++; + regA = m_busInterface.peek8(REG_WZ); + break; + + case 0x84: /* ADD A,IXh */ + add(regIXY.byte8.hi); + break; + + case 0x85: /* ADD A,IXl */ + add(regIXY.byte8.lo); + break; + + case 0x86: /* ADD A,(IX+d) */ + REG_WZ = regIXY.word + static_cast(m_busInterface.peek8(REG_PC)); + m_busInterface.addressOnBus(REG_PC, 5); + REG_PC++; + add(m_busInterface.peek8(REG_WZ)); + break; + + case 0x8C: /* ADC A,IXh */ + adc(regIXY.byte8.hi); + break; + + case 0x8D: /* ADC A,IXl */ + adc(regIXY.byte8.lo); + break; + + case 0x8E: /* ADC A,(IX+d) */ + REG_WZ = regIXY.word + static_cast(m_busInterface.peek8(REG_PC)); + m_busInterface.addressOnBus(REG_PC, 5); + REG_PC++; + adc(m_busInterface.peek8(REG_WZ)); + break; + + case 0x94: /* SUB IXh */ + sub(regIXY.byte8.hi); + break; + + case 0x95: /* SUB IXl */ + sub(regIXY.byte8.lo); + break; + + case 0x96: /* SUB (IX+d) */ + REG_WZ = regIXY.word + static_cast(m_busInterface.peek8(REG_PC)); + m_busInterface.addressOnBus(REG_PC, 5); + REG_PC++; + sub(m_busInterface.peek8(REG_WZ)); + break; + + case 0x9C: /* SBC A,IXh */ + sbc(regIXY.byte8.hi); + break; + + case 0x9D: /* SBC A,IXl */ + sbc(regIXY.byte8.lo); + break; + + case 0x9E: /* SBC A,(IX+d) */ + REG_WZ = regIXY.word + static_cast(m_busInterface.peek8(REG_PC)); + m_busInterface.addressOnBus(REG_PC, 5); + REG_PC++; + sbc(m_busInterface.peek8(REG_WZ)); + break; + + case 0xA4: /* AND IXh */ + and_(regIXY.byte8.hi); + break; + + case 0xA5: /* AND IXl */ + and_(regIXY.byte8.lo); + break; + + case 0xA6: /* AND (IX+d) */ + REG_WZ = regIXY.word + static_cast(m_busInterface.peek8(REG_PC)); + m_busInterface.addressOnBus(REG_PC, 5); + REG_PC++; + and_(m_busInterface.peek8(REG_WZ)); + break; + + case 0xAC: /* XOR IXh */ + xor_(regIXY.byte8.hi); + break; + + case 0xAD: /* XOR IXl */ + xor_(regIXY.byte8.lo); + break; + + case 0xAE: /* XOR (IX+d) */ + REG_WZ = regIXY.word + static_cast(m_busInterface.peek8(REG_PC)); + m_busInterface.addressOnBus(REG_PC, 5); + REG_PC++; + xor_(m_busInterface.peek8(REG_WZ)); + break; + + case 0xB4: /* OR IXh */ + or_(regIXY.byte8.hi); + break; + + case 0xB5: /* OR IXl */ + or_(regIXY.byte8.lo); + break; + + case 0xB6: /* OR (IX+d) */ + REG_WZ = regIXY.word + static_cast(m_busInterface.peek8(REG_PC)); + m_busInterface.addressOnBus(REG_PC, 5); + REG_PC++; + or_(m_busInterface.peek8(REG_WZ)); + break; + + case 0xBC: /* CP IXh */ + cp(regIXY.byte8.hi); + break; + + case 0xBD: /* CP IXl */ + cp(regIXY.byte8.lo); + break; + + case 0xBE: /* CP (IX+d) */ + REG_WZ = regIXY.word + static_cast(m_busInterface.peek8(REG_PC)); + m_busInterface.addressOnBus(REG_PC, 5); + REG_PC++; + cp(m_busInterface.peek8(REG_WZ)); + break; + + case 0xCB: /* Subconjunto de instrucciones */ + REG_WZ = regIXY.word + static_cast(m_busInterface.peek8(REG_PC)); + REG_PC++; + opCode = m_busInterface.peek8(REG_PC); + m_busInterface.addressOnBus(REG_PC, 2); + REG_PC++; + decodeDDFDCB(opCode, REG_WZ); + break; + + case 0xDD: + prefixOpcode = 0xDD; + break; + case 0xE1: /* POP IX */ + regIXY.word = pop(); + break; + + case 0xE3: /* EX (SP),IX */ { + // Instrucción de ejecución sutil como pocas... atento al dato. + RegisterPair work16 = regIXY; + regIXY.word = m_busInterface.peek16(REG_SP); + m_busInterface.addressOnBus(REG_SP + 1, 1); + /* I can't call poke16 from here because the Z80 CPU does the writes in inverted order. + * Same thing goes for EX (SP), HL. + */ + m_busInterface.poke8(REG_SP + 1, work16.byte8.hi); + m_busInterface.poke8(REG_SP, work16.byte8.lo); + m_busInterface.addressOnBus(REG_SP, 2); + REG_WZ = regIXY.word; + break; + } + + case 0xE5: /* PUSH IX */ + m_busInterface.addressOnBus(getPairIR().word, 1); + push(regIXY.word); + break; + + case 0xE9: /* JP (IX) */ + REG_PC = regIXY.word; + break; + + case 0xED: + prefixOpcode = 0xED; + break; + + case 0xF9: /* LD SP,IX */ + m_busInterface.addressOnBus(getPairIR().word, 2); + REG_SP = regIXY.word; + break; + + case 0xFD: + prefixOpcode = 0xFD; + break; + + default: { + // Detrás de un DD/FD o varios en secuencia venía un código + // que no correspondía con una instrucción que involucra a + // IX o IY. Se trata como si fuera un código normal. + // Sin esto, además de emular mal, falla el test + // ld , de ZEXALL. +#ifdef WITH_BREAKPOINT_SUPPORT + if (breakpointEnabled && prefixOpcode == 0) { + opCode = m_busInterface.breakpoint(REG_PC, opCode); + } +#endif + decodeOpcode(opCode); + break; + } + } +} + +// Subconjunto de instrucciones 0xDDCB +template void Z80::decodeDDFDCB(uint8_t opCode, uint16_t address) { + + switch (opCode) { + default: + case 0x00: /* RLC (IX+d),B */ + case 0x01: /* RLC (IX+d),C */ + case 0x02: /* RLC (IX+d),D */ + case 0x03: /* RLC (IX+d),E */ + case 0x04: /* RLC (IX+d),H */ + case 0x05: /* RLC (IX+d),L */ + case 0x06: /* RLC (IX+d) */ + case 0x07: /* RLC (IX+d),A */ + { + uint8_t work8 = m_busInterface.peek8(address); + rlc(work8); + m_busInterface.addressOnBus(address, 1); + m_busInterface.poke8(address, work8); + copyToRegister(opCode, work8); + break; + } + case 0x08: /* RRC (IX+d),B */ + case 0x09: /* RRC (IX+d),C */ + case 0x0A: /* RRC (IX+d),D */ + case 0x0B: /* RRC (IX+d),E */ + case 0x0C: /* RRC (IX+d),H */ + case 0x0D: /* RRC (IX+d),L */ + case 0x0E: /* RRC (IX+d) */ + case 0x0F: /* RRC (IX+d),A */ + { + uint8_t work8 = m_busInterface.peek8(address); + rrc(work8); + m_busInterface.addressOnBus(address, 1); + m_busInterface.poke8(address, work8); + copyToRegister(opCode, work8); + break; + } + case 0x10: /* RL (IX+d),B */ + case 0x11: /* RL (IX+d),C */ + case 0x12: /* RL (IX+d),D */ + case 0x13: /* RL (IX+d),E */ + case 0x14: /* RL (IX+d),H */ + case 0x15: /* RL (IX+d),L */ + case 0x16: /* RL (IX+d) */ + case 0x17: /* RL (IX+d),A */ + { + uint8_t work8 = m_busInterface.peek8(address); + rl(work8); + m_busInterface.addressOnBus(address, 1); + m_busInterface.poke8(address, work8); + copyToRegister(opCode, work8); + break; + } + case 0x18: /* RR (IX+d),B */ + case 0x19: /* RR (IX+d),C */ + case 0x1A: /* RR (IX+d),D */ + case 0x1B: /* RR (IX+d),E */ + case 0x1C: /* RR (IX+d),H */ + case 0x1D: /* RR (IX+d),L */ + case 0x1E: /* RR (IX+d) */ + case 0x1F: /* RR (IX+d),A */ + { + uint8_t work8 = m_busInterface.peek8(address); + rr(work8); + m_busInterface.addressOnBus(address, 1); + m_busInterface.poke8(address, work8); + copyToRegister(opCode, work8); + break; + } + case 0x20: /* SLA (IX+d),B */ + case 0x21: /* SLA (IX+d),C */ + case 0x22: /* SLA (IX+d),D */ + case 0x23: /* SLA (IX+d),E */ + case 0x24: /* SLA (IX+d),H */ + case 0x25: /* SLA (IX+d),L */ + case 0x26: /* SLA (IX+d) */ + case 0x27: /* SLA (IX+d),A */ + { + uint8_t work8 = m_busInterface.peek8(address); + sla(work8); + m_busInterface.addressOnBus(address, 1); + m_busInterface.poke8(address, work8); + copyToRegister(opCode, work8); + break; + } + case 0x28: /* SRA (IX+d),B */ + case 0x29: /* SRA (IX+d),C */ + case 0x2A: /* SRA (IX+d),D */ + case 0x2B: /* SRA (IX+d),E */ + case 0x2C: /* SRA (IX+d),H */ + case 0x2D: /* SRA (IX+d),L */ + case 0x2E: /* SRA (IX+d) */ + case 0x2F: /* SRA (IX+d),A */ + { + uint8_t work8 = m_busInterface.peek8(address); + sra(work8); + m_busInterface.addressOnBus(address, 1); + m_busInterface.poke8(address, work8); + copyToRegister(opCode, work8); + break; + } + case 0x30: /* SLL (IX+d),B */ + case 0x31: /* SLL (IX+d),C */ + case 0x32: /* SLL (IX+d),D */ + case 0x33: /* SLL (IX+d),E */ + case 0x34: /* SLL (IX+d),H */ + case 0x35: /* SLL (IX+d),L */ + case 0x36: /* SLL (IX+d) */ + case 0x37: /* SLL (IX+d),A */ + { + uint8_t work8 = m_busInterface.peek8(address); + sll(work8); + m_busInterface.addressOnBus(address, 1); + m_busInterface.poke8(address, work8); + copyToRegister(opCode, work8); + break; + } + case 0x38: /* SRL (IX+d),B */ + case 0x39: /* SRL (IX+d),C */ + case 0x3A: /* SRL (IX+d),D */ + case 0x3B: /* SRL (IX+d),E */ + case 0x3C: /* SRL (IX+d),H */ + case 0x3D: /* SRL (IX+d),L */ + case 0x3E: /* SRL (IX+d) */ + case 0x3F: /* SRL (IX+d),A */ + { + uint8_t work8 = m_busInterface.peek8(address); + srl(work8); + m_busInterface.addressOnBus(address, 1); + m_busInterface.poke8(address, work8); + copyToRegister(opCode, work8); + break; + } + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: /* BIT 0,(IX+d) */ + bitTest(0x01, m_busInterface.peek8(address)); + sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZHP_MASK) | ((address >> 8) & FLAG_53_MASK); + m_busInterface.addressOnBus(address, 1); + break; + + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: /* BIT 1,(IX+d) */ + bitTest(0x02, m_busInterface.peek8(address)); + sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZHP_MASK) | ((address >> 8) & FLAG_53_MASK); + m_busInterface.addressOnBus(address, 1); + break; + + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: /* BIT 2,(IX+d) */ + bitTest(0x04, m_busInterface.peek8(address)); + sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZHP_MASK) | ((address >> 8) & FLAG_53_MASK); + m_busInterface.addressOnBus(address, 1); + break; + + case 0x58: + case 0x59: + case 0x5A: + case 0x5B: + case 0x5C: + case 0x5D: + case 0x5E: + case 0x5F: /* BIT 3,(IX+d) */ + bitTest(0x08, m_busInterface.peek8(address)); + sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZHP_MASK) | ((address >> 8) & FLAG_53_MASK); + m_busInterface.addressOnBus(address, 1); + break; + + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: /* BIT 4,(IX+d) */ + bitTest(0x10, m_busInterface.peek8(address)); + sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZHP_MASK) | ((address >> 8) & FLAG_53_MASK); + m_busInterface.addressOnBus(address, 1); + break; + + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: /* BIT 5,(IX+d) */ + bitTest(0x20, m_busInterface.peek8(address)); + sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZHP_MASK) | ((address >> 8) & FLAG_53_MASK); + m_busInterface.addressOnBus(address, 1); + break; + + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: /* BIT 6,(IX+d) */ + bitTest(0x40, m_busInterface.peek8(address)); + sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZHP_MASK) | ((address >> 8) & FLAG_53_MASK); + m_busInterface.addressOnBus(address, 1); + break; + + case 0x78: + case 0x79: + case 0x7A: + case 0x7B: + case 0x7C: + case 0x7D: + case 0x7E: + case 0x7F: /* BIT 7,(IX+d) */ + bitTest(0x80, m_busInterface.peek8(address)); + sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZHP_MASK) | ((address >> 8) & FLAG_53_MASK); + m_busInterface.addressOnBus(address, 1); + break; + + case 0x80: /* RES 0,(IX+d),B */ + case 0x81: /* RES 0,(IX+d),C */ + case 0x82: /* RES 0,(IX+d),D */ + case 0x83: /* RES 0,(IX+d),E */ + case 0x84: /* RES 0,(IX+d),H */ + case 0x85: /* RES 0,(IX+d),L */ + case 0x86: /* RES 0,(IX+d) */ + case 0x87: /* RES 0,(IX+d),A */ + { + uint8_t work8 = m_busInterface.peek8(address) & 0xFE; + m_busInterface.addressOnBus(address, 1); + m_busInterface.poke8(address, work8); + copyToRegister(opCode, work8); + break; + } + case 0x88: /* RES 1,(IX+d),B */ + case 0x89: /* RES 1,(IX+d),C */ + case 0x8A: /* RES 1,(IX+d),D */ + case 0x8B: /* RES 1,(IX+d),E */ + case 0x8C: /* RES 1,(IX+d),H */ + case 0x8D: /* RES 1,(IX+d),L */ + case 0x8E: /* RES 1,(IX+d) */ + case 0x8F: /* RES 1,(IX+d),A */ + { + uint8_t work8 = m_busInterface.peek8(address) & 0xFD; + m_busInterface.addressOnBus(address, 1); + m_busInterface.poke8(address, work8); + copyToRegister(opCode, work8); + break; + } + case 0x90: /* RES 2,(IX+d),B */ + case 0x91: /* RES 2,(IX+d),C */ + case 0x92: /* RES 2,(IX+d),D */ + case 0x93: /* RES 2,(IX+d),E */ + case 0x94: /* RES 2,(IX+d),H */ + case 0x95: /* RES 2,(IX+d),L */ + case 0x96: /* RES 2,(IX+d) */ + case 0x97: /* RES 2,(IX+d),A */ + { + uint8_t work8 = m_busInterface.peek8(address) & 0xFB; + m_busInterface.addressOnBus(address, 1); + m_busInterface.poke8(address, work8); + copyToRegister(opCode, work8); + break; + } + case 0x98: /* RES 3,(IX+d),B */ + case 0x99: /* RES 3,(IX+d),C */ + case 0x9A: /* RES 3,(IX+d),D */ + case 0x9B: /* RES 3,(IX+d),E */ + case 0x9C: /* RES 3,(IX+d),H */ + case 0x9D: /* RES 3,(IX+d),L */ + case 0x9E: /* RES 3,(IX+d) */ + case 0x9F: /* RES 3,(IX+d),A */ + { + uint8_t work8 = m_busInterface.peek8(address) & 0xF7; + m_busInterface.addressOnBus(address, 1); + m_busInterface.poke8(address, work8); + copyToRegister(opCode, work8); + break; + } + case 0xA0: /* RES 4,(IX+d),B */ + case 0xA1: /* RES 4,(IX+d),C */ + case 0xA2: /* RES 4,(IX+d),D */ + case 0xA3: /* RES 4,(IX+d),E */ + case 0xA4: /* RES 4,(IX+d),H */ + case 0xA5: /* RES 4,(IX+d),L */ + case 0xA6: /* RES 4,(IX+d) */ + case 0xA7: /* RES 4,(IX+d),A */ + { + uint8_t work8 = m_busInterface.peek8(address) & 0xEF; + m_busInterface.addressOnBus(address, 1); + m_busInterface.poke8(address, work8); + copyToRegister(opCode, work8); + break; + } + case 0xA8: /* RES 5,(IX+d),B */ + case 0xA9: /* RES 5,(IX+d),C */ + case 0xAA: /* RES 5,(IX+d),D */ + case 0xAB: /* RES 5,(IX+d),E */ + case 0xAC: /* RES 5,(IX+d),H */ + case 0xAD: /* RES 5,(IX+d),L */ + case 0xAE: /* RES 5,(IX+d) */ + case 0xAF: /* RES 5,(IX+d),A */ + { + uint8_t work8 = m_busInterface.peek8(address) & 0xDF; + m_busInterface.addressOnBus(address, 1); + m_busInterface.poke8(address, work8); + copyToRegister(opCode, work8); + break; + } + case 0xB0: /* RES 6,(IX+d),B */ + case 0xB1: /* RES 6,(IX+d),C */ + case 0xB2: /* RES 6,(IX+d),D */ + case 0xB3: /* RES 6,(IX+d),E */ + case 0xB4: /* RES 6,(IX+d),H */ + case 0xB5: /* RES 6,(IX+d),L */ + case 0xB6: /* RES 6,(IX+d) */ + case 0xB7: /* RES 6,(IX+d),A */ + { + uint8_t work8 = m_busInterface.peek8(address) & 0xBF; + m_busInterface.addressOnBus(address, 1); + m_busInterface.poke8(address, work8); + copyToRegister(opCode, work8); + break; + } + case 0xB8: /* RES 7,(IX+d),B */ + case 0xB9: /* RES 7,(IX+d),C */ + case 0xBA: /* RES 7,(IX+d),D */ + case 0xBB: /* RES 7,(IX+d),E */ + case 0xBC: /* RES 7,(IX+d),H */ + case 0xBD: /* RES 7,(IX+d),L */ + case 0xBE: /* RES 7,(IX+d) */ + case 0xBF: /* RES 7,(IX+d),A */ + { + uint8_t work8 = m_busInterface.peek8(address) & 0x7F; + m_busInterface.addressOnBus(address, 1); + m_busInterface.poke8(address, work8); + copyToRegister(opCode, work8); + break; + } + case 0xC0: /* SET 0,(IX+d),B */ + case 0xC1: /* SET 0,(IX+d),C */ + case 0xC2: /* SET 0,(IX+d),D */ + case 0xC3: /* SET 0,(IX+d),E */ + case 0xC4: /* SET 0,(IX+d),H */ + case 0xC5: /* SET 0,(IX+d),L */ + case 0xC6: /* SET 0,(IX+d) */ + case 0xC7: /* SET 0,(IX+d),A */ + { + uint8_t work8 = m_busInterface.peek8(address) | 0x01; + m_busInterface.addressOnBus(address, 1); + m_busInterface.poke8(address, work8); + copyToRegister(opCode, work8); + break; + } + case 0xC8: /* SET 1,(IX+d),B */ + case 0xC9: /* SET 1,(IX+d),C */ + case 0xCA: /* SET 1,(IX+d),D */ + case 0xCB: /* SET 1,(IX+d),E */ + case 0xCC: /* SET 1,(IX+d),H */ + case 0xCD: /* SET 1,(IX+d),L */ + case 0xCE: /* SET 1,(IX+d) */ + case 0xCF: /* SET 1,(IX+d),A */ + { + uint8_t work8 = m_busInterface.peek8(address) | 0x02; + m_busInterface.addressOnBus(address, 1); + m_busInterface.poke8(address, work8); + copyToRegister(opCode, work8); + break; + } + case 0xD0: /* SET 2,(IX+d),B */ + case 0xD1: /* SET 2,(IX+d),C */ + case 0xD2: /* SET 2,(IX+d),D */ + case 0xD3: /* SET 2,(IX+d),E */ + case 0xD4: /* SET 2,(IX+d),H */ + case 0xD5: /* SET 2,(IX+d),L */ + case 0xD6: /* SET 2,(IX+d) */ + case 0xD7: /* SET 2,(IX+d),A */ + { + uint8_t work8 = m_busInterface.peek8(address) | 0x04; + m_busInterface.addressOnBus(address, 1); + m_busInterface.poke8(address, work8); + copyToRegister(opCode, work8); + break; + } + case 0xD8: /* SET 3,(IX+d),B */ + case 0xD9: /* SET 3,(IX+d),C */ + case 0xDA: /* SET 3,(IX+d),D */ + case 0xDB: /* SET 3,(IX+d),E */ + case 0xDC: /* SET 3,(IX+d),H */ + case 0xDD: /* SET 3,(IX+d),L */ + case 0xDE: /* SET 3,(IX+d) */ + case 0xDF: /* SET 3,(IX+d),A */ + { + uint8_t work8 = m_busInterface.peek8(address) | 0x08; + m_busInterface.addressOnBus(address, 1); + m_busInterface.poke8(address, work8); + copyToRegister(opCode, work8); + break; + } + case 0xE0: /* SET 4,(IX+d),B */ + case 0xE1: /* SET 4,(IX+d),C */ + case 0xE2: /* SET 4,(IX+d),D */ + case 0xE3: /* SET 4,(IX+d),E */ + case 0xE4: /* SET 4,(IX+d),H */ + case 0xE5: /* SET 4,(IX+d),L */ + case 0xE6: /* SET 4,(IX+d) */ + case 0xE7: /* SET 4,(IX+d),A */ + { + uint8_t work8 = m_busInterface.peek8(address) | 0x10; + m_busInterface.addressOnBus(address, 1); + m_busInterface.poke8(address, work8); + copyToRegister(opCode, work8); + break; + } + case 0xE8: /* SET 5,(IX+d),B */ + case 0xE9: /* SET 5,(IX+d),C */ + case 0xEA: /* SET 5,(IX+d),D */ + case 0xEB: /* SET 5,(IX+d),E */ + case 0xEC: /* SET 5,(IX+d),H */ + case 0xED: /* SET 5,(IX+d),L */ + case 0xEE: /* SET 5,(IX+d) */ + case 0xEF: /* SET 5,(IX+d),A */ + { + uint8_t work8 = m_busInterface.peek8(address) | 0x20; + m_busInterface.addressOnBus(address, 1); + m_busInterface.poke8(address, work8); + copyToRegister(opCode, work8); + break; + } + case 0xF0: /* SET 6,(IX+d),B */ + case 0xF1: /* SET 6,(IX+d),C */ + case 0xF2: /* SET 6,(IX+d),D */ + case 0xF3: /* SET 6,(IX+d),E */ + case 0xF4: /* SET 6,(IX+d),H */ + case 0xF5: /* SET 6,(IX+d),L */ + case 0xF6: /* SET 6,(IX+d) */ + case 0xF7: /* SET 6,(IX+d),A */ + { + uint8_t work8 = m_busInterface.peek8(address) | 0x40; + m_busInterface.addressOnBus(address, 1); + m_busInterface.poke8(address, work8); + copyToRegister(opCode, work8); + break; + } + case 0xF8: /* SET 7,(IX+d),B */ + case 0xF9: /* SET 7,(IX+d),C */ + case 0xFA: /* SET 7,(IX+d),D */ + case 0xFB: /* SET 7,(IX+d),E */ + case 0xFC: /* SET 7,(IX+d),H */ + case 0xFD: /* SET 7,(IX+d),L */ + case 0xFE: /* SET 7,(IX+d) */ + case 0xFF: /* SET 7,(IX+d),A */ + { + uint8_t work8 = m_busInterface.peek8(address) | 0x80; + m_busInterface.addressOnBus(address, 1); + m_busInterface.poke8(address, work8); + copyToRegister(opCode, work8); + break; + } + } +} + +// Subconjunto de instrucciones 0xED + +template void Z80::decodeED(uint8_t opCode) { + switch (opCode) { + case 0x40: /* IN B,(C) */ + REG_WZ = REG_BC; + REG_B = m_busInterface.inPort(REG_WZ); + REG_WZ++; + sz5h3pnFlags = sz53pn_addTable[REG_B]; + break; + + case 0x41: /* OUT (C),B */ + REG_WZ = REG_BC; + m_busInterface.outPort(REG_WZ, REG_B); + REG_WZ++; + break; + + case 0x42: /* SBC HL,BC */ + m_busInterface.addressOnBus(getPairIR().word, 7); + sbc16(REG_BC); + break; + + case 0x43: /* LD (nn),BC */ + REG_WZ = m_busInterface.peek16(REG_PC); + m_busInterface.poke16(REG_WZ, regBC); + REG_WZ++; + REG_PC = REG_PC + 2; + break; + + case 0x44: + case 0x4C: + case 0x54: + case 0x5C: + case 0x64: + case 0x6C: + case 0x74: + case 0x7C: /* NEG */ { + uint8_t aux = regA; + regA = 0; + carryFlag = false; + sbc(aux); + break; + } + + case 0x45: + case 0x55: + case 0x5D: + case 0x65: + case 0x6D: + case 0x75: + case 0x7D: /* RETN */ + ffIFF1 = ffIFF2; + REG_PC = REG_WZ = pop(); + break; + + case 0x4D: /* RETI */ + /* According to the Z80 documentation, RETI should not update IFF1 from + * IFF2; only RETN does this (to restore interrupts after NMI). This affects + * precise interrupt handling behavior. + */ + REG_PC = REG_WZ = pop(); + break; + + case 0x46: + case 0x4E: + case 0x66: + case 0x6E: /* IM 0 */ + modeINT = IntMode::IM0; + break; + + case 0x47: /* LD I,A */ + /* + * El par IR se pone en el bus de direcciones *antes* + * de poner A en el registro I. Detalle importante. + */ + m_busInterface.addressOnBus(getPairIR().word, 1); + regI = regA; + break; + + case 0x48: /* IN C,(C) */ + REG_WZ = REG_BC; + REG_C = m_busInterface.inPort(REG_WZ); + REG_WZ++; + sz5h3pnFlags = sz53pn_addTable[REG_C]; + break; + + case 0x49: /* OUT (C),C */ + REG_WZ = REG_BC; + m_busInterface.outPort(REG_WZ, REG_C); + REG_WZ++; + break; + + case 0x4A: /* ADC HL,BC */ + m_busInterface.addressOnBus(getPairIR().word, 7); + adc16(REG_BC); + break; + + case 0x4B: /* LD BC,(nn) */ + REG_WZ = m_busInterface.peek16(REG_PC); + REG_BC = m_busInterface.peek16(REG_WZ); + REG_WZ++; + REG_PC = REG_PC + 2; + break; + + case 0x4F: /* LD R,A */ + /* + * El par IR se pone en el bus de direcciones *antes* + * de poner A en el registro R. Detalle importante. + */ + m_busInterface.addressOnBus(getPairIR().word, 1); + setRegR(regA); + break; + + case 0x50: /* IN D,(C) */ + REG_WZ = REG_BC; + REG_D = m_busInterface.inPort(REG_WZ); + REG_WZ++; + sz5h3pnFlags = sz53pn_addTable[REG_D]; + break; + + case 0x51: /* OUT (C),D */ + REG_WZ = REG_BC; + m_busInterface.outPort(REG_WZ++, REG_D); + break; + + case 0x52: /* SBC HL,DE */ + m_busInterface.addressOnBus(getPairIR().word, 7); + sbc16(REG_DE); + break; + + case 0x53: /* LD (nn),DE */ + REG_WZ = m_busInterface.peek16(REG_PC); + m_busInterface.poke16(REG_WZ++, regDE); + REG_PC = REG_PC + 2; + break; + + case 0x56: + case 0x76: /* IM 1 */ + modeINT = IntMode::IM1; + break; + + case 0x57: { /* LD A,I */ + m_busInterface.addressOnBus(getPairIR().word, 1); + regA = regI; + sz5h3pnFlags = sz53n_addTable[regA]; + /* + * The P / V flag should reflect IFF2 state regardless of whether an + * interrupt is currently being signaled on the bus. + */ + if (ffIFF2) { + sz5h3pnFlags |= PARITY_MASK; + } + break; + } + case 0x58: /* IN E,(C) */ + REG_WZ = REG_BC; + REG_E = m_busInterface.inPort(REG_WZ++); + sz5h3pnFlags = sz53pn_addTable[REG_E]; + break; + + case 0x59: /* OUT (C),E */ + REG_WZ = REG_BC; + m_busInterface.outPort(REG_WZ++, REG_E); + break; + + case 0x5A: /* ADC HL,DE */ + m_busInterface.addressOnBus(getPairIR().word, 7); + adc16(REG_DE); + break; + + case 0x5B: /* LD DE,(nn) */ + REG_WZ = m_busInterface.peek16(REG_PC); + REG_DE = m_busInterface.peek16(REG_WZ++); + REG_PC = REG_PC + 2; + break; + + case 0x5E: + case 0x7E: /* IM 2 */ + modeINT = IntMode::IM2; + break; + + case 0x5F: { /* LD A,R */ + m_busInterface.addressOnBus(getPairIR().word, 1); + regA = getRegR(); + sz5h3pnFlags = sz53n_addTable[regA]; + /* + * The P / V flag should reflect IFF2 state regardless of whether an + * interrupt is currently being signaled on the bus. + */ + if (ffIFF2) { + sz5h3pnFlags |= PARITY_MASK; + } + break; + } + case 0x60: /* IN H,(C) */ + REG_WZ = REG_BC; + REG_H = m_busInterface.inPort(REG_WZ++); + sz5h3pnFlags = sz53pn_addTable[REG_H]; + break; + + case 0x61: /* OUT (C),H */ + REG_WZ = REG_BC; + m_busInterface.outPort(REG_WZ++, REG_H); + break; + + case 0x62: /* SBC HL,HL */ + m_busInterface.addressOnBus(getPairIR().word, 7); + sbc16(REG_HL); + break; + + case 0x63: /* LD (nn),HL */ + REG_WZ = m_busInterface.peek16(REG_PC); + m_busInterface.poke16(REG_WZ++, regHL); + REG_PC = REG_PC + 2; + break; + + case 0x67: /* RRD */ { + // A = A7 A6 A5 A4 (HL)3 (HL)2 (HL)1 (HL)0 + // (HL) = A3 A2 A1 A0 (HL)7 (HL)6 (HL)5 (HL)4 + // Los bits 3,2,1 y 0 de (HL) se copian a los bits 3,2,1 y 0 de A. + // Los 4 bits bajos que había en A se copian a los bits 7,6,5 y 4 de (HL). + // Los 4 bits altos que había en (HL) se copian a los 4 bits bajos de (HL) + // Los 4 bits superiores de A no se tocan. ¡p'habernos matao! + uint8_t aux = regA << 4; + REG_WZ = REG_HL; + uint16_t memHL = m_busInterface.peek8(REG_WZ); + regA = (regA & 0xf0) | (memHL & 0x0f); + m_busInterface.addressOnBus(REG_WZ, 4); + m_busInterface.poke8(REG_WZ++, (memHL >> 4) | aux); + sz5h3pnFlags = sz53pn_addTable[regA]; + break; + } + + case 0x68: /* IN L,(C) */ + REG_WZ = REG_BC; + REG_L = m_busInterface.inPort(REG_WZ++); + sz5h3pnFlags = sz53pn_addTable[REG_L]; + break; + + case 0x69: /* OUT (C),L */ + REG_WZ = REG_BC; + m_busInterface.outPort(REG_WZ++, REG_L); + break; + + case 0x6A: /* ADC HL,HL */ + m_busInterface.addressOnBus(getPairIR().word, 7); + adc16(REG_HL); + break; + + case 0x6B: /* LD HL,(nn) */ + REG_WZ = m_busInterface.peek16(REG_PC); + REG_HL = m_busInterface.peek16(REG_WZ++); + REG_PC = REG_PC + 2; + break; + + case 0x6F: /* RLD */ { + // A = A7 A6 A5 A4 (HL)7 (HL)6 (HL)5 (HL)4 + // (HL) = (HL)3 (HL)2 (HL)1 (HL)0 A3 A2 A1 A0 + // Los 4 bits bajos que había en (HL) se copian a los bits altos de (HL). + // Los 4 bits altos que había en (HL) se copian a los 4 bits bajos de A + // Los bits 3,2,1 y 0 de A se copian a los bits 3,2,1 y 0 de (HL). + // Los 4 bits superiores de A no se tocan. ¡p'habernos matao! + uint8_t aux = regA & 0x0f; + REG_WZ = REG_HL; + uint16_t memHL = m_busInterface.peek8(REG_WZ); + regA = (regA & 0xf0) | (memHL >> 4); + m_busInterface.addressOnBus(REG_WZ, 4); + m_busInterface.poke8(REG_WZ++, (memHL << 4) | aux); + sz5h3pnFlags = sz53pn_addTable[regA]; + break; + } + + case 0x70: /* IN (C) */ { + REG_WZ = REG_BC; + uint8_t inPort = m_busInterface.inPort(REG_WZ++); + sz5h3pnFlags = sz53pn_addTable[inPort]; + break; + } + + case 0x71: /* OUT (C),0 */ + REG_WZ = REG_BC; + m_busInterface.outPort(REG_WZ++, 0x00); + break; + + case 0x72: /* SBC HL,SP */ + m_busInterface.addressOnBus(getPairIR().word, 7); + sbc16(REG_SP); + break; + + case 0x73: /* LD (nn),SP */ + REG_WZ = m_busInterface.peek16(REG_PC); + m_busInterface.poke16(REG_WZ++, regSP); + REG_PC = REG_PC + 2; + break; + + case 0x78: /* IN A,(C) */ + REG_WZ = REG_BC; + regA = m_busInterface.inPort(REG_WZ++); + sz5h3pnFlags = sz53pn_addTable[regA]; + break; + + case 0x79: /* OUT (C),A */ + REG_WZ = REG_BC; + m_busInterface.outPort(REG_WZ++, regA); + break; + + case 0x7A: /* ADC HL,SP */ + m_busInterface.addressOnBus(getPairIR().word, 7); + adc16(REG_SP); + break; + + case 0x7B: /* LD SP,(nn) */ + REG_WZ = m_busInterface.peek16(REG_PC); + REG_SP = m_busInterface.peek16(REG_WZ++); + REG_PC = REG_PC + 2; + break; + + case 0xA0: /* LDI */ + ldi(); + break; + + case 0xA1: /* CPI */ + cpi(); + break; + + case 0xA2: /* INI */ + ini(); + break; + + case 0xA3: /* OUTI */ + outi(); + break; + + case 0xA8: /* LDD */ + ldd(); + break; + + case 0xA9: /* CPD */ + cpd(); + break; + + case 0xAA: /* IND */ + ind(); + break; + + case 0xAB: /* OUTD */ + outd(); + break; + + case 0xB0: { /* LDIR */ + ldi(); + if (REG_BC != 0) { + REG_PC = REG_PC - 2; + REG_WZ = REG_PC + 1; + m_busInterface.addressOnBus(REG_DE - 1, 5); + sz5h3pnFlags &= ~FLAG_53_MASK; + sz5h3pnFlags |= (REG_PCh & FLAG_53_MASK); + } + break; + } + case 0xB1: { /* CPIR */ + cpi(); + if ((sz5h3pnFlags & PARITY_MASK) == PARITY_MASK && (sz5h3pnFlags & ZERO_MASK) == 0) { + REG_PC = REG_PC - 2; + REG_WZ = REG_PC + 1; + m_busInterface.addressOnBus(REG_HL - 1, 5); + sz5h3pnFlags &= ~FLAG_53_MASK; + sz5h3pnFlags |= (REG_PCh & FLAG_53_MASK); + } + break; + } + case 0xB2: { /* INIR */ + ini(); + if (REG_B != 0) { + REG_PC = REG_PC - 2; + REG_WZ = REG_PC + 1; + m_busInterface.addressOnBus(REG_HL - 1, 5); + adjustINxROUTxRFlags(); + } + break; + } + case 0xB3: { /* OTIR */ + outi(); + if (REG_B != 0) { + REG_PC = REG_PC - 2; + REG_WZ = REG_PC + 1; + m_busInterface.addressOnBus(REG_BC, 5); + adjustINxROUTxRFlags(); + } + break; + } + case 0xB8: { /* LDDR */ + ldd(); + if (REG_BC != 0) { + REG_PC = REG_PC - 2; + REG_WZ = REG_PC + 1; + m_busInterface.addressOnBus(REG_DE + 1, 5); + sz5h3pnFlags &= ~FLAG_53_MASK; + sz5h3pnFlags |= (REG_PCh & FLAG_53_MASK); + } + break; + } + case 0xB9: { /* CPDR */ + cpd(); + if ((sz5h3pnFlags & PARITY_MASK) == PARITY_MASK && (sz5h3pnFlags & ZERO_MASK) == 0) { + REG_PC = REG_PC - 2; + REG_WZ = REG_PC + 1; + m_busInterface.addressOnBus(REG_HL + 1, 5); + sz5h3pnFlags &= ~FLAG_53_MASK; + sz5h3pnFlags |= (REG_PCh & FLAG_53_MASK); + } + break; + } + case 0xBA: { /* INDR */ + ind(); + if (REG_B != 0) { + REG_PC = REG_PC - 2; + REG_WZ = REG_PC + 1; + m_busInterface.addressOnBus(REG_HL + 1, 5); + adjustINxROUTxRFlags(); + } + break; + } + case 0xBB: { /* OTDR */ + outd(); + if (REG_B != 0) { + REG_PC = REG_PC - 2; + REG_WZ = REG_PC + 1; + m_busInterface.addressOnBus(REG_BC, 5); + adjustINxROUTxRFlags(); + } + break; + } + case 0xDD: + prefixOpcode = 0xDD; + break; + case 0xED: + prefixOpcode = 0xED; + break; + case 0xFD: + prefixOpcode = 0xFD; + break; + default: + break; + } +} + +template void Z80::copyToRegister(uint8_t opCode, uint8_t value) { + switch (opCode & 0x07) { + case 0x00: + REG_B = value; + break; + case 0x01: + REG_C = value; + break; + case 0x02: + REG_D = value; + break; + case 0x03: + REG_E = value; + break; + case 0x04: + REG_H = value; + break; + case 0x05: + REG_L = value; + break; + case 0x07: + regA = value; + default: + break; + } +} + +template void Z80::adjustINxROUTxRFlags() { + sz5h3pnFlags &= ~FLAG_53_MASK; + sz5h3pnFlags |= (REG_PCh & FLAG_53_MASK); + + uint8_t pf = sz5h3pnFlags & PARITY_MASK; + if (carryFlag) { + int8_t addsub = 1 - (sz5h3pnFlags & ADDSUB_MASK); + pf ^= sz53pn_addTable[(REG_B + addsub) & 0x07] ^ PARITY_MASK; + if ((REG_B & 0x0F) == (addsub != 1 ? 0x00 : 0x0F)) { + sz5h3pnFlags |= HALFCARRY_MASK; + } else { + sz5h3pnFlags &= ~HALFCARRY_MASK; + } + } else { + pf ^= sz53pn_addTable[REG_B & 0x07] ^ PARITY_MASK; + sz5h3pnFlags &= ~HALFCARRY_MASK; + } + + if ((pf & PARITY_MASK) != 0) { + sz5h3pnFlags |= PARITY_MASK; + } else { + sz5h3pnFlags &= ~PARITY_MASK; + } +} + +#endif // Z80_HPP diff --git a/include/z80_bus_interface.h b/include/z80_bus_interface.h new file mode 100644 index 0000000..8f9d7e8 --- /dev/null +++ b/include/z80_bus_interface.h @@ -0,0 +1,98 @@ +#ifndef Z80_BUS_INTERFACE_HPP +#define Z80_BUS_INTERFACE_HPP + +#include + +#include "z80_types.h" + +// CRTP bus interface: TBusInterface must implement the *Impl methods. +template class Z80BusInterface { + public: + private: + Z80BusInterface() = default; + + public: + ~Z80BusInterface() = default; + + private: + Z80BusInterface(const Z80BusInterface&) = default; + + public: + Z80BusInterface& operator=(const Z80BusInterface&) = default; + + private: + Z80BusInterface(Z80BusInterface&&) = default; + + public: + Z80BusInterface& operator=(Z80BusInterface&&) = default; + + /* Read opcode from RAM */ + Z80_FORCE_INLINE uint8_t fetchOpcode(uint16_t address) { + return derived().fetchOpcodeImpl(address); + } + + /* Read/Write byte from/to RAM */ + Z80_FORCE_INLINE uint8_t peek8(uint16_t address) { + return derived().peek8Impl(address); + } + + Z80_FORCE_INLINE void poke8(uint16_t address, uint8_t value) { + derived().poke8Impl(address, value); + } + + /* Read/Write word from/to RAM */ + Z80_FORCE_INLINE uint16_t peek16(uint16_t address) { + return derived().peek16Impl(address); + } + + Z80_FORCE_INLINE void poke16(uint16_t address, RegisterPair word) { + derived().poke16Impl(address, word); + } + + /* In/Out byte from/to IO Bus */ + Z80_FORCE_INLINE uint8_t inPort(uint16_t port) { + return derived().inPortImpl(port); + } + + Z80_FORCE_INLINE void outPort(uint16_t port, uint8_t value) { + derived().outPortImpl(port, value); + } + + /* Put an address on bus lasting 'tstates' cycles */ + Z80_FORCE_INLINE void addressOnBus(uint16_t address, int32_t wstates) { + derived().addressOnBusImpl(address, wstates); + } + + /* Clocks needed for processing INT and NMI */ + Z80_FORCE_INLINE void interruptHandlingTime(int32_t wstates) { + derived().interruptHandlingTimeImpl(wstates); + } + + /* Callback to know when the INT signal is active */ + Z80_FORCE_INLINE bool isActiveINT() { + return derived().isActiveINTImpl(); + } + +#ifdef WITH_BREAKPOINT_SUPPORT + /* Callback for notify at PC address */ + uint8_t breakpoint(uint16_t address, uint8_t opcode) { + return derived().breakpointImpl(address, opcode); + } +#endif + +#ifdef WITH_EXEC_DONE + /* Callback to notify that one instruction has ended */ + void execDone(void) { + derived().execDoneImpl(); + } +#endif + + private: + TBusInterface& derived() { + return static_cast(*this); + } + + friend TBusInterface; +}; + +#endif // Z80_BUS_INTERFACE_HPP diff --git a/include/z80_types.h b/include/z80_types.h new file mode 100644 index 0000000..6686b50 --- /dev/null +++ b/include/z80_types.h @@ -0,0 +1,50 @@ +#ifndef Z80_TYPES_HPP +#define Z80_TYPES_HPP + +#include + +#ifndef Z80_FORCE_INLINE +# ifdef _MSC_VER +# define Z80_FORCE_INLINE __forceinline +# elif defined(__GNUC__) || defined(__clang__) +# define Z80_FORCE_INLINE __attribute__((always_inline)) inline +# else +# define Z80_FORCE_INLINE inline +# endif +#endif + +#ifndef Z80_LIKELY +# if defined(__GNUC__) || defined(__clang__) +# define Z80_LIKELY(x) __builtin_expect(!!(x), 1) // NOLINT(cppcoreguidelines-macro-usage) +# define Z80_UNLIKELY(x) __builtin_expect(!!(x), 0) // NOLINT(cppcoreguidelines-macro-usage) +# else +# define Z80_LIKELY(x) (x) +# define Z80_UNLIKELY(x) (x) +# endif +#endif + +#ifndef Z80_RESTRICT +# ifdef _MSC_VER +# define Z80_RESTRICT __restrict +# elif defined(__GNUC__) || defined(__clang__) +# define Z80_RESTRICT __restrict__ +# else +# define Z80_RESTRICT +# endif +#endif + +/* Union allowing a register pair to be accessed as bytes or as a word */ +using RegisterPair = union { +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + struct { + uint8_t hi, lo; + } byte8; +#else + struct { + uint8_t lo, hi; + } byte8; +#endif + uint16_t word; +}; + +#endif // Z80_TYPES_HPP diff --git a/include/z80operations.h b/include/z80operations.h deleted file mode 100644 index e229c94..0000000 --- a/include/z80operations.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef Z80OPERATIONS_H -#define Z80OPERATIONS_H - -#include - -class Z80operations { -public: - Z80operations() = default; - - virtual ~Z80operations() = default; - - /* Read opcode from RAM */ - virtual uint8_t fetchOpcode(uint16_t address) = 0; - - /* Read/Write byte from/to RAM */ - virtual uint8_t peek8(uint16_t address) = 0; - virtual void poke8(uint16_t address, uint8_t value) = 0; - - /* Read/Write word from/to RAM */ - virtual uint16_t peek16(uint16_t address) = 0; - virtual void poke16(uint16_t address, RegisterPair word) = 0; - - /* In/Out byte from/to IO Bus */ - virtual uint8_t inPort(uint16_t port) = 0; - virtual void outPort(uint16_t port, uint8_t value) = 0; - - /* Put an address on bus lasting 'tstates' cycles */ - virtual void addressOnBus(uint16_t address, int32_t wstates) = 0; - - /* Clocks needed for processing INT and NMI */ - virtual void interruptHandlingTime(int32_t wstates) = 0; - - /* Callback to know when the INT signal is active */ - virtual bool isActiveINT() = 0; - -#ifdef WITH_BREAKPOINT_SUPPORT - /* Callback for notify at PC address */ - virtual uint8_t breakpoint(uint16_t address, uint8_t opcode) = 0; -#endif - -#ifdef WITH_EXEC_DONE - /* Callback to notify that one instruction has ended */ - virtual void execDone(void) = 0; -#endif -}; - -#endif // Z80OPERATIONS_H diff --git a/src/z80.cpp b/src/z80.cpp deleted file mode 100644 index e9ebad8..0000000 --- a/src/z80.cpp +++ /dev/null @@ -1,5462 +0,0 @@ -// Converted to C++ from Java at -//... https://github.com/jsanchezv/Z80Core -//... commit c4f267e3564fa89bd88fd2d1d322f4d6b0069dbd -//... GPL 3 -//... v1.0.0 (13/02/2017) -// quick & dirty conversion by dddddd (AKA deesix) - -#include "z80.h" - -// Constructor de la clase -Z80::Z80(Z80operations *ops) { - for (uint32_t idx = 0; idx < 256; idx++) { - - if (idx > 0x7f) { - sz53n_addTable[idx] |= SIGN_MASK; - } - - bool evenBits = true; - for (uint8_t mask = 0x01; mask != 0; mask <<= 1) { - if ((idx & mask) != 0) { - evenBits = !evenBits; - } - } - - sz53n_addTable[idx] |= (idx & FLAG_53_MASK); - sz53n_subTable[idx] = sz53n_addTable[idx] | ADDSUB_MASK; - - if (evenBits) { - sz53pn_addTable[idx] = sz53n_addTable[idx] | PARITY_MASK; - sz53pn_subTable[idx] = sz53n_subTable[idx] | PARITY_MASK; - } else { - sz53pn_addTable[idx] = sz53n_addTable[idx]; - sz53pn_subTable[idx] = sz53n_subTable[idx]; - } - } - - sz53n_addTable[0] |= ZERO_MASK; - sz53pn_addTable[0] |= ZERO_MASK; - sz53n_subTable[0] |= ZERO_MASK; - sz53pn_subTable[0] |= ZERO_MASK; - - Z80opsImpl = ops; - execDone = false; - reset(); -} - -Z80::~Z80() = default; - -RegisterPair Z80::getPairIR() const { - RegisterPair IR; - IR.byte8.hi = regI; - IR.byte8.lo = regR & 0x7f; - if (regRbit7) { - IR.byte8.lo |= SIGN_MASK; - } - return IR; -} - -void Z80::setAddSubFlag(bool state) { - // Branchless flag update to reduce mispredictions in hot paths - const uint8_t m = state ? 0xFF : 0x00; - sz5h3pnFlags = static_cast((sz5h3pnFlags & static_cast(~ADDSUB_MASK)) | (m & ADDSUB_MASK)); -} - -void Z80::setParOverFlag(bool state) { - const uint8_t m = state ? 0xFF : 0x00; - sz5h3pnFlags = static_cast((sz5h3pnFlags & static_cast(~PARITY_MASK)) | (m & PARITY_MASK)); -} - -void Z80::setBit3Flag(bool state) { - const uint8_t m = state ? 0xFF : 0x00; - sz5h3pnFlags = static_cast((sz5h3pnFlags & static_cast(~BIT3_MASK)) | (m & BIT3_MASK)); -} - -void Z80::setHalfCarryFlag(bool state) { - const uint8_t m = state ? 0xFF : 0x00; - sz5h3pnFlags = static_cast((sz5h3pnFlags & static_cast(~HALFCARRY_MASK)) | (m & HALFCARRY_MASK)); -} - -void Z80::setBit5Flag(bool state) { - const uint8_t m = state ? 0xFF : 0x00; - sz5h3pnFlags = static_cast((sz5h3pnFlags & static_cast(~BIT5_MASK)) | (m & BIT5_MASK)); -} - -void Z80::setZeroFlag(bool state) { - const uint8_t m = state ? 0xFF : 0x00; - sz5h3pnFlags = static_cast((sz5h3pnFlags & static_cast(~ZERO_MASK)) | (m & ZERO_MASK)); -} - -void Z80::setSignFlag(bool state) { - const uint8_t m = state ? 0xFF : 0x00; - sz5h3pnFlags = static_cast((sz5h3pnFlags & static_cast(~SIGN_MASK)) | (m & SIGN_MASK)); -} - -// Reset -/* Según el documento de Sean Young, que se encuentra en - * [http://www.myquest.com/z80undocumented], la mejor manera de emular el - * reset es poniendo PC, IFF1, IFF2, R e IM0 a 0 y todos los demás registros - * a 0xFFFF. - * - * 29/05/2011: cuando la CPU recibe alimentación por primera vez, los - * registros PC e IR se inicializan a cero y el resto a 0xFF. - * Si se produce un reset a través de la patilla correspondiente, - * los registros PC e IR se inicializan a 0 y el resto se preservan. - * En cualquier caso, todo parece depender bastante del modelo - * concreto de Z80, así que se escoge el comportamiento del - * modelo Zilog Z8400APS. Z80A CPU. - * http://www.worldofspectrum.org/forums/showthread.php?t=34574 - */ -void Z80::reset() { - if (pinReset) { - pinReset = false; - } else { - regA = 0xff; - - setFlags(0xfd); // The only one flag reset at cold start is the add/sub flag - - REG_AFx = 0xffff; - REG_BC = REG_BCx = 0xffff; - REG_DE = REG_DEx = 0xffff; - REG_HL = REG_HLx = 0xffff; - - REG_IX = REG_IY = 0xffff; - - REG_SP = 0xffff; - - REG_WZ = 0xffff; - } - - REG_PC = 0; - regI = regR = 0; - regRbit7 = false; - ffIFF1 = false; - ffIFF2 = false; - pendingEI = false; - activeNMI = false; - halted = false; - setIM(IntMode::IM0); - lastFlagQ = false; - prefixOpcode = 0x00; -} - -// Rota a la izquierda el valor del argumento -// El bit 0 y el flag C toman el valor del bit 7 antes de la operación -void Z80::rlc(uint8_t &oper8) { - carryFlag = (oper8 > 0x7f); - oper8 <<= 1; - if (carryFlag) { - oper8 |= CARRY_MASK; - } - sz5h3pnFlags = sz53pn_addTable[oper8]; - flagQ = true; -} -// Rota a la izquierda el valor del argumento -// El bit 7 va al carry flag -// El bit 0 toma el valor del flag C antes de la operación -void Z80::rl(uint8_t &oper8) { - bool carry = carryFlag; - carryFlag = (oper8 > 0x7f); - oper8 <<= 1; - if (carry) { - oper8 |= CARRY_MASK; - } - sz5h3pnFlags = sz53pn_addTable[oper8]; - flagQ = true; -} - -// Rota a la izquierda el valor del argumento -// El bit 7 va al carry flag -// El bit 0 toma el valor 0 -void Z80::sla(uint8_t &oper8) { - carryFlag = (oper8 > 0x7f); - oper8 <<= 1; - sz5h3pnFlags = sz53pn_addTable[oper8]; - flagQ = true; -} - -// Rota a la izquierda el valor del argumento (como sla salvo por el bit 0) -// El bit 7 va al carry flag -// El bit 0 toma el valor 1 -// Instrucción indocumentada -void Z80::sll(uint8_t &oper8) { - carryFlag = (oper8 > 0x7f); - oper8 <<= 1; - oper8 |= CARRY_MASK; - sz5h3pnFlags = sz53pn_addTable[oper8]; - flagQ = true; -} - -// Rota a la derecha el valor del argumento -// El bit 7 y el flag C toman el valor del bit 0 antes de la operación -void Z80::rrc(uint8_t &oper8) { - carryFlag = (oper8 & CARRY_MASK) != 0; - oper8 >>= 1; - if (carryFlag) { - oper8 |= SIGN_MASK; - } - sz5h3pnFlags = sz53pn_addTable[oper8]; - flagQ = true; -} - -// Rota a la derecha el valor del argumento -// El bit 0 va al carry flag -// El bit 7 toma el valor del flag C antes de la operación -void Z80::rr(uint8_t &oper8) { - bool carry = carryFlag; - carryFlag = (oper8 & CARRY_MASK) != 0; - oper8 >>= 1; - if (carry) { - oper8 |= SIGN_MASK; - } - sz5h3pnFlags = sz53pn_addTable[oper8]; - flagQ = true; -} - -// Rota a la derecha 1 bit el valor del argumento -// El bit 0 pasa al carry. -// El bit 7 conserva el valor que tenga -void Z80::sra(uint8_t &oper8) { - uint8_t sign = oper8 & SIGN_MASK; - carryFlag = (oper8 & CARRY_MASK) != 0; - oper8 = (oper8 >> 1) | sign; - sz5h3pnFlags = sz53pn_addTable[oper8]; - flagQ = true; -} - -// Rota a la derecha 1 bit el valor del argumento -// El bit 0 pasa al carry. -// El bit 7 toma el valor 0 -void Z80::srl(uint8_t &oper8) { - carryFlag = (oper8 & CARRY_MASK) != 0; - oper8 >>= 1; - sz5h3pnFlags = sz53pn_addTable[oper8]; - flagQ = true; -} - -/* - * Half-carry flag: - * - * FLAG = (A ^ B ^ RESULT) & 0x10 for any operation - * - * Overflow flag: - * - * FLAG = ~(A ^ B) & (B ^ RESULT) & 0x80 for addition [ADD/ADC] - * FLAG = (A ^ B) & (A ^ RESULT) &0x80 for subtraction [SUB/SBC] - * - * For INC/DEC, you can use following simplifications: - * - * INC: - * H_FLAG = (RESULT & 0x0F) == 0x00 - * V_FLAG = RESULT == 0x80 - * - * DEC: - * H_FLAG = (RESULT & 0x0F) == 0x0F - * V_FLAG = RESULT == 0x7F - */ -// Incrementa un valor de 8 bits modificando los flags oportunos -void Z80::inc8(uint8_t &oper8) { - oper8++; - - sz5h3pnFlags = sz53n_addTable[oper8]; - - if ((oper8 & 0x0f) == 0) { - sz5h3pnFlags |= HALFCARRY_MASK; - } - - if (oper8 == 0x80) { - sz5h3pnFlags |= OVERFLOW_MASK; - } - - flagQ = true; -} - -// Decrementa un valor de 8 bits modificando los flags oportunos -void Z80::dec8(uint8_t &oper8) { - oper8--; - - sz5h3pnFlags = sz53n_subTable[oper8]; - - if ((oper8 & 0x0f) == 0x0f) { - sz5h3pnFlags |= HALFCARRY_MASK; - } - - if (oper8 == 0x7f) { - sz5h3pnFlags |= OVERFLOW_MASK; - } - - flagQ = true; -} - -// Suma de 8 bits afectando a los flags -void Z80::add(uint8_t oper8) { - uint16_t res = regA + oper8; - - carryFlag = res > 0xff; - res &= 0xff; - sz5h3pnFlags = sz53n_addTable[res]; - - /* El módulo 16 del resultado será menor que el módulo 16 del registro A - * si ha habido HalfCarry. Sucede lo mismo para todos los métodos suma - * SIN carry */ - if ((res & 0x0f) < (regA & 0x0f)) { - sz5h3pnFlags |= HALFCARRY_MASK; - } - - if (((regA ^ ~oper8) & (regA ^ res)) > 0x7f) { - sz5h3pnFlags |= OVERFLOW_MASK; - } - - regA = res; - flagQ = true; -} - -// Suma con acarreo de 8 bits -void Z80::adc(uint8_t oper8) { - uint16_t res = regA + oper8; - - if (carryFlag) { - res++; - } - - carryFlag = res > 0xff; - res &= 0xff; - sz5h3pnFlags = sz53n_addTable[res]; - - if (((regA ^ oper8 ^ res) & 0x10) != 0) { - sz5h3pnFlags |= HALFCARRY_MASK; - } - - if (((regA ^ ~oper8) & (regA ^ res)) > 0x7f) { - sz5h3pnFlags |= OVERFLOW_MASK; - } - - regA = res; - flagQ = true; -} - -// Suma dos operandos de 16 bits sin carry afectando a los flags -void Z80::add16(RegisterPair ®16, uint16_t oper16) { - uint32_t tmp = oper16 + reg16.word; - - REG_WZ = reg16.word + 1; - carryFlag = tmp > 0xffff; - reg16.word = tmp; - sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZP_MASK) | ((reg16.word >> 8) & FLAG_53_MASK); - - if ((reg16.word & 0x0fff) < (oper16 & 0x0fff)) { - sz5h3pnFlags |= HALFCARRY_MASK; - } - - flagQ = true; -} - -// Suma con acarreo de 16 bits -void Z80::adc16(uint16_t reg16) { - uint16_t tmpHL = REG_HL; - REG_WZ = REG_HL + 1; - - uint32_t res = REG_HL + reg16; - if (carryFlag) { - res++; - } - - carryFlag = res > 0xffff; - res &= 0xffff; - REG_HL = static_cast(res); - - sz5h3pnFlags = sz53n_addTable[REG_H]; - if (res != 0) { - sz5h3pnFlags &= ~ZERO_MASK; - } - - if (((res ^ tmpHL ^ reg16) & 0x1000) != 0) { - sz5h3pnFlags |= HALFCARRY_MASK; - } - - if (((tmpHL ^ ~reg16) & (tmpHL ^ res)) > 0x7fff) { - sz5h3pnFlags |= OVERFLOW_MASK; - } - - flagQ = true; -} - -// Resta de 8 bits -void Z80::sub(uint8_t oper8) { - auto res = static_cast(regA - oper8); - - carryFlag = res < 0; - res &= 0xff; - sz5h3pnFlags = sz53n_subTable[res]; - - /* El módulo 16 del resultado será mayor que el módulo 16 del registro A - * si ha habido HalfCarry. Sucede lo mismo para todos los métodos resta - * SIN carry, incluido cp */ - if ((res & 0x0f) > (regA & 0x0f)) { - sz5h3pnFlags |= HALFCARRY_MASK; - } - - if (((regA ^ oper8) & (regA ^ res)) > 0x7f) { - sz5h3pnFlags |= OVERFLOW_MASK; - } - - regA = res; - flagQ = true; -} - -// Resta con acarreo de 8 bits -void Z80::sbc(uint8_t oper8) { - auto res = static_cast(regA - oper8); - - if (carryFlag) { - res--; - } - - carryFlag = res < 0; - res &= 0xff; - sz5h3pnFlags = sz53n_subTable[res]; - - if (((regA ^ oper8 ^ res) & 0x10) != 0) { - sz5h3pnFlags |= HALFCARRY_MASK; - } - - if (((regA ^ oper8) & (regA ^ res)) > 0x7f) { - sz5h3pnFlags |= OVERFLOW_MASK; - } - - regA = res; - flagQ = true; -} - -// Resta con acarreo de 16 bits -void Z80::sbc16(uint16_t reg16) { - uint16_t tmpHL = REG_HL; - REG_WZ = REG_HL + 1; - - int32_t res = REG_HL - reg16; - if (carryFlag) { - res--; - } - - carryFlag = res < 0; - res &= 0xffff; - REG_HL = static_cast(res); - - sz5h3pnFlags = sz53n_subTable[REG_H]; - if (res != 0) { - sz5h3pnFlags &= ~ZERO_MASK; - } - - if (((res ^ tmpHL ^ reg16) & 0x1000) != 0) { - sz5h3pnFlags |= HALFCARRY_MASK; - } - - if (((tmpHL ^ reg16) & (tmpHL ^ res)) > 0x7fff) { - sz5h3pnFlags |= OVERFLOW_MASK; - } - flagQ = true; -} - -// Operación AND lógica -void Z80::and_(uint8_t oper8) { - regA &= oper8; - carryFlag = false; - sz5h3pnFlags = sz53pn_addTable[regA] | HALFCARRY_MASK; - flagQ = true; -} - -// Operación XOR lógica -void Z80::xor_(uint8_t oper8) { - regA ^= oper8; - carryFlag = false; - sz5h3pnFlags = sz53pn_addTable[regA]; - flagQ = true; -} - -// Operación OR lógica -void Z80::or_(uint8_t oper8) { - regA |= oper8; - carryFlag = false; - sz5h3pnFlags = sz53pn_addTable[regA]; - flagQ = true; -} - -// Operación de comparación con el registro A -// es como SUB, pero solo afecta a los flags -// Los flags SIGN y ZERO se calculan a partir del resultado -// Los flags 3 y 5 se copian desde el operando (sigh!) -void Z80::cp(uint8_t oper8) { - auto res = static_cast(regA - oper8); - - carryFlag = res < 0; - res &= 0xff; - - sz5h3pnFlags = (sz53n_addTable[oper8] & FLAG_53_MASK) - | // No necesito preservar H, pero está a 0 en la tabla de todas formas - (sz53n_subTable[res] & FLAG_SZHN_MASK); - - if ((res & 0x0f) > (regA & 0x0f)) { - sz5h3pnFlags |= HALFCARRY_MASK; - } - - if (((regA ^ oper8) & (regA ^ res)) > 0x7f) { - sz5h3pnFlags |= OVERFLOW_MASK; - } - - flagQ = true; -} - -// DAA -void Z80::daa() { - uint8_t suma = 0; - bool carry = carryFlag; - - if ((sz5h3pnFlags & HALFCARRY_MASK) != 0 || (regA & 0x0f) > 0x09) { - suma = 6; - } - - if (carry || (regA > 0x99)) { - suma |= 0x60; - } - - if (regA > 0x99) { - carry = true; - } - - if ((sz5h3pnFlags & ADDSUB_MASK) != 0) { - sub(suma); - sz5h3pnFlags = (sz5h3pnFlags & HALFCARRY_MASK) | sz53pn_subTable[regA]; - } else { - add(suma); - sz5h3pnFlags = (sz5h3pnFlags & HALFCARRY_MASK) | sz53pn_addTable[regA]; - } - - carryFlag = carry; - // Los add/sub ya ponen el resto de los flags - flagQ = true; -} - -// POP -uint16_t Z80::pop() { - uint16_t word = Z80opsImpl->peek16(REG_SP); - REG_SP = REG_SP + 2; - return word; -} - -// PUSH -void Z80::push(uint16_t word) { - Z80opsImpl->poke8(--REG_SP, word >> 8); - Z80opsImpl->poke8(--REG_SP, word); -} - -// LDI -void Z80::ldi() { - uint8_t work8 = Z80opsImpl->peek8(REG_HL); - Z80opsImpl->poke8(REG_DE, work8); - Z80opsImpl->addressOnBus(REG_DE, 2); - REG_HL++; - REG_DE++; - REG_BC--; - work8 += regA; - - sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZ_MASK) | (work8 & BIT3_MASK); - - if ((work8 & ADDSUB_MASK) != 0) { - sz5h3pnFlags |= BIT5_MASK; - } - - if (REG_BC != 0) { - sz5h3pnFlags |= PARITY_MASK; - } - flagQ = true; -} - -// LDD -void Z80::ldd() { - uint8_t work8 = Z80opsImpl->peek8(REG_HL); - Z80opsImpl->poke8(REG_DE, work8); - Z80opsImpl->addressOnBus(REG_DE, 2); - REG_HL--; - REG_DE--; - REG_BC--; - work8 += regA; - - sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZ_MASK) | (work8 & BIT3_MASK); - - if ((work8 & ADDSUB_MASK) != 0) { - sz5h3pnFlags |= BIT5_MASK; - } - - if (REG_BC != 0) { - sz5h3pnFlags |= PARITY_MASK; - } - flagQ = true; -} - -// CPI -void Z80::cpi() { - uint8_t memHL = Z80opsImpl->peek8(REG_HL); - bool carry = carryFlag; // lo guardo porque cp lo toca - cp(memHL); - carryFlag = carry; - Z80opsImpl->addressOnBus(REG_HL, 5); - REG_HL++; - REG_BC--; - memHL = regA - memHL - ((sz5h3pnFlags & HALFCARRY_MASK) != 0 ? 1 : 0); - sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZHN_MASK) | (memHL & BIT3_MASK); - - if ((memHL & ADDSUB_MASK) != 0) { - sz5h3pnFlags |= BIT5_MASK; - } - - if (REG_BC != 0) { - sz5h3pnFlags |= PARITY_MASK; - } - - REG_WZ++; - flagQ = true; -} - -// CPD -void Z80::cpd() { - uint8_t memHL = Z80opsImpl->peek8(REG_HL); - bool carry = carryFlag; // lo guardo porque cp lo toca - cp(memHL); - carryFlag = carry; - Z80opsImpl->addressOnBus(REG_HL, 5); - REG_HL--; - REG_BC--; - memHL = regA - memHL - ((sz5h3pnFlags & HALFCARRY_MASK) != 0 ? 1 : 0); - sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZHN_MASK) | (memHL & BIT3_MASK); - - if ((memHL & ADDSUB_MASK) != 0) { - sz5h3pnFlags |= BIT5_MASK; - } - - if (REG_BC != 0) { - sz5h3pnFlags |= PARITY_MASK; - } - - REG_WZ--; - flagQ = true; -} - -// INI -void Z80::ini() { - REG_WZ = REG_BC; - Z80opsImpl->addressOnBus(getPairIR().word, 1); - uint8_t work8 = Z80opsImpl->inPort(REG_WZ++); - Z80opsImpl->poke8(REG_HL, work8); - - REG_B--; - REG_HL++; - - sz5h3pnFlags = sz53pn_addTable[REG_B]; - if (work8 > 0x7f) { - sz5h3pnFlags |= ADDSUB_MASK; - } - - carryFlag = false; - uint16_t tmp = work8 + ((REG_C + 1) & 0xff); - if (tmp > 0xff) { - sz5h3pnFlags |= HALFCARRY_MASK; - carryFlag = true; - } - - if ((sz53pn_addTable[((tmp & 0x07) ^ REG_B)] - & PARITY_MASK) == PARITY_MASK) { - sz5h3pnFlags |= PARITY_MASK; - } else { - sz5h3pnFlags &= ~PARITY_MASK; - } - flagQ = true; -} - -// IND -void Z80::ind() { - REG_WZ = REG_BC; - Z80opsImpl->addressOnBus(getPairIR().word, 1); - uint8_t work8 = Z80opsImpl->inPort(REG_WZ--); - Z80opsImpl->poke8(REG_HL, work8); - - REG_B--; - REG_HL--; - - sz5h3pnFlags = sz53pn_addTable[REG_B]; - if (work8 > 0x7f) { - sz5h3pnFlags |= ADDSUB_MASK; - } - - carryFlag = false; - uint16_t tmp = work8 + ((REG_C - 1) & 0xff); - if (tmp > 0xff) { - sz5h3pnFlags |= HALFCARRY_MASK; - carryFlag = true; - } - - if ((sz53pn_addTable[((tmp & 0x07) ^ REG_B)] - & PARITY_MASK) == PARITY_MASK) { - sz5h3pnFlags |= PARITY_MASK; - } else { - sz5h3pnFlags &= ~PARITY_MASK; - } - flagQ = true; -} - -// OUTI -void Z80::outi() { - - Z80opsImpl->addressOnBus(getPairIR().word, 1); - - REG_B--; - REG_WZ = REG_BC; - - uint8_t work8 = Z80opsImpl->peek8(REG_HL); - Z80opsImpl->outPort(REG_WZ++, work8); - - REG_HL++; - - carryFlag = false; - if (work8 > 0x7f) { - sz5h3pnFlags = sz53n_subTable[REG_B]; - } else { - sz5h3pnFlags = sz53n_addTable[REG_B]; - } - - if ((REG_L + work8) > 0xff) { - sz5h3pnFlags |= HALFCARRY_MASK; - carryFlag = true; - } - - if ((sz53pn_addTable[(((REG_L + work8) & 0x07) ^ REG_B)] - & PARITY_MASK) == PARITY_MASK) { - sz5h3pnFlags |= PARITY_MASK; - } - flagQ = true; -} - -// OUTD -void Z80::outd() { - - Z80opsImpl->addressOnBus(getPairIR().word, 1); - - REG_B--; - REG_WZ = REG_BC; - - uint8_t work8 = Z80opsImpl->peek8(REG_HL); - Z80opsImpl->outPort(REG_WZ--, work8); - - REG_HL--; - - carryFlag = false; - if (work8 > 0x7f) { - sz5h3pnFlags = sz53n_subTable[REG_B]; - } else { - sz5h3pnFlags = sz53n_addTable[REG_B]; - } - - if ((REG_L + work8) > 0xff) { - sz5h3pnFlags |= HALFCARRY_MASK; - carryFlag = true; - } - - if ((sz53pn_addTable[(((REG_L + work8) & 0x07) ^ REG_B)] - & PARITY_MASK) == PARITY_MASK) { - sz5h3pnFlags |= PARITY_MASK; - } - flagQ = true; -} - -// Pone a 1 el Flag Z si el bit b del registro -// r es igual a 0 -/* - * En contra de lo que afirma el Z80-Undocumented, los bits 3 y 5 toman - * SIEMPRE el valor de los bits correspondientes del valor a comparar para - * las instrucciones BIT n,r. Para BIT n,(HL) toman el valor del registro - * escondido (REG_WZ), y para las BIT n, (IX/IY+n) toman el valor de los - * bits superiores de la dirección indicada por IX/IY+n. - * - * 04/12/08 Confirmado el comentario anterior: - * http://scratchpad.wikia.com/wiki/Z80 - */ -void Z80::bitTest(uint8_t mask, uint8_t reg) { - bool zeroFlag = (mask & reg) == 0; - - sz5h3pnFlags = (sz53n_addTable[reg] & ~FLAG_SZP_MASK) | HALFCARRY_MASK; - - if (zeroFlag) { - sz5h3pnFlags |= (PARITY_MASK | ZERO_MASK); - } - - if (mask == SIGN_MASK && !zeroFlag) { - sz5h3pnFlags |= SIGN_MASK; - } - flagQ = true; -} - -//Interrupción -/* Desglose de la interrupción, según el modo: - * IM0: - * M1: 7 T-Estados -> reconocer INT y decSP - * M2: 3 T-Estados -> escribir byte alto y decSP - * M3: 3 T-Estados -> escribir byte bajo y salto a N - * IM1: - * M1: 7 T-Estados -> reconocer INT y decSP - * M2: 3 T-Estados -> escribir byte alto PC y decSP - * M3: 3 T-Estados -> escribir byte bajo PC y PC=0x0038 - * IM2: - * M1: 7 T-Estados -> reconocer INT y decSP - * M2: 3 T-Estados -> escribir byte alto y decSP - * M3: 3 T-Estados -> escribir byte bajo - * M4: 3 T-Estados -> leer byte bajo del vector de INT - * M5: 3 T-Estados -> leer byte alto y saltar a la rutina de INT - */ -void Z80::interrupt() { - // Si estaba en un HALT esperando una INT, lo saca de la espera - halted = false; - - Z80opsImpl->interruptHandlingTime(7); - - regR++; - ffIFF1 = ffIFF2 = false; - push(REG_PC); // el push añadirá 6 t-estados (+contended si toca) - if (modeINT == IntMode::IM2) { - REG_PC = Z80opsImpl->peek16((regI << 8) | 0xff); // +6 t-estados - } else { - REG_PC = 0x0038; - } - REG_WZ = REG_PC; -} - -//Interrupción NMI, no utilizado por ahora -/* Desglose de ciclos de máquina y T-Estados - * M1: 5 T-Estados -> extraer opcode (pá ná, es tontería) y decSP - * M2: 3 T-Estados -> escribe byte alto de PC y decSP - * M3: 3 T-Estados -> escrib e byte bajo de PC y PC=0x0066 - */ -void Z80::nmi() { - halted = false; - // Esta lectura consigue dos cosas: - // 1.- La lectura del opcode del M1 que se descarta - // 2.- Si estaba en un HALT esperando una INT, lo saca de la espera - Z80opsImpl->fetchOpcode(REG_PC); - Z80opsImpl->interruptHandlingTime(1); - regR++; - ffIFF1 = false; - push(REG_PC); // 3+3 t-estados + contended si procede - REG_PC = REG_WZ = 0x0066; -} - -void Z80::execute() { - - m_opCode = Z80opsImpl->fetchOpcode(REG_PC); - regR++; - -#ifdef WITH_BREAKPOINT_SUPPORT - if (breakpointEnabled && prefixOpcode == 0) { - m_opCode = Z80opsImpl->breakpoint(REG_PC, m_opCode); - } -#endif - if (!halted) { - REG_PC++; - - // El prefijo 0xCB no cuenta para esta guerra. - // En CBxx todas las xx producen un código válido - // de instrucción, incluyendo CBCB. - switch (prefixOpcode) { - case 0x00: - flagQ = pendingEI = false; - decodeOpcode(m_opCode); - break; - case 0xDD: - prefixOpcode = 0; - decodeDDFD(m_opCode, regIX); - break; - case 0xED: - prefixOpcode = 0; - decodeED(m_opCode); - break; - case 0xFD: - prefixOpcode = 0; - decodeDDFD(m_opCode, regIY); - break; - default: - return; - } - - if (prefixOpcode != 0) - return; - - lastFlagQ = flagQ; - -#ifdef WITH_EXEC_DONE - if (execDone) { - Z80opsImpl->execDone(); - } -#endif - } - - // Primero se comprueba NMI - // Si se activa NMI no se comprueba INT porque la siguiente - // instrucción debe ser la de 0x0066. - if (activeNMI) { - activeNMI = false; - lastFlagQ = false; - nmi(); - return; - } - - // Ahora se comprueba si está activada la señal INT - if (ffIFF1 && !pendingEI && Z80opsImpl->isActiveINT()) { - lastFlagQ = false; - interrupt(); - } -} - -void Z80::decodeOpcode(uint8_t opCode) { - - switch (opCode) { - case 0x00: - { /* NOP */ - break; - } - case 0x01: - { /* LD BC,nn */ - REG_BC = Z80opsImpl->peek16(REG_PC); - REG_PC = REG_PC + 2; - break; - } - case 0x02: - { /* LD (BC),A */ - Z80opsImpl->poke8(REG_BC, regA); - REG_W = regA; - REG_Z = REG_C + 1; - //REG_WZ = (regA << 8) | (REG_C + 1); - break; - } - case 0x03: - { /* INC BC */ - Z80opsImpl->addressOnBus(getPairIR().word, 2); - REG_BC++; - break; - } - case 0x04: - { /* INC B */ - inc8(REG_B); - break; - } - case 0x05: - { /* DEC B */ - dec8(REG_B); - break; - } - case 0x06: - { /* LD B,n */ - REG_B = Z80opsImpl->peek8(REG_PC); - REG_PC++; - break; - } - case 0x07: - { /* RLCA */ - carryFlag = (regA > 0x7f); - regA <<= 1; - if (carryFlag) { - regA |= CARRY_MASK; - } - sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZP_MASK) | (regA & FLAG_53_MASK); - flagQ = true; - break; - } - case 0x08: - { /* EX AF,AF' */ - uint8_t work8 = regA; - regA = REG_Ax; - REG_Ax = work8; - - work8 = getFlags(); - setFlags(REG_Fx); - REG_Fx = work8; - break; - } - case 0x09: - { /* ADD HL,BC */ - Z80opsImpl->addressOnBus(getPairIR().word, 7); - add16(regHL, REG_BC); - break; - } - case 0x0A: - { /* LD A,(BC) */ - regA = Z80opsImpl->peek8(REG_BC); - REG_WZ = REG_BC + 1; - break; - } - case 0x0B: - { /* DEC BC */ - Z80opsImpl->addressOnBus(getPairIR().word, 2); - REG_BC--; - break; - } - case 0x0C: - { /* INC C */ - inc8(REG_C); - break; - } - case 0x0D: - { /* DEC C */ - dec8(REG_C); - break; - } - case 0x0E: - { /* LD C,n */ - REG_C = Z80opsImpl->peek8(REG_PC); - REG_PC++; - break; - } - case 0x0F: - { /* RRCA */ - carryFlag = (regA & CARRY_MASK) != 0; - regA >>= 1; - if (carryFlag) { - regA |= SIGN_MASK; - } - sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZP_MASK) | (regA & FLAG_53_MASK); - flagQ = true; - break; - } - case 0x10: - { /* DJNZ e */ - Z80opsImpl->addressOnBus(getPairIR().word, 1); - auto offset = static_cast(Z80opsImpl->peek8(REG_PC)); - if (--REG_B != 0) { - Z80opsImpl->addressOnBus(REG_PC, 5); - REG_PC = REG_WZ = REG_PC + offset + 1; - } else { - REG_PC++; - } - break; - } - case 0x11: - { /* LD DE,nn */ - REG_DE = Z80opsImpl->peek16(REG_PC); - REG_PC = REG_PC + 2; - break; - } - case 0x12: - { /* LD (DE),A */ - Z80opsImpl->poke8(REG_DE, regA); - REG_W = regA; - REG_Z = REG_E + 1; - //REG_WZ = (regA << 8) | (REG_E + 1); - break; - } - case 0x13: - { /* INC DE */ - Z80opsImpl->addressOnBus(getPairIR().word, 2); - REG_DE++; - break; - } - case 0x14: - { /* INC D */ - inc8(REG_D); - break; - } - case 0x15: - { /* DEC D */ - dec8(REG_D); - break; - } - case 0x16: - { /* LD D,n */ - REG_D = Z80opsImpl->peek8(REG_PC); - REG_PC++; - break; - } - case 0x17: - { /* RLA */ - bool oldCarry = carryFlag; - carryFlag = regA > 0x7f; - regA <<= 1; - if (oldCarry) { - regA |= CARRY_MASK; - } - sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZP_MASK) | (regA & FLAG_53_MASK); - flagQ = true; - break; - } - case 0x18: - { /* JR e */ - auto offset = static_cast(Z80opsImpl->peek8(REG_PC)); - Z80opsImpl->addressOnBus(REG_PC, 5); - REG_PC = REG_WZ = REG_PC + offset + 1; - break; - } - case 0x19: - { /* ADD HL,DE */ - Z80opsImpl->addressOnBus(getPairIR().word, 7); - add16(regHL, REG_DE); - break; - } - case 0x1A: - { /* LD A,(DE) */ - regA = Z80opsImpl->peek8(REG_DE); - REG_WZ = REG_DE + 1; - break; - } - case 0x1B: - { /* DEC DE */ - Z80opsImpl->addressOnBus(getPairIR().word, 2); - REG_DE--; - break; - } - case 0x1C: - { /* INC E */ - inc8(REG_E); - break; - } - case 0x1D: - { /* DEC E */ - dec8(REG_E); - break; - } - case 0x1E: - { /* LD E,n */ - REG_E = Z80opsImpl->peek8(REG_PC); - REG_PC++; - break; - } - case 0x1F: - { /* RRA */ - bool oldCarry = carryFlag; - carryFlag = (regA & CARRY_MASK) != 0; - regA >>= 1; - if (oldCarry) { - regA |= SIGN_MASK; - } - sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZP_MASK) | (regA & FLAG_53_MASK); - flagQ = true; - break; - } - case 0x20: - { /* JR NZ,e */ - auto offset = static_cast(Z80opsImpl->peek8(REG_PC)); - if ((sz5h3pnFlags & ZERO_MASK) == 0) { - Z80opsImpl->addressOnBus(REG_PC, 5); - REG_PC += offset; - REG_WZ = REG_PC + 1; - } - REG_PC++; - break; - } - case 0x21: - { /* LD HL,nn */ - REG_HL = Z80opsImpl->peek16(REG_PC); - REG_PC = REG_PC + 2; - break; - } - case 0x22: - { /* LD (nn),HL */ - REG_WZ = Z80opsImpl->peek16(REG_PC); - Z80opsImpl->poke16(REG_WZ, regHL); - REG_WZ++; - REG_PC = REG_PC + 2; - break; - } - case 0x23: - { /* INC HL */ - Z80opsImpl->addressOnBus(getPairIR().word, 2); - REG_HL++; - break; - } - case 0x24: - { /* INC H */ - inc8(REG_H); - break; - } - case 0x25: - { /* DEC H */ - dec8(REG_H); - break; - } - case 0x26: - { /* LD H,n */ - REG_H = Z80opsImpl->peek8(REG_PC); - REG_PC++; - break; - } - case 0x27: - { /* DAA */ - daa(); - break; - } - case 0x28: - { /* JR Z,e */ - auto offset = static_cast(Z80opsImpl->peek8(REG_PC)); - if ((sz5h3pnFlags & ZERO_MASK) != 0) { - Z80opsImpl->addressOnBus(REG_PC, 5); - REG_PC += offset; - REG_WZ = REG_PC + 1; - } - REG_PC++; - break; - } - case 0x29: - { /* ADD HL,HL */ - Z80opsImpl->addressOnBus(getPairIR().word, 7); - add16(regHL, REG_HL); - break; - } - case 0x2A: - { /* LD HL,(nn) */ - REG_WZ = Z80opsImpl->peek16(REG_PC); - REG_HL = Z80opsImpl->peek16(REG_WZ); - REG_WZ++; - REG_PC = REG_PC + 2; - break; - } - case 0x2B: - { /* DEC HL */ - Z80opsImpl->addressOnBus(getPairIR().word, 2); - REG_HL--; - break; - } - case 0x2C: - { /* INC L */ - inc8(REG_L); - break; - } - case 0x2D: - { /* DEC L */ - dec8(REG_L); - break; - } - case 0x2E: - { /* LD L,n */ - REG_L = Z80opsImpl->peek8(REG_PC); - REG_PC++; - break; - } - case 0x2F: - { /* CPL */ - regA ^= 0xff; - sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZP_MASK) | HALFCARRY_MASK - | (regA & FLAG_53_MASK) | ADDSUB_MASK; - flagQ = true; - break; - } - case 0x30: - { /* JR NC,e */ - auto offset = static_cast(Z80opsImpl->peek8(REG_PC)); - if (!carryFlag) { - Z80opsImpl->addressOnBus(REG_PC, 5); - REG_PC += offset; - REG_WZ = REG_PC + 1; - } - REG_PC++; - break; - } - case 0x31: - { /* LD SP,nn */ - REG_SP = Z80opsImpl->peek16(REG_PC); - REG_PC = REG_PC + 2; - break; - } - case 0x32: - { /* LD (nn),A */ - REG_WZ = Z80opsImpl->peek16(REG_PC); - Z80opsImpl->poke8(REG_WZ, regA); - REG_WZ = (regA << 8) | ((REG_WZ + 1) & 0xff); - REG_PC = REG_PC + 2; - break; - } - case 0x33: - { /* INC SP */ - Z80opsImpl->addressOnBus(getPairIR().word, 2); - REG_SP++; - break; - } - case 0x34: - { /* INC (HL) */ - uint8_t work8 = Z80opsImpl->peek8(REG_HL); - inc8(work8); - Z80opsImpl->addressOnBus(REG_HL, 1); - Z80opsImpl->poke8(REG_HL, work8); - break; - } - case 0x35: - { /* DEC (HL) */ - uint8_t work8 = Z80opsImpl->peek8(REG_HL); - dec8(work8); - Z80opsImpl->addressOnBus(REG_HL, 1); - Z80opsImpl->poke8(REG_HL, work8); - break; - } - case 0x36: - { /* LD (HL),n */ - Z80opsImpl->poke8(REG_HL, Z80opsImpl->peek8(REG_PC)); - REG_PC++; - break; - } - case 0x37: - { /* SCF */ - uint8_t regQ = lastFlagQ ? sz5h3pnFlags : 0; - carryFlag = true; - sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZP_MASK) | (((regQ ^ sz5h3pnFlags) | regA) & FLAG_53_MASK); - flagQ = true; - break; - } - case 0x38: - { /* JR C,e */ - auto offset = static_cast(Z80opsImpl->peek8(REG_PC)); - if (carryFlag) { - Z80opsImpl->addressOnBus(REG_PC, 5); - REG_PC += offset; - REG_WZ = REG_PC + 1; - } - REG_PC++; - break; - } - case 0x39: - { /* ADD HL,SP */ - Z80opsImpl->addressOnBus(getPairIR().word, 7); - add16(regHL, REG_SP); - break; - } - case 0x3A: - { /* LD A,(nn) */ - REG_WZ = Z80opsImpl->peek16(REG_PC); - regA = Z80opsImpl->peek8(REG_WZ); - REG_WZ++; - REG_PC = REG_PC + 2; - break; - } - case 0x3B: - { /* DEC SP */ - Z80opsImpl->addressOnBus(getPairIR().word, 2); - REG_SP--; - break; - } - case 0x3C: - { /* INC A */ - inc8(regA); - break; - } - case 0x3D: - { /* DEC A */ - dec8(regA); - break; - } - case 0x3E: - { /* LD A,n */ - regA = Z80opsImpl->peek8(REG_PC); - REG_PC++; - break; - } - case 0x3F: - { /* CCF */ - uint8_t regQ = lastFlagQ ? sz5h3pnFlags : 0; - sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZP_MASK) | (((regQ ^ sz5h3pnFlags) | regA) & FLAG_53_MASK); - if (carryFlag) { - sz5h3pnFlags |= HALFCARRY_MASK; - } - carryFlag = !carryFlag; - flagQ = true; - break; - } -// case 0x40: { /* LD B,B */ -// break; -// } - case 0x41: - { /* LD B,C */ - REG_B = REG_C; - break; - } - case 0x42: - { /* LD B,D */ - REG_B = REG_D; - break; - } - case 0x43: - { /* LD B,E */ - REG_B = REG_E; - break; - } - case 0x44: - { /* LD B,H */ - REG_B = REG_H; - break; - } - case 0x45: - { /* LD B,L */ - REG_B = REG_L; - break; - } - case 0x46: - { /* LD B,(HL) */ - REG_B = Z80opsImpl->peek8(REG_HL); - break; - } - case 0x47: - { /* LD B,A */ - REG_B = regA; - break; - } - case 0x48: - { /* LD C,B */ - REG_C = REG_B; - break; - } -// case 0x49: { /* LD C,C */ -// break; -// } - case 0x4A: - { /* LD C,D */ - REG_C = REG_D; - break; - } - case 0x4B: - { /* LD C,E */ - REG_C = REG_E; - break; - } - case 0x4C: - { /* LD C,H */ - REG_C = REG_H; - break; - } - case 0x4D: - { /* LD C,L */ - REG_C = REG_L; - break; - } - case 0x4E: - { /* LD C,(HL) */ - REG_C = Z80opsImpl->peek8(REG_HL); - break; - } - case 0x4F: - { /* LD C,A */ - REG_C = regA; - break; - } - case 0x50: - { /* LD D,B */ - REG_D = REG_B; - break; - } - case 0x51: - { /* LD D,C */ - REG_D = REG_C; - break; - } -// case 0x52: { /* LD D,D */ -// break; -// } - case 0x53: - { /* LD D,E */ - REG_D = REG_E; - break; - } - case 0x54: - { /* LD D,H */ - REG_D = REG_H; - break; - } - case 0x55: - { /* LD D,L */ - REG_D = REG_L; - break; - } - case 0x56: - { /* LD D,(HL) */ - REG_D = Z80opsImpl->peek8(REG_HL); - break; - } - case 0x57: - { /* LD D,A */ - REG_D = regA; - break; - } - case 0x58: - { /* LD E,B */ - REG_E = REG_B; - break; - } - case 0x59: - { /* LD E,C */ - REG_E = REG_C; - break; - } - case 0x5A: - { /* LD E,D */ - REG_E = REG_D; - break; - } -// case 0x5B: { /* LD E,E */ -// break; -// } - case 0x5C: - { /* LD E,H */ - REG_E = REG_H; - break; - } - case 0x5D: - { /* LD E,L */ - REG_E = REG_L; - break; - } - case 0x5E: - { /* LD E,(HL) */ - REG_E = Z80opsImpl->peek8(REG_HL); - break; - } - case 0x5F: - { /* LD E,A */ - REG_E = regA; - break; - } - case 0x60: - { /* LD H,B */ - REG_H = REG_B; - break; - } - case 0x61: - { /* LD H,C */ - REG_H = REG_C; - break; - } - case 0x62: - { /* LD H,D */ - REG_H = REG_D; - break; - } - case 0x63: - { /* LD H,E */ - REG_H = REG_E; - break; - } -// case 0x64: { /* LD H,H */ -// break; -// } - case 0x65: - { /* LD H,L */ - REG_H = REG_L; - break; - } - case 0x66: - { /* LD H,(HL) */ - REG_H = Z80opsImpl->peek8(REG_HL); - break; - } - case 0x67: - { /* LD H,A */ - REG_H = regA; - break; - } - case 0x68: - { /* LD L,B */ - REG_L = REG_B; - break; - } - case 0x69: - { /* LD L,C */ - REG_L = REG_C; - break; - } - case 0x6A: - { /* LD L,D */ - REG_L = REG_D; - break; - } - case 0x6B: - { /* LD L,E */ - REG_L = REG_E; - break; - } - case 0x6C: - { /* LD L,H */ - REG_L = REG_H; - break; - } -// case 0x6D: { /* LD L,L */ -// break; -// } - case 0x6E: - { /* LD L,(HL) */ - REG_L = Z80opsImpl->peek8(REG_HL); - break; - } - case 0x6F: - { /* LD L,A */ - REG_L = regA; - break; - } - case 0x70: - { /* LD (HL),B */ - Z80opsImpl->poke8(REG_HL, REG_B); - break; - } - case 0x71: - { /* LD (HL),C */ - Z80opsImpl->poke8(REG_HL, REG_C); - break; - } - case 0x72: - { /* LD (HL),D */ - Z80opsImpl->poke8(REG_HL, REG_D); - break; - } - case 0x73: - { /* LD (HL),E */ - Z80opsImpl->poke8(REG_HL, REG_E); - break; - } - case 0x74: - { /* LD (HL),H */ - Z80opsImpl->poke8(REG_HL, REG_H); - break; - } - case 0x75: - { /* LD (HL),L */ - Z80opsImpl->poke8(REG_HL, REG_L); - break; - } - case 0x76: - { /* HALT */ - halted = true; - break; - } - case 0x77: - { /* LD (HL),A */ - Z80opsImpl->poke8(REG_HL, regA); - break; - } - case 0x78: - { /* LD A,B */ - regA = REG_B; - break; - } - case 0x79: - { /* LD A,C */ - regA = REG_C; - break; - } - case 0x7A: - { /* LD A,D */ - regA = REG_D; - break; - } - case 0x7B: - { /* LD A,E */ - regA = REG_E; - break; - } - case 0x7C: - { /* LD A,H */ - regA = REG_H; - break; - } - case 0x7D: - { /* LD A,L */ - regA = REG_L; - break; - } - case 0x7E: - { /* LD A,(HL) */ - regA = Z80opsImpl->peek8(REG_HL); - break; - } -// case 0x7F: { /* LD A,A */ -// break; -// } - case 0x80: - { /* ADD A,B */ - add(REG_B); - break; - } - case 0x81: - { /* ADD A,C */ - add(REG_C); - break; - } - case 0x82: - { /* ADD A,D */ - add(REG_D); - break; - } - case 0x83: - { /* ADD A,E */ - add(REG_E); - break; - } - case 0x84: - { /* ADD A,H */ - add(REG_H); - break; - } - case 0x85: - { /* ADD A,L */ - add(REG_L); - break; - } - case 0x86: - { /* ADD A,(HL) */ - add(Z80opsImpl->peek8(REG_HL)); - break; - } - case 0x87: - { /* ADD A,A */ - add(regA); - break; - } - case 0x88: - { /* ADC A,B */ - adc(REG_B); - break; - } - case 0x89: - { /* ADC A,C */ - adc(REG_C); - break; - } - case 0x8A: - { /* ADC A,D */ - adc(REG_D); - break; - } - case 0x8B: - { /* ADC A,E */ - adc(REG_E); - break; - } - case 0x8C: - { /* ADC A,H */ - adc(REG_H); - break; - } - case 0x8D: - { /* ADC A,L */ - adc(REG_L); - break; - } - case 0x8E: - { /* ADC A,(HL) */ - adc(Z80opsImpl->peek8(REG_HL)); - break; - } - case 0x8F: - { /* ADC A,A */ - adc(regA); - break; - } - case 0x90: - { /* SUB B */ - sub(REG_B); - break; - } - case 0x91: - { /* SUB C */ - sub(REG_C); - break; - } - case 0x92: - { /* SUB D */ - sub(REG_D); - break; - } - case 0x93: - { /* SUB E */ - sub(REG_E); - break; - } - case 0x94: - { /* SUB H */ - sub(REG_H); - break; - } - case 0x95: - { /* SUB L */ - sub(REG_L); - break; - } - case 0x96: - { /* SUB (HL) */ - sub(Z80opsImpl->peek8(REG_HL)); - break; - } - case 0x97: - { /* SUB A */ - sub(regA); - break; - } - case 0x98: - { /* SBC A,B */ - sbc(REG_B); - break; - } - case 0x99: - { /* SBC A,C */ - sbc(REG_C); - break; - } - case 0x9A: - { /* SBC A,D */ - sbc(REG_D); - break; - } - case 0x9B: - { /* SBC A,E */ - sbc(REG_E); - break; - } - case 0x9C: - { /* SBC A,H */ - sbc(REG_H); - break; - } - case 0x9D: - { /* SBC A,L */ - sbc(REG_L); - break; - } - case 0x9E: - { /* SBC A,(HL) */ - sbc(Z80opsImpl->peek8(REG_HL)); - break; - } - case 0x9F: - { /* SBC A,A */ - sbc(regA); - break; - } - case 0xA0: - { /* AND B */ - and_(REG_B); - break; - } - case 0xA1: - { /* AND C */ - and_(REG_C); - break; - } - case 0xA2: - { /* AND D */ - and_(REG_D); - break; - } - case 0xA3: - { /* AND E */ - and_(REG_E); - break; - } - case 0xA4: - { /* AND H */ - and_(REG_H); - break; - } - case 0xA5: - { /* AND L */ - and_(REG_L); - break; - } - case 0xA6: - { /* AND (HL) */ - and_(Z80opsImpl->peek8(REG_HL)); - break; - } - case 0xA7: - { /* AND A */ - and_(regA); - break; - } - case 0xA8: - { /* XOR B */ - xor_(REG_B); - break; - } - case 0xA9: - { /* XOR C */ - xor_(REG_C); - break; - } - case 0xAA: - { /* XOR D */ - xor_(REG_D); - break; - } - case 0xAB: - { /* XOR E */ - xor_(REG_E); - break; - } - case 0xAC: - { /* XOR H */ - xor_(REG_H); - break; - } - case 0xAD: - { /* XOR L */ - xor_(REG_L); - break; - } - case 0xAE: - { /* XOR (HL) */ - xor_(Z80opsImpl->peek8(REG_HL)); - break; - } - case 0xAF: - { /* XOR A */ - xor_(regA); - break; - } - case 0xB0: - { /* OR B */ - or_(REG_B); - break; - } - case 0xB1: - { /* OR C */ - or_(REG_C); - break; - } - case 0xB2: - { /* OR D */ - or_(REG_D); - break; - } - case 0xB3: - { /* OR E */ - or_(REG_E); - break; - } - case 0xB4: - { /* OR H */ - or_(REG_H); - break; - } - case 0xB5: - { /* OR L */ - or_(REG_L); - break; - } - case 0xB6: - { /* OR (HL) */ - or_(Z80opsImpl->peek8(REG_HL)); - break; - } - case 0xB7: - { /* OR A */ - or_(regA); - break; - } - case 0xB8: - { /* CP B */ - cp(REG_B); - break; - } - case 0xB9: - { /* CP C */ - cp(REG_C); - break; - } - case 0xBA: - { /* CP D */ - cp(REG_D); - break; - } - case 0xBB: - { /* CP E */ - cp(REG_E); - break; - } - case 0xBC: - { /* CP H */ - cp(REG_H); - break; - } - case 0xBD: - { /* CP L */ - cp(REG_L); - break; - } - case 0xBE: - { /* CP (HL) */ - cp(Z80opsImpl->peek8(REG_HL)); - break; - } - case 0xBF: - { /* CP A */ - cp(regA); - break; - } - case 0xC0: - { /* RET NZ */ - Z80opsImpl->addressOnBus(getPairIR().word, 1); - if ((sz5h3pnFlags & ZERO_MASK) == 0) { - REG_PC = REG_WZ = pop(); - } - break; - } - case 0xC1: - { /* POP BC */ - REG_BC = pop(); - break; - } - case 0xC2: - { /* JP NZ,nn */ - REG_WZ = Z80opsImpl->peek16(REG_PC); - if ((sz5h3pnFlags & ZERO_MASK) == 0) { - REG_PC = REG_WZ; - break; - } - REG_PC = REG_PC + 2; - break; - } - case 0xC3: - { /* JP nn */ - REG_WZ = REG_PC = Z80opsImpl->peek16(REG_PC); - break; - } - case 0xC4: - { /* CALL NZ,nn */ - REG_WZ = Z80opsImpl->peek16(REG_PC); - if ((sz5h3pnFlags & ZERO_MASK) == 0) { - Z80opsImpl->addressOnBus(REG_PC + 1, 1); - push(REG_PC + 2); - REG_PC = REG_WZ; - break; - } - REG_PC = REG_PC + 2; - break; - } - case 0xC5: - { /* PUSH BC */ - Z80opsImpl->addressOnBus(getPairIR().word, 1); - push(REG_BC); - break; - } - case 0xC6: - { /* ADD A,n */ - add(Z80opsImpl->peek8(REG_PC)); - REG_PC++; - break; - } - case 0xC7: - { /* RST 00H */ - Z80opsImpl->addressOnBus(getPairIR().word, 1); - push(REG_PC); - REG_PC = REG_WZ = 0x00; - break; - } - case 0xC8: - { /* RET Z */ - Z80opsImpl->addressOnBus(getPairIR().word, 1); - if ((sz5h3pnFlags & ZERO_MASK) != 0) { - REG_PC = REG_WZ = pop(); - } - break; - } - case 0xC9: - { /* RET */ - REG_PC = REG_WZ = pop(); - break; - } - case 0xCA: - { /* JP Z,nn */ - REG_WZ = Z80opsImpl->peek16(REG_PC); - if ((sz5h3pnFlags & ZERO_MASK) != 0) { - REG_PC = REG_WZ; - break; - } - REG_PC = REG_PC + 2; - break; - } - case 0xCB: - { /* Subconjunto de instrucciones */ - decodeCB(); - break; - } - case 0xCC: - { /* CALL Z,nn */ - REG_WZ = Z80opsImpl->peek16(REG_PC); - if ((sz5h3pnFlags & ZERO_MASK) != 0) { - Z80opsImpl->addressOnBus(REG_PC + 1, 1); - push(REG_PC + 2); - REG_PC = REG_WZ; - break; - } - REG_PC = REG_PC + 2; - break; - } - case 0xCD: - { /* CALL nn */ - REG_WZ = Z80opsImpl->peek16(REG_PC); - Z80opsImpl->addressOnBus(REG_PC + 1, 1); - push(REG_PC + 2); - REG_PC = REG_WZ; - break; - } - case 0xCE: - { /* ADC A,n */ - adc(Z80opsImpl->peek8(REG_PC)); - REG_PC++; - break; - } - case 0xCF: - { /* RST 08H */ - Z80opsImpl->addressOnBus(getPairIR().word, 1); - push(REG_PC); - REG_PC = REG_WZ = 0x08; - break; - } - case 0xD0: - { /* RET NC */ - Z80opsImpl->addressOnBus(getPairIR().word, 1); - if (!carryFlag) { - REG_PC = REG_WZ = pop(); - } - break; - } - case 0xD1: - { /* POP DE */ - REG_DE = pop(); - break; - } - case 0xD2: - { /* JP NC,nn */ - REG_WZ = Z80opsImpl->peek16(REG_PC); - if (!carryFlag) { - REG_PC = REG_WZ; - break; - } - REG_PC = REG_PC + 2; - break; - } - case 0xD3: - { /* OUT (n),A */ - uint8_t work8 = Z80opsImpl->peek8(REG_PC); - REG_PC++; - REG_WZ = regA << 8; - Z80opsImpl->outPort(REG_WZ | work8, regA); - REG_WZ |= (work8 + 1); - break; - } - case 0xD4: - { /* CALL NC,nn */ - REG_WZ = Z80opsImpl->peek16(REG_PC); - if (!carryFlag) { - Z80opsImpl->addressOnBus(REG_PC + 1, 1); - push(REG_PC + 2); - REG_PC = REG_WZ; - break; - } - REG_PC = REG_PC + 2; - break; - } - case 0xD5: - { /* PUSH DE */ - Z80opsImpl->addressOnBus(getPairIR().word, 1); - push(REG_DE); - break; - } - case 0xD6: - { /* SUB n */ - sub(Z80opsImpl->peek8(REG_PC)); - REG_PC++; - break; - } - case 0xD7: - { /* RST 10H */ - Z80opsImpl->addressOnBus(getPairIR().word, 1); - push(REG_PC); - REG_PC = REG_WZ = 0x10; - break; - } - case 0xD8: - { /* RET C */ - Z80opsImpl->addressOnBus(getPairIR().word, 1); - if (carryFlag) { - REG_PC = REG_WZ = pop(); - } - break; - } - case 0xD9: - { /* EXX */ - uint16_t tmp; - tmp = REG_BC; - REG_BC = REG_BCx; - REG_BCx = tmp; - - tmp = REG_DE; - REG_DE = REG_DEx; - REG_DEx = tmp; - - tmp = REG_HL; - REG_HL = REG_HLx; - REG_HLx = tmp; - break; - } - case 0xDA: - { /* JP C,nn */ - REG_WZ = Z80opsImpl->peek16(REG_PC); - if (carryFlag) { - REG_PC = REG_WZ; - break; - } - REG_PC = REG_PC + 2; - break; - } - case 0xDB: - { /* IN A,(n) */ - REG_W = regA; - REG_Z = Z80opsImpl->peek8(REG_PC); - //REG_WZ = (regA << 8) | Z80opsImpl->peek8(REG_PC); - REG_PC++; - regA = Z80opsImpl->inPort(REG_WZ); - REG_WZ++; - break; - } - case 0xDC: - { /* CALL C,nn */ - REG_WZ = Z80opsImpl->peek16(REG_PC); - if (carryFlag) { - Z80opsImpl->addressOnBus(REG_PC + 1, 1); - push(REG_PC + 2); - REG_PC = REG_WZ; - break; - } - REG_PC = REG_PC + 2; - break; - } - case 0xDD: - { /* Subconjunto de instrucciones */ - opCode = Z80opsImpl->fetchOpcode(REG_PC++); - regR++; - decodeDDFD(opCode, regIX); - break; - } - case 0xDE: - { /* SBC A,n */ - sbc(Z80opsImpl->peek8(REG_PC)); - REG_PC++; - break; - } - case 0xDF: - { /* RST 18H */ - Z80opsImpl->addressOnBus(getPairIR().word, 1); - push(REG_PC); - REG_PC = REG_WZ = 0x18; - break; - } - case 0xE0: /* RET PO */ - Z80opsImpl->addressOnBus(getPairIR().word, 1); - if ((sz5h3pnFlags & PARITY_MASK) == 0) { - REG_PC = REG_WZ = pop(); - } - break; - case 0xE1: /* POP HL */ - REG_HL = pop(); - break; - case 0xE2: /* JP PO,nn */ - REG_WZ = Z80opsImpl->peek16(REG_PC); - if ((sz5h3pnFlags & PARITY_MASK) == 0) { - REG_PC = REG_WZ; - break; - } - REG_PC = REG_PC + 2; - break; - case 0xE3: - { /* EX (SP),HL */ - // Instrucción de ejecución sutil. - RegisterPair work = regHL; - REG_HL = Z80opsImpl->peek16(REG_SP); - Z80opsImpl->addressOnBus(REG_SP + 1, 1); - // No se usa poke16 porque el Z80 escribe los bytes AL REVES - Z80opsImpl->poke8(REG_SP + 1, work.byte8.hi); - Z80opsImpl->poke8(REG_SP, work.byte8.lo); - Z80opsImpl->addressOnBus(REG_SP, 2); - REG_WZ = REG_HL; - break; - } - case 0xE4: /* CALL PO,nn */ - REG_WZ = Z80opsImpl->peek16(REG_PC); - if ((sz5h3pnFlags & PARITY_MASK) == 0) { - Z80opsImpl->addressOnBus(REG_PC + 1, 1); - push(REG_PC + 2); - REG_PC = REG_WZ; - break; - } - REG_PC = REG_PC + 2; - break; - case 0xE5: /* PUSH HL */ - Z80opsImpl->addressOnBus(getPairIR().word, 1); - push(REG_HL); - break; - case 0xE6: /* AND n */ - and_(Z80opsImpl->peek8(REG_PC)); - REG_PC++; - break; - case 0xE7: /* RST 20H */ - Z80opsImpl->addressOnBus(getPairIR().word, 1); - push(REG_PC); - REG_PC = REG_WZ = 0x20; - break; - case 0xE8: /* RET PE */ - Z80opsImpl->addressOnBus(getPairIR().word, 1); - if ((sz5h3pnFlags & PARITY_MASK) != 0) { - REG_PC = REG_WZ = pop(); - } - break; - case 0xE9: /* JP (HL) */ - REG_PC = REG_HL; - break; - case 0xEA: /* JP PE,nn */ - REG_WZ = Z80opsImpl->peek16(REG_PC); - if ((sz5h3pnFlags & PARITY_MASK) != 0) { - REG_PC = REG_WZ; - break; - } - REG_PC = REG_PC + 2; - break; - case 0xEB: - { /* EX DE,HL */ - uint16_t tmp = REG_HL; - REG_HL = REG_DE; - REG_DE = tmp; - break; - } - case 0xEC: /* CALL PE,nn */ - REG_WZ = Z80opsImpl->peek16(REG_PC); - if ((sz5h3pnFlags & PARITY_MASK) != 0) { - Z80opsImpl->addressOnBus(REG_PC + 1, 1); - push(REG_PC + 2); - REG_PC = REG_WZ; - break; - } - REG_PC = REG_PC + 2; - break; - case 0xED: /*Subconjunto de instrucciones*/ - opCode = Z80opsImpl->fetchOpcode(REG_PC++); - regR++; - decodeED(opCode); - break; - case 0xEE: /* XOR n */ - xor_(Z80opsImpl->peek8(REG_PC)); - REG_PC++; - break; - case 0xEF: /* RST 28H */ - Z80opsImpl->addressOnBus(getPairIR().word, 1); - push(REG_PC); - REG_PC = REG_WZ = 0x28; - break; - case 0xF0: /* RET P */ - Z80opsImpl->addressOnBus(getPairIR().word, 1); - if (sz5h3pnFlags < SIGN_MASK) { - REG_PC = REG_WZ = pop(); - } - break; - case 0xF1: /* POP AF */ - setRegAF(pop()); - break; - case 0xF2: /* JP P,nn */ - REG_WZ = Z80opsImpl->peek16(REG_PC); - if (sz5h3pnFlags < SIGN_MASK) { - REG_PC = REG_WZ; - break; - } - REG_PC = REG_PC + 2; - break; - case 0xF3: /* DI */ - ffIFF1 = ffIFF2 = false; - break; - case 0xF4: /* CALL P,nn */ - REG_WZ = Z80opsImpl->peek16(REG_PC); - if (sz5h3pnFlags < SIGN_MASK) { - Z80opsImpl->addressOnBus(REG_PC + 1, 1); - push(REG_PC + 2); - REG_PC = REG_WZ; - break; - } - REG_PC = REG_PC + 2; - break; - case 0xF5: /* PUSH AF */ - Z80opsImpl->addressOnBus(getPairIR().word, 1); - push(getRegAF()); - break; - case 0xF6: /* OR n */ - or_(Z80opsImpl->peek8(REG_PC)); - REG_PC++; - break; - case 0xF7: /* RST 30H */ - Z80opsImpl->addressOnBus(getPairIR().word, 1); - push(REG_PC); - REG_PC = REG_WZ = 0x30; - break; - case 0xF8: /* RET M */ - Z80opsImpl->addressOnBus(getPairIR().word, 1); - if (sz5h3pnFlags > 0x7f) { - REG_PC = REG_WZ = pop(); - } - break; - case 0xF9: /* LD SP,HL */ - Z80opsImpl->addressOnBus(getPairIR().word, 2); - REG_SP = REG_HL; - break; - case 0xFA: /* JP M,nn */ - REG_WZ = Z80opsImpl->peek16(REG_PC); - if (sz5h3pnFlags > 0x7f) { - REG_PC = REG_WZ; - break; - } - REG_PC = REG_PC + 2; - break; - case 0xFB: /* EI */ - ffIFF1 = ffIFF2 = true; - pendingEI = true; - break; - case 0xFC: /* CALL M,nn */ - REG_WZ = Z80opsImpl->peek16(REG_PC); - if (sz5h3pnFlags > 0x7f) { - Z80opsImpl->addressOnBus(REG_PC + 1, 1); - push(REG_PC + 2); - REG_PC = REG_WZ; - break; - } - REG_PC = REG_PC + 2; - break; - case 0xFD: /* Subconjunto de instrucciones */ - opCode = Z80opsImpl->fetchOpcode(REG_PC++); - regR++; - decodeDDFD(opCode, regIY); - break; - case 0xFE: /* CP n */ - cp(Z80opsImpl->peek8(REG_PC)); - REG_PC++; - break; - case 0xFF: /* RST 38H */ - Z80opsImpl->addressOnBus(getPairIR().word, 1); - push(REG_PC); - REG_PC = REG_WZ = 0x38; - } /* del switch( codigo ) */ -} - -//Subconjunto de instrucciones 0xCB - -void Z80::decodeCB() { - uint8_t opCode = Z80opsImpl->fetchOpcode(REG_PC++); - regR++; - - switch (opCode) { - case 0x00: - { /* RLC B */ - rlc(REG_B); - break; - } - case 0x01: - { /* RLC C */ - rlc(REG_C); - break; - } - case 0x02: - { /* RLC D */ - rlc(REG_D); - break; - } - case 0x03: - { /* RLC E */ - rlc(REG_E); - break; - } - case 0x04: - { /* RLC H */ - rlc(REG_H); - break; - } - case 0x05: - { /* RLC L */ - rlc(REG_L); - break; - } - case 0x06: - { /* RLC (HL) */ - uint8_t work8 = Z80opsImpl->peek8(REG_HL); - rlc(work8); - Z80opsImpl->addressOnBus(REG_HL, 1); - Z80opsImpl->poke8(REG_HL, work8); - break; - } - case 0x07: - { /* RLC A */ - rlc(regA); - break; - } - case 0x08: - { /* RRC B */ - rrc(REG_B); - break; - } - case 0x09: - { /* RRC C */ - rrc(REG_C); - break; - } - case 0x0A: - { /* RRC D */ - rrc(REG_D); - break; - } - case 0x0B: - { /* RRC E */ - rrc(REG_E); - break; - } - case 0x0C: - { /* RRC H */ - rrc(REG_H); - break; - } - case 0x0D: - { /* RRC L */ - rrc(REG_L); - break; - } - case 0x0E: - { /* RRC (HL) */ - uint8_t work8 = Z80opsImpl->peek8(REG_HL); - rrc(work8); - Z80opsImpl->addressOnBus(REG_HL, 1); - Z80opsImpl->poke8(REG_HL, work8); - break; - } - case 0x0F: - { /* RRC A */ - rrc(regA); - break; - } - case 0x10: - { /* RL B */ - rl(REG_B); - break; - } - case 0x11: - { /* RL C */ - rl(REG_C); - break; - } - case 0x12: - { /* RL D */ - rl(REG_D); - break; - } - case 0x13: - { /* RL E */ - rl(REG_E); - break; - } - case 0x14: - { /* RL H */ - rl(REG_H); - break; - } - case 0x15: - { /* RL L */ - rl(REG_L); - break; - } - case 0x16: - { /* RL (HL) */ - uint8_t work8 = Z80opsImpl->peek8(REG_HL); - rl(work8); - Z80opsImpl->addressOnBus(REG_HL, 1); - Z80opsImpl->poke8(REG_HL, work8); - break; - } - case 0x17: - { /* RL A */ - rl(regA); - break; - } - case 0x18: - { /* RR B */ - rr(REG_B); - break; - } - case 0x19: - { /* RR C */ - rr(REG_C); - break; - } - case 0x1A: - { /* RR D */ - rr(REG_D); - break; - } - case 0x1B: - { /* RR E */ - rr(REG_E); - break; - } - case 0x1C: - { /*RR H*/ - rr(REG_H); - break; - } - case 0x1D: - { /* RR L */ - rr(REG_L); - break; - } - case 0x1E: - { /* RR (HL) */ - uint8_t work8 = Z80opsImpl->peek8(REG_HL); - rr(work8); - Z80opsImpl->addressOnBus(REG_HL, 1); - Z80opsImpl->poke8(REG_HL, work8); - break; - } - case 0x1F: - { /* RR A */ - rr(regA); - break; - } - case 0x20: - { /* SLA B */ - sla(REG_B); - break; - } - case 0x21: - { /* SLA C */ - sla(REG_C); - break; - } - case 0x22: - { /* SLA D */ - sla(REG_D); - break; - } - case 0x23: - { /* SLA E */ - sla(REG_E); - break; - } - case 0x24: - { /* SLA H */ - sla(REG_H); - break; - } - case 0x25: - { /* SLA L */ - sla(REG_L); - break; - } - case 0x26: - { /* SLA (HL) */ - uint8_t work8 = Z80opsImpl->peek8(REG_HL); - sla(work8); - Z80opsImpl->addressOnBus(REG_HL, 1); - Z80opsImpl->poke8(REG_HL, work8); - break; - } - case 0x27: - { /* SLA A */ - sla(regA); - break; - } - case 0x28: - { /* SRA B */ - sra(REG_B); - break; - } - case 0x29: - { /* SRA C */ - sra(REG_C); - break; - } - case 0x2A: - { /* SRA D */ - sra(REG_D); - break; - } - case 0x2B: - { /* SRA E */ - sra(REG_E); - break; - } - case 0x2C: - { /* SRA H */ - sra(REG_H); - break; - } - case 0x2D: - { /* SRA L */ - sra(REG_L); - break; - } - case 0x2E: - { /* SRA (HL) */ - uint8_t work8 = Z80opsImpl->peek8(REG_HL); - sra(work8); - Z80opsImpl->addressOnBus(REG_HL, 1); - Z80opsImpl->poke8(REG_HL, work8); - break; - } - case 0x2F: - { /* SRA A */ - sra(regA); - break; - } - case 0x30: - { /* SLL B */ - sll(REG_B); - break; - } - case 0x31: - { /* SLL C */ - sll(REG_C); - break; - } - case 0x32: - { /* SLL D */ - sll(REG_D); - break; - } - case 0x33: - { /* SLL E */ - sll(REG_E); - break; - } - case 0x34: - { /* SLL H */ - sll(REG_H); - break; - } - case 0x35: - { /* SLL L */ - sll(REG_L); - break; - } - case 0x36: - { /* SLL (HL) */ - uint8_t work8 = Z80opsImpl->peek8(REG_HL); - sll(work8); - Z80opsImpl->addressOnBus(REG_HL, 1); - Z80opsImpl->poke8(REG_HL, work8); - break; - } - case 0x37: - { /* SLL A */ - sll(regA); - break; - } - case 0x38: - { /* SRL B */ - srl(REG_B); - break; - } - case 0x39: - { /* SRL C */ - srl(REG_C); - break; - } - case 0x3A: - { /* SRL D */ - srl(REG_D); - break; - } - case 0x3B: - { /* SRL E */ - srl(REG_E); - break; - } - case 0x3C: - { /* SRL H */ - srl(REG_H); - break; - } - case 0x3D: - { /* SRL L */ - srl(REG_L); - break; - } - case 0x3E: - { /* SRL (HL) */ - uint8_t work8 = Z80opsImpl->peek8(REG_HL); - srl(work8); - Z80opsImpl->addressOnBus(REG_HL, 1); - Z80opsImpl->poke8(REG_HL, work8); - break; - } - case 0x3F: - { /* SRL A */ - srl(regA); - break; - } - case 0x40: - { /* BIT 0,B */ - bitTest(0x01, REG_B); - break; - } - case 0x41: - { /* BIT 0,C */ - bitTest(0x01, REG_C); - break; - } - case 0x42: - { /* BIT 0,D */ - bitTest(0x01, REG_D); - break; - } - case 0x43: - { /* BIT 0,E */ - bitTest(0x01, REG_E); - break; - } - case 0x44: - { /* BIT 0,H */ - bitTest(0x01, REG_H); - break; - } - case 0x45: - { /* BIT 0,L */ - bitTest(0x01, REG_L); - break; - } - case 0x46: - { /* BIT 0,(HL) */ - bitTest(0x01, Z80opsImpl->peek8(REG_HL)); - sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZHP_MASK) | (REG_W & FLAG_53_MASK); - Z80opsImpl->addressOnBus(REG_HL, 1); - break; - } - case 0x47: - { /* BIT 0,A */ - bitTest(0x01, regA); - break; - } - case 0x48: - { /* BIT 1,B */ - bitTest(0x02, REG_B); - break; - } - case 0x49: - { /* BIT 1,C */ - bitTest(0x02, REG_C); - break; - } - case 0x4A: - { /* BIT 1,D */ - bitTest(0x02, REG_D); - break; - } - case 0x4B: - { /* BIT 1,E */ - bitTest(0x02, REG_E); - break; - } - case 0x4C: - { /* BIT 1,H */ - bitTest(0x02, REG_H); - break; - } - case 0x4D: - { /* BIT 1,L */ - bitTest(0x02, REG_L); - break; - } - case 0x4E: - { /* BIT 1,(HL) */ - bitTest(0x02, Z80opsImpl->peek8(REG_HL)); - sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZHP_MASK) | (REG_W & FLAG_53_MASK); - Z80opsImpl->addressOnBus(REG_HL, 1); - break; - } - case 0x4F: - { /* BIT 1,A */ - bitTest(0x02, regA); - break; - } - case 0x50: - { /* BIT 2,B */ - bitTest(0x04, REG_B); - break; - } - case 0x51: - { /* BIT 2,C */ - bitTest(0x04, REG_C); - break; - } - case 0x52: - { /* BIT 2,D */ - bitTest(0x04, REG_D); - break; - } - case 0x53: - { /* BIT 2,E */ - bitTest(0x04, REG_E); - break; - } - case 0x54: - { /* BIT 2,H */ - bitTest(0x04, REG_H); - break; - } - case 0x55: - { /* BIT 2,L */ - bitTest(0x04, REG_L); - break; - } - case 0x56: - { /* BIT 2,(HL) */ - bitTest(0x04, Z80opsImpl->peek8(REG_HL)); - sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZHP_MASK) | (REG_W & FLAG_53_MASK); - Z80opsImpl->addressOnBus(REG_HL, 1); - break; - } - case 0x57: - { /* BIT 2,A */ - bitTest(0x04, regA); - break; - } - case 0x58: - { /* BIT 3,B */ - bitTest(0x08, REG_B); - break; - } - case 0x59: - { /* BIT 3,C */ - bitTest(0x08, REG_C); - break; - } - case 0x5A: - { /* BIT 3,D */ - bitTest(0x08, REG_D); - break; - } - case 0x5B: - { /* BIT 3,E */ - bitTest(0x08, REG_E); - break; - } - case 0x5C: - { /* BIT 3,H */ - bitTest(0x08, REG_H); - break; - } - case 0x5D: - { /* BIT 3,L */ - bitTest(0x08, REG_L); - break; - } - case 0x5E: - { /* BIT 3,(HL) */ - bitTest(0x08, Z80opsImpl->peek8(REG_HL)); - sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZHP_MASK) | (REG_W & FLAG_53_MASK); - Z80opsImpl->addressOnBus(REG_HL, 1); - break; - } - case 0x5F: - { /* BIT 3,A */ - bitTest(0x08, regA); - break; - } - case 0x60: - { /* BIT 4,B */ - bitTest(0x10, REG_B); - break; - } - case 0x61: - { /* BIT 4,C */ - bitTest(0x10, REG_C); - break; - } - case 0x62: - { /* BIT 4,D */ - bitTest(0x10, REG_D); - break; - } - case 0x63: - { /* BIT 4,E */ - bitTest(0x10, REG_E); - break; - } - case 0x64: - { /* BIT 4,H */ - bitTest(0x10, REG_H); - break; - } - case 0x65: - { /* BIT 4,L */ - bitTest(0x10, REG_L); - break; - } - case 0x66: - { /* BIT 4,(HL) */ - bitTest(0x10, Z80opsImpl->peek8(REG_HL)); - sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZHP_MASK) | (REG_W & FLAG_53_MASK); - Z80opsImpl->addressOnBus(REG_HL, 1); - break; - } - case 0x67: - { /* BIT 4,A */ - bitTest(0x10, regA); - break; - } - case 0x68: - { /* BIT 5,B */ - bitTest(0x20, REG_B); - break; - } - case 0x69: - { /* BIT 5,C */ - bitTest(0x20, REG_C); - break; - } - case 0x6A: - { /* BIT 5,D */ - bitTest(0x20, REG_D); - break; - } - case 0x6B: - { /* BIT 5,E */ - bitTest(0x20, REG_E); - break; - } - case 0x6C: - { /* BIT 5,H */ - bitTest(0x20, REG_H); - break; - } - case 0x6D: - { /* BIT 5,L */ - bitTest(0x20, REG_L); - break; - } - case 0x6E: - { /* BIT 5,(HL) */ - bitTest(0x20, Z80opsImpl->peek8(REG_HL)); - sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZHP_MASK) | (REG_W & FLAG_53_MASK); - Z80opsImpl->addressOnBus(REG_HL, 1); - break; - } - case 0x6F: - { /* BIT 5,A */ - bitTest(0x20, regA); - break; - } - case 0x70: - { /* BIT 6,B */ - bitTest(0x40, REG_B); - break; - } - case 0x71: - { /* BIT 6,C */ - bitTest(0x40, REG_C); - break; - } - case 0x72: - { /* BIT 6,D */ - bitTest(0x40, REG_D); - break; - } - case 0x73: - { /* BIT 6,E */ - bitTest(0x40, REG_E); - break; - } - case 0x74: - { /* BIT 6,H */ - bitTest(0x40, REG_H); - break; - } - case 0x75: - { /* BIT 6,L */ - bitTest(0x40, REG_L); - break; - } - case 0x76: - { /* BIT 6,(HL) */ - bitTest(0x40, Z80opsImpl->peek8(REG_HL)); - sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZHP_MASK) | (REG_W & FLAG_53_MASK); - Z80opsImpl->addressOnBus(REG_HL, 1); - break; - } - case 0x77: - { /* BIT 6,A */ - bitTest(0x40, regA); - break; - } - case 0x78: - { /* BIT 7,B */ - bitTest(0x80, REG_B); - break; - } - case 0x79: - { /* BIT 7,C */ - bitTest(0x80, REG_C); - break; - } - case 0x7A: - { /* BIT 7,D */ - bitTest(0x80, REG_D); - break; - } - case 0x7B: - { /* BIT 7,E */ - bitTest(0x80, REG_E); - break; - } - case 0x7C: - { /* BIT 7,H */ - bitTest(0x80, REG_H); - break; - } - case 0x7D: - { /* BIT 7,L */ - bitTest(0x80, REG_L); - break; - } - case 0x7E: - { /* BIT 7,(HL) */ - bitTest(0x80, Z80opsImpl->peek8(REG_HL)); - sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZHP_MASK) | (REG_W & FLAG_53_MASK); - Z80opsImpl->addressOnBus(REG_HL, 1); - break; - } - case 0x7F: - { /* BIT 7,A */ - bitTest(0x80, regA); - break; - } - case 0x80: - { /* RES 0,B */ - REG_B &= 0xFE; - break; - } - case 0x81: - { /* RES 0,C */ - REG_C &= 0xFE; - break; - } - case 0x82: - { /* RES 0,D */ - REG_D &= 0xFE; - break; - } - case 0x83: - { /* RES 0,E */ - REG_E &= 0xFE; - break; - } - case 0x84: - { /* RES 0,H */ - REG_H &= 0xFE; - break; - } - case 0x85: - { /* RES 0,L */ - REG_L &= 0xFE; - break; - } - case 0x86: - { /* RES 0,(HL) */ - uint8_t work8 = Z80opsImpl->peek8(REG_HL) & 0xFE; - Z80opsImpl->addressOnBus(REG_HL, 1); - Z80opsImpl->poke8(REG_HL, work8); - break; - } - case 0x87: - { /* RES 0,A */ - regA &= 0xFE; - break; - } - case 0x88: - { /* RES 1,B */ - REG_B &= 0xFD; - break; - } - case 0x89: - { /* RES 1,C */ - REG_C &= 0xFD; - break; - } - case 0x8A: - { /* RES 1,D */ - REG_D &= 0xFD; - break; - } - case 0x8B: - { /* RES 1,E */ - REG_E &= 0xFD; - break; - } - case 0x8C: - { /* RES 1,H */ - REG_H &= 0xFD; - break; - } - case 0x8D: - { /* RES 1,L */ - REG_L &= 0xFD; - break; - } - case 0x8E: - { /* RES 1,(HL) */ - uint8_t work8 = Z80opsImpl->peek8(REG_HL) & 0xFD; - Z80opsImpl->addressOnBus(REG_HL, 1); - Z80opsImpl->poke8(REG_HL, work8); - break; - } - case 0x8F: - { /* RES 1,A */ - regA &= 0xFD; - break; - } - case 0x90: - { /* RES 2,B */ - REG_B &= 0xFB; - break; - } - case 0x91: - { /* RES 2,C */ - REG_C &= 0xFB; - break; - } - case 0x92: - { /* RES 2,D */ - REG_D &= 0xFB; - break; - } - case 0x93: - { /* RES 2,E */ - REG_E &= 0xFB; - break; - } - case 0x94: - { /* RES 2,H */ - REG_H &= 0xFB; - break; - } - case 0x95: - { /* RES 2,L */ - REG_L &= 0xFB; - break; - } - case 0x96: - { /* RES 2,(HL) */ - uint8_t work8 = Z80opsImpl->peek8(REG_HL) & 0xFB; - Z80opsImpl->addressOnBus(REG_HL, 1); - Z80opsImpl->poke8(REG_HL, work8); - break; - } - case 0x97: - { /* RES 2,A */ - regA &= 0xFB; - break; - } - case 0x98: - { /* RES 3,B */ - REG_B &= 0xF7; - break; - } - case 0x99: - { /* RES 3,C */ - REG_C &= 0xF7; - break; - } - case 0x9A: - { /* RES 3,D */ - REG_D &= 0xF7; - break; - } - case 0x9B: - { /* RES 3,E */ - REG_E &= 0xF7; - break; - } - case 0x9C: - { /* RES 3,H */ - REG_H &= 0xF7; - break; - } - case 0x9D: - { /* RES 3,L */ - REG_L &= 0xF7; - break; - } - case 0x9E: - { /* RES 3,(HL) */ - uint8_t work8 = Z80opsImpl->peek8(REG_HL) & 0xF7; - Z80opsImpl->addressOnBus(REG_HL, 1); - Z80opsImpl->poke8(REG_HL, work8); - break; - } - case 0x9F: - { /* RES 3,A */ - regA &= 0xF7; - break; - } - case 0xA0: - { /* RES 4,B */ - REG_B &= 0xEF; - break; - } - case 0xA1: - { /* RES 4,C */ - REG_C &= 0xEF; - break; - } - case 0xA2: - { /* RES 4,D */ - REG_D &= 0xEF; - break; - } - case 0xA3: - { /* RES 4,E */ - REG_E &= 0xEF; - break; - } - case 0xA4: - { /* RES 4,H */ - REG_H &= 0xEF; - break; - } - case 0xA5: - { /* RES 4,L */ - REG_L &= 0xEF; - break; - } - case 0xA6: - { /* RES 4,(HL) */ - uint8_t work8 = Z80opsImpl->peek8(REG_HL) & 0xEF; - Z80opsImpl->addressOnBus(REG_HL, 1); - Z80opsImpl->poke8(REG_HL, work8); - break; - } - case 0xA7: - { /* RES 4,A */ - regA &= 0xEF; - break; - } - case 0xA8: - { /* RES 5,B */ - REG_B &= 0xDF; - break; - } - case 0xA9: - { /* RES 5,C */ - REG_C &= 0xDF; - break; - } - case 0xAA: - { /* RES 5,D */ - REG_D &= 0xDF; - break; - } - case 0xAB: - { /* RES 5,E */ - REG_E &= 0xDF; - break; - } - case 0xAC: - { /* RES 5,H */ - REG_H &= 0xDF; - break; - } - case 0xAD: - { /* RES 5,L */ - REG_L &= 0xDF; - break; - } - case 0xAE: - { /* RES 5,(HL) */ - uint8_t work8 = Z80opsImpl->peek8(REG_HL) & 0xDF; - Z80opsImpl->addressOnBus(REG_HL, 1); - Z80opsImpl->poke8(REG_HL, work8); - break; - } - case 0xAF: - { /* RES 5,A */ - regA &= 0xDF; - break; - } - case 0xB0: - { /* RES 6,B */ - REG_B &= 0xBF; - break; - } - case 0xB1: - { /* RES 6,C */ - REG_C &= 0xBF; - break; - } - case 0xB2: - { /* RES 6,D */ - REG_D &= 0xBF; - break; - } - case 0xB3: - { /* RES 6,E */ - REG_E &= 0xBF; - break; - } - case 0xB4: - { /* RES 6,H */ - REG_H &= 0xBF; - break; - } - case 0xB5: - { /* RES 6,L */ - REG_L &= 0xBF; - break; - } - case 0xB6: - { /* RES 6,(HL) */ - uint8_t work8 = Z80opsImpl->peek8(REG_HL) & 0xBF; - Z80opsImpl->addressOnBus(REG_HL, 1); - Z80opsImpl->poke8(REG_HL, work8); - break; - } - case 0xB7: - { /* RES 6,A */ - regA &= 0xBF; - break; - } - case 0xB8: - { /* RES 7,B */ - REG_B &= 0x7F; - break; - } - case 0xB9: - { /* RES 7,C */ - REG_C &= 0x7F; - break; - } - case 0xBA: - { /* RES 7,D */ - REG_D &= 0x7F; - break; - } - case 0xBB: - { /* RES 7,E */ - REG_E &= 0x7F; - break; - } - case 0xBC: - { /* RES 7,H */ - REG_H &= 0x7F; - break; - } - case 0xBD: - { /* RES 7,L */ - REG_L &= 0x7F; - break; - } - case 0xBE: - { /* RES 7,(HL) */ - uint8_t work8 = Z80opsImpl->peek8(REG_HL) & 0x7F; - Z80opsImpl->addressOnBus(REG_HL, 1); - Z80opsImpl->poke8(REG_HL, work8); - break; - } - case 0xBF: - { /* RES 7,A */ - regA &= 0x7F; - break; - } - case 0xC0: - { /* SET 0,B */ - REG_B |= 0x01; - break; - } - case 0xC1: - { /* SET 0,C */ - REG_C |= 0x01; - break; - } - case 0xC2: - { /* SET 0,D */ - REG_D |= 0x01; - break; - } - case 0xC3: - { /* SET 0,E */ - REG_E |= 0x01; - break; - } - case 0xC4: - { /* SET 0,H */ - REG_H |= 0x01; - break; - } - case 0xC5: - { /* SET 0,L */ - REG_L |= 0x01; - break; - } - case 0xC6: - { /* SET 0,(HL) */ - uint8_t work8 = Z80opsImpl->peek8(REG_HL) | 0x01; - Z80opsImpl->addressOnBus(REG_HL, 1); - Z80opsImpl->poke8(REG_HL, work8); - break; - } - case 0xC7: - { /* SET 0,A */ - regA |= 0x01; - break; - } - case 0xC8: - { /* SET 1,B */ - REG_B |= 0x02; - break; - } - case 0xC9: - { /* SET 1,C */ - REG_C |= 0x02; - break; - } - case 0xCA: - { /* SET 1,D */ - REG_D |= 0x02; - break; - } - case 0xCB: - { /* SET 1,E */ - REG_E |= 0x02; - break; - } - case 0xCC: - { /* SET 1,H */ - REG_H |= 0x02; - break; - } - case 0xCD: - { /* SET 1,L */ - REG_L |= 0x02; - break; - } - case 0xCE: - { /* SET 1,(HL) */ - uint8_t work8 = Z80opsImpl->peek8(REG_HL) | 0x02; - Z80opsImpl->addressOnBus(REG_HL, 1); - Z80opsImpl->poke8(REG_HL, work8); - break; - } - case 0xCF: - { /* SET 1,A */ - regA |= 0x02; - break; - } - case 0xD0: - { /* SET 2,B */ - REG_B |= 0x04; - break; - } - case 0xD1: - { /* SET 2,C */ - REG_C |= 0x04; - break; - } - case 0xD2: - { /* SET 2,D */ - REG_D |= 0x04; - break; - } - case 0xD3: - { /* SET 2,E */ - REG_E |= 0x04; - break; - } - case 0xD4: - { /* SET 2,H */ - REG_H |= 0x04; - break; - } - case 0xD5: - { /* SET 2,L */ - REG_L |= 0x04; - break; - } - case 0xD6: - { /* SET 2,(HL) */ - uint8_t work8 = Z80opsImpl->peek8(REG_HL) | 0x04; - Z80opsImpl->addressOnBus(REG_HL, 1); - Z80opsImpl->poke8(REG_HL, work8); - break; - } - case 0xD7: - { /* SET 2,A */ - regA |= 0x04; - break; - } - case 0xD8: - { /* SET 3,B */ - REG_B |= 0x08; - break; - } - case 0xD9: - { /* SET 3,C */ - REG_C |= 0x08; - break; - } - case 0xDA: - { /* SET 3,D */ - REG_D |= 0x08; - break; - } - case 0xDB: - { /* SET 3,E */ - REG_E |= 0x08; - break; - } - case 0xDC: - { /* SET 3,H */ - REG_H |= 0x08; - break; - } - case 0xDD: - { /* SET 3,L */ - REG_L |= 0x08; - break; - } - case 0xDE: - { /* SET 3,(HL) */ - uint8_t work8 = Z80opsImpl->peek8(REG_HL) | 0x08; - Z80opsImpl->addressOnBus(REG_HL, 1); - Z80opsImpl->poke8(REG_HL, work8); - break; - } - case 0xDF: - { /* SET 3,A */ - regA |= 0x08; - break; - } - case 0xE0: - { /* SET 4,B */ - REG_B |= 0x10; - break; - } - case 0xE1: - { /* SET 4,C */ - REG_C |= 0x10; - break; - } - case 0xE2: - { /* SET 4,D */ - REG_D |= 0x10; - break; - } - case 0xE3: - { /* SET 4,E */ - REG_E |= 0x10; - break; - } - case 0xE4: - { /* SET 4,H */ - REG_H |= 0x10; - break; - } - case 0xE5: - { /* SET 4,L */ - REG_L |= 0x10; - break; - } - case 0xE6: - { /* SET 4,(HL) */ - uint8_t work8 = Z80opsImpl->peek8(REG_HL) | 0x10; - Z80opsImpl->addressOnBus(REG_HL, 1); - Z80opsImpl->poke8(REG_HL, work8); - break; - } - case 0xE7: - { /* SET 4,A */ - regA |= 0x10; - break; - } - case 0xE8: - { /* SET 5,B */ - REG_B |= 0x20; - break; - } - case 0xE9: - { /* SET 5,C */ - REG_C |= 0x20; - break; - } - case 0xEA: - { /* SET 5,D */ - REG_D |= 0x20; - break; - } - case 0xEB: - { /* SET 5,E */ - REG_E |= 0x20; - break; - } - case 0xEC: - { /* SET 5,H */ - REG_H |= 0x20; - break; - } - case 0xED: - { /* SET 5,L */ - REG_L |= 0x20; - break; - } - case 0xEE: - { /* SET 5,(HL) */ - uint8_t work8 = Z80opsImpl->peek8(REG_HL) | 0x20; - Z80opsImpl->addressOnBus(REG_HL, 1); - Z80opsImpl->poke8(REG_HL, work8); - break; - } - case 0xEF: - { /* SET 5,A */ - regA |= 0x20; - break; - } - case 0xF0: - { /* SET 6,B */ - REG_B |= 0x40; - break; - } - case 0xF1: - { /* SET 6,C */ - REG_C |= 0x40; - break; - } - case 0xF2: - { /* SET 6,D */ - REG_D |= 0x40; - break; - } - case 0xF3: - { /* SET 6,E */ - REG_E |= 0x40; - break; - } - case 0xF4: - { /* SET 6,H */ - REG_H |= 0x40; - break; - } - case 0xF5: - { /* SET 6,L */ - REG_L |= 0x40; - break; - } - case 0xF6: - { /* SET 6,(HL) */ - uint8_t work8 = Z80opsImpl->peek8(REG_HL) | 0x40; - Z80opsImpl->addressOnBus(REG_HL, 1); - Z80opsImpl->poke8(REG_HL, work8); - break; - } - case 0xF7: - { /* SET 6,A */ - regA |= 0x40; - break; - } - case 0xF8: - { /* SET 7,B */ - REG_B |= 0x80; - break; - } - case 0xF9: - { /* SET 7,C */ - REG_C |= 0x80; - break; - } - case 0xFA: - { /* SET 7,D */ - REG_D |= 0x80; - break; - } - case 0xFB: - { /* SET 7,E */ - REG_E |= 0x80; - break; - } - case 0xFC: - { /* SET 7,H */ - REG_H |= 0x80; - break; - } - case 0xFD: - { /* SET 7,L */ - REG_L |= 0x80; - break; - } - case 0xFE: - { /* SET 7,(HL) */ - uint8_t work8 = Z80opsImpl->peek8(REG_HL) | 0x80; - Z80opsImpl->addressOnBus(REG_HL, 1); - Z80opsImpl->poke8(REG_HL, work8); - break; - } - case 0xFF: - { /* SET 7,A */ - regA |= 0x80; - break; - } - default: - { - break; - } - } -} - -//Subconjunto de instrucciones 0xDD / 0xFD -/* - * Hay que tener en cuenta el manejo de secuencias códigos DD/FD que no - * hacen nada. Según el apartado 3.7 del documento - * [http://www.myquest.nl/z80undocumented/z80-documented-v0.91.pdf] - * secuencias de códigos como FD DD 00 21 00 10 NOP NOP NOP LD HL,1000h - * activan IY con el primer FD, IX con el segundo DD y vuelven al - * registro HL con el código NOP. Es decir, si detrás del código DD/FD no - * viene una instrucción que maneje el registro HL, el código DD/FD - * "se olvida" y hay que procesar la instrucción como si nunca se - * hubiera visto el prefijo (salvo por los 4 t-estados que ha costado). - * Naturalmente, en una serie repetida de DDFD no hay que comprobar las - * interrupciones entre cada prefijo. - */ -void Z80::decodeDDFD(uint8_t opCode, RegisterPair& regIXY) { - switch (opCode) { - case 0x09: - { /* ADD IX,BC */ - Z80opsImpl->addressOnBus(getPairIR().word, 7); - add16(regIXY, REG_BC); - break; - } - case 0x19: - { /* ADD IX,DE */ - Z80opsImpl->addressOnBus(getPairIR().word, 7); - add16(regIXY, REG_DE); - break; - } - case 0x21: - { /* LD IX,nn */ - regIXY.word = Z80opsImpl->peek16(REG_PC); - REG_PC = REG_PC + 2; - break; - } - case 0x22: - { /* LD (nn),IX */ - REG_WZ = Z80opsImpl->peek16(REG_PC); - Z80opsImpl->poke16(REG_WZ++, regIXY); - REG_PC = REG_PC + 2; - break; - } - case 0x23: - { /* INC IX */ - Z80opsImpl->addressOnBus(getPairIR().word, 2); - regIXY.word++; - break; - } - case 0x24: - { /* INC IXh */ - inc8(regIXY.byte8.hi); - break; - } - case 0x25: - { /* DEC IXh */ - dec8(regIXY.byte8.hi); - break; - } - case 0x26: - { /* LD IXh,n */ - regIXY.byte8.hi = Z80opsImpl->peek8(REG_PC); - REG_PC++; - break; - } - case 0x29: - { /* ADD IX,IX */ - Z80opsImpl->addressOnBus(getPairIR().word, 7); - add16(regIXY, regIXY.word); - break; - } - case 0x2A: - { /* LD IX,(nn) */ - REG_WZ = Z80opsImpl->peek16(REG_PC); - regIXY.word = Z80opsImpl->peek16(REG_WZ++); - REG_PC = REG_PC + 2; - break; - } - case 0x2B: - { /* DEC IX */ - Z80opsImpl->addressOnBus(getPairIR().word, 2); - regIXY.word--; - break; - } - case 0x2C: - { /* INC IXl */ - inc8(regIXY.byte8.lo); - break; - } - case 0x2D: - { /* DEC IXl */ - dec8(regIXY.byte8.lo); - break; - } - case 0x2E: - { /* LD IXl,n */ - regIXY.byte8.lo = Z80opsImpl->peek8(REG_PC); - REG_PC++; - break; - } - case 0x34: - { /* INC (IX+d) */ - REG_WZ = regIXY.word + static_cast(Z80opsImpl->peek8(REG_PC)); - Z80opsImpl->addressOnBus(REG_PC, 5); - REG_PC++; - uint8_t work8 = Z80opsImpl->peek8(REG_WZ); - Z80opsImpl->addressOnBus(REG_WZ, 1); - inc8(work8); - Z80opsImpl->poke8(REG_WZ, work8); - break; - } - case 0x35: - { /* DEC (IX+d) */ - REG_WZ = regIXY.word + static_cast(Z80opsImpl->peek8(REG_PC)); - Z80opsImpl->addressOnBus(REG_PC, 5); - REG_PC++; - uint8_t work8 = Z80opsImpl->peek8(REG_WZ); - Z80opsImpl->addressOnBus(REG_WZ, 1); - dec8(work8); - Z80opsImpl->poke8(REG_WZ, work8); - break; - } - case 0x36: - { /* LD (IX+d),n */ - REG_WZ = regIXY.word + static_cast(Z80opsImpl->peek8(REG_PC)); - REG_PC++; - uint8_t work8 = Z80opsImpl->peek8(REG_PC); - Z80opsImpl->addressOnBus(REG_PC, 2); - REG_PC++; - Z80opsImpl->poke8(REG_WZ, work8); - break; - } - case 0x39: - { /* ADD IX,SP */ - Z80opsImpl->addressOnBus(getPairIR().word, 7); - add16(regIXY, REG_SP); - break; - } - case 0x44: - { /* LD B,IXh */ - REG_B = regIXY.byte8.hi; - break; - } - case 0x45: - { /* LD B,IXl */ - REG_B = regIXY.byte8.lo; - break; - } - case 0x46: - { /* LD B,(IX+d) */ - REG_WZ = regIXY.word + static_cast(Z80opsImpl->peek8(REG_PC)); - Z80opsImpl->addressOnBus(REG_PC, 5); - REG_PC++; - REG_B = Z80opsImpl->peek8(REG_WZ); - break; - } - case 0x4C: - { /* LD C,IXh */ - REG_C = regIXY.byte8.hi; - break; - } - case 0x4D: - { /* LD C,IXl */ - REG_C = regIXY.byte8.lo; - break; - } - case 0x4E: - { /* LD C,(IX+d) */ - REG_WZ = regIXY.word + static_cast(Z80opsImpl->peek8(REG_PC)); - Z80opsImpl->addressOnBus(REG_PC, 5); - REG_PC++; - REG_C = Z80opsImpl->peek8(REG_WZ); - break; - } - case 0x54: - { /* LD D,IXh */ - REG_D = regIXY.byte8.hi; - break; - } - case 0x55: - { /* LD D,IXl */ - REG_D = regIXY.byte8.lo; - break; - } - case 0x56: - { /* LD D,(IX+d) */ - REG_WZ = regIXY.word + static_cast(Z80opsImpl->peek8(REG_PC)); - Z80opsImpl->addressOnBus(REG_PC, 5); - REG_PC++; - REG_D = Z80opsImpl->peek8(REG_WZ); - break; - } - case 0x5C: - { /* LD E,IXh */ - REG_E = regIXY.byte8.hi; - break; - } - case 0x5D: - { /* LD E,IXl */ - REG_E = regIXY.byte8.lo; - break; - } - case 0x5E: - { /* LD E,(IX+d) */ - REG_WZ = regIXY.word + static_cast(Z80opsImpl->peek8(REG_PC)); - Z80opsImpl->addressOnBus(REG_PC, 5); - REG_PC++; - REG_E = Z80opsImpl->peek8(REG_WZ); - break; - } - case 0x60: - { /* LD IXh,B */ - regIXY.byte8.hi = REG_B; - break; - } - case 0x61: - { /* LD IXh,C */ - regIXY.byte8.hi = REG_C; - break; - } - case 0x62: - { /* LD IXh,D */ - regIXY.byte8.hi = REG_D; - break; - } - case 0x63: - { /* LD IXh,E */ - regIXY.byte8.hi = REG_E; - break; - } - case 0x64: - { /* LD IXh,IXh */ - break; - } - case 0x65: - { /* LD IXh,IXl */ - regIXY.byte8.hi = regIXY.byte8.lo; - break; - } - case 0x66: - { /* LD H,(IX+d) */ - REG_WZ = regIXY.word + static_cast(Z80opsImpl->peek8(REG_PC)); - Z80opsImpl->addressOnBus(REG_PC, 5); - REG_PC++; - REG_H = Z80opsImpl->peek8(REG_WZ); - break; - } - case 0x67: - { /* LD IXh,A */ - regIXY.byte8.hi = regA; - break; - } - case 0x68: - { /* LD IXl,B */ - regIXY.byte8.lo = REG_B; - break; - } - case 0x69: - { /* LD IXl,C */ - regIXY.byte8.lo = REG_C; - break; - } - case 0x6A: - { /* LD IXl,D */ - regIXY.byte8.lo = REG_D; - break; - } - case 0x6B: - { /* LD IXl,E */ - regIXY.byte8.lo = REG_E; - break; - } - case 0x6C: - { /* LD IXl,IXh */ - regIXY.byte8.lo = regIXY.byte8.hi; - break; - } - case 0x6D: - { /* LD IXl,IXl */ - break; - } - case 0x6E: - { /* LD L,(IX+d) */ - REG_WZ = regIXY.word + static_cast(Z80opsImpl->peek8(REG_PC)); - Z80opsImpl->addressOnBus(REG_PC, 5); - REG_PC++; - REG_L = Z80opsImpl->peek8(REG_WZ); - break; - } - case 0x6F: - { /* LD IXl,A */ - regIXY.byte8.lo = regA; - break; - } - case 0x70: - { /* LD (IX+d),B */ - REG_WZ = regIXY.word + static_cast(Z80opsImpl->peek8(REG_PC)); - Z80opsImpl->addressOnBus(REG_PC, 5); - REG_PC++; - Z80opsImpl->poke8(REG_WZ, REG_B); - break; - } - case 0x71: - { /* LD (IX+d),C */ - REG_WZ = regIXY.word + static_cast(Z80opsImpl->peek8(REG_PC)); - Z80opsImpl->addressOnBus(REG_PC, 5); - REG_PC++; - Z80opsImpl->poke8(REG_WZ, REG_C); - break; - } - case 0x72: - { /* LD (IX+d),D */ - REG_WZ = regIXY.word + static_cast(Z80opsImpl->peek8(REG_PC)); - Z80opsImpl->addressOnBus(REG_PC, 5); - REG_PC++; - Z80opsImpl->poke8(REG_WZ, REG_D); - break; - } - case 0x73: - { /* LD (IX+d),E */ - REG_WZ = regIXY.word + static_cast(Z80opsImpl->peek8(REG_PC)); - Z80opsImpl->addressOnBus(REG_PC, 5); - REG_PC++; - Z80opsImpl->poke8(REG_WZ, REG_E); - break; - } - case 0x74: - { /* LD (IX+d),H */ - REG_WZ = regIXY.word + static_cast(Z80opsImpl->peek8(REG_PC)); - Z80opsImpl->addressOnBus(REG_PC, 5); - REG_PC++; - Z80opsImpl->poke8(REG_WZ, REG_H); - break; - } - case 0x75: - { /* LD (IX+d),L */ - REG_WZ = regIXY.word + static_cast(Z80opsImpl->peek8(REG_PC)); - Z80opsImpl->addressOnBus(REG_PC, 5); - REG_PC++; - Z80opsImpl->poke8(REG_WZ, REG_L); - break; - } - case 0x77: - { /* LD (IX+d),A */ - REG_WZ = regIXY.word + static_cast(Z80opsImpl->peek8(REG_PC)); - Z80opsImpl->addressOnBus(REG_PC, 5); - REG_PC++; - Z80opsImpl->poke8(REG_WZ, regA); - break; - } - case 0x7C: - { /* LD A,IXh */ - regA = regIXY.byte8.hi; - break; - } - case 0x7D: - { /* LD A,IXl */ - regA = regIXY.byte8.lo; - break; - } - case 0x7E: - { /* LD A,(IX+d) */ - REG_WZ = regIXY.word + static_cast(Z80opsImpl->peek8(REG_PC)); - Z80opsImpl->addressOnBus(REG_PC, 5); - REG_PC++; - regA = Z80opsImpl->peek8(REG_WZ); - break; - } - case 0x84: - { /* ADD A,IXh */ - add(regIXY.byte8.hi); - break; - } - case 0x85: - { /* ADD A,IXl */ - add(regIXY.byte8.lo); - break; - } - case 0x86: - { /* ADD A,(IX+d) */ - REG_WZ = regIXY.word + static_cast(Z80opsImpl->peek8(REG_PC)); - Z80opsImpl->addressOnBus(REG_PC, 5); - REG_PC++; - add(Z80opsImpl->peek8(REG_WZ)); - break; - } - case 0x8C: - { /* ADC A,IXh */ - adc(regIXY.byte8.hi); - break; - } - case 0x8D: - { /* ADC A,IXl */ - adc(regIXY.byte8.lo); - break; - } - case 0x8E: - { /* ADC A,(IX+d) */ - REG_WZ = regIXY.word + static_cast(Z80opsImpl->peek8(REG_PC)); - Z80opsImpl->addressOnBus(REG_PC, 5); - REG_PC++; - adc(Z80opsImpl->peek8(REG_WZ)); - break; - } - case 0x94: - { /* SUB IXh */ - sub(regIXY.byte8.hi); - break; - } - case 0x95: - { /* SUB IXl */ - sub(regIXY.byte8.lo); - break; - } - case 0x96: - { /* SUB (IX+d) */ - REG_WZ = regIXY.word + static_cast(Z80opsImpl->peek8(REG_PC)); - Z80opsImpl->addressOnBus(REG_PC, 5); - REG_PC++; - sub(Z80opsImpl->peek8(REG_WZ)); - break; - } - case 0x9C: - { /* SBC A,IXh */ - sbc(regIXY.byte8.hi); - break; - } - case 0x9D: - { /* SBC A,IXl */ - sbc(regIXY.byte8.lo); - break; - } - case 0x9E: - { /* SBC A,(IX+d) */ - REG_WZ = regIXY.word + static_cast(Z80opsImpl->peek8(REG_PC)); - Z80opsImpl->addressOnBus(REG_PC, 5); - REG_PC++; - sbc(Z80opsImpl->peek8(REG_WZ)); - break; - } - case 0xA4: - { /* AND IXh */ - and_(regIXY.byte8.hi); - break; - } - case 0xA5: - { /* AND IXl */ - and_(regIXY.byte8.lo); - break; - } - case 0xA6: - { /* AND (IX+d) */ - REG_WZ = regIXY.word + static_cast(Z80opsImpl->peek8(REG_PC)); - Z80opsImpl->addressOnBus(REG_PC, 5); - REG_PC++; - and_(Z80opsImpl->peek8(REG_WZ)); - break; - } - case 0xAC: - { /* XOR IXh */ - xor_(regIXY.byte8.hi); - break; - } - case 0xAD: - { /* XOR IXl */ - xor_(regIXY.byte8.lo); - break; - } - case 0xAE: - { /* XOR (IX+d) */ - REG_WZ = regIXY.word + static_cast(Z80opsImpl->peek8(REG_PC)); - Z80opsImpl->addressOnBus(REG_PC, 5); - REG_PC++; - xor_(Z80opsImpl->peek8(REG_WZ)); - break; - } - case 0xB4: - { /* OR IXh */ - or_(regIXY.byte8.hi); - break; - } - case 0xB5: - { /* OR IXl */ - or_(regIXY.byte8.lo); - break; - } - case 0xB6: - { /* OR (IX+d) */ - REG_WZ = regIXY.word + (int8_t) Z80opsImpl->peek8(REG_PC); - Z80opsImpl->addressOnBus(REG_PC, 5); - REG_PC++; - or_(Z80opsImpl->peek8(REG_WZ)); - break; - } - case 0xBC: - { /* CP IXh */ - cp(regIXY.byte8.hi); - break; - } - case 0xBD: - { /* CP IXl */ - cp(regIXY.byte8.lo); - break; - } - case 0xBE: - { /* CP (IX+d) */ - REG_WZ = regIXY.word + (int8_t) Z80opsImpl->peek8(REG_PC); - Z80opsImpl->addressOnBus(REG_PC, 5); - REG_PC++; - cp(Z80opsImpl->peek8(REG_WZ)); - break; - } - case 0xCB: - { /* Subconjunto de instrucciones */ - REG_WZ = regIXY.word + (int8_t) Z80opsImpl->peek8(REG_PC); - REG_PC++; - opCode = Z80opsImpl->peek8(REG_PC); - Z80opsImpl->addressOnBus(REG_PC, 2); - REG_PC++; - decodeDDFDCB(opCode, REG_WZ); - break; - } - case 0xDD: - prefixOpcode = 0xDD; - break; - case 0xE1: - { /* POP IX */ - regIXY.word = pop(); - break; - } - case 0xE3: - { /* EX (SP),IX */ - // Instrucción de ejecución sutil como pocas... atento al dato. - RegisterPair work16 = regIXY; - regIXY.word = Z80opsImpl->peek16(REG_SP); - Z80opsImpl->addressOnBus(REG_SP + 1, 1); - /* I can't call poke16 from here because the Z80 CPU does the writes in inverted order. - * Same thing goes for EX (SP), HL. - */ - Z80opsImpl->poke8(REG_SP + 1, work16.byte8.hi); - Z80opsImpl->poke8(REG_SP, work16.byte8.lo); - Z80opsImpl->addressOnBus(REG_SP, 2); - REG_WZ = regIXY.word; - break; - } - case 0xE5: - { /* PUSH IX */ - Z80opsImpl->addressOnBus(getPairIR().word, 1); - push(regIXY.word); - break; - } - case 0xE9: - { /* JP (IX) */ - REG_PC = regIXY.word; - break; - } - case 0xED: - { - prefixOpcode = 0xED; - break; - } - case 0xF9: - { /* LD SP,IX */ - Z80opsImpl->addressOnBus(getPairIR().word, 2); - REG_SP = regIXY.word; - break; - } - case 0xFD: - { - prefixOpcode = 0xFD; - break; - } - default: - { - // Detrás de un DD/FD o varios en secuencia venía un código - // que no correspondía con una instrucción que involucra a - // IX o IY. Se trata como si fuera un código normal. - // Sin esto, además de emular mal, falla el test - // ld , de ZEXALL. -#ifdef WITH_BREAKPOINT_SUPPORT - if (breakpointEnabled && prefixOpcode == 0) { - opCode = Z80opsImpl->breakpoint(REG_PC, opCode); - } -#endif - decodeOpcode(opCode); - break; - } - } -} - -// Subconjunto de instrucciones 0xDDCB -void Z80::decodeDDFDCB(uint8_t opCode, uint16_t address) { - - switch (opCode) { - case 0x00: /* RLC (IX+d),B */ - case 0x01: /* RLC (IX+d),C */ - case 0x02: /* RLC (IX+d),D */ - case 0x03: /* RLC (IX+d),E */ - case 0x04: /* RLC (IX+d),H */ - case 0x05: /* RLC (IX+d),L */ - case 0x06: /* RLC (IX+d) */ - case 0x07: /* RLC (IX+d),A */ - { - uint8_t work8 = Z80opsImpl->peek8(address); - rlc(work8); - Z80opsImpl->addressOnBus(address, 1); - Z80opsImpl->poke8(address, work8); - copyToRegister(opCode, work8); - break; - } - case 0x08: /* RRC (IX+d),B */ - case 0x09: /* RRC (IX+d),C */ - case 0x0A: /* RRC (IX+d),D */ - case 0x0B: /* RRC (IX+d),E */ - case 0x0C: /* RRC (IX+d),H */ - case 0x0D: /* RRC (IX+d),L */ - case 0x0E: /* RRC (IX+d) */ - case 0x0F: /* RRC (IX+d),A */ - { - uint8_t work8 = Z80opsImpl->peek8(address); - rrc(work8); - Z80opsImpl->addressOnBus(address, 1); - Z80opsImpl->poke8(address, work8); - copyToRegister(opCode, work8); - break; - } - case 0x10: /* RL (IX+d),B */ - case 0x11: /* RL (IX+d),C */ - case 0x12: /* RL (IX+d),D */ - case 0x13: /* RL (IX+d),E */ - case 0x14: /* RL (IX+d),H */ - case 0x15: /* RL (IX+d),L */ - case 0x16: /* RL (IX+d) */ - case 0x17: /* RL (IX+d),A */ - { - uint8_t work8 = Z80opsImpl->peek8(address); - rl(work8); - Z80opsImpl->addressOnBus(address, 1); - Z80opsImpl->poke8(address, work8); - copyToRegister(opCode, work8); - break; - } - case 0x18: /* RR (IX+d),B */ - case 0x19: /* RR (IX+d),C */ - case 0x1A: /* RR (IX+d),D */ - case 0x1B: /* RR (IX+d),E */ - case 0x1C: /* RR (IX+d),H */ - case 0x1D: /* RR (IX+d),L */ - case 0x1E: /* RR (IX+d) */ - case 0x1F: /* RR (IX+d),A */ - { - uint8_t work8 = Z80opsImpl->peek8(address); - rr(work8); - Z80opsImpl->addressOnBus(address, 1); - Z80opsImpl->poke8(address, work8); - copyToRegister(opCode, work8); - break; - } - case 0x20: /* SLA (IX+d),B */ - case 0x21: /* SLA (IX+d),C */ - case 0x22: /* SLA (IX+d),D */ - case 0x23: /* SLA (IX+d),E */ - case 0x24: /* SLA (IX+d),H */ - case 0x25: /* SLA (IX+d),L */ - case 0x26: /* SLA (IX+d) */ - case 0x27: /* SLA (IX+d),A */ - { - uint8_t work8 = Z80opsImpl->peek8(address); - sla(work8); - Z80opsImpl->addressOnBus(address, 1); - Z80opsImpl->poke8(address, work8); - copyToRegister(opCode, work8); - break; - } - case 0x28: /* SRA (IX+d),B */ - case 0x29: /* SRA (IX+d),C */ - case 0x2A: /* SRA (IX+d),D */ - case 0x2B: /* SRA (IX+d),E */ - case 0x2C: /* SRA (IX+d),H */ - case 0x2D: /* SRA (IX+d),L */ - case 0x2E: /* SRA (IX+d) */ - case 0x2F: /* SRA (IX+d),A */ - { - uint8_t work8 = Z80opsImpl->peek8(address); - sra(work8); - Z80opsImpl->addressOnBus(address, 1); - Z80opsImpl->poke8(address, work8); - copyToRegister(opCode, work8); - break; - } - case 0x30: /* SLL (IX+d),B */ - case 0x31: /* SLL (IX+d),C */ - case 0x32: /* SLL (IX+d),D */ - case 0x33: /* SLL (IX+d),E */ - case 0x34: /* SLL (IX+d),H */ - case 0x35: /* SLL (IX+d),L */ - case 0x36: /* SLL (IX+d) */ - case 0x37: /* SLL (IX+d),A */ - { - uint8_t work8 = Z80opsImpl->peek8(address); - sll(work8); - Z80opsImpl->addressOnBus(address, 1); - Z80opsImpl->poke8(address, work8); - copyToRegister(opCode, work8); - break; - } - case 0x38: /* SRL (IX+d),B */ - case 0x39: /* SRL (IX+d),C */ - case 0x3A: /* SRL (IX+d),D */ - case 0x3B: /* SRL (IX+d),E */ - case 0x3C: /* SRL (IX+d),H */ - case 0x3D: /* SRL (IX+d),L */ - case 0x3E: /* SRL (IX+d) */ - case 0x3F: /* SRL (IX+d),A */ - { - uint8_t work8 = Z80opsImpl->peek8(address); - srl(work8); - Z80opsImpl->addressOnBus(address, 1); - Z80opsImpl->poke8(address, work8); - copyToRegister(opCode, work8); - break; - } - case 0x40: - case 0x41: - case 0x42: - case 0x43: - case 0x44: - case 0x45: - case 0x46: - case 0x47: - { /* BIT 0,(IX+d) */ - bitTest(0x01, Z80opsImpl->peek8(address)); - sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZHP_MASK) - | ((address >> 8) & FLAG_53_MASK); - Z80opsImpl->addressOnBus(address, 1); - break; - } - case 0x48: - case 0x49: - case 0x4A: - case 0x4B: - case 0x4C: - case 0x4D: - case 0x4E: - case 0x4F: - { /* BIT 1,(IX+d) */ - bitTest(0x02, Z80opsImpl->peek8(address)); - sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZHP_MASK) - | ((address >> 8) & FLAG_53_MASK); - Z80opsImpl->addressOnBus(address, 1); - break; - } - case 0x50: - case 0x51: - case 0x52: - case 0x53: - case 0x54: - case 0x55: - case 0x56: - case 0x57: - { /* BIT 2,(IX+d) */ - bitTest(0x04, Z80opsImpl->peek8(address)); - sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZHP_MASK) - | ((address >> 8) & FLAG_53_MASK); - Z80opsImpl->addressOnBus(address, 1); - break; - } - case 0x58: - case 0x59: - case 0x5A: - case 0x5B: - case 0x5C: - case 0x5D: - case 0x5E: - case 0x5F: - { /* BIT 3,(IX+d) */ - bitTest(0x08, Z80opsImpl->peek8(address)); - sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZHP_MASK) - | ((address >> 8) & FLAG_53_MASK); - Z80opsImpl->addressOnBus(address, 1); - break; - } - case 0x60: - case 0x61: - case 0x62: - case 0x63: - case 0x64: - case 0x65: - case 0x66: - case 0x67: - { /* BIT 4,(IX+d) */ - bitTest(0x10, Z80opsImpl->peek8(address)); - sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZHP_MASK) - | ((address >> 8) & FLAG_53_MASK); - Z80opsImpl->addressOnBus(address, 1); - break; - } - case 0x68: - case 0x69: - case 0x6A: - case 0x6B: - case 0x6C: - case 0x6D: - case 0x6E: - case 0x6F: - { /* BIT 5,(IX+d) */ - bitTest(0x20, Z80opsImpl->peek8(address)); - sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZHP_MASK) - | ((address >> 8) & FLAG_53_MASK); - Z80opsImpl->addressOnBus(address, 1); - break; - } - case 0x70: - case 0x71: - case 0x72: - case 0x73: - case 0x74: - case 0x75: - case 0x76: - case 0x77: - { /* BIT 6,(IX+d) */ - bitTest(0x40, Z80opsImpl->peek8(address)); - sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZHP_MASK) - | ((address >> 8) & FLAG_53_MASK); - Z80opsImpl->addressOnBus(address, 1); - break; - } - case 0x78: - case 0x79: - case 0x7A: - case 0x7B: - case 0x7C: - case 0x7D: - case 0x7E: - case 0x7F: - { /* BIT 7,(IX+d) */ - bitTest(0x80, Z80opsImpl->peek8(address)); - sz5h3pnFlags = (sz5h3pnFlags & FLAG_SZHP_MASK) - | ((address >> 8) & FLAG_53_MASK); - Z80opsImpl->addressOnBus(address, 1); - break; - } - case 0x80: /* RES 0,(IX+d),B */ - case 0x81: /* RES 0,(IX+d),C */ - case 0x82: /* RES 0,(IX+d),D */ - case 0x83: /* RES 0,(IX+d),E */ - case 0x84: /* RES 0,(IX+d),H */ - case 0x85: /* RES 0,(IX+d),L */ - case 0x86: /* RES 0,(IX+d) */ - case 0x87: /* RES 0,(IX+d),A */ - { - uint8_t work8 = Z80opsImpl->peek8(address) & 0xFE; - Z80opsImpl->addressOnBus(address, 1); - Z80opsImpl->poke8(address, work8); - copyToRegister(opCode, work8); - break; - } - case 0x88: /* RES 1,(IX+d),B */ - case 0x89: /* RES 1,(IX+d),C */ - case 0x8A: /* RES 1,(IX+d),D */ - case 0x8B: /* RES 1,(IX+d),E */ - case 0x8C: /* RES 1,(IX+d),H */ - case 0x8D: /* RES 1,(IX+d),L */ - case 0x8E: /* RES 1,(IX+d) */ - case 0x8F: /* RES 1,(IX+d),A */ - { - uint8_t work8 = Z80opsImpl->peek8(address) & 0xFD; - Z80opsImpl->addressOnBus(address, 1); - Z80opsImpl->poke8(address, work8); - copyToRegister(opCode, work8); - break; - } - case 0x90: /* RES 2,(IX+d),B */ - case 0x91: /* RES 2,(IX+d),C */ - case 0x92: /* RES 2,(IX+d),D */ - case 0x93: /* RES 2,(IX+d),E */ - case 0x94: /* RES 2,(IX+d),H */ - case 0x95: /* RES 2,(IX+d),L */ - case 0x96: /* RES 2,(IX+d) */ - case 0x97: /* RES 2,(IX+d),A */ - { - uint8_t work8 = Z80opsImpl->peek8(address) & 0xFB; - Z80opsImpl->addressOnBus(address, 1); - Z80opsImpl->poke8(address, work8); - copyToRegister(opCode, work8); - break; - } - case 0x98: /* RES 3,(IX+d),B */ - case 0x99: /* RES 3,(IX+d),C */ - case 0x9A: /* RES 3,(IX+d),D */ - case 0x9B: /* RES 3,(IX+d),E */ - case 0x9C: /* RES 3,(IX+d),H */ - case 0x9D: /* RES 3,(IX+d),L */ - case 0x9E: /* RES 3,(IX+d) */ - case 0x9F: /* RES 3,(IX+d),A */ - { - uint8_t work8 = Z80opsImpl->peek8(address) & 0xF7; - Z80opsImpl->addressOnBus(address, 1); - Z80opsImpl->poke8(address, work8); - copyToRegister(opCode, work8); - break; - } - case 0xA0: /* RES 4,(IX+d),B */ - case 0xA1: /* RES 4,(IX+d),C */ - case 0xA2: /* RES 4,(IX+d),D */ - case 0xA3: /* RES 4,(IX+d),E */ - case 0xA4: /* RES 4,(IX+d),H */ - case 0xA5: /* RES 4,(IX+d),L */ - case 0xA6: /* RES 4,(IX+d) */ - case 0xA7: /* RES 4,(IX+d),A */ - { - uint8_t work8 = Z80opsImpl->peek8(address) & 0xEF; - Z80opsImpl->addressOnBus(address, 1); - Z80opsImpl->poke8(address, work8); - copyToRegister(opCode, work8); - break; - } - case 0xA8: /* RES 5,(IX+d),B */ - case 0xA9: /* RES 5,(IX+d),C */ - case 0xAA: /* RES 5,(IX+d),D */ - case 0xAB: /* RES 5,(IX+d),E */ - case 0xAC: /* RES 5,(IX+d),H */ - case 0xAD: /* RES 5,(IX+d),L */ - case 0xAE: /* RES 5,(IX+d) */ - case 0xAF: /* RES 5,(IX+d),A */ - { - uint8_t work8 = Z80opsImpl->peek8(address) & 0xDF; - Z80opsImpl->addressOnBus(address, 1); - Z80opsImpl->poke8(address, work8); - copyToRegister(opCode, work8); - break; - } - case 0xB0: /* RES 6,(IX+d),B */ - case 0xB1: /* RES 6,(IX+d),C */ - case 0xB2: /* RES 6,(IX+d),D */ - case 0xB3: /* RES 6,(IX+d),E */ - case 0xB4: /* RES 6,(IX+d),H */ - case 0xB5: /* RES 6,(IX+d),L */ - case 0xB6: /* RES 6,(IX+d) */ - case 0xB7: /* RES 6,(IX+d),A */ - { - uint8_t work8 = Z80opsImpl->peek8(address) & 0xBF; - Z80opsImpl->addressOnBus(address, 1); - Z80opsImpl->poke8(address, work8); - copyToRegister(opCode, work8); - break; - } - case 0xB8: /* RES 7,(IX+d),B */ - case 0xB9: /* RES 7,(IX+d),C */ - case 0xBA: /* RES 7,(IX+d),D */ - case 0xBB: /* RES 7,(IX+d),E */ - case 0xBC: /* RES 7,(IX+d),H */ - case 0xBD: /* RES 7,(IX+d),L */ - case 0xBE: /* RES 7,(IX+d) */ - case 0xBF: /* RES 7,(IX+d),A */ - { - uint8_t work8 = Z80opsImpl->peek8(address) & 0x7F; - Z80opsImpl->addressOnBus(address, 1); - Z80opsImpl->poke8(address, work8); - copyToRegister(opCode, work8); - break; - } - case 0xC0: /* SET 0,(IX+d),B */ - case 0xC1: /* SET 0,(IX+d),C */ - case 0xC2: /* SET 0,(IX+d),D */ - case 0xC3: /* SET 0,(IX+d),E */ - case 0xC4: /* SET 0,(IX+d),H */ - case 0xC5: /* SET 0,(IX+d),L */ - case 0xC6: /* SET 0,(IX+d) */ - case 0xC7: /* SET 0,(IX+d),A */ - { - uint8_t work8 = Z80opsImpl->peek8(address) | 0x01; - Z80opsImpl->addressOnBus(address, 1); - Z80opsImpl->poke8(address, work8); - copyToRegister(opCode, work8); - break; - } - case 0xC8: /* SET 1,(IX+d),B */ - case 0xC9: /* SET 1,(IX+d),C */ - case 0xCA: /* SET 1,(IX+d),D */ - case 0xCB: /* SET 1,(IX+d),E */ - case 0xCC: /* SET 1,(IX+d),H */ - case 0xCD: /* SET 1,(IX+d),L */ - case 0xCE: /* SET 1,(IX+d) */ - case 0xCF: /* SET 1,(IX+d),A */ - { - uint8_t work8 = Z80opsImpl->peek8(address) | 0x02; - Z80opsImpl->addressOnBus(address, 1); - Z80opsImpl->poke8(address, work8); - copyToRegister(opCode, work8); - break; - } - case 0xD0: /* SET 2,(IX+d),B */ - case 0xD1: /* SET 2,(IX+d),C */ - case 0xD2: /* SET 2,(IX+d),D */ - case 0xD3: /* SET 2,(IX+d),E */ - case 0xD4: /* SET 2,(IX+d),H */ - case 0xD5: /* SET 2,(IX+d),L */ - case 0xD6: /* SET 2,(IX+d) */ - case 0xD7: /* SET 2,(IX+d),A */ - { - uint8_t work8 = Z80opsImpl->peek8(address) | 0x04; - Z80opsImpl->addressOnBus(address, 1); - Z80opsImpl->poke8(address, work8); - copyToRegister(opCode, work8); - break; - } - case 0xD8: /* SET 3,(IX+d),B */ - case 0xD9: /* SET 3,(IX+d),C */ - case 0xDA: /* SET 3,(IX+d),D */ - case 0xDB: /* SET 3,(IX+d),E */ - case 0xDC: /* SET 3,(IX+d),H */ - case 0xDD: /* SET 3,(IX+d),L */ - case 0xDE: /* SET 3,(IX+d) */ - case 0xDF: /* SET 3,(IX+d),A */ - { - uint8_t work8 = Z80opsImpl->peek8(address) | 0x08; - Z80opsImpl->addressOnBus(address, 1); - Z80opsImpl->poke8(address, work8); - copyToRegister(opCode, work8); - break; - } - case 0xE0: /* SET 4,(IX+d),B */ - case 0xE1: /* SET 4,(IX+d),C */ - case 0xE2: /* SET 4,(IX+d),D */ - case 0xE3: /* SET 4,(IX+d),E */ - case 0xE4: /* SET 4,(IX+d),H */ - case 0xE5: /* SET 4,(IX+d),L */ - case 0xE6: /* SET 4,(IX+d) */ - case 0xE7: /* SET 4,(IX+d),A */ - { - uint8_t work8 = Z80opsImpl->peek8(address) | 0x10; - Z80opsImpl->addressOnBus(address, 1); - Z80opsImpl->poke8(address, work8); - copyToRegister(opCode, work8); - break; - } - case 0xE8: /* SET 5,(IX+d),B */ - case 0xE9: /* SET 5,(IX+d),C */ - case 0xEA: /* SET 5,(IX+d),D */ - case 0xEB: /* SET 5,(IX+d),E */ - case 0xEC: /* SET 5,(IX+d),H */ - case 0xED: /* SET 5,(IX+d),L */ - case 0xEE: /* SET 5,(IX+d) */ - case 0xEF: /* SET 5,(IX+d),A */ - { - uint8_t work8 = Z80opsImpl->peek8(address) | 0x20; - Z80opsImpl->addressOnBus(address, 1); - Z80opsImpl->poke8(address, work8); - copyToRegister(opCode, work8); - break; - } - case 0xF0: /* SET 6,(IX+d),B */ - case 0xF1: /* SET 6,(IX+d),C */ - case 0xF2: /* SET 6,(IX+d),D */ - case 0xF3: /* SET 6,(IX+d),E */ - case 0xF4: /* SET 6,(IX+d),H */ - case 0xF5: /* SET 6,(IX+d),L */ - case 0xF6: /* SET 6,(IX+d) */ - case 0xF7: /* SET 6,(IX+d),A */ - { - uint8_t work8 = Z80opsImpl->peek8(address) | 0x40; - Z80opsImpl->addressOnBus(address, 1); - Z80opsImpl->poke8(address, work8); - copyToRegister(opCode, work8); - break; - } - case 0xF8: /* SET 7,(IX+d),B */ - case 0xF9: /* SET 7,(IX+d),C */ - case 0xFA: /* SET 7,(IX+d),D */ - case 0xFB: /* SET 7,(IX+d),E */ - case 0xFC: /* SET 7,(IX+d),H */ - case 0xFD: /* SET 7,(IX+d),L */ - case 0xFE: /* SET 7,(IX+d) */ - case 0xFF: /* SET 7,(IX+d),A */ - { - uint8_t work8 = Z80opsImpl->peek8(address) | 0x80; - Z80opsImpl->addressOnBus(address, 1); - Z80opsImpl->poke8(address, work8); - copyToRegister(opCode, work8); - break; - } - } -} - -//Subconjunto de instrucciones 0xED - -void Z80::decodeED(uint8_t opCode) { - switch (opCode) { - case 0x40: - { /* IN B,(C) */ - REG_WZ = REG_BC; - REG_B = Z80opsImpl->inPort(REG_WZ); - REG_WZ++; - sz5h3pnFlags = sz53pn_addTable[REG_B]; - flagQ = true; - break; - } - case 0x41: - { /* OUT (C),B */ - REG_WZ = REG_BC; - Z80opsImpl->outPort(REG_WZ, REG_B); - REG_WZ++; - break; - } - case 0x42: - { /* SBC HL,BC */ - Z80opsImpl->addressOnBus(getPairIR().word, 7); - sbc16(REG_BC); - break; - } - case 0x43: - { /* LD (nn),BC */ - REG_WZ = Z80opsImpl->peek16(REG_PC); - Z80opsImpl->poke16(REG_WZ, regBC); - REG_WZ++; - REG_PC = REG_PC + 2; - break; - } - case 0x44: - case 0x4C: - case 0x54: - case 0x5C: - case 0x64: - case 0x6C: - case 0x74: - case 0x7C: - { /* NEG */ - uint8_t aux = regA; - regA = 0; - carryFlag = false; - sbc(aux); - break; - } - case 0x45: - case 0x55: - case 0x5D: - case 0x65: - case 0x6D: - case 0x75: - case 0x7D: - { /* RETN */ - ffIFF1 = ffIFF2; - REG_PC = REG_WZ = pop(); - break; - } - case 0x4D: - { /* RETI */ - /* According to the Z80 documentation, RETI should not update IFF1 from - * IFF2; only RETN does this (to restore interrupts after NMI). This affects - * precise interrupt handling behavior. - */ - REG_PC = REG_WZ = pop(); - break; - } - case 0x46: - case 0x4E: - case 0x66: - case 0x6E: - { /* IM 0 */ - modeINT = IntMode::IM0; - break; - } - case 0x47: - { /* LD I,A */ - /* - * El par IR se pone en el bus de direcciones *antes* - * de poner A en el registro I. Detalle importante. - */ - Z80opsImpl->addressOnBus(getPairIR().word, 1); - regI = regA; - break; - } - case 0x48: - { /* IN C,(C) */ - REG_WZ = REG_BC; - REG_C = Z80opsImpl->inPort(REG_WZ); - REG_WZ++; - sz5h3pnFlags = sz53pn_addTable[REG_C]; - flagQ = true; - break; - } - case 0x49: - { /* OUT (C),C */ - REG_WZ = REG_BC; - Z80opsImpl->outPort(REG_WZ, REG_C); - REG_WZ++; - break; - } - case 0x4A: - { /* ADC HL,BC */ - Z80opsImpl->addressOnBus(getPairIR().word, 7); - adc16(REG_BC); - break; - } - case 0x4B: - { /* LD BC,(nn) */ - REG_WZ = Z80opsImpl->peek16(REG_PC); - REG_BC = Z80opsImpl->peek16(REG_WZ); - REG_WZ++; - REG_PC = REG_PC + 2; - break; - } - case 0x4F: - { /* LD R,A */ - /* - * El par IR se pone en el bus de direcciones *antes* - * de poner A en el registro R. Detalle importante. - */ - Z80opsImpl->addressOnBus(getPairIR().word, 1); - setRegR(regA); - break; - } - case 0x50: - { /* IN D,(C) */ - REG_WZ = REG_BC; - REG_D = Z80opsImpl->inPort(REG_WZ); - REG_WZ++; - sz5h3pnFlags = sz53pn_addTable[REG_D]; - flagQ = true; - break; - } - case 0x51: - { /* OUT (C),D */ - REG_WZ = REG_BC; - Z80opsImpl->outPort(REG_WZ++, REG_D); - break; - } - case 0x52: - { /* SBC HL,DE */ - Z80opsImpl->addressOnBus(getPairIR().word, 7); - sbc16(REG_DE); - break; - } - case 0x53: - { /* LD (nn),DE */ - REG_WZ = Z80opsImpl->peek16(REG_PC); - Z80opsImpl->poke16(REG_WZ++, regDE); - REG_PC = REG_PC + 2; - break; - } - case 0x56: - case 0x76: - { /* IM 1 */ - modeINT = IntMode::IM1; - break; - } - case 0x57: - { /* LD A,I */ - Z80opsImpl->addressOnBus(getPairIR().word, 1); - regA = regI; - sz5h3pnFlags = sz53n_addTable[regA]; - /* - * The P / V flag should reflect IFF2 state regardless of whether an - * interrupt is currently being signaled on the bus. - */ - if (ffIFF2) { - sz5h3pnFlags |= PARITY_MASK; - } - flagQ = true; - break; - } - case 0x58: - { /* IN E,(C) */ - REG_WZ = REG_BC; - REG_E = Z80opsImpl->inPort(REG_WZ++); - sz5h3pnFlags = sz53pn_addTable[REG_E]; - flagQ = true; - break; - } - case 0x59: - { /* OUT (C),E */ - REG_WZ = REG_BC; - Z80opsImpl->outPort(REG_WZ++, REG_E); - break; - } - case 0x5A: - { /* ADC HL,DE */ - Z80opsImpl->addressOnBus(getPairIR().word, 7); - adc16(REG_DE); - break; - } - case 0x5B: - { /* LD DE,(nn) */ - REG_WZ = Z80opsImpl->peek16(REG_PC); - REG_DE = Z80opsImpl->peek16(REG_WZ++); - REG_PC = REG_PC + 2; - break; - } - case 0x5E: - case 0x7E: - { /* IM 2 */ - modeINT = IntMode::IM2; - break; - } - case 0x5F: - { /* LD A,R */ - Z80opsImpl->addressOnBus(getPairIR().word, 1); - regA = getRegR(); - sz5h3pnFlags = sz53n_addTable[regA]; - /* - * The P / V flag should reflect IFF2 state regardless of whether an - * interrupt is currently being signaled on the bus. - */ - if (ffIFF2) { - sz5h3pnFlags |= PARITY_MASK; - } - flagQ = true; - break; - } - case 0x60: - { /* IN H,(C) */ - REG_WZ = REG_BC; - REG_H = Z80opsImpl->inPort(REG_WZ++); - sz5h3pnFlags = sz53pn_addTable[REG_H]; - flagQ = true; - break; - } - case 0x61: - { /* OUT (C),H */ - REG_WZ = REG_BC; - Z80opsImpl->outPort(REG_WZ++, REG_H); - break; - } - case 0x62: - { /* SBC HL,HL */ - Z80opsImpl->addressOnBus(getPairIR().word, 7); - sbc16(REG_HL); - break; - } - case 0x63: - { /* LD (nn),HL */ - REG_WZ = Z80opsImpl->peek16(REG_PC); - Z80opsImpl->poke16(REG_WZ++, regHL); - REG_PC = REG_PC + 2; - break; - } - case 0x67: - { /* RRD */ - // A = A7 A6 A5 A4 (HL)3 (HL)2 (HL)1 (HL)0 - // (HL) = A3 A2 A1 A0 (HL)7 (HL)6 (HL)5 (HL)4 - // Los bits 3,2,1 y 0 de (HL) se copian a los bits 3,2,1 y 0 de A. - // Los 4 bits bajos que había en A se copian a los bits 7,6,5 y 4 de (HL). - // Los 4 bits altos que había en (HL) se copian a los 4 bits bajos de (HL) - // Los 4 bits superiores de A no se tocan. ¡p'habernos matao! - uint8_t aux = regA << 4; - REG_WZ = REG_HL; - uint16_t memHL = Z80opsImpl->peek8(REG_WZ); - regA = (regA & 0xf0) | (memHL & 0x0f); - Z80opsImpl->addressOnBus(REG_WZ, 4); - Z80opsImpl->poke8(REG_WZ++, (memHL >> 4) | aux); - sz5h3pnFlags = sz53pn_addTable[regA]; - flagQ = true; - break; - } - case 0x68: - { /* IN L,(C) */ - REG_WZ = REG_BC; - REG_L = Z80opsImpl->inPort(REG_WZ++); - sz5h3pnFlags = sz53pn_addTable[REG_L]; - flagQ = true; - break; - } - case 0x69: - { /* OUT (C),L */ - REG_WZ = REG_BC; - Z80opsImpl->outPort(REG_WZ++, REG_L); - break; - } - case 0x6A: - { /* ADC HL,HL */ - Z80opsImpl->addressOnBus(getPairIR().word, 7); - adc16(REG_HL); - break; - } - case 0x6B: - { /* LD HL,(nn) */ - REG_WZ = Z80opsImpl->peek16(REG_PC); - REG_HL = Z80opsImpl->peek16(REG_WZ++); - REG_PC = REG_PC + 2; - break; - } - case 0x6F: - { /* RLD */ - // A = A7 A6 A5 A4 (HL)7 (HL)6 (HL)5 (HL)4 - // (HL) = (HL)3 (HL)2 (HL)1 (HL)0 A3 A2 A1 A0 - // Los 4 bits bajos que había en (HL) se copian a los bits altos de (HL). - // Los 4 bits altos que había en (HL) se copian a los 4 bits bajos de A - // Los bits 3,2,1 y 0 de A se copian a los bits 3,2,1 y 0 de (HL). - // Los 4 bits superiores de A no se tocan. ¡p'habernos matao! - uint8_t aux = regA & 0x0f; - REG_WZ = REG_HL; - uint16_t memHL = Z80opsImpl->peek8(REG_WZ); - regA = (regA & 0xf0) | (memHL >> 4); - Z80opsImpl->addressOnBus(REG_WZ, 4); - Z80opsImpl->poke8(REG_WZ++, (memHL << 4) | aux); - sz5h3pnFlags = sz53pn_addTable[regA]; - flagQ = true; - break; - } - case 0x70: - { /* IN (C) */ - REG_WZ = REG_BC; - uint8_t inPort = Z80opsImpl->inPort(REG_WZ++); - sz5h3pnFlags = sz53pn_addTable[inPort]; - flagQ = true; - break; - } - case 0x71: - { /* OUT (C),0 */ - REG_WZ = REG_BC; - Z80opsImpl->outPort(REG_WZ++, 0x00); - break; - } - case 0x72: - { /* SBC HL,SP */ - Z80opsImpl->addressOnBus(getPairIR().word, 7); - sbc16(REG_SP); - break; - } - case 0x73: - { /* LD (nn),SP */ - REG_WZ = Z80opsImpl->peek16(REG_PC); - Z80opsImpl->poke16(REG_WZ++, regSP); - REG_PC = REG_PC + 2; - break; - } - case 0x78: - { /* IN A,(C) */ - REG_WZ = REG_BC; - regA = Z80opsImpl->inPort(REG_WZ++); - sz5h3pnFlags = sz53pn_addTable[regA]; - flagQ = true; - break; - } - case 0x79: - { /* OUT (C),A */ - REG_WZ = REG_BC; - Z80opsImpl->outPort(REG_WZ++, regA); - break; - } - case 0x7A: - { /* ADC HL,SP */ - Z80opsImpl->addressOnBus(getPairIR().word, 7); - adc16(REG_SP); - break; - } - case 0x7B: - { /* LD SP,(nn) */ - REG_WZ = Z80opsImpl->peek16(REG_PC); - REG_SP = Z80opsImpl->peek16(REG_WZ++); - REG_PC = REG_PC + 2; - break; - } - case 0xA0: - { /* LDI */ - ldi(); - break; - } - case 0xA1: - { /* CPI */ - cpi(); - break; - } - case 0xA2: - { /* INI */ - ini(); - break; - } - case 0xA3: - { /* OUTI */ - outi(); - break; - } - case 0xA8: - { /* LDD */ - ldd(); - break; - } - case 0xA9: - { /* CPD */ - cpd(); - break; - } - case 0xAA: - { /* IND */ - ind(); - break; - } - case 0xAB: - { /* OUTD */ - outd(); - break; - } - case 0xB0: - { /* LDIR */ - ldi(); - if (REG_BC != 0) { - REG_PC = REG_PC - 2; - REG_WZ = REG_PC + 1; - Z80opsImpl->addressOnBus(REG_DE - 1, 5); - sz5h3pnFlags &= ~FLAG_53_MASK; - sz5h3pnFlags |= (REG_PCh & FLAG_53_MASK); - } - break; - } - case 0xB1: - { /* CPIR */ - cpi(); - if ((sz5h3pnFlags & PARITY_MASK) == PARITY_MASK - && (sz5h3pnFlags & ZERO_MASK) == 0) { - REG_PC = REG_PC - 2; - REG_WZ = REG_PC + 1; - Z80opsImpl->addressOnBus(REG_HL - 1, 5); - sz5h3pnFlags &= ~FLAG_53_MASK; - sz5h3pnFlags |= (REG_PCh & FLAG_53_MASK); - } - break; - } - case 0xB2: - { /* INIR */ - ini(); - if (REG_B != 0) { - REG_PC = REG_PC - 2; - REG_WZ = REG_PC + 1; - Z80opsImpl->addressOnBus(REG_HL - 1, 5); - adjustINxROUTxRFlags(); - } - break; - } - case 0xB3: - { /* OTIR */ - outi(); - if (REG_B != 0) { - REG_PC = REG_PC - 2; - REG_WZ = REG_PC + 1; - Z80opsImpl->addressOnBus(REG_BC, 5); - adjustINxROUTxRFlags(); - } - break; - } - case 0xB8: - { /* LDDR */ - ldd(); - if (REG_BC != 0) { - REG_PC = REG_PC - 2; - REG_WZ = REG_PC + 1; - Z80opsImpl->addressOnBus(REG_DE + 1, 5); - sz5h3pnFlags &= ~FLAG_53_MASK; - sz5h3pnFlags |= (REG_PCh & FLAG_53_MASK); - } - break; - } - case 0xB9: - { /* CPDR */ - cpd(); - if ((sz5h3pnFlags & PARITY_MASK) == PARITY_MASK - && (sz5h3pnFlags & ZERO_MASK) == 0) { - REG_PC = REG_PC - 2; - REG_WZ = REG_PC + 1; - Z80opsImpl->addressOnBus(REG_HL + 1, 5); - sz5h3pnFlags &= ~FLAG_53_MASK; - sz5h3pnFlags |= (REG_PCh & FLAG_53_MASK); - } - break; - } - case 0xBA: - { /* INDR */ - ind(); - if (REG_B != 0) { - REG_PC = REG_PC - 2; - REG_WZ = REG_PC + 1; - Z80opsImpl->addressOnBus(REG_HL + 1, 5); - adjustINxROUTxRFlags(); - } - break; - } - case 0xBB: - { /* OTDR */ - outd(); - if (REG_B != 0) { - REG_PC = REG_PC - 2; - REG_WZ = REG_PC + 1; - Z80opsImpl->addressOnBus(REG_BC, 5); - adjustINxROUTxRFlags(); - } - break; - } - case 0xDD: - prefixOpcode = 0xDD; - break; - case 0xED: - prefixOpcode = 0xED; - break; - case 0xFD: - prefixOpcode = 0xFD; - break; - default: - { - break; - } - } -} - -void Z80::copyToRegister(uint8_t opCode, uint8_t value) -{ - switch (opCode & 0x07) - { - case 0x00: - REG_B = value; - break; - case 0x01: - REG_C = value; - break; - case 0x02: - REG_D = value; - break; - case 0x03: - REG_E = value; - break; - case 0x04: - REG_H = value; - break; - case 0x05: - REG_L = value; - break; - case 0x07: - regA = value; - default: - break; - } -} - -void Z80::adjustINxROUTxRFlags() -{ - sz5h3pnFlags &= ~FLAG_53_MASK; - sz5h3pnFlags |= (REG_PCh & FLAG_53_MASK); - - uint8_t pf = sz5h3pnFlags & PARITY_MASK; - if (carryFlag) - { - int8_t addsub = 1 - (sz5h3pnFlags & ADDSUB_MASK); - pf ^= sz53pn_addTable[(REG_B + addsub) & 0x07] ^ PARITY_MASK; - if ((REG_B & 0x0F) == (addsub != 1 ? 0x00 : 0x0F)) - sz5h3pnFlags |= HALFCARRY_MASK; - else - sz5h3pnFlags &= ~HALFCARRY_MASK; - } else { - pf ^= sz53pn_addTable[REG_B & 0x07] ^ PARITY_MASK; - sz5h3pnFlags &= ~HALFCARRY_MASK; - } - - if (pf & PARITY_MASK) - sz5h3pnFlags |= PARITY_MASK; - else - sz5h3pnFlags &= ~PARITY_MASK; -} \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..c1f09a7 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,81 @@ +cmake_minimum_required (VERSION 3.25.1) + +add_executable(z80_sim_test + z80_sim_test.cpp + z80_sim_test.h +) + +target_compile_features(z80_sim_test PRIVATE cxx_std_14) + +if(TARGET z80cpp-static) + target_link_libraries(z80_sim_test PRIVATE z80cpp::z80cpp-static) +elseif(TARGET z80cpp) + target_link_libraries(z80_sim_test PRIVATE z80cpp::z80cpp) +else() + message(FATAL_ERROR "No z80cpp library target available for z80_sim_test") +endif() + +# Copy test ROM if it exists +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/zexall.bin) + configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/zexall.bin + ${CMAKE_CURRENT_BINARY_DIR}/zexall.bin + COPYONLY + ) +else() + message(WARNING "zexall.bin not found - z80_sim_test will fail without it") +endif() + +add_test( + NAME z80_sim_test + COMMAND ${CMAKE_COMMAND} -E env CTEST_INTERACTIVE_DEBUG_MODE=1 $ + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} +) + +# Benchmark Tests +add_executable(z80_benchmark_test + z80_benchmark_test.cpp +) + +target_compile_features(z80_benchmark_test PRIVATE cxx_std_17) + +if(TARGET z80cpp-static) + target_link_libraries(z80_benchmark_test PRIVATE z80cpp::z80cpp-static) +elseif(TARGET z80cpp) + target_link_libraries(z80_benchmark_test PRIVATE z80cpp::z80cpp) +endif() + +add_test( + NAME z80_benchmark_test + COMMAND $ + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} +) + +# Game Benchmark Tests +file(GLOB TAP_FILES "${CMAKE_CURRENT_SOURCE_DIR}/roms/*.tap") +list(LENGTH TAP_FILES TAP_FILES_COUNT) + +if(TAP_FILES_COUNT GREATER 0) + add_executable(z80_game_test + z80_game_test.cpp + ) + + target_compile_features(z80_game_test PRIVATE cxx_std_17) + + if(TARGET z80cpp-static) + target_link_libraries(z80_game_test PRIVATE z80cpp::z80cpp-static) + elseif(TARGET z80cpp) + target_link_libraries(z80_game_test PRIVATE z80cpp::z80cpp) + endif() + + add_test( + NAME z80_game_test + COMMAND $ + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + + # Copy roms directory to build directory + file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/roms DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) +else() + message(STATUS "No .tap files found in roms directory - skipping z80_game_test") +endif() diff --git a/tests/benchmark_shared.h b/tests/benchmark_shared.h new file mode 100644 index 0000000..c1d0905 --- /dev/null +++ b/tests/benchmark_shared.h @@ -0,0 +1,210 @@ +#pragma once + +#include "../include/z80.h" +#include "../include/z80_bus_interface.h" +#include +#include +#include +#include +#include +#include +#include + +// Benchmark configuration +struct BenchmarkConfig { + std::string name; + std::string file; // For file-based tests (e.g. ZEXALL) + std::vector code; // For in-memory tests + int64_t instructions = 0; + double expected_min_mips = 0.0; // Minimum acceptable MIPS + bool is_cpm_program = false; // true for CP/M (like ZEXALL), false for raw Z80 + uint16_t load_address = 0; // Load address for raw Z80 programs +}; + +// Benchmark result +struct BenchmarkResult { + std::string name; + double elapsed_seconds{}; + int64_t instructions{}; + int64_t tstates{}; + double mips{}; + double mts_per_sec{}; + double speedup{}; + bool passed{}; +}; + +class BenchmarkSim : public Z80BusInterface { + public: + Z80 cpu; + std::array ram{}; + uint64_t tstates{0}; + bool cpmMode{false}; + + BenchmarkSim() : cpu(*this) { + } + + // Bus Interface Implementation + uint8_t fetchOpcodeImpl(uint16_t address) { + tstates += 4; + return ram[address]; + } + + uint8_t peek8Impl(uint16_t address) { + tstates += 3; + return ram[address]; + } + + void poke8Impl(uint16_t address, uint8_t value) { + tstates += 3; + ram[address] = value; + } + + uint16_t peek16Impl(uint16_t address) { + tstates += 6; + return ram[address] | (ram[(address + 1) & 0xFFFF] << 8); + } + + void poke16Impl(uint16_t address, RegisterPair word) { + tstates += 6; + ram[address] = word.byte8.lo; + ram[(address + 1) & 0xFFFF] = word.byte8.hi; + } + + uint8_t inPortImpl(uint16_t port) { + tstates += 4; + return 0xFF; + } + + void outPortImpl(uint16_t port, uint8_t value) { + tstates += 4; + } + + void addressOnBusImpl(uint16_t address, int32_t wstates) { + tstates += wstates; + } + + void interruptHandlingTimeImpl(int32_t wstates) { + tstates += wstates; + } + + static bool isActiveINTImpl() { + return false; + } + + // Helper methods + std::array& getRam() { + return ram; + } + + Z80& getCpu() { + return cpu; + } + + [[nodiscard]] uint64_t getTstates() const { + return tstates; + } + + void setCpmMode(bool mode) { + cpmMode = mode; + } +}; + +// Run a single benchmark test +inline BenchmarkResult runBenchmark(const BenchmarkConfig& config) { + BenchmarkResult result; + result.name = config.name; + result.instructions = config.instructions; + result.passed = false; + + std::cout << "Testing: " << config.name << '\n'; + + // Create simulator + BenchmarkSim sim; + sim.setCpmMode(config.is_cpm_program); + + if (!config.code.empty()) { + // Load from memory + if (config.code.size() + config.load_address > 0x10000) { + std::cerr << " ERROR: Code too large for RAM" << '\n'; + return result; + } + std::copy(config.code.begin(), config.code.end(), std::next(sim.getRam().begin(), config.load_address)); + } else if (!config.file.empty()) { + // Load binary file + std::ifstream file(config.file, std::ios::binary | std::ios::ate); + if (!file.is_open()) { + std::cerr << " ERROR: Cannot open file: " << config.file << '\n'; + return result; + } + + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); + + if (config.is_cpm_program) { + // CP/M program (like ZEXALL) - load at 0x100 + size = std::min(size, 0x10000 - 0x100); + file.read(reinterpret_cast(&sim.getRam()[0x100]), size); + file.close(); + + // Set up CP/M environment + sim.getRam()[0] = 0xC3; // JP 0x100 + sim.getRam()[1] = 0x00; + sim.getRam()[2] = 0x01; + sim.getRam()[5] = 0xC9; // RET at BDOS call address + } else { + // Raw Z80 program (synthetic tests) - load at 0x0000 + size = std::min(size, 0x10000); + file.read(reinterpret_cast(sim.getRam().data()), size); + file.close(); + } + } else { + std::cerr << " ERROR: No code or file specified" << '\n'; + return result; + } + + // Reset CPU + sim.getCpu().reset(); + + // If raw Z80 program has a custom load address, we might need to set PC + // But for now, synthetic tests assume 0x0000 or handle it. + // For game tests, we might need to set PC to the entry point. + if (!config.is_cpm_program && config.load_address != 0) { + sim.getCpu().setRegPC(config.load_address); + } + + uint64_t instructionsExecuted = 0; + + // Run benchmark + auto start = std::chrono::high_resolution_clock::now(); + + while (instructionsExecuted < config.instructions && !sim.getCpu().isHalted()) { + sim.getCpu().execute(); + instructionsExecuted++; + } + + auto end = std::chrono::high_resolution_clock::now(); + + // Calculate metrics + std::chrono::duration elapsed = end - start; + result.elapsed_seconds = elapsed.count(); + result.tstates = static_cast(sim.getTstates()); + + if (result.elapsed_seconds > 0) { + result.mips = (static_cast(config.instructions) / 1000000.0) / result.elapsed_seconds; + result.mts_per_sec = (static_cast(result.tstates) / 1000000.0) / result.elapsed_seconds; + result.speedup = result.mts_per_sec / 3.5; // ZX Spectrum = 3.5 MHz + result.passed = result.mips >= config.expected_min_mips; + } + + // Print results + std::cout << std::fixed << std::setprecision(2); + std::cout << " Time: " << std::fixed << std::setprecision(3) << result.elapsed_seconds << " sec\n"; + std::cout << " T-States: " << result.tstates << " (" << result.mts_per_sec << " MT/s)\n"; + std::cout << " Speedup: " << result.speedup << "%" << '\n'; + if (result.instructions > 0) { + std::cout << " MIPS: " << result.mips << "\n"; + } + std::cout << " Result: " << (result.passed ? "Passed" : "Failed") << "\n\n"; + + return result; +} diff --git a/tests/benchmark_shared.hpp b/tests/benchmark_shared.hpp new file mode 100644 index 0000000..2e918c3 --- /dev/null +++ b/tests/benchmark_shared.hpp @@ -0,0 +1,210 @@ +#pragma once + +#include "../include/z80.h" +#include "../include/z80_bus_interface.h" +#include +#include +#include +#include +#include +#include +#include + +// Benchmark configuration +struct BenchmarkConfig { + std::string name; + std::string file; // For file-based tests (e.g. ZEXALL) + std::vector code; // For in-memory tests + int64_t instructions = 0; + double expected_min_mips = 0.0; // Minimum acceptable MIPS + bool is_cpm_program = false; // true for CP/M (like ZEXALL), false for raw Z80 + uint16_t load_address = 0; // Load address for raw Z80 programs +}; + +// Benchmark result +struct BenchmarkResult { + std::string name; + double elapsed_seconds{}; + int64_t instructions{}; + int64_t tstates{}; + double mips{}; + double mts_per_sec{}; + double speedup{}; + bool passed{}; +}; + +class BenchmarkSim : public Z80BusInterface { + public: + Z80 cpu; + std::array ram{}; + uint64_t tstates{0}; + bool cpmMode{false}; + + BenchmarkSim() : cpu(this) { + } + + // Bus Interface Implementation + uint8_t fetchOpcodeImpl(uint16_t address) { + tstates += 4; + return ram[address]; + } + + uint8_t peek8Impl(uint16_t address) { + tstates += 3; + return ram[address]; + } + + void poke8Impl(uint16_t address, uint8_t value) { + tstates += 3; + ram[address] = value; + } + + uint16_t peek16Impl(uint16_t address) { + tstates += 6; + return ram[address] | (ram[(address + 1) & 0xFFFF] << 8); + } + + void poke16Impl(uint16_t address, RegisterPair word) { + tstates += 6; + ram[address] = word.byte8.lo; + ram[(address + 1) & 0xFFFF] = word.byte8.hi; + } + + uint8_t inPortImpl(uint16_t port) { + tstates += 4; + return 0xFF; + } + + void outPortImpl(uint16_t port, uint8_t value) { + tstates += 4; + } + + void addressOnBusImpl(uint16_t address, int32_t wstates) { + tstates += wstates; + } + + void interruptHandlingTimeImpl(int32_t wstates) { + tstates += wstates; + } + + static bool isActiveINTImpl() { + return false; + } + + // Helper methods + std::array& getRam() { + return ram; + } + + Z80& getCpu() { + return cpu; + } + + [[nodiscard]] uint64_t getTstates() const { + return tstates; + } + + void setCpmMode(bool mode) { + cpmMode = mode; + } +}; + +// Run a single benchmark test +inline BenchmarkResult runBenchmark(const BenchmarkConfig& config) { + BenchmarkResult result; + result.name = config.name; + result.instructions = config.instructions; + result.passed = false; + + std::cout << "Testing: " << config.name << '\n'; + + // Create simulator + BenchmarkSim sim; + sim.setCpmMode(config.is_cpm_program); + + if (!config.code.empty()) { + // Load from memory + if (config.code.size() + config.load_address > 0x10000) { + std::cerr << " ERROR: Code too large for RAM" << '\n'; + return result; + } + std::copy(config.code.begin(), config.code.end(), std::next(sim.getRam().begin(), config.load_address)); + } else if (!config.file.empty()) { + // Load binary file + std::ifstream file(config.file, std::ios::binary | std::ios::ate); + if (!file.is_open()) { + std::cerr << " ERROR: Cannot open file: " << config.file << '\n'; + return result; + } + + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); + + if (config.is_cpm_program) { + // CP/M program (like ZEXALL) - load at 0x100 + size = std::min(size, 0x10000 - 0x100); + file.read(reinterpret_cast(&sim.getRam()[0x100]), size); + file.close(); + + // Set up CP/M environment + sim.getRam()[0] = 0xC3; // JP 0x100 + sim.getRam()[1] = 0x00; + sim.getRam()[2] = 0x01; + sim.getRam()[5] = 0xC9; // RET at BDOS call address + } else { + // Raw Z80 program (synthetic tests) - load at 0x0000 + size = std::min(size, 0x10000); + file.read(reinterpret_cast(sim.getRam().data()), size); + file.close(); + } + } else { + std::cerr << " ERROR: No code or file specified" << '\n'; + return result; + } + + // Reset CPU + sim.getCpu().reset(); + + // If raw Z80 program has a custom load address, we might need to set PC + // But for now, synthetic tests assume 0x0000 or handle it. + // For game tests, we might need to set PC to the entry point. + if (!config.is_cpm_program && config.load_address != 0) { + sim.getCpu().setRegPC(config.load_address); + } + + uint64_t instructionsExecuted = 0; + + // Run benchmark + auto start = std::chrono::high_resolution_clock::now(); + + while (instructionsExecuted < config.instructions && !sim.getCpu().isHalted()) { + sim.getCpu().execute(); + instructionsExecuted++; + } + + auto end = std::chrono::high_resolution_clock::now(); + + // Calculate metrics + std::chrono::duration elapsed = end - start; + result.elapsed_seconds = elapsed.count(); + result.tstates = static_cast(sim.getTstates()); + + if (result.elapsed_seconds > 0) { + result.mips = (static_cast(config.instructions) / 1000000.0) / result.elapsed_seconds; + result.mts_per_sec = (static_cast(result.tstates) / 1000000.0) / result.elapsed_seconds; + result.speedup = result.mts_per_sec / 3.5; // ZX Spectrum = 3.5 MHz + result.passed = result.mips >= config.expected_min_mips; + } + + // Print results + std::cout << std::fixed << std::setprecision(2); + std::cout << " Time: " << std::fixed << std::setprecision(3) << result.elapsed_seconds << " sec\n"; + std::cout << " T-States: " << result.tstates << " (" << result.mts_per_sec << " MT/s)\n"; + std::cout << " Speedup: " << result.speedup << "%" << '\n'; + if (result.instructions > 0) { + std::cout << " MIPS: " << result.mips << "\n"; + } + std::cout << " Result: " << (result.passed ? "Passed" : "Failed") << "\n\n"; + + return result; +} diff --git a/tests/roms/.gitkeep b/tests/roms/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/z80_benchmark_test.cpp b/tests/z80_benchmark_test.cpp new file mode 100644 index 0000000..3edd9d8 --- /dev/null +++ b/tests/z80_benchmark_test.cpp @@ -0,0 +1,271 @@ +// Z80 Performance Benchmark Test Suite +// Integrated C++ tests for CMake/CTest + +#include "benchmark_shared.h" +#include +#include +#include +#include + +// --- Test Generation Helpers --- + +// Helper function to append a byte +void push_byte(std::vector& code, uint8_t byte) { + code.push_back(byte); +} + +// Helper function to append a list of bytes +void push_bytes(std::vector& code, std::initializer_list bytes) { + code.insert(code.end(), bytes); +} + +// Helper function to append a 16-bit word (little-endian) +void push_word(std::vector& code, uint16_t word) { + code.push_back(word & 0xFF); + code.push_back((word >> 8) & 0xFF); +} + +std::vector generate_instruction_mix_test(int iterations = 10000) { + std::vector code; + + // LD BC, iterations + push_byte(code, 0x01); // LD BC,nn + push_word(code, static_cast(iterations)); + + size_t loop_start = code.size(); + + // Arithmetic operations + push_bytes(code, {0x3E, 0x42}); // LD A, 0x42 + push_bytes(code, {0xC6, 0x10}); // ADD A, 0x10 + push_bytes(code, {0xD6, 0x08}); // SUB 0x08 + push_bytes(code, {0xE6, 0x0F}); // AND 0x0F + push_bytes(code, {0xF6, 0x20}); // OR 0x20 + push_bytes(code, {0xEE, 0x55}); // XOR 0x55 + + // Register operations + push_byte(code, 0x47); // LD B, A + push_byte(code, 0x4F); // LD C, A + push_byte(code, 0x57); // LD D, A + push_byte(code, 0x5F); // LD E, A + push_byte(code, 0x67); // LD H, A + push_byte(code, 0x6F); // LD L, A + + // 16-bit operations + push_bytes(code, {0x21, 0x00, 0x80}); // LD HL, 0x8000 + push_byte(code, 0x23); // INC HL + push_byte(code, 0x2B); // DEC HL + push_byte(code, 0x09); // ADD HL, BC + + // Memory operations + push_byte(code, 0x77); // LD (HL), A + push_byte(code, 0x7E); // LD A, (HL) + + // Rotations + push_byte(code, 0x07); // RLCA + push_byte(code, 0x0F); // RRCA + push_byte(code, 0x17); // RLA + push_byte(code, 0x1F); // RRA + + // Stack operations + push_byte(code, 0xC5); // PUSH BC + push_byte(code, 0xD5); // PUSH DE + push_byte(code, 0xE5); // PUSH HL + push_byte(code, 0xE1); // POP HL + push_byte(code, 0xD1); // POP DE + push_byte(code, 0xC1); // POP BC + + // Loop control + push_byte(code, 0x0B); // DEC BC + push_byte(code, 0x78); // LD A, B + push_byte(code, 0xB1); // OR C + int offset = 256 - static_cast(code.size() - loop_start + 2); + push_bytes(code, {0x20, static_cast(offset & 0xFF)}); // JR NZ, loop_start + + // HALT + push_byte(code, 0x76); + + return code; +} + +std::vector generate_memory_intensive_test(int size = 1000) { + std::vector code; + + // Initialize: LD HL, buffer_start + push_bytes(code, {0x21, 0x00, 0x80}); // LD HL, 0x8000 + + // LD BC, size + push_byte(code, 0x01); + push_word(code, static_cast(size)); + + // Fill loop + size_t fill_start = code.size(); + push_bytes(code, {0x3E, 0x55}); // LD A, 0x55 + push_byte(code, 0x77); // LD (HL), A + push_byte(code, 0x23); // INC HL + push_byte(code, 0x0B); // DEC BC + push_byte(code, 0x78); // LD A, B + push_byte(code, 0xB1); // OR C + int offset = 256 - static_cast(code.size() - fill_start + 2); + push_bytes(code, {0x20, static_cast(offset & 0xFF)}); // JR NZ, fill_start + + // Reset pointer + push_bytes(code, {0x21, 0x00, 0x80}); // LD HL, 0x8000 + push_byte(code, 0x01); + push_word(code, static_cast(size)); + + // Read loop + size_t read_start = code.size(); + push_byte(code, 0x7E); // LD A, (HL) + push_bytes(code, {0xC6, 0x01}); // ADD A, 1 + push_byte(code, 0x77); // LD (HL), A + push_byte(code, 0x23); // INC HL + push_byte(code, 0x0B); // DEC BC + push_byte(code, 0x78); // LD A, B + push_byte(code, 0xB1); // OR C + offset = 256 - static_cast(code.size() - read_start + 2); + push_bytes(code, {0x20, static_cast(offset & 0xFF)}); // JR NZ, read_start + + // HALT + push_byte(code, 0x76); + + return code; +} + +std::vector generate_arithmetic_test(int iterations = 50000) { + std::vector code; + + // LD BC, iterations + push_byte(code, 0x01); + push_word(code, static_cast(iterations)); + + size_t loop_start = code.size(); + + // Complex arithmetic sequence + push_bytes(code, {0x3E, 0x01}); // LD A, 1 + for (int i = 0; i < 10; ++i) { + push_byte(code, 0xC6); // ADD A, n + push_byte(code, static_cast(i + 1)); + push_byte(code, 0x47); // LD B, A + push_byte(code, 0x4F); // LD C, A + push_byte(code, 0x80); // ADD A, B + push_byte(code, 0x81); // ADD A, C + } + + // Loop control + push_byte(code, 0x0B); // DEC BC + push_byte(code, 0x78); // LD A, B + push_byte(code, 0xB1); // OR C + int offset = 256 - static_cast(code.size() - loop_start + 2); + push_bytes(code, {0x20, static_cast(offset & 0xFF)}); // JR NZ, loop_start + + push_byte(code, 0x76); // HALT + + return code; +} + +std::vector generate_jump_test(int iterations = 10000) { + std::vector code; + + // LD BC, iterations (must fit in 16-bit: max 65535) + iterations = std::min(iterations, 65535); + push_byte(code, 0x01); + push_word(code, static_cast(iterations)); + + size_t loop_start = code.size(); + + // Create multiple jump targets + push_bytes(code, {0x3E, 0x00}); // LD A, 0 + + // Conditional jumps + push_byte(code, 0xA7); // AND A + push_bytes(code, {0x28, 0x02}); // JR Z, +2 + push_bytes(code, {0x3E, 0xFF}); // LD A, 0xFF + + push_bytes(code, {0x3E, 0x01}); // LD A, 1 + push_bytes(code, {0xFE, 0x01}); // CP 1 + push_bytes(code, {0x20, 0x02}); // JR NZ, +2 + push_bytes(code, {0x3E, 0x02}); // LD A, 2 + + // Unconditional jump + push_bytes(code, {0x18, 0x02}); // JR +2 + push_bytes(code, {0x00, 0x00}); // NOP NOP (skipped) + + // Loop control + push_byte(code, 0x0B); // DEC BC + push_byte(code, 0x78); // LD A, B + push_byte(code, 0xB1); // OR C + int offset = 256 - static_cast(code.size() - loop_start + 2); + push_bytes(code, {0x20, static_cast(offset & 0xFF)}); // JR NZ, loop_start + + push_byte(code, 0x76); // HALT + + return code; +} + +int main(int argc, char* argv[]) { + try { + std::cout << "========================================" << '\n'; + std::cout << "Z80 Performance Benchmark Test Suite" << '\n'; + std::cout << "========================================" << '\n'; + std::cout << '\n'; + + // Define benchmark tests + // Paths are relative to build directory where tests run + // Min MIPS thresholds are conservative to avoid intermittent failures + // is_cpm_program: true for CP/M programs (ZEXALL), false for raw Z80 code + std::vector benchmarks = { + {"ZEXALL", "zexall.bin", {}, 10000000, 100.0, true}, // CP/M program + {"Instruction Mix", "", generate_instruction_mix_test(10000), 5000000, 100.0, false}, // Raw Z80 + {"Memory Intensive", "", generate_memory_intensive_test(1000), 2000000, 80.0, false}, // Raw Z80 + {"Arithmetic Heavy", "", generate_arithmetic_test(50000), 5000000, 90.0, false}, // Raw Z80 + {"Branch Heavy", "", generate_jump_test(10000), 3000000, 90.0, false}, // Raw Z80 + }; + + // Run benchmarks + std::vector results; + int passed = 0; + int failed = 0; + + for (const auto& config : benchmarks) { + BenchmarkResult result = runBenchmark(config); + results.push_back(result); + + if (result.passed) { + passed++; + } else { + failed++; + } + + std::cout << '\n'; + } + + // Summary + std::cout << "========================================" << '\n'; + std::cout << "Summary" << '\n'; + std::cout << "========================================" << '\n'; + std::cout << "Tests run: " << results.size() << '\n'; + std::cout << "Passed: " << passed << '\n'; + std::cout << "Failed: " << failed << '\n'; + std::cout << '\n'; + + // Calculate average performance + double total_mips = 0; + int valid_results = 0; + for (const auto& result : results) { + if (result.passed) { + total_mips += result.mips; + valid_results++; + } + } + + if (valid_results > 0) { + double avg_mips = total_mips / valid_results; + std::cout << "Average Performance: " << std::fixed << std::setprecision(2) << avg_mips << " MIPS" << '\n'; + } + + return (failed == 0) ? 0 : 1; + } catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << '\n'; + return 1; + } +} diff --git a/tests/z80_game_test.cpp b/tests/z80_game_test.cpp new file mode 100644 index 0000000..c056a02 --- /dev/null +++ b/tests/z80_game_test.cpp @@ -0,0 +1,165 @@ +// Z80 Game Benchmark Test Suite +// Runs real Spectrum games (extracted from TAP files) as benchmarks + +#include "benchmark_shared.h" +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +// Helper to read a TAP block +std::vector read_tap_block(std::ifstream& tap_file) { + uint16_t length = 0; + if (!tap_file.read(reinterpret_cast(&length), 2)) { + return {}; + } + + std::vector data(length); + if (!tap_file.read(reinterpret_cast(data.data()), length)) { + return {}; + } + + return data; +} + +// Load the main code block from a TAP file +// Heuristic: Returns the largest code block found +bool load_game_from_tap(const std::string& tap_path, BenchmarkConfig& config) { + std::ifstream tap_file(tap_path, std::ios::binary); + if (!tap_file.is_open()) { + std::cerr << "Error: File not found: " << tap_path << '\n'; + return false; + } + + config.name = fs::path(tap_path).stem().string(); + config.code.clear(); + config.load_address = 0; + config.is_cpm_program = false; + + size_t max_code_size = 0; + bool found_code = false; + + while (true) { + auto block = read_tap_block(tap_file); + if (block.empty()) { + break; + } + + if (block.size() < 2) { + continue; + } + + uint8_t flag = block[0]; + + // Header block + if (flag == 0x00 && block.size() >= 19) { + uint8_t block_type = block[1]; + // uint16_t data_length = *reinterpret_cast(&block[12]); + uint16_t param1 = *reinterpret_cast(&block[14]); // Load address for code + + if (block_type == 0x03) { // Code + // Read the data block immediately following + auto data_block = read_tap_block(tap_file); + if (data_block.empty()) { + break; + } + + if (data_block.size() > 2 && data_block[0] == 0xFF) { + // Remove flag and checksum + size_t actual_size = data_block.size() - 2; + + // Heuristic: Keep the largest code block + if (actual_size > max_code_size) { + max_code_size = actual_size; + config.code.assign(data_block.begin() + 1, data_block.end() - 1); + config.load_address = param1; + found_code = true; + } + } + } + } + } + + return found_code; +} + +int main(int argc, char* argv[]) { + try { + std::cout << "========================================" << '\n'; + std::cout << "Z80 Game Benchmark Test Suite" << '\n'; + std::cout << "========================================" << '\n'; + std::cout << '\n'; + + // Dynamically find all TAP files in the roms directory + std::vector tap_files; + const std::string roms_dir = "roms"; + + if (fs::exists(roms_dir) && fs::is_directory(roms_dir)) { + for (const auto& entry : fs::directory_iterator(roms_dir)) { + if (entry.is_regular_file() && entry.path().extension() == ".tap") { + tap_files.push_back(entry.path().string()); + } + } + std::sort(tap_files.begin(), tap_files.end()); + } else { + std::cerr << "Warning: roms directory not found\n"; + } + + std::vector results; + int passed = 0; + int failed = 0; + + for (const auto& tap_file : tap_files) { + BenchmarkConfig config; + // Set default expectations + config.instructions = 5000000; // Run for 5M instructions + config.expected_min_mips = 50.0; // Expect at least 50 MIPS (conservative) + + if (load_game_from_tap(tap_file, config)) { + BenchmarkResult result = runBenchmark(config); + results.push_back(result); + + if (result.passed) { + passed++; + } else { + failed++; + } + } else { + std::cout << "Skipping " << tap_file << " (not found or no code block)\n"; + } + std::cout << '\n'; + } + + // Summary + std::cout << "========================================" << '\n'; + std::cout << "Summary" << '\n'; + std::cout << "========================================" << '\n'; + std::cout << "Tests run: " << results.size() << '\n'; + std::cout << "Passed: " << passed << '\n'; + std::cout << "Failed: " << failed << '\n'; + std::cout << '\n'; + + // Calculate average performance + double total_mips = 0; + int valid_results = 0; + for (const auto& result : results) { + if (result.passed) { + total_mips += result.mips; + valid_results++; + } + } + + if (valid_results > 0) { + double avg_mips = total_mips / valid_results; + std::cout << "Average Performance: " << std::fixed << std::setprecision(2) << avg_mips << " MIPS" << '\n'; + } + + return (failed == 0 && valid_results > 0) ? 0 : 1; + } catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << '\n'; + return 1; + } +} diff --git a/tests/z80_sim_test.cpp b/tests/z80_sim_test.cpp new file mode 100644 index 0000000..502da5a --- /dev/null +++ b/tests/z80_sim_test.cpp @@ -0,0 +1,218 @@ +#include "z80_sim_test.h" + +#include +#include +#include +#include + +Z80SimTest::Z80SimTest() : cpu(*this) { +} + +Z80SimTest::~Z80SimTest() = default; + +uint8_t Z80SimTest::fetchOpcodeImpl(uint16_t address) { + // 3 clocks to fetch opcode from RAM and 1 execution clock + tstates += 4; + +#ifdef WITH_BREAKPOINT_SUPPORT + return z80Ram.at(address); +#else + uint8_t opcode = z80Ram.at(address); + return (cpmMode && address == 0x0005 ? breakpoint(address, opcode) : opcode); +#endif +} + +uint8_t Z80SimTest::peek8Impl(uint16_t address) { + // 3 clocks for read byte from RAM + tstates += 3; + return z80Ram.at(address); +} + +void Z80SimTest::poke8Impl(uint16_t address, uint8_t value) { + // 3 clocks for write byte to RAM + tstates += 3; + z80Ram.at(address) = value; +} + +uint16_t Z80SimTest::peek16Impl(uint16_t address) { + // Order matters, first read lsb, then read msb, don't "optimize" + uint8_t lsb = peek8Impl(address); + uint8_t msb = peek8Impl(address + 1); + return (msb << 8) | lsb; +} + +void Z80SimTest::poke16Impl(uint16_t address, RegisterPair word) { + // Order matters, first write lsb, then write msb, don't "optimize" + poke8Impl(address, word.byte8.lo); + poke8Impl(address + 1, word.byte8.hi); +} + +uint8_t Z80SimTest::inPortImpl(uint16_t port) { + // 4 clocks for read byte from bus + tstates += 3; + return z80Ports.at(port); +} + +void Z80SimTest::outPortImpl(uint16_t port, uint8_t value) { + // 4 clocks for write byte to bus + tstates += 4; + z80Ports.at(port) = value; +} + +void Z80SimTest::addressOnBusImpl(uint16_t address, int32_t tstates) { + // Additional clocks to be added on some instructions + this->tstates += tstates; +} + +void Z80SimTest::interruptHandlingTimeImpl(int32_t tstates) { + this->tstates += tstates; +} + +bool Z80SimTest::isActiveINTImpl() { + // Put here the needed logic to trigger an INT + return false; +} + +#ifdef WITH_EXEC_DONE +void Z80SimTest::execDoneImpl(void) { +} +#endif + +#ifdef WITH_BREAKPOINT_SUPPORT +uint8_t Z80SimTest::breakpointImpl(uint16_t address, uint8_t opcode) { + return breakpoint(address, opcode); +} +#endif + +uint8_t Z80SimTest::breakpoint(uint16_t address, uint8_t opcode) { + // Only handle CP/M BDOS calls if in CP/M mode + if (!cpmMode) { + return opcode; // No breakpoint handling for raw Z80 programs + } + +#ifdef WITH_BREAKPOINT_SUPPORT + // Emulate CP/M Syscall at address 5 + if (address != 0x0005) { + return opcode; + } +#endif + + switch (cpu.getRegC()) { + case 0: // BDOS 0 System Reset + { + std::cout << '\n' << "Z80 reset after " << tstates << " t-states\n"; + finish = true; + return opcode; + } + case 2: // BDOS 2 console char output + { + std::cout << static_cast(cpu.getRegE()); + return opcode; + } + case 9: // BDOS 9 console string output (string terminated by "$") + { + handleStringOutput(); + return opcode; + } + default: { + std::cout << "BDOS Call " << static_cast(cpu.getRegC()) << '\n'; + finish = true; + std::cout << finish << '\n'; + } + } + // opcode would be modified before the decodeOpcode method + return opcode; +} + +void Z80SimTest::handleStringOutput() { + uint16_t strAddr = cpu.getRegDE(); + std::string output; + + while (z80Ram.at(strAddr) != '$') { + output += static_cast(z80Ram.at(strAddr++)); + } + + auto current_time = std::chrono::high_resolution_clock::now(); + elapsed_ms = std::chrono::duration_cast(current_time - opcode_start_time).count(); + /* + * Check for " OK\n" at the end of the output to append timing info or mark the test as failed + * if " ERROR" is found in any part of the output. + */ + if (output.find(" OK\n") != std::string::npos) { + std::ostringstream oss; + oss << "Passed " << std::setw(6) << std::fixed << std::setprecision(3) + << static_cast(elapsed_ms) / 1000.0f << " sec\n"; + output.replace(output.find("OK\n"), 3, oss.str()); + total_ms += elapsed_ms; + ++num_tests; + } else if (output.find(" ERROR") != std::string::npos) { + output.replace(output.find("ERROR"), 5, "Failed"); + ++failed; + } + std::cout << output << std::flush; + opcode_start_time = current_time; +} + +void Z80SimTest::runTest(std::ifstream* fileStream) { + if (!fileStream->is_open()) { + std::cout << "file NOT OPEN\n"; + return; + } + + std::cout << "file open\n"; + + const std::streampos size = fileStream->tellg(); + std::cout << "Test size: " << size << '\n'; + fileStream->seekg(0, std::ios::beg); + fileStream->read(reinterpret_cast(&z80Ram[0x100]), size); + fileStream->close(); + +#ifdef WITH_BREAKPOINT_SUPPORT + cpu.setBreakpoint(true); +#endif + + cpu.reset(); + finish = false; + + z80Ram[0] = static_cast(0xC3); + z80Ram[1] = 0x00; + z80Ram[2] = 0x01; // JP 0x100 CP/M TPA + z80Ram[5] = static_cast(0xC9); // Return from BDOS call + + std::cout << "Running zexall...\n"; + std::cout.flush(); + start_time = opcode_start_time = std::chrono::high_resolution_clock::now(); + while (!finish) { + cpu.execute(); + } + auto end_time = std::chrono::high_resolution_clock::now(); + elapsed_ms = std::chrono::duration_cast(end_time - start_time).count(); + + std::cout << "\n"; + std::cout << "Elapsed T-state count: " << tstates << "\n"; + std::cout << "Cummulative test time: " << static_cast(total_ms) / 1000.0f << " sec\n"; + if (num_tests > 0) { + std::cout << "Average time per test: " << static_cast(total_ms) / num_tests / 1000.0f << " sec\n\n"; + std::cout << "✓ Tests passed: " << num_tests - failed << "\n"; + std::cout << "✗ Tests failed: " << failed << "\n"; + } + std::cout << "Total elapsed time: " << static_cast(elapsed_ms) / 1000.0f << " sec\n"; + std::cout.flush(); + if (failed > 0) { + throw std::runtime_error("Test failed"); + } +} + +int main() { + try { + Z80SimTest sim; + + std::ifstream f1("zexall.bin", std::ios::in | std::ios::binary | std::ios::ate); + sim.runTest(&f1); + f1.close(); + return 0; + } catch (const std::exception& e) { + std::cerr << "Exception: " << e.what() << '\n'; + return 1; + } +} diff --git a/tests/z80_sim_test.h b/tests/z80_sim_test.h new file mode 100644 index 0000000..89e85e0 --- /dev/null +++ b/tests/z80_sim_test.h @@ -0,0 +1,61 @@ +#ifndef Z80_SIM_TEST_HPP +#define Z80_SIM_TEST_HPP + +#include +#include +#include +#include + +#include "../include/z80.h" +#include "../include/z80_bus_interface.h" + +class Z80SimTest : public Z80BusInterface { + private: + uint64_t tstates{0}; + Z80 cpu; + std::array z80Ram{}; + std::array z80Ports{}; + volatile bool finish{false}; + volatile uint16_t failed{0}; + int64_t elapsed_ms{0}; + int64_t total_ms{0}; + int64_t num_tests{0}; + std::chrono::high_resolution_clock::time_point start_time; + std::chrono::high_resolution_clock::time_point opcode_start_time; + bool cpmMode{true}; + + public: + Z80SimTest(); + ~Z80SimTest(); + Z80SimTest(const Z80SimTest&) = delete; + Z80SimTest& operator=(const Z80SimTest&) = delete; + Z80SimTest(Z80SimTest&&) = delete; + Z80SimTest& operator=(Z80SimTest&&) = delete; + + uint8_t fetchOpcodeImpl(uint16_t address); + uint8_t peek8Impl(uint16_t address); + void poke8Impl(uint16_t address, uint8_t value); + uint16_t peek16Impl(uint16_t address); + void poke16Impl(uint16_t address, RegisterPair word); + uint8_t inPortImpl(uint16_t port); + void outPortImpl(uint16_t port, uint8_t value); + void addressOnBusImpl(uint16_t address, int32_t tstates); + void interruptHandlingTimeImpl(int32_t tstates); + static bool isActiveINTImpl(); + +#ifdef WITH_BREAKPOINT_SUPPORT + uint8_t breakpointImpl(uint16_t address, uint8_t opcode); +#else + uint8_t breakpoint(uint16_t address, uint8_t opcode); +#endif // WITH_BREAKPOINT_SUPPORT + + void handleStringOutput(); + +#ifdef WITH_EXEC_DONE + void execDoneImpl(void); +#endif + + void runTest(std::ifstream* fileStream); +}; + +#endif // Z80_SIM_TEST_HPP diff --git a/example/zexall.bin b/tests/zexall.bin similarity index 100% rename from example/zexall.bin rename to tests/zexall.bin From 40c59e58bd0abd198dfbaef354168ee160fd1f54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Hern=C3=A1ndez?= Date: Fri, 16 Jan 2026 15:47:48 +0000 Subject: [PATCH 02/12] Update tests/z80_sim_test.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/z80_sim_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/z80_sim_test.cpp b/tests/z80_sim_test.cpp index 14e82bf..d760d0a 100644 --- a/tests/z80_sim_test.cpp +++ b/tests/z80_sim_test.cpp @@ -49,7 +49,7 @@ void Z80SimTest::poke16Impl(uint16_t address, RegisterPair word) { uint8_t Z80SimTest::inPortImpl(uint16_t port) { // 4 clocks for read byte from bus - tstates += 3; + tstates += 4; return z80Ports.at(port); } From 03ff405d933a3e280a3dfd002b00bd4d5fd64606 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Hern=C3=A1ndez?= Date: Fri, 16 Jan 2026 15:52:02 +0000 Subject: [PATCH 03/12] Update tests/z80_sim_test.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/z80_sim_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/z80_sim_test.cpp b/tests/z80_sim_test.cpp index d760d0a..661ca53 100644 --- a/tests/z80_sim_test.cpp +++ b/tests/z80_sim_test.cpp @@ -190,7 +190,7 @@ void Z80SimTest::runTest(std::ifstream* fileStream) { std::cout << "\n"; std::cout << "Elapsed T-state count: " << tstates << "\n"; - std::cout << "Cummulative test time: " << static_cast(total_ms) / 1000.0f << " sec\n"; + std::cout << "Cumulative test time: " << static_cast(total_ms) / 1000.0f << " sec\n"; if (num_tests > 0) { std::cout << "Average time per test: " << static_cast(total_ms) / num_tests / 1000.0f << " sec\n\n"; std::cout << "\xE2\x9C\x93 Tests passed: " << num_tests - failed << "\n"; From cd1c99bd320e4e536cecbfc137f6e8a32ce7a8f7 Mon Sep 17 00:00:00 2001 From: Jose Hernandez Date: Fri, 16 Jan 2026 16:02:35 +0000 Subject: [PATCH 04/12] Merge origin/master into performance_improvements: resolve conflicts, keep header-only core, align tests and CMake, remove legacy src/z80.cpp --- include/z80_bus_interface.h | 24 +++++++----------------- include/z80_types.h | 6 +++--- tests/z80_sim_test.h | 6 +++--- 3 files changed, 13 insertions(+), 23 deletions(-) diff --git a/include/z80_bus_interface.h b/include/z80_bus_interface.h index 8f9d7e8..9bc2d1c 100644 --- a/include/z80_bus_interface.h +++ b/include/z80_bus_interface.h @@ -1,5 +1,5 @@ -#ifndef Z80_BUS_INTERFACE_HPP -#define Z80_BUS_INTERFACE_HPP +#ifndef Z80_BUS_INTERFACE_H +#define Z80_BUS_INTERFACE_H #include @@ -7,23 +7,9 @@ // CRTP bus interface: TBusInterface must implement the *Impl methods. template class Z80BusInterface { - public: - private: - Z80BusInterface() = default; - public: ~Z80BusInterface() = default; - - private: - Z80BusInterface(const Z80BusInterface&) = default; - - public: Z80BusInterface& operator=(const Z80BusInterface&) = default; - - private: - Z80BusInterface(Z80BusInterface&&) = default; - - public: Z80BusInterface& operator=(Z80BusInterface&&) = default; /* Read opcode from RAM */ @@ -88,6 +74,10 @@ template class Z80BusInterface { #endif private: + Z80BusInterface() = default; + Z80BusInterface(const Z80BusInterface&) = default; + Z80BusInterface(Z80BusInterface&&) = default; + TBusInterface& derived() { return static_cast(*this); } @@ -95,4 +85,4 @@ template class Z80BusInterface { friend TBusInterface; }; -#endif // Z80_BUS_INTERFACE_HPP +#endif // Z80_BUS_INTERFACE_H diff --git a/include/z80_types.h b/include/z80_types.h index 6686b50..88de84e 100644 --- a/include/z80_types.h +++ b/include/z80_types.h @@ -1,5 +1,5 @@ -#ifndef Z80_TYPES_HPP -#define Z80_TYPES_HPP +#ifndef Z80_TYPES_H +#define Z80_TYPES_H #include @@ -47,4 +47,4 @@ using RegisterPair = union { uint16_t word; }; -#endif // Z80_TYPES_HPP +#endif // Z80_TYPES_H diff --git a/tests/z80_sim_test.h b/tests/z80_sim_test.h index 89e85e0..241b6eb 100644 --- a/tests/z80_sim_test.h +++ b/tests/z80_sim_test.h @@ -1,5 +1,5 @@ -#ifndef Z80_SIM_TEST_HPP -#define Z80_SIM_TEST_HPP +#ifndef Z80_SIM_TEST_H +#define Z80_SIM_TEST_H #include #include @@ -58,4 +58,4 @@ class Z80SimTest : public Z80BusInterface { void runTest(std::ifstream* fileStream); }; -#endif // Z80_SIM_TEST_HPP +#endif // Z80_SIM_TEST_H From 604b53758e74095cc2a74c8c48d619c79ac9e311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Hern=C3=A1ndez?= Date: Fri, 16 Jan 2026 16:28:46 +0000 Subject: [PATCH 05/12] Update CMakeLists.txt Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3cde17b..cc1676f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required (VERSION 3.25.1) +cmake_minimum_required (VERSION 3.22.1) project( z80cpp From 30460011ace854d7b8919d294d26111cecd8389e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Hern=C3=A1ndez?= Date: Fri, 16 Jan 2026 16:29:38 +0000 Subject: [PATCH 06/12] Update include/z80_types.h Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- include/z80_types.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/z80_types.h b/include/z80_types.h index 88de84e..67c153b 100644 --- a/include/z80_types.h +++ b/include/z80_types.h @@ -34,7 +34,7 @@ #endif /* Union allowing a register pair to be accessed as bytes or as a word */ -using RegisterPair = union { +union RegisterPair { #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ struct { uint8_t hi, lo; From a56dc34cf7b8c82556e25fdfc581a27fab846f81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Hern=C3=A1ndez?= Date: Fri, 16 Jan 2026 16:30:33 +0000 Subject: [PATCH 07/12] Update tests/z80_game_test.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/z80_game_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/z80_game_test.cpp b/tests/z80_game_test.cpp index c056a02..b061ca5 100644 --- a/tests/z80_game_test.cpp +++ b/tests/z80_game_test.cpp @@ -116,7 +116,7 @@ int main(int argc, char* argv[]) { BenchmarkConfig config; // Set default expectations config.instructions = 5000000; // Run for 5M instructions - config.expected_min_mips = 50.0; // Expect at least 50 MIPS (conservative) + config.expected_min_mips = 5.0; // Expect at least 5 MIPS (tolerant minimum) if (load_game_from_tap(tap_file, config)) { BenchmarkResult result = runBenchmark(config); From deffbf9c2683ae947fdd188fbbf0641d5c8ebef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Hern=C3=A1ndez?= Date: Fri, 16 Jan 2026 16:31:13 +0000 Subject: [PATCH 08/12] Update tests/benchmark_shared.h Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/benchmark_shared.h | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/benchmark_shared.h b/tests/benchmark_shared.h index c1d0905..a8bb088 100644 --- a/tests/benchmark_shared.h +++ b/tests/benchmark_shared.h @@ -39,6 +39,7 @@ class BenchmarkSim : public Z80BusInterface { std::array ram{}; uint64_t tstates{0}; bool cpmMode{false}; + volatile bool finished{false}; BenchmarkSim() : cpu(*this) { } From 6de032507804b57813013c135d323634af63158e Mon Sep 17 00:00:00 2001 From: Jose Hernandez Date: Fri, 16 Jan 2026 16:32:34 +0000 Subject: [PATCH 09/12] Merge origin/master into performance_improvements: resolve conflicts, keep header-only core, align tests and CMake, remove legacy src/z80.cpp --- tests/z80_game_test.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/z80_game_test.cpp b/tests/z80_game_test.cpp index b061ca5..6fed6fb 100644 --- a/tests/z80_game_test.cpp +++ b/tests/z80_game_test.cpp @@ -115,8 +115,8 @@ int main(int argc, char* argv[]) { for (const auto& tap_file : tap_files) { BenchmarkConfig config; // Set default expectations - config.instructions = 5000000; // Run for 5M instructions - config.expected_min_mips = 5.0; // Expect at least 5 MIPS (tolerant minimum) + config.instructions = 5000000; // Run for 5M instructions + config.expected_min_mips = 5.0; // Expect at least 5 MIPS (tolerant minimum) if (load_game_from_tap(tap_file, config)) { BenchmarkResult result = runBenchmark(config); From c1aa96d4cf31c50981dbc6ba43a89df91c680786 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Hern=C3=A1ndez?= Date: Fri, 16 Jan 2026 23:14:37 +0000 Subject: [PATCH 10/12] Update tests/z80_sim_test.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/z80_sim_test.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/z80_sim_test.cpp b/tests/z80_sim_test.cpp index 661ca53..f00b5e7 100644 --- a/tests/z80_sim_test.cpp +++ b/tests/z80_sim_test.cpp @@ -193,8 +193,8 @@ void Z80SimTest::runTest(std::ifstream* fileStream) { std::cout << "Cumulative test time: " << static_cast(total_ms) / 1000.0f << " sec\n"; if (num_tests > 0) { std::cout << "Average time per test: " << static_cast(total_ms) / num_tests / 1000.0f << " sec\n\n"; - std::cout << "\xE2\x9C\x93 Tests passed: " << num_tests - failed << "\n"; - std::cout << "\xE2\x9C\x97 Tests failed: " << failed << "\n"; + std::cout << "✓ Tests passed: " << num_tests - failed << "\n"; + std::cout << "✗ Tests failed: " << failed << "\n"; } std::cout << "Total elapsed time: " << static_cast(elapsed_ms) / 1000.0f << " sec\n"; std::cout.flush(); From db1b786b46d3196e000d397dfe5842c2b9348ff4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Hern=C3=A1ndez?= Date: Fri, 16 Jan 2026 23:15:39 +0000 Subject: [PATCH 11/12] Update tests/z80_game_test.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/z80_game_test.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/z80_game_test.cpp b/tests/z80_game_test.cpp index 6fed6fb..b455eae 100644 --- a/tests/z80_game_test.cpp +++ b/tests/z80_game_test.cpp @@ -157,7 +157,12 @@ int main(int argc, char* argv[]) { std::cout << "Average Performance: " << std::fixed << std::setprecision(2) << avg_mips << " MIPS" << '\n'; } - return (failed == 0 && valid_results > 0) ? 0 : 1; + if (results.empty()) { + std::cout << "No benchmarks were run (no valid TAP files found)." << '\n'; + return 0; + } + + return (failed == 0) ? 0 : 1; } catch (const std::exception& e) { std::cerr << "Error: " << e.what() << '\n'; return 1; From 1aa0e71c76ec3fa2b89af28f39b971db2da0d4a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Hern=C3=A1ndez?= Date: Fri, 16 Jan 2026 23:16:13 +0000 Subject: [PATCH 12/12] Update tests/CMakeLists.txt Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d87c57c..2c2aa7a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -29,7 +29,7 @@ endif() add_test( NAME z80_sim_test - COMMAND ${CMAKE_COMMAND} -E env CTEST_INTERACTIVE_DEBUG_MODE=1 $ + COMMAND $ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} )