From c7a118cee8c3482c8ab28ed5b3d228c80c8ef62f Mon Sep 17 00:00:00 2001 From: xenua Date: Sun, 5 Nov 2023 15:02:53 +0100 Subject: [PATCH] hello, world! --- .gitignore | 1 + Cargo.lock | 297 +++++++++++++++++++++++++++++ Cargo.toml | 17 ++ README.md | 7 + alea-core/.gitignore | 2 + alea-core/Cargo.toml | 11 ++ alea-core/src/decode.rs | 135 ++++++++++++++ alea-core/src/isa.rs | 404 ++++++++++++++++++++++++++++++++++++++++ alea-core/src/lib.rs | 12 ++ alea-core/src/memory.rs | 177 ++++++++++++++++++ alea-core/src/vcore.rs | 341 +++++++++++++++++++++++++++++++++ src/console.rs | 44 +++++ src/hello.alea | Bin 0 -> 52 bytes src/main.rs | 32 ++++ 14 files changed, 1480 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 alea-core/.gitignore create mode 100644 alea-core/Cargo.toml create mode 100644 alea-core/src/decode.rs create mode 100644 alea-core/src/isa.rs create mode 100644 alea-core/src/lib.rs create mode 100644 alea-core/src/memory.rs create mode 100644 alea-core/src/vcore.rs create mode 100644 src/console.rs create mode 100644 src/hello.alea create mode 100644 src/main.rs 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..dd386b8 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,297 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "alea" +version = "0.1.0" +dependencies = [ + "alea-core", + "bytes", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "alea-core" +version = "0.1.0" +dependencies = [ + "bitflags", + "bytes", + "tracing", +] + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "proc-macro2" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.3", + "regex-syntax 0.8.2", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.2", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "smallvec" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" + +[[package]] +name = "syn" +version = "2.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..8450532 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[workspace] +members = [ + "alea-core" +] + +[package] +name = "alea" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +alea-core = { path = "alea-core" } +bytes = "1.5.0" +tracing = "0.1.40" +tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } diff --git a/README.md b/README.md new file mode 100644 index 0000000..9888271 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +## what + +virtual cpu + +goes beep boop + +complete enough to do hello world \ No newline at end of file diff --git a/alea-core/.gitignore b/alea-core/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/alea-core/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/alea-core/Cargo.toml b/alea-core/Cargo.toml new file mode 100644 index 0000000..bad33cb --- /dev/null +++ b/alea-core/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "alea-core" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bitflags = "2.4.1" +bytes = "1.5.0" +tracing = "0.1.40" diff --git a/alea-core/src/decode.rs b/alea-core/src/decode.rs new file mode 100644 index 0000000..d880360 --- /dev/null +++ b/alea-core/src/decode.rs @@ -0,0 +1,135 @@ +use crate::{ + isa::{CmpFlags, InstVariant, Instruction, InstructionData}, + vcore::CPUError, +}; + +use tracing::{debug, trace}; + +impl TryFrom for Instruction { + type Error = CPUError; + + fn try_from(value: u32) -> Result { + use InstVariant::*; + + let cmp = value.into(); + let sign = (value & 0x00080000) != 0; + + let id = value.into(); + + let inst = match value >> 27 { + 0b00000 => { + if value > 0 { + // TODO: change this to only check low 16 bits, i.e. instdata + Halt + } else { + Nop + } + } + 0b00001 => Cmp(id), + 0b00010 => Set, + 0b00011 => Rst, + 0b00100 => Call(id), + 0b00101 => Ret, + 0b00110 => Jmp(id), + 0b00111 => JmpAbs(id), + 0b01000 => Ld(id), + 0b01001 => Sto(id), + 0b01011 => Push(id), + 0b01100 => Pop(id), + 0b01101 => Mov(id), + 0b01110 => return Err(CPUError::InvalidInstruction), + 0b01111 => return Err(CPUError::InvalidInstruction), + 0b10000 => Add(id), + 0b10001 => Mul(id), + 0b10010 => And(id), + 0b10011 => Or(id), + 0b10100 => Xor(id), + 0b10101 => Not(id), + 0b10110 => return Err(CPUError::InvalidInstruction), + 0b10111 => return Err(CPUError::InvalidInstruction), + 0b11000 => Lsl(id), + 0b11001 => Lsr(id), + 0b11010 => Asr(id), + 0b11011 => Ror(id), + 0b11100 => return Err(CPUError::InvalidInstruction), + 0b11101 => return Err(CPUError::InvalidInstruction), + 0b11110 => return Err(CPUError::InvalidInstruction), + 0b11111 => return Err(CPUError::InvalidInstruction), + + _ => unreachable!(), + }; + let i = Instruction { + cmp, + sign, + var: inst, + }; + + debug!("decoded {:#010x} into {}", value, i); + + Ok(i) + } +} + +impl From for InstructionData { + fn from(value: u32) -> Self { + use InstructionData::*; + let variant = (value & 0x00070000) >> 16; + trace!("variant is {}, value is {:#010x}", variant, value); + + match variant { + 0b000 => { + let r1 = ((value & 0x0000F000) >> 12) as u8; + let v = (value & 0x000000FF) as u8; + RegisterAndU8(r1.try_into().unwrap(), v) + } + 0b001 => { + let r1 = ((value & 0x0000F000) >> 12) as u8; + RegisterAndU32(r1.try_into().unwrap(), ()) + } + 0b010 => { + let r1 = ((value & 0x0000F000) >> 12) as u8; + let r2 = ((value & 0x00000F00) >> 8) as u8; + let v = (value & 0x000000FF) as u8; + TwoRegistersAndU8(r1.try_into().unwrap(), r2.try_into().unwrap(), v) + } + 0b011 => { + let r1 = ((value & 0x0000F000) >> 12) as u8; + let r2 = ((value & 0x00000F00) >> 8) as u8; + TwoRegistersAndU32(r1.try_into().unwrap(), r2.try_into().unwrap(), ()) + } + 0b100 => { + let r1 = ((value & 0x0000F000) >> 12) as u8; + OneRegister( + r1.try_into() + .expect("this should only be 4 bits large hmmm"), + ) + } + 0b101 => { + let r1 = ((value & 0x0000F000) >> 12) as u8; + let r2 = ((value & 0x00000F00) >> 8) as u8; + TwoRegisters(r1.try_into().expect("fuck"), r2.try_into().expect("fuck")) + } + 0b110 => { + let r1 = ((value & 0x0000F000) >> 12) as u8; + let r2 = ((value & 0x00000F00) >> 8) as u8; + let r3 = ((value & 0x000000F0) >> 4) as u8; + ThreeRegisters( + r1.try_into().unwrap(), + r2.try_into().unwrap(), + r3.try_into().unwrap(), + ) + } + 0b111 => ImmediateOnly(()), + _ => unreachable!(), + } + } +} + +impl From for CmpFlags { + fn from(value: u32) -> Self { + let gt = (value & 0x04000000) != 0; + let eq = (value & 0x02000000) != 0; + let lt = (value & 0x01000000) != 0; + CmpFlags { gt, eq, lt } + } +} diff --git a/alea-core/src/isa.rs b/alea-core/src/isa.rs new file mode 100644 index 0000000..4e44130 --- /dev/null +++ b/alea-core/src/isa.rs @@ -0,0 +1,404 @@ +/// xgayuh instruction set +/// fuck it we ball +/// +/// +/// 32 bit instruction width +/// all instructions optional via state flag comparison +/// mmapped i/o +/// 16 registers +/// 32bit address space +/// +use std::{ + fmt::Display, + ops::{Index, IndexMut}, +}; + +#[derive(Clone, Copy, Debug)] +pub enum Register { + R0, + R1, + R2, + R3, + R4, + R5, + R6, + R7, + R8, + R9, + CS, + CA, + ST, + IA, + RA, + SA, +} + +const ALL_REGISTERS: [Register; 16] = [ + Register::R0, + Register::R1, + Register::R2, + Register::R3, + Register::R4, + Register::R5, + Register::R6, + Register::R7, + Register::R8, + Register::R9, + Register::CS, + Register::CA, + Register::ST, + Register::IA, + Register::RA, + Register::SA, +]; + +impl TryFrom for Register { + type Error = (); + + fn try_from(value: u8) -> Result { + use Register::*; + + match value { + 0b0000 => Ok(R0), + 0b0001 => Ok(R1), + 0b0010 => Ok(R2), + 0b0011 => Ok(R3), + 0b0100 => Ok(R4), + 0b0101 => Ok(R5), + 0b0110 => Ok(R6), + 0b0111 => Ok(R7), + 0b1000 => Ok(R8), + 0b1001 => Ok(R9), + 0b1010 => Ok(CS), + 0b1011 => Ok(CA), + 0b1100 => Ok(ST), + 0b1101 => Ok(IA), + 0b1110 => Ok(RA), + 0b1111 => Ok(SA), + _ => Err(()), + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct State(pub u32); + +bitflags::bitflags! { + impl State: u32 { + const CMP_GT = 1; + const CMP_EQ = 1 << 1; + const CMP_LT = 1 << 2; + const CMP_EN = 1 << 3; + const RET_ST = 1 << 4; + } +} + +#[derive(Clone, Copy, Default, Debug)] +pub struct CmpFlags { + pub gt: bool, + pub eq: bool, + pub lt: bool, +} + +impl CmpFlags { + pub fn new(a: T, b: T) -> Self { + CmpFlags { + gt: a > b, + eq: a == b, + lt: a < b, + } + } +} + +impl Display for CmpFlags { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "CmpFlags({})", + match (self.gt, self.eq, self.lt) { + (true, true, true) => "ALL", + (true, true, false) => "GE", + (true, false, true) => "NE", + (false, true, true) => "LE", + (true, false, false) => "GT", + (false, true, false) => "EQ", + (false, false, true) => "LT", + (false, false, false) => "NONE", + }, + ) + } +} + +impl Into for CmpFlags { + fn into(self) -> State { + self.gt.then_some(State::CMP_GT).unwrap_or(State(0)) + | self.eq.then_some(State::CMP_EQ).unwrap_or(State(0)) + | self.lt.then_some(State::CMP_LT).unwrap_or(State(0)) + } +} + +impl Default for State { + fn default() -> Self { + Self::CMP_EQ | Self::CMP_GT | Self::CMP_LT + } +} + +impl State { + pub fn set_cmp(&mut self, new: CmpFlags) { + self.set(Self::CMP_GT, new.gt); + self.set(Self::CMP_EQ, new.eq); + self.set(Self::CMP_LT, new.lt); + } + + pub fn get_cmp(&self) -> CmpFlags { + CmpFlags { + gt: self.contains(Self::CMP_GT), + eq: self.contains(Self::CMP_EQ), + lt: self.contains(Self::CMP_LT), + } + } +} + +#[repr(C)] +#[derive(Default)] +pub struct RegisterFile { + pub r0: u32, + pub r1: u32, + pub r2: u32, + pub r3: u32, + pub r4: u32, + pub r5: u32, + pub r6: u32, + pub r7: u32, + pub r8: u32, + pub r9: u32, + /// call stack + pub cs: u32, + /// "carry", used by the alu for extended results + pub ca: u32, + /// "state", internal state and alu flags + pub st: State, + /// instruction address + pub ia: u32, + /// return address + pub ra: u32, + /// stack address + pub sa: u32, +} + +impl Index for RegisterFile { + type Output = u32; + + fn index(&self, index: u8) -> &Self::Output { + assert!(index < 16, "there's only 16 registers"); + match index { + 0 => &self.r0, + 1 => &self.r1, + 2 => &self.r2, + 3 => &self.r3, + 4 => &self.r4, + 5 => &self.r5, + 6 => &self.r6, + 7 => &self.r7, + 8 => &self.r8, + 9 => &self.r9, + 10 => &self.cs, + 11 => &self.ca, + 12 => &self.st.0, + 13 => &self.ia, + 14 => &self.ra, + 15 => &self.sa, + _ => unreachable!(), + } + } +} + +impl IndexMut for RegisterFile { + fn index_mut(&mut self, index: u8) -> &mut Self::Output { + assert!(index < 16, "there's only 16 registers"); + match index { + 0 => &mut self.r0, + 1 => &mut self.r1, + 2 => &mut self.r2, + 3 => &mut self.r3, + 4 => &mut self.r4, + 5 => &mut self.r5, + 6 => &mut self.r6, + 7 => &mut self.r7, + 8 => &mut self.r8, + 9 => &mut self.r9, + 10 => &mut self.cs, + 11 => &mut self.ca, + 12 => &mut self.st.0, + 13 => &mut self.ia, + 14 => &mut self.ra, + 15 => &mut self.sa, + _ => unreachable!(), + } + } +} + +impl Index for RegisterFile { + type Output = u32; + + fn index(&self, index: Register) -> &Self::Output { + use Register::*; + + match index { + R0 => &self.r0, + R1 => &self.r1, + R2 => &self.r2, + R3 => &self.r3, + R4 => &self.r4, + R5 => &self.r5, + R6 => &self.r6, + R7 => &self.r7, + R8 => &self.r8, + R9 => &self.r9, + CS => &self.cs, + CA => &self.ca, + ST => &self.st.0, + IA => &self.ia, + RA => &self.ra, + SA => &self.sa, + } + } +} + +impl IndexMut for RegisterFile { + fn index_mut(&mut self, index: Register) -> &mut Self::Output { + use Register::*; + + match index { + R0 => &mut self.r0, + R1 => &mut self.r1, + R2 => &mut self.r2, + R3 => &mut self.r3, + R4 => &mut self.r4, + R5 => &mut self.r5, + R6 => &mut self.r6, + R7 => &mut self.r7, + R8 => &mut self.r8, + R9 => &mut self.r9, + CS => &mut self.cs, + CA => &mut self.ca, + ST => &mut self.st.0, + IA => &mut self.ia, + RA => &mut self.ra, + SA => &mut self.sa, + } + } +} + +impl Display for RegisterFile { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut out = String::new(); + out += "RegisterFile {\n"; + for regs in ALL_REGISTERS.chunks(4) { + for r in regs { + out += format!(" {:?}: {:#010x}", r, self[*r]).as_str(); + } + out += "\n"; + } + out += "};"; + write!(f, "{}", out) + } +} + +#[derive(Clone, Copy, Debug)] +pub struct Instruction { + pub cmp: CmpFlags, + pub sign: bool, + pub var: InstVariant, +} + +impl Instruction { + pub fn has_upcoming_value(&self) -> bool { + use InstVariant::*; + use InstructionData::*; + match self.var { + // this might be the worst match i've ever written + Set | Rst | Ret | Nop | Halt => false, + Add(id) | Mul(id) | And(id) | Or(id) | Xor(id) | Not(id) | Cmp(id) | Lsl(id) + | Lsr(id) | Asr(id) | Ror(id) | Ld(id) | Sto(id) | Push(id) | Pop(id) | Mov(id) + | Call(id) | Jmp(id) | JmpAbs(id) => match id { + RegisterAndU32(_, _) | TwoRegistersAndU32(_, _, _) | ImmediateOnly(_) => true, + _ => false, + }, + } + } +} + +impl Display for Instruction { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Instruction({} {} {:?})", + self.cmp, + if self.sign { "Neg" } else { "Pos" }, + self.var + ) + } +} + +#[derive(Default, Clone, Copy, Debug)] +/// first 5 bits: instruction variant +/// last 16 bits: decode into instruction data +/// bits 6-8: cmp bits, gt eq lt +/// 9-12: 4 bits of cozy padding +/// 13: sign bit +/// 14-16: instruction data variant +pub enum InstVariant { + // data processing + Add(InstructionData), + Mul(InstructionData), + And(InstructionData), + Or(InstructionData), + Xor(InstructionData), + Not(InstructionData), + + // comparison, branch control + Cmp(InstructionData), + Set, + Rst, + + // shifts + Lsl(InstructionData), + Lsr(InstructionData), + Asr(InstructionData), + Ror(InstructionData), + + // Load and store and move + Ld(InstructionData), + Sto(InstructionData), + Push(InstructionData), + Pop(InstructionData), + Mov(InstructionData), + + // subroutine and jumps + Call(InstructionData), + Ret, + Jmp(InstructionData), + JmpAbs(InstructionData), + + // misc + #[default] + Nop, + Halt, +} + +/// a marker to make the cpu read the next 4 bytes from the program code as a literal value +pub type UpcomingU32 = (); + +// up to 16 bits large +#[derive(Clone, Copy, Debug)] +pub enum InstructionData { + OneRegister(Register), + TwoRegisters(Register, Register), + ThreeRegisters(Register, Register, Register), + RegisterAndU8(Register, u8), + RegisterAndU32(Register, UpcomingU32), + TwoRegistersAndU8(Register, Register, u8), + TwoRegistersAndU32(Register, Register, UpcomingU32), + ImmediateOnly(UpcomingU32), +} diff --git a/alea-core/src/lib.rs b/alea-core/src/lib.rs new file mode 100644 index 0000000..cb8264b --- /dev/null +++ b/alea-core/src/lib.rs @@ -0,0 +1,12 @@ +mod decode; +pub mod isa; +pub mod memory; +pub mod vcore; + +pub mod prelude { + pub use crate::isa::{ + CmpFlags, InstVariant, Instruction, InstructionData, Register, RegisterFile, State, + }; + pub use crate::memory::{MappedMemory, Memory, MemoryMap}; + pub use crate::vcore::{CPUError, CPU}; +} diff --git a/alea-core/src/memory.rs b/alea-core/src/memory.rs new file mode 100644 index 0000000..1cb8bf0 --- /dev/null +++ b/alea-core/src/memory.rs @@ -0,0 +1,177 @@ +use std::fmt::{Debug, Display}; + +use bytes::BytesMut; +use tracing::{trace, warn}; + +pub trait Memory { + fn size(&self) -> u32; + fn read(&self, addr: u32) -> u8; + fn write(&mut self, addr: u32, value: u8); + + fn read_u16(&self, addr: u32) -> u16 { + let b1 = self.read(addr); + let b2 = self.read(addr + 1); + + u16::from_le_bytes([b1, b2]) + } + + fn read_u32(&self, addr: u32) -> u32 { + let b1 = self.read(addr); + let b2 = self.read(addr + 1); + let b3 = self.read(addr + 2); + let b4 = self.read(addr + 3); + + u32::from_le_bytes([b1, b2, b3, b4]) + } + + fn write_u16(&mut self, addr: u32, value: u16) { + let [b1, b2] = value.to_le_bytes(); + self.write(addr, b1); + self.write(addr + 1, b2); + } + + fn write_u32(&mut self, addr: u32, value: u32) { + let [b1, b2, b3, b4] = value.to_le_bytes(); + self.write(addr, b1); + self.write(addr + 1, b2); + self.write(addr + 2, b3); + self.write(addr + 3, b4); + } + + fn pre_step(&mut self) {} + fn post_step(&mut self) { + warn!("mem-trait default: post-step"); + } +} + +type BoxedMem = Box; + +impl Memory for BytesMut { + fn size(&self) -> u32 { + self.len() as u32 + } + + fn read(&self, addr: u32) -> u8 { + self[addr as usize] + } + + fn write(&mut self, addr: u32, value: u8) { + self.get_mut(addr as usize).map(|v| *v = value); + } +} + +#[derive(Default)] +pub struct MemoryMap { + pub mems: Vec, +} + +impl MemoryMap { + fn pos_resp(&self, addr: u32) -> Option { + self.mems + .iter() + .position(|mem| mem.start <= addr && mem.end >= addr) + } + + fn get_responsible(&self, addr: u32) -> Option<&MappedMemory> { + self.pos_resp(addr).map(|idx| &self.mems[idx]) + } + + fn get_responsible_mut(&mut self, addr: u32) -> Option<&mut MappedMemory> { + self.pos_resp(addr).map(|idx| &mut self.mems[idx]) + } + + fn collides(&self, b: &MappedMemory) -> bool { + self.mems.iter().any(|a| { + if a.start == b.start || a.end == b.end { + false + } else if a.start < b.start { + a.end > b.start + } else { + a.start < b.end + } + }) + } + + pub fn add(&mut self, mem: impl Memory + 'static, at: u32) -> Result<(), ()> { + let mapped = MappedMemory::new(Box::new(mem), at); + if self.collides(&mapped) { + return Err(()); + } + self.mems.push(mapped); + + Ok(()) + } +} + +impl Display for MemoryMap { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "MemoryMap({:?})", self.mems) + } +} + +impl Memory for MemoryMap { + fn size(&self) -> u32 { + u32::MAX + } + + fn read(&self, addr: u32) -> u8 { + match self.get_responsible(addr) { + None => { + warn!("out of bounds read at {:#010x}", addr); + 0 + } + Some(mem) => { + trace!("mmap: read of {:#x} delegated to {:?}", addr, mem); + mem.read(addr) + } + } + } + + fn write(&mut self, addr: u32, value: u8) { + self.get_responsible_mut(addr) + .map(|mem| mem.write(addr, value)); + } +} + +pub struct MappedMemory { + pub start: u32, + pub end: u32, + pub inner: BoxedMem, +} + +impl MappedMemory { + pub fn new(mem: BoxedMem, start: u32) -> MappedMemory { + let size = mem.size(); + let end = start + size - 1; + + Self { + start, + end, + inner: mem, + } + } +} + +impl Debug for MappedMemory { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "MappedMem({:#x} .. {:#x})", self.start, self.end) + } +} + +impl Memory for MappedMemory { + fn size(&self) -> u32 { + self.inner.size() + } + + fn read(&self, addr: u32) -> u8 { + let internal = addr - self.start; + assert!(internal < self.size()); + self.inner.read(internal) + } + + fn write(&mut self, addr: u32, value: u8) { + let internal = addr - self.start; + assert!(internal < self.size()); + self.inner.write(internal, value); + } +} diff --git a/alea-core/src/vcore.rs b/alea-core/src/vcore.rs new file mode 100644 index 0000000..b7f0046 --- /dev/null +++ b/alea-core/src/vcore.rs @@ -0,0 +1,341 @@ +use std::io; + +use tracing::{debug, info, trace}; + +use crate::{ + isa::{CmpFlags, InstVariant, Instruction, InstructionData, RegisterFile, State}, + memory::{Memory, MemoryMap}, +}; + +#[derive(Default)] +pub struct CPU { + pub mmap: MemoryMap, + pub reg: RegisterFile, + + pub step_wait: bool, +} + +#[derive(Debug)] +pub enum CPUError { + InvalidInstruction, + Unimplemented, + Halt, +} + +fn wait() { + io::stdin() + .read_line(&mut String::new()) + .expect("couldn't read stdin"); +} + +impl CPU { + pub fn run_until_halt(&mut self) -> Result<(), CPUError> { + loop { + match self.step() { + Ok(_) => continue, + Err(CPUError::Halt) => { + info!("halting"); + return Ok(()); + } + Err(e) => return Err(e), + } + } + } + + pub fn call_peripherals_pre(&mut self) { + self.mmap.mems.iter_mut().for_each(|m| m.inner.pre_step()) + } + + pub fn call_peripherals_post(&mut self) { + self.mmap.mems.iter_mut().for_each(|m| m.inner.post_step()) + } + + pub fn step(&mut self) -> Result<(), CPUError> { + use InstVariant::*; + use InstructionData::*; + + debug!("calling peripherals pre-step"); + self.call_peripherals_pre(); + + let current_ia = self.reg.ia; + let current_inst: Instruction = self.read_ia().try_into()?; + + debug!("running instruction at {:#010x}", current_ia); + trace!("register dump:\n{}", self.reg); + + if self.step_wait { + info!("will run {}", current_inst); + info!("waiting..."); + wait(); + } + + if self.reg.st.contains(State::CMP_EN) + && (self.reg.st & current_inst.cmp.into()) == State(0) + { + debug!("optional instruction skipped!"); + if current_inst.has_upcoming_value() { + self.read_ia(); + } + return Ok(()); + } + + match current_inst.var { + Nop => (), + Halt => return Err(CPUError::Halt), + Add(id) => { + let (t, a, b) = match id { + TwoRegisters(a, b) => (a, a, self.reg[b]), + ThreeRegisters(t, a, b) => (t, a, self.reg[b]), + RegisterAndU8(r, v) => (r, r, v as u32), + RegisterAndU32(r, _) => (r, r, self.read_ia()), + TwoRegistersAndU8(t, a, b) => (t, a, b as u32), + TwoRegistersAndU32(t, a, _) => (t, a, self.read_ia()), + _ => return Err(CPUError::InvalidInstruction), + }; + self.reg[t] = if current_inst.sign { + self.reg[a] - b + } else { + self.reg[a] + b + }; + } + And(id) => { + let (t, a, b) = match id { + TwoRegisters(a, b) => (a, a, self.reg[b]), + ThreeRegisters(t, a, b) => (t, a, self.reg[b]), + RegisterAndU8(r, v) => (r, r, v as u32), + RegisterAndU32(r, _) => (r, r, self.read_ia()), + TwoRegistersAndU8(t, a, b) => (t, a, b as u32), + TwoRegistersAndU32(t, a, _) => (t, a, self.read_ia()), + _ => return Err(CPUError::InvalidInstruction), + }; + self.reg[t] = self.reg[a] & b; + } + Or(id) => { + let (t, a, b) = match id { + TwoRegisters(a, b) => (a, a, self.reg[b]), + ThreeRegisters(t, a, b) => (t, a, self.reg[b]), + RegisterAndU8(r, v) => (r, r, v as u32), + RegisterAndU32(r, _) => (r, r, self.read_ia()), + TwoRegistersAndU8(t, a, b) => (t, a, b as u32), + TwoRegistersAndU32(t, a, _) => (t, a, self.read_ia()), + _ => return Err(CPUError::InvalidInstruction), + }; + self.reg[t] = self.reg[a] | b; + } + Xor(id) => { + let (t, a, b) = match id { + TwoRegisters(a, b) => (a, a, self.reg[b]), + ThreeRegisters(t, a, b) => (t, a, self.reg[b]), + RegisterAndU8(r, v) => (r, r, v as u32), + RegisterAndU32(r, _) => (r, r, self.read_ia()), + TwoRegistersAndU8(t, a, b) => (t, a, b as u32), + TwoRegistersAndU32(t, a, _) => (t, a, self.read_ia()), + _ => return Err(CPUError::InvalidInstruction), + }; + self.reg[t] = self.reg[a] ^ b; + } + Not(id) => { + let (t, v) = match id { + OneRegister(r) => (r, self.reg[r]), + TwoRegisters(t, v) => (t, self.reg[v]), + RegisterAndU8(t, v) => (t, v as u32), + RegisterAndU32(t, _) => (t, self.read_ia()), + _ => return Err(CPUError::InvalidInstruction), + }; + self.reg[t] = !v; + } + Mul(id) => { + let (t, a, b) = match id { + TwoRegisters(a, b) => (a, a, self.reg[b]), + ThreeRegisters(t, a, b) => (t, a, self.reg[b]), + RegisterAndU8(r, v) => (r, r, v as u32), + RegisterAndU32(r, _) => (r, r, self.read_ia()), + TwoRegistersAndU8(t, a, b) => (t, a, b as u32), + TwoRegistersAndU32(t, a, _) => (t, a, self.read_ia()), + _ => return Err(CPUError::InvalidInstruction), + }; + self.reg[t] = self.reg[a] * b; + } + Cmp(id) => { + let (a, b) = match id { + OneRegister(a) => (a, 0), + TwoRegisters(a, b) => (a, self.reg[b]), + RegisterAndU8(a, b) => (a, b as u32), + RegisterAndU32(a, _) => (a, self.read_ia()), + _ => return Err(CPUError::InvalidInstruction), + }; + self.reg.st.set_cmp(CmpFlags::new(self.reg[a], b)); + } + Set => { + self.reg.st.set(State::CMP_EN, current_inst.sign); + } + Rst => self.reg.st.set_cmp(CmpFlags { + gt: true, + eq: true, + lt: true, + }), + Lsl(id) => { + let (t, a, b) = match id { + TwoRegisters(a, b) => (a, a, self.reg[b]), + ThreeRegisters(t, a, b) => (t, a, self.reg[b]), + RegisterAndU8(r, v) => (r, r, v as u32), + TwoRegistersAndU8(t, a, b) => (t, a, b as u32), + _ => return Err(CPUError::InvalidInstruction), + }; + self.reg[t] = self.reg[a] << b; + } + Lsr(id) => { + let (t, a, b) = match id { + TwoRegisters(a, b) => (a, a, self.reg[b]), + ThreeRegisters(t, a, b) => (t, a, self.reg[b]), + RegisterAndU8(r, v) => (r, r, v as u32), + TwoRegistersAndU8(t, a, b) => (t, a, b as u32), + _ => return Err(CPUError::InvalidInstruction), + }; + self.reg[t] = self.reg[a] >> b; + } + Asr(id) => { + let (t, a, b) = match id { + TwoRegisters(a, b) => (a, a, self.reg[b]), + ThreeRegisters(t, a, b) => (t, a, self.reg[b]), + RegisterAndU8(r, v) => (r, r, v as u32), + TwoRegistersAndU8(t, a, b) => (t, a, b as u32), + _ => return Err(CPUError::InvalidInstruction), + }; + // rust does asr for signed ints, and lsr for unsigned + self.reg[t] = ((self.reg[a] as i32) >> b) as u32; + } + Ror(id) => { + let (t, a, b) = match id { + TwoRegisters(a, b) => (a, a, self.reg[b]), + ThreeRegisters(t, a, b) => (t, a, self.reg[b]), + RegisterAndU8(r, v) => (r, r, v as u32), + TwoRegistersAndU8(t, a, b) => (t, a, b as u32), + _ => return Err(CPUError::InvalidInstruction), + }; + self.reg[t] = self.reg[a].rotate_right(b); + } + Ld(id) => { + let (a, r) = match id { + TwoRegisters(r, a) => (self.reg[a], r), + RegisterAndU32(r, _) => (self.read_ia(), r), + _ => return Err(CPUError::InvalidInstruction), + }; + self.reg[r] = self.mmap.read_u32(a); + } + Sto(id) => { + let (v, a) = match id { + TwoRegisters(v, a) => (self.reg[v], self.reg[a]), + RegisterAndU32(v, _) => (self.reg[v], self.read_ia()), + _ => return Err(CPUError::InvalidInstruction), + }; + self.mmap.write_u32(a, v); + } + Push(id) => { + match id { + OneRegister(a) => vec![self.reg[a]], + TwoRegisters(a, b) => vec![self.reg[a], self.reg[b]], + ThreeRegisters(a, b, c) => vec![self.reg[a], self.reg[b], self.reg[c]], + RegisterAndU8(a, b) => vec![self.reg[a], b as u32], + RegisterAndU32(a, _) => vec![self.reg[a], self.read_ia()], + TwoRegistersAndU8(a, b, c) => vec![self.reg[a], self.reg[b], c as u32], + TwoRegistersAndU32(a, b, _) => vec![self.reg[a], self.reg[b], self.read_ia()], + ImmediateOnly(_) => vec![self.read_ia()], + } + .into_iter() + .for_each(|val| self.push_stack(val)); + } + Pop(id) => { + match id { + OneRegister(a) => vec![a], + TwoRegisters(a, b) => vec![a, b], + ThreeRegisters(a, b, c) => vec![a, b, c], + _ => return Err(CPUError::InvalidInstruction), + } + .into_iter() + .for_each(|r| self.reg[r] = self.pop_stack()); + } + Mov(id) => { + let (r, v) = match id { + TwoRegisters(a, b) => (a, self.reg[b]), + RegisterAndU32(a, _) => (a, self.read_ia()), + RegisterAndU8(a, v) => (a, v as u32), + _ => return Err(CPUError::InvalidInstruction), + }; + self.reg[r] = v; + } + Call(id) => { + let call_addr = match id { + OneRegister(r) => self.reg[r], + ImmediateOnly(_) => self.read_ia(), + _ => return Err(CPUError::InvalidInstruction), + }; + self.push_cs(); + self.reg.ra = self.reg.ia; + self.reg.ia = call_addr; + } + Ret => { + self.reg.ia = self.reg.ra; + self.pop_cs(); + } + Jmp(id) => { + let jump_offset = match id { + OneRegister(r) => self.reg[r], + ImmediateOnly(_) => self.read_ia(), + RegisterAndU8(_, v) | TwoRegistersAndU8(_, _, v) => (v as u32) * 4, + _ => return Err(CPUError::InvalidInstruction), + }; + self.reg.ia = if current_inst.sign { + current_ia - jump_offset + } else { + current_ia + jump_offset + }; + } + JmpAbs(id) => { + let jump_addr = match id { + OneRegister(r) => self.reg[r], + ImmediateOnly(_) => self.read_ia(), + _ => return Err(CPUError::InvalidInstruction), + }; + self.reg.ia = jump_addr; + } + }; + + debug!("calling peripherals post-step"); + self.call_peripherals_post(); + + Ok(()) + } + + fn read_ia(&mut self) -> u32 { + let v = self.mmap.read_u32(self.reg.ia); + self.reg.ia += 4; + trace!("read_ia: {:#010x}", v); + v + } + + fn push_stack(&mut self, v: u32) { + self.reg.sa -= 4; + debug!("pushed {:#010x} to {:#010x}", v, self.reg.sa); + self.mmap.write_u32(self.reg.sa, v); + } + + fn pop_stack(&mut self) -> u32 { + let v = self.mmap.read_u32(self.reg.sa); + debug!("popped {:#010x} from {:#010x}", v, self.reg.sa); + self.reg.sa += 4; + v + } + + fn push_cs(&mut self) { + self.reg.cs -= 4; + self.mmap.write_u32(self.reg.cs, self.reg.ra); + debug!("pushed {:#010x} onto callstack", self.reg.ra); + } + + fn pop_cs(&mut self) { + self.reg.ra = self.mmap.read_u32(self.reg.cs); + self.reg.cs += 4; + debug!("popped {:#010x} from callstack", self.reg.ra); + } +} diff --git a/src/console.rs b/src/console.rs new file mode 100644 index 0000000..652d6de --- /dev/null +++ b/src/console.rs @@ -0,0 +1,44 @@ +use alea_core::prelude::*; +use bytes::BytesMut; + +pub struct Console(pub BytesMut); + +impl Memory for Console { + fn size(&self) -> u32 { + self.0.size() + } + + fn read(&self, addr: u32) -> u8 { + self.0.read(addr) + } + + fn write(&mut self, addr: u32, value: u8) { + self.0.write(addr, value) + } + + fn post_step(&mut self) { + if self.ready_to_flush() { + self.flush(); + } + } +} + +impl Console { + fn ready_to_flush(&self) -> bool { + self.0.iter().any(|thing| *thing == 0x17) + } + + fn flush(&mut self) { + let mut bytes = Vec::new(); + for byte in self.0.iter_mut() { + let copy = *byte; + *byte = 0; + if copy == 0x17 { + break; + } + bytes.push(copy); + } + let s = String::from_utf8_lossy(bytes.as_slice()); + print!("{}", s); + } +} diff --git a/src/hello.alea b/src/hello.alea new file mode 100644 index 0000000000000000000000000000000000000000..513eaaa9818475af0e342d8d79bdae01d50f7e2a GIT binary patch literal 52 zcmZP&V9aM|U|?XF0HhUwGy{h?h-Bbob!T8;;b&xEU}9io^=A=aXlI$g(9XudVGaO( ClLY$! literal 0 HcmV?d00001 diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..77b042d --- /dev/null +++ b/src/main.rs @@ -0,0 +1,32 @@ +mod console; + +use alea_core::prelude::*; + +use bytes::BytesMut; +use console::Console; +use tracing::info; +use tracing_subscriber::EnvFilter; + +fn main() { + tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_default_env()) + .init(); + + let mut cpu = CPU::default(); + + let hello_world_data = BytesMut::from(b"hello, world!\n\x17".as_slice()); + let program = BytesMut::from(include_bytes!("hello.alea").as_slice()); + let mem = BytesMut::zeroed(0x4000); + let console = Console(BytesMut::zeroed(0x20)); + + cpu.mmap.add(program, 0x0).unwrap(); + cpu.mmap.add(hello_world_data, 0x2000).unwrap(); + cpu.mmap.add(mem, 0x4000).unwrap(); + cpu.mmap.add(console, 0x8000).unwrap(); + + cpu.step_wait = false; + + info!("running with {}", cpu.mmap); + + cpu.run_until_halt().unwrap(); +}