From 4bf88cfa5379d84549c199709a7589df77029f8b Mon Sep 17 00:00:00 2001 From: Murad Karammaev Date: Fri, 12 Aug 2022 22:57:51 +0300 Subject: [PATCH] use cargo workspace --- Cargo.lock | 260 ++++++- Cargo.toml | 14 +- src/assembler.rs | 966 ----------------------- src/cpu.rs | 1634 --------------------------------------- src/instruction.rs | 354 --------- src/lib.rs | 38 - toy_cpu_4bit/Cargo.toml | 9 + toy_cpu_4bit/src/assembler.rs | 966 +++++++++++++++++++++++ toy_cpu_4bit/src/cpu.rs | 1634 +++++++++++++++++++++++++++++++++++++++ toy_cpu_4bit/src/instruction.rs | 354 +++++++++ toy_cpu_4bit/src/lib.rs | 38 + toyasm/Cargo.lock | 286 ------- toyasm/Cargo.toml | 2 +- toyvm/Cargo.lock | 286 ------- toyvm/Cargo.toml | 2 +- 15 files changed, 3261 insertions(+), 3582 deletions(-) delete mode 100644 src/assembler.rs delete mode 100644 src/cpu.rs delete mode 100644 src/instruction.rs delete mode 100644 src/lib.rs create mode 100644 toy_cpu_4bit/Cargo.toml create mode 100644 toy_cpu_4bit/src/assembler.rs create mode 100644 toy_cpu_4bit/src/cpu.rs create mode 100644 toy_cpu_4bit/src/instruction.rs create mode 100644 toy_cpu_4bit/src/lib.rs delete mode 100644 toyasm/Cargo.lock delete mode 100644 toyvm/Cargo.lock diff --git a/Cargo.lock b/Cargo.lock index 34155ee..b8c8e7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,22 +12,175 @@ dependencies = [ ] [[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "clap" +version = "3.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3dbbb6653e7c55cc8595ad3e1f7be8f32aba4eb7ff7f0fd1163d4f3d137c0a9" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "clap_lex", + "indexmap", + "once_cell", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_derive" +version = "3.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ba52acd3b0a5c33aeada5cdaa3267cdc7c594a98731d4268cdc1532f4264cb4" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] +name = "libc" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "505e71a4706fa491e9b1b55f51b95d4037d0821ee40131190475f692b35b009b" + +[[package]] name = "memchr" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "once_cell" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" + +[[package]] +name = "os_str_bytes" +version = "6.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "648001efe5d5c0102d8cea768e348da85d90af8ba91f0bea908f157951493cd4" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] [[package]] name = "regex" -version = "1.5.4" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" dependencies = [ "aho-corasick", "memchr", @@ -36,9 +189,41 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.25" +version = "0.6.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" [[package]] name = "toy_cpu_4bit" @@ -50,7 +235,66 @@ dependencies = [ ] [[package]] +name = "toyasm" +version = "0.1.0" +dependencies = [ + "clap", + "toy_cpu_4bit", +] + +[[package]] +name = "toyvm" +version = "0.1.0" +dependencies = [ + "clap", + "toy_cpu_4bit", +] + +[[package]] +name = "unicode-ident" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" + +[[package]] name = "ux" -version = "0.1.3" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efdcf885b33bb81bc9336e66cebc10d503288449466c0e43e51250ddc93a3d8" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88dfeb711b61ce620c0cb6fd9f8e3e678622f0c971da2a63c4b3e25e88ed012f" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index 902c4cd..7a3f37d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,7 @@ -[package] -name = "toy_cpu_4bit" -version = "0.1.0" -edition = "2021" +[workspace] -[dependencies] -ux = "0.1.3" -regex = "1.5.4" -lazy_static = "1.4.0" +members = [ + "toyvm", + "toyasm", + "toy_cpu_4bit", +] diff --git a/src/assembler.rs b/src/assembler.rs deleted file mode 100644 index 60e8400..0000000 --- a/src/assembler.rs +++ /dev/null @@ -1,966 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright Murad Karammaev, Nikita Kuzmin - -use crate::instruction::{decode, encode, Instruction, Register, ShiftMode}; -use regex::Regex; -use std::{ - error::Error, - fmt::{Display, Formatter}, - num, - ops::RangeInclusive, -}; -use ux::{u2, u3, u4}; - -type InstructionDecoder = fn(&str, &Regex) -> Result; - -const REGEX_REG: &str = r"(R[01])"; -const REGEX_REG_HALF: &str = r"(R[01]\.[lh])"; -const REGEX_NUM: &str = r"(\d{1,3}|0b[01]{1,8}|0o[0-7]{1,3}|0x[\da-fA-F]{1,2})"; -const REGEX_INUM: &str = r"(-?\d{1,3}|-?0b[01]{1,8}|-?0o[0-7]{1,3}|-?0x[\da-fA-F]{1,2})"; -const REGEX_ADDR: &str = r"\[(\d{1,3}|0b[01]{1,8}|0o[0-7]{1,3}|0x[\da-fA-F]{1,2})\]"; -const REGEX_COMMA: &str = r"\s*,\s*"; -const REGEX_CARRY: &str = r"([01])"; - -macro_rules! asm_entry { - ($regex:expr, $handler:expr) => { - (Regex::new(&$regex).unwrap(), $handler) - }; -} - -#[derive(Debug, Clone)] -enum AssemblerErrorKind { - TooMuchInstructions, - UnknownInstruction { - instruction: String, - }, - InvalidRegister { - register: String, - }, - InvalidRegisterHalf { - register_half: String, - }, - InvalidCarry { - carry: String, - }, - NumberOutOfRange { - number: i128, - range: RangeInclusive, - }, - InvalidInteger { - parse_int_error: num::ParseIntError, - }, - InvalidRelativeJumpOffset { - jump_offset: i8, - }, - MalformedRegisterToRegisterCopy, -} - -impl AssemblerErrorKind { - fn unknown_instruction(instruction: &str) -> AssemblerErrorKind { - AssemblerErrorKind::UnknownInstruction { - instruction: instruction.to_string(), - } - } - - fn invalid_register(register: &str) -> AssemblerErrorKind { - AssemblerErrorKind::InvalidRegister { - register: register.to_string(), - } - } - - fn invalid_register_half(register_half: &str) -> AssemblerErrorKind { - AssemblerErrorKind::InvalidRegisterHalf { - register_half: register_half.to_string(), - } - } - - fn invalid_carry(carry: &str) -> AssemblerErrorKind { - AssemblerErrorKind::InvalidCarry { - carry: carry.to_string(), - } - } - - fn number_out_of_range + Copy>( - number: T, - range: RangeInclusive, - ) -> AssemblerErrorKind { - AssemblerErrorKind::NumberOutOfRange { - number: number.into(), - // am I doing this right? - range: (*range.start()).into()..=(*range.end()).into(), - } - } - - fn invalid_relative_jump_offset(jump_offset: i8) -> AssemblerErrorKind { - AssemblerErrorKind::InvalidRelativeJumpOffset { jump_offset } - } - - fn malformed_register_to_register_copy() -> AssemblerErrorKind { - AssemblerErrorKind::MalformedRegisterToRegisterCopy - } - - fn to_assembler_error(&self, line: usize) -> AssemblerError { - AssemblerError { - line, - error: self.clone(), - } - } -} - -impl From for AssemblerErrorKind { - fn from(parse_int_error: num::ParseIntError) -> AssemblerErrorKind { - AssemblerErrorKind::InvalidInteger { parse_int_error } - } -} - -impl Display for AssemblerErrorKind { - fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { - write!( - f, - "{}", - match &self { - AssemblerErrorKind::TooMuchInstructions => String::from("too much instructions"), - AssemblerErrorKind::UnknownInstruction { instruction } => - format!("unknown instruction: '{}'", instruction), - AssemblerErrorKind::InvalidRegister { register } => - format!("invalid register: '{}'", register), - AssemblerErrorKind::InvalidRegisterHalf { register_half } => - format!("invalid register half: '{}'", register_half), - AssemblerErrorKind::InvalidCarry { carry } => format!("invalid carry: '{}'", carry), - AssemblerErrorKind::NumberOutOfRange { number, range } => format!( - "number {} is out of range [{}, {}]", - number, - range.start(), - range.end() - ), - AssemblerErrorKind::InvalidInteger { parse_int_error } => - format!("failed to parse integer: {}", parse_int_error), - AssemblerErrorKind::InvalidRelativeJumpOffset { jump_offset } => - format!("{} is not a valid relative jump offset", jump_offset), - AssemblerErrorKind::MalformedRegisterToRegisterCopy => - String::from("malformed register to register copy"), - } - ) - } -} - -impl Error for AssemblerErrorKind { - fn description(&self) -> &str { - match self { - AssemblerErrorKind::TooMuchInstructions => "Too much instructions", - AssemblerErrorKind::UnknownInstruction { .. } => "Unknown instruction", - AssemblerErrorKind::InvalidRegister { .. } => "Invalid register", - AssemblerErrorKind::InvalidRegisterHalf { .. } => "Invalid register half", - AssemblerErrorKind::InvalidCarry { .. } => "Invalid carry", - AssemblerErrorKind::NumberOutOfRange { .. } => "Number out of range", - AssemblerErrorKind::InvalidInteger { .. } => "Failed to parse integer", - AssemblerErrorKind::InvalidRelativeJumpOffset { .. } => "Invalid relative jump offset", - AssemblerErrorKind::MalformedRegisterToRegisterCopy => { - "Malformed register to register copy" - } - } - } -} - -#[derive(Debug)] -pub struct AssemblerError { - line: usize, - error: AssemblerErrorKind, -} - -type AssemblerResult = Result; - -impl Display for AssemblerError { - fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { - write!(f, "Failed to assemble, line {}: {}", self.line, self.error) - } -} - -impl Error for AssemblerError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - Some(&self.error) - } -} - -impl AssemblerError { - fn too_much_instructions(line: usize) -> AssemblerError { - AssemblerError { - line, - error: AssemblerErrorKind::TooMuchInstructions, - } - } -} - -pub struct Assembler { - table: Vec<(Regex, InstructionDecoder)>, -} - -impl Assembler { - pub fn new() -> Self { - Assembler { - table: vec![ - asm_entry!(format!(r"^BYTE\s{}$", REGEX_NUM), parse_byte), - asm_entry!( - format!(r"^MOV\s{}{}{}$", REGEX_REG, REGEX_COMMA, REGEX_ADDR), - parse_mov_reg_mem - ), - asm_entry!( - format!(r"^MOV\s{}{}{}$", REGEX_ADDR, REGEX_COMMA, REGEX_REG), - parse_mov_mem_reg - ), - asm_entry!( - format!(r"^MOV\s{}{}{}$", REGEX_REG_HALF, REGEX_COMMA, REGEX_NUM), - parse_mov_reg_half_imm - ), - asm_entry!( - format!(r"^MOV\s{}{}{}", REGEX_REG, REGEX_COMMA, REGEX_REG), - parse_mov_reg_reg - ), - asm_entry!(format!(r"^JMP\s{}$", REGEX_INUM), parse_jmp), - asm_entry!(format!(r"^JMPC\s{}$", REGEX_INUM), parse_jmpc), - asm_entry!(format!(r"^AJMP\s{}$", REGEX_REG), parse_ajmp), - asm_entry!(format!(r"^AJMPC\s{}$", REGEX_REG), parse_ajmpc), - asm_entry!(r"^EQ$", |_, _| Ok(Instruction::CmpEqual)), - asm_entry!(r"^GT$", |_, _| Ok(Instruction::CmpGreater)), - asm_entry!(r"^LE$", |_, _| Ok(Instruction::CmpLess)), - asm_entry!(r"^NOT$", |_, _| Ok(Instruction::Not)), - asm_entry!(r"^SHR$", |_, _| Ok(Instruction::BitShiftVar { - mode: ShiftMode::Logical, - right: true - })), - asm_entry!(r"^SHL$", |_, _| Ok(Instruction::BitShiftVar { - mode: ShiftMode::Logical, - right: false - })), - asm_entry!(r"^ROTR$", |_, _| Ok(Instruction::BitShiftVar { - mode: ShiftMode::Circular, - right: true - })), - asm_entry!(r"^ROTL$", |_, _| Ok(Instruction::BitShiftVar { - mode: ShiftMode::Circular, - right: false - })), - asm_entry!( - format!(r"^SHR\s{}{}{}$", REGEX_REG, REGEX_COMMA, REGEX_NUM), - parse_shr - ), - asm_entry!( - format!(r"^SHL\s{}{}{}$", REGEX_REG, REGEX_COMMA, REGEX_NUM), - parse_shl - ), - asm_entry!( - format!(r"^ROTR\s{}{}{}$", REGEX_REG, REGEX_COMMA, REGEX_NUM), - parse_rotr - ), - asm_entry!( - format!(r"^ROTL\s{}{}{}$", REGEX_REG, REGEX_COMMA, REGEX_NUM), - parse_rotl - ), - asm_entry!(format!(r"^INC\s{}$", REGEX_REG), parse_inc), - asm_entry!(format!(r"^DEC\s{}$", REGEX_REG), parse_dec), - asm_entry!(r"^ADD$", |_, _| Ok(Instruction::Add)), - asm_entry!(r"^SUB$", |_, _| Ok(Instruction::Sub)), - asm_entry!(r"^AND$", |_, _| Ok(Instruction::And)), - asm_entry!(r"^OR$", |_, _| Ok(Instruction::Or)), - asm_entry!(r"^XOR$", |_, _| Ok(Instruction::Xor)), - asm_entry!(r"^XNOR$", |_, _| Ok(Instruction::Xnor)), - asm_entry!(format!(r"^COMPL\s{}$", REGEX_REG), parse_compl), - asm_entry!(format!(r"^SETC\s{}$", REGEX_CARRY), parse_setc), - asm_entry!(r"^NOP$", |_, _| Ok(Instruction::Nop)), - asm_entry!(r"^HALT$", |_, _| Ok(Instruction::Halt)), - asm_entry!(format!(r"^CLOAD\s{}$", REGEX_REG), parse_cload), - asm_entry!(format!(r"^ZERO\s{}$", REGEX_REG), parse_zero), - asm_entry!(r"^DIV$", |_, _| Ok(Instruction::Div)), - asm_entry!(r"^MUL$", |_, _| Ok(Instruction::Mul)), - asm_entry!(r"^SWAP$", |_, _| Ok(Instruction::Swap)), - asm_entry!(r"^PRTCHK$", |_, _| Ok(Instruction::PortReady)), - asm_entry!(r"^PRTRD$", |_, _| Ok(Instruction::ReadPort)), - asm_entry!(r"^PRTWR$", |_, _| Ok(Instruction::WritePort)), - ], - } - } - - pub fn assemble(&self, input: &str) -> AssemblerResult<[u8; 0x100]> { - let mut ret = [0u8; 0x100]; - let mut i: usize = 0; - for (line_num, line) in input - .lines() - .map(|line| strip_comment(line).trim()) - .enumerate() - { - match line { - "" => (), - line => { - if i > 0xFF { - return Err(AssemblerError::too_much_instructions(line_num)); - } - let insn = self - .line_to_insn(line) - .map_err(|e| e.to_assembler_error(line_num))?; - ret[i] = encode(insn); - i += 1; - } - } - } - Ok(ret) - } - - fn line_to_insn(&self, line: &str) -> Result { - for (regex, handler) in &self.table { - if regex.is_match(line) { - return handler(line, regex); - } - } - Err(AssemblerErrorKind::unknown_instruction(line)) - } -} - -impl Default for Assembler { - fn default() -> Self { - Self::new() - } -} - -fn strip_comment(input: &str) -> &str { - match input.find('#') { - Some(x) => &input[0..x], - None => input, - } -} - -fn parse_u128(src: &str) -> Result { - if let Some(s) = src.strip_prefix("0b") { - Ok(u128::from_str_radix(s, 2)?) - } else if let Some(s) = src.strip_prefix("0o") { - Ok(u128::from_str_radix(s, 8)?) - } else if let Some(s) = src.strip_prefix("0x") { - Ok(u128::from_str_radix(s, 16)?) - } else { - Ok(src.parse()?) - } -} - -fn parse_u8(src: &str) -> Result { - match parse_u128(src)? { - x @ 0..=255 => Ok(x as u8), - x => Err(AssemblerErrorKind::number_out_of_range(x as i128, 0..=255)), - } -} - -fn parse_i8(src: &str) -> Result { - let (sign, num): (i128, u128) = match src.strip_prefix('-') { - Some(s) => (-1, parse_u128(s)?), - None => (1, parse_u128(src)?), - }; - match (sign, num) { - (1, x) if x <= 127 => Ok((x as i128 * sign) as i8), - (-1, x) if x <= 128 => Ok((x as i128 * sign) as i8), - (_, x) => Err(AssemblerErrorKind::number_out_of_range( - x as i128, - -128..=127, - )), - } -} - -fn parse_u4(src: &str) -> Result { - match parse_u8(src)? { - x @ 0..=15 => Ok(u4::new(x)), - x => Err(AssemblerErrorKind::number_out_of_range(x as i128, 0..=15)), - } -} - -fn parse_u3(src: &str) -> Result { - match parse_u8(src)? { - x @ 0..=7 => Ok(u3::new(x)), - x => Err(AssemblerErrorKind::number_out_of_range(x, 0..=7)), - } -} - -fn parse_reg(src: &str) -> Result { - match src { - "R0" => Ok(Register::R0), - "R1" => Ok(Register::R1), - x => Err(AssemblerErrorKind::invalid_register(x)), - } -} - -fn parse_reg_half(src: &str) -> Result<(Register, bool), AssemblerErrorKind> { - match src { - "R0.l" => Ok((Register::R0, true)), - "R0.h" => Ok((Register::R0, false)), - "R1.l" => Ok((Register::R1, true)), - "R1.h" => Ok((Register::R1, false)), - x => Err(AssemblerErrorKind::invalid_register_half(x)), - } -} - -fn parse_carry(src: &str) -> Result { - match src { - "0" => Ok(false), - "1" => Ok(true), - x => Err(AssemblerErrorKind::invalid_carry(x)), - } -} - -fn parse_byte(line: &str, regex: &Regex) -> Result { - Ok(decode(parse_u8( - regex.captures(line).unwrap().get(1).unwrap().as_str(), - )?)) -} - -fn parse_mov_reg_mem(line: &str, regex: &Regex) -> Result { - let captures = regex.captures(line).unwrap(); - let reg = parse_reg(captures.get(1).unwrap().as_str())?; - let addr = parse_u4(captures.get(2).unwrap().as_str())?; - Ok(Instruction::Load { reg, addr }) -} - -fn parse_mov_mem_reg(line: &str, regex: &Regex) -> Result { - let captures = regex.captures(line).unwrap(); - let addr = parse_u4(captures.get(1).unwrap().as_str())?; - let reg = parse_reg(captures.get(2).unwrap().as_str())?; - Ok(Instruction::Store { reg, addr }) -} - -fn parse_mov_reg_half_imm(line: &str, regex: &Regex) -> Result { - let captures = regex.captures(line).unwrap(); - let (reg, low) = parse_reg_half(captures.get(1).unwrap().as_str())?; - let val = parse_u4(captures.get(2).unwrap().as_str())?; - Ok(Instruction::LoadImmediate { low, reg, val }) -} - -fn parse_mov_reg_reg(line: &str, regex: &Regex) -> Result { - let captures = regex.captures(line).unwrap(); - let reg_to = parse_reg(captures.get(1).unwrap().as_str())?; - let reg_from = parse_reg(captures.get(2).unwrap().as_str())?; - match (reg_to, reg_from) { - (Register::R0, Register::R1) => Ok(Instruction::Copy { - reg_from: Register::R1, - }), - (Register::R1, Register::R0) => Ok(Instruction::Copy { - reg_from: Register::R0, - }), - _ => Err(AssemblerErrorKind::malformed_register_to_register_copy()), - } -} - -fn parse_jmp_impl( - line: &str, - regex: &Regex, - use_carry: bool, -) -> Result { - match parse_i8(regex.captures(line).unwrap().get(1).unwrap().as_str())? { - x @ -3..=-1 => Ok(Instruction::NearJumpBackward { - use_carry, - offset: u2::new(-x as u8), - }), - x @ 0..=7 => Ok(Instruction::NearJumpForward { - use_carry, - offset: u3::new(x as u8), - }), - x => Err(AssemblerErrorKind::invalid_relative_jump_offset(x)), - } -} - -fn parse_jmp(line: &str, regex: &Regex) -> Result { - parse_jmp_impl(line, regex, false) -} - -fn parse_jmpc(line: &str, regex: &Regex) -> Result { - parse_jmp_impl(line, regex, true) -} - -fn parse_ajmp_impl( - line: &str, - regex: &Regex, - use_carry: bool, -) -> Result { - let reg = parse_reg(regex.captures(line).unwrap().get(1).unwrap().as_str())?; - Ok(Instruction::FarJump { reg, use_carry }) -} - -fn parse_ajmp(line: &str, regex: &Regex) -> Result { - parse_ajmp_impl(line, regex, false) -} - -fn parse_ajmpc(line: &str, regex: &Regex) -> Result { - parse_ajmp_impl(line, regex, true) -} - -fn parse_shift( - line: &str, - regex: &Regex, - right: bool, - mode: ShiftMode, -) -> Result { - let captures = regex.captures(line).unwrap(); - let reg = parse_reg(captures.get(1).unwrap().as_str())?; - let len = parse_u3(captures.get(2).unwrap().as_str())?; - Ok(Instruction::BitShift { - right, - reg, - mode, - len, - }) -} - -fn parse_shr(line: &str, regex: &Regex) -> Result { - parse_shift(line, regex, true, ShiftMode::Logical) -} - -fn parse_shl(line: &str, regex: &Regex) -> Result { - parse_shift(line, regex, false, ShiftMode::Logical) -} - -fn parse_rotr(line: &str, regex: &Regex) -> Result { - parse_shift(line, regex, true, ShiftMode::Circular) -} - -fn parse_rotl(line: &str, regex: &Regex) -> Result { - parse_shift(line, regex, false, ShiftMode::Circular) -} - -fn parse_inc(line: &str, regex: &Regex) -> Result { - let reg = parse_reg(regex.captures(line).unwrap().get(1).unwrap().as_str())?; - Ok(Instruction::Inc { reg }) -} - -fn parse_dec(line: &str, regex: &Regex) -> Result { - let reg = parse_reg(regex.captures(line).unwrap().get(1).unwrap().as_str())?; - Ok(Instruction::Dec { reg }) -} - -fn parse_compl(line: &str, regex: &Regex) -> Result { - let reg = parse_reg(regex.captures(line).unwrap().get(1).unwrap().as_str())?; - Ok(Instruction::Complement { reg }) -} - -fn parse_setc(line: &str, regex: &Regex) -> Result { - let carry = parse_carry(regex.captures(line).unwrap().get(1).unwrap().as_str())?; - Ok(Instruction::Setc { carry }) -} - -fn parse_cload(line: &str, regex: &Regex) -> Result { - let reg = parse_reg(regex.captures(line).unwrap().get(1).unwrap().as_str())?; - Ok(Instruction::Cload { reg }) -} - -fn parse_zero(line: &str, regex: &Regex) -> Result { - let reg = parse_reg(regex.captures(line).unwrap().get(1).unwrap().as_str())?; - Ok(Instruction::Zero { reg }) -} - -#[cfg(test)] -mod tests { - use crate::{ - assembler::{strip_comment, Assembler}, - instruction::{decode, Instruction, Register, ShiftMode}, - }; - use lazy_static::lazy_static; - use std::fmt::{Binary, Display, LowerHex, Octal}; - use ux::{u2, u3, u4}; - - lazy_static! { - static ref ASM: Assembler = Assembler::new(); - } - - enum IntegerFormat { - Decimal, - Binary, - Octal, - Hexadecimal, - None, - } - - struct PrintIntegers { - num: T, - format: IntegerFormat, - } - - impl PrintIntegers { - fn new(num: T) -> Self { - PrintIntegers { - num, - format: IntegerFormat::Decimal, - } - } - } - - impl Iterator for PrintIntegers { - type Item = String; - - fn next(&mut self) -> Option { - match self.format { - IntegerFormat::Decimal => { - self.format = IntegerFormat::Binary; - Some(format!("{}", self.num)) - } - IntegerFormat::Binary => { - self.format = IntegerFormat::Octal; - Some(format!("{:#b}", self.num)) - } - IntegerFormat::Octal => { - self.format = IntegerFormat::Hexadecimal; - Some(format!("{:#o}", self.num)) - } - IntegerFormat::Hexadecimal => { - self.format = IntegerFormat::None; - Some(format!("{:#x}", self.num)) - } - IntegerFormat::None => None, - } - } - } - - macro_rules! make_test_insn_no_args { - ($name:ident, $line:expr, $insn:expr) => { - #[test] - fn $name() { - assert_eq!(ASM.line_to_insn($line).unwrap(), $insn); - } - }; - } - - make_test_insn_no_args!(asm_line_eq, "EQ", Instruction::CmpEqual); - make_test_insn_no_args!(asm_line_gt, "GT", Instruction::CmpGreater); - make_test_insn_no_args!(asm_line_le, "LE", Instruction::CmpLess); - make_test_insn_no_args!(asm_line_not, "NOT", Instruction::Not); - make_test_insn_no_args!(asm_line_add, "ADD", Instruction::Add); - make_test_insn_no_args!(asm_line_sub, "SUB", Instruction::Sub); - make_test_insn_no_args!(asm_line_and, "AND", Instruction::And); - make_test_insn_no_args!(asm_line_or, "OR", Instruction::Or); - make_test_insn_no_args!(asm_line_xor, "XOR", Instruction::Xor); - make_test_insn_no_args!(asm_line_xnor, "XNOR", Instruction::Xnor); - make_test_insn_no_args!(asm_line_nop, "NOP", Instruction::Nop); - make_test_insn_no_args!(asm_line_halt, "HALT", Instruction::Halt); - make_test_insn_no_args!(asm_line_div, "DIV", Instruction::Div); - make_test_insn_no_args!(asm_line_mul, "MUL", Instruction::Mul); - make_test_insn_no_args!(asm_line_swap, "SWAP", Instruction::Swap); - make_test_insn_no_args!(asm_line_prtchk, "PRTCHK", Instruction::PortReady); - make_test_insn_no_args!(asm_line_prtrd, "PRTRD", Instruction::ReadPort); - make_test_insn_no_args!(asm_line_prtwr, "PRTWR", Instruction::WritePort); - make_test_insn_no_args!( - asm_line_shr, - "SHR", - Instruction::BitShiftVar { - mode: ShiftMode::Logical, - right: true - } - ); - make_test_insn_no_args!( - asm_line_shl, - "SHL", - Instruction::BitShiftVar { - mode: ShiftMode::Logical, - right: false - } - ); - make_test_insn_no_args!( - asm_line_rotr, - "ROTR", - Instruction::BitShiftVar { - mode: ShiftMode::Circular, - right: true - } - ); - make_test_insn_no_args!( - asm_line_rotl, - "ROTL", - Instruction::BitShiftVar { - mode: ShiftMode::Circular, - right: false - } - ); - - #[test] - fn asm_line_byte() { - for i in 0..=255u8 { - for j in PrintIntegers::new(i) { - assert_eq!(ASM.line_to_insn(&format!("BYTE {}", j)).unwrap(), decode(i)); - } - } - } - - #[test] - fn asm_line_mov_reg_mem() { - for (reg_str, reg) in [("R0", Register::R0)] { - for i in 0..=15u8 { - for j in PrintIntegers::new(i) { - assert_eq!( - ASM.line_to_insn(&format!("MOV {}, [{}]", reg_str, j)) - .unwrap(), - Instruction::Load { - reg: reg.clone(), - addr: u4::new(i) - } - ); - } - } - } - } - - #[test] - fn asm_line_mov_mem_reg() { - for (reg_str, reg) in [("R0", Register::R0)] { - for i in 0..=15u8 { - for j in PrintIntegers::new(i) { - assert_eq!( - ASM.line_to_insn(&format!("MOV [{}], {}", j, reg_str)) - .unwrap(), - Instruction::Store { - reg: reg.clone(), - addr: u4::new(i) - } - ); - } - } - } - } - - #[test] - fn asm_line_mov_reg_half_imm() { - for (reg_str, reg) in [("R0", Register::R0), ("R1", Register::R1)] { - for (low_str, low) in [("l", true), ("h", false)] { - for i in 0..=15 { - for j in PrintIntegers::new(i) { - assert_eq!( - ASM.line_to_insn(&format!("MOV {}.{}, {}", reg_str, low_str, j)) - .unwrap(), - Instruction::LoadImmediate { - low, - reg: reg.clone(), - val: u4::new(i) - } - ); - } - } - } - } - } - - #[test] - fn asm_line_jmp() { - for i in 0..=7u8 { - for j in PrintIntegers::new(i) { - assert_eq!( - ASM.line_to_insn(&format!("JMP {}", j)).unwrap(), - Instruction::NearJumpForward { - use_carry: false, - offset: u3::new(i) - } - ) - } - } - for i in 1..=3u8 { - for j in PrintIntegers::new(i) { - assert_eq!( - ASM.line_to_insn(&format!("JMP -{}", j)).unwrap(), - Instruction::NearJumpBackward { - use_carry: false, - offset: u2::new(i) - } - ) - } - } - } - - #[test] - fn asm_line_jmpc() { - for i in 0..=7u8 { - for j in PrintIntegers::new(i) { - assert_eq!( - ASM.line_to_insn(&format!("JMPC {}", j)).unwrap(), - Instruction::NearJumpForward { - use_carry: true, - offset: u3::new(i) - } - ) - } - } - for i in 1..=3u8 { - for j in PrintIntegers::new(i) { - assert_eq!( - ASM.line_to_insn(&format!("JMPC -{}", j)).unwrap(), - Instruction::NearJumpBackward { - use_carry: true, - offset: u2::new(i) - } - ) - } - } - } - - #[test] - fn asm_lime_ajmp() { - assert_eq!( - ASM.line_to_insn("AJMP R0").unwrap(), - Instruction::FarJump { - reg: Register::R0, - use_carry: false - } - ); - assert_eq!( - ASM.line_to_insn("AJMP R1").unwrap(), - Instruction::FarJump { - reg: Register::R1, - use_carry: false - } - ); - } - - #[test] - fn asm_lime_ajmpc() { - for (reg_str, reg) in [("R0", Register::R0), ("R1", Register::R1)] { - assert_eq!( - ASM.line_to_insn(&format!("AJMPC {}", reg_str)).unwrap(), - Instruction::FarJump { - reg, - use_carry: true - } - ); - } - } - - #[test] - fn asm_line_shift_rotate() { - for (insn, right, mode) in [ - ("SHR", true, ShiftMode::Logical), - ("SHL", false, ShiftMode::Logical), - ("ROTR", true, ShiftMode::Circular), - ("ROTL", false, ShiftMode::Circular), - ] { - for (reg_str, reg) in [("R0", Register::R0), ("R1", Register::R1)] { - for i in 0..7u8 { - for j in PrintIntegers::new(i) { - assert_eq!( - ASM.line_to_insn(&format!("{} {}, {}", insn, reg_str, j)) - .unwrap(), - Instruction::BitShift { - right, - reg: reg.clone(), - mode: mode.clone(), - len: u3::new(i) - } - ) - } - } - } - } - } - - #[test] - fn asm_line_inc() { - for (reg_str, reg) in [("R0", Register::R0), ("R1", Register::R1)] { - assert_eq!( - ASM.line_to_insn(&format!("INC {}", reg_str)).unwrap(), - Instruction::Inc { reg } - ); - } - } - - #[test] - fn asm_line_dec() { - for (reg_str, reg) in [("R0", Register::R0), ("R1", Register::R1)] { - assert_eq!( - ASM.line_to_insn(&format!("DEC {}", reg_str)).unwrap(), - Instruction::Dec { reg } - ); - } - } - - #[test] - fn asm_line_compl() { - for (reg_str, reg) in [("R0", Register::R0), ("R1", Register::R1)] { - assert_eq!( - ASM.line_to_insn(&format!("COMPL {}", reg_str)).unwrap(), - Instruction::Complement { reg } - ); - } - } - - #[test] - fn asm_line_setc() { - for (i, carry) in [(0, false), (1, true)] { - assert_eq!( - ASM.line_to_insn(&format!("SETC {}", i)).unwrap(), - Instruction::Setc { carry } - ) - } - } - - #[test] - fn asm_line_cload() { - for (reg_str, reg) in [("R0", Register::R0), ("R1", Register::R1)] { - assert_eq!( - ASM.line_to_insn(&format!("CLOAD {}", reg_str)).unwrap(), - Instruction::Cload { reg } - ); - } - } - - #[test] - fn asm_line_zero() { - for (reg_str, reg) in [("R0", Register::R0), ("R1", Register::R1)] { - assert_eq!( - ASM.line_to_insn(&format!("ZERO {}", reg_str)).unwrap(), - Instruction::Zero { reg } - ); - } - } - - #[test] - fn asm_assemble_empty() { - assert_eq!(ASM.assemble("").unwrap(), [0u8; 256]); - } - - #[test] - fn asm_assemble() { - let expected = { - let mut code = [0u8; 256]; - code[0..6].copy_from_slice(&[0x4F, 0x53, 0xE4, 0x20, 0xF8, 0xF5]); - code - }; - assert_eq!( - ASM.assemble( - r#" - MOV R0.l, 0xF - MOV R1.l, 0x3 - ADD - MOV [0], R0 - ZERO R0 - HALT - "# - ) - .unwrap(), - expected - ); - } - - #[test] - fn asm_strip_comment() { - assert_eq!(strip_comment("MOV [2], R1 # comment"), "MOV [2], R1 "); - } - - #[test] - fn asm_assemble_comments() { - let expected = { - let mut code = [0u8; 256]; - code[0..6].copy_from_slice(&[0x4F, 0x53, 0xE4, 0x20, 0xF8, 0xF5]); - code - }; - assert_eq!( - ASM.assemble( - r#" - MOV R0.l, 0xF # R0 = 15 - MOV R1.l, 0x3 # R1 = 3 - ADD # R0 += R1 (18) - MOV [0], R0 # MEM[0] = R0 (18) - ZERO R0 # R0 = 0 - HALT # EXIT - "# - ) - .unwrap(), - expected - ); - } -} diff --git a/src/cpu.rs b/src/cpu.rs deleted file mode 100644 index e4d668a..0000000 --- a/src/cpu.rs +++ /dev/null @@ -1,1634 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright Murad Karammaev, Nikita Kuzmin - -use crate::instruction::{decode, Instruction, Register, ShiftMode}; -use std::{cmp, collections::VecDeque, mem, num::Wrapping}; -use ux::{u3, u4}; - -#[allow(non_snake_case)] -pub struct Cpu { - pub IP: Wrapping, - pub R0: Wrapping, - pub R1: Wrapping, - pub C: bool, - pub code: [u8; 0x100], - pub data: [u8; 0x10], - pub port_in: VecDeque, - pub port_out: VecDeque, - pub num_cycles: u64, -} - -impl Cpu { - pub fn new(code: &[u8]) -> Cpu { - let num_instructions = cmp::min(0x100, code.len()); - let mut new_code = [0u8; 0x100]; - new_code[..num_instructions].clone_from_slice(&code[..num_instructions]); - Cpu { - IP: Wrapping(0), - R0: Wrapping(0), - R1: Wrapping(0), - C: false, - code: new_code, - data: [0u8; 0x10], - port_in: VecDeque::::new(), - port_out: VecDeque::::new(), - num_cycles: 0, - } - } - - fn load(&mut self, reg: Register, addr: u4) { - match reg { - Register::R0 => self.R0 = Wrapping(self.data[u8::from(addr) as usize]), - Register::R1 => self.R1 = Wrapping(self.data[u8::from(addr) as usize]), - } - self.IP += Wrapping(1); - } - - fn store(&mut self, reg: Register, addr: u4) { - match reg { - Register::R0 => self.data[u8::from(addr) as usize] = self.R0.0, - Register::R1 => self.data[u8::from(addr) as usize] = self.R1.0, - } - self.IP += Wrapping(1); - } - - fn apply_imm(low: bool, mut reg: u8, val: u4) -> Wrapping { - if low { - reg &= 0b11110000; - reg |= u8::from(val); - } else { - reg &= 0b00001111; - reg |= u8::from(val) << 4; - } - Wrapping(reg) - } - - fn load_imm(&mut self, low: bool, reg: Register, val: u4) { - match reg { - Register::R0 => self.R0 = Cpu::apply_imm(low, self.R0.0, val), - Register::R1 => self.R1 = Cpu::apply_imm(low, self.R1.0, val), - } - self.IP += Wrapping(1); - } - - fn near_jump(&mut self, use_carry: bool, forward: bool, offset: u8) { - if use_carry && !self.C { - self.IP += Wrapping(1); - return; - } - if forward { - self.IP += Wrapping(offset); - } else { - self.IP -= Wrapping(offset); - } - } - - fn far_jump(&mut self, use_carry: bool, reg: Register) { - if use_carry && !self.C { - self.IP += Wrapping(1); - return; - } - self.IP = match reg { - Register::R0 => self.R0, - Register::R1 => self.R1, - }; - } - - fn cmp_equal(&mut self) { - self.C = self.R0 == self.R1; - self.IP += Wrapping(1); - } - - fn cmp_gt(&mut self) { - self.C = self.R0 > self.R1; - self.IP += Wrapping(1); - } - - fn cmp_le(&mut self) { - self.C = self.R0 < self.R1; - self.IP += Wrapping(1); - } - - fn not(&mut self) { - self.C = !self.C; - self.IP += Wrapping(1); - } - - fn bit_shift_logical(&mut self, right: bool, reg: Register, len: u8) { - match len { - 0..=7 => match reg { - Register::R0 => { - self.R0 = Wrapping(if right { - self.R0.0 >> len - } else { - self.R0.0 << len - }) - } - Register::R1 => { - self.R1 = Wrapping(if right { - self.R1.0 >> len - } else { - self.R1.0 << len - }) - } - }, - _ => match reg { - Register::R0 => self.R0 = Wrapping(0), - Register::R1 => self.R1 = Wrapping(0), - }, - } - } - - fn bit_shift_circular(&mut self, right: bool, reg: Register, len: u32) { - match len { - 0..=7 => match reg { - Register::R0 => { - self.R0 = Wrapping(if right { - self.R0.0.rotate_right(len) - } else { - self.R0.0.rotate_left(len) - }) - } - Register::R1 => { - self.R1 = Wrapping(if right { - self.R1.0.rotate_right(len) - } else { - self.R1.0.rotate_left(len) - }) - } - }, - _ => match reg { - Register::R0 => self.R0 = Wrapping(0), - Register::R1 => self.R1 = Wrapping(0), - }, - } - } - - fn bit_shift(&mut self, right: bool, reg: Register, mode: ShiftMode, len: u3) { - match mode { - ShiftMode::Logical => self.bit_shift_logical(right, reg, u8::from(len)), - ShiftMode::Circular => self.bit_shift_circular(right, reg, u32::from(len)), - } - self.IP += Wrapping(1); - } - - fn inc(&mut self, reg: Register) { - match reg { - Register::R0 => { - self.R0 += Wrapping(1); - self.C = self.R0 == Wrapping(0); - } - Register::R1 => { - self.R1 += Wrapping(1); - self.C = self.R1 == Wrapping(0); - } - } - self.IP += Wrapping(1); - } - - fn dec(&mut self, reg: Register) { - match reg { - Register::R0 => { - self.R0 -= Wrapping(1); - self.C = self.R0 == Wrapping(0); - } - Register::R1 => { - self.R1 -= Wrapping(1); - self.C = self.R1 == Wrapping(0); - } - } - self.IP += Wrapping(1); - } - - fn add(&mut self) { - self.C = self.R0.0 as u16 + self.R1.0 as u16 > 0xFF; - self.R0 += self.R1; - self.IP += Wrapping(1); - } - - fn sub(&mut self) { - self.C = self.R1 > self.R0; - self.R0 -= self.R1; - self.IP += Wrapping(1); - } - - fn and(&mut self) { - self.R0 &= self.R1; - self.IP += Wrapping(1); - } - - fn or(&mut self) { - self.R0 |= self.R1; - self.IP += Wrapping(1); - } - - fn xor(&mut self) { - self.R0 ^= self.R1; - self.IP += Wrapping(1); - } - - fn xnor(&mut self) { - self.R0 = !(self.R0 ^ self.R1); - self.IP += Wrapping(1); - } - - fn complement(&mut self, reg: Register) { - match reg { - Register::R0 => self.R0 = !self.R0, - Register::R1 => self.R1 = !self.R1, - } - self.IP += Wrapping(1); - } - - fn bit_shift_var(&mut self, right: bool, mode: ShiftMode) { - match mode { - ShiftMode::Logical => self.bit_shift_logical(right, Register::R0, self.R1.0), - ShiftMode::Circular => self.bit_shift_circular(right, Register::R0, self.R1.0 as u32), - } - self.C = self.R0 == Wrapping(0); - self.IP += Wrapping(1); - } - - fn copy(&mut self, reg_from: Register) { - match reg_from { - Register::R0 => self.R1 = self.R0, - Register::R1 => self.R0 = self.R1, - } - self.IP += Wrapping(1); - } - - fn setc(&mut self, carry: bool) { - self.C = carry; - self.IP += Wrapping(1); - } - - fn nop(&mut self) { - self.IP += Wrapping(1); - } - - fn cload(&mut self, reg: Register) { - match reg { - Register::R0 => self.R0 = Wrapping(self.code[self.R0.0 as usize]), - Register::R1 => self.R1 = Wrapping(self.code[self.R0.0 as usize]), - } - self.IP += Wrapping(1); - } - - fn zero(&mut self, reg: Register) { - match reg { - Register::R0 => self.R0 = Wrapping(0), - Register::R1 => self.R1 = Wrapping(0), - } - self.IP += Wrapping(1); - } - - fn div(&mut self) { - if self.R1 == Wrapping(0) { - self.R0 = Wrapping(0xFF); - self.R1 = Wrapping(0); - self.C = true; - } else { - let q = self.R0.0 / self.R1.0; - let r = self.R0.0 % self.R1.0; - self.R0 = Wrapping(q); - self.R1 = Wrapping(r); - self.C = false; - } - self.IP += Wrapping(1); - } - - fn mul(&mut self) { - let p = self.R0.0 as u16 * self.R1.0 as u16; - self.R0 = Wrapping((p >> 8 & 0xFF) as u8); - self.R1 = Wrapping((p & 0xFF) as u8); - self.IP += Wrapping(1); - } - - fn swap(&mut self) { - mem::swap(&mut self.R0, &mut self.R1); - self.IP += Wrapping(1); - } - - fn port_ready(&mut self) { - self.C = !self.port_in.is_empty(); - self.IP += Wrapping(1); - } - - fn read_port(&mut self) { - match self.port_in.pop_front() { - Some(v) => self.R0 = Wrapping(v), - None => self.R0 = Wrapping(0), - } - self.IP += Wrapping(1); - } - - fn write_port(&mut self) { - self.port_out.push_back(self.R0.0); - self.IP += Wrapping(1); - } - - pub fn step(&mut self) -> bool { - self.num_cycles += 1; - let insn = decode(self.code[self.IP.0 as usize]); - match insn { - Instruction::Load { reg, addr } => self.load(reg, addr), - Instruction::Store { reg, addr } => self.store(reg, addr), - Instruction::LoadImmediate { low, reg, val } => self.load_imm(low, reg, val), - Instruction::NearJumpBackward { use_carry, offset } => { - self.near_jump(use_carry, false, u8::from(offset)) - } - Instruction::NearJumpForward { use_carry, offset } => { - self.near_jump(use_carry, true, u8::from(offset)) - } - Instruction::FarJump { reg, use_carry } => self.far_jump(use_carry, reg), - Instruction::CmpEqual => self.cmp_equal(), - Instruction::CmpGreater => self.cmp_gt(), - Instruction::CmpLess => self.cmp_le(), - Instruction::Not => self.not(), - Instruction::BitShift { - right, - reg, - mode, - len, - } => self.bit_shift(right, reg, mode, len), - Instruction::Inc { reg } => self.inc(reg), - Instruction::Dec { reg } => self.dec(reg), - Instruction::Add => self.add(), - Instruction::Sub => self.sub(), - Instruction::And => self.and(), - Instruction::Or => self.or(), - Instruction::Xor => self.xor(), - Instruction::Xnor => self.xnor(), - Instruction::Complement { reg } => self.complement(reg), - Instruction::BitShiftVar { right, mode } => self.bit_shift_var(right, mode), - Instruction::Copy { reg_from } => self.copy(reg_from), - Instruction::Setc { carry } => self.setc(carry), - Instruction::Nop => self.nop(), - Instruction::Halt => return true, - Instruction::Cload { reg } => self.cload(reg), - Instruction::Zero { reg } => self.zero(reg), - Instruction::Div => self.div(), - Instruction::Mul => self.mul(), - Instruction::Swap => self.swap(), - Instruction::PortReady => self.port_ready(), - Instruction::ReadPort => self.read_port(), - Instruction::WritePort => self.write_port(), - } - false - } - - pub fn visualize(&self) { - println!("+--+----+----------+"); - println!("|IP|{:#04X}|{:#010b}|", self.IP, self.IP); - println!("|R0|{:#04X}|{:#010b}|", self.R0, self.R0); - println!("|R1|{:#04X}|{:#010b}|", self.R1, self.R1); - println!("+--+----+----------+"); - println!("|FLAGS:{} |", if self.C { "C" } else { " " }); - println!("+------------------+"); - println!("|CYCLES:{:11}|", self.num_cycles); - println!("|PORTIN:{:11}|", self.port_in.len()); - println!("|PORTOUT:{:10}|", self.port_out.len()); - println!("+------------------+"); - println!("| MEM +0|+1|+2|+3 |"); - for i in 0..4 { - println!( - "| 0x{:X} {:02X}|{:02X}|{:02X}|{:02X} |", - i * 4, - self.data[i * 4], - self.data[i * 4 + 1], - self.data[i * 4 + 2], - self.data[i * 4 + 3] - ); - } - println!("+------------------+"); - } -} - -#[cfg(test)] -mod tests { - use super::Cpu; - use std::num::Wrapping; - - #[test] - fn insn_load_r0() { - let mut cpu = Cpu::new(&[0b00000001]); - cpu.data[0b1] = 0x45; - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.R0.0, 0x45); // data is loaded - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_load_r1() { - let mut cpu = Cpu::new(&[0b00010101]); - cpu.data[0b101] = 0x54; - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.R1.0, 0x54); // data is loaded - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_load_r0_r1() { - let mut cpu = Cpu::new(&[0b00001011, 0b00011100]); - cpu.data[0b1011] = 0x12; - cpu.data[0b1100] = 0x78; - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.R0.0, 0x12); // data is loaded - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert!(!cpu.C); // carry is not affected - } - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.R1.0, 0x78); // data is loaded - assert_eq!(cpu.IP.0, 0x02); // instruction pointer has advanced - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_store_r0() { - let mut cpu = Cpu::new(&[0b00100100]); - cpu.R0 = Wrapping(0x42); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.data[0b0100], 0x42); // data is stored - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_store_r1() { - let mut cpu = Cpu::new(&[0b00110010]); - cpu.R1 = Wrapping(0x29); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.data[0b0010], 0x29); // data is stored - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_store_r0_r1() { - let mut cpu = Cpu::new(&[0b00101111, 0b00110010]); - cpu.R0 = Wrapping(0xA4); - cpu.R1 = Wrapping(0xB3); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.data[0b1111], 0xA4); // data is stored - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert!(!cpu.C); // carry is not affected - } - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.data[0b0010], 0xB3); // data is stored - assert_eq!(cpu.IP.0, 0x02); // instruction pointer has advanced - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_store_and_load() { - let mut cpu = Cpu::new(&[0b00100101, 0b00010101]); - cpu.R0 = Wrapping(0xF3); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.data[0b0101], 0xF3); // data is stored - } - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.R1.0, 0xF3); // same data is loaded - } - } - - #[test] - fn insn_load_imm_r0() { - let mut cpu = Cpu::new(&[0b01000101, 0b01100101]); - { - assert!(!cpu.step()); // CPU has not halted - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.R0.0, 0b01010101); // data is loaded - assert_eq!(cpu.IP.0, 0x02); // instruction pointer has advanced - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_load_imm_r1() { - let mut cpu = Cpu::new(&[0b01010110, 0b01111001]); - { - assert!(!cpu.step()); // CPU has not halted - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.R1.0, 0b10010110); // data is loaded - assert_eq!(cpu.IP.0, 0x02); // instruction pointer has advanced - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_load_imm_low_only() { - let mut cpu = Cpu::new(&[0b01001111]); - cpu.R0 = Wrapping(0b10101010); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.R0.0, 0b10101111); // data is loaded correctly - } - } - - #[test] - fn insn_near_jump_backward_unconditional() { - let mut cpu = Cpu::new(&[]); - cpu.code[20] = 0b10001010; - cpu.IP = Wrapping(20); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 18); // instruction pointer had advanced - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_near_jump_backward_conditional_must() { - let mut cpu = Cpu::new(&[]); - cpu.code[40] = 0b10001110; - cpu.IP = Wrapping(40); - cpu.C = true; - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 38); // instruction pointer had advanced - assert!(cpu.C); // carry is not affected - } - } - - #[test] - fn insn_near_jump_backward_conditional_must_not() { - let mut cpu = Cpu::new(&[]); - cpu.code[40] = 0b10001110; - cpu.IP = Wrapping(40); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 41); // instruction pointer had advanced - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_near_jump_forward_unconditional() { - let mut cpu = Cpu::new(&[0b10010101]); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0b101); // instruction pointer had advanced - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_near_jump_forward_conditional_must() { - let mut cpu = Cpu::new(&[0b10011011]); - cpu.C = true; - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0b011); // instruction pointer had advanced - assert!(cpu.C); // carry is not affected - } - } - - #[test] - fn insn_near_jump_forward_conditional_must_not() { - let mut cpu = Cpu::new(&[0b10011011]); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 1); // instruction pointer had advanced - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_far_jump_unconditional_r0() { - let mut cpu = Cpu::new(&[0b10000000]); - cpu.R0 = Wrapping(0xA0); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0xA0); // instruction pointer has advanced - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_far_jump_unconditional_r1() { - let mut cpu = Cpu::new(&[0b10000010]); - cpu.R1 = Wrapping(0xC7); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0xC7); // instruction pointer has advanced - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_far_jump_conditional_must_r0() { - let mut cpu = Cpu::new(&[0b10000001]); - cpu.R0 = Wrapping(0x2B); - cpu.C = true; - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x2B); // instruction pointer has advanced - assert!(cpu.C); // carry is not affected - } - } - - #[test] - fn insn_far_jump_conditional_must_r1() { - let mut cpu = Cpu::new(&[0b10000011]); - cpu.R1 = Wrapping(0x0A); - cpu.C = true; - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x0A); // instruction pointer has advanced - assert!(cpu.C); // carry is not affected - } - } - - #[test] - fn insn_far_jump_conditional_must_not_r0() { - let mut cpu = Cpu::new(&[0b10000001]); - cpu.R0 = Wrapping(0x44); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_far_jump_conditional_must_not_r1() { - let mut cpu = Cpu::new(&[0b10000011]); - cpu.R1 = Wrapping(0x88); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_equal_true() { - let mut cpu = Cpu::new(&[0b10000100]); - cpu.R0 = Wrapping(0xC2); - cpu.R1 = Wrapping(0xC2); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert!(cpu.C); // carry is set - } - } - - #[test] - fn insn_equal_false() { - let mut cpu = Cpu::new(&[0b10000100]); - cpu.R0 = Wrapping(0xC2); - cpu.R1 = Wrapping(0xC1); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert!(!cpu.C); // carry is unset - } - } - - #[test] - fn insn_greater_true() { - let mut cpu = Cpu::new(&[0b10000101]); - cpu.R0 = Wrapping(0xF4); - cpu.R1 = Wrapping(0xD3); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert!(cpu.C); // carry is set - } - } - - #[test] - fn insn_greater_false_eq() { - let mut cpu = Cpu::new(&[0b10000101]); - cpu.R0 = Wrapping(0xD3); - cpu.R1 = Wrapping(0xD3); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert!(!cpu.C); // carry is unset - } - } - - #[test] - fn insn_greater_false() { - let mut cpu = Cpu::new(&[0b10000101]); - cpu.R0 = Wrapping(0xD3); - cpu.R1 = Wrapping(0xF4); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert!(!cpu.C); // carry is unset - } - } - - #[test] - fn insn_less_true() { - let mut cpu = Cpu::new(&[0b10000110]); - cpu.R0 = Wrapping(0xD3); - cpu.R1 = Wrapping(0xF4); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert!(cpu.C); // carry is set - } - } - - #[test] - fn insn_less_false_eq() { - let mut cpu = Cpu::new(&[0b10000110]); - cpu.R0 = Wrapping(0xD3); - cpu.R1 = Wrapping(0xD3); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert!(!cpu.C); // carry is unset - } - } - - #[test] - fn insn_less_false() { - let mut cpu = Cpu::new(&[0b10000110]); - cpu.R0 = Wrapping(0xF4); - cpu.R1 = Wrapping(0xD3); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert!(!cpu.C); // carry is unset - } - } - - #[test] - fn insn_not_from_true() { - let mut cpu = Cpu::new(&[0b10000111]); - cpu.C = true; - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert!(!cpu.C); // carry is unset - } - } - - #[test] - fn insn_not_from_false() { - let mut cpu = Cpu::new(&[0b10000111]); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert!(cpu.C); // carry is set - } - } - - #[test] - fn insn_right_shift_zero_r0() { - let mut cpu = Cpu::new(&[0b10100000]); - cpu.R0 = Wrapping(0b10011101); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0b10011101); // data is not shifted - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_right_shift_r0() { - let mut cpu = Cpu::new(&[0b10100010]); - cpu.R0 = Wrapping(0b10011101); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0b00100111); // data is shifted - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_right_shift_zero_r1() { - let mut cpu = Cpu::new(&[0b10110000]); - cpu.R1 = Wrapping(0b10011101); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R1.0, 0b10011101); // data is not shifted - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_right_shift_r1() { - let mut cpu = Cpu::new(&[0b10110010]); - cpu.R1 = Wrapping(0b10011101); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R1.0, 0b00100111); // data is shifted - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_right_rotate_zero_r0() { - let mut cpu = Cpu::new(&[0b10101000]); - cpu.R0 = Wrapping(0b10011101); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0b10011101); // data is not rotated - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_right_rotate_r0() { - let mut cpu = Cpu::new(&[0b10101010]); - cpu.R0 = Wrapping(0b10011101); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0b01100111); // data is rotated - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_right_rotate_zero_r1() { - let mut cpu = Cpu::new(&[0b10111000]); - cpu.R1 = Wrapping(0b10011101); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R1.0, 0b10011101); // data is not rotated - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_right_rotate_r1() { - let mut cpu = Cpu::new(&[0b10111010]); - cpu.R1 = Wrapping(0b10011101); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R1.0, 0b01100111); // data is rotated - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_left_shift_zero_r0() { - let mut cpu = Cpu::new(&[0b11000000]); - cpu.R0 = Wrapping(0b10011101); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0b10011101); // data is not shifted - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_left_shift_r0() { - let mut cpu = Cpu::new(&[0b11000010]); - cpu.R0 = Wrapping(0b10011101); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0b01110100); // data is shifted - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_left_shift_zero_r1() { - let mut cpu = Cpu::new(&[0b11010000]); - cpu.R1 = Wrapping(0b10011101); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R1.0, 0b10011101); // data is not shifted - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_left_shift_r1() { - let mut cpu = Cpu::new(&[0b11010010]); - cpu.R1 = Wrapping(0b10011101); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R1.0, 0b01110100); // data is shifted - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_left_rotate_zero_r0() { - let mut cpu = Cpu::new(&[0b11001000]); - cpu.R0 = Wrapping(0b10011101); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0b10011101); // data is not rotated - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_left_rotate_r0() { - let mut cpu = Cpu::new(&[0b11001010]); - cpu.R0 = Wrapping(0b10011101); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0b01110110); // data is rotated - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_left_rotate_zero_r1() { - let mut cpu = Cpu::new(&[0b11011000]); - cpu.R1 = Wrapping(0b10011101); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R1.0, 0b10011101); // data is not rotated - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_left_rotate_r1() { - let mut cpu = Cpu::new(&[0b11011010]); - cpu.R1 = Wrapping(0b10011101); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R1.0, 0b01110110); // data is rotated - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_inc_carry_false_r0() { - let mut cpu = Cpu::new(&[0b11100000]); - cpu.R0 = Wrapping(68); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 69); // data is incremented - assert!(!cpu.C); // carry is unset - } - } - - #[test] - fn insn_inc_carry_true_r0() { - let mut cpu = Cpu::new(&[0b11100000]); - cpu.R0 = Wrapping(255); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0); // data is incremented - assert!(cpu.C); // carry is unset - } - } - - #[test] - fn insn_inc_carry_false_r1() { - let mut cpu = Cpu::new(&[0b11100001]); - cpu.R1 = Wrapping(68); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R1.0, 69); // data is incremented - assert!(!cpu.C); // carry is unset - } - } - - #[test] - fn insn_inc_carry_true_r1() { - let mut cpu = Cpu::new(&[0b11100001]); - cpu.R1 = Wrapping(255); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R1.0, 0); // data is incremented - assert!(cpu.C); // carry is unset - } - } - - #[test] - fn insn_dec_carry_false_r0() { - let mut cpu = Cpu::new(&[0b11100010]); - cpu.R0 = Wrapping(68); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 67); // data is incremented - assert!(!cpu.C); // carry is unset - } - } - - #[test] - fn insn_dec_carry_true_r0() { - let mut cpu = Cpu::new(&[0b11100010]); - cpu.R0 = Wrapping(1); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0); // data is incremented - assert!(cpu.C); // carry is unset - } - } - - #[test] - fn insn_dec_carry_false_r1() { - let mut cpu = Cpu::new(&[0b11100011]); - cpu.R1 = Wrapping(68); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R1.0, 67); // data is incremented - assert!(!cpu.C); // carry is unset - } - } - - #[test] - fn insn_dec_carry_true_r1() { - let mut cpu = Cpu::new(&[0b11100011]); - cpu.R1 = Wrapping(1); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R1.0, 0); // data is incremented - assert!(cpu.C); // carry is unset - } - } - - #[test] - fn insn_add() { - let mut cpu = Cpu::new(&[0b11100100]); - cpu.R0 = Wrapping(0x12); - cpu.R1 = Wrapping(0x34); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0x46); // destination data is correct - assert_eq!(cpu.R1.0, 0x34); // R1 is not modified - assert!(!cpu.C); // carry is unset - } - } - - #[test] - fn insn_add_overflow() { - let mut cpu = Cpu::new(&[0b11100100]); - cpu.R0 = Wrapping(0xA3); - cpu.R1 = Wrapping(0xCB); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0x6E); // destination data is correct - assert_eq!(cpu.R1.0, 0xCB); // R1 is not modified - assert!(cpu.C); // carry is set - } - } - - #[test] - fn insn_sub() { - let mut cpu = Cpu::new(&[0b11100101]); - cpu.R0 = Wrapping(0x50); - cpu.R1 = Wrapping(0x10); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0x40); // destination data is correct - assert_eq!(cpu.R1.0, 0x10); // R1 is not modified - assert!(!cpu.C); // carry is unset - } - } - - #[test] - fn insn_sub_underflow() { - let mut cpu = Cpu::new(&[0b11100101]); - cpu.R0 = Wrapping(0x50); - cpu.R1 = Wrapping(0x90); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0xC0); // destination data is correct - assert_eq!(cpu.R1.0, 0x90); // R1 is not modified - assert!(cpu.C); // carry is set - } - } - - #[test] - fn insn_and() { - let mut cpu = Cpu::new(&[0b11100110]); - cpu.R0 = Wrapping(0b10110111); - cpu.R1 = Wrapping(0b10001100); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0b10000100); // destination data is correct - assert_eq!(cpu.R1.0, 0b10001100); // R1 is not modified - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_or() { - let mut cpu = Cpu::new(&[0b11100111]); - cpu.R0 = Wrapping(0b10110111); - cpu.R1 = Wrapping(0b10001100); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0b10111111); // destination data is correct - assert_eq!(cpu.R1.0, 0b10001100); // R1 is not modified - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_xor() { - let mut cpu = Cpu::new(&[0b11101000]); - cpu.R0 = Wrapping(0b10110111); - cpu.R1 = Wrapping(0b10001100); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0b00111011); // destination data is correct - assert_eq!(cpu.R1.0, 0b10001100); // R1 is not modified - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_xnor() { - let mut cpu = Cpu::new(&[0b11101001]); - cpu.R0 = Wrapping(0b10110111); - cpu.R1 = Wrapping(0b10001100); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0b11000100); // destination data is correct - assert_eq!(cpu.R1.0, 0b10001100); // R1 is not modified - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_complement_r0() { - let mut cpu = Cpu::new(&[0b11101010]); - cpu.R0 = Wrapping(0b10101010); - cpu.R1 = Wrapping(0x55); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0b01010101); // destination data is correct - assert_eq!(cpu.R1.0, 0x55); // R1 is not modified - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_complement_r1() { - let mut cpu = Cpu::new(&[0b11101011]); - cpu.R0 = Wrapping(0x55); - cpu.R1 = Wrapping(0b10101010); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0x55); // R0 is not modified - assert_eq!(cpu.R1.0, 0b01010101); // destination data is correct - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_bit_shift_right_var_zero() { - let mut cpu = Cpu::new(&[0b11101100]); - cpu.R0 = Wrapping(0b01110010); - cpu.R1 = Wrapping(0); - cpu.C = true; - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0b01110010); // value is not changed - assert_eq!(cpu.R1.0, 0); // R1 is not modified - assert!(!cpu.C); // carry is unset - } - } - - #[test] - fn insn_bit_shift_right_var() { - let mut cpu = Cpu::new(&[0b11101100]); - cpu.R0 = Wrapping(0b01110010); - cpu.R1 = Wrapping(3); - cpu.C = true; - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0b00001110); // value is shifted correctly - assert_eq!(cpu.R1.0, 3); // R1 is not modified - assert!(!cpu.C); // carry is unset - } - } - - #[test] - fn insn_bit_shift_right_var_overflow() { - let mut cpu = Cpu::new(&[0b11101100]); - cpu.R0 = Wrapping(0b01110010); - cpu.R1 = Wrapping(20); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0); // value is zeroed out - assert_eq!(cpu.R1.0, 20); // R1 is not modified - assert!(cpu.C); // carry is set - } - } - - #[test] - fn insn_bit_shift_left_var_zero() { - let mut cpu = Cpu::new(&[0b11101110]); - cpu.R0 = Wrapping(0b01110010); - cpu.R1 = Wrapping(0); - cpu.C = true; - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0b01110010); // value is not changed - assert_eq!(cpu.R1.0, 0); // R1 is not modified - assert!(!cpu.C); // carry is unset - } - } - - #[test] - fn insn_bit_shift_left_var() { - let mut cpu = Cpu::new(&[0b11101110]); - cpu.R0 = Wrapping(0b01110010); - cpu.R1 = Wrapping(3); - cpu.C = true; - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0b10010000); // value is shifted correctly - assert_eq!(cpu.R1.0, 3); // R1 is not modified - assert!(!cpu.C); // carry is unset - } - } - - #[test] - fn insn_bit_shift_left_var_overflow() { - let mut cpu = Cpu::new(&[0b11101110]); - cpu.R0 = Wrapping(0b01110010); - cpu.R1 = Wrapping(20); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0); // value is zeroed out - assert_eq!(cpu.R1.0, 20); // R1 is not modified - assert!(cpu.C); // carry is set - } - } - - #[test] - fn insn_bit_rotate_right_var_zero() { - let mut cpu = Cpu::new(&[0b11101101]); - cpu.R0 = Wrapping(0b01110010); - cpu.R1 = Wrapping(0); - cpu.C = true; - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0b01110010); // value is not changed - assert_eq!(cpu.R1.0, 0); // R1 is not modified - assert!(!cpu.C); // carry is unset - } - } - - #[test] - fn insn_bit_rotate_right_var() { - let mut cpu = Cpu::new(&[0b11101101]); - cpu.R0 = Wrapping(0b01110010); - cpu.R1 = Wrapping(3); - cpu.C = true; - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0b01001110); // value is rotated correctly - assert_eq!(cpu.R1.0, 3); // R1 is not modified - assert!(!cpu.C); // carry is unset - } - } - - #[test] - fn insn_bit_rotate_right_var_overflow() { - let mut cpu = Cpu::new(&[0b11101101]); - cpu.R0 = Wrapping(0b01110010); - cpu.R1 = Wrapping(20); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0); // value is zeroed out - assert_eq!(cpu.R1.0, 20); // R1 is not modified - assert!(cpu.C); // carry is set - } - } - - #[test] - fn insn_bit_rotate_left_var_zero() { - let mut cpu = Cpu::new(&[0b11101111]); - cpu.R0 = Wrapping(0b01110010); - cpu.R1 = Wrapping(0); - cpu.C = true; - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0b01110010); // value is not changed - assert_eq!(cpu.R1.0, 0); // R1 is not modified - assert!(!cpu.C); // carry is unset - } - } - - #[test] - fn insn_bit_rotate_left_var() { - let mut cpu = Cpu::new(&[0b11101111]); - cpu.R0 = Wrapping(0b01110010); - cpu.R1 = Wrapping(3); - cpu.C = true; - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0b10010011); // value is rotated correctly - assert_eq!(cpu.R1.0, 3); // R1 is not modified - assert!(!cpu.C); // carry is unset - } - } - - #[test] - fn insn_bit_rotate_left_var_overflow() { - let mut cpu = Cpu::new(&[0b11101111]); - cpu.R0 = Wrapping(0b01110010); - cpu.R1 = Wrapping(20); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0); // value is zeroed out - assert_eq!(cpu.R1.0, 20); // R1 is not modified - assert!(cpu.C); // carry is set - } - } - - #[test] - fn insn_copy_from_r0() { - let mut cpu = Cpu::new(&[0b11110000]); - cpu.R0 = Wrapping(34); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 34); // R0 is not changed - assert_eq!(cpu.R1.0, 34); // destination value is correct - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_copy_from_r1() { - let mut cpu = Cpu::new(&[0b11110001]); - cpu.R1 = Wrapping(34); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 34); // destination value is correct - assert_eq!(cpu.R1.0, 34); // R1 is not changed - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_setc_true() { - let mut cpu = Cpu::new(&[0b11110011]); - cpu.C = false; - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert!(cpu.C); // carry is set - } - } - - #[test] - fn insn_setc_false() { - let mut cpu = Cpu::new(&[0b11110010]); - cpu.C = true; - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert!(!cpu.C); // carry is unset - } - } - - #[test] - fn insn_nop() { - let mut cpu = Cpu::new(&[0b11110100]); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - } - } - - #[test] - fn insn_halt() { - let mut cpu = Cpu::new(&[0b11110101]); - { - assert!(cpu.step()); // CPU has halted - } - } - - #[test] - fn insn_cload_r0() { - let mut cpu = Cpu::new(&[0b11110110]); - cpu.code[26] = 23; - cpu.R0 = Wrapping(26); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 23); // value is loaded - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_cload_r1() { - let mut cpu = Cpu::new(&[0b11110111]); - cpu.code[26] = 23; - cpu.R0 = Wrapping(26); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R1.0, 23); // value is loaded - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_zero_r0() { - let mut cpu = Cpu::new(&[0b11111000]); - cpu.R0 = Wrapping(1); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0); // R0 is zeroed out - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_zero_r1() { - let mut cpu = Cpu::new(&[0b11111001]); - cpu.R1 = Wrapping(1); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R1.0, 0); // R1 is zeroed out - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_div() { - let mut cpu = Cpu::new(&[0b11111010]); - cpu.R0 = Wrapping(100); - cpu.R1 = Wrapping(23); - cpu.C = true; - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 4); // quotient is valid - assert_eq!(cpu.R1.0, 8); // remainder is valid - assert!(!cpu.C); // carry is unset - } - } - - #[test] - fn insn_div_zero() { - let mut cpu = Cpu::new(&[0b11111010]); - cpu.R0 = Wrapping(100); - cpu.R1 = Wrapping(0); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0xFF); // quotient is valid - assert_eq!(cpu.R1.0, 0); // remainder is valid - assert!(cpu.C); // carry is set - } - } - - #[test] - fn insn_mul() { - let mut cpu = Cpu::new(&[0b11111011]); - cpu.R0 = Wrapping(0xC2); - cpu.R1 = Wrapping(0xA7); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0x7E); // R0 value is valid - assert_eq!(cpu.R1.0, 0x8E); // R1 value is valid - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_swap() { - let mut cpu = Cpu::new(&[0b11111100]); - cpu.R0 = Wrapping(7); - cpu.R1 = Wrapping(42); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 42); // R0 value is valid - assert_eq!(cpu.R1.0, 7); // R1 value is valid - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_port_ready_false() { - let mut cpu = Cpu::new(&[0b11111101]); - cpu.C = true; - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert!(!cpu.C); // carry is unset - } - } - - #[test] - fn insn_port_ready_true() { - let mut cpu = Cpu::new(&[0b11111101]); - cpu.port_in.push_back(0x23); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert!(cpu.C); // carry is set - } - } - - #[test] - fn insn_read_port_empty() { - let mut cpu = Cpu::new(&[0b11111110]); - cpu.R0 = Wrapping(1); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0); // R0 is zeroed out - assert!(!cpu.C); // carry is not affected - } - } - - #[test] - fn insn_read_port() { - let mut cpu = Cpu::new(&[0b11111110]); - cpu.port_in.push_back(0x23); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 0x23); // value is correct - assert!(!cpu.C); // carry is not affected - assert_eq!(cpu.port_in.len(), 0); // port is empty - } - } - - #[test] - fn insn_write_port() { - let mut cpu = Cpu::new(&[0b11111111]); - cpu.R0 = Wrapping(33); - { - assert!(!cpu.step()); // CPU has not halted - assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced - assert_eq!(cpu.R0.0, 33); // R0 is not affected - assert!(!cpu.C); // carry is not affected - assert_eq!(cpu.port_out.back(), Some(&33)); // data is in port - } - } -} diff --git a/src/instruction.rs b/src/instruction.rs deleted file mode 100644 index 139dc25..0000000 --- a/src/instruction.rs +++ /dev/null @@ -1,354 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright Murad Karammaev, Nikita Kuzmin - -use ux::{u2, u3, u4}; - -#[derive(Debug, PartialEq, Clone)] -pub enum Register { - R0, - R1, -} - -#[derive(Debug, PartialEq, Clone)] -pub enum ShiftMode { - Logical, - Circular, -} - -#[derive(Debug, PartialEq)] -pub enum Instruction { - Load { - reg: Register, - addr: u4, - }, - Store { - reg: Register, - addr: u4, - }, - LoadImmediate { - low: bool, - reg: Register, - val: u4, - }, - NearJumpBackward { - use_carry: bool, - offset: u2, - }, - NearJumpForward { - use_carry: bool, - offset: u3, - }, - FarJump { - reg: Register, - use_carry: bool, - }, - CmpEqual, - CmpGreater, - CmpLess, - Not, - BitShift { - right: bool, - reg: Register, - mode: ShiftMode, - len: u3, - }, - Inc { - reg: Register, - }, - Dec { - reg: Register, - }, - Add, - Sub, - And, - Or, - Xor, - Xnor, - Complement { - reg: Register, - }, - BitShiftVar { - right: bool, - mode: ShiftMode, - }, - Copy { - reg_from: Register, - }, - Setc { - carry: bool, - }, - Nop, - Halt, - Cload { - reg: Register, - }, - Zero { - reg: Register, - }, - Div, - Mul, - Swap, - PortReady, - ReadPort, - WritePort, -} - -pub fn decode(insn: u8) -> Instruction { - match insn { - 0b00000000..=0b00111111 => { - let reg = if insn & 0b00010000 == 0 { - Register::R0 - } else { - Register::R1 - }; - let addr = u4::new(insn & 0b00001111); - match insn & 0b00100000 { - 0 => Instruction::Load { reg, addr }, - _ => Instruction::Store { reg, addr }, - } - } - 0b01000000..=0b01111111 => Instruction::LoadImmediate { - low: insn & 0b00100000 == 0, - reg: if insn & 0b00010000 == 0 { - Register::R0 - } else { - Register::R1 - }, - val: u4::new(insn & 0b00001111), - }, - 0b10001000..=0b10001111 => Instruction::NearJumpBackward { - use_carry: insn & 0b00000100 != 0, - offset: u2::new(insn & 0b00000011), - }, - 0b10010000..=0b10011111 => Instruction::NearJumpForward { - use_carry: insn & 0b00001000 != 0, - offset: u3::new(insn & 0b00000111), - }, - 0b10000000..=0b10000011 => Instruction::FarJump { - reg: if insn & 0b00000010 == 0 { - Register::R0 - } else { - Register::R1 - }, - use_carry: insn & 0b00000001 != 0, - }, - 0b10000100 => Instruction::CmpEqual, - 0b10000101 => Instruction::CmpGreater, - 0b10000110 => Instruction::CmpLess, - 0b10000111 => Instruction::Not, - 0b10100000..=0b10111111 => Instruction::BitShift { - right: true, - reg: if insn & 0b00010000 == 0 { - Register::R0 - } else { - Register::R1 - }, - mode: if insn & 0b00001000 == 0 { - ShiftMode::Logical - } else { - ShiftMode::Circular - }, - len: u3::new(insn & 0b00000111), - }, - 0b11000000..=0b11011111 => Instruction::BitShift { - right: false, - reg: if insn & 0b00010000 == 0 { - Register::R0 - } else { - Register::R1 - }, - mode: if insn & 0b00001000 == 0 { - ShiftMode::Logical - } else { - ShiftMode::Circular - }, - len: u3::new(insn & 0b00000111), - }, - 0b11100000..=0b11100001 => Instruction::Inc { - reg: if insn & 0b00000001 == 0 { - Register::R0 - } else { - Register::R1 - }, - }, - 0b11100010..=0b11100011 => Instruction::Dec { - reg: if insn & 0b00000001 == 0 { - Register::R0 - } else { - Register::R1 - }, - }, - 0b11100100 => Instruction::Add, - 0b11100101 => Instruction::Sub, - 0b11100110 => Instruction::And, - 0b11100111 => Instruction::Or, - 0b11101000 => Instruction::Xor, - 0b11101001 => Instruction::Xnor, - 0b11101010..=0b11101011 => Instruction::Complement { - reg: if insn & 0b00000001 == 0 { - Register::R0 - } else { - Register::R1 - }, - }, - 0b11101100..=0b11101111 => Instruction::BitShiftVar { - right: insn & 0b00000010 == 0, - mode: if insn & 0b00000001 == 0 { - ShiftMode::Logical - } else { - ShiftMode::Circular - }, - }, - 0b11110000..=0b11110001 => Instruction::Copy { - reg_from: if insn & 0b00000001 == 0 { - Register::R0 - } else { - Register::R1 - }, - }, - 0b11110010..=0b11110011 => Instruction::Setc { - carry: insn & 0b00000001 != 0, - }, - 0b11110100 => Instruction::Nop, - 0b11110101 => Instruction::Halt, - 0b11110110..=0b11110111 => Instruction::Cload { - reg: if insn & 0b00000001 == 0 { - Register::R0 - } else { - Register::R1 - }, - }, - 0b11111000..=0b11111001 => Instruction::Zero { - reg: if insn & 0b00000001 == 0 { - Register::R0 - } else { - Register::R1 - }, - }, - 0b11111010 => Instruction::Div, - 0b11111011 => Instruction::Mul, - 0b11111100 => Instruction::Swap, - 0b11111101 => Instruction::PortReady, - 0b11111110 => Instruction::ReadPort, - 0b11111111 => Instruction::WritePort, - } -} - -pub fn encode(insn: Instruction) -> u8 { - match insn { - Instruction::Load { reg, addr } => match reg { - Register::R0 => u8::from(addr), - Register::R1 => u8::from(addr) | 0b00010000, - }, - Instruction::Store { reg, addr } => match reg { - Register::R0 => 0b00100000 | u8::from(addr), - Register::R1 => 0b00110000 | u8::from(addr), - }, - Instruction::LoadImmediate { low, reg, val } => { - let mut ret = 0b01000000u8; - ret |= match low { - true => 0, - false => 0b00100000, - }; - ret |= match reg { - Register::R0 => 0, - Register::R1 => 0b00010000, - }; - ret | u8::from(val) - } - Instruction::NearJumpBackward { use_carry, offset } => match use_carry { - false => 0b10001000 | u8::from(offset), - true => 0b10001100 | u8::from(offset), - }, - Instruction::NearJumpForward { use_carry, offset } => match use_carry { - false => 0b10010000 | u8::from(offset), - true => 0b10011000 | u8::from(offset), - }, - Instruction::FarJump { reg, use_carry } => match reg { - Register::R0 => 0b10000000 | if use_carry { 1 } else { 0 }, - Register::R1 => 0b10000010 | if use_carry { 1 } else { 0 }, - }, - Instruction::CmpEqual => 0b10000100, - Instruction::CmpGreater => 0b10000101, - Instruction::CmpLess => 0b10000110, - Instruction::Not => 0b10000111, - Instruction::BitShift { - right, - reg, - mode, - len, - } => { - let mut ret = if right { 0b10100000 } else { 0b11000000 }; - ret |= match reg { - Register::R0 => 0, - Register::R1 => 0b00010000, - }; - ret |= match mode { - ShiftMode::Logical => 0, - ShiftMode::Circular => 0b00001000, - }; - ret | u8::from(len) - } - Instruction::Inc { reg } => match reg { - Register::R0 => 0b11100000, - Register::R1 => 0b11100001, - }, - Instruction::Dec { reg } => match reg { - Register::R0 => 0b11100010, - Register::R1 => 0b11100011, - }, - Instruction::Add => 0b11100100, - Instruction::Sub => 0b11100101, - Instruction::And => 0b11100110, - Instruction::Or => 0b11100111, - Instruction::Xor => 0b11101000, - Instruction::Xnor => 0b11101001, - Instruction::Complement { reg } => match reg { - Register::R0 => 0b11101010, - Register::R1 => 0b11101011, - }, - Instruction::BitShiftVar { right, mode } => { - let mut ret = 0b11101100; - ret |= if right { 0 } else { 0b00000010 }; - ret |= match mode { - ShiftMode::Logical => 0, - ShiftMode::Circular => 1, - }; - ret - } - Instruction::Copy { reg_from } => match reg_from { - Register::R0 => 0b11110000, - Register::R1 => 0b11110001, - }, - Instruction::Setc { carry } => 0b11110010 | u8::from(carry), - Instruction::Nop => 0b11110100, - Instruction::Halt => 0b11110101, - Instruction::Cload { reg } => match reg { - Register::R0 => 0b11110110, - Register::R1 => 0b11110111, - }, - Instruction::Zero { reg } => match reg { - Register::R0 => 0b11111000, - Register::R1 => 0b11111001, - }, - Instruction::Div => 0b11111010, - Instruction::Mul => 0b11111011, - Instruction::Swap => 0b11111100, - Instruction::PortReady => 0b11111101, - Instruction::ReadPort => 0b11111110, - Instruction::WritePort => 0b11111111, - } -} - -// This crate really needs more tests, but I can't be bothered -#[cfg(test)] -mod tests { - use super::{decode, encode}; - - #[test] - fn deterministic_instruction_encoding() { - for i in 0..=255u8 { - assert_eq!(i, encode(decode(i))); - } - } -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 520cd80..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright Murad Karammaev, Nikita Kuzmin - -pub mod assembler; -pub mod cpu; -pub mod instruction; - -#[cfg(test)] -mod tests { - use crate::{assembler::Assembler, cpu::Cpu}; - use std::num::Wrapping; - - #[test] - fn integration_assemble_execute() { - let code = r#" - MOV R0.l, 0xF # R0 = 15 - MOV R1.l, 0x3 # R1 = 3 - ADD # R0 += R1 (18) - MOV [0], R0 # MEM[0] = R0 (18) - ZERO R0 # R0 = 0 - HALT # EXIT - "#; - let code = Assembler::new().assemble(code).unwrap(); - let mut cpu = Cpu::new(&code); - for _ in 0..5 { - assert!(!cpu.step()); // CPU does not halt… - } - assert!(cpu.step()); // …until it reaches halt instruction - assert_eq!(cpu.IP, Wrapping(5)); // IP points at halt instruction - assert_eq!(cpu.R0, Wrapping(0)); - assert_eq!(cpu.R1, Wrapping(3)); - assert!(!cpu.C); - assert_eq!(cpu.data[0], 18); - for i in 1..=15 { - assert_eq!(cpu.data[i], 0); - } - } -} diff --git a/toy_cpu_4bit/Cargo.toml b/toy_cpu_4bit/Cargo.toml new file mode 100644 index 0000000..902c4cd --- /dev/null +++ b/toy_cpu_4bit/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "toy_cpu_4bit" +version = "0.1.0" +edition = "2021" + +[dependencies] +ux = "0.1.3" +regex = "1.5.4" +lazy_static = "1.4.0" diff --git a/toy_cpu_4bit/src/assembler.rs b/toy_cpu_4bit/src/assembler.rs new file mode 100644 index 0000000..60e8400 --- /dev/null +++ b/toy_cpu_4bit/src/assembler.rs @@ -0,0 +1,966 @@ +// SPDX-License-Identifier: MIT +// Copyright Murad Karammaev, Nikita Kuzmin + +use crate::instruction::{decode, encode, Instruction, Register, ShiftMode}; +use regex::Regex; +use std::{ + error::Error, + fmt::{Display, Formatter}, + num, + ops::RangeInclusive, +}; +use ux::{u2, u3, u4}; + +type InstructionDecoder = fn(&str, &Regex) -> Result; + +const REGEX_REG: &str = r"(R[01])"; +const REGEX_REG_HALF: &str = r"(R[01]\.[lh])"; +const REGEX_NUM: &str = r"(\d{1,3}|0b[01]{1,8}|0o[0-7]{1,3}|0x[\da-fA-F]{1,2})"; +const REGEX_INUM: &str = r"(-?\d{1,3}|-?0b[01]{1,8}|-?0o[0-7]{1,3}|-?0x[\da-fA-F]{1,2})"; +const REGEX_ADDR: &str = r"\[(\d{1,3}|0b[01]{1,8}|0o[0-7]{1,3}|0x[\da-fA-F]{1,2})\]"; +const REGEX_COMMA: &str = r"\s*,\s*"; +const REGEX_CARRY: &str = r"([01])"; + +macro_rules! asm_entry { + ($regex:expr, $handler:expr) => { + (Regex::new(&$regex).unwrap(), $handler) + }; +} + +#[derive(Debug, Clone)] +enum AssemblerErrorKind { + TooMuchInstructions, + UnknownInstruction { + instruction: String, + }, + InvalidRegister { + register: String, + }, + InvalidRegisterHalf { + register_half: String, + }, + InvalidCarry { + carry: String, + }, + NumberOutOfRange { + number: i128, + range: RangeInclusive, + }, + InvalidInteger { + parse_int_error: num::ParseIntError, + }, + InvalidRelativeJumpOffset { + jump_offset: i8, + }, + MalformedRegisterToRegisterCopy, +} + +impl AssemblerErrorKind { + fn unknown_instruction(instruction: &str) -> AssemblerErrorKind { + AssemblerErrorKind::UnknownInstruction { + instruction: instruction.to_string(), + } + } + + fn invalid_register(register: &str) -> AssemblerErrorKind { + AssemblerErrorKind::InvalidRegister { + register: register.to_string(), + } + } + + fn invalid_register_half(register_half: &str) -> AssemblerErrorKind { + AssemblerErrorKind::InvalidRegisterHalf { + register_half: register_half.to_string(), + } + } + + fn invalid_carry(carry: &str) -> AssemblerErrorKind { + AssemblerErrorKind::InvalidCarry { + carry: carry.to_string(), + } + } + + fn number_out_of_range + Copy>( + number: T, + range: RangeInclusive, + ) -> AssemblerErrorKind { + AssemblerErrorKind::NumberOutOfRange { + number: number.into(), + // am I doing this right? + range: (*range.start()).into()..=(*range.end()).into(), + } + } + + fn invalid_relative_jump_offset(jump_offset: i8) -> AssemblerErrorKind { + AssemblerErrorKind::InvalidRelativeJumpOffset { jump_offset } + } + + fn malformed_register_to_register_copy() -> AssemblerErrorKind { + AssemblerErrorKind::MalformedRegisterToRegisterCopy + } + + fn to_assembler_error(&self, line: usize) -> AssemblerError { + AssemblerError { + line, + error: self.clone(), + } + } +} + +impl From for AssemblerErrorKind { + fn from(parse_int_error: num::ParseIntError) -> AssemblerErrorKind { + AssemblerErrorKind::InvalidInteger { parse_int_error } + } +} + +impl Display for AssemblerErrorKind { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + write!( + f, + "{}", + match &self { + AssemblerErrorKind::TooMuchInstructions => String::from("too much instructions"), + AssemblerErrorKind::UnknownInstruction { instruction } => + format!("unknown instruction: '{}'", instruction), + AssemblerErrorKind::InvalidRegister { register } => + format!("invalid register: '{}'", register), + AssemblerErrorKind::InvalidRegisterHalf { register_half } => + format!("invalid register half: '{}'", register_half), + AssemblerErrorKind::InvalidCarry { carry } => format!("invalid carry: '{}'", carry), + AssemblerErrorKind::NumberOutOfRange { number, range } => format!( + "number {} is out of range [{}, {}]", + number, + range.start(), + range.end() + ), + AssemblerErrorKind::InvalidInteger { parse_int_error } => + format!("failed to parse integer: {}", parse_int_error), + AssemblerErrorKind::InvalidRelativeJumpOffset { jump_offset } => + format!("{} is not a valid relative jump offset", jump_offset), + AssemblerErrorKind::MalformedRegisterToRegisterCopy => + String::from("malformed register to register copy"), + } + ) + } +} + +impl Error for AssemblerErrorKind { + fn description(&self) -> &str { + match self { + AssemblerErrorKind::TooMuchInstructions => "Too much instructions", + AssemblerErrorKind::UnknownInstruction { .. } => "Unknown instruction", + AssemblerErrorKind::InvalidRegister { .. } => "Invalid register", + AssemblerErrorKind::InvalidRegisterHalf { .. } => "Invalid register half", + AssemblerErrorKind::InvalidCarry { .. } => "Invalid carry", + AssemblerErrorKind::NumberOutOfRange { .. } => "Number out of range", + AssemblerErrorKind::InvalidInteger { .. } => "Failed to parse integer", + AssemblerErrorKind::InvalidRelativeJumpOffset { .. } => "Invalid relative jump offset", + AssemblerErrorKind::MalformedRegisterToRegisterCopy => { + "Malformed register to register copy" + } + } + } +} + +#[derive(Debug)] +pub struct AssemblerError { + line: usize, + error: AssemblerErrorKind, +} + +type AssemblerResult = Result; + +impl Display for AssemblerError { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + write!(f, "Failed to assemble, line {}: {}", self.line, self.error) + } +} + +impl Error for AssemblerError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + Some(&self.error) + } +} + +impl AssemblerError { + fn too_much_instructions(line: usize) -> AssemblerError { + AssemblerError { + line, + error: AssemblerErrorKind::TooMuchInstructions, + } + } +} + +pub struct Assembler { + table: Vec<(Regex, InstructionDecoder)>, +} + +impl Assembler { + pub fn new() -> Self { + Assembler { + table: vec![ + asm_entry!(format!(r"^BYTE\s{}$", REGEX_NUM), parse_byte), + asm_entry!( + format!(r"^MOV\s{}{}{}$", REGEX_REG, REGEX_COMMA, REGEX_ADDR), + parse_mov_reg_mem + ), + asm_entry!( + format!(r"^MOV\s{}{}{}$", REGEX_ADDR, REGEX_COMMA, REGEX_REG), + parse_mov_mem_reg + ), + asm_entry!( + format!(r"^MOV\s{}{}{}$", REGEX_REG_HALF, REGEX_COMMA, REGEX_NUM), + parse_mov_reg_half_imm + ), + asm_entry!( + format!(r"^MOV\s{}{}{}", REGEX_REG, REGEX_COMMA, REGEX_REG), + parse_mov_reg_reg + ), + asm_entry!(format!(r"^JMP\s{}$", REGEX_INUM), parse_jmp), + asm_entry!(format!(r"^JMPC\s{}$", REGEX_INUM), parse_jmpc), + asm_entry!(format!(r"^AJMP\s{}$", REGEX_REG), parse_ajmp), + asm_entry!(format!(r"^AJMPC\s{}$", REGEX_REG), parse_ajmpc), + asm_entry!(r"^EQ$", |_, _| Ok(Instruction::CmpEqual)), + asm_entry!(r"^GT$", |_, _| Ok(Instruction::CmpGreater)), + asm_entry!(r"^LE$", |_, _| Ok(Instruction::CmpLess)), + asm_entry!(r"^NOT$", |_, _| Ok(Instruction::Not)), + asm_entry!(r"^SHR$", |_, _| Ok(Instruction::BitShiftVar { + mode: ShiftMode::Logical, + right: true + })), + asm_entry!(r"^SHL$", |_, _| Ok(Instruction::BitShiftVar { + mode: ShiftMode::Logical, + right: false + })), + asm_entry!(r"^ROTR$", |_, _| Ok(Instruction::BitShiftVar { + mode: ShiftMode::Circular, + right: true + })), + asm_entry!(r"^ROTL$", |_, _| Ok(Instruction::BitShiftVar { + mode: ShiftMode::Circular, + right: false + })), + asm_entry!( + format!(r"^SHR\s{}{}{}$", REGEX_REG, REGEX_COMMA, REGEX_NUM), + parse_shr + ), + asm_entry!( + format!(r"^SHL\s{}{}{}$", REGEX_REG, REGEX_COMMA, REGEX_NUM), + parse_shl + ), + asm_entry!( + format!(r"^ROTR\s{}{}{}$", REGEX_REG, REGEX_COMMA, REGEX_NUM), + parse_rotr + ), + asm_entry!( + format!(r"^ROTL\s{}{}{}$", REGEX_REG, REGEX_COMMA, REGEX_NUM), + parse_rotl + ), + asm_entry!(format!(r"^INC\s{}$", REGEX_REG), parse_inc), + asm_entry!(format!(r"^DEC\s{}$", REGEX_REG), parse_dec), + asm_entry!(r"^ADD$", |_, _| Ok(Instruction::Add)), + asm_entry!(r"^SUB$", |_, _| Ok(Instruction::Sub)), + asm_entry!(r"^AND$", |_, _| Ok(Instruction::And)), + asm_entry!(r"^OR$", |_, _| Ok(Instruction::Or)), + asm_entry!(r"^XOR$", |_, _| Ok(Instruction::Xor)), + asm_entry!(r"^XNOR$", |_, _| Ok(Instruction::Xnor)), + asm_entry!(format!(r"^COMPL\s{}$", REGEX_REG), parse_compl), + asm_entry!(format!(r"^SETC\s{}$", REGEX_CARRY), parse_setc), + asm_entry!(r"^NOP$", |_, _| Ok(Instruction::Nop)), + asm_entry!(r"^HALT$", |_, _| Ok(Instruction::Halt)), + asm_entry!(format!(r"^CLOAD\s{}$", REGEX_REG), parse_cload), + asm_entry!(format!(r"^ZERO\s{}$", REGEX_REG), parse_zero), + asm_entry!(r"^DIV$", |_, _| Ok(Instruction::Div)), + asm_entry!(r"^MUL$", |_, _| Ok(Instruction::Mul)), + asm_entry!(r"^SWAP$", |_, _| Ok(Instruction::Swap)), + asm_entry!(r"^PRTCHK$", |_, _| Ok(Instruction::PortReady)), + asm_entry!(r"^PRTRD$", |_, _| Ok(Instruction::ReadPort)), + asm_entry!(r"^PRTWR$", |_, _| Ok(Instruction::WritePort)), + ], + } + } + + pub fn assemble(&self, input: &str) -> AssemblerResult<[u8; 0x100]> { + let mut ret = [0u8; 0x100]; + let mut i: usize = 0; + for (line_num, line) in input + .lines() + .map(|line| strip_comment(line).trim()) + .enumerate() + { + match line { + "" => (), + line => { + if i > 0xFF { + return Err(AssemblerError::too_much_instructions(line_num)); + } + let insn = self + .line_to_insn(line) + .map_err(|e| e.to_assembler_error(line_num))?; + ret[i] = encode(insn); + i += 1; + } + } + } + Ok(ret) + } + + fn line_to_insn(&self, line: &str) -> Result { + for (regex, handler) in &self.table { + if regex.is_match(line) { + return handler(line, regex); + } + } + Err(AssemblerErrorKind::unknown_instruction(line)) + } +} + +impl Default for Assembler { + fn default() -> Self { + Self::new() + } +} + +fn strip_comment(input: &str) -> &str { + match input.find('#') { + Some(x) => &input[0..x], + None => input, + } +} + +fn parse_u128(src: &str) -> Result { + if let Some(s) = src.strip_prefix("0b") { + Ok(u128::from_str_radix(s, 2)?) + } else if let Some(s) = src.strip_prefix("0o") { + Ok(u128::from_str_radix(s, 8)?) + } else if let Some(s) = src.strip_prefix("0x") { + Ok(u128::from_str_radix(s, 16)?) + } else { + Ok(src.parse()?) + } +} + +fn parse_u8(src: &str) -> Result { + match parse_u128(src)? { + x @ 0..=255 => Ok(x as u8), + x => Err(AssemblerErrorKind::number_out_of_range(x as i128, 0..=255)), + } +} + +fn parse_i8(src: &str) -> Result { + let (sign, num): (i128, u128) = match src.strip_prefix('-') { + Some(s) => (-1, parse_u128(s)?), + None => (1, parse_u128(src)?), + }; + match (sign, num) { + (1, x) if x <= 127 => Ok((x as i128 * sign) as i8), + (-1, x) if x <= 128 => Ok((x as i128 * sign) as i8), + (_, x) => Err(AssemblerErrorKind::number_out_of_range( + x as i128, + -128..=127, + )), + } +} + +fn parse_u4(src: &str) -> Result { + match parse_u8(src)? { + x @ 0..=15 => Ok(u4::new(x)), + x => Err(AssemblerErrorKind::number_out_of_range(x as i128, 0..=15)), + } +} + +fn parse_u3(src: &str) -> Result { + match parse_u8(src)? { + x @ 0..=7 => Ok(u3::new(x)), + x => Err(AssemblerErrorKind::number_out_of_range(x, 0..=7)), + } +} + +fn parse_reg(src: &str) -> Result { + match src { + "R0" => Ok(Register::R0), + "R1" => Ok(Register::R1), + x => Err(AssemblerErrorKind::invalid_register(x)), + } +} + +fn parse_reg_half(src: &str) -> Result<(Register, bool), AssemblerErrorKind> { + match src { + "R0.l" => Ok((Register::R0, true)), + "R0.h" => Ok((Register::R0, false)), + "R1.l" => Ok((Register::R1, true)), + "R1.h" => Ok((Register::R1, false)), + x => Err(AssemblerErrorKind::invalid_register_half(x)), + } +} + +fn parse_carry(src: &str) -> Result { + match src { + "0" => Ok(false), + "1" => Ok(true), + x => Err(AssemblerErrorKind::invalid_carry(x)), + } +} + +fn parse_byte(line: &str, regex: &Regex) -> Result { + Ok(decode(parse_u8( + regex.captures(line).unwrap().get(1).unwrap().as_str(), + )?)) +} + +fn parse_mov_reg_mem(line: &str, regex: &Regex) -> Result { + let captures = regex.captures(line).unwrap(); + let reg = parse_reg(captures.get(1).unwrap().as_str())?; + let addr = parse_u4(captures.get(2).unwrap().as_str())?; + Ok(Instruction::Load { reg, addr }) +} + +fn parse_mov_mem_reg(line: &str, regex: &Regex) -> Result { + let captures = regex.captures(line).unwrap(); + let addr = parse_u4(captures.get(1).unwrap().as_str())?; + let reg = parse_reg(captures.get(2).unwrap().as_str())?; + Ok(Instruction::Store { reg, addr }) +} + +fn parse_mov_reg_half_imm(line: &str, regex: &Regex) -> Result { + let captures = regex.captures(line).unwrap(); + let (reg, low) = parse_reg_half(captures.get(1).unwrap().as_str())?; + let val = parse_u4(captures.get(2).unwrap().as_str())?; + Ok(Instruction::LoadImmediate { low, reg, val }) +} + +fn parse_mov_reg_reg(line: &str, regex: &Regex) -> Result { + let captures = regex.captures(line).unwrap(); + let reg_to = parse_reg(captures.get(1).unwrap().as_str())?; + let reg_from = parse_reg(captures.get(2).unwrap().as_str())?; + match (reg_to, reg_from) { + (Register::R0, Register::R1) => Ok(Instruction::Copy { + reg_from: Register::R1, + }), + (Register::R1, Register::R0) => Ok(Instruction::Copy { + reg_from: Register::R0, + }), + _ => Err(AssemblerErrorKind::malformed_register_to_register_copy()), + } +} + +fn parse_jmp_impl( + line: &str, + regex: &Regex, + use_carry: bool, +) -> Result { + match parse_i8(regex.captures(line).unwrap().get(1).unwrap().as_str())? { + x @ -3..=-1 => Ok(Instruction::NearJumpBackward { + use_carry, + offset: u2::new(-x as u8), + }), + x @ 0..=7 => Ok(Instruction::NearJumpForward { + use_carry, + offset: u3::new(x as u8), + }), + x => Err(AssemblerErrorKind::invalid_relative_jump_offset(x)), + } +} + +fn parse_jmp(line: &str, regex: &Regex) -> Result { + parse_jmp_impl(line, regex, false) +} + +fn parse_jmpc(line: &str, regex: &Regex) -> Result { + parse_jmp_impl(line, regex, true) +} + +fn parse_ajmp_impl( + line: &str, + regex: &Regex, + use_carry: bool, +) -> Result { + let reg = parse_reg(regex.captures(line).unwrap().get(1).unwrap().as_str())?; + Ok(Instruction::FarJump { reg, use_carry }) +} + +fn parse_ajmp(line: &str, regex: &Regex) -> Result { + parse_ajmp_impl(line, regex, false) +} + +fn parse_ajmpc(line: &str, regex: &Regex) -> Result { + parse_ajmp_impl(line, regex, true) +} + +fn parse_shift( + line: &str, + regex: &Regex, + right: bool, + mode: ShiftMode, +) -> Result { + let captures = regex.captures(line).unwrap(); + let reg = parse_reg(captures.get(1).unwrap().as_str())?; + let len = parse_u3(captures.get(2).unwrap().as_str())?; + Ok(Instruction::BitShift { + right, + reg, + mode, + len, + }) +} + +fn parse_shr(line: &str, regex: &Regex) -> Result { + parse_shift(line, regex, true, ShiftMode::Logical) +} + +fn parse_shl(line: &str, regex: &Regex) -> Result { + parse_shift(line, regex, false, ShiftMode::Logical) +} + +fn parse_rotr(line: &str, regex: &Regex) -> Result { + parse_shift(line, regex, true, ShiftMode::Circular) +} + +fn parse_rotl(line: &str, regex: &Regex) -> Result { + parse_shift(line, regex, false, ShiftMode::Circular) +} + +fn parse_inc(line: &str, regex: &Regex) -> Result { + let reg = parse_reg(regex.captures(line).unwrap().get(1).unwrap().as_str())?; + Ok(Instruction::Inc { reg }) +} + +fn parse_dec(line: &str, regex: &Regex) -> Result { + let reg = parse_reg(regex.captures(line).unwrap().get(1).unwrap().as_str())?; + Ok(Instruction::Dec { reg }) +} + +fn parse_compl(line: &str, regex: &Regex) -> Result { + let reg = parse_reg(regex.captures(line).unwrap().get(1).unwrap().as_str())?; + Ok(Instruction::Complement { reg }) +} + +fn parse_setc(line: &str, regex: &Regex) -> Result { + let carry = parse_carry(regex.captures(line).unwrap().get(1).unwrap().as_str())?; + Ok(Instruction::Setc { carry }) +} + +fn parse_cload(line: &str, regex: &Regex) -> Result { + let reg = parse_reg(regex.captures(line).unwrap().get(1).unwrap().as_str())?; + Ok(Instruction::Cload { reg }) +} + +fn parse_zero(line: &str, regex: &Regex) -> Result { + let reg = parse_reg(regex.captures(line).unwrap().get(1).unwrap().as_str())?; + Ok(Instruction::Zero { reg }) +} + +#[cfg(test)] +mod tests { + use crate::{ + assembler::{strip_comment, Assembler}, + instruction::{decode, Instruction, Register, ShiftMode}, + }; + use lazy_static::lazy_static; + use std::fmt::{Binary, Display, LowerHex, Octal}; + use ux::{u2, u3, u4}; + + lazy_static! { + static ref ASM: Assembler = Assembler::new(); + } + + enum IntegerFormat { + Decimal, + Binary, + Octal, + Hexadecimal, + None, + } + + struct PrintIntegers { + num: T, + format: IntegerFormat, + } + + impl PrintIntegers { + fn new(num: T) -> Self { + PrintIntegers { + num, + format: IntegerFormat::Decimal, + } + } + } + + impl Iterator for PrintIntegers { + type Item = String; + + fn next(&mut self) -> Option { + match self.format { + IntegerFormat::Decimal => { + self.format = IntegerFormat::Binary; + Some(format!("{}", self.num)) + } + IntegerFormat::Binary => { + self.format = IntegerFormat::Octal; + Some(format!("{:#b}", self.num)) + } + IntegerFormat::Octal => { + self.format = IntegerFormat::Hexadecimal; + Some(format!("{:#o}", self.num)) + } + IntegerFormat::Hexadecimal => { + self.format = IntegerFormat::None; + Some(format!("{:#x}", self.num)) + } + IntegerFormat::None => None, + } + } + } + + macro_rules! make_test_insn_no_args { + ($name:ident, $line:expr, $insn:expr) => { + #[test] + fn $name() { + assert_eq!(ASM.line_to_insn($line).unwrap(), $insn); + } + }; + } + + make_test_insn_no_args!(asm_line_eq, "EQ", Instruction::CmpEqual); + make_test_insn_no_args!(asm_line_gt, "GT", Instruction::CmpGreater); + make_test_insn_no_args!(asm_line_le, "LE", Instruction::CmpLess); + make_test_insn_no_args!(asm_line_not, "NOT", Instruction::Not); + make_test_insn_no_args!(asm_line_add, "ADD", Instruction::Add); + make_test_insn_no_args!(asm_line_sub, "SUB", Instruction::Sub); + make_test_insn_no_args!(asm_line_and, "AND", Instruction::And); + make_test_insn_no_args!(asm_line_or, "OR", Instruction::Or); + make_test_insn_no_args!(asm_line_xor, "XOR", Instruction::Xor); + make_test_insn_no_args!(asm_line_xnor, "XNOR", Instruction::Xnor); + make_test_insn_no_args!(asm_line_nop, "NOP", Instruction::Nop); + make_test_insn_no_args!(asm_line_halt, "HALT", Instruction::Halt); + make_test_insn_no_args!(asm_line_div, "DIV", Instruction::Div); + make_test_insn_no_args!(asm_line_mul, "MUL", Instruction::Mul); + make_test_insn_no_args!(asm_line_swap, "SWAP", Instruction::Swap); + make_test_insn_no_args!(asm_line_prtchk, "PRTCHK", Instruction::PortReady); + make_test_insn_no_args!(asm_line_prtrd, "PRTRD", Instruction::ReadPort); + make_test_insn_no_args!(asm_line_prtwr, "PRTWR", Instruction::WritePort); + make_test_insn_no_args!( + asm_line_shr, + "SHR", + Instruction::BitShiftVar { + mode: ShiftMode::Logical, + right: true + } + ); + make_test_insn_no_args!( + asm_line_shl, + "SHL", + Instruction::BitShiftVar { + mode: ShiftMode::Logical, + right: false + } + ); + make_test_insn_no_args!( + asm_line_rotr, + "ROTR", + Instruction::BitShiftVar { + mode: ShiftMode::Circular, + right: true + } + ); + make_test_insn_no_args!( + asm_line_rotl, + "ROTL", + Instruction::BitShiftVar { + mode: ShiftMode::Circular, + right: false + } + ); + + #[test] + fn asm_line_byte() { + for i in 0..=255u8 { + for j in PrintIntegers::new(i) { + assert_eq!(ASM.line_to_insn(&format!("BYTE {}", j)).unwrap(), decode(i)); + } + } + } + + #[test] + fn asm_line_mov_reg_mem() { + for (reg_str, reg) in [("R0", Register::R0)] { + for i in 0..=15u8 { + for j in PrintIntegers::new(i) { + assert_eq!( + ASM.line_to_insn(&format!("MOV {}, [{}]", reg_str, j)) + .unwrap(), + Instruction::Load { + reg: reg.clone(), + addr: u4::new(i) + } + ); + } + } + } + } + + #[test] + fn asm_line_mov_mem_reg() { + for (reg_str, reg) in [("R0", Register::R0)] { + for i in 0..=15u8 { + for j in PrintIntegers::new(i) { + assert_eq!( + ASM.line_to_insn(&format!("MOV [{}], {}", j, reg_str)) + .unwrap(), + Instruction::Store { + reg: reg.clone(), + addr: u4::new(i) + } + ); + } + } + } + } + + #[test] + fn asm_line_mov_reg_half_imm() { + for (reg_str, reg) in [("R0", Register::R0), ("R1", Register::R1)] { + for (low_str, low) in [("l", true), ("h", false)] { + for i in 0..=15 { + for j in PrintIntegers::new(i) { + assert_eq!( + ASM.line_to_insn(&format!("MOV {}.{}, {}", reg_str, low_str, j)) + .unwrap(), + Instruction::LoadImmediate { + low, + reg: reg.clone(), + val: u4::new(i) + } + ); + } + } + } + } + } + + #[test] + fn asm_line_jmp() { + for i in 0..=7u8 { + for j in PrintIntegers::new(i) { + assert_eq!( + ASM.line_to_insn(&format!("JMP {}", j)).unwrap(), + Instruction::NearJumpForward { + use_carry: false, + offset: u3::new(i) + } + ) + } + } + for i in 1..=3u8 { + for j in PrintIntegers::new(i) { + assert_eq!( + ASM.line_to_insn(&format!("JMP -{}", j)).unwrap(), + Instruction::NearJumpBackward { + use_carry: false, + offset: u2::new(i) + } + ) + } + } + } + + #[test] + fn asm_line_jmpc() { + for i in 0..=7u8 { + for j in PrintIntegers::new(i) { + assert_eq!( + ASM.line_to_insn(&format!("JMPC {}", j)).unwrap(), + Instruction::NearJumpForward { + use_carry: true, + offset: u3::new(i) + } + ) + } + } + for i in 1..=3u8 { + for j in PrintIntegers::new(i) { + assert_eq!( + ASM.line_to_insn(&format!("JMPC -{}", j)).unwrap(), + Instruction::NearJumpBackward { + use_carry: true, + offset: u2::new(i) + } + ) + } + } + } + + #[test] + fn asm_lime_ajmp() { + assert_eq!( + ASM.line_to_insn("AJMP R0").unwrap(), + Instruction::FarJump { + reg: Register::R0, + use_carry: false + } + ); + assert_eq!( + ASM.line_to_insn("AJMP R1").unwrap(), + Instruction::FarJump { + reg: Register::R1, + use_carry: false + } + ); + } + + #[test] + fn asm_lime_ajmpc() { + for (reg_str, reg) in [("R0", Register::R0), ("R1", Register::R1)] { + assert_eq!( + ASM.line_to_insn(&format!("AJMPC {}", reg_str)).unwrap(), + Instruction::FarJump { + reg, + use_carry: true + } + ); + } + } + + #[test] + fn asm_line_shift_rotate() { + for (insn, right, mode) in [ + ("SHR", true, ShiftMode::Logical), + ("SHL", false, ShiftMode::Logical), + ("ROTR", true, ShiftMode::Circular), + ("ROTL", false, ShiftMode::Circular), + ] { + for (reg_str, reg) in [("R0", Register::R0), ("R1", Register::R1)] { + for i in 0..7u8 { + for j in PrintIntegers::new(i) { + assert_eq!( + ASM.line_to_insn(&format!("{} {}, {}", insn, reg_str, j)) + .unwrap(), + Instruction::BitShift { + right, + reg: reg.clone(), + mode: mode.clone(), + len: u3::new(i) + } + ) + } + } + } + } + } + + #[test] + fn asm_line_inc() { + for (reg_str, reg) in [("R0", Register::R0), ("R1", Register::R1)] { + assert_eq!( + ASM.line_to_insn(&format!("INC {}", reg_str)).unwrap(), + Instruction::Inc { reg } + ); + } + } + + #[test] + fn asm_line_dec() { + for (reg_str, reg) in [("R0", Register::R0), ("R1", Register::R1)] { + assert_eq!( + ASM.line_to_insn(&format!("DEC {}", reg_str)).unwrap(), + Instruction::Dec { reg } + ); + } + } + + #[test] + fn asm_line_compl() { + for (reg_str, reg) in [("R0", Register::R0), ("R1", Register::R1)] { + assert_eq!( + ASM.line_to_insn(&format!("COMPL {}", reg_str)).unwrap(), + Instruction::Complement { reg } + ); + } + } + + #[test] + fn asm_line_setc() { + for (i, carry) in [(0, false), (1, true)] { + assert_eq!( + ASM.line_to_insn(&format!("SETC {}", i)).unwrap(), + Instruction::Setc { carry } + ) + } + } + + #[test] + fn asm_line_cload() { + for (reg_str, reg) in [("R0", Register::R0), ("R1", Register::R1)] { + assert_eq!( + ASM.line_to_insn(&format!("CLOAD {}", reg_str)).unwrap(), + Instruction::Cload { reg } + ); + } + } + + #[test] + fn asm_line_zero() { + for (reg_str, reg) in [("R0", Register::R0), ("R1", Register::R1)] { + assert_eq!( + ASM.line_to_insn(&format!("ZERO {}", reg_str)).unwrap(), + Instruction::Zero { reg } + ); + } + } + + #[test] + fn asm_assemble_empty() { + assert_eq!(ASM.assemble("").unwrap(), [0u8; 256]); + } + + #[test] + fn asm_assemble() { + let expected = { + let mut code = [0u8; 256]; + code[0..6].copy_from_slice(&[0x4F, 0x53, 0xE4, 0x20, 0xF8, 0xF5]); + code + }; + assert_eq!( + ASM.assemble( + r#" + MOV R0.l, 0xF + MOV R1.l, 0x3 + ADD + MOV [0], R0 + ZERO R0 + HALT + "# + ) + .unwrap(), + expected + ); + } + + #[test] + fn asm_strip_comment() { + assert_eq!(strip_comment("MOV [2], R1 # comment"), "MOV [2], R1 "); + } + + #[test] + fn asm_assemble_comments() { + let expected = { + let mut code = [0u8; 256]; + code[0..6].copy_from_slice(&[0x4F, 0x53, 0xE4, 0x20, 0xF8, 0xF5]); + code + }; + assert_eq!( + ASM.assemble( + r#" + MOV R0.l, 0xF # R0 = 15 + MOV R1.l, 0x3 # R1 = 3 + ADD # R0 += R1 (18) + MOV [0], R0 # MEM[0] = R0 (18) + ZERO R0 # R0 = 0 + HALT # EXIT + "# + ) + .unwrap(), + expected + ); + } +} diff --git a/toy_cpu_4bit/src/cpu.rs b/toy_cpu_4bit/src/cpu.rs new file mode 100644 index 0000000..e4d668a --- /dev/null +++ b/toy_cpu_4bit/src/cpu.rs @@ -0,0 +1,1634 @@ +// SPDX-License-Identifier: MIT +// Copyright Murad Karammaev, Nikita Kuzmin + +use crate::instruction::{decode, Instruction, Register, ShiftMode}; +use std::{cmp, collections::VecDeque, mem, num::Wrapping}; +use ux::{u3, u4}; + +#[allow(non_snake_case)] +pub struct Cpu { + pub IP: Wrapping, + pub R0: Wrapping, + pub R1: Wrapping, + pub C: bool, + pub code: [u8; 0x100], + pub data: [u8; 0x10], + pub port_in: VecDeque, + pub port_out: VecDeque, + pub num_cycles: u64, +} + +impl Cpu { + pub fn new(code: &[u8]) -> Cpu { + let num_instructions = cmp::min(0x100, code.len()); + let mut new_code = [0u8; 0x100]; + new_code[..num_instructions].clone_from_slice(&code[..num_instructions]); + Cpu { + IP: Wrapping(0), + R0: Wrapping(0), + R1: Wrapping(0), + C: false, + code: new_code, + data: [0u8; 0x10], + port_in: VecDeque::::new(), + port_out: VecDeque::::new(), + num_cycles: 0, + } + } + + fn load(&mut self, reg: Register, addr: u4) { + match reg { + Register::R0 => self.R0 = Wrapping(self.data[u8::from(addr) as usize]), + Register::R1 => self.R1 = Wrapping(self.data[u8::from(addr) as usize]), + } + self.IP += Wrapping(1); + } + + fn store(&mut self, reg: Register, addr: u4) { + match reg { + Register::R0 => self.data[u8::from(addr) as usize] = self.R0.0, + Register::R1 => self.data[u8::from(addr) as usize] = self.R1.0, + } + self.IP += Wrapping(1); + } + + fn apply_imm(low: bool, mut reg: u8, val: u4) -> Wrapping { + if low { + reg &= 0b11110000; + reg |= u8::from(val); + } else { + reg &= 0b00001111; + reg |= u8::from(val) << 4; + } + Wrapping(reg) + } + + fn load_imm(&mut self, low: bool, reg: Register, val: u4) { + match reg { + Register::R0 => self.R0 = Cpu::apply_imm(low, self.R0.0, val), + Register::R1 => self.R1 = Cpu::apply_imm(low, self.R1.0, val), + } + self.IP += Wrapping(1); + } + + fn near_jump(&mut self, use_carry: bool, forward: bool, offset: u8) { + if use_carry && !self.C { + self.IP += Wrapping(1); + return; + } + if forward { + self.IP += Wrapping(offset); + } else { + self.IP -= Wrapping(offset); + } + } + + fn far_jump(&mut self, use_carry: bool, reg: Register) { + if use_carry && !self.C { + self.IP += Wrapping(1); + return; + } + self.IP = match reg { + Register::R0 => self.R0, + Register::R1 => self.R1, + }; + } + + fn cmp_equal(&mut self) { + self.C = self.R0 == self.R1; + self.IP += Wrapping(1); + } + + fn cmp_gt(&mut self) { + self.C = self.R0 > self.R1; + self.IP += Wrapping(1); + } + + fn cmp_le(&mut self) { + self.C = self.R0 < self.R1; + self.IP += Wrapping(1); + } + + fn not(&mut self) { + self.C = !self.C; + self.IP += Wrapping(1); + } + + fn bit_shift_logical(&mut self, right: bool, reg: Register, len: u8) { + match len { + 0..=7 => match reg { + Register::R0 => { + self.R0 = Wrapping(if right { + self.R0.0 >> len + } else { + self.R0.0 << len + }) + } + Register::R1 => { + self.R1 = Wrapping(if right { + self.R1.0 >> len + } else { + self.R1.0 << len + }) + } + }, + _ => match reg { + Register::R0 => self.R0 = Wrapping(0), + Register::R1 => self.R1 = Wrapping(0), + }, + } + } + + fn bit_shift_circular(&mut self, right: bool, reg: Register, len: u32) { + match len { + 0..=7 => match reg { + Register::R0 => { + self.R0 = Wrapping(if right { + self.R0.0.rotate_right(len) + } else { + self.R0.0.rotate_left(len) + }) + } + Register::R1 => { + self.R1 = Wrapping(if right { + self.R1.0.rotate_right(len) + } else { + self.R1.0.rotate_left(len) + }) + } + }, + _ => match reg { + Register::R0 => self.R0 = Wrapping(0), + Register::R1 => self.R1 = Wrapping(0), + }, + } + } + + fn bit_shift(&mut self, right: bool, reg: Register, mode: ShiftMode, len: u3) { + match mode { + ShiftMode::Logical => self.bit_shift_logical(right, reg, u8::from(len)), + ShiftMode::Circular => self.bit_shift_circular(right, reg, u32::from(len)), + } + self.IP += Wrapping(1); + } + + fn inc(&mut self, reg: Register) { + match reg { + Register::R0 => { + self.R0 += Wrapping(1); + self.C = self.R0 == Wrapping(0); + } + Register::R1 => { + self.R1 += Wrapping(1); + self.C = self.R1 == Wrapping(0); + } + } + self.IP += Wrapping(1); + } + + fn dec(&mut self, reg: Register) { + match reg { + Register::R0 => { + self.R0 -= Wrapping(1); + self.C = self.R0 == Wrapping(0); + } + Register::R1 => { + self.R1 -= Wrapping(1); + self.C = self.R1 == Wrapping(0); + } + } + self.IP += Wrapping(1); + } + + fn add(&mut self) { + self.C = self.R0.0 as u16 + self.R1.0 as u16 > 0xFF; + self.R0 += self.R1; + self.IP += Wrapping(1); + } + + fn sub(&mut self) { + self.C = self.R1 > self.R0; + self.R0 -= self.R1; + self.IP += Wrapping(1); + } + + fn and(&mut self) { + self.R0 &= self.R1; + self.IP += Wrapping(1); + } + + fn or(&mut self) { + self.R0 |= self.R1; + self.IP += Wrapping(1); + } + + fn xor(&mut self) { + self.R0 ^= self.R1; + self.IP += Wrapping(1); + } + + fn xnor(&mut self) { + self.R0 = !(self.R0 ^ self.R1); + self.IP += Wrapping(1); + } + + fn complement(&mut self, reg: Register) { + match reg { + Register::R0 => self.R0 = !self.R0, + Register::R1 => self.R1 = !self.R1, + } + self.IP += Wrapping(1); + } + + fn bit_shift_var(&mut self, right: bool, mode: ShiftMode) { + match mode { + ShiftMode::Logical => self.bit_shift_logical(right, Register::R0, self.R1.0), + ShiftMode::Circular => self.bit_shift_circular(right, Register::R0, self.R1.0 as u32), + } + self.C = self.R0 == Wrapping(0); + self.IP += Wrapping(1); + } + + fn copy(&mut self, reg_from: Register) { + match reg_from { + Register::R0 => self.R1 = self.R0, + Register::R1 => self.R0 = self.R1, + } + self.IP += Wrapping(1); + } + + fn setc(&mut self, carry: bool) { + self.C = carry; + self.IP += Wrapping(1); + } + + fn nop(&mut self) { + self.IP += Wrapping(1); + } + + fn cload(&mut self, reg: Register) { + match reg { + Register::R0 => self.R0 = Wrapping(self.code[self.R0.0 as usize]), + Register::R1 => self.R1 = Wrapping(self.code[self.R0.0 as usize]), + } + self.IP += Wrapping(1); + } + + fn zero(&mut self, reg: Register) { + match reg { + Register::R0 => self.R0 = Wrapping(0), + Register::R1 => self.R1 = Wrapping(0), + } + self.IP += Wrapping(1); + } + + fn div(&mut self) { + if self.R1 == Wrapping(0) { + self.R0 = Wrapping(0xFF); + self.R1 = Wrapping(0); + self.C = true; + } else { + let q = self.R0.0 / self.R1.0; + let r = self.R0.0 % self.R1.0; + self.R0 = Wrapping(q); + self.R1 = Wrapping(r); + self.C = false; + } + self.IP += Wrapping(1); + } + + fn mul(&mut self) { + let p = self.R0.0 as u16 * self.R1.0 as u16; + self.R0 = Wrapping((p >> 8 & 0xFF) as u8); + self.R1 = Wrapping((p & 0xFF) as u8); + self.IP += Wrapping(1); + } + + fn swap(&mut self) { + mem::swap(&mut self.R0, &mut self.R1); + self.IP += Wrapping(1); + } + + fn port_ready(&mut self) { + self.C = !self.port_in.is_empty(); + self.IP += Wrapping(1); + } + + fn read_port(&mut self) { + match self.port_in.pop_front() { + Some(v) => self.R0 = Wrapping(v), + None => self.R0 = Wrapping(0), + } + self.IP += Wrapping(1); + } + + fn write_port(&mut self) { + self.port_out.push_back(self.R0.0); + self.IP += Wrapping(1); + } + + pub fn step(&mut self) -> bool { + self.num_cycles += 1; + let insn = decode(self.code[self.IP.0 as usize]); + match insn { + Instruction::Load { reg, addr } => self.load(reg, addr), + Instruction::Store { reg, addr } => self.store(reg, addr), + Instruction::LoadImmediate { low, reg, val } => self.load_imm(low, reg, val), + Instruction::NearJumpBackward { use_carry, offset } => { + self.near_jump(use_carry, false, u8::from(offset)) + } + Instruction::NearJumpForward { use_carry, offset } => { + self.near_jump(use_carry, true, u8::from(offset)) + } + Instruction::FarJump { reg, use_carry } => self.far_jump(use_carry, reg), + Instruction::CmpEqual => self.cmp_equal(), + Instruction::CmpGreater => self.cmp_gt(), + Instruction::CmpLess => self.cmp_le(), + Instruction::Not => self.not(), + Instruction::BitShift { + right, + reg, + mode, + len, + } => self.bit_shift(right, reg, mode, len), + Instruction::Inc { reg } => self.inc(reg), + Instruction::Dec { reg } => self.dec(reg), + Instruction::Add => self.add(), + Instruction::Sub => self.sub(), + Instruction::And => self.and(), + Instruction::Or => self.or(), + Instruction::Xor => self.xor(), + Instruction::Xnor => self.xnor(), + Instruction::Complement { reg } => self.complement(reg), + Instruction::BitShiftVar { right, mode } => self.bit_shift_var(right, mode), + Instruction::Copy { reg_from } => self.copy(reg_from), + Instruction::Setc { carry } => self.setc(carry), + Instruction::Nop => self.nop(), + Instruction::Halt => return true, + Instruction::Cload { reg } => self.cload(reg), + Instruction::Zero { reg } => self.zero(reg), + Instruction::Div => self.div(), + Instruction::Mul => self.mul(), + Instruction::Swap => self.swap(), + Instruction::PortReady => self.port_ready(), + Instruction::ReadPort => self.read_port(), + Instruction::WritePort => self.write_port(), + } + false + } + + pub fn visualize(&self) { + println!("+--+----+----------+"); + println!("|IP|{:#04X}|{:#010b}|", self.IP, self.IP); + println!("|R0|{:#04X}|{:#010b}|", self.R0, self.R0); + println!("|R1|{:#04X}|{:#010b}|", self.R1, self.R1); + println!("+--+----+----------+"); + println!("|FLAGS:{} |", if self.C { "C" } else { " " }); + println!("+------------------+"); + println!("|CYCLES:{:11}|", self.num_cycles); + println!("|PORTIN:{:11}|", self.port_in.len()); + println!("|PORTOUT:{:10}|", self.port_out.len()); + println!("+------------------+"); + println!("| MEM +0|+1|+2|+3 |"); + for i in 0..4 { + println!( + "| 0x{:X} {:02X}|{:02X}|{:02X}|{:02X} |", + i * 4, + self.data[i * 4], + self.data[i * 4 + 1], + self.data[i * 4 + 2], + self.data[i * 4 + 3] + ); + } + println!("+------------------+"); + } +} + +#[cfg(test)] +mod tests { + use super::Cpu; + use std::num::Wrapping; + + #[test] + fn insn_load_r0() { + let mut cpu = Cpu::new(&[0b00000001]); + cpu.data[0b1] = 0x45; + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.R0.0, 0x45); // data is loaded + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_load_r1() { + let mut cpu = Cpu::new(&[0b00010101]); + cpu.data[0b101] = 0x54; + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.R1.0, 0x54); // data is loaded + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_load_r0_r1() { + let mut cpu = Cpu::new(&[0b00001011, 0b00011100]); + cpu.data[0b1011] = 0x12; + cpu.data[0b1100] = 0x78; + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.R0.0, 0x12); // data is loaded + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert!(!cpu.C); // carry is not affected + } + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.R1.0, 0x78); // data is loaded + assert_eq!(cpu.IP.0, 0x02); // instruction pointer has advanced + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_store_r0() { + let mut cpu = Cpu::new(&[0b00100100]); + cpu.R0 = Wrapping(0x42); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.data[0b0100], 0x42); // data is stored + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_store_r1() { + let mut cpu = Cpu::new(&[0b00110010]); + cpu.R1 = Wrapping(0x29); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.data[0b0010], 0x29); // data is stored + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_store_r0_r1() { + let mut cpu = Cpu::new(&[0b00101111, 0b00110010]); + cpu.R0 = Wrapping(0xA4); + cpu.R1 = Wrapping(0xB3); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.data[0b1111], 0xA4); // data is stored + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert!(!cpu.C); // carry is not affected + } + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.data[0b0010], 0xB3); // data is stored + assert_eq!(cpu.IP.0, 0x02); // instruction pointer has advanced + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_store_and_load() { + let mut cpu = Cpu::new(&[0b00100101, 0b00010101]); + cpu.R0 = Wrapping(0xF3); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.data[0b0101], 0xF3); // data is stored + } + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.R1.0, 0xF3); // same data is loaded + } + } + + #[test] + fn insn_load_imm_r0() { + let mut cpu = Cpu::new(&[0b01000101, 0b01100101]); + { + assert!(!cpu.step()); // CPU has not halted + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.R0.0, 0b01010101); // data is loaded + assert_eq!(cpu.IP.0, 0x02); // instruction pointer has advanced + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_load_imm_r1() { + let mut cpu = Cpu::new(&[0b01010110, 0b01111001]); + { + assert!(!cpu.step()); // CPU has not halted + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.R1.0, 0b10010110); // data is loaded + assert_eq!(cpu.IP.0, 0x02); // instruction pointer has advanced + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_load_imm_low_only() { + let mut cpu = Cpu::new(&[0b01001111]); + cpu.R0 = Wrapping(0b10101010); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.R0.0, 0b10101111); // data is loaded correctly + } + } + + #[test] + fn insn_near_jump_backward_unconditional() { + let mut cpu = Cpu::new(&[]); + cpu.code[20] = 0b10001010; + cpu.IP = Wrapping(20); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 18); // instruction pointer had advanced + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_near_jump_backward_conditional_must() { + let mut cpu = Cpu::new(&[]); + cpu.code[40] = 0b10001110; + cpu.IP = Wrapping(40); + cpu.C = true; + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 38); // instruction pointer had advanced + assert!(cpu.C); // carry is not affected + } + } + + #[test] + fn insn_near_jump_backward_conditional_must_not() { + let mut cpu = Cpu::new(&[]); + cpu.code[40] = 0b10001110; + cpu.IP = Wrapping(40); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 41); // instruction pointer had advanced + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_near_jump_forward_unconditional() { + let mut cpu = Cpu::new(&[0b10010101]); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0b101); // instruction pointer had advanced + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_near_jump_forward_conditional_must() { + let mut cpu = Cpu::new(&[0b10011011]); + cpu.C = true; + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0b011); // instruction pointer had advanced + assert!(cpu.C); // carry is not affected + } + } + + #[test] + fn insn_near_jump_forward_conditional_must_not() { + let mut cpu = Cpu::new(&[0b10011011]); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 1); // instruction pointer had advanced + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_far_jump_unconditional_r0() { + let mut cpu = Cpu::new(&[0b10000000]); + cpu.R0 = Wrapping(0xA0); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0xA0); // instruction pointer has advanced + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_far_jump_unconditional_r1() { + let mut cpu = Cpu::new(&[0b10000010]); + cpu.R1 = Wrapping(0xC7); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0xC7); // instruction pointer has advanced + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_far_jump_conditional_must_r0() { + let mut cpu = Cpu::new(&[0b10000001]); + cpu.R0 = Wrapping(0x2B); + cpu.C = true; + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x2B); // instruction pointer has advanced + assert!(cpu.C); // carry is not affected + } + } + + #[test] + fn insn_far_jump_conditional_must_r1() { + let mut cpu = Cpu::new(&[0b10000011]); + cpu.R1 = Wrapping(0x0A); + cpu.C = true; + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x0A); // instruction pointer has advanced + assert!(cpu.C); // carry is not affected + } + } + + #[test] + fn insn_far_jump_conditional_must_not_r0() { + let mut cpu = Cpu::new(&[0b10000001]); + cpu.R0 = Wrapping(0x44); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_far_jump_conditional_must_not_r1() { + let mut cpu = Cpu::new(&[0b10000011]); + cpu.R1 = Wrapping(0x88); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_equal_true() { + let mut cpu = Cpu::new(&[0b10000100]); + cpu.R0 = Wrapping(0xC2); + cpu.R1 = Wrapping(0xC2); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert!(cpu.C); // carry is set + } + } + + #[test] + fn insn_equal_false() { + let mut cpu = Cpu::new(&[0b10000100]); + cpu.R0 = Wrapping(0xC2); + cpu.R1 = Wrapping(0xC1); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert!(!cpu.C); // carry is unset + } + } + + #[test] + fn insn_greater_true() { + let mut cpu = Cpu::new(&[0b10000101]); + cpu.R0 = Wrapping(0xF4); + cpu.R1 = Wrapping(0xD3); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert!(cpu.C); // carry is set + } + } + + #[test] + fn insn_greater_false_eq() { + let mut cpu = Cpu::new(&[0b10000101]); + cpu.R0 = Wrapping(0xD3); + cpu.R1 = Wrapping(0xD3); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert!(!cpu.C); // carry is unset + } + } + + #[test] + fn insn_greater_false() { + let mut cpu = Cpu::new(&[0b10000101]); + cpu.R0 = Wrapping(0xD3); + cpu.R1 = Wrapping(0xF4); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert!(!cpu.C); // carry is unset + } + } + + #[test] + fn insn_less_true() { + let mut cpu = Cpu::new(&[0b10000110]); + cpu.R0 = Wrapping(0xD3); + cpu.R1 = Wrapping(0xF4); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert!(cpu.C); // carry is set + } + } + + #[test] + fn insn_less_false_eq() { + let mut cpu = Cpu::new(&[0b10000110]); + cpu.R0 = Wrapping(0xD3); + cpu.R1 = Wrapping(0xD3); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert!(!cpu.C); // carry is unset + } + } + + #[test] + fn insn_less_false() { + let mut cpu = Cpu::new(&[0b10000110]); + cpu.R0 = Wrapping(0xF4); + cpu.R1 = Wrapping(0xD3); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert!(!cpu.C); // carry is unset + } + } + + #[test] + fn insn_not_from_true() { + let mut cpu = Cpu::new(&[0b10000111]); + cpu.C = true; + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert!(!cpu.C); // carry is unset + } + } + + #[test] + fn insn_not_from_false() { + let mut cpu = Cpu::new(&[0b10000111]); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert!(cpu.C); // carry is set + } + } + + #[test] + fn insn_right_shift_zero_r0() { + let mut cpu = Cpu::new(&[0b10100000]); + cpu.R0 = Wrapping(0b10011101); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b10011101); // data is not shifted + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_right_shift_r0() { + let mut cpu = Cpu::new(&[0b10100010]); + cpu.R0 = Wrapping(0b10011101); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b00100111); // data is shifted + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_right_shift_zero_r1() { + let mut cpu = Cpu::new(&[0b10110000]); + cpu.R1 = Wrapping(0b10011101); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R1.0, 0b10011101); // data is not shifted + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_right_shift_r1() { + let mut cpu = Cpu::new(&[0b10110010]); + cpu.R1 = Wrapping(0b10011101); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R1.0, 0b00100111); // data is shifted + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_right_rotate_zero_r0() { + let mut cpu = Cpu::new(&[0b10101000]); + cpu.R0 = Wrapping(0b10011101); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b10011101); // data is not rotated + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_right_rotate_r0() { + let mut cpu = Cpu::new(&[0b10101010]); + cpu.R0 = Wrapping(0b10011101); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b01100111); // data is rotated + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_right_rotate_zero_r1() { + let mut cpu = Cpu::new(&[0b10111000]); + cpu.R1 = Wrapping(0b10011101); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R1.0, 0b10011101); // data is not rotated + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_right_rotate_r1() { + let mut cpu = Cpu::new(&[0b10111010]); + cpu.R1 = Wrapping(0b10011101); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R1.0, 0b01100111); // data is rotated + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_left_shift_zero_r0() { + let mut cpu = Cpu::new(&[0b11000000]); + cpu.R0 = Wrapping(0b10011101); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b10011101); // data is not shifted + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_left_shift_r0() { + let mut cpu = Cpu::new(&[0b11000010]); + cpu.R0 = Wrapping(0b10011101); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b01110100); // data is shifted + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_left_shift_zero_r1() { + let mut cpu = Cpu::new(&[0b11010000]); + cpu.R1 = Wrapping(0b10011101); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R1.0, 0b10011101); // data is not shifted + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_left_shift_r1() { + let mut cpu = Cpu::new(&[0b11010010]); + cpu.R1 = Wrapping(0b10011101); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R1.0, 0b01110100); // data is shifted + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_left_rotate_zero_r0() { + let mut cpu = Cpu::new(&[0b11001000]); + cpu.R0 = Wrapping(0b10011101); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b10011101); // data is not rotated + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_left_rotate_r0() { + let mut cpu = Cpu::new(&[0b11001010]); + cpu.R0 = Wrapping(0b10011101); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b01110110); // data is rotated + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_left_rotate_zero_r1() { + let mut cpu = Cpu::new(&[0b11011000]); + cpu.R1 = Wrapping(0b10011101); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R1.0, 0b10011101); // data is not rotated + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_left_rotate_r1() { + let mut cpu = Cpu::new(&[0b11011010]); + cpu.R1 = Wrapping(0b10011101); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R1.0, 0b01110110); // data is rotated + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_inc_carry_false_r0() { + let mut cpu = Cpu::new(&[0b11100000]); + cpu.R0 = Wrapping(68); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 69); // data is incremented + assert!(!cpu.C); // carry is unset + } + } + + #[test] + fn insn_inc_carry_true_r0() { + let mut cpu = Cpu::new(&[0b11100000]); + cpu.R0 = Wrapping(255); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0); // data is incremented + assert!(cpu.C); // carry is unset + } + } + + #[test] + fn insn_inc_carry_false_r1() { + let mut cpu = Cpu::new(&[0b11100001]); + cpu.R1 = Wrapping(68); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R1.0, 69); // data is incremented + assert!(!cpu.C); // carry is unset + } + } + + #[test] + fn insn_inc_carry_true_r1() { + let mut cpu = Cpu::new(&[0b11100001]); + cpu.R1 = Wrapping(255); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R1.0, 0); // data is incremented + assert!(cpu.C); // carry is unset + } + } + + #[test] + fn insn_dec_carry_false_r0() { + let mut cpu = Cpu::new(&[0b11100010]); + cpu.R0 = Wrapping(68); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 67); // data is incremented + assert!(!cpu.C); // carry is unset + } + } + + #[test] + fn insn_dec_carry_true_r0() { + let mut cpu = Cpu::new(&[0b11100010]); + cpu.R0 = Wrapping(1); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0); // data is incremented + assert!(cpu.C); // carry is unset + } + } + + #[test] + fn insn_dec_carry_false_r1() { + let mut cpu = Cpu::new(&[0b11100011]); + cpu.R1 = Wrapping(68); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R1.0, 67); // data is incremented + assert!(!cpu.C); // carry is unset + } + } + + #[test] + fn insn_dec_carry_true_r1() { + let mut cpu = Cpu::new(&[0b11100011]); + cpu.R1 = Wrapping(1); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R1.0, 0); // data is incremented + assert!(cpu.C); // carry is unset + } + } + + #[test] + fn insn_add() { + let mut cpu = Cpu::new(&[0b11100100]); + cpu.R0 = Wrapping(0x12); + cpu.R1 = Wrapping(0x34); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0x46); // destination data is correct + assert_eq!(cpu.R1.0, 0x34); // R1 is not modified + assert!(!cpu.C); // carry is unset + } + } + + #[test] + fn insn_add_overflow() { + let mut cpu = Cpu::new(&[0b11100100]); + cpu.R0 = Wrapping(0xA3); + cpu.R1 = Wrapping(0xCB); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0x6E); // destination data is correct + assert_eq!(cpu.R1.0, 0xCB); // R1 is not modified + assert!(cpu.C); // carry is set + } + } + + #[test] + fn insn_sub() { + let mut cpu = Cpu::new(&[0b11100101]); + cpu.R0 = Wrapping(0x50); + cpu.R1 = Wrapping(0x10); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0x40); // destination data is correct + assert_eq!(cpu.R1.0, 0x10); // R1 is not modified + assert!(!cpu.C); // carry is unset + } + } + + #[test] + fn insn_sub_underflow() { + let mut cpu = Cpu::new(&[0b11100101]); + cpu.R0 = Wrapping(0x50); + cpu.R1 = Wrapping(0x90); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0xC0); // destination data is correct + assert_eq!(cpu.R1.0, 0x90); // R1 is not modified + assert!(cpu.C); // carry is set + } + } + + #[test] + fn insn_and() { + let mut cpu = Cpu::new(&[0b11100110]); + cpu.R0 = Wrapping(0b10110111); + cpu.R1 = Wrapping(0b10001100); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b10000100); // destination data is correct + assert_eq!(cpu.R1.0, 0b10001100); // R1 is not modified + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_or() { + let mut cpu = Cpu::new(&[0b11100111]); + cpu.R0 = Wrapping(0b10110111); + cpu.R1 = Wrapping(0b10001100); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b10111111); // destination data is correct + assert_eq!(cpu.R1.0, 0b10001100); // R1 is not modified + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_xor() { + let mut cpu = Cpu::new(&[0b11101000]); + cpu.R0 = Wrapping(0b10110111); + cpu.R1 = Wrapping(0b10001100); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b00111011); // destination data is correct + assert_eq!(cpu.R1.0, 0b10001100); // R1 is not modified + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_xnor() { + let mut cpu = Cpu::new(&[0b11101001]); + cpu.R0 = Wrapping(0b10110111); + cpu.R1 = Wrapping(0b10001100); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b11000100); // destination data is correct + assert_eq!(cpu.R1.0, 0b10001100); // R1 is not modified + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_complement_r0() { + let mut cpu = Cpu::new(&[0b11101010]); + cpu.R0 = Wrapping(0b10101010); + cpu.R1 = Wrapping(0x55); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b01010101); // destination data is correct + assert_eq!(cpu.R1.0, 0x55); // R1 is not modified + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_complement_r1() { + let mut cpu = Cpu::new(&[0b11101011]); + cpu.R0 = Wrapping(0x55); + cpu.R1 = Wrapping(0b10101010); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0x55); // R0 is not modified + assert_eq!(cpu.R1.0, 0b01010101); // destination data is correct + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_bit_shift_right_var_zero() { + let mut cpu = Cpu::new(&[0b11101100]); + cpu.R0 = Wrapping(0b01110010); + cpu.R1 = Wrapping(0); + cpu.C = true; + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b01110010); // value is not changed + assert_eq!(cpu.R1.0, 0); // R1 is not modified + assert!(!cpu.C); // carry is unset + } + } + + #[test] + fn insn_bit_shift_right_var() { + let mut cpu = Cpu::new(&[0b11101100]); + cpu.R0 = Wrapping(0b01110010); + cpu.R1 = Wrapping(3); + cpu.C = true; + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b00001110); // value is shifted correctly + assert_eq!(cpu.R1.0, 3); // R1 is not modified + assert!(!cpu.C); // carry is unset + } + } + + #[test] + fn insn_bit_shift_right_var_overflow() { + let mut cpu = Cpu::new(&[0b11101100]); + cpu.R0 = Wrapping(0b01110010); + cpu.R1 = Wrapping(20); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0); // value is zeroed out + assert_eq!(cpu.R1.0, 20); // R1 is not modified + assert!(cpu.C); // carry is set + } + } + + #[test] + fn insn_bit_shift_left_var_zero() { + let mut cpu = Cpu::new(&[0b11101110]); + cpu.R0 = Wrapping(0b01110010); + cpu.R1 = Wrapping(0); + cpu.C = true; + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b01110010); // value is not changed + assert_eq!(cpu.R1.0, 0); // R1 is not modified + assert!(!cpu.C); // carry is unset + } + } + + #[test] + fn insn_bit_shift_left_var() { + let mut cpu = Cpu::new(&[0b11101110]); + cpu.R0 = Wrapping(0b01110010); + cpu.R1 = Wrapping(3); + cpu.C = true; + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b10010000); // value is shifted correctly + assert_eq!(cpu.R1.0, 3); // R1 is not modified + assert!(!cpu.C); // carry is unset + } + } + + #[test] + fn insn_bit_shift_left_var_overflow() { + let mut cpu = Cpu::new(&[0b11101110]); + cpu.R0 = Wrapping(0b01110010); + cpu.R1 = Wrapping(20); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0); // value is zeroed out + assert_eq!(cpu.R1.0, 20); // R1 is not modified + assert!(cpu.C); // carry is set + } + } + + #[test] + fn insn_bit_rotate_right_var_zero() { + let mut cpu = Cpu::new(&[0b11101101]); + cpu.R0 = Wrapping(0b01110010); + cpu.R1 = Wrapping(0); + cpu.C = true; + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b01110010); // value is not changed + assert_eq!(cpu.R1.0, 0); // R1 is not modified + assert!(!cpu.C); // carry is unset + } + } + + #[test] + fn insn_bit_rotate_right_var() { + let mut cpu = Cpu::new(&[0b11101101]); + cpu.R0 = Wrapping(0b01110010); + cpu.R1 = Wrapping(3); + cpu.C = true; + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b01001110); // value is rotated correctly + assert_eq!(cpu.R1.0, 3); // R1 is not modified + assert!(!cpu.C); // carry is unset + } + } + + #[test] + fn insn_bit_rotate_right_var_overflow() { + let mut cpu = Cpu::new(&[0b11101101]); + cpu.R0 = Wrapping(0b01110010); + cpu.R1 = Wrapping(20); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0); // value is zeroed out + assert_eq!(cpu.R1.0, 20); // R1 is not modified + assert!(cpu.C); // carry is set + } + } + + #[test] + fn insn_bit_rotate_left_var_zero() { + let mut cpu = Cpu::new(&[0b11101111]); + cpu.R0 = Wrapping(0b01110010); + cpu.R1 = Wrapping(0); + cpu.C = true; + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b01110010); // value is not changed + assert_eq!(cpu.R1.0, 0); // R1 is not modified + assert!(!cpu.C); // carry is unset + } + } + + #[test] + fn insn_bit_rotate_left_var() { + let mut cpu = Cpu::new(&[0b11101111]); + cpu.R0 = Wrapping(0b01110010); + cpu.R1 = Wrapping(3); + cpu.C = true; + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b10010011); // value is rotated correctly + assert_eq!(cpu.R1.0, 3); // R1 is not modified + assert!(!cpu.C); // carry is unset + } + } + + #[test] + fn insn_bit_rotate_left_var_overflow() { + let mut cpu = Cpu::new(&[0b11101111]); + cpu.R0 = Wrapping(0b01110010); + cpu.R1 = Wrapping(20); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0); // value is zeroed out + assert_eq!(cpu.R1.0, 20); // R1 is not modified + assert!(cpu.C); // carry is set + } + } + + #[test] + fn insn_copy_from_r0() { + let mut cpu = Cpu::new(&[0b11110000]); + cpu.R0 = Wrapping(34); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 34); // R0 is not changed + assert_eq!(cpu.R1.0, 34); // destination value is correct + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_copy_from_r1() { + let mut cpu = Cpu::new(&[0b11110001]); + cpu.R1 = Wrapping(34); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 34); // destination value is correct + assert_eq!(cpu.R1.0, 34); // R1 is not changed + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_setc_true() { + let mut cpu = Cpu::new(&[0b11110011]); + cpu.C = false; + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert!(cpu.C); // carry is set + } + } + + #[test] + fn insn_setc_false() { + let mut cpu = Cpu::new(&[0b11110010]); + cpu.C = true; + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert!(!cpu.C); // carry is unset + } + } + + #[test] + fn insn_nop() { + let mut cpu = Cpu::new(&[0b11110100]); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + } + } + + #[test] + fn insn_halt() { + let mut cpu = Cpu::new(&[0b11110101]); + { + assert!(cpu.step()); // CPU has halted + } + } + + #[test] + fn insn_cload_r0() { + let mut cpu = Cpu::new(&[0b11110110]); + cpu.code[26] = 23; + cpu.R0 = Wrapping(26); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 23); // value is loaded + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_cload_r1() { + let mut cpu = Cpu::new(&[0b11110111]); + cpu.code[26] = 23; + cpu.R0 = Wrapping(26); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R1.0, 23); // value is loaded + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_zero_r0() { + let mut cpu = Cpu::new(&[0b11111000]); + cpu.R0 = Wrapping(1); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0); // R0 is zeroed out + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_zero_r1() { + let mut cpu = Cpu::new(&[0b11111001]); + cpu.R1 = Wrapping(1); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R1.0, 0); // R1 is zeroed out + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_div() { + let mut cpu = Cpu::new(&[0b11111010]); + cpu.R0 = Wrapping(100); + cpu.R1 = Wrapping(23); + cpu.C = true; + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 4); // quotient is valid + assert_eq!(cpu.R1.0, 8); // remainder is valid + assert!(!cpu.C); // carry is unset + } + } + + #[test] + fn insn_div_zero() { + let mut cpu = Cpu::new(&[0b11111010]); + cpu.R0 = Wrapping(100); + cpu.R1 = Wrapping(0); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0xFF); // quotient is valid + assert_eq!(cpu.R1.0, 0); // remainder is valid + assert!(cpu.C); // carry is set + } + } + + #[test] + fn insn_mul() { + let mut cpu = Cpu::new(&[0b11111011]); + cpu.R0 = Wrapping(0xC2); + cpu.R1 = Wrapping(0xA7); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0x7E); // R0 value is valid + assert_eq!(cpu.R1.0, 0x8E); // R1 value is valid + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_swap() { + let mut cpu = Cpu::new(&[0b11111100]); + cpu.R0 = Wrapping(7); + cpu.R1 = Wrapping(42); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 42); // R0 value is valid + assert_eq!(cpu.R1.0, 7); // R1 value is valid + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_port_ready_false() { + let mut cpu = Cpu::new(&[0b11111101]); + cpu.C = true; + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert!(!cpu.C); // carry is unset + } + } + + #[test] + fn insn_port_ready_true() { + let mut cpu = Cpu::new(&[0b11111101]); + cpu.port_in.push_back(0x23); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert!(cpu.C); // carry is set + } + } + + #[test] + fn insn_read_port_empty() { + let mut cpu = Cpu::new(&[0b11111110]); + cpu.R0 = Wrapping(1); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0); // R0 is zeroed out + assert!(!cpu.C); // carry is not affected + } + } + + #[test] + fn insn_read_port() { + let mut cpu = Cpu::new(&[0b11111110]); + cpu.port_in.push_back(0x23); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0x23); // value is correct + assert!(!cpu.C); // carry is not affected + assert_eq!(cpu.port_in.len(), 0); // port is empty + } + } + + #[test] + fn insn_write_port() { + let mut cpu = Cpu::new(&[0b11111111]); + cpu.R0 = Wrapping(33); + { + assert!(!cpu.step()); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 33); // R0 is not affected + assert!(!cpu.C); // carry is not affected + assert_eq!(cpu.port_out.back(), Some(&33)); // data is in port + } + } +} diff --git a/toy_cpu_4bit/src/instruction.rs b/toy_cpu_4bit/src/instruction.rs new file mode 100644 index 0000000..139dc25 --- /dev/null +++ b/toy_cpu_4bit/src/instruction.rs @@ -0,0 +1,354 @@ +// SPDX-License-Identifier: MIT +// Copyright Murad Karammaev, Nikita Kuzmin + +use ux::{u2, u3, u4}; + +#[derive(Debug, PartialEq, Clone)] +pub enum Register { + R0, + R1, +} + +#[derive(Debug, PartialEq, Clone)] +pub enum ShiftMode { + Logical, + Circular, +} + +#[derive(Debug, PartialEq)] +pub enum Instruction { + Load { + reg: Register, + addr: u4, + }, + Store { + reg: Register, + addr: u4, + }, + LoadImmediate { + low: bool, + reg: Register, + val: u4, + }, + NearJumpBackward { + use_carry: bool, + offset: u2, + }, + NearJumpForward { + use_carry: bool, + offset: u3, + }, + FarJump { + reg: Register, + use_carry: bool, + }, + CmpEqual, + CmpGreater, + CmpLess, + Not, + BitShift { + right: bool, + reg: Register, + mode: ShiftMode, + len: u3, + }, + Inc { + reg: Register, + }, + Dec { + reg: Register, + }, + Add, + Sub, + And, + Or, + Xor, + Xnor, + Complement { + reg: Register, + }, + BitShiftVar { + right: bool, + mode: ShiftMode, + }, + Copy { + reg_from: Register, + }, + Setc { + carry: bool, + }, + Nop, + Halt, + Cload { + reg: Register, + }, + Zero { + reg: Register, + }, + Div, + Mul, + Swap, + PortReady, + ReadPort, + WritePort, +} + +pub fn decode(insn: u8) -> Instruction { + match insn { + 0b00000000..=0b00111111 => { + let reg = if insn & 0b00010000 == 0 { + Register::R0 + } else { + Register::R1 + }; + let addr = u4::new(insn & 0b00001111); + match insn & 0b00100000 { + 0 => Instruction::Load { reg, addr }, + _ => Instruction::Store { reg, addr }, + } + } + 0b01000000..=0b01111111 => Instruction::LoadImmediate { + low: insn & 0b00100000 == 0, + reg: if insn & 0b00010000 == 0 { + Register::R0 + } else { + Register::R1 + }, + val: u4::new(insn & 0b00001111), + }, + 0b10001000..=0b10001111 => Instruction::NearJumpBackward { + use_carry: insn & 0b00000100 != 0, + offset: u2::new(insn & 0b00000011), + }, + 0b10010000..=0b10011111 => Instruction::NearJumpForward { + use_carry: insn & 0b00001000 != 0, + offset: u3::new(insn & 0b00000111), + }, + 0b10000000..=0b10000011 => Instruction::FarJump { + reg: if insn & 0b00000010 == 0 { + Register::R0 + } else { + Register::R1 + }, + use_carry: insn & 0b00000001 != 0, + }, + 0b10000100 => Instruction::CmpEqual, + 0b10000101 => Instruction::CmpGreater, + 0b10000110 => Instruction::CmpLess, + 0b10000111 => Instruction::Not, + 0b10100000..=0b10111111 => Instruction::BitShift { + right: true, + reg: if insn & 0b00010000 == 0 { + Register::R0 + } else { + Register::R1 + }, + mode: if insn & 0b00001000 == 0 { + ShiftMode::Logical + } else { + ShiftMode::Circular + }, + len: u3::new(insn & 0b00000111), + }, + 0b11000000..=0b11011111 => Instruction::BitShift { + right: false, + reg: if insn & 0b00010000 == 0 { + Register::R0 + } else { + Register::R1 + }, + mode: if insn & 0b00001000 == 0 { + ShiftMode::Logical + } else { + ShiftMode::Circular + }, + len: u3::new(insn & 0b00000111), + }, + 0b11100000..=0b11100001 => Instruction::Inc { + reg: if insn & 0b00000001 == 0 { + Register::R0 + } else { + Register::R1 + }, + }, + 0b11100010..=0b11100011 => Instruction::Dec { + reg: if insn & 0b00000001 == 0 { + Register::R0 + } else { + Register::R1 + }, + }, + 0b11100100 => Instruction::Add, + 0b11100101 => Instruction::Sub, + 0b11100110 => Instruction::And, + 0b11100111 => Instruction::Or, + 0b11101000 => Instruction::Xor, + 0b11101001 => Instruction::Xnor, + 0b11101010..=0b11101011 => Instruction::Complement { + reg: if insn & 0b00000001 == 0 { + Register::R0 + } else { + Register::R1 + }, + }, + 0b11101100..=0b11101111 => Instruction::BitShiftVar { + right: insn & 0b00000010 == 0, + mode: if insn & 0b00000001 == 0 { + ShiftMode::Logical + } else { + ShiftMode::Circular + }, + }, + 0b11110000..=0b11110001 => Instruction::Copy { + reg_from: if insn & 0b00000001 == 0 { + Register::R0 + } else { + Register::R1 + }, + }, + 0b11110010..=0b11110011 => Instruction::Setc { + carry: insn & 0b00000001 != 0, + }, + 0b11110100 => Instruction::Nop, + 0b11110101 => Instruction::Halt, + 0b11110110..=0b11110111 => Instruction::Cload { + reg: if insn & 0b00000001 == 0 { + Register::R0 + } else { + Register::R1 + }, + }, + 0b11111000..=0b11111001 => Instruction::Zero { + reg: if insn & 0b00000001 == 0 { + Register::R0 + } else { + Register::R1 + }, + }, + 0b11111010 => Instruction::Div, + 0b11111011 => Instruction::Mul, + 0b11111100 => Instruction::Swap, + 0b11111101 => Instruction::PortReady, + 0b11111110 => Instruction::ReadPort, + 0b11111111 => Instruction::WritePort, + } +} + +pub fn encode(insn: Instruction) -> u8 { + match insn { + Instruction::Load { reg, addr } => match reg { + Register::R0 => u8::from(addr), + Register::R1 => u8::from(addr) | 0b00010000, + }, + Instruction::Store { reg, addr } => match reg { + Register::R0 => 0b00100000 | u8::from(addr), + Register::R1 => 0b00110000 | u8::from(addr), + }, + Instruction::LoadImmediate { low, reg, val } => { + let mut ret = 0b01000000u8; + ret |= match low { + true => 0, + false => 0b00100000, + }; + ret |= match reg { + Register::R0 => 0, + Register::R1 => 0b00010000, + }; + ret | u8::from(val) + } + Instruction::NearJumpBackward { use_carry, offset } => match use_carry { + false => 0b10001000 | u8::from(offset), + true => 0b10001100 | u8::from(offset), + }, + Instruction::NearJumpForward { use_carry, offset } => match use_carry { + false => 0b10010000 | u8::from(offset), + true => 0b10011000 | u8::from(offset), + }, + Instruction::FarJump { reg, use_carry } => match reg { + Register::R0 => 0b10000000 | if use_carry { 1 } else { 0 }, + Register::R1 => 0b10000010 | if use_carry { 1 } else { 0 }, + }, + Instruction::CmpEqual => 0b10000100, + Instruction::CmpGreater => 0b10000101, + Instruction::CmpLess => 0b10000110, + Instruction::Not => 0b10000111, + Instruction::BitShift { + right, + reg, + mode, + len, + } => { + let mut ret = if right { 0b10100000 } else { 0b11000000 }; + ret |= match reg { + Register::R0 => 0, + Register::R1 => 0b00010000, + }; + ret |= match mode { + ShiftMode::Logical => 0, + ShiftMode::Circular => 0b00001000, + }; + ret | u8::from(len) + } + Instruction::Inc { reg } => match reg { + Register::R0 => 0b11100000, + Register::R1 => 0b11100001, + }, + Instruction::Dec { reg } => match reg { + Register::R0 => 0b11100010, + Register::R1 => 0b11100011, + }, + Instruction::Add => 0b11100100, + Instruction::Sub => 0b11100101, + Instruction::And => 0b11100110, + Instruction::Or => 0b11100111, + Instruction::Xor => 0b11101000, + Instruction::Xnor => 0b11101001, + Instruction::Complement { reg } => match reg { + Register::R0 => 0b11101010, + Register::R1 => 0b11101011, + }, + Instruction::BitShiftVar { right, mode } => { + let mut ret = 0b11101100; + ret |= if right { 0 } else { 0b00000010 }; + ret |= match mode { + ShiftMode::Logical => 0, + ShiftMode::Circular => 1, + }; + ret + } + Instruction::Copy { reg_from } => match reg_from { + Register::R0 => 0b11110000, + Register::R1 => 0b11110001, + }, + Instruction::Setc { carry } => 0b11110010 | u8::from(carry), + Instruction::Nop => 0b11110100, + Instruction::Halt => 0b11110101, + Instruction::Cload { reg } => match reg { + Register::R0 => 0b11110110, + Register::R1 => 0b11110111, + }, + Instruction::Zero { reg } => match reg { + Register::R0 => 0b11111000, + Register::R1 => 0b11111001, + }, + Instruction::Div => 0b11111010, + Instruction::Mul => 0b11111011, + Instruction::Swap => 0b11111100, + Instruction::PortReady => 0b11111101, + Instruction::ReadPort => 0b11111110, + Instruction::WritePort => 0b11111111, + } +} + +// This crate really needs more tests, but I can't be bothered +#[cfg(test)] +mod tests { + use super::{decode, encode}; + + #[test] + fn deterministic_instruction_encoding() { + for i in 0..=255u8 { + assert_eq!(i, encode(decode(i))); + } + } +} diff --git a/toy_cpu_4bit/src/lib.rs b/toy_cpu_4bit/src/lib.rs new file mode 100644 index 0000000..520cd80 --- /dev/null +++ b/toy_cpu_4bit/src/lib.rs @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +// Copyright Murad Karammaev, Nikita Kuzmin + +pub mod assembler; +pub mod cpu; +pub mod instruction; + +#[cfg(test)] +mod tests { + use crate::{assembler::Assembler, cpu::Cpu}; + use std::num::Wrapping; + + #[test] + fn integration_assemble_execute() { + let code = r#" + MOV R0.l, 0xF # R0 = 15 + MOV R1.l, 0x3 # R1 = 3 + ADD # R0 += R1 (18) + MOV [0], R0 # MEM[0] = R0 (18) + ZERO R0 # R0 = 0 + HALT # EXIT + "#; + let code = Assembler::new().assemble(code).unwrap(); + let mut cpu = Cpu::new(&code); + for _ in 0..5 { + assert!(!cpu.step()); // CPU does not halt… + } + assert!(cpu.step()); // …until it reaches halt instruction + assert_eq!(cpu.IP, Wrapping(5)); // IP points at halt instruction + assert_eq!(cpu.R0, Wrapping(0)); + assert_eq!(cpu.R1, Wrapping(3)); + assert!(!cpu.C); + assert_eq!(cpu.data[0], 18); + for i in 1..=15 { + assert_eq!(cpu.data[i], 0); + } + } +} diff --git a/toyasm/Cargo.lock b/toyasm/Cargo.lock deleted file mode 100644 index 73f5bd3..0000000 --- a/toyasm/Cargo.lock +++ /dev/null @@ -1,286 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "aho-corasick" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" -dependencies = [ - "memchr", -] - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "clap" -version = "3.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47582c09be7c8b32c0ab3a6181825ababb713fde6fff20fc573a3870dd45c6a0" -dependencies = [ - "atty", - "bitflags", - "clap_derive", - "clap_lex", - "indexmap", - "lazy_static", - "strsim", - "termcolor", - "textwrap", -] - -[[package]] -name = "clap_derive" -version = "3.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3aab4734e083b809aaf5794e14e756d1c798d2c69c7f7de7a09a2f5214993c1" -dependencies = [ - "heck", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213" -dependencies = [ - "os_str_bytes", -] - -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" - -[[package]] -name = "heck" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "indexmap" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" -dependencies = [ - "autocfg", - "hashbrown", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.125" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b" - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "os_str_bytes" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro2" -version = "1.0.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9027b48e9d4c9175fa2218adf3557f91c1137021739951d4932f5f8268ac48aa" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "quote" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "regex" -version = "1.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.6.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" - -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - -[[package]] -name = "syn" -version = "1.0.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff7c592601f11445996a06f8ad0c27f094a58857c2f89e97974ab9235b92c52" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "termcolor" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "textwrap" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" - -[[package]] -name = "toy_cpu_4bit" -version = "0.1.0" -dependencies = [ - "lazy_static", - "regex", - "ux", -] - -[[package]] -name = "toyasm" -version = "0.1.0" -dependencies = [ - "clap", - "toy_cpu_4bit", -] - -[[package]] -name = "unicode-xid" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" - -[[package]] -name = "ux" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5efdcf885b33bb81bc9336e66cebc10d503288449466c0e43e51250ddc93a3d8" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/toyasm/Cargo.toml b/toyasm/Cargo.toml index 5fb2d6e..fef05d2 100644 --- a/toyasm/Cargo.toml +++ b/toyasm/Cargo.toml @@ -4,5 +4,5 @@ version = "0.1.0" edition = "2021" [dependencies] -toy_cpu_4bit = { version = "0.1.0", path = ".." } +toy_cpu_4bit = { version = "0.1.0", path = "../toy_cpu_4bit" } clap = { version = "3.1.17", features = ["derive"] } diff --git a/toyvm/Cargo.lock b/toyvm/Cargo.lock deleted file mode 100644 index b40e2a3..0000000 --- a/toyvm/Cargo.lock +++ /dev/null @@ -1,286 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "aho-corasick" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" -dependencies = [ - "memchr", -] - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "clap" -version = "3.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47582c09be7c8b32c0ab3a6181825ababb713fde6fff20fc573a3870dd45c6a0" -dependencies = [ - "atty", - "bitflags", - "clap_derive", - "clap_lex", - "indexmap", - "lazy_static", - "strsim", - "termcolor", - "textwrap", -] - -[[package]] -name = "clap_derive" -version = "3.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3aab4734e083b809aaf5794e14e756d1c798d2c69c7f7de7a09a2f5214993c1" -dependencies = [ - "heck", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213" -dependencies = [ - "os_str_bytes", -] - -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" - -[[package]] -name = "heck" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "indexmap" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" -dependencies = [ - "autocfg", - "hashbrown", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.125" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b" - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "os_str_bytes" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro2" -version = "1.0.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9027b48e9d4c9175fa2218adf3557f91c1137021739951d4932f5f8268ac48aa" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "quote" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "regex" -version = "1.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.6.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" - -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - -[[package]] -name = "syn" -version = "1.0.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff7c592601f11445996a06f8ad0c27f094a58857c2f89e97974ab9235b92c52" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "termcolor" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "textwrap" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" - -[[package]] -name = "toy_cpu_4bit" -version = "0.1.0" -dependencies = [ - "lazy_static", - "regex", - "ux", -] - -[[package]] -name = "toyvm" -version = "0.1.0" -dependencies = [ - "clap", - "toy_cpu_4bit", -] - -[[package]] -name = "unicode-xid" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" - -[[package]] -name = "ux" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5efdcf885b33bb81bc9336e66cebc10d503288449466c0e43e51250ddc93a3d8" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/toyvm/Cargo.toml b/toyvm/Cargo.toml index 2382388..6416646 100644 --- a/toyvm/Cargo.toml +++ b/toyvm/Cargo.toml @@ -4,5 +4,5 @@ version = "0.1.0" edition = "2021" [dependencies] -toy_cpu_4bit = { version = "0.1.0", path = ".." } +toy_cpu_4bit = { version = "0.1.0", path = "../toy_cpu_4bit" } clap = { version = "3.1.17", features = ["derive"] }