Skip to main content

ab_riscv_interpreter/rv32/
zacas.rs

1//! RV32 Zacas extension
2
3#[cfg(test)]
4mod tests;
5
6use crate::rv32::a::amo_helpers;
7use crate::{
8    ExecutableInstruction, ExecutableInstructionCsr, ExecutableInstructionOperands, ExecutionError,
9    ExecutionResult, FetchInstructionResult, InstructionFetcher, OpaqueThreadedExecutionResult,
10    PackedAddress, RegisterFile, Rs1Rs2OperandValues, Rs1Rs2Operands,
11    ThreadedExecutableInstruction, ThreadedExecutionResult, VirtualMemory,
12};
13use ab_riscv_macros::instruction_execution;
14use ab_riscv_primitives::prelude::*;
15
16#[instruction_execution]
17const impl<Reg> ExecutableInstructionOperands for Rv32ZacasInstruction<Reg> where
18    Reg: Register<Type = u32>
19{
20}
21
22#[instruction_execution]
23const impl<Reg, Env> ExecutableInstructionCsr<Env> for Rv32ZacasInstruction<Reg> where
24    Reg: Register<Type = u32>
25{
26}
27
28#[instruction_execution]
29const impl<Reg, Regs, Env, Memory, PC> ExecutableInstruction<Regs, Env, Memory, PC>
30    for Rv32ZacasInstruction<Reg>
31where
32    Reg: [const] Register<Type = u32>,
33    Regs: [const] RegisterFile<Reg>,
34    Memory: [const] VirtualMemory,
35{
36    #[inline(always)]
37    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
38    fn execute(
39        self,
40        Rs1Rs2OperandValues {
41            rs1_value,
42            rs2_value,
43        }: Rs1Rs2OperandValues<<Self::Reg as Register>::Type>,
44        regs: &mut Regs,
45        _env: &mut Env,
46        memory: &mut Memory,
47        _program_counter: &mut PC,
48    ) -> ExecutionResult<Self::Reg> {
49        match self {
50            Self::AmocasW {
51                rd,
52                rs1: _,
53                rs2: _,
54                aq: _,
55                rl: _,
56            } => {
57                let addr = u64::from(rs1_value);
58                // The 4-byte access must not cross a misaligned atomicity granule (4096 bytes)
59                // boundary
60                if addr / 4096 != (addr + 3) / 4096 {
61                    ::core::hint::cold_path();
62                    return ExecutionResult::Err(ExecutionError::MisalignedAtomic {
63                        address: PackedAddress::new(addr),
64                    });
65                }
66                let compare = regs.read(rd);
67                let old = amo_helpers::amo_read::<u32, _, _>(memory, addr)?;
68                if old == compare {
69                    memory.write(addr, rs2_value)?;
70                }
71                ExecutionResult::Continue { rd, value: old }
72            }
73            Self::AmocasD {
74                rd,
75                rs1: _,
76                rs2,
77                rd_hi,
78                rs2_hi,
79                aq: _,
80                rl: _,
81            } => {
82                let addr = u64::from(rs1_value);
83                // The 8-byte access must not cross a misaligned atomicity granule (4096 bytes)
84                // boundary
85                if addr / 4096 != (addr + 7) / 4096 {
86                    ::core::hint::cold_path();
87                    return ExecutionResult::Err(ExecutionError::MisalignedAtomic {
88                        address: PackedAddress::new(addr),
89                    });
90                }
91                // Per spec, when the first register of a pair is `x0`, BOTH halves of that pair
92                // read as zero - not just the literal `x0` half. `compare_lo`/`rs2_value` are
93                // already 0 in that case since `x0` is hardwired, but `compare_hi`/`swap_hi`
94                // need an explicit override since `rd_hi`/`rs2_hi` are real registers.
95                let compare_lo = regs.read(rd);
96                let compare_hi = if rd == Reg::ZERO { 0 } else { regs.read(rd_hi) };
97                let swap_hi = if rs2 == Reg::ZERO {
98                    0
99                } else {
100                    regs.read(rs2_hi)
101                };
102                let old_lo = amo_helpers::amo_read::<u32, _, _>(memory, addr)?;
103                let old_hi = amo_helpers::amo_read::<u32, _, _>(memory, addr + 4)?;
104                if old_lo == compare_lo && old_hi == compare_hi {
105                    memory.write(addr, rs2_value)?;
106                    memory.write(addr + 4, swap_hi)?;
107                }
108                // Per spec, when `rd == x0` the whole register-pair write (both halves) is
109                // skipped, not just the low half (which is a no-op anyway since x0 is
110                // hardwired). Only `rd_hi` needs an explicit guard since it's a real register.
111                if rd != Reg::ZERO {
112                    regs.write(rd_hi, old_hi);
113                }
114                ExecutionResult::Continue { rd, value: old_lo }
115            }
116        }
117    }
118}