Skip to main content

ab_riscv_interpreter/rv32/
zacas.rs

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