Browse Source

hello, world!

main
xenua 6 months ago
commit
c7a118cee8
Signed by: xenua
GPG Key ID: 8F93B68BD37255B8
  1. 1
      .gitignore
  2. 297
      Cargo.lock
  3. 17
      Cargo.toml
  4. 7
      README.md
  5. 2
      alea-core/.gitignore
  6. 11
      alea-core/Cargo.toml
  7. 135
      alea-core/src/decode.rs
  8. 404
      alea-core/src/isa.rs
  9. 12
      alea-core/src/lib.rs
  10. 177
      alea-core/src/memory.rs
  11. 341
      alea-core/src/vcore.rs
  12. 44
      src/console.rs
  13. BIN
      src/hello.alea
  14. 32
      src/main.rs

1
.gitignore vendored

@ -0,0 +1 @@ @@ -0,0 +1 @@
/target

297
Cargo.lock generated

@ -0,0 +1,297 @@ @@ -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"

17
Cargo.toml

@ -0,0 +1,17 @@ @@ -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"] }

7
README.md

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
## what
virtual cpu
goes beep boop
complete enough to do hello world

2
alea-core/.gitignore vendored

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
/target
/Cargo.lock

11
alea-core/Cargo.toml

@ -0,0 +1,11 @@ @@ -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"

135
alea-core/src/decode.rs

@ -0,0 +1,135 @@ @@ -0,0 +1,135 @@
use crate::{
isa::{CmpFlags, InstVariant, Instruction, InstructionData},
vcore::CPUError,
};
use tracing::{debug, trace};
impl TryFrom<u32> for Instruction {
type Error = CPUError;
fn try_from(value: u32) -> Result<Self, Self::Error> {
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<u32> 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<u32> 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 }
}
}

404
alea-core/src/isa.rs

@ -0,0 +1,404 @@ @@ -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<u8> for Register {
type Error = ();
fn try_from(value: u8) -> Result<Self, Self::Error> {
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<T: Ord>(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<State> 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<u8> 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<u8> 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<Register> 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<Register> 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),
}

12
alea-core/src/lib.rs

@ -0,0 +1,12 @@ @@ -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};
}

177
alea-core/src/memory.rs

@ -0,0 +1,177 @@ @@ -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<dyn Memory>;
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<MappedMemory>,
}
impl MemoryMap {
fn pos_resp(&self, addr: u32) -> Option<usize> {
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);
}
}

341
alea-core/src/vcore.rs

@ -0,0 +1,341 @@ @@ -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);
}
}

44
src/console.rs

@ -0,0 +1,44 @@ @@ -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);
}
}

BIN
src/hello.alea

Binary file not shown.

32
src/main.rs

@ -0,0 +1,32 @@ @@ -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();
}
Loading…
Cancel
Save