Skip to main content

ab_riscv_interpreter/
zkr.rs

1//! Zkr extension
2
3#[cfg(test)]
4mod tests;
5pub mod zkr_helpers;
6
7use crate::zicsr::zicsr_helpers;
8use crate::{
9    CsrError, Csrs, ExecutableInstruction, ExecutableInstructionCsr, ExecutableInstructionOperands,
10    ExecutableInstructionResult, ExecutionError, RegisterFile, Rs1Rs2OperandValues, Rs1Rs2Operands,
11};
12use ab_riscv_macros::instruction_execution;
13use ab_riscv_primitives::prelude::*;
14use core::marker::Destruct;
15use core::ops::ControlFlow;
16
17/// Result of polling the entropy source behind the `Zkr` extension's `seed` CSR, corresponding to
18/// one of the specification's `OPST` states
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum ZkrSeedPoll {
21    /// Built-in self-test is being performed (`OPST=BIST`).
22    ///
23    /// If this is returned after some other state was previously observed, it signals a
24    /// non-fatal, but noteworthy, self-test alarm.
25    Bist,
26    /// A sufficient amount of entropy is not yet available (`OPST=WAIT`).
27    ///
28    /// This is not an error condition and may be observed more often than [`Self::Es16`], since
29    /// physical entropy sources often have low bandwidth.
30    Wait,
31    /// 16 bits of randomness are available (`OPST=ES16`), guaranteed to meet the specification's
32    /// minimum entropy requirements regardless of implementation.
33    Es16(u16),
34    /// An unrecoverable self-test error has occurred (`OPST=DEAD`).
35    ///
36    /// Once returned, all future polls are expected to also return [`Self::Dead`].
37    Dead,
38}
39
40/// Entropy source behind the `Zkr` extension's `seed` CSR.
41///
42/// Every access to `seed` polls the entropy source once.
43pub const trait ZkrSeedSource {
44    /// Poll the entropy source once, advancing its internal state as necessary
45    fn poll_seed(&mut self) -> ZkrSeedPoll;
46}
47
48#[instruction_execution]
49const impl<Reg> ExecutableInstructionOperands for ZkrInstruction<Reg> where Reg: [const] Register {}
50
51#[instruction_execution]
52const impl<Reg, ExtState, CustomError> ExecutableInstructionCsr<ExtState, CustomError>
53    for ZkrInstruction<Reg>
54where
55    Reg: [const] Register,
56    ExtState: [const] ZkrSeedSource,
57    CustomError: [const] Destruct,
58{
59    /// Reads of `seed` are pass-through: the raw stored value already reflects the outcome of the
60    /// most recent poll (see [`Self::prepare_csr_write()`]).
61    ///
62    /// Per specification, only genuine read-write accesses (not e.g. `csrrs`/`csrrc` with
63    /// `rs1 = x0`) are legal against `seed`; a pure read is rejected as an illegal access.
64    #[inline(always)]
65    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
66    fn prepare_csr_read(
67        _ext_state: &ExtState,
68        csr_index: u16,
69        will_write: bool,
70        raw_value: Reg::Type,
71        output_value: &mut Reg::Type,
72    ) -> Result<bool, CsrError<CustomError>> {
73        if csr_index == SEED_CSR_INDEX {
74            if will_write {
75                *output_value = raw_value;
76                Ok(true)
77            } else {
78                Err(CsrError::IllegalRead { csr_index })
79            }
80        } else {
81            Ok(false)
82        }
83    }
84
85    /// Writes to `seed` ignore the write value entirely (per specification) and instead poll the
86    /// entropy source once, storing the newly encoded [`ZkrSeedPoll`] to be observed by the next
87    /// read
88    #[inline(always)]
89    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
90    fn prepare_csr_write(
91        ext_state: &mut ExtState,
92        csr_index: u16,
93        write_value: Reg::Type,
94        output_value: &mut Reg::Type,
95    ) -> Result<bool, CsrError<CustomError>> {
96        // Necessary to avoid unused variable depending on instructions composition
97        let _: Reg::Type = write_value;
98
99        if csr_index == SEED_CSR_INDEX {
100            *output_value = zkr_helpers::encode_seed_poll::<Reg>(ext_state.poll_seed());
101            Ok(true)
102        } else {
103            Ok(false)
104        }
105    }
106}
107
108#[instruction_execution]
109const impl<Reg, Regs, ExtState, Memory, PC, InstructionHandler, CustomError>
110    ExecutableInstruction<Regs, ExtState, Memory, PC, InstructionHandler, CustomError>
111    for ZkrInstruction<Reg>
112where
113    Reg: Register,
114    ExtState: [const] ZkrSeedSource,
115{
116    #[inline(always)]
117    // TODO: #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
118    fn execute(
119        self,
120        Rs1Rs2OperandValues {
121            rs1_value,
122            rs2_value: _,
123        }: Rs1Rs2OperandValues<<Self::Reg as Register>::Type>,
124        _regs: &mut Regs,
125        ext_state: &mut ExtState,
126        _memory: &mut Memory,
127        _program_counter: &mut PC,
128        _system_instruction_handler: &mut InstructionHandler,
129    ) -> ExecutableInstructionResult<(), Self, CustomError> {
130        Ok(ControlFlow::Continue(Default::default()))
131    }
132}