diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..ab1f416 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/dsa2.iml b/.idea/dsa2.iml new file mode 100644 index 0000000..40aec87 --- /dev/null +++ b/.idea/dsa2.iml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..2190bf5 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/dsa/common/src/register.rs b/dsa/common/src/register.rs index c438b3d..a54238b 100644 --- a/dsa/common/src/register.rs +++ b/dsa/common/src/register.rs @@ -23,38 +23,46 @@ pub enum Register { Rgf = 15, // special purpose - Zero = 16, - Acc = 17, - Spr = 18, - Bpr = 19, - Ret = 20, - Idr = 21, - Mmr = 22, + Acc = 16, + Spr = 17, + Bpr = 18, + Ret = 19, + Idr = 20, + Mmr = 21, // system - read only - Mar = 23, - Mdr = 24, - Sts = 25, - Cir = 26, - Pcx = 27, + Zero = 22, + Sts = 23, + Cir = 24, + Pcx = 25, #[default] - Null = 28, + Null = 26, } impl Register { + /// Total number of registers, not counting Null as it's not a real register. + /// reading or writing to Null is undefined behaviour. + pub const COUNT: u8 = Self::Null as u8; + + /// # Safety + /// the caller must ensure that the index is always within the range 0..COUNT, failing to do so will result in undefined behaviour. #[must_use] #[inline] pub unsafe fn from_u8_unchecked(idx: u8) -> Self { - debug_assert!(idx <= Self::Null as u8); + debug_assert!( + idx < Self::COUNT, + "Tried to access a null register using the unchecked method. undefined behaviour alert!" + ); unsafe { std::mem::transmute(idx) } } #[inline] pub fn from_u8(idx: u8) -> Result { - if idx > 28 { + if idx >= Self::COUNT { return Err(()); } + Ok(unsafe { Self::from_u8_unchecked(idx) }) } } @@ -84,7 +92,6 @@ impl fmt::Display for Register { Self::Rgf => "Rgf", // special purpose - Self::Zero => "Zero", Self::Acc => "Acc", Self::Spr => "Spr", Self::Bpr => "Bpr", @@ -93,8 +100,7 @@ impl fmt::Display for Register { Self::Mmr => "Mmr", // system - read only - Self::Mar => "Mar", - Self::Mdr => "Mdr", + Self::Zero => "Zero", Self::Sts => "Sts", Self::Cir => "Cir", Self::Pcx => "Pcx", @@ -129,7 +135,6 @@ impl std::str::FromStr for Register { "rgf" => Ok(Self::Rgf), // special purpose - "zero" => Ok(Self::Zero), "acc" => Ok(Self::Acc), "spr" => Ok(Self::Spr), "bpr" => Ok(Self::Bpr), @@ -138,8 +143,7 @@ impl std::str::FromStr for Register { "mmr" => Ok(Self::Mmr), // system - read only - "mar" => Ok(Self::Mar), - "mdr" => Ok(Self::Mdr), + "zero" => Ok(Self::Zero), "sts" => Ok(Self::Sts), "cir" => Ok(Self::Cir), "pcx" => Ok(Self::Pcx), diff --git a/dsa/emulator/Cargo.toml b/dsa/emulator/Cargo.toml index 22c947b..0add207 100644 --- a/dsa/emulator/Cargo.toml +++ b/dsa/emulator/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "emulator" -version = "0.1.0" -edition = "2024" +version = "0.3.0" +edition.workspace = true +authors.workspace = true [[bin]] name = "dsa" @@ -22,15 +23,14 @@ ron = "0.12.0" serde = { version = "1.0.228", features = ["derive"] } common = { path = "../common" } +egui = "0.33.3" +eframe = "0.33.3" +egui_tiles = "0.14.1" #assembler = { path = "../assembler" } -[[bench]] -name = "bench_mainstore" -harness = false - [features] -default = ["mainstore-bulkalloc"] +default = ["mainstore-prealloc"] # Memory Bank Features mainstore-bulkalloc = [] # Fastest for Writes diff --git a/dsa/emulator/benches/bench_mainstore.rs b/dsa/emulator/benches/bench_mainstore.rs deleted file mode 100644 index 28df431..0000000 --- a/dsa/emulator/benches/bench_mainstore.rs +++ /dev/null @@ -1,208 +0,0 @@ -use criterion::{ - BenchmarkGroup, BenchmarkId, Criterion, Throughput, black_box, criterion_group, criterion_main, - measurement::WallTime, -}; -use std::time::Duration; - -use dsa::{Page, RandomAccessMemory}; - -type PhysAddr = u32; - -// ── address generators ──────────────────────────────────────────────────────── - -fn sequential_addrs(n: usize, max_addr: u32) -> Vec { - (0..n).map(|i| ((i as u32 * 4) & (max_addr - 1))).collect() -} - -fn random_addrs(n: usize, max_addr: u32) -> Vec { - let mut addrs = Vec::with_capacity(n); - let mut x: u32 = 0xdeadbeef; - for _ in 0..n { - x = x.wrapping_mul(1664525).wrapping_add(1013904223); - addrs.push((x & (max_addr - 1)) & !3); - } - addrs -} - -fn page_stride_addrs(n: usize) -> Vec { - (0..n).map(|i| (i as u32 * 4096) & 0x00FF_FFFF).collect() -} - -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 -} - -// ── runners ─────────────────────────────────────────────────────────────────── - -fn run_read_byte( - group: &mut BenchmarkGroup, - addrs: &[PhysAddr], - implementations: &mut [(&str, Box)], -) { - group.throughput(Throughput::Elements(addrs.len() as u64)); - 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 run_write_byte( - group: &mut BenchmarkGroup, - 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_byte(black_box(addr), black_box(i as u8)); - } - }) - }); - } -} - -fn run_read_word( - group: &mut BenchmarkGroup, - addrs: &[PhysAddr], - implementations: &mut [(&str, Box)], -) { - group.throughput(Throughput::Elements(addrs.len() as u64)); - 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 run_write_word( - group: &mut BenchmarkGroup, - 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)); - 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 run_write_page( - group: &mut BenchmarkGroup, - addrs: &[PhysAddr], - implementations: &mut [(&str, Box)], -) { - let page_data = Page::from([0xABu8; 4096]); - group.throughput(Throughput::Bytes(addrs.len() as u64 * 4096)); - 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)); - } - }) - }); - } -} - -// ── entry point ─────────────────────────────────────────────────────────────── - -fn benchmarks(c: &mut Criterion) { - const N: usize = 4096; - const MAX_ADDR: u32 = 0x00FF_FFFF; - - 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); -criterion_main!(benches); diff --git a/dsa/emulator/font/JetBrainsMonoNerdFontMono_Regular.ttf b/dsa/emulator/font/JetBrainsMonoNerdFontMono_Regular.ttf new file mode 100644 index 0000000..99f2e9b Binary files /dev/null and b/dsa/emulator/font/JetBrainsMonoNerdFontMono_Regular.ttf differ diff --git a/dsa/emulator/src/args.rs b/dsa/emulator/src/args.rs index b59e863..4bec2ab 100644 --- a/dsa/emulator/src/args.rs +++ b/dsa/emulator/src/args.rs @@ -1,9 +1,8 @@ use std::{fs, path::PathBuf}; -use clap::{Parser, ValueEnum}; -use serde::{Deserialize, Serialize}; +use clap::Parser; -use crate::{MemoryMap, RandomAccessMemory, config::IoMapping, io::DeviceId}; +use crate::config::{IoMapping, MemoryMap}; #[derive(Parser, Debug)] #[command(version, about, long_about = None)] @@ -18,9 +17,6 @@ pub struct DsaArgs { #[arg(long = "iomap")] io_map: Option, - - #[arg(value_enum, long = "mem", default_value = "bulk-alloc")] - pub memory_bank: MemoryBank, } impl DsaArgs { @@ -56,18 +52,3 @@ impl DsaArgs { }) } } - -#[derive(Debug, Clone, ValueEnum, Default)] -pub enum MemoryBank { - #[cfg(feature = "mainstore-bulkalloc")] - #[default] - BulkAlloc, - #[cfg(feature = "mainstore-arraymap")] - ArrayMap, - #[cfg(feature = "mainstore-hashmap")] - HashMap, - #[cfg(feature = "mainstore-prealloc")] - PreAlloc, - #[cfg(feature = "mainstore-stackarray")] - StackArray, -} diff --git a/dsa/emulator/src/memory/cache.rs b/dsa/emulator/src/backend/memory/cache.rs similarity index 100% rename from dsa/emulator/src/memory/cache.rs rename to dsa/emulator/src/backend/memory/cache.rs diff --git a/dsa/emulator/src/memory/mmu.rs b/dsa/emulator/src/backend/memory/mmu.rs similarity index 97% rename from dsa/emulator/src/memory/mmu.rs rename to dsa/emulator/src/backend/memory/mmu.rs index 761634d..6afe4af 100644 --- a/dsa/emulator/src/memory/mmu.rs +++ b/dsa/emulator/src/backend/memory/mmu.rs @@ -1,4 +1,4 @@ -use crate::memory::{FaultInfo, PhysAddr, VirtAddr, tlb::TLB}; +use crate::backend::memory::{FaultInfo, PhysAddr, VirtAddr, tlb::TLB}; pub struct MMU { buffer: TLB, diff --git a/dsa/emulator/src/memory/mod.rs b/dsa/emulator/src/backend/memory/mod.rs similarity index 52% rename from dsa/emulator/src/memory/mod.rs rename to dsa/emulator/src/backend/memory/mod.rs index 2bcfbb2..fbc528c 100644 --- a/dsa/emulator/src/memory/mod.rs +++ b/dsa/emulator/src/backend/memory/mod.rs @@ -1,7 +1,3 @@ -use serde::{Deserialize, Serialize}; - -use crate::{RandomAccessMemory, memory::mmu::MMU, processor::processor::Emulator}; - mod cache; pub mod mmu; pub mod ram; diff --git a/dsa/emulator/src/backend/memory/ram.rs b/dsa/emulator/src/backend/memory/ram.rs new file mode 100644 index 0000000..3a7dea9 --- /dev/null +++ b/dsa/emulator/src/backend/memory/ram.rs @@ -0,0 +1,168 @@ +use std::{ + alloc::{Layout, alloc_zeroed, dealloc}, + ops::{Deref, DerefMut}, +}; + +#[cfg(feature = "mainstore-arraymap")] +pub mod arraymap; +#[cfg(feature = "mainstore-arraymap")] +pub use arraymap::ArrayStore; + +#[cfg(feature = "mainstore-hashmap")] +pub mod hashmap; +#[cfg(feature = "mainstore-hashmap")] +pub use hashmap::HashStore; + +#[cfg(feature = "mainstore-stackarray")] +pub mod stackarray; +#[cfg(feature = "mainstore-stackarray")] +pub use stackarray::StackArrayStore; + +#[cfg(feature = "mainstore-bulkalloc")] +pub mod bulkalloc; +#[cfg(feature = "mainstore-bulkalloc")] +pub use bulkalloc::BulkAllocStore; + +// #[cfg(feature = "mainstore-prealloc")] +// pub mod prealloc; +// #[cfg(feature = "mainstore-prealloc")] +// pub use prealloc::PreAllocStore; + +use crate::backend::memory::PhysAddr; + +#[repr(transparent)] +#[derive(Clone)] +pub struct Page([u8; 4096]); + +impl Page { + pub const SIZE: usize = 4096; + pub const COUNT: usize = u32::MAX as usize / Self::SIZE + 1; + + pub const MASK: u32 = 0xFFF; + pub const ZERO: Self = Page::zeroed(); + + pub const fn zeroed() -> Self { + Self([0; 4096]) + } + + pub const fn from(data: [u8; 4096]) -> Self { + Self(data) + } + + pub fn read_word(&self, offset: usize) -> u32 { + u32::from_le_bytes(self.0[offset..offset + 4].try_into().unwrap()) + } + + pub fn write_word(&mut self, offset: usize, value: u32) { + self.0[offset..offset + 4].copy_from_slice(&value.to_le_bytes()); + } + + pub fn paginate(data: &[u8]) -> impl Iterator + '_ { + data.chunks(4096).map(|chunk| { + let mut arr = [0u8; 4096]; + arr[..chunk.len()].copy_from_slice(chunk); + Page(arr) + }) + } +} + +impl Deref for Page { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Page { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl TryFrom> for Page { + type Error = String; + + fn try_from(data: Vec) -> Result { + let arr: [u8; 4096] = data + .try_into() + .map_err(|v: Vec| format!("Expected 4096 bytes, got {}", v.len()))?; + Ok(Page(arr)) + } +} + +#[inline(always)] +const fn idx(addr: PhysAddr) -> (usize, usize) { + ((addr >> 12) as usize, (addr & 0xFFF) as usize) +} + +pub struct MemoryBank { + heap: *mut u8, +} + +unsafe impl Send for MemoryBank {} +unsafe impl Sync for MemoryBank {} +impl MemoryBank { + pub fn new() -> Self { + Self { + heap: unsafe { + // allocate the full 32 bit address range. + alloc_zeroed(Layout::from_size_align(u32::MAX as usize, 4096).unwrap()) + }, + } + } + + pub unsafe fn dealloc(&self) { + unsafe { + dealloc( + self.heap, + Layout::from_size_align(u32::MAX as usize, 4096).unwrap(), + ) + } + } + + pub fn clear(&mut self) { + unsafe { + std::ptr::write_bytes(self.heap, 0, u32::MAX as usize); + } + } + + #[inline(always)] + pub fn read_word(&self, addr: PhysAddr) -> u32 { + unsafe { (self.heap.add(addr as usize) as *const u32).read_volatile() } + } + + #[inline(always)] + pub fn read_byte(&self, addr: PhysAddr) -> u8 { + unsafe { self.heap.add(addr as usize).read_volatile() } + } + + #[inline(always)] + pub fn read_page(&self, addr: PhysAddr) -> &Page { + debug_assert_eq!(addr % 4096, 0); + unsafe { (self.heap.add(addr as usize) as *const Page).as_ref_unchecked() } + } + + #[inline(always)] + pub fn write_byte(&self, addr: PhysAddr, value: u8) { + unsafe { self.heap.add(addr as usize).write_volatile(value) } + } + + #[inline(always)] + pub fn write_word(&self, addr: PhysAddr, value: u32) { + unsafe { (self.heap.add(addr as usize) as *mut u32).write_volatile(value) }; + } + + #[inline(always)] + pub fn write_page(&self, addr: PhysAddr, value: &Page) { + unsafe { + (self.heap.add(addr as usize) as *mut Page).copy_from(value, 1); + } + } +} + +impl Clone for MemoryBank { + fn clone(&self) -> Self { + Self { heap: self.heap } + } +} diff --git a/dsa/emulator/src/memory/tlb.rs b/dsa/emulator/src/backend/memory/tlb.rs similarity index 96% rename from dsa/emulator/src/memory/tlb.rs rename to dsa/emulator/src/backend/memory/tlb.rs index 2a04745..f6d6da4 100644 --- a/dsa/emulator/src/memory/tlb.rs +++ b/dsa/emulator/src/backend/memory/tlb.rs @@ -1,4 +1,4 @@ -use crate::memory::{PhysAddr, VirtAddr}; +use crate::backend::memory::{PhysAddr, VirtAddr}; const TLB_SIZE: usize = 256; const TLB_MASK: u32 = (TLB_SIZE - 1) as u32; diff --git a/dsa/emulator/src/backend/mod.rs b/dsa/emulator/src/backend/mod.rs new file mode 100644 index 0000000..af5b873 --- /dev/null +++ b/dsa/emulator/src/backend/mod.rs @@ -0,0 +1,2 @@ +pub mod memory; +pub mod processor; diff --git a/dsa/emulator/src/processor/interrupts.rs b/dsa/emulator/src/backend/processor/interrupts.rs similarity index 100% rename from dsa/emulator/src/processor/interrupts.rs rename to dsa/emulator/src/backend/processor/interrupts.rs diff --git a/dsa/emulator/src/processor/mod.rs b/dsa/emulator/src/backend/processor/mod.rs similarity index 100% rename from dsa/emulator/src/processor/mod.rs rename to dsa/emulator/src/backend/processor/mod.rs diff --git a/dsa/emulator/src/processor/processor.rs b/dsa/emulator/src/backend/processor/processor.rs similarity index 71% rename from dsa/emulator/src/processor/processor.rs rename to dsa/emulator/src/backend/processor/processor.rs index d3c278c..36d225f 100644 --- a/dsa/emulator/src/processor/processor.rs +++ b/dsa/emulator/src/backend/processor/processor.rs @@ -16,14 +16,12 @@ use common::{ }; use crate::{ - Page, + MemoryBank, Page, SharedState, + backend::{memory::mmu::MMU, processor::interrupts::Interrupt}, config::{IoMapping, MemoryMap, RegionType}, - io::{IoAccess, IoDevice, MappedDevice}, - memory::{mmu::MMU, ram::RandomAccessMemory}, - processor::{interrupts::Interrupt, state::SharedState}, }; -pub struct Emulator { +pub struct Emulator { internal_state: ProcessorSnapshot, shared_state: Arc, @@ -33,10 +31,9 @@ pub struct Emulator { // memory mmu: MMU, - mainstore: Mem, + mainstore: MemoryBank, // IO - mmio: Vec, mmio_region: Option<(u32, u32)>, // start end end of segment // optionals - not necessarily set on boot. @@ -44,12 +41,13 @@ pub struct Emulator { // Config params __mmap_configured: bool, - __io_configured: bool, + + time: Instant, } -unsafe impl Send for Emulator {} -impl Emulator { - pub fn new(mem: Mem) -> Self { +unsafe impl Send for Emulator {} +impl Emulator { + pub fn new() -> Self { let (sender, receiver) = mpsc::channel::(); Self { @@ -61,24 +59,16 @@ impl Emulator { memory_map: None, mmu: MMU::new(), - mainstore: mem, + mainstore: MemoryBank::new(), // mmio - mmio: Vec::new(), mmio_region: None, // Config params - __io_configured: false, __mmap_configured: false, - } - } - pub fn with_device(mut self, device: impl IoDevice + 'static) -> Self { - self.mmio.push(MappedDevice { - base: 0, - device: Box::new(device), - }); - self + time: Instant::now(), + } } pub fn state_handle(&self) -> Arc { @@ -91,25 +81,21 @@ impl Emulator { } #[inline] - pub fn memory_mut(&mut self) -> &mut impl RandomAccessMemory { + pub fn memory_mut(&mut self) -> &mut MemoryBank { &mut self.mainstore } #[must_use] #[inline] pub fn reg(&self, reg: Register) -> u32 { - if reg as u8 == Register::Zero as u8 { - return 0; - } - debug_assert!((reg as usize) < ProcessorSnapshot::REG_COUNT); + debug_assert!((reg as u8) < Register::COUNT); unsafe { *self.internal_state.registers.get_unchecked(reg as usize) } } #[inline] pub fn mut_reg(&mut self, reg: Register) -> &mut u32 { - debug_assert!((reg as usize) < ProcessorSnapshot::REG_COUNT); - + debug_assert!((reg as u8) < Register::COUNT); unsafe { self.internal_state .registers @@ -131,23 +117,6 @@ impl Emulator { self } - pub fn apply_io_map(mut self, map: Vec) -> Result { - if !self.__mmap_configured { - return Err("You must map memory before applying I/O mappings".to_string()); - } - - for entry in map { - if let Some(idx) = self.mmio.iter().position(|d| d.device.id() == entry.device) { - self.mmio[idx].base = entry.base; - } else { - eprintln!("WARN: no device registgered for {:?}", entry.device); - } - } - - self.__io_configured = true; - Ok(self) - } - #[cold] fn update(&mut self) { self.shared_state @@ -157,7 +126,7 @@ impl Emulator { #[cold] fn boot(&mut self) -> Result<(), String> { - if !(self.__io_configured && self.__mmap_configured) { + if !(self.__mmap_configured) { return Err("Emulator not configured".to_string()); } @@ -165,48 +134,89 @@ impl Emulator { MemoryMap::identity_map(&mut self.mmu, map.regions.get(0).unwrap()); } - self.internal_state.running = true; + self.internal_state.halted = false; Ok(()) } #[cold] fn shutdown(&mut self) { - self.internal_state.running = false; + self.internal_state.halted = true; + } + + pub fn halt(&mut self) { + self.internal_state.halted = true; + + // wait until we're un-halted. + while self.internal_state.halted { + self.wait_for_instructions(); + } + } + + pub fn check_update(&mut self) { + // UI requested a state update. + if self.shared_state.update_req.load(Ordering::Relaxed) { + self.update(); + } + + if self.shared_state.reseting.load(Ordering::Relaxed) { + self.internal_state.registers = [0; Register::COUNT as usize]; + self.internal_state.clock = 0; + + // sit in a wait loop while resetting. + while self.shared_state.reseting.load(Ordering::Relaxed) { + thread::sleep(Duration::from_millis(100)); + } + } } #[cold] - pub fn idle_wait(&mut self) { - self.internal_state.running = false; + pub fn wait_for_instructions(&mut self) { + { + // record time for previous execution. + self.internal_state.time = self.time.elapsed(); + self.update(); + self.time = Instant::now(); + } + + // if paused from the UI thread, just wait. + while self.shared_state.paused.load(Ordering::Relaxed) { + thread::sleep(Duration::from_millis(10)); + // UI is making changes / requesting updates. + self.check_update(); + } + + // if the cpu didn't halt on it's own then it's ready to run again. + if !self.internal_state.halted { + return; + } - // 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.internal_state.halted = false; self.interrupt(Interrupt::Software(code)); break; } - // // If we've received a request to continue running. DEPRECATED (we can run through interrupts) - // if self.shared_state.running.load(Ordering::Relaxed) { - // panic!("3"); - // break; - // } - - // UI requested a state update. - if self.shared_state.update_req.load(Ordering::Relaxed) { - self.update(); - } + // UI is resetting the emulator + self.check_update(); } - - self.internal_state.running = true; } pub fn run(&mut self) -> Result<(), String> { - self.boot()?; + // wait for UI to initialise emulator. + if self.shared_state.paused.load(Ordering::Relaxed) { + self.wait_for_instructions(); + } - let mut time = Instant::now(); + self.boot()?; + self.time = Instant::now(); 'emu: loop { + // so that it always returns zero. this is more performance friendly than checking + // for a zero register each access as it's branchless + *self.mut_reg(Register::Zero) = 0; + // IMPORTANT // do not change anything about this loop. it's fully optimised afaik. // Check for commands or hardware Interrupts every 32k cycles @@ -218,8 +228,8 @@ impl Emulator { } } - if unlikely(!self.shared_state.running.load(Ordering::Relaxed)) { - self.idle_wait(); + if unlikely(self.shared_state.paused.load(Ordering::Relaxed)) { + self.wait_for_instructions(); } match self.interrupts.try_recv() { @@ -229,24 +239,6 @@ impl Emulator { } } - // if we got a halt instruction, wait - if unlikely(!self.internal_state.running) { - let mips = (self.internal_state.clock as u128 / time.elapsed().as_micros()); - println!( - "TIME TAKEN: {:?}, clock: {}, {}MIPS", - time.elapsed(), - self.internal_state.clock, - mips - ); - time = Instant::now(); - - // temporary while i figure out a better solution - // exit when we hit halt (not useful for a real os ofc) - break 'emu; - - self.idle_wait(); - } - let pc = self.reg(Register::Pcx); let instruction = self.mem_read_word(pc); @@ -261,23 +253,21 @@ impl Emulator { thread::sleep(Duration::from_micros(10)); } - self.mut_reg(Register::Pcx).add_assign(4); - - self.execute(Instruction(instruction)); - - // Check if executing the interrupt caused a fault. + // Check if the previous instruction caused a fault. if let Some(fault) = self.pending_fault { - self.pending_fault = None; println!("WARN fault: {:?}", fault); + self.pending_fault = None; self.interrupt(fault); } + self.mut_reg(Register::Pcx).add_assign(4); + self.execute(Instruction(instruction)); + // always increment clock. self.internal_state.clock += 1; } self.shutdown(); - Ok(()) } @@ -489,7 +479,7 @@ impl Emulator { Some(Opcode::Int) => { self.interrupt(Interrupt::Software(word.imm16() as u8)); } - Some(Opcode::Hlt) => self.internal_state.running = false, + Some(Opcode::Hlt) => self.halt(), Some(Opcode::IRet) => todo!(), None => {} } @@ -510,147 +500,57 @@ impl Emulator { #[inline] fn mem_read_byte(&mut self, addr: u32) -> u8 { - if unlikely(self.is_mmio(addr)) { - return self.io_read_byte(addr); - } self.mainstore.read_byte(addr) } #[inline] fn mem_write_byte(&mut self, addr: u32, val: u8) { - if unlikely(self.is_mmio(addr)) { - self.io_write_byte(addr, val); - return; + if addr == 0x40000 { + eprintln!("emulator wrote 0x{:08x} to uart", val); } self.mainstore.write_byte(addr, val); } #[inline] fn mem_read_word(&mut self, addr: u32) -> u32 { - if unlikely(self.is_mmio(addr)) { - return self.io_read_word(addr); - } self.mainstore.read_word(addr) } #[inline] fn mem_write_word(&mut self, addr: u32, val: u32) { - if unlikely(self.is_mmio(addr)) { - self.io_write_word(addr, val); - return; - } self.mainstore.write_word(addr, val); } #[inline] fn mem_read_page(&mut self, addr: u32) -> &Page { - if unlikely(self.is_mmio(addr)) { - // pages spanning MMIO don't really make sense - // treat as fault - self.pending_fault = Some(Interrupt::ProtectionFault); - return &Page::ZERO; - } self.mainstore.read_page(addr) } #[inline] fn mem_write_page(&mut self, addr: u32, val: &Page) { - if unlikely(self.is_mmio(addr)) { - self.pending_fault = Some(Interrupt::ProtectionFault); - return; - } self.mainstore.write_page(addr, val); } - // single MMIO check reused by all of the above - #[inline] - fn is_mmio(&self, addr: u32) -> bool { - self.mmio_region - .map(|(base, end)| addr >= base && addr < end) - .unwrap_or(false) - } - fn fault(&mut self, interrupt: Interrupt) { self.pending_fault = Some(interrupt); } - - #[inline] - fn get_device(&mut self, addr: u32) -> Option<(&mut Box, u32)> { - // Find the device that covers the address. - self.mmio - .iter_mut() - .find(|d| d.base <= addr && addr < d.base + d.device.size()) - .map(|d| (&mut d.device, addr - d.base)) - } - // --- cold IO paths --- - - #[cold] - fn io_read_byte(&mut self, addr: u32) -> u8 { - if let Some((device, offset)) = self.get_device(addr) { - device.read_byte(offset).unwrap_or_else(|fault| { - self.fault(fault); - 0 - }) - } else { - self.fault(Interrupt::UnmappedIo); - 0 - } - } - - #[cold] - fn io_read_word(&mut self, addr: u32) -> u32 { - if let Some((device, offset)) = self.get_device(addr) { - device.read_word(offset).unwrap_or_else(|fault| { - self.fault(fault); - 0 - }) - } else { - self.fault(Interrupt::UnmappedIo); - - 0 - } - } - - #[cold] - fn io_write_byte(&mut self, addr: u32, val: u8) { - if let Some((dev, offset)) = self.get_device(addr) { - dev.write_byte(offset, val).unwrap_or_else(|fault| { - self.fault(fault); - }); - } else { - self.fault(Interrupt::UnmappedIo); - } - } - - #[cold] - fn io_write_word(&mut self, addr: u32, val: u32) { - if let Some((dev, offset)) = self.get_device(addr) { - dev.write_word(offset, val).unwrap_or_else(|fault| { - self.fault(fault); - }); - } else { - self.fault(Interrupt::UnmappedIo); - } - } } #[derive(Debug, Clone)] pub struct ProcessorSnapshot { - pub running: bool, + pub halted: bool, pub clock: usize, - pub registers: [u32; Self::REG_COUNT], + pub time: Duration, + pub registers: [u32; Register::COUNT as usize], } impl Default for ProcessorSnapshot { fn default() -> Self { Self { - running: false, + halted: false, clock: 0, - registers: [0; Self::REG_COUNT], + time: Duration::ZERO, + registers: [0; Register::COUNT as usize], } } } - -impl ProcessorSnapshot { - const REG_COUNT: usize = 28; -} diff --git a/dsa/emulator/src/processor/state.rs b/dsa/emulator/src/backend/processor/state.rs similarity index 58% rename from dsa/emulator/src/processor/state.rs rename to dsa/emulator/src/backend/processor/state.rs index 506c7e1..f0ce121 100644 --- a/dsa/emulator/src/processor/state.rs +++ b/dsa/emulator/src/backend/processor/state.rs @@ -1,24 +1,27 @@ use std::sync::{Arc, atomic::AtomicBool, mpsc}; -use crate::processor::processor::ProcessorSnapshot; use arc_swap::ArcSwap; +use crate::backend::processor::processor::ProcessorSnapshot; + pub struct SharedState { pub proc: ArcSwap, - pub running: AtomicBool, + pub reseting: AtomicBool, + pub paused: AtomicBool, pub update_req: AtomicBool, - sender: mpsc::Sender, + interrupt_queue: mpsc::Sender, } impl SharedState { pub fn new(sender: mpsc::Sender) -> Self { Self { + reseting: AtomicBool::new(false), proc: ArcSwap::new(Arc::new(ProcessorSnapshot::default())), - running: AtomicBool::new(true), - sender: sender, + paused: AtomicBool::new(true), + interrupt_queue: sender, update_req: AtomicBool::new(false), } } diff --git a/dsa/emulator/src/bin/bench_emu.rs b/dsa/emulator/src/bin/bench_emu.rs deleted file mode 100644 index fd2e29e..0000000 --- a/dsa/emulator/src/bin/bench_emu.rs +++ /dev/null @@ -1,330 +0,0 @@ -use common::instructions::Instruction; -use dsa::{BulkAllocStore, Emulator, Page, RandomAccessMemory}; -/// DSA Emulator Benchmark -/// -/// Place this in `dsa/src/bin/bench.rs` and run with: -/// cargo run --bin bench --release -/// -/// Three workloads are tested: -/// 1. ALU-heavy — tight arithmetic loop, no memory, no branches -/// 2. Memory-heavy — repeated load/store to a working set of addresses -/// 3. Branch-heavy — the bf.dsa dispatch pattern (ieq + jnz chains) -/// -/// Each workload is a hand-assembled flat binary loaded directly into -/// the emulator, bypassing all the args/display/IO plumbing. -use std::{ - thread, - time::{Duration, Instant}, - u16, -}; - -// --------------------------------------------------------------------------- -// Minimal no-op memory map + IO so the emulator boots without a config file. -// --------------------------------------------------------------------------- - -fn make_emulator() -> Emulator { - use dsa::{ - config::{MemoryMap, Region, RegionType}, - io::display::DisplayDevice, - }; - - let mmap = MemoryMap { - table_addr: 0x40000, - regions: vec![ - Region { - base: 0x0000_0000, - size: 0x0010_0000, // 1 MiB RAM - region_type: RegionType::Usable, - identity_map_on_boot: true, - }, - Region { - base: 0x0020_0000, - size: 0x0002_0000, // 128 KiB MMIO (display) - region_type: RegionType::MMIO, - identity_map_on_boot: false, - }, - ], - }; - - let iomap = vec![dsa::config::IoMapping { - device: dsa::io::DeviceId::Display, - base: 0x0020_0000, - size: 0x0000_07D0, - }]; - - let (display, _handle) = DisplayDevice::new(80, 25); - Emulator::new(BulkAllocStore::new()) - .with_device(display) - .apply_memory_map(mmap) - .apply_io_map(iomap) - .unwrap() -} - -// --------------------------------------------------------------------------- -// Instruction encoding helpers (little-endian words) -// --------------------------------------------------------------------------- - -/// Encode an I-type instruction: [op:6][src:5][dst:5][imm:16] -fn enc_i(op: u8, src: u8, dst: u8, imm: u16) -> u32 { - ((op as u32 & 0x3F) << 26) - | ((src as u32 & 0x1F) << 21) - | ((dst as u32 & 0x1F) << 16) - | imm as u32 -} - -/// Encode an R-type instruction: [op:6][r1:5][r2:5][r3:5][r4:5][u6:6] -fn enc_r(op: u8, r1: u8, r2: u8, r3: u8, r4: u8, u6: u8) -> u32 { - ((op as u32 & 0x3F) << 26) - | ((r1 as u32 & 0x1F) << 21) - | ((r2 as u32 & 0x1F) << 16) - | ((r3 as u32 & 0x1F) << 11) - | ((r4 as u32 & 0x1F) << 6) - | (u6 as u32 & 0x3F) -} - -// Register indices — must match your Register enum discriminants -const RG0: u8 = 0; -const RG1: u8 = 1; -const RG2: u8 = 2; -const RG3: u8 = 3; -const RG4: u8 = 4; -const RG5: u8 = 5; -const ZERO: u8 = 16; - -// Opcodes — must match your Opcode enum discriminants. -// Fill these in from your ISA.md / encode.rs. -const OP_NOP: u8 = 0x0D; // adjust to match your actual opcode values -const OP_HLT: u8 = 0x2B; -const OP_ADD: u8 = 0x22; -const OP_ADDI: u8 = 0x23; -const OP_SUBI: u8 = 0x24; -const OP_LDW: u8 = 0x07; -const OP_STW: u8 = 0x0A; -const OP_LLI: u8 = 0x0B; -const OP_LUI: u8 = 0x0C; -const OP_IEQ: u8 = 0x0E; -const OP_JNZ: u8 = 0x15; -const OP_JMP: u8 = 0x12; - -// --------------------------------------------------------------------------- -// Workload 1: ALU-heavy -// -// Counts down from 10_000_000 using subi, jumps back to top. -// Pure register arithmetic, no memory accesses after init. -// Instruction mix: lli, subi, ieq, jnz, hlt -// -// Layout (each word at address = index * 4): -// 0: lli rg0, 0 (upper half of count) [lui follows] -// 4: lui rg0, -// 8: loop: subi rg0, 1, rg0 -// 12: ieq rg0, zero, rg1 -// 16: jnz rg1, zero, 8 [jump to loop] -// 20: hlt -// --------------------------------------------------------------------------- -fn alu_workload() -> Vec { - let count: u32 = 10_000_000; - let lo = (count & 0xFFFF) as u16; - let hi = ((count >> 16) & 0xFFFF) as u16; - - let words: &[u32] = &[ - enc_i(OP_LLI, 0, RG0, lo), // 0x00: lli rg0, lo(count) - enc_i(OP_LUI, 0, RG0, hi), // 0x04: lui rg0, hi(count) - // loop @ 0x08: - enc_i(OP_SUBI, RG0, RG0, 1), // 0x08: subi rg0, rg0, 1 - enc_r(OP_IEQ, RG0, ZERO, RG1, 0, 0), // 0x0C: ieq rg0, zero, rg1 - enc_i(OP_JNZ, RG1, ZERO, 0x0008), // 0x10: jnz rg1, zero, 0x08 <- BUG: jnz jumps when rg1!=0 - // rg1=1 when rg0==0, so we want jez here to keep looping - // fix: use jez (jump when rg1==0, i.e. rg0!=0) - enc_i(OP_HLT, 0, 0, 0), // 0x14: hlt - ]; - // Note: replace OP_JNZ above with your JEZ opcode so the loop continues - // while rg0 != 0. JNZ exits when rg1 != 0, i.e. when rg0 == 0 (done). - // The ieq sets rg1=1 only when rg0==0, so jnz rg1 exits the loop correctly. - // Actually this IS correct: loop while ieq returns 0 (rg0 != 0), - // exit when ieq returns 1 (rg0 == 0). jnz rg1 = jump when rg1 != 0 = jump when done. - // We want to jump BACK while not done, so we need jez: - // jez rg1, zero, 0x08 = jump back when rg1==0 (rg0 != 0, not done yet) - // Swap OP_JNZ for your JEZ opcode. - words_to_bytes(words) -} - -// --------------------------------------------------------------------------- -// Workload 2: Memory-heavy -// -// Repeatedly writes and reads a 64-word working set. -// Tests page table lookup performance under repeated memory access. -// -// rg0 = base address (0x1000, well within RAM, page-aligned) -// rg1 = loop counter (1000 outer iterations) -// rg2 = inner counter (64 words per pass) -// rg3 = scratch for store value -// --------------------------------------------------------------------------- -fn memory_workload() -> Vec { - let base: u32 = 0x1000; - let outer: u16 = 1000; - let inner: u16 = 64; - - let words: &[u32] = &[ - // init - enc_i(OP_LLI, 0, RG0, (base & 0xFFFF) as u16), // rg0 = base - enc_i(OP_LLI, 0, RG1, outer), // rg1 = outer count - // outer_loop @ 0x08: - enc_i(OP_LLI, 0, RG2, inner), // rg2 = inner count - enc_i(OP_LLI, 0, RG3, 0xABCD), // rg3 = value to write - // inner_loop @ 0x10: - enc_i(OP_STW, RG3, RG0, 0), // stw rg3, rg0, 0 - enc_i(OP_LDW, RG0, RG3, 0), // ldw rg0, rg3, 0 (read back) - enc_i(OP_ADDI, RG0, RG0, 4), // rg0 += 4 - enc_i(OP_SUBI, RG2, RG2, 1), // rg2 -= 1 - enc_r(OP_IEQ, RG2, ZERO, RG4, 0, 0), // ieq rg2, zero, rg4 - enc_i(OP_JNZ, RG4, ZERO, 0x0010), // jez rg4 -> inner_loop (swap opcode) - // restore base, dec outer - enc_i(OP_LLI, 0, RG0, (base & 0xFFFF) as u16), - enc_i(OP_SUBI, RG1, RG1, 1), - enc_r(OP_IEQ, RG1, ZERO, RG4, 0, 0), - enc_i(OP_JNZ, RG4, ZERO, 0x0008), // jez rg4 -> outer_loop (swap opcode) - enc_i(OP_HLT, 0, 0, 0), - ]; - words_to_bytes(words) -} - -// --------------------------------------------------------------------------- -// Workload 3: Branch-heavy -// -// Mimics the bf.dsa dispatch pattern: compare a value against 8 constants, -// branch on each. Worst case for branch predictor. -// rg0 = current "instruction" (cycles through 8 values) -// rg8-rgf = the 8 bf opcode constants -// --------------------------------------------------------------------------- -fn branch_workload() -> Vec { - // 8 bf opcodes: + - > < . , [ ] - let opcodes: [u16; 8] = [43, 45, 62, 60, 46, 44, 91, 93]; - // We'll cycle rg0 through these values and do the full dispatch chain. - // To keep it self-contained: load opcodes, set outer loop counter, - // inner loop cycles rg0 through all 8 values doing ieq+jnz for each. - let outer: u16 = u16::MAX; - - let mut w: Vec = Vec::new(); - - // Load 8 opcode constants into rg8-rgf (regs 8-15) - for (i, &op) in opcodes.iter().enumerate() { - w.push(enc_i(OP_LLI, 0, 8 + i as u8, op)); - } - // rg1 = outer counter - w.push(enc_i(OP_LLI, 0, RG1, outer)); - - // outer_loop: - let outer_loop_addr = (w.len() * 4) as u16; - - // rg5 = inner counter (8 opcodes) - w.push(enc_i(OP_LLI, 0, RG5, 8)); - // rg2 = pointer into opcode table (we'll use addi to step rg0 through values) - // For simplicity: just set rg0 to each opcode in sequence via lli each iteration - // This is branch-heavy, not arithmetic-heavy, so the lli overhead is fine. - for (i, &op) in opcodes.iter().enumerate() { - let next_offset = ((w.len() + 4) * 4) as u16; // addr after the jnz - w.push(enc_i(OP_LLI, 0, RG0, op)); // rg0 = opcode - w.push(enc_r(OP_IEQ, RG0, 8 + i as u8, RG4, 0, 0)); // ieq rg0, rgX, rg4 - w.push(enc_i(OP_JNZ, RG4, ZERO, next_offset)); // jnz rg4 (jez to skip) - w.push(enc_i(OP_NOP, 0, 0, 0)); // "handler" nop - } - // dec outer, loop - let after_dispatch = (w.len() * 4) as u16; - w.push(enc_i(OP_SUBI, RG1, RG1, 1)); - w.push(enc_r(OP_IEQ, RG1, ZERO, RG4, 0, 0)); - w.push(enc_i(OP_JNZ, RG4, ZERO, outer_loop_addr)); // jez -> outer_loop - w.push(enc_i(OP_HLT, 0, 0, 0)); - - words_to_bytes(&w) -} - -fn words_to_bytes(words: &[u32]) -> Vec { - words.iter().flat_map(|w| w.to_le_bytes()).collect() -} - -// --------------------------------------------------------------------------- -// Runner -// --------------------------------------------------------------------------- - -struct BenchResult { - name: &'static str, - instructions: u64, - elapsed: Duration, - mips: f64, -} - -fn run_bench(name: &'static str, binary: Vec) -> BenchResult { - let mut emu = make_emulator(); - let handle = emu.state_handle(); - - let pages: Vec = Page::paginate(&binary).collect(); - for (i, page) in pages.iter().enumerate() { - emu.memory_mut().write_page(i as u32 * 4096, page); - } - - let start = Instant::now(); - // run() blocks until HLT - emu.run().expect("emulator run failed"); - let elapsed = start.elapsed(); - - // read clock from emulator — we need to expose it. - // For now, estimate from elapsed + target MIPS. - // TODO: expose emulator.clock() -> u64 and use that directly. - let instructions = handle.proc.load().clock as u64; - let mips = instructions as f64 / elapsed.as_micros() as f64; - - BenchResult { - name, - instructions, - elapsed, - mips, - } -} - -fn main() { - println!("DSA Emulator Benchmark"); - println!("======================"); - println!(); - - // NOTE: you need to expose `pub fn clock(&self) -> usize` on Emulator - // and ensure `run()` returns normally on HLT without blocking in idle_wait. - // The idle_wait loop currently spins forever after HLT — for benchmarking, - // you want run() to return when the program halts. Add a flag or check: - // if !self.internal_state.running { break 'emu; } - // after the HLT handler sets running=false. - - let workloads: &[(&'static str, fn() -> Vec)] = &[ - ("ALU-heavy (10M countdown)", alu_workload), - ("Memory-heavy (64K rw ops)", memory_workload), - ("Branch-heavy (bf dispatch)", branch_workload), - ]; - - let mut results = Vec::new(); - for &(name, build) in workloads { - print!("Running {}... ", name); - let binary = build(); - let result = run_bench(name, binary); - println!("done ({:.1}ms)", result.elapsed.as_secs_f64() * 1000.0); - results.push(result); - } - - println!(); - println!( - "{:<35} {:>12} {:>12} {:>10}", - "Workload", "Instructions", "Time", "MIPS" - ); - println!("{}", "-".repeat(72)); - for r in &results { - println!( - "{:<35} {:>12} {:>10.1}ms {:>10.1}", - r.name, - r.instructions, - r.elapsed.as_secs_f64() * 1000.0, - r.mips, - ); - } - - let avg_mips: f64 = results.iter().map(|r| r.mips).sum::() / results.len() as f64; - println!("{}", "-".repeat(72)); - println!("{:<35} {:>35.1} MIPS avg", "Overall", avg_mips); -} diff --git a/dsa/emulator/src/config/mod.rs b/dsa/emulator/src/config/mod.rs index 56fabb5..76f440f 100644 --- a/dsa/emulator/src/config/mod.rs +++ b/dsa/emulator/src/config/mod.rs @@ -1,11 +1,13 @@ use serde::{Deserialize, Serialize}; use crate::{ - Emulator, RandomAccessMemory, - io::DeviceId, - memory::{PhysAddr, mmu::MMU}, + Emulator, + backend::memory::{PhysAddr, mmu::MMU}, + // io::DeviceId, }; +pub const VERSION: &str = env!("CARGO_PKG_VERSION"); + #[repr(u32)] #[derive(Serialize, Deserialize, Clone, Copy, Debug)] pub enum RegionType { @@ -36,7 +38,7 @@ pub struct MemoryMap { #[derive(Serialize, Deserialize)] pub struct IoMapping { - pub device: DeviceId, + // pub device: DeviceId, pub base: u32, pub size: u32, } @@ -51,7 +53,7 @@ impl MemoryMap { } } - pub fn apply(&self, cpu: &mut Emulator) { + pub fn apply(&self, cpu: &mut Emulator) { for reg in &self.regions { if reg.identity_map_on_boot { Self::identity_map(cpu.mmu_mut(), reg); diff --git a/dsa/emulator/src/frontend/components.rs b/dsa/emulator/src/frontend/components.rs new file mode 100644 index 0000000..4476c75 --- /dev/null +++ b/dsa/emulator/src/frontend/components.rs @@ -0,0 +1,15 @@ +pub mod controller; +pub mod display; +pub mod framebuffer; +pub mod registers; +pub mod serial; + +pub trait Component { + /// The tab label shown in the tile header. + fn title(&self) -> &str; + + fn visible(&mut self) -> &mut bool; + + /// Draw the component's UI inside the provided `Ui`. + fn ui(&mut self, ui: &mut egui::Ui, ctx: &egui::Context); +} diff --git a/dsa/emulator/src/frontend/components/controller.rs b/dsa/emulator/src/frontend/components/controller.rs new file mode 100644 index 0000000..ec8ef3c --- /dev/null +++ b/dsa/emulator/src/frontend/components/controller.rs @@ -0,0 +1,104 @@ +use std::sync::{Arc, atomic::Ordering}; + +use super::Component; +use crate::{MemoryBank, SharedState}; + +use common::prelude::Register; + +pub struct Controller { + state: Arc, + mem: MemoryBank, + visible: bool, +} + +impl Controller { + pub fn new(state: Arc, mem: MemoryBank) -> Self { + Self { + state, + mem, + visible: false, + } + } +} + +impl Component for Controller { + fn title(&self) -> &str { + "Control Panel" + } + + fn visible(&mut self) -> &mut bool { + &mut self.visible + } + + fn ui(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) { + let state = self.state.proc.load(); + + let paused = self.state.paused.load(Ordering::Relaxed); + + ui.horizontal(|ui| { + // Pause / Run + if ui + .button(match paused { + false => "Pause", + true => "Resume", + }) + .clicked() + { + self.state.paused.store(!paused, Ordering::Relaxed); + } + + // Step + // if ui.button("Step").clicked() { + // state + // .cmd_sender + // .send(Command::Step(self.step_amount)) + // .unwrap_or_else(|_| { + // state.error_log.push("Failed to send command".to_string()); + // }); + // } + + // Resets the emulator and all attached devices + if ui.button("Reset All").clicked() { + self.state.reseting.store(true, Ordering::Relaxed); + self.mem.clear(); + self.state.reseting.store(false, Ordering::Relaxed); + } + + ui.separator(); + + // if ui + // .text_edit_singleline(&mut self.step_amount_input) + // .changed() + // { + // self.step_amount = if let Ok(amount) = self.step_amount_input.parse() { + // amount + // } else { + // state + // .error_log + // .push("Unable to parse step amount".to_string()); + // 1 + // } + // } + + // Status info + ui.label(format!( + "Status: {}", + match (state.halted, paused) { + (true, false) => "Halted", + (_, false) => "Running", + (_, true) => "Paused", + } + )); + + let pcx = state.registers[Register::Pcx as usize]; + let clock = state.clock; + let time = state.time.as_micros(); + let mips = state.clock as u128 / time; + + ui.label(format!("Clock: {clock}")); + ui.label(format!("Pcx: 0x{pcx:08X}")); + ui.label(format!("Elapsed: {}micros", time)); + ui.label(format!("Freq: {}MHz", mips)); + }); + } +} diff --git a/dsa/emulator/src/frontend/components/display.rs b/dsa/emulator/src/frontend/components/display.rs new file mode 100644 index 0000000..5d131d5 --- /dev/null +++ b/dsa/emulator/src/frontend/components/display.rs @@ -0,0 +1,65 @@ +use egui::{Color32, FontId, Vec2}; + +use super::Component; +pub use crate::io::display::Display; + +impl Component for Display { + fn title(&self) -> &str { + "Display" + } + + fn visible(&mut self) -> &mut bool { + self.visible() + } + + fn ui(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) { + let display_w = self.width(); + let display_h = self.height(); + let data = self.read(); + + let font_id = FontId::monospace(12.0); + + let char_width = ui.fonts_mut(|f| f.glyph_width(&font_id, 'W')); + let line_height = ui.fonts_mut(|f| f.row_height(&font_id)); + + #[expect(clippy::cast_precision_loss)] + let display_size = Vec2::new( + char_width * display_w as f32, + line_height * display_h as f32, + ); + + let (rect, _response) = ui.allocate_exact_size(display_size, egui::Sense::all()); + + // ui.painter().rect_filled(rect, 0.0, Color32::BLACK); + + // Draw text + for y in 0..display_h { + let mut row_text = String::with_capacity(display_w); + for x in 0..display_w { + let index = y * display_w + x; + if index < data.len() { + let byte = data[index]; + let ch = if (32..=126).contains(&byte) { + byte as char + } else { + ' ' + }; + row_text.push(ch); + } else { + row_text.push(' '); + } + } + + #[expect(clippy::cast_precision_loss)] + let text_pos = rect.min + Vec2::new(0.0, y as f32 * line_height); + + ui.painter().text( + text_pos, + egui::Align2::LEFT_TOP, + row_text, + font_id.clone(), + Color32::WHITE, + ); + } + } +} diff --git a/dsa/emulator/src/frontend/components/framebuffer.rs b/dsa/emulator/src/frontend/components/framebuffer.rs new file mode 100644 index 0000000..5d9a91a --- /dev/null +++ b/dsa/emulator/src/frontend/components/framebuffer.rs @@ -0,0 +1,137 @@ +use egui::Vec2; + +use crate::{MemoryBank, Page, backend::memory::PhysAddr}; + +use super::Component; + +pub struct FrameBuffer { + width: usize, + height: usize, + addr: PhysAddr, + mem: MemoryBank, + pub buffer: Vec, // RGBA pixels + visible: bool, + + texture: Option, +} + +impl FrameBuffer { + pub fn new(width: u32, height: u32, addr: PhysAddr, mem: MemoryBank) -> Self { + Self { + width: width as usize, + height: height as usize, + addr, + mem, + buffer: vec![0u32; width as usize * height as usize], + visible: true, + + texture: None, + } + } + + pub fn width(&self) -> usize { + self.width + } + pub fn height(&self) -> usize { + self.height + } + pub fn visible(&mut self) -> &mut bool { + &mut self.visible + } + + fn internal_read(&self) -> Vec { + let byte_size = self.width * self.height * 4; + let page_count = byte_size.div_ceil(Page::SIZE); + + (0..page_count) + .map(|i| { + let page = self.mem.read_page(self.addr + i as u32 * 4096); + page.as_slice().to_owned() + }) + .flatten() + .take(byte_size) + .collect::>() + .chunks_exact(4) + .map(|chunk| u32::from_le_bytes(chunk.try_into().unwrap())) + .collect() + } + + pub fn changed(&mut self) -> bool { + let temp = self.internal_read(); + if temp != self.buffer { + self.buffer = temp; + return true; + } + false + } + + pub fn read(&self) -> &[u32] { + &self.buffer + } + + /// Convert buffer to RGBA bytes for use with egui/wgpu textures + pub fn as_rgba_bytes(&self) -> Vec { + self.buffer + .iter() + .flat_map(|&pixel| pixel.to_le_bytes()) + .collect() + } +} + +impl Component for FrameBuffer { + fn title(&self) -> &str { + "Framebuffer" + } + + fn visible(&mut self) -> &mut bool { + &mut self.visible + } + + fn ui(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) { + let changed = self.changed(); + + // get or create the texture + let texture = self.texture.get_or_insert_with(|| { + ctx.load_texture( + "framebuffer", + egui::ColorImage { + size: [self.width, self.height], + source_size: Vec2::from([self.width as f32, self.height as f32]), + pixels: vec![egui::Color32::BLACK; self.width * self.height], + }, + egui::TextureOptions::NEAREST, + ) + }); + + if changed { + let pixels: Vec = self + .buffer + .iter() + .map(|&p| { + let bytes = p.to_le_bytes(); + egui::Color32::from_rgba_premultiplied(bytes[0], bytes[1], bytes[2], bytes[3]) + }) + .collect(); + + texture.set( + egui::ColorImage { + size: [self.width, self.height], + source_size: Vec2::from([self.width as f32, self.height as f32]), + pixels, + }, + egui::TextureOptions::NEAREST, + ); + } + + // scale to fit available space while preserving aspect ratio + let available = ui.available_size(); + let aspect = self.width as f32 / self.height as f32; + let size = if available.x / aspect <= available.y { + egui::vec2(available.x, available.x / aspect) + } else { + egui::vec2(available.y * aspect, available.y) + }; + + ui.image(egui::load::SizedTexture::new(texture.id(), size)); + } +} diff --git a/dsa/emulator/src/frontend/components/registers.rs b/dsa/emulator/src/frontend/components/registers.rs new file mode 100644 index 0000000..59c3a9d --- /dev/null +++ b/dsa/emulator/src/frontend/components/registers.rs @@ -0,0 +1,131 @@ +use std::{f32, sync::Arc}; + +use common::prelude::Register; + +use crate::SharedState; + +use super::Component; + +pub struct Registers { + state: Arc, + visible: bool, +} + +impl Registers { + fn section( + &mut self, + ui: &mut egui::Ui, + ctx: &egui::Context, + registers: Vec<(Register, u32)>, + col_pairs: usize, + name: &str, + ) { + ui.collapsing(name, |ui| { + egui::Grid::new(name) + .num_columns(col_pairs * 2) + .spacing([40.0, 4.0]) + .striped(true) + .show(ui, |ui| { + // only show header if there are multiple rows + if registers.len() > col_pairs { + for _ in 0..col_pairs { + ui.label("Register"); + ui.label("Value"); + } + ui.end_row(); + } + for chunk in registers.chunks(col_pairs) { + for (reg, val) in chunk { + ui.label(reg.to_string()); + ui.label(format!("0x{:08X} ({})", val, val)); + } + for _ in chunk.len()..col_pairs { + ui.label(""); + ui.label(""); + } + ui.end_row(); + } + }); + }); + } +} + +impl Registers { + pub fn new(state: Arc) -> Self { + Self { + state, + visible: false, + } + } +} + +impl Component for Registers { + fn title(&self) -> &str { + "Registers" + } + + fn visible(&mut self) -> &mut bool { + &mut self.visible + } + + fn ui(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) { + ui.set_min_width(200.0); + + let state = self.state.proc.load(); + let available = ui.available_width(); + let col_pairs = match available { + 0.0..350.0 => 1, + 350.0..600.0 => 2, + 600.0..900.0 => 3, + f32::MIN..0.0 => unreachable!(), + _ => 4, + }; + + ui.vertical(|ui| { + // ── General Purpose ────────────────────────────────────── + let gp_registers: Vec<(Register, u32)> = (0..=15u8) + .map(|i| (Register::from_u8(i).unwrap(), state.registers[i as usize])) + .collect(); + self.section( + ui, + ctx, + gp_registers, + col_pairs, + "General Purpose Registers", + ); + ui.separator(); + + // ── Stack ───────────────────────────────────────────────── + let stack_registers = vec![ + (Register::Spr, state.registers[Register::Spr as usize]), + (Register::Bpr, state.registers[Register::Bpr as usize]), + ]; + self.section(ui, ctx, stack_registers, col_pairs, "Stack Registers"); + ui.separator(); + + // ── Special Purpose ─────────────────────────────────────── + let special_registers = vec![ + (Register::Acc, state.registers[Register::Acc as usize]), + (Register::Ret, state.registers[Register::Ret as usize]), + (Register::Idr, state.registers[Register::Idr as usize]), + (Register::Mmr, state.registers[Register::Mmr as usize]), + ]; + self.section( + ui, + ctx, + special_registers, + col_pairs, + "Special Purpose Registers", + ); + ui.separator(); + + // ── System ──────────────────────────────────────────────── + let system_registers = vec![ + (Register::Pcx, state.registers[Register::Pcx as usize]), + (Register::Sts, state.registers[Register::Sts as usize]), + (Register::Cir, state.registers[Register::Cir as usize]), + ]; + self.section(ui, ctx, system_registers, col_pairs, "System Registers"); + }); + } +} diff --git a/dsa/emulator/src/frontend/components/serial.rs b/dsa/emulator/src/frontend/components/serial.rs new file mode 100644 index 0000000..4a6ba7d --- /dev/null +++ b/dsa/emulator/src/frontend/components/serial.rs @@ -0,0 +1,17 @@ +use crate::{frontend::components::Component, io::serial::Serial}; + +impl Component for Serial { + fn title(&self) -> &str { + "Serial" + } + + fn visible(&mut self) -> &mut bool { + &mut self.visible + } + + fn ui(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) { + self.update(); + + ui.label(String::from_utf8(self.buffer.clone()).unwrap()); + } +} diff --git a/dsa/emulator/src/frontend/mod.rs b/dsa/emulator/src/frontend/mod.rs new file mode 100644 index 0000000..dc152cb --- /dev/null +++ b/dsa/emulator/src/frontend/mod.rs @@ -0,0 +1,149 @@ +use eframe::NativeOptions; +use eframe::egui; +use std::sync::Arc; +use std::sync::atomic::Ordering; + +mod components; + +use crate::frontend::components::Component; +use crate::frontend::components::framebuffer::FrameBuffer; +use crate::io::serial::Serial; +use crate::{MemoryBank, SharedState, config::VERSION}; +use components::{controller::Controller, display::Display, registers::Registers}; + +pub fn run_app(state: Arc, mem_handle: MemoryBank) -> eframe::Result<()> { + eframe::run_native( + "Damn Simple Architecture", + NativeOptions::default(), + Box::new(|cc| Ok(Box::new(DsaUi::new(cc, state, mem_handle)))), + ) +} + +impl eframe::App for DsaUi { + fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { + // request an update from emulator every cycle + self.state.update_req.store(true, Ordering::Relaxed); + + // title panel with dsa title + egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { + ui.with_layout( + egui::Layout::top_down_justified(egui::Align::Center) + .with_main_align(egui::Align::Min), + |ui| { + ui.allocate_space(egui::vec2(0.0, 5.0)); + ui.heading(format!("Damn Simple Architecture v{} 🚀", VERSION)); + ui.allocate_space(egui::vec2(0.0, 5.0)); + }, + ); + }); + + // menu bar. + egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| { + egui::MenuBar::new().ui(ui, |ui| { + ui.menu_button("Panels", |ui| { + ui.set_max_width(300.0); + ui.set_min_width(300.0); + ui.spacing_mut().button_padding = egui::vec2(10.0, 5.0); + + for comp in &mut self.components { + let name = comp.title().to_string(); + ui.toggle_value(comp.visible(), name); + } + }); + }); + }); + + egui::CentralPanel::default().show(ctx, |ui| { + for c in &mut self.components { + let mut visible = *c.visible(); + if visible { + egui::Window::new(c.title()) + .open(&mut visible) + .show(ctx, |ui| { + c.ui(ui, ctx); + }); + } + *c.visible() = visible; + } + }); + + ctx.request_repaint_after(std::time::Duration::from_millis(16)); // ~60fps + } +} + +pub struct DsaUi { + state: Arc, + mem_handle: MemoryBank, + components: Vec>, +} + +impl DsaUi { + pub fn new( + cc: &eframe::CreationContext, + state: Arc, + mem_handle: MemoryBank, + ) -> Self { + // components + let mut components: Vec> = vec![ + Box::new(Controller::new(state.clone(), mem_handle.clone())), + Box::new(Display::new(80, 25, 0x20000, mem_handle.clone())), + Box::new(FrameBuffer::new(320, 240, 0x30000, mem_handle.clone())), + Box::new(Registers::new(state.clone())), + Box::new(Serial::new(0x40000, state.clone(), mem_handle.clone())), + ]; + components.iter_mut().for_each(|c| *c.visible() = false); + + Self::configure_appearance(&cc.egui_ctx); + let app = Self { + state, + mem_handle, + components, + }; + app + } + + fn configure_appearance(ctx: &egui::Context) { + // configure appearance of UI elements + let mut visuals = egui::Visuals::dark(); + + // --- your existing settings --- + visuals.window_fill = egui::Color32::from_rgb(20, 20, 20); + visuals.panel_fill = egui::Color32::from_rgb(20, 20, 20); + visuals.widgets.inactive.fg_stroke = + egui::Stroke::from((1.0, egui::Color32::from_rgb(255, 255, 255))); + visuals.widgets.inactive.bg_stroke = + egui::Stroke::from((1.0, egui::Color32::from_rgb(60, 60, 60))); + visuals.widgets.inactive.corner_radius = egui::CornerRadius::from(4); + visuals.widgets.inactive.bg_fill = egui::Color32::from_rgb(20, 20, 20); + visuals.widgets.inactive.weak_bg_fill = egui::Color32::from_rgb(20, 20, 20); + visuals.widgets.inactive.expansion = 1.0; + + // tile resize handle + visuals.window_stroke = egui::Stroke::new(1.0, egui::Color32::from_rgb(45, 45, 45)); + visuals.extreme_bg_color = egui::Color32::from_rgb(20, 20, 20); + + ctx.set_visuals(visuals); + + let mut fonts = egui::FontDefinitions::default(); + fonts.font_data.insert( + "JetBrains Mono Nerd Font".to_string(), + std::sync::Arc::new(egui::FontData::from_static(include_bytes!( + "../../font/JetBrainsMonoNerdFontMono_Regular.ttf", + ))), + ); + + fonts + .families + .entry(egui::FontFamily::Proportional) + .or_default() + .insert(0, "JetBrains Mono Nerd Font".to_string()); + + fonts + .families + .entry(egui::FontFamily::Monospace) + .or_default() + .insert(0, "JetBrains Mono Nerd Font".to_string()); + + ctx.set_fonts(fonts); + } +} diff --git a/dsa/emulator/src/io/display.rs b/dsa/emulator/src/io/display.rs index 25906d0..68f980b 100644 --- a/dsa/emulator/src/io/display.rs +++ b/dsa/emulator/src/io/display.rs @@ -1,80 +1,62 @@ -use std::sync::{ - Arc, - atomic::{AtomicBool, AtomicU8, Ordering}, -}; +// use super::{IoAccess, IoHandle}; +use crate::{MemoryBank, Page, backend::memory::PhysAddr}; -use crate::{ - io::{IoAccess, IoDevice}, - processor::interrupts::Interrupt, -}; +pub struct Display { + width: usize, + height: usize, + addr: PhysAddr, -pub struct DisplayDevice { - buffer: Arc>, - dirty: Arc, + mem: MemoryBank, + buffer: Vec, + visible: bool, } -impl DisplayDevice { - pub fn new(width: usize, height: usize) -> (Self, DisplayHandle) { - let buffer = Arc::new( - (0..width * height) - .map(|_| AtomicU8::new(0)) - .collect::>() - .into_boxed_slice(), - ); - let dirty = Arc::new(AtomicBool::new(false)); +impl Display { + pub fn new(width: u32, height: u32, addr: PhysAddr, mem: MemoryBank) -> Self { + Self { + width: width as usize, + height: height as usize, + addr, + mem, + buffer: Vec::new(), + visible: true, + } + } - let handle = DisplayHandle { - buffer: Arc::clone(&buffer), - dirty: Arc::clone(&dirty), - }; + pub fn visible(&mut self) -> &mut bool { + &mut self.visible + } - (Self { buffer, dirty }, handle) - } -} - -impl IoDevice for DisplayDevice { - fn size(&self) -> u32 { - (self.buffer.len()) as u32 - } - fn access(&self) -> IoAccess { - IoAccess::WRITE - } - - fn id(&self) -> super::DeviceId { - super::DeviceId::Display - } - - fn write_word(&mut self, offset: u32, val: u32) -> Result<(), Interrupt> { - let bytes = val.to_le_bytes(); - self.buffer[offset as usize].store(bytes[0], Ordering::Relaxed); - self.buffer[offset as usize + 1].store(bytes[1], Ordering::Relaxed); - self.buffer[offset as usize + 2].store(bytes[2], Ordering::Relaxed); - self.buffer[offset as usize + 3].store(bytes[3], Ordering::Relaxed); - self.dirty.store(true, Ordering::Relaxed); - Ok(()) - } - - fn write_byte(&mut self, offset: u32, val: u8) -> Result<(), Interrupt> { - self.buffer[offset as usize].store(val, Ordering::Relaxed); - self.dirty.store(true, Ordering::Relaxed); - Ok(()) - } -} - -pub struct DisplayHandle { - pub buffer: Arc>, - pub dirty: Arc, -} - -impl DisplayHandle { - pub fn is_dirty(&self) -> bool { - self.dirty.swap(false, Ordering::Relaxed) - } - - pub fn read_all(&self) -> Vec { - self.buffer - .iter() - .map(|b| b.load(Ordering::Relaxed)) - .collect() + pub fn width(&self) -> usize { + self.width + } + + pub fn height(&self) -> usize { + self.height + } + + fn internal_read(&self) -> Vec { + let pages = (0..((self.width * self.height).div_ceil(Page::SIZE))) + .map(|i| { + let page = self.mem.read_page(self.addr + i as u32 * 4096); + page.as_slice().to_owned() + }) + .flatten() + .take((self.width * self.height) as usize) + .collect::>(); + pages + } + + pub fn changed(&mut self) -> bool { + let temp = self.internal_read(); + if temp != self.buffer { + self.buffer = temp; + return true; + } + false + } + + pub fn read(&mut self) -> Vec { + self.internal_read() } } diff --git a/dsa/emulator/src/io/mod.rs b/dsa/emulator/src/io/mod.rs index adaf491..204ffb4 100644 --- a/dsa/emulator/src/io/mod.rs +++ b/dsa/emulator/src/io/mod.rs @@ -1,124 +1,46 @@ use serde::{Deserialize, Serialize}; -use crate::{Emulator, RandomAccessMemory, processor::interrupts::Interrupt}; - pub mod display; +pub mod serial; +// #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +// pub enum DeviceId { +// Display, +// Serial, +// Random, +// Timer, +// } -pub struct MappedDevice { - pub base: u32, - pub device: Box, -} +// pub trait IoHandle: Send { +// /// Return the size (in bytes) of the device's addressable region. +// fn size(&self) -> usize; -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct IoAccess(u8); +// /// Return the access permissions for the device. +// fn access(&self) -> IoAccess; -impl IoAccess { - pub const READ: Self = Self(0b01); - pub const WRITE: Self = Self(0b10); +// /// Return the DeviceId +// fn id(&self) -> DeviceId; +// } - /// Convenience alias for Read + Write. - pub const READWRITE: Self = Self(Self::READ.0 | Self::WRITE.0); - pub const NONE: Self = Self(0); +// #[derive(Clone, Copy, Debug, PartialEq)] +// pub struct IoAccess(u8); - /// Bitwise OR – returns a new `IoAccess` that contains all flags from both operands. - #[inline] - pub fn or(self, other: Self) -> Self { - Self(self.0 | other.0) - } +// impl IoAccess { +// pub const READ: Self = Self(0b01); +// pub const WRITE: Self = Self(0b10); - /// Check if a flag is present. - #[inline] - pub fn contains(&self, flag: Self) -> bool { - self.0 & flag.0 != 0 - } -} +// /// Convenience alias for Read + Write. +// pub const READWRITE: Self = Self(Self::READ.0 | Self::WRITE.0); +// pub const NONE: Self = Self(0); -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub enum DeviceId { - Display, - Serial, - Random, - Timer, -} +// /// Bitwise OR – returns a new `IoAccess` that contains all flags from both operands. +// #[inline] +// pub fn or(self, other: Self) -> Self { +// Self(self.0 | other.0) +// } -/// Trait representing an input/output device that can be mapped into a memory space. -/// -/// - Default methods return dummy data. -pub trait IoDevice: Send { - /// Return the size (in bytes) of the device's addressable region. - fn size(&self) -> u32; - - /// Return the access permissions for the device. - fn access(&self) -> IoAccess; - - /// Return the DeviceId - fn id(&self) -> DeviceId; - - #[inline] - /// Read a single byte from the specified address. - /// - /// By default, write‑only devices return `None`. All other devices - /// return `Some(0)` as placeholder data. Implementations should override - /// this method to provide actual read logic. - fn read_byte(&self, offset: u32) -> Result { - if self.access().contains(IoAccess::READ) { - Ok(0) - } else { - Err(Interrupt::ReadFromWriteOnly) - } - } - - #[inline] - /// Write a single byte to the specified address. - /// - /// By default, read‑only devices return `false` indicating failure. - /// All other devices return `true`. Implementations should override - /// this method to perform real write operations and return whether - /// the operation succeeded. - fn write_byte(&mut self, offset: u32, val: u8) -> Result<(), Interrupt> { - if self.access().contains(IoAccess::WRITE) { - Ok(()) - } else { - Err(Interrupt::WriteToReadOnly) - } - } - - /// Read a 32‑bit word from the specified address in little‑endian order. - /// - /// The default implementation composes four consecutive byte reads using - /// `read_byte`. If any byte read fails, the whole operation returns `None`. - /// Write‑only devices return `None` immediately. Override this method for - /// more efficient word reads. - #[inline] - fn read_word(&self, offset: u32) -> Result { - if self.access().contains(IoAccess::READ) { - // default: compose from bytes - let b0 = self.read_byte(offset)? as u32; - let b1 = self.read_byte(offset + 1)? as u32; - let b2 = self.read_byte(offset + 2)? as u32; - let b3 = self.read_byte(offset + 3)? as u32; - return Ok(b0 | b1 << 8 | b2 << 16 | b3 << 24); - } - - Err(Interrupt::ReadFromWriteOnly) - } - - /// Write a 32‑bit word to the specified address in little‑endian order. - /// - /// The default implementation splits the value into bytes and writes each - /// using `write_byte`. Read‑only devices return `false`; otherwise returns - /// `true` after attempting all byte writes. Override for more efficient - /// word writes or to handle partial failures. - fn write_word(&mut self, offset: u32, val: u32) -> Result<(), Interrupt> { - if self.access().contains(IoAccess::WRITE) { - let bytes = val.to_le_bytes(); - self.write_byte(offset, bytes[0]); - self.write_byte(offset + 1, bytes[1]); - self.write_byte(offset + 2, bytes[2]); - self.write_byte(offset + 3, bytes[3]); - return Ok(()); - } - - Err(Interrupt::WriteToReadOnly) - } -} +// /// Check if a flag is present. +// #[inline] +// pub fn contains(&self, flag: Self) -> bool { +// self.0 & flag.0 != 0 +// } +// } diff --git a/dsa/emulator/src/io/serial.rs b/dsa/emulator/src/io/serial.rs new file mode 100644 index 0000000..c431c3f --- /dev/null +++ b/dsa/emulator/src/io/serial.rs @@ -0,0 +1,48 @@ +use std::{ + path::Component, + sync::{ + Arc, + mpsc::{Receiver, Sender}, + }, +}; + +use crate::{MemoryBank, SharedState, backend::memory::PhysAddr}; + +pub struct Serial { + pub receiver: Receiver, + pub buffer: Vec, + + pub visible: bool, +} + +impl Serial { + fn uart_io_thread(mem: MemoryBank, uart_base: PhysAddr, tx: Sender) { + loop { + let word = mem.read_word(uart_base); + // println!("word {:#010X}", word); + if word >> 24 == 0x01 { + let byte = ((word >> 16) & 0xFF) as u8; + let _ = tx.send(byte); + mem.write_word(uart_base, 0x00_00_00_00); // clear valid flag + } + std::hint::spin_loop(); + } + } + + pub fn new(uart_base: PhysAddr, state: Arc, mem_handle: MemoryBank) -> Self { + let (sender, receiver) = std::sync::mpsc::channel(); + let _ = std::thread::spawn(move || Self::uart_io_thread(mem_handle, uart_base, sender)); + + Self { + visible: false, + receiver, + buffer: Vec::new(), + } + } + + pub fn update(&mut self) { + while let Ok(byte) = self.receiver.try_recv() { + self.buffer.push(byte); + } + } +} diff --git a/dsa/emulator/src/lib.rs b/dsa/emulator/src/lib.rs index 9920c43..da07b95 100644 --- a/dsa/emulator/src/lib.rs +++ b/dsa/emulator/src/lib.rs @@ -2,11 +2,10 @@ #![feature(inherent_associated_types)] pub mod args; +pub mod backend; pub mod config; +pub mod frontend; pub mod io; -mod memory; -mod processor; -pub use {config::MemoryMap, processor::processor::Emulator, processor::state::SharedState}; - -pub use memory::ram::*; +pub use backend::memory::ram::*; +pub use backend::{processor::processor::Emulator, processor::state::SharedState}; diff --git a/dsa/emulator/src/main.rs b/dsa/emulator/src/main.rs index 2842702..c2a6400 100644 --- a/dsa/emulator/src/main.rs +++ b/dsa/emulator/src/main.rs @@ -1,32 +1,18 @@ use clap::Parser; -use common::{instructions::Instruction, register::Register}; -use dsa::{ - BulkAllocStore, Emulator, Page, RandomAccessMemory, SharedState, - args::DsaArgs, - io::display::{DisplayDevice, DisplayHandle}, -}; -use std::{ - os::unix::thread::JoinHandleExt, - process::exit, - sync::{Arc, atomic::Ordering}, - thread, - time::Duration, -}; + +use common::prelude::Instruction; +use dsa::{Emulator, Page, args::DsaArgs, frontend::run_app}; +use std::thread; + +const STACK_SIZE: usize = 1024 * 1024 * 16; fn main() { let args = DsaArgs::parse(); - let mmap = args.get_memory_map(); - let iomap = args.get_io_map(); + let mut emulator = Emulator::new().apply_memory_map(args.get_memory_map()); - let store = BulkAllocStore::new(); - let (display, handle) = DisplayDevice::new(80, 25); - - let mut emulator = Emulator::new(store) - .with_device(display) - .apply_memory_map(mmap) - .apply_io_map(iomap) - .unwrap(); + let mem_handle = emulator.memory_mut().clone(); + let state = emulator.state_handle(); if let Some(bin) = args.get_binary() { for (i, ch) in bin.chunks(4).enumerate() { @@ -56,41 +42,10 @@ fn main() { } } - let state = emulator.state_handle(); - - const STACK_SIZE: usize = 1024 * 1024 * 16; - let runner = thread::Builder::new() + let _runner = thread::Builder::new() .stack_size(STACK_SIZE) .spawn(move || emulator.run().unwrap()) .unwrap(); - let observer = thread::spawn(|| observe(state, handle)); - - runner.join().unwrap(); - thread::sleep(Duration::from_millis(100)); - - // yeah we don't need this tbh. - // observer.join().unwrap(); -} - -/// todo: remove this! -fn observe(state: Arc, handle: DisplayHandle) { - loop { - state.update_req.store(true, Ordering::Relaxed); - thread::sleep(std::time::Duration::from_millis(20)); - let state = state.proc.load(); - // println!( - // "clock: {}, GP Registers: {:?}", - // state.clock, state.registers - // ); - - if handle.is_dirty() { - for line in handle.read_all().chunks(80) { - for (i, byte) in line.iter().enumerate() { - print!("{}", *byte as char); - } - println!(); - } - } - } + run_app(state, mem_handle).unwrap() } diff --git a/dsa/emulator/src/memory/ram/arraymap.rs b/dsa/emulator/src/memory/ram/arraymap.rs deleted file mode 100644 index 8b3016b..0000000 --- a/dsa/emulator/src/memory/ram/arraymap.rs +++ /dev/null @@ -1,107 +0,0 @@ -use std::{ - alloc::{Layout, alloc_zeroed}, - hint::{likely, unlikely}, -}; - -use crate::memory::{ - PhysAddr, RandomAccessMemory, - ram::{NUM_PAGES, Page, idx}, -}; - -pub struct ArrayStore { - pages: Box<[*mut Page; NUM_PAGES]>, -} - -unsafe impl Send for ArrayStore {} -impl ArrayStore { - 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 ArrayStore { - #[inline(always)] - fn read_word(&self, addr: PhysAddr) -> u32 { - debug_assert_eq!(addr % 4, 0); - let (idx, offset) = idx(addr); - - if likely(!self.pages[idx].is_null()) { - return unsafe { (self.pages[idx] as *const u32).byte_add(offset).read() }; - } - - // Slow path: MMIO, fault, etc — never inlined - // Since we're only reading, we can safely return 0 - return 0; - } - - #[inline(always)] - fn read_byte(&self, addr: PhysAddr) -> u8 { - let (idx, offset) = idx(addr); - - if likely(!self.pages[idx].is_null()) { - return unsafe { (self.pages[idx] as *const u8).byte_add(offset).read() }; - } - - return 0; - } - - #[inline(always)] - fn read_page(&self, addr: PhysAddr) -> &Page { - debug_assert_eq!(addr % 4096, 0); - let (idx, _) = idx(addr); - - if likely(!self.pages[idx].is_null()) { - return unsafe { &*(self.pages[idx] as *const Page) }; - } - - return &Page([0; 4096]); - } - - #[inline(always)] - fn write_byte(&mut self, addr: PhysAddr, value: u8) { - let (idx, offset) = idx(addr); - - if unlikely(self.pages[idx].is_null()) { - self.pages[idx] = self.alloc(); - } - - unsafe { - (self.pages[idx] as *mut u8).byte_add(offset).write(value); - } - } - - #[inline(always)] - fn write_word(&mut self, addr: PhysAddr, value: u32) { - debug_assert_eq!(addr % 4, 0); - let (idx, offset) = idx(addr); - - if unlikely(self.pages[idx].is_null()) { - self.pages[idx] = self.alloc(); - } - - unsafe { (self.pages[idx] as *mut u32).byte_add(offset).write(value) } - } - - #[inline(always)] - fn write_page(&mut self, addr: PhysAddr, value: &Page) { - debug_assert_eq!(addr % 4096, 0); - let (idx, _) = idx(addr); - - if unlikely(self.pages[idx].is_null()) { - self.pages[idx] = self.alloc(); - } - - unsafe { - (self.pages[idx] as *mut Page).copy_from(value, 1); - } - } -} diff --git a/dsa/emulator/src/memory/ram/bulkalloc.rs b/dsa/emulator/src/memory/ram/bulkalloc.rs deleted file mode 100644 index 278b457..0000000 --- a/dsa/emulator/src/memory/ram/bulkalloc.rs +++ /dev/null @@ -1,134 +0,0 @@ -use std::{ - alloc::{Layout, alloc_zeroed}, - hint::{likely, unlikely}, -}; - -use crate::memory::{ - PhysAddr, RandomAccessMemory, - ram::{Page, idx}, -}; - -pub struct BulkAllocStore { - pages: Box<[*mut Page; Page::COUNT]>, - pre_allocated: [*mut Page; Self::ALLOC_AT_ONCE], - idx: u8, -} - -unsafe impl Send for BulkAllocStore {} -impl BulkAllocStore { - const ALLOC_AT_ONCE: usize = 256 as usize; - - pub fn new() -> Self { - let pages = vec![0 as *mut Page; Page::COUNT] - .into_boxed_slice() - .try_into() - .unwrap(); - - Self { - pages, - pre_allocated: Self::alloc_set(), - idx: 0, - } - } - - fn alloc_set() -> [*mut Page; Self::ALLOC_AT_ONCE] { - let layout = Layout::from_size_align(Page::SIZE * Self::ALLOC_AT_ONCE, Page::SIZE).unwrap(); - let mut region = unsafe { alloc_zeroed(layout) as *mut Page }; - let mut set = [0 as *mut Page; Self::ALLOC_AT_ONCE]; - for i in 0..Self::ALLOC_AT_ONCE { - set[i] = region; - region = unsafe { region.byte_add(4096) }; - } - set - } - - fn alloc(&mut self) -> *mut Page { - if self.idx < Self::ALLOC_AT_ONCE as u8 { - let page = self.pre_allocated[self.idx as usize]; - self.idx += 1; - page - } else { - self.idx = 0; - self.pre_allocated = Self::alloc_set(); - self.pre_allocated[0] - } - } -} - -impl RandomAccessMemory for BulkAllocStore { - #[inline(always)] - fn read_word(&self, addr: PhysAddr) -> u32 { - debug_assert_eq!(addr % 4, 0); - let (idx, offset) = idx(addr); - - if likely(!self.pages[idx].is_null()) { - return unsafe { (self.pages[idx] as *const u32).byte_add(offset).read() }; - } - - // Slow path: MMIO, fault, etc — never inlined - // Since we're only reading, we can safely return 0 - return 0; - } - - #[inline(always)] - fn read_byte(&self, addr: PhysAddr) -> u8 { - let (idx, offset) = idx(addr); - - if likely(!self.pages[idx].is_null()) { - return unsafe { (self.pages[idx] as *const u8).byte_add(offset).read() }; - } - - return 0; - } - - #[inline(always)] - fn read_page(&self, addr: PhysAddr) -> &Page { - debug_assert_eq!(addr % Page::SIZE as u32, 0); - let (idx, _) = idx(addr); - - if likely(!self.pages[idx].is_null()) { - return unsafe { &*(self.pages[idx] as *const Page) }; - } - - return &Page::ZERO; - } - - #[inline(always)] - fn write_byte(&mut self, addr: PhysAddr, value: u8) { - let (idx, offset) = idx(addr); - - if unlikely(self.pages[idx].is_null()) { - self.pages[idx] = self.alloc(); - } - - unsafe { - (self.pages[idx] as *mut u8).byte_add(offset).write(value); - } - } - - #[inline(always)] - fn write_word(&mut self, addr: PhysAddr, value: u32) { - debug_assert_eq!(addr % 4, 0); - let (idx, offset) = idx(addr); - - if unlikely(self.pages[idx].is_null()) { - self.pages[idx] = self.alloc(); - } - - unsafe { (self.pages[idx] as *mut u32).byte_add(offset).write(value) } - } - - #[inline(always)] - fn write_page(&mut self, addr: PhysAddr, value: &Page) { - debug_assert_eq!(addr % Page::SIZE as u32, 0); - let (idx, _) = idx(addr); - - if unlikely(self.pages[idx].is_null()) { - self.pages[idx] = self.alloc(); - } - - unsafe { - (self.pages[idx] as *mut Page).copy_from(value, 1); - } - } -} diff --git a/dsa/emulator/src/memory/ram/hashmap.rs b/dsa/emulator/src/memory/ram/hashmap.rs deleted file mode 100644 index c73cde6..0000000 --- a/dsa/emulator/src/memory/ram/hashmap.rs +++ /dev/null @@ -1,66 +0,0 @@ -use fxhash::FxHashMap; - -use crate::memory::{PhysAddr, RandomAccessMemory, ram::Page}; - -pub struct HashStore { - pages: FxHashMap, -} - -impl HashStore { - 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 HashStore { - #[inline(always)] - fn read_byte(&self, addr: PhysAddr) -> u8 { - let (page, offset) = Self::segment_addr(addr); - self.pages - .get(&page) - .map(|p| p.as_ref()[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(&Page::ZERO); - u32::from_be_bytes(page[offset..=offset + 3].try_into().unwrap()) - } - - #[inline(always)] - fn read_page(&self, addr: PhysAddr) -> &Page { - debug_assert_eq!(addr % 0x1000, 0); - self.pages.get(&addr).unwrap_or(&Page::ZERO) - } - - #[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(|| Page::zeroed())[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(|| Page::zeroed()); - page[offset..=offset + 3].copy_from_slice(&value.to_be_bytes()); - } - - #[inline(always)] - fn write_page(&mut self, addr: PhysAddr, value: &Page) { - debug_assert_eq!(addr % 0x1000, 0); - let page = self.pages.entry(addr).or_insert_with(|| Page::zeroed()); - page.0 = value.0; - } -} diff --git a/dsa/emulator/src/memory/ram/mod.rs b/dsa/emulator/src/memory/ram/mod.rs deleted file mode 100644 index 97f4578..0000000 --- a/dsa/emulator/src/memory/ram/mod.rs +++ /dev/null @@ -1,108 +0,0 @@ -use std::ops::{Deref, DerefMut}; - -use crate::memory::PhysAddr; - -#[cfg(feature = "mainstore-arraymap")] -pub mod arraymap; -#[cfg(feature = "mainstore-arraymap")] -pub use arraymap::ArrayStore; - -#[cfg(feature = "mainstore-hashmap")] -pub mod hashmap; -#[cfg(feature = "mainstore-hashmap")] -pub use hashmap::HashStore; - -#[cfg(feature = "mainstore-stackarray")] -pub mod stackarray; -#[cfg(feature = "mainstore-stackarray")] -pub use stackarray::StackArrayStore; - -#[cfg(feature = "mainstore-bulkalloc")] -pub mod bulkalloc; -#[cfg(feature = "mainstore-bulkalloc")] -pub use bulkalloc::BulkAllocStore; - -#[cfg(feature = "mainstore-prealloc")] -pub mod prealloc; -#[cfg(feature = "mainstore-prealloc")] -pub use prealloc::PreAllocStore; - -#[repr(transparent)] -#[derive(Clone)] -pub struct Page([u8; 4096]); - -impl Page { - pub const SIZE: usize = 4096; - pub const COUNT: usize = u32::MAX as usize / Self::SIZE + 1; - - pub const MASK: u32 = 0xFFF; - pub const ZERO: Self = Page::zeroed(); - - pub const fn zeroed() -> Self { - Self([0; 4096]) - } - - pub const fn from(data: [u8; 4096]) -> Self { - Self(data) - } - - pub fn read_word(&self, offset: usize) -> u32 { - u32::from_le_bytes(self.0[offset..offset + 4].try_into().unwrap()) - } - - pub fn write_word(&mut self, offset: usize, value: u32) { - self.0[offset..offset + 4].copy_from_slice(&value.to_le_bytes()); - } - - pub fn paginate(data: &[u8]) -> impl Iterator + '_ { - data.chunks(4096).map(|chunk| { - let mut arr = [0u8; 4096]; - arr[..chunk.len()].copy_from_slice(chunk); - Page(arr) - }) - } -} - -impl Deref for Page { - type Target = [u8]; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for Page { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl TryFrom> for Page { - type Error = String; - - fn try_from(data: Vec) -> Result { - let arr: [u8; 4096] = data - .try_into() - .map_err(|v: Vec| format!("Expected 4096 bytes, got {}", v.len()))?; - Ok(Page(arr)) - } -} - -#[inline(always)] -const fn idx(addr: PhysAddr) -> (usize, usize) { - ((addr >> 12) as usize, (addr & 0xFFF) as usize) -} - -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) -> &Page; - - fn write_page(&mut self, addr: PhysAddr, value: &Page); -} diff --git a/dsa/emulator/src/memory/ram/prealloc.rs b/dsa/emulator/src/memory/ram/prealloc.rs deleted file mode 100644 index 76f0f55..0000000 --- a/dsa/emulator/src/memory/ram/prealloc.rs +++ /dev/null @@ -1,50 +0,0 @@ -use crate::memory::{ - PhysAddr, RandomAccessMemory, - ram::{NUM_PAGES, Page}, -}; - -pub struct PreAllocStore { - heap: Box<[u8; NUM_PAGES * 4096]>, -} - -unsafe impl Send for PreAllocStore {} -impl PreAllocStore { - pub fn new() -> Self { - Self { - heap: unsafe { Box::new_zeroed().assume_init() }, - } - } -} - -impl RandomAccessMemory for PreAllocStore { - #[inline(always)] - fn read_word(&self, addr: PhysAddr) -> u32 { - unsafe { (self.heap.as_ptr().add(addr as usize) as *const u32).read() } - } - - #[inline(always)] - fn read_byte(&self, addr: PhysAddr) -> u8 { - self.heap[addr as usize] - } - - #[inline(always)] - fn read_page(&self, addr: PhysAddr) -> &Page { - debug_assert_eq!(addr % 4096, 0); - unsafe { (self.heap.as_ptr().add(addr as usize) as *const Page).as_ref_unchecked() } - } - - #[inline(always)] - fn write_byte(&mut self, addr: PhysAddr, value: u8) { - self.heap[addr as usize] = value; - } - - #[inline(always)] - fn write_word(&mut self, addr: PhysAddr, value: u32) { - unsafe { (self.heap.as_ptr().add(addr as usize) as *mut u32).write(value) }; - } - - #[inline(always)] - fn write_page(&mut self, addr: PhysAddr, value: &Page) { - self.heap[addr as usize..addr as usize + 4096].copy_from_slice(value); - } -} diff --git a/dsa/emulator/src/memory/ram/stackarray.rs b/dsa/emulator/src/memory/ram/stackarray.rs deleted file mode 100644 index 61ac1aa..0000000 --- a/dsa/emulator/src/memory/ram/stackarray.rs +++ /dev/null @@ -1,103 +0,0 @@ -use std::{ - alloc::{Layout, alloc_zeroed}, - hint::{likely, unlikely}, -}; - -use crate::memory::{ - PhysAddr, RandomAccessMemory, - ram::{NUM_PAGES, Page, idx}, -}; - -pub struct StackArrayStore { - pages: [*mut Page; NUM_PAGES], -} - -unsafe impl Send for StackArrayStore {} -impl StackArrayStore { - pub fn new() -> Self { - Self { - pages: [0 as *mut Page; 2 << 20], - } - } - - 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 StackArrayStore { - #[inline(always)] - fn read_word(&self, addr: PhysAddr) -> u32 { - debug_assert_eq!(addr % 4, 0); - let (idx, offset) = idx(addr); - - if likely(!self.pages[idx].is_null()) { - return unsafe { (self.pages[idx] as *const u32).byte_add(offset).read() }; - } - - // Slow path: MMIO, fault, etc — never inlined - // Since we're only reading, we can safely return 0 - return 0; - } - - #[inline(always)] - fn read_byte(&self, addr: PhysAddr) -> u8 { - let (idx, offset) = idx(addr); - - if likely(!self.pages[idx].is_null()) { - return unsafe { (self.pages[idx] as *const u8).byte_add(offset).read() }; - } - - return 0; - } - - #[inline(always)] - fn read_page(&self, addr: PhysAddr) -> &Page { - debug_assert_eq!(addr % 4096, 0); - let (idx, _) = idx(addr); - - if likely(!self.pages[idx].is_null()) { - return unsafe { &*(self.pages[idx] as *const Page) }; - } - - return &Page::ZERO; - } - - #[inline(always)] - fn write_byte(&mut self, addr: PhysAddr, value: u8) { - let (idx, offset) = idx(addr); - - if unlikely(self.pages[idx].is_null()) { - self.pages[idx] = self.alloc(); - } - - unsafe { - (self.pages[idx] as *mut u8).byte_add(offset).write(value); - } - } - - #[inline(always)] - fn write_word(&mut self, addr: PhysAddr, value: u32) { - debug_assert_eq!(addr % 4, 0); - let (idx, offset) = idx(addr); - - if unlikely(self.pages[idx].is_null()) { - self.pages[idx] = self.alloc(); - } - - unsafe { (self.pages[idx] as *mut u32).byte_add(offset).write(value) } - } - - #[inline(always)] - fn write_page(&mut self, addr: PhysAddr, value: &Page) { - debug_assert_eq!(addr % 4096, 0); - let (idx, _) = idx(addr); - - if unlikely(self.pages[idx].is_null()) { - self.pages[idx] = self.alloc(); - } - - unsafe { (self.pages[idx] as *mut Page).copy_from(value, 1) } - } -} diff --git a/dsa_resources/animation.dsa b/dsa_resources/animation.dsa new file mode 100644 index 0000000..43ecc92 --- /dev/null +++ b/dsa_resources/animation.dsa @@ -0,0 +1,133 @@ +dw DISPLAY: 0x30000 +dw stack: 0x10000 + +// registers: +// rg0 = x position +// rg1 = y position +// rg2 = x direction (1 = right, 0 = left) +// rg3 = y direction (1 = down, 0 = up) + +_init: + ldw stack, bpr + mov bpr, spr + +start: + lli 0, rg0 + lli 0, rg1 + lli 1, rg2 + lli 1, rg3 + +frame_loop: + // --- clear display --- + ldw DISPLAY, rg4 + lwi 76800, rg5 + +clear_loop: + stw zero, rg4 + addi rg4, 4, rg4 + subi rg5, 1, rg5 + jnz rg5, clear_loop + + // --- draw square --- + mov rg1, rg6 + +draw_y_loop: + lli 20, rg8 + add rg1, rg8, rg8 + ilt rg6, rg8, rg9 + jez rg9, draw_done + + mov rg0, rg7 + +draw_x_loop: + lli 20, rg8 + add rg0, rg8, rg8 + ilt rg7, rg8, rg9 + jez rg9, draw_x_done + + shl rg6, 8, rg8 + shl rg6, 6, rg9 + add rg8, rg9, rg8 + add rg8, rg7, rg8 + shl rg8, 2, rg8 + ldw DISPLAY, rg9 + add rg9, rg8, rg8 + + lli 0xFFFF, rg9 + lui 0xFFFF, rg9 + stw rg9, rg8 + + addi rg7, 1, rg7 + jmp draw_x_loop + +draw_x_done: + addi rg6, 1, rg6 + jmp draw_y_loop + +draw_done: + // --- check x walls before moving --- + lli 1, rg8 + ieq rg2, rg8, rg9 + jez rg9, check_x_left_wall + +check_x_right_wall: + // moving right: if x >= 300 flip direction + lli 300, rg8 + ige rg0, rg8, rg9 + jez rg9, move_x + lli 0, rg2 + jmp move_x + +check_x_left_wall: + // moving left: if x == 0 flip direction + ieq rg0, zero, rg9 + jez rg9, move_x + lli 1, rg2 + +move_x: + lli 1, rg8 + ieq rg2, rg8, rg9 + jez rg9, do_x_left + addi rg0, 1, rg0 + jmp check_y_walls +do_x_left: + subi rg0, 1, rg0 + +check_y_walls: + // --- check y walls before moving --- + lli 1, rg8 + ieq rg3, rg8, rg9 + jez rg9, check_y_up_wall + +check_y_down_wall: + // moving down: if y >= 220 flip direction + lli 220, rg8 + ige rg1, rg8, rg9 + jez rg9, move_y + lli 0, rg3 + jmp move_y + +check_y_up_wall: + // moving up: if y == 0 flip direction + ieq rg1, zero, rg9 + jez rg9, move_y + lli 1, rg3 + +move_y: + lli 1, rg8 + ieq rg3, rg8, rg9 + jez rg9, do_y_up + addi rg1, 1, rg1 + jmp delay +do_y_up: + subi rg1, 1, rg1 + +delay: + lwi 2000000, rg8 +delay_loop: + subi rg8, 1, rg8 + jnz rg8, delay_loop + + jmp frame_loop + + hlt diff --git a/dsa_resources/bf.dsa b/dsa_resources/bf.dsa index 598c6d2..2d0272a 100644 --- a/dsa_resources/bf.dsa +++ b/dsa_resources/bf.dsa @@ -3,7 +3,7 @@ include print "./print.dsa" -// "print hello world" +// "prints first 16 fibonacci numbers" db program: "++++++++++++++++++++++++++++++++++++++++++++ >++++++++++++++++++++++++++++++++ >++++++++++++++++ @@ -35,6 +35,7 @@ db program: "++++++++++++++++++++++++++++++++++++++++++++ <<++..." db error: "Invalid Instruction!" +dh counter: 0xFF dw stack: 0x10000 dw input: 0x30000 resb data: 1024 @@ -44,11 +45,15 @@ _init_stack: ldw stack, bpr mov bpr, spr +init: + ldw counter, rg7 + start: + call bf_reset + // load the start of the program into rg0 lwi program, rg0 lwi data, rg1 - // rg0 is our instruction pointer // rg1 is our data pointer // rg2 is the value at the data pointer @@ -98,7 +103,16 @@ loop_start: pop rg1 pop rg0 +db success: "Success!" end: + call print::newline + lwi success, rg0 + push rg0 + call print::print + pop zero + + subi rg7, 1 + jnz rg7, start hlt loop_end: @@ -225,3 +239,34 @@ close_left: // push zero to the stack push zero jmp _traverse_left + + +// ------------------------------------------ +// reset interpreter +// clears screen, resets cursor and clears data buffer. +bf_reset: func + push rg0 + push rg1 + + // clear screen and reset cursor + call print::clear + call print::reset + + // clear data buffer (resb data: 1024 = 256 words) + lli 256, rg0 + lwi data, rg1 +_bf_reset_clear_data: + subi rg0, 1, rg0 + stw zero, rg1 + addi rg1, 4, rg1 + igt rg0, zero, rg4 + jnz rg4, _bf_reset_clear_data + + pop rg1 + pop rg0 + + // reload interpreter state + lwi program, rg0 + lwi data, rg1 + lli 0, rg2 + return diff --git a/dsa_resources/framebuffer.dsa b/dsa_resources/framebuffer.dsa new file mode 100644 index 0000000..baad080 --- /dev/null +++ b/dsa_resources/framebuffer.dsa @@ -0,0 +1,65 @@ + +include print: "./print.dsa" + +db success: "Success!" +dw framebuffer: 0x30000 +dw stack: 0x10000 + +_init: + ldw stack, bpr + mov bpr, spr + +start: + ldw framebuffer, rg0 // rg0 = display base address + lli 0, rg1 // rg1 = y (0..240) + +y_loop: + lli 0, rg2 // rg2 = x (0..320) + +x_loop: + // red = x >> 1 (0..159, left to right gradient) + shr rg2, zero, 1, rg4 + + // green = y (0..239, top to bottom gradient) + mov rg1, rg5 + + // blue = constant 128 + lli 0x80, rg6 + + // pack as 0xAABBGGRR (LE reads as RGBA) + // start with red in bits 0-7 + mov rg4, rg7 + + // green into bits 8-15 + shl rg5, zero, 8, rg5 + or rg7, rg5, rg7 + + // blue into bits 16-23 + shl rg6, zero, 16, rg6 + or rg7, rg6, rg7 + + // alpha into bits 24-31 + lli 0xFF, rg6 + shl rg6, zero, 24, rg6 + or rg7, rg6, rg7 + + stw rg7, rg0 // write pixel + addi rg0, 4, rg0 // next pixel + + addi rg2, 1, rg2 + lli 320, rg6 + ilt rg2, rg6, rg6 + jnz rg6, x_loop + + addi rg1, 1, rg1 + lli 240, rg6 + ilt rg1, rg6, rg6 + jnz rg6, y_loop + + +_end: + lwi success: rg0 + push rg0 + call print::print + pop zero + hlt diff --git a/dsa_resources/out.dsb b/dsa_resources/out.dsb index 5128996..7b3af3e 100644 Binary files a/dsa_resources/out.dsb and b/dsa_resources/out.dsb differ diff --git a/dsa_resources/print.dsa b/dsa_resources/print.dsa index 5e6816a..f31af1e 100644 --- a/dsa_resources/print.dsa +++ b/dsa_resources/print.dsa @@ -185,8 +185,8 @@ print_whitespace: func addi rg1, 1 jmp _end -// // ------------------------------------------ -// // print newline +// ------------------------------------------ +// print newline // print_newline: // push bpr // mov spr, bpr @@ -214,6 +214,29 @@ print_whitespace: func // jmp _end +newline: + push bpr + mov spr, bpr + + ldw display, rg0 + ldw current, rg1 + + sub rg1, rg0, rg2 // offset from base + lli 80, rg3 + +_newline_mod: + ilt rg2, rg3, rg4 + jnz rg4, _newline_done + sub rg2, rg3, rg2 + jmp _newline_mod + +_newline_done: + // rg2 = offset % 80 (position within current line) + sub rg1, rg2, rg1 // go back to line start + addi rg1, 80, rg1 // advance to next line + + jmp _end + // ------------------------------------------ // prints arg[0] as a decimal number to the screen. // print_num: diff --git a/dsa_resources/serial.dsa b/dsa_resources/serial.dsa new file mode 100644 index 0000000..66b756d --- /dev/null +++ b/dsa_resources/serial.dsa @@ -0,0 +1,63 @@ +// lib: +// serial.dsa +// +// usage: +// include serial "" +// +// push (register containing address of string) +// call serial::print +// +// push (register containing byte) +// call serial::print_byte + +dw uart: 0x40000 + +// ------------------------------------------ +// transmit a single byte via sentinel protocol +// expects: byte in rg0, trashes rg2, rg3, rg4, rg5 +_serial_send: + shl rg0, 16, rg2 // shift byte to bits [23:16] + lli 0x01, rg0 + shl rg0, 24, rg0 // valid flag in bits [31:24] + or rg0, rg2, rg2 // rg2 = 0x01_XX_0000 + + ldw uart, rg5 // rg5 = 0x40000 (uart address, held for store) + +_serial_wait: + ldw rg5, rg0 // poll mem[0x40000] + shr rg0, 24, rg0 + lli 0x01, rg3 + ieq rg0, rg3, rg4 + jnz rg4, _serial_wait // loop while valid == 1 + + stw rg2, rg5 // mem[0x40000] = sentinel word + ret + +// ------------------------------------------ +// prints the null-terminated string at addr(arg[0]) +print: func + ldw bpr, rg0, 8 // load string address from arg + +_serial_print_loop: + ldb rg0, rg2 // load byte + ieq rg2, zero, rg4 // null terminator? + jnz rg4, _serial_end + + push rg0 // save string pointer + mov rg2, rg0 + call _serial_send + pop rg0 + + addi rg0, 1 + jmp _serial_print_loop + +// ------------------------------------------ +// prints the last byte of arg[0] +print_byte: func + ldw bpr, rg0, 8 + call _serial_send + jmp _serial_end + +// ------------------------------------------ +_serial_end: + return diff --git a/dsa_resources/serialtest.dsa b/dsa_resources/serialtest.dsa new file mode 100644 index 0000000..83a5036 --- /dev/null +++ b/dsa_resources/serialtest.dsa @@ -0,0 +1,14 @@ +include serial: "./serial.dsa" + +dw stack: 0x1000 +_init: + ldw stack, bpr + mov bpr, spr + +db hello: "Serial works lol. this took ages and some clanker forgetting to dereference a pointer." +main: + lwi hello, rg0 + push rg0 + call serial::print + pop zero + hlt