2022-02-18 16:15:37 +00:00
|
|
|
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) {
|
|
|
|
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;
|
|
|
|
}
|
2022-02-18 18:53:07 +00:00
|
|
|
fn iread(&self) -> &u16 {
|
2022-02-18 16:15:37 +00:00
|
|
|
&self.i
|
|
|
|
}
|
2022-02-18 18:53:07 +00:00
|
|
|
fn iwrite(&mut self, value: u16) {
|
2022-02-18 16:15:37 +00:00
|
|
|
self.i = value;
|
|
|
|
}
|
2022-02-18 17:21:01 +00:00
|
|
|
fn incrementpc(&mut self) {
|
|
|
|
self.pc = self.pc + 2
|
|
|
|
}
|
2022-02-18 18:53:07 +00:00
|
|
|
fn fetch(&mut self) -> u16 {
|
|
|
|
return ((self.memory[self.pc as usize] as u16) << 8 | self.memory[self.pc as usize + 1 as usize] as u16)
|
|
|
|
}
|
2022-02-18 16:15:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-18 17:21:01 +00:00
|
|
|
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 == "LD_VX_BYTE" {
|
|
|
|
cpu.regwrite(args[0] as usize, args[1] as u8);
|
|
|
|
cpu.incrementpc();
|
|
|
|
}
|
|
|
|
else { println!("Unimplemented instruction {}. Skipping.", instruction.name) }
|
|
|
|
|
|
|
|
return cpu;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-02-18 16:15:37 +00:00
|
|
|
fn main() {
|
2022-02-18 17:21:01 +00:00
|
|
|
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 };
|
2022-02-18 16:15:37 +00:00
|
|
|
println!("CPU initialised!");
|
|
|
|
torture();
|
|
|
|
println!("If you see messages above that are about opcodes, there's a bug!");
|
2022-02-18 17:21:01 +00:00
|
|
|
|
2022-02-18 18:53:07 +00:00
|
|
|
println!("Executing LD_VX_BYTE with opcode 0x6010 through memory.");
|
|
|
|
cpu.memory[0x200] = 0x60;
|
|
|
|
cpu.memory[0x201] = 0x10;
|
|
|
|
let mut opcode = cpu.fetch();
|
2022-02-18 17:21:01 +00:00
|
|
|
let mut instruction = disassemble(opcode);
|
|
|
|
cpu = execute(opcode, instruction, cpu);
|
|
|
|
|
|
|
|
println!("Register 0 is {}!", cpu.registers[0]);
|
2022-02-18 16:15:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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");
|
|
|
|
}
|