Skip to main content

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}