commit 1f5de190da64b59496c50a2c9a2f69b825cc78b6
parent 81a3dd7ab7740519a1546f99521613d5e60163e5
Author: Ryan Jeffrey <ryan@ryanmj.xyz>
Date: Fri, 10 Jun 2022 15:46:08 -0700
Move decoding to its own file, better default handling for config.
Diffstat:
M | src/config.rs | | | 109 | +++++++++++++++++++++++++++++++++++++++++++++++++------------------------------ |
A | src/decode.rs | | | 374 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | src/main.rs | | | 363 | +------------------------------------------------------------------------------ |
M | src/sdl.rs | | | 5 | +++-- |
M | src/util.rs | | | 34 | ++++++++++++++++++++++++++++++++++ |
5 files changed, 482 insertions(+), 403 deletions(-)
diff --git a/src/config.rs b/src/config.rs
@@ -20,7 +20,6 @@ extern crate serde_derive;
extern crate toml;
use std::env;
-use std::ffi::OsString;
use std::fmt;
use std::fs;
use std::path::Path;
@@ -33,44 +32,36 @@ use self::serde_derive::{Deserialize, Serialize};
#[derive(Deserialize)]
pub struct Config {
/// Amount of memory the system will have.
- #[serde(default)]
+ #[serde(default = "default_mem")]
mem: u32,
/// Number of CPUs the system will have.
- #[serde(default)]
+ #[serde(default = "default_ncpu")]
ncpu: u32,
/// Path to the configuration file.
- #[serde(default)]
+ #[serde(skip_deserializing)]
config_file_path: String,
/// Path to the system cache directory.
- #[serde(default)]
+ #[serde(default = "default_cache")]
cache_path: String,
+ /// Width of the window.
+ #[serde(default = "default_width")]
+ win_width: u32,
+ /// Height of the window.
+ #[serde(default = "default_height")]
+ win_height: u32,
}
// Struct impls.
impl Config {
pub fn new() -> Result<Config, String> {
+ let home_dir = util::get_home_nofail();
// Find a configuration path specified on the command line.
- let home_dir = match env::var("HOME") {
- Ok(v) => format!("{}", v),
- Err(e) => {
- eprintln!("$HOME is not set. Defaulting to current directory.");
- format!(
- "{}",
- match env::current_dir() {
- Ok(r) => os_string_result_to_strings(r.into_os_string().into_string())?,
- Err(e) => format!("{}", e),
- }
- )
- }
- };
-
let config_path = match env::var("XDG_CONFIG_HOME") {
Ok(v) => format!("{}", v),
Err(e) => format!("{}", home_dir),
};
- let cache_dir = ".cache/riscii".to_string();
Ok(Config {
mem: 0,
ncpu: 0,
@@ -78,10 +69,9 @@ impl Config {
&config_path,
&".config/riscii/config.toml".to_string(),
)?,
- cache_path: match env::var("XDG_CACHE_HOME") {
- Ok(v) => concat_paths(&v, &cache_dir)?,
- Err(v) => concat_paths(&home_dir, &cache_dir)?,
- },
+ cache_path: String::new(),
+ win_width: 0,
+ win_height: 0,
})
}
@@ -96,16 +86,16 @@ impl Config {
Some(s) => s.to_string(),
};
- config.parse_cmd_args(&args)?;
config.read_config_file()?;
+ config.parse_cmd_args(&args)?;
Ok(config)
}
fn read_config_file(&mut self) -> Result<(), String> {
+ // TODO do not exit if config.toml does not exist
+ // TODO get ~ in paths to expand
// Keep the data we want to survive the assignment.
let config_file_path = self.config_file_path.clone();
- let cache_path = self.cache_path.clone();
- // TODO get ~ in paths to expand
*self = match toml::from_str(&match fs::read_to_string(Path::new(&config_file_path)) {
Err(e) => return Err(format!("Could not read {}, {}", config_file_path, e)),
Ok(r) => r,
@@ -120,9 +110,6 @@ impl Config {
};
self.config_file_path = config_file_path;
- if self.cache_path.is_empty() {
- self.cache_path = cache_path;
- }
Ok(())
}
@@ -130,7 +117,7 @@ impl Config {
fn find_cmd_config_path(&self, args: &Vec<String>) -> Result<Option<String>, String> {
for (i, arg) in args.iter().enumerate() {
match arg.as_str() {
- "--config_file_path" => {
+ "--config_path" => {
return Ok(Some(
args_get_next_arg(&args, i, &format!("config_path"))?.clone(),
))
@@ -162,10 +149,18 @@ impl Config {
self.cache_path = args_get_next_arg(&args, i, &format!("cache_path"))?.clone();
skips += 1;
}
- "--config_file_path" => {
+ "--config_path" => {
args_get_next_arg(&args, i, &format!("config_path"))?;
skips += 1;
}
+ "--win_width" => {
+ self.win_width = args_get_next_uint(&args, i, &format!("win_width"))?;
+ skips += 1;
+ }
+ "--win_height" => {
+ self.win_height = args_get_next_uint(&args, i, &format!("win_height"))?;
+ skips += 1;
+ }
_ => {
println!(
"Usage: riscii [OPTIONS]
@@ -181,6 +176,14 @@ impl Config {
}
Ok(())
}
+
+ pub fn get_win_width(&self) -> u32 {
+ self.win_width
+ }
+
+ pub fn get_win_height(&self) -> u32 {
+ self.win_height
+ }
}
// Local functions.
@@ -227,20 +230,44 @@ impl fmt::Display for Config {
"Number of cpus: {}
Memory (MB): {}
Configuration file: {}
-Cache Directory: {}",
- self.ncpu, self.mem, self.config_file_path, self.cache_path
+Cache Directory: {}
+Window dimensions: ({}, {})",
+ self.ncpu,
+ self.mem,
+ self.config_file_path,
+ self.cache_path,
+ self.win_width,
+ self.win_height
)
}
}
// Local functions.
-fn os_string_result_to_strings(r: Result<String, OsString>) -> Result<String, String> {
- match r {
- Err(e) => Err(match e.into_string() {
- Ok(s) => s,
- Err(ee) => "Could not coerce OS string into utf8 string".to_string(),
- }),
- Ok(rr) => Ok(rr.to_string()),
+// Default functions for serde.
+
+fn default_mem() -> u32 {
+ 64
+}
+
+fn default_ncpu() -> u32 {
+ 1
+}
+
+fn default_cache() -> String {
+ let home_dir = util::get_home_nofail();
+
+ let cache_dir = ".cache/riscii".to_string();
+ match env::var("XDG_CACHE_HOME") {
+ Ok(v) => concat_paths(&v, &cache_dir).unwrap(),
+ Err(v) => concat_paths(&home_dir, &cache_dir).unwrap(),
}
}
+
+fn default_width() -> u32 {
+ 1200
+}
+
+fn default_height() -> u32 {
+ 900
+}
diff --git a/src/decode.rs b/src/decode.rs
@@ -0,0 +1,374 @@
+// RISC-II decoder.
+// (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/>.
+extern crate core;
+
+use core::convert::TryInto;
+
+/// Types of conditionals the RISC II supports.
+pub enum Conditional {
+ /// Greater than.
+ Gt,
+ /// Less than or equal to.
+ Le,
+ /// Greater than or equal to.
+ Ge,
+ /// Less than.
+ Lt,
+ /// Higher than.
+ Hi,
+ /// Lower than or same.
+ Los,
+ /// Lower than no carry.
+ Lonc,
+ /// Higher than, carry.
+ Hisc,
+ /// Plus (test sign).
+ Pl,
+ /// Minus (test sign).
+ Mi,
+ /// Not equal.
+ Ne,
+ /// Equal.
+ Eq,
+ /// Now overflow (signed arithmetic).
+ Nv,
+ /// Overflow (signed arithmetic).
+ V,
+ /// Always (constant 1).
+ Alw,
+}
+
+/// The 'source' of the instruction, which can either be a register name,
+/// or a 13 bit immediate (signed or unsigned).
+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),
+}
+
+/// A RISC-II Instruction.
+/// A RISC-II instruction is in one of two formats: short source and long immediate.
+///
+/// Short source has the following members: (SCC: bool, dest: u8, rs1: u8, short source: u16).
+/// The short source can either be a 13 bit immediate value or a 5 bit register name.
+///
+/// The long immediate format has the following members: (SCC: bool, dest: u8, imm: 19).
+///
+/// For both formats, if SCC is true, then update conditional registers (CC's).
+///
+/// Shift CC's: shift, logical instructions: V := 0; C := 0;
+/// Arithmetic instructions update CC's as follows: Z := [d == 0]; N := d<31>;
+/// Arithmetic's V := [32 bit 2's-complement overflow occurred].
+/// additions: C := carry<31>to<32> (assuming s1, s2: unsigned).
+/// subtractions: C := NOT[borrow<31>to<32>] (for s1, s2: unsigned).
+///
+/// Load instructions: If the instruction has the letter `r` in the name
+/// it is relative to PC (PC + imm19). If it has the letter `x` instead,
+/// the load is register indexed.
+pub enum Instruction {
+ /// Call interrupt.
+ /// Notes:
+ /// - PRIVILEGED INSTRUCTION.
+ /// - 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.
+ Calli(bool, u8),
+ /// Get pointer to window. rd := (-1)<31:13> & PSW<12:0>;
+ GetPSW(bool, u8, u8, ShortSource),
+ /// Get the last Program Counter. rd := LSTPC.
+ /// Iff SCC == true, Z := [LSTPC == 0]; N := LSTPC<31>; V,C := garbage.
+ /// Notes:
+ /// - PRIVILEGED INSTRUCTION.
+ GetIPC(bool, u8, u8, ShortSource),
+ /// Set PSW. PSW := [rs1 + ShortSource2]<12:0>;
+ /// 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.
+ /// - Rd is discarded.
+ /// - New PSW is not in effect until AFTER the next cycle following execution
+ /// of this instruction.
+ PutPSW(bool, u8, u8, ShortSource),
+ /// Call procedure at `shortSource` + `rs1`.
+ /// - 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 := PC; CC's have same rules as getipc.
+ Callx(bool, u8, u8, ShortSource),
+ /// Call procedure at `PC` + `imm19`.
+ /// - 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 := PC; CC's have same rules as getipc.
+ Callr(bool, u8, u32),
+ /// If conditional is true: PC := `rs1` + `shortSource`;
+ Jmpx(bool, Conditional, u8, ShortSource),
+ /// If conditional is true: PC += `imm19`;
+ /// Test alignment: if newPC<0> == 1 then abort instruction and jump
+ /// to 0x80000000.
+ Jmpr(bool, Conditional, u32),
+ /// Return from the current procedure if conditional is true.
+ /// CWP := CWP + 1 MOD 8.
+ /// Notes:
+ /// - `rs1` and `rs1` are read from the OLD window.
+ /// - The usual use case of this instruction is with target address
+ /// `rs1` + 8 (with `rs1`=`rd` of the call).
+ Ret(bool, Conditional, u8, ShortSource),
+ /// Return from interrupt if condition is true.
+ /// CWP := CWP + 1 MOD 8.
+ /// Notes:
+ /// - PRIVILEGED INSTRUCTION.
+ /// - `rs1` and `rs1` are read from the OLD window.
+ /// - The usual use case of this instruction is with target address
+ /// `rs1` + 8 (with `rs1`=`rd` of the call).
+ Reti(bool, Conditional, u8, ShortSource),
+
+ /// Shift left logical.
+ Sll(bool, u8, u8, ShortSource),
+ /// Shift right logical.
+ Srl(bool, u8, u8, ShortSource),
+ /// Shift right arithmetic.
+ Sra(bool, u8, u8, ShortSource),
+
+ /// Bitwise OR.
+ Or(bool, u8, u8, ShortSource),
+ /// Bitwise And.
+ And(bool, u8, u8, ShortSource),
+ /// Bitwise Xor.
+ Xor(bool, u8, u8, ShortSource),
+
+ /// Arithmetic add: d := s1 + s2;
+ Add(bool, u8, u8, ShortSource),
+ /// Arithmetic add with constant: d := s1 + s2 + C;
+ Addc(bool, u8, u8, ShortSource),
+ /// Arithmetic sub: d := s1 - s2; (d := s1 + NOT(s2) + 1)
+ Sub(bool, u8, u8, ShortSource),
+ /// Arithmetic sub with constant: d := s1 - s2 - NOT(C); (d := s1 + NOT(s2) + C)
+ Subc(bool, u8, u8, ShortSource),
+ /// Subtract inverse: d := s2 - s1; (d := s2 + NOT(s1))
+ Subi(bool, u8, u8, ShortSource),
+ /// Subtract inverse with constant: d := s2 - s1 - NOT(C); (d := s2 - s1 - NOT(C))
+ Subci(bool, u8, u8, ShortSource),
+
+ /// Load high: Load 19 bit immediate into top 19 bits of destination register,
+ /// and set the bottom 13 bits to 0.
+ Ldhi(bool, u8, u32),
+ /// Load word, register indexed.
+ Ldxw(bool, u8, u8, ShortSource),
+ /// Load word, long-immediate.
+ Ldrw(bool, u8, u32),
+
+ /// Load half signed, register indexed.
+ Ldxhs(bool, u8, u8, ShortSource),
+ /// Load half signed, long-immediate.
+ Ldrhs(bool, u8, u32),
+ /// Load half unsigned, register indexed.
+ Ldxhu(bool, u8, u8, ShortSource),
+ /// Load half unsigned, long-immediate.
+ Ldrhu(bool, u8, u32),
+
+ /// Load byte signed, register indexed.
+ Ldxbs(bool, u8, u8, ShortSource),
+ /// Load byte signed, long-immediate.
+ Ldrbs(bool, u8, u32),
+ /// Load byte unsigned, register indexed.
+ Ldxbu(bool, u8, u8, ShortSource),
+ /// Load byte unsigned, long-immediate.
+ Ldrbu(bool, u8, u32),
+
+ /// Store word, register indexed.
+ Stxw(bool, u8, u8, ShortSource),
+ /// Store word, long-immediate.
+ Strw(bool, u8, u32),
+
+ /// Store half, register indexed.
+ Stxh(bool, u8, u8, ShortSource),
+ /// Store half, long-immediate.
+ Strh(bool, u8, u32),
+
+ /// Store byte, register indexed.
+ Stxb(bool, u8, u8, ShortSource),
+ /// Store byte, long-immediate.
+ Strb(bool, u8, u32),
+}
+
+pub enum DecodeError {
+ /// Indicates an invalid instruction. The first u32 indicates which bits are invalid,
+ /// the final u32 is the whole opcode.
+ InvalidInstruction(u32, u32),
+ InvalidJumpCondition,
+
+ /// Indicates some bug in this program with a string description.
+ CodeError(String),
+}
+
+/// Get the RISC-II conditional type from a opcode<22-19>.
+/// opcode A RISC-II opcode.
+/// return RISC-II conditional, or DecodeError if 0.
+fn get_cond_from_opcode(opcode: u32) -> Result<Conditional, DecodeError> {
+ type C = Conditional;
+ Ok(match (opcode & 0x780000) >> 18 {
+ 1 => C::Gt,
+ 2 => C::Le,
+ 3 => C::Ge,
+ 4 => C::Lt,
+ 5 => C::Hi,
+ 6 => C::Los,
+ 7 => C::Lonc,
+ 8 => C::Hisc,
+ 9 => C::Pl,
+ 10 => C::Mi,
+ 11 => C::Ne,
+ 12 => C::Eq,
+ 13 => C::Nv,
+ 14 => C::V,
+ 15 => C::Alw,
+ _ => return Err(DecodeError::InvalidJumpCondition),
+ })
+}
+
+pub fn decode(opcode: u32) -> Result<Instruction, DecodeError> {
+ type I = Instruction;
+ // SCC flag (<24>).
+ let scc = opcode & 0x1000000 != 0;
+ // Destination bits (<23-19>).
+ let dest = ((opcode & 0xF80000) >> 18) as u8;
+ // Short-immediate RS1 value (<18-14>).
+ let rs1 = ((opcode & 0x7C000) >> 13) as u8;
+ // Immediate-mode bottom 19 bits <18-0>.
+ let imm19 = opcode & 0x7FFFF;
+ // Short source immediate-mode bottom 13 bits <12-0> or rs1 <4-0>.
+ let short_source = if opcode & 0x2000 != 0 {
+ ShortSource::UImm13(opcode & 0x1fff)
+ } else {
+ ShortSource::Reg((opcode & 0x1f) as u8)
+ }; // TODO fix ambiguous sign problem.
+ // The opcode itself.
+ let op = (opcode & 0xFE) >> 24;
+
+ let cond = get_cond_from_opcode(opcode);
+
+ // Math the opcode's prefix.
+ Ok(match op >> 5 {
+ // Match the bottom four bytes of the opcode's prefix.
+ 0 => match op & 0xF {
+ 0 => return Err(DecodeError::InvalidInstruction(0x0f, opcode)),
+ 1 => I::Calli(scc, dest),
+ 2 => I::GetPSW(scc, dest, rs1, short_source),
+ 3 => I::GetIPC(scc, dest, rs1, short_source),
+ 4 => I::PutPSW(scc, dest, rs1, short_source),
+ 5..=7 => return Err(DecodeError::InvalidInstruction(0x0f, opcode)),
+ 8 => I::Callx(scc, dest, rs1, short_source),
+ 9 => I::Callr(scc, dest, imm19),
+ 10..=11 => return Err(DecodeError::InvalidInstruction(0x0f, opcode)),
+ 12 => I::Jmpx(scc, cond?, rs1, short_source),
+ 13 => I::Jmpr(scc, cond?, imm19),
+ 14 => I::Ret(scc, cond?, rs1, short_source),
+ 15 => I::Reti(scc, cond?, rs1, short_source),
+ // Should never be reached.
+ _ => {
+ return Err(DecodeError::CodeError(String::from(
+ "Match bottom four bytes of opcode prefix",
+ )))
+ }
+ },
+ 1 => match op & 0xF {
+ 0 => return Err(DecodeError::InvalidInstruction(0x0f, opcode)),
+ 1 => I::Sll(scc, dest, rs1, short_source),
+ 2 => I::Sra(scc, dest, rs1, short_source),
+ 3 => I::Srl(scc, dest, rs1, short_source),
+ 4 => I::Ldhi(scc, dest, imm19),
+ 5 => I::And(scc, dest, rs1, short_source),
+ 6 => I::Or(scc, dest, rs1, short_source),
+ 7 => I::Xor(scc, dest, rs1, short_source),
+ 8 => I::Add(scc, dest, rs1, short_source),
+ 9 => I::Addc(scc, dest, rs1, short_source),
+ 10..=11 => return Err(DecodeError::InvalidInstruction(0x0f, opcode)),
+ 12 => I::Sub(scc, dest, rs1, short_source),
+ 13 => I::Subc(scc, dest, rs1, short_source),
+ 14 => I::Subi(scc, dest, rs1, short_source),
+ 15 => I::Subci(scc, dest, rs1, short_source),
+ // Should never be reached.
+ _ => {
+ return Err(DecodeError::CodeError(String::from(
+ "Match bottom four bytes of opcode prefix",
+ )))
+ }
+ },
+ 2 => match op & 0xF {
+ 0..=5 => return Err(DecodeError::InvalidInstruction(0x0f, opcode)),
+ 6 => I::Ldxw(scc, dest, rs1, short_source),
+ 7 => I::Ldrw(scc, dest, imm19),
+ 8 => I::Ldxhu(scc, dest, rs1, short_source),
+ 9 => I::Ldrhu(scc, dest, imm19),
+ 10 => I::Ldxhs(scc, dest, rs1, short_source),
+ 11 => I::Ldrhs(scc, dest, imm19),
+ 12 => I::Ldxbu(scc, dest, rs1, short_source),
+ 13 => I::Ldrbu(scc, dest, imm19),
+ 14 => I::Ldxbs(scc, dest, rs1, short_source),
+ 15 => I::Ldrbs(scc, dest, imm19),
+ // Should never be reached.
+ _ => {
+ return Err(DecodeError::CodeError(String::from(
+ "Match bottom four bytes of opcode prefix",
+ )))
+ }
+ },
+ 3 => match op & 0xF {
+ 0..=5 => return Err(DecodeError::InvalidInstruction(0x0f, opcode)),
+ 6 => I::Stxw(scc, dest, rs1, short_source),
+ 7 => I::Strw(scc, dest, imm19),
+ 8..=9 => return Err(DecodeError::InvalidInstruction(0x0f, opcode)),
+ 10 => I::Stxh(scc, dest, rs1, short_source),
+ 11 => I::Strh(scc, dest, imm19),
+ 12..=13 => return Err(DecodeError::InvalidInstruction(0x0f, opcode)),
+ 14 => I::Stxb(scc, dest, rs1, short_source),
+ 15 => I::Strb(scc, dest, imm19),
+ // Should never be reached.
+ _ => {
+ return Err(DecodeError::CodeError(String::from(
+ "Match bottom four bytes of opcode prefix",
+ )))
+ }
+ },
+ // Top bit is 1, meaning an extension opcode.
+ 4..=8 => match opcode {
+ // TODO
+ _ => return Err(DecodeError::CodeError(String::from("Not yet implemented!"))),
+ },
+ _ => return Err(DecodeError::InvalidInstruction(0x8, opcode)),
+ })
+}
+
+pub fn decode_file(file: &Vec<u8>, pos: usize) -> Result<(), DecodeError> {
+ let result = 0usize;
+
+ for i in (0..file.len()).step_by(4) {
+ decode(u32::from_ne_bytes(file[pos..pos + 4].try_into().unwrap()))?;
+ }
+
+ Ok(())
+}
diff --git a/src/main.rs b/src/main.rs
@@ -19,360 +19,13 @@ extern crate sdl2;
mod main_test;
mod config;
+mod decode;
mod sdl;
mod util;
-use core::convert::TryInto;
+use decode::decode_file;
use std::fs;
-enum Conditional {
- /// Greater than.
- Gt,
- /// Less than or equal to.
- Le,
- /// Greater than or equal to.
- Ge,
- /// Less than.
- Lt,
- /// Higher than.
- Hi,
- /// Lower than or same.
- Los,
- /// Lower than no carry.
- Lonc,
- /// Higher than, carry.
- Hisc,
- /// Plus (test sign).
- Pl,
- /// Minus (test sign).
- Mi,
- /// Not equal.
- Ne,
- /// Equal.
- Eq,
- /// Now overflow (signed arithmetic).
- Nv,
- /// Overflow (signed arithmetic).
- V,
- /// Always (constant 1).
- Alw,
-}
-
-/// The 'source' of the instruction, which can either be a register name,
-/// or a 13 bit immediate (signed or unsigned).
-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),
-}
-
-/// A RISC-II Instruction.
-/// A RISC-II instruction is in one of two formats: short source and long immediate.
-///
-/// Short source has the following members: (SCC: bool, dest: u8, rs1: u8, short source: u16).
-/// The short source can either be a 13 bit immediate value or a 5 bit register name.
-///
-/// The long immediate format has the following members: (SCC: bool, dest: u8, imm: 19).
-///
-/// For both formats, if SCC is true, then update conditional registers (CC's).
-///
-/// Shift CC's: shift, logical instructions: V := 0; C := 0;
-/// Arithmetic instructions update CC's as follows: Z := [d == 0]; N := d<31>;
-/// Arithmetic's V := [32 bit 2's-complement overflow occurred].
-/// additions: C := carry<31>to<32> (assuming s1, s2: unsigned).
-/// subtractions: C := NOT[borrow<31>to<32>] (for s1, s2: unsigned).
-///
-/// Load instructions: If the instruction has the letter `r` in the name
-/// it is relative to PC (PC + imm19). If it has the letter `x` instead,
-/// the load is register indexed.
-enum Instruction {
- /// Call interrupt.
- /// Notes:
- /// - PRIVILEGED INSTRUCTION.
- /// - 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.
- Calli(bool, u8),
- /// Get pointer to window. rd := (-1)<31:13> & PSW<12:0>;
- GetPSW(bool, u8, u8, ShortSource),
- /// Get the last Program Counter. rd := LSTPC.
- /// Iff SCC == true, Z := [LSTPC == 0]; N := LSTPC<31>; V,C := garbage.
- /// Notes:
- /// - PRIVILEGED INSTRUCTION.
- GetIPC(bool, u8, u8, ShortSource),
- /// Set PSW. PSW := [rs1 + ShortSource2]<12:0>;
- /// 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.
- /// - Rd is discarded.
- /// - New PSW is not in effect until AFTER the next cycle following execution
- /// of this instruction.
- PutPSW(bool, u8, u8, ShortSource),
- /// Call procedure at `shortSource` + `rs1`.
- /// - 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 := PC; CC's have same rules as getipc.
- Callx(bool, u8, u8, ShortSource),
- /// Call procedure at `PC` + `imm19`.
- /// - 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 := PC; CC's have same rules as getipc.
- Callr(bool, u8, u32),
- /// If conditional is true: PC := `rs1` + `shortSource`;
- Jmpx(bool, Conditional, u8, ShortSource),
- /// If conditional is true: PC += `imm19`;
- /// Test alignment: if newPC<0> == 1 then abort instruction and jump
- /// to 0x80000000.
- Jmpr(bool, Conditional, u32),
- /// Return from the current procedure if conditional is true.
- /// CWP := CWP + 1 MOD 8.
- /// Notes:
- /// - `rs1` and `rs1` are read from the OLD window.
- /// - The usual use case of this instruction is with target address
- /// `rs1` + 8 (with `rs1`=`rd` of the call).
- Ret(bool, Conditional, u8, ShortSource),
- /// Return from interrupt if condition is true.
- /// CWP := CWP + 1 MOD 8.
- /// Notes:
- /// - PRIVILEGED INSTRUCTION.
- /// - `rs1` and `rs1` are read from the OLD window.
- /// - The usual use case of this instruction is with target address
- /// `rs1` + 8 (with `rs1`=`rd` of the call).
- Reti(bool, Conditional, u8, ShortSource),
-
- /// Shift left logical.
- Sll(bool, u8, u8, ShortSource),
- /// Shift right logical.
- Srl(bool, u8, u8, ShortSource),
- /// Shift right arithmetic.
- Sra(bool, u8, u8, ShortSource),
-
- /// Bitwise OR.
- Or(bool, u8, u8, ShortSource),
- /// Bitwise And.
- And(bool, u8, u8, ShortSource),
- /// Bitwise Xor.
- Xor(bool, u8, u8, ShortSource),
-
- /// Arithmetic add: d := s1 + s2;
- Add(bool, u8, u8, ShortSource),
- /// Arithmetic add with constant: d := s1 + s2 + C;
- Addc(bool, u8, u8, ShortSource),
- /// Arithmetic sub: d := s1 - s2; (d := s1 + NOT(s2) + 1)
- Sub(bool, u8, u8, ShortSource),
- /// Arithmetic sub with constant: d := s1 - s2 - NOT(C); (d := s1 + NOT(s2) + C)
- Subc(bool, u8, u8, ShortSource),
- /// Subtract inverse: d := s2 - s1; (d := s2 + NOT(s1))
- Subi(bool, u8, u8, ShortSource),
- /// Subtract inverse with constant: d := s2 - s1 - NOT(C); (d := s2 - s1 - NOT(C))
- Subci(bool, u8, u8, ShortSource),
-
- /// Load high: Load 19 bit immediate into top 19 bits of destination register,
- /// and set the bottom 13 bits to 0.
- Ldhi(bool, u8, u32),
- /// Load word, register indexed.
- Ldxw(bool, u8, u8, ShortSource),
- /// Load word, long-immediate.
- Ldrw(bool, u8, u32),
-
- /// Load half signed, register indexed.
- Ldxhs(bool, u8, u8, ShortSource),
- /// Load half signed, long-immediate.
- Ldrhs(bool, u8, u32),
- /// Load half unsigned, register indexed.
- Ldxhu(bool, u8, u8, ShortSource),
- /// Load half unsigned, long-immediate.
- Ldrhu(bool, u8, u32),
-
- /// Load byte signed, register indexed.
- Ldxbs(bool, u8, u8, ShortSource),
- /// Load byte signed, long-immediate.
- Ldrbs(bool, u8, u32),
- /// Load byte unsigned, register indexed.
- Ldxbu(bool, u8, u8, ShortSource),
- /// Load byte unsigned, long-immediate.
- Ldrbu(bool, u8, u32),
-
- /// Store word, register indexed.
- Stxw(bool, u8, u8, ShortSource),
- /// Store word, long-immediate.
- Strw(bool, u8, u32),
-
- /// Store half, register indexed.
- Stxh(bool, u8, u8, ShortSource),
- /// Store half, long-immediate.
- Strh(bool, u8, u32),
-
- /// Store byte, register indexed.
- Stxb(bool, u8, u8, ShortSource),
- /// Store byte, long-immediate.
- Strb(bool, u8, u32),
-}
-
-enum DecodeError {
- /// Indicates an invalid instruction. The first u32 indicates which bits are invalid,
- /// the final u32 is the whole opcode.
- InvalidInstruction(u32, u32),
- InvalidJumpCondition,
-
- /// Indicates some bug in this program with a string description.
- CodeError(String),
-}
-
-/// Get the RISC-II conditional type from a opcode<22-19>.
-/// opcode A RISC-II opcode.
-/// return RISC-II conditional, or DecodeError if 0.
-fn get_cond_from_opcode(opcode: u32) -> Result<Conditional, DecodeError> {
- type C = Conditional;
- Ok(match (opcode & 0x780000) >> 18 {
- 1 => C::Gt,
- 2 => C::Le,
- 3 => C::Ge,
- 4 => C::Lt,
- 5 => C::Hi,
- 6 => C::Los,
- 7 => C::Lonc,
- 8 => C::Hisc,
- 9 => C::Pl,
- 10 => C::Mi,
- 11 => C::Ne,
- 12 => C::Eq,
- 13 => C::Nv,
- 14 => C::V,
- 15 => C::Alw,
- _ => return Err(DecodeError::InvalidJumpCondition),
- })
-}
-
-fn decode(opcode: u32) -> Result<Instruction, DecodeError> {
- type I = Instruction;
- // SCC flag (<24>).
- let scc = opcode & 0x1000000 != 0;
- // Destination bits (<23-19>).
- let dest = ((opcode & 0xF80000) >> 18) as u8;
- // Short-immediate RS1 value (<18-14>).
- let rs1 = ((opcode & 0x7C000) >> 13) as u8;
- // Immediate-mode bottom 19 bits <18-0>.
- let imm19 = opcode & 0x7FFFF;
- // Short source immediate-mode bottom 13 bits <12-0> or rs1 <4-0>.
- let short_source = if opcode & 0x2000 != 0 {
- ShortSource::UImm13(opcode & 0x1fff)
- } else {
- ShortSource::Reg((opcode & 0x1f) as u8)
- }; // TODO fix ambiguous sign problem.
- // The opcode itself.
- let op = (opcode & 0xFE) >> 24;
-
- let cond = get_cond_from_opcode(opcode);
-
- // Math the opcode's prefix.
- Ok(match op >> 5 {
- // Match the bottom four bytes of the opcode's prefix.
- 0 => match op & 0xF {
- 0 => return Err(DecodeError::InvalidInstruction(0x0f, opcode)),
- 1 => I::Calli(scc, dest),
- 2 => I::GetPSW(scc, dest, rs1, short_source),
- 3 => I::GetIPC(scc, dest, rs1, short_source),
- 4 => I::PutPSW(scc, dest, rs1, short_source),
- 5..=7 => return Err(DecodeError::InvalidInstruction(0x0f, opcode)),
- 8 => I::Callx(scc, dest, rs1, short_source),
- 9 => I::Callr(scc, dest, imm19),
- 10..=11 => return Err(DecodeError::InvalidInstruction(0x0f, opcode)),
- 12 => I::Jmpx(scc, cond?, rs1, short_source),
- 13 => I::Jmpr(scc, cond?, imm19),
- 14 => I::Ret(scc, cond?, rs1, short_source),
- 15 => I::Reti(scc, cond?, rs1, short_source),
- // Should never be reached.
- _ => {
- return Err(DecodeError::CodeError(String::from(
- "Match bottom four bytes of opcode prefix",
- )))
- }
- },
- 1 => match op & 0xF {
- 0 => return Err(DecodeError::InvalidInstruction(0x0f, opcode)),
- 1 => I::Sll(scc, dest, rs1, short_source),
- 2 => I::Sra(scc, dest, rs1, short_source),
- 3 => I::Srl(scc, dest, rs1, short_source),
- 4 => I::Ldhi(scc, dest, imm19),
- 5 => I::And(scc, dest, rs1, short_source),
- 6 => I::Or(scc, dest, rs1, short_source),
- 7 => I::Xor(scc, dest, rs1, short_source),
- 8 => I::Add(scc, dest, rs1, short_source),
- 9 => I::Addc(scc, dest, rs1, short_source),
- 10..=11 => return Err(DecodeError::InvalidInstruction(0x0f, opcode)),
- 12 => I::Sub(scc, dest, rs1, short_source),
- 13 => I::Subc(scc, dest, rs1, short_source),
- 14 => I::Subi(scc, dest, rs1, short_source),
- 15 => I::Subci(scc, dest, rs1, short_source),
- // Should never be reached.
- _ => {
- return Err(DecodeError::CodeError(String::from(
- "Match bottom four bytes of opcode prefix",
- )))
- }
- },
- 2 => match op & 0xF {
- 0..=5 => return Err(DecodeError::InvalidInstruction(0x0f, opcode)),
- 6 => I::Ldxw(scc, dest, rs1, short_source),
- 7 => I::Ldrw(scc, dest, imm19),
- 8 => I::Ldxhu(scc, dest, rs1, short_source),
- 9 => I::Ldrhu(scc, dest, imm19),
- 10 => I::Ldxhs(scc, dest, rs1, short_source),
- 11 => I::Ldrhs(scc, dest, imm19),
- 12 => I::Ldxbu(scc, dest, rs1, short_source),
- 13 => I::Ldrbu(scc, dest, imm19),
- 14 => I::Ldxbs(scc, dest, rs1, short_source),
- 15 => I::Ldrbs(scc, dest, imm19),
- // Should never be reached.
- _ => {
- return Err(DecodeError::CodeError(String::from(
- "Match bottom four bytes of opcode prefix",
- )))
- }
- },
- 3 => match op & 0xF {
- 0..=5 => return Err(DecodeError::InvalidInstruction(0x0f, opcode)),
- 6 => I::Stxw(scc, dest, rs1, short_source),
- 7 => I::Strw(scc, dest, imm19),
- 8..=9 => return Err(DecodeError::InvalidInstruction(0x0f, opcode)),
- 10 => I::Stxh(scc, dest, rs1, short_source),
- 11 => I::Strh(scc, dest, imm19),
- 12..=13 => return Err(DecodeError::InvalidInstruction(0x0f, opcode)),
- 14 => I::Stxb(scc, dest, rs1, short_source),
- 15 => I::Strb(scc, dest, imm19),
- // Should never be reached.
- _ => {
- return Err(DecodeError::CodeError(String::from(
- "Match bottom four bytes of opcode prefix",
- )))
- }
- },
- // Top bit is 1, meaning an extension opcode.
- 4..=8 => match opcode {
- // TODO
- _ => return Err(DecodeError::CodeError(String::from("Not yet implemented!"))),
- },
- _ => return Err(DecodeError::InvalidInstruction(0x8, opcode)),
- })
-}
-
-fn load_firmware(file: &String) {}
-
fn get_program(path: &String) -> Result<Vec<u8>, String> {
println!("Opening binary file {}.", path);
@@ -382,19 +35,9 @@ fn get_program(path: &String) -> Result<Vec<u8>, String> {
})
}
-fn decode_file(file: &Vec<u8>, pos: usize) -> Result<(), DecodeError> {
- let result = 0usize;
-
- for i in (0..file.len()).step_by(4) {
- decode(u32::from_ne_bytes(file[pos..pos + 4].try_into().unwrap()))?;
- }
-
- Ok(())
-}
-
fn main() -> Result<(), String> {
- let context = sdl::Context::new()?;
let config = config::Config::init()?;
+ let context = sdl::Context::new(&config)?;
println!(
"Running emulator with the following configuration: \n{}\n",
diff --git a/src/sdl.rs b/src/sdl.rs
@@ -17,6 +17,7 @@
extern crate sdl2;
+use config::Config;
use sdl2::event::Event;
use sdl2::keyboard::Keycode;
use sdl2::pixels::Color;
@@ -39,11 +40,11 @@ pub struct Context {
// Struct impls.
impl Context {
- pub fn new() -> Result<Self, String> {
+ pub fn new(config: &Config) -> Result<Self, String> {
let sdl_context = sdl2::init()?;
let video_subsystem = sdl_context.video()?;
let window = video_subsystem
- .window("RISC II", 1200, 900)
+ .window("RISC II", config.get_win_width(), config.get_win_height())
.position_centered()
.opengl()
.build()
diff --git a/src/util.rs b/src/util.rs
@@ -13,6 +13,8 @@
// 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::env;
+use std::ffi::OsString;
use std::fs;
use std::fs::{Metadata, OpenOptions};
use std::io::{Read, Write};
@@ -47,6 +49,38 @@ pub fn get_unix_timestamp() -> Result<Duration, String> {
}
}
+pub fn os_string_result_to_strings(r: Result<String, OsString>) -> Result<String, String> {
+ match r {
+ Err(e) => Err(match e.into_string() {
+ Ok(s) => s,
+ Err(ee) => "Could not coerce OS string into utf8 string".to_string(),
+ }),
+ Ok(rr) => Ok(rr.to_string()),
+ }
+}
+
+pub fn get_home_nofail() -> String {
+ match env::var("HOME") {
+ Ok(v) => format!("{}", v),
+ Err(e) => {
+ eprintln!("$HOME is not set. Defaulting to current directory.");
+ format!(
+ "{}",
+ match env::current_dir() {
+ Ok(r) => match os_string_result_to_strings(r.into_os_string().into_string()) {
+ Ok(rr) => rr,
+ Err(ee) => {
+ eprintln!("Could not get current dir as utf8 string. Defaulting to nothing for $HOME: {}", e);
+ String::new()
+ }
+ },
+ Err(e) => format!("{}", e),
+ }
+ )
+ }
+ }
+}
+
// Struct impls.
impl File {