megacommit:

- setup new UI layout using tiling and an action/message based system for updating global state based on interactions with individual tiles and vice versa
- added custom dialogs and UI helpers to create a more cohesive UI
- added a disassembler widget (WIP)
- added a documentation widget (WIP)
- refactored backend halt/pause logic to fix bugs.
- started emulator implementation documentation (to complement the ISA spec)
This commit is contained in:
2026-07-19 01:18:00 +01:00
parent a306575cdd
commit adfba876d2
32 changed files with 1890 additions and 427 deletions
+8 -1
View File
@@ -136,7 +136,7 @@ impl MemoryBank {
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);
@@ -148,6 +148,13 @@ impl MemoryBank {
unsafe { self.heap.add(addr as usize).write_volatile(value) }
}
#[inline]
pub fn write_bytes(&self, addr: PhysAddr, bytes: &[u8]) {
unsafe {
self.heap.add(addr as usize).copy_from(bytes.as_ptr(), bytes.len());
}
}
#[inline(always)]
pub fn write_word(&self, addr: PhysAddr, value: u32) {
unsafe { (self.heap.add(addr as usize) as *mut u32).write_volatile(value) };
@@ -26,6 +26,8 @@ use crate::{
};
use crate::config::{MemoryMap, RegionType};
use crate::memory::ram::Page;
use crate::processor::state::{ProcessorSnapshot, RunningState};
use crate::processor::state::RunningState::{Halted, Paused, Running, Shutdown};
pub struct Emulator {
internal_state: ProcessorSnapshot,
@@ -124,7 +126,7 @@ impl Emulator {
}
#[cold]
fn update(&mut self) {
fn share_state(&mut self) {
self.shared_state
.proc
.store(Arc::new(self.internal_state.clone()));
@@ -140,90 +142,87 @@ impl Emulator {
MemoryMap::identity_map(&mut self.mmu, map.regions.get(0).unwrap());
}
self.internal_state.halted = false;
self.internal_state.running = Running;
Ok(())
}
#[cold]
fn shutdown(&mut self) {
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 let x = self.shared_state.goto.load(Ordering::Relaxed) && x != -1 {
*self.mut_reg(Register::Pcx) = x as u32;
self.shared_state.goto.store(-1, Ordering::Relaxed);
}
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.halted = false;
}
}
#[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();
}
pub fn run_state_halted(&mut self, prior: RunningState) {
self.internal_state.running = Halted;
// 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();
}
// Responsibility: keep track of the time spent in the running state across multiple hlt/interrupt cycles
self.internal_state.time += self.time.elapsed();
loop {
// Check for interrupts.
// Transition: interrupt received
if let Ok(int) = self.interrupts.recv_timeout(Duration::from_millis(100)) {
self.internal_state.halted = false;
self.internal_state.running = Running;
self.interrupt(int);
break;
}
// if the cpu didn't halt on it's own then it's ready to run again.
if !self.internal_state.halted {
return;
// Transition: pause signal received
if self.shared_state.paused.load(Ordering::Relaxed) {
self.run_state_paused(Halted);
}
// UI is resetting the ui
self.check_update();
// Responsibility: update UI when requested
if self.shared_state.update_req.load(Ordering::Relaxed) == true {
self.share_state();
}
}
// Responsibility: reset `private.time` on state exit
self.time = Instant::now();
self.internal_state.running = prior;
}
#[cold]
pub fn run_state_paused(&mut self, prior: RunningState) {
self.internal_state.running = Paused;
// Responsibility: save the running-time since last pause state.
self.internal_state.time = self.time.elapsed();
self.share_state();
loop {
// Responsibility: update UI when requested
if self.shared_state.update_req.load(Ordering::Relaxed) == true {
self.share_state();
}
// Responsibility: reset when requested by the UI
if self.shared_state.reseting.load(Ordering::Relaxed) == true {
self.internal_state.registers = [0; Register::COUNT as usize];
self.internal_state.clock = 0;
while self.shared_state.reseting.load(Ordering::Relaxed) == true {
thread::sleep(Duration::from_millis(100));
}
}
// Transition: run signal received
if self.shared_state.paused.load(Ordering::Relaxed) == false {
break;
}
}
// Responsibility: reset private.time, shared.time, shared.cycles on state exit
self.time = Instant::now();
self.internal_state.time = Duration::new(0, 0);
self.internal_state.clock = 0;
self.internal_state.running = prior;
}
pub fn run(&mut self) -> Result<(), String> {
// wait for UI to initialise ui.
// wait for UI to initialise.
if self.shared_state.paused.load(Ordering::Relaxed) {
self.wait_for_instructions();
self.run_state_paused(Running)
}
self.boot()?;
self.time = Instant::now();
'emu: loop {
// so that it always returns zero. this is more performance friendly than checking
@@ -234,17 +233,21 @@ impl Emulator {
// do not change anything about this loop. it's fully optimised afaik.
// Check for commands or hardware Interrupts every 32k cycles
if unlikely(self.internal_state.clock & 0x7FFF == 0) {
// Responsibility: keep the UI thread up to date periodically
// Update UI thread (roughly every 512k cycles targeting 60 UPS)
if unlikely(self.internal_state.clock & 0x7FFFF == 0) {
if self.shared_state.update_req.load(Ordering::Relaxed) {
self.update();
self.share_state();
}
}
// Transition: Paused
if unlikely(self.shared_state.paused.load(Ordering::Relaxed)) {
self.wait_for_instructions();
self.run_state_paused(Running)
}
// Responsibility: Handle interrupts
match self.interrupts.try_recv() {
Ok(int) => self.interrupt(int),
Err(TryRecvError::Disconnected) => break 'emu,
@@ -252,6 +255,7 @@ impl Emulator {
}
}
// Responsibility: run fetch-decode-execute cycle.
let pc = self.reg(Register::Pcx);
let instruction = self.mem_read_word(pc);
@@ -280,7 +284,7 @@ impl Emulator {
self.internal_state.clock += 1;
}
self.shutdown();
self.internal_state.running = Shutdown;
Ok(())
}
@@ -512,7 +516,9 @@ impl Emulator {
Some(Opcode::Int) => {
self.interrupt(Interrupt::Software(word.imm16() as u8));
}
Some(Opcode::Hlt) => self.halt(),
Some(Opcode::Hlt) => {
self.run_state_halted(Running);
},
Some(Opcode::IRet) => {
self.pop(Register::Ret);
*self.mut_reg(Register::Pcx) = self.reg(Register::Ret);
@@ -569,21 +575,3 @@ impl Emulator {
}
}
#[derive(Debug, Clone)]
pub struct ProcessorSnapshot {
pub halted: bool,
pub clock: usize,
pub time: Duration,
pub registers: [u32; Register::COUNT as usize],
}
impl Default for ProcessorSnapshot {
fn default() -> Self {
Self {
halted: false,
clock: 0,
time: Duration::ZERO,
registers: [0; Register::COUNT as usize],
}
}
}
@@ -1,15 +1,13 @@
use std::sync::{Arc, atomic::AtomicBool, mpsc};
use std::sync::atomic::AtomicIsize;
use std::time::Duration;
use arc_swap::ArcSwap;
use crate::processor::processor::ProcessorSnapshot;
use common::prelude::Register;
use super::interrupts::Interrupt;
pub struct SharedState {
pub proc: ArcSwap<ProcessorSnapshot>,
pub goto: AtomicIsize,
pub reseting: AtomicBool,
pub paused: AtomicBool,
pub update_req: AtomicBool,
@@ -22,10 +20,37 @@ impl SharedState {
reseting: AtomicBool::new(false),
proc: ArcSwap::new(Arc::new(ProcessorSnapshot::default())),
goto: AtomicIsize::new(-1),
paused: AtomicBool::new(true),
interrupt_queue: sender,
update_req: AtomicBool::new(false),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum RunningState {
Paused,
Halted,
Running,
Shutdown,
Breakpoint(u32),
}
#[derive(Debug, Clone)]
pub struct ProcessorSnapshot {
pub running: RunningState,
pub clock: usize,
pub time: Duration,
pub registers: [u32; Register::COUNT as usize],
}
impl Default for ProcessorSnapshot {
fn default() -> Self {
Self {
running: RunningState::Paused,
clock: 0,
time: Duration::ZERO,
registers: [0; Register::COUNT as usize],
}
}
}