emulator: fix errors calling new fallible Instruction::decode

TODO: Add a logger for smarter looking loggiing output
This commit is contained in:
2025-06-15 15:53:58 +01:00
parent ecf443e59e
commit 423d768e40
7 changed files with 466 additions and 230 deletions
+214 -212
View File
@@ -1,4 +1,9 @@
use crate::common::instructions::encode::Encode;
use crate::common::instructions::{
args::{ITypeArgs, RTypeArgs},
encode::Encode,
errors::InstructionDecodeError,
errors::RegisterParseError,
};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Interrupt {
@@ -15,6 +20,7 @@ impl Interrupt {
}
}
// TODO: This should be TryFrom.
impl From<u8> for Interrupt {
#[allow(unreachable_code)]
fn from(_code: u8) -> Self {
@@ -24,7 +30,15 @@ impl From<u8> for Interrupt {
}
}
/// Whether an [`Instruction`] is an I-type or R-type instruction.
#[non_exhaustive]
pub enum InstructionType {
Register,
Immediate,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Register {
// general purpose registers
Rg0,
@@ -68,22 +82,6 @@ impl Default for Register {
}
}
#[derive(Debug)]
/// Error type for parsing register numbers.
pub enum RegisterParseError {
InvalidIndex(u8),
}
impl std::fmt::Display for RegisterParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidIndex(idx) => write!(f, "invalid index given ({idx})"),
}
}
}
impl std::error::Error for RegisterParseError {}
impl TryFrom<u8> for Register {
type Error = RegisterParseError;
@@ -159,146 +157,63 @@ impl std::fmt::Display for Register {
}
}
// /// Used by instructions with no arguments.
// struct NoArgs {}
#[derive(Debug, Clone, Copy)]
/// Used by instructions with 2 registers and an immediate argument.
pub struct ITypeArgs {
pub immediate: u16,
pub r1: Register,
/// May not actually be used by some instructions taking an immediate e.g. LUI. This is solved by making the constructor take Options.
pub r2: Register,
}
impl ITypeArgs {
#[must_use]
/// Creates a new [`ITypeArgs`]. If r1 or r2 is unset, they will be replaced with [`Register::NoReg`].
pub fn new(immediate: u16, r1: Option<Register>, r2: Option<Register>) -> Self {
let r1 = r1.unwrap_or_default();
let r2 = r2.unwrap_or_default();
Self { immediate, r1, r2 }
}
}
impl Encode for ITypeArgs {
/// Encodes an I-type instruction from its fields. These must have some unused high-order
/// bits set to 0 else the bit shifting logic gets fucked.
fn encode(self, opcode: u8) -> u32 {
let opcode = u32::from(opcode);
let r1 = self.r1 as u32;
let dr = self.r2 as u32;
let immediate = u32::from(self.immediate);
(opcode << 26) | (r1 << 21) | (dr << 16) | immediate
}
}
/// Used by instructions not using immediates (besides 5 bit shift values).
#[derive(Debug, Clone, Copy)]
pub struct RTypeArgs {
pub sr1: Register,
pub sr2: Register,
pub dr: Register,
/// 5 bit shift amount.
pub shamt: u8,
}
impl RTypeArgs {
#[must_use]
/// Creates a new [`RTypeArgs`]. If any registers are unset, they will be replaced with [`Register::NoReg`]. If `shamt` is unset, it will be set to 0.
pub fn new(
sr1: Option<Register>,
sr2: Option<Register>,
dr: Option<Register>,
shamt: Option<u8>,
) -> Self {
let sr1 = sr1.unwrap_or_default();
let shamt = shamt.unwrap_or_default();
let sr2 = sr2.unwrap_or_default();
let dr = dr.unwrap_or_default();
Self {
sr1,
sr2,
dr,
shamt,
}
}
}
impl Encode for RTypeArgs {
/// Encodes an R-type instruction from its fields. These must have unused high-order
/// bits set to 0 else the bit shifting logic is fucked.
///
/// # Arguments
///
/// - `shamt`: The amount to shift value (used only in shift instructions, otherwise 0).
fn encode(self, opcode: u8) -> u32 {
let opcode = u32::from(opcode);
let sr1 = self.sr1 as u32;
let sr2 = self.sr2 as u32;
let dr = self.dr as u32;
let shamt = u32::from(self.shamt);
(opcode << 26) | (sr1 << 21) | (sr2 << 16) | (dr << 11) | (shamt << 6)
}
}
/// TODO: Turn argument tuples into simple structs like `TwoReg`, `TwoRegAndOff`
/// etc just to make code a little cleaner.
#[derive(Debug, Clone, Copy)]
#[repr(u8)]
#[non_exhaustive]
/// A list of all current instructions in the DSA.
///
/// # Note
///
/// This is subject to change and is therefore marked non exhaustive.
pub enum Instruction {
// No-op
Nop = 0x0,
// Data transfer instructions
Mov(RTypeArgs) = 0x1,
MovSigned(RTypeArgs) = 0x2,
Mov(args::RTypeArgs) = 0x1,
MovSigned(args::RTypeArgs) = 0x2,
LoadByte(ITypeArgs) = 0x3,
LoadByteSigned(ITypeArgs) = 0x4,
LoadHalfword(ITypeArgs) = 0x5,
LoadHalfwordSigned(ITypeArgs) = 0x6,
LoadWord(ITypeArgs) = 0x7,
LoadByte(args::ITypeArgs) = 0x3,
LoadByteSigned(args::ITypeArgs) = 0x4,
LoadHalfword(args::ITypeArgs) = 0x5,
LoadHalfwordSigned(args::ITypeArgs) = 0x6,
LoadWord(args::ITypeArgs) = 0x7,
StoreByte(ITypeArgs) = 0x8,
StoreHalfword(ITypeArgs) = 0x9,
StoreWord(ITypeArgs) = 0xA,
StoreByte(args::ITypeArgs) = 0x8,
StoreHalfword(args::ITypeArgs) = 0x9,
StoreWord(args::ITypeArgs) = 0xA,
LoadLowerImmediate(ITypeArgs) = 0xB,
LoadUpperImmediate(ITypeArgs) = 0xC,
LoadLowerImmediate(args::ITypeArgs) = 0xB,
LoadUpperImmediate(args::ITypeArgs) = 0xC,
// Jump Instructions
Jump(ITypeArgs) = 0xD,
JumpEq(ITypeArgs) = 0xE,
JumpNeq(ITypeArgs) = 0xF,
JumpGt(ITypeArgs) = 0x10,
JumpGe(ITypeArgs) = 0x11,
JumpLt(ITypeArgs) = 0x12,
JumpLe(ITypeArgs) = 0x13,
Jump(args::ITypeArgs) = 0xD,
JumpEq(args::ITypeArgs) = 0xE,
JumpNeq(args::ITypeArgs) = 0xF,
JumpGt(args::ITypeArgs) = 0x10,
JumpGe(args::ITypeArgs) = 0x11,
JumpLt(args::ITypeArgs) = 0x12,
JumpLe(args::ITypeArgs) = 0x13,
// Comparison
Compare(RTypeArgs) = 0x14,
Compare(args::RTypeArgs) = 0x14,
// Arithmetic
Add(RTypeArgs) = 0x19,
Sub(RTypeArgs) = 0x1A,
Increment(RTypeArgs) = 0x15,
Decrement(RTypeArgs) = 0x16,
ShiftLeft(RTypeArgs) = 0x17,
ShiftRight(RTypeArgs) = 0x18,
Add(args::RTypeArgs) = 0x19,
Sub(args::RTypeArgs) = 0x1A,
Increment(args::RTypeArgs) = 0x15,
Decrement(args::RTypeArgs) = 0x16,
ShiftLeft(args::RTypeArgs) = 0x17,
ShiftRight(args::RTypeArgs) = 0x18,
// Logical
And(RTypeArgs) = 0x1B,
Or(RTypeArgs) = 0x1C,
Not(RTypeArgs) = 0x1D,
Xor(RTypeArgs) = 0x1E,
Nand(RTypeArgs) = 0x1F,
Nor(RTypeArgs) = 0x20,
Xnor(RTypeArgs) = 0x21,
And(args::RTypeArgs) = 0x1B,
Or(args::RTypeArgs) = 0x1C,
Not(args::RTypeArgs) = 0x1D,
Xor(args::RTypeArgs) = 0x1E,
Nand(args::RTypeArgs) = 0x1F,
Nor(args::RTypeArgs) = 0x20,
Xnor(args::RTypeArgs) = 0x21,
// Misc
Interrupt(Interrupt) = 0x22,
@@ -317,92 +232,179 @@ impl Instruction {
unsafe { *std::ptr::from_ref::<Self>(self).cast::<u8>() }
}
/// Encodes an [`Instruction`] into a word.
#[must_use]
#[allow(unused)]
pub fn encode(&self) -> u32 {
Encode::encode(*self, self.opcode())
}
#[must_use]
#[deprecated = "I am a little confused as to whether we keep these around for jumps."]
/// Encodes a J-type instruction from its fields. These must have some unused high-order
/// bits set to 0 else the bit shifting logic gets fucked.
///
/// Note: the address argument is passed as-is, caller is responsible for resolving this
/// address properly.
pub const fn _encode_j_type(opcode: u8, addr: u32) -> u32 {
let opcode = opcode as u32;
(opcode << 26) | addr
/// Decodes an [`Instruction`] from a word `data`.
pub fn decode(data: u32) -> Result<Self, InstructionDecodeError> {
data.try_into()
}
/// Returns the mnemonic for a given [`Instruction`].
#[must_use]
pub const fn decode(_data: u32) -> Self {
// TODO: this needs to actually decode something
Self::Nop
}
}
impl std::fmt::Display for Instruction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
pub const fn mnemonic(self) -> &'static str {
match self {
Self::Nop => write!(f, "NOP"),
Self::Mov(args) => write!(f, "MOV {}, {}", args.sr1, args.dr),
Self::MovSigned(args) => write!(f, "MOVS {}, {}", args.sr1, args.dr),
Self::LoadByte(args) => write!(f, "LDB {:x}({}), {}", args.immediate, args.r1, args.r2),
Self::LoadByteSigned(args) => {
write!(f, "LDBS {:x}({}), {}", args.immediate, args.r1, args.r2)
}
Self::LoadHalfword(args) => {
write!(f, "LDH {:x}({}), {}", args.immediate, args.r1, args.r2)
}
Self::LoadHalfwordSigned(args) => {
write!(f, "LDHS {:x}({}), {}", args.immediate, args.r1, args.r2)
}
Self::LoadWord(args) => write!(f, "LDW {:x}({}), {}", args.immediate, args.r1, args.r2),
Self::StoreByte(args) => {
write!(f, "STB {:x}({}), {}", args.immediate, args.r1, args.r2)
}
Self::StoreHalfword(args) => {
write!(f, "STH {:x}({}), {}", args.immediate, args.r1, args.r2)
}
Self::StoreWord(args) => {
write!(f, "STW {:x}({}), {}", args.immediate, args.r1, args.r2)
}
Self::Add(_) => "add",
Self::Sub(_) => "sub",
Self::Increment(_) => "inc",
Self::Decrement(_) => "dec",
Self::Compare(_) => "cmp",
Self::Halt => "hlt",
Self::And(_) => "and",
Self::IntReturn => "intr",
Self::Interrupt(_) => "int",
Self::Jump(_) => "jmp",
Self::JumpEq(_) => "jeq",
Self::JumpNeq(_) => "jneq",
Self::JumpGt(_) => "jgt",
Self::JumpGe(_) => "jge",
Self::JumpLt(_) => "jlt",
Self::JumpLe(_) => "jle",
Self::Mov(_) => "mov",
Self::MovSigned(_) => "movs",
Self::LoadByte(_) => "ldb",
Self::LoadByteSigned(_) => "ldbs",
Self::LoadHalfword(_) => "ldh",
Self::LoadHalfwordSigned(_) => "ldhs",
Self::LoadWord(_) => "ldw",
Self::StoreByte(_) => "stb",
Self::StoreHalfword(_) => "sth",
Self::StoreWord(_) => "stw",
Self::LoadLowerImmediate(_) => "lli",
Self::LoadUpperImmediate(_) => "lui",
Self::ShiftLeft(_) => "shl",
Self::ShiftRight(_) => "shr",
Self::Or(_) => "or",
Self::Not(_) => "not",
Self::Nop => "nop",
Self::Xor(_) => "xor",
Self::Nand(_) => "nand",
Self::Nor(_) => "nor",
Self::Xnor(_) => "xnor",
}
}
Self::LoadLowerImmediate(args) => write!(f, "LLI {}, {}", args.r1, args.r2),
Self::LoadUpperImmediate(args) => write!(f, "LUI {}, {}", args.r1, args.r2),
/// Returns the [`InstructionType`] for the given [`Instruction`].
#[must_use]
pub const fn instruction_type(self) -> InstructionType {
Self::instruction_type_from_opcode(self.opcode())
}
Self::Jump(args) => write!(f, "JMP ({:x}){}", args.immediate, args.r1),
Self::JumpEq(args) => write!(f, "JEQ ({:x}){}", args.immediate, args.r1),
Self::JumpNeq(args) => write!(f, "JNEQ ({:x}){}", args.immediate, args.r1),
Self::JumpGt(args) => write!(f, "JGT {:x}({})", args.immediate, args.r1),
Self::JumpGe(args) => write!(f, "JGE {:x}({})", args.immediate, args.r1),
Self::JumpLt(args) => write!(f, "JLT {:x}({})", args.immediate, args.r1),
Self::JumpLe(args) => write!(f, "JLE {:x}({})", args.immediate, args.r1),
Self::Compare(args) => write!(f, "CMP {}, {}", args.sr1, args.sr2),
Self::Add(args) => write!(f, "ADD {}, {}, {}", args.sr1, args.sr2, args.dr),
Self::Sub(args) => write!(f, "SUB {}, {}, {}", args.sr1, args.sr2, args.dr),
Self::Increment(a) => write!(f, "INC {}", a.dr),
Self::Decrement(a) => write!(f, "DEC {}", a.dr),
Self::ShiftLeft(args) => write!(f, "SHL {}, {}, {}", args.sr1, args.sr2, args.dr),
Self::ShiftRight(args) => write!(f, "SHR {}, {}, {}", args.sr1, args.sr2, args.dr),
Self::And(args) => write!(f, "AND {}, {}, {}", args.sr1, args.sr2, args.dr),
Self::Or(args) => write!(f, "OR {}, {}, {}", args.sr1, args.sr2, args.dr),
Self::Not(args) => write!(f, "NOT {}, {}", args.sr1, args.sr2),
Self::Xor(args) => write!(f, "XOR {}, {}, {}", args.sr1, args.sr2, args.dr),
Self::Nand(args) => write!(f, "NAND {}, {}, {}", args.sr1, args.sr2, args.dr),
Self::Nor(args) => write!(f, "NOR {}, {}, {}", args.sr1, args.sr2, args.dr),
Self::Xnor(args) => write!(f, "XNOR {}, {}, {}", args.sr1, args.sr2, args.dr),
Self::Interrupt(a) => write!(f, "INT {}", a.as_u8()),
Self::IntReturn => write!(f, "INTR"),
Self::Halt => write!(f, "HALT"),
/// Returns the [`InstructionType`] for the given `opcode`.
#[must_use]
pub const fn instruction_type_from_opcode(opcode: u8) -> InstructionType {
match opcode {
0x3..=0x13 => InstructionType::Immediate,
_ => InstructionType::Register,
}
}
}
// Instruction decoding logic goes here.
impl std::fmt::Display for Instruction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.mnemonic())?;
match self {
Self::Mov(args) | Self::MovSigned(args) => write!(f, " {}, {}", args.sr1, args.dr),
Self::LoadByte(args)
| Self::LoadByteSigned(args)
| Self::LoadHalfword(args)
| Self::LoadHalfwordSigned(args)
| Self::LoadWord(args)
| Self::StoreByte(args)
| Self::StoreHalfword(args)
| Self::StoreWord(args) => {
write!(f, " {:x}({}), {}", args.immediate, args.r1, args.r2)
}
Self::Jump(args)
| Self::JumpEq(args)
| Self::JumpNeq(args)
| Self::JumpGt(args)
| Self::JumpGe(args)
| Self::JumpLt(args)
| Self::JumpLe(args) => {
write!(f, " ({:x}){}", args.immediate, args.r1)
}
Self::LoadLowerImmediate(args) | Self::LoadUpperImmediate(args) => {
write!(f, " {}, {}", args.r1, args.r2)
}
Self::Compare(args) | Self::Not(args) => write!(f, " {}, {}", args.sr1, args.sr2),
Self::Add(args)
| Self::Sub(args)
| Self::Xor(args)
| Self::Nand(args)
| Self::Nor(args)
| Self::Xnor(args)
| Self::ShiftLeft(args)
| Self::ShiftRight(args)
| Self::And(args)
| Self::Or(args) => {
write!(f, " {}, {}, {}", args.sr1, args.sr2, args.dr)
}
Self::Increment(a) | Self::Decrement(a) => write!(f, " {}", a.dr),
Self::Interrupt(a) => write!(f, " {}", a.as_u8()),
_ => Ok(()),
}
}
}
impl TryFrom<u32> for Instruction {
type Error = InstructionDecodeError;
/// Instruction decoding can be using using [`Instruction::try_from`]
fn try_from(data: u32) -> Result<Self, Self::Error> {
// Pull the opcode out so we can parse it correctly.
let opcode: u8 = ((data & 0xfc << 26) >> 26) as u8;
match opcode {
0x0 => Ok(Self::Nop),
0x1 => Ok(Self::Mov(RTypeArgs::try_from(data)?)),
0x2 => Ok(Self::MovSigned(RTypeArgs::try_from(data)?)),
0x3 => Ok(Self::LoadByte(ITypeArgs::try_from(data)?)),
0x4 => Ok(Self::LoadByteSigned(ITypeArgs::try_from(data)?)),
0x5 => Ok(Self::LoadHalfword(ITypeArgs::try_from(data)?)),
0x6 => Ok(Self::LoadHalfwordSigned(ITypeArgs::try_from(data)?)),
0x7 => Ok(Self::LoadWord(ITypeArgs::try_from(data)?)),
0x8 => Ok(Self::StoreByte(ITypeArgs::try_from(data)?)),
0x9 => Ok(Self::StoreHalfword(ITypeArgs::try_from(data)?)),
0xA => Ok(Self::StoreWord(ITypeArgs::try_from(data)?)),
0xB => Ok(Self::LoadLowerImmediate(ITypeArgs::try_from(data)?)),
0xC => Ok(Self::LoadUpperImmediate(ITypeArgs::try_from(data)?)),
0xD => Ok(Self::Jump(ITypeArgs::try_from(data)?)),
0xE => Ok(Self::JumpEq(ITypeArgs::try_from(data)?)),
0xF => Ok(Self::JumpNeq(ITypeArgs::try_from(data)?)),
0x10 => Ok(Self::JumpGt(ITypeArgs::try_from(data)?)),
0x11 => Ok(Self::JumpGe(ITypeArgs::try_from(data)?)),
0x12 => Ok(Self::JumpLt(ITypeArgs::try_from(data)?)),
0x13 => Ok(Self::JumpLe(ITypeArgs::try_from(data)?)),
0x14 => Ok(Self::Compare(RTypeArgs::try_from(data)?)),
0x15 => Ok(Self::Increment(RTypeArgs::try_from(data)?)),
0x16 => Ok(Self::Decrement(RTypeArgs::try_from(data)?)),
0x17 => Ok(Self::ShiftLeft(RTypeArgs::try_from(data)?)),
0x18 => Ok(Self::ShiftRight(RTypeArgs::try_from(data)?)),
0x19 => Ok(Self::Add(RTypeArgs::try_from(data)?)),
0x1A => Ok(Self::Sub(RTypeArgs::try_from(data)?)),
0x1B => Ok(Self::And(RTypeArgs::try_from(data)?)),
0x1C => Ok(Self::Or(RTypeArgs::try_from(data)?)),
0x1D => Ok(Self::Not(RTypeArgs::try_from(data)?)),
0x1E => Ok(Self::Xor(RTypeArgs::try_from(data)?)),
0x1F => Ok(Self::Nand(RTypeArgs::try_from(data)?)),
0x20 => Ok(Self::Nor(RTypeArgs::try_from(data)?)),
0x21 => Ok(Self::Xnor(RTypeArgs::try_from(data)?)),
0x22 => Ok(Self::Interrupt(Interrupt::from((data & 0xFF) as u8))),
0x23 => Ok(Self::IntReturn),
0x24 => Ok(Self::Halt),
_ => Err(InstructionDecodeError::InvalidOpcode(opcode)),
}
}
}
pub mod args;
mod encode;
pub mod errors;
+152
View File
@@ -0,0 +1,152 @@
//! Various types of arguments that instructions can take, alongside encoding and decoding logic.
use crate::common::instructions::{RegisterParseError, encode::Encode};
use super::Register;
/// A list of errors that can be returned when decoding instruction arguments.
#[derive(Debug)]
pub enum ArgsDecodeError {
/// The register was not valid.
InvalidRegister(u8),
}
impl From<RegisterParseError> for ArgsDecodeError {
fn from(value: RegisterParseError) -> Self {
match value {
RegisterParseError::InvalidIndex(idx) => Self::InvalidRegister(idx),
}
}
}
impl std::fmt::Display for ArgsDecodeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidRegister(idx) => {
write!(f, "invalid register index, got {idx:x}")?;
}
}
Ok(())
}
}
impl std::error::Error for ArgsDecodeError {}
#[derive(Debug, Clone, Copy)]
/// Used by instructions with 2 registers and an immediate argument.
pub struct ITypeArgs {
pub immediate: u16,
pub r1: Register,
/// May not actually be used by some instructions taking an immediate e.g. LUI. This is solved by making the constructor take Options.
pub r2: Register,
}
impl ITypeArgs {
#[must_use]
/// Creates a new [`ITypeArgs`]. If r1 or r2 is unset, they will be replaced with [`Register::NoReg`].
pub fn new(immediate: u16, r1: Option<Register>, r2: Option<Register>) -> Self {
let r1 = r1.unwrap_or_default();
let r2 = r2.unwrap_or_default();
Self { immediate, r1, r2 }
}
}
impl Encode for ITypeArgs {
/// Encodes an I-type instruction from its fields. These must have some unused high-order
/// bits set to 0 else the bit shifting logic gets fucked.
fn encode(self, opcode: u8) -> u32 {
let opcode = u32::from(opcode);
let r1 = self.r1 as u32;
let dr = self.r2 as u32;
let immediate = u32::from(self.immediate);
(opcode << 26) | (r1 << 21) | (dr << 16) | immediate
}
}
impl TryFrom<u32> for ITypeArgs {
type Error = ArgsDecodeError;
fn try_from(data: u32) -> Result<Self, Self::Error> {
let r1 = ((data >> 21) as u8).try_into()?;
let r2 = ((data >> 16) as u8).try_into()?;
let immediate = data as u16;
Ok(Self { immediate, r1, r2 })
}
}
/// Used by instructions not using immediates (besides 5 bit shift values).
#[derive(Debug, Clone, Copy)]
pub struct RTypeArgs {
pub sr1: Register,
pub sr2: Register,
pub dr: Register,
/// 5 bit shift amount.
pub shamt: u8,
}
impl RTypeArgs {
#[must_use]
/// Creates a new [`RTypeArgs`]. If any registers are unset, they will be replaced with [`Register::NoReg`]. If `shamt` is unset, it will be set to 0.
pub fn new(
sr1: Option<Register>,
sr2: Option<Register>,
dr: Option<Register>,
shamt: Option<u8>,
) -> Self {
let sr1 = sr1.unwrap_or_default();
let shamt = shamt.unwrap_or_default();
let sr2 = sr2.unwrap_or_default();
let dr = dr.unwrap_or_default();
Self {
sr1,
sr2,
dr,
shamt,
}
}
}
impl Encode for RTypeArgs {
/// Encodes an R-type instruction from its fields. These must have unused high-order
/// bits set to 0 else the bit shifting logic is fucked.
///
/// # Arguments
///
/// - `shamt`: The amount to shift value (used only in shift instructions, otherwise 0).
fn encode(self, opcode: u8) -> u32 {
let opcode = u32::from(opcode);
let sr1 = self.sr1 as u32;
let sr2 = self.sr2 as u32;
let dr = self.dr as u32;
let shamt = u32::from(self.shamt);
(opcode << 26) | (sr1 << 21) | (sr2 << 16) | (dr << 11) | (shamt << 6)
}
}
impl TryFrom<u32> for RTypeArgs {
type Error = ArgsDecodeError;
fn try_from(data: u32) -> Result<Self, Self::Error> {
let sr1 = (data >> 21) as u8;
let sr2 = (data >> 16) as u8;
let dr = (data >> 11) as u8;
let shamt = (data >> 6) as u8;
let sr1_reg = sr1.try_into()?;
let sr2_reg = sr2.try_into()?;
let dr_reg = dr.try_into()?;
Ok(Self {
sr1: sr1_reg,
sr2: sr2_reg,
dr: dr_reg,
shamt,
})
}
}
+54
View File
@@ -0,0 +1,54 @@
//! All the errors that may be returned from [`instructions`].
use super::args::ArgsDecodeError;
#[derive(Debug)]
/// Error type for parsing register numbers.
pub enum RegisterParseError {
InvalidIndex(u8),
}
impl std::fmt::Display for RegisterParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidIndex(idx) => write!(f, "invalid index given ({idx})"),
}
}
}
impl std::error::Error for RegisterParseError {}
/// A list of errors that can be returned when decoding instructions.
#[derive(Debug)]
pub enum InstructionDecodeError {
/// Some field was incorrect. Returns an error for debugging purposes.
InvalidArgument(ArgsDecodeError),
/// Some opcode was invalid. Returns the offending opcode for debugging purposes etc.
InvalidOpcode(u8),
}
impl From<ArgsDecodeError> for InstructionDecodeError {
fn from(err: ArgsDecodeError) -> Self {
Self::InvalidArgument(err)
}
}
impl std::fmt::Display for InstructionDecodeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidOpcode(code) => write!(f, "invalid opcode, got {code:x}")?,
Self::InvalidArgument(err) => write!(f, "invalid arguments, got an error {err}")?,
}
Ok(())
}
}
impl std::error::Error for InstructionDecodeError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::InvalidArgument(err) => Some(err),
_ => None,
}
}
}
+2
View File
@@ -1,3 +1,5 @@
use crate::common::instructions::args::{ITypeArgs, RTypeArgs};
use super::instructions::*;
#[test]
+25 -8
View File
@@ -67,8 +67,17 @@ pub fn run_emulator(
}
Command::Step => {
running = Running::Paused;
// Execute one cycle
cpu_lock.cycle(); // or advance() - whatever your method is called
// Execute one cycle.
match cpu_lock.cycle() {
Ok(_) => {}
Err(why) => {
let pcx = cpu_lock.get(Register::Pcx);
eprintln!("Could not decode instruction at {pcx:x}. Reason: {why}");
continue;
}
}
instruction_count += 1;
println!("Stepped one instruction");
}
@@ -93,12 +102,20 @@ pub fn run_emulator(
if running == Running::Running {
let mut update = false;
// Execute one cycle
cpu_lock.cycle();
if matches!(
Instruction::decode(cpu_lock.get(Register::Cir)),
Instruction::Halt
) {
// Execute one cycle.
let instruction = match cpu_lock.cycle() {
Ok(instruction) => instruction,
Err(why) => {
let pcx = cpu_lock.get(Register::Pcx);
eprintln!("Could not decode instruction at {pcx:x}. Reason: {why}");
continue;
}
};
// let instruction = match Instruction::decode(cpu_lock.get(Register::Cir)) {};
if matches!(instruction, Instruction::Halt) {
running = Running::Halted;
update = true;
}
+12 -9
View File
@@ -1,7 +1,7 @@
use std::cmp::{max, min};
use crate::{
common::instructions::{Instruction, Interrupt, Register},
common::instructions::{Instruction, Interrupt, Register, errors::InstructionDecodeError},
emulator::system::{memory::MemoryUnit, model::RegFile},
};
@@ -32,24 +32,27 @@ impl Processor {
self.memory.reset();
}
pub fn cycle(&mut self) -> Instruction {
pub fn cycle(&mut self) -> Result<Instruction, InstructionDecodeError> {
self.halted = false;
// get value from PCX
// Get value from PCX.
let addr = self.fetch();
// increment PCX
// Increment PCX.
self.advance();
// set MAR to the previous value of PCX
// Set MAR to the previous value of PCX.
*self.reg(Register::Mar) = addr;
let val = self.memory.read_word(addr);
// set CIR to the value of RAM[MAR]
// Set CIR to the value of RAM[MAR].
*self.reg(Register::Mar) = val;
// decode and execute the instruction
let instruction = Instruction::decode(val);
// Decode and execute the instruction.
let instruction = Instruction::decode(val)?;
instruction.execute(self);
instruction
Ok(instruction)
}
fn fetch(&self) -> u32 {
+7 -1
View File
@@ -1,5 +1,11 @@
use super::*;
use crate::{common::instructions::*, emulator::system::memory::*};
use crate::{
common::instructions::{
args::{ITypeArgs, RTypeArgs},
*,
},
emulator::system::memory::*,
};
fn create_test_processor() -> Processor {
let memory = Box::new(MainStore::new());