diff --git a/CMakeLists.txt b/CMakeLists.txt index 3a19583..990e275 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,28 +20,30 @@ target_compile_options(unit_tests PUBLIC -fprofile-arcs -ftest-coverage) # make app add_executable(ComSquare - main.cpp - sources/SNES.cpp - sources/SNES.hpp - sources/Memory/MemoryBus.cpp - sources/Memory/MemoryBus.hpp - sources/Memory/IMemory.hpp - sources/Memory/IMemory.cpp - sources/PPU/PPU.cpp - sources/PPU/PPU.hpp - sources/CPU/CPU.cpp - sources/CPU/CPU.hpp - sources/Cartridge/Cartridge.cpp - sources/Cartridge/Cartridge.hpp - sources/Exceptions/NotImplementedException.hpp - sources/APU/APU.hpp - sources/APU/APU.cpp - sources/Exceptions/InvalidAddress.hpp - sources/Exceptions/InvalidRom.hpp - sources/Models/Ints.hpp - sources/Models/Ints.hpp - sources/Ram/Ram.cpp - sources/Ram/Ram.hpp - sources/Memory/MemoryShadow.cpp - sources/Memory/MemoryShadow.hpp + main.cpp + sources/SNES.cpp + sources/SNES.hpp + sources/Memory/MemoryBus.cpp + sources/Memory/MemoryBus.hpp + sources/Memory/IMemory.hpp + sources/Memory/IMemory.cpp + sources/PPU/PPU.cpp + sources/PPU/PPU.hpp + sources/CPU/CPU.cpp + sources/CPU/CPU.hpp + sources/Cartridge/Cartridge.cpp + sources/Cartridge/Cartridge.hpp + sources/Exceptions/NotImplementedException.hpp + sources/APU/APU.hpp + sources/APU/APU.cpp + sources/Exceptions/InvalidAddress.hpp + sources/Exceptions/InvalidRom.hpp + sources/Models/Ints.hpp + sources/Models/Ints.hpp + sources/Ram/Ram.cpp + sources/Ram/Ram.hpp + sources/Memory/MemoryShadow.cpp + sources/Memory/MemoryShadow.hpp + sources/Memory/IRectangleMemory.cpp + sources/Memory/IRectangleMemory.hpp ) diff --git a/main.cpp b/main.cpp index b93334c..00241e4 100644 --- a/main.cpp +++ b/main.cpp @@ -6,14 +6,16 @@ #include #include "sources/SNES.hpp" +using namespace ComSquare; + int main(int argc, char **argv) { if (argc != 2) { std::cout << "ComSquare:" << std::endl << "\tUsage: " << argv[0] << " rom_path" << std::endl; return 1; } - ComSquare::MemoryBus bus; - ComSquare::SNES snes(std::make_shared(bus), argv[1]); + Memory::MemoryBus bus; + SNES snes(std::make_shared(bus), argv[1]); bus.mapComponents(snes); return 0; } \ No newline at end of file diff --git a/sources/APU/APU.hpp b/sources/APU/APU.hpp index 8088257..bb4ce6a 100644 --- a/sources/APU/APU.hpp +++ b/sources/APU/APU.hpp @@ -105,7 +105,7 @@ namespace ComSquare::APU class DSP { }; - class APU : public IMemory { + class APU : public Memory::IMemory { private: Registers _registers; InternalRegisters _internalRegisters{}; diff --git a/sources/CPU/CPU.cpp b/sources/CPU/CPU.cpp index f2b6358..d3714f8 100644 --- a/sources/CPU/CPU.cpp +++ b/sources/CPU/CPU.cpp @@ -10,7 +10,7 @@ namespace ComSquare::CPU { - CPU::CPU(std::shared_ptr bus) + CPU::CPU(std::shared_ptr bus) : _bus(std::move(bus)) { } diff --git a/sources/CPU/CPU.hpp b/sources/CPU/CPU.hpp index f399295..18660e2 100644 --- a/sources/CPU/CPU.hpp +++ b/sources/CPU/CPU.hpp @@ -174,7 +174,7 @@ namespace ComSquare::CPU }; //! @brief The main CPU - class CPU : public IMemory { + class CPU : public Memory::IMemory { private: //! @brief All the registers of the CPU Registers _registers{}; @@ -183,13 +183,13 @@ namespace ComSquare::CPU //! @brief Internal registers of the CPU (accessible from the bus via addr $4200 to $421F). InternalRegisters _internalRegisters{}; //! @brief The memory bus to use for read/write. - std::shared_ptr _bus; + std::shared_ptr _bus; //! @brief Execute a single instruction. //! @return The number of CPU cycles that the instruction took. int executeInstruction(); public: - explicit CPU(std::shared_ptr bus); + explicit CPU(std::shared_ptr bus); //! @brief This function continue to execute the Cartridge code. //! @return The number of CPU cycles that elapsed int update(); diff --git a/sources/Cartridge/Cartridge.cpp b/sources/Cartridge/Cartridge.cpp index 13062d4..3f6db78 100644 --- a/sources/Cartridge/Cartridge.cpp +++ b/sources/Cartridge/Cartridge.cpp @@ -43,14 +43,14 @@ namespace ComSquare::Cartridge } - uint8_t Cartridge::read(uint24_t addr) + uint8_t Cartridge::read_internal(uint24_t addr) { if (addr >= this->_size) throw InvalidAddress("Cartridge read", addr); return this->_data[addr]; } - void Cartridge::write(uint24_t addr, uint8_t data) + void Cartridge::write_internal(uint24_t addr, uint8_t data) { if (addr >= this->_size) throw InvalidAddress("Cartridge write", addr); diff --git a/sources/Cartridge/Cartridge.hpp b/sources/Cartridge/Cartridge.hpp index af35323..6310b91 100644 --- a/sources/Cartridge/Cartridge.hpp +++ b/sources/Cartridge/Cartridge.hpp @@ -8,16 +8,28 @@ #include #include "../Memory/IMemory.hpp" #include "../Models/Ints.hpp" +#include "../Memory/IRectangleMemory.hpp" namespace ComSquare::Cartridge { + enum MappingMode { + LowRom, + HiRom, + ExLowRom, + EwHiRom, + SA1Rom, // unimplemented + FastLoRom, + FastHiRom + }; + //! @brief Contains the rom's memory/instructions. - class Cartridge : IMemory { + class Cartridge : Memory::IRectangleMemory { private: //! @brief The rom data (contains all the instructions). uint8_t *_data; //! @brief The size of the rom data. size_t _size; + //! @brief Get the size of a rom from it's path. //! @param romPath The path of the rom to get info from. //! @return The size of the rom. @@ -27,16 +39,22 @@ namespace ComSquare::Cartridge explicit Cartridge(const std::string &romPath); //! @brief Destructor that free the cartridge data. ~Cartridge(); + + //! @brief The name of the game + std::string gameName; + //! @brief The memory mapping of the ROM. + MappingMode mappingMode; + //! @brief Read from the rom. //! @param addr The address to read from. The address 0x0 should refer to the first byte of the rom's memory. //! @throw InvalidAddress will be thrown if the address is more than the size of the rom's memory. //! @return Return the data at the address. - uint8_t read(uint24_t addr) override; + uint8_t read_internal(uint24_t addr) override; //! @brief Write data to the rom. //! @param addr The address to write to. The address 0x0 should refer to the first byte of the rom's memory. //! @param data The data to write. //! @throw InvalidAddress will be thrown if the address is more than the size of the rom's memory. - void write(uint24_t addr, uint8_t data) override; + void write_internal(uint24_t addr, uint8_t data) override; }; } diff --git a/sources/Memory/IMemory.cpp b/sources/Memory/IMemory.cpp index 7f0a380..03b2098 100644 --- a/sources/Memory/IMemory.cpp +++ b/sources/Memory/IMemory.cpp @@ -5,7 +5,7 @@ #include "IMemory.hpp" #include -namespace ComSquare +namespace ComSquare::Memory { void IMemory::setMemoryRegion(uint24_t start, uint24_t end) { diff --git a/sources/Memory/IMemory.hpp b/sources/Memory/IMemory.hpp index 990807b..694469d 100644 --- a/sources/Memory/IMemory.hpp +++ b/sources/Memory/IMemory.hpp @@ -10,7 +10,7 @@ #include #include "../Models/Ints.hpp" -namespace ComSquare +namespace ComSquare::Memory { //! @brief Common interface implemented by all components mapping memory. class IMemory { @@ -38,10 +38,10 @@ namespace ComSquare //! @brief Return true if this component has mapped the address. //! @param addr The address to check. //! @return True if this address is mapped to the component. False otherwise. - bool hasMemoryAt(uint24_t addr); + virtual bool hasMemoryAt(uint24_t addr); //! @brief Get the first address mapped to this component. //! @return the _start value. - uint24_t getStart(); + virtual uint24_t getStart(); }; }; diff --git a/sources/Memory/IRectangleMemory.cpp b/sources/Memory/IRectangleMemory.cpp new file mode 100644 index 0000000..43e0db5 --- /dev/null +++ b/sources/Memory/IRectangleMemory.cpp @@ -0,0 +1,65 @@ +// +// Created by anonymus-raccoon on 1/29/20. +// + +#include "IRectangleMemory.hpp" +#include "../Exceptions/InvalidAddress.hpp" + +namespace ComSquare::Memory +{ + uint8_t IRectangleMemory::read(uint24_t addr) + { + uint8_t bank = addr >> 16u; + uint16_t page = addr; + unsigned bankCount = bank - this->_startBank; + unsigned pageCount = this->_endPage - this->_startPage; + + if (bank < this->_startBank || bank >= this->_endBank) + throw InvalidAddress("Rectangle memory read Invalid Bank", addr); + if (page < this->_startPage || page >= this->_endPage) + throw InvalidAddress("Rectangle memory read Invalid Page", addr); + page -= this->_startPage; + page += pageCount * bankCount; + return this->read_internal(page); + } + + void IRectangleMemory::write(uint24_t addr, uint8_t data) + { + uint8_t bank = addr >> 16u; + uint16_t page = addr; + unsigned bankCount = bank - this->_startBank; + unsigned pageCount = this->_endPage - this->_startPage; + + if (bank < this->_startBank || bank >= this->_endBank) + throw InvalidAddress("Rectangle memory write Invalid Bank", addr); + if (page < this->_startPage || page >= this->_endPage) + throw InvalidAddress("Rectangle memory write Invalid Page", addr); + page -= this->_startPage; + page += pageCount * bankCount; + this->write_internal(page, data); + } + + bool IRectangleMemory::hasMemoryAt(uint24_t addr) + { + uint8_t bank = addr >> 16u; + uint16_t page = addr; + + if (this->_startBank <= bank && bank < this->_endBank) + if (this->_startPage <= page && page < this->_endPage) + return true; + return false; + } + + uint24_t IRectangleMemory::getStart() + { + return this->_startBank + this->_startPage; + } + + void IRectangleMemory::setMemoryRegion(uint8_t startBank, uint8_t endBank, uint16_t startPage, uint16_t endPage) + { + this->_startBank = startBank; + this->_endBank = endBank; + this->_startPage = startPage; + this->_endPage = endPage; + } +} \ No newline at end of file diff --git a/sources/Memory/IRectangleMemory.hpp b/sources/Memory/IRectangleMemory.hpp new file mode 100644 index 0000000..dc1835e --- /dev/null +++ b/sources/Memory/IRectangleMemory.hpp @@ -0,0 +1,62 @@ +// +// Created by anonymus-raccoon on 1/29/20. +// + +#ifndef COMSQUARE_IRECTANGLEMEMORY_HPP +#define COMSQUARE_IRECTANGLEMEMORY_HPP + + +#include "IMemory.hpp" + +namespace ComSquare::Memory +{ + //! @brief Superset of the IMemory to map non continuous rectangle to the memory. (A rectangle that spam across more than one bank but that does not start at 0000 or end at FFFF). + class IRectangleMemory : public IMemory { + private: + //! @brief The first bank to map to. + uint8_t _startBank = 0; + //! @brief The last bank to map to. + uint8_t _endBank = 0; + //! @brief The first address of each bank to map. + uint16_t _startPage = 0; + //! @brief The last address of each bank to map. + uint16_t _endPage = 0; + public: + //! @brief Read data from the component using the same method as the basic IMemory. + //! @param addr The local address to read from. 0x0 should refer to the first byte of this component on the fist bank. This method is responsible of mapping to the component's read. + //! @throw InvalidAddress if the address is not mapped to the component. + //! @return Return the data at the address given as parameter. + uint8_t read(uint24_t addr) override; + //! @brief Write data to this component using the same method as the basic IMemory. + //! @param addr The local address to write data 0x0 should refer to the first byte of this component on the fist bank. This method is responsible of mapping to the component's write. + //! @param data The new data to write. + //! @throw InvalidAddress if the address is not mapped to the component. + void write(uint24_t addr, uint8_t data) override; + //! @brief Internal component read. Implement this as you would implement a basic IMemory's read. + //! @param addr The local address to read from. 0x0 refer to the first byte of your data and the address is in the component's space. That means that you can consider this address as continuous + //! @throw This function should thrown an InvalidAddress for address that are not mapped to the component. + //! @return Return the data at the address given as parameter. + virtual uint8_t read_internal(uint24_t addr) = 0; + //! @brief Internal component write. Implement this as you would implement a basic IMemory's write. + //! @param addr The local address to write to. 0x0 refer to the first byte of your data and the address is in the component's space. That means that you can consider this address as continuous + //! @param data The new data to write. + //! @throw This function should thrown an InvalidAddress for address that are not mapped to the component. + virtual void write_internal(uint24_t addr, uint8_t data) = 0; + //! @brief Return true if this component has mapped the address. + //! @param addr The address to check. + //! @return True if this address is mapped to the component. False otherwise. + bool hasMemoryAt(uint24_t addr) override; + //! @brief Get the first address mapped to this component. + //! @return the _start value. + uint24_t getStart() override; + //! @brief Change starting and ending points of this mapped memory. + //! @param startBank The first bank mapped to this component. + //! @param endBank The last bank mapped to this component. + //! @param startPage The first page mapped to this component (every mapped banks will have this page mapped) + //! @param endPage The end page mapped to this component (every mapped banks will have this pages lower than this mapped) + //! @warning The start/end address should be a rectangle. To mirror memory, use the MemoryShadow class and not this one. + void setMemoryRegion(uint8_t startBank, uint8_t endBank, uint16_t startPage, uint16_t endPage); + }; +} + +#endif //COMSQUARE_IRECTANGLEMEMORY_HPP diff --git a/sources/Memory/MemoryBus.cpp b/sources/Memory/MemoryBus.cpp index fb18700..28e4245 100644 --- a/sources/Memory/MemoryBus.cpp +++ b/sources/Memory/MemoryBus.cpp @@ -8,7 +8,7 @@ #include "../SNES.hpp" #include "MemoryShadow.hpp" -namespace ComSquare +namespace ComSquare::Memory { std::shared_ptr MemoryBus::getAccessor(uint24_t addr) { @@ -78,6 +78,6 @@ namespace ComSquare for (uint24_t i = 0x800000; i < 0xC00000; i += 0x10000) this->_mirrorComponents(console, i); - // TODO should map sram, cartridge etc via the mapping mode of the cartridge. + // TODO should map SRam, cartridge etc via the mapping mode of the cartridge. } } \ No newline at end of file diff --git a/sources/Memory/MemoryBus.hpp b/sources/Memory/MemoryBus.hpp index 2494e56..83b2b5c 100644 --- a/sources/Memory/MemoryBus.hpp +++ b/sources/Memory/MemoryBus.hpp @@ -12,34 +12,45 @@ namespace ComSquare { - //! @brief The memory bus is the component responsible of mapping addresses to components address and transmitting the data. - class MemoryBus { - private: - //! @brief The list of components registered inside the bus. Every components that can read/write to a public address should be in this vector. - std::vector> _memoryAccessors; - //! @brief Helper function to get the components that is responsible of read/write at an address. - //! @param addr The address you want to look for. - //! @return The components responsible for the address param or nullptr if none was found. - std::shared_ptr getAccessor(uint24_t addr); - //! @brief The last value read via the memory bus. - uint8_t _openbus = 0; - //! @brief WRam, CPU, PPU & ALU registers are mirrored to all banks of Q1 & Q3. This function is used for the mirroring. - //! @param console All the components. - //! @param i Base address for the mirrors. - inline void _mirrorComponents(struct SNES &console, int i); - public: - //! @brief Read data at a global address. - //! @param addr The address to read from. - //! @return The value that the component returned for this address. If the address was mapped to ram, it simply returned the value. If the address was mapped to a register the component returned the register. - uint8_t read(uint24_t addr); - //! @brief Write a data to a global address. - //! @param addr The address to write to. - //! @param data The data to write. - void write(uint24_t addr, uint8_t data); - //! @brief Map components to the address space using the currently loaded cartridge to set the right mapping mode. - //! @param console All the components. - void mapComponents(struct SNES &console); - }; + struct SNES; + + namespace Memory + { + //! @brief The memory bus is the component responsible of mapping addresses to components address and transmitting the data. + class MemoryBus { + private: + //! @brief The list of components registered inside the bus. Every components that can read/write to a public address should be in this vector. + std::vector> _memoryAccessors; + + //! @brief Helper function to get the components that is responsible of read/write at an address. + //! @param addr The address you want to look for. + //! @return The components responsible for the address param or nullptr if none was found. + std::shared_ptr getAccessor(uint24_t addr); + + //! @brief The last value read via the memory bus. + uint8_t _openbus = 0; + + //! @brief WRam, CPU, PPU & ALU registers are mirrored to all banks of Q1 & Q3. This function is used for the mirroring. + //! @param console All the components. + //! @param i Base address for the mirrors. + inline void _mirrorComponents(SNES &console, int i); + + public: + //! @brief Read data at a global address. + //! @param addr The address to read from. + //! @return The value that the component returned for this address. If the address was mapped to ram, it simply returned the value. If the address was mapped to a register the component returned the register. + uint8_t read(uint24_t addr); + + //! @brief Write a data to a global address. + //! @param addr The address to write to. + //! @param data The data to write. + void write(uint24_t addr, uint8_t data); + + //! @brief Map components to the address space using the currently loaded cartridge to set the right mapping mode. + //! @param console All the components. + void mapComponents(SNES &console); + }; + } } diff --git a/sources/PPU/PPU.hpp b/sources/PPU/PPU.hpp index 7b7f24c..69f9442 100644 --- a/sources/PPU/PPU.hpp +++ b/sources/PPU/PPU.hpp @@ -11,7 +11,7 @@ namespace ComSquare::PPU { //! @brief The struct containing all the registers the PPU - class PPU : public IMemory { + class PPU : public Memory::IMemory { private: //! @brief INIDISP Register (F-blank and Brightness) union { diff --git a/sources/Ram/Ram.hpp b/sources/Ram/Ram.hpp index 98b73cf..e4eca02 100644 --- a/sources/Ram/Ram.hpp +++ b/sources/Ram/Ram.hpp @@ -9,7 +9,7 @@ namespace ComSquare::Ram { - class Ram : public IMemory { + class Ram : public Memory::IMemory { private: //! @brief The ram. (Can be used for WRam, SRam, VRam etc) uint8_t *_data; diff --git a/sources/SNES.cpp b/sources/SNES.cpp index 022a1aa..8e864dd 100644 --- a/sources/SNES.cpp +++ b/sources/SNES.cpp @@ -6,7 +6,7 @@ namespace ComSquare { - SNES::SNES(const std::shared_ptr &bus, const std::string &romPath) : + SNES::SNES(const std::shared_ptr &bus, const std::string &romPath) : cpu(new CPU::CPU(bus)), ppu(new PPU::PPU()), apu(new APU::APU()), diff --git a/sources/SNES.hpp b/sources/SNES.hpp index 8adc2f1..9557551 100644 --- a/sources/SNES.hpp +++ b/sources/SNES.hpp @@ -23,7 +23,7 @@ namespace ComSquare std::shared_ptr cartridge; std::shared_ptr wram; //! @brief Create all the components using a common memory bus for all of them. - SNES(const std::shared_ptr &bus, const std::string &ramPath); + SNES(const std::shared_ptr &bus, const std::string &ramPath); }; }