add PowerISA decoder #7

Merged
programmerjake merged 35 commits from programmerjake/cpu:add-powerisa-decoder into master 2026-01-29 02:22:14 +00:00
5 changed files with 6101 additions and 2182 deletions
Showing only changes of commit 7ebcd5de1e - Show all commits

View file

@ -1,22 +1,19 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::instruction::{
AddSubMOp, CompareMOp, CompareMode, MOpDestReg, MOpRegNum, OutputIntegerMode,
};
use crate::powerisa_instructions_xml::{
InstructionBitFieldName, InstructionBitFieldsInner, TextLineItem,
};
use crate::util::array_vec::Length;
use crate::{
config::CpuConfig, instruction::MOp, powerisa_instructions_xml::Instructions,
util::array_vec::ArrayVec,
config::CpuConfig,
instruction::{
AddSubMOp, CompareMOp, CompareMode, LogicalMOp, MOp, MOpDestReg, MOpRegNum, MoveRegMOp,
OutputIntegerMode,
},
powerisa_instructions_xml::{
InstructionBitFieldName, InstructionBitFieldsInner, Instructions, TextLineItem,
},
util::array_vec::{ArrayVec, Length},
};
use fayalite::module::wire_with_loc;
use fayalite::prelude::*;
use fayalite::ty::StaticType;
use std::collections::BTreeMap;
use std::ops::RangeInclusive;
use fayalite::{module::wire_with_loc, prelude::*, ty::StaticType};
use std::{collections::BTreeMap, ops::RangeInclusive};
#[rustfmt::skip]
const FP_MNEMONICS: &[&str] = &[
@ -122,6 +119,7 @@ const VSX_MNEMONICS: &[&str] = &[
"xxspltiw", "xxspltw", "xxperm", "xxpermr", "xxsldwi", "xxgenpcvdm", "xxgenpcvwm", "lxvkq", "xvtlsbb",
];
#[derive(Debug)]
struct DecodeState {
mnemonic: &'static str,
arguments: Option<&'static str>,
@ -185,6 +183,8 @@ impl_fields! {
struct FieldRA(FieldGpr);
#[name = "RB"]
struct FieldRB(FieldGpr);
#[name = "RS"]
struct FieldRS(FieldGpr);
#[name = "RT"]
struct FieldRT(FieldGpr);
#[name = "SI"]
@ -1024,6 +1024,103 @@ impl DecodeState {
);
});
}
/// for `andi[s]./ori[s]/xori[s]`
#[hdl]
fn decode_andis_oris_xoris(&mut self) {
assert_eq!(self.arguments, Some("RA,RS,UI"), "{self:?}");
let lut = match self.mnemonic {
"andi." | "andis." => LogicalMOp::lut_from_fn(|[a, b]| a & b),
"ori" | "oris" => LogicalMOp::lut_from_fn(|[a, b]| a | b),
"xori" | "xoris" => LogicalMOp::lut_from_fn(|[a, b]| a ^ b),
_ => unreachable!(),
};
self.decode_scope(|this, (FieldRS(rs), FieldRA(ra), FieldUI(ui))| {
// TODO: handle SO propagation
connect(
ArrayVec::len(this.output),
1usize.cast_to_static::<Length<_>>(),
);
connect(
this.output[0],
LogicalMOp::logical_i(
MOpDestReg::new(
[gpr(ra)],
[(
MOpRegNum::POWER_ISA_CR_0_REG_NUM,
this.mnemonic.contains('.').to_expr(),
)],
),
[gpr(rs).value],
if this.mnemonic.contains('s') {
(ui << 16).cast_to_static::<SInt<_>>()
} else {
ui.cast_to_static::<SInt<_>>()
},
OutputIntegerMode.Full64(),
lut,
),
);
if this.mnemonic == "ori" {
#[hdl]
if rs.reg_num.cmp_eq(0u8) & ra.reg_num.cmp_eq(0u8) & ui.cmp_eq(0u8) {
// found `nop`, remove at decode time
connect(
ArrayVec::len(this.output),
0usize.cast_to_static::<Length<_>>(),
);
}
}
});
}
/// for `and[.]/xor[.]/nand[.]/or[.]/orc[.]/nor[.]/eqv[.]/andc[.]`
#[hdl]
fn decode_and_xor_nand_or_orc_nor_eqv_andc(&mut self) {
assert_eq!(self.arguments, Some("RA,RS,RB"));
let lut = match self.mnemonic.trim_end_matches('.') {
"and" => LogicalMOp::lut_from_fn(|[a, b]| a & b),
"xor" => LogicalMOp::lut_from_fn(|[a, b]| a ^ b),
"nand" => LogicalMOp::lut_from_fn(|[a, b]| !(a & b)),
"or" => LogicalMOp::lut_from_fn(|[a, b]| a | b),
"orc" => LogicalMOp::lut_from_fn(|[a, b]| a | !b),
"nor" => LogicalMOp::lut_from_fn(|[a, b]| !(a | b)),
"eqv" => LogicalMOp::lut_from_fn(|[a, b]| a == b),
"andc" => LogicalMOp::lut_from_fn(|[a, b]| a & !b),
_ => unreachable!(),
};
self.decode_scope(
|this, (FieldRA(ra), FieldRS(rs), FieldRB(rb), FieldRc(rc))| {
// TODO: handle SO propagation
connect(
ArrayVec::len(this.output),
1usize.cast_to_static::<Length<_>>(),
);
connect(
this.output[0],
LogicalMOp::logical(
MOpDestReg::new([gpr(ra)], [(MOpRegNum::POWER_ISA_CR_0_REG_NUM, rc)]),
[gpr(rs).value, gpr(rb).value],
0.cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
lut,
),
);
if this.mnemonic == "or" {
#[hdl]
if rs.reg_num.cmp_eq(rb.reg_num) & !rc {
// found `mr`
connect(
this.output[0],
MoveRegMOp::move_reg(
MOpDestReg::new([gpr(ra)], []),
[gpr(rs).value],
0i8.cast_to_static::<SInt<_>>(),
),
);
}
}
},
);
}
}
type DecodeFn = fn(&mut DecodeState);
@ -1179,15 +1276,16 @@ const DECODE_FNS: &[(&[&str], DecodeFn)] = &[
(&["isel"], |_state| {
// TODO
}),
(
&["andi.", "andis.", "ori", "oris", "xori", "xoris"],
DecodeState::decode_andis_oris_xoris,
),
(
&[
"andi.", "andis.", "ori", "oris", "xori", "xoris", "and", "and.", "xor", "xor.",
"nand", "nand.", "or", "or.", "orc", "orc.", "nor", "nor.", "eqv", "eqv.", "andc",
"andc.",
"and", "and.", "xor", "xor.", "nand", "nand.", "or", "or.", "orc", "orc.", "nor",
"nor.", "eqv", "eqv.", "andc", "andc.",
],
|_state| {
// TODO
},
DecodeState::decode_and_xor_nand_or_orc_nor_eqv_andc,
),
(&["extsb", "extsb.", "extsh", "extsh."], |_state| {
// TODO
@ -1367,13 +1465,13 @@ pub fn decode_one_insn() {
let mut conditions;
if let Some((mnemonic_, rest)) = mnemonic_line.split_once(char::is_whitespace) {
mnemonic = mnemonic_;
if let Some((arguments_, rest)) =
rest.trim_start().split_once(char::is_whitespace)
{
let rest = rest.trim_start();
if let Some((arguments_, rest)) = rest.split_once(char::is_whitespace) {
arguments = Some(arguments_);
conditions = Some(rest.trim_start()).filter(|v| !v.is_empty());
} else {
arguments = None;
assert!(!rest.starts_with('('));
arguments = Some(rest).filter(|v| !v.is_empty());
conditions = None;
}
} else {

View file

@ -701,16 +701,48 @@ impl<DestReg: Type, SrcRegWidth: Size> AddSubMOp<DestReg, SrcRegWidth, ConstUsiz
}
common_mop_struct! {
#[mapped(<NewDestReg, NewSrcRegWidth> LogicalMOp<NewDestReg, NewSrcRegWidth>)]
#[mapped(<NewDestReg, NewSrcRegWidth> LogicalMOp<NewDestReg, NewSrcRegWidth, SrcCount>)]
#[hdl(cmp_eq)]
pub struct LogicalMOp<DestReg: Type, SrcRegWidth: Size> {
/// computes the output like so:
/// ```
/// fn logical_output(src: [u64; 2], lut: u8) -> u64 {
/// let mut output = 0u64;
/// for i in 0..64 {
/// let mask = 1 << i;
/// let mut lut_index = 0;
/// if (src[0] & mask) != 0 {
/// lut_index |= 1;
/// }
/// if (src[1] & mask) != 0 {
/// lut_index |= 2;
/// }
/// if (lut & (1 << lut_index)) != 0 {
/// output |= mask;
/// }
/// }
/// output
/// }
/// ```
pub struct LogicalMOp<DestReg: Type, SrcRegWidth: Size, SrcCount: KnownSize> {
#[common]
pub alu_common: AluCommonMOp<DestReg, SrcRegWidth, ConstUsize<2>>,
pub alu_common: AluCommonMOp<DestReg, SrcRegWidth, SrcCount>,
pub lut: UInt<4>,
}
}
impl<DestReg: Type, SrcRegWidth: Size> LogicalMOp<DestReg, SrcRegWidth> {
impl LogicalMOp<MOpDestReg, ConstUsize<{ MOpRegNum::WIDTH }>, ConstUsize<2>> {
pub fn lut_from_fn(f: impl Fn([bool; 2]) -> bool) -> UIntValue<ConstUsize<4>> {
let mut retval = 0u8;
for lut_index in 0..4 {
if f([(lut_index & 1) != 0, (lut_index & 2) != 0]) {
retval |= 1 << lut_index;
}
}
retval.cast_to_static::<UInt<4>>()
}
}
impl<DestReg: Type, SrcRegWidth: Size> LogicalMOp<DestReg, SrcRegWidth, ConstUsize<2>> {
#[hdl]
pub fn logical<Target: MOpTrait>(
dest: impl ToExpr<Type = DestReg>,
@ -736,6 +768,32 @@ impl<DestReg: Type, SrcRegWidth: Size> LogicalMOp<DestReg, SrcRegWidth> {
}
}
impl<DestReg: Type, SrcRegWidth: Size> LogicalMOp<DestReg, SrcRegWidth, ConstUsize<1>> {
#[hdl]
pub fn logical_i<Target: MOpTrait>(
dest: impl ToExpr<Type = DestReg>,
src: impl ToExpr<Type = Array<UIntType<SrcRegWidth>, 1>>,
imm: impl ToExpr<Type = SInt<{ COMMON_MOP_1_IMM_WIDTH }>>,
output_integer_mode: impl ToExpr<Type = OutputIntegerMode>,
lut: impl ToExpr<Type = UInt<4>>,
) -> Expr<Target>
where
Self: MOpInto<Target>,
{
MOpInto::mop_into(
#[hdl]
LogicalMOp {
alu_common: #[hdl]
AluCommonMOp {
common: CommonMOp::new(0_hdl_u0, dest, src, Expr::as_dyn_int(imm.to_expr())),
output_integer_mode,
},
lut,
},
)
}
}
#[hdl]
pub enum CompareMode {
U64,
@ -866,7 +924,8 @@ mop_enum! {
pub enum AluBranchMOp<DestReg: Type, SrcRegWidth: Size> {
AddSub(AddSubMOp<DestReg, SrcRegWidth, ConstUsize<3>>),
AddSubI(AddSubMOp<DestReg, SrcRegWidth, ConstUsize<2>>),
Logical(LogicalMOp<DestReg, SrcRegWidth>),
Logical(LogicalMOp<DestReg, SrcRegWidth, ConstUsize<2>>),
LogicalI(LogicalMOp<DestReg, SrcRegWidth, ConstUsize<1>>),
Compare(CompareMOp<DestReg, SrcRegWidth, ConstUsize<2>>),
CompareI(CompareMOp<DestReg, SrcRegWidth, ConstUsize<1>>),
}
@ -944,6 +1003,43 @@ common_mop_struct! {
}
}
impl<DestReg: Type, SrcRegWidth: Size, Target: MOpTrait> MOpInto<Target>
for MoveRegMOp<DestReg, SrcRegWidth>
where
UnitMOp<DestReg, SrcRegWidth, Self>: MOpInto<Target>,
{
fn mop_into_ty(self) -> Target {
MOpInto::mop_into_ty(
UnitMOp[MOpTrait::dest_reg_ty(self)][MOpTrait::src_reg_width(self)][self],
)
}
fn mop_into(this: Expr<Self>) -> Expr<Target> {
MOpInto::mop_into(
MOpInto::<UnitMOp<DestReg, SrcRegWidth, Self>>::mop_into_ty(this.ty())
.TransformedMove(this),
)
}
}
impl<DestReg: Type, SrcRegWidth: Size> MoveRegMOp<DestReg, SrcRegWidth> {
#[hdl]
pub fn move_reg<Target: MOpTrait>(
dest: impl ToExpr<Type = DestReg>,
src: impl ToExpr<Type = Array<UIntType<SrcRegWidth>, 1>>,
imm: impl ToExpr<Type = SInt<{ COMMON_MOP_1_IMM_WIDTH }>>,
) -> Expr<Target>
where
Self: MOpInto<Target>,
{
MOpInto::mop_into(
#[hdl]
MoveRegMOp {
common: CommonMOp::new(0_hdl_u2, dest, src, Expr::as_dyn_int(imm.to_expr())),
},
)
}
}
#[hdl(cmp_eq)]
/// there may be more than one unit of a given kind, so UnitNum is not the same as UnitKind.
/// zero is used for built-in constants, such as the zero register

View file

@ -232,7 +232,21 @@ fn add_sub<SrcCount: KnownSize>(
#[hdl]
fn logical(
mop: Expr<LogicalMOp<UnitOutRegNum<DynSize>, DynSize>>,
mop: Expr<LogicalMOp<UnitOutRegNum<DynSize>, DynSize, ConstUsize<2>>>,
flags_mode: Expr<FlagsMode>,
src_values: Expr<Array<PRegValue, { COMMON_MOP_SRC_LEN }>>,
) -> Expr<UnitResultCompleted<()>> {
// TODO: finish
#[hdl]
UnitResultCompleted::<_> {
value: PRegValue::zeroed(),
extra_out: (),
}
}
#[hdl]
fn logical_i(
mop: Expr<LogicalMOp<UnitOutRegNum<DynSize>, DynSize, ConstUsize<1>>>,
flags_mode: Expr<FlagsMode>,
src_values: Expr<Array<PRegValue, { COMMON_MOP_SRC_LEN }>>,
) -> Expr<UnitResultCompleted<()>> {
@ -350,6 +364,23 @@ pub fn alu_branch(config: &CpuConfig, unit_index: usize) {
},
),
),
AluBranchMOp::<_, _>::LogicalI(mop) => connect(
unit_base.execute_end,
HdlSome(
#[hdl]
ExecuteEnd::<_, _> {
unit_output: #[hdl]
UnitOutput::<_, _> {
which: MOpTrait::dest_reg(mop),
result: UnitResult[()].Completed(logical_i(
mop,
global_state.flags_mode,
src_values,
)),
},
},
),
),
AluBranchMOp::<_, _>::Compare(mop) => connect(
unit_base.execute_end,
HdlSome(

File diff suppressed because it is too large Load diff

View file

@ -4,7 +4,8 @@
use cpu::{
decoder::simple_power_isa::decode_one_insn,
instruction::{
AddSubMOp, CompareMOp, CompareMode, MOp, MOpDestReg, MOpRegNum, OutputIntegerMode,
AddSubMOp, CompareMOp, CompareMode, LogicalMOp, MOp, MOpDestReg, MOpRegNum, MoveRegMOp,
OutputIntegerMode,
},
util::array_vec::ArrayVec,
};
@ -52,6 +53,19 @@ impl fmt::Debug for TestCase {
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,
@ -68,7 +82,7 @@ fn test_cases() -> Vec<TestCase> {
mnemonic,
first_input,
second_input,
output: single_storage.clone(),
output: single_storage,
loc: std::panic::Location::caller(),
}
}
@ -663,6 +677,221 @@ fn test_cases() -> Vec<TestCase> {
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(
($src as u8).cast_to_static::<UInt<_>>().to_expr(),
)
.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(
($src0 as u8).cast_to_static::<UInt<_>>().to_expr(),
)
.value,
MOpRegNum::power_isa_gpr_reg(
($src1 as u8).cast_to_static::<UInt<_>>().to_expr(),
)
.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(4_hdl_u5).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;
}
retval
}