refactor 2 electric boogaloo
This commit is contained in:
@@ -1,414 +0,0 @@
|
||||
use crate::common::{instructions::encode::Encode, prelude::*};
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Interrupt {
|
||||
Software(u8),
|
||||
}
|
||||
|
||||
pub type Address = u32;
|
||||
|
||||
impl Interrupt {
|
||||
const fn as_u8(self) -> u8 {
|
||||
match self {
|
||||
Self::Software(code) => code,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This should be TryFrom.
|
||||
impl From<u8> for Interrupt {
|
||||
#[allow(unreachable_code)]
|
||||
fn from(_code: u8) -> Self {
|
||||
todo!("Implement this once a hardware interrupt convention is established.");
|
||||
|
||||
// Self::Software(_code)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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,
|
||||
Rg1,
|
||||
Rg2,
|
||||
Rg3,
|
||||
Rg4,
|
||||
Rg5,
|
||||
Rg6,
|
||||
Rg7,
|
||||
Rg8,
|
||||
Rg9,
|
||||
Rga,
|
||||
Rgb,
|
||||
Rgc,
|
||||
Rgd,
|
||||
Rge,
|
||||
Rgf,
|
||||
|
||||
// special purpose registers
|
||||
Acc,
|
||||
Spr,
|
||||
Bpr,
|
||||
Ret,
|
||||
Idr,
|
||||
Mmr,
|
||||
Zero,
|
||||
NoReg,
|
||||
|
||||
// system registers - can't be written to by instructions.
|
||||
Mar,
|
||||
Mdr,
|
||||
Sts,
|
||||
Cir,
|
||||
Pcx,
|
||||
}
|
||||
|
||||
impl Default for Register {
|
||||
fn default() -> Self {
|
||||
Self::NoReg
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for Register {
|
||||
type Error = RegisterParseError;
|
||||
|
||||
fn try_from(idx: u8) -> Result<Self, Self::Error> {
|
||||
if idx > 0x18 {
|
||||
return Err(RegisterParseError::InvalidIndex(idx));
|
||||
}
|
||||
|
||||
Ok(match idx {
|
||||
// System registers are not indexable in the reg file so they cannot be modified by instructions.
|
||||
0x0 => Self::Rg0,
|
||||
0x1 => Self::Rg1,
|
||||
0x2 => Self::Rg2,
|
||||
0x3 => Self::Rg3,
|
||||
0x4 => Self::Rg4,
|
||||
0x5 => Self::Rg5,
|
||||
0x6 => Self::Rg6,
|
||||
0x7 => Self::Rg7,
|
||||
0x8 => Self::Rg8,
|
||||
0x9 => Self::Rg9,
|
||||
0xA => Self::Rga,
|
||||
0xB => Self::Rgb,
|
||||
0xC => Self::Rgc,
|
||||
0xD => Self::Rgd,
|
||||
0xE => Self::Rge,
|
||||
0xF => Self::Rgf,
|
||||
0x10 => Self::Acc,
|
||||
0x11 => Self::Spr,
|
||||
0x12 => Self::Bpr,
|
||||
0x13 => Self::Ret,
|
||||
0x14 => Self::Idr,
|
||||
0x15 => Self::Mmr,
|
||||
0x16 => Self::Zero,
|
||||
0x17 => Self::NoReg,
|
||||
_ => unreachable!("This is already checked for in top `if` branch."),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Register {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Rg0 => write!(f, "rg0"),
|
||||
Self::Rg1 => write!(f, "rg1"),
|
||||
Self::Rg2 => write!(f, "rg2"),
|
||||
Self::Rg3 => write!(f, "rg3"),
|
||||
Self::Rg4 => write!(f, "rg4"),
|
||||
Self::Rg5 => write!(f, "rg5"),
|
||||
Self::Rg6 => write!(f, "rg6"),
|
||||
Self::Rg7 => write!(f, "rg7"),
|
||||
Self::Rg8 => write!(f, "rg8"),
|
||||
Self::Rg9 => write!(f, "rg9"),
|
||||
Self::Rga => write!(f, "rga"),
|
||||
Self::Rgb => write!(f, "rgb"),
|
||||
Self::Rgc => write!(f, "rgc"),
|
||||
Self::Rgd => write!(f, "rgd"),
|
||||
Self::Rge => write!(f, "rge"),
|
||||
Self::Rgf => write!(f, "rgf"),
|
||||
Self::Acc => write!(f, "acc"),
|
||||
Self::Spr => write!(f, "spr"),
|
||||
Self::Bpr => write!(f, "bpr"),
|
||||
Self::Ret => write!(f, "ret"),
|
||||
Self::Idr => write!(f, "idr"),
|
||||
Self::Mmr => write!(f, "mmr"),
|
||||
Self::Zero => write!(f, "zero"),
|
||||
Self::NoReg => write!(f, "noreg"),
|
||||
Self::Mar => write!(f, "mar"),
|
||||
Self::Mdr => write!(f, "mdr"),
|
||||
Self::Sts => write!(f, "sts"),
|
||||
Self::Cir => write!(f, "cir"),
|
||||
Self::Pcx => write!(f, "pcx"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq)]
|
||||
#[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(args::RTypeArgs) = 0x1,
|
||||
MovSigned(args::RTypeArgs) = 0x2,
|
||||
|
||||
LoadByte(args::ITypeArgs) = 0x3,
|
||||
LoadByteSigned(args::ITypeArgs) = 0x4,
|
||||
LoadHalfword(args::ITypeArgs) = 0x5,
|
||||
LoadHalfwordSigned(args::ITypeArgs) = 0x6,
|
||||
LoadWord(args::ITypeArgs) = 0x7,
|
||||
|
||||
StoreByte(args::ITypeArgs) = 0x8,
|
||||
StoreHalfword(args::ITypeArgs) = 0x9,
|
||||
StoreWord(args::ITypeArgs) = 0xA,
|
||||
|
||||
LoadLowerImmediate(args::ITypeArgs) = 0xB,
|
||||
LoadUpperImmediate(args::ITypeArgs) = 0xC,
|
||||
|
||||
// Jump Instructions
|
||||
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(args::RTypeArgs) = 0x14,
|
||||
|
||||
// Arithmetic
|
||||
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(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,
|
||||
IntReturn = 0x23,
|
||||
Halt = 0x24,
|
||||
}
|
||||
|
||||
impl PartialEq for Instruction {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.encode() == other.encode()
|
||||
}
|
||||
}
|
||||
|
||||
impl Instruction {
|
||||
/// Returns the opcode of an instruction.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// The top two bits shall be 0, opcodes are 6-bits long.
|
||||
#[must_use]
|
||||
pub const fn opcode(&self) -> u8 {
|
||||
unsafe { *std::ptr::from_ref::<Self>(self).cast::<u8>() }
|
||||
}
|
||||
|
||||
/// Encodes an [`Instruction`] into a word.
|
||||
#[must_use]
|
||||
pub fn encode(&self) -> u32 {
|
||||
Encode::encode(*self, self.opcode())
|
||||
}
|
||||
|
||||
/// 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 mnemonic(self) -> &'static str {
|
||||
match self {
|
||||
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",
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`InstructionType`] for the given [`Instruction`].
|
||||
#[must_use]
|
||||
pub const fn instruction_type(self) -> InstructionType {
|
||||
Self::instruction_type_from_opcode(self.opcode())
|
||||
}
|
||||
|
||||
/// 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 = ((data >> 26) & 0x3F) 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;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -1,154 +0,0 @@
|
||||
//! Various types of arguments that instructions can take, alongside encoding and decoding logic.
|
||||
|
||||
use crate::common::instructions::Encode;
|
||||
use crate::common::prelude::*;
|
||||
|
||||
/// 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, PartialEq, Eq)]
|
||||
/// 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) & 0x1F) as u8;
|
||||
let r2 = ((data >> 16) & 0x1F) as u8;
|
||||
let immediate = data as u16;
|
||||
|
||||
let r1 = r1.try_into()?;
|
||||
let r2 = r2.try_into()?;
|
||||
|
||||
Ok(Self { immediate, r1, r2 })
|
||||
}
|
||||
}
|
||||
|
||||
/// Used by instructions not using immediates (besides 5 bit shift values).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
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) & 0x1F) as u8;
|
||||
let sr2 = ((data >> 16) & 0x1F) as u8;
|
||||
let dr = ((data >> 11) & 0x1F) as u8;
|
||||
let shamt = ((data >> 6) & 0x1F) 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,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
use crate::common::prelude::*;
|
||||
|
||||
/// Not to be used directly, just call [`Instruction::encode`].
|
||||
pub trait Encode {
|
||||
fn encode(self, opcode: u8) -> u32;
|
||||
}
|
||||
|
||||
/// Encodes a zero argument instruction.
|
||||
fn encode_no_args(opcode: u8) -> u32 {
|
||||
let opcode = u32::from(opcode);
|
||||
let sr1 = Register::NoReg as u32;
|
||||
let sr2 = Register::NoReg as u32;
|
||||
let dr = Register::NoReg as u32;
|
||||
let shamt = 0;
|
||||
|
||||
(opcode << 26) | (sr1 << 21) | (sr2 << 16) | (dr << 11) | (shamt << 6)
|
||||
}
|
||||
|
||||
/// Expands to a match statement that calls encode on instructions that implement [`Encode`]:
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// ```rs
|
||||
/// encode_instruction!(self, with_args: [...], no_args: [...], special: [...] )
|
||||
/// ```
|
||||
macro_rules! encode_instruction {
|
||||
($self:expr, with_args: [$($variant:ident),+ $(,)?], no_args: [$($no_arg_variant:ident),* $(,)?] $(, special: [$($special:pat => $body:expr),* $(,)?])?) => {
|
||||
match $self {
|
||||
$(
|
||||
Instruction::$variant(args) => args.encode($self.opcode()),
|
||||
)+
|
||||
$(
|
||||
Instruction::$no_arg_variant => encode_no_args($self.opcode()),
|
||||
)*
|
||||
$($(
|
||||
$special => $body,
|
||||
)*)?
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl Encode for Instruction {
|
||||
fn encode(self, _: u8) -> u32 {
|
||||
encode_instruction!(
|
||||
self,
|
||||
with_args: [
|
||||
Mov, MovSigned, LoadByte, LoadByteSigned, LoadHalfword,
|
||||
LoadHalfwordSigned, LoadWord, StoreByte, StoreHalfword,
|
||||
StoreWord, LoadLowerImmediate, LoadUpperImmediate, Jump,
|
||||
JumpEq, JumpNeq, JumpGt, JumpGe, JumpLt, JumpLe, Compare,
|
||||
Add, Sub, Increment, Decrement, ShiftLeft, ShiftRight,
|
||||
And, Or, Not, Xor, Nand, Nor, Xnor
|
||||
],
|
||||
no_args: [Nop, IntReturn, Halt],
|
||||
special: [
|
||||
Self::Interrupt(_) => todo!()
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -1,95 +0,0 @@
|
||||
use crate::common::prelude::*;
|
||||
|
||||
#[test]
|
||||
fn test_encode_nop() {
|
||||
let no_reg = Register::NoReg as u32;
|
||||
let no_op = u32::from(Instruction::Nop.opcode());
|
||||
|
||||
let expected = no_op << 26 | no_reg << 21 | no_reg << 16 | no_reg << 11;
|
||||
let got = Instruction::Nop.encode();
|
||||
|
||||
assert_eq!(expected, got);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_mov() {
|
||||
let rg0 = Register::Rg0 as u32;
|
||||
let rg1 = Register::Rg1 as u32;
|
||||
let no_reg = Register::NoReg as u32;
|
||||
|
||||
let instruction = Instruction::Mov(RTypeArgs::new(
|
||||
Some(Register::Rg0),
|
||||
None,
|
||||
Some(Register::Rg1),
|
||||
None,
|
||||
));
|
||||
let mov = u32::from(instruction.opcode());
|
||||
|
||||
let expected = mov << 26 | rg0 << 21 | no_reg << 16 | rg1 << 11;
|
||||
let got = instruction.encode();
|
||||
|
||||
assert_eq!(expected, got);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_load_byte() {
|
||||
let rg0 = Register::Rg0 as u32;
|
||||
let rg1 = Register::Rg1 as u32;
|
||||
let immediate = 100;
|
||||
|
||||
let instruction = Instruction::LoadByte(ITypeArgs::new(
|
||||
immediate,
|
||||
Some(Register::Rg0),
|
||||
Some(Register::Rg1),
|
||||
));
|
||||
let load_byte = u32::from(instruction.opcode());
|
||||
|
||||
let expected = load_byte << 26 | rg0 << 21 | rg1 << 16 | u32::from(immediate);
|
||||
let got = instruction.encode();
|
||||
|
||||
assert_eq!(expected, got);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_shift_left_shamt() {
|
||||
let rg0 = Register::Rg0 as u32;
|
||||
let no_reg = Register::NoReg as u32;
|
||||
|
||||
let shift_amount = 5;
|
||||
|
||||
let instruction = Instruction::ShiftLeft(RTypeArgs::new(
|
||||
Some(Register::Rg0),
|
||||
None,
|
||||
None,
|
||||
Some(shift_amount),
|
||||
));
|
||||
let shift_left = u32::from(instruction.opcode());
|
||||
|
||||
let expected =
|
||||
shift_left << 26 | rg0 << 21 | no_reg << 16 | no_reg << 11 | u32::from(shift_amount) << 6;
|
||||
|
||||
let got = instruction.encode();
|
||||
|
||||
assert_eq!(expected, got);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_shift_left_reg() {
|
||||
let rg0 = Register::Rg0 as u32;
|
||||
let rg1 = Register::Rg1 as u32;
|
||||
let no_reg = Register::NoReg as u32;
|
||||
|
||||
let instruction = Instruction::ShiftLeft(RTypeArgs::new(
|
||||
Some(Register::Rg0),
|
||||
Some(Register::Rg1),
|
||||
None,
|
||||
None,
|
||||
));
|
||||
let shift_left = u32::from(instruction.opcode());
|
||||
|
||||
let expected = shift_left << 26 | rg0 << 21 | rg1 << 16 | no_reg << 11;
|
||||
|
||||
let got = instruction.encode();
|
||||
|
||||
assert_eq!(expected, got);
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
//! All the errors that may be returned from [`instructions`].
|
||||
|
||||
use crate::common::prelude::*;
|
||||
|
||||
#[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,211 +0,0 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
use crate::common::prelude::*;
|
||||
|
||||
#[test]
|
||||
fn test_opcode_nop() {
|
||||
let instr = Instruction::Nop;
|
||||
assert_eq!(instr.opcode(), 0x0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_opcode_data_transfer() {
|
||||
let args = RTypeArgs::new(None, None, None, None);
|
||||
assert_eq!(Instruction::Mov(args).opcode(), 0x1);
|
||||
assert_eq!(Instruction::MovSigned(args).opcode(), 0x2);
|
||||
|
||||
let iargs = ITypeArgs::new(0, None, None);
|
||||
assert_eq!(Instruction::LoadByte(iargs).opcode(), 0x3);
|
||||
assert_eq!(Instruction::LoadByteSigned(iargs).opcode(), 0x4);
|
||||
assert_eq!(Instruction::LoadHalfword(iargs).opcode(), 0x5);
|
||||
assert_eq!(Instruction::LoadHalfwordSigned(iargs).opcode(), 0x6);
|
||||
assert_eq!(Instruction::LoadWord(iargs).opcode(), 0x7);
|
||||
assert_eq!(Instruction::StoreByte(iargs).opcode(), 0x8);
|
||||
assert_eq!(Instruction::StoreHalfword(iargs).opcode(), 0x9);
|
||||
assert_eq!(Instruction::StoreWord(iargs).opcode(), 0xA);
|
||||
assert_eq!(Instruction::LoadLowerImmediate(iargs).opcode(), 0xB);
|
||||
assert_eq!(Instruction::LoadUpperImmediate(iargs).opcode(), 0xC);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_opcode_jump_instructions() {
|
||||
let args = ITypeArgs::new(0, None, None);
|
||||
assert_eq!(Instruction::Jump(args).opcode(), 0xD);
|
||||
assert_eq!(Instruction::JumpEq(args).opcode(), 0xE);
|
||||
assert_eq!(Instruction::JumpNeq(args).opcode(), 0xF);
|
||||
assert_eq!(Instruction::JumpGt(args).opcode(), 0x10);
|
||||
assert_eq!(Instruction::JumpGe(args).opcode(), 0x11);
|
||||
assert_eq!(Instruction::JumpLt(args).opcode(), 0x12);
|
||||
assert_eq!(Instruction::JumpLe(args).opcode(), 0x13);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_opcode_arithmetic() {
|
||||
let args = RTypeArgs::new(None, None, None, None);
|
||||
assert_eq!(Instruction::Compare(args).opcode(), 0x14);
|
||||
assert_eq!(Instruction::Increment(args).opcode(), 0x15);
|
||||
assert_eq!(Instruction::Decrement(args).opcode(), 0x16);
|
||||
assert_eq!(Instruction::ShiftLeft(args).opcode(), 0x17);
|
||||
assert_eq!(Instruction::ShiftRight(args).opcode(), 0x18);
|
||||
assert_eq!(Instruction::Add(args).opcode(), 0x19);
|
||||
assert_eq!(Instruction::Sub(args).opcode(), 0x1A);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_opcode_logical() {
|
||||
let args = RTypeArgs::new(None, None, None, None);
|
||||
assert_eq!(Instruction::And(args).opcode(), 0x1B);
|
||||
assert_eq!(Instruction::Or(args).opcode(), 0x1C);
|
||||
assert_eq!(Instruction::Not(args).opcode(), 0x1D);
|
||||
assert_eq!(Instruction::Xor(args).opcode(), 0x1E);
|
||||
assert_eq!(Instruction::Nand(args).opcode(), 0x1F);
|
||||
assert_eq!(Instruction::Nor(args).opcode(), 0x20);
|
||||
assert_eq!(Instruction::Xnor(args).opcode(), 0x21);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_opcode_misc() {
|
||||
let interrupt = Interrupt::Software(5);
|
||||
assert_eq!(Instruction::Interrupt(interrupt).opcode(), 0x22);
|
||||
assert_eq!(Instruction::IntReturn.opcode(), 0x23);
|
||||
assert_eq!(Instruction::Halt.opcode(), 0x24);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_opcode_with_different_args() {
|
||||
let args1 = RTypeArgs::new(
|
||||
Some(Register::Rg0),
|
||||
Some(Register::Rg1),
|
||||
Some(Register::Rg2),
|
||||
Some(5),
|
||||
);
|
||||
let args2 = RTypeArgs::new(
|
||||
Some(Register::Acc),
|
||||
Some(Register::Spr),
|
||||
Some(Register::Bpr),
|
||||
Some(31),
|
||||
);
|
||||
|
||||
// Opcode should be the same regardless of arguments
|
||||
assert_eq!(
|
||||
Instruction::Add(args1).opcode(),
|
||||
Instruction::Add(args2).opcode()
|
||||
);
|
||||
assert_eq!(
|
||||
Instruction::Sub(args1).opcode(),
|
||||
Instruction::Sub(args2).opcode()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_opcode_boundary_values() {
|
||||
// Test highest opcode value
|
||||
assert_eq!(Instruction::Halt.opcode(), 0x24);
|
||||
|
||||
// Test lowest opcode value
|
||||
assert_eq!(Instruction::Nop.opcode(), 0x0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_instruction_decode_nop() {
|
||||
let instr = Instruction::Nop;
|
||||
let encoded = instr.encode();
|
||||
let decoded = Instruction::decode(encoded).unwrap();
|
||||
assert_eq!(instr, decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_instruction_decode_data_transfer() {
|
||||
let args = RTypeArgs::new(
|
||||
Some(Register::Rg0),
|
||||
Some(Register::Rg1),
|
||||
Some(Register::Rg2),
|
||||
Some(5),
|
||||
);
|
||||
let instr = Instruction::Mov(args);
|
||||
let encoded = instr.encode();
|
||||
let decoded = Instruction::decode(encoded).unwrap();
|
||||
assert_eq!(instr, decoded);
|
||||
|
||||
let iargs = ITypeArgs::new(100, Some(Register::Rg3), Some(Register::Rg4));
|
||||
let instr = Instruction::LoadWord(iargs);
|
||||
let encoded = instr.encode();
|
||||
let decoded = Instruction::decode(encoded).unwrap();
|
||||
assert_eq!(instr, decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_instruction_decode_jump() {
|
||||
let args = ITypeArgs::new(200, Some(Register::Acc), Some(Register::Spr));
|
||||
let instr = Instruction::Jump(args);
|
||||
let encoded = instr.encode();
|
||||
let decoded = Instruction::decode(encoded).unwrap();
|
||||
assert_eq!(instr, decoded);
|
||||
|
||||
let instr = Instruction::JumpEq(args);
|
||||
let encoded = instr.encode();
|
||||
let decoded = Instruction::decode(encoded).unwrap();
|
||||
assert_eq!(instr, decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_instruction_decode_arithmetic() {
|
||||
let args = RTypeArgs::new(
|
||||
Some(Register::Bpr),
|
||||
Some(Register::Rg7),
|
||||
Some(Register::Rgf),
|
||||
Some(31),
|
||||
);
|
||||
let instr = Instruction::Add(args);
|
||||
let encoded = instr.encode();
|
||||
let decoded = Instruction::decode(encoded).unwrap();
|
||||
assert_eq!(instr, decoded);
|
||||
|
||||
let instr = Instruction::Compare(args);
|
||||
let encoded = instr.encode();
|
||||
let decoded = Instruction::decode(encoded).unwrap();
|
||||
assert_eq!(instr, decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_instruction_decode_logical() {
|
||||
let args = RTypeArgs::new(
|
||||
Some(Register::Rg8),
|
||||
Some(Register::Rg9),
|
||||
Some(Register::Rga),
|
||||
Some(15),
|
||||
);
|
||||
let instr = Instruction::And(args);
|
||||
let encoded = instr.encode();
|
||||
let decoded = Instruction::decode(encoded).unwrap();
|
||||
assert_eq!(instr, decoded);
|
||||
|
||||
let instr = Instruction::Xor(args);
|
||||
let encoded = instr.encode();
|
||||
let decoded = Instruction::decode(encoded).unwrap();
|
||||
assert_eq!(instr, decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_instruction_decode_misc() {
|
||||
let instr = Instruction::Halt;
|
||||
let encoded = instr.encode();
|
||||
let decoded = Instruction::decode(encoded).unwrap();
|
||||
assert_eq!(instr, decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_instruction_decode_invalid() {
|
||||
// Test with invalid opcode.
|
||||
let invalid_encoded = 0xFF00_0000;
|
||||
assert!(Instruction::decode(invalid_encoded).is_err());
|
||||
}
|
||||
|
||||
// TODO: Get interrupts working.
|
||||
// #[test]
|
||||
// fn test_instruction_decode_interrupt() {
|
||||
// let interrupt = Interrupt::Software(10);
|
||||
// let instr = Instruction::Interrupt(interrupt);
|
||||
// let encoded = instr.encode();
|
||||
// let decoded = Instruction::decode(encoded).unwrap();
|
||||
// assert_eq!(instr, decoded);
|
||||
// }
|
||||
@@ -1,8 +0,0 @@
|
||||
pub mod instructions;
|
||||
|
||||
pub mod prelude {
|
||||
//! A collection of types you should definitely import when working with this crate.
|
||||
pub use super::instructions::{
|
||||
Address, Instruction, InstructionType, Interrupt, Register, args::*, errors::*,
|
||||
};
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
pub mod system;
|
||||
pub mod ui;
|
||||
@@ -1,158 +0,0 @@
|
||||
use std::{
|
||||
sync::{
|
||||
Arc, Mutex, MutexGuard,
|
||||
mpsc::{self, Receiver, Sender},
|
||||
},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
common::instructions::{Instruction, Register},
|
||||
emulator::system::{
|
||||
model::{Command, Running, State},
|
||||
processor::Processor,
|
||||
},
|
||||
};
|
||||
|
||||
pub fn run_emulator(
|
||||
cmd_rx: &Receiver<Command>,
|
||||
state_tx: &Sender<State>,
|
||||
mut processor: Processor,
|
||||
) {
|
||||
println!("starting");
|
||||
|
||||
let mut running = Running::Paused;
|
||||
let mut addr = 0u32;
|
||||
let size = 256;
|
||||
|
||||
let memory_view = processor.memory.read_range(addr, size);
|
||||
let initial_state = state(&mut processor, running, 0, memory_view);
|
||||
let _ = state_tx.send(initial_state);
|
||||
|
||||
let mut instruction_count = 0;
|
||||
|
||||
loop {
|
||||
println!("looping");
|
||||
|
||||
let cmd = if running == Running::Running {
|
||||
match cmd_rx.try_recv() {
|
||||
Ok(cmd) => Some(cmd),
|
||||
Err(mpsc::TryRecvError::Empty) => None,
|
||||
Err(mpsc::TryRecvError::Disconnected) => break,
|
||||
}
|
||||
} else {
|
||||
match cmd_rx.recv() {
|
||||
Ok(cmd) => Some(cmd),
|
||||
Err(_) => break,
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(cmd) = cmd {
|
||||
println!("Received command: {:?}", cmd);
|
||||
|
||||
match cmd {
|
||||
Command::Start => {
|
||||
running = Running::Running;
|
||||
println!("Emulator started");
|
||||
}
|
||||
Command::Stop => {
|
||||
running = Running::Paused;
|
||||
println!("Emulator stopped");
|
||||
}
|
||||
Command::Reset => {
|
||||
running = Running::Paused;
|
||||
processor.reset();
|
||||
instruction_count = 0;
|
||||
println!("Emulator rebooted");
|
||||
}
|
||||
Command::Step => {
|
||||
running = Running::Paused;
|
||||
|
||||
// Execute one cycle.
|
||||
match processor.cycle() {
|
||||
Ok(_) => {}
|
||||
Err(why) => {
|
||||
let pcx = processor.get(Register::Pcx);
|
||||
eprintln!("Could not decode instruction at {pcx:x}. Reason: {why}");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
instruction_count += 1;
|
||||
println!("Stepped one instruction");
|
||||
}
|
||||
Command::Read(new, _size) => {
|
||||
addr = new;
|
||||
println!("Memory view for address 0x{addr:08x}");
|
||||
}
|
||||
Command::Write(offset, data) => {
|
||||
processor.memory.write_range(offset, data);
|
||||
println!("Program loaded");
|
||||
}
|
||||
Command::Interrupt(_interrupt) => {
|
||||
todo!("implement interrupts")
|
||||
}
|
||||
}
|
||||
|
||||
let memory_view = processor.memory.read_range(addr, size);
|
||||
let state = state(&mut processor, running, instruction_count, memory_view);
|
||||
|
||||
let _ = state_tx.send(state);
|
||||
}
|
||||
|
||||
if running == Running::Running {
|
||||
let mut update = false;
|
||||
|
||||
// Execute one cycle.
|
||||
let instruction = match processor.cycle() {
|
||||
Ok(instruction) => instruction,
|
||||
Err(why) => {
|
||||
let pcx = processor.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;
|
||||
}
|
||||
|
||||
instruction_count += 1;
|
||||
|
||||
// Send state updates every 100 instructions
|
||||
if instruction_count % 100 == 0 {
|
||||
update = true;
|
||||
}
|
||||
|
||||
if update {
|
||||
let memory_view = processor.memory.read_range(addr, size);
|
||||
let state = state(&mut processor, running, instruction_count, memory_view);
|
||||
let _ = state_tx.send(state);
|
||||
}
|
||||
} else {
|
||||
thread::sleep(Duration::from_millis(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn state(
|
||||
cpu_lock: &mut Processor,
|
||||
running: Running,
|
||||
instruction_count: usize,
|
||||
memory_view: Vec<u8>,
|
||||
) -> State {
|
||||
State {
|
||||
// TODO: Replace with actual register access from your CPU.
|
||||
reg_file: cpu_lock.registers,
|
||||
running,
|
||||
instructions: instruction_count,
|
||||
stack_view: cpu_lock.get_stack(32),
|
||||
memory_view,
|
||||
display_view: cpu_lock.display(),
|
||||
error: None,
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub trait MemoryUnit: Send + Sync {
|
||||
fn reset(&mut self);
|
||||
fn read_byte(&mut self, addr: u32) -> u8;
|
||||
fn write_byte(&mut self, addr: u32, value: u8);
|
||||
fn read_word(&mut self, addr: u32) -> u32;
|
||||
fn write_word(&mut self, addr: u32, value: u32);
|
||||
fn read_range(&mut self, addr: u32, size: u32) -> Vec<u8>;
|
||||
fn write_range(&mut self, addr: u32, value: Vec<u8>);
|
||||
}
|
||||
|
||||
pub struct MainStore {
|
||||
pub data: HashMap<u32, Block>,
|
||||
}
|
||||
|
||||
pub struct Block {
|
||||
data: [u8; 256],
|
||||
}
|
||||
|
||||
impl Default for MainStore {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl MainStore {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
data: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
const fn segment_addr(addr: u32) -> (u32, u8) {
|
||||
(addr / 256, (addr % 256) as u8)
|
||||
}
|
||||
|
||||
fn mut_block(&mut self, addr: u32) -> &mut Block {
|
||||
self.data
|
||||
.entry(addr)
|
||||
.or_insert_with(|| Block { data: [0; 256] });
|
||||
|
||||
self.data.get_mut(&addr).map_or_else(
|
||||
|| panic!("Could not fetch block with address {addr:x?}"),
|
||||
|block| block,
|
||||
)
|
||||
}
|
||||
|
||||
fn block(&mut self, addr: u32) -> &Block {
|
||||
self.data
|
||||
.entry(addr)
|
||||
.or_insert_with(|| Block { data: [0; 256] });
|
||||
|
||||
self.data.get(&addr).map_or_else(
|
||||
|| panic!("Could not fetch block with address {addr:x?}"),
|
||||
|block| block,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl MemoryUnit for MainStore {
|
||||
fn reset(&mut self) {
|
||||
self.data.clear();
|
||||
}
|
||||
|
||||
fn read_byte(&mut self, addr: u32) -> u8 {
|
||||
let (block_addr, offset) = Self::segment_addr(addr);
|
||||
let block = self.block(block_addr);
|
||||
block.data[offset as usize]
|
||||
}
|
||||
|
||||
fn read_word(&mut self, addr: u32) -> u32 {
|
||||
let (block_addr, offset) = Self::segment_addr(addr);
|
||||
let block = self.mut_block(block_addr);
|
||||
let mut bytes = [0; 4];
|
||||
bytes[0] = block.data[offset as usize];
|
||||
bytes[1] = block.data[(offset + 1) as usize];
|
||||
bytes[2] = block.data[(offset + 2) as usize];
|
||||
bytes[3] = block.data[(offset + 3) as usize];
|
||||
u32::from_be_bytes(bytes)
|
||||
}
|
||||
|
||||
fn read_range(&mut self, addr: u32, size: u32) -> Vec<u8> {
|
||||
let mut data = Vec::with_capacity(size as usize);
|
||||
for i in 0..size {
|
||||
data.push(self.read_byte(addr + i));
|
||||
}
|
||||
data
|
||||
}
|
||||
|
||||
fn write_byte(&mut self, addr: u32, value: u8) {
|
||||
let (block_addr, offset) = Self::segment_addr(addr);
|
||||
let block = self.mut_block(block_addr);
|
||||
block.data[offset as usize] = value;
|
||||
}
|
||||
|
||||
fn write_word(&mut self, addr: u32, value: u32) {
|
||||
let (block_addr, offset) = Self::segment_addr(addr);
|
||||
let block = self.mut_block(block_addr);
|
||||
block.data[offset as usize] = (value >> 24) as u8;
|
||||
block.data[(offset + 1) as usize] = (value >> 16) as u8;
|
||||
block.data[(offset + 2) as usize] = (value >> 8) as u8;
|
||||
block.data[(offset + 3) as usize] = value as u8;
|
||||
}
|
||||
|
||||
fn write_range(&mut self, addr: u32, value: Vec<u8>) {
|
||||
for byte in value {
|
||||
let (block_addr, offset) = Self::segment_addr(addr);
|
||||
let block = self.mut_block(block_addr);
|
||||
block.data[offset as usize] = byte;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
pub mod emulator;
|
||||
pub mod memory;
|
||||
pub mod model;
|
||||
pub mod processor;
|
||||
@@ -1,223 +0,0 @@
|
||||
use crate::common::prelude::*;
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
|
||||
pub enum Running {
|
||||
Running,
|
||||
Paused,
|
||||
Halted,
|
||||
}
|
||||
|
||||
pub trait IODevice: Send + Sync {
|
||||
fn read_byte(&mut self, addr: u32) -> u8;
|
||||
fn write_byte(&mut self, addr: u32, value: u8);
|
||||
fn read_range(&mut self, addr: u32, size: u32) -> Vec<u8>;
|
||||
fn write_range(&mut self, addr: u32, value: Vec<u8>);
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
pub enum Command {
|
||||
Start,
|
||||
Stop,
|
||||
Step,
|
||||
Reset,
|
||||
Interrupt(Interrupt),
|
||||
|
||||
// Performs direct read/write operations on the emulator's memory.
|
||||
Read(Address, u32),
|
||||
Write(Address, Vec<u8>),
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct RegFile {
|
||||
// General Purpose Registers
|
||||
rg0: u32,
|
||||
rg1: u32,
|
||||
rg2: u32,
|
||||
rg3: u32,
|
||||
rg4: u32,
|
||||
rg5: u32,
|
||||
rg6: u32,
|
||||
rg7: u32,
|
||||
rg8: u32,
|
||||
rg9: u32,
|
||||
rga: u32,
|
||||
rgb: u32,
|
||||
rgc: u32,
|
||||
rgd: u32,
|
||||
rge: u32,
|
||||
rgf: u32,
|
||||
|
||||
// Special Purpose Registers
|
||||
acc: u32,
|
||||
spr: u32,
|
||||
bpr: u32,
|
||||
ret: u32,
|
||||
idr: u32,
|
||||
mmr: u32,
|
||||
|
||||
// System Registers
|
||||
mar: u32,
|
||||
mdr: u32,
|
||||
sts: u32,
|
||||
cir: u32,
|
||||
pcx: u32,
|
||||
}
|
||||
|
||||
impl RegFile {
|
||||
#[must_use]
|
||||
pub fn all(&self) -> Vec<(&str, u32)> {
|
||||
vec![
|
||||
("Rg0", self.rg0),
|
||||
("Rg1", self.rg1),
|
||||
("Rg2", self.rg2),
|
||||
("Rg3", self.rg3),
|
||||
("Rg4", self.rg4),
|
||||
("Rg5", self.rg5),
|
||||
("Rg6", self.rg6),
|
||||
("Rg7", self.rg7),
|
||||
("Rg8", self.rg8),
|
||||
("Rg9", self.rg9),
|
||||
("Rga", self.rga),
|
||||
("Rgb", self.rgb),
|
||||
("Rgc", self.rgc),
|
||||
("Rgd", self.rgd),
|
||||
("Rge", self.rge),
|
||||
("Rgf", self.rgf),
|
||||
("Acc", self.acc),
|
||||
("Spr", self.spr),
|
||||
("Bpr", self.bpr),
|
||||
("Ret", self.ret),
|
||||
("Idr", self.idr),
|
||||
("Mmr", self.mmr),
|
||||
("Mar", self.mar),
|
||||
("Mdr", self.mdr),
|
||||
("Sts", self.sts),
|
||||
("Cir", self.cir),
|
||||
("Pcx", self.pcx),
|
||||
]
|
||||
}
|
||||
|
||||
pub const fn reset(&mut self) {
|
||||
self.rg0 = 0;
|
||||
self.rg1 = 0;
|
||||
self.rg2 = 0;
|
||||
self.rg3 = 0;
|
||||
self.rg4 = 0;
|
||||
self.rg5 = 0;
|
||||
self.rg6 = 0;
|
||||
self.rg7 = 0;
|
||||
self.rg8 = 0;
|
||||
self.rg9 = 0;
|
||||
self.rga = 0;
|
||||
self.rgb = 0;
|
||||
self.rgc = 0;
|
||||
self.rgd = 0;
|
||||
self.rge = 0;
|
||||
self.rgf = 0;
|
||||
self.acc = 0;
|
||||
self.spr = 0;
|
||||
self.bpr = 0;
|
||||
self.ret = 0;
|
||||
self.idr = 0;
|
||||
self.mmr = 0;
|
||||
self.mar = 0;
|
||||
self.mdr = 0;
|
||||
self.sts = 0;
|
||||
self.cir = 0;
|
||||
self.pcx = 0;
|
||||
}
|
||||
|
||||
pub fn reg(&mut self, reg: Register) -> &mut u32 {
|
||||
match reg {
|
||||
Register::Rg0 => &mut self.rg0,
|
||||
Register::Rg1 => &mut self.rg1,
|
||||
Register::Rg2 => &mut self.rg2,
|
||||
Register::Rg3 => &mut self.rg3,
|
||||
Register::Rg4 => &mut self.rg4,
|
||||
Register::Rg5 => &mut self.rg5,
|
||||
Register::Rg6 => &mut self.rg6,
|
||||
Register::Rg7 => &mut self.rg7,
|
||||
Register::Rg8 => &mut self.rg8,
|
||||
Register::Rg9 => &mut self.rg9,
|
||||
Register::Rga => &mut self.rga,
|
||||
Register::Rgb => &mut self.rgb,
|
||||
Register::Rgc => &mut self.rgc,
|
||||
Register::Rgd => &mut self.rgd,
|
||||
Register::Rge => &mut self.rge,
|
||||
Register::Rgf => &mut self.rgf,
|
||||
Register::Acc => &mut self.acc,
|
||||
Register::Spr => &mut self.spr,
|
||||
Register::Bpr => &mut self.bpr,
|
||||
Register::Ret => &mut self.ret,
|
||||
Register::Idr => &mut self.idr,
|
||||
Register::Mmr => &mut self.mmr,
|
||||
Register::Mar => &mut self.mar,
|
||||
Register::Mdr => &mut self.mdr,
|
||||
Register::Sts => &mut self.sts,
|
||||
Register::Cir => &mut self.cir,
|
||||
Register::Pcx => &mut self.pcx,
|
||||
_ => panic!("Invalid register."),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get(&self, reg: Register) -> u32 {
|
||||
match reg {
|
||||
Register::Rg0 => self.rg0,
|
||||
Register::Rg1 => self.rg1,
|
||||
Register::Rg2 => self.rg2,
|
||||
Register::Rg3 => self.rg3,
|
||||
Register::Rg4 => self.rg4,
|
||||
Register::Rg5 => self.rg5,
|
||||
Register::Rg6 => self.rg6,
|
||||
Register::Rg7 => self.rg7,
|
||||
Register::Rg8 => self.rg8,
|
||||
Register::Rg9 => self.rg9,
|
||||
Register::Rga => self.rga,
|
||||
Register::Rgb => self.rgb,
|
||||
Register::Rgc => self.rgc,
|
||||
Register::Rgd => self.rgd,
|
||||
Register::Rge => self.rge,
|
||||
Register::Rgf => self.rgf,
|
||||
Register::Acc => self.acc,
|
||||
Register::Spr => self.spr,
|
||||
Register::Bpr => self.bpr,
|
||||
Register::Ret => self.ret,
|
||||
Register::Idr => self.idr,
|
||||
Register::Mmr => self.mmr,
|
||||
Register::Mar => self.mar,
|
||||
Register::Mdr => self.mdr,
|
||||
Register::Sts => self.sts,
|
||||
Register::Cir => self.cir,
|
||||
Register::Pcx => self.pcx,
|
||||
Register::Zero => 0,
|
||||
_ => panic!("Invalid register."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct State {
|
||||
pub reg_file: RegFile,
|
||||
pub running: Running,
|
||||
pub instructions: usize,
|
||||
|
||||
// Memory access views
|
||||
pub stack_view: Vec<u8>,
|
||||
pub memory_view: Vec<u8>,
|
||||
pub display_view: Vec<u8>,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for State {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
reg_file: RegFile::default(),
|
||||
running: Running::Paused,
|
||||
instructions: 0,
|
||||
stack_view: vec![],
|
||||
memory_view: vec![],
|
||||
display_view: vec![],
|
||||
error: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,432 +0,0 @@
|
||||
use std::{
|
||||
cmp::{max, min},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
common::instructions::{Instruction, Interrupt, Register, errors::InstructionDecodeError},
|
||||
emulator::system::{
|
||||
memory::MemoryUnit,
|
||||
model::{IODevice, RegFile},
|
||||
},
|
||||
};
|
||||
|
||||
pub struct Processor {
|
||||
pub memory: Box<dyn MemoryUnit>,
|
||||
pub registers: RegFile,
|
||||
pub halted: bool,
|
||||
pub io_devices: Vec<Arc<dyn IODevice>>,
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_ref_mut)]
|
||||
impl Processor {
|
||||
#[must_use]
|
||||
pub fn new(memory: Box<dyn MemoryUnit>, io_devices: Vec<Arc<dyn IODevice>>) -> Self {
|
||||
Self {
|
||||
memory,
|
||||
registers: RegFile::default(),
|
||||
halted: false,
|
||||
io_devices,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
// set all registers to zero
|
||||
// run memory.reset()
|
||||
self.registers.reset();
|
||||
self.memory.reset();
|
||||
}
|
||||
|
||||
pub fn cycle(&mut self) -> Result<Instruction, InstructionDecodeError> {
|
||||
self.halted = false;
|
||||
|
||||
// Get value from PCX.
|
||||
let addr = self.fetch();
|
||||
// Increment PCX.
|
||||
self.advance();
|
||||
|
||||
// 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].
|
||||
*self.reg(Register::Mar) = val;
|
||||
|
||||
// Decode and execute the instruction.
|
||||
let instruction = Instruction::decode(val)?;
|
||||
|
||||
instruction.execute(self);
|
||||
|
||||
Ok(instruction)
|
||||
}
|
||||
|
||||
fn fetch(&self) -> u32 {
|
||||
self.get(Register::Pcx)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get(&self, reg: Register) -> u32 {
|
||||
self.registers.get(reg)
|
||||
}
|
||||
|
||||
pub fn reg(&mut self, reg: Register) -> &mut u32 {
|
||||
self.registers.reg(reg)
|
||||
}
|
||||
|
||||
pub fn display(&mut self) -> Vec<u8> {
|
||||
self.memory.read_range(0x20000, 2000)
|
||||
}
|
||||
|
||||
pub fn cmp(&mut self, a: u32, b: u32) {
|
||||
self.set_flag(Flag::Equal, a == b);
|
||||
self.set_flag(Flag::GreaterThan, a > b);
|
||||
self.set_flag(Flag::LessThan, a < b);
|
||||
}
|
||||
|
||||
// stack operations
|
||||
|
||||
pub fn push(&mut self, value: u32) {
|
||||
let stack_ptr = self.get(Register::Spr);
|
||||
*self.reg(Register::Spr) += 4;
|
||||
self.memory.write_word(stack_ptr, value);
|
||||
}
|
||||
|
||||
pub fn pop(&mut self) -> u32 {
|
||||
*self.reg(Register::Spr) -= 4;
|
||||
self.memory.read_word(self.get(Register::Spr))
|
||||
}
|
||||
|
||||
// functions to set new state
|
||||
|
||||
fn set_flag(&mut self, flag: Flag, value: bool) {
|
||||
if value {
|
||||
*self.reg(Register::Sts) |= flag as u32;
|
||||
} else {
|
||||
*self.reg(Register::Sts) &= !(flag as u32);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_flag(&self, flag: Flag) -> bool {
|
||||
self.get(Register::Sts) & (flag as u32) != 0
|
||||
}
|
||||
|
||||
fn advance(&mut self) {
|
||||
// increment PCX
|
||||
*self.reg(Register::Pcx) += 4;
|
||||
}
|
||||
|
||||
fn jump(&mut self, reg: Register, offset: u16) {
|
||||
*self.reg(Register::Pcx) = self.get(reg) + u32::from(offset);
|
||||
}
|
||||
|
||||
fn begin_interrupt(&mut self, _int: Interrupt) {
|
||||
// first we get the address of the interrupt descriptor table.
|
||||
todo!();
|
||||
}
|
||||
|
||||
fn end_interrupt(&mut self) {
|
||||
todo!();
|
||||
}
|
||||
|
||||
pub fn get_stack(&mut self, n: u32) -> Vec<u8> {
|
||||
let addr = self.get(Register::Spr);
|
||||
let size = n * 4;
|
||||
// returns the stack
|
||||
self.memory.read_range(
|
||||
max(addr, 0), // ensures that we cannot read from a negative address
|
||||
min(size, addr), // ensures we don't read above the top of the stack
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[expect(dead_code)]
|
||||
enum Flag {
|
||||
Equal = 1,
|
||||
GreaterThan = 2,
|
||||
LessThan = 4,
|
||||
Zero = 8,
|
||||
Positive = 16,
|
||||
Negative = 32,
|
||||
Carry = 64,
|
||||
UserMode = 128,
|
||||
InterruptsEnabled = 256,
|
||||
}
|
||||
|
||||
trait Executable {
|
||||
fn execute(self, cpu: &mut Processor);
|
||||
}
|
||||
|
||||
impl Executable for Instruction {
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn execute(self, cpu: &mut Processor) {
|
||||
match self {
|
||||
// No operation - a blank line.
|
||||
Self::Nop => {}
|
||||
|
||||
// Copies from SrcReg to a.drReg.
|
||||
Self::Mov(a) => {
|
||||
*cpu.reg(a.dr) = cpu.get(a.sr1);
|
||||
}
|
||||
|
||||
// Copies from SrcReg to a.drReg, sign extending the value to take up a full word.
|
||||
Self::MovSigned(a) => {
|
||||
*cpu.reg(a.dr) = sign_extend(cpu.get(a.sr1));
|
||||
}
|
||||
|
||||
// Loads a byte from memory address (base + offset) into a.drReg. The effective address must be byte-aligned.
|
||||
Self::LoadByte(a) => {
|
||||
*cpu.reg(a.r2) =
|
||||
u32::from(cpu.memory.read_byte(cpu.get(a.r1) + u32::from(a.immediate)));
|
||||
}
|
||||
|
||||
// Loads a sign-extended byte from memory address (base + offset) into a.drReg. The effective address must be byte-aligned.
|
||||
Self::LoadByteSigned(a) => {
|
||||
*cpu.reg(a.r2) = sign_extend(u32::from(
|
||||
cpu.memory.read_byte(cpu.get(a.r1) + u32::from(a.immediate)),
|
||||
));
|
||||
}
|
||||
|
||||
// Loads a half-word from memory address (base + offset) into a.drReg. The effective address must be 2-byte-aligned.
|
||||
Self::LoadHalfword(a) => {
|
||||
// we read an entire word, then right shift so we only get the first half of the word
|
||||
*cpu.reg(a.r2) = cpu.memory.read_word(cpu.get(a.r1) + u32::from(a.immediate)) >> 16;
|
||||
}
|
||||
|
||||
// Loads a sign-extended half-word from memory address (base + offset) into a.drReg. The effective address must be 2-byte-aligned.
|
||||
Self::LoadHalfwordSigned(a) => {
|
||||
*cpu.reg(a.r2) =
|
||||
sign_extend(cpu.memory.read_word(cpu.get(a.r1) + u32::from(a.immediate)) >> 16);
|
||||
}
|
||||
|
||||
// Loads a word from memory address (base + offset) into a.drReg. The effective address must be 4-byte-aligned.
|
||||
Self::LoadWord(a) => {
|
||||
*cpu.reg(a.r2) = cpu.memory.read_word(cpu.get(a.r1) + u32::from(a.immediate));
|
||||
}
|
||||
|
||||
// Stores a byte from SrcReg in memory address (base + offset) The effective address must be byte-aligned.
|
||||
Self::StoreByte(a) => {
|
||||
cpu.memory
|
||||
.write_byte(cpu.get(a.r1) + u32::from(a.immediate), cpu.get(a.r2) as u8);
|
||||
}
|
||||
|
||||
// Stores a half-word from SrcReg in memory address (base + offset) The effective address must be 2-byte-aligned.
|
||||
Self::StoreHalfword(a) => {
|
||||
// split the value into bytes and then write two bytes
|
||||
let bytes = (cpu.get(a.r1) as u16).to_be_bytes();
|
||||
cpu.memory
|
||||
.write_byte(cpu.get(a.r1) + u32::from(a.immediate), bytes[0]);
|
||||
cpu.memory
|
||||
.write_byte(cpu.get(a.r1) + u32::from(a.immediate) + 1, bytes[1]);
|
||||
}
|
||||
|
||||
// Stores a word from SrcReg in memory address (base + offset) The effective address must be 4-byte-aligned.
|
||||
Self::StoreWord(a) => {
|
||||
cpu.memory
|
||||
.write_word(cpu.get(a.r1) + u32::from(a.immediate), cpu.get(a.r2));
|
||||
}
|
||||
|
||||
// Loads a 16-bit literal value into reg, setting the bottom 16 bits of the word. To populate the upper 16 bits, see LUI.
|
||||
Self::LoadLowerImmediate(a) => {
|
||||
*cpu.reg(a.r1) = u32::from(a.immediate);
|
||||
}
|
||||
|
||||
// Loads a 16-bit literal value into reg, setting the top 16 bits of the word. To populate the lower 16 bits, see LLI.
|
||||
Self::LoadUpperImmediate(a) => {
|
||||
*cpu.reg(a.r1) = (cpu.get(a.r1) & 0x0000_FFFF) | u32::from(a.immediate) << 16;
|
||||
}
|
||||
|
||||
// Unconditionally jumps to the calculated address or direct address
|
||||
Self::Jump(a) => cpu.jump(a.r1, a.immediate),
|
||||
|
||||
// Jumps to the calculated address or direct address if equal flag set.
|
||||
Self::JumpEq(a) => {
|
||||
if cpu.get_flag(Flag::Equal) {
|
||||
cpu.jump(a.r1, a.immediate);
|
||||
}
|
||||
}
|
||||
|
||||
// Jumps to the calculated address or direct address if equal flag not set.
|
||||
Self::JumpNeq(a) => {
|
||||
if !cpu.get_flag(Flag::Equal) {
|
||||
cpu.jump(a.r1, a.immediate);
|
||||
}
|
||||
}
|
||||
|
||||
// Jumps to the calculated address or direct address if greater than flag set.
|
||||
Self::JumpGt(a) => {
|
||||
if cpu.get_flag(Flag::GreaterThan) {
|
||||
cpu.jump(a.r1, a.immediate);
|
||||
}
|
||||
}
|
||||
|
||||
// Jumps to the calculated address or direct address if greater than flag or equal flag set.
|
||||
Self::JumpGe(a) => {
|
||||
if cpu.get_flag(Flag::GreaterThan) || cpu.get_flag(Flag::Equal) {
|
||||
cpu.jump(a.r1, a.immediate);
|
||||
}
|
||||
}
|
||||
|
||||
// Jumps to the calculated address or direct address if less than flag set.
|
||||
Self::JumpLt(a) => {
|
||||
if cpu.get_flag(Flag::LessThan) {
|
||||
cpu.jump(a.r1, a.immediate);
|
||||
}
|
||||
}
|
||||
|
||||
// Jumps to the calculated address or direct address if less than flag or equal flag set.
|
||||
Self::JumpLe(a) => {
|
||||
if cpu.get_flag(Flag::LessThan) || cpu.get_flag(Flag::Equal) {
|
||||
cpu.jump(a.r1, a.immediate);
|
||||
}
|
||||
}
|
||||
|
||||
// Increments the value in the given register
|
||||
Self::Increment(a) => *cpu.reg(a.sr1) = inc(cpu.get(a.sr1)),
|
||||
|
||||
// Decrements the value in the given register
|
||||
Self::Decrement(a) => *cpu.reg(a.sr1) = dec(cpu.get(a.sr1)),
|
||||
|
||||
// Left shifts the value in Reg by the given amount (either a register, or a literal value)
|
||||
Self::ShiftLeft(a) => {
|
||||
let regval = cpu.get(a.sr2);
|
||||
let val = cpu.get(a.sr1);
|
||||
|
||||
*cpu.reg(a.sr1) = shl(val, if regval != 0 { regval as u8 } else { a.shamt });
|
||||
}
|
||||
|
||||
// Right shifts the value in Reg by the given amount (either a register, or a literal value).
|
||||
Self::ShiftRight(a) => {
|
||||
let regval = cpu.get(a.sr2);
|
||||
let val = cpu.get(a.sr1);
|
||||
|
||||
*cpu.reg(a.sr1) = shr(val, if regval != 0 { regval as u8 } else { a.shamt });
|
||||
}
|
||||
|
||||
// Adds the value of Src2 to Src1 and writes the result to a.dr
|
||||
Self::Add(a) => {
|
||||
*cpu.reg(a.dr) = add(cpu.get(a.sr1), cpu.get(a.sr2));
|
||||
}
|
||||
|
||||
// Subtracts the value of Src2 from Src1 and writes the result to a.dr
|
||||
Self::Sub(a) => {
|
||||
*cpu.reg(a.dr) = sub(cpu.get(a.sr1), cpu.get(a.sr2));
|
||||
}
|
||||
|
||||
// Performs bitwise AND on Src1 and Src2 storing the result in a.dr
|
||||
Self::And(a) => *cpu.reg(a.dr) = and(cpu.get(a.sr1), cpu.get(a.sr2)),
|
||||
|
||||
// Performs bitwise OR on Src1 and Src2 storing the result in a.dr
|
||||
Self::Or(a) => *cpu.reg(a.dr) = or(cpu.get(a.sr1), cpu.get(a.sr2)),
|
||||
|
||||
// Performs bitwise NOT on Src storing the result in a.dr
|
||||
Self::Not(a) => *cpu.reg(a.dr) = not(cpu.get(a.sr1)),
|
||||
|
||||
// Performs bitwise XOR on Src1 and Src2 storing the result in a.dr
|
||||
Self::Xor(a) => *cpu.reg(a.dr) = xor(cpu.get(a.sr1), cpu.get(a.sr2)),
|
||||
|
||||
// Performs bitwise NAND on Src1 and Src2 storing the result in a.dr
|
||||
Self::Nand(a) => *cpu.reg(a.dr) = nand(cpu.get(a.sr1), cpu.get(a.sr2)),
|
||||
|
||||
// Performs bitwise NOR on Src1 and Src2 storing the result in a.dr
|
||||
Self::Nor(a) => *cpu.reg(a.dr) = nor(cpu.get(a.sr1), cpu.get(a.sr2)),
|
||||
|
||||
// Performs bitwise XNOR on Src1 and Src2 storing the result in a.dr
|
||||
Self::Xnor(a) => *cpu.reg(a.dr) = xnor(cpu.get(a.sr1), cpu.get(a.sr2)),
|
||||
|
||||
// Compares the value of Reg1 to the value in Reg2. The results of the comparisons are set in the Status register.
|
||||
Self::Compare(a) => {
|
||||
cpu.cmp(cpu.get(a.sr1), cpu.get(a.sr2));
|
||||
}
|
||||
|
||||
// Initiates an interrupt with the given 8 bit interrupt code.
|
||||
// Triggering an interrupt invokes the following behaviour:
|
||||
// - The return address is saved to the RET register.
|
||||
// - The stack base ptr is set to the kernel stack.
|
||||
Self::Interrupt(interrupt_code) => {
|
||||
cpu.begin_interrupt(interrupt_code);
|
||||
}
|
||||
|
||||
// Returns from an interrupt,
|
||||
Self::IntReturn => {
|
||||
cpu.end_interrupt();
|
||||
}
|
||||
|
||||
// Halts the processor.
|
||||
Self::Halt => {
|
||||
cpu.halted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// mathematical and logical functions & other operations
|
||||
const fn add(a: u32, b: u32) -> u32 {
|
||||
a + b
|
||||
}
|
||||
|
||||
const fn sub(a: u32, b: u32) -> u32 {
|
||||
a - b
|
||||
}
|
||||
|
||||
const fn and(a: u32, b: u32) -> u32 {
|
||||
a & b
|
||||
}
|
||||
|
||||
const fn inc(a: u32) -> u32 {
|
||||
a + 1
|
||||
}
|
||||
|
||||
const fn dec(a: u32) -> u32 {
|
||||
a - 1
|
||||
}
|
||||
|
||||
const fn shl(a: u32, amount: u8) -> u32 {
|
||||
a << amount
|
||||
}
|
||||
|
||||
const fn shr(a: u32, amount: u8) -> u32 {
|
||||
a >> amount
|
||||
}
|
||||
|
||||
const fn or(a: u32, b: u32) -> u32 {
|
||||
a | b
|
||||
}
|
||||
|
||||
const fn not(a: u32) -> u32 {
|
||||
!a
|
||||
}
|
||||
|
||||
const fn xor(a: u32, b: u32) -> u32 {
|
||||
a ^ b
|
||||
}
|
||||
|
||||
const fn nand(a: u32, b: u32) -> u32 {
|
||||
!(a & b)
|
||||
}
|
||||
|
||||
const fn nor(a: u32, b: u32) -> u32 {
|
||||
!(a | b)
|
||||
}
|
||||
|
||||
const fn xnor(a: u32, b: u32) -> u32 {
|
||||
!(a ^ b)
|
||||
}
|
||||
|
||||
const fn sign_extend(val: u32) -> u32 {
|
||||
let (mask, sign_bit): (u32, u8) = match val {
|
||||
0..=0xFF => (0xFFFF_FF00, 7),
|
||||
// I presume this was the intended behaviour?
|
||||
0x100..=0xFFFF => (0xFFFF_0000, 15),
|
||||
_ => (0x0000_0000, 31),
|
||||
};
|
||||
|
||||
if val & (1 << sign_bit) != 0 {
|
||||
val | mask
|
||||
} else {
|
||||
val
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -1,499 +0,0 @@
|
||||
use super::*;
|
||||
use crate::{
|
||||
common::instructions::{
|
||||
args::{ITypeArgs, RTypeArgs},
|
||||
*,
|
||||
},
|
||||
emulator::system::memory::*,
|
||||
};
|
||||
|
||||
fn create_test_processor() -> Processor {
|
||||
let memory = Box::new(MainStore::new());
|
||||
Processor::new(memory, Vec::new())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nop_instruction() {
|
||||
let mut cpu = create_test_processor();
|
||||
let initial_state = cpu.registers;
|
||||
|
||||
Instruction::Nop.execute(&mut cpu);
|
||||
|
||||
assert_eq!(
|
||||
cpu.registers.get(Register::Rg0),
|
||||
initial_state.get(Register::Rg0)
|
||||
);
|
||||
assert_eq!(
|
||||
cpu.registers.get(Register::Acc),
|
||||
initial_state.get(Register::Acc)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mov_instruction() {
|
||||
let mut cpu = create_test_processor();
|
||||
*cpu.reg(Register::Rg1) = 0x1234_5678;
|
||||
|
||||
let mov_instr = Instruction::Mov(RTypeArgs::new(
|
||||
Some(Register::Rg1),
|
||||
None,
|
||||
Some(Register::Rg2),
|
||||
None,
|
||||
));
|
||||
|
||||
mov_instr.execute(&mut cpu);
|
||||
assert_eq!(cpu.get(Register::Rg2), 0x1234_5678);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mov_signed_instruction() {
|
||||
let mut cpu = create_test_processor();
|
||||
*cpu.reg(Register::Rg1) = 0x0000_00FF;
|
||||
|
||||
let mov_signed_instr = Instruction::MovSigned(RTypeArgs::new(
|
||||
Some(Register::Rg1),
|
||||
None,
|
||||
Some(Register::Rg2),
|
||||
None,
|
||||
));
|
||||
|
||||
mov_signed_instr.execute(&mut cpu);
|
||||
assert_eq!(cpu.get(Register::Rg2), 0xFFFF_FFFF);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_byte_instruction() {
|
||||
let mut cpu = create_test_processor();
|
||||
let addr = 0x100;
|
||||
cpu.memory.write_byte(addr, 0xAB);
|
||||
*cpu.reg(Register::Rg1) = addr - 4;
|
||||
|
||||
let load_byte_instr =
|
||||
Instruction::LoadByte(ITypeArgs::new(4, Some(Register::Rg1), Some(Register::Rg2)));
|
||||
|
||||
load_byte_instr.execute(&mut cpu);
|
||||
assert_eq!(cpu.get(Register::Rg2), 0x0000_00AB);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_byte_signed_instruction() {
|
||||
let mut cpu = create_test_processor();
|
||||
let addr = 0x100;
|
||||
cpu.memory.write_byte(addr, 0xFF);
|
||||
*cpu.reg(Register::Rg1) = addr;
|
||||
|
||||
let load_byte_signed_instr =
|
||||
Instruction::LoadByteSigned(ITypeArgs::new(0, Some(Register::Rg1), Some(Register::Rg2)));
|
||||
|
||||
load_byte_signed_instr.execute(&mut cpu);
|
||||
assert_eq!(cpu.get(Register::Rg2), 0xFFFF_FFFF);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_halfword_instruction() {
|
||||
let mut cpu = create_test_processor();
|
||||
let addr = 0x100;
|
||||
cpu.memory.write_word(addr, 0x1234_5678);
|
||||
*cpu.reg(Register::Rg1) = addr;
|
||||
|
||||
let load_halfword_instr =
|
||||
Instruction::LoadHalfword(ITypeArgs::new(0, Some(Register::Rg1), Some(Register::Rg2)));
|
||||
|
||||
load_halfword_instr.execute(&mut cpu);
|
||||
assert_eq!(cpu.get(Register::Rg2), 0x0000_1234);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_word_instruction() {
|
||||
let mut cpu = create_test_processor();
|
||||
let addr = 0x100;
|
||||
cpu.memory.write_word(addr, 0x1234_5678);
|
||||
*cpu.reg(Register::Rg1) = addr;
|
||||
|
||||
let load_word_instr =
|
||||
Instruction::LoadWord(ITypeArgs::new(0, Some(Register::Rg1), Some(Register::Rg2)));
|
||||
|
||||
load_word_instr.execute(&mut cpu);
|
||||
assert_eq!(cpu.get(Register::Rg2), 0x1234_5678);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_store_byte_instruction() {
|
||||
let mut cpu = create_test_processor();
|
||||
let addr = 0x100;
|
||||
*cpu.reg(Register::Rg1) = addr;
|
||||
*cpu.reg(Register::Rg2) = 0xAB;
|
||||
|
||||
let store_byte_instr =
|
||||
Instruction::StoreByte(ITypeArgs::new(0, Some(Register::Rg1), Some(Register::Rg2)));
|
||||
|
||||
store_byte_instr.execute(&mut cpu);
|
||||
assert_eq!(cpu.memory.read_byte(addr), 0xAB);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_store_word_instruction() {
|
||||
let mut cpu = create_test_processor();
|
||||
let addr = 0x100;
|
||||
*cpu.reg(Register::Rg1) = addr;
|
||||
*cpu.reg(Register::Rg2) = 0x1234_5678;
|
||||
|
||||
let store_word_instr =
|
||||
Instruction::StoreWord(ITypeArgs::new(0, Some(Register::Rg1), Some(Register::Rg2)));
|
||||
|
||||
store_word_instr.execute(&mut cpu);
|
||||
assert_eq!(cpu.memory.read_word(addr), 0x1234_5678);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_instruction() {
|
||||
let mut cpu = create_test_processor();
|
||||
*cpu.reg(Register::Rg1) = 15;
|
||||
*cpu.reg(Register::Rg2) = 25;
|
||||
|
||||
let add_instr = Instruction::Add(RTypeArgs::new(
|
||||
Some(Register::Rg1),
|
||||
Some(Register::Rg2),
|
||||
Some(Register::Rg3),
|
||||
None,
|
||||
));
|
||||
|
||||
add_instr.execute(&mut cpu);
|
||||
assert_eq!(cpu.get(Register::Rg3), 40);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sub_instruction() {
|
||||
let mut cpu = create_test_processor();
|
||||
*cpu.reg(Register::Rg1) = 50;
|
||||
*cpu.reg(Register::Rg2) = 20;
|
||||
|
||||
let sub_instr = Instruction::Sub(RTypeArgs::new(
|
||||
Some(Register::Rg1),
|
||||
Some(Register::Rg2),
|
||||
Some(Register::Rg3),
|
||||
None,
|
||||
));
|
||||
|
||||
sub_instr.execute(&mut cpu);
|
||||
assert_eq!(cpu.get(Register::Rg3), 30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_and_instruction() {
|
||||
let mut cpu = create_test_processor();
|
||||
*cpu.reg(Register::Rg1) = 0b1100;
|
||||
*cpu.reg(Register::Rg2) = 0b1010;
|
||||
|
||||
let and_instr = Instruction::And(RTypeArgs::new(
|
||||
Some(Register::Rg1),
|
||||
Some(Register::Rg2),
|
||||
Some(Register::Rg3),
|
||||
None,
|
||||
));
|
||||
|
||||
and_instr.execute(&mut cpu);
|
||||
assert_eq!(cpu.get(Register::Rg3), 0b1000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_or_instruction() {
|
||||
let mut cpu = create_test_processor();
|
||||
*cpu.reg(Register::Rg1) = 0b1100;
|
||||
*cpu.reg(Register::Rg2) = 0b1010;
|
||||
|
||||
let or_instr = Instruction::Or(RTypeArgs::new(
|
||||
Some(Register::Rg1),
|
||||
Some(Register::Rg2),
|
||||
Some(Register::Rg3),
|
||||
None,
|
||||
));
|
||||
|
||||
or_instr.execute(&mut cpu);
|
||||
assert_eq!(cpu.get(Register::Rg3), 0b1110);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_xor_instruction() {
|
||||
let mut cpu = create_test_processor();
|
||||
*cpu.reg(Register::Rg1) = 0b1100;
|
||||
*cpu.reg(Register::Rg2) = 0b1010;
|
||||
|
||||
let xor_instr = Instruction::Xor(RTypeArgs::new(
|
||||
Some(Register::Rg1),
|
||||
Some(Register::Rg2),
|
||||
Some(Register::Rg3),
|
||||
None,
|
||||
));
|
||||
|
||||
xor_instr.execute(&mut cpu);
|
||||
assert_eq!(cpu.get(Register::Rg3), 0b0110);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_not_instruction() {
|
||||
let mut cpu = create_test_processor();
|
||||
*cpu.reg(Register::Rg1) = 0x0F0F_0F0F;
|
||||
|
||||
let not_instr = Instruction::Not(RTypeArgs::new(
|
||||
Some(Register::Rg1),
|
||||
None,
|
||||
Some(Register::Rg2),
|
||||
None,
|
||||
));
|
||||
|
||||
not_instr.execute(&mut cpu);
|
||||
assert_eq!(cpu.get(Register::Rg2), 0xF0F0_F0F0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compare_equal() {
|
||||
let mut cpu = create_test_processor();
|
||||
*cpu.reg(Register::Rg1) = 42;
|
||||
*cpu.reg(Register::Rg2) = 42;
|
||||
|
||||
let cmp_instr = Instruction::Compare(RTypeArgs::new(
|
||||
Some(Register::Rg1),
|
||||
Some(Register::Rg2),
|
||||
None,
|
||||
None,
|
||||
));
|
||||
|
||||
cmp_instr.execute(&mut cpu);
|
||||
|
||||
assert!(cpu.get_flag(Flag::Equal));
|
||||
assert!(!cpu.get_flag(Flag::GreaterThan));
|
||||
assert!(!cpu.get_flag(Flag::LessThan));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compare_greater_than() {
|
||||
let mut cpu = create_test_processor();
|
||||
*cpu.reg(Register::Rg1) = 50;
|
||||
*cpu.reg(Register::Rg2) = 30;
|
||||
|
||||
let cmp_instr = Instruction::Compare(RTypeArgs::new(
|
||||
Some(Register::Rg1),
|
||||
Some(Register::Rg2),
|
||||
None,
|
||||
None,
|
||||
));
|
||||
|
||||
cmp_instr.execute(&mut cpu);
|
||||
|
||||
assert!(!cpu.get_flag(Flag::Equal));
|
||||
assert!(cpu.get_flag(Flag::GreaterThan));
|
||||
assert!(!cpu.get_flag(Flag::LessThan));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compare_less_than() {
|
||||
let mut cpu = create_test_processor();
|
||||
*cpu.reg(Register::Rg1) = 20;
|
||||
*cpu.reg(Register::Rg2) = 30;
|
||||
|
||||
let cmp_instr = Instruction::Compare(RTypeArgs::new(
|
||||
Some(Register::Rg1),
|
||||
Some(Register::Rg2),
|
||||
None,
|
||||
None,
|
||||
));
|
||||
|
||||
cmp_instr.execute(&mut cpu);
|
||||
|
||||
assert!(!cpu.get_flag(Flag::Equal));
|
||||
assert!(!cpu.get_flag(Flag::GreaterThan));
|
||||
assert!(cpu.get_flag(Flag::LessThan));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_increment_instruction() {
|
||||
let mut cpu = create_test_processor();
|
||||
*cpu.reg(Register::Rg1) = 42;
|
||||
|
||||
let inc_instr = Instruction::Increment(RTypeArgs::new(Some(Register::Rg1), None, None, None));
|
||||
|
||||
inc_instr.execute(&mut cpu);
|
||||
assert_eq!(cpu.get(Register::Rg1), 43);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decrement_instruction() {
|
||||
let mut cpu = create_test_processor();
|
||||
*cpu.reg(Register::Rg1) = 42;
|
||||
|
||||
let dec_instr = Instruction::Decrement(RTypeArgs::new(Some(Register::Rg1), None, None, None));
|
||||
|
||||
dec_instr.execute(&mut cpu);
|
||||
assert_eq!(cpu.get(Register::Rg1), 41);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shift_left_with_shamt() {
|
||||
let mut cpu = create_test_processor();
|
||||
*cpu.reg(Register::Rg1) = 0b1010;
|
||||
|
||||
let shl_instr = Instruction::ShiftLeft(RTypeArgs::new(
|
||||
Some(Register::Rg1),
|
||||
Some(Register::Zero),
|
||||
None,
|
||||
Some(2),
|
||||
));
|
||||
|
||||
shl_instr.execute(&mut cpu);
|
||||
assert_eq!(cpu.get(Register::Rg1), 0b10_1000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shift_right_with_shamt() {
|
||||
let mut cpu = create_test_processor();
|
||||
*cpu.reg(Register::Rg1) = 0b10_1000;
|
||||
|
||||
let shr_instr = Instruction::ShiftRight(RTypeArgs::new(
|
||||
Some(Register::Rg1),
|
||||
Some(Register::Zero),
|
||||
None,
|
||||
Some(2),
|
||||
));
|
||||
|
||||
shr_instr.execute(&mut cpu);
|
||||
assert_eq!(cpu.get(Register::Rg1), 0b1010);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shift_left_with_register() {
|
||||
let mut cpu = create_test_processor();
|
||||
*cpu.reg(Register::Rg1) = 0b1010;
|
||||
*cpu.reg(Register::Rg2) = 3;
|
||||
|
||||
let shl_instr = Instruction::ShiftLeft(RTypeArgs::new(
|
||||
Some(Register::Rg1),
|
||||
Some(Register::Rg2),
|
||||
None,
|
||||
None,
|
||||
));
|
||||
|
||||
shl_instr.execute(&mut cpu);
|
||||
assert_eq!(cpu.get(Register::Rg1), 0b101_0000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_lower_immediate() {
|
||||
let mut cpu = create_test_processor();
|
||||
|
||||
let lli_instr =
|
||||
Instruction::LoadLowerImmediate(ITypeArgs::new(0x1234, Some(Register::Rg1), None));
|
||||
|
||||
lli_instr.execute(&mut cpu);
|
||||
assert_eq!(cpu.get(Register::Rg1), 0x0000_1234);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_upper_immediate() {
|
||||
let mut cpu = create_test_processor();
|
||||
*cpu.reg(Register::Rg1) = 0x0000_5678;
|
||||
|
||||
let lui_instr =
|
||||
Instruction::LoadUpperImmediate(ITypeArgs::new(0x1234, Some(Register::Rg1), None));
|
||||
|
||||
lui_instr.execute(&mut cpu);
|
||||
assert_eq!(cpu.get(Register::Rg1), 0x1234_5678);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_jump_unconditional() {
|
||||
let mut cpu = create_test_processor();
|
||||
*cpu.reg(Register::Rg1) = 0x1000;
|
||||
let initial_pc = cpu.get(Register::Pcx);
|
||||
|
||||
let jump_instr = Instruction::Jump(ITypeArgs::new(0x100, Some(Register::Rg1), None));
|
||||
|
||||
jump_instr.execute(&mut cpu);
|
||||
assert_eq!(cpu.get(Register::Pcx), 0x1100);
|
||||
assert_ne!(cpu.get(Register::Pcx), initial_pc);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_jump_equal_when_flag_set() {
|
||||
let mut cpu = create_test_processor();
|
||||
cpu.set_flag(Flag::Equal, true);
|
||||
*cpu.reg(Register::Rg1) = 0x1000;
|
||||
|
||||
let jump_eq_instr = Instruction::JumpEq(ITypeArgs::new(0x100, Some(Register::Rg1), None));
|
||||
|
||||
jump_eq_instr.execute(&mut cpu);
|
||||
assert_eq!(cpu.get(Register::Pcx), 0x1100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_jump_equal_when_flag_not_set() {
|
||||
let mut cpu = create_test_processor();
|
||||
cpu.set_flag(Flag::Equal, false);
|
||||
*cpu.reg(Register::Rg1) = 0x1000;
|
||||
let initial_pc = cpu.get(Register::Pcx);
|
||||
|
||||
let jump_eq_instr = Instruction::JumpEq(ITypeArgs::new(0x100, Some(Register::Rg1), None));
|
||||
|
||||
jump_eq_instr.execute(&mut cpu);
|
||||
assert_eq!(cpu.get(Register::Pcx), initial_pc);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_halt_instruction() {
|
||||
let mut cpu = create_test_processor();
|
||||
assert!(!cpu.halted);
|
||||
|
||||
Instruction::Halt.execute(&mut cpu);
|
||||
assert!(cpu.halted);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nand_instruction() {
|
||||
let mut cpu = create_test_processor();
|
||||
*cpu.reg(Register::Rg1) = 0b1100;
|
||||
*cpu.reg(Register::Rg2) = 0b1010;
|
||||
|
||||
let nand_instr = Instruction::Nand(RTypeArgs::new(
|
||||
Some(Register::Rg1),
|
||||
Some(Register::Rg2),
|
||||
Some(Register::Rg3),
|
||||
None,
|
||||
));
|
||||
|
||||
nand_instr.execute(&mut cpu);
|
||||
assert_eq!(cpu.get(Register::Rg3), !0b1000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nor_instruction() {
|
||||
let mut cpu = create_test_processor();
|
||||
*cpu.reg(Register::Rg1) = 0b1100;
|
||||
*cpu.reg(Register::Rg2) = 0b1010;
|
||||
|
||||
let nor_instr = Instruction::Nor(RTypeArgs::new(
|
||||
Some(Register::Rg1),
|
||||
Some(Register::Rg2),
|
||||
Some(Register::Rg3),
|
||||
None,
|
||||
));
|
||||
|
||||
nor_instr.execute(&mut cpu);
|
||||
assert_eq!(cpu.get(Register::Rg3), !0b1110);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_xnor_instruction() {
|
||||
let mut cpu = create_test_processor();
|
||||
*cpu.reg(Register::Rg1) = 0b1100;
|
||||
*cpu.reg(Register::Rg2) = 0b1010;
|
||||
|
||||
let xnor_instr = Instruction::Xnor(RTypeArgs::new(
|
||||
Some(Register::Rg1),
|
||||
Some(Register::Rg2),
|
||||
Some(Register::Rg3),
|
||||
None,
|
||||
));
|
||||
|
||||
xnor_instr.execute(&mut cpu);
|
||||
assert_eq!(cpu.get(Register::Rg3), !0b0110);
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
use std::sync::mpsc::Sender;
|
||||
|
||||
use crate::{
|
||||
common::instructions::Register,
|
||||
emulator::{
|
||||
system::model::{Command, Running, State},
|
||||
ui::interface::Component,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct ControlPanel {
|
||||
visible: bool,
|
||||
sender: Sender<Command>,
|
||||
}
|
||||
|
||||
impl ControlPanel {
|
||||
#[must_use]
|
||||
pub const fn new(sender: Sender<Command>) -> Self {
|
||||
Self {
|
||||
visible: false,
|
||||
sender,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for ControlPanel {
|
||||
fn category(&self) -> super::interface::Category {
|
||||
super::interface::Category::Control
|
||||
}
|
||||
|
||||
fn visible(&mut self) -> &mut bool {
|
||||
&mut self.visible
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"Control Panel"
|
||||
}
|
||||
|
||||
fn render(&mut self, state: &mut State, ui: &mut egui::Ui, ctx: &egui::Context) {
|
||||
ui.horizontal(|ui| {
|
||||
// Pause / Run
|
||||
if ui
|
||||
.button(if state.running == Running::Running {
|
||||
"Pause"
|
||||
} else {
|
||||
"Run"
|
||||
})
|
||||
.clicked()
|
||||
{
|
||||
if state.running == Running::Running {
|
||||
self.sender.send(Command::Stop).unwrap_or_else(|_| {
|
||||
state.error = Some("Failed to send command".to_string());
|
||||
});
|
||||
} else {
|
||||
self.sender.send(Command::Start).unwrap_or_else(|_| {
|
||||
state.error = Some("Failed to send command".to_string());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Step
|
||||
if ui.button("Step").clicked() {
|
||||
self.sender
|
||||
.send(Command::Step)
|
||||
.unwrap_or_else(|_| state.error = Some("Failed to send command".to_string()));
|
||||
}
|
||||
|
||||
ui.separator();
|
||||
|
||||
// Status info
|
||||
ui.label(format!(
|
||||
"Status: {}",
|
||||
match state.running {
|
||||
Running::Running => "Running",
|
||||
Running::Paused => "Paused",
|
||||
Running::Halted => "Halted",
|
||||
}
|
||||
));
|
||||
ui.label(format!("Instructions: {}", state.instructions));
|
||||
ui.label(format!("PC: 0x{:08X}", state.reg_file.get(Register::Pcx)));
|
||||
ui.label(format!(
|
||||
"Last Instruction: {:?}",
|
||||
"TODO: DECODE INSTRUCTION" // TODO: decode instruction
|
||||
// Instruction::decode(state.current_state.cir)
|
||||
));
|
||||
});
|
||||
|
||||
render_register_table(state, ui, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
fn render_register_table(state: &State, ui: &mut egui::Ui, _ctx: &egui::Context) {
|
||||
// Left column - Registers
|
||||
ui.vertical(|ui| {
|
||||
ui.heading("Registers");
|
||||
|
||||
egui::ScrollArea::vertical()
|
||||
.id_salt("register_inspector_scroll")
|
||||
.show(ui, |ui| {
|
||||
egui::Grid::new("registers_grid")
|
||||
.num_columns(8)
|
||||
.spacing([40.0, 4.0])
|
||||
.striped(true)
|
||||
.show(ui, |ui| {
|
||||
ui.label("Register");
|
||||
ui.label("Value");
|
||||
ui.label("Register");
|
||||
ui.label("Value");
|
||||
ui.label("Register");
|
||||
ui.label("Value");
|
||||
ui.label("Register");
|
||||
ui.label("Value");
|
||||
ui.end_row();
|
||||
|
||||
// iterate over state.reg_file.iter() in chunks of 4 registers
|
||||
for chunk in state.reg_file.all().chunks(4) {
|
||||
for reg in chunk {
|
||||
ui.label(reg.0.to_string());
|
||||
ui.label(format!("0x{:08X} ({})", reg.1, reg.1,));
|
||||
}
|
||||
ui.end_row();
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -1,326 +0,0 @@
|
||||
use std::sync::mpsc::Sender;
|
||||
|
||||
use egui::{Align, Context, Layout, Ui};
|
||||
use rfd::FileDialog;
|
||||
|
||||
use crate::emulator::{
|
||||
system::model::{Command, State},
|
||||
ui::interface::Component,
|
||||
};
|
||||
|
||||
pub struct Editor {
|
||||
filename: String,
|
||||
text: String,
|
||||
output: Vec<u8>,
|
||||
sender: Sender<Command>,
|
||||
cursor_col: usize,
|
||||
cursor_line: usize,
|
||||
visible: bool,
|
||||
load_offset: u32,
|
||||
offset_str: String,
|
||||
error: Option<String>,
|
||||
}
|
||||
|
||||
impl Component for Editor {
|
||||
fn name(&self) -> &'static str {
|
||||
"Editor"
|
||||
}
|
||||
|
||||
fn visible(&mut self) -> &mut bool {
|
||||
&mut self.visible
|
||||
}
|
||||
|
||||
fn category(&self) -> super::interface::Category {
|
||||
super::interface::Category::Programming
|
||||
}
|
||||
|
||||
fn render(&mut self, state: &mut State, ui: &mut Ui, ctx: &Context) {
|
||||
ui.vertical(|ui| {
|
||||
// Top bar
|
||||
self.render_toolbar(state, ui, ctx);
|
||||
|
||||
ui.add_space(4.0); // Add some spacing instead of just a separator
|
||||
ui.separator();
|
||||
|
||||
let remaining_height = f32::max(ui.available_height() - 100.0, 100.0);
|
||||
|
||||
ui.allocate_ui_with_layout(
|
||||
egui::Vec2::new(ui.available_width(), remaining_height),
|
||||
Layout::left_to_right(Align::Min),
|
||||
|ui| {
|
||||
self.render_editor(state, ui, ctx);
|
||||
ui.separator();
|
||||
self.render_output(state, ui, ctx);
|
||||
},
|
||||
);
|
||||
|
||||
ui.label(format!("Ln {}, Col {}", self.cursor_line, self.cursor_col));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Editor {
|
||||
pub fn new(sender: Sender<Command>) -> Self {
|
||||
Self {
|
||||
filename: String::new(),
|
||||
text: String::new(),
|
||||
output: Vec::new(),
|
||||
sender,
|
||||
cursor_col: 1,
|
||||
cursor_line: 1,
|
||||
visible: false,
|
||||
load_offset: 0,
|
||||
offset_str: String::new(),
|
||||
error: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn render_output(&mut self, _state: &mut State, ui: &mut Ui, _ctx: &Context) {
|
||||
// Output area with synchronized scrolling
|
||||
egui::ScrollArea::vertical()
|
||||
.id_salt("output_scroll")
|
||||
.max_width(300.0)
|
||||
.show(ui, |ui| {
|
||||
if self.output.is_empty() {
|
||||
ui.label(
|
||||
egui::RichText::new("No output data")
|
||||
.font(egui::FontId::monospace(12.0))
|
||||
.color(egui::Color32::GRAY),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
egui::Grid::new("output_grid")
|
||||
.num_columns(4)
|
||||
.spacing([20.0, 2.0]) // Horizontal and vertical spacing
|
||||
.striped(false)
|
||||
.show(ui, |ui| {
|
||||
// Process bytes in chunks of 4
|
||||
for (line_num, chunk) in self.output.chunks(4).enumerate() {
|
||||
let address = line_num * 4;
|
||||
|
||||
// Convert chunk to u32 (little-endian)
|
||||
let mut bytes = [0u8; 4];
|
||||
for (i, &byte) in chunk.iter().enumerate() {
|
||||
if i < 4 {
|
||||
bytes[i] = byte;
|
||||
}
|
||||
}
|
||||
let value = u32::from_le_bytes(bytes);
|
||||
|
||||
// Address column
|
||||
ui.with_layout(
|
||||
egui::Layout::left_to_right(egui::Align::Center),
|
||||
|ui| {
|
||||
ui.set_min_width(80.0);
|
||||
let style = ui.style_mut();
|
||||
style.visuals.widgets.inactive.bg_fill =
|
||||
egui::Color32::from_gray(30);
|
||||
ui.label(
|
||||
egui::RichText::new(format!("0x{:04X}", address))
|
||||
.font(egui::FontId::monospace(12.0)),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// Individual bytes column
|
||||
let byte_str = chunk
|
||||
.iter()
|
||||
.map(|b| format!("{:02X}", b))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
|
||||
ui.label(
|
||||
egui::RichText::new(format!("{:<11}", byte_str))
|
||||
.font(egui::FontId::monospace(12.0))
|
||||
.color(egui::Color32::from_rgb(200, 200, 255)),
|
||||
);
|
||||
|
||||
// Hex column
|
||||
ui.label(
|
||||
egui::RichText::new(format!("0x{:08X}", value))
|
||||
.font(egui::FontId::monospace(12.0))
|
||||
.color(egui::Color32::from_rgb(255, 200, 200)),
|
||||
);
|
||||
|
||||
// Decimal column
|
||||
ui.label(
|
||||
egui::RichText::new(format!("{:10}", value))
|
||||
.font(egui::FontId::monospace(12.0))
|
||||
.color(egui::Color32::from_rgb(200, 255, 200)),
|
||||
);
|
||||
|
||||
ui.end_row();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn render_editor(&mut self, _state: &mut State, ui: &mut Ui, _ctx: &Context) {
|
||||
let available_width = ui.available_width();
|
||||
|
||||
// Main editor area with synchronized scrolling
|
||||
egui::ScrollArea::vertical()
|
||||
.max_width(available_width - 400.0)
|
||||
.id_salt("editor_scroll")
|
||||
.show(ui, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
// Line numbers column
|
||||
let line_count = self.text.lines().count();
|
||||
ui.vertical(|ui| {
|
||||
ui.set_width(50.0);
|
||||
ui.style_mut().visuals.widgets.inactive.bg_fill =
|
||||
egui::Color32::from_gray(30);
|
||||
|
||||
// Calculate line height to match text editor
|
||||
let line_height = ui.text_style_height(&egui::TextStyle::Monospace);
|
||||
|
||||
for line_num in 1..=line_count {
|
||||
let line_response = ui.allocate_response(
|
||||
egui::vec2(50.0, line_height),
|
||||
egui::Sense::hover(),
|
||||
);
|
||||
|
||||
ui.painter().text(
|
||||
line_response.rect.left_center() + egui::vec2(5.0, 0.0),
|
||||
egui::Align2::LEFT_CENTER,
|
||||
format!("{:3}", line_num),
|
||||
egui::FontId::monospace(12.0),
|
||||
ui.style().visuals.text_color(),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
|
||||
// Text editor area
|
||||
ui.vertical(|ui| {
|
||||
let available_size = ui.available_size();
|
||||
let response = ui.add_sized(
|
||||
available_size,
|
||||
egui::TextEdit::multiline(&mut self.text)
|
||||
.font(egui::TextStyle::Monospace)
|
||||
.margin(egui::vec2(5.0, 0.0))
|
||||
.code_editor(),
|
||||
);
|
||||
|
||||
// Update cursor position when text changes
|
||||
if response.changed() {
|
||||
// Simple but functional cursor tracking
|
||||
let lines = self.text.lines().collect::<Vec<_>>();
|
||||
self.cursor_line = lines.len().max(1);
|
||||
|
||||
// Get the length of the last line for column position
|
||||
if let Some(last_line) = lines.last() {
|
||||
self.cursor_col = last_line.chars().count() + 1;
|
||||
} else {
|
||||
self.cursor_col = 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn render_toolbar(&mut self, _state: &mut State, ui: &mut Ui, _ctx: &Context) {
|
||||
ui.horizontal(|ui| {
|
||||
// current filename
|
||||
ui.label(format!("File: {}", self.filename));
|
||||
|
||||
// error display
|
||||
ui.label(
|
||||
egui::RichText::new(self.error.clone().unwrap_or("".to_string()))
|
||||
.color(egui::Color32::RED),
|
||||
);
|
||||
|
||||
// number of lines in the file
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
let line_count = self.text.lines().count();
|
||||
ui.label(format!("Lines: {}", line_count));
|
||||
});
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.spacing_mut().button_padding = egui::vec2(8.0, 4.0);
|
||||
ui.spacing_mut().item_spacing.x = 6.0;
|
||||
|
||||
// Opens a file
|
||||
if ui.button("Open").clicked() {
|
||||
if let Some(path) = FileDialog::new()
|
||||
.add_filter("dsafiles", &["dsa", "dsb", "dsc", "dsd"])
|
||||
.add_filter("all", &["*"])
|
||||
.set_directory(std::env::current_dir().unwrap())
|
||||
.pick_file()
|
||||
{
|
||||
if let Ok(content) = std::fs::read_to_string(&path) {
|
||||
self.text = content;
|
||||
self.filename = path.display().to_string();
|
||||
}
|
||||
}
|
||||
|
||||
self.output = Vec::new();
|
||||
}
|
||||
|
||||
// Saves the current file
|
||||
if ui.button("Save").clicked() {
|
||||
if let Some(path) = FileDialog::new()
|
||||
.add_filter("dsafiles", &["dsa", "dsb", "dsc", "dsd"])
|
||||
.add_filter("all", &["*"])
|
||||
.set_directory(std::env::current_dir().unwrap())
|
||||
.save_file()
|
||||
{
|
||||
if let Err(e) = std::fs::write(&path, &self.text) {
|
||||
self.error = Some(format!("Failed to save file: {}", e));
|
||||
} else {
|
||||
self.filename = path.display().to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// builds the current file
|
||||
if ui.button("Build").clicked() {
|
||||
self.output = vec![0x00; 256];
|
||||
}
|
||||
|
||||
// Loads the generated binary into the assembler at the provided offset
|
||||
if ui.button("Load").clicked() {
|
||||
if self.error.is_some() {
|
||||
self.error = Some("Can't load program at invalid offset!".to_string());
|
||||
}
|
||||
|
||||
self.sender
|
||||
.send(Command::Write(self.load_offset, self.output.clone()))
|
||||
.unwrap_or_else(|_| self.error = Some("Failed to send command".to_string()));
|
||||
}
|
||||
|
||||
// Entry widget to enter a load offset
|
||||
if ui.text_edit_singleline(&mut self.offset_str).changed() {
|
||||
if let Some(offset) = parse_address(&self.offset_str) {
|
||||
self.load_offset = offset;
|
||||
self.error = None;
|
||||
} else {
|
||||
self.error = Some("Invalid offset".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
// Resets the emulator and all attached devices
|
||||
if ui.button("Reset Emulator").clicked() {
|
||||
self.sender
|
||||
.send(Command::Reset)
|
||||
.unwrap_or_else(|_| self.error = Some("Failed to send command".to_string()));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_address(address: &str) -> Option<u32> {
|
||||
if address.starts_with("0x") {
|
||||
u32::from_str_radix(&address[2..], 16).ok()
|
||||
} else if address.starts_with("0b") {
|
||||
u32::from_str_radix(&address[2..], 2).ok()
|
||||
} else if address.starts_with("0o") {
|
||||
u32::from_str_radix(&address[2..], 8).ok()
|
||||
} else {
|
||||
address.parse::<u32>().ok()
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
use crate::emulator::system::model::{Command, Running, State};
|
||||
use std::sync::mpsc::{Receiver, Sender};
|
||||
|
||||
pub trait Component {
|
||||
fn render(&mut self, state: &mut State, ui: &mut egui::Ui, ctx: &egui::Context);
|
||||
fn visible(&mut self) -> &mut bool;
|
||||
fn name(&self) -> &'static str;
|
||||
fn category(&self) -> Category;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Category {
|
||||
Control,
|
||||
Memory,
|
||||
IO,
|
||||
Programming,
|
||||
}
|
||||
|
||||
impl Category {
|
||||
#[must_use]
|
||||
pub const fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Control => "Control Systems",
|
||||
Self::Memory => "Memory Systems",
|
||||
Self::IO => "I/O Systems",
|
||||
Self::Programming => "Programming",
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn list() -> Vec<Self> {
|
||||
vec![Self::Control, Self::Memory, Self::IO, Self::Programming]
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EmulatorUI {
|
||||
pub sender: Sender<Command>,
|
||||
pub receiver: Receiver<State>,
|
||||
pub state: State,
|
||||
pub components: Vec<Box<dyn Component>>,
|
||||
}
|
||||
|
||||
impl EmulatorUI {
|
||||
#[must_use]
|
||||
pub fn new(sender: Sender<Command>, receiver: Receiver<State>) -> Self {
|
||||
Self {
|
||||
sender,
|
||||
receiver,
|
||||
state: State::default(),
|
||||
components: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_component(&mut self, component: Box<dyn Component>) {
|
||||
self.components.push(component);
|
||||
}
|
||||
|
||||
fn update_state(&mut self) {
|
||||
while let Ok(state) = self.receiver.try_recv() {
|
||||
self.state = state;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for EmulatorUI {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
self.update_state();
|
||||
|
||||
if self.state.running == Running::Running {
|
||||
ctx.request_repaint();
|
||||
}
|
||||
|
||||
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
|
||||
ui.with_layout(
|
||||
egui::Layout::top_down_justified(egui::Align::Center)
|
||||
.with_main_align(egui::Align::Min),
|
||||
|ui| {
|
||||
ui.allocate_space(egui::vec2(0.0, 15.0));
|
||||
ui.heading("DSA Simulator (Damn Simple Architecture 🔥)");
|
||||
ui.allocate_space(egui::vec2(0.0, 15.0));
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |_ui| {
|
||||
egui::Window::new("Main Menu")
|
||||
.resizable(false)
|
||||
.default_width(300.0)
|
||||
.show(ctx, |ui| {
|
||||
super::menu::render_menu(self, ui, ctx);
|
||||
});
|
||||
|
||||
for c in &mut self.components {
|
||||
let mut visible = *c.visible();
|
||||
if visible {
|
||||
egui::Window::new(c.name())
|
||||
.open(&mut visible)
|
||||
.show(ctx, |ui| {
|
||||
c.render(&mut self.state, ui, ctx);
|
||||
});
|
||||
}
|
||||
*c.visible() = visible;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
egui::TopBottomPanel::bottom("bottom_panel").show(ctx, |_ui| {
|
||||
// interface::bottompanel::render_bottom_panel(self, ui, ctx);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
use std::{num::ParseIntError, sync::mpsc::Sender};
|
||||
|
||||
use crate::emulator::{
|
||||
system::model::{Command, State},
|
||||
ui::interface::Component,
|
||||
};
|
||||
|
||||
pub struct MemoryInspector {
|
||||
view_size: u32,
|
||||
view_addr: u32,
|
||||
visible: bool,
|
||||
addr_input: String,
|
||||
sender: Sender<Command>,
|
||||
}
|
||||
|
||||
impl MemoryInspector {
|
||||
#[must_use]
|
||||
pub const fn new(sender: Sender<Command>) -> Self {
|
||||
Self {
|
||||
view_size: 256,
|
||||
view_addr: 0,
|
||||
visible: false,
|
||||
addr_input: String::new(),
|
||||
sender,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for MemoryInspector {
|
||||
fn category(&self) -> super::interface::Category {
|
||||
super::interface::Category::Memory
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"Memory Inspector"
|
||||
}
|
||||
|
||||
fn visible(&mut self) -> &mut bool {
|
||||
&mut self.visible
|
||||
}
|
||||
|
||||
fn render(&mut self, state: &mut State, ui: &mut egui::Ui, ctx: &egui::Context) {
|
||||
// Right column - Memory
|
||||
ui.vertical(|ui| {
|
||||
ui.heading("Memory Inspector");
|
||||
ui.add_space(10.0);
|
||||
|
||||
// Address input section
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Address:");
|
||||
|
||||
let address_response = ui.add(
|
||||
egui::TextEdit::singleline(&mut self.addr_input)
|
||||
.hint_text("0x1000 or 4096")
|
||||
.desired_width(150.0),
|
||||
);
|
||||
|
||||
ui.add_space(10.0);
|
||||
|
||||
// Search button
|
||||
let search_clicked = ui.button("🔍 Search").clicked();
|
||||
|
||||
// Handle Enter key in text field
|
||||
let enter_pressed =
|
||||
address_response.lost_focus() && ctx.input(|i| i.key_pressed(egui::Key::Enter));
|
||||
|
||||
if search_clicked || enter_pressed {
|
||||
if let Ok(new) = parse_address(&self.addr_input) {
|
||||
self.view_addr = new;
|
||||
|
||||
if let Err(why) = self.sender.send(Command::Read(new, self.view_size)) {
|
||||
panic!(
|
||||
"Error sending message across threads -- cannot be recovered: {why}"
|
||||
)
|
||||
}
|
||||
} else {
|
||||
state.error = Some("Invalid address".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
ui.label("(hex or decimal)");
|
||||
});
|
||||
|
||||
// Show input error if any
|
||||
if let Some(error) = &state.error {
|
||||
ui.colored_label(egui::Color32::RED, format!("Error: {error}"));
|
||||
}
|
||||
|
||||
ui.add_space(10.0);
|
||||
|
||||
// Memory table
|
||||
egui::ScrollArea::vertical()
|
||||
.auto_shrink(true)
|
||||
.id_salt("memory_inspector_scroll")
|
||||
.show(ui, |ui| {
|
||||
egui::Grid::new("memory_grid")
|
||||
.spacing([12.0, 2.0])
|
||||
.min_col_width(5.0)
|
||||
.striped(true)
|
||||
.show(ui, |ui| {
|
||||
// Header
|
||||
ui.strong("Address");
|
||||
|
||||
for i in 0..4 {
|
||||
ui.strong(format!("{i:X}"));
|
||||
}
|
||||
|
||||
ui.strong("Decimal");
|
||||
ui.strong("Instruction");
|
||||
|
||||
ui.end_row();
|
||||
|
||||
// Memory data (8 bytes per row)
|
||||
for (row, chunk) in (0u32..).zip(state.memory_view.chunks(4)) {
|
||||
let row_address = self.view_addr + (row * 4);
|
||||
ui.monospace(format!("0x{row_address:08X} ({row_address})"));
|
||||
for &byte in chunk {
|
||||
ui.monospace(format!("{byte:02X}"));
|
||||
}
|
||||
|
||||
// Fill remaining columns if last row is incomplete
|
||||
for _ in chunk.len()..4 {
|
||||
ui.label("");
|
||||
}
|
||||
|
||||
// combine all 4 bytes in the chunk into a u32
|
||||
let combined = chunk
|
||||
.iter()
|
||||
.fold(0u32, |acc, &byte| acc << 8 | u32::from(byte));
|
||||
|
||||
ui.monospace(format!("{combined}"));
|
||||
// ui.monospace(format!("{:?}", Instruction::decode(combined)));
|
||||
ui.monospace("TODO! instruction");
|
||||
|
||||
ui.end_row();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_address(address: &str) -> Result<u32, ParseIntError> {
|
||||
if let Some(hex_part) = address.strip_prefix("0x") {
|
||||
return u32::from_str_radix(hex_part, 16);
|
||||
}
|
||||
|
||||
if let Some(bin_part) = address.strip_prefix("0b") {
|
||||
return u32::from_str_radix(bin_part, 2);
|
||||
}
|
||||
|
||||
if let Some(oct_part) = address.strip_prefix("0o") {
|
||||
return u32::from_str_radix(oct_part, 8);
|
||||
}
|
||||
|
||||
address.parse::<u32>()
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
use crate::emulator::ui::interface::{Category, EmulatorUI};
|
||||
|
||||
pub fn render_menu(state: &mut EmulatorUI, ui: &mut egui::Ui, _ctx: &egui::Context) {
|
||||
ui.with_layout(
|
||||
egui::Layout::top_down_justified(egui::Align::Center),
|
||||
|ui| {
|
||||
ui.set_max_width(300.0);
|
||||
ui.set_min_width(300.0);
|
||||
ui.spacing_mut().button_padding = egui::vec2(10.0, 5.0);
|
||||
|
||||
for cat in Category::list() {
|
||||
ui.add_space(10.0);
|
||||
ui.heading(cat.as_str());
|
||||
ui.add_space(10.0);
|
||||
|
||||
for comp in &mut state.components {
|
||||
let name = comp.name();
|
||||
if comp.category() == cat {
|
||||
ui.toggle_value(comp.visible(), name);
|
||||
}
|
||||
}
|
||||
|
||||
ui.add_space(10.0);
|
||||
ui.separator();
|
||||
}
|
||||
|
||||
ui.add_space(10.0);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
pub mod control_unit;
|
||||
pub mod editor;
|
||||
pub mod interface;
|
||||
pub mod memory_inspector;
|
||||
pub mod menu;
|
||||
pub mod stack_inspector;
|
||||
@@ -1,70 +0,0 @@
|
||||
use crate::{
|
||||
common::instructions::Register,
|
||||
emulator::{system::model::State, ui::interface::Component},
|
||||
};
|
||||
|
||||
pub struct StackInspector {
|
||||
visible: bool,
|
||||
}
|
||||
|
||||
impl Default for StackInspector {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl StackInspector {
|
||||
#[must_use]
|
||||
pub const fn new() -> Self {
|
||||
Self { visible: false }
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for StackInspector {
|
||||
fn visible(&mut self) -> &mut bool {
|
||||
&mut self.visible
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"Stack Inspector"
|
||||
}
|
||||
|
||||
fn category(&self) -> super::interface::Category {
|
||||
super::interface::Category::Memory
|
||||
}
|
||||
|
||||
fn render(&mut self, state: &mut State, ui: &mut egui::Ui, _ctx: &egui::Context) {
|
||||
ui.vertical(|ui| {
|
||||
ui.heading("Stack Inspector");
|
||||
egui::ScrollArea::vertical()
|
||||
.id_salt("stack_inspector_scroll")
|
||||
.show(ui, |ui| {
|
||||
egui::Grid::new("stack_grid")
|
||||
.num_columns(2)
|
||||
.spacing([40.0, 4.0])
|
||||
.striped(true)
|
||||
.show(ui, |ui| {
|
||||
ui.label("Address");
|
||||
ui.label("Value");
|
||||
ui.end_row();
|
||||
|
||||
for (i, value) in (0u32..).zip(state.stack_view.iter().take(32)) {
|
||||
ui.label(format!(
|
||||
"{} [{}]",
|
||||
i,
|
||||
state.reg_file.get(Register::Spr) - i * 4
|
||||
));
|
||||
ui.label(format!("0x{value:08X} ({value})"));
|
||||
ui.end_row();
|
||||
}
|
||||
|
||||
if state.stack_view.is_empty() {
|
||||
ui.label("(empty)");
|
||||
ui.label("-");
|
||||
ui.end_row();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
#![deny(
|
||||
clippy::unwrap_used,
|
||||
clippy::nursery,
|
||||
clippy::perf,
|
||||
clippy::pedantic,
|
||||
clippy::complexity
|
||||
)]
|
||||
#![allow(
|
||||
clippy::cast_possible_truncation,
|
||||
clippy::missing_panics_doc,
|
||||
clippy::missing_errors_doc,
|
||||
clippy::match_wildcard_for_single_variants
|
||||
)]
|
||||
|
||||
pub mod common;
|
||||
pub mod emulator;
|
||||
-53
@@ -1,53 +0,0 @@
|
||||
use std::thread;
|
||||
|
||||
use dsa_rs::emulator::{
|
||||
system::{emulator::run_emulator, memory::MainStore, processor::Processor},
|
||||
ui::{
|
||||
control_unit::ControlPanel, editor::Editor, interface::EmulatorUI,
|
||||
memory_inspector::MemoryInspector, stack_inspector::StackInspector,
|
||||
},
|
||||
};
|
||||
|
||||
fn main() -> Result<(), eframe::Error> {
|
||||
// Initialize Channels
|
||||
let (cmd_sender, cmd_receiver) = std::sync::mpsc::channel();
|
||||
let (state_sender, state_receiver) = std::sync::mpsc::channel();
|
||||
|
||||
let mainstore = MainStore::new();
|
||||
let processor = Processor::new(Box::new(mainstore), vec![]);
|
||||
|
||||
thread::spawn(move || {
|
||||
run_emulator(&cmd_receiver, &state_sender, processor);
|
||||
});
|
||||
|
||||
// Create UI
|
||||
let mut ui = EmulatorUI::new(cmd_sender.clone(), state_receiver);
|
||||
|
||||
// Create UI modules
|
||||
let control_unit = ControlPanel::new(cmd_sender.clone());
|
||||
ui.add_component(Box::new(control_unit));
|
||||
|
||||
let mem_inspector = MemoryInspector::new(cmd_sender.clone());
|
||||
ui.add_component(Box::new(mem_inspector));
|
||||
|
||||
let stack_inspector = StackInspector::new();
|
||||
ui.add_component(Box::new(stack_inspector));
|
||||
|
||||
let editor = Editor::new(cmd_sender.clone());
|
||||
ui.add_component(Box::new(editor));
|
||||
|
||||
// Run UI
|
||||
let options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default().with_inner_size([800.0, 600.0]),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
eframe::run_native(
|
||||
"DSA Simulator (Damn Simple Architecture 🔥)",
|
||||
options,
|
||||
Box::new(move |cc| {
|
||||
cc.egui_ctx.set_visuals(egui::Visuals::default());
|
||||
Ok(Box::new(ui))
|
||||
}),
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user