Compare commits

...
Sign in to create a new pull request.

4 commits

11 changed files with 2767 additions and 11 deletions

13
Cargo.lock generated
View file

@ -279,11 +279,13 @@ dependencies = [
"fayalite",
"hex-literal",
"parse_powerisa_pdf",
"regex",
"roxmltree",
"serde",
"sha2",
"simple-mermaid",
"ureq",
"which",
]
[[package]]
@ -359,12 +361,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
version = "0.3.9"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@ -630,9 +632,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.159"
version = "0.2.180"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
[[package]]
name = "libloading"
@ -1210,6 +1212,7 @@ checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f"
dependencies = [
"either",
"home",
"regex",
"rustix",
"winsafe",
]

View file

@ -23,6 +23,7 @@ serde = { version = "1.0.202", features = ["derive"] }
sha2 = "0.10.9"
simple-mermaid = "0.2.0"
ureq = "3.1.4"
which = { version = "6.0.3", features = ["regex"] }
[profile.dev]
opt-level = 1

View file

@ -30,4 +30,6 @@ ureq.workspace = true
[dev-dependencies]
base16ct.workspace = true
hex-literal.workspace = true
regex = "1.12.2"
sha2.workspace = true
which.workspace = true

View file

@ -0,0 +1,4 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
pub mod simple_power_isa;

File diff suppressed because it is too large Load diff

View file

@ -4,7 +4,9 @@ use crate::{unit::UnitMOp, util::range_u32_len};
use fayalite::{
expr::{HdlPartialEqImpl, ops::ArrayLiteral},
intern::Interned,
module::wire_with_loc,
prelude::*,
ty::StaticType,
};
use std::{borrow::Cow, fmt, marker::PhantomData, ops::Range};
@ -947,6 +949,87 @@ pub struct MOpDestReg {
pub flag_regs: Array<HdlOption<()>, { range_u32_len(&MOpRegNum::FLAG_REG_NUMS) }>,
}
impl MOpDestReg {
#[hdl]
#[track_caller]
pub fn new_sim(normal_regs: &[u32], flag_regs: &[u32]) -> SimValue<Self> {
let zero_reg = MOpRegNum::const_zero().to_sim_value();
let mut normal_regs_sim = std::array::from_fn(|_| zero_reg.clone());
for (i, reg) in normal_regs.iter().copied().enumerate() {
let Some(normal_reg_sim) = normal_regs_sim.get_mut(i) else {
panic!("too many normal regs");
};
if reg >= 1 << MOpRegNum::WIDTH {
panic!("normal reg number out of range");
}
*normal_reg_sim.value = reg.cast_to_static::<UInt<_>>();
}
let mut flag_regs_sim = std::array::from_fn(|_| {
#[hdl(sim)]
HdlNone()
});
for &flag_reg in flag_regs {
let Some(index) = { MOpRegNum::FLAG_REG_NUMS }.position(|v| flag_reg == v) else {
panic!(
"flag reg number {flag_reg} is out of range, supported range is: {:?}",
MOpRegNum::FLAG_REG_NUMS
);
};
flag_regs_sim[index] = #[hdl(sim)]
HdlSome(());
}
#[hdl(sim)]
Self {
normal_regs: normal_regs_sim,
flag_regs: flag_regs_sim,
}
}
#[hdl]
#[track_caller]
pub fn new(
normal_regs: impl IntoIterator<Item = Expr<MOpRegNum>>,
flag_regs: impl IntoIterator<Item = (u32, Expr<Bool>)>,
) -> Expr<Self> {
let mut normal_regs_array = [MOpRegNum::const_zero(); Self::NORMAL_REG_COUNT];
const FLAG_REG_COUNT: usize = range_u32_len(&MOpRegNum::FLAG_REG_NUMS);
let mut used_flag_regs = [false; FLAG_REG_COUNT];
let mut flag_regs_array = [HdlNone(); FLAG_REG_COUNT];
for (i, normal_reg) in normal_regs.into_iter().enumerate() {
assert!(i < Self::NORMAL_REG_COUNT, "too many normal regs");
normal_regs_array[i] = normal_reg;
}
for (flag_reg_num, flag_reg_enabled) in flag_regs {
let Some(index) = { MOpRegNum::FLAG_REG_NUMS }.position(|v| flag_reg_num == v) else {
panic!(
"flag reg number {flag_reg_num} is out of range, supported range is: {:?}",
MOpRegNum::FLAG_REG_NUMS
);
};
assert!(
!used_flag_regs[index],
"duplicate flag reg number {flag_reg_num}"
);
used_flag_regs[index] = true;
let wire = wire_with_loc(
&format!("flag_reg_{index}"),
SourceLocation::caller(),
StaticType::TYPE,
);
connect(wire, HdlNone());
#[hdl]
if flag_reg_enabled {
connect(wire, HdlSome(()));
}
flag_regs_array[index] = wire;
}
#[hdl]
Self {
normal_regs: normal_regs_array,
flag_regs: flag_regs_array,
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub enum RenameTableName {
/// the large rename table for normal registers (has less read/write ports)

View file

@ -29,24 +29,150 @@ impl MOpRegNum {
pub const POWER_ISA_LR_REG_NUM: u32 = 1;
pub const POWER_ISA_CTR_REG_NUM: u32 = 2;
pub const POWER_ISA_TAR_REG_NUM: u32 = 3;
/// XER bits are stored in [`PRegValue.flags`], bits that don't exist in [`PRegValue.flags`] are stored in [`PRegValue.int_fp`]
/// SO, OV, and OV32 XER bits -- in [`PRegValue.flags`]
///
/// [`PRegValue.flags`]: struct@crate::register::PRegValue
/// [`PRegValue.int_fp`]: struct@crate::register::PRegValue
pub const POWER_ISA_XER_REG_NUM: u32 = 4;
pub const POWER_ISA_XER_SO_OV_OV32_REG_NUM: u32 =
range_u32_nth_or_panic(&Self::FLAG_REG_NUMS, 0);
/// CA and CA32 XER bits -- in [`PRegValue.flags`]
///
/// [`PRegValue.flags`]: struct@crate::register::PRegValue
pub const POWER_ISA_XER_CA_CA32_REG_NUM: u32 = 4;
/// only the XER bits that don't exist in [`PRegValue.flags`]
///
/// [`PRegValue.flags`]: struct@crate::register::PRegValue
pub const POWER_ISA_XER_OTHER_REG_NUM: u32 = 5;
pub const POWER_ISA_CR_REG_NUMS: Range<u32> = 8..16;
/// SO, OV, and OV32 XER bits -- in [`PRegValue.flags`]
///
/// [`PRegValue.flags`]: struct@crate::register::PRegValue
#[hdl]
pub fn power_isa_xer_so_ov_ov32_reg() -> Expr<Self> {
#[hdl]
Self {
value: Self::POWER_ISA_XER_SO_OV_OV32_REG_NUM.cast_to_static::<UInt<_>>(),
}
}
/// CA and CA32 XER bits -- in [`PRegValue.flags`]
///
/// [`PRegValue.flags`]: struct@crate::register::PRegValue
#[hdl]
pub fn power_isa_xer_ca_ca32_reg() -> Expr<Self> {
#[hdl]
Self {
value: Self::POWER_ISA_XER_CA_CA32_REG_NUM.cast_to_static::<UInt<_>>(),
}
}
/// only the XER bits that don't exist in [`PRegValue.flags`]
///
/// [`PRegValue.flags`]: struct@crate::register::PRegValue
#[hdl]
pub fn power_isa_xer_other_reg() -> Expr<Self> {
#[hdl]
Self {
value: Self::POWER_ISA_XER_OTHER_REG_NUM.cast_to_static::<UInt<_>>(),
}
}
pub const POWER_ISA_CR_0_REG_NUM: u32 = range_u32_nth_or_panic(&Self::FLAG_REG_NUMS, 1);
pub const POWER_ISA_CR_1_THRU_7_REG_NUMS: Range<u32> = 9..16;
pub const fn power_isa_cr_reg_num(index: usize) -> u32 {
range_u32_nth_or_panic(&Self::POWER_ISA_CR_REG_NUMS, index)
if index == 0 {
Self::POWER_ISA_CR_0_REG_NUM
} else {
range_u32_nth_or_panic(&Self::POWER_ISA_CR_1_THRU_7_REG_NUMS, index - 1)
}
}
#[hdl]
pub fn power_isa_cr_reg(field_num: Expr<UInt<3>>) -> Expr<Self> {
#[hdl]
let power_isa_cr_reg: Self = wire();
#[hdl]
if field_num.cmp_eq(0u8) {
connect_any(power_isa_cr_reg.value, Self::POWER_ISA_CR_0_REG_NUM);
} else {
connect_any(
power_isa_cr_reg.value,
Self::POWER_ISA_CR_1_THRU_7_REG_NUMS.start + field_num,
);
}
power_isa_cr_reg
}
#[hdl]
pub fn power_isa_cr_reg_sim(field_num: &SimValue<UInt<3>>) -> SimValue<Self> {
#[hdl(sim)]
Self {
value: Self::power_isa_cr_reg_num(
field_num.cast_to_static::<UInt<8>>().as_int() as usize
)
.cast_to_static::<UInt<_>>(),
}
}
pub const POWER_ISA_GPR_REG_NUMS: Range<u32> = 32..64;
pub const fn power_isa_gpr_reg_num(index: usize) -> u32 {
range_u32_nth_or_panic(&Self::POWER_ISA_GPR_REG_NUMS, index)
}
#[hdl]
pub fn power_isa_gpr_reg(reg_num: Expr<UInt<5>>) -> Expr<Self> {
#[hdl]
Self {
value: (Self::POWER_ISA_GPR_REG_NUMS.start + reg_num).cast_to_static::<UInt<_>>(),
}
}
#[hdl]
pub fn power_isa_gpr_reg_sim(reg_num: &SimValue<UInt<5>>) -> SimValue<Self> {
#[hdl(sim)]
Self {
value: (Self::POWER_ISA_GPR_REG_NUMS.start + reg_num).cast_to_static::<UInt<_>>(),
}
}
pub const fn power_isa_gpr_or_zero_reg_num(index: usize) -> u32 {
if index == 0 {
Self::CONST_ZERO_REG_NUM
} else {
Self::power_isa_gpr_reg_num(index)
}
}
#[hdl]
pub fn power_isa_gpr_or_zero_reg(reg_num: Expr<UInt<5>>) -> Expr<Self> {
#[hdl]
let power_isa_gpr_or_zero_reg: Self = wire();
connect(power_isa_gpr_or_zero_reg, Self::power_isa_gpr_reg(reg_num));
#[hdl]
if reg_num.cmp_eq(0u8) {
connect(power_isa_gpr_or_zero_reg, Self::const_zero());
}
power_isa_gpr_or_zero_reg
}
#[hdl]
pub fn power_isa_gpr_or_zero_reg_sim(reg_num: &SimValue<UInt<5>>) -> SimValue<Self> {
#[hdl(sim)]
Self {
value: Self::power_isa_gpr_or_zero_reg_num(
reg_num.cast_to_static::<UInt<8>>().as_int() as usize,
)
.cast_to_static::<UInt<_>>(),
}
}
pub const POWER_ISA_FPR_REG_NUMS: Range<u32> = 64..96;
pub const fn power_isa_fpr_reg_num(index: usize) -> u32 {
range_u32_nth_or_panic(&Self::POWER_ISA_FPR_REG_NUMS, index)
}
#[hdl]
pub fn power_isa_fpr_reg(reg_num: Expr<UInt<5>>) -> Expr<Self> {
#[hdl]
Self {
value: (Self::POWER_ISA_FPR_REG_NUMS.start + reg_num).cast_to_static::<UInt<_>>(),
}
}
#[hdl]
pub fn power_isa_fpr_reg_sim(reg_num: &SimValue<UInt<5>>) -> SimValue<Self> {
#[hdl(sim)]
Self {
value: (Self::POWER_ISA_FPR_REG_NUMS.start + reg_num).cast_to_static::<UInt<_>>(),
}
}
}

View file

@ -1,9 +1,10 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
pub mod config;
pub mod decoder;
pub mod instruction;
pub mod next_pc;
pub mod powerisa;
pub mod powerisa_instructions_xml;
pub mod reg_alloc;
pub mod register;
pub mod unit;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,366 @@
// 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, 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,
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()
}
}
#[hdl]
fn test_cases() -> Vec<TestCase> {
let mut retval = Vec::new();
#[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.clone(),
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(4_hdl_u5).value,
MOpRegNum::const_zero().value,
],
0x1234.cast_to_static::<SInt<_>>(),
#[hdl(sim)]
OutputIntegerMode::Full64(),
false,
false,
false,
false,
),
));
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(4_hdl_u5).value,
MOpRegNum::const_zero().value,
],
0x12340000.cast_to_static::<SInt<_>>(),
#[hdl(sim)]
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<_>>(),
#[hdl(sim)]
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(4_hdl_u5).value,
MOpRegNum::power_isa_gpr_reg(5_hdl_u5).value,
MOpRegNum::const_zero().value,
],
0.cast_to_static::<SInt<_>>(),
#[hdl(sim)]
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(4_hdl_u5).value,
MOpRegNum::const_zero().value,
],
0x1234.cast_to_static::<SInt<_>>(),
#[hdl(sim)]
OutputIntegerMode::Full64(),
false,
false,
false,
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(4_hdl_u5).value,
MOpRegNum::power_isa_gpr_reg(5_hdl_u5).value,
MOpRegNum::const_zero().value,
],
0.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,
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:?}");
};
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}"
);
}
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
}