1981 lines
68 KiB
Rust
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);
|
|
}
|
|
}
|