This repository has been archived on 2024-11-11. You can view files and clone it, but cannot push or open issues or pull requests.
celc8/src/main.rs
Celeste b0faea8079
RET didn't decrement the stack pointer resulting in unintended overflows.
Turns out there _was_ a reason the program was overflowing and it was my fault.
2022-02-20 18:54:15 +00:00

551 lines
14 KiB
Rust

use std::io;
use std::io::prelude::*;
use std::fs::File;
struct Cpu {
memory: Vec<u8>,
pc: u16,
registers: Vec<u8>,
i: u16,
stack: Vec<u16>,
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<u16> = 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<u8> = 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");
}