wrote instruction logic, just need a new assembler now :)

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