From d174d1fb4ed4623c4896236dbbf9507f6baee666 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= Date: Thu, 18 Jul 2024 17:17:53 +0200 Subject: [PATCH] Implement basic arith opcodes and add example rom with a moving sprite (#9) * Implement DEI opcode * Add DUP2 opcode * Add DUP opcode * Add INC opcode * Add SUB opcode * Add MUL opcode * Add DIV opcode * Add ADD opcode * Implement video vector callback and add example rom --- coco-core/src/lib.rs | 172 +++++++++++++++++++++++++++++++++++ coco-core/src/opcodes.rs | 8 ++ coco-ui/index.html | 1 + coco-ui/index.js | 2 +- coco-ui/roms/sprite_move.rom | Bin 0 -> 89 bytes coco-vm/src/lib.rs | 3 +- coco-vm/src/video.rs | 17 +++- coco-vm/tests/vm_cpu_test.rs | 1 - 8 files changed, 199 insertions(+), 5 deletions(-) create mode 100644 coco-ui/roms/sprite_move.rom diff --git a/coco-core/src/lib.rs b/coco-core/src/lib.rs index b08fc7c..b0229fa 100644 --- a/coco-core/src/lib.rs +++ b/coco-core/src/lib.rs @@ -59,8 +59,16 @@ impl Cpu { let op = self.read_byte(); match op { opcodes::BRK => break, + opcodes::INC => self.op_inc(), + opcodes::DUP => self.op_dup(), + opcodes::DUP2 => self.op_dup2(), + opcodes::DEI => self.op_dei(machine), opcodes::DEO => self.op_deo(machine), opcodes::DEO2 => self.op_deo2(machine), + opcodes::ADD => self.op_add(), + opcodes::SUB => self.op_sub(), + opcodes::MUL => self.op_mul(), + opcodes::DIV => self.op_div(), opcodes::PUSH => self.op_push(), opcodes::PUSH2 => self.op_push2(), _ => {} @@ -103,6 +111,26 @@ impl Cpu { u16::from_be_bytes([hi, lo]) } + #[inline] + fn op_inc(&mut self) { + let value = self.stack.pop_byte(); + self.stack.push_byte(value.wrapping_add(1)); + } + + #[inline] + fn op_dup(&mut self) { + let value = self.stack.pop_byte(); + self.stack.push_byte(value); + self.stack.push_byte(value); + } + + #[inline] + fn op_dup2(&mut self) { + let value = self.stack.pop_short(); + self.stack.push_short(value); + self.stack.push_short(value); + } + #[inline] fn op_push(&mut self) { let value = self.read_byte(); @@ -115,6 +143,15 @@ impl Cpu { self.stack.push_short(value); } + #[inline] + fn op_dei(&mut self, machine: &mut impl Machine) { + let target = self.stack.pop_byte(); + self.stack.push_byte(self.devices[target as usize]); + + // callback for I/O + machine.dei(self, target); + } + #[inline] fn op_deo(&mut self, machine: &mut impl Machine) { let target = self.stack.pop_byte(); @@ -140,6 +177,38 @@ impl Cpu { // callback for I/0 machine.deo(self, target); } + + #[inline] + fn op_add(&mut self) { + let b = self.stack.pop_byte(); + let a = self.stack.pop_byte(); + let value = a.wrapping_add(b); + self.stack.push_byte(value); + } + + #[inline] + fn op_sub(&mut self) { + let b = self.stack.pop_byte(); + let a = self.stack.pop_byte(); + let value = a.wrapping_sub(b); + self.stack.push_byte(value); + } + + #[inline] + fn op_mul(&mut self) { + let b = self.stack.pop_byte(); + let a = self.stack.pop_byte(); + let value = a.wrapping_mul(b); + self.stack.push_byte(value); + } + + #[inline] + fn op_div(&mut self) { + let b = self.stack.pop_byte(); + let a = self.stack.pop_byte(); + let value = a.wrapping_div(b); + self.stack.push_byte(value); + } } impl fmt::Display for Cpu { @@ -201,12 +270,51 @@ mod tests { assert_eq!(pc, cpu.pc); } + #[test] + fn inc_opcode() { + let rom = rom_from(&[PUSH, 0xff, INC, BRK]); + let mut cpu = Cpu::new(&rom); + + let pc = cpu.run(0x100, &mut AnyMachine {}); + + assert_eq!(pc, 0x104); + assert_eq!(cpu.stack.len(), 1); + assert_eq!(cpu.stack.byte_at(0), 0x00); + } + + #[test] + fn dup_opcode() { + let rom = rom_from(&[PUSH, 0xab, DUP, BRK]); + let mut cpu = Cpu::new(&rom); + + let pc = cpu.run(0x100, &mut AnyMachine {}); + + assert_eq!(pc, 0x104); + assert_eq!(cpu.stack.len(), 2); + assert_eq!(cpu.stack.byte_at(0), 0xab); + assert_eq!(cpu.stack.byte_at(1), 0xab); + } + + #[test] + fn dup2_opcode() { + let rom = rom_from(&[PUSH2, 0xab, 0xcb, DUP2, BRK]); + let mut cpu = Cpu::new(&rom); + + let pc = cpu.run(0x100, &mut AnyMachine {}); + + assert_eq!(pc, 0x105); + assert_eq!(cpu.stack.len(), 4); + assert_eq!(cpu.stack.short_at(0), 0xabcb); + assert_eq!(cpu.stack.short_at(2), 0xabcb); + } + #[test] fn push_opcode() { let rom = rom_from(&[PUSH, 0xab, BRK]); let mut cpu = Cpu::new(&rom); let pc = cpu.run(0x100, &mut AnyMachine {}); + assert_eq!(pc, 0x103); assert_eq!(cpu.stack.len(), 1); assert_eq!(cpu.stack.byte_at(0), 0xab); @@ -218,17 +326,32 @@ mod tests { let mut cpu = Cpu::new(&rom); let pc = cpu.run(0x100, &mut AnyMachine {}); + assert_eq!(pc, 0x104); assert_eq!(cpu.stack.len(), 2); assert_eq!(cpu.stack.short_at(0), 0xabcd); } + #[test] + fn dei_opcode() { + let rom = rom_from(&[PUSH, 0x10, DEI, BRK]); + let mut cpu = Cpu::new(&rom); + cpu.devices[0x10] = 0xab; + + let pc = cpu.run(0x100, &mut AnyMachine {}); + + assert_eq!(pc, 0x104); + assert_eq!(cpu.stack.len(), 1); + assert_eq!(cpu.stack.byte_at(0), 0xab); + } + #[test] fn deo_opcode() { let rom = rom_from(&[PUSH, 0xab, PUSH, 0x02, DEO, BRK]); let mut cpu = Cpu::new(&rom); let pc = cpu.run(0x100, &mut AnyMachine {}); + assert_eq!(pc, 0x106); assert_eq!(cpu.stack.len(), 0); assert_eq!(cpu.devices[0x02], 0xab); @@ -241,10 +364,59 @@ mod tests { let mut cpu = Cpu::new(&rom); let pc = cpu.run(0x100, &mut AnyMachine {}); + assert_eq!(pc, 0x107); assert_eq!(cpu.stack.len(), 0); assert_eq!(cpu.devices[0x00], 0xab); assert_eq!(cpu.devices[0x01], 0xcd); // TODO: check AnyMachine.deo has been called with 0xab as target arg } + + #[test] + fn add_opcode() { + let rom = rom_from(&[PUSH, 0xab, PUSH, 0x02, ADD, BRK]); + let mut cpu = Cpu::new(&rom); + + let pc = cpu.run(0x100, &mut AnyMachine {}); + + assert_eq!(pc, 0x106); + assert_eq!(cpu.stack.len(), 1); + assert_eq!(cpu.stack.byte_at(0), 0xad); + } + + #[test] + fn sub_opcode() { + let rom = rom_from(&[PUSH, 0xab, PUSH, 0x02, SUB, BRK]); + let mut cpu = Cpu::new(&rom); + + let pc = cpu.run(0x100, &mut AnyMachine {}); + + assert_eq!(pc, 0x106); + assert_eq!(cpu.stack.len(), 1); + assert_eq!(cpu.stack.byte_at(0), 0xa9); + } + + #[test] + fn mul_opcode() { + let rom = rom_from(&[PUSH, 0x03, PUSH, 0x02, MUL, BRK]); + let mut cpu = Cpu::new(&rom); + + let pc = cpu.run(0x100, &mut AnyMachine {}); + + assert_eq!(pc, 0x106); + assert_eq!(cpu.stack.len(), 1); + assert_eq!(cpu.stack.byte_at(0), 0x06); + } + + #[test] + fn div_opcode() { + let rom = rom_from(&[PUSH, 0x07, PUSH, 0x02, DIV, BRK]); + let mut cpu = Cpu::new(&rom); + + let pc = cpu.run(0x100, &mut AnyMachine {}); + + assert_eq!(pc, 0x106); + assert_eq!(cpu.stack.len(), 1); + assert_eq!(cpu.stack.byte_at(0), 0x03); + } } diff --git a/coco-core/src/opcodes.rs b/coco-core/src/opcodes.rs index bc3ac74..64d3321 100644 --- a/coco-core/src/opcodes.rs +++ b/coco-core/src/opcodes.rs @@ -1,5 +1,13 @@ pub const BRK: u8 = 0x00; +pub const INC: u8 = 0x01; +pub const DUP: u8 = 0x06; +pub const DUP2: u8 = 0x26; +pub const DEI: u8 = 0x16; pub const DEO: u8 = 0x17; +pub const ADD: u8 = 0x18; +pub const SUB: u8 = 0x19; +pub const MUL: u8 = 0x1a; +pub const DIV: u8 = 0x1b; pub const DEO2: u8 = 0x37; pub const PUSH: u8 = 0x80; pub const PUSH2: u8 = 0xa0; diff --git a/coco-ui/index.html b/coco-ui/index.html index 625c6ec..175e9e3 100644 --- a/coco-ui/index.html +++ b/coco-ui/index.html @@ -20,6 +20,7 @@

👻-8

+
diff --git a/coco-ui/index.js b/coco-ui/index.js index 82b0e8a..3cb7830 100644 --- a/coco-ui/index.js +++ b/coco-ui/index.js @@ -51,7 +51,7 @@ async function main() { const _ = await initWasm("./vendor/coco_ui_bg.wasm"); const romSelector = document.querySelector("#coco-rom-selector"); - const defaultRom = "sprite.rom"; + const defaultRom = "sprite_move.rom"; setupRomSelector(romSelector, defaultRom); setupControls(); diff --git a/coco-ui/roms/sprite_move.rom b/coco-ui/roms/sprite_move.rom new file mode 100644 index 0000000000000000000000000000000000000000..a9bc92639e014e64e4b6a880babb9a0869bb450b GIT binary patch literal 89 zcmZ3$DAXWezJSrPLBhO2u|Y(fp+QKj0Z51g31RUD1E3IN!vQsEDM^r+3rI|$K}wu~ ip}d@dy}Z02UA(ZYzP$W DeviceOutput { - // TODO: call video vector - cpu.run(0x200, self); + cpu.run(self.video.vector(), self); self.output() } diff --git a/coco-vm/src/video.rs b/coco-vm/src/video.rs index 6fc4a95..3c7242b 100644 --- a/coco-vm/src/video.rs +++ b/coco-vm/src/video.rs @@ -11,7 +11,6 @@ impl Ports for VideoPorts { } impl VideoPorts { - #[allow(dead_code)] const VECTOR: u8 = 0x00; const X: u8 = 0x02; const Y: u8 = 0x03; @@ -32,6 +31,7 @@ pub struct VideoDevice { pub layers: [VideoBuffer; 2], is_dirty: bool, buffer: VideoBuffer, + vector: u16, } impl VideoDevice { @@ -40,9 +40,14 @@ impl VideoDevice { layers: [[0x00; VIDEO_BUFFER_LEN]; 2], is_dirty: true, buffer: [0x00; VIDEO_BUFFER_LEN], + vector: 0, } } + pub fn vector(&self) -> u16 { + self.vector + } + pub fn pixels(&mut self) -> &VideoBuffer { if std::mem::take(&mut self.is_dirty) { self.refresh_buffer(); @@ -76,6 +81,15 @@ impl VideoDevice { u16::from_be_bytes([hi, lo]) } + #[inline] + fn deo_vector(&mut self, cpu: &mut Cpu) { + let ports = cpu.device_page::(); + let hi = ports[VideoPorts::VECTOR as usize]; + let lo = ports[VideoPorts::VECTOR as usize + 1]; + + self.vector = u16::from_be_bytes([hi, lo]); + } + fn deo_pixel(&mut self, cpu: &mut Cpu) { self.is_dirty = true; @@ -162,6 +176,7 @@ impl VideoDevice { impl Device for VideoDevice { fn deo(&mut self, cpu: &mut Cpu, target: u8) { match target { + VideoPorts::VECTOR => self.deo_vector(cpu), VideoPorts::X => {} VideoPorts::Y => {} VideoPorts::PIXEL => self.deo_pixel(cpu), diff --git a/coco-vm/tests/vm_cpu_test.rs b/coco-vm/tests/vm_cpu_test.rs index 3d986d7..0215f0f 100644 --- a/coco-vm/tests/vm_cpu_test.rs +++ b/coco-vm/tests/vm_cpu_test.rs @@ -126,7 +126,6 @@ fn test_deo_sprite() { let _ = vm.on_reset(&mut cpu); let buffer = vm.pixels(); - // println!("{:?}", buffer); assert_eq!(buffer[0..8], [0x01; 8]); }