Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement basic arith opcodes and add example rom with a moving sprite #9

Merged
merged 9 commits into from
Jul 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 172 additions & 0 deletions coco-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
_ => {}
Expand Down Expand Up @@ -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();
Expand All @@ -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();
Expand All @@ -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 {
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
}
}
8 changes: 8 additions & 0 deletions coco-core/src/opcodes.rs
Original file line number Diff line number Diff line change
@@ -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;
1 change: 1 addition & 0 deletions coco-ui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ <h1>👻-8</h1>
<option value="put_pixel.rom">Pixel (single)</option>
<option value="pixel_fill.rom">Pixel (fill)</option>
<option value="sprite.rom">Sprite</option>
<option value="sprite_move.rom">Moving Sprite</option>
</select>

<div>
Expand Down
2 changes: 1 addition & 1 deletion coco-ui/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
Binary file added coco-ui/roms/sprite_move.rom
Binary file not shown.
3 changes: 1 addition & 2 deletions coco-vm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@ impl Vm {
}

pub fn on_video(&mut self, cpu: &mut Cpu) -> DeviceOutput {
// TODO: call video vector
cpu.run(0x200, self);
cpu.run(self.video.vector(), self);
self.output()
}

Expand Down
17 changes: 16 additions & 1 deletion coco-vm/src/video.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -32,6 +31,7 @@ pub struct VideoDevice {
pub layers: [VideoBuffer; 2],
is_dirty: bool,
buffer: VideoBuffer,
vector: u16,
}

impl VideoDevice {
Expand All @@ -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();
Expand Down Expand Up @@ -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::<VideoPorts>();
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;

Expand Down Expand Up @@ -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),
Expand Down
1 change: 0 additions & 1 deletion coco-vm/tests/vm_cpu_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
}