PR-04 - CAP fixes

This commit is contained in:
Nilton Constantino 2026-01-16 18:05:22 +00:00
parent 293d1029a2
commit e519408f51
No known key found for this signature in database
4 changed files with 132 additions and 19 deletions

View File

@ -1,7 +1,14 @@
use crate::virtual_machine::Program; use crate::virtual_machine::Program;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AppMode {
Game,
System,
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct AppHeader { pub struct AppHeader {
pub mode: AppMode,
pub app_id: String, pub app_id: String,
pub magic: u32, pub magic: u32,
pub version: u16, pub version: u16,

View File

@ -8,7 +8,7 @@ mod sample;
mod cartridge; mod cartridge;
pub use button::Button; pub use button::Button;
pub use cartridge::Cartridge; pub use cartridge::{AppHeader, AppMode, Cartridge};
pub use color::Color; pub use color::Color;
pub use sample::Sample; pub use sample::Sample;
pub use sprite::Sprite; pub use sprite::Sprite;

View File

@ -9,7 +9,8 @@ use std::sync::Arc;
pub struct PrometeuOS { pub struct PrometeuOS {
pub tick_index: u64, pub tick_index: u64,
pub logical_frame_index: u64, pub logical_frame_index: u64,
pub logical_frame_open: bool, pub logical_frame_active: bool,
pub logical_frame_remaining_cycles: u64,
pub last_frame_cpu_time_us: u64, pub last_frame_cpu_time_us: u64,
// Assets de exemplo (mantidos para compatibilidade com syscalls de áudio v0) // Assets de exemplo (mantidos para compatibilidade com syscalls de áudio v0)
@ -19,13 +20,15 @@ pub struct PrometeuOS {
} }
impl PrometeuOS { impl PrometeuOS {
pub const CYCLES_PER_FRAME: u64 = 100_000; pub const CYCLES_PER_LOGICAL_FRAME: u64 = 100_000;
pub const SLICE_PER_TICK: u64 = 100_000; // Por enquanto, o mesmo, mas permite granularidade diferente
pub fn new() -> Self { pub fn new() -> Self {
let mut os = Self { let mut os = Self {
tick_index: 0, tick_index: 0,
logical_frame_index: 0, logical_frame_index: 0,
logical_frame_open: false, logical_frame_active: false,
logical_frame_remaining_cycles: 0,
last_frame_cpu_time_us: 0, last_frame_cpu_time_us: 0,
sample_square: None, sample_square: None,
sample_kick: None, sample_kick: None,
@ -42,7 +45,8 @@ impl PrometeuOS {
*vm = VirtualMachine::default(); *vm = VirtualMachine::default();
self.tick_index = 0; self.tick_index = 0;
self.logical_frame_index = 0; self.logical_frame_index = 0;
self.logical_frame_open = false; self.logical_frame_active = false;
self.logical_frame_remaining_cycles = 0;
self.last_frame_cpu_time_us = 0; self.last_frame_cpu_time_us = 0;
} }
@ -56,25 +60,34 @@ impl PrometeuOS {
let start = std::time::Instant::now(); let start = std::time::Instant::now();
self.tick_index += 1; self.tick_index += 1;
if !self.logical_frame_open { if !self.logical_frame_active {
self.logical_frame_open = true; self.logical_frame_active = true;
self.logical_frame_remaining_cycles = Self::CYCLES_PER_LOGICAL_FRAME;
self.begin_logical_frame(signals, hw); self.begin_logical_frame(signals, hw);
} }
// Executa budget // Budget para este tick: o mínimo entre a fatia do tick e o que resta no frame lógico
let run_result = vm.run_budget(Self::CYCLES_PER_FRAME, self, hw); let budget = std::cmp::min(Self::SLICE_PER_TICK, self.logical_frame_remaining_cycles);
match run_result { if budget > 0 {
Ok(run) => { // Executa budget
if run.reason == crate::virtual_machine::LogicalFrameEndingReason::FrameSync { let run_result = vm.run_budget(budget, self, hw);
hw.gfx_mut().render_all();
self.end_logical_frame(hw); match run_result {
self.logical_frame_index += 1; Ok(run) => {
self.logical_frame_open = false; self.logical_frame_remaining_cycles = self.logical_frame_remaining_cycles.saturating_sub(run.cycles_used);
if run.reason == crate::virtual_machine::LogicalFrameEndingReason::FrameSync {
hw.gfx_mut().render_all();
self.end_logical_frame(hw);
self.logical_frame_index += 1;
self.logical_frame_active = false;
self.logical_frame_remaining_cycles = 0;
}
}
Err(e) => {
return Some(format!("PVM Fault: {:?}", e));
} }
}
Err(e) => {
return Some(format!("PVM Fault: {:?}", e));
} }
} }
@ -150,6 +163,98 @@ impl PrometeuOS {
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::hardware::InputSignals;
use crate::model::{AppHeader, AppMode, Cartridge};
use crate::virtual_machine::{Program, VirtualMachine};
use crate::Hardware;
#[test]
fn test_infinite_loop_budget_reset_bug() {
let mut os = PrometeuOS::new();
let mut vm = VirtualMachine::default();
let mut hw = Hardware::new();
let signals = InputSignals::default();
// JMP 0 (Loop infinito)
// OpCode::Jmp = 0x02, seguido por u32 0 (0x00, 0x00, 0x00, 0x00)
let rom = vec![0x02, 0x00, 0x00, 0x00, 0x00, 0x00];
let program = Program::new(rom, vec![]);
let cartridge = Cartridge {
header: AppHeader {
mode: AppMode::Game,
app_id: "test".to_string(),
magic: 0,
version: 1,
title: "test".to_string(),
entrypoint: 0,
},
program,
};
os.initialize_vm(&mut vm, &cartridge);
// Primeiro tick
os.step_frame(&mut vm, &signals, &mut hw);
let cycles_after_tick_1 = vm.cycles;
assert!(cycles_after_tick_1 >= PrometeuOS::CYCLES_PER_LOGICAL_FRAME);
// Segundo tick - Agora ele NÃO deve ganhar mais budget
os.step_frame(&mut vm, &signals, &mut hw);
let cycles_after_tick_2 = vm.cycles;
// CORREÇÃO: Ele não deve ter consumido ciclos no segundo tick porque o budget do frame lógico acabou
println!("Cycles after tick 1: {}, tick 2: {}", cycles_after_tick_1, cycles_after_tick_2);
assert_eq!(cycles_after_tick_2, cycles_after_tick_1, "VM should NOT have consumed more cycles in the second tick because logical frame budget is exhausted");
}
#[test]
fn test_budget_reset_on_frame_sync() {
let mut os = PrometeuOS::new();
let mut vm = VirtualMachine::default();
let mut hw = Hardware::new();
let signals = InputSignals::default();
// Loop que chama FrameSync:
// PUSH_CONST 0 (dummy)
// FrameSync (0x80)
// JMP 0
let rom = vec![
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // PUSH const 0 (2 bytes opcode + 4 bytes u32)
0x80, 0x00, // FrameSync (2 bytes opcode)
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // JMP 0 (2 bytes opcode + 4 bytes u32)
];
let program = Program::new(rom, vec![Value::Integer(0)]);
let cartridge = Cartridge {
header: AppHeader {
mode: AppMode::Game,
app_id: "test".to_string(),
magic: 0,
version: 1,
title: "test".to_string(),
entrypoint: 0,
},
program,
};
os.initialize_vm(&mut vm, &cartridge);
// Primeiro tick
os.step_frame(&mut vm, &signals, &mut hw);
let cycles_after_tick_1 = vm.cycles;
// Deve ter parado no FrameSync
assert!(cycles_after_tick_1 > 0, "VM should have consumed some cycles");
assert!(cycles_after_tick_1 < PrometeuOS::CYCLES_PER_LOGICAL_FRAME);
// Segundo tick - Deve resetar o budget e rodar mais um pouco até o próximo FrameSync
os.step_frame(&mut vm, &signals, &mut hw);
let cycles_after_tick_2 = vm.cycles;
assert!(cycles_after_tick_2 > cycles_after_tick_1, "VM should have consumed more cycles because FrameSync reset the budget");
}
}
impl NativeInterface for PrometeuOS { impl NativeInterface for PrometeuOS {
fn syscall(&mut self, id: u32, vm: &mut VirtualMachine, hw: &mut dyn HardwareBridge) -> Result<u64, String> { fn syscall(&mut self, id: u32, vm: &mut VirtualMachine, hw: &mut dyn HardwareBridge) -> Result<u64, String> {
match id { match id {

View File

@ -51,6 +51,7 @@ impl VirtualMachine {
self.call_stack.clear(); self.call_stack.clear();
self.globals.clear(); self.globals.clear();
self.heap.clear(); self.heap.clear();
self.cycles = 0;
self.halted = false; self.halted = false;
} }
} }