cpu/crates/cpu/src/instruction.rs

1981 lines
68 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::{CastToImpl, HdlPartialEqImpl, ops::ArrayLiteral},
int::BoolOrIntType,
intern::{Intern, InternSlice, Interned},
module::wire_with_loc,
prelude::*,
ty::StaticType,
util::ConstBool,
};
use std::{
borrow::Cow,
fmt,
marker::PhantomData,
ops::{ControlFlow, Range},
};
pub mod power_isa;
pub trait MOpInto<Target: MOpTrait>:
MOpTrait<SrcRegWidth = Target::SrcRegWidth, DestReg = Target::DestReg>
{
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 MOpVariantVisitOps {
type MOp: MOpTrait<
DestReg = <Self::Target as MOpTrait>::DestReg,
SrcRegWidth = <Self::Target as MOpTrait>::SrcRegWidth,
>;
type Target: MOpTrait;
fn mop_ty(&self) -> &Self::MOp;
fn target_ty(&self) -> &Self::Target;
fn path() -> Vec<&'static str>;
fn mop_into_target(&self, mop: Expr<Self::MOp>) -> Expr<Self::Target>;
}
impl<T: MOpTrait> MOpVariantVisitOps for T {
type MOp = T;
type Target = T;
fn mop_ty(&self) -> &Self::MOp {
self
}
fn target_ty(&self) -> &Self::Target {
self
}
fn path() -> Vec<&'static str> {
Vec::new()
}
fn mop_into_target(&self, mop: Expr<Self::MOp>) -> Expr<Self::Target> {
assert_eq!(*self, mop.ty());
mop
}
}
pub trait MOpVariantVisitor<Target: MOpTrait> {
type Break;
fn visit_variant<VisitOps: ?Sized + MOpVariantVisitOps<Target = Target, MOp: CommonMOpTrait>>(
&mut self,
visit_ops: &VisitOps,
) -> ControlFlow<Self::Break>;
}
pub trait MOpVisitVariants: MOpTrait {
fn visit_variants<V, VisitOps>(visitor: &mut V, visit_ops: &VisitOps) -> ControlFlow<V::Break>
where
V: ?Sized + MOpVariantVisitor<VisitOps::Target>,
VisitOps: ?Sized + MOpVariantVisitOps<MOp = Self>,
VisitOps::Target: MOpTrait<DestReg = Self::DestReg, SrcRegWidth = Self::SrcRegWidth>;
}
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),
),
)
}
}
impl<T: CommonMOpTrait> MOpVisitVariants for T {
fn visit_variants<V, VisitOps>(visitor: &mut V, visit_ops: &VisitOps) -> ControlFlow<V::Break>
where
V: ?Sized + MOpVariantVisitor<VisitOps::Target>,
VisitOps: ?Sized + MOpVariantVisitOps<MOp = Self>,
VisitOps::Target: MOpTrait<DestReg = Self::DestReg, SrcRegWidth = Self::SrcRegWidth>,
{
visitor.visit_variant(visit_ops)
}
}
#[hdl]
pub enum OutputIntegerMode {
Full64,
DupLow32,
ZeroExt32,
SignExt32,
ZeroExt16,
SignExt16,
ZeroExt8,
SignExt8,
}
impl HdlPartialEqImpl<Self> for OutputIntegerMode {
#[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())
}
}
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!(imm.ty(), Self::imm_ty());
let src_reg_ty = src_in.ty().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().src.element();
let imm = imm.to_expr();
assert_eq!(imm.ty(), 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)*
$(, #[MOpVisitVariants] [$($visit_variants_bounds:tt)*])?
> {
$(#[$($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),)*
}
}
mop_enum! {
@impl_visit_variants [
enum $MOp<
$DestReg: Type,
$SrcRegWidth: Size
$(, #[MOp] $MOpTypes: Type)*
$(, #[Size(get_size = $sizes_get_size)] $Sizes: Size)*
$(, #[MOpVisitVariants] [$($visit_variants_bounds)*])?
>
]
enum $MOp<
$DestReg: Type,
$SrcRegWidth: Size
$(, #[MOp] $MOpTypes: Type)*
$(, #[Size] $Sizes: Size)*
$(, #[MOpVisitVariants] [$($visit_variants_bounds)*])?
> {
$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(input.ty().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 = input.ty().mapped_ty(new_dest.ty(), 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_visit_variants $visit_variant_args:tt
enum $MOp:ident<
$DestReg:ident: Type,
$SrcRegWidth:ident: Size
$(, #[MOp] $MOpTypes:ident: Type)*
$(, #[Size] $Sizes:ident: Size)*
$(, #[MOpVisitVariants] [$($visit_variants_bounds:tt)*])?
> {
$(
$Variant:ident($ty:ty),
)*
}
) => {
const _: () = {
mod variant_visit_ops {
$(
#[derive(Copy, Clone)]
pub(super) struct $Variant<VisitOps>(pub(super) VisitOps);
)*
}
$(mop_enum! {
@impl_visit_variant $visit_variant_args
#[variant_ty = $ty]
struct variant_visit_ops::$Variant<_>(_);
})*
impl<
$DestReg: Type,
$SrcRegWidth: Size,
$($MOpTypes: Type + MOpTrait<DestReg = $DestReg, SrcRegWidth = $SrcRegWidth>,)*
$($Sizes: Size,)*
> MOpVisitVariants for $MOp<
$DestReg,
$SrcRegWidth,
$($MOpTypes,)*
$($Sizes,)*
>
where
$($($visit_variants_bounds)*)?
{
fn visit_variants<V, VisitOps>(visitor: &mut V, visit_ops: &VisitOps) -> ControlFlow<V::Break>
where
V: ?Sized + MOpVariantVisitor<VisitOps::Target>,
VisitOps: ?Sized + MOpVariantVisitOps<MOp = Self>,
VisitOps::Target: MOpTrait<DestReg = Self::DestReg, SrcRegWidth = Self::SrcRegWidth>,
{
$(MOpVisitVariants::visit_variants(visitor, &variant_visit_ops::$Variant(visit_ops))?;)*
std::ops::ControlFlow::Continue(())
}
}
};
};
(
@impl_visit_variant [
enum $MOp:ident<
$DestReg:ident: Type,
$SrcRegWidth:ident: Size
$(, #[MOp] $MOpTypes:ident: Type)*
$(, #[Size(get_size = $sizes_get_size:expr)] $Sizes:ident: Size)*
$(, #[MOpVisitVariants] [$($visit_variants_bounds:tt)*])?
>
]
#[variant_ty = $ty:ty]
struct $variant_visit_ops:ident::$Variant:ident<_>(_);
) => {
impl<
$DestReg: Type,
$SrcRegWidth: Size,
$($MOpTypes: Type + MOpTrait<DestReg = $DestReg, SrcRegWidth = $SrcRegWidth>,)*
$($Sizes: Size,)*
VisitOps,
> MOpVariantVisitOps for $variant_visit_ops::$Variant<&'_ VisitOps>
where
VisitOps: ?Sized + MOpVariantVisitOps<MOp = $MOp<
$DestReg,
$SrcRegWidth,
$($MOpTypes,)*
$($Sizes,)*
>>,
VisitOps::Target: MOpTrait<DestReg = $DestReg, SrcRegWidth = $SrcRegWidth>,
{
type MOp = $ty;
type Target = VisitOps::Target;
fn mop_ty(&self) -> &Self::MOp {
&self.0.mop_ty().$Variant
}
fn target_ty(&self) -> &Self::Target {
self.0.target_ty()
}
fn path() -> Vec<&'static str> {
let mut retval = VisitOps::path();
retval.push(stringify!($Variant));
retval
}
fn mop_into_target(&self, mop: Expr<Self::MOp>) -> Expr<Self::Target> {
let mop_ty = self.0.mop_ty();
assert_eq!(mop_ty.$Variant, mop.ty());
self.0.mop_into_target(mop_ty.$Variant(mop))
}
}
};
(
@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, $($Sizes: Size,)*> MOpInto<Target> for $ty
where
$MOp<$DestReg, $SrcRegWidth, $($Sizes,)*>: MOpInto<Target>,
Target: MOpTrait<DestReg = $DestReg, SrcRegWidth = $SrcRegWidth>,
{
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(this.ty()).$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,
},
)
}
}
#[hdl(cmp_eq)]
pub struct Lut4 {
pub lut: Array<Bool, 4>,
}
impl Lut4 {
#[track_caller]
fn output_impl<T, LutBit, A, B, UIntTy, Output>(lut: [LutBit; 4], a: A, b: B) -> Output
where
T: BoolOrIntType<Signed = ConstBool<false>>,
LutBit: ValueType<Type = Bool> + CastTo,
A: ValueType<Type = T> + CastToBits<Output = UIntTy>,
B: ValueType<Type = T> + CastToBits<Output = UIntTy>,
UIntTy: ValueType<Type = UInt>
+ CastBitsTo<Output<T> = Output>
+ std::ops::Not<Output = UIntTy>
+ std::ops::BitAnd<Output = UIntTy>
+ std::ops::BitOr<Output = UIntTy>
+ Clone,
Output: ValueType<Type = T>,
<LutBit as CastTo>::Output<SInt<1>>: CastTo<Output<UInt> = UIntTy>,
{
let ty = a.ty();
assert_eq!(ty, b.ty(), "input types must match");
let a = a.cast_to_bits();
let b = b.cast_to_bits();
let uint_ty = a.ty();
let [v0, v1, v2, v3] = std::array::from_fn(|lut_index| {
let a = if (lut_index & 1) == 0 {
!a.clone()
} else {
a.clone()
};
let b = if (lut_index & 2) == 0 {
!b.clone()
} else {
b.clone()
};
let mask = lut[lut_index].cast_to_static::<SInt<1>>().cast_to(uint_ty);
a & b & mask
});
((v0 | v1) | (v2 | v3)).cast_bits_to(ty)
}
#[track_caller]
pub fn output<T: BoolOrIntType<Signed = ConstBool<false>>>(
this: impl ToExpr<Type = Self>,
a: impl ToExpr<Type = T>,
b: impl ToExpr<Type = T>,
) -> Expr<T> {
Self::output_impl(*this.to_expr().lut, a.to_expr(), b.to_expr())
}
#[track_caller]
pub fn output_sim<T: BoolOrIntType<Signed = ConstBool<false>>>(
this: impl ToSimValue<Type = Self>,
a: impl ToSimValue<Type = T>,
b: impl ToSimValue<Type = T>,
) -> SimValue<T> {
Self::output_impl(
SimValue::into_value(SimValue::into_value(this.into_sim_value()).lut),
a.into_sim_value(),
b.into_sim_value(),
)
}
#[hdl]
pub fn from_fn(f: impl Fn(bool, bool) -> bool) -> SimValue<Self> {
let lut = std::array::from_fn(|lut_index| f((lut_index & 1) != 0, (lut_index & 2) != 0));
#[hdl(sim)]
Self { lut }
}
}
common_mop_struct! {
#[mapped(<NewDestReg, NewSrcRegWidth> LogicalMOp<NewDestReg, NewSrcRegWidth, SrcCount>)]
#[hdl(cmp_eq)]
pub struct LogicalMOp<DestReg: Type, SrcRegWidth: Size, SrcCount: KnownSize> {
#[common]
pub alu_common: AluCommonMOp<DestReg, SrcRegWidth, SrcCount>,
pub lut: Lut4,
}
}
impl<DestReg: Type, SrcRegWidth: Size> LogicalMOp<DestReg, SrcRegWidth, ConstUsize<2>> {
#[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 = Lut4>,
) -> 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,
},
)
}
}
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 = Lut4>,
) -> 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,
S64,
U32,
S32,
U16,
S16,
U8,
S8,
/// compare one ranged byte -- like the PowerISA `cmprb _, 0, ..` instruction
CmpRBOne,
/// compare two ranged bytes -- like the PowerISA `cmprb _, 1, ..` instruction
CmpRBTwo,
/// like the PowerISA `cmpeqb` instruction
CmpEqB,
}
impl HdlPartialEqImpl<Self> for CompareMode {
#[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> CompareMOp<NewDestReg, NewSrcRegWidth, SrcCount>)]
#[hdl(cmp_eq)]
pub struct CompareMOp<DestReg: Type, SrcRegWidth: Size, SrcCount: KnownSize> {
#[common]
pub alu_common: AluCommonMOp<DestReg, SrcRegWidth, SrcCount>,
pub compare_mode: CompareMode,
}
}
impl<DestReg: Type, SrcRegWidth: Size> CompareMOp<DestReg, SrcRegWidth, ConstUsize<2>> {
#[hdl]
pub fn compare<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>,
compare_mode: impl ToExpr<Type = CompareMode>,
) -> Expr<Target>
where
Self: MOpInto<Target>,
{
MOpInto::mop_into(
#[hdl]
CompareMOp {
alu_common: #[hdl]
AluCommonMOp {
common: CommonMOp::new(0_hdl_u0, dest, src, Expr::as_dyn_int(imm.to_expr())),
output_integer_mode,
},
compare_mode,
},
)
}
}
impl<DestReg: Type, SrcRegWidth: Size> CompareMOp<DestReg, SrcRegWidth, ConstUsize<1>> {
#[hdl]
pub fn compare_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>,
compare_mode: impl ToExpr<Type = CompareMode>,
) -> Expr<Target>
where
Self: MOpInto<Target>,
{
MOpInto::mop_into(
#[hdl]
CompareMOp {
alu_common: #[hdl]
AluCommonMOp {
common: CommonMOp::new(0_hdl_u0, dest, src, Expr::as_dyn_int(imm.to_expr())),
output_integer_mode,
},
compare_mode,
},
)
}
}
#[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, SrcCount>)]
#[hdl(cmp_eq)]
/// `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 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,
)
}
}
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, ConstUsize<2>>),
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>>),
}
}
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<2>, DestReg, SrcRegWidth, ConstUsize<0>>),
Store(CommonMOp<ConstUsize<2>, 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<3>, DestReg, SrcRegWidth, ConstUsize<1>>,
}
}
impl<DestReg: Type, SrcRegWidth: Size, Target> MOpInto<Target> for MoveRegMOp<DestReg, SrcRegWidth>
where
UnitMOp<DestReg, SrcRegWidth, Self>: MOpInto<Target>,
Target: MOpTrait<DestReg = DestReg, SrcRegWidth = SrcRegWidth>,
{
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.cast_to_static::<UInt<_>>(),
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
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().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().adj_value]);
connect(unit_index, unit_index.ty().HdlNone());
#[hdl]
if expr.adj_value.cmp_ne(0u8) {
connect(
unit_index,
HdlSome((expr.adj_value - 1u8).cast_to(expr.ty().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::<UInt<_>>(),
}
}
/// 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) }>,
}
impl MOpDestReg {
#[hdl]
#[track_caller]
pub fn new_sim(normal_regs: &[u32], flag_regs: &[u32]) -> SimValue<Self> {
let zero_reg = MOpRegNum::const_zero().to_sim_value();
let mut normal_regs_sim = std::array::from_fn(|_| zero_reg.clone());
for (i, reg) in normal_regs.iter().copied().enumerate() {
let Some(normal_reg_sim) = normal_regs_sim.get_mut(i) else {
panic!("too many normal regs");
};
if reg >= 1 << MOpRegNum::WIDTH {
panic!("normal reg number out of range");
}
*normal_reg_sim.value = reg.cast_to_static::<UInt<_>>();
}
let mut flag_regs_sim = std::array::from_fn(|_| {
#[hdl(sim)]
HdlNone()
});
for &flag_reg in flag_regs {
let Some(index) = { MOpRegNum::FLAG_REG_NUMS }.position(|v| flag_reg == v) else {
panic!(
"flag reg number {flag_reg} is out of range, supported range is: {:?}",
MOpRegNum::FLAG_REG_NUMS
);
};
flag_regs_sim[index] = #[hdl(sim)]
HdlSome(());
}
#[hdl(sim)]
Self {
normal_regs: normal_regs_sim,
flag_regs: flag_regs_sim,
}
}
#[hdl]
#[track_caller]
pub fn new(
normal_regs: impl IntoIterator<Item = Expr<MOpRegNum>>,
flag_regs: impl IntoIterator<Item = (u32, Expr<Bool>)>,
) -> Expr<Self> {
let mut normal_regs_array = [MOpRegNum::const_zero(); Self::NORMAL_REG_COUNT];
const FLAG_REG_COUNT: usize = range_u32_len(&MOpRegNum::FLAG_REG_NUMS);
let mut used_flag_regs = [false; FLAG_REG_COUNT];
let mut flag_regs_array = [HdlNone(); FLAG_REG_COUNT];
for (i, normal_reg) in normal_regs.into_iter().enumerate() {
assert!(i < Self::NORMAL_REG_COUNT, "too many normal regs");
normal_regs_array[i] = normal_reg;
}
for (flag_reg_num, flag_reg_enabled) in flag_regs {
let Some(index) = { MOpRegNum::FLAG_REG_NUMS }.position(|v| flag_reg_num == v) else {
panic!(
"flag reg number {flag_reg_num} is out of range, supported range is: {:?}",
MOpRegNum::FLAG_REG_NUMS
);
};
assert!(
!used_flag_regs[index],
"duplicate flag reg number {flag_reg_num}"
);
used_flag_regs[index] = true;
let wire = wire_with_loc(
&format!("flag_reg_{index}"),
SourceLocation::caller(),
StaticType::TYPE,
);
connect(wire, HdlNone());
#[hdl]
if flag_reg_enabled {
connect(wire, HdlSome(()));
}
flag_regs_array[index] = wire;
}
#[hdl]
Self {
normal_regs: normal_regs_array,
flag_regs: flag_regs_array,
}
}
}
#[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::<UInt<_>>(),
},
);
}
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>>;
#[cfg(test)]
mod tests {
use super::*;
use std::{convert::Infallible, fmt::Write, usize};
#[test]
fn test_lut() {
macro_rules! case {
([$lut0:literal, $lut1:literal, $lut2:literal, $lut3:literal], $expected:literal, |$a:ident, $b:ident| $e:expr) => {
let lut = Lut4::from_fn(|$a, $b| $e);
assert_eq!(
lut.lut,
[
($lut0 != 0).into_sim_value(),
($lut1 != 0).into_sim_value(),
($lut2 != 0).into_sim_value(),
($lut3 != 0).into_sim_value()
]
.into_sim_value()
);
let output = Lut4::output_sim(&lut, 0xAAu8, 0xCCu8);
let expected = <u8 as ToSimValue>::into_sim_value($expected);
assert_eq!(output, expected, "{lut:?}");
};
}
case!([0, 0, 0, 0], 0x00, |_a, _b| false);
case!([1, 0, 0, 0], 0x11, |a, b| !(a | b));
case!([0, 1, 0, 0], 0x22, |a, b| a & !b);
case!([1, 1, 0, 0], 0x33, |_a, b| !b);
case!([0, 0, 1, 0], 0x44, |a, b| !a & b);
case!([1, 0, 1, 0], 0x55, |a, _b| !a);
case!([0, 1, 1, 0], 0x66, |a, b| a ^ b);
case!([1, 1, 1, 0], 0x77, |a, b| !(a & b));
case!([0, 0, 0, 1], 0x88, |a, b| a & b);
case!([1, 0, 0, 1], 0x99, |a, b| a == b);
case!([0, 1, 0, 1], 0xaa, |a, _b| a);
case!([1, 1, 0, 1], 0xbb, |a, b| a | !b);
case!([0, 0, 1, 1], 0xcc, |_a, b| b);
case!([1, 0, 1, 1], 0xdd, |a, b| !a | b);
case!([0, 1, 1, 1], 0xee, |a, b| a | b);
case!([1, 1, 1, 1], 0xff, |_a, _b| true);
}
#[test]
fn ensure_reg_fields_are_in_the_same_place() {
struct Visitor {
dest_reg_offset: Option<(usize, String)>,
max_dest_reg_offset: usize,
min_prefix_pad: usize,
errors: Option<String>,
}
impl MOpVariantVisitor<MOp> for Visitor {
type Break = Infallible;
fn visit_variant<
VisitOps: ?Sized + MOpVariantVisitOps<Target = MOp, MOp: CommonMOpTrait>,
>(
&mut self,
visit_ops: &VisitOps,
) -> ControlFlow<Self::Break> {
self.min_prefix_pad = self
.min_prefix_pad
.min(<VisitOps::MOp as CommonMOpTrait>::PrefixPad::VALUE);
let variant = visit_ops.mop_ty();
let zeroed_variant = UInt[variant.canonical().bit_width()]
.zero()
.cast_bits_to(*variant);
let mut common_mop = CommonMOpTrait::common_mop(&zeroed_variant).into_sim_value();
SimValue::bits_mut(&mut common_mop.dest)
.bits_mut()
.fill(false);
let with_zeros = visit_ops
.mop_into_target(Expr::from_canonical(Expr::canonical(
CommonMOpTrait::with_common_mop(&zeroed_variant, &common_mop),
)))
.into_sim_value();
SimValue::bits_mut(&mut common_mop.dest)
.bits_mut()
.fill(true);
let with_ones = visit_ops
.mop_into_target(Expr::from_canonical(Expr::canonical(
CommonMOpTrait::with_common_mop(&zeroed_variant, &common_mop),
)))
.into_sim_value();
let mut dest_reg_offset = None;
for (i, (a, b)) in SimValue::bits(&with_zeros)
.bits()
.iter()
.by_vals()
.zip(SimValue::bits(&with_ones).bits().iter().by_vals())
.enumerate()
{
if a != b {
dest_reg_offset = Some(i);
break;
}
}
let Some(dest_reg_offset) = dest_reg_offset else {
panic!("no dest reg offset: {variant:#?}");
};
self.max_dest_reg_offset = self.max_dest_reg_offset.max(dest_reg_offset);
if let Some((first_dest_reg_offset, _)) = self.dest_reg_offset {
if first_dest_reg_offset != dest_reg_offset {
writeln!(
self.errors.get_or_insert_default(),
"dest_reg_offset {dest_reg_offset} doesn't match first \
variant's dest_reg_offset {first_dest_reg_offset}\n\
variant's path: {:?}\n\
variant: {variant:#?}\n",
VisitOps::path(),
)
.unwrap();
}
} else {
self.dest_reg_offset = Some((
dest_reg_offset,
format!(
"first variant's path: {:?}\nfirst variant: {variant:#?}",
VisitOps::path()
),
));
}
ControlFlow::Continue(())
}
}
let mut visitor = Visitor {
dest_reg_offset: None,
max_dest_reg_offset: 0,
min_prefix_pad: usize::MAX,
errors: None,
};
let ControlFlow::Continue(()) = MOp::visit_variants(&mut visitor, &MOp);
let Visitor {
dest_reg_offset: Some((_, first_variant)),
max_dest_reg_offset,
min_prefix_pad,
errors,
} = visitor
else {
panic!("no variants");
};
println!("max_dest_reg_offset: {max_dest_reg_offset}");
println!("min_prefix_pad: {min_prefix_pad}");
println!("{first_variant}");
if let Some(errors) = errors {
panic!("{errors}");
}
assert_eq!(min_prefix_pad, 0);
}
}