cpu/crates/cpu/tests/simple_power_isa_decoder.rs

190 lines
5.9 KiB
Rust

// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use cpu::{
decoder::simple_power_isa::decode_one_32bit_insn,
instruction::{AddSubMOp, MOp, MOpDestReg, MOpRegNum, 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,
input: 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,
input,
output,
loc,
} = self;
f.debug_struct("TestCase")
.field("mnemonic", mnemonic)
.field("input", &format_args!("0x{input:08x}"))
.field("output", &ArrayVec::elements_sim_ref(output))
.field("loc", &format_args!("{loc}"))
.finish()
}
}
#[hdl]
fn test_cases() -> Vec<TestCase> {
let mut retval = Vec::new();
#[track_caller]
fn insn_single(
mnemonic: &'static str,
input: 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,
input,
output: single_storage.clone(),
loc: std::panic::Location::caller(),
}
}
retval.push(insn_single(
"addi 3, 4, 0x1234",
0x38641234,
AddSubMOp::add_sub_i(
MOpDestReg::new_sim(&[3], &[]),
[
(MOpRegNum::POWER_ISA_GPR_REG_NUMS.start + 4).cast_to_static::<UInt<_>>(),
MOpRegNum::CONST_ZERO_REG_NUM.cast_to_static::<UInt<_>>(),
],
0x1234.cast_to_static::<SInt<_>>(),
#[hdl(sim)]
OutputIntegerMode::Full64(),
false,
false,
false,
false,
),
));
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,
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 in test_cases {
let Some(line) = lines.next() else {
panic!("output missing line for: {test_case:?}");
};
let Some((_, comment)) = line.split_once('#') else {
panic!("output line missing comment. test_case={test_case:?}\nline:\n{line}");
};
let [b0, b1, b2, b3] = test_case.input.to_le_bytes();
let expected_comment = 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_one_32bit_insn() {
let _n = SourceLocation::normalize_files_for_tests();
let m = decode_one_32bit_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 in test_cases() {
sim.write(sim.io().input, test_case.input);
sim.advance_time(SimDuration::from_micros(1));
let output = sim.read(sim.io().output);
let expected = format!("{:?}", ArrayVec::elements_sim_ref(&test_case.output));
let output = format!("{:?}", ArrayVec::elements_sim_ref(&output));
assert!(
expected == output,
"test_case={test_case:?}\noutput={output}"
);
}
let vcd = String::from_utf8(writer.writer.take().unwrap().take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("expected/decode_one_32bit_insn.vcd") {
panic!();
}
}
#[hdl]
#[test]
fn test_simple_power_isa_decoder() {
// TODO
}