From 57c0b1352ef594d3d6f807560dea3cc920893b8f Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Thu, 15 Jan 2026 14:39:54 +0000 Subject: [PATCH] added vm --- crates/core/src/lib.rs | 1 + crates/core/src/machine.rs | 395 ++++++-------- crates/core/src/model/cartridge.rs | 13 + crates/core/src/model/mod.rs | 2 + crates/core/src/vm.rs | 820 +++++++++++++++++++++++++++++ docs/specs/topics/chapter-2.md | 69 ++- 6 files changed, 1039 insertions(+), 261 deletions(-) create mode 100644 crates/core/src/model/cartridge.rs create mode 100644 crates/core/src/vm.rs diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 08a0e5c2..e74cb6f2 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -1,5 +1,6 @@ pub mod machine; pub mod peripherals; +pub mod vm; mod model; pub use machine::Machine; diff --git a/crates/core/src/machine.rs b/crates/core/src/machine.rs index 38d7c504..0ee3743e 100644 --- a/crates/core/src/machine.rs +++ b/crates/core/src/machine.rs @@ -1,7 +1,7 @@ -use crate::model::Color; -use crate::peripherals::{Gfx, InputSignals, Pad, Touch, Audio, LoopMode}; +use crate::model::{Color, Sample, Cartridge}; +use crate::peripherals::{Gfx, InputSignals, Pad, Touch, Audio}; +use crate::vm::{VirtualMachine, NativeInterface, Value}; use std::sync::Arc; -use crate::model::Sample; /// PROMETEU "hardware lógico" (v0). /// O Host alimenta INPUT SIGNALS e chama `step_frame()` em 60Hz. @@ -12,6 +12,8 @@ pub struct Machine { pub touch: Touch, pub frame_index: u64, pub last_frame_cpu_time_us: u64, + pub vm: VirtualMachine, + pub cartridge: Option, // Assets de exemplo pub sample_square: Option>, @@ -19,22 +21,164 @@ pub struct Machine { pub sample_snare: Option>, } +impl NativeInterface for Machine { + fn call(&mut self, id: u32, vm: &mut VirtualMachine) -> Result { + match id { + // system.has_cart() -> bool + 0x0001 => { + vm.push(Value::Boolean(self.cartridge.is_some())); + Ok(10) + } + // system.run_cart() + 0x0002 => { + if let Some(cart) = self.cartridge.as_ref() { + vm.rom = cart.rom.clone(); + vm.constant_pool = cart.constant_pool.clone(); + vm.pc = 0; + vm.operand_stack.clear(); + vm.call_stack.clear(); + vm.halted = false; + } else { + return Err("No cartridge inserted".into()); + } + Ok(100) + } + // gfx.clear(color_index) + 0x1001 => { + let color_idx = vm.pop_integer()? as usize; + let color = self.get_color(color_idx); + self.gfx.clear(color); + Ok(100) + } + // gfx.draw_rect(x, y, w, h, color_index) + 0x1002 => { + let color_idx = vm.pop_integer()? as usize; + let h = vm.pop_integer()? as i32; + let w = vm.pop_integer()? as i32; + let y = vm.pop_integer()? as i32; + let x = vm.pop_integer()? as i32; + let color = self.get_color(color_idx); + self.gfx.fill_rect(x, y, w, h, color); + Ok(200) + } + // input.get_pad(button_id) -> bool + 0x2001 => { + let button_id = vm.pop_integer()? as u32; + let is_down = self.is_button_down(button_id); + vm.push(Value::Boolean(is_down)); + Ok(50) + } + // audio.play_sample(sample_id, voice_id, volume, pan, pitch) + // sample_id: 0=square, 1=kick, 2=snare + 0x3001 => { + let pitch = vm.pop_number()?; + let pan = vm.pop_integer()? as u8; + let volume = vm.pop_integer()? as u8; + let voice_id = vm.pop_integer()? as usize; + let sample_id = vm.pop_integer()? as u32; + + let sample = match sample_id { + 0 => self.sample_square.clone(), + 1 => self.sample_kick.clone(), + 2 => self.sample_snare.clone(), + _ => None, + }; + + if let Some(s) = sample { + self.audio.play(s, voice_id, volume, pan, pitch, 0, crate::peripherals::LoopMode::Off); + } + Ok(300) + } + _ => Err(format!("Unknown native call: 0x{:08X}", id)), + } + } +} + impl Machine { pub const W: usize = 320; pub const H: usize = 180; + pub const CYCLES_PER_FRAME: u64 = 100_000; // Exemplo de budget + + fn get_color(&self, index: usize) -> Color { + // Implementação simplificada: se houver bancos, usa a paleta do banco 0 + // Caso contrário, usa cores básicas fixas + if let Some(bank) = self.gfx.banks[0].as_ref() { + bank.palettes[0][index % 16] + } else { + match index % 16 { + 0 => Color::BLACK, + 1 => Color::WHITE, + 2 => Color::RED, + 3 => Color::GREEN, + 4 => Color::BLUE, + 5 => Color::YELLOW, + 6 => Color::CYAN, + 7 => Color::INDIGO, + 8 => Color::GRAY, + _ => Color::BLACK, + } + } + } + + fn is_button_down(&self, id: u32) -> bool { + match id { + 0 => self.pad.up.down, + 1 => self.pad.down.down, + 2 => self.pad.left.down, + 3 => self.pad.right.down, + 4 => self.pad.a.down, + 5 => self.pad.b.down, + 6 => self.pad.x.down, + 7 => self.pad.y.down, + 8 => self.pad.l.down, + 9 => self.pad.r.down, + 10 => self.pad.start.down, + 11 => self.pad.select.down, + _ => false, + } + } pub fn new() -> Self { - Self { + let mut machine = Self { gfx: Gfx::new(Self::W, Self::H), audio: Audio::new(), pad: Pad::default(), touch: Touch::default(), frame_index: 0, last_frame_cpu_time_us: 0, + vm: VirtualMachine::default(), + cartridge: None, sample_square: None, sample_kick: None, sample_snare: None, + }; + + // Inicializa samples básicos + machine.sample_square = Some(Arc::new(Self::create_square_sample(440.0, 0.1))); + + machine + } + + pub fn load_cartridge(&mut self, cart: Cartridge) { + self.cartridge = Some(cart); + } + + fn create_square_sample(freq: f64, duration: f64) -> Sample { + let sample_rate = crate::peripherals::OUTPUT_SAMPLE_RATE; + let num_samples = (duration * sample_rate as f64) as usize; + let mut data = Vec::with_capacity(num_samples); + let period = sample_rate as f64 / freq; + + for i in 0..num_samples { + let val = if (i as f64 % period) < (period / 2.0) { + 10000 // Volume médio + } else { + -10000 + }; + data.push(val); } + + Sample::new(sample_rate, data) } /// "Contrato de frame" do PROMETEU (Template Method sem loop). @@ -42,7 +186,13 @@ impl Machine { pub fn step_frame(&mut self, signals: &InputSignals) { let start = std::time::Instant::now(); self.begin_frame(signals); - self.tick(); // futuro: executa cartucho/VM + + // Retira a VM temporariamente para executar run_budget passando self (Machine) como NativeInterface. + // Como VirtualMachine implementa Default, usamos std::mem::take. + let mut vm = std::mem::take(&mut self.vm); + let _result = vm.run_budget(Self::CYCLES_PER_FRAME, self); + self.vm = vm; + self.gfx.render_all(); // A máquina executa o pipeline gráfico (Ação física) self.end_frame(); // present/housekeeping self.last_frame_cpu_time_us = start.elapsed().as_micros() as u64; // Calcula quanto tempo a "CPU simulada" trabalhou de verdade @@ -64,243 +214,8 @@ impl Machine { self.pad.begin_frame(signals); } - /// Lógica do frame (demo hardcoded por enquanto). - pub fn tick(&mut self) { - // Limpa a tela com Preto - self.gfx.clear(Color::GRAY); - - // SETUP BANCO 0 (Tiles 8x8) - if self.gfx.banks[0].is_none() { - let mut bank = crate::model::TileBank::new(crate::model::TileSize::Size8, 128, 128); - // Define Cor na Paleta 0, Índice 1 = VERDE - bank.palettes[0][1] = Color::GREEN; - - let tile_size = 8; - let start_x = 8; // Tile ID 1 - for y in 0..tile_size { - for x in 0..tile_size { - let idx = y * 128 + (start_x + x); - bank.pixel_indices[idx] = 1; // Atribui o índice da paleta - } - } - self.gfx.banks[0] = Some(bank); - } - - // SETUP BANCO 1 (Tiles 16x16) - if self.gfx.banks[1].is_none() { - let mut bank = crate::model::TileBank::new(crate::model::TileSize::Size16, 128, 128); - // Define Cor na Paleta 0, Índice 1 = VERMELHO - bank.palettes[0][1] = Color::RED; - - let tile_size = 16; - let start_x = 16; // Tile ID 1 - for y in 0..tile_size { - for x in 0..tile_size { - let idx = y * 128 + (start_x + x); - bank.pixel_indices[idx] = 1; - } - } - self.gfx.banks[1] = Some(bank); - } - - // SETUP BANCO 2 (Tiles 16x16 para Sprites) - if self.gfx.banks[2].is_none() { - let mut bank = crate::model::TileBank::new(crate::model::TileSize::Size16, 128, 128); - bank.palettes[0][1] = Color::INDIGO; - bank.palettes[1][1] = Color::YELLOW; - bank.palettes[2][1] = Color::RED; - bank.palettes[3][1] = Color::GREEN; - bank.palettes[4][1] = Color::ORANGE; - - let tile_size = 16; - let start_x = 16; - for y in 0..tile_size { - for x in 0..tile_size { - let idx = y * 128 + (start_x + x); - bank.pixel_indices[idx] = 1; - } - } - self.gfx.banks[2] = Some(bank); - } - - // --- LÓGICA DE ESTADO --- - - // HUD - self.gfx.hud.map.set_tile(18, 6, crate::model::Tile { id: 1, palette_id: 0, ..Default::default() }); - - // Camadas de Jogo (usando Banco 1 - Vermelho) - for i in 0..4 { - self.gfx.layers[i].bank_id = 1; - self.gfx.layers[i].map.set_tile(8 + i, 1 + i, crate::model::Tile { id: 1, palette_id: 0, ..Default::default() }); - } - - // 512 Sprites (usando Banco 2 - Indigo e Amarelo) - for i in 0..510 { - let s = &mut self.gfx.sprites[i]; - s.active = true; - s.bank_id = 2; - s.tile.id = 1; - - // Palette Swap: pares Indigo (0), ímpares Amarelo (1) - s.tile.palette_id = (i % 2) as u8; - - let speed = (1 + (i % 3)) as u64; - s.x = (((self.frame_index * speed) + (i as u64 * 10)) % 400) as i32 - 40; - let y_base = (((self.frame_index * speed) + (i as u64 * 4)) % 180) as i32; - s.y = if i % 2 == 0 { y_base } else { 179 - y_base }; - - s.priority = (i % 5) as u8; - s.flip_x = i % 2 == 0; - s.flip_y = i % 3 == 0; - } - - // --- INTERATIVIDADE COM TOUCH --- - let cursor = &mut self.gfx.sprites[511]; - cursor.active = true; - cursor.bank_id = 2; // Banco Verde - cursor.tile.id = 1; - cursor.priority = 4; - - // Se o dedo/mouse estiver pressionado agora (down) - if self.touch.f.down { - cursor.x = self.touch.x - 4; // Centraliza tile 8x8 - cursor.y = self.touch.y - 4; - cursor.tile.palette_id = 2; // RED - } else { - cursor.tile.palette_id = 3; // GREEN - } - - // --- INTERATIVIDADE COM INPUT (Gamepad) --- - let player = &mut self.gfx.sprites[510]; // Usaremos outro sprite para o Pad - player.active = true; - player.bank_id = 2; - player.tile.id = 1; - player.priority = 4; - player.tile.palette_id = 4; // ORANGE - - // Movimento contínuo (enquanto segura) - let move_speed = 2; - if self.pad.up.down { player.y -= move_speed; } - if self.pad.down.down { player.y += move_speed; } - if self.pad.left.down { player.x -= move_speed; } - if self.pad.right.down { player.x += move_speed; } - - // Trigger (apenas no frame que apertou) - if self.pad.a.down { - player.tile.palette_id = 2; - } - - // Exemplo de uso do released - if self.pad.start.released { - // Se soltar o Start, podemos pausar algo futuramente - } - - // --- DEMO DE ÁUDIO --- - // Inicializa assets de áudio se não existirem - if self.sample_square.is_none() { - // 1. Onda Quadrada com Loop (Instrumento) - let freq = 440.0; - let sample_rate = 44100; - let period = (sample_rate as f32 / freq) as usize; - let mut data = Vec::with_capacity(period); - for i in 0..period { - data.push(if i < period / 2 { 4000 } else { -4000 }); - } - self.sample_square = Some(Arc::new(Sample::new(sample_rate, data).with_loop(0, period as u32))); - - // 2. "Kick" simples (Pitch descent) - let mut kick_data = Vec::new(); - for i in 0..4000 { - let t = i as f32 / 4000.0; - let f = 100.0 * (1.0 - t * 0.9); - let val = (i as f32 * f * 2.0 * std::f32::consts::PI / 44100.0).sin(); - kick_data.push((val * 12000.0 * (1.0 - t)) as i16); - } - self.sample_kick = Some(Arc::new(Sample::new(44100, kick_data))); - - // 3. "Snare" simples (White noise) - let mut snare_data = Vec::new(); - for i in 0..3000 { - let t = i as f32 / 3000.0; - let noise = Self::rand_f32(i as u32) * 2.0 - 1.0; - snare_data.push((noise * 8000.0 * (1.0 - t)) as i16); - } - self.sample_snare = Some(Arc::new(Sample::new(44100, snare_data))); - } - - // --- SEQUENCER SIMPLES (60Hz) --- - let bpm = 125; - let frames_per_beat = (60 * 60) / bpm; - let frames_per_step = frames_per_beat / 4; // 16th notes - - let step = (self.frame_index / frames_per_step) % 16; - let is_new_step = self.frame_index % frames_per_step == 0; - - if is_new_step { - // Bassline (Square) - // Notas: C-2, C-2, Eb2, F-2, C-2, C-2, G-2, Bb2... - let melody = [ - 36, 36, 39, 41, - 36, 36, 43, 46, - 36, 36, 39, 41, - 34, 35, 36, 0, - ]; - - let note = melody[step as usize]; - if note > 0 { - // Cálculo de pitch otimizado (tabela simples ou aproximação rápida se necessário, - // mas powf uma vez por step está ok. O problema é se fosse todo frame) - let freq = 440.0 * 2.0f64.powf((note as f64 - 69.0) / 12.0); - let pitch = freq / 440.0; - if let Some(s) = &self.sample_square { - self.audio.play(s.clone(), 0, 160, 100, pitch, 0, LoopMode::On); - } - } - - // Drums - let kick_pattern = 0b0101100100011001u16; // Invertido para facilitar bitshift - let snare_pattern = 0b0001000000001000u16; - - if (kick_pattern >> step) & 1 == 1 { - if let Some(s) = &self.sample_kick { - self.audio.play(s.clone(), 1, 255, 127, 1.0, 1, LoopMode::Off); - } - } - if (snare_pattern >> step) & 1 == 1 { - if let Some(s) = &self.sample_snare { - self.audio.play(s.clone(), 2, 140, 150, 1.0, 1, LoopMode::Off); - } - } - } - - // Toca um "beep" extra se apertar A (mantendo interatividade) - if self.pad.a.pressed { - if let Some(s) = &self.sample_square { - self.audio.play(s.clone(), 3, 255, 60, 2.0, 10, LoopMode::Off); - } - } - - // Post-FX Fade Pulsante - let pulse = (self.frame_index / 4) % 64; - let level = if pulse > 31 { 63 - pulse } else { pulse }; - self.gfx.scene_fade_level = level as u8; - self.gfx.scene_fade_color = Color::BLACK; - self.gfx.hud_fade_level = 31; - } - /// Final do frame: troca buffers. pub fn end_frame(&mut self) { self.gfx.present(); } - - /// Helper para noise determinístico - fn rand_f32(seed: u32) -> f32 { - let mut x = seed.wrapping_add(0x9E3779B9); - x = x ^ (x >> 16); - x = x.wrapping_mul(0x85EBCA6B); - x = x ^ (x >> 13); - x = x.wrapping_mul(0xC2B2AE35); - x = x ^ (x >> 16); - (x as f32) / (u32::MAX as f32) - } } diff --git a/crates/core/src/model/cartridge.rs b/crates/core/src/model/cartridge.rs new file mode 100644 index 00000000..11d57a7b --- /dev/null +++ b/crates/core/src/model/cartridge.rs @@ -0,0 +1,13 @@ +use crate::vm::Value; + +#[derive(Clone, Debug)] +pub struct Cartridge { + pub rom: Vec, + pub constant_pool: Vec, +} + +impl Cartridge { + pub fn new(rom: Vec, constant_pool: Vec) -> Self { + Self { rom, constant_pool } + } +} diff --git a/crates/core/src/model/mod.rs b/crates/core/src/model/mod.rs index b25811f7..4d673091 100644 --- a/crates/core/src/model/mod.rs +++ b/crates/core/src/model/mod.rs @@ -5,6 +5,7 @@ mod tile_layer; mod tile_bank; mod sprite; mod sample; +mod cartridge; pub use button::Button; pub use color::Color; @@ -13,3 +14,4 @@ pub use tile_bank::{TileBank, TileSize}; pub use tile_layer::{HudTileLayer, ScrollableTileLayer, TileMap}; pub use sprite::Sprite; pub use sample::Sample; +pub use cartridge::Cartridge; diff --git a/crates/core/src/vm.rs b/crates/core/src/vm.rs new file mode 100644 index 00000000..286c27d9 --- /dev/null +++ b/crates/core/src/vm.rs @@ -0,0 +1,820 @@ +use std::convert::TryFrom; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u16)] +pub enum OpCode { + // 6.1 Controle de Execução + Nop = 0x00, + Halt = 0x01, + Jmp = 0x02, + JmpIfFalse = 0x03, + + // 6.2 Pilha + PushConst = 0x10, + Pop = 0x11, + Dup = 0x12, + Swap = 0x13, + + // 6.3 Aritmética + Add = 0x20, + Sub = 0x21, + Mul = 0x22, + Div = 0x23, + + // 6.4 Comparação e Lógica + Eq = 0x30, + Neq = 0x31, + Lt = 0x32, + Gt = 0x33, + And = 0x34, + Or = 0x35, + Not = 0x36, + + // 6.5 Variáveis + GetGlobal = 0x40, + SetGlobal = 0x41, + GetLocal = 0x42, + SetLocal = 0x43, + + // 6.6 Funções + Call = 0x50, + Ret = 0x51, + PushScope = 0x52, + PopScope = 0x53, + + // 6.7 Heap + Alloc = 0x60, + LoadRef = 0x61, + StoreRef = 0x62, + + // 6.8 Periféricos e Sistema + Syscall = 0x70, + FrameSync = 0x80, +} + +impl TryFrom for OpCode { + type Error = String; + + fn try_from(value: u16) -> Result { + match value { + 0x00 => Ok(OpCode::Nop), + 0x01 => Ok(OpCode::Halt), + 0x02 => Ok(OpCode::Jmp), + 0x03 => Ok(OpCode::JmpIfFalse), + 0x10 => Ok(OpCode::PushConst), + 0x11 => Ok(OpCode::Pop), + 0x12 => Ok(OpCode::Dup), + 0x13 => Ok(OpCode::Swap), + 0x20 => Ok(OpCode::Add), + 0x21 => Ok(OpCode::Sub), + 0x22 => Ok(OpCode::Mul), + 0x23 => Ok(OpCode::Div), + 0x30 => Ok(OpCode::Eq), + 0x31 => Ok(OpCode::Neq), + 0x32 => Ok(OpCode::Lt), + 0x33 => Ok(OpCode::Gt), + 0x34 => Ok(OpCode::And), + 0x35 => Ok(OpCode::Or), + 0x36 => Ok(OpCode::Not), + 0x40 => Ok(OpCode::GetGlobal), + 0x41 => Ok(OpCode::SetGlobal), + 0x42 => Ok(OpCode::GetLocal), + 0x43 => Ok(OpCode::SetLocal), + 0x50 => Ok(OpCode::Call), + 0x51 => Ok(OpCode::Ret), + 0x52 => Ok(OpCode::PushScope), + 0x53 => Ok(OpCode::PopScope), + 0x60 => Ok(OpCode::Alloc), + 0x61 => Ok(OpCode::LoadRef), + 0x62 => Ok(OpCode::StoreRef), + 0x70 => Ok(OpCode::Syscall), + 0x80 => Ok(OpCode::FrameSync), + _ => Err(format!("Invalid OpCode: 0x{:04X}", value)), + } + } +} + +impl OpCode { + pub fn cycles(&self) -> u64 { + match self { + OpCode::Nop => 1, + OpCode::Halt => 1, + OpCode::Jmp => 2, + OpCode::JmpIfFalse => 3, + OpCode::PushConst => 2, + OpCode::Pop => 1, + OpCode::Dup => 1, + OpCode::Swap => 1, + OpCode::Add => 2, + OpCode::Sub => 2, + OpCode::Mul => 4, + OpCode::Div => 6, + OpCode::Eq => 2, + OpCode::Neq => 2, + OpCode::Lt => 2, + OpCode::Gt => 2, + OpCode::And => 2, + OpCode::Or => 2, + OpCode::Not => 1, + OpCode::GetGlobal => 3, + OpCode::SetGlobal => 3, + OpCode::GetLocal => 2, + OpCode::SetLocal => 2, + OpCode::Call => 5, + OpCode::Ret => 4, + OpCode::PushScope => 3, + OpCode::PopScope => 3, + OpCode::Alloc => 10, + OpCode::LoadRef => 3, + OpCode::StoreRef => 3, + OpCode::Syscall => 1, // Variável, mas vamos usar 1 como base ou definir via ID + OpCode::FrameSync => 1, + } + } +} + +#[derive(Debug, Clone)] +pub enum Value { + Integer(i64), + Float(f64), + Boolean(bool), + String(String), + Ref(usize), // Referência ao heap + Null, +} + +impl PartialEq for Value { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Value::Integer(a), Value::Integer(b)) => a == b, + (Value::Float(a), Value::Float(b)) => a == b, + (Value::Integer(a), Value::Float(b)) => *a as f64 == *b, + (Value::Float(a), Value::Integer(b)) => *a == *b as f64, + (Value::Boolean(a), Value::Boolean(b)) => a == b, + (Value::String(a), Value::String(b)) => a == b, + (Value::Ref(a), Value::Ref(b)) => a == b, + (Value::Null, Value::Null) => true, + _ => false, + } + } +} + +impl Value { + pub fn as_float(&self) -> Option { + match self { + Value::Integer(i) => Some(*i as f64), + Value::Float(f) => Some(*f), + _ => None, + } + } + + pub fn as_integer(&self) -> Option { + match self { + Value::Integer(i) => Some(*i), + Value::Float(f) => Some(*f as i64), + _ => None, + } + } +} + +pub struct CallFrame { + pub return_address: usize, + pub stack_base: usize, + pub locals_count: usize, +} + +pub trait NativeInterface { + fn call(&mut self, id: u32, vm: &mut VirtualMachine) -> Result; +} + +pub struct VirtualMachine { + pub pc: usize, + pub operand_stack: Vec, + pub call_stack: Vec, + pub globals: Vec, + pub constant_pool: Vec, + pub rom: Vec, + pub heap: Vec, // Simplificado para demo, futuramente RAM/Heap real + pub cycles: u64, + pub halted: bool, +} + +impl VirtualMachine { + pub fn new(rom: Vec, constant_pool: Vec) -> Self { + Self { + pc: 0, + operand_stack: Vec::new(), + call_stack: Vec::new(), + globals: Vec::new(), + constant_pool, + rom, + heap: Vec::new(), + cycles: 0, + halted: false, + } + } + + /// Cria uma VM com um "Boot ROM" básico que exibe uma animação de padrão de teste. + /// Isso garante que a máquina sempre mostre algo ao ser ligada, mesmo sem cartucho. + pub fn new_boot_rom() -> Self { + let mut rom = Vec::new(); + let mut constant_pool = Vec::new(); + + // Constantes do Boot ROM + constant_pool.push(Value::Integer(140)); // 0: x + constant_pool.push(Value::Integer(70)); // 1: y + constant_pool.push(Value::Integer(40)); // 2: largura + constant_pool.push(Value::Integer(40)); // 3: altura + constant_pool.push(Value::Integer(7)); // 4: cor rect (indigo) + constant_pool.push(Value::Integer(0)); // 5: sample_id (square) + constant_pool.push(Value::Integer(0)); // 6: voice_id + constant_pool.push(Value::Integer(255)); // 7: volume + constant_pool.push(Value::Integer(127)); // 8: pan (center) + constant_pool.push(Value::Float(1.0)); // 9: pitch + constant_pool.push(Value::Integer(0)); // 10: bg color (black) + constant_pool.push(Value::Integer(10)); // 11: button START + + // -- PROGRAMA -- + + // 1. Toca o som de boot "plim" (uma vez) + // Push arguments for audio.play_sample(sample_id, voice_id, volume, pan, pitch) + rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); + rom.extend_from_slice(&5u32.to_le_bytes()); // sample_id + rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); + rom.extend_from_slice(&6u32.to_le_bytes()); // voice_id + rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); + rom.extend_from_slice(&7u32.to_le_bytes()); // volume + rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); + rom.extend_from_slice(&8u32.to_le_bytes()); // pan + rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); + rom.extend_from_slice(&9u32.to_le_bytes()); // pitch + rom.extend_from_slice(&(OpCode::Syscall as u16).to_le_bytes()); + rom.extend_from_slice(&0x3001u32.to_le_bytes()); + + let loop_start = rom.len() as u32; + + // 2. Verifica Cartucho e Input para Boot + // system.has_cart? + rom.extend_from_slice(&(OpCode::Syscall as u16).to_le_bytes()); + rom.extend_from_slice(&0x0001u32.to_le_bytes()); + + let jmp_no_cart_idx = rom.len(); + rom.extend_from_slice(&(OpCode::JmpIfFalse as u16).to_le_bytes()); + rom.extend_from_slice(&0u32.to_le_bytes()); // placeholder + + // Se tem cartucho, checa START + rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); + rom.extend_from_slice(&11u32.to_le_bytes()); // START button ID + rom.extend_from_slice(&(OpCode::Syscall as u16).to_le_bytes()); + rom.extend_from_slice(&0x2001u32.to_le_bytes()); // input.get_pad + + let jmp_no_start_idx = rom.len(); + rom.extend_from_slice(&(OpCode::JmpIfFalse as u16).to_le_bytes()); + rom.extend_from_slice(&0u32.to_le_bytes()); // placeholder + + // Se tem cartucho E START, run_cart! + rom.extend_from_slice(&(OpCode::Syscall as u16).to_le_bytes()); + rom.extend_from_slice(&0x0002u32.to_le_bytes()); // system.run_cart + + // Destino para quando não tem cartucho ou não apertou START + let skip_cart_addr = rom.len() as u32; + // Patch placeholders + let skip_bytes = skip_cart_addr.to_le_bytes(); + rom[jmp_no_cart_idx+2..jmp_no_cart_idx+6].copy_from_slice(&skip_bytes); + rom[jmp_no_start_idx+2..jmp_no_start_idx+6].copy_from_slice(&skip_bytes); + + // 3. Limpa a tela + rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); + rom.extend_from_slice(&10u32.to_le_bytes()); // bg color + rom.extend_from_slice(&(OpCode::Syscall as u16).to_le_bytes()); + rom.extend_from_slice(&0x1001u32.to_le_bytes()); + + // 4. Desenha o quadrado no centro + rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); + rom.extend_from_slice(&0u32.to_le_bytes()); // x + rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); + rom.extend_from_slice(&1u32.to_le_bytes()); // y + rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); + rom.extend_from_slice(&2u32.to_le_bytes()); // w + rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); + rom.extend_from_slice(&3u32.to_le_bytes()); // h + rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); + rom.extend_from_slice(&4u32.to_le_bytes()); // color + rom.extend_from_slice(&(OpCode::Syscall as u16).to_le_bytes()); + rom.extend_from_slice(&0x1002u32.to_le_bytes()); + + // 4. Sincroniza frame + rom.extend_from_slice(&(OpCode::FrameSync as u16).to_le_bytes()); + + // 5. Loop infinito + rom.extend_from_slice(&(OpCode::Jmp as u16).to_le_bytes()); + rom.extend_from_slice(&loop_start.to_le_bytes()); + + Self::new(rom, constant_pool) + } +} + +impl Default for VirtualMachine { + fn default() -> Self { + Self::new_boot_rom() + } +} + +impl VirtualMachine { + pub fn run_budget(&mut self, budget: u64, native: &mut dyn NativeInterface) -> Result { + let start_cycles = self.cycles; + let mut budget_used = 0; + + while budget_used < budget && !self.halted && self.pc < self.rom.len() { + let pc_before = self.pc; + let cycles_before = self.cycles; + let opcode_val = self.peek_u16()?; + let opcode = OpCode::try_from(opcode_val)?; + + if opcode == OpCode::FrameSync { + self.pc += 2; + self.cycles += OpCode::FrameSync.cycles(); + break; + } + + self.step(native)?; + + // Garantir progresso para evitar loop infinito real (onde nem PC nem ciclos avançam) + if self.pc == pc_before && self.cycles == cycles_before && !self.halted { + return Err(format!("VM stuck at PC 0x{:08X}", self.pc)); + } + + budget_used = self.cycles - start_cycles; + } + + Ok(budget_used) + } + + fn peek_u16(&self) -> Result { + if self.pc + 2 > self.rom.len() { + return Err("Unexpected end of ROM".into()); + } + let bytes = [ + self.rom[self.pc], + self.rom[self.pc + 1], + ]; + Ok(u16::from_le_bytes(bytes)) + } + + pub fn step(&mut self, native: &mut dyn NativeInterface) -> Result<(), String> { + if self.halted || self.pc >= self.rom.len() { + return Ok(()); + } + + let opcode_val = self.read_u16()?; + let opcode = OpCode::try_from(opcode_val)?; + + match opcode { + OpCode::Nop => {} + OpCode::Halt => { + self.halted = true; + } + OpCode::Jmp => { + let addr = self.read_u32()? as usize; + self.pc = addr; + } + OpCode::JmpIfFalse => { + let addr = self.read_u32()? as usize; + let val = self.pop()?; + if let Value::Boolean(false) = val { + self.pc = addr; + } + } + OpCode::PushConst => { + let idx = self.read_u32()? as usize; + let val = self.constant_pool.get(idx).cloned().ok_or("Invalid constant index")?; + self.push(val); + } + OpCode::Pop => { + self.pop()?; + } + OpCode::Dup => { + let val = self.peek()?.clone(); + self.push(val); + } + OpCode::Swap => { + let a = self.pop()?; + let b = self.pop()?; + self.push(a); + self.push(b); + } + OpCode::Add => self.binary_op(|a, b| match (a, b) { + (Value::Integer(a), Value::Integer(b)) => Ok(Value::Integer(a.wrapping_add(b))), + (Value::Float(a), Value::Float(b)) => Ok(Value::Float(a + b)), + (Value::Integer(a), Value::Float(b)) => Ok(Value::Float(a as f64 + b)), + (Value::Float(a), Value::Integer(b)) => Ok(Value::Float(a + b as f64)), + _ => Err("Invalid types for ADD".into()), + })?, + OpCode::Sub => self.binary_op(|a, b| match (a, b) { + (Value::Integer(a), Value::Integer(b)) => Ok(Value::Integer(a.wrapping_sub(b))), + (Value::Float(a), Value::Float(b)) => Ok(Value::Float(a - b)), + (Value::Integer(a), Value::Float(b)) => Ok(Value::Float(a as f64 - b)), + (Value::Float(a), Value::Integer(b)) => Ok(Value::Float(a - b as f64)), + _ => Err("Invalid types for SUB".into()), + })?, + OpCode::Mul => self.binary_op(|a, b| match (a, b) { + (Value::Integer(a), Value::Integer(b)) => Ok(Value::Integer(a.wrapping_mul(b))), + (Value::Float(a), Value::Float(b)) => Ok(Value::Float(a * b)), + (Value::Integer(a), Value::Float(b)) => Ok(Value::Float(a as f64 * b)), + (Value::Float(a), Value::Integer(b)) => Ok(Value::Float(a * b as f64)), + _ => Err("Invalid types for MUL".into()), + })?, + OpCode::Div => self.binary_op(|a, b| match (a, b) { + (Value::Integer(a), Value::Integer(b)) => { + if b == 0 { return Err("Division by zero".into()); } + Ok(Value::Integer(a / b)) + } + (Value::Float(a), Value::Float(b)) => { + if b == 0.0 { return Err("Division by zero".into()); } + Ok(Value::Float(a / b)) + } + (Value::Integer(a), Value::Float(b)) => { + if b == 0.0 { return Err("Division by zero".into()); } + Ok(Value::Float(a as f64 / b)) + } + (Value::Float(a), Value::Integer(b)) => { + if b == 0 { return Err("Division by zero".into()); } + Ok(Value::Float(a / b as f64)) + } + _ => Err("Invalid types for DIV".into()), + })?, + OpCode::Eq => self.binary_op(|a, b| Ok(Value::Boolean(a == b)))?, + OpCode::Neq => self.binary_op(|a, b| Ok(Value::Boolean(a != b)))?, + OpCode::Lt => self.binary_op(|a, b| { + match (a, b) { + (Value::Integer(a), Value::Integer(b)) => Ok(Value::Boolean(a < b)), + (Value::Float(a), Value::Float(b)) => Ok(Value::Boolean(a < b)), + (Value::Integer(a), Value::Float(b)) => Ok(Value::Boolean((a as f64) < b)), + (Value::Float(a), Value::Integer(b)) => Ok(Value::Boolean(a < (b as f64))), + _ => Err("Invalid types for LT".into()), + } + })?, + OpCode::Gt => self.binary_op(|a, b| { + match (a, b) { + (Value::Integer(a), Value::Integer(b)) => Ok(Value::Boolean(a > b)), + (Value::Float(a), Value::Float(b)) => Ok(Value::Boolean(a > b)), + (Value::Integer(a), Value::Float(b)) => Ok(Value::Boolean((a as f64) > b)), + (Value::Float(a), Value::Integer(b)) => Ok(Value::Boolean(a > (b as f64))), + _ => Err("Invalid types for GT".into()), + } + })?, + OpCode::And => self.binary_op(|a, b| match (a, b) { + (Value::Boolean(a), Value::Boolean(b)) => Ok(Value::Boolean(a && b)), + _ => Err("Invalid types for AND".into()), + })?, + OpCode::Or => self.binary_op(|a, b| match (a, b) { + (Value::Boolean(a), Value::Boolean(b)) => Ok(Value::Boolean(a || b)), + _ => Err("Invalid types for OR".into()), + })?, + OpCode::Not => { + let val = self.pop()?; + if let Value::Boolean(b) = val { + self.push(Value::Boolean(!b)); + } else { + return Err("Invalid type for NOT".into()); + } + } + OpCode::GetGlobal => { + let idx = self.read_u32()? as usize; + let val = self.globals.get(idx).cloned().ok_or("Invalid global index")?; + self.push(val); + } + OpCode::SetGlobal => { + let idx = self.read_u32()? as usize; + let val = self.pop()?; + if idx >= self.globals.len() { + self.globals.resize(idx + 1, Value::Null); + } + self.globals[idx] = val; + } + OpCode::GetLocal => { + let idx = self.read_u32()? as usize; + let frame = self.call_stack.last().ok_or("No active call frame")?; + let val = self.operand_stack.get(frame.stack_base + idx).cloned().ok_or("Invalid local index")?; + self.push(val); + } + OpCode::SetLocal => { + let idx = self.read_u32()? as usize; + let val = self.pop()?; + let frame = self.call_stack.last().ok_or("No active call frame")?; + let stack_idx = frame.stack_base + idx; + if stack_idx >= self.operand_stack.len() { + return Err("Local index out of bounds".into()); + } + self.operand_stack[stack_idx] = val; + } + OpCode::Call => { + let addr = self.read_u32()? as usize; + let args_count = self.read_u32()? as usize; + let stack_base = self.operand_stack.len() - args_count; + self.call_stack.push(CallFrame { + return_address: self.pc, + stack_base, + locals_count: args_count, + }); + self.pc = addr; + } + OpCode::Ret => { + let frame = self.call_stack.pop().ok_or("Call stack underflow")?; + let return_val = self.pop()?; + self.operand_stack.truncate(frame.stack_base); + self.push(return_val); + self.pc = frame.return_address; + } + OpCode::PushScope => { + let locals_count = self.read_u32()? as usize; + let stack_base = self.operand_stack.len(); + for _ in 0..locals_count { + self.push(Value::Null); + } + self.call_stack.push(CallFrame { + return_address: 0, + stack_base, + locals_count, + }); + } + OpCode::PopScope => { + let frame = self.call_stack.pop().ok_or("Call stack underflow")?; + self.operand_stack.truncate(frame.stack_base); + } + OpCode::Alloc => { + let size = self.read_u32()? as usize; + let ref_idx = self.heap.len(); + for _ in 0..size { + self.heap.push(Value::Null); + } + self.push(Value::Ref(ref_idx)); + } + OpCode::LoadRef => { + let offset = self.read_u32()? as usize; + let ref_val = self.pop()?; + if let Value::Ref(base) = ref_val { + let val = self.heap.get(base + offset).cloned().ok_or("Invalid heap access")?; + self.push(val); + } else { + return Err("Expected reference for LOAD_REF".into()); + } + } + OpCode::StoreRef => { + let offset = self.read_u32()? as usize; + let val = self.pop()?; + let ref_val = self.pop()?; + if let Value::Ref(base) = ref_val { + if base + offset >= self.heap.len() { + return Err("Invalid heap access".into()); + } + self.heap[base + offset] = val; + } else { + return Err("Expected reference for STORE_REF".into()); + } + } + OpCode::Syscall => { + let id = self.read_u32()?; + let native_cycles = native.call(id, self).map_err(|e| format!("Native call 0x{:08X} failed: {}", id, e))?; + self.cycles += native_cycles; + } + OpCode::FrameSync => { + return Ok(()); + } + } + + self.cycles += opcode.cycles(); + Ok(()) + } + + fn read_u32(&mut self) -> Result { + if self.pc + 4 > self.rom.len() { + return Err("Unexpected end of ROM".into()); + } + let bytes = [ + self.rom[self.pc], + self.rom[self.pc + 1], + self.rom[self.pc + 2], + self.rom[self.pc + 3], + ]; + self.pc += 4; + Ok(u32::from_le_bytes(bytes)) + } + + fn read_u16(&mut self) -> Result { + if self.pc + 2 > self.rom.len() { + return Err("Unexpected end of ROM".into()); + } + let bytes = [ + self.rom[self.pc], + self.rom[self.pc + 1], + ]; + self.pc += 2; + Ok(u16::from_le_bytes(bytes)) + } + + pub fn push(&mut self, val: Value) { + self.operand_stack.push(val); + } + + pub fn pop(&mut self) -> Result { + self.operand_stack.pop().ok_or("Stack underflow".into()) + } + + pub fn pop_number(&mut self) -> Result { + let val = self.pop()?; + val.as_float().ok_or_else(|| "Expected number".into()) + } + + pub fn pop_integer(&mut self) -> Result { + let val = self.pop()?; + val.as_integer().ok_or_else(|| "Expected integer".into()) + } + + pub fn peek(&self) -> Result<&Value, String> { + self.operand_stack.last().ok_or("Stack underflow".into()) + } + + fn binary_op(&mut self, f: F) -> Result<(), String> + where + F: FnOnce(Value, Value) -> Result, + { + let b = self.pop()?; + let a = self.pop()?; + let res = f(a, b)?; + self.push(res); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_add_instructions() { + struct NoopNative; + impl NativeInterface for NoopNative { + fn call(&mut self, _id: u32, _vm: &mut VirtualMachine) -> Result { Ok(0) } + } + let mut native = NoopNative; + + let mut rom = Vec::new(); + rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); + rom.extend_from_slice(&0u32.to_le_bytes()); // Const index 0 + rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); + rom.extend_from_slice(&1u32.to_le_bytes()); // Const index 1 + rom.extend_from_slice(&(OpCode::Add as u16).to_le_bytes()); + rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes()); + + let constant_pool = vec![Value::Integer(10), Value::Integer(20)]; + let mut vm = VirtualMachine::new(rom, constant_pool); + + vm.run_budget(100, &mut native).unwrap(); + + assert_eq!(vm.operand_stack.len(), 1); + assert_eq!(vm.operand_stack[0], Value::Integer(30)); + assert!(vm.halted); + } + + #[test] + fn test_jump_and_loop() { + struct NoopNative; + impl NativeInterface for NoopNative { + fn call(&mut self, _id: u32, _vm: &mut VirtualMachine) -> Result { Ok(0) } + } + let mut native = NoopNative; + + let mut rom = Vec::new(); + // Index 0: PUSH 0 (counter) + rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); + rom.extend_from_slice(&0u32.to_le_bytes()); + + // Index 6: DUP + let loop_start = rom.len(); + rom.extend_from_slice(&(OpCode::Dup as u16).to_le_bytes()); + + // PUSH 10 + rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); + rom.extend_from_slice(&1u32.to_le_bytes()); + + // LT (counter < 10) + rom.extend_from_slice(&(OpCode::Lt as u16).to_le_bytes()); + + // JMP_IF_FALSE to end + rom.extend_from_slice(&(OpCode::JmpIfFalse as u16).to_le_bytes()); + let jmp_placeholder = rom.len(); + rom.extend_from_slice(&0u32.to_le_bytes()); + + // PUSH 1 + rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); + rom.extend_from_slice(&2u32.to_le_bytes()); + + // ADD (increment counter) + rom.extend_from_slice(&(OpCode::Add as u16).to_le_bytes()); + + // JMP to start + rom.extend_from_slice(&(OpCode::Jmp as u16).to_le_bytes()); + rom.extend_from_slice(&(loop_start as u32).to_le_bytes()); + + // End + let loop_end = rom.len(); + rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes()); + + // Patch JMP_IF_FALSE addr + let end_addr_bytes = (loop_end as u32).to_le_bytes(); + rom[jmp_placeholder..jmp_placeholder+4].copy_from_slice(&end_addr_bytes); + + let constant_pool = vec![Value::Integer(0), Value::Integer(10), Value::Integer(1)]; + let mut vm = VirtualMachine::new(rom, constant_pool); + + vm.run_budget(1000, &mut native).unwrap(); + + assert!(vm.halted); + // O valor final na pilha deve ser 10 + assert_eq!(vm.operand_stack.last(), Some(&Value::Integer(10))); + } + + #[test] + fn test_native_call_gfx_clear() { + struct MockGfx { + cleared_color: Option, + } + impl NativeInterface for MockGfx { + fn call(&mut self, id: u32, vm: &mut VirtualMachine) -> Result { + if id == 0x1001 { + let color = vm.pop_integer()? as usize; + self.cleared_color = Some(color); + return Ok(100); + } + Ok(0) + } + } + let mut native = MockGfx { cleared_color: None }; + + let mut rom = Vec::new(); + // PUSH 5 (color index) + rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); + rom.extend_from_slice(&0u32.to_le_bytes()); + // CALL_NATIVE 0x1001 + rom.extend_from_slice(&(OpCode::Syscall as u16).to_le_bytes()); + rom.extend_from_slice(&0x1001u32.to_le_bytes()); + rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes()); + + let constant_pool = vec![Value::Integer(5)]; + let mut vm = VirtualMachine::new(rom, constant_pool); + + vm.run_budget(1000, &mut native).unwrap(); + + assert!(vm.halted); + assert_eq!(native.cleared_color, Some(5)); + } + + #[test] + fn test_system_run_cart() { + use crate::Machine; + use crate::model::Cartridge; + + let mut machine = Machine::new(); + + // 1. Verifica que não tem cartucho inicialmente + let mut rom = Vec::new(); + // CALL_NATIVE 0x0001 (has_cart) + rom.extend_from_slice(&(OpCode::Syscall as u16).to_le_bytes()); + rom.extend_from_slice(&0x0001u32.to_le_bytes()); + rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes()); + + machine.vm = VirtualMachine::new(rom, vec![]); + let mut vm = std::mem::take(&mut machine.vm); + vm.run_budget(100, &mut machine).unwrap(); + machine.vm = vm; + + assert_eq!(machine.vm.pop().unwrap(), Value::Boolean(false)); + + // 2. Adiciona um cartucho e roda + let mut cart_rom = Vec::new(); + // PUSH_CONST 0 + cart_rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); + cart_rom.extend_from_slice(&0u32.to_le_bytes()); + cart_rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes()); + let cart_pool = vec![Value::Integer(42)]; + + let cart = Cartridge::new(cart_rom, cart_pool); + machine.load_cartridge(cart); + + // Código para rodar o cartucho + let mut boot_rom = Vec::new(); + // CALL_NATIVE 0x0002 (run_cart) + boot_rom.extend_from_slice(&(OpCode::Syscall as u16).to_le_bytes()); + boot_rom.extend_from_slice(&0x0002u32.to_le_bytes()); + + machine.vm = VirtualMachine::new(boot_rom, vec![]); + let mut vm = std::mem::take(&mut machine.vm); + vm.run_budget(1000, &mut machine).unwrap(); + machine.vm = vm; + + // Após o run_budget, a VM deve ter executado o cartucho + assert_eq!(machine.vm.operand_stack.last(), Some(&Value::Integer(42))); + assert!(machine.vm.halted); + } +} diff --git a/docs/specs/topics/chapter-2.md b/docs/specs/topics/chapter-2.md index b12302a9..cb97763f 100644 --- a/docs/specs/topics/chapter-2.md +++ b/docs/specs/topics/chapter-2.md @@ -4,7 +4,7 @@ ## 1. Visão Geral -A **PROMETEU VM** é uma máquina virtual: +A **PROMETEU VM** é uma máquina virtual obrigatória e sempre presente no hardware lógico: * **stack-based** * determinística @@ -58,13 +58,14 @@ Propriedades: ## 3. Tipos Fundamentais -| Tipo | Descrição | -| --------- | ---------------------- | -| `number` | ponto flutuante 64-bit | -| `boolean` | verdadeiro/falso | -| `string` | UTF-8 imutável | -| `null` | ausência de valor | -| `ref` | referência ao heap | +| Tipo | Descrição | +| --------- | ----------------------- | +| `integer` | inteiro 64-bit (signed) | +| `float` | ponto flutuante 64-bit | +| `boolean` | verdadeiro/falso | +| `string` | UTF-8 imutável | +| `null` | ausência de valor | +| `ref` | referência ao heap | Não existem: @@ -200,19 +201,26 @@ Heap é: ### 6.8 Periféricos (Syscalls) -| Instrução | Ciclos | Descrição | -| ---------------- | -------- | ------------------- | -| `CALL_NATIVE id` | variável | Chamada ao hardware | +| Instrução | Ciclos | Descrição | +|--------------| -------- | ------------------- | +| `SYSCALL id` | variável | Chamada ao hardware | -Exemplos: +#### Syscalls Implementadas (v0.1) -* `gfx.clear` -* `gfx.fillRect` -* `gfx.present` -* `input.get` -* `audio.play` +| ID | Nome | Argumentos (Pilha) | Retorno | +| ---------- | ----------------- | ---------------------------- | ------- | +| `0x0001` | `system.has_cart` | - | `bool` | +| `0x0002` | `system.run_cart` | - | - | +| `0x1001` | `gfx.clear` | `color_idx` | - | +| `0x1002` | `gfx.draw_rect` | `x, y, w, h, color_idx` | - | +| `0x2001` | `input.get_pad` | `button_id` | `bool` | +| `0x3001` | `audio.play` | `s_id, v_id, vol, pan, pitch`| - | -O custo depende do periférico e entra no CAP. +**IDs de Botões:** +- `0`: Up, `1`: Down, `2`: Left, `3`: Right +- `4`: A, `5`: B, `6`: X, `7`: Y +- `8`: L, `9`: R +- `10`: Start, `11`: Select --- @@ -361,7 +369,26 @@ Efeito prático: --- -## 13. Boot e Cartucho +## 13. Boot ROM e Estado Inicial + +A **PROMETEU VM** nunca inicia em um estado totalmente "vazio" ou inativo. + +### 13.1 O Boot ROM +Se a máquina for inicializada sem um cartucho específico carregado, a VM executa um **Boot ROM padrão**. Este é um pequeno programa em bytecode embutido no core que: +* Emite um som de "plim" ao iniciar. +* Realiza um ciclo básico de limpeza de tela (preto). +* Exibe um quadrado centralizado (índigo). +* Serve como indicador visual e auditivo de que o hardware lógico está operacional. + +### 13.2 Ciclo de Boot +1. O Core é inicializado via `Machine::new()`. +2. A VM é carregada com o Boot ROM via `VirtualMachine::default()`. +3. O PC (Program Counter) é definido como `0`. +4. A execução começa imediatamente no primeiro `step_frame`. + +--- + +## 14. Boot e Cartucho O core: @@ -379,7 +406,7 @@ O cartucho: --- -## 13. Extensibilidade +## 15. Extensibilidade O Instruction Set é versionado. @@ -394,7 +421,7 @@ Nenhuma instrução existente muda de significado. --- -## 14. Resumo +## 16. Resumo * VM stack-based * custo explícito