IT WORKS HELL YEAH.

This commit is contained in:
2025-06-18 22:53:01 +01:00
parent 1210b19333
commit e281bc2d1d
26 changed files with 715 additions and 188 deletions
Generated
+17 -9
View File
@@ -659,6 +659,12 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "colorful"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffb474a9c3219a8254ead020421ecf1b90427f29b55f6aae9a2471fa62c126ef"
[[package]]
name = "combine"
version = "4.6.7"
@@ -903,6 +909,16 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76"
[[package]]
name = "dsa_editor"
version = "0.1.0"
dependencies = [
"colorful",
"eframe",
"egui",
"serde",
]
[[package]]
name = "ecolor"
version = "0.31.1"
@@ -1005,14 +1021,6 @@ dependencies = [
"winit",
]
[[package]]
name = "egui_code_editor"
version = "0.2.13"
source = "git+https://github.com/zxq5-dev/egui_code_editor?rev=5eb313e#5eb313e38504410ce0a6b27231cda28842f542fe"
dependencies = [
"egui",
]
[[package]]
name = "egui_glow"
version = "0.31.1"
@@ -1048,9 +1056,9 @@ dependencies = [
"common",
"dirs",
"discord-presence",
"dsa_editor",
"eframe",
"egui",
"egui_code_editor",
"rfd",
"serde",
"toml",
+1 -1
View File
@@ -1,5 +1,5 @@
[workspace]
members = ["emulator", "common", "assembler"]
members = ["emulator", "common", "assembler", "dsa_editor"]
resolver = "3"
[workspace.package]
+187 -3
View File
@@ -1,6 +1,190 @@
use common::{
instructions,
prelude::{Instruction, RTypeArgs},
args, instructions,
prelude::{ITypeArgs, Instruction, Interrupt, RTypeArgs, Register},
};
use crate::model::{Node, Opcode};
use crate::{
AssembleError, expect_token, expect_type,
model::{Node, Opcode, Token, TokenType},
};
pub fn codegen(nodes: Vec<Node>) -> Result<Vec<Instruction>, AssembleError> {
let mut instructions = vec![];
for node in nodes {
instructions.push(build_instruction(node)?);
}
Ok(instructions)
}
fn build_instruction(node: Node) -> Result<Instruction, AssembleError> {
let opcode = node.opcode();
let args = node.args();
// println!("{node}");
match opcode {
Opcode::Nop => Ok(Instruction::Nop),
Opcode::Mov => {
let src = expect_token!(args.get(0).unwrap(), Register)?;
let dest = expect_token!(args.get(1).unwrap(), Register)?;
Ok(Instruction::Mov(args!(R, sr1: src, dr: dest)))
}
Opcode::Movs => {
let src = expect_token!(args.get(0).unwrap(), Register)?;
let dest = expect_token!(args.get(1).unwrap(), Register)?;
Ok(Instruction::MovSigned(args!(R, sr1: src, dr: dest)))
}
Opcode::Ldb
| Opcode::Ldw
| Opcode::Ldh
| Opcode::Ldbs
| Opcode::Ldhs
| Opcode::Stb
| Opcode::Stw
| Opcode::Sth => {
let base = expect_token!(args.get(0).unwrap(), Register)?;
let dest = expect_token!(args.get(1).unwrap(), Register)?;
let offset = expect_token!(args.get(2).unwrap(), Immediate)?;
let args = args!(I, immediate: offset as u16, r1: base, r2: dest);
match opcode {
Opcode::Ldb => Ok(Instruction::LoadByte(args)),
Opcode::Ldw => Ok(Instruction::LoadWord(args)),
Opcode::Ldh => Ok(Instruction::LoadHalfword(args)),
Opcode::Ldbs => Ok(Instruction::LoadByteSigned(args)),
Opcode::Ldhs => Ok(Instruction::LoadHalfwordSigned(args)),
Opcode::Stb => Ok(Instruction::StoreByte(args)),
Opcode::Stw => Ok(Instruction::StoreWord(args)),
Opcode::Sth => Ok(Instruction::StoreHalfword(args)),
_ => unreachable!(),
}
}
Opcode::Lli => {
let value = expect_token!(args.get(0).unwrap(), Immediate)?;
let dest = expect_token!(args.get(1).unwrap(), Register)?;
let args = args!(I, immediate: value as u16, r1: dest);
Ok(Instruction::LoadLowerImmediate(args))
}
Opcode::Lui => {
let value = expect_token!(args.get(0).unwrap(), Immediate)? >> 16;
let dest = expect_token!(args.get(1).unwrap(), Register)?;
let args = args!(I, immediate: value as u16, r1: dest);
Ok(Instruction::LoadUpperImmediate(args))
}
Opcode::Jmp
| Opcode::Jeq
| Opcode::Jne
| Opcode::Jgt
| Opcode::Jge
| Opcode::Jlt
| Opcode::Jle => {
let address = expect_token!(args.get(0).unwrap(), Immediate)?;
let offset = expect_token!(args.get(1).unwrap(), Register)?;
let args = args!(I, immediate: address as u16, r1: offset);
match opcode {
Opcode::Jmp => Ok(Instruction::Jump(args)),
Opcode::Jeq => Ok(Instruction::JumpEq(args)),
Opcode::Jne => Ok(Instruction::JumpNeq(args)),
Opcode::Jgt => Ok(Instruction::JumpGt(args)),
Opcode::Jge => Ok(Instruction::JumpGe(args)),
Opcode::Jlt => Ok(Instruction::JumpLt(args)),
Opcode::Jle => Ok(Instruction::JumpLe(args)),
_ => unreachable!(),
}
}
Opcode::Cmp => {
let left = expect_token!(args.get(0).unwrap(), Register)?;
let right = expect_token!(args.get(1).unwrap(), Register)?;
Ok(Instruction::Compare(args!(R, sr1: left, sr2: right)))
}
Opcode::Inc => {
let reg = expect_token!(args.get(0).unwrap(), Register)?;
Ok(Instruction::Increment(args!(R, sr1: reg)))
}
Opcode::Dec => {
let reg = expect_token!(args.get(0).unwrap(), Register)?;
Ok(Instruction::Decrement(args!(R, sr1: reg)))
}
Opcode::Shl => {
let reg = expect_token!(args.get(0).unwrap(), Register)?;
let amount = expect_token!(args.get(1).unwrap(), Immediate)? as u8;
Ok(Instruction::ShiftLeft(args!(R, sr1: reg, shamt: amount)))
}
Opcode::Shr => {
let reg = expect_token!(args.get(0).unwrap(), Register)?;
let amount = expect_token!(args.get(1).unwrap(), Immediate)? as u8;
Ok(Instruction::ShiftRight(args!(R, sr1: reg, shamt: amount)))
}
Opcode::Add
| Opcode::Sub
| Opcode::And
| Opcode::Or
| Opcode::Xor
| Opcode::Nand
| Opcode::Nor
| Opcode::Xnor => {
let left = expect_token!(args.get(0).unwrap(), Register)?;
let right = expect_token!(args.get(1).unwrap(), Register)?;
let dest = expect_token!(args.get(2).unwrap(), Register)?;
let args = args!(R, sr1: left, sr2: right, dr: dest);
match opcode {
Opcode::Add => Ok(Instruction::Add(args)),
Opcode::Sub => Ok(Instruction::Sub(args)),
Opcode::And => Ok(Instruction::And(args)),
Opcode::Or => Ok(Instruction::Or(args)),
Opcode::Xor => Ok(Instruction::Xor(args)),
Opcode::Nand => Ok(Instruction::Nand(args)),
Opcode::Nor => Ok(Instruction::Nor(args)),
Opcode::Xnor => Ok(Instruction::Xnor(args)),
_ => unreachable!(),
}
}
Opcode::Iadd | Opcode::Isub => {
let reg = expect_token!(args.get(0).unwrap(), Register)?;
let immediate = expect_token!(args.get(1).unwrap(), Immediate)? as u16;
let dest = expect_token!(args.get(2).unwrap(), Register)?;
let args = args!(I, immediate: immediate, r1: reg, r2: dest);
match opcode {
Opcode::Iadd => Ok(Instruction::AddImmediate(args)),
Opcode::Isub => Ok(Instruction::SubImmediate(args)),
_ => unreachable!(),
}
}
Opcode::Not => {
let reg = expect_token!(args.get(0).unwrap(), Register)?;
let dest = expect_token!(args.get(1).unwrap(), Register)?;
Ok(Instruction::Not(args!(R, sr1: reg, dr: dest)))
}
Opcode::Int => {
let code = expect_token!(args.get(0).unwrap(), Immediate)? as u8;
Ok(Instruction::Interrupt(Interrupt::Software(code)))
}
Opcode::Irt => Ok(Instruction::IntReturn),
Opcode::Hlt => Ok(Instruction::Halt),
Opcode::Data => {
let immediate = expect_token!(args.get(0).unwrap(), Immediate)?;
Ok(Instruction::Data(immediate))
}
Opcode::Segment => {
let immediate = expect_token!(args.get(0).unwrap(), Immediate)?;
Ok(Instruction::Segment(immediate))
}
Opcode::Db
| Opcode::Dh
| Opcode::Dw
| Opcode::Resb
| Opcode::Resh
| Opcode::Resw
| Opcode::Push
| Opcode::Pop
| Opcode::Lwi
| Opcode::Include => todo!(),
}
}
+43 -11
View File
@@ -29,7 +29,14 @@ fn try_expand(
match node.opcode() {
Opcode::Push => expand_push(node.clone(), result)?,
Opcode::Pop => expand_pop(node.clone(), result)?,
Opcode::Ldb | Opcode::Ldh | Opcode::Ldw => expand_ldx(node.clone(), result)?,
Opcode::Ldb
| Opcode::Ldbs
| Opcode::Ldh
| Opcode::Ldhs
| Opcode::Ldw
| Opcode::Stb
| Opcode::Sth
| Opcode::Stw => expand_ldx(node.clone(), result)?,
Opcode::Lwi => expand_lwi(node.clone(), result)?,
Opcode::Resb | Opcode::Resh | Opcode::Resw => expand_resx(node.clone(), result)?,
Opcode::Db | Opcode::Dh | Opcode::Dw => expand_dx(node.clone(), result)?,
@@ -43,8 +50,20 @@ fn expand_push(current: Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError
let reg = expect_type!(current.arg(0).unwrap(), Register)?;
nodes.extend(vec![
node!(label, Opcode::Iadd, reg.clone(), Token::Immediate(4)),
node!(None, Opcode::Stw, reg, Token::Register(Register::Spr)),
node!(
label,
Opcode::Iadd,
reg.clone(),
Token::Immediate(4),
reg.clone()
),
node!(
None,
Opcode::Stw,
reg,
Token::Register(Register::Spr),
Token::Immediate(0)
),
]);
Ok(())
@@ -55,8 +74,20 @@ fn expand_pop(current: Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError>
let reg = expect_type!(current.arg(0).unwrap(), Register)?;
nodes.extend(vec![
node!(label, Opcode::Isub, reg.clone(), Token::Immediate(4)),
node!(None, Opcode::Ldw, reg, Token::Register(Register::Spr)),
node!(
label,
Opcode::Isub,
reg.clone(),
Token::Immediate(4),
reg.clone()
),
node!(
None,
Opcode::Ldw,
reg,
Token::Register(Register::Spr),
Token::Immediate(0)
),
]);
Ok(())
@@ -65,24 +96,25 @@ fn expand_pop(current: Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError>
fn expand_ldx(current: Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
let opcode = current.opcode();
let name = expect_type!(current.arg(0).unwrap(), Symbol)?;
let reg = expect_type!(current.arg(2).unwrap(), Register)?;
let reg = expect_type!(current.arg(1).unwrap(), Register)?;
let offset = expect_type!(current.arg(2).unwrap(), Immediate)?;
nodes.extend(vec![
node!(current.label(), Opcode::Lli, name.clone(), reg.clone()),
node!(None, Opcode::Lui, name.clone(), reg.clone()),
node!(None, opcode, reg.clone(), Token::Immediate(0), reg),
node!(None, opcode, reg.clone(), reg, offset),
]);
Ok(())
}
fn expand_lwi(current: Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
let reg = expect_type!(current.arg(0).unwrap(), Register)?;
let name = expect_type!(current.arg(1).unwrap(), Symbol)?;
let val = expect_type!(current.arg(0).unwrap(), Symbol, Immediate)?;
let reg = expect_type!(current.arg(1).unwrap(), Register)?;
nodes.extend(vec![
node!(current.label(), Opcode::Lli, name.clone(), reg.clone()),
node!(None, Opcode::Lui, name.clone(), reg.clone()),
node!(current.label(), Opcode::Lli, val.clone(), reg.clone()),
node!(None, Opcode::Lui, val.clone(), reg.clone()),
]);
Ok(())
+16 -11
View File
@@ -9,10 +9,11 @@ use std::{
use common::prelude::Instruction;
use crate::{
codegen::codegen,
expand::expand_pseudo_ops,
model::{Node, Symbol, Token, TokenType},
parser::{Parser, Program},
resolver::{resolve_dependencies, resolve_symbols},
resolver::{create_sections, resolve_dependencies, resolve_symbols},
};
pub mod codegen;
@@ -22,26 +23,25 @@ pub mod model;
pub mod parser;
pub mod resolver;
pub fn assemble(src: &Path) -> Vec<Instruction> {
pub fn assemble(src: &Path) -> Result<Vec<Instruction>, AssembleError> {
let mut modules = HashSet::<u64>::new();
let mut program = Program::new();
let hash = quick_hash(src);
modules.insert(hash);
match prepare_dependency(src, &mut modules, &mut program) {
Ok(_) => {}
Err(err) => println!("BIG ERROR {err:?}"),
}
prepare_dependency(src, &mut modules, &mut program)?;
let mut nodes = program.nodes;
resolve_symbols(&mut nodes).unwrap();
for node in nodes {
println!("{node}");
create_sections(&mut nodes)?;
resolve_symbols(&mut nodes)?;
let instructions = codegen(nodes)?;
for inst in instructions.iter() {
println!("{}", inst);
}
vec![]
Ok(instructions)
}
fn prepare_dependency(
@@ -71,6 +71,7 @@ fn prepare_dependency(
log(&format!("{:20} {:20}", "Resolving Deps", filename));
let nodes = resolve_dependencies(parsed)?;
let deps = Parser::get_dependencies(&nodes)?;
log(&format!(
@@ -79,6 +80,10 @@ fn prepare_dependency(
));
let nodes = expand_pseudo_ops(nodes, file_hash)?;
for n in nodes.iter() {
println!("{}", n);
}
program.add_module(nodes);
for dep in deps {
+11 -6
View File
@@ -13,10 +13,15 @@ fn main() {
let src = PathBuf::from(input_path);
let mut output_file = fs::File::create(output_path).unwrap();
assembler::assemble(&src)
.iter()
.map(|i| i.encode())
.for_each(|i| {
output_file.write_all(&i.to_le_bytes()).unwrap();
});
match assembler::assemble(&src) {
Ok(res) => {
res.iter().map(|i| i.encode()).for_each(|i| {
output_file.write_all(&i.to_le_bytes()).unwrap();
});
}
Err(e) => {
eprintln!("{}", e);
std::process::exit(1);
}
}
}
+3
View File
@@ -141,6 +141,7 @@ impl fmt::Display for Opcode {
// special - generated by assembler
Opcode::Data => write!(f, "data"),
Opcode::Segment => write!(f, "[SEGMENT]"),
}
}
}
@@ -249,6 +250,7 @@ pub enum Opcode {
// fake instructions (these aren't present in the binary as instructions)
Data,
Segment,
}
#[derive(Debug)]
@@ -376,6 +378,7 @@ impl Opcode {
Self::Hlt => Some(0x24),
Self::Iadd => Some(0x25),
Self::Isub => Some(0x26),
Self::Segment => Some(0x27),
// Pseudo-instructions don't have opcode values
_ => None,
}
+50 -34
View File
@@ -68,6 +68,8 @@ impl Parser {
unreachable!();
}
println!("{:?}", self.peek_next()?);
// check if the Node starts with a label
let label = expect_token!(self.peek_next()?, Symbol).ok();
if label.is_some() {
@@ -85,32 +87,25 @@ impl Parser {
args = vec![reg1, reg2];
}
Opcode::Ldb | Opcode::Ldbs | Opcode::Ldh | Opcode::Ldhs | Opcode::Ldw => {
Opcode::Ldb
| Opcode::Ldbs
| Opcode::Ldh
| Opcode::Ldhs
| Opcode::Ldw
| Opcode::Stb
| Opcode::Sth
| Opcode::Stw => {
let base = expect_type!(self.next()?, Register, Symbol)?;
let dest = expect_type!(self.next()?, Register, Symbol)?;
let dest = expect_type!(self.next()?, Register)?;
let mut offset = Token::Immediate(0);
if let Ok(next) = self.peek_next() {
if let Ok(_) = expect_type!(next, Register, Immediate) {
if let Ok(_) = expect_type!(next, Immediate) {
offset = self.next()?;
}
}
args = vec![base, offset, dest];
}
Opcode::Stb | Opcode::Sth | Opcode::Stw => {
let base = expect_type!(self.next()?, Register, Symbol)?;
let dest = expect_type!(self.next()?, Register, Symbol)?;
let mut offset = Token::Immediate(0);
if let Ok(next) = self.peek_next() {
if let Ok(_) = expect_type!(next, Register, Immediate) {
offset = self.next()?;
}
}
args = vec![base, offset, dest];
args = vec![base, dest, offset];
}
Opcode::Add
@@ -159,14 +154,31 @@ impl Parser {
| Opcode::Jlt
| Opcode::Jle => {
let imm = expect_type!(self.next()?, Immediate, Symbol)?;
args = vec![imm];
let offset = if expect_type!(self.peek_next()?, Register).is_ok() {
self.next()?
} else {
Token::Register(Register::Zero)
};
args = vec![imm, offset];
}
// I-type instructions
Opcode::Lui | Opcode::Lli | Opcode::Lwi | Opcode::Iadd | Opcode::Isub => {
let reg = expect_type!(self.next()?, Register)?;
Opcode::Lui | Opcode::Lli | Opcode::Lwi => {
let imm = expect_type!(self.next()?, Immediate, Symbol)?;
args = vec![reg, imm];
let reg = expect_type!(self.next()?, Register)?;
args = vec![imm, reg];
}
// Immediate Arithmetic
Opcode::Iadd | Opcode::Isub => {
let reg = expect_type!(self.next()?, Register)?;
let imm = expect_type!(self.next()?, Immediate)?;
let reg2 = if expect_type!(self.peek_next()?, Register).is_ok() {
self.next()?
} else {
reg.clone()
};
args = vec![reg, imm, reg2];
}
// D-type pseudoinstructions (data definition)
@@ -199,10 +211,10 @@ impl Parser {
// Instructions with no arguments
Opcode::Hlt | Opcode::Nop | Opcode::Irt => {
args = Vec::new();
args = vec![];
}
Opcode::Data => {
Opcode::Data | Opcode::Segment => {
return Err(AssembleError::Generic);
}
}
@@ -238,14 +250,14 @@ impl Parser {
Opcode::Dh => {
// dh can take u16 immediates
while !self.tokens.is_empty() {
if let Token::Immediate(val) = self.tokens.last().unwrap() {
if *val <= u16::MAX as u32 {
match self.tokens.last().unwrap() {
Token::StringLit(_) => {
values.push(self.tokens.pop().unwrap());
} else {
break;
}
} else {
break;
Token::Immediate(val) if *val <= u16::MAX as u32 => {
values.push(self.tokens.pop().unwrap());
}
_ => break,
}
}
}
@@ -253,10 +265,14 @@ impl Parser {
Opcode::Dw => {
// dw can take u32 immediates
while !self.tokens.is_empty() {
if let Token::Immediate(_) = self.tokens.last().unwrap() {
values.push(self.tokens.pop().unwrap());
} else {
break;
match self.tokens.last().unwrap() {
Token::StringLit(_) => {
values.push(self.tokens.pop().unwrap());
}
Token::Immediate(val) if *val <= u32::MAX as u32 => {
values.push(self.tokens.pop().unwrap());
}
_ => break,
}
}
}
+36 -1
View File
@@ -1,9 +1,11 @@
use std::{collections::HashMap, path::PathBuf};
use common::prelude::Register;
use crate::{
AssembleError,
model::{Module, Node, Opcode, Symbol, Token},
quick_hash,
node, quick_hash,
};
pub fn resolve_symbols(nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
@@ -119,3 +121,36 @@ pub fn resolve_dependencies(mut nodes: Vec<Node>) -> Result<Vec<Node>, AssembleE
Ok(nodes)
}
pub fn create_sections(nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
let mut res = Vec::<Node>::with_capacity(nodes.len());
res.push(node!(None, Opcode::Segment, Token::Immediate(0)));
for n in nodes.iter() {
if n.opcode() == Opcode::Data {
res.push(n.clone());
}
}
res.push(node!(None, Opcode::Segment, Token::Immediate(1)));
let start = res.len() + 1;
res.insert(
0,
node!(
None,
Opcode::Jmp,
Token::Immediate(start as u32 * 4),
Token::Register(Register::Zero)
),
);
for n in nodes.iter() {
if !matches!(n.opcode(), Opcode::Data | Opcode::Include) {
res.push(n.clone());
}
}
*nodes = res;
Ok(())
}
+31 -4
View File
@@ -81,7 +81,7 @@ impl TryFrom<u8> for Register {
type Error = RegisterParseError;
fn try_from(idx: u8) -> Result<Self, Self::Error> {
if idx > 0x18 {
if idx > 0x1C {
return Err(RegisterParseError::InvalidIndex(idx));
}
@@ -111,6 +111,11 @@ impl TryFrom<u8> for Register {
0x15 => Self::Mmr,
0x16 => Self::Zero,
0x17 => Self::NoReg,
0x18 => Self::Mar,
0x19 => Self::Mdr,
0x1A => Self::Sts,
0x1B => Self::Cir,
0x1C => Self::Pcx,
_ => unreachable!("This is already checked for in top `if` branch."),
})
}
@@ -145,6 +150,7 @@ impl TryFrom<&str> for Register {
"mmr" => Ok(Self::Mmr),
"zero" => Ok(Self::Zero),
"null" => Ok(Self::NoReg),
"pcx" => Ok(Self::Pcx),
_ => Err(RegisterParseError::InvalidName(value.to_string())),
}
}
@@ -248,6 +254,14 @@ pub enum Instruction {
Interrupt(Interrupt) = 0x22,
IntReturn = 0x23,
Halt = 0x24,
// Immediate Arithmetic
AddImmediate(args::ITypeArgs) = 0x25,
SubImmediate(args::ITypeArgs) = 0x26,
// Fake Instructions
Data(u32) = 0x3E,
Segment(u32) = 0x3F,
}
impl PartialEq for Instruction {
@@ -319,6 +333,10 @@ impl Instruction {
Self::Nand(_) => "nand",
Self::Nor(_) => "nor",
Self::Xnor(_) => "xnor",
Self::Data(_) => "data",
Self::AddImmediate(_) => "addi",
Self::SubImmediate(_) => "subi",
Self::Segment(_) => "[SEGMENT]",
}
}
@@ -355,7 +373,11 @@ impl std::fmt::Display for Instruction {
| Self::StoreByte(args)
| Self::StoreHalfword(args)
| Self::StoreWord(args) => {
write!(f, " {:x}({}), {}", args.immediate, args.r1, args.r2)
write!(
f,
" {}({:x}/{}), {}",
args.r1, args.immediate, args.immediate, args.r2
)
}
Self::Jump(args)
| Self::JumpEq(args)
@@ -364,10 +386,10 @@ impl std::fmt::Display for Instruction {
| Self::JumpGe(args)
| Self::JumpLt(args)
| Self::JumpLe(args) => {
write!(f, " ({:x}){}", args.immediate, args.r1)
write!(f, " 0x{:x}/{}({})", args.immediate, args.immediate, args.r1)
}
Self::LoadLowerImmediate(args) | Self::LoadUpperImmediate(args) => {
write!(f, " {}, {}, {}", args.immediate, args.r1, args.r2)
write!(f, " 0x{:x}, {}, {}", args.immediate, args.r1, args.r2)
}
Self::Compare(args) | Self::Not(args) => {
write!(f, " {}, {}", args.sr1, args.sr2)
@@ -388,6 +410,8 @@ impl std::fmt::Display for Instruction {
Self::Increment(a) | Self::Decrement(a) => write!(f, " {}", a.dr),
Self::Interrupt(a) => write!(f, " {}", a.as_u8()),
Self::Data(a) => write!(f, " {}", a),
Self::Segment(x) => write!(f, " [SEGMENT {}]", x),
_ => Ok(()),
}
}
@@ -439,6 +463,9 @@ impl TryFrom<u32> for Instruction {
0x22 => Ok(Self::Interrupt(Interrupt::from((data & 0xFF) as u8))),
0x23 => Ok(Self::IntReturn),
0x24 => Ok(Self::Halt),
0x25 => Ok(Self::AddImmediate(ITypeArgs::try_from(data)?)),
0x26 => Ok(Self::SubImmediate(ITypeArgs::try_from(data)?)),
0x3F => Ok(Self::Segment(data as u8 as u32)),
_ => Err(InstructionDecodeError::InvalidOpcode(opcode)),
}
}
+51
View File
@@ -155,3 +155,54 @@ impl TryFrom<u32> for RTypeArgs {
})
}
}
#[macro_export]
macro_rules! args {
// R-type arguments - allows omitting any field
(R $(, $field:ident: $value:expr)* $(,)?) => {{
let mut sr1: Option<Register> = None;
let mut sr2: Option<Register> = None;
let mut dr: Option<Register> = None;
let mut shamt: Option<u8> = None;
$(
args!(@assign_r_option sr1, sr2, dr, shamt, $field, $value);
)*
RTypeArgs::new(sr1, sr2, dr, shamt)
}};
// I-type arguments - requires immediate, allows omitting registers
(I, immediate: $immediate:expr $(, $field:ident: $value:expr)* $(,)?) => {{
let mut r1: Option<Register> = None;
let mut r2: Option<Register> = None;
$(
args!(@assign_i_option r1, r2, $field, $value);
)*
ITypeArgs::new($immediate, r1, r2)
}};
// Internal helpers (same as above for R-type)
(@assign_r_option $sr1:ident, $sr2:ident, $dr:ident, $shamt:ident, sr1, $value:expr) => {
$sr1 = Some($value);
};
(@assign_r_option $sr1:ident, $sr2:ident, $dr:ident, $shamt:ident, sr2, $value:expr) => {
$sr2 = Some($value);
};
(@assign_r_option $sr1:ident, $sr2:ident, $dr:ident, $shamt:ident, dr, $value:expr) => {
$dr = Some($value);
};
(@assign_r_option $sr1:ident, $sr2:ident, $dr:ident, $shamt:ident, shamt, $value:expr) => {
$shamt = Some($value);
};
// Internal helpers for I-type (without immediate handling)
(@assign_i_option $r1:ident, $r2:ident, r1, $value:expr) => {
$r1 = Some($value);
};
(@assign_i_option $r1:ident, $r2:ident, r2, $value:expr) => {
$r2 = Some($value);
};
}
+8 -2
View File
@@ -49,11 +49,17 @@ impl Encode for Instruction {
StoreWord, LoadLowerImmediate, LoadUpperImmediate, Jump,
JumpEq, JumpNeq, JumpGt, JumpGe, JumpLt, JumpLe, Compare,
Add, Sub, Increment, Decrement, ShiftLeft, ShiftRight,
And, Or, Not, Xor, Nand, Nor, Xnor
And, Or, Not, Xor, Nand, Nor, Xnor, AddImmediate, SubImmediate
],
no_args: [Nop, IntReturn, Halt],
special: [
Self::Interrupt(_) => todo!()
Self::Interrupt(_) => todo!(),
Self::Data(data) => data,
Self::Segment(segment) => {
let opcode = u32::from(self.opcode());
let segment = segment as u8;
(opcode << 26) | (segment as u32)
}
]
)
}
+1 -1
View File
@@ -18,7 +18,7 @@ impl Syntax {
"db", "dh", "dw", "resb", "resh", "resw", "push", "pop", "lwi", "call",
"ret",
]),
types: BTreeSet::from(["ptr", "byte", "word", "dword", "qword"]),
types: BTreeSet::from([]),
special: BTreeSet::from([
"rg0", "rg1", "rg2", "rg3", "rg4", "rg5", "rg6", "rg7", "rg8", "rg9",
"rga", "rgb", "rgc", "rgd", "rge", "rgf", "acc", "spr", "bpr", "ret",
+1 -1
View File
@@ -14,7 +14,7 @@ required-features = ["config"]
[dependencies]
common = { path = "../common" }
assembler = { path = "../assembler" }
dsa_editor = { git = "https://github.com/zxq5-dev/egui_code_editor", package = "egui_code_editor", rev = "5eb313e" }
dsa_editor = { path = "../dsa_editor" }
eframe = "0.31.1"
egui = "0.31.1"
rfd = "0.15.3"
-11
View File
@@ -36,8 +36,6 @@ pub fn run_emulator(
let mut instruction_count = 0;
loop {
println!("Looping");
let cmd = if running == Running::Running {
match cmd_rx.try_recv() {
Ok(cmd) => Some(cmd),
@@ -52,8 +50,6 @@ pub fn run_emulator(
};
if let Some(cmd) = cmd {
println!("Received command: {cmd:?}");
match cmd {
Command::Start => {
running = Running::Running;
@@ -69,18 +65,14 @@ pub fn run_emulator(
.expect("This is a valid path, WTF."),
));
}
println!("Emulator started");
}
Command::Stop => {
running = Running::Paused;
println!("Emulator stopped");
}
Command::Reset => {
running = Running::Paused;
processor.reset();
instruction_count = 0;
println!("Emulator rebooted");
}
Command::Step => {
running = Running::Paused;
@@ -98,15 +90,12 @@ pub fn run_emulator(
}
instruction_count += 1;
println!("Stepped one instruction");
}
Command::Read(new, _size) => {
addr = new;
println!("Memory view for address 0x{addr:08x}");
}
Command::Write(offset, data) => {
processor.memory.write_range(offset, data);
println!("Program loaded");
}
Command::Interrupt(_interrupt) => {
todo!("implement interrupts")
+1 -1
View File
@@ -78,7 +78,7 @@ impl MemoryUnit for MainStore {
bytes[1] = block.data[(offset + 1) as usize];
bytes[2] = block.data[(offset + 2) as usize];
bytes[3] = block.data[(offset + 3) as usize];
u32::from_le_bytes(bytes)
u32::from_be_bytes(bytes)
}
fn read_range(&mut self, addr: u32, size: u32) -> Vec<u8> {
+20 -6
View File
@@ -220,8 +220,8 @@ impl Executable for Instruction {
// address must be byte-aligned.
Self::StoreByte(a) => {
cpu.memory.write_byte(
cpu.get(a.r1) + u32::from(a.immediate),
cpu.get(a.r2) as u8,
cpu.get(a.r2) + u32::from(a.immediate),
cpu.get(a.r1) as u8,
);
}
@@ -231,16 +231,16 @@ impl Executable for Instruction {
// split the value into bytes and then write two bytes
let bytes = (cpu.get(a.r1) as u16).to_le_bytes();
cpu.memory
.write_byte(cpu.get(a.r1) + u32::from(a.immediate), bytes[0]);
.write_byte(cpu.get(a.r2) + u32::from(a.immediate), bytes[0]);
cpu.memory
.write_byte(cpu.get(a.r1) + u32::from(a.immediate) + 1, bytes[1]);
.write_byte(cpu.get(a.r2) + u32::from(a.immediate) + 1, bytes[1]);
}
// Stores a word from SrcReg in memory address (base + offset) The effective
// address must be 4-byte-aligned.
Self::StoreWord(a) => {
cpu.memory
.write_word(cpu.get(a.r1) + u32::from(a.immediate), cpu.get(a.r2));
.write_word(cpu.get(a.r2) + u32::from(a.immediate), cpu.get(a.r1));
}
// Loads a 16-bit literal value into reg, setting the bottom 16 bits of the
@@ -339,6 +339,14 @@ impl Executable for Instruction {
*cpu.reg(a.dr) = sub(cpu.get(a.sr1), cpu.get(a.sr2));
}
Self::AddImmediate(a) => {
*cpu.reg(a.r2) = add(cpu.get(a.r1), u32::from(a.immediate));
}
Self::SubImmediate(a) => {
*cpu.reg(a.r2) = sub(cpu.get(a.r1), u32::from(a.immediate));
}
// Performs bitwise AND on Src1 and Src2 storing the result in a.dr
Self::And(a) => *cpu.reg(a.dr) = and(cpu.get(a.sr1), cpu.get(a.sr2)),
@@ -384,7 +392,13 @@ impl Executable for Instruction {
cpu.halted = true;
}
_ => todo!(),
Self::Segment(_) => {}
Self::Data(_) => {}
_ => {
println!("unimplemented instruction: {}", self);
todo!()
}
}
}
}
+80
View File
@@ -0,0 +1,80 @@
use crate::emulator::{
system::model::State,
ui::interface::{Category, Component},
};
use eframe::egui;
use egui::{Color32, FontId, Margin, RichText, Vec2};
const VGA_WIDTH: usize = 80;
const VGA_HEIGHT: usize = 25;
pub struct Display {
visible: bool,
}
impl Display {
pub fn new() -> Self {
Self { visible: false }
}
}
impl Component for Display {
fn name(&self) -> &'static str {
"Display"
}
fn category(&self) -> Category {
Category::IO
}
fn visible(&mut self) -> &mut bool {
&mut self.visible
}
fn render(&mut self, state: &mut State, ui: &mut egui::Ui, ctx: &egui::Context) {
let display: Vec<u8> = state.display_view.clone();
let font_id = FontId::monospace(12.0);
let char_width = ui.fonts(|f| f.glyph_width(&font_id, 'W'));
let line_height = ui.fonts(|f| f.row_height(&font_id));
let display_size = Vec2::new(
char_width * VGA_WIDTH as f32,
line_height * VGA_HEIGHT as f32,
);
let (rect, _response) = ui.allocate_exact_size(display_size, egui::Sense::all());
// Fill background
// ui.painter().rect_filled(rect, 0.0, Color32::BLACK);
// Draw text
for y in 0..VGA_HEIGHT {
let mut row_text = String::with_capacity(VGA_WIDTH);
for x in 0..VGA_WIDTH {
let index = y * VGA_WIDTH + x;
if index < display.len() {
let byte = display[index];
let ch = if byte >= 32 && byte <= 126 {
byte as char
} else {
' '
};
row_text.push(ch);
} else {
row_text.push(' ');
}
}
let text_pos = rect.min + Vec2::new(0.0, y as f32 * line_height);
ui.painter().text(
text_pos,
egui::Align2::LEFT_TOP,
row_text,
font_id.clone(),
Color32::WHITE,
);
}
}
}
+84 -42
View File
@@ -12,8 +12,10 @@ use crate::emulator::{
};
pub struct Editor {
path: PathBuf,
path: Option<PathBuf>,
text: String,
buffer: String,
unsaved: bool,
output: Vec<u8>,
sender: Sender<Command>,
cursor_col: usize,
@@ -38,6 +40,10 @@ impl Component for Editor {
}
fn render(&mut self, state: &mut State, ui: &mut Ui, ctx: &Context) {
if self.buffer != self.text {
self.unsaved = true;
}
ui.vertical(|ui| {
// Top bar
@@ -62,7 +68,7 @@ impl Component for Editor {
},
);
ui.label(format!("Ln {}, Col {}", self.cursor_line, self.cursor_col));
self.render_bottom_bar(state, ui, ctx);
});
}
}
@@ -71,9 +77,11 @@ impl Editor {
#[must_use]
pub fn new(sender: Sender<Command>) -> Self {
Self {
path: PathBuf::new(),
path: None,
text: String::new(),
buffer: String::new(),
output: Vec::new(),
unsaved: true,
sender,
cursor_col: 1,
cursor_line: 1,
@@ -85,25 +93,31 @@ impl Editor {
}
fn filename(&self) -> &str {
self.path
.file_name()
.unwrap_or_else(|| OsStr::new("Unnamed!"))
.to_str()
.map_or_else(
|| unreachable!("File name should be valid UTF-8."),
|ext| ext,
)
if let Some(path) = &self.path {
return path
.file_name()
.unwrap_or_else(|| OsStr::new("Unnamed!"))
.to_str()
.map_or_else(
|| unreachable!("File name should be valid UTF-8."),
|ext| ext,
);
}
"Unnamed!"
}
fn extension(&self) -> &str {
self.path
.extension()
.unwrap_or_else(|| OsStr::new("Unknown!"))
.to_str()
.map_or_else(
|| unreachable!("File name should be valid UTF-8."),
|ext| ext,
)
if let Some(path) = &self.path {
return path
.extension()
.map_or_else(|| OsStr::new("Unknown!"), |ext| ext)
.to_str()
.map_or_else(
|| unreachable!("File name should be valid UTF-8."),
|ext| ext,
);
}
"Unknown!"
}
fn save(&mut self) {
@@ -113,6 +127,17 @@ impl Editor {
)
});
if let Some(path) = &self.path {
if let Err(why) = std::fs::write(path, &self.text) {
self.error = Some(format!("Failed to save file: {why}"));
return;
}
self.buffer = self.text.clone();
self.unsaved = false;
return;
}
if let Some(path) = FileDialog::new()
.add_filter("damn simple files", &["dsa", "dsb", "dsc", "dsd"])
.add_filter("all", &["*"])
@@ -122,7 +147,9 @@ impl Editor {
if let Err(why) = std::fs::write(&path, &self.text) {
self.error = Some(format!("Failed to save file: {why}"));
} else {
self.path = path;
self.path = Some(path);
self.buffer = self.text.clone();
self.unsaved = false;
}
}
}
@@ -141,8 +168,10 @@ impl Editor {
.pick_file()
{
if let Ok(contents) = std::fs::read_to_string(&path) {
self.path = path;
self.text = contents;
self.path = Some(path);
self.text = contents.clone();
self.buffer = contents;
self.unsaved = false;
}
}
}
@@ -151,7 +180,7 @@ impl Editor {
// Output area with synchronized scrolling
egui::ScrollArea::vertical()
.id_salt("output_scroll")
.max_width(350.0)
.max_width(400.0)
.show(ui, |ui| {
if self.output.is_empty() {
ui.label(
@@ -178,7 +207,7 @@ impl Editor {
bytes[i] = byte;
}
}
let value = u32::from_le_bytes(bytes);
let value = u32::from_be_bytes(bytes);
// Address column
ui.with_layout(
@@ -245,8 +274,9 @@ impl Editor {
.with_fontsize(12.0)
.with_rows(0)
.with_theme(ColorTheme::default())
.with_syntax(Syntax::dsa())
.with_numlines(true)
.desired_width(available_width - 450.0);
.desired_width(available_width - 500.0);
let mut editor = ed.clone();
@@ -257,17 +287,27 @@ impl Editor {
editor.show(ui, &mut self.text);
}
fn render_toolbar(&mut self, _state: &mut State, ui: &mut Ui, _ctx: &Context) {
fn render_bottom_bar(&mut self, _state: &mut State, ui: &mut Ui, _ctx: &Context) {
ui.horizontal(|ui| {
ui.label(format!("File type: {}", self.extension()));
ui.label(format!("Filename: {}", self.filename()));
// 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 render_toolbar(&mut self, _state: &mut State, ui: &mut Ui, _ctx: &Context) {
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();
@@ -291,20 +331,22 @@ impl Editor {
// builds the current file
if ui.button("Build").clicked() {
// TODO: uncomment this once assembler works!!!
// let instructions = assembler::assemble(&self.text);
// self.output = instructions
// .iter()
// .flat_map(|i| i.encode().to_le_bytes().to_vec())
// .collect();
if !self.unsaved {
if let Some(path) = &self.path {
let instructions = match assembler::assemble(path) {
Ok(instructions) => instructions,
Err(error) => {
self.error = Some(error.to_string());
return;
}
};
self.output = vec![
0x2e, 0xe0, 0x00, 0x00, 0x2e, 0xe1, 0x00, 0x01, 0x2e, 0xe2, 0x00,
0x06, 0x2e, 0xf6, 0x00, 0x00, 0x64, 0x01, 0x18, 0x00, 0x04, 0x37,
0x00, 0x00, 0x04, 0x77, 0x08, 0x00, 0x58, 0x57, 0x10, 0x00, 0x50,
0x56, 0xb8, 0x00, 0x42, 0xf7, 0x00, 0x04, 0x04, 0x37, 0x80, 0x00,
0x92, 0xf7, 0xb8, 0x00,
];
self.output = instructions
.iter()
.flat_map(|i| i.encode().to_be_bytes().to_vec())
.collect();
}
}
}
// Loads the generated binary into the assembler at the provided offset
+1 -2
View File
@@ -131,8 +131,7 @@ impl Component for MemoryInspector {
.fold(0u32, |acc, &byte| acc << 8 | u32::from(byte));
ui.monospace(format!("{combined}"));
ui.monospace(format!("{:?}", Instruction::decode(combined)));
ui.monospace("TODO! instruction");
ui.monospace(format!("{}", Instruction::decode(combined).unwrap_or(Instruction::Nop)));
ui.end_row();
}
+1
View File
@@ -1,4 +1,5 @@
pub mod control_unit;
pub mod display;
pub mod editor;
pub mod interface;
pub mod memory_inspector;
+6 -2
View File
@@ -18,8 +18,9 @@ use dsa_rs::emulator::{
processor::Processor,
},
ui::{
control_unit::ControlPanel, editor::Editor, interface::EmulatorUI,
memory_inspector::MemoryInspector, stack_inspector::StackInspector,
control_unit::ControlPanel, display::Display, editor::Editor,
interface::EmulatorUI, memory_inspector::MemoryInspector,
stack_inspector::StackInspector,
},
};
@@ -97,5 +98,8 @@ fn setup_ui(cmd_sender: Sender<Command>, state_reciever: Receiver<State>) -> Emu
let editor = Editor::new(cmd_sender.clone());
ui.add_component(Box::new(editor));
let display = Display::new();
ui.add_component(Box::new(display));
ui
}
+14 -21
View File
@@ -1,34 +1,27 @@
// include print "../resources/dsa/print.dsa"
// Fibonacci sequence calculator in DSA assembly
// Calculates the first 8 Fibonacci numbers: 0, 1, 1, 2, 3, 5, 8, 13
dw fib_count: 6 // How many more numbers to calculate after F(0) and F(1)
db fib_count: 10 // How many more numbers to calculate after F(0) and F(1)
init:
// Initialize the first two Fibonacci numbers
lli rg0, 0 // F(0) = 0
lli rg1, 1 // F(1) = 1
lli 0, rg0 // F(0) = 0
lli 1, rg1 // F(1) = 1
ldb fib_count, rg2
loop:
add rg0, rg1, acc // rg4 = rg0 + rg1 (new Fibonacci number)
push rg0
// Load loop counter
ldw fib_count, zero, rg2 // Load number of iterations remaining
fibonacci_loop:
// Calculate next Fibonacci number: F(n) = F(n-1) + F(n-2)
add rg0, rg1, rg4 // rg4 = rg0 + rg1 (new Fibonacci number)
// Shift the sequence forward
mov rg1, rg0 // rg0 = previous rg1 (F(n-2) = F(n-1))
mov rg4, rg1 // rg1 = rg4 (F(n-1) = F(n))
mov acc, rg1 // rg1 = rg4 (F(n-1) = F(n))
// Decrement loop counter
dec rg2 // rg2 = rg2 - 1
// Check if we should continue looping
cmp rg2, zero // Compare counter with 0
jgt fibonacci_loop // Jump back if counter > 0
jgt loop // Jump back if counter > 0
finish:
mov rg1, acc // Final Fibonacci number is in acc
push rg0,
push rg1,
// Final Fibonacci number is in rg1
hlt
jmp print::run
// jmp print::run
+16 -19
View File
@@ -1,28 +1,25 @@
dw stack: 0x10000
dw screen: 0x20000
db string: "Dominos sucks!"
db string2: 0, 1, 2, 3, 4, 5, 6
db length: 14
// PRINT LIBRARY.
// don't run this as a program as it won't do anything useful.
init:
ldw stack, bpr
mov bpr, spr
dw display: 0x20000
start:
ldb length, rg0
lwi rg1, string
lwi rg2, display
pop ret // return address
pop rg0 // length
pop rg1 // string
ldw display, rg2
loop:
// read from string and write to display
ldb rg1, rg3, rg4
stb rg3, rg1, rg4
ldw rg1, acc
stw acc, rg2
// increment the offset & decrement the loop counter
inc rg4
dec rg0
// if loop counter <= 0 return.
cmp rg0, zero,
jgt loop
iadd rg1, 4
iadd rg2, 4
cmp rg0, zero
jge loop
end:
jmp ret
+36
View File
@@ -0,0 +1,36 @@
dw string: "this is some random string idk"
dw len: 8
start:
// we use lwi in this case because
// in a generic case 'string' could be a 32 bit addr
lwi string, rg1
push rg1
lwi len, rg1
push rg1
push pcx
jmp start
hlt
dw display: 0x20000
print:
pop ret // return address
pop rg0 // length
pop rg1 // string
ldw display, rg2
loop:
ldw rg1, acc
stw acc, rg2
dec rg0
iadd rg1, 4
iadd rg2, 4
cmp rg0, zero
jge loop
end:
jmp 4, ret
Binary file not shown.