use crate::exec::compute::vector::mem::{is_vec_load, is_vec_store};
use crate::isa::op::{SystemOp, VectorOp};
use crate::isa::reg::RegIdx;
use crate::trace_issue;
use crate::uarch::ctx::StageCtx;
use crate::uarch::pipeline::backend::o3::fu_pool::{FuPool, FuType};
use crate::uarch::pipeline::backend::shared::issue_stats::IssueHold;
use crate::uarch::pipeline::latches::RenameIssueEntry;
use crate::uarch::pipeline::lsq::store_buffer::StoreBuffer;
use crate::uarch::pipeline::lsq::vec_store_buffer::VecStoreBuffer;
use crate::uarch::pipeline::rob::{Rob, RobTag};
use crate::uarch::pipeline::squash::PendingSquash;
use std::collections::VecDeque;
#[derive(Clone, Copy, Debug)]
pub struct IssuedUnit {
pub tag: RobTag,
pub fu_type: FuType,
pub complete_cycle: u64,
pub busy_cycles: u64,
}
#[derive(Debug)]
pub struct InOrderIssueUnit {
queue: VecDeque<RenameIssueEntry>,
capacity: usize,
}
impl InOrderIssueUnit {
pub fn new(capacity: usize) -> Self {
Self { queue: VecDeque::with_capacity(capacity), capacity }
}
pub fn dispatch(&mut self, entries: Vec<RenameIssueEntry>) {
for entry in entries {
debug_assert!(
self.queue.len() < self.capacity,
"issue queue overflow: len={} capacity={} — entry rob_tag={} pc={:#x} would be silently dropped",
self.queue.len(),
self.capacity,
entry.rob_tag.0,
entry.inst.pc,
);
if self.queue.len() < self.capacity {
self.queue.push_back(entry);
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn select(
&mut self,
width: usize,
rob: &Rob,
store_buffer: &StoreBuffer,
vec_store_buffer: &VecStoreBuffer,
state: &StageCtx<'_>,
fu_pool: &mut FuPool,
now: u64,
pending_squash: Option<PendingSquash>,
) -> (Vec<RenameIssueEntry>, Vec<IssuedUnit>, Option<IssueHold>) {
let mut selected = Vec::with_capacity(width);
let mut units = Vec::with_capacity(width);
let mut held = None;
for _ in 0..width {
let Some(entry) = self.queue.front() else { break };
if pending_squash.is_some_and(|squash| squash.squashes(entry.rob_tag)) {
held = Some(IssueHold::Ordering);
break;
}
if entry.trap.is_some() {
if let Some(e) = self.queue.pop_front() {
selected.push(e);
}
continue;
}
let waits_for_head = (entry.inst.ctrl.vec_op != VectorOp::None
&& !entry.inst.ctrl.vec_op.is_config())
|| (entry.inst.ctrl.system_op != SystemOp::None
&& entry.inst.ctrl.system_op != SystemOp::Fence
&& !entry.inst.ctrl.system_op.is_cbo())
|| entry.inst.ctrl.performs_at_rob_head();
if waits_for_head && !rob.is_head(entry.rob_tag) {
held = Some(IssueHold::Ordering);
break;
}
if entry.inst.ctrl.system_op == SystemOp::Fence {
let pred_bits = ((entry.inst.bits >> 24) & 0xF) as u8;
let pred_r = pred_bits & 0b0010 != 0;
let pred_w = pred_bits & 0b0001 != 0;
if !rob.fence_pred_satisfied(entry.rob_tag, pred_r, pred_w) {
held = Some(IssueHold::Ordering);
break;
}
}
let (reads, writes) = (entry.inst.ctrl.reads_memory(), entry.inst.ctrl.writes_memory());
if (reads || writes) && rob.has_fence_blocking(entry.rob_tag, reads, writes) {
held = Some(IssueHold::Ordering);
break;
}
if entry.inst.ctrl.mem_read
&& (store_buffer.has_unresolved_store_before(entry.rob_tag)
|| vec_store_buffer.has_unresolved_store_before(entry.rob_tag))
{
held = Some(IssueHold::Ordering);
break;
}
let rv1 = read_operand_by_tag(
entry.inst.rs1,
entry.inst.ctrl.rs1_fp,
entry.rs1_tag,
rob,
state,
);
let rv2 = read_operand_by_tag(
entry.inst.rs2,
entry.inst.ctrl.rs2_fp,
entry.rs2_tag,
rob,
state,
);
let rv3 = if entry.inst.ctrl.rs3_fp {
read_operand_by_tag(entry.inst.rs3, true, entry.rs3_tag, rob, state)
} else {
Some(0)
};
if let (Some(v1), Some(v2), Some(v3)) = (rv1, rv2, rv3) {
let fu_type = FuType::classify(&entry.inst.ctrl);
let Some(unit) = fu_pool.free_unit(fu_type, now) else {
held = Some(IssueHold::Unit);
break;
};
let complete_cycle = if is_vector_arithmetic(entry.inst.ctrl.vec_op) {
let latency = fu_pool.vector_op_latency(
fu_type,
&entry.inst.ctrl,
state.hart().csrs.vl as usize,
state.config.pipeline.vector_lanes(),
);
fu_pool.acquire_with_latency(unit, now, latency)
} else {
fu_pool.acquire(unit, now)
};
let busy_cycles = fu_pool.busy_cycles(unit, now);
let Some(mut issued) = self.queue.pop_front() else { break };
let tag = issued.rob_tag;
units.push(IssuedUnit { tag, fu_type, complete_cycle, busy_cycles });
issued.inst.rv1 = v1;
issued.inst.rv2 = v2;
issued.inst.rv3 = v3;
selected.push(issued);
} else {
trace_issue!(state.config.general.trace_instructions;
pc = %crate::common::trace::Hex(entry.inst.pc),
rs1 = entry.inst.rs1.as_usize(),
rs1_tag = ?entry.rs1_tag,
rs1_rdy = rv1.is_some(),
rs2 = entry.inst.rs2.as_usize(),
rs2_tag = ?entry.rs2_tag,
rs2_rdy = rv2.is_some(),
"IS: stall — operand not ready"
);
held = Some(IssueHold::Operands);
break;
}
}
(selected, units, held)
}
pub fn queue_snapshot(&self) -> Vec<RenameIssueEntry> {
self.queue.iter().cloned().collect()
}
pub fn available_slots(&self) -> usize {
self.capacity - self.queue.len()
}
pub fn flush(&mut self) {
self.queue.clear();
}
}
const fn is_vector_arithmetic(op: VectorOp) -> bool {
!matches!(op, VectorOp::None) && !op.is_config() && !is_vec_load(op) && !is_vec_store(op)
}
fn read_operand_by_tag(
reg: RegIdx,
is_fp: bool,
tag: Option<RobTag>,
rob: &Rob,
state: &StageCtx<'_>,
) -> Option<u64> {
if !is_fp && reg.is_zero() {
return Some(0);
}
let from_register_file =
|| Some(if is_fp { state.hart().regs.read_f(reg) } else { state.hart().regs.read(reg) });
let Some(tag) = tag else { return from_register_file() };
rob.find_entry(tag).map_or_else(from_register_file, |entry| entry.result)
}