cpu/crates/cpu/src/instruction.rs
Jacob Lifshay 00ddd602c5
All checks were successful
/ deps (push) Successful in 14s
/ test (push) Successful in 26m32s
format code
2025-08-24 19:08:58 -07:00

1092 lines
38 KiB
Rust

// 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<Target: MOpTrait>: MOpTrait {
fn mop_into_ty(self) -> Target;
fn mop_into(this: Expr<Self>) -> Expr<Target>;
}
impl<T: MOpTrait> MOpInto<T> for T {
fn mop_into_ty(self) -> T {
self
}
fn mop_into(this: Expr<Self>) -> Expr<T> {
this
}
}
pub trait MOpTrait: Type {
type Mapped<NewDestReg: Type, NewSrcRegWidth: Size>: MOpTrait<DestReg = NewDestReg, SrcRegWidth = NewSrcRegWidth>;
type DestReg: Type;
type SrcRegWidth: Size;
fn dest_reg_ty(self) -> Self::DestReg;
fn dest_reg(input: impl ToExpr<Type = Self>) -> Expr<Self::DestReg>;
fn src_reg_width(self) -> <Self::SrcRegWidth as Size>::SizeType;
fn src_reg_ty(self) -> UIntType<Self::SrcRegWidth> {
UInt[self.src_reg_width()]
}
fn src_regs_ty(self) -> Array<UIntType<Self::SrcRegWidth>, { COMMON_MOP_SRC_LEN }> {
Array[self.src_reg_ty()][ConstUsize::<{ COMMON_MOP_SRC_LEN }>]
}
fn for_each_src_reg(
input: impl ToExpr<Type = Self>,
f: &mut impl FnMut(Expr<UIntType<Self::SrcRegWidth>>, usize),
);
fn connect_src_regs(
input: impl ToExpr<Type = Self>,
src_regs: impl ToExpr<Type = Array<UIntType<Self::SrcRegWidth>, { 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<NewDestReg: Type, NewSrcRegWidth: Size>(
self,
new_dest_reg: NewDestReg,
new_src_reg_width: NewSrcRegWidth::SizeType,
) -> Self::Mapped<NewDestReg, NewSrcRegWidth>;
fn map_regs<NewDestReg: Type, NewSrcRegWidth: Size>(
input: impl ToExpr<Type = Self>,
new_dest: impl ToExpr<Type = NewDestReg>,
new_src_reg_width: NewSrcRegWidth::SizeType,
map_src: &mut impl FnMut(
Expr<UIntType<Self::SrcRegWidth>>,
usize,
) -> Expr<UIntType<NewSrcRegWidth>>,
) -> Expr<Self::Mapped<NewDestReg, NewSrcRegWidth>>;
}
pub trait CommonMOpTrait: MOpTrait {
type PrefixPad: KnownSize;
type SrcCount: KnownSize;
type CommonMOpTraitMapped<NewDestReg: Type, NewSrcRegWidth: Size>: CommonMOpTrait<
DestReg = NewDestReg,
SrcRegWidth = NewSrcRegWidth,
PrefixPad = Self::PrefixPad,
SrcCount = Self::SrcCount,
>;
type CommonMOpTraitDestReg: Type;
type CommonMOpTraitSrcRegWidth: Size;
fn common_mop_ty(
self,
) -> CommonMOp<Self::PrefixPad, Self::DestReg, Self::SrcRegWidth, Self::SrcCount>;
fn common_mop(
input: impl ToExpr<Type = Self>,
) -> Expr<CommonMOp<Self::PrefixPad, Self::DestReg, Self::SrcRegWidth, Self::SrcCount>>;
fn with_common_mop_ty<NewDestReg: Type, NewSrcRegWidth: Size>(
self,
new_common_mop_ty: CommonMOp<Self::PrefixPad, NewDestReg, NewSrcRegWidth, Self::SrcCount>,
) -> Self::Mapped<NewDestReg, NewSrcRegWidth>;
fn with_common_mop<NewDestReg: Type, NewSrcRegWidth: Size>(
input: impl ToExpr<Type = Self>,
new_common_mop: impl ToExpr<
Type = CommonMOp<Self::PrefixPad, NewDestReg, NewSrcRegWidth, Self::SrcCount>,
>,
) -> Expr<Self::Mapped<NewDestReg, NewSrcRegWidth>>;
}
impl<T: CommonMOpTrait> MOpTrait for T {
type Mapped<NewDestReg: Type, NewSrcRegWidth: Size> =
T::CommonMOpTraitMapped<NewDestReg, NewSrcRegWidth>;
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<Type = Self>) -> Expr<Self::DestReg> {
T::common_mop(input).dest
}
fn src_reg_width(self) -> <Self::SrcRegWidth as Size>::SizeType {
self.common_mop_ty().src.element().width
}
fn for_each_src_reg(
input: impl ToExpr<Type = Self>,
f: &mut impl FnMut(Expr<UIntType<Self::SrcRegWidth>>, 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<NewDestReg: Type, NewSrcRegWidth: Size>(
self,
new_dest_reg: NewDestReg,
new_src_reg_width: NewSrcRegWidth::SizeType,
) -> Self::Mapped<NewDestReg, NewSrcRegWidth> {
self.with_common_mop_ty(
CommonMOp[T::PrefixPad::SIZE][new_dest_reg][new_src_reg_width][T::SrcCount::SIZE],
)
}
fn map_regs<NewDestReg: Type, NewSrcRegWidth: Size>(
input: impl ToExpr<Type = Self>,
new_dest: impl ToExpr<Type = NewDestReg>,
new_src_reg_width: NewSrcRegWidth::SizeType,
map_src: &mut impl FnMut(
Expr<UIntType<Self::SrcRegWidth>>,
usize,
) -> Expr<UIntType<NewSrcRegWidth>>,
) -> Expr<Self::Mapped<NewDestReg, NewSrcRegWidth>> {
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<Self> for OutputIntegerMode {
fn cmp_eq(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
lhs.cast_to_bits().cmp_eq(rhs.cast_to_bits())
}
fn cmp_ne(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
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<PrefixPad: KnownSize, DestReg: Type, SrcRegWidth: Size, SrcCount: KnownSize> {
pub prefix_pad: UIntType<PrefixPad>,
pub dest: DestReg,
pub src: Array<UIntType<SrcRegWidth>, { COMMON_MOP_SRC_LEN }>,
pub imm_low: UInt<{ COMMON_MOP_IMM_LOW_WIDTH }>,
pub imm_sign: SInt<1>,
pub _phantom: PhantomData<SrcCount>,
}
impl<PrefixPad: KnownSize, DestReg: Type, SrcRegWidth: Size, SrcCount: KnownSize> CommonMOpTrait
for CommonMOp<PrefixPad, DestReg, SrcRegWidth, SrcCount>
{
type PrefixPad = PrefixPad;
type SrcCount = SrcCount;
type CommonMOpTraitMapped<NewDestReg: Type, NewSrcRegWidth: Size> =
CommonMOp<PrefixPad, NewDestReg, NewSrcRegWidth, SrcCount>;
type CommonMOpTraitDestReg = DestReg;
type CommonMOpTraitSrcRegWidth = SrcRegWidth;
fn common_mop_ty(
self,
) -> CommonMOp<Self::PrefixPad, Self::DestReg, Self::SrcRegWidth, Self::SrcCount> {
self
}
fn common_mop(
input: impl ToExpr<Type = Self>,
) -> Expr<CommonMOp<Self::PrefixPad, Self::DestReg, Self::SrcRegWidth, Self::SrcCount>> {
input.to_expr()
}
fn with_common_mop_ty<NewDestReg: Type, NewSrcRegWidth: Size>(
self,
new_common_mop_ty: CommonMOp<Self::PrefixPad, NewDestReg, NewSrcRegWidth, Self::SrcCount>,
) -> Self::Mapped<NewDestReg, NewSrcRegWidth> {
new_common_mop_ty
}
fn with_common_mop<NewDestReg: Type, NewSrcRegWidth: Size>(
input: impl ToExpr<Type = Self>,
new_common_mop: impl ToExpr<
Type = CommonMOp<Self::PrefixPad, NewDestReg, NewSrcRegWidth, Self::SrcCount>,
>,
) -> Expr<Self::Mapped<NewDestReg, NewSrcRegWidth>> {
let _ = input.to_expr();
new_common_mop.to_expr()
}
}
#[hdl(cmp_eq)]
pub struct CommonMOpImmParts<ImmInSrcCount: Size> {
// fields must be in this exact order
pub imm_low: UInt<{ COMMON_MOP_IMM_LOW_WIDTH }>,
pub reversed_src: ArrayType<UInt<{ MOP_MIN_REG_WIDTH }>, ImmInSrcCount>,
pub imm_sign: SInt<1>,
}
type CommonMOpWithMaxSrcCount = CommonMOpForImm<{ COMMON_MOP_SRC_LEN }>;
type CommonMOpForImm<const SRC_COUNT: usize> =
CommonMOp<ConstUsize<0>, (), ConstUsize<{ MOP_MIN_REG_WIDTH }>, ConstUsize<SRC_COUNT>>;
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<PrefixPad: KnownSize, DestReg: Type, SrcRegWidth: Size, SrcCount: KnownSize>
CommonMOp<PrefixPad, DestReg, SrcRegWidth, SrcCount>
{
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<usize> =
(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<DynSize> {
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<Type = UIntType<PrefixPad>>,
dest: impl ToExpr<Type = DestReg>,
src: impl ToExpr<Type = ArrayType<UIntType<SrcRegWidth>, SrcCount>>,
imm: impl ToExpr<Type = SInt>,
) -> Expr<Self> {
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<Type = Self>) -> Expr<SInt> {
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<Type = Self>, imm: impl ToExpr<Type = SInt>) {
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<Self::PrefixPad, Self::DestReg, Self::SrcRegWidth, Self::SrcCount> {
CommonMOpTrait::common_mop_ty(self.$common)
}
fn common_mop(
input: impl ToExpr<Type = Self>,
) -> Expr<CommonMOp<Self::PrefixPad, Self::DestReg, Self::SrcRegWidth, Self::SrcCount>> {
CommonMOpTrait::common_mop(input.to_expr().$common)
}
fn with_common_mop_ty<NewDestReg: Type, NewSrcRegWidth: Size>(
self,
new_common_mop_ty: CommonMOp<Self::PrefixPad, NewDestReg, NewSrcRegWidth, Self::SrcCount>,
) -> Self::Mapped<NewDestReg, NewSrcRegWidth> {
$MOp {
$common: CommonMOpTrait::with_common_mop_ty(self.$common, new_common_mop_ty),
$($field: self.$field,)*
}
}
#[hdl]
fn with_common_mop<NewDestReg: Type, NewSrcRegWidth: Size>(
input: impl ToExpr<Type = Self>,
new_common_mop: impl ToExpr<
Type = CommonMOp<Self::PrefixPad, NewDestReg, NewSrcRegWidth, Self::SrcCount>,
>,
) -> Expr<Self::Mapped<NewDestReg, NewSrcRegWidth>> {
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<DestReg = $DestReg, SrcRegWidth = $SrcRegWidth>,)*
$($Sizes: Size,)*
> MOpTrait for $MOp<
$DestReg,
$SrcRegWidth,
$($MOpTypes,)*
$($Sizes,)*
> {
type Mapped<NewDestReg: Type, NewSrcRegWidth: Size> = $MOp<
NewDestReg,
NewSrcRegWidth,
$(<$MOpTypes as MOpTrait>::Mapped<NewDestReg, NewSrcRegWidth>,)*
$($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<Type = Self>) -> Expr<Self::DestReg> {
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) -> <Self::SrcRegWidth as Size>::SizeType {
self.$FirstVariant.src_reg_width()
}
#[hdl]
fn for_each_src_reg(
input: impl ToExpr<Type = Self>,
f: &mut impl FnMut(Expr<UIntType<Self::SrcRegWidth>>, 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<NewDestReg: Type, NewSrcRegWidth: Size>(
self,
new_dest_reg: NewDestReg,
new_src_reg_width: NewSrcRegWidth::SizeType,
) -> Self::Mapped<NewDestReg, NewSrcRegWidth> {
$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<NewDestReg: Type, NewSrcRegWidth: Size>(
input: impl ToExpr<Type = Self>,
new_dest: impl ToExpr<Type = NewDestReg>,
new_src_reg_width: NewSrcRegWidth::SizeType,
map_src: &mut impl FnMut(
Expr<UIntType<Self::SrcRegWidth>>,
usize,
) -> Expr<UIntType<NewSrcRegWidth>>,
) -> Expr<Self::Mapped<NewDestReg, NewSrcRegWidth>> {
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<Target> for $ty
where
$MOp<$DestReg, $SrcRegWidth, $($Sizes,)*>: MOpInto<Target>
{
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<Self>) -> Expr<Target> {
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(<NewDestReg, NewSrcRegWidth> AluCommonMOp<NewDestReg, NewSrcRegWidth, SrcCount>)]
#[hdl(cmp_eq)]
pub struct AluCommonMOp<DestReg: Type, SrcRegWidth: Size, SrcCount: KnownSize> {
#[common]
pub common: CommonMOp<ConstUsize<0>, DestReg, SrcRegWidth, SrcCount>,
pub output_integer_mode: OutputIntegerMode,
}
}
common_mop_struct! {
#[mapped(<NewDestReg, NewSrcRegWidth> AddSubMOp<NewDestReg, NewSrcRegWidth, SrcCount>)]
#[hdl(cmp_eq)]
pub struct AddSubMOp<DestReg: Type, SrcRegWidth: Size, SrcCount: KnownSize> {
#[common]
pub alu_common: AluCommonMOp<DestReg, SrcRegWidth, SrcCount>,
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<DestReg: Type, SrcRegWidth: Size> AddSubMOp<DestReg, SrcRegWidth, ConstUsize<3>> {
#[hdl]
pub fn add_sub<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 }>>,
output_integer_mode: impl ToExpr<Type = OutputIntegerMode>,
invert_src0: impl ToExpr<Type = Bool>,
src1_is_carry_in: impl ToExpr<Type = Bool>,
invert_carry_in: impl ToExpr<Type = Bool>,
add_pc: impl ToExpr<Type = Bool>,
) -> Expr<Target>
where
Self: MOpInto<Target>,
{
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<DestReg: Type, SrcRegWidth: Size> AddSubMOp<DestReg, SrcRegWidth, ConstUsize<2>> {
#[hdl]
pub fn add_sub_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 }>>,
output_integer_mode: impl ToExpr<Type = OutputIntegerMode>,
invert_src0: impl ToExpr<Type = Bool>,
src1_is_carry_in: impl ToExpr<Type = Bool>,
invert_carry_in: impl ToExpr<Type = Bool>,
add_pc: impl ToExpr<Type = Bool>,
) -> Expr<Target>
where
Self: MOpInto<Target>,
{
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(<NewDestReg, NewSrcRegWidth> LogicalMOp<NewDestReg, NewSrcRegWidth>)]
#[hdl(cmp_eq)]
pub struct LogicalMOp<DestReg: Type, SrcRegWidth: Size> {
#[common]
pub alu_common: AluCommonMOp<DestReg, SrcRegWidth, ConstUsize<2>>,
pub lut: UInt<4>,
}
}
impl<DestReg: Type, SrcRegWidth: Size> LogicalMOp<DestReg, SrcRegWidth> {
#[hdl]
pub fn logical<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 }>>,
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,
},
)
}
}
common_mop_struct! {
#[mapped(<NewDestReg, NewSrcRegWidth> BranchMOp<NewDestReg, NewSrcRegWidth>)]
#[hdl(cmp_eq)]
pub struct BranchMOp<DestReg: Type, SrcRegWidth: Size> {
#[common]
pub alu_common: AluCommonMOp<DestReg, SrcRegWidth, ConstUsize<2>>,
pub lut: UInt<4>,
}
}
mop_enum! {
#[impl_mop_into = true]
#[hdl]
pub enum AluBranchMOp<DestReg: Type, SrcRegWidth: Size> {
AddSub(AddSubMOp<DestReg, SrcRegWidth, ConstUsize<3>>),
AddSubI(AddSubMOp<DestReg, SrcRegWidth, ConstUsize<2>>),
Logical(LogicalMOp<DestReg, SrcRegWidth>),
}
}
common_mop_struct! {
#[mapped(<NewDestReg, NewSrcRegWidth> ReadL2RegMOp<NewDestReg, NewSrcRegWidth>)]
#[hdl(cmp_eq)]
pub struct ReadL2RegMOp<DestReg: Type, SrcRegWidth: Size> {
#[common]
pub common: CommonMOp<ConstUsize<1>, DestReg, SrcRegWidth, ConstUsize<0>>,
}
}
common_mop_struct! {
#[mapped(<NewDestReg, NewSrcRegWidth> WriteL2RegMOp<NewDestReg, NewSrcRegWidth>)]
#[hdl(cmp_eq)]
pub struct WriteL2RegMOp<DestReg: Type, SrcRegWidth: Size> {
#[common]
pub common: CommonMOp<ConstUsize<1>, DestReg, SrcRegWidth, ConstUsize<1>>,
}
}
mop_enum! {
#[impl_mop_into = true]
#[hdl]
pub enum L2RegisterFileMOp<DestReg: Type, SrcRegWidth: Size> {
ReadL2Reg(ReadL2RegMOp<DestReg, SrcRegWidth>),
WriteL2Reg(WriteL2RegMOp<DestReg, SrcRegWidth>),
}
}
common_mop_struct! {
#[mapped(<NewDestReg, NewSrcRegWidth> LoadStoreCommonMOp<NewDestReg, NewSrcRegWidth, SrcCount>)]
#[hdl(cmp_eq)]
pub struct LoadStoreCommonMOp<DestReg: Type, SrcRegWidth: Size, SrcCount: KnownSize> {
#[common]
pub common: CommonMOp<ConstUsize<1>, DestReg, SrcRegWidth, SrcCount>,
}
}
common_mop_struct! {
#[mapped(<NewDestReg, NewSrcRegWidth> LoadMOp<NewDestReg, NewSrcRegWidth>)]
#[hdl(cmp_eq)]
pub struct LoadMOp<DestReg: Type, SrcRegWidth: Size> {
#[common]
pub load_store_common: LoadStoreCommonMOp<DestReg, SrcRegWidth, ConstUsize<1>>,
}
}
common_mop_struct! {
#[mapped(<NewDestReg, NewSrcRegWidth> StoreMOp<NewDestReg, NewSrcRegWidth>)]
#[hdl(cmp_eq)]
pub struct StoreMOp<DestReg: Type, SrcRegWidth: Size> {
#[common]
pub load_store_common: LoadStoreCommonMOp<DestReg, SrcRegWidth, ConstUsize<2>>,
}
}
mop_enum! {
#[impl_mop_into = true]
#[hdl]
pub enum LoadStoreMOp<DestReg: Type, SrcRegWidth: Size> {
Load(CommonMOp<ConstUsize<1>, DestReg, SrcRegWidth, ConstUsize<0>>),
Store(CommonMOp<ConstUsize<1>, DestReg, SrcRegWidth, ConstUsize<1>>),
}
}
common_mop_struct! {
#[mapped(<NewDestReg, NewSrcRegWidth> MoveRegMOp<NewDestReg, NewSrcRegWidth>)]
#[hdl(cmp_eq)]
pub struct MoveRegMOp<DestReg: Type, SrcRegWidth: Size> {
#[common]
pub common: CommonMOp<ConstUsize<2>, 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<Width: Size> {
pub adj_value: UIntType<Width>,
}
impl<Width: Size> UnitNum<Width> {
#[hdl]
pub fn const_zero(self) -> Expr<Self> {
#[hdl]
UnitNum {
adj_value: CONST_ZERO_UNIT_NUM.cast_to(self.adj_value),
}
}
#[hdl]
pub fn from_index(self, index: usize) -> Expr<Self> {
#[hdl]
UnitNum {
adj_value: (index + 1).cast_to(self.adj_value),
}
}
pub fn is_index(expr: impl ToExpr<Type = Self>, index: usize) -> Expr<Bool> {
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<Type = Self>) -> Expr<HdlOption<UIntType<Width>>> {
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<Width: Size> {
pub value: UIntType<Width>,
}
#[hdl(cmp_eq)]
/// Physical Register Number -- registers in the CPU's backend
pub struct PRegNum<UnitNumWidth: Size, OutRegNumWidth: Size> {
pub unit_num: UnitNum<UnitNumWidth>,
pub unit_out_reg: UnitOutRegNum<OutRegNumWidth>,
}
impl<UnitNumWidth: Size, OutRegNumWidth: Size> PRegNum<UnitNumWidth, OutRegNumWidth> {
#[hdl]
pub fn const_zero(self) -> Expr<Self> {
#[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<Self> {
#[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<u32> = 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<u32> = 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<u32> =
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<MOpRegNum, { MOpDestReg::NORMAL_REG_COUNT }>,
/// 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<HdlOption<()>, { 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<u32> {
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<u32>,
}
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<u32> {
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<u32> {
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<Type = Self>) -> [Expr<MOpRegNum>; 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<MOpDestReg, ConstUsize<{ MOpRegNum::WIDTH }>>,
>;
#[hdl]
pub type RenamedMOp<DestReg: Type, SrcRegWidth: Size> =
UnitMOp<DestReg, SrcRegWidth, L2RegisterFileMOp<DestReg, SrcRegWidth>>;