diff --git a/Cargo.lock b/Cargo.lock index 606a287..1d40624 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -865,7 +865,7 @@ dependencies = [ [[package]] name = "fox32" -version = "0.2.1" +version = "0.3.0" dependencies = [ "anyhow", "image", diff --git a/Cargo.toml b/Cargo.toml index 2e4cd75..dae7269 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fox32" -version = "0.2.1" +version = "0.3.0" authors = ["ry"] edition = "2021" build = "build.rs" diff --git a/docs/encoding.md b/docs/encoding.md index 5f72aea..c23dfbf 100644 --- a/docs/encoding.md +++ b/docs/encoding.md @@ -16,9 +16,9 @@ If the instruction doesn't allow variable sizes or a size was not specified, set # Instruction table | 0x | -0 | -1 | -2 | -3 | -4 | -5 | -6 | -7 | -8 | -9 | -A | -B | -C | -D | -E | -F | | :-: | ---- | ------------- | ------------- | ------------- | ------------- | ------------- | ------------- | -------------- | ---- | ----- | -------------- | --- | --- | --- | --- | --- | -| 0- | NOP | ADD[.8,16,32] | MUL[.8,16,32] | AND[.8,16,32] | SLA[.8,16,32] | SRA[.8,16,32] | BSE[.8,16,32] | CMP[.8,16,32] | JMP | RJMP | PUSH[.8,16,32] | IN | ISE | | | | -| 1- | HALT | INC[.8,16,32] | | OR[.8,16,32] | | SRL[.8,16,32] | BCL[.8,16,32] | MOV[.8,16,32] | CALL | RCALL | POP[.8,16,32] | OUT | ICL | | | | -| 2- | BRK | SUB[.8,16,32] | DIV[.8,16,32] | XOR[.8,16,32] | ROL[.8,16,32] | ROR[.8,16,32] | BTS[.8,16,32] | MOVZ[.8,16,32] | LOOP | RLOOP | RET | | INT | | | | +| 0- | NOP | ADD[.8,16,32] | MUL[.8,16,32] | AND[.8,16,32] | SLA[.8,16,32] | SRA[.8,16,32] | BSE[.8,16,32] | CMP[.8,16,32] | JMP | RJMP | PUSH[.8,16,32] | IN | ISE | MSE | | | +| 1- | HALT | INC[.8,16,32] | | OR[.8,16,32] | | SRL[.8,16,32] | BCL[.8,16,32] | MOV[.8,16,32] | CALL | RCALL | POP[.8,16,32] | OUT | ICL | MCL | | | +| 2- | BRK | SUB[.8,16,32] | DIV[.8,16,32] | XOR[.8,16,32] | ROL[.8,16,32] | ROR[.8,16,32] | BTS[.8,16,32] | MOVZ[.8,16,32] | LOOP | RLOOP | RET | | INT | TLB | | | | 3- | | DEC[.8,16,32] | REM[.8,16,32] | NOT[.8,16,32] | | | | | | RTA | RETI | | | | | | # Condition table diff --git a/src/cpu.rs b/src/cpu.rs index 0e91522..4e0c925 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -35,6 +35,7 @@ impl std::convert::From for Flag { pub enum Exception { DivideByZero, InvalidOpcode(u32), + PageFault(u32), } #[derive(Debug)] @@ -159,52 +160,34 @@ impl Cpu { pub fn push_stack_8(&mut self, byte: u8) { let decremented_stack_pointer = self.stack_pointer.overflowing_sub(1); self.stack_pointer = decremented_stack_pointer.0; - if decremented_stack_pointer.1 { - // TODO: stack overflow exception - } self.bus.memory.write_8(self.stack_pointer, byte); } pub fn pop_stack_8(&mut self) -> u8 { let byte = self.bus.memory.read_8(self.stack_pointer); let incremented_stack_pointer = self.stack_pointer.overflowing_add(1); self.stack_pointer = incremented_stack_pointer.0; - if incremented_stack_pointer.1 { - // TODO: stack overflow exception - } byte } pub fn push_stack_16(&mut self, half: u16) { let decremented_stack_pointer = self.stack_pointer.overflowing_sub(2); self.stack_pointer = decremented_stack_pointer.0; - if decremented_stack_pointer.1 { - // TODO: stack overflow exception - } self.bus.memory.write_16(self.stack_pointer, half); } pub fn pop_stack_16(&mut self) -> u16 { let half = self.bus.memory.read_16(self.stack_pointer); let incremented_stack_pointer = self.stack_pointer.overflowing_add(2); self.stack_pointer = incremented_stack_pointer.0; - if incremented_stack_pointer.1 { - // TODO: stack overflow exception - } half } pub fn push_stack_32(&mut self, word: u32) { let decremented_stack_pointer = self.stack_pointer.overflowing_sub(4); self.stack_pointer = decremented_stack_pointer.0; - if decremented_stack_pointer.1 { - // TODO: stack overflow exception - } self.bus.memory.write_32(self.stack_pointer, word); } pub fn pop_stack_32(&mut self) -> u32 { let word = self.bus.memory.read_32(self.stack_pointer); let incremented_stack_pointer = self.stack_pointer.overflowing_add(4); self.stack_pointer = incremented_stack_pointer.0; - if incremented_stack_pointer.1 { - // TODO: stack overflow exception - } word } pub fn interrupt(&mut self, interrupt: Interrupt) { @@ -224,6 +207,10 @@ impl Cpu { let vector: u16 = 1; self.handle_exception(vector, Some(opcode)); } + Exception::PageFault(virtual_address) => { + let vector: u16 = 2; + self.handle_exception(vector, Some(virtual_address)); + } } } } @@ -2580,7 +2567,32 @@ impl Cpu { self.instruction_pointer } else { self.instruction_pointer + instruction_pointer_offset - } + } + } + + Instruction::Mse(condition) => { + let instruction_pointer_offset = 2; // increment past opcode half + let should_run = self.check_condition(condition); + if should_run { + *self.bus.memory.mmu_enabled() = true; + } + self.instruction_pointer + instruction_pointer_offset + } + Instruction::Mcl(condition) => { + let instruction_pointer_offset = 2; // increment past opcode half + let should_run = self.check_condition(condition); + if should_run { + *self.bus.memory.mmu_enabled() = false; + } + self.instruction_pointer + instruction_pointer_offset + } + Instruction::Tlb(condition, source) => { + let (source_value, instruction_pointer_offset) = self.read_source(source); + let should_run = self.check_condition(condition); + if should_run { + self.bus.memory.flush_tlb(Some(source_value)); + } + self.instruction_pointer + instruction_pointer_offset } } } @@ -2672,6 +2684,10 @@ enum Instruction { Ise(Condition), Icl(Condition), Int(Condition, Operand), + + Mse(Condition), + Mcl(Condition), + Tlb(Condition, Operand), } impl Instruction { @@ -2768,6 +2784,10 @@ impl Instruction { 0x1C => Some(Instruction::Icl(condition)), 0x2C => Some(Instruction::Int(condition, source)), + 0x0D => Some(Instruction::Mse(condition)), + 0x1D => Some(Instruction::Mcl(condition)), + 0x2D => Some(Instruction::Tlb(condition, source)), + _ => None, } } diff --git a/src/main.rs b/src/main.rs index b08b7fc..d47449a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,7 @@ pub mod disk; use audio::AudioChannel; use bus::Bus; -use cpu::{Cpu, Interrupt}; +use cpu::{Cpu, Exception, Interrupt}; use keyboard::Keyboard; use mouse::Mouse; use disk::DiskController; @@ -84,7 +84,9 @@ fn main() { let audio_channel_2 = Arc::new(Mutex::new(AudioChannel::new(2))); let audio_channel_3 = Arc::new(Mutex::new(AudioChannel::new(3))); - let memory = Memory::new(read_rom().as_slice()); + let (exception_sender, exception_receiver) = mpsc::channel::(); + + let memory = Memory::new(read_rom().as_slice(), exception_sender); let mut bus = Bus { memory: memory.clone(), audio_channel_0: audio_channel_0.clone(), @@ -155,6 +157,9 @@ fn main() { move || { loop { while !cpu.halted { + if let Ok(exception) = exception_receiver.try_recv() { + cpu.interrupt(Interrupt::Exception(exception)); + } if let Ok(interrupt) = interrupt_receiver.try_recv() { cpu.interrupt(interrupt); } diff --git a/src/memory.rs b/src/memory.rs index 23ccd3d..ba6fc75 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -1,9 +1,12 @@ // memory.rs use crate::error; +use crate::cpu::Exception; use std::cell::UnsafeCell; +use std::collections::HashMap; use std::sync::Arc; +use std::sync::mpsc::Sender; use std::io::Write; use std::fs::File; @@ -16,18 +19,33 @@ pub const MEMORY_ROM_START: usize = 0xF0000000; pub type MemoryRam = [u8; MEMORY_RAM_SIZE]; pub type MemoryRom = [u8; MEMORY_ROM_SIZE]; +#[derive(Debug)] +pub struct MemoryPage { + physical_address: u32, + present: bool, + rw: bool, +} + struct MemoryInner { ram: Box, rom: Box, + mmu_enabled: Box, + tlb: Box>, + paging_directory_address: Box, + exception_sender: Sender, } impl MemoryInner { - pub fn new(rom: &[u8]) -> Self { + pub fn new(rom: &[u8], exception_sender: Sender) -> Self { let mut this = Self { // HACK: allocate directly on the heap to avoid a stack overflow // at runtime while trying to move around a 64MB array ram: unsafe { Box::from_raw(Box::into_raw(vec![0u8; MEMORY_RAM_SIZE].into_boxed_slice()) as *mut MemoryRam) }, rom: unsafe { Box::from_raw(Box::into_raw(vec![0u8; MEMORY_ROM_SIZE].into_boxed_slice()) as *mut MemoryRom) }, + mmu_enabled: Box::from(false), + tlb: Box::from(HashMap::with_capacity(1024)), + paging_directory_address: Box::from(0x00000000), + exception_sender, }; this.rom.as_mut_slice().write(rom).expect("failed to copy ROM to memory"); this @@ -44,8 +62,8 @@ unsafe impl Send for Memory {} unsafe impl Sync for Memory {} impl Memory { - pub fn new(rom: &[u8]) -> Self { - Self(Arc::new(UnsafeCell::new(MemoryInner::new(rom)))) + pub fn new(rom: &[u8], exception_sender: Sender) -> Self { + Self(Arc::new(UnsafeCell::new(MemoryInner::new(rom, exception_sender)))) } fn inner(&self) -> &mut MemoryInner { @@ -54,13 +72,87 @@ impl Memory { pub fn ram(&self) -> &mut MemoryRam { &mut self.inner().ram } pub fn rom(&self) -> &mut MemoryRom { &mut self.inner().rom } + pub fn mmu_enabled(&self) -> &mut bool { &mut self.inner().mmu_enabled } + pub fn tlb(&self) -> &mut HashMap { &mut self.inner().tlb } + pub fn paging_directory_address(&self) -> &mut u32 { &mut self.inner().paging_directory_address } + pub fn exception_sender(&self) -> &mut Sender { &mut self.inner().exception_sender } pub fn dump(&self) { let mut file = File::create("memory.dump").expect("failed to open memory dump file"); file.write_all(self.ram()).expect("failed to write memory dump file"); } - pub fn read_8(&self, address: u32) -> u8 { + pub fn flush_tlb(&self, paging_directory_address: Option) { + let directory_address = if let Some(address) = paging_directory_address { + *self.paging_directory_address() = address; + address + } else { + *self.paging_directory_address() + }; + + self.tlb().clear(); + + // each table contains 1024 entries + // the paging directory contains pointers to paging tables with the following format: + // bit 0: present + // remaining bits are ignored, should be zero + // bits 12-31: physical address of paging table + + // the paging table contains pointers to physical memory pages with the following format: + // bit 0: present + // bit 1: r/w + // remaining bits are ignored, should be zero + // bits 12-31: physical address + + for directory_index in 0..1024 { + let directory = self.read_32(directory_address + (directory_index * 4)); + let dir_present = directory & 0b1 != 0; + let dir_address = directory & 0xFFFFF000; + if dir_present { + for table_index in 0..1024 { + let table = self.read_32(dir_address + (table_index * 4)); + let table_present = table & 0b01 != 0; + let table_rw = table & 0b10 != 0; + let table_address = table & 0xFFFFF000; + + let tlb_entry = MemoryPage { + //physical_address: (((directory_index + 1) * (table_index + 1) * 4096) - 4096), + physical_address: (directory_index << 22) | (table_index << 12), + present: table_present, + rw: table_rw, + }; + self.tlb().entry(table_address).or_insert(tlb_entry); + } + } + } + println!("{:#X?}", self.tlb()); + } + + pub fn virtual_to_physical(&self, virtual_address: u32) -> Option<(u32, bool)> { + let virtual_page = virtual_address & 0xFFFFF000; + let offset = virtual_address & 0x00000FFF; + let physical_page = self.tlb().get(&virtual_page); + let physical_address = match physical_page { + Some(page) => { + if page.present { + Some((page.physical_address | offset, page.rw)) + } else { + None + } + }, + None => None, + }; + physical_address + } + + pub fn read_8(&self, mut address: u32) -> u8 { + if *self.mmu_enabled() { + (address, _) = self.virtual_to_physical(address as u32).unwrap_or_else(|| { + self.exception_sender().send(Exception::PageFault(address)).unwrap(); + (0, false) + }); + } + let address = address as usize; let result = if address >= MEMORY_ROM_START && address < MEMORY_ROM_START + MEMORY_ROM_SIZE { @@ -74,7 +166,7 @@ impl Memory { *value } None => { - error(&format!("attempting to read from unmapped memory address: {:#010X}", address)); + error(&format!("attempting to read from unmapped physical memory address: {:#010X}", address)); } } } @@ -89,20 +181,32 @@ impl Memory { (self.read_8(address + 3) as u32) << 24 } - pub fn write_8(&self, address: u32, byte: u8) { - let address = address as usize; - - if address >= MEMORY_ROM_START && address < MEMORY_ROM_START + MEMORY_ROM_SIZE { - error(&format!("attempting to write to ROM address: {:#010X}", address)); + pub fn write_8(&self, mut address: u32, byte: u8) { + let mut writable = true; + if *self.mmu_enabled() { + (address, writable) = self.virtual_to_physical(address as u32).unwrap_or_else(|| { + self.exception_sender().send(Exception::PageFault(address)).unwrap(); + (0, false) + }); } - match self.ram().get_mut(address - MEMORY_RAM_START) { - Some(value) => { - *value = byte; + if writable { + let address = address as usize; + + if address >= MEMORY_ROM_START && address < MEMORY_ROM_START + MEMORY_ROM_SIZE { + error(&format!("attempting to write to ROM address: {:#010X}", address)); } - None => { - error(&format!("attempting to write to unmapped memory address: {:#010X}", address)); + + match self.ram().get_mut(address - MEMORY_RAM_START) { + Some(value) => { + *value = byte; + } + None => { + error(&format!("attempting to write to unmapped physical memory address: {:#010X}", address)); + } } + } else { + self.exception_sender().send(Exception::PageFault(address)).unwrap(); } } pub fn write_16(&self, address: u32, half: u16) {