implement decoding mtspr/mfspr/mftb
All checks were successful
/ test (pull_request) Successful in 31m50s
/ test (push) Successful in 32m14s

This commit is contained in:
Jacob Lifshay 2026-01-28 17:00:08 -08:00
parent a42b76b468
commit f88346ea37
Signed by: programmerjake
SSH key fingerprint: SHA256:HnFTLGpSm4Q4Fj502oCFisjZSoakwEuTsJJMSke63RQ
8 changed files with 175160 additions and 165913 deletions

View file

@ -6,8 +6,9 @@ use crate::{
instruction::{
AddSubMOp, BranchMOp, CompareMOp, CompareMode, ConditionMode, LoadMOp, LoadStoreConversion,
LoadStoreWidth, LogicalFlagsMOp, LogicalFlagsMOpImm, LogicalMOp, Lut4, MOp, MOpDestReg,
MOpRegNum, MoveRegMOp, OutputIntegerMode, ShiftRotateDestLogicOp, ShiftRotateMOp,
ShiftRotateMOpImm, ShiftRotateMode, StoreMOp,
MOpRegNum, MoveRegMOp, OutputIntegerMode, ReadSpecialMOp, ReadSpecialMOpImm,
ShiftRotateDestLogicOp, ShiftRotateMOp, ShiftRotateMOpImm, ShiftRotateMode, StoreMOp,
power_isa::PowerIsaSprEnum,
},
powerisa_instructions_xml::{
InstructionBitFieldName, InstructionBitFieldsInner, Instructions, TextLineItem,
@ -239,6 +240,10 @@ impl_fields! {
struct FieldSi0(SInt<18>);
#[name = "si1"]
struct FieldSi1(UInt<16>);
#[name = "spr"]
struct FieldSpr(UInt<10>);
#[name = "tbr"]
struct FieldTbr(UInt<10>);
#[name = "UI"]
struct FieldUI(UInt<16>);
#[name = "d0"]
@ -2319,6 +2324,110 @@ impl DecodeState<'_> {
},
);
}
fn read_write_spr(
&mut self,
spr: PowerIsaSprEnum,
reg: Expr<FieldGpr>,
is_write_to_spr: bool,
) -> Result<(), ()> {
let normal_move = |spr: Expr<MOpRegNum>| {
connect(
ArrayVec::len(self.output),
1usize.cast_to_static::<Length<_>>(),
);
if is_write_to_spr {
connect(
self.output[0],
MoveRegMOp::move_reg(
MOpDestReg::new([spr], []),
[gpr(reg).value],
0.cast_to_static::<SInt<_>>(),
),
);
} else {
connect(
self.output[0],
MoveRegMOp::move_reg(
MOpDestReg::new([gpr(reg)], []),
[spr.value],
0.cast_to_static::<SInt<_>>(),
),
);
}
Ok(())
};
let read_special = |imm: Expr<ReadSpecialMOpImm>| {
connect(
ArrayVec::len(self.output),
1usize.cast_to_static::<Length<_>>(),
);
connect(
self.output[0],
ReadSpecialMOp::read_special(
MOpDestReg::new([gpr(reg)], []),
[MOpRegNum::const_zero().value; 0],
imm,
),
);
Ok(())
};
match spr {
// PowerIsaSprEnum::Xer => todo!(),
PowerIsaSprEnum::Lr => normal_move(MOpRegNum::power_isa_lr_reg()),
PowerIsaSprEnum::Ctr => normal_move(MOpRegNum::power_isa_ctr_reg()),
PowerIsaSprEnum::Tar => normal_move(MOpRegNum::power_isa_tar_reg()),
PowerIsaSprEnum::Tb if !is_write_to_spr => {
read_special(ReadSpecialMOpImm.PowerIsaTimeBase())
}
PowerIsaSprEnum::Tbu if !is_write_to_spr => {
read_special(ReadSpecialMOpImm.PowerIsaTimeBaseU())
}
_ => {
Err(()) // allow emulation
}
}
}
/// for `mtspr/mfspr/mftb`
fn decode_mtspr_mfspr_mftb(&mut self) {
#[hdl]
fn do_read_write_spr(
this: &mut DecodeState<'_>,
spr_field: Expr<UInt<10>>,
reg: Expr<FieldGpr>,
is_write_to_spr: bool,
) {
#[hdl]
let spr_num = wire();
connect(spr_num, spr_field.rotate_left(5));
connect(this.is_illegal, true); // trap and/or allow emulation
for &spr in PowerIsaSprEnum::VARIANTS {
let num = spr.to_expr().num;
#[hdl]
if spr_num.cmp_eq(num) {
match this.read_write_spr(spr, reg, is_write_to_spr) {
Ok(()) => {
connect(this.is_illegal, false);
}
Err(()) => {
// self.is_illegal is already set to true
}
}
}
}
}
match self.mnemonic {
"mtspr" => self.decode_scope(|this, (FieldSpr(spr), FieldRS(rs))| {
do_read_write_spr(this, spr, rs, true)
}),
"mfspr" => self.decode_scope(|this, (FieldRT(rt), FieldSpr(spr))| {
do_read_write_spr(this, spr, rt, false)
}),
"mftb" => self.decode_scope(|this, (FieldRT(rt), FieldTbr(tbr))| {
do_read_write_spr(this, tbr, rt, false)
}),
_ => unreachable!(),
}
}
/// for `mcrxrx`
#[hdl]
fn decode_mcrxrx(&mut self) {
@ -2572,12 +2681,12 @@ const DECODE_FNS: &[(&[&str], DecodeFn)] = &[
// TODO(FP) -- mostly intentionally not implemented
},
),
(
&["mtspr", "mfspr", "mftb", "mtmsr", "mtmsrd", "mfmsr"],
|_state| {
// TODO
},
),
(&["mtspr", "mfspr", "mftb"], |state| {
DecodeState::decode_mtspr_mfspr_mftb(state)
}),
(&["mtmsr", "mtmsrd", "mfmsr"], |_state| {
// TODO
}),
(&["mcrxrx"], |state| DecodeState::decode_mcrxrx(state)),
(
&[

View file

@ -2239,6 +2239,80 @@ impl<DestReg: Type, SrcRegWidth: Size> BranchMOp<DestReg, SrcRegWidth, ConstUsiz
}
}
#[hdl]
pub enum ReadSpecialMOpImm {
PowerIsaTimeBase,
PowerIsaTimeBaseU,
}
impl HdlPartialEqImpl<Self> for ReadSpecialMOpImm {
#[track_caller]
fn cmp_value_eq(
lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
rhs: Self,
rhs_value: Cow<'_, Self::SimValue>,
) -> bool {
SimValue::opaque(&SimValue::from_value(lhs, lhs_value.into_owned()))
== SimValue::opaque(&SimValue::from_value(rhs, rhs_value.into_owned()))
}
#[track_caller]
fn cmp_sim_value_eq(
lhs: Cow<'_, SimValue<Self>>,
rhs: Cow<'_, SimValue<Self>>,
) -> SimValue<Bool> {
(SimValue::opaque(&lhs) == SimValue::opaque(&rhs)).to_sim_value()
}
#[track_caller]
fn cmp_sim_value_ne(
lhs: Cow<'_, SimValue<Self>>,
rhs: Cow<'_, SimValue<Self>>,
) -> SimValue<Bool> {
(SimValue::opaque(&lhs) != SimValue::opaque(&rhs)).to_sim_value()
}
#[track_caller]
fn cmp_expr_eq(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
lhs.cast_to_bits().cmp_eq(rhs.cast_to_bits())
}
}
common_mop_struct! {
#[mapped(<NewDestReg, NewSrcRegWidth> ReadSpecialMOp<NewDestReg, NewSrcRegWidth>)]
#[hdl(cmp_eq)]
pub struct ReadSpecialMOp<DestReg: Type, SrcRegWidth: Size> {
#[common]
pub common: CommonMOp<ConstUsize<0>, DestReg, SrcRegWidth, ConstUsize<0>, ReadSpecialMOpImm>,
}
}
impl<DestReg: Type, SrcRegWidth: Size> ReadSpecialMOp<DestReg, SrcRegWidth> {
#[hdl]
pub fn read_special<Target: MOpTrait>(
dest: impl ToExpr<Type = DestReg>,
src: impl ToExpr<Type = Array<UIntType<SrcRegWidth>, 0>>,
imm: impl ToExpr<Type = ReadSpecialMOpImm>,
) -> Expr<Target>
where
Self: MOpInto<Target>,
{
MOpInto::mop_into(
#[hdl]
ReadSpecialMOp {
common: #[hdl]
CommonMOp {
prefix_pad: 0_hdl_u0,
dest,
src,
imm,
},
},
)
}
}
mop_enum! {
#[impl_mop_into = true]
#[hdl]
@ -2253,6 +2327,7 @@ mop_enum! {
CompareI(CompareMOp<DestReg, SrcRegWidth, ConstUsize<1>>),
Branch(BranchMOp<DestReg, SrcRegWidth, ConstUsize<3>>),
BranchI(BranchMOp<DestReg, SrcRegWidth, ConstUsize<2>>),
ReadSpecial(ReadSpecialMOp<DestReg, SrcRegWidth>),
}
}

View file

@ -221,3 +221,238 @@ impl MOpRegNum {
}
}
}
#[hdl(cmp_eq)]
pub struct PowerIsaSpr {
pub num: UInt<10>,
}
macro_rules! make_spr_enum {
(
$(#[$enum_meta:meta])*
$enum_vis:vis enum $PowerIsaSprEnum:ident {
$($enum_body:tt)*
}
) => {
$(#[$enum_meta])*
$enum_vis enum $PowerIsaSprEnum {
$($enum_body)*
Unknown(u16),
}
make_spr_enum! {
@impl
$enum_vis enum $PowerIsaSprEnum {
$($enum_body)*
}
}
};
(
@impl
$enum_vis:vis enum $PowerIsaSprEnum:ident {
$(
$(#[$variant_meta:meta])*
$Variant:ident = $value:literal,
)+
}
) => {
impl $PowerIsaSprEnum {
pub const VARIANTS: &[Self; 1 << 10] = &{
let mut retval = [Self::Unknown(0); 1 << 10];
let mut i = 0;
while i < retval.len() {
retval[i] = Self::Unknown(i as u16);
i += 1;
}
let mut last_value = None;
#[track_caller]
const fn add_variant(
values: &mut [$PowerIsaSprEnum; 1 << 10],
last_value: &mut Option<u16>,
variant: $PowerIsaSprEnum,
value: u16,
) {
assert!(value < 1 << 10, "variant value out of range");
if let Some(last_value) = *last_value {
assert!(last_value < value, "variants must be in ascending order with no duplicates");
}
*last_value = Some(value);
values[value as usize] = variant;
}
$(add_variant(&mut retval, &mut last_value, Self::$Variant, $value);)+
retval
};
pub const fn value(self) -> u16 {
match self {
$(Self::$Variant => $value,)+
Self::Unknown(v) => v,
}
}
}
};
}
make_spr_enum! {
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
#[repr(u16)]
pub enum PowerIsaSprEnum {
Xer = 1,
UserDscr = 3,
Lr = 8,
Ctr = 9,
UserAmr = 13,
Dscr = 17,
Dsisr = 18,
Dar = 19,
Dec = 22,
Srr0 = 26,
Srr1 = 27,
Cfar = 28,
Amr = 29,
Pidr = 48,
Iamr = 61,
ReadCtrl = 136,
WriteCtrl = 152,
Fscr = 153,
Uamor = 157,
Pspb = 159,
Dpdes = 176,
Dawr0 = 180,
Dawr1 = 181,
Rpr = 186,
Ciabr = 187,
Dawrx0 = 188,
Dawrx1 = 189,
Hfscr = 190,
Vrsave = 256,
UserSprg3 = 259,
Tb = 268,
Tbu = 269,
Sprg0 = 272,
Sprg1 = 273,
Sprg2 = 274,
Sprg3 = 275,
Tbl = 284,
WriteTbu = 285,
Tbu40 = 286,
Pvr = 287,
Hsprg0 = 304,
Hsprg1 = 305,
Hdsisr = 306,
Hdar = 307,
Spurr = 308,
Purr = 309,
Hdec = 310,
Hrmor = 313,
Hsrr0 = 314,
Hsrr1 = 315,
Lpcr = 318,
Lpidr = 319,
Hmer = 336,
Hmeer = 337,
Pcr = 338,
Heir = 339,
Amor = 349,
Tir = 446,
UserHdexcr = 455,
Ptcr = 464,
Hashkeyr = 468,
Hashpkeyr = 469,
Hdexcr = 471,
Usprg0 = 496,
Usprg1 = 497,
Urmor = 505,
Usrr0 = 506,
Usrr1 = 507,
Smfctrl = 511,
UserSier2 = 736,
UserSier3 = 737,
UserMmcr3 = 738,
Sier2 = 752,
Sier3 = 753,
Mmcr3 = 754,
UserSier = 768,
Mmcr2 = 769,
Mmcra = 770,
Pmc1 = 771,
Pmc2 = 772,
Pmc3 = 773,
Pmc4 = 774,
Pmc5 = 775,
Pmc6 = 776,
Mmcr0 = 779,
Siar = 780,
Sdar = 781,
Mmcr1 = 782,
Sier = 784,
PrivMmcr2 = 785,
PrivMmcra = 786,
PrivPmc1 = 787,
PrivPmc2 = 788,
PrivPmc3 = 789,
PrivPmc4 = 790,
PrivPmc5 = 791,
PrivPmc6 = 792,
PrivMmcr0 = 795,
PrivSiar = 796,
PrivSdar = 797,
PrivMmcr1 = 798,
Bescrs15 = 800,
Bescrsu16 = 801,
Bescrr15 = 802,
Bescrru16 = 803,
Ebbhr = 804,
Ebbrr = 805,
Bescr = 806,
Reserved808 = 808,
Reserved809 = 809,
Reserved810 = 810,
Reserved811 = 811,
UserDexcr = 812,
Tar = 815,
Asdr = 816,
Psscr = 823,
Dexcr = 828,
Ic = 848,
Vtb = 849,
HyperPsscr = 855,
Ppr = 896,
Ppr32 = 898,
Pir = 1023,
}
}
impl ValueType for PowerIsaSprEnum {
type Type = PowerIsaSpr;
type ValueCategory = fayalite::expr::value_category::ValueCategoryValue;
fn ty(&self) -> Self::Type {
PowerIsaSpr
}
}
impl ToExpr for PowerIsaSprEnum {
#[hdl]
fn to_expr(&self) -> Expr<Self::Type> {
#[hdl]
PowerIsaSpr {
num: self.value().cast_to_static::<UInt<_>>(),
}
}
}
impl ToSimValueWithType<PowerIsaSpr> for PowerIsaSprEnum {
fn to_sim_value_with_type(&self, _ty: PowerIsaSpr) -> SimValue<PowerIsaSpr> {
self.to_sim_value()
}
}
impl ToSimValue for PowerIsaSprEnum {
#[hdl]
fn to_sim_value(&self) -> SimValue<Self::Type> {
#[hdl(sim)]
PowerIsaSpr {
num: self.value().cast_to_static::<UInt<_>>(),
}
}
}

View file

@ -5,8 +5,8 @@ use crate::{
config::CpuConfig,
instruction::{
AddSubMOp, AluBranchMOp, AluCommonMOp, BranchMOp, COMMON_MOP_SRC_LEN, CommonMOpDefaultImm,
CompareMOp, LogicalFlagsMOp, LogicalMOp, MOpTrait, OutputIntegerMode, RenamedMOp,
ShiftRotateMOp, UnitOutRegNum,
CompareMOp, LogicalFlagsMOp, LogicalMOp, MOpTrait, OutputIntegerMode, ReadSpecialMOp,
RenamedMOp, ShiftRotateMOp, UnitOutRegNum,
},
register::{
FlagsMode, PRegFlagsPowerISA, PRegFlagsPowerISAView, PRegFlagsViewTrait, PRegFlagsX86,
@ -328,6 +328,21 @@ fn branch<SrcCount: KnownSize>(
}
}
#[hdl]
fn read_special(
mop: Expr<ReadSpecialMOp<UnitOutRegNum<DynSize>, DynSize>>,
pc: Expr<UInt<64>>,
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_module]
pub fn alu_branch(config: &CpuConfig, unit_index: usize) {
#[hdl]
@ -541,6 +556,24 @@ pub fn alu_branch(config: &CpuConfig, unit_index: usize) {
},
),
),
AluBranchMOp::<_, _>::ReadSpecial(mop) => connect(
unit_base.execute_end,
HdlSome(
#[hdl]
ExecuteEnd::<_, _> {
unit_output: #[hdl]
UnitOutput::<_, _> {
which: MOpTrait::dest_reg(mop),
result: UnitResult[()].Completed(read_special(
mop,
pc,
global_state.flags_mode,
src_values,
)),
},
},
),
),
}
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -151,5 +151,8 @@ pub fn test_cases() -> Vec<TestCase> {
&mut retval,
);
prefixed_no_operation::test_cases_book_i_3_3_20_prefixed_no_operation(&mut retval);
move_to_from_system_register::test_cases_book_iii_5_4_4_move_to_from_system_register(
&mut retval,
);
retval
}

View file

@ -2,11 +2,15 @@
// See Notices.txt for copyright information
use crate::test_cases::{TestCase, insn_single};
use cpu::instruction::{LogicalFlagsMOp, LogicalFlagsMOpImm, Lut4, MOpDestReg, MOpRegNum};
use cpu::instruction::{
LogicalFlagsMOp, LogicalFlagsMOpImm, Lut4, MOpDestReg, MOpRegNum, MoveRegMOp, ReadSpecialMOp,
ReadSpecialMOpImm,
};
use fayalite::prelude::*;
/// covers instructions in PowerISA v3.1C Book I 3.3.19 Move To/From System Register Instructions
pub fn test_cases_book_i_3_3_19_move_to_from_system_register(retval: &mut Vec<TestCase>) {
// mfspr/mtspr are covered by test_cases_book_iii_5_4_4_move_to_from_system_register
#[hdl]
fn mcrxrx_imm() -> SimValue<LogicalFlagsMOpImm> {
#[hdl(sim)]
@ -35,3 +39,111 @@ pub fn test_cases_book_i_3_3_19_move_to_from_system_register(retval: &mut Vec<Te
),
));
}
/// covers instructions in PowerISA v3.1C Book III 5.4.4 Move To/From System Register Instructions
pub fn test_cases_book_iii_5_4_4_move_to_from_system_register(retval: &mut Vec<TestCase>) {
retval.push(insn_single(
"mflr 3",
0x7c6802a6,
None,
MoveRegMOp::move_reg(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num(3)], &[]),
[MOpRegNum::power_isa_lr_reg().value],
0.cast_to_static::<SInt<_>>(),
),
));
retval.push(insn_single(
"mtlr 3",
0x7c6803a6,
None,
MoveRegMOp::move_reg(
MOpDestReg::new_sim(&[MOpRegNum::POWER_ISA_LR_REG_NUM], &[]),
[MOpRegNum::power_isa_gpr_reg_imm(3).value],
0.cast_to_static::<SInt<_>>(),
),
));
retval.push(insn_single(
"mfctr 3",
0x7c6902a6,
None,
MoveRegMOp::move_reg(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num(3)], &[]),
[MOpRegNum::power_isa_ctr_reg().value],
0.cast_to_static::<SInt<_>>(),
),
));
retval.push(insn_single(
"mtctr 3",
0x7c6903a6,
None,
MoveRegMOp::move_reg(
MOpDestReg::new_sim(&[MOpRegNum::POWER_ISA_CTR_REG_NUM], &[]),
[MOpRegNum::power_isa_gpr_reg_imm(3).value],
0.cast_to_static::<SInt<_>>(),
),
));
retval.push(insn_single(
"mfspr 3, 815 # mftar 3",
0x7c6fcaa6,
None,
MoveRegMOp::move_reg(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num(3)], &[]),
[MOpRegNum::power_isa_tar_reg().value],
0.cast_to_static::<SInt<_>>(),
),
));
retval.push(insn_single(
"mtspr 815, 3 # mttar 3",
0x7c6fcba6,
None,
MoveRegMOp::move_reg(
MOpDestReg::new_sim(&[MOpRegNum::POWER_ISA_TAR_REG_NUM], &[]),
[MOpRegNum::power_isa_gpr_reg_imm(3).value],
0.cast_to_static::<SInt<_>>(),
),
));
// make sure we generate mfspr and not the phased-out mftb
retval.push(insn_single(
"mfspr 3, 268 # mftb 3",
0x7c6c42a6,
None,
ReadSpecialMOp::read_special(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num(3)], &[]),
[MOpRegNum::const_zero().value; 0],
ReadSpecialMOpImm.PowerIsaTimeBase(),
),
));
// make sure we generate mfspr and not the phased-out mftb
retval.push(insn_single(
"mfspr 3, 269 # mftbu 3",
0x7c6d42a6,
None,
ReadSpecialMOp::read_special(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num(3)], &[]),
[MOpRegNum::const_zero().value; 0],
ReadSpecialMOpImm.PowerIsaTimeBaseU(),
),
));
// phased-out mftb -- not actually generated by the assembler so we have to use .long
retval.push(insn_single(
".long 0x7c6c42e6 # mftb 3, 268",
0x7c6c42e6,
None,
ReadSpecialMOp::read_special(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num(3)], &[]),
[MOpRegNum::const_zero().value; 0],
ReadSpecialMOpImm.PowerIsaTimeBase(),
),
));
// phased-out mftb -- not actually generated by the assembler so we have to use .long
retval.push(insn_single(
".long 0x7c6d42e6 # mftb 3, 269",
0x7c6d42e6,
None,
ReadSpecialMOp::read_special(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num(3)], &[]),
[MOpRegNum::const_zero().value; 0],
ReadSpecialMOpImm.PowerIsaTimeBaseU(),
),
));
}