/* SPDX-License-Identifier: Unlicense */ #include "vdp.hpp" #include #include uint32_t VDP::Read(const uint32_t offset, const enum bitness bitness) { if (DEBUG_TRACE_VDP_ACCESS) { printf( "VDP r%d%s @0x%08x\n", bitness * 8, (bitness <= 1 ? " " : ""), base_address + offset); } if (bitness == BITNESS_32) { if (offset == 0) { const uint16_t ret = readData(_address_mode, _address & 0xfffe); _address += _reg[static_cast(RegID::kAutoIncrement)]; return ret; } else if (offset == 4) { return readStatusRegister() | (readStatusRegister() << 16); } } else if (bitness == BITNESS_16) { if (offset == 0 || offset == 2) { const uint16_t ret = readData(_address_mode, _address & 0xfffe); _address += _reg[static_cast(RegID::kAutoIncrement)]; return ret; } else if (offset == 4 || offset == 6) { return readStatusRegister(); } } // XXX: Ignore 8-bit reads for now return 0; } void VDP::Write(const uint32_t offset, const enum bitness bitness, const uint32_t value) { if (DEBUG_TRACE_VDP_ACCESS) { printf( "VDP w%d%s @0x%08x 0x%0*x", bitness * 8, (bitness <= 1 ? " " : ""), base_address + offset, bitness * 2, value); } if (bitness == BITNESS_32) { if (offset == 0) { writeData(_address_mode, _address & 0xfffe, (value >> 16) & 0xffff); _address += _reg[static_cast(RegID::kAutoIncrement)]; writeData(_address_mode, _address & 0xfffe, value & 0xffff); _address += _reg[static_cast(RegID::kAutoIncrement)]; } else if (offset == 4) { writeControl((value >> 16) & 0xffff); writeControl(value & 0xffff); } } else if (bitness == BITNESS_16) { if (offset == 0 || offset == 2) { writeData(_address_mode, _address & 0xfffe, value & 0xffff); _address += _reg[static_cast(RegID::kAutoIncrement)]; } else if (offset == 4 || offset == 6) { writeControl(value & 0xffff); } } // XXX: Ignore 8-bit writes for now if (DEBUG_TRACE_VDP_ACCESS) { printf("\n"); } } void VDP::Reset() { _status = {}; _control_write_second_word = {}; _address_mode = {}; _address = {}; memset(_reg, 0, kRegistersCount); memset(_vram, 0, kVRAMSize); memset(_cram, 0, kCRAMSize); memset(_vsram, 0, kVSRAMSize); } static inline bool IsWriteToReg(const uint16_t value) { return ((value >> 13) & 0x7) == 4; } void VDP::writeData(const uint8_t address_mode, const uint16_t address, const uint16_t value) { switch (static_cast(address_mode & 0xf)) { case AddressMode::kVRAMWrite: if (address >= kVRAMSize) { if (DEBUG_TRACE_VDP_ACCESS) { printf(": VRAM @0x%04x: invalid address, ignoring", address); } return; } if (DEBUG_TRACE_VDP_ACCESS) { printf(": VRAM @0x%04x 0x%04x", address, value); } _vram[address] = (value >> 8) & 0xff;; _vram[address + 1] = value & 0xff;; return; case AddressMode::kCRAMWrite: if (address >= kCRAMSize) { if (DEBUG_TRACE_VDP_ACCESS) { printf(": CRAM @0x%04x: invalid address, ignoring", address); } return; } if (DEBUG_TRACE_VDP_ACCESS) { printf(": CRAM @0x%04x 0x%04x", address, value); } _cram[address] = (value >> 8) & 0xff;; _cram[address + 1] = value & 0xff;; return; case AddressMode::kVSRAMWrite: if (address >= kVSRAMSize) { if (DEBUG_TRACE_VDP_ACCESS) { printf(": VSRAM @0x%04x: invalid address, ignoring", address); } return; } if (DEBUG_TRACE_VDP_ACCESS) { printf(": VSRAM @0x%04x 0x%04x", address, value); } _vsram[address] = (value >> 8) & 0xff;; _vsram[address + 1] = value & 0xff;; return; case AddressMode::kVRAMRead: case AddressMode::kCRAMRead: case AddressMode::kVSRAMRead: break; } if (DEBUG_TRACE_VDP_ACCESS) { printf( ": 0x%02x(%s): invalid address_mode", address_mode, addressModeToString(address_mode)); } } void VDP::writeControl(const uint16_t value) { if (_control_write_second_word) { // Write second word // 0 0 0 0 0 0 0 0 // CD5 CD4 CD3 CD2 0 0 A15 A14 _address |= (value & 0x3) << 14; _address_mode |= ((value >> 4) & 0xf) << 2; _control_write_second_word = false; if (DEBUG_TRACE_VDP_ACCESS) { printf( ": New address_mode=0x%02x(%s), address=0x%04x", _address_mode, addressModeToString(_address_mode), _address); } } else if (IsWriteToReg(value)) { // Write register // 1 0 0 RS4 RS3 RS2 RS1 RS0 // D7 D6 D5 D4 D3 D2 D1 D0 const uint8_t reg_num = (value >> 8) & 0x1f; const uint8_t reg_val = value & 0xff; if (DEBUG_TRACE_VDP_ACCESS) { printf( ": reg 0x%02x(%s) = 0x%02x", reg_num, regIDToString(static_cast(reg_num)), reg_val); } _reg[reg_num] = reg_val; } else if (!_control_write_second_word) { // Write first word // CD1 CD0 A13 A12 A11 A10 A9 A8 // A7 A6 A5 A4 A3 A2 A1 A0 _address = value & 0x3f; _address_mode = (value >> 14) & 0x3; _control_write_second_word = true; } } uint16_t VDP::readData(const uint8_t address_mode, const uint16_t address) { switch (static_cast(address_mode & 0xf)) { case AddressMode::kVRAMWrite: case AddressMode::kCRAMWrite: case AddressMode::kVSRAMWrite: break; case AddressMode::kVRAMRead: if (address >= kVRAMSize) { if (DEBUG_TRACE_VDP_ACCESS) { printf(": VRAM @0x%04x: invalid address, ignoring", address); } return 0; } else { const uint16_t value = (_vram[address] << 8) | _vram[address + 1]; if (DEBUG_TRACE_VDP_ACCESS) { printf(": VRAM @0x%04x 0x%04x", address, value); } return value; } case AddressMode::kCRAMRead: if (address >= kCRAMSize) { if (DEBUG_TRACE_VDP_ACCESS) { printf(": CRAM @0x%04x: invalid address, ignoring", address); } return 0; } else { const uint16_t value = (_cram[address] << 8) | _cram[address + 1]; if (DEBUG_TRACE_VDP_ACCESS) { printf(": CRAM @0x%04x 0x%04x", address, value); } return value; } case AddressMode::kVSRAMRead: if (address >= kVSRAMSize) { if (DEBUG_TRACE_VDP_ACCESS) { printf(": VSRAM @0x%04x: invalid address, ignoring", address); } return 0; } else { const uint16_t value = (_vsram[address] << 8) | _vsram[address + 1]; if (DEBUG_TRACE_VDP_ACCESS) { printf(": VSRAM @0x%04x 0x%04x", address, value); } return value; } } if (DEBUG_TRACE_VDP_ACCESS) { printf( ": 0x%02x(%s): invalid address_mode", address_mode, addressModeToString(address_mode)); } return 0; } uint16_t VDP::readStatusRegister() const { return 0 | (!_status.fifo_not_empty << 9) | (_status.fifo_full << 8) | (_status.v_irq << 7) | (_status.sprites_overflow << 6) | (_status.sprites_collision << 5) | (_status.odd_frame << 4) | (_status.vblank << 3) | (_status.hblank << 2) | (_status.dma_busy << 1) | (_status.pal_mode << 0) ; } const char* VDP::addressModeToString(const uint8_t address_mode) { const uint8_t dma_address_mode = (address_mode >> 5) & 0x3; switch (static_cast(address_mode & 0xf)) { case AddressMode::kVRAMWrite: switch (dma_address_mode) { case 0: return "VRAM write"; case 1: return "DMA Memory to VRAM, VRAM write"; case 2: return "DMA VRAM fill, VRAM write"; case 3: return "DMA VRAM copy, VRAM write"; } break; case AddressMode::kCRAMWrite: switch (dma_address_mode) { case 0: return "CRAM write"; case 1: return "DMA Memory to VRAM, CRAM write"; case 2: return "DMA VRAM fill, CRAM write"; case 3: return "DMA VRAM copy, CRAM write"; } break; case AddressMode::kVSRAMWrite: switch (dma_address_mode) { case 0: return "VSRAM write"; case 1: return "DMA Memory to VRAM, VSRAM write"; case 2: return "DMA VRAM fill, VSRAM write"; case 3: return "DMA VRAM copy, VSRAM write"; } break; case AddressMode::kVRAMRead: switch (dma_address_mode) { case 0: return "VRAM read"; case 1: return "DMA Memory to VRAM, VRAM read"; case 2: return "DMA VRAM fill, VRAM read"; case 3: return "DMA VRAM copy, VRAM read"; } break; case AddressMode::kCRAMRead: switch (dma_address_mode) { case 0: return "CRAM read"; case 1: return "DMA Memory to VRAM, CRAM read"; case 2: return "DMA VRAM fill, CRAM read"; case 3: return "DMA VRAM copy, CRAM read"; } break; case AddressMode::kVSRAMRead: switch (dma_address_mode) { case 0: return "VSRAM read"; case 1: return "DMA Memory to VRAM, VSRAM read"; case 2: return "DMA VRAM fill, VSRAM read"; case 3: return "DMA VRAM copy, VSRAM read"; } break; } return "Unknown"; } const char* VDP::regIDToString(const RegID reg_id) { switch (reg_id) { case RegID::kModeSet1: return "ModeSet1"; case RegID::kModeSet2: return "ModeSet2"; case RegID::kScrollAAddress: return "ScrollAAddress"; case RegID::kWindowAddress: return "WindowAddress"; case RegID::kScrollBAddress: return "ScrollBAddress"; case RegID::kSpritesTableAddress: return "SpritesTableAddress"; case RegID::kBackgroundColor: return "BackgroundColor"; case RegID::kHint: return "Hint"; case RegID::kModeSet3: return "ModeSet3"; case RegID::kModeSet4: return "ModeSet4"; case RegID::kHScrollTableAddress: return "HScrollTableAddress"; case RegID::kAutoIncrement: return "AutoIncrement"; case RegID::kScrollSize: return "ScrollSize"; case RegID::kWindowHorizontalPosition: return "WindowHorizontalPosition"; case RegID::kWindowVertialPosition: return "WindowVertialPosition"; case RegID::kDMACounterLow: return "DMACounterLow"; case RegID::kDMACounterHigh: return "DMACounterHigh"; case RegID::kDMASourceAddressLow: return "DMASourceAddressLow"; case RegID::kDMASourceAddressMid: return "DMASourceAddressMid"; case RegID::kDMASourceAddressHigh: return "DMASourceAddressHigh"; case RegID::kRegistersCount: break; } return "Unknown"; }