progress on serial and added editor
This commit is contained in:
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
[workspace]
|
[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"
|
resolver = "3"
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "assembler_old"
|
name = "assembler"
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
authors.workspace = true
|
authors.workspace = true
|
||||||
@@ -13,7 +13,9 @@ name = "assembler"
|
|||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
clap = { version = "4.6.0", features = ["derive"] }
|
||||||
common = { path = "../common" }
|
common = { path = "../common" }
|
||||||
|
|
||||||
num_cpus = "1.17.0"
|
num_cpus = "1.17.0"
|
||||||
|
strum = { version = "0.28.0", features = ["derive"] }
|
||||||
threadpool = "1.8.1"
|
threadpool = "1.8.1"
|
||||||
@@ -26,6 +26,8 @@ pub mod model;
|
|||||||
pub mod parser;
|
pub mod parser;
|
||||||
pub mod resolver;
|
pub mod resolver;
|
||||||
|
|
||||||
|
use crate::assemblerv2::lexer::Lexer;
|
||||||
|
|
||||||
// Re-exports
|
// Re-exports
|
||||||
pub use self::{
|
pub use self::{
|
||||||
codegen::codegen,
|
codegen::codegen,
|
||||||
@@ -173,6 +175,7 @@ fn prepare_dependency(
|
|||||||
|
|
||||||
println!("{:20} {:20}", "Tokenising", filename);
|
println!("{:20} {:20}", "Tokenising", filename);
|
||||||
let tokens = lexer::lexer(src, file_hash)?;
|
let tokens = lexer::lexer(src, file_hash)?;
|
||||||
|
// let tokens = Lexer::new(src, file_hash).run()?;
|
||||||
|
|
||||||
println!("{:20} {:20}", "Parsing", filename);
|
println!("{:20} {:20}", "Parsing", filename);
|
||||||
let parsed = Parser::parse_nodes(tokens)?;
|
let parsed = Parser::parse_nodes(tokens)?;
|
||||||
@@ -42,16 +42,12 @@ impl Default for Program {
|
|||||||
|
|
||||||
impl Parser {
|
impl Parser {
|
||||||
pub fn parse_nodes(tokens: Vec<Token>) -> Result<Vec<Node>, AssembleError> {
|
pub fn parse_nodes(tokens: Vec<Token>) -> Result<Vec<Node>, AssembleError> {
|
||||||
println!("parsing");
|
|
||||||
|
|
||||||
let mut self_ = Self {
|
let mut self_ = Self {
|
||||||
tokens: tokens.into_iter().rev().collect(),
|
tokens: tokens.into_iter().rev().collect(),
|
||||||
nodes: vec![],
|
nodes: vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
while !self_.tokens.is_empty() {
|
while !self_.tokens.is_empty() {
|
||||||
println!("NEXT {}", self_.peek_next().unwrap());
|
|
||||||
|
|
||||||
let ins = self_.parse_instruction()?;
|
let ins = self_.parse_instruction()?;
|
||||||
self_.nodes.push(ins);
|
self_.nodes.push(ins);
|
||||||
}
|
}
|
||||||
@@ -214,13 +210,10 @@ impl Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Opcode::Ieq | Opcode::Ine | Opcode::Ilt | Opcode::Igt | Opcode::Ile | Opcode::Ige => {
|
Opcode::Ieq | Opcode::Ine | Opcode::Ilt | Opcode::Igt | Opcode::Ile | Opcode::Ige => {
|
||||||
println!("yes");
|
|
||||||
let src1 = expect_type!(self.next()?, Register, Symbol)?;
|
let src1 = expect_type!(self.next()?, Register, Symbol)?;
|
||||||
let src2 = expect_type!(self.next()?, Register, Symbol)?;
|
let src2 = expect_type!(self.next()?, Register, Symbol)?;
|
||||||
let dest = expect_type!(self.next()?, Register, Symbol)?;
|
let dest = expect_type!(self.next()?, Register, Symbol)?;
|
||||||
args = vec![src1, src2, dest];
|
args = vec![src1, src2, dest];
|
||||||
|
|
||||||
println!("done");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Opcode::Jez | Opcode::Jnz => {
|
Opcode::Jez | Opcode::Jnz => {
|
||||||
@@ -307,8 +300,6 @@ impl Parser {
|
|||||||
fn parse_data_definition(&mut self, opcode: Opcode) -> Result<Vec<Token>, AssembleError> {
|
fn parse_data_definition(&mut self, opcode: Opcode) -> Result<Vec<Token>, AssembleError> {
|
||||||
let mut values = Vec::new();
|
let mut values = Vec::new();
|
||||||
|
|
||||||
println!("e {:?}", self.peek_next());
|
|
||||||
|
|
||||||
let name = expect_type!(self.next()?, Symbol)?;
|
let name = expect_type!(self.next()?, Symbol)?;
|
||||||
values.push(name);
|
values.push(name);
|
||||||
|
|
||||||
@@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+83
-18
@@ -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 {
|
pub struct Lexer {
|
||||||
src: Vec<u8>,
|
src: Vec<u8>,
|
||||||
@@ -13,10 +17,25 @@ impl Lexer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lexer(&mut self) -> Result<Vec<Token>, AssembleError> {
|
pub fn run(&mut self) -> Result<Vec<Token>, AssembleError> {
|
||||||
let mut tokens = Vec::new();
|
let mut tokens = Vec::new();
|
||||||
|
|
||||||
while !self.src.is_empty() {
|
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"//") {
|
if self.src.ends_with(b"//") {
|
||||||
self.src.pop();
|
self.src.pop();
|
||||||
self.src.pop();
|
self.src.pop();
|
||||||
@@ -24,7 +43,7 @@ impl Lexer {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.src.ends_with(b"/*") {
|
if self.src.ends_with(b"*/") {
|
||||||
self.src.pop();
|
self.src.pop();
|
||||||
self.src.pop();
|
self.src.pop();
|
||||||
self.parse_comment_multiline();
|
self.parse_comment_multiline();
|
||||||
@@ -34,15 +53,64 @@ impl Lexer {
|
|||||||
if self.src.ends_with(b"\"") {
|
if self.src.ends_with(b"\"") {
|
||||||
self.src.pop();
|
self.src.pop();
|
||||||
tokens.push(Token::StringLit(self.parse_string_literal()));
|
tokens.push(Token::StringLit(self.parse_string_literal()));
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.src.ends_with(b"'") {
|
if self.src.ends_with(b"'") {
|
||||||
self.src.pop();
|
self.src.pop();
|
||||||
tokens.push(Token::CharLit(self.parse_char_literal()));
|
tokens.push(Token::CharLit(self.parse_char_literal()));
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.src.last().unwrap().is_ascii_digit() {
|
if self.src.last().unwrap().is_ascii_digit() {
|
||||||
tokens.push(Token::Immediate(self.parse_number()));
|
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 {
|
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") => {
|
Some(b"0x") => {
|
||||||
self.src.pop();
|
self.src.pop();
|
||||||
self.src.pop();
|
self.src.pop();
|
||||||
@@ -93,10 +161,13 @@ impl Lexer {
|
|||||||
}
|
}
|
||||||
n
|
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;
|
let mut n = 0u32;
|
||||||
while let Some(&b) = self.src.last() {
|
while let Some(&b) = self.src.last() {
|
||||||
if b.is_ascii_digit() {
|
if matches!(b, b'0'..=b'9') {
|
||||||
self.src.pop();
|
self.src.pop();
|
||||||
n = n * 10 + (b - b'0') as u32;
|
n = n * 10 + (b - b'0') as u32;
|
||||||
} else {
|
} else {
|
||||||
@@ -105,35 +176,27 @@ impl Lexer {
|
|||||||
}
|
}
|
||||||
n
|
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"),
|
_ => panic!("Invalid syntax"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns nothing as the assembler discards comments
|
// returns nothing as the assembler discards comments
|
||||||
fn parse_comment(&mut self) {
|
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();
|
self.src.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_comment_multiline(&mut self) {
|
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();
|
self.src.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_string_literal(&mut self) -> String {
|
fn parse_string_literal(&mut self) -> String {
|
||||||
let mut result = String::new();
|
let mut result = String::new();
|
||||||
while let Some(ch) = self.src.pop()
|
while self.src.last().is_some() && !self.src.ends_with(b"\"") {
|
||||||
&& self.src.ends_with(b"\"")
|
let ch = self.src.pop().unwrap();
|
||||||
{
|
|
||||||
if ch == b'\\' {
|
if ch == b'\\' {
|
||||||
let escaped = self
|
let escaped = self
|
||||||
.src
|
.src
|
||||||
@@ -154,6 +217,8 @@ impl Lexer {
|
|||||||
result.push(ch as char);
|
result.push(ch as char);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.src.pop().unwrap();
|
||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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<Vec<u8>, 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}",),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
@@ -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<Symbol>,
|
||||||
|
pub instruction: Option<AsmInstruction>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Parser {
|
||||||
|
tokens: Vec<Token>,
|
||||||
|
nodes: Option<Vec<AsmInstruction>>,
|
||||||
|
dependencies: Vec<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parser {
|
||||||
|
pub fn new(tokens: Vec<Token>) -> Self {
|
||||||
|
Parser {
|
||||||
|
tokens,
|
||||||
|
nodes: Some(vec![]),
|
||||||
|
dependencies: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(&mut self) -> Result<Vec<AsmInstruction>, AssembleError> {
|
||||||
|
Ok(vec![])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_instruction(&mut self) -> Result<AsmNode, AssembleError> {
|
||||||
|
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<Token, AssembleError> {
|
||||||
|
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<Token, AssembleError> {
|
||||||
|
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))),
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
pub mod lexer;
|
|
||||||
pub mod parser;
|
|
||||||
@@ -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<String> = 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,3 +7,4 @@ edition = "2024"
|
|||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
strum = { version = "0.28.0", features = ["derive"] }
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
|
use strum::{Display, EnumString};
|
||||||
|
|
||||||
use crate::prelude::Register;
|
use crate::prelude::Register;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct Location {
|
pub struct Location {
|
||||||
offset: u16,
|
offset: u16,
|
||||||
register: Option<Register>,
|
register: Option<Register>,
|
||||||
@@ -20,13 +23,95 @@ impl Location {
|
|||||||
// if a register is being used already we have to add the offset to that.
|
// if a register is being used already we have to add the offset to that.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum ImmConst {
|
pub enum ImmConst {
|
||||||
Label(String),
|
Label(String),
|
||||||
Imm16(u16),
|
Imm16(u16),
|
||||||
Imm32(u32),
|
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,
|
Nop,
|
||||||
|
|
||||||
// move
|
// move
|
||||||
|
|||||||
@@ -222,7 +222,7 @@ impl fmt::Debug for Instruction {
|
|||||||
Some(op @ (Opcode::Shl | Opcode::Shr)) => {
|
Some(op @ (Opcode::Shl | Opcode::Shr)) => {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"{op:?} {src:?}, {dest:?}, r:{rshamt:?} + i:{ishamt}",
|
"{op:?} {src:?}, {dest:?}, {rshamt:?} + {ishamt}",
|
||||||
op = op,
|
op = op,
|
||||||
src = self.src1_checked(),
|
src = self.src1_checked(),
|
||||||
dest = self.dest_checked(),
|
dest = self.dest_checked(),
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
|
use strum::{Display, EnumString};
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default, EnumString)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
|
#[strum(ascii_case_insensitive)]
|
||||||
pub enum Register {
|
pub enum Register {
|
||||||
// general purpose
|
// general purpose
|
||||||
Rg0 = 0,
|
Rg0 = 0,
|
||||||
@@ -22,24 +25,35 @@ pub enum Register {
|
|||||||
Rge = 14,
|
Rge = 14,
|
||||||
Rgf = 15,
|
Rgf = 15,
|
||||||
|
|
||||||
// special purpose
|
Acc = 16, // temp/scratch register
|
||||||
Acc = 16,
|
|
||||||
|
// stack registers
|
||||||
Spr = 17,
|
Spr = 17,
|
||||||
Bpr = 18,
|
Bpr = 18,
|
||||||
Ret = 19,
|
|
||||||
Idr = 20,
|
Idr = 20, // points to interrupt descriptor table
|
||||||
Mmr = 21,
|
Mmr = 21, // points to page tables
|
||||||
|
|
||||||
// system - read only
|
// system - read only
|
||||||
Zero = 22,
|
Zero = 22, // always reads as zero.
|
||||||
Sts = 23,
|
Sts = 23, // status register for flags
|
||||||
Cir = 24,
|
Cir = 24, // current instruction register
|
||||||
Pcx = 25,
|
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]
|
#[default]
|
||||||
|
#[strum(disabled, to_string = "Null")] // don't include this in FromStr impl
|
||||||
Null = 26,
|
Null = 26,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Register {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{:?}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Register {
|
impl Register {
|
||||||
/// Total number of registers, not counting Null as it's not a real register.
|
/// Total number of registers, not counting Null as it's not a real register.
|
||||||
/// reading or writing to Null is undefined behaviour.
|
/// reading or writing to Null is undefined behaviour.
|
||||||
@@ -66,90 +80,3 @@ impl Register {
|
|||||||
Ok(unsafe { Self::from_u8_unchecked(idx) })
|
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<Self, Self::Err> {
|
|
||||||
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(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -26,8 +26,11 @@ common = { path = "../common" }
|
|||||||
egui = "0.33.3"
|
egui = "0.33.3"
|
||||||
eframe = "0.33.3"
|
eframe = "0.33.3"
|
||||||
egui_tiles = "0.14.1"
|
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]
|
[features]
|
||||||
default = ["mainstore-prealloc"]
|
default = ["mainstore-prealloc"]
|
||||||
|
|||||||
@@ -17,9 +17,16 @@ pub struct DsaArgs {
|
|||||||
|
|
||||||
#[arg(long = "iomap")]
|
#[arg(long = "iomap")]
|
||||||
io_map: Option<PathBuf>,
|
io_map: Option<PathBuf>,
|
||||||
|
|
||||||
|
#[arg(long = "headless")]
|
||||||
|
headless: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DsaArgs {
|
impl DsaArgs {
|
||||||
|
pub fn headless(&self) -> bool {
|
||||||
|
self.headless
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_memory_map(&self) -> MemoryMap {
|
pub fn get_memory_map(&self) -> MemoryMap {
|
||||||
self.memory_map
|
self.memory_map
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ pub enum Interrupt {
|
|||||||
|
|
||||||
// IRQ's (32-63)
|
// IRQ's (32-63)
|
||||||
Hardware(u8),
|
Hardware(u8),
|
||||||
|
SerialIn = 32,
|
||||||
|
|
||||||
// Syscalls (64-255)
|
// Syscalls (64-255)
|
||||||
// OS defined, program uses INT 64-127
|
// OS defined, program uses INT 64-127
|
||||||
@@ -33,6 +34,7 @@ impl Interrupt {
|
|||||||
Interrupt::WriteToReadOnly => 6,
|
Interrupt::WriteToReadOnly => 6,
|
||||||
Interrupt::ReadFromWriteOnly => 7,
|
Interrupt::ReadFromWriteOnly => 7,
|
||||||
Interrupt::UnmappedIo => 8,
|
Interrupt::UnmappedIo => 8,
|
||||||
|
Interrupt::SerialIn => 32,
|
||||||
Interrupt::Hardware(x) => {
|
Interrupt::Hardware(x) => {
|
||||||
if *x < 32 || *x > 63 {
|
if *x < 32 || *x > 63 {
|
||||||
0
|
0
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ pub struct Emulator {
|
|||||||
shared_state: Arc<SharedState>,
|
shared_state: Arc<SharedState>,
|
||||||
|
|
||||||
// Interrupts
|
// Interrupts
|
||||||
interrupts: mpsc::Receiver<u8>,
|
interrupts: mpsc::Receiver<Interrupt>,
|
||||||
pending_fault: Option<Interrupt>,
|
pending_fault: Option<Interrupt>,
|
||||||
|
|
||||||
// memory
|
// memory
|
||||||
@@ -45,7 +45,7 @@ pub struct Emulator {
|
|||||||
unsafe impl Send for Emulator {}
|
unsafe impl Send for Emulator {}
|
||||||
impl Emulator {
|
impl Emulator {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let (sender, receiver) = mpsc::channel::<u8>();
|
let (sender, receiver) = mpsc::channel::<Interrupt>();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
interrupts: receiver,
|
interrupts: receiver,
|
||||||
@@ -163,6 +163,8 @@ impl Emulator {
|
|||||||
while self.shared_state.reseting.load(Ordering::Relaxed) {
|
while self.shared_state.reseting.load(Ordering::Relaxed) {
|
||||||
thread::sleep(Duration::from_millis(100));
|
thread::sleep(Duration::from_millis(100));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.internal_state.halted = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,19 +184,19 @@ impl Emulator {
|
|||||||
self.check_update();
|
self.check_update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// Check for interrupts.
|
||||||
|
if let Ok(int) = self.interrupts.recv_timeout(Duration::from_millis(100)) {
|
||||||
|
self.internal_state.halted = false;
|
||||||
|
self.interrupt(int);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// if the cpu didn't halt on it's own then it's ready to run again.
|
// if the cpu didn't halt on it's own then it's ready to run again.
|
||||||
if !self.internal_state.halted {
|
if !self.internal_state.halted {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
loop {
|
|
||||||
// Check for interrupts.
|
|
||||||
if let Ok(code) = self.interrupts.recv_timeout(Duration::from_millis(100)) {
|
|
||||||
self.internal_state.halted = false;
|
|
||||||
self.interrupt(Interrupt::Software(code));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// UI is resetting the emulator
|
// UI is resetting the emulator
|
||||||
self.check_update();
|
self.check_update();
|
||||||
}
|
}
|
||||||
@@ -230,7 +232,7 @@ impl Emulator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
match self.interrupts.try_recv() {
|
match self.interrupts.try_recv() {
|
||||||
Ok(code) => self.interrupt(Interrupt::Software(code)),
|
Ok(int) => self.interrupt(int),
|
||||||
Err(TryRecvError::Disconnected) => break 'emu,
|
Err(TryRecvError::Disconnected) => break 'emu,
|
||||||
Err(TryRecvError::Empty) => {}
|
Err(TryRecvError::Empty) => {}
|
||||||
}
|
}
|
||||||
@@ -270,14 +272,21 @@ impl Emulator {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn interrupt(&mut self, int: Interrupt) {
|
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;
|
*self.mut_reg(Register::Spr) -= 4;
|
||||||
let spr = self.reg(Register::Spr);
|
self.mem_write_word(self.reg(Register::Spr), self.reg(reg));
|
||||||
let pcx = self.reg(Register::Pcx);
|
}
|
||||||
self.mem_write_word(spr, pcx);
|
|
||||||
|
|
||||||
*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]
|
#[inline]
|
||||||
@@ -477,24 +486,15 @@ impl Emulator {
|
|||||||
self.interrupt(Interrupt::Software(word.imm16() as u8));
|
self.interrupt(Interrupt::Software(word.imm16() as u8));
|
||||||
}
|
}
|
||||||
Some(Opcode::Hlt) => self.halt(),
|
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 => {}
|
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]
|
#[inline]
|
||||||
fn mem_read_byte(&mut self, addr: u32) -> u8 {
|
fn mem_read_byte(&mut self, addr: u32) -> u8 {
|
||||||
self.mainstore.read_byte(addr)
|
self.mainstore.read_byte(addr)
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ use arc_swap::ArcSwap;
|
|||||||
|
|
||||||
use crate::backend::processor::processor::ProcessorSnapshot;
|
use crate::backend::processor::processor::ProcessorSnapshot;
|
||||||
|
|
||||||
|
use super::interrupts::Interrupt;
|
||||||
|
|
||||||
pub struct SharedState {
|
pub struct SharedState {
|
||||||
pub proc: ArcSwap<ProcessorSnapshot>,
|
pub proc: ArcSwap<ProcessorSnapshot>,
|
||||||
|
|
||||||
@@ -11,11 +13,11 @@ pub struct SharedState {
|
|||||||
pub paused: AtomicBool,
|
pub paused: AtomicBool,
|
||||||
pub update_req: AtomicBool,
|
pub update_req: AtomicBool,
|
||||||
|
|
||||||
interrupt_queue: mpsc::Sender<u8>,
|
pub interrupt_queue: mpsc::Sender<Interrupt>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SharedState {
|
impl SharedState {
|
||||||
pub fn new(sender: mpsc::Sender<u8>) -> Self {
|
pub fn new(sender: mpsc::Sender<Interrupt>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
reseting: AtomicBool::new(false),
|
reseting: AtomicBool::new(false),
|
||||||
proc: ArcSwap::new(Arc::new(ProcessorSnapshot::default())),
|
proc: ArcSwap::new(Arc::new(ProcessorSnapshot::default())),
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
pub mod controller;
|
pub mod controller;
|
||||||
pub mod display;
|
pub mod display;
|
||||||
|
pub mod editor;
|
||||||
pub mod framebuffer;
|
pub mod framebuffer;
|
||||||
|
pub mod memory;
|
||||||
pub mod registers;
|
pub mod registers;
|
||||||
pub mod serial;
|
pub mod serial;
|
||||||
|
|
||||||
|
|||||||
@@ -83,13 +83,15 @@ impl Component for Controller {
|
|||||||
// Status info
|
// Status info
|
||||||
ui.label(format!(
|
ui.label(format!(
|
||||||
"Status: {}",
|
"Status: {}",
|
||||||
match (state.halted, paused) {
|
match paused {
|
||||||
(true, false) => "Halted",
|
true => "Paused",
|
||||||
(_, false) => "Running",
|
false => "Running",
|
||||||
(_, true) => "Paused",
|
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
|
||||||
|
ui.label(format!("Halted: {}", state.halted));
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
let pcx = state.registers[Register::Pcx as usize];
|
let pcx = state.registers[Register::Pcx as usize];
|
||||||
let clock = state.clock;
|
let clock = state.clock;
|
||||||
let time = state.time.as_micros();
|
let time = state.time.as_micros();
|
||||||
|
|||||||
@@ -1,7 +1,68 @@
|
|||||||
use egui::{Color32, FontId, Vec2};
|
use egui::{Color32, FontId, Vec2};
|
||||||
|
|
||||||
|
use crate::{MemoryBank, Page, backend::memory::PhysAddr};
|
||||||
|
|
||||||
use super::Component;
|
use super::Component;
|
||||||
pub use crate::io::display::Display;
|
|
||||||
|
pub struct Display {
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
addr: PhysAddr,
|
||||||
|
|
||||||
|
mem: MemoryBank,
|
||||||
|
buffer: Vec<u8>,
|
||||||
|
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<u8> {
|
||||||
|
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::<Vec<u8>>();
|
||||||
|
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<u8> {
|
||||||
|
self.internal_read()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Component for Display {
|
impl Component for Display {
|
||||||
fn title(&self) -> &str {
|
fn title(&self) -> &str {
|
||||||
|
|||||||
@@ -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<SharedState>,
|
||||||
|
memory: MemoryBank,
|
||||||
|
|
||||||
|
// editor state
|
||||||
|
path: Option<PathBuf>,
|
||||||
|
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<FileDialog>,
|
||||||
|
save_file_dialog: Option<FileDialog>,
|
||||||
|
|
||||||
|
// other
|
||||||
|
visible: bool,
|
||||||
|
show_output: bool,
|
||||||
|
error: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<SharedState>, 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::<Vec<u8>>()
|
||||||
|
} 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::<Vec<u8>>()
|
||||||
|
} 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> = 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<u32> {
|
||||||
|
address.strip_prefix("0x").map_or_else(
|
||||||
|
|| {
|
||||||
|
address.strip_prefix("0b").map_or_else(
|
||||||
|
|| {
|
||||||
|
address.strip_prefix("0o").map_or_else(
|
||||||
|
|| address.parse::<u32>().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<u8>,
|
||||||
|
|
||||||
|
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<u8> {
|
||||||
|
&mut self.data
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> &Vec<u8> {
|
||||||
|
&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::<Vec<_>>()
|
||||||
|
.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::<Vec<_>>()
|
||||||
|
// .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();
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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([
|
||||||
|
",", ";", ".", ":", "=", "+", "-", "*", "/", "%", "&", "|", "^", "~", "!", "?", "<",
|
||||||
|
">", "<<", ">>", "==", "!=", "<=", ">=", "&&", "||",
|
||||||
|
]),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<u32, ParseIntError> {
|
||||||
|
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::<u32>()
|
||||||
|
}
|
||||||
@@ -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<u8>,
|
||||||
|
pub write_tx: Sender<u8>,
|
||||||
|
pub read_buff: Vec<u8>,
|
||||||
|
pub write_buff: String,
|
||||||
|
|
||||||
|
pub visible: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serial {
|
||||||
|
fn uart_io_thread(
|
||||||
|
mem: MemoryBank,
|
||||||
|
state: Arc<SharedState>,
|
||||||
|
uart_base: PhysAddr,
|
||||||
|
tx: Sender<u8>,
|
||||||
|
rx: Receiver<u8>,
|
||||||
|
) {
|
||||||
|
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<SharedState>, 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 {
|
impl Component for Serial {
|
||||||
fn title(&self) -> &str {
|
fn title(&self) -> &str {
|
||||||
@@ -9,9 +88,23 @@ impl Component for Serial {
|
|||||||
&mut self.visible
|
&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();
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
use eframe::NativeOptions;
|
use eframe::NativeOptions;
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
|
use std::mem;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
mod components;
|
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 crate::{MemoryBank, SharedState, config::VERSION};
|
||||||
use components::{controller::Controller, display::Display, registers::Registers};
|
|
||||||
|
|
||||||
pub fn run_app(state: Arc<SharedState>, mem_handle: MemoryBank) -> eframe::Result<()> {
|
pub fn run_app(state: Arc<SharedState>, mem_handle: MemoryBank) -> eframe::Result<()> {
|
||||||
eframe::run_native(
|
eframe::run_native(
|
||||||
@@ -90,6 +91,8 @@ impl DsaUi {
|
|||||||
Box::new(FrameBuffer::new(320, 240, 0x30000, mem_handle.clone())),
|
Box::new(FrameBuffer::new(320, 240, 0x30000, mem_handle.clone())),
|
||||||
Box::new(Registers::new(state.clone())),
|
Box::new(Registers::new(state.clone())),
|
||||||
Box::new(Serial::new(0x40000, state.clone(), mem_handle.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);
|
components.iter_mut().for_each(|c| *c.visible() = false);
|
||||||
|
|
||||||
|
|||||||
@@ -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<u8>,
|
|
||||||
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<u8> {
|
|
||||||
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::<Vec<u8>>();
|
|
||||||
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<u8> {
|
|
||||||
self.internal_read()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -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<u8>,
|
|
||||||
pub buffer: Vec<u8>,
|
|
||||||
|
|
||||||
pub visible: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Serial {
|
|
||||||
fn uart_io_thread(mem: MemoryBank, uart_base: PhysAddr, tx: Sender<u8>) {
|
|
||||||
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<SharedState>, 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,6 @@ pub mod args;
|
|||||||
pub mod backend;
|
pub mod backend;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod frontend;
|
pub mod frontend;
|
||||||
pub mod io;
|
|
||||||
|
|
||||||
pub use backend::memory::ram::*;
|
pub use backend::memory::ram::*;
|
||||||
pub use backend::{processor::processor::Emulator, processor::state::SharedState};
|
pub use backend::{processor::processor::Emulator, processor::state::SharedState};
|
||||||
|
|||||||
@@ -47,5 +47,17 @@ fn main() {
|
|||||||
.spawn(move || emulator.run().unwrap())
|
.spawn(move || emulator.run().unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
if args.headless() {
|
||||||
|
run_server(&args);
|
||||||
|
} else {
|
||||||
run_app(state, mem_handle).unwrap()
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
Binary file not shown.
+22
-2
@@ -34,20 +34,34 @@ dw current: 0x20000
|
|||||||
// ------------------------------------------
|
// ------------------------------------------
|
||||||
// prints the string at addr(arg[0]) to the screen.
|
// prints the string at addr(arg[0]) to the screen.
|
||||||
print: func
|
print: func
|
||||||
|
push rg0
|
||||||
|
push rg1
|
||||||
|
push rg2
|
||||||
|
push rg3
|
||||||
|
|
||||||
ldw bpr, rg0, 8
|
ldw bpr, rg0, 8
|
||||||
ldw current, rg1
|
ldw current, rg1
|
||||||
|
|
||||||
_print_loop:
|
_print_loop:
|
||||||
ldb rg0, rg2
|
ldb rg0, rg2
|
||||||
ieq rg2, zero, rg4
|
ieq rg2, zero, rg3
|
||||||
jnz rg4, _end
|
jnz rg3, _print_end
|
||||||
stb rg2, rg1
|
stb rg2, rg1
|
||||||
|
|
||||||
addi rg0, 1
|
addi rg0, 1
|
||||||
addi rg1, 1
|
addi rg1, 1
|
||||||
|
|
||||||
jmp _print_loop
|
jmp _print_loop
|
||||||
|
|
||||||
|
_print_end:
|
||||||
|
stw rg1, current
|
||||||
|
|
||||||
|
pop rg3
|
||||||
|
pop rg2
|
||||||
|
pop rg1
|
||||||
|
pop rg0
|
||||||
|
return
|
||||||
|
|
||||||
// ------------------------------------------
|
// ------------------------------------------
|
||||||
// println:
|
// println:
|
||||||
// push bpr
|
// push bpr
|
||||||
@@ -99,11 +113,17 @@ _print_loop:
|
|||||||
// ------------------------------------------
|
// ------------------------------------------
|
||||||
// prints the last byte of arg[0] to the screen.
|
// prints the last byte of arg[0] to the screen.
|
||||||
print_byte: func
|
print_byte: func
|
||||||
|
push rg0
|
||||||
|
push rg1
|
||||||
|
|
||||||
ldw bpr, rg0, 8
|
ldw bpr, rg0, 8
|
||||||
ldw current, rg1
|
ldw current, rg1
|
||||||
|
|
||||||
stb rg0, rg1
|
stb rg0, rg1
|
||||||
addi rg1, 1
|
addi rg1, 1
|
||||||
|
|
||||||
|
pop rg1
|
||||||
|
pop rg0
|
||||||
jmp _end
|
jmp _end
|
||||||
|
|
||||||
// ------------------------------------------
|
// ------------------------------------------
|
||||||
|
|||||||
+92
-39
@@ -1,63 +1,116 @@
|
|||||||
// lib:
|
|
||||||
// serial.dsa
|
|
||||||
//
|
|
||||||
// usage:
|
|
||||||
// include serial "<relative path>"
|
|
||||||
//
|
|
||||||
// push (register containing address of string)
|
|
||||||
// call serial::print
|
|
||||||
//
|
|
||||||
// push (register containing byte)
|
|
||||||
// call serial::print_byte
|
|
||||||
|
|
||||||
dw uart: 0x40000
|
dw uart: 0x40000
|
||||||
|
|
||||||
// ------------------------------------------
|
// ------------------------------------------
|
||||||
// transmit a single byte via sentinel protocol
|
// reads up to rg1 bytes into buffer at rg2
|
||||||
// expects: byte in rg0, trashes rg2, rg3, rg4, rg5
|
// 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:
|
_serial_send:
|
||||||
shl rg0, 16, rg2 // shift byte to bits [23:16]
|
push rg0
|
||||||
lli 0x01, rg0
|
push rg2
|
||||||
shl rg0, 24, rg0 // valid flag in bits [31:24]
|
push rg3
|
||||||
or rg0, rg2, rg2 // rg2 = 0x01_XX_0000
|
push rg4
|
||||||
|
push rg5
|
||||||
ldw uart, rg5 // rg5 = 0x40000 (uart address, held for store)
|
ldw uart, rg5
|
||||||
|
|
||||||
_serial_wait:
|
_serial_wait:
|
||||||
ldw rg5, rg0 // poll mem[0x40000]
|
ldb rg5, rg2, 3 // base+0x03 = write valid flag
|
||||||
shr rg0, 24, rg0
|
|
||||||
lli 0x01, rg3
|
lli 0x01, rg3
|
||||||
ieq rg0, rg3, rg4
|
ieq rg2, rg3, rg4
|
||||||
jnz rg4, _serial_wait // loop while valid == 1
|
jnz rg4, _serial_wait // wait while emulator hasn't consumed yet
|
||||||
|
stb rg0, rg5, 2 // base+0x02 = data byte
|
||||||
stw rg2, rg5 // mem[0x40000] = sentinel word
|
lli 0x01, rg2
|
||||||
|
stb rg2, rg5, 3 // base+0x03 = set valid flag
|
||||||
|
pop rg5
|
||||||
|
pop rg4
|
||||||
|
pop rg3
|
||||||
|
pop rg2
|
||||||
|
pop rg0
|
||||||
ret
|
ret
|
||||||
|
|
||||||
// ------------------------------------------
|
// ------------------------------------------
|
||||||
// prints the null-terminated string at addr(arg[0])
|
// prints the null-terminated string at addr(arg[0])
|
||||||
print: func
|
print: func
|
||||||
|
push rg0
|
||||||
|
push rg1
|
||||||
|
push rg2
|
||||||
ldw bpr, rg0, 8 // load string address from arg
|
ldw bpr, rg0, 8 // load string address from arg
|
||||||
|
|
||||||
_serial_print_loop:
|
_serial_print_loop:
|
||||||
ldb rg0, rg2 // load byte
|
ldb rg0, rg1 // load byte
|
||||||
ieq rg2, zero, rg4 // null terminator?
|
ieq rg1, zero, rg2 // null terminator?
|
||||||
jnz rg4, _serial_end
|
jnz rg2, _serial_print_end
|
||||||
|
|
||||||
push rg0 // save string pointer
|
push rg0 // save string pointer
|
||||||
mov rg2, rg0
|
mov rg1, rg0
|
||||||
call _serial_send
|
call _serial_send
|
||||||
pop rg0
|
pop rg0
|
||||||
|
addi rg0, 1, rg0
|
||||||
addi rg0, 1
|
|
||||||
jmp _serial_print_loop
|
jmp _serial_print_loop
|
||||||
|
_serial_print_end:
|
||||||
|
pop rg2
|
||||||
|
pop rg1
|
||||||
|
pop rg0
|
||||||
|
jmp _serial_end
|
||||||
// ------------------------------------------
|
// ------------------------------------------
|
||||||
// prints the last byte of arg[0]
|
// prints the last byte of arg[0]
|
||||||
print_byte: func
|
print_byte: func
|
||||||
|
push rg0
|
||||||
ldw bpr, rg0, 8
|
ldw bpr, rg0, 8
|
||||||
call _serial_send
|
call _serial_send
|
||||||
|
pop rg0
|
||||||
jmp _serial_end
|
jmp _serial_end
|
||||||
|
|
||||||
// ------------------------------------------
|
// ------------------------------------------
|
||||||
_serial_end:
|
_serial_end:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,14 +1,44 @@
|
|||||||
include serial: "./serial.dsa"
|
include serial: "./serial.dsa"
|
||||||
|
include idt: "./idt.dsa"
|
||||||
|
include print: "./print.dsa"
|
||||||
|
|
||||||
|
resb buff: 256
|
||||||
|
|
||||||
dw stack: 0x1000
|
dw stack: 0x1000
|
||||||
_init:
|
_init:
|
||||||
ldw stack, bpr
|
ldw stack, bpr
|
||||||
mov bpr, spr
|
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:
|
main:
|
||||||
lwi hello, rg0
|
lwi hello, rg0
|
||||||
push rg0
|
push rg0
|
||||||
call serial::print
|
call serial::print
|
||||||
pop zero
|
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
|
hlt
|
||||||
|
|||||||
Reference in New Issue
Block a user