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:
@@ -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],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user