mirror of
https://github.com/MoleskiCoder/EightBit.git
synced 2024-12-22 09:30:32 +00:00
First stab at implementing MBC1 support for LR35902. Not complete, but all old tests still work.
Signed-off-by: Adrian.Conlon <adrian.conlon@gmail.com>
This commit is contained in:
parent
12c9125e9b
commit
2c7e32aa78
@ -20,7 +20,7 @@ void Board::initialise() {
|
||||
m_memory.loadRam(romDirectory + "/8080EX1.COM", 0x100); // Cringle/Bartholomew
|
||||
//m_memory.loadRam(romDirectory + "/CPUTEST.COM", 0x100); // SuperSoft diagnostics
|
||||
|
||||
m_memory.write(5, 0xc9); // ret
|
||||
m_memory.poke(5, 0xc9); // ret
|
||||
m_cpu.ExecutingInstruction.connect(std::bind(&Board::Cpu_ExecutingInstruction_Cpm, this, std::placeholders::_1));
|
||||
|
||||
if (m_configuration.isProfileMode()) {
|
||||
|
@ -15,7 +15,8 @@ Fuse::TestRunner::TestRunner(const Test& test, const ExpectedTestResult& expecte
|
||||
//
|
||||
|
||||
void Fuse::TestRunner::initialise() {
|
||||
m_bus.writeRegister(EightBit::Bus::BOOT_DISABLE, 1);
|
||||
m_bus.disableBootRom();
|
||||
m_bus.disableGameRom();
|
||||
initialiseRegisters();
|
||||
initialiseMemory();
|
||||
}
|
||||
@ -39,7 +40,7 @@ void Fuse::TestRunner::initialiseMemory() {
|
||||
auto address = memoryDatum.address;
|
||||
auto bytes = memoryDatum.bytes;
|
||||
for (int i = 0; i < bytes.size(); ++i)
|
||||
m_bus.write(address + i, bytes[i]);
|
||||
m_bus.poke(address + i, bytes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,18 @@ namespace EightBit {
|
||||
class Bus : public Memory {
|
||||
public:
|
||||
|
||||
enum CartridgeType {
|
||||
ROM = 0,
|
||||
ROM_MBC1 = 1,
|
||||
ROM_MBC1_RAM = 2,
|
||||
ROM_MBC1_RAM_BATTERY = 3,
|
||||
};
|
||||
|
||||
enum MbcOneMode {
|
||||
SixteenEight,
|
||||
FourThirtyTwo,
|
||||
};
|
||||
|
||||
enum {
|
||||
TotalLineCount = 154
|
||||
};
|
||||
@ -155,22 +167,41 @@ namespace EightBit {
|
||||
writeRegister(LY, 0);
|
||||
}
|
||||
|
||||
void disableBootRom() { m_disableBootRom = true; }
|
||||
void enableBootRom() { m_disableBootRom = false; }
|
||||
|
||||
void disableGameRom() { m_disableGameRom = true; }
|
||||
void enableGameRom() { m_disableGameRom = false; }
|
||||
|
||||
bool bootRomDisabled() const { return m_disableBootRom; }
|
||||
bool bootRomEnabled() const { return !bootRomDisabled(); }
|
||||
|
||||
void loadBootRom(const std::string& path);
|
||||
bool gameRomDisabled() const { return m_disableGameRom; }
|
||||
bool gameRomEnabled() const { return !gameRomDisabled(); }
|
||||
|
||||
bool isBootRom(uint16_t address) const {
|
||||
return (address < m_boot.size()) && bootRomEnabled();
|
||||
}
|
||||
void loadBootRom(const std::string& path);
|
||||
void loadGameRom(const std::string& path);
|
||||
|
||||
virtual uint8_t peek(uint16_t address) const;
|
||||
virtual void poke(uint16_t address, uint8_t value);
|
||||
|
||||
virtual uint8_t& reference();
|
||||
|
||||
private:
|
||||
std::array<uint8_t, 0x100> m_boot;
|
||||
std::vector<uint8_t> m_bootRom;
|
||||
std::vector<uint8_t> m_gameRom;
|
||||
|
||||
bool m_disableBootRom;
|
||||
bool m_disableGameRom;
|
||||
|
||||
bool m_mbc1;
|
||||
bool m_rom;
|
||||
bool m_ram;
|
||||
bool m_battery;
|
||||
|
||||
MbcOneMode m_mbc1Mode;
|
||||
int m_romBank;
|
||||
int m_ramBank;
|
||||
|
||||
int m_divCounter;
|
||||
int m_timerCounter;
|
||||
@ -180,5 +211,7 @@ namespace EightBit {
|
||||
|
||||
void checkDiv(int cycles);
|
||||
void checkTimer(int cycles);
|
||||
|
||||
void validateCartridgeType();
|
||||
};
|
||||
}
|
@ -4,9 +4,19 @@
|
||||
EightBit::Bus::Bus()
|
||||
: Memory(0xffff),
|
||||
m_disableBootRom(false),
|
||||
m_disableGameRom(false),
|
||||
m_mbc1(false),
|
||||
m_rom(false),
|
||||
m_ram(false),
|
||||
m_battery(false),
|
||||
m_mbc1Mode(SixteenEight),
|
||||
m_romBank(0),
|
||||
m_ramBank(0),
|
||||
m_divCounter(256),
|
||||
m_timerCounter(0),
|
||||
m_timerRate(0) {
|
||||
m_bootRom.resize(0x100);
|
||||
m_gameRom.resize(0x10000);
|
||||
WrittenByte.connect(std::bind(&Bus::Bus_WrittenByte, this, std::placeholders::_1));
|
||||
}
|
||||
|
||||
@ -17,42 +27,77 @@ void EightBit::Bus::reset() {
|
||||
|
||||
void EightBit::Bus::clear() {
|
||||
Memory::clear();
|
||||
m_boot.fill(0);
|
||||
std::fill(m_bootRom.begin(), m_bootRom.end(), 0);
|
||||
std::fill(m_gameRom.begin(), m_gameRom.end(), 0);
|
||||
}
|
||||
|
||||
void EightBit::Bus::loadBootRom(const std::string& path) {
|
||||
auto size = loadMemory(path, 0);
|
||||
if (size != 0x100)
|
||||
throw std::runtime_error("Incorrectly sized boot ROM");
|
||||
std::copy_n(m_bus.cbegin(), size, m_boot.begin());
|
||||
loadBinary(path, m_bootRom, 0, 0x100);
|
||||
}
|
||||
|
||||
void EightBit::Bus::loadGameRom(const std::string& path) {
|
||||
loadBinary(path, m_gameRom);
|
||||
validateCartridgeType();
|
||||
}
|
||||
|
||||
uint8_t& EightBit::Bus::reference() {
|
||||
auto effective = effectiveAddress(ADDRESS().word);
|
||||
if (isBootRom(effective))
|
||||
return placeDATA(m_boot[effective]);
|
||||
if ((effective < 0x100) && bootRomEnabled())
|
||||
return placeDATA(m_bootRom[effective]);
|
||||
if ((effective < 0x4000) && gameRomEnabled())
|
||||
return placeDATA(m_gameRom[effective]);
|
||||
if ((effective < 0x8000) && gameRomEnabled())
|
||||
return placeDATA(m_gameRom[effective + (m_romBank * 0x4000)]);
|
||||
return Memory::reference();
|
||||
}
|
||||
|
||||
uint8_t EightBit::Bus::peek(uint16_t address) const {
|
||||
auto effective = effectiveAddress(address);
|
||||
if (isBootRom(effective))
|
||||
return m_boot[effective];
|
||||
if ((effective < 0x100) && bootRomEnabled())
|
||||
return m_bootRom[effective];
|
||||
if ((effective < 0x4000) && gameRomEnabled())
|
||||
return m_gameRom[effective];
|
||||
if ((effective < 0x8000) && gameRomEnabled())
|
||||
return m_gameRom[effective + (m_romBank * 0x4000)];
|
||||
return m_bus[effective];
|
||||
}
|
||||
|
||||
void EightBit::Bus::poke(uint16_t address, uint8_t value) {
|
||||
auto effective = effectiveAddress(address);
|
||||
if ((effective < 0x100) && bootRomEnabled())
|
||||
m_bootRom[effective] = value;
|
||||
if ((effective < 0x4000) && gameRomEnabled())
|
||||
m_gameRom[effective] = value;
|
||||
if ((effective < 0x8000) && gameRomEnabled())
|
||||
m_gameRom[effective + (m_romBank * 0x4000)] = value;
|
||||
m_bus[effective] = value;
|
||||
}
|
||||
|
||||
void EightBit::Bus::Bus_WrittenByte(const AddressEventArgs& e) {
|
||||
switch (e.getAddress()) {
|
||||
case BASE + TAC:
|
||||
m_timerRate = timerClockTicks();
|
||||
|
||||
const auto address = e.getAddress();
|
||||
const auto value = e.getCell();
|
||||
|
||||
switch (address & 0xe000) {
|
||||
case 0x2000:
|
||||
m_romBank = value & Processor::Mask5;
|
||||
break;
|
||||
case BASE + BOOT_DISABLE:
|
||||
m_disableBootRom = e.getCell() != 0;
|
||||
break;
|
||||
case BASE + DIV:
|
||||
reference() = 0;
|
||||
m_timerCounter = 0;
|
||||
case 0x6000:
|
||||
m_mbc1Mode = value & Processor::Mask1 ? FourThirtyTwo : SixteenEight;
|
||||
break;
|
||||
default:
|
||||
switch (address) {
|
||||
case BASE + TAC:
|
||||
m_timerRate = timerClockTicks();
|
||||
break;
|
||||
case BASE + BOOT_DISABLE:
|
||||
m_disableBootRom = value != 0;
|
||||
break;
|
||||
case BASE + DIV:
|
||||
reference() = 0;
|
||||
m_timerCounter = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,3 +123,25 @@ void EightBit::Bus::checkTimer(int cycles) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EightBit::Bus::validateCartridgeType() {
|
||||
|
||||
m_rom = m_mbc1 = m_ram = m_battery = false;
|
||||
|
||||
switch (m_gameRom[0x147]) {
|
||||
case ROM:
|
||||
m_rom = true;
|
||||
break;
|
||||
case ROM_MBC1:
|
||||
m_rom = m_mbc1 = true;
|
||||
break;
|
||||
case ROM_MBC1_RAM:
|
||||
m_rom = m_mbc1 = m_ram = true;
|
||||
break;
|
||||
case ROM_MBC1_RAM_BATTERY:
|
||||
m_rom = m_mbc1 = m_ram = m_battery = true;
|
||||
break;
|
||||
default:
|
||||
throw std::domain_error("Unhandled cartridge ROM type");
|
||||
}
|
||||
}
|
||||
|
@ -133,5 +133,5 @@ void Board::Cpu_ExecutedInstruction_Poll(const EightBit::MOS6502& cpu) {
|
||||
|
||||
void Board::pollKeyboard() {
|
||||
if (_kbhit())
|
||||
m_memory.write(m_configuration.getInputAddress(), _getch());
|
||||
m_memory.poke(m_configuration.getInputAddress(), _getch());
|
||||
}
|
@ -56,7 +56,7 @@ void Fuse::TestRunner::initialiseMemory() {
|
||||
auto address = memoryDatum.address;
|
||||
auto bytes = memoryDatum.bytes;
|
||||
for (int i = 0; i < bytes.size(); ++i)
|
||||
m_memory.write(address + i, bytes[i]);
|
||||
m_memory.poke(address + i, bytes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ void Board::initialise() {
|
||||
//m_memory.loadRam(romDirectory + "/CPUTEST.COM", 0x100); // SuperSoft diagnostics
|
||||
//m_memory.loadRam(romDirectory + "/TEST.COM", 0x100); // Microcosm
|
||||
|
||||
m_memory.write(5, 0xc9); // ret
|
||||
m_memory.poke(5, 0xc9); // ret
|
||||
m_cpu.ExecutingInstruction.connect(std::bind(&Board::Cpu_ExecutingInstruction_Cpm, this, std::placeholders::_1));
|
||||
|
||||
if (m_configuration.isProfileMode()) {
|
||||
|
@ -54,6 +54,8 @@ namespace EightBit {
|
||||
}
|
||||
|
||||
virtual uint8_t peek(uint16_t address) const;
|
||||
virtual void poke(uint16_t address, uint8_t value);
|
||||
|
||||
virtual uint16_t peekWord(uint16_t address) const;
|
||||
|
||||
virtual int effectiveAddress(int address) const {
|
||||
@ -100,14 +102,16 @@ namespace EightBit {
|
||||
}
|
||||
|
||||
protected:
|
||||
std::array<uint8_t, 0x10000> m_bus;
|
||||
std::array<bool, 0x10000> m_locked;
|
||||
std::vector<uint8_t> m_bus;
|
||||
std::vector<bool> m_locked;
|
||||
|
||||
uint16_t m_addressMask; // Mirror
|
||||
uint8_t m_temporary; // Used to simulate ROM
|
||||
register16_t m_address;
|
||||
uint8_t* m_data;
|
||||
|
||||
static int loadBinary(const std::string& path, std::vector<uint8_t>& output, int offset = 0, int maximumSize = -1);
|
||||
|
||||
void fireReadBusEvent() {
|
||||
ReadByte.fire(AddressEventArgs(ADDRESS().word, DATA()));
|
||||
}
|
||||
|
@ -8,10 +8,12 @@
|
||||
|
||||
EightBit::Memory::Memory(uint16_t addressMask)
|
||||
: m_addressMask(addressMask),
|
||||
m_temporary(0) {
|
||||
m_temporary(0),
|
||||
m_data(nullptr) {
|
||||
clear();
|
||||
m_address.word = 0;
|
||||
m_data = &(m_bus[m_address.word]);
|
||||
m_bus.resize(0x10000);
|
||||
m_locked.resize(0x10000);
|
||||
}
|
||||
|
||||
EightBit::Memory::~Memory() {
|
||||
@ -21,6 +23,10 @@ uint8_t EightBit::Memory::peek(uint16_t address) const {
|
||||
return m_bus[address];
|
||||
}
|
||||
|
||||
void EightBit::Memory::poke(uint16_t address, uint8_t value) {
|
||||
m_bus[address] = value;
|
||||
}
|
||||
|
||||
uint16_t EightBit::Memory::peekWord(uint16_t address) const {
|
||||
register16_t returned;
|
||||
returned.low = peek(address);
|
||||
@ -29,8 +35,8 @@ uint16_t EightBit::Memory::peekWord(uint16_t address) const {
|
||||
}
|
||||
|
||||
void EightBit::Memory::clear() {
|
||||
m_bus.fill(0);
|
||||
m_locked.fill(false);
|
||||
std::fill(m_bus.begin(), m_bus.end(), 0);
|
||||
std::fill(m_locked.begin(), m_locked.end(), false);
|
||||
}
|
||||
|
||||
void EightBit::Memory::loadRom(const std::string& path, uint16_t offset) {
|
||||
@ -43,21 +49,26 @@ void EightBit::Memory::loadRam(const std::string& path, uint16_t offset) {
|
||||
}
|
||||
|
||||
int EightBit::Memory::loadMemory(const std::string& path, uint16_t offset) {
|
||||
return loadBinary(path, m_bus, offset, 0x10000 - offset);
|
||||
}
|
||||
|
||||
int EightBit::Memory::loadBinary(const std::string& path, std::vector<uint8_t>& output, int offset, int maximumSize) {
|
||||
std::ifstream file;
|
||||
file.exceptions(std::ios::failbit | std::ios::badbit);
|
||||
|
||||
file.open(path, std::ios::binary | std::ios::ate);
|
||||
auto size = (int)file.tellg();
|
||||
if ((maximumSize > 0) && (size > maximumSize))
|
||||
throw std::runtime_error("Binary cannot fit");
|
||||
|
||||
size_t extent = size + offset;
|
||||
|
||||
if (extent > m_bus.size())
|
||||
throw std::runtime_error("ROM cannot fit");
|
||||
if (output.size() < extent)
|
||||
output.resize(extent);
|
||||
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
file.read((char*)&m_bus[offset], size);
|
||||
file.read((char*)&output[offset], size);
|
||||
file.close();
|
||||
|
||||
return size;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user