riscv_emulator/cpu.rs
1//! CPU state, trap handling, and timer logic.
2//!
3//! This module defines the complete architectural state of the emulated
4//! RV32IMA hart: general-purpose registers, program counter, all machine-mode
5//! CSRs, and the CLINT timer counters.
6//!
7//! ## Key types
8//!
9//! - [`CpuState`] — the full register file and CSR set for one hart.
10//! - [`Trap`] — every exception and interrupt cause the CPU can raise.
11//! - [`StepResult`] — what the execution loop returns to its caller.
12//! - [`Csr`] — symbolic names for CSR addresses.
13//!
14//! ## Design note: `extraflags`
15//!
16//! To stay compatible with the original C implementation, three logical fields
17//! are packed into the single `extraflags` word:
18//!
19//! ```text
20//! bits [1:0] — privilege mode (0 = U-mode, 3 = M-mode)
21//! bit [2] — WFI sleep flag
22//! bits [31:3] — LR/SC reservation address (RAM offset >> 3)
23//! ```
24//!
25//! Accessor methods ([`CpuState::get_privilege`], [`CpuState::get_wfi`], etc.)
26//! hide this packing from the rest of the emulator.
27
28/// Outcome of a single execution batch ([`crate::emulator::Emulator::step`]).
29///
30/// The run loop inspects this value after every `step` call to decide what to
31/// do next (sleep, restart, stop, etc.).
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33#[repr(u32)]
34pub enum StepResult {
35 /// Normal execution — at least one instruction retired.
36 Ok = 0,
37 /// CPU executed a WFI and is sleeping until the next interrupt.
38 Wfi = 1,
39 /// Unrecoverable fault (only raised when `fail_on_all_faults` is set).
40 Fault = 3,
41 /// Kernel requested a system restart via SYSCON (`0x7777`).
42 Restart = 0x7777,
43 /// Kernel requested a clean shutdown via SYSCON (`0x5555`).
44 Poweroff = 0x5555,
45}
46
47/// Trap cause codes, as defined by the RISC-V privileged specification.
48///
49/// `Trap::None` is an internal sentinel meaning "no trap is pending"; it is
50/// never written to `mcause`. Interrupt causes have bit 31 set (e.g.
51/// [`Trap::IntTimer`] = `0x8000_0007`).
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53#[repr(u32)]
54pub enum Trap {
55 /// Sentinel — no trap pending.
56 None = 0xffff_ffff,
57 /// Instruction address misaligned.
58 ExcInsnMisaligned = 0,
59 /// Instruction access fault (PC outside RAM).
60 ExcInsnAccessFault = 1,
61 /// Illegal instruction encoding.
62 ExcIllegalInsn = 2,
63 /// `ebreak` breakpoint.
64 ExcBreakpoint = 3,
65 /// Load access fault (address outside RAM and not MMIO).
66 ExcLoadAccessFault = 5,
67 /// Store/AMO access fault.
68 ExcStoreAccessFault = 7,
69 /// `ecall` from U-mode.
70 ExcEcallU = 8,
71 /// `ecall` from M-mode.
72 ExcEcallM = 11,
73 /// Machine timer interrupt (MTIP — bit 7 of `mip`/`mie`).
74 IntTimer = 0x8000_0007,
75}
76
77impl Trap {
78 /// Returns `true` if this is an interrupt (bit 31 set in mcause).
79 #[inline]
80 pub fn is_interrupt(self) -> bool {
81 (self as u32) & 0x8000_0000 != 0
82 }
83 /// The value to write directly into `mcause`.
84 #[inline]
85 pub fn to_mcause(self) -> u32 {
86 self as u32
87 }
88 /// Returns `true` if this is the no-trap sentinel.
89 #[inline]
90 pub fn is_none(self) -> bool {
91 self == Trap::None
92 }
93}
94
95/// CSR addresses used by the emulator.
96///
97/// The standard machine-mode CSRs follow the RISC-V privileged spec.
98/// The `Print*` and `ReadKbd` entries (`0x136`–`0x140`) are custom debug
99/// extensions inherited from the original mini-rv32ima: they let bare-metal
100/// code print integers, hex values, strings, and characters without needing a
101/// UART driver.
102#[repr(u32)]
103pub enum Csr {
104 /// Machine status register.
105 Mstatus = 0x300,
106 /// Machine ISA register (reports RV32IMA to the kernel).
107 Misa = 0x301,
108 /// Machine interrupt-enable register.
109 Mie = 0x304,
110 /// Machine trap-handler base address.
111 Mtvec = 0x305,
112 /// Machine scratch register (used by trap handlers for context save).
113 Mscratch = 0x340,
114 /// Machine exception PC — address of the trapping instruction.
115 Mepc = 0x341,
116 /// Machine cause register.
117 Mcause = 0x342,
118 /// Machine trap value (faulting address or instruction).
119 Mtval = 0x343,
120 /// Machine interrupt-pending register.
121 Mip = 0x344,
122 /// Cycle counter (lower 32 bits).
123 Cycle = 0xc00,
124 /// Vendor ID (returns `0xff0ff0ff`).
125 Mvendorid = 0xf11,
126 // ── Debug extensions (mini-rv32ima custom CSRs) ───────────────────────
127 /// Write a signed integer to stdout.
128 PrintInt = 0x136,
129 /// Write a hex word to stdout.
130 PrintHex = 0x137,
131 /// Write a null-terminated string from RAM to stdout.
132 PrintStr = 0x138,
133 /// Write a single character to stdout.
134 PrintChar = 0x139,
135 /// Read one byte from stdin (-1 if none available).
136 ReadKbd = 0x140,
137}
138
139/// Complete architectural state of one RV32IMA hart.
140///
141/// All fields that map directly to ISA registers are `pub` so that the
142/// emulator core in [`crate::emulator`] and the MMIO layer in
143/// [`crate::mmio`] can access them without indirection. Fields that pack
144/// multiple logical values (only `extraflags`) expose typed accessors instead.
145///
146/// ## Timer fields
147///
148/// The CLINT timer is implemented with four 32-bit fields that form two 64-bit
149/// values:
150///
151/// - `timerl` / `timerh` — the running `mtime` counter, incremented by
152/// [`CpuState::tick_timer`] on every `step` call.
153/// - `timermatchl` / `timermatchh` — the `mtimecmp` threshold written by the
154/// kernel via MMIO. When `mtime > mtimecmp`, MTIP fires.
155#[derive(Default)]
156pub struct CpuState {
157 /// General-purpose registers x0–x31. x0 is architecturally zero; the
158 /// decode loop enforces this by skipping writes when `rdid == 0`.
159 pub regs: [u32; 32],
160 /// Program counter.
161 pub pc: u32,
162 /// `mstatus` CSR.
163 pub mstatus: u32,
164 /// Cycle counter — low 32 bits.
165 pub cyclel: u32,
166 /// Cycle counter — high 32 bits.
167 pub cycleh: u32,
168 /// `mtime` — low 32 bits. Incremented each `step` by elapsed microseconds.
169 pub timerl: u32,
170 /// `mtime` — high 32 bits.
171 pub timerh: u32,
172 /// `mtimecmp` — low 32 bits. Written by the kernel via CLINT MMIO.
173 pub timermatchl: u32,
174 /// `mtimecmp` — high 32 bits.
175 pub timermatchh: u32,
176 /// `mscratch` CSR.
177 pub mscratch: u32,
178 /// `mtvec` CSR — trap handler base address.
179 pub mtvec: u32,
180 /// `mie` CSR — interrupt enable bits.
181 pub mie: u32,
182 /// `mip` CSR — interrupt pending bits.
183 pub mip: u32,
184 /// `mepc` CSR — exception program counter.
185 pub mepc: u32,
186 /// `mtval` CSR — trap value (faulting address or bad instruction).
187 pub mtval: u32,
188 /// `mcause` CSR — trap cause code.
189 pub mcause: u32,
190 /// Packed field — see module-level docs for the bit layout.
191 pub extraflags: u32,
192}
193
194impl CpuState {
195 // ── Privilege mode ────────────────────────────────────────────────────────
196
197 /// Current privilege level: `0` = U-mode, `3` = M-mode.
198 ///
199 /// This emulator only uses M-mode (3) and, when the kernel is running,
200 /// U-mode (0). S-mode is not implemented.
201 #[inline]
202 pub fn get_privilege(&self) -> u32 {
203 self.extraflags & 0x3
204 }
205
206 /// Set the current privilege level.
207 #[inline]
208 pub fn set_privilege(&mut self, p: u32) {
209 self.extraflags = (self.extraflags & !0x3) | (p & 0x3);
210 }
211
212 // ── WFI sleep flag ────────────────────────────────────────────────────────
213
214 /// Returns `true` if the CPU is in WFI (Wait-For-Interrupt) sleep.
215 ///
216 /// While sleeping, `step` returns [`StepResult::Wfi`] immediately without
217 /// executing any instructions. The flag is cleared by [`tick_timer`] when
218 /// MTIP fires.
219 ///
220 /// [`tick_timer`]: CpuState::tick_timer
221 #[inline]
222 pub fn get_wfi(&self) -> bool {
223 (self.extraflags >> 2) & 1 != 0
224 }
225
226 /// Set or clear the WFI sleep flag.
227 #[inline]
228 pub fn set_wfi(&mut self, v: bool) {
229 if v {
230 self.extraflags |= 4;
231 } else {
232 self.extraflags &= !4;
233 }
234 }
235
236 // ── LR/SC reservation ─────────────────────────────────────────────────────
237
238 /// Returns the current LR/SC reservation address (RAM offset).
239 ///
240 /// Set by `LR.W`, consumed and cleared by `SC.W`.
241 #[inline]
242 pub fn get_reservation(&self) -> u32 {
243 self.extraflags >> 3
244 }
245
246 /// Record a new LR/SC reservation at the given RAM offset.
247 #[inline]
248 pub fn set_reservation(&mut self, ofs: u32) {
249 self.extraflags = (self.extraflags & 0x7) | (ofs << 3);
250 }
251
252 // ── 64-bit cycle counter ──────────────────────────────────────────────────
253
254 /// Read the full 64-bit cycle counter.
255 #[inline]
256 pub fn get_cycle64(&self) -> u64 {
257 ((self.cycleh as u64) << 32) | self.cyclel as u64
258 }
259
260 /// Write the full 64-bit cycle counter.
261 #[inline]
262 pub fn set_cycle64(&mut self, v: u64) {
263 self.cyclel = v as u32;
264 self.cycleh = (v >> 32) as u32;
265 }
266
267 // ── Timer ─────────────────────────────────────────────────────────────────
268
269 /// Advance `mtime` by `elapsed_us` microseconds and update MTIP.
270 ///
271 /// Called at the start of every [`crate::emulator::Emulator::step`] with
272 /// the number of microseconds that have passed since the previous call.
273 /// This keeps the CLINT timer accurate relative to wall-clock time.
274 ///
275 /// **MTIP** (Machine Timer Interrupt Pending, `mip` bit 7) is set when
276 /// `mtime > mtimecmp` and `mtimecmp != 0`, and cleared otherwise. Setting
277 /// MTIP also clears the WFI flag so the CPU wakes up.
278 pub fn tick_timer(&mut self, elapsed_us: u32) {
279 // Increment mtime with carry propagation between the two 32-bit halves.
280 let new = self.timerl.wrapping_add(elapsed_us);
281 if new < self.timerl {
282 self.timerh = self.timerh.wrapping_add(1);
283 }
284 self.timerl = new;
285
286 if (self.timerh > self.timermatchh
287 || (self.timerh == self.timermatchh && self.timerl > self.timermatchl))
288 && (self.timermatchh != 0 || self.timermatchl != 0)
289 {
290 self.set_wfi(false);
291 self.mip |= 1 << 7; // set MTIP
292 } else {
293 self.mip &= !(1 << 7); // clear MTIP
294 }
295 }
296
297 // ── Commit Trap ───────────────────────────────────────────────────────────
298
299 /// Save machine context and redirect the PC to `mtvec`.
300 ///
301 /// This is the final step of trap handling inside `step`. It writes the
302 /// standard trap CSRs and returns the new PC value (always `mtvec`).
303 ///
304 /// ## What gets saved
305 ///
306 /// | CSR | Value written |
307 /// |-----|--------------|
308 /// | `mepc` | PC of the trapping instruction (PC+4 for interrupts) |
309 /// | `mcause` | Trap cause code from [`Trap::to_mcause`] |
310 /// | `mtval` | Faulting address (load/store faults) or trapping PC |
311 /// | `mstatus` | MIE → MPIE, current privilege → MPP, MIE cleared |
312 ///
313 /// The privilege mode is set to M-mode (3) regardless of where the trap
314 /// originated. The kernel's trap handler is expected to inspect `mcause`,
315 /// handle the event, and return with `mret`.
316 pub fn commit_trap(&mut self, trap: Trap, rval: u32, mut pc: u32) -> u32 {
317 if trap.is_interrupt() {
318 self.mcause = trap.to_mcause();
319 self.mtval = 0;
320 // Interrupts return to the instruction *after* the one that was
321 // about to execute, so the kernel adjusts mepc += 4 before mret.
322 pc = pc.wrapping_add(4);
323 } else {
324 self.mcause = trap.to_mcause();
325 // For memory faults, mtval holds the faulting address; for all
326 // other exceptions it holds the PC of the bad instruction.
327 self.mtval = if trap == Trap::ExcLoadAccessFault || trap == Trap::ExcStoreAccessFault {
328 rval
329 } else {
330 pc
331 };
332 }
333
334 let cur_priv = self.get_privilege();
335 self.mepc = pc;
336 // Pack MIE into MPIE (bit 3 → bit 7) and privilege into MPP (bits 11:12).
337 self.mstatus = ((self.mstatus & 0x08) << 4) | (cur_priv << 11);
338 self.set_privilege(3); // enter M-mode
339
340 self.mtvec // caller sets pc = mtvec
341 }
342}