diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..1c565eb --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "rust-analyzer.check.command": "clippy" +} diff --git a/src/common/instructions.rs b/src/common/instructions.rs index 4f94276..18144b8 100644 --- a/src/common/instructions.rs +++ b/src/common/instructions.rs @@ -1,7 +1,7 @@ type Offset = u16; type Immediate = u16; -#[derive(Copy, Clone, Debug, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Interrupt { Software(u8), } @@ -9,22 +9,23 @@ pub enum Interrupt { pub type Address = u32; impl Interrupt { - fn as_u8(&self) -> u8 { + const fn as_u8(self) -> u8 { match self { - Interrupt::Software(code) => *code, + Self::Software(code) => code, } } } impl From for Interrupt { + #[allow(unreachable_code)] fn from(_code: u8) -> Self { - todo!("implement this once a hardware interrupt convention is established"); - #[allow(unreachable_code)] - Interrupt::Software(_code) + todo!("Implement this once a hardware interrupt convention is established."); + + // Self::Software(_code) } } -#[derive(Copy, Clone, Debug, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Register { // general purpose registers Rg0, @@ -62,71 +63,93 @@ pub enum Register { Pcx, } -impl From for Register { - fn from(idx: u8) -> Register { - match idx { - // system registers are not indexable in the reg file so they cannot be modified by instructions. - 0x0 => Register::Rg0, - 0x1 => Register::Rg1, - 0x2 => Register::Rg2, - 0x3 => Register::Rg3, - 0x4 => Register::Rg4, - 0x5 => Register::Rg5, - 0x6 => Register::Rg6, - 0x7 => Register::Rg7, - 0x8 => Register::Rg8, - 0x9 => Register::Rg9, - 0xA => Register::Rga, - 0xB => Register::Rgb, - 0xC => Register::Rgc, - 0xD => Register::Rgd, - 0xE => Register::Rge, - 0xF => Register::Rgf, - 0x10 => Register::Acc, - 0x11 => Register::Spr, - 0x12 => Register::Bpr, - 0x13 => Register::Ret, - 0x14 => Register::Idr, - 0x15 => Register::Mmr, - 0x16 => Register::Zero, - 0x17 => Register::None, - _ => panic!("invalid register"), +#[derive(Debug)] +/// Error type for parsing register numbers. +pub enum RegisterParseError { + InvalidIndex(u8), +} + +impl std::fmt::Display for RegisterParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::InvalidIndex(idx) => write!(f, "invalid index given ({idx})"), } } } +impl std::error::Error for RegisterParseError {} + +impl TryFrom for Register { + type Error = RegisterParseError; + + fn try_from(idx: u8) -> Result { + if idx > 0x18 { + return Err(RegisterParseError::InvalidIndex(idx)); + } + + Ok(match idx { + // System registers are not indexable in the reg file so they cannot be modified by instructions. + 0x0 => Self::Rg0, + 0x1 => Self::Rg1, + 0x2 => Self::Rg2, + 0x3 => Self::Rg3, + 0x4 => Self::Rg4, + 0x5 => Self::Rg5, + 0x6 => Self::Rg6, + 0x7 => Self::Rg7, + 0x8 => Self::Rg8, + 0x9 => Self::Rg9, + 0xA => Self::Rga, + 0xB => Self::Rgb, + 0xC => Self::Rgc, + 0xD => Self::Rgd, + 0xE => Self::Rge, + 0xF => Self::Rgf, + 0x10 => Self::Acc, + 0x11 => Self::Spr, + 0x12 => Self::Bpr, + 0x13 => Self::Ret, + 0x14 => Self::Idr, + 0x15 => Self::Mmr, + 0x16 => Self::Zero, + 0x17 => Self::None, + _ => unreachable!("This is already checked for in top `if` branch."), + }) + } +} + impl std::fmt::Display for Register { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Register::Rg0 => write!(f, "Rg0"), - Register::Rg1 => write!(f, "Rg1"), - Register::Rg2 => write!(f, "Rg2"), - Register::Rg3 => write!(f, "Rg3"), - Register::Rg4 => write!(f, "Rg4"), - Register::Rg5 => write!(f, "Rg5"), - Register::Rg6 => write!(f, "Rg6"), - Register::Rg7 => write!(f, "Rg7"), - Register::Rg8 => write!(f, "Rg8"), - Register::Rg9 => write!(f, "Rg9"), - Register::Rga => write!(f, "Rga"), - Register::Rgb => write!(f, "Rgb"), - Register::Rgc => write!(f, "Rgc"), - Register::Rgd => write!(f, "Rgd"), - Register::Rge => write!(f, "Rge"), - Register::Rgf => write!(f, "Rgf"), - Register::Acc => write!(f, "Acc"), - Register::Spr => write!(f, "Spr"), - Register::Bpr => write!(f, "Bpr"), - Register::Ret => write!(f, "Ret"), - Register::Idr => write!(f, "Idr"), - Register::Mmr => write!(f, "Mmr"), - Register::Zero => write!(f, "Zero"), - Register::None => write!(f, "None"), - Register::Mar => write!(f, "Mar"), - Register::Mdr => write!(f, "Mdr"), - Register::Sts => write!(f, "Sts"), - Register::Cir => write!(f, "Cir"), - Register::Pcx => write!(f, "Pcx"), + Self::Rg0 => write!(f, "Rg0"), + Self::Rg1 => write!(f, "Rg1"), + Self::Rg2 => write!(f, "Rg2"), + Self::Rg3 => write!(f, "Rg3"), + Self::Rg4 => write!(f, "Rg4"), + Self::Rg5 => write!(f, "Rg5"), + Self::Rg6 => write!(f, "Rg6"), + Self::Rg7 => write!(f, "Rg7"), + Self::Rg8 => write!(f, "Rg8"), + Self::Rg9 => write!(f, "Rg9"), + Self::Rga => write!(f, "Rga"), + Self::Rgb => write!(f, "Rgb"), + Self::Rgc => write!(f, "Rgc"), + Self::Rgd => write!(f, "Rgd"), + Self::Rge => write!(f, "Rge"), + Self::Rgf => write!(f, "Rgf"), + Self::Acc => write!(f, "Acc"), + Self::Spr => write!(f, "Spr"), + Self::Bpr => write!(f, "Bpr"), + Self::Ret => write!(f, "Ret"), + Self::Idr => write!(f, "Idr"), + Self::Mmr => write!(f, "Mmr"), + Self::Zero => write!(f, "Zero"), + Self::None => write!(f, "None"), + Self::Mar => write!(f, "Mar"), + Self::Mdr => write!(f, "Mdr"), + Self::Sts => write!(f, "Sts"), + Self::Cir => write!(f, "Cir"), + Self::Pcx => write!(f, "Pcx"), } } } @@ -189,63 +212,65 @@ pub enum Instruction { } impl Instruction { + #[must_use] pub fn encode(&self) -> u32 { todo!("imlement instruction encoding") } - pub fn decode(data: u32) -> Self { + #[must_use] + pub const fn decode(_data: u32) -> Self { // TODO: this needs to actually decode something - Instruction::Nop + Self::Nop } } impl std::fmt::Display for Instruction { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Instruction::Nop => write!(f, "No Operation"), - Instruction::Mov(a, b) => write!(f, "MOV {}, {}", a, b), - Instruction::MovSigned(a, b) => write!(f, "MOVS {}, {}", a, b), - Instruction::LoadByte(a, b, c) => write!(f, "LDB {}, {}, {}", a, b, c), - Instruction::LoadByteSigned(a, b, c) => write!(f, "LDBS {}, {}, {}", a, b, c), - 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), + Self::Nop => write!(f, "No Operation"), + Self::Mov(a, b) => write!(f, "MOV {a}, {b}"), + Self::MovSigned(a, b) => write!(f, "MOVS {a}, {b}"), + Self::LoadByte(a, b, c) => write!(f, "LDB {a}, {b}, {c}"), + Self::LoadByteSigned(a, b, c) => write!(f, "LDBS {a}, {b}, {c}"), + Self::LoadHalfword(a, b, c) => write!(f, "LDH {a}, {b}, {c}"), + Self::LoadHalfwordSigned(a, b, c) => write!(f, "LDHS {a}, {b}, {c}"), + Self::LoadWord(a, b, c) => write!(f, "LDW {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), - Instruction::StoreWord(a, b, c) => write!(f, "STW {}, {}, {}", a, b, c), + Self::StoreByte(a, b, c) => write!(f, "STB {a}, {b}, {c}"), + Self::StoreHalfword(a, b, c) => write!(f, "STH {a}, {b}, {c}"), + Self::StoreWord(a, b, c) => write!(f, "STW {a}, {b}, {c}"), - Instruction::LoadLowerImmediate(a, b) => write!(f, "LLI {}, {}", a, b), - Instruction::LoadUpperImmediate(a, b) => write!(f, "LUI {}, {}", a, b), + Self::LoadLowerImmediate(a, b) => write!(f, "LLI {a}, {b}"), + Self::LoadUpperImmediate(a, b) => write!(f, "LUI {a}, {b}"), - Instruction::Jump(a, b) => write!(f, "JMP {}, {}", a, b), - Instruction::JumpEq(a, b) => write!(f, "JEQ {}, {}", a, b), - Instruction::JumpNeq(a, b) => write!(f, "JNEQ {}, {}", a, b), - Instruction::JumpGt(a, b) => write!(f, "JGT {}, {}", a, b), - Instruction::JumpGe(a, b) => write!(f, "JGE {}, {}", a, b), - Instruction::JumpLt(a, b) => write!(f, "JLT {}, {}", a, b), - Instruction::JumpLe(a, b) => write!(f, "JLE {}, {}", a, b), + Self::Jump(a, b) => write!(f, "JMP {a}, {b}"), + Self::JumpEq(a, b) => write!(f, "JEQ {a}, {b}"), + Self::JumpNeq(a, b) => write!(f, "JNEQ {a}, {b}"), + Self::JumpGt(a, b) => write!(f, "JGT {a}, {b}"), + Self::JumpGe(a, b) => write!(f, "JGE {a}, {b}"), + Self::JumpLt(a, b) => write!(f, "JLT {a}, {b}"), + Self::JumpLe(a, b) => write!(f, "JLE {a}, {b}"), - Instruction::Compare(a, b) => write!(f, "CMP {}, {}", a, b), + Self::Compare(a, b) => write!(f, "CMP {a}, {b}"), - Instruction::Add(a, b, c) => write!(f, "ADD {}, {}, {}", a, b, c), - Instruction::Sub(a, b, c) => write!(f, "SUB {}, {}, {}", a, b, c), - Instruction::Increment(a) => write!(f, "INC {}", a), - Instruction::Decrement(a) => write!(f, "DEC {}", a), - Instruction::ShiftLeft(a, b, c) => write!(f, "SHL {}, {}, {}", a, b, c), - Instruction::ShiftRight(a, b, c) => write!(f, "SHR {}, {}, {}", a, b, c), + Self::Add(a, b, c) => write!(f, "ADD {a}, {b}, {c}"), + Self::Sub(a, b, c) => write!(f, "SUB {a}, {b}, {c}"), + Self::Increment(a) => write!(f, "INC {a}"), + Self::Decrement(a) => write!(f, "DEC {a}"), + Self::ShiftLeft(a, b, c) => write!(f, "SHL {a}, {b}, {c}"), + Self::ShiftRight(a, b, c) => write!(f, "SHR {a}, {b}, {c}"), - Instruction::And(a, b, c) => write!(f, "AND {}, {}, {}", a, b, c), - Instruction::Or(a, b, c) => write!(f, "OR {}, {}, {}", a, b, c), - Instruction::Not(a, b) => write!(f, "NOT {}, {}", a, b), - Instruction::Xor(a, b, c) => write!(f, "XOR {}, {}, {}", a, b, c), - Instruction::Nand(a, b, c) => write!(f, "NAND {}, {}, {}", a, b, c), - Instruction::Nor(a, b, c) => write!(f, "NOR {}, {}, {}", a, b, c), - Instruction::Xnor(a, b, c) => write!(f, "XNOR {}, {}, {}", a, b, c), + Self::And(a, b, c) => write!(f, "AND {a}, {b}, {c}"), + Self::Or(a, b, c) => write!(f, "OR {a}, {b}, {c}"), + Self::Not(a, b) => write!(f, "NOT {a}, {b}"), + Self::Xor(a, b, c) => write!(f, "XOR {a}, {b}, {c}"), + Self::Nand(a, b, c) => write!(f, "NAND {a}, {b}, {c}"), + Self::Nor(a, b, c) => write!(f, "NOR {a}, {b}, {c}"), + Self::Xnor(a, b, c) => write!(f, "XNOR {a}, {b}, {c}"), - Instruction::Interrupt(a) => write!(f, "INT {}", a.as_u8()), - Instruction::IntReturn => write!(f, "INTR"), - Instruction::Halt => write!(f, "HALT"), + Self::Interrupt(a) => write!(f, "INT {}", a.as_u8()), + Self::IntReturn => write!(f, "INTR"), + Self::Halt => write!(f, "HALT"), } } } diff --git a/src/emulator/system/emulator.rs b/src/emulator/system/emulator.rs index 838ad35..9d3bfbb 100644 --- a/src/emulator/system/emulator.rs +++ b/src/emulator/system/emulator.rs @@ -16,17 +16,21 @@ use crate::{ }; pub fn run_emulator( - cmd_rx: Receiver, - state_tx: Sender, - cpu: Arc>, + cmd_rx: &Receiver, + state_tx: &Sender, + cpu: &Arc>, ) { let mut running = Running::Paused; let mut addr = 0u32; - let mut size = 256; + let size = 256; - // Send initial state - let memory_view = cpu.lock().unwrap().memory.read_range(addr, size); - let initial_state = state(&cpu, &running, 0, memory_view); + // Send initial state. + let Ok(mut cpu_lock) = cpu.lock() else { + panic!("Failed to lock CPU.") + }; + + let memory_view = cpu_lock.memory.read_range(addr, size); + let initial_state = state(cpu, running, 0, memory_view); let _ = state_tx.send(initial_state); let mut instruction_count = 0; @@ -57,38 +61,32 @@ pub fn run_emulator( } Command::Reset => { running = Running::Paused; - if let Ok(mut cpu_lock) = cpu.lock() { - cpu_lock.reset(); - } + cpu_lock.reset(); instruction_count = 0; println!("Emulator rebooted"); } Command::Step => { running = Running::Paused; // Execute one cycle - if let Ok(mut cpu_lock) = cpu.lock() { - cpu_lock.cycle(); // or advance() - whatever your method is called - } + cpu_lock.cycle(); // or advance() - whatever your method is called instruction_count += 1; println!("Stepped one instruction"); } - Command::Read(new, size) => { - addr = new as u32; - println!("Memory view for address 0x{:08x}", addr); + Command::Read(new, _size) => { + addr = new; + println!("Memory view for address 0x{addr:08x}"); } Command::Write(offset, data) => { - if let Ok(mut cpu_lock) = cpu.lock() { - cpu_lock.memory.write_range(offset, data); - } + cpu_lock.memory.write_range(offset, data); println!("Program loaded"); } - Command::Interrupt(interrupt) => { + Command::Interrupt(_interrupt) => { todo!("implement interrupts") } } - let memory_view = cpu.lock().unwrap().memory.read_range(addr, size); - let state = state(&cpu, &running, instruction_count, memory_view); + let memory_view = cpu_lock.memory.read_range(addr, size); + let state = state(cpu, running, instruction_count, memory_view); let _ = state_tx.send(state); } @@ -96,12 +94,13 @@ pub fn run_emulator( if running == Running::Running { let mut update = false; // Execute one cycle - if let Ok(mut cpu_lock) = cpu.lock() { - cpu_lock.cycle(); - if let Instruction::Halt = Instruction::decode(cpu_lock.get(Register::Cir)) { - running = Running::Halted; - update = true; - } + cpu_lock.cycle(); + if matches!( + Instruction::decode(cpu_lock.get(Register::Cir)), + Instruction::Halt + ) { + running = Running::Halted; + update = true; } instruction_count += 1; @@ -112,8 +111,8 @@ pub fn run_emulator( } if update { - let memory_view = cpu.lock().unwrap().memory.read_range(addr, size); - let state = state(&cpu, &running, instruction_count, memory_view); + let memory_view = cpu_lock.memory.read_range(addr, size); + let state = state(cpu, running, instruction_count, memory_view); let _ = state_tx.send(state); } } else { @@ -124,22 +123,22 @@ pub fn run_emulator( fn state( cpu: &Arc>, - running: &Running, + running: Running, instruction_count: usize, memory_view: Vec, ) -> State { - if let Ok(mut cpu_lock) = cpu.lock() { - State { - // TODO: Replace with actual register access from your CPU - reg_file: cpu_lock.registers.clone(), - running: running.clone(), - instructions: instruction_count, - stack_view: cpu_lock.get_stack(32), - memory_view, - display_view: cpu_lock.display(), - error: None, - } - } else { - panic!("Failed to lock CPU"); + let Ok(mut cpu_lock) = cpu.lock() else { + panic!("Could not lock CPU.") + }; + + 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, } } diff --git a/src/emulator/system/memory.rs b/src/emulator/system/memory.rs index 7fc55af..87c3772 100644 --- a/src/emulator/system/memory.rs +++ b/src/emulator/system/memory.rs @@ -14,35 +14,48 @@ pub struct MainStore { pub data: HashMap, } -struct Block { +pub struct Block { data: [u8; 256], } +impl Default for MainStore { + fn default() -> Self { + Self::new() + } +} + impl MainStore { - pub fn new() -> MainStore { - MainStore { + #[must_use] + pub fn new() -> Self { + Self { data: HashMap::new(), } } - fn segment_addr(addr: u32) -> (u32, u8) { + const fn segment_addr(addr: u32) -> (u32, u8) { (addr / 256, (addr % 256) as u8) } - fn mut_block<'a>(&'a mut self, addr: u32) -> &'a mut Block { - if !self.data.contains_key(&addr) { - self.data.insert(addr, Block { data: [0; 256] }); - } + 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).unwrap() + 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 { - if !self.data.contains_key(&addr) { - self.data.insert(addr, Block { data: [0; 256] }); - } + self.data + .entry(addr) + .or_insert_with(|| Block { data: [0; 256] }); - self.data.get(&addr).unwrap() + self.data.get(&addr).map_or_else( + || panic!("Could not fetch block with address {addr:x?}"), + |block| block, + ) } } @@ -52,16 +65,16 @@ impl MemoryUnit for MainStore { } fn read_byte(&mut self, addr: u32) -> u8 { - let (block_addr, offset) = MainStore::segment_addr(addr); + 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) = MainStore::segment_addr(addr); + 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 + 0) as usize]; + 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]; @@ -77,21 +90,21 @@ impl MemoryUnit for MainStore { } fn write_byte(&mut self, addr: u32, value: u8) { - let (block_addr, offset) = MainStore::segment_addr(addr); + 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) = MainStore::segment_addr(addr); + let (block_addr, offset) = Self::segment_addr(addr); let block = self.mut_block(block_addr); - block.data[(offset + 0) as usize] = (value >> 24) as u8; + 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) { - todo!("we might need this for DMA"); + fn write_range(&mut self, _addr: u32, _value: Vec) { + todo!("We might need this for DMA."); } } diff --git a/src/emulator/system/model.rs b/src/emulator/system/model.rs index f1023dd..b68e09c 100644 --- a/src/emulator/system/model.rs +++ b/src/emulator/system/model.rs @@ -1,6 +1,6 @@ use crate::common::instructions::{Address, Interrupt, Register}; -#[derive(PartialEq, Debug, Clone, Copy)] +#[derive(PartialEq, Eq, Debug, Clone, Copy)] pub enum Running { Running, Paused, @@ -56,6 +56,7 @@ pub struct RegFile { } impl RegFile { + #[must_use] pub fn all(&self) -> Vec<(&str, u32)> { vec![ ("Rg0", self.rg0), @@ -88,7 +89,7 @@ impl RegFile { ] } - pub fn reset(&mut self) { + pub const fn reset(&mut self) { self.rg0 = 0; self.rg1 = 0; self.rg2 = 0; @@ -147,10 +148,11 @@ impl RegFile { Register::Sts => &mut self.sts, Register::Cir => &mut self.cir, Register::Pcx => &mut self.pcx, - _ => panic!("invalid register"), + _ => panic!("Invalid register."), } } + #[must_use] pub fn get(&self, reg: Register) -> u32 { match reg { Register::Rg0 => self.rg0, @@ -181,7 +183,7 @@ impl RegFile { Register::Cir => self.cir, Register::Pcx => self.pcx, Register::Zero => 0, - _ => panic!("invalid register"), + _ => panic!("Invalid register."), } } } diff --git a/src/emulator/system/processor.rs b/src/emulator/system/processor.rs index 92fd693..51af413 100644 --- a/src/emulator/system/processor.rs +++ b/src/emulator/system/processor.rs @@ -1,10 +1,7 @@ -use std::{ - cmp::{max, min}, - sync::Arc, -}; +use std::cmp::{max, min}; use crate::{ - common::instructions::{Address, Instruction, Interrupt, Register}, + common::instructions::{Instruction, Interrupt, Register}, emulator::system::{memory::MemoryUnit, model::RegFile}, }; @@ -15,10 +12,12 @@ pub struct Processor { // pub io_devices: Vec>, } +#[allow(clippy::needless_pass_by_ref_mut)] impl Processor { // io_devices: Vec> + #[must_use] pub fn new(memory: Box) -> Self { - Processor { + Self { // io_devices, memory, registers: RegFile::default(), @@ -45,7 +44,7 @@ impl Processor { let val = self.memory.read_word(addr); // set CIR to the value of RAM[MAR] - *self.reg(Register::Mar) = val as u32; + *self.reg(Register::Mar) = val; // decode and execute the instruction let instruction = Instruction::decode(val); @@ -57,6 +56,7 @@ impl Processor { self.get(Register::Pcx) } + #[must_use] pub fn get(&self, reg: Register) -> u32 { self.registers.get(reg) } @@ -108,10 +108,10 @@ impl Processor { } fn jump(&mut self, reg: Register, offset: u16) { - *self.reg(Register::Pcx) = self.get(reg) + offset as u32; + *self.reg(Register::Pcx) = self.get(reg) + u32::from(offset); } - fn begin_interrupt(&mut self, int: Interrupt) { + fn begin_interrupt(&mut self, _int: Interrupt) { // first we get the address of the interrupt descriptor table. todo!(); } @@ -132,6 +132,7 @@ impl Processor { } #[derive(Debug)] +#[expect(dead_code)] enum Flag { Equal = 0, GreaterThan = 1, @@ -149,6 +150,7 @@ trait Executable { } impl Executable for Instruction { + #[allow(clippy::too_many_lines)] fn execute(self, cpu: &mut Processor) { match self { // No operation - a blank line. @@ -166,36 +168,37 @@ impl Executable for Instruction { // 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; + *cpu.reg(dest) = u32::from(cpu.memory.read_byte(cpu.get(base) + u32::from(offset))); } // 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); + *cpu.reg(dest) = sign_extend(u32::from( + cpu.memory.read_byte(cpu.get(base) + u32::from(offset)), + )); } // 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; + *cpu.reg(dest) = cpu.memory.read_word(cpu.get(base) + u32::from(offset)) >> 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); + sign_extend(cpu.memory.read_word(cpu.get(base) + u32::from(offset)) >> 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); + *cpu.reg(dest) = cpu.memory.read_word(cpu.get(base) + u32::from(offset)); } // 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); + .write_byte(cpu.get(base) + u32::from(offset), cpu.get(src) as u8); } // Stores a half-word from SrcReg in memory address (base + offset) The effective address must be 2-byte-aligned. @@ -203,25 +206,25 @@ impl Executable for Instruction { // 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]); + .write_byte(cpu.get(base) + u32::from(offset), bytes[0]); cpu.memory - .write_byte(cpu.get(base) + offset as u32 + 1, bytes[1]); + .write_byte(cpu.get(base) + u32::from(offset) + 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)); + .write_word(cpu.get(base) + u32::from(offset), 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) = imm as u32; + *cpu.reg(reg) = u32::from(imm); } // 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; + *cpu.reg(reg) = (cpu.get(reg) & 0x0000_FFFF) | u32::from(imm) << 16; } // Unconditionally jumps to the calculated address or direct address @@ -230,42 +233,42 @@ impl Executable for Instruction { // 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) + 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) + 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) + 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) + 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) + 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) + cpu.jump(reg, offset); } } @@ -294,39 +297,39 @@ impl Executable for Instruction { } // 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)); + Self::Add(src1, src2, dest) => { + *cpu.reg(dest) = add(cpu.get(src1), cpu.get(src2)); } // 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)); + Self::Sub(src1, src2, dest) => { + *cpu.reg(dest) = sub(cpu.get(src1), cpu.get(src2)); } // 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)), + Self::And(src1, src2, dest) => *cpu.reg(dest) = and(cpu.get(src1), cpu.get(src2)), // 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)), + Self::Or(src1, src2, dest) => *cpu.reg(dest) = or(cpu.get(src1), cpu.get(src2)), // 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)), + Self::Xor(src1, src2, dest) => *cpu.reg(dest) = xor(cpu.get(src1), cpu.get(src2)), // 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)), + Self::Nand(src1, src2, dest) => *cpu.reg(dest) = nand(cpu.get(src1), cpu.get(src2)), // 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)), + Self::Nor(src1, src2, dest) => *cpu.reg(dest) = nor(cpu.get(src1), cpu.get(src2)), // 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)), + Self::Xnor(src1, src2, dest) => *cpu.reg(dest) = xnor(cpu.get(src1), cpu.get(src2)), // 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)); + Self::Compare(src1, src2) => { + cpu.cmp(cpu.get(src1), cpu.get(src2)); } /* @@ -353,63 +356,64 @@ impl Executable for Instruction { } // mathematical and logical functions & other operations -fn add(a: u32, b: u32) -> u32 { +const fn add(a: u32, b: u32) -> u32 { a + b } -fn sub(a: u32, b: u32) -> u32 { +const fn sub(a: u32, b: u32) -> u32 { a - b } -fn and(a: u32, b: u32) -> u32 { +const fn and(a: u32, b: u32) -> u32 { a & b } -fn inc(a: u32) -> u32 { +const fn inc(a: u32) -> u32 { a + 1 } -fn dec(a: u32) -> u32 { +const fn dec(a: u32) -> u32 { a - 1 } -fn shl(a: u32, amount: u8) -> u32 { +const fn shl(a: u32, amount: u8) -> u32 { a << amount } -fn shr(a: u32, amount: u8) -> u32 { +const fn shr(a: u32, amount: u8) -> u32 { a >> amount } -fn or(a: u32, b: u32) -> u32 { +const fn or(a: u32, b: u32) -> u32 { a | b } -fn not(a: u32) -> u32 { +const fn not(a: u32) -> u32 { !a } -fn xor(a: u32, b: u32) -> u32 { +const fn xor(a: u32, b: u32) -> u32 { a ^ b } -fn nand(a: u32, b: u32) -> u32 { +const fn nand(a: u32, b: u32) -> u32 { !(a & b) } -fn nor(a: u32, b: u32) -> u32 { +const fn nor(a: u32, b: u32) -> u32 { !(a | b) } -fn xnor(a: u32, b: u32) -> u32 { +const fn xnor(a: u32, b: u32) -> u32 { !(a ^ b) } -fn sign_extend(val: u32) -> u32 { +const fn sign_extend(val: u32) -> u32 { let (mask, sign_bit): (u32, u8) = match val { - 0..=0xFF => (0xFFFFFF00, 7), - 0..=0xFFFF => (0xFFFF0000, 15), - _ => (0x00000000, 31), + 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 { diff --git a/src/emulator/ui/control_unit.rs b/src/emulator/ui/control_unit.rs index 5d9fadc..d235a6b 100644 --- a/src/emulator/ui/control_unit.rs +++ b/src/emulator/ui/control_unit.rs @@ -14,7 +14,8 @@ pub struct ControlPanel { } impl ControlPanel { - pub fn new(sender: Sender) -> Self { + #[must_use] + pub const fn new(sender: Sender) -> Self { Self { visible: false, sender, @@ -48,11 +49,11 @@ impl Component for ControlPanel { { if state.running == Running::Running { self.sender.send(Command::Stop).unwrap_or_else(|_| { - state.error = Some("Failed to send command".to_string()) + 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()) + state.error = Some("Failed to send command".to_string()); }); } } @@ -88,7 +89,7 @@ impl Component for ControlPanel { } } -fn render_register_table(state: &mut State, ui: &mut egui::Ui, _ctx: &egui::Context) { +fn render_register_table(state: &State, ui: &mut egui::Ui, _ctx: &egui::Context) { // Left column - Registers ui.vertical(|ui| { ui.heading("Registers"); @@ -114,7 +115,7 @@ 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(reg.0.to_string()); 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 ed83cdb..f21c89b 100644 --- a/src/emulator/ui/interface.rs +++ b/src/emulator/ui/interface.rs @@ -1,7 +1,4 @@ -use crate::{ - common::instructions::{Address, Interrupt}, - emulator::system::model::{Command, Running, State}, -}; +use crate::emulator::system::model::{Command, Running, State}; use std::sync::mpsc::{Receiver, Sender}; pub trait Component { @@ -11,7 +8,7 @@ pub trait Component { fn category(&self) -> Category; } -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Category { Control, Memory, @@ -20,22 +17,19 @@ pub enum Category { } impl Category { - pub fn as_str(&self) -> &'static str { + #[must_use] + pub const fn as_str(&self) -> &'static str { match self { - Category::Control => "Control Systems", - Category::Memory => "Memory Systems", - Category::Io => "I/O Systems", - Category::Programming => "Programming", + Self::Control => "Control Systems", + Self::Memory => "Memory Systems", + Self::Io => "I/O Systems", + Self::Programming => "Programming", } } - pub fn list() -> Vec { - vec![ - Category::Control, - Category::Memory, - Category::Io, - Category::Programming, - ] + #[must_use] + pub fn list() -> Vec { + vec![Self::Control, Self::Memory, Self::Io, Self::Programming] } } @@ -47,6 +41,7 @@ pub struct EmulatorUI { } impl EmulatorUI { + #[must_use] pub fn new(sender: Sender, receiver: Receiver) -> Self { Self { sender, @@ -68,10 +63,10 @@ impl EmulatorUI { } impl eframe::App for EmulatorUI { - fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { self.update_state(); - if let Running::Running = self.state.running { + if self.state.running == Running::Running { ctx.request_repaint(); } @@ -88,7 +83,7 @@ impl eframe::App for EmulatorUI { }); egui::CentralPanel::default().show(ctx, |ui| { - ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| { + ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |_ui| { egui::Window::new("Main Menu") .resizable(false) .default_width(300.0) @@ -96,9 +91,9 @@ impl eframe::App for EmulatorUI { super::menu::render_menu(self, ui, ctx); }); - for c in self.components.iter_mut() { + for c in &mut self.components { let mut visible = *c.visible(); - if visible == true { + if visible { egui::Window::new(c.name()) .open(&mut visible) .show(ctx, |ui| { @@ -110,7 +105,7 @@ impl eframe::App for EmulatorUI { }); }); - egui::TopBottomPanel::bottom("bottom_panel").show(ctx, |ui| { + egui::TopBottomPanel::bottom("bottom_panel").show(ctx, |_ui| { // interface::bottompanel::render_bottom_panel(self, ui, ctx); }); } diff --git a/src/emulator/ui/memory_inspector.rs b/src/emulator/ui/memory_inspector.rs index 154f6f8..37f09b2 100644 --- a/src/emulator/ui/memory_inspector.rs +++ b/src/emulator/ui/memory_inspector.rs @@ -14,7 +14,8 @@ pub struct MemoryInspector { } impl MemoryInspector { - pub fn new(sender: Sender) -> Self { + #[must_use] + pub const fn new(sender: Sender) -> Self { Self { view_size: 256, view_addr: 0, @@ -66,9 +67,12 @@ impl Component for MemoryInspector { 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(); + + 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()); } @@ -79,7 +83,7 @@ impl Component for MemoryInspector { // Show input error if any if let Some(error) = &state.error { - ui.colored_label(egui::Color32::RED, format!("Error: {}", error)); + ui.colored_label(egui::Color32::RED, format!("Error: {error}")); } ui.add_space(10.0); @@ -98,7 +102,7 @@ impl Component for MemoryInspector { ui.strong("Address"); for i in 0..4 { - ui.strong(format!("{:X}", i)); + ui.strong(format!("{i:X}")); } ui.strong("Decimal"); @@ -107,11 +111,11 @@ impl Component for MemoryInspector { 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 (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!("{:02X}", byte)); + ui.monospace(format!("{byte:02X}")); } // Fill remaining columns if last row is incomplete @@ -120,10 +124,11 @@ impl Component for MemoryInspector { } // combine all 4 bytes in the chunk into a u32 - let combined = - chunk.iter().fold(0u32, |acc, &byte| acc << 8 | byte as u32); + let combined = chunk + .iter() + .fold(0u32, |acc, &byte| acc << 8 | u32::from(byte)); - ui.monospace(format!("{}", combined)); + ui.monospace(format!("{combined}")); // ui.monospace(format!("{:?}", Instruction::decode(combined))); ui.monospace("TODO! instruction"); @@ -136,17 +141,17 @@ impl Component for MemoryInspector { } fn parse_address(address: &str) -> Result { - if address.starts_with("0x") { - return u32::from_str_radix(&address[2..], 16); + if let Some(hex_part) = address.strip_prefix("0x") { + return u32::from_str_radix(hex_part, 16); } - if address.starts_with("0b") { - return u32::from_str_radix(&address[2..], 2); + if let Some(bin_part) = address.strip_prefix("0b") { + return u32::from_str_radix(bin_part, 2); } - if address.starts_with("0o") { - return u32::from_str_radix(&address[1..], 8); + if let Some(oct_part) = address.strip_prefix("0o") { + return u32::from_str_radix(oct_part, 8); } - u32::from_str_radix(address, 10) + address.parse::() } diff --git a/src/emulator/ui/menu.rs b/src/emulator/ui/menu.rs index 313440e..edd5008 100644 --- a/src/emulator/ui/menu.rs +++ b/src/emulator/ui/menu.rs @@ -13,7 +13,7 @@ pub fn render_menu(state: &mut EmulatorUI, ui: &mut egui::Ui, _ctx: &egui::Conte ui.heading(cat.as_str()); ui.add_space(10.0); - for comp in state.components.iter_mut() { + for comp in &mut state.components { let name = comp.name(); if comp.category() == cat { ui.toggle_value(comp.visible(), name); diff --git a/src/emulator/ui/stack_inspector.rs b/src/emulator/ui/stack_inspector.rs index 80bde39..5401321 100644 --- a/src/emulator/ui/stack_inspector.rs +++ b/src/emulator/ui/stack_inspector.rs @@ -1,17 +1,21 @@ use crate::{ common::instructions::Register, - emulator::{ - system::model::State, - ui::interface::{Component, EmulatorUI}, - }, + emulator::{system::model::State, ui::interface::Component}, }; pub struct StackInspector { visible: bool, } +impl Default for StackInspector { + fn default() -> Self { + Self::new() + } +} + impl StackInspector { - pub fn new() -> Self { + #[must_use] + pub const fn new() -> Self { Self { visible: false } } } @@ -29,7 +33,7 @@ impl Component for StackInspector { super::interface::Category::Memory } - fn render(&mut self, state: &mut State, ui: &mut egui::Ui, ctx: &egui::Context) { + fn render(&mut self, state: &mut State, ui: &mut egui::Ui, _ctx: &egui::Context) { ui.vertical(|ui| { ui.heading("Stack Inspector"); egui::ScrollArea::vertical() @@ -44,13 +48,13 @@ impl Component for StackInspector { ui.label("Value"); ui.end_row(); - for (i, value) in state.stack_view.iter().take(32).enumerate() { + for (i, value) in (0u32..).zip(state.stack_view.iter().take(32)) { ui.label(format!( "{} [{}]", i, - state.reg_file.get(Register::Spr) - i as u32 * 4 + state.reg_file.get(Register::Spr) - i * 4 )); - ui.label(format!("0x{:08X} ({})", value, value)); + ui.label(format!("0x{value:08X} ({value})")); ui.end_row(); } diff --git a/src/lib.rs b/src/lib.rs index f59a991..9e75fa0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,16 @@ +#![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 common; pub mod emulator; diff --git a/src/main.rs b/src/main.rs index 2dd2511..0c40f3e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,7 +20,11 @@ fn main() -> Result<(), eframe::Error> { 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