首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >用Z80编写的部分Zilog Z80仿真器

用Z80编写的部分Zilog Z80仿真器
EN

Code Review用户
提问于 2019-11-25 18:56:01
回答 1查看 339关注 0票数 10

跟踪这里

我用z80编写了一个部分C++仿真器。它有相当一部分的指令可以重用、实现,但我在重复代码方面遇到了一些问题。

我还想指出,这是我的第一个C++项目;在此之前,我主要讨论了C和程序集。

emulate.cpp

代码语言:javascript
复制
#include <stdexcept>
#include "z80emu.hpp"
#include "opcodes.h"

#ifndef NDEBUG
# include <iostream>
using std::cout;
using std::endl;
#endif

namespace z80emu {
    // return value: number of instructions executed
    uint16_t z80::emulate()
    {
        reg *rp[] =
        {
            ®s.bc,
            ®s.de,
            ®s.hl,
            ®s.sp
        };
        reg *rp2[] =
        {
            ®s.bc,
            ®s.de,
            ®s.hl,
            ®s.af
        };
        uint16_t inst = 0;

        for(;;)
        {
            switch(mem[regs.pc])
            {
                case NOP:
                    break;
                case LD_BC_IMM:
                case LD_DE_IMM:
                case LD_HL_IMM:
                case LD_SP_IMM:
                    ld16imm(mem[regs.pc] >> 4, rp);
                    break;
                case LD_DBC_A:
                case LD_DDE_A:
                    deref16_u8(mem[regs.pc] >> 4, rp) = regs.af.high;
                    break;
                case INC_BC:
                case INC_DE:
                case INC_HL:
                case INC_SP:
                    inc16(mem[regs.pc] >> 4, rp);
                    break;
                case DEC_BC:
                case DEC_DE:
                case DEC_HL:
                case DEC_SP:
                    dec16(mem[regs.pc] >> 4, rp);
                    break;
                case INC_B:
                case INC_C:
                case INC_D:
                case INC_E:
                case INC_H:
                case INC_L:
                    inc8(mem[regs.pc], rp);
                    break;
                case DEC_B:
                case DEC_C:
                case DEC_D:
                case DEC_E:
                case DEC_H:
                case DEC_L:
                    dec8(mem[regs.pc], rp);
                    break;
                case LD_B_IMM:
                case LD_C_IMM:
                case LD_D_IMM:
                case LD_E_IMM:
                case LD_H_IMM:
                case LD_L_IMM:
                    ld8imm(mem[regs.pc], rp);
                    break;
                case RRCA:
                    rrc8(mem[regs.pc], rp);
                    break;
                case EX_AF_AF:
                    regs.af.exchange();
                    break;
                case ADD_HL_BC:
                case ADD_HL_DE:
                case ADD_HL_HL:
                case ADD_HL_SP:
                    regs.hl.combined += rp[mem[regs.pc] >> 4]->combined;
                    regs.hl.uncombine();
                    break;
                case LD_A_DBC:
                case LD_A_DDE:
                    regs.af.high = deref16_u8(mem[regs.pc] >> 4, rp);
                    regs.af.combine();
                    break;
                default:
#ifndef NDEBUG
                    cout << std::hex << std::showbase
                         << "af: " << regs.af.combined << endl
                         << "af': " << regs.af.exx << endl
                         << "bc: " << regs.bc.combined << endl
                         << "bc': " << regs.bc.exx << endl
                         << "de: " << regs.de.combined << endl
                         << "de': " << regs.de.exx << endl
                         << "hl: " << regs.hl.combined << endl
                         << "hl': " << regs.hl.exx << endl
                         << "sp: " << regs.sp.combined << endl
                         << "a: " << +regs.af.high << endl
                         << "f: " << +regs.af.low << endl
                         << "b: " << +regs.bc.high << endl
                         << "c: " << +regs.bc.low << endl
                         << "d: " << +regs.de.high << endl
                         << "e: " << +regs.de.low << endl
                         << "h: " << +regs.hl.high << endl
                         << "l: " << +regs.hl.low << endl;
#endif
                    throw std::logic_error("Unimplemented opcode!");
            }
            regs.pc++;
            inst++;
        }
        return inst;
    }
}

main.cpp

代码语言:javascript
复制
#include <cerrno>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <exception>
#include "z80emu.hpp"

void usage(const char *prog_name);

int main(int argc, const char **argv)
{
    z80emu::z80 z80;
    std::ifstream infile;
    uint16_t file_size;

    if((unsigned)argc - 2 > 0)
    {
         usage(argv[0]);
         return EXIT_FAILURE;
    }

    infile.open(argv[1], std::ifstream::in | std::ifstream::binary);
    if(!infile.good())
    {
        std::cerr << "Opening " << argv[1] << " failed: "
                  << std::strerror(errno) << std::endl;
    }

    file_size = infile.seekg(0, infile.end).tellg();
    infile.seekg(0, infile.beg);

    infile.read((char *)z80.mem, file_size);

    try {
        z80.emulate();
    } catch(std::exception &e) {
        std::cerr << "Emulation failed: " << e.what() << std::endl;
        return EXIT_FAILURE;
    }

    return 0;
}

void usage(const char *progname)
{
    std::cout << "  Usage: " << progname << " z80-prog" << std::endl;
}

opcodes.h

代码语言:javascript
复制
#ifndef Z80EMU_OPCODES_H
#define Z80EMU_OPCODES_H 1

#define NOP     0x00
#define LD_BC_IMM   0x01
#define LD_DBC_A    0x02
#define INC_BC      0x03
#define INC_B       0x04
#define DEC_B       0x05
#define LD_B_IMM    0x06
#define RLCA        0x07
#define EX_AF_AF    0x08
#define ADD_HL_BC   0x09
#define LD_A_DBC    0x0a
#define DEC_BC      0x0b
#define INC_C       0x0c
#define DEC_C       0x0d
#define LD_C_IMM    0x0e
#define RRCA        0x0f
#define DJNZ_IMM    0x10
#define LD_DE_IMM   0x11
#define LD_DDE_A    0x12
#define INC_DE      0x13
#define INC_D       0x14
#define DEC_D       0x15
#define LD_D_IMM    0x16
#define RLA     0x17
#define JR_IMM      0x18
#define ADD_HL_DE   0x19
#define LD_A_DDE    0x1a
#define DEC_DE      0x1b
#define INC_E       0x1c
#define DEC_E       0x1d
#define LD_E_IMM    0x1e
#define RRA     0x1f
#define JR_NZ_IMM   0x20
#define LD_HL_IMM   0x21
#define LD_DIMM_HL  0x22
#define INC_HL      0x23
#define INC_H       0x24
#define DEC_H       0x25
#define LD_H_IMM    0x26
#define DAA     0x27
#define JR_Z_IMM    0x28
#define ADD_HL_HL   0x29
#define LD_HL_DIMM  0x2a
#define DEC_HL      0x2b
#define INC_L       0x2c
#define DEC_L       0x2d
#define LD_L_IMM    0x2e
#define CPL     0x2f
#define JR_NC_IMM   0x30
#define LD_SP_IMM   0x31
#define LD_DIMM_A   0x32
#define INC_SP      0x33
#define INC_DHL     0x34
#define DEC_DHL     0x35
#define LD_DHL_IMM  0x36
#define SCF     0x37
#define JR_C_IMM    0x38
#define ADD_HL_SP   0x39
#define LD_A_DIMM   0x3a
#define DEC_SP      0x3b
#define INC_A       0x3c
#define DEC_A       0x3d
#define LD_A_IMM    0x3e
#define CCF     0x3f

#endif

tools.cpp

代码语言:javascript
复制
#include "z80emu.hpp"

namespace z80emu
{
    // return reference to 8-bit register or memory location
    uint8_t &z80::ref8(uint8_t idx, bool low, reg **tab)
    {
        // idx is the index for the 16-bit register (usually op >> 4)

        // if low is true, return the low part of the variable,
        // otherwise return the high part (usually !!(op & 8))

        switch(idx & 3)
        {
            case 3:
                return low ? regs.af.high : mem[regs.hl.combined];
            default:
                return low ? tab[idx]->low : tab[idx]->high;
        }
    }

    // return reference to a byte in memory
    // specified by a 16-bit pointer
    uint8_t &z80::deref16_u8(uint8_t idx, reg **tab)
    {
        return mem[tab[idx]->combined];
    }

    // load 16-bit register with immediate
    void z80::ld16imm(uint8_t idx, reg **tab)
    {
        // Do these individually because
        // of endianness and memory wrapping
        tab[idx]->low = mem[++regs.pc];
        tab[idx]->high = mem[++regs.pc];
        tab[idx]->combine();
    }

    // load 8-bit register with immediate
    void z80::ld8imm(uint8_t op, reg **tab)
    {
        uint8_t idx = op >> 4;
        ref8(idx, !!(op & 8), tab) = mem[++regs.pc];
        if((idx & 3) != 3) tab[idx]->combine();
        else if(op & 8) regs.af.combine();
    }

    // increment 16-bit register
    void z80::inc16(uint8_t idx, reg **tab)
    {
        tab[idx]->combined++;
        tab[idx]->uncombine();
    }

    // decrement 16-bit register
    void z80::dec16(uint8_t idx, reg **tab)
    {
        tab[idx]->combined--;
        tab[idx]->uncombine();
    }

    // add number to 8-bit register
    void z80::add8(uint8_t op, reg **tab, uint8_t incr)
    {
        uint8_t idx = op >> 4;
        ref8(idx, !!(op & 8), tab) += incr;
        if((idx & 3) != 3) tab[idx]->combine();
        else if(op & 8) regs.af.combine();
    }

    // increment 8-bit register
    void z80::inc8(uint8_t op, reg **tab)
    {
        add8(op, tab, 1);
    }

    // decrement 8-bit register
    void z80::dec8(uint8_t op, reg **tab)
    {
        add8(op, tab, -1);
    }

    void z80::rrc8(uint8_t op, reg **tab)
    {
        uint8_t idx = (op & 0x7) >> 1;
        uint8_t &ref = ref8(idx, op & 1, tab);
        ref = ref >> 1 | (ref & 1) << 7;
        if((idx & 3) != 3) tab[idx]->combine();
        else if((op & 0x7) == 0x7) regs.af.combine();
    }
}

z80emu.hpp

代码语言:javascript
复制
#ifndef Z80EMU_HPP
#define Z80EMU_HPP 1

#if __cplusplus >= 201103L
# include <cstdint>
# include <utility>
using std::uint16_t;
using std::uint8_t;
#else
# include <stdint.h>
# include <algorithm>
#endif

#include <cstring>
#include <vector>

// TODO: Decide whether to use struct or class

namespace z80emu
{
    enum cc
    {
        NZ = 0,
        Z  = 1,
        NC = 2,
        C  = 3,
        PO = 4,
        PE = 5,
        P  = 6,
        M  = 7
    };

    struct reg
    {
        uint8_t high, low;
        uint16_t combined, exx;

        void combine()
        {
            combined = high << 8 | low;
        }
        void uncombine()
        {
            high = combined >> 8;
            low = combined;
        }
        void exchange()
        {
            std::swap(combined, exx);
            uncombine();
        }
    };

#if __cplusplus >= 201103L
    static_assert(sizeof(reg) == 6, "sizeof(reg) != 6");
#endif

    struct registers
    {
        reg af;
        reg bc;
        reg de;
        reg hl;
        reg ix;
        reg iy;
        reg sp;
        reg wz;
        uint16_t pc;
    };

    struct z80
    {
        uint8_t *mem;
        registers regs;

        uint16_t emulate();
        uint8_t &ref8(uint8_t op, bool low, reg **tab);
        uint8_t &deref16_u8(uint8_t op, reg **tab);
        void ld16imm(uint8_t op, reg **tab);
        void ld8imm(uint8_t op, reg **tab);
        void inc16(uint8_t op, reg **tab);
        void dec16(uint8_t op, reg **tab);
        void add8(uint8_t op, reg **tab, uint8_t incr);
        void inc8(uint8_t op, reg **tab);
        void dec8(uint8_t op, reg **tab);
        void rrc8(uint8_t op, reg **tab);

        z80()
        {
            mem = new uint8_t[0xffff];
        }

        ~z80()
        {
            delete[] mem;
        }
    };
}

#endif

用于创建示例程序(文件名test)的Shell脚本,该脚本将导致使用修改后的emulate.cpp版本构建的程序在退出时打印寄存器:

代码语言:javascript
复制
#!/bin/sh

printf '\001\275\274\021\337\336\041\316\315\061\255\336\377' > test

我想指出的是,在稍后的实现中(当我完成了15/16的实现时),rp2将用于pushpop,这样就可以保存af寄存器(累加器和标志)。

而且,对于我已经实现的指令,一切似乎都很正常(就像在使用gdbstd::cout时,寄存器值似乎是正确的)。

我主要感兴趣的是:

  • 我还能做什么“更多的C++”(从c++98到c++2a)?我开始充分使用这种语言了吗?
  • 我能减少代码重复吗?tools.cpp中的大多数函数在修改寄存器后都必须调用uncombinecombine。有办法绕道吗?关于rrc8函数呢?
  • 这里有没有我缺少的C++“最佳实践”?
  • 如果还有什么其他的事情可以改进的话,请告诉我,如果你能更多地关注其他的问题,我会很感激的。
EN

回答 1

Code Review用户

回答已采纳

发布于 2019-11-26 00:40:31

以下是一些简短的观察:

  1. 不要对所有这些编译时间常数(操作码)使用#define。它们应该是操作码enum的一部分,或者声明为constexpr int
  2. 您只为mem分配65,535字节,而应该分配65,536字节。访问mem[0xFFFF]的尝试将导致未定义的行为,因为它已经过了分配空间的末尾。
  3. 拥有一个默认的case标签来捕获未实现的操作码是一件好事。
  4. if((unsigned)argc - 2 > 0)有点模糊。if (argc != 2)怎么了?
  5. 我不知道您要用reg结构做什么,但是您可以消除重复的数据存储以添加字节和字访问器方法。GCC和Clang将优化(-O3)这些对单个指令字节的访问。MSVC对getter也这样做,但是setter并没有完全优化(使用/O2和我尝试过的变体)。结构{ uint16_t组合;uint8_t高() const {返回组合>> 8;} uint8_t low() const {返回组合& 255;} void (Uint8_t v) {组合=(组合& 255) x (v << 8);} void (Uint8_t v) {组合=(组合&255个)\ v;}}如果需要的话,可以将combined变成私有的,并将访问器添加到其中。
  6. 虽然不太可能复制z80对象,但是您应该=delete复制和移动构造函数和赋值运算符。在堆栈溢出上看到这个问题
票数 5
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/232962

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档