emulator: fix errors calling new fallible Instruction::decode
TODO: Add a logger for smarter looking loggiing output
This commit is contained in:
+214
-212
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
use crate::common::instructions::args::{ITypeArgs, RTypeArgs};
|
||||
|
||||
use super::instructions::*;
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user