Chipvuelto8/src/cpu.ts

464 lines
12 KiB
TypeScript

import RNG from './RNG'
import Chip8Audio from './audio';
import Keyboard from './keyboard';
const SCREEN_WIDTH = 64;
const SCREEN_HEIGHT = 32;
const MEMORY_SIZE = 4096;
const STACK_SIZE = 16;
const REGISTERS = 16;
class CPU {
pc: number; // 16b
indexReg: number; // 16b
regs: Uint8Array;
delayTimer: number; // 8b
soundTimer: number; // 8b
stackPointer: number; // 16b
stack: Uint16Array; // 16b
memory: Uint8Array;
displayMemory: Uint8Array;
keyboard: Keyboard;
audio: Chip8Audio;
rng: RNG;
constructor(keyboard: Keyboard, audio: Chip8Audio) {
this.memory = new Uint8Array(MEMORY_SIZE);
this.displayMemory = new Uint8Array(SCREEN_WIDTH*SCREEN_HEIGHT);
this.pc = 0x200;
this.indexReg = 0;
this.stackPointer = 0;
this.stack = new Uint16Array(STACK_SIZE);
this.delayTimer = 0;
this.soundTimer = 0;
this.regs = new Uint8Array(REGISTERS);
this.keyboard = keyboard;
this.audio = audio;
this.rng = new RNG(0);
this.clearMemory();
this.clearDisplay();
this.clearRegisters();
// 0x000-0x1FF - Chip 8 interpreter
// 0x050-0x0A0 - 4x5 pixel font set (0-F)
// 0x200-0xFFF - Program ROM and work RAM
this.setupFont();
}
clearRegisters(): void {
this.regs.fill(0);
this.pc = 0x200; // Start of the program on normal programs
this.indexReg = 0;
this.stackPointer = 0;
this.delayTimer = 0;
this.soundTimer = 0;
this.rng = new RNG(0);
}
clearMemory(): void {
this.memory.fill(0);
}
clearDisplay(): void {
this.displayMemory.fill(0);
}
private setupFont(): void {
// Using address from 050 - 09F
let addressFont = 0x050;
// Every character has 5 Bytes
const characters = [
0xF0, 0x90, 0x90, 0x90, 0xF0, // 0
0x20, 0x60, 0x20, 0x20, 0x70, // 1
0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2
0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3
0x90, 0x90, 0xF0, 0x10, 0x10, // 4
0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5
0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6
0xF0, 0x10, 0x20, 0x40, 0x40, // 7
0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8
0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9
0xF0, 0x90, 0xF0, 0x90, 0x90, // A
0xE0, 0x90, 0xE0, 0x90, 0xE0, // B
0xF0, 0x80, 0x80, 0x80, 0xF0, // C
0xE0, 0x90, 0x90, 0x90, 0xE0, // D
0xF0, 0x80, 0xF0, 0x80, 0xF0, // E
0xF0, 0x80, 0xF0, 0x80, 0x80 // F
];
this.memory.set(characters, addressFont);
}
public loadRom(rom: Uint8Array): void {
// Loads a ROM into starting PC address
for(let i = 0; i < rom.length; i++) {
this.memory[this.pc+i] = rom[i];
}
this.memory.set(rom, this.pc);
}
public getCPUStatus() : string{
let status = "";
status += "PC: " + this.pc + "\n";
status += "I: " + this.indexReg.toString(16) + "\n";
for(let i = 0; i < REGISTERS; i++) {
status += "V" + i + ": " + this.regs[i].toString(16).toUpperCase().padStart(2, '0') + "\n";
}
status += "SP: " + this.stackPointer.toString(16) + "\n";
for(let i = 0; i < this.stackPointer; i++) {
status += "ST" + i + ": " + this.stack[i] + "\n";
}
status += "\n";
let firstOpcode: number = this.memory[this.pc];
let secondOpcode: number = this.memory[this.pc + 1];
let opcode: number = firstOpcode << 8 | secondOpcode;
status += "OPCODE FOR PC: " + opcode.toString(16).toUpperCase().padStart(4, '0') + "\n";
return status;
}
private incrementPC(): void {
this.pc += 2;
}
public cycle(): void {
// Fetch opcode
let firstOpcode: number = this.memory[this.pc];
let secondOpcode: number = this.memory[this.pc + 1];
let opcode: number = firstOpcode << 8 | secondOpcode;
// Decode opcode
let nib1 = firstOpcode >> 4; // X000
let nib2 = firstOpcode & 0x0F; // 0X00
let nib3 = secondOpcode >> 4; // 00X0
let nib4 = secondOpcode & 0x0F; // 000X
switch(nib1) {
case 0x0:
if(opcode == 0x00E0) {
// 00E0
// Clear the screen
this.clearDisplay();
this.incrementPC();
}else if(opcode == 0x00EE) {
// 00EE
// Returns from a subroutine
this.stackPointer--;
if(this.stackPointer < 0) {
throw new Error("Stack pointer cant go to -1");
}
this.pc = this.stack[this.stackPointer];
}
break;
case 0x1: {
// 1NNN
// goto NNN;
let address = opcode & 0x0FFF;
this.pc = address;
}
break;
case 0x2: {
// 2NNN
// Calls subroutine at NNN
this.stack[this.stackPointer] = this.pc + 2; // Store PC at stack
this.stackPointer++; // Increment it
let address = opcode & 0x0FFF;
this.pc = address; // Go to that address to execute code
}
break;
case 0x3: {
// 3XNN
// Skips the next instruction if VX equals NN
if(this.regs[nib2] == secondOpcode){
this.incrementPC();
}
this.incrementPC();
}
break;
case 0x4: {
// 4XNN
// Skips the next instruction if VX NOT equals NN
if(this.regs[nib2] != secondOpcode){
this.incrementPC();
}
this.incrementPC();
}
break;
case 0x5: {
// 5XY0
// Skips the next instruction if VX == VY
if(this.regs[nib2] == this.regs[nib3]) {
this.incrementPC();
}
this.incrementPC();
}
break;
case 0x6: {
// 6XNN
// Sets VX to NN
this.regs[nib2] = secondOpcode;
this.incrementPC();
}
break;
case 0x7: {
// 7XNN
// Adds NN to VX
this.regs[nib2] += secondOpcode;
this.incrementPC();
}
break;
case 0x8: {
if(nib4 == 0x0) {
// 8XY0
// Vx |= Vy
this.regs[nib2] = this.regs[nib3];
this.incrementPC();
}else if(nib4 == 0x1) {
// 8XY1
// Vx |= Vy
this.regs[nib2] |= this.regs[nib3];
this.incrementPC();
}else if(nib4 == 0x2) {
// 8XY2
// Vx &= Vy
this.regs[nib2] &= this.regs[nib3];
this.incrementPC();
}else if(nib4 == 0x3) {
// 8XY3
// Vx ^= Vy
this.regs[nib2] ^= this.regs[nib3];
this.incrementPC();
}else if(nib4 == 0x4) {
// 8XY4
// Vx += Vy, Set VF to carry
let sum = this.regs[nib2] + this.regs[nib3];
this.regs[nib2] = sum & 0xFF; // Get only the first byte
this.regs[0xF] = sum > 255 ? 1 : 0; // Set carry flag
this.incrementPC();
}else if(nib4 == 0x5) {
// 8XY5
// Vx -= Vy
let subs = 0;
if(this.regs[nib2] >= this.regs[nib3]) {
subs = this.regs[nib2] - this.regs[nib3];
}else{
subs = this.regs[nib2] - this.regs[nib3];
//subs = 0xFF - subs;
}
this.regs[0xF] = this.regs[nib2] >= this.regs[nib3] ? 1 : 0;
this.regs[nib2] = subs & 0xFF;
this.incrementPC();
}else if(nib4 == 0x6) {
// 8XY6
// Vx >>= 1
// Stores the least significant bit of VX prior to the shift in VF
// Then. Shift Vx by 1 to the right.
this.regs[0xF] = this.regs[nib2] & 0x1; // Get the least significat bit
this.regs[nib2] = (this.regs[nib2] >> 1) & 0xFF;
this.incrementPC();
}else if(nib4 == 0x7) {
// 8XY7
// Vx = Vy - Vx
let subs = this.regs[nib3] - this.regs[nib2];
this.regs[nib2] = subs;
this.regs[0xF] = this.regs[nib3] > this.regs[nib2] ? 1 : 0;
this.incrementPC();
}else if(nib4 == 0xE) {
// 8XYE
// Vx <<= 1
// Set VF to 1 if the most significant bit of VX.
// Then shift to the left Vx
this.regs[0xF] = (this.regs[nib2]) >> 7; // Get msb
this.regs[nib2] = (this.regs[nib2] << 1) & 0xFF;
this.incrementPC();
}
}
break;
case 0x9: {
// 9XY0
// Skips the next instruction if VX != VY
if(this.regs[nib2] != this.regs[nib3]) {
this.incrementPC();
}
this.incrementPC();
}
break;
case 0xA: {
// ANNN
// Sets I to the address NNN
let address = opcode & 0x0FFF;
this.indexReg = address;
this.incrementPC();
}
break;
case 0xB:{
// BNNN
// Jumps to the address NNN plus V0
let address = opcode & 0x0FFF;
this.pc = this.regs[0] + address;
}
break;
case 0xC: {
// CXNN
// Vx = rand() & NN
this.regs[nib2] = this.rng.next255() & secondOpcode;
this.incrementPC();
}
break;
case 0xD: {
// DXYN
// draw(Vx, Vy, N)
this.regs[0xF] = 0; // Reset register VF
let regX = this.regs[nib2];
let regY = this.regs[nib3];
for(let y = 0; y < nib4; y++) {
// A sprite is always 8 bits horizontal
let pixel = this.memory[this.indexReg + y]; // Fetch pixel from memory starting at I reg
for(let x = 0; x < 8; x++) {
const MSB = 0x80; // 0b10000000
if((pixel & (MSB >> x)) != 0) {
let mem = x + regX + ((y + regY) * SCREEN_WIDTH);
if(this.displayMemory[mem] == 1) {
this.regs[0xF] = 1;
}
this.displayMemory[mem] ^= 1;
}
}
}
this.incrementPC();
}
break;
case 0xE: {
if(secondOpcode == 0x9E) {
// EX9E
// if (key() == Vx)
// Skips the next instruction if the key stored at VX is pressed
if(this.keyboard.keys[this.regs[nib2]] == 1) {
this.incrementPC();
}
}else if(secondOpcode == 0xA1) {
// EXA1
// if (key() != Vx)
// Skips the next instruction if the key stored at VX is NOT pressed
if(this.keyboard.keys[this.regs[nib2]] != 1) {
this.incrementPC();
}
}
this.incrementPC();
}
break;
case 0xF: {
if(secondOpcode == 0x07) {
// FX07
// Vx = delayTimer
this.regs[nib2] = this.delayTimer;
this.incrementPC();
}else if(secondOpcode == 0x0A) {
// FX0A
// A key pressed is awaited and then stored at VX.
// [BLOCKING OPERATION], all instructions are halted until next key event
// Vx = get_key()
let keyPressed = false;
for(let i = 0; i < this.keyboard.keys.length; i++) {
if(this.keyboard.keys[i] != 0) {
keyPressed = true;
this.regs[nib2] = i;
break;
}
}
if(!keyPressed)
return; // NOT INCREMENT PC SO WE ARE STUCK IN THIS WAITING (also don't decrement timers???)
this.incrementPC();
}else if(secondOpcode == 0x15) {
// FX15
// delayTimer = Vx
this.delayTimer = this.regs[nib2];
this.incrementPC();
}else if(secondOpcode == 0x18) {
// FX18
// soundTimer = Vx
this.soundTimer = this.regs[nib2];
this.incrementPC();
}else if(secondOpcode == 0x1E) {
// FX1E
// I += Vx
// Adds VX to I. VF is not affected
this.indexReg += this.regs[nib2];
this.incrementPC();
}else if(secondOpcode == 0x29) {
// FX29
// I = sprite_addr[Vx]
// Sets I to the location of the sprite for the character in VX
this.indexReg = this.regs[nib2] * 0x5; // ??
this.incrementPC();
}else if(secondOpcode == 0x33) {
// FX33
// set_BCD(Vx)
// *(I+0) = BCD(3);
// *(I+1) = BCD(2);
// *(I+2) = BCD(1);
// Stores the binary-coded decimal representation of Vx, with the hundreds digit memory at location in I,
//
this.memory[this.indexReg] = this.regs[nib2] / 100;
this.memory[this.indexReg + 1] = (this.regs[nib2] / 10) % 10;
this.memory[this.indexReg + 2] = this.regs[nib2] % 10;
this.incrementPC();
}else if(secondOpcode == 0x55) {
// FX55
// reg_dump(Vx, &I)
// Stores from V0 to VX in memory starting at address I.
for(let i = 0; i <= nib2; i++) {
this.memory[this.indexReg + i] = this.regs[i];
}
this.incrementPC();
}else if(secondOpcode == 0x65) {
// FX65
// reg_load(Vx, &I)
// Fills fomr V0 to VX with values from memory staring at address I.
for(let i = 0; i <= nib2; i++) {
this.regs[i] = this.memory[this.indexReg + i];
}
this.incrementPC();
}
}
break;
}
// Timers
if(this.delayTimer > 0) {
this.delayTimer--;
}
if(this.soundTimer > 0) {
this.audio.startBeep();
this.soundTimer--;
}else{
this.audio.stopBeep();
}
}
}
export default CPU;