use crate::arch::reservation::LrScRecord;
use crate::arch::translation::{DirtyUpdates, SfenceVmaInfo};
use crate::common::{InstSeq, PhysAddr, VirtAddr};
use crate::exec::inst::Inst;
use crate::exec::signals::ControlSignals;
use crate::isa::instruction::InstSize;
use crate::isa::privileged::Trap;
use crate::isa::reg::RegIdx;
use crate::isa::rvv::{ElemIdx, Sew};
use crate::sim::memory::write_log::WriteSeq;
use crate::uarch::pipeline::backend::shared::vec_mem::VecMemAddrOp;
use crate::uarch::pipeline::exception::ExceptionStage;
use crate::uarch::pipeline::rename::prf::PhysReg;
use crate::uarch::pipeline::rename::vec_prf::VecPhysReg;
use crate::uarch::pipeline::rob::RobTag;
#[derive(Clone, Debug)]
pub struct Latch<T> {
entries: Vec<T>,
ready_at: u64,
delay: u64,
}
impl<T> Latch<T> {
#[must_use]
pub const fn new(delay: u64) -> Self {
Self { entries: Vec::new(), ready_at: 0, delay }
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.entries.is_empty()
}
#[cfg(test)]
#[must_use]
pub const fn len(&self) -> usize {
self.entries.len()
}
pub fn push(&mut self, now: u64, entries: impl IntoIterator<Item = T>) {
self.entries.extend(entries);
self.ready_at = now.saturating_add(self.delay);
}
#[must_use]
pub fn ready(&mut self, now: u64) -> Option<&mut Vec<T>> {
(!self.entries.is_empty() && self.ready_at <= now).then_some(&mut self.entries)
}
pub fn take(&mut self, now: u64) -> Vec<T> {
self.ready(now).map(std::mem::take).unwrap_or_default()
}
pub fn clear(&mut self) {
self.entries.clear();
}
#[must_use]
pub fn entries(&self) -> &[T] {
&self.entries
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct MicroOpIdx(usize);
impl MicroOpIdx {
#[must_use]
pub const fn new(index: usize) -> Self {
Self(index)
}
}
#[derive(Clone, Debug)]
pub struct VecMemAccess {
pub micro_op: MicroOpIdx,
pub is_store: bool,
pub target: VecMemTarget,
}
impl VecMemAccess {
#[must_use]
pub fn bytes(&self) -> usize {
match &self.target {
VecMemTarget::Element { eew, .. } => eew.bytes(),
VecMemTarget::Span(span) => span.bytes(),
}
}
}
#[derive(Clone, Debug)]
pub enum VecMemTarget {
Element {
elem_idx: ElemIdx,
eew: Sew,
vd_phys: VecPhysReg,
},
Span(Box<VecMemSpan>),
}
#[derive(Clone, Debug)]
pub struct VecMemSpan {
pub elements: Vec<(MicroOpIdx, VecMemAddrOp)>,
pub data: Option<Box<[u8]>>,
}
impl VecMemSpan {
#[must_use]
pub fn vaddr(&self) -> VirtAddr {
self.elements.first().map_or(VirtAddr::new(0), |(_, first)| first.vaddr)
}
#[must_use]
pub fn bytes(&self) -> usize {
let (Some((_, first)), Some((_, last))) = (self.elements.first(), self.elements.last())
else {
return 0;
};
(last.vaddr.val() - first.vaddr.val()) as usize + last.eew.bytes()
}
#[must_use]
pub fn offset_of(&self, element: &VecMemAddrOp) -> usize {
(element.vaddr.val() - self.vaddr().val()) as usize
}
}
#[derive(Clone, Default, Debug)]
pub struct IfIdEntry {
pub pc: u64,
pub inst: u32,
pub inst_size: InstSize,
pub pred_taken: bool,
pub pred_target: u64,
pub trap: Option<Trap>,
pub exception_stage: Option<ExceptionStage>,
pub seq: InstSeq,
}
#[derive(Clone, Default, Debug)]
pub struct IdExEntry {
pub inst: Inst,
pub trap: Option<Trap>,
pub exception_stage: Option<ExceptionStage>,
pub pred_taken: bool,
pub pred_target: u64,
pub seq: InstSeq,
}
#[derive(Clone, Default, Debug)]
pub struct Fetch1Fetch2Entry {
pub pc: u64,
pub paddr: PhysAddr,
pub upper_paddr: Option<PhysAddr>,
pub pred_taken: bool,
pub pred_target: u64,
pub trap: Option<Trap>,
pub exception_stage: Option<ExceptionStage>,
pub seq: InstSeq,
}
#[derive(Clone, Default, Debug)]
pub struct RenameIssueEntry {
pub rob_tag: RobTag,
pub inst: Inst,
pub rs1_tag: Option<RobTag>,
pub rs2_tag: Option<RobTag>,
pub rs3_tag: Option<RobTag>,
pub rs1_phys: PhysReg,
pub rs2_phys: PhysReg,
pub rs3_phys: PhysReg,
pub rd_phys: PhysReg,
pub trap: Option<Trap>,
pub exception_stage: Option<ExceptionStage>,
pub pred_taken: bool,
pub pred_target: u64,
pub seq: InstSeq,
pub vs1_phys: [VecPhysReg; 8],
pub vs2_phys: [VecPhysReg; 8],
pub vs3_phys: [VecPhysReg; 8],
pub vd_phys: [VecPhysReg; 8],
pub vec_src1_count: u8,
pub vec_src2_count: u8,
pub vec_src3_count: u8,
pub mask_phys: VecPhysReg,
pub vec_vtype: u64,
pub vec_vl: u64,
pub vec_vstart: u64,
pub vec_vxrm: u64,
pub vec_frm: u64,
}
#[derive(Clone, Debug, Default)]
pub struct ExMem1Entry {
pub rob_tag: RobTag,
pub pc: u64,
pub inst: u32,
pub inst_size: InstSize,
pub rd: RegIdx,
pub rd_phys: PhysReg,
pub alu: u64,
pub store_data: u64,
pub ctrl: ControlSignals,
pub trap: Option<Trap>,
pub exception_stage: Option<ExceptionStage>,
pub fp_flags: u8,
pub sfence_vma: Option<SfenceVmaInfo>,
pub vec_mem: Option<VecMemAccess>,
pub store_data_follows: bool,
pub replaying: bool,
}
#[derive(Clone, Default, Debug)]
pub struct Mem1Mem2Entry {
pub rob_tag: RobTag,
pub pc: u64,
pub inst: u32,
pub inst_size: InstSize,
pub rd: RegIdx,
pub rd_phys: PhysReg,
pub alu: u64,
pub vaddr: VirtAddr,
pub paddr: PhysAddr,
pub store_data: u64,
pub load_data: u64,
pub sb_forwarded: bool,
pub ctrl: ControlSignals,
pub trap: Option<Trap>,
pub exception_stage: Option<ExceptionStage>,
pub fp_flags: u8,
pub complete_cycle: u64,
pub dirty_updates: DirtyUpdates,
pub sfence_vma: Option<SfenceVmaInfo>,
pub vec_mem: Option<VecMemAccess>,
pub observed: Option<WriteSeq>,
}
impl ExMem1Entry {
pub const fn from_issue(id: &RenameIssueEntry, alu: u64, store_data: u64) -> Self {
Self {
rob_tag: id.rob_tag,
pc: id.inst.pc,
inst: id.inst.bits,
inst_size: id.inst.size,
rd: id.inst.rd,
rd_phys: id.rd_phys,
alu,
store_data,
ctrl: id.inst.ctrl,
trap: None,
exception_stage: None,
fp_flags: 0,
sfence_vma: None,
vec_mem: None,
store_data_follows: false,
replaying: false,
}
}
}
impl Mem1Mem2Entry {
pub fn from_execute(ex: ExMem1Entry, vaddr: VirtAddr, paddr: PhysAddr) -> Self {
Self {
rob_tag: ex.rob_tag,
pc: ex.pc,
inst: ex.inst,
inst_size: ex.inst_size,
rd: ex.rd,
rd_phys: ex.rd_phys,
alu: ex.alu,
vaddr,
paddr,
store_data: ex.store_data,
load_data: 0,
sb_forwarded: false,
ctrl: ex.ctrl,
trap: ex.trap,
exception_stage: ex.exception_stage,
fp_flags: ex.fp_flags,
complete_cycle: 0,
dirty_updates: DirtyUpdates::NONE,
sfence_vma: ex.sfence_vma,
vec_mem: ex.vec_mem,
observed: None,
}
}
}
#[derive(Clone, Default, Debug)]
pub struct Mem2WbEntry {
pub rob_tag: RobTag,
pub pc: u64,
pub inst: u32,
pub inst_size: InstSize,
pub rd: RegIdx,
pub rd_phys: PhysReg,
pub alu: u64,
pub load_data: u64,
pub ctrl: ControlSignals,
pub trap: Option<Trap>,
pub exception_stage: Option<ExceptionStage>,
pub fp_flags: u8,
pub dirty_updates: DirtyUpdates,
pub sfence_vma: Option<SfenceVmaInfo>,
pub lr_sc: Option<LrScRecord>,
pub vec_mem: Option<VecMemAccess>,
pub observed: Option<WriteSeq>,
}
impl Mem2WbEntry {
pub fn from_memory2(mem: Mem1Mem2Entry, load_data: u64, lr_sc: Option<LrScRecord>) -> Self {
Self {
rob_tag: mem.rob_tag,
pc: mem.pc,
inst: mem.inst,
inst_size: mem.inst_size,
rd: mem.rd,
rd_phys: mem.rd_phys,
alu: mem.alu,
load_data,
ctrl: mem.ctrl,
trap: mem.trap,
exception_stage: mem.exception_stage,
fp_flags: mem.fp_flags,
dirty_updates: mem.dirty_updates,
sfence_vma: mem.sfence_vma,
lr_sc,
vec_mem: mem.vec_mem,
observed: mem.observed,
}
}
}
#[cfg(test)]
mod latch_tests {
use super::Latch;
#[test]
fn a_bundle_is_visible_after_the_latch_delay() {
let mut latch = Latch::new(1);
latch.push(10, [1, 2]);
assert!(latch.ready(10).is_none(), "written this cycle, not visible yet");
assert_eq!(latch.ready(11).map(|bundle| bundle.clone()), Some(vec![1, 2]));
}
#[test]
fn a_zero_delay_latch_is_visible_the_cycle_it_is_written() {
let mut latch = Latch::new(0);
latch.push(5, [7]);
assert_eq!(latch.take(5), vec![7]);
assert!(latch.is_empty());
}
#[test]
fn the_consumer_may_leave_part_of_the_bundle_for_later() {
let mut latch = Latch::new(1);
latch.push(0, [1, 2, 3]);
let _ = latch.ready(1).map(|bundle| bundle.drain(..2));
assert_eq!(latch.take(1), vec![3]);
}
#[test]
fn take_before_the_delay_returns_nothing_and_keeps_the_bundle() {
let mut latch = Latch::new(2);
latch.push(0, [9]);
assert!(latch.take(1).is_empty());
assert_eq!(latch.len(), 1);
assert_eq!(latch.take(2), vec![9]);
}
}