Merge remote-tracking branch 'refs/remotes/origin/main'
This commit is contained in:
Generated
+10
@@ -1,8 +1,18 @@
|
|||||||
# Default ignored files
|
# Default ignored files
|
||||||
/shelf/
|
/shelf/
|
||||||
/workspace.xml
|
/workspace.xml
|
||||||
|
<<<<<<< HEAD
|
||||||
# Editor-based HTTP Client requests
|
# Editor-based HTTP Client requests
|
||||||
/httpRequests/
|
/httpRequests/
|
||||||
# Datasource local storage ignored files
|
# Datasource local storage ignored files
|
||||||
/dataSources/
|
/dataSources/
|
||||||
/dataSources.local.xml
|
/dataSources.local.xml
|
||||||
|
=======
|
||||||
|
# Ignored default folder with query files
|
||||||
|
/queries/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
>>>>>>> refs/remotes/origin/main
|
||||||
|
|||||||
@@ -23,38 +23,46 @@ pub enum Register {
|
|||||||
Rgf = 15,
|
Rgf = 15,
|
||||||
|
|
||||||
// special purpose
|
// special purpose
|
||||||
Zero = 16,
|
Acc = 16,
|
||||||
Acc = 17,
|
Spr = 17,
|
||||||
Spr = 18,
|
Bpr = 18,
|
||||||
Bpr = 19,
|
Ret = 19,
|
||||||
Ret = 20,
|
Idr = 20,
|
||||||
Idr = 21,
|
Mmr = 21,
|
||||||
Mmr = 22,
|
|
||||||
|
|
||||||
// system - read only
|
// system - read only
|
||||||
Mar = 23,
|
Zero = 22,
|
||||||
Mdr = 24,
|
Sts = 23,
|
||||||
Sts = 25,
|
Cir = 24,
|
||||||
Cir = 26,
|
Pcx = 25,
|
||||||
Pcx = 27,
|
|
||||||
|
|
||||||
#[default]
|
#[default]
|
||||||
Null = 28,
|
Null = 26,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Register {
|
impl Register {
|
||||||
|
/// Total number of registers, not counting Null as it's not a real register.
|
||||||
|
/// reading or writing to Null is undefined behaviour.
|
||||||
|
pub const COUNT: u8 = Self::Null as u8;
|
||||||
|
|
||||||
|
/// # Safety
|
||||||
|
/// the caller must ensure that the index is always within the range 0..COUNT, failing to do so will result in undefined behaviour.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub unsafe fn from_u8_unchecked(idx: u8) -> Self {
|
pub unsafe fn from_u8_unchecked(idx: u8) -> Self {
|
||||||
debug_assert!(idx <= Self::Null as u8);
|
debug_assert!(
|
||||||
|
idx < Self::COUNT,
|
||||||
|
"Tried to access a null register using the unchecked method. undefined behaviour alert!"
|
||||||
|
);
|
||||||
unsafe { std::mem::transmute(idx) }
|
unsafe { std::mem::transmute(idx) }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn from_u8(idx: u8) -> Result<Self, ()> {
|
pub fn from_u8(idx: u8) -> Result<Self, ()> {
|
||||||
if idx > 28 {
|
if idx >= Self::COUNT {
|
||||||
return Err(());
|
return Err(());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(unsafe { Self::from_u8_unchecked(idx) })
|
Ok(unsafe { Self::from_u8_unchecked(idx) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -84,7 +92,6 @@ impl fmt::Display for Register {
|
|||||||
Self::Rgf => "Rgf",
|
Self::Rgf => "Rgf",
|
||||||
|
|
||||||
// special purpose
|
// special purpose
|
||||||
Self::Zero => "Zero",
|
|
||||||
Self::Acc => "Acc",
|
Self::Acc => "Acc",
|
||||||
Self::Spr => "Spr",
|
Self::Spr => "Spr",
|
||||||
Self::Bpr => "Bpr",
|
Self::Bpr => "Bpr",
|
||||||
@@ -93,8 +100,7 @@ impl fmt::Display for Register {
|
|||||||
Self::Mmr => "Mmr",
|
Self::Mmr => "Mmr",
|
||||||
|
|
||||||
// system - read only
|
// system - read only
|
||||||
Self::Mar => "Mar",
|
Self::Zero => "Zero",
|
||||||
Self::Mdr => "Mdr",
|
|
||||||
Self::Sts => "Sts",
|
Self::Sts => "Sts",
|
||||||
Self::Cir => "Cir",
|
Self::Cir => "Cir",
|
||||||
Self::Pcx => "Pcx",
|
Self::Pcx => "Pcx",
|
||||||
@@ -129,7 +135,6 @@ impl std::str::FromStr for Register {
|
|||||||
"rgf" => Ok(Self::Rgf),
|
"rgf" => Ok(Self::Rgf),
|
||||||
|
|
||||||
// special purpose
|
// special purpose
|
||||||
"zero" => Ok(Self::Zero),
|
|
||||||
"acc" => Ok(Self::Acc),
|
"acc" => Ok(Self::Acc),
|
||||||
"spr" => Ok(Self::Spr),
|
"spr" => Ok(Self::Spr),
|
||||||
"bpr" => Ok(Self::Bpr),
|
"bpr" => Ok(Self::Bpr),
|
||||||
@@ -138,8 +143,7 @@ impl std::str::FromStr for Register {
|
|||||||
"mmr" => Ok(Self::Mmr),
|
"mmr" => Ok(Self::Mmr),
|
||||||
|
|
||||||
// system - read only
|
// system - read only
|
||||||
"mar" => Ok(Self::Mar),
|
"zero" => Ok(Self::Zero),
|
||||||
"mdr" => Ok(Self::Mdr),
|
|
||||||
"sts" => Ok(Self::Sts),
|
"sts" => Ok(Self::Sts),
|
||||||
"cir" => Ok(Self::Cir),
|
"cir" => Ok(Self::Cir),
|
||||||
"pcx" => Ok(Self::Pcx),
|
"pcx" => Ok(Self::Pcx),
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "emulator"
|
name = "emulator"
|
||||||
version = "0.1.0"
|
version = "0.3.0"
|
||||||
edition = "2024"
|
edition.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "dsa"
|
name = "dsa"
|
||||||
@@ -22,15 +23,14 @@ ron = "0.12.0"
|
|||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
|
|
||||||
common = { path = "../common" }
|
common = { path = "../common" }
|
||||||
|
egui = "0.33.3"
|
||||||
|
eframe = "0.33.3"
|
||||||
|
egui_tiles = "0.14.1"
|
||||||
#assembler = { path = "../assembler" }
|
#assembler = { path = "../assembler" }
|
||||||
|
|
||||||
|
|
||||||
[[bench]]
|
|
||||||
name = "bench_mainstore"
|
|
||||||
harness = false
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["mainstore-bulkalloc"]
|
default = ["mainstore-prealloc"]
|
||||||
|
|
||||||
# Memory Bank Features
|
# Memory Bank Features
|
||||||
mainstore-bulkalloc = [] # Fastest for Writes
|
mainstore-bulkalloc = [] # Fastest for Writes
|
||||||
|
|||||||
@@ -1,208 +0,0 @@
|
|||||||
use criterion::{
|
|
||||||
BenchmarkGroup, BenchmarkId, Criterion, Throughput, black_box, criterion_group, criterion_main,
|
|
||||||
measurement::WallTime,
|
|
||||||
};
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use dsa::{Page, RandomAccessMemory};
|
|
||||||
|
|
||||||
type PhysAddr = u32;
|
|
||||||
|
|
||||||
// ── address generators ────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
fn sequential_addrs(n: usize, max_addr: u32) -> Vec<PhysAddr> {
|
|
||||||
(0..n).map(|i| ((i as u32 * 4) & (max_addr - 1))).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn random_addrs(n: usize, max_addr: u32) -> Vec<PhysAddr> {
|
|
||||||
let mut addrs = Vec::with_capacity(n);
|
|
||||||
let mut x: u32 = 0xdeadbeef;
|
|
||||||
for _ in 0..n {
|
|
||||||
x = x.wrapping_mul(1664525).wrapping_add(1013904223);
|
|
||||||
addrs.push((x & (max_addr - 1)) & !3);
|
|
||||||
}
|
|
||||||
addrs
|
|
||||||
}
|
|
||||||
|
|
||||||
fn page_stride_addrs(n: usize) -> Vec<PhysAddr> {
|
|
||||||
(0..n).map(|i| (i as u32 * 4096) & 0x00FF_FFFF).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── runners ───────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
fn run_read_byte(
|
|
||||||
group: &mut BenchmarkGroup<WallTime>,
|
|
||||||
addrs: &[PhysAddr],
|
|
||||||
implementations: &mut [(&str, Box<dyn RandomAccessMemory>)],
|
|
||||||
) {
|
|
||||||
group.throughput(Throughput::Elements(addrs.len() as u64));
|
|
||||||
for (name, mem) in implementations.iter() {
|
|
||||||
group.bench_with_input(BenchmarkId::new(*name, ""), addrs, |b, addrs| {
|
|
||||||
b.iter(|| {
|
|
||||||
let mut sum: u32 = 0;
|
|
||||||
for &addr in addrs {
|
|
||||||
sum = sum.wrapping_add(mem.read_byte(black_box(addr)) as u32);
|
|
||||||
}
|
|
||||||
black_box(sum)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_write_byte(
|
|
||||||
group: &mut BenchmarkGroup<WallTime>,
|
|
||||||
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_byte(black_box(addr), black_box(i as u8));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_read_word(
|
|
||||||
group: &mut BenchmarkGroup<WallTime>,
|
|
||||||
addrs: &[PhysAddr],
|
|
||||||
implementations: &mut [(&str, Box<dyn RandomAccessMemory>)],
|
|
||||||
) {
|
|
||||||
group.throughput(Throughput::Elements(addrs.len() as u64));
|
|
||||||
for (name, mem) in implementations.iter() {
|
|
||||||
group.bench_with_input(BenchmarkId::new(*name, ""), addrs, |b, addrs| {
|
|
||||||
b.iter(|| {
|
|
||||||
let mut sum: u32 = 0;
|
|
||||||
for &addr in addrs {
|
|
||||||
sum = sum.wrapping_add(mem.read_word(black_box(addr)));
|
|
||||||
}
|
|
||||||
black_box(sum)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_write_word(
|
|
||||||
group: &mut BenchmarkGroup<WallTime>,
|
|
||||||
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));
|
|
||||||
for (name, mem) in implementations.iter() {
|
|
||||||
group.bench_with_input(BenchmarkId::new(*name, ""), addrs, |b, addrs| {
|
|
||||||
b.iter(|| {
|
|
||||||
let mut sum: u64 = 0;
|
|
||||||
for &addr in addrs {
|
|
||||||
let page = mem.read_page(black_box(addr));
|
|
||||||
for chunk in page.chunks_exact(8) {
|
|
||||||
sum = sum.wrapping_add(u64::from_le_bytes(chunk.try_into().unwrap()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
black_box(sum)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_write_page(
|
|
||||||
group: &mut BenchmarkGroup<WallTime>,
|
|
||||||
addrs: &[PhysAddr],
|
|
||||||
implementations: &mut [(&str, Box<dyn RandomAccessMemory>)],
|
|
||||||
) {
|
|
||||||
let page_data = Page::from([0xABu8; 4096]);
|
|
||||||
group.throughput(Throughput::Bytes(addrs.len() as u64 * 4096));
|
|
||||||
for (name, mem) in implementations.iter_mut() {
|
|
||||||
group.bench_with_input(BenchmarkId::new(*name, ""), addrs, |b, addrs| {
|
|
||||||
b.iter(|| {
|
|
||||||
for &addr in addrs {
|
|
||||||
mem.write_page(black_box(addr), black_box(&page_data));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── entry point ───────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
fn benchmarks(c: &mut Criterion) {
|
|
||||||
const N: usize = 4096;
|
|
||||||
const MAX_ADDR: u32 = 0x00FF_FFFF;
|
|
||||||
|
|
||||||
let seq_addrs = sequential_addrs(N, MAX_ADDR);
|
|
||||||
let rand_addrs = random_addrs(N, MAX_ADDR);
|
|
||||||
let page_addrs_seq = page_stride_addrs(N);
|
|
||||||
let page_addrs_rand = random_page_addrs(N, MAX_ADDR);
|
|
||||||
|
|
||||||
let mut implementations: Vec<(&str, Box<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_main!(benches);
|
|
||||||
Binary file not shown.
@@ -1,9 +1,8 @@
|
|||||||
use std::{fs, path::PathBuf};
|
use std::{fs, path::PathBuf};
|
||||||
|
|
||||||
use clap::{Parser, ValueEnum};
|
use clap::Parser;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::{MemoryMap, RandomAccessMemory, config::IoMapping, io::DeviceId};
|
use crate::config::{IoMapping, MemoryMap};
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(version, about, long_about = None)]
|
#[command(version, about, long_about = None)]
|
||||||
@@ -18,9 +17,6 @@ pub struct DsaArgs {
|
|||||||
|
|
||||||
#[arg(long = "iomap")]
|
#[arg(long = "iomap")]
|
||||||
io_map: Option<PathBuf>,
|
io_map: Option<PathBuf>,
|
||||||
|
|
||||||
#[arg(value_enum, long = "mem", default_value = "bulk-alloc")]
|
|
||||||
pub memory_bank: MemoryBank,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DsaArgs {
|
impl DsaArgs {
|
||||||
@@ -56,18 +52,3 @@ impl DsaArgs {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, ValueEnum, Default)]
|
|
||||||
pub enum MemoryBank {
|
|
||||||
#[cfg(feature = "mainstore-bulkalloc")]
|
|
||||||
#[default]
|
|
||||||
BulkAlloc,
|
|
||||||
#[cfg(feature = "mainstore-arraymap")]
|
|
||||||
ArrayMap,
|
|
||||||
#[cfg(feature = "mainstore-hashmap")]
|
|
||||||
HashMap,
|
|
||||||
#[cfg(feature = "mainstore-prealloc")]
|
|
||||||
PreAlloc,
|
|
||||||
#[cfg(feature = "mainstore-stackarray")]
|
|
||||||
StackArray,
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::memory::{FaultInfo, PhysAddr, VirtAddr, tlb::TLB};
|
use crate::backend::memory::{FaultInfo, PhysAddr, VirtAddr, tlb::TLB};
|
||||||
|
|
||||||
pub struct MMU {
|
pub struct MMU {
|
||||||
buffer: TLB,
|
buffer: TLB,
|
||||||
@@ -1,7 +1,3 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::{RandomAccessMemory, memory::mmu::MMU, processor::processor::Emulator};
|
|
||||||
|
|
||||||
mod cache;
|
mod cache;
|
||||||
pub mod mmu;
|
pub mod mmu;
|
||||||
pub mod ram;
|
pub mod ram;
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
use std::{
|
||||||
|
alloc::{Layout, alloc_zeroed, dealloc},
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "mainstore-arraymap")]
|
||||||
|
pub mod arraymap;
|
||||||
|
#[cfg(feature = "mainstore-arraymap")]
|
||||||
|
pub use arraymap::ArrayStore;
|
||||||
|
|
||||||
|
#[cfg(feature = "mainstore-hashmap")]
|
||||||
|
pub mod hashmap;
|
||||||
|
#[cfg(feature = "mainstore-hashmap")]
|
||||||
|
pub use hashmap::HashStore;
|
||||||
|
|
||||||
|
#[cfg(feature = "mainstore-stackarray")]
|
||||||
|
pub mod stackarray;
|
||||||
|
#[cfg(feature = "mainstore-stackarray")]
|
||||||
|
pub use stackarray::StackArrayStore;
|
||||||
|
|
||||||
|
#[cfg(feature = "mainstore-bulkalloc")]
|
||||||
|
pub mod bulkalloc;
|
||||||
|
#[cfg(feature = "mainstore-bulkalloc")]
|
||||||
|
pub use bulkalloc::BulkAllocStore;
|
||||||
|
|
||||||
|
// #[cfg(feature = "mainstore-prealloc")]
|
||||||
|
// pub mod prealloc;
|
||||||
|
// #[cfg(feature = "mainstore-prealloc")]
|
||||||
|
// pub use prealloc::PreAllocStore;
|
||||||
|
|
||||||
|
use crate::backend::memory::PhysAddr;
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Page([u8; 4096]);
|
||||||
|
|
||||||
|
impl Page {
|
||||||
|
pub const SIZE: usize = 4096;
|
||||||
|
pub const COUNT: usize = u32::MAX as usize / Self::SIZE + 1;
|
||||||
|
|
||||||
|
pub const MASK: u32 = 0xFFF;
|
||||||
|
pub const ZERO: Self = Page::zeroed();
|
||||||
|
|
||||||
|
pub const fn zeroed() -> Self {
|
||||||
|
Self([0; 4096])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn from(data: [u8; 4096]) -> Self {
|
||||||
|
Self(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_word(&self, offset: usize) -> u32 {
|
||||||
|
u32::from_le_bytes(self.0[offset..offset + 4].try_into().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_word(&mut self, offset: usize, value: u32) {
|
||||||
|
self.0[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn paginate(data: &[u8]) -> impl Iterator<Item = Page> + '_ {
|
||||||
|
data.chunks(4096).map(|chunk| {
|
||||||
|
let mut arr = [0u8; 4096];
|
||||||
|
arr[..chunk.len()].copy_from_slice(chunk);
|
||||||
|
Page(arr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Page {
|
||||||
|
type Target = [u8];
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for Page {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Vec<u8>> for Page {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(data: Vec<u8>) -> Result<Self, Self::Error> {
|
||||||
|
let arr: [u8; 4096] = data
|
||||||
|
.try_into()
|
||||||
|
.map_err(|v: Vec<u8>| format!("Expected 4096 bytes, got {}", v.len()))?;
|
||||||
|
Ok(Page(arr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
const fn idx(addr: PhysAddr) -> (usize, usize) {
|
||||||
|
((addr >> 12) as usize, (addr & 0xFFF) as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MemoryBank {
|
||||||
|
heap: *mut u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for MemoryBank {}
|
||||||
|
unsafe impl Sync for MemoryBank {}
|
||||||
|
impl MemoryBank {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
heap: unsafe {
|
||||||
|
// allocate the full 32 bit address range.
|
||||||
|
alloc_zeroed(Layout::from_size_align(u32::MAX as usize, 4096).unwrap())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dealloc(&self) {
|
||||||
|
unsafe {
|
||||||
|
dealloc(
|
||||||
|
self.heap,
|
||||||
|
Layout::from_size_align(u32::MAX as usize, 4096).unwrap(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
std::ptr::write_bytes(self.heap, 0, u32::MAX as usize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn read_word(&self, addr: PhysAddr) -> u32 {
|
||||||
|
unsafe { (self.heap.add(addr as usize) as *const u32).read_volatile() }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn read_byte(&self, addr: PhysAddr) -> u8 {
|
||||||
|
unsafe { self.heap.add(addr as usize).read_volatile() }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn read_page(&self, addr: PhysAddr) -> &Page {
|
||||||
|
debug_assert_eq!(addr % 4096, 0);
|
||||||
|
unsafe { (self.heap.add(addr as usize) as *const Page).as_ref_unchecked() }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn write_byte(&self, addr: PhysAddr, value: u8) {
|
||||||
|
unsafe { self.heap.add(addr as usize).write_volatile(value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn write_word(&self, addr: PhysAddr, value: u32) {
|
||||||
|
unsafe { (self.heap.add(addr as usize) as *mut u32).write_volatile(value) };
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn write_page(&self, addr: PhysAddr, value: &Page) {
|
||||||
|
unsafe {
|
||||||
|
(self.heap.add(addr as usize) as *mut Page).copy_from(value, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for MemoryBank {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self { heap: self.heap }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::memory::{PhysAddr, VirtAddr};
|
use crate::backend::memory::{PhysAddr, VirtAddr};
|
||||||
|
|
||||||
const TLB_SIZE: usize = 256;
|
const TLB_SIZE: usize = 256;
|
||||||
const TLB_MASK: u32 = (TLB_SIZE - 1) as u32;
|
const TLB_MASK: u32 = (TLB_SIZE - 1) as u32;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod memory;
|
||||||
|
pub mod processor;
|
||||||
+96
-188
@@ -13,14 +13,20 @@ use std::{
|
|||||||
use common::isa::instructions::{Instruction, Opcode};
|
use common::isa::instructions::{Instruction, Opcode};
|
||||||
use common::isa::register::Register;
|
use common::isa::register::Register;
|
||||||
use crate::{
|
use crate::{
|
||||||
|
<<<<<<< HEAD:dsa/emulator/src/processor/processor.rs
|
||||||
config::{IoMapping, MemoryMap, RegionType},
|
config::{IoMapping, MemoryMap, RegionType},
|
||||||
io::{IoDevice, MappedDevice},
|
io::{IoDevice, MappedDevice},
|
||||||
memory::{mmu::MMU, ram::RandomAccessMemory},
|
memory::{mmu::MMU, ram::RandomAccessMemory},
|
||||||
processor::{interrupts::Interrupt, state::SharedState},
|
processor::{interrupts::Interrupt, state::SharedState},
|
||||||
Page,
|
Page,
|
||||||
|
=======
|
||||||
|
MemoryBank, Page, SharedState,
|
||||||
|
backend::{memory::mmu::MMU, processor::interrupts::Interrupt},
|
||||||
|
config::{IoMapping, MemoryMap, RegionType},
|
||||||
|
>>>>>>> refs/remotes/origin/main:dsa/emulator/src/backend/processor/processor.rs
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Emulator<Mem: RandomAccessMemory> {
|
pub struct Emulator {
|
||||||
internal_state: ProcessorSnapshot,
|
internal_state: ProcessorSnapshot,
|
||||||
shared_state: Arc<SharedState>,
|
shared_state: Arc<SharedState>,
|
||||||
|
|
||||||
@@ -30,10 +36,9 @@ pub struct Emulator<Mem: RandomAccessMemory> {
|
|||||||
|
|
||||||
// memory
|
// memory
|
||||||
mmu: MMU,
|
mmu: MMU,
|
||||||
mainstore: Mem,
|
mainstore: MemoryBank,
|
||||||
|
|
||||||
// IO
|
// IO
|
||||||
mmio: Vec<MappedDevice>,
|
|
||||||
mmio_region: Option<(u32, u32)>, // start end end of segment
|
mmio_region: Option<(u32, u32)>, // start end end of segment
|
||||||
|
|
||||||
// optionals - not necessarily set on boot.
|
// optionals - not necessarily set on boot.
|
||||||
@@ -41,12 +46,13 @@ pub struct Emulator<Mem: RandomAccessMemory> {
|
|||||||
|
|
||||||
// Config params
|
// Config params
|
||||||
__mmap_configured: bool,
|
__mmap_configured: bool,
|
||||||
__io_configured: bool,
|
|
||||||
|
time: Instant,
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl<Mem: RandomAccessMemory> Send for Emulator<Mem> {}
|
unsafe impl Send for Emulator {}
|
||||||
impl<Mem: RandomAccessMemory> Emulator<Mem> {
|
impl Emulator {
|
||||||
pub fn new(mem: Mem) -> Self {
|
pub fn new() -> Self {
|
||||||
let (sender, receiver) = mpsc::channel::<u8>();
|
let (sender, receiver) = mpsc::channel::<u8>();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
@@ -58,24 +64,16 @@ impl<Mem: RandomAccessMemory> Emulator<Mem> {
|
|||||||
|
|
||||||
memory_map: None,
|
memory_map: None,
|
||||||
mmu: MMU::new(),
|
mmu: MMU::new(),
|
||||||
mainstore: mem,
|
mainstore: MemoryBank::new(),
|
||||||
|
|
||||||
// mmio
|
// mmio
|
||||||
mmio: Vec::new(),
|
|
||||||
mmio_region: None,
|
mmio_region: None,
|
||||||
|
|
||||||
// Config params
|
// Config params
|
||||||
__io_configured: false,
|
|
||||||
__mmap_configured: false,
|
__mmap_configured: false,
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_device(mut self, device: impl IoDevice + 'static) -> Self {
|
time: Instant::now(),
|
||||||
self.mmio.push(MappedDevice {
|
}
|
||||||
base: 0,
|
|
||||||
device: Box::new(device),
|
|
||||||
});
|
|
||||||
self
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn state_handle(&self) -> Arc<SharedState> {
|
pub fn state_handle(&self) -> Arc<SharedState> {
|
||||||
@@ -88,25 +86,21 @@ impl<Mem: RandomAccessMemory> Emulator<Mem> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn memory_mut(&mut self) -> &mut impl RandomAccessMemory {
|
pub fn memory_mut(&mut self) -> &mut MemoryBank {
|
||||||
&mut self.mainstore
|
&mut self.mainstore
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn reg(&self, reg: Register) -> u32 {
|
pub fn reg(&self, reg: Register) -> u32 {
|
||||||
if reg as u8 == Register::Zero as u8 {
|
debug_assert!((reg as u8) < Register::COUNT);
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
debug_assert!((reg as usize) < ProcessorSnapshot::REG_COUNT);
|
|
||||||
|
|
||||||
unsafe { *self.internal_state.registers.get_unchecked(reg as usize) }
|
unsafe { *self.internal_state.registers.get_unchecked(reg as usize) }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn mut_reg(&mut self, reg: Register) -> &mut u32 {
|
pub fn mut_reg(&mut self, reg: Register) -> &mut u32 {
|
||||||
debug_assert!((reg as usize) < ProcessorSnapshot::REG_COUNT);
|
debug_assert!((reg as u8) < Register::COUNT);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
self.internal_state
|
self.internal_state
|
||||||
.registers
|
.registers
|
||||||
@@ -128,23 +122,6 @@ impl<Mem: RandomAccessMemory> Emulator<Mem> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_io_map(mut self, map: Vec<IoMapping>) -> Result<Self, String> {
|
|
||||||
if !self.__mmap_configured {
|
|
||||||
return Err("You must map memory before applying I/O mappings".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
for entry in map {
|
|
||||||
if let Some(idx) = self.mmio.iter().position(|d| d.device.id() == entry.device) {
|
|
||||||
self.mmio[idx].base = entry.base;
|
|
||||||
} else {
|
|
||||||
eprintln!("WARN: no device registgered for {:?}", entry.device);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.__io_configured = true;
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cold]
|
#[cold]
|
||||||
fn update(&mut self) {
|
fn update(&mut self) {
|
||||||
self.shared_state
|
self.shared_state
|
||||||
@@ -154,7 +131,7 @@ impl<Mem: RandomAccessMemory> Emulator<Mem> {
|
|||||||
|
|
||||||
#[cold]
|
#[cold]
|
||||||
fn boot(&mut self) -> Result<(), String> {
|
fn boot(&mut self) -> Result<(), String> {
|
||||||
if !(self.__io_configured && self.__mmap_configured) {
|
if !(self.__mmap_configured) {
|
||||||
return Err("Emulator<Mem> not configured".to_string());
|
return Err("Emulator<Mem> not configured".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,48 +139,89 @@ impl<Mem: RandomAccessMemory> Emulator<Mem> {
|
|||||||
MemoryMap::identity_map(&mut self.mmu, map.regions.get(0).unwrap());
|
MemoryMap::identity_map(&mut self.mmu, map.regions.get(0).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
self.internal_state.running = true;
|
self.internal_state.halted = false;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cold]
|
#[cold]
|
||||||
fn shutdown(&mut self) {
|
fn shutdown(&mut self) {
|
||||||
self.internal_state.running = false;
|
self.internal_state.halted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn halt(&mut self) {
|
||||||
|
self.internal_state.halted = true;
|
||||||
|
|
||||||
|
// wait until we're un-halted.
|
||||||
|
while self.internal_state.halted {
|
||||||
|
self.wait_for_instructions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_update(&mut self) {
|
||||||
|
// UI requested a state update.
|
||||||
|
if self.shared_state.update_req.load(Ordering::Relaxed) {
|
||||||
|
self.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.shared_state.reseting.load(Ordering::Relaxed) {
|
||||||
|
self.internal_state.registers = [0; Register::COUNT as usize];
|
||||||
|
self.internal_state.clock = 0;
|
||||||
|
|
||||||
|
// sit in a wait loop while resetting.
|
||||||
|
while self.shared_state.reseting.load(Ordering::Relaxed) {
|
||||||
|
thread::sleep(Duration::from_millis(100));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cold]
|
#[cold]
|
||||||
pub fn idle_wait(&mut self) {
|
pub fn wait_for_instructions(&mut self) {
|
||||||
self.internal_state.running = false;
|
{
|
||||||
|
// record time for previous execution.
|
||||||
|
self.internal_state.time = self.time.elapsed();
|
||||||
|
self.update();
|
||||||
|
self.time = Instant::now();
|
||||||
|
}
|
||||||
|
|
||||||
|
// if paused from the UI thread, just wait.
|
||||||
|
while self.shared_state.paused.load(Ordering::Relaxed) {
|
||||||
|
thread::sleep(Duration::from_millis(10));
|
||||||
|
// UI is making changes / requesting updates.
|
||||||
|
self.check_update();
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the cpu didn't halt on it's own then it's ready to run again.
|
||||||
|
if !self.internal_state.halted {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Wait for an interrupt or state update to continue.
|
|
||||||
loop {
|
loop {
|
||||||
// Check for interrupts.
|
// Check for interrupts.
|
||||||
if let Ok(code) = self.interrupts.recv_timeout(Duration::from_millis(100)) {
|
if let Ok(code) = self.interrupts.recv_timeout(Duration::from_millis(100)) {
|
||||||
|
self.internal_state.halted = false;
|
||||||
self.interrupt(Interrupt::Software(code));
|
self.interrupt(Interrupt::Software(code));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// // If we've received a request to continue running. DEPRECATED (we can run through interrupts)
|
// UI is resetting the emulator
|
||||||
// if self.shared_state.running.load(Ordering::Relaxed) {
|
self.check_update();
|
||||||
// panic!("3");
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// UI requested a state update.
|
|
||||||
if self.shared_state.update_req.load(Ordering::Relaxed) {
|
|
||||||
self.update();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.internal_state.running = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&mut self) -> Result<(), String> {
|
pub fn run(&mut self) -> Result<(), String> {
|
||||||
self.boot()?;
|
// wait for UI to initialise emulator.
|
||||||
|
if self.shared_state.paused.load(Ordering::Relaxed) {
|
||||||
|
self.wait_for_instructions();
|
||||||
|
}
|
||||||
|
|
||||||
let mut time = Instant::now();
|
self.boot()?;
|
||||||
|
self.time = Instant::now();
|
||||||
|
|
||||||
'emu: loop {
|
'emu: loop {
|
||||||
|
// so that it always returns zero. this is more performance friendly than checking
|
||||||
|
// for a zero register each access as it's branchless
|
||||||
|
*self.mut_reg(Register::Zero) = 0;
|
||||||
|
|
||||||
// IMPORTANT
|
// IMPORTANT
|
||||||
// do not change anything about this loop. it's fully optimised afaik.
|
// do not change anything about this loop. it's fully optimised afaik.
|
||||||
// Check for commands or hardware Interrupts every 32k cycles
|
// Check for commands or hardware Interrupts every 32k cycles
|
||||||
@@ -215,8 +233,8 @@ impl<Mem: RandomAccessMemory> Emulator<Mem> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if unlikely(!self.shared_state.running.load(Ordering::Relaxed)) {
|
if unlikely(self.shared_state.paused.load(Ordering::Relaxed)) {
|
||||||
self.idle_wait();
|
self.wait_for_instructions();
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.interrupts.try_recv() {
|
match self.interrupts.try_recv() {
|
||||||
@@ -226,24 +244,6 @@ impl<Mem: RandomAccessMemory> Emulator<Mem> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we got a halt instruction, wait
|
|
||||||
if unlikely(!self.internal_state.running) {
|
|
||||||
let mips = (self.internal_state.clock as u128 / time.elapsed().as_micros());
|
|
||||||
println!(
|
|
||||||
"TIME TAKEN: {:?}, clock: {}, {}MIPS",
|
|
||||||
time.elapsed(),
|
|
||||||
self.internal_state.clock,
|
|
||||||
mips
|
|
||||||
);
|
|
||||||
time = Instant::now();
|
|
||||||
|
|
||||||
// temporary while i figure out a better solution
|
|
||||||
// exit when we hit halt (not useful for a real os ofc)
|
|
||||||
break 'emu;
|
|
||||||
|
|
||||||
self.idle_wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
let pc = self.reg(Register::Pcx);
|
let pc = self.reg(Register::Pcx);
|
||||||
let instruction = self.mem_read_word(pc);
|
let instruction = self.mem_read_word(pc);
|
||||||
|
|
||||||
@@ -258,23 +258,21 @@ impl<Mem: RandomAccessMemory> Emulator<Mem> {
|
|||||||
thread::sleep(Duration::from_micros(10));
|
thread::sleep(Duration::from_micros(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.mut_reg(Register::Pcx).add_assign(4);
|
// Check if the previous instruction caused a fault.
|
||||||
|
|
||||||
self.execute(Instruction(instruction));
|
|
||||||
|
|
||||||
// Check if executing the interrupt caused a fault.
|
|
||||||
if let Some(fault) = self.pending_fault {
|
if let Some(fault) = self.pending_fault {
|
||||||
self.pending_fault = None;
|
|
||||||
println!("WARN fault: {:?}", fault);
|
println!("WARN fault: {:?}", fault);
|
||||||
|
self.pending_fault = None;
|
||||||
self.interrupt(fault);
|
self.interrupt(fault);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.mut_reg(Register::Pcx).add_assign(4);
|
||||||
|
self.execute(Instruction(instruction));
|
||||||
|
|
||||||
// always increment clock.
|
// always increment clock.
|
||||||
self.internal_state.clock += 1;
|
self.internal_state.clock += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.shutdown();
|
self.shutdown();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -486,7 +484,7 @@ impl<Mem: RandomAccessMemory> Emulator<Mem> {
|
|||||||
Some(Opcode::Int) => {
|
Some(Opcode::Int) => {
|
||||||
self.interrupt(Interrupt::Software(word.imm16() as u8));
|
self.interrupt(Interrupt::Software(word.imm16() as u8));
|
||||||
}
|
}
|
||||||
Some(Opcode::Hlt) => self.internal_state.running = false,
|
Some(Opcode::Hlt) => self.halt(),
|
||||||
Some(Opcode::IRet) => todo!(),
|
Some(Opcode::IRet) => todo!(),
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
@@ -507,147 +505,57 @@ impl<Mem: RandomAccessMemory> Emulator<Mem> {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn mem_read_byte(&mut self, addr: u32) -> u8 {
|
fn mem_read_byte(&mut self, addr: u32) -> u8 {
|
||||||
if unlikely(self.is_mmio(addr)) {
|
|
||||||
return self.io_read_byte(addr);
|
|
||||||
}
|
|
||||||
self.mainstore.read_byte(addr)
|
self.mainstore.read_byte(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn mem_write_byte(&mut self, addr: u32, val: u8) {
|
fn mem_write_byte(&mut self, addr: u32, val: u8) {
|
||||||
if unlikely(self.is_mmio(addr)) {
|
if addr == 0x40000 {
|
||||||
self.io_write_byte(addr, val);
|
eprintln!("emulator wrote 0x{:08x} to uart", val);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
self.mainstore.write_byte(addr, val);
|
self.mainstore.write_byte(addr, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn mem_read_word(&mut self, addr: u32) -> u32 {
|
fn mem_read_word(&mut self, addr: u32) -> u32 {
|
||||||
if unlikely(self.is_mmio(addr)) {
|
|
||||||
return self.io_read_word(addr);
|
|
||||||
}
|
|
||||||
self.mainstore.read_word(addr)
|
self.mainstore.read_word(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn mem_write_word(&mut self, addr: u32, val: u32) {
|
fn mem_write_word(&mut self, addr: u32, val: u32) {
|
||||||
if unlikely(self.is_mmio(addr)) {
|
|
||||||
self.io_write_word(addr, val);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.mainstore.write_word(addr, val);
|
self.mainstore.write_word(addr, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn mem_read_page(&mut self, addr: u32) -> &Page {
|
fn mem_read_page(&mut self, addr: u32) -> &Page {
|
||||||
if unlikely(self.is_mmio(addr)) {
|
|
||||||
// pages spanning MMIO don't really make sense
|
|
||||||
// treat as fault
|
|
||||||
self.pending_fault = Some(Interrupt::ProtectionFault);
|
|
||||||
return &Page::ZERO;
|
|
||||||
}
|
|
||||||
self.mainstore.read_page(addr)
|
self.mainstore.read_page(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn mem_write_page(&mut self, addr: u32, val: &Page) {
|
fn mem_write_page(&mut self, addr: u32, val: &Page) {
|
||||||
if unlikely(self.is_mmio(addr)) {
|
|
||||||
self.pending_fault = Some(Interrupt::ProtectionFault);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.mainstore.write_page(addr, val);
|
self.mainstore.write_page(addr, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
// single MMIO check reused by all of the above
|
|
||||||
#[inline]
|
|
||||||
fn is_mmio(&self, addr: u32) -> bool {
|
|
||||||
self.mmio_region
|
|
||||||
.map(|(base, end)| addr >= base && addr < end)
|
|
||||||
.unwrap_or(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fault(&mut self, interrupt: Interrupt) {
|
fn fault(&mut self, interrupt: Interrupt) {
|
||||||
self.pending_fault = Some(interrupt);
|
self.pending_fault = Some(interrupt);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn get_device(&mut self, addr: u32) -> Option<(&mut Box<dyn IoDevice>, u32)> {
|
|
||||||
// Find the device that covers the address.
|
|
||||||
self.mmio
|
|
||||||
.iter_mut()
|
|
||||||
.find(|d| d.base <= addr && addr < d.base + d.device.size())
|
|
||||||
.map(|d| (&mut d.device, addr - d.base))
|
|
||||||
}
|
|
||||||
// --- cold IO paths ---
|
|
||||||
|
|
||||||
#[cold]
|
|
||||||
fn io_read_byte(&mut self, addr: u32) -> u8 {
|
|
||||||
if let Some((device, offset)) = self.get_device(addr) {
|
|
||||||
device.read_byte(offset).unwrap_or_else(|fault| {
|
|
||||||
self.fault(fault);
|
|
||||||
0
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
self.fault(Interrupt::UnmappedIo);
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cold]
|
|
||||||
fn io_read_word(&mut self, addr: u32) -> u32 {
|
|
||||||
if let Some((device, offset)) = self.get_device(addr) {
|
|
||||||
device.read_word(offset).unwrap_or_else(|fault| {
|
|
||||||
self.fault(fault);
|
|
||||||
0
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
self.fault(Interrupt::UnmappedIo);
|
|
||||||
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cold]
|
|
||||||
fn io_write_byte(&mut self, addr: u32, val: u8) {
|
|
||||||
if let Some((dev, offset)) = self.get_device(addr) {
|
|
||||||
dev.write_byte(offset, val).unwrap_or_else(|fault| {
|
|
||||||
self.fault(fault);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
self.fault(Interrupt::UnmappedIo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cold]
|
|
||||||
fn io_write_word(&mut self, addr: u32, val: u32) {
|
|
||||||
if let Some((dev, offset)) = self.get_device(addr) {
|
|
||||||
dev.write_word(offset, val).unwrap_or_else(|fault| {
|
|
||||||
self.fault(fault);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
self.fault(Interrupt::UnmappedIo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ProcessorSnapshot {
|
pub struct ProcessorSnapshot {
|
||||||
pub running: bool,
|
pub halted: bool,
|
||||||
pub clock: usize,
|
pub clock: usize,
|
||||||
pub registers: [u32; Self::REG_COUNT],
|
pub time: Duration,
|
||||||
|
pub registers: [u32; Register::COUNT as usize],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ProcessorSnapshot {
|
impl Default for ProcessorSnapshot {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
running: false,
|
halted: false,
|
||||||
clock: 0,
|
clock: 0,
|
||||||
registers: [0; Self::REG_COUNT],
|
time: Duration::ZERO,
|
||||||
|
registers: [0; Register::COUNT as usize],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProcessorSnapshot {
|
|
||||||
const REG_COUNT: usize = 28;
|
|
||||||
}
|
|
||||||
@@ -1,24 +1,27 @@
|
|||||||
use std::sync::{Arc, atomic::AtomicBool, mpsc};
|
use std::sync::{Arc, atomic::AtomicBool, mpsc};
|
||||||
|
|
||||||
use crate::processor::processor::ProcessorSnapshot;
|
|
||||||
use arc_swap::ArcSwap;
|
use arc_swap::ArcSwap;
|
||||||
|
|
||||||
|
use crate::backend::processor::processor::ProcessorSnapshot;
|
||||||
|
|
||||||
pub struct SharedState {
|
pub struct SharedState {
|
||||||
pub proc: ArcSwap<ProcessorSnapshot>,
|
pub proc: ArcSwap<ProcessorSnapshot>,
|
||||||
|
|
||||||
pub running: AtomicBool,
|
pub reseting: AtomicBool,
|
||||||
|
pub paused: AtomicBool,
|
||||||
pub update_req: AtomicBool,
|
pub update_req: AtomicBool,
|
||||||
|
|
||||||
sender: mpsc::Sender<u8>,
|
interrupt_queue: mpsc::Sender<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SharedState {
|
impl SharedState {
|
||||||
pub fn new(sender: mpsc::Sender<u8>) -> Self {
|
pub fn new(sender: mpsc::Sender<u8>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
reseting: AtomicBool::new(false),
|
||||||
proc: ArcSwap::new(Arc::new(ProcessorSnapshot::default())),
|
proc: ArcSwap::new(Arc::new(ProcessorSnapshot::default())),
|
||||||
|
|
||||||
running: AtomicBool::new(true),
|
paused: AtomicBool::new(true),
|
||||||
sender: sender,
|
interrupt_queue: sender,
|
||||||
update_req: AtomicBool::new(false),
|
update_req: AtomicBool::new(false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Emulator, RandomAccessMemory,
|
Emulator,
|
||||||
io::DeviceId,
|
backend::memory::{PhysAddr, mmu::MMU},
|
||||||
memory::{PhysAddr, mmu::MMU},
|
// io::DeviceId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
|
||||||
pub enum RegionType {
|
pub enum RegionType {
|
||||||
@@ -36,7 +38,7 @@ pub struct MemoryMap {
|
|||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct IoMapping {
|
pub struct IoMapping {
|
||||||
pub device: DeviceId,
|
// pub device: DeviceId,
|
||||||
pub base: u32,
|
pub base: u32,
|
||||||
pub size: u32,
|
pub size: u32,
|
||||||
}
|
}
|
||||||
@@ -51,7 +53,7 @@ impl MemoryMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply(&self, cpu: &mut Emulator<impl RandomAccessMemory>) {
|
pub fn apply(&self, cpu: &mut Emulator) {
|
||||||
for reg in &self.regions {
|
for reg in &self.regions {
|
||||||
if reg.identity_map_on_boot {
|
if reg.identity_map_on_boot {
|
||||||
Self::identity_map(cpu.mmu_mut(), reg);
|
Self::identity_map(cpu.mmu_mut(), reg);
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
pub mod controller;
|
||||||
|
pub mod display;
|
||||||
|
pub mod framebuffer;
|
||||||
|
pub mod registers;
|
||||||
|
pub mod serial;
|
||||||
|
|
||||||
|
pub trait Component {
|
||||||
|
/// The tab label shown in the tile header.
|
||||||
|
fn title(&self) -> &str;
|
||||||
|
|
||||||
|
fn visible(&mut self) -> &mut bool;
|
||||||
|
|
||||||
|
/// Draw the component's UI inside the provided `Ui`.
|
||||||
|
fn ui(&mut self, ui: &mut egui::Ui, ctx: &egui::Context);
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
use std::sync::{Arc, atomic::Ordering};
|
||||||
|
|
||||||
|
use super::Component;
|
||||||
|
use crate::{MemoryBank, SharedState};
|
||||||
|
|
||||||
|
use common::prelude::Register;
|
||||||
|
|
||||||
|
pub struct Controller {
|
||||||
|
state: Arc<SharedState>,
|
||||||
|
mem: MemoryBank,
|
||||||
|
visible: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Controller {
|
||||||
|
pub fn new(state: Arc<SharedState>, mem: MemoryBank) -> Self {
|
||||||
|
Self {
|
||||||
|
state,
|
||||||
|
mem,
|
||||||
|
visible: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for Controller {
|
||||||
|
fn title(&self) -> &str {
|
||||||
|
"Control Panel"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visible(&mut self) -> &mut bool {
|
||||||
|
&mut self.visible
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ui(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) {
|
||||||
|
let state = self.state.proc.load();
|
||||||
|
|
||||||
|
let paused = self.state.paused.load(Ordering::Relaxed);
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
// Pause / Run
|
||||||
|
if ui
|
||||||
|
.button(match paused {
|
||||||
|
false => "Pause",
|
||||||
|
true => "Resume",
|
||||||
|
})
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
self.state.paused.store(!paused, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step
|
||||||
|
// if ui.button("Step").clicked() {
|
||||||
|
// state
|
||||||
|
// .cmd_sender
|
||||||
|
// .send(Command::Step(self.step_amount))
|
||||||
|
// .unwrap_or_else(|_| {
|
||||||
|
// state.error_log.push("Failed to send command".to_string());
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Resets the emulator and all attached devices
|
||||||
|
if ui.button("Reset All").clicked() {
|
||||||
|
self.state.reseting.store(true, Ordering::Relaxed);
|
||||||
|
self.mem.clear();
|
||||||
|
self.state.reseting.store(false, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
// if ui
|
||||||
|
// .text_edit_singleline(&mut self.step_amount_input)
|
||||||
|
// .changed()
|
||||||
|
// {
|
||||||
|
// self.step_amount = if let Ok(amount) = self.step_amount_input.parse() {
|
||||||
|
// amount
|
||||||
|
// } else {
|
||||||
|
// state
|
||||||
|
// .error_log
|
||||||
|
// .push("Unable to parse step amount".to_string());
|
||||||
|
// 1
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Status info
|
||||||
|
ui.label(format!(
|
||||||
|
"Status: {}",
|
||||||
|
match (state.halted, paused) {
|
||||||
|
(true, false) => "Halted",
|
||||||
|
(_, false) => "Running",
|
||||||
|
(_, true) => "Paused",
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
let pcx = state.registers[Register::Pcx as usize];
|
||||||
|
let clock = state.clock;
|
||||||
|
let time = state.time.as_micros();
|
||||||
|
let mips = state.clock as u128 / time;
|
||||||
|
|
||||||
|
ui.label(format!("Clock: {clock}"));
|
||||||
|
ui.label(format!("Pcx: 0x{pcx:08X}"));
|
||||||
|
ui.label(format!("Elapsed: {}micros", time));
|
||||||
|
ui.label(format!("Freq: {}MHz", mips));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
use egui::{Color32, FontId, Vec2};
|
||||||
|
|
||||||
|
use super::Component;
|
||||||
|
pub use crate::io::display::Display;
|
||||||
|
|
||||||
|
impl Component for Display {
|
||||||
|
fn title(&self) -> &str {
|
||||||
|
"Display"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visible(&mut self) -> &mut bool {
|
||||||
|
self.visible()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ui(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) {
|
||||||
|
let display_w = self.width();
|
||||||
|
let display_h = self.height();
|
||||||
|
let data = self.read();
|
||||||
|
|
||||||
|
let font_id = FontId::monospace(12.0);
|
||||||
|
|
||||||
|
let char_width = ui.fonts_mut(|f| f.glyph_width(&font_id, 'W'));
|
||||||
|
let line_height = ui.fonts_mut(|f| f.row_height(&font_id));
|
||||||
|
|
||||||
|
#[expect(clippy::cast_precision_loss)]
|
||||||
|
let display_size = Vec2::new(
|
||||||
|
char_width * display_w as f32,
|
||||||
|
line_height * display_h as f32,
|
||||||
|
);
|
||||||
|
|
||||||
|
let (rect, _response) = ui.allocate_exact_size(display_size, egui::Sense::all());
|
||||||
|
|
||||||
|
// ui.painter().rect_filled(rect, 0.0, Color32::BLACK);
|
||||||
|
|
||||||
|
// Draw text
|
||||||
|
for y in 0..display_h {
|
||||||
|
let mut row_text = String::with_capacity(display_w);
|
||||||
|
for x in 0..display_w {
|
||||||
|
let index = y * display_w + x;
|
||||||
|
if index < data.len() {
|
||||||
|
let byte = data[index];
|
||||||
|
let ch = if (32..=126).contains(&byte) {
|
||||||
|
byte as char
|
||||||
|
} else {
|
||||||
|
' '
|
||||||
|
};
|
||||||
|
row_text.push(ch);
|
||||||
|
} else {
|
||||||
|
row_text.push(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::cast_precision_loss)]
|
||||||
|
let text_pos = rect.min + Vec2::new(0.0, y as f32 * line_height);
|
||||||
|
|
||||||
|
ui.painter().text(
|
||||||
|
text_pos,
|
||||||
|
egui::Align2::LEFT_TOP,
|
||||||
|
row_text,
|
||||||
|
font_id.clone(),
|
||||||
|
Color32::WHITE,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
use egui::Vec2;
|
||||||
|
|
||||||
|
use crate::{MemoryBank, Page, backend::memory::PhysAddr};
|
||||||
|
|
||||||
|
use super::Component;
|
||||||
|
|
||||||
|
pub struct FrameBuffer {
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
addr: PhysAddr,
|
||||||
|
mem: MemoryBank,
|
||||||
|
pub buffer: Vec<u32>, // RGBA pixels
|
||||||
|
visible: bool,
|
||||||
|
|
||||||
|
texture: Option<egui::TextureHandle>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FrameBuffer {
|
||||||
|
pub fn new(width: u32, height: u32, addr: PhysAddr, mem: MemoryBank) -> Self {
|
||||||
|
Self {
|
||||||
|
width: width as usize,
|
||||||
|
height: height as usize,
|
||||||
|
addr,
|
||||||
|
mem,
|
||||||
|
buffer: vec![0u32; width as usize * height as usize],
|
||||||
|
visible: true,
|
||||||
|
|
||||||
|
texture: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn width(&self) -> usize {
|
||||||
|
self.width
|
||||||
|
}
|
||||||
|
pub fn height(&self) -> usize {
|
||||||
|
self.height
|
||||||
|
}
|
||||||
|
pub fn visible(&mut self) -> &mut bool {
|
||||||
|
&mut self.visible
|
||||||
|
}
|
||||||
|
|
||||||
|
fn internal_read(&self) -> Vec<u32> {
|
||||||
|
let byte_size = self.width * self.height * 4;
|
||||||
|
let page_count = byte_size.div_ceil(Page::SIZE);
|
||||||
|
|
||||||
|
(0..page_count)
|
||||||
|
.map(|i| {
|
||||||
|
let page = self.mem.read_page(self.addr + i as u32 * 4096);
|
||||||
|
page.as_slice().to_owned()
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.take(byte_size)
|
||||||
|
.collect::<Vec<u8>>()
|
||||||
|
.chunks_exact(4)
|
||||||
|
.map(|chunk| u32::from_le_bytes(chunk.try_into().unwrap()))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn changed(&mut self) -> bool {
|
||||||
|
let temp = self.internal_read();
|
||||||
|
if temp != self.buffer {
|
||||||
|
self.buffer = temp;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(&self) -> &[u32] {
|
||||||
|
&self.buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert buffer to RGBA bytes for use with egui/wgpu textures
|
||||||
|
pub fn as_rgba_bytes(&self) -> Vec<u8> {
|
||||||
|
self.buffer
|
||||||
|
.iter()
|
||||||
|
.flat_map(|&pixel| pixel.to_le_bytes())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for FrameBuffer {
|
||||||
|
fn title(&self) -> &str {
|
||||||
|
"Framebuffer"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visible(&mut self) -> &mut bool {
|
||||||
|
&mut self.visible
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ui(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) {
|
||||||
|
let changed = self.changed();
|
||||||
|
|
||||||
|
// get or create the texture
|
||||||
|
let texture = self.texture.get_or_insert_with(|| {
|
||||||
|
ctx.load_texture(
|
||||||
|
"framebuffer",
|
||||||
|
egui::ColorImage {
|
||||||
|
size: [self.width, self.height],
|
||||||
|
source_size: Vec2::from([self.width as f32, self.height as f32]),
|
||||||
|
pixels: vec![egui::Color32::BLACK; self.width * self.height],
|
||||||
|
},
|
||||||
|
egui::TextureOptions::NEAREST,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
if changed {
|
||||||
|
let pixels: Vec<egui::Color32> = self
|
||||||
|
.buffer
|
||||||
|
.iter()
|
||||||
|
.map(|&p| {
|
||||||
|
let bytes = p.to_le_bytes();
|
||||||
|
egui::Color32::from_rgba_premultiplied(bytes[0], bytes[1], bytes[2], bytes[3])
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
texture.set(
|
||||||
|
egui::ColorImage {
|
||||||
|
size: [self.width, self.height],
|
||||||
|
source_size: Vec2::from([self.width as f32, self.height as f32]),
|
||||||
|
pixels,
|
||||||
|
},
|
||||||
|
egui::TextureOptions::NEAREST,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// scale to fit available space while preserving aspect ratio
|
||||||
|
let available = ui.available_size();
|
||||||
|
let aspect = self.width as f32 / self.height as f32;
|
||||||
|
let size = if available.x / aspect <= available.y {
|
||||||
|
egui::vec2(available.x, available.x / aspect)
|
||||||
|
} else {
|
||||||
|
egui::vec2(available.y * aspect, available.y)
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.image(egui::load::SizedTexture::new(texture.id(), size));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
use std::{f32, sync::Arc};
|
||||||
|
|
||||||
|
use common::prelude::Register;
|
||||||
|
|
||||||
|
use crate::SharedState;
|
||||||
|
|
||||||
|
use super::Component;
|
||||||
|
|
||||||
|
pub struct Registers {
|
||||||
|
state: Arc<SharedState>,
|
||||||
|
visible: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Registers {
|
||||||
|
fn section(
|
||||||
|
&mut self,
|
||||||
|
ui: &mut egui::Ui,
|
||||||
|
ctx: &egui::Context,
|
||||||
|
registers: Vec<(Register, u32)>,
|
||||||
|
col_pairs: usize,
|
||||||
|
name: &str,
|
||||||
|
) {
|
||||||
|
ui.collapsing(name, |ui| {
|
||||||
|
egui::Grid::new(name)
|
||||||
|
.num_columns(col_pairs * 2)
|
||||||
|
.spacing([40.0, 4.0])
|
||||||
|
.striped(true)
|
||||||
|
.show(ui, |ui| {
|
||||||
|
// only show header if there are multiple rows
|
||||||
|
if registers.len() > col_pairs {
|
||||||
|
for _ in 0..col_pairs {
|
||||||
|
ui.label("Register");
|
||||||
|
ui.label("Value");
|
||||||
|
}
|
||||||
|
ui.end_row();
|
||||||
|
}
|
||||||
|
for chunk in registers.chunks(col_pairs) {
|
||||||
|
for (reg, val) in chunk {
|
||||||
|
ui.label(reg.to_string());
|
||||||
|
ui.label(format!("0x{:08X} ({})", val, val));
|
||||||
|
}
|
||||||
|
for _ in chunk.len()..col_pairs {
|
||||||
|
ui.label("");
|
||||||
|
ui.label("");
|
||||||
|
}
|
||||||
|
ui.end_row();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Registers {
|
||||||
|
pub fn new(state: Arc<SharedState>) -> Self {
|
||||||
|
Self {
|
||||||
|
state,
|
||||||
|
visible: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for Registers {
|
||||||
|
fn title(&self) -> &str {
|
||||||
|
"Registers"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visible(&mut self) -> &mut bool {
|
||||||
|
&mut self.visible
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ui(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) {
|
||||||
|
ui.set_min_width(200.0);
|
||||||
|
|
||||||
|
let state = self.state.proc.load();
|
||||||
|
let available = ui.available_width();
|
||||||
|
let col_pairs = match available {
|
||||||
|
0.0..350.0 => 1,
|
||||||
|
350.0..600.0 => 2,
|
||||||
|
600.0..900.0 => 3,
|
||||||
|
f32::MIN..0.0 => unreachable!(),
|
||||||
|
_ => 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
// ── General Purpose ──────────────────────────────────────
|
||||||
|
let gp_registers: Vec<(Register, u32)> = (0..=15u8)
|
||||||
|
.map(|i| (Register::from_u8(i).unwrap(), state.registers[i as usize]))
|
||||||
|
.collect();
|
||||||
|
self.section(
|
||||||
|
ui,
|
||||||
|
ctx,
|
||||||
|
gp_registers,
|
||||||
|
col_pairs,
|
||||||
|
"General Purpose Registers",
|
||||||
|
);
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
// ── Stack ─────────────────────────────────────────────────
|
||||||
|
let stack_registers = vec![
|
||||||
|
(Register::Spr, state.registers[Register::Spr as usize]),
|
||||||
|
(Register::Bpr, state.registers[Register::Bpr as usize]),
|
||||||
|
];
|
||||||
|
self.section(ui, ctx, stack_registers, col_pairs, "Stack Registers");
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
// ── Special Purpose ───────────────────────────────────────
|
||||||
|
let special_registers = vec![
|
||||||
|
(Register::Acc, state.registers[Register::Acc as usize]),
|
||||||
|
(Register::Ret, state.registers[Register::Ret as usize]),
|
||||||
|
(Register::Idr, state.registers[Register::Idr as usize]),
|
||||||
|
(Register::Mmr, state.registers[Register::Mmr as usize]),
|
||||||
|
];
|
||||||
|
self.section(
|
||||||
|
ui,
|
||||||
|
ctx,
|
||||||
|
special_registers,
|
||||||
|
col_pairs,
|
||||||
|
"Special Purpose Registers",
|
||||||
|
);
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
// ── System ────────────────────────────────────────────────
|
||||||
|
let system_registers = vec![
|
||||||
|
(Register::Pcx, state.registers[Register::Pcx as usize]),
|
||||||
|
(Register::Sts, state.registers[Register::Sts as usize]),
|
||||||
|
(Register::Cir, state.registers[Register::Cir as usize]),
|
||||||
|
];
|
||||||
|
self.section(ui, ctx, system_registers, col_pairs, "System Registers");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
use crate::{frontend::components::Component, io::serial::Serial};
|
||||||
|
|
||||||
|
impl Component for Serial {
|
||||||
|
fn title(&self) -> &str {
|
||||||
|
"Serial"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visible(&mut self) -> &mut bool {
|
||||||
|
&mut self.visible
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ui(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) {
|
||||||
|
self.update();
|
||||||
|
|
||||||
|
ui.label(String::from_utf8(self.buffer.clone()).unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
use eframe::NativeOptions;
|
||||||
|
use eframe::egui;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
|
mod components;
|
||||||
|
|
||||||
|
use crate::frontend::components::Component;
|
||||||
|
use crate::frontend::components::framebuffer::FrameBuffer;
|
||||||
|
use crate::io::serial::Serial;
|
||||||
|
use crate::{MemoryBank, SharedState, config::VERSION};
|
||||||
|
use components::{controller::Controller, display::Display, registers::Registers};
|
||||||
|
|
||||||
|
pub fn run_app(state: Arc<SharedState>, mem_handle: MemoryBank) -> eframe::Result<()> {
|
||||||
|
eframe::run_native(
|
||||||
|
"Damn Simple Architecture",
|
||||||
|
NativeOptions::default(),
|
||||||
|
Box::new(|cc| Ok(Box::new(DsaUi::new(cc, state, mem_handle)))),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl eframe::App for DsaUi {
|
||||||
|
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||||
|
// request an update from emulator every cycle
|
||||||
|
self.state.update_req.store(true, Ordering::Relaxed);
|
||||||
|
|
||||||
|
// title panel with dsa title
|
||||||
|
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
|
||||||
|
ui.with_layout(
|
||||||
|
egui::Layout::top_down_justified(egui::Align::Center)
|
||||||
|
.with_main_align(egui::Align::Min),
|
||||||
|
|ui| {
|
||||||
|
ui.allocate_space(egui::vec2(0.0, 5.0));
|
||||||
|
ui.heading(format!("Damn Simple Architecture v{} 🚀", VERSION));
|
||||||
|
ui.allocate_space(egui::vec2(0.0, 5.0));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// menu bar.
|
||||||
|
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
|
||||||
|
egui::MenuBar::new().ui(ui, |ui| {
|
||||||
|
ui.menu_button("Panels", |ui| {
|
||||||
|
ui.set_max_width(300.0);
|
||||||
|
ui.set_min_width(300.0);
|
||||||
|
ui.spacing_mut().button_padding = egui::vec2(10.0, 5.0);
|
||||||
|
|
||||||
|
for comp in &mut self.components {
|
||||||
|
let name = comp.title().to_string();
|
||||||
|
ui.toggle_value(comp.visible(), name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
|
for c in &mut self.components {
|
||||||
|
let mut visible = *c.visible();
|
||||||
|
if visible {
|
||||||
|
egui::Window::new(c.title())
|
||||||
|
.open(&mut visible)
|
||||||
|
.show(ctx, |ui| {
|
||||||
|
c.ui(ui, ctx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
*c.visible() = visible;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.request_repaint_after(std::time::Duration::from_millis(16)); // ~60fps
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DsaUi {
|
||||||
|
state: Arc<SharedState>,
|
||||||
|
mem_handle: MemoryBank,
|
||||||
|
components: Vec<Box<dyn Component>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DsaUi {
|
||||||
|
pub fn new(
|
||||||
|
cc: &eframe::CreationContext,
|
||||||
|
state: Arc<SharedState>,
|
||||||
|
mem_handle: MemoryBank,
|
||||||
|
) -> Self {
|
||||||
|
// components
|
||||||
|
let mut components: Vec<Box<dyn Component>> = vec![
|
||||||
|
Box::new(Controller::new(state.clone(), mem_handle.clone())),
|
||||||
|
Box::new(Display::new(80, 25, 0x20000, mem_handle.clone())),
|
||||||
|
Box::new(FrameBuffer::new(320, 240, 0x30000, mem_handle.clone())),
|
||||||
|
Box::new(Registers::new(state.clone())),
|
||||||
|
Box::new(Serial::new(0x40000, state.clone(), mem_handle.clone())),
|
||||||
|
];
|
||||||
|
components.iter_mut().for_each(|c| *c.visible() = false);
|
||||||
|
|
||||||
|
Self::configure_appearance(&cc.egui_ctx);
|
||||||
|
let app = Self {
|
||||||
|
state,
|
||||||
|
mem_handle,
|
||||||
|
components,
|
||||||
|
};
|
||||||
|
app
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configure_appearance(ctx: &egui::Context) {
|
||||||
|
// configure appearance of UI elements
|
||||||
|
let mut visuals = egui::Visuals::dark();
|
||||||
|
|
||||||
|
// --- your existing settings ---
|
||||||
|
visuals.window_fill = egui::Color32::from_rgb(20, 20, 20);
|
||||||
|
visuals.panel_fill = egui::Color32::from_rgb(20, 20, 20);
|
||||||
|
visuals.widgets.inactive.fg_stroke =
|
||||||
|
egui::Stroke::from((1.0, egui::Color32::from_rgb(255, 255, 255)));
|
||||||
|
visuals.widgets.inactive.bg_stroke =
|
||||||
|
egui::Stroke::from((1.0, egui::Color32::from_rgb(60, 60, 60)));
|
||||||
|
visuals.widgets.inactive.corner_radius = egui::CornerRadius::from(4);
|
||||||
|
visuals.widgets.inactive.bg_fill = egui::Color32::from_rgb(20, 20, 20);
|
||||||
|
visuals.widgets.inactive.weak_bg_fill = egui::Color32::from_rgb(20, 20, 20);
|
||||||
|
visuals.widgets.inactive.expansion = 1.0;
|
||||||
|
|
||||||
|
// tile resize handle
|
||||||
|
visuals.window_stroke = egui::Stroke::new(1.0, egui::Color32::from_rgb(45, 45, 45));
|
||||||
|
visuals.extreme_bg_color = egui::Color32::from_rgb(20, 20, 20);
|
||||||
|
|
||||||
|
ctx.set_visuals(visuals);
|
||||||
|
|
||||||
|
let mut fonts = egui::FontDefinitions::default();
|
||||||
|
fonts.font_data.insert(
|
||||||
|
"JetBrains Mono Nerd Font".to_string(),
|
||||||
|
std::sync::Arc::new(egui::FontData::from_static(include_bytes!(
|
||||||
|
"../../font/JetBrainsMonoNerdFontMono_Regular.ttf",
|
||||||
|
))),
|
||||||
|
);
|
||||||
|
|
||||||
|
fonts
|
||||||
|
.families
|
||||||
|
.entry(egui::FontFamily::Proportional)
|
||||||
|
.or_default()
|
||||||
|
.insert(0, "JetBrains Mono Nerd Font".to_string());
|
||||||
|
|
||||||
|
fonts
|
||||||
|
.families
|
||||||
|
.entry(egui::FontFamily::Monospace)
|
||||||
|
.or_default()
|
||||||
|
.insert(0, "JetBrains Mono Nerd Font".to_string());
|
||||||
|
|
||||||
|
ctx.set_fonts(fonts);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,80 +1,62 @@
|
|||||||
use std::sync::{
|
// use super::{IoAccess, IoHandle};
|
||||||
Arc,
|
use crate::{MemoryBank, Page, backend::memory::PhysAddr};
|
||||||
atomic::{AtomicBool, AtomicU8, Ordering},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
pub struct Display {
|
||||||
io::{IoAccess, IoDevice},
|
width: usize,
|
||||||
processor::interrupts::Interrupt,
|
height: usize,
|
||||||
};
|
addr: PhysAddr,
|
||||||
|
|
||||||
pub struct DisplayDevice {
|
mem: MemoryBank,
|
||||||
buffer: Arc<Box<[AtomicU8]>>,
|
buffer: Vec<u8>,
|
||||||
dirty: Arc<AtomicBool>,
|
visible: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DisplayDevice {
|
impl Display {
|
||||||
pub fn new(width: usize, height: usize) -> (Self, DisplayHandle) {
|
pub fn new(width: u32, height: u32, addr: PhysAddr, mem: MemoryBank) -> Self {
|
||||||
let buffer = Arc::new(
|
Self {
|
||||||
(0..width * height)
|
width: width as usize,
|
||||||
.map(|_| AtomicU8::new(0))
|
height: height as usize,
|
||||||
.collect::<Vec<_>>()
|
addr,
|
||||||
.into_boxed_slice(),
|
mem,
|
||||||
);
|
buffer: Vec::new(),
|
||||||
let dirty = Arc::new(AtomicBool::new(false));
|
visible: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let handle = DisplayHandle {
|
pub fn visible(&mut self) -> &mut bool {
|
||||||
buffer: Arc::clone(&buffer),
|
&mut self.visible
|
||||||
dirty: Arc::clone(&dirty),
|
}
|
||||||
};
|
|
||||||
|
|
||||||
(Self { buffer, dirty }, handle)
|
pub fn width(&self) -> usize {
|
||||||
}
|
self.width
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IoDevice for DisplayDevice {
|
pub fn height(&self) -> usize {
|
||||||
fn size(&self) -> u32 {
|
self.height
|
||||||
(self.buffer.len()) as u32
|
}
|
||||||
}
|
|
||||||
fn access(&self) -> IoAccess {
|
fn internal_read(&self) -> Vec<u8> {
|
||||||
IoAccess::WRITE
|
let pages = (0..((self.width * self.height).div_ceil(Page::SIZE)))
|
||||||
}
|
.map(|i| {
|
||||||
|
let page = self.mem.read_page(self.addr + i as u32 * 4096);
|
||||||
fn id(&self) -> super::DeviceId {
|
page.as_slice().to_owned()
|
||||||
super::DeviceId::Display
|
})
|
||||||
}
|
.flatten()
|
||||||
|
.take((self.width * self.height) as usize)
|
||||||
fn write_word(&mut self, offset: u32, val: u32) -> Result<(), Interrupt> {
|
.collect::<Vec<u8>>();
|
||||||
let bytes = val.to_le_bytes();
|
pages
|
||||||
self.buffer[offset as usize].store(bytes[0], Ordering::Relaxed);
|
}
|
||||||
self.buffer[offset as usize + 1].store(bytes[1], Ordering::Relaxed);
|
|
||||||
self.buffer[offset as usize + 2].store(bytes[2], Ordering::Relaxed);
|
pub fn changed(&mut self) -> bool {
|
||||||
self.buffer[offset as usize + 3].store(bytes[3], Ordering::Relaxed);
|
let temp = self.internal_read();
|
||||||
self.dirty.store(true, Ordering::Relaxed);
|
if temp != self.buffer {
|
||||||
Ok(())
|
self.buffer = temp;
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
fn write_byte(&mut self, offset: u32, val: u8) -> Result<(), Interrupt> {
|
false
|
||||||
self.buffer[offset as usize].store(val, Ordering::Relaxed);
|
}
|
||||||
self.dirty.store(true, Ordering::Relaxed);
|
|
||||||
Ok(())
|
pub fn read(&mut self) -> Vec<u8> {
|
||||||
}
|
self.internal_read()
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DisplayHandle {
|
|
||||||
pub buffer: Arc<Box<[AtomicU8]>>,
|
|
||||||
pub dirty: Arc<AtomicBool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DisplayHandle {
|
|
||||||
pub fn is_dirty(&self) -> bool {
|
|
||||||
self.dirty.swap(false, Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_all(&self) -> Vec<u8> {
|
|
||||||
self.buffer
|
|
||||||
.iter()
|
|
||||||
.map(|b| b.load(Ordering::Relaxed))
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+35
-113
@@ -1,124 +1,46 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{Emulator, RandomAccessMemory, processor::interrupts::Interrupt};
|
|
||||||
|
|
||||||
pub mod display;
|
pub mod display;
|
||||||
|
pub mod serial;
|
||||||
|
// #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
// pub enum DeviceId {
|
||||||
|
// Display,
|
||||||
|
// Serial,
|
||||||
|
// Random,
|
||||||
|
// Timer,
|
||||||
|
// }
|
||||||
|
|
||||||
pub struct MappedDevice {
|
// pub trait IoHandle: Send {
|
||||||
pub base: u32,
|
// /// Return the size (in bytes) of the device's addressable region.
|
||||||
pub device: Box<dyn IoDevice>,
|
// fn size(&self) -> usize;
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
// /// Return the access permissions for the device.
|
||||||
pub struct IoAccess(u8);
|
// fn access(&self) -> IoAccess;
|
||||||
|
|
||||||
impl IoAccess {
|
// /// Return the DeviceId
|
||||||
pub const READ: Self = Self(0b01);
|
// fn id(&self) -> DeviceId;
|
||||||
pub const WRITE: Self = Self(0b10);
|
// }
|
||||||
|
|
||||||
/// Convenience alias for Read + Write.
|
// #[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub const READWRITE: Self = Self(Self::READ.0 | Self::WRITE.0);
|
// pub struct IoAccess(u8);
|
||||||
pub const NONE: Self = Self(0);
|
|
||||||
|
|
||||||
/// Bitwise OR – returns a new `IoAccess` that contains all flags from both operands.
|
// impl IoAccess {
|
||||||
#[inline]
|
// pub const READ: Self = Self(0b01);
|
||||||
pub fn or(self, other: Self) -> Self {
|
// pub const WRITE: Self = Self(0b10);
|
||||||
Self(self.0 | other.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if a flag is present.
|
// /// Convenience alias for Read + Write.
|
||||||
#[inline]
|
// pub const READWRITE: Self = Self(Self::READ.0 | Self::WRITE.0);
|
||||||
pub fn contains(&self, flag: Self) -> bool {
|
// pub const NONE: Self = Self(0);
|
||||||
self.0 & flag.0 != 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
// /// Bitwise OR – returns a new `IoAccess` that contains all flags from both operands.
|
||||||
pub enum DeviceId {
|
// #[inline]
|
||||||
Display,
|
// pub fn or(self, other: Self) -> Self {
|
||||||
Serial,
|
// Self(self.0 | other.0)
|
||||||
Random,
|
// }
|
||||||
Timer,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Trait representing an input/output device that can be mapped into a memory space.
|
// /// Check if a flag is present.
|
||||||
///
|
// #[inline]
|
||||||
/// - Default methods return dummy data.
|
// pub fn contains(&self, flag: Self) -> bool {
|
||||||
pub trait IoDevice: Send {
|
// self.0 & flag.0 != 0
|
||||||
/// Return the size (in bytes) of the device's addressable region.
|
// }
|
||||||
fn size(&self) -> u32;
|
// }
|
||||||
|
|
||||||
/// Return the access permissions for the device.
|
|
||||||
fn access(&self) -> IoAccess;
|
|
||||||
|
|
||||||
/// Return the DeviceId
|
|
||||||
fn id(&self) -> DeviceId;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// Read a single byte from the specified address.
|
|
||||||
///
|
|
||||||
/// By default, write‑only devices return `None`. All other devices
|
|
||||||
/// return `Some(0)` as placeholder data. Implementations should override
|
|
||||||
/// this method to provide actual read logic.
|
|
||||||
fn read_byte(&self, offset: u32) -> Result<u8, Interrupt> {
|
|
||||||
if self.access().contains(IoAccess::READ) {
|
|
||||||
Ok(0)
|
|
||||||
} else {
|
|
||||||
Err(Interrupt::ReadFromWriteOnly)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// Write a single byte to the specified address.
|
|
||||||
///
|
|
||||||
/// By default, read‑only devices return `false` indicating failure.
|
|
||||||
/// All other devices return `true`. Implementations should override
|
|
||||||
/// this method to perform real write operations and return whether
|
|
||||||
/// the operation succeeded.
|
|
||||||
fn write_byte(&mut self, offset: u32, val: u8) -> Result<(), Interrupt> {
|
|
||||||
if self.access().contains(IoAccess::WRITE) {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(Interrupt::WriteToReadOnly)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read a 32‑bit word from the specified address in little‑endian order.
|
|
||||||
///
|
|
||||||
/// The default implementation composes four consecutive byte reads using
|
|
||||||
/// `read_byte`. If any byte read fails, the whole operation returns `None`.
|
|
||||||
/// Write‑only devices return `None` immediately. Override this method for
|
|
||||||
/// more efficient word reads.
|
|
||||||
#[inline]
|
|
||||||
fn read_word(&self, offset: u32) -> Result<u32, Interrupt> {
|
|
||||||
if self.access().contains(IoAccess::READ) {
|
|
||||||
// default: compose from bytes
|
|
||||||
let b0 = self.read_byte(offset)? as u32;
|
|
||||||
let b1 = self.read_byte(offset + 1)? as u32;
|
|
||||||
let b2 = self.read_byte(offset + 2)? as u32;
|
|
||||||
let b3 = self.read_byte(offset + 3)? as u32;
|
|
||||||
return Ok(b0 | b1 << 8 | b2 << 16 | b3 << 24);
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(Interrupt::ReadFromWriteOnly)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write a 32‑bit word to the specified address in little‑endian order.
|
|
||||||
///
|
|
||||||
/// The default implementation splits the value into bytes and writes each
|
|
||||||
/// using `write_byte`. Read‑only devices return `false`; otherwise returns
|
|
||||||
/// `true` after attempting all byte writes. Override for more efficient
|
|
||||||
/// word writes or to handle partial failures.
|
|
||||||
fn write_word(&mut self, offset: u32, val: u32) -> Result<(), Interrupt> {
|
|
||||||
if self.access().contains(IoAccess::WRITE) {
|
|
||||||
let bytes = val.to_le_bytes();
|
|
||||||
self.write_byte(offset, bytes[0]);
|
|
||||||
self.write_byte(offset + 1, bytes[1]);
|
|
||||||
self.write_byte(offset + 2, bytes[2]);
|
|
||||||
self.write_byte(offset + 3, bytes[3]);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(Interrupt::WriteToReadOnly)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
use std::{
|
||||||
|
path::Component,
|
||||||
|
sync::{
|
||||||
|
Arc,
|
||||||
|
mpsc::{Receiver, Sender},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{MemoryBank, SharedState, backend::memory::PhysAddr};
|
||||||
|
|
||||||
|
pub struct Serial {
|
||||||
|
pub receiver: Receiver<u8>,
|
||||||
|
pub buffer: Vec<u8>,
|
||||||
|
|
||||||
|
pub visible: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serial {
|
||||||
|
fn uart_io_thread(mem: MemoryBank, uart_base: PhysAddr, tx: Sender<u8>) {
|
||||||
|
loop {
|
||||||
|
let word = mem.read_word(uart_base);
|
||||||
|
// println!("word {:#010X}", word);
|
||||||
|
if word >> 24 == 0x01 {
|
||||||
|
let byte = ((word >> 16) & 0xFF) as u8;
|
||||||
|
let _ = tx.send(byte);
|
||||||
|
mem.write_word(uart_base, 0x00_00_00_00); // clear valid flag
|
||||||
|
}
|
||||||
|
std::hint::spin_loop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(uart_base: PhysAddr, state: Arc<SharedState>, mem_handle: MemoryBank) -> Self {
|
||||||
|
let (sender, receiver) = std::sync::mpsc::channel();
|
||||||
|
let _ = std::thread::spawn(move || Self::uart_io_thread(mem_handle, uart_base, sender));
|
||||||
|
|
||||||
|
Self {
|
||||||
|
visible: false,
|
||||||
|
receiver,
|
||||||
|
buffer: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self) {
|
||||||
|
while let Ok(byte) = self.receiver.try_recv() {
|
||||||
|
self.buffer.push(byte);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,11 +2,10 @@
|
|||||||
#![feature(inherent_associated_types)]
|
#![feature(inherent_associated_types)]
|
||||||
|
|
||||||
pub mod args;
|
pub mod args;
|
||||||
|
pub mod backend;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
pub mod frontend;
|
||||||
pub mod io;
|
pub mod io;
|
||||||
mod memory;
|
|
||||||
mod processor;
|
|
||||||
|
|
||||||
pub use {config::MemoryMap, processor::processor::Emulator, processor::state::SharedState};
|
pub use backend::memory::ram::*;
|
||||||
|
pub use backend::{processor::processor::Emulator, processor::state::SharedState};
|
||||||
pub use memory::ram::*;
|
|
||||||
|
|||||||
+15
-43
@@ -1,4 +1,5 @@
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
<<<<<<< HEAD
|
||||||
use common::isa::instructions::Instruction;
|
use common::isa::instructions::Instruction;
|
||||||
use dsa::{
|
use dsa::{
|
||||||
args::DsaArgs, io::display::{DisplayDevice, DisplayHandle}, BulkAllocStore, Emulator, Page,
|
args::DsaArgs, io::display::{DisplayDevice, DisplayHandle}, BulkAllocStore, Emulator, Page,
|
||||||
@@ -10,20 +11,22 @@ use std::{
|
|||||||
thread,
|
thread,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
=======
|
||||||
|
|
||||||
|
use common::prelude::Instruction;
|
||||||
|
use dsa::{Emulator, Page, args::DsaArgs, frontend::run_app};
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
const STACK_SIZE: usize = 1024 * 1024 * 16;
|
||||||
|
|
||||||
|
>>>>>>> refs/remotes/origin/main
|
||||||
fn main() {
|
fn main() {
|
||||||
let args = DsaArgs::parse();
|
let args = DsaArgs::parse();
|
||||||
|
|
||||||
let mmap = args.get_memory_map();
|
let mut emulator = Emulator::new().apply_memory_map(args.get_memory_map());
|
||||||
let iomap = args.get_io_map();
|
|
||||||
|
|
||||||
let store = BulkAllocStore::new();
|
let mem_handle = emulator.memory_mut().clone();
|
||||||
let (display, handle) = DisplayDevice::new(80, 25);
|
let state = emulator.state_handle();
|
||||||
|
|
||||||
let mut emulator = Emulator::new(store)
|
|
||||||
.with_device(display)
|
|
||||||
.apply_memory_map(mmap)
|
|
||||||
.apply_io_map(iomap)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
if let Some(bin) = args.get_binary() {
|
if let Some(bin) = args.get_binary() {
|
||||||
for (i, ch) in bin.chunks(4).enumerate() {
|
for (i, ch) in bin.chunks(4).enumerate() {
|
||||||
@@ -53,41 +56,10 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let state = emulator.state_handle();
|
let _runner = thread::Builder::new()
|
||||||
|
|
||||||
const STACK_SIZE: usize = 1024 * 1024 * 16;
|
|
||||||
let runner = thread::Builder::new()
|
|
||||||
.stack_size(STACK_SIZE)
|
.stack_size(STACK_SIZE)
|
||||||
.spawn(move || emulator.run().unwrap())
|
.spawn(move || emulator.run().unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let observer = thread::spawn(|| observe(state, handle));
|
run_app(state, mem_handle).unwrap()
|
||||||
|
|
||||||
runner.join().unwrap();
|
|
||||||
thread::sleep(Duration::from_millis(100));
|
|
||||||
|
|
||||||
// yeah we don't need this tbh.
|
|
||||||
// observer.join().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// todo: remove this!
|
|
||||||
fn observe(state: Arc<SharedState>, handle: DisplayHandle) {
|
|
||||||
loop {
|
|
||||||
state.update_req.store(true, Ordering::Relaxed);
|
|
||||||
thread::sleep(std::time::Duration::from_millis(20));
|
|
||||||
let state = state.proc.load();
|
|
||||||
// println!(
|
|
||||||
// "clock: {}, GP Registers: {:?}",
|
|
||||||
// state.clock, state.registers
|
|
||||||
// );
|
|
||||||
|
|
||||||
if handle.is_dirty() {
|
|
||||||
for line in handle.read_all().chunks(80) {
|
|
||||||
for (i, byte) in line.iter().enumerate() {
|
|
||||||
print!("{}", *byte as char);
|
|
||||||
}
|
|
||||||
println!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,107 +0,0 @@
|
|||||||
use std::{
|
|
||||||
alloc::{Layout, alloc_zeroed},
|
|
||||||
hint::{likely, unlikely},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::memory::{
|
|
||||||
PhysAddr, RandomAccessMemory,
|
|
||||||
ram::{NUM_PAGES, Page, idx},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct ArrayStore {
|
|
||||||
pages: Box<[*mut Page; NUM_PAGES]>,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Send for ArrayStore {}
|
|
||||||
impl ArrayStore {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
let pages = vec![0 as *mut Page; 2 << 20]
|
|
||||||
.into_boxed_slice()
|
|
||||||
.try_into()
|
|
||||||
.unwrap();
|
|
||||||
Self { pages }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn alloc(&mut self) -> *mut Page {
|
|
||||||
let layout = Layout::from_size_align(4096, 4096).unwrap();
|
|
||||||
unsafe { alloc_zeroed(layout) as *mut Page }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RandomAccessMemory for ArrayStore {
|
|
||||||
#[inline(always)]
|
|
||||||
fn read_word(&self, addr: PhysAddr) -> u32 {
|
|
||||||
debug_assert_eq!(addr % 4, 0);
|
|
||||||
let (idx, offset) = idx(addr);
|
|
||||||
|
|
||||||
if likely(!self.pages[idx].is_null()) {
|
|
||||||
return unsafe { (self.pages[idx] as *const u32).byte_add(offset).read() };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Slow path: MMIO, fault, etc — never inlined
|
|
||||||
// Since we're only reading, we can safely return 0
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn read_byte(&self, addr: PhysAddr) -> u8 {
|
|
||||||
let (idx, offset) = idx(addr);
|
|
||||||
|
|
||||||
if likely(!self.pages[idx].is_null()) {
|
|
||||||
return unsafe { (self.pages[idx] as *const u8).byte_add(offset).read() };
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn read_page(&self, addr: PhysAddr) -> &Page {
|
|
||||||
debug_assert_eq!(addr % 4096, 0);
|
|
||||||
let (idx, _) = idx(addr);
|
|
||||||
|
|
||||||
if likely(!self.pages[idx].is_null()) {
|
|
||||||
return unsafe { &*(self.pages[idx] as *const Page) };
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Page([0; 4096]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn write_byte(&mut self, addr: PhysAddr, value: u8) {
|
|
||||||
let (idx, offset) = idx(addr);
|
|
||||||
|
|
||||||
if unlikely(self.pages[idx].is_null()) {
|
|
||||||
self.pages[idx] = self.alloc();
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
(self.pages[idx] as *mut u8).byte_add(offset).write(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn write_word(&mut self, addr: PhysAddr, value: u32) {
|
|
||||||
debug_assert_eq!(addr % 4, 0);
|
|
||||||
let (idx, offset) = idx(addr);
|
|
||||||
|
|
||||||
if unlikely(self.pages[idx].is_null()) {
|
|
||||||
self.pages[idx] = self.alloc();
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe { (self.pages[idx] as *mut u32).byte_add(offset).write(value) }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn write_page(&mut self, addr: PhysAddr, value: &Page) {
|
|
||||||
debug_assert_eq!(addr % 4096, 0);
|
|
||||||
let (idx, _) = idx(addr);
|
|
||||||
|
|
||||||
if unlikely(self.pages[idx].is_null()) {
|
|
||||||
self.pages[idx] = self.alloc();
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
(self.pages[idx] as *mut Page).copy_from(value, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
use std::{
|
|
||||||
alloc::{Layout, alloc_zeroed},
|
|
||||||
hint::{likely, unlikely},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::memory::{
|
|
||||||
PhysAddr, RandomAccessMemory,
|
|
||||||
ram::{Page, idx},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct BulkAllocStore {
|
|
||||||
pages: Box<[*mut Page; Page::COUNT]>,
|
|
||||||
pre_allocated: [*mut Page; Self::ALLOC_AT_ONCE],
|
|
||||||
idx: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Send for BulkAllocStore {}
|
|
||||||
impl BulkAllocStore {
|
|
||||||
const ALLOC_AT_ONCE: usize = 256 as usize;
|
|
||||||
|
|
||||||
pub fn new() -> Self {
|
|
||||||
let pages = vec![0 as *mut Page; Page::COUNT]
|
|
||||||
.into_boxed_slice()
|
|
||||||
.try_into()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
pages,
|
|
||||||
pre_allocated: Self::alloc_set(),
|
|
||||||
idx: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn alloc_set() -> [*mut Page; Self::ALLOC_AT_ONCE] {
|
|
||||||
let layout = Layout::from_size_align(Page::SIZE * Self::ALLOC_AT_ONCE, Page::SIZE).unwrap();
|
|
||||||
let mut region = unsafe { alloc_zeroed(layout) as *mut Page };
|
|
||||||
let mut set = [0 as *mut Page; Self::ALLOC_AT_ONCE];
|
|
||||||
for i in 0..Self::ALLOC_AT_ONCE {
|
|
||||||
set[i] = region;
|
|
||||||
region = unsafe { region.byte_add(4096) };
|
|
||||||
}
|
|
||||||
set
|
|
||||||
}
|
|
||||||
|
|
||||||
fn alloc(&mut self) -> *mut Page {
|
|
||||||
if self.idx < Self::ALLOC_AT_ONCE as u8 {
|
|
||||||
let page = self.pre_allocated[self.idx as usize];
|
|
||||||
self.idx += 1;
|
|
||||||
page
|
|
||||||
} else {
|
|
||||||
self.idx = 0;
|
|
||||||
self.pre_allocated = Self::alloc_set();
|
|
||||||
self.pre_allocated[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RandomAccessMemory for BulkAllocStore {
|
|
||||||
#[inline(always)]
|
|
||||||
fn read_word(&self, addr: PhysAddr) -> u32 {
|
|
||||||
debug_assert_eq!(addr % 4, 0);
|
|
||||||
let (idx, offset) = idx(addr);
|
|
||||||
|
|
||||||
if likely(!self.pages[idx].is_null()) {
|
|
||||||
return unsafe { (self.pages[idx] as *const u32).byte_add(offset).read() };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Slow path: MMIO, fault, etc — never inlined
|
|
||||||
// Since we're only reading, we can safely return 0
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn read_byte(&self, addr: PhysAddr) -> u8 {
|
|
||||||
let (idx, offset) = idx(addr);
|
|
||||||
|
|
||||||
if likely(!self.pages[idx].is_null()) {
|
|
||||||
return unsafe { (self.pages[idx] as *const u8).byte_add(offset).read() };
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn read_page(&self, addr: PhysAddr) -> &Page {
|
|
||||||
debug_assert_eq!(addr % Page::SIZE as u32, 0);
|
|
||||||
let (idx, _) = idx(addr);
|
|
||||||
|
|
||||||
if likely(!self.pages[idx].is_null()) {
|
|
||||||
return unsafe { &*(self.pages[idx] as *const Page) };
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Page::ZERO;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn write_byte(&mut self, addr: PhysAddr, value: u8) {
|
|
||||||
let (idx, offset) = idx(addr);
|
|
||||||
|
|
||||||
if unlikely(self.pages[idx].is_null()) {
|
|
||||||
self.pages[idx] = self.alloc();
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
(self.pages[idx] as *mut u8).byte_add(offset).write(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn write_word(&mut self, addr: PhysAddr, value: u32) {
|
|
||||||
debug_assert_eq!(addr % 4, 0);
|
|
||||||
let (idx, offset) = idx(addr);
|
|
||||||
|
|
||||||
if unlikely(self.pages[idx].is_null()) {
|
|
||||||
self.pages[idx] = self.alloc();
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe { (self.pages[idx] as *mut u32).byte_add(offset).write(value) }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn write_page(&mut self, addr: PhysAddr, value: &Page) {
|
|
||||||
debug_assert_eq!(addr % Page::SIZE as u32, 0);
|
|
||||||
let (idx, _) = idx(addr);
|
|
||||||
|
|
||||||
if unlikely(self.pages[idx].is_null()) {
|
|
||||||
self.pages[idx] = self.alloc();
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
(self.pages[idx] as *mut Page).copy_from(value, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
use fxhash::FxHashMap;
|
|
||||||
|
|
||||||
use crate::memory::{PhysAddr, RandomAccessMemory, ram::Page};
|
|
||||||
|
|
||||||
pub struct HashStore {
|
|
||||||
pages: FxHashMap<PhysAddr, Page>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HashStore {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
pages: FxHashMap::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
const fn segment_addr(addr: PhysAddr) -> (PhysAddr, usize) {
|
|
||||||
(addr & !(0xFFF), (addr & 0xFFF) as usize)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RandomAccessMemory for HashStore {
|
|
||||||
#[inline(always)]
|
|
||||||
fn read_byte(&self, addr: PhysAddr) -> u8 {
|
|
||||||
let (page, offset) = Self::segment_addr(addr);
|
|
||||||
self.pages
|
|
||||||
.get(&page)
|
|
||||||
.map(|p| p.as_ref()[offset])
|
|
||||||
.unwrap_or(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn read_word(&self, addr: PhysAddr) -> u32 {
|
|
||||||
let (page, offset) = Self::segment_addr(addr);
|
|
||||||
debug_assert_eq!(offset % 4, 0);
|
|
||||||
let page = self.pages.get(&page).unwrap_or(&Page::ZERO);
|
|
||||||
u32::from_be_bytes(page[offset..=offset + 3].try_into().unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn read_page(&self, addr: PhysAddr) -> &Page {
|
|
||||||
debug_assert_eq!(addr % 0x1000, 0);
|
|
||||||
self.pages.get(&addr).unwrap_or(&Page::ZERO)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn write_byte(&mut self, addr: PhysAddr, value: u8) {
|
|
||||||
let (page, offset) = Self::segment_addr(addr);
|
|
||||||
self.pages.entry(page).or_insert_with(|| Page::zeroed())[offset] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn write_word(&mut self, addr: PhysAddr, value: u32) {
|
|
||||||
let (page, offset) = Self::segment_addr(addr);
|
|
||||||
debug_assert_eq!(offset % 4, 0);
|
|
||||||
let page = self.pages.entry(page).or_insert_with(|| Page::zeroed());
|
|
||||||
page[offset..=offset + 3].copy_from_slice(&value.to_be_bytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn write_page(&mut self, addr: PhysAddr, value: &Page) {
|
|
||||||
debug_assert_eq!(addr % 0x1000, 0);
|
|
||||||
let page = self.pages.entry(addr).or_insert_with(|| Page::zeroed());
|
|
||||||
page.0 = value.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
use std::ops::{Deref, DerefMut};
|
|
||||||
|
|
||||||
use crate::memory::PhysAddr;
|
|
||||||
|
|
||||||
#[cfg(feature = "mainstore-arraymap")]
|
|
||||||
pub mod arraymap;
|
|
||||||
#[cfg(feature = "mainstore-arraymap")]
|
|
||||||
pub use arraymap::ArrayStore;
|
|
||||||
|
|
||||||
#[cfg(feature = "mainstore-hashmap")]
|
|
||||||
pub mod hashmap;
|
|
||||||
#[cfg(feature = "mainstore-hashmap")]
|
|
||||||
pub use hashmap::HashStore;
|
|
||||||
|
|
||||||
#[cfg(feature = "mainstore-stackarray")]
|
|
||||||
pub mod stackarray;
|
|
||||||
#[cfg(feature = "mainstore-stackarray")]
|
|
||||||
pub use stackarray::StackArrayStore;
|
|
||||||
|
|
||||||
#[cfg(feature = "mainstore-bulkalloc")]
|
|
||||||
pub mod bulkalloc;
|
|
||||||
#[cfg(feature = "mainstore-bulkalloc")]
|
|
||||||
pub use bulkalloc::BulkAllocStore;
|
|
||||||
|
|
||||||
#[cfg(feature = "mainstore-prealloc")]
|
|
||||||
pub mod prealloc;
|
|
||||||
#[cfg(feature = "mainstore-prealloc")]
|
|
||||||
pub use prealloc::PreAllocStore;
|
|
||||||
|
|
||||||
#[repr(transparent)]
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Page([u8; 4096]);
|
|
||||||
|
|
||||||
impl Page {
|
|
||||||
pub const SIZE: usize = 4096;
|
|
||||||
pub const COUNT: usize = u32::MAX as usize / Self::SIZE + 1;
|
|
||||||
|
|
||||||
pub const MASK: u32 = 0xFFF;
|
|
||||||
pub const ZERO: Self = Page::zeroed();
|
|
||||||
|
|
||||||
pub const fn zeroed() -> Self {
|
|
||||||
Self([0; 4096])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn from(data: [u8; 4096]) -> Self {
|
|
||||||
Self(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_word(&self, offset: usize) -> u32 {
|
|
||||||
u32::from_le_bytes(self.0[offset..offset + 4].try_into().unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_word(&mut self, offset: usize, value: u32) {
|
|
||||||
self.0[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn paginate(data: &[u8]) -> impl Iterator<Item = Page> + '_ {
|
|
||||||
data.chunks(4096).map(|chunk| {
|
|
||||||
let mut arr = [0u8; 4096];
|
|
||||||
arr[..chunk.len()].copy_from_slice(chunk);
|
|
||||||
Page(arr)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for Page {
|
|
||||||
type Target = [u8];
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for Page {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
&mut self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<Vec<u8>> for Page {
|
|
||||||
type Error = String;
|
|
||||||
|
|
||||||
fn try_from(data: Vec<u8>) -> Result<Self, Self::Error> {
|
|
||||||
let arr: [u8; 4096] = data
|
|
||||||
.try_into()
|
|
||||||
.map_err(|v: Vec<u8>| format!("Expected 4096 bytes, got {}", v.len()))?;
|
|
||||||
Ok(Page(arr))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
const fn idx(addr: PhysAddr) -> (usize, usize) {
|
|
||||||
((addr >> 12) as usize, (addr & 0xFFF) as usize)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait RandomAccessMemory {
|
|
||||||
fn read_byte(&self, addr: PhysAddr) -> u8;
|
|
||||||
|
|
||||||
fn write_byte(&mut self, addr: PhysAddr, value: u8);
|
|
||||||
|
|
||||||
fn read_word(&self, addr: PhysAddr) -> u32;
|
|
||||||
|
|
||||||
fn write_word(&mut self, addr: PhysAddr, value: u32);
|
|
||||||
|
|
||||||
fn read_page(&self, addr: PhysAddr) -> &Page;
|
|
||||||
|
|
||||||
fn write_page(&mut self, addr: PhysAddr, value: &Page);
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
use crate::memory::{
|
|
||||||
PhysAddr, RandomAccessMemory,
|
|
||||||
ram::{NUM_PAGES, Page},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct PreAllocStore {
|
|
||||||
heap: Box<[u8; NUM_PAGES * 4096]>,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Send for PreAllocStore {}
|
|
||||||
impl PreAllocStore {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
heap: unsafe { Box::new_zeroed().assume_init() },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RandomAccessMemory for PreAllocStore {
|
|
||||||
#[inline(always)]
|
|
||||||
fn read_word(&self, addr: PhysAddr) -> u32 {
|
|
||||||
unsafe { (self.heap.as_ptr().add(addr as usize) as *const u32).read() }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn read_byte(&self, addr: PhysAddr) -> u8 {
|
|
||||||
self.heap[addr as usize]
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn read_page(&self, addr: PhysAddr) -> &Page {
|
|
||||||
debug_assert_eq!(addr % 4096, 0);
|
|
||||||
unsafe { (self.heap.as_ptr().add(addr as usize) as *const Page).as_ref_unchecked() }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn write_byte(&mut self, addr: PhysAddr, value: u8) {
|
|
||||||
self.heap[addr as usize] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn write_word(&mut self, addr: PhysAddr, value: u32) {
|
|
||||||
unsafe { (self.heap.as_ptr().add(addr as usize) as *mut u32).write(value) };
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn write_page(&mut self, addr: PhysAddr, value: &Page) {
|
|
||||||
self.heap[addr as usize..addr as usize + 4096].copy_from_slice(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
use std::{
|
|
||||||
alloc::{Layout, alloc_zeroed},
|
|
||||||
hint::{likely, unlikely},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::memory::{
|
|
||||||
PhysAddr, RandomAccessMemory,
|
|
||||||
ram::{NUM_PAGES, Page, idx},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct StackArrayStore {
|
|
||||||
pages: [*mut Page; NUM_PAGES],
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Send for StackArrayStore {}
|
|
||||||
impl StackArrayStore {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
pages: [0 as *mut Page; 2 << 20],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn alloc(&mut self) -> *mut Page {
|
|
||||||
let layout = Layout::from_size_align(4096, 4096).unwrap();
|
|
||||||
unsafe { alloc_zeroed(layout) as *mut Page }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RandomAccessMemory for StackArrayStore {
|
|
||||||
#[inline(always)]
|
|
||||||
fn read_word(&self, addr: PhysAddr) -> u32 {
|
|
||||||
debug_assert_eq!(addr % 4, 0);
|
|
||||||
let (idx, offset) = idx(addr);
|
|
||||||
|
|
||||||
if likely(!self.pages[idx].is_null()) {
|
|
||||||
return unsafe { (self.pages[idx] as *const u32).byte_add(offset).read() };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Slow path: MMIO, fault, etc — never inlined
|
|
||||||
// Since we're only reading, we can safely return 0
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn read_byte(&self, addr: PhysAddr) -> u8 {
|
|
||||||
let (idx, offset) = idx(addr);
|
|
||||||
|
|
||||||
if likely(!self.pages[idx].is_null()) {
|
|
||||||
return unsafe { (self.pages[idx] as *const u8).byte_add(offset).read() };
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn read_page(&self, addr: PhysAddr) -> &Page {
|
|
||||||
debug_assert_eq!(addr % 4096, 0);
|
|
||||||
let (idx, _) = idx(addr);
|
|
||||||
|
|
||||||
if likely(!self.pages[idx].is_null()) {
|
|
||||||
return unsafe { &*(self.pages[idx] as *const Page) };
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Page::ZERO;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn write_byte(&mut self, addr: PhysAddr, value: u8) {
|
|
||||||
let (idx, offset) = idx(addr);
|
|
||||||
|
|
||||||
if unlikely(self.pages[idx].is_null()) {
|
|
||||||
self.pages[idx] = self.alloc();
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
(self.pages[idx] as *mut u8).byte_add(offset).write(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn write_word(&mut self, addr: PhysAddr, value: u32) {
|
|
||||||
debug_assert_eq!(addr % 4, 0);
|
|
||||||
let (idx, offset) = idx(addr);
|
|
||||||
|
|
||||||
if unlikely(self.pages[idx].is_null()) {
|
|
||||||
self.pages[idx] = self.alloc();
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe { (self.pages[idx] as *mut u32).byte_add(offset).write(value) }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn write_page(&mut self, addr: PhysAddr, value: &Page) {
|
|
||||||
debug_assert_eq!(addr % 4096, 0);
|
|
||||||
let (idx, _) = idx(addr);
|
|
||||||
|
|
||||||
if unlikely(self.pages[idx].is_null()) {
|
|
||||||
self.pages[idx] = self.alloc();
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe { (self.pages[idx] as *mut Page).copy_from(value, 1) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
dw DISPLAY: 0x30000
|
||||||
|
dw stack: 0x10000
|
||||||
|
|
||||||
|
// registers:
|
||||||
|
// rg0 = x position
|
||||||
|
// rg1 = y position
|
||||||
|
// rg2 = x direction (1 = right, 0 = left)
|
||||||
|
// rg3 = y direction (1 = down, 0 = up)
|
||||||
|
|
||||||
|
_init:
|
||||||
|
ldw stack, bpr
|
||||||
|
mov bpr, spr
|
||||||
|
|
||||||
|
start:
|
||||||
|
lli 0, rg0
|
||||||
|
lli 0, rg1
|
||||||
|
lli 1, rg2
|
||||||
|
lli 1, rg3
|
||||||
|
|
||||||
|
frame_loop:
|
||||||
|
// --- clear display ---
|
||||||
|
ldw DISPLAY, rg4
|
||||||
|
lwi 76800, rg5
|
||||||
|
|
||||||
|
clear_loop:
|
||||||
|
stw zero, rg4
|
||||||
|
addi rg4, 4, rg4
|
||||||
|
subi rg5, 1, rg5
|
||||||
|
jnz rg5, clear_loop
|
||||||
|
|
||||||
|
// --- draw square ---
|
||||||
|
mov rg1, rg6
|
||||||
|
|
||||||
|
draw_y_loop:
|
||||||
|
lli 20, rg8
|
||||||
|
add rg1, rg8, rg8
|
||||||
|
ilt rg6, rg8, rg9
|
||||||
|
jez rg9, draw_done
|
||||||
|
|
||||||
|
mov rg0, rg7
|
||||||
|
|
||||||
|
draw_x_loop:
|
||||||
|
lli 20, rg8
|
||||||
|
add rg0, rg8, rg8
|
||||||
|
ilt rg7, rg8, rg9
|
||||||
|
jez rg9, draw_x_done
|
||||||
|
|
||||||
|
shl rg6, 8, rg8
|
||||||
|
shl rg6, 6, rg9
|
||||||
|
add rg8, rg9, rg8
|
||||||
|
add rg8, rg7, rg8
|
||||||
|
shl rg8, 2, rg8
|
||||||
|
ldw DISPLAY, rg9
|
||||||
|
add rg9, rg8, rg8
|
||||||
|
|
||||||
|
lli 0xFFFF, rg9
|
||||||
|
lui 0xFFFF, rg9
|
||||||
|
stw rg9, rg8
|
||||||
|
|
||||||
|
addi rg7, 1, rg7
|
||||||
|
jmp draw_x_loop
|
||||||
|
|
||||||
|
draw_x_done:
|
||||||
|
addi rg6, 1, rg6
|
||||||
|
jmp draw_y_loop
|
||||||
|
|
||||||
|
draw_done:
|
||||||
|
// --- check x walls before moving ---
|
||||||
|
lli 1, rg8
|
||||||
|
ieq rg2, rg8, rg9
|
||||||
|
jez rg9, check_x_left_wall
|
||||||
|
|
||||||
|
check_x_right_wall:
|
||||||
|
// moving right: if x >= 300 flip direction
|
||||||
|
lli 300, rg8
|
||||||
|
ige rg0, rg8, rg9
|
||||||
|
jez rg9, move_x
|
||||||
|
lli 0, rg2
|
||||||
|
jmp move_x
|
||||||
|
|
||||||
|
check_x_left_wall:
|
||||||
|
// moving left: if x == 0 flip direction
|
||||||
|
ieq rg0, zero, rg9
|
||||||
|
jez rg9, move_x
|
||||||
|
lli 1, rg2
|
||||||
|
|
||||||
|
move_x:
|
||||||
|
lli 1, rg8
|
||||||
|
ieq rg2, rg8, rg9
|
||||||
|
jez rg9, do_x_left
|
||||||
|
addi rg0, 1, rg0
|
||||||
|
jmp check_y_walls
|
||||||
|
do_x_left:
|
||||||
|
subi rg0, 1, rg0
|
||||||
|
|
||||||
|
check_y_walls:
|
||||||
|
// --- check y walls before moving ---
|
||||||
|
lli 1, rg8
|
||||||
|
ieq rg3, rg8, rg9
|
||||||
|
jez rg9, check_y_up_wall
|
||||||
|
|
||||||
|
check_y_down_wall:
|
||||||
|
// moving down: if y >= 220 flip direction
|
||||||
|
lli 220, rg8
|
||||||
|
ige rg1, rg8, rg9
|
||||||
|
jez rg9, move_y
|
||||||
|
lli 0, rg3
|
||||||
|
jmp move_y
|
||||||
|
|
||||||
|
check_y_up_wall:
|
||||||
|
// moving up: if y == 0 flip direction
|
||||||
|
ieq rg1, zero, rg9
|
||||||
|
jez rg9, move_y
|
||||||
|
lli 1, rg3
|
||||||
|
|
||||||
|
move_y:
|
||||||
|
lli 1, rg8
|
||||||
|
ieq rg3, rg8, rg9
|
||||||
|
jez rg9, do_y_up
|
||||||
|
addi rg1, 1, rg1
|
||||||
|
jmp delay
|
||||||
|
do_y_up:
|
||||||
|
subi rg1, 1, rg1
|
||||||
|
|
||||||
|
delay:
|
||||||
|
lwi 2000000, rg8
|
||||||
|
delay_loop:
|
||||||
|
subi rg8, 1, rg8
|
||||||
|
jnz rg8, delay_loop
|
||||||
|
|
||||||
|
jmp frame_loop
|
||||||
|
|
||||||
|
hlt
|
||||||
+47
-2
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
include print "./print.dsa"
|
include print "./print.dsa"
|
||||||
|
|
||||||
// "print hello world"
|
// "prints first 16 fibonacci numbers"
|
||||||
db program: "++++++++++++++++++++++++++++++++++++++++++++
|
db program: "++++++++++++++++++++++++++++++++++++++++++++
|
||||||
>++++++++++++++++++++++++++++++++
|
>++++++++++++++++++++++++++++++++
|
||||||
>++++++++++++++++
|
>++++++++++++++++
|
||||||
@@ -35,6 +35,7 @@ db program: "++++++++++++++++++++++++++++++++++++++++++++
|
|||||||
<<++..."
|
<<++..."
|
||||||
|
|
||||||
db error: "Invalid Instruction!"
|
db error: "Invalid Instruction!"
|
||||||
|
dh counter: 0xFF
|
||||||
dw stack: 0x10000
|
dw stack: 0x10000
|
||||||
dw input: 0x30000
|
dw input: 0x30000
|
||||||
resb data: 1024
|
resb data: 1024
|
||||||
@@ -44,11 +45,15 @@ _init_stack:
|
|||||||
ldw stack, bpr
|
ldw stack, bpr
|
||||||
mov bpr, spr
|
mov bpr, spr
|
||||||
|
|
||||||
|
init:
|
||||||
|
ldw counter, rg7
|
||||||
|
|
||||||
start:
|
start:
|
||||||
|
call bf_reset
|
||||||
|
|
||||||
// load the start of the program into rg0
|
// load the start of the program into rg0
|
||||||
lwi program, rg0
|
lwi program, rg0
|
||||||
lwi data, rg1
|
lwi data, rg1
|
||||||
|
|
||||||
// rg0 is our instruction pointer
|
// rg0 is our instruction pointer
|
||||||
// rg1 is our data pointer
|
// rg1 is our data pointer
|
||||||
// rg2 is the value at the data pointer
|
// rg2 is the value at the data pointer
|
||||||
@@ -98,7 +103,16 @@ loop_start:
|
|||||||
pop rg1
|
pop rg1
|
||||||
pop rg0
|
pop rg0
|
||||||
|
|
||||||
|
db success: "Success!"
|
||||||
end:
|
end:
|
||||||
|
call print::newline
|
||||||
|
lwi success, rg0
|
||||||
|
push rg0
|
||||||
|
call print::print
|
||||||
|
pop zero
|
||||||
|
|
||||||
|
subi rg7, 1
|
||||||
|
jnz rg7, start
|
||||||
hlt
|
hlt
|
||||||
|
|
||||||
loop_end:
|
loop_end:
|
||||||
@@ -225,3 +239,34 @@ close_left:
|
|||||||
// push zero to the stack
|
// push zero to the stack
|
||||||
push zero
|
push zero
|
||||||
jmp _traverse_left
|
jmp _traverse_left
|
||||||
|
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// reset interpreter
|
||||||
|
// clears screen, resets cursor and clears data buffer.
|
||||||
|
bf_reset: func
|
||||||
|
push rg0
|
||||||
|
push rg1
|
||||||
|
|
||||||
|
// clear screen and reset cursor
|
||||||
|
call print::clear
|
||||||
|
call print::reset
|
||||||
|
|
||||||
|
// clear data buffer (resb data: 1024 = 256 words)
|
||||||
|
lli 256, rg0
|
||||||
|
lwi data, rg1
|
||||||
|
_bf_reset_clear_data:
|
||||||
|
subi rg0, 1, rg0
|
||||||
|
stw zero, rg1
|
||||||
|
addi rg1, 4, rg1
|
||||||
|
igt rg0, zero, rg4
|
||||||
|
jnz rg4, _bf_reset_clear_data
|
||||||
|
|
||||||
|
pop rg1
|
||||||
|
pop rg0
|
||||||
|
|
||||||
|
// reload interpreter state
|
||||||
|
lwi program, rg0
|
||||||
|
lwi data, rg1
|
||||||
|
lli 0, rg2
|
||||||
|
return
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
|
||||||
|
include print: "./print.dsa"
|
||||||
|
|
||||||
|
db success: "Success!"
|
||||||
|
dw framebuffer: 0x30000
|
||||||
|
dw stack: 0x10000
|
||||||
|
|
||||||
|
_init:
|
||||||
|
ldw stack, bpr
|
||||||
|
mov bpr, spr
|
||||||
|
|
||||||
|
start:
|
||||||
|
ldw framebuffer, rg0 // rg0 = display base address
|
||||||
|
lli 0, rg1 // rg1 = y (0..240)
|
||||||
|
|
||||||
|
y_loop:
|
||||||
|
lli 0, rg2 // rg2 = x (0..320)
|
||||||
|
|
||||||
|
x_loop:
|
||||||
|
// red = x >> 1 (0..159, left to right gradient)
|
||||||
|
shr rg2, zero, 1, rg4
|
||||||
|
|
||||||
|
// green = y (0..239, top to bottom gradient)
|
||||||
|
mov rg1, rg5
|
||||||
|
|
||||||
|
// blue = constant 128
|
||||||
|
lli 0x80, rg6
|
||||||
|
|
||||||
|
// pack as 0xAABBGGRR (LE reads as RGBA)
|
||||||
|
// start with red in bits 0-7
|
||||||
|
mov rg4, rg7
|
||||||
|
|
||||||
|
// green into bits 8-15
|
||||||
|
shl rg5, zero, 8, rg5
|
||||||
|
or rg7, rg5, rg7
|
||||||
|
|
||||||
|
// blue into bits 16-23
|
||||||
|
shl rg6, zero, 16, rg6
|
||||||
|
or rg7, rg6, rg7
|
||||||
|
|
||||||
|
// alpha into bits 24-31
|
||||||
|
lli 0xFF, rg6
|
||||||
|
shl rg6, zero, 24, rg6
|
||||||
|
or rg7, rg6, rg7
|
||||||
|
|
||||||
|
stw rg7, rg0 // write pixel
|
||||||
|
addi rg0, 4, rg0 // next pixel
|
||||||
|
|
||||||
|
addi rg2, 1, rg2
|
||||||
|
lli 320, rg6
|
||||||
|
ilt rg2, rg6, rg6
|
||||||
|
jnz rg6, x_loop
|
||||||
|
|
||||||
|
addi rg1, 1, rg1
|
||||||
|
lli 240, rg6
|
||||||
|
ilt rg1, rg6, rg6
|
||||||
|
jnz rg6, y_loop
|
||||||
|
|
||||||
|
|
||||||
|
_end:
|
||||||
|
lwi success: rg0
|
||||||
|
push rg0
|
||||||
|
call print::print
|
||||||
|
pop zero
|
||||||
|
hlt
|
||||||
Binary file not shown.
+25
-2
@@ -185,8 +185,8 @@ print_whitespace: func
|
|||||||
addi rg1, 1
|
addi rg1, 1
|
||||||
jmp _end
|
jmp _end
|
||||||
|
|
||||||
// // ------------------------------------------
|
// ------------------------------------------
|
||||||
// // print newline
|
// print newline
|
||||||
// print_newline:
|
// print_newline:
|
||||||
// push bpr
|
// push bpr
|
||||||
// mov spr, bpr
|
// mov spr, bpr
|
||||||
@@ -214,6 +214,29 @@ print_whitespace: func
|
|||||||
|
|
||||||
// jmp _end
|
// jmp _end
|
||||||
|
|
||||||
|
newline:
|
||||||
|
push bpr
|
||||||
|
mov spr, bpr
|
||||||
|
|
||||||
|
ldw display, rg0
|
||||||
|
ldw current, rg1
|
||||||
|
|
||||||
|
sub rg1, rg0, rg2 // offset from base
|
||||||
|
lli 80, rg3
|
||||||
|
|
||||||
|
_newline_mod:
|
||||||
|
ilt rg2, rg3, rg4
|
||||||
|
jnz rg4, _newline_done
|
||||||
|
sub rg2, rg3, rg2
|
||||||
|
jmp _newline_mod
|
||||||
|
|
||||||
|
_newline_done:
|
||||||
|
// rg2 = offset % 80 (position within current line)
|
||||||
|
sub rg1, rg2, rg1 // go back to line start
|
||||||
|
addi rg1, 80, rg1 // advance to next line
|
||||||
|
|
||||||
|
jmp _end
|
||||||
|
|
||||||
// ------------------------------------------
|
// ------------------------------------------
|
||||||
// prints arg[0] as a decimal number to the screen.
|
// prints arg[0] as a decimal number to the screen.
|
||||||
// print_num:
|
// print_num:
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
// lib:
|
||||||
|
// serial.dsa
|
||||||
|
//
|
||||||
|
// usage:
|
||||||
|
// include serial "<relative path>"
|
||||||
|
//
|
||||||
|
// push (register containing address of string)
|
||||||
|
// call serial::print
|
||||||
|
//
|
||||||
|
// push (register containing byte)
|
||||||
|
// call serial::print_byte
|
||||||
|
|
||||||
|
dw uart: 0x40000
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// transmit a single byte via sentinel protocol
|
||||||
|
// expects: byte in rg0, trashes rg2, rg3, rg4, rg5
|
||||||
|
_serial_send:
|
||||||
|
shl rg0, 16, rg2 // shift byte to bits [23:16]
|
||||||
|
lli 0x01, rg0
|
||||||
|
shl rg0, 24, rg0 // valid flag in bits [31:24]
|
||||||
|
or rg0, rg2, rg2 // rg2 = 0x01_XX_0000
|
||||||
|
|
||||||
|
ldw uart, rg5 // rg5 = 0x40000 (uart address, held for store)
|
||||||
|
|
||||||
|
_serial_wait:
|
||||||
|
ldw rg5, rg0 // poll mem[0x40000]
|
||||||
|
shr rg0, 24, rg0
|
||||||
|
lli 0x01, rg3
|
||||||
|
ieq rg0, rg3, rg4
|
||||||
|
jnz rg4, _serial_wait // loop while valid == 1
|
||||||
|
|
||||||
|
stw rg2, rg5 // mem[0x40000] = sentinel word
|
||||||
|
ret
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// prints the null-terminated string at addr(arg[0])
|
||||||
|
print: func
|
||||||
|
ldw bpr, rg0, 8 // load string address from arg
|
||||||
|
|
||||||
|
_serial_print_loop:
|
||||||
|
ldb rg0, rg2 // load byte
|
||||||
|
ieq rg2, zero, rg4 // null terminator?
|
||||||
|
jnz rg4, _serial_end
|
||||||
|
|
||||||
|
push rg0 // save string pointer
|
||||||
|
mov rg2, rg0
|
||||||
|
call _serial_send
|
||||||
|
pop rg0
|
||||||
|
|
||||||
|
addi rg0, 1
|
||||||
|
jmp _serial_print_loop
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// prints the last byte of arg[0]
|
||||||
|
print_byte: func
|
||||||
|
ldw bpr, rg0, 8
|
||||||
|
call _serial_send
|
||||||
|
jmp _serial_end
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
_serial_end:
|
||||||
|
return
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
include serial: "./serial.dsa"
|
||||||
|
|
||||||
|
dw stack: 0x1000
|
||||||
|
_init:
|
||||||
|
ldw stack, bpr
|
||||||
|
mov bpr, spr
|
||||||
|
|
||||||
|
db hello: "Serial works lol. this took ages and some clanker forgetting to dereference a pointer."
|
||||||
|
main:
|
||||||
|
lwi hello, rg0
|
||||||
|
push rg0
|
||||||
|
call serial::print
|
||||||
|
pop zero
|
||||||
|
hlt
|
||||||
Reference in New Issue
Block a user