| @@ -207,9 +207,6 @@ const char* gamegear_get_exception(GameGear *gg) | |||
| case Z80_EXC_UNIMPLEMENTED_OPCODE: | |||
| SET_EXC("unimplemented opcode: 0x%02X", gg->cpu.exc_data) | |||
| break; | |||
| case Z80_EXC_IO_ERROR: | |||
| SET_EXC("I/O error on port 0x%02X", gg->cpu.exc_data) | |||
| break; | |||
| default: | |||
| SET_EXC("unknown exception") | |||
| break; | |||
| @@ -2,6 +2,7 @@ | |||
| Released under the terms of the MIT License. See LICENSE for details. */ | |||
| #include "io.h" | |||
| #include "logging.h" | |||
| /* | |||
| Initialize an IO object. | |||
| @@ -17,7 +18,12 @@ void io_init(IO *io, VDP *vdp, PSG *psg) | |||
| */ | |||
| 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. | |||
| */ | |||
| 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; | |||
| } else if (port <= 0x7F && !(port % 2)) { | |||
| else if (port <= 0x7F && !(port % 2)) | |||
| return io->vdp->v_counter; | |||
| } else if (port <= 0x7F) { | |||
| else if (port <= 0x7F) | |||
| return io->vdp->h_counter; | |||
| } else if (port <= 0xBF && !(port % 2)) { | |||
| else if (port <= 0xBF && !(port % 2)) | |||
| return vdp_read_data(io->vdp); | |||
| } else if (port <= 0xBF) { | |||
| else if (port <= 0xBF) | |||
| 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; | |||
| } 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) | |||
| { | |||
| 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); | |||
| } else if (port <= 0xBF && !(port % 2)) { | |||
| else if (port <= 0xBF && !(port % 2)) | |||
| vdp_write_data(io->vdp, value); | |||
| } else if (port <= 0xBF) { | |||
| else if (port <= 0xBF) | |||
| vdp_write_control(io->vdp, value); | |||
| } else { | |||
| return; | |||
| } | |||
| return; | |||
| except: | |||
| io->except = true; | |||
| io->exc_port = port; | |||
| } | |||
| @@ -14,8 +14,7 @@ | |||
| typedef struct { | |||
| VDP *vdp; | |||
| PSG *psg; | |||
| bool except; | |||
| uint8_t exc_port; | |||
| uint8_t ports[6]; | |||
| } IO; | |||
| /* Functions */ | |||
| @@ -41,3 +41,13 @@ void psg_write(PSG *psg, uint8_t byte) | |||
| (void) psg; | |||
| 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_power(PSG*); | |||
| 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("- $09: 0x%02X (VS)", regs[0x09]) | |||
| 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. | |||
| */ | |||
| static inline uint8_t* extract_reg(Z80 *z80, uint8_t opcode) | |||
| @@ -11,7 +11,6 @@ | |||
| #define Z80_EXC_NOT_POWERED 0 | |||
| #define Z80_EXC_UNIMPLEMENTED_OPCODE 1 | |||
| #define Z80_EXC_IO_ERROR 2 | |||
| /* Structs */ | |||
| @@ -1969,7 +1969,7 @@ static uint8_t z80_inst_in_a_n(Z80 *z80, uint8_t opcode) | |||
| { | |||
| (void) opcode; | |||
| 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++; | |||
| 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) | |||
| { | |||
| uint8_t data = read_port(z80, z80->regs.c); | |||
| uint8_t data = io_port_read(z80->io, z80->regs.c); | |||
| if (opcode != 0x70) | |||
| *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) | |||
| { | |||
| (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); | |||
| 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) | |||
| { | |||
| (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); | |||
| z80->regs.hl--; | |||
| @@ -2055,7 +2057,7 @@ static uint8_t z80_inst_out_n_a(Z80 *z80, uint8_t opcode) | |||
| { | |||
| (void) opcode; | |||
| 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++; | |||
| 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) | |||
| { | |||
| 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++; | |||
| 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) | |||
| { | |||
| (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); | |||
| 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) | |||
| { | |||
| (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); | |||
| z80->regs.hl--; | |||