use super::gate::older_stores_pending;
use super::is_pure_ram;
use crate::common::{PhysAddr, crosses_cache_line};
use crate::exec::cbo::CboEffect;
use crate::isa::encoding::zicboz::CBOZ_BLOCK_SIZE;
use crate::isa::op::MemWidth;
use crate::sim::components::{ComponentId, ReqId};
use crate::sim::packet::{AccessSize, Maintenance, MemOp, Packet, WriteData, WriteOrigin};
use crate::trace_commit;
use crate::uarch::ctx::CoreCtx;
use crate::uarch::pipeline::engine::BackendCommon;
use crate::uarch::pipeline::lsq::store_buffer::{StoreBuffer, StoreData, width_to_bytes};
use crate::uarch::pipeline::lsq::vec_store_buffer::{VSB_LINE_BYTES, VecStoreBuffer};
use crate::uarch::pipeline::lsq::write_buffer::WcbLine;
use crate::uarch::pipeline::outstanding::{OutstandingStore, StoreOwner};
pub(super) fn try_drain_one_store(
state: &mut CoreCtx<'_>,
common: &mut BackendCommon,
store_buffer: &mut StoreBuffer,
) -> bool {
let Some(write) = store_buffer.begin_write() else { return false };
let requests = match write.data {
StoreData::Bytes(data) => send_data_store(state, common, write.paddr, data, write.width),
StoreData::Block(effect) => {
if state.core.wcb.request_send(write.paddr, CBOZ_BLOCK_SIZE as usize) {
return false;
}
vec![send_block_op(state, common, write.paddr, effect)]
}
};
store_buffer.issue_write(write, &requests);
true
}
pub(super) fn send_data_store(
state: &mut CoreCtx<'_>,
common: &mut BackendCommon,
paddr: PhysAddr,
data: u64,
width: MemWidth,
) -> Vec<ReqId> {
let via_wcb = !state.core.wcb.is_disabled() && is_pure_ram(state, paddr, width);
trace_commit!(state.config.general.trace_instructions;
paddr = %crate::common::trace::Hex(paddr.val()),
data = %crate::common::trace::Hex(data),
width = ?width,
via_wcb = via_wcb,
"CM: committed store's write sent to memory"
);
if !via_wcb {
let hart = WriteOrigin::Hart(state.hart.hart_id);
let store = StoreWrite { origin: hart, owner: StoreOwner::StoreBuffer };
return emit_store_write_packet(state, common, paddr, data, width, store);
}
let span = state.core.wcb.entry_bytes();
for (part_paddr, part_data, part_bytes) in span_parts(paddr, data, width_to_bytes(width), span)
{
if state.core.wcb.holds_entry_for(part_paddr) {
state.uncore.stats.counter(state.core.stat_paths.wcb.coalesces).inc();
}
if let Some(evicted) = state.core.wcb.merge_store(part_paddr, part_data, part_bytes) {
send_wcb_line(state, common, &evicted);
}
}
Vec::new()
}
pub(super) fn send_block_op(
state: &mut CoreCtx<'_>,
common: &mut BackendCommon,
block: PhysAddr,
effect: CboEffect,
) -> ReqId {
let maintain = |op| MemOp::Maintain { op, dirty: false };
let op = match effect {
CboEffect::Zero => MemOp::Write {
data: WriteData::Line {
bytes: vec![0; CBOZ_BLOCK_SIZE as usize].into(),
mask: u64::MAX,
},
origin: WriteOrigin::Hart(state.hart.hart_id),
},
CboEffect::Clean => maintain(Maintenance::Clean),
CboEffect::Flush => maintain(Maintenance::Flush),
CboEffect::Invalidate => maintain(Maintenance::Invalidate),
};
let req_id = common.alloc_req_id();
let _ = common
.outstanding_stores
.insert(req_id, OutstandingStore { owner: StoreOwner::StoreBuffer });
let cycle = state.cycle;
state.event_queue.schedule(
cycle,
ComponentId::Cache(common.l1_d_id),
ComponentId::Pipeline(common.pipeline_id),
Packet::MemReq { req_id, paddr: block, vaddr: None, pc: None, size: AccessSize::Line, op },
);
req_id
}
pub fn send_one_write(
state: &mut CoreCtx<'_>,
common: &mut BackendCommon,
store_buffer: &mut StoreBuffer,
vec_store_buffer: &mut VecStoreBuffer,
) {
if !send_urgent_wcb_line(state, common)
&& !try_drain_one_store(state, common, store_buffer)
&& !drain_vec_store_line(state, common, vec_store_buffer)
{
send_oldest_wcb_line(state, common);
}
}
pub fn committed_writes_pending(
state: &CoreCtx<'_>,
store_buffer: &StoreBuffer,
vec_store_buffer: &VecStoreBuffer,
) -> bool {
older_stores_pending(store_buffer, vec_store_buffer, &state.core.wcb)
}
pub(super) fn send_urgent_wcb_line(state: &mut CoreCtx<'_>, common: &mut BackendCommon) -> bool {
let Some(line) = state.core.wcb.take_urgent() else { return false };
send_wcb_line(state, common, &line);
true
}
pub(super) fn send_oldest_wcb_line(state: &mut CoreCtx<'_>, common: &mut BackendCommon) {
if let Some(line) = state.core.wcb.take_oldest() {
send_wcb_line(state, common, &line);
}
}
pub(super) fn send_wcb_line(state: &mut CoreCtx<'_>, common: &mut BackendCommon, line: &WcbLine) {
let span = state.core.wcb.entry_bytes();
let paddr = PhysAddr::new(line.line_addr);
let req_id = emit_line_write(
state,
common,
paddr,
&line.data[..span],
line.mask,
StoreOwner::WriteCombining,
);
state.core.wcb.sent(req_id, line.clone());
state.uncore.stats.counter(state.core.stat_paths.wcb.drains).inc();
}
pub(super) fn drain_vec_store_line(
state: &mut CoreCtx<'_>,
common: &mut BackendCommon,
vec_store_buffer: &mut VecStoreBuffer,
) -> bool {
let Some((rob_tag, line)) = vec_store_buffer.take_drainable_line() else { return false };
let paddr = PhysAddr::new(line.line_addr);
let owner = StoreOwner::VecStoreBuffer;
let requests = if state.bus.is_ram(paddr, VSB_LINE_BYTES as u64) {
vec![emit_line_write(state, common, paddr, &line.data, line.valid_mask, owner)]
} else {
let write = StoreWrite { origin: WriteOrigin::Hart(state.hart.hart_id), owner };
line.natural_writes()
.into_iter()
.flat_map(|(paddr, data, width)| {
emit_store_write_packet(state, common, paddr, data, width, write)
})
.collect()
};
vec_store_buffer.line_sent(rob_tag, requests);
true
}
pub(super) fn emit_line_write(
state: &mut CoreCtx<'_>,
common: &mut BackendCommon,
line: PhysAddr,
bytes: &[u8],
mask: u64,
owner: StoreOwner,
) -> ReqId {
let req_id = common.alloc_req_id();
let _ = common.outstanding_stores.insert(req_id, OutstandingStore { owner });
let origin = WriteOrigin::Hart(state.hart.hart_id);
let cycle = state.cycle;
state.event_queue.schedule(
cycle,
ComponentId::Cache(common.l1_d_id),
ComponentId::Pipeline(common.pipeline_id),
Packet::MemReq {
req_id,
paddr: line,
vaddr: None,
pc: None,
size: AccessSize::Line,
op: MemOp::Write { data: WriteData::Line { bytes: bytes.into(), mask }, origin },
},
);
req_id
}
pub(super) fn write_store_to_memory(
state: &mut CoreCtx<'_>,
common: &mut BackendCommon,
paddr: PhysAddr,
data: u64,
width: MemWidth,
owner: StoreOwner,
) -> Vec<ReqId> {
if width == MemWidth::Nop {
return Vec::new();
}
state.publish_write(paddr, data, width);
emit_store_write_packet(state, common, paddr, data, width, StoreWrite::placed(owner))
}
pub(super) fn emit_store_write_packet(
state: &mut CoreCtx<'_>,
common: &mut BackendCommon,
paddr: PhysAddr,
data: u64,
width: MemWidth,
write: StoreWrite,
) -> Vec<ReqId> {
if width == MemWidth::Nop {
return Vec::new();
}
let width_bytes = width.bytes() as usize;
if !is_pure_ram(state, paddr, width) {
let target = ComponentId::Bus;
return vec![emit_store_write_packet_to(
state,
common,
paddr,
data,
width_bytes,
write,
target,
)];
}
let l1d = ComponentId::Cache(common.l1_d_id);
line_parts(state, paddr, data, width_bytes)
.into_iter()
.map(|(part_paddr, part_data, part_bytes)| {
emit_store_write_packet_to(state, common, part_paddr, part_data, part_bytes, write, l1d)
})
.collect()
}
#[derive(Clone, Copy)]
pub(super) struct StoreWrite {
origin: WriteOrigin,
owner: StoreOwner,
}
impl StoreWrite {
const fn placed(owner: StoreOwner) -> Self {
Self { origin: WriteOrigin::Placed, owner }
}
}
pub(super) fn line_parts(
state: &CoreCtx<'_>,
paddr: PhysAddr,
data: u64,
width_bytes: usize,
) -> Vec<(PhysAddr, u64, usize)> {
span_parts(paddr, data, width_bytes, state.core.l1_d_cache.line_bytes())
}
pub(super) fn span_parts(
paddr: PhysAddr,
data: u64,
width_bytes: usize,
span: usize,
) -> Vec<(PhysAddr, u64, usize)> {
let span = span as u64;
if !crosses_cache_line(paddr.val(), width_bytes as u64, span) {
return vec![(paddr, data, width_bytes)];
}
let second = (paddr.val() | (span - 1)) + 1;
let first_bytes = (second - paddr.val()) as usize;
vec![
(paddr, data, first_bytes),
(PhysAddr::new(second), data >> (8 * first_bytes), width_bytes - first_bytes),
]
}
pub(super) fn emit_store_write_packet_to(
state: &mut CoreCtx<'_>,
common: &mut BackendCommon,
paddr: PhysAddr,
data: u64,
bytes: usize,
write: StoreWrite,
target: ComponentId,
) -> ReqId {
let req_id = common.alloc_req_id();
let pipeline_id = common.pipeline_id;
let _ = common.outstanding_stores.insert(req_id, OutstandingStore { owner: write.owner });
let cycle = state.cycle;
state.event_queue.schedule(
cycle,
target,
ComponentId::Pipeline(pipeline_id),
Packet::MemReq {
req_id,
paddr,
vaddr: None,
pc: None,
size: AccessSize::of_bytes(bytes),
op: MemOp::Write { data: WriteData::Small(data), origin: write.origin },
},
);
req_id
}