815 lines
24 KiB
JavaScript
815 lines
24 KiB
JavaScript
function MemoryView(memory, offset) {
|
|
this.inherit();
|
|
this.buffer = memory;
|
|
this.view = new DataView(this.buffer, typeof(offset) === "number" ? offset : 0);
|
|
this.mask = memory.byteLength - 1;
|
|
this.resetMask();
|
|
};
|
|
|
|
MemoryView.prototype.resetMask = function() {
|
|
this.mask8 = this.mask & 0xFFFFFFFF;
|
|
this.mask16 = this.mask & 0xFFFFFFFE;
|
|
this.mask32 = this.mask & 0xFFFFFFFC;
|
|
};
|
|
|
|
MemoryView.prototype.load8 = function(offset) {
|
|
return this.view.getInt8(offset & this.mask8);
|
|
};
|
|
|
|
MemoryView.prototype.load16 = function(offset) {
|
|
// Unaligned 16-bit loads are unpredictable...let's just pretend they work
|
|
return this.view.getInt16(offset & this.mask, true);
|
|
};
|
|
|
|
MemoryView.prototype.loadU8 = function(offset) {
|
|
return this.view.getUint8(offset & this.mask8);
|
|
};
|
|
|
|
MemoryView.prototype.loadU16 = function(offset) {
|
|
// Unaligned 16-bit loads are unpredictable...let's just pretend they work
|
|
return this.view.getUint16(offset & this.mask, true);
|
|
};
|
|
|
|
MemoryView.prototype.load32 = function(offset) {
|
|
// Unaligned 32-bit loads are "rotated" so they make some semblance of sense
|
|
var rotate = (offset & 3) << 3;
|
|
var mem = this.view.getInt32(offset & this.mask32, true);
|
|
return (mem >>> rotate) | (mem << (32 - rotate));
|
|
};
|
|
|
|
MemoryView.prototype.store8 = function(offset, value) {
|
|
this.view.setInt8(offset & this.mask8, value);
|
|
};
|
|
|
|
MemoryView.prototype.store16 = function(offset, value) {
|
|
this.view.setInt16(offset & this.mask16, value, true);
|
|
};
|
|
|
|
MemoryView.prototype.store32 = function(offset, value) {
|
|
this.view.setInt32(offset & this.mask32, value, true);
|
|
};
|
|
|
|
MemoryView.prototype.invalidatePage = function(address) {};
|
|
|
|
MemoryView.prototype.replaceData = function(memory, offset) {
|
|
this.buffer = memory;
|
|
this.view = new DataView(this.buffer, typeof(offset) === "number" ? offset : 0);
|
|
if (this.icache) {
|
|
this.icache = new Array(this.icache.length);
|
|
}
|
|
};
|
|
|
|
function MemoryBlock(size, cacheBits) {
|
|
MemoryView.call(this, new ArrayBuffer(size));
|
|
this.ICACHE_PAGE_BITS = cacheBits;
|
|
this.PAGE_MASK = (2 << this.ICACHE_PAGE_BITS) - 1;
|
|
this.icache = new Array(size >> (this.ICACHE_PAGE_BITS + 1));
|
|
};
|
|
|
|
MemoryBlock.prototype = Object.create(MemoryView.prototype);
|
|
|
|
MemoryBlock.prototype.invalidatePage = function(address) {
|
|
var page = this.icache[(address & this.mask) >> this.ICACHE_PAGE_BITS];
|
|
if (page) {
|
|
page.invalid = true;
|
|
}
|
|
};
|
|
|
|
function ROMView(rom, offset) {
|
|
MemoryView.call(this, rom, offset);
|
|
this.ICACHE_PAGE_BITS = 10;
|
|
this.PAGE_MASK = (2 << this.ICACHE_PAGE_BITS) - 1;
|
|
this.icache = new Array(rom.byteLength >> (this.ICACHE_PAGE_BITS + 1));
|
|
this.mask = 0x01FFFFFF;
|
|
this.resetMask();
|
|
};
|
|
|
|
ROMView.prototype = Object.create(MemoryView.prototype);
|
|
|
|
ROMView.prototype.store8 = function(offset, value) {};
|
|
|
|
ROMView.prototype.store16 = function(offset, value) {
|
|
if (offset < 0xCA && offset >= 0xC4) {
|
|
if (!this.gpio) {
|
|
this.gpio = this.mmu.allocGPIO(this);
|
|
}
|
|
this.gpio.store16(offset, value);
|
|
}
|
|
};
|
|
|
|
ROMView.prototype.store32 = function(offset, value) {
|
|
if (offset < 0xCA && offset >= 0xC4) {
|
|
if (!this.gpio) {
|
|
this.gpio = this.mmu.allocGPIO(this);
|
|
}
|
|
this.gpio.store32(offset, value);
|
|
}
|
|
};
|
|
|
|
function BIOSView(rom, offset) {
|
|
MemoryView.call(this, rom, offset);
|
|
this.ICACHE_PAGE_BITS = 16;
|
|
this.PAGE_MASK = (2 << this.ICACHE_PAGE_BITS) - 1;
|
|
this.icache = new Array(1);
|
|
};
|
|
|
|
BIOSView.prototype = Object.create(MemoryView.prototype);
|
|
|
|
BIOSView.prototype.load8 = function(offset) {
|
|
if (offset >= this.buffer.byteLength) {
|
|
return -1;
|
|
}
|
|
return this.view.getInt8(offset);
|
|
};
|
|
|
|
BIOSView.prototype.load16 = function(offset) {
|
|
if (offset >= this.buffer.byteLength) {
|
|
return -1;
|
|
}
|
|
return this.view.getInt16(offset, true);
|
|
};
|
|
|
|
BIOSView.prototype.loadU8 = function(offset) {
|
|
if (offset >= this.buffer.byteLength) {
|
|
return -1;
|
|
}
|
|
return this.view.getUint8(offset);
|
|
};
|
|
|
|
BIOSView.prototype.loadU16 = function(offset) {
|
|
if (offset >= this.buffer.byteLength) {
|
|
return -1;
|
|
}
|
|
return this.view.getUint16(offset, true);
|
|
};
|
|
|
|
BIOSView.prototype.load32 = function(offset) {
|
|
if (offset >= this.buffer.byteLength) {
|
|
return -1;
|
|
}
|
|
return this.view.getInt32(offset, true);
|
|
};
|
|
|
|
BIOSView.prototype.store8 = function(offset, value) {};
|
|
|
|
BIOSView.prototype.store16 = function(offset, value) {};
|
|
|
|
BIOSView.prototype.store32 = function(offset, value) {};
|
|
|
|
function BadMemory(mmu, cpu) {
|
|
this.inherit();
|
|
this.cpu = cpu;
|
|
this.mmu = mmu
|
|
};
|
|
|
|
BadMemory.prototype.load8 = function(offset) {
|
|
return this.mmu.load8(this.cpu.gprs[this.cpu.PC] - this.cpu.instructionWidth + (offset & 0x3));
|
|
};
|
|
|
|
BadMemory.prototype.load16 = function(offset) {
|
|
return this.mmu.load16(this.cpu.gprs[this.cpu.PC] - this.cpu.instructionWidth + (offset & 0x2));
|
|
};
|
|
|
|
BadMemory.prototype.loadU8 = function(offset) {
|
|
return this.mmu.loadU8(this.cpu.gprs[this.cpu.PC] - this.cpu.instructionWidth + (offset & 0x3));
|
|
};
|
|
|
|
BadMemory.prototype.loadU16 = function(offset) {
|
|
return this.mmu.loadU16(this.cpu.gprs[this.cpu.PC] - this.cpu.instructionWidth + (offset & 0x2));
|
|
};
|
|
|
|
BadMemory.prototype.load32 = function(offset) {
|
|
if (this.cpu.execMode == this.cpu.MODE_ARM) {
|
|
return this.mmu.load32(this.cpu.gprs[this.cpu.gprs.PC] - this.cpu.instructionWidth);
|
|
} else {
|
|
var halfword = this.mmu.loadU16(this.cpu.gprs[this.cpu.PC] - this.cpu.instructionWidth);
|
|
return halfword | (halfword << 16);
|
|
}
|
|
};
|
|
|
|
BadMemory.prototype.store8 = function(offset, value) {};
|
|
|
|
BadMemory.prototype.store16 = function(offset, value) {};
|
|
|
|
BadMemory.prototype.store32 = function(offset, value) {};
|
|
|
|
BadMemory.prototype.invalidatePage = function(address) {};
|
|
|
|
function GameBoyAdvanceMMU() {
|
|
this.inherit();
|
|
this.REGION_BIOS = 0x0;
|
|
this.REGION_WORKING_RAM = 0x2;
|
|
this.REGION_WORKING_IRAM = 0x3;
|
|
this.REGION_IO = 0x4;
|
|
this.REGION_PALETTE_RAM = 0x5;
|
|
this.REGION_VRAM = 0x6;
|
|
this.REGION_OAM = 0x7;
|
|
this.REGION_CART0 = 0x8;
|
|
this.REGION_CART1 = 0xA;
|
|
this.REGION_CART2 = 0xC;
|
|
this.REGION_CART_SRAM = 0xE;
|
|
|
|
this.BASE_BIOS = 0x00000000;
|
|
this.BASE_WORKING_RAM = 0x02000000;
|
|
this.BASE_WORKING_IRAM = 0x03000000;
|
|
this.BASE_IO = 0x04000000;
|
|
this.BASE_PALETTE_RAM = 0x05000000;
|
|
this.BASE_VRAM = 0x06000000;
|
|
this.BASE_OAM = 0x07000000;
|
|
this.BASE_CART0 = 0x08000000;
|
|
this.BASE_CART1 = 0x0A000000;
|
|
this.BASE_CART2 = 0x0C000000;
|
|
this.BASE_CART_SRAM = 0x0E000000;
|
|
|
|
this.BASE_MASK = 0x0F000000;
|
|
this.BASE_OFFSET = 24;
|
|
this.OFFSET_MASK = 0x00FFFFFF;
|
|
|
|
this.SIZE_BIOS = 0x00004000;
|
|
this.SIZE_WORKING_RAM = 0x00040000;
|
|
this.SIZE_WORKING_IRAM = 0x00008000;
|
|
this.SIZE_IO = 0x00000400;
|
|
this.SIZE_PALETTE_RAM = 0x00000400;
|
|
this.SIZE_VRAM = 0x00018000;
|
|
this.SIZE_OAM = 0x00000400;
|
|
this.SIZE_CART0 = 0x02000000;
|
|
this.SIZE_CART1 = 0x02000000;
|
|
this.SIZE_CART2 = 0x02000000;
|
|
this.SIZE_CART_SRAM = 0x00008000;
|
|
this.SIZE_CART_FLASH512 = 0x00010000;
|
|
this.SIZE_CART_FLASH1M = 0x00020000;
|
|
this.SIZE_CART_EEPROM = 0x00002000;
|
|
|
|
this.DMA_TIMING_NOW = 0;
|
|
this.DMA_TIMING_VBLANK = 1;
|
|
this.DMA_TIMING_HBLANK = 2;
|
|
this.DMA_TIMING_CUSTOM = 3;
|
|
|
|
this.DMA_INCREMENT = 0;
|
|
this.DMA_DECREMENT = 1;
|
|
this.DMA_FIXED = 2;
|
|
this.DMA_INCREMENT_RELOAD = 3;
|
|
|
|
this.DMA_OFFSET = [ 1, -1, 0, 1 ];
|
|
|
|
this.WAITSTATES = [ 0, 0, 2, 0, 0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4 ];
|
|
this.WAITSTATES_32 = [ 0, 0, 5, 0, 0, 1, 0, 1, 7, 7, 9, 9, 13, 13, 8 ];
|
|
this.WAITSTATES_SEQ = [ 0, 0, 2, 0, 0, 0, 0, 0, 2, 2, 4, 4, 8, 8, 4 ];
|
|
this.WAITSTATES_SEQ_32 = [ 0, 0, 5, 0, 0, 1, 0, 1, 5, 5, 9, 9, 17, 17, 8 ];
|
|
this.NULLWAIT = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ];
|
|
|
|
for (var i = 15; i < 256; ++i) {
|
|
this.WAITSTATES[i] = 0;
|
|
this.WAITSTATES_32[i] = 0;
|
|
this.WAITSTATES_SEQ[i] = 0;
|
|
this.WAITSTATES_SEQ_32[i] = 0;
|
|
this.NULLWAIT[i] = 0;
|
|
}
|
|
|
|
this.ROM_WS = [ 4, 3, 2, 8 ];
|
|
this.ROM_WS_SEQ = [
|
|
[ 2, 1 ],
|
|
[ 4, 1 ],
|
|
[ 8, 1 ]
|
|
];
|
|
|
|
this.ICACHE_PAGE_BITS = 8;
|
|
this.PAGE_MASK = (2 << this.ICACHE_PAGE_BITS) - 1;
|
|
|
|
this.bios = null;
|
|
};
|
|
|
|
GameBoyAdvanceMMU.prototype.mmap = function(region, object) {
|
|
this.memory[region] = object;
|
|
}
|
|
|
|
GameBoyAdvanceMMU.prototype.clear = function() {
|
|
this.badMemory = new BadMemory(this, this.cpu);
|
|
this.memory = [
|
|
this.bios,
|
|
this.badMemory, // Unused
|
|
new MemoryBlock(this.SIZE_WORKING_RAM, 9),
|
|
new MemoryBlock(this.SIZE_WORKING_IRAM, 7),
|
|
null, // This is owned by GameBoyAdvanceIO
|
|
null, // This is owned by GameBoyAdvancePalette
|
|
null, // This is owned by GameBoyAdvanceVRAM
|
|
null, // This is owned by GameBoyAdvanceOAM
|
|
this.badMemory,
|
|
this.badMemory,
|
|
this.badMemory,
|
|
this.badMemory,
|
|
this.badMemory,
|
|
this.badMemory,
|
|
this.badMemory,
|
|
this.badMemory // Unused
|
|
];
|
|
for (var i = 16; i < 256; ++i) {
|
|
this.memory[i] = this.badMemory;
|
|
}
|
|
|
|
this.waitstates = this.WAITSTATES.slice(0);
|
|
this.waitstatesSeq = this.WAITSTATES_SEQ.slice(0);
|
|
this.waitstates32 = this.WAITSTATES_32.slice(0);
|
|
this.waitstatesSeq32 = this.WAITSTATES_SEQ_32.slice(0);
|
|
this.waitstatesPrefetch = this.WAITSTATES_SEQ.slice(0);
|
|
this.waitstatesPrefetch32 = this.WAITSTATES_SEQ_32.slice(0);
|
|
|
|
this.cart = null;
|
|
this.save = null;
|
|
|
|
this.DMA_REGISTER = [
|
|
this.core.io.DMA0CNT_HI >> 1,
|
|
this.core.io.DMA1CNT_HI >> 1,
|
|
this.core.io.DMA2CNT_HI >> 1,
|
|
this.core.io.DMA3CNT_HI >> 1
|
|
];
|
|
};
|
|
|
|
GameBoyAdvanceMMU.prototype.freeze = function() {
|
|
return {
|
|
'ram': Serializer.prefix(this.memory[this.REGION_WORKING_RAM].buffer),
|
|
'iram': Serializer.prefix(this.memory[this.REGION_WORKING_IRAM].buffer),
|
|
};
|
|
};
|
|
|
|
GameBoyAdvanceMMU.prototype.defrost = function(frost) {
|
|
this.memory[this.REGION_WORKING_RAM].replaceData(frost.ram);
|
|
this.memory[this.REGION_WORKING_IRAM].replaceData(frost.iram);
|
|
};
|
|
|
|
GameBoyAdvanceMMU.prototype.loadBios = function(bios, real) {
|
|
this.bios = new BIOSView(bios);
|
|
this.bios.real = !!real;
|
|
};
|
|
|
|
GameBoyAdvanceMMU.prototype.loadRom = function(rom, process) {
|
|
var cart = {
|
|
title: null,
|
|
code: null,
|
|
maker: null,
|
|
memory: rom,
|
|
saveType: null,
|
|
};
|
|
|
|
var lo = new ROMView(rom);
|
|
if (lo.view.getUint8(0xB2) != 0x96) {
|
|
// Not a valid ROM
|
|
return null;
|
|
}
|
|
lo.mmu = this; // Needed for GPIO
|
|
this.memory[this.REGION_CART0] = lo;
|
|
this.memory[this.REGION_CART1] = lo;
|
|
this.memory[this.REGION_CART2] = lo;
|
|
|
|
if (rom.byteLength > 0x01000000) {
|
|
var hi = new ROMView(rom, 0x01000000);
|
|
this.memory[this.REGION_CART0 + 1] = hi;
|
|
this.memory[this.REGION_CART1 + 1] = hi;
|
|
this.memory[this.REGION_CART2 + 1] = hi;
|
|
}
|
|
|
|
if (process) {
|
|
var name = '';
|
|
for (var i = 0; i < 12; ++i) {
|
|
var c = lo.loadU8(i + 0xA0);
|
|
if (!c) {
|
|
break;
|
|
}
|
|
name += String.fromCharCode(c);
|
|
}
|
|
cart.title = name;
|
|
|
|
var code = '';
|
|
for (var i = 0; i < 4; ++i) {
|
|
var c = lo.loadU8(i + 0xAC);
|
|
if (!c) {
|
|
break;
|
|
}
|
|
code += String.fromCharCode(c);
|
|
}
|
|
cart.code = code;
|
|
|
|
var maker = '';
|
|
for (var i = 0; i < 2; ++i) {
|
|
var c = lo.loadU8(i + 0xB0);
|
|
if (!c) {
|
|
break;
|
|
}
|
|
maker += String.fromCharCode(c);
|
|
}
|
|
cart.maker = maker;
|
|
|
|
// Find savedata type
|
|
var state = '';
|
|
var next;
|
|
var terminal = false;
|
|
for (var i = 0xE4; i < rom.byteLength && !terminal; ++i) {
|
|
next = String.fromCharCode(lo.loadU8(i));
|
|
state += next;
|
|
switch (state) {
|
|
case 'F':
|
|
case 'FL':
|
|
case 'FLA':
|
|
case 'FLAS':
|
|
case 'FLASH':
|
|
case 'FLASH_':
|
|
case 'FLASH5':
|
|
case 'FLASH51':
|
|
case 'FLASH512':
|
|
case 'FLASH512_':
|
|
case 'FLASH1':
|
|
case 'FLASH1M':
|
|
case 'FLASH1M_':
|
|
case 'S':
|
|
case 'SR':
|
|
case 'SRA':
|
|
case 'SRAM':
|
|
case 'SRAM_':
|
|
case 'E':
|
|
case 'EE':
|
|
case 'EEP':
|
|
case 'EEPR':
|
|
case 'EEPRO':
|
|
case 'EEPROM':
|
|
case 'EEPROM_':
|
|
break;
|
|
case 'FLASH_V':
|
|
case 'FLASH512_V':
|
|
case 'FLASH1M_V':
|
|
case 'SRAM_V':
|
|
case 'EEPROM_V':
|
|
terminal = true;
|
|
break;
|
|
default:
|
|
state = next;
|
|
break;
|
|
}
|
|
}
|
|
if (terminal) {
|
|
cart.saveType = state;
|
|
switch (state) {
|
|
case 'FLASH_V':
|
|
case 'FLASH512_V':
|
|
this.save = this.memory[this.REGION_CART_SRAM] = new FlashSavedata(this.SIZE_CART_FLASH512);
|
|
break;
|
|
case 'FLASH1M_V':
|
|
this.save = this.memory[this.REGION_CART_SRAM] = new FlashSavedata(this.SIZE_CART_FLASH1M);
|
|
break;
|
|
case 'SRAM_V':
|
|
this.save = this.memory[this.REGION_CART_SRAM] = new SRAMSavedata(this.SIZE_CART_SRAM);
|
|
break;
|
|
case 'EEPROM_V':
|
|
this.save = this.memory[this.REGION_CART2 + 1] = new EEPROMSavedata(this.SIZE_CART_EEPROM, this);
|
|
break;
|
|
}
|
|
}
|
|
if (!this.save) {
|
|
// Assume we have SRAM
|
|
this.save = this.memory[this.REGION_CART_SRAM] = new SRAMSavedata(this.SIZE_CART_SRAM);
|
|
}
|
|
}
|
|
|
|
this.cart = cart;
|
|
return cart;
|
|
};
|
|
|
|
GameBoyAdvanceMMU.prototype.loadSavedata = function(save) {
|
|
this.save.replaceData(save);
|
|
};
|
|
|
|
GameBoyAdvanceMMU.prototype.load8 = function(offset) {
|
|
return this.memory[offset >>> this.BASE_OFFSET].load8(offset & 0x00FFFFFF);
|
|
};
|
|
|
|
GameBoyAdvanceMMU.prototype.load16 = function(offset) {
|
|
return this.memory[offset >>> this.BASE_OFFSET].load16(offset & 0x00FFFFFF);
|
|
};
|
|
|
|
GameBoyAdvanceMMU.prototype.load32 = function(offset) {
|
|
return this.memory[offset >>> this.BASE_OFFSET].load32(offset & 0x00FFFFFF);
|
|
};
|
|
|
|
GameBoyAdvanceMMU.prototype.loadU8 = function(offset) {
|
|
return this.memory[offset >>> this.BASE_OFFSET].loadU8(offset & 0x00FFFFFF);
|
|
};
|
|
|
|
GameBoyAdvanceMMU.prototype.loadU16 = function(offset) {
|
|
return this.memory[offset >>> this.BASE_OFFSET].loadU16(offset & 0x00FFFFFF);
|
|
};
|
|
|
|
GameBoyAdvanceMMU.prototype.store8 = function(offset, value) {
|
|
var maskedOffset = offset & 0x00FFFFFF;
|
|
var memory = this.memory[offset >>> this.BASE_OFFSET];
|
|
memory.store8(maskedOffset, value);
|
|
memory.invalidatePage(maskedOffset);
|
|
};
|
|
|
|
GameBoyAdvanceMMU.prototype.store16 = function(offset, value) {
|
|
var maskedOffset = offset & 0x00FFFFFE;
|
|
var memory = this.memory[offset >>> this.BASE_OFFSET];
|
|
memory.store16(maskedOffset, value);
|
|
memory.invalidatePage(maskedOffset);
|
|
};
|
|
|
|
GameBoyAdvanceMMU.prototype.store32 = function(offset, value) {
|
|
var maskedOffset = offset & 0x00FFFFFC;
|
|
var memory = this.memory[offset >>> this.BASE_OFFSET];
|
|
memory.store32(maskedOffset, value);
|
|
memory.invalidatePage(maskedOffset);
|
|
memory.invalidatePage(maskedOffset + 2);
|
|
};
|
|
|
|
GameBoyAdvanceMMU.prototype.waitPrefetch = function(memory) {
|
|
this.cpu.cycles += 1 + this.waitstatesPrefetch[memory >>> this.BASE_OFFSET];
|
|
};
|
|
|
|
GameBoyAdvanceMMU.prototype.waitPrefetch32 = function(memory) {
|
|
this.cpu.cycles += 1 + this.waitstatesPrefetch32[memory >>> this.BASE_OFFSET];
|
|
};
|
|
|
|
GameBoyAdvanceMMU.prototype.wait = function(memory) {
|
|
this.cpu.cycles += 1 + this.waitstates[memory >>> this.BASE_OFFSET];
|
|
};
|
|
|
|
GameBoyAdvanceMMU.prototype.wait32 = function(memory) {
|
|
this.cpu.cycles += 1 + this.waitstates32[memory >>> this.BASE_OFFSET];
|
|
};
|
|
|
|
GameBoyAdvanceMMU.prototype.waitSeq = function(memory) {
|
|
this.cpu.cycles += 1 + this.waitstatesSeq[memory >>> this.BASE_OFFSET];
|
|
};
|
|
|
|
GameBoyAdvanceMMU.prototype.waitSeq32 = function(memory) {
|
|
this.cpu.cycles += 1 + this.waitstatesSeq32[memory >>> this.BASE_OFFSET];
|
|
};
|
|
|
|
GameBoyAdvanceMMU.prototype.waitMul = function(rs) {
|
|
if ((rs & 0xFFFFFF00 == 0xFFFFFF00) || !(rs & 0xFFFFFF00)) {
|
|
this.cpu.cycles += 1;
|
|
} else if ((rs & 0xFFFF0000 == 0xFFFF0000) || !(rs & 0xFFFF0000)) {
|
|
this.cpu.cycles += 2;
|
|
} else if ((rs & 0xFF000000 == 0xFF000000) || !(rs & 0xFF000000)) {
|
|
this.cpu.cycles += 3;
|
|
} else {
|
|
this.cpu.cycles += 4;
|
|
}
|
|
}
|
|
|
|
GameBoyAdvanceMMU.prototype.waitMulti32 = function(memory, seq) {
|
|
this.cpu.cycles += 1 + this.waitstates32[memory >>> this.BASE_OFFSET];
|
|
this.cpu.cycles += (1 + this.waitstatesSeq32[memory >>> this.BASE_OFFSET]) * (seq - 1);
|
|
};
|
|
|
|
GameBoyAdvanceMMU.prototype.addressToPage = function(region, address) {
|
|
return address >> this.memory[region].ICACHE_PAGE_BITS;
|
|
};
|
|
|
|
GameBoyAdvanceMMU.prototype.accessPage = function(region, pageId) {
|
|
var memory = this.memory[region];
|
|
var page = memory.icache[pageId];
|
|
if (!page || page.invalid) {
|
|
page = {
|
|
thumb: new Array(1 << (memory.ICACHE_PAGE_BITS)),
|
|
arm: new Array(1 << memory.ICACHE_PAGE_BITS - 1),
|
|
invalid: false
|
|
}
|
|
memory.icache[pageId] = page;
|
|
}
|
|
return page;
|
|
};
|
|
|
|
GameBoyAdvanceMMU.prototype.scheduleDma = function(number, info) {
|
|
switch (info.timing) {
|
|
case this.DMA_TIMING_NOW:
|
|
this.serviceDma(number, info);
|
|
break;
|
|
case this.DMA_TIMING_HBLANK:
|
|
// Handled implicitly
|
|
break;
|
|
case this.DMA_TIMING_VBLANK:
|
|
// Handled implicitly
|
|
break;
|
|
case this.DMA_TIMING_CUSTOM:
|
|
switch (number) {
|
|
case 0:
|
|
this.core.WARN('Discarding invalid DMA0 scheduling');
|
|
break;
|
|
case 1:
|
|
case 2:
|
|
this.cpu.irq.audio.scheduleFIFODma(number, info);
|
|
break;
|
|
case 3:
|
|
this.cpu.irq.video.scheduleVCaptureDma(dma, info);
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
GameBoyAdvanceMMU.prototype.runHblankDmas = function() {
|
|
var dma;
|
|
for (var i = 0; i < this.cpu.irq.dma.length; ++i) {
|
|
dma = this.cpu.irq.dma[i];
|
|
if (dma.enable && dma.timing == this.DMA_TIMING_HBLANK) {
|
|
this.serviceDma(i, dma);
|
|
}
|
|
}
|
|
};
|
|
|
|
GameBoyAdvanceMMU.prototype.runVblankDmas = function() {
|
|
var dma;
|
|
for (var i = 0; i < this.cpu.irq.dma.length; ++i) {
|
|
dma = this.cpu.irq.dma[i];
|
|
if (dma.enable && dma.timing == this.DMA_TIMING_VBLANK) {
|
|
this.serviceDma(i, dma);
|
|
}
|
|
}
|
|
};
|
|
|
|
GameBoyAdvanceMMU.prototype.serviceDma = function(number, info) {
|
|
if (!info.enable) {
|
|
// There was a DMA scheduled that got canceled
|
|
return;
|
|
}
|
|
|
|
var width = info.width;
|
|
var sourceOffset = this.DMA_OFFSET[info.srcControl] * width;
|
|
var destOffset = this.DMA_OFFSET[info.dstControl] * width;
|
|
var wordsRemaining = info.nextCount;
|
|
var source = info.nextSource & this.OFFSET_MASK;
|
|
var dest = info.nextDest & this.OFFSET_MASK;
|
|
var sourceRegion = info.nextSource >>> this.BASE_OFFSET;
|
|
var destRegion = info.nextDest >>> this.BASE_OFFSET;
|
|
var sourceBlock = this.memory[sourceRegion];
|
|
var destBlock = this.memory[destRegion];
|
|
var sourceView = null;
|
|
var destView = null;
|
|
var sourceMask = 0xFFFFFFFF;
|
|
var destMask = 0xFFFFFFFF;
|
|
var word;
|
|
|
|
if (destBlock.ICACHE_PAGE_BITS) {
|
|
var endPage = (dest + wordsRemaining * width) >> destBlock.ICACHE_PAGE_BITS;
|
|
for (var i = dest >> destBlock.ICACHE_PAGE_BITS; i <= endPage; ++i) {
|
|
destBlock.invalidatePage(i << destBlock.ICACHE_PAGE_BITS);
|
|
}
|
|
}
|
|
|
|
if (destRegion == this.REGION_WORKING_RAM || destRegion == this.REGION_WORKING_IRAM) {
|
|
destView = destBlock.view;
|
|
destMask = destBlock.mask;
|
|
}
|
|
|
|
if (sourceRegion == this.REGION_WORKING_RAM || sourceRegion == this.REGION_WORKING_IRAM || sourceRegion == this.REGION_CART0 || sourceRegion == this.REGION_CART1) {
|
|
sourceView = sourceBlock.view;
|
|
sourceMask = sourceBlock.mask;
|
|
}
|
|
|
|
if (sourceBlock && destBlock) {
|
|
if (sourceView && destView) {
|
|
if (width == 4) {
|
|
source &= 0xFFFFFFFC;
|
|
dest &= 0xFFFFFFFC;
|
|
while (wordsRemaining--) {
|
|
word = sourceView.getInt32(source & sourceMask);
|
|
destView.setInt32(dest & destMask, word);
|
|
source += sourceOffset;
|
|
dest += destOffset;
|
|
}
|
|
} else {
|
|
while (wordsRemaining--) {
|
|
word = sourceView.getUint16(source & sourceMask);
|
|
destView.setUint16(dest & destMask, word);
|
|
source += sourceOffset;
|
|
dest += destOffset;
|
|
}
|
|
}
|
|
} else if (sourceView) {
|
|
if (width == 4) {
|
|
source &= 0xFFFFFFFC;
|
|
dest &= 0xFFFFFFFC;
|
|
while (wordsRemaining--) {
|
|
word = sourceView.getInt32(source & sourceMask, true);
|
|
destBlock.store32(dest, word);
|
|
source += sourceOffset;
|
|
dest += destOffset;
|
|
}
|
|
} else {
|
|
while (wordsRemaining--) {
|
|
word = sourceView.getUint16(source & sourceMask, true);
|
|
destBlock.store16(dest, word);
|
|
source += sourceOffset;
|
|
dest += destOffset;
|
|
}
|
|
}
|
|
} else {
|
|
if (width == 4) {
|
|
source &= 0xFFFFFFFC;
|
|
dest &= 0xFFFFFFFC;
|
|
while (wordsRemaining--) {
|
|
word = sourceBlock.load32(source);
|
|
destBlock.store32(dest, word);
|
|
source += sourceOffset;
|
|
dest += destOffset;
|
|
}
|
|
} else {
|
|
while (wordsRemaining--) {
|
|
word = sourceBlock.loadU16(source);
|
|
destBlock.store16(dest, word);
|
|
source += sourceOffset;
|
|
dest += destOffset;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
this.core.WARN('Invalid DMA');
|
|
}
|
|
|
|
if (info.doIrq) {
|
|
info.nextIRQ = this.cpu.cycles + 2;
|
|
info.nextIRQ += (width == 4 ? this.waitstates32[sourceRegion] + this.waitstates32[destRegion]
|
|
: this.waitstates[sourceRegion] + this.waitstates[destRegion]);
|
|
info.nextIRQ += (info.count - 1) * (width == 4 ? this.waitstatesSeq32[sourceRegion] + this.waitstatesSeq32[destRegion]
|
|
: this.waitstatesSeq[sourceRegion] + this.waitstatesSeq[destRegion]);
|
|
}
|
|
|
|
info.nextSource = source | (sourceRegion << this.BASE_OFFSET);
|
|
info.nextDest = dest | (destRegion << this.BASE_OFFSET);
|
|
info.nextCount = wordsRemaining;
|
|
|
|
if (!info.repeat) {
|
|
info.enable = false;
|
|
|
|
// Clear the enable bit in memory
|
|
var io = this.memory[this.REGION_IO];
|
|
io.registers[this.DMA_REGISTER[number]] &= 0x7FE0;
|
|
} else {
|
|
info.nextCount = info.count;
|
|
if (info.dstControl == this.DMA_INCREMENT_RELOAD) {
|
|
info.nextDest = info.dest;
|
|
}
|
|
this.scheduleDma(number, info);
|
|
}
|
|
};
|
|
|
|
GameBoyAdvanceMMU.prototype.adjustTimings = function(word) {
|
|
var sram = word & 0x0003;
|
|
var ws0 = (word & 0x000C) >> 2;
|
|
var ws0seq = (word & 0x0010) >> 4;
|
|
var ws1 = (word & 0x0060) >> 5;
|
|
var ws1seq = (word & 0x0080) >> 7;
|
|
var ws2 = (word & 0x0300) >> 8;
|
|
var ws2seq = (word & 0x0400) >> 10;
|
|
var prefetch = word & 0x4000;
|
|
|
|
this.waitstates[this.REGION_CART_SRAM] = this.ROM_WS[sram];
|
|
this.waitstatesSeq[this.REGION_CART_SRAM] = this.ROM_WS[sram];
|
|
this.waitstates32[this.REGION_CART_SRAM] = this.ROM_WS[sram];
|
|
this.waitstatesSeq32[this.REGION_CART_SRAM] = this.ROM_WS[sram];
|
|
|
|
this.waitstates[this.REGION_CART0] = this.waitstates[this.REGION_CART0 + 1] = this.ROM_WS[ws0];
|
|
this.waitstates[this.REGION_CART1] = this.waitstates[this.REGION_CART1 + 1] = this.ROM_WS[ws1];
|
|
this.waitstates[this.REGION_CART2] = this.waitstates[this.REGION_CART2 + 1] = this.ROM_WS[ws2];
|
|
|
|
this.waitstatesSeq[this.REGION_CART0] = this.waitstatesSeq[this.REGION_CART0 + 1] = this.ROM_WS_SEQ[0][ws0seq];
|
|
this.waitstatesSeq[this.REGION_CART1] = this.waitstatesSeq[this.REGION_CART1 + 1] = this.ROM_WS_SEQ[1][ws1seq];
|
|
this.waitstatesSeq[this.REGION_CART2] = this.waitstatesSeq[this.REGION_CART2 + 1] = this.ROM_WS_SEQ[2][ws2seq];
|
|
|
|
this.waitstates32[this.REGION_CART0] = this.waitstates32[this.REGION_CART0 + 1] = this.waitstates[this.REGION_CART0] + 1 + this.waitstatesSeq[this.REGION_CART0];
|
|
this.waitstates32[this.REGION_CART1] = this.waitstates32[this.REGION_CART1 + 1] = this.waitstates[this.REGION_CART1] + 1 + this.waitstatesSeq[this.REGION_CART1];
|
|
this.waitstates32[this.REGION_CART2] = this.waitstates32[this.REGION_CART2 + 1] = this.waitstates[this.REGION_CART2] + 1 + this.waitstatesSeq[this.REGION_CART2];
|
|
|
|
this.waitstatesSeq32[this.REGION_CART0] = this.waitstatesSeq32[this.REGION_CART0 + 1] = 2 * this.waitstatesSeq[this.REGION_CART0] + 1;
|
|
this.waitstatesSeq32[this.REGION_CART1] = this.waitstatesSeq32[this.REGION_CART1 + 1] = 2 * this.waitstatesSeq[this.REGION_CART1] + 1;
|
|
this.waitstatesSeq32[this.REGION_CART2] = this.waitstatesSeq32[this.REGION_CART2 + 1] = 2 * this.waitstatesSeq[this.REGION_CART2] + 1;
|
|
|
|
if (prefetch) {
|
|
this.waitstatesPrefetch[this.REGION_CART0] = this.waitstatesPrefetch[this.REGION_CART0 + 1] = 0;
|
|
this.waitstatesPrefetch[this.REGION_CART1] = this.waitstatesPrefetch[this.REGION_CART1 + 1] = 0;
|
|
this.waitstatesPrefetch[this.REGION_CART2] = this.waitstatesPrefetch[this.REGION_CART2 + 1] = 0;
|
|
|
|
this.waitstatesPrefetch32[this.REGION_CART0] = this.waitstatesPrefetch32[this.REGION_CART0 + 1] = 0;
|
|
this.waitstatesPrefetch32[this.REGION_CART1] = this.waitstatesPrefetch32[this.REGION_CART1 + 1] = 0;
|
|
this.waitstatesPrefetch32[this.REGION_CART2] = this.waitstatesPrefetch32[this.REGION_CART2 + 1] = 0;
|
|
} else {
|
|
this.waitstatesPrefetch[this.REGION_CART0] = this.waitstatesPrefetch[this.REGION_CART0 + 1] = this.waitstatesSeq[this.REGION_CART0];
|
|
this.waitstatesPrefetch[this.REGION_CART1] = this.waitstatesPrefetch[this.REGION_CART1 + 1] = this.waitstatesSeq[this.REGION_CART1];
|
|
this.waitstatesPrefetch[this.REGION_CART2] = this.waitstatesPrefetch[this.REGION_CART2 + 1] = this.waitstatesSeq[this.REGION_CART2];
|
|
|
|
this.waitstatesPrefetch32[this.REGION_CART0] = this.waitstatesPrefetch32[this.REGION_CART0 + 1] = this.waitstatesSeq32[this.REGION_CART0];
|
|
this.waitstatesPrefetch32[this.REGION_CART1] = this.waitstatesPrefetch32[this.REGION_CART1 + 1] = this.waitstatesSeq32[this.REGION_CART1];
|
|
this.waitstatesPrefetch32[this.REGION_CART2] = this.waitstatesPrefetch32[this.REGION_CART2 + 1] = this.waitstatesSeq32[this.REGION_CART2];
|
|
}
|
|
};
|
|
|
|
GameBoyAdvanceMMU.prototype.saveNeedsFlush = function() {
|
|
return this.save.writePending;
|
|
};
|
|
|
|
GameBoyAdvanceMMU.prototype.flushSave = function() {
|
|
this.save.writePending = false;
|
|
};
|
|
|
|
GameBoyAdvanceMMU.prototype.allocGPIO = function(rom) {
|
|
return new GameBoyAdvanceGPIO(this.core, rom);
|
|
};
|