diff --git a/Cargo.toml b/Cargo.toml index 7115af1..f9d7779 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["dsa/common", "dsa/assembler_old", "dsa/emulator", "dsa/linker", "dsa/compiler"] +members = ["dsa/common", "dsa/assembler", "dsa/emulator", "dsa/linker", "dsa/compiler"] resolver = "3" [workspace.package] diff --git a/doc/interrupts.md b/doc/interrupts.md new file mode 100644 index 0000000..07958c3 --- /dev/null +++ b/doc/interrupts.md @@ -0,0 +1,27 @@ +0 => **InvalidInterrupt** – CPU exception for an invalid interrupt request +1 => **PageFault** – CPU exception triggered by an illegal memory access +2 => **ProtectionFault** – CPU exception for a protection violation (e.g., privilege level) +3 => **InvalidOpcode** – CPU exception for executing an undefined opcode +4 => **DivideByZero** – CPU exception when dividing by zero +5 => **StackOverflow** – CPU exception due to stack overflow +6 => **WriteToReadOnly** – CPU exception for writing to a read‑only segment +7 => **ReadFromWriteOnly** – CPU exception for reading from a write‑only segment +8 => **UnmappedIo** – CPU exception for accessing unmapped I/O + +> **Reserved Range (9–31):** Future CPU exceptions + +--- + +### IRQ Range (32–63) + +- **Hardware** – Represents hardware interrupt requests. + +32 => **SerialIn** – Interrupt triggered by serial input + +--- + +### Syscall Range (64–255) + +- **Software(u8)** – Represents system calls. + The enum is aliased to start at value **128**, so valid syscall numbers are 128‑255. + Programs typically use the range **64–127** for OS‑defined syscalls, while values ≥128 are reserved for software interrupts. diff --git a/dsa/assembler_old/Cargo.toml b/dsa/assembler/Cargo.toml similarity index 68% rename from dsa/assembler_old/Cargo.toml rename to dsa/assembler/Cargo.toml index 6519294..6165c2d 100644 --- a/dsa/assembler_old/Cargo.toml +++ b/dsa/assembler/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "assembler_old" +name = "assembler" version.workspace = true edition.workspace = true authors.workspace = true @@ -13,7 +13,9 @@ name = "assembler" path = "src/lib.rs" [dependencies] +clap = { version = "4.6.0", features = ["derive"] } common = { path = "../common" } num_cpus = "1.17.0" +strum = { version = "0.28.0", features = ["derive"] } threadpool = "1.8.1" diff --git a/dsa/assembler_old/src/assemblerv2/parser.rs b/dsa/assembler/example.dsa similarity index 100% rename from dsa/assembler_old/src/assemblerv2/parser.rs rename to dsa/assembler/example.dsa diff --git a/dsa/assembler_old/src/assembler/codegen.rs b/dsa/assembler/src/assembler/codegen.rs similarity index 100% rename from dsa/assembler_old/src/assembler/codegen.rs rename to dsa/assembler/src/assembler/codegen.rs diff --git a/dsa/assembler_old/src/assembler/expand.rs b/dsa/assembler/src/assembler/expand.rs similarity index 100% rename from dsa/assembler_old/src/assembler/expand.rs rename to dsa/assembler/src/assembler/expand.rs diff --git a/dsa/assembler_old/src/assembler/lexer.rs b/dsa/assembler/src/assembler/lexer.rs similarity index 100% rename from dsa/assembler_old/src/assembler/lexer.rs rename to dsa/assembler/src/assembler/lexer.rs diff --git a/dsa/assembler_old/src/assembler/macros.rs b/dsa/assembler/src/assembler/macros.rs similarity index 100% rename from dsa/assembler_old/src/assembler/macros.rs rename to dsa/assembler/src/assembler/macros.rs diff --git a/dsa/assembler_old/src/assembler/mod.rs b/dsa/assembler/src/assembler/mod.rs similarity index 98% rename from dsa/assembler_old/src/assembler/mod.rs rename to dsa/assembler/src/assembler/mod.rs index 69f1e5d..d2f3262 100644 --- a/dsa/assembler_old/src/assembler/mod.rs +++ b/dsa/assembler/src/assembler/mod.rs @@ -26,6 +26,8 @@ pub mod model; pub mod parser; pub mod resolver; +use crate::assemblerv2::lexer::Lexer; + // Re-exports pub use self::{ codegen::codegen, @@ -173,6 +175,7 @@ fn prepare_dependency( println!("{:20} {:20}", "Tokenising", filename); let tokens = lexer::lexer(src, file_hash)?; + // let tokens = Lexer::new(src, file_hash).run()?; println!("{:20} {:20}", "Parsing", filename); let parsed = Parser::parse_nodes(tokens)?; diff --git a/dsa/assembler_old/src/assembler/model.rs b/dsa/assembler/src/assembler/model.rs similarity index 100% rename from dsa/assembler_old/src/assembler/model.rs rename to dsa/assembler/src/assembler/model.rs diff --git a/dsa/assembler_old/src/assembler/parser.rs b/dsa/assembler/src/assembler/parser.rs similarity index 98% rename from dsa/assembler_old/src/assembler/parser.rs rename to dsa/assembler/src/assembler/parser.rs index 31abfe3..62bd1b6 100644 --- a/dsa/assembler_old/src/assembler/parser.rs +++ b/dsa/assembler/src/assembler/parser.rs @@ -42,16 +42,12 @@ impl Default for Program { impl Parser { pub fn parse_nodes(tokens: Vec) -> Result, AssembleError> { - println!("parsing"); - let mut self_ = Self { tokens: tokens.into_iter().rev().collect(), nodes: vec![], }; while !self_.tokens.is_empty() { - println!("NEXT {}", self_.peek_next().unwrap()); - let ins = self_.parse_instruction()?; self_.nodes.push(ins); } @@ -214,13 +210,10 @@ impl Parser { } Opcode::Ieq | Opcode::Ine | Opcode::Ilt | Opcode::Igt | Opcode::Ile | Opcode::Ige => { - println!("yes"); let src1 = expect_type!(self.next()?, Register, Symbol)?; let src2 = expect_type!(self.next()?, Register, Symbol)?; let dest = expect_type!(self.next()?, Register, Symbol)?; args = vec![src1, src2, dest]; - - println!("done"); } Opcode::Jez | Opcode::Jnz => { @@ -307,8 +300,6 @@ impl Parser { fn parse_data_definition(&mut self, opcode: Opcode) -> Result, AssembleError> { let mut values = Vec::new(); - println!("e {:?}", self.peek_next()); - let name = expect_type!(self.next()?, Symbol)?; values.push(name); diff --git a/dsa/assembler_old/src/assembler/resolver.rs b/dsa/assembler/src/assembler/resolver.rs similarity index 100% rename from dsa/assembler_old/src/assembler/resolver.rs rename to dsa/assembler/src/assembler/resolver.rs diff --git a/dsa/assembler/src/assemblerv2/error.rs b/dsa/assembler/src/assemblerv2/error.rs new file mode 100644 index 0000000..0c7f03e --- /dev/null +++ b/dsa/assembler/src/assemblerv2/error.rs @@ -0,0 +1,16 @@ +use super::Token; + +#[derive(Debug, Clone)] +pub enum AssembleError { + UnexpectedToken { expected: String, got: Token }, + UnexpectedEof, +} + +impl AssembleError { + pub fn unexpected_token(token: Token, expected: &str) -> Self { + AssembleError::UnexpectedToken { + expected: expected.to_string(), + got: token, + } + } +} diff --git a/dsa/assembler_old/src/assemblerv2/lexer.rs b/dsa/assembler/src/assemblerv2/lexer.rs similarity index 62% rename from dsa/assembler_old/src/assemblerv2/lexer.rs rename to dsa/assembler/src/assemblerv2/lexer.rs index 929eb75..f280404 100644 --- a/dsa/assembler_old/src/assemblerv2/lexer.rs +++ b/dsa/assembler/src/assemblerv2/lexer.rs @@ -1,4 +1,8 @@ -use crate::assembler::{AssembleError, Token}; +use std::{str::FromStr, thread, time::Duration}; + +use common::{asm::AsmOpcode, prelude::Register}; + +use crate::assembler::{AssembleError, Module, Opcode, Symbol, Token, lexer::parse_opcode}; pub struct Lexer { src: Vec, @@ -13,10 +17,25 @@ impl Lexer { } } - pub fn lexer(&mut self) -> Result, AssembleError> { + pub fn run(&mut self) -> Result, AssembleError> { let mut tokens = Vec::new(); while !self.src.is_empty() { + // thread::sleep(Duration::from_millis(10)); + // println!("{}", *self.src.last().unwrap() as char); + // println!("{:?}", tokens); + + if self.src.ends_with(b"\n") + || self.src.ends_with(b"\r") + || self.src.ends_with(b" ") + || self.src.ends_with(b",") + || self.src.ends_with(b".") + || self.src.ends_with(b":") + { + self.src.pop(); + continue; + } + if self.src.ends_with(b"//") { self.src.pop(); self.src.pop(); @@ -24,7 +43,7 @@ impl Lexer { continue; } - if self.src.ends_with(b"/*") { + if self.src.ends_with(b"*/") { self.src.pop(); self.src.pop(); self.parse_comment_multiline(); @@ -34,15 +53,64 @@ impl Lexer { if self.src.ends_with(b"\"") { self.src.pop(); tokens.push(Token::StringLit(self.parse_string_literal())); + continue; } if self.src.ends_with(b"'") { self.src.pop(); tokens.push(Token::CharLit(self.parse_char_literal())); + continue; } if self.src.last().unwrap().is_ascii_digit() { tokens.push(Token::Immediate(self.parse_number())); + continue; + } + + if matches!(self.src.last().unwrap(), b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_') { + let mut buffer = String::new(); + + while matches!(self.src.last().unwrap(), b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_') + { + buffer.push(self.src.pop().unwrap() as char); + } + + if let Ok(opcode) = Opcode::from_str(&buffer) { + tokens.push(Token::Opcode(opcode)); + continue; + } + + if let Ok(register) = Register::from_str(&buffer) { + tokens.push(Token::Register(register)); + continue; + } + + // Check for qualified symbol: identifier::identifier + if self.src.ends_with(b"::") { + self.src.pop(); + self.src.pop(); + let mut rhs = String::new(); + while matches!( + self.src.last(), + Some(b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_') + ) { + rhs.push(self.src.pop().unwrap() as char); + } + if rhs.is_empty() { + return Err(AssembleError::Generic); + } + tokens.push(Token::Symbol(Symbol { + name: rhs, + module: Module::Unresolved(buffer), + })); + continue; + } + + tokens.push(Token::Symbol(Symbol { + name: buffer, + module: Module::Resolved(self.module_id), + })); + continue; } } @@ -50,7 +118,7 @@ impl Lexer { } fn parse_number(&mut self) -> u32 { - match self.src.last_chunk::<2>() { + match self.src.last_chunk::<2>().map(|[x, y]| [*y, *x]).as_ref() { Some(b"0x") => { self.src.pop(); self.src.pop(); @@ -93,10 +161,13 @@ impl Lexer { } n } - _ if self.src.last().is_some_and(|b| b.is_ascii_digit()) => { + // decimal number + _ if let Some(x) = self.src.last() + && x.is_ascii_digit() => + { let mut n = 0u32; while let Some(&b) = self.src.last() { - if b.is_ascii_digit() { + if matches!(b, b'0'..=b'9') { self.src.pop(); n = n * 10 + (b - b'0') as u32; } else { @@ -105,35 +176,27 @@ impl Lexer { } n } - None if let Some(x) = self.src.last() - && x.is_ascii_digit() => - { - let x = *x; - self.src.pop(); - (x - b'0') as u32 - } _ => panic!("Invalid syntax"), } } // returns nothing as the assembler discards comments fn parse_comment(&mut self) { - while !self.src.is_empty() && self.src.ends_with(b"\n") { + while !self.src.is_empty() && !self.src.ends_with(b"\n") { self.src.pop(); } } fn parse_comment_multiline(&mut self) { - while !self.src.is_empty() && self.src.ends_with(b"*/") { + while !self.src.is_empty() && !self.src.ends_with(b"*/") { self.src.pop(); } } fn parse_string_literal(&mut self) -> String { let mut result = String::new(); - while let Some(ch) = self.src.pop() - && self.src.ends_with(b"\"") - { + while self.src.last().is_some() && !self.src.ends_with(b"\"") { + let ch = self.src.pop().unwrap(); if ch == b'\\' { let escaped = self .src @@ -154,6 +217,8 @@ impl Lexer { result.push(ch as char); } + self.src.pop().unwrap(); + result } diff --git a/dsa/assembler/src/assemblerv2/mod.rs b/dsa/assembler/src/assemblerv2/mod.rs new file mode 100644 index 0000000..ac38c29 --- /dev/null +++ b/dsa/assembler/src/assemblerv2/mod.rs @@ -0,0 +1,46 @@ +// mod error; +pub mod lexer; +// pub mod parser; + +use core::fmt; + +use crate::assembler::{AssembleError, Symbol}; +// use common::{asm::AsmOpcode, prelude::Register}; +use lexer::Lexer; +// use parser::Parser; + +pub fn asm(input: &str) -> Result, AssembleError> { + let mut lexer = Lexer::new(input.to_string(), 0); + + let tokens = lexer.run()?; + + // let ast = Parser::new(tokens).parse().unwrap(); + + // println!("{:#?}", ast); + + // let ast = parser::parse(tokens)?; + Ok(vec![]) +} + +// #[derive(Debug, Clone)] +// pub enum Token { +// Symbol(Symbol), +// Register(Register), +// Immediate(u32), +// StringLit(String), +// CharLit(char), +// Opcode(AsmOpcode), +// } + +// impl fmt::Display for Token { +// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +// match self { +// Self::Symbol(symbol) => write!(f, "{symbol}"), +// Self::Register(register) => write!(f, "{register}",), +// Self::Immediate(immediate) => write!(f, "{immediate}",), +// Self::StringLit(string_lit) => write!(f, "{string_lit}",), +// Self::CharLit(char_lit) => write!(f, "{char_lit}",), +// Self::Opcode(opcode) => write!(f, "{opcode}",), +// } +// } +// } diff --git a/dsa/assembler/src/assemblerv2/parser.rs b/dsa/assembler/src/assemblerv2/parser.rs new file mode 100644 index 0000000..c77d978 --- /dev/null +++ b/dsa/assembler/src/assemblerv2/parser.rs @@ -0,0 +1,130 @@ +use std::path::PathBuf; + +use common::asm::{AsmInstruction, AsmOpcode}; + +use crate::assembler::Symbol; + +use crate::{expect_tt, expect_value}; + +use super::{Token, error::AssembleError}; + +pub struct AsmNode { + pub label: Option, + pub instruction: Option, +} + +pub struct Parser { + tokens: Vec, + nodes: Option>, + dependencies: Vec, +} + +impl Parser { + pub fn new(tokens: Vec) -> Self { + Parser { + tokens, + nodes: Some(vec![]), + dependencies: vec![], + } + } + + pub fn parse(&mut self) -> Result, AssembleError> { + Ok(vec![]) + } + + fn parse_instruction(&mut self) -> Result { + let label = expect_value!(self.peek_next()?, Symbol).ok(); + if label.is_some() { + self.next()?; + } + + let opcode = expect_value!(self.next()?, Opcode)?; + + let ins = match opcode { + AsmOpcode::Mov => { + let src = expect_value!(self.next()?, Register)?; + let dest = expect_value!(self.next()?, Register)?; + AsmInstruction::Mov { src, dest } + } + AsmOpcode::CMov => { + let src = expect_value!(self.next()?, Register)?; + let dest = expect_value!(self.next()?, Register)?; + let condition = expect_value!(self.next()?, Register)?; + AsmInstruction::CMov { + src, + dest, + condition, + } + } + + // AsmOpcode::Ldb => { + // let src = expect_value!(self.next()?, Register)?; + // let dest = expect_value!(self.next()?, Register)?; + // AsmInstruction::Ldb { src, dest } + // } + _ => { + todo!() + } + }; + + Ok(AsmNode { + label, + instruction: Some(ins), + }) + } + + fn next(&mut self) -> Result { + if self.tokens.is_empty() { + Err(AssembleError::UnexpectedEof) + } else { + Ok(self + .tokens + .pop() + .expect("tokens vector was unexpectedly empty in next()")) + } + } + + fn peek_next(&self) -> Result { + if self.tokens.is_empty() { + Err(AssembleError::UnexpectedEof) + } else { + Ok(self + .tokens + .last() + .expect("peek_next called on empty tokens vector") + .clone()) + } + } +} + +#[macro_export] +macro_rules! expect_tt { + ($token:expr, $($variant:ident),+) => {{ + let tok = $token; + let tt = token.tt().to_string(); + + match tt.as_str() { + $( + stringify!($variant) => Ok(token), + )+ + _ => { + // let expected = format!("[{}]", vec![$(stringify!($variant)),+].join(" | ")); + Err(AssembleError::unexpected_token( + tok, + format!("[{}]", vec![$(stringify!($variant)),+].join(" | ")).as_str()) + ) + } + } + }}; +} + +#[macro_export] +macro_rules! expect_value { + ($token:expr, $variant:ident) => {{ + let tok = $token; + match tok.clone() { + Token::$variant(first, ..) => Ok(first), + _ => Err(AssembleError::unexpected_token(tok, stringify!($variant))), + } + }}; +} diff --git a/dsa/assembler_old/src/lib.rs b/dsa/assembler/src/lib.rs similarity index 100% rename from dsa/assembler_old/src/lib.rs rename to dsa/assembler/src/lib.rs diff --git a/dsa/assembler/src/main.rs b/dsa/assembler/src/main.rs new file mode 100644 index 0000000..06f58eb --- /dev/null +++ b/dsa/assembler/src/main.rs @@ -0,0 +1,33 @@ +use assembler::prelude::*; +use clap::{Parser, arg, command}; +use std::{fs, path::PathBuf}; + +#[derive(Parser, Debug, Clone)] +#[command(version, about, long_about = None)] +struct Args { + #[arg(short = 'i')] + pub input_path: PathBuf, + #[arg(short = 'o')] + pub output_path: PathBuf, +} + +fn main() { + // Parse command line arguments + let args = Args::parse(); + + let mut engine = Assembler::new(PathBuf::from(args.input_path)); + engine.start(()); + let result = engine.output().expect("assembler failed."); + + if let Err(e) = fs::write(args.output_path, result) { + eprintln!("Failed to write to output file: {e}"); + std::process::exit(1); + } + + // let input = fs::read_to_string("../../dsa_resources/framebuffer.dsa").unwrap(); + // let output = asm(&input).unwrap(); + + // for token in output { + // println!("{:?}", token); + // } +} diff --git a/dsa/assembler_old/src/assemblerv2/mod.rs b/dsa/assembler_old/src/assemblerv2/mod.rs deleted file mode 100644 index 6054fb1..0000000 --- a/dsa/assembler_old/src/assemblerv2/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod lexer; -pub mod parser; diff --git a/dsa/assembler_old/src/main.rs b/dsa/assembler_old/src/main.rs deleted file mode 100644 index 2ab7227..0000000 --- a/dsa/assembler_old/src/main.rs +++ /dev/null @@ -1,21 +0,0 @@ -use common::{self as _}; - -use assembler::prelude::*; -use std::{fs, io::Write, path::PathBuf}; - -fn main() { - // Parse command line arguments - let args: Vec = std::env::args().collect(); - - let input_path = &args[1]; - let output_path = &args[2]; - - let mut engine = Assembler::new(PathBuf::from(input_path)); - engine.start(()); - let result = engine.output().expect("assembler failed."); - - if let Err(e) = fs::write(output_path, result) { - eprintln!("Failed to write to output file: {e}"); - std::process::exit(1); - } -} diff --git a/dsa/common/Cargo.toml b/dsa/common/Cargo.toml index d8a1436..c2431e4 100644 --- a/dsa/common/Cargo.toml +++ b/dsa/common/Cargo.toml @@ -7,3 +7,4 @@ edition = "2024" path = "src/lib.rs" [dependencies] +strum = { version = "0.28.0", features = ["derive"] } diff --git a/dsa/common/src/asm/mod.rs b/dsa/common/src/asm/mod.rs index 75e82cd..d407520 100644 --- a/dsa/common/src/asm/mod.rs +++ b/dsa/common/src/asm/mod.rs @@ -1,5 +1,8 @@ +use strum::{Display, EnumString}; + use crate::prelude::Register; +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Location { offset: u16, register: Option, @@ -20,13 +23,95 @@ impl Location { // if a register is being used already we have to add the offset to that. } +#[derive(Debug, Clone, PartialEq, Eq)] pub enum ImmConst { Label(String), Imm16(u16), Imm32(u32), } -pub enum PseudoInstruction { +#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumString, Display)] +#[strum(ascii_case_insensitive)] +pub enum AsmOpcode { + Nop, + + Mov, + CMov, + + Ldb, + Ldbs, + Ldh, + Ldhs, + Ldw, + + Stb, + Sth, + Stw, + + Lli, + Lui, + Lwi, + + Jmp, + Jez, + Jnz, + Jic, + Jnc, + + Ieq, + Ine, + Igt, + Ige, + Ilt, + Ile, + + Shl, + Shr, + Add, + Sub, + AddI, + SubI, + + And, + Or, + Not, + Xor, + Nand, + Nor, + Xnor, + + Int, + IRet, + Hlt, + + // Function instructions + Call, + Ret, + + // Stack ops + Push, + Pop, + + // data directives + Db, + Dh, + Dw, + + // data reservations + Resb, + Resh, + Resw, + + // Function pseudo-instructions + Func, + Return, + + // include directive + Include, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum AsmInstruction { Nop, // move diff --git a/dsa/common/src/isa/instructions/mod.rs b/dsa/common/src/isa/instructions/mod.rs index 8568d83..1c1f8c1 100644 --- a/dsa/common/src/isa/instructions/mod.rs +++ b/dsa/common/src/isa/instructions/mod.rs @@ -222,7 +222,7 @@ impl fmt::Debug for Instruction { Some(op @ (Opcode::Shl | Opcode::Shr)) => { write!( f, - "{op:?} {src:?}, {dest:?}, r:{rshamt:?} + i:{ishamt}", + "{op:?} {src:?}, {dest:?}, {rshamt:?} + {ishamt}", op = op, src = self.src1_checked(), dest = self.dest_checked(), diff --git a/dsa/common/src/isa/register.rs b/dsa/common/src/isa/register.rs index a54238b..113b7ce 100644 --- a/dsa/common/src/isa/register.rs +++ b/dsa/common/src/isa/register.rs @@ -1,8 +1,11 @@ use std::fmt; -#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)] +use strum::{Display, EnumString}; + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Default, EnumString)] #[repr(u8)] #[non_exhaustive] +#[strum(ascii_case_insensitive)] pub enum Register { // general purpose Rg0 = 0, @@ -22,24 +25,35 @@ pub enum Register { Rge = 14, Rgf = 15, - // special purpose - Acc = 16, + Acc = 16, // temp/scratch register + + // stack registers Spr = 17, Bpr = 18, - Ret = 19, - Idr = 20, - Mmr = 21, + + Idr = 20, // points to interrupt descriptor table + Mmr = 21, // points to page tables // system - read only - Zero = 22, - Sts = 23, - Cir = 24, - Pcx = 25, + Zero = 22, // always reads as zero. + Sts = 23, // status register for flags + Cir = 24, // current instruction register + Pcx = 25, // program counter + + #[strum(disabled, to_string = "Ret")] + Ret = 19, // used under the hood by call/ret instructions but not used directly in asm. #[default] + #[strum(disabled, to_string = "Null")] // don't include this in FromStr impl Null = 26, } +impl std::fmt::Display for Register { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + impl Register { /// Total number of registers, not counting Null as it's not a real register. /// reading or writing to Null is undefined behaviour. @@ -66,90 +80,3 @@ impl Register { Ok(unsafe { Self::from_u8_unchecked(idx) }) } } - -impl fmt::Display for Register { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}", - match self { - // general purpose - Self::Rg0 => "Rg0", - Self::Rg1 => "Rg1", - Self::Rg2 => "Rg2", - Self::Rg3 => "Rg3", - Self::Rg4 => "Rg4", - Self::Rg5 => "Rg5", - Self::Rg6 => "Rg6", - Self::Rg7 => "Rg7", - Self::Rg8 => "Rg8", - Self::Rg9 => "Rg9", - Self::Rga => "Rga", - Self::Rgb => "Rgb", - Self::Rgc => "Rgc", - Self::Rgd => "Rgd", - Self::Rge => "Rge", - Self::Rgf => "Rgf", - - // special purpose - Self::Acc => "Acc", - Self::Spr => "Spr", - Self::Bpr => "Bpr", - Self::Ret => "Ret", - Self::Idr => "Idr", - Self::Mmr => "Mmr", - - // system - read only - Self::Zero => "Zero", - Self::Sts => "Sts", - Self::Cir => "Cir", - Self::Pcx => "Pcx", - - Self::Null => "Null", - } - ) - } -} - -impl std::str::FromStr for Register { - type Err = (); - - fn from_str(s: &str) -> Result { - match s.to_ascii_lowercase().as_str() { - // general purpose - "rg0" => Ok(Self::Rg0), - "rg1" => Ok(Self::Rg1), - "rg2" => Ok(Self::Rg2), - "rg3" => Ok(Self::Rg3), - "rg4" => Ok(Self::Rg4), - "rg5" => Ok(Self::Rg5), - "rg6" => Ok(Self::Rg6), - "rg7" => Ok(Self::Rg7), - "rg8" => Ok(Self::Rg8), - "rg9" => Ok(Self::Rg9), - "rga" => Ok(Self::Rga), - "rgb" => Ok(Self::Rgb), - "rgc" => Ok(Self::Rgc), - "rgd" => Ok(Self::Rgd), - "rge" => Ok(Self::Rge), - "rgf" => Ok(Self::Rgf), - - // special purpose - "acc" => Ok(Self::Acc), - "spr" => Ok(Self::Spr), - "bpr" => Ok(Self::Bpr), - // "ret" => Ok(Self::Ret), - "idr" => Ok(Self::Idr), - "mmr" => Ok(Self::Mmr), - - // system - read only - "zero" => Ok(Self::Zero), - "sts" => Ok(Self::Sts), - "cir" => Ok(Self::Cir), - "pcx" => Ok(Self::Pcx), - - "null" => Ok(Self::Null), - _ => Err(()), - } - } -} diff --git a/dsa/emulator/Cargo.toml b/dsa/emulator/Cargo.toml index 0add207..cf12517 100644 --- a/dsa/emulator/Cargo.toml +++ b/dsa/emulator/Cargo.toml @@ -26,8 +26,11 @@ common = { path = "../common" } egui = "0.33.3" eframe = "0.33.3" egui_tiles = "0.14.1" -#assembler = { path = "../assembler" } - +egui_code_editor = "0.2.21" +egui_file = "0.25.0" +dirs = "6.0.0" +assembler = { path = "../assembler" } +egui_extras = "0.33.3" [features] default = ["mainstore-prealloc"] diff --git a/dsa/emulator/src/args.rs b/dsa/emulator/src/args.rs index 4bec2ab..a35ecfd 100644 --- a/dsa/emulator/src/args.rs +++ b/dsa/emulator/src/args.rs @@ -17,9 +17,16 @@ pub struct DsaArgs { #[arg(long = "iomap")] io_map: Option, + + #[arg(long = "headless")] + headless: bool, } impl DsaArgs { + pub fn headless(&self) -> bool { + self.headless + } + pub fn get_memory_map(&self) -> MemoryMap { self.memory_map .as_ref() diff --git a/dsa/emulator/src/backend/processor/interrupts.rs b/dsa/emulator/src/backend/processor/interrupts.rs index 268be36..2698b31 100644 --- a/dsa/emulator/src/backend/processor/interrupts.rs +++ b/dsa/emulator/src/backend/processor/interrupts.rs @@ -15,6 +15,7 @@ pub enum Interrupt { // IRQ's (32-63) Hardware(u8), + SerialIn = 32, // Syscalls (64-255) // OS defined, program uses INT 64-127 @@ -33,6 +34,7 @@ impl Interrupt { Interrupt::WriteToReadOnly => 6, Interrupt::ReadFromWriteOnly => 7, Interrupt::UnmappedIo => 8, + Interrupt::SerialIn => 32, Interrupt::Hardware(x) => { if *x < 32 || *x > 63 { 0 diff --git a/dsa/emulator/src/backend/processor/processor.rs b/dsa/emulator/src/backend/processor/processor.rs index 4339c14..5d4d3a6 100644 --- a/dsa/emulator/src/backend/processor/processor.rs +++ b/dsa/emulator/src/backend/processor/processor.rs @@ -23,7 +23,7 @@ pub struct Emulator { shared_state: Arc, // Interrupts - interrupts: mpsc::Receiver, + interrupts: mpsc::Receiver, pending_fault: Option, // memory @@ -45,7 +45,7 @@ pub struct Emulator { unsafe impl Send for Emulator {} impl Emulator { pub fn new() -> Self { - let (sender, receiver) = mpsc::channel::(); + let (sender, receiver) = mpsc::channel::(); Self { interrupts: receiver, @@ -163,6 +163,8 @@ impl Emulator { while self.shared_state.reseting.load(Ordering::Relaxed) { thread::sleep(Duration::from_millis(100)); } + + self.internal_state.halted = false; } } @@ -182,19 +184,19 @@ impl Emulator { self.check_update(); } - // if the cpu didn't halt on it's own then it's ready to run again. - if !self.internal_state.halted { - return; - } - loop { // Check for interrupts. - if let Ok(code) = self.interrupts.recv_timeout(Duration::from_millis(100)) { + if let Ok(int) = self.interrupts.recv_timeout(Duration::from_millis(100)) { self.internal_state.halted = false; - self.interrupt(Interrupt::Software(code)); + self.interrupt(int); break; } + // if the cpu didn't halt on it's own then it's ready to run again. + if !self.internal_state.halted { + return; + } + // UI is resetting the emulator self.check_update(); } @@ -230,7 +232,7 @@ impl Emulator { } match self.interrupts.try_recv() { - Ok(code) => self.interrupt(Interrupt::Software(code)), + Ok(int) => self.interrupt(int), Err(TryRecvError::Disconnected) => break 'emu, Err(TryRecvError::Empty) => {} } @@ -270,14 +272,21 @@ impl Emulator { #[inline] fn interrupt(&mut self, int: Interrupt) { - let idt = self.reg(Register::Idr); + self.push(Register::Pcx); + *self.mut_reg(Register::Pcx) = + self.mem_read_word(self.reg(Register::Idr) + int.code() as u32 * 4) + } + #[inline] + fn push(&mut self, reg: Register) { *self.mut_reg(Register::Spr) -= 4; - let spr = self.reg(Register::Spr); - let pcx = self.reg(Register::Pcx); - self.mem_write_word(spr, pcx); + self.mem_write_word(self.reg(Register::Spr), self.reg(reg)); + } - *self.mut_reg(Register::Pcx) = self.mem_read_word(idt + int.code() as u32 * 4) + #[inline] + fn pop(&mut self, reg: Register) { + *self.mut_reg(reg) = self.mem_read_word(self.reg(Register::Spr)); + *self.mut_reg(Register::Spr) += 4; } #[inline] @@ -477,24 +486,15 @@ impl Emulator { self.interrupt(Interrupt::Software(word.imm16() as u8)); } Some(Opcode::Hlt) => self.halt(), - Some(Opcode::IRet) => todo!(), + Some(Opcode::IRet) => { + self.pop(Register::Ret); + *self.mut_reg(Register::Pcx) = self.reg(Register::Ret); + } None => {} } } } - #[inline] - fn push(&mut self, reg: Register) { - *self.mut_reg(Register::Spr) -= 4; - self.mem_write_word(self.reg(Register::Spr), self.reg(reg)); - } - - #[inline] - fn pop(&mut self, reg: Register) { - *self.mut_reg(reg) = self.mem_read_word(self.reg(Register::Spr)); - *self.mut_reg(Register::Spr) += 4; - } - #[inline] fn mem_read_byte(&mut self, addr: u32) -> u8 { self.mainstore.read_byte(addr) diff --git a/dsa/emulator/src/backend/processor/state.rs b/dsa/emulator/src/backend/processor/state.rs index f0ce121..1ea5ad5 100644 --- a/dsa/emulator/src/backend/processor/state.rs +++ b/dsa/emulator/src/backend/processor/state.rs @@ -4,6 +4,8 @@ use arc_swap::ArcSwap; use crate::backend::processor::processor::ProcessorSnapshot; +use super::interrupts::Interrupt; + pub struct SharedState { pub proc: ArcSwap, @@ -11,11 +13,11 @@ pub struct SharedState { pub paused: AtomicBool, pub update_req: AtomicBool, - interrupt_queue: mpsc::Sender, + pub interrupt_queue: mpsc::Sender, } impl SharedState { - pub fn new(sender: mpsc::Sender) -> Self { + pub fn new(sender: mpsc::Sender) -> Self { Self { reseting: AtomicBool::new(false), proc: ArcSwap::new(Arc::new(ProcessorSnapshot::default())), diff --git a/dsa/emulator/src/frontend/components.rs b/dsa/emulator/src/frontend/components.rs index 4476c75..68ca1b2 100644 --- a/dsa/emulator/src/frontend/components.rs +++ b/dsa/emulator/src/frontend/components.rs @@ -1,6 +1,8 @@ pub mod controller; pub mod display; +pub mod editor; pub mod framebuffer; +pub mod memory; pub mod registers; pub mod serial; diff --git a/dsa/emulator/src/frontend/components/controller.rs b/dsa/emulator/src/frontend/components/controller.rs index ec8ef3c..4205483 100644 --- a/dsa/emulator/src/frontend/components/controller.rs +++ b/dsa/emulator/src/frontend/components/controller.rs @@ -83,13 +83,15 @@ impl Component for Controller { // Status info ui.label(format!( "Status: {}", - match (state.halted, paused) { - (true, false) => "Halted", - (_, false) => "Running", - (_, true) => "Paused", + match paused { + true => "Paused", + false => "Running", } )); + ui.label(format!("Halted: {}", state.halted)); + ui.separator(); + let pcx = state.registers[Register::Pcx as usize]; let clock = state.clock; let time = state.time.as_micros(); @@ -97,7 +99,7 @@ impl Component for Controller { ui.label(format!("Clock: {clock}")); ui.label(format!("Pcx: 0x{pcx:08X}")); - ui.label(format!("Elapsed: {}micros", time)); + ui.label(format!("Elapsed: {} micros", time)); ui.label(format!("Freq: {}MHz", mips)); }); } diff --git a/dsa/emulator/src/frontend/components/display.rs b/dsa/emulator/src/frontend/components/display.rs index 5d131d5..bad05ae 100644 --- a/dsa/emulator/src/frontend/components/display.rs +++ b/dsa/emulator/src/frontend/components/display.rs @@ -1,7 +1,68 @@ use egui::{Color32, FontId, Vec2}; +use crate::{MemoryBank, Page, backend::memory::PhysAddr}; + use super::Component; -pub use crate::io::display::Display; + +pub struct Display { + width: usize, + height: usize, + addr: PhysAddr, + + mem: MemoryBank, + buffer: Vec, + visible: bool, +} + +impl Display { + pub fn new(width: u32, height: u32, addr: PhysAddr, mem: MemoryBank) -> Self { + Self { + width: width as usize, + height: height as usize, + addr, + mem, + buffer: Vec::new(), + visible: true, + } + } + + pub fn visible(&mut self) -> &mut bool { + &mut self.visible + } + + pub fn width(&self) -> usize { + self.width + } + + pub fn height(&self) -> usize { + self.height + } + + fn internal_read(&self) -> Vec { + let pages = (0..((self.width * self.height).div_ceil(Page::SIZE))) + .map(|i| { + let page = self.mem.read_page(self.addr + i as u32 * 4096); + page.as_slice().to_owned() + }) + .flatten() + .take((self.width * self.height) as usize) + .collect::>(); + pages + } + + pub fn changed(&mut self) -> bool { + let temp = self.internal_read(); + if temp != self.buffer { + self.buffer = temp; + return true; + } + false + } + + pub fn read(&mut self) -> Vec { + self.internal_read() + } +} impl Component for Display { fn title(&self) -> &str { diff --git a/dsa/emulator/src/frontend/components/editor/mod.rs b/dsa/emulator/src/frontend/components/editor/mod.rs new file mode 100644 index 0000000..6d36e45 --- /dev/null +++ b/dsa/emulator/src/frontend/components/editor/mod.rs @@ -0,0 +1,799 @@ +use std::fmt::Write; +use std::fs::{self, File}; +use std::sync::Arc; +use std::sync::atomic::Ordering; +use std::{ + ffi::OsStr, + path::{Path, PathBuf}, +}; + +pub mod syntax; + +use assembler::prelude::Assembler; +use common::prelude::Instruction; +use egui::{Align, Context, Key, Layout, Ui}; +use egui_code_editor::{CodeEditor, ColorTheme, Completer, Syntax}; +use egui_extras::{Column, TableBuilder}; +use egui_file::FileDialog; + +use crate::{MemoryBank, Page, SharedState}; + +use super::Component; + +pub struct Editor { + state: Arc, + memory: MemoryBank, + + // editor state + path: Option, + unsaved: bool, + text: String, + buffer: String, + + // editor widget + editor: CodeEditor, + completer: Completer, + + // output / loading + output: Output, + load_offset: u32, + offset_str: String, + + // cursor - currently unused + cursor_col: usize, + cursor_line: usize, + + // file dialogs + open_file_dialog: Option, + save_file_dialog: Option, + + // other + visible: bool, + show_output: bool, + error: Option, +} + +impl Component for Editor { + fn title(&self) -> &'static str { + "Editor" + } + + fn visible(&mut self) -> &mut bool { + &mut self.visible + } + + fn ui(&mut self, ui: &mut Ui, ctx: &Context) { + if self.buffer != self.text { + self.unsaved = true; + } + + ui.vertical(|ui| { + // Top bar + + if ui.input(|i| i.key_pressed(Key::S) && i.modifiers.ctrl) { + self.save(); + } + + self.render_toolbar(ui, ctx); + + ui.add_space(4.0); // Add some spacing instead of just a separator + ui.separator(); + + let remaining_height = f32::max(ui.available_height() - 100.0, 100.0); + + ui.allocate_ui_with_layout( + egui::Vec2::new(ui.available_width(), remaining_height), + Layout::left_to_right(Align::Min), + |ui| { + if self.show_output { + egui::SidePanel::right("Editor Output").show_inside(ui, |ui| { + self.output.ui(ui, ctx); + }); + } + self.render_editor(ui, ctx); + }, + ); + + self.render_bottom_bar(ui, ctx); + }); + } +} + +impl Editor { + #[must_use] + pub fn new(state: Arc, memory: MemoryBank) -> Self { + Self { + memory, + state, + path: None, + text: String::new(), + buffer: String::new(), + completer: Completer::new_with_syntax(&Syntax::default()).with_user_words(), + editor: CodeEditor::default() + .id_source("editor") + .with_fontsize(12.0) + .with_rows(0) + .with_theme(THEME) + .with_syntax(Syntax::default()) + .with_numlines(true), + output: Output::new(), + unsaved: true, + cursor_col: 1, + cursor_line: 1, + visible: false, + load_offset: 0, + offset_str: String::new(), + error: None, + open_file_dialog: None, + save_file_dialog: None, + show_output: false, + } + } + + fn filename(&self) -> &str { + if let Some(path) = &self.path { + return path + .file_name() + .unwrap_or_else(|| OsStr::new("Unnamed!")) + .to_str() + .unwrap_or_else(|| unreachable!("File name should be valid UTF-8.")); + } + "Unnamed!" + } + + fn extension(&self) -> &str { + if let Some(path) = &self.path { + return path + .extension() + .unwrap_or_else(|| OsStr::new("Unknown!")) + .to_str() + .unwrap_or_else(|| unreachable!("File name should be valid UTF-8.")); + } + "Unknown!" + } + + fn save(&mut self) { + if self.open_file_dialog.is_some() { + // TODO: Flash an error stating you can only have one menu open at once. + self.open_file_dialog = None; + } + + if let Some(path) = &self.path { + // Save to existing path + self.buffer = self.text.clone(); + + let text = if path.extension().is_some_and(|ext| ext == "dsb") { + let mut res = Vec::new(); + for line in self.text.lines() { + for line in line.split_whitespace() { + match u32::from_str_radix(line, 16) { + Ok(num) => res.push(num), + Err(e) => { + self.error = Some(format!("Failed to parse file: {e}")); + return; + } + } + } + } + res.into_iter() + .flat_map(u32::to_be_bytes) + .collect::>() + } else { + self.text.as_bytes().to_vec() + }; + + if let Err(why) = std::fs::write(path, text) { + self.error = Some(format!("Failed to save file: {why}")); + } else { + self.unsaved = false; + } + } else { + // Open the save dialog. + let work_dir = std::env::current_dir().unwrap_or_else(|_| { + dirs::home_dir() + .expect("Couldn't get your current working directory or your home directory.") + }); + + if self.save_file_dialog.is_none() { + let mut dialog = FileDialog::save_file().initial_path(work_dir); + dialog.open(); + self.save_file_dialog = Some(dialog); + } + } + } + + fn open(&mut self) { + let work_dir = std::env::current_dir().unwrap_or_else(|_| { + dirs::home_dir() + .expect("Couldn't get your current working directory or your home directory.") + }); + + if self.save_file_dialog.is_some() { + // TODO: Flash an error stating you can only have one menu open at once. + self.save_file_dialog = None; + } + + if self.open_file_dialog.is_none() { + let mut dialog = FileDialog::open_file(); + if let Some(p) = &self.path + && let Some(path) = p.parent().map(Path::to_path_buf) + { + dialog.set_path(path); + } else { + dialog.set_path(work_dir); + } + + dialog.open(); + self.open_file_dialog = Some(dialog); + } + + let syntax = match self.extension() { + "dsa" => syntax::dsa(), + "dsc" => syntax::dsc(), + "rs" => Syntax::rust(), + _ => Syntax::default(), + }; + + self.completer = Completer::new_with_syntax(&syntax).with_user_words(); + self.editor = self.editor.clone().with_syntax(syntax); + } + + fn handle_file_dialogs(&mut self, ctx: &egui::Context) { + // Handle open dialog + if let Some(dialog) = &mut self.open_file_dialog + && dialog.show(ctx).selected() + { + if let Some(file) = dialog.path() { + // check if the file is a binary file + if file.extension().is_some_and(|ext| ext == "dsb") { + match std::fs::read(file) { + Ok(content) => { + let mut res = String::new(); + for (i, b) in content.iter().enumerate() { + _ = write!(res, "{b:02x}"); + if i % 4 == 3 { + res.push('\n'); + } + } + self.text = res.clone(); + self.buffer = res; + self.path = Some(file.to_path_buf()); + self.unsaved = false; + self.error = None; + } + Err(e) => { + self.error = Some(format!("Failed to read file: {e}")); + } + } + } else { + match std::fs::read_to_string(file) { + Ok(content) => { + self.text = content.clone(); + self.buffer = content; + self.path = Some(file.to_path_buf()); + self.unsaved = false; + self.error = None; + } + Err(e) => { + self.error = Some(format!("Failed to read file: {e}")); + } + } + } + } + self.open_file_dialog = None; + } + + // Handle save dialog + if let Some(dialog) = &mut self.save_file_dialog + && dialog.show(ctx).selected() + { + if let Some(file) = dialog.path() { + self.buffer = self.text.clone(); + + let content = if file.extension().is_some_and(|ext| ext == "dsb") { + let mut res = Vec::new(); + for line in self.text.lines() { + for line in line.split_whitespace() { + match u32::from_str_radix(line, 16) { + Ok(num) => res.push(num), + Err(e) => { + self.error = Some(format!("Failed to parse file: {e}")); + return; + } + } + } + } + res.into_iter() + .flat_map(u32::to_be_bytes) + .collect::>() + } else { + self.text.clone().as_bytes().to_vec() + }; + + match std::fs::write(file, content) { + Ok(()) => { + self.path = Some(file.to_path_buf()); + self.unsaved = false; + self.error = None; + } + Err(e) => { + self.error = Some(format!("Failed to save file: {e}")); + } + } + } + self.save_file_dialog = None; + } + } + + fn render_editor(&mut self, ui: &mut Ui, _ctx: &Context) { + let available_width = ui.available_width(); + let mut editor = self.editor.clone().desired_width(available_width - 500.0); + editor.show_with_completer(ui, &mut self.text, &mut self.completer); + } + + fn render_bottom_bar(&self, ui: &mut Ui, _ctx: &Context) { + ui.horizontal(|ui| { + // error display + ui.label( + egui::RichText::new(self.error.clone().unwrap_or_default()) + .color(egui::Color32::RED), + ); + + // line and col + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + ui.label(format!("Ln {}, Col {}", self.cursor_line, self.cursor_col)); + }); + }); + } + + fn build(&mut self) { + if let Some(path) = &self.path { + match path.extension().and_then(|ext| ext.to_str()) { + Some("dsa") => { + let mut assembler = Assembler::new(path); + assembler.start(()); + + // Or block until done + *self.output.mut_data() = match assembler.output() { + Ok(instructions) => instructions, + Err(e) => { + self.error = Some(e.to_string()); + return; + } + }; + } + // Some("dsc") => { + // let dsa_path = Path::new(path).with_extension("dsa"); + // let mut compiler = Compiler::new(path); + + // let is_lib = false; + // compiler.start(is_lib); + + // if let Err(e) = compiler.write_result(&dsa_path) { + // self.error = Some(e.to_string()); + // return; + // } + + // let mut assembler = Assembler::new(&dsa_path); + // assembler.start(()); + + // // Or block until done + // self.output = match assembler.output() { + // Ok(instructions) => instructions, + // Err(e) => { + // self.error = Some(format!("Assembler error: {e}")); + // return; + // } + // }; + // } + Some("dsb") => { + if let Ok(bytes) = fs::read(path) { + *self.output.mut_data() = bytes; + } else { + self.error = Some("Failed to read file".to_string()); + } + } + _ => { + self.error = Some(format!("Invalid file type: {}", self.filename())); + } + } + } + } + + fn render_toolbar(&mut self, ui: &mut Ui, ctx: &Context) { + self.handle_file_dialogs(ctx); + + ui.horizontal(|ui| { + ui.label(format!("File type: {}", self.extension())); + ui.label(format!("Filename: {}", self.filename())); + ui.label(format!("Unsaved: {}", self.unsaved)); + + // number of lines in the file + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + let line_count = self.text.lines().count(); + ui.label(format!("Lines: {line_count}")); + }); + }); + + ui.horizontal(|ui| { + ui.spacing_mut().button_padding = egui::vec2(8.0, 4.0); + ui.spacing_mut().item_spacing.x = 6.0; + + // Opens a file + // ui.add_enabled(false, egui::Button::new("Open")); + if ui.button("Open").clicked() { + self.open(); + } + + // Saves the current file + // ui.add_enabled(false, egui::Button::new("Save")); + if ui.button("Save").clicked() { + self.save(); + } + + // // builds the current file + // ui.add_enabled(false, egui::Button::new("Build")); + if ui.button("Build").clicked() && !self.unsaved { + self.build(); + } + + // // Loads the generated binary into the assembler at the provided offset + // ui.add_enabled(false, egui::Button::new("Load")); + if ui.button("Load").clicked() { + if self.error.is_some() { + self.error = Some("Can't load program at invalid offset!".to_string()); + } + + let program: Vec = Page::paginate(self.output.data()).collect(); + + self.state.reseting.store(true, Ordering::Relaxed); + self.memory.clear(); + for (i, page) in program.iter().enumerate() { + self.memory.write_page(i as u32 * 4096, &page); + } + self.state.reseting.store(false, Ordering::Relaxed); + } + + // Entry widget to enter a load offset + if ui.text_edit_singleline(&mut self.offset_str).changed() { + if let Some(offset) = parse_address(&self.offset_str) { + self.load_offset = offset; + self.error = None; + } else { + self.error = Some("Invalid offset".to_string()); + } + } + + ui.checkbox(&mut self.show_output, "Show Output") + }); + } +} + +fn parse_address(address: &str) -> Option { + address.strip_prefix("0x").map_or_else( + || { + address.strip_prefix("0b").map_or_else( + || { + address.strip_prefix("0o").map_or_else( + || address.parse::().ok(), + |oct| u32::from_str_radix(oct, 8).ok(), + ) + }, + |bin| u32::from_str_radix(bin, 2).ok(), + ) + }, + |hex| u32::from_str_radix(hex, 16).ok(), + ) +} + +pub const THEME: ColorTheme = ColorTheme { + name: "Theme", + dark: true, + bg: "#1b1b1b", + cursor: "#de5252", // fg4 + selection: "#28323B", // bg2 + comments: "#444444", // gray1 + functions: "#7CCCC7", // green1 + keywords: "#6C81E0", // red1 + literals: "#A3ABFF", // fg1 + numerics: "#8A46CF", // purple1 + punctuation: "#99C9C9", // orange1 + strs: "#618c84", // aqua1 + types: "#B8B9D4", // yellow1 + special: "#de5252", // blue1 +}; + +struct Output { + pub data: Vec, + + show_bytes: bool, + show_words: bool, + show_ascii: bool, + show_instruction: bool, + show_decimal: bool, +} + +impl Output { + fn new() -> Self { + Self { + data: vec![], + show_bytes: true, + show_words: true, + show_ascii: true, + show_instruction: true, + show_decimal: true, + } + } + + fn mut_data(&mut self) -> &mut Vec { + &mut self.data + } + + fn data(&self) -> &Vec { + &self.data + } + + fn ui(&mut self, ui: &mut Ui, _ctx: &Context) { + // Output area with synchronized scrolling + egui::ScrollArea::vertical() + .id_salt("output_scroll") + .max_width(400.0) + .show(ui, |ui| { + if self.data.is_empty() { + ui.label( + egui::RichText::new("No output data") + .font(egui::FontId::monospace(12.0)) + .color(egui::Color32::GRAY), + ); + return; + } + + ui.horizontal(|ui| { + ui.toggle_value(&mut self.show_bytes, "Bytes"); + ui.toggle_value(&mut self.show_words, "Words"); + ui.toggle_value(&mut self.show_decimal, "Decimal"); + ui.toggle_value(&mut self.show_ascii, "Ascii"); + ui.toggle_value(&mut self.show_instruction, "Instructions"); + }); + + ui.separator(); + + let mut table = TableBuilder::new(ui) + .resizable(false) // makes all columns resizable at once + .column(Column::initial(55.0)); // addr + + if self.show_bytes { + table = table.column(Column::initial(90.0)); + } + if self.show_words { + table = table.column(Column::initial(75.0)); + } + if self.show_decimal { + table = table.column(Column::initial(80.0)); + } + if self.show_ascii { + table = table.column(Column::initial(50.0)); + } + if self.show_instruction { + table = table.column(Column::initial(200.0).clip(true)); + } + + table + .header(20.0, |mut header| { + header.col(|ui| { + ui.label("Addr"); + }); + if self.show_bytes { + header.col(|ui| { + ui.label("Bytes"); + }); + } + if self.show_words { + header.col(|ui| { + ui.label("Word"); + }); + } + if self.show_decimal { + header.col(|ui| { + ui.label("Decimal"); + }); + } + if self.show_ascii { + header.col(|ui| { + ui.label("Ascii"); + }); + } + if self.show_instruction { + header.col(|ui| { + ui.label("Instruction"); + }); + } + }) + .body(|mut body| { + // Process bytes in chunks of 4 + for (line_num, chunk) in self.data.chunks(4).enumerate() { + body.row(18.0, |mut row| { + let address = line_num * 4; + + let value = u32::from_le_bytes(*chunk.as_array().unwrap()); + + // Address column + row.col(|ui| { + ui.label( + egui::RichText::new(format!("0x{address:04X}")) + .font(egui::FontId::monospace(12.0)), + ); + }); + + if self.show_bytes { + row.col(|ui| { + // Individual bytes column + ui.label( + egui::RichText::new( + chunk + .iter() + .map(|b| format!("{b:02X}")) + .collect::>() + .join(" "), + ) + .font(egui::FontId::monospace(12.0)) + .color(egui::Color32::from_rgb(200, 200, 255)), + ); + }); + } + + if self.show_words { + row.col(|ui| { + // Hex column + ui.label( + egui::RichText::new(format!("0x{value:08X}")) + .font(egui::FontId::monospace(12.0)) + .color(egui::Color32::from_rgb(255, 100, 100)), + ); + }); + } + + if self.show_decimal { + row.col(|ui| { + ui.label( + egui::RichText::new(format!("{value}")) + .font(egui::FontId::monospace(12.0)) + .color(egui::Color32::from_rgb(100, 255, 100)), + ); + }); + } + + if self.show_ascii { + let ascii = value.to_le_bytes(); + row.col(|ui| { + ui.label( + egui::RichText::new(String::from_utf8_lossy(&ascii)) + .font(egui::FontId::monospace(12.0)) + .color(egui::Color32::from_rgb(100, 255, 255)), + ); + }); + } + + if self.show_instruction { + row.col(|ui| { + ui.label( + egui::RichText::new(format!( + "{:?}", + Instruction(value) + )) + .font(egui::FontId::monospace(12.0)) + .color(egui::Color32::from_rgb(255, 100, 255)), + ); + }); + } + }) + } + }); + + // egui::Grid::new("output_grid") + // .spacing([5.0, 2.0]) // Horizontal and vertical spacing + // .num_columns(4) + // .striped(false) + // .show(ui, |ui| { + // ui.label("Address"); + + // if self.show_bytes { + // ui.label("Bytes"); + // } + // if self.show_words { + // ui.label("Word"); + // } + // if self.show_decimal { + // ui.label("Decimal"); + // } + // if self.show_ascii { + // ui.label("Ascii"); + // } + // if self.show_instruction { + // ui.label("Hex"); + // } + // ui.end_row(); + + // // Process bytes in chunks of 4 + // for (line_num, chunk) in self.data.chunks(4).enumerate() { + // let address = line_num * 4; + + // // Convert chunk to u32 (little-endian) + // let mut bytes = [0u8; 4]; + // for (i, &byte) in chunk.iter().enumerate() { + // if i < 4 { + // bytes[i] = byte; + // } + // } + // let value = u32::from_le_bytes(bytes); + + // // Address column + // ui.with_layout( + // egui::Layout::left_to_right(egui::Align::Center), + // |ui| { + // ui.set_min_width(80.0); + // let style = ui.style_mut(); + // style.visuals.widgets.inactive.bg_fill = + // egui::Color32::from_gray(30); + // ui.label( + // egui::RichText::new(format!("0x{address:04X}")) + // .font(egui::FontId::monospace(12.0)), + // ); + // }, + // ); + + // // Individual bytes column + // let byte_str = chunk + // .iter() + // .map(|b| format!("{b:02X}")) + // .collect::>() + // .join(" "); + + // if self.show_bytes { + // ui.label( + // egui::RichText::new(format!("{byte_str:<11}")) + // .font(egui::FontId::monospace(12.0)) + // .color(egui::Color32::from_rgb(200, 200, 255)), + // ); + // } + + // if self.show_words { + // // Hex column + // ui.label( + // egui::RichText::new(format!("0x{value:08X}")) + // .font(egui::FontId::monospace(12.0)) + // .color(egui::Color32::from_rgb(255, 100, 100)), + // ); + // } + + // if self.show_decimal { + // ui.label( + // egui::RichText::new(format!("{value}")) + // .font(egui::FontId::monospace(12.0)) + // .color(egui::Color32::from_rgb(100, 255, 100)), + // ); + // } + + // if self.show_ascii { + // let ascii = value.to_le_bytes(); + // ui.label( + // egui::RichText::new(String::from_utf8_lossy(&ascii)) + // .font(egui::FontId::monospace(12.0)) + // .color(egui::Color32::from_rgb(100, 255, 255)), + // ); + // } + + // if self.show_instruction { + // ui.label( + // egui::RichText::new(format!("{:?}", Instruction(value))) + // .font(egui::FontId::monospace(12.0)) + // .color(egui::Color32::from_rgb(255, 100, 255)), + // ); + // } + + // ui.end_row(); + // } + // }); + }); + } +} diff --git a/dsa/emulator/src/frontend/components/editor/syntax/mod.rs b/dsa/emulator/src/frontend/components/editor/syntax/mod.rs new file mode 100644 index 0000000..e388fd7 --- /dev/null +++ b/dsa/emulator/src/frontend/components/editor/syntax/mod.rs @@ -0,0 +1,48 @@ +use std::collections::BTreeSet; + +use egui_code_editor::Syntax; + +pub fn dsa() -> Syntax { + Syntax { + quotes: BTreeSet::from(['"', '\'']), + language: "Assembly", + case_sensitive: false, + comment: "//", + comment_multiline: ["/*", "*/"], + hyperlinks: BTreeSet::from(["HTTP", "HTTPS"]), + keywords: BTreeSet::from([ + "NOP", "MOV", "CMOV", "LDB", "LDBS", "LDH", "LDHS", "LDW", "STB", "STH", "STW", "LLI", + "LUI", "LWI", "JMP", "JNZ", "JEZ", "JNC", "JIC", "IEQ", "INE", "IGE", "IGT", "ILE", + "ILT", "SHL", "SHR", "ADD", "SUB", "AND", "OR", "NOT", "XOR", "NAND", "NOR", "XNOR", + "IRET", "RET", "CALL", "INT", "PUSH", "POP", "HLT", + ]), + types: BTreeSet::from(["DB", "DW", "DH", "RESB", "RESH", "RESW", "INCLUDE"]), + special: BTreeSet::from([ + "RG0", "RG1", "RG2", "RG3", "RG4", "RG5", "RG6", "RG7", "RG8", "RG9", "RGA", "RGB", + "RGC", "RGD", "RGE", "RGF", "ACC", "SPR", "BPR", "IDR", "MMR", "ZERO", "PCX", "STS", + "CIR", + ]), + } +} + +pub fn dsc() -> Syntax { + Syntax { + quotes: BTreeSet::from(['"', '\'', '`']), + language: "Damn Simple Code", + case_sensitive: false, + comment: "//", + comment_multiline: ["/*", "*/"], + hyperlinks: BTreeSet::from(["HTTP", "HTTPS"]), + keywords: BTreeSet::from([ + "INCLUDE", "FN", "LET", "CONST", "STATIC", "IF", "ELSE", "WHILE", "FOR", "BREAK", + "CONTINUE", "LOOP", "RETURN", "CLASS", "DEFER", "PUB", "CONST", + ]), + types: BTreeSet::from([ + "U32", "U16", "U8", "I32", "I16", "I8", "STR", "CHAR", "BOOL", "VOID", + ]), + special: BTreeSet::from([ + ",", ";", ".", ":", "=", "+", "-", "*", "/", "%", "&", "|", "^", "~", "!", "?", "<", + ">", "<<", ">>", "==", "!=", "<=", ">=", "&&", "||", + ]), + } +} diff --git a/dsa/emulator/src/frontend/components/memory.rs b/dsa/emulator/src/frontend/components/memory.rs new file mode 100644 index 0000000..97e1014 --- /dev/null +++ b/dsa/emulator/src/frontend/components/memory.rs @@ -0,0 +1,138 @@ +use std::num::ParseIntError; + +use common::prelude::Instruction; + +use crate::{MemoryBank, frontend::components::Component}; + +pub struct MemoryInspector { + memory: MemoryBank, + + view_size: u32, + view_addr: u32, + visible: bool, + addr_input: String, +} + +impl MemoryInspector { + #[must_use] + pub fn new(memory: MemoryBank) -> Self { + Self { + memory, + view_size: 1024, + view_addr: 0, + visible: false, + addr_input: String::from("0x00"), + } + } +} + +impl Component for MemoryInspector { + fn title(&self) -> &'static str { + "Memory Inspector" + } + + fn visible(&mut self) -> &mut bool { + &mut self.visible + } + + fn ui(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) { + // Right column - Memory + ui.vertical(|ui| { + ui.heading("Memory Inspector"); + ui.add_space(10.0); + + // Address input section + ui.horizontal(|ui| { + ui.label("Address:"); + + let address_response = ui.add( + egui::TextEdit::singleline(&mut self.addr_input) + .hint_text("0x1000 or 4096") + .desired_width(150.0), + ); + + ui.add_space(10.0); + + // Search button + let search_clicked = ui.button("🔍 Search").clicked(); + + // Handle Enter key in text field + let enter_pressed = + address_response.lost_focus() && ctx.input(|i| i.key_pressed(egui::Key::Enter)); + + if search_clicked || enter_pressed { + if let Ok(new) = parse_address(&self.addr_input) { + if new % 4 == 0 { + self.view_addr = new; + } else { + println!("Address must be 4-byte aligned"); + } + } else { + println!("Invalid address"); + // state.error_log.push("Invalid address".to_string()); + } + } + + ui.label("(hex or decimal)"); + }); + + ui.add_space(10.0); + + // Memory table + egui::ScrollArea::vertical() + .auto_shrink(true) + .id_salt("memory_inspector_scroll") + .show(ui, |ui| { + egui::Grid::new("memory_grid") + .spacing([12.0, 2.0]) + .min_col_width(5.0) + .striped(true) + .show(ui, |ui| { + // Header + ui.strong("Address"); + + for i in 0..4 { + ui.strong(format!("{i:X}")); + } + + ui.strong("Decimal"); + ui.strong("Instruction"); + + ui.end_row(); + + // Memory data (8 bytes per row) + for (row, word) in (0..self.view_size / 4) + .map(|i| i * 4) + .map(|i| (i, self.memory.read_word(self.view_addr + i))) + { + let row_address = self.view_addr + (row * 4); + ui.monospace(format!("0x{row_address:08X} ({row_address})")); + for byte in word.to_le_bytes() { + ui.monospace(format!("{byte:02X}")); + } + + ui.monospace(format!("{word}")); + ui.monospace(format!("{:?}", Instruction(word))); + ui.end_row(); + } + }); + }); + }); + } +} + +fn parse_address(address: &str) -> Result { + if let Some(hex_part) = address.strip_prefix("0x") { + return u32::from_str_radix(hex_part, 16); + } + + if let Some(bin_part) = address.strip_prefix("0b") { + return u32::from_str_radix(bin_part, 2); + } + + if let Some(oct_part) = address.strip_prefix("0o") { + return u32::from_str_radix(oct_part, 8); + } + + address.parse::() +} diff --git a/dsa/emulator/src/frontend/components/serial.rs b/dsa/emulator/src/frontend/components/serial.rs index 4a6ba7d..d20d190 100644 --- a/dsa/emulator/src/frontend/components/serial.rs +++ b/dsa/emulator/src/frontend/components/serial.rs @@ -1,4 +1,83 @@ -use crate::{frontend::components::Component, io::serial::Serial}; +use std::sync::{ + Arc, + atomic::Ordering, + mpsc::{Receiver, Sender}, +}; + +use super::Component; +use crate::{ + MemoryBank, SharedState, + backend::{memory::PhysAddr, processor::interrupts::Interrupt}, +}; + +/// ## SERIAL LAYOUT: (from the perspective of code inside the emulator) +/// - [base+0x00]: write valid flag - set by kernel after writing a byte to serial +/// - [base+0x01]: write byte - data payload for emulator to read +/// - [base+0x02]: read valid flag - set by emulator after writing a byte to serial +/// - [base+0x03]: read byte - data payload for kernel to read +pub struct Serial { + pub read_rx: Receiver, + pub write_tx: Sender, + pub read_buff: Vec, + pub write_buff: String, + + pub visible: bool, +} + +impl Serial { + fn uart_io_thread( + mem: MemoryBank, + state: Arc, + uart_base: PhysAddr, + tx: Sender, + rx: Receiver, + ) { + loop { + let word = mem.read_word(uart_base); + + // Kernel → emulator: valid flag in bits 31:24, data in bits 23:16 + if (word >> 24) as u8 == 0x01 { + let byte = (word >> 16) as u8; + let _ = tx.send(byte); + mem.write_word(uart_base, 0x00); // clear the whole word + } + + // Emulator → kernel: your existing rx path + if (word >> 8) as u8 == 0x00 { + if let Ok(byte) = rx.try_recv() { + state.interrupt_queue.send(Interrupt::SerialIn).unwrap(); + + mem.write_byte(uart_base + 3, byte); + mem.write_byte(uart_base + 2, 0x01); + } + } + + std::thread::sleep(std::time::Duration::from_millis(1)); + } + } + + pub fn new(uart_base: PhysAddr, state: Arc, mem_handle: MemoryBank) -> Self { + let (read_tx, read_rx) = std::sync::mpsc::channel(); + let (write_tx, write_rx) = std::sync::mpsc::channel(); + let _ = std::thread::spawn(move || { + Self::uart_io_thread(mem_handle, state, uart_base, read_tx, write_rx) + }); + + Self { + visible: false, + write_tx, + read_rx, + read_buff: Vec::new(), + write_buff: String::new(), + } + } + + pub fn update(&mut self) { + while let Ok(byte) = self.read_rx.try_recv() { + self.read_buff.push(byte); + } + } +} impl Component for Serial { fn title(&self) -> &str { @@ -9,9 +88,23 @@ impl Component for Serial { &mut self.visible } - fn ui(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) { + fn ui(&mut self, ui: &mut egui::Ui, _ctx: &egui::Context) { self.update(); - ui.label(String::from_utf8(self.buffer.clone()).unwrap()); + // Text field for input and a send button + ui.text_edit_multiline(&mut self.write_buff); + if ui.button("Send").clicked() { + self.write_buff.push(0 as char); + for byte in self.write_buff.bytes() { + self.write_tx.send(byte).unwrap(); + } + self.write_buff.clear(); + } + + // Display received data + ui.label(String::from_utf8(self.read_buff.clone()).unwrap()); + if ui.button("Clear").clicked() { + self.read_buff.clear(); + } } } diff --git a/dsa/emulator/src/frontend/mod.rs b/dsa/emulator/src/frontend/mod.rs index dc152cb..5333261 100644 --- a/dsa/emulator/src/frontend/mod.rs +++ b/dsa/emulator/src/frontend/mod.rs @@ -1,15 +1,16 @@ use eframe::NativeOptions; use eframe::egui; +use std::mem; use std::sync::Arc; use std::sync::atomic::Ordering; mod components; +use components::{ + Component, controller::Controller, display::Display, editor::Editor, framebuffer::FrameBuffer, + memory::MemoryInspector, registers::Registers, serial::Serial, +}; -use crate::frontend::components::Component; -use crate::frontend::components::framebuffer::FrameBuffer; -use crate::io::serial::Serial; use crate::{MemoryBank, SharedState, config::VERSION}; -use components::{controller::Controller, display::Display, registers::Registers}; pub fn run_app(state: Arc, mem_handle: MemoryBank) -> eframe::Result<()> { eframe::run_native( @@ -90,6 +91,8 @@ impl DsaUi { Box::new(FrameBuffer::new(320, 240, 0x30000, mem_handle.clone())), Box::new(Registers::new(state.clone())), Box::new(Serial::new(0x40000, state.clone(), mem_handle.clone())), + Box::new(Editor::new(state.clone(), mem_handle.clone())), + Box::new(MemoryInspector::new(mem_handle.clone())), ]; components.iter_mut().for_each(|c| *c.visible() = false); diff --git a/dsa/emulator/src/io/display.rs b/dsa/emulator/src/io/display.rs deleted file mode 100644 index 68f980b..0000000 --- a/dsa/emulator/src/io/display.rs +++ /dev/null @@ -1,62 +0,0 @@ -// use super::{IoAccess, IoHandle}; -use crate::{MemoryBank, Page, backend::memory::PhysAddr}; - -pub struct Display { - width: usize, - height: usize, - addr: PhysAddr, - - mem: MemoryBank, - buffer: Vec, - visible: bool, -} - -impl Display { - pub fn new(width: u32, height: u32, addr: PhysAddr, mem: MemoryBank) -> Self { - Self { - width: width as usize, - height: height as usize, - addr, - mem, - buffer: Vec::new(), - visible: true, - } - } - - pub fn visible(&mut self) -> &mut bool { - &mut self.visible - } - - pub fn width(&self) -> usize { - self.width - } - - pub fn height(&self) -> usize { - self.height - } - - fn internal_read(&self) -> Vec { - let pages = (0..((self.width * self.height).div_ceil(Page::SIZE))) - .map(|i| { - let page = self.mem.read_page(self.addr + i as u32 * 4096); - page.as_slice().to_owned() - }) - .flatten() - .take((self.width * self.height) as usize) - .collect::>(); - pages - } - - pub fn changed(&mut self) -> bool { - let temp = self.internal_read(); - if temp != self.buffer { - self.buffer = temp; - return true; - } - false - } - - pub fn read(&mut self) -> Vec { - self.internal_read() - } -} diff --git a/dsa/emulator/src/io/mod.rs b/dsa/emulator/src/io/mod.rs deleted file mode 100644 index 204ffb4..0000000 --- a/dsa/emulator/src/io/mod.rs +++ /dev/null @@ -1,46 +0,0 @@ -use serde::{Deserialize, Serialize}; - -pub mod display; -pub mod serial; -// #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -// pub enum DeviceId { -// Display, -// Serial, -// Random, -// Timer, -// } - -// pub trait IoHandle: Send { -// /// Return the size (in bytes) of the device's addressable region. -// fn size(&self) -> usize; - -// /// Return the access permissions for the device. -// fn access(&self) -> IoAccess; - -// /// Return the DeviceId -// fn id(&self) -> DeviceId; -// } - -// #[derive(Clone, Copy, Debug, PartialEq)] -// pub struct IoAccess(u8); - -// impl IoAccess { -// pub const READ: Self = Self(0b01); -// pub const WRITE: Self = Self(0b10); - -// /// Convenience alias for Read + Write. -// pub const READWRITE: Self = Self(Self::READ.0 | Self::WRITE.0); -// pub const NONE: Self = Self(0); - -// /// Bitwise OR – returns a new `IoAccess` that contains all flags from both operands. -// #[inline] -// pub fn or(self, other: Self) -> Self { -// Self(self.0 | other.0) -// } - -// /// Check if a flag is present. -// #[inline] -// pub fn contains(&self, flag: Self) -> bool { -// self.0 & flag.0 != 0 -// } -// } diff --git a/dsa/emulator/src/io/serial.rs b/dsa/emulator/src/io/serial.rs deleted file mode 100644 index c431c3f..0000000 --- a/dsa/emulator/src/io/serial.rs +++ /dev/null @@ -1,48 +0,0 @@ -use std::{ - path::Component, - sync::{ - Arc, - mpsc::{Receiver, Sender}, - }, -}; - -use crate::{MemoryBank, SharedState, backend::memory::PhysAddr}; - -pub struct Serial { - pub receiver: Receiver, - pub buffer: Vec, - - pub visible: bool, -} - -impl Serial { - fn uart_io_thread(mem: MemoryBank, uart_base: PhysAddr, tx: Sender) { - loop { - let word = mem.read_word(uart_base); - // println!("word {:#010X}", word); - if word >> 24 == 0x01 { - let byte = ((word >> 16) & 0xFF) as u8; - let _ = tx.send(byte); - mem.write_word(uart_base, 0x00_00_00_00); // clear valid flag - } - std::hint::spin_loop(); - } - } - - pub fn new(uart_base: PhysAddr, state: Arc, mem_handle: MemoryBank) -> Self { - let (sender, receiver) = std::sync::mpsc::channel(); - let _ = std::thread::spawn(move || Self::uart_io_thread(mem_handle, uart_base, sender)); - - Self { - visible: false, - receiver, - buffer: Vec::new(), - } - } - - pub fn update(&mut self) { - while let Ok(byte) = self.receiver.try_recv() { - self.buffer.push(byte); - } - } -} diff --git a/dsa/emulator/src/lib.rs b/dsa/emulator/src/lib.rs index da07b95..f8f2c41 100644 --- a/dsa/emulator/src/lib.rs +++ b/dsa/emulator/src/lib.rs @@ -5,7 +5,6 @@ pub mod args; pub mod backend; pub mod config; pub mod frontend; -pub mod io; pub use backend::memory::ram::*; pub use backend::{processor::processor::Emulator, processor::state::SharedState}; diff --git a/dsa/emulator/src/main.rs b/dsa/emulator/src/main.rs index c2a6400..1d9e154 100644 --- a/dsa/emulator/src/main.rs +++ b/dsa/emulator/src/main.rs @@ -47,5 +47,17 @@ fn main() { .spawn(move || emulator.run().unwrap()) .unwrap(); - run_app(state, mem_handle).unwrap() + if args.headless() { + run_server(&args); + } else { + run_app(state, mem_handle).unwrap() + } +} + +fn run_server(args: &DsaArgs) { + if args.headless() { + println!("Running in headless mode"); + } else { + println!("Running in GUI mode"); + } } diff --git a/dsa_resources/idt.dsa b/dsa_resources/idt.dsa new file mode 100644 index 0000000..3fc482c --- /dev/null +++ b/dsa_resources/idt.dsa @@ -0,0 +1,61 @@ +include print: "./print.dsa" + +dw idt: 0xFFFF0000 +setup_base: func + push rg0 + push rg1 + push rg2 + push rg3 + + ldw idt, idr // load the IDT into the IDR + + // set all 256 interrupt addresses to point to guard + lwi _guard, rg1 // load address of guard handler + addi idr, 1024, rg2 // counter for 256 entries +_loop_start: + stw rg1, rg2 // store guard into current IDT entry + subi rg2, 4, rg2 // decrement counter + ieq rg2, zero, rg3 // check if counter is zero + jnz rg3, _loop_start // repeat until all entries set + + // make sure we can handle an invalid interrupt + lwi _handle_invalid_interrupt, rg0 + stw rg0, idr, 0 + + pop rg3 + pop rg2 + pop rg1 + pop rg0 + return + +setup_io: func + push rg0 + + lwi _serial_interrupt, rg0 + stw rg0, idr, 132 // 33*4, interrupt idx 32 + + pop rg0 + return + +// does nothing, but wakes the kernel +_serial_interrupt: + nop + iret + +db _invalid_int: "FATAL: Invalid Interrupt!" +_handle_invalid_interrupt: + call print::reset + lwi _invalid_int, rg0 + push rg0 + call print::print + pop zero + hlt + +db _err: "FATAL: Interrupt handler was not set!" +_guard: + call print::reset + lwi _err, rg0 + push rg0 + call print::print + pop zero + hlt diff --git a/dsa_resources/out.dsb b/dsa_resources/out.dsb index 6b47949..50db19c 100644 Binary files a/dsa_resources/out.dsb and b/dsa_resources/out.dsb differ diff --git a/dsa_resources/print.dsa b/dsa_resources/print.dsa index f31af1e..61e12d6 100644 --- a/dsa_resources/print.dsa +++ b/dsa_resources/print.dsa @@ -34,20 +34,34 @@ dw current: 0x20000 // ------------------------------------------ // prints the string at addr(arg[0]) to the screen. print: func + push rg0 + push rg1 + push rg2 + push rg3 ldw bpr, rg0, 8 ldw current, rg1 _print_loop: ldb rg0, rg2 - ieq rg2, zero, rg4 - jnz rg4, _end + ieq rg2, zero, rg3 + jnz rg3, _print_end stb rg2, rg1 addi rg0, 1 addi rg1, 1 jmp _print_loop + +_print_end: + stw rg1, current + + pop rg3 + pop rg2 + pop rg1 + pop rg0 + return + // ------------------------------------------ // println: // push bpr @@ -99,11 +113,17 @@ _print_loop: // ------------------------------------------ // prints the last byte of arg[0] to the screen. print_byte: func + push rg0 + push rg1 + ldw bpr, rg0, 8 ldw current, rg1 stb rg0, rg1 addi rg1, 1 + + pop rg1 + pop rg0 jmp _end // ------------------------------------------ diff --git a/dsa_resources/serial.dsa b/dsa_resources/serial.dsa index 66b756d..5dd00a5 100644 --- a/dsa_resources/serial.dsa +++ b/dsa_resources/serial.dsa @@ -1,63 +1,116 @@ -// lib: -// serial.dsa -// -// usage: -// include serial "" -// -// push (register containing address of string) -// call serial::print -// -// push (register containing byte) -// call serial::print_byte - dw uart: 0x40000 - // ------------------------------------------ -// transmit a single byte via sentinel protocol -// expects: byte in rg0, trashes rg2, rg3, rg4, rg5 +// reads up to rg1 bytes into buffer at rg2 +// args: buf_size @ bpr+8, buf_addr @ bpr+12 +// returns: 0 in bpr+8 on success, 1 on nullptr error +buf_read: func + push rg0 + push rg1 + push rg2 + push rg3 + ldw bpr, rg1, 8 // buf size + ldw bpr, rg2, 12 // buf addr + jez rg2, _buf_read_err // return err if nullptr +_buf_read_loop: + jez rg1, _buf_read_end + call read_byte + ldw bpr, rg3, 8 // read return value from frame + stb rg3, rg2 // store into buffer + addi rg2, 1, rg2 + subi rg1, 1, rg1 + jmp _buf_read_loop +_buf_read_end: + stw zero, bpr, 8 // return 0 (success) + pop rg3 + pop rg2 + pop rg1 + pop rg0 + return +_buf_read_err: + lli 1, rg0 + stw rg0, bpr, 8 // return 1 (error) + pop rg3 + pop rg2 + pop rg1 + pop rg0 + return + +// read_byte: emulator → kernel +// reads data from base+0x00, valid flag at base+0x01 +read_byte: func + push rg0 + push rg1 + push rg2 + ldw uart, rg1 +_serial_read_wait: + ldb rg1, rg0, 1 // base+0x01 = read valid flag + lli 0x01, rg2 + ieq rg0, rg2, rg2 + jnz rg2, _serial_read_ready + hlt + jmp _serial_read_wait +_serial_read_ready: + ldb rg1, rg0, 0 // base+0x00 = data byte + stb zero, rg1, 1 // clear read valid flag + stb rg0, bpr, 8 // return byte + pop rg2 + pop rg1 + pop rg0 + return + +// _serial_send: kernel → emulator +// writes data to base+0x02, valid flag to base+0x03 _serial_send: - shl rg0, 16, rg2 // shift byte to bits [23:16] - lli 0x01, rg0 - shl rg0, 24, rg0 // valid flag in bits [31:24] - or rg0, rg2, rg2 // rg2 = 0x01_XX_0000 - - ldw uart, rg5 // rg5 = 0x40000 (uart address, held for store) - + push rg0 + push rg2 + push rg3 + push rg4 + push rg5 + ldw uart, rg5 _serial_wait: - ldw rg5, rg0 // poll mem[0x40000] - shr rg0, 24, rg0 + ldb rg5, rg2, 3 // base+0x03 = write valid flag lli 0x01, rg3 - ieq rg0, rg3, rg4 - jnz rg4, _serial_wait // loop while valid == 1 - - stw rg2, rg5 // mem[0x40000] = sentinel word + ieq rg2, rg3, rg4 + jnz rg4, _serial_wait // wait while emulator hasn't consumed yet + stb rg0, rg5, 2 // base+0x02 = data byte + lli 0x01, rg2 + stb rg2, rg5, 3 // base+0x03 = set valid flag + pop rg5 + pop rg4 + pop rg3 + pop rg2 + pop rg0 ret - // ------------------------------------------ // prints the null-terminated string at addr(arg[0]) print: func + push rg0 + push rg1 + push rg2 ldw bpr, rg0, 8 // load string address from arg - _serial_print_loop: - ldb rg0, rg2 // load byte - ieq rg2, zero, rg4 // null terminator? - jnz rg4, _serial_end - + ldb rg0, rg1 // load byte + ieq rg1, zero, rg2 // null terminator? + jnz rg2, _serial_print_end push rg0 // save string pointer - mov rg2, rg0 + mov rg1, rg0 call _serial_send pop rg0 - - addi rg0, 1 + addi rg0, 1, rg0 jmp _serial_print_loop - +_serial_print_end: + pop rg2 + pop rg1 + pop rg0 + jmp _serial_end // ------------------------------------------ // prints the last byte of arg[0] print_byte: func + push rg0 ldw bpr, rg0, 8 call _serial_send + pop rg0 jmp _serial_end - // ------------------------------------------ _serial_end: return diff --git a/dsa_resources/serialtest.dsa b/dsa_resources/serialtest.dsa index 83a5036..c5083e9 100644 --- a/dsa_resources/serialtest.dsa +++ b/dsa_resources/serialtest.dsa @@ -1,14 +1,44 @@ include serial: "./serial.dsa" +include idt: "./idt.dsa" +include print: "./print.dsa" + +resb buff: 256 dw stack: 0x1000 _init: ldw stack, bpr mov bpr, spr + call idt::setup_base + call idt::setup_io -db hello: "Serial works lol. this took ages and some clanker forgetting to dereference a pointer." +db hello2: "hello2" +db hello: "Test message, hello world etc etc." main: lwi hello, rg0 push rg0 call serial::print pop zero + + lwi hello, rg0 + push rg0 + call print::print + pop zero + + //lwi buff, rg0 + //lwi 32, rg1 + //push rg0 + //push rg1 + push zero + call serial::read_byte + pop rg0 + //pop zero + push rg0 + call print::print + pop zero + + lwi hello2, rg0 + push rg0 + call print::print + pop zero + hlt