overhaul memory, manage open bus, pal timing, v_pos, h_pos, cartridges, dma channels

This commit is contained in:
scawful
2024-04-22 15:57:19 -04:00
parent 4715cd30bc
commit 3b462970f6
3 changed files with 335 additions and 161 deletions

View File

@@ -14,6 +14,215 @@ namespace app {
namespace emu {
namespace memory {
void MemoryImpl::Initialize(const std::vector<uint8_t>& romData, bool verbose) {
verbose_ = verbose;
auto location = 0x7FC0; // GetHeaderOffset();
romSize = 0x400 << romData[location + 0x17];
sramSize = 0x400 << romData[location + 0x18];
rom_.resize(romSize);
// Copy memory into rom_
std::copy(romData.begin() + kROMStart, romData.begin() + kROMStart + kROMSize,
rom_.begin());
memory_.resize(0x1000000); // 16 MB
const size_t ROM_CHUNK_SIZE = 0x8000; // 32 KB
// Clear memory
std::fill(memory_.begin(), memory_.end(), 0);
// Load ROM data into memory based on LoROM mapping
size_t romSize = romData.size();
size_t romAddress = 0;
for (size_t bank = 0x00; bank <= 0x3F; ++bank) {
for (size_t offset = 0x8000; offset <= 0xFFFF; offset += ROM_CHUNK_SIZE) {
if (romAddress < romSize) {
std::copy(romData.begin() + romAddress,
romData.begin() + romAddress + ROM_CHUNK_SIZE,
memory_.begin() + (bank << 16) + offset);
romAddress += ROM_CHUNK_SIZE;
}
}
}
// Copy data into rom_ vector
rom_.resize(kROMSize);
std::copy(memory_.begin() + kROMStart, memory_.begin() + kROMStart + kROMSize,
rom_.begin());
// Copy data into ram_ vector
ram_.resize(kRAMSize);
std::copy(memory_.begin() + kRAMStart, memory_.begin() + kRAMStart + kRAMSize,
ram_.begin());
}
memory::RomInfo MemoryImpl::ReadRomHeader() {
memory::RomInfo romInfo;
uint32_t offset = GetHeaderOffset();
// Read cartridge title
char title[22];
for (int i = 0; i < 21; ++i) {
title[i] = ReadByte(offset + i);
}
title[21] = '\0'; // Null-terminate the string
romInfo.title = std::string(title);
// Read ROM speed and memory map mode
uint8_t romSpeedAndMapMode = ReadByte(offset + 0x15);
romInfo.romSpeed = (memory::RomSpeed)(romSpeedAndMapMode & 0x07);
romInfo.bankSize = (memory::BankSize)((romSpeedAndMapMode >> 5) & 0x01);
// Read ROM type
romInfo.romType = (memory::RomType)ReadByte(offset + 0x16);
// Read ROM size
romInfo.romSize = (memory::RomSize)ReadByte(offset + 0x17);
// Read RAM size
romInfo.sramSize = (memory::SramSize)ReadByte(offset + 0x18);
// Read country code
romInfo.countryCode = (memory::CountryCode)ReadByte(offset + 0x19);
// Read license
romInfo.license = (memory::License)ReadByte(offset + 0x1A);
// Read ROM version
romInfo.version = ReadByte(offset + 0x1B);
// Read checksum complement
romInfo.checksumComplement = ReadWord(offset + 0x1E);
// Read checksum
romInfo.checksum = ReadWord(offset + 0x1C);
// Read NMI VBL vector
romInfo.nmiVblVector = ReadWord(offset + 0x3E);
// Read reset vector
romInfo.resetVector = ReadWord(offset + 0x3C);
return romInfo;
}
uint8_t MemoryImpl::cart_read(uint8_t bank, uint16_t adr) {
switch (type_) {
case 0:
return open_bus_;
case 1:
return cart_readLorom(bank, adr);
case 2:
return cart_readHirom(bank, adr);
case 3:
return cart_readExHirom(bank, adr);
}
return open_bus_;
}
void MemoryImpl::cart_write(uint8_t bank, uint16_t adr, uint8_t val) {
switch (type_) {
case 0:
break;
case 1:
cart_writeLorom(bank, adr, val);
break;
case 2:
cart_writeHirom(bank, adr, val);
break;
case 3:
cart_writeHirom(bank, adr, val);
break;
}
}
uint8_t MemoryImpl::cart_readLorom(uint8_t bank, uint16_t adr) {
if (((bank >= 0x70 && bank < 0x7e) || bank >= 0xf0) && adr < 0x8000 &&
sramSize > 0) {
// banks 70-7e and f0-ff, adr 0000-7fff
return ram_[(((bank & 0xf) << 15) | adr) & (sramSize - 1)];
}
bank &= 0x7f;
if (adr >= 0x8000 || bank >= 0x40) {
// adr 8000-ffff in all banks or all addresses in banks 40-7f and c0-ff
return rom_[((bank << 15) | (adr & 0x7fff)) & (romSize - 1)];
}
return open_bus_;
}
void MemoryImpl::cart_writeLorom(uint8_t bank, uint16_t adr, uint8_t val) {
if (((bank >= 0x70 && bank < 0x7e) || bank > 0xf0) && adr < 0x8000 &&
sramSize > 0) {
// banks 70-7e and f0-ff, adr 0000-7fff
ram_[(((bank & 0xf) << 15) | adr) & (sramSize - 1)] = val;
}
}
uint8_t MemoryImpl::cart_readHirom(uint8_t bank, uint16_t adr) {
bank &= 0x7f;
if (bank < 0x40 && adr >= 0x6000 && adr < 0x8000 && sramSize > 0) {
// banks 00-3f and 80-bf, adr 6000-7fff
return ram_[(((bank & 0x3f) << 13) | (adr & 0x1fff)) & (sramSize - 1)];
}
if (adr >= 0x8000 || bank >= 0x40) {
// adr 8000-ffff in all banks or all addresses in banks 40-7f and c0-ff
return rom_[(((bank & 0x3f) << 16) | adr) & (romSize - 1)];
}
return open_bus_;
}
uint8_t MemoryImpl::cart_readExHirom(uint8_t bank, uint16_t adr) {
if ((bank & 0x7f) < 0x40 && adr >= 0x6000 && adr < 0x8000 && sramSize > 0) {
// banks 00-3f and 80-bf, adr 6000-7fff
return ram_[(((bank & 0x3f) << 13) | (adr & 0x1fff)) & (sramSize - 1)];
}
bool secondHalf = bank < 0x80;
bank &= 0x7f;
if (adr >= 0x8000 || bank >= 0x40) {
// adr 8000-ffff in all banks or all addresses in banks 40-7f and c0-ff
return rom_[(((bank & 0x3f) << 16) | (secondHalf ? 0x400000 : 0) | adr) &
(romSize - 1)];
}
return open_bus_;
}
void MemoryImpl::cart_writeHirom(uint8_t bank, uint16_t adr, uint8_t val) {
bank &= 0x7f;
if (bank < 0x40 && adr >= 0x6000 && adr < 0x8000 && sramSize > 0) {
// banks 00-3f and 80-bf, adr 6000-7fff
ram_[(((bank & 0x3f) << 13) | (adr & 0x1fff)) & (sramSize - 1)] = val;
}
}
uint32_t MemoryImpl::GetMappedAddress(uint32_t address) const {
uint8_t bank = address >> 16;
uint32_t offset = address & 0xFFFF;
if (bank <= 0x3F) {
if (address <= 0x1FFF) {
return (0x7E << 16) + offset; // Shadow RAM
} else if (address <= 0x5FFF) {
return (bank << 16) + (offset - 0x2000) + 0x2000; // Hardware Registers
} else if (address <= 0x7FFF) {
return offset - 0x6000 + 0x6000; // Expansion RAM
} else {
// Return lorom mapping
return (bank << 16) + (offset - 0x8000) + 0x8000; // ROM
}
} else if (bank == 0x7D) {
return offset + 0x7D0000; // SRAM
} else if (bank == 0x7E || bank == 0x7F) {
return offset + 0x7E0000; // System RAM
} else if (bank >= 0x80) {
// Handle HiROM and mirrored areas
}
return address; // Return the original address if no mapping is defined
}
void DrawSnesMemoryMapping(const MemoryImpl& memory) {
// Using those as a base value to create width/height that are factor of the
// size of our font

View File

@@ -2,11 +2,13 @@
#define MEM_H
#include <cstdint>
#include <functional>
#include <iostream>
#include <string>
#include <vector>
#include "app/emu/debug/log.h"
#include "app/emu/memory/dma_channel.h"
// LoROM (Mode 20):
@@ -91,20 +93,16 @@ class RomInfo {
uint16_t resetVector;
};
class Observer {
public:
virtual ~Observer() = default;
virtual void Notify(uint32_t address, uint16_t data) = 0;
};
typedef struct CpuCallbacks {
std::function<uint8_t(uint32_t)> read_byte;
std::function<void(uint32_t, uint8_t)> write_byte;
std::function<void(bool waiting)> idle;
} CpuCallbacks;
constexpr uint32_t kROMStart = 0x008000;
constexpr uint32_t kROMSize = 0x200000;
constexpr uint32_t kRAMStart = 0x7E0000;
constexpr uint32_t kRAMSize = 0x20000;
constexpr uint32_t kVRAMStart = 0x210000;
constexpr uint32_t kVRAMSize = 0x10000;
constexpr uint32_t kOAMStart = 0x218000;
constexpr uint32_t kOAMSize = 0x220;
/**
* @brief Memory interface
@@ -136,139 +134,86 @@ class Memory {
virtual uint8_t operator[](int i) const = 0;
virtual uint8_t at(int i) const = 0;
};
enum class MemoryMapping { SNES_LOROM = 0, PC_ADDRESS = 1 };
virtual uint8_t open_bus() const = 0;
virtual void set_open_bus(uint8_t value) = 0;
virtual bool hdma_init_requested() const = 0;
virtual bool hdma_run_requested() const = 0;
virtual void init_hdma_request() = 0;
virtual void run_hdma_request() = 0;
virtual void set_hdma_run_requested(bool value) = 0;
virtual void set_hdma_init_requested(bool value) = 0;
virtual void set_pal_timing(bool value) = 0;
virtual void set_h_pos(uint16_t value) = 0;
virtual void set_v_pos(uint16_t value) = 0;
// get h_pos and v_pos
virtual auto h_pos() const -> uint16_t = 0;
virtual auto v_pos() const -> uint16_t = 0;
// get pal timing
virtual auto pal_timing() const -> bool = 0;
};
/**
* @class MemoryImpl
* @brief Implementation of the Memory interface for emulating memory in a SNES
* system.
*
* The MemoryImpl class provides methods for initializing and accessing memory
* in a SNES system. It implements the Memory interface and inherits from the
* Loggable class.
*
* The class supports different memory mappings, including LoROM and PC_ADDRESS
* mappings. It provides methods for reading and writing bytes, words, and longs
* from/to memory. It also supports stack operations for pushing and popping
* values.
*
* The class maintains separate vectors for ROM, RAM, VRAM, and OAM memory
* regions. It provides methods for accessing these memory regions and
* retrieving their sizes.
*
* The class also allows adding observers to be notified when memory is read or
* written.
*
* @note This class assumes a 16-bit address space.
*/
class MemoryImpl : public Memory, public Loggable {
public:
void Initialize(const std::vector<uint8_t>& romData, bool verbose = false,
MemoryMapping mapping = MemoryMapping::SNES_LOROM) {
verbose_ = verbose;
mapping_ = mapping;
if (mapping == MemoryMapping::PC_ADDRESS) {
memory_.resize(romData.size());
std::copy(romData.begin(), romData.end(), memory_.begin());
return;
uint32_t romSize;
uint32_t sramSize;
void Initialize(const std::vector<uint8_t>& romData, bool verbose = false);
uint16_t GetHeaderOffset() {
uint8_t mapMode = rom_[(0x00 << 16) + 0xFFD5];
uint16_t offset;
switch (mapMode & 0x07) {
case 0: // LoROM
offset = 0x7FC0;
break;
case 1: // HiROM
offset = 0xFFC0;
break;
case 5: // ExHiROM
offset = 0x40;
break;
default:
throw std::invalid_argument(
"Unable to locate supported ROM mapping mode in the provided ROM "
"file. Please try another ROM file.");
}
memory_.resize(0x1000000); // 16 MB
const size_t ROM_CHUNK_SIZE = 0x8000; // 32 KB
const size_t SRAM_SIZE = 0x10000; // 64 KB
const size_t SYSTEM_RAM_SIZE = 0x20000; // 128 KB
const size_t EXPANSION_RAM_SIZE = 0x2000; // 8 KB
const size_t HARDWARE_REGISTERS_SIZE = 0x4000; // 16 KB
// Clear memory
std::fill(memory_.begin(), memory_.end(), 0);
// Load ROM data into memory based on LoROM mapping
size_t romSize = romData.size();
size_t romAddress = 0;
for (size_t bank = 0x00; bank <= 0x3F; ++bank) {
for (size_t offset = 0x8000; offset <= 0xFFFF; offset += ROM_CHUNK_SIZE) {
if (romAddress < romSize) {
std::copy(romData.begin() + romAddress,
romData.begin() + romAddress + ROM_CHUNK_SIZE,
memory_.begin() + (bank << 16) + offset);
romAddress += ROM_CHUNK_SIZE;
}
}
}
// Initialize SRAM at banks 0x7D and 0xFD
std::fill(memory_.begin() + (0x7D << 16), memory_.begin() + (0x7E << 16),
0);
std::fill(memory_.begin() + (0xFD << 16), memory_.begin() + (0xFE << 16),
0);
// Initialize System RAM at banks 0x7E and 0x7F
std::fill(memory_.begin() + (0x7E << 16),
memory_.begin() + (0x7E << 16) + SYSTEM_RAM_SIZE, 0);
// Initialize Shadow RAM at banks 0x00-0x3F and 0x80-0xBF
for (size_t bank = 0x00; bank <= 0xBF; bank += 0x80) {
std::fill(memory_.begin() + (bank << 16),
memory_.begin() + (bank << 16) + 0x2000, 0);
}
// Initialize Hardware Registers at banks 0x00-0x3F and 0x80-0xBF
for (size_t bank = 0x00; bank <= 0xBF; bank += 0x80) {
std::fill(
memory_.begin() + (bank << 16) + 0x2000,
memory_.begin() + (bank << 16) + 0x2000 + HARDWARE_REGISTERS_SIZE, 0);
}
// Initialize Expansion RAM at banks 0x00-0x3F and 0x80-0xBF
for (size_t bank = 0x00; bank <= 0xBF; bank += 0x80) {
std::fill(memory_.begin() + (bank << 16) + 0x6000,
memory_.begin() + (bank << 16) + 0x6000 + EXPANSION_RAM_SIZE,
0);
}
// Initialize Reset and NMI Vectors at bank 0xFF
std::fill(memory_.begin() + (0xFF << 16) + 0xFF00,
memory_.begin() + (0xFF << 16) + 0xFFFF + 1, 0);
// Copy data into rom_ vector
rom_.resize(kROMSize);
std::copy(memory_.begin() + kROMStart,
memory_.begin() + kROMStart + kROMSize, rom_.begin());
// Copy data into ram_ vector
ram_.resize(kRAMSize);
std::copy(memory_.begin() + kRAMStart,
memory_.begin() + kRAMStart + kRAMSize, ram_.begin());
// Copy data into vram_ vector
vram_.resize(kVRAMSize);
std::copy(memory_.begin() + kVRAMStart,
memory_.begin() + kVRAMStart + kVRAMSize, vram_.begin());
// Copy data into oam_ vector
oam_.resize(kOAMSize);
std::copy(memory_.begin() + kOAMStart,
memory_.begin() + kOAMStart + kOAMSize, oam_.begin());
return offset;
}
memory::RomInfo ReadRomHeader();
uint8_t cart_read(uint8_t bank, uint16_t adr);
void cart_write(uint8_t bank, uint16_t adr, uint8_t val);
uint8_t cart_readLorom(uint8_t bank, uint16_t adr);
void cart_writeLorom(uint8_t bank, uint16_t adr, uint8_t val);
uint8_t cart_readHirom(uint8_t bank, uint16_t adr);
uint8_t cart_readExHirom(uint8_t bank, uint16_t adr);
void cart_writeHirom(uint8_t bank, uint16_t adr, uint8_t val);
uint8_t ReadByte(uint32_t address) const override {
uint32_t mapped_address = GetMappedAddress(address);
NotifyObservers(mapped_address, /*data=*/0);
return memory_.at(mapped_address);
}
uint16_t ReadWord(uint32_t address) const override {
uint32_t mapped_address = GetMappedAddress(address);
NotifyObservers(mapped_address, /*data=*/0);
return static_cast<uint16_t>(memory_.at(mapped_address)) |
(static_cast<uint16_t>(memory_.at(mapped_address + 1)) << 8);
}
uint32_t ReadWordLong(uint32_t address) const override {
uint32_t mapped_address = GetMappedAddress(address);
NotifyObservers(mapped_address, /*data=*/0);
return static_cast<uint32_t>(memory_.at(mapped_address)) |
(static_cast<uint32_t>(memory_.at(mapped_address + 1)) << 8) |
(static_cast<uint32_t>(memory_.at(mapped_address + 2)) << 16);
@@ -276,7 +221,6 @@ class MemoryImpl : public Memory, public Loggable {
std::vector<uint8_t> ReadByteVector(uint32_t address,
uint16_t length) const override {
uint32_t mapped_address = GetMappedAddress(address);
NotifyObservers(mapped_address, /*data=*/0);
return std::vector<uint8_t>(memory_.begin() + mapped_address,
memory_.begin() + mapped_address + length);
}
@@ -343,8 +287,6 @@ class MemoryImpl : public Memory, public Loggable {
(static_cast<uint32_t>(mid) << 8) | low;
}
void AddObserver(Observer* observer) { observers_.push_back(observer); }
// Stack Pointer access.
uint16_t SP() const override { return SP_; }
void SetSP(uint16_t value) override { SP_ = value; }
@@ -363,61 +305,69 @@ class MemoryImpl : public Memory, public Loggable {
auto begin() const { return memory_.begin(); }
auto end() const { return memory_.end(); }
auto data() const { return memory_.data(); }
void set_open_bus(uint8_t value) override { open_bus_ = value; }
auto open_bus() const -> uint8_t override { return open_bus_; }
auto hdma_init_requested() const -> bool override {
return hdma_init_requested_;
}
auto hdma_run_requested() const -> bool override {
return hdma_run_requested_;
}
void init_hdma_request() override { hdma_init_requested_ = true; }
void run_hdma_request() override { hdma_run_requested_ = true; }
void set_hdma_run_requested(bool value) override {
hdma_run_requested_ = value;
}
void set_hdma_init_requested(bool value) override {
hdma_init_requested_ = value;
}
void set_pal_timing(bool value) override { pal_timing_ = value; }
void set_h_pos(uint16_t value) override { h_pos_ = value; }
void set_v_pos(uint16_t value) override { v_pos_ = value; }
auto h_pos() const -> uint16_t override { return h_pos_; }
auto v_pos() const -> uint16_t override { return v_pos_; }
auto pal_timing() const -> bool override { return pal_timing_; }
auto dma_state() -> uint8_t& { return dma_state_; }
void set_dma_state(uint8_t value) { dma_state_ = value; }
auto dma_channels() -> DmaChannel* { return channel; }
// Define memory regions
std::vector<uint8_t> rom_;
std::vector<uint8_t> ram_;
std::vector<uint8_t> vram_;
std::vector<uint8_t> oam_;
private:
uint32_t GetMappedAddress(uint32_t address) const {
uint8_t bank = address >> 16;
uint32_t offset = address & 0xFFFF;
if (mapping_ == MemoryMapping::PC_ADDRESS) {
return address;
}
if (bank <= 0x3F) {
if (address <= 0x1FFF) {
return (0x7E << 16) + offset; // Shadow RAM
} else if (address <= 0x5FFF) {
return (bank << 16) + (offset - 0x2000) + 0x2000; // Hardware Registers
} else if (address <= 0x7FFF) {
return offset - 0x6000 + 0x6000; // Expansion RAM
} else {
// Return lorom mapping
return (bank << 16) + (offset - 0x8000) + 0x8000; // ROM
}
} else if (bank == 0x7D) {
return offset + 0x7D0000; // SRAM
} else if (bank == 0x7E || bank == 0x7F) {
return offset + 0x7E0000; // System RAM
} else if (bank >= 0x80) {
// Handle HiROM and mirrored areas
}
return address; // Return the original address if no mapping is defined
}
void NotifyObservers(uint32_t address, uint16_t data) const {
for (auto observer : observers_) {
observer->Notify(address, data);
}
}
uint32_t GetMappedAddress(uint32_t address) const;
bool verbose_ = false;
std::vector<Observer*> observers_;
// DMA requests
bool hdma_run_requested_ = false;
bool hdma_init_requested_ = false;
// Memory (64KB)
std::vector<uint8_t> memory_;
bool pal_timing_ = false;
// Frame timing
uint16_t h_pos_ = 0;
uint16_t v_pos_ = 0;
// Dma State
uint8_t dma_state_ = 0;
// Dma Channels
DmaChannel channel[8];
// Open bus
uint8_t open_bus_ = 0;
// Stack Pointer
uint16_t SP_ = 0x01FF;
MemoryMapping mapping_ = MemoryMapping::SNES_LOROM;
// Cart Type
uint8_t type_;
// Memory (64KB)
std::vector<uint8_t> memory_;
};
void DrawSnesMemoryMapping(const MemoryImpl& memory);

View File

@@ -72,6 +72,21 @@ class MockMemory : public Memory {
MOCK_CONST_METHOD1(at, uint8_t(int i));
uint8_t operator[](int i) const override { return memory_[i]; }
MOCK_METHOD0(init_hdma_request, void());
MOCK_METHOD0(run_hdma_request, void());
MOCK_METHOD1(set_hdma_run_requested, void(bool value));
MOCK_METHOD1(set_hdma_init_requested, void(bool value));
MOCK_CONST_METHOD0(hdma_init_requested, bool());
MOCK_CONST_METHOD0(hdma_run_requested, bool());
MOCK_METHOD1(set_pal_timing, void(bool value));
MOCK_CONST_METHOD0(pal_timing, bool());
MOCK_CONST_METHOD0(h_pos, uint16_t());
MOCK_CONST_METHOD0(v_pos, uint16_t());
MOCK_METHOD1(set_h_pos, void(uint16_t value));
MOCK_METHOD1(set_v_pos, void(uint16_t value));
MOCK_METHOD1(set_open_bus, void(uint8_t value));
MOCK_CONST_METHOD0(open_bus, uint8_t());
void SetMemoryContents(const std::vector<uint8_t>& data) {
if (data.size() > memory_.size()) {
memory_.resize(data.size());