simple-rv128i-emulator.rs 19 KB

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