refactor 2 electric boogaloo

This commit is contained in:
2025-06-15 21:40:43 +01:00
parent 277f210b3e
commit 2b8281157e
30 changed files with 125 additions and 71 deletions
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "emulator"
version = "0.1.0"
edition = "2024"
[lib]
name = "dsa_rs"
path = "src/lib.rs"
[dependencies]
common = { path = "../common" }
eframe = "0.31.1"
egui = "0.31.1"
rfd = "0.15.3"
+2
View File
@@ -0,0 +1,2 @@
pub mod system;
pub mod ui;
+157
View File
@@ -0,0 +1,157 @@
use std::{
sync::{
Arc, Mutex, MutexGuard,
mpsc::{self, Receiver, Sender},
},
thread,
time::Duration,
};
use crate::emulator::system::{
model::{Command, Running, State},
processor::Processor,
};
use common::instructions::{Instruction, Register};
pub fn run_emulator(
cmd_rx: &Receiver<Command>,
state_tx: &Sender<State>,
mut processor: Processor,
) {
println!("starting");
let mut running = Running::Paused;
let mut addr = 0u32;
let size = 256;
let memory_view = processor.memory.read_range(addr, size);
let initial_state = state(&mut processor, running, 0, memory_view);
let _ = state_tx.send(initial_state);
let mut instruction_count = 0;
loop {
println!("looping");
let cmd = if running == Running::Running {
match cmd_rx.try_recv() {
Ok(cmd) => Some(cmd),
Err(mpsc::TryRecvError::Empty) => None,
Err(mpsc::TryRecvError::Disconnected) => break,
}
} else {
match cmd_rx.recv() {
Ok(cmd) => Some(cmd),
Err(_) => break,
}
};
if let Some(cmd) = cmd {
println!("Received command: {:?}", cmd);
match cmd {
Command::Start => {
running = Running::Running;
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;
// Execute one cycle.
match processor.cycle() {
Ok(_) => {}
Err(why) => {
let pcx = processor.get(Register::Pcx);
eprintln!("Could not decode instruction at {pcx:x}. Reason: {why}");
continue;
}
}
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")
}
}
let memory_view = processor.memory.read_range(addr, size);
let state = state(&mut processor, running, instruction_count, memory_view);
let _ = state_tx.send(state);
}
if running == Running::Running {
let mut update = false;
// Execute one cycle.
let instruction = match processor.cycle() {
Ok(instruction) => instruction,
Err(why) => {
let pcx = processor.get(Register::Pcx);
eprintln!("Could not decode instruction at {pcx:x}. Reason: {why}");
continue;
}
};
// let instruction = match Instruction::decode(cpu_lock.get(Register::Cir)) {};
if matches!(instruction, Instruction::Halt) {
running = Running::Halted;
update = true;
}
instruction_count += 1;
// Send state updates every 100 instructions
if instruction_count % 100 == 0 {
update = true;
}
if update {
let memory_view = processor.memory.read_range(addr, size);
let state = state(&mut processor, running, instruction_count, memory_view);
let _ = state_tx.send(state);
}
} else {
thread::sleep(Duration::from_millis(1));
}
}
}
fn state(
cpu_lock: &mut Processor,
running: Running,
instruction_count: usize,
memory_view: Vec<u8>,
) -> State {
State {
// TODO: Replace with actual register access from your CPU.
reg_file: cpu_lock.registers,
running,
instructions: instruction_count,
stack_view: cpu_lock.get_stack(32),
memory_view,
display_view: cpu_lock.display(),
error: None,
}
}
+114
View File
@@ -0,0 +1,114 @@
use std::collections::HashMap;
pub trait MemoryUnit: Send + Sync {
fn reset(&mut self);
fn read_byte(&mut self, addr: u32) -> u8;
fn write_byte(&mut self, addr: u32, value: u8);
fn read_word(&mut self, addr: u32) -> u32;
fn write_word(&mut self, addr: u32, value: u32);
fn read_range(&mut self, addr: u32, size: u32) -> Vec<u8>;
fn write_range(&mut self, addr: u32, value: Vec<u8>);
}
pub struct MainStore {
pub data: HashMap<u32, Block>,
}
pub struct Block {
data: [u8; 256],
}
impl Default for MainStore {
fn default() -> Self {
Self::new()
}
}
impl MainStore {
#[must_use]
pub fn new() -> Self {
Self {
data: HashMap::new(),
}
}
const fn segment_addr(addr: u32) -> (u32, u8) {
(addr / 256, (addr % 256) as u8)
}
fn mut_block(&mut self, addr: u32) -> &mut Block {
self.data
.entry(addr)
.or_insert_with(|| Block { data: [0; 256] });
self.data.get_mut(&addr).map_or_else(
|| panic!("Could not fetch block with address {addr:x?}"),
|block| block,
)
}
fn block(&mut self, addr: u32) -> &Block {
self.data
.entry(addr)
.or_insert_with(|| Block { data: [0; 256] });
self.data.get(&addr).map_or_else(
|| panic!("Could not fetch block with address {addr:x?}"),
|block| block,
)
}
}
impl MemoryUnit for MainStore {
fn reset(&mut self) {
self.data.clear();
}
fn read_byte(&mut self, addr: u32) -> u8 {
let (block_addr, offset) = Self::segment_addr(addr);
let block = self.block(block_addr);
block.data[offset as usize]
}
fn read_word(&mut self, addr: u32) -> u32 {
let (block_addr, offset) = Self::segment_addr(addr);
let block = self.mut_block(block_addr);
let mut bytes = [0; 4];
bytes[0] = block.data[offset as usize];
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_be_bytes(bytes)
}
fn read_range(&mut self, addr: u32, size: u32) -> Vec<u8> {
let mut data = Vec::with_capacity(size as usize);
for i in 0..size {
data.push(self.read_byte(addr + i));
}
data
}
fn write_byte(&mut self, addr: u32, value: u8) {
let (block_addr, offset) = Self::segment_addr(addr);
let block = self.mut_block(block_addr);
block.data[offset as usize] = value;
}
fn write_word(&mut self, addr: u32, value: u32) {
let (block_addr, offset) = Self::segment_addr(addr);
let block = self.mut_block(block_addr);
block.data[offset as usize] = (value >> 24) as u8;
block.data[(offset + 1) as usize] = (value >> 16) as u8;
block.data[(offset + 2) as usize] = (value >> 8) as u8;
block.data[(offset + 3) as usize] = value as u8;
}
fn write_range(&mut self, addr: u32, value: Vec<u8>) {
for byte in value {
let (block_addr, offset) = Self::segment_addr(addr);
let block = self.mut_block(block_addr);
block.data[offset as usize] = byte;
}
}
}
+4
View File
@@ -0,0 +1,4 @@
pub mod emulator;
pub mod memory;
pub mod model;
pub mod processor;
+223
View File
@@ -0,0 +1,223 @@
use common::prelude::*;
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub enum Running {
Running,
Paused,
Halted,
}
pub trait IODevice: Send + Sync {
fn read_byte(&mut self, addr: u32) -> u8;
fn write_byte(&mut self, addr: u32, value: u8);
fn read_range(&mut self, addr: u32, size: u32) -> Vec<u8>;
fn write_range(&mut self, addr: u32, value: Vec<u8>);
}
#[derive(PartialEq, Eq, Debug, Clone)]
pub enum Command {
Start,
Stop,
Step,
Reset,
Interrupt(Interrupt),
// Performs direct read/write operations on the emulator's memory.
Read(Address, u32),
Write(Address, Vec<u8>),
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub struct RegFile {
// General Purpose Registers
rg0: u32,
rg1: u32,
rg2: u32,
rg3: u32,
rg4: u32,
rg5: u32,
rg6: u32,
rg7: u32,
rg8: u32,
rg9: u32,
rga: u32,
rgb: u32,
rgc: u32,
rgd: u32,
rge: u32,
rgf: u32,
// Special Purpose Registers
acc: u32,
spr: u32,
bpr: u32,
ret: u32,
idr: u32,
mmr: u32,
// System Registers
mar: u32,
mdr: u32,
sts: u32,
cir: u32,
pcx: u32,
}
impl RegFile {
#[must_use]
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),
]
}
pub const fn reset(&mut self) {
self.rg0 = 0;
self.rg1 = 0;
self.rg2 = 0;
self.rg3 = 0;
self.rg4 = 0;
self.rg5 = 0;
self.rg6 = 0;
self.rg7 = 0;
self.rg8 = 0;
self.rg9 = 0;
self.rga = 0;
self.rgb = 0;
self.rgc = 0;
self.rgd = 0;
self.rge = 0;
self.rgf = 0;
self.acc = 0;
self.spr = 0;
self.bpr = 0;
self.ret = 0;
self.idr = 0;
self.mmr = 0;
self.mar = 0;
self.mdr = 0;
self.sts = 0;
self.cir = 0;
self.pcx = 0;
}
pub fn reg(&mut self, reg: Register) -> &mut u32 {
match reg {
Register::Rg0 => &mut self.rg0,
Register::Rg1 => &mut self.rg1,
Register::Rg2 => &mut self.rg2,
Register::Rg3 => &mut self.rg3,
Register::Rg4 => &mut self.rg4,
Register::Rg5 => &mut self.rg5,
Register::Rg6 => &mut self.rg6,
Register::Rg7 => &mut self.rg7,
Register::Rg8 => &mut self.rg8,
Register::Rg9 => &mut self.rg9,
Register::Rga => &mut self.rga,
Register::Rgb => &mut self.rgb,
Register::Rgc => &mut self.rgc,
Register::Rgd => &mut self.rgd,
Register::Rge => &mut self.rge,
Register::Rgf => &mut self.rgf,
Register::Acc => &mut self.acc,
Register::Spr => &mut self.spr,
Register::Bpr => &mut self.bpr,
Register::Ret => &mut self.ret,
Register::Idr => &mut self.idr,
Register::Mmr => &mut self.mmr,
Register::Mar => &mut self.mar,
Register::Mdr => &mut self.mdr,
Register::Sts => &mut self.sts,
Register::Cir => &mut self.cir,
Register::Pcx => &mut self.pcx,
_ => panic!("Invalid register."),
}
}
#[must_use]
pub fn get(&self, reg: Register) -> u32 {
match reg {
Register::Rg0 => self.rg0,
Register::Rg1 => self.rg1,
Register::Rg2 => self.rg2,
Register::Rg3 => self.rg3,
Register::Rg4 => self.rg4,
Register::Rg5 => self.rg5,
Register::Rg6 => self.rg6,
Register::Rg7 => self.rg7,
Register::Rg8 => self.rg8,
Register::Rg9 => self.rg9,
Register::Rga => self.rga,
Register::Rgb => self.rgb,
Register::Rgc => self.rgc,
Register::Rgd => self.rgd,
Register::Rge => self.rge,
Register::Rgf => self.rgf,
Register::Acc => self.acc,
Register::Spr => self.spr,
Register::Bpr => self.bpr,
Register::Ret => self.ret,
Register::Idr => self.idr,
Register::Mmr => self.mmr,
Register::Mar => self.mar,
Register::Mdr => self.mdr,
Register::Sts => self.sts,
Register::Cir => self.cir,
Register::Pcx => self.pcx,
Register::Zero => 0,
_ => panic!("Invalid register."),
}
}
}
pub struct State {
pub reg_file: RegFile,
pub running: Running,
pub instructions: usize,
// Memory access views
pub stack_view: Vec<u8>,
pub memory_view: Vec<u8>,
pub display_view: Vec<u8>,
pub error: Option<String>,
}
impl Default for State {
fn default() -> Self {
Self {
reg_file: RegFile::default(),
running: Running::Paused,
instructions: 0,
stack_view: vec![],
memory_view: vec![],
display_view: vec![],
error: None,
}
}
}
@@ -0,0 +1,433 @@
use std::{
cmp::{max, min},
sync::Arc,
};
use crate::emulator::system::{
memory::MemoryUnit,
model::{IODevice, RegFile},
};
use common::instructions::{Instruction, Interrupt, Register, errors::InstructionDecodeError};
pub struct Processor {
pub memory: Box<dyn MemoryUnit>,
pub registers: RegFile,
pub halted: bool,
pub io_devices: Vec<Arc<dyn IODevice>>,
}
#[allow(clippy::needless_pass_by_ref_mut)]
impl Processor {
#[must_use]
pub fn new(memory: Box<dyn MemoryUnit>, io_devices: Vec<Arc<dyn IODevice>>) -> Self {
Self {
memory,
registers: RegFile::default(),
halted: false,
io_devices,
}
}
pub fn reset(&mut self) {
// set all registers to zero
// run memory.reset()
self.registers.reset();
self.memory.reset();
}
pub fn cycle(&mut self) -> Result<Instruction, InstructionDecodeError> {
self.halted = false;
// Get value from PCX.
let addr = self.fetch();
// Increment PCX.
self.advance();
// Set MAR to the previous value of PCX.
*self.reg(Register::Mar) = addr;
let val = self.memory.read_word(addr);
// Set CIR to the value of RAM[MAR].
*self.reg(Register::Mar) = val;
// Decode and execute the instruction.
let instruction = Instruction::decode(val)?;
instruction.execute(self);
Ok(instruction)
}
fn fetch(&self) -> u32 {
self.get(Register::Pcx)
}
#[must_use]
pub fn get(&self, reg: Register) -> u32 {
self.registers.get(reg)
}
pub fn reg(&mut self, reg: Register) -> &mut u32 {
self.registers.reg(reg)
}
pub fn display(&mut self) -> Vec<u8> {
self.memory.read_range(0x20000, 2000)
}
pub fn cmp(&mut self, a: u32, b: u32) {
self.set_flag(Flag::Equal, a == b);
self.set_flag(Flag::GreaterThan, a > b);
self.set_flag(Flag::LessThan, a < b);
}
// stack operations
pub fn push(&mut self, value: u32) {
let stack_ptr = self.get(Register::Spr);
*self.reg(Register::Spr) += 4;
self.memory.write_word(stack_ptr, value);
}
pub fn pop(&mut self) -> u32 {
*self.reg(Register::Spr) -= 4;
self.memory.read_word(self.get(Register::Spr))
}
// functions to set new state
fn set_flag(&mut self, flag: Flag, value: bool) {
if value {
*self.reg(Register::Sts) |= flag as u32;
} else {
*self.reg(Register::Sts) &= !(flag as u32);
}
}
fn get_flag(&self, flag: Flag) -> bool {
self.get(Register::Sts) & (flag as u32) != 0
}
fn advance(&mut self) {
// increment PCX
*self.reg(Register::Pcx) += 4;
}
fn jump(&mut self, reg: Register, offset: u16) {
*self.reg(Register::Pcx) = self.get(reg) + u32::from(offset);
}
fn begin_interrupt(&mut self, _int: Interrupt) {
// first we get the address of the interrupt descriptor table.
todo!();
}
fn end_interrupt(&mut self) {
todo!();
}
pub fn get_stack(&mut self, n: u32) -> Vec<u8> {
let addr = self.get(Register::Spr);
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
)
}
}
#[derive(Debug)]
#[expect(dead_code)]
enum Flag {
Equal = 1,
GreaterThan = 2,
LessThan = 4,
Zero = 8,
Positive = 16,
Negative = 32,
Carry = 64,
UserMode = 128,
InterruptsEnabled = 256,
}
trait Executable {
fn execute(self, cpu: &mut Processor);
}
impl Executable for Instruction {
#[allow(clippy::too_many_lines)]
fn execute(self, cpu: &mut Processor) {
match self {
// No operation - a blank line.
Self::Nop => {}
// Copies from SrcReg to a.drReg.
Self::Mov(a) => {
*cpu.reg(a.dr) = cpu.get(a.sr1);
}
// Copies from SrcReg to a.drReg, sign extending the value to take up a full word.
Self::MovSigned(a) => {
*cpu.reg(a.dr) = sign_extend(cpu.get(a.sr1));
}
// Loads a byte from memory address (base + offset) into a.drReg. The effective address must be byte-aligned.
Self::LoadByte(a) => {
*cpu.reg(a.r2) =
u32::from(cpu.memory.read_byte(cpu.get(a.r1) + u32::from(a.immediate)));
}
// Loads a sign-extended byte from memory address (base + offset) into a.drReg. The effective address must be byte-aligned.
Self::LoadByteSigned(a) => {
*cpu.reg(a.r2) = sign_extend(u32::from(
cpu.memory.read_byte(cpu.get(a.r1) + u32::from(a.immediate)),
));
}
// Loads a half-word from memory address (base + offset) into a.drReg. The effective address must be 2-byte-aligned.
Self::LoadHalfword(a) => {
// we read an entire word, then right shift so we only get the first half of the word
*cpu.reg(a.r2) = cpu.memory.read_word(cpu.get(a.r1) + u32::from(a.immediate)) >> 16;
}
// Loads a sign-extended half-word from memory address (base + offset) into a.drReg. The effective address must be 2-byte-aligned.
Self::LoadHalfwordSigned(a) => {
*cpu.reg(a.r2) =
sign_extend(cpu.memory.read_word(cpu.get(a.r1) + u32::from(a.immediate)) >> 16);
}
// Loads a word from memory address (base + offset) into a.drReg. The effective address must be 4-byte-aligned.
Self::LoadWord(a) => {
*cpu.reg(a.r2) = cpu.memory.read_word(cpu.get(a.r1) + u32::from(a.immediate));
}
// Stores a byte from SrcReg in memory address (base + offset) The effective 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);
}
// Stores a half-word from SrcReg in memory address (base + offset) The effective address must be 2-byte-aligned.
Self::StoreHalfword(a) => {
// split the value into bytes and then write two bytes
let bytes = (cpu.get(a.r1) as u16).to_be_bytes();
cpu.memory
.write_byte(cpu.get(a.r1) + u32::from(a.immediate), bytes[0]);
cpu.memory
.write_byte(cpu.get(a.r1) + 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));
}
// 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(a) => {
*cpu.reg(a.r1) = u32::from(a.immediate);
}
// 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(a) => {
*cpu.reg(a.r1) = (cpu.get(a.r1) & 0x0000_FFFF) | u32::from(a.immediate) << 16;
}
// Unconditionally jumps to the calculated address or direct address
Self::Jump(a) => cpu.jump(a.r1, a.immediate),
// Jumps to the calculated address or direct address if equal flag set.
Self::JumpEq(a) => {
if cpu.get_flag(Flag::Equal) {
cpu.jump(a.r1, a.immediate);
}
}
// Jumps to the calculated address or direct address if equal flag not set.
Self::JumpNeq(a) => {
if !cpu.get_flag(Flag::Equal) {
cpu.jump(a.r1, a.immediate);
}
}
// Jumps to the calculated address or direct address if greater than flag set.
Self::JumpGt(a) => {
if cpu.get_flag(Flag::GreaterThan) {
cpu.jump(a.r1, a.immediate);
}
}
// Jumps to the calculated address or direct address if greater than flag or equal flag set.
Self::JumpGe(a) => {
if cpu.get_flag(Flag::GreaterThan) || cpu.get_flag(Flag::Equal) {
cpu.jump(a.r1, a.immediate);
}
}
// Jumps to the calculated address or direct address if less than flag set.
Self::JumpLt(a) => {
if cpu.get_flag(Flag::LessThan) {
cpu.jump(a.r1, a.immediate);
}
}
// Jumps to the calculated address or direct address if less than flag or equal flag set.
Self::JumpLe(a) => {
if cpu.get_flag(Flag::LessThan) || cpu.get_flag(Flag::Equal) {
cpu.jump(a.r1, a.immediate);
}
}
// Increments the value in the given register
Self::Increment(a) => *cpu.reg(a.sr1) = inc(cpu.get(a.sr1)),
// Decrements the value in the given register
Self::Decrement(a) => *cpu.reg(a.sr1) = dec(cpu.get(a.sr1)),
// Left shifts the value in Reg by the given amount (either a register, or a literal value)
Self::ShiftLeft(a) => {
let regval = cpu.get(a.sr2);
let val = cpu.get(a.sr1);
*cpu.reg(a.sr1) = shl(val, if regval != 0 { regval as u8 } else { a.shamt });
}
// Right shifts the value in Reg by the given amount (either a register, or a literal value).
Self::ShiftRight(a) => {
let regval = cpu.get(a.sr2);
let val = cpu.get(a.sr1);
*cpu.reg(a.sr1) = shr(val, if regval != 0 { regval as u8 } else { a.shamt });
}
// Adds the value of Src2 to Src1 and writes the result to a.dr
Self::Add(a) => {
*cpu.reg(a.dr) = add(cpu.get(a.sr1), cpu.get(a.sr2));
}
// Subtracts the value of Src2 from Src1 and writes the result to a.dr
Self::Sub(a) => {
*cpu.reg(a.dr) = sub(cpu.get(a.sr1), cpu.get(a.sr2));
}
// 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)),
// Performs bitwise OR on Src1 and Src2 storing the result in a.dr
Self::Or(a) => *cpu.reg(a.dr) = or(cpu.get(a.sr1), cpu.get(a.sr2)),
// Performs bitwise NOT on Src storing the result in a.dr
Self::Not(a) => *cpu.reg(a.dr) = not(cpu.get(a.sr1)),
// Performs bitwise XOR on Src1 and Src2 storing the result in a.dr
Self::Xor(a) => *cpu.reg(a.dr) = xor(cpu.get(a.sr1), cpu.get(a.sr2)),
// Performs bitwise NAND on Src1 and Src2 storing the result in a.dr
Self::Nand(a) => *cpu.reg(a.dr) = nand(cpu.get(a.sr1), cpu.get(a.sr2)),
// Performs bitwise NOR on Src1 and Src2 storing the result in a.dr
Self::Nor(a) => *cpu.reg(a.dr) = nor(cpu.get(a.sr1), cpu.get(a.sr2)),
// Performs bitwise XNOR on Src1 and Src2 storing the result in a.dr
Self::Xnor(a) => *cpu.reg(a.dr) = xnor(cpu.get(a.sr1), cpu.get(a.sr2)),
// Compares the value of Reg1 to the value in Reg2. The results of the comparisons are set in the Status register.
Self::Compare(a) => {
cpu.cmp(cpu.get(a.sr1), cpu.get(a.sr2));
}
// 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;
}
_ => todo!(),
}
}
}
// mathematical and logical functions & other operations
const fn add(a: u32, b: u32) -> u32 {
a + b
}
const fn sub(a: u32, b: u32) -> u32 {
a - b
}
const fn and(a: u32, b: u32) -> u32 {
a & b
}
const fn inc(a: u32) -> u32 {
a + 1
}
const fn dec(a: u32) -> u32 {
a - 1
}
const fn shl(a: u32, amount: u8) -> u32 {
a << amount
}
const fn shr(a: u32, amount: u8) -> u32 {
a >> amount
}
const fn or(a: u32, b: u32) -> u32 {
a | b
}
const fn not(a: u32) -> u32 {
!a
}
const fn xor(a: u32, b: u32) -> u32 {
a ^ b
}
const fn nand(a: u32, b: u32) -> u32 {
!(a & b)
}
const fn nor(a: u32, b: u32) -> u32 {
!(a | b)
}
const fn xnor(a: u32, b: u32) -> u32 {
!(a ^ b)
}
const fn sign_extend(val: u32) -> u32 {
let (mask, sign_bit): (u32, u8) = match val {
0..=0xFF => (0xFFFF_FF00, 7),
// I presume this was the intended behaviour?
0x100..=0xFFFF => (0xFFFF_0000, 15),
_ => (0x0000_0000, 31),
};
if val & (1 << sign_bit) != 0 {
val | mask
} else {
val
}
}
#[cfg(test)]
mod tests;
@@ -0,0 +1,498 @@
use super::*;
use crate::emulator::system::memory::*;
use common::instructions::{
args::{ITypeArgs, RTypeArgs},
*,
};
fn create_test_processor() -> Processor {
let memory = Box::new(MainStore::new());
Processor::new(memory, Vec::new())
}
#[test]
fn test_nop_instruction() {
let mut cpu = create_test_processor();
let initial_state = cpu.registers;
Instruction::Nop.execute(&mut cpu);
assert_eq!(
cpu.registers.get(Register::Rg0),
initial_state.get(Register::Rg0)
);
assert_eq!(
cpu.registers.get(Register::Acc),
initial_state.get(Register::Acc)
);
}
#[test]
fn test_mov_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 0x1234_5678;
let mov_instr = Instruction::Mov(RTypeArgs::new(
Some(Register::Rg1),
None,
Some(Register::Rg2),
None,
));
mov_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg2), 0x1234_5678);
}
#[test]
fn test_mov_signed_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 0x0000_00FF;
let mov_signed_instr = Instruction::MovSigned(RTypeArgs::new(
Some(Register::Rg1),
None,
Some(Register::Rg2),
None,
));
mov_signed_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg2), 0xFFFF_FFFF);
}
#[test]
fn test_load_byte_instruction() {
let mut cpu = create_test_processor();
let addr = 0x100;
cpu.memory.write_byte(addr, 0xAB);
*cpu.reg(Register::Rg1) = addr - 4;
let load_byte_instr =
Instruction::LoadByte(ITypeArgs::new(4, Some(Register::Rg1), Some(Register::Rg2)));
load_byte_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg2), 0x0000_00AB);
}
#[test]
fn test_load_byte_signed_instruction() {
let mut cpu = create_test_processor();
let addr = 0x100;
cpu.memory.write_byte(addr, 0xFF);
*cpu.reg(Register::Rg1) = addr;
let load_byte_signed_instr =
Instruction::LoadByteSigned(ITypeArgs::new(0, Some(Register::Rg1), Some(Register::Rg2)));
load_byte_signed_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg2), 0xFFFF_FFFF);
}
#[test]
fn test_load_halfword_instruction() {
let mut cpu = create_test_processor();
let addr = 0x100;
cpu.memory.write_word(addr, 0x1234_5678);
*cpu.reg(Register::Rg1) = addr;
let load_halfword_instr =
Instruction::LoadHalfword(ITypeArgs::new(0, Some(Register::Rg1), Some(Register::Rg2)));
load_halfword_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg2), 0x0000_1234);
}
#[test]
fn test_load_word_instruction() {
let mut cpu = create_test_processor();
let addr = 0x100;
cpu.memory.write_word(addr, 0x1234_5678);
*cpu.reg(Register::Rg1) = addr;
let load_word_instr =
Instruction::LoadWord(ITypeArgs::new(0, Some(Register::Rg1), Some(Register::Rg2)));
load_word_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg2), 0x1234_5678);
}
#[test]
fn test_store_byte_instruction() {
let mut cpu = create_test_processor();
let addr = 0x100;
*cpu.reg(Register::Rg1) = addr;
*cpu.reg(Register::Rg2) = 0xAB;
let store_byte_instr =
Instruction::StoreByte(ITypeArgs::new(0, Some(Register::Rg1), Some(Register::Rg2)));
store_byte_instr.execute(&mut cpu);
assert_eq!(cpu.memory.read_byte(addr), 0xAB);
}
#[test]
fn test_store_word_instruction() {
let mut cpu = create_test_processor();
let addr = 0x100;
*cpu.reg(Register::Rg1) = addr;
*cpu.reg(Register::Rg2) = 0x1234_5678;
let store_word_instr =
Instruction::StoreWord(ITypeArgs::new(0, Some(Register::Rg1), Some(Register::Rg2)));
store_word_instr.execute(&mut cpu);
assert_eq!(cpu.memory.read_word(addr), 0x1234_5678);
}
#[test]
fn test_add_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 15;
*cpu.reg(Register::Rg2) = 25;
let add_instr = Instruction::Add(RTypeArgs::new(
Some(Register::Rg1),
Some(Register::Rg2),
Some(Register::Rg3),
None,
));
add_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg3), 40);
}
#[test]
fn test_sub_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 50;
*cpu.reg(Register::Rg2) = 20;
let sub_instr = Instruction::Sub(RTypeArgs::new(
Some(Register::Rg1),
Some(Register::Rg2),
Some(Register::Rg3),
None,
));
sub_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg3), 30);
}
#[test]
fn test_and_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 0b1100;
*cpu.reg(Register::Rg2) = 0b1010;
let and_instr = Instruction::And(RTypeArgs::new(
Some(Register::Rg1),
Some(Register::Rg2),
Some(Register::Rg3),
None,
));
and_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg3), 0b1000);
}
#[test]
fn test_or_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 0b1100;
*cpu.reg(Register::Rg2) = 0b1010;
let or_instr = Instruction::Or(RTypeArgs::new(
Some(Register::Rg1),
Some(Register::Rg2),
Some(Register::Rg3),
None,
));
or_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg3), 0b1110);
}
#[test]
fn test_xor_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 0b1100;
*cpu.reg(Register::Rg2) = 0b1010;
let xor_instr = Instruction::Xor(RTypeArgs::new(
Some(Register::Rg1),
Some(Register::Rg2),
Some(Register::Rg3),
None,
));
xor_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg3), 0b0110);
}
#[test]
fn test_not_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 0x0F0F_0F0F;
let not_instr = Instruction::Not(RTypeArgs::new(
Some(Register::Rg1),
None,
Some(Register::Rg2),
None,
));
not_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg2), 0xF0F0_F0F0);
}
#[test]
fn test_compare_equal() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 42;
*cpu.reg(Register::Rg2) = 42;
let cmp_instr = Instruction::Compare(RTypeArgs::new(
Some(Register::Rg1),
Some(Register::Rg2),
None,
None,
));
cmp_instr.execute(&mut cpu);
assert!(cpu.get_flag(Flag::Equal));
assert!(!cpu.get_flag(Flag::GreaterThan));
assert!(!cpu.get_flag(Flag::LessThan));
}
#[test]
fn test_compare_greater_than() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 50;
*cpu.reg(Register::Rg2) = 30;
let cmp_instr = Instruction::Compare(RTypeArgs::new(
Some(Register::Rg1),
Some(Register::Rg2),
None,
None,
));
cmp_instr.execute(&mut cpu);
assert!(!cpu.get_flag(Flag::Equal));
assert!(cpu.get_flag(Flag::GreaterThan));
assert!(!cpu.get_flag(Flag::LessThan));
}
#[test]
fn test_compare_less_than() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 20;
*cpu.reg(Register::Rg2) = 30;
let cmp_instr = Instruction::Compare(RTypeArgs::new(
Some(Register::Rg1),
Some(Register::Rg2),
None,
None,
));
cmp_instr.execute(&mut cpu);
assert!(!cpu.get_flag(Flag::Equal));
assert!(!cpu.get_flag(Flag::GreaterThan));
assert!(cpu.get_flag(Flag::LessThan));
}
#[test]
fn test_increment_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 42;
let inc_instr = Instruction::Increment(RTypeArgs::new(Some(Register::Rg1), None, None, None));
inc_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg1), 43);
}
#[test]
fn test_decrement_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 42;
let dec_instr = Instruction::Decrement(RTypeArgs::new(Some(Register::Rg1), None, None, None));
dec_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg1), 41);
}
#[test]
fn test_shift_left_with_shamt() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 0b1010;
let shl_instr = Instruction::ShiftLeft(RTypeArgs::new(
Some(Register::Rg1),
Some(Register::Zero),
None,
Some(2),
));
shl_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg1), 0b10_1000);
}
#[test]
fn test_shift_right_with_shamt() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 0b10_1000;
let shr_instr = Instruction::ShiftRight(RTypeArgs::new(
Some(Register::Rg1),
Some(Register::Zero),
None,
Some(2),
));
shr_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg1), 0b1010);
}
#[test]
fn test_shift_left_with_register() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 0b1010;
*cpu.reg(Register::Rg2) = 3;
let shl_instr = Instruction::ShiftLeft(RTypeArgs::new(
Some(Register::Rg1),
Some(Register::Rg2),
None,
None,
));
shl_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg1), 0b101_0000);
}
#[test]
fn test_load_lower_immediate() {
let mut cpu = create_test_processor();
let lli_instr =
Instruction::LoadLowerImmediate(ITypeArgs::new(0x1234, Some(Register::Rg1), None));
lli_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg1), 0x0000_1234);
}
#[test]
fn test_load_upper_immediate() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 0x0000_5678;
let lui_instr =
Instruction::LoadUpperImmediate(ITypeArgs::new(0x1234, Some(Register::Rg1), None));
lui_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg1), 0x1234_5678);
}
#[test]
fn test_jump_unconditional() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 0x1000;
let initial_pc = cpu.get(Register::Pcx);
let jump_instr = Instruction::Jump(ITypeArgs::new(0x100, Some(Register::Rg1), None));
jump_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Pcx), 0x1100);
assert_ne!(cpu.get(Register::Pcx), initial_pc);
}
#[test]
fn test_jump_equal_when_flag_set() {
let mut cpu = create_test_processor();
cpu.set_flag(Flag::Equal, true);
*cpu.reg(Register::Rg1) = 0x1000;
let jump_eq_instr = Instruction::JumpEq(ITypeArgs::new(0x100, Some(Register::Rg1), None));
jump_eq_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Pcx), 0x1100);
}
#[test]
fn test_jump_equal_when_flag_not_set() {
let mut cpu = create_test_processor();
cpu.set_flag(Flag::Equal, false);
*cpu.reg(Register::Rg1) = 0x1000;
let initial_pc = cpu.get(Register::Pcx);
let jump_eq_instr = Instruction::JumpEq(ITypeArgs::new(0x100, Some(Register::Rg1), None));
jump_eq_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Pcx), initial_pc);
}
#[test]
fn test_halt_instruction() {
let mut cpu = create_test_processor();
assert!(!cpu.halted);
Instruction::Halt.execute(&mut cpu);
assert!(cpu.halted);
}
#[test]
fn test_nand_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 0b1100;
*cpu.reg(Register::Rg2) = 0b1010;
let nand_instr = Instruction::Nand(RTypeArgs::new(
Some(Register::Rg1),
Some(Register::Rg2),
Some(Register::Rg3),
None,
));
nand_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg3), !0b1000);
}
#[test]
fn test_nor_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 0b1100;
*cpu.reg(Register::Rg2) = 0b1010;
let nor_instr = Instruction::Nor(RTypeArgs::new(
Some(Register::Rg1),
Some(Register::Rg2),
Some(Register::Rg3),
None,
));
nor_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg3), !0b1110);
}
#[test]
fn test_xnor_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 0b1100;
*cpu.reg(Register::Rg2) = 0b1010;
let xnor_instr = Instruction::Xnor(RTypeArgs::new(
Some(Register::Rg1),
Some(Register::Rg2),
Some(Register::Rg3),
None,
));
xnor_instr.execute(&mut cpu);
assert_eq!(cpu.get(Register::Rg3), !0b0110);
}
+125
View File
@@ -0,0 +1,125 @@
use std::sync::mpsc::Sender;
use crate::emulator::{
system::model::{Command, Running, State},
ui::interface::Component,
};
use common::instructions::Register;
pub struct ControlPanel {
visible: bool,
sender: Sender<Command>,
}
impl ControlPanel {
#[must_use]
pub const fn new(sender: Sender<Command>) -> Self {
Self {
visible: false,
sender,
}
}
}
impl Component for ControlPanel {
fn category(&self) -> super::interface::Category {
super::interface::Category::Control
}
fn visible(&mut self) -> &mut bool {
&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());
});
}
}
// 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",
}
));
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: &State, ui: &mut egui::Ui, _ctx: &egui::Context) {
// Left column - Registers
ui.vertical(|ui| {
ui.heading("Registers");
egui::ScrollArea::vertical()
.id_salt("register_inspector_scroll")
.show(ui, |ui| {
egui::Grid::new("registers_grid")
.num_columns(8)
.spacing([40.0, 4.0])
.striped(true)
.show(ui, |ui| {
ui.label("Register");
ui.label("Value");
ui.label("Register");
ui.label("Value");
ui.label("Register");
ui.label("Value");
ui.label("Register");
ui.label("Value");
ui.end_row();
// 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(reg.0.to_string());
ui.label(format!("0x{:08X} ({})", reg.1, reg.1,));
}
ui.end_row();
}
});
})
});
}
+326
View File
@@ -0,0 +1,326 @@
use std::sync::mpsc::Sender;
use egui::{Align, Context, Layout, Ui};
use rfd::FileDialog;
use crate::emulator::{
system::model::{Command, State},
ui::interface::Component,
};
pub struct Editor {
filename: String,
text: String,
output: Vec<u8>,
sender: Sender<Command>,
cursor_col: usize,
cursor_line: usize,
visible: bool,
load_offset: u32,
offset_str: String,
error: Option<String>,
}
impl Component for Editor {
fn name(&self) -> &'static str {
"Editor"
}
fn visible(&mut self) -> &mut bool {
&mut self.visible
}
fn category(&self) -> super::interface::Category {
super::interface::Category::Programming
}
fn render(&mut self, state: &mut State, ui: &mut Ui, ctx: &Context) {
ui.vertical(|ui| {
// Top bar
self.render_toolbar(state, 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| {
self.render_editor(state, ui, ctx);
ui.separator();
self.render_output(state, ui, ctx);
},
);
ui.label(format!("Ln {}, Col {}", self.cursor_line, self.cursor_col));
});
}
}
impl Editor {
pub fn new(sender: Sender<Command>) -> Self {
Self {
filename: String::new(),
text: String::new(),
output: Vec::new(),
sender,
cursor_col: 1,
cursor_line: 1,
visible: false,
load_offset: 0,
offset_str: String::new(),
error: None,
}
}
fn render_output(&mut self, _state: &mut State, ui: &mut Ui, _ctx: &Context) {
// Output area with synchronized scrolling
egui::ScrollArea::vertical()
.id_salt("output_scroll")
.max_width(300.0)
.show(ui, |ui| {
if self.output.is_empty() {
ui.label(
egui::RichText::new("No output data")
.font(egui::FontId::monospace(12.0))
.color(egui::Color32::GRAY),
);
return;
}
egui::Grid::new("output_grid")
.num_columns(4)
.spacing([20.0, 2.0]) // Horizontal and vertical spacing
.striped(false)
.show(ui, |ui| {
// Process bytes in chunks of 4
for (line_num, chunk) in self.output.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{:04X}", address))
.font(egui::FontId::monospace(12.0)),
);
},
);
// Individual bytes column
let byte_str = chunk
.iter()
.map(|b| format!("{:02X}", b))
.collect::<Vec<_>>()
.join(" ");
ui.label(
egui::RichText::new(format!("{:<11}", byte_str))
.font(egui::FontId::monospace(12.0))
.color(egui::Color32::from_rgb(200, 200, 255)),
);
// Hex column
ui.label(
egui::RichText::new(format!("0x{:08X}", value))
.font(egui::FontId::monospace(12.0))
.color(egui::Color32::from_rgb(255, 200, 200)),
);
// Decimal column
ui.label(
egui::RichText::new(format!("{:10}", value))
.font(egui::FontId::monospace(12.0))
.color(egui::Color32::from_rgb(200, 255, 200)),
);
ui.end_row();
}
});
});
}
fn render_editor(&mut self, _state: &mut State, ui: &mut Ui, _ctx: &Context) {
let available_width = ui.available_width();
// Main editor area with synchronized scrolling
egui::ScrollArea::vertical()
.max_width(available_width - 400.0)
.id_salt("editor_scroll")
.show(ui, |ui| {
ui.horizontal(|ui| {
// Line numbers column
let line_count = self.text.lines().count();
ui.vertical(|ui| {
ui.set_width(50.0);
ui.style_mut().visuals.widgets.inactive.bg_fill =
egui::Color32::from_gray(30);
// Calculate line height to match text editor
let line_height = ui.text_style_height(&egui::TextStyle::Monospace);
for line_num in 1..=line_count {
let line_response = ui.allocate_response(
egui::vec2(50.0, line_height),
egui::Sense::hover(),
);
ui.painter().text(
line_response.rect.left_center() + egui::vec2(5.0, 0.0),
egui::Align2::LEFT_CENTER,
format!("{:3}", line_num),
egui::FontId::monospace(12.0),
ui.style().visuals.text_color(),
);
}
});
ui.separator();
// Text editor area
ui.vertical(|ui| {
let available_size = ui.available_size();
let response = ui.add_sized(
available_size,
egui::TextEdit::multiline(&mut self.text)
.font(egui::TextStyle::Monospace)
.margin(egui::vec2(5.0, 0.0))
.code_editor(),
);
// Update cursor position when text changes
if response.changed() {
// Simple but functional cursor tracking
let lines = self.text.lines().collect::<Vec<_>>();
self.cursor_line = lines.len().max(1);
// Get the length of the last line for column position
if let Some(last_line) = lines.last() {
self.cursor_col = last_line.chars().count() + 1;
} else {
self.cursor_col = 1;
}
}
});
});
});
}
fn render_toolbar(&mut self, _state: &mut State, ui: &mut Ui, _ctx: &Context) {
ui.horizontal(|ui| {
// current filename
ui.label(format!("File: {}", self.filename));
// error display
ui.label(
egui::RichText::new(self.error.clone().unwrap_or("".to_string()))
.color(egui::Color32::RED),
);
// 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
if ui.button("Open").clicked() {
if let Some(path) = FileDialog::new()
.add_filter("dsafiles", &["dsa", "dsb", "dsc", "dsd"])
.add_filter("all", &["*"])
.set_directory(std::env::current_dir().unwrap())
.pick_file()
{
if let Ok(content) = std::fs::read_to_string(&path) {
self.text = content;
self.filename = path.display().to_string();
}
}
self.output = Vec::new();
}
// Saves the current file
if ui.button("Save").clicked() {
if let Some(path) = FileDialog::new()
.add_filter("dsafiles", &["dsa", "dsb", "dsc", "dsd"])
.add_filter("all", &["*"])
.set_directory(std::env::current_dir().unwrap())
.save_file()
{
if let Err(e) = std::fs::write(&path, &self.text) {
self.error = Some(format!("Failed to save file: {}", e));
} else {
self.filename = path.display().to_string();
}
}
}
// builds the current file
if ui.button("Build").clicked() {
self.output = vec![0x00; 256];
}
// Loads the generated binary into the assembler at the provided offset
if ui.button("Load").clicked() {
if self.error.is_some() {
self.error = Some("Can't load program at invalid offset!".to_string());
}
self.sender
.send(Command::Write(self.load_offset, self.output.clone()))
.unwrap_or_else(|_| self.error = Some("Failed to send command".to_string()));
}
// 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());
}
}
// Resets the emulator and all attached devices
if ui.button("Reset Emulator").clicked() {
self.sender
.send(Command::Reset)
.unwrap_or_else(|_| self.error = Some("Failed to send command".to_string()));
}
});
}
}
fn parse_address(address: &str) -> Option<u32> {
if address.starts_with("0x") {
u32::from_str_radix(&address[2..], 16).ok()
} else if address.starts_with("0b") {
u32::from_str_radix(&address[2..], 2).ok()
} else if address.starts_with("0o") {
u32::from_str_radix(&address[2..], 8).ok()
} else {
address.parse::<u32>().ok()
}
}
+112
View File
@@ -0,0 +1,112 @@
use crate::emulator::system::model::{Command, Running, State};
use std::sync::mpsc::{Receiver, Sender};
pub trait Component {
fn render(&mut self, state: &mut State, ui: &mut egui::Ui, ctx: &egui::Context);
fn visible(&mut self) -> &mut bool;
fn name(&self) -> &'static str;
fn category(&self) -> Category;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Category {
Control,
Memory,
IO,
Programming,
}
impl Category {
#[must_use]
pub const fn as_str(&self) -> &'static str {
match self {
Self::Control => "Control Systems",
Self::Memory => "Memory Systems",
Self::IO => "I/O Systems",
Self::Programming => "Programming",
}
}
#[must_use]
pub fn list() -> Vec<Self> {
vec![Self::Control, Self::Memory, Self::IO, Self::Programming]
}
}
pub struct EmulatorUI {
pub sender: Sender<Command>,
pub receiver: Receiver<State>,
pub state: State,
pub components: Vec<Box<dyn Component>>,
}
impl EmulatorUI {
#[must_use]
pub fn new(sender: Sender<Command>, receiver: Receiver<State>) -> Self {
Self {
sender,
receiver,
state: State::default(),
components: vec![],
}
}
pub fn add_component(&mut self, component: Box<dyn Component>) {
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 self.state.running == Running::Running {
ctx.request_repaint();
}
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
ui.with_layout(
egui::Layout::top_down_justified(egui::Align::Center)
.with_main_align(egui::Align::Min),
|ui| {
ui.allocate_space(egui::vec2(0.0, 15.0));
ui.heading("DSA Simulator (Damn Simple Architecture 🔥)");
ui.allocate_space(egui::vec2(0.0, 15.0));
},
);
});
egui::CentralPanel::default().show(ctx, |ui| {
ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |_ui| {
egui::Window::new("Main Menu")
.resizable(false)
.default_width(300.0)
.show(ctx, |ui| {
super::menu::render_menu(self, ui, ctx);
});
for c in &mut self.components {
let mut visible = *c.visible();
if visible {
egui::Window::new(c.name())
.open(&mut visible)
.show(ctx, |ui| {
c.render(&mut self.state, ui, ctx);
});
}
*c.visible() = visible;
}
});
});
egui::TopBottomPanel::bottom("bottom_panel").show(ctx, |_ui| {
// interface::bottompanel::render_bottom_panel(self, ui, ctx);
});
}
}
@@ -0,0 +1,157 @@
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<Command>,
}
impl MemoryInspector {
#[must_use]
pub const fn new(sender: Sender<Command>) -> 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;
if let Err(why) = self.sender.send(Command::Read(new, self.view_size)) {
panic!(
"Error sending message across threads -- cannot be recovered: {why}"
)
}
} 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!("{i:X}"));
}
ui.strong("Decimal");
ui.strong("Instruction");
ui.end_row();
// Memory data (8 bytes per row)
for (row, chunk) in (0u32..).zip(state.memory_view.chunks(4)) {
let row_address = self.view_addr + (row * 4);
ui.monospace(format!("0x{row_address:08X} ({row_address})"));
for &byte in chunk {
ui.monospace(format!("{byte:02X}"));
}
// 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 | u32::from(byte));
ui.monospace(format!("{combined}"));
// ui.monospace(format!("{:?}", Instruction::decode(combined)));
ui.monospace("TODO! instruction");
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>()
}
+30
View File
@@ -0,0 +1,30 @@
use crate::emulator::ui::interface::{Category, EmulatorUI};
pub fn render_menu(state: &mut EmulatorUI, ui: &mut egui::Ui, _ctx: &egui::Context) {
ui.with_layout(
egui::Layout::top_down_justified(egui::Align::Center),
|ui| {
ui.set_max_width(300.0);
ui.set_min_width(300.0);
ui.spacing_mut().button_padding = egui::vec2(10.0, 5.0);
for cat in Category::list() {
ui.add_space(10.0);
ui.heading(cat.as_str());
ui.add_space(10.0);
for comp in &mut state.components {
let name = comp.name();
if comp.category() == cat {
ui.toggle_value(comp.visible(), name);
}
}
ui.add_space(10.0);
ui.separator();
}
ui.add_space(10.0);
},
);
}
+6
View File
@@ -0,0 +1,6 @@
pub mod control_unit;
pub mod editor;
pub mod interface;
pub mod memory_inspector;
pub mod menu;
pub mod stack_inspector;
@@ -0,0 +1,69 @@
use crate::emulator::{system::model::State, ui::interface::Component};
use common::instructions::Register;
pub struct StackInspector {
visible: bool,
}
impl Default for StackInspector {
fn default() -> Self {
Self::new()
}
}
impl StackInspector {
#[must_use]
pub const 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 (0u32..).zip(state.stack_view.iter().take(32)) {
ui.label(format!(
"{} [{}]",
i,
state.reg_file.get(Register::Spr) - i * 4
));
ui.label(format!("0x{value:08X} ({value})"));
ui.end_row();
}
if state.stack_view.is_empty() {
ui.label("(empty)");
ui.label("-");
ui.end_row();
}
});
});
});
}
}
+15
View File
@@ -0,0 +1,15 @@
#![deny(
clippy::unwrap_used,
clippy::nursery,
clippy::perf,
clippy::pedantic,
clippy::complexity
)]
#![allow(
clippy::cast_possible_truncation,
clippy::missing_panics_doc,
clippy::missing_errors_doc,
clippy::match_wildcard_for_single_variants
)]
pub mod emulator;
+53
View File
@@ -0,0 +1,53 @@
use std::thread;
use dsa_rs::emulator::{
system::{emulator::run_emulator, memory::MainStore, processor::Processor},
ui::{
control_unit::ControlPanel, editor::Editor, 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), vec![]);
thread::spawn(move || {
run_emulator(&cmd_receiver, &state_sender, processor);
});
// Create UI
let mut ui = EmulatorUI::new(cmd_sender.clone(), state_receiver);
// Create UI modules
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));
let editor = Editor::new(cmd_sender.clone());
ui.add_component(Box::new(editor));
// Run UI
let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default().with_inner_size([800.0, 600.0]),
..Default::default()
};
eframe::run_native(
"DSA Simulator (Damn Simple Architecture 🔥)",
options,
Box::new(move |cc| {
cc.egui_ctx.set_visuals(egui::Visuals::default());
Ok(Box::new(ui))
}),
)
}