From 53ed41c0770940d6ba1cbf0f80953a6966b8a201 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Sun, 15 Jun 2025 02:34:23 +0100 Subject: [PATCH] CPU can now decode instructions, just waiting on the assembler --- src/common/instructions.rs | 9 +- src/emulator/mod.rs | 4 +- src/emulator/system/emulator.rs | 24 +- src/emulator/system/mod.rs | 4 +- src/emulator/system/model.rs | 59 +++-- src/emulator/system/processor.rs | 337 +++++++++++++++++++--------- src/emulator/ui/control_unit.rs | 109 +++++---- src/emulator/ui/interface.rs | 21 +- src/emulator/ui/memory_inspector.rs | 147 ++++++++++++ src/emulator/ui/mod.rs | 4 +- src/emulator/ui/stack_inspector.rs | 65 ++++++ src/main.rs | 29 ++- 12 files changed, 588 insertions(+), 224 deletions(-) create mode 100644 src/emulator/ui/memory_inspector.rs create mode 100644 src/emulator/ui/stack_inspector.rs diff --git a/src/common/instructions.rs b/src/common/instructions.rs index dc33935..4f94276 100644 --- a/src/common/instructions.rs +++ b/src/common/instructions.rs @@ -1,6 +1,7 @@ type Offset = u16; type Immediate = u16; +#[derive(Copy, Clone, Debug, PartialEq)] pub enum Interrupt { Software(u8), } @@ -23,6 +24,7 @@ impl From for Interrupt { } } +#[derive(Copy, Clone, Debug, PartialEq)] pub enum Register { // general purpose registers Rg0, @@ -129,6 +131,7 @@ impl std::fmt::Display for Register { } } +#[derive(Debug, Clone)] pub enum Instruction { // No-op Nop, @@ -142,7 +145,6 @@ pub enum Instruction { LoadHalfword(Register, Offset, Register), LoadHalfwordSigned(Register, Offset, Register), LoadWord(Register, Offset, Register), - LoadWordSigned(Register, Offset, Register), StoreByte(Register, Offset, Register), StoreHalfword(Register, Offset, Register), @@ -168,8 +170,8 @@ pub enum Instruction { Sub(Register, Register, Register), Increment(Register), Decrement(Register), - ShiftLeft(Register, Register, Register), - ShiftRight(Register, Register, Register), + ShiftLeft(Register, Register, Immediate), + ShiftRight(Register, Register, Immediate), // Logical And(Register, Register, Register), @@ -208,7 +210,6 @@ impl std::fmt::Display for Instruction { Instruction::LoadHalfword(a, b, c) => write!(f, "LDH {}, {}, {}", a, b, c), Instruction::LoadHalfwordSigned(a, b, c) => write!(f, "LDHS {}, {}, {}", a, b, c), Instruction::LoadWord(a, b, c) => write!(f, "LDW {}, {}, {}", a, b, c), - Instruction::LoadWordSigned(a, b, c) => write!(f, "LDWS {}, {}, {}", a, b, c), Instruction::StoreByte(a, b, c) => write!(f, "STB {}, {}, {}", a, b, c), Instruction::StoreHalfword(a, b, c) => write!(f, "STH {}, {}, {}", a, b, c), diff --git a/src/emulator/mod.rs b/src/emulator/mod.rs index 645c991..20a6533 100644 --- a/src/emulator/mod.rs +++ b/src/emulator/mod.rs @@ -1,2 +1,2 @@ -pub mod ui; -pub mod system; \ No newline at end of file +pub mod system; +pub mod ui; \ No newline at end of file diff --git a/src/emulator/system/emulator.rs b/src/emulator/system/emulator.rs index ebbe10d..838ad35 100644 --- a/src/emulator/system/emulator.rs +++ b/src/emulator/system/emulator.rs @@ -1,6 +1,19 @@ -use std::{sync::{mpsc::{self, Receiver, Sender}, Arc, Mutex}, thread, time::Duration}; +use std::{ + sync::{ + Arc, Mutex, + mpsc::{self, Receiver, Sender}, + }, + thread, + time::Duration, +}; -use crate::{common::instructions::{Instruction, Register}, emulator::system::{model::{Command, Running, State}, processor::Processor}}; +use crate::{ + common::instructions::{Instruction, Register}, + emulator::system::{ + model::{Command, Running, State}, + processor::Processor, + }, +}; pub fn run_emulator( cmd_rx: Receiver, @@ -9,9 +22,10 @@ pub fn run_emulator( ) { let mut running = Running::Paused; let mut addr = 0u32; + let mut size = 256; // Send initial state - let memory_view = cpu.lock().unwrap().memory.read_range(addr, 256); + let memory_view = cpu.lock().unwrap().memory.read_range(addr, size); let initial_state = state(&cpu, &running, 0, memory_view); let _ = state_tx.send(initial_state); @@ -73,7 +87,7 @@ pub fn run_emulator( } } - let memory_view = cpu.lock().unwrap().memory.read_range(addr, 256); + let memory_view = cpu.lock().unwrap().memory.read_range(addr, size); let state = state(&cpu, &running, instruction_count, memory_view); let _ = state_tx.send(state); @@ -98,7 +112,7 @@ pub fn run_emulator( } if update { - let memory_view = cpu.lock().unwrap().memory.read_range(addr, 256); + let memory_view = cpu.lock().unwrap().memory.read_range(addr, size); let state = state(&cpu, &running, instruction_count, memory_view); let _ = state_tx.send(state); } diff --git a/src/emulator/system/mod.rs b/src/emulator/system/mod.rs index 35941a2..e483168 100644 --- a/src/emulator/system/mod.rs +++ b/src/emulator/system/mod.rs @@ -1,4 +1,4 @@ -pub mod model; pub mod emulator; +pub mod memory; +pub mod model; pub mod processor; -pub mod memory; \ No newline at end of file diff --git a/src/emulator/system/model.rs b/src/emulator/system/model.rs index a20ad99..f1023dd 100644 --- a/src/emulator/system/model.rs +++ b/src/emulator/system/model.rs @@ -1,6 +1,5 @@ use crate::common::instructions::{Address, Interrupt, Register}; - #[derive(PartialEq, Debug, Clone, Copy)] pub enum Running { Running, @@ -59,33 +58,33 @@ pub struct RegFile { impl RegFile { pub fn all(&self) -> Vec<(&str, u32)> { vec![ - ("rg0", self.rg0), - ("rg1", self.rg1), - ("rg2", self.rg2), - ("rg3", self.rg3), - ("rg4", self.rg4), - ("rg5", self.rg5), - ("rg6", self.rg6), - ("rg7", self.rg7), - ("rg8", self.rg8), - ("rg9", self.rg9), - ("rga", self.rga), - ("rgb", self.rgb), - ("rgc", self.rgc), - ("rgd", self.rgd), - ("rge", self.rge), - ("rgf", self.rgf), - ("acc", self.acc), - ("spr", self.spr), - ("bpr", self.bpr), - ("ret", self.ret), - ("idr", self.idr), - ("mmr", self.mmr), - ("mar", self.mar), - ("mdr", self.mdr), - ("sts", self.sts), - ("cir", self.cir), - ("pcx", self.pcx), + ("Rg0", self.rg0), + ("Rg1", self.rg1), + ("Rg2", self.rg2), + ("Rg3", self.rg3), + ("Rg4", self.rg4), + ("Rg5", self.rg5), + ("Rg6", self.rg6), + ("Rg7", self.rg7), + ("Rg8", self.rg8), + ("Rg9", self.rg9), + ("Rga", self.rga), + ("Rgb", self.rgb), + ("Rgc", self.rgc), + ("Rgd", self.rgd), + ("Rge", self.rge), + ("Rgf", self.rgf), + ("Acc", self.acc), + ("Spr", self.spr), + ("Bpr", self.bpr), + ("Ret", self.ret), + ("Idr", self.idr), + ("Mmr", self.mmr), + ("Mar", self.mar), + ("Mdr", self.mdr), + ("Sts", self.sts), + ("Cir", self.cir), + ("Pcx", self.pcx), ] } @@ -119,7 +118,6 @@ impl RegFile { self.pcx = 0; } - pub fn reg(&mut self, reg: Register) -> &mut u32 { match reg { Register::Rg0 => &mut self.rg0, @@ -188,7 +186,6 @@ impl RegFile { } } - pub struct State { pub reg_file: RegFile, pub running: Running, @@ -213,4 +210,4 @@ impl Default for State { error: None, } } -} \ No newline at end of file +} diff --git a/src/emulator/system/processor.rs b/src/emulator/system/processor.rs index b7d1d42..77fc1aa 100644 --- a/src/emulator/system/processor.rs +++ b/src/emulator/system/processor.rs @@ -1,10 +1,17 @@ -use std::{cmp::{max, min}, sync::Arc}; +use std::{ + cmp::{max, min}, + sync::Arc, +}; -use crate::{common::instructions::{Address, Instruction, Register}, emulator::system::{memory::MemoryUnit, model::RegFile}}; +use crate::{ + common::instructions::{Address, Instruction, Interrupt, Register}, + emulator::system::{memory::MemoryUnit, model::RegFile}, +}; pub struct Processor { pub memory: Box, pub registers: RegFile, + pub halted: bool, // pub io_devices: Vec>, } @@ -15,6 +22,7 @@ impl Processor { // io_devices, memory, registers: RegFile::default(), + halted: false, } } @@ -26,6 +34,7 @@ impl Processor { } pub fn cycle(&mut self) -> Instruction { + self.halted = false; // get value from PCX let addr = self.fetch(); // increment PCX @@ -39,7 +48,8 @@ impl Processor { *self.reg(Register::Mar) = val as u32; // decode and execute the instruction let instruction = Instruction::decode(val); - instruction.execute(self); + + instruction.clone().execute(self); instruction } @@ -97,11 +107,11 @@ impl Processor { *self.reg(Register::Pcx) += 4; } - fn jump(&mut self, addr: &Address) { - *self.reg(Register::Pcx) = self.get(Register::from(*addr as u8)); + fn jump(&mut self, reg: Register, offset: u16) { + *self.reg(Register::Pcx) = self.get(reg) + offset as u32; } - fn begin_interrupt(&mut self, _code: u8) { + fn begin_interrupt(&mut self, int: Interrupt) { // first we get the address of the interrupt descriptor table. todo!(); } @@ -110,13 +120,13 @@ impl Processor { todo!(); } - pub fn get_stack(&mut self, n: u32) -> Vec { + pub fn get_stack(&mut self, n: u32) -> Vec { let addr = self.get(Register::Spr); - let size = n*4; + let size = n * 4; // returns the stack self.memory.read_range( - max(addr, 0), // ensures that we cannot read from a negative address - min(size, addr), // ensures we don't read above the top of the stack + max(addr, 0), // ensures that we cannot read from a negative address + min(size, addr), // ensures we don't read above the top of the stack ) } } @@ -135,102 +145,209 @@ enum Flag { } trait Executable { - fn execute(&self, cpu: &mut Processor); + fn execute(self, cpu: &mut Processor); } impl Executable for Instruction { - fn execute(&self, cpu: &mut Processor) { + fn execute(self, cpu: &mut Processor) { match self { - // Instruction::Nop => (), - // Instruction::Mov(reg0, reg1) => *cpu.reg(reg1) = cpu.get(reg0), + // No operation - a blank line. + Self::Nop => {} - // Instruction::Ldb(addr) => { - // cpu.mdr = cpu.memory.read_word(match addr { - // Address::Literal(addr) => *addr, - // Address::Register(reg) => cpu.get(reg), - // _ => panic!("invalid address"), - // }) - // } - // Instruction::Stb(addr) => cpu.memory.write_word( - // match addr { - // Address::Literal(addr) => *addr, - // Address::Register(reg) => cpu.get(reg), - // _ => panic!("invalid address"), - // }, - // cpu.get(&Reg::MDR), - // ), + // Copies from SrcReg to DestReg. + Self::Mov(src, dest) => { + *cpu.reg(dest) = cpu.get(src); + } - // Instruction::Add(reg0, reg1, reg2) => { - // *cpu.reg(reg2) = add(cpu.get(reg0), cpu.get(reg1)) - // } - // Instruction::Sub(reg0, reg1, reg2) => { - // *cpu.reg(reg2) = sub(cpu.get(reg0), cpu.get(reg1)) - // } - // Instruction::Inc(reg0) => *cpu.reg(reg0) = inc(cpu.get(reg0)), - // Instruction::Dec(reg0) => *cpu.reg(reg0) = dec(cpu.get(reg0)), - // Instruction::Shl(reg0) => *cpu.reg(reg0) = shl(cpu.get(reg0)), - // Instruction::Shr(reg0) => *cpu.reg(reg0) = shr(cpu.get(reg0)), - // Instruction::And(reg0, reg1, reg2) => { - // *cpu.reg(reg2) = and(cpu.get(reg0), cpu.get(reg1)) - // } - // Instruction::Orr(reg0, reg1, reg2) => *cpu.reg(reg2) = or(cpu.get(reg0), cpu.get(reg1)), - // Instruction::Not(reg0, reg1) => *cpu.reg(reg1) = not(cpu.get(reg0)), - // Instruction::Xor(reg0, reg1, reg2) => { - // *cpu.reg(reg2) = xor(cpu.get(reg0), cpu.get(reg1)) - // } - // Instruction::Nnd(reg0, reg1, reg2) => { - // *cpu.reg(reg2) = nand(cpu.get(reg0), cpu.get(reg1)) - // } - // Instruction::Nor(reg0, reg1, reg2) => { - // *cpu.reg(reg2) = nor(cpu.get(reg0), cpu.get(reg1)) - // } - // Instruction::Xnr(reg0, reg1, reg2) => { - // *cpu.reg(reg2) = xnor(cpu.get(reg0), cpu.get(reg1)) - // } - // Instruction::Cmp(reg0, reg1) => cpu.cmp(cpu.get(reg0), cpu.get(reg1)), + // Copies from SrcReg to DestReg, sign extending the value to take up a full word. + Self::MovSigned(src, dest) => { + *cpu.reg(dest) = sign_extend(cpu.get(src)); + } - // Instruction::Jmp(addr) => cpu.jump(addr), - // Instruction::Jeq(addr) if cpu.get_flag(Flag::Equal) => cpu.jump(addr), - // Instruction::Jne(addr) if !cpu.get_flag(Flag::Equal) => cpu.jump(addr), - // Instruction::Jgt(addr) if cpu.get_flag(Flag::GreaterThan) => cpu.jump(addr), - // Instruction::Jlt(addr) if cpu.get_flag(Flag::LessThan) => cpu.jump(addr), - // Instruction::Jle(addr) if cpu.get_flag(Flag::LessThan) || cpu.get_flag(Flag::Equal) => { - // cpu.jump(addr) - // } - // Instruction::Jge(addr) - // if cpu.get_flag(Flag::GreaterThan) || cpu.get_flag(Flag::Equal) => - // { - // cpu.jump(addr) - // } - // Instruction::Hlt => (), - // Instruction::Int(interrupt_code) => { - // cpu.begin_interrupt(*interrupt_code); - // } - // Instruction::Cll(addr) => { - // cpu.ret = cpu.pcx; - // cpu.pcx = addr.force_resolve(); - // } - // Instruction::Ret => { - // cpu.pcx = cpu.ret; - // } - // Instruction::Psh(reg) => { - // cpu.push(cpu.get(reg)); - // } - // Instruction::Pop(reg) => { - // let val = cpu.pop(); - // *cpu.reg(reg) = val; - // } - // Instruction::Lui(addr) => { - // *cpu.reg(&Reg::MDR) = match addr { - // Address::Literal(addr) => *addr, - // Address::Register(reg) => cpu.get(reg), - // _ => panic!("invalid address"), - // }; - // } - // Instruction::Irt => { - // cpu.end_interrupt(); - // } - _ => {} + // Loads a byte from memory address (base + offset) into DestReg. The effective address must be byte-aligned. + Self::LoadByte(base, offset, dest) => { + *cpu.reg(dest) = cpu.memory.read_byte(cpu.get(base) + offset as u32) as u32; + } + + // Loads a sign-extended byte from memory address (base + offset) into DestReg. The effective address must be byte-aligned. + Self::LoadByteSigned(base, offset, dest) => { + *cpu.reg(dest) = + sign_extend(cpu.memory.read_byte(cpu.get(base) + offset as u32) as u32); + } + + // Loads a half-word from memory address (base + offset) into DestReg. The effective address must be 2-byte-aligned. + Self::LoadHalfword(base, offset, dest) => { + // we read an entire word, then right shift so we only get the first half of the word + *cpu.reg(dest) = cpu.memory.read_word(cpu.get(base) + offset as u32) >> 16; + } + + // Loads a sign-extended half-word from memory address (base + offset) into DestReg. The effective address must be 2-byte-aligned. + Self::LoadHalfwordSigned(base, offset, dest) => { + *cpu.reg(dest) = + sign_extend(cpu.memory.read_word(cpu.get(base) + offset as u32) >> 16); + } + + // Loads a word from memory address (base + offset) into DestReg. The effective address must be 4-byte-aligned. + Self::LoadWord(base, offset, dest) => { + *cpu.reg(dest) = cpu.memory.read_word(cpu.get(base) + offset as u32); + } + + // Stores a byte from SrcReg in memory address (base + offset) The effective address must be byte-aligned. + Self::StoreByte(src, offset, base) => { + cpu.memory + .write_byte(cpu.get(base) + offset as u32, cpu.get(src) as u8); + } + + // Stores a half-word from SrcReg in memory address (base + offset) The effective address must be 2-byte-aligned. + Self::StoreHalfword(src, offset, base) => { + // split the value into bytes and then write two bytes + let bytes = (cpu.get(src) as u16).to_be_bytes(); + cpu.memory + .write_byte(cpu.get(base) + offset as u32, bytes[0]); + cpu.memory + .write_byte(cpu.get(base) + offset as u32 + 1, bytes[1]); + } + + // Stores a word from SrcReg in memory address (base + offset) The effective address must be 4-byte-aligned. + Self::StoreWord(src, offset, base) => { + cpu.memory + .write_word(cpu.get(base) + offset as u32, cpu.get(src)); + } + + // Loads a 16-bit literal value into reg, setting the bottom 16 bits of the word. To populate the upper 16 bits, see LUI. + Self::LoadLowerImmediate(reg, imm) => { + *cpu.reg(reg) = (cpu.get(reg) & 0xFFFF_0000) | imm as u32; + } + + // Loads a 16-bit literal value into reg, setting the top 16 bits of the word. To populate the lower 16 bits, see LLI. + Self::LoadUpperImmediate(reg, imm) => { + *cpu.reg(reg) = (cpu.get(reg) & 0x0000_FFFF) | (imm as u32) << 16; + } + + // Unconditionally jumps to the calculated address or direct address + Self::Jump(reg, offset) => cpu.jump(reg, offset), + + // Jumps to the calculated address or direct address if equal flag set. + Self::JumpEq(reg, offset) => { + if cpu.get_flag(Flag::Equal) { + cpu.jump(reg, offset) + } + } + + // Jumps to the calculated address or direct address if equal flag not set. + Self::JumpNeq(reg, offset) => { + if !cpu.get_flag(Flag::Equal) { + cpu.jump(reg, offset) + } + } + + // Jumps to the calculated address or direct address if greater than flag set. + Self::JumpGt(reg, offset) => { + if cpu.get_flag(Flag::GreaterThan) { + cpu.jump(reg, offset) + } + } + + // Jumps to the calculated address or direct address if greater than flag or equal flag set. + Self::JumpGe(reg, offset) => { + if cpu.get_flag(Flag::GreaterThan) || cpu.get_flag(Flag::Equal) { + cpu.jump(reg, offset) + } + } + + // Jumps to the calculated address or direct address if less than flag set. + Self::JumpLt(reg, offset) => { + if cpu.get_flag(Flag::LessThan) { + cpu.jump(reg, offset) + } + } + + // Jumps to the calculated address or direct address if less than flag or equal flag set. + Self::JumpLe(reg, offset) => { + if cpu.get_flag(Flag::LessThan) || cpu.get_flag(Flag::Equal) { + cpu.jump(reg, offset) + } + } + + // Increments the value in the given register + Self::Increment(reg) => *cpu.reg(reg) = inc(cpu.get(reg)), + + // Decrements the value in the given register + Self::Decrement(reg) => *cpu.reg(reg) = dec(cpu.get(reg)), + + // Left shifts the value in Reg by the given amount (either a register, or a literal value) + Self::ShiftLeft(src, reg, imm) => { + let regval = cpu.get(reg); + *cpu.reg(reg) = shl( + cpu.get(src), + if regval != 0 { regval as u8 } else { imm as u8 }, + ); + } + + // Right shifts the value in Reg by the given amount (either a register, or a literal value). + Self::ShiftRight(src, reg, imm) => { + let regval = cpu.get(reg); + *cpu.reg(reg) = shr( + cpu.get(src), + if regval != 0 { regval as u8 } else { imm as u8 }, + ); + } + + // Adds the value of Src2 to Src1 and writes the result to Dest + Self::Add(srcx, srcy, dest) => { + *cpu.reg(dest) = add(cpu.get(srcx), cpu.get(srcy)); + } + + // Subtracts the value of Src2 from Src1 and writes the result to Dest + Self::Sub(srcx, srcy, dest) => { + *cpu.reg(dest) = sub(cpu.get(srcx), cpu.get(srcy)); + } + + // Performs bitwise AND on Src1 and Src2 storing the result in Dest + Self::And(srcx, srcy, dest) => *cpu.reg(dest) = and(cpu.get(srcx), cpu.get(srcy)), + + // Performs bitwise OR on Src1 and Src2 storing the result in Dest + Self::Or(srcx, srcy, dest) => *cpu.reg(dest) = or(cpu.get(srcx), cpu.get(srcy)), + + // Performs bitwise NOT on Src storing the result in Dest + Self::Not(src, dest) => *cpu.reg(dest) = not(cpu.get(src)), + + // Performs bitwise XOR on Src1 and Src2 storing the result in Dest + Self::Xor(srcx, srcy, dest) => *cpu.reg(dest) = xor(cpu.get(srcx), cpu.get(srcy)), + + // Performs bitwise NAND on Src1 and Src2 storing the result in Dest + Self::Nand(srcx, srcy, dest) => *cpu.reg(dest) = nand(cpu.get(srcx), cpu.get(srcy)), + + // Performs bitwise NOR on Src1 and Src2 storing the result in Dest + Self::Nor(srcx, srcy, dest) => *cpu.reg(dest) = nor(cpu.get(srcx), cpu.get(srcy)), + + // Performs bitwise XNOR on Src1 and Src2 storing the result in Dest + Self::Xnor(srcx, srcy, dest) => *cpu.reg(dest) = xnor(cpu.get(srcx), cpu.get(srcy)), + + // Compares the value of Reg1 to the value in Reg2. The results of the comparisons are set in the Status register. + Self::Compare(srcx, srcy) => { + cpu.cmp(cpu.get(srcx), cpu.get(srcy)); + } + + /* + Initiates an interrupt with the given 8 bit interrupt code. + Triggering an interrupt invokes the following behaviour + - The return address is saved to the RET register + - The stack base ptr is set to the kernel stack. + */ + Self::Interrupt(interrupt_code) => { + cpu.begin_interrupt(interrupt_code); + } + + // Returns from an interrupt, + Self::IntReturn => { + cpu.end_interrupt(); + } + + // Halts the processor. + Self::Halt => { + cpu.halted = true; + } } } } @@ -256,12 +373,12 @@ fn dec(a: u32) -> u32 { a - 1 } -fn shl(a: u32) -> u32 { - a << 1 +fn shl(a: u32, amount: u8) -> u32 { + a << amount } -fn shr(a: u32) -> u32 { - a >> 1 +fn shr(a: u32, amount: u8) -> u32 { + a >> amount } fn or(a: u32, b: u32) -> u32 { @@ -287,3 +404,17 @@ fn nor(a: u32, b: u32) -> u32 { fn xnor(a: u32, b: u32) -> u32 { !(a ^ b) } + +fn sign_extend(val: u32) -> u32 { + let (mask, sign_bit): (u32, u8) = match val { + 0..=0xFF => (0xFFFFFF00, 7), + 0..=0xFFFF => (0xFFFF0000, 15), + _ => (0x00000000, 31), + }; + + if val & (1 << sign_bit) != 0 { + val | mask + } else { + val + } +} diff --git a/src/emulator/ui/control_unit.rs b/src/emulator/ui/control_unit.rs index 47a1b85..5d9fadc 100644 --- a/src/emulator/ui/control_unit.rs +++ b/src/emulator/ui/control_unit.rs @@ -1,6 +1,12 @@ use std::sync::mpsc::Sender; -use crate::{common::instructions::Register, emulator::{system::model::{Command, Running, State}, ui::interface::Component}}; +use crate::{ + common::instructions::Register, + emulator::{ + system::model::{Command, Running, State}, + ui::interface::Component, + }, +}; pub struct ControlPanel { visible: bool, @@ -25,66 +31,63 @@ impl Component for ControlPanel { &mut self.visible } - fn name(&self) -> &'static str { "Control Panel" } - fn render(&mut self, state: &mut State, ui: &mut egui::Ui, ctx: &egui::Context) { - ui.horizontal(|ui| { - // Pause / Run - if ui - .button(if state.running == Running::Running { - "Pause" - } else { - "Run" - }) - .clicked() - { - if state.running == Running::Running { - self.sender.send(Command::Stop).unwrap_or_else(|_| state.error = Some("Failed to send command".to_string())); - } else { - self.sender.send(Command::Start).unwrap_or_else(|_| state.error = Some("Failed to send command".to_string())); + ui.horizontal(|ui| { + // Pause / Run + if ui + .button(if state.running == Running::Running { + "Pause" + } else { + "Run" + }) + .clicked() + { + if state.running == Running::Running { + self.sender.send(Command::Stop).unwrap_or_else(|_| { + state.error = Some("Failed to send command".to_string()) + }); + } else { + self.sender.send(Command::Start).unwrap_or_else(|_| { + state.error = Some("Failed to send command".to_string()) + }); + } } - } - // Step - if ui.button("Step").clicked() { - self.sender.send(Command::Step).unwrap_or_else(|_| state.error = Some("Failed to send command".to_string())); - } - - ui.separator(); - - // Status info - ui.label(format!( - "Status: {}", - match state.running { - Running::Running => "Running", - Running::Paused => "Paused", - Running::Halted => "Halted", + // Step + if ui.button("Step").clicked() { + self.sender + .send(Command::Step) + .unwrap_or_else(|_| state.error = Some("Failed to send command".to_string())); } - )); - ui.label(format!( - "Instructions: {}", - state.instructions - )); - ui.label(format!("PC: 0x{:08X}", state.reg_file.get(Register::Pcx))); - ui.label(format!( - "Last Instruction: {:?}", - "TODO: DECODE INSTRUCTION" // TODO: decode instruction - // Instruction::decode(state.current_state.cir) - )); - }); - render_register_table(state, ui, ctx); + ui.separator(); + + // Status info + ui.label(format!( + "Status: {}", + match state.running { + Running::Running => "Running", + Running::Paused => "Paused", + Running::Halted => "Halted", + } + )); + ui.label(format!("Instructions: {}", state.instructions)); + ui.label(format!("PC: 0x{:08X}", state.reg_file.get(Register::Pcx))); + ui.label(format!( + "Last Instruction: {:?}", + "TODO: DECODE INSTRUCTION" // TODO: decode instruction + // Instruction::decode(state.current_state.cir) + )); + }); + + render_register_table(state, ui, ctx); } - - } - - fn render_register_table(state: &mut State, ui: &mut egui::Ui, _ctx: &egui::Context) { // Left column - Registers ui.vertical(|ui| { @@ -111,12 +114,8 @@ fn render_register_table(state: &mut State, ui: &mut egui::Ui, _ctx: &egui::Cont // iterate over state.reg_file.iter() in chunks of 4 registers for chunk in state.reg_file.all().chunks(4) { for reg in chunk { - ui.label(format!("{:?}", reg.0)); - ui.label(format!( - "0x{:08X} ({})", - reg.1, - reg.1, - )); + ui.label(format!("{}", reg.0)); + ui.label(format!("0x{:08X} ({})", reg.1, reg.1,)); } ui.end_row(); } diff --git a/src/emulator/ui/interface.rs b/src/emulator/ui/interface.rs index ad00f5b..ed83cdb 100644 --- a/src/emulator/ui/interface.rs +++ b/src/emulator/ui/interface.rs @@ -1,4 +1,7 @@ -use crate::{common::instructions::{Address, Interrupt}, emulator::system::model::{Command, Running, State}}; +use crate::{ + common::instructions::{Address, Interrupt}, + emulator::system::model::{Command, Running, State}, +}; use std::sync::mpsc::{Receiver, Sender}; pub trait Component { @@ -56,10 +59,18 @@ impl EmulatorUI { pub fn add_component(&mut self, component: Box) { self.components.push(component); } + + fn update_state(&mut self) { + while let Ok(state) = self.receiver.try_recv() { + self.state = state; + } + } } impl eframe::App for EmulatorUI { fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { + self.update_state(); + if let Running::Running = self.state.running { ctx.request_repaint(); } @@ -89,10 +100,10 @@ impl eframe::App for EmulatorUI { let mut visible = *c.visible(); if visible == true { egui::Window::new(c.name()) - .open(&mut visible) - .show(ctx, |ui| { - c.render(&mut self.state, ui, ctx); - }); + .open(&mut visible) + .show(ctx, |ui| { + c.render(&mut self.state, ui, ctx); + }); } *c.visible() = visible; } diff --git a/src/emulator/ui/memory_inspector.rs b/src/emulator/ui/memory_inspector.rs new file mode 100644 index 0000000..85dab98 --- /dev/null +++ b/src/emulator/ui/memory_inspector.rs @@ -0,0 +1,147 @@ +use std::{num::ParseIntError, sync::mpsc::Sender}; + +use crate::emulator::{system::model::{Command, State}, ui::interface::Component}; + +pub struct MemoryInspector { + view_size: u32, + view_addr: u32, + visible: bool, + addr_input: String, + sender: Sender, +} + +impl MemoryInspector { + pub fn new(sender: Sender) -> Self { + Self { + view_size: 256, + view_addr: 0, + visible: false, + addr_input: String::new(), + sender, + } + } +} + +impl Component for MemoryInspector { + fn category(&self) -> super::interface::Category { + super::interface::Category::Memory + } + + fn name(&self) -> &'static str { + "Memory Inspector" + } + + fn visible(&mut self) -> &mut bool { + &mut self.visible + } + + fn render(&mut self, state: &mut State, 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) { + self.view_addr = new; + self.sender.send(Command::Read(new, self.view_size)).unwrap(); + } else { + state.error = Some("Invalid address".to_string()); + } + } + + ui.label("(hex or decimal)"); + }); + + // Show input error if any + if let Some(error) = &state.error { + ui.colored_label(egui::Color32::RED, format!("Error: {}", error)); + } + + 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!("{:X}", i)); + } + + ui.strong("Decimal"); + ui.strong("Instruction"); + + ui.end_row(); + + // Memory data (8 bytes per row) + for (row, chunk) in state.memory_view.chunks(4).enumerate() { + let row_address = self.view_addr + (row * 4) as u32; + ui.monospace(format!("0x{:08X} ({})", row_address, row_address)); + for &byte in chunk { + ui.monospace(format!("{:02X}", byte)); + } + + // Fill remaining columns if last row is incomplete + for _ in chunk.len()..4 { + ui.label(""); + } + + // combine all 4 bytes in the chunk into a u32 + let combined = + chunk.iter().fold(0u32, |acc, &byte| acc << 8 | byte as u32); + + ui.monospace(format!("{}", combined)); + // ui.monospace(format!("{:?}", Instruction::decode(combined))); + ui.monospace("TODO! instruction"); + + ui.end_row(); + } + }); + }); + }); + } +} + +fn parse_address(address: &str) -> Result { + if address.starts_with("0x") { + return u32::from_str_radix(&address[2..], 16); + } + + if address.starts_with("0b") { + return u32::from_str_radix(&address[2..], 2); + } + + if address.starts_with("0o") { + return u32::from_str_radix(&address[1..], 8); + } + + u32::from_str_radix(address, 10) +} \ No newline at end of file diff --git a/src/emulator/ui/mod.rs b/src/emulator/ui/mod.rs index a43f998..b5aa439 100644 --- a/src/emulator/ui/mod.rs +++ b/src/emulator/ui/mod.rs @@ -1,3 +1,5 @@ +pub mod control_unit; pub mod interface; pub mod menu; -pub mod control_unit; \ No newline at end of file +pub mod memory_inspector; +pub mod stack_inspector; \ No newline at end of file diff --git a/src/emulator/ui/stack_inspector.rs b/src/emulator/ui/stack_inspector.rs new file mode 100644 index 0000000..559fd22 --- /dev/null +++ b/src/emulator/ui/stack_inspector.rs @@ -0,0 +1,65 @@ +use crate::{common::instructions::Register, emulator::{system::model::State, ui::interface::{Component, EmulatorUI}}}; + +pub struct StackInspector { + visible: bool +} + +impl StackInspector { + pub fn new() -> Self { + Self { + visible: false + } + } +} + +impl Component for StackInspector { + fn visible(&mut self) -> &mut bool { + &mut self.visible + } + + fn name(&self) -> &'static str { + "Stack Inspector" + } + + fn category(&self) -> super::interface::Category { + super::interface::Category::Memory + } + + fn render(&mut self, state: &mut State, ui: &mut egui::Ui, ctx: &egui::Context) { + ui.vertical(|ui| { + ui.heading("Stack Inspector"); + egui::ScrollArea::vertical() + .id_salt("stack_inspector_scroll") + .show(ui, |ui| { + egui::Grid::new("stack_grid") + .num_columns(2) + .spacing([40.0, 4.0]) + .striped(true) + .show(ui, |ui| { + ui.label("Address"); + ui.label("Value"); + ui.end_row(); + + for (i, value) in state.stack_view.iter().take(32).enumerate() { + ui.label(format!( + "{} [{}]", + i, + state.reg_file.get(Register::Spr) - i as u32 * 4 + )); + ui.label(format!("0x{:08X} ({})", value, value)); + ui.end_row(); + } + + if state.stack_view.is_empty() { + ui.label("(empty)"); + ui.label("-"); + ui.end_row(); + } + }); + }); + }); + } +} + + + diff --git a/src/main.rs b/src/main.rs index 66344f4..a9a5efb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,24 +1,23 @@ -use std::{sync::{Arc, Mutex}, thread}; +use std::{ + sync::{Arc, Mutex}, + thread, +}; -use dsa_rs::emulator::{system::{emulator::run_emulator, memory::MainStore, processor::Processor}, ui::{control_unit::ControlPanel, interface::EmulatorUI}}; -use egui::Memory; +use dsa_rs::emulator::{ + system::{emulator::run_emulator, memory::MainStore, processor::Processor}, + ui::{control_unit::ControlPanel, interface::EmulatorUI, memory_inspector::MemoryInspector, stack_inspector::StackInspector}, +}; fn main() -> Result<(), eframe::Error> { - // Initialize Channels let (cmd_sender, cmd_receiver) = std::sync::mpsc::channel(); let (state_sender, state_receiver) = std::sync::mpsc::channel(); let mainstore = MainStore::new(); let processor = Processor::new(Box::new(mainstore)); - thread::spawn(move || { - run_emulator( - cmd_receiver, - state_sender, - Arc::new(Mutex::new(processor)), - ); + run_emulator(cmd_receiver, state_sender, Arc::new(Mutex::new(processor))); }); // Create UI @@ -28,13 +27,11 @@ fn main() -> Result<(), eframe::Error> { let control_unit = ControlPanel::new(cmd_sender.clone()); ui.add_component(Box::new(control_unit)); + let mem_inspector = MemoryInspector::new(cmd_sender.clone()); + ui.add_component(Box::new(mem_inspector)); - - - - - - + let stack_inspector = StackInspector::new(); + ui.add_component(Box::new(stack_inspector)); // Run UI let options = eframe::NativeOptions {