ab_riscv_interpreter/lib.rs
1//! Composable and generic RISC-V interpreter.
2//!
3//! This interpreter is designed to work with abstractions from [`ab-riscv-primitives`] crate and is
4//! similarly composable with a powerful macro system and trait abstractions over handling of
5//! memory, syscalls, etc.
6//!
7//! [`ab-riscv-primitives`]: ab_riscv_primitives
8//!
9//! The immediate needs dictate the current set of available instructions and extensions. Consider
10//! contributing if you need something not yet available.
11//!
12//! `ab-riscv-act4-runner` crate in the repository contains a complementary RISC-V Architectural
13//! Certification Tests runner for <https://github.com/riscv-non-isa/riscv-arch-test> that ensures
14//! correct implementation.
15//!
16//! Does not require a standard library (`no_std`) or an allocator.
17//!
18//! ## Supported ISA variants and extensions
19//!
20//! ISA variants:
21//! * RV32I (version 2.1)
22//! * RV32E (version 2.0)
23//! * RV64I (version 2.1)
24//! * RV64E (version 2.0)
25//!
26//! Extensions:
27//! * M (version 2.0)
28//! * B (version 1.0.0)
29//! * Zba (version 1.0.0)
30//! * Zbb (version 1.0.0)
31//! * Zbc (version 1.0.0)
32//! * Zbkb (version 1.0.1)
33//! * Zbkc (version 1.0.1)
34//! * Zbkx (version 1.0.1)
35//! * Zbs (version 1.0.0)
36//! * Zca (version 1.0.0)
37//! * Zcb (version 1.0.0)
38//! * (experimental) Zcmp (version 1.0.0)
39//! * Zkn (version 1.0.1)
40//! * Zknd (version 1.0.1)
41//! * Zkne (version 1.0.1)
42//! * Zknh (version 1.0.1)
43//! * Zicond (version 2.0)
44//! * Zicsr (version 2.0)
45//! * ZveXx (version 1.0.0), where `X` is anything allowed by the specification like Zve32x or
46//! Zve64x
47//! * Zvl*b (version 1.0.0), where `*` is anything allowed by the specification like Zvl128b or
48//! Zvl512b
49//!
50//! All extensions except experimental pass all relevant RISC-V Architectural Certification Tests
51//! (ACTs) using the ACT4 framework.
52//!
53//! Any permutation of compatible extensions is supported.
54//!
55//! Experimental extensions are known to have bugs and need more work. They are not tested against
56//! ACTs yet.
57
58#![expect(incomplete_features, reason = "generic_const_exprs")]
59#![feature(
60 bool_to_result,
61 const_cmp,
62 const_convert,
63 const_default,
64 const_index,
65 const_trait_impl,
66 generic_const_exprs,
67 result_option_map_or_default,
68 signed_bigint_helpers
69)]
70#![cfg_attr(
71 not(any(
72 all(target_arch = "riscv32", target_feature = "zbkx"),
73 all(target_arch = "riscv64", target_feature = "zbkx")
74 )),
75 feature(portable_simd)
76)]
77#![cfg_attr(
78 any(
79 all(
80 target_arch = "riscv32",
81 any(
82 target_feature = "zbb",
83 target_feature = "zbc",
84 target_feature = "zbkb",
85 target_feature = "zbkx",
86 target_feature = "zknd",
87 target_feature = "zkne",
88 target_feature = "zknh"
89 )
90 ),
91 all(
92 target_arch = "riscv64",
93 any(
94 target_feature = "zbb",
95 target_feature = "zbc",
96 target_feature = "zbkx",
97 target_feature = "zknd",
98 target_feature = "zkne",
99 target_feature = "zknh"
100 )
101 )
102 ),
103 feature(riscv_ext_intrinsics)
104)]
105#![no_std]
106
107pub mod basic;
108pub mod prelude;
109mod private;
110pub mod rv32;
111pub mod rv64;
112pub mod v;
113pub mod zicond;
114pub mod zicsr;
115
116use crate::private::BasicIntSealed;
117use ab_riscv_primitives::prelude::*;
118use core::fmt;
119use core::ops::{ControlFlow, Sub};
120
121type RegisterType<I> = <<I as Instruction>::Reg as Register>::Type;
122type Address<I> = RegisterType<I>;
123
124/// A GPR (General Purpose Register) file abstraction
125pub const trait RegisterFile<Reg>
126where
127 Reg: [const] Register,
128{
129 /// Read register value
130 fn read(&self, reg: Reg) -> Reg::Type;
131
132 /// Write register value
133 fn write(&mut self, reg: Reg, value: Reg::Type);
134}
135
136/// Errors for [`VirtualMemory`]
137#[derive(Debug, thiserror::Error)]
138pub enum VirtualMemoryError {
139 /// Out-of-bounds read
140 #[error("Out-of-bounds read at address {address}")]
141 OutOfBoundsRead {
142 /// Address of the out-of-bounds read
143 address: u64,
144 },
145 /// Out-of-bounds write
146 #[error("Out-of-bounds write at address {address}")]
147 OutOfBoundsWrite {
148 /// Address of the out-of-bounds write
149 address: u64,
150 },
151}
152
153/// Basic integer types that can be read and written to/from memory freely
154pub trait BasicInt: Sized + Copy + BasicIntSealed + 'static {}
155
156impl BasicIntSealed for u8 {}
157impl BasicIntSealed for u16 {}
158impl BasicIntSealed for u32 {}
159impl BasicIntSealed for u64 {}
160impl BasicIntSealed for i8 {}
161impl BasicIntSealed for i16 {}
162impl BasicIntSealed for i32 {}
163impl BasicIntSealed for i64 {}
164
165impl BasicInt for u8 {}
166impl BasicInt for u16 {}
167impl BasicInt for u32 {}
168impl BasicInt for u64 {}
169impl BasicInt for i8 {}
170impl BasicInt for i16 {}
171impl BasicInt for i32 {}
172impl BasicInt for i64 {}
173
174/// Virtual memory interface
175pub trait VirtualMemory {
176 /// Read a value from memory at the specified address
177 fn read<T>(&self, address: u64) -> Result<T, VirtualMemoryError>
178 where
179 T: BasicInt;
180
181 /// Unchecked read a value from memory at the specified address.
182 ///
183 /// # Safety
184 /// The address and value must be in-bounds.
185 unsafe fn read_unchecked<T>(&self, address: u64) -> T
186 where
187 T: BasicInt;
188
189 /// Read a contiguous byte slice from memory
190 fn read_slice(&self, address: u64, len: u32) -> Result<&[u8], VirtualMemoryError>;
191
192 /// Read as many contiguous bytes as possible starting at `address`, up to `len` bytes total.
193 ///
194 /// Can return an empty slice in cases like when the address is out of bounds.
195 fn read_slice_up_to(&self, address: u64, len: u32) -> &[u8];
196
197 /// Write a value to memory at the specified address
198 fn write<T>(&mut self, address: u64, value: T) -> Result<(), VirtualMemoryError>
199 where
200 T: BasicInt;
201
202 /// Write a contiguous byte slice to memory
203 fn write_slice(&mut self, address: u64, data: &[u8]) -> Result<(), VirtualMemoryError>;
204}
205
206/// Placeholder for custom errors in [`ExecutionError`]
207#[derive(Debug, Copy, Clone)]
208pub struct CustomErrorPlaceholder;
209
210impl fmt::Display for CustomErrorPlaceholder {
211 fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
212 Ok(())
213 }
214}
215
216/// Program counter errors
217#[derive(Debug, thiserror::Error)]
218pub enum ProgramCounterError<Address, CustomError = CustomErrorPlaceholder> {
219 /// Unaligned instruction
220 #[error("Unaligned instruction at address {address}")]
221 UnalignedInstruction {
222 /// Address of the unaligned instruction fetch
223 address: Address,
224 },
225 /// Memory access error
226 #[error("Memory access error: {0}")]
227 MemoryAccess(#[from] VirtualMemoryError),
228 /// Custom error
229 #[error("Custom error: {0}")]
230 Custom(CustomError),
231}
232
233/// Generic program counter
234pub trait ProgramCounter<Address, Memory, CustomError = CustomErrorPlaceholder> {
235 /// Get the current value of the program counter
236 fn get_pc(&self) -> Address;
237
238 /// Get the previous value of the program counter before executing an `instruction`.
239 ///
240 /// This is usually called from under instruction execution when the program counter is already
241 /// advanced during instruction fetching. As such, `pc - instruction_size` is expected to never
242 /// underflow.
243 #[inline(always)]
244 fn old_pc(&self, instruction_size: u8) -> Address
245 where
246 Address: From<u8> + Sub<Output = Address>,
247 {
248 // TODO: Wrapping subtraction would be nice, but causes a lot of additional generic bounds
249 // that are bad for ergonomics
250 self.get_pc() - Address::from(instruction_size)
251 }
252
253 /// Set the current value of the program counter
254 fn set_pc(
255 &mut self,
256 memory: &Memory,
257 pc: Address,
258 ) -> Result<ControlFlow<()>, ProgramCounterError<Address, CustomError>>;
259}
260
261/// Execution errors
262#[derive(Debug, thiserror::Error)]
263pub enum ExecutionError<Address, CustomError = CustomErrorPlaceholder> {
264 /// Program counter error
265 #[error("Program counter error: {0}")]
266 ProgramCounter(#[from] ProgramCounterError<Address, CustomError>),
267 /// Memory access error
268 #[error("Memory access error: {0}")]
269 MemoryAccess(#[from] VirtualMemoryError),
270 /// Unsupported `ecall` instruction
271 #[error("Unsupported `ecall` instruction at address {address:#x}")]
272 EcallUnsupported {
273 /// Address of the unsupported instruction
274 address: Address,
275 },
276 /// Unimplemented/illegal instruction
277 #[error("Unimplemented/illegal instruction at address {address:#x}")]
278 IllegalInstruction {
279 /// Address of the `unimp` instruction
280 address: Address,
281 },
282 /// Invalid instruction
283 #[error("Invalid instruction at address {address:#x}: {instruction:#010x}")]
284 InvalidInstruction {
285 /// Address of the invalid instruction
286 address: Address,
287 /// Instruction that caused the error
288 instruction: u32,
289 },
290 /// CSR error
291 #[error("CSR error: {0}")]
292 CsrError(#[from] CsrError<CustomError>),
293 /// Custom error
294 #[error("Custom error: {0}")]
295 Custom(CustomError),
296}
297
298/// Result of [`InstructionFetcher::fetch_instruction()`] call
299#[derive(Debug, Copy, Clone)]
300pub enum FetchInstructionResult<Instruction> {
301 /// Instruction fetched successfully
302 Instruction(Instruction),
303 /// Control flow instruction encountered
304 ControlFlow(ControlFlow<()>),
305}
306
307/// Generic instruction fetcher
308pub trait InstructionFetcher<I, Memory, CustomError = CustomErrorPlaceholder>
309where
310 Self: ProgramCounter<Address<I>, Memory, CustomError>,
311 I: Instruction,
312{
313 /// Fetch a single instruction at a specified address and advance the program counter on
314 /// successful fetch
315 fn fetch_instruction(
316 &mut self,
317 memory: &Memory,
318 ) -> Result<FetchInstructionResult<I>, ExecutionError<Address<I>, CustomError>>;
319}
320
321/// CSR error
322#[derive(Debug, thiserror::Error)]
323pub enum CsrError<CustomError = CustomErrorPlaceholder> {
324 /// Read only CSR
325 #[error("Read only CSR {csr_index:#x}")]
326 ReadOnly {
327 /// Index of CSR where write was attempted
328 csr_index: u16,
329 },
330 /// Illegal read access
331 #[error("Illegal read access to CSR {csr_index:#x}")]
332 IllegalRead {
333 /// Index of the accessed CSR
334 csr_index: u16,
335 },
336 /// Illegal write access
337 #[error("Illegal write access to CSR {csr_index:#x}")]
338 IllegalWrite {
339 /// Index of the accessed CSR
340 csr_index: u16,
341 },
342 /// Unknown CSR
343 #[error("Unknown CSR {csr_index:#x}")]
344 Unknown {
345 /// Index of the accessed CSR
346 csr_index: u16,
347 },
348 /// Insufficient privilege level
349 #[error(
350 "Insufficient privilege level for CSR {csr_index:#x}: required {required:?}, \
351 current {current:?}"
352 )]
353 InsufficientPrivilege {
354 /// Index of the accessed CSR
355 csr_index: u16,
356 /// Required privilege level
357 required: PrivilegeLevel,
358 /// Current privilege level
359 current: PrivilegeLevel,
360 },
361 /// Custom error
362 #[error("Custom error: {0}")]
363 Custom(CustomError),
364}
365
366/// CSRs (Control and Status Registers)
367pub trait Csrs<Reg, CustomError = CustomErrorPlaceholder>
368where
369 Reg: Register,
370{
371 /// Current privilege level
372 #[inline(always)]
373 fn privilege_level(&self) -> PrivilegeLevel {
374 PrivilegeLevel::Machine
375 }
376
377 /// Reads register value
378 fn read_csr(&self, csr_index: u16) -> Result<Reg::Type, CsrError<CustomError>>;
379
380 /// Writes register value
381 fn write_csr(&mut self, csr_index: u16, value: Reg::Type) -> Result<(), CsrError<CustomError>>;
382
383 // TODO: Remove this method once tests do not need to customize it
384 /// Process CSR read.
385 ///
386 /// Must proxy calls to [`ExecutableInstructionCsr::prepare_csr_read()`] of the root instruction
387 /// and return the output value on success. The default implementation of the method should be
388 /// fine most of the time but can be overridden in special cases or for testing purposes.
389 #[doc(hidden)]
390 fn process_csr_read<I>(
391 &self,
392 csr_index: u16,
393 raw_value: Reg::Type,
394 ) -> Result<Reg::Type, CsrError<CustomError>>
395 where
396 I: ExecutableInstructionCsr<Self, CustomError, Reg = Reg>,
397 {
398 let mut out = Reg::Type::default();
399 I::prepare_csr_read(self, csr_index, raw_value, &mut out)?
400 .ok_or(CsrError::IllegalRead { csr_index })?;
401
402 Ok(out)
403 }
404
405 // TODO: Remove this method once tests do not need to customize it
406 /// Process CSR write.
407 ///
408 /// Must proxy calls to [`ExecutableInstructionCsr::prepare_csr_write()`] of the root
409 /// instruction and return the output value on success. The default implementation of the method
410 /// should be fine most of the time but can be overridden in special cases or for testing
411 /// purposes.
412 #[doc(hidden)]
413 fn process_csr_write<I>(
414 &mut self,
415 csr_index: u16,
416 write_value: Reg::Type,
417 ) -> Result<Reg::Type, CsrError<CustomError>>
418 where
419 I: ExecutableInstructionCsr<Self, CustomError, Reg = Reg>,
420 {
421 let mut out = Reg::Type::default();
422 I::prepare_csr_write(self, csr_index, write_value, &mut out)?
423 .ok_or(CsrError::IllegalWrite { csr_index })?;
424
425 Ok(out)
426 }
427}
428
429/// Custom handler for system instructions `ecall` and `ebreak`
430pub trait SystemInstructionHandler<Reg, Regs, Memory, PC, CustomError = CustomErrorPlaceholder>
431where
432 Reg: Register,
433 Regs: RegisterFile<Reg>,
434{
435 // TODO: Figure out the correct API for this method
436 /// Handle a `fence` instruction
437 #[inline(always)]
438 fn handle_fence(&mut self, pred: u8, succ: u8) {
439 let _: u8 = pred;
440 let _: u8 = succ;
441 // NOP by default
442 }
443
444 // TODO: Figure out the correct API for this method
445 /// Handle a `fence.tso` instruction
446 #[inline(always)]
447 fn handle_fence_tso(&mut self) {
448 // NOP by default
449 }
450
451 /// Handle an `ecall` instruction
452 fn handle_ecall(
453 &mut self,
454 regs: &mut Regs,
455 memory: &mut Memory,
456 program_counter: &mut PC,
457 ) -> Result<ControlFlow<()>, ExecutionError<Reg::Type, CustomError>>;
458
459 /// Handle an `ebreak` instruction.
460 ///
461 /// NOTE: the program counter here is the current value, meaning it is already incremented past
462 /// the instruction itself.
463 #[inline(always)]
464 fn handle_ebreak(&mut self, regs: &mut Regs, memory: &mut Memory, pc: Reg::Type) {
465 // These are for cleaner trait API without leading `_` on arguments
466 let _: &Regs = regs;
467 let _: &mut Memory = memory;
468 let _: Reg::Type = pc;
469 // NOP by default
470 }
471}
472
473/// `rs1`/`rs2` instruction operands
474#[derive(Debug, Default, Copy, Clone)]
475pub struct Rs1Rs2Operands<Reg> {
476 /// `rs1` operand.
477 ///
478 /// Zero register if `rs1` was missing in the original instruction definition.
479 pub rs1: Reg,
480 /// `rs2` operand.
481 ///
482 /// Zero register if `rs2` was missing in the original instruction definition.
483 pub rs2: Reg,
484}
485
486/// `rs1`/`rs2` instruction operands
487#[derive(Debug, Default, Copy, Clone)]
488pub struct Rs1Rs2OperandValues<RegType> {
489 /// `rs1` operand value.
490 ///
491 /// Zero if `rs1` was missing in the original instruction definition.
492 pub rs1_value: RegType,
493 /// `rs2` operand value.
494 ///
495 /// Zero if `rs2` was missing in the original instruction definition.
496 pub rs2_value: RegType,
497}
498
499/// `rs1`/`rs2` instruction operands
500pub trait ExecutableInstructionOperands
501where
502 Self: Instruction,
503{
504 /// `rs1`/`rs2` instruction operands.
505 ///
506 /// Returns zero register for `rs1`/`rs2` that were missing in the original instruction
507 /// definition.
508 fn get_rs1_rs2_operands(self) -> Rs1Rs2Operands<Self::Reg>;
509}
510
511pub trait ExecutableInstructionCsr<ExtState, CustomError = CustomErrorPlaceholder>
512where
513 Self: Instruction,
514 ExtState: ?Sized,
515{
516 /// Prepare CSR read.
517 ///
518 /// This method is called on each extension one by one with the `raw_value` (contents of the
519 /// corresponding CSR register) and initially zero-initialized `output_value`. In return value
520 /// every extension can accept (`Ok(true)`), ignore (`Ok(false)`) or reject (`Err(CsrError)`)
521 /// read request. For accepted reads the extension must update `output_value` accordingly, which
522 /// will be the value used by the `Zicsr` extension handler.
523 ///
524 /// Some extensions will just copy `raw_value` to output value, others will copy only some bits
525 /// or zero some bits of the `raw_value`, as required by the specification.
526 ///
527 /// If no extension returns `Ok(true)`, the read operation is implicitly rejected as illegal
528 /// access.
529 #[inline(always)]
530 fn prepare_csr_read(
531 ext_state: &ExtState,
532 csr_index: u16,
533 raw_value: RegisterType<Self>,
534 output_value: &mut RegisterType<Self>,
535 ) -> Result<bool, CsrError<CustomError>> {
536 // These are for cleaner trait API without leading `_` on arguments
537 let _: &ExtState = ext_state;
538 let _: u16 = csr_index;
539 let _: RegisterType<Self> = raw_value;
540 let _: &mut RegisterType<Self> = output_value;
541 // The default implementation is to not allow anything
542 Ok(false)
543 }
544
545 /// Prepare CSR write.
546 ///
547 /// This method is called on each extension one by one with `write_value` being prepared by the
548 /// `Zicsr` extension handler. In return value every extension can accept (`Ok(true)`), ignore
549 /// (`Ok(false)`) or reject (`Err(CsrError)`) write request. For accepted writes the extension
550 /// must update `output_value` accordingly, which will be written to the corresponding CSR
551 /// register.
552 ///
553 /// Some extensions will just copy `write_value` to output value, others will copy some bits or
554 /// zero some bits of the `write_value`, as required by the specification.
555 ///
556 /// If no extension returns `Ok(true)`, the write operation is implicitly rejected as illegal
557 /// access.
558 #[inline(always)]
559 fn prepare_csr_write(
560 ext_state: &mut ExtState,
561 csr_index: u16,
562 write_value: RegisterType<Self>,
563 output_value: &mut RegisterType<Self>,
564 ) -> Result<bool, CsrError<CustomError>> {
565 // These are for cleaner trait API without leading `_` on arguments
566 let _: &mut ExtState = ext_state;
567 let _: u16 = csr_index;
568 let _: RegisterType<Self> = write_value;
569 let _: &mut RegisterType<Self> = output_value;
570 // The default implementation is to not allow anything
571 Ok(false)
572 }
573}
574
575/// Trait for executable instructions
576pub trait ExecutableInstruction<
577 Regs,
578 ExtState,
579 Memory,
580 PC,
581 InstructionHandler,
582 CustomError = CustomErrorPlaceholder,
583> where
584 Self: ExecutableInstructionOperands + ExecutableInstructionCsr<ExtState, CustomError>,
585{
586 /// Execute instruction.
587 ///
588 /// Instructions might place additional constraints on `ExtState` to require additional
589 /// registers or other resources. If no such constraint is used, `()` can be used as a
590 /// placeholder.
591 ///
592 /// On success `Ok(ControlFlow::Continue((rd, rd_value)))` is returned, which will be written
593 /// into the register file. In most cases this is the only register that needs to be written. If
594 /// no value needs to be written, `Ok(ControlFlow::Continue(Default::default()))` should be
595 /// returned, which corresponds to `Ok(ControlFlow::Continue(Reg::ZERO, 0))` and is no-op.
596 #[expect(clippy::type_complexity, reason = "Generic return type")]
597 fn execute(
598 self,
599 rs1rs2_values: Rs1Rs2OperandValues<<Self::Reg as Register>::Type>,
600 regs: &mut Regs,
601 ext_state: &mut ExtState,
602 memory: &mut Memory,
603 program_counter: &mut PC,
604 system_instruction_handler: &mut InstructionHandler,
605 ) -> Result<
606 ControlFlow<(), (Self::Reg, <Self::Reg as Register>::Type)>,
607 ExecutionError<Address<Self>, CustomError>,
608 >;
609}