1093 lines
32 KiB
Rust
1093 lines
32 KiB
Rust
// SPDX-License-Identifier: LGPL-3.0-or-later
|
|
// See Notices.txt for copyright information
|
|
|
|
use cpu::{
|
|
decoder::simple_power_isa::decode_one_insn,
|
|
instruction::{
|
|
AddSubMOp, CompareMOp, CompareMode, LogicalMOp, MOp, MOpDestReg, MOpRegNum, MoveRegMOp,
|
|
OutputIntegerMode,
|
|
},
|
|
util::array_vec::ArrayVec,
|
|
};
|
|
use fayalite::{prelude::*, sim::vcd::VcdWriterDecls, util::RcWriter};
|
|
use std::{
|
|
fmt::{self, Write as _},
|
|
io::Write,
|
|
process::Command,
|
|
};
|
|
|
|
struct TestCase {
|
|
mnemonic: &'static str,
|
|
first_input: u32,
|
|
second_input: Option<u32>,
|
|
output: SimValue<ArrayVec<MOp, ConstUsize<2>>>,
|
|
loc: &'static std::panic::Location<'static>,
|
|
}
|
|
|
|
impl fmt::Debug for TestCase {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
let Self {
|
|
mnemonic,
|
|
first_input,
|
|
second_input,
|
|
output,
|
|
loc,
|
|
} = self;
|
|
let mut debug_struct = f.debug_struct("TestCase");
|
|
debug_struct
|
|
.field("mnemonic", mnemonic)
|
|
.field("first_input", &format_args!("0x{first_input:08x}"));
|
|
if let Some(second_input) = second_input {
|
|
debug_struct.field("second_input", &format_args!("0x{second_input:08x}"));
|
|
} else {
|
|
debug_struct.field("second_input", &format_args!("None"));
|
|
}
|
|
debug_struct
|
|
.field("output", &ArrayVec::elements_sim_ref(output))
|
|
.field("loc", &format_args!("{loc}"))
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
fn test_cases() -> Vec<TestCase> {
|
|
let mut retval = Vec::new();
|
|
#[track_caller]
|
|
fn insn_empty(mnemonic: &'static str, first_input: u32, second_input: Option<u32>) -> TestCase {
|
|
let zero_mop = UInt::new_dyn(MOp.canonical().bit_width())
|
|
.zero()
|
|
.cast_bits_to(MOp);
|
|
TestCase {
|
|
mnemonic,
|
|
first_input,
|
|
second_input,
|
|
output: ArrayVec::new_sim(ArrayVec[MOp][ConstUsize], &zero_mop),
|
|
loc: std::panic::Location::caller(),
|
|
}
|
|
}
|
|
#[track_caller]
|
|
fn insn_single(
|
|
mnemonic: &'static str,
|
|
first_input: u32,
|
|
second_input: Option<u32>,
|
|
output: impl ToSimValue<Type = MOp>,
|
|
) -> TestCase {
|
|
let zero_mop = UInt::new_dyn(MOp.canonical().bit_width())
|
|
.zero()
|
|
.cast_bits_to(MOp);
|
|
let mut single_storage = ArrayVec::new_sim(ArrayVec[MOp][ConstUsize], &zero_mop);
|
|
ArrayVec::try_push_sim(&mut single_storage, zero_mop).expect("known to have space");
|
|
ArrayVec::elements_sim_mut(&mut single_storage)[0] = output.to_sim_value();
|
|
TestCase {
|
|
mnemonic,
|
|
first_input,
|
|
second_input,
|
|
output: single_storage,
|
|
loc: std::panic::Location::caller(),
|
|
}
|
|
}
|
|
retval.push(insn_single(
|
|
"addi 3, 4, 0x1234",
|
|
0x38641234,
|
|
None,
|
|
AddSubMOp::add_sub_i(
|
|
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num(3)], &[]),
|
|
[
|
|
MOpRegNum::power_isa_gpr_reg_imm(4).value,
|
|
MOpRegNum::const_zero().value,
|
|
],
|
|
0x1234.cast_to_static::<SInt<_>>(),
|
|
OutputIntegerMode.Full64(),
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
),
|
|
));
|
|
retval.push(insn_single(
|
|
"paddi 3, 4, 0x123456789, 0",
|
|
0x06012345,
|
|
Some(0x38646789),
|
|
AddSubMOp::add_sub_i(
|
|
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num(3)], &[]),
|
|
[
|
|
MOpRegNum::power_isa_gpr_reg_imm(4).value,
|
|
MOpRegNum::const_zero().value,
|
|
],
|
|
0x123456789i64.cast_to_static::<SInt<_>>(),
|
|
OutputIntegerMode.Full64(),
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
),
|
|
));
|
|
retval.push(insn_single(
|
|
"paddi 3, 0, 0x123456789, 1",
|
|
0x06112345,
|
|
Some(0x38606789),
|
|
AddSubMOp::add_sub_i(
|
|
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num(3)], &[]),
|
|
[MOpRegNum::const_zero().value, MOpRegNum::const_zero().value],
|
|
0x123456789i64.cast_to_static::<SInt<_>>(),
|
|
OutputIntegerMode.Full64(),
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
),
|
|
));
|
|
retval.push(insn_single(
|
|
"addis 3, 4, 0x1234",
|
|
0x3C641234,
|
|
None,
|
|
AddSubMOp::add_sub_i(
|
|
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num(3)], &[]),
|
|
[
|
|
MOpRegNum::power_isa_gpr_reg_imm(4).value,
|
|
MOpRegNum::const_zero().value,
|
|
],
|
|
0x12340000.cast_to_static::<SInt<_>>(),
|
|
OutputIntegerMode.Full64(),
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
),
|
|
));
|
|
retval.push(insn_single(
|
|
"addpcis 3, 0x1234",
|
|
0x4c7a1204,
|
|
None,
|
|
AddSubMOp::add_sub_i(
|
|
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num(3)], &[]),
|
|
[MOpRegNum::const_zero().value; _],
|
|
0x12340004.cast_to_static::<SInt<_>>(),
|
|
OutputIntegerMode.Full64(),
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
),
|
|
));
|
|
retval.push(insn_single(
|
|
"add. 3, 4, 5",
|
|
0x7c642a15,
|
|
None,
|
|
AddSubMOp::add_sub(
|
|
MOpDestReg::new_sim(
|
|
&[MOpRegNum::power_isa_gpr_reg_num(3)],
|
|
&[MOpRegNum::POWER_ISA_CR_0_REG_NUM],
|
|
),
|
|
[
|
|
MOpRegNum::power_isa_gpr_reg_imm(4).value,
|
|
MOpRegNum::power_isa_gpr_reg_imm(5).value,
|
|
MOpRegNum::const_zero().value,
|
|
],
|
|
0.cast_to_static::<SInt<_>>(),
|
|
OutputIntegerMode.Full64(),
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
),
|
|
));
|
|
retval.push(insn_single(
|
|
"addic. 3, 4, 0x1234",
|
|
0x34641234,
|
|
None,
|
|
AddSubMOp::add_sub_i(
|
|
MOpDestReg::new_sim(
|
|
&[
|
|
MOpRegNum::power_isa_gpr_reg_num(3),
|
|
MOpRegNum::POWER_ISA_XER_CA_CA32_REG_NUM,
|
|
],
|
|
&[MOpRegNum::POWER_ISA_CR_0_REG_NUM],
|
|
),
|
|
[
|
|
MOpRegNum::power_isa_gpr_reg_imm(4).value,
|
|
MOpRegNum::const_zero().value,
|
|
],
|
|
0x1234.cast_to_static::<SInt<_>>(),
|
|
OutputIntegerMode.Full64(),
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
),
|
|
));
|
|
retval.push(insn_single(
|
|
"subf. 3, 4, 5",
|
|
0x7c642851,
|
|
None,
|
|
AddSubMOp::add_sub(
|
|
MOpDestReg::new_sim(
|
|
&[MOpRegNum::power_isa_gpr_reg_num(3)],
|
|
&[MOpRegNum::POWER_ISA_CR_0_REG_NUM],
|
|
),
|
|
[
|
|
MOpRegNum::power_isa_gpr_reg_imm(4).value,
|
|
MOpRegNum::power_isa_gpr_reg_imm(5).value,
|
|
MOpRegNum::const_zero().value,
|
|
],
|
|
0.cast_to_static::<SInt<_>>(),
|
|
OutputIntegerMode.Full64(),
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
),
|
|
));
|
|
retval.push(insn_single(
|
|
"subfic 3, 4, 0x1234",
|
|
0x20641234,
|
|
None,
|
|
AddSubMOp::add_sub_i(
|
|
MOpDestReg::new_sim(
|
|
&[
|
|
MOpRegNum::power_isa_gpr_reg_num(3),
|
|
MOpRegNum::POWER_ISA_XER_CA_CA32_REG_NUM,
|
|
],
|
|
&[],
|
|
),
|
|
[
|
|
MOpRegNum::power_isa_gpr_reg_imm(4).value,
|
|
MOpRegNum::const_zero().value,
|
|
],
|
|
0x1234.cast_to_static::<SInt<_>>(),
|
|
OutputIntegerMode.Full64(),
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
),
|
|
));
|
|
retval.push(insn_single(
|
|
"addc. 3, 4, 5",
|
|
0x7c642815,
|
|
None,
|
|
AddSubMOp::add_sub(
|
|
MOpDestReg::new_sim(
|
|
&[
|
|
MOpRegNum::power_isa_gpr_reg_num(3),
|
|
MOpRegNum::POWER_ISA_XER_CA_CA32_REG_NUM,
|
|
],
|
|
&[MOpRegNum::POWER_ISA_CR_0_REG_NUM],
|
|
),
|
|
[
|
|
MOpRegNum::power_isa_gpr_reg_imm(4).value,
|
|
MOpRegNum::power_isa_gpr_reg_imm(5).value,
|
|
MOpRegNum::const_zero().value,
|
|
],
|
|
0.cast_to_static::<SInt<_>>(),
|
|
OutputIntegerMode.Full64(),
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
),
|
|
));
|
|
retval.push(insn_single(
|
|
"subfc. 3, 4, 5",
|
|
0x7c642811,
|
|
None,
|
|
AddSubMOp::add_sub(
|
|
MOpDestReg::new_sim(
|
|
&[
|
|
MOpRegNum::power_isa_gpr_reg_num(3),
|
|
MOpRegNum::POWER_ISA_XER_CA_CA32_REG_NUM,
|
|
],
|
|
&[MOpRegNum::POWER_ISA_CR_0_REG_NUM],
|
|
),
|
|
[
|
|
MOpRegNum::power_isa_gpr_reg_imm(4).value,
|
|
MOpRegNum::power_isa_gpr_reg_imm(5).value,
|
|
MOpRegNum::const_zero().value,
|
|
],
|
|
0.cast_to_static::<SInt<_>>(),
|
|
OutputIntegerMode.Full64(),
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
),
|
|
));
|
|
retval.push(insn_single(
|
|
"adde. 3, 4, 5",
|
|
0x7c642915,
|
|
None,
|
|
AddSubMOp::add_sub(
|
|
MOpDestReg::new_sim(
|
|
&[
|
|
MOpRegNum::power_isa_gpr_reg_num(3),
|
|
MOpRegNum::POWER_ISA_XER_CA_CA32_REG_NUM,
|
|
],
|
|
&[MOpRegNum::POWER_ISA_CR_0_REG_NUM],
|
|
),
|
|
[
|
|
MOpRegNum::power_isa_gpr_reg_imm(4).value,
|
|
MOpRegNum::power_isa_xer_ca_ca32_reg().value,
|
|
MOpRegNum::power_isa_gpr_reg_imm(5).value,
|
|
],
|
|
0.cast_to_static::<SInt<_>>(),
|
|
OutputIntegerMode.Full64(),
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
),
|
|
));
|
|
retval.push(insn_single(
|
|
"subfe. 3, 4, 5",
|
|
0x7c642911,
|
|
None,
|
|
AddSubMOp::add_sub(
|
|
MOpDestReg::new_sim(
|
|
&[
|
|
MOpRegNum::power_isa_gpr_reg_num(3),
|
|
MOpRegNum::POWER_ISA_XER_CA_CA32_REG_NUM,
|
|
],
|
|
&[MOpRegNum::POWER_ISA_CR_0_REG_NUM],
|
|
),
|
|
[
|
|
MOpRegNum::power_isa_gpr_reg_imm(4).value,
|
|
MOpRegNum::power_isa_xer_ca_ca32_reg().value,
|
|
MOpRegNum::power_isa_gpr_reg_imm(5).value,
|
|
],
|
|
0.cast_to_static::<SInt<_>>(),
|
|
OutputIntegerMode.Full64(),
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
),
|
|
));
|
|
retval.push(insn_single(
|
|
"addme. 3, 4",
|
|
0x7c6401d5,
|
|
None,
|
|
AddSubMOp::add_sub(
|
|
MOpDestReg::new_sim(
|
|
&[
|
|
MOpRegNum::power_isa_gpr_reg_num(3),
|
|
MOpRegNum::POWER_ISA_XER_CA_CA32_REG_NUM,
|
|
],
|
|
&[MOpRegNum::POWER_ISA_CR_0_REG_NUM],
|
|
),
|
|
[
|
|
MOpRegNum::power_isa_gpr_reg_imm(4).value,
|
|
MOpRegNum::power_isa_xer_ca_ca32_reg().value,
|
|
MOpRegNum::const_zero().value,
|
|
],
|
|
(-1i8).cast_to_static::<SInt<_>>(),
|
|
OutputIntegerMode.Full64(),
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
),
|
|
));
|
|
retval.push(insn_single(
|
|
"subfme. 3, 4",
|
|
0x7c6401d1,
|
|
None,
|
|
AddSubMOp::add_sub(
|
|
MOpDestReg::new_sim(
|
|
&[
|
|
MOpRegNum::power_isa_gpr_reg_num(3),
|
|
MOpRegNum::POWER_ISA_XER_CA_CA32_REG_NUM,
|
|
],
|
|
&[MOpRegNum::POWER_ISA_CR_0_REG_NUM],
|
|
),
|
|
[
|
|
MOpRegNum::power_isa_gpr_reg_imm(4).value,
|
|
MOpRegNum::power_isa_xer_ca_ca32_reg().value,
|
|
MOpRegNum::const_zero().value,
|
|
],
|
|
(-1i8).cast_to_static::<SInt<_>>(),
|
|
OutputIntegerMode.Full64(),
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
),
|
|
));
|
|
retval.push(insn_single(
|
|
"addze. 3, 4",
|
|
0x7c640195,
|
|
None,
|
|
AddSubMOp::add_sub(
|
|
MOpDestReg::new_sim(
|
|
&[
|
|
MOpRegNum::power_isa_gpr_reg_num(3),
|
|
MOpRegNum::POWER_ISA_XER_CA_CA32_REG_NUM,
|
|
],
|
|
&[MOpRegNum::POWER_ISA_CR_0_REG_NUM],
|
|
),
|
|
[
|
|
MOpRegNum::power_isa_gpr_reg_imm(4).value,
|
|
MOpRegNum::power_isa_xer_ca_ca32_reg().value,
|
|
MOpRegNum::const_zero().value,
|
|
],
|
|
0.cast_to_static::<SInt<_>>(),
|
|
OutputIntegerMode.Full64(),
|
|
false,
|
|
true,
|
|
false,
|
|
false,
|
|
),
|
|
));
|
|
retval.push(insn_single(
|
|
"subfze. 3, 4",
|
|
0x7c640191,
|
|
None,
|
|
AddSubMOp::add_sub(
|
|
MOpDestReg::new_sim(
|
|
&[
|
|
MOpRegNum::power_isa_gpr_reg_num(3),
|
|
MOpRegNum::POWER_ISA_XER_CA_CA32_REG_NUM,
|
|
],
|
|
&[MOpRegNum::POWER_ISA_CR_0_REG_NUM],
|
|
),
|
|
[
|
|
MOpRegNum::power_isa_gpr_reg_imm(4).value,
|
|
MOpRegNum::power_isa_xer_ca_ca32_reg().value,
|
|
MOpRegNum::const_zero().value,
|
|
],
|
|
0.cast_to_static::<SInt<_>>(),
|
|
OutputIntegerMode.Full64(),
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
),
|
|
));
|
|
retval.push(insn_single(
|
|
"neg. 3, 4",
|
|
0x7c6400d1,
|
|
None,
|
|
AddSubMOp::add_sub(
|
|
MOpDestReg::new_sim(
|
|
&[MOpRegNum::power_isa_gpr_reg_num(3)],
|
|
&[MOpRegNum::POWER_ISA_CR_0_REG_NUM],
|
|
),
|
|
[
|
|
MOpRegNum::power_isa_gpr_reg_imm(4).value,
|
|
MOpRegNum::const_zero().value,
|
|
MOpRegNum::const_zero().value,
|
|
],
|
|
0.cast_to_static::<SInt<_>>(),
|
|
OutputIntegerMode.Full64(),
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
),
|
|
));
|
|
retval.push(insn_single(
|
|
"cmpi 3, 0, 4, 0x1234",
|
|
0x2d841234,
|
|
None,
|
|
CompareMOp::compare_i(
|
|
MOpDestReg::new_sim(&[MOpRegNum::power_isa_cr_reg_num(3)], &[]),
|
|
[MOpRegNum::power_isa_gpr_reg_imm(4).value],
|
|
0x1234.cast_to_static::<SInt<_>>(),
|
|
OutputIntegerMode.Full64(),
|
|
CompareMode.S32(),
|
|
),
|
|
));
|
|
retval.push(insn_single(
|
|
"cmpi 3, 1, 4, -0x7655",
|
|
0x2da489ab,
|
|
None,
|
|
CompareMOp::compare_i(
|
|
MOpDestReg::new_sim(&[MOpRegNum::power_isa_cr_reg_num(3)], &[]),
|
|
[MOpRegNum::power_isa_gpr_reg_imm(4).value],
|
|
(0x89abu16 as i16).cast_to_static::<SInt<_>>(),
|
|
OutputIntegerMode.Full64(),
|
|
CompareMode.S64(),
|
|
),
|
|
));
|
|
retval.push(insn_single(
|
|
"cmp 3, 0, 4, 5",
|
|
0x7d842800,
|
|
None,
|
|
CompareMOp::compare(
|
|
MOpDestReg::new_sim(&[MOpRegNum::power_isa_cr_reg_num(3)], &[]),
|
|
[
|
|
MOpRegNum::power_isa_gpr_reg_imm(4).value,
|
|
MOpRegNum::power_isa_gpr_reg_imm(5).value,
|
|
],
|
|
0.cast_to_static::<SInt<_>>(),
|
|
OutputIntegerMode.Full64(),
|
|
CompareMode.S32(),
|
|
),
|
|
));
|
|
retval.push(insn_single(
|
|
"cmp 3, 1, 4, 5",
|
|
0x7da42800,
|
|
None,
|
|
CompareMOp::compare(
|
|
MOpDestReg::new_sim(&[MOpRegNum::power_isa_cr_reg_num(3)], &[]),
|
|
[
|
|
MOpRegNum::power_isa_gpr_reg_imm(4).value,
|
|
MOpRegNum::power_isa_gpr_reg_imm(5).value,
|
|
],
|
|
0.cast_to_static::<SInt<_>>(),
|
|
OutputIntegerMode.Full64(),
|
|
CompareMode.S64(),
|
|
),
|
|
));
|
|
retval.push(insn_single(
|
|
"cmpli 3, 0, 4, 0x1234",
|
|
0x29841234,
|
|
None,
|
|
CompareMOp::compare_i(
|
|
MOpDestReg::new_sim(&[MOpRegNum::power_isa_cr_reg_num(3)], &[]),
|
|
[MOpRegNum::power_isa_gpr_reg_imm(4).value],
|
|
0x1234.cast_to_static::<SInt<_>>(),
|
|
OutputIntegerMode.Full64(),
|
|
CompareMode.U32(),
|
|
),
|
|
));
|
|
retval.push(insn_single(
|
|
"cmpli 3, 1, 4, 0x89ab",
|
|
0x29a489ab,
|
|
None,
|
|
CompareMOp::compare_i(
|
|
MOpDestReg::new_sim(&[MOpRegNum::power_isa_cr_reg_num(3)], &[]),
|
|
[MOpRegNum::power_isa_gpr_reg_imm(4).value],
|
|
0x89ab.cast_to_static::<SInt<_>>(),
|
|
OutputIntegerMode.Full64(),
|
|
CompareMode.U64(),
|
|
),
|
|
));
|
|
retval.push(insn_single(
|
|
"cmpl 3, 0, 4, 5",
|
|
0x7d842840,
|
|
None,
|
|
CompareMOp::compare(
|
|
MOpDestReg::new_sim(&[MOpRegNum::power_isa_cr_reg_num(3)], &[]),
|
|
[
|
|
MOpRegNum::power_isa_gpr_reg_imm(4).value,
|
|
MOpRegNum::power_isa_gpr_reg_imm(5).value,
|
|
],
|
|
0.cast_to_static::<SInt<_>>(),
|
|
OutputIntegerMode.Full64(),
|
|
CompareMode.U32(),
|
|
),
|
|
));
|
|
retval.push(insn_single(
|
|
"cmpl 3, 1, 4, 5",
|
|
0x7da42840,
|
|
None,
|
|
CompareMOp::compare(
|
|
MOpDestReg::new_sim(&[MOpRegNum::power_isa_cr_reg_num(3)], &[]),
|
|
[
|
|
MOpRegNum::power_isa_gpr_reg_imm(4).value,
|
|
MOpRegNum::power_isa_gpr_reg_imm(5).value,
|
|
],
|
|
0.cast_to_static::<SInt<_>>(),
|
|
OutputIntegerMode.Full64(),
|
|
CompareMode.U64(),
|
|
),
|
|
));
|
|
retval.push(insn_single(
|
|
"cmprb 3, 0, 4, 5",
|
|
0x7d842980,
|
|
None,
|
|
CompareMOp::compare(
|
|
MOpDestReg::new_sim(&[MOpRegNum::power_isa_cr_reg_num(3)], &[]),
|
|
[
|
|
MOpRegNum::power_isa_gpr_reg_imm(4).value,
|
|
MOpRegNum::power_isa_gpr_reg_imm(5).value,
|
|
],
|
|
0.cast_to_static::<SInt<_>>(),
|
|
OutputIntegerMode.Full64(),
|
|
CompareMode.CmpRBOne(),
|
|
),
|
|
));
|
|
retval.push(insn_single(
|
|
"cmprb 3, 1, 4, 5",
|
|
0x7da42980,
|
|
None,
|
|
CompareMOp::compare(
|
|
MOpDestReg::new_sim(&[MOpRegNum::power_isa_cr_reg_num(3)], &[]),
|
|
[
|
|
MOpRegNum::power_isa_gpr_reg_imm(4).value,
|
|
MOpRegNum::power_isa_gpr_reg_imm(5).value,
|
|
],
|
|
0.cast_to_static::<SInt<_>>(),
|
|
OutputIntegerMode.Full64(),
|
|
CompareMode.CmpRBTwo(),
|
|
),
|
|
));
|
|
retval.push(insn_single(
|
|
"cmpeqb 3, 4, 5",
|
|
0x7d8429c0,
|
|
None,
|
|
CompareMOp::compare(
|
|
MOpDestReg::new_sim(&[MOpRegNum::power_isa_cr_reg_num(3)], &[]),
|
|
[
|
|
MOpRegNum::power_isa_gpr_reg_imm(4).value,
|
|
MOpRegNum::power_isa_gpr_reg_imm(5).value,
|
|
],
|
|
0.cast_to_static::<SInt<_>>(),
|
|
OutputIntegerMode.Full64(),
|
|
CompareMode.CmpEqB(),
|
|
),
|
|
));
|
|
macro_rules! insn_logic_i {
|
|
(
|
|
$mnemonic:literal $dest:literal, $src:literal, $imm:literal;
|
|
$encoding:literal;
|
|
|[$a:ident, $b:ident]| $lut_fn:expr;
|
|
) => {
|
|
retval.push(insn_single(
|
|
concat!(
|
|
$mnemonic,
|
|
" ",
|
|
stringify!($dest),
|
|
", ",
|
|
stringify!($src),
|
|
", ",
|
|
stringify!($imm)
|
|
),
|
|
$encoding,
|
|
None,
|
|
LogicalMOp::logical_i(
|
|
MOpDestReg::new_sim(
|
|
&[MOpRegNum::power_isa_gpr_reg_num($dest)],
|
|
if $mnemonic.contains('.') {
|
|
&[MOpRegNum::POWER_ISA_CR_0_REG_NUM]
|
|
} else {
|
|
&[]
|
|
},
|
|
),
|
|
[MOpRegNum::power_isa_gpr_reg_imm($src).value],
|
|
(($imm as u32) << if $mnemonic.contains('s') { 16 } else { 0 })
|
|
.cast_to_static::<SInt<_>>(),
|
|
OutputIntegerMode.Full64(),
|
|
LogicalMOp::lut_from_fn(|[$a, $b]| $lut_fn),
|
|
),
|
|
));
|
|
};
|
|
}
|
|
insn_logic_i! {
|
|
"andi." 3, 4, 0x89ab;
|
|
0x708389ab;
|
|
|[a, b]| a & b;
|
|
}
|
|
insn_logic_i! {
|
|
"andis." 3, 4, 0x89ab;
|
|
0x748389ab;
|
|
|[a, b]| a & b;
|
|
}
|
|
insn_logic_i! {
|
|
"ori" 3, 4, 0x89ab;
|
|
0x608389ab;
|
|
|[a, b]| a | b;
|
|
}
|
|
// ensure nop decodes to zero instructions
|
|
retval.push(insn_empty("ori 0, 0, 0", 0x60000000, None));
|
|
insn_logic_i! {
|
|
"oris" 3, 4, 0x89ab;
|
|
0x648389ab;
|
|
|[a, b]| a | b;
|
|
}
|
|
insn_logic_i! {
|
|
"xori" 3, 4, 0x89ab;
|
|
0x688389ab;
|
|
|[a, b]| a ^ b;
|
|
}
|
|
insn_logic_i! {
|
|
"xori" 0, 0, 0; // ensure xnop actually decodes to a normal ALU instruction
|
|
0x68000000;
|
|
|[a, b]| a ^ b;
|
|
}
|
|
insn_logic_i! {
|
|
"xoris" 3, 4, 0x89ab;
|
|
0x6c8389ab;
|
|
|[a, b]| a ^ b;
|
|
}
|
|
macro_rules! insn_logic {
|
|
(
|
|
$mnemonic:literal $dest:literal, $src0:literal, $src1:literal;
|
|
$encoding:literal;
|
|
|[$a:ident, $b:ident]| $lut_fn:expr;
|
|
) => {
|
|
retval.push(insn_single(
|
|
concat!(
|
|
$mnemonic,
|
|
" ",
|
|
stringify!($dest),
|
|
", ",
|
|
stringify!($src0),
|
|
", ",
|
|
stringify!($src1)
|
|
),
|
|
$encoding,
|
|
None,
|
|
LogicalMOp::logical(
|
|
MOpDestReg::new_sim(
|
|
&[MOpRegNum::power_isa_gpr_reg_num($dest)],
|
|
if $mnemonic.contains('.') {
|
|
&[MOpRegNum::POWER_ISA_CR_0_REG_NUM]
|
|
} else {
|
|
&[]
|
|
},
|
|
),
|
|
[
|
|
MOpRegNum::power_isa_gpr_reg_imm($src0).value,
|
|
MOpRegNum::power_isa_gpr_reg_imm($src1).value,
|
|
],
|
|
0.cast_to_static::<SInt<_>>(),
|
|
OutputIntegerMode.Full64(),
|
|
LogicalMOp::lut_from_fn(|[$a, $b]| $lut_fn),
|
|
),
|
|
));
|
|
};
|
|
}
|
|
insn_logic! {
|
|
"and" 3, 4, 5;
|
|
0x7c832838;
|
|
|[a, b]| a & b;
|
|
}
|
|
insn_logic! {
|
|
"and." 3, 4, 5;
|
|
0x7c832839;
|
|
|[a, b]| a & b;
|
|
}
|
|
insn_logic! {
|
|
"xor" 3, 4, 5;
|
|
0x7c832a78;
|
|
|[a, b]| a ^ b;
|
|
}
|
|
insn_logic! {
|
|
"xor." 3, 4, 5;
|
|
0x7c832a79;
|
|
|[a, b]| a ^ b;
|
|
}
|
|
insn_logic! {
|
|
"nand" 3, 4, 5;
|
|
0x7c832bb8;
|
|
|[a, b]| !(a & b);
|
|
}
|
|
insn_logic! {
|
|
"nand." 3, 4, 5;
|
|
0x7c832bb9;
|
|
|[a, b]| !(a & b);
|
|
}
|
|
insn_logic! {
|
|
"or" 3, 4, 5;
|
|
0x7c832b78;
|
|
|[a, b]| a | b;
|
|
}
|
|
retval.push(insn_single(
|
|
"or 3, 4, 4", // mr 3, 4
|
|
0x7c832378,
|
|
None,
|
|
MoveRegMOp::move_reg(
|
|
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num(3)], &[]),
|
|
[MOpRegNum::power_isa_gpr_reg_imm(4).value],
|
|
0.cast_to_static::<SInt<_>>(),
|
|
),
|
|
));
|
|
insn_logic! {
|
|
"or." 3, 4, 5;
|
|
0x7c832b79;
|
|
|[a, b]| a | b;
|
|
}
|
|
insn_logic! {
|
|
"or." 3, 4, 4; // mr. 3, 4
|
|
0x7c832379;
|
|
|[a, b]| a | b;
|
|
}
|
|
insn_logic! {
|
|
"orc" 3, 4, 5;
|
|
0x7c832b38;
|
|
|[a, b]| a | !b;
|
|
}
|
|
insn_logic! {
|
|
"orc." 3, 4, 5;
|
|
0x7c832b39;
|
|
|[a, b]| a | !b;
|
|
}
|
|
insn_logic! {
|
|
"nor" 3, 4, 5;
|
|
0x7c8328f8;
|
|
|[a, b]| !(a | b);
|
|
}
|
|
insn_logic! {
|
|
"nor." 3, 4, 5;
|
|
0x7c8328f9;
|
|
|[a, b]| !(a | b);
|
|
}
|
|
insn_logic! {
|
|
"eqv" 3, 4, 5;
|
|
0x7c832a38;
|
|
|[a, b]| a == b;
|
|
}
|
|
insn_logic! {
|
|
"eqv." 3, 4, 5;
|
|
0x7c832a39;
|
|
|[a, b]| a == b;
|
|
}
|
|
insn_logic! {
|
|
"andc" 3, 4, 5;
|
|
0x7c832878;
|
|
|[a, b]| a & !b;
|
|
}
|
|
insn_logic! {
|
|
"andc." 3, 4, 5;
|
|
0x7c832879;
|
|
|[a, b]| a & !b;
|
|
}
|
|
macro_rules! insn_exts {
|
|
(
|
|
$mnemonic:literal $dest:literal, $src:literal;
|
|
$encoding:literal;
|
|
$OutputIntegerMode:ident;
|
|
) => {
|
|
retval.push(insn_single(
|
|
concat!($mnemonic, " ", stringify!($dest), ", ", stringify!($src)),
|
|
$encoding,
|
|
None,
|
|
LogicalMOp::logical_i(
|
|
MOpDestReg::new_sim(
|
|
&[MOpRegNum::power_isa_gpr_reg_num($dest)],
|
|
if $mnemonic.contains('.') {
|
|
&[MOpRegNum::POWER_ISA_CR_0_REG_NUM]
|
|
} else {
|
|
&[]
|
|
},
|
|
),
|
|
[MOpRegNum::power_isa_gpr_reg_imm($src).value],
|
|
0.cast_to_static::<SInt<_>>(),
|
|
OutputIntegerMode.$OutputIntegerMode(),
|
|
LogicalMOp::lut_from_fn(|[a, b]| a | b),
|
|
),
|
|
));
|
|
};
|
|
}
|
|
insn_exts! {
|
|
"extsb" 3, 4;
|
|
0x7c830774;
|
|
SignExt8;
|
|
}
|
|
insn_exts! {
|
|
"extsb." 3, 4;
|
|
0x7c830775;
|
|
SignExt8;
|
|
}
|
|
insn_exts! {
|
|
"extsh" 3, 4;
|
|
0x7c830734;
|
|
SignExt16;
|
|
}
|
|
insn_exts! {
|
|
"extsh." 3, 4;
|
|
0x7c830735;
|
|
SignExt16;
|
|
}
|
|
insn_exts! {
|
|
"extsw" 3, 4;
|
|
0x7c8307b4;
|
|
SignExt32;
|
|
}
|
|
insn_exts! {
|
|
"extsw." 3, 4;
|
|
0x7c8307b5;
|
|
SignExt32;
|
|
}
|
|
// ensure pnop decodes to zero instructions
|
|
retval.push(insn_empty(
|
|
// LLVM doesn't support the pnop instruction:
|
|
// https://github.com/llvm/llvm-project/issues/176831
|
|
".long 0x07000000, 0 # pnop",
|
|
0x07000000,
|
|
Some(0),
|
|
));
|
|
retval
|
|
}
|
|
|
|
#[test]
|
|
fn test_test_cases_assembly() -> std::io::Result<()> {
|
|
let llvm_mc_regex = regex::Regex::new(r"llvm-mc(-\d+)?$").expect("known to be a valid regex");
|
|
let llvm_mc = which::which_re(llvm_mc_regex)
|
|
.expect("can't find llvm-mc or llvm-mc-<num> in path")
|
|
.next()
|
|
.expect("can't find llvm-mc or llvm-mc-<num> in path");
|
|
let test_cases = test_cases();
|
|
let mut assembly = String::new();
|
|
for TestCase {
|
|
mnemonic,
|
|
first_input: _,
|
|
second_input: _,
|
|
output: _,
|
|
loc: _,
|
|
} in &test_cases
|
|
{
|
|
writeln!(assembly, "{mnemonic}").unwrap();
|
|
}
|
|
let (reader, mut writer) = std::io::pipe()?;
|
|
let thread = std::thread::spawn(move || writer.write_all(assembly.as_bytes()));
|
|
let std::process::Output {
|
|
status,
|
|
stdout,
|
|
stderr,
|
|
} = Command::new(&llvm_mc)
|
|
.arg("--triple=powerpc64le-linux-gnu")
|
|
.arg("--assemble")
|
|
.arg("--filetype=asm")
|
|
.arg("--show-encoding")
|
|
.arg("-")
|
|
.stdin(reader)
|
|
.output()?;
|
|
let _ = thread.join();
|
|
let stderr = String::from_utf8_lossy(&stderr);
|
|
eprint!("{stderr}");
|
|
if !status.success() {
|
|
panic!("{} failed: {status}", llvm_mc.display());
|
|
}
|
|
let stdout = String::from_utf8_lossy(&stdout);
|
|
print!("{stdout}");
|
|
let mut lines = stdout.lines();
|
|
let text_line = lines.next();
|
|
assert_eq!(text_line, Some("\t.text"));
|
|
for test_case @ TestCase {
|
|
mnemonic: _,
|
|
first_input,
|
|
second_input,
|
|
output: _,
|
|
loc: _,
|
|
} in test_cases
|
|
{
|
|
let Some(line) = lines.next() else {
|
|
panic!("output missing line for: {test_case:?}");
|
|
};
|
|
if line.starts_with("\t.long") {
|
|
assert_eq!(
|
|
line,
|
|
format!("\t.long\t{first_input}"),
|
|
"test_case={test_case:?}\nline:\n{line}"
|
|
);
|
|
if let Some(second_input) = second_input {
|
|
let Some(line) = lines.next() else {
|
|
panic!("output missing line for: {test_case:?}");
|
|
};
|
|
assert_eq!(
|
|
line,
|
|
format!("\t.long\t{second_input}"),
|
|
"test_case={test_case:?}\nline:\n{line}"
|
|
);
|
|
}
|
|
continue;
|
|
}
|
|
let Some((_, comment)) = line.split_once('#') else {
|
|
panic!("output line missing comment. test_case={test_case:?}\nline:\n{line}");
|
|
};
|
|
let [b0, b1, b2, b3] = first_input.to_le_bytes();
|
|
let expected_comment = if let Some(second_input) = second_input {
|
|
let [b4, b5, b6, b7] = second_input.to_le_bytes();
|
|
format!(
|
|
" encoding: [0x{b0:02x},0x{b1:02x},0x{b2:02x},0x{b3:02x},0x{b4:02x},0x{b5:02x},0x{b6:02x},0x{b7:02x}]"
|
|
)
|
|
} else {
|
|
format!(" encoding: [0x{b0:02x},0x{b1:02x},0x{b2:02x},0x{b3:02x}]")
|
|
};
|
|
assert_eq!(
|
|
comment, expected_comment,
|
|
"test_case={test_case:?}\nline:\n{line}"
|
|
);
|
|
}
|
|
for line in lines {
|
|
assert!(line.trim().is_empty(), "bad trailing output line: {line:?}");
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[hdl]
|
|
#[test]
|
|
fn test_decode_insn() {
|
|
let _n = SourceLocation::normalize_files_for_tests();
|
|
let m = decode_one_insn();
|
|
let mut sim = Simulation::new(m);
|
|
let writer = RcWriter::default();
|
|
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
|
|
struct DumpVcdOnDrop {
|
|
writer: Option<RcWriter>,
|
|
}
|
|
impl Drop for DumpVcdOnDrop {
|
|
fn drop(&mut self) {
|
|
if let Some(mut writer) = self.writer.take() {
|
|
let vcd = String::from_utf8(writer.take()).unwrap();
|
|
println!("####### VCD:\n{vcd}\n#######");
|
|
}
|
|
}
|
|
}
|
|
let mut writer = DumpVcdOnDrop {
|
|
writer: Some(writer),
|
|
};
|
|
for test_case @ TestCase {
|
|
mnemonic: _,
|
|
first_input,
|
|
second_input,
|
|
output: _,
|
|
loc: _,
|
|
} in test_cases()
|
|
{
|
|
sim.write(sim.io().first_input, first_input);
|
|
sim.write(
|
|
sim.io().second_input,
|
|
if let Some(v) = second_input {
|
|
#[hdl(sim)]
|
|
HdlSome(v)
|
|
} else {
|
|
#[hdl(sim)]
|
|
HdlNone()
|
|
},
|
|
);
|
|
sim.advance_time(SimDuration::from_micros(1));
|
|
let second_input_used = sim.read_bool(sim.io().second_input_used);
|
|
let is_illegal = sim.read_bool(sim.io().is_illegal);
|
|
let output = sim.read(sim.io().output);
|
|
#[derive(Debug)]
|
|
#[expect(dead_code, reason = "used only for Debug formatting")]
|
|
struct FormattedOutput<'a> {
|
|
insns: &'a [SimValue<MOp>],
|
|
second_input_used: bool,
|
|
is_illegal: bool,
|
|
}
|
|
let expected = format!(
|
|
"{:#?}",
|
|
FormattedOutput {
|
|
insns: ArrayVec::elements_sim_ref(&test_case.output),
|
|
second_input_used: second_input.is_some(),
|
|
is_illegal: false,
|
|
},
|
|
);
|
|
let output = format!(
|
|
"{:#?}",
|
|
FormattedOutput {
|
|
insns: ArrayVec::elements_sim_ref(&output),
|
|
second_input_used,
|
|
is_illegal,
|
|
},
|
|
);
|
|
assert!(
|
|
expected == output,
|
|
"test_case={test_case:#?}\noutput={output}\nexpected={expected}"
|
|
);
|
|
}
|
|
let vcd = String::from_utf8(writer.writer.take().unwrap().take()).unwrap();
|
|
println!("####### VCD:\n{vcd}\n#######");
|
|
if vcd != include_str!("expected/decode_one_insn.vcd") {
|
|
panic!();
|
|
}
|
|
}
|
|
|
|
#[hdl]
|
|
#[test]
|
|
fn test_simple_power_isa_decoder() {
|
|
// TODO
|
|
}
|