Skip to main content

ab_riscv_interpreter/rv64/
zacas.rs

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