use super::CoreCtx;
use crate::common::{Asid, PAGE_OFFSET_MASK, PAGE_SHIFT, PhysAddr, VPN_MASK, Vpn};
use crate::isa::csr;
use crate::isa::encoding::privileged::WFI;
use crate::isa::privileged::PrivilegeMode;
use crate::isa::reg;
use crate::soc::bus::HartIrqs;
use crate::trace_trap;
const HANG_DETECTION_THRESHOLD: u64 = 5000;
const STATUS_UPDATE_INTERVAL: u64 = 5_000_000;
impl CoreCtx<'_> {
pub fn count_idle_cycle(&mut self) {
let paths = &self.core.stat_paths;
self.uncore.stats.counter(paths.commit.retire_hist_zero).inc();
self.uncore.stats.counter(paths.pipeline.cycles_wfi).inc();
}
pub fn skip_quiet_cycles(&mut self, cycles: u64) {
let hart_idx = self.hart.hart_id.as_index();
let debug = &mut self.uncore.per_hart_debug[hart_idx];
debug.same_pc_count = debug.same_pc_count.saturating_add(cycles);
self.hart.csrs.count_cycles(cycles);
let hart_paths = self.hart_paths();
let mode_cycles = match self.hart.privilege {
PrivilegeMode::User => hart_paths.cycles_user,
PrivilegeMode::Supervisor => hart_paths.cycles_kernel,
PrivilegeMode::Machine => hart_paths.cycles_machine,
};
let paths = &self.core.stat_paths;
let stats = &mut self.uncore.stats;
stats.counter(mode_cycles).add(cycles);
stats.counter(paths.pipeline.cycles_total).add(cycles);
stats.counter(paths.commit.retire_hist_zero).add(cycles);
stats.counter(paths.pipeline.cycles_wfi).add(cycles);
}
pub fn pre_tick(&mut self, irqs: HartIrqs) {
let hart_idx = self.hart.hart_id.as_index();
let debug = &mut self.uncore.per_hart_debug[hart_idx];
if self.hart.pc == debug.last_pc {
debug.same_pc_count += 1;
if debug.same_pc_count == HANG_DETECTION_THRESHOLD {
let asid = Asid::new(
((self.hart.csrs.satp >> csr::SATP_ASID_SHIFT) & csr::SATP_ASID_MASK) as u16,
);
let paddr_raw = if let Some(hit) =
self.core.mmu.dtlb.peek(Vpn::new((self.hart.pc >> PAGE_SHIFT) & VPN_MASK), asid)
{
hit.ppn.to_addr() | (self.hart.pc & PAGE_OFFSET_MASK)
} else {
self.hart.pc
};
let inst = self.memory.read(PhysAddr::new(paddr_raw), 4).unwrap_or(0) as u32;
if inst == WFI {
trace_trap!(self.config.general.trace_instructions;
event = "wfi-wait",
pc = %crate::common::trace::Hex(self.hart.pc),
"CPU stuck in WFI — waiting for interrupt"
);
} else {
trace_trap!(self.config.general.trace_instructions;
event = "potential-hang",
pc = %crate::common::trace::Hex(self.hart.pc),
inst = inst,
"CPU potential hang detected"
);
}
}
} else {
debug.last_pc = self.hart.pc;
debug.same_pc_count = 0;
}
let mut mip = self.hart.csrs.mip;
if irqs.mtip {
mip |= csr::MIP_MTIP;
} else {
mip &= !csr::MIP_MTIP;
}
if irqs.msip {
mip |= csr::MIP_MSIP;
} else {
mip &= !csr::MIP_MSIP;
}
if irqs.meip {
mip |= csr::MIP_MEIP;
} else {
mip &= !csr::MIP_MEIP;
}
if irqs.seip {
mip |= csr::MIP_SEIP;
} else if !self.hart.sw_seip {
mip &= !csr::MIP_SEIP;
}
if (self.hart.csrs.menvcfg & csr::MENVCFG_STCE) != 0 {
if self.bus.mtime() >= self.hart.csrs.stimecmp {
mip |= csr::MIP_STIP;
} else {
mip &= !csr::MIP_STIP;
}
}
self.hart.csrs.mip = mip;
}
pub fn post_tick(&mut self, prev_priv: PrivilegeMode) {
self.hart.regs.write(reg::REG_ZERO, 0);
if self.config.general.trace_instructions {
if self.hart.privilege != prev_priv {
trace_trap!(self.config.general.trace_instructions;
event = "mode-switch",
from_mode = prev_priv.name(),
to_mode = self.hart.privilege.name(),
pc = %crate::common::trace::Hex(self.hart.pc),
"CPU privilege mode switch"
);
}
if self.cycle.is_multiple_of(STATUS_UPDATE_INTERVAL) {
::tracing::debug!(
target: "rvsim::cpu",
cycles = self.cycle,
pc = %crate::common::trace::Hex(self.hart.pc),
mode = self.hart.privilege.name(),
"CPU status"
);
}
}
}
pub fn track_mode_cycles(&mut self) {
self.hart.csrs.count_cycle();
let hart_paths = self.hart_paths();
let core_cycles = self.core.stat_paths.pipeline.cycles_total;
self.stats.counter(core_cycles).inc();
match self.hart.privilege {
PrivilegeMode::User => self.stats.counter(hart_paths.cycles_user).inc(),
PrivilegeMode::Supervisor => self.stats.counter(hart_paths.cycles_kernel).inc(),
PrivilegeMode::Machine => self.stats.counter(hart_paths.cycles_machine).inc(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::Config;
#[test]
fn test_track_mode_cycles() {
let config = Config::default();
let mut sys = crate::system::SystemState::build(&config, "");
let mut state = sys.core_ctx(0);
let paths = state.hart_paths();
state.hart.privilege = PrivilegeMode::User;
state.track_mode_cycles();
assert_eq!(state.stats.get(paths.cycles_user).unwrap_or(0.0) as u64, 1);
state.hart.privilege = PrivilegeMode::Supervisor;
state.track_mode_cycles();
assert_eq!(state.stats.get(paths.cycles_kernel).unwrap_or(0.0) as u64, 1);
state.hart.privilege = PrivilegeMode::Machine;
state.track_mode_cycles();
assert_eq!(state.stats.get(paths.cycles_machine).unwrap_or(0.0) as u64, 1);
assert_eq!(
state.stats.get(state.core.stat_paths.pipeline.cycles_total).unwrap_or(0.0) as u64,
3
);
}
#[test]
fn test_post_tick_zero_reg() {
let config = Config::default();
let mut sys = crate::system::SystemState::build(&config, "");
let mut state = sys.core_ctx(0);
state.hart.regs.write(reg::REG_ZERO, 42);
state.post_tick(PrivilegeMode::Machine);
assert_eq!(state.hart.regs.read(reg::REG_ZERO), 0);
}
}