From 6c1cf0f62031b0f229c6e635033eb1166cc4384d Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Mon, 8 May 2023 17:59:54 -0400 Subject: [PATCH] assembler and examples --- Cargo.lock | 338 ++++++++++++++++++++++++++++++++ Cargo.toml | 2 + hello_world.asm | 45 ++++- hello_world.bin | Bin 0 -> 96 bytes nop.asm | 3 + nop.bin | Bin 0 -> 8 bytes simple_add.asm | 4 + simple_add.bin | Bin 0 -> 16 bytes smiley.asm | 11 ++ smiley.bin | Bin 0 -> 36 bytes src/asm.rs | 36 ++++ src/disasm.rs | 6 + src/main.rs | 86 +++++++- src/normalizer.rs | 74 +++++++ src/ops.rs | 8 +- src/opt_sim_asm.rs | 466 ++++++++++++++++++++++++++++++++++++++++++++ src/preprocessor.rs | 23 +++ 17 files changed, 1088 insertions(+), 14 deletions(-) create mode 100644 hello_world.bin create mode 100644 nop.asm create mode 100644 nop.bin create mode 100644 simple_add.asm create mode 100644 simple_add.bin create mode 100644 smiley.asm create mode 100644 smiley.bin create mode 100644 src/asm.rs create mode 100644 src/disasm.rs create mode 100644 src/normalizer.rs create mode 100644 src/opt_sim_asm.rs create mode 100644 src/preprocessor.rs diff --git a/Cargo.lock b/Cargo.lock index 343a2c6..4cbd224 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,344 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "anstream" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" + +[[package]] +name = "anstyle-parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[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 = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "clap" +version = "4.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34d21f9bf1b425d2968943631ec91202fe5e837264063503708b83013f8fc938" +dependencies = [ + "clap_builder", + "clap_derive", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914c8c79fb560f238ef6429439a30023c862f7a28e688c58f7203f12b29970bd" +dependencies = [ + "anstream", + "anstyle", + "bitflags", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "io-lifetimes" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys", +] + +[[package]] +name = "is-terminal" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +dependencies = [ + "hermit-abi", + "io-lifetimes", + "rustix", + "windows-sys", +] + +[[package]] +name = "libc" +version = "0.2.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" + +[[package]] +name = "linux-raw-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "parse_int" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d695b79916a2c08bcff7be7647ab60d1402885265005a6658ffe6d763553c5a" +dependencies = [ + "num-traits", +] + +[[package]] +name = "proc-macro2" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustix" +version = "0.37.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "2.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "tmcpu" version = "0.1.0" +dependencies = [ + "clap", + "parse_int", +] + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" diff --git a/Cargo.toml b/Cargo.toml index c68ab8e..bb20354 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +clap = { version = "4.2.7", features = ["derive"] } +parse_int = { version = "0.6.0" } \ No newline at end of file diff --git a/hello_world.asm b/hello_world.asm index 130785d..7c29b7c 100644 --- a/hello_world.asm +++ b/hello_world.asm @@ -1,15 +1,42 @@ +nop +nop + ldi ra, 0x01 ldi rb, 0x02 add rc, ra rb ; should be 3 out 0x02, rc ; displays 03 on the numeric display ldi ra, 0b10100000 ; load the num for red and blue into ra -mrm ra, 0x001 -mrm ra, 0x003 -mrm ra, 0x01e -mrm ra, 0x02e -mrm ra, 0x02f -mrm ra, 0x030 -mrm ra, 0x031 -mrm ra, 0x023 -; Draw a smiley face \ No newline at end of file +mrm 0x001, ra +mrm 0x003, ra +mrm 0x01e, ra +mrm 0x02e, ra +mrm 0x02f, ra +mrm 0x030, ra +mrm 0x031, ra +mrm 0x023, ra +; Draw a smiley face + +; Test the normalizer +ldi ra, 0b10100101 +lsh rb, ra +; Expands to: +; add rb, ra ra + +cmp ra rb +; Expands to: +; sub ra, ra rb + +cpy rc, rb +; Expands to: +; ldi r0, 0 +; add rc, rb r0 + +not rd, rc +; Expands to: +; ldi r0, 0 +; nor rd, rc r0 + +; test validation +in ra, 0x02 +out 0x03, rb \ No newline at end of file diff --git a/hello_world.bin b/hello_world.bin new file mode 100644 index 0000000000000000000000000000000000000000..fc35449afff1e1581892dcb274673104fc80795e GIT binary patch literal 96 zcmWlRu?>JQ3L#p(4|+yF2FXM(GsZ;HA~ n{1ZmZK{2M=C_l}Z!{_i)oEQ2+ypzJC(f8=YJ_pb!sG9KsMxr1} literal 0 HcmV?d00001 diff --git a/nop.asm b/nop.asm new file mode 100644 index 0000000..e7733b8 --- /dev/null +++ b/nop.asm @@ -0,0 +1,3 @@ +nop +nop +; comments! \ No newline at end of file diff --git a/nop.bin b/nop.bin new file mode 100644 index 0000000000000000000000000000000000000000..b1bcccb1ec881fb9112b06871faf0a566620aed2 GIT binary patch literal 8 Ncmey*z`*byhye@y0sjC1 literal 0 HcmV?d00001 diff --git a/simple_add.asm b/simple_add.asm new file mode 100644 index 0000000..731ea97 --- /dev/null +++ b/simple_add.asm @@ -0,0 +1,4 @@ +ldi ra, 0x01 ; set ra = 0x01 +ldi rb, 0x02 ; set rb = 0x02 +add rc, ra rb ; set rc = ra + rb +out 0x02, rc ; send rc to the numerical display \ No newline at end of file diff --git a/simple_add.bin b/simple_add.bin new file mode 100644 index 0000000000000000000000000000000000000000..9358e6807587e6c92eed3342572e98f25443723e GIT binary patch literal 16 XcmeySz{v2;fr;U7iUY&@1SSRmFunys literal 0 HcmV?d00001 diff --git a/smiley.asm b/smiley.asm new file mode 100644 index 0000000..9074c5f --- /dev/null +++ b/smiley.asm @@ -0,0 +1,11 @@ +ldi ra, 0b10100000 ; load the num for red and blue into ra +mrm 0x001, ra +mrm 0x003, ra +mrm 0x01e, ra +mrm 0x02e, ra +mrm 0x02f, ra +mrm 0x030, ra +mrm 0x031, ra +mrm 0x023, ra +; Draw a teal smiley face in the top left corner of the framebuffer +; See the spreadsheet (Memory Mapped Framebuffer) for the chart I used to get these values \ No newline at end of file diff --git a/smiley.bin b/smiley.bin new file mode 100644 index 0000000000000000000000000000000000000000..0ca2bfd754140785c272654730455c38ff4fbcf3 GIT binary patch literal 36 lcmeySuz=x<0)qe$8+=h Result<(), Box> { + let mut simple_sim = SimpleAssemblySimulator { + rom: [0u8; 4096], + ram: [0u8; 4096], + r0: 0, + ra: 0, + rb: 0, + rc: 0, + rd: 0, + re: 0, + pc: 0, + flg: 0, + }; + + let mut asm = vec![]; + + for (line_no, line) in processed.lines().enumerate() { + match simple_sim.line(line) { + Ok(mut d) => asm.append(&mut d), + Err(e) => { + eprintln!("{} | {}", line_no, line); + eprintln!("Error on {}: {}", line_no, e); + return Err(e)?; + } + } + } + + fs::write(out, asm)?; + + Ok(()) +} \ No newline at end of file diff --git a/src/disasm.rs b/src/disasm.rs new file mode 100644 index 0000000..43c1f80 --- /dev/null +++ b/src/disasm.rs @@ -0,0 +1,6 @@ +use std::error::Error; +use std::path::PathBuf; + +pub fn disassemble(file: PathBuf, out: PathBuf) -> Result<(), Box> { + Ok(()) +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 38a0227..dc090c4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,89 @@ +use std::path::PathBuf; +use clap::{Parser, ArgAction}; +use crate::asm::assemble; +use crate::disasm::disassemble; +use crate::normalizer::normalize; +use crate::preprocessor::preprocess; + pub mod ops; +pub mod disasm; +pub mod normalizer; +pub mod asm; +pub mod preprocessor; +pub mod opt_sim_asm; + +#[derive(Parser)] +pub struct Cli { + // The input assembly file to assemble + pub input: PathBuf, + // The file to write the output to + pub output: PathBuf, + /// Only run the normalizer, and write the normalized code to the output file instead. + #[clap(short, long, action = ArgAction::SetTrue)] + pub normalize_only: bool, + /// Only run up to the preprocessor, and write the normalized and preprocessed code to the output file instead. + #[clap(short, long, action = ArgAction::SetTrue)] + pub preprocess_only: bool, + #[clap(short, long, action = ArgAction::SetTrue)] + pub disassemble: bool +} fn main() { - println!("Hello, world!"); + let args = Cli::parse(); + + if args.disassemble { + match disassemble(args.input.clone(), args.output) { + Ok(_) => std::process::exit(0), + Err(e) => { + eprintln!("{}", e); + std::process::exit(1); + } + } + } + + let normalized = match normalize(args.input) { + Ok(s) => s, + Err(e) => { + eprintln!("Normalizer error: {}", e); + std::process::exit(1); + } + }; + + if args.normalize_only { + match std::fs::write(args.output, normalized) { + Ok(s) => s, + Err(e) => { + eprintln!("Error writing output: {}", e); + std::process::exit(1); + } + } + std::process::exit(0); + } + + let preprocessed = match preprocess(normalized) { + Ok(s) => s, + Err(e) => { + eprintln!("Preprocessor error: {}", e); + std::process::exit(1); + } + }; + + if args.preprocess_only { + match std::fs::write(args.output, preprocessed) { + Ok(s) => s, + Err(e) => { + eprintln!("Error writing output: {}", e); + std::process::exit(1); + } + } + std::process::exit(0); + } + + match assemble(preprocessed, args.output) { + Ok(s) => s, + Err(e) => { + eprintln!("Error assembling: {}", e); + std::process::exit(1); + } + } } diff --git a/src/normalizer.rs b/src/normalizer.rs new file mode 100644 index 0000000..f5a5fa3 --- /dev/null +++ b/src/normalizer.rs @@ -0,0 +1,74 @@ +use std::error::Error; +use std::fs; +use std::path::PathBuf; + +pub fn normalize(file: PathBuf) -> Result> { + let file_str = fs::read_to_string(file)?; + + let mut normalized = "; This file has only been normalized. It has not been processed or optimized.\n; This code cannot yet directly be passed into the assembler.\n".to_string(); + normalized += "; t+normalizer:tmCPU t+preprocessor:none t+optimizer:none @generated\n"; + + for (line_num, line) in file_str.lines().enumerate() { + let line_split: Vec<&str> = line.split(' ').collect(); + + match line_split[0] { + "lsh" => { + if line_split.len() != 3 { + return Err(format!("line {}: expected exactly 2 arguments to 'lsh', instead got {}", line_num, line_split.len() - 1).into()); + } + let dest = line_split[1]; + if !dest.ends_with(',') { + return Err(format!("line {}: arg 1 to 'lsh' must end in ','", line_num).into()); + } + let dest = &dest[..dest.len()-1]; + let rg = line_split[2]; + + normalized += &format!("add {}, {} {} ; normalizer: originally '{}'\n", dest, rg, rg, line); + + }, + "cmp" => { + if line_split.len() != 3 { + return Err(format!("line {}: expected exactly 2 arguments to 'cmp', instead got {}", line_num, line_split.len() - 1).into()); + } + let a = line_split[1]; + let b = line_split[2]; + + normalized += &format!("sub r0, {} {} ; normalizer: originally '{}'\n", a, b, line); + }, + "cpy" => { + if line_split.len() != 3 { + return Err(format!("line {}: expected exactly 2 arguments to 'cpy', instead got {}", line_num, line_split.len() - 1).into()); + } + let dest = line_split[1]; + let a = line_split[2]; + + if !dest.ends_with(',') { + return Err(format!("line {}: arg 1 to 'lsh' must end in ','", line_num).into()); + } + + normalized += "ldi r0, 0 ; normalizer: originally below\n"; + normalized += &format!("add {}, {} r0 ; normalizer: originally '{}'\n", &dest[..dest.len()-1], a, line); + }, + "not" => { + if line_split.len() != 3 { + return Err(format!("line {}: expected exactly 2 arguments to 'not', instead got {}", line_num, line_split.len() - 1).into()); + } + let dest = line_split[1]; + let a = line_split[2]; + + if !dest.ends_with(',') { + return Err(format!("line {}: arg 1 to 'not' must end in ','", line_num).into()); + } + + normalized += "ldi r0, 0 ; normalizer: originally below\n"; + normalized += &format!("nor {}, {} r0 ; normalizer: originally '{}'\n", &dest[..dest.len()-1], a, line); + } + _ => { + normalized += line; + normalized += "\n"; + } + } + } + + Ok(normalized) +} \ No newline at end of file diff --git a/src/ops.rs b/src/ops.rs index b382b68..907a57f 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -1,8 +1,8 @@ pub fn nop() -> Vec { - vec![0b1111_1111] + vec![0b1111_1111, 0b0000_0000, 0b0000_0000, 0b0000_0000] } pub fn hcf() -> Vec { - vec![0b1111_1110] + vec![0b1111_1110, 0b0000_0000, 0b0000_0000, 0b0000_0000] } pub enum Register { @@ -183,12 +183,12 @@ mod tests { #[test] fn test_nop() { - assert_eq!(nop(), vec![0b1111_1111]); + assert_eq!(nop(), vec![0b1111_1111, 0, 0, 0]); } #[test] fn test_hcf() { - assert_eq!(hcf(), vec![0b1111_1110]); + assert_eq!(hcf(), vec![0b1111_1110, 0, 0, 0]); } #[test] diff --git a/src/opt_sim_asm.rs b/src/opt_sim_asm.rs new file mode 100644 index 0000000..46df26c --- /dev/null +++ b/src/opt_sim_asm.rs @@ -0,0 +1,466 @@ +use std::error::Error; +use crate::ops::{add, and, hcf, ldi, mmr, mrm, nop, nor, or, out, r#in, Register, rmmr, rsh, sub, xor}; + +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct SimpleAssemblySimulator { + pub rom: [u8; 4096], + pub ram: [u8; 4096], + + pub r0: u8, + pub ra: u8, + pub rb: u8, + pub rc: u8, + pub rd: u8, + pub re: u8, + pub pc: u16, + pub flg: u8 +} +impl SimpleAssemblySimulator { + pub fn line(&mut self, line: &str) -> Result, Box> { + let line_split: Vec<&str> = line.split(' ').collect(); + + match line_split[0] { + "nop" => Ok(nop()), + "hcf" => Ok(hcf()), // ignored for the simulator + "add" => { + if line_split.len() != 4 { return Err("add requires exactly 3 arguments".into()) } + + let dest = line_split[1]; + if !dest.ends_with(',') { return Err("1st argument of add must end in , ".into()) } + let dest = to_reg(&dest[..dest.len() - 1])?; + let a = to_reg(line_split[2])?; + + let a_val = match a { + Register::R0 => self.r0, + Register::RA => self.ra, + Register::RB => self.rb, + Register::RC => self.rc, + Register::RD => self.rd, + Register::RE => self.re, + Register::PC => return Err("Cannot use program counter as a register".into()), + Register::FLG => self.flg + }; + + let b = to_reg(line_split[3])?; + + let b_val = match b { + Register::R0 => self.r0, + Register::RA => self.ra, + Register::RB => self.rb, + Register::RC => self.rc, + Register::RD => self.rd, + Register::RE => self.re, + Register::PC => return Err("Cannot use program counter as a register".into()), + Register::FLG => self.flg + }; + + let mut val = (a_val as u16) + (b_val as u16); + if val > u8::MAX as u16 { + val = u8::MAX as u16; // ignore overflow + } + + match dest { + Register::R0 => self.r0 = val as u8, + Register::RA => self.ra = val as u8, + Register::RB => self.rb = val as u8, + Register::RC => self.rc = val as u8, + Register::RD => self.rd = val as u8, + Register::RE => self.re = val as u8, + Register::PC => self.pc = (val as u8) as u16, + Register::FLG => self.flg = val as u8 + } + + Ok(add(dest, a, b)) + }, + "sub" => { + if line_split.len() != 4 { return Err("sub requires exactly 3 arguments".into()) } + + let dest = line_split[1]; + if !dest.ends_with(',') { return Err("1st argument of sub must end in , ".into()) } + let dest = to_reg(&dest[..dest.len() - 1])?; + let a = to_reg(line_split[2])?; + + let a_val = match a { + Register::R0 => self.r0, + Register::RA => self.ra, + Register::RB => self.rb, + Register::RC => self.rc, + Register::RD => self.rd, + Register::RE => self.re, + Register::PC => return Err("Cannot use program counter as a register".into()), + Register::FLG => self.flg + }; + + let b = to_reg(line_split[3])?; + + let b_val = match b { + Register::R0 => self.r0, + Register::RA => self.ra, + Register::RB => self.rb, + Register::RC => self.rc, + Register::RD => self.rd, + Register::RE => self.re, + Register::PC => return Err("Cannot use program counter as a register".into()), + Register::FLG => self.flg + }; + + let val; + if b_val > a_val { + val = 0; + } else { + val = a_val - b_val; + } + + match dest { + Register::R0 => self.r0 = val, + Register::RA => self.ra = val, + Register::RB => self.rb = val, + Register::RC => self.rc = val, + Register::RD => self.rd = val, + Register::RE => self.re = val, + Register::PC => self.pc = (val) as u16, + Register::FLG => self.flg = val + } + + Ok(sub(dest, a, b)) + }, + "or" => { + if line_split.len() != 4 { return Err("or requires exactly 3 arguments".into()) } + + let dest = line_split[1]; + if !dest.ends_with(',') { return Err("1st argument of or must end in , ".into()) } + let dest = to_reg(&dest[..dest.len() - 1])?; + let a = to_reg(line_split[2])?; + + let a_val = match a { + Register::R0 => self.r0, + Register::RA => self.ra, + Register::RB => self.rb, + Register::RC => self.rc, + Register::RD => self.rd, + Register::RE => self.re, + Register::PC => return Err("Cannot use program counter as a register".into()), + Register::FLG => self.flg + }; + + let b = to_reg(line_split[3])?; + + let b_val = match b { + Register::R0 => self.r0, + Register::RA => self.ra, + Register::RB => self.rb, + Register::RC => self.rc, + Register::RD => self.rd, + Register::RE => self.re, + Register::PC => return Err("Cannot use program counter as a register".into()), + Register::FLG => self.flg + }; + + match dest { + Register::R0 => self.r0 = a_val | b_val, + Register::RA => self.ra = a_val | b_val, + Register::RB => self.rb = a_val | b_val, + Register::RC => self.rc = a_val | b_val, + Register::RD => self.rd = a_val | b_val, + Register::RE => self.re = a_val | b_val, + Register::PC => self.pc = (a_val | b_val) as u16, + Register::FLG => self.flg = a_val | b_val + } + + Ok(or(dest, a, b)) + }, + "nor" => { + if line_split.len() != 4 { return Err("nor requires exactly 3 arguments".into()) } + + let dest = line_split[1]; + if !dest.ends_with(',') { return Err("1st argument of nor must end in , ".into()) } + let dest = to_reg(&dest[..dest.len() - 1])?; + let a = to_reg(line_split[2])?; + + let a_val = match a { + Register::R0 => self.r0, + Register::RA => self.ra, + Register::RB => self.rb, + Register::RC => self.rc, + Register::RD => self.rd, + Register::RE => self.re, + Register::PC => return Err("Cannot use program counter as a register".into()), + Register::FLG => self.flg + }; + + let b = to_reg(line_split[3])?; + + let b_val = match b { + Register::R0 => self.r0, + Register::RA => self.ra, + Register::RB => self.rb, + Register::RC => self.rc, + Register::RD => self.rd, + Register::RE => self.re, + Register::PC => return Err("Cannot use program counter as a register".into()), + Register::FLG => self.flg + }; + + match dest { + Register::R0 => self.r0 = !(a_val | b_val), + Register::RA => self.ra = !(a_val | b_val), + Register::RB => self.rb = !(a_val | b_val), + Register::RC => self.rc = !(a_val | b_val), + Register::RD => self.rd = !(a_val | b_val), + Register::RE => self.re = !(a_val | b_val), + Register::PC => self.pc = !(a_val | b_val) as u16, + Register::FLG => self.flg = !(a_val | b_val) + } + + Ok(nor(dest, a, b)) + }, + "and" => { + if line_split.len() != 4 { return Err("and requires exactly 3 arguments".into()) } + + let dest = line_split[1]; + if !dest.ends_with(',') { return Err("1st argument of and must end in , ".into()) } + let dest = to_reg(&dest[..dest.len() - 1])?; + let a = to_reg(line_split[2])?; + + let a_val = match a { + Register::R0 => self.r0, + Register::RA => self.ra, + Register::RB => self.rb, + Register::RC => self.rc, + Register::RD => self.rd, + Register::RE => self.re, + Register::PC => return Err("Cannot use program counter as a register".into()), + Register::FLG => self.flg + }; + + let b = to_reg(line_split[3])?; + + let b_val = match b { + Register::R0 => self.r0, + Register::RA => self.ra, + Register::RB => self.rb, + Register::RC => self.rc, + Register::RD => self.rd, + Register::RE => self.re, + Register::PC => return Err("Cannot use program counter as a register".into()), + Register::FLG => self.flg + }; + + match dest { + Register::R0 => self.r0 = a_val & b_val, + Register::RA => self.ra = a_val & b_val, + Register::RB => self.rb = a_val & b_val, + Register::RC => self.rc = a_val & b_val, + Register::RD => self.rd = a_val & b_val, + Register::RE => self.re = a_val & b_val, + Register::PC => self.pc = (a_val & b_val) as u16, + Register::FLG => self.flg = a_val & b_val + } + + Ok(and(dest, a, b)) + }, + "xor" => { + if line_split.len() != 4 { return Err("xor requires exactly 3 arguments".into()) } + + let dest = line_split[1]; + if !dest.ends_with(',') { return Err("1st argument of xor must end in , ".into()) } + let dest = to_reg(&dest[..dest.len() - 1])?; + let a = to_reg(line_split[2])?; + + let a_val = match a { + Register::R0 => self.r0, + Register::RA => self.ra, + Register::RB => self.rb, + Register::RC => self.rc, + Register::RD => self.rd, + Register::RE => self.re, + Register::PC => return Err("Cannot use program counter as a register".into()), + Register::FLG => self.flg + }; + + let b = to_reg(line_split[3])?; + + let b_val = match b { + Register::R0 => self.r0, + Register::RA => self.ra, + Register::RB => self.rb, + Register::RC => self.rc, + Register::RD => self.rd, + Register::RE => self.re, + Register::PC => return Err("Cannot use program counter as a register".into()), + Register::FLG => self.flg + }; + + match dest { + Register::R0 => self.r0 = a_val ^ b_val, + Register::RA => self.ra = a_val ^ b_val, + Register::RB => self.rb = a_val ^ b_val, + Register::RC => self.rc = a_val ^ b_val, + Register::RD => self.rd = a_val ^ b_val, + Register::RE => self.re = a_val ^ b_val, + Register::PC => self.pc = (a_val ^ b_val) as u16, + Register::FLG => self.flg = a_val ^ b_val + } + + Ok(xor(dest, a, b)) + }, + "rsh" => { + if line_split.len() != 3 { return Err("rsh requires exactly 2 arguments".into()) } + + let dest = line_split[1]; + if !dest.ends_with(',') { return Err("1st argument of rsh must end in , ".into()) } + let dest = to_reg(&dest[..dest.len() - 1])?; + let a = to_reg(line_split[2])?; + + let a_val = match a { + Register::R0 => self.r0, + Register::RA => self.ra, + Register::RB => self.rb, + Register::RC => self.rc, + Register::RD => self.rd, + Register::RE => self.re, + Register::PC => return Err("Cannot use program counter as a register".into()), + Register::FLG => self.flg + }; + + match dest { + Register::R0 => self.r0 = a_val >> 1, + Register::RA => self.ra = a_val >> 1, + Register::RB => self.rb = a_val >> 1, + Register::RC => self.rc = a_val >> 1, + Register::RD => self.rd = a_val >> 1, + Register::RE => self.re = a_val >> 1, + Register::PC => self.pc = (a_val >> 1) as u16, + Register::FLG => self.flg = a_val >> 1 + } + + Ok(rsh(dest, a)) + }, + "ldi" => { + if line_split.len() != 3 { return Err("rsh requires exactly 2 arguments".into()) } + + let dest = line_split[1]; + if !dest.ends_with(',') { return Err("1st argument of rsh must end in , ".into()) } + let dest = to_reg(&dest[..dest.len() - 1])?; + let data: u8 = parse_int::parse(line_split[2])?; + + match dest { + Register::R0 => self.r0 = data, + Register::RA => self.ra = data, + Register::RB => self.rb = data, + Register::RC => self.rc = data, + Register::RD => self.rd = data, + Register::RE => self.re = data, + Register::PC => self.pc = data as u16, + Register::FLG => self.flg = data + } + + Ok(ldi(dest, data)) + }, + "mmr" => { + if line_split.len() != 3 { return Err("mmr requires exactly 2 arguments".into()) } + + let dest = line_split[1]; + if !dest.ends_with(',') { return Err("1st argument of mmr must end in , ".into()) } + let dest = to_reg(&dest[..dest.len() - 1])?; + let address: u16 = parse_int::parse(line_split[2])?; + + match dest { + Register::R0 => self.r0 = self.ram[address as usize], + Register::RA => self.ra = self.ram[address as usize], + Register::RB => self.rb = self.ram[address as usize], + Register::RC => self.rc = self.ram[address as usize], + Register::RD => self.rd = self.ram[address as usize], + Register::RE => self.re = self.ram[address as usize], + Register::PC => self.pc = self.ram[address as usize] as u16, + Register::FLG => self.flg = self.ram[address as usize] + } + + Ok(mmr(dest, address)) + }, + "mrm" => { + if line_split.len() != 3 { return Err("mrm requires exactly 2 arguments".into()) } + + let dest = line_split[1]; + if !dest.ends_with(',') { return Err("1st argument of mrm must end in , ".into()) } + + let src = line_split[2]; + let src = to_reg(src)?; + + let address: u16 = parse_int::parse(&line_split[1][..line_split[1].len()-1])?; + + match src { + Register::R0 => self.ram[address as usize] = self.r0, + Register::RA => self.ram[address as usize] = self.ra, + Register::RB => self.ram[address as usize] = self.rb, + Register::RC => self.ram[address as usize] = self.rc, + Register::RD => self.ram[address as usize] = self.rd, + Register::RE => self.ram[address as usize] = self.re, + Register::PC => self.ram[address as usize] = self.pc.try_into().unwrap(), + Register::FLG => self.ram[address as usize] = self.flg + } + + Ok(mrm(src, address)) + }, + "in" => { + if line_split.len() != 3 { return Err("in requires exactly 2 arguments".into()) } + + let dest = line_split[1]; + if !dest.ends_with(',') { return Err("1st argument of in must end in , ".into()) } + let dest = to_reg(&dest[..dest.len() - 1])?; + let port: u8 = parse_int::parse(line_split[2])?; + + Ok(r#in(dest, port)) + }, + "out" => { + if line_split.len() != 3 { return Err("in requires exactly 2 arguments".into()) } + + let dest = line_split[1]; + if !dest.ends_with(',') { return Err("1st argument of in must end in , ".into()) } + + let src = line_split[2]; + let src = to_reg(src)?; + + let port: u8 = parse_int::parse(&line_split[1][..line_split[1].len()-1])?; + + Ok(out(src, port)) + }, + "rmmr" => { + if line_split.len() != 3 { return Err("rmmr requires exactly 2 arguments".into()) } + + let dest = line_split[1]; + if !dest.ends_with(',') { return Err("1st argument of rmmr must end in , ".into()) } + let dest = to_reg(&dest[..dest.len() - 1])?; + let address: u16 = parse_int::parse(line_split[2])?; + + match dest { + Register::R0 => self.r0 = self.ram[address as usize], + Register::RA => self.ra = self.ram[address as usize], + Register::RB => self.rb = self.ram[address as usize], + Register::RC => self.rc = self.ram[address as usize], + Register::RD => self.rd = self.ram[address as usize], + Register::RE => self.re = self.ram[address as usize], + Register::PC => self.pc = self.ram[address as usize] as u16, + Register::FLG => self.flg = self.ram[address as usize] + } + + Ok(rmmr(dest, address)) + } + _ => Err(format!("Unknown instruction {}", line_split[0]).into()) + } + } +} + +fn to_reg(s: &str) -> Result> { + match s { + "r0" => Ok(Register::R0), + "ra" => Ok(Register::RA), + "rb" => Ok(Register::RB), + "rc" => Ok(Register::RC), + "rd" => Ok(Register::RD), + "re" => Ok(Register::RE), + "pc" => Ok(Register::PC), + "flg" => Ok(Register::FLG), + _ => Err(format!("Unknown register {}", s).into()) + } +} \ No newline at end of file diff --git a/src/preprocessor.rs b/src/preprocessor.rs new file mode 100644 index 0000000..56468df --- /dev/null +++ b/src/preprocessor.rs @@ -0,0 +1,23 @@ +use std::error::Error; + +pub fn preprocess(normalized: String) -> Result> { + let mut processed = String::new(); + + for (_line_num, line) in normalized.lines().enumerate() { + let mut line_comments_removed = String::new(); + for char in line.chars() { + if char == ';' { + break; + } else { + line_comments_removed += char.to_string().as_str(); + } + } + + if !line_comments_removed.is_empty() { + processed += line_comments_removed.trim_start_matches(' ').trim_end_matches(' '); + processed += "\n"; + } + } + + Ok(processed) +} \ No newline at end of file