riscii

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

commit 883e60896e55db7a2f512ca411fc1036dbc086d4
parent a448398b21b9cf2ec89bf9d7867baa0b3a001e5a
Author: Ryan Jeffrey <ryan@ryanmj.xyz>
Date:   Fri, 14 Oct 2022 15:58:29 -0700

Finish output pins, move clock, etc..

Diffstat:
Msrc/clock.rs | 58+++++++++++++++++++++++++++++++++++++++++++++++-----------
Msrc/config.rs | 4++++
Msrc/cpu.rs | 47++++++++++++++++++++++++++++++++++++++++++++---
Msrc/data_path.rs | 103+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Msrc/instruction.rs | 27+++++++++++++++++++++++++--
Msrc/main.rs | 1+
Msrc/system.rs | 61++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
7 files changed, 261 insertions(+), 40 deletions(-)

diff --git a/src/clock.rs b/src/clock.rs @@ -14,44 +14,80 @@ // along with this program. If not, see <https://www.gnu.org/licenses/>. use config::Config; +use std::fmt; +use std::time::{Duration, Instant}; + +use crate::system; /// Phases for RISCII's multi (4) phase clock non-overlapping clock. +#[derive(PartialEq, Eq, Clone)] 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, + /// is read and forwarded to the shifter and ALOE. + One = 1, /// 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, + Two = 2, /// 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, + Three = 3, /// 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, + Four = 4, /// Special interrupt phase TODO. - Interrupt, + Interrupt = 5, } #[derive(PartialEq, Eq, Clone)] pub struct Clock { rate: u64, count: u64, - phase: Phase, + last_time: Instant, + seconds_coutner: Duration, } impl Clock { - pub fn tick(clock: &mut Self) { - // TODO cycle accurate clock. + pub fn tick(&mut self, phase: Phase) { + match phase { + Phase::One => { + self.count += 1; + } + _ => {} + } + } + + pub fn tick_and_wait(&mut self, phase: Phase) { + match phase { + Phase::One => { + self.count += 1; + if self.count == self.rate { + self.idle_clock(); + } + } + _ => {} + } } pub fn new(config: &Config) -> Self { Self { - rate: config.clock_rate, + rate: config.get_clock_rate(), count: 0, - phase: Phase::One, + last_time: Instant::now(), + seconds_coutner: Duration::new(0, 0), + } + } + + fn idle_clock(&mut self) { + // Calc curTime - lastTime (in nanoseconds). If less than a second has + // passed, sleep until we've reached that next second. + const ONE_SECOND: Duration = Duration::from_secs(1); + let now = Instant::now(); + let time_passed = now - self.last_time; + if time_passed < ONE_SECOND { + std::thread::sleep(time_passed); + self.last_time = now + time_passed; } } } diff --git a/src/config.rs b/src/config.rs @@ -244,6 +244,10 @@ impl Config { pub fn is_debug_mode(&self) -> bool { self.debug_mode } + + pub fn get_clock_rate(&self) -> u64 { + self.clock_rate + } } // Local functions. diff --git a/src/cpu.rs b/src/cpu.rs @@ -84,6 +84,26 @@ pub struct ProcessorStatusWord(u16); #[derive(Debug, Copy, Clone, PartialEq)] pub struct RegisterFile([u32; NUM_GLOBALS + NUM_WINDOW_REGISTERS]); +/// CPU output pins to memory. +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct OutputPins { + /// 32 bit memory output port. For sending data to memory (address). + pub address: u32, + /// 32 bit memory output port. For sending data to memory (data). + pub data: u32, + /// If the current memory write is a word. + pub width_code_word: bool, + /// If the current memory write is a half word. + pub width_code_half: bool, + /// If the current memory operation is a write (true) or read (false). + pub read_write: bool, + /// If the system is currently in system mode. + pub system_mode: bool, + /// If the read/write data is an instruction (1) or data (0). For writes, + /// it is always data (0). + pub instr_or_data_write: bool, +} + // Struct implementations. impl RegisterFile { @@ -208,9 +228,10 @@ impl RegisterFile { /// [31-26] -> Ins /// Anything outside this [0-31] range is an invalid argument. /// # Arguments - /// * `which` - Which register. [0-31] are the only valid values. - /// * `psw` - Processor status object, contains window information. - pub fn write(&mut self, address: u32, value: u32, cwp: u8) { + /// * `address` - Which register. [0-31] are the only valid values. + /// * `value` - Value to write into the register. + /// * `cwp` - Current window pointer. Used to determine the real address of the register. + pub fn write(&mut self, address: u8, value: u32, cwp: u8) { let addr = address as usize; let ptr = cwp as usize; match addr { @@ -392,6 +413,26 @@ CC Carry: {}", } } +impl OutputPins { + pub fn new() -> Self { + Self { + address: 0, + data: 0, + width_code_half: false, + width_code_word: false, + read_write: false, + system_mode: false, + instr_or_data_write: false, + } + } + + pub fn phase_two_copy(&self, other: &mut Self) { + let addr = other.address; + *other = *self; + other.address = addr; + } +} + // Private functions. /// Create a descriptive string for the system's privilege state bits. diff --git a/src/data_path.rs b/src/data_path.rs @@ -15,7 +15,7 @@ // along with this program. If not, see <https://www.gnu.org/licenses/>. use config::Config; -use cpu::{ProcessorStatusWord, RegisterFile, SIZEOF_INSTRUCTION}; +use cpu::{OutputPins, ProcessorStatusWord, RegisterFile, SIZEOF_INSTRUCTION}; use instruction::noop; use memory::Memory; use std::fmt; @@ -34,18 +34,6 @@ pub struct DataPath { 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, @@ -58,10 +46,42 @@ pub struct DataPath { 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, + /// Pins for communicating with the outside world (memory). + output_pins: OutputPins, + + // Control unit latches and registers. + /// Data from memory. + dimm: u32, + /// Immediate register (for instruction being decoded). + imm1: u32, + /// Immediate register (for currently executing instruction). + imm2: u32, + /// Byte address register, bottom two bits of memory address being accesses. + bar: u8, + /// Destination register address (for instruction being decoded). + rd1: u8, + /// Destination register address (for currently executing instruction). + rd2: u8, + /// Destination register address (for commiting/previous instruction). + rd3: u8, + /// Source register one. + ra: u8, + /// Source register two. + rb: u8, + /// Opcode register (for instruction being decoded). + op1: u8, + /// Opcode register (for currently executing instruction). + op2: u8, + /// SCC flag of the instruction (for instruction being decoded). + scc_flag1: bool, + /// SCC flag of the instruction (for currently executing instruction). + scc_flag2: bool, + /// SCC flag of the instruction (for commiting/previous instruction). + scc_flag3: bool, + /// Immediate flag of the instruction (for instruction being decoded). + imm_flag1: bool, + /// Immediate flag of the instruction (for currently executing instruction). + imm_flag2: bool, } // Impls. @@ -77,21 +97,58 @@ impl DataPath { psw: ProcessorStatusWord::new(), src_latch: 0, dst_latch: 0, - next_instruction: noop(), - rd: 0, + bar: 0, + rd1: 0, + rd2: 0, + rd3: 0, ra: 0, rb: 0, - op: 0, - imm: 0, + op1: 0, + op2: 0, + dimm: 0, + imm1: 0, + imm2: 0, nxtpc: 0, pc: 0, lstpc: 0, pins_in: 0, - pins_out_addr: 0, - pins_out_data: 0, + output_pins: OutputPins::new(), + scc_flag1: false, + scc_flag2: false, + scc_flag3: false, + imm_flag1: false, + imm_flag2: false, }) } + pub fn commit(&mut self) { + let dest_value = self.dst_latch; + let dest_reg = self.rd3; + let cwp = self.psw.get_cwp(); + self.regs.write(dest_reg, dest_value, cwp); + } + + /// Decode the next instruction's (in `self.pins_in`) source registers. + pub fn decode_input_regs(&mut self) { + let next_instruction = self.pins_in; + } + + pub fn set_input_pins(&mut self, value: u32) { + self.pins_in = value; + } + + pub fn get_out_address(&self) -> u32 { + self.output_pins.address + } + + pub fn get_out_data(&self) -> u32 { + self.output_pins.data + } + + pub fn get_output_pins_ref(&self) -> &OutputPins { + &self.output_pins + } + fn increment_pcs(&mut self) { self.lstpc = self.pc; self.pc = self.nxtpc; diff --git a/src/instruction.rs b/src/instruction.rs @@ -1,4 +1,5 @@ // RISC II cpu instruction info. + // "execute" and then "commit". // (C) Ryan Jeffrey <ryan@ryanmj.xyz>, 2022 // This program is free software: you can redistribute it and/or modify @@ -14,8 +15,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 clock::Phase; +use data_path::DataPath; use std::fmt; use std::fmt::LowerHex; +use std::ops::Fn; + +use crate::data_path; pub const SCC_LOC: u32 = 0x1000000; pub const DEST_LOC: u32 = 0x00F80000; @@ -25,8 +31,12 @@ pub const SHORT_SOURCE_TYPE_LOC: u32 = 0x2000; // Public functions. -pub fn noop() -> u32 { - Instruction::And(ShortInstruction::new(false, 0, 0, ShortSource::Imm13(0))).encode() +// Enums and structs. + +pub struct MicroOperation(fn(data_path: &mut DataPath) -> Self); + +pub fn noop(dp: &mut DataPath) -> MicroOperation { + MicroOperation::new(noop) } /// Types of conditionals the RISC II supports. @@ -572,6 +582,19 @@ impl fmt::Display for Conditional { } } +impl MicroOperation { + pub fn new(func: fn(data_path: &mut DataPath) -> Self) -> Self { + Self { 0: func } + } + + // TODO temporary until implementing Fn becomes stable. + pub fn call(&self, data_path: &mut DataPath) -> Self { + self.0(data_path) + } +} + +// Static functions. + fn get_opdata_from_cond(cond: Conditional) -> u8 { type C = Conditional; match cond { diff --git a/src/main.rs b/src/main.rs @@ -25,6 +25,7 @@ mod encode_test; mod main_test; // Modules declared as pub to shut up rust-analyzer about dead code. +pub mod clock; pub mod config; pub mod cpu; pub mod data_path; diff --git a/src/system.rs b/src/system.rs @@ -14,27 +14,86 @@ // 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 clock::Clock; use config::Config; +use cpu::OutputPins; use data_path::DataPath; +use instruction::{noop, MicroOperation}; use memory::Memory; use util::Result; +use crate::clock::Phase; + pub struct System { /// RISCII data path. data_path: DataPath, /// Memory state. mem: Memory, + /// External, four phase clock. + clock: Clock, + /// Next micro operation to perform for the currently executing instruction. + op: MicroOperation, + /// Current CPU non-overlapping clock phase. + phase: Phase, + // TODO move below to an MMU emulator. + /// CPU's output pins, input pins for memory. + pins_out: OutputPins, } impl System { pub fn new(config: &Config) -> Result<Self> { + let mut dp = DataPath::new(config)?; + let nop = noop(&mut dp); Ok(Self { - data_path: DataPath::new(config)?, + data_path: dp, mem: Memory::new(config), + clock: Clock::new(config), + op: nop, + phase: Phase::One, + pins_out: OutputPins::new(), }) } pub fn get_mem_ref(&mut self) -> &mut Memory { &mut self.mem } + + pub fn tick(&mut self) { + let cur_phase = self.phase.clone(); + self.clock.tick_and_wait(cur_phase); + + // Fetch + // Execute. + // Commit. + + let dp = &mut self.data_path; + self.phase = match self.phase { + Phase::One => Phase::Two, + Phase::Two => { + dp.get_output_pins_ref().phase_two_copy(&mut self.pins_out); + Phase::Two + } + Phase::Three => { + // Finish read from last cycle. + let mem = &self.mem; + let addr = self.pins_out.address; + // TODO check for invalid address from MMU. + self.data_path.set_input_pins(match mem.get_word(addr) { + Ok(v) => v, + Err(_) => { + eprint!("Bad mem read: {}", addr); + 0 + } + }); + self.data_path.commit(); + Phase::Four + } + Phase::Four => { + dp.decode_input_regs(); + self.pins_out.address = dp.get_out_address(); + Phase::One + } + Phase::Interrupt => Phase::One, + }; + } }