208 lines
6.5 KiB
Rust
208 lines
6.5 KiB
Rust
// SPDX-License-Identifier: LGPL-3.0-or-later
|
|
// See Notices.txt for copyright information
|
|
|
|
use crate::test_cases::TestCase;
|
|
use cpu::{
|
|
decoder::simple_power_isa::decode_one_insn, instruction::MOp, util::array_vec::ArrayVec,
|
|
};
|
|
use fayalite::{prelude::*, sim::vcd::VcdWriterDecls, util::RcWriter};
|
|
use std::{fmt::Write as _, io::Write, process::Command};
|
|
|
|
mod test_cases;
|
|
|
|
#[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::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"));
|
|
let mut any_error = false;
|
|
macro_rules! assert_eq_cont {
|
|
($l:expr, $r:expr, $($msg:tt)+) => {
|
|
match (&$l, &$r) {
|
|
(l, r) => if l != r {
|
|
eprintln!("assertion failed: {}\nl={l:#?}\nr={r:#?}", format_args!($($msg)+));
|
|
any_error = true;
|
|
}
|
|
}
|
|
};
|
|
}
|
|
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_cont!(
|
|
comment,
|
|
expected_comment,
|
|
"test_case={test_case:?}\nline:\n{line}"
|
|
);
|
|
}
|
|
for line in lines {
|
|
assert!(line.trim().is_empty(), "bad trailing output line: {line:?}");
|
|
}
|
|
if any_error {
|
|
panic!();
|
|
}
|
|
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::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
|
|
}
|