emulator: applied some clippy lints

This commit is contained in:
2025-06-15 04:03:48 +01:00
parent a16f57c737
commit a240346a84
13 changed files with 357 additions and 288 deletions
+3
View File
@@ -0,0 +1,3 @@
{
"rust-analyzer.check.command": "clippy"
}
+129 -104
View File
@@ -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<u8> 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<u8> 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<u8> for Register {
type Error = RegisterParseError;
fn try_from(idx: u8) -> Result<Self, Self::Error> {
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"),
}
}
}
+43 -44
View File
@@ -16,17 +16,21 @@ use crate::{
};
pub fn run_emulator(
cmd_rx: Receiver<Command>,
state_tx: Sender<State>,
cpu: Arc<Mutex<Processor>>,
cmd_rx: &Receiver<Command>,
state_tx: &Sender<State>,
cpu: &Arc<Mutex<Processor>>,
) {
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<Mutex<Processor>>,
running: &Running,
running: Running,
instruction_count: usize,
memory_view: Vec<u8>,
) -> 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,
}
}
+34 -21
View File
@@ -14,35 +14,48 @@ pub struct MainStore {
pub data: HashMap<u32, Block>,
}
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<u8>) {
todo!("we might need this for DMA");
fn write_range(&mut self, _addr: u32, _value: Vec<u8>) {
todo!("We might need this for DMA.");
}
}
+6 -4
View File
@@ -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."),
}
}
}
+60 -56
View File
@@ -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<Arc<dyn IODevice>>,
}
#[allow(clippy::needless_pass_by_ref_mut)]
impl Processor {
// io_devices: Vec<Arc<dyn IODevice>>
#[must_use]
pub fn new(memory: Box<dyn MemoryUnit>) -> 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 {
+6 -5
View File
@@ -14,7 +14,8 @@ pub struct ControlPanel {
}
impl ControlPanel {
pub fn new(sender: Sender<Command>) -> Self {
#[must_use]
pub const fn new(sender: Sender<Command>) -> 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();
+18 -23
View File
@@ -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<Category> {
vec![
Category::Control,
Category::Memory,
Category::Io,
Category::Programming,
]
#[must_use]
pub fn list() -> Vec<Self> {
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<Command>, receiver: Receiver<State>) -> 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);
});
}
+25 -20
View File
@@ -14,7 +14,8 @@ pub struct MemoryInspector {
}
impl MemoryInspector {
pub fn new(sender: Sender<Command>) -> Self {
#[must_use]
pub const fn new(sender: Sender<Command>) -> 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<u32, ParseIntError> {
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::<u32>()
}
+1 -1
View File
@@ -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);
+13 -9
View File
@@ -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();
}
+14
View File
@@ -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;
+5 -1
View File
@@ -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