Compare commits

...

4 commits

17 changed files with 6782 additions and 1252 deletions

9
Cargo.lock generated
View file

@ -210,6 +210,7 @@ name = "cpu"
version = "0.1.0"
dependencies = [
"fayalite",
"serde",
]
[[package]]
@ -303,7 +304,7 @@ checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
[[package]]
name = "fayalite"
version = "0.3.0"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#edcc5927a5f9ebca6df5720bb1f5931e50095a57"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#9e803223d0f08c3398182cbabbff28cda7853b0f"
dependencies = [
"base64",
"bitvec",
@ -330,7 +331,7 @@ dependencies = [
[[package]]
name = "fayalite-proc-macros"
version = "0.3.0"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#edcc5927a5f9ebca6df5720bb1f5931e50095a57"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#9e803223d0f08c3398182cbabbff28cda7853b0f"
dependencies = [
"fayalite-proc-macros-impl",
]
@ -338,7 +339,7 @@ dependencies = [
[[package]]
name = "fayalite-proc-macros-impl"
version = "0.3.0"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#edcc5927a5f9ebca6df5720bb1f5931e50095a57"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#9e803223d0f08c3398182cbabbff28cda7853b0f"
dependencies = [
"base16ct",
"num-bigint",
@ -353,7 +354,7 @@ dependencies = [
[[package]]
name = "fayalite-visit-gen"
version = "0.3.0"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#edcc5927a5f9ebca6df5720bb1f5931e50095a57"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#9e803223d0f08c3398182cbabbff28cda7853b0f"
dependencies = [
"indexmap",
"prettyplease",

View file

@ -15,6 +15,7 @@ rust-version = "1.89.0"
[workspace.dependencies]
fayalite = { git = "https://git.libre-chip.org/libre-chip/fayalite.git", version = "0.3.0", branch = "master" }
serde = { version = "1.0.202", features = ["derive"] }
[profile.dev]
opt-level = 1

View file

@ -16,3 +16,4 @@ version.workspace = true
[dependencies]
fayalite.workspace = true
serde.workspace = true

View file

@ -8,9 +8,10 @@ use crate::{
},
};
use fayalite::prelude::*;
use serde::{Deserialize, Serialize};
use std::num::NonZeroUsize;
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
#[derive(Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct UnitConfig {
pub kind: UnitKind,
@ -27,12 +28,14 @@ impl UnitConfig {
}
}
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
#[derive(Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct CpuConfig {
pub units: Vec<UnitConfig>,
pub out_reg_num_width: usize,
pub fetch_width: NonZeroUsize,
pub max_branches_per_fetch: NonZeroUsize,
pub log2_fetch_width_in_bytes: u8,
/// default value for [`UnitConfig::max_in_flight`]
pub default_unit_max_in_flight: NonZeroUsize,
pub rob_size: NonZeroUsize,
@ -46,6 +49,13 @@ impl CpuConfig {
};
v
};
pub const DEFAULT_MAX_BRANCHES_PER_FETCH: NonZeroUsize = {
let Some(v) = NonZeroUsize::new(1) else {
unreachable!();
};
v
};
pub const DEFAULT_LOG2_FETCH_WIDTH_IN_BYTES: u8 = 3;
pub const DEFAULT_UNIT_MAX_IN_FLIGHT: NonZeroUsize = {
let Some(v) = NonZeroUsize::new(8) else {
unreachable!();
@ -57,6 +67,8 @@ impl CpuConfig {
units,
out_reg_num_width: Self::DEFAULT_OUT_REG_NUM_WIDTH,
fetch_width: Self::DEFAULT_FETCH_WIDTH,
max_branches_per_fetch: Self::DEFAULT_MAX_BRANCHES_PER_FETCH,
log2_fetch_width_in_bytes: Self::DEFAULT_LOG2_FETCH_WIDTH_IN_BYTES,
default_unit_max_in_flight: Self::DEFAULT_UNIT_MAX_IN_FLIGHT,
rob_size,
}
@ -116,4 +128,21 @@ impl CpuConfig {
UnitToRegAlloc[mop_ty][extra_out_ty][self.unit_num_width()][self.out_reg_num_width]
[self.non_const_unit_nums().len()]
}
pub fn fetch_width_in_bytes(&self) -> usize {
1usize
.checked_shl(self.log2_fetch_width_in_bytes.into())
.expect("log2_fetch_width_in_bytes is too big")
}
}
#[hdl(get(|c| c.fetch_width.get()))]
pub type CpuConfigFetchWidth<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.max_branches_per_fetch.get()))]
pub type CpuConfigMaxBranchesPerFetch<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.log2_fetch_width_in_bytes.into()))]
pub type CpuConfigLog2FetchWidthInBytes<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.fetch_width_in_bytes()))]
pub type CpuConfigFetchWidthInBytes<C: PhantomConstGet<CpuConfig>> = DynSize;

View file

@ -2,12 +2,11 @@
// See Notices.txt for copyright information
use crate::{unit::UnitMOp, util::range_u32_len};
use fayalite::{
expr::ops::{ArrayLiteral, ExprPartialEq},
expr::{HdlPartialEqImpl, ops::ArrayLiteral},
intern::Interned,
prelude::*,
sim::value::SimValuePartialEq,
};
use std::{fmt, marker::PhantomData, ops::Range};
use std::{borrow::Cow, fmt, marker::PhantomData, ops::Range};
pub mod power_isa;
@ -172,20 +171,38 @@ pub enum OutputIntegerMode {
SignExt8,
}
impl ExprPartialEq<Self> for OutputIntegerMode {
fn cmp_eq(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
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())
}
fn cmp_ne(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
lhs.cast_to_bits().cmp_ne(rhs.cast_to_bits())
}
}
impl SimValuePartialEq<Self> for OutputIntegerMode {
fn sim_value_eq(this: &SimValue<Self>, other: &SimValue<Self>) -> bool {
SimValue::opaque(this) == SimValue::opaque(other)
}
}
pub const MOP_IMM_WIDTH: usize = 34;
@ -296,8 +313,8 @@ impl<PrefixPad: KnownSize, DestReg: Type, SrcRegWidth: Size, SrcCount: KnownSize
let dest = dest.to_expr();
let src_in = src.to_expr();
let imm = imm.to_expr();
assert_eq!(Expr::ty(imm), Self::imm_ty());
let src_reg_ty = Expr::ty(src_in).element();
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 {
@ -341,9 +358,9 @@ impl<PrefixPad: KnownSize, DestReg: Type, SrcRegWidth: Size, SrcCount: KnownSize
#[hdl]
pub fn connect_to_imm(expr: impl ToExpr<Type = Self>, imm: impl ToExpr<Type = SInt>) {
let expr = expr.to_expr();
let src_reg_ty = Expr::ty(expr).src.element();
let src_reg_ty = expr.ty().src.element();
let imm = imm.to_expr();
assert_eq!(Expr::ty(imm), Self::imm_ty());
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 {
@ -496,7 +513,7 @@ macro_rules! mop_enum {
fn dest_reg(input: impl ToExpr<Type = Self>) -> Expr<Self::DestReg> {
let input = input.to_expr();
#[hdl]
let dest_reg = wire(Expr::ty(input).dest_reg_ty());
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)),
@ -537,7 +554,7 @@ macro_rules! mop_enum {
) -> Expr<Self::Mapped<NewDestReg, NewSrcRegWidth>> {
let input = input.to_expr();
let new_dest = new_dest.to_expr();
let mapped_ty = Expr::ty(input).mapped_ty(Expr::ty(new_dest), new_src_reg_width);
let mapped_ty = input.ty().mapped_ty(new_dest.ty(), new_src_reg_width);
#[hdl]
let mapped_regs = wire(mapped_ty);
#[hdl]
@ -584,7 +601,7 @@ macro_rules! mop_enum {
MOpInto::mop_into_ty($MOp[MOpTrait::dest_reg_ty(self)][MOpTrait::src_reg_width(self)]$([$sizes_get_size(self)])*)
}
fn mop_into(this: Expr<Self>) -> Expr<Target> {
MOpInto::mop_into(MOpInto::<$MOp<$DestReg, $SrcRegWidth, $($Sizes,)*>>::mop_into_ty(Expr::ty(this)).$Variant(this))
MOpInto::mop_into(MOpInto::<$MOp<$DestReg, $SrcRegWidth, $($Sizes,)*>>::mop_into_ty(this.ty()).$Variant(this))
}
}
};
@ -833,22 +850,19 @@ impl<Width: Size> UnitNum<Width> {
}
pub fn is_index(expr: impl ToExpr<Type = Self>, index: usize) -> Expr<Bool> {
let expr = expr.to_expr();
Expr::ty(expr)
.from_index(index)
.adj_value
.cmp_eq(expr.adj_value)
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(expr).adj_value]);
connect(unit_index, Expr::ty(unit_index).HdlNone());
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(expr).adj_value)),
HdlSome((expr.adj_value - 1u8).cast_to(expr.ty().adj_value)),
);
}
unit_index
@ -900,7 +914,7 @@ impl MOpRegNum {
pub fn const_zero() -> Expr<Self> {
#[hdl]
MOpRegNum {
value: Self::CONST_ZERO_REG_NUM.cast_to_static(),
value: Self::CONST_ZERO_REG_NUM.cast_to_static::<UInt<_>>(),
}
}
/// a lot of instructions write to flag registers that we want
@ -1077,7 +1091,7 @@ impl MOpDestReg {
flag_reg,
#[hdl]
MOpRegNum {
value: reg_num.cast_to_static(),
value: reg_num.cast_to_static::<UInt<_>>(),
},
);
}

View file

@ -2,6 +2,7 @@
// See Notices.txt for copyright information
pub mod config;
pub mod instruction;
pub mod next_pc;
pub mod reg_alloc;
pub mod register;
pub mod unit;

728
crates/cpu/src/next_pc.rs Normal file
View file

@ -0,0 +1,728 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! [Next-Instruction Logic](https://git.libre-chip.org/libre-chip/grant-tracking/issues/10)
//!
//! The basic idea here is that there's a `next_pc` stage that sends predicted fetch PCs to the `fetch` stage,
//! the `fetch` stage's outputs eventually end up in the `decode` stage,
//! after the `decode` stage there's a `post_decode` stage (that may run in the same clock cycle as `decode`)
//! that checks that the fetched instructions' kinds match the predicted instruction kinds and that feeds
//! information back to the `fetch` stage to cancel fetches that need to be predicted differently.
use crate::{config::CpuConfig, util::array_vec::ArrayVec};
use fayalite::{
int::{UIntInRange, UIntInRangeInclusive, UIntInRangeType},
prelude::*,
sim::{ForkJoinScope, value::SimOnlyValueTrait},
util::ready_valid::ReadyValid,
};
#[hdl]
pub enum PredictedCond {
Taken,
Fallthrough,
}
#[hdl]
pub struct PredictedFallthrough {}
#[hdl]
pub enum BranchPredictionKind<CondKind> {
Branch(HdlOption<CondKind>),
IndirectBranch(HdlOption<CondKind>),
Call(HdlOption<CondKind>),
IndirectCall(HdlOption<CondKind>),
Ret(HdlOption<CondKind>),
}
#[hdl(get(|c| c.max_branches_per_fetch.get() - 1))]
pub type NextPcPredictionMaxBranchesBeforeLast<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(no_static)]
pub struct NextPcPrediction<C: PhantomConstGet<CpuConfig>> {
pub fetch_pc: UInt<64>,
pub async_interrupt: Bool,
pub branches_before_last: ArrayVec<
BranchPredictionKind<PredictedFallthrough>,
NextPcPredictionMaxBranchesBeforeLast<C>,
>,
pub last_branch: HdlOption<BranchPredictionKind<PredictedCond>>,
pub last_branch_target_pc: UInt<64>,
}
pub const FETCH_BLOCK_ID_WIDTH: usize = FetchBlockIdInt::BITS as usize;
type FetchBlockIdInt = u8;
#[hdl]
pub struct NextPcToFetchInterfaceInner {
pub next_fetch_pc: UInt<64>,
pub fetch_block_id: UInt<{ FETCH_BLOCK_ID_WIDTH }>,
pub in_progress_fetches_to_cancel: UInt<8>,
}
#[hdl(no_static)]
pub struct NextPcToFetchInterface<C: PhantomConstGet<CpuConfig>> {
pub inner: ReadyValid<NextPcToFetchInterfaceInner>,
pub config: C,
}
#[hdl]
/// WIP version of decoded instruction just good enough to represent stuff needed for [`next_pc()`] since the actual instruction definition isn't finalized yet. This will be replaced at a later point.
pub enum WipDecodedInsnKind {
NonBranch,
Branch(UInt<64>),
BranchCond(UInt<64>),
IndirectBranch,
IndirectBranchCond,
Call(UInt<64>),
CallCond(UInt<64>),
IndirectCall,
IndirectCallCond,
Ret,
RetCond,
/// not actually an instruction read from memory, covers stuff like external interrupts, page faults, memory errors, and so on.
Interrupt(UInt<64>),
}
#[hdl]
/// WIP version of decoded instruction just good enough to represent stuff needed for [`next_pc()`] since the actual instruction definition isn't finalized yet. This will be replaced at a later point.
pub struct WipDecodedInsn {
pub fetch_block_id: UInt<8>,
pub id: UInt<12>,
pub pc: UInt<64>,
pub kind: WipDecodedInsnKind,
}
#[hdl(no_static)]
/// handles updating speculative branch predictor state (e.g. branch histories) when instructions retire,
/// as well as updating state when a branch instruction is mis-speculated.
pub struct NextPcToRetireInterface<C: PhantomConstGet<CpuConfig>> {
// TODO: add needed fields
pub config: C,
}
#[hdl(no_static)]
pub struct DecodeToPostDecodeInterface<C: PhantomConstGet<CpuConfig>> {
// TODO: add needed fields
pub config: C,
}
#[hdl(no_static)]
pub struct PostDecodeOutputInterface<C: PhantomConstGet<CpuConfig>> {
// TODO: add needed fields
pub config: C,
}
#[hdl]
enum BranchPredictionState {
StronglyNotTaken,
WeaklyNotTaken,
WeaklyTaken,
StronglyTaken,
}
impl BranchPredictionState {
#[must_use]
#[hdl]
fn is_taken(this: &SimValue<Self>) -> bool {
#[hdl(sim)]
match this {
Self::StronglyNotTaken => false,
Self::WeaklyNotTaken => false,
Self::WeaklyTaken => true,
Self::StronglyTaken => true,
}
}
#[must_use]
#[hdl]
fn towards_taken(this: &SimValue<Self>) -> SimValue<Self> {
(#[hdl(sim)]
match this {
Self::StronglyNotTaken => BranchPredictionState.WeaklyNotTaken(),
Self::WeaklyNotTaken => BranchPredictionState.WeaklyTaken(),
Self::WeaklyTaken => BranchPredictionState.StronglyTaken(),
Self::StronglyTaken => BranchPredictionState.StronglyTaken(),
})
.to_sim_value()
}
#[must_use]
#[hdl]
fn towards_not_taken(this: &SimValue<Self>) -> SimValue<Self> {
(#[hdl(sim)]
match this {
Self::StronglyNotTaken => BranchPredictionState.StronglyNotTaken(),
Self::WeaklyNotTaken => BranchPredictionState.StronglyNotTaken(),
Self::WeaklyTaken => BranchPredictionState.WeaklyNotTaken(),
Self::StronglyTaken => BranchPredictionState.WeaklyTaken(),
})
.to_sim_value()
}
}
impl SimValueDefault for BranchPredictionState {
fn sim_value_default(self) -> SimValue<Self> {
self.WeaklyNotTaken().to_sim_value()
}
}
#[derive(Copy, Clone, Debug)]
#[must_use]
enum ResetStatus {
Done,
Working,
}
impl ResetStatus {
fn and(self, other: Self) -> Self {
match (self, other) {
(ResetStatus::Done, ResetStatus::Done) => ResetStatus::Done,
(ResetStatus::Done | ResetStatus::Working, ResetStatus::Working)
| (ResetStatus::Working, ResetStatus::Done) => ResetStatus::Working,
}
}
}
trait SimValueDefault: Type {
fn sim_value_default(self) -> SimValue<Self>;
}
impl<T: SimOnlyValueTrait> SimValueDefault for SimOnly<T> {
fn sim_value_default(self) -> SimValue<Self> {
SimOnlyValue::<T>::default().to_sim_value_with_type(self)
}
}
impl<T: Type> SimValueDefault for HdlOption<T> {
fn sim_value_default(self) -> SimValue<Self> {
self.HdlNone().to_sim_value_with_type(self)
}
}
impl SimValueDefault for Bool {
fn sim_value_default(self) -> SimValue<Self> {
false.to_sim_value()
}
}
impl<Width: Size> SimValueDefault for UIntType<Width> {
fn sim_value_default(self) -> SimValue<Self> {
self.zero().to_sim_value()
}
}
trait ResetSteps: Type {
fn reset_step(this: &mut SimValue<Self>, step: usize) -> ResetStatus;
}
impl<T: SimValueDefault, Len: Size> ResetSteps for ArrayType<T, Len> {
fn reset_step(this: &mut SimValue<Self>, step: usize) -> ResetStatus {
let element = SimValue::ty(this).element();
let len = SimValue::ty(this).len();
if step < len {
this[step] = element.sim_value_default();
}
if step.saturating_add(1) >= len {
ResetStatus::Done
} else {
ResetStatus::Working
}
}
}
#[hdl]
struct CallStack {
return_addresses: Array<UInt<64>, { CallStack::SIZE }>,
len: UIntInRangeInclusive<0, { CallStack::SIZE }>,
}
impl CallStack {
const SIZE: usize = 16;
}
impl SimValueDefault for CallStack {
#[hdl]
fn sim_value_default(self) -> SimValue<Self> {
#[hdl(sim)]
CallStack {
// something other than zero so you can see the values getting reset
return_addresses: [!0u64; Self::SIZE],
len: 0usize.to_sim_value_with_type(self.len),
}
}
}
impl ResetSteps for CallStack {
#[hdl]
fn reset_step(this: &mut SimValue<Self>, _step: usize) -> ResetStatus {
#[hdl(sim)]
let CallStack {
return_addresses,
len,
} = this;
// return_addresses is implemented as a shift register, so it can be all reset at once
return_addresses.fill(0u64.to_sim_value());
**len = 0;
ResetStatus::Done
}
}
#[hdl]
struct BranchTargetBuffer {
branch_pc_to_target_map: Array<HdlOption<(UInt<64>, UInt<64>)>, { BranchTargetBuffer::SIZE }>,
}
impl BranchTargetBuffer {
const SIZE: usize = 16;
}
impl SimValueDefault for BranchTargetBuffer {
#[hdl]
fn sim_value_default(self) -> SimValue<Self> {
#[hdl(sim)]
BranchTargetBuffer {
// something other than zero so you can see the values getting reset
branch_pc_to_target_map: [HdlSome((0u64, 0u64)); Self::SIZE],
}
}
}
impl ResetSteps for BranchTargetBuffer {
#[hdl]
fn reset_step(this: &mut SimValue<Self>, step: usize) -> ResetStatus {
#[hdl(sim)]
let BranchTargetBuffer {
branch_pc_to_target_map,
} = this;
ResetSteps::reset_step(branch_pc_to_target_map, step)
}
}
#[hdl]
struct BranchHistory {
history: Array<Bool, { BranchHistory::SIZE }>,
/// exclusive
tail: UIntInRange<0, { BranchHistory::SIZE }>,
/// inclusive, always at or after tail, always at or before speculative_head
non_speculative_head: UIntInRange<0, { BranchHistory::SIZE }>,
/// inclusive, always at or after both tail and non_speculative_head
speculative_head: UIntInRange<0, { BranchHistory::SIZE }>,
}
impl ResetSteps for BranchHistory {
#[hdl]
fn reset_step(this: &mut SimValue<Self>, step: usize) -> ResetStatus {
#[hdl(sim)]
let Self {
history,
tail,
non_speculative_head,
speculative_head,
} = this;
**tail = 0;
**non_speculative_head = 0;
**speculative_head = 0;
ResetSteps::reset_step(history, step)
}
}
impl SimValueDefault for BranchHistory {
#[hdl]
fn sim_value_default(self) -> SimValue<Self> {
#[hdl(sim)]
BranchHistory {
// something other than zero so you can see the values getting reset
history: [true; Self::SIZE],
tail: 0usize.to_sim_value_with_type(self.tail),
non_speculative_head: 0usize.to_sim_value_with_type(self.non_speculative_head),
speculative_head: 0usize.to_sim_value_with_type(self.speculative_head),
}
}
}
enum BranchHistoryTryPushSpeculativeError {
NoSpace,
}
enum BranchHistoryTryPushNonSpeculativeError {
NoSpace,
Misprediction { speculated: bool },
}
impl BranchHistory {
const LOG2_SIZE: usize = 8;
const SIZE: usize = 1 << Self::LOG2_SIZE;
fn next_pos(pos: usize) -> usize {
(pos + 1) % Self::SIZE
}
fn prev_pos(pos: usize) -> usize {
(pos + Self::SIZE - 1) % Self::SIZE
}
fn history_from_head<const N: usize>(this: &SimValue<Self>, head: usize) -> [bool; N] {
let mut retval = [false; N];
let mut pos = head;
for entry in &mut retval {
if pos == *this.tail {
break;
}
*entry = *this.history[pos];
pos = Self::prev_pos(pos);
}
retval
}
fn delete_speculative_history(this: &mut SimValue<Self>) {
let non_speculative_head = *this.non_speculative_head;
*this.speculative_head = non_speculative_head;
}
fn recent_history_including_speculative<const N: usize>(this: &SimValue<Self>) -> [bool; N] {
let head = *this.speculative_head;
Self::history_from_head(this, head)
}
fn speculative_full(this: &SimValue<Self>) -> bool {
let speculative_head = *this.speculative_head;
Self::next_pos(speculative_head) == *this.tail
}
fn try_push_speculative(
this: &mut SimValue<Self>,
value: bool,
) -> Result<(), BranchHistoryTryPushSpeculativeError> {
if Self::speculative_full(this) {
Err(BranchHistoryTryPushSpeculativeError::NoSpace)
} else {
let speculative_head = Self::next_pos(*this.speculative_head);
*this.speculative_head = speculative_head;
*this.history[speculative_head] = value;
Ok(())
}
}
fn try_push_non_speculative(
this: &mut SimValue<Self>,
value: bool,
) -> Result<(), BranchHistoryTryPushNonSpeculativeError> {
let speculative_head = *this.speculative_head;
let non_speculative_head = *this.non_speculative_head;
if speculative_head == non_speculative_head {
Err(BranchHistoryTryPushNonSpeculativeError::NoSpace)
} else {
let pos = Self::next_pos(non_speculative_head);
let speculated = *this.history[pos];
if speculated != value {
Err(BranchHistoryTryPushNonSpeculativeError::Misprediction { speculated })
} else {
*this.non_speculative_head = pos;
Ok(())
}
}
}
}
#[hdl]
struct Queue<T, Capacity: Size> {
data: ArrayType<T, Capacity>,
/// inclusive
head: UIntInRangeType<ConstUsize<0>, Capacity>,
/// exclusive
tail: UIntInRangeType<ConstUsize<0>, Capacity>,
}
impl<T: Type, Capacity: Size> Queue<T, Capacity> {
fn capacity(self) -> usize {
self.data.len()
}
fn next_pos(self, pos: usize) -> usize {
assert_ne!(self.capacity(), 0);
(pos + 1) % self.capacity()
}
fn prev_pos(self, pos: usize) -> usize {
assert_ne!(self.capacity(), 0);
(pos + self.capacity() - 1) % self.capacity()
}
fn is_empty(this: &SimValue<Self>) -> bool {
this.head == this.tail
}
fn is_full(this: &SimValue<Self>) -> bool {
let head = *this.head;
let tail = *this.tail;
SimValue::ty(this).next_pos(head) == tail
}
fn try_push(this: &mut SimValue<Self>, value: impl ToSimValueWithType<T>) -> Result<(), ()> {
if Self::is_full(this) {
Err(())
} else {
let head = *this.head;
let head = SimValue::ty(this).next_pos(head);
*this.head = head;
let data = &mut this.data[head];
*data = value.to_sim_value_with_type(SimValue::ty(data));
Ok(())
}
}
}
impl<T: SimValueDefault, Capacity: Size> SimValueDefault for Queue<T, Capacity> {
#[hdl]
fn sim_value_default(self) -> SimValue<Self> {
let Self { data, head, tail } = self;
#[hdl(sim)]
Queue::<T, Capacity> {
data: repeat(
data.element().sim_value_default(),
Capacity::from_usize(data.len()),
),
head: 0usize.to_sim_value_with_type(head),
tail: 0usize.to_sim_value_with_type(tail),
}
}
}
impl<T: SimValueDefault, Capacity: Size> ResetSteps for Queue<T, Capacity> {
#[hdl]
fn reset_step(this: &mut SimValue<Self>, step: usize) -> ResetStatus {
#[hdl(sim)]
let Queue::<T, Capacity> { data, head, tail } = this;
**head = 0;
**tail = 0;
ResetSteps::reset_step(data, step)
}
}
#[hdl]
struct FetchQueueEntry {
fetch_block_id: UInt<{ FETCH_BLOCK_ID_WIDTH }>,
}
impl SimValueDefault for FetchQueueEntry {
#[hdl]
fn sim_value_default(self) -> SimValue<Self> {
#[hdl(sim)]
FetchQueueEntry {
fetch_block_id: 0 as FetchBlockIdInt,
}
}
}
const BRANCH_PREDICTOR_LOG2_SIZE: usize = 8;
const BRANCH_PREDICTOR_SIZE: usize = 1 << BRANCH_PREDICTOR_LOG2_SIZE;
#[hdl]
pub struct NextPcState<C: PhantomConstGet<CpuConfig>> {
speculative_call_stack: CallStack,
non_speculative_call_stack: CallStack,
branch_target_buffer: BranchTargetBuffer,
branch_history: BranchHistory,
branch_predictor: Array<BranchPredictionState, { BRANCH_PREDICTOR_SIZE }>,
fetching_queue: Queue<FetchQueueEntry, ConstUsize<{ 1 << FETCH_BLOCK_ID_WIDTH }>>,
pc: UInt<64>,
fetch_block_id: UInt<{ FETCH_BLOCK_ID_WIDTH }>,
config: C,
}
impl<C: Type + PhantomConstGet<CpuConfig>> NextPcState<C> {
fn next_fetch_pc(this: &SimValue<Self>) -> u64 {
let pc = u64::try_from(this.pc.to_bigint()).expect("in range");
pc & (!0u64 << SimValue::ty(&this.config).get().log2_fetch_width_in_bytes)
}
fn branch_predictor_index(this: &SimValue<Self>, pc: u64) -> usize {
let mut history = 0u64;
let history_bits: [bool; BRANCH_PREDICTOR_LOG2_SIZE] =
BranchHistory::recent_history_including_speculative(&this.branch_history);
for history_bit in history_bits {
history <<= 1;
if history_bit {
history |= 1;
}
}
let mut t = history;
t ^= t.rotate_left(5) & !pc.rotate_right(3);
t ^= pc;
t ^= !t.rotate_left(2) & t.rotate_left(4);
let mut retval = 0;
for i in (0..BRANCH_PREDICTOR_LOG2_SIZE).step_by(BRANCH_PREDICTOR_LOG2_SIZE) {
retval ^= t >> i;
}
retval as usize % BRANCH_PREDICTOR_SIZE
}
}
impl SimValueDefault for NextPcState<PhantomConst<CpuConfig>> {
#[hdl]
fn sim_value_default(self) -> SimValue<Self> {
let Self {
speculative_call_stack,
non_speculative_call_stack,
branch_target_buffer,
branch_history,
branch_predictor: _,
fetching_queue,
pc: _,
fetch_block_id: _,
config,
} = self;
#[hdl(sim)]
Self {
speculative_call_stack: speculative_call_stack.sim_value_default(),
non_speculative_call_stack: non_speculative_call_stack.sim_value_default(),
branch_target_buffer: branch_target_buffer.sim_value_default(),
branch_history: branch_history.sim_value_default(),
// use something other than the default so you can see the reset progress
branch_predictor: std::array::from_fn(|_| {
BranchPredictionState::towards_not_taken(&BranchPredictionState.sim_value_default())
}),
fetching_queue: fetching_queue.sim_value_default(),
// use something other than the default so you can see the reset progress
pc: !0u64,
// use something other than the default so you can see the reset progress
fetch_block_id: !0u8,
config,
}
}
}
impl<C: Type + PhantomConstGet<CpuConfig>> ResetSteps for NextPcState<C> {
#[hdl]
fn reset_step(this: &mut SimValue<Self>, step: usize) -> ResetStatus {
#[hdl(sim)]
let NextPcState::<C> {
speculative_call_stack,
non_speculative_call_stack,
branch_target_buffer,
branch_history,
branch_predictor,
fetching_queue,
pc,
fetch_block_id,
config: _,
} = this;
**pc = 0u64.into(); // match Microwatt's reset PC
**fetch_block_id = 0u8.into();
let speculative_call_stack = ResetSteps::reset_step(speculative_call_stack, step);
let non_speculative_call_stack = ResetSteps::reset_step(non_speculative_call_stack, step);
let branch_target_buffer = ResetSteps::reset_step(branch_target_buffer, step);
let branch_history = ResetSteps::reset_step(branch_history, step);
let branch_predictor = ResetSteps::reset_step(branch_predictor, step);
let fetching_queue = ResetSteps::reset_step(fetching_queue, step);
speculative_call_stack
.and(non_speculative_call_stack)
.and(branch_target_buffer)
.and(branch_history)
.and(branch_predictor)
.and(fetching_queue)
}
}
#[hdl_module(extern)]
pub fn next_pc(config: PhantomConst<CpuConfig>) {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let to_fetch: NextPcToFetchInterface<PhantomConst<CpuConfig>> =
m.output(NextPcToFetchInterface[config]);
#[hdl]
let state_for_debug: NextPcState<PhantomConst<CpuConfig>> = m.output(NextPcState[config]);
m.register_clock_for_past(cd.clk);
#[hdl]
async fn run(
scope: ForkJoinScope<'_>,
mut sim: ExternModuleSimulationState,
cd: Expr<ClockDomain>,
to_fetch: Expr<NextPcToFetchInterface<PhantomConst<CpuConfig>>>,
state_expr: Expr<NextPcState<PhantomConst<CpuConfig>>>,
) {
let config = Expr::ty(state_expr.config);
let mut state = sim.read(state_expr).await;
for step in 0usize.. {
sim.write(state_expr, state).await;
sim.wait_for_clock_edge(cd.clk).await;
state = sim.read_past(state_expr, cd.clk).await;
let reset_status = ResetSteps::reset_step(&mut state, step);
match reset_status {
ResetStatus::Done => break,
ResetStatus::Working => {}
}
}
scope.spawn_detached(|_, mut sim: ExternModuleSimulationState| async move {
loop {
let state = sim.read(state_expr).await;
if Queue::is_full(&state.fetching_queue) {
sim.write(to_fetch.inner.data, HdlNone()).await;
} else {
sim.write(
to_fetch.inner.data,
HdlSome(
#[hdl(sim)]
NextPcToFetchInterfaceInner {
next_fetch_pc: NextPcState::next_fetch_pc(&state),
fetch_block_id: state.fetch_block_id,
in_progress_fetches_to_cancel: 0u8, // TODO: implement
},
),
)
.await;
}
sim.wait_for_changes([state_expr], None).await;
}
});
loop {
sim.write(state_expr, state).await;
sim.wait_for_clock_edge(cd.clk).await;
state = sim.read_past(state_expr, cd.clk).await;
let next_fetch_pc = NextPcState::next_fetch_pc(&state);
if Queue::is_full(&state.fetching_queue) {
continue;
}
if sim.read_past_bool(to_fetch.inner.ready, cd.clk).await {
let fetch_block_id =
FetchBlockIdInt::try_from(state.fetch_block_id.to_bigint()).expect("in range");
Queue::try_push(
&mut state.fetching_queue,
#[hdl(sim)]
FetchQueueEntry { fetch_block_id },
)
.expect("checked is_full above");
// TODO: handle instructions not aligned with fetch blocks
let mut new_pc =
next_fetch_pc.wrapping_add(config.get().fetch_width_in_bytes() as u64);
for entry in &state.branch_target_buffer.branch_pc_to_target_map {
#[hdl(sim)]
match entry {
HdlNone => continue,
HdlSome(entry) => {
#[hdl(sim)]
let (entry_pc, entry_target) = entry;
if *entry_pc == state.pc {
new_pc = entry_target
.to_bigint()
.try_into()
.expect("known to be in range");
break;
}
}
}
}
*state.pc = new_pc.into();
*state.fetch_block_id = fetch_block_id.wrapping_add(1).into();
}
}
// TODO: finish
}
m.extern_module_simulation_fn(
(cd, to_fetch, state_for_debug),
|(cd, to_fetch, state_for_debug), mut sim| async move {
sim.write(
state_for_debug,
Expr::ty(state_for_debug).sim_value_default(),
)
.await;
sim.resettable(
cd,
|mut sim: ExternModuleSimulationState| async move {
sim.write(to_fetch.inner.data, HdlNone()).await;
},
|mut sim: ExternModuleSimulationState, ()| async move {
sim.fork_join_scope(|scope, sim| run(scope, sim, cd, to_fetch, state_for_debug))
.await
},
)
.await;
},
);
}

View file

@ -241,7 +241,7 @@ pub fn reg_alloc(config: &CpuConfig) {
// TODO: finish
connect(
rob.renamed_insns_in[fetch_index].data,
Expr::ty(rob).renamed_insns_in.element().data.HdlNone(),
rob.ty().renamed_insns_in.element().data.HdlNone(),
);
// TODO: finish
connect(
@ -263,7 +263,7 @@ pub fn reg_alloc(config: &CpuConfig) {
);
connect(
renamed_mops[fetch_index],
Expr::ty(renamed_mops).element().HdlNone(),
renamed_mops.ty().element().HdlNone(),
);
#[hdl]
struct RenameTableReadPort<T> {
@ -332,7 +332,7 @@ pub fn reg_alloc(config: &CpuConfig) {
let write_port = wire_with_loc(
&format!("{table_name}_{fetch_index}_{}", reg_kind.reg_name()),
SourceLocation::caller(),
Expr::ty(write_port_),
write_port_.ty(),
);
connect(write_port_, write_port);
write_ports.push_back(write_port);
@ -343,7 +343,7 @@ pub fn reg_alloc(config: &CpuConfig) {
addr: 0_hdl_u0,
en: false,
clk: cd.clk,
data: Expr::ty(write_port.data).uninit(),
data: write_port.data.ty().uninit(),
mask: splat_mask(config.p_reg_num(), true.to_expr()),
},
);
@ -375,7 +375,7 @@ pub fn reg_alloc(config: &CpuConfig) {
config.renamed_mop_in_unit().TransformedMove,
|renamed_mop, renamed_move_op: Expr<MoveRegMOp<_, _>>| {
// TODO: finish handling MoveRegMOp
connect(renamed_mop, Expr::ty(renamed_mop).HdlNone());
connect(renamed_mop, renamed_mop.ty().HdlNone());
},
);
connect(
@ -429,7 +429,7 @@ pub fn reg_alloc(config: &CpuConfig) {
);
connect(
selected_unit_index_leaf,
Expr::ty(selected_unit_index_leaf).HdlNone(),
selected_unit_index_leaf.ty().HdlNone(),
);
let unit_index_wire = wire_with_loc(
&format!("unit_index_{fetch_index}_{unit_index}"),
@ -447,7 +447,7 @@ pub fn reg_alloc(config: &CpuConfig) {
let selected_unit_index_node = wire_with_loc(
&format!("selected_unit_index_node_{fetch_index}_{state}"),
SourceLocation::caller(),
Expr::ty(l),
l.ty(),
);
*state += 1;
connect(selected_unit_index_node, l);
@ -516,7 +516,7 @@ pub fn reg_alloc(config: &CpuConfig) {
connect(unit_free_regs_tracker.alloc_out[0].ready, false);
connect(
unit_to_reg_alloc.input.data,
Expr::ty(unit_to_reg_alloc.input).data.HdlNone(),
unit_to_reg_alloc.input.ty().data.HdlNone(),
);
for fetch_index in 0..config.fetch_width.get() {
#[hdl]
@ -550,7 +550,7 @@ pub fn reg_alloc(config: &CpuConfig) {
} else {
connect(
unit_to_reg_alloc.input.data,
HdlSome(Expr::ty(unit_to_reg_alloc.input).data.HdlSome.uninit()),
HdlSome(unit_to_reg_alloc.input.ty().data.HdlSome.uninit()),
);
// FIXME: add hdl_assert(cd.clk, false.to_expr(), "");
}
@ -578,7 +578,7 @@ pub fn reg_alloc(config: &CpuConfig) {
connect(unit_to_reg_alloc.unit_forwarding_info, unit_forwarding_info);
connect(
unit_forwarding_info.unit_output_writes[unit_index],
Expr::ty(unit_forwarding_info)
unit_forwarding_info.ty()
.unit_output_writes
.element()
.HdlNone(),

View file

@ -73,7 +73,7 @@ pub fn unit_free_regs_tracker(
let reduced_alloc_nums = wire_with_loc(
&format!("reduced_alloc_nums_{}_{}", range.start, range.end),
SourceLocation::caller(),
Array[UInt[Expr::ty(l.alloc_nums).element().width() + 1]][alloc_at_once.get()],
Array[UInt[l.alloc_nums.ty().element().width() + 1]][alloc_at_once.get()],
);
for alloc_index in 0..alloc_at_once.get() {
#[hdl]
@ -195,7 +195,7 @@ mod tests {
}
}
#[hdl]
let free_before_alloc_array = wire(Array[Expr::ty(free_reg)][alloc_at_once.get() + 1]);
let free_before_alloc_array = wire(Array[free_reg.ty()][alloc_at_once.get() + 1]);
connect(free_before_alloc_array[0], free_reg);
#[hdl]
let expected_alloc = wire(Array[HdlOption[reg_num_ty]][alloc_at_once.get()]);

View file

@ -15,6 +15,7 @@ use fayalite::{
intern::{Intern, Interned},
prelude::*,
};
use serde::{Deserialize, Serialize};
pub mod alu_branch;
pub mod unit_base;
@ -36,7 +37,7 @@ macro_rules! all_units {
}
) => {
$(#[$enum_meta])*
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize, Deserialize)]
$vis enum $UnitKind {
$(
$(#[$variant_meta])*
@ -52,9 +53,16 @@ macro_rules! all_units {
}
}
impl ToExpr for $UnitKind {
impl ValueType for $UnitKind {
type Type = $HdlUnitKind;
type ValueCategory = fayalite::expr::value_category::ValueCategoryExpr;
fn ty(&self) -> Self::Type {
$HdlUnitKind
}
}
impl ToExpr for $UnitKind {
fn to_expr(&self) -> Expr<Self::Type> {
match self {
$($UnitKind::$Unit => $HdlUnitKind.$Unit(),)*
@ -98,7 +106,7 @@ macro_rules! all_units {
#[hdl]
$vis fn $extract(expr: impl ToExpr<Type = Self>) -> Expr<HdlOption<$Op>> {
let expr = expr.to_expr();
let ty = Expr::ty(expr);
let ty = expr.ty();
#[hdl]
let $extract = wire(HdlOption[ty.$Unit]);
connect($extract, HdlOption[ty.$Unit].HdlNone());
@ -164,10 +172,10 @@ macro_rules! all_units {
$TransformedMoveOp: MOpTrait<DestReg = $DestReg, SrcRegWidth = $SrcRegWidth>,
{
let this = this.to_expr();
let new_ty = Expr::ty(this).with_transformed_move_op_ty(new_transformed_move_op_ty);
let new_ty = this.ty().with_transformed_move_op_ty(new_transformed_move_op_ty);
#[hdl]
let with_transformed_move_op = wire(HdlOption[new_ty]);
connect(with_transformed_move_op, Expr::ty(with_transformed_move_op).HdlNone());
connect(with_transformed_move_op, with_transformed_move_op.ty().HdlNone());
// workaround #[hdl] match expanding to a loop, so you can't move variables in it
let mut connect_transformed_move_op = Some(connect_transformed_move_op);
#[hdl]
@ -209,7 +217,7 @@ macro_rules! all_units {
RenamedMOp[MOpTrait::dest_reg_ty(self)][MOpTrait::src_reg_width(self)]
}
fn mop_into(this: Expr<Self>) -> Expr<RenamedMOp<$DestReg, $SrcRegWidth>> {
MOpInto::<RenamedMOp<$DestReg, $SrcRegWidth>>::mop_into_ty(Expr::ty(this)).$BeforeUnit(this)
MOpInto::<RenamedMOp<$DestReg, $SrcRegWidth>>::mop_into_ty(this.ty()).$BeforeUnit(this)
}
})*
@ -218,7 +226,7 @@ macro_rules! all_units {
RenamedMOp[MOpTrait::dest_reg_ty(self)][MOpTrait::src_reg_width(self)]
}
fn mop_into(this: Expr<Self>) -> Expr<RenamedMOp<$DestReg, $SrcRegWidth>> {
MOpInto::<RenamedMOp<$DestReg, $SrcRegWidth>>::mop_into_ty(Expr::ty(this)).$AfterUnit(this)
MOpInto::<RenamedMOp<$DestReg, $SrcRegWidth>>::mop_into_ty(this.ty()).$AfterUnit(this)
}
})*
};

View file

@ -266,7 +266,7 @@ pub fn alu_branch(config: &CpuConfig, unit_index: usize) {
let unit_base = instance(unit_base(
config,
unit_index,
Expr::ty(unit_to_reg_alloc).input.data.HdlSome.mop,
unit_to_reg_alloc.ty().input.data.HdlSome.mop,
(),
));
connect(unit_to_reg_alloc, unit_base.unit_to_reg_alloc);
@ -274,7 +274,7 @@ pub fn alu_branch(config: &CpuConfig, unit_index: usize) {
connect(unit_base.execute_start.ready, true);
connect(
unit_base.execute_end,
Expr::ty(unit_base.execute_end).HdlNone(),
unit_base.execute_end.ty().HdlNone(),
);
#[hdl]
if let HdlSome(execute_start) = ReadyValid::firing_data(unit_base.execute_start) {

View file

@ -227,7 +227,7 @@ impl InFlightOpsSummary<DynSize> {
in_flight_ops: impl ToExpr<Type = ArrayType<HdlOption<InFlightOp<MOp>>, MaxInFlight>>,
) -> Expr<Self> {
let in_flight_ops = in_flight_ops.to_expr();
let max_in_flight = Expr::ty(in_flight_ops).len();
let max_in_flight = in_flight_ops.ty().len();
let index_range = 0..max_in_flight;
let index_ty = UInt::range(index_range.clone());
tree_reduce(
@ -259,7 +259,7 @@ pub fn unit_base<
let execute_end: HdlOption<ExecuteEnd<DynSize, ExtraOut>> =
m.input(HdlOption[ExecuteEnd[config.out_reg_num_width][extra_out_ty]]);
connect(execute_start.data, Expr::ty(execute_start).data.HdlNone());
connect(execute_start.data, execute_start.ty().data.HdlNone());
let max_in_flight = config.unit_max_in_flight(unit_index).get();
let in_flight_op_ty = InFlightOp[mop_ty];
@ -270,7 +270,7 @@ pub fn unit_base<
let in_flight_ops_summary_value = InFlightOpsSummary::summarize(in_flight_ops);
#[hdl]
let in_flight_ops_summary = wire(Expr::ty(in_flight_ops_summary_value));
let in_flight_ops_summary = wire(in_flight_ops_summary_value.ty());
connect(in_flight_ops_summary, in_flight_ops_summary_value);
connect(
@ -302,7 +302,7 @@ pub fn unit_base<
#[hdl]
let input_src_regs_valid = wire();
connect(input_src_regs_valid, [true; COMMON_MOP_SRC_LEN]);
let mut unit_output_regs_valid: Vec<MemBuilder<Bool>> = (0..Expr::ty(unit_output_writes).len())
let mut unit_output_regs_valid: Vec<MemBuilder<Bool>> = (0..unit_output_writes.ty().len())
.map(|unit_index| {
let mut mem = memory_with_loc(
&format!("unit_{unit_index}_output_regs_valid"),
@ -313,7 +313,7 @@ pub fn unit_base<
mem
})
.collect();
for unit_index in 0..Expr::ty(unit_output_writes).len() {
for unit_index in 0..unit_output_writes.ty().len() {
let mut unit_output_regs = memory_with_loc(
&format!("unit_{unit_index}_output_regs"),
PRegValue,
@ -411,7 +411,7 @@ pub fn unit_base<
connect(
unit_to_reg_alloc.output,
Expr::ty(unit_to_reg_alloc.output).HdlNone(),
unit_to_reg_alloc.output.ty().HdlNone(),
);
#[hdl]
@ -503,7 +503,7 @@ pub fn unit_base<
#[hdl]
if in_flight_ops_summary.ready_op_index.cmp_eq(HdlSome(
in_flight_op_index.cast_to(Expr::ty(in_flight_ops_summary).ready_op_index.HdlSome),
in_flight_op_index.cast_to(in_flight_ops_summary.ty().ready_op_index.HdlSome),
)) {
connect(read_src_regs, src_regs);
}
@ -512,7 +512,7 @@ pub fn unit_base<
in_flight_op_next_src_ready_flags[in_flight_op_index],
src_ready_flags,
);
for unit_index in 0..Expr::ty(unit_output_writes).len() {
for unit_index in 0..unit_output_writes.ty().len() {
#[hdl]
if let HdlSome(unit_output_write) = unit_output_writes[unit_index] {
#[hdl]

View file

@ -39,8 +39,8 @@ impl<T: Type, N: Size> ArrayVec<T, N> {
let elements = elements.to_expr();
let len = len.to_expr();
assert_eq!(
Length[N::from_usize(Expr::ty(elements).len())],
Expr::ty(len),
Length[N::from_usize(elements.ty().len())],
len.ty(),
"len type mismatch",
);
#[hdl]
@ -89,7 +89,7 @@ impl<T: Type, N: Size> ArrayVec<T, N> {
) -> Expr<ArrayVec<U, N>> {
let this = this.to_expr();
#[hdl]
let mapped_array_vec = wire(Expr::ty(this).mapped_ty(new_element_ty));
let mapped_array_vec = wire(this.ty().mapped_ty(new_element_ty));
connect(mapped_array_vec.len, this.len);
Self::for_each(this, |index, element| {
connect(mapped_array_vec[index], f(index, element));
@ -101,11 +101,11 @@ impl<T: Type, N: Size> ArrayVec<T, N> {
let this = this.to_expr();
#[hdl]
let array_vec_as_array_of_options = wire(
ArrayType[HdlOption[Expr::ty(this).element()]]
[N::from_usize(Expr::ty(this).capacity())],
ArrayType[HdlOption[this.ty().element()]]
[N::from_usize(this.ty().capacity())],
);
for element in array_vec_as_array_of_options {
connect(element, Expr::ty(element).HdlNone());
connect(element, element.ty().HdlNone());
}
Self::for_each(this, |index, element| {
connect(array_vec_as_array_of_options[index], HdlSome(element))

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,45 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use cpu::{
config::{CpuConfig, UnitConfig},
next_pc::next_pc,
unit::UnitKind,
};
use fayalite::{prelude::*, sim::vcd::VcdWriterDecls, util::RcWriter};
use std::num::NonZeroUsize;
#[hdl]
#[test]
fn test_next_pc() {
let _n = SourceLocation::normalize_files_for_tests();
let mut config = CpuConfig::new(
vec![
UnitConfig::new(UnitKind::AluBranch),
UnitConfig::new(UnitKind::AluBranch),
],
NonZeroUsize::new(20).unwrap(),
);
config.fetch_width = NonZeroUsize::new(2).unwrap();
let m = next_pc(PhantomConst::new_sized(config));
let mut sim = Simulation::new(m);
let mut writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
let to_fetch = sim.io().to_fetch;
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, true);
sim.write_bool(to_fetch.inner.ready, true);
for _cycle in 0..300 {
sim.advance_time(SimDuration::from_nanos(500));
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(SimDuration::from_nanos(500));
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, false);
}
// FIXME: vcd is just whatever next_pc does now, which isn't known to be correct
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("expected/next_pc.vcd") {
panic!();
}
}

View file

@ -59,7 +59,7 @@ fn test_reg_alloc() {
[HdlSome(()), HdlNone()],
},
[0u8; 2],
0x12345678u32.cast_to_static(),
0x12345678u32.cast_to_static::<SInt<_>>(),
OutputIntegerMode.DupLow32(),
false,
false,
@ -81,7 +81,7 @@ fn test_reg_alloc() {
[HdlSome(()), HdlNone()],
},
[1u8, 0, 0],
1.cast_to_static(),
1.cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
false,
false,
@ -99,7 +99,7 @@ fn test_reg_alloc() {
flag_regs: [HdlNone(), HdlSome(())],
},
[2u8, 4u8],
0.cast_to_static(),
0.cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
0b0110_hdl_u4,
),