From 8d01c9f1d70adbc84c3b39a865ab3468af36d6c8 Mon Sep 17 00:00:00 2001 From: Noratrieb <48135649+Noratrieb@users.noreply.github.com> Date: Sun, 13 Apr 2025 17:53:50 +0200 Subject: [PATCH] continue RV64 --- rvdc/src/lib.rs | 183 ++++++++++++++++++++++++++++------------- src/emu.rs | 108 ++++++++++++++++++++++-- tests/check.rs | 5 +- tests/check/int_comp.S | 61 +++++++++++++- 4 files changed, 290 insertions(+), 67 deletions(-) diff --git a/rvdc/src/lib.rs b/rvdc/src/lib.rs index 9664713..ff6e41f 100644 --- a/rvdc/src/lib.rs +++ b/rvdc/src/lib.rs @@ -274,10 +274,16 @@ pub enum Inst { /// Add Add { dest: Reg, src1: Reg, src2: Reg }, + /// Add 32-bit (**RV64 only**) + AddW { dest: Reg, src1: Reg, src2: Reg }, /// Subtract Sub { dest: Reg, src1: Reg, src2: Reg }, + /// Subtract 32-bit (**RV64 only**) + SubW { dest: Reg, src1: Reg, src2: Reg }, /// Shift Left Logical Sll { dest: Reg, src1: Reg, src2: Reg }, + /// Shift Left Logical 32-bit (**RV64 only**) + SllW { dest: Reg, src1: Reg, src2: Reg }, /// Set Less Than (signed) Slt { dest: Reg, src1: Reg, src2: Reg }, /// Set Less Than Unsigned @@ -286,8 +292,12 @@ pub enum Inst { Xor { dest: Reg, src1: Reg, src2: Reg }, /// Shift Right Logical (unsigned) Srl { dest: Reg, src1: Reg, src2: Reg }, + /// Shift Right Logical (unsigned) 32-bit (**RV64 only**) + SrlW { dest: Reg, src1: Reg, src2: Reg }, /// Shift Right Arithmetic (unsigned) Sra { dest: Reg, src1: Reg, src2: Reg }, + /// Shift Right Arithmetic (unsigned) 32-bit (**RV64 only**) + SraW { dest: Reg, src1: Reg, src2: Reg }, /// OR Or { dest: Reg, src1: Reg, src2: Reg }, /// AND @@ -303,6 +313,8 @@ pub enum Inst { // ------------- M extension ------------- /// Multiply Mul { dest: Reg, src1: Reg, src2: Reg }, + /// Multiply 32-bit (**RV64 only**) + MulW { dest: Reg, src1: Reg, src2: Reg }, /// Mul Upper Half Signed-Signed Mulh { dest: Reg, src1: Reg, src2: Reg }, /// Mul Upper Half Signed-Unsigned @@ -311,12 +323,20 @@ pub enum Inst { Mulhu { dest: Reg, src1: Reg, src2: Reg }, /// Divide (signed) Div { dest: Reg, src1: Reg, src2: Reg }, + /// Divide (signed) 32-bit (**RV64 only**) + DivW { dest: Reg, src1: Reg, src2: Reg }, /// Divide Unsigned Divu { dest: Reg, src1: Reg, src2: Reg }, + /// Divide Unsigned 32-bit (**RV64 only**) + DivuW { dest: Reg, src1: Reg, src2: Reg }, /// Remainder (signed) Rem { dest: Reg, src1: Reg, src2: Reg }, + /// Remainder (signed) 32-bit (**RV64 only**) + RemW { dest: Reg, src1: Reg, src2: Reg }, /// Remainder Unsigned Remu { dest: Reg, src1: Reg, src2: Reg }, + /// Remainder Unsigned 32-bit (**RV64 only**) + RemuW { dest: Reg, src1: Reg, src2: Reg }, // ------------- A extension ------------- /// Load-Reserved Word @@ -545,7 +565,7 @@ impl Display for Inst { if imm.as_u32() == 0 { write!(f, "sext.w {dest}, {src1}") } else { - write!(f, "addi.w {dest}, {src1}, {}", imm.as_i32()) + write!(f, "addiw {dest}, {src1}, {}", imm.as_i32()) } } Inst::Slti { @@ -606,13 +626,20 @@ impl Display for Inst { Inst::Add { dest, src1, src2 } => { write!(f, "add {dest}, {src1}, {src2}") } + Inst::AddW { dest, src1, src2 } => { + write!(f, "addw {dest}, {src1}, {src2}") + } Inst::Sub { dest, src1, src2 } => write!(f, "sub {dest}, {src1}, {src2}"), + Inst::SubW { dest, src1, src2 } => write!(f, "subw {dest}, {src1}, {src2}"), Inst::Sll { dest, src1, src2 } => write!(f, "sll {dest}, {src1}, {src2}"), + Inst::SllW { dest, src1, src2 } => write!(f, "sllw {dest}, {src1}, {src2}"), Inst::Slt { dest, src1, src2 } => write!(f, "slt {dest}, {src1}, {src2}"), Inst::Sltu { dest, src1, src2 } => write!(f, "sltu {dest}, {src1}, {src2}"), Inst::Xor { dest, src1, src2 } => write!(f, "xor {dest}, {src1}, {src2}"), Inst::Srl { dest, src1, src2 } => write!(f, "srl {dest}, {src1}, {src2}"), + Inst::SrlW { dest, src1, src2 } => write!(f, "srlw {dest}, {src1}, {src2}"), Inst::Sra { dest, src1, src2 } => write!(f, "sra {dest}, {src1}, {src2}"), + Inst::SraW { dest, src1, src2 } => write!(f, "sraw {dest}, {src1}, {src2}"), Inst::Or { dest, src1, src2 } => write!(f, "or {dest}, {src1}, {src2}"), Inst::And { dest, src1, src2 } => write!(f, "and {dest}, {src1}, {src2}"), Inst::Fence { fence } => match fence.fm { @@ -625,13 +652,18 @@ impl Display for Inst { Inst::Ecall => write!(f, "ecall"), Inst::Ebreak => write!(f, "ebreak"), Inst::Mul { dest, src1, src2 } => write!(f, "mul {dest}, {src1}, {src2}"), + Inst::MulW { dest, src1, src2 } => write!(f, "mulw {dest}, {src1}, {src2}"), Inst::Mulh { dest, src1, src2 } => write!(f, "mulh {dest}, {src1}, {src2}"), Inst::Mulhsu { dest, src1, src2 } => write!(f, "mulhsu {dest}, {src1}, {src2}"), Inst::Mulhu { dest, src1, src2 } => write!(f, "mulhu {dest}, {src1}, {src2}"), Inst::Div { dest, src1, src2 } => write!(f, "div {dest}, {src1}, {src2}"), + Inst::DivW { dest, src1, src2 } => write!(f, "divw {dest}, {src1}, {src2}"), Inst::Divu { dest, src1, src2 } => write!(f, "divu {dest}, {src1}, {src2}"), + Inst::DivuW { dest, src1, src2 } => write!(f, "divuw {dest}, {src1}, {src2}"), Inst::Rem { dest, src1, src2 } => write!(f, "rem {dest}, {src1}, {src2}"), + Inst::RemW { dest, src1, src2 } => write!(f, "remw {dest}, {src1}, {src2}"), Inst::Remu { dest, src1, src2 } => write!(f, "remu {dest}, {src1}, {src2}"), + Inst::RemuW { dest, src1, src2 } => write!(f, "remuw {dest}, {src1}, {src2}"), Inst::LrW { order, dest, addr } => write!(f, "lr.w{order} {dest}, ({addr})",), Inst::ScW { order, @@ -737,14 +769,6 @@ impl InstCode { let end_span = 32 - (range.end() + 1); (self.0 << (end_span)) >> (end_span + range.start()) } - fn immediate_u(self, mappings: &[(RangeInclusive, u32)]) -> Imm { - let mut imm = 0; - for (from, to) in mappings { - let value = self.extract(from.clone()); - imm |= value << to; - } - Imm::new_u32(imm) - } fn immediate_s(self, mappings: &[(RangeInclusive, u32)]) -> Imm { let mut imm = 0; let mut size = 0; @@ -775,6 +799,10 @@ impl InstCode { fn rs2_imm(self) -> u32 { self.extract(20..=24) } + // shifts on RV64 have one extra bit + fn rs2_imm_plus(self) -> u32 { + self.extract(20..=25) + } fn rd(self) -> Reg { Reg(self.extract(7..=11) as u8) } @@ -788,7 +816,8 @@ impl InstCode { self.immediate_s(&[(31..=31, 12), (7..=7, 11), (25..=30, 5), (8..=11, 1)]) } fn imm_u(self) -> Imm { - self.immediate_u(&[(12..=31, 12)]) + // Don't be fooled by the "u", LUI/AUIPC immediates are sign-extended on RV64. + self.immediate_s(&[(12..=31, 12)]) } fn imm_j(self) -> Imm { self.immediate_s(&[(31..=31, 20), (21..=30, 1), (20..=20, 11), (12..=19, 12)]) @@ -1096,7 +1125,7 @@ impl Inst { _ => { let uimm = code.immediate_s(&[(2..=6, 12), (12..=12, 17)]); if uimm.as_u32() == 0 { - return Err(decode_error(code, "C.LUI imm")); + return Err(decode_error(code, "C.LUI zero immediate")); } Inst::Lui { uimm, @@ -1352,7 +1381,13 @@ impl Inst { src1: code.rs1(), }, 0b001 => { - if code.funct7() != 0 { + // For RV32, bit 25 must be zero as well. + let left_zeroes = code.funct7() + >> match xlen { + Xlen::Rv32 => 0, + Xlen::Rv64 => 1, + }; + if left_zeroes != 0 { return Err(decode_error(code, "slli shift overflow")); } Inst::Slli { @@ -1361,24 +1396,42 @@ impl Inst { src1: code.rs1(), } } - 0b101 => match code.funct7() { - 0b0000000 => Inst::Srli { - imm: Imm::new_u32(code.rs2_imm()), - dest: code.rd(), - src1: code.rs1(), + 0b101 => match xlen { + Xlen::Rv32 => match code.funct7() { + 0b0000000 => Inst::Srli { + imm: Imm::new_u32(code.rs2_imm()), + dest: code.rd(), + src1: code.rs1(), + }, + 0b0100000 => Inst::Srai { + imm: Imm::new_u32(code.rs2_imm()), + dest: code.rd(), + src1: code.rs1(), + }, + _ => return Err(decode_error(code, "srli shift overflow")), }, - 0b0100000 => Inst::Srai { - imm: Imm::new_u32(code.rs2_imm()), - dest: code.rd(), - src1: code.rs1(), - }, - _ => return Err(decode_error(code, "OP-IMM funct7")), + Xlen::Rv64 => { + let upper = code.funct7() >> 1; + match upper { + 0b010000 => Inst::Srai { + imm: Imm::new_u32(code.rs2_imm_plus()), + dest: code.rd(), + src1: code.rs1(), + }, + 0b000000 => Inst::Srli { + imm: Imm::new_u32(code.rs2_imm_plus()), + dest: code.rd(), + src1: code.rs1(), + }, + _ => return Err(decode_error(code, "srai/srli upper bits")), + } + } }, _ => return Err(decode_error(code, "OP-IMM funct3")), }, // OP-IMM-32 0b0011011 => { - if !xlen.is_64() { + if xlen.is_32() { return Err(decode_error(code, "OP-IMM-32 only on RV64")); } @@ -1443,6 +1496,28 @@ impl Inst { _ => return Err(decode_error(code, "OP funct3/funct7")), } } + // OP-32 + 0b0111011 => { + if xlen.is_32() { + return Err(decode_error(code, "OP-IMM-32 only on RV64")); + } + + let (dest, src1, src2) = (code.rd(), code.rs1(), code.rs2()); + match (code.funct3(), code.funct7()) { + (0b000, 0b0000000) => Inst::AddW { dest, src1, src2 }, + (0b000, 0b0100000) => Inst::SubW { dest, src1, src2 }, + (0b001, 0b0000000) => Inst::SllW { dest, src1, src2 }, + (0b101, 0b0000000) => Inst::SrlW { dest, src1, src2 }, + (0b101, 0b0100000) => Inst::SraW { dest, src1, src2 }, + + (0b000, 0b0000001) => Inst::MulW { dest, src1, src2 }, + (0b100, 0b0000001) => Inst::DivW { dest, src1, src2 }, + (0b101, 0b0000001) => Inst::DivuW { dest, src1, src2 }, + (0b110, 0b0000001) => Inst::RemW { dest, src1, src2 }, + (0b111, 0b0000001) => Inst::RemuW { dest, src1, src2 }, + _ => return Err(decode_error(code, "OP-32 funct3/funct7")), + } + } // MISC-MEM 0b0001111 => { let fm = code.extract(28..=31); @@ -1713,47 +1788,45 @@ mod tests { let start_time = std::time::Instant::now(); let completed = AtomicU32::new(0); - chunks - .par_iter() - .for_each(|&start| { - let insts = (start..=start.saturating_add(CHUNK_SIZE)) - .filter_map(|code| Some((code, Inst::decode_normal(code, Xlen::Rv32).ok()?))) - .filter(|(_, inst)| is_inst_supposed_to_roundtrip(inst)) - .collect::>(); + chunks.par_iter().for_each(|&start| { + let insts = (start..=start.saturating_add(CHUNK_SIZE)) + .filter_map(|code| Some((code, Inst::decode_normal(code, Xlen::Rv32).ok()?))) + .filter(|(_, inst)| is_inst_supposed_to_roundtrip(inst)) + .collect::>(); - let mut text = - std::format!(".section {TEST_SECTION_NAME}\n.globl _start\n_start:\n"); - for (_, inst) in &insts { - writeln!(text, " {inst}").unwrap(); - } + let mut text = std::format!(".section {TEST_SECTION_NAME}\n.globl _start\n_start:\n"); + for (_, inst) in &insts { + writeln!(text, " {inst}").unwrap(); + } - let data = clang_assemble(&text, "-march=rv32ima_zihintpause"); + let data = clang_assemble(&text, "-march=rv32ima_zihintpause"); - for (i, result_code) in data.chunks(4).enumerate() { - let result_code = u32::from_le_bytes(result_code.try_into().unwrap()); + for (i, result_code) in data.chunks(4).enumerate() { + let result_code = u32::from_le_bytes(result_code.try_into().unwrap()); - assert_eq!( - insts[i].0, result_code, - "failed to rountrip!\n\ + assert_eq!( + insts[i].0, result_code, + "failed to rountrip!\n\ instruction `{:0>32b}` failed to rountrip\n\ resulted in `{:0>32b}` instead.\n\ disassembly of original instruction: `{}`", - insts[i].0, result_code, insts[i].1 - ); - } + insts[i].0, result_code, insts[i].1 + ); + } - let already_completed = completed.fetch_add(1, Ordering::Relaxed); - let already_elapsed = start_time.elapsed(); + let already_completed = completed.fetch_add(1, Ordering::Relaxed); + let already_elapsed = start_time.elapsed(); - let remaining_chunks = CHUNKS.saturating_sub(already_completed); - let remaining = already_elapsed / std::cmp::max(already_completed, 1) * remaining_chunks; + let remaining_chunks = CHUNKS.saturating_sub(already_completed); + let remaining = + already_elapsed / std::cmp::max(already_completed, 1) * remaining_chunks; - writeln!( - std::io::stdout(), - "Completed chunk {already_completed}/{CHUNKS} (estimated {remaining:?} remaining)", - ) - .unwrap(); - }); + writeln!( + std::io::stdout(), + "Completed chunk {already_completed}/{CHUNKS} (estimated {remaining:?} remaining)", + ) + .unwrap(); + }); } #[test] diff --git a/src/emu.rs b/src/emu.rs index 2881412..06a1eb9 100644 --- a/src/emu.rs +++ b/src/emu.rs @@ -386,8 +386,33 @@ impl Emulator { Inst::Srli { imm, dest, src1 } => self[dest] = self[src1].unsigned_shr(imm.as_u32()), Inst::Srai { imm, dest, src1 } => self[dest] = self[src1].signed_shr(imm.as_u32()), Inst::Add { dest, src1, src2 } => self[dest] = self[src1].add(self[src2]), + Inst::AddW { dest, src1, src2 } => { + self[dest] = XLEN::switch2( + self[src1], + self[src2], + |_, _| unreachable!(), + |a, b| (a as u32).add(b as u32) as i32 as i64 as u64, + ); + } Inst::Sub { dest, src1, src2 } => self[dest] = self[src1].sub(self[src2]), - Inst::Sll { dest, src1, src2 } => self[dest] = self[src1].shl(self[src2].truncate32()), + Inst::SubW { dest, src1, src2 } => { + self[dest] = XLEN::switch2( + self[src1], + self[src2], + |_, _| unreachable!(), + |a, b| (a as u32).sub(b as u32) as i32 as i64 as u64, + ); + } + Inst::Sll { dest, src1, src2 } => { + self[dest] = self[src1].shl(self[src2].truncate32()); + } + Inst::SllW { dest, src1, src2 } => { + self[dest] = XLEN::switch( + self[src1], + |_| unreachable!(), + |a| (a as u32).shl(self[src2].truncate32()) as i32 as i64 as u64, + ); + } Inst::Slt { dest, src1, src2 } => { self[dest] = XLEN::from_bool(self[src1].signed_lt(self[src2])); } @@ -398,9 +423,23 @@ impl Emulator { Inst::Srl { dest, src1, src2 } => { self[dest] = self[src1].unsigned_shr(self[src2].truncate32()) } + Inst::SrlW { dest, src1, src2 } => { + self[dest] = XLEN::switch( + self[src1], + |_| unreachable!(), + |a| (a as u32).unsigned_shr(self[src2].truncate32()) as i32 as i64 as u64, + ); + } Inst::Sra { dest, src1, src2 } => { self[dest] = self[src1].signed_shr(self[src2].truncate32()) } + Inst::SraW { dest, src1, src2 } => { + self[dest] = XLEN::switch( + self[src1], + |_| unreachable!(), + |a| (a as u32).unsigned_shr(self[src2].truncate32()) as i32 as i64 as u64, + ); + } Inst::Or { dest, src1, src2 } => self[dest] = self[src1].or(self[src2]), Inst::And { dest, src1, src2 } => self[dest] = self[src1].and(self[src2]), Inst::Fence { fence: _ } => { /* dont care */ } @@ -502,7 +541,40 @@ impl Emulator { } self.reservation_set = None; } - _ => return Err(Status::UnsupportedInst(inst)), + Inst::AddiW { imm, dest, src1 } => { + self[dest] = XLEN::switch( + self[src1], + |_| unreachable!(), + |src| ((src as u32).wrapping_add(imm.as_u32())) as i32 as i64 as u64, + ); + } + Inst::SlliW { imm, dest, src1 } => { + self[dest] = XLEN::switch( + self[src1], + |_| unreachable!(), + |src| ((src as u32).shl(imm.as_u32())) as u64, + ); + } + Inst::SrliW { imm, dest, src1 } => { + self[dest] = XLEN::switch( + self[src1], + |_| unreachable!(), + |src| ((src as u32).unsigned_shr(imm.as_u32())) as u64, + ); + } + Inst::SraiW { imm, dest, src1 } => { + self[dest] = XLEN::switch( + self[src1], + |_| unreachable!(), + |src| ((src as u32).signed_shr(imm.as_u32())) as u64, + ); + } + Inst::MulW { dest, src1, src2 } => todo!(), + Inst::DivW { dest, src1, src2 } => todo!(), + Inst::DivuW { dest, src1, src2 } => todo!(), + Inst::RemW { dest, src1, src2 } => todo!(), + Inst::RemuW { dest, src1, src2 } => todo!(), + _ => todo!(), } if !jumped { @@ -676,7 +748,7 @@ impl Emulator { } } -pub trait XLen: Copy + PartialEq + Eq { +pub trait XLen: Copy + PartialEq + Eq + Debug { type Signed; type NextUnsigned; type NextSigned; @@ -687,7 +759,15 @@ pub trait XLen: Copy + PartialEq + Eq { const SIGNED_MIN: Self; const MAX: Self; - fn switch(self, on_32: impl FnOnce(u32) -> R, on_64: impl FnOnce(u64) -> R) -> R; + const IS_64: bool = matches!(Self::XLEN, rvdc::Xlen::Rv64); + + fn switch(self, on_32: impl FnOnce(u32) -> u32, on_64: impl FnOnce(u64) -> u64) -> Self; + fn switch2( + self, + other: Self, + on_32: impl FnOnce(u32, u32) -> u32, + on_64: impl FnOnce(u64, u64) -> u64, + ) -> Self; fn from_bool(v: bool) -> Self { Self::from_32_z(v as u32) @@ -839,9 +919,17 @@ impl XLen for u32 { const XLEN: rvdc::Xlen = rvdc::Xlen::Rv32; - fn switch(self, on_32: impl FnOnce(u32) -> R, _: impl FnOnce(u64) -> R) -> R { + fn switch(self, on_32: impl FnOnce(u32) -> u32, _: impl FnOnce(u64) -> u64) -> Self { on_32(self) } + fn switch2( + self, + other: Self, + on_32: impl FnOnce(u32, u32) -> u32, + _: impl FnOnce(u64, u64) -> u64, + ) -> Self { + on_32(self, other) + } fn from_32_s(v: u32) -> Self { v @@ -863,9 +951,17 @@ impl XLen for u64 { const XLEN: rvdc::Xlen = rvdc::Xlen::Rv64; - fn switch(self, _: impl FnOnce(u32) -> R, on_64: impl FnOnce(u64) -> R) -> R { + fn switch(self, _: impl FnOnce(u32) -> u32, on_64: impl FnOnce(u64) -> u64) -> Self { on_64(self) } + fn switch2( + self, + other: Self, + _: impl FnOnce(u32, u32) -> u32, + on_64: impl FnOnce(u64, u64) -> u64, + ) -> Self { + on_64(self, other) + } fn from_32_s(v: u32) -> Self { v as i32 as i64 as u64 diff --git a/tests/check.rs b/tests/check.rs index 9dd4fc2..91d4380 100644 --- a/tests/check.rs +++ b/tests/check.rs @@ -30,7 +30,7 @@ fn check() -> eyre::Result<()> { test_case(tmpdir.path(), &file, name, 32, "rv32ima")?; test_case(tmpdir.path(), &file, name, 32, "rv32imac")?; - // test_case(tmpdir.path(), &file, name, 64, "rv64ima")?; + test_case(tmpdir.path(), &file, name, 64, "rv64ima")?; // test_case(tmpdir.path(), &file, name, 64, "rv64imac")?; } @@ -70,7 +70,7 @@ fn test_case( } }), Box::new(|_, xreg| { - if xreg[Reg::A7.0 as usize] == u32::MAX as u64 { + if xreg[Reg::A7.0 as usize] == u64::MAX { if xreg[Reg::A0.0 as usize] == 1 { Err(rustv32i::emu::Status::Exit { code: 0 }) } else { @@ -101,6 +101,7 @@ fn build(tmpdir: &Path, src: &Path, size: u8, march: &str) -> eyre::Result