forked from libre-chip/cpu
WIP
This commit is contained in:
parent
dca59a24ec
commit
b6f3ecfb32
6 changed files with 606 additions and 17 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
|
@ -211,6 +211,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fayalite",
|
"fayalite",
|
||||||
"serde",
|
"serde",
|
||||||
|
"simple-mermaid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -690,6 +691,12 @@ version = "1.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simple-mermaid"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "589144a964b4b30fe3a83b4bb1a09e2475aac194ec832a046a23e75bddf9eb29"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ rust-version = "1.89.0"
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
fayalite = { git = "https://git.libre-chip.org/libre-chip/fayalite.git", version = "0.3.0", branch = "master" }
|
fayalite = { git = "https://git.libre-chip.org/libre-chip/fayalite.git", version = "0.3.0", branch = "master" }
|
||||||
serde = { version = "1.0.202", features = ["derive"] }
|
serde = { version = "1.0.202", features = ["derive"] }
|
||||||
|
simple-mermaid = "0.2.0"
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
opt-level = 1
|
opt-level = 1
|
||||||
|
|
|
||||||
|
|
@ -17,3 +17,4 @@ version.workspace = true
|
||||||
[dependencies]
|
[dependencies]
|
||||||
fayalite.workspace = true
|
fayalite.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
simple-mermaid.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@
|
||||||
//! after the `decode` stage there's a `post_decode` stage (that may run in the same clock cycle as `decode`)
|
//! 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
|
//! 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.
|
//! information back to the `fetch` stage to cancel fetches that need to be predicted differently.
|
||||||
|
//!
|
||||||
|
#![doc = simple_mermaid::mermaid!("next_pc/next_pc.mermaid")]
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{CpuConfig, CpuConfigFetchWidth},
|
config::{CpuConfig, CpuConfigFetchWidth},
|
||||||
|
|
@ -17,6 +19,7 @@ use fayalite::{
|
||||||
int::{UIntInRange, UIntInRangeInclusive, UIntInRangeType},
|
int::{UIntInRange, UIntInRangeInclusive, UIntInRangeType},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
sim::value::SimOnlyValueTrait,
|
sim::value::SimOnlyValueTrait,
|
||||||
|
ty::StaticType,
|
||||||
util::ready_valid::ReadyValid,
|
util::ready_valid::ReadyValid,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -142,6 +145,505 @@ pub struct PostDecodeOutputInterface<C: PhantomConstGet<CpuConfig>> {
|
||||||
pub config: C,
|
pub config: C,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[hdl(no_static)]
|
||||||
|
struct Cancel<C: PhantomConstGet<CpuConfig>> {
|
||||||
|
call_stack: CallStack,
|
||||||
|
start_pc: UInt<64>,
|
||||||
|
new_btb_entry: HdlOption<BTBEntryWithoutStartPc>,
|
||||||
|
btb_entry_index: HdlOption<UIntInRange<0, { BranchTargetBuffer::SIZE }>>,
|
||||||
|
config: C,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the output of `Stage::run`.
|
||||||
|
/// when cancelling operations, the returned [`StageOutput.cancel`] should be the state after running all operations returned in [`StageOutput.output`]
|
||||||
|
#[hdl(no_static)]
|
||||||
|
struct StageOutput<Output, MaxOutputCount: Size, C: PhantomConstGet<CpuConfig>> {
|
||||||
|
outputs: ArrayVec<Output, MaxOutputCount>,
|
||||||
|
cancel: HdlOption<Cancel<C>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Stage: Type + SimValueDefault + ResetSteps {
|
||||||
|
type Inputs: Type;
|
||||||
|
type Output: Type;
|
||||||
|
type MaxOutputCount: Size;
|
||||||
|
|
||||||
|
fn output_ty(config: PhantomConst<CpuConfig>) -> Self::Output;
|
||||||
|
fn max_output_count(
|
||||||
|
config: PhantomConst<CpuConfig>,
|
||||||
|
) -> <Self::MaxOutputCount as Size>::SizeType;
|
||||||
|
fn stage_output_ty(
|
||||||
|
config: PhantomConst<CpuConfig>,
|
||||||
|
) -> StageOutput<Self::Output, Self::MaxOutputCount, PhantomConst<CpuConfig>> {
|
||||||
|
StageOutput[Self::output_ty(config)][Self::max_output_count(config)][config]
|
||||||
|
}
|
||||||
|
fn run(
|
||||||
|
state: &mut SimValue<Self>,
|
||||||
|
inputs: &SimValue<Self::Inputs>,
|
||||||
|
) -> SimValue<StageOutput<Self::Output, Self::MaxOutputCount, PhantomConst<CpuConfig>>>;
|
||||||
|
/// changes state to match `cancel`
|
||||||
|
fn cancel(state: &mut SimValue<Self>, cancel: &SimValue<Cancel<PhantomConst<CpuConfig>>>);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[hdl(no_static)]
|
||||||
|
struct NextPcStageOutput<C: PhantomConstGet<CpuConfig>> {
|
||||||
|
start_pc: UInt<64>,
|
||||||
|
next_start_pc: UInt<64>,
|
||||||
|
btb_entry: HdlOption<(
|
||||||
|
UIntInRange<0, { BranchTargetBuffer::SIZE }>,
|
||||||
|
BTBEntryWithoutStartPc,
|
||||||
|
)>,
|
||||||
|
fetch_block_id: UInt<{ FETCH_BLOCK_ID_WIDTH }>,
|
||||||
|
config: C,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[hdl(no_static)]
|
||||||
|
struct NextPcStageState<C: PhantomConstGet<CpuConfig>> {
|
||||||
|
call_stack: CallStack,
|
||||||
|
branch_target_buffer: BranchTargetBuffer,
|
||||||
|
next_pc: UInt<64>,
|
||||||
|
next_fetch_block_id: UInt<{ FETCH_BLOCK_ID_WIDTH }>,
|
||||||
|
config: C,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SimValueDefault for NextPcStageState<PhantomConst<CpuConfig>> {
|
||||||
|
#[hdl]
|
||||||
|
fn sim_value_default(self) -> SimValue<Self> {
|
||||||
|
let Self {
|
||||||
|
call_stack,
|
||||||
|
branch_target_buffer,
|
||||||
|
next_pc: _,
|
||||||
|
next_fetch_block_id: _,
|
||||||
|
config,
|
||||||
|
} = self;
|
||||||
|
#[hdl(sim)]
|
||||||
|
Self {
|
||||||
|
call_stack: call_stack.sim_value_default(),
|
||||||
|
branch_target_buffer: branch_target_buffer.sim_value_default(),
|
||||||
|
// use something other than the default so you can see the reset progress
|
||||||
|
next_pc: !0u64,
|
||||||
|
// use something other than the default so you can see the reset progress
|
||||||
|
next_fetch_block_id: !0u8,
|
||||||
|
config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResetSteps for NextPcStageState<PhantomConst<CpuConfig>> {
|
||||||
|
#[hdl]
|
||||||
|
fn reset_step(this: &mut SimValue<Self>, step: usize) -> ResetStatus {
|
||||||
|
#[hdl(sim)]
|
||||||
|
let Self {
|
||||||
|
call_stack,
|
||||||
|
branch_target_buffer,
|
||||||
|
next_pc,
|
||||||
|
next_fetch_block_id,
|
||||||
|
config: _,
|
||||||
|
} = this;
|
||||||
|
**next_pc = 0u64.into(); // match Microwatt's reset PC
|
||||||
|
**next_fetch_block_id = 0u8.into();
|
||||||
|
let call_stack = ResetSteps::reset_step(call_stack, step);
|
||||||
|
let branch_target_buffer = ResetSteps::reset_step(branch_target_buffer, step);
|
||||||
|
call_stack.and(branch_target_buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stage for NextPcStageState<PhantomConst<CpuConfig>> {
|
||||||
|
type Inputs = ();
|
||||||
|
type Output = NextPcStageOutput<PhantomConst<CpuConfig>>;
|
||||||
|
type MaxOutputCount = ConstUsize<1>;
|
||||||
|
|
||||||
|
fn output_ty(config: PhantomConst<CpuConfig>) -> Self::Output {
|
||||||
|
NextPcStageOutput[config]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn max_output_count(
|
||||||
|
_config: PhantomConst<CpuConfig>,
|
||||||
|
) -> <Self::MaxOutputCount as Size>::SizeType {
|
||||||
|
ConstUsize
|
||||||
|
}
|
||||||
|
|
||||||
|
#[hdl]
|
||||||
|
fn run(
|
||||||
|
state: &mut SimValue<Self>,
|
||||||
|
_inputs: &SimValue<Self::Inputs>,
|
||||||
|
) -> SimValue<StageOutput<Self::Output, Self::MaxOutputCount, PhantomConst<CpuConfig>>> {
|
||||||
|
let config = state.config.ty();
|
||||||
|
let fetch_block_id = state.next_fetch_block_id.as_int();
|
||||||
|
*state.next_fetch_block_id = state.next_fetch_block_id.as_int().wrapping_add(1).into();
|
||||||
|
let start_pc = state.next_pc.as_int();
|
||||||
|
let fetch_pc = start_pc & (!0u64 << config.get().log2_fetch_width_in_bytes);
|
||||||
|
|
||||||
|
let btb_entry_index = state
|
||||||
|
.branch_target_buffer
|
||||||
|
.branch_pc_to_target_map
|
||||||
|
.iter()
|
||||||
|
.position(|entry| {
|
||||||
|
#[hdl(sim)]
|
||||||
|
match entry {
|
||||||
|
HdlNone => false,
|
||||||
|
HdlSome(entry) => entry.start_pc.as_int() == start_pc,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let (next_start_pc, btb_entry) = if let Some(btb_entry_index) = btb_entry_index {
|
||||||
|
#[hdl(sim)]
|
||||||
|
let Self {
|
||||||
|
call_stack,
|
||||||
|
branch_target_buffer,
|
||||||
|
..
|
||||||
|
} = state;
|
||||||
|
let entry = #[hdl(sim)]
|
||||||
|
match &branch_target_buffer.branch_pc_to_target_map[btb_entry_index] {
|
||||||
|
HdlSome(entry) => entry,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
let next_start_pc = #[hdl(sim)]
|
||||||
|
match &entry.rest.insn_kind {
|
||||||
|
BTBEntryInsnKind::Branch => {
|
||||||
|
if BTBEntryAddrKind::taken(&entry.rest.addr_kind) {
|
||||||
|
BTBEntry::taken_pc(entry)
|
||||||
|
} else {
|
||||||
|
BTBEntry::not_taken_start_pc(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BTBEntryInsnKind::Call => {
|
||||||
|
if BTBEntryAddrKind::taken(&entry.rest.addr_kind) {
|
||||||
|
CallStack::push(call_stack, BTBEntry::after_call_pc(entry));
|
||||||
|
BTBEntry::taken_pc(entry)
|
||||||
|
} else {
|
||||||
|
BTBEntry::not_taken_start_pc(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BTBEntryInsnKind::Ret => {
|
||||||
|
if BTBEntryAddrKind::taken(&entry.rest.addr_kind) {
|
||||||
|
CallStack::pop(call_stack).unwrap_or(BTBEntry::taken_pc(entry))
|
||||||
|
} else {
|
||||||
|
BTBEntry::not_taken_start_pc(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BTBEntryInsnKind::Unknown => unreachable!(),
|
||||||
|
};
|
||||||
|
(
|
||||||
|
next_start_pc,
|
||||||
|
#[hdl(sim)]
|
||||||
|
HdlSome((btb_entry_index, &entry.rest)),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
fetch_pc.wrapping_add(config.get().fetch_width_in_bytes() as u64),
|
||||||
|
#[hdl(sim)]
|
||||||
|
HdlNone(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let output = #[hdl(sim)]
|
||||||
|
NextPcStageOutput::<_> {
|
||||||
|
start_pc,
|
||||||
|
next_start_pc,
|
||||||
|
btb_entry,
|
||||||
|
fetch_block_id,
|
||||||
|
config,
|
||||||
|
};
|
||||||
|
#[hdl(sim)]
|
||||||
|
StageOutput::<_, _, _> {
|
||||||
|
outputs: Self::stage_output_ty(config).outputs.new_full_sim([output]),
|
||||||
|
cancel: #[hdl(sim)]
|
||||||
|
(HdlOption[Cancel[config]]).HdlNone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[hdl]
|
||||||
|
fn cancel(state: &mut SimValue<Self>, cancel: &SimValue<Cancel<PhantomConst<CpuConfig>>>) {
|
||||||
|
#[hdl(sim)]
|
||||||
|
let Self {
|
||||||
|
call_stack,
|
||||||
|
branch_target_buffer,
|
||||||
|
next_pc,
|
||||||
|
next_fetch_block_id: _,
|
||||||
|
config: _,
|
||||||
|
} = state;
|
||||||
|
#[hdl(sim)]
|
||||||
|
let Cancel::<_> {
|
||||||
|
call_stack: new_call_stack,
|
||||||
|
start_pc,
|
||||||
|
new_btb_entry,
|
||||||
|
btb_entry_index,
|
||||||
|
config: _,
|
||||||
|
} = cancel;
|
||||||
|
call_stack.clone_from(new_call_stack);
|
||||||
|
next_pc.clone_from(start_pc);
|
||||||
|
#[hdl(sim)]
|
||||||
|
if let HdlSome(new_btb_entry) = new_btb_entry {
|
||||||
|
// add/update btb entry
|
||||||
|
|
||||||
|
// get old entry if it's still there
|
||||||
|
let btb_entry_index = #[hdl(sim)]
|
||||||
|
if let HdlSome(btb_entry_index) = btb_entry_index {
|
||||||
|
#[hdl(sim)]
|
||||||
|
if let HdlSome(entry) =
|
||||||
|
&branch_target_buffer.branch_pc_to_target_map[**btb_entry_index]
|
||||||
|
{
|
||||||
|
if entry.start_pc == *start_pc {
|
||||||
|
// found the old entry
|
||||||
|
Some(**btb_entry_index)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let btb_entry_index = btb_entry_index.unwrap_or_else(|| {
|
||||||
|
// old entry isn't there, pick an entry to replace
|
||||||
|
BranchTargetBuffer::next_index_to_replace(branch_target_buffer)
|
||||||
|
});
|
||||||
|
|
||||||
|
// replace with new entry
|
||||||
|
branch_target_buffer.branch_pc_to_target_map[btb_entry_index] = #[hdl(sim)]
|
||||||
|
HdlSome(
|
||||||
|
#[hdl(sim)]
|
||||||
|
BTBEntry {
|
||||||
|
start_pc,
|
||||||
|
rest: new_btb_entry,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else if let HdlSome(btb_entry_index) = btb_entry_index {
|
||||||
|
// remove btb entry if it's still there
|
||||||
|
let entry_mut = &mut branch_target_buffer.branch_pc_to_target_map[**btb_entry_index];
|
||||||
|
#[hdl(sim)]
|
||||||
|
if let HdlSome(entry) = &entry_mut {
|
||||||
|
if entry.start_pc == *start_pc {
|
||||||
|
// we found it, remove it
|
||||||
|
*entry_mut = #[hdl(sim)]
|
||||||
|
HdlNone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[hdl(no_static)]
|
||||||
|
struct BrPredStageOutput<C: PhantomConstGet<CpuConfig>> {
|
||||||
|
config: C,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SimValueDefault for BrPredStageOutput<PhantomConst<CpuConfig>> {
|
||||||
|
#[hdl]
|
||||||
|
fn sim_value_default(self) -> SimValue<Self> {
|
||||||
|
#[hdl(sim)]
|
||||||
|
Self {
|
||||||
|
config: self.config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[hdl(no_static)]
|
||||||
|
struct BrPredStageState<C: PhantomConstGet<CpuConfig>> {
|
||||||
|
branch_history: UInt<6>,
|
||||||
|
branch_predictor: Array<BranchPredictionState, { BRANCH_PREDICTOR_SIZE }>,
|
||||||
|
config: C,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BrPredStageState<PhantomConst<CpuConfig>> {
|
||||||
|
fn branch_predictor_index(this: &SimValue<Self>, branch_pc: u64) -> usize {
|
||||||
|
let mut t = this.branch_history.cast_to_static::<UInt<64>>().as_int();
|
||||||
|
t ^= t.rotate_left(5) & !branch_pc.rotate_right(3);
|
||||||
|
t ^= branch_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 BrPredStageState<PhantomConst<CpuConfig>> {
|
||||||
|
#[hdl]
|
||||||
|
fn sim_value_default(self) -> SimValue<Self> {
|
||||||
|
let Self {
|
||||||
|
branch_history: _,
|
||||||
|
branch_predictor: _,
|
||||||
|
config,
|
||||||
|
} = self;
|
||||||
|
#[hdl(sim)]
|
||||||
|
Self {
|
||||||
|
// use something other than the default so you can see the reset progress
|
||||||
|
branch_history: (-1i8).cast_to_static::<UInt<_>>(),
|
||||||
|
// 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())
|
||||||
|
}),
|
||||||
|
config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResetSteps for BrPredStageState<PhantomConst<CpuConfig>> {
|
||||||
|
#[hdl]
|
||||||
|
fn reset_step(this: &mut SimValue<Self>, step: usize) -> ResetStatus {
|
||||||
|
#[hdl(sim)]
|
||||||
|
let Self {
|
||||||
|
branch_history,
|
||||||
|
branch_predictor,
|
||||||
|
config: _,
|
||||||
|
} = this;
|
||||||
|
**branch_history = 0u8.cast_to_static::<UInt<_>>();
|
||||||
|
ResetSteps::reset_step(branch_predictor, step)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stage for BrPredStageState<PhantomConst<CpuConfig>> {
|
||||||
|
type Inputs = NextPcStageOutput<PhantomConst<CpuConfig>>;
|
||||||
|
type Output = BrPredStageOutput<PhantomConst<CpuConfig>>;
|
||||||
|
type MaxOutputCount = ConstUsize<1>;
|
||||||
|
|
||||||
|
fn output_ty(config: PhantomConst<CpuConfig>) -> Self::Output {
|
||||||
|
BrPredStageOutput[config]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn max_output_count(
|
||||||
|
_config: PhantomConst<CpuConfig>,
|
||||||
|
) -> <Self::MaxOutputCount as Size>::SizeType {
|
||||||
|
ConstUsize
|
||||||
|
}
|
||||||
|
|
||||||
|
#[hdl]
|
||||||
|
fn run(
|
||||||
|
state: &mut SimValue<Self>,
|
||||||
|
inputs: &SimValue<Self::Inputs>,
|
||||||
|
) -> SimValue<StageOutput<Self::Output, Self::MaxOutputCount, PhantomConst<CpuConfig>>> {
|
||||||
|
let config = state.config.ty();
|
||||||
|
#[hdl(sim)]
|
||||||
|
let NextPcStageOutput::<_> {
|
||||||
|
start_pc,
|
||||||
|
next_start_pc,
|
||||||
|
btb_entry,
|
||||||
|
fetch_block_id,
|
||||||
|
config: _,
|
||||||
|
} = inputs;
|
||||||
|
#[hdl(sim)]
|
||||||
|
if let HdlSome(btb_entry) = btb_entry {
|
||||||
|
let taken = #[hdl(sim)]
|
||||||
|
match &btb_entry.1.addr_kind {
|
||||||
|
BTBEntryAddrKind::Unconditional | BTBEntryAddrKind::Indirect => None,
|
||||||
|
BTBEntryAddrKind::CondTaken => Some(true),
|
||||||
|
BTBEntryAddrKind::CondNotTaken => Some(false),
|
||||||
|
};
|
||||||
|
if let Some(taken) = taken {
|
||||||
|
let index = Self::branch_predictor_index(
|
||||||
|
state,
|
||||||
|
BTBEntry::branch_pc(
|
||||||
|
&#[hdl(sim)]
|
||||||
|
BTBEntry {
|
||||||
|
start_pc,
|
||||||
|
rest: &btb_entry.1,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if taken != BranchPredictionState::is_taken(&state.branch_predictor[index]) {
|
||||||
|
let retval = #[hdl(sim)]
|
||||||
|
StageOutput::<_, _, _> {
|
||||||
|
outputs: Self::stage_output_ty(config).outputs.sim_value_default(),
|
||||||
|
cancel: #[hdl(sim)]
|
||||||
|
(HdlOption[Cancel[config]]).HdlSome(
|
||||||
|
#[hdl(sim)]
|
||||||
|
Cancel::<_> {
|
||||||
|
call_stack: todo!(),
|
||||||
|
start_pc: todo!(),
|
||||||
|
new_btb_entry: todo!(),
|
||||||
|
btb_entry_index: todo!(),
|
||||||
|
config: todo!(),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
};
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let output = #[hdl(sim)]
|
||||||
|
BrPredStageOutput::<_> { config };
|
||||||
|
#[hdl(sim)]
|
||||||
|
StageOutput::<_, _, _> {
|
||||||
|
outputs: Self::stage_output_ty(config).outputs.new_full_sim([output]),
|
||||||
|
cancel: #[hdl(sim)]
|
||||||
|
(HdlOption[Cancel[config]]).HdlNone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cancel(state: &mut SimValue<Self>, cancel: &SimValue<Cancel<PhantomConst<CpuConfig>>>) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[hdl(no_static)]
|
||||||
|
struct FetchOpInFetchDecode<C: PhantomConstGet<CpuConfig>> {
|
||||||
|
start_pc: UInt<64>,
|
||||||
|
config: C,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: Type + PhantomConstGet<CpuConfig>> FetchOpInFetchDecode<C> {
|
||||||
|
#[hdl]
|
||||||
|
fn from_fetch_op_in_next_pc(v: SimValue<FetchOpInNextPc<C>>) -> SimValue<Self> {
|
||||||
|
#[hdl(sim)]
|
||||||
|
let FetchOpInNextPc::<_> { start_pc, config } = v;
|
||||||
|
#[hdl(sim)]
|
||||||
|
Self { start_pc, config }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[hdl(no_static)]
|
||||||
|
struct InsnInPostDecode<C: PhantomConstGet<CpuConfig>> {
|
||||||
|
insn: WipDecodedInsn,
|
||||||
|
config: C,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: Type + PhantomConstGet<CpuConfig>> InsnInPostDecode<C> {
|
||||||
|
#[hdl]
|
||||||
|
fn from_fetch_op_in_fetch_decode(
|
||||||
|
fetch_op: SimValue<FetchOpInFetchDecode<C>>,
|
||||||
|
insn: SimValue<WipDecodedInsn>,
|
||||||
|
) -> SimValue<InsnInPostDecode<C>> {
|
||||||
|
#[hdl(sim)]
|
||||||
|
let FetchOpInFetchDecode::<_> {} = fetch_op;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[hdl(no_static)]
|
||||||
|
struct InsnInRenameDispatchExecute<C: PhantomConstGet<CpuConfig>> {
|
||||||
|
insn: WipDecodedInsn,
|
||||||
|
config: C,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: Type + PhantomConstGet<CpuConfig>> InsnInRenameDispatchExecute<C> {
|
||||||
|
#[hdl]
|
||||||
|
fn from_insn_in_post_decode(v: SimValue<InsnInPostDecode<C>>) -> SimValue<Self> {
|
||||||
|
#[hdl(sim)]
|
||||||
|
let InsnInPostDecode::<_> { insn, config } = v;
|
||||||
|
#[hdl(sim)]
|
||||||
|
Self { insn, config }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[hdl(no_static)]
|
||||||
|
struct InsnInRetire<C: PhantomConstGet<CpuConfig>> {
|
||||||
|
insn: WipDecodedInsn,
|
||||||
|
config: C,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: Type + PhantomConstGet<CpuConfig>> InsnInRetire<C> {
|
||||||
|
#[hdl]
|
||||||
|
fn from_insn_in_rename_dispatch_execute(
|
||||||
|
v: SimValue<InsnInRenameDispatchExecute<C>>,
|
||||||
|
) -> SimValue<Self> {
|
||||||
|
#[hdl(sim)]
|
||||||
|
let InsnInRenameDispatchExecute::<_> { insn, config } = v;
|
||||||
|
#[hdl(sim)]
|
||||||
|
Self { insn, config }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[hdl]
|
#[hdl]
|
||||||
enum BranchPredictionState {
|
enum BranchPredictionState {
|
||||||
StronglyNotTaken,
|
StronglyNotTaken,
|
||||||
|
|
@ -221,6 +723,12 @@ impl<T: SimOnlyValueTrait> SimValueDefault for SimOnly<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: SimValueDefault, N: KnownSize> SimValueDefault for ArrayVec<T, N> {
|
||||||
|
fn sim_value_default(self) -> SimValue<Self> {
|
||||||
|
self.new_sim(self.element().sim_value_default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: Type> SimValueDefault for HdlOption<T> {
|
impl<T: Type> SimValueDefault for HdlOption<T> {
|
||||||
fn sim_value_default(self) -> SimValue<Self> {
|
fn sim_value_default(self) -> SimValue<Self> {
|
||||||
self.HdlNone().to_sim_value_with_type(self)
|
self.HdlNone().to_sim_value_with_type(self)
|
||||||
|
|
@ -411,27 +919,51 @@ impl BTBEntryAddrKind {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[hdl]
|
#[hdl]
|
||||||
struct BTBEntry {
|
struct BTBEntryWithoutStartPc {
|
||||||
/// address of first instruction to run in this fetch block
|
|
||||||
start_pc: UInt<64>,
|
|
||||||
target_pc: UInt<64>,
|
target_pc: UInt<64>,
|
||||||
/// when branch is not taken, the next pc to fetch from is `start_pc + fallthrough_offset`.
|
/// when branch is not taken, the next pc to fetch from is `start_pc + fallthrough_offset`.
|
||||||
/// needed because there may be more than one branch in a fetch block
|
/// needed because there may be more than one branch in a fetch block
|
||||||
fallthrough_offset: UInt<8>,
|
fallthrough_offset: UInt<8>,
|
||||||
|
/// the pc to use for branch prediction is `start_pc + branch_offset`
|
||||||
|
branch_offset: UInt<8>,
|
||||||
/// when a call is made, the return address is `start_pc + after_call_offset`
|
/// when a call is made, the return address is `start_pc + after_call_offset`
|
||||||
after_call_offset: UInt<8>,
|
after_call_offset: UInt<8>,
|
||||||
insn_kind: BTBEntryInsnKind,
|
insn_kind: BTBEntryInsnKind,
|
||||||
addr_kind: BTBEntryAddrKind,
|
addr_kind: BTBEntryAddrKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[hdl]
|
||||||
|
struct BTBEntry {
|
||||||
|
/// address of first instruction to run in this fetch block
|
||||||
|
start_pc: UInt<64>,
|
||||||
|
rest: BTBEntryWithoutStartPc,
|
||||||
|
}
|
||||||
|
|
||||||
impl BTBEntry {
|
impl BTBEntry {
|
||||||
fn taken_pc(this: &SimValue<Self>) -> u64 {
|
fn taken_pc(this: &SimValue<Self>) -> u64 {
|
||||||
this.target_pc.as_int()
|
this.rest.target_pc.as_int()
|
||||||
}
|
}
|
||||||
fn not_taken_fetch_pc(this: &SimValue<Self>) -> u64 {
|
fn not_taken_start_pc(this: &SimValue<Self>) -> u64 {
|
||||||
|
Self::fallthrough_pc(this)
|
||||||
|
}
|
||||||
|
/// when branch is not taken, this returns the next pc to fetch from.
|
||||||
|
/// needed because there may be more than one branch in a fetch block
|
||||||
|
fn fallthrough_pc(this: &SimValue<Self>) -> u64 {
|
||||||
this.start_pc
|
this.start_pc
|
||||||
.as_int()
|
.as_int()
|
||||||
.wrapping_add(this.fallthrough_offset.as_int().into())
|
.wrapping_add(this.rest.fallthrough_offset.as_int().into())
|
||||||
|
}
|
||||||
|
/// the pc to use for branch prediction
|
||||||
|
fn branch_pc(this: &SimValue<Self>) -> u64 {
|
||||||
|
this.start_pc
|
||||||
|
.as_int()
|
||||||
|
.wrapping_add(this.rest.branch_offset.as_int().into())
|
||||||
|
}
|
||||||
|
/// when a call is made, this gives the return address
|
||||||
|
fn after_call_pc(this: &SimValue<Self>) -> u64 {
|
||||||
|
this.start_pc
|
||||||
|
.as_int()
|
||||||
|
.wrapping_add(this.rest.after_call_offset.as_int().into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -489,11 +1021,15 @@ impl SimValueDefault for BranchTargetBuffer {
|
||||||
#[hdl(sim)]
|
#[hdl(sim)]
|
||||||
BTBEntry {
|
BTBEntry {
|
||||||
start_pc: !0u64,
|
start_pc: !0u64,
|
||||||
target_pc: !0u64,
|
rest: #[hdl(sim)]
|
||||||
fallthrough_offset: !0u8,
|
BTBEntryWithoutStartPc {
|
||||||
after_call_offset: !0u8,
|
target_pc: !0u64,
|
||||||
insn_kind: BTBEntryInsnKind.Call(),
|
fallthrough_offset: !0u8,
|
||||||
addr_kind: BTBEntryAddrKind.CondNotTaken(),
|
branch_offset: !0u8,
|
||||||
|
after_call_offset: !0u8,
|
||||||
|
insn_kind: BTBEntryInsnKind.Call(),
|
||||||
|
addr_kind: BTBEntryAddrKind.CondNotTaken(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
); Self::SIZE],
|
); Self::SIZE],
|
||||||
next_index_to_replace_lfsr: LFSR31.sim_value_default(),
|
next_index_to_replace_lfsr: LFSR31.sim_value_default(),
|
||||||
|
|
@ -1007,13 +1543,13 @@ impl<C: Type + PhantomConstGet<CpuConfig>> NextPcState<C> {
|
||||||
// for now we just truncate the fetch block right before the second ctrl transfer insn.
|
// for now we just truncate the fetch block right before the second ctrl transfer insn.
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
btb_entry_fields = Some((insn_kind, addr_kind, target_pc));
|
|
||||||
fallthrough_offset += size_in_bytes.cast_to_static::<UInt<8>>().as_int();
|
|
||||||
#[hdl(sim)]
|
#[hdl(sim)]
|
||||||
match insn_kind {
|
match &insn_kind {
|
||||||
BTBEntryInsnKind::Call => after_call_offset = fallthrough_offset,
|
BTBEntryInsnKind::Call => after_call_offset = fallthrough_offset,
|
||||||
BTBEntryInsnKind::Branch | BTBEntryInsnKind::Ret | BTBEntryInsnKind::Unknown => {}
|
BTBEntryInsnKind::Branch | BTBEntryInsnKind::Ret | BTBEntryInsnKind::Unknown => {}
|
||||||
}
|
}
|
||||||
|
btb_entry_fields = Some((insn_kind, addr_kind, target_pc));
|
||||||
|
fallthrough_offset += size_in_bytes.cast_to_static::<UInt<8>>().as_int();
|
||||||
}
|
}
|
||||||
let new_next_pc = if let Some((insn_kind, addr_kind, mut target_pc)) = btb_entry_fields {
|
let new_next_pc = if let Some((insn_kind, addr_kind, mut target_pc)) = btb_entry_fields {
|
||||||
// add/update BTBEntry if it doesn't match
|
// add/update BTBEntry if it doesn't match
|
||||||
|
|
@ -1046,15 +1582,22 @@ impl<C: Type + PhantomConstGet<CpuConfig>> NextPcState<C> {
|
||||||
BranchTargetBuffer::next_index_to_replace(&mut this.branch_target_buffer)
|
BranchTargetBuffer::next_index_to_replace(&mut this.branch_target_buffer)
|
||||||
});
|
});
|
||||||
let new_next_pc = #[hdl(sim)]
|
let new_next_pc = #[hdl(sim)]
|
||||||
match insn_kind {
|
match &insn_kind {
|
||||||
BTBEntryInsnKind::Branch => {}
|
BTBEntryInsnKind::Branch => {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
BTBEntryInsnKind::Call => {
|
BTBEntryInsnKind::Call => {
|
||||||
CallStack::push(&mut this.speculative_call_stack, todo!());
|
CallStack::push(
|
||||||
|
&mut this.speculative_call_stack,
|
||||||
|
start_pc + u64::from(after_call_offset),
|
||||||
|
);
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
BTBEntryInsnKind::Ret => {
|
BTBEntryInsnKind::Ret => {
|
||||||
target_pc = CallStack::pop(&mut this.speculative_call_stack).or(target_pc);
|
target_pc = CallStack::pop(&mut this.speculative_call_stack).or(target_pc);
|
||||||
|
target_pc.unwrap_or(0u64)
|
||||||
}
|
}
|
||||||
|
BTBEntryInsnKind::Unknown => unreachable!(),
|
||||||
};
|
};
|
||||||
let new_entry = #[hdl(sim)]
|
let new_entry = #[hdl(sim)]
|
||||||
BTBEntry {
|
BTBEntry {
|
||||||
|
|
|
||||||
25
crates/cpu/src/next_pc/next_pc.mermaid
Normal file
25
crates/cpu/src/next_pc/next_pc.mermaid
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
stateDiagram-v2
|
||||||
|
direction LR
|
||||||
|
|
||||||
|
state "Next PC" as next_pc
|
||||||
|
[*] --> next_pc
|
||||||
|
|
||||||
|
state "Fetch/Decode" as fetch_decode
|
||||||
|
next_pc --> fetch_decode
|
||||||
|
|
||||||
|
state "Branch Predictor" as br_pred
|
||||||
|
next_pc --> br_pred
|
||||||
|
br_pred --> next_pc: cancel following
|
||||||
|
|
||||||
|
state "Post-decode" as post_decode
|
||||||
|
fetch_decode --> post_decode
|
||||||
|
br_pred --> post_decode
|
||||||
|
post_decode --> next_pc: cancel following
|
||||||
|
|
||||||
|
state "Rename\nDispatch\nExecute" as execute
|
||||||
|
post_decode --> execute
|
||||||
|
|
||||||
|
state "Retire" as retire
|
||||||
|
execute --> retire
|
||||||
|
retire --> [*]
|
||||||
|
retire --> next_pc: cancel following
|
||||||
|
|
@ -34,6 +34,18 @@ impl<T: Type, N: Size> ArrayVec<T, N> {
|
||||||
len: 0u8.cast_to(self.len),
|
len: 0u8.cast_to(self.len),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[hdl]
|
||||||
|
pub fn new_full_sim(
|
||||||
|
self,
|
||||||
|
elements: impl ToSimValueWithType<ArrayType<T, N>>,
|
||||||
|
) -> SimValue<Self> {
|
||||||
|
let elements = elements.to_sim_value_with_type(self.elements);
|
||||||
|
#[hdl(sim)]
|
||||||
|
Self {
|
||||||
|
elements,
|
||||||
|
len: self.elements.len().to_sim_value_with_type(self.len),
|
||||||
|
}
|
||||||
|
}
|
||||||
pub fn element(self) -> T {
|
pub fn element(self) -> T {
|
||||||
self.elements.element()
|
self.elements.element()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue