/* SPDX-License-Identifier: Unlicense */ #include "vdp.hpp" #include "musashi-m68k/m68k.h" #include "utils.hpp" #include #include #include /// DISP: Enable (1) / Disable (0) Display #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)) /// IE0: Enable (1) / Disable (0) V interrupt (IRQ 6) #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)) /// M1: Enable (1) / Disable (0) DMA #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)) /// M2: V 30 (1) / V 28 (0) cell mode (PAL mode, always 0 in NTSC mdoe) #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 SCROLLA_ADDR_MASK (0x38) #define SCROLLA_ADDR_SHIFT (10) #define SCROLLA_ADDR_GET(reg) \ ((((uint16_t)(reg)) & (SCROLLA_ADDR_MASK)) << SCROLLA_ADDR_SHIFT) #define SCROLLA_ADDR_SET(reg, v) \ ((reg) = ((reg) & ~SCROLLA_ADDR_MASK) \ | (((uint16_t)(v) >> SCROLLA_ADDR_SHIFT) & SCROLLA_ADDR_MASK)) #define SCROLLB_ADDR_MASK (7) #define SCROLLB_ADDR_SHIFT (13) #define SCROLLB_ADDR_GET(reg) \ ((((uint16_t)(reg)) & (SCROLLB_ADDR_MASK)) << SCROLLB_ADDR_SHIFT) #define SCROLLB_ADDR_SET(reg, v) \ ((reg) = ((reg) & ~SCROLLB_ADDR_MASK) \ | (((uint16_t)(v) >> SCROLLB_ADDR_SHIFT) & SCROLLB_ADDR_MASK)) #define HSCROLL_TABLE_ADDR_MASK (0x3f) #define HSCROLL_TABLE_ADDR_SHIFT (10) #define HSCROLL_TABLE_ADDR_GET(reg) \ ((((uint16_t)(reg)) & (HSCROLL_TABLE_ADDR_MASK)) << HSCROLL_TABLE_ADDR_SHIFT) #define HSCROLL_TABLE_ADDR_SET(reg, v) \ ((reg) = ((reg) & ~HSCROLL_TABLE_ADDR_MASK) \ | (((uint16_t)(v) >> HSCROLL_TABLE_ADDR_SHIFT) & HSCROLL_TABLE_ADDR_MASK)) // 0: V32 CELL, 1: V64 CELL, 2: Prohibited, 3: V128 CELL #define SCROLLSIZE_VCELL_MASK (3) #define SCROLLSIZE_VCELL_SHIFT (4) #define SCROLLSIZE_VCELL_GET(reg) \ (((reg) & (SCROLLSIZE_VCELL_MASK << SCROLLSIZE_VCELL_SHIFT)) >> SCROLLSIZE_VCELL_SHIFT) #define SCROLLSIZE_VCELL_SET(reg, v) \ ((reg) = ((reg) & ~(SCROLLSIZE_VCELL_MASK << SCROLLSIZE_VCELL_SHIFT)) \ | ((v & SCROLLSIZE_VCELL_MASK) << SCROLLSIZE_VCELL_SHIFT)) // 0: H32 CELL, 1: H64 CELL, 2: Prohibited, 3: H128 CELL #define SCROLLSIZE_HCELL_MASK (3) #define SCROLLSIZE_HCELL_SHIFT (0) #define SCROLLSIZE_HCELL_GET(reg) \ (((reg) & (SCROLLSIZE_HCELL_MASK << SCROLLSIZE_HCELL_SHIFT)) >> SCROLLSIZE_HCELL_SHIFT) #define SCROLLSIZE_HCELL_SET(reg, v) \ ((reg) = ((reg) & ~(SCROLLSIZE_HCELL_MASK << SCROLLSIZE_HCELL_SHIFT)) \ | ((v & SCROLLSIZE_HCELL_MASK) << SCROLLSIZE_HCELL_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)) static constexpr size_t kCellWidthPixels = 8; static constexpr size_t kCellHeightPixels = 8; static constexpr size_t kPixelsPerByte = 2; 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"); } } static inline size_t scrollsize_cells_count(uint8_t xcell_value) { assert(xcell_value <= 3); // It actually returns 96 when xcell_value == 2, but it is prohibited and we // just assume that this is not the case. return (xcell_value + 1) * 32; } static inline uint32_t RenderColorFromCRAM(uint16_t color) { const uint32_t blue = ((color >> 9) & 7) << 5; const uint32_t green = ((color >> 5) & 7) << 5; const uint32_t red = ((color >> 1) & 7) << 5; return 0xff000000 | (red << 16) | (green << 8) | blue; } static inline uint32_t GetU32BE(const void *buf) { return (static_cast(reinterpret_cast(buf)[0]) << 24) | (static_cast(reinterpret_cast(buf)[1]) << 16) | (static_cast(reinterpret_cast(buf)[2]) << 8) | (static_cast(reinterpret_cast(buf)[3]) << 0); } static inline uint16_t GetU16BE(const void *buf) { return (static_cast(reinterpret_cast(buf)[0]) << 8) | (static_cast(reinterpret_cast(buf)[1]) << 0); } void VDP::renderScrollBLine(const size_t line_index, const size_t hcell_count) { const uint8_t scroll_reg = _reg[static_cast(RegID::kScrollBAddress)]; const uint16_t scroll_addr = SCROLLB_ADDR_GET(scroll_reg); const size_t cy = line_index / kCellHeightPixels; const size_t y = line_index % kCellHeightPixels; for (size_t cx = 0; cx < hcell_count; cx++) { if (cx * kCellWidthPixels >= kRenderWidth) { break; } const uint16_t sprite_id = GetU16BE( _vram + scroll_addr + (cy * hcell_count + cx) * sizeof(uint16_t)); const uint32_t val = GetU32BE( _vram + (sprite_id * kCellHeightPixels + y) * (kCellWidthPixels / kPixelsPerByte)); const size_t hscroll_table_addr_reg = _reg[static_cast(RegID::kHScrollTableAddress)]; const size_t hscroll_table_addr = HSCROLL_TABLE_ADDR_GET(hscroll_table_addr_reg); const size_t hscroll = GetU16BE(_vram + hscroll_table_addr + sizeof(uint16_t) * 1) & 0x3ff; for (size_t x = 0; x < kCellWidthPixels; x++) { const size_t render_offset_x = (cx * kCellWidthPixels + x + hscroll) % (hcell_count * kCellWidthPixels); if (render_offset_x >= kRenderWidth) { continue; } const size_t color_index = (val >> ((kCellWidthPixels - 1 - x) * 4)) & 0xf; const uint16_t bg_color = _reg[static_cast(RegID::kBackgroundColor)] & 0xf; const uint16_t bg_palette = (_reg[static_cast(RegID::kBackgroundColor)] & 0x30) >> 4; // TODO compare current palette too (void) bg_palette; if (color_index == bg_color) { // Do not render anything - background color means transparency continue; } const uint16_t cram_color = GetU16BE(_cram + color_index * sizeof(uint16_t)); const uint32_t color = RenderColorFromCRAM(cram_color); const size_t render_offset = ((cy * kCellHeightPixels + y) * kRenderWidth) + render_offset_x; _rendered_buffer[render_offset] = color; } } } void VDP::renderScrollALine(const size_t line_index, const size_t hcell_count) { const uint8_t scroll_reg = _reg[static_cast(RegID::kScrollAAddress)]; const uint16_t scroll_addr = SCROLLA_ADDR_GET(scroll_reg); const size_t cy = line_index / kCellHeightPixels; const size_t y = line_index % kCellHeightPixels; for (size_t cx = 0; cx < hcell_count; cx++) { if (cx * kCellWidthPixels >= kRenderWidth) { break; } const uint16_t sprite_id = GetU16BE( _vram + scroll_addr + (cy * hcell_count + cx) * sizeof(uint16_t)); const uint32_t val = GetU32BE( _vram + (sprite_id * kCellHeightPixels + y) * (kCellWidthPixels / kPixelsPerByte)); const size_t hscroll_table_addr_reg = _reg[static_cast(RegID::kHScrollTableAddress)]; const size_t hscroll_table_addr = HSCROLL_TABLE_ADDR_GET(hscroll_table_addr_reg); const size_t hscroll = GetU16BE(_vram + hscroll_table_addr + sizeof(uint16_t) * 0) & 0x3ff; for (size_t x = 0; x < kCellWidthPixels; x++) { const size_t render_offset_x = (cx * kCellWidthPixels + x + hscroll) % (hcell_count * kCellWidthPixels); if (render_offset_x >= kRenderWidth) { continue; } const size_t color_index = (val >> ((kCellWidthPixels - 1 - x) * 4)) & 0xf; const uint16_t bg_color = _reg[static_cast(RegID::kBackgroundColor)] & 0xf; const uint16_t bg_palette = (_reg[static_cast(RegID::kBackgroundColor)] & 0x30) >> 4; // TODO compare current palette too (void) bg_palette; if (color_index == bg_color) { // Do not render anything - background color means transparency continue; } const uint16_t cram_color = GetU16BE(_cram + color_index * sizeof(uint16_t)); const uint32_t color = RenderColorFromCRAM(cram_color); const size_t render_offset = ((cy * kCellHeightPixels + y) * kRenderWidth) + render_offset_x; _rendered_buffer[render_offset] = color; } } } 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; memset( _rendered_buffer + kRenderWidth * _lines_counter, 0, kRenderWidth * sizeof(*_rendered_buffer)); if (_lines_counter >= (int)kRenderHeight) { // just render palette for (size_t i = 0; i < kRenderWidth; i++) { const size_t color_index = (i * 2) % kCRAMSize; const uint16_t cram_color = GetU16BE(_cram +color_index * sizeof(uint16_t)); const uint32_t color = RenderColorFromCRAM(cram_color); _rendered_buffer[kRenderWidth * _lines_counter + i] = color; } } else { // Render sprites const uint8_t scrollsize_reg = _reg[static_cast(RegID::kScrollSize)]; const uint8_t vcell = SCROLLSIZE_VCELL_GET(scrollsize_reg); const uint8_t hcell = SCROLLSIZE_HCELL_GET(scrollsize_reg); // 2 is prohibited value, so we just won't render if it is set if (vcell != 2 && hcell != 2) { const size_t hcell_count = scrollsize_cells_count(hcell); renderScrollBLine(_lines_counter, hcell_count); renderScrollALine(_lines_counter, hcell_count); } } _lines_counter++; if (_lines_counter >= lines_per_screen) { _lines_counter = 0; if (MODESET2_IE0_GET(mode_set_2)) { m68k_set_irq(M68K_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; // TODO print warnings of all invalid values or writes that ignore a mask } 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 & 0x3fff; _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 }