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,
8    ExecutableInstructionResult, RegisterFile, Rs1Rs2OperandValues, Rs1Rs2Operands, VirtualMemory,
9};
10use ab_riscv_macros::instruction_execution;
11use ab_riscv_primitives::prelude::*;
12use core::ops::ControlFlow;
13
14#[instruction_execution]
15const impl<Reg> ExecutableInstructionOperands for Rv64ZacasInstruction<Reg> where
16    Reg: Register<Type = u64>
17{
18}
19
20#[instruction_execution]
21const impl<Reg, ExtState, CustomError> ExecutableInstructionCsr<ExtState, CustomError>
22    for Rv64ZacasInstruction<Reg>
23where
24    Reg: Register<Type = u64>,
25{
26}
27
28#[instruction_execution]
29const impl<Reg, Regs, ExtState, Memory, PC, InstructionHandler, CustomError>
30    ExecutableInstruction<Regs, ExtState, Memory, PC, InstructionHandler, CustomError>
31    for Rv64ZacasInstruction<Reg>
32where
33    Reg: [const] Register<Type = u64>,
34    Regs: [const] RegisterFile<Reg>,
35    Memory: [const] VirtualMemory,
36{
37    #[inline(always)]
38    #[cfg_attr(feature = "no-panic", no_panic_const::no_panic(const))]
39    fn execute(
40        self,
41        Rs1Rs2OperandValues {
42            rs1_value,
43            rs2_value,
44        }: Rs1Rs2OperandValues<<Self::Reg as Register>::Type>,
45        regs: &mut Regs,
46        _ext_state: &mut ExtState,
47        memory: &mut Memory,
48        _program_counter: &mut PC,
49        _system_instruction_handler: &mut InstructionHandler,
50    ) -> ExecutableInstructionResult<(), Self, CustomError> {
51        match self {
52            Self::AmocasW {
53                rd,
54                rs1: _,
55                rs2: _,
56                aq: _,
57                rl: _,
58            } => {
59                let addr = rs1_value;
60                // Ignore the upper bits of `rd` when comparing, per spec
61                let compare = regs.read(rd) as u32;
62                let old = memory.read::<i32>(addr)?;
63                if old.cast_unsigned() == compare {
64                    memory.write(addr, rs2_value as u32)?;
65                }
66                Ok(ControlFlow::Continue((rd, i64::from(old).cast_unsigned())))
67            }
68            Self::AmocasD {
69                rd,
70                rs1: _,
71                rs2: _,
72                aq: _,
73                rl: _,
74            } => {
75                let addr = rs1_value;
76                let compare = regs.read(rd);
77                let old = memory.read::<u64>(addr)?;
78                if old == compare {
79                    memory.write(addr, rs2_value)?;
80                }
81                Ok(ControlFlow::Continue((rd, old)))
82            }
83            Self::AmocasQ {
84                rd,
85                rs1: _,
86                rs2,
87                rd_hi,
88                rs2_hi,
89                aq: _,
90                rl: _,
91            } => {
92                let addr = rs1_value;
93                // Per spec, when the first register of a pair is `x0`, BOTH halves of that pair
94                // read as zero - not just the literal `x0` half. `compare_lo`/`rs2_value` are
95                // already 0 in that case since `x0` is hardwired, but `compare_hi`/`swap_hi`
96                // need an explicit override since `rd_hi`/`rs2_hi` are real registers.
97                let compare_lo = regs.read(rd);
98                let compare_hi = if rd == Reg::ZERO { 0 } else { regs.read(rd_hi) };
99                let swap_hi = if rs2 == Reg::ZERO {
100                    0
101                } else {
102                    regs.read(rs2_hi)
103                };
104                let old_lo = memory.read::<u64>(addr)?;
105                let old_hi = memory.read::<u64>(addr + 8)?;
106                if old_lo == compare_lo && old_hi == compare_hi {
107                    memory.write(addr, rs2_value)?;
108                    memory.write(addr + 8, swap_hi)?;
109                }
110                // Per spec, when `rd == x0` the whole register-pair write (both halves) is
111                // skipped, not just the low half (which is a no-op anyway since x0 is
112                // hardwired). Only `rd_hi` needs an explicit guard since it's a real register.
113                if rd != Reg::ZERO {
114                    regs.write(rd_hi, old_hi);
115                }
116                Ok(ControlFlow::Continue((rd, old_lo)))
117            }
118        }
119    }
120}