Comparing version 0.1.6 to 0.1.7
@@ -1,8 +0,13 @@ | ||
const { Disassembler } = require('./Disassembler') | ||
const { FONT_SET } = require('../constants/fontSet') | ||
const Disassembler = require('./Disassembler') | ||
const { FONT_SET } = require('../data/fontSet') | ||
const { DISPLAY_HEIGHT, DISPLAY_WIDTH } = require('../data/constants') | ||
class CPU { | ||
/** | ||
* @param { class } cpuInterface I/O for Chip8 | ||
*/ | ||
constructor(cpuInterface) { | ||
this.interface = cpuInterface | ||
} | ||
/** | ||
@@ -19,2 +24,4 @@ * Set or reset the state to initial values. | ||
* SP - Stack Pointer (8-bit) points at topost level of stack | ||
* | ||
* Any time an error would cause program execution to halt, halted is set to true | ||
*/ | ||
@@ -31,5 +38,7 @@ reset() { | ||
this.halted = false | ||
this.soundEnabled = false | ||
} | ||
load(romBuffer) { | ||
// Reset the CPU every time it is loaded | ||
this.reset() | ||
@@ -42,2 +51,3 @@ | ||
// Get ROM data from ROM buffer | ||
const romData = romBuffer.data | ||
@@ -47,4 +57,8 @@ let memoryStart = 0x200 | ||
// Place ROM data in memory starting at 0x200 | ||
// Since memory is stored in an 8-bit array and opcodes are 16-bit, we have to store the opcodes | ||
// across two indices in memory | ||
for (let i = 0; i < romData.length; i++) { | ||
// set the first index with the most significant byte (i.e., 0x1234 would be 0x12) | ||
this.memory[memoryStart + 2 * i] = romData[i] >> 8 | ||
// set the second index with the least significant byte (i.e., 0x1234 would be 0x34) | ||
this.memory[memoryStart + 2 * i + 1] = romData[i] & 0x00ff | ||
@@ -56,15 +70,19 @@ } | ||
if (this.DT > 0) { | ||
// Decrement the delay timer by one until it reaches zero | ||
this.DT-- | ||
} else { | ||
console.log('fixme') | ||
} | ||
if (this.ST > 0) { | ||
// The sound timer is active whenever the sound timer register (ST) is non-zero. | ||
this.ST-- | ||
} else { | ||
console.log('fixme') | ||
// When ST reaches zero, the sound timer deactivates. | ||
if (this.soundEnabled) { | ||
this.interface.disableSound() | ||
this.soundEnabled = false | ||
} | ||
} | ||
} | ||
step() { | ||
async step() { | ||
if (this.halted) { | ||
@@ -75,12 +93,11 @@ throw new Error( | ||
} | ||
// Fetch 16-bit opcode from memory | ||
const opcode = this._fetch() | ||
// Decode the opcode and get an object with the instruction and arguments | ||
const instruction = this._decode(opcode) | ||
console.log( | ||
'PC: ' + this.PC.toString(16).padStart(4, '0') + ' ' + Disassembler.format(instruction), | ||
opcode.toString(16).padStart(4, '0'), | ||
instruction.instruction.id | ||
) | ||
this._execute(instruction) | ||
// Execute code based on the instruction set | ||
await this._execute(instruction) // will remove opcode after debugging | ||
} | ||
@@ -104,2 +121,6 @@ | ||
// We have to combine two bytes in memory back into one big endian opcode | ||
// Left shifting by eight will move one byte over two positions - 0x12 will become 0x1200 | ||
// Left shifting by zero will keep one byte in the same position - 0x34 is still 0x34 | ||
// OR them together and get one 16-bit opcode - 0x1200 | 0x34 returns 0x1234 | ||
return (this.memory[this.PC] << 8) | (this.memory[this.PC + 1] << 0) | ||
@@ -109,17 +130,20 @@ } | ||
_decode(opcode) { | ||
// Return { instruction <object>, args <array> } | ||
return Disassembler.disassemble(opcode) | ||
} | ||
_execute(instruction) { | ||
async _execute(instruction) { | ||
const id = instruction.instruction.id | ||
const args = instruction.args | ||
// Execute code based on the ID of the instruction | ||
switch (id) { | ||
case 'CLS': | ||
// Clear the display | ||
console.log('todo - CLS') | ||
// 00E0 - Clear the display | ||
this.interface.clearDisplay() | ||
this._nextInstruction() | ||
break | ||
case 'RET': | ||
// Return from a subroutine. | ||
// 00EE - Return from a subroutine. | ||
if (this.SP === -1) { | ||
@@ -133,8 +157,10 @@ this.halted = true | ||
break | ||
case 'JP_ADDR': | ||
// Jump to location nnn. | ||
// 1nnn - Jump to location nnn. | ||
this.PC = args[0] | ||
break | ||
case 'CALL_ADDR': | ||
// Call subroutine at nnn. | ||
// 2nnn - Call subroutine at nnn. | ||
if (this.SP === 15) { | ||
@@ -149,4 +175,5 @@ this.halted = true | ||
break | ||
case 'SE_VX_NN': | ||
// Skip next instruction if Vx = kk. | ||
// 3xnn - Skip next instruction if Vx = nn. | ||
if (this.registers[args[0]] === args[1]) { | ||
@@ -158,4 +185,5 @@ this._skipInstruction() | ||
break | ||
case 'SNE_VX_NN': | ||
// Skip next instruction if Vx != kk. | ||
// 4xnn - Skip next instruction if Vx != nn. | ||
if (this.registers[args[0]] !== args[1]) { | ||
@@ -167,4 +195,5 @@ this._skipInstruction() | ||
break | ||
case 'SE_VX_VY': | ||
// Skip next instruction if Vx = Vy. | ||
// 5xy0 - Skip next instruction if Vx = Vy. | ||
if (this.registers[args[0]] === this.registers[args[1]]) { | ||
@@ -176,48 +205,61 @@ this._skipInstruction() | ||
break | ||
case 'LD_VX_NN': | ||
// Set Vx = kk. | ||
// 6xnn - Set Vx = nn. | ||
this.registers[args[0]] = args[1] | ||
this._nextInstruction() | ||
break | ||
case 'ADD_VX_NN': | ||
// Set Vx = Vx + kk. | ||
this.registers[args[0]] = this.registers[args[0]] + args[1] | ||
// 7xnn - Set Vx = Vx + nn. | ||
let v = this.registers[args[0]] + args[1] | ||
if (v > 255) { | ||
v -= 256 | ||
} | ||
this.registers[args[0]] = v | ||
this._nextInstruction() | ||
break | ||
case 'LD_VX_VY': | ||
// Set Vx = Vy. | ||
// 8xy0 - Set Vx = Vy. | ||
this.registers[args[0]] = this.registers[args[1]] | ||
this._nextInstruction() | ||
break | ||
case 'OR_VX_VY': | ||
// Set Vx = Vx OR Vy. | ||
// 8xy1 - Set Vx = Vx OR Vy. | ||
this.registers[args[0]] |= this.registers[args[1]] | ||
this._nextInstruction() | ||
break | ||
case 'AND_VX_VY': | ||
// Set Vx = Vx AND Vy. | ||
// 8xy2 - Set Vx = Vx AND Vy. | ||
this.registers[args[0]] &= this.registers[args[1]] | ||
this._nextInstruction() | ||
break | ||
case 'XOR_VX_VY': | ||
// Set Vx = Vx XOR Vy. | ||
// 8xy3 - Set Vx = Vx XOR Vy. | ||
this.registers[args[0]] ^= this.registers[args[1]] | ||
this._nextInstruction() | ||
break | ||
case 'ADD_VX_VY': | ||
// Set Vx = Vx + Vy, set VF = carry. | ||
// 8xy4 - Set Vx = Vx + Vy, set VF = carry. | ||
this.registers[args[0]] += this.registers[args[1]] | ||
this.registers[0xf] = this.registers[args[0]] + this.registers[args[1]] > 0xff ? 1 : 0 | ||
this.registers[args[0]] = this.registers[args[0]] + this.registers[args[1]] | ||
this._nextInstruction() | ||
break | ||
case 'SUB_VX_VY': | ||
// Set Vx = Vx - Vy, set VF = NOT borrow. | ||
// 8xy5 - Set Vx = Vx - Vy, set VF = NOT borrow. | ||
this.registers[0xf] = this.registers[args[0]] > this.registers[args[1]] ? 1 : 0 | ||
this.registers[args[0]] -= this.registers[args[1]] | ||
this.registers[args[0]] = this.registers[args[0]] - this.registers[args[1]] | ||
this._nextInstruction() | ||
break | ||
case 'SHR_VX_VY': | ||
// Set Vx = Vx SHR 1. | ||
// 8xy6 - Set Vx = Vx SHR 1. | ||
this.registers[0xf] = this.registers[args[0]] & 1 | ||
@@ -227,4 +269,5 @@ this.registers[args[0]] >>= 1 | ||
break | ||
case 'SUBN_VX_VY': | ||
// Set Vx = Vy - Vx, set VF = NOT borrow. | ||
// 8xy7 - Set Vx = Vy - Vx, set VF = NOT borrow. | ||
this.registers[0xf] = this.registers[args[1]] > this.registers[args[0]] ? 1 : 0 | ||
@@ -235,11 +278,13 @@ | ||
break | ||
case 'SHL_VX_VY': | ||
// Set Vx = Vx SHL 1. | ||
// 8xyE - Set Vx = Vx SHL 1. | ||
this.registers[0xf] = this.registers[args[0]] >> 7 | ||
this.registers[args[0]] = this.registers[args[0]] << 1 | ||
this.registers[args[0]] <<= 1 | ||
this._nextInstruction() | ||
break | ||
case 'SNE_VX_VY': | ||
// Skip next instruction if Vx != Vy. | ||
// 9xy0 - Skip next instruction if Vx != Vy. | ||
if (this.registers[args[0]] !== this.registers[args[1]]) { | ||
@@ -251,20 +296,23 @@ this._skipInstruction() | ||
break | ||
case 'LD_I_ADDR': | ||
// Set I = nnn. | ||
// Annn - Set I = nnn. | ||
this.I = args[1] | ||
this._nextInstruction() | ||
break | ||
case 'JP_V0_ADDR': | ||
// Jump to location nnn + V0. | ||
// Bnnn - Jump to location nnn + V0. | ||
this.PC = this.registers[0] + args[1] | ||
break | ||
case 'RND_VX_NN': | ||
// Set Vx = random byte AND kk. | ||
let random = Math.floor(Math.random() * 256) | ||
// Cxnn - Set Vx = random byte AND nn. | ||
let random = Math.floor(Math.random() * 0xff) | ||
this.registers[args[0]] = random & args[1] | ||
this._nextInstruction() | ||
break | ||
case 'DRW_VX_VY_N': | ||
// Display n-byte sprite starting at memory location I at (Vx, Vy), set VF = collision. | ||
// The interpreter reads n bytes from memory, starting at the address stored in I. | ||
// Dxyn - Display n-byte sprite starting at memory location I at (Vx, Vy), set VF = collision. | ||
if (this.I > 4095 - args[2]) { | ||
@@ -275,25 +323,28 @@ this.halted = true | ||
let sprite = '' | ||
// If no pixels are erased, set VF to 0 | ||
this.registers[0xf] = 0 | ||
// The interpreter reads n bytes from memory, starting at the address stored in I. | ||
for (let i = 0; i < args[2]; i++) { | ||
let line = this.memory[this.I + i] | ||
// Each byte is a line of eight pixels | ||
for (let position = 0; position < 8; position++) { | ||
// Get the byte to set by position | ||
let value = line & (1 << (7 - position)) ? 1 : 0 | ||
// If this causes any pixels to be erased, VF is set to 1 | ||
let x = (this.registers[args[0]] + position) % DISPLAY_WIDTH // wrap around width | ||
let y = (this.registers[args[1]] + i) % DISPLAY_HEIGHT // wrap around height | ||
for (let position = 7; position >= 0; position--) { | ||
if (line & (1 << position)) { | ||
sprite += '█' | ||
} else { | ||
sprite += ' ' | ||
if (this.interface.drawPixel(x, y, value)) { | ||
this.registers[0xf] = 1 | ||
} | ||
} | ||
sprite += '\n' | ||
} | ||
console.log(sprite) | ||
this._nextInstruction() | ||
break | ||
case 'SKP_VX': | ||
// Skip next instruction if key with the value of Vx is pressed. | ||
console.log('fixme 0') | ||
if (0 === this.registers[args[0]]) { | ||
// Ex9E - Skip next instruction if key with the value of Vx is pressed. | ||
if (this.interface.getKeys() & (1 << this.registers[args[0]])) { | ||
this._skipInstruction() | ||
@@ -304,6 +355,6 @@ } else { | ||
break | ||
case 'SKNP_VX': | ||
// Skip next instruction if key with the value of Vx is not pressed. | ||
console.log('fixme 0') | ||
if (0 !== this.registers[args[0]]) { | ||
// ExA1 - Skip next instruction if key with the value of Vx is not pressed. | ||
if (!(this.interface.getKeys() & (1 << this.registers[args[0]]))) { | ||
this._skipInstruction() | ||
@@ -314,30 +365,39 @@ } else { | ||
break | ||
case 'LD_VX_DT': | ||
// Set Vx = delay timer value. | ||
// Fx07 - Set Vx = delay timer value. | ||
this.registers[args[0]] = this.DT | ||
this._nextInstruction() | ||
break | ||
case 'LD_VX_K': | ||
// Wait for a key press, store the value of the key in Vx. | ||
console.log('fixme 0') | ||
this.registers[args[0]] = 0 | ||
case 'LD_VX_N': | ||
// Fx0A - Wait for a key press, store the value of the key in Vx. | ||
this.registers[args[0]] = await this.interface.waitKey() | ||
this._nextInstruction() | ||
break | ||
case 'LD_DT_VX': | ||
// Set delay timer = Vx. | ||
// Fx15 - Set delay timer = Vx. | ||
this.DT = this.registers[args[1]] | ||
this._nextInstruction() | ||
break | ||
case 'LD_ST_VX': | ||
// Set sound timer = Vx. | ||
// Fx18 - Set sound timer = Vx. | ||
this.ST = this.registers[args[1]] | ||
if (this.ST > 0) { | ||
this.soundEnabled = true | ||
this.interface.enableSound() | ||
} | ||
this._nextInstruction() | ||
break | ||
case 'ADD_I_VX': | ||
// Set I = I + Vx. | ||
// Fx1E - Set I = I + Vx. | ||
this.I = this.I + this.registers[args[1]] | ||
this._nextInstruction() | ||
break | ||
case 'LD_F_VX': | ||
// Set I = location of sprite for digit Vx. | ||
// Fx29 - Set I = location of sprite for digit Vx. | ||
if (this.registers[args[1]] > 0xf) { | ||
@@ -351,4 +411,7 @@ this.halted = true | ||
break | ||
case 'LD_B_VX': | ||
// Store BCD representation of Vx in memory locations I, I+1, and I+2. | ||
// Fx33 - Store BCD representation of Vx in memory locations I, I+1, and I+2. | ||
// BCD means binary-coded decimal | ||
// If VX is 0xef, or 239, we want 2, 3, and 9 in I, I+1, and I+2. | ||
if (this.I > 4093) { | ||
@@ -360,7 +423,7 @@ this.halted = true | ||
let x = this.registers[args[1]] | ||
const a = Math.floor(x / 100) | ||
x = x - a * 100 | ||
const b = Math.floor(x / 10) | ||
x = x - b * 10 | ||
const c = Math.floor(x) | ||
const a = Math.floor(x / 100) // for 239, a is 2 | ||
x = x - a * 100 // subtract value of a * 100 from x (200) | ||
const b = Math.floor(x / 10) // x is now 39, b is 3 | ||
x = x - b * 10 // subtract value of b * 10 from x (30) | ||
const c = Math.floor(x) // x is now 9 | ||
@@ -373,4 +436,5 @@ this.memory[this.I] = a | ||
break | ||
case 'LD_I_VX': | ||
// Store registers V0 through Vx in memory starting at location I. | ||
// Fx55 - Store registers V0 through Vx in memory starting at location I. | ||
if (this.I > 4095 - args[1]) { | ||
@@ -384,6 +448,8 @@ this.halted = true | ||
} | ||
this._nextInstruction() | ||
break | ||
case 'LD_VX_I': | ||
// Read registers V0 through Vx from memory starting at location I. | ||
// Fx65 - Read registers V0 through Vx from memory starting at location I. | ||
if (this.I > 4095 - args[0]) { | ||
@@ -394,5 +460,6 @@ this.halted = true | ||
for (let i = 0; i <= args[1]; i++) { | ||
for (let i = 0; i <= args[0]; i++) { | ||
this.registers[i] = this.memory[this.I + i] | ||
} | ||
this._nextInstruction() | ||
@@ -399,0 +466,0 @@ break |
@@ -1,12 +0,20 @@ | ||
const { INSTRUCTION_SET } = require('../constants/instructionSet') | ||
const { INSTRUCTION_SET } = require('../data/instructionSet') | ||
class Disassembler { | ||
static disassemble(opcode) { | ||
const instruction = INSTRUCTION_SET.filter( | ||
const Disassembler = { | ||
disassemble(opcode) { | ||
// Find the instruction in which the opcode & mask equals the pattern | ||
// For example, the opcode 0x1234 with the mask of 0xf000 applied would return 0x1000. | ||
// This matches the JP_ADDR mask and pattern | ||
const instruction = INSTRUCTION_SET.find( | ||
instruction => (opcode & instruction.mask) === instruction.pattern | ||
)[0] | ||
) | ||
// Each instruction may also have arguments. An argument will either be 4, 8, or 12 bits. | ||
// In the case of SE_VX_NN, one argument's mask is 0x0f00 and shift is 8. | ||
// An example opcode of 0x3abb with the mask of 0x0f00 will give us 0xa00, right shifted by 8 will give us 0xa. | ||
const args = instruction.arguments.map(arg => (opcode & arg.mask) >> arg.shift) | ||
// Return an object containing the instruction argument object and an array of arguments | ||
return { instruction, args } | ||
} | ||
}, | ||
@@ -26,7 +34,9 @@ /** | ||
*/ | ||
static format(decodedInstruction) { | ||
format(decodedInstruction) { | ||
// Print out formatted instructions from the disassembled instructions | ||
const types = decodedInstruction.instruction.arguments.map(arg => arg.type) | ||
const rawArgs = decodedInstruction.args | ||
let formatted | ||
let formattedInstruction | ||
// Format the display of arguments based on type | ||
if (rawArgs.length > 0) { | ||
@@ -59,11 +69,11 @@ let args = [] | ||
}) | ||
formatted = decodedInstruction.instruction.name + ' ' + args.join(', ') | ||
formattedInstruction = decodedInstruction.instruction.name + ' ' + args.join(', ') | ||
} else { | ||
formatted = decodedInstruction.instruction.name | ||
formattedInstruction = decodedInstruction.instruction.name | ||
} | ||
return formatted | ||
} | ||
return formattedInstruction | ||
}, | ||
static dump(data) { | ||
dump(data) { | ||
const lines = data.map((code, i) => { | ||
@@ -78,7 +88,5 @@ const address = (i * 2).toString(16).padStart(6, '0') | ||
return lines.join('\n') | ||
} | ||
}, | ||
} | ||
module.exports = { | ||
Disassembler, | ||
} | ||
module.exports = Disassembler |
@@ -8,9 +8,29 @@ class CpuInterface { | ||
draw() { | ||
throw new TypeError('Draw must be implemented on the inherited class.') | ||
clearDisplay() { | ||
throw new TypeError('Must be implemented on the inherited class.') | ||
} | ||
clear() { | ||
throw new TypeError('Clear must be implemented on the inherited class.') | ||
renderDisplay() { | ||
throw new TypeError('Must be implemented on the inherited class.') | ||
} | ||
waitKey() { | ||
throw new TypeError('Must be implemented on the inherited class.') | ||
} | ||
getKeys() { | ||
throw new TypeError('Must be implemented on the inherited class.') | ||
} | ||
drawPixel() { | ||
throw new TypeError('Must be implemented on the inherited class.') | ||
} | ||
enableSound() { | ||
throw new TypeError('Must be implemented on the inherited class.') | ||
} | ||
disableSound() { | ||
throw new TypeError('Must be implemented on the inherited class.') | ||
} | ||
} | ||
@@ -17,0 +37,0 @@ |
@@ -7,8 +7,6 @@ const fs = require('fs') | ||
// Read the raw data buffer from the file | ||
const buffer = fs.readFileSync(filename) | ||
if (buffer.length % 2 !== 0) { | ||
throw new Error('Invalid input') | ||
} | ||
// Create 16-bit big endian opcodes from the buffer | ||
for (let i = 0; i < buffer.length; i += 2) { | ||
@@ -15,0 +13,0 @@ this.data.push((buffer[i] << 8) | (buffer[i + 1] << 0)) |
const { RomBuffer } = require('./classes/RomBuffer') | ||
const { CPU } = require('./classes/CPU') | ||
const { TerminalCpuInterface } = require('./classes/TerminalCpuInterface') | ||
@@ -7,2 +8,3 @@ module.exports = { | ||
CPU, | ||
TerminalCpuInterface, | ||
} |
{ | ||
"name": "chip8js", | ||
"version": "0.1.6", | ||
"version": "0.1.7", | ||
"description": "A Chip-8 emulator written in JavaScript (Node.js).", | ||
@@ -14,6 +14,6 @@ "author": { | ||
"scripts": { | ||
"start": "node index.js", | ||
"play": "node scripts/play", | ||
"hexdump": "node scripts/hexdump", | ||
"example": "node scripts/run roms/CONNECT4", | ||
"test": "jest" | ||
"example": "node scripts/play roms/CONNECT4", | ||
"test": "jest --verbose" | ||
}, | ||
@@ -31,3 +31,6 @@ "devDependencies": { | ||
"license": "MIT", | ||
"private": false | ||
"private": false, | ||
"dependencies": { | ||
"blessed": "^0.1.81" | ||
} | ||
} |
@@ -14,3 +14,2 @@ # Chip8.js [![Build Status](https://travis-ci.org/taniarascia/chip8.svg?branch=master)](https://travis-ci.org/taniarascia/chip8) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) | ||
- [View hex dump](#view-hex-dump) | ||
- [Reference](#reference) | ||
- [Automated Testing](#automated-testing) | ||
@@ -25,3 +24,3 @@ - [Instruction tests](#instruction-tests) | ||
Chip8.js is an ongoing project to write a Chip-8 emulator in JavaScript. The main motivation is to learn lower level programming concepts and to increase familiarity with the Node.js environment. | ||
Chip8.js is an ongoing project to write a Chip-8 emulator in JavaScript. The main motivation is to learn lower level programming concepts and to increase familiarity with the Node.js environment. | ||
@@ -47,3 +46,3 @@ Here are some of the concepts I learned while writing this program: | ||
> This guide assumes you already have [Node.js](https://nodejs.org/en/) and [Yarn](https://yarnpkg.com/en/) installed. | ||
> This guide assumes you already have [Node.js](https://nodejs.org/en/) and [Yarn](https://yarnpkg.com/en/) installed. | ||
@@ -54,3 +53,3 @@ You can add the module directly from the [chip8js](https://www.npmjs.com/package/chip8js) npm package. | ||
yarn add chip8js | ||
# npm i --save chip8js | ||
# npm i chip8js | ||
``` | ||
@@ -75,3 +74,3 @@ | ||
Chip-8 compatible ROMs can be saved in the `roms/` directory. A copy of *Connect 4* is shipped with Chip8.js (at `roms/CONNECT4`) for example and testing purposes. | ||
Chip-8 compatible ROMs can be saved in the `roms/` directory. A copy of _Connect 4_ is shipped with Chip8.js (at `roms/CONNECT4`) for example and testing purposes. | ||
@@ -110,6 +109,2 @@ ### Load ROM | ||
## Reference | ||
In progress. | ||
## Automated Testing | ||
@@ -134,3 +129,3 @@ | ||
The [instruction tests](tests/instructions.test.js) cover the `INSTRUCTION_SET` found in `constants/instructionSet.js`. Each instruction has: | ||
The [instruction tests](tests/instructions.test.js) cover the `INSTRUCTION_SET` found in `data/instructionSet.js`. Each instruction has: | ||
@@ -148,3 +143,3 @@ - A `key`: for internal use | ||
```js | ||
// constants/instructionSet.js | ||
// data/instructionSet.js | ||
@@ -170,3 +165,3 @@ { | ||
test('test instruction 06: 3xkk - SE Vx, byte', () => { | ||
test('6: Expect disassembler to match opcode 3xnn to instruction SE_VX_NN', () => { | ||
expect(Disassembler.disassemble(0x3abb).instruction).toHaveProperty('id', 'SE_VX_NN') | ||
@@ -183,5 +178,5 @@ expect(Disassembler.disassemble(0x3abb).args).toHaveLength(2) | ||
The CPU decodes the opcode and returns the instruction object from `constants/instructionSet.js`. Each instruction performs a specific, unique action in the `case`. The [CPU tests](tests/cpu.test.js) test the state of the CPU after an executing an instruction. | ||
The CPU decodes the opcode and returns the instruction object from `data/instructionSet.js`. Each instruction performs a specific, unique action in the `case`. The [CPU tests](tests/cpu.test.js) test the state of the CPU after an executing an instruction. | ||
In the below example, the instruction is skipping an instruction if `Vx === kk`, otherwise it's going to the next instruction as usual. | ||
In the below example, the instruction is skipping an instruction if `Vx === nn`, otherwise it's going to the next instruction as usual. | ||
@@ -192,3 +187,3 @@ ```js | ||
case 'SE_VX_NN': | ||
// Skip next instruction if Vx = kk. | ||
// Skip next instruction if Vx = nn. | ||
if (this.registers[args[0]] === args[1]) { | ||
@@ -214,11 +209,13 @@ this._skipInstruction() | ||
test('test cpu 06: 3xkk - SE Vx, byte', () => { | ||
test('6: SE_VX_NN (3xnn) - Program counter should increment by two bytes if register x is not equal to nn argument', async () => { | ||
cpu.load({ data: [0x3abb] }) | ||
cpu.step() | ||
await cpu.step() | ||
expect(cpu.PC).toBe(0x202) | ||
}) | ||
test('6: SE_VX_NN (3xnn) - Program counter should increment by four bytes if register x is equal to nn argument', async () => { | ||
cpu.load({ data: [0x3abb] }) | ||
cpu.registers[0xa] = 0xbb | ||
cpu.step() | ||
await cpu.step() | ||
@@ -231,14 +228,8 @@ expect(cpu.PC).toBe(0x204) | ||
- [x] Tests: Errors: 8 | ||
- [x] RET | ||
- [x] CALL_ADDR | ||
- [x] DRW_VX_VY_N | ||
- [x] LD_F_VX | ||
- [x] LD_B_VX | ||
- [x] LD_I_VX | ||
- [x] LD_VX_I | ||
- [x] DW | ||
- [ ] Create Interface Classes | ||
- [ ] Fix speed and timers | ||
- [ ] Web I/O | ||
- [ ] Libui I/O | ||
- [ ] Convert to TypeScript | ||
- [ ] Write an assembler | ||
## Acknowledgements | ||
@@ -256,2 +247,2 @@ | ||
The code is open source and available under the [MIT License](LICENSE). | ||
This project is open source and available under the [MIT License](LICENSE). |
@@ -5,5 +5,5 @@ const { CPU } = require('../classes/CPU') | ||
for (let i = 0; i < 76; i = i + 5) { | ||
cpu.load({ data: [ 0xdab5 ] }) | ||
cpu.load({ data: [0xdab5] }) | ||
cpu.I = i | ||
cpu.step() | ||
} |
@@ -1,37 +0,55 @@ | ||
describe('CPU tests', () => { | ||
describe('CPU tests', async () => { | ||
const { CPU } = require('../classes/CPU') | ||
const cpu = new CPU(null) | ||
const { MockCpuInterface } = require('../classes/interfaces/MockCpuInterface') | ||
const cpuInterface = new MockCpuInterface() | ||
const cpu = new CPU(cpuInterface) | ||
test('CPU does not execute after halting', () => { | ||
test('CPU should not execute after halting', async () => { | ||
cpu.load({ data: 0x0000 }) | ||
cpu.halted = true | ||
expect(() => { | ||
cpu.step() | ||
}).toThrowError( | ||
'A problem has been detected and Chip-8 has been shut down to prevent damage to your computer.' | ||
let error | ||
try { | ||
await cpu.step() | ||
} catch (e) { | ||
error = e | ||
} | ||
// BSOD on halted program | ||
expect(error).toEqual( | ||
new Error( | ||
'A problem has been detected and Chip-8 has been shut down to prevent damage to your computer.' | ||
) | ||
) | ||
}) | ||
// test.skip('test cpu 02: CLS', () => {}) | ||
// test.skip('2: CLS', () => {}) | ||
test('test cpu 03: RET', () => { | ||
cpu.load({ data: [ 0x00ee ] }) | ||
cpu.SP = 2 | ||
cpu.stack[2] = 0xf | ||
cpu.step() | ||
test('3: RET (00ee) - Program counter should be set to stack pointer, then decrement stack pointer', async () => { | ||
cpu.load({ data: [0x00ee] }) | ||
cpu.SP = 0x2 | ||
cpu.stack[0x2] = 0xf | ||
await cpu.step() | ||
expect(cpu.PC).toBe(0xf) | ||
expect(cpu.SP).toBe(0x1) | ||
}) | ||
cpu.load({ data: [ 0x00ee ] }) | ||
test('3: RET (00ee) - CPU should halt if stack pointer is set to 0', async () => { | ||
cpu.load({ data: [0x00ee] }) | ||
expect(() => { | ||
cpu.step() | ||
}).toThrowError('Stack underflow.') | ||
let error | ||
try { | ||
await cpu.step() | ||
} catch (e) { | ||
error = e | ||
} | ||
// BSOD on halted program | ||
expect(error).toEqual(new Error('Stack underflow.')) | ||
}) | ||
test('test cpu 04: 1nnn - JP addr', () => { | ||
cpu.load({ data: [ 0x1333 ] }) | ||
cpu.step() | ||
test('4: JP_ADDR (1nnn) - Program counter should be set to address in argument', async () => { | ||
cpu.load({ data: [0x1333] }) | ||
await cpu.step() | ||
@@ -41,6 +59,7 @@ expect(cpu.PC).toBe(0x333) | ||
test('test cpu 05: 2nnn - CALL addr', () => { | ||
cpu.load({ data: [ 0x2062 ] }) | ||
test('5: CALL_ADDR (2nnn) - Stack pointer should increment, program counter should be set to address in argument', async () => { | ||
cpu.load({ data: [0x2062] }) | ||
// Set PC to retain original value | ||
const PC = cpu.PC | ||
cpu.step() | ||
await cpu.step() | ||
@@ -50,20 +69,30 @@ expect(cpu.SP).toBe(0) | ||
expect(cpu.PC).toBe(0x062) | ||
}) | ||
cpu.load({ data: [ 0x2062 ] }) | ||
test('5: CALL_ADDR (2nnn) - CPU should halt if stack pointer is set to 15', async () => { | ||
cpu.load({ data: [0x2062] }) | ||
cpu.SP = 15 | ||
expect(() => { | ||
cpu.step() | ||
}).toThrowError('Stack overflow.') | ||
let error | ||
try { | ||
await cpu.step() | ||
} catch (e) { | ||
error = e | ||
} | ||
// BSOD on halted program | ||
expect(error).toEqual(new Error('Stack overflow.')) | ||
}) | ||
test('test cpu 06: 3xkk - SE Vx, byte', () => { | ||
cpu.load({ data: [ 0x3abb ] }) | ||
cpu.step() | ||
test('6: SE_VX_NN (3xnn) - Program counter should increment by two bytes if register x is not equal to nn argument', async () => { | ||
cpu.load({ data: [0x3abb] }) | ||
await cpu.step() | ||
expect(cpu.PC).toBe(0x202) | ||
}) | ||
cpu.load({ data: [ 0x3abb ] }) | ||
test('6: SE_VX_NN (3xnn) - Program counter should increment by four bytes if register x is equal to nn argument', async () => { | ||
cpu.load({ data: [0x3abb] }) | ||
cpu.registers[0xa] = 0xbb | ||
cpu.step() | ||
await cpu.step() | ||
@@ -73,11 +102,13 @@ expect(cpu.PC).toBe(0x204) | ||
test('test cpu 07: 4xkk - SNE Vx, byte', () => { | ||
cpu.load({ data: [ 0x4acc ] }) | ||
cpu.step() | ||
test('7: SNE_VX_NN (4xnn) - Program counter should increment by four bytes if register x is not equal to nn argument', async () => { | ||
cpu.load({ data: [0x4acc] }) | ||
await cpu.step() | ||
expect(cpu.PC).toBe(0x204) | ||
}) | ||
cpu.load({ data: [ 0x4acc ] }) | ||
test('7: SNE_VX_NN (4xnn) - Program counter should increment by two bytes if register x is equal to nn argument', async () => { | ||
cpu.load({ data: [0x4acc] }) | ||
cpu.registers[0xa] = 0xcc | ||
cpu.step() | ||
await cpu.step() | ||
@@ -87,11 +118,16 @@ expect(cpu.PC).toBe(0x202) | ||
test('test cpu 08: 5xy0 - SE Vx, Vy', () => { | ||
cpu.load({ data: [ 0x5ab0 ] }) | ||
cpu.step() | ||
test('8: SE_VX_VY (5xy0) - Program counter should increment by four if register x is equal to register y', async () => { | ||
cpu.load({ data: [0x5ab0] }) | ||
cpu.registers[0xa] = 0x5 | ||
cpu.registers[0xb] = 0x5 | ||
await cpu.step() | ||
expect(cpu.PC).toBe(0x204) | ||
}) | ||
cpu.load({ data: [ 0x5ab0 ] }) | ||
test('8: SE_VX_VY (5xy0) - Program counter should increment by two if register x is not equal to register y', async () => { | ||
cpu.load({ data: [0x5ab0] }) | ||
cpu.registers[0xa] = 0x5 | ||
cpu.step() | ||
cpu.registers[0xa] = 0x6 | ||
await cpu.step() | ||
@@ -101,6 +137,6 @@ expect(cpu.PC).toBe(0x202) | ||
test('test cpu 09: 6xkk - LD Vx, byte', () => { | ||
cpu.load({ data: [ 0x6abb ] }) | ||
test('9: LD_VX_NN (6xnn) - Register x should be set to the value of argument nn', async () => { | ||
cpu.load({ data: [0x6abb] }) | ||
cpu.registers[0xa] = 0x10 | ||
cpu.step() | ||
await cpu.step() | ||
@@ -110,6 +146,6 @@ expect(cpu.registers[0xa]).toBe(0xbb) | ||
test('test cpu 10: 7xkk - ADD Vx, byte', () => { | ||
cpu.load({ data: [ 0x7abb ] }) | ||
test('10: ADD_VX_NN (7xnn) - Register x should be set to the value of register x plus argument nn', async () => { | ||
cpu.load({ data: [0x7abb] }) | ||
cpu.registers[0xa] = 0x10 | ||
cpu.step() | ||
await cpu.step() | ||
@@ -119,6 +155,6 @@ expect(cpu.registers[0xa]).toBe(0x10 + 0xbb) | ||
test('test cpu 11: 8xy0 - LD Vx, Vy', () => { | ||
cpu.load({ data: [ 0x8ab0 ] }) | ||
test('11: LD_VX_VY (8xy0) - Register x should be set to the value of register y', async () => { | ||
cpu.load({ data: [0x8ab0] }) | ||
cpu.registers[0xb] = 0x8 | ||
cpu.step() | ||
await cpu.step() | ||
@@ -128,42 +164,47 @@ expect(cpu.registers[0xa]).toBe(0x8) | ||
test('test cpu 12: 8xy1 - OR Vx, Vy', () => { | ||
cpu.load({ data: [ 0x8ab1 ] }) | ||
test('12: OR_VX_VY (8xy1) - Register x should be set to the value of register x OR register y', async () => { | ||
cpu.load({ data: [0x8ab1] }) | ||
cpu.registers[0xa] = 0x3 | ||
cpu.registers[0xb] = 0x4 | ||
cpu.step() | ||
await cpu.step() | ||
expect(0x3 | 0x4).toBe(0x7) | ||
expect(cpu.registers[0xa]).toBe(0x7) | ||
}) | ||
test('test cpu 13: 8xy2 - AND Vx, Vy', () => { | ||
cpu.load({ data: [ 0x8ab2 ] }) | ||
test('13: AND_VX_VY (8xy2) - Register x should be set to the value of register x AND register y', async () => { | ||
cpu.load({ data: [0x8ab2] }) | ||
cpu.registers[0xa] = 0x3 | ||
cpu.registers[0xb] = 0x4 | ||
cpu.step() | ||
await cpu.step() | ||
expect(0x3 & 0x4).toBe(0) | ||
expect(cpu.registers[0xa]).toBe(0) | ||
}) | ||
test('test cpu 14: 8xy3 - XOR Vx, Vy', () => { | ||
cpu.load({ data: [ 0x8ab3 ] }) | ||
test('14: XOR_VX_VY (8xy3) - Register x should be set to the value of register x XOR register y', async () => { | ||
cpu.load({ data: [0x8ab3] }) | ||
cpu.registers[0xa] = 0x3 | ||
cpu.registers[0xb] = 0x3 | ||
cpu.step() | ||
await cpu.step() | ||
expect(0x3 ^ 0x3).toBe(0) | ||
expect(cpu.registers[0xa]).toBe(0) | ||
}) | ||
test('test cpu 15: 8xy4 - ADD Vx, Vy', () => { | ||
cpu.load({ data: [ 0x8ab4 ] }) | ||
test('15: ADD_VX_VY (8xy4) - Register x should be set to the value of the sum of register x and register y (VF with no carry)', async () => { | ||
cpu.load({ data: [0x8ab4] }) | ||
cpu.registers[0xa] = 0x3 | ||
cpu.registers[0xb] = 0x4 | ||
cpu.step() | ||
await cpu.step() | ||
expect(cpu.registers[0xa]).toBe(0x7) | ||
expect(cpu.registers[0xf]).toBe(0) | ||
}) | ||
cpu.load({ data: [ 0x8ab4 ] }) | ||
test('15: ADD_VX_VY (8xy4) - Register x should be set to the value of the sum of register x and register y (VF with carry)', async () => { | ||
cpu.load({ data: [0x8ab4] }) | ||
cpu.registers[0xa] = 0xff | ||
cpu.registers[0xb] = 0xff | ||
cpu.step() | ||
await cpu.step() | ||
@@ -174,15 +215,17 @@ expect(cpu.registers[0xa]).toBe(0xfe) | ||
test('test cpu 16: 8xy5 - SUB Vx, Vy', () => { | ||
cpu.load({ data: [ 0x8ab5 ] }) | ||
test('16: SUB_VX_VY (8xy5) - Register x should be set to the difference of register x and register y (VF with carry)', async () => { | ||
cpu.load({ data: [0x8ab5] }) | ||
cpu.registers[0xa] = 0x4 | ||
cpu.registers[0xb] = 0x2 | ||
cpu.step() | ||
await cpu.step() | ||
expect(cpu.registers[0xa]).toBe(2) | ||
expect(cpu.registers[0xf]).toBe(1) | ||
}) | ||
cpu.load({ data: [ 0x8ab5 ] }) | ||
test('16: SUB_VX_VY (8xy5) - Register x should be set to the difference of register x and register y (VF with no carry)', async () => { | ||
cpu.load({ data: [0x8ab5] }) | ||
cpu.registers[0xa] = 0x2 | ||
cpu.registers[0xb] = 0x3 | ||
cpu.step() | ||
await cpu.step() | ||
@@ -193,6 +236,6 @@ expect(cpu.registers[0xa]).toBe(255) | ||
test('test cpu 17: 8xy6 - SHR Vx {, Vy}', () => { | ||
cpu.load({ data: [ 0x8ab6 ] }) | ||
test('17: SHR_VX_VY (8xy6) - Shift register x right 1 (AKA divide x by 2). Set VF to 1 if least significant bit of register x is 1', async () => { | ||
cpu.load({ data: [0x8ab6] }) | ||
cpu.registers[0xa] = 0x3 | ||
cpu.step() | ||
await cpu.step() | ||
@@ -203,15 +246,17 @@ expect(cpu.registers[0xa]).toBe(0x3 >> 1) | ||
test('test cpu 18: 8xy7 - SUBN Vx, Vy', () => { | ||
cpu.load({ data: [ 0x8ab7 ] }) | ||
test('18: SUBN_VX_VY (8xy7) - Set register x to the difference of register y and register x (VF with no carry)', async () => { | ||
cpu.load({ data: [0x8ab7] }) | ||
cpu.registers[0xa] = 0x3 | ||
cpu.registers[0xb] = 0x2 | ||
cpu.step() | ||
await cpu.step() | ||
expect(cpu.registers[0xa]).toBe(255) | ||
expect(cpu.registers[0xf]).toBe(0) | ||
}) | ||
cpu.load({ data: [ 0x8ab7 ] }) | ||
test('18: SUBN_VX_VY (8xy7) - Set register x to the difference of register y and register x (VF with carry)', async () => { | ||
cpu.load({ data: [0x8ab7] }) | ||
cpu.registers[0xa] = 0x2 | ||
cpu.registers[0xb] = 0x3 | ||
cpu.step() | ||
await cpu.step() | ||
@@ -222,6 +267,6 @@ expect(cpu.registers[0xa]).toBe(1) | ||
test('test cpu 19: 8xyE - SHL Vx, {, Vy}', () => { | ||
cpu.load({ data: [ 0x8abe ] }) | ||
test('19: SHL_VX_VY (8xyE) - Shift register x left one (AKA multiply by 2). Set VF to 1 if least significant bit of register x is 1', async () => { | ||
cpu.load({ data: [0x8abe] }) | ||
cpu.registers[0xa] = 0x3 | ||
cpu.step() | ||
await cpu.step() | ||
@@ -232,14 +277,16 @@ expect(cpu.registers[0xa]).toBe(0x3 << 1) | ||
test('test cpu 20: 9xy0 - SNE Vx, Vy', () => { | ||
cpu.load({ data: [ 0x9ab0 ] }) | ||
test('20: SNE_VX_VY (9xy0) - Program counter should increment by four bytes if register x is not equal to register y', async () => { | ||
cpu.load({ data: [0x9ab0] }) | ||
cpu.registers[0xa] = 0x3 | ||
cpu.registers[0xb] = 0x4 | ||
cpu.step() | ||
await cpu.step() | ||
expect(cpu.PC).toBe(0x204) | ||
}) | ||
cpu.load({ data: [ 0x9ab0 ] }) | ||
test('20: SNE_VX_VY (9xy0) - Program counter should increment by two bytes if register x is equal to register y', async () => { | ||
cpu.load({ data: [0x9ab0] }) | ||
cpu.registers[0xa] = 0x3 | ||
cpu.registers[0xb] = 0x3 | ||
cpu.step() | ||
await cpu.step() | ||
@@ -249,5 +296,5 @@ expect(cpu.PC).toBe(0x202) | ||
test('test cpu 21: Annn - LD I, addr', () => { | ||
cpu.load({ data: [ 0xa999 ] }) | ||
cpu.step() | ||
test('21: LD_I_ADDR (Annn) - I should be set to the value of argument nnn', async () => { | ||
cpu.load({ data: [0xa999] }) | ||
await cpu.step() | ||
@@ -257,6 +304,6 @@ expect(cpu.I).toBe(0x999) | ||
test('test cpu 22: Bnnn - JP V0, addr', () => { | ||
cpu.load({ data: [ 0xb300 ] }) | ||
test('22: JP_V0_ADDR (Bnnn) - Program counter should be set to the sum of V0 and argument nnn', async () => { | ||
cpu.load({ data: [0xb300] }) | ||
cpu.registers[0] = 0x2 | ||
cpu.step() | ||
await cpu.step() | ||
@@ -266,25 +313,88 @@ expect(cpu.PC).toBe(0x2 + 0x300) | ||
// test.skip('test cpu 23: Cxkk - RND Vx, byte', () => {}) | ||
// 23: RND_VX_NN (Cxnn) Can't seed/test random number | ||
test('test cpu 24: Dxyn - DRW Vx, Vy, nibble', () => { | ||
cpu.load({ data: [ 0xdab5 ] }) | ||
test('24: DRW_VX_VY_N (Dxyn) - CPU should halt if I + argument nn exceed 4095', async () => { | ||
cpu.load({ data: [0xd005] }) | ||
cpu.I = 4091 | ||
expect(() => { | ||
cpu.step() | ||
}).toThrowError('Memory out of bounds.') | ||
// todo: passing test | ||
let error | ||
try { | ||
await cpu.step() | ||
} catch (e) { | ||
error = e | ||
} | ||
// BSOD on halted program | ||
expect(error).toEqual(new Error('Memory out of bounds.')) | ||
}) | ||
test('test cpu 25: Ex9E - SKP Vx', () => { | ||
// todo | ||
cpu.load({ data: [ 0xea9e ] }) | ||
cpu.registers[0xa] = 0 | ||
cpu.step() | ||
test('24: DRW_VX_VY_N (Dxyn) - n byte sprite should be disiplayed at coordinates in register x, register y', async () => { | ||
cpu.load({ data: [0xd125] }) | ||
cpu.registers[0x1] = 1 | ||
cpu.registers[0x2] = 1 | ||
await cpu.step() | ||
cpu.interface.renderDisplay() | ||
expect(cpuInterface.display[1][1]).toBe(1) | ||
expect(cpuInterface.display[2][1]).toBe(1) | ||
expect(cpuInterface.display[3][1]).toBe(1) | ||
expect(cpuInterface.display[4][1]).toBe(1) | ||
expect(cpuInterface.display[2][1]).toBe(1) | ||
expect(cpuInterface.display[2][2]).toBe(0) | ||
expect(cpuInterface.display[2][3]).toBe(0) | ||
expect(cpuInterface.display[2][4]).toBe(1) | ||
expect(cpuInterface.display[3][1]).toBe(1) | ||
expect(cpuInterface.display[3][2]).toBe(0) | ||
expect(cpuInterface.display[3][3]).toBe(0) | ||
expect(cpuInterface.display[3][4]).toBe(1) | ||
expect(cpuInterface.display[4][1]).toBe(1) | ||
expect(cpuInterface.display[4][2]).toBe(0) | ||
expect(cpuInterface.display[4][3]).toBe(0) | ||
expect(cpuInterface.display[4][4]).toBe(1) | ||
expect(cpuInterface.display[5][1]).toBe(1) | ||
expect(cpuInterface.display[5][1]).toBe(1) | ||
expect(cpuInterface.display[5][1]).toBe(1) | ||
expect(cpuInterface.display[5][1]).toBe(1) | ||
// No pixels were erased (no collision) | ||
expect(cpu.registers[0xf]).toBe(0) | ||
// This test relies on the previous one, to erase the previous values with collisions | ||
cpu.load({ data: [0xd125] }) | ||
cpu.registers[0x1] = 1 | ||
cpu.registers[0x2] = 1 | ||
await cpu.step() | ||
cpu.interface.renderDisplay() | ||
expect(cpuInterface.display[1][1]).toBe(0) | ||
expect(cpuInterface.display[2][1]).toBe(0) | ||
expect(cpuInterface.display[3][1]).toBe(0) | ||
expect(cpuInterface.display[4][1]).toBe(0) | ||
expect(cpuInterface.display[2][1]).toBe(0) | ||
expect(cpuInterface.display[2][4]).toBe(0) | ||
expect(cpuInterface.display[3][1]).toBe(0) | ||
expect(cpuInterface.display[3][4]).toBe(0) | ||
expect(cpuInterface.display[4][1]).toBe(0) | ||
expect(cpuInterface.display[4][4]).toBe(0) | ||
expect(cpuInterface.display[5][1]).toBe(0) | ||
expect(cpuInterface.display[5][1]).toBe(0) | ||
expect(cpuInterface.display[5][1]).toBe(0) | ||
expect(cpuInterface.display[5][1]).toBe(0) | ||
// All pixels were erased (collision) | ||
expect(cpu.registers[0xf]).toBe(1) | ||
}) | ||
test('25: SKP_VX (Ex9E) - Program counter should increment by four bytes if key with value of register x is selected', async () => { | ||
cpu.load({ data: [0xea9e] }) | ||
cpu.registers[0xa] = 4 | ||
// Mock CPU interface keys 0, 2, 3, 4 selected | ||
await cpu.step() | ||
expect(cpu.PC).toBe(0x204) | ||
}) | ||
cpu.load({ data: [ 0xea9e ] }) | ||
test('25: SKP_VX (Ex9E) - Program counter should increment by two bytes if key with value of register x is not selected', async () => { | ||
cpu.load({ data: [0xea9e] }) | ||
cpu.registers[0xa] = 1 | ||
cpu.step() | ||
// Mock CPU interface does not have 1 selected | ||
await cpu.step() | ||
@@ -294,13 +404,14 @@ expect(cpu.PC).toBe(0x202) | ||
test('test cpu 26: ExA1 - SKNP Vx', () => { | ||
// todo | ||
cpu.load({ data: [ 0xeba1 ] }) | ||
cpu.registers[0xb] = 0 | ||
cpu.step() | ||
test('26: SKNP_VX (ExA1) - Program counter should increment by two bytes if value of register x is a selected key', async () => { | ||
cpu.load({ data: [0xeba1] }) | ||
cpu.registers[0xb] = 4 | ||
await cpu.step() | ||
expect(cpu.PC).toBe(0x202) | ||
}) | ||
cpu.load({ data: [ 0xea9e ] }) | ||
test('26: SKNP_VX (ExA1) - Program counter should increment by four bytes if value of register x is not a selected key', async () => { | ||
cpu.load({ data: [0xea9e] }) | ||
cpu.registers[0xb] = 1 | ||
cpu.step() | ||
await cpu.step() | ||
@@ -310,6 +421,6 @@ expect(cpu.PC).toBe(0x204) | ||
test('test cpu 27: Fx07 - LD Vx, DT', () => { | ||
cpu.load({ data: [ 0xfa07 ] }) | ||
test('27: LD_VX_DT (Fx07) - Register x should be set to the value of DT (delay timer)', async () => { | ||
cpu.load({ data: [0xfa07] }) | ||
cpu.DT = 0xf | ||
cpu.step() | ||
await cpu.step() | ||
@@ -319,14 +430,15 @@ expect(cpu.registers[0xa]).toBe(0xf) | ||
test('test cpu 28: Fx0A - LD Vx, K', () => { | ||
// todo | ||
cpu.load({ data: [ 0xfb0a ] }) | ||
cpu.step() | ||
test('28: LD_VX_N (Fx0A) - Register x should be set to the value of keypress', async () => { | ||
// todo tick | ||
cpu.load({ data: [0xfb0a] }) | ||
await cpu.step() | ||
expect(cpu.registers[0xb]).toBe(0) | ||
expect(cpu.registers[0xb]).toBe(5) | ||
}) | ||
test('test cpu 29: Fx15 - LD DT, Vx', () => { | ||
cpu.load({ data: [ 0xfb15 ] }) | ||
test('29: LD_DT_VX (Fx15) - Delay timer should be set to the value of register x', async () => { | ||
// todo tick | ||
cpu.load({ data: [0xfb15] }) | ||
cpu.registers[0xb] = 0xf | ||
cpu.step() | ||
await cpu.step() | ||
@@ -336,6 +448,7 @@ expect(cpu.DT).toBe(0xf) | ||
test('test cpu 30: Fx18 - LD ST, Vx', () => { | ||
cpu.load({ data: [ 0xfa18 ] }) | ||
test('30: LD_ST_VX (Fx18) - Sound timer should be set to the value of register x', async () => { | ||
// todo tick | ||
cpu.load({ data: [0xfa18] }) | ||
cpu.registers[0xa] = 0xf | ||
cpu.step() | ||
await cpu.step() | ||
@@ -345,7 +458,7 @@ expect(cpu.ST).toBe(0xf) | ||
test('test cpu 31: Fx1E - ADD I, Vx', () => { | ||
cpu.load({ data: [ 0xfa1e ] }) | ||
test('31: ADD_I_VX (Fx1E) - I should be set to the value of the sum of I and register x', async () => { | ||
cpu.load({ data: [0xfa1e] }) | ||
cpu.I = 0xe | ||
cpu.registers[0xa] = 0xf | ||
cpu.step() | ||
await cpu.step() | ||
@@ -355,28 +468,46 @@ expect(cpu.I).toBe(0xe + 0xf) | ||
test('test cpu 32: Fx29 - LD F, Vx', () => { | ||
cpu.load({ data: [ 0xfa29 ] }) | ||
test('32: LD_F_VX (Fx29) - CPU should halt if register x is not equal to 0 through F', async () => { | ||
cpu.load({ data: [0xfa29] }) | ||
cpu.registers[0xa] = 16 | ||
expect(() => { | ||
cpu.step() | ||
}).toThrowError('Invalid digit.') | ||
let error | ||
try { | ||
await cpu.step() | ||
} catch (e) { | ||
error = e | ||
} | ||
cpu.load({ data: [ 0xfa29 ] }) | ||
// BSOD on halted program | ||
expect(error).toEqual(new Error('Invalid digit.')) | ||
}) | ||
test('32: LD_F_VX (Fx29) - I should be set to the location of the sprite for digit in register x', async () => { | ||
cpu.load({ data: [0xfa29] }) | ||
cpu.registers[0xa] = 0xa | ||
cpu.step() | ||
await cpu.step() | ||
expect(cpu.I).toBe(0xa * 5) | ||
}) | ||
// todo print a to console for now | ||
cpu.load({ data: [ 0xfa29, 0xdab5 ] }) | ||
cpu.registers[0xa] = 0xa | ||
cpu.step() | ||
cpu.step() | ||
test('33: LD_B_VX (Fx33) - CPU should halt if I is greater than 4093', async () => { | ||
cpu.load({ data: [0xfa33] }) | ||
cpu.registers[0xa] = 0x7b | ||
cpu.I = 4094 | ||
let error | ||
try { | ||
await cpu.step() | ||
} catch (e) { | ||
error = e | ||
} | ||
// BSOD on halted program | ||
expect(error).toEqual(new Error('Memory out of bounds.')) | ||
}) | ||
test('test cpu 33: Fx33 - LD B, Vx', () => { | ||
cpu.load({ data: [ 0xfa33 ] }) | ||
test('33: LD_B_VX (Fx33) - BCD representation of register x should be loaded into memory I, I+1, I+2 ', async () => { | ||
cpu.load({ data: [0xfa33] }) | ||
cpu.registers[0xa] = 0x7b | ||
cpu.I = 0x300 | ||
cpu.step() | ||
await cpu.step() | ||
@@ -386,14 +517,21 @@ expect(cpu.memory[0x300]).toBe(1) | ||
expect(cpu.memory[0x302]).toBe(3) | ||
}) | ||
cpu.load({ data: [ 0xfa33 ] }) | ||
cpu.registers[0xa] = 0x7b | ||
cpu.I = 4094 | ||
test('34: LD_I_VX (Fx55) - CPU should halt if memory will exceed 4095', async () => { | ||
cpu.load({ data: [0xfb55] }) | ||
cpu.I = 4085 | ||
expect(() => { | ||
cpu.step() | ||
}).toThrowError('Memory out of bounds.') | ||
let error | ||
try { | ||
await cpu.step() | ||
} catch (e) { | ||
error = e | ||
} | ||
// BSOD on halted program | ||
expect(error).toEqual(new Error('Memory out of bounds.')) | ||
}) | ||
test('test cpu 34: Fx55 - LD [I], Vx', () => { | ||
cpu.load({ data: [ 0xfb55 ] }) | ||
test('34: LD_I_VX (Fx55) - Memory should be set to register 0 through register x starting at location I', async () => { | ||
cpu.load({ data: [0xfb55] }) | ||
cpu.I = 0x400 | ||
@@ -404,3 +542,3 @@ | ||
} | ||
cpu.step() | ||
await cpu.step() | ||
@@ -410,20 +548,29 @@ for (let i = 0; i <= 0xb; i++) { | ||
} | ||
expect(cpu.memory[cpu.I + 0xc]).toBe(0) | ||
}) | ||
cpu.load({ data: [ 0xfb55 ] }) | ||
cpu.I = 4085 | ||
test('35: LD_VX_I (Fx65) - CPU should halt if memory will exceed 4095', async () => { | ||
cpu.load({ data: [0xfa65] }) | ||
cpu.I = 4086 | ||
expect(() => { | ||
cpu.step() | ||
}).toThrowError('Memory out of bounds.') | ||
let error | ||
try { | ||
await cpu.step() | ||
} catch (e) { | ||
error = e | ||
} | ||
// BSOD on halted program | ||
expect(error).toEqual(new Error('Memory out of bounds.')) | ||
}) | ||
test('test cpu 35: Fx65 - LD Vx, [I]', () => { | ||
cpu.load({ data: [ 0xfa65 ] }) | ||
test('35: LD_VX_I (Fx65) - Registers 0 through x should be set to the value of memory starting at location I', async () => { | ||
cpu.load({ data: [0xfa65] }) | ||
cpu.I = 0x400 | ||
for (let i = 0; i <= 0xa; i++) { | ||
cpu.registers[i] = i | ||
cpu.memory[cpu.I + i] = i | ||
} | ||
cpu.step() | ||
await cpu.step() | ||
@@ -434,18 +581,17 @@ for (let i = 0; i <= 0xa; i++) { | ||
expect(cpu.registers[0xb]).toBe(0) | ||
}) | ||
cpu.load({ data: [ 0xfa65 ] }) | ||
cpu.I = 4086 | ||
test('36: DW (Data Word) - CPU should halt if instruction is a data word', async () => { | ||
cpu.load({ data: [0x5154] }) | ||
expect(() => { | ||
cpu.step() | ||
}).toThrowError('Memory out of bounds.') | ||
}) | ||
let error | ||
try { | ||
await cpu.step() | ||
} catch (e) { | ||
error = e | ||
} | ||
test('test data word', () => { | ||
cpu.load({ data: [ 0x5154 ] }) | ||
expect(() => { | ||
cpu.step() | ||
}).toThrowError('Illegal instruction.') | ||
// BSOD on halted program | ||
expect(error).toEqual(new Error('Illegal instruction.')) | ||
}) | ||
}) |
@@ -1,5 +0,5 @@ | ||
describe('Instructions tests', () => { | ||
const { Disassembler } = require('../classes/Disassembler') | ||
describe('Instruction set tests', () => { | ||
const Disassembler = require('../classes/Disassembler') | ||
test('test instruction 02: 00E0 - CLS', () => { | ||
test('2: Expect disassembler to match opcode 00E0 to instruction CLS', () => { | ||
expect(Disassembler.disassemble(0x00e0).instruction).toHaveProperty('id', 'CLS') | ||
@@ -9,3 +9,3 @@ expect(Disassembler.disassemble(0x00e0).args).toHaveLength(0) | ||
test('test instruction 03: 00EE - RET', () => { | ||
test('3: Expect disassembler to match opcode 00EE to instruction RET', () => { | ||
expect(Disassembler.disassemble(0x00ee).instruction).toHaveProperty('id', 'RET') | ||
@@ -15,3 +15,3 @@ expect(Disassembler.disassemble(0x00ee).args).toHaveLength(0) | ||
test('test instruction 04: 1nnn - JP addr', () => { | ||
test('4: Expect disassembler to match opcode 1nnn to instruction JP_ADDR', () => { | ||
expect(Disassembler.disassemble(0x1333).instruction).toHaveProperty('id', 'JP_ADDR') | ||
@@ -22,3 +22,3 @@ expect(Disassembler.disassemble(0x1333).args).toHaveLength(1) | ||
test('test instruction 05: 2nnn - CALL addr', () => { | ||
test('5: Expect disassembler to match opcode 2nnn to instruction CALL_ADDR', () => { | ||
expect(Disassembler.disassemble(0x2062).instruction).toHaveProperty('id', 'CALL_ADDR') | ||
@@ -29,3 +29,3 @@ expect(Disassembler.disassemble(0x2062).args).toHaveLength(1) | ||
test('test instruction 06: 3xkk - SE Vx, byte', () => { | ||
test('6: Expect disassembler to match opcode 3xnn to instruction SE_VX_NN', () => { | ||
expect(Disassembler.disassemble(0x3abb).instruction).toHaveProperty('id', 'SE_VX_NN') | ||
@@ -37,3 +37,3 @@ expect(Disassembler.disassemble(0x3abb).args).toHaveLength(2) | ||
test('test instruction 07: 4xkk - SNE Vx, byte', () => { | ||
test('7: Expect disassembler to match opcode 4xnn to instruction SNE_VX_NN', () => { | ||
expect(Disassembler.disassemble(0x4acc).instruction).toHaveProperty('id', 'SNE_VX_NN') | ||
@@ -45,3 +45,3 @@ expect(Disassembler.disassemble(0x4acc).args).toHaveLength(2) | ||
test('test instruction 08: 5xy0 - SE Vx, Vy', () => { | ||
test('8: Expect disassembler to match opcode 5xy0 to instruction SE_VX_VY', () => { | ||
expect(Disassembler.disassemble(0x5ab0).instruction).toHaveProperty('id', 'SE_VX_VY') | ||
@@ -53,3 +53,3 @@ expect(Disassembler.disassemble(0x5ab0).args).toHaveLength(2) | ||
test('test instruction 09: 6xkk - LD Vx, byte', () => { | ||
test('9: Expect disassembler to match opcode 6xnn to instruction LD_VX_NN', () => { | ||
expect(Disassembler.disassemble(0x6abb).instruction).toHaveProperty('id', 'LD_VX_NN') | ||
@@ -61,3 +61,3 @@ expect(Disassembler.disassemble(0x6abb).args).toHaveLength(2) | ||
test('test instruction 10: 7xkk - ADD Vx, byte', () => { | ||
test('10: Expect disassembler to match opcode 7xnn to instruction ADD_VX_NN', () => { | ||
expect(Disassembler.disassemble(0x7abb).instruction).toHaveProperty('id', 'ADD_VX_NN') | ||
@@ -69,3 +69,3 @@ expect(Disassembler.disassemble(0x7abb).args).toHaveLength(2) | ||
test('test instruction 11: 8xy0 - LD Vx, Vy', () => { | ||
test('11: Expect disassembler to match opcode 8xy0 to instruction LD_VX_VY', () => { | ||
expect(Disassembler.disassemble(0x8ab0).instruction).toHaveProperty('id', 'LD_VX_VY') | ||
@@ -77,3 +77,3 @@ expect(Disassembler.disassemble(0x8ab0).args).toHaveLength(2) | ||
test('test instruction 12: 8xy1 - OR Vx, Vy', () => { | ||
test('12: Expect disassembler to match opcode 8xy1 to instruction OR_VX_VY', () => { | ||
expect(Disassembler.disassemble(0x8ab1).instruction).toHaveProperty('id', 'OR_VX_VY') | ||
@@ -85,3 +85,3 @@ expect(Disassembler.disassemble(0x8ab1).args).toHaveLength(2) | ||
test('test instruction 13: 8xy2 - AND Vx, Vy', () => { | ||
test('13: Expect disassembler to match opcode 8xy2 to instruction AND_VX_VY', () => { | ||
expect(Disassembler.disassemble(0x8ab2).instruction).toHaveProperty('id', 'AND_VX_VY') | ||
@@ -93,3 +93,3 @@ expect(Disassembler.disassemble(0x8ab2).args).toHaveLength(2) | ||
test('test instruction 14: 8xy3 - XOR Vx, Vy', () => { | ||
test('14: Expect disassembler to match opcode 8xy3 to instruction XOR_VX_VY', () => { | ||
expect(Disassembler.disassemble(0x8ab3).instruction).toHaveProperty('id', 'XOR_VX_VY') | ||
@@ -101,3 +101,3 @@ expect(Disassembler.disassemble(0x8ab3).args).toHaveLength(2) | ||
test('test instruction 15: 8xy4 - ADD Vx, Vy', () => { | ||
test('15: Expect disassembler to match opcode 8xy4 to instruction ADD_VX_VY', () => { | ||
expect(Disassembler.disassemble(0x8ab4).instruction).toHaveProperty('id', 'ADD_VX_VY') | ||
@@ -109,3 +109,3 @@ expect(Disassembler.disassemble(0x8ab4).args).toHaveLength(2) | ||
test('test instruction 16: 8xy5 - SUB Vx, Vy', () => { | ||
test('16: Expect disassembler to match opcode 8xy5 to instruction SUB_VX_VY', () => { | ||
expect(Disassembler.disassemble(0x8ab5).instruction).toHaveProperty('id', 'SUB_VX_VY') | ||
@@ -117,3 +117,3 @@ expect(Disassembler.disassemble(0x8ab5).args).toHaveLength(2) | ||
test('test instruction 17: 8xy6 - SHR Vx {, Vy}', () => { | ||
test('17: Expect disassembler to match opcode 8xy6 to instruction SHR_VX_VY', () => { | ||
expect(Disassembler.disassemble(0x8ab6).instruction).toHaveProperty('id', 'SHR_VX_VY') | ||
@@ -125,3 +125,3 @@ expect(Disassembler.disassemble(0x8ab6).args).toHaveLength(2) | ||
test('test instruction 18: 8xy7 - SUBN Vx, Vy', () => { | ||
test('18: Expect disassembler to match opcode 8xy7 to instruction SUBN_VX_VY', () => { | ||
expect(Disassembler.disassemble(0x8ab7).instruction).toHaveProperty('id', 'SUBN_VX_VY') | ||
@@ -133,3 +133,3 @@ expect(Disassembler.disassemble(0x8ab7).args).toHaveLength(2) | ||
test('test instruction 19: 8xyE - SHL Vx, {, Vy}', () => { | ||
test('19: Expect disassembler to match opcode 8xyE to instruction SHL_VX_VY', () => { | ||
expect(Disassembler.disassemble(0x8abe).instruction).toHaveProperty('id', 'SHL_VX_VY') | ||
@@ -141,3 +141,3 @@ expect(Disassembler.disassemble(0x8abe).args).toHaveLength(2) | ||
test('test instruction 20: 9xy0 - SNE Vx, Vy', () => { | ||
test('20: Expect disassembler to match opcode 9xy0 to instruction SNE_VX_VY', () => { | ||
expect(Disassembler.disassemble(0x9ab0).instruction).toHaveProperty('id', 'SNE_VX_VY') | ||
@@ -149,3 +149,3 @@ expect(Disassembler.disassemble(0x9ab0).args).toHaveLength(2) | ||
test('test instruction 21: Annn - LD I, addr', () => { | ||
test('21: Expect disassembler to match opcode Annn to instruction LD_I_ADDR', () => { | ||
expect(Disassembler.disassemble(0xa999).instruction).toHaveProperty('id', 'LD_I_ADDR') | ||
@@ -157,3 +157,3 @@ expect(Disassembler.disassemble(0xa999).args).toHaveLength(2) | ||
test('test instruction 22: Bnnn - JP V0, addr', () => { | ||
test('22: Expect disassembler to match opcode Bnnn to instruction JP_V0_ADDR', () => { | ||
expect(Disassembler.disassemble(0xb400).instruction).toHaveProperty('id', 'JP_V0_ADDR') | ||
@@ -165,3 +165,3 @@ expect(Disassembler.disassemble(0xb400).args).toHaveLength(2) | ||
test('test instruction 23: Cxkk - RND Vx, byte', () => { | ||
test('23: Expect disassembler to match opcode Cxnn to instruction RND_VX_NN', () => { | ||
expect(Disassembler.disassemble(0xcabb).instruction).toHaveProperty('id', 'RND_VX_NN') | ||
@@ -173,3 +173,3 @@ expect(Disassembler.disassemble(0xcabb).args).toHaveLength(2) | ||
test('test instruction 24: Dxyn - DRW Vx, Vy, nibble', () => { | ||
test('24: Expect disassembler to match opcode Dxyn to instruction DRW_VX_VY_N', () => { | ||
expect(Disassembler.disassemble(0xdab9).instruction).toHaveProperty('id', 'DRW_VX_VY_N') | ||
@@ -182,3 +182,3 @@ expect(Disassembler.disassemble(0xdab9).args).toHaveLength(3) | ||
test('test instruction 25: Ex9E - SKP Vx', () => { | ||
test('25: Expect disassembler to match opcode Ex9E to instruction SKP_VX', () => { | ||
expect(Disassembler.disassemble(0xea9e).instruction).toHaveProperty('id', 'SKP_VX') | ||
@@ -189,3 +189,3 @@ expect(Disassembler.disassemble(0xea9e).args).toHaveLength(1) | ||
test('test instruction 26: ExA1 - SKNP Vx', () => { | ||
test('26: Expect disassembler to match opcode ExA1 to instruction SKNP_VX', () => { | ||
expect(Disassembler.disassemble(0xeba1).instruction).toHaveProperty('id', 'SKNP_VX') | ||
@@ -196,3 +196,3 @@ expect(Disassembler.disassemble(0xeba1).args).toHaveLength(1) | ||
test('test instruction 27: Fx07 - LD Vx, DT', () => { | ||
test('27: Expect disassembler to match opcode Fx07 to instruction LD_VX_DT', () => { | ||
expect(Disassembler.disassemble(0xfa07).instruction).toHaveProperty('id', 'LD_VX_DT') | ||
@@ -204,4 +204,4 @@ expect(Disassembler.disassemble(0xfa07).args).toHaveLength(2) | ||
test('test instruction 28: Fx0A - LD Vx, K', () => { | ||
expect(Disassembler.disassemble(0xfb0a).instruction).toHaveProperty('id', 'LD_VX_K') | ||
test('28: Expect disassembler to match opcode Fx0A to instruction LD_VX_N', () => { | ||
expect(Disassembler.disassemble(0xfb0a).instruction).toHaveProperty('id', 'LD_VX_N') | ||
expect(Disassembler.disassemble(0xfb0a).args).toHaveLength(2) | ||
@@ -212,3 +212,3 @@ expect(Disassembler.disassemble(0xfb0a).args[0]).toBe(0xb) | ||
test('test instruction 29: Fx15 - LD DT, Vx', () => { | ||
test('29: Expect disassembler to match opcode Fx15 to instruction LD_DT_VX', () => { | ||
expect(Disassembler.disassemble(0xfb15).instruction).toHaveProperty('id', 'LD_DT_VX') | ||
@@ -220,3 +220,3 @@ expect(Disassembler.disassemble(0xfb15).args).toHaveLength(2) | ||
test('test instruction 30: Fx18 - LD ST, Vx', () => { | ||
test('30: Expect disassembler to match opcode Fx18 to instruction LD_ST_VX', () => { | ||
expect(Disassembler.disassemble(0xfa18).instruction).toHaveProperty('id', 'LD_ST_VX') | ||
@@ -228,3 +228,3 @@ expect(Disassembler.disassemble(0xfa18).args).toHaveLength(2) | ||
test('test instruction 31: Fx1E - ADD I, Vx', () => { | ||
test('31: Expect disassembler to match opcode Fx1E to instruction ADD_I_VX', () => { | ||
expect(Disassembler.disassemble(0xfa1e).instruction).toHaveProperty('id', 'ADD_I_VX') | ||
@@ -236,3 +236,3 @@ expect(Disassembler.disassemble(0xfa1e).args).toHaveLength(2) | ||
test('test instruction 32: Fx29 - LD F, Vx', () => { | ||
test('32: Expect disassembler to match opcode Fx29 to instruction LD_F_VX', () => { | ||
expect(Disassembler.disassemble(0xfa29).instruction).toHaveProperty('id', 'LD_F_VX') | ||
@@ -244,3 +244,3 @@ expect(Disassembler.disassemble(0xfa29).args).toHaveLength(2) | ||
test('test instruction 33: Fx33 - LD B, Vx', () => { | ||
test('33: Expect disassembler to match opcode Fx33 to instruction LD_B_VX', () => { | ||
expect(Disassembler.disassemble(0xfa33).instruction).toHaveProperty('id', 'LD_B_VX') | ||
@@ -252,3 +252,3 @@ expect(Disassembler.disassemble(0xfa33).args).toHaveLength(2) | ||
test('test instruction 34: Fx55 - LD [I], Vx', () => { | ||
test('34: Expect disassembler to match opcode Fx55 to instruction LD_I_VX', () => { | ||
expect(Disassembler.disassemble(0xfa55).instruction).toHaveProperty('id', 'LD_I_VX') | ||
@@ -260,3 +260,3 @@ expect(Disassembler.disassemble(0xfa55).args).toHaveLength(2) | ||
test('test instruction 35: Fx65 - LD Vx, [I]', () => { | ||
test('35: Expect disassembler to match opcode Fx65 to instruction LD_VX_I', () => { | ||
expect(Disassembler.disassemble(0xfa65).instruction).toHaveProperty('id', 'LD_VX_I') | ||
@@ -268,3 +268,3 @@ expect(Disassembler.disassemble(0xfa65).args).toHaveLength(2) | ||
test('test data word', () => { | ||
test('36: Expect all other opcodes to match DW (Data Word)', () => { | ||
expect(Disassembler.disassemble(0x5154).instruction).toHaveProperty('id', 'DW') | ||
@@ -271,0 +271,0 @@ expect(Disassembler.disassemble(0x5154).args).toHaveLength(1) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
215221
26
1733
1
235
1
+ Addedblessed@^0.1.81
+ Addedblessed@0.1.81(transitive)