riscii

An emulator for the RISC II
Log | Files | Refs | LICENSE

commit 7fabdd6a1ec4309862ccd16c01991015cf884180
parent 57f237af2aabb16c63e6ff698551e38e5e72447c
Author: Ryan Jeffrey <ryan@ryanmj.xyz>
Date:   Thu, 16 Jun 2022 19:54:24 -0700

Basic execution stub

Diffstat:
MREADME.org | 1+
Msrc/cpu.rs | 464+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Msrc/decode.rs | 2+-
Msrc/decode_test.rs | 4++--
Msrc/execute.rs | 681++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Msrc/instruction.rs | 65+++++++++++++++++++++++++++++++++++++++++++++--------------------
Msrc/memory.rs | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/system.rs | 134+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Msrc/util.rs | 21+++++++++++++++++++++
9 files changed, 1228 insertions(+), 241 deletions(-)

diff --git a/README.org b/README.org @@ -8,6 +8,7 @@ An emulated PC with a RISC II processor. ** Feature completeness - [ ] RISC II emulation - [ ] SDL window, I/O, sound +- [ ] MMU - [ ] Firmware (probably IEEE 1275) implementation - [ ] Floating point co processor - [ ] Multicore diff --git a/src/cpu.rs b/src/cpu.rs @@ -12,11 +12,13 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. +use instruction::ShortSource; +use memory::Memory; use std::convert::TryInto; use std::fmt; -/// The number of register window_regs the RISCII supports. -pub const NUM_WINDOW_REGS: usize = 8; +/// The number of register windows the RISCII supports. +pub const NUM_REG_WINDOWS: usize = 8; /// The number of local registers per window. pub const NUM_LOCALS: usize = 10; /// The number of registers shared with the previous register window (input arguments). @@ -30,18 +32,43 @@ pub const NUM_GLOBALS: usize = 10; /// Number of registers that adding a window adds to the total amount of registers. pub const NUM_ADDED_PER_WINDOW: usize = NUM_LOCALS + NUM_SHARED_NEXT; /// Number of general purpose registers that exist in window_regs. -pub const NUM_WINDOW_REGISTERS: usize = NUM_WINDOW_REGS * NUM_ADDED_PER_WINDOW; +pub const NUM_WINDOW_REGISTERS: usize = NUM_REG_WINDOWS * NUM_ADDED_PER_WINDOW; /// Number of "special" registers (cwp, swp, sp, etc.). -pub const NUM_SPECIAL_REGISTERS: usize = 5; +pub const NUM_SPECIAL_REGISTERS: usize = 3; /// The total number of registers on the system. pub const TOTAL_NUM_REGISTERS: usize = NUM_SPECIAL_REGISTERS + NUM_GLOBALS + NUM_WINDOW_REGISTERS; /// The size of a register::RegisterFile object (in bytes). -pub const SIZEOF_STATE: usize = TOTAL_NUM_REGISTERS * 4; +pub const SIZEOF_REG_FILE: usize = TOTAL_NUM_REGISTERS * 4; +/// The size of an instruction in bytes. Amount to increment the program counter registers by. +pub const SIZEOF_INSTRUCTION: u32 = 4; // Struct definitions. /// A RISC II 32bit register. type Register = u32; +/// PSW. Contains internal state that is usually opaque to the system. +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct ProcessorStatusWord { + /// Current window pointer (MOD 8). + cwp: u32, + /// Saved window pointer (MOD 8). + swp: u32, + /// If interrupts are enabled. + interrupt_enable_bit: bool, + /// System bit, true if running in privileged state. + system_mode: bool, + /// The previous state of the `system_mode` bit the last time it was changed. + previous_system_mode: bool, + /// Condition codes zero (Z). + cc_zero: bool, + /// Condition code negative (N). + cc_neg: bool, + /// Condition code overflow (V). + cc_overflow: bool, + /// Condition code carry (C). + cc_carry: bool, +} + /// The CPU's register state. #[derive(Debug, Copy, Clone, PartialEq)] pub struct RegisterFile { @@ -55,43 +82,21 @@ pub struct RegisterFile { /// (or last attempted to be executed). When running an interrupt `lstpc` /// holds the address of the instruction that was aborted. lstpc: Register, - /// Current window pointer, index of the currently active window. - cwp: Register, - /// Saved window pointer, the index of the youngest window saved in memory. - swp: Register, /// Global registers. globals: [Register; NUM_GLOBALS], /// Register window stack. window_regs: [Register; NUM_WINDOW_REGISTERS], // TODO test, there should be 138 regs. } -/// Load/Store queries. Specifies the register and the value to set it to (if store). -pub enum LoadStore { - /// Global register. - Global { - /// Which global register. g0 is special and will always return 0, - /// attempts to store to it are ignored. Values above 10 throw an error. - which: u32, - /// Value to set register to (ignored owhen loading). - val: u32, - }, - /// Window register. - Window { - /// Which window register. Values above 22 throw an error. - which: u32, - /// Value to set register to (ignored owhen loading). - val: u32, - }, -} - // Struct implementations. impl RegisterFile { /// Create a 0'd out register window. pub fn new() -> Self { Self { - cwp: 0, - swp: 0, + nxtpc: 0, + pc: 0, + lstpc: 0, globals: [0; NUM_GLOBALS], window_regs: [0; NUM_WINDOW_REGISTERS], } @@ -100,24 +105,20 @@ impl RegisterFile { /// Create a register state from a buffer. /// # Arguments /// * `buf` - A byte buffer that is the size of the sum of of register::RegisterFile's - /// members (in bytes) (see `SIZEOF_STATE`). + /// members (in bytes) (see `SIZEOF_REG_FILE`). /// The registers should appear in the following order: /// - NXTPC /// - PC /// - LSTPC - /// - CWP - /// - SWP /// - Global registers /// - Window registers - pub fn from_buf(buf: [u8; SIZEOF_STATE]) -> Self { + pub fn from_buf(buf: [u8; SIZEOF_REG_FILE]) -> Self { // Offset used for gloabls and window_regs. let mut cur_offset = NUM_SPECIAL_REGISTERS * 4; Self { nxtpc: u32::from_be_bytes(buf[..4].try_into().unwrap()), pc: u32::from_be_bytes(buf[4..8].try_into().unwrap()), - lstpc: u32::from_be_bytes(buf[8..12].try_into().unwrap()), - cwp: u32::from_be_bytes(buf[12..16].try_into().unwrap()), - swp: u32::from_be_bytes(buf[16..20].try_into().unwrap()), + lstpc: u32::from_be_bytes(buf[8..cur_offset].try_into().unwrap()), globals: { let mut result = [0u32; NUM_GLOBALS]; for i in 0..result.len() { @@ -142,24 +143,29 @@ impl RegisterFile { } /// Convert self to a byte buffer of all of the register values. - pub fn to_buf(&self) -> [u8; SIZEOF_STATE] { - let mut result = [0u8; SIZEOF_STATE]; - result[0..4].copy_from_slice(&self.cwp.to_be_bytes()); - result[4..8].copy_from_slice(&self.swp.to_be_bytes()); + pub fn to_buf(&self) -> [u8; SIZEOF_REG_FILE] { + let mut result = [0u8; SIZEOF_REG_FILE]; + // Offset of the special registers to the general purpose registers (bytes). + const SPECIAL_OFFSET: usize = NUM_SPECIAL_REGISTERS * 4; + result[..4].copy_from_slice(&self.nxtpc.to_be_bytes()); + result[4..8].copy_from_slice(&self.pc.to_be_bytes()); + result[8..SPECIAL_OFFSET].copy_from_slice(&self.lstpc.to_be_bytes()); let globals = { let mut tmp = [0u8; NUM_GLOBALS * 4]; for i in 0..NUM_GLOBALS { - tmp[i * 4..i * 4 + 4].copy_from_slice(&self.globals[i].to_be_bytes()); + tmp[i * SPECIAL_OFFSET..i * SPECIAL_OFFSET + 4] + .copy_from_slice(&self.globals[i].to_be_bytes()); } tmp }; + const GLOBAL_OFFSET: usize = NUM_SPECIAL_REGISTERS + NUM_GLOBALS * 4; + result[NUM_SPECIAL_REGISTERS..GLOBAL_OFFSET].copy_from_slice(&globals); - const GLOBAL_OFFSET: usize = 8 + NUM_GLOBALS * 4; - result[8..GLOBAL_OFFSET].copy_from_slice(&globals); let win_regs = { let mut tmp = [0u8; NUM_WINDOW_REGISTERS * 4]; for i in 0..NUM_WINDOW_REGISTERS { - tmp[i * 4..i * 4 + 4].copy_from_slice(&self.window_regs[i].to_be_bytes()); + tmp[i * SPECIAL_OFFSET..i * SPECIAL_OFFSET + 4] + .copy_from_slice(&self.window_regs[i].to_be_bytes()); } tmp }; @@ -168,88 +174,91 @@ impl RegisterFile { result } - /// Push the register window stack. Increment CWP by 1 and flush the bottom - /// windows to memory if necessary and change SWP. - pub fn push_reg_window(&mut self) { - self.cwp += 1; - while self.cwp >= self.swp { - // TODO save the top window_regs into memory. - self.swp += 1; - } - } - - /// Pop the register window stack. Decrement CWP by 1 and pull the bottom - /// windows from memory if necessary and change SWP. - pub fn pop_reg_window(&mut self) { - self.cwp -= 1; - while self.swp >= self.cwp { - // TODO load window_regs from memory. - self.swp -= 1; - } + /// Flush entire register window to memory. + /// # Arguments + /// * `mem` - Memory to flush to. + /// * `addr` - Memory address to flush to. + pub fn flush_to_mem(&self, mem: &mut Memory, addr: u32) { + mem.write_buf(addr, &self.to_buf()); } - /// Load from a register (unsigned). Return the register's value + /// Get a register's value (unsigned). Return the register's value /// on success and a string message on error. + /// Register mapping: [0-9] -> Globals + /// [10-15] -> Outs + /// [16-25] -> Locals + /// [31-26] -> Ins + /// Anything outside this [0-31] range is an invalid argument. /// # Arguments - /// * `ls` - Load/Store instruction. Will error if `which` is out of range. - pub fn load_u(&self, ls: LoadStore) -> Result<u32, String> { - type LS = LoadStore; - Ok(match ls { - LS::Global { which: rd, val: _ } => { - if rd <= NUM_GLOBALS { - self.globals[rd] - } else { - return Err(format!("RD for global registers out of range ({})", rd)); - } - } - LS::Window { which: rd, val: _ } => { - let q = NUM_ADDED_PER_WINDOW * self.cwp + rd; - if q < NUM_WINDOW_REGISTERS { - self.window_regs[q] - } else { - return Err(format!( - "RD for window registers out of range ({}), window {} rd{}", - q, self.cwp, rd - )); - } - } + /// * `which` - Which register. [0-31] are the only valid values. + pub fn ru(&self, which: u32, psw: &ProcessorStatusWord) -> Result<u32, String> { + Ok(match which { + 0..=9 => self.globals[which], + 10..=31 => self.window_regs[NUM_ADDED_PER_WINDOW * psw.get_cwp() + rd], + _ => return Err(format!("Register {} is out of range", which)), }) } - /// Load from a register (signed). Return the register's value + /// Get a register's value (signed). Return the register's value /// on success and a string message on error. + /// Register mapping: [0-9] -> Globals + /// [10-15] -> Outs + /// [16-25] -> Locals + /// [31-26] -> Ins + /// Anything outside this [0-31] range is an invalid argument. /// # Arguments - /// * `ls` - Load/Store instruction. `val` is ignored. - pub fn load_s(&self, ls: LoadStore) -> Result<i32, String> { - self.load_u(ls)? as i32 + /// * `which` - Which register. [0-31] are the only valid values. + pub fn rs(&self, which: u32, psw: &ProcessorStatusWord) -> Result<i32, String> { + Ok(self.ru(which, psw)? as i32) } - /// Store to a register. Return void on success and a string message on - /// failure. + /// Set a register's value (unsigned). Return the register's value on + /// success and a string message on error. + /// Register mapping: [0-9] -> Globals + /// [10-15] -> Outs + /// [16-25] -> Locals + /// [31-26] -> Ins + /// Anything outside this [0-31] range is an invalid argument. /// # Arguments - /// * `ls` - Load/Store instruction. Will error if `which` is out of range. - pub fn store(&mut self, ls: LoadStore) -> Result<(), String> { - type LS = LoadStore; - Ok(match ls { - LS::Global { which: rd, val: v } => { - if rd <= NUM_GLOBALS && rd > 0 { - self.globals[rd] = val; - } else if rd == 0 { - } else { - return Err(format!("RD for global registers out of range ({})", rd)); - } - } - LS::Window { which: rd, val: v } => { - let q = NUM_ADDED_PER_WINDOW * self.cwp + rd; - if q < NUM_WINDOW_REGISTERS { - self.window_regs[q] = v; - } else { - return Err(format!( - "RD for window registers out of range ({}), window {} rd{}", - q, self.cwp, rd - )); - } - } + /// * `which` - Which register. [0-31] are the only valid values. + pub fn rus(&mut self, which: u32, value: u32, psw: ProcessorStatusWord) -> Result<u32, String> { + match which { + 0..=9 => self.globals[which] = value, + 10..=31 => self.window_regs[NUM_ADDED_PER_WINDOW * psw.get_cwp() + rd] = value, + _ => return Err(format!("Register {} is out of range", which)), + } + Ok(value) + } + + pub fn get_last_pc(&self) -> u32 { + self.lstpc + } + + pub fn get_pc(&self) -> u32 { + self.pc + } + + pub fn get_next_pc(&self) -> u32 { + self.nxtpc + } + + pub fn inc_pcs(&mut self) { + self.lstpc = self.pc; + self.pc = self.nxtpc; + self.nxtpc += SIZEOF_INSTRUCTION; + } + + pub fn branch_to(&mut self, to: u32) { + self.lstpc = self.pc; + self.pc = nxtpc; + self.nxtpc = to; + } + + pub fn get_ss_val(&self, ss: ShortSource) -> Result<u32, String> { + type SS = ShortSource; + Ok(match ss { + SS::Reg(r) => self.ru(r as u32)?, + SS::Imm13(u) => u, }) } } @@ -261,11 +270,230 @@ impl fmt::Display for RegisterFile { "Next PC: 0x{:x} PC: 0x{:x} Last PC: 0x{:x} -Current window pointer: {} -Saved window pointer: {} Globals: {:?} Window: {:?}", - self.nxtpc, self.pc, self.lstpc, self.cwp, self.swp, self.globals, self.window_regs + self.nxtpc, self.pc, self.lstpc, self.globals, self.window_regs ) } } + +impl ProcessorStatusWord { + /// Create a 0'd out PSW. + pub fn new() -> Self { + Self { + cwp: 0, + swp: 0, + interrupt_enable_bit: false, + system_mode: false, + previous_system_mode: false, + cc_zero: false, + cc_neg: false, + cc_overflow: false, + cc_carry: false, + } + } + + pub fn from_u32(v: u32) -> Self { + Self { + cwp: (v & (0x7 << 7)) >> 7, + swp: (v & (0x7 << 10)) >> 10, + interrupt_enabled_bit: v & (0x1 << 6) != 0, + previous_system_bit: v & (0x1 << 5) != 0, + system_bit: v & (0x1 << 4) != 0, + cc_zero: v & (0x1 << 3) != 0, + cc_neg: v & (0x1 << 2) != 0, + cc_overflow: v & (0x1 << 1) != 0, + cc_carry: v & 0x1 != 0, + } + } + + pub fn init( + cwp: u32, + swp: u32, + interrupt_enable_bit: bool, + previous_system_bit: bool, + system_bit: bool, + cc_zero: bool, + cc_neg: bool, + cc_overflow: bool, + cc_carry: bool, + ) -> Self { + Self { + cwp: cwp % 8, + swp: swp % 8, + interrupt_enable_bit: interrupt_enable_bit, + previous_system_bit: previous_system_bit, + system_bit: system_bit, + cc_zero: cc_zero, + cc_neg: cc_neg, + cc_overflow: cc_overflow, + cc_carry: cc_carry, + } + } + + /// Get the 13 bit PSW value. PSW is the state of the system's special + /// registers and CC's. After the 13th bit PSW is 0 padded. + /// Format of PSW: + /// [0]: Carry bit + /// [1]: Overflow bit + /// [2]: Negative bit + /// [3]: Zero bit + /// [4]: Previous system mode bit. + /// [5]: System mode bit. + /// [6]: Interrupt enable bit. + /// [7-9]: SWP register mod 8. + /// [10-12]: CWP register mod 8. + pub fn to_u32(&self) -> u32 { + (self.cc_carry as u32 + | (self.cc_overflow as u32) << 1 + | (self.cc_neg as u32) << 2 + | (self.cc_zero as u32) << 3 + | (self.previous_system_mode as u32) << 4 + | (self.system_mode as u32) << 5 + | (self.interrupt_enable_bit as u32) << 6 + | (self.cwp) << 7 + | (self.swp) << 10) + & 0x1fff + } + + /// Push the register window stack. Set CWP to CWP-1 MOD 8. Push the top + /// window to memory and increment SWP if necessary. + pub fn push_reg_window(&mut self) { + self.cwp = (self.cwp - 1) % NUM_REG_WINDOWS; + if self.cwp == self.swp { + // TODO save windows to memory. + self.swp = (self.swp + 1) % NUM_REG_WINDOWS; + } + } + + /// Pop the register window stack. Set CWP to CWP+1 MOD 8. Pull the bottom + /// window from memory and decrement SWP if necessary. + pub fn pop_reg_window(&mut self) { + self.cwp = (self.cwp + 1) % NUM_REG_WINDOWS; + if self.cwp == self.swp { + // TODO restore windows from memory. + self.swp = (self.swp - 1) % NUM_REG_WINDOWS; + } + } + + pub fn set_cwp(&mut self, v: u32) { + self.cwp = v % NUM_REG_WINDOWS; + } + + pub fn set_swp(&mut self, v: u32) { + self.swp = v % NUM_REG_WINDOWS; + } + + pub fn get_cwp(&self) -> u32 { + self.cwp + } + + pub fn get_swp(&self) -> u32 { + self.swp + } + + pub fn get_cc_overflow(&self) -> bool { + self.cc_overflow + } + + pub fn get_cc_carry(&self) -> bool { + self.cc_carry + } + + pub fn get_cc_zero(&self) -> bool { + self.cc_zero + } + + pub fn get_cc_neg(&self) -> bool { + self.cc_neg + } + + pub fn set_cc_overflow(&mut self, value: bool) { + self.cc_overflow = value; + } + + pub fn set_cc_carry(&mut self, value: bool) { + self.cc_carry = value; + } + + pub fn set_cc_zero(&mut self, value: bool) { + self.cc_zero = value; + } + + pub fn set_cc_neg(&mut self, value: bool) { + self.cc_neg = value; + } + + pub fn set_system_bit(&mut self, v: bool) { + self.system_mode = v; + } + + pub fn set_previous_system_bit(&mut self, v: bool) { + self.previous_system_mode = v; + } + + pub fn set_interrupt_enabled(&mut self, v: bool) { + self.interrupt_enable_bit = v; + } + + pub fn is_system_mode(&self) -> bool { + self.system_mode + } + + pub fn is_previous_system_mode(&self) -> bool { + self.previous_system_mode + } + + pub fn is_interrupt_enabled(&self) -> bool { + self.interrupt_enable_bit + } +} + +impl fmt::Display for ProcessorStatusWord { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "Current window pointer: {} +Saved window pointer: {} +Interrupts Enabled: {} +System mode: {} +Previous system mode: {} +CC Zero: {} +CC Negative: {} +CC Overflow: {} +CC Carry: {}", + self.cwp, + self.swp, + privilege_string(self.system_mode), + privilege_string(self.previous_system_mode), + bool_hl_string(self.cc_zero), + bool_hl_string(self.cc_neg), + bool_hl_string(self.cc_overflow), + bool_hl_string(self.cc_carry) + ) + } +} + +// Private functions. + +/// Create a descriptive string for the system's privilege state bits. +/// # Arguments +/// * `s` - Privilege state bit. +fn privilege_string(s: bool) -> &str { + if s { + "Privileged" + } else { + "Unprivileged" + } +} + +/// Stringify booleans with hardware terminology. +/// # Arguments +/// * `s` - Boolean. +fn bool_hl_string(s: bool) -> &str { + if s { + "High" + } else { + "Low" + } +} diff --git a/src/decode.rs b/src/decode.rs @@ -65,7 +65,7 @@ pub fn decode(opcode: u32) -> Result<Instruction, DecodeError> { 0 => return Err(DecodeError::InvalidInstruction(0x0f, opcode)), 1 => I::Calli(ShortInstruction::new(scc, dest, rs1, short_source)), 2 => I::GetPSW(ShortInstruction::new(scc, dest, rs1, short_source)), - 3 => I::GetIPC(ShortInstruction::new(scc, dest, rs1, short_source)), + 3 => I::GetLPC(ShortInstruction::new(scc, dest, rs1, short_source)), 4 => I::PutPSW(ShortInstruction::new(scc, dest, rs1, short_source)), 5..=7 => return Err(DecodeError::InvalidInstruction(0x0f, opcode)), 8 => I::Callx(ShortInstruction::new(scc, dest, rs1, short_source)), diff --git a/src/decode_test.rs b/src/decode_test.rs @@ -49,7 +49,7 @@ mod test { fn decode_getipc() -> Result<(), DecodeError> { assert_eq!( decode(0x07293f69)?, - I::GetIPC(ShortInstruction::new(true, 5, 4, SS::UImm13(0x1f69))) + I::GetLPC(ShortInstruction::new(true, 5, 4, SS::UImm13(0x1f69))) ); Ok(()) } @@ -437,7 +437,7 @@ mod test { match *self { I::Calli(o) => format!("Calli {}", o), I::GetPSW(o) => format!("GetPSW {}", o), - I::GetIPC(o) => format!("GetIPC {}", o), + I::GetLPC(o) => format!("GetLPC {}", o), I::PutPSW(o) => format!("GetPSW {}", o), I::Callx(o) => format!("Callx {}", o), I::Callr(o) => format!("Callr {}", o), diff --git a/src/execute.rs b/src/execute.rs @@ -14,54 +14,653 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. +use cpu::ProcessorStatusWord; use instruction::*; use system::System; +use util::U32_MSB; + +// Public structs. + +pub struct ExecResult { + psw: ProcessorStatusWord, + regs: RegisterFile, + was_branch: bool, + psw_delayed: bool, +} // Public functions. -pub fn execute(instruction: &Instruction, system: &System) -> Result<(), String> { +// TODO timing and memory reads/writes. Need to emulate the pipeline and cpu clock. +pub fn execute(instruction: &Instruction, system: &mut System) -> Result<ExecResult, String> { type I = Instruction; + + let mut result = ExecResult::from_system(system); + let mut register_file = result.get_register_file(); + let cur_pc = register_file.get_pc(); + + let mut memory = system.get_mem_ref(); + match *instruction { - I::Calli(si) => {} - I::GetPSW(o) => format!("GetPSW {}", o), - I::GetIPC(o) => format!("GetIPC {}", o), - I::PutPSW(o) => format!("GetPSW {}", o), - I::Callx(o) => format!("Callx {}", o), - I::Callr(o) => format!("Callr {}", o), - I::Jmpx(o) => format!("Jmpx {}", o), - I::Jmpr(o) => format!("Jmpr {}", o), - I::Ret(o) => format!("Ret {}", o), - I::Reti(o) => format!("Reti {}", o), - I::Sll(o) => format!("Sll {}", o), - I::Srl(o) => format!("Srl {}", o), - I::Sra(o) => format!("Sra {}", o), - I::Or(o) => format!("Or {}", o), - I::And(o) => format!("And {}", o), - I::Xor(o) => format!("Xor {}", o), - I::Add(o) => format!("Add {}", o), - I::Addc(o) => format!("Addc {}", o), - I::Sub(o) => format!("Sub {}", o), - I::Subc(o) => format!("Subc {}", o), - I::Subi(o) => format!("Subi {}", o), - I::Subci(o) => format!("Subci {}", o), - I::Ldhi(o) => format!("Ldhi {}", o), - I::Ldxw(o) => format!("Ldxw {}", o), - I::Ldrw(o) => format!("Ldrw {}", o), - I::Ldxhs(o) => format!("Ldxhs {}", o), - I::Ldrhs(o) => format!("Ldrhs {}", o), - I::Ldxhu(o) => format!("Ldxhu {}", o), - I::Ldrhu(o) => format!("Ldrhu {}", o), - I::Ldxbs(o) => format!("Ldxbs {}", o), - I::Ldrbs(o) => format!("Ldrbs {}", o), - I::Ldxbu(o) => format!("Ldxbu {}", o), - I::Ldrbu(o) => format!("Ldxbu {}", o), - I::Stxw(o) => format!("Stxw {}", o), - I::Strw(o) => format!("Strw {}", o), - I::Stxh(o) => format!("Stxh {}", o), - I::Strh(o) => format!("Strh {}", o), - I::Stxb(o) => format!("Stxb {}", o), - I::Strb(o) => format!("Strb {}", o), + I::Calli { + scc: scc, + dest: dest, + rs1: _, + short_source: _, + } => { + if !system.is_system_mode { + // TODO error + } + system.call(); + let lstpc = system.get_last_pc(); + if scc { + system.set_cc_zero(lstpc == 0); + system.set_cc_neg(lstpc & U32_MSB != 0); + } + register_file.rus(dest, lstpc)?; + // TODO maybe handle interrupts. + } + I::GetPSW { + scc: scc, + dest: dest, + rs1: _, + short_source: ss, + } => { + let psw = system.get_psw12() & 0xffff7; + register_file.rus(dest, psw)?; + if scc { + let dest_val = register_file.ru(dest)?; + self.set_cc_neg(dest_val & U32_MSB != 1); + self.set_cc_zero(dest_val == 0); + self.set_cc_carry(false); + self.set_cc_overflow(false); + } + } + I::GetLPC { + scc: scc, + dest: dest, + rs1: _, + short_source: _, + } => { + if !system.is_system_mode { + // TODO error + } + let lstpc = system.get_last_pc(); + register_file.rus(dest, lstpc)?; + if scc { + system.set_cc_zero(lstpc == 0); + system.set_cc_neg(lstpc & U32_MSB != 0); + } + } + I::PutPSW { + scc: scc, + dest: _, + rs1: rs1, + short_source: ss, + } => { + if !system.is_system_mode { + // TODO error + } + if scc { + // TODO error + } + + let val = register_file.get_ss_val(ss)?; + result.set_psw(register_file.ru(rs1)? + val); + } + I::Callx { + scc: _, + dest: dest, + rs1: rs1, + short_source: ss, + } => { + // TODO test alignment (addr[0] == 1). + let rs_val = register_file.ru(rs1)?; + let addr = register_file.get_ss_val(ss)? + rs_val; + register_file.push_reg_window(); + register_file.branch_to(addr); + result.rus(cur_pc)?; + } + I::Callr { + scc: _, + dest: dest, + imm19: imm19, + } => { + // TODO test alignment (addr[0] == 1). + result.set_branch(true); + register_file.push_reg_window(); + register_file.branch_to(cur_pc + imm19); + } + I::Jmpx { + scc: _, + dest: cond, + rs1: rs1, + short_source: ss, + } => { + // TODO test alignment (addr[0] == 1). + if exec_conditional(cond, result.get_psw()) { + result.set_branch(true); + let rs_val = register_file.ru(rs1)?; + let addr = register_file.get_ss_val(ss)? + rs_val; + register_file.branch_to(addr); + } + } + I::Jmpr { + scc: _, + dest: cond, + imm19: imm19, + } => { + if exec_conditional(cond, result.get_psw()) { + result.set_branch(true); + register_file.branch_to(cur_pc + imm19); + } + } + I::Ret { + scc: _, + dest: cond, + rs1: rs1, + short_source: ss, + } => { + if exec_conditional(cond, result.get_psw()) { + result.set_branch(true); + let rs_val = register_file.ru(rs1)?; + register_file.branch_to(rs_val + (SIZEOF_INSTRUCTION * 2)); + register_file.pop_reg_window(); + } + } + I::Reti { + scc: _, + dest: cond, + rs1: rs1, + short_source: ss, + } => { + if !system.is_system_mode { + // TODO error + } + if exec_conditional(cond, result.get_psw()) { + result.set_branch(true); + let rs_val = register_file.ru(rs1)?; + register_file.branch_to(rs_val + (SIZEOF_INSTRUCTION * 2)); + register_file.pop_reg_window(); + } + } + I::Sll { + scc: scc, + dest: dest, + rs1: rs1, + short_source: ss, + } => { + let s1_val = register_file.ru(rs1)?; + let s2_val = register_file.get_ss_val(ss)?; + let d = register_file.rus(dest, s1_val << s2_val)?; + if scc { + set_shift_cc(result.get_psw_ref(), d); + } + } + I::Srl { + scc: scc, + dest: dest, + rs1: rs1, + short_source: ss, + } => { + let s1_val = register_file.ru(rs1)?; + let s2_val = register_file.get_ss_val(ss)?; + let d = register_file.rus(dest, s1_val >> s2_val)?; + if scc { + set_shift_cc(scc, result.get_psw_ref(), d); + } + } + I::Sra { + scc: scc, + dest: dest, + rs1: rs1, + short_source: ss, + } => { + let s1_val = register_file.ru(rs1)?; + let s2_val = register_file.get_ss_val(ss)?; + let d = register_file.rus(dest, s1_val as i32 >> s2_val)?; + if scc { + set_shift_cc(result.get_psw_ref(), d); + } + } + I::Or { + scc: scc, + dest: dest, + rs1: rs1, + short_source: ss, + } => { + let s1_val = register_file.ru(rs1)?; + let s2_val = register_file.get_ss_val(ss)?; + let d = register_file.rus(dest, s1_val | s2_val)?; + if scc { + set_shift_cc(result.get_psw_ref(), d); + } + } + I::And { + scc: scc, + dest: dest, + rs1: rs1, + short_source: ss, + } => { + let s1_val = register_file.ru(rs1)?; + let s2_val = register_file.get_ss_val(ss)?; + let d = register_file.rus(dest, s1_val & s2_val)?; + if scc { + set_shift_cc(result.get_psw_ref(), d); + } + } + I::Xor { + scc: scc, + dest: dest, + rs1: rs1, + short_source: ss, + } => { + let s1_val = register_file.ru(rs1)?; + let s2_val = register_file.get_ss_val(ss)?; + let d = register_file.rus(dest, s1_val ^ s2_val)?; + if scc { + set_shift_cc(result.get_psw_ref(), d); + } + } + I::Add { + scc: scc, + dest: dest, + rs1: rs1, + short_source: ss, + } => { + let s1_val = register_file.ru(rs1)?; + let s2_val = register_file.get_ss_val(ss)?; + let (res, o) = s1_val.overflowing_add(s2_val); + let d = register_file.rus(dest, res)?; + if scc { + let mut psw = result.get_psw_ref(); + set_operator_cc(psw, d); + psw.set_cc_overflow(o); + psw.set_cc_carry(o); + } + } + I::Addc { + scc: scc, + dest: dest, + rs1: rs1, + short_source: ss, + } => { + let s1_val = register_file.ru(rs1)?; + let s2_val = register_file.get_ss_val(ss)?; + let mut psw = result.get_psw_ref(); + let (r1, o1) = s1_val.overflowing_add(s2_val); + let (res, o2) = r1.overflowing_add(psw.get_cc_carry() as u32); + let o = o1 || o2; + let d = register_file.rus(dest, res)?; + if scc { + set_operator_cc(psw, d); + psw.set_cc_overflow(o); + psw.set_cc_carry(o); + } + } + I::Sub { + scc: scc, + dest: dest, + rs1: rs1, + short_source: ss, + } => { + let s1_val = register_file.ru(rs1)?; + let s2_val = register_file.get_ss_val(ss)?; + let (res, o) = s1_val.overflowing_sub(s2_val); + let d = register_file.rus(dest, res)?; + if scc { + let mut psw = result.get_psw_ref(); + set_operator_cc(psw, d); + psw.set_cc_overflow(o); + psw.set_cc_carry(!o); + } + } + I::Subc { + scc: scc, + dest: dest, + rs1: rs1, + short_source: ss, + } => { + let s1_val = register_file.ru(rs1)?; + let s2_val = register_file.get_ss_val(ss)?; + let mut psw = result.get_psw_ref(); + let (r1, o1) = s1_val.overflowing_sub(s2_val); + let (res, o2) = r1.overflowing_sub(!psw.get_cc_carry() as u32); + let o = o2 || o1; + let d = register_file.rus(dest, res); + if scc { + let mut psw = result.get_psw_ref(); + set_operator_cc(psw, d); + psw.set_cc_overflow(o); + psw.set_cc_carry(!o); + } + } + I::Subi { + scc: scc, + dest: dest, + rs1: rs1, + short_source: ss, + } => { + let s1_val = register_file.ru(rs1)?; + let s2_val = register_file.get_ss_val(ss)?; + let (res, o) = s2_val.overflowing_sub(s1_val); + let d = register_file.rus(dest, res)?; + if scc { + let mut psw = result.get_psw_ref(); + set_operator_cc(psw, d); + let v = d > s2_val; + psw.set_cc_overflow(v); + psw.set_cc_carry(!v); + } + } + I::Subci { + scc: scc, + dest: dest, + rs1: rs1, + short_source: ss, + } => { + let s1_val = register_file.ru(rs1)?; + let s2_val = register_file.get_ss_val(ss)?; + let mut psw = result.get_psw_ref(); + let (r1, o1) = s2_val.overflowing_sub(s1_val); + let (res, o2) = r1.overflowing_sub(!psw.get_cc_carry() as u32); + let o = o1 || o2; + let d = register_file.rus(dest, res)?; + if scc { + set_operator_cc(psw, d); + psw.set_cc_overflow(o); + psw.set_cc_carry(!o); + } + } + I::Ldhi { + scc: scc, + dest: dest, + imm19: imm19, + } => { + // TODO Test alignment + let cur_d = register_file.ru(dest)?; + let d = register_file.rus(dest, dest & ((imm19 << 13) & 0x1fff))?; + if scc { + set_load_cc(result.get_psw_ref(), d); + } + } + I::Ldxw { + scc: scc, + dest: dest, + rs1: rs1, + short_source: ss, + } => { + // TODO Test alignment + let ss_val = register_file.get_ss_val(ss)?; + let d = register_file.rus(dest, memory.get_word(ss_val)?)?; + if scc { + set_load_cc(result.get_psw_ref(), d); + } + } + I::Ldrw { + scc: scc, + dest: dest, + imm19: imm19, + } => { + let addr = imm19 + regs.get_pc(); + let d = register_file.rus(dest, memory.get_word(ss_val)?)?; + if scc { + set_load_cc(result.get_psw_ref(), d); + } + } + I::Ldxhs { + scc: scc, + dest: dest, + rs1: rs1, + short_source: ss, + } => { + let ss_val = register_file.get_ss_val(ss)?; + let d = register_file.rus(dest, memory.get_hword(ss_val)? as i32 as u32)?; + if scc { + set_load_cc(result.get_psw_ref(), d); + } + } + I::Ldrhs { + scc: scc, + dest: dest, + imm19: imm19, + } => { + let addr = imm19 + regs.get_pc(); + let d = register_file.rus(dest, memory.get_hword(ss_val)? as i32 as u32)?; + if scc { + set_load_cc(result.get_psw_ref(), d); + } + } + I::Ldxhu { + scc: scc, + dest: dest, + rs1: rs1, + short_source: ss, + } => { + let ss_val = register_file.get_ss_val(ss)?; + let d = register_file.rus(dest, memory.get_hword(ss_val)? as u32)?; + if scc { + set_load_cc(result.get_psw_ref(), d); + } + } + I::Ldrhu { + scc: scc, + dest: dest, + imm19: imm19, + } => { + let addr = imm19 + regs.get_pc(); + let d = register_file.rus(dest, memory.get_hword(ss_val)? as u32)?; + if scc { + set_load_cc(result.get_psw_ref(), d); + } + } + I::Ldxbs { + scc: scc, + dest: dest, + rs1: rs1, + short_source: ss, + } => { + let ss_val = register_file.get_ss_val(ss)?; + let d = register_file.rus(dest, memory.get_byte(ss_val)? as i32 as u32)?; + if scc { + set_load_cc(result.get_psw_ref(), d); + } + } + I::Ldrbs { + scc: scc, + dest: dest, + imm19: imm19, + } => { + let addr = imm19 + regs.get_pc(); + let d = register_file.rus(dest, memory.get_byte(ss_val)? as i32 as u32)?; + if scc { + set_load_cc(result.get_psw_ref(), d); + } + } + I::Ldxbu { + scc: scc, + dest: dest, + rs1: rs1, + short_source: ss, + } => { + let ss_val = register_file.get_ss_val(ss)?; + let d = register_file.rus(dest, memory.get_byte(ss_val)? as u32)?; + if scc { + set_load_cc(result.get_psw_ref(), d); + } + } + I::Ldrbu { + scc: scc, + dest: dest, + imm19: imm19, + } => { + let addr = imm19 + regs.get_pc(); + let d = register_file.rus(dest, memory.get_byte(ss_val)? as u32)?; + if scc { + set_load_cc(result.get_psw_ref(), d); + } + } + I::Stxw { + scc: scc, + dest: dest, + rs1: rs1, + short_source: ss, + } => { + if short_source == ShortSource::Reg(_) { + // warn + // return Err("Store instructions should be immediate only (not registers)"); + } + let ss_val = register_file.get_ss_val(ss)?; + let rs1_val = register_file.ru(rs1); + let dest_val = register_file.ru(dest); + memory.set_word(ss_val + rs1_val, dest_val); + if scc { + set_store_cc(result.get_psw_ref()); + } + } + I::Strw { + scc: scc, + dest: dest, + imm19: imm19, + } => { + if short_source == ShortSource::Reg(_) { + // warn + // return Err("Store instructions should be immediate only (not registers)"); + } + let dest_val = register_file.ru(dest); + memory.set_word(register_file.get_pc() + imm19, dest_val); + if scc { + set_store_cc(result.get_psw_ref()); + } + } + I::Stxh { + scc: scc, + dest: dest, + rs1: rs1, + short_source: ss, + } => { + if short_source == ShortSource::Reg(_) { + // warn + // return Err("Store instructions should be immediate only (not registers)"); + } + let ss_val = register_file.get_ss_val(ss)?; + let rs1_val = register_file.ru(rs1); + let dest_val = register_file.ru(dest); + memory.set_hword(ss_val + rs1_val, dest_val as u16); + if scc { + set_store_cc(result.get_psw_ref()); + } + } + I::Strh { + scc: scc, + dest: dest, + imm19: imm19, + } => { + if short_source == ShortSource::Reg(_) { + // warn + // return Err("Store instructions should be immediate only (not registers)"); + } + let dest_val = register_file.ru(dest); + memory.set_hword(register_file.get_pc() + imm19, dest_val as u16); + if scc { + set_store_cc(result.get_psw_ref()); + } + } + I::Stxb { + scc: scc, + dest: dest, + rs1: rs1, + short_source: ss, + } => { + if short_source == ShortSource::Reg(_) { + // warn + // return Err("Store instructions should be immediate only (not registers)"); + } + let ss_val = register_file.get_ss_val(ss)?; + let rs1_val = register_file.ru(rs1); + let dest_val = register_file.ru(dest); + memory.set_byte(ss_val + rs1_val, dest_val as u8); + if scc { + set_store_cc(result.get_psw_ref()); + } + } + I::Strb { + scc: scc, + dest: dest, + imm19: imm19, + } => { + if short_source == ShortSource::Reg(_) { + // warn + // return Err("Store instructions should be immediate only (not registers)"); + } + let dest_val = register_file.ru(dest); + memory.set_byte(register_file.get_pc() + imm19, dest_val as u8); + if scc { + set_store_cc(result.get_psw_ref()); + } + } + } + + Ok(result) +} + +// Struct impls. + +impl ExecResult { + pub fn from_system(system: &System) -> Self { + Self { + psw: system.get_psw(), + register_file: system.copy_register_file(), + was_branch: false, + psw_delayed: false, + } + } + + pub fn set_psw(&mut self, psw: u32) { + self.psw.from_u32(psw); + } + + pub fn get_psw_ref(&mut self) -> &mut ProcessorStatusWord { + &mut self.psw } - Ok(()) + pub fn get_register_file(&mut self) -> &mut RegisterFile { + &mut self.regs + } + + pub fn was_branch(&self) -> bool { + self.was_branch + } + + pub fn set_branch(&mut self, v: bool) { + self.was_branch = v; + } +} + +// Private functions. + +fn exec_conditional(what: Conditional, psw: ProcessorStatusWord) -> bool { + todo!() +} + +fn set_operator_cc(psw: &mut ProcessorStatusWord, dest_val: u32) { + psw.set_cc_zero(register_file.ru(dest)? == 0); + psw.set_cc_neg(register_file.ru(dest)? & U32_MSB != 0); +} + +fn set_shift_cc(psw: &mut ProcessorStatusWord, dest_val: u32) { + set_operator_cc(psw, dest_val); + psw.set_cc_overflow(false); + psw.set_cc_carry(false); +} + +fn set_load_cc(psw: &mut ProcessorStatusWord, dest_val: u32) { + psw.set_cc_carry(false); + psw.set_cc_overflow(false); + psw.set_cc_zero(d == 0); + psw.set_cc_neg(d & U32_MSB != 0); +} + +fn set_store_cc(psw: &mut ProcessorStatusWord) { + psw.set_cc_overflow(false); + psw.set_cc_carry(false); } diff --git a/src/instruction.rs b/src/instruction.rs @@ -59,9 +59,7 @@ pub enum ShortSource { /// Register name. Reg(u8), /// Unsigned 13 bit immediate, 0-padded to 32 bits. - UImm13(u32), - /// Signed 13 bit immediate, Sign-extended to 32 bits. - SImm13(i32), + Imm13(u32), } /// Short instruction format data. @@ -136,32 +134,62 @@ pub enum Instruction { /// Call interrupt. /// Notes: /// - PRIVILEGED INSTRUCTION. + /// - Only meant for use by the interrupt handler. /// - The `RS1` and `RS1` registers are read from the OLD window. /// - The PC instruction saved is the `PC` at the `CALLI`. /// - The `Rd` refers to the destination register in the NEW window. /// - If the change to `CWP` makes it equal to `SWP`: stop execution, - /// generate a trap, and go to address 0x80000020. - /// CWP := CWP - 1 MOD 8, rd := LSTPC; CC's have same rules as getipc. + /// generate a trap, and go to address 0x80000020. + /// CWP := CWP - 1 MOD 8, rd := LSTPC; + /// Iff SCC == true, Z := [LSTPC == 0]; N := LSTPC<31>; V,C := garbage. Calli(ShortInstruction), - /// Get pointer to window. rd := (-1)<31:13> & PSW<12:0>; + /// Get information on the current CPU state and store it in the bottom 13 + /// bits of rd. Set the top 19 bits to 1. + /// Format of PSW: + /// [0]: Carry bit + /// [1]: Overflow bit + /// [2]: Negative bit + /// [3]: Zero bit + /// [4]: Previous system mode bit. + /// [5]: System mode bit. + /// [6]: Interrupt enable bit. + /// [7-9]: SWP register mod 8. + /// [10-12]: CWP register mod 8. + /// Notes: + /// - Previous instruction must have its SCC bit off (for timing reasons?). + /// - shortsource must be a register and r0. + /// rd := (-1)<31:13> & PSW<12:0>; + /// Iff SCC == true, Z := [dest == 0]; N := LSTPC<31>; V,C := 0. GetPSW(ShortInstruction), /// Get the last Program Counter. rd := LSTPC. /// Iff SCC == true, Z := [LSTPC == 0]; N := LSTPC<31>; V,C := garbage. /// Notes: /// - PRIVILEGED INSTRUCTION. - GetIPC(ShortInstruction), + /// - Not transparent to interrupts. + /// - rs1 and short_source are discarded. + GetLPC(ShortInstruction), /// Set PSW. PSW := [rs1 + ShortSource2]<12:0>; + /// Format of PSW. + /// [0]: Carry bit + /// [1]: Overflow bit + /// [2]: Negative bit + /// [3]: Zero bit + /// [4]: Previous system mode bit. + /// [5]: System mode bit. + /// [6]: Interrupt enable bit. + /// [7-9]: SWP register. + /// [10-12]: CWP registerr /// Notes: /// - PRIVILEGED INSTRUCTION. - /// - SCC-bit MUST be false. - /// - The next instruction CANNOT be `CALLX`, `CALLR`, CALLI`, `RET`, `RETI`, - /// i.e. it cannot modify CWP. It also must not set the CC's. + /// - SCC-bit MUST be off. + /// - The next instruction CANNOT be `CALLX`, `CALLR`, `CALLI`, `RET`, `RETI`, + /// i.e. it cannot modify CWP/SWP. It also cannot modify the CC's. /// - Rd is discarded. /// - New PSW is not in effect until AFTER the next cycle following execution /// of this instruction. PutPSW(ShortInstruction), /// Call procedure at `shortSource` + `rs1`. - /// - The `RS1` and `RS1` registers are read from the OLD window. + /// - The `RS1` and `RS2` registers are read from the OLD window. /// - The PC instruction saved is the `PC` at the `CALLI`. /// - The `Rd` refers to the destination register in the NEW window. /// - If the change to `CWP` makes it equal to `SWP`: stop execution, @@ -278,7 +306,7 @@ impl ShortSource { pub fn new(opcode: u32, signed: bool) -> Self { // Short source immediate-mode bottom 13 bits <12-0> or rs1 <4-0>. if opcode & 0x2000 != 0 { - let mut tmp = Self::UImm13(opcode & 0x1fff); + let mut tmp = Self::Imm13(opcode & 0x1fff); if signed { tmp.uimm_to_simm() } else { @@ -293,15 +321,14 @@ impl ShortSource { /// convert it a signed constant. Else, return `self`. pub fn uimm_to_simm(&self) -> Self { match *self { - Self::UImm13(u) => { + Self::Imm13(u) => { if u & 0x1000 != 0 { // Sign-extend the 13 bit value to 32 bits. - Self::SImm13(-(u as i32)) + Self::Imm13((-(u as i32)) as u32) } else { - Self::SImm13(u as i32) + Self::Imm13(u) } } - Self::SImm13(s) => *self, Self::Reg(r) => *self, } } @@ -311,8 +338,7 @@ impl fmt::Display for ShortSource { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Self::Reg(r) => write!(f, "Reg {}", r), - Self::UImm13(u) => write!(f, "U{}", u), - Self::SImm13(i) => write!(f, "S{}", i), + Self::Imm13(u) => write!(f, "U{}", u), } } } @@ -321,8 +347,7 @@ impl LowerHex for ShortSource { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Self::Reg(r) => write!(f, "Register {:x}", r), - Self::UImm13(u) => write!(f, "(UImm) {:x}", u), - Self::SImm13(i) => write!(f, "(SImm) {:x}", i), + Self::Imm13(u) => write!(f, "(UImm) {:x}", u), } } } diff --git a/src/memory.rs b/src/memory.rs @@ -15,11 +15,11 @@ // Struct definitions. -use util::File; - use config::Config; +use util::{check_hword_alignment, check_word_alignment, File}; /// The real memory of the RISC II emulator. +#[derive(Debug, Clone, Copy)] pub struct Memory(Vec<u8>); // Struct impls. @@ -43,4 +43,97 @@ impl Memory { file.write_vec(&self.0)?; Ok(()) } + + pub fn write_buf(&mut self, addr: u32, buf: &[u8]) { + self[addr..buf.len()].copy_from_slice(buf); + } + + pub fn get_byte(&self, addr: u32) -> Result<u8, String> { + if addr >= self.len() { + Err(format!( + "Memory read: address 0x{:x} is out range (memory is of size 0x{:x})", + addr, + self.len() + )) + } else { + Ok(self.0[addr]) + } + } + + pub fn get_hword(&self, addr: u32) -> Result<u16, String> { + check_hword_alignment(addr)?; + if addr >= self.len() { + Err(format!( + "Memory read: address 0x{:x} is out range (memory is of size 0x{:x})", + addr, + self.len() + )) + } else { + Ok(u16::from_be_bytes(self.0[addr..addr + 1])) + } + } + + pub fn get_word(&self, addr: u32) -> Result<u32, String> { + check_word_alignment(addr)?; + if addr >= self.len() { + Err(format!( + "Memory read: address 0x{:x} is out range (memory is of size 0x{:x})", + addr, + self.len() + )) + } else { + Ok(self.0[addr]) + } + } + + pub fn set_word(&mut self, addr: u32, what: u32) -> Result<u32, String> { + check_word_alignment()?; + if addr >= self.len() - 4 { + Err(format!( + "Memory write: address 0x{:x} is out range (memory is of size 0x{:x})", + addr, + self.len() + )) + } else { + let what_bytes = if cfg!(target_endian = "little") { + u32::from_ne_bytes(what.swap_bytes()) + } else { + u32::from_ne_bytes(what) + }; + self.0[addr..addr + 4].copy_from_slice(what_bytes); + Ok(what) + } + } + + pub fn set_hword(&mut self, addr: u32, what: u16) -> Result<u32, String> { + check_word_alignment()?; + if addr >= self.len() - 2 { + Err(format!( + "Memory write: address 0x{:x} is out range (memory is of size 0x{:x})", + addr, + self.len() + )) + } else { + let what_bytes = if cfg!(target_endian = "little") { + u16::from_ne_bytes(what.swap_bytes()) + } else { + u16::from_ne_bytes(what) + }; + self.0[addr..addr + 2].copy_from_slice(what_bytes); + Ok(what) + } + } + + pub fn set_byte(&mut self, addr: u32, what: u8) -> Result<u32, String> { + if addr >= self.len() { + Err(format!( + "Memory write: address 0x{:x} is out range (memory is of size 0x{:x})", + addr, + self.len() + )) + } else { + self.0[addr] = what; + Ok(what) + } + } } diff --git a/src/system.rs b/src/system.rs @@ -15,28 +15,19 @@ // along with this program. If not, see <https://www.gnu.org/licenses/>. use config::Config; -use cpu::RegisterFile; +use cpu::{ProcessorStatusWord, RegisterFile}; use memory::Memory; use std::fmt; /// RISC II emulated system. +#[derive(Debug, Copy, Clone, PartialEq)] pub struct System { /// RISC II register file. regs: RegisterFile, + /// Processor status. + psw: ProcessorStatusWord, /// Memory state. mem: Memory, - /// System bit, true if running in privileged state. - system_mode: bool, - /// The previous state of the `system_mode` bit the last time it was changed. - previous_system_mode: bool, - /// Condition codes zero (Z). - cc_zero: bool, - /// Condition code negative (N). - cc_neg: bool, - /// Condition code overflow (V). - cc_overflow: bool, - /// Condition code carry (C). - cc_carry: bool, } // Impls. @@ -49,59 +40,88 @@ impl System { pub fn new(config: &Config) -> Result<Self, String> { Ok(Self { regs: RegisterFile::new(), + psw: ProcessorStatusWord::new(), mem: Memory::new(config), - system_mode: true, - previous_system_mode: false, - cc_zero: false, - cc_neg: false, - cc_overflow: false, - cc_carry: false, }) } -} -impl fmt::Display for System { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "CPU register state: \n{} -Privilege level: {} -Previous privilege level: {} -CC Zero: {} -CC Neg: {} -CC Overflow: {} -CC Carry: {}", - self.regs, - privilege_string(self.system_mode), - privilege_string(self.previous_system_mode), - bool_hl_string(self.cc_zero), - bool_hl_string(self.cc_neg), - bool_hl_string(self.cc_overflow), - bool_hl_string(self.cc_carry) - ) + /// Get the 13 bit PSW value. PSW is the state of the system's special + /// registers and CC's. After the 13th bit PSW is 0 padded. + /// Format of PSW: + /// [0]: Carry bit + /// [1]: Overflow bit + /// [2]: Negative bit + /// [3]: Zero bit + /// [4]: Previous system mode bit. + /// [5]: System mode bit. + /// [6]: Interrupt enable bit. + /// [7-9]: SWP register mod 8. + /// [10-12]: CWP register mod 8. + pub fn get_psw_as_u32(&self) -> u32 { + self.psw.to_u32() } -} -// Private functions. + pub fn call(&mut self, addr: u32) { + self.regs.push_reg_window(); + } -/// Create a descriptive string for the system's privilege state bits. -/// # Arguments -/// * `s` - Privilege state bit. -fn privilege_string(s: bool) -> &str { - if s { - "Privileged" - } else { - "Unprivileged" + pub fn ret(&mut self) { + self.regs.pop_reg_window(); + } + + pub fn get_register_file(&mut self) -> &mut RegisterFile { + &mut self.regs + } + + pub fn copy_register_file(&self) -> RegisterFile { + self.regs + } + + pub fn get_last_pc(&self) -> u32 { + self.regs.get_last_pc() + } + + pub fn get_pc(&self) -> u32 { + self.regs.get_pc() + } + + pub fn get_next_pc(&self) -> u32 { + self.regs.get_next_pc() + } + + pub fn integrate_system_changes(&mut self, other: &System) { + self.regs = other.regs; + self.psw = other.psw; + } + + pub fn get_psw(&self) -> ProcessorStatusWord { + self.psw + } + + pub fn set_psw(&mut self, psw: u32) { + self.psw = ProcessorStatusWord::from_u32(psw); + } + + pub fn copy_no_mem(&self) -> Self { + System { + regs: self.regs, + psw: self.psw, + mem: vec![0; 0], + } + } + + pub fn get_mem_ref(&mut self) -> &mut Memory { + &mut self.memory } } -/// Stringify booleans with hardware terminology. -/// # Arguments -/// * `s` - Boolean. -fn bool_hl_string(s: bool) -> &str { - if s { - "High" - } else { - "Low" +impl fmt::Display for System { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "CPU register state:\n{} +Processor Status Word:\n{}", + self.regs, self.psw, + ) } } diff --git a/src/util.rs b/src/util.rs @@ -21,6 +21,11 @@ use std::io::{Read, Write}; use std::path::Path; use std::time::{Duration, SystemTime, SystemTimeError, UNIX_EPOCH}; +// Public constants. + +/// Most significant bit of a u32. +pub const U32_MSB: u32 = 0x80000000; + // Public struct definitions. /// File object. Wrapper around fs::File but caches more data. @@ -97,6 +102,22 @@ pub fn get_home_nofail() -> String { } } +pub fn check_hword_alignment(addr: u32) -> Result<(), String> { + if addr & 0x1 != 0 { + Err(format!("Bad half word alignment: 0x{:x}", addr)) + } else { + Ok(()) + } +} + +pub fn check_word_alignment(addr: u32) -> Result<(), String> { + if addr & 0x3 != 0 { + Err(format!("Bad word alignment: 0x{:x}", addr)) + } else { + Ok(()) + } +} + // Struct impls. impl File {