use std::io; use std::io::prelude::*; use std::fs::File; struct Cpu { memory: Vec, pc: u16, registers: Vec, i: u16, stack: Vec, sp: i8, dt: u8, st: u8, } impl Cpu { fn ramread(&self, index: usize) -> &u8 { &self.memory[index] } fn ramwrite(&mut self, index: usize, value: u8) { if index < 512 { println!("Segmentation fault in emulated CPU! Bailing out!"); panic!() } self.memory[index] = value; } fn regread(&self, index: usize) -> &u8 { &self.registers[index] } fn regwrite(&mut self, index: usize, value: u8) { self.registers[index] = value; } fn iread(&self) -> &u16 { &self.i } fn iwrite(&mut self, value: u16) { self.i = value; } fn incrementpc(&mut self) { self.pc = self.pc + 2 } fn fetch(&self) -> u16 { return ((self.memory[self.pc as usize] as u16) << 8 | self.memory[self.pc as usize + 1 as usize] as u16) } fn setpc(&mut self, prc: u16) { self.pc = prc; } fn incrementsp(&mut self) { self.sp = self.sp + 1 } fn decrementsp(&mut self) { self.sp = self.sp - 1 } fn stackpush(&mut self) { if self.stack.len() == 15 { println!("Stack overflow in emulated CPU! Bailing out!"); panic!(); } self.stack.push(self.pc); } fn stackpop(&mut self) -> u16 { if self.stack.len() == 0 { println!("Stack underflow in emulated CPU! Bailing out!"); panic!(); } let stacker = *self.stack.last().unwrap() as u16; self.stack.pop(); return stacker; } } struct Instruction { name: &'static str, mask: u16, pattern: u16, masks: &'static [u16], shifts: &'static [u8], } const ISET: [Instruction; 34] = [Instruction { name: "CLS", mask: 0xffff, pattern: 0x00e0, masks: &[], shifts: &[] }, Instruction { name: "RET", mask: 0xffff, pattern: 0x00ee, masks: &[], shifts: &[] }, Instruction { name: "JP_ADDR", mask: 0xf000, pattern: 0x1000, masks: &[0x0fff], shifts: &[0] }, Instruction { name: "CALL_ADDR", mask: 0xf000, pattern: 0x2000, masks: &[0x0fff], shifts: &[0] }, Instruction { name: "SE_VX_BYTE", mask: 0xf000, pattern: 0x3000, masks: &[0x0f00, 0x00ff], shifts: &[8, 0] }, Instruction { name: "SNE_VX_BYTE", mask: 0xf000, pattern: 0x4000, masks: &[0x0f00, 0x00ff], shifts: &[8, 0] }, Instruction { name: "SE_VX_VY", mask: 0xf00f, pattern: 0x5000, masks: &[0x0f00, 0x00f0], shifts: &[8, 4] }, Instruction { name: "LD_VX_BYTE", mask: 0xf000, pattern: 0x6000, masks: &[0x0f00, 0x00ff], shifts: &[8, 0] }, Instruction { name: "ADD_VX_BYTE", mask: 0xf000, pattern: 0x7000, masks: &[0x0f00, 0x00ff], shifts: &[8, 0] }, Instruction { name: "LD_VX_VY", mask: 0xf00f, pattern: 0x8000, masks: &[0x0f00, 0x00f0], shifts: &[8, 4] }, Instruction { name: "OR_VX_VY", mask: 0xf00f, pattern: 0x8001, masks: &[0x0f00, 0x00f0], shifts: &[8, 4] }, Instruction { name: "AND_VX_VY", mask: 0xf00f, pattern: 0x8002, masks: &[0x0f00, 0x00f0], shifts: &[8, 4] }, Instruction { name: "XOR_VX_VY", mask: 0xf00f, pattern: 0x8003, masks: &[0x0f00, 0x00f0], shifts: &[8, 4] }, Instruction { name: "ADD_VX_VY", mask: 0xf00f, pattern: 0x8004, masks: &[0x0f00, 0x00f0], shifts: &[8, 4] }, Instruction { name: "SUB_VX_VY", mask: 0xf00f, pattern: 0x8005, masks: &[0x0f00, 0x00f0], shifts: &[8, 4] }, Instruction { name: "SHR_VX_VY", mask: 0xf00f, pattern: 0x8006, masks: &[0x0f00, 0x00f0], shifts: &[8, 4] }, Instruction { name: "SUBN_VX_VY", mask: 0xf00f, pattern: 0x8007, masks: &[0x0f00, 0x00f0], shifts: &[8, 4] }, Instruction { name: "SHL_VX_VY", mask: 0xf00f, pattern: 0x800e, masks: &[0x0f00, 0x00f0], shifts: &[8, 4] }, Instruction { name: "SNE_VX_VY", mask: 0xf00f, pattern: 0x9000, masks: &[0x0f00, 0x00f0], shifts: &[8, 4] }, Instruction { name: "LD_I_ADDR", mask: 0xf000, pattern: 0xa000, masks: &[0x0fff], shifts: &[0] }, Instruction { name: "JP_V0_ADDR", mask: 0xf000, pattern: 0xb000, masks: &[0x0fff], shifts: &[0] }, Instruction { name: "RND_VX_BYTE", mask: 0xf000, pattern: 0xc000, masks: &[0x0f00, 0x00ff], shifts: &[8, 0] }, Instruction { name: "DRW_VX_VY_NIBBLE", mask: 0xf000, pattern: 0xd000, masks: &[0x0f00, 0x00f0, 0x000f], shifts: &[8, 4, 0] }, Instruction { name: "SKP_VX", mask: 0xf0ff, pattern: 0xe09e, masks: &[0x0f00], shifts: &[8] }, Instruction { name: "SKNP_VX", mask: 0xf0ff, pattern: 0xe0a1, masks: &[0x0f00], shifts: &[8] }, Instruction { name: "LD_VX_DT", mask: 0xf0ff, pattern: 0xf007, masks: &[0x0f00], shifts: &[8] }, Instruction { name: "LD_VX_K", mask: 0xf0ff, pattern: 0xf00a, masks: &[0x0f00], shifts: &[8] }, Instruction { name: "LD_DT_VX", mask: 0xf0ff, pattern: 0xf015, masks: &[0x0f00], shifts: &[8] }, Instruction { name: "LD_ST_VX", mask: 0xf0ff, pattern: 0xf018, masks: &[0x0f00], shifts: &[8] }, Instruction { name: "ADD_I_VX", mask: 0xf0ff, pattern: 0xf01e, masks: &[0x0f00], shifts: &[8] }, Instruction { name: "LD_F_VX", mask: 0xf0ff, pattern: 0xf029, masks: &[0x0f00], shifts: &[8] }, Instruction { name: "LD_B_VX", mask: 0xf0ff, pattern: 0xf033, masks: &[0x0f00], shifts: &[8] }, Instruction { name: "LD_IB_VX", mask: 0xf0ff, pattern: 0xf055, masks: &[0x0f00], shifts: &[8] }, Instruction { name: "LD_VX_IB", mask: 0xf0ff, pattern: 0xf065, masks: &[0x0f00], shifts: &[8] } ]; fn disassemble(opcode: u16) -> Instruction { let mut instruction; for instr in ISET { if (opcode & instr.mask) == instr.pattern { instruction = instr; return instruction; } } println!("Invalid opcode 0x{}! Bailing out!", format!("{:x}", opcode)); panic!(); } fn decodetest(opcode: u16, expected: &str) { let instruction = disassemble(opcode); if instruction.name != expected { println!("Opcode 0x{} disassembled as instruction {}. Expected {}.", format!("{:x}", opcode), instruction.name, expected); } } fn execute(opcode: u16, instruction: Instruction, mut cpu: Cpu) -> Cpu { let mut args: Vec = vec![]; let mut i = 0; if instruction.masks.len() != 0 { for mask in instruction.masks { let shift = instruction.shifts[i]; args.push((opcode & mask) >> shift); i = i + 1; } } if instruction.name == "RET" { let returnaddress = cpu.stackpop(); cpu.decrementsp(); cpu.setpc(returnaddress); } else if instruction.name == "JP_ADDR" { cpu.setpc(args[0] as u16); } else if instruction.name == "CALL_ADDR" { cpu.incrementsp(); cpu.stackpush(); cpu.setpc(args[0] as u16); } else if instruction.name == "SE_VX_BYTE" { let register = args[0] as usize; let byte = args[1] as u8; if cpu.regread(register) == &byte { cpu.incrementpc(); cpu.incrementpc(); } else { cpu.incrementpc(); } } else if instruction.name == "SNE_VX_BYTE" { let register = args[0] as usize; let byte = args[1] as u8; if cpu.regread(register) != &byte { cpu.incrementpc(); cpu.incrementpc(); } else { cpu.incrementpc(); } } else if instruction.name == "SE_VX_VY" { let register1 = args[0] as usize; let register2 = args[1] as usize; if cpu.regread(register1) == cpu.regread(register2) { cpu.incrementpc(); cpu.incrementpc(); } else { cpu.incrementpc(); } } else if instruction.name == "LD_VX_BYTE" { cpu.regwrite(args[0] as usize, args[1] as u8); cpu.incrementpc(); } else if instruction.name == "ADD_VX_BYTE" { cpu.regwrite(args[0] as usize, (*cpu.regread(args[0] as usize) + args[1] as u8)); cpu.incrementpc(); } else if instruction.name == "LD_VX_VY" { cpu.regwrite(args[0] as usize, *cpu.regread(args[1] as usize)); cpu.incrementpc(); } else if instruction.name == "OR_VX_VY" { let result = *cpu.regread(args[0] as usize) | *cpu.regread(args[1] as usize); cpu.regwrite(args[0] as usize, result as u8); cpu.incrementpc(); } else if instruction.name == "AND_VX_VY" { let result = *cpu.regread(args[0] as usize) & *cpu.regread(args[1] as usize); cpu.regwrite(args[0] as usize, result as u8); cpu.incrementpc(); } else if instruction.name == "XOR_VX_VY" { let result = *cpu.regread(args[0] as usize) ^ *cpu.regread(args[1] as usize); cpu.regwrite(args[0] as usize, result as u8); cpu.incrementpc(); } else if instruction.name == "ADD_VX_VY" { let reg1 = *cpu.regread(args[0] as usize) as u16; let reg2 = *cpu.regread(args[1] as usize) as u16; let bigresult = reg1 + reg2; if bigresult > 255 { // According to Cowgod, we can only keep the // lowest 8 bits and Rust does this fine when // you do bigresult as u8 so we are all good! cpu.regwrite(0xf as usize, 1 as u8); cpu.regwrite(args[0] as usize, bigresult as u8); } else { cpu.regwrite(0xf as usize, 0 as u8); cpu.regwrite(args[0] as usize, bigresult as u8); } cpu.incrementpc(); } else if instruction.name == "SUB_VX_VY" { let reg1 = *cpu.regread(args[0] as usize) as u16; let reg2 = *cpu.regread(args[1] as usize) as u16; // Rust doesn't like it when you do arithmetic overflow so // I'm gonna fudge it :D if reg1 < reg2 { cpu.regwrite(args[0] as usize, 0); } else { if reg1 > reg2 { cpu.regwrite(0xf as usize, 1 as u8); } else { cpu.regwrite(0xf as usize, 0 as u8); } cpu.regwrite(args[0] as usize, (reg1 - reg2) as u8); } cpu.incrementpc(); } else if instruction.name == "SHR_VX_VY" { let reg1 = *cpu.regread(args[0] as usize) as u16; if reg1 % 2 != 0 { cpu.regwrite(0xf as usize, 1 as u8); cpu.regwrite(args[0] as usize, ((reg1 - 2) / 2) as u8); } else { cpu.regwrite(0xf as usize, 0 as u8); cpu.regwrite(args[0] as usize, (reg1 / 2) as u8); } cpu.incrementpc(); } else if instruction.name == "SUBN_VX_VY" { let reg1 = *cpu.regread(args[0] as usize) as u16; let reg2 = *cpu.regread(args[1] as usize) as u16; if reg1 > reg2 { cpu.regwrite(args[0] as usize, 0); } else { if reg1 < reg2 { cpu.regwrite(0xf as usize, 1 as u8); } else { cpu.regwrite(0xf as usize, 0 as u8); } cpu.regwrite(args[0] as usize, (reg2 - reg1) as u8); } cpu.incrementpc(); } else if instruction.name == "SHL_VX_VY" { let reg1 = *cpu.regread(args[0] as usize) as u16; if reg1 % 2 != 0 { cpu.regwrite(0xf as usize, 1 as u8); cpu.regwrite(args[0] as usize, (reg1 * 2) as u8); } else { cpu.regwrite(0xf as usize, 0 as u8); cpu.regwrite(args[0] as usize, (reg1 * 2) as u8); } cpu.incrementpc(); } else if instruction.name == "SNE_VX_VY" { let register1 = args[0] as usize; let register2 = args[1] as usize; if cpu.regread(register1) != cpu.regread(register2) { cpu.incrementpc(); cpu.incrementpc(); } else { cpu.incrementpc(); } } else if instruction.name == "JP_V0_ADDR" { cpu.setpc(*cpu.regread(0) as u16 + args[0] as u16); } else if instruction.name == "LD_I_ADDR" { cpu.iwrite(args[0]); cpu.incrementpc(); } else if instruction.name == "LD_VX_DT" { cpu.regwrite(args[0] as usize, cpu.dt); cpu.incrementpc(); } else if instruction.name == "LD_DT_VX" { cpu.dt = *cpu.regread(args[0] as usize); cpu.incrementpc(); } else if instruction.name == "LD_ST_VX" { cpu.st = *cpu.regread(args[0] as usize); cpu.incrementpc(); } else if instruction.name == "ADD_I_VX" { cpu.iwrite(*cpu.iread() + (*cpu.regread(args[0] as usize)) as u16); cpu.incrementpc(); } else if instruction.name == "LD_B_VX" { let i = *cpu.iread(); let a = args[0] / 100; let b = (args[0] / 10) - (a * 10); let c = args[0] - (a * 100) - (b * 10); cpu.ramwrite(i as usize, a as u8); cpu.ramwrite(i as usize + 1 as usize, b as u8); cpu.ramwrite(i as usize + 2 as usize, c as u8); cpu.incrementpc(); } else if instruction.name == "LD_IB_VX" { let i = *cpu.iread(); let mut x = 0; let y = args[0]; cpu.ramwrite(i as usize, *cpu.regread(0)); while x != y { x = x + 1; cpu.ramwrite(i as usize + x as usize, *cpu.regread(x as usize)); } cpu.incrementpc(); } else if instruction.name == "LD_VX_IB" { let i = *cpu.iread(); let mut x = 0; let y = args[0]; cpu.regwrite(0, *cpu.ramread(i as usize)); while x != y { x = x + 1; cpu.regwrite(x as usize, *cpu.ramread(i as usize + x as usize)); } cpu.incrementpc(); } else { println!("Unimplemented instruction {}. Skipping.", instruction.name); cpu.incrementpc(); } return cpu; } fn main() -> io::Result<()> { let mut f = File::open("program.ch8")?; let mut buffer: Vec = vec![]; println!("Buffer initialised!"); f.read_to_end(&mut buffer)?; let mut cpu = Cpu { memory: vec![0; 4096], pc: 0x200, registers: vec![0; 16], i: 0, stack: vec![0; 16], sp: -1, dt: 0, st: 0 }; println!("CPU initialised!"); let mut i = 0x200; for byte in buffer { cpu.ramwrite(i, byte); i = i + 1; } println!("File loaded to memory!"); torture(); println!("If you see messages above that are about opcodes, there's a bug!"); loop { let mut opcode = cpu.fetch(); let mut instruction = disassemble(opcode); cpu = execute(opcode, instruction, cpu); } Ok(()) } fn torture() { decodetest(0x00e0, "CLS"); decodetest(0x00ee, "RET"); decodetest(0x1123, "JP_ADDR"); decodetest(0x2123, "CALL_ADDR"); decodetest(0x3123, "SE_VX_BYTE"); decodetest(0x4123, "SNE_VX_BYTE"); decodetest(0x5120, "SE_VX_VY"); decodetest(0x6123, "LD_VX_BYTE"); decodetest(0x7123, "ADD_VX_BYTE"); decodetest(0x8120, "LD_VX_VY"); decodetest(0x8121, "OR_VX_VY"); decodetest(0x8122, "AND_VX_VY"); decodetest(0x8123, "XOR_VX_VY"); decodetest(0x8124, "ADD_VX_VY"); decodetest(0x8125, "SUB_VX_VY"); decodetest(0x8126, "SHR_VX_VY"); decodetest(0x8127, "SUBN_VX_VY"); decodetest(0x812e, "SHL_VX_VY"); decodetest(0x9120, "SNE_VX_VY"); decodetest(0xa123, "LD_I_ADDR"); decodetest(0xb123, "JP_V0_ADDR"); decodetest(0xc123, "RND_VX_BYTE"); decodetest(0xd123, "DRW_VX_VY_NIBBLE"); decodetest(0xe19e, "SKP_VX"); decodetest(0xe1a1, "SKNP_VX"); decodetest(0xf107, "LD_VX_DT"); decodetest(0xf10a, "LD_VX_K"); decodetest(0xf115, "LD_DT_VX"); decodetest(0xf118, "LD_ST_VX"); decodetest(0xf11e, "ADD_I_VX"); decodetest(0xf129, "LD_F_VX"); decodetest(0xf133, "LD_B_VX"); decodetest(0xf155, "LD_IB_VX"); decodetest(0xf165, "LD_VX_IB"); }