Skip to content

Commit

Permalink
System Device: Debug DEO (#4)
Browse files Browse the repository at this point in the history
* Refactor VideoDevice into its own module

* Implement showing debug info for the CPU

* Refactor stack and opcodes into their own modules

* Implement push opcode

* Add PUSH and DEO opcodes and implement system/debug deo
  • Loading branch information
belen-albeza authored Jul 17, 2024
1 parent cb899b7 commit 9ddc0fe
Show file tree
Hide file tree
Showing 11 changed files with 302 additions and 80 deletions.
129 changes: 92 additions & 37 deletions coco-core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,35 +1,32 @@
#![cfg_attr(not(test), no_std)]
#![forbid(unsafe_code)]

use core::cmp;
use core::fmt;

mod stack;
use stack::Stack;

pub mod opcodes;

/// The trait to implement for COCO virtual machines.
pub trait Machine {
fn deo(&mut self, cpu: &mut Cpu, target: u8) -> bool;
fn deo(&mut self, cpu: &mut Cpu, target: u8);
fn dei(&mut self, cpu: &mut Cpu, target: u8);
}

#[derive(Debug)]
struct Stack {
data: [u8; 0x100],
index: u8,
}

impl Stack {
pub fn new() -> Self {
Self {
data: [0_u8; 0x100],
index: 0,
}
}
}
mod opcodes {
pub const BRK: u8 = 0x00;
/// The trait to implement a COCO device's ports
pub trait Ports {
const BASE: u8;
}

/// COCO-8 CPU.
#[derive(Debug)]
pub struct Cpu {
/// Main memory (64 Kb)
ram: [u8; 0x10000],
/// Device page (256 bytes)
devices: [u8; 0x100],
/// Main, working stack (256 bytes)
stack: Stack,
/// Return stack (256 bytes)
Expand All @@ -40,9 +37,14 @@ pub struct Cpu {

impl Cpu {
/// Returns a new CPU with their memory, stacks and PC reset to zero.
pub fn new(ram: [u8; 0x10000]) -> Self {
pub fn new(rom: &[u8]) -> Self {
// load rom at address 0x100
let mut ram = [0; 0x10000];
ram[0x100..cmp::min(0x100 + rom.len(), 0x10000)].copy_from_slice(rom);

Self {
ram: ram,
ram,
devices: [0; 0x100],
stack: Stack::new(),
ret_stack: Stack::new(),
pc: 0,
Expand All @@ -51,21 +53,27 @@ impl Cpu {

/// Runs the code starting the PC in the given address until
/// it finds a BRK opcode
pub fn run<M: Machine>(&mut self, addr: u16, _: &mut M) -> u16 {
pub fn run(&mut self, addr: u16, machine: &mut impl Machine) -> u16 {
self.pc = addr;
loop {
let op = self.read_byte();
match op {
opcodes::BRK => {
break;
}
opcodes::BRK => break,
opcodes::PUSH => self.op_push(),
opcodes::DEO => self.op_deo(machine),
_ => {}
}
}

self.pc
}

/// Returns the requested device page
#[inline]
pub fn device_page<D: Ports>(&mut self) -> &mut [u8] {
&mut self.devices[(D::BASE as usize)..(D::BASE as usize + 0x10)]
}

/// Returns the current value for the program counter (PC)
pub fn pc(&self) -> u16 {
self.pc
Expand All @@ -77,58 +85,105 @@ impl Cpu {
self.pc = self.pc.wrapping_add(1);
res
}

#[inline]
fn op_push(&mut self) {
let value = self.read_byte();
self.stack.push_byte(value);
}

#[inline]
fn op_deo(&mut self, machine: &mut impl Machine) {
let target = self.stack.pop_byte();

// write value to device port
let value = self.stack.pop_byte();
self.devices[target as usize] = value;

// callback for I/O
machine.deo(self, target);
}
}

impl fmt::Display for Cpu {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "WRK: [{}]", self.stack)?;
write!(f, "RET: [{}]", self.ret_stack)
}
}

#[cfg(test)]
mod tests {
use super::opcodes::*;
use super::*;

pub struct AnyMachine {}
impl Machine for AnyMachine {
fn deo(&mut self, _: &mut Cpu, _: u8) -> bool {
false
}
fn deo(&mut self, _: &mut Cpu, _: u8) {}
fn dei(&mut self, _: &mut Cpu, _: u8) {}
}

fn zeroed_memory() -> [u8; 0x10000] {
[0_u8; 0x10000]
fn zeroed_memory() -> [u8; 0x10000 - 0x100] {
[0_u8; 0x10000 - 0x100]
}

fn rom_from(rom: &[u8]) -> [u8; 0x10000] {
let mut res = [0_u8; 0x10000];
fn rom_from(rom: &[u8]) -> [u8; 0x10000 - 0x100] {
let mut res = [0_u8; 0x10000 - 0x100];
res[0..rom.len()].copy_from_slice(rom);
res
}

#[test]
fn creates_cpu() {
let memory = zeroed_memory();
let cpu = Cpu::new(memory);
let cpu = Cpu::new(&memory);

assert_eq!(cpu.pc, 0);
}

#[test]
pub fn runs_until_break() {
let rom = rom_from(&[0x01, 0x01, 0x00]);
let mut cpu = Cpu::new(rom);
let rom = rom_from(&[0x01, 0x01, BRK]);
let mut cpu = Cpu::new(&rom);

let pc = cpu.run(0x00, &mut AnyMachine {});
let pc = cpu.run(0x100, &mut AnyMachine {});

assert_eq!(pc, 0x03);
assert_eq!(pc, 0x103);
assert_eq!(pc, cpu.pc);
}

#[test]
pub fn run_wraps_pc_at_the_end_of_ram() {
let mut rom = zeroed_memory();
rom[0xffff] = 0x01;
let mut cpu = Cpu::new(rom);
rom[rom.len() - 1] = 0x01;
let mut cpu = Cpu::new(&rom);

let pc = cpu.run(0xffff, &mut AnyMachine {});

assert_eq!(pc, 0x01);
assert_eq!(pc, cpu.pc);
}

#[test]
pub 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);
}

#[test]
pub 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);
// TODO: check AnyMachine.deo has been called with 0xab as target arg
}
}
3 changes: 3 additions & 0 deletions coco-core/src/opcodes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub const BRK: u8 = 0x00;
pub const DEO: u8 = 0x17;
pub const PUSH: u8 = 0x80;
47 changes: 47 additions & 0 deletions coco-core/src/stack.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use core::fmt;

#[derive(Debug)]
pub struct Stack {
data: [u8; 0x100],
index: u8,
}

impl Stack {
pub fn new() -> Self {
Self {
data: [0_u8; 0x100],
index: u8::MAX,
}
}

pub fn len(&self) -> usize {
self.index.wrapping_add(1) as usize
}

pub fn push_byte(&mut self, x: u8) {
self.index = self.index.wrapping_add(1);
self.data[self.index as usize] = x;
}

pub fn pop_byte(&mut self) -> u8 {
let res = self.data[self.index as usize];
self.index = self.index.wrapping_sub(1);
res
}

pub fn byte_at(&self, i: u8) -> u8 {
return self.data[i as usize];
}
}

impl fmt::Display for Stack {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for i in self.len().saturating_sub(8)..self.len() {
write!(f, "{:02x}", self.byte_at(i as u8))?;
if i < self.len() - 1 {
write!(f, " ")?;
}
}
Ok(())
}
}
3 changes: 2 additions & 1 deletion coco-ui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta charset="utf-8">
<title>👻 COCO-8</title>
<script type="module" defer src="./index.js"></script>
<link rel="stylesheet" href="styles.css" type="text/css"
<link rel="stylesheet" href="styles.css" type="text/css">
</head>
<body>
<header>
Expand All @@ -15,6 +15,7 @@ <h1>👻-8</h1>
<p>
<select name="rom" id="coco-rom-selector">
<option value="empty.rom">Empty</option>
<option value="deo_system_debug.rom">Debug</option>
</select>
</p>
</main>
Expand Down
8 changes: 5 additions & 3 deletions coco-ui/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import initWasm, { runRom } from "./vendor/coco_ui.js";

async function handleFile(file) {
const buffer = file.arrayBuffer();
const buffer = await file.arrayBuffer();
const rom = new Uint8Array(buffer);

const output = runRom(rom);
console.debug(`PC: 0x${output.pc.toString(16)}`);
if (output.debug) {
console.log(output.debug);
}
}

async function fetchRom(path) {
Expand Down Expand Up @@ -35,7 +37,7 @@ async function main() {
const _ = await initWasm("./vendor/coco_ui_bg.wasm");
const romSelector = document.querySelector("#coco-rom-selector");

const defaultRom = "empty.rom";
const defaultRom = "deo_system_debug.rom";
await setupRomSelector(romSelector, defaultRom);

const rom = await fetchRom(`/roms/${defaultRom}`);
Expand Down
Binary file added coco-ui/roms/deo_system_debug.rom
Binary file not shown.
22 changes: 14 additions & 8 deletions coco-ui/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use std::cell::RefCell;
use std::rc::Rc;
use std::{cell::RefCell, path::Display};
use wasm_bindgen::prelude::*;

use coco_core::Cpu;
use coco_vm::{Vm, SCREEN_HEIGHT, SCREEN_WIDTH};

#[wasm_bindgen]
#[wasm_bindgen(getter_with_clone)]
#[derive(Debug)]
pub struct Output {
pub pc: u16,
pub debug: String,
}

pub type Result<T> = core::result::Result<T, JsValue>;
Expand Down Expand Up @@ -38,10 +38,14 @@ const THEME: [RGB; 0x10] = [

#[wasm_bindgen(js_name=runRom)]
pub fn run_rom(rom: &[u8]) -> Result<Output> {
let mut memory = [0; 0x10000];
memory[0x100..(0x100 + rom.len())].copy_from_slice(rom);

let cpu = Rc::new(RefCell::new(Cpu::new(memory)));
web_sys::console::log_1(&JsValue::from(
rom.iter()
.map(|x| format!("{:02x}", x))
.collect::<Vec<String>>()
.join(" "),
));

let cpu = Rc::new(RefCell::new(Cpu::new(&rom)));
let vm = Rc::new(RefCell::new(Vm::new()));

// call reset vector
Expand All @@ -61,7 +65,9 @@ pub fn run_rom(rom: &[u8]) -> Result<Output> {

request_animation_frame(g.borrow().as_ref().unwrap());

Ok(Output { pc: output.pc })
Ok(Output {
debug: output.sys_stdout,
})
}

fn render(vm: &Vm, ctx: &web_sys::CanvasRenderingContext2d, buffer: &mut DisplayBuffer) {
Expand Down
Loading

0 comments on commit 9ddc0fe

Please sign in to comment.