#![expect(clippy::identity_op, reason = "Test readability")]
use crate::instructions::Instruction;
use crate::instructions::rv32::zce::zcmp::{Rv32ZcmpInstruction, ZcmpUrlist};
use crate::registers::general_purpose::{EReg, Reg};
const fn make_push_pop(op_sel: u16, urlist: u16, spimm: u16) -> u32 {
let inst: u16 =
(0b101 << 13) | (0b11 << 11) | (op_sel << 9) | (urlist << 4) | (spimm << 2) | 0b10;
u32::from(inst)
}
const fn make_mv_pair(which: u16, r1s: u16, r2s: u16) -> u32 {
let inst: u16 =
(0b101 << 13) | (0b01 << 11) | (which << 10) | (r1s << 7) | (0b11 << 5) | (r2s << 2) | 0b10;
u32::from(inst)
}
fn expected_stack_adj(urlist_raw: u8, spimm: u32) -> u32 {
ZcmpUrlist::<Reg<u32>>::try_from_raw(urlist_raw)
.unwrap()
.stack_adj_base()
+ spimm * 16
}
#[test]
fn test_cm_push_ra_only() {
let inst = make_push_pop(0b00, 4, 0);
let decoded = Rv32ZcmpInstruction::<Reg<u32>>::try_decode(inst).unwrap();
assert_eq!(
decoded,
Rv32ZcmpInstruction::CmPush {
urlist: ZcmpUrlist::try_from_raw(4).unwrap(),
stack_adj: 16,
}
);
}
#[test]
fn test_cm_push_ra_s0_s11() {
let inst = make_push_pop(0b00, 15, 3);
let decoded = Rv32ZcmpInstruction::<Reg<u32>>::try_decode(inst).unwrap();
assert_eq!(
decoded,
Rv32ZcmpInstruction::CmPush {
urlist: ZcmpUrlist::try_from_raw(15).unwrap(),
stack_adj: 112,
}
);
}
#[test]
fn test_cm_push_all_valid_urlists() {
for urlist in 4u16..=15 {
let inst = make_push_pop(0b00, urlist, 0);
let decoded = Rv32ZcmpInstruction::<Reg<u32>>::try_decode(inst).unwrap();
assert_eq!(
decoded,
Rv32ZcmpInstruction::CmPush {
urlist: ZcmpUrlist::try_from_raw(urlist as u8).unwrap(),
stack_adj: expected_stack_adj(urlist as u8, 0),
},
"urlist={urlist}"
);
}
}
#[test]
fn test_cm_push_reserved_urlist() {
for urlist in 0u16..4 {
let inst = make_push_pop(0b00, urlist, 0);
assert!(
Rv32ZcmpInstruction::<Reg<u32>>::try_decode(inst).is_none(),
"urlist={urlist} should be reserved"
);
}
}
#[test]
fn test_cm_pop_basic() {
let inst = make_push_pop(0b10, 5, 1);
let decoded = Rv32ZcmpInstruction::<Reg<u32>>::try_decode(inst).unwrap();
assert_eq!(
decoded,
Rv32ZcmpInstruction::CmPop {
urlist: ZcmpUrlist::try_from_raw(5).unwrap(),
stack_adj: 32,
}
);
}
#[test]
fn test_cm_pop_ra_s0_s9() {
let inst = make_push_pop(0b10, 14, 2);
let decoded = Rv32ZcmpInstruction::<Reg<u32>>::try_decode(inst).unwrap();
assert_eq!(
decoded,
Rv32ZcmpInstruction::CmPop {
urlist: ZcmpUrlist::try_from_raw(14).unwrap(),
stack_adj: 80,
}
);
}
#[test]
fn test_cm_popretz_basic() {
let inst = make_push_pop(0b01, 4, 0);
let decoded = Rv32ZcmpInstruction::<Reg<u32>>::try_decode(inst).unwrap();
assert_eq!(
decoded,
Rv32ZcmpInstruction::CmPopretz {
urlist: ZcmpUrlist::try_from_raw(4).unwrap(),
stack_adj: 16,
}
);
}
#[test]
fn test_cm_popret_basic() {
let inst = make_push_pop(0b11, 6, 0);
let decoded = Rv32ZcmpInstruction::<Reg<u32>>::try_decode(inst).unwrap();
assert_eq!(
decoded,
Rv32ZcmpInstruction::CmPopret {
urlist: ZcmpUrlist::try_from_raw(6).unwrap(),
stack_adj: 16,
}
);
}
#[test]
fn test_cm_popret_all_spimm_values() {
let expected_adjs = [32, 48, 64, 80];
for (spimm, &expected) in expected_adjs.iter().enumerate() {
let inst = make_push_pop(0b11, 8, spimm as u16);
let decoded = Rv32ZcmpInstruction::<Reg<u32>>::try_decode(inst).unwrap();
assert_eq!(
decoded,
Rv32ZcmpInstruction::CmPopret {
urlist: ZcmpUrlist::try_from_raw(8).unwrap(),
stack_adj: expected,
},
"spimm={spimm}"
);
}
}
#[test]
fn test_cm_mva01s_s0_s1() {
let inst = make_mv_pair(1, 0, 1);
let decoded = Rv32ZcmpInstruction::<Reg<u32>>::try_decode(inst).unwrap();
assert_eq!(
decoded,
Rv32ZcmpInstruction::CmMva01s {
r1s: Reg::S0,
r2s: Reg::S1,
}
);
}
#[test]
fn test_cm_mva01s_same_reg() {
let inst = make_mv_pair(1, 2, 2);
let decoded = Rv32ZcmpInstruction::<Reg<u32>>::try_decode(inst).unwrap();
assert_eq!(
decoded,
Rv32ZcmpInstruction::CmMva01s {
r1s: Reg::S2,
r2s: Reg::S2,
}
);
}
#[test]
fn test_cm_mva01s_all_s_regs() {
let expected_regs = [
Reg::S0,
Reg::S1,
Reg::S2,
Reg::S3,
Reg::S4,
Reg::S5,
Reg::S6,
Reg::S7,
];
for (field, &expected) in expected_regs.iter().enumerate() {
let inst = make_mv_pair(1, field as u16, 0);
let decoded = Rv32ZcmpInstruction::<Reg<u32>>::try_decode(inst).unwrap();
if let Rv32ZcmpInstruction::CmMva01s { r1s, .. } = decoded {
assert_eq!(r1s, expected, "field={field}");
} else {
panic!("wrong variant for field={field}");
}
}
}
#[test]
fn test_cm_mvsa01_distinct_regs() {
let inst = make_mv_pair(0, 0, 2);
let decoded = Rv32ZcmpInstruction::<Reg<u32>>::try_decode(inst).unwrap();
assert_eq!(
decoded,
Rv32ZcmpInstruction::CmMvsa01 {
r1s: Reg::S0,
r2s: Reg::S2,
}
);
}
#[test]
fn test_cm_mvsa01_reserved_same_reg() {
let inst = make_mv_pair(0, 3, 3);
assert!(Rv32ZcmpInstruction::<Reg<u32>>::try_decode(inst).is_none());
}
#[test]
fn test_non_zcmp_q00_returns_none() {
let inst = (0b101 << 13) | 0b00;
assert!(Rv32ZcmpInstruction::<Reg<u32>>::try_decode(inst).is_none());
}
#[test]
fn test_non_zcmp_q01_returns_none() {
let inst = (0b101 << 13) | 0b01;
assert!(Rv32ZcmpInstruction::<Reg<u32>>::try_decode(inst).is_none());
}
#[test]
fn test_non_zcmp_funct3_mismatch() {
let inst = (0b100 << 13) | (0b11 << 11) | (0b00 << 9) | (4 << 4) | 0b10;
assert!(Rv32ZcmpInstruction::<Reg<u32>>::try_decode(inst).is_none());
}
#[test]
fn test_reserved_funct2_00_returns_none() {
let inst = (0b101 << 13) | (0b00 << 11) | 0b10;
assert!(Rv32ZcmpInstruction::<Reg<u32>>::try_decode(inst).is_none());
}
#[test]
fn test_reserved_funct2_10_returns_none() {
let inst = (0b101 << 13) | (0b10 << 11) | 0b10;
assert!(Rv32ZcmpInstruction::<Reg<u32>>::try_decode(inst).is_none());
}
#[test]
fn test_stack_adj_base_rv32() {
let cases = &[
(4, 16),
(5, 16),
(6, 16),
(7, 16),
(8, 32),
(9, 32),
(10, 32),
(11, 32),
(12, 48),
(13, 48),
(14, 48),
(15, 64),
];
for &(raw, expected) in cases {
let urlist = ZcmpUrlist::<Reg<u32>>::try_from_raw(raw).unwrap();
assert_eq!(urlist.stack_adj_base(), expected, "urlist={raw}");
}
}
#[test]
fn test_reg_list_ra_only() {
let urlist = ZcmpUrlist::<Reg<u32>>::try_from_raw(4).unwrap();
assert!(urlist.reg_list().eq([Reg::Ra]));
}
#[test]
fn test_reg_list_ra_s0_s11() {
let urlist = ZcmpUrlist::<Reg<u32>>::try_from_raw(15).unwrap();
assert!(urlist.reg_list().eq([
Reg::Ra,
Reg::S0,
Reg::S1,
Reg::S2,
Reg::S3,
Reg::S4,
Reg::S5,
Reg::S6,
Reg::S7,
Reg::S8,
Reg::S9,
Reg::S11,
]));
}
#[test]
fn test_reg_list_count_matches_urlist() {
let expected_counts = [1usize, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
for (i, &expected) in expected_counts.iter().enumerate() {
let raw = (i + 4) as u8;
let urlist = ZcmpUrlist::<Reg<u32>>::try_from_raw(raw).unwrap();
let count = urlist.reg_list().count();
assert_eq!(count, expected, "urlist={raw}");
}
}
#[test]
fn test_rve_urlist_max_is_ra_s0_s1() {
assert!(ZcmpUrlist::<EReg<u32>>::try_from_raw(6).is_some());
assert!(ZcmpUrlist::<EReg<u32>>::try_from_raw(7).is_none());
}
#[test]
fn test_rve_push_reserved_urlist() {
let inst = make_push_pop(0b00, 7, 0);
assert!(Rv32ZcmpInstruction::<EReg<u32>>::try_decode(inst).is_none());
}
#[test]
fn test_rve_mva01s_accessible_regs() {
let inst = make_mv_pair(1, 0, 1);
assert!(Rv32ZcmpInstruction::<EReg<u32>>::try_decode(inst).is_some());
}
#[test]
fn test_rve_mva01s_inaccessible_reg_returns_none() {
let inst = make_mv_pair(1, 2, 0);
assert!(Rv32ZcmpInstruction::<EReg<u32>>::try_decode(inst).is_none());
}
#[test]
fn test_rve_mvsa01_accessible_regs() {
let inst = make_mv_pair(0, 0, 1);
assert!(Rv32ZcmpInstruction::<EReg<u32>>::try_decode(inst).is_some());
}
#[test]
fn test_rve_mvsa01_inaccessible_reg_returns_none() {
let inst = make_mv_pair(0, 0, 2);
assert!(Rv32ZcmpInstruction::<EReg<u32>>::try_decode(inst).is_none());
}