From 307953b8f5ef168ae40b831e9041daac816d0455 Mon Sep 17 00:00:00 2001 From: Murad Date: Mon, 2 May 2022 13:57:55 +0300 Subject: [PATCH 1/4] gitignore .idea/ --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2f7896d..96ef862 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ target/ +.idea/ From e51151ad04254419735c14c44e9fc5bae841b682 Mon Sep 17 00:00:00 2001 From: Murad Date: Sat, 7 May 2022 21:14:11 +0300 Subject: [PATCH 2/4] implement assembler library with tests --- ASM-SYNTAX.md | 18 +- Cargo.lock | 40 +++ Cargo.toml | 2 + MISTAKES.md | 18 +- src/assembler.rs | 717 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/instruction.rs | 125 +++++++++- src/main.rs | 33 ++- 7 files changed, 933 insertions(+), 20 deletions(-) create mode 100644 src/assembler.rs diff --git a/ASM-SYNTAX.md b/ASM-SYNTAX.md index 0a5aec1..f9ffe06 100644 --- a/ASM-SYNTAX.md +++ b/ASM-SYNTAX.md @@ -33,8 +33,8 @@ instruction 'LE' 'NOT' shift - 'INC' - 'DEC' + 'INC' register + 'DEC' register 'ADD' 'SUB' 'AND' @@ -42,7 +42,7 @@ instruction 'XOR' 'XNOR' 'COMPL' register - 'SETC' digit(0, 1) + 'SETC' carry 'NOP' 'HALT' 'CLOAD' register @@ -54,6 +54,10 @@ instruction 'PRTRD' 'PRTWR' +carry + '0' + '1' + mov 'MOV' register ',' memory 'MOV' memory ',' register @@ -61,12 +65,10 @@ mov 'MOV' register ',' register jmp - 'JMP' digit(-4, 8) - 'JE' digit(-4, 8) - 'JNE' digit(-4, 8) + 'JMP' digit(-3, 7) + 'JMPC' digit(-3, 7) 'AJMP' register - 'AJE' register - 'AJNE' register + 'AJMPC' register shift 'SHR' diff --git a/Cargo.lock b/Cargo.lock index e92f4a3..34155ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,9 +3,49 @@ 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 = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +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 = "toy_cpu_4bit" version = "0.1.0" dependencies = [ + "lazy_static", + "regex", "ux", ] diff --git a/Cargo.toml b/Cargo.toml index 962f61b..902c4cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,3 +5,5 @@ edition = "2021" [dependencies] ux = "0.1.3" +regex = "1.5.4" +lazy_static = "1.4.0" diff --git a/MISTAKES.md b/MISTAKES.md index bc52033..d5c80a4 100644 --- a/MISTAKES.md +++ b/MISTAKES.md @@ -1,4 +1,4 @@ -# Mistakes +33# Mistakes ## Don't design opcodes in your head @@ -6,8 +6,24 @@ You WILL frick up and your binary encodings WILL overlap. You definitely need some tool to design instruction opcodes for anything more complicated than just 1-byte instructions. +Turned out this mistake propogates further when implementing +assembler. + ## Load/store instructions MUST be able to use register as address I have no idea how we didn't notice this mistake. I guess it is not too bad for a CPU with 16 bytes of memory, but for something bigger it is unacceptable. + +## Jump instructions need more love + +Jumping 3 instructions back and 7 forward is too limiting. +Seriously, that's stupid. + +Also, in my current design, jumping offset of 0 will +move CPU to an infinite loop. + +## Implementing assembler with regex is not fancy + +It is probably easier to implement than writing proper +lexer/parser, but error reporting can be weird. diff --git a/src/assembler.rs b/src/assembler.rs new file mode 100644 index 0000000..2b48ba4 --- /dev/null +++ b/src/assembler.rs @@ -0,0 +1,717 @@ +// SPDX-License-Identifier: MIT +// Copyright Murad Karammaev, Nikita Kuzmin + +use crate::instruction::{decode, encode, Instruction, Register, ShiftMode}; +use regex::Regex; +use std::error::Error; +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) + }; +} + +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) -> Result<[u8; 0x100], Box> { + let mut ret = [0u8; 0x100]; + let mut i: usize = 0; + for line in input.lines().map(|line| line.trim()) { + match line { + "" => (), + line => { + if i > 0xFF { + return Err("Too much instructions".into()); + } + let insn = self.line_to_insn(line)?; + 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("Unknown instruction".into()) + } +} + +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(format!("{} is not in range [0, 255]", x).into()), + } +} + +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(format!("{} is not in range [-128, 127]", x).into()), + } +} + +fn parse_u4(src: &str) -> Result> { + match parse_u8(src)? { + x @ 0..=15 => Ok(u4::new(x)), + x => Err(format!("{} is not in range [0, 15]", x).into()), + } +} + +fn parse_u3(src: &str) -> Result> { + match parse_u8(src)? { + x @ 0..=7 => Ok(u3::new(x)), + x => Err(format!("{} is not in range [0, 7]", x).into()), + } +} + +fn parse_reg(src: &str) -> Result> { + match src { + "R0" => Ok(Register::R0), + "R1" => Ok(Register::R1), + x => Err(format!("'{}' is not a valid register", x).into()), + } +} + +fn parse_reg_half(src: &str) -> Result<(Register, bool), Box> { + 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(format!("'{}' is not a valid register half", x).into()), + } +} + +fn parse_carry(src: &str) -> Result> { + match src { + "0" => Ok(false), + "1" => Ok(true), + x => Err(format!("'{}' is an invalid carry value", x).into()), + } +} + +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("Malformed register to register copy".into()), + } +} + +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(format!("{} is not a valid relative jump offset", x).into()), + } +} + +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::Assembler; + use crate::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 } + ); + } + } +} diff --git a/src/instruction.rs b/src/instruction.rs index 5c32419..bd16169 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -3,19 +3,19 @@ use ux::{u2, u3, u4}; -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone)] pub enum Register { R0, R1, } -#[derive(Debug)] +#[derive(Debug, PartialEq, Clone)] pub enum ShiftMode { Logical, Circular, } -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum Instruction { Load { reg: Register, @@ -233,3 +233,122 @@ pub fn decode(insn: u8) -> Instruction { 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 crate::instruction::{decode, encode}; + + #[test] + fn deterministic_instruction_encoding() { + for i in 0..=255u8 { + assert_eq!(i, encode(decode(i))); + } + } +} diff --git a/src/main.rs b/src/main.rs index bd67f6b..7681981 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,20 +1,37 @@ // SPDX-License-Identifier: MIT // Copyright Murad Karammaev, Nikita Kuzmin +mod assembler; mod cpu; mod instruction; +use assembler::Assembler; use cpu::*; fn main() { - let mut cpu = Cpu::new(&[ - 0b01001111, // R0 = 0xF - 0b01010011, // R1.l = 0x3 - 0b11100100, // ADD - 0b00100000, // [0] = R0 - 0b11111000, // R0 = 0 - 0b11110101, // HALT - ]); + /* + 0b01001111, // R0 = 0xF + 0b01010011, // R1.l = 0x3 + 0b11100100, // ADD + 0b00100000, // [0] = R0 + 0b11111000, // R0 = 0 + 0b11110101, // HALT + */ + let asm = Assembler::new(); + let code = asm + .assemble( + r#" + MOV R0.h, 0x0 + MOV R0.l, 0xF + MOV R1.l, 0x3 + ADD + MOV [0], R0 + ZERO R0 + HALT + "#, + ) + .unwrap(); + let mut cpu = Cpu::new(&code); loop { if cpu.step() { break; From 05609d77451b012cfb87b4f10c263d2720a328e9 Mon Sep 17 00:00:00 2001 From: Murad Date: Sat, 7 May 2022 21:17:36 +0300 Subject: [PATCH 3/4] delete useless comment --- src/main.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/main.rs b/src/main.rs index 7681981..2e56219 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,14 +9,6 @@ use assembler::Assembler; use cpu::*; fn main() { - /* - 0b01001111, // R0 = 0xF - 0b01010011, // R1.l = 0x3 - 0b11100100, // ADD - 0b00100000, // [0] = R0 - 0b11111000, // R0 = 0 - 0b11110101, // HALT - */ let asm = Assembler::new(); let code = asm .assemble( From c680151984d9a0c2ac04c8fcc5d489af36f09a23 Mon Sep 17 00:00:00 2001 From: Murad Date: Sun, 8 May 2022 13:18:03 +0300 Subject: [PATCH 4/4] MISTAKES.md: oops: fix stupid typo --- MISTAKES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MISTAKES.md b/MISTAKES.md index d5c80a4..5feb673 100644 --- a/MISTAKES.md +++ b/MISTAKES.md @@ -1,4 +1,4 @@ -33# Mistakes +# Mistakes ## Don't design opcodes in your head