Skip to main content

ab_riscv_interpreter/rv64/
zacas.rs

1//! RV64 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 Rv64ZacasInstruction<Reg> where
17    Reg: Register<Type = u64>
18{
19}
20
21#[instruction_execution]
22const impl<Reg, Env> ExecutableInstructionCsr<Env> for Rv64ZacasInstruction<Reg> where
23    Reg: Register<Type = u64>
24{
25}
26
27#[instruction_execution]
28const impl<Reg, Regs, Env, Memory, PC> ExecutableInstruction<Regs, Env, Memory, PC>
29    for Rv64ZacasInstruction<Reg>
30where
31    Reg: [const] Register<Type = u64>,
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 = 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                // Ignore the upper bits of `rd` when comparing, per spec
66                let compare = regs.read(rd) as u32;
67                let old = memory.read::<i32>(addr)?;
68                if old.cast_unsigned() == compare {
69                    memory.write(addr, rs2_value as u32)?;
70                }
71                ExecutionResult::Continue {
72                    rd,
73                    value: i64::from(old).cast_unsigned(),
74                }
75            }
76            Self::AmocasD {
77                rd,
78                rs1: _,
79                rs2: _,
80                aq: _,
81                rl: _,
82            } => {
83                let addr = rs1_value;
84                // The 8-byte access must not cross a misaligned atomicity granule (4096 bytes)
85                // boundary
86                if addr / 4096 != (addr + 7) / 4096 {
87                    ::core::hint::cold_path();
88                    return ExecutionResult::Err(ExecutionError::MisalignedAtomic {
89                        address: PackedAddress::new(addr),
90                    });
91                }
92                let compare = regs.read(rd);
93                let old = memory.read::<u64>(addr)?;
94                if old == compare {
95                    memory.write(addr, rs2_value)?;
96                }
97                ExecutionResult::Continue { rd, value: old }
98            }
99            Self::AmocasQ {
100                rd,
101                rs1: _,
102                rs2,
103                rd_hi,
104                rs2_hi,
105                aq: _,
106                rl: _,
107            } => {
108                let addr = rs1_value;
109                // The 16-byte access must not cross a misaligned atomicity granule (4096 bytes)
110                // boundary
111                if addr / 4096 != (addr + 15) / 4096 {
112                    ::core::hint::cold_path();
113                    return ExecutionResult::Err(ExecutionError::MisalignedAtomic {
114                        address: PackedAddress::new(addr),
115                    });
116                }
117                // Per spec, when the first register of a pair is `x0`, BOTH halves of that pair
118                // read as zero - not just the literal `x0` half. `compare_lo`/`rs2_value` are
119                // already 0 in that case since `x0` is hardwired, but `compare_hi`/`swap_hi`
120                // need an explicit override since `rd_hi`/`rs2_hi` are real registers.
121                let compare_lo = regs.read(rd);
122                let compare_hi = if rd == Reg::ZERO { 0 } else { regs.read(rd_hi) };
123                let swap_hi = if rs2 == Reg::ZERO {
124                    0
125                } else {
126                    regs.read(rs2_hi)
127                };
128                let old_lo = memory.read::<u64>(addr)?;
129                let old_hi = memory.read::<u64>(addr + 8)?;
130                if old_lo == compare_lo && old_hi == compare_hi {
131                    memory.write(addr, rs2_value)?;
132                    memory.write(addr + 8, swap_hi)?;
133                }
134                // Per spec, when `rd == x0` the whole register-pair write (both halves) is
135                // skipped, not just the low half (which is a no-op anyway since x0 is
136                // hardwired). Only `rd_hi` needs an explicit guard since it's a real register.
137                if rd != Reg::ZERO {
138                    regs.write(rd_hi, old_hi);
139                }
140                ExecutionResult::Continue { rd, value: old_lo }
141            }
142        }
143    }
144}