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:
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,
- )
- }
}