riscii

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

commit a448398b21b9cf2ec89bf9d7867baa0b3a001e5a
parent 88683f9e8accc92605d708e195badaa0a68ce6d0
Author: Ryan Jeffrey <ryan@ryanmj.xyz>
Date:   Thu, 13 Oct 2022 23:46:44 -0700

Move data path and system into their own files, separate data path and memory

Diffstat:
Asrc/clock.rs | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/config.rs | 8++++++++
Msrc/cpu.rs | 3---
Asrc/data_path.rs | 180+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/main.rs | 1+
Msrc/system.rs | 153+++----------------------------------------------------------------------------
6 files changed, 263 insertions(+), 151 deletions(-)

diff --git a/src/clock.rs b/src/clock.rs @@ -0,0 +1,69 @@ +// Clock emulator for RISCII. +// (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; + +/// Phases for RISCII's multi (4) phase clock non-overlapping clock. +pub enum Phase { + /// Phase one of the RISCII's clock. During this phase the register file + /// is read and forwarded to the shifter and ALU. + One, + /// Phase two of the RISCII's clock. During this phase the immediate value + /// is routed through the shifter. The destination register for the previous + /// instruction is decoded. + Two, + /// Phase three of the RISCII's clock. During this phase the ALU computes + /// its result value and the previous instruction's result is written to + /// the destination register. + Three, + /// Phase four of the RISCII's clock. During this phase the source and destination + /// registers are decoded. Load instructions use the shifter to align data. + Four, + /// Special interrupt phase TODO. + Interrupt, +} + +#[derive(PartialEq, Eq, Clone)] +pub struct Clock { + rate: u64, + count: u64, + phase: Phase, +} + +impl Clock { + pub fn tick(clock: &mut Self) { + // TODO cycle accurate clock. + } + + pub fn new(config: &Config) -> Self { + Self { + rate: config.clock_rate, + count: 0, + phase: Phase::One, + } + } +} + +impl fmt::Debug for Clock { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Clock: {}", self) + } +} + +impl fmt::Display for Clock { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}, {}", self.rate, self.count) + } +} diff --git a/src/config.rs b/src/config.rs @@ -44,6 +44,9 @@ pub struct Config { /// Path to the system cache directory. #[serde(default = "default_cache")] cache_path: String, + /// The clock rate (in hertz). + #[serde(default = "default_clock_rate")] + clock_rate: u64, /// Width of the window. #[serde(default = "default_width")] win_width: u32, @@ -80,6 +83,7 @@ impl Config { &config_path, &".config/riscii/config.toml".to_string(), )?, + clock_rate: 0, cache_path: String::new(), win_width: 0, win_height: 0, @@ -345,3 +349,7 @@ fn default_height() -> u32 { fn default_debug_mode() -> bool { true } + +fn default_clock_rate() -> u64 { + 5_000_000 +} diff --git a/src/cpu.rs b/src/cpu.rs @@ -66,9 +66,6 @@ pub const CWP_LOC: u16 = 0x7 << 10; pub const PSW_LOC: u16 = 0x1fff; // Struct definitions. -/// A RISC II 32bit register. -type Register = u32; - // TODO maybe convert this into a u16? /// PSW. Contains internal state that is usually opaque to the system. /// [12:10] -> Current window pointer (CWP). diff --git a/src/data_path.rs b/src/data_path.rs @@ -0,0 +1,180 @@ +// RISC II emulated data path. +// 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::{ProcessorStatusWord, RegisterFile, SIZEOF_INSTRUCTION}; +use instruction::noop; +use memory::Memory; +use std::fmt; +use util::Result; + +use crate::cpu; + +/// RISC II emulated data path. +#[derive(Debug, Clone)] +pub struct DataPath { + /// RISC II register file. + regs: RegisterFile, + /// Processor status. + psw: ProcessorStatusWord, + /// Temporary latch for destination register. + dst_latch: u32, + /// Source latch for the shifter and ALU. + src_latch: u32, + /// Next instruction. + next_instruction: u32, + /// Destination register address. + rd: u8, + /// Source register one. + ra: u8, + /// Source register two. + rb: u8, + /// Opcode register. + op: u8, + /// Immediate register. + imm: u32, + /// Next program counter, holds the address of the instruction being + /// fetched for the next cycle. + nxtpc: u32, + /// Program counter, holds the address of current instruction being + /// executed. + pc: u32, + /// 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: u32, + /// 32 bit memory input pin. For receiving from main memory. + pins_in: u32, + /// 32 bit memory output port. For sending data to memory (address). + pins_out_addr: u32, + /// 32 bit memory output port. For sending data to memory (data). + pins_out_data: u32, +} + +// Impls. + +impl DataPath { + /// Create a new emulated RISC II system. Return system on success and + /// a string on error. + /// # Arguments + /// * `config` - Emulator configuration. + pub fn new(config: &Config) -> Result<Self> { + Ok(Self { + regs: RegisterFile::new(), + psw: ProcessorStatusWord::new(), + src_latch: 0, + dst_latch: 0, + next_instruction: noop(), + rd: 0, + ra: 0, + rb: 0, + op: 0, + imm: 0, + nxtpc: 0, + pc: 0, + lstpc: 0, + pins_in: 0, + pins_out_addr: 0, + pins_out_data: 0, + }) + } + + fn increment_pcs(&mut self) { + self.lstpc = self.pc; + self.pc = self.nxtpc; + self.nxtpc += cpu::SIZEOF_INSTRUCTION; + } + + fn branch_to(&mut self, address: u32) { + self.lstpc = self.pc; + self.pc = self.nxtpc; + self.nxtpc = address; + } + + /// 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.get() as u32 + } + + pub fn call(&mut self, addr: u32) { + self.psw.push(); + } + + pub fn ret(&mut self) { + self.psw.pop(); + } + + 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.lstpc + } + + pub fn get_pc(&self) -> u32 { + self.pc + } + + pub fn get_next_pc(&self) -> u32 { + self.nxtpc + } + + pub fn get_psw(&self) -> ProcessorStatusWord { + self.psw + } + + pub fn set_psw(&mut self, psw: u16) { + self.psw = ProcessorStatusWord::from_u16(psw); + } +} + +// Clock notes: +// Reads happen in phase 1 +// Register decoding happens in phase 2 +// Immediates are routed thru shifter in phase 2 +// Loads can use the shifter in phase 4 for aligning data. + +// f1: register read and int. forwarding. +// f2: routes sources and imm thru shifter, Reg dec, +// f3: register write, ALU +// f4: register dec, shift alignment (for ld) + +impl fmt::Display for DataPath { + 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/main.rs b/src/main.rs @@ -27,6 +27,7 @@ mod main_test; // Modules declared as pub to shut up rust-analyzer about dead code. pub mod config; pub mod cpu; +pub mod data_path; pub mod debug_window; pub mod decode; pub mod instruction; diff --git a/src/system.rs b/src/system.rs @@ -1,4 +1,4 @@ -// RISC II emulated machine state. +// RISC II emulated PC. // 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 @@ -15,169 +15,26 @@ // along with this program. If not, see <https://www.gnu.org/licenses/>. use config::Config; -use cpu::{ProcessorStatusWord, RegisterFile, SIZEOF_INSTRUCTION}; -use instruction::noop; +use data_path::DataPath; use memory::Memory; -use std::fmt; use util::Result; -use crate::cpu; - -/// RISC II emulated data path. -#[derive(Debug, Clone)] pub struct System { - /// RISC II register file. - regs: RegisterFile, - /// Processor status. - psw: ProcessorStatusWord, + /// RISCII data path. + data_path: DataPath, /// Memory state. mem: Memory, - /// Temporary latch for destination register. - dst_latch: u32, - /// Source latch for the shifter and ALU. - src_latch: u32, - /// Next instruction. - next_instruction: u32, - /// Destination register address. - rd: u8, - /// Source register one. - ra: u8, - /// Source register two. - rb: u8, - /// Opcode register. - op: u8, - /// Immediate register. - imm: u32, - /// Next program counter, holds the address of the instruction being - /// fetched for the next cycle. - nxtpc: u32, - /// Program counter, holds the address of current instruction being - /// executed. - pc: u32, - /// 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: u32, } -// Impls. - impl System { - /// Create a new emulated RISC II system. Return system on success and - /// a string on error. - /// # Arguments - /// * `config` - Emulator configuration. pub fn new(config: &Config) -> Result<Self> { Ok(Self { - regs: RegisterFile::new(), - psw: ProcessorStatusWord::new(), + data_path: DataPath::new(config)?, mem: Memory::new(config), - src_latch: 0, - dst_latch: 0, - next_instruction: noop(), - rd: 0, - ra: 0, - rb: 0, - op: 0, - imm: 0, - nxtpc: 0, - pc: 0, - lstpc: 0, }) } - fn increment_pcs(&mut self) { - self.lstpc = self.pc; - self.pc = self.nxtpc; - self.nxtpc += cpu::SIZEOF_INSTRUCTION; - } - - fn branch_to(&mut self, address: u32) { - self.lstpc = self.pc; - self.pc = self.nxtpc; - self.nxtpc = address; - } - - /// 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.get() as u32 - } - - pub fn call(&mut self, addr: u32) { - self.psw.push(); - } - - pub fn ret(&mut self) { - self.psw.pop(); - } - - 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.lstpc - } - - pub fn get_pc(&self) -> u32 { - self.pc - } - - pub fn get_next_pc(&self) -> u32 { - self.nxtpc - } - - pub fn get_psw(&self) -> ProcessorStatusWord { - self.psw - } - - pub fn set_psw(&mut self, psw: u16) { - self.psw = ProcessorStatusWord::from_u16(psw); - } - pub fn get_mem_ref(&mut self) -> &mut Memory { &mut self.mem } - - /// Run for a single clock cycle. - pub fn step(&mut self) { - self.fetch(); - self.execute(); - self.commit(); - } - - fn fetch(&mut self) -> Result<()> { - self.next_instruction = self.mem.get_word(self.nxtpc)?; - Ok(()) - } - - fn execute(&mut self) {} - - fn commit(&mut self) {} -} - -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, - ) - } }