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    ExecutionError, ExecutionResult, FetchInstructionResult, InstructionFetcher,
11    OpaqueThreadedExecutionResult, RegisterFile, Rs1Rs2OperandValues, Rs1Rs2Operands,
12    ThreadedExecutableInstruction, ThreadedExecutionResult,
13};
14use ab_riscv_macros::instruction_execution;
15use ab_riscv_primitives::prelude::*;
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// Convenience for threaded execution
49const impl<T> ZkrSeedSource for &mut T
50where
51    T: [const] ZkrSeedSource,
52{
53    #[inline(always)]
54    fn poll_seed(&mut self) -> ZkrSeedPoll {
55        T::poll_seed(self)
56    }
57}
58
59#[instruction_execution]
60const impl<Reg, Hart> ExecutableInstructionOperands for ZkrInstruction<Hart>
61where
62    Reg: [const] Register,
63    Hart: HartConfig<Reg = Reg>,
64{
65}
66
67#[instruction_execution]
68const impl<Reg, Hart, Env> ExecutableInstructionCsr<Env> for ZkrInstruction<Hart>
69where
70    Reg: [const] Register,
71    Hart: HartConfig<Reg = Reg>,
72    Env: [const] ZkrSeedSource,
73{
74    /// Reads of `seed` are pass-through: the raw stored value already reflects the outcome of the
75    /// most recent poll (see [`Self::prepare_csr_write()`]).
76    ///
77    /// Per specification, only genuine read-write accesses (not e.g. `csrrs`/`csrrc` with
78    /// `rs1 = x0`) are legal against `seed`; a pure read is rejected as an illegal access.
79    #[inline(always)]
80    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
81    fn prepare_csr_read(
82        _env: &Env,
83        csr_index: u16,
84        will_write: bool,
85        raw_value: Reg::Type,
86        output_value: &mut Reg::Type,
87    ) -> Result<bool, CsrError> {
88        if csr_index == SEED_CSR_INDEX {
89            if will_write {
90                *output_value = raw_value;
91                Ok(true)
92            } else {
93                Err(CsrError::IllegalRead { csr_index })
94            }
95        } else {
96            Ok(false)
97        }
98    }
99
100    /// Writes to `seed` ignore the write value entirely (per specification) and instead poll the
101    /// entropy source once, storing the newly encoded [`ZkrSeedPoll`] to be observed by the next
102    /// read
103    #[inline(always)]
104    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
105    fn prepare_csr_write(
106        env: &mut Env,
107        csr_index: u16,
108        write_value: Reg::Type,
109        output_value: &mut Reg::Type,
110    ) -> Result<bool, CsrError> {
111        // Necessary to avoid unused variable depending on instructions composition
112        let _: Reg::Type = write_value;
113
114        if csr_index == SEED_CSR_INDEX {
115            *output_value = zkr_helpers::encode_seed_poll::<Reg>(env.poll_seed());
116            Ok(true)
117        } else {
118            Ok(false)
119        }
120    }
121}
122
123#[instruction_execution]
124const impl<Reg, Hart, Regs, Env, Memory, PC> ExecutableInstruction<Regs, Env, Memory, PC>
125    for ZkrInstruction<Hart>
126where
127    Reg: Register,
128    Hart: HartConfig<Reg = Reg>,
129    Env: [const] ZkrSeedSource,
130{
131    #[inline(always)]
132    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
133    fn execute(
134        self,
135        Rs1Rs2OperandValues {
136            rs1_value,
137            rs2_value: _,
138        }: Rs1Rs2OperandValues<Reg::Type>,
139        _regs: &mut Regs,
140        env: &mut Env,
141        _memory: &mut Memory,
142        _program_counter: &PC,
143    ) -> ExecutionResult<Reg> {
144        ExecutionResult::ContinueNoWrite
145    }
146}