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:
Adrian.Conlon 2017-08-23 23:17:45 +01:00
parent 12c9125e9b
commit 2c7e32aa78
9 changed files with 156 additions and 40 deletions

View File

@ -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()) {

View File

@ -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]);
}
}

View File

@ -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();
};
}

View File

@ -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");
}
}

View File

@ -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());
}

View File

@ -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]);
}
}

View File

@ -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()) {

View File

@ -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()));
}

View File

@ -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;
}
}