@@ -1,7 +1,9 @@ | |||
module.exports = Object.freeze({ | |||
Memory: require("./memory.js"), | |||
ROM: require("./rom.js"), | |||
RAM: require("./ram.js"), | |||
Shadow: require("./shadow.js"), | |||
MMC: require("./mmc.js"), | |||
IMem: require("./imem.js") | |||
}); |
@@ -1,219 +0,0 @@ | |||
const IO = require('../io.js'); | |||
const IMem = require('./imem'); | |||
class ROM extends IMem{ | |||
constructor(pages){ | |||
if (pages < 1) | |||
throw new RangeError("Memory requires positive page values."); | |||
super(); | |||
this.__addr = 0; | |||
this.__map = new Uint8Array(pages * 256); | |||
// Allow writable events because RAM will inherit ROM and use the same | |||
// __io object. | |||
this.__io = new IO([], []); | |||
} | |||
get pages(){return this.__map.length / 256;} | |||
get size(){return this.__map.length;} | |||
get address(){return this.__addr;} | |||
set address(a){ | |||
this.__addr = Math.min(this.__map.length - 1, Math.max(0, a)); | |||
} | |||
get byte(){ | |||
this.__io.triggerRead(this.__addr); | |||
return this.__map[this.__addr]; | |||
} | |||
set byte(b){} | |||
onAddressRead(addr, fn){ | |||
if (addr < 0 || addr >= this.size) | |||
throw new RangeError("Memory address is out of bounds."); | |||
this.__io.onRead(addr, fn); | |||
return this; | |||
} | |||
peek(a){ | |||
a = Math.min(this.__map.length - 1, Math.max(0, a)); | |||
return this.__map[a]; | |||
} | |||
load(address, data){ | |||
let dc = data; | |||
if (address < 0 || address >= this.__map.length) | |||
throw new RangeError("Memory address out of range."); | |||
if (this.__map.length - address < dc.length) | |||
dc = dc.slice(0, this.__map.length - address); | |||
this.__map.set(dc, address); | |||
return dc.length; | |||
} | |||
clearPage(page){ | |||
let addr = (page << 8) & 0xFF00; | |||
if (addr < 0 || addr >= this.__map.length) | |||
throw new RangeError("Memory address out of range."); | |||
this.__map.fill(0, addr, addr + 256); | |||
return this; | |||
} | |||
clear(){ | |||
this.__map.fill(0); | |||
return this; | |||
} | |||
} | |||
class RAM extends ROM { | |||
constructor(pages){ | |||
super(pages); | |||
} | |||
get pages(){return this.__map.length/256;} | |||
get size(){return this.__map.length;} | |||
get writable(){return true;} | |||
get address(){return this.__addr;} | |||
set address(a){ | |||
this.__addr = Math.min(this.__map.length - 1, Math.max(0, a)); | |||
} | |||
get byte(){ | |||
this.__io.triggerRead(this.__addr); | |||
return this.__map[this.__addr]; | |||
} | |||
set byte(b){ | |||
this.__map[this.__addr] = b & 0xFF; | |||
this.__io.triggerWrite(this.__addr, b & 0xFF); | |||
} | |||
onAddressWrite(addr, fn){ | |||
if (addr < 0 || addr >= this.size) | |||
throw new RangeError("Memory address is out of bounds."); | |||
this.__io.onWrite(addr, fn); | |||
return this; | |||
} | |||
poke(a, b){ | |||
a = Math.min(this.__map.length - 1, Math.max(0, a)); | |||
this.__map[a] = b & 0xFF; | |||
return this; | |||
} | |||
} | |||
class Shadow extends IMem { | |||
constructor(pages, rbc){ | |||
if (rbc > (pages * 256)) | |||
throw RangeError("Register Byte Count exceeds available pages defined."); | |||
super(); | |||
this.__map = new Uint8Array(rbc); | |||
this.__addr = 0; | |||
this.__size = pages * 256; | |||
this.__io = new IO([], []); | |||
} | |||
get pages(){return this.__size/256;} | |||
get size(){return this.__size;} | |||
get registers(){return this.__map.length;} | |||
get writable(){return true;} | |||
get address(){return this.__addr;} | |||
set address(a){ | |||
if (this.__map) | |||
this.__addr = Math.min(this.__size - 1, Math.max(0, a)); | |||
} | |||
get byte(){ | |||
let a = this.__addr % this.__map.length; | |||
this.__io.triggerRead(this.__addr); | |||
return this.__map[a]; | |||
} | |||
set byte(b){ | |||
if (this.__addr >= 0 && this.__addr < this.__map.length){ | |||
this.__map[this.__addr] = b & 0xFF; | |||
this.__io.triggerWrite(this.__addr, b & 0xFF); | |||
} | |||
} | |||
onAddressRead(addr, fn){ | |||
if (addr < 0 || addr >= this.size) | |||
throw new RangeError("Memory address is out of bounds."); | |||
this.__io.onRead(addr, fn); | |||
return this; | |||
} | |||
onAddressWrite(addr, fn){ | |||
if (addr < 0 || addr >= this.size) | |||
throw new RangeError("Memory address is out of bounds."); | |||
let a = this.__addr % this.__map.length; | |||
this.__io.onWrite(a, fn); | |||
return this; | |||
} | |||
peek(a){ | |||
a = Math.min(this.__size - 1, Math.max(0, a)) % this.__map.length; | |||
return this.__map[a]; | |||
} | |||
poke(a, b){ | |||
if (a >= 0 && a < this.__map.length) | |||
this.__map[a] = b & 0xFF; | |||
return this; | |||
} | |||
load(addr, data){ | |||
let dc = data; | |||
if (addr < 0 || addr >= this.__size) | |||
throw new RangeError("Memory address out of range."); | |||
// We simply quit here because, while it's legal to try to write, | |||
// there's actually no memory at this point. We're playing pretend :p | |||
if (addr >= this.__map.length){return 0;} | |||
if (this.__map.length - addr < dc.length) | |||
dc = dc.slice(0, this.__map.length - addr); | |||
this.__map.set(dc, addr); | |||
return dc.length; | |||
} | |||
clearPage(page){ | |||
let addr = (page << 8) & 0xFF00; | |||
if (addr < 0 || addr >= this.__size) | |||
throw new RangeError("Memory address out of range."); | |||
if (addr < this.__map.length){ | |||
let end = (addr + 256 <= this.__map.length) ? 256 : this.__map.length - addr; | |||
this.__map.fill(0, addr, end); | |||
} | |||
return this; | |||
} | |||
clear(){ | |||
if (this.__map) | |||
this.__map.fill(0); | |||
return this; | |||
} | |||
} | |||
module.exports = Object.freeze({ | |||
RAM: RAM, | |||
ROM: ROM, | |||
Shadow: Shadow | |||
}); | |||
@@ -0,0 +1,51 @@ | |||
const IO = require('../io.js'); | |||
const IMem = require('./imem.js'); | |||
const ROM = require('./rom.js'); | |||
class RAM extends ROM { | |||
constructor(pages){ | |||
super(pages); | |||
} | |||
get pages(){return this.__map.length/256;} | |||
get size(){return this.__map.length;} | |||
get writable(){return true;} | |||
get address(){return this.__addr;} | |||
set address(a){ | |||
this.__addr = Math.min(this.__map.length - 1, Math.max(0, a)); | |||
} | |||
get byte(){ | |||
this.__io.triggerRead(this.__addr); | |||
return this.__map[this.__addr]; | |||
} | |||
set byte(b){ | |||
this.__map[this.__addr] = b & 0xFF; | |||
this.__io.triggerWrite(this.__addr, b & 0xFF); | |||
} | |||
onAddressWrite(addr, fn){ | |||
if (addr < 0 || addr >= this.size) | |||
throw new RangeError("Memory address is out of bounds."); | |||
this.__io.onWrite(addr, fn); | |||
return this; | |||
} | |||
poke(a, b){ | |||
a = Math.min(this.__map.length - 1, Math.max(0, a)); | |||
this.__map[a] = b & 0xFF; | |||
return this; | |||
} | |||
} | |||
module.exports = RAM; | |||
@@ -0,0 +1,80 @@ | |||
const IO = require('../io.js'); | |||
const IMem = require('./imem.js'); | |||
class ROM extends IMem{ | |||
constructor(pages){ | |||
if (pages < 1) | |||
throw new RangeError("Memory requires positive page values."); | |||
super(); | |||
this.__addr = 0; | |||
this.__map = new Uint8Array(pages * 256); | |||
// Allow writable events because RAM will inherit ROM and use the same | |||
// __io object. | |||
this.__io = new IO([], []); | |||
} | |||
get pages(){return this.__map.length / 256;} | |||
get size(){return this.__map.length;} | |||
get address(){return this.__addr;} | |||
set address(a){ | |||
this.__addr = Math.min(this.__map.length - 1, Math.max(0, a)); | |||
} | |||
get byte(){ | |||
this.__io.triggerRead(this.__addr); | |||
return this.__map[this.__addr]; | |||
} | |||
set byte(b){} | |||
onAddressRead(addr, fn){ | |||
if (addr < 0 || addr >= this.size) | |||
throw new RangeError("Memory address is out of bounds."); | |||
this.__io.onRead(addr, fn); | |||
return this; | |||
} | |||
peek(a){ | |||
a = Math.min(this.__map.length - 1, Math.max(0, a)); | |||
return this.__map[a]; | |||
} | |||
load(address, data){ | |||
let dc = data; | |||
if (address < 0 || address >= this.__map.length) | |||
throw new RangeError("Memory address out of range."); | |||
if (this.__map.length - address < dc.length) | |||
dc = dc.slice(0, this.__map.length - address); | |||
this.__map.set(dc, address); | |||
return dc.length; | |||
} | |||
clearPage(page){ | |||
let addr = (page << 8) & 0xFF00; | |||
if (addr < 0 || addr >= this.__map.length) | |||
throw new RangeError("Memory address out of range."); | |||
this.__map.fill(0, addr, addr + 256); | |||
return this; | |||
} | |||
clear(){ | |||
this.__map.fill(0); | |||
return this; | |||
} | |||
} | |||
module.exports = ROM; | |||
@@ -0,0 +1,105 @@ | |||
const IO = require('../io.js'); | |||
const IMem = require('./imem.js'); | |||
class Shadow extends IMem { | |||
constructor(pages, rbc){ | |||
if (rbc > (pages * 256)) | |||
throw RangeError("Register Byte Count exceeds available pages defined."); | |||
super(); | |||
this.__map = new Uint8Array(rbc); | |||
this.__addr = 0; | |||
this.__size = pages * 256; | |||
this.__io = new IO([], []); | |||
} | |||
get pages(){return this.__size/256;} | |||
get size(){return this.__size;} | |||
get registers(){return this.__map.length;} | |||
get writable(){return true;} | |||
get address(){return this.__addr;} | |||
set address(a){ | |||
if (this.__map) | |||
this.__addr = Math.min(this.__size - 1, Math.max(0, a)); | |||
} | |||
get byte(){ | |||
let a = this.__addr % this.__map.length; | |||
this.__io.triggerRead(this.__addr); | |||
return this.__map[a]; | |||
} | |||
set byte(b){ | |||
if (this.__addr >= 0 && this.__addr < this.__map.length){ | |||
this.__map[this.__addr] = b & 0xFF; | |||
this.__io.triggerWrite(this.__addr, b & 0xFF); | |||
} | |||
} | |||
onAddressRead(addr, fn){ | |||
if (addr < 0 || addr >= this.size) | |||
throw new RangeError("Memory address is out of bounds."); | |||
this.__io.onRead(addr, fn); | |||
return this; | |||
} | |||
onAddressWrite(addr, fn){ | |||
if (addr < 0 || addr >= this.size) | |||
throw new RangeError("Memory address is out of bounds."); | |||
let a = this.__addr % this.__map.length; | |||
this.__io.onWrite(a, fn); | |||
return this; | |||
} | |||
peek(a){ | |||
a = Math.min(this.__size - 1, Math.max(0, a)) % this.__map.length; | |||
return this.__map[a]; | |||
} | |||
poke(a, b){ | |||
if (a >= 0 && a < this.__map.length) | |||
this.__map[a] = b & 0xFF; | |||
return this; | |||
} | |||
load(addr, data){ | |||
let dc = data; | |||
if (addr < 0 || addr >= this.__size) | |||
throw new RangeError("Memory address out of range."); | |||
// We simply quit here because, while it's legal to try to write, | |||
// there's actually no memory at this point. We're playing pretend :p | |||
if (addr >= this.__map.length){return 0;} | |||
if (this.__map.length - addr < dc.length) | |||
dc = dc.slice(0, this.__map.length - addr); | |||
this.__map.set(dc, addr); | |||
return dc.length; | |||
} | |||
clearPage(page){ | |||
let addr = (page << 8) & 0xFF00; | |||
if (addr < 0 || addr >= this.__size) | |||
throw new RangeError("Memory address out of range."); | |||
if (addr < this.__map.length){ | |||
let end = (addr + 256 <= this.__map.length) ? 256 : this.__map.length - addr; | |||
this.__map.fill(0, addr, end); | |||
} | |||
return this; | |||
} | |||
clear(){ | |||
if (this.__map) | |||
this.__map.fill(0); | |||
return this; | |||
} | |||
} | |||
module.exports = Shadow; | |||
@@ -7,7 +7,7 @@ describe("Testing MOS6502 CPU...", function(){ | |||
var asm = new MOS6502.Assembler(); | |||
var cpu = new MOS6502.CPU(); | |||
var tick = cpu.clk(); | |||
cpu.memory = new Mem.Memory.RAM(256); | |||
cpu.memory = new Mem.RAM(256); | |||
cpu.memory.load(0xFFFC, [0x00, 0x00]); | |||
it("Resetting (IRQ Disabled flag must be on", function(){ |
@@ -5,8 +5,8 @@ const Mem = require('../src/memory'); | |||
describe("Testing Memory Module", function(){ | |||
describe("Core Memory Classes...", function(){ | |||
describe("ROM Class", function(){ | |||
var m1 = new Mem.Memory.ROM(1); | |||
var m2 = new Mem.Memory.ROM(4); | |||
var m1 = new Mem.ROM(1); | |||
var m2 = new Mem.ROM(4); | |||
it("Bytes match 256 byte page sizes", function(){ | |||
expect(m1.size).to.equal(256); | |||
@@ -108,8 +108,8 @@ describe("Testing Memory Module", function(){ | |||
}); | |||
describe("RAM Class", function(){ | |||
var m1 = new Mem.Memory.RAM(1); | |||
var m2 = new Mem.Memory.RAM(4); | |||
var m1 = new Mem.RAM(1); | |||
var m2 = new Mem.RAM(4); | |||
it("Bytes match 256 byte page sizes", function(){ | |||
expect(m1.size).to.equal(256); | |||
@@ -251,7 +251,7 @@ describe("Testing Memory Module", function(){ | |||
}); | |||
describe("Shadow Class", function(){ | |||
var sm = new Mem.Memory.Shadow(1, 4); | |||
var sm = new Mem.Shadow(1, 4); | |||
it("Bytes Match 256 Size", function(){ | |||
expect(sm.size).to.equal(256); | |||
@@ -364,12 +364,12 @@ describe("Testing Memory Module", function(){ | |||
var mmc = new Mem.MMC(); | |||
var rcb = sinon.fake(); | |||
var wcb = sinon.fake(); | |||
let m1 = new Mem.Memory.RAM(1); | |||
let m1 = new Mem.RAM(1); | |||
m1.onAddressRead(0x31, rcb); | |||
m1.onAddressWrite(0x31, wcb); | |||
mmc.connectMemory(m1); | |||
mmc.connectMemory(new Mem.Memory.RAM(2)); | |||
mmc.connectMemory(new Mem.Memory.RAM(1)); | |||
mmc.connectMemory(new Mem.RAM(2)); | |||
mmc.connectMemory(new Mem.RAM(1)); | |||
it("Reports 4 pages", function(){ | |||
expect(mmc.pages).to.equal(4); | |||
@@ -402,7 +402,7 @@ describe("Testing Memory Module", function(){ | |||
}); | |||
it("Adding Switchable Banks", function(){ | |||
let b2 = new Mem.Memory.ROM(1); | |||
let b2 = new Mem.ROM(1); | |||
expect(function(){ | |||
mmc.connectMemory(b2, 0x0100); | |||
}).to.throw("Memory modules assigned to the same bank must be the same byte size."); | |||
@@ -417,7 +417,7 @@ describe("Testing Memory Module", function(){ | |||
mmc.switchBank(0x00); | |||
expect(mmc.read(0x01)).to.equal(0x02); | |||
mmc.connectMemory(new Mem.Memory.ROM(2), 0x0100); | |||
mmc.connectMemory(new Mem.ROM(2), 0x0100); | |||
expect(mmc.read(0x0115)).to.equal(0x15); | |||
mmc.switchBank(0x11); | |||
expect(mmc.read(0x0115)).to.equal(0x00); |