From 64e2d0b45cbdf687e4a2719ae2158cfc97a8e9de Mon Sep 17 00:00:00 2001 From: Stefan Date: Fri, 28 May 2021 15:08:21 +0200 Subject: [PATCH] initial commit passes all RV32IM tests (run './test.sh all') instructions.txt is extracted from takahirox/riscv-rust --- .gitignore | 7 + .gitmodules | 6 + Makefile | 21 + elfy/Cargo.toml | 15 + elfy/build.rs | 15 + elfy/elfy.h | 6 + elfy/src/lib.rs | 37 + instructions.txt | 1682 ++++++++++++++++++++++++++++++++++++++++++++++ parse_ins.pl | 138 ++++ riscv-opcodes | 1 + riscv-tests | 1 + src/cpu.h | 41 ++ src/csr.h | 17 + src/emu.h | 477 +++++++++++++ src/ins.h | 120 ++++ src/main.c | 52 ++ src/mem.h | 42 ++ src/types.h | 37 + test.sh | 100 +++ 19 files changed, 2815 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 Makefile create mode 100644 elfy/Cargo.toml create mode 100644 elfy/build.rs create mode 100644 elfy/elfy.h create mode 100644 elfy/src/lib.rs create mode 100644 instructions.txt create mode 100755 parse_ins.pl create mode 160000 riscv-opcodes create mode 160000 riscv-tests create mode 100644 src/cpu.h create mode 100644 src/csr.h create mode 100644 src/emu.h create mode 100644 src/ins.h create mode 100644 src/main.c create mode 100644 src/mem.h create mode 100644 src/types.h create mode 100755 test.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..462ce12b --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +compile_commands.json +.ccls-cache +elfy/.ccls-cache +rvc +*.o +elfy/target +elfy/Cargo.lock diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..25605790 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "riscv-opcodes"] + path = riscv-opcodes + url = https://github.com/riscv/riscv-opcodes +[submodule "riscv-tests"] + path = riscv-tests + url = https://github.com/riscv/riscv-tests diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..17af4376 --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +TARGET ?= rvc +SRC_DIRS ?= ./src + +CC=clang + +SRCS := $(shell find $(SRC_DIRS) -name '*.cpp' -or -name '*.c' -or -name '*.s') +OBJS := $(addsuffix .o,$(basename $(SRCS))) +DEPS := $(OBJS:.o=.d) + +INC_DIRS := $(shell find $(SRC_DIRS) -type d) +INC_FLAGS := $(addprefix -I,$(INC_DIRS)) + +$(TARGET): $(OBJS) + $(CC) $(LDFLAGS) -I./elfy/elfy.h $(OBJS) -o $@ $(LOADLIBES) $(LDLIBS) -L./elfy/target/debug/ -Wl,--no-as-needed -ldl -lpthread -lelfy + +.PHONY: clean +clean: + $(RM) $(TARGET) $(OBJS) $(DEPS) + +-include $(DEPS) + diff --git a/elfy/Cargo.toml b/elfy/Cargo.toml new file mode 100644 index 00000000..26278bd5 --- /dev/null +++ b/elfy/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "elfy" +version = "0.1.0" +authors = ["Stefan "] +edition = "2018" + +[lib] +name = "elfy" +crate-type = ["staticlib"] + +[dependencies] +elf = "0.0.10" + +[build-dependencies] +cbindgen = "0.19" diff --git a/elfy/build.rs b/elfy/build.rs new file mode 100644 index 00000000..3f63f6ba --- /dev/null +++ b/elfy/build.rs @@ -0,0 +1,15 @@ +use std::env; +use std::path::PathBuf; + +fn main() { + let crate_dir = PathBuf::from( + env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR env var is not defined"), + ); + + cbindgen::Builder::new() + .with_crate(crate_dir) + .with_language(cbindgen::Language::C) + .generate() + .expect("Unable to generate bindings") + .write_to_file("elfy.h"); +} diff --git a/elfy/elfy.h b/elfy/elfy.h new file mode 100644 index 00000000..2ed7fc78 --- /dev/null +++ b/elfy/elfy.h @@ -0,0 +1,6 @@ +#include +#include +#include +#include + +void load_elf(const char *path, uint64_t path_len, uint8_t *data, uint64_t data_len); diff --git a/elfy/src/lib.rs b/elfy/src/lib.rs new file mode 100644 index 00000000..6fd95df1 --- /dev/null +++ b/elfy/src/lib.rs @@ -0,0 +1,37 @@ +use std::ffi::{CStr, OsStr}; +use std::path::PathBuf; +use std::os::unix::ffi::OsStrExt; +use std::os::raw::c_char; + +#[no_mangle] +pub extern "C" fn load_elf(path: *const c_char, path_len: u64, data: *mut u8, data_len: u64) { + let path = unsafe { CStr::from_bytes_with_nul_unchecked(std::slice::from_raw_parts(path as *const u8, path_len as usize)) }; + let path = OsStr::from_bytes(path.to_bytes()); + let path = PathBuf::from(path); + + println!("Loading ELF binary '{}'", path.display()); + + let elf = elf::File::open_path(path).unwrap(); + + for section in elf.sections { + if section.shdr.shtype.0 != 1 { + continue; + } + + let name = section.shdr.name; + let addr = section.shdr.addr; + let addr_real = addr & 0x7FFFFFFF; + let size = section.data.len() as u64; + println!("Loading ELF section '{}' @{:#x} (@{:#x}) size={}", name, addr, addr_real, size); + + if addr_real + size > data_len { + panic!("ELF section too big or offset to great"); + } + + let mut i = 0; + for byte in section.data { + unsafe { std::ptr::write(data.add(addr_real as usize + i), byte) }; + i += 1; + } + } +} diff --git a/instructions.txt b/instructions.txt new file mode 100644 index 00000000..f04e7262 --- /dev/null +++ b/instructions.txt @@ -0,0 +1,1682 @@ +Instruction { + mask: 0xfe00707f, + data: 0x00000033, + name: "ADD", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + cpu.x[f.rd] = cpu.sign_extend(cpu.x[f.rs1].wrapping_add(cpu.x[f.rs2])); + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0x0000707f, + data: 0x00000013, + name: "ADDI", + operation: |cpu, word, _address| { + let f = parse_format_i(word); + cpu.x[f.rd] = cpu.sign_extend(cpu.x[f.rs1].wrapping_add(f.imm)); + Ok(()) + }, + disassemble: dump_format_i + }, + Instruction { + mask: 0x0000707f, + data: 0x0000001b, + name: "ADDIW", + operation: |cpu, word, _address| { + let f = parse_format_i(word); + cpu.x[f.rd] = cpu.x[f.rs1].wrapping_add(f.imm) as i32 as i64; + Ok(()) + }, + disassemble: dump_format_i + }, + Instruction { + mask: 0xfe00707f, + data: 0x0000003b, + name: "ADDW", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + cpu.x[f.rd] = cpu.x[f.rs1].wrapping_add(cpu.x[f.rs2]) as i32 as i64; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xf800707f, + data: 0x0000302f, + name: "AMOADD.D", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + let tmp = match cpu.mmu.load_doubleword(cpu.x[f.rs1] as u64) { + Ok(data) => data as i64, + Err(e) => return Err(e) + }; + match cpu.mmu.store_doubleword(cpu.x[f.rs1] as u64, cpu.x[f.rs2].wrapping_add(tmp) as u64) { + Ok(()) => {}, + Err(e) => return Err(e) + }; + cpu.x[f.rd] = tmp; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xf800707f, + data: 0x0000202f, + name: "AMOADD.W", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + let tmp = match cpu.mmu.load_word(cpu.x[f.rs1] as u64) { + Ok(data) => data as i32 as i64, + Err(e) => return Err(e) + }; + match cpu.mmu.store_word(cpu.x[f.rs1] as u64, cpu.x[f.rs2].wrapping_add(tmp) as u32) { + Ok(()) => {}, + Err(e) => return Err(e) + }; + cpu.x[f.rd] = tmp; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xf800707f, + data: 0x6000302f, + name: "AMOAND.D", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + let tmp = match cpu.mmu.load_doubleword(cpu.x[f.rs1] as u64) { + Ok(data) => data as i64, + Err(e) => return Err(e) + }; + match cpu.mmu.store_doubleword(cpu.x[f.rs1] as u64, (cpu.x[f.rs2] & tmp) as u64) { + Ok(()) => {}, + Err(e) => return Err(e) + }; + cpu.x[f.rd] = tmp; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xf800707f, + data: 0x6000202f, + name: "AMOAND.W", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + let tmp = match cpu.mmu.load_word(cpu.x[f.rs1] as u64) { + Ok(data) => data as i32 as i64, + Err(e) => return Err(e) + }; + match cpu.mmu.store_word(cpu.x[f.rs1] as u64, (cpu.x[f.rs2] & tmp) as u32) { + Ok(()) => {}, + Err(e) => return Err(e) + }; + cpu.x[f.rd] = tmp; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xf800707f, + data: 0xe000302f, + name: "AMOMAXU.D", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + let tmp = match cpu.mmu.load_doubleword(cpu.x[f.rs1] as u64) { + Ok(data) => data, + Err(e) => return Err(e) + }; + let max = match cpu.x[f.rs2] as u64 >= tmp { + true => cpu.x[f.rs2] as u64, + false => tmp + }; + match cpu.mmu.store_doubleword(cpu.x[f.rs1] as u64, max) { + Ok(()) => {}, + Err(e) => return Err(e) + }; + cpu.x[f.rd] = tmp as i64; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xf800707f, + data: 0xe000202f, + name: "AMOMAXU.W", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + let tmp = match cpu.mmu.load_word(cpu.x[f.rs1] as u64) { + Ok(data) => data, + Err(e) => return Err(e) + }; + let max = match cpu.x[f.rs2] as u32 >= tmp { + true => cpu.x[f.rs2] as u32, + false => tmp + }; + match cpu.mmu.store_word(cpu.x[f.rs1] as u64, max) { + Ok(()) => {}, + Err(e) => return Err(e) + }; + cpu.x[f.rd] = tmp as i32 as i64; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xf800707f, + data: 0x4000302f, + name: "AMOOR.D", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + let tmp = match cpu.mmu.load_doubleword(cpu.x[f.rs1] as u64) { + Ok(data) => data as i64, + Err(e) => return Err(e) + }; + match cpu.mmu.store_doubleword(cpu.x[f.rs1] as u64, (cpu.x[f.rs2] | tmp) as u64) { + Ok(()) => {}, + Err(e) => return Err(e) + }; + cpu.x[f.rd] = tmp; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xf800707f, + data: 0x4000202f, + name: "AMOOR.W", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + let tmp = match cpu.mmu.load_word(cpu.x[f.rs1] as u64) { + Ok(data) => data as i32 as i64, + Err(e) => return Err(e) + }; + match cpu.mmu.store_word(cpu.x[f.rs1] as u64, (cpu.x[f.rs2] | tmp) as u32) { + Ok(()) => {}, + Err(e) => return Err(e) + }; + cpu.x[f.rd] = tmp; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xf800707f, + data: 0x0800302f, + name: "AMOSWAP.D", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + let tmp = match cpu.mmu.load_doubleword(cpu.x[f.rs1] as u64) { + Ok(data) => data as i64, + Err(e) => return Err(e) + }; + match cpu.mmu.store_doubleword(cpu.x[f.rs1] as u64, cpu.x[f.rs2] as u64) { + Ok(()) => {}, + Err(e) => return Err(e) + }; + cpu.x[f.rd] = tmp; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xf800707f, + data: 0x0800202f, + name: "AMOSWAP.W", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + let tmp = match cpu.mmu.load_word(cpu.x[f.rs1] as u64) { + Ok(data) => data as i32 as i64, + Err(e) => return Err(e) + }; + match cpu.mmu.store_word(cpu.x[f.rs1] as u64, cpu.x[f.rs2] as u32) { + Ok(()) => {}, + Err(e) => return Err(e) + }; + cpu.x[f.rd] = tmp; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfe00707f, + data: 0x00007033, + name: "AND", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + cpu.x[f.rd] = cpu.sign_extend(cpu.x[f.rs1] & cpu.x[f.rs2]); + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0x0000707f, + data: 0x00007013, + name: "ANDI", + operation: |cpu, word, _address| { + let f = parse_format_i(word); + cpu.x[f.rd] = cpu.sign_extend(cpu.x[f.rs1] & f.imm); + Ok(()) + }, + disassemble: dump_format_i + }, + Instruction { + mask: 0x0000007f, + data: 0x00000017, + name: "AUIPC", + operation: |cpu, word, address| { + let f = parse_format_u(word); + cpu.x[f.rd] = cpu.sign_extend(address.wrapping_add(f.imm) as i64); + Ok(()) + }, + disassemble: dump_format_u + }, + Instruction { + mask: 0x0000707f, + data: 0x00000063, + name: "BEQ", + operation: |cpu, word, address| { + let f = parse_format_b(word); + if cpu.sign_extend(cpu.x[f.rs1]) == cpu.sign_extend(cpu.x[f.rs2]) { + cpu.pc = address.wrapping_add(f.imm); + } + Ok(()) + }, + disassemble: dump_format_b + }, + Instruction { + mask: 0x0000707f, + data: 0x00005063, + name: "BGE", + operation: |cpu, word, address| { + let f = parse_format_b(word); + if cpu.sign_extend(cpu.x[f.rs1]) >= cpu.sign_extend(cpu.x[f.rs2]) { + cpu.pc = address.wrapping_add(f.imm); + } + Ok(()) + }, + disassemble: dump_format_b + }, + Instruction { + mask: 0x0000707f, + data: 0x00007063, + name: "BGEU", + operation: |cpu, word, address| { + let f = parse_format_b(word); + if cpu.unsigned_data(cpu.x[f.rs1]) >= cpu.unsigned_data(cpu.x[f.rs2]) { + cpu.pc = address.wrapping_add(f.imm); + } + Ok(()) + }, + disassemble: dump_format_b + }, + Instruction { + mask: 0x0000707f, + data: 0x00004063, + name: "BLT", + operation: |cpu, word, address| { + let f = parse_format_b(word); + if cpu.sign_extend(cpu.x[f.rs1]) < cpu.sign_extend(cpu.x[f.rs2]) { + cpu.pc = address.wrapping_add(f.imm); + } + Ok(()) + }, + disassemble: dump_format_b + }, + Instruction { + mask: 0x0000707f, + data: 0x00006063, + name: "BLTU", + operation: |cpu, word, address| { + let f = parse_format_b(word); + if cpu.unsigned_data(cpu.x[f.rs1]) < cpu.unsigned_data(cpu.x[f.rs2]) { + cpu.pc = address.wrapping_add(f.imm); + } + Ok(()) + }, + disassemble: dump_format_b + }, + Instruction { + mask: 0x0000707f, + data: 0x00001063, + name: "BNE", + operation: |cpu, word, address| { + let f = parse_format_b(word); + if cpu.sign_extend(cpu.x[f.rs1]) != cpu.sign_extend(cpu.x[f.rs2]) { + cpu.pc = address.wrapping_add(f.imm); + } + Ok(()) + }, + disassemble: dump_format_b + }, + Instruction { + mask: 0x0000707f, + data: 0x00003073, + name: "CSRRC", + operation: |cpu, word, _address| { + let f = parse_format_csr(word); + let data = match cpu.read_csr(f.csr) { + Ok(data) => data as i64, + Err(e) => return Err(e) + }; + let tmp = cpu.x[f.rs]; + cpu.x[f.rd] = cpu.sign_extend(data); + match cpu.write_csr(f.csr, (cpu.x[f.rd] & !tmp) as u64) { + Ok(()) => {}, + Err(e) => return Err(e) + }; + Ok(()) + }, + disassemble: dump_format_csr + }, + Instruction { + mask: 0x0000707f, + data: 0x00007073, + name: "CSRRCI", + operation: |cpu, word, _address| { + let f = parse_format_csr(word); + let data = match cpu.read_csr(f.csr) { + Ok(data) => data as i64, + Err(e) => return Err(e) + }; + cpu.x[f.rd] = cpu.sign_extend(data); + match cpu.write_csr(f.csr, (cpu.x[f.rd] & !(f.rs as i64)) as u64) { + Ok(()) => {}, + Err(e) => return Err(e) + }; + Ok(()) + }, + disassemble: dump_format_csr + }, + Instruction { + mask: 0x0000707f, + data: 0x00002073, + name: "CSRRS", + operation: |cpu, word, _address| { + let f = parse_format_csr(word); + let data = match cpu.read_csr(f.csr) { + Ok(data) => data as i64, + Err(e) => return Err(e) + }; + let tmp = cpu.x[f.rs]; + cpu.x[f.rd] = cpu.sign_extend(data); + match cpu.write_csr(f.csr, cpu.unsigned_data(cpu.x[f.rd] | tmp)) { + Ok(()) => {}, + Err(e) => return Err(e) + }; + Ok(()) + }, + disassemble: dump_format_csr + }, + Instruction { + mask: 0x0000707f, + data: 0x00006073, + name: "CSRRSI", + operation: |cpu, word, _address| { + let f = parse_format_csr(word); + let data = match cpu.read_csr(f.csr) { + Ok(data) => data as i64, + Err(e) => return Err(e) + }; + cpu.x[f.rd] = cpu.sign_extend(data); + match cpu.write_csr(f.csr, cpu.unsigned_data(cpu.x[f.rd] | (f.rs as i64))) { + Ok(()) => {}, + Err(e) => return Err(e) + }; + Ok(()) + }, + disassemble: dump_format_csr + }, + Instruction { + mask: 0x0000707f, + data: 0x00001073, + name: "CSRRW", + operation: |cpu, word, _address| { + let f = parse_format_csr(word); + let data = match cpu.read_csr(f.csr) { + Ok(data) => data as i64, + Err(e) => return Err(e) + }; + let tmp = cpu.x[f.rs]; + cpu.x[f.rd] = cpu.sign_extend(data); + match cpu.write_csr(f.csr, cpu.unsigned_data(tmp)) { + Ok(()) => {}, + Err(e) => return Err(e) + }; + Ok(()) + }, + disassemble: dump_format_csr + }, + Instruction { + mask: 0x0000707f, + data: 0x00005073, + name: "CSRRWI", + operation: |cpu, word, _address| { + let f = parse_format_csr(word); + let data = match cpu.read_csr(f.csr) { + Ok(data) => data as i64, + Err(e) => return Err(e) + }; + cpu.x[f.rd] = cpu.sign_extend(data); + match cpu.write_csr(f.csr, f.rs as u64) { + Ok(()) => {}, + Err(e) => return Err(e) + }; + Ok(()) + }, + disassemble: dump_format_csr + }, + Instruction { + mask: 0xfe00707f, + data: 0x02004033, + name: "DIV", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + let dividend = cpu.x[f.rs1]; + let divisor = cpu.x[f.rs2]; + if divisor == 0 { + cpu.x[f.rd] = -1; + } else if dividend == cpu.most_negative() && divisor == -1 { + cpu.x[f.rd] = dividend; + } else { + cpu.x[f.rd] = cpu.sign_extend(dividend.wrapping_div(divisor)) + } + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfe00707f, + data: 0x02005033, + name: "DIVU", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + let dividend = cpu.unsigned_data(cpu.x[f.rs1]); + let divisor = cpu.unsigned_data(cpu.x[f.rs2]); + if divisor == 0 { + cpu.x[f.rd] = -1; + } else { + cpu.x[f.rd] = cpu.sign_extend(dividend.wrapping_div(divisor) as i64) + } + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfe00707f, + data: 0x0200503b, + name: "DIVUW", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + let dividend = cpu.unsigned_data(cpu.x[f.rs1]) as u32; + let divisor = cpu.unsigned_data(cpu.x[f.rs2]) as u32; + if divisor == 0 { + cpu.x[f.rd] = -1; + } else { + cpu.x[f.rd] = dividend.wrapping_div(divisor) as i32 as i64 + } + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfe00707f, + data: 0x0200403b, + name: "DIVW", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + let dividend = cpu.x[f.rs1] as i32; + let divisor = cpu.x[f.rs2] as i32; + if divisor == 0 { + cpu.x[f.rd] = -1; + } else if dividend == std::i32::MIN && divisor == -1 { + cpu.x[f.rd] = dividend as i32 as i64; + } else { + cpu.x[f.rd] = dividend.wrapping_div(divisor) as i32 as i64 + } + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xffffffff, + data: 0x00100073, + name: "EBREAK", + operation: |_cpu, _word, _address| { + // @TODO: Implement + Ok(()) + }, + disassemble: dump_empty + }, + Instruction { + mask: 0xffffffff, + data: 0x00000073, + name: "ECALL", + operation: |cpu, _word, address| { + let exception_type = match cpu.privilege_mode { + PrivilegeMode::User => TrapType::EnvironmentCallFromUMode, + PrivilegeMode::Supervisor => TrapType::EnvironmentCallFromSMode, + PrivilegeMode::Machine => TrapType::EnvironmentCallFromMMode, + PrivilegeMode::Reserved => panic!("Unknown Privilege mode") + }; + return Err(Trap { + trap_type: exception_type, + value: address + }); + }, + disassemble: dump_empty + }, + Instruction { + mask: 0xfe00007f, + data: 0x02000053, + name: "FADD.D", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + cpu.f[f.rd] = cpu.f[f.rs1] + cpu.f[f.rs2]; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfff0007f, + data: 0xd2200053, + name: "FCVT.D.L", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + cpu.f[f.rd] = cpu.x[f.rs1] as f64; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfff0007f, + data: 0x42000053, + name: "FCVT.D.S", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + // Is this implementation correct? + cpu.f[f.rd] = f32::from_bits(cpu.f[f.rs1].to_bits() as u32) as f64; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfff0007f, + data: 0xd2000053, + name: "FCVT.D.W", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + cpu.f[f.rd] = cpu.x[f.rs1] as i32 as f64; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfff0007f, + data: 0xd2100053, + name: "FCVT.D.WU", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + cpu.f[f.rd] = cpu.x[f.rs1] as u32 as f64; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfff0007f, + data: 0x40100053, + name: "FCVT.S.D", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + // Is this implementation correct? + cpu.f[f.rd] = cpu.f[f.rs1] as f32 as f64; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfff0007f, + data: 0xc2000053, + name: "FCVT.W.D", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + // Is this implementation correct? + cpu.x[f.rd] = cpu.f[f.rs1] as u32 as i32 as i64; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfe00007f, + data: 0x1a000053, + name: "FDIV.D", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + let dividend = cpu.f[f.rs1]; + let divisor = cpu.f[f.rs2]; + // Is this implementation correct? + if divisor == 0.0 { + cpu.f[f.rd] = std::f64::INFINITY; + cpu.set_fcsr_dz(); + } else if divisor == -0.0 { + cpu.f[f.rd] = std::f64::NEG_INFINITY; + cpu.set_fcsr_dz(); + } else { + cpu.f[f.rd] = dividend / divisor; + } + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0x0000707f, + data: 0x0000000f, + name: "FENCE", + operation: |_cpu, _word, _address| { + // Do nothing? + Ok(()) + }, + disassemble: dump_empty + }, + Instruction { + mask: 0x0000707f, + data: 0x0000100f, + name: "FENCE.I", + operation: |_cpu, _word, _address| { + // Do nothing? + Ok(()) + }, + disassemble: dump_empty + }, + Instruction { + mask: 0xfe00707f, + data: 0xa2002053, + name: "FEQ.D", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + cpu.x[f.rd] = match cpu.f[f.rs1] == cpu.f[f.rs2] { + true => 1, + false => 0 + }; + Ok(()) + }, + disassemble: dump_empty + }, + Instruction { + mask: 0x0000707f, + data: 0x00003007, + name: "FLD", + operation: |cpu, word, _address| { + let f = parse_format_i(word); + cpu.f[f.rd] = match cpu.mmu.load_doubleword(cpu.x[f.rs1].wrapping_add(f.imm) as u64) { + Ok(data) => f64::from_bits(data), + Err(e) => return Err(e) + }; + Ok(()) + }, + disassemble: dump_format_i + }, + Instruction { + mask: 0xfe00707f, + data: 0xa2000053, + name: "FLE.D", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + cpu.x[f.rd] = match cpu.f[f.rs1] <= cpu.f[f.rs2] { + true => 1, + false => 0 + }; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfe00707f, + data: 0xa2001053, + name: "FLT.D", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + cpu.x[f.rd] = match cpu.f[f.rs1] < cpu.f[f.rs2] { + true => 1, + false => 0 + }; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0x0000707f, + data: 0x00002007, + name: "FLW", + operation: |cpu, word, _address| { + let f = parse_format_i(word); + cpu.f[f.rd] = match cpu.mmu.load_word(cpu.x[f.rs1].wrapping_add(f.imm) as u64) { + Ok(data) => f64::from_bits(data as i32 as i64 as u64), + Err(e) => return Err(e) + }; + Ok(()) + }, + disassemble: dump_format_i_mem + }, + Instruction { + mask: 0x0600007f, + data: 0x02000043, + name: "FMADD.D", + operation: |cpu, word, _address| { + // @TODO: Update fcsr if needed? + let f = parse_format_r2(word); + cpu.f[f.rd] = cpu.f[f.rs1] * cpu.f[f.rs2] + cpu.f[f.rs3]; + Ok(()) + }, + disassemble: dump_format_r2 + }, + Instruction { + mask: 0xfe00007f, + data: 0x12000053, + name: "FMUL.D", + operation: |cpu, word, _address| { + // @TODO: Update fcsr if needed? + let f = parse_format_r(word); + cpu.f[f.rd] = cpu.f[f.rs1] * cpu.f[f.rs2]; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfff0707f, + data: 0xf2000053, + name: "FMV.D.X", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + cpu.f[f.rd] = f64::from_bits(cpu.x[f.rs1] as u64); + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfff0707f, + data: 0xe2000053, + name: "FMV.X.D", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + cpu.x[f.rd] = cpu.f[f.rs1].to_bits() as i64; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfff0707f, + data: 0xe0000053, + name: "FMV.X.W", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + cpu.x[f.rd] = cpu.f[f.rs1].to_bits() as i32 as i64; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfff0707f, + data: 0xf0000053, + name: "FMV.W.X", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + cpu.f[f.rd] = f64::from_bits(cpu.x[f.rs1] as u32 as u64); + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0x0600007f, + data: 0x0200004b, + name: "FNMSUB.D", + operation: |cpu, word, _address| { + let f = parse_format_r2(word); + cpu.f[f.rd] = -(cpu.f[f.rs1] * cpu.f[f.rs2]) + cpu.f[f.rs3]; + Ok(()) + }, + disassemble: dump_format_r2 + }, + Instruction { + mask: 0x0000707f, + data: 0x00003027, + name: "FSD", + operation: |cpu, word, _address| { + let f = parse_format_s(word); + cpu.mmu.store_doubleword(cpu.x[f.rs1].wrapping_add(f.imm) as u64, cpu.f[f.rs2].to_bits()) + }, + disassemble: dump_format_s + }, + Instruction { + mask: 0xfe00707f, + data: 0x22000053, + name: "FSGNJ.D", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + let rs1_bits = cpu.f[f.rs1].to_bits(); + let rs2_bits = cpu.f[f.rs2].to_bits(); + let sign_bit = rs2_bits & 0x8000000000000000; + cpu.f[f.rd] = f64::from_bits(sign_bit | (rs1_bits & 0x7fffffffffffffff)); + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfe00707f, + data: 0x22002053, + name: "FSGNJX.D", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + let rs1_bits = cpu.f[f.rs1].to_bits(); + let rs2_bits = cpu.f[f.rs2].to_bits(); + let sign_bit = (rs1_bits ^ rs2_bits) & 0x8000000000000000; + cpu.f[f.rd] = f64::from_bits(sign_bit | (rs1_bits & 0x7fffffffffffffff)); + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfe00007f, + data: 0x0a000053, + name: "FSUB.D", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + // @TODO: Update fcsr if needed? + cpu.f[f.rd] = cpu.f[f.rs1] - cpu.f[f.rs2]; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0x0000707f, + data: 0x00002027, + name: "FSW", + operation: |cpu, word, _address| { + let f = parse_format_s(word); + cpu.mmu.store_word(cpu.x[f.rs1].wrapping_add(f.imm) as u64, cpu.f[f.rs2].to_bits() as u32) + }, + disassemble: dump_format_s + }, + Instruction { + mask: 0x0000007f, + data: 0x0000006f, + name: "JAL", + operation: |cpu, word, address| { + let f = parse_format_j(word); + cpu.x[f.rd] = cpu.sign_extend(cpu.pc as i64); + cpu.pc = address.wrapping_add(f.imm); + Ok(()) + }, + disassemble: dump_format_j + }, + Instruction { + mask: 0x0000707f, + data: 0x00000067, + name: "JALR", + operation: |cpu, word, _address| { + let f = parse_format_i(word); + let tmp = cpu.sign_extend(cpu.pc as i64); + cpu.pc = (cpu.x[f.rs1] as u64).wrapping_add(f.imm as u64); + cpu.x[f.rd] = tmp; + Ok(()) + }, + disassemble: |cpu, word, _address, evaluate| { + let f = parse_format_i(word); + let mut s = String::new(); + s += &format!("{}", get_register_name(f.rd)); + if evaluate { + s += &format!(":{:x}", cpu.x[f.rd]); + } + s += &format!(",{:x}({}", f.imm, get_register_name(f.rs1)); + if evaluate { + s += &format!(":{:x}", cpu.x[f.rs1]); + } + s += &format!(")"); + s + } + }, + Instruction { + mask: 0x0000707f, + data: 0x00000003, + name: "LB", + operation: |cpu, word, _address| { + let f = parse_format_i(word); + cpu.x[f.rd] = match cpu.mmu.load(cpu.x[f.rs1].wrapping_add(f.imm) as u64) { + Ok(data) => data as i8 as i64, + Err(e) => return Err(e) + }; + Ok(()) + }, + disassemble: dump_format_i_mem + }, + Instruction { + mask: 0x0000707f, + data: 0x00004003, + name: "LBU", + operation: |cpu, word, _address| { + let f = parse_format_i(word); + cpu.x[f.rd] = match cpu.mmu.load(cpu.x[f.rs1].wrapping_add(f.imm) as u64) { + Ok(data) => data as i64, + Err(e) => return Err(e) + }; + Ok(()) + }, + disassemble: dump_format_i_mem + }, + Instruction { + mask: 0x0000707f, + data: 0x00003003, + name: "LD", + operation: |cpu, word, _address| { + let f = parse_format_i(word); + cpu.x[f.rd] = match cpu.mmu.load_doubleword(cpu.x[f.rs1].wrapping_add(f.imm) as u64) { + Ok(data) => data as i64, + Err(e) => return Err(e) + }; + Ok(()) + }, + disassemble: dump_format_i_mem + }, + Instruction { + mask: 0x0000707f, + data: 0x00001003, + name: "LH", + operation: |cpu, word, _address| { + let f = parse_format_i(word); + cpu.x[f.rd] = match cpu.mmu.load_halfword(cpu.x[f.rs1].wrapping_add(f.imm) as u64) { + Ok(data) => data as i16 as i64, + Err(e) => return Err(e) + }; + Ok(()) + }, + disassemble: dump_format_i_mem + }, + Instruction { + mask: 0x0000707f, + data: 0x00005003, + name: "LHU", + operation: |cpu, word, _address| { + let f = parse_format_i(word); + cpu.x[f.rd] = match cpu.mmu.load_halfword(cpu.x[f.rs1].wrapping_add(f.imm) as u64) { + Ok(data) => data as i64, + Err(e) => return Err(e) + }; + Ok(()) + }, + disassemble: dump_format_i_mem + }, + Instruction { + mask: 0xf9f0707f, + data: 0x1000302f, + name: "LR.D", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + // @TODO: Implement properly + cpu.x[f.rd] = match cpu.mmu.load_doubleword(cpu.x[f.rs1] as u64) { + Ok(data) => { + cpu.is_reservation_set = true; + cpu.reservation = cpu.x[f.rs1] as u64; // Is virtual address ok? + data as i64 + }, + Err(e) => return Err(e) + }; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xf9f0707f, + data: 0x1000202f, + name: "LR.W", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + // @TODO: Implement properly + cpu.x[f.rd] = match cpu.mmu.load_word(cpu.x[f.rs1] as u64) { + Ok(data) => { + cpu.is_reservation_set = true; + cpu.reservation = cpu.x[f.rs1] as u64; // Is virtual address ok? + data as i32 as i64 + }, + Err(e) => return Err(e) + }; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0x0000007f, + data: 0x00000037, + name: "LUI", + operation: |cpu, word, _address| { + let f = parse_format_u(word); + cpu.x[f.rd] = f.imm as i64; + Ok(()) + }, + disassemble: dump_format_u + }, + Instruction { + mask: 0x0000707f, + data: 0x00002003, + name: "LW", + operation: |cpu, word, _address| { + let f = parse_format_i(word); + cpu.x[f.rd] = match cpu.mmu.load_word(cpu.x[f.rs1].wrapping_add(f.imm) as u64) { + Ok(data) => data as i32 as i64, + Err(e) => return Err(e) + }; + Ok(()) + }, + disassemble: dump_format_i_mem + }, + Instruction { + mask: 0x0000707f, + data: 0x00006003, + name: "LWU", + operation: |cpu, word, _address| { + let f = parse_format_i(word); + cpu.x[f.rd] = match cpu.mmu.load_word(cpu.x[f.rs1].wrapping_add(f.imm) as u64) { + Ok(data) => data as i64, + Err(e) => return Err(e) + }; + Ok(()) + }, + disassemble: dump_format_i_mem + }, + Instruction { + mask: 0xfe00707f, + data: 0x02000033, + name: "MUL", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + cpu.x[f.rd] = cpu.sign_extend(cpu.x[f.rs1].wrapping_mul(cpu.x[f.rs2])); + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfe00707f, + data: 0x02001033, + name: "MULH", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + cpu.x[f.rd] = match cpu.xlen { + Xlen::Bit32 => { + cpu.sign_extend((cpu.x[f.rs1] * cpu.x[f.rs2]) >> 32) + }, + Xlen::Bit64 => { + ((cpu.x[f.rs1] as i128) * (cpu.x[f.rs2] as i128) >> 64) as i64 + } + }; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfe00707f, + data: 0x02003033, + name: "MULHU", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + cpu.x[f.rd] = match cpu.xlen { + Xlen::Bit32 => { + cpu.sign_extend((((cpu.x[f.rs1] as u32 as u64) * (cpu.x[f.rs2] as u32 as u64)) >> 32) as i64) + }, + Xlen::Bit64 => { + ((cpu.x[f.rs1] as u64 as u128).wrapping_mul(cpu.x[f.rs2] as u64 as u128) >> 64) as i64 + } + }; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfe00707f, + data: 0x02002033, + name: "MULHSU", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + cpu.x[f.rd] = match cpu.xlen { + Xlen::Bit32 => { + cpu.sign_extend(((cpu.x[f.rs1] as i64).wrapping_mul(cpu.x[f.rs2] as u32 as i64) >> 32) as i64) + }, + Xlen::Bit64 => { + ((cpu.x[f.rs1] as u128).wrapping_mul(cpu.x[f.rs2] as u64 as u128) >> 64) as i64 + } + }; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfe00707f, + data: 0x0200003b, + name: "MULW", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + cpu.x[f.rd] = cpu.sign_extend((cpu.x[f.rs1] as i32).wrapping_mul(cpu.x[f.rs2] as i32) as i64); + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xffffffff, + data: 0x30200073, + name: "MRET", + operation: |cpu, _word, _address| { + cpu.pc = match cpu.read_csr(CSR_MEPC_ADDRESS) { + Ok(data) => data, + Err(e) => return Err(e) + }; + let status = cpu.read_csr_raw(CSR_MSTATUS_ADDRESS); + let mpie = (status >> 7) & 1; + let mpp = (status >> 11) & 0x3; + let mprv = match get_privilege_mode(mpp) { + PrivilegeMode::Machine => (status >> 17) & 1, + _ => 0 + }; + // Override MIE[3] with MPIE[7], set MPIE[7] to 1, set MPP[12:11] to 0 + // and override MPRV[17] + let new_status = (status & !0x21888) | (mprv << 17) | (mpie << 3) | (1 << 7); + cpu.write_csr_raw(CSR_MSTATUS_ADDRESS, new_status); + cpu.privilege_mode = match mpp { + 0 => PrivilegeMode::User, + 1 => PrivilegeMode::Supervisor, + 3 => PrivilegeMode::Machine, + _ => panic!() // Shouldn't happen + }; + cpu.mmu.update_privilege_mode(cpu.privilege_mode.clone()); + Ok(()) + }, + disassemble: dump_empty + }, + Instruction { + mask: 0xfe00707f, + data: 0x00006033, + name: "OR", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + cpu.x[f.rd] = cpu.sign_extend(cpu.x[f.rs1] | cpu.x[f.rs2]); + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0x0000707f, + data: 0x00006013, + name: "ORI", + operation: |cpu, word, _address| { + let f = parse_format_i(word); + cpu.x[f.rd] = cpu.sign_extend(cpu.x[f.rs1] | f.imm); + Ok(()) + }, + disassemble: dump_format_i + }, + Instruction { + mask: 0xfe00707f, + data: 0x02006033, + name: "REM", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + let dividend = cpu.x[f.rs1]; + let divisor = cpu.x[f.rs2]; + if divisor == 0 { + cpu.x[f.rd] = dividend; + } else if dividend == cpu.most_negative() && divisor == -1 { + cpu.x[f.rd] = 0; + } else { + cpu.x[f.rd] = cpu.sign_extend(cpu.x[f.rs1].wrapping_rem(cpu.x[f.rs2])); + } + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfe00707f, + data: 0x02007033, + name: "REMU", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + let dividend = cpu.unsigned_data(cpu.x[f.rs1]); + let divisor = cpu.unsigned_data(cpu.x[f.rs2]); + cpu.x[f.rd] = match divisor { + 0 => cpu.sign_extend(dividend as i64), + _ => cpu.sign_extend(dividend.wrapping_rem(divisor) as i64) + }; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfe00707f, + data: 0x0200703b, + name: "REMUW", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + let dividend = cpu.x[f.rs1] as u32; + let divisor = cpu.x[f.rs2] as u32; + cpu.x[f.rd] = match divisor { + 0 => dividend as i32 as i64, + _ => dividend.wrapping_rem(divisor) as i32 as i64 + }; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfe00707f, + data: 0x0200603b, + name: "REMW", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + let dividend = cpu.x[f.rs1] as i32; + let divisor = cpu.x[f.rs2] as i32; + if divisor == 0 { + cpu.x[f.rd] = dividend as i64; + } else if dividend == std::i32::MIN && divisor == -1 { + cpu.x[f.rd] = 0; + } else { + cpu.x[f.rd] = dividend.wrapping_rem(divisor) as i64; + } + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0x0000707f, + data: 0x00000023, + name: "SB", + operation: |cpu, word, _address| { + let f = parse_format_s(word); + cpu.mmu.store(cpu.x[f.rs1].wrapping_add(f.imm) as u64, cpu.x[f.rs2] as u8) + }, + disassemble: dump_format_s + }, + Instruction { + mask: 0xf800707f, + data: 0x1800302f, + name: "SC.D", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + // @TODO: Implement properly + cpu.x[f.rd] = match cpu.is_reservation_set && cpu.reservation == (cpu.x[f.rs1] as u64) { + true => match cpu.mmu.store_doubleword(cpu.x[f.rs1] as u64, cpu.x[f.rs2] as u64) { + Ok(()) => { + cpu.is_reservation_set = false; + 0 + }, + Err(e) => return Err(e) + }, + false => 1 + }; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xf800707f, + data: 0x1800202f, + name: "SC.W", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + // @TODO: Implement properly + cpu.x[f.rd] = match cpu.is_reservation_set && cpu.reservation == (cpu.x[f.rs1] as u64) { + true => match cpu.mmu.store_word(cpu.x[f.rs1] as u64, cpu.x[f.rs2] as u32) { + Ok(()) => { + cpu.is_reservation_set = false; + 0 + }, + Err(e) => return Err(e) + }, + false => 1 + }; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0x0000707f, + data: 0x00003023, + name: "SD", + operation: |cpu, word, _address| { + let f = parse_format_s(word); + cpu.mmu.store_doubleword(cpu.x[f.rs1].wrapping_add(f.imm) as u64, cpu.x[f.rs2] as u64) + }, + disassemble: dump_format_s + }, + Instruction { + mask: 0xfe007fff, + data: 0x12000073, + name: "SFENCE.VMA", + operation: |_cpu, _word, _address| { + // Do nothing? + Ok(()) + }, + disassemble: dump_empty + }, + Instruction { + mask: 0x0000707f, + data: 0x00001023, + name: "SH", + operation: |cpu, word, _address| { + let f = parse_format_s(word); + cpu.mmu.store_halfword(cpu.x[f.rs1].wrapping_add(f.imm) as u64, cpu.x[f.rs2] as u16) + }, + disassemble: dump_format_s + }, + Instruction { + mask: 0xfe00707f, + data: 0x00001033, + name: "SLL", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + cpu.x[f.rd] = cpu.sign_extend(cpu.x[f.rs1].wrapping_shl(cpu.x[f.rs2] as u32)); + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfc00707f, + data: 0x00001013, + name: "SLLI", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + let mask = match cpu.xlen { + Xlen::Bit32 => 0x1f, + Xlen::Bit64 => 0x3f + }; + let shamt = (word >> 20) & mask; + cpu.x[f.rd] = cpu.sign_extend(cpu.x[f.rs1] << shamt); + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfe00707f, + data: 0x0000101b, + name: "SLLIW", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + let shamt = f.rs2 as u32; + cpu.x[f.rd] = (cpu.x[f.rs1] << shamt) as i32 as i64; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfe00707f, + data: 0x0000103b, + name: "SLLW", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + cpu.x[f.rd] = (cpu.x[f.rs1] as u32).wrapping_shl(cpu.x[f.rs2] as u32) as i32 as i64; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfe00707f, + data: 0x00002033, + name: "SLT", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + cpu.x[f.rd] = match cpu.x[f.rs1] < cpu.x[f.rs2] { + true => 1, + false => 0 + }; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0x0000707f, + data: 0x00002013, + name: "SLTI", + operation: |cpu, word, _address| { + let f = parse_format_i(word); + cpu.x[f.rd] = match cpu.x[f.rs1] < f.imm { + true => 1, + false => 0 + }; + Ok(()) + }, + disassemble: dump_format_i + }, + Instruction { + mask: 0x0000707f, + data: 0x00003013, + name: "SLTIU", + operation: |cpu, word, _address| { + let f = parse_format_i(word); + cpu.x[f.rd] = match cpu.unsigned_data(cpu.x[f.rs1]) < cpu.unsigned_data(f.imm) { + true => 1, + false => 0 + }; + Ok(()) + }, + disassemble: dump_format_i + }, + Instruction { + mask: 0xfe00707f, + data: 0x00003033, + name: "SLTU", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + cpu.x[f.rd] = match cpu.unsigned_data(cpu.x[f.rs1]) < cpu.unsigned_data(cpu.x[f.rs2]) { + true => 1, + false => 0 + }; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfe00707f, + data: 0x40005033, + name: "SRA", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + cpu.x[f.rd] = cpu.sign_extend(cpu.x[f.rs1].wrapping_shr(cpu.x[f.rs2] as u32)); + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfc00707f, + data: 0x40005013, + name: "SRAI", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + let mask = match cpu.xlen { + Xlen::Bit32 => 0x1f, + Xlen::Bit64 => 0x3f + }; + let shamt = (word >> 20) & mask; + cpu.x[f.rd] = cpu.sign_extend(cpu.x[f.rs1] >> shamt); + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfc00707f, + data: 0x4000501b, + name: "SRAIW", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + let shamt = ((word >> 20) & 0x1f) as u32; + cpu.x[f.rd] = ((cpu.x[f.rs1] as i32) >> shamt) as i64; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfe00707f, + data: 0x4000503b, + name: "SRAW", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + cpu.x[f.rd] = (cpu.x[f.rs1] as i32).wrapping_shr(cpu.x[f.rs2] as u32) as i64; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xffffffff, + data: 0x10200073, + name: "SRET", + operation: |cpu, _word, _address| { + // @TODO: Throw error if higher privilege return instruction is executed + cpu.pc = match cpu.read_csr(CSR_SEPC_ADDRESS) { + Ok(data) => data, + Err(e) => return Err(e) + }; + let status = cpu.read_csr_raw(CSR_SSTATUS_ADDRESS); + let spie = (status >> 5) & 1; + let spp = (status >> 8) & 1; + let mprv = match get_privilege_mode(spp) { + PrivilegeMode::Machine => (status >> 17) & 1, + _ => 0 + }; + // Override SIE[1] with SPIE[5], set SPIE[5] to 1, set SPP[8] to 0, + // and override MPRV[17] + let new_status = (status & !0x20122) | (mprv << 17) | (spie << 1) | (1 << 5); + cpu.write_csr_raw(CSR_SSTATUS_ADDRESS, new_status); + cpu.privilege_mode = match spp { + 0 => PrivilegeMode::User, + 1 => PrivilegeMode::Supervisor, + _ => panic!() // Shouldn't happen + }; + cpu.mmu.update_privilege_mode(cpu.privilege_mode.clone()); + Ok(()) + }, + disassemble: dump_empty + }, + Instruction { + mask: 0xfe00707f, + data: 0x00005033, + name: "SRL", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + cpu.x[f.rd] = cpu.sign_extend(cpu.unsigned_data(cpu.x[f.rs1]).wrapping_shr(cpu.x[f.rs2] as u32) as i64); + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfc00707f, + data: 0x00005013, + name: "SRLI", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + let mask = match cpu.xlen { + Xlen::Bit32 => 0x1f, + Xlen::Bit64 => 0x3f + }; + let shamt = (word >> 20) & mask; + cpu.x[f.rd] = cpu.sign_extend((cpu.unsigned_data(cpu.x[f.rs1]) >> shamt) as i64); + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfc00707f, + data: 0x0000501b, + name: "SRLIW", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + let mask = match cpu.xlen { + Xlen::Bit32 => 0x1f, + Xlen::Bit64 => 0x3f + }; + let shamt = (word >> 20) & mask; + cpu.x[f.rd] = ((cpu.x[f.rs1] as u32) >> shamt) as i32 as i64; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfe00707f, + data: 0x0000503b, + name: "SRLW", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + cpu.x[f.rd] = (cpu.x[f.rs1] as u32).wrapping_shr(cpu.x[f.rs2] as u32) as i32 as i64; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfe00707f, + data: 0x40000033, + name: "SUB", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + cpu.x[f.rd] = cpu.sign_extend(cpu.x[f.rs1].wrapping_sub(cpu.x[f.rs2])); + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0xfe00707f, + data: 0x4000003b, + name: "SUBW", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + cpu.x[f.rd] = cpu.x[f.rs1].wrapping_sub(cpu.x[f.rs2]) as i32 as i64; + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0x0000707f, + data: 0x00002023, + name: "SW", + operation: |cpu, word, _address| { + let f = parse_format_s(word); + cpu.mmu.store_word(cpu.x[f.rs1].wrapping_add(f.imm) as u64, cpu.x[f.rs2] as u32) + }, + disassemble: dump_format_s + }, + Instruction { + mask: 0xffffffff, + data: 0x00200073, + name: "URET", + operation: |_cpu, _word, _address| { + // @TODO: Implement + panic!("URET instruction is not implemented yet."); + }, + disassemble: dump_empty + }, + Instruction { + mask: 0xffffffff, + data: 0x10500073, + name: "WFI", + operation: |cpu, _word, _address| { + cpu.wfi = true; + Ok(()) + }, + disassemble: dump_empty + }, + Instruction { + mask: 0xfe00707f, + data: 0x00004033, + name: "XOR", + operation: |cpu, word, _address| { + let f = parse_format_r(word); + cpu.x[f.rd] = cpu.sign_extend(cpu.x[f.rs1] ^ cpu.x[f.rs2]); + Ok(()) + }, + disassemble: dump_format_r + }, + Instruction { + mask: 0x0000707f, + data: 0x00004013, + name: "XORI", + operation: |cpu, word, _address| { + let f = parse_format_i(word); + cpu.x[f.rd] = cpu.sign_extend(cpu.x[f.rs1] ^ f.imm); + Ok(()) + }, + disassemble: dump_format_i + }, diff --git a/parse_ins.pl b/parse_ins.pl new file mode 100755 index 00000000..27043517 --- /dev/null +++ b/parse_ins.pl @@ -0,0 +1,138 @@ +#!/usr/bin/env perl + +use warnings; +use strict; + +my $file = 'instructions.txt'; +open my $info, $file or die "Could not open $file: $!\n"; + +my $ins = {}; +my $cur_ins; + +while(my $line = <$info>) { + $line =~ s/(^\s+|\s+$)//; + if ($line =~ m/^Instruction/) { + if ($cur_ins && $cur_ins->{name}) { + $ins->{delete $cur_ins->{name}} = $cur_ins; + } + $cur_ins = {}; + } elsif ($line =~ m/^mask: ([0-9abcdefx]+),/) { + $cur_ins->{mask} = $1; + } elsif ($line =~ m/^data: ([0-9abcdefx]+),/) { + $cur_ins->{data} = $1; + } elsif ($line =~ m/^name:\s*"(.+)",/) { + my $n = lc $1; + $n =~ s/\./_/g; + $cur_ins->{name} = $n; + } elsif ($line =~ m/^disassemble: dump_(.+),?$/) { + my $f = $1; + $f =~ s/_mem$//; + $cur_ins->{format} = $f; + } +} +$ins->{delete $cur_ins->{name}} = $cur_ins; + +# add layers to the onion + +my $ins_masks = {}; +foreach my $name (keys %$ins) { + my $mask = $ins->{$name}->{mask}; + $ins_masks->{$mask}->{$name} = $ins->{$name}; +} + +# parse opcode directory +sub parse_opcode_file { + my ($file) = @_; +} + +my $opcode_dir = {}; +for my $fname () { + $fname =~ m/\/opcodes-(.*)$/; + my $dir = $1; + + open my $dirf, $fname or die "Could not open $fname: $!\n"; + + while(my $line = <$dirf>) { + next if $line =~ m/^\s*$/; + next if $line =~ m/^@/; + next if $line =~ m/^#/; + + $line =~ m/^(.+?)\s/; + my $op = $1; + $op =~ s/\./_/g; + if (exists($opcode_dir->{$op})) { + die "instruction registered multiple times: $op\n"; + } + $opcode_dir->{$op} = $dir; + } + + close $dirf; +} + +my $fmt = { + format_r => "FormatR", + format_r2 => "FormatR", + format_i => "FormatI", + format_s => "FormatS", + format_u => "FormatU", + format_j => "FormatJ", + format_b => "FormatB", + format_csr => "FormatCSR", + empty => "FormatEmpty", + unknown => "FormatEmpty /* UNKNOWN */", +}; + +my $l1 = 0; +my $l2 = 0; + +my $supported = { + rv32i => 1, + rv32m => 1, + rv32a => 1, + system => 1, +}; + +my @accepted = (); + +foreach my $name (sort keys %$ins) { + # DEF(addi, FormatI, { + $l2++; + my $format = $fmt->{$ins->{$name}->{format} // "unknown"}; + die "unknown format: '$ins->{$name}->{format}'\n" if !$format; + + my $dir = $opcode_dir->{$name} or die "unknown instruction origin: $name\n"; + next if !exists($supported->{$dir}); + $l1++; + + print "DEF($name, $format, { // $dir\n NOT_IMPL\n})\n"; +} + +print "\n"; + +foreach my $mask (sort keys %$ins_masks) { + my $out = ""; + $out .= "ins_masked = ins_word & $mask;\n"; + $out .= "switch (ins_masked) {\n"; + my $found = 0; + foreach my $name (sort keys %{$ins_masks->{$mask}}) { + # RUN(addi, 0x00000013) + my $dir = $opcode_dir->{$name} or die "unknown instruction origin: $name\n"; + next if !exists($supported->{$dir}); + + my $format = $fmt->{$ins->{$name}->{format} // "unknown"}; + + $out .= " RUN($name, $ins->{$name}->{data}, ins_$format)\n"; + $found++; + } + + next if !$found; + + $out .= "}\n"; + print $out; +} + +print "\n"; +print "Found $l2 instructions, accepted $l1\n"; + + +close $info; diff --git a/riscv-opcodes b/riscv-opcodes new file mode 160000 index 00000000..7d1a0e31 --- /dev/null +++ b/riscv-opcodes @@ -0,0 +1 @@ +Subproject commit 7d1a0e3153c37cd180be9e95f331f32c225d9257 diff --git a/riscv-tests b/riscv-tests new file mode 160000 index 00000000..09cfdaac --- /dev/null +++ b/riscv-tests @@ -0,0 +1 @@ +Subproject commit 09cfdaacd9322cf0ac94818d8c852e1f4dc5bc4f diff --git a/src/cpu.h b/src/cpu.h new file mode 100644 index 00000000..3f425ffb --- /dev/null +++ b/src/cpu.h @@ -0,0 +1,41 @@ +#ifndef CPU_H +#define CPU_H + +#include +#include +#include "types.h" +#include "mem.h" +#include "emu.h" + +cpu_t cpu_init(uint8_t *mem) { + cpu_t ret; + ret.clock = 0; + for (unsigned char i = 0; i < 32; i++) { + ret.xreg[i] = 0; + } + ret.pc = 0x80000000; + ret.mem = mem; + return ret; +} + +void cpu_dump(cpu_t *cpu) { + printf("CPU state @%d:\n", cpu->clock); + for (int i = 0; i < 32; i += 4) { + printf(" .x%02d = %08x .x%02d = %08x .%02d = %08x .%02d = %08x\n", + i, cpu->xreg[i], + i+1, cpu->xreg[i+1], + i+2, cpu->xreg[i+2], + i+3, cpu->xreg[i+3]); + } + printf(" .pc = %08x\n", cpu->pc); + printf(" next ins: %08x\n", *(uint*)(cpu->mem + (cpu->pc & 0x7FFFFFFF))); +} + +void cpu_tick(cpu_t *cpu) { + cpu->clock++; + + uint ins_raw = mem_get_word(cpu, cpu->pc); + emulate(cpu, ins_raw); +} + +#endif diff --git a/src/csr.h b/src/csr.h new file mode 100644 index 00000000..7a69cbc7 --- /dev/null +++ b/src/csr.h @@ -0,0 +1,17 @@ +#ifndef CSR_H +#define CSR_H + +#include "types.h" + +uint get_csr(uint addr) { + // TODO + printf("CSR read: %x\n", addr); + return 0; +} + +void set_csr(uint addr, uint val) { + // TODO + printf("CSR write: %x <- %x\n", addr, val); +} + +#endif diff --git a/src/emu.h b/src/emu.h new file mode 100644 index 00000000..48d9324e --- /dev/null +++ b/src/emu.h @@ -0,0 +1,477 @@ +#ifndef EMU_H +#define EMU_H + +#include +#include +#include "types.h" +#include "ins.h" +#include "mem.h" +#include "csr.h" + +#define AS_SIGNED(val) (*(int32_t*)&val) +#define AS_UNSIGNED(val) (*(uint*)&val) +const uint ZERO = 0; +const uint ONE = 1; + +#define DEF(name, fmt_t, code) \ + void emu_##name(cpu_t *cpu, uint ins_word, ins_ret *ret, fmt_t ins) { code } + +#define NOT_IMPL { printf("Unimplemented instruction: %08x\n", ins_word); exit(3); } + +#define WR_RD(code) { ret->write_reg = ins.rd; ret->write_val = AS_UNSIGNED(code); } +#define WR_PC(code) { ret->pc_write = 1; ret->pc_val = code; } +#define WR_CSR(code) { ret->csr_write = ins.csr; ret->csr_val = code; } + +/* + * BEGIN INSTRUCTIONS + */ + +DEF(add, FormatR, { // rv32i + WR_RD(AS_SIGNED(cpu->xreg[ins.rs1]) + AS_SIGNED(cpu->xreg[ins.rs2])); +}) +DEF(addi, FormatI, { // rv32i + WR_RD(AS_SIGNED(cpu->xreg[ins.rs1]) + AS_SIGNED(ins.imm)); +}) +DEF(amoadd_w, FormatR, { // rv32a + NOT_IMPL +}) +DEF(amoand_w, FormatR, { // rv32a + NOT_IMPL +}) +DEF(amomaxu_w, FormatR, { // rv32a + NOT_IMPL +}) +DEF(amoor_w, FormatR, { // rv32a + NOT_IMPL +}) +DEF(amoswap_w, FormatR, { // rv32a + NOT_IMPL +}) +DEF(and, FormatR, { // rv32i + WR_RD(cpu->xreg[ins.rs1] & cpu->xreg[ins.rs2]) +}) +DEF(andi, FormatI, { // rv32i + WR_RD(cpu->xreg[ins.rs1] & ins.imm) +}) +DEF(auipc, FormatU, { // rv32i + WR_RD(cpu->pc + ins.imm) +}) +DEF(beq, FormatB, { // rv32i + if (cpu->xreg[ins.rs1] == cpu->xreg[ins.rs2]) { + WR_PC(cpu->pc + ins.imm); + } +}) +DEF(bge, FormatB, { // rv32i + if (AS_SIGNED(cpu->xreg[ins.rs1]) >= AS_SIGNED(cpu->xreg[ins.rs2])) { + WR_PC(cpu->pc + ins.imm); + } +}) +DEF(bgeu, FormatB, { // rv32i + if (AS_UNSIGNED(cpu->xreg[ins.rs1]) >= AS_UNSIGNED(cpu->xreg[ins.rs2])) { + WR_PC(cpu->pc + ins.imm); + } +}) +DEF(blt, FormatB, { // rv32i + if (AS_SIGNED(cpu->xreg[ins.rs1]) < AS_SIGNED(cpu->xreg[ins.rs2])) { + WR_PC(cpu->pc + ins.imm); + } +}) +DEF(bltu, FormatB, { // rv32i + if (AS_UNSIGNED(cpu->xreg[ins.rs1]) < AS_UNSIGNED(cpu->xreg[ins.rs2])) { + WR_PC(cpu->pc + ins.imm); + } +}) +DEF(bne, FormatB, { // rv32i + if (cpu->xreg[ins.rs1] != cpu->xreg[ins.rs2]) { + WR_PC(cpu->pc + ins.imm); + } +}) +DEF(csrrc, FormatCSR, { // system + WR_CSR(ins.value & (~cpu->xreg[ins.rs])); + WR_RD(ins.value) +}) +DEF(csrrci, FormatCSR, { // system + WR_CSR(ins.value & (~ins.rs)); + WR_RD(ins.value) +}) +DEF(csrrs, FormatCSR, { // system + WR_CSR(ins.value | cpu->xreg[ins.rs]); + WR_RD(ins.value) +}) +DEF(csrrsi, FormatCSR, { // system + WR_CSR(ins.value | ins.rs); + WR_RD(ins.value) +}) +DEF(csrrw, FormatCSR, { // system + WR_CSR(cpu->xreg[ins.rs]); + WR_RD(ins.value) +}) +DEF(csrrwi, FormatCSR, { // system + WR_CSR(ins.rs); + WR_RD(ins.value) +}) +DEF(div, FormatR, { // rv32m + uint dividend = cpu->xreg[ins.rs1]; + uint divisor = cpu->xreg[ins.rs2]; + uint result; + if (divisor == 0) { + result = 0xFFFFFFFF; + } else if (dividend == 0x80000000 && divisor == 0xFFFFFFFF) { + result = dividend; + } else { + int32_t tmp = AS_SIGNED(dividend) / AS_SIGNED(divisor); + result = AS_UNSIGNED(tmp); + } + WR_RD(result) +}) +DEF(divu, FormatR, { // rv32m + uint dividend = cpu->xreg[ins.rs1]; + uint divisor = cpu->xreg[ins.rs2]; + uint result; + if (divisor == 0) { + result = 0xFFFFFFFF; + } else { + result = dividend / divisor; + } + WR_RD(result) +}) +DEF(ebreak, FormatEmpty, { // system + NOT_IMPL +}) +DEF(ecall, FormatEmpty, { // system + if (cpu->xreg[17] == 93) { + // EXIT CALL + uint status = cpu->xreg[10] >> 1; + printf("ecall EXIT = %d (0x%x)\n", status, status); + exit(status); + } + + NOT_IMPL +}) +DEF(fence, FormatEmpty, { // rv32i + // skip +}) +DEF(fence_i, FormatEmpty, { // rv32i + // skip +}) +DEF(jal, FormatJ, { // rv32i + WR_RD(cpu->pc + 4); + WR_PC(cpu->pc + ins.imm); +}) +DEF(jalr, FormatI, { // rv32i + WR_RD(cpu->pc + 4); + WR_PC(cpu->xreg[ins.rs1] + ins.imm); +}) +DEF(lb, FormatI, { // rv32i + uint tmp = sign_extend(mem_get_byte(cpu, cpu->xreg[ins.rs1] + ins.imm), 8); + WR_RD(tmp) +}) +DEF(lbu, FormatI, { // rv32i + uint tmp = mem_get_byte(cpu, cpu->xreg[ins.rs1] + ins.imm); + WR_RD(tmp) +}) +DEF(lh, FormatI, { // rv32i + uint tmp = sign_extend(mem_get_half_word(cpu, cpu->xreg[ins.rs1] + ins.imm), 16); + WR_RD(tmp) +}) +DEF(lhu, FormatI, { // rv32i + uint tmp = mem_get_half_word(cpu, cpu->xreg[ins.rs1] + ins.imm); + WR_RD(tmp) +}) +DEF(lr_w, FormatR, { // rv32a + NOT_IMPL +}) +DEF(lui, FormatU, { // rv32i + WR_RD(ins.imm) +}) +DEF(lw, FormatI, { // rv32i + // would need sign extend for xlen > 32 + uint tmp = mem_get_word(cpu, cpu->xreg[ins.rs1] + ins.imm); + WR_RD(tmp) +}) +DEF(mret, FormatEmpty, { // system + // skip ? FIXME +}) +DEF(mul, FormatR, { // rv32m + uint tmp = AS_SIGNED(cpu->xreg[ins.rs1]) * AS_SIGNED(cpu->xreg[ins.rs2]); + WR_RD(tmp) +}) +DEF(mulh, FormatR, { // rv32m + uint tmp = ((int64_t)AS_SIGNED(cpu->xreg[ins.rs1]) * (int64_t)AS_SIGNED(cpu->xreg[ins.rs2])) >> 32; + WR_RD(tmp) +}) +DEF(mulhsu, FormatR, { // rv32m + uint tmp = ((int64_t)AS_SIGNED(cpu->xreg[ins.rs1]) * (uint64_t)AS_UNSIGNED(cpu->xreg[ins.rs2])) >> 32; + WR_RD(tmp) +}) +DEF(mulhu, FormatR, { // rv32m + uint tmp = ((uint64_t)AS_UNSIGNED(cpu->xreg[ins.rs1]) * (uint64_t)AS_UNSIGNED(cpu->xreg[ins.rs2])) >> 32; + WR_RD(tmp) +}) +DEF(or, FormatR, { // rv32i + WR_RD(cpu->xreg[ins.rs1] | cpu->xreg[ins.rs2]) +}) +DEF(ori, FormatI, { // rv32i + WR_RD(cpu->xreg[ins.rs1] | ins.imm) +}) +DEF(rem, FormatR, { // rv32m + uint dividend = cpu->xreg[ins.rs1]; + uint divisor = cpu->xreg[ins.rs2]; + uint result; + if (divisor == 0) { + result = dividend; + } else if (dividend == 0x80000000 && divisor == 0xFFFFFFFF) { + result = 0; + } else { + int32_t tmp = AS_SIGNED(dividend) % AS_SIGNED(divisor); + result = AS_UNSIGNED(tmp); + } + WR_RD(result) +}) +DEF(remu, FormatR, { // rv32m + uint dividend = cpu->xreg[ins.rs1]; + uint divisor = cpu->xreg[ins.rs2]; + uint result; + if (divisor == 0) { + result = dividend; + } else { + result = dividend % divisor; + } + WR_RD(result) +}) +DEF(sb, FormatS, { // rv32i + mem_set_byte(cpu, cpu->xreg[ins.rs1] + ins.imm, cpu->xreg[ins.rs2]); +}) +DEF(sc_w, FormatR, { // rv32a + NOT_IMPL +}) +DEF(sfence_vma, FormatEmpty, { // system + // skip +}) +DEF(sh, FormatS, { // rv32i + mem_set_half_word(cpu, cpu->xreg[ins.rs1] + ins.imm, cpu->xreg[ins.rs2]); +}) +DEF(sll, FormatR, { // rv32i + WR_RD(cpu->xreg[ins.rs1] << cpu->xreg[ins.rs2]) +}) +DEF(slli, FormatR, { // rv32i + uint shamt = (ins_word >> 20) & 0x1F; + WR_RD(cpu->xreg[ins.rs1] << shamt) +}) +DEF(slt, FormatR, { // rv32i + if (AS_SIGNED(cpu->xreg[ins.rs1]) < AS_SIGNED(cpu->xreg[ins.rs2])) { + WR_RD(ONE) + } else { + WR_RD(ZERO) + } +}) +DEF(slti, FormatI, { // rv32i + if (AS_SIGNED(cpu->xreg[ins.rs1]) < AS_SIGNED(ins.imm)) { + WR_RD(ONE) + } else { + WR_RD(ZERO) + } +}) +DEF(sltiu, FormatI, { // rv32i + if (AS_UNSIGNED(cpu->xreg[ins.rs1]) < AS_UNSIGNED(ins.imm)) { + WR_RD(ONE) + } else { + WR_RD(ZERO) + } +}) +DEF(sltu, FormatR, { // rv32i + if (AS_UNSIGNED(cpu->xreg[ins.rs1]) < AS_UNSIGNED(cpu->xreg[ins.rs2])) { + WR_RD(ONE) + } else { + WR_RD(ZERO) + } +}) +DEF(sra, FormatR, { // rv32i + uint msr = cpu->xreg[ins.rs1] & 0x80000000; + WR_RD(msr ? ~(~cpu->xreg[ins.rs1] >> cpu->xreg[ins.rs2]) : + cpu->xreg[ins.rs1] >> cpu->xreg[ins.rs2]) +}) +DEF(srai, FormatR, { // rv32i + uint msr = cpu->xreg[ins.rs1] & 0x80000000; + uint shamt = (ins_word >> 20) & 0x1F; + WR_RD(msr ? ~(~cpu->xreg[ins.rs1] >> shamt) : + cpu->xreg[ins.rs1] >> shamt) +}) +DEF(sret, FormatEmpty, { // system + NOT_IMPL +}) +DEF(srl, FormatR, { // rv32i + WR_RD(cpu->xreg[ins.rs1] >> cpu->xreg[ins.rs2]) +}) +DEF(srli, FormatR, { // rv32i + uint shamt = (ins_word >> 20) & 0x1F; + WR_RD(cpu->xreg[ins.rs1] >> shamt) +}) +DEF(sub, FormatR, { // rv32i + WR_RD(AS_SIGNED(cpu->xreg[ins.rs1]) - AS_SIGNED(cpu->xreg[ins.rs2])); +}) +DEF(sw, FormatS, { // rv32i + mem_set_word(cpu, cpu->xreg[ins.rs1] + ins.imm, cpu->xreg[ins.rs2]); +}) +DEF(uret, FormatEmpty, { // system + NOT_IMPL +}) +DEF(wfi, FormatEmpty, { // system + NOT_IMPL +}) +DEF(xor, FormatR, { // rv32i + WR_RD(cpu->xreg[ins.rs1] ^ cpu->xreg[ins.rs2]) +}) +DEF(xori, FormatI, { // rv32i + WR_RD(cpu->xreg[ins.rs1] ^ ins.imm) +}) + +/* + * END INSTRUCTIONS + */ + +#ifdef VERBOSE +#define pr_ins(name) printf("INS %s (%08x)\n", #name, ins_word); +#else +#define pr_ins(name) {} +#endif + +#define RUN(name, data, insf) case data : { \ + pr_ins(name) \ + emu_##name(cpu, ins_word, &ret, insf); \ + return ret; \ +} +ins_ret ins_select(cpu_t *cpu, uint ins_word) { + uint ins_masked; + ins_ret ret = ins_ret_noop(); + + FormatR ins_FormatR = parse_FormatR(ins_word); + FormatI ins_FormatI = parse_FormatI(ins_word); + FormatS ins_FormatS = parse_FormatS(ins_word); + FormatU ins_FormatU = parse_FormatU(ins_word); + FormatJ ins_FormatJ = parse_FormatJ(ins_word); + FormatB ins_FormatB = parse_FormatB(ins_word); + FormatCSR ins_FormatCSR = parse_FormatCSR(ins_word); + FormatEmpty ins_FormatEmpty = parse_FormatEmpty(ins_word); + + if ((ins_word & 0x00000073) == 0x00000073) { + // could be CSR instruction + ins_FormatCSR.value = get_csr(ins_FormatCSR.csr); + } + + ins_masked = ins_word & 0x0000007f; + switch (ins_masked) { + RUN(auipc, 0x00000017, ins_FormatU) + RUN(jal, 0x0000006f, ins_FormatJ) + RUN(lui, 0x00000037, ins_FormatU) + } + ins_masked = ins_word & 0x0000707f; + switch (ins_masked) { + RUN(addi, 0x00000013, ins_FormatI) + RUN(andi, 0x00007013, ins_FormatI) + RUN(beq, 0x00000063, ins_FormatB) + RUN(bge, 0x00005063, ins_FormatB) + RUN(bgeu, 0x00007063, ins_FormatB) + RUN(blt, 0x00004063, ins_FormatB) + RUN(bltu, 0x00006063, ins_FormatB) + RUN(bne, 0x00001063, ins_FormatB) + RUN(csrrc, 0x00003073, ins_FormatCSR) + RUN(csrrci, 0x00007073, ins_FormatCSR) + RUN(csrrs, 0x00002073, ins_FormatCSR) + RUN(csrrsi, 0x00006073, ins_FormatCSR) + RUN(csrrw, 0x00001073, ins_FormatCSR) + RUN(csrrwi, 0x00005073, ins_FormatCSR) + RUN(fence, 0x0000000f, ins_FormatEmpty) + RUN(fence_i, 0x0000100f, ins_FormatEmpty) + RUN(jalr, 0x00000067, ins_FormatI) + RUN(lb, 0x00000003, ins_FormatI) + RUN(lbu, 0x00004003, ins_FormatI) + RUN(lh, 0x00001003, ins_FormatI) + RUN(lhu, 0x00005003, ins_FormatI) + RUN(lw, 0x00002003, ins_FormatI) + RUN(ori, 0x00006013, ins_FormatI) + RUN(sb, 0x00000023, ins_FormatS) + RUN(sh, 0x00001023, ins_FormatS) + RUN(slti, 0x00002013, ins_FormatI) + RUN(sltiu, 0x00003013, ins_FormatI) + RUN(sw, 0x00002023, ins_FormatS) + RUN(xori, 0x00004013, ins_FormatI) + } + ins_masked = ins_word & 0xf800707f; + switch (ins_masked) { + RUN(amoadd_w, 0x0000202f, ins_FormatR) + RUN(amoand_w, 0x6000202f, ins_FormatR) + RUN(amomaxu_w, 0xe000202f, ins_FormatR) + RUN(amoor_w, 0x4000202f, ins_FormatR) + RUN(amoswap_w, 0x0800202f, ins_FormatR) + RUN(sc_w, 0x1800202f, ins_FormatR) + } + ins_masked = ins_word & 0xf9f0707f; + switch (ins_masked) { + RUN(lr_w, 0x1000202f, ins_FormatR) + } + ins_masked = ins_word & 0xfc00707f; + switch (ins_masked) { + RUN(slli, 0x00001013, ins_FormatR) + RUN(srai, 0x40005013, ins_FormatR) + RUN(srli, 0x00005013, ins_FormatR) + } + ins_masked = ins_word & 0xfe00707f; + switch (ins_masked) { + RUN(add, 0x00000033, ins_FormatR) + RUN(and, 0x00007033, ins_FormatR) + RUN(div, 0x02004033, ins_FormatR) + RUN(divu, 0x02005033, ins_FormatR) + RUN(mul, 0x02000033, ins_FormatR) + RUN(mulh, 0x02001033, ins_FormatR) + RUN(mulhsu, 0x02002033, ins_FormatR) + RUN(mulhu, 0x02003033, ins_FormatR) + RUN(or, 0x00006033, ins_FormatR) + RUN(rem, 0x02006033, ins_FormatR) + RUN(remu, 0x02007033, ins_FormatR) + RUN(sll, 0x00001033, ins_FormatR) + RUN(slt, 0x00002033, ins_FormatR) + RUN(sltu, 0x00003033, ins_FormatR) + RUN(sra, 0x40005033, ins_FormatR) + RUN(srl, 0x00005033, ins_FormatR) + RUN(sub, 0x40000033, ins_FormatR) + RUN(xor, 0x00004033, ins_FormatR) + } + ins_masked = ins_word & 0xfe007fff; + switch (ins_masked) { + RUN(sfence_vma, 0x12000073, ins_FormatEmpty) + } + ins_masked = ins_word & 0xffffffff; + switch (ins_masked) { + RUN(ebreak, 0x00100073, ins_FormatEmpty) + RUN(ecall, 0x00000073, ins_FormatEmpty) + RUN(mret, 0x30200073, ins_FormatEmpty) + RUN(sret, 0x10200073, ins_FormatEmpty) + RUN(uret, 0x00200073, ins_FormatEmpty) + RUN(wfi, 0x10500073, ins_FormatEmpty) + } + + printf("Invalid instruction: %08x\n", ins_word); + exit(2); +} + + +void emulate(cpu_t *cpu, uint ins_word) { + ins_ret ret = ins_select(cpu, ins_word); + + if (ret.pc_write > 0) { + cpu->pc = ret.pc_val; + } else { + cpu->pc += 4; + } + + if (ret.write_reg < 32 && ret.write_reg > 0) { + cpu->xreg[ret.write_reg] = ret.write_val; + } + + if (ret.csr_write) { + set_csr(ret.csr_write, ret.csr_val); + } +} + +#endif diff --git a/src/ins.h b/src/ins.h new file mode 100644 index 00000000..76a36838 --- /dev/null +++ b/src/ins.h @@ -0,0 +1,120 @@ +#ifndef INS_H +#define INS_H + +#include "types.h" + +typedef struct { + uint rs1; + uint rs2; + uint imm; +} FormatB; + +FormatB parse_FormatB(uint word) { + FormatB ret; + ret.rs1 = (word >> 15) & 0x1f; + ret.rs2 = (word >> 20) & 0x1f; + ret.imm = (word & 0x80000000 ? 0xfffff000 : 0) | + ((word << 4) & 0x00000800) | + ((word >> 20) & 0x000007e0) | + ((word >> 7) & 0x0000001e); + return ret; +} + +typedef struct { + uint csr; + uint rs; + uint rd; + uint value; +} FormatCSR; + +FormatCSR parse_FormatCSR(uint word) { + FormatCSR ret; + ret.csr = (word >> 20) & 0xfff; + ret.rs = (word >> 15) & 0x1f; + ret.rd = (word >> 7) & 0x1f; + return ret; +} + +typedef struct { + uint rd; + uint rs1; + uint imm; +} FormatI; + +FormatI parse_FormatI(uint word) { + FormatI ret; + ret.rd = (word >> 7) & 0x1f; + ret.rs1 = (word >> 15) & 0x1f; + ret.imm = (word & 0x80000000 ? 0xfffff800 : 0) | + ((word >> 20) & 0x000007ff); + return ret; +} + +typedef struct { + uint rd; + uint imm; +} FormatJ; + +FormatJ parse_FormatJ(uint word) { + FormatJ ret; + ret.rd = (word >> 7) & 0x1f; + ret.imm = (word & 0x80000000 ? 0xfff00000 : 0) | + (word & 0x000ff000) | + ((word & 0x00100000) >> 9) | + ((word & 0x7fe00000) >> 20); + return ret; +} + +typedef struct { + uint rd; + uint rs1; + uint rs2; + uint rs3; +} FormatR; + +FormatR parse_FormatR(uint word) { + FormatR ret; + ret.rd = (word >> 7) & 0x1f; + ret.rs1 = (word >> 15) & 0x1f; + ret.rs2 = (word >> 20) & 0x1f; + ret.rs3 = (word >> 27) & 0x1f; + return ret; +} + +typedef struct { + uint rs1; + uint rs2; + uint imm; +} FormatS; + +FormatS parse_FormatS(uint word) { + FormatS ret; + ret.rs1 = (word >> 15) & 0x1f; + ret.rs2 = (word >> 20) & 0x1f; + ret.imm = (word & 0x80000000 ? 0xfffff000 : 0) | + ((word >> 20) & 0xfe0) | + ((word >> 7) & 0x1f); + return ret; +} + +typedef struct { + uint rd; + uint imm; +} FormatU; + +FormatU parse_FormatU(uint word) { + FormatU ret; + ret.rd = (word >> 7) & 0x1f; + ret.imm = word & 0xfffff000; + return ret; +} + +typedef struct { +} FormatEmpty; + +FormatEmpty parse_FormatEmpty(uint word) { + FormatEmpty ret; + return ret; +} + +#endif diff --git a/src/main.c b/src/main.c new file mode 100644 index 00000000..f8d2bac3 --- /dev/null +++ b/src/main.c @@ -0,0 +1,52 @@ +/* #define VERBOSE */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "types.h" +#include "cpu.h" + +#include "../elfy/elfy.h" + +// lots of inspiration from: +// https://github.com/takahirox/riscv-rust/blob/master/src/cpu.rs + +#define handle_error(msg) \ + do { perror(msg); exit(EXIT_FAILURE); } while (0) + +const int MEM_SIZE = 1024 * 1024 * 128; + +int main(int argc, char *argv[]) { + if (argc > 2 || (argc == 2 && !strcmp(argv[1], "--help"))) { + printf("Usage: rvc []\n"); + exit(EXIT_FAILURE); + } + + uint8_t *mem = malloc(MEM_SIZE); + + if (argc == 2) { + load_elf(argv[1], strlen(argv[1]) + 1, mem, MEM_SIZE); + } + + cpu_t cpu = cpu_init(mem); + + printf("CPU initialized!\n"); + +#ifdef VERBOSE + cpu_dump(&cpu); +#endif + + while (1) { + cpu_tick(&cpu); +#ifdef VERBOSE + cpu_dump(&cpu); +#endif + } + + return 0; +} diff --git a/src/mem.h b/src/mem.h new file mode 100644 index 00000000..01517965 --- /dev/null +++ b/src/mem.h @@ -0,0 +1,42 @@ +#ifndef MEM_H +#define MEM_H + +#include +#include "types.h" + +// little endian, zero extended +uint mem_get_byte(cpu_t *cpu, uint addr) { + /* printf("TRACE: mem_get_byte(%d)\n", addr); */ + assert(addr & 0x80000000); + return cpu->mem[addr & 0x7FFFFFFF]; +} + +uint mem_get_half_word(cpu_t *cpu, uint addr) { + return mem_get_byte(cpu, addr) | ((uint16_t)mem_get_byte(cpu, addr + 1) << 8); +} + +uint mem_get_word(cpu_t *cpu, uint addr) { + return mem_get_byte(cpu, addr) | + ((uint16_t)mem_get_byte(cpu, addr + 1) << 8) | + ((uint16_t)mem_get_byte(cpu, addr + 2) << 16) | + ((uint16_t)mem_get_byte(cpu, addr + 3) << 24); +} + +void mem_set_byte(cpu_t *cpu, uint addr, uint val) { + assert(addr & 0x80000000); + cpu->mem[addr & 0x7FFFFFFF] = val; +} + +void mem_set_half_word(cpu_t *cpu, uint addr, uint val) { + mem_set_byte(cpu, addr, val & 0xFF); + mem_set_byte(cpu, addr + 1, (val >> 8) & 0xFF); +} + +void mem_set_word(cpu_t *cpu, uint addr, uint val) { + mem_set_byte(cpu, addr, val & 0xFF); + mem_set_byte(cpu, addr + 1, (val >> 8) & 0xFF); + mem_set_byte(cpu, addr + 2, (val >> 16) & 0xFF); + mem_set_byte(cpu, addr + 3, val >> 24); +} + +#endif diff --git a/src/types.h b/src/types.h new file mode 100644 index 00000000..ed4df316 --- /dev/null +++ b/src/types.h @@ -0,0 +1,37 @@ +#ifndef TYPES_H +#define TYPES_H + +#include +#include + +typedef uint32_t uint; +typedef uint32_t uint; + +typedef struct { + uint32_t clock; + uint32_t xreg[32]; + uint32_t pc; + uint8_t *mem; +} cpu_t; + +typedef struct { + uint write_reg; + uint write_val; + uint pc_write; + uint pc_val; + uint csr_write; + uint csr_val; +} ins_ret; + +ins_ret ins_ret_noop() { + ins_ret ret; + memset(&ret, 0, sizeof(ins_ret)); + return ret; +} + +uint sign_extend(uint x, uint b) { + uint m = 1U << (b - 1); + return (x ^ m) - m; +} + +#endif diff --git a/test.sh b/test.sh new file mode 100755 index 00000000..9421e5a9 --- /dev/null +++ b/test.sh @@ -0,0 +1,100 @@ +#!/bin/bash + +echo "==== Building rvc ====" +make clean +intercept-build make + +function run_test { + pushd ./riscv-tests/isa + + # rm "$1" + # rm "$1.text" + make "$1" + + # riscv64-unknown-elf-objcopy -j .text.init -O binary "$1" "$1.text" + + popd + + timeout 5s ./rvc "./riscv-tests/isa/$1" + + if [ $? -gt 0 ]; then + echo "Test failed!" + exit $? + fi +} + +if [ "$1" == "all" ]; then + TESTS="rv32ui-p-add +rv32ui-p-addi +rv32ui-p-and +rv32ui-p-andi +rv32ui-p-auipc +rv32ui-p-beq +rv32ui-p-bge +rv32ui-p-bgeu +rv32ui-p-blt +rv32ui-p-bltu +rv32ui-p-bne +rv32ui-p-fence_i +rv32ui-p-jal +rv32ui-p-jalr +rv32ui-p-lb +rv32ui-p-lbu +rv32ui-p-lh +rv32ui-p-lhu +rv32ui-p-lui +rv32ui-p-lw +rv32ui-p-or +rv32ui-p-ori +rv32ui-p-sb +rv32ui-p-sh +rv32ui-p-simple +rv32ui-p-sll +rv32ui-p-slli +rv32ui-p-slt +rv32ui-p-slti +rv32ui-p-sltiu +rv32ui-p-sltu +rv32ui-p-sra +rv32ui-p-srai +rv32ui-p-srl +rv32ui-p-srli +rv32ui-p-sub +rv32ui-p-sw +rv32ui-p-xor +rv32ui-p-xori +rv32um-p-div +rv32um-p-divu +rv32um-p-mul +rv32um-p-mulh +rv32um-p-mulhsu +rv32um-p-mulhu +rv32um-p-rem +rv32um-p-remu +#rv32ua-p-amoadd_w +#rv32ua-p-amoand_w +#rv32ua-p-amomaxu_w +#rv32ua-p-amomax_w +#rv32ua-p-amominu_w +#rv32ua-p-amomin_w +#rv32ua-p-amoor_w +#rv32ua-p-amoswap_w +#rv32ua-p-amoxor_w +#rv32mi-p-mcsr +#rv32mi-p-csr +#rv32si-p-csr" + for t in $TESTS; do + if [[ "$t" =~ ^# ]]; then continue; fi + echo + echo "==== TEST: $t ====" + echo + run_test "$t" + done + + echo + echo "All tests done!" +elif [ -n "$1" ]; then + run_test "$1" +else + echo "./test.sh (all|rv32ui-p-foo...)" +fi