wrote instruction logic, just need a new assembler now :)
This commit is contained in:
@@ -20,3 +20,13 @@ serde = { version = "1.0.228", features = ["derive"] }
|
||||
[[bench]]
|
||||
name = "bench_mainstore"
|
||||
harness = false
|
||||
|
||||
[features]
|
||||
default = ["mainstore-bulkalloc", "mainstore-arraymap"]
|
||||
|
||||
# Memory Bank Features
|
||||
mainstore-bulkalloc = [] # Fastest for Writes
|
||||
mainstore-prealloc = [] # Fastest for Reads
|
||||
mainstore-stackarray = [] # Slightly outperforms ArrayMap but requires a large stack
|
||||
mainstore-arraymap = [] # Simple implementation
|
||||
mainstore-hashmap = [] # Old implementation, no raw pointers. Uses a hashmap
|
||||
|
||||
@@ -4,21 +4,16 @@ use criterion::{
|
||||
};
|
||||
use std::time::Duration;
|
||||
|
||||
use dsa::{FastStore, MainStore, RandomAccessMemory};
|
||||
use dsa::RandomAccessMemory;
|
||||
|
||||
// ── reproduce the trait and PhysAddr here, or import from your crate ─────────
|
||||
type PhysAddr = u32;
|
||||
|
||||
// ── address generators ────────────────────────────────────────────────────────
|
||||
|
||||
/// Cycles through a small set of addresses — stays hot in L1/L2.
|
||||
/// Simulates a tight inner loop hitting the same working set repeatedly.
|
||||
fn sequential_addrs(n: usize, max_addr: u32) -> Vec<PhysAddr> {
|
||||
(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> {
|
||||
let mut addrs = Vec::with_capacity(n);
|
||||
let mut x: u32 = 0xdeadbeef;
|
||||
@@ -29,21 +24,30 @@ fn random_addrs(n: usize, max_addr: u32) -> Vec<PhysAddr> {
|
||||
addrs
|
||||
}
|
||||
|
||||
/// Walks addresses page by page — tests page-boundary crossing behaviour.
|
||||
fn page_stride_addrs(n: usize) -> Vec<PhysAddr> {
|
||||
(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>,
|
||||
name: &str,
|
||||
mem: &M,
|
||||
addrs: &[PhysAddr],
|
||||
implementations: &mut [(&str, Box<dyn RandomAccessMemory>)],
|
||||
) {
|
||||
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(|| {
|
||||
let mut sum: u32 = 0;
|
||||
for &addr in addrs {
|
||||
@@ -53,15 +57,16 @@ fn bench_read_byte<M: RandomAccessMemory>(
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn bench_write_byte<M: RandomAccessMemory>(
|
||||
fn run_write_byte(
|
||||
group: &mut BenchmarkGroup<WallTime>,
|
||||
name: &str,
|
||||
mem: &mut M,
|
||||
addrs: &[PhysAddr],
|
||||
implementations: &mut [(&str, Box<dyn RandomAccessMemory>)],
|
||||
) {
|
||||
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(|| {
|
||||
for (i, &addr) in addrs.iter().enumerate() {
|
||||
mem.write_byte(black_box(addr), black_box(i as u8));
|
||||
@@ -69,15 +74,16 @@ fn bench_write_byte<M: RandomAccessMemory>(
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn bench_read_word<M: RandomAccessMemory>(
|
||||
fn run_read_word(
|
||||
group: &mut BenchmarkGroup<WallTime>,
|
||||
name: &str,
|
||||
mem: &M,
|
||||
addrs: &[PhysAddr],
|
||||
implementations: &mut [(&str, Box<dyn RandomAccessMemory>)],
|
||||
) {
|
||||
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(|| {
|
||||
let mut sum: u32 = 0;
|
||||
for &addr in addrs {
|
||||
@@ -87,15 +93,16 @@ fn bench_read_word<M: RandomAccessMemory>(
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn bench_write_word<M: RandomAccessMemory>(
|
||||
fn run_write_word(
|
||||
group: &mut BenchmarkGroup<WallTime>,
|
||||
name: &str,
|
||||
mem: &mut M,
|
||||
addrs: &[PhysAddr],
|
||||
implementations: &mut [(&str, Box<dyn RandomAccessMemory>)],
|
||||
) {
|
||||
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(|| {
|
||||
for (i, &addr) in addrs.iter().enumerate() {
|
||||
mem.write_word(black_box(addr), black_box(i as u32));
|
||||
@@ -103,35 +110,39 @@ fn bench_write_word<M: RandomAccessMemory>(
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn bench_read_page<M: RandomAccessMemory>(
|
||||
fn run_read_page(
|
||||
group: &mut BenchmarkGroup<WallTime>,
|
||||
name: &str,
|
||||
mem: &M,
|
||||
addrs: &[PhysAddr],
|
||||
implementations: &mut [(&str, Box<dyn RandomAccessMemory>)],
|
||||
) {
|
||||
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(|| {
|
||||
let mut sum: u8 = 0;
|
||||
let mut sum: u64 = 0;
|
||||
for &addr in addrs {
|
||||
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)
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn bench_write_page<M: RandomAccessMemory>(
|
||||
fn run_write_page(
|
||||
group: &mut BenchmarkGroup<WallTime>,
|
||||
name: &str,
|
||||
mem: &mut M,
|
||||
addrs: &[PhysAddr],
|
||||
implementations: &mut [(&str, Box<dyn RandomAccessMemory>)],
|
||||
) {
|
||||
let page_data = [0xABu8; 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(|| {
|
||||
for &addr in addrs {
|
||||
mem.write_page(black_box(addr), black_box(&page_data));
|
||||
@@ -139,95 +150,58 @@ fn bench_write_page<M: RandomAccessMemory>(
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// ── 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();
|
||||
}
|
||||
// ── entry point ───────────────────────────────────────────────────────────────
|
||||
|
||||
// ── 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 ────────────────────────────────────────
|
||||
//
|
||||
// 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) {
|
||||
// TODO: replace with your implementations
|
||||
bench_implementation(c, "MainStore", MainStore::new(), 0x00FF_FFFF);
|
||||
bench_implementation(c, "FastStore", FastStore::new(), 0x00FF_FFFF);
|
||||
const N: usize = 4096;
|
||||
const MAX_ADDR: u32 = 0x00FF_FFFF;
|
||||
|
||||
let _ = c;
|
||||
let seq_addrs = sequential_addrs(N, MAX_ADDR);
|
||||
let rand_addrs = random_addrs(N, MAX_ADDR);
|
||||
let page_addrs_seq = page_stride_addrs(N);
|
||||
let page_addrs_rand = random_page_addrs(N, MAX_ADDR);
|
||||
|
||||
let mut implementations: Vec<(&str, Box<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);
|
||||
|
||||
+5
-6
@@ -1,12 +1,11 @@
|
||||
#![feature(likely_unlikely)]
|
||||
#![feature(ptr_as_ref_unchecked)]
|
||||
|
||||
pub mod args;
|
||||
mod common;
|
||||
mod memory;
|
||||
mod processor;
|
||||
|
||||
pub use {
|
||||
memory::MemoryMap,
|
||||
memory::mainstore::{FastStore, MainStore, RandomAccessMemory},
|
||||
processor::processor::Emulator,
|
||||
processor::state::SharedState,
|
||||
};
|
||||
pub use {memory::MemoryMap, processor::processor::Emulator, processor::state::SharedState};
|
||||
|
||||
pub use memory::ram::*;
|
||||
|
||||
+13
-42
@@ -1,61 +1,32 @@
|
||||
use arc_swap::ArcSwap;
|
||||
use clap::Parser;
|
||||
use std::{
|
||||
fs,
|
||||
path::PathBuf,
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{AtomicBool, AtomicU8},
|
||||
},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use dsa::{Emulator, FastStore, MainStore, MemoryMap, SharedState};
|
||||
use dsa::{BulkAllocStore, Emulator, MemoryMap, SharedState, args::DsaArgs};
|
||||
use std::{sync::Arc, thread};
|
||||
|
||||
fn main() {
|
||||
let args = DsaArgs::parse();
|
||||
|
||||
let mut emulator = Emulator::new(FastStore::new());
|
||||
|
||||
let mmap = match args.get_memory_map() {
|
||||
Some(m) => m,
|
||||
None => MemoryMap::default(),
|
||||
};
|
||||
|
||||
emulator.apply_memory_map(mmap);
|
||||
let mut emulator = Emulator::new(BulkAllocStore::new());
|
||||
emulator.apply_memory_map(args.get_memory_map().unwrap_or_default());
|
||||
|
||||
let state = emulator.state_handle();
|
||||
let runner = thread::spawn(move || emulator.run());
|
||||
|
||||
const STACK_SIZE: usize = 1024 * 1024 * 16;
|
||||
let runner = thread::Builder::new()
|
||||
.stack_size(STACK_SIZE)
|
||||
.spawn(move || emulator.run())
|
||||
.unwrap();
|
||||
|
||||
let observer = thread::spawn(|| observe(state));
|
||||
|
||||
runner.join().unwrap();
|
||||
observer.join().unwrap();
|
||||
}
|
||||
|
||||
/// todo: remove this!
|
||||
fn observe(state: Arc<SharedState>) {
|
||||
loop {
|
||||
thread::sleep(std::time::Duration::from_millis(100));
|
||||
let state = state.proc.load();
|
||||
println!("GP Registers: {:?}", state.gp_registers);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
struct DsaArgs {
|
||||
/// Memory map to use on boot.
|
||||
/// Overrides default value.
|
||||
#[arg(long = "mmap")]
|
||||
memory_map: Option<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())
|
||||
})
|
||||
println!("GP Registers: {:?}", state.registers);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,10 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
memory::{mainstore::RandomAccessMemory, mmu::MMU},
|
||||
processor::processor::Emulator,
|
||||
};
|
||||
use crate::{RandomAccessMemory, memory::mmu::MMU, processor::processor::Emulator};
|
||||
|
||||
mod cache;
|
||||
pub mod mainstore;
|
||||
pub mod mmu;
|
||||
pub mod ram;
|
||||
mod tlb;
|
||||
|
||||
pub type VirtAddr = u32;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub enum Interrupt {
|
||||
PageFault,
|
||||
ProtectionFault,
|
||||
Generic(u8),
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
use std::{sync::Arc, thread, time::Duration};
|
||||
use std::{
|
||||
hint::unlikely,
|
||||
sync::{Arc, atomic::Ordering, mpsc},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
memory::{
|
||||
FaultInfo, MemoryMap,
|
||||
mainstore::{MainStore, RandomAccessMemory},
|
||||
mmu::MMU,
|
||||
},
|
||||
processor::{
|
||||
interrupts::Interrupt,
|
||||
state::{ProcessorSnapshot, SharedState},
|
||||
},
|
||||
common::instructions::{InstructionWord, Opcode, Reg},
|
||||
memory::{FaultInfo, MemoryMap, mmu::MMU, ram::RandomAccessMemory},
|
||||
processor::{interrupts::Interrupt, state::SharedState},
|
||||
};
|
||||
|
||||
pub struct Emulator<Mem: RandomAccessMemory> {
|
||||
internal_state: ProcessorSnapshot,
|
||||
shared_state: Arc<SharedState>,
|
||||
interrupts: mpsc::Receiver<u8>,
|
||||
|
||||
// memory
|
||||
mmu: MMU,
|
||||
@@ -27,9 +27,12 @@ pub struct Emulator<Mem: RandomAccessMemory> {
|
||||
unsafe impl<Mem: RandomAccessMemory> Send for Emulator<Mem> {}
|
||||
impl<Mem: RandomAccessMemory> Emulator<Mem> {
|
||||
pub fn new(mem: Mem) -> Self {
|
||||
let (sender, receiver) = mpsc::channel::<u8>();
|
||||
|
||||
Self {
|
||||
interrupts: receiver,
|
||||
internal_state: ProcessorSnapshot::default(),
|
||||
shared_state: Arc::new(SharedState::new()),
|
||||
shared_state: Arc::new(SharedState::new(sender)),
|
||||
mmu: MMU::new(),
|
||||
mainstore: mem,
|
||||
memory_map: None,
|
||||
@@ -40,48 +43,284 @@ impl<Mem: RandomAccessMemory> Emulator<Mem> {
|
||||
self.shared_state.clone()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn mmu_mut(&mut self) -> &mut MMU {
|
||||
&mut self.mmu
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn memory_mut(&mut self) -> &mut impl RandomAccessMemory {
|
||||
&mut self.mainstore
|
||||
}
|
||||
|
||||
#[cold]
|
||||
pub fn apply_memory_map(&mut self, map: MemoryMap) -> &mut Self {
|
||||
map.apply(self);
|
||||
self.memory_map = Some(map);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
let mut clock = 0;
|
||||
|
||||
#[cold]
|
||||
fn boot(&mut self) {
|
||||
let regions = MemoryMap::default();
|
||||
|
||||
self.mmu.paging_enabled(true);
|
||||
// self.mmu.paging_enabled(true);
|
||||
MemoryMap::identity_map(&mut self.mmu, regions.regions.get(0).unwrap());
|
||||
|
||||
loop {
|
||||
thread::sleep(Duration::from_micros(1000));
|
||||
|
||||
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.running = true;
|
||||
}
|
||||
|
||||
self.internal_state.gp_registers[0] += 10;
|
||||
|
||||
if clock % 1000 == 0 {
|
||||
#[cold]
|
||||
fn update(&mut self) {
|
||||
self.shared_state
|
||||
.proc
|
||||
.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!()
|
||||
}
|
||||
}
|
||||
|
||||
#[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;
|
||||
}
|
||||
|
||||
@@ -1,37 +1,29 @@
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicBool, AtomicU8},
|
||||
mpsc,
|
||||
};
|
||||
|
||||
use crate::processor::processor::ProcessorSnapshot;
|
||||
use arc_swap::ArcSwap;
|
||||
|
||||
pub struct SharedState {
|
||||
pub proc: ArcSwap<ProcessorSnapshot>,
|
||||
|
||||
pub running: AtomicBool,
|
||||
pub interrupt: AtomicU8,
|
||||
pub update_req: AtomicBool,
|
||||
|
||||
sender: mpsc::Sender<u8>,
|
||||
}
|
||||
|
||||
impl SharedState {
|
||||
pub fn new() -> Self {
|
||||
pub fn new(sender: mpsc::Sender<u8>) -> Self {
|
||||
Self {
|
||||
proc: ArcSwap::new(Arc::new(ProcessorSnapshot::default())),
|
||||
|
||||
running: AtomicBool::new(true),
|
||||
interrupt: AtomicU8::new(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProcessorSnapshot {
|
||||
pub gp_registers: [u32; 16],
|
||||
}
|
||||
|
||||
impl Default for ProcessorSnapshot {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
gp_registers: [0; 16],
|
||||
sender: sender,
|
||||
update_req: AtomicBool::new(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user