| * Emulate a basic 6502 (MOS) chip. | * Emulate a basic 6502 (MOS) chip. | ||||
| */ | */ | ||||
| const BITM = require('../../utils/bitman.js'); | const BITM = require('../../utils/bitman.js'); | ||||
| var Memory = require('../../common/memory.js'); | |||||
| var IMem = require('../../memory').IMem; | |||||
| // mode = 0 - Immediate | // mode = 0 - Immediate | ||||
| // mode = 1 - Zero Page | // mode = 1 - Zero Page | ||||
| // Set and Get Memory property. | // Set and Get Memory property. | ||||
| get memory(){return this.__mem;} | get memory(){return this.__mem;} | ||||
| set memory(m){ | set memory(m){ | ||||
| if (!(m instanceof Memory)) | |||||
| if (!(m instanceof IMem)) | |||||
| throw new ValueError("Expected Memory instance object."); | throw new ValueError("Expected Memory instance object."); | ||||
| this.__mem = m; | this.__mem = m; | ||||
| } | } |
| var Memory = require('./memory'); | |||||
| class Bank extends Memory{ | |||||
| constructor(size, ro){ | |||||
| super(); | |||||
| this.__ro = (ro === true); | |||||
| this.__map = null; | |||||
| this.__addr = 0; | |||||
| if (size > 0){ | |||||
| this.__map = new Uint8Array(size); | |||||
| } | |||||
| this.__listeners = {}; | |||||
| } | |||||
| get size(){return (this.__map) ? this.__map.length : 0;} | |||||
| get address(){return this.__addr;} | |||||
| set address(a){ | |||||
| if (this.__map) | |||||
| this.__addr = Math.min(this.__map.length, Math.max(0, a)); | |||||
| } | |||||
| get byte(){return (this.__map) ? this.__map[this.__addr] : -1;} | |||||
| set byte(b){ | |||||
| if (!this.__ro && this.__map){ | |||||
| this.__map[this.__addr] = b; | |||||
| if (this.__addr in this.__listeners) | |||||
| this.__listeners[this.__addr].forEach((fn)=>{fn(b);}); | |||||
| } | |||||
| } | |||||
| onAddressWrite(addr, fn){ | |||||
| if (addr < 0 || addr >= this.size) | |||||
| throw new RangeError("Memory address is out of bounds."); | |||||
| if (typeof(fn) !== 'function') | |||||
| throw new TypeErrpr("Expected a callback function."); | |||||
| if (!(addr in this.__listeners)) | |||||
| this.__listeners[addr] = []; | |||||
| // WARNING: Not testing to see if using the same function more than once. | |||||
| this.__listeners[addr].push(fn); | |||||
| } | |||||
| load(address, data){ | |||||
| if (this.__map){ | |||||
| 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){ | |||||
| if (this.__map){ | |||||
| 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); | |||||
| } | |||||
| } | |||||
| clear(){ | |||||
| if (this.__map) | |||||
| this.__map.fill(0); | |||||
| } | |||||
| } | |||||
| module.exports = Bank; |
| class Memory{ | |||||
| constructor(){} | |||||
| get size(){return 0;} | |||||
| get address(){return 0;} | |||||
| set address(a){} | |||||
| get byte(){return -1;} | |||||
| set byte(b){} | |||||
| } | |||||
| module.exports = Memory; |
| class IMem{ | |||||
| constructor(){} | |||||
| get size(){return 0;} | |||||
| get writable(){return false;} | |||||
| get address(){return 0;} | |||||
| set address(a){} | |||||
| get byte(){return -1;} | |||||
| set byte(b){} | |||||
| read(a){ | |||||
| this.address = a; | |||||
| return this.byte; | |||||
| } | |||||
| write(a, b){ | |||||
| if (this.writable){ | |||||
| this.address = a; | |||||
| this.byte = b & 0xFF; | |||||
| } | |||||
| return this; | |||||
| } | |||||
| load(address, data){ | |||||
| return this; | |||||
| } | |||||
| clearPage(page){ | |||||
| return this; | |||||
| } | |||||
| clear(){ | |||||
| return this; | |||||
| } | |||||
| } | |||||
| module.exports = IMem; |
| module.exports = Object.freeze({ | |||||
| Memory: require("./memory.js"), | |||||
| MMC: require("./mmc.js"), | |||||
| IMem: require("./imem.js") | |||||
| }); | |||||
| var IMem = require('./imem'); | |||||
| class Listener{ | |||||
| constructor(){ | |||||
| this.__listeners = {}; | |||||
| } | |||||
| on(n, fn){ | |||||
| if (typeof(fn) !== 'function') | |||||
| throw new TypeErrpr("Expected a callback function."); | |||||
| if (!(n in this.__listeners)) | |||||
| this.__listeners[n] = []; | |||||
| this.__listeners[n].push(fn); | |||||
| } | |||||
| trigger(n, data){ | |||||
| if (n in this.__listeners){ | |||||
| this.__listeners[n].forEach((l)=>{ | |||||
| l(data); | |||||
| }); | |||||
| } | |||||
| } | |||||
| } | |||||
| 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 * 1024); | |||||
| this.__rlisteners = new Listener(); | |||||
| } | |||||
| get size(){return this.__map.length;} | |||||
| get address(){return this.__addr;} | |||||
| set address(a){ | |||||
| this.__addr = Math.min(this.__map.length, Math.max(0, a)); | |||||
| } | |||||
| get byte(){ | |||||
| this.__rlisteners.trigger(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.__rlisteners.on(addr, fn); | |||||
| return this; | |||||
| } | |||||
| 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); | |||||
| this.__wlisteners = new Listener(); | |||||
| } | |||||
| 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, Math.max(0, a)); | |||||
| } | |||||
| get byte(){ | |||||
| this.__rlisteners.trigger(this.__addr); | |||||
| return this.__map[this.__addr]; | |||||
| } | |||||
| set byte(b){ | |||||
| this.__map[this.__addr] = b & 0xFF; | |||||
| this.__wlisteners.trigger(this.__addr, b & 0xFF); | |||||
| } | |||||
| onAddressWrite(addr, fn){ | |||||
| if (addr < 0 || addr >= this.size) | |||||
| throw new RangeError("Memory address is out of bounds."); | |||||
| this.__elisteners.on(addr, fn); | |||||
| return this; | |||||
| } | |||||
| } | |||||
| class Shadow extends IMem { | |||||
| constructor(pages, rbc){ | |||||
| if (rbc > (pages * 1024)) | |||||
| throw RangeError("Register Byte Count exceeds available pages defined."); | |||||
| super(); | |||||
| this.__map = new Uint8Array(rbc); | |||||
| this.__addr = 0; | |||||
| this.__size = pages * 1024; | |||||
| this.__rlisteners = new Listener(); | |||||
| this.__wlisteners = new Listener(); | |||||
| } | |||||
| get size(){return this.__size;} | |||||
| get writable(){return true;} | |||||
| get address(){return this.__addr;} | |||||
| set address(a){ | |||||
| if (this.__map) | |||||
| this.__addr = Math.min(this.__size, Math.max(0, a)); | |||||
| } | |||||
| get byte(){ | |||||
| let a = this.__addr % this.__map.length; | |||||
| this.__rlisteners.trigger(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.__wlisteners.trigger(this.__addr, b & 0xFF); | |||||
| } | |||||
| } | |||||
| onAddressRead(addr, fn){ | |||||
| if (addr < 0 || addr >= this.size) | |||||
| throw new RangeError("Memory address is out of bounds."); | |||||
| this.__rlisteners.on(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.__wlisteners.on(a, fn); | |||||
| 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 | |||||
| }); | |||||
| var Memory = require('src/memory'); | |||||
| var IMem = require('./imem.js'); | |||||
| class Switch{ | class Switch{ | ||||
| constructor(mem){ | constructor(mem){ | ||||
| class MMC extends Memory{ | |||||
| class MMC extends IMem{ | |||||
| constructor(){ | constructor(){ | ||||
| super(); | super(); | ||||
| this.__switches = []; | this.__switches = []; | ||||
| } | } | ||||
| module.exports = MMC; | module.exports = MMC; | ||||
| const expect = require('chai').expect; | const expect = require('chai').expect; | ||||
| const CPU = require('../src/chip/MOS6502/cpu.js'); | const CPU = require('../src/chip/MOS6502/cpu.js'); | ||||
| const Bank = require('../src/common/bank.js'); | |||||
| const Mem = require('../src/memory'); | |||||
| describe("Testing MOS6502 CPU...", function(){ | describe("Testing MOS6502 CPU...", function(){ | ||||
| var cpu = new CPU(); | var cpu = new CPU(); | ||||
| var tick = cpu.clk(); | var tick = cpu.clk(); | ||||
| cpu.memory = new Bank(65536); | |||||
| cpu.memory = new Mem.Memory.RAM(64); | |||||
| 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(){ | ||||
| cpu.reset = true; | cpu.reset = true; | ||||
| tick(); // reset. | tick(); // reset. | ||||
| tick(); tick(); // Two ticks to process opcode. | tick(); tick(); // Two ticks to process opcode. | ||||
| console.log(cpu); | |||||
| expect(cpu.C).to.equal(0); | expect(cpu.C).to.equal(0); | ||||
| tick(); tick(); | tick(); tick(); | ||||
| //console.log(cpu) | |||||
| expect(cpu.C).to.equal(1); | expect(cpu.C).to.equal(1); | ||||
| }); | }); | ||||