nes-emu
Version:
A NES emulator
144 lines (136 loc) • 4.84 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _registers = require("./registers");
var _CPUMemoryMap = _interopRequireDefault(require("./CPUMemoryMap"));
var _Stack = _interopRequireDefault(require("./Stack"));
var _operations = _interopRequireDefault(require("./operations"));
var _constants = require("./constants");
var _constants2 = _interopRequireDefault(require("../constants"));
var _helpers = require("../helpers");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
/** The Center Processing Unit. It runs programs. */
class CPU {
constructor() {
_helpers.WithContext.apply(this);
this.pc = new _registers.Register16Bit(); // -> program counter
this.sp = new _registers.Register8Bit(); // -> stack pointer
this.flags = new _registers.FlagsRegister(); // -> also called "P" register
this.cycle = 0; // -> current cycle
this.extraCycles = 0; // -> pending cycles (to add in next step)
this.registers = {
a: new _registers.Register8Bit(0),
// accumulator
x: new _registers.Register8Bit(0),
// index X
y: new _registers.Register8Bit(0) // index Y
};
this.memory = new _CPUMemoryMap.default();
this.stack = new _Stack.default();
this._argument = null;
}
/** When a context is loaded. */
onLoad(context) {
this.memory.loadContext(context);
this.stack.loadContext(context);
this._reset();
}
/** Executes the next step (1 step = 1 instruction = N cycles). Returns N. */
step() {
const pc = this.pc.value;
const operation = this._readOperation();
const argument = this._readArgument(operation);
if (this.context.logger) this.context.logger.log({
context: this.context,
pc,
operation,
initialArgument: this._argument,
finalArgument: argument
});
operation.instruction.execute(this.context, argument);
const cycles = operation.cycles + this.extraCycles;
this.cycle += cycles;
this.extraCycles = 0;
return cycles;
}
/** Pushes the context to the stack and jumps to the interrupt handler. */
interrupt(interrupt, withB2Flag) {
if (interrupt.id === "IRQ" && !this._areInterruptsEnabled) return 0;
this.stack.push2Bytes(this.pc.value);
this.pushFlags(withB2Flag);
this.cycle += _constants2.default.CPU_INTERRUPT_CYCLES;
this.flags.i = true; // (to make sure handler doesn't get interrupted)
this._jumpToInterruptHandler(interrupt);
return _constants2.default.CPU_INTERRUPT_CYCLES;
}
/**
* Pushes the flags to the stack.
* B1 (bit 5) is always on, while B2 (bit 4) depends on `withB2Flag`.
*/
pushFlags() {
let withB2Flag = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
this.stack.push(this.flags.toByte() | (withB2Flag && 0b00010000));
}
/** Returns a snapshot of the current state. */
getSaveState() {
return {
pc: this.pc.value,
sp: this.sp.value,
flags: this.flags.toByte(),
cycle: this.cycle,
a: this.registers.a.value,
x: this.registers.x.value,
y: this.registers.y.value,
memory: this.memory.getSaveState()
};
}
/** Restores state from a snapshot. */
setSaveState(saveState) {
this.pc.value = saveState.pc;
this.sp.value = saveState.sp;
this.flags.load(saveState.flags);
this.cycle = saveState.cycle;
this.registers.a.value = saveState.a;
this.registers.x.value = saveState.x;
this.registers.y.value = saveState.y;
this.memory.setSaveState(saveState.memory);
}
_reset() {
this.pc.reset();
this.sp.reset();
this.cycle = 0;
this.extraCycles = 0;
this.registers.a.reset();
this.registers.x.reset();
this.registers.y.reset();
this._argument = null;
this.interrupt(_constants.interrupts.RESET);
}
_readOperation() {
const opcode = this.memory.readAt(this.pc.value);
const operation = _operations.default[opcode];
if (!operation) throw new Error("Unknown opcode: 0x".concat(opcode.toString(16), "."));
this.pc.increment();
return operation;
}
_readArgument(_ref) {
let {
instruction,
addressing,
canTakeExtraCycles
} = _ref;
const argument = this.memory.readBytesAt(this.pc.value, addressing.parameterSize);
this.pc.value += addressing.parameterSize;
this._argument = argument;
return instruction.needsValue ? addressing.getValue(this.context, argument, canTakeExtraCycles) : addressing.getAddress(this.context, argument, canTakeExtraCycles);
}
_jumpToInterruptHandler(interrupt) {
this.pc.value = this.memory.read2BytesAt(interrupt.vector);
}
get _areInterruptsEnabled() {
return !this.flags.i;
}
}
exports.default = CPU;