diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..0cdd7ba --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,18 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "asm" +version = "0.1.0" + +[[package]] +name = "jnd" +version = "0.1.0" + +[[package]] +name = "jnvm" +version = "0.1.0" +dependencies = [ + "jnd", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..361c935 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +members = [ "asm","jnd", "vm"] +resolver = "2" diff --git a/asm/Cargo.toml b/asm/Cargo.toml new file mode 100644 index 0000000..7005e69 --- /dev/null +++ b/asm/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "asm" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/asm/src/main.rs b/asm/src/main.rs new file mode 100644 index 0000000..35c3a0f --- /dev/null +++ b/asm/src/main.rs @@ -0,0 +1,29 @@ +use std::{ + env, + fs::File, + io::{self, Read, Write}, + process::exit, +}; + +fn main() { + let args = env::args().collect::>(); + + if args.len() < 2 { + println!("Usage: {} ", args[0]); + exit(1); + } + + let mut file = File::open(&args[1]).expect("Failed to open file"); + let mut buf = String::new(); + + file.read_to_string(&mut buf).unwrap(); + + let mut out = Vec::new(); + for word in buf.split_whitespace() { + // let byte = word.parse::().unwrap(); + let byte = u8::from_str_radix(word, 16).unwrap(); + out.push(byte); + } + let mut stdout = io::stdout().lock(); + stdout.write_all(&out).expect("Writing to stdout"); +} diff --git a/jnd/Cargo.toml b/jnd/Cargo.toml new file mode 100644 index 0000000..cd35506 --- /dev/null +++ b/jnd/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "jnd" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/jnd/src/disassembler.rs b/jnd/src/disassembler.rs new file mode 100644 index 0000000..e2bf326 --- /dev/null +++ b/jnd/src/disassembler.rs @@ -0,0 +1,28 @@ +use crate::vm::Machine; + +pub trait Disassemble { + fn print_reg(&self); + fn print_mem_map(&self); +} + +impl Disassemble for Machine { + fn print_reg(&self) { + println!("== Registers =="); + + for (i, ®) in ["a", "b", "c", "m", "sp", "pc", "bp", "flags"] + .iter() + .enumerate() + { + println!( + " {reg:<7}: {}", + self.get_reg((i as u8).try_into().unwrap()) + ); + } + + println!("==============="); + } + + fn print_mem_map(&self) { + todo!() + } +} diff --git a/jnd/src/errors/asme.rs b/jnd/src/errors/asme.rs new file mode 100644 index 0000000..e69de29 diff --git a/jnd/src/errors/mod.rs b/jnd/src/errors/mod.rs new file mode 100644 index 0000000..1f61634 --- /dev/null +++ b/jnd/src/errors/mod.rs @@ -0,0 +1,30 @@ +use core::panic; + +pub mod asme; +pub mod vme; + +pub trait Erroring { + fn err(&self) -> &str; +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum Jerror { + Vme(String), + Asme(String), +} + +impl Jerror { + pub fn vme(&self, err: &str) -> Self { + match self { + Self::Vme(e) => Self::Vme(format!("{e}: {err}")), + _ => panic!("This shouldnt happen. Jerror::vme shoudlnt access asme"), + } + } + + pub fn asme(&self, err: &str) -> Self { + match self { + Self::Asme(e) => Self::Asme(format!("{e}: {err}")), + _ => panic!("This shouldnt happen. Jerror::asme shoudlnt access vme"), + } + } +} diff --git a/jnd/src/errors/vme.rs b/jnd/src/errors/vme.rs new file mode 100644 index 0000000..b392c59 --- /dev/null +++ b/jnd/src/errors/vme.rs @@ -0,0 +1,124 @@ +use core::fmt; + +use super::Erroring; + +#[derive(Debug)] +pub enum VMErr { + WriteOutOfBounds, + ReadOutOfBound, + InvalidRegister, + MemReadFail, + MemRead2Fail, + MemWriteFail, + MemWrite2Fail, + UnknownInstruction, + InterruptHandlerNotFound, + InterruptHandlerInsert, +} + +impl Erroring for VMErr { + fn err(&self) -> &str { + match self { + Self::WriteOutOfBounds => "write out of bounds", + Self::ReadOutOfBound => "read out of bounds", + Self::InvalidRegister => "accessed register invalid", + Self::MemReadFail => "mem read failed", + Self::MemRead2Fail => "mem read 16bit failed", + Self::MemWriteFail => "mem write failed", + Self::MemWrite2Fail => "mem write 16bit failed", + Self::UnknownInstruction => "instruction unknown", + Self::InterruptHandlerNotFound => "no known handlers for the interrupt", + Self::InterruptHandlerInsert => "failed to insert interrupt handler", + } + } +} + +impl fmt::Display for VMErr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.err()) + } +} + +#[macro_export] +macro_rules! vme { + ($err: expr, $kind: ident, $($args:tt)*) => {{ + use $crate::errors::Erroring; + $err.vme(format!("{}: {}", $crate::errors::vme::VMErr::$kind.err(), format_args!($($args)*)).as_str()) + }}; + + ($kind:ident, $($args:tt)*) => {{ + use $crate::errors::Erroring; + $crate::errors::Jerror::Vme(format!("{}: {}", $crate::errors::vme::VMErr::$kind.err(), format_args!($($args)*))) + }}; +} + +#[cfg(test)] +mod test { + use crate::errors::Erroring; + + const ARM: usize = 0xdeadbeef; + + #[test] + fn vme_macro_arm1() { + let control = super::super::Jerror::Vme(format!( + "{}: {}", + super::VMErr::WriteOutOfBounds.err(), + "testing 123" + )); + let test = vme!(WriteOutOfBounds, "testing 123"); + assert_eq!(control, test) + } + + #[test] + fn vme_macro_arm1_2() { + let control = super::super::Jerror::Vme(format!( + "{}: {} {}", + super::VMErr::WriteOutOfBounds.err(), + "testing 123", + ARM + )); + let test = vme!(WriteOutOfBounds, "testing 123 {}", ARM); + assert_eq!(control, test) + } + + #[test] + fn vme_macro_arm2() { + let test = super::super::Jerror::Vme(format!( + "{}: {}", + super::VMErr::WriteOutOfBounds.err(), + "testing 123" + )); + let test = vme!(test, MemReadFail, "123"); + + let control = super::super::Jerror::Vme(format!( + "{}: {}: {}: {}", + super::VMErr::WriteOutOfBounds.err(), + "testing 123", + super::VMErr::MemReadFail, + "123" + )); + + assert_eq!(test, control) + } + + #[test] + fn vme_macro_arm2_2() { + let test = super::super::Jerror::Vme(format!( + "{}: {}", + super::VMErr::WriteOutOfBounds.err(), + "testing 123", + )); + let test = vme!(test, MemReadFail, "123 {}", ARM); + + let control = super::super::Jerror::Vme(format!( + "{}: {}: {}: {} {}", + super::VMErr::WriteOutOfBounds.err(), + "testing 123", + super::VMErr::MemReadFail, + "123", + ARM + )); + + assert_eq!(test, control) + } +} diff --git a/jnd/src/interrupts.rs b/jnd/src/interrupts.rs new file mode 100644 index 0000000..29dde82 --- /dev/null +++ b/jnd/src/interrupts.rs @@ -0,0 +1,6 @@ +use crate::{vm::Machine, Res}; + +pub fn halt(vm: &mut Machine) -> Res<()> { + vm.state = false; + Ok(()) +} diff --git a/jnd/src/lib.rs b/jnd/src/lib.rs new file mode 100644 index 0000000..fa50369 --- /dev/null +++ b/jnd/src/lib.rs @@ -0,0 +1,9 @@ +pub mod disassembler; +pub mod errors; +pub mod interrupts; +pub mod mem; +pub mod op; +pub mod reg; +pub mod vm; + +pub type Res = Result; diff --git a/jnd/src/mem/linear.rs b/jnd/src/mem/linear.rs new file mode 100644 index 0000000..609612f --- /dev/null +++ b/jnd/src/mem/linear.rs @@ -0,0 +1,31 @@ +use super::Addressable; +use crate::{vme, Res}; + +#[derive(Debug)] +pub struct LinearMemory { + bytes: Vec, +} + +impl LinearMemory { + pub fn new(n: usize) -> Self { + Self { + bytes: vec![0u8; n], + } + } +} + +impl Addressable for LinearMemory { + fn read(&self, addr: u16) -> Option { + self.bytes.get(addr as usize).copied() + } + + fn write(&mut self, addr: u16, value: u8) -> Res<()> { + let addr = addr as usize; + if addr > self.bytes.len() { + return Err(vme!(WriteOutOfBounds, "addr: 0x{addr:X} - values: {value}")); + } + + self.bytes[addr] = value; + Ok(()) + } +} diff --git a/jnd/src/mem/mod.rs b/jnd/src/mem/mod.rs new file mode 100644 index 0000000..4b8c1d1 --- /dev/null +++ b/jnd/src/mem/mod.rs @@ -0,0 +1,34 @@ +pub mod linear; + +use crate::Res; + +pub trait Addressable { + fn read(&self, addr: u16) -> Option; + fn write(&mut self, addr: u16, value: u8) -> Res<()>; + + fn read_u16(&self, addr: u16) -> Option { + if let Some(x0) = self.read(addr) { + if let Some(x1) = self.read(addr + 1) { + return Some(x0 as u16 | ((x1 as u16) << 8)); + } + } + None + } + + fn write_u16(&mut self, addr: u16, value: u16) -> Res<()> { + let lower = value & 0xff; + let upper = (value & 0xff00) >> 8; + self.write(addr, lower as u8)?; + self.write(addr + 1, upper as u8) + } + + fn copy(&mut self, from: u16, to: u16, n: usize) -> Res<()> { + for i in 0..n { + let i = i as u16; + if let Some(x) = self.read(from + i) { + self.write(to + i, x)?; + } + } + Ok(()) + } +} diff --git a/jnd/src/op.rs b/jnd/src/op.rs new file mode 100644 index 0000000..8ca9313 --- /dev/null +++ b/jnd/src/op.rs @@ -0,0 +1,68 @@ +use crate::{reg::Register, vme, Res}; + +/// return-eq +macro_rules! retq { + ($name: ident, $x: expr) => { + $x == $crate::op::Op::$name.as_u8() + }; + + ($name:ident($($args:tt)*), $x: expr) => { + $x == $crate::op::Op::$name($($args)*).as_u8() + }; +} + +#[derive(Debug, PartialEq, PartialOrd)] +#[repr(u8)] +pub enum Op { + Nop, + Push(u8), + PopRegister(Register), + AddStack, + AddRegister(Register, Register), + Interrupt(u8), +} + +pub trait Parser { + fn parse(self) -> Res; +} + +impl Parser for u16 { + fn parse(self) -> Res { + crate::op::Op::parse(self) + } +} + +impl Op { + pub fn as_u8(&self) -> u8 { + // SAFETY: Because `Self` is marked `repr(u8)`, its layout is a `repr(C)` `union` + // between `repr(C)` structs, each of which has the `u8` discriminant as its first + // field, so we can read the discriminant without offsetting the pointer. + unsafe { *<*const _>::from(self).cast::() } + } + + fn parse(byte: u16) -> Res { + let op = (byte & 0xff) as u8; + Ok(match op { + x if retq!(Nop, x) => Op::Nop, + x if retq!(Push(0), x) => { + let arg = ((byte & 0xff00) >> 8) as u8; + Op::Push(arg) + } + x if retq!(PopRegister(Register::A), x) => { + let reg: Register = ((byte & 0xf00) >> 8).try_into()?; + Op::PopRegister(reg) + } + x if retq!(AddStack, x) => Op::AddStack, + x if retq!(AddRegister(Register::A, Register::A), x) => { + let reg1 = ((byte & 0xf00) >> 8) as u8; + let reg2 = ((byte & 0xf0) >> 8) as u8; + Op::AddRegister(reg1.try_into()?, reg2.try_into()?) + } + x if retq!(Interrupt(0), x) => { + let arg = ((byte & 0xff00) >> 8) as u8; + Op::Interrupt(arg) + } + _ => return Err(vme!(UnknownInstruction, "found 0x{byte:X}")), + }) + } +} diff --git a/jnd/src/reg.rs b/jnd/src/reg.rs new file mode 100644 index 0000000..b5210b7 --- /dev/null +++ b/jnd/src/reg.rs @@ -0,0 +1,39 @@ +use crate::{errors, vme}; + +#[derive(Debug, PartialEq, PartialOrd)] +pub enum Register { + A, + B, + C, + M, + SP, + PC, + BP, + Flags, +} + +impl TryFrom for Register { + type Error = errors::Jerror; + + fn try_from(value: u8) -> Result { + Ok(match value { + x if x == Register::A as u8 => Register::A, + x if x == Register::B as u8 => Register::B, + x if x == Register::C as u8 => Register::C, + x if x == Register::M as u8 => Register::M, + x if x == Register::SP as u8 => Register::SP, + x if x == Register::PC as u8 => Register::PC, + x if x == Register::BP as u8 => Register::BP, + x if x == Register::Flags as u8 => Register::Flags, + _ => return Err(vme!(InvalidRegister, "{}", value)), + }) + } +} + +impl TryFrom for Register { + type Error = errors::Jerror; + + fn try_from(value: u16) -> Result { + (value as u8).try_into() + } +} diff --git a/jnd/src/vm.rs b/jnd/src/vm.rs new file mode 100644 index 0000000..709ad35 --- /dev/null +++ b/jnd/src/vm.rs @@ -0,0 +1,134 @@ +use std::collections::HashMap; + +use crate::{ + disassembler::Disassemble, + mem::{linear::LinearMemory, Addressable}, + op::{Op, Parser}, + reg::Register, + vme, Res, +}; + +pub type Interrupt = fn(&mut Machine) -> Res<()>; + +pub struct Machine { + registers: [u16; 8], + interrupt_map: HashMap, + pub state: bool, + memory: Box, +} + +impl Default for Machine { + fn default() -> Self { + Self { + registers: [0u16; 8], + interrupt_map: HashMap::new(), + state: false, + memory: Box::new(LinearMemory::new(4096)), + } + } +} + +impl Machine { + pub fn mem_write(&mut self, addr: u16, byte: u8) -> Res<()> { + self.memory.write(addr, byte) + } + + pub fn mem_write2(&mut self, addr: u16, byte: u16) -> Res<()> { + self.memory.write_u16(addr, byte) + } + + pub fn mem_read(&self, addr: u16) -> Res { + let sp = self.registers[Register::SP as usize]; + self.memory + .read(addr) + .ok_or(vme!(MemReadFail, "addr: 0x{addr:X} Sp: 0x{sp:X}")) + } + + pub fn mem_read2(&self, addr: u16) -> Res { + let sp = self.registers[Register::SP as usize]; + self.memory + .read_u16(addr) + .ok_or(vme!(MemRead2Fail, "addr: 0x{addr:X} Sp: 0x{sp:X}")) + } + + pub fn push(&mut self, value: u16) -> Res<()> { + let sp = self.registers[Register::SP as usize]; + self.mem_write2(sp, value)?; + self.registers[Register::SP as usize] += 2; + Ok(()) + } + + pub fn pop(&mut self) -> Res { + let sp = self.registers[Register::SP as usize] - 2; + let val = self.mem_read2(sp)?; + self.registers[Register::SP as usize] -= 2; + Ok(val) + } + + pub fn get_reg(&self, r: Register) -> u16 { + self.registers[r as usize] + } + + fn get_interrupt_handler(&self, interrupt: u16) -> Res<&Interrupt> { + self.interrupt_map + .get(&interrupt) + .ok_or(vme!(InterruptHandlerNotFound, "interrupt 0x{interrupt:X}")) + } + + pub fn insert_interrupt(&mut self, interrupt: u16, int_fn: Interrupt) { + self.interrupt_map.insert(interrupt, int_fn); + } + + pub fn step(&mut self) -> Res<()> { + let pc = self.registers[Register::PC as usize]; + let instruction = self.mem_read2(pc)?; + self.registers[Register::PC as usize] = pc + 2; + + let op = instruction.parse()?; + match op { + Op::Nop => Ok(()), + Op::Push(val) => self.push(val as u16), + Op::PopRegister(r) => { + let v = self.pop()?; + self.registers[r as usize] = v; + Ok(()) + } + Op::AddStack => { + let a = self.pop()?; + let b = self.pop()?; + self.push(a + b) + } + Op::AddRegister(r1, r2) => { + self.registers[r1 as usize] += self.registers[r2 as usize]; + Ok(()) + } // _ => Err(format!("unknown operand: {op:?} at {pc}").into()), + Op::Interrupt(sig) => { + let int = self.get_interrupt_handler(sig as u16)?; + int(self) + } + } + } + + pub fn execute(&mut self) -> Res<()> { + self.state = true; + while self.state { + self.step()?; + } + Ok(()) + } + + pub fn load_vector(&mut self, instruction_buf: &[u8], offset: u16) -> Res<()> { + for (index, &byte) in instruction_buf.iter().enumerate() { + self.mem_write(offset + (index as u16), byte)?; + } + Ok(()) + } +} + +impl Drop for Machine { + fn drop(&mut self) { + if cfg!(debug_assertions) { + self.print_reg(); + } + } +} diff --git a/test.asm b/test.asm new file mode 100644 index 0000000..4073600 --- /dev/null +++ b/test.asm @@ -0,0 +1 @@ +01 0a 01 08 03 00 02 00 05 f0 diff --git a/test.bin b/test.bin new file mode 100644 index 0000000..0a06dc2 Binary files /dev/null and b/test.bin differ diff --git a/vm/Cargo.toml b/vm/Cargo.toml new file mode 100644 index 0000000..8ec1a41 --- /dev/null +++ b/vm/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "jnvm" +version = "0.1.0" +edition = "2021" + +[dependencies] +jnd = { version = "0.1.0", path = "../jnd" } diff --git a/vm/src/main.rs b/vm/src/main.rs new file mode 100644 index 0000000..bc6da72 --- /dev/null +++ b/vm/src/main.rs @@ -0,0 +1,47 @@ +use std::{env, fs, io::Read, process::exit}; + +use jnd::{interrupts::halt, vm::Machine, Res}; + +fn main() -> Res<()> { + let args = env::args().collect::>(); + + if args.len() < 2 { + println!("Usage: {} ", &args[0]); + exit(1); + } + + let mut internal_buf = Vec::new(); + let mut file = fs::File::open(&args[1]).expect("Failed to open file"); + file.read_to_end(&mut internal_buf) + .expect("Failed to read file"); + + let mut vm = Machine::default(); + vm.insert_interrupt(0xf0_u16, halt); + + vm.load_vector(&internal_buf, 0)?; + + /* + * push 10 + * push 8 + * addstack + * popregister + */ + + // vm.mem_write(0, 0x1)?; + // vm.mem_write(1, 10)?; + // vm.mem_write(2, 0x1)?; + // vm.mem_write(3, 8)?; + // vm.mem_write(4, 0x3)?; + // vm.mem_write(6, 0x2)?; + // vm.mem_write(7, 0x0)?; + + // vm.step()?; + // vm.step()?; + // vm.step()?; + // vm.step()?; + + vm.execute()?; + println!("A = {}", vm.get_reg(jnd::reg::Register::A)); + + Ok(()) +}