From fb2e734a2f76b6335c48ef379c18d740c2a5a6f9 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Wed, 4 Mar 2026 04:38:11 +0000 Subject: [PATCH] wrote instruction logic, just need a new assembler now :) --- dsa-emu/Cargo.toml | 10 + dsa-emu/benches/bench_mainstore.rs | 304 ++++++++++++-------------- dsa-emu/src/lib.rs | 11 +- dsa-emu/src/main.rs | 55 ++--- dsa-emu/src/memory/mainstore.rs | 166 -------------- dsa-emu/src/memory/mod.rs | 7 +- dsa-emu/src/processor/interrupts.rs | 1 + dsa-emu/src/processor/processor.rs | 322 +++++++++++++++++++++++++--- dsa-emu/src/processor/state.rs | 24 +-- 9 files changed, 469 insertions(+), 431 deletions(-) delete mode 100644 dsa-emu/src/memory/mainstore.rs diff --git a/dsa-emu/Cargo.toml b/dsa-emu/Cargo.toml index 25ba853..86f8904 100644 --- a/dsa-emu/Cargo.toml +++ b/dsa-emu/Cargo.toml @@ -20,3 +20,13 @@ serde = { version = "1.0.228", features = ["derive"] } [[bench]] name = "bench_mainstore" harness = false + +[features] +default = ["mainstore-bulkalloc", "mainstore-arraymap"] + +# Memory Bank Features +mainstore-bulkalloc = [] # Fastest for Writes +mainstore-prealloc = [] # Fastest for Reads +mainstore-stackarray = [] # Slightly outperforms ArrayMap but requires a large stack +mainstore-arraymap = [] # Simple implementation +mainstore-hashmap = [] # Old implementation, no raw pointers. Uses a hashmap diff --git a/dsa-emu/benches/bench_mainstore.rs b/dsa-emu/benches/bench_mainstore.rs index 2e25eb9..5076c6d 100644 --- a/dsa-emu/benches/bench_mainstore.rs +++ b/dsa-emu/benches/bench_mainstore.rs @@ -4,21 +4,16 @@ use criterion::{ }; use std::time::Duration; -use dsa::{FastStore, MainStore, RandomAccessMemory}; +use dsa::RandomAccessMemory; -// ── reproduce the trait and PhysAddr here, or import from your crate ───────── type PhysAddr = u32; // ── address generators ──────────────────────────────────────────────────────── -/// Cycles through a small set of addresses — stays hot in L1/L2. -/// Simulates a tight inner loop hitting the same working set repeatedly. fn sequential_addrs(n: usize, max_addr: u32) -> Vec { (0..n).map(|i| ((i as u32 * 4) & (max_addr - 1))).collect() } -/// LCG pseudo-random addresses across the full address space. -/// Simulates worst-case cache behaviour. fn random_addrs(n: usize, max_addr: u32) -> Vec { let mut addrs = Vec::with_capacity(n); let mut x: u32 = 0xdeadbeef; @@ -29,205 +24,184 @@ fn random_addrs(n: usize, max_addr: u32) -> Vec { addrs } -/// Walks addresses page by page — tests page-boundary crossing behaviour. fn page_stride_addrs(n: usize) -> Vec { (0..n).map(|i| (i as u32 * 4096) & 0x00FF_FFFF).collect() } -// ── generic benchmark functions ─────────────────────────────────────────────── - -fn bench_read_byte( - group: &mut BenchmarkGroup, - name: &str, - mem: &M, - addrs: &[PhysAddr], -) { - group.throughput(Throughput::Elements(addrs.len() as u64)); - group.bench_function(name, |b| { - b.iter(|| { - let mut sum: u32 = 0; - for &addr in addrs { - sum = sum.wrapping_add(mem.read_byte(black_box(addr)) as u32); - } - black_box(sum) - }) - }); +fn random_page_addrs(n: usize, max_addr: u32) -> Vec { + let mut addrs = Vec::with_capacity(n); + let mut x: u32 = 0xc0ffee42; + for _ in 0..n { + x = x.wrapping_mul(1664525).wrapping_add(1013904223); + addrs.push((x & (max_addr - 1)) & !0xFFF); + } + addrs } -fn bench_write_byte( +// ── runners ─────────────────────────────────────────────────────────────────── + +fn run_read_byte( group: &mut BenchmarkGroup, - name: &str, - mem: &mut M, addrs: &[PhysAddr], + implementations: &mut [(&str, Box)], ) { group.throughput(Throughput::Elements(addrs.len() as u64)); - group.bench_function(name, |b| { - b.iter(|| { - for (i, &addr) in addrs.iter().enumerate() { - mem.write_byte(black_box(addr), black_box(i as u8)); - } - }) - }); + for (name, mem) in implementations.iter() { + group.bench_with_input(BenchmarkId::new(*name, ""), addrs, |b, addrs| { + b.iter(|| { + let mut sum: u32 = 0; + for &addr in addrs { + sum = sum.wrapping_add(mem.read_byte(black_box(addr)) as u32); + } + black_box(sum) + }) + }); + } } -fn bench_read_word( +fn run_write_byte( group: &mut BenchmarkGroup, - name: &str, - mem: &M, addrs: &[PhysAddr], + implementations: &mut [(&str, Box)], ) { group.throughput(Throughput::Elements(addrs.len() as u64)); - group.bench_function(name, |b| { - b.iter(|| { - let mut sum: u32 = 0; - for &addr in addrs { - sum = sum.wrapping_add(mem.read_word(black_box(addr))); - } - black_box(sum) - }) - }); + for (name, mem) in implementations.iter_mut() { + group.bench_with_input(BenchmarkId::new(*name, ""), addrs, |b, addrs| { + b.iter(|| { + for (i, &addr) in addrs.iter().enumerate() { + mem.write_byte(black_box(addr), black_box(i as u8)); + } + }) + }); + } } -fn bench_write_word( +fn run_read_word( group: &mut BenchmarkGroup, - name: &str, - mem: &mut M, addrs: &[PhysAddr], + implementations: &mut [(&str, Box)], ) { group.throughput(Throughput::Elements(addrs.len() as u64)); - group.bench_function(name, |b| { - b.iter(|| { - for (i, &addr) in addrs.iter().enumerate() { - mem.write_word(black_box(addr), black_box(i as u32)); - } - }) - }); + for (name, mem) in implementations.iter() { + group.bench_with_input(BenchmarkId::new(*name, ""), addrs, |b, addrs| { + b.iter(|| { + let mut sum: u32 = 0; + for &addr in addrs { + sum = sum.wrapping_add(mem.read_word(black_box(addr))); + } + black_box(sum) + }) + }); + } } -fn bench_read_page( +fn run_write_word( group: &mut BenchmarkGroup, - name: &str, - mem: &M, addrs: &[PhysAddr], + implementations: &mut [(&str, Box)], +) { + group.throughput(Throughput::Elements(addrs.len() as u64)); + for (name, mem) in implementations.iter_mut() { + group.bench_with_input(BenchmarkId::new(*name, ""), addrs, |b, addrs| { + b.iter(|| { + for (i, &addr) in addrs.iter().enumerate() { + mem.write_word(black_box(addr), black_box(i as u32)); + } + }) + }); + } +} + +fn run_read_page( + group: &mut BenchmarkGroup, + addrs: &[PhysAddr], + implementations: &mut [(&str, Box)], ) { group.throughput(Throughput::Bytes(addrs.len() as u64 * 4096)); - group.bench_function(name, |b| { - b.iter(|| { - let mut sum: u8 = 0; - for &addr in addrs { - let page = mem.read_page(black_box(addr)); - sum = sum.wrapping_add(page[0]); - } - black_box(sum) - }) - }); + for (name, mem) in implementations.iter() { + group.bench_with_input(BenchmarkId::new(*name, ""), addrs, |b, addrs| { + b.iter(|| { + let mut sum: u64 = 0; + for &addr in addrs { + let page = mem.read_page(black_box(addr)); + for chunk in page.chunks_exact(8) { + sum = sum.wrapping_add(u64::from_le_bytes(chunk.try_into().unwrap())); + } + } + black_box(sum) + }) + }); + } } -fn bench_write_page( +fn run_write_page( group: &mut BenchmarkGroup, - name: &str, - mem: &mut M, addrs: &[PhysAddr], + implementations: &mut [(&str, Box)], ) { let page_data = [0xABu8; 4096]; group.throughput(Throughput::Bytes(addrs.len() as u64 * 4096)); - group.bench_function(name, |b| { - b.iter(|| { - for &addr in addrs { - mem.write_page(black_box(addr), black_box(&page_data)); - } - }) - }); -} - -// ── run all access patterns for a given implementation ──────────────────────── - -fn bench_implementation( - c: &mut Criterion, - impl_name: &str, - mut mem: M, - max_addr: u32, -) { - const N: usize = 64; - - let seq_addrs = sequential_addrs(N, max_addr); - let rand_addrs = random_addrs(N, max_addr); - let page_stride = page_stride_addrs(N); - - // ── read_byte ──────────────────────────────────────────────────────────── - { - let mut g = c.benchmark_group(format!("{}/read_byte", impl_name)); - g.measurement_time(Duration::from_secs(3)); - bench_read_byte(&mut g, "sequential", &mem, &seq_addrs); - bench_read_byte(&mut g, "random", &mem, &rand_addrs); - g.finish(); - } - - // ── write_byte ─────────────────────────────────────────────────────────── - { - let mut g = c.benchmark_group(format!("{}/write_byte", impl_name)); - g.measurement_time(Duration::from_secs(3)); - bench_write_byte(&mut g, "sequential", &mut mem, &seq_addrs); - bench_write_byte(&mut g, "random", &mut mem, &rand_addrs); - g.finish(); - } - - // ── read_word ──────────────────────────────────────────────────────────── - { - let mut g = c.benchmark_group(format!("{}/read_word", impl_name)); - g.measurement_time(Duration::from_secs(3)); - bench_read_word(&mut g, "sequential", &mem, &seq_addrs); - bench_read_word(&mut g, "random", &mem, &rand_addrs); - g.finish(); - } - - // ── write_word ─────────────────────────────────────────────────────────── - { - let mut g = c.benchmark_group(format!("{}/write_word", impl_name)); - g.measurement_time(Duration::from_secs(3)); - bench_write_word(&mut g, "sequential", &mut mem, &seq_addrs); - bench_write_word(&mut g, "random", &mut mem, &rand_addrs); - g.finish(); - } - - // ── read_page ──────────────────────────────────────────────────────────── - { - let mut g = c.benchmark_group(format!("{}/read_page", impl_name)); - g.measurement_time(Duration::from_secs(3)); - bench_read_page(&mut g, "sequential", &mem, &seq_addrs); - bench_read_page(&mut g, "random", &mem, &rand_addrs); - bench_read_page(&mut g, "page_stride", &mem, &page_stride); - g.finish(); - } - - // ── write_page ─────────────────────────────────────────────────────────── - { - let mut g = c.benchmark_group(format!("{}/write_page", impl_name)); - g.measurement_time(Duration::from_secs(3)); - bench_write_page(&mut g, "sequential", &mut mem, &seq_addrs); - bench_write_page(&mut g, "random", &mut mem, &rand_addrs); - bench_write_page(&mut g, "page_stride", &mut mem, &page_stride); - g.finish(); + for (name, mem) in implementations.iter_mut() { + group.bench_with_input(BenchmarkId::new(*name, ""), addrs, |b, addrs| { + b.iter(|| { + for &addr in addrs { + mem.write_page(black_box(addr), black_box(&page_data)); + } + }) + }); } } -// ── wire up your implementations here ──────────────────────────────────────── -// -// Replace these stubs with your actual types. Each call to bench_implementation -// runs the full suite and labels it separately in the HTML report. -// -// Example: -// fn benchmarks(c: &mut Criterion) { -// bench_implementation(c, "MainStore", MainStore::new(), 0x00FF_FFFF); -// bench_implementation(c, "FxHashMap", HashMapMem::new(), 0x00FF_FFFF); -// } -// +// ── entry point ─────────────────────────────────────────────────────────────── + fn benchmarks(c: &mut Criterion) { - // TODO: replace with your implementations - bench_implementation(c, "MainStore", MainStore::new(), 0x00FF_FFFF); - bench_implementation(c, "FastStore", FastStore::new(), 0x00FF_FFFF); + const N: usize = 4096; + const MAX_ADDR: u32 = 0x00FF_FFFF; - let _ = c; + let seq_addrs = sequential_addrs(N, MAX_ADDR); + let rand_addrs = random_addrs(N, MAX_ADDR); + let page_addrs_seq = page_stride_addrs(N); + let page_addrs_rand = random_page_addrs(N, MAX_ADDR); + + let mut implementations: Vec<(&str, Box)> = vec![ + #[cfg(feature = "mainstore-hashmap")] + ("HashStore", Box::new(dsa::HashStore::new())), + #[cfg(feature = "mainstore-arraymap")] + ("ArrayStore", Box::new(dsa::ArrayStore::new())), + #[cfg(feature = "mainstore-stackarray")] + ("StackArrayStore", Box::new(dsa::StackArrayStore::new())), + #[cfg(feature = "mainstore-prealloc")] + ("PreAllocStore", Box::new(dsa::PreAllocStore::new())), + #[cfg(feature = "mainstore-bulkalloc")] + ("BulkAllocStore", Box::new(dsa::BulkAllocStore::new())), + ]; + + macro_rules! group { + ($name:expr, $secs:expr, $runner:ident, $addrs:expr) => {{ + let mut g = c.benchmark_group($name); + g.measurement_time(Duration::from_secs($secs)); + $runner(&mut g, $addrs, &mut implementations); + g.finish(); + }}; + } + + group!("write_word/random", 30, run_write_word, &rand_addrs); + group!("write_byte/random", 30, run_write_byte, &rand_addrs); + + group!("read_byte/sequential", 30, run_read_byte, &seq_addrs); + group!("read_byte/random", 30, run_read_byte, &rand_addrs); + + group!("read_word/sequential", 30, run_read_word, &seq_addrs); + group!("read_word/random", 30, run_read_word, &rand_addrs); + + group!("write_byte/sequential", 30, run_write_byte, &seq_addrs); + group!("write_word/sequential", 30, run_write_word, &seq_addrs); + + group!("read_page/sequential", 30, run_read_page, &page_addrs_seq); + group!("read_page/random", 30, run_read_page, &page_addrs_rand); + + group!("write_page/sequential", 30, run_write_page, &page_addrs_seq); + group!("write_page/random", 30, run_write_page, &page_addrs_rand); } criterion_group!(benches, benchmarks); diff --git a/dsa-emu/src/lib.rs b/dsa-emu/src/lib.rs index db98b6a..5b6a16f 100644 --- a/dsa-emu/src/lib.rs +++ b/dsa-emu/src/lib.rs @@ -1,12 +1,11 @@ #![feature(likely_unlikely)] #![feature(ptr_as_ref_unchecked)] +pub mod args; +mod common; mod memory; mod processor; -pub use { - memory::MemoryMap, - memory::mainstore::{FastStore, MainStore, RandomAccessMemory}, - processor::processor::Emulator, - processor::state::SharedState, -}; +pub use {memory::MemoryMap, processor::processor::Emulator, processor::state::SharedState}; + +pub use memory::ram::*; diff --git a/dsa-emu/src/main.rs b/dsa-emu/src/main.rs index 94e1662..bf22912 100644 --- a/dsa-emu/src/main.rs +++ b/dsa-emu/src/main.rs @@ -1,61 +1,32 @@ -use arc_swap::ArcSwap; use clap::Parser; -use std::{ - fs, - path::PathBuf, - sync::{ - Arc, - atomic::{AtomicBool, AtomicU8}, - }, - thread, - time::Duration, -}; - -use dsa::{Emulator, FastStore, MainStore, MemoryMap, SharedState}; +use dsa::{BulkAllocStore, Emulator, MemoryMap, SharedState, args::DsaArgs}; +use std::{sync::Arc, thread}; fn main() { let args = DsaArgs::parse(); - let mut emulator = Emulator::new(FastStore::new()); - - let mmap = match args.get_memory_map() { - Some(m) => m, - None => MemoryMap::default(), - }; - - emulator.apply_memory_map(mmap); + let mut emulator = Emulator::new(BulkAllocStore::new()); + emulator.apply_memory_map(args.get_memory_map().unwrap_or_default()); let state = emulator.state_handle(); - let runner = thread::spawn(move || emulator.run()); + + const STACK_SIZE: usize = 1024 * 1024 * 16; + let runner = thread::Builder::new() + .stack_size(STACK_SIZE) + .spawn(move || emulator.run()) + .unwrap(); + let observer = thread::spawn(|| observe(state)); runner.join().unwrap(); observer.join().unwrap(); } +/// todo: remove this! fn observe(state: Arc) { loop { thread::sleep(std::time::Duration::from_millis(100)); let state = state.proc.load(); - println!("GP Registers: {:?}", state.gp_registers); - } -} - -#[derive(Parser, Debug)] -#[command(version, about, long_about = None)] -struct DsaArgs { - /// Memory map to use on boot. - /// Overrides default value. - #[arg(long = "mmap")] - memory_map: Option, -} - -impl DsaArgs { - pub fn get_memory_map(&self) -> Option { - self.memory_map.as_ref().and_then(|m| { - fs::read_to_string(m) - .ok() - .and_then(|map| ron::from_str(&map).ok()) - }) + println!("GP Registers: {:?}", state.registers); } } diff --git a/dsa-emu/src/memory/mainstore.rs b/dsa-emu/src/memory/mainstore.rs deleted file mode 100644 index 3d8a844..0000000 --- a/dsa-emu/src/memory/mainstore.rs +++ /dev/null @@ -1,166 +0,0 @@ -use fxhash::FxHashMap; - -use crate::memory::PhysAddr; -use std::{ - alloc::{Layout, alloc_zeroed}, - hint::likely, -}; - -type Page = [u8; 4096]; - -pub trait RandomAccessMemory { - fn read_byte(&self, addr: PhysAddr) -> u8; - - fn write_byte(&mut self, addr: PhysAddr, value: u8); - - fn read_word(&self, addr: PhysAddr) -> u32; - - fn write_word(&mut self, addr: PhysAddr, value: u32); - - fn read_page(&self, addr: PhysAddr) -> &[u8; 4096]; - - fn write_page(&mut self, addr: PhysAddr, value: &[u8; 4096]); -} - -pub struct MainStore { - pages: FxHashMap, -} - -impl MainStore { - pub fn new() -> Self { - Self { - pages: FxHashMap::default(), - } - } - - #[inline(always)] - const fn segment_addr(addr: PhysAddr) -> (PhysAddr, usize) { - (addr & !(0xFFF), (addr & 0xFFF) as usize) - } -} - -impl RandomAccessMemory for MainStore { - #[inline(always)] - fn read_byte(&self, addr: PhysAddr) -> u8 { - let (page, offset) = Self::segment_addr(addr); - self.pages.get(&page).map(|p| p[offset]).unwrap_or(0) - } - - #[inline(always)] - fn read_word(&self, addr: PhysAddr) -> u32 { - let (page, offset) = Self::segment_addr(addr); - debug_assert_eq!(offset % 4, 0); - let page = self.pages.get(&page).unwrap_or(&[0; 4096]); - u32::from_be_bytes(page[offset..=offset + 3].try_into().unwrap()) - } - - #[inline(always)] - fn read_page(&self, addr: PhysAddr) -> &[u8; 4096] { - debug_assert_eq!(addr % 0x1000, 0); - self.pages.get(&addr).unwrap_or(&[0; 4096]) - } - - #[inline(always)] - fn write_byte(&mut self, addr: PhysAddr, value: u8) { - let (page, offset) = Self::segment_addr(addr); - self.pages.entry(page).or_insert_with(|| [0; 4096])[offset] = value; - } - - #[inline(always)] - fn write_word(&mut self, addr: PhysAddr, value: u32) { - let (page, offset) = Self::segment_addr(addr); - debug_assert_eq!(offset % 4, 0); - let page = self.pages.entry(page).or_insert_with(|| [0; 4096]); - page[offset..=offset + 3].copy_from_slice(&value.to_be_bytes()); - } - - #[inline(always)] - fn write_page(&mut self, addr: PhysAddr, value: &[u8; 4096]) { - debug_assert_eq!(addr % 0x1000, 0); - let page = self.pages.entry(addr).or_insert_with(|| [0; 4096]); - page.copy_from_slice(value); - } -} - -const NUM_PAGES: usize = 2 << 20; - -pub struct FastStore { - pages: Box<[*mut Page; NUM_PAGES]>, -} - -unsafe impl Send for FastStore {} -impl FastStore { - pub fn new() -> Self { - let pages = vec![0 as *mut Page; 2 << 20] - .into_boxed_slice() - .try_into() - .unwrap(); - Self { pages } - } - - fn alloc(&mut self) -> *mut Page { - let layout = Layout::from_size_align(4096, 4096).unwrap(); - unsafe { alloc_zeroed(layout) as *mut Page } - } -} - -impl RandomAccessMemory for FastStore { - fn read_word(&self, addr: PhysAddr) -> u32 { - let page = self.pages[(addr >> 12) as usize]; - if likely(!page.is_null()) { - let offset = (addr & 0xFFF) as usize; - return unsafe { (page.add(offset) as *const u32).read() }; - } - - // Slow path: MMIO, fault, etc — never inlined - // Since we're only reading, we can safely return 0 - return 0; - } - - fn read_byte(&self, addr: PhysAddr) -> u8 { - let page = self.pages[(addr >> 12) as usize]; - if likely(!page.is_null()) { - let offset = (addr & 0xFFF) as usize; - return unsafe { (page.add(offset) as *const u8).read() }; - } - - return 0; - } - - fn read_page(&self, addr: PhysAddr) -> &[u8; 4096] { - let page = self.pages[(addr >> 12) as usize]; - if likely(!page.is_null()) { - let offset = (addr & 0xFFF) as usize; - return unsafe { &*(page.add(offset) as *const [u8; 4096]) }; - } - - return &[0; 4096]; - } - - fn write_byte(&mut self, addr: PhysAddr, value: u8) { - let page = self.pages[(addr >> 12) as usize]; - - if likely(!page.is_null()) { - let offset = (addr & 0xFFF) as usize; - unsafe { (page.add(offset) as *mut u8).write(value) }; - } - } - - fn write_word(&mut self, addr: PhysAddr, value: u32) { - let page = self.pages[(addr >> 12) as usize]; - - if likely(!page.is_null()) { - let offset = (addr & 0xFFF) as usize; - unsafe { (page.add(offset) as *mut u32).write(value) }; - } - } - - fn write_page(&mut self, addr: PhysAddr, value: &[u8; 4096]) { - let page = self.pages[(addr >> 12) as usize]; - - if likely(!page.is_null()) { - let offset = (addr & 0xFFF) as usize; - unsafe { (page.add(offset) as *mut [u8; 4096]).write(*value) }; - } - } -} diff --git a/dsa-emu/src/memory/mod.rs b/dsa-emu/src/memory/mod.rs index 797ae3a..a1b0485 100644 --- a/dsa-emu/src/memory/mod.rs +++ b/dsa-emu/src/memory/mod.rs @@ -1,13 +1,10 @@ use serde::{Deserialize, Serialize}; -use crate::{ - memory::{mainstore::RandomAccessMemory, mmu::MMU}, - processor::processor::Emulator, -}; +use crate::{RandomAccessMemory, memory::mmu::MMU, processor::processor::Emulator}; mod cache; -pub mod mainstore; pub mod mmu; +pub mod ram; mod tlb; pub type VirtAddr = u32; diff --git a/dsa-emu/src/processor/interrupts.rs b/dsa-emu/src/processor/interrupts.rs index 80bf748..4171001 100644 --- a/dsa-emu/src/processor/interrupts.rs +++ b/dsa-emu/src/processor/interrupts.rs @@ -1,4 +1,5 @@ pub enum Interrupt { PageFault, ProtectionFault, + Generic(u8), } diff --git a/dsa-emu/src/processor/processor.rs b/dsa-emu/src/processor/processor.rs index 37b04ee..942754c 100644 --- a/dsa-emu/src/processor/processor.rs +++ b/dsa-emu/src/processor/processor.rs @@ -1,20 +1,20 @@ -use std::{sync::Arc, thread, time::Duration}; +use std::{ + hint::unlikely, + sync::{Arc, atomic::Ordering, mpsc}, + thread, + time::Duration, +}; use crate::{ - memory::{ - FaultInfo, MemoryMap, - mainstore::{MainStore, RandomAccessMemory}, - mmu::MMU, - }, - processor::{ - interrupts::Interrupt, - state::{ProcessorSnapshot, SharedState}, - }, + common::instructions::{InstructionWord, Opcode, Reg}, + memory::{FaultInfo, MemoryMap, mmu::MMU, ram::RandomAccessMemory}, + processor::{interrupts::Interrupt, state::SharedState}, }; pub struct Emulator { internal_state: ProcessorSnapshot, shared_state: Arc, + interrupts: mpsc::Receiver, // memory mmu: MMU, @@ -27,9 +27,12 @@ pub struct Emulator { unsafe impl Send for Emulator {} impl Emulator { pub fn new(mem: Mem) -> Self { + let (sender, receiver) = mpsc::channel::(); + Self { + interrupts: receiver, internal_state: ProcessorSnapshot::default(), - shared_state: Arc::new(SharedState::new()), + shared_state: Arc::new(SharedState::new(sender)), mmu: MMU::new(), mainstore: mem, memory_map: None, @@ -40,48 +43,284 @@ impl Emulator { self.shared_state.clone() } + #[inline] pub fn mmu_mut(&mut self) -> &mut MMU { &mut self.mmu } + #[inline] pub fn memory_mut(&mut self) -> &mut impl RandomAccessMemory { &mut self.mainstore } + #[cold] pub fn apply_memory_map(&mut self, map: MemoryMap) -> &mut Self { map.apply(self); self.memory_map = Some(map); self } - pub fn run(&mut self) { - let mut clock = 0; - + #[cold] + fn boot(&mut self) { let regions = MemoryMap::default(); - - self.mmu.paging_enabled(true); + // self.mmu.paging_enabled(true); MemoryMap::identity_map(&mut self.mmu, regions.regions.get(0).unwrap()); - loop { - thread::sleep(Duration::from_micros(1000)); + self.internal_state.running = true; + } - match self.mmu.lookup(clock) { - Ok(addr) => { - let x = self.mainstore.read_word(addr); - println!("{}", x); - self.mainstore.write_word(addr, x + 1); + #[cold] + fn update(&mut self) { + self.shared_state + .proc + .store(Arc::new(self.internal_state.clone())); + } + + #[must_use] + #[inline] + pub fn reg(&self, reg: Reg) -> u32 { + if reg as u8 == Reg::Zero as u8 { + return 0; + } + debug_assert!((reg as usize) < ProcessorSnapshot::REG_COUNT); + + unsafe { *self.internal_state.registers.get_unchecked(reg as usize) } + } + + #[inline] + pub fn mut_reg(&mut self, reg: Reg) -> &mut u32 { + debug_assert!((reg as usize) < ProcessorSnapshot::REG_COUNT); + + unsafe { + self.internal_state + .registers + .get_unchecked_mut(reg as usize) + } + } + + #[inline] + fn execute(&mut self, word: InstructionWord) { + match Opcode::from_u8(word.opcode()) { + // Nothing + Some(Opcode::Nop) => {} + + // move + Some(Opcode::Mov) => { + *self.mut_reg(word.dest()) = self.reg(word.src1()); + } + Some(Opcode::CMov) => { + if self.reg(word.misc()) != 0 { + *self.mut_reg(word.dest()) = self.reg(word.src1()); } - Err(FaultInfo::PageFault) => self.interrupt(Interrupt::PageFault), } - self.internal_state.gp_registers[0] += 10; - - if clock % 1000 == 0 { - self.shared_state - .proc - .store(Arc::new(self.internal_state.clone())); + // load + Some(Opcode::Ldbs) => todo!(), + Some(Opcode::Ldhs) => todo!(), + Some(Opcode::Ldb) => { + *self.mut_reg(word.dest()) = u32::from( + self.mainstore + .read_byte(self.reg(word.src1()) + word.imm16() as u32), + ) } - clock += 1; + Some(Opcode::Ldh) => { + *self.mut_reg(word.dest()) = u32::from( + self.mainstore + .read_word(self.reg(word.src1()) + word.imm16() as u32) + >> 16, + ) + } + Some(Opcode::Ldw) => { + *self.mut_reg(word.dest()) = u32::from( + self.mainstore + .read_word(self.reg(word.src1()) + word.imm16() as u32), + ) + } + + // store + Some(Opcode::Stb) => { + self.mainstore.write_byte( + self.reg(word.dest()) + word.imm16() as u32, + self.reg(word.src1()) as u8, + ); + } + Some(Opcode::Sth) => { + self.mainstore.write_byte( + self.reg(word.dest()) + word.imm16() as u32, + (self.reg(word.src1()) as u16 >> 8) as u8, + ); + self.mainstore.write_byte( + self.reg(word.dest()) + word.imm16() as u32 + 1, + self.reg(word.src1()) as u8, + ); + } + Some(Opcode::Stw) => { + self.mainstore.write_word( + self.reg(word.dest()) + word.imm16() as u32, + self.reg(word.src1()), + ); + } + + // load immediate + Some(Opcode::Lli) => { + *self.mut_reg(word.dest()) = word.imm16() as u32; + } + Some(Opcode::Lui) => { + *self.mut_reg(word.dest()) = (word.imm16() as u32) << 16 & self.reg(word.dest()); + } + + // Comparison + Some(Opcode::Ieq) => { + *self.mut_reg(word.dest()) = + (self.reg(word.src1()) == self.reg(word.src2())) as u32; + } + Some(Opcode::Ine) => { + *self.mut_reg(word.dest()) = + (self.reg(word.src1()) != self.reg(word.src2())) as u32; + } + Some(Opcode::Ilt) => { + *self.mut_reg(word.dest()) = (self.reg(word.src1()) < self.reg(word.src2())) as u32; + } + Some(Opcode::Ile) => { + *self.mut_reg(word.dest()) = + (self.reg(word.src1()) <= self.reg(word.src2())) as u32; + } + Some(Opcode::Igt) => { + *self.mut_reg(word.dest()) = (self.reg(word.src1()) > self.reg(word.src2())) as u32; + } + Some(Opcode::Ige) => { + *self.mut_reg(word.dest()) = + (self.reg(word.src1()) >= self.reg(word.src2())) as u32; + } + + // Jump + Some(Opcode::Jmp) => *self.mut_reg(Reg::Pcx) = self.reg(word.dest()), + + Some(Opcode::Jez) => { + if self.reg(word.src1()) == 0 { + *self.mut_reg(Reg::Pcx) = self.reg(word.dest()) + } + } + Some(Opcode::Jnz) => { + if self.reg(word.src1()) != 0 { + *self.mut_reg(Reg::Pcx) = self.reg(word.dest()) + } + } + Some(Opcode::Jnc) => todo!(), + Some(Opcode::Jic) => todo!(), + + // Bitwise + Some(Opcode::And) => { + *self.mut_reg(word.dest()) = self.reg(word.src1()) & self.reg(word.src2()); + } + Some(Opcode::Nand) => { + *self.mut_reg(word.dest()) = !(self.reg(word.src1()) & self.reg(word.src2())); + } + Some(Opcode::Or) => { + *self.mut_reg(word.dest()) = self.reg(word.src1()) | self.reg(word.src2()); + } + Some(Opcode::Nor) => { + *self.mut_reg(word.dest()) = !(self.reg(word.src1()) | self.reg(word.src2())); + } + Some(Opcode::Xor) => { + *self.mut_reg(word.dest()) = self.reg(word.src1()) ^ self.reg(word.src2()); + } + Some(Opcode::Xnor) => { + *self.mut_reg(word.dest()) = !(self.reg(word.src1()) ^ self.reg(word.src2())); + } + Some(Opcode::Not) => { + *self.mut_reg(word.dest()) = !self.reg(word.src1()); + } + + // Arithmetic + Some(Opcode::Add) => { + *self.mut_reg(word.dest()) = self.reg(word.src1()) + self.reg(word.src2()); + } + + Some(Opcode::Sub) => { + *self.mut_reg(word.dest()) = self.reg(word.src1()) - self.reg(word.src2()); + } + + Some(Opcode::Shl) => { + *self.mut_reg(word.dest()) = + self.reg(word.src1()) << (self.reg(word.src2()) + word.shamt() as u32); + } + + Some(Opcode::Shr) => { + *self.mut_reg(word.dest()) = + self.reg(word.src1()) >> (self.reg(word.src2()) + word.shamt() as u32); + } + + Some(Opcode::Addi) => { + *self.mut_reg(word.dest()) = self.reg(word.src1()) + word.imm16() as u32; + } + + Some(Opcode::Subi) => { + *self.mut_reg(word.dest()) = self.reg(word.src1()) - word.imm16() as u32; + } + + Some(Opcode::Int) => { + self.interrupt(Interrupt::Generic(word.shamt())); + } + Some(Opcode::Hlt) => self.internal_state.running = false, + None => {} + } + } + + pub fn waiting(&mut self) { + self.internal_state.running = false; + + // Wait for an interrupt or state update to continue. + loop { + // Check for interrupts. + if let Ok(code) = self.interrupts.recv_timeout(Duration::from_millis(100)) { + self.interrupt(Interrupt::Generic(code)); + break; + } + + // UI requested a state update. + if self.shared_state.update_req.load(Ordering::Relaxed) { + self.update(); + } + + // If we've received a request to continue running. + if self.shared_state.running.load(Ordering::Relaxed) { + break; + } + } + + self.internal_state.running = true; + } + + pub fn run(&mut self) { + self.boot(); + + loop { + // Update UI thread (roughly every 512k cycles targeting UPS) + if unlikely(self.internal_state.clock & 0x7FFFF == 0) { + if self.shared_state.update_req.load(Ordering::Relaxed) { + self.update(); + } + } + + // Check for commands or hardware Interrupts every 32k cycles + if self.internal_state.clock % 0x7FFF == 0 { + if self.shared_state.running.load(Ordering::Relaxed) == false { + self.waiting(); + } + + if let Ok(code) = self.interrupts.try_recv() { + self.interrupt(Interrupt::Generic(code)); + } + } + + // let instruction = self.mmu.lookup(self.internal_state.reg(Reg::Pcx)); + let pc = self.reg(Reg::Pcx); + let instruction = self.memory_mut().read_word(pc); + self.execute(InstructionWord(instruction)); + + // always increment clock. + self.internal_state.clock += 1; } } @@ -89,3 +328,24 @@ impl Emulator { todo!() } } + +#[derive(Debug, Clone)] +pub struct ProcessorSnapshot { + pub running: bool, + pub clock: usize, + pub registers: [u32; Self::REG_COUNT], +} + +impl Default for ProcessorSnapshot { + fn default() -> Self { + Self { + running: false, + clock: 0, + registers: [0; Self::REG_COUNT], + } + } +} + +impl ProcessorSnapshot { + const REG_COUNT: usize = 28; +} diff --git a/dsa-emu/src/processor/state.rs b/dsa-emu/src/processor/state.rs index 8b4aabb..c70e0a5 100644 --- a/dsa-emu/src/processor/state.rs +++ b/dsa-emu/src/processor/state.rs @@ -1,37 +1,29 @@ use std::sync::{ Arc, atomic::{AtomicBool, AtomicU8}, + mpsc, }; +use crate::processor::processor::ProcessorSnapshot; use arc_swap::ArcSwap; pub struct SharedState { pub proc: ArcSwap, pub running: AtomicBool, - pub interrupt: AtomicU8, + pub update_req: AtomicBool, + + sender: mpsc::Sender, } impl SharedState { - pub fn new() -> Self { + pub fn new(sender: mpsc::Sender) -> Self { Self { proc: ArcSwap::new(Arc::new(ProcessorSnapshot::default())), running: AtomicBool::new(true), - interrupt: AtomicU8::new(0), - } - } -} - -#[derive(Debug, Clone)] -pub struct ProcessorSnapshot { - pub gp_registers: [u32; 16], -} - -impl Default for ProcessorSnapshot { - fn default() -> Self { - Self { - gp_registers: [0; 16], + sender: sender, + update_req: AtomicBool::new(false), } } }