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]]
|
[[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
|
||||||
|
|||||||
+139
-165
@@ -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,205 +24,184 @@ 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);
|
||||||
fn bench_read_byte<M: RandomAccessMemory>(
|
let mut x: u32 = 0xc0ffee42;
|
||||||
group: &mut BenchmarkGroup<WallTime>,
|
for _ in 0..n {
|
||||||
name: &str,
|
x = x.wrapping_mul(1664525).wrapping_add(1013904223);
|
||||||
mem: &M,
|
addrs.push((x & (max_addr - 1)) & !0xFFF);
|
||||||
addrs: &[PhysAddr],
|
}
|
||||||
) {
|
addrs
|
||||||
group.throughput(Throughput::Elements(addrs.len() as u64));
|
|
||||||
group.bench_function(name, |b| {
|
|
||||||
b.iter(|| {
|
|
||||||
let mut sum: u32 = 0;
|
|
||||||
for &addr in addrs {
|
|
||||||
sum = sum.wrapping_add(mem.read_byte(black_box(addr)) as u32);
|
|
||||||
}
|
|
||||||
black_box(sum)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bench_write_byte<M: RandomAccessMemory>(
|
// ── runners ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
fn run_read_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() {
|
||||||
b.iter(|| {
|
group.bench_with_input(BenchmarkId::new(*name, ""), addrs, |b, addrs| {
|
||||||
for (i, &addr) in addrs.iter().enumerate() {
|
b.iter(|| {
|
||||||
mem.write_byte(black_box(addr), black_box(i as u8));
|
let mut sum: u32 = 0;
|
||||||
}
|
for &addr in addrs {
|
||||||
})
|
sum = sum.wrapping_add(mem.read_byte(black_box(addr)) as u32);
|
||||||
});
|
}
|
||||||
|
black_box(sum)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bench_read_word<M: RandomAccessMemory>(
|
fn run_write_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_mut() {
|
||||||
b.iter(|| {
|
group.bench_with_input(BenchmarkId::new(*name, ""), addrs, |b, addrs| {
|
||||||
let mut sum: u32 = 0;
|
b.iter(|| {
|
||||||
for &addr in addrs {
|
for (i, &addr) in addrs.iter().enumerate() {
|
||||||
sum = sum.wrapping_add(mem.read_word(black_box(addr)));
|
mem.write_byte(black_box(addr), black_box(i as u8));
|
||||||
}
|
}
|
||||||
black_box(sum)
|
})
|
||||||
})
|
});
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bench_write_word<M: RandomAccessMemory>(
|
fn run_read_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() {
|
||||||
b.iter(|| {
|
group.bench_with_input(BenchmarkId::new(*name, ""), addrs, |b, addrs| {
|
||||||
for (i, &addr) in addrs.iter().enumerate() {
|
b.iter(|| {
|
||||||
mem.write_word(black_box(addr), black_box(i as u32));
|
let mut sum: u32 = 0;
|
||||||
}
|
for &addr in addrs {
|
||||||
})
|
sum = sum.wrapping_add(mem.read_word(black_box(addr)));
|
||||||
});
|
}
|
||||||
|
black_box(sum)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bench_read_page<M: RandomAccessMemory>(
|
fn run_write_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));
|
||||||
|
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<WallTime>,
|
||||||
|
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() {
|
||||||
b.iter(|| {
|
group.bench_with_input(BenchmarkId::new(*name, ""), addrs, |b, addrs| {
|
||||||
let mut sum: u8 = 0;
|
b.iter(|| {
|
||||||
for &addr in addrs {
|
let mut sum: u64 = 0;
|
||||||
let page = mem.read_page(black_box(addr));
|
for &addr in addrs {
|
||||||
sum = sum.wrapping_add(page[0]);
|
let page = mem.read_page(black_box(addr));
|
||||||
}
|
for chunk in page.chunks_exact(8) {
|
||||||
black_box(sum)
|
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>,
|
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() {
|
||||||
b.iter(|| {
|
group.bench_with_input(BenchmarkId::new(*name, ""), addrs, |b, addrs| {
|
||||||
for &addr in addrs {
|
b.iter(|| {
|
||||||
mem.write_page(black_box(addr), black_box(&page_data));
|
for &addr in addrs {
|
||||||
}
|
mem.write_page(black_box(addr), black_box(&page_data));
|
||||||
})
|
}
|
||||||
});
|
})
|
||||||
}
|
});
|
||||||
|
|
||||||
// ── run all access patterns for a given implementation ────────────────────────
|
|
||||||
|
|
||||||
fn bench_implementation<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
@@ -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
@@ -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())
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 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,4 +1,5 @@
|
|||||||
pub enum Interrupt {
|
pub enum Interrupt {
|
||||||
PageFault,
|
PageFault,
|
||||||
ProtectionFault,
|
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::{
|
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) {
|
#[cold]
|
||||||
Ok(addr) => {
|
fn update(&mut self) {
|
||||||
let x = self.mainstore.read_word(addr);
|
self.shared_state
|
||||||
println!("{}", x);
|
.proc
|
||||||
self.mainstore.write_word(addr, x + 1);
|
.store(Arc::new(self.internal_state.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
#[inline]
|
||||||
|
pub fn reg(&self, reg: Reg) -> u32 {
|
||||||
|
if reg as u8 == Reg::Zero as u8 {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
debug_assert!((reg as usize) < ProcessorSnapshot::REG_COUNT);
|
||||||
|
|
||||||
|
unsafe { *self.internal_state.registers.get_unchecked(reg as usize) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn mut_reg(&mut self, reg: Reg) -> &mut u32 {
|
||||||
|
debug_assert!((reg as usize) < ProcessorSnapshot::REG_COUNT);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
self.internal_state
|
||||||
|
.registers
|
||||||
|
.get_unchecked_mut(reg as usize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn execute(&mut self, word: InstructionWord) {
|
||||||
|
match Opcode::from_u8(word.opcode()) {
|
||||||
|
// Nothing
|
||||||
|
Some(Opcode::Nop) => {}
|
||||||
|
|
||||||
|
// move
|
||||||
|
Some(Opcode::Mov) => {
|
||||||
|
*self.mut_reg(word.dest()) = self.reg(word.src1());
|
||||||
|
}
|
||||||
|
Some(Opcode::CMov) => {
|
||||||
|
if self.reg(word.misc()) != 0 {
|
||||||
|
*self.mut_reg(word.dest()) = self.reg(word.src1());
|
||||||
}
|
}
|
||||||
Err(FaultInfo::PageFault) => self.interrupt(Interrupt::PageFault),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.internal_state.gp_registers[0] += 10;
|
// load
|
||||||
|
Some(Opcode::Ldbs) => todo!(),
|
||||||
if clock % 1000 == 0 {
|
Some(Opcode::Ldhs) => todo!(),
|
||||||
self.shared_state
|
Some(Opcode::Ldb) => {
|
||||||
.proc
|
*self.mut_reg(word.dest()) = u32::from(
|
||||||
.store(Arc::new(self.internal_state.clone()));
|
self.mainstore
|
||||||
|
.read_byte(self.reg(word.src1()) + word.imm16() as u32),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
clock += 1;
|
Some(Opcode::Ldh) => {
|
||||||
|
*self.mut_reg(word.dest()) = u32::from(
|
||||||
|
self.mainstore
|
||||||
|
.read_word(self.reg(word.src1()) + word.imm16() as u32)
|
||||||
|
>> 16,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Some(Opcode::Ldw) => {
|
||||||
|
*self.mut_reg(word.dest()) = u32::from(
|
||||||
|
self.mainstore
|
||||||
|
.read_word(self.reg(word.src1()) + word.imm16() as u32),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// store
|
||||||
|
Some(Opcode::Stb) => {
|
||||||
|
self.mainstore.write_byte(
|
||||||
|
self.reg(word.dest()) + word.imm16() as u32,
|
||||||
|
self.reg(word.src1()) as u8,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Some(Opcode::Sth) => {
|
||||||
|
self.mainstore.write_byte(
|
||||||
|
self.reg(word.dest()) + word.imm16() as u32,
|
||||||
|
(self.reg(word.src1()) as u16 >> 8) as u8,
|
||||||
|
);
|
||||||
|
self.mainstore.write_byte(
|
||||||
|
self.reg(word.dest()) + word.imm16() as u32 + 1,
|
||||||
|
self.reg(word.src1()) as u8,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Some(Opcode::Stw) => {
|
||||||
|
self.mainstore.write_word(
|
||||||
|
self.reg(word.dest()) + word.imm16() as u32,
|
||||||
|
self.reg(word.src1()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// load immediate
|
||||||
|
Some(Opcode::Lli) => {
|
||||||
|
*self.mut_reg(word.dest()) = word.imm16() as u32;
|
||||||
|
}
|
||||||
|
Some(Opcode::Lui) => {
|
||||||
|
*self.mut_reg(word.dest()) = (word.imm16() as u32) << 16 & self.reg(word.dest());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comparison
|
||||||
|
Some(Opcode::Ieq) => {
|
||||||
|
*self.mut_reg(word.dest()) =
|
||||||
|
(self.reg(word.src1()) == self.reg(word.src2())) as u32;
|
||||||
|
}
|
||||||
|
Some(Opcode::Ine) => {
|
||||||
|
*self.mut_reg(word.dest()) =
|
||||||
|
(self.reg(word.src1()) != self.reg(word.src2())) as u32;
|
||||||
|
}
|
||||||
|
Some(Opcode::Ilt) => {
|
||||||
|
*self.mut_reg(word.dest()) = (self.reg(word.src1()) < self.reg(word.src2())) as u32;
|
||||||
|
}
|
||||||
|
Some(Opcode::Ile) => {
|
||||||
|
*self.mut_reg(word.dest()) =
|
||||||
|
(self.reg(word.src1()) <= self.reg(word.src2())) as u32;
|
||||||
|
}
|
||||||
|
Some(Opcode::Igt) => {
|
||||||
|
*self.mut_reg(word.dest()) = (self.reg(word.src1()) > self.reg(word.src2())) as u32;
|
||||||
|
}
|
||||||
|
Some(Opcode::Ige) => {
|
||||||
|
*self.mut_reg(word.dest()) =
|
||||||
|
(self.reg(word.src1()) >= self.reg(word.src2())) as u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jump
|
||||||
|
Some(Opcode::Jmp) => *self.mut_reg(Reg::Pcx) = self.reg(word.dest()),
|
||||||
|
|
||||||
|
Some(Opcode::Jez) => {
|
||||||
|
if self.reg(word.src1()) == 0 {
|
||||||
|
*self.mut_reg(Reg::Pcx) = self.reg(word.dest())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(Opcode::Jnz) => {
|
||||||
|
if self.reg(word.src1()) != 0 {
|
||||||
|
*self.mut_reg(Reg::Pcx) = self.reg(word.dest())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(Opcode::Jnc) => todo!(),
|
||||||
|
Some(Opcode::Jic) => todo!(),
|
||||||
|
|
||||||
|
// Bitwise
|
||||||
|
Some(Opcode::And) => {
|
||||||
|
*self.mut_reg(word.dest()) = self.reg(word.src1()) & self.reg(word.src2());
|
||||||
|
}
|
||||||
|
Some(Opcode::Nand) => {
|
||||||
|
*self.mut_reg(word.dest()) = !(self.reg(word.src1()) & self.reg(word.src2()));
|
||||||
|
}
|
||||||
|
Some(Opcode::Or) => {
|
||||||
|
*self.mut_reg(word.dest()) = self.reg(word.src1()) | self.reg(word.src2());
|
||||||
|
}
|
||||||
|
Some(Opcode::Nor) => {
|
||||||
|
*self.mut_reg(word.dest()) = !(self.reg(word.src1()) | self.reg(word.src2()));
|
||||||
|
}
|
||||||
|
Some(Opcode::Xor) => {
|
||||||
|
*self.mut_reg(word.dest()) = self.reg(word.src1()) ^ self.reg(word.src2());
|
||||||
|
}
|
||||||
|
Some(Opcode::Xnor) => {
|
||||||
|
*self.mut_reg(word.dest()) = !(self.reg(word.src1()) ^ self.reg(word.src2()));
|
||||||
|
}
|
||||||
|
Some(Opcode::Not) => {
|
||||||
|
*self.mut_reg(word.dest()) = !self.reg(word.src1());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arithmetic
|
||||||
|
Some(Opcode::Add) => {
|
||||||
|
*self.mut_reg(word.dest()) = self.reg(word.src1()) + self.reg(word.src2());
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Opcode::Sub) => {
|
||||||
|
*self.mut_reg(word.dest()) = self.reg(word.src1()) - self.reg(word.src2());
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Opcode::Shl) => {
|
||||||
|
*self.mut_reg(word.dest()) =
|
||||||
|
self.reg(word.src1()) << (self.reg(word.src2()) + word.shamt() as u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Opcode::Shr) => {
|
||||||
|
*self.mut_reg(word.dest()) =
|
||||||
|
self.reg(word.src1()) >> (self.reg(word.src2()) + word.shamt() as u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Opcode::Addi) => {
|
||||||
|
*self.mut_reg(word.dest()) = self.reg(word.src1()) + word.imm16() as u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Opcode::Subi) => {
|
||||||
|
*self.mut_reg(word.dest()) = self.reg(word.src1()) - word.imm16() as u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Opcode::Int) => {
|
||||||
|
self.interrupt(Interrupt::Generic(word.shamt()));
|
||||||
|
}
|
||||||
|
Some(Opcode::Hlt) => self.internal_state.running = false,
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn waiting(&mut self) {
|
||||||
|
self.internal_state.running = false;
|
||||||
|
|
||||||
|
// Wait for an interrupt or state update to continue.
|
||||||
|
loop {
|
||||||
|
// Check for interrupts.
|
||||||
|
if let Ok(code) = self.interrupts.recv_timeout(Duration::from_millis(100)) {
|
||||||
|
self.interrupt(Interrupt::Generic(code));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UI requested a state update.
|
||||||
|
if self.shared_state.update_req.load(Ordering::Relaxed) {
|
||||||
|
self.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we've received a request to continue running.
|
||||||
|
if self.shared_state.running.load(Ordering::Relaxed) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.internal_state.running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(&mut self) {
|
||||||
|
self.boot();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// Update UI thread (roughly every 512k cycles targeting UPS)
|
||||||
|
if unlikely(self.internal_state.clock & 0x7FFFF == 0) {
|
||||||
|
if self.shared_state.update_req.load(Ordering::Relaxed) {
|
||||||
|
self.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for commands or hardware Interrupts every 32k cycles
|
||||||
|
if self.internal_state.clock % 0x7FFF == 0 {
|
||||||
|
if self.shared_state.running.load(Ordering::Relaxed) == false {
|
||||||
|
self.waiting();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(code) = self.interrupts.try_recv() {
|
||||||
|
self.interrupt(Interrupt::Generic(code));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// let instruction = self.mmu.lookup(self.internal_state.reg(Reg::Pcx));
|
||||||
|
let pc = self.reg(Reg::Pcx);
|
||||||
|
let instruction = self.memory_mut().read_word(pc);
|
||||||
|
self.execute(InstructionWord(instruction));
|
||||||
|
|
||||||
|
// always increment clock.
|
||||||
|
self.internal_state.clock += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,3 +328,24 @@ impl<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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user