add branch instructions, no tests yet
All checks were successful
/ test (pull_request) Successful in 27m31s

This commit is contained in:
Jacob Lifshay 2026-01-19 17:19:38 -08:00
parent 1fc56e02f9
commit 2e05329c36
Signed by: programmerjake
SSH key fingerprint: SHA256:HnFTLGpSm4Q4Fj502oCFisjZSoakwEuTsJJMSke63RQ
6 changed files with 84886 additions and 13307 deletions

View file

@ -4,8 +4,8 @@
use crate::{
config::CpuConfig,
instruction::{
AddSubMOp, CompareMOp, CompareMode, LogicalMOp, MOp, MOpDestReg, MOpRegNum, MoveRegMOp,
OutputIntegerMode,
AddSubMOp, BranchMOp, CompareMOp, CompareMode, ConditionMode, LogicalMOp, MOp, MOpDestReg,
MOpRegNum, MoveRegMOp, OutputIntegerMode,
},
powerisa_instructions_xml::{
InstructionBitFieldName, InstructionBitFieldsInner, Instructions, TextLineItem,
@ -179,6 +179,8 @@ macro_rules! impl_fields {
impl_fields! {
#[name = "BF"]
struct FieldBF(FieldCrf);
#[name = "BI"]
struct FieldBI(FieldCrBit);
#[name = "RA"]
struct FieldRA(FieldGpr);
#[name = "RB"]
@ -187,6 +189,10 @@ impl_fields! {
struct FieldRS(FieldGpr);
#[name = "RT"]
struct FieldRT(FieldGpr);
#[name = "BD"]
struct FieldBD(SInt<14>);
#[name = "LI"]
struct FieldLI(SInt<24>);
#[name = "SI"]
struct FieldSI(SInt<16>);
#[name = "si0"]
@ -201,8 +207,16 @@ impl_fields! {
struct FieldAddPcISD1(UInt<5>);
#[name = "d2"]
struct FieldAddPcISD2(UInt<1>);
#[name = "BH"]
struct FieldBH(UInt<2>);
#[name = "BO"]
struct FieldBO(UInt<5>);
#[name = "AA"]
struct FieldAA(Bool);
#[name = "L"]
struct FieldL(Bool);
#[name = "LK"]
struct FieldLK(Bool);
#[name = "OE"]
struct FieldOE(Bool);
#[name = "R"]
@ -223,6 +237,12 @@ struct FieldCrf {
reg_num: UInt<3>,
}
/// condition register bit
#[hdl]
struct FieldCrBit {
bit_num: UInt<5>,
}
fn gpr(this: impl ToExpr<Type = FieldGpr>) -> Expr<MOpRegNum> {
MOpRegNum::power_isa_gpr_reg(this.to_expr().reg_num)
}
@ -235,6 +255,26 @@ fn crf(this: impl ToExpr<Type = FieldCrf>) -> Expr<MOpRegNum> {
MOpRegNum::power_isa_cr_reg(this.to_expr().reg_num)
}
#[hdl]
fn cr_bit(cr_bit: impl ToExpr<Type = FieldCrBit>) -> (Expr<MOpRegNum>, Expr<ConditionMode>) {
let cr_bit = cr_bit.to_expr();
#[hdl]
let condition_mode = wire();
let field_bit = cr_bit.bit_num.cast_to_static::<UInt<2>>();
let field_num = (cr_bit.bit_num >> 2).cast_to_static::<UInt<3>>();
#[hdl]
if field_bit.cmp_eq(0_hdl_u2) {
connect(condition_mode, ConditionMode.SLt());
} else if field_bit.cmp_eq(1_hdl_u2) {
connect(condition_mode, ConditionMode.SGt());
} else if field_bit.cmp_eq(2_hdl_u2) {
connect(condition_mode, ConditionMode.Eq());
} else {
connect(condition_mode, ConditionMode.Overflow());
}
(MOpRegNum::power_isa_cr_reg(field_num), condition_mode)
}
impl DecodeState {
fn form(&self) -> &'static str {
let mut title_words = self
@ -439,7 +479,160 @@ impl DecodeState {
}
}
fn decode_b_ba_bl_bla(&mut self) {
// TODO
self.decode_scope(|this, (FieldLI(li), FieldAA(aa), FieldLK(lk))| {
connect(
ArrayVec::len(this.output),
1usize.cast_to_static::<Length<_>>(),
);
connect(
this.output[0],
BranchMOp::branch_i(
MOpDestReg::new(
[if this.mnemonic.contains('l') {
MOpRegNum::power_isa_lr_reg()
} else {
MOpRegNum::const_zero()
}],
[],
),
MOpRegNum::const_zero().value,
(li << 2).cast_to_static(),
!aa,
lk,
false,
),
);
});
}
#[hdl]
fn decode_bc_bclr_bcctr_bctar(&mut self) {
let addr_reg = match self.mnemonic {
"bc" | "bca" | "bcl" | "bcla" => None,
"bclr" | "bclrl" => Some(MOpRegNum::power_isa_lr_reg()),
"bcctr" | "bcctrl" => Some(MOpRegNum::power_isa_ctr_reg()),
"bctar" | "bctarl" => Some(MOpRegNum::power_isa_tar_reg()),
_ => unreachable!(),
};
let body = |this: &mut DecodeState,
bo: Expr<UInt<5>>,
bi: Expr<FieldCrBit>,
is_ret: Expr<Bool>,
bd: Option<Expr<SInt<14>>>,
aa: Option<Expr<Bool>>,
lk: Expr<Bool>| {
let use_eq_for_ctr_compare = bo[1]; // BO_3 in specification
let no_ctr = bo[2]; // BO_2 in specification
let expected_cr_bit_value = bo[3]; // BO_1 in specification
let no_cr_bit = bo[4]; // BO_0 in specification
let (cr_field, condition_mode) = cr_bit(bi);
#[hdl]
let branch_mop = wire();
#[hdl]
let branch_lr_dest_reg = wire();
connect(branch_lr_dest_reg, MOpRegNum::const_zero());
#[hdl]
if lk {
connect(branch_lr_dest_reg, MOpRegNum::power_isa_lr_reg());
}
#[hdl]
let branch_ctr_reg: MOpRegNum = wire();
let dest = MOpDestReg::new([branch_lr_dest_reg], []);
let src1 = addr_reg.unwrap_or_else(|| MOpRegNum::const_zero()).value;
let imm = (bd.unwrap_or(0_hdl_i14) << 2).cast_to_static();
let invert_src2_eq_zero = !use_eq_for_ctr_compare;
let pc_relative = match aa {
Some(aa) => !aa,
None => addr_reg.is_none().to_expr(),
};
#[hdl]
if no_cr_bit {
connect(
branch_mop,
BranchMOp::branch_ctr(
dest,
src1,
branch_ctr_reg.value,
imm,
invert_src2_eq_zero,
pc_relative,
lk,
is_ret,
),
);
} else {
connect(
branch_mop,
BranchMOp::branch_cond_ctr(
dest,
[cr_field.value, src1, branch_ctr_reg.value],
imm,
!expected_cr_bit_value,
condition_mode,
invert_src2_eq_zero,
pc_relative,
lk,
is_ret,
),
);
}
#[hdl]
if no_ctr {
connect(
ArrayVec::len(this.output),
1usize.cast_to_static::<Length<_>>(),
);
connect(this.output[0], branch_mop);
connect(branch_ctr_reg, MOpRegNum::const_zero());
} else {
connect(
ArrayVec::len(this.output),
1usize.cast_to_static::<Length<_>>(),
);
connect(
this.output[0],
AddSubMOp::add_sub_i(
MOpDestReg::new([MOpRegNum::power_isa_ctr_reg()], []),
[
MOpRegNum::power_isa_ctr_reg().value,
MOpRegNum::const_zero().value,
],
(-1).cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
false,
false,
false,
false,
),
);
connect(this.output[1], branch_mop);
connect(branch_ctr_reg, MOpRegNum::power_isa_ctr_reg());
}
};
if addr_reg.is_some() {
self.decode_scope(
|this, (FieldBO(bo), FieldBI(bi), FieldBH(bh), FieldLK(lk))| {
body(
this,
bo,
bi,
if this.mnemonic.starts_with("bclr") {
bh.cmp_eq(0u8)
} else {
false.to_expr()
},
None,
None,
lk,
)
},
);
} else {
self.decode_scope(
|this, (FieldBO(bo), FieldBI(bi), FieldBD(bd), FieldAA(aa), FieldLK(lk))| {
body(this, bo, bi, false.to_expr(), Some(bd), Some(aa), lk)
},
);
}
}
#[hdl]
fn decode_addi_paddi(&mut self) {
@ -1165,18 +1358,12 @@ type DecodeFn = fn(&mut DecodeState);
const DECODE_FNS: &[(&[&str], DecodeFn)] = &[
(&["b", "ba", "bl", "bla"], DecodeState::decode_b_ba_bl_bla),
(&["bc", "bca", "bcl", "bcla"], |_state| {
// TODO
}),
(&["bclr", "bclrl"], |_state| {
// TODO
}),
(&["bcctr", "bcctrl"], |_state| {
// TODO
}),
(&["bctar", "bctarl"], |_state| {
// TODO
}),
(
&[
"bc", "bca", "bcl", "bcla", "bclr", "bclrl", "bcctr", "bcctrl", "bctar", "bctarl",
],
DecodeState::decode_bc_bclr_bcctr_bctar,
),
(
&[
"crand", "crnand", "cror", "crxor", "crnor", "creqv", "crandc", "crorc",

View file

@ -3,7 +3,7 @@
use crate::{unit::UnitMOp, util::range_u32_len};
use fayalite::{
expr::{HdlPartialEqImpl, ops::ArrayLiteral},
intern::Interned,
intern::{Intern, InternSlice, Interned},
module::wire_with_loc,
prelude::*,
ty::StaticType,
@ -1101,13 +1101,195 @@ impl<DestReg: Type, SrcRegWidth: Size> CompareMOp<DestReg, SrcRegWidth, ConstUsi
}
}
#[hdl]
pub enum ConditionMode {
Eq,
ULt,
UGt,
SLt,
SGt,
Sign,
Overflow,
Parity,
}
impl HdlPartialEqImpl<Self> for ConditionMode {
#[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> BranchMOp<NewDestReg, NewSrcRegWidth>)]
#[mapped(<NewDestReg, NewSrcRegWidth> BranchMOp<NewDestReg, NewSrcRegWidth, SrcCount>)]
#[hdl(cmp_eq)]
pub struct BranchMOp<DestReg: Type, SrcRegWidth: Size> {
/// `src0` is the value used for reading flags from.
/// `src1 + imm + if pc_relative { pc } else { 0 }` is the target address.
/// `src2` (if present) is the counter to compare against zero.
/// The branch is taken only if all of `src2` (if present) and `src0`'s conditions pass
/// The output value is the next instruction's address used for a return address when this is a call.
pub struct BranchMOp<DestReg: Type, SrcRegWidth: Size, SrcCount: KnownSize> {
#[common]
pub alu_common: AluCommonMOp<DestReg, SrcRegWidth, ConstUsize<2>>,
pub lut: UInt<4>,
pub common: CommonMOp<ConstUsize<0>, DestReg, SrcRegWidth, SrcCount>,
pub invert_src0_cond: Bool,
pub src0_cond_mode: ConditionMode,
/// `src2`'s condition passes if `src2`'s value `== 0`.
/// However, if `invert_src2_eq_zero` is set, then the comparison is instead `!= 0`.
pub invert_src2_eq_zero: Bool,
pub pc_relative: Bool,
pub is_call: Bool,
pub is_ret: Bool,
}
}
impl<DestReg: Type, SrcRegWidth: Size> BranchMOp<DestReg, SrcRegWidth, ConstUsize<2>> {
#[hdl]
pub fn branch_cond_i<Target: MOpTrait>(
dest: impl ToExpr<Type = DestReg>,
src: impl ToExpr<Type = Array<UIntType<SrcRegWidth>, 2>>,
imm: impl ToExpr<Type = SInt<{ COMMON_MOP_2_IMM_WIDTH }>>,
invert_src0_cond: impl ToExpr<Type = Bool>,
src0_cond_mode: impl ToExpr<Type = ConditionMode>,
pc_relative: impl ToExpr<Type = Bool>,
is_call: impl ToExpr<Type = Bool>,
is_ret: impl ToExpr<Type = Bool>,
) -> Expr<Target>
where
Self: MOpInto<Target>,
{
MOpInto::mop_into(
#[hdl]
BranchMOp {
common: CommonMOp::new(0_hdl_u0, dest, src, Expr::as_dyn_int(imm.to_expr())),
invert_src0_cond,
src0_cond_mode,
invert_src2_eq_zero: false,
pc_relative,
is_call,
is_ret,
},
)
}
#[hdl]
pub fn branch_i<Target: MOpTrait>(
dest: impl ToExpr<Type = DestReg>,
src1: impl ToExpr<Type = UIntType<SrcRegWidth>>,
imm: impl ToExpr<Type = SInt<{ COMMON_MOP_2_IMM_WIDTH }>>,
pc_relative: impl ToExpr<Type = Bool>,
is_call: impl ToExpr<Type = Bool>,
is_ret: impl ToExpr<Type = Bool>,
) -> Expr<Target>
where
Self: MOpInto<Target>,
{
let src1 = src1.to_expr();
Self::branch_cond_i(
dest,
ArrayLiteral::new(
src1.ty(),
[src1.ty().zero().to_expr(), src1]
.into_iter()
.map(Expr::canonical)
.collect(),
),
imm,
true,
ConditionMode.ULt(),
pc_relative,
is_call,
is_ret,
)
}
}
impl<DestReg: Type, SrcRegWidth: Size> BranchMOp<DestReg, SrcRegWidth, ConstUsize<3>> {
#[hdl]
pub fn branch_cond_ctr<Target: MOpTrait>(
dest: impl ToExpr<Type = DestReg>,
src: impl ToExpr<Type = Array<UIntType<SrcRegWidth>, 3>>,
imm: impl ToExpr<Type = SInt<{ COMMON_MOP_3_IMM_WIDTH }>>,
invert_src0_cond: impl ToExpr<Type = Bool>,
src0_cond_mode: impl ToExpr<Type = ConditionMode>,
invert_src2_eq_zero: impl ToExpr<Type = Bool>,
pc_relative: impl ToExpr<Type = Bool>,
is_call: impl ToExpr<Type = Bool>,
is_ret: impl ToExpr<Type = Bool>,
) -> Expr<Target>
where
Self: MOpInto<Target>,
{
MOpInto::mop_into(
#[hdl]
BranchMOp {
common: CommonMOp::new(0_hdl_u0, dest, src, Expr::as_dyn_int(imm.to_expr())),
invert_src0_cond,
src0_cond_mode,
invert_src2_eq_zero,
pc_relative,
is_call,
is_ret,
},
)
}
#[hdl]
pub fn branch_ctr<Target: MOpTrait>(
dest: impl ToExpr<Type = DestReg>,
src1: impl ToExpr<Type = UIntType<SrcRegWidth>>,
src2: impl ToExpr<Type = UIntType<SrcRegWidth>>,
imm: impl ToExpr<Type = SInt<{ COMMON_MOP_3_IMM_WIDTH }>>,
invert_src2_eq_zero: impl ToExpr<Type = Bool>,
pc_relative: impl ToExpr<Type = Bool>,
is_call: impl ToExpr<Type = Bool>,
is_ret: impl ToExpr<Type = Bool>,
) -> Expr<Target>
where
Self: MOpInto<Target>,
{
let src1 = src1.to_expr();
Self::branch_cond_ctr(
dest,
ArrayLiteral::new(
src1.ty(),
[src1.ty().zero().to_expr(), src1, src2.to_expr()]
.into_iter()
.map(Expr::canonical)
.collect(),
),
imm,
true,
ConditionMode.ULt(),
invert_src2_eq_zero,
pc_relative,
is_call,
is_ret,
)
}
}
@ -1121,6 +1303,8 @@ mop_enum! {
LogicalI(LogicalMOp<DestReg, SrcRegWidth, ConstUsize<1>>),
Compare(CompareMOp<DestReg, SrcRegWidth, ConstUsize<2>>),
CompareI(CompareMOp<DestReg, SrcRegWidth, ConstUsize<1>>),
Branch(BranchMOp<DestReg, SrcRegWidth, ConstUsize<3>>),
BranchI(BranchMOp<DestReg, SrcRegWidth, ConstUsize<2>>),
}
}

View file

@ -27,8 +27,29 @@ pub struct PowerIsaCrBitNum {
impl MOpRegNum {
pub const POWER_ISA_LR_REG_NUM: u32 = 1;
#[hdl]
pub fn power_isa_lr_reg() -> Expr<Self> {
#[hdl]
Self {
value: Self::POWER_ISA_LR_REG_NUM.cast_to_static::<UInt<_>>(),
}
}
pub const POWER_ISA_CTR_REG_NUM: u32 = 2;
#[hdl]
pub fn power_isa_ctr_reg() -> Expr<Self> {
#[hdl]
Self {
value: Self::POWER_ISA_CTR_REG_NUM.cast_to_static::<UInt<_>>(),
}
}
pub const POWER_ISA_TAR_REG_NUM: u32 = 3;
#[hdl]
pub fn power_isa_tar_reg() -> Expr<Self> {
#[hdl]
Self {
value: Self::POWER_ISA_TAR_REG_NUM.cast_to_static::<UInt<_>>(),
}
}
/// SO, OV, and OV32 XER bits -- in [`PRegValue.flags`]
///

View file

@ -4,8 +4,8 @@
use crate::{
config::CpuConfig,
instruction::{
AddSubMOp, AluBranchMOp, AluCommonMOp, COMMON_MOP_SRC_LEN, CommonMOp, CompareMOp,
LogicalMOp, MOpTrait, OutputIntegerMode, RenamedMOp, UnitOutRegNum,
AddSubMOp, AluBranchMOp, AluCommonMOp, BranchMOp, COMMON_MOP_SRC_LEN, CommonMOp,
CompareMOp, LogicalMOp, MOpTrait, OutputIntegerMode, RenamedMOp, UnitOutRegNum,
},
register::{FlagsMode, PRegFlagsPowerISA, PRegFlagsX86, PRegValue},
unit::{
@ -272,6 +272,21 @@ fn compare<SrcCount: KnownSize>(
}
}
#[hdl]
fn branch<SrcCount: KnownSize>(
mop: Expr<BranchMOp<UnitOutRegNum<DynSize>, DynSize, SrcCount>>,
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]
@ -415,6 +430,42 @@ pub fn alu_branch(config: &CpuConfig, unit_index: usize) {
},
),
),
AluBranchMOp::<_, _>::Branch(mop) => connect(
unit_base.execute_end,
HdlSome(
#[hdl]
ExecuteEnd::<_, _> {
unit_output: #[hdl]
UnitOutput::<_, _> {
which: MOpTrait::dest_reg(mop),
result: UnitResult[()].Completed(branch(
mop,
pc,
global_state.flags_mode,
src_values,
)),
},
},
),
),
AluBranchMOp::<_, _>::BranchI(mop) => connect(
unit_base.execute_end,
HdlSome(
#[hdl]
ExecuteEnd::<_, _> {
unit_output: #[hdl]
UnitOutput::<_, _> {
which: MOpTrait::dest_reg(mop),
result: UnitResult[()].Completed(branch(
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