| module.exports = Object.freeze({ | module.exports = Object.freeze({ | ||||
| Memory: require("./memory.js"), | |||||
| ROM: require("./rom.js"), | |||||
| RAM: require("./ram.js"), | |||||
| Shadow: require("./shadow.js"), | |||||
| MMC: require("./mmc.js"), | MMC: require("./mmc.js"), | ||||
| IMem: require("./imem.js") | IMem: require("./imem.js") | ||||
| }); | }); |
| 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 | |||||
| }); | |||||
| 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; | |||||
| 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; | |||||
| 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; | |||||
| var asm = new MOS6502.Assembler(); | var asm = new MOS6502.Assembler(); | ||||
| var cpu = new MOS6502.CPU(); | var cpu = new MOS6502.CPU(); | ||||
| var tick = cpu.clk(); | var tick = cpu.clk(); | ||||
| cpu.memory = new Mem.Memory.RAM(256); | |||||
| cpu.memory = new Mem.RAM(256); | |||||
| cpu.memory.load(0xFFFC, [0x00, 0x00]); | cpu.memory.load(0xFFFC, [0x00, 0x00]); | ||||
| it("Resetting (IRQ Disabled flag must be on", function(){ | it("Resetting (IRQ Disabled flag must be on", function(){ |
| describe("Testing Memory Module", function(){ | describe("Testing Memory Module", function(){ | ||||
| describe("Core Memory Classes...", function(){ | describe("Core Memory Classes...", function(){ | ||||
| describe("ROM Class", 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(){ | it("Bytes match 256 byte page sizes", function(){ | ||||
| expect(m1.size).to.equal(256); | expect(m1.size).to.equal(256); | ||||
| }); | }); | ||||
| describe("RAM Class", 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(){ | it("Bytes match 256 byte page sizes", function(){ | ||||
| expect(m1.size).to.equal(256); | expect(m1.size).to.equal(256); | ||||
| }); | }); | ||||
| describe("Shadow Class", 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(){ | it("Bytes Match 256 Size", function(){ | ||||
| expect(sm.size).to.equal(256); | expect(sm.size).to.equal(256); | ||||
| var mmc = new Mem.MMC(); | var mmc = new Mem.MMC(); | ||||
| var rcb = sinon.fake(); | var rcb = sinon.fake(); | ||||
| var wcb = sinon.fake(); | var wcb = sinon.fake(); | ||||
| let m1 = new Mem.Memory.RAM(1); | |||||
| let m1 = new Mem.RAM(1); | |||||
| m1.onAddressRead(0x31, rcb); | m1.onAddressRead(0x31, rcb); | ||||
| m1.onAddressWrite(0x31, wcb); | m1.onAddressWrite(0x31, wcb); | ||||
| mmc.connectMemory(m1); | 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(){ | it("Reports 4 pages", function(){ | ||||
| expect(mmc.pages).to.equal(4); | expect(mmc.pages).to.equal(4); | ||||
| }); | }); | ||||
| it("Adding Switchable Banks", function(){ | it("Adding Switchable Banks", function(){ | ||||
| let b2 = new Mem.Memory.ROM(1); | |||||
| let b2 = new Mem.ROM(1); | |||||
| expect(function(){ | expect(function(){ | ||||
| mmc.connectMemory(b2, 0x0100); | mmc.connectMemory(b2, 0x0100); | ||||
| }).to.throw("Memory modules assigned to the same bank must be the same byte size."); | }).to.throw("Memory modules assigned to the same bank must be the same byte size."); | ||||
| mmc.switchBank(0x00); | mmc.switchBank(0x00); | ||||
| expect(mmc.read(0x01)).to.equal(0x02); | 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); | expect(mmc.read(0x0115)).to.equal(0x15); | ||||
| mmc.switchBank(0x11); | mmc.switchBank(0x11); | ||||
| expect(mmc.read(0x0115)).to.equal(0x00); | expect(mmc.read(0x0115)).to.equal(0x00); |