{"id":41056201,"url":"https://github.com/suzukiplan/z80","last_synced_at":"2026-01-22T11:37:43.250Z","repository":{"id":43751233,"uuid":"204284393","full_name":"suzukiplan/z80","owner":"suzukiplan","description":"Single header Z80 emulator for C++ (C++11 or later)","archived":false,"fork":false,"pushed_at":"2023-12-13T05:40:12.000Z","size":1043,"stargazers_count":30,"open_issues_count":0,"forks_count":5,"subscribers_count":3,"default_branch":"master","last_synced_at":"2023-12-13T06:32:14.951Z","etag":null,"topics":["8080","8bit","c-plus-plus","cpu","emulator","z80","z80-emulator"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/suzukiplan.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2019-08-25T11:22:21.000Z","updated_at":"2023-12-10T04:21:10.000Z","dependencies_parsed_at":"2023-12-13T06:30:26.116Z","dependency_job_id":null,"html_url":"https://github.com/suzukiplan/z80","commit_stats":null,"previous_names":[],"tags_count":30,"template":null,"template_full_name":null,"purl":"pkg:github/suzukiplan/z80","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/suzukiplan%2Fz80","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/suzukiplan%2Fz80/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/suzukiplan%2Fz80/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/suzukiplan%2Fz80/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/suzukiplan","download_url":"https://codeload.github.com/suzukiplan/z80/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/suzukiplan%2Fz80/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28662205,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-22T01:17:37.254Z","status":"online","status_checked_at":"2026-01-22T02:00:07.137Z","response_time":144,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["8080","8bit","c-plus-plus","cpu","emulator","z80","z80-emulator"],"created_at":"2026-01-22T11:37:43.164Z","updated_at":"2026-01-22T11:37:43.240Z","avatar_url":"https://github.com/suzukiplan.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SUZUKI PLAN - Z80 Emulator \n[![suzukiplan](https://circleci.com/gh/suzukiplan/z80.svg?style=svg)](https://app.circleci.com/pipelines/github/suzukiplan/z80)\n\nThe Z80 is an 8-bit CPU developed by Zilog corporation, released in 1976, and widely used in computers and game consoles in the 1980s.\nIt is not just a relic of the past, but continues to be used in embedded systems that require accuracy in processing execution time, such as real-time systems.\n\n**SUZUKI PLAN - Z80 Emulator** is an emulator under development based on the following design guidelines to support the development of programs and/or emulators using Z80, 8080:\n\n**(FOUR EASY GUIDELINES FOR EASILY)**\n\n1. Make emulator implementation `EASY` \u0026 simple (Realized by providing single header: [z80.hpp](z80.hpp))\n2. `EASILY` debugging the Z80 programs (Realized by having [dynamic disassemble feature](#dynamic-disassemble-for-debug))\n3. Highly readable and `EASILY` customizable (priority for readability over performance)\n4. Provide under the license that `EASY` to adopt in various programs ([MIT](LICENSE.txt))\n\n\u003e Since I do not have deep knowledge about Z80 myself, I'm implementing it with reference to the information on the following web sites:\n\u003e\n\u003e - [Z80 CPU User Manual - Zilog](https://www.zilog.com/manage_directlink.php?filepath=docs/z80/um0080\u0026extn=.pdf)\n\u003e - [8 ビット CPU Z80](http://www.yamamo10.jp/yamamoto/comp/Z80/index.php) of [山本研究所](http://www.yamamo10.jp/yamamoto/index.html)\n\u003e - [Z80 Code Refference](http://mydocuments.g2.xrea.com/html/p6/z80ref.html) of [Bookworm's Library](http://mydocuments.g2.xrea.com/index.html)\n\u003e - [Zilog Z80 DAA Result Table](http://ver0.sakura.ne.jp/doc/daa.html) of [Version 0](http://ver0.sakura.ne.jp/)\n\n[z80.hpp](z80.hpp) passes all `zexdoc` and `zexall` tests. ([See the detail](test-ex))\n\n## How to test\n\n### Prerequests\n\nYou can test on the any 32bit/64bit platform/OS (UNIX, Linux, macOS...etc) but needs following middlewares:\n\n- Git\n- GNU Make\n- clang\n- clang-format\n\n### Build (UNIX, Linux, macOS)\n\n```\ngit clone https://github.com/suzukiplan/z80.git\ncd z80\nmake\n```\n\n## Minimum usage\n\n### 1. Include\n\n```c++\n#include \"z80.hpp\"\n```\n\n### 2. Implement MMU \u0026 access callback\n\n```c++\nclass MMU\n{\n  public:\n    unsigned char RAM[0x10000]; // 64KB memory (minimum)\n    unsigned char IO[0x100]; // 256bytes port\n\n    MMU()\n    {\n        memset(\u0026RAM, 0, sizeof(RAM));\n        memset(\u0026IO, 0, sizeof(IO));\n    }\n};\n\n// memory read request per 1 byte from CPU\nunsigned char readByte(void* arg, unsigned short addr)\n{\n    // NOTE: implement switching procedure here if your MMU has bank switch feature\n    return ((MMU*)arg)-\u003eRAM[addr];\n}\n\n// memory write request per 1 byte from CPU\nvoid writeByte(void* arg, unsigned short addr, unsigned char value)\n{\n    // NOTE: implement switching procedure here if your MMU has bank switch feature\n    ((MMU*)arg)-\u003eRAM[addr] = value;\n}\n\n// IN operand request from CPU\nunsigned char inPort(void* arg, unsigned short port)\n{\n    return ((MMU*)arg)-\u003eIO[port];\n}\n\n// OUT operand request from CPU\nvoid outPort(void* arg, unsigned short port, unsigned char value)\n{\n    ((MMU*)arg)-\u003eIO[port] = value;\n}\n```\n\n### 3. Make Z80 instance\n\n```c++\n    MMU mmu;\n    /**\n     * readByte: callback of memory read request\n     * writeByte: callback of memory write request\n     * inPort: callback of input request\n     * outPort: callback of output request\n     * \u0026mmu: 1st argument of the callbacks\n     */\n    Z80 z80(readByte, writeByte, inPort, outPort, \u0026mmu);\n```\n\n#### 3-1. Cases when want to use 16bit port \n\nNote that by default, only the lower 8 bits of the port number can be obtained in the callback argument, and the upper 8 bits must be referenced from register B.\n\nIf you want to get it in 16 bits from the beginning, please initialize with `returnPortAs16Bits` (6th argument) to `true` as follows:\n\n```c++\n    Z80 z80(readByte, writeByte, inPort, outPort, \u0026mmu, true);\n```\n\n#### 3-2. Cases when performance-sensitive\n\nNormally, `std::function` is used for callbacks, but in more performance-sensitive cases or `std::function` is not exist environment (ex: RaspberryPi Baremetal), all can be replaced with function pointers by specifying the compile option `-DZ80_NO_FUNCTIONAL`.\n\n\u003e The following article (in Japanese) provides a performance comparison between function pointers and `std::function`:\n\u003e\n\u003e https://qiita.com/suzukiplan/items/e459bf47f6c659acc74d\n\u003e\n\u003e The above article gives an example of the time it took to execute 100 million times call of function-pointer and three patterns of `std::function` calls (`bind`, `direct`, `lambda`)  as following:\n\u003e\n\u003e |Optimization Option|function pointer|`bind`|`direct`|`lambda`|\n\u003e |:-:|-:|-:|-:|-:|\n\u003e |none|195,589μs|5,574,856μs|2,570,016μs|2,417,802μs|\n\u003e |-O|184,692μs|2,293,151μs|827,113μs|580,442μs|\n\u003e |-O2|154,206μs|197,683μs|209,626μs|167,703μs|\n\u003e |-Ofast|154,332μs|250,490μs|255,401μs|164,773μs|\n\n### 4. Execute\n\n```c++\n    // when executing about 1234Hz\n    int actualExecutedClocks = z80.execute(1234);\n```\n\n#### 4-1. Actual executed clocks\n\n- The `execute` method repeats the execution of an instruction until the total number of clocks from the time of the call is greater than or equal to the value specified in the \"clocks\" argument.\n- If a value less than or equal to 0 is specified, no instruction is executed at all.\n- If you want single operand execution, you can specify 1.\n\n#### 4-2. Interruption of execution\n\nExecution of the `requestBreak` method can abort the `execute` at an arbitrary time.\n\n\u003e A typical 8-bit game console emulator implementation that I envision:\n\u003e\n\u003e - Implement synchronization with Video Display Processor (VDP) and other devices (sound modules, etc.) in `consumeClock` callback.\n\u003e - Call `requestBreak` when V-SYNC signal is received from VDP.\n\u003e - Call `execute` with a large value such as `INT_MAX`.\n\n#### 4-3. Example\n\nCode: [test/test-execute.cpp](test/test-execute.cpp)\n\n```c++\n#include \"z80.hpp\"\n\nint main()\n{\n    unsigned char rom[256] = {\n        0x01, 0x34, 0x12, // LD BC, $1234\n        0x3E, 0x01,       // LD A, $01\n        0xED, 0x79,       // OUT (C), A\n        0xED, 0x78,       // IN A, (C)\n        0xc3, 0x09, 0x00, // JMP $0009\n    };\n    Z80 z80([=](void* arg, unsigned short addr) { return rom[addr \u0026 0xFF]; },\n            [](void* arg, unsigned short addr, unsigned char value) {},\n            [](void* arg, unsigned short port) { return 0x00; },\n            [](void* arg, unsigned short port, unsigned char value) {\n                // request break the execute function after output port operand has executed.\n                ((Z80*)arg)-\u003erequestBreak();\n            }, \u0026z80);\n    z80.setDebugMessage([](void* arg, const char* msg) { puts(msg); });\n    z80.setConsumeClockCallback([](void* arg, int clocks) { printf(\"consume %dHz\\n\", clocks); });\n    puts(\"===== execute(0) =====\");\n    printf(\"actualExecuteClocks = %dHz\\n\", z80.execute(0));\n    puts(\"===== execute(1) =====\");\n    printf(\"actualExecuteClocks = %dHz\\n\", z80.execute(1));\n    puts(\"===== execute(0x7FFFFFFF) =====\");\n    printf(\"actualExecuteClocks = %dHz\\n\", z80.execute(0x7FFFFFFF));\n    return 0;\n}\n```\n\nResult is following:\n\n```\n===== execute(0) =====\nactualExecuteClocks = 0Hz ... 0 is specified, no instruction is executed at all\n===== execute(1) =====\nconsume 2Hz\nconsume 2Hz\nconsume 3Hz\nconsume 3Hz\n[0000] LD BC\u003c$0000\u003e, $1234\nactualExecuteClocks = 10Hz ... specify 1 to single step execution\n===== execute(0x7FFFFFFF) =====\nconsume 2Hz\nconsume 2Hz\nconsume 3Hz\n[0003] LD A\u003c$FF\u003e, $01\nconsume 2Hz\nconsume 2Hz\nconsume 4Hz\n[0005] OUT (C\u003c$34\u003e), A\u003c$01\u003e\nconsume 4Hz\nactualExecuteClocks = 19Hz ... 2147483647Hz+ is not executed but interrupted after completion of OUT due to requestBreak during OUT execution\n```\n\n### 5. Generate interrupt\n\n#### IRQ; Interrupt Request\n\n```c++\n    z80.generateIRQ(vector);\n```\n\n#### NMI; Non Maskable Interrupt\n\n```c++\n    z80.generateNMI(address);\n```\n\n## Optional features\n\n### Dynamic disassemble (for debug)\n\nYou can acquire the debug messages with `setDebugMessage`.\nDebug message contains dynamic disassembly results step by step.\n\n```c++\n    z80.setDebugMessage([](void* arg, const char* message) -\u003e void {\n        time_t t1 = time(NULL);\n        struct tm* t2 = localtime(\u0026t1);\n        printf(\"%04d.%02d.%02d %02d:%02d:%02d %s\\n\",\n               t2-\u003etm_year + 1900, t2-\u003etm_mon, t2-\u003etm_mday, t2-\u003etm_hour,\n               t2-\u003etm_min, t2-\u003etm_sec, message);\n    });\n```\n\n- call `resetDebugMessage` if you want to remove the detector.\n- call `setDebugMessageFP` if you want to use the function pointer.\n\n### Use break point\n\nIf you want to execute processing just before executing an instruction of specific program counter value _(in this ex: \\$008E)_, you can set a breakpoint as follows:\n\n```c++\n    z80.addBreakPoint(0x008E, [](void* arg) -\u003e void {\n        printf(\"Detect break point! (PUSH ENTER TO CONTINUE)\");\n        char buf[80];\n        fgets(buf, sizeof(buf), stdin);\n    });\n```\n\n- `addBreakPoint` can set multiple breakpoints for the same address.\n- call `removeBreakPoint` or `removeAllBreakPoints` if you want to remove the break point(s).\n- call `addBreakPointFP` if you want to use the function pointer.\n\n### Use break operand\n\nIf you want to execute processing just before executing an instruction of specific operand number, you can set a breakpoint as follows:\n\n```c++\n    // break when NOP ... $00\n    z80.addBreakOperand(0x00, [](void* arg, unsigned char* opcode, int opcodeLength) -\u003e void {\n        printf(\"Detect break operand! (PUSH ENTER TO CONTINUE)\");\n        char buf[80];\n        fgets(buf, sizeof(buf), stdin);\n    });\n\n    // break when RLC B ... $CB $00\n    z80.addBreakOperand(0xCB, 0x00, [](void* arg, unsigned char* opcode, int opcodeLength) -\u003e void {\n        printf(\"Detect break operand! (PUSH ENTER TO CONTINUE)\");\n        char buf[80];\n        fgets(buf, sizeof(buf), stdin);\n    });\n\n    // break when RLC (IX+d) ... $DD $CB, $06\n    z80.addBreakOperand(0xDD, 0xCB, 0x06, [](void* arg, unsigned char* opcode, int opcodeLength) -\u003e void {\n        printf(\"Detect break operand! (PUSH ENTER TO CONTINUE)\");\n        char buf[80];\n        fgets(buf, sizeof(buf), stdin);\n    });\n```\n\n- the opcode and length at break are stored in `opcode` and `opcodeLength` when the callback is made.\n- `addBreakOperand` can set multiple breakpoints for the same operand.\n- call `removeBreakOperand` or `removeAllBreakOperands` if you want to remove the break operand(s).\n- call `addBreakOperandFP` if you want to use the function pointer.\n\n### Detect clock consuming\n\nIf you want to implement stricter synchronization, you can capture the CPU clock consumption timing as follows:\n\n```c++\n    z80.setConsumeClockCallback([](void* arg, int clock) -\u003e void {\n        printf(\"consumed: %dHz\\n\", clock);\n    });\n```\n\n- call `resetConsumeClockCallback` if you want to remove the detector.\n- call `setConsumeClockCallbackFP` if you want to use the function pointer.\n\n\u003e With this callback, the CPU cycle (clock) can be synchronized in units of 3 to 4 Hz, and while the execution of a single Z80 instruction requires approximately 10 to 20 Hz of CPU cycle (time), the SUZUKI PLAN - Z80 Emulator can synchronize the CPU cycle (time) for fetch, execution, write back, etc. However, the SUZUKI PLAN - Z80 Emulator can synchronize fetches, executions, writes, backs, etc. in smaller units. This makes it easy to implement severe timing emulation.\n\n### If implement quick save/load\n\nSave the member variable `reg` when quick saving:\n\n```c++\n    fwrite(\u0026z80.reg, sizeof(z80.reg), 1, fp);\n```\n\nExtract to the member variable `reg` when quick loading:\n\n```c++\n    fread(\u0026z80.reg, sizeof(z80.reg), 1, fp);\n```\n\n### Handling of CALL instructions\n\nThe occurrence of the branches by the CALL instructions can be captured by the CallHandler.\nCallHandler will be called back immediately **after** a branch by a CALL instruction occurs.\n\n\u003e CallHandle will called back after the return address is stacked in the RAM.\n\n```c++\n    z80.addCallHandler([](void* arg) -\u003e void {\n        printf(\"Executed a CALL instruction:\\n\");\n        printf(\"- Branched to: $%04X\\n\", ((Z80*)arg)-\u003ereg.PC);\n        unsigned short sp = ((Z80*)arg)-\u003ereg.SP;\n        unsigned short returnAddr = ((Z80*)arg)-\u003ereadByte(sp + 1);\n        returnAddr \u003c\u003c= 8;\n        returnAddr |= ((Z80*)arg)-\u003ereadByte(sp);\n        printf(\"- Return to: $%04X\\n\", returnAddr);\n    });\n```\n\n- `addCallHandler` can set multiple CallHandlers.\n- call `removeAllCallHandlers` if you want to remove the CallHandler(s).\n- call `addCallHandlerFP` if you want to use the function pointer.\n- CallHandler also catches branches caused by interrupts.\n- In the case of a condition-specified branch instruction, only the case where the branch is executed is callbacked.\n\n### Handling of RET instructions\n\nThe occurrence of the branches by the RET instructions can be captured by the ReturnHandler.\nReturnHandler will be called back immediately **before** a branch by a RET instruction occurs.\n\n\u003e ReturnHandle will called back while the return address is stacked in the RAM.\n\n```c++\n    z80.addReturnHandler([](void* arg) -\u003e void {\n        printf(\"Detected a RET instruction:\\n\");\n        printf(\"- Branch from: $%04X\\n\", ((Z80*)arg)-\u003ereg.PC);\n        unsigned short sp = ((Z80*)arg)-\u003ereg.SP;\n        unsigned short returnAddr = ((Z80*)arg)-\u003ereadByte(sp + 1);\n        returnAddr \u003c\u003c= 8;\n        returnAddr |= ((Z80*)arg)-\u003ereadByte(sp);\n        printf(\"- Return to: $%04X\\n\", returnAddr);\n    });\n```\n\n- `addReturnHandler` can set multiple ReturnHandlers.\n- call `removeAllReturnHandlers` if you want to remove the ReturnHandler(s).\n- call `addReturnHandlerFP` if you want to use the function pointer.\n- In the case of a condition-specified branch instruction, only the case where the branch is executed is callbacked.\n\n## Advanced Compile Flags\n\nThere is a compile flag that disables certain features in order to adapt to environments with poor performance environments, i.e: Arduino or ESP32:\n\n|Compile Flag|Feature|\n|:-|:-|\n|`-DZ80_DISABLE_DEBUG`|disable `setDebugMessage` method|\n|`-DZ80_DISABLE_BREAKPOINT`|disable `addBreakPoint` and `addBreakOperand` methods|\n|`-DZ80_DISABLE_NESTCHECK`|disable `addCallHandler` and `addReturnHandler` methods|\n|`-DZ80_CALLBACK_WITHOUT_CHECK`|Omit the check process when calling `consumeClock` callback (NOTE: Crashes if `setConsumeClock` is not done)|\n|`-DZ80_CALLBACK_PER_INSTRUCTION`|Calls `consumeClock` callback on an instruction-by-instruction basis (NOTE: two or more instructions when interrupting)|\n|`-DZ80_UNSUPPORT_16BIT_PORT`|Reduces extra branches by always assuming the port number to be 8 bits|\n|`-DZ80_NO_FUNCTIONAL`|Do not use `std::function` in the callbacks (use function pointer)|\n|`-DZ80_NO_EXCEPTION`|Do not throw exceptions|\n\n## License\n\n[MIT](LICENSE.txt)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsuzukiplan%2Fz80","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsuzukiplan%2Fz80","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsuzukiplan%2Fz80/lists"}