riscii

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

commit 338d24a611c0c8f6be6f04d18b03015b02d1132d
parent a75c3cb341f967507f918ba46dfc03e9eb0ec2e4
Author: Ryan Jeffrey <ryan@ryanmj.xyz>
Date:   Mon, 13 Jun 2022 00:59:25 -0700

Refactor register to cpu, register file struct, more local registers,
system object, and format printing

Diffstat:
Asrc/cpu.rs | 262+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/execute.rs | 46+++++++++++++++++++++++++++++++++++++++++++++-
Msrc/main.rs | 27+++++----------------------
Dsrc/register.rs | 145-------------------------------------------------------------------------------
Asrc/system.rs | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 408 insertions(+), 168 deletions(-)

diff --git a/src/cpu.rs b/src/cpu.rs @@ -0,0 +1,262 @@ +// RISC II register system. +// (C) Ryan Jeffrey <ryan@ryanmj.xyz>, 2022 +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. + +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. + +// 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 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 local registers per window. +pub const NUM_LOCALS: usize = 10; +/// The number of registers shared with the previous register window (input arguments). +pub const NUM_SHARED_PREV: usize = 6; +/// The number of registers shared with the next register window (output arguments). +pub const NUM_SHARED_NEXT: usize = 6; +/// The number of registers per window. +pub const WINDOW_SIZE: usize = NUM_LOCALS + NUM_SHARED_PREV + NUM_SHARED_NEXT; +/// Number of global registers. +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; +/// Number of "special" registers (cwp, swp, sp, etc.). +pub const NUM_SPECIAL_REGISTERS: usize = 5; +/// 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; +// Struct definitions. + +/// A RISC II 32bit register. +type Register = u32; + +/// The CPU's register state. +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct RegisterFile { + /// Next program counter, holds the address of the instruction being + /// fetched for the next cycle. + nxtpc: Register, + /// Program counter, holds the address of current instruction being + /// executed. + pc: Register, + /// The lastpc, holds the address of the last executed instruction + /// (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. +} + +/// +pub enum LoadStore { + NXTPC { val: u32 }, + PC { val: u32 }, + LSTPC { val: u32 }, + CWP { val: u32 }, + SWP { val: u32 }, + Global { which: u32, val: u32 }, + Window { which: u32, val: u32 }, +} + +// Struct implementations. + +impl RegisterFile { + /// Create a 0'd out register window. + pub fn new() -> Self { + Self { + cwp: 0, + swp: 0, + globals: [0; NUM_GLOBALS], + window_regs: [0; NUM_WINDOW_REGISTERS], + } + } + + /// Create a register state from a buffer. + /// # Arguments + /// * `buffer` - A byte buffer that is the size of the sum of of register::RegisterFile's + /// members (in bytes) (see `SIZEOF_STATE`). + /// 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 { + // 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()), + globals: { + let mut result = [0u32; NUM_GLOBALS]; + for i in 0..result.len() { + result[i] = + u32::from_be_bytes(buf[cur_offset..cur_offset + 4].try_into().unwrap()); + cur_offset += 4; + } + // Ensure r0 is 0. + result[0] = 0; + result + }, + window_regs: { + let mut result = [0u32; NUM_WINDOW_REGISTERS]; + for i in 0..result.len() { + result[i] = + u32::from_be_bytes(buf[cur_offset..cur_offset + 4].try_into().unwrap()); + cur_offset += 4; + } + result + }, + } + } + + 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()); + 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 + }; + + 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 + }; + + result[GLOBAL_OFFSET..].copy_from_slice(&win_regs); + result + } + + 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; + } + } + + pub fn pop_reg_window(&mut self) { + self.cwp -= 1; + while self.swp >= self.cwp { + // TODO load window_regs from memory. + self.swp -= 1; + } + } + + pub fn load(&self, ls: LoadStore) -> Result<u32, String> { + type LS = LoadStore; + Ok(match ls { + LS::NXTPC { val: _ } => self.nxtpc, + LS::PC { val: _ } => self.pc, + LS::LSTPC { val: _ } => self.lstpc, + LS::CWP { val: _ } => self.cwp, + LS::SWP { val: _ } => self.swp, + 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 + )); + } + } + }) + } + + pub fn store(&mut self, ls: LoadStore) -> Result<(), String> { + type LS = LoadStore; + Ok(match ls { + LS::NXTPC { val: v } => { + self.nxtpc = v; + } + LS::PC { val: v } => { + self.pc = v; + } + LS::LSTPC { val: v } => { + self.lstpc = v; + } + LS::CWP { val: v } => { + self.cwp = v; + } + LS::SWP { val: v } => { + self.swp = v; + } + 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 + )); + } + } + }) + } +} + +impl fmt::Display for RegisterFile { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "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 + ) + } +} diff --git a/src/execute.rs b/src/execute.rs @@ -15,9 +15,53 @@ // along with this program. If not, see <https://www.gnu.org/licenses/>. use instruction::*; +use system::System; // Public functions. -pub fn execute(instruction: Instruction) -> Result<(), String> { +pub fn execute(instruction: &Instruction, system: &System) -> Result<(), String> { + type I = Instruction; + 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), + } + Ok(()) } diff --git a/src/main.rs b/src/main.rs @@ -32,23 +32,17 @@ use decode::decode_file; use std::fs; use config::Config; -use memory::Memory; -use register::State; +use system::System; // Struct/enum declarations. -struct System { - regs: register::State, - mem: memory::Memory, -} - fn get_program(path: &String) -> Result<Vec<u8>, String> { println!("Opening binary file {}.", path); - Ok(match fs::read(path) { - Ok(raw_p) => raw_p.to_vec(), - Err(raw_e) => return Err(raw_e.to_string()), - }) + match fs::read(path) { + Ok(raw_p) => Ok(raw_p.to_vec()), + Err(raw_e) => Err(raw_e.to_string()), + } } fn main() -> Result<(), String> { @@ -64,14 +58,3 @@ fn main() -> Result<(), String> { Ok(()) } - -// Impls. - -impl System { - pub fn new(config: &Config) -> Result<Self, String> { - Ok(Self { - regs: State::new(), - mem: Memory::new(config), - }) - } -} diff --git a/src/register.rs b/src/register.rs @@ -1,145 +0,0 @@ -// RISC II register system. -// (C) Ryan Jeffrey <ryan@ryanmj.xyz>, 2022 -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or (at -// your option) any later version. - -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. - -// 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 std::convert::TryInto; - -/// The number of register window_regs the RISCII supports. -pub const NUM_WINDOW_REGS: 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). -pub const NUM_SHARED_PREV: usize = 6; -/// The number of registers shared with the next register window (output arguments). -pub const NUM_SHARED_NEXT: usize = 6; -/// The number of registers per window. -pub const WINDOW_SIZE: usize = NUM_LOCALS + NUM_SHARED_PREV + NUM_SHARED_NEXT; -/// Number of global registers. -pub const NUM_GLOBALS: usize = 10; -/// Number of general purpose registers that exist in window_regs. -pub const NUM_WINDOW_REGISTERS: usize = NUM_WINDOW_REGS * (NUM_LOCALS + NUM_SHARED_NEXT); -/// Number of "special" registers (cwp, swp, sp, etc.). -pub const NUM_SPECIAL_REGISTERS: usize = 2; -/// 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::State object (in bytes). -pub const SIZEOF_STATE: usize = TOTAL_NUM_REGISTERS * 4; -// Struct definitions. - -/// A RISC II 32bit register. -type Register = u32; - -/// The CPU's register state. -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct State { - /// 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], -} - -// Struct implementations. - -impl State { - /// Create a 0'd out register window. - pub fn new() -> State { - State { - cwp: 0, - swp: 0, - globals: [0; NUM_GLOBALS], - window_regs: [0; NUM_WINDOW_REGISTERS], - } - } - - /// Create a register state from a buffer. - /// # Arguments - /// * `buffer` - A byte buffer that is the size of the sum of of register::State's - /// members (in bytes) (see `SIZEOF_STATE`). - /// The registers should appear in the following order: - /// - CWP - /// - SWP - /// - Global registers - /// - Window registers - pub fn from_buf(buf: [u8; SIZEOF_STATE]) -> Self { - // Offset used for gloabls and window_regs. - let mut cur_offset = NUM_SPECIAL_REGISTERS * 4; - Self { - cwp: u32::from_be_bytes(buf[..4].try_into().unwrap()), - swp: u32::from_be_bytes(buf[4..8].try_into().unwrap()), - globals: { - let mut result = [0u32; NUM_GLOBALS]; - for i in 0..result.len() { - result[i] = - u32::from_be_bytes(buf[cur_offset..cur_offset + 4].try_into().unwrap()); - cur_offset += 4; - } - result - }, - window_regs: { - let mut result = [0u32; NUM_WINDOW_REGISTERS]; - for i in 0..result.len() { - result[i] = - u32::from_be_bytes(buf[cur_offset..cur_offset + 4].try_into().unwrap()); - cur_offset += 4; - } - result - }, - } - } - - 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()); - 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 - }; - - 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 - }; - - result[GLOBAL_OFFSET..].copy_from_slice(&win_regs); - result - } - - 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; - } - } - - pub fn pop_reg_window(&mut self) { - self.cwp -= 1; - while self.swp >= self.cwp { - // TODO load window_regs from memory. - self.swp -= 1; - } - } -} diff --git a/src/system.rs b/src/system.rs @@ -0,0 +1,96 @@ +// RISC II emulated machine state. +// See `decode.rs` for the first step, and `commit.rs` for the third step. +// (C) Ryan Jeffrey <ryan@ryanmj.xyz>, 2022 +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. + +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. + +// 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 config::Config; +use cpu::RegisterFile; +use memory::Memory; +use std::fmt; + +pub struct System { + /// RISC II register file. + regs: RegisterFile, + /// 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. + +impl System { + pub fn new(config: &Config) -> Result<Self, String> { + Ok(Self { + regs: RegisterFile::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) + ) + } +} + +// Private functions. + +fn privilege_string(s: bool) -> String { + if s { + "Privileged".to_string() + } else { + "Unprivileged".to_string() + } +} + +fn bool_hl_string(s: bool) -> String { + if s { + "High".to_string() + } else { + "Low".to_string() + } +}