跟踪这里。
我用z80编写了一个部分C++仿真器。它有相当一部分的指令可以重用、实现,但我在重复代码方面遇到了一些问题。
我还想指出,这是我的第一个C++项目;在此之前,我主要讨论了C和程序集。
emulate.cpp:
#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:
#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:
#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
#endiftools.cpp:
#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:
#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版本构建的程序在退出时打印寄存器:
#!/bin/sh
printf '\001\275\274\021\337\336\041\316\315\061\255\336\377' > test我想指出的是,在稍后的实现中(当我完成了15/16的实现时),rp2将用于push和pop,这样就可以保存af寄存器(累加器和标志)。
而且,对于我已经实现的指令,一切似乎都很正常(就像在使用gdb和std::cout时,寄存器值似乎是正确的)。
我主要感兴趣的是:
tools.cpp中的大多数函数在修改寄存器后都必须调用uncombine或combine。有办法绕道吗?关于rrc8函数呢?发布于 2019-11-26 00:40:31
以下是一些简短的观察:
#define。它们应该是操作码enum的一部分,或者声明为constexpr int。mem分配65,535字节,而应该分配65,536字节。访问mem[0xFFFF]的尝试将导致未定义的行为,因为它已经过了分配空间的末尾。if((unsigned)argc - 2 > 0)有点模糊。if (argc != 2)怎么了?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变成私有的,并将访问器添加到其中。z80对象,但是您应该=delete复制和移动构造函数和赋值运算符。在堆栈溢出上看到这个问题。https://codereview.stackexchange.com/questions/232962
复制相似问题