From 033d5d4f346b8d713e3752a8a1ab01253fa244ec Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Fri, 28 Nov 2025 01:41:18 -0800 Subject: [PATCH] WIP adding next_pc: add call stack --- crates/cpu/src/next_pc.rs | 185 ++++++++++++++++++++++++++++---------- 1 file changed, 136 insertions(+), 49 deletions(-) diff --git a/crates/cpu/src/next_pc.rs b/crates/cpu/src/next_pc.rs index afabd90..7c62d72 100644 --- a/crates/cpu/src/next_pc.rs +++ b/crates/cpu/src/next_pc.rs @@ -17,7 +17,6 @@ use fayalite::{ int::{UIntInRange, UIntInRangeInclusive, UIntInRangeType}, prelude::*, sim::value::SimOnlyValueTrait, - ty::StaticType, util::ready_valid::ReadyValid, }; @@ -77,11 +76,9 @@ pub enum WipDecodedInsnKind { 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. @@ -98,11 +95,9 @@ impl WipDecodedInsnKind { | Self::Branch(_) | Self::BranchCond(_) | Self::IndirectBranch - | Self::IndirectBranchCond | Self::Call(_) | Self::CallCond(_) | Self::IndirectCall - | Self::IndirectCallCond | Self::Ret | Self::RetCond | Self::Unknown => None, @@ -267,10 +262,33 @@ impl ResetSteps for ArrayType { struct CallStack { return_addresses: Array, { CallStack::SIZE }>, len: UIntInRangeInclusive<0, { CallStack::SIZE }>, + top: UIntInRange<0, { CallStack::SIZE }>, } impl CallStack { const SIZE: usize = 16; + fn push(this: &mut SimValue, value: impl ToSimValue>) { + let new_len = *this.len + 1; + *this.len = if new_len > Self::SIZE { + Self::SIZE + } else { + new_len + }; + let top = *this.top; + this.return_addresses[top] = value.into_sim_value(); + *this.top = (top + 1) % Self::SIZE; + } + fn pop(this: &mut SimValue) -> Option { + if *this.len == 0 { + None + } else { + *this.len -= 1; + let top = *this.top; + let top = (top + Self::SIZE - 1) % Self::SIZE; + *this.top = top; + Some(this.return_addresses[top].as_int()) + } + } } impl SimValueDefault for CallStack { @@ -281,6 +299,7 @@ impl SimValueDefault for 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), + top: 0usize.to_sim_value_with_type(self.top), } } } @@ -292,10 +311,12 @@ impl ResetSteps for CallStack { let CallStack { return_addresses, len, + top, } = 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; + **top = 0; ResetStatus::Done } } @@ -315,15 +336,13 @@ impl BTBEntryInsnKind { WipDecodedInsnKind::NonBranch => None, WipDecodedInsnKind::Branch(_) | WipDecodedInsnKind::BranchCond(_) - | WipDecodedInsnKind::IndirectBranch - | WipDecodedInsnKind::IndirectBranchCond => Some( + | WipDecodedInsnKind::IndirectBranch => Some( #[hdl(sim)] BTBEntryInsnKind::Branch(), ), WipDecodedInsnKind::Call(_) | WipDecodedInsnKind::CallCond(_) - | WipDecodedInsnKind::IndirectCall - | WipDecodedInsnKind::IndirectCallCond => Some( + | WipDecodedInsnKind::IndirectCall => Some( #[hdl(sim)] BTBEntryInsnKind::Call(), ), @@ -385,11 +404,6 @@ impl BTBEntryAddrKind { #[hdl(sim)] Self::Indirect(), ), - WipDecodedInsnKind::IndirectBranchCond | WipDecodedInsnKind::IndirectCallCond => Some( - // our conditional branch prediction doesn't work with indirect branches - #[hdl(sim)] - Self::Indirect(), - ), WipDecodedInsnKind::Interrupt(_) => None, WipDecodedInsnKind::Unknown => None, } @@ -404,6 +418,8 @@ struct BTBEntry { /// 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 fallthrough_offset: UInt<8>, + /// when a call is made, the return address is `start_pc + after_call_offset` + after_call_offset: UInt<8>, insn_kind: BTBEntryInsnKind, addr_kind: BTBEntryAddrKind, } @@ -419,13 +435,48 @@ impl BTBEntry { } } +#[hdl] +struct LFSR31 { + // MSB is always zero, 32 bits makes it easier to manipulate + state: UInt<32>, +} + +impl SimValueDefault for LFSR31 { + #[hdl] + fn sim_value_default(self) -> SimValue { + #[hdl(sim)] + Self { state: 1u32 } + } +} + +impl LFSR31 { + fn next(this: &mut SimValue) -> u32 { + let state = this.state.as_int(); + let state = if state == 0 { + 1u32 + } else { + // a maximal-length 31-bit LFSR + let lsb = ((state >> 30) ^ (state >> 27)) & 1; + let msb = (state << 1) & ((1 << 31) - 1); + lsb | msb + }; + *this.state = state.into(); + state + } +} + #[hdl] struct BranchTargetBuffer { branch_pc_to_target_map: Array, { BranchTargetBuffer::SIZE }>, + next_index_to_replace_lfsr: LFSR31, } impl BranchTargetBuffer { - const SIZE: usize = 16; + const LOG2_SIZE: usize = 4; + const SIZE: usize = 1 << Self::LOG2_SIZE; + fn next_index_to_replace(this: &mut SimValue) -> usize { + LFSR31::next(&mut this.next_index_to_replace_lfsr) as usize % Self::SIZE + } } impl SimValueDefault for BranchTargetBuffer { @@ -440,10 +491,12 @@ impl SimValueDefault for BranchTargetBuffer { start_pc: !0u64, target_pc: !0u64, fallthrough_offset: !0u8, + after_call_offset: !0u8, insn_kind: BTBEntryInsnKind.Call(), addr_kind: BTBEntryAddrKind.CondNotTaken(), }, ); Self::SIZE], + next_index_to_replace_lfsr: LFSR31.sim_value_default(), } } } @@ -454,7 +507,9 @@ impl ResetSteps for BranchTargetBuffer { #[hdl(sim)] let BranchTargetBuffer { branch_pc_to_target_map, + next_index_to_replace_lfsr, } = this; + *next_index_to_replace_lfsr = LFSR31.sim_value_default(); ResetSteps::reset_step(branch_pc_to_target_map, step) } } @@ -850,11 +905,11 @@ impl> NextPcState { fetch_block_id: expected_fetch_block_id, btb_entry, btb_entry_index, - next_pc, + next_pc: orig_next_pc, } = fetch_queue_entry; let insns = ArrayVec::elements_sim_ref(&insns); if let Some(target_pc) = WipDecodedInsnKind::interrupt_target_pc_sim(&insns[0].kind) { - if **target_pc != *next_pc { + if *target_pc != orig_next_pc { *this.cancel_in_progress_fetches = true; this.pc = target_pc.clone(); } @@ -862,6 +917,7 @@ impl> NextPcState { } let start_pc = insns[0].pc.as_int(); let mut fallthrough_offset = 0u8; + let mut after_call_offset = 0u8; let mut btb_entry_fields = None; let mut eval_cond_branch = || -> SimValue { todo!(); @@ -889,13 +945,13 @@ impl> NextPcState { BTBEntryInsnKind::Branch(); addr_kind = #[hdl(sim)] BTBEntryAddrKind::Unconditional(); - Some(target_pc.clone()) + Some(target_pc.as_int()) } WipDecodedInsnKind::BranchCond(target_pc) => { insn_kind = #[hdl(sim)] BTBEntryInsnKind::Branch(); addr_kind = eval_cond_branch(); - Some(target_pc.clone()) + Some(target_pc.as_int()) } WipDecodedInsnKind::IndirectBranch => { insn_kind = #[hdl(sim)] @@ -904,26 +960,18 @@ impl> NextPcState { BTBEntryAddrKind::Indirect(); None } - WipDecodedInsnKind::IndirectBranchCond => { - // our conditional branch prediction doesn't work with indirect branches - insn_kind = #[hdl(sim)] - BTBEntryInsnKind::Branch(); - addr_kind = #[hdl(sim)] - BTBEntryAddrKind::Indirect(); - None - } WipDecodedInsnKind::Call(target_pc) => { insn_kind = #[hdl(sim)] BTBEntryInsnKind::Call(); addr_kind = #[hdl(sim)] BTBEntryAddrKind::Unconditional(); - Some(target_pc.clone()) + Some(target_pc.as_int()) } WipDecodedInsnKind::CallCond(target_pc) => { insn_kind = #[hdl(sim)] BTBEntryInsnKind::Call(); addr_kind = eval_cond_branch(); - Some(target_pc.clone()) + Some(target_pc.as_int()) } WipDecodedInsnKind::IndirectCall => { insn_kind = #[hdl(sim)] @@ -932,14 +980,6 @@ impl> NextPcState { BTBEntryAddrKind::Indirect(); None } - WipDecodedInsnKind::IndirectCallCond => { - // our conditional branch prediction doesn't work with indirect calls - insn_kind = #[hdl(sim)] - BTBEntryInsnKind::Call(); - addr_kind = #[hdl(sim)] - BTBEntryAddrKind::Indirect(); - None - } WipDecodedInsnKind::Ret => { insn_kind = #[hdl(sim)] BTBEntryInsnKind::Ret(); @@ -969,28 +1009,70 @@ impl> NextPcState { } btb_entry_fields = Some((insn_kind, addr_kind, target_pc)); fallthrough_offset += size_in_bytes.cast_to_static::>().as_int(); + #[hdl(sim)] + match insn_kind { + BTBEntryInsnKind::Call => after_call_offset = fallthrough_offset, + BTBEntryInsnKind::Branch | BTBEntryInsnKind::Ret | BTBEntryInsnKind::Unknown => {} + } } - if let Some((insn_kind, addr_kind, target_pc)) = btb_entry_fields { - let expected_btb_entry = #[hdl(sim)] + 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 + let btb_entry_index = #[hdl(sim)] + if let HdlSome(btb_entry) = btb_entry { + // verify it hasn't been changed meanwhile + #[hdl(sim)] + if let HdlSome(entry) = + &this.branch_target_buffer.branch_pc_to_target_map[*btb_entry_index] + { + // we have a btb entry, check if it has been modified + if entry.start_pc == btb_entry.start_pc { + // we found the correct BTBEntry + if target_pc.is_none() { + // save the existing target_pc if we know it + target_pc = Some(entry.target_pc.as_int()); + } + Some(*btb_entry_index) + } else { + None + } + } else { + None + } + } else { + None + }; + let btb_entry_index = btb_entry_index.unwrap_or_else(|| { + // we need to add a new entry, pick an entry to replace + BranchTargetBuffer::next_index_to_replace(&mut this.branch_target_buffer) + }); + let new_next_pc = #[hdl(sim)] + match insn_kind { + BTBEntryInsnKind::Branch => {} + BTBEntryInsnKind::Call => { + CallStack::push(&mut this.speculative_call_stack, todo!()); + todo!() + } + BTBEntryInsnKind::Ret => { + target_pc = CallStack::pop(&mut this.speculative_call_stack).or(target_pc); + } + }; + let new_entry = #[hdl(sim)] BTBEntry { start_pc, - target_pc: target_pc.unwrap_or_else(|| 0u64.to_sim_value()), + target_pc: target_pc.unwrap_or(0u64), fallthrough_offset, + after_call_offset, insn_kind, addr_kind, }; - // add/update BTBEntry if it doesn't match - #[hdl(sim)] - if let HdlSome(btb_entry) = btb_entry { - todo!() - } else { - // add BTBEntry - todo!() - } + let entry_mut = &mut this.branch_target_buffer.branch_pc_to_target_map[btb_entry_index]; + *entry_mut = #[hdl(sim)] + HdlSome(new_entry); + new_next_pc } else { #[hdl(sim)] if let HdlSome(btb_entry) = btb_entry { - // remove BTBEntry + // the fetched instructions do not need a BTBEntry, remove the BTBEntry if it still exists let entry_mut = &mut this.branch_target_buffer.branch_pc_to_target_map[*btb_entry_index]; // verify it hasn't been changed meanwhile @@ -1002,6 +1084,11 @@ impl> NextPcState { } } } + start_pc + u64::from(fallthrough_offset) + }; + if new_next_pc != orig_next_pc.as_int() { + *this.cancel_in_progress_fetches = true; + *this.pc = new_next_pc.into(); } } }