Merge remote-tracking branch 'refs/remotes/origin/main'

This commit is contained in:
2026-03-13 16:41:28 +00:00
42 changed files with 1448 additions and 1266 deletions
+10
View File
@@ -1,8 +1,18 @@
# Default ignored files
/shelf/
/workspace.xml
<<<<<<< HEAD
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/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
+25 -21
View File
@@ -23,38 +23,46 @@ pub enum Register {
Rgf = 15,
// special purpose
Zero = 16,
Acc = 17,
Spr = 18,
Bpr = 19,
Ret = 20,
Idr = 21,
Mmr = 22,
Acc = 16,
Spr = 17,
Bpr = 18,
Ret = 19,
Idr = 20,
Mmr = 21,
// system - read only
Mar = 23,
Mdr = 24,
Sts = 25,
Cir = 26,
Pcx = 27,
Zero = 22,
Sts = 23,
Cir = 24,
Pcx = 25,
#[default]
Null = 28,
Null = 26,
}
impl Register {
/// Total number of registers, not counting Null as it's not a real register.
/// reading or writing to Null is undefined behaviour.
pub const COUNT: u8 = Self::Null as u8;
/// # Safety
/// the caller must ensure that the index is always within the range 0..COUNT, failing to do so will result in undefined behaviour.
#[must_use]
#[inline]
pub unsafe fn from_u8_unchecked(idx: u8) -> Self {
debug_assert!(idx <= Self::Null as u8);
debug_assert!(
idx < Self::COUNT,
"Tried to access a null register using the unchecked method. undefined behaviour alert!"
);
unsafe { std::mem::transmute(idx) }
}
#[inline]
pub fn from_u8(idx: u8) -> Result<Self, ()> {
if idx > 28 {
if idx >= Self::COUNT {
return Err(());
}
Ok(unsafe { Self::from_u8_unchecked(idx) })
}
}
@@ -84,7 +92,6 @@ impl fmt::Display for Register {
Self::Rgf => "Rgf",
// special purpose
Self::Zero => "Zero",
Self::Acc => "Acc",
Self::Spr => "Spr",
Self::Bpr => "Bpr",
@@ -93,8 +100,7 @@ impl fmt::Display for Register {
Self::Mmr => "Mmr",
// system - read only
Self::Mar => "Mar",
Self::Mdr => "Mdr",
Self::Zero => "Zero",
Self::Sts => "Sts",
Self::Cir => "Cir",
Self::Pcx => "Pcx",
@@ -129,7 +135,6 @@ impl std::str::FromStr for Register {
"rgf" => Ok(Self::Rgf),
// special purpose
"zero" => Ok(Self::Zero),
"acc" => Ok(Self::Acc),
"spr" => Ok(Self::Spr),
"bpr" => Ok(Self::Bpr),
@@ -138,8 +143,7 @@ impl std::str::FromStr for Register {
"mmr" => Ok(Self::Mmr),
// system - read only
"mar" => Ok(Self::Mar),
"mdr" => Ok(Self::Mdr),
"zero" => Ok(Self::Zero),
"sts" => Ok(Self::Sts),
"cir" => Ok(Self::Cir),
"pcx" => Ok(Self::Pcx),
+7 -7
View File
@@ -1,7 +1,8 @@
[package]
name = "emulator"
version = "0.1.0"
edition = "2024"
version = "0.3.0"
edition.workspace = true
authors.workspace = true
[[bin]]
name = "dsa"
@@ -22,15 +23,14 @@ ron = "0.12.0"
serde = { version = "1.0.228", features = ["derive"] }
common = { path = "../common" }
egui = "0.33.3"
eframe = "0.33.3"
egui_tiles = "0.14.1"
#assembler = { path = "../assembler" }
[[bench]]
name = "bench_mainstore"
harness = false
[features]
default = ["mainstore-bulkalloc"]
default = ["mainstore-prealloc"]
# Memory Bank Features
mainstore-bulkalloc = [] # Fastest for Writes
-208
View File
@@ -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);
+2 -21
View File
@@ -1,9 +1,8 @@
use std::{fs, path::PathBuf};
use clap::{Parser, ValueEnum};
use serde::{Deserialize, Serialize};
use clap::Parser;
use crate::{MemoryMap, RandomAccessMemory, config::IoMapping, io::DeviceId};
use crate::config::{IoMapping, MemoryMap};
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
@@ -18,9 +17,6 @@ pub struct DsaArgs {
#[arg(long = "iomap")]
io_map: Option<PathBuf>,
#[arg(value_enum, long = "mem", default_value = "bulk-alloc")]
pub memory_bank: MemoryBank,
}
impl DsaArgs {
@@ -56,18 +52,3 @@ impl DsaArgs {
})
}
}
#[derive(Debug, Clone, ValueEnum, Default)]
pub enum MemoryBank {
#[cfg(feature = "mainstore-bulkalloc")]
#[default]
BulkAlloc,
#[cfg(feature = "mainstore-arraymap")]
ArrayMap,
#[cfg(feature = "mainstore-hashmap")]
HashMap,
#[cfg(feature = "mainstore-prealloc")]
PreAlloc,
#[cfg(feature = "mainstore-stackarray")]
StackArray,
}
@@ -1,4 +1,4 @@
use crate::memory::{FaultInfo, PhysAddr, VirtAddr, tlb::TLB};
use crate::backend::memory::{FaultInfo, PhysAddr, VirtAddr, tlb::TLB};
pub struct MMU {
buffer: TLB,
@@ -1,7 +1,3 @@
use serde::{Deserialize, Serialize};
use crate::{RandomAccessMemory, memory::mmu::MMU, processor::processor::Emulator};
mod cache;
pub mod mmu;
pub mod ram;
+168
View File
@@ -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_MASK: u32 = (TLB_SIZE - 1) as u32;
+2
View File
@@ -0,0 +1,2 @@
pub mod memory;
pub mod processor;
@@ -13,14 +13,20 @@ use std::{
use common::isa::instructions::{Instruction, Opcode};
use common::isa::register::Register;
use crate::{
<<<<<<< HEAD:dsa/emulator/src/processor/processor.rs
config::{IoMapping, MemoryMap, RegionType},
io::{IoDevice, MappedDevice},
memory::{mmu::MMU, ram::RandomAccessMemory},
processor::{interrupts::Interrupt, state::SharedState},
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,
shared_state: Arc<SharedState>,
@@ -30,10 +36,9 @@ pub struct Emulator<Mem: RandomAccessMemory> {
// memory
mmu: MMU,
mainstore: Mem,
mainstore: MemoryBank,
// IO
mmio: Vec<MappedDevice>,
mmio_region: Option<(u32, u32)>, // start end end of segment
// optionals - not necessarily set on boot.
@@ -41,12 +46,13 @@ pub struct Emulator<Mem: RandomAccessMemory> {
// Config params
__mmap_configured: bool,
__io_configured: bool,
time: Instant,
}
unsafe impl<Mem: RandomAccessMemory> Send for Emulator<Mem> {}
impl<Mem: RandomAccessMemory> Emulator<Mem> {
pub fn new(mem: Mem) -> Self {
unsafe impl Send for Emulator {}
impl Emulator {
pub fn new() -> Self {
let (sender, receiver) = mpsc::channel::<u8>();
Self {
@@ -58,24 +64,16 @@ impl<Mem: RandomAccessMemory> Emulator<Mem> {
memory_map: None,
mmu: MMU::new(),
mainstore: mem,
mainstore: MemoryBank::new(),
// mmio
mmio: Vec::new(),
mmio_region: None,
// Config params
__io_configured: false,
__mmap_configured: false,
}
}
pub fn with_device(mut self, device: impl IoDevice + 'static) -> Self {
self.mmio.push(MappedDevice {
base: 0,
device: Box::new(device),
});
self
time: Instant::now(),
}
}
pub fn state_handle(&self) -> Arc<SharedState> {
@@ -88,25 +86,21 @@ impl<Mem: RandomAccessMemory> Emulator<Mem> {
}
#[inline]
pub fn memory_mut(&mut self) -> &mut impl RandomAccessMemory {
pub fn memory_mut(&mut self) -> &mut MemoryBank {
&mut self.mainstore
}
#[must_use]
#[inline]
pub fn reg(&self, reg: Register) -> u32 {
if reg as u8 == Register::Zero as u8 {
return 0;
}
debug_assert!((reg as usize) < ProcessorSnapshot::REG_COUNT);
debug_assert!((reg as u8) < Register::COUNT);
unsafe { *self.internal_state.registers.get_unchecked(reg as usize) }
}
#[inline]
pub fn mut_reg(&mut self, reg: Register) -> &mut u32 {
debug_assert!((reg as usize) < ProcessorSnapshot::REG_COUNT);
debug_assert!((reg as u8) < Register::COUNT);
unsafe {
self.internal_state
.registers
@@ -128,23 +122,6 @@ impl<Mem: RandomAccessMemory> Emulator<Mem> {
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]
fn update(&mut self) {
self.shared_state
@@ -154,7 +131,7 @@ impl<Mem: RandomAccessMemory> Emulator<Mem> {
#[cold]
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());
}
@@ -162,48 +139,89 @@ impl<Mem: RandomAccessMemory> Emulator<Mem> {
MemoryMap::identity_map(&mut self.mmu, map.regions.get(0).unwrap());
}
self.internal_state.running = true;
self.internal_state.halted = false;
Ok(())
}
#[cold]
fn shutdown(&mut self) {
self.internal_state.running = false;
self.internal_state.halted = true;
}
#[cold]
pub fn idle_wait(&mut self) {
self.internal_state.running = false;
pub fn halt(&mut self) {
self.internal_state.halted = true;
// Wait for an interrupt or state update to continue.
loop {
// Check for interrupts.
if let Ok(code) = self.interrupts.recv_timeout(Duration::from_millis(100)) {
self.interrupt(Interrupt::Software(code));
break;
// wait until we're un-halted.
while self.internal_state.halted {
self.wait_for_instructions();
}
}
// // If we've received a request to continue running. DEPRECATED (we can run through interrupts)
// if self.shared_state.running.load(Ordering::Relaxed) {
// panic!("3");
// break;
// }
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));
}
}
}
self.internal_state.running = true;
#[cold]
pub fn wait_for_instructions(&mut self) {
{
// record time for previous execution.
self.internal_state.time = self.time.elapsed();
self.update();
self.time = Instant::now();
}
// if paused from the UI thread, just wait.
while self.shared_state.paused.load(Ordering::Relaxed) {
thread::sleep(Duration::from_millis(10));
// UI is making changes / requesting updates.
self.check_update();
}
// if the cpu didn't halt on it's own then it's ready to run again.
if !self.internal_state.halted {
return;
}
loop {
// Check for interrupts.
if let Ok(code) = self.interrupts.recv_timeout(Duration::from_millis(100)) {
self.internal_state.halted = false;
self.interrupt(Interrupt::Software(code));
break;
}
// UI is resetting the emulator
self.check_update();
}
}
pub fn run(&mut self) -> Result<(), String> {
self.boot()?;
// wait for UI to initialise emulator.
if self.shared_state.paused.load(Ordering::Relaxed) {
self.wait_for_instructions();
}
let mut time = Instant::now();
self.boot()?;
self.time = Instant::now();
'emu: loop {
// so that it always returns zero. this is more performance friendly than checking
// for a zero register each access as it's branchless
*self.mut_reg(Register::Zero) = 0;
// IMPORTANT
// do not change anything about this loop. it's fully optimised afaik.
// Check for commands or hardware Interrupts every 32k cycles
@@ -215,8 +233,8 @@ impl<Mem: RandomAccessMemory> Emulator<Mem> {
}
}
if unlikely(!self.shared_state.running.load(Ordering::Relaxed)) {
self.idle_wait();
if unlikely(self.shared_state.paused.load(Ordering::Relaxed)) {
self.wait_for_instructions();
}
match self.interrupts.try_recv() {
@@ -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 instruction = self.mem_read_word(pc);
@@ -258,23 +258,21 @@ impl<Mem: RandomAccessMemory> Emulator<Mem> {
thread::sleep(Duration::from_micros(10));
}
self.mut_reg(Register::Pcx).add_assign(4);
self.execute(Instruction(instruction));
// Check if executing the interrupt caused a fault.
// Check if the previous instruction caused a fault.
if let Some(fault) = self.pending_fault {
self.pending_fault = None;
println!("WARN fault: {:?}", fault);
self.pending_fault = None;
self.interrupt(fault);
}
self.mut_reg(Register::Pcx).add_assign(4);
self.execute(Instruction(instruction));
// always increment clock.
self.internal_state.clock += 1;
}
self.shutdown();
Ok(())
}
@@ -486,7 +484,7 @@ impl<Mem: RandomAccessMemory> Emulator<Mem> {
Some(Opcode::Int) => {
self.interrupt(Interrupt::Software(word.imm16() as u8));
}
Some(Opcode::Hlt) => self.internal_state.running = false,
Some(Opcode::Hlt) => self.halt(),
Some(Opcode::IRet) => todo!(),
None => {}
}
@@ -507,147 +505,57 @@ impl<Mem: RandomAccessMemory> Emulator<Mem> {
#[inline]
fn mem_read_byte(&mut self, addr: u32) -> u8 {
if unlikely(self.is_mmio(addr)) {
return self.io_read_byte(addr);
}
self.mainstore.read_byte(addr)
}
#[inline]
fn mem_write_byte(&mut self, addr: u32, val: u8) {
if unlikely(self.is_mmio(addr)) {
self.io_write_byte(addr, val);
return;
if addr == 0x40000 {
eprintln!("emulator wrote 0x{:08x} to uart", val);
}
self.mainstore.write_byte(addr, val);
}
#[inline]
fn mem_read_word(&mut self, addr: u32) -> u32 {
if unlikely(self.is_mmio(addr)) {
return self.io_read_word(addr);
}
self.mainstore.read_word(addr)
}
#[inline]
fn mem_write_word(&mut self, addr: u32, val: u32) {
if unlikely(self.is_mmio(addr)) {
self.io_write_word(addr, val);
return;
}
self.mainstore.write_word(addr, val);
}
#[inline]
fn mem_read_page(&mut self, addr: u32) -> &Page {
if unlikely(self.is_mmio(addr)) {
// pages spanning MMIO don't really make sense
// treat as fault
self.pending_fault = Some(Interrupt::ProtectionFault);
return &Page::ZERO;
}
self.mainstore.read_page(addr)
}
#[inline]
fn mem_write_page(&mut self, addr: u32, val: &Page) {
if unlikely(self.is_mmio(addr)) {
self.pending_fault = Some(Interrupt::ProtectionFault);
return;
}
self.mainstore.write_page(addr, val);
}
// single MMIO check reused by all of the above
#[inline]
fn is_mmio(&self, addr: u32) -> bool {
self.mmio_region
.map(|(base, end)| addr >= base && addr < end)
.unwrap_or(false)
}
fn fault(&mut self, interrupt: Interrupt) {
self.pending_fault = Some(interrupt);
}
#[inline]
fn get_device(&mut self, addr: u32) -> Option<(&mut Box<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)]
pub struct ProcessorSnapshot {
pub running: bool,
pub halted: bool,
pub clock: usize,
pub registers: [u32; Self::REG_COUNT],
pub time: Duration,
pub registers: [u32; Register::COUNT as usize],
}
impl Default for ProcessorSnapshot {
fn default() -> Self {
Self {
running: false,
halted: false,
clock: 0,
registers: [0; Self::REG_COUNT],
time: Duration::ZERO,
registers: [0; Register::COUNT as usize],
}
}
}
impl ProcessorSnapshot {
const REG_COUNT: usize = 28;
}
@@ -1,24 +1,27 @@
use std::sync::{Arc, atomic::AtomicBool, mpsc};
use crate::processor::processor::ProcessorSnapshot;
use arc_swap::ArcSwap;
use crate::backend::processor::processor::ProcessorSnapshot;
pub struct SharedState {
pub proc: ArcSwap<ProcessorSnapshot>,
pub running: AtomicBool,
pub reseting: AtomicBool,
pub paused: AtomicBool,
pub update_req: AtomicBool,
sender: mpsc::Sender<u8>,
interrupt_queue: mpsc::Sender<u8>,
}
impl SharedState {
pub fn new(sender: mpsc::Sender<u8>) -> Self {
Self {
reseting: AtomicBool::new(false),
proc: ArcSwap::new(Arc::new(ProcessorSnapshot::default())),
running: AtomicBool::new(true),
sender: sender,
paused: AtomicBool::new(true),
interrupt_queue: sender,
update_req: AtomicBool::new(false),
}
}
+7 -5
View File
@@ -1,11 +1,13 @@
use serde::{Deserialize, Serialize};
use crate::{
Emulator, RandomAccessMemory,
io::DeviceId,
memory::{PhysAddr, mmu::MMU},
Emulator,
backend::memory::{PhysAddr, mmu::MMU},
// io::DeviceId,
};
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
#[repr(u32)]
#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
pub enum RegionType {
@@ -36,7 +38,7 @@ pub struct MemoryMap {
#[derive(Serialize, Deserialize)]
pub struct IoMapping {
pub device: DeviceId,
// pub device: DeviceId,
pub base: u32,
pub size: u32,
}
@@ -51,7 +53,7 @@ impl MemoryMap {
}
}
pub fn apply(&self, cpu: &mut Emulator<impl RandomAccessMemory>) {
pub fn apply(&self, cpu: &mut Emulator) {
for reg in &self.regions {
if reg.identity_map_on_boot {
Self::identity_map(cpu.mmu_mut(), reg);
+15
View File
@@ -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());
}
}
+149
View File
@@ -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);
}
}
+43 -61
View File
@@ -1,80 +1,62 @@
use std::sync::{
Arc,
atomic::{AtomicBool, AtomicU8, Ordering},
};
// use super::{IoAccess, IoHandle};
use crate::{MemoryBank, Page, backend::memory::PhysAddr};
use crate::{
io::{IoAccess, IoDevice},
processor::interrupts::Interrupt,
};
pub struct Display {
width: usize,
height: usize,
addr: PhysAddr,
pub struct DisplayDevice {
buffer: Arc<Box<[AtomicU8]>>,
dirty: Arc<AtomicBool>,
mem: MemoryBank,
buffer: Vec<u8>,
visible: bool,
}
impl DisplayDevice {
pub fn new(width: usize, height: usize) -> (Self, DisplayHandle) {
let buffer = Arc::new(
(0..width * height)
.map(|_| AtomicU8::new(0))
.collect::<Vec<_>>()
.into_boxed_slice(),
);
let dirty = Arc::new(AtomicBool::new(false));
let handle = DisplayHandle {
buffer: Arc::clone(&buffer),
dirty: Arc::clone(&dirty),
};
(Self { buffer, dirty }, handle)
impl Display {
pub fn new(width: u32, height: u32, addr: PhysAddr, mem: MemoryBank) -> Self {
Self {
width: width as usize,
height: height as usize,
addr,
mem,
buffer: Vec::new(),
visible: true,
}
}
impl IoDevice for DisplayDevice {
fn size(&self) -> u32 {
(self.buffer.len()) as u32
}
fn access(&self) -> IoAccess {
IoAccess::WRITE
pub fn visible(&mut self) -> &mut bool {
&mut self.visible
}
fn id(&self) -> super::DeviceId {
super::DeviceId::Display
pub fn width(&self) -> usize {
self.width
}
fn write_word(&mut self, offset: u32, val: u32) -> Result<(), Interrupt> {
let bytes = val.to_le_bytes();
self.buffer[offset as usize].store(bytes[0], Ordering::Relaxed);
self.buffer[offset as usize + 1].store(bytes[1], Ordering::Relaxed);
self.buffer[offset as usize + 2].store(bytes[2], Ordering::Relaxed);
self.buffer[offset as usize + 3].store(bytes[3], Ordering::Relaxed);
self.dirty.store(true, Ordering::Relaxed);
Ok(())
pub fn height(&self) -> usize {
self.height
}
fn write_byte(&mut self, offset: u32, val: u8) -> Result<(), Interrupt> {
self.buffer[offset as usize].store(val, Ordering::Relaxed);
self.dirty.store(true, Ordering::Relaxed);
Ok(())
}
fn internal_read(&self) -> Vec<u8> {
let pages = (0..((self.width * self.height).div_ceil(Page::SIZE)))
.map(|i| {
let page = self.mem.read_page(self.addr + i as u32 * 4096);
page.as_slice().to_owned()
})
.flatten()
.take((self.width * self.height) as usize)
.collect::<Vec<u8>>();
pages
}
pub struct DisplayHandle {
pub buffer: Arc<Box<[AtomicU8]>>,
pub dirty: Arc<AtomicBool>,
pub fn changed(&mut self) -> bool {
let temp = self.internal_read();
if temp != self.buffer {
self.buffer = temp;
return true;
}
false
}
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()
pub fn read(&mut self) -> Vec<u8> {
self.internal_read()
}
}
+35 -113
View File
@@ -1,124 +1,46 @@
use serde::{Deserialize, Serialize};
use crate::{Emulator, RandomAccessMemory, processor::interrupts::Interrupt};
pub mod display;
pub mod serial;
// #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
// pub enum DeviceId {
// Display,
// Serial,
// Random,
// Timer,
// }
pub struct MappedDevice {
pub base: u32,
pub device: Box<dyn IoDevice>,
}
// pub trait IoHandle: Send {
// /// Return the size (in bytes) of the device's addressable region.
// fn size(&self) -> usize;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct IoAccess(u8);
// /// Return the access permissions for the device.
// fn access(&self) -> IoAccess;
impl IoAccess {
pub const READ: Self = Self(0b01);
pub const WRITE: Self = Self(0b10);
// /// Return the DeviceId
// fn id(&self) -> DeviceId;
// }
/// Convenience alias for Read + Write.
pub const READWRITE: Self = Self(Self::READ.0 | Self::WRITE.0);
pub const NONE: Self = Self(0);
// #[derive(Clone, Copy, Debug, PartialEq)]
// pub struct IoAccess(u8);
/// Bitwise OR returns a new `IoAccess` that contains all flags from both operands.
#[inline]
pub fn or(self, other: Self) -> Self {
Self(self.0 | other.0)
}
// impl IoAccess {
// pub const READ: Self = Self(0b01);
// pub const WRITE: Self = Self(0b10);
/// Check if a flag is present.
#[inline]
pub fn contains(&self, flag: Self) -> bool {
self.0 & flag.0 != 0
}
}
// /// Convenience alias for Read + Write.
// pub const READWRITE: Self = Self(Self::READ.0 | Self::WRITE.0);
// pub const NONE: Self = Self(0);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum DeviceId {
Display,
Serial,
Random,
Timer,
}
// /// Bitwise OR returns a new `IoAccess` that contains all flags from both operands.
// #[inline]
// pub fn or(self, other: Self) -> Self {
// Self(self.0 | other.0)
// }
/// Trait representing an input/output device that can be mapped into a memory space.
///
/// - Default methods return dummy data.
pub trait IoDevice: Send {
/// Return the size (in bytes) of the device's addressable region.
fn size(&self) -> u32;
/// Return the access permissions for the device.
fn access(&self) -> IoAccess;
/// Return the DeviceId
fn id(&self) -> DeviceId;
#[inline]
/// Read a single byte from the specified address.
///
/// By default, writeonly 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, readonly 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 32bit word from the specified address in littleendian order.
///
/// The default implementation composes four consecutive byte reads using
/// `read_byte`. If any byte read fails, the whole operation returns `None`.
/// Writeonly 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 32bit word to the specified address in littleendian order.
///
/// The default implementation splits the value into bytes and writes each
/// using `write_byte`. Readonly devices return `false`; otherwise returns
/// `true` after attempting all byte writes. Override for more efficient
/// word writes or to handle partial failures.
fn write_word(&mut self, offset: u32, val: u32) -> Result<(), Interrupt> {
if self.access().contains(IoAccess::WRITE) {
let bytes = val.to_le_bytes();
self.write_byte(offset, bytes[0]);
self.write_byte(offset + 1, bytes[1]);
self.write_byte(offset + 2, bytes[2]);
self.write_byte(offset + 3, bytes[3]);
return Ok(());
}
Err(Interrupt::WriteToReadOnly)
}
}
// /// Check if a flag is present.
// #[inline]
// pub fn contains(&self, flag: Self) -> bool {
// self.0 & flag.0 != 0
// }
// }
+48
View File
@@ -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);
}
}
}
+4 -5
View File
@@ -2,11 +2,10 @@
#![feature(inherent_associated_types)]
pub mod args;
pub mod backend;
pub mod config;
pub mod frontend;
pub mod io;
mod memory;
mod processor;
pub use {config::MemoryMap, processor::processor::Emulator, processor::state::SharedState};
pub use memory::ram::*;
pub use backend::memory::ram::*;
pub use backend::{processor::processor::Emulator, processor::state::SharedState};
+15 -43
View File
@@ -1,4 +1,5 @@
use clap::Parser;
<<<<<<< HEAD
use common::isa::instructions::Instruction;
use dsa::{
args::DsaArgs, io::display::{DisplayDevice, DisplayHandle}, BulkAllocStore, Emulator, Page,
@@ -10,20 +11,22 @@ use std::{
thread,
time::Duration,
};
=======
use common::prelude::Instruction;
use dsa::{Emulator, Page, args::DsaArgs, frontend::run_app};
use std::thread;
const STACK_SIZE: usize = 1024 * 1024 * 16;
>>>>>>> refs/remotes/origin/main
fn main() {
let args = DsaArgs::parse();
let mmap = args.get_memory_map();
let iomap = args.get_io_map();
let mut emulator = Emulator::new().apply_memory_map(args.get_memory_map());
let store = BulkAllocStore::new();
let (display, handle) = DisplayDevice::new(80, 25);
let mut emulator = Emulator::new(store)
.with_device(display)
.apply_memory_map(mmap)
.apply_io_map(iomap)
.unwrap();
let mem_handle = emulator.memory_mut().clone();
let state = emulator.state_handle();
if let Some(bin) = args.get_binary() {
for (i, ch) in bin.chunks(4).enumerate() {
@@ -53,41 +56,10 @@ fn main() {
}
}
let state = emulator.state_handle();
const STACK_SIZE: usize = 1024 * 1024 * 16;
let runner = thread::Builder::new()
let _runner = thread::Builder::new()
.stack_size(STACK_SIZE)
.spawn(move || emulator.run().unwrap())
.unwrap();
let observer = thread::spawn(|| observe(state, handle));
runner.join().unwrap();
thread::sleep(Duration::from_millis(100));
// yeah we don't need this tbh.
// observer.join().unwrap();
}
/// todo: remove this!
fn observe(state: Arc<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!();
}
}
}
run_app(state, mem_handle).unwrap()
}
-107
View File
@@ -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);
}
}
}
-134
View File
@@ -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);
}
}
}
-66
View File
@@ -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;
}
}
-108
View File
@@ -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);
}
-50
View File
@@ -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);
}
}
-103
View File
@@ -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) }
}
}
+133
View File
@@ -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
View File
@@ -3,7 +3,7 @@
include print "./print.dsa"
// "print hello world"
// "prints first 16 fibonacci numbers"
db program: "++++++++++++++++++++++++++++++++++++++++++++
>++++++++++++++++++++++++++++++++
>++++++++++++++++
@@ -35,6 +35,7 @@ db program: "++++++++++++++++++++++++++++++++++++++++++++
<<++..."
db error: "Invalid Instruction!"
dh counter: 0xFF
dw stack: 0x10000
dw input: 0x30000
resb data: 1024
@@ -44,11 +45,15 @@ _init_stack:
ldw stack, bpr
mov bpr, spr
init:
ldw counter, rg7
start:
call bf_reset
// load the start of the program into rg0
lwi program, rg0
lwi data, rg1
// rg0 is our instruction pointer
// rg1 is our data pointer
// rg2 is the value at the data pointer
@@ -98,7 +103,16 @@ loop_start:
pop rg1
pop rg0
db success: "Success!"
end:
call print::newline
lwi success, rg0
push rg0
call print::print
pop zero
subi rg7, 1
jnz rg7, start
hlt
loop_end:
@@ -225,3 +239,34 @@ close_left:
// push zero to the stack
push zero
jmp _traverse_left
// ------------------------------------------
// reset interpreter
// clears screen, resets cursor and clears data buffer.
bf_reset: func
push rg0
push rg1
// clear screen and reset cursor
call print::clear
call print::reset
// clear data buffer (resb data: 1024 = 256 words)
lli 256, rg0
lwi data, rg1
_bf_reset_clear_data:
subi rg0, 1, rg0
stw zero, rg1
addi rg1, 4, rg1
igt rg0, zero, rg4
jnz rg4, _bf_reset_clear_data
pop rg1
pop rg0
// reload interpreter state
lwi program, rg0
lwi data, rg1
lli 0, rg2
return
+65
View File
@@ -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
View File
@@ -185,8 +185,8 @@ print_whitespace: func
addi rg1, 1
jmp _end
// // ------------------------------------------
// // print newline
// ------------------------------------------
// print newline
// print_newline:
// push bpr
// mov spr, bpr
@@ -214,6 +214,29 @@ print_whitespace: func
// jmp _end
newline:
push bpr
mov spr, bpr
ldw display, rg0
ldw current, rg1
sub rg1, rg0, rg2 // offset from base
lli 80, rg3
_newline_mod:
ilt rg2, rg3, rg4
jnz rg4, _newline_done
sub rg2, rg3, rg2
jmp _newline_mod
_newline_done:
// rg2 = offset % 80 (position within current line)
sub rg1, rg2, rg1 // go back to line start
addi rg1, 80, rg1 // advance to next line
jmp _end
// ------------------------------------------
// prints arg[0] as a decimal number to the screen.
// print_num:
+63
View File
@@ -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
+14
View File
@@ -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