simple-rv128i-emulator.rs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  1. /// This example illustrates how to use non-usize SBI register values on emulators.
  2. ///
  3. /// We take RISC-V RV128I as an example, since as of now (2025 CE) there are almost no common host
  4. /// platforms that support 128-bit pointer width. This example shows how to write an emulator whose
  5. /// pointer width differs from that of the host platform.
  6. ///
  7. /// The emulator starts in S-mode, allowing the emulated supervisor software to make SBI calls via
  8. /// the `ecall` instruction.
  9. use XReg::*;
  10. use core::ops::ControlFlow;
  11. use sbi_spec::binary::SbiRet;
  12. /// Represents a simple RV128I hardware thread (hart) emulator.
  13. ///
  14. /// The hart contains:
  15. /// - A set of 32 general-purpose registers (each 128-bit wide)
  16. /// - A program counter (PC) also 128-bit wide
  17. /// - An instruction memory that holds the binary instructions to execute
  18. pub struct SimpleRv128IHart {
  19. xregs: [u128; 32],
  20. pc: u128,
  21. inst_memory: InstMemory<0x2000_0000, { 0x4000 / 4 }>,
  22. }
  23. /// The main function that sets up the instruction memory, runs the emulator, and prints the result.
  24. fn main() {
  25. // Create a new instruction memory with all instructions unimplemented (zeroed out)
  26. let mut memory = InstMemory::new_unimp();
  27. // --- Build a simple program in instruction memory ---
  28. // Call SBI probe_extension to probe the BASE extension which always exists
  29. memory.li(0x0, A0, 0x10);
  30. memory.li(0x4, A6, 3);
  31. memory.li(0x8, A7, 0x10);
  32. memory.ecall(0xC);
  33. // Judge if the SBI call result is zero; if non-zero, jump to emulation failed.
  34. memory.beqz(0x10, A1, 0x1C);
  35. // Emulation success, call SBI system_reset to shutdown emulation
  36. memory.li(0x14, A0, 0x0);
  37. memory.li(0x18, A1, 0x0);
  38. memory.li(0x1C, A6, 0x0);
  39. memory.li(0x20, A7, 0x53525354);
  40. memory.ecall(0x28);
  41. // Emulation failed, call SBI system_reset with SYSTEM_FAILURE to shutdown emulation
  42. memory.li(0x2C, A0, 0x0);
  43. memory.li(0x30, A1, 0x1);
  44. memory.li(0x34, A6, 0x0);
  45. memory.li(0x38, A7, 0x53525354);
  46. memory.ecall(0x44);
  47. // --- Initialize the emulator hart ---
  48. let mut hart = SimpleRv128IHart::new(memory);
  49. // Run the emulation loop, executing one instruction at a time.
  50. // The loop breaks when an SBI call requests to shutdown the emulator (returns a special error value).
  51. let emulation_result = loop {
  52. match hart.stepi() {
  53. Ok(()) => {} // Instruction executed normally; continue to the next one.
  54. Err(Exception::SupervisorEcall) => match handle_ecall(&mut hart) {
  55. // If the SBI call indicates a shutdown (with a special error value), break out of the loop.
  56. ControlFlow::Break(value) => break value,
  57. // Otherwise, continue execution.
  58. ControlFlow::Continue(()) => continue,
  59. },
  60. // Any other exception is considered unexpected, so we print an error and terminate.
  61. Err(e) => {
  62. println!("Emulation failed for unexpected exception: {:?}", e);
  63. return;
  64. }
  65. }
  66. };
  67. // Print the final result of the emulation.
  68. println!("Emulation finished. Result: {}", emulation_result);
  69. if emulation_result == 0 {
  70. println!("✓ Test success!");
  71. } else {
  72. println!("✗ Test failed, emulator returns {}", emulation_result);
  73. }
  74. }
  75. /// Handle an SBI call given the extension and function numbers, along with its parameters.
  76. ///
  77. /// This is a simple SBI implementation (without using RustSBI) that supports a few functions:
  78. /// - BASE probe_extension: checks if an SBI extension exists.
  79. /// - SRST system_reset: performs a system reset (shutdown).
  80. ///
  81. /// Note that the returned `SbiRet<u128>` represents an `SbiRet` with `u128` as the SBI register
  82. /// type.
  83. ///
  84. /// # Parameters
  85. /// - `extension`: The SBI extension identifier (from register A7).
  86. /// - `function`: The SBI function number (from register A6).
  87. /// - `param`: An array containing SBI call parameters (from registers A0-A5).
  88. ///
  89. /// # Returns
  90. /// An `SbiRet` structure containing the error and return values.
  91. fn handle_sbi_call(extension: u128, function: u128, param: [u128; 6]) -> SbiRet<u128> {
  92. match (extension, function) {
  93. // BASE probe_extension: if the parameter matches the BASE extension identifier, return 1.
  94. (0x10, 3) => {
  95. if param[0] == 0x10 {
  96. SbiRet::success(1)
  97. } else {
  98. SbiRet::success(0)
  99. }
  100. }
  101. // SRST system_reset: perform a system reset if the reset type is shutdown.
  102. (0x53525354, 0) => {
  103. let (reset_type, reset_reason) = (param[0], param[1]);
  104. if reset_type == sbi_spec::srst::RESET_TYPE_SHUTDOWN as u128 {
  105. // Use a special SBI error value (0x114514) to signal platform shutdown.
  106. SbiRet {
  107. value: reset_reason,
  108. error: 0x114514,
  109. }
  110. } else {
  111. SbiRet::not_supported()
  112. }
  113. }
  114. // All other SBI calls are not supported.
  115. _ => SbiRet::not_supported(),
  116. }
  117. }
  118. /* -- Implementations of SimpleRv128Platform -- */
  119. /// Handle the supervisor call (ecall) exception by performing an SBI call.
  120. ///
  121. /// This function extracts the parameters from the hart's registers, performs the SBI call, and
  122. /// then updates the hart's registers and program counter with the results.
  123. ///
  124. /// # Parameters
  125. /// - `hart`: A mutable reference to the RV128I hart emulator.
  126. ///
  127. /// # Returns
  128. /// - `ControlFlow::Break(value)` if the SBI call indicates that the platform should shutdown.
  129. /// - `ControlFlow::Continue(())` if the emulation should continue.
  130. fn handle_ecall(hart: &mut SimpleRv128IHart) -> ControlFlow<u128> {
  131. println!("Handle ecall, registers: {:x?}", hart.xregs);
  132. // Extract SBI call parameters from registers A0-A5.
  133. let param = [
  134. hart.xregs[A0 as usize],
  135. hart.xregs[A1 as usize],
  136. hart.xregs[A2 as usize],
  137. hart.xregs[A3 as usize],
  138. hart.xregs[A4 as usize],
  139. hart.xregs[A5 as usize],
  140. ];
  141. // Call the SBI handler with the extension and function numbers from registers A7 and A6.
  142. let ret = handle_sbi_call(hart.xregs[A7 as usize], hart.xregs[A6 as usize], param);
  143. println!("SbiRet: {:?}", ret);
  144. // If the SBI call returns the special error value (0x114514), signal shutdown.
  145. if ret.error == 0x114514 {
  146. return ControlFlow::Break(ret.value);
  147. }
  148. // Otherwise, store the error and return values into registers A0 and A1, respectively.
  149. hart.xregs[A0 as usize] = ret.error;
  150. hart.xregs[A1 as usize] = ret.value;
  151. // Advance the program counter past the ecall instruction.
  152. hart.pc = hart.pc.wrapping_add(4);
  153. ControlFlow::Continue(())
  154. }
  155. /// An instruction memory implementation that holds a fixed number of instructions.
  156. ///
  157. /// `BASE` defines the starting memory address, and `N_INSNS` is the number of 32-bit words.
  158. pub struct InstMemory<const BASE: usize, const N_INSNS: usize> {
  159. inner: [u32; N_INSNS],
  160. }
  161. /// Opcode and function constant definitions for a simplified RISC-V subset.
  162. const OPCODE_OP_IMM: u32 = 0b001_0011;
  163. const OPCODE_LUI: u32 = 0b011_0111;
  164. const OPCODE_BRANCH: u32 = 0b110_0011;
  165. const FUNCT3_OP_ADD_SUB: u32 = 0b000;
  166. const FUNCT3_BRANCH_BEQ: u32 = 0b000;
  167. impl<const BASE: usize, const N_INSNS: usize> InstMemory<BASE, N_INSNS> {
  168. /// Creates a new instance of instruction memory with all instructions set to unimplemented (zero).
  169. pub fn new_unimp() -> Self {
  170. Self {
  171. inner: [0; N_INSNS],
  172. }
  173. }
  174. /// Assemble an ADDI instruction and store it at the given memory index.
  175. ///
  176. /// # Parameters
  177. /// - `idx`: The byte offset at which to place the instruction.
  178. /// - `rd`: The destination register.
  179. /// - `rs`: The source register.
  180. /// - `simm12`: The 12-bit signed immediate.
  181. pub fn addi(&mut self, idx: usize, rd: XReg, rs: XReg, simm12: impl Into<Simm12>) {
  182. let funct3 = FUNCT3_OP_ADD_SUB;
  183. let opcode = OPCODE_OP_IMM;
  184. let word = (u32::from(simm12.into().0) << 20)
  185. | ((rs as u32) << 15)
  186. | (funct3 << 12)
  187. | ((rd as u32) << 7)
  188. | opcode;
  189. self.inner[idx / 4] = word;
  190. }
  191. /// Assemble a LUI (Load Upper Immediate) instruction and store it at the given memory index.
  192. ///
  193. /// # Parameters
  194. /// - `idx`: The byte offset at which to place the instruction.
  195. /// - `rd`: The destination register.
  196. /// - `simm20`: The 20-bit immediate value.
  197. pub fn lui(&mut self, idx: usize, rd: XReg, simm20: impl Into<Simm20>) {
  198. let opcode = OPCODE_LUI;
  199. let word = (u32::from(simm20.into().0) << 12) | ((rd as u32) << 7) | opcode;
  200. self.inner[idx / 4] = word;
  201. }
  202. /// Load an immediate value into a register.
  203. ///
  204. /// This function will generate either a single ADDI instruction (if the upper 20 bits are zero)
  205. /// or a LUI followed by an ADDI instruction.
  206. ///
  207. /// # Parameters
  208. /// - `idx`: The byte offset at which to place the instructions.
  209. /// - `rd`: The destination register.
  210. /// - `imm`: The immediate value (32-bit).
  211. pub fn li(&mut self, idx: usize, rd: XReg, imm: u32) {
  212. let simm20 = (imm >> 12) & 0xFFFFF;
  213. let simm12 = imm & 0xFFF;
  214. if simm20 != 0 {
  215. self.lui(idx, rd, simm20);
  216. self.addi(idx + 0x4, rd, rd, simm12);
  217. } else {
  218. self.addi(idx, rd, XReg::Zero, simm12);
  219. }
  220. }
  221. /// Assemble a BEQ (branch if equal) instruction and store it at the given memory index.
  222. ///
  223. /// # Parameters
  224. /// - `idx`: The byte offset at which to place the instruction.
  225. /// - `rs1`: The first source register.
  226. /// - `rs2`: The second source register.
  227. /// - `offset`: The branch offset.
  228. pub fn beq(&mut self, idx: usize, rs1: XReg, rs2: XReg, offset: impl Into<Offset>) {
  229. let opcode = OPCODE_BRANCH;
  230. let funct3 = FUNCT3_BRANCH_BEQ;
  231. // Convert offset into the proper bit segments for the instruction encoding.
  232. let offset_u32 = u32::from_ne_bytes(i32::to_ne_bytes(offset.into().0));
  233. let simm12_12 = (offset_u32 & 0b1_0000_0000_0000) >> 12;
  234. let simm12_11 = (offset_u32 & 0b1000_0000_0000) >> 11;
  235. let simm12_10_5 = (offset_u32 & 0b111_1110_0000) >> 5;
  236. let simm12_4_1 = (offset_u32 & 0b1_1110) >> 1;
  237. let word = simm12_12 << 31
  238. | simm12_10_5 << 25
  239. | ((rs2 as u32) << 20)
  240. | ((rs1 as u32) << 15)
  241. | (funct3 << 12)
  242. | simm12_4_1 << 8
  243. | simm12_11 << 7
  244. | opcode;
  245. self.inner[idx / 4] = word;
  246. }
  247. /// Assemble a BEQZ (branch if equal to zero) instruction.
  248. ///
  249. /// This is a special case of BEQ where the second register is hardwired to zero.
  250. ///
  251. /// # Parameters
  252. /// - `idx`: The byte offset at which to place the instruction.
  253. /// - `rs`: The register to test for zero.
  254. /// - `offset`: The branch offset.
  255. pub fn beqz(&mut self, idx: usize, rs: XReg, offset: impl Into<Offset>) {
  256. self.beq(idx, rs, Zero, offset);
  257. }
  258. /// Assemble an ECALL instruction at the given offset.
  259. ///
  260. /// This instruction triggers a supervisor call exception.
  261. ///
  262. /// # Parameters
  263. /// - `offset`: The byte offset at which to place the ecall instruction.
  264. pub fn ecall(&mut self, offset: usize) {
  265. let word = 0b000000000000_00000_000_00000_1110011;
  266. self.inner[offset / 4] = word;
  267. }
  268. /// Retrieve an instruction word from instruction memory based on the given pointer.
  269. ///
  270. /// Returns `None` if the pointer is not aligned or outside the allocated memory range.
  271. ///
  272. /// # Parameters
  273. /// - `ptr`: The 128-bit address from which to fetch the instruction.
  274. pub fn get(&mut self, ptr: u128) -> Option<u32> {
  275. if ptr % 4 != 0 || ptr >= (BASE + 4 * N_INSNS) as u128 {
  276. return None;
  277. }
  278. Some(self.inner[(ptr as usize - BASE) / 4])
  279. }
  280. }
  281. impl SimpleRv128IHart {
  282. /// Creates a new RV128I hart emulator with the given instruction memory.
  283. ///
  284. /// The hart is initialized with all registers set to zero and the program counter
  285. /// set to the base address of the instruction memory.
  286. pub fn new(inst_memory: InstMemory<0x2000_0000, { 0x4000 / 4 }>) -> Self {
  287. Self {
  288. xregs: [0; 32],
  289. pc: 0x2000_0000,
  290. inst_memory,
  291. }
  292. }
  293. /// Execute one instruction step.
  294. ///
  295. /// This function fetches the instruction at the current program counter (PC),
  296. /// decodes it, and then executes it. The PC is updated accordingly.
  297. ///
  298. /// # Returns
  299. /// - `Ok(())` if the instruction executed normally.
  300. /// - `Err(Exception::SupervisorEcall)` if an ecall instruction was encountered (SBI call).
  301. /// - `Err(e)` for any other exceptions.
  302. pub fn stepi(&mut self) -> Result<(), Exception> {
  303. let raw_insn = self
  304. .inst_memory
  305. .get(self.pc)
  306. .ok_or(Exception::InstructionAccessFault)?;
  307. println!("Raw insn at 0x{:x?} is 0x{:x?}", self.pc, raw_insn);
  308. // Attempt to decode the raw instruction into one of the supported instruction variants.
  309. let parsed_insn =
  310. Instruction::try_from(raw_insn).map_err(|_| Exception::IllegalInstruction)?;
  311. match parsed_insn {
  312. Instruction::Addi(rd, rs, simm12) => {
  313. self.xregs[rd as usize] = self.xregs[rs as usize] + simm12.0 as u128;
  314. self.pc = self.pc.wrapping_add(4);
  315. }
  316. Instruction::Lui(rd, simm20) => {
  317. self.xregs[rd as usize] = (simm20.0 as u128) << 12;
  318. self.pc = self.pc.wrapping_add(4);
  319. }
  320. Instruction::Beq(rs1, rs2, offset) => {
  321. if self.xregs[rs1 as usize] == self.xregs[rs2 as usize] {
  322. self.pc = self.pc.wrapping_add_signed(offset.0 as i128);
  323. } else {
  324. self.pc = self.pc.wrapping_add(4);
  325. }
  326. }
  327. Instruction::Ecall => return Err(Exception::SupervisorEcall),
  328. }
  329. Ok(())
  330. }
  331. }
  332. /* -- RISC-V ISA enumerations and structures -- */
  333. /// RISC-V exceptions that may occur during emulation.
  334. #[derive(Debug)]
  335. pub enum Exception {
  336. /// The instruction is illegal or not supported.
  337. IllegalInstruction,
  338. /// The instruction memory access failed (e.g., due to an out-of-bound address).
  339. InstructionAccessFault,
  340. /// An ecall was executed in supervisor mode.
  341. SupervisorEcall,
  342. }
  343. /// Enum representing the supported instructions in our simplified RV128I emulator.
  344. #[derive(Debug)]
  345. pub enum Instruction {
  346. /// ADDI instruction: rd = rs + immediate.
  347. Addi(XReg, XReg, Simm12),
  348. /// LUI instruction: rd = immediate << 12.
  349. Lui(XReg, Simm20),
  350. /// BEQ instruction: if (rs1 == rs2) branch to PC + offset.
  351. Beq(XReg, XReg, Offset),
  352. /// ECALL instruction to trigger a supervisor call.
  353. Ecall,
  354. }
  355. impl TryFrom<u32> for Instruction {
  356. type Error = ();
  357. /// Attempts to decode a 32-bit word into a supported Instruction.
  358. ///
  359. /// Returns an error if the instruction encoding does not match any known pattern.
  360. fn try_from(value: u32) -> Result<Self, Self::Error> {
  361. let opcode = value & 0x7F;
  362. let rd = ((value >> 7) & 0x1F).try_into().unwrap();
  363. let rs1 = ((value >> 15) & 0x1F).try_into().unwrap();
  364. let rs2 = ((value >> 20) & 0x1F).try_into().unwrap();
  365. let funct3 = (value >> 12) & 0b111;
  366. let simm12 = (value >> 20).into();
  367. let simm20 = (value >> 12).into();
  368. // Decode the branch offset from its scattered bit fields.
  369. let offset = {
  370. let offset12 = value >> 31;
  371. let offset10_5 = (value >> 25) & 0x3F;
  372. let offset4_1 = (value >> 8) & 0xF;
  373. let offset11 = (value >> 7) & 0x1;
  374. let value = (offset4_1 << 1) | (offset10_5 << 5) | (offset11 << 11) | (offset12 << 12);
  375. value.into()
  376. };
  377. if opcode == OPCODE_OP_IMM && funct3 == FUNCT3_OP_ADD_SUB {
  378. Ok(Self::Addi(rd, rs1, simm12))
  379. } else if opcode == OPCODE_LUI {
  380. Ok(Self::Lui(rd, simm20))
  381. } else if opcode == OPCODE_BRANCH && funct3 == FUNCT3_BRANCH_BEQ {
  382. Ok(Self::Beq(rs1, rs2, offset))
  383. } else if value == 0b000000000000_00000_000_00000_1110011 {
  384. Ok(Self::Ecall)
  385. } else {
  386. Err(())
  387. }
  388. }
  389. }
  390. /// Enumeration of RISC-V registers.
  391. ///
  392. /// Each variant corresponds to a register name and its associated register number.
  393. #[derive(Clone, Copy, Debug)]
  394. pub enum XReg {
  395. Zero = 0,
  396. Ra = 1,
  397. Sp = 2,
  398. Gp = 3,
  399. Tp = 4,
  400. T0 = 5,
  401. T1 = 6,
  402. T2 = 7,
  403. S0 = 8,
  404. S1 = 9,
  405. A0 = 10,
  406. A1 = 11,
  407. A2 = 12,
  408. A3 = 13,
  409. A4 = 14,
  410. A5 = 15,
  411. A6 = 16,
  412. A7 = 17,
  413. S2 = 18,
  414. S3 = 19,
  415. S4 = 20,
  416. S5 = 21,
  417. S6 = 22,
  418. S7 = 23,
  419. S8 = 24,
  420. S9 = 25,
  421. S10 = 26,
  422. S11 = 27,
  423. T3 = 28,
  424. T4 = 29,
  425. T5 = 30,
  426. T6 = 31,
  427. }
  428. impl TryFrom<u32> for XReg {
  429. type Error = ();
  430. /// Convert a u32 into an XReg.
  431. /// Returns an error if the value does not correspond to a valid register number.
  432. fn try_from(value: u32) -> Result<Self, Self::Error> {
  433. Ok(match value {
  434. 0 => Zero,
  435. 1 => Ra,
  436. 2 => Sp,
  437. 3 => Gp,
  438. 4 => Tp,
  439. 5 => T0,
  440. 6 => T1,
  441. 7 => T2,
  442. 8 => S0,
  443. 9 => S1,
  444. 10 => A0,
  445. 11 => A1,
  446. 12 => A2,
  447. 13 => A3,
  448. 14 => A4,
  449. 15 => A5,
  450. 16 => A6,
  451. 17 => A7,
  452. 18 => S2,
  453. 19 => S3,
  454. 20 => S4,
  455. 21 => S5,
  456. 22 => S6,
  457. 23 => S7,
  458. 24 => S8,
  459. 25 => S9,
  460. 26 => S10,
  461. 27 => S11,
  462. 28 => T3,
  463. 29 => T4,
  464. 30 => T5,
  465. 31 => T6,
  466. _ => return Err(()),
  467. })
  468. }
  469. }
  470. /// A 12-bit signed immediate value used in instructions such as ADDI.
  471. #[derive(Clone, Copy, Debug)]
  472. pub struct Simm12(u16);
  473. impl From<u32> for Simm12 {
  474. fn from(value: u32) -> Self {
  475. Self((value & 0x0FFF) as u16)
  476. }
  477. }
  478. /// A 20-bit immediate value used in instructions such as LUI.
  479. #[derive(Clone, Copy, Debug)]
  480. pub struct Simm20(u32);
  481. impl From<u32> for Simm20 {
  482. fn from(value: u32) -> Self {
  483. Self(value & 0xFFFFF)
  484. }
  485. }
  486. /// A branch offset used in branch instructions.
  487. #[derive(Clone, Copy, Debug)]
  488. pub struct Offset(i32);
  489. impl From<i32> for Offset {
  490. fn from(value: i32) -> Self {
  491. Self(value & 0x1FFE)
  492. }
  493. }
  494. impl From<u32> for Offset {
  495. fn from(mut value: u32) -> Self {
  496. value = value & 0x1FFE;
  497. if value & 0x1000 != 0 {
  498. value |= 0xFFFFE000;
  499. }
  500. let ans = i32::from_ne_bytes(u32::to_ne_bytes(value));
  501. Self(ans)
  502. }
  503. }