├── .github └── workflows │ ├── build.yml │ └── test.yml ├── .gitignore ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── assembler ├── Cargo.toml └── src │ ├── assembler.rs │ ├── encode.rs │ ├── error.rs │ ├── lib.rs │ ├── parse.rs │ └── test.rs ├── cli ├── Cargo.toml └── src │ ├── assertions.rs │ └── bin.rs ├── common ├── Cargo.toml └── src │ ├── bits.rs │ ├── constants.rs │ ├── instructions.rs │ ├── lib.rs │ └── util.rs ├── install-hooks.sh ├── isa-sim ├── Cargo.toml └── src │ ├── decode.rs │ ├── error.rs │ ├── exec.rs │ ├── lib.rs │ ├── test.rs │ ├── traits.rs │ └── util.rs ├── mcu ├── Cargo.toml ├── build-tests.sh ├── build.rs ├── programs │ ├── Makefile │ ├── link.ld │ └── mul │ │ ├── init.s │ │ ├── mul.c │ │ └── test_case.json └── src │ ├── lib.rs │ ├── memory.rs │ ├── register_file.rs │ └── test_runner.rs └── wasm ├── Cargo.toml ├── node ├── .gitignore ├── .travis.yml ├── LICENSE-APACHE ├── LICENSE-MIT ├── bootstrap.js ├── index.html ├── index.js ├── package.json └── webpack.config.js └── src ├── lib.rs └── logger.rs /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: [ main, development ] 6 | pull_request: 7 | branches: [ main, development ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Install pre-requisites 20 | run: rustup target add wasm32-unknown-unknown 21 | - name: Check 22 | run: make check 23 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: [ main, development ] 6 | pull_request: 7 | branches: [ main, development ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | test: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Install pre-requisites 20 | run: sudo apt install gcc-riscv64-unknown-elf binutils-riscv64-unknown-elf 21 | - name: Run tests 22 | run: make test 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/target 2 | **/Cargo.lock 3 | .vscode 4 | .vim 5 | *.o 6 | *.bin 7 | *.elf 8 | **/dump.txt 9 | /wasm/www/node_modules 10 | /wasm/www/dist 11 | /wasm/www/package-lock.json 12 | /wasm/pkg 13 | *.tar.gz 14 | wasm/node/node_modules 15 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "common", 5 | "isa-sim", 6 | "cli", 7 | "assembler", 8 | "mcu", 9 | "wasm", 10 | ] 11 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # This Dockerfile aims to emulate CI for local debugging. 2 | # It's also useful if you want to test/develop but do not 3 | # have a toolchain installed locally. 4 | 5 | from ubuntu:latest 6 | 7 | RUN apt update 8 | 9 | RUN \ 10 | DEBIAN_FRONTEND="noninteractive" \ 11 | apt install -y \ 12 | git cargo make gcc-riscv64-unknown-elf binutils-riscv64-unknown-elf 13 | 14 | RUN mkdir -p /repo 15 | COPY . /repo 16 | 17 | WORKDIR /repo 18 | ENTRYPOINT ["make", "test"] 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Trevor McKay 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL = bash 2 | 3 | build: 4 | cargo build --verbose 5 | 6 | test: 7 | cargo test --verbose 8 | 9 | release: 10 | cargo build --verbose --release 11 | 12 | clean: 13 | (cd isa-sim && cargo clean) 14 | (cd mcu && cargo clean) 15 | (cd assembler && cargo clean) 16 | (cd wasm && cargo clean) 17 | (cd common && cargo clean) 18 | (cd wasm && cargo clean) 19 | (cd wasm/node && rm -rf dist) 20 | rm -rf wasm/pkg 21 | shopt -s globstar && rm -f mcu/programs/**/{*.elf,*.bin,dump.txt} 22 | 23 | wasm: 24 | (cd wasm && wasm-pack build) 25 | 26 | app: 27 | (cd wasm && wasm-pack build) 28 | (cd wasm/node && npm install -q && npm run-script build) 29 | (cd wasm/node && tar czvf riscv-wasm-app.tar.gz dist && rm -rf dist) 30 | mv wasm/node/riscv-wasm-app.tar.gz . 31 | 32 | format: 33 | shopt -s globstar && rustfmt **/*.rs 34 | 35 | check: 36 | shopt -s globstar && rustfmt **/*.rs --check 37 | cargo check --release 38 | (cd isa-sim && cargo check --release --target wasm32-unknown-unknown) 39 | (cd mcu && cargo check --release --target wasm32-unknown-unknown) 40 | (cd assembler && cargo check --release --target wasm32-unknown-unknown) 41 | (cd wasm && cargo check --release --target wasm32-unknown-unknown) 42 | 43 | ci: clean 44 | docker build -t lib-rv32-test . 45 | docker run -it --rm lib-rv32-test . 46 | docker rmi lib-rv32-test 47 | 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lib-rv32 2 | 3 | ![build](https://github.com/trmckay/lib-rv32i/actions/workflows/build.yml/badge.svg) 4 | ![tests](https://github.com/trmckay/lib-rv32i/actions/workflows/test.yml/badge.svg) 5 | 6 | ## Overview 7 | 8 | lib-rv32 is a collection of Rust libraries for emulating, learning, and assembling 32-bit RISC-V 9 | integer ISAs. 10 | 11 | - [lib-rv32-isa](https://crates.io/crates/lib-rv32-isa): library for ISA simulation 12 | - [lib-rv32-mcu](https://crates.io/crates/lib-rv32-mcu): reference implemenation of an MCU used in conjunction with lib_rv32_isa 13 | - [lib-rv32-asm](https://crates.io/crates/lib-rv32-asm): library for assembling RISC-V programs 14 | - [lib-rv32-cli](https://crates.io/crates/lib-rv32-cli): CLI tool exposing the libraries 15 | - [lib-rv32-wasm](https://rvwasm.trmckay.com): An webapp using the library's WASM bindings. 16 | 17 | --- 18 | 19 | ## Libraries 20 | 21 | ### ISA simulator 22 | 23 | This library can execute instructions against any memory and register file that implements 24 | the required primitives in the traits `lib_rv32_common::traits::{Memory, RegisterFile}`. This is to 25 | encourage usage with whatever frontend you desire. 26 | 27 | However, reference implementations are provided in `lib_rv32_mcu::*`. The library provides 28 | functions to read from the memory, registers, and step a single instruction. Since, the 29 | user decides when to call these functions, these will probably fit most use-cases. 30 | 31 | ### MCU 32 | 33 | The MCU crate provides an implemenation of `Memory` and `RegisterFile` for use with the ISA 34 | simulator. With this, one can fully emulate an embedded RISC-V core. 35 | 36 | ### Assembler 37 | 38 | This crate can be used to assemble simple RISC-V assembly programs. The main functions offered 39 | by this library are: 40 | 41 | - `assemble_ir`: assemble an instruction `&str` to a `u32` 42 | - `assemble_program`: assemble a program `&str` to a `Vec` 43 | - `assemble_program_buf`: assemble a `BufRead` to a `Vec` 44 | 45 | 46 | ## CLI 47 | 48 | ### Emulator 49 | 50 | The primary use of the emulator is tracing execution of RISC-V programs and making assertions 51 | about their behavior. It currently only supports simple binary memory images 52 | (not ELF binaries). 53 | 54 | Enter assertions into a JSON file (note: all numbers are strings to allow for hex or decimal radices). 55 | 56 | `assert.json`: 57 | ```json 58 | { 59 | "registers": { 60 | "x0": "0x0", 61 | "a0": "20" 62 | }, 63 | "memory": { 64 | "0x0000": "0x00010117" 65 | } 66 | } 67 | ``` 68 | 69 | Then run: 70 | ``` 71 | lrv-cli -v ./prog.bin -s 24 -a assert.json 72 | ``` 73 | 74 | This will execute `prog.bin`, stop at the PC value 0x24, and then make the assertions from `assert.json`. 75 | 76 | The program will trace the execution instruction-by-instruction: 77 | ``` 78 | [0000] 00010117 | auipc sp, 0x10 | sp <- 0x10000 (65536); 79 | [0004] fe010113 | addi sp, sp, -32 | sp <- 0xffe0 (65504); 80 | [0008] 00400513 | addi a0, zero, 4 | a0 <- 0x4 (4); 81 | [000c] 00500593 | addi a1, zero, 5 | a1 <- 0x5 (5); 82 | [0010] 00000097 | auipc ra, 0x0 | ra <- 0x10 (16); 83 | [0014] 018080e7 | jalr ra, (24)ra | ra <- 0x18 (24); pc <- 0x28; 84 | ... 85 | ``` 86 | 87 | When complete, it will summarize results: 88 | ``` 89 | ... 90 | [001c] f0028293 | addi t0, t0, -256 | t0 <- 0xf00 (3840); 91 | [0020] 00a2a023 | sw a0, 0(t0) | (word *)0x00000f00 <- 0x14 (20); 92 | 93 | Reached stop-PC. 94 | 95 | a0 == 20 96 | *0x00000000 == 65815 97 | ``` 98 | 99 | ### Assembler 100 | 101 | The CLI also exposes the assembler via the command line. You can assemble the file 102 | `program.s` to `program.bin` using 103 | 104 | `lrv-cli -cv program.s -o program.bin` 105 | 106 | --- 107 | 108 | ## Testing 109 | 110 | This project has a very flexible testing system. 111 | 112 | Unit-tests are provided wherever appropriate. 113 | 114 | Additionally, to test the whole system, test programs can be added to `mcu/tests/programs`. 115 | A test is simply a directory containing `.c` and `.s` source files and a `test_case.json` 116 | consisting of assertions about the state of the MCU after the program is complete. 117 | 118 | During testing, Cargo will for each test: 119 | 120 | 1. Compile it for RISC-V 121 | 2. Spin up a new MCU 122 | 3. Program it with the generated binary 123 | 4. Run the test program for some number of cycles 124 | 5. Make assertions 125 | 6. Report succes or failure 126 | 127 | If a test fails, it will describe the error that caused the crash or the assertion that failed 128 | and print an object dump of the compiled test binary: 129 | 130 | ``` 131 | ... 132 | [001c] f0028293 | addi t0, t0, -256 | t0 <- 0xf00 (3840); 133 | [0020] 00a2a023 | sw a0, 0(t0) | (word *)0x00000f00 <- 0x14 (20); 134 | Stopping because the stop PC 0x24 was reached. 135 | 136 | 137 | Failed test: tests/programs/mul@0x00000024: Register assertion failed: (x10=0x00000014) != 0x00000018. 138 | 139 | prog.elf: file format elf32-littleriscv 140 | 141 | 142 | Disassembly of section .text.init: 143 | 144 | 00000000 : 145 | 0: 00010117 auipc sp,0x10 146 | 4: fe010113 addi sp,sp,-32 # ffe0 <__global_pointer$+0xf75c> 147 | 8: 00400513 li a0,4 148 | c: 00500593 li a1,5 149 | ... 150 | ``` 151 | 152 | Tests are run in CI, but can be run locally provided your system has `riscv(32|64)-unknown-elf-gcc`. 153 | -------------------------------------------------------------------------------- /assembler/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lib-rv32-asm" 3 | description = "An extension to lib_rv32 which provides an assembler." 4 | keywords = ["riscv", "testing", "simulator", "emulator", "assembler"] 5 | authors = ["Trevor McKay "] 6 | homepage = "https://github.com/trmckay/lib-rv32" 7 | repository = "https://github.com/trmckay/lib-rv32" 8 | version = "0.2.0" 9 | edition = "2018" 10 | license = "MIT" 11 | readme = "../README.md" 12 | 13 | [lib] 14 | name = "lib_rv32_asm" 15 | path = "src/lib.rs" 16 | 17 | [dependencies] 18 | log = "0.4.*" 19 | lib-rv32-common = "0.2.*" 20 | 21 | [dev-dependencies] 22 | lib-rv32-isa = "0.2.*" 23 | -------------------------------------------------------------------------------- /assembler/src/assembler.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | #[cfg(not(target_arch = "wasm32"))] 3 | use std::io::prelude::*; 4 | 5 | use log::info; 6 | 7 | use lib_rv32_common::constants::*; 8 | 9 | use crate::{ 10 | encode_b_imm, encode_func3, encode_func7, encode_i_imm, encode_j_imm, encode_opcode, encode_rd, 11 | encode_rs1, encode_rs2, encode_s_imm, encode_u_imm, error::AssemblerError, match_func3, 12 | match_func7, parse::*, tokenize, 13 | }; 14 | 15 | enum InstructionFormat { 16 | Itype, 17 | Rtype, 18 | Jtype, 19 | Utype, 20 | Stype, 21 | Btype, 22 | } 23 | 24 | /// Assemble a single instruction. 25 | /// 26 | /// Parameters: 27 | /// `ir_string: &str`: The instruction 28 | /// `labels: &mut std::collections::HashMap`: Map of labels 29 | /// `pc: u32` Current location of the program 30 | /// 31 | /// Returns: 32 | /// `Result>`: The assembled binary instruction, an error, or nothing. 33 | pub fn assemble_ir( 34 | ir_string: &str, 35 | labels: &mut HashMap, 36 | pc: u32, 37 | ) -> Result, AssemblerError> { 38 | let mut msg = String::new(); 39 | let mut ir: u32 = 0; 40 | 41 | let mut tokens: Vec = tokenize!(ir_string); 42 | 43 | if tokens.is_empty() { 44 | return Ok(None); 45 | } else if tokens.len() > 5 { 46 | return Err(AssemblerError::TooManyTokensError); 47 | } 48 | 49 | // Add and remove leading label. 50 | if tokens[0].ends_with(':') { 51 | labels.insert(tokens[0].strip_suffix(':').unwrap().to_owned(), pc); 52 | tokens.remove(0); 53 | } 54 | 55 | if tokens.is_empty() { 56 | return Ok(None); 57 | } 58 | 59 | msg += &format!("{:18} -> [{:02x}] ", ir_string, pc); 60 | 61 | let op = &tokens[0][..]; 62 | let opcode = match_opcode(op); 63 | if let Err(why) = opcode { 64 | return Err(why); 65 | } 66 | let opcode = opcode.unwrap(); 67 | ir |= encode_opcode!(opcode); 68 | 69 | // Use the opcode to identify the instruction format. 70 | let format = match opcode { 71 | OPCODE_ARITHMETIC_IMM | OPCODE_JALR | OPCODE_LOAD => InstructionFormat::Itype, 72 | OPCODE_ARITHMETIC => InstructionFormat::Rtype, 73 | OPCODE_JAL => InstructionFormat::Jtype, 74 | OPCODE_LUI | OPCODE_AUIPC => InstructionFormat::Utype, 75 | OPCODE_BRANCH => InstructionFormat::Btype, 76 | OPCODE_STORE => InstructionFormat::Stype, 77 | _ => unreachable!(), 78 | }; 79 | 80 | // Use the destination register field. 81 | if let InstructionFormat::Rtype | InstructionFormat::Itype | InstructionFormat::Utype = format { 82 | let rd = match_register(&tokens[1]); 83 | if let Err(why) = rd { 84 | return Err(why); 85 | } 86 | ir |= encode_rd!(rd.unwrap()); 87 | } 88 | 89 | // Use the first register operand and func3 fields. 90 | if let InstructionFormat::Itype 91 | | InstructionFormat::Rtype 92 | | InstructionFormat::Btype 93 | | InstructionFormat::Stype = format 94 | { 95 | let rs1 = match_register( 96 | &tokens[match opcode { 97 | OPCODE_LOAD => 3, 98 | OPCODE_BRANCH => 1, 99 | _ => 2, 100 | }], 101 | ); 102 | if let Err(why) = rs1 { 103 | return Err(why); 104 | } 105 | ir |= encode_rs1!(rs1.unwrap()); 106 | 107 | ir |= encode_func3!(match_func3!(op)); 108 | } 109 | 110 | // Use the second register operand field. 111 | if let InstructionFormat::Rtype | InstructionFormat::Stype | InstructionFormat::Btype = format { 112 | let rs2 = match_register( 113 | &tokens[match opcode { 114 | OPCODE_STORE => 1, 115 | OPCODE_BRANCH => 2, 116 | _ => 3, 117 | }], 118 | ); 119 | if let Err(why) = rs2 { 120 | return Err(why); 121 | } 122 | ir |= encode_rs2!(rs2.unwrap()); 123 | } 124 | 125 | // Use the func7 field. 126 | if let InstructionFormat::Rtype = format { 127 | ir |= encode_func7!(match_func7!(op)); 128 | } 129 | 130 | match format { 131 | InstructionFormat::Itype => { 132 | let imm = parse_imm( 133 | &tokens[match opcode { 134 | OPCODE_LOAD => 2, 135 | _ => 3, 136 | }], 137 | labels, 138 | pc, 139 | ); 140 | if let Err(why) = imm { 141 | return Err(why); 142 | } 143 | let imm = imm.unwrap(); 144 | ir |= encode_i_imm!(imm); 145 | } 146 | InstructionFormat::Utype => { 147 | let imm = parse_imm(&tokens[2], labels, pc); 148 | if let Err(why) = imm { 149 | return Err(why); 150 | } 151 | let imm = imm.unwrap(); 152 | ir |= encode_u_imm!(imm); 153 | } 154 | InstructionFormat::Jtype => { 155 | let imm = parse_imm(&tokens[2], labels, pc); 156 | if let Err(why) = imm { 157 | return Err(why); 158 | } 159 | let imm = imm.unwrap(); 160 | ir |= encode_j_imm!(imm); 161 | } 162 | InstructionFormat::Btype => { 163 | let imm = parse_imm(&tokens[3], labels, pc); 164 | if let Err(why) = imm { 165 | return Err(why); 166 | } 167 | let imm = imm.unwrap(); 168 | ir |= encode_b_imm!(imm); 169 | } 170 | InstructionFormat::Stype => { 171 | let imm = parse_imm(&tokens[2], labels, pc); 172 | if let Err(why) = imm { 173 | return Err(why); 174 | } 175 | let imm = imm.unwrap(); 176 | ir |= encode_s_imm!(imm); 177 | } 178 | InstructionFormat::Rtype => (), 179 | } 180 | 181 | msg += &format!("{:08x}", ir); 182 | info!("{}", msg); 183 | 184 | Ok(Some(ir)) 185 | } 186 | 187 | /// Assemble a `BufRead` down to a vector of words. The input should contain 188 | /// the entire program. 189 | #[cfg(not(target_arch = "wasm32"))] 190 | pub fn assemble_program_buf(reader: &mut R) -> Result, AssemblerError> 191 | where 192 | R: BufRead, 193 | { 194 | let mut prog = Vec::new(); 195 | let mut buf = String::new(); 196 | let mut labels = HashMap::new(); 197 | let mut pc: u32 = 0; 198 | 199 | loop { 200 | let bytes_rd = reader.read_line(&mut buf); 201 | 202 | if bytes_rd.is_err() { 203 | return Err(AssemblerError::IOError); 204 | } 205 | 206 | if bytes_rd.unwrap() == 0 { 207 | break; 208 | } 209 | 210 | let ir = assemble_ir(buf.trim_end(), &mut labels, pc); 211 | 212 | if let Err(why) = ir { 213 | return Err(why); 214 | } 215 | 216 | if let Some(i) = ir.unwrap() { 217 | prog.push(i); 218 | pc += 4; 219 | } 220 | buf.clear(); 221 | } 222 | 223 | Ok(prog) 224 | } 225 | 226 | /// Assemble a full program of newline-separated instructions. 227 | pub fn assemble_program(program: &str) -> Result, AssemblerError> { 228 | let mut prog = Vec::new(); 229 | let mut labels = HashMap::new(); 230 | let mut pc: u32 = 0; 231 | 232 | for line in program.split("\n") { 233 | let ir = assemble_ir(line, &mut labels, pc); 234 | 235 | if let Err(why) = ir { 236 | return Err(why); 237 | } 238 | 239 | if let Some(i) = ir.unwrap() { 240 | prog.push(i); 241 | pc += 4; 242 | } 243 | } 244 | 245 | Ok(prog) 246 | } 247 | -------------------------------------------------------------------------------- /assembler/src/encode.rs: -------------------------------------------------------------------------------- 1 | /// Encode an integer as a bitmask for the opcode. 2 | #[macro_export] 3 | macro_rules! encode_opcode { 4 | ($n:expr) => { 5 | (($n as u32) & 0b0111_1111) 6 | }; 7 | } 8 | 9 | /// Encode a register number as a bitmask for rd. 10 | #[macro_export] 11 | macro_rules! encode_rd { 12 | ($n:expr) => { 13 | (($n as u32) << 7) 14 | }; 15 | } 16 | 17 | /// Encode a register number as a bitmask for rs1. 18 | #[macro_export] 19 | macro_rules! encode_rs1 { 20 | ($n:expr) => { 21 | (($n as u32) << 15) 22 | }; 23 | } 24 | 25 | /// Encode a register number as a bitmask for rs2. 26 | #[macro_export] 27 | macro_rules! encode_rs2 { 28 | ($n:expr) => { 29 | (($n as u32) << 20) 30 | }; 31 | } 32 | 33 | /// Encode an integer as a bitmask for func3. 34 | #[macro_export] 35 | macro_rules! encode_func3 { 36 | ($n: expr) => { 37 | (($n as u32) << 12) 38 | }; 39 | } 40 | 41 | /// Encode an integer as a bitmask for func7. 42 | #[macro_export] 43 | macro_rules! encode_func7 { 44 | ($n: expr) => { 45 | (($n as u32) << 25) 46 | }; 47 | } 48 | 49 | /// Encode and integer as a bitmask for an I-type immediate. 50 | #[macro_export] 51 | macro_rules! encode_i_imm { 52 | ($n:expr) => {{ 53 | let n_bits = ($n & 0xFFF) as u32; 54 | let mut res: u32 = 0; 55 | res |= (n_bits as u32) << 20; 56 | let sign_bit = if (($n as u32) & lib_rv32_common::bit!(31)) != 0 { 57 | 1 58 | } else { 59 | 0 60 | }; 61 | res |= sign_bit << 31; 62 | res 63 | }}; 64 | } 65 | 66 | /// Encode and integer as a bitmask for a J-type immediate. 67 | #[macro_export] 68 | macro_rules! encode_j_imm { 69 | ($n:expr) => { 70 | (((($n as u32) & 0b10000000_00000000_00000000_00000000) << (31 - 31)) 71 | | ((($n as u32) & 0b00000000_00001111_11110000_00000000) << (12 - 12)) 72 | | ((($n as u32) & 0b00000000_00000000_00001000_00000000) << (20 - 11)) 73 | | ((($n as u32) & 0b00000000_00000000_00000111_11100000) << (25 - 5)) 74 | | ((($n as u32) & 0b00000000_00000000_00000000_00011110) << (21 - 1))) 75 | }; 76 | } 77 | 78 | /// Encode and integer as a bitmask for a U-type immediate. 79 | #[macro_export] 80 | macro_rules! encode_u_imm { 81 | ($n:expr) => { 82 | (($n as u32) << 12) 83 | }; 84 | } 85 | 86 | /// Encode and integer as a bitmask for an S-type immediate. 87 | #[macro_export] 88 | macro_rules! encode_s_imm { 89 | ($n:expr) => { 90 | (((($n as u32) & 0b111111100000) << 25) | ((($n as u32) & 0b000000011111) << 7)) 91 | }; 92 | } 93 | 94 | /// Encode and integer as a bitmask for a B-type immediate. 95 | #[macro_export] 96 | macro_rules! encode_b_imm { 97 | ($n:expr) => { 98 | (((($n as u32) & 0b10000000_00000000_00000000_00000000) << (31 - 31)) 99 | | ((($n as u32) & 0b00000000_00000000_00000111_11100000) << (25 - 5)) 100 | | ((($n as u32) & 0b00000000_00000000_00000000_00011110) << (8 - 1)) 101 | | ((($n as u32) & 0b00000000_00000000_00001000_00000000) >> (11 - 7))) 102 | }; 103 | } 104 | -------------------------------------------------------------------------------- /assembler/src/error.rs: -------------------------------------------------------------------------------- 1 | /// Enumeration of possible errors when assembling a program. 2 | #[derive(Debug, PartialEq)] 3 | pub enum AssemblerError { 4 | InvalidOperationError, 5 | NoSuchLabelError, 6 | NoSuchRegisterError, 7 | WrongOperandTypeError, 8 | TooManyTokensError, 9 | TooFewTokensError, 10 | ImmediateTooLargeError, 11 | InvalidImmediateError, 12 | IOError, 13 | } 14 | -------------------------------------------------------------------------------- /assembler/src/lib.rs: -------------------------------------------------------------------------------- 1 | /// Functions for assembling instructions and buffers. 2 | mod assembler; 3 | 4 | /// Functions for encoding integers as instruction fields. 5 | pub mod encode; 6 | 7 | /// Errors that may arise when assembling. 8 | pub mod error; 9 | 10 | /// Functions for parsing an instruction string. 11 | pub mod parse; 12 | 13 | /// Unit-tests. 14 | #[cfg(test)] 15 | mod test; 16 | 17 | /// Re-export common library. 18 | pub use lib_rv32_common as common; 19 | 20 | // Re-export assembler functions. 21 | pub use assembler::*; 22 | -------------------------------------------------------------------------------- /assembler/src/parse.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use lib_rv32_common::{constants::*, parse_int}; 4 | 5 | use crate::error::AssemblerError; 6 | 7 | /// Convert an instruction to it's tokens, stripping out whitespace, 8 | /// parenthesis, and commas. 9 | #[macro_export] 10 | macro_rules! tokenize { 11 | ($s:expr) => { 12 | $s.replace(",", " ") 13 | .replace("\n", " ") 14 | .replace("(", " ") 15 | .replace(")", " ") 16 | .to_ascii_lowercase() 17 | .split_whitespace() 18 | .map(|s| s.to_owned()) 19 | .collect(); 20 | }; 21 | } 22 | 23 | /// Match an operation to the correct opcode. 24 | pub fn match_opcode(op: &str) -> Result { 25 | let opcode = match op { 26 | "add" | "sub" | "sll" | "slt" | "sltu" | "xor" | "sra" | "or" | "and" => OPCODE_ARITHMETIC, 27 | "addi" | "slli" | "slti" | "xori" | "srai" | "ori" | "andi" => OPCODE_ARITHMETIC_IMM, 28 | "lui" => OPCODE_LUI, 29 | "auipc" => OPCODE_AUIPC, 30 | "jal" => OPCODE_JAL, 31 | "jalr" => OPCODE_JALR, 32 | "beq" | "bne" | "blt" | "bge" | "bgeu" => OPCODE_BRANCH, 33 | "lb" | "lbu" | "lh" | "lhu" | "lw" => OPCODE_LOAD, 34 | "sb" | "sh" | "sw" => OPCODE_STORE, 35 | _ => return Err(AssemblerError::InvalidOperationError), 36 | }; 37 | Ok(opcode) 38 | } 39 | 40 | /// Match a register number or name to its integer number. 41 | pub fn match_register(reg: &str) -> Result { 42 | if reg.starts_with('x') { 43 | match reg.strip_prefix('x').unwrap().parse() { 44 | Ok(n) => Ok(n), 45 | Err(_) => Err(AssemblerError::NoSuchRegisterError), 46 | } 47 | } else { 48 | match REG_NAMES.iter().position(|e| *e == reg) { 49 | Some(n) => Ok(n as u8), 50 | None => Err(AssemblerError::NoSuchRegisterError), 51 | } 52 | } 53 | } 54 | 55 | /// Parse a label or an immediate literal into an integer. 56 | pub fn parse_imm(s: &str, labels: &HashMap, pc: u32) -> Result { 57 | let num = parse_int!(i64, s); 58 | match num { 59 | Err(_) => { 60 | let label = labels.get(s); 61 | if let Some(v) = label { 62 | Ok((*v).wrapping_sub(pc)) 63 | } else { 64 | Err(AssemblerError::InvalidImmediateError) 65 | } 66 | } 67 | Ok(d) => Ok(d as u32), 68 | } 69 | } 70 | 71 | /// Match an operation to the correct func3. 72 | #[macro_export] 73 | macro_rules! match_func3 { 74 | ($t:expr) => { 75 | match $t { 76 | "beq" => FUNC3_BEQ, 77 | "bne" => FUNC3_BNE, 78 | "blt" => FUNC3_BLT, 79 | "bge" => FUNC3_BGE, 80 | "bltu" => FUNC3_BLTU, 81 | "bgeu" => FUNC3_BGEU, 82 | "lb" => FUNC3_LB, 83 | "lbu" => FUNC3_LBU, 84 | "lh" => FUNC3_LH, 85 | "lhu" => FUNC3_LHU, 86 | "lw" => FUNC3_LW, 87 | "sb" => FUNC3_SB, 88 | "sh" => FUNC3_SH, 89 | "sw" => FUNC3_SW, 90 | "add" | "addi" | "sub" => FUNC3_ADD_SUB, 91 | "sll" | "slli" => FUNC3_SLL, 92 | "slt" | "slti" => FUNC3_SLT, 93 | "sltu" => FUNC3_SLTU, 94 | "xor" | "xori" => FUNC3_XOR, 95 | "sra" | "srai" | "srl" | "srli" => FUNC3_SR, 96 | "or" | "ori" => FUNC3_OR, 97 | "and" | "andi" => FUNC3_AND, 98 | _ => unreachable!(), 99 | } 100 | }; 101 | } 102 | 103 | /// Match an operation to the correct func7. 104 | #[macro_export] 105 | macro_rules! match_func7 { 106 | ($t:expr) => { 107 | match $t { 108 | "add" | "addi" => FUNC7_ADD, 109 | "sub" => FUNC7_SUB, 110 | "sra" | "srai" => FUNC7_SRA, 111 | "srl" | "srli" => FUNC7_SRL, 112 | _ => unreachable!(), 113 | } 114 | }; 115 | } 116 | -------------------------------------------------------------------------------- /assembler/src/test.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use lib_rv32_common::{constants::*, instructions}; 4 | 5 | use crate::{parse::*, *}; 6 | 7 | #[test] 8 | fn test_tokenize() { 9 | let tokens: Vec = tokenize!("addi t0, t1, 17"); 10 | assert_eq!( 11 | vec![ 12 | "addi".to_string(), 13 | "t0".to_string(), 14 | "t1".to_string(), 15 | "17".to_string() 16 | ], 17 | tokens 18 | ); 19 | } 20 | 21 | #[test] 22 | fn test_tokenize_with_offsets() { 23 | let tokens: Vec = tokenize!("lw t0, 17(s0)"); 24 | assert_eq!( 25 | vec![ 26 | "lw".to_string(), 27 | "t0".to_string(), 28 | "17".to_string(), 29 | "s0".to_string(), 30 | ], 31 | tokens 32 | ); 33 | let tokens: Vec = tokenize!("lw x5, 0(x5)"); 34 | assert_eq!( 35 | vec![ 36 | "lw".to_string(), 37 | "x5".to_string(), 38 | "0".to_string(), 39 | "x5".to_string(), 40 | ], 41 | tokens 42 | ); 43 | } 44 | 45 | #[test] 46 | fn test_tokenize_many_commas() { 47 | let tokens: Vec = tokenize!("lw,,, t0,,,,, 17,,,(s0),,,,,,"); 48 | assert_eq!( 49 | vec![ 50 | "lw".to_string(), 51 | "t0".to_string(), 52 | "17".to_string(), 53 | "s0".to_string(), 54 | ], 55 | tokens 56 | ); 57 | } 58 | 59 | #[test] 60 | fn test_tokenize_many_spaces() { 61 | let tokens: Vec = tokenize!("lw t0 17 (s0) "); 62 | assert_eq!( 63 | vec![ 64 | "lw".to_string(), 65 | "t0".to_string(), 66 | "17".to_string(), 67 | "s0".to_string(), 68 | ], 69 | tokens 70 | ); 71 | } 72 | 73 | #[test] 74 | fn test_tokenize_label() { 75 | let tokens: Vec = tokenize!("label: addi t0, t1, 12"); 76 | assert_eq!( 77 | vec![ 78 | "label:".to_string(), 79 | "addi".to_string(), 80 | "t0".to_string(), 81 | "t1".to_string(), 82 | "12".to_string(), 83 | ], 84 | tokens 85 | ); 86 | } 87 | 88 | #[test] 89 | fn test_parse_imm() { 90 | let mut labels: HashMap = HashMap::new(); 91 | labels.insert("loop".to_string(), 0); 92 | let pc = 4; 93 | 94 | assert_eq!(-4, parse_imm("loop", &labels, pc).unwrap() as i32); 95 | assert_eq!(-24, parse_imm("-24", &labels, pc).unwrap() as i32); 96 | assert_eq!(16, parse_imm("16", &labels, pc).unwrap()); 97 | } 98 | 99 | macro_rules! assert_eq { 100 | ($a:expr, $b:expr) => { 101 | std::assert_eq!($a, $b, "\n{:032b}\n{:032b}", $a, $b) 102 | }; 103 | } 104 | 105 | macro_rules! test_field { 106 | ($field:expr,$expect:expr) => { 107 | assert_eq!($expect, $field | $expect) 108 | }; 109 | } 110 | 111 | #[test] 112 | fn test_assemble_copious_commas() { 113 | let mut empty_hash: HashMap = HashMap::new(); 114 | assert_eq!( 115 | instructions::ADDI_X5_X6_0, 116 | assemble_ir("addi,, t0,,, x6,, 0,,,", &mut empty_hash, 0) 117 | .unwrap() 118 | .unwrap() 119 | ); 120 | } 121 | 122 | #[test] 123 | fn test_assemble_no_commas() { 124 | let mut empty_hash: HashMap = HashMap::new(); 125 | assert_eq!( 126 | instructions::ADDI_X5_X6_0, 127 | assemble_ir("addi t0 x6 0", &mut empty_hash, 0) 128 | .unwrap() 129 | .unwrap() 130 | ); 131 | } 132 | 133 | #[test] 134 | fn test_assemble_uppercase() { 135 | let mut empty_hash: HashMap = HashMap::new(); 136 | assert_eq!( 137 | instructions::ADDI_X5_X6_0, 138 | assemble_ir("ADDI T0, X6, 0", &mut empty_hash, 0) 139 | .unwrap() 140 | .unwrap() 141 | ); 142 | } 143 | 144 | #[test] 145 | fn test_assemble_random_case() { 146 | let mut empty_hash: HashMap = HashMap::new(); 147 | assert_eq!( 148 | instructions::ADDI_X5_X6_0, 149 | assemble_ir("aDdI t0, X6, 0", &mut empty_hash, 0) 150 | .unwrap() 151 | .unwrap() 152 | ); 153 | } 154 | 155 | #[test] 156 | fn test_assemble_lower_case() { 157 | let mut empty_hash: HashMap = HashMap::new(); 158 | assert_eq!( 159 | instructions::ADDI_X5_X6_0, 160 | assemble_ir("addi t0, x6, 0", &mut empty_hash, 0) 161 | .unwrap() 162 | .unwrap() 163 | ); 164 | } 165 | 166 | #[test] 167 | fn test_assemble_i_type() { 168 | let mut empty_hash: HashMap = HashMap::new(); 169 | assert_eq!( 170 | instructions::ADDI_X5_X6_0, 171 | assemble_ir("addi t0, x6, 0", &mut empty_hash, 0) 172 | .unwrap() 173 | .unwrap() 174 | ); 175 | assert_eq!( 176 | instructions::ADDI_X0_X0_17, 177 | assemble_ir("addi zero, x0, 17", &mut empty_hash, 0) 178 | .unwrap() 179 | .unwrap() 180 | ); 181 | assert_eq!( 182 | instructions::ADDI_X5_X6_NEG_12, 183 | assemble_ir("addi t0, t1, -12", &mut empty_hash, 0) 184 | .unwrap() 185 | .unwrap() 186 | ); 187 | assert_eq!( 188 | instructions::LW_X5_0_X5, 189 | assemble_ir("lw x5, 0(x5)", &mut empty_hash, 0) 190 | .unwrap() 191 | .unwrap() 192 | ) 193 | } 194 | 195 | #[test] 196 | fn test_assemble_u_type() { 197 | let mut empty_hash: HashMap = HashMap::new(); 198 | assert_eq!( 199 | instructions::AUIPC_X5_4, 200 | assemble_ir("auipc x5, 4", &mut empty_hash, 0) 201 | .unwrap() 202 | .unwrap() 203 | ); 204 | assert_eq!( 205 | instructions::LUI_X5_4, 206 | assemble_ir("lui x5, 4", &mut empty_hash, 0) 207 | .unwrap() 208 | .unwrap() 209 | ); 210 | } 211 | 212 | #[test] 213 | fn test_assemble_b_type() { 214 | let mut empty_hash: HashMap = HashMap::new(); 215 | 216 | let expect = instructions::BEQ_X5_X5_12; 217 | let actual = assemble_ir("beq x5, x5, 12", &mut empty_hash, 0) 218 | .unwrap() 219 | .unwrap(); 220 | assert_eq!(expect, actual); 221 | 222 | let expect = instructions::BNE_X5_X5_76; 223 | let actual = assemble_ir("bne t0, t0, 76", &mut empty_hash, 0) 224 | .unwrap() 225 | .unwrap(); 226 | assert_eq!(expect, actual); 227 | } 228 | 229 | #[test] 230 | fn test_assemble_with_label() { 231 | let mut labels: HashMap = HashMap::new(); 232 | 233 | assert_eq!( 234 | instructions::LUI_X5_4, 235 | assemble_ir("loop: lui x5, 4", &mut labels, 0) 236 | .unwrap() 237 | .unwrap() 238 | ); 239 | 240 | assert_eq!(0, *(labels.get("loop").unwrap())); 241 | 242 | let expect = instructions::JAL_X0_NEG_4; 243 | let actual = assemble_ir("jal x0, loop", &mut labels, 4) 244 | .unwrap() 245 | .unwrap(); 246 | assert_eq!(expect, actual); 247 | 248 | let expect = instructions::BNE_X0_X5_NEG_4; 249 | let actual = assemble_ir("bne x0, t0, loop", &mut labels, 4) 250 | .unwrap() 251 | .unwrap(); 252 | assert_eq!(expect, actual); 253 | } 254 | 255 | #[test] 256 | fn test_encode_b_imm() { 257 | test_field!(encode_b_imm!(72), instructions::BLT_X5_X5_72); 258 | test_field!(encode_b_imm!(76), instructions::BNE_X5_X5_76); 259 | } 260 | 261 | #[test] 262 | fn test_encode_i_imm() { 263 | test_field!(encode_i_imm!(17), instructions::ADDI_X0_X0_17); 264 | let i: i32 = -2048; 265 | test_field!(encode_i_imm!(i as u32), instructions::ADDI_X5_X6_NEG_2048); 266 | } 267 | 268 | #[test] 269 | fn test_encode_j_imm() { 270 | let i = -4; 271 | test_field!(encode_j_imm!(i as u32), instructions::JAL_X0_NEG_4); 272 | let i = -8; 273 | test_field!(encode_j_imm!(i as u32), instructions::JAL_X0_NEG_8); 274 | let i = 16; 275 | test_field!(encode_j_imm!(i as u32), instructions::JAL_X0_16); 276 | } 277 | 278 | #[test] 279 | fn test_encode_rs1() { 280 | test_field!(encode_rs1!(5), instructions::BEQ_X5_X5_12); 281 | test_field!(encode_rs1!(5), instructions::BNE_X5_X5_76); 282 | } 283 | 284 | #[test] 285 | fn test_encode_rs2() { 286 | test_field!(encode_rs2!(5), instructions::BNE_X0_X5_NEG_4); 287 | } 288 | 289 | #[test] 290 | fn test_encode_func3() { 291 | test_field!(encode_func3!(FUNC3_BEQ), instructions::BEQ_X5_X5_12); 292 | test_field!(encode_func3!(FUNC3_BNE), instructions::BNE_X5_X5_76); 293 | } 294 | -------------------------------------------------------------------------------- /cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lib-rv32-cli" 3 | description = "Extension to lib_rv32 that provides an MCU implementation" 4 | keywords = ["riscv", "testing", "simulator", "emulator"] 5 | authors = ["Trevor McKay "] 6 | homepage = "https://github.com/trmckay/lib-rv32" 7 | repository = "https://github.com/trmckay/lib-rv32" 8 | version = "0.2.0" 9 | edition = "2018" 10 | license = "MIT" 11 | readme = "../README.md" 12 | 13 | [[bin]] 14 | name = "lib-rv32-cli" 15 | path = "src/bin.rs" 16 | 17 | [dependencies] 18 | serde_json = "1.0.*" 19 | lazy_static = "1.4.*" 20 | clap = "2.33.*" 21 | log = "0.4.*" 22 | lib-rv32-mcu = "0.2.*" 23 | lib-rv32-asm = "0.2.*" 24 | -------------------------------------------------------------------------------- /cli/src/assertions.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, path::Path}; 2 | 3 | use lib_rv32_mcu::{ 4 | common::{constants::*, parse_int}, 5 | isa::traits::*, 6 | }; 7 | 8 | /// Used to contain a set of assertions about the state of an MCU. 9 | pub struct Assertions { 10 | pub register_assertions: Vec<(u8, u32, bool)>, 11 | pub memory_assertions: Vec<(u32, u32, bool)>, 12 | } 13 | 14 | impl Assertions { 15 | /// Construct an `Assertion` from a JSON file. 16 | /// 17 | /// Example: 18 | /// 19 | /// In `assert.json`: 20 | /// ```json 21 | /// { 22 | /// "registers": { 23 | /// "a0": "10", 24 | /// "t0": "0" 25 | /// }, 26 | /// "memory": { 27 | /// "0x1000": "0x10", 28 | /// "0x1004": "0x4" 29 | /// } 30 | /// } 31 | /// ``` 32 | /// 33 | /// ```no_run 34 | /// # use lib_rv32::Assertions; 35 | /// use std::path::PathBuf; 36 | /// 37 | /// let asserts = Assertions::load(&PathBuf::from("assert.json")); 38 | /// ``` 39 | pub fn load(path: &Path) -> Self { 40 | let test_params: serde_json::Value = 41 | serde_json::from_str(&fs::read_to_string(path).unwrap()).unwrap(); 42 | 43 | let mut register_assertions: Vec<(u8, u32, bool)> = Vec::new(); 44 | for (i, name) in REG_NAMES.iter().enumerate() { 45 | if let Some(s) = test_params["registers"][*name].as_str() { 46 | let d = parse_int!(u32, s).unwrap(); 47 | register_assertions.push((i as u8, d as u32, true)); 48 | } 49 | } 50 | 51 | let mut memory_assertions: Vec<(u32, u32, bool)> = Vec::new(); 52 | let kvs = test_params["memory"].as_object(); 53 | if kvs.is_some() { 54 | for (k, v) in kvs.unwrap() { 55 | let addr = parse_int!(u32, k).unwrap(); 56 | let data = parse_int!(u32, v.as_str().unwrap()).unwrap(); 57 | memory_assertions.push((addr, data, true)); 58 | } 59 | } 60 | 61 | Assertions { 62 | register_assertions, 63 | memory_assertions, 64 | } 65 | } 66 | 67 | /// Iterate through all assertions and compare their expected values 68 | /// with the actual. If the assertion fails, the third member of the 69 | /// tuple is populated with `false`. 70 | pub fn assert_all(&mut self, mem: &mut M, rf: &mut R) 71 | where 72 | M: Memory, 73 | R: RegisterFile, 74 | { 75 | for a in self.register_assertions.iter_mut() { 76 | a.2 = rf.read(a.0).unwrap() == a.1; 77 | } 78 | for a in self.memory_assertions.iter_mut() { 79 | a.2 = mem.fetch(a.0).unwrap() == a.1; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /cli/src/bin.rs: -------------------------------------------------------------------------------- 1 | pub mod assertions; 2 | 3 | use std::{ 4 | fs, 5 | io::{prelude::*, BufReader}, 6 | path::PathBuf, 7 | }; 8 | 9 | use clap::{App, Arg}; 10 | use lazy_static::lazy_static; 11 | use log::{info, Level, LevelFilter, Metadata, Record}; 12 | 13 | use lib_rv32_asm::assemble_program_buf; 14 | use lib_rv32_mcu::{common::constants::*, isa::exec_one, *}; 15 | 16 | use assertions::Assertions; 17 | 18 | const DEFAULT_MEM_SIZE: usize = 1024 * 64; 19 | 20 | lazy_static! { 21 | static ref CFG: Config = Config::new(); 22 | } 23 | 24 | static LOGGER: Logger = Logger; 25 | 26 | enum Mode { 27 | Emulator, 28 | Assembler, 29 | } 30 | 31 | struct Config { 32 | file: PathBuf, 33 | mem_size: usize, 34 | stop_pc: Option, 35 | assertions: Option, 36 | output: Option, 37 | mode: Mode, 38 | } 39 | 40 | impl Config { 41 | fn new() -> Self { 42 | let matches = App::new("lib-rv32") 43 | .version("0.2.0") 44 | .author("Trevor McKay ") 45 | .about("Emulate RISC-V") 46 | .arg( 47 | Arg::with_name("file") 48 | .help("File on which to act") 49 | .required(true) 50 | .index(1), 51 | ) 52 | .arg( 53 | Arg::with_name("mem") 54 | .short("m") 55 | .long("mem") 56 | .value_name("MEM_SIZE") 57 | .help("Set the size of the MCU memory (default 64 KB)") 58 | .takes_value(true), 59 | ) 60 | .arg( 61 | Arg::with_name("stop") 62 | .short("s") 63 | .long("--stop") 64 | .value_name("STOP_PC") 65 | .help("Set the program counter at which to stop emulation") 66 | .takes_value(true), 67 | ) 68 | .arg( 69 | Arg::with_name("assertions") 70 | .short("a") 71 | .long("--assertions") 72 | .value_name("ASSERTIONS_FILE") 73 | .help("A JSON formatted set of assertions") 74 | .takes_value(true), 75 | ) 76 | .arg( 77 | Arg::with_name("output") 78 | .short("o") 79 | .long("--output") 80 | .value_name("OUTPUT_FILE") 81 | .help("Out-file for binary in assembler mode, or memory dump in emulator mode") 82 | .takes_value(true), 83 | ) 84 | .arg( 85 | Arg::with_name("verbose") 86 | .short("v") 87 | .long("verbose") 88 | .help("Enable verbose logging") 89 | .takes_value(false), 90 | ) 91 | .arg( 92 | Arg::with_name("assemble") 93 | .short("c") 94 | .long("--assemble") 95 | .help("Launch in assembler mode") 96 | .takes_value(false), 97 | ) 98 | .arg( 99 | Arg::with_name("emulate") 100 | .short("e") 101 | .long("--emulate") 102 | .default_value("e") 103 | .help("Launch in emulator mode") 104 | .takes_value(false), 105 | ) 106 | .get_matches(); 107 | 108 | let mem_size = match matches.value_of("config") { 109 | Some(s) => str::parse(s).unwrap(), 110 | None => DEFAULT_MEM_SIZE, 111 | }; 112 | let stop_pc = matches.value_of("stop").map(|s| { 113 | u32::from_str_radix(s, 16) 114 | .unwrap_or_else(|_| panic!("{} is not a valid hex literal.", s)) 115 | }); 116 | let verbose = !matches!(matches.occurrences_of("verbose"), 0); 117 | let path = PathBuf::from(matches.value_of("file").unwrap()); 118 | let assertions = matches.value_of("assertions").map(PathBuf::from); 119 | let output = matches.value_of("output").map(PathBuf::from); 120 | 121 | let mode: Mode = if matches.occurrences_of("emulate") == 1 { 122 | Mode::Emulator 123 | } else if matches.occurrences_of("assemble") == 1 { 124 | Mode::Assembler 125 | } else { 126 | panic!("No mode provided."); 127 | }; 128 | if matches.occurrences_of("emulate") == matches.occurrences_of("assemble") { 129 | panic!("Cannot launch in both modes."); 130 | } 131 | 132 | if verbose { 133 | log::set_logger(&LOGGER) 134 | .map(|()| log::set_max_level(LevelFilter::Info)) 135 | .unwrap(); 136 | } 137 | 138 | Config { 139 | file: path, 140 | mem_size, 141 | stop_pc, 142 | assertions, 143 | mode, 144 | output, 145 | } 146 | } 147 | } 148 | 149 | struct Logger; 150 | 151 | impl log::Log for Logger { 152 | fn enabled(&self, metadata: &Metadata) -> bool { 153 | metadata.level() <= Level::Info 154 | } 155 | 156 | fn log(&self, record: &Record) { 157 | if self.enabled(record.metadata()) { 158 | print!("{}", record.args()); 159 | } 160 | } 161 | 162 | fn flush(&self) {} 163 | } 164 | 165 | fn emu() { 166 | let assertions = CFG.assertions.as_ref().map(|p| Assertions::load(p)); 167 | 168 | let mut mcu: Mcu = Mcu::new(CFG.mem_size); 169 | mcu.mem 170 | .program_from_file(&CFG.file) 171 | .expect("Could not program MCU."); 172 | 173 | loop { 174 | exec_one(&mut mcu.pc, &mut mcu.mem, &mut mcu.rf).unwrap(); 175 | if Some(mcu.pc) == CFG.stop_pc { 176 | info!("\nReached stop-PC.\n"); 177 | break; 178 | } 179 | } 180 | 181 | if let Some(mut assertions) = assertions { 182 | assertions.assert_all(&mut mcu.mem, &mut mcu.rf); 183 | println!(); 184 | 185 | for assert in assertions.register_assertions { 186 | if assert.2 { 187 | println!("{} == {}", REG_NAMES[assert.0 as usize], assert.1) 188 | } else { 189 | println!( 190 | "({} = {}) != {}", 191 | REG_NAMES[assert.0 as usize], 192 | mcu.rf.read(assert.0).unwrap(), 193 | assert.1 194 | ); 195 | } 196 | } 197 | println!(); 198 | 199 | for assert in assertions.memory_assertions { 200 | if assert.2 { 201 | println!("*0x{:08x} == {}", assert.0, assert.1) 202 | } else { 203 | println!( 204 | "(*0x{:08x} = {}) != {}", 205 | assert.0, 206 | mcu.mem.fetch(assert.0).unwrap(), 207 | assert.1 208 | ); 209 | } 210 | } 211 | } 212 | } 213 | 214 | fn asm() { 215 | let file = fs::File::open(&CFG.file).unwrap(); 216 | let mut reader = BufReader::new(file); 217 | let words = assemble_program_buf(&mut reader).unwrap(); 218 | 219 | if let Some(path) = &CFG.output { 220 | let mut output = fs::File::create(&path).unwrap(); 221 | for w in words { 222 | output.write_all(&w.to_le_bytes()).unwrap(); 223 | } 224 | } 225 | } 226 | 227 | fn main() { 228 | match CFG.mode { 229 | Mode::Assembler => asm(), 230 | Mode::Emulator => emu(), 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lib-rv32-common" 3 | description = "Common data for all lib_rv32 crates." 4 | keywords = ["riscv", "testing", "simulator", "emulator", "assembler"] 5 | authors = ["Trevor McKay "] 6 | homepage = "https://github.com/trmckay/lib-rv32" 7 | repository = "https://github.com/trmckay/lib-rv32" 8 | version = "0.2.0" 9 | edition = "2018" 10 | license = "MIT" 11 | readme = "../README.md" 12 | 13 | [lib] 14 | name = "lib_rv32_common" 15 | path = "src/lib.rs" 16 | -------------------------------------------------------------------------------- /common/src/bits.rs: -------------------------------------------------------------------------------- 1 | /// Returns a bitmask for the n'th bit. 2 | #[macro_export] 3 | macro_rules! bit { 4 | ($n:expr) => { 5 | ((0b1 as u32) << $n) 6 | }; 7 | } 8 | 9 | /// Macro to help with bit level access to integers. Example 10 | /// attempts to mimic Verilog syntax. 11 | /// 12 | /// Example: 13 | /// 14 | /// ``` 15 | /// # use lib_rv32_common::bit_slice; 16 | /// bit_slice!(0b1101, 3, 2) == 0b11; 17 | /// bit_slice!(0b1101, 1) == 0b0; 18 | /// ``` 19 | #[macro_export] 20 | macro_rules! bit_slice { 21 | ($n:expr, $i:expr) => { 22 | ($n & (0b1 << $i)) >> $i 23 | }; 24 | 25 | ($n:expr, $msb:expr, $lsb:expr) => { 26 | ($n & (((0b1 << ($msb - $lsb + 1)) - 1) << $lsb)) >> $lsb 27 | }; 28 | } 29 | 30 | /// Concatenate the bits of integers. 31 | /// 32 | /// Example: 33 | /// 34 | /// ``` 35 | /// # use lib_rv32_common::bit_concat; 36 | /// bit_concat!( 37 | /// (0b111, 3), 38 | /// (0b01, 2) 39 | /// ) == 0b11101; 40 | /// ``` 41 | #[macro_export] 42 | macro_rules! bit_concat { 43 | ($($x:expr),*) => {{ 44 | let mut i = 0; 45 | let mut t = 0; 46 | for n in [$($x),*].iter().rev() { 47 | t += n.0 << i; 48 | i += n.1; 49 | } 50 | t 51 | }} 52 | } 53 | 54 | /// Extend a bit (useful for sign extension). 55 | /// 56 | /// Example: 57 | /// 58 | /// ``` 59 | /// # use lib_rv32_common::bit_extend; 60 | /// bit_extend!(0b1, 8) == 0b1111_1111; 61 | /// ``` 62 | #[macro_export] 63 | macro_rules! bit_extend { 64 | ($n:expr, $r:expr) => { 65 | match $n { 66 | 0 => 0, 67 | _ => (0..$r).map(|i| 1 << i).sum(), 68 | } 69 | }; 70 | } 71 | 72 | /// Like `bit_slice`, but outputs the result and its 73 | /// size in a tuple. 74 | #[macro_export] 75 | macro_rules! sized_bit_slice { 76 | ($n: expr, $i:expr) => { 77 | (bit_slice!($n, $i), 1) 78 | }; 79 | 80 | ($n: expr, $msb:expr, $lsb:expr) => { 81 | (bit_slice!($n, $msb, $lsb), $msb - $lsb + 1) 82 | }; 83 | } 84 | 85 | /// Like `bit_extend`, but outputs the result and its 86 | /// size in a tuple. 87 | #[macro_export] 88 | macro_rules! sized_bit_extend { 89 | ($n: expr, $r:expr) => { 90 | (bit_extend!($n, $r), $r) 91 | }; 92 | } 93 | 94 | #[cfg(test)] 95 | mod tests { 96 | #[test] 97 | fn test_bit_slice() { 98 | let x = 0b1011; 99 | 100 | assert_eq!(0b1, bit_slice!(x, 3)); 101 | assert_eq!(0b0, bit_slice!(x, 2)); 102 | assert_eq!(0b1, bit_slice!(x, 1)); 103 | assert_eq!(0b1, bit_slice!(x, 0)); 104 | 105 | assert_eq!(0b10, bit_slice!(x, 3, 2)); 106 | assert_eq!(0b101, bit_slice!(x, 3, 1)); 107 | assert_eq!(0b1011, bit_slice!(x, 3, 0)); 108 | assert_eq!(0b011, bit_slice!(x, 2, 0)); 109 | assert_eq!(0b11, bit_slice!(x, 1, 0)); 110 | } 111 | 112 | #[test] 113 | fn test_bit_concat() { 114 | assert_eq!(0b1101, bit_concat!((0b11, 2), (0b01, 2))); 115 | } 116 | 117 | #[test] 118 | fn test_bit_extend() { 119 | assert_eq!(0b1111, bit_extend!(1, 4)); 120 | assert_eq!(0b0, bit_extend!(0, 32)); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /common/src/constants.rs: -------------------------------------------------------------------------------- 1 | pub const OPCODE_LUI: u8 = 0b0110111; 2 | pub const OPCODE_AUIPC: u8 = 0b0010111; 3 | pub const OPCODE_JAL: u8 = 0b1101111; 4 | pub const OPCODE_JALR: u8 = 0b1100111; 5 | pub const OPCODE_BRANCH: u8 = 0b1100011; 6 | pub const OPCODE_LOAD: u8 = 0b0000011; 7 | pub const OPCODE_STORE: u8 = 0b0100011; 8 | pub const OPCODE_ARITHMETIC_IMM: u8 = 0b0010011; 9 | pub const OPCODE_ARITHMETIC: u8 = 0b0110011; 10 | 11 | pub const FUNC3_BEQ: u8 = 0b000; 12 | pub const FUNC3_BNE: u8 = 0b001; 13 | pub const FUNC3_BLT: u8 = 0b100; 14 | pub const FUNC3_BGE: u8 = 0b101; 15 | pub const FUNC3_BLTU: u8 = 0b110; 16 | pub const FUNC3_BGEU: u8 = 0b111; 17 | pub const FUNC3_LB: u8 = 0b000; 18 | pub const FUNC3_LH: u8 = 0b001; 19 | pub const FUNC3_LW: u8 = 0b010; 20 | pub const FUNC3_LBU: u8 = 0b100; 21 | pub const FUNC3_LHU: u8 = 0b101; 22 | pub const FUNC3_SB: u8 = 0b000; 23 | pub const FUNC3_SH: u8 = 0b001; 24 | pub const FUNC3_SW: u8 = 0b010; 25 | pub const FUNC3_ADD_SUB: u8 = 0b000; 26 | pub const FUNC3_SLL: u8 = 0b001; 27 | pub const FUNC3_SLT: u8 = 0b010; 28 | pub const FUNC3_SLTU: u8 = 0b011; 29 | pub const FUNC3_XOR: u8 = 0b100; 30 | pub const FUNC3_SR: u8 = 0b101; 31 | pub const FUNC3_OR: u8 = 0b110; 32 | pub const FUNC3_AND: u8 = 0b111; 33 | 34 | pub const FUNC7_ADD: u8 = 0b0000000; 35 | pub const FUNC7_SUB: u8 = 0b0100000; 36 | pub const FUNC7_SRA: u8 = 0b0000000; 37 | pub const FUNC7_SRL: u8 = 0b0100000; 38 | 39 | /// Array to match register numbers to their common names. 40 | pub static REG_NAMES: &[&str] = &[ 41 | "zero", "ra", "sp", "gp", "tp", "t0", "t1", "t2", "s0", "s1", "a0", "a1", "a2", "a3", "a4", 42 | "a5", "a6", "a7", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11", "t3", "t4", 43 | "t5", "t6", 44 | ]; 45 | -------------------------------------------------------------------------------- /common/src/instructions.rs: -------------------------------------------------------------------------------- 1 | /// Shortcut to declare an instruction as a `const u32`. 2 | macro_rules! declare_ir { 3 | ($name:ident, $code:expr) => { 4 | #[allow(dead_code)] 5 | pub const $name: u32 = $code; 6 | }; 7 | } 8 | 9 | // ADD_SAME_REG_FIELDS_IR[i] = "add Xi, Xi, Xi" 10 | pub const ADD_SAME_REG_FIELDS_IRS: [u32; 32] = [ 11 | 0x00000033, 0x001080b3, 0x00210133, 0x003181b3, 0x00420233, 0x005282b3, 0x00630333, 0x007383b3, 12 | 0x00840433, 0x009484b3, 0x00a50533, 0x00b585b3, 0x00c60633, 0x00d686b3, 0x00e70733, 0x00f787b3, 13 | 0x01080833, 0x011888b3, 0x01290933, 0x013989b3, 0x014a0a33, 0x015a8ab3, 0x016b0b33, 0x017b8bb3, 14 | 0x018c0c33, 0x019c8cb3, 0x01ad0d33, 0x01bd8db3, 0x01ce0e33, 0x01de8eb3, 0x01ef0f33, 0x01ff8fb3, 15 | ]; 16 | 17 | declare_ir!(ADDI_X0_X0_17, 0x01100013); 18 | declare_ir!(XORI_X5_X6_82, 0x05234293); 19 | declare_ir!(ADDI_X5_X6_NEG_12, 0xff430293); 20 | declare_ir!(ADDI_X5_X6_NEG_1, 0xfff30293); 21 | declare_ir!(ADDI_X5_X6_NEG_2048, 0x80030293); 22 | declare_ir!(ADDI_X5_X6_0, 0x00030293); 23 | declare_ir!(ADDI_X5_X6_2047, 0x7ff30293); 24 | declare_ir!(JAL_X0_NEG_8, 0xff9ff06f); 25 | declare_ir!(JAL_X0_16, 0x0100006f); 26 | declare_ir!(LUI_X5_4, 0x000042b7); 27 | declare_ir!(AUIPC_X5_4, 0x00004297); 28 | declare_ir!(JAL_X5_20, 0x014002ef); 29 | declare_ir!(JALR_X5_X5_4, 0x004282e7); 30 | declare_ir!(BEQ_X5_X5_12, 0x00528663); 31 | declare_ir!(LW_X5_0_X5, 0x0002a283); 32 | declare_ir!(SW_X5_0_X5, 0x0052a023); 33 | declare_ir!(BEQ_X5_X5_80, 0x04528863); 34 | declare_ir!(BNE_X5_X5_76, 0x04529663); 35 | declare_ir!(BLT_X5_X5_72, 0x0452c463); 36 | declare_ir!(BGEU_X5_X5_68, 0x0452f263); 37 | declare_ir!(LB_X5_0_X5, 0x00028283); 38 | declare_ir!(LBU_X5_0_X5, 0x0002c283); 39 | declare_ir!(LH_X5_0_X5, 0x00029283); 40 | declare_ir!(LHU_X5_0_X5, 0x0002d283); 41 | declare_ir!(SB_X5_0_X5, 0x00528023); 42 | declare_ir!(SH_X5_0_X5, 0x00529023); 43 | declare_ir!(ADDI_X5_X5_1, 0x00128293); 44 | declare_ir!(SLLI_X5_X5_1, 0x00129293); 45 | declare_ir!(SLTI_X5_X5_1, 0x0012a293); 46 | declare_ir!(SLTU_X5_X5_X5, 0x0052b2b3); 47 | declare_ir!(XORI_X5_X5_1, 0x0012c293); 48 | declare_ir!(SRAI_X5_X5_1, 0x4012d293); 49 | declare_ir!(ORI_X5_X5_1, 0x0012e293); 50 | declare_ir!(ANDI_X5_X5_1, 0x0012f293); 51 | declare_ir!(SUB_X5_X5_X5, 0x405282b3); 52 | declare_ir!(ADDI_X6_X0_1, 0x00100313); 53 | declare_ir!(SW_X5_16_X5, 0x0052a823); 54 | declare_ir!(SW_X5_NEG_40_X5, 0xfc52ac23); 55 | declare_ir!(SW_A0_NEG_36_SP, 0xfca42e23); 56 | declare_ir!(SW_A0_NEG_20_S0, 0xfea42623); 57 | declare_ir!(JAL_X0_NEG_4, 0xffdff06f); 58 | declare_ir!(BNE_X0_X5_NEG_4, 0xfe501ee3); 59 | -------------------------------------------------------------------------------- /common/src/lib.rs: -------------------------------------------------------------------------------- 1 | /// Macros for bit-wise operations. 2 | pub mod bits; 3 | 4 | /// RISC-V constants. 5 | pub mod constants; 6 | 7 | /// General utility functions. 8 | pub mod util; 9 | 10 | /// Pre-assembled instructions for testing. 11 | pub mod instructions; 12 | -------------------------------------------------------------------------------- /common/src/util.rs: -------------------------------------------------------------------------------- 1 | /// Parse an integer from a string slice. If it leads with `0x` or `0X`, it 2 | /// will be parsed as base 16, otherwise it will be parsed as base 10. 3 | #[macro_export] 4 | macro_rules! parse_int { 5 | ($t:ty,$s:expr) => {{ 6 | if !$s.to_ascii_lowercase().starts_with("0x") { 7 | $s.parse() 8 | } else { 9 | <$t>::from_str_radix(&$s[2..], 16) 10 | } 11 | }}; 12 | } 13 | 14 | #[cfg(test)] 15 | mod tests { 16 | use crate::parse_int; 17 | 18 | #[test] 19 | fn test_parse_int() { 20 | assert_eq!(17, parse_int!(u32, "17").unwrap()); 21 | assert_eq!(17, parse_int!(u32, "017").unwrap()); 22 | assert_eq!(17, parse_int!(i32, "17").unwrap()); 23 | 24 | assert_eq!(-21, parse_int!(i32, "-21").unwrap()); 25 | 26 | assert_eq!(0x16, parse_int!(u32, "0x16").unwrap()); 27 | assert_eq!(0x16, parse_int!(u32, "0x0016").unwrap()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /install-hooks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | cd $(git rev-parse --show-toplevel) 6 | 7 | echo ''' 8 | #!/bin/bash 9 | 10 | set -e 11 | 12 | cd "$(git rev-parse --show-toplevel)" 13 | staged_rust=$(git diff --staged --name-only --diff-filter=d | grep -e '.rs$') || exit 0 14 | 15 | rustfmt $staged_rust 16 | git add $staged_rust 17 | 18 | if ! make check; then 19 | echo "Failed check." 20 | exit 1 21 | fi 22 | ''' > .git/hooks/pre-commit 23 | 24 | chmod +x .git/hooks/pre-commit 25 | -------------------------------------------------------------------------------- /isa-sim/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lib-rv32-isa" 3 | description = "A library and CLI tool for emulating, testing, and learning RISC-V." 4 | keywords = ["riscv", "testing", "simulator", "emulator"] 5 | authors = ["Trevor McKay "] 6 | homepage = "https://github.com/trmckay/lib-rv32" 7 | repository = "https://github.com/trmckay/lib-rv32" 8 | version = "0.2.0" 9 | edition = "2018" 10 | license = "MIT" 11 | readme = "../README.md" 12 | 13 | [lib] 14 | name = "lib_rv32_isa" 15 | path = "src/lib.rs" 16 | 17 | [dependencies] 18 | lib-rv32-common = "0.2.*" 19 | log = "0.4.*" 20 | -------------------------------------------------------------------------------- /isa-sim/src/decode.rs: -------------------------------------------------------------------------------- 1 | pub use lib_rv32_common::{bit_concat, bit_extend, bit_slice, sized_bit_extend, sized_bit_slice}; 2 | 3 | /// Decode the J-type immediate from a `u32` formatted instruction. 4 | #[macro_export] 5 | macro_rules! decode_j_imm { 6 | ($ir:expr) => { 7 | bit_concat!( 8 | sized_bit_extend!(bit_slice!($ir, 31), 12), 9 | sized_bit_slice!($ir, 19, 12), 10 | sized_bit_slice!($ir, 20), 11 | sized_bit_slice!($ir, 30, 21), 12 | sized_bit_extend!(0, 1) 13 | ) as u32 14 | }; 15 | } 16 | 17 | /// Decode the U-type immediate from a `u32` formatted instruction. 18 | #[macro_export] 19 | macro_rules! decode_u_imm { 20 | ($ir:expr) => { 21 | bit_concat!(sized_bit_slice!($ir, 31, 12), sized_bit_extend!(0, 12)) as u32 22 | }; 23 | } 24 | 25 | /// Decode the B-type immediate from a `u32` formatted instruction. 26 | #[macro_export] 27 | macro_rules! b_imm { 28 | ($ir:expr) => { 29 | bit_concat!( 30 | sized_bit_extend!(bit_slice!($ir, 31), 20), 31 | sized_bit_slice!($ir, 7), 32 | sized_bit_slice!($ir, 30, 25), 33 | sized_bit_slice!($ir, 11, 8), 34 | sized_bit_extend!(0, 1) 35 | ) as u32 36 | }; 37 | } 38 | 39 | /// Decode the I-type immediate from a `u32` formatted instruction. 40 | #[macro_export] 41 | macro_rules! decode_i_imm { 42 | ($ir:expr) => { 43 | bit_concat!( 44 | sized_bit_extend!(bit_slice!($ir, 31), 20), 45 | sized_bit_slice!($ir, 31, 20) 46 | ) as u32 47 | }; 48 | } 49 | 50 | /// Decode the S-type immediate from a `u32` formatted instruction. 51 | #[macro_export] 52 | macro_rules! decode_s_imm { 53 | ($ir:expr) => { 54 | bit_concat!( 55 | sized_bit_extend!(bit_slice!($ir, 31), 20), 56 | sized_bit_slice!($ir, 31, 25), 57 | sized_bit_slice!($ir, 11, 7) 58 | ) as u32 59 | }; 60 | } 61 | 62 | /// Decode the FUNC3 field from a `u32` formatted instruction. 63 | #[macro_export] 64 | macro_rules! decode_func3 { 65 | ($ir:expr) => { 66 | bit_slice!($ir, 14, 12) as u8 67 | }; 68 | } 69 | 70 | /// Decode the FUNC7 field from a `u32` formatted instruction. 71 | #[macro_export] 72 | macro_rules! decode_func7 { 73 | ($ir:expr) => { 74 | bit_slice!($ir, 31, 25) as u8 75 | }; 76 | } 77 | 78 | /// Decode the destination register field from a `u32` formatted instruction. 79 | #[macro_export] 80 | macro_rules! decode_rd { 81 | ($ir:expr) => { 82 | bit_slice!($ir, 11, 7) as u8 83 | }; 84 | } 85 | 86 | /// Decode the first operand register field from a `u32` formatted instruction. 87 | #[macro_export] 88 | macro_rules! decode_rs1 { 89 | ($ir:expr) => { 90 | bit_slice!($ir, 19, 15) as u8 91 | }; 92 | } 93 | 94 | /// Decode the second operand register field from a `u32` formatted instruction. 95 | #[macro_export] 96 | macro_rules! decode_rs2 { 97 | ($ir:expr) => { 98 | bit_slice!($ir, 24, 20) as u8 99 | }; 100 | } 101 | 102 | /// Decode the opcode field from a `u32` formatted instruction. 103 | #[macro_export] 104 | macro_rules! decode_opcode { 105 | ($ir:expr) => { 106 | bit_slice!($ir, 6, 0) as u8 107 | }; 108 | } 109 | -------------------------------------------------------------------------------- /isa-sim/src/error.rs: -------------------------------------------------------------------------------- 1 | /// Enum that encapsulates the various different ways execution can fail. 2 | /// Some errors are caused by other errors and reference them. 3 | /// 4 | /// Instruction format errors contain `(instruction: u32, bad_field: u8)`. 5 | /// 6 | /// Memory errors contain `(address: u32)`. 7 | /// 8 | /// Register file errors contain `(reg_num: u8)`. 9 | #[derive(Debug, PartialEq)] 10 | pub enum RiscvError { 11 | InvalidOpcodeError(u32, u8), 12 | InvalidFunc3Error(u32, u8), 13 | InvalidFunc7Error(u32, u8), 14 | RegisterOutOfRangeError(u8), 15 | MemoryOutOfBoundsError(u32), 16 | MemoryAlignmentError(u32), 17 | } 18 | -------------------------------------------------------------------------------- /isa-sim/src/exec.rs: -------------------------------------------------------------------------------- 1 | use log::info; 2 | 3 | use lib_rv32_common::constants::*; 4 | 5 | use crate::{ 6 | b_imm, decode::*, decode_func3, decode_func7, decode_i_imm, decode_j_imm, decode_opcode, 7 | decode_rd, decode_rs1, decode_rs2, decode_s_imm, decode_u_imm, traits::Memory, 8 | traits::RegisterFile, RiscvError, 9 | }; 10 | 11 | /// Decode and execute instruction. This will use the program counter to 12 | /// fetch an instruction from memory, decode/evaluate it, and commit the 13 | /// results to `pc`, `mem`, and `rf`. 14 | /// 15 | /// It logs using the `log` crate. If a logger is registered, log outputs 16 | /// are in this format: 17 | /// 18 | /// `[PC] instruction | op rd, rs1, rs2 | \n` 19 | /// 20 | /// Parameters: 21 | /// `ir`: The instruction 22 | /// `mem`: The memory which implements the `Memory` trait 23 | /// `rf`: The register file which implements the `RegisterFile` trait 24 | /// 25 | /// Returns: 26 | /// `Result`: Returns the next program counter and 27 | /// the error that occurred during execution, if one exists. 28 | /// 29 | /// A note on signedness: 30 | /// 31 | /// We interpret everything as an unsigned 32-bit integer (u32). 32 | /// This works great since the assembler encodes signed numbers 33 | /// as two's-complement. So, if we read '-1' encoded in 2C from the 34 | /// register-file, then add it to another number, the overflow 35 | /// will result in a correct operation. There is--in general--no 36 | /// need to care about the signedness of what we are operating on. 37 | /// 38 | /// The only exception to this is comparisons and loading 39 | /// bytes/half-words. For comparisons, we need to consider the sign 40 | /// due to the sign bit being the MSB. For loading units smaller than 41 | /// a word, we need to sign extend them before putting them into the 42 | /// 32-bit registers. 43 | pub fn exec_one(pc: &mut u32, mem: &mut M, rf: &mut R) -> Result<(), RiscvError> 44 | where 45 | M: Memory, 46 | R: RegisterFile, 47 | { 48 | let ir = mem.fetch(*pc); 49 | if let Err(why) = ir { 50 | return Err(why); 51 | } 52 | let ir = ir.unwrap(); 53 | let opcode = decode_opcode!(ir) as u8; 54 | 55 | info!("[{:04x}] {:08x}", pc, ir); 56 | 57 | return match opcode { 58 | OPCODE_LUI => { 59 | let rd = decode_rd!(ir); 60 | let imm = decode_u_imm!(ir); 61 | 62 | info!( 63 | "{:6} {}, 0x{:x} ({})", 64 | "lui", REG_NAMES[rd as usize], imm, imm as i32 65 | ); 66 | 67 | if let Err(why) = rf.write(rd, imm) { 68 | return Err(why); 69 | } 70 | *pc += 4; 71 | 72 | Ok(()) 73 | } 74 | 75 | OPCODE_AUIPC => { 76 | let rd = decode_rd!(ir); 77 | let imm = decode_u_imm!(ir); 78 | 79 | info!( 80 | "{:6} {}, 0x{:x}", 81 | "auipc", 82 | REG_NAMES[rd as usize], 83 | (imm >> 12) 84 | ); 85 | 86 | if let Err(why) = rf.write(rd, *pc + imm) { 87 | return Err(why); 88 | } 89 | *pc += 4; 90 | 91 | Ok(()) 92 | } 93 | 94 | OPCODE_JAL => { 95 | let rd = decode_rd!(ir); 96 | let imm = decode_j_imm!(ir); 97 | 98 | info!( 99 | "{:6} {}, 0x{:x} ({})", 100 | "jal", REG_NAMES[rd as usize], imm, imm as i32 101 | ); 102 | 103 | if let Err(why) = rf.write(rd, *pc + 4) { 104 | return Err(why); 105 | } 106 | *pc = pc.wrapping_add(imm); 107 | info!("pc <- 0x{:x}", pc); 108 | 109 | Ok(()) 110 | } 111 | 112 | OPCODE_JALR => { 113 | let rd = decode_rd!(ir); 114 | let rs1 = decode_rs1!(ir); 115 | let imm = decode_i_imm!(ir); 116 | 117 | info!( 118 | "{:6} {}, ({}){}", 119 | "jalr", REG_NAMES[rd as usize], imm as i32, REG_NAMES[rs1 as usize] 120 | ); 121 | 122 | let rs1_data = match rf.read(rs1) { 123 | Ok(d) => d, 124 | Err(why) => return Err(why), 125 | }; 126 | if let Err(why) = rf.write(rd, *pc + 4) { 127 | return Err(why); 128 | } 129 | 130 | *pc = rs1_data.wrapping_add(imm); 131 | info!("pc <- 0x{:x}", pc); 132 | 133 | Ok(()) 134 | } 135 | 136 | OPCODE_BRANCH => { 137 | let rs1 = decode_rs1!(ir); 138 | let rs1_data = match rf.read(rs1) { 139 | Ok(d) => d, 140 | Err(why) => return Err(why), 141 | }; 142 | let rs2 = decode_rs2!(ir); 143 | let rs2_data = match rf.read(rs2) { 144 | Ok(d) => d, 145 | Err(why) => return Err(why), 146 | }; 147 | let func3 = decode_func3!(ir); 148 | let taken = match func3 { 149 | FUNC3_BEQ => rs1_data == rs2_data, 150 | FUNC3_BNE => rs1_data != rs2_data, 151 | FUNC3_BLT => rs1_data < rs2_data, 152 | FUNC3_BGE => rs1_data >= rs2_data, 153 | FUNC3_BLTU => rs1_data < rs2_data, 154 | FUNC3_BGEU => rs1_data > rs2_data, 155 | _ => return Err(RiscvError::InvalidFunc3Error(ir, func3)), 156 | }; 157 | let imm = b_imm!(ir); 158 | 159 | info!( 160 | "{:6} {}, {}, {}", 161 | match func3 { 162 | FUNC3_BEQ => "beq", 163 | FUNC3_BNE => "bne", 164 | FUNC3_BLT => "blt", 165 | FUNC3_BGE => "bge", 166 | FUNC3_BLTU => "bltu", 167 | FUNC3_BGEU => "bgeu", 168 | _ => "", 169 | }, 170 | REG_NAMES[rs1 as usize], 171 | REG_NAMES[rs2 as usize], 172 | imm as i32, 173 | ); 174 | if taken { 175 | info!("branch taken"); 176 | } else { 177 | info!("branch not taken"); 178 | } 179 | 180 | if taken { 181 | *pc = pc.wrapping_add(imm); 182 | info!("pc <- 0x{:x}", pc); 183 | } else { 184 | *pc += 4; 185 | } 186 | 187 | Ok(()) 188 | } 189 | 190 | OPCODE_LOAD => { 191 | let rs1 = decode_rs1!(ir); 192 | let base = match rf.read(rs1) { 193 | Ok(d) => d, 194 | Err(why) => return Err(why), 195 | }; 196 | let imm = decode_i_imm!(ir); 197 | let addr = base.wrapping_add(imm); 198 | let rd = decode_rd!(ir); 199 | let func3 = decode_func3!(ir); 200 | 201 | info!( 202 | "{:6} {}, {}({})", 203 | match func3 { 204 | FUNC3_LB => "lb", 205 | FUNC3_LH => "lh", 206 | FUNC3_LBU => "lbu", 207 | FUNC3_LHU => "lhu", 208 | FUNC3_LW => "lw", 209 | _ => "", 210 | }, 211 | REG_NAMES[rd as usize], 212 | imm as i32, 213 | REG_NAMES[rs1 as usize] 214 | ); 215 | 216 | if let Err(why) = rf.write( 217 | rd, 218 | match { 219 | match func3 { 220 | FUNC3_LB => match mem.read_byte(addr) { 221 | Ok(d) => Ok((d as i8) as u32), // sign-extension 222 | Err(why) => return Err(why), 223 | }, 224 | FUNC3_LH => match mem.read_half_word(addr) { 225 | Ok(d) => Ok((d as i16) as u32), // sign-extension 226 | Err(why) => return Err(why), 227 | }, 228 | FUNC3_LBU => mem.read_byte(addr), 229 | FUNC3_LHU => mem.read_half_word(addr), 230 | FUNC3_LW => mem.read_word(addr), 231 | _ => return Err(RiscvError::InvalidFunc3Error(ir, func3)), 232 | } 233 | } { 234 | Ok(d) => d, 235 | Err(why) => return Err(why), 236 | }, 237 | ) { 238 | return Err(why); 239 | } 240 | *pc += 4; 241 | 242 | Ok(()) 243 | } 244 | 245 | OPCODE_STORE => { 246 | let rs1 = decode_rs1!(ir); 247 | let rs2 = decode_rs2!(ir); 248 | let imm = decode_s_imm!(ir); 249 | let func3 = decode_func3!(ir); 250 | 251 | info!( 252 | "{:6} {}, {}({})", 253 | match func3 { 254 | FUNC3_SB => "sb", 255 | FUNC3_SH => "sh", 256 | FUNC3_SW => "sw", 257 | _ => "", 258 | }, 259 | REG_NAMES[rs2 as usize], 260 | imm as i32, 261 | REG_NAMES[rs1 as usize] 262 | ); 263 | 264 | let addr = match rf.read(rs1) { 265 | Ok(d) => d, 266 | Err(why) => return Err(why), 267 | } 268 | .wrapping_add(imm); 269 | let data = match rf.read(rs2) { 270 | Ok(d) => d, 271 | Err(why) => return Err(why), 272 | }; 273 | if let Err(why) = match func3 { 274 | FUNC3_SB => mem.write_byte(addr, data), 275 | FUNC3_SH => mem.write_half_word(addr, data), 276 | FUNC3_SW => mem.write_word(addr, data), 277 | _ => Err(RiscvError::InvalidFunc3Error(ir, decode_func3!(ir))), 278 | } { 279 | return Err(why); 280 | } 281 | *pc += 4; 282 | 283 | Ok(()) 284 | } 285 | 286 | OPCODE_ARITHMETIC | OPCODE_ARITHMETIC_IMM => { 287 | let rd = decode_rd!(ir); 288 | let rs1 = decode_rs1!(ir); 289 | let lhs = match rf.read(rs1) { 290 | Ok(d) => d, 291 | Err(why) => return Err(why), 292 | }; 293 | let rhs = match opcode { 294 | OPCODE_ARITHMETIC => match rf.read(decode_rs2!(ir)) { 295 | Ok(d) => d, 296 | Err(why) => return Err(why), 297 | }, 298 | OPCODE_ARITHMETIC_IMM => decode_i_imm!(ir), 299 | _ => return Err(RiscvError::InvalidOpcodeError(ir, decode_opcode!(ir))), 300 | }; 301 | let ir_name: &str; 302 | let bi_operator = match decode_func3!(ir) { 303 | // This func3 is complicated, it depends on whether we're 304 | // using immediates or not. 305 | FUNC3_ADD_SUB => match opcode { 306 | OPCODE_ARITHMETIC => match decode_func3!(ir) { 307 | FUNC7_SUB => { 308 | ir_name = "sub"; 309 | |l: u32, r: u32| l.wrapping_add(r) 310 | } 311 | FUNC7_ADD => { 312 | ir_name = "add"; 313 | |l: u32, r: u32| l.wrapping_add(r) 314 | } 315 | _ => return Err(RiscvError::InvalidFunc7Error(ir, decode_func7!(ir))), 316 | }, 317 | OPCODE_ARITHMETIC_IMM => { 318 | ir_name = "add"; 319 | |l: u32, r: u32| l.wrapping_add(r) 320 | } 321 | _ => return Err(RiscvError::InvalidOpcodeError(ir, decode_opcode!(ir))), 322 | }, 323 | FUNC3_SLL => { 324 | ir_name = "sll"; 325 | |l: u32, r: u32| l << r 326 | } 327 | FUNC3_SLT => { 328 | ir_name = "slt"; 329 | // sign-extension 330 | |l: u32, r: u32| if (l as i32) < (r as i32) { 1 } else { 0 } 331 | } 332 | FUNC3_SLTU => { 333 | ir_name = "sltu"; 334 | |l: u32, r: u32| if l < r { 1 } else { 0 } 335 | } 336 | FUNC3_XOR => { 337 | ir_name = "xor"; 338 | |l: u32, r: u32| l ^ r 339 | } 340 | FUNC3_SR => match decode_func7!(ir) { 341 | FUNC7_SRA => { 342 | ir_name = "sra"; 343 | |l: u32, r: u32| ((l as i32) >> r) as u32 // sign-extension 344 | } 345 | FUNC7_SRL => { 346 | ir_name = "srl"; 347 | |l: u32, r: u32| l >> r 348 | } 349 | _ => return Err(RiscvError::InvalidFunc3Error(ir, decode_func3!(ir))), 350 | }, 351 | FUNC3_OR => { 352 | ir_name = "or"; 353 | |l: u32, r: u32| l | r 354 | } 355 | FUNC3_AND => { 356 | ir_name = "and"; 357 | |l: u32, r: u32| l & r 358 | } 359 | _ => return Err(RiscvError::InvalidFunc3Error(ir, decode_func3!(ir))), 360 | }; 361 | 362 | info!( 363 | "{:6} {}, {}, {}", 364 | ir_name.to_owned() 365 | + match opcode { 366 | OPCODE_ARITHMETIC => "", 367 | OPCODE_ARITHMETIC_IMM => "i", 368 | _ => "?", 369 | }, 370 | REG_NAMES[rd as usize], 371 | REG_NAMES[rs1 as usize], 372 | match opcode { 373 | OPCODE_ARITHMETIC => String::from(REG_NAMES[decode_rs2!(ir) as usize]), 374 | OPCODE_ARITHMETIC_IMM => (decode_i_imm!(ir) as i32).to_string(), 375 | _ => String::from("?"), 376 | } 377 | ); 378 | 379 | if let Err(why) = rf.write(decode_rd!(ir), bi_operator(lhs, rhs)) { 380 | return Err(why); 381 | } 382 | *pc += 4; 383 | 384 | Ok(()) 385 | } 386 | _ => Err(RiscvError::InvalidOpcodeError(ir, decode_opcode!(ir))), 387 | }; 388 | } 389 | -------------------------------------------------------------------------------- /isa-sim/src/lib.rs: -------------------------------------------------------------------------------- 1 | /// Decoding macros. 2 | pub mod decode; 3 | /// Enumeration for errors thrown by an MCU. 4 | mod error; 5 | /// Execution and decoding logic. 6 | mod exec; 7 | 8 | /// Traits to be implementation by other implementations of 9 | /// an MCU. 10 | pub mod traits; 11 | 12 | #[cfg(test)] 13 | mod test; 14 | 15 | /// Re-export common library. 16 | pub use lib_rv32_common as common; 17 | 18 | pub use error::RiscvError; 19 | pub use exec::exec_one; 20 | -------------------------------------------------------------------------------- /isa-sim/src/test.rs: -------------------------------------------------------------------------------- 1 | use lib_rv32_common::{constants::*, instructions, *}; 2 | 3 | use crate::*; 4 | 5 | macro_rules! assert_eq { 6 | ($a:expr, $b:expr) => { 7 | std::assert_eq!($a, $b, "\n{:032b}\n{:032b}", $a, $b) 8 | }; 9 | } 10 | 11 | #[test] 12 | fn test_decode_opcode() { 13 | assert_eq!(OPCODE_LUI, decode_opcode!(instructions::LUI_X5_4)); 14 | assert_eq!(OPCODE_AUIPC, decode_opcode!(instructions::AUIPC_X5_4)); 15 | assert_eq!(OPCODE_JAL, decode_opcode!(instructions::JAL_X0_16)); 16 | assert_eq!(OPCODE_JALR, decode_opcode!(instructions::JALR_X5_X5_4)); 17 | assert_eq!(OPCODE_BRANCH, decode_opcode!(instructions::BEQ_X5_X5_12)); 18 | assert_eq!(OPCODE_LOAD, decode_opcode!(instructions::LW_X5_0_X5)); 19 | assert_eq!(OPCODE_STORE, decode_opcode!(instructions::SW_X5_0_X5)); 20 | } 21 | 22 | #[test] 23 | fn test_decode_func3() { 24 | assert_eq!(FUNC3_BEQ, decode_func3!(instructions::BEQ_X5_X5_12)); 25 | assert_eq!(FUNC3_BNE, decode_func3!(instructions::BNE_X5_X5_76)); 26 | assert_eq!(FUNC3_BLT, decode_func3!(instructions::BLT_X5_X5_72)); 27 | assert_eq!(FUNC3_BGEU, decode_func3!(instructions::BGEU_X5_X5_68)); 28 | assert_eq!(FUNC3_LB, decode_func3!(instructions::LB_X5_0_X5)); 29 | assert_eq!(FUNC3_LBU, decode_func3!(instructions::LBU_X5_0_X5)); 30 | assert_eq!(FUNC3_LH, decode_func3!(instructions::LH_X5_0_X5)); 31 | assert_eq!(FUNC3_LHU, decode_func3!(instructions::LHU_X5_0_X5)); 32 | assert_eq!(FUNC3_SB, decode_func3!(instructions::SB_X5_0_X5)); 33 | assert_eq!(FUNC3_SH, decode_func3!(instructions::SH_X5_0_X5)); 34 | assert_eq!(FUNC3_SW, decode_func3!(instructions::SW_X5_0_X5)); 35 | assert_eq!(FUNC3_ADD_SUB, decode_func3!(instructions::ADDI_X0_X0_17)); 36 | assert_eq!(FUNC3_ADD_SUB, decode_func3!(instructions::SUB_X5_X5_X5)); 37 | assert_eq!(FUNC3_SLL, decode_func3!(instructions::SLLI_X5_X5_1)); 38 | assert_eq!(FUNC3_SLT, decode_func3!(instructions::SLTI_X5_X5_1)); 39 | assert_eq!(FUNC3_SLTU, decode_func3!(instructions::SLTU_X5_X5_X5)); 40 | assert_eq!(FUNC3_XOR, decode_func3!(instructions::XORI_X5_X5_1)); 41 | assert_eq!(FUNC3_SR, decode_func3!(instructions::SRAI_X5_X5_1)); 42 | assert_eq!(FUNC3_OR, decode_func3!(instructions::ORI_X5_X5_1)); 43 | assert_eq!(FUNC3_AND, decode_func3!(instructions::ANDI_X5_X5_1)); 44 | } 45 | 46 | #[test] 47 | fn test_decode_i_imm() { 48 | assert_eq!(17, decode_i_imm!(instructions::ADDI_X0_X0_17)); 49 | assert_eq!(82, decode_i_imm!(instructions::XORI_X5_X6_82)); 50 | assert_eq!(0, decode_i_imm!(instructions::ADDI_X5_X6_0)); 51 | assert_eq!(2047, decode_i_imm!(instructions::ADDI_X5_X6_2047)); 52 | assert_eq!(-12, decode_i_imm!(instructions::ADDI_X5_X6_NEG_12) as i32); 53 | assert_eq!(-1, decode_i_imm!(instructions::ADDI_X5_X6_NEG_1) as i32); 54 | assert_eq!( 55 | -2048, 56 | decode_i_imm!(instructions::ADDI_X5_X6_NEG_2048) as i32 57 | ); 58 | } 59 | 60 | #[test] 61 | fn test_decode_j_imm() { 62 | assert_eq!(-8, decode_j_imm!(instructions::JAL_X0_NEG_8) as i32); 63 | assert_eq!(16, decode_j_imm!(instructions::JAL_X0_16)); 64 | } 65 | 66 | #[test] 67 | fn test_decode_b_imm() {} 68 | 69 | #[test] 70 | fn test_decode_s_imm() { 71 | assert_eq!(0, decode_s_imm!(instructions::SW_X5_0_X5)); 72 | assert_eq!(16, decode_s_imm!(instructions::SW_X5_16_X5)); 73 | assert_eq!(-40, decode_s_imm!(instructions::SW_X5_NEG_40_X5) as i32); 74 | assert_eq!(-36, decode_s_imm!(instructions::SW_A0_NEG_36_SP) as i32); 75 | assert_eq!(-20, decode_s_imm!(instructions::SW_A0_NEG_20_S0) as i32); 76 | } 77 | 78 | #[test] 79 | fn test_decode_rs1() { 80 | for i in 0..32 { 81 | assert_eq!( 82 | i as u8, 83 | decode_rs1!(instructions::ADD_SAME_REG_FIELDS_IRS[i]) 84 | ); 85 | } 86 | } 87 | 88 | #[test] 89 | fn test_decode_rs2() { 90 | for i in 0..32 { 91 | assert_eq!( 92 | i as u8, 93 | decode_rs2!(instructions::ADD_SAME_REG_FIELDS_IRS[i]) 94 | ); 95 | } 96 | } 97 | 98 | #[test] 99 | fn test_decode_rd() { 100 | for i in 0..32 { 101 | assert_eq!( 102 | i as u8, 103 | decode_rd!(instructions::ADD_SAME_REG_FIELDS_IRS[i]) 104 | ); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /isa-sim/src/traits.rs: -------------------------------------------------------------------------------- 1 | use crate::RiscvError; 2 | 3 | /// Trait to be implemented by a RISC-V register file. Should support 4 | /// reads to registers 0-31 and writes to registers 1-31. 5 | pub trait RegisterFile { 6 | /// Read a value from the register numbered `num`. Returns a `Result` containing 7 | /// an error, or the `u32` data contained. 8 | fn read(&self, num: u8) -> Result; 9 | 10 | /// Write a value `data` to the register numbered `num`. Returns a `Result` containing 11 | /// an error if one occured, otherwise returns an empty `Result`. 12 | fn write(&mut self, num: u8, data: u32) -> Result<(), RiscvError>; 13 | } 14 | 15 | pub trait Memory { 16 | /// This should have the same behavior as `read_word`, with the distinction that 17 | /// it does not generate logs or count as an access for the purpose of performance 18 | /// counters. 19 | fn fetch(&self, pc: u32) -> Result; 20 | 21 | /// Read a 32-bit word from the address `addr`. Returns a `Result` containing 22 | /// an error, or the `u32` data contained. 23 | fn read_word(&self, addr: u32) -> Result; 24 | 25 | /// Read a 16-bit half-word from the address `addr`. Returns a `Result` containing 26 | /// an error, or the `u32` data contained. 27 | fn read_half_word(&self, addr: u32) -> Result; 28 | 29 | /// Read a byte from the address `addr`. Returns a `Result` containing 30 | /// an error, or the `u32` data contained. 31 | fn read_byte(&self, addr: u32) -> Result; 32 | 33 | /// Write a 32-bit word `data` to the address `addr`. Returns a `Result` containing 34 | /// an error, otherwise returns an empty `Result`. 35 | /// 36 | /// This makes no guarantees about endianness, only that `read_word` returns the same 37 | /// data after a `write_word`. 38 | fn write_word(&mut self, addr: u32, data: u32) -> Result<(), RiscvError>; 39 | 40 | /// Write 16-bit half-word `data` to the address `addr`. Returns a `Result` containing 41 | /// an error, otherwise returns an empty `Result`. 42 | /// 43 | /// This makes no guarantees about endianness, only that `read_half_word` returns the same 44 | /// data after a `write_half_word`. 45 | fn write_half_word(&mut self, addr: u32, data: u32) -> Result<(), RiscvError>; 46 | 47 | /// Write byte `data` to the address `addr`. Returns a `Result` containing 48 | /// an error, otherwise returns an empty `Result`. 49 | /// 50 | /// This makes no guarantees about endianness, only that `read_byte` returns the same 51 | /// data after a `write_byte`. 52 | fn write_byte(&mut self, addr: u32, data: u32) -> Result<(), RiscvError>; 53 | } 54 | -------------------------------------------------------------------------------- /isa-sim/src/util.rs: -------------------------------------------------------------------------------- 1 | /// Parse an integer from a string slice. If it leads with `0x` or `0X`, it 2 | /// will be parsed as base 16, otherwise it will be parsed as base 10. 3 | #[macro_export] 4 | macro_rules! parse_int { 5 | ($t:ty,$s:expr) => {{ 6 | if !$s.to_ascii_lowercase().starts_with("0x") { 7 | $s.parse() 8 | } else { 9 | <$t>::from_str_radix(&$s[2..], 16) 10 | } 11 | }}; 12 | } 13 | 14 | #[cfg(test)] 15 | mod tests { 16 | use crate::parse_int; 17 | 18 | #[test] 19 | fn test_parse_int() { 20 | assert_eq!(17, parse_int!(u32, "17").unwrap()); 21 | assert_eq!(17, parse_int!(u32, "017").unwrap()); 22 | assert_eq!(17, parse_int!(i32, "17").unwrap()); 23 | 24 | assert_eq!(-21, parse_int!(i32, "-21").unwrap()); 25 | 26 | assert_eq!(0x16, parse_int!(u32, "0x16").unwrap()); 27 | assert_eq!(0x16, parse_int!(u32, "0x0016").unwrap()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /mcu/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lib-rv32-mcu" 3 | description = "A library and CLI tool for emulating, testing, and learning RISC-V." 4 | keywords = ["riscv", "testing", "simulator", "emulator"] 5 | authors = ["Trevor McKay "] 6 | homepage = "https://github.com/trmckay/lib-rv32" 7 | repository = "https://github.com/trmckay/lib-rv32" 8 | version = "0.2.1" 9 | edition = "2018" 10 | license = "MIT" 11 | readme = "../README.md" 12 | exclude = ["build.rs", "build-tests.sh", "programs"] 13 | 14 | [lib] 15 | name = "lib_rv32_mcu" 16 | path = "src/lib.rs" 17 | 18 | [dependencies] 19 | log = "0.4.*" 20 | lib-rv32-isa = "0.2.0" 21 | serde = { version = "1.0", features = ["derive"] } 22 | wasm-bindgen = { version = "0.2", features = ["serde-serialize"] } 23 | 24 | [dev-dependencies] 25 | serde_json = "1.0.*" 26 | glob = "0.3.*" 27 | -------------------------------------------------------------------------------- /mcu/build-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | for dir in `find ./programs -type d -not -path ./programs`; do 6 | (cd $dir && make -f ../Makefile) 7 | done 8 | -------------------------------------------------------------------------------- /mcu/build.rs: -------------------------------------------------------------------------------- 1 | #[cfg(debug_assertions)] 2 | use std::{ 3 | io::{self, Write}, 4 | process, 5 | }; 6 | 7 | #[cfg(debug_assertions)] 8 | fn build_tests() { 9 | let output = process::Command::new("./build-tests.sh") 10 | .output() 11 | .expect("Failed to execute test build script."); 12 | 13 | io::stdout().write_all(&output.stdout).unwrap(); 14 | io::stderr().write_all(&output.stderr).unwrap(); 15 | 16 | assert!(output.status.success()); 17 | } 18 | 19 | fn main() { 20 | #[cfg(debug_assertions)] 21 | build_tests(); 22 | } 23 | -------------------------------------------------------------------------------- /mcu/programs/Makefile: -------------------------------------------------------------------------------- 1 | BUILD = . 2 | SRC = . 3 | 4 | # source files 5 | C_FILES := $(wildcard $(SRC)/*.c) 6 | AS_FILES := $(wildcard $(SRC)/*.s) 7 | ASP_FILES := $(wildcard $(SRC)/*.S) 8 | 9 | # object files 10 | C_OBJ_FILES := $(patsubst $(SRC)/%.c,$(BUILD)/%.o,$(C_FILES)) 11 | AS_OBJ_FILES := $(patsubst $(SRC)/%.s,$(BUILD)/%.o,$(AS_FILES)) 12 | ASP_OBJ_FILES := $(patsubst $(SRC)/%.S,$(BUILD)/%.o,$(ASP_FILES)) 13 | 14 | LINKER_SCRIPT = ../link.ld 15 | 16 | USE_RV64 := $(shell command -v riscv64-unknown-elf-gcc 2> /dev/null) 17 | ifdef USE_RV64 18 | RISCV_PREFIX = riscv64-unknown-elf- 19 | else 20 | RISCV_PREFIX = riscv32-unknown-elf- 21 | endif 22 | 23 | CC = $(RISCV_PREFIX)gcc 24 | LD = $(RISCV_PREFIX)ld 25 | OBJCOPY = $(RISCV_PREFIX)objcopy 26 | OBJDUMP = $(RISCV_PREFIX)objdump 27 | 28 | TARGET_ARCH = -march=rv32i -mabi=ilp32 29 | CCFLAGS = -O0 $(TARGET_ARCH) -mno-relax -nostdlib -nostartfiles -mcmodel=medany 30 | 31 | all: $(BUILD)/prog.bin $(BUILD)/dump.txt cleanup 32 | 33 | $(BUILD)/%.o: $(SRC)/%.c 34 | $(CC) -c -o $@ $< $(CCFLAGS) 35 | 36 | $(BUILD)/%.o: $(SRC)/%.s 37 | $(CC) -c -o $@ $< $(CCFLAGS) 38 | 39 | $(BUILD)/%.o: $(SRC)/%.S 40 | $(CC) -c -o $@ $< $(CCFLAGS) 41 | 42 | $(BUILD)/prog.elf: ${AS_OBJ_FILES} ${C_OBJ_FILES} ${ASP_OBJ_FILES} 43 | $(CC) -o $@ $^ -T $(LINKER_SCRIPT) $(CCFLAGS) 44 | 45 | $(BUILD)/dump.txt: $(BUILD)/prog.elf 46 | $(OBJDUMP) -S $< > $@ 47 | 48 | $(BUILD)/prog.bin: $(BUILD)/prog.elf 49 | $(OBJCOPY) -O binary --only-section=.data* --only-section=.text* $< $@ 50 | 51 | cleanup: 52 | rm -rf $(C_OBJ_FILES) $(ASP_OBJ_FILES) $(AS_OBJ_FILES) $(BUILD)/prog.elf 53 | 54 | print-% : ; @echo $* = $($*) 55 | -------------------------------------------------------------------------------- /mcu/programs/link.ld: -------------------------------------------------------------------------------- 1 | __sp = 0x10000; 2 | 3 | SECTIONS 4 | { 5 | . =0x00000000; 6 | .text.init : { 7 | *(.text.init) 8 | } 9 | 10 | .text : { 11 | *(.text) 12 | } 13 | 14 | __global_pointer$ = . + 0x800; 15 | .data : { 16 | *(.sbss) 17 | *(COMMON) 18 | *(.bss) 19 | 20 | *(.data*) 21 | *(.sdata) 22 | *(.srodata*) 23 | *(.rodata) 24 | *(.got*) 25 | /**(._edata*)*/ 26 | *(._end) 27 | /*_edata=.;*/ 28 | _edata = .; PROVIDE (edata = .); 29 | . = .; 30 | } 31 | 32 | /*_edata = .; PROVIDE (edata = .);*/ 33 | _end=.; 34 | } 35 | -------------------------------------------------------------------------------- /mcu/programs/mul/init.s: -------------------------------------------------------------------------------- 1 | .section .text.init 2 | 3 | start: 4 | la sp, __sp-32 5 | 6 | li a0, 4 7 | li a1, 5 8 | 9 | call mul 10 | 11 | li t0, 0x0F00 12 | sw a0, 0(t0) 13 | 14 | end: j end 15 | -------------------------------------------------------------------------------- /mcu/programs/mul/mul.c: -------------------------------------------------------------------------------- 1 | typedef unsigned int u32; 2 | 3 | u32 mul(u32 a, u32 b) { 4 | u32 i; 5 | u32 r = 0; 6 | 7 | for (i = b; i > 0; i--) { 8 | r += a; 9 | } 10 | 11 | return r; 12 | } -------------------------------------------------------------------------------- /mcu/programs/mul/test_case.json: -------------------------------------------------------------------------------- 1 | { 2 | "max_cycles": 80, 3 | "stop_pc": 24, 4 | "assertions": { 5 | "registers": { 6 | "a0": 20 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /mcu/src/lib.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// Contains reference `Memory` struct. 4 | mod memory; 5 | 6 | /// Contains referende `RegisterFile` struct. 7 | mod register_file; 8 | 9 | #[cfg(test)] 10 | mod test_runner; 11 | 12 | /// Re-export ISA simulator. 13 | pub use lib_rv32_isa as isa; 14 | 15 | /// Re-export common library. 16 | pub use lib_rv32_isa::common; 17 | 18 | pub use memory::*; 19 | pub use register_file::*; 20 | 21 | /// Reference implementation of an MCU. Contains a PC, 22 | /// register file, and memory. 23 | #[derive(Clone, Serialize, Deserialize)] 24 | pub struct Mcu { 25 | pub pc: u32, 26 | pub mem: Memory, 27 | pub rf: RegisterFile, 28 | } 29 | 30 | impl Mcu { 31 | /// Construct an MCU with the provided memory size. 32 | pub fn new(size: usize) -> Self { 33 | Mcu { 34 | pc: 0, 35 | mem: Memory::new(size), 36 | rf: RegisterFile::new(), 37 | } 38 | } 39 | } 40 | 41 | #[cfg(test)] 42 | mod tests { 43 | use super::*; 44 | use lib_rv32_isa::{common::instructions, exec_one}; 45 | 46 | const MEM_SIZE: u32 = 0x10000; 47 | 48 | #[test] 49 | fn test_addi_x5_x5_1() { 50 | let mut mcu = Mcu::new(MEM_SIZE as usize); 51 | let bytes = instructions::ADDI_X5_X5_1.to_le_bytes(); 52 | mcu.mem.program_le_bytes(&bytes).unwrap(); 53 | exec_one(&mut mcu.pc, &mut mcu.mem, &mut mcu.rf).unwrap(); 54 | 55 | for i in 0..32 { 56 | assert_eq!( 57 | match i { 58 | 5 => 1, 59 | _ => 0, 60 | }, 61 | mcu.rf.read(i).unwrap() 62 | ); 63 | } 64 | 65 | for i in 1..(MEM_SIZE / 4) { 66 | assert_eq!(0, mcu.mem.read_word(i * 4).unwrap()); 67 | } 68 | 69 | assert_eq!(4, mcu.pc); 70 | } 71 | 72 | #[test] 73 | fn test_addi_x5_x6_neg_1() { 74 | let mut mcu = Mcu::new(MEM_SIZE as usize); 75 | let bytes = instructions::ADDI_X5_X6_NEG_1.to_le_bytes(); 76 | mcu.mem.program_le_bytes(&bytes).unwrap(); 77 | exec_one(&mut mcu.pc, &mut mcu.mem, &mut mcu.rf).unwrap(); 78 | 79 | for i in 0..32 { 80 | assert_eq!( 81 | match i { 82 | 5 => -1, 83 | _ => 0, 84 | }, 85 | mcu.rf.read(i).unwrap() as i32 86 | ); 87 | } 88 | 89 | for i in 1..(MEM_SIZE / 4) { 90 | assert_eq!(0, mcu.mem.read_word(i * 4).unwrap()); 91 | } 92 | 93 | assert_eq!(4, mcu.pc); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /mcu/src/memory.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, path::Path}; 2 | 3 | use log::info; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | pub use lib_rv32_isa::traits::Memory as MemoryTrait; 7 | use lib_rv32_isa::{common::bit_slice, RiscvError}; 8 | 9 | /// Heap allocated, little-endian implementation of memory. 10 | #[derive(Clone, Serialize, Deserialize)] 11 | pub struct Memory { 12 | pub size: usize, 13 | mem: Vec, 14 | } 15 | 16 | impl Memory { 17 | /// Allocate a memory with the given size. 18 | pub fn new(size: usize) -> Self { 19 | assert!(size % 4 == 0); 20 | assert!(size > 0); 21 | 22 | Memory { 23 | size, 24 | mem: vec![0; size], 25 | } 26 | } 27 | 28 | /// Read a little-endian number from a byte-vector of arbitrary size. 29 | fn read(&self, base: usize, size: usize, log: bool) -> Result { 30 | // Check if read falls on a word, half-word, or byte boundary. 31 | if base % size != 0 { 32 | return Err(RiscvError::MemoryAlignmentError(base as u32)); 33 | // Check that the read is within bounds. 34 | } else if base >= self.size as usize { 35 | return Err(RiscvError::MemoryOutOfBoundsError(base as u32)); 36 | } 37 | 38 | let data = self.mem[base..base + size] 39 | .iter() 40 | .enumerate() 41 | .map(|(i, b)| ((*b as u32) << (i * 8)) as u32) 42 | .sum(); 43 | 44 | if log { 45 | match size { 46 | 1 => info!("(byte *)0x{:08x} = 0x{:x} ({})", base, data, data as i32), 47 | 2 => info!( 48 | "(half-word *)0x{:08x} = 0x{:x} ({})", 49 | base, data, data as i32 50 | ), 51 | 4 => info!("(word *)0x{:08x} = 0x{:x} ({})", base, data, data as i32), 52 | _ => (), 53 | } 54 | } 55 | 56 | Ok(data) 57 | } 58 | 59 | /// Write a little-endian number of arbitrary size. 60 | fn write(&mut self, base: usize, data: u32, size: usize, log: bool) -> Result<(), RiscvError> { 61 | if log { 62 | match size { 63 | 1 => info!("(byte *)0x{:08x} <- 0x{:x} ({})", base, data, data as i32), 64 | 2 => info!( 65 | "(half-word *)0x{:08x} <- 0x{:x} ({})", 66 | base, data, data as i32 67 | ), 68 | 4 => info!("(word *)0x{:08x} <- 0x{:x} ({})", base, data, data as i32), 69 | _ => (), 70 | } 71 | } 72 | 73 | // Check if read falls on a word, half-word, or byte boundary. 74 | if base % size != 0 { 75 | return Err(RiscvError::MemoryAlignmentError(base as u32)); 76 | // Check that the read is within bounds. 77 | } else if base >= self.size as usize { 78 | return Err(RiscvError::MemoryOutOfBoundsError(base as u32)); 79 | } 80 | 81 | for (i, b) in self.mem[base..base + size].iter_mut().enumerate() { 82 | *b = bit_slice!(data, 8 * (i + 1), 8 * i) as u8; 83 | } 84 | 85 | Ok(()) 86 | } 87 | 88 | /// Program the memory from a vector of little-endian bytes. 89 | pub fn program_le_bytes(&mut self, bytes: &[u8]) -> Result<(), RiscvError> { 90 | for (word_addr, chunk) in bytes.chunks(4).enumerate() { 91 | for (byte_offset, byte) in chunk.iter().enumerate() { 92 | if let Err(why) = self.write(word_addr * 4 + byte_offset, *byte as u32, 1, false) { 93 | return Err(why); 94 | } 95 | } 96 | } 97 | Ok(()) 98 | } 99 | 100 | /// Program the memory from a vector of words. 101 | pub fn program_words(&mut self, words: &[u32]) -> Result<(), RiscvError> { 102 | for (addr, word) in words.iter().enumerate() { 103 | if let Err(why) = self.write(addr * 4, *word as u32, 4, false) { 104 | return Err(why); 105 | } 106 | } 107 | Ok(()) 108 | } 109 | 110 | /// Program the memory from a binary file generally created by gcc or clang. 111 | pub fn program_from_file(&mut self, path: &Path) -> Result { 112 | let prog_bytes = fs::read(&path).expect("Could not read binary."); 113 | match self.program_le_bytes(&prog_bytes) { 114 | Err(why) => Err(why), 115 | Ok(_) => Ok(prog_bytes.len() as u32), 116 | } 117 | } 118 | } 119 | 120 | // Implement the trait that allows us to execute instructions on this memory. 121 | impl MemoryTrait for Memory { 122 | fn fetch(&self, pc: u32) -> Result { 123 | self.read(pc as usize, 4, false) 124 | } 125 | 126 | fn read_word(&self, addr: u32) -> Result { 127 | self.read(addr as usize, 4, true) 128 | } 129 | 130 | fn read_half_word(&self, addr: u32) -> Result { 131 | match self.read(addr as usize, 2, true) { 132 | Ok(d) => Ok(d), 133 | Err(why) => Err(why), 134 | } 135 | } 136 | fn read_byte(&self, addr: u32) -> Result { 137 | match self.read(addr as usize, 1, true) { 138 | Ok(d) => Ok(d), 139 | Err(why) => Err(why), 140 | } 141 | } 142 | 143 | fn write_word(&mut self, addr: u32, data: u32) -> Result<(), RiscvError> { 144 | self.write(addr as usize, data, 4, true) 145 | } 146 | 147 | fn write_half_word(&mut self, addr: u32, data: u32) -> Result<(), RiscvError> { 148 | self.write(addr as usize, data as u32, 2, true) 149 | } 150 | 151 | fn write_byte(&mut self, addr: u32, data: u32) -> Result<(), RiscvError> { 152 | self.write(addr as usize, data as u32, 1, true) 153 | } 154 | } 155 | 156 | #[cfg(test)] 157 | mod test { 158 | use super::*; 159 | 160 | #[test] 161 | #[should_panic] 162 | fn test_create_misaligned() { 163 | let _ = Memory::new(3); 164 | } 165 | 166 | #[test] 167 | #[should_panic] 168 | fn test_create_zero() { 169 | let _ = Memory::new(0); 170 | } 171 | 172 | #[test] 173 | fn test_out_of_bounds() { 174 | let mem = Memory::new(1024); 175 | match mem.read_byte(1028) { 176 | Err(why) => assert_eq!(why, RiscvError::MemoryOutOfBoundsError(1028)), 177 | _ => panic!(), 178 | }; 179 | } 180 | 181 | #[test] 182 | fn test_misaligned() { 183 | let mut mem = Memory::new(1024); 184 | 185 | match mem.read_half_word(3) { 186 | Err(why) => assert_eq!(why, RiscvError::MemoryAlignmentError(3)), 187 | _ => panic!(), 188 | }; 189 | match mem.read_word(2) { 190 | Err(why) => assert_eq!(why, RiscvError::MemoryAlignmentError(2)), 191 | _ => panic!(), 192 | }; 193 | match mem.write_half_word(3, 0) { 194 | Err(why) => assert_eq!(why, RiscvError::MemoryAlignmentError(3)), 195 | _ => panic!(), 196 | }; 197 | match mem.write_word(2, 0) { 198 | Err(why) => assert_eq!(why, RiscvError::MemoryAlignmentError(2)), 199 | _ => panic!(), 200 | }; 201 | } 202 | 203 | #[test] 204 | fn test_create() { 205 | let mem = Memory::new(1024); 206 | assert_eq!(1024, mem.size); 207 | } 208 | 209 | #[test] 210 | fn test_byte() { 211 | let mut mem = Memory::new(1024); 212 | 213 | for data in 0..0xFF { 214 | for addr in 0..16 { 215 | mem.write_byte(addr, data).unwrap(); 216 | assert_eq!(data, mem.read_byte(addr).unwrap()); 217 | } 218 | } 219 | } 220 | 221 | #[test] 222 | fn test_half_word_write() { 223 | const ADDR: u32 = 0x02; 224 | let mut mem = Memory::new(1024); 225 | 226 | mem.write_half_word(ADDR, 0x1712).unwrap(); 227 | 228 | // Is it little-endian? 229 | assert_eq!(mem.mem[ADDR as usize], 0x12); 230 | assert_eq!(mem.mem[(ADDR + 1) as usize], 0x17); 231 | } 232 | 233 | #[test] 234 | fn test_half_word_read() { 235 | const ADDR: u32 = 0x02; 236 | let mut mem = Memory::new(1024); 237 | 238 | // mem[ADDR] = 0x1712; 239 | mem.mem[ADDR as usize] = 0x12; 240 | mem.mem[(ADDR + 1) as usize] = 0x17; 241 | 242 | assert_eq!(0x1712, mem.read_half_word(ADDR).unwrap()); 243 | } 244 | 245 | #[test] 246 | fn test_half_word_read_write() { 247 | const ADDR: u32 = 0x02; 248 | let mut mem = Memory::new(1024); 249 | for data in 0..0xFFFF { 250 | mem.write_half_word(ADDR, data).unwrap(); 251 | assert_eq!(data, mem.read_half_word(ADDR).unwrap()); 252 | } 253 | } 254 | 255 | #[test] 256 | fn test_word_write() { 257 | const ADDR: u32 = 0x04; 258 | let mut mem = Memory::new(1024); 259 | 260 | mem.write_word(ADDR, 0x76821712).unwrap(); 261 | 262 | // Is it little-endian? 263 | assert_eq!(mem.mem[ADDR as usize], 0x12); 264 | assert_eq!(mem.mem[(ADDR + 1) as usize], 0x17); 265 | assert_eq!(mem.mem[(ADDR + 2) as usize], 0x82); 266 | assert_eq!(mem.mem[(ADDR + 3) as usize], 0x76); 267 | } 268 | 269 | #[test] 270 | fn test_word_read() { 271 | const ADDR: u32 = 0x04; 272 | let mut mem = Memory::new(1024); 273 | 274 | // mem[ADDR] = 0x1712; 275 | mem.mem[ADDR as usize] = 0x12; 276 | mem.mem[(ADDR + 1) as usize] = 0x17; 277 | mem.mem[(ADDR + 2) as usize] = 0x82; 278 | mem.mem[(ADDR + 3) as usize] = 0x76; 279 | 280 | assert_eq!(0x76821712, mem.read_word(ADDR).unwrap()); 281 | } 282 | 283 | #[test] 284 | fn test_word_read_write() { 285 | const ADDR: u32 = 0x04; 286 | let mut mem = Memory::new(1024); 287 | for data in 0xFE000000..0xFE100000 { 288 | mem.write_word(ADDR, data).unwrap(); 289 | assert_eq!(data, mem.read_word(ADDR).unwrap()); 290 | } 291 | } 292 | 293 | #[test] 294 | fn test_program_little_endian() { 295 | const NUM: u32 = 0x12345678; 296 | const LE_BYTES: [u8; 4] = [0x78, 0x56, 0x34, 0x12]; 297 | 298 | let mut mem = Memory::new(1024); 299 | mem.program_le_bytes(&LE_BYTES).unwrap(); 300 | 301 | assert_eq!(NUM, mem.read_word(0).unwrap()); 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /mcu/src/register_file.rs: -------------------------------------------------------------------------------- 1 | use log::info; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | pub use lib_rv32_isa::traits::RegisterFile as RegisterFileTrait; 5 | use lib_rv32_isa::{common::constants::*, RiscvError}; 6 | 7 | /// Heap allocated implementation of a register file. 8 | #[derive(Default, Clone, Serialize, Deserialize)] 9 | pub struct RegisterFile { 10 | registers: Vec, 11 | } 12 | 13 | impl RegisterFile { 14 | pub fn new() -> Self { 15 | RegisterFile { 16 | registers: vec![0; 31], 17 | } 18 | } 19 | } 20 | 21 | impl RegisterFileTrait for RegisterFile { 22 | fn write(&mut self, num: u8, data: u32) -> Result<(), RiscvError> { 23 | if num > 31 { 24 | return Err(RiscvError::RegisterOutOfRangeError(num)); 25 | } else if num >= 1 { 26 | self.registers[num as usize - 1] = data; 27 | } 28 | 29 | info!( 30 | "{} <- 0x{:x} ({})", 31 | REG_NAMES[num as usize], data, data as i32 32 | ); 33 | 34 | Ok(()) 35 | } 36 | 37 | fn read(&self, num: u8) -> Result { 38 | if num == 0 { 39 | Ok(0) 40 | } else if num > 31 { 41 | Err(RiscvError::RegisterOutOfRangeError(num)) 42 | } else { 43 | Ok(self.registers[num as usize - 1]) 44 | } 45 | } 46 | } 47 | 48 | #[cfg(test)] 49 | mod tests { 50 | use super::*; 51 | 52 | #[test] 53 | fn test_zero() { 54 | let mut rf = RegisterFile::new(); 55 | rf.write(0, 17).unwrap(); 56 | assert_eq!(0, rf.read(0).unwrap()); 57 | } 58 | 59 | #[test] 60 | fn test_read_write() { 61 | let mut rf = RegisterFile::new(); 62 | for i in 0..128 { 63 | let d = i << 16; 64 | for n in 0..32 { 65 | rf.write(n, d).unwrap(); 66 | assert_eq!(if n == 0 { 0 } else { d }, rf.read(n).unwrap()); 67 | } 68 | } 69 | } 70 | 71 | #[test] 72 | fn test_out_of_range() { 73 | assert_eq!( 74 | Err(RiscvError::RegisterOutOfRangeError(32)), 75 | RegisterFile::new().read(32) 76 | ) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /mcu/src/test_runner.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, path::Path}; 2 | 3 | use glob::glob; 4 | use log::{info, Level, LevelFilter, Metadata, Record}; 5 | 6 | use lib_rv32_isa::{common::constants::*, exec_one, RiscvError}; 7 | 8 | use crate::*; 9 | 10 | const MEM_SIZE: u32 = 0x10000; 11 | 12 | struct Logger; 13 | 14 | impl log::Log for Logger { 15 | fn enabled(&self, metadata: &Metadata) -> bool { 16 | metadata.level() <= Level::Info 17 | } 18 | 19 | fn log(&self, record: &Record) { 20 | if self.enabled(record.metadata()) { 21 | print!("{}", record.args()); 22 | } 23 | } 24 | 25 | fn flush(&self) {} 26 | } 27 | 28 | static LOGGER: Logger = Logger; 29 | 30 | struct TestResult { 31 | name: String, 32 | dump: String, 33 | state: Box, 34 | err: Option, 35 | why: String, 36 | } 37 | 38 | fn run_test(dir: &Path) -> Result<(), TestResult> { 39 | let test_bin_path_str = format!("{}/prog.bin", dir.display()); 40 | let test_bin_path = Path::new(&test_bin_path_str); 41 | 42 | let test_dump_path_str = format!("{}/dump.txt", dir.display()); 43 | let test_dump_path = Path::new(&test_dump_path_str); 44 | 45 | let test_json_path_str = format!("{}/test_case.json", dir.display()); 46 | let test_json_path = Path::new(&test_json_path_str); 47 | let test_params: serde_json::Value = 48 | serde_json::from_str(&fs::read_to_string(test_json_path).unwrap()).unwrap(); 49 | 50 | let max_cycles = test_params["max_cycles"].as_u64().unwrap(); 51 | let stop_pc = 52 | u32::from_str_radix(&test_params["stop_pc"].as_u64().unwrap().to_string(), 16).unwrap(); 53 | 54 | let mut register_assertions: Vec<(u8, u32)> = Vec::new(); 55 | for (i, name) in REG_NAMES.iter().enumerate() { 56 | if let Some(d) = test_params["assertions"]["registers"][*name].as_u64() { 57 | register_assertions.push((i as u8, d as u32)); 58 | } 59 | } 60 | 61 | info!("{}:\n", test_bin_path_str); 62 | 63 | let mut mcu = Mcu::new(MEM_SIZE as usize); 64 | let prog_len = mcu.mem.program_from_file(test_bin_path).unwrap(); 65 | 66 | let mut cycles = 0; 67 | 68 | loop { 69 | if mcu.pc >= prog_len as u32 { 70 | info!("Stopping because the program has exited the text.\n"); 71 | break; 72 | } 73 | 74 | if mcu.pc == stop_pc { 75 | info!("Stopping because the stop PC 0x{:x} was reached.\n", mcu.pc); 76 | break; 77 | } 78 | 79 | if cycles >= max_cycles as u32 { 80 | info!("Stopping because the cycle limit was reached.\n"); 81 | break; 82 | } 83 | 84 | if let Err(e) = exec_one(&mut mcu.pc, &mut mcu.mem, &mut mcu.rf) { 85 | return Err(TestResult { 86 | name: dir.display().to_string(), 87 | dump: fs::read_to_string(test_dump_path).unwrap(), 88 | state: Box::new(mcu.clone()), 89 | err: Some(e), 90 | why: String::from("Error during execution"), 91 | }); 92 | } 93 | 94 | cycles += 1; 95 | } 96 | 97 | for assertion in register_assertions { 98 | if assertion.1 != mcu.rf.read(assertion.0).unwrap() { 99 | return Err(TestResult { 100 | name: dir.display().to_string(), 101 | dump: fs::read_to_string(test_dump_path).unwrap(), 102 | state: Box::new(mcu.clone()), 103 | err: None, 104 | why: format!( 105 | "Register assertion failed: (x{}=0x{:08x}) != 0x{:08x}.", 106 | assertion.0, 107 | mcu.rf.read(assertion.0).unwrap(), 108 | assertion.1 109 | ), 110 | }); 111 | } 112 | } 113 | 114 | Ok(()) 115 | } 116 | 117 | #[test] 118 | fn test_program_harness() { 119 | log::set_logger(&LOGGER) 120 | .map(|()| log::set_max_level(LevelFilter::Info)) 121 | .unwrap(); 122 | 123 | let mut pass = true; 124 | for dir in match glob("./programs/*") { 125 | Err(_) => return, 126 | Ok(p) => p, 127 | } 128 | .map(|p| p.unwrap()) 129 | { 130 | if dir.is_dir() { 131 | if let Err(res) = run_test(&dir) { 132 | pass = false; 133 | eprint!( 134 | "\n\nFailed test: {}@0x{:08x}: {}", 135 | res.name, res.state.pc, res.why 136 | ); 137 | match res.err { 138 | None => eprintln!(), 139 | Some(e) => eprintln!(": {:?}", e), 140 | } 141 | eprintln!("{}", res.dump); 142 | } else { 143 | eprintln!("{}... ok", dir.display()); 144 | } 145 | (0..64).into_iter().for_each(|_| eprint!("=")); 146 | eprintln!("\n"); 147 | } 148 | } 149 | assert!(pass); 150 | } 151 | -------------------------------------------------------------------------------- /wasm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lib-rv32-wasm" 3 | version = "0.2.0" 4 | edition = "2018" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "rlib"] 8 | 9 | [dependencies] 10 | lib-rv32-asm = "0.2.*" 11 | lib-rv32-isa = "0.2.*" 12 | lib-rv32-mcu = "0.2.*" 13 | lib-rv32-common = "0.2.*" 14 | wasm-bindgen = { version = "0.2", features = ["serde-serialize"] } 15 | log = "0.4.*" 16 | -------------------------------------------------------------------------------- /wasm/node/.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | package-lock.json 3 | -------------------------------------------------------------------------------- /wasm/node/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: "10" 3 | 4 | script: 5 | - ./node_modules/.bin/webpack 6 | -------------------------------------------------------------------------------- /wasm/node/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /wasm/node/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) [year] [name] 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /wasm/node/bootstrap.js: -------------------------------------------------------------------------------- 1 | // A dependency graph that contains any wasm must all be imported 2 | // asynchronously. This `bootstrap.js` file does the single async import, so 3 | // that no one else needs to worry about it again. 4 | import("./index.js") 5 | .catch(e => console.error("Error importing `index.js`:", e)); 6 | -------------------------------------------------------------------------------- /wasm/node/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | lib-rv32 WASM Demo 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 |

15 | 16 | 17 | 18 | 19 |

20 | 21 | 22 | 23 | 24 | 25 |

26 | 27 | 28 | -------------------------------------------------------------------------------- /wasm/node/index.js: -------------------------------------------------------------------------------- 1 | import * as riscv from "lib-rv32"; 2 | 3 | document.getElementById("console").value = "" 4 | document.getElementById("state").value = "" 5 | document.getElementById("text").value = "" 6 | 7 | const state = riscv.State.new(); 8 | document.getElementById("console").value = riscv.get_logs() 9 | 10 | 11 | function assemble() { 12 | var program_buffer = document.getElementById("program").value; 13 | let program = program_buffer.replaceAll('\n', '\\n'); 14 | 15 | state.assemble(program); 16 | 17 | document.getElementById("text").value = state.get_text(); 18 | document.getElementById("console").value = riscv.get_logs() 19 | }; 20 | 21 | function run() { 22 | state.run(); 23 | document.getElementById("state").value = state.get_state(); 24 | document.getElementById("console").value = riscv.get_logs() 25 | } 26 | 27 | document.getElementById("assemble").onclick = assemble; 28 | document.getElementById("run").onclick = run; 29 | -------------------------------------------------------------------------------- /wasm/node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-wasm-app", 3 | "version": "0.1.0", 4 | "description": "create an app to consume rust-generated wasm packages", 5 | "main": "index.js", 6 | "bin": { 7 | "create-wasm-app": ".bin/create-wasm-app.js" 8 | }, 9 | "scripts": { 10 | "build": "webpack --config webpack.config.js", 11 | "start": "webpack-dev-server" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/rustwasm/create-wasm-app.git" 16 | }, 17 | "keywords": [ 18 | "webassembly", 19 | "wasm", 20 | "rust", 21 | "webpack" 22 | ], 23 | "author": "Ashley Williams ", 24 | "license": "(MIT OR Apache-2.0)", 25 | "bugs": { 26 | "url": "https://github.com/rustwasm/create-wasm-app/issues" 27 | }, 28 | "homepage": "https://github.com/rustwasm/create-wasm-app#readme", 29 | "dependencies": { 30 | "lib-rv32": "file:../pkg" 31 | }, 32 | "devDependencies": { 33 | "webpack": "^4.29.3", 34 | "webpack-cli": "^3.1.0", 35 | "webpack-dev-server": "^3.1.5", 36 | "copy-webpack-plugin": "^5.0.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /wasm/node/webpack.config.js: -------------------------------------------------------------------------------- 1 | const CopyWebpackPlugin = require("copy-webpack-plugin"); 2 | const path = require('path'); 3 | 4 | module.exports = { 5 | entry: "./bootstrap.js", 6 | output: { 7 | path: path.resolve(__dirname, "dist"), 8 | filename: "bootstrap.js", 9 | }, 10 | mode: "development", 11 | plugins: [ 12 | new CopyWebpackPlugin(['index.html']) 13 | ], 14 | }; 15 | -------------------------------------------------------------------------------- /wasm/src/lib.rs: -------------------------------------------------------------------------------- 1 | use log::{info, LevelFilter}; 2 | use wasm_bindgen::prelude::*; 3 | 4 | use lib_rv32_asm::assemble_program; 5 | use lib_rv32_common::constants::*; 6 | use lib_rv32_isa::exec_one; 7 | use lib_rv32_mcu::*; 8 | 9 | mod logger; 10 | use logger::*; 11 | 12 | pub const DEFAULT_MEM_SIZE: usize = 1024; 13 | 14 | static LOGGER: Logger = Logger; 15 | static mut CONSOLE_TEXT: String = String::new(); 16 | 17 | #[wasm_bindgen] 18 | pub fn get_logs() -> String { 19 | // Not thread-safe. ¯\_(ツ)_/¯ 20 | unsafe { CONSOLE_TEXT.clone() } 21 | } 22 | 23 | #[wasm_bindgen] 24 | pub struct State { 25 | mcu: Mcu, 26 | text_size: usize, 27 | } 28 | 29 | #[wasm_bindgen] 30 | impl State { 31 | /// Initiate the MCU state. 32 | pub fn new() -> Self { 33 | log::set_logger(&LOGGER) 34 | .map(|()| log::set_max_level(LevelFilter::Info)) 35 | .unwrap(); 36 | 37 | let mcu = Mcu::new(DEFAULT_MEM_SIZE); 38 | info!( 39 | "Initialized RISC-V rv32i MCU with {}k memory.\n", 40 | mcu.mem.size / 1024 41 | ); 42 | 43 | State { mcu, text_size: 0 } 44 | } 45 | 46 | /// Program the MCU with a string program. 47 | pub fn assemble(&mut self, program: String) { 48 | let program = program.replace("\\n", "\n"); 49 | 50 | let words = assemble_program(&program); 51 | 52 | if words.is_err() { 53 | info!("Assembler error: {:?}", words); 54 | return; 55 | } else { 56 | info!("Successfully assembled program.\n"); 57 | let words = words.unwrap(); 58 | self.mcu.mem.program_words(&words).unwrap(); 59 | self.text_size = words.len() * 4; 60 | } 61 | } 62 | 63 | pub fn run(&mut self) { 64 | while self.mcu.pc < self.text_size as u32 { 65 | if let Err(why) = exec_one(&mut self.mcu.pc, &mut self.mcu.mem, &mut self.mcu.rf) { 66 | info!("MCU runtime error: {:?}", why); 67 | return; 68 | } 69 | info!(""); 70 | } 71 | 72 | info!("\nProgram complete."); 73 | } 74 | 75 | pub fn get_text(&self) -> String { 76 | let mut text = String::new(); 77 | for wa in 0..self.text_size / 4 { 78 | text += &format!("{:08x}\n", self.mcu.mem.fetch((wa as u32) * 4).unwrap()); 79 | } 80 | text 81 | } 82 | 83 | pub fn get_state(&self) -> String { 84 | let mut state = String::new(); 85 | 86 | for i in 0..32 { 87 | let val = self.mcu.rf.read(i as u8).unwrap(); 88 | state += &format!("{:4} = 0x{:08x} ({})\n", REG_NAMES[i], val, val as i32); 89 | } 90 | 91 | state 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /wasm/src/logger.rs: -------------------------------------------------------------------------------- 1 | pub use log::{Level, LevelFilter, Metadata, Record}; 2 | use wasm_bindgen::prelude::*; 3 | 4 | #[wasm_bindgen] 5 | extern "C" { 6 | #[wasm_bindgen(js_namespace = console)] 7 | fn log(s: &str); 8 | } 9 | 10 | pub struct Logger; 11 | 12 | impl log::Log for Logger { 13 | fn enabled(&self, metadata: &Metadata) -> bool { 14 | metadata.level() <= Level::Info 15 | } 16 | 17 | fn log(&self, record: &Record) { 18 | if self.enabled(record.metadata()) { 19 | log(&format!("{}", record.args())); 20 | // Not thread-safe. 21 | unsafe { 22 | crate::CONSOLE_TEXT += &format!("{}\n", record.args()); 23 | } 24 | } 25 | } 26 | 27 | fn flush(&self) {} 28 | } 29 | --------------------------------------------------------------------------------