Track HALT
This commit is contained in:
parent
aa3cc3df9b
commit
7f9791c4c5
|
@ -210,7 +210,7 @@ extern "C" fn on_execute(sim: *mut VB, address: u32, code: *const u16, length: c
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut stopped = data.stop_reason.is_some();
|
let mut stopped = data.stop_reason.is_some() || data.monitor.event.is_some();
|
||||||
if data.step_from.is_some_and(|s| s != address) {
|
if data.step_from.is_some_and(|s| s != address) {
|
||||||
data.step_from = None;
|
data.step_from = None;
|
||||||
data.stop_reason = Some(StopReason::Stepped);
|
data.stop_reason = Some(StopReason::Stepped);
|
||||||
|
@ -247,7 +247,13 @@ extern "C" fn on_exception(sim: *mut VB, cause: *mut u16) -> c_int {
|
||||||
// There is no way for the userdata to be null or otherwise invalid.
|
// There is no way for the userdata to be null or otherwise invalid.
|
||||||
let data: &mut VBState = unsafe { &mut *vb_get_user_data(sim).cast() };
|
let data: &mut VBState = unsafe { &mut *vb_get_user_data(sim).cast() };
|
||||||
data.monitor.event = data.monitor.queued_event.take();
|
data.monitor.event = data.monitor.queued_event.take();
|
||||||
data.monitor.queued_event = Some(SimEvent::Interrupt(unsafe { *cause }));
|
let cause = unsafe { *cause };
|
||||||
|
let pc = if cause == 0xff70 {
|
||||||
|
0xffffff60
|
||||||
|
} else {
|
||||||
|
(cause & 0xfff0) as u32 | 0xffff0000
|
||||||
|
};
|
||||||
|
data.monitor.queued_event = Some(SimEvent::Interrupt(cause, pc));
|
||||||
unsafe { vb_set_exception_callback(sim, None) };
|
unsafe { vb_set_exception_callback(sim, None) };
|
||||||
unsafe { vb_set_fetch_callback(sim, Some(on_fetch)) };
|
unsafe { vb_set_fetch_callback(sim, Some(on_fetch)) };
|
||||||
if data.monitor.event.is_some() { 1 } else { 0 }
|
if data.monitor.event.is_some() { 1 } else { 0 }
|
||||||
|
@ -319,7 +325,8 @@ extern "C" fn on_write(
|
||||||
pub enum SimEvent {
|
pub enum SimEvent {
|
||||||
Call(u32),
|
Call(u32),
|
||||||
Return,
|
Return,
|
||||||
Interrupt(u16),
|
Halt,
|
||||||
|
Interrupt(u16, u32),
|
||||||
Reti,
|
Reti,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -327,6 +334,7 @@ struct EventMonitor {
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
event: Option<SimEvent>,
|
event: Option<SimEvent>,
|
||||||
queued_event: Option<SimEvent>,
|
queued_event: Option<SimEvent>,
|
||||||
|
just_halted: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventMonitor {
|
impl EventMonitor {
|
||||||
|
@ -335,6 +343,7 @@ impl EventMonitor {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
event: None,
|
event: None,
|
||||||
queued_event: None,
|
queued_event: None,
|
||||||
|
just_halted: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,7 +352,8 @@ impl EventMonitor {
|
||||||
self.queued_event.is_some()
|
self.queued_event.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn do_detect_event(&self, sim: *mut VB, address: u32, code: &[u16]) -> Option<SimEvent> {
|
fn do_detect_event(&mut self, sim: *mut VB, address: u32, code: &[u16]) -> Option<SimEvent> {
|
||||||
|
const HALT_OPCODE: u16 = 0b011010;
|
||||||
const JAL_OPCODE: u16 = 0b101011;
|
const JAL_OPCODE: u16 = 0b101011;
|
||||||
const JMP_OPCODE: u16 = 0b000110;
|
const JMP_OPCODE: u16 = 0b000110;
|
||||||
const RETI_OPCODE: u16 = 0b011001;
|
const RETI_OPCODE: u16 = 0b011001;
|
||||||
|
@ -359,6 +369,18 @@ impl EventMonitor {
|
||||||
|
|
||||||
let opcode = code[0] >> 10;
|
let opcode = code[0] >> 10;
|
||||||
|
|
||||||
|
if opcode == HALT_OPCODE {
|
||||||
|
if !self.just_halted {
|
||||||
|
self.just_halted = true;
|
||||||
|
self.event = Some(SimEvent::Halt);
|
||||||
|
} else {
|
||||||
|
self.just_halted = false;
|
||||||
|
}
|
||||||
|
// Don't _return_ an event, we want to emit this right away.
|
||||||
|
// If the CPU is halting, no other callbacks will run for a long time.
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
if opcode == JAL_OPCODE {
|
if opcode == JAL_OPCODE {
|
||||||
let disp = format_iv_disp(code);
|
let disp = format_iv_disp(code);
|
||||||
if disp != 4 {
|
if disp != 4 {
|
||||||
|
|
|
@ -96,10 +96,11 @@ struct ProfileSession {
|
||||||
|
|
||||||
struct StackFrame {
|
struct StackFrame {
|
||||||
#[expect(dead_code)]
|
#[expect(dead_code)]
|
||||||
address: Option<u32>,
|
address: u32,
|
||||||
cycles: u64,
|
cycles: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const RESET_CODE: u16 = 0xfff0;
|
||||||
impl ProfileSession {
|
impl ProfileSession {
|
||||||
async fn new(file_path: PathBuf) -> Self {
|
async fn new(file_path: PathBuf) -> Self {
|
||||||
let symbol_manager = SymbolManager::with_config(Default::default());
|
let symbol_manager = SymbolManager::with_config(Default::default());
|
||||||
|
@ -109,22 +110,24 @@ impl ProfileSession {
|
||||||
.expect("cannae load symbols");
|
.expect("cannae load symbols");
|
||||||
let mut call_stacks = HashMap::new();
|
let mut call_stacks = HashMap::new();
|
||||||
call_stacks.insert(
|
call_stacks.insert(
|
||||||
0,
|
RESET_CODE,
|
||||||
vec![StackFrame {
|
vec![StackFrame {
|
||||||
address: None,
|
address: 0xfffffff0,
|
||||||
cycles: 0,
|
cycles: 0,
|
||||||
}],
|
}],
|
||||||
);
|
);
|
||||||
Self {
|
Self {
|
||||||
symbol_map,
|
symbol_map,
|
||||||
call_stacks,
|
call_stacks,
|
||||||
context_stack: vec![],
|
context_stack: vec![RESET_CODE],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn track_elapsed_cycles(&mut self, cycles: u32) {
|
fn track_elapsed_cycles(&mut self, cycles: u32) {
|
||||||
let code = self.context_stack.last().copied().unwrap_or(0);
|
let Some(code) = self.context_stack.last() else {
|
||||||
let Some(stack) = self.call_stacks.get_mut(&code) else {
|
return; // program is halted, CPU is idle
|
||||||
|
};
|
||||||
|
let Some(stack) = self.call_stacks.get_mut(code) else {
|
||||||
panic!("missing stack {code:04x}");
|
panic!("missing stack {code:04x}");
|
||||||
};
|
};
|
||||||
for frame in stack {
|
for frame in stack {
|
||||||
|
@ -134,42 +137,63 @@ impl ProfileSession {
|
||||||
|
|
||||||
fn track_event(&mut self, event: SimEvent) {
|
fn track_event(&mut self, event: SimEvent) {
|
||||||
match event {
|
match event {
|
||||||
SimEvent::Interrupt(code) => {
|
SimEvent::Call(address) => {
|
||||||
self.context_stack.push(code);
|
let Some(code) = self.context_stack.last() else {
|
||||||
if self.call_stacks.insert(code, vec![]).is_some() {
|
panic!("How did we call anything when we're halted?");
|
||||||
panic!("{code:04x} fired twice");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SimEvent::Reti => {
|
|
||||||
let Some(code) = self.context_stack.pop() else {
|
|
||||||
panic!("reti when not in interrupt");
|
|
||||||
};
|
};
|
||||||
if self.call_stacks.remove(&code).is_none() {
|
let Some(stack) = self.call_stacks.get_mut(code) else {
|
||||||
panic!("{code:04x} popped but never called")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SimEvent::Call(addr) => {
|
|
||||||
let code = self.context_stack.last().copied().unwrap_or(0);
|
|
||||||
let Some(stack) = self.call_stacks.get_mut(&code) else {
|
|
||||||
panic!("missing stack {code:04x}");
|
panic!("missing stack {code:04x}");
|
||||||
};
|
};
|
||||||
let name = self
|
let name = self
|
||||||
.symbol_map
|
.symbol_map
|
||||||
.lookup_sync(wholesym::LookupAddress::Svma(addr as u64));
|
.lookup_sync(wholesym::LookupAddress::Svma(address as u64));
|
||||||
println!("depth {}: {:?}", stack.len(), name);
|
println!("depth {}: {:x?}", stack.len(), name);
|
||||||
stack.push(StackFrame {
|
stack.push(StackFrame { address, cycles: 0 });
|
||||||
address: Some(addr),
|
|
||||||
cycles: 0,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
SimEvent::Return => {
|
SimEvent::Return => {
|
||||||
let code = self.context_stack.last().copied().unwrap_or(0);
|
let Some(code) = self.context_stack.last() else {
|
||||||
let Some(stack) = self.call_stacks.get_mut(&code) else {
|
panic!("how did we return when we're halted?");
|
||||||
|
};
|
||||||
|
let Some(stack) = self.call_stacks.get_mut(code) else {
|
||||||
panic!("missing stack {code:04x}");
|
panic!("missing stack {code:04x}");
|
||||||
};
|
};
|
||||||
if stack.pop().is_none() {
|
if stack.pop().is_none() {
|
||||||
panic!("returned from {code:04x} but stack was empty");
|
panic!("returned from {code:04x} but stack was empty");
|
||||||
}
|
}
|
||||||
|
if stack.is_empty() {
|
||||||
|
panic!("returned to oblivion");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SimEvent::Halt => {
|
||||||
|
let Some(RESET_CODE) = self.context_stack.pop() else {
|
||||||
|
panic!("halted when not in an interrupt");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
SimEvent::Interrupt(code, address) => {
|
||||||
|
// if the CPU was halted before, wake it up now
|
||||||
|
if self.context_stack.is_empty() {
|
||||||
|
self.context_stack.push(RESET_CODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.context_stack.push(code);
|
||||||
|
if self
|
||||||
|
.call_stacks
|
||||||
|
.insert(code, vec![StackFrame { address, cycles: 0 }])
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
panic!("{code:04x} fired twice");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SimEvent::Reti => {
|
||||||
|
let Some(code) = self.context_stack.pop() else {
|
||||||
|
panic!("RETI when halted");
|
||||||
|
};
|
||||||
|
if code == RESET_CODE {
|
||||||
|
panic!("RETI when not in interrupt");
|
||||||
|
}
|
||||||
|
if self.call_stacks.remove(&code).is_none() {
|
||||||
|
panic!("{code:04x} popped but never called");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue