| @@ -207,9 +207,6 @@ const char* gamegear_get_exception(GameGear *gg) | |||||
| case Z80_EXC_UNIMPLEMENTED_OPCODE: | case Z80_EXC_UNIMPLEMENTED_OPCODE: | ||||
| SET_EXC("unimplemented opcode: 0x%02X", gg->cpu.exc_data) | SET_EXC("unimplemented opcode: 0x%02X", gg->cpu.exc_data) | ||||
| break; | break; | ||||
| case Z80_EXC_IO_ERROR: | |||||
| SET_EXC("I/O error on port 0x%02X", gg->cpu.exc_data) | |||||
| break; | |||||
| default: | default: | ||||
| SET_EXC("unknown exception") | SET_EXC("unknown exception") | ||||
| break; | break; | ||||
| @@ -2,6 +2,7 @@ | |||||
| Released under the terms of the MIT License. See LICENSE for details. */ | Released under the terms of the MIT License. See LICENSE for details. */ | ||||
| #include "io.h" | #include "io.h" | ||||
| #include "logging.h" | |||||
| /* | /* | ||||
| Initialize an IO object. | Initialize an IO object. | ||||
| @@ -17,7 +18,12 @@ void io_init(IO *io, VDP *vdp, PSG *psg) | |||||
| */ | */ | ||||
| void io_power(IO *io) | void io_power(IO *io) | ||||
| { | { | ||||
| io->except = false; | |||||
| io->ports[0x00] = 0xC0; // Overseas mode, NTSC | |||||
| io->ports[0x01] = 0x7F; | |||||
| io->ports[0x02] = 0xFF; | |||||
| io->ports[0x03] = 0x00; | |||||
| io->ports[0x04] = 0xFF; | |||||
| io->ports[0x05] = 0x00; | |||||
| } | } | ||||
| /* | /* | ||||
| @@ -29,37 +35,67 @@ bool io_check_irq(IO *io) | |||||
| } | } | ||||
| /* | /* | ||||
| Read from one of the system ports, which are numbered from 0x00 to 0x06. | |||||
| */ | |||||
| static uint8_t read_system_port(IO *io, uint8_t port) | |||||
| { | |||||
| switch (port) { | |||||
| case 0x00: | |||||
| // TODO: MSB is state of START button | |||||
| return (io->ports[port] & 0x7F) | (0 << 7); | |||||
| case 0x01: | |||||
| case 0x02: | |||||
| case 0x03: | |||||
| case 0x04: | |||||
| case 0x05: | |||||
| return io->ports[port]; | |||||
| } | |||||
| return 0xFF; | |||||
| } | |||||
| /* | |||||
| Write to one of the system ports, which are numbered from 0x00 to 0x06. | |||||
| */ | |||||
| static void write_system_port(IO *io, uint8_t port, uint8_t value) | |||||
| { | |||||
| switch (port) { | |||||
| case 0x01: | |||||
| case 0x02: | |||||
| case 0x03: | |||||
| io->ports[port] = value; | |||||
| break; | |||||
| case 0x05: | |||||
| io->ports[port] = value & 0xF8; | |||||
| break; | |||||
| case 0x06: | |||||
| psg_stereo(io->psg, value); | |||||
| break; | |||||
| } | |||||
| } | |||||
| /* | |||||
| Read and return a byte from the given port. | Read and return a byte from the given port. | ||||
| */ | */ | ||||
| uint8_t io_port_read(IO *io, uint8_t port) | uint8_t io_port_read(IO *io, uint8_t port) | ||||
| { | { | ||||
| if (port <= 0x06) { | |||||
| if (port == 0x00) | |||||
| return 0xC0; | |||||
| // TODO: GG specific registers; initial state: C0 7F FF 00 FF 00 FF | |||||
| } else if (port <= 0x3F) { | |||||
| if (port <= 0x06) | |||||
| return read_system_port(io, port); | |||||
| else if (port <= 0x3F) | |||||
| return 0xFF; | return 0xFF; | ||||
| } else if (port <= 0x7F && !(port % 2)) { | |||||
| else if (port <= 0x7F && !(port % 2)) | |||||
| return io->vdp->v_counter; | return io->vdp->v_counter; | ||||
| } else if (port <= 0x7F) { | |||||
| else if (port <= 0x7F) | |||||
| return io->vdp->h_counter; | return io->vdp->h_counter; | ||||
| } else if (port <= 0xBF && !(port % 2)) { | |||||
| else if (port <= 0xBF && !(port % 2)) | |||||
| return vdp_read_data(io->vdp); | return vdp_read_data(io->vdp); | ||||
| } else if (port <= 0xBF) { | |||||
| else if (port <= 0xBF) | |||||
| return vdp_read_control(io->vdp); | return vdp_read_control(io->vdp); | ||||
| } else if (port == 0xCD || port == 0xDC) { | |||||
| // TODO: Return the I/O port A/B register | |||||
| return 0xFF; | |||||
| } else if (port == 0xC1 || port == 0xDD) { | |||||
| // TODO: Return the I/O port B/misc. register | |||||
| else if (port == 0xCD || port == 0xDC) | |||||
| return 0xFF; // TODO: Return the I/O port A/B register | |||||
| else if (port == 0xC1 || port == 0xDD) | |||||
| return 0xFF; // TODO: Return the I/O port B/misc. register | |||||
| else | |||||
| return 0xFF; | return 0xFF; | ||||
| } else { | |||||
| return 0xFF; | |||||
| } | |||||
| io->except = true; | |||||
| io->exc_port = port; | |||||
| return 0; | |||||
| } | } | ||||
| /* | /* | ||||
| @@ -67,28 +103,16 @@ uint8_t io_port_read(IO *io, uint8_t port) | |||||
| */ | */ | ||||
| void io_port_write(IO *io, uint8_t port, uint8_t value) | void io_port_write(IO *io, uint8_t port, uint8_t value) | ||||
| { | { | ||||
| if (port <= 0x06) { | |||||
| // TODO: GG specific registers; initial state: C0 7F FF 00 FF 00 FF | |||||
| goto except; | |||||
| } else if (port <= 0x3F && !(port % 2)) { | |||||
| // TODO: Write to memory control register | |||||
| goto except; | |||||
| } else if (port <= 0x3F) { | |||||
| // TODO: Write to I/O control register | |||||
| goto except; | |||||
| } else if (port <= 0x7F) { | |||||
| if (port <= 0x06) | |||||
| write_system_port(io, port, value); | |||||
| else if (port <= 0x3F && !(port % 2)) | |||||
| return; // TODO: Write to memory control register | |||||
| else if (port <= 0x3F) | |||||
| return; // TODO: Write to I/O control register | |||||
| else if (port <= 0x7F) | |||||
| psg_write(io->psg, value); | psg_write(io->psg, value); | ||||
| } else if (port <= 0xBF && !(port % 2)) { | |||||
| else if (port <= 0xBF && !(port % 2)) | |||||
| vdp_write_data(io->vdp, value); | vdp_write_data(io->vdp, value); | ||||
| } else if (port <= 0xBF) { | |||||
| else if (port <= 0xBF) | |||||
| vdp_write_control(io->vdp, value); | vdp_write_control(io->vdp, value); | ||||
| } else { | |||||
| return; | |||||
| } | |||||
| return; | |||||
| except: | |||||
| io->except = true; | |||||
| io->exc_port = port; | |||||
| } | } | ||||
| @@ -14,8 +14,7 @@ | |||||
| typedef struct { | typedef struct { | ||||
| VDP *vdp; | VDP *vdp; | ||||
| PSG *psg; | PSG *psg; | ||||
| bool except; | |||||
| uint8_t exc_port; | |||||
| uint8_t ports[6]; | |||||
| } IO; | } IO; | ||||
| /* Functions */ | /* Functions */ | ||||
| @@ -41,3 +41,13 @@ void psg_write(PSG *psg, uint8_t byte) | |||||
| (void) psg; | (void) psg; | ||||
| TRACE("PSG ignoring write: 0x%02X", byte) | TRACE("PSG ignoring write: 0x%02X", byte) | ||||
| } | } | ||||
| /* | |||||
| Send a byte to the PSG's stereo control. | |||||
| */ | |||||
| void psg_stereo(PSG *psg, uint8_t byte) | |||||
| { | |||||
| // TODO | |||||
| (void) psg; | |||||
| TRACE("PSG ignoring stereo: 0x%02X", byte) | |||||
| } | |||||
| @@ -19,3 +19,4 @@ void psg_init(PSG*); | |||||
| void psg_free(PSG*); | void psg_free(PSG*); | ||||
| void psg_power(PSG*); | void psg_power(PSG*); | ||||
| void psg_write(PSG*, uint8_t); | void psg_write(PSG*, uint8_t); | ||||
| void psg_stereo(PSG*, uint8_t); | |||||
| @@ -433,43 +433,4 @@ void vdp_dump_registers(const VDP *vdp) | |||||
| DEBUG("- $08: 0x%02X (HS)", regs[0x08]) | DEBUG("- $08: 0x%02X (HS)", regs[0x08]) | ||||
| DEBUG("- $09: 0x%02X (VS)", regs[0x09]) | DEBUG("- $09: 0x%02X (VS)", regs[0x09]) | ||||
| DEBUG("- $0A: 0x%02X (LC)", regs[0x0A]) | DEBUG("- $0A: 0x%02X (LC)", regs[0x0A]) | ||||
| // TODO: remove me! | |||||
| DEBUG("Dumping CRAM:") | |||||
| for (uint8_t i = 0; i < 32; i += 8) { | |||||
| uint16_t w[8]; | |||||
| for (uint8_t j = 0; j < 8; j++) | |||||
| w[j] = get_color(vdp, (i + j) % 16, i & 16); | |||||
| DEBUG("- %04X %04X %04X %04X %04X %04X %04X %04X", | |||||
| w[0], w[1], w[2], w[3], w[4], w[5], w[6], w[7]) | |||||
| } | |||||
| return; | |||||
| DEBUG("Dumping PNT:") | |||||
| for (uint16_t i = 0; i < 18; i++) { | |||||
| uint16_t w[20]; | |||||
| for (uint8_t j = 0; j < 20; j++) | |||||
| w[j] = get_background_tile(vdp, i + 3, j + 6); | |||||
| DEBUG("- %03X %03X %03X %03X %03X %03X %03X %03X %03X %03X" | |||||
| " %03X %03X %03X %03X %03X %03X %03X %03X %03X %03X", | |||||
| w[0x00], w[0x01], w[0x02], w[0x03], w[0x04], w[0x05], w[0x06], | |||||
| w[0x07], w[0x08], w[0x09], w[0x0A], w[0x0B], w[0x0C], w[0x0D], | |||||
| w[0x0E], w[0x0F], w[0x10], w[0x11], w[0x12], w[0x13]) | |||||
| } | |||||
| DEBUG("Dumping PGT:") | |||||
| for (uint16_t i = 0; i < /* 512 */ 32; i++) { | |||||
| uint32_t w[8]; | |||||
| for (uint8_t j = 0; j < 8; j++) | |||||
| w[j] = vdp->vram[32 * i + 4 * j] + | |||||
| (vdp->vram[32 * i + 4 * j + 1] << 8) + | |||||
| (vdp->vram[32 * i + 4 * j + 2] << 16) + | |||||
| (vdp->vram[32 * i + 4 * j + 3] << 24); | |||||
| DEBUG("- 0x%04X: %08X %08X %08X %08X %08X %08X %08X %08X", i, | |||||
| w[0], w[1], w[2], w[3], w[4], w[5], w[6], w[7]) | |||||
| } | |||||
| } | } | ||||
| @@ -130,37 +130,6 @@ static inline uint16_t stack_pop(Z80 *z80) | |||||
| } | } | ||||
| /* | /* | ||||
| Check for errors after an I/O operation. | |||||
| */ | |||||
| static void handle_io_errors(Z80 *z80) | |||||
| { | |||||
| if (z80->io->except) { | |||||
| z80->except = true; | |||||
| z80->exc_code = Z80_EXC_IO_ERROR; | |||||
| z80->exc_data = z80->io->exc_port; | |||||
| } | |||||
| } | |||||
| /* | |||||
| Read and return a byte from the given port, and check for errors. | |||||
| */ | |||||
| static inline uint8_t read_port(Z80 *z80, uint8_t port) | |||||
| { | |||||
| uint8_t value = io_port_read(z80->io, port); | |||||
| handle_io_errors(z80); | |||||
| return value; | |||||
| } | |||||
| /* | |||||
| Write a byte to the given port, and check for errors. | |||||
| */ | |||||
| static inline void write_port(Z80 *z80, uint8_t port, uint8_t value) | |||||
| { | |||||
| io_port_write(z80->io, port, value); | |||||
| handle_io_errors(z80); | |||||
| } | |||||
| /* | |||||
| Extract an 8-bit register from the given opcode and return a pointer to it. | Extract an 8-bit register from the given opcode and return a pointer to it. | ||||
| */ | */ | ||||
| static inline uint8_t* extract_reg(Z80 *z80, uint8_t opcode) | static inline uint8_t* extract_reg(Z80 *z80, uint8_t opcode) | ||||
| @@ -11,7 +11,6 @@ | |||||
| #define Z80_EXC_NOT_POWERED 0 | #define Z80_EXC_NOT_POWERED 0 | ||||
| #define Z80_EXC_UNIMPLEMENTED_OPCODE 1 | #define Z80_EXC_UNIMPLEMENTED_OPCODE 1 | ||||
| #define Z80_EXC_IO_ERROR 2 | |||||
| /* Structs */ | /* Structs */ | ||||
| @@ -1969,7 +1969,7 @@ static uint8_t z80_inst_in_a_n(Z80 *z80, uint8_t opcode) | |||||
| { | { | ||||
| (void) opcode; | (void) opcode; | ||||
| uint8_t port = mmu_read_byte(z80->mmu, ++z80->regs.pc); | uint8_t port = mmu_read_byte(z80->mmu, ++z80->regs.pc); | ||||
| z80->regs.a = read_port(z80, port); | |||||
| z80->regs.a = io_port_read(z80->io, port); | |||||
| z80->regs.pc++; | z80->regs.pc++; | ||||
| return 11; | return 11; | ||||
| } | } | ||||
| @@ -1980,7 +1980,7 @@ static uint8_t z80_inst_in_a_n(Z80 *z80, uint8_t opcode) | |||||
| */ | */ | ||||
| static uint8_t z80_inst_in_r_c(Z80 *z80, uint8_t opcode) | static uint8_t z80_inst_in_r_c(Z80 *z80, uint8_t opcode) | ||||
| { | { | ||||
| uint8_t data = read_port(z80, z80->regs.c); | |||||
| uint8_t data = io_port_read(z80->io, z80->regs.c); | |||||
| if (opcode != 0x70) | if (opcode != 0x70) | ||||
| *extract_reg(z80, opcode) = data; | *extract_reg(z80, opcode) = data; | ||||
| @@ -1996,7 +1996,8 @@ static uint8_t z80_inst_in_r_c(Z80 *z80, uint8_t opcode) | |||||
| static uint8_t z80_inst_ini(Z80 *z80, uint8_t opcode) | static uint8_t z80_inst_ini(Z80 *z80, uint8_t opcode) | ||||
| { | { | ||||
| (void) opcode; | (void) opcode; | ||||
| mmu_write_byte(z80->mmu, z80->regs.hl, read_port(z80, z80->regs.c)); | |||||
| uint8_t data = io_port_read(z80->io, z80->regs.c); | |||||
| mmu_write_byte(z80->mmu, z80->regs.hl, data); | |||||
| set_flags_blockio(z80); | set_flags_blockio(z80); | ||||
| z80->regs.hl++; | z80->regs.hl++; | ||||
| @@ -2025,7 +2026,8 @@ static uint8_t z80_inst_inir(Z80 *z80, uint8_t opcode) | |||||
| static uint8_t z80_inst_ind(Z80 *z80, uint8_t opcode) | static uint8_t z80_inst_ind(Z80 *z80, uint8_t opcode) | ||||
| { | { | ||||
| (void) opcode; | (void) opcode; | ||||
| mmu_write_byte(z80->mmu, z80->regs.hl, read_port(z80, z80->regs.c)); | |||||
| uint8_t data = io_port_read(z80->io, z80->regs.c); | |||||
| mmu_write_byte(z80->mmu, z80->regs.hl, data); | |||||
| set_flags_blockio(z80); | set_flags_blockio(z80); | ||||
| z80->regs.hl--; | z80->regs.hl--; | ||||
| @@ -2055,7 +2057,7 @@ static uint8_t z80_inst_out_n_a(Z80 *z80, uint8_t opcode) | |||||
| { | { | ||||
| (void) opcode; | (void) opcode; | ||||
| uint8_t port = mmu_read_byte(z80->mmu, ++z80->regs.pc); | uint8_t port = mmu_read_byte(z80->mmu, ++z80->regs.pc); | ||||
| write_port(z80, port, z80->regs.a); | |||||
| io_port_write(z80->io, port, z80->regs.a); | |||||
| z80->regs.pc++; | z80->regs.pc++; | ||||
| return 11; | return 11; | ||||
| } | } | ||||
| @@ -2068,7 +2070,7 @@ static uint8_t z80_inst_out_n_a(Z80 *z80, uint8_t opcode) | |||||
| static uint8_t z80_inst_out_c_r(Z80 *z80, uint8_t opcode) | static uint8_t z80_inst_out_c_r(Z80 *z80, uint8_t opcode) | ||||
| { | { | ||||
| uint8_t value = opcode != 0x71 ? *extract_reg(z80, opcode) : 0; | uint8_t value = opcode != 0x71 ? *extract_reg(z80, opcode) : 0; | ||||
| write_port(z80, z80->regs.c, value); | |||||
| io_port_write(z80->io, z80->regs.c, value); | |||||
| z80->regs.pc++; | z80->regs.pc++; | ||||
| return 12; | return 12; | ||||
| } | } | ||||
| @@ -2080,7 +2082,8 @@ static uint8_t z80_inst_out_c_r(Z80 *z80, uint8_t opcode) | |||||
| static uint8_t z80_inst_outi(Z80 *z80, uint8_t opcode) | static uint8_t z80_inst_outi(Z80 *z80, uint8_t opcode) | ||||
| { | { | ||||
| (void) opcode; | (void) opcode; | ||||
| write_port(z80, z80->regs.c, mmu_read_byte(z80->mmu, z80->regs.hl)); | |||||
| uint8_t data = mmu_read_byte(z80->mmu, z80->regs.hl); | |||||
| io_port_write(z80->io, z80->regs.c, data); | |||||
| set_flags_blockio(z80); | set_flags_blockio(z80); | ||||
| z80->regs.hl++; | z80->regs.hl++; | ||||
| @@ -2109,7 +2112,8 @@ static uint8_t z80_inst_otir(Z80 *z80, uint8_t opcode) | |||||
| static uint8_t z80_inst_outd(Z80 *z80, uint8_t opcode) | static uint8_t z80_inst_outd(Z80 *z80, uint8_t opcode) | ||||
| { | { | ||||
| (void) opcode; | (void) opcode; | ||||
| write_port(z80, z80->regs.c, mmu_read_byte(z80->mmu, z80->regs.hl)); | |||||
| uint8_t data = mmu_read_byte(z80->mmu, z80->regs.hl); | |||||
| io_port_write(z80->io, z80->regs.c, data); | |||||
| set_flags_blockio(z80); | set_flags_blockio(z80); | ||||
| z80->regs.hl--; | z80->regs.hl--; | ||||