/* SPDX-License-Identifier: Unlicense */ #include "vdp.hpp" #include "musashi-m68k/m68k.h" #include "utils.hpp" #include #include #include #define MODESET2_DISP_MASK (1) #define MODESET2_DISP_SHIFT (6) #define MODESET2_DISP_GET(reg) \ (((reg) & (MODESET2_DISP_MASK << MODESET2_DISP_SHIFT)) >> MODESET2_DISP_SHIFT) #define MODESET2_DISP_SET(reg, v) \ ((reg) = ((reg) & ~(MODESET2_DISP_MASK << MODESET2_DISP_SHIFT)) \ | ((v & MODESET2_DISP_MASK) << MODESET2_DISP_SHIFT)) #define MODESET2_IE0_MASK (1) #define MODESET2_IE0_SHIFT (5) #define MODESET2_IE0_GET(reg) \ (((reg) & (MODESET2_IE0_MASK << MODESET2_IE0_SHIFT)) >> MODESET2_IE0_SHIFT) #define MODESET2_IE0_SET(reg, v) \ ((reg) = ((reg) & ~(MODESET2_IE0_MASK << MODESET2_IE0_SHIFT)) \ | ((v & MODESET2_IE0_MASK) << MODESET2_IE0_SHIFT)) #define MODESET2_M1_MASK (1) #define MODESET2_M1_SHIFT (4) #define MODESET2_M1_GET(reg) \ (((reg) & (MODESET2_M1_MASK << MODESET2_M1_SHIFT)) >> MODESET2_M1_SHIFT) #define MODESET2_M1_SET(reg, v) \ ((reg) = ((reg) & ~(MODESET2_M1_MASK << MODESET2_M1_SHIFT)) \ | ((v & MODESET2_M1_MASK) << MODESET2_M1_SHIFT)) #define MODESET2_M2_MASK (1) #define MODESET2_M2_SHIFT (3) #define MODESET2_M2_GET(reg) \ (((reg) & (MODESET2_M2_MASK << MODESET2_M2_SHIFT)) >> MODESET2_M2_SHIFT) #define MODESET2_M2_SET(reg, v) \ ((reg) = ((reg) & ~(MODESET2_M2_MASK << MODESET2_M2_SHIFT)) \ | ((v & MODESET2_M2_MASK) << MODESET2_M2_SHIFT)) #define DMASRCADRHIGH_DMD_MASK (3) #define DMASRCADRHIGH_DMD_SHIFT (6) #define DMASRCADRHIGH_DMD_GET(reg) \ (((reg) & (DMASRCADRHIGH_DMD_MASK << DMASRCADRHIGH_DMD_SHIFT)) >> DMASRCADRHIGH_DMD_SHIFT) #define DMASRCADRHIGH_DMD_SET(reg, v) \ ((reg) = ((reg) & ~(DMASRCADRHIGH_DMD_MASK << DMASRCADRHIGH_DMD_SHIFT)) \ | ((v & DMASRCADRHIGH_DMD_MASK) << DMASRCADRHIGH_DMD_SHIFT)) uint32_t VDP::Read(const uint32_t offset, const enum bitness bitness) { uint32_t ret{}; if (DEBUG_TRACE_VDP_ACCESS) { printf( "VDP r%d%s @0x%08x", bitness * 8, (bitness <= 1 ? " " : ""), base_address + offset); } if (bitness == BITNESS_32) { if (offset == 0) { ret = readData(_address_mode, _address & 0xfffe) << 16; _address += _reg[static_cast(RegID::kAutoIncrement)]; ret |= readData(_address_mode, _address & 0xfffe); _address += _reg[static_cast(RegID::kAutoIncrement)]; } else if (offset == 4) { ret = readStatusRegister() | (readStatusRegister() << 16); } } else if (bitness == BITNESS_16) { if (offset == 0 || offset == 2) { ret = readData(_address_mode, _address & 0xfffe); _address += _reg[static_cast(RegID::kAutoIncrement)]; } else if (offset == 4 || offset == 6) { ret = readStatusRegister(); } } // XXX: Ignore 8-bit reads for now if (DEBUG_TRACE_VDP_ACCESS) { printf("\n"); } return ret; } 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 (_dma_ready) { _dma_ready = false; if (DEBUG_TRACE_VDP_ACCESS) { printf(": Running DMA VRAM Fill"); } const uint16_t destination_address = _address; const uint16_t transfer_size = _reg[static_cast(RegID::kDMALengthCounterLow)] | (_reg[static_cast(RegID::kDMALengthCounterHigh)] << 8); const uint8_t increment = _reg[static_cast(RegID::kAutoIncrement)]; runDMAVRAMFill( baseFromAddressMode(_address_mode), destination_address, transfer_size, increment, value); _address += transfer_size * increment / 2; } else 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"); } } bool VDP::Scanline() { const uint16_t mode_set_2 = _reg[static_cast(RegID::kModeSet2)]; if (!MODESET2_DISP_GET(mode_set_2)) { return true; } const uint16_t lines_per_screen = _status.pal_mode ? kLinesPerScreenPAL : kLinesPerScreenNTSC; // TODO remove the `1 || ` part if (1 || _lines_counter + 1 == lines_per_screen) { for (size_t i = 0; i < render_width; i++) { const size_t color_index = (i * 2) % kCRAMSize; const uint16_t cram_color = (((uint16_t)_cram[color_index]) << 8) | _cram[color_index + 1]; const uint8_t blue = ((cram_color >> 9) & 7) << 4; const uint8_t green = ((cram_color >> 5) & 7) << 4; const uint8_t red = ((cram_color >> 1) & 7) << 4; const uint32_t color = 0xff000000 | (red << 16) | (green << 8) | blue; _rendered_buffer[render_width * _lines_counter + i] = color; } } else { for (size_t i = 0; i < render_width; i++) { _rendered_buffer[render_width * _lines_counter + i] = 0xff000000; } } _lines_counter++; if (_lines_counter >= lines_per_screen) { _lines_counter = 0; if (MODESET2_IE0_GET(mode_set_2)) { // TODO somehow this acknowledgment messes up the `hello word` example //m68k_set_irq(6); _status.vblank = true; return true; } } _status.vblank = false; return false; } void VDP::Reset() { _status = {}; _control_write_second_word = {}; _dma_ready = {}; _lines_counter = {}; _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); } const uint16_t mode_set_2 = _reg[static_cast(RegID::kModeSet2)]; const uint8_t dma_address_mode = (_address_mode >> 4) & 0x3; if (MODESET2_M1_GET(mode_set_2) && dma_address_mode) { const uint16_t destination_address = _address; const uint16_t transfer_size = _reg[static_cast(RegID::kDMALengthCounterLow)] | (_reg[static_cast(RegID::kDMALengthCounterHigh)] << 8); const uint8_t increment = _reg[static_cast(RegID::kAutoIncrement)]; const uint8_t dma_action = DMASRCADRHIGH_DMD_GET(_reg[static_cast(RegID::kDMASourceAddressHigh)]); switch (dma_action) { case 0: case 1: { if (DEBUG_TRACE_VDP_ACCESS) { printf(": Running DMA Memory to VRAM"); } const uint32_t source_address = _reg[static_cast(RegID::kDMASourceAddressLow)] | (_reg[static_cast(RegID::kDMASourceAddressMid)] << 8) | ((_reg[static_cast(RegID::kDMASourceAddressHigh)] & 0x3f) << 16); runDMAMemoryToVRAM( baseFromAddressMode(_address_mode), source_address, destination_address, transfer_size, increment); _address += transfer_size * increment / 2; } break; case 2: { if (DEBUG_TRACE_VDP_ACCESS) { printf(": Ready to run DMA VRAM Fill"); } _dma_ready = true; } break; case 3: { if (DEBUG_TRACE_VDP_ACCESS) { printf(": Running DMA VRAM Copy"); } runDMAVRAMCopy(baseFromAddressMode(_address_mode)); _address += transfer_size * increment / 2; } break; } } } 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 { const uint16_t value = 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) ; if (DEBUG_TRACE_VDP_ACCESS) { printf(": Status %04x", value); } return value; } uint8_t* VDP::baseFromAddressMode(uint8_t address_mode) { switch (static_cast(address_mode & 0xf)) { case AddressMode::kVRAMWrite: return _vram; case AddressMode::kCRAMWrite: return _cram; case AddressMode::kVSRAMWrite: return _vsram; case AddressMode::kVRAMRead: return _vram; case AddressMode::kCRAMRead: return _cram; case AddressMode::kVSRAMRead: return _vsram; } UNREACHABLE; return nullptr; } const char* VDP::addressModeToString(const uint8_t address_mode) { // Bits other than CD5-CD0 cannot be set assert((address_mode >> 6) == 0); const uint8_t dma_address_mode = (address_mode >> 4) & 0x3; switch (static_cast(address_mode & 0xf)) { case AddressMode::kVRAMWrite: switch (dma_address_mode) { case 0: return "VRAM write"; case 1: return "DMA ???, VRAM write"; case 2: return "DMA Memory to VRAM/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 ???, CRAM write"; case 2: return "DMA Memory to VRAM/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 ???, VSRAM write"; case 2: return "DMA Memory to VRAM/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 ???, VRAM read"; case 2: return "DMA Memory to VRAM/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 ???, CRAM read"; case 2: return "DMA Memory to VRAM/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 ???, VSRAM read"; case 2: return "DMA Memory to VRAM/VRAM fill, VSRAM read"; case 3: return "DMA VRAM copy, VSRAM read"; } break; } UNREACHABLE; 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::kDMALengthCounterLow: return "DMACounterLow"; case RegID::kDMALengthCounterHigh: return "DMACounterHigh"; case RegID::kDMASourceAddressLow: return "DMASourceAddressLow"; case RegID::kDMASourceAddressMid: return "DMASourceAddressMid"; case RegID::kDMASourceAddressHigh: return "DMASourceAddressHigh"; case RegID::kRegistersCount: break; } return "Unknown"; } void VDP::runDMAMemoryToVRAM( uint8_t* const base, const uint32_t source_address, const uint16_t destination_address, const uint16_t transfer_size, const uint8_t increment) { constexpr size_t kDumpWidth = 16; for (size_t i = 0, o = 0; i < transfer_size; i += 2, o += increment) { const uint32_t src_addr = source_address + i; const uint32_t dst_addr = destination_address + o; const uint16_t value = m68k_read_memory_16(src_addr); if (DEBUG_TRACE_VDP_ACCESS) { if (i % kDumpWidth == 0) { printf("\n%06x:%06x: ", src_addr, dst_addr); } printf("%04x ", value); } base[dst_addr + 0] = (value >> 8) & 0xff; base[dst_addr + 1] = value & 0xff; } } void VDP::runDMAVRAMFill( uint8_t* const base, const uint16_t destination_address, const uint16_t transfer_size, const uint8_t increment, const uint16_t filler) { if (DEBUG_TRACE_VDP_ACCESS) { printf( "\nVDP DMA Fill @%06x..%06x = 0x%04x", destination_address, destination_address + transfer_size, filler); } for (size_t i = 0, o = 0; i < transfer_size; i += 2, o += increment) { // TODO: There must some complex byte order shit be happening, depending // on odd/even destination address const uint16_t dst_addr = destination_address + o; const uint16_t value = filler; base[dst_addr + 0] = (value >> 8) & 0xff; base[dst_addr + 1] = value & 0xff; } } void VDP::runDMAVRAMCopy(uint8_t* base) { (void) base; // TODO }