// SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information use crate::{unit::UnitMOp, util::range_u32_len}; use fayalite::{ expr::ops::{ArrayLiteral, ExprPartialEq}, intern::Interned, prelude::*, }; use std::{fmt, marker::PhantomData, ops::Range}; pub mod power_isa; pub trait MOpInto: MOpTrait { fn mop_into_ty(self) -> Target; fn mop_into(this: Expr) -> Expr; } impl MOpInto for T { fn mop_into_ty(self) -> T { self } fn mop_into(this: Expr) -> Expr { this } } pub trait MOpTrait: Type { type Mapped: MOpTrait; type DestReg: Type; type SrcRegWidth: Size; fn dest_reg_ty(self) -> Self::DestReg; fn dest_reg(input: impl ToExpr) -> Expr; fn src_reg_width(self) -> ::SizeType; fn src_reg_ty(self) -> UIntType { UInt[self.src_reg_width()] } fn src_regs_ty(self) -> Array, { COMMON_MOP_SRC_LEN }> { Array[self.src_reg_ty()][ConstUsize::<{ COMMON_MOP_SRC_LEN }>] } fn for_each_src_reg( input: impl ToExpr, f: &mut impl FnMut(Expr>, usize), ); fn connect_src_regs( input: impl ToExpr, src_regs: impl ToExpr, { COMMON_MOP_SRC_LEN }>>, ) { let src_regs = src_regs.to_expr(); Self::for_each_src_reg(input.to_expr(), &mut |src_reg, index| { connect(src_regs[index], src_reg); }); } fn mapped_ty( self, new_dest_reg: NewDestReg, new_src_reg_width: NewSrcRegWidth::SizeType, ) -> Self::Mapped; fn map_regs( input: impl ToExpr, new_dest: impl ToExpr, new_src_reg_width: NewSrcRegWidth::SizeType, map_src: &mut impl FnMut( Expr>, usize, ) -> Expr>, ) -> Expr>; } pub trait CommonMOpTrait: MOpTrait { type PrefixPad: KnownSize; type SrcCount: KnownSize; type CommonMOpTraitMapped: CommonMOpTrait< DestReg = NewDestReg, SrcRegWidth = NewSrcRegWidth, PrefixPad = Self::PrefixPad, SrcCount = Self::SrcCount, >; type CommonMOpTraitDestReg: Type; type CommonMOpTraitSrcRegWidth: Size; fn common_mop_ty( self, ) -> CommonMOp; fn common_mop( input: impl ToExpr, ) -> Expr>; fn with_common_mop_ty( self, new_common_mop_ty: CommonMOp, ) -> Self::Mapped; fn with_common_mop( input: impl ToExpr, new_common_mop: impl ToExpr< Type = CommonMOp, >, ) -> Expr>; } impl MOpTrait for T { type Mapped = T::CommonMOpTraitMapped; type DestReg = T::CommonMOpTraitDestReg; type SrcRegWidth = T::CommonMOpTraitSrcRegWidth; fn dest_reg_ty(self) -> Self::DestReg { self.common_mop_ty().dest } fn dest_reg(input: impl ToExpr) -> Expr { T::common_mop(input).dest } fn src_reg_width(self) -> ::SizeType { self.common_mop_ty().src.element().width } fn for_each_src_reg( input: impl ToExpr, f: &mut impl FnMut(Expr>, usize), ) { let input = input.to_expr(); let common = T::common_mop(input); for index in 0..T::SrcCount::VALUE { f(common.src[index], index); } } fn mapped_ty( self, new_dest_reg: NewDestReg, new_src_reg_width: NewSrcRegWidth::SizeType, ) -> Self::Mapped { self.with_common_mop_ty( CommonMOp[T::PrefixPad::SIZE][new_dest_reg][new_src_reg_width][T::SrcCount::SIZE], ) } fn map_regs( input: impl ToExpr, new_dest: impl ToExpr, new_src_reg_width: NewSrcRegWidth::SizeType, map_src: &mut impl FnMut( Expr>, usize, ) -> Expr>, ) -> Expr> { let input = input.to_expr(); let common = T::common_mop(input); let new_dest = new_dest.to_expr(); T::with_common_mop( input, CommonMOp::new( common.prefix_pad, new_dest, ArrayLiteral::new( UIntType[new_src_reg_width], Interned::from_iter( (0..T::SrcCount::VALUE) .map(|index| Expr::canonical(map_src(common.src[index], index))), ), ) .to_expr(), CommonMOp::imm(common), ), ) } } #[hdl] pub enum OutputIntegerMode { Full64, DupLow32, ZeroExt32, SignExt32, ZeroExt16, SignExt16, ZeroExt8, SignExt8, } impl ExprPartialEq for OutputIntegerMode { fn cmp_eq(lhs: Expr, rhs: Expr) -> Expr { lhs.cast_to_bits().cmp_eq(rhs.cast_to_bits()) } fn cmp_ne(lhs: Expr, rhs: Expr) -> Expr { lhs.cast_to_bits().cmp_ne(rhs.cast_to_bits()) } } pub const MOP_IMM_WIDTH: usize = 34; pub const MOP_MIN_REG_WIDTH: usize = 8; pub const COMMON_MOP_SRC_LEN: usize = 3; pub const COMMON_MOP_MIN_SRC_LEN_WITH_FULL_IMM: usize = 2; pub const COMMON_MOP_IMM_LOW_WIDTH: usize = CommonMOpWithMaxSrcCount::IMM_WIDTH - 1; #[hdl(cmp_eq)] pub struct CommonMOp { pub prefix_pad: UIntType, pub dest: DestReg, pub src: Array, { COMMON_MOP_SRC_LEN }>, pub imm_low: UInt<{ COMMON_MOP_IMM_LOW_WIDTH }>, pub imm_sign: SInt<1>, pub _phantom: PhantomData, } impl CommonMOpTrait for CommonMOp { type PrefixPad = PrefixPad; type SrcCount = SrcCount; type CommonMOpTraitMapped = CommonMOp; type CommonMOpTraitDestReg = DestReg; type CommonMOpTraitSrcRegWidth = SrcRegWidth; fn common_mop_ty( self, ) -> CommonMOp { self } fn common_mop( input: impl ToExpr, ) -> Expr> { input.to_expr() } fn with_common_mop_ty( self, new_common_mop_ty: CommonMOp, ) -> Self::Mapped { new_common_mop_ty } fn with_common_mop( input: impl ToExpr, new_common_mop: impl ToExpr< Type = CommonMOp, >, ) -> Expr> { let _ = input.to_expr(); new_common_mop.to_expr() } } #[hdl(cmp_eq)] pub struct CommonMOpImmParts { // fields must be in this exact order pub imm_low: UInt<{ COMMON_MOP_IMM_LOW_WIDTH }>, pub reversed_src: ArrayType, ImmInSrcCount>, pub imm_sign: SInt<1>, } type CommonMOpWithMaxSrcCount = CommonMOpForImm<{ COMMON_MOP_SRC_LEN }>; type CommonMOpForImm = CommonMOp, (), ConstUsize<{ MOP_MIN_REG_WIDTH }>, ConstUsize>; pub const COMMON_MOP_0_IMM_WIDTH: usize = CommonMOpForImm::<0>::IMM_WIDTH; pub const COMMON_MOP_1_IMM_WIDTH: usize = CommonMOpForImm::<1>::IMM_WIDTH; pub const COMMON_MOP_2_IMM_WIDTH: usize = CommonMOpForImm::<2>::IMM_WIDTH; pub const COMMON_MOP_3_IMM_WIDTH: usize = CommonMOpForImm::<3>::IMM_WIDTH; const COMMON_MOP_0_IMM_IN_SRC_COUNT: usize = CommonMOpForImm::<0>::IMM_IN_SRC_COUNT; impl CommonMOp { pub const IMM_IN_SRC_COUNT: usize = { assert!(SrcCount::VALUE <= COMMON_MOP_SRC_LEN, "too many sources"); const _: () = assert!(COMMON_MOP_MIN_SRC_LEN_WITH_FULL_IMM <= COMMON_MOP_SRC_LEN); (COMMON_MOP_SRC_LEN - COMMON_MOP_MIN_SRC_LEN_WITH_FULL_IMM) - SrcCount::VALUE.saturating_sub(COMMON_MOP_MIN_SRC_LEN_WITH_FULL_IMM) }; pub const IMM_IN_SRC_RANGE: Range = (COMMON_MOP_SRC_LEN - Self::IMM_IN_SRC_COUNT)..COMMON_MOP_SRC_LEN; pub const IMM_WIDTH: usize = { MOP_IMM_WIDTH - (COMMON_MOP_0_IMM_IN_SRC_COUNT - Self::IMM_IN_SRC_COUNT) * MOP_MIN_REG_WIDTH }; pub fn imm_ty() -> SInt { SInt::new(Self::IMM_WIDTH) } pub fn imm_parts_ty() -> CommonMOpImmParts { let retval = CommonMOpImmParts[Self::IMM_IN_SRC_COUNT]; assert_eq!( retval.canonical().bit_width(), Self::IMM_WIDTH, "{retval:#?}" ); retval } #[hdl] pub fn new( prefix_pad: impl ToExpr>, dest: impl ToExpr, src: impl ToExpr, SrcCount>>, imm: impl ToExpr, ) -> Expr { let prefix_pad = prefix_pad.to_expr(); let dest = dest.to_expr(); let src_in = src.to_expr(); let imm = imm.to_expr(); assert_eq!(Expr::ty(imm), Self::imm_ty()); let src_reg_ty = Expr::ty(src_in).element(); let imm_parts = imm.cast_to_bits().cast_bits_to(Self::imm_parts_ty()); let mut src = [0_hdl_u0.cast_to(src_reg_ty); COMMON_MOP_SRC_LEN]; for i in 0..SrcCount::VALUE { src[i] = src_in[i]; } for (reversed_src_index, src_index) in Self::IMM_IN_SRC_RANGE.rev().enumerate() { src[src_index] = imm_parts.reversed_src[reversed_src_index].cast_to(src_reg_ty); } #[hdl] Self { prefix_pad, dest, src: ArrayLiteral::new( src_reg_ty, Interned::from_iter(src.iter().map(|v| Expr::canonical(*v))), ) .to_expr(), imm_low: Expr::from_dyn_int(imm[..COMMON_MOP_IMM_LOW_WIDTH]), imm_sign: Expr::from_dyn_int(imm >> (Self::IMM_WIDTH - 1)), _phantom: PhantomData, } } #[hdl] pub fn imm(expr: impl ToExpr) -> Expr { let expr = expr.to_expr(); let reversed_src = Vec::from_iter( Self::IMM_IN_SRC_RANGE .rev() .map(|src_index| expr.src[src_index].cast_to_static()), ); let imm_parts = { #[hdl] CommonMOpImmParts { imm_low: expr.imm_low, reversed_src, imm_sign: expr.imm_sign, } }; imm_parts.cast_to_bits().cast_bits_to(Self::imm_ty()) } #[hdl] pub fn connect_to_imm(expr: impl ToExpr, imm: impl ToExpr) { let expr = expr.to_expr(); let src_reg_ty = Expr::ty(expr).src.element(); let imm = imm.to_expr(); assert_eq!(Expr::ty(imm), Self::imm_ty()); let imm_parts = imm.cast_to_bits().cast_bits_to(Self::imm_parts_ty()); let mut src = [Some(0_hdl_u0.cast_to(src_reg_ty)); COMMON_MOP_SRC_LEN]; for i in 0..SrcCount::VALUE { src[i] = None; } for (reversed_src_index, src_index) in Self::IMM_IN_SRC_RANGE.rev().enumerate() { src[src_index] = Some(imm_parts.reversed_src[reversed_src_index].cast_to(src_reg_ty)); } for i in 0..COMMON_MOP_SRC_LEN { if let Some(v) = src[i] { connect(expr.src[i], v); } } } } macro_rules! common_mop_struct { ( #[mapped(<$NewDestReg:ident, $SrcRegWidth:ident> $mapped_ty:ty)] $(#[$struct_meta:meta])* $vis:vis struct $MOp:ident<$($Generic:ident: $GenericBound:ident),* $(,)?> { #[common] $(#[$common_meta:meta])* $common_vis:vis $common:ident: $common_ty:ty, $( $(#[$field_meta:meta])* $field_vis:vis $field:ident: $field_ty:ty, )* } ) => { $(#[$struct_meta])* $vis struct $MOp<$($Generic: $GenericBound),*> { $(#[$common_meta])* $common_vis $common: $common_ty, $( $(#[$field_meta])* $field_vis $field: $field_ty, )* } impl<$($Generic: $GenericBound),*> CommonMOpTrait for $MOp<$($Generic),*> { type PrefixPad = <$common_ty as CommonMOpTrait>::PrefixPad; type SrcCount = <$common_ty as CommonMOpTrait>::SrcCount; type CommonMOpTraitMapped<$NewDestReg: Type, $SrcRegWidth: Size> = $mapped_ty; type CommonMOpTraitDestReg = <$common_ty as CommonMOpTrait>::CommonMOpTraitDestReg; type CommonMOpTraitSrcRegWidth = <$common_ty as CommonMOpTrait>::CommonMOpTraitSrcRegWidth; fn common_mop_ty( self, ) -> CommonMOp { CommonMOpTrait::common_mop_ty(self.$common) } fn common_mop( input: impl ToExpr, ) -> Expr> { CommonMOpTrait::common_mop(input.to_expr().$common) } fn with_common_mop_ty( self, new_common_mop_ty: CommonMOp, ) -> Self::Mapped { $MOp { $common: CommonMOpTrait::with_common_mop_ty(self.$common, new_common_mop_ty), $($field: self.$field,)* } } #[hdl] fn with_common_mop( input: impl ToExpr, new_common_mop: impl ToExpr< Type = CommonMOp, >, ) -> Expr> { let input = input.to_expr(); #[hdl] $MOp { $common: CommonMOpTrait::with_common_mop(input.$common, new_common_mop), $($field: input.$field,)* } } } }; } macro_rules! mop_enum { ( #[impl_mop_into = $impl_mop_into:tt] $(#[$enum_meta:meta])* $vis:vis enum $MOp:ident< $DestReg:ident: Type, $SrcRegWidth:ident: Size $(, #[MOp(get_ty = $mop_types_get_ty:expr)] $MOpTypes:ident: Type)* $(, #[Size(get_size = $sizes_get_size:expr)] $Sizes:ident: Size)* > { $(#[$($first_variant_meta:tt)*])* $FirstVariant:ident($first_ty:ty), $( $(#[$variant_meta:meta])* $Variant:ident($ty:ty), )* } ) => { $(#[$enum_meta])* $vis enum $MOp<$DestReg: Type, $SrcRegWidth: Size $(, $MOpTypes: Type)* $(, $Sizes: Size)*> { $(#[$first_variant_meta])* $FirstVariant($first_ty), $( $(#[$variant_meta])* $Variant($ty), )* } mop_enum! { @impl_variants #[impl_mop_into = $impl_mop_into] enum $MOp [ $DestReg: Type, $SrcRegWidth: Size $(, #[MOp(get_ty = $mop_types_get_ty)] $MOpTypes: Type)* $(, #[Size(get_size = $sizes_get_size)] $Sizes: Size)* ] { $FirstVariant($first_ty), $($Variant($ty),)* } } impl< $DestReg: Type, $SrcRegWidth: Size, $($MOpTypes: Type + MOpTrait,)* $($Sizes: Size,)* > MOpTrait for $MOp< $DestReg, $SrcRegWidth, $($MOpTypes,)* $($Sizes,)* > { type Mapped = $MOp< NewDestReg, NewSrcRegWidth, $(<$MOpTypes as MOpTrait>::Mapped,)* $($Sizes,)* >; type DestReg = $DestReg; type SrcRegWidth = $SrcRegWidth; fn dest_reg_ty(self) -> Self::DestReg { self.$FirstVariant.dest_reg_ty() } #[hdl] fn dest_reg(input: impl ToExpr) -> Expr { let input = input.to_expr(); #[hdl] let dest_reg = wire(Expr::ty(input).dest_reg_ty()); #[hdl] match input { Self::$FirstVariant(v) => connect(dest_reg, <$first_ty as MOpTrait>::dest_reg(v)), $(Self::$Variant(v) => connect(dest_reg, <$ty as MOpTrait>::dest_reg(v)),)* } dest_reg } fn src_reg_width(self) -> ::SizeType { self.$FirstVariant.src_reg_width() } #[hdl] fn for_each_src_reg( input: impl ToExpr, f: &mut impl FnMut(Expr>, usize), ) { #[hdl] match input { Self::$FirstVariant(v) => MOpTrait::for_each_src_reg(v, f), $(Self::$Variant(v) => MOpTrait::for_each_src_reg(v, f),)* } } fn mapped_ty( self, new_dest_reg: NewDestReg, new_src_reg_width: NewSrcRegWidth::SizeType, ) -> Self::Mapped { $MOp[new_dest_reg][new_src_reg_width]$([$mop_types_get_ty(self, new_dest_reg, new_src_reg_width)])*$([$sizes_get_size(self)])* } #[hdl] fn map_regs( input: impl ToExpr, new_dest: impl ToExpr, new_src_reg_width: NewSrcRegWidth::SizeType, map_src: &mut impl FnMut( Expr>, usize, ) -> Expr>, ) -> Expr> { let input = input.to_expr(); let new_dest = new_dest.to_expr(); let mapped_ty = Expr::ty(input).mapped_ty(Expr::ty(new_dest), new_src_reg_width); #[hdl] let mapped_regs = wire(mapped_ty); #[hdl] match input { Self::$FirstVariant(v) => connect(mapped_regs, mapped_ty.$FirstVariant(MOpTrait::map_regs(v, new_dest, new_src_reg_width, map_src))), $(Self::$Variant(v) => connect(mapped_regs, mapped_ty.$Variant(MOpTrait::map_regs(v, new_dest, new_src_reg_width, map_src))),)* } mapped_regs } } }; ( @impl_variants #[impl_mop_into = true] enum $MOp:ident $generics:tt { $($Variant:ident($ty:ty),)+ } ) => { $(mop_enum! { @impl_variant enum $MOp $generics { $Variant($ty), } })+ }; ( @impl_variants #[impl_mop_into = false] enum $MOp:ident $generics:tt { $($Variant:ident($ty:ty),)+ } ) => {}; ( @impl_variant enum $MOp:ident[$DestReg:ident: Type, $SrcRegWidth:ident: Size $(, #[Size(get_size = $sizes_get_size:expr)] $Sizes:ident: Size)*] { $Variant:ident($ty:ty), } ) => { impl<$DestReg: Type, $SrcRegWidth: Size, Target: MOpTrait, $($Sizes: Size,)*> MOpInto for $ty where $MOp<$DestReg, $SrcRegWidth, $($Sizes,)*>: MOpInto { fn mop_into_ty(self) -> Target { MOpInto::mop_into_ty($MOp[MOpTrait::dest_reg_ty(self)][MOpTrait::src_reg_width(self)]$([$sizes_get_size(self)])*) } fn mop_into(this: Expr) -> Expr { MOpInto::mop_into(MOpInto::<$MOp<$DestReg, $SrcRegWidth, $($Sizes,)*>>::mop_into_ty(Expr::ty(this)).$Variant(this)) } } }; } pub(crate) use mop_enum; common_mop_struct! { #[mapped( AluCommonMOp)] #[hdl(cmp_eq)] pub struct AluCommonMOp { #[common] pub common: CommonMOp, DestReg, SrcRegWidth, SrcCount>, pub output_integer_mode: OutputIntegerMode, } } common_mop_struct! { #[mapped( AddSubMOp)] #[hdl(cmp_eq)] pub struct AddSubMOp { #[common] pub alu_common: AluCommonMOp, pub invert_src0: Bool, /// * if this is `true`, use `alu_common.src[1]`'s [`PRegFlagsPowerISA::xer_ca`] as a carry-in/borrow-in /// * else, use `alu_common.src[1]` as a normal addend pub src1_is_carry_in: Bool, pub invert_carry_in: Bool, pub add_pc: Bool, } } impl AddSubMOp> { #[hdl] pub fn add_sub( dest: impl ToExpr, src: impl ToExpr, 3>>, imm: impl ToExpr>, output_integer_mode: impl ToExpr, invert_src0: impl ToExpr, src1_is_carry_in: impl ToExpr, invert_carry_in: impl ToExpr, add_pc: impl ToExpr, ) -> Expr where Self: MOpInto, { MOpInto::mop_into( #[hdl] AddSubMOp { alu_common: #[hdl] AluCommonMOp { common: CommonMOp::new(0_hdl_u0, dest, src, Expr::as_dyn_int(imm.to_expr())), output_integer_mode, }, invert_src0, src1_is_carry_in, invert_carry_in, add_pc, }, ) } } impl AddSubMOp> { #[hdl] pub fn add_sub_i( dest: impl ToExpr, src: impl ToExpr, 2>>, imm: impl ToExpr>, output_integer_mode: impl ToExpr, invert_src0: impl ToExpr, src1_is_carry_in: impl ToExpr, invert_carry_in: impl ToExpr, add_pc: impl ToExpr, ) -> Expr where Self: MOpInto, { MOpInto::mop_into( #[hdl] AddSubMOp { alu_common: #[hdl] AluCommonMOp { common: CommonMOp::new(0_hdl_u0, dest, src, Expr::as_dyn_int(imm.to_expr())), output_integer_mode, }, invert_src0, src1_is_carry_in, invert_carry_in, add_pc, }, ) } } common_mop_struct! { #[mapped( LogicalMOp)] #[hdl(cmp_eq)] pub struct LogicalMOp { #[common] pub alu_common: AluCommonMOp>, pub lut: UInt<4>, } } impl LogicalMOp { #[hdl] pub fn logical( dest: impl ToExpr, src: impl ToExpr, 2>>, imm: impl ToExpr>, output_integer_mode: impl ToExpr, lut: impl ToExpr>, ) -> Expr where Self: MOpInto, { 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, }, ) } } common_mop_struct! { #[mapped( BranchMOp)] #[hdl(cmp_eq)] pub struct BranchMOp { #[common] pub alu_common: AluCommonMOp>, pub lut: UInt<4>, } } mop_enum! { #[impl_mop_into = true] #[hdl] pub enum AluBranchMOp { AddSub(AddSubMOp>), AddSubI(AddSubMOp>), Logical(LogicalMOp), } } common_mop_struct! { #[mapped( ReadL2RegMOp)] #[hdl(cmp_eq)] pub struct ReadL2RegMOp { #[common] pub common: CommonMOp, DestReg, SrcRegWidth, ConstUsize<0>>, } } common_mop_struct! { #[mapped( WriteL2RegMOp)] #[hdl(cmp_eq)] pub struct WriteL2RegMOp { #[common] pub common: CommonMOp, DestReg, SrcRegWidth, ConstUsize<1>>, } } mop_enum! { #[impl_mop_into = true] #[hdl] pub enum L2RegisterFileMOp { ReadL2Reg(ReadL2RegMOp), WriteL2Reg(WriteL2RegMOp), } } common_mop_struct! { #[mapped( LoadStoreCommonMOp)] #[hdl(cmp_eq)] pub struct LoadStoreCommonMOp { #[common] pub common: CommonMOp, DestReg, SrcRegWidth, SrcCount>, } } common_mop_struct! { #[mapped( LoadMOp)] #[hdl(cmp_eq)] pub struct LoadMOp { #[common] pub load_store_common: LoadStoreCommonMOp>, } } common_mop_struct! { #[mapped( StoreMOp)] #[hdl(cmp_eq)] pub struct StoreMOp { #[common] pub load_store_common: LoadStoreCommonMOp>, } } mop_enum! { #[impl_mop_into = true] #[hdl] pub enum LoadStoreMOp { Load(CommonMOp, DestReg, SrcRegWidth, ConstUsize<0>>), Store(CommonMOp, DestReg, SrcRegWidth, ConstUsize<1>>), } } common_mop_struct! { #[mapped( MoveRegMOp)] #[hdl(cmp_eq)] pub struct MoveRegMOp { #[common] pub common: CommonMOp, DestReg, SrcRegWidth, ConstUsize<1>>, } } #[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 pub struct UnitNum { pub adj_value: UIntType, } impl UnitNum { #[hdl] pub fn const_zero(self) -> Expr { #[hdl] UnitNum { adj_value: CONST_ZERO_UNIT_NUM.cast_to(self.adj_value), } } #[hdl] pub fn from_index(self, index: usize) -> Expr { #[hdl] UnitNum { adj_value: (index + 1).cast_to(self.adj_value), } } pub fn is_index(expr: impl ToExpr, index: usize) -> Expr { let expr = expr.to_expr(); Expr::ty(expr) .from_index(index) .adj_value .cmp_eq(expr.adj_value) } #[hdl] pub fn as_index(expr: impl ToExpr) -> Expr>> { let expr = expr.to_expr(); #[hdl] let unit_index = wire(HdlOption[Expr::ty(expr).adj_value]); connect(unit_index, Expr::ty(unit_index).HdlNone()); #[hdl] if expr.adj_value.cmp_ne(0u8) { connect( unit_index, HdlSome((expr.adj_value - 1u8).cast_to(Expr::ty(expr).adj_value)), ); } unit_index } } pub const CONST_ZERO_UNIT_NUM: usize = 0; #[hdl(cmp_eq)] pub struct UnitOutRegNum { pub value: UIntType, } #[hdl(cmp_eq)] /// Physical Register Number -- registers in the CPU's backend pub struct PRegNum { pub unit_num: UnitNum, pub unit_out_reg: UnitOutRegNum, } impl PRegNum { #[hdl] pub fn const_zero(self) -> Expr { #[hdl] PRegNum { unit_num: self.unit_num.const_zero(), unit_out_reg: #[hdl] UnitOutRegNum { value: 0u8.cast_to(self.unit_out_reg.value), }, } } } #[hdl(cmp_eq)] /// µOp Register Number -- register in a micro-operation before register renaming #[doc(alias = "UOpRegNum")] // help you find it in the docs if you mis-spell it #[doc(alias = "\u{B5}OpRegNum")] // micro sign #[doc(alias = "\u{39C}OpRegNum")] // greek capital letter mu #[doc(alias = "\u{3BC}OpRegNum")] // greek small letter mu pub struct MOpRegNum { pub value: UInt<{ MOpRegNum::WIDTH }>, } impl MOpRegNum { pub const WIDTH: usize = 8; pub const CONST_ZERO_REG_NUM: u32 = 0; #[hdl] pub fn const_zero() -> Expr { #[hdl] MOpRegNum { value: Self::CONST_ZERO_REG_NUM.cast_to_static(), } } /// a lot of instructions write to flag registers that we want /// to register allocate separately. /// /// e.g. x86 `CF` is not modified by `INC`, but is by `ADD`, so to not slow down `INC`, /// we'd need to have `CF` be renamed separately from other flags such as `ZF`. /// /// Note this doesn't mean that each instruction has to write to multiple physical registers, /// all registers written by an instruction are renamed to the same physical register. // // TODO: maybe add more registers later. pub const FLAG_REG_NUMS: Range = 0xFE..0x100; /// registers handled by a special small rename table (for flags and stuff, since it has more read/write ports) pub const SPECIAL_REG_NUMS: Range = Self::FLAG_REG_NUMS; /// registers handled by the large rename table for normal registers (has less read/write ports) pub const NORMAL_REG_NUMS: Range = Self::CONST_ZERO_REG_NUM + 1..Self::SPECIAL_REG_NUMS.start; } #[hdl(cmp_eq)] /// all the registers this instruction will write to, they are all renamed to the same physical register. pub struct MOpDestReg { /// some instructions have multiple destination registers, e.g. x86 div pub normal_regs: Array, /// a lot of instructions also write to flag registers. /// /// when an element with index `index` is `HdlSome(())`, /// then the register to write to is [`MOpRegNum::FLAG_REG_NUMS[index]`][MOpRegNum::FLAG_REG_NUMS]. pub flag_regs: Array, { range_u32_len(&MOpRegNum::FLAG_REG_NUMS) }>, } #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] pub enum RenameTableName { /// the large rename table for normal registers (has less read/write ports) Normal, /// a special small rename table (for flags and stuff, since it has more read/write ports) Special, } impl RenameTableName { pub const fn reg_range(self) -> std::ops::Range { match self { Self::Normal => MOpRegNum::NORMAL_REG_NUMS, Self::Special => MOpRegNum::SPECIAL_REG_NUMS, } } pub const fn as_str(self) -> &'static str { match self { Self::Normal => "rename_table_normal", Self::Special => "rename_table_special", } } } #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] pub enum MOpDestRegKind { NormalReg { /// index in `MOpDestReg::normal_regs` dest_reg_index: usize, }, FlagReg { /// index in `MOpDestReg::flag_regs` flag_reg_index: usize, /// value for `MOpRegNum::value` reg_num: u32, }, } #[derive(Copy, Clone, Debug)] pub struct MOpDestRegName { base_name: &'static str, index: usize, reg_num: Option, } impl fmt::Display for MOpDestRegName { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { base_name, index, reg_num, } = self; write!(f, "{base_name}{index}")?; if let Some(reg_num) = reg_num { write!(f, "_r{reg_num:02X}")?; } Ok(()) } } impl MOpDestRegKind { pub const fn reg_range(self) -> std::ops::Range { match self { Self::NormalReg { .. } => MOpRegNum::NORMAL_REG_NUMS, Self::FlagReg { .. } => MOpRegNum::FLAG_REG_NUMS, } } pub const fn rename_table_names(self) -> &'static [RenameTableName] { match self { Self::NormalReg { .. } => &[RenameTableName::Normal, RenameTableName::Special], Self::FlagReg { .. } => &[RenameTableName::Special], } } pub fn fixed_reg_num(self) -> Option { match self { Self::NormalReg { dest_reg_index: _ } => None, Self::FlagReg { flag_reg_index: _, reg_num, } => Some(reg_num), } } pub fn reg_name(self) -> MOpDestRegName { match self { Self::NormalReg { dest_reg_index } => MOpDestRegName { base_name: "dest", index: dest_reg_index, reg_num: None, }, Self::FlagReg { flag_reg_index, reg_num, } => MOpDestRegName { base_name: "flag", index: flag_reg_index, reg_num: Some(reg_num), }, } } } impl MOpDestReg { pub const NORMAL_REG_COUNT: usize = 2; pub const REG_COUNT: usize = Self::NORMAL_REG_COUNT + range_u32_len(&MOpRegNum::FLAG_REG_NUMS); pub const REG_KINDS: [MOpDestRegKind; Self::REG_COUNT] = { let mut retval = [MOpDestRegKind::NormalReg { dest_reg_index: 0 }; Self::REG_COUNT]; let mut write_index = 0; let mut dest_reg_index = 0; while dest_reg_index < Self::NORMAL_REG_COUNT { retval[write_index] = MOpDestRegKind::NormalReg { dest_reg_index }; write_index += 1; dest_reg_index += 1; } let mut flag_reg_index = 0; while flag_reg_index < range_u32_len(&MOpRegNum::FLAG_REG_NUMS) { retval[write_index] = MOpDestRegKind::FlagReg { flag_reg_index, reg_num: flag_reg_index as u32 + MOpRegNum::FLAG_REG_NUMS.start, }; write_index += 1; flag_reg_index += 1; } // make sure we didn't miss filling any assert!(write_index == Self::REG_COUNT); retval }; #[hdl] pub fn regs(this: impl ToExpr) -> [Expr; Self::REG_COUNT] { let this = this.to_expr(); std::array::from_fn(|index| match Self::REG_KINDS[index] { MOpDestRegKind::NormalReg { dest_reg_index } => this.normal_regs[dest_reg_index], MOpDestRegKind::FlagReg { flag_reg_index, reg_num, } => { #[hdl] let flag_reg = wire(); connect(flag_reg, MOpRegNum::const_zero()); #[hdl] if let HdlSome(v) = this.flag_regs[flag_reg_index] { let () = *v; connect( flag_reg, #[hdl] MOpRegNum { value: reg_num.cast_to_static(), }, ); } flag_reg } }) } } #[hdl] pub type MOp = UnitMOp< MOpDestReg, ConstUsize<{ MOpRegNum::WIDTH }>, MoveRegMOp>, >; #[hdl] pub type RenamedMOp = UnitMOp>;