refactor: fully moved to tile-based UI layout and removed most remnants of old layout (aside from editor which needs to be implemented properly before it's fully replaced.
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::config::default::{DEFAULT_IO_MAP, DEFAULT_MEMORY_MAP};
|
||||
use crate::memory::mmu::MMU;
|
||||
use crate::memory::PhysAddr;
|
||||
use crate::processor::processor::Emulator;
|
||||
use crate::memory::mmu::MMU;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::processor::DsaProcessor;
|
||||
|
||||
mod default;
|
||||
|
||||
@@ -68,7 +68,7 @@ impl MemoryMap {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply(&self, cpu: &mut Emulator) {
|
||||
pub fn apply(&self, cpu: &mut DsaProcessor) {
|
||||
for reg in &self.regions {
|
||||
if reg.identity_map_on_boot {
|
||||
Self::identity_map(cpu.mmu_mut(), reg);
|
||||
|
||||
@@ -1,5 +1,212 @@
|
||||
#![feature(likely_unlikely)]
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::thread;
|
||||
use std::thread::{JoinHandle, Thread};
|
||||
use std::time::Duration;
|
||||
use common::prelude::{Instruction, Register};
|
||||
use crate::config::{IoMap, MemoryMap};
|
||||
use crate::InternalImplementation::{Handle, Owned};
|
||||
use crate::memory::PhysAddr;
|
||||
use crate::memory::ram::MemoryBank;
|
||||
use crate::processor::{Interrupt, RunningState, SharedState};
|
||||
|
||||
pub mod processor;
|
||||
pub mod memory;
|
||||
pub mod config;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DsaError {
|
||||
BootFailure(String),
|
||||
CommunicationError(String),
|
||||
CriticalException(String),
|
||||
}
|
||||
|
||||
/// This trait should be implemented for any type that can act as a fully functional DSA emulator with the ability to share state
|
||||
pub trait DsaImplementation: Send {
|
||||
/// may be modified in future with extra args like config
|
||||
/// having direct control over the emulator's POST/boot state can be useful for
|
||||
/// integration testing.
|
||||
fn boot(&mut self) -> Result<(), DsaError>;
|
||||
|
||||
/// main run method. the main-loop of the program execution
|
||||
fn run(&mut self) -> Result<(), DsaError>;
|
||||
|
||||
/// memory mapping is required for emulator memory sections
|
||||
fn apply_memory_map(&mut self, map: MemoryMap);
|
||||
|
||||
/// IO mapping also
|
||||
fn apply_io_mapping(&mut self, map: IoMap);
|
||||
|
||||
/// List all memory devices
|
||||
/// returns a copy of the IOmap (cannot be a reference since across-threads)
|
||||
fn get_io_map(&self) -> IoMap;
|
||||
|
||||
/// returns a wrapper around the physical memory used by the emulator
|
||||
fn get_memory_handle(&mut self) -> &mut MemoryBank;
|
||||
|
||||
/// returns a wrapper around the shared state of the emulator
|
||||
fn get_state_handle(&mut self) -> Arc<SharedState>;
|
||||
}
|
||||
|
||||
// pub trait DsaDebugImplementation: DsaImplementation {
|
||||
// fn step()
|
||||
// }
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EmulatorHandle {
|
||||
memory: MemoryBank,
|
||||
state: Arc<SharedState>,
|
||||
}
|
||||
|
||||
impl EmulatorHandle {
|
||||
|
||||
pub fn read_byte(&mut self, addr: PhysAddr) -> u8 {
|
||||
self.memory.read_byte(addr)
|
||||
}
|
||||
|
||||
pub fn write_byte(&mut self, addr: PhysAddr, val: u8) {
|
||||
self.memory.write_byte(addr, val);
|
||||
}
|
||||
|
||||
pub fn mem_read(&mut self, addr: PhysAddr, size: u32) -> Vec<u8> {
|
||||
self.memory.read_bytes(addr, size as usize).to_vec()
|
||||
}
|
||||
|
||||
pub fn mem_write(&mut self, addr: PhysAddr, data: &[u8]) {
|
||||
self.memory.write_bytes(addr, data);
|
||||
}
|
||||
|
||||
pub fn interrupt(&mut self, code: Interrupt) -> Result<(), DsaError> {
|
||||
self.state.interrupt_queue.send(code)
|
||||
.map_err(|_| DsaError::CommunicationError("interrupt failed".into()) )
|
||||
}
|
||||
|
||||
pub fn request_update(&mut self) {
|
||||
self.state.update_req.store(true, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn status(&self) -> RunningState {
|
||||
self.state.proc.load().running.clone()
|
||||
}
|
||||
|
||||
pub fn pause(&mut self) {
|
||||
self.state.paused.store(true, Ordering::Relaxed);
|
||||
}
|
||||
pub fn resume(&mut self) {
|
||||
self.state.paused.store(false, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.state.reseting.store(true, Ordering::Relaxed);
|
||||
self.memory.clear();
|
||||
self.state.reseting.store(false, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn get_register(&self, rg: Register) -> u32 {
|
||||
self.state.proc.load().registers[rg as usize]
|
||||
}
|
||||
|
||||
pub fn registers(&self) -> [u32; Register::COUNT as usize] {
|
||||
self.state.proc.load().registers.clone()
|
||||
}
|
||||
|
||||
pub fn clock(&self) -> usize {
|
||||
self.state.proc.load().clock
|
||||
}
|
||||
|
||||
pub fn time(&self) -> Duration {
|
||||
self.state.proc.load().time
|
||||
}
|
||||
}
|
||||
|
||||
enum InternalImplementation {
|
||||
Owned(Box<dyn DsaImplementation + Send>),
|
||||
Handle(JoinHandle<()>),
|
||||
}
|
||||
|
||||
pub struct EmulatorController {
|
||||
// the emulator itself
|
||||
internal: Option<InternalImplementation>,
|
||||
handle: EmulatorHandle,
|
||||
}
|
||||
|
||||
impl AsMut<EmulatorHandle> for EmulatorController {
|
||||
fn as_mut(&mut self) -> &mut EmulatorHandle {
|
||||
&mut self.handle
|
||||
}
|
||||
}
|
||||
|
||||
impl EmulatorController {
|
||||
|
||||
pub fn new(mut emulator: Box<dyn DsaImplementation>) -> Self {
|
||||
Self {
|
||||
handle: EmulatorHandle {
|
||||
memory: emulator.get_memory_handle().clone(),
|
||||
state: emulator.get_state_handle().clone(),
|
||||
},
|
||||
internal: Some(Owned(emulator)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> Result<(), DsaError> {
|
||||
const STACK_SIZE: usize = 1024 * 1024 * 16;
|
||||
|
||||
let Some(Owned(mut handle)) = self.internal.take() else {
|
||||
return Err(DsaError::CriticalException(String::from("owned emulator returned none!")))
|
||||
};
|
||||
|
||||
self.internal = Some(Handle(
|
||||
thread::Builder::new()
|
||||
.stack_size(STACK_SIZE)
|
||||
.spawn(move || {handle.run().unwrap() })
|
||||
.unwrap()
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_handle(&mut self) -> &mut EmulatorHandle {
|
||||
&mut self.handle
|
||||
}
|
||||
|
||||
pub fn map_memory(&mut self, map: MemoryMap) -> Result<(), DsaError> {
|
||||
let Some(Owned(t)) = &self.internal else {
|
||||
return Err(DsaError::BootFailure("Emulator already initialised".into()));
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn shutdown(&mut self) {
|
||||
// if let Some(handle) = &self.handle {
|
||||
// handle.request_shutdown(); // sets an atomic the run loop checks each poll
|
||||
// }
|
||||
todo!();
|
||||
if let Some(InternalImplementation::Handle(join_handle)) = self.internal.take() {
|
||||
let _ = join_handle.join(); // wait for the thread to actually exit
|
||||
}
|
||||
self.internal = None;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -137,6 +137,16 @@ impl MemoryBank {
|
||||
unsafe { self.heap.add(addr as usize).read_volatile() }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn read_bytes(&self, addr: PhysAddr, size: usize) -> &[u8] {
|
||||
let start = addr as usize;
|
||||
assert!(start + size <= u32::MAX as usize, "read out of bounds");
|
||||
|
||||
unsafe {
|
||||
std::slice::from_raw_parts(self.heap.add(start), size)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn read_page(&self, addr: PhysAddr) -> &Page {
|
||||
debug_assert_eq!(addr % 4096, 0);
|
||||
|
||||
+127
-324
@@ -1,321 +1,48 @@
|
||||
use std::{
|
||||
hint::unlikely,
|
||||
ops::AddAssign,
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::Ordering,
|
||||
mpsc::{self, TryRecvError},
|
||||
},
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use common::isa::instructions::Opcode;
|
||||
use common::prelude::{Instruction, Register};
|
||||
use crate::config::{IoMap, MemoryMap, RegionType};
|
||||
use crate::DsaError;
|
||||
use crate::DsaError::BootFailure;
|
||||
use crate::memory::mmu::MMU;
|
||||
use crate::memory::ram::{MemoryBank, Page};
|
||||
use crate::processor::interrupts::Interrupt;
|
||||
use crate::processor::state::{ProcessorSnapshot, SharedState};
|
||||
use crate::processor::state::RunningState::Running;
|
||||
|
||||
use common::{
|
||||
isa::instructions::{Instruction, Opcode},
|
||||
isa::register::Register
|
||||
};
|
||||
use crate::{
|
||||
memory::{
|
||||
mmu::MMU,
|
||||
ram::MemoryBank
|
||||
},
|
||||
processor::{
|
||||
interrupts::Interrupt,
|
||||
state::SharedState
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum ExecutionResult {
|
||||
Continue,
|
||||
Halt,
|
||||
Interrupt(Interrupt),
|
||||
}
|
||||
};
|
||||
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,
|
||||
shared_state: Arc<SharedState>,
|
||||
pub struct DsaProcessor {
|
||||
pub internal_state: ProcessorSnapshot,
|
||||
|
||||
// Interrupts
|
||||
interrupts: mpsc::Receiver<Interrupt>,
|
||||
pending_fault: Option<Interrupt>,
|
||||
pub(crate) mmu: MMU,
|
||||
pub(crate) mainstore: MemoryBank,
|
||||
|
||||
// memory
|
||||
mmu: MMU,
|
||||
mainstore: MemoryBank,
|
||||
|
||||
// IO
|
||||
mmio_region: Option<(u32, u32)>, // start end end of segment
|
||||
|
||||
// optionals - not necessarily set on boot.
|
||||
mmio_region: Option<(u32, u32)>,
|
||||
memory_map: Option<MemoryMap>,
|
||||
|
||||
// Config params
|
||||
__mmap_configured: bool,
|
||||
|
||||
time: Instant,
|
||||
}
|
||||
|
||||
unsafe impl Send for Emulator {}
|
||||
impl Emulator {
|
||||
impl DsaProcessor {
|
||||
pub fn new() -> Self {
|
||||
let (sender, receiver) = mpsc::channel::<Interrupt>();
|
||||
|
||||
Self {
|
||||
interrupts: receiver,
|
||||
pending_fault: None,
|
||||
|
||||
internal_state: ProcessorSnapshot::default(),
|
||||
shared_state: Arc::new(SharedState::new(sender)),
|
||||
|
||||
memory_map: None,
|
||||
mmu: MMU::new(),
|
||||
mainstore: MemoryBank::new(),
|
||||
|
||||
// mmio
|
||||
mmio_region: None,
|
||||
|
||||
// Config params
|
||||
memory_map: None,
|
||||
__mmap_configured: false,
|
||||
|
||||
time: Instant::now(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn state_handle(&self) -> Arc<SharedState> {
|
||||
self.shared_state.clone()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn mmu_mut(&mut self) -> &mut MMU {
|
||||
&mut self.mmu
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn memory_mut(&mut self) -> &mut MemoryBank {
|
||||
&mut self.mainstore
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn reg(&self, reg: Register) -> u32 {
|
||||
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 u8) < Register::COUNT);
|
||||
unsafe {
|
||||
self.internal_state
|
||||
.registers
|
||||
.get_unchecked_mut(reg as usize)
|
||||
}
|
||||
}
|
||||
|
||||
#[cold]
|
||||
pub fn apply_memory_map(mut self, map: MemoryMap) -> Self {
|
||||
self.mmio_region = map
|
||||
.regions
|
||||
.iter()
|
||||
.find(|r| matches!(r.region_type, RegionType::MMIO))
|
||||
.map(|r| (r.base, r.base + r.size));
|
||||
|
||||
map.apply(&mut self);
|
||||
self.memory_map = Some(map);
|
||||
self.__mmap_configured = true;
|
||||
self
|
||||
}
|
||||
|
||||
#[cold]
|
||||
fn share_state(&mut self) {
|
||||
self.shared_state
|
||||
.proc
|
||||
.store(Arc::new(self.internal_state.clone()));
|
||||
}
|
||||
|
||||
#[cold]
|
||||
fn boot(&mut self) -> Result<(), String> {
|
||||
if !(self.__mmap_configured) {
|
||||
return Err("Emulator<Mem> not configured".to_string());
|
||||
}
|
||||
|
||||
if let Some(map) = &self.memory_map {
|
||||
MemoryMap::identity_map(&mut self.mmu, map.regions.get(0).unwrap());
|
||||
}
|
||||
|
||||
self.internal_state.running = Running;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
#[cold]
|
||||
pub fn run_state_halted(&mut self, prior: RunningState) {
|
||||
self.internal_state.running = Halted;
|
||||
|
||||
// Responsibility: keep track of the time spent in the running state across multiple hlt/interrupt cycles
|
||||
self.internal_state.time += self.time.elapsed();
|
||||
|
||||
loop {
|
||||
// Transition: interrupt received
|
||||
if let Ok(int) = self.interrupts.recv_timeout(Duration::from_millis(100)) {
|
||||
self.internal_state.running = Running;
|
||||
self.interrupt(int);
|
||||
break;
|
||||
}
|
||||
|
||||
// Transition: pause signal received
|
||||
if self.shared_state.paused.load(Ordering::Relaxed) {
|
||||
self.run_state_paused(Halted);
|
||||
}
|
||||
|
||||
// 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.
|
||||
if self.shared_state.paused.load(Ordering::Relaxed) {
|
||||
self.run_state_paused(Running)
|
||||
}
|
||||
|
||||
self.boot()?;
|
||||
|
||||
'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
|
||||
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.share_state();
|
||||
}
|
||||
}
|
||||
|
||||
// Transition: Paused
|
||||
if unlikely(self.shared_state.paused.load(Ordering::Relaxed)) {
|
||||
self.run_state_paused(Running)
|
||||
}
|
||||
|
||||
// Responsibility: Handle interrupts
|
||||
match self.interrupts.try_recv() {
|
||||
Ok(int) => self.interrupt(int),
|
||||
Err(TryRecvError::Disconnected) => break 'emu,
|
||||
Err(TryRecvError::Empty) => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Responsibility: run fetch-decode-execute cycle.
|
||||
let pc = self.reg(Register::Pcx);
|
||||
let instruction = self.mem_read_word(pc);
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
println!(
|
||||
"Clock: {} Executing {:?} PCX: {}, reg: {:?}",
|
||||
self.internal_state.clock,
|
||||
Instruction(instruction),
|
||||
pc,
|
||||
self.internal_state.registers
|
||||
);
|
||||
thread::sleep(Duration::from_micros(10));
|
||||
}
|
||||
|
||||
// Check if the previous instruction caused a fault.
|
||||
if let Some(fault) = self.pending_fault {
|
||||
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.internal_state.running = Shutdown;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn interrupt(&mut self, int: Interrupt) {
|
||||
println!("Interrupt while at address {:04X}", self.reg(Register::Pcx));
|
||||
|
||||
self.push(Register::Pcx);
|
||||
*self.mut_reg(Register::Pcx) =
|
||||
self.mem_read_word(self.reg(Register::Idr) + int.code() as u32 * 4);
|
||||
|
||||
println!(
|
||||
"Jumping to interrupt at address {:04X}",
|
||||
self.reg(Register::Pcx)
|
||||
);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn push(&mut self, reg: Register) {
|
||||
*self.mut_reg(Register::Spr) -= 4;
|
||||
self.mem_write_word(self.reg(Register::Spr), self.reg(reg));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn pop(&mut self, reg: Register) {
|
||||
*self.mut_reg(reg) = self.mem_read_word(self.reg(Register::Spr));
|
||||
*self.mut_reg(Register::Spr) += 4;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn execute(&mut self, word: Instruction) {
|
||||
pub fn execute(&mut self, word: Instruction) -> ExecutionResult {
|
||||
// This needs to be unsafe as we're using word.xxxx_uc() functions for decoding which are unsafe
|
||||
// as they perform unchecked transmute operations (performance critical)
|
||||
unsafe {
|
||||
@@ -334,8 +61,8 @@ impl Emulator {
|
||||
}
|
||||
|
||||
// load
|
||||
Some(Opcode::Ldbs) => todo!(),
|
||||
Some(Opcode::Ldhs) => todo!(),
|
||||
Some(Opcode::Ldbs) => return ExecutionResult::Interrupt(Interrupt::InvalidOpcode),
|
||||
Some(Opcode::Ldhs) => return ExecutionResult::Interrupt(Interrupt::InvalidOpcode),
|
||||
Some(Opcode::Ldb) => {
|
||||
*self.mut_reg(word.dest_uc()) = u32::from(
|
||||
self.mem_read_byte(self.reg(word.src1_uc()) + word.imm16() as u32),
|
||||
@@ -434,9 +161,8 @@ impl Emulator {
|
||||
self.reg(word.dest_uc()) + word.imm16() as u32
|
||||
}
|
||||
}
|
||||
Some(Opcode::Jnc) => todo!(),
|
||||
Some(Opcode::Jic) => todo!(),
|
||||
|
||||
Some(Opcode::Jnc) => return ExecutionResult::Interrupt(Interrupt::InvalidOpcode),
|
||||
Some(Opcode::Jic) => return ExecutionResult::Interrupt(Interrupt::InvalidOpcode),
|
||||
// Bitwise
|
||||
Some(Opcode::And) => {
|
||||
*self.mut_reg(word.dest_uc()) =
|
||||
@@ -512,13 +238,6 @@ impl Emulator {
|
||||
self.pop(Register::Ret);
|
||||
*self.mut_reg(Register::Pcx) = self.reg(Register::Ret);
|
||||
}
|
||||
|
||||
Some(Opcode::Int) => {
|
||||
self.interrupt(Interrupt::Software(word.imm16() as u8));
|
||||
}
|
||||
Some(Opcode::Hlt) => {
|
||||
self.run_state_halted(Running);
|
||||
},
|
||||
Some(Opcode::IRet) => {
|
||||
self.pop(Register::Ret);
|
||||
*self.mut_reg(Register::Pcx) = self.reg(Register::Ret);
|
||||
@@ -528,50 +247,134 @@ impl Emulator {
|
||||
self.reg(Register::Pcx)
|
||||
);
|
||||
}
|
||||
None => {}
|
||||
Some(Opcode::Int) => return ExecutionResult::Interrupt(Interrupt::Software(word.imm16() as u8)),
|
||||
Some(Opcode::Hlt) => return ExecutionResult::Halt,
|
||||
None => return ExecutionResult::Interrupt(Interrupt::InvalidOpcode),
|
||||
}
|
||||
};
|
||||
|
||||
ExecutionResult::Continue
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn interrupt(&mut self, int: Interrupt) {
|
||||
self.push(Register::Pcx);
|
||||
*self.mut_reg(Register::Pcx) =
|
||||
self.mem_read_word(self.reg(Register::Idr) + int.code() as u32 * 4);
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn reg(&self, reg: Register) -> u32 {
|
||||
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 u8) < Register::COUNT);
|
||||
unsafe {
|
||||
self.internal_state
|
||||
.registers
|
||||
.get_unchecked_mut(reg as usize)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn mem_read_byte(&mut self, addr: u32) -> u8 {
|
||||
pub fn push(&mut self, reg: Register) {
|
||||
*self.mut_reg(Register::Spr) -= 4;
|
||||
self.mem_write_word(self.reg(Register::Spr), self.reg(reg));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn pop(&mut self, reg: Register) {
|
||||
*self.mut_reg(reg) = self.mem_read_word(self.reg(Register::Spr));
|
||||
*self.mut_reg(Register::Spr) += 4;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn mem_read_byte(&mut self, addr: u32) -> u8 {
|
||||
self.mainstore.read_byte(addr)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn mem_write_byte(&mut self, addr: u32, val: u8) {
|
||||
if addr == 0x40000 || addr == 0x40001 || addr == 0x40002 || addr == 0x40003 {
|
||||
eprintln!("ui wrote 0x{:08x} to uart + {}", val, addr - 0x40000);
|
||||
|
||||
if addr == 0x40001 || addr == 0x40003 {
|
||||
eprintln!("writing char {} to uart + {}", val as char, addr - 0x40000);
|
||||
}
|
||||
}
|
||||
pub fn mem_write_byte(&mut self, addr: u32, val: u8) {
|
||||
// TODO: how can we extract this method for debugging?
|
||||
// if addr == 0x40000 || addr == 0x40001 || addr == 0x40002 || addr == 0x40003 {
|
||||
// if addr == 0x40001 || addr == 0x40003 {
|
||||
// eprintln!("writing char {} to uart + {}", val as char, addr - 0x40000);
|
||||
// }
|
||||
// }
|
||||
self.mainstore.write_byte(addr, val);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn mem_read_word(&mut self, addr: u32) -> u32 {
|
||||
pub fn mem_read_word(&mut self, addr: u32) -> u32 {
|
||||
self.mainstore.read_word(addr)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn mem_write_word(&mut self, addr: u32, val: u32) {
|
||||
pub fn mem_write_word(&mut self, addr: u32, val: u32) {
|
||||
self.mainstore.write_word(addr, val);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn mem_read_page(&mut self, addr: u32) -> &Page {
|
||||
pub fn mem_read_page(&mut self, addr: u32) -> &Page {
|
||||
self.mainstore.read_page(addr)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn mem_write_page(&mut self, addr: u32, val: &Page) {
|
||||
pub fn mem_write_page(&mut self, addr: u32, val: &Page) {
|
||||
self.mainstore.write_page(addr, val);
|
||||
}
|
||||
|
||||
fn fault(&mut self, interrupt: Interrupt) {
|
||||
self.pending_fault = Some(interrupt);
|
||||
#[inline]
|
||||
pub fn mmu_mut(&mut self) -> &mut MMU {
|
||||
&mut self.mmu
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn memory_mut(&mut self) -> &mut MemoryBank {
|
||||
&mut self.mainstore
|
||||
}
|
||||
|
||||
pub fn apply_memory_map(&mut self, map: MemoryMap) {
|
||||
self.mmio_region = map
|
||||
.regions
|
||||
.iter()
|
||||
.find(|r| matches!(r.region_type, RegionType::MMIO))
|
||||
.map(|r| (r.base, r.base + r.size));
|
||||
|
||||
map.apply(self);
|
||||
self.memory_map = Some(map);
|
||||
self.__mmap_configured = true;
|
||||
}
|
||||
|
||||
pub fn boot(&mut self) -> Result<(), DsaError> {
|
||||
if !(self.__mmap_configured) {
|
||||
return Err(BootFailure("Emulator<Mem> not configured".to_string()));
|
||||
}
|
||||
|
||||
if let Some(map) = &self.memory_map {
|
||||
MemoryMap::identity_map(&mut self.mmu, map.regions.get(0).unwrap());
|
||||
}
|
||||
|
||||
self.internal_state.running = Running;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn apply_io_mapping(&mut self, map: IoMap) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn get_io_map(&self) -> IoMap {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for DsaProcessor {
|
||||
fn drop(&mut self) {
|
||||
// drop is only ever called once on an emulator instance, so we can be sure a double free will not happen.
|
||||
unsafe { self.mainstore.dealloc() }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
use crate::config::{IoMap, MemoryMap};
|
||||
use crate::memory::ram::MemoryBank;
|
||||
use crate::processor::dsa_core::{DsaProcessor, ExecutionResult};
|
||||
use crate::processor::interrupts::Interrupt;
|
||||
use crate::processor::state::RunningState::{Halted, Paused, Running, Shutdown};
|
||||
use crate::processor::state::{RunningState, SharedState};
|
||||
use crate::{DsaError, DsaImplementation};
|
||||
use common::prelude::{Instruction, Register};
|
||||
use std::hint::unlikely;
|
||||
use std::ops::AddAssign;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::{Arc, mpsc};
|
||||
use std::sync::mpsc::TryRecvError;
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
pub struct Emulator {
|
||||
core: DsaProcessor,
|
||||
|
||||
shared_state: Arc<SharedState>,
|
||||
|
||||
// Interrupts
|
||||
interrupts: mpsc::Receiver<Interrupt>,
|
||||
pending_fault: Option<Interrupt>,
|
||||
|
||||
time: Instant,
|
||||
}
|
||||
|
||||
impl DsaImplementation for Emulator {
|
||||
#[cold]
|
||||
fn boot(&mut self) -> Result<(), DsaError> {
|
||||
self.core.boot()
|
||||
}
|
||||
|
||||
fn run(&mut self) -> Result<(), DsaError> {
|
||||
// wait for UI to initialise.
|
||||
if self.shared_state.paused.load(Ordering::Relaxed) {
|
||||
self.run_state_paused(Running)
|
||||
}
|
||||
|
||||
self.boot()?;
|
||||
|
||||
'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.core.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
|
||||
if unlikely(self.core.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.core.internal_state.clock & 0x7FFFF == 0) {
|
||||
if self.shared_state.update_req.load(Ordering::Relaxed) {
|
||||
self.core.internal_state.time = self.time.elapsed();
|
||||
self.share_state();
|
||||
}
|
||||
}
|
||||
|
||||
// Transition: Paused
|
||||
if unlikely(self.shared_state.paused.load(Ordering::Relaxed)) {
|
||||
self.run_state_paused(Running);
|
||||
}
|
||||
|
||||
// Responsibility: Handle interrupts
|
||||
match self.interrupts.try_recv() {
|
||||
Ok(int) => self.core.interrupt(int),
|
||||
Err(TryRecvError::Disconnected) => break 'emu,
|
||||
Err(TryRecvError::Empty) => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Responsibility: run fetch-decode-execute cycle.
|
||||
let pc = self.core.reg(Register::Pcx);
|
||||
let instruction = self.core.mem_read_word(pc);
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
println!(
|
||||
"Clock: {} Executing {:?} PCX: {}, reg: {:?}",
|
||||
self.core.internal_state.clock,
|
||||
Instruction(instruction),
|
||||
pc,
|
||||
self.core.internal_state.registers
|
||||
);
|
||||
thread::sleep(Duration::from_micros(10));
|
||||
}
|
||||
|
||||
// Check if the previous instruction caused a fault.
|
||||
if let Some(fault) = self.pending_fault {
|
||||
println!("WARN fault: {:?}", fault);
|
||||
self.pending_fault = None;
|
||||
self.core.interrupt(fault);
|
||||
}
|
||||
|
||||
self.core.mut_reg(Register::Pcx).add_assign(4);
|
||||
|
||||
match self.core.execute(Instruction(instruction)) {
|
||||
ExecutionResult::Continue => {},
|
||||
ExecutionResult::Halt => self.run_state_halted(Running),
|
||||
|
||||
ExecutionResult::Interrupt(interrupt) => {
|
||||
if cfg!(debug_assertions) {
|
||||
println!("Interrupt: {:?}", interrupt);
|
||||
println!("Interrupt while at address {:04X}", self.core.reg(Register::Pcx));
|
||||
}
|
||||
self.core.interrupt(interrupt);
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
println!(
|
||||
"Jumping to interrupt at address {:04X}",
|
||||
self.core.reg(Register::Pcx)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.core.internal_state.clock += 1;
|
||||
}
|
||||
|
||||
self.core.internal_state.running = Shutdown;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn apply_memory_map(&mut self, map: MemoryMap) {
|
||||
self.core.apply_memory_map(map)
|
||||
}
|
||||
|
||||
fn apply_io_mapping(&mut self, map: IoMap) {
|
||||
self.core.apply_io_mapping(map)
|
||||
}
|
||||
|
||||
fn get_io_map(&self) -> IoMap {
|
||||
self.core.get_io_map()
|
||||
}
|
||||
|
||||
fn get_memory_handle(&mut self) -> &mut MemoryBank {
|
||||
self.core.memory_mut()
|
||||
}
|
||||
|
||||
fn get_state_handle(&mut self) -> Arc<SharedState> {
|
||||
self.shared_state.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Emulator {
|
||||
pub fn new() -> Self {
|
||||
let (sender, receiver) = mpsc::channel::<Interrupt>();
|
||||
|
||||
Self {
|
||||
core: DsaProcessor::new(),
|
||||
shared_state: Arc::new(SharedState::new(sender)),
|
||||
|
||||
interrupts: receiver,
|
||||
pending_fault: None,
|
||||
|
||||
time: Instant::now(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn state_handle(&self) -> Arc<SharedState> {
|
||||
self.shared_state.clone()
|
||||
}
|
||||
|
||||
#[cold]
|
||||
fn share_state(&mut self) {
|
||||
self.shared_state
|
||||
.proc
|
||||
.store(Arc::new(self.core.internal_state.clone()));
|
||||
}
|
||||
|
||||
#[cold]
|
||||
pub fn run_state_halted(&mut self, prior: RunningState) {
|
||||
self.core.internal_state.running = Halted;
|
||||
|
||||
// Responsibility: keep track of the time spent in the running state across multiple hlt/interrupt cycles
|
||||
self.core.internal_state.time += self.time.elapsed();
|
||||
|
||||
loop {
|
||||
// Transition: interrupt received
|
||||
if let Ok(int) = self.interrupts.recv_timeout(Duration::from_millis(100)) {
|
||||
self.core.internal_state.running = Running;
|
||||
self.core.interrupt(int);
|
||||
break;
|
||||
}
|
||||
|
||||
// Transition: pause signal received
|
||||
if self.shared_state.paused.load(Ordering::Relaxed) {
|
||||
self.run_state_paused(Halted);
|
||||
}
|
||||
|
||||
// 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.core.internal_state.running = prior;
|
||||
}
|
||||
|
||||
#[cold]
|
||||
pub fn run_state_paused(&mut self, prior: RunningState) {
|
||||
self.core.internal_state.running = Paused;
|
||||
|
||||
// Responsibility: save the running-time since last pause state.
|
||||
self.core.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.core.internal_state.registers = [0; Register::COUNT as usize];
|
||||
self.core.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.core.internal_state.time = Duration::new(0, 0);
|
||||
self.core.internal_state.clock = 0;
|
||||
|
||||
self.core.internal_state.running = prior;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,13 @@
|
||||
pub mod interrupts;
|
||||
pub mod processor;
|
||||
pub mod state;
|
||||
mod state;
|
||||
mod dsa_core;
|
||||
mod dsa_debug;
|
||||
mod dsa_optimised;
|
||||
mod interrupts;
|
||||
|
||||
pub use dsa_core::DsaProcessor;
|
||||
pub use interrupts::Interrupt;
|
||||
pub use dsa_optimised::Emulator;
|
||||
pub use state::{
|
||||
RunningState,
|
||||
SharedState,
|
||||
};
|
||||
@@ -1,61 +1,50 @@
|
||||
use clap::Parser;
|
||||
|
||||
use common::prelude::Instruction;
|
||||
use std::thread;
|
||||
use dsa::args::DsaArgs;
|
||||
use dsa::ui::run_app;
|
||||
use emulator_core::{
|
||||
memory::ram::Page,
|
||||
processor::processor::Emulator
|
||||
};
|
||||
|
||||
const STACK_SIZE: usize = 1024 * 1024 * 16;
|
||||
use emulator_core::{DsaImplementation, EmulatorController, processor::Emulator};
|
||||
|
||||
fn main() {
|
||||
let args = DsaArgs::parse();
|
||||
|
||||
let mut emulator = Emulator::new().apply_memory_map(args.get_memory_map());
|
||||
let mut emulator = Emulator::new();
|
||||
emulator.apply_memory_map(args.get_memory_map());
|
||||
|
||||
let mem_handle = emulator.memory_mut().clone();
|
||||
let state = emulator.state_handle();
|
||||
let controller = EmulatorController::new(Box::new(emulator));
|
||||
|
||||
if let Some(bin) = args.get_binary() {
|
||||
for (i, ch) in bin.chunks(4).enumerate() {
|
||||
let instruction = Instruction(u32::from_le_bytes(ch.try_into().unwrap()));
|
||||
let hex = ch
|
||||
.iter()
|
||||
.map(|b| format!("{:02X}", b))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
let chars = ch
|
||||
.iter()
|
||||
.map(|b| {
|
||||
if b.is_ascii_graphic() {
|
||||
*b as char
|
||||
} else {
|
||||
'.'
|
||||
}
|
||||
})
|
||||
.collect::<String>();
|
||||
println!("{:04X}: {:<12} {:4} {:?}", i * 4, hex, chars, instruction);
|
||||
}
|
||||
|
||||
let program: Vec<Page> = Page::paginate(&bin).collect();
|
||||
|
||||
for (i, page) in program.iter().enumerate() {
|
||||
emulator.memory_mut().write_page(i as u32 * 4096, &page);
|
||||
}
|
||||
}
|
||||
|
||||
let _runner = thread::Builder::new()
|
||||
.stack_size(STACK_SIZE)
|
||||
.spawn(move || emulator.run().unwrap())
|
||||
.unwrap();
|
||||
//
|
||||
// if let Some(bin) = args.get_binary() {
|
||||
// for (i, ch) in bin.chunks(4).enumerate() {
|
||||
// let instruction = Instruction(u32::from_le_bytes(ch.try_into().unwrap()));
|
||||
// let hex = ch
|
||||
// .iter()
|
||||
// .map(|b| format!("{:02X}", b))
|
||||
// .collect::<Vec<_>>()
|
||||
// .join(" ");
|
||||
// let chars = ch
|
||||
// .iter()
|
||||
// .map(|b| {
|
||||
// if b.is_ascii_graphic() {
|
||||
// *b as char
|
||||
// } else {
|
||||
// '.'
|
||||
// }
|
||||
// })
|
||||
// .collect::<String>();
|
||||
// println!("{:04X}: {:<12} {:4} {:?}", i * 4, hex, chars, instruction);
|
||||
// }
|
||||
//
|
||||
// let program: Vec<Page> = Page::paginate(&bin).collect();
|
||||
//
|
||||
// for (i, page) in program.iter().enumerate() {
|
||||
// emulator.memory_mut().write_page(i as u32 * 4096, &page);
|
||||
// }
|
||||
// }
|
||||
|
||||
if args.headless() {
|
||||
run_server(&args);
|
||||
} else {
|
||||
run_app(state, mem_handle).unwrap()
|
||||
run_app(controller).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,219 +0,0 @@
|
||||
use std::any::Any;
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
use std::path::Path;
|
||||
use egui::{ScrollArea, Ui, WidgetText};
|
||||
use egui_extras::{Size, StripBuilder};
|
||||
use egui_file::FileDialog;
|
||||
use egui_tiles::TileId;
|
||||
use common::formats::binary_dse::DseExecutable;
|
||||
use disassembler::{disassemble, Disassembly};
|
||||
use crate::ui::components::Component;
|
||||
use crate::ui::debugger::Action;
|
||||
use crate::ui::debugger::panels::TilePane;
|
||||
|
||||
pub struct Disassembler {
|
||||
visible: bool,
|
||||
dialog: Option<FileDialog>,
|
||||
error: Option<String>,
|
||||
|
||||
program: Option<DseExecutable>,
|
||||
disassembled: Option<Disassembly>,
|
||||
}
|
||||
|
||||
impl TilePane for Disassembler {
|
||||
fn title(&self) -> WidgetText {
|
||||
"Disassembler".into()
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option<Action> {
|
||||
Component::ui(self, ui);
|
||||
None
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Disassembler {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
visible: true,
|
||||
dialog: None,
|
||||
error: None,
|
||||
|
||||
program: None,
|
||||
disassembled: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_open(path: &Path) -> Self {
|
||||
let reader = File::open(path).unwrap();
|
||||
let bin = DseExecutable::read(&mut BufReader::new(reader)).unwrap();
|
||||
|
||||
Self {
|
||||
visible: true,
|
||||
dialog: None,
|
||||
error: None,
|
||||
program: Some(bin),
|
||||
disassembled: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn open(&mut self) {
|
||||
let Ok(cwd) = std::env::current_dir() else {
|
||||
self.error = Some("Couldn't get CWD to open file!".to_string());
|
||||
return;
|
||||
};
|
||||
|
||||
if self.dialog.is_none() {
|
||||
let mut dialog = FileDialog::open_file();
|
||||
dialog.set_path(cwd);
|
||||
dialog.open();
|
||||
self.dialog = Some(dialog);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_file_dialog(&mut self, ui: &mut Ui) {
|
||||
|
||||
let Some(dialog) = &mut self.dialog else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !dialog.show(ui).selected() { return };
|
||||
|
||||
let Some(file) = dialog.path() else {
|
||||
return
|
||||
};
|
||||
|
||||
if !file.extension().is_some_and(|ext| ext == "dsb") {
|
||||
return
|
||||
}
|
||||
|
||||
let reader = match File::open(file) {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
self.error = Some(e.to_string());
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
match DseExecutable::read(&mut BufReader::new(reader)) {
|
||||
Ok(bin) => self.program = Some(bin),
|
||||
Err(e) => {
|
||||
self.error = Some(e.to_string());
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
self.dialog = None;
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Disassembler {
|
||||
fn title(&self) -> &'static str {
|
||||
"Disassembler"
|
||||
}
|
||||
|
||||
fn visible(&mut self) -> &mut bool {
|
||||
&mut self.visible
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
self.handle_file_dialog(ui);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.spacing_mut().button_padding = egui::vec2(8.0, 4.0);
|
||||
ui.spacing_mut().item_spacing.x = 6.0;
|
||||
|
||||
// Opens a file
|
||||
// ui.add_enabled(false, egui::Button::new("Open"));
|
||||
if ui.button("Open").clicked() {
|
||||
self.open();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if let Some(binary) = &self.program {
|
||||
if let Some(output) = &self.disassembled {
|
||||
StripBuilder::new(ui)
|
||||
.size(Size::exact(350.0))
|
||||
.size(Size::remainder().at_most(500.0))
|
||||
.horizontal(|mut strip| {
|
||||
|
||||
strip.cell(|ui| {
|
||||
ScrollArea::vertical()
|
||||
.id_salt("symbols")
|
||||
.show(ui, |ui| {
|
||||
egui::Grid::new("symbols_grid")
|
||||
.striped(true)
|
||||
.spacing([12.0, 2.0]) // horizontal, vertical spacing
|
||||
.show(ui, |ui| {
|
||||
// Header
|
||||
ui.strong("Addr");
|
||||
ui.strong("Name");
|
||||
ui.strong("Section");
|
||||
ui.strong("Bind");
|
||||
ui.end_row();
|
||||
|
||||
// Rows
|
||||
for symbol in &output.symbols {
|
||||
ui.monospace(format!("{:#06x}", symbol.address));
|
||||
ui.monospace(&symbol.name);
|
||||
ui.label(format!("{:?}", symbol.section));
|
||||
ui.label(format!("{:?}", symbol.binding));
|
||||
|
||||
ui.end_row();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
strip.cell(|ui| {
|
||||
ScrollArea::vertical()
|
||||
.id_salt("asm")
|
||||
.show(ui, |ui| {
|
||||
egui::Grid::new("asm_grid")
|
||||
.striped(true)
|
||||
.spacing([12.0, 2.0])
|
||||
.show(ui, |ui| {
|
||||
for (addr, line) in output.text_lines.iter().enumerate() {
|
||||
ui.label(
|
||||
egui::RichText::new(format!("{:04X}", addr))
|
||||
.monospace()
|
||||
.weak(),
|
||||
);
|
||||
|
||||
ui.monospace(line);
|
||||
|
||||
ui.end_row();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// ui.horizontal(|ui| {
|
||||
//
|
||||
// egui::ScrollArea::vertical()
|
||||
// .id_salt("symbols_scroll")
|
||||
// .show(ui, |ui| {
|
||||
//
|
||||
// });
|
||||
//
|
||||
// ui.separator();
|
||||
//
|
||||
// egui::ScrollArea::vertical()
|
||||
// .id_salt("asm_scroll")
|
||||
// .show(ui, |ui| {
|
||||
//
|
||||
// });
|
||||
// });
|
||||
} else {
|
||||
self.disassembled = Some(disassemble(&binary))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+33
-53
@@ -1,79 +1,58 @@
|
||||
use crate::ui::debugger::ui_helpers::{ PaddedWidget, StatusIndicator};
|
||||
use std::sync::{Arc, atomic::Ordering};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use crate::ui::ui_helpers::StatusIndicator;
|
||||
use eframe::emath::Vec2;
|
||||
use eframe::epaint::Color32;
|
||||
use egui::Widget;
|
||||
use super::Component;
|
||||
use common::prelude::Register;
|
||||
use emulator_core::{
|
||||
memory::ram::MemoryBank,
|
||||
processor::state::SharedState
|
||||
};
|
||||
use emulator_core::processor::state::RunningState;
|
||||
use emulator_core::EmulatorHandle;
|
||||
use emulator_core::processor::RunningState;
|
||||
use crate::ui::ui_helpers::PaddedWidget;
|
||||
|
||||
pub struct Controller {
|
||||
state: Arc<SharedState>,
|
||||
mem: MemoryBank,
|
||||
emulator: EmulatorHandle,
|
||||
visible: bool,
|
||||
}
|
||||
|
||||
impl Controller {
|
||||
pub fn new(state: Arc<SharedState>, mem: MemoryBank) -> Self {
|
||||
pub fn new(state: EmulatorHandle) -> Self {
|
||||
Self {
|
||||
state,
|
||||
mem,
|
||||
emulator: state,
|
||||
visible: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Controller {
|
||||
fn title(&self) -> &str {
|
||||
"Control Panel"
|
||||
}
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
|
||||
fn visible(&mut self) -> &mut bool {
|
||||
&mut self.visible
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
|
||||
self.state.update_req.store(true, Ordering::Relaxed);
|
||||
|
||||
thread::sleep(Duration::from_millis(5));
|
||||
|
||||
|
||||
let state = self.state.proc.load();
|
||||
let paused = self.state.paused.load(Ordering::Relaxed);
|
||||
self.emulator.request_update();
|
||||
|
||||
egui::Frame::new()
|
||||
.inner_margin(10.0)
|
||||
.show(ui, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
|
||||
let status = self.emulator.status();
|
||||
let padding = Vec2::new(14.0, 8.0);
|
||||
|
||||
if match state.running {
|
||||
RunningState::Paused | RunningState::Halted | RunningState::Breakpoint(_)
|
||||
=> PaddedWidget::button("▶ Run").color(Color32::GREEN).ui(ui),
|
||||
RunningState::Running
|
||||
=> PaddedWidget::button("⏸ Pause").color(Color32::YELLOW).ui(ui),
|
||||
match status {
|
||||
RunningState::Paused | RunningState::Halted | RunningState::Breakpoint(_) => {
|
||||
if PaddedWidget::button("▶ Run").color(Color32::GREEN).ui(ui).clicked() {
|
||||
self.emulator.resume()
|
||||
}
|
||||
},
|
||||
|
||||
RunningState::Running => {
|
||||
if PaddedWidget::button("⏸ Pause").color(Color32::YELLOW).ui(ui).clicked() {
|
||||
self.emulator.pause()
|
||||
}
|
||||
}
|
||||
|
||||
RunningState::Shutdown => {
|
||||
ui.add_enabled_ui(false, |ui| {
|
||||
PaddedWidget::button("▶ Run").color(Color32::GREEN).ui(ui)
|
||||
}).response
|
||||
},
|
||||
}.clicked()
|
||||
{
|
||||
self.state.paused.store(!paused, Ordering::Relaxed);
|
||||
PaddedWidget::button("▶ Run").color(Color32::GREEN).ui(ui);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if PaddedWidget::button("⟳ Reset All").color(Color32::YELLOW).ui(ui).clicked() {
|
||||
self.state.reseting.store(true, Ordering::Relaxed);
|
||||
self.mem.clear();
|
||||
self.state.reseting.store(false, Ordering::Relaxed);
|
||||
self.emulator.reset();
|
||||
}
|
||||
|
||||
// Step
|
||||
@@ -100,16 +79,17 @@ impl Component for Controller {
|
||||
// }
|
||||
// }
|
||||
|
||||
state.running.clone().ui(ui, padding);
|
||||
|
||||
status.clone().ui(ui, padding);
|
||||
|
||||
ui.separator();
|
||||
|
||||
let pcx = state.registers[Register::Pcx as usize];
|
||||
let clock = state.clock;
|
||||
let time = state.time.as_micros();
|
||||
let pcx = self.emulator.get_register(Register::Pcx);
|
||||
let clock = self.emulator.clock();
|
||||
let time = self.emulator.time().as_micros();
|
||||
|
||||
let mips = if time != 0 {
|
||||
state.clock as u128 / time
|
||||
clock as u128 / time
|
||||
} else {
|
||||
0
|
||||
};
|
||||
@@ -1,299 +0,0 @@
|
||||
use std::fmt::Debug;
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use egui::{CentralPanel, Panel, Ui};
|
||||
use egui_tiles::TileId;
|
||||
use common::formats::binary_dse::DseExecutable;
|
||||
use emulator_core::memory::ram::MemoryBank;
|
||||
use emulator_core::processor::state::SharedState;
|
||||
use crate::ui::components::controller::Controller;
|
||||
use crate::ui::components::disassembler::Disassembler;
|
||||
use crate::ui::components::memory::MemoryInspector;
|
||||
use crate::ui::DsaUi;
|
||||
use crate::ui::components::Component;
|
||||
use dialog::confirm_dialog::ConfirmDialog;
|
||||
use panels::documentation::Documentation;
|
||||
use panels::editor::Editor;
|
||||
use dialog::file_dialog::DsaFileDialog;
|
||||
use emulator_core::memory::PhysAddr;
|
||||
use panels::PanelFactory;
|
||||
use crate::ui::components::display::Display;
|
||||
use crate::ui::components::framebuffer::FrameBuffer;
|
||||
use crate::ui::components::registers::Registers;
|
||||
use crate::ui::components::serial::Serial;
|
||||
use crate::ui::debugger::dialog::error_dialog::ErrorDialog;
|
||||
use crate::ui::debugger::panels::{CentralTiles, TilePane};
|
||||
use crate::ui::debugger::panels::about::AboutScreen;
|
||||
|
||||
pub mod dialog;
|
||||
pub mod panels;
|
||||
pub mod ui_helpers;
|
||||
|
||||
trait FileFormat: Debug + Clone {
|
||||
fn extension(&self) -> &'static str;
|
||||
}
|
||||
|
||||
impl FileFormat for DseExecutable {
|
||||
fn extension(&self) -> &'static str {
|
||||
"dse"
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Action {
|
||||
RequestCloseTab(TileId),
|
||||
CloseTab(TileId),
|
||||
Quit,
|
||||
OpenFile(PathBuf),
|
||||
// OpenCachedFile(Box<dyn FileFormat>),
|
||||
LoadBinary(PathBuf),
|
||||
SaveFile(PathBuf, TileId),
|
||||
RequestSaveAs(TileId),
|
||||
ShowError(String),
|
||||
}
|
||||
|
||||
struct EmulatorHandle {
|
||||
state: Arc<SharedState>,
|
||||
memory: MemoryBank,
|
||||
}
|
||||
|
||||
impl EmulatorHandle {
|
||||
pub fn new(state: Arc<SharedState>, memory: MemoryBank) -> Self {
|
||||
Self { state, memory }
|
||||
}
|
||||
|
||||
pub fn write_bytes(&mut self, offset: PhysAddr, data: &[u8]) {
|
||||
self.memory.write_bytes(offset, data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub struct DebuggerUi {
|
||||
emulator: EmulatorHandle,
|
||||
|
||||
central_tiles: CentralTiles,
|
||||
|
||||
controller: Controller,
|
||||
confirm_dialog: ConfirmDialog<Action>,
|
||||
file_dialog: DsaFileDialog<Action>,
|
||||
error_dialog: ErrorDialog,
|
||||
|
||||
tools: Vec<PanelFactory>
|
||||
}
|
||||
|
||||
impl DebuggerUi {
|
||||
|
||||
fn open_or_focus<T: TilePane + 'static>(
|
||||
central_tiles: &mut CentralTiles,
|
||||
slot: &mut Option<TileId>,
|
||||
make_pane: impl FnOnce() -> T,
|
||||
) {
|
||||
match slot {
|
||||
Some(tile_id) => central_tiles.activate_tab(*tile_id),
|
||||
None => *slot = Some(central_tiles.add_tab(make_pane())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
cc: &eframe::CreationContext,
|
||||
state: Arc<SharedState>,
|
||||
mem_handle: MemoryBank,
|
||||
) -> Self {
|
||||
DsaUi::configure_appearance(&cc.egui_ctx);
|
||||
Self {
|
||||
emulator: EmulatorHandle::new(state.clone(), mem_handle.clone()),
|
||||
central_tiles: CentralTiles::new(),
|
||||
controller: Controller::new(state.clone(), mem_handle.clone()),
|
||||
confirm_dialog: ConfirmDialog::new(),
|
||||
file_dialog: DsaFileDialog::new(),
|
||||
error_dialog: ErrorDialog::default(),
|
||||
|
||||
tools: vec![
|
||||
PanelFactory::register("Memory Inspector", {
|
||||
let mem_handle = mem_handle.clone();
|
||||
move || MemoryInspector::new(mem_handle.clone())
|
||||
}),
|
||||
PanelFactory::register("Documentation", Documentation::default),
|
||||
PanelFactory::register("Display", {
|
||||
let mem_handle = mem_handle.clone();
|
||||
move || Display::new(80, 25, 0x20000, mem_handle.clone())
|
||||
}),
|
||||
PanelFactory::register("Framebuffer", {
|
||||
let mem_handle = mem_handle.clone();
|
||||
move || FrameBuffer::new(320, 240, 0x30000, mem_handle.clone())
|
||||
}),
|
||||
PanelFactory::register("Serial", {
|
||||
let mem_handle = mem_handle.clone();
|
||||
let state = state.clone();
|
||||
move || Serial::new(0x40000, state.clone(), mem_handle.clone())
|
||||
}),
|
||||
PanelFactory::register("Registers", {
|
||||
let state = state.clone();
|
||||
move || Registers::new(state.clone())
|
||||
})
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn perform_action(&mut self, action: Action) {
|
||||
match action {
|
||||
|
||||
Action::RequestCloseTab(tile_id) => {
|
||||
self.confirm_dialog.open(
|
||||
"Close Tab",
|
||||
"Closing this tab may result in losing unsaved work",
|
||||
Action::CloseTab(tile_id)
|
||||
);
|
||||
}
|
||||
|
||||
Action::CloseTab(tile_id) => {
|
||||
self.central_tiles.close_tab(tile_id);
|
||||
}
|
||||
|
||||
Action::Quit => std::process::exit(0),
|
||||
|
||||
// Opens a file (usually constructed by the file dialogue)
|
||||
Action::OpenFile(path) => {
|
||||
match path.extension().map(|s| s.to_str()).flatten() {
|
||||
Some("dsa" | "dsc") => { self.central_tiles.add_tab(Editor::new_open(&path)); },
|
||||
Some("dsb") => { self.central_tiles.add_tab(Disassembler::new_open(&path)); },
|
||||
Some("md") => { self.central_tiles.add_tab(Documentation::open(&path)); },
|
||||
Some(_) => todo!(),
|
||||
None => {},
|
||||
}
|
||||
}
|
||||
|
||||
Action::ShowError(err) => {
|
||||
self.error_dialog.open(err);
|
||||
}
|
||||
|
||||
Action::LoadBinary(path) => {
|
||||
if let Some(ext) = path.extension().map(|s| s.to_str()).flatten() && ext == "dsb" {
|
||||
|
||||
let Ok(file) = File::open(path) else {
|
||||
self.perform_action(Action::ShowError("Failed to open file".into()));
|
||||
return
|
||||
};
|
||||
|
||||
let Ok(binary) = DseExecutable::read(&mut BufReader::new(file)) else {
|
||||
self.perform_action(Action::ShowError("Failed to read binary".into()));
|
||||
return;
|
||||
};
|
||||
|
||||
self.emulator.write_bytes(0, &binary.to_memory_image())
|
||||
}
|
||||
}
|
||||
|
||||
Action::SaveFile(path, tile) => {
|
||||
if let Some(editor) = self.central_tiles.pane_mut::<Editor>(tile) {
|
||||
if let Err(e) = std::fs::write(path, editor.get_buffer().as_bytes()) {
|
||||
self.perform_action(Action::ShowError(e.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Action::RequestSaveAs(tile) => {
|
||||
self.file_dialog.open_save(move |path| Action::SaveFile(path, tile));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn toolbar(&mut self, ui: &mut Ui) {
|
||||
egui::MenuBar::new().ui(ui, |ui| {
|
||||
|
||||
let save_enabled = self.central_tiles.active_tile()
|
||||
.and_then(|id| self.central_tiles.pane_mut::<Editor>(id))
|
||||
.is_some();
|
||||
|
||||
ui.set_height(30.0);
|
||||
ui.menu_button("File", |ui| {
|
||||
|
||||
if ui.button("Open").clicked() {
|
||||
self.file_dialog.open_pick(Action::OpenFile);
|
||||
}
|
||||
|
||||
if ui.button("Load Binary").clicked() {
|
||||
self.file_dialog.open_pick(Action::LoadBinary);
|
||||
}
|
||||
|
||||
if ui.add_enabled(save_enabled, egui::Button::new("Save")).clicked() {
|
||||
if let Some(active) = self.central_tiles.active_tile() {
|
||||
if let Some(editor) = self.central_tiles.pane_mut::<Editor>(active) {
|
||||
match editor.filename().cloned() {
|
||||
Some(path) => std::fs::write(path, editor.get_buffer().as_bytes()).unwrap(),
|
||||
None => self.file_dialog.open_save(move |path| Action::SaveFile(path, active)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ui.add_enabled(save_enabled, egui::Button::new("Save As")).clicked() {
|
||||
if let Some(active) = self.central_tiles.active_tile() {
|
||||
if let Some(editor) = self.central_tiles.pane_mut::<Editor>(active) {
|
||||
self.file_dialog.open_save(move |path| Action::SaveFile(path, active))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ui.button("Exit").clicked() {
|
||||
self.confirm_dialog.open("Exit","Exit Application\n(you will lose unsaved work)", Action::Quit)
|
||||
}
|
||||
});
|
||||
|
||||
ui.menu_button("Tools", |ui| {
|
||||
for PanelFactory(name, constructor) in &self.tools {
|
||||
if ui.button(name).clicked() {
|
||||
self.central_tiles.add_dyn_tab(constructor());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ui.menu_button("Help", |ui| {
|
||||
if ui.button("About").clicked() {
|
||||
self.central_tiles.add_tab(AboutScreen);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for DebuggerUi {
|
||||
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
|
||||
ui.request_repaint_after(Duration::from_millis(16));
|
||||
|
||||
if let Some(action) = self.confirm_dialog.show(ui) {
|
||||
self.perform_action(action);
|
||||
}
|
||||
|
||||
if let Some(action) = self.file_dialog.show(ui) {
|
||||
self.perform_action(action);
|
||||
}
|
||||
|
||||
self.error_dialog.show(ui);
|
||||
|
||||
Panel::top("menu_bar").resizable(false).show(ui, |ui| {
|
||||
self.toolbar(ui);
|
||||
});
|
||||
|
||||
Panel::top("control_panel").resizable(false).show(ui, |ui| {
|
||||
self.controller.ui(ui);
|
||||
});
|
||||
|
||||
Panel::left("left_panel").resizable(false).show(ui, |ui| {
|
||||
// TODO: File Explorer!
|
||||
});
|
||||
|
||||
// Panel::right("right_panel").resizable(false).show(ui, |ui| {
|
||||
// // TODO: not sure what to put here!
|
||||
// });
|
||||
|
||||
CentralPanel::default().show(ui, |ui| {
|
||||
if let Some(action) = self.central_tiles.ui(ui) {
|
||||
self.perform_action(action);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
use egui::{Modal, Id, Layout, Align, RichText};
|
||||
use crate::ui::debugger::ui_helpers::PaddedWidget;
|
||||
use crate::ui::ui_helpers::PaddedWidget;
|
||||
|
||||
pub struct ConfirmDialog<T> {
|
||||
open: bool,
|
||||
-3
@@ -1,8 +1,5 @@
|
||||
use std::path::PathBuf;
|
||||
use egui::Ui;
|
||||
use egui_file_dialog::FileDialog;
|
||||
use crate::ui::debugger::Action;
|
||||
|
||||
|
||||
pub struct DsaFileDialog<T> {
|
||||
inner: FileDialog, // or whatever backend you're using
|
||||
@@ -1,127 +1,306 @@
|
||||
use std::fmt::Debug;
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
use eframe::NativeOptions;
|
||||
use eframe::egui;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::Ordering;
|
||||
use egui::Order::Debug;
|
||||
use egui::{CentralPanel, Panel, Ui};
|
||||
use egui_tiles::TileId;
|
||||
use common::formats::binary_dse::DseExecutable;
|
||||
use dialog::confirm_dialog::ConfirmDialog;
|
||||
use panels::documentation::Documentation;
|
||||
use panels::editor::Editor;
|
||||
use dialog::file_dialog::DsaFileDialog;
|
||||
use emulator_core::{EmulatorController};
|
||||
use panels::PanelFactory;
|
||||
use crate::ui::controller::Controller;
|
||||
use crate::ui::dialog::error_dialog::ErrorDialog;
|
||||
use crate::ui::panels::{CentralTiles, TilePane};
|
||||
use crate::ui::panels::about::AboutScreen;
|
||||
use crate::ui::panels::disassembler::BinaryViewer;
|
||||
use crate::ui::panels::display::Display;
|
||||
use crate::ui::panels::framebuffer::FrameBuffer;
|
||||
use crate::ui::panels::memory::MemoryInspector;
|
||||
use crate::ui::panels::registers::Registers;
|
||||
use crate::ui::panels::serial::Serial;
|
||||
|
||||
mod components;
|
||||
pub mod debugger;
|
||||
mod dialog;
|
||||
mod panels;
|
||||
mod ui_helpers;
|
||||
mod controller;
|
||||
|
||||
use components::{
|
||||
controller::Controller, display::Display, editor::Editor, framebuffer::FrameBuffer, memory::MemoryInspector,
|
||||
registers::Registers, serial::Serial, Component,
|
||||
};
|
||||
use emulator_core::{
|
||||
config::VERSION,
|
||||
memory::ram::MemoryBank,
|
||||
processor::state::SharedState
|
||||
};
|
||||
use crate::ui::components::disassembler::Disassembler;
|
||||
use crate::ui::debugger::DebuggerUi;
|
||||
|
||||
pub fn run_app(state: Arc<SharedState>, mem_handle: MemoryBank) -> eframe::Result<()> {
|
||||
|
||||
let e = e();
|
||||
pub fn run_app(mut controller: EmulatorController) -> eframe::Result<()> {
|
||||
// initialise emulator
|
||||
controller.run().unwrap();
|
||||
|
||||
eframe::run_native(
|
||||
"Damn Simple Architecture",
|
||||
NativeOptions::default(),
|
||||
|
||||
// Box::new(|cc| Ok(Box::new(DsaUi::new(cc, state, mem_handle)))),
|
||||
Box::new(|cc| Ok(Box::new(DebuggerUi::new(cc, state, mem_handle)))),
|
||||
Box::new(|cc| Ok(Box::new(DamnSimpleUI::new(cc, controller)))),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
fn e() -> i32 {
|
||||
return 5
|
||||
trait FileFormat: Debug + Clone {
|
||||
fn extension(&self) -> &'static str;
|
||||
}
|
||||
|
||||
impl eframe::App for DsaUi {
|
||||
impl FileFormat for DseExecutable {
|
||||
fn extension(&self) -> &'static str {
|
||||
"dse"
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DataSource {
|
||||
File(PathBuf),
|
||||
Data(Vec<u8>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Action {
|
||||
RequestCloseTab(TileId),
|
||||
CloseTab(TileId),
|
||||
Quit,
|
||||
OpenFile(PathBuf),
|
||||
// OpenCachedFile(Box<dyn FileFormat>),
|
||||
LoadBinary(DataSource),
|
||||
SaveFile(PathBuf, TileId),
|
||||
RequestSaveAs(TileId),
|
||||
ShowError(String),
|
||||
}
|
||||
|
||||
|
||||
pub struct DamnSimpleUI {
|
||||
emulator: EmulatorController,
|
||||
central_tiles: CentralTiles,
|
||||
|
||||
controller: Controller,
|
||||
confirm_dialog: ConfirmDialog<Action>,
|
||||
file_dialog: DsaFileDialog<Action>,
|
||||
error_dialog: ErrorDialog,
|
||||
|
||||
tools: Vec<PanelFactory>,
|
||||
}
|
||||
|
||||
|
||||
impl eframe::App for DamnSimpleUI {
|
||||
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
|
||||
// request an update from ui every cycle
|
||||
self.state.update_req.store(true, Ordering::Relaxed);
|
||||
ui.request_repaint_after(Duration::from_millis(16));
|
||||
|
||||
// title panel with dsa title
|
||||
egui::containers::Panel::top("top_panel").show(ui, |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));
|
||||
},
|
||||
);
|
||||
if let Some(action) = self.confirm_dialog.show(ui) {
|
||||
self.perform_action(action);
|
||||
}
|
||||
|
||||
if let Some(action) = self.file_dialog.show(ui) {
|
||||
self.perform_action(action);
|
||||
}
|
||||
|
||||
self.error_dialog.show(ui);
|
||||
|
||||
Panel::top("menu_bar").resizable(false).show(ui, |ui| {
|
||||
self.toolbar(ui);
|
||||
});
|
||||
|
||||
// menu bar.
|
||||
egui::Panel::top("menu_bar").show(ui, |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);
|
||||
Panel::top("control_panel").resizable(false).show(ui, |ui| {
|
||||
self.controller.ui(ui);
|
||||
});
|
||||
|
||||
for comp in &mut self.components {
|
||||
let name = comp.title().to_string();
|
||||
ui.toggle_value(comp.visible(), name);
|
||||
Panel::left("left_panel").resizable(false).show(ui, |ui| {
|
||||
// TODO: File Explorer!
|
||||
});
|
||||
|
||||
Panel::bottom("bottom_panel").resizable(false).show(ui, |ui| {
|
||||
// self.process_manager.ui(ui);
|
||||
});
|
||||
|
||||
CentralPanel::default().show(ui, |ui| {
|
||||
if let Some(action) = self.central_tiles.ui(ui) {
|
||||
self.perform_action(action);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
egui::CentralPanel::default().show(ui, |ui| {
|
||||
for c in &mut self.components {
|
||||
let mut visible = *c.visible();
|
||||
if visible {
|
||||
egui::Window::new(c.title())
|
||||
.open(&mut visible)
|
||||
.show(ui, |ui| {
|
||||
c.ui(ui);
|
||||
});
|
||||
}
|
||||
*c.visible() = visible;
|
||||
}
|
||||
});
|
||||
|
||||
ui.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 DamnSimpleUI {
|
||||
|
||||
fn open_or_focus<T: TilePane + 'static>(
|
||||
central_tiles: &mut CentralTiles,
|
||||
slot: &mut Option<TileId>,
|
||||
make_pane: impl FnOnce() -> T,
|
||||
) {
|
||||
match slot {
|
||||
Some(tile_id) => central_tiles.activate_tab(*tile_id),
|
||||
None => *slot = Some(central_tiles.add_tab(make_pane())),
|
||||
}
|
||||
}
|
||||
|
||||
impl DsaUi {
|
||||
pub fn new(
|
||||
cc: &eframe::CreationContext,
|
||||
state: Arc<SharedState>,
|
||||
mem_handle: MemoryBank,
|
||||
mut emulator: EmulatorController,
|
||||
) -> 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())),
|
||||
Box::new(Editor::new(state.clone(), mem_handle.clone())),
|
||||
Box::new(MemoryInspector::new(mem_handle.clone())),
|
||||
Box::new(Disassembler::new())
|
||||
];
|
||||
components.iter_mut().for_each(|c| *c.visible() = false);
|
||||
configure_appearance(&cc.egui_ctx);
|
||||
let handle = emulator.get_handle().clone();
|
||||
Self {
|
||||
central_tiles: CentralTiles::new(),
|
||||
controller: Controller::new(handle.clone()),
|
||||
confirm_dialog: ConfirmDialog::new(),
|
||||
file_dialog: DsaFileDialog::new(),
|
||||
error_dialog: ErrorDialog::default(),
|
||||
|
||||
Self::configure_appearance(&cc.egui_ctx);
|
||||
let app = Self {
|
||||
state,
|
||||
mem_handle,
|
||||
components,
|
||||
tools: vec![
|
||||
PanelFactory::register("Memory Inspector", {
|
||||
let handle = handle.clone();
|
||||
move || MemoryInspector::new(handle.clone())
|
||||
}),
|
||||
PanelFactory::register("Documentation", Documentation::default),
|
||||
PanelFactory::register("Display", {
|
||||
let handle = handle.clone();
|
||||
move || Display::new(80, 25, 0x20000, handle.clone())
|
||||
}),
|
||||
PanelFactory::register("Framebuffer", {
|
||||
let handle = handle.clone();
|
||||
move || FrameBuffer::new(320, 240, 0x30000, handle.clone())
|
||||
}),
|
||||
PanelFactory::register("Serial", {
|
||||
let handle = handle.clone();
|
||||
move || Serial::new(0x40000, handle.clone())
|
||||
}),
|
||||
PanelFactory::register("Registers", {
|
||||
let handle = handle.clone();
|
||||
move || Registers::new(handle.clone())
|
||||
})
|
||||
],
|
||||
emulator,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn perform_action(&mut self, action: Action) {
|
||||
match action {
|
||||
|
||||
Action::RequestCloseTab(tile_id) => {
|
||||
self.confirm_dialog.open(
|
||||
"Close Tab",
|
||||
"Closing this tab may result in losing unsaved work",
|
||||
Action::CloseTab(tile_id)
|
||||
);
|
||||
}
|
||||
|
||||
Action::CloseTab(tile_id) => {
|
||||
self.central_tiles.close_tab(tile_id);
|
||||
}
|
||||
|
||||
Action::Quit => std::process::exit(0),
|
||||
|
||||
// Opens a file (usually constructed by the file dialogue)
|
||||
Action::OpenFile(path) => {
|
||||
match path.extension().map(|s| s.to_str()).flatten() {
|
||||
Some("dsa" | "dsc") => { self.central_tiles.add_tab(Editor::new_open(&path)); },
|
||||
Some("dsb") => { self.central_tiles.add_tab(BinaryViewer::new_open(&path)); },
|
||||
Some("md") => { self.central_tiles.add_tab(Documentation::open(&path)); },
|
||||
Some(_) => todo!(),
|
||||
None => {},
|
||||
}
|
||||
}
|
||||
|
||||
Action::ShowError(err) => {
|
||||
self.error_dialog.open(err);
|
||||
}
|
||||
|
||||
Action::LoadBinary(DataSource::Data(exe)) => {
|
||||
self.emulator.get_handle().mem_write(0, &exe);
|
||||
}
|
||||
|
||||
Action::LoadBinary(DataSource::File(path)) => {
|
||||
if let Some(ext) = path.extension().map(|s| s.to_str()).flatten() && ext == "dsb" {
|
||||
|
||||
let Ok(file) = File::open(path) else {
|
||||
self.perform_action(Action::ShowError("Failed to open file".into()));
|
||||
return
|
||||
};
|
||||
app
|
||||
|
||||
let Ok(binary) = DseExecutable::read(&mut BufReader::new(file)) else {
|
||||
self.perform_action(Action::ShowError("Failed to read binary".into()));
|
||||
return;
|
||||
};
|
||||
|
||||
self.emulator.get_handle().mem_write(0, &binary.to_memory_image())
|
||||
}
|
||||
}
|
||||
|
||||
Action::SaveFile(path, tile) => {
|
||||
if let Some(editor) = self.central_tiles.pane_mut::<Editor>(tile) {
|
||||
if let Err(e) = std::fs::write(path, editor.get_buffer().as_bytes()) {
|
||||
self.perform_action(Action::ShowError(e.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Action::RequestSaveAs(tile) => {
|
||||
self.file_dialog.open_save(move |path| Action::SaveFile(path, tile));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn toolbar(&mut self, ui: &mut Ui) {
|
||||
egui::MenuBar::new().ui(ui, |ui| {
|
||||
|
||||
let save_enabled = self.central_tiles.active_tile()
|
||||
.and_then(|id| self.central_tiles.pane_mut::<Editor>(id))
|
||||
.is_some();
|
||||
|
||||
ui.set_height(30.0);
|
||||
ui.menu_button("File", |ui| {
|
||||
|
||||
if ui.button("Open").clicked() {
|
||||
self.file_dialog.open_pick(Action::OpenFile);
|
||||
}
|
||||
|
||||
if ui.button("Load Binary").clicked() {
|
||||
self.file_dialog.open_pick(|path| Action::LoadBinary(DataSource::File(path)));
|
||||
}
|
||||
|
||||
if ui.add_enabled(save_enabled, egui::Button::new("Save")).clicked() {
|
||||
if let Some(active) = self.central_tiles.active_tile() {
|
||||
if let Some(editor) = self.central_tiles.pane_mut::<Editor>(active) {
|
||||
match editor.filename().cloned() {
|
||||
Some(path) => std::fs::write(path, editor.get_buffer().as_bytes()).unwrap(),
|
||||
None => self.file_dialog.open_save(move |path| Action::SaveFile(path, active)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ui.add_enabled(save_enabled, egui::Button::new("Save As")).clicked() {
|
||||
if let Some(active) = self.central_tiles.active_tile() {
|
||||
if let Some(editor) = self.central_tiles.pane_mut::<Editor>(active) {
|
||||
self.file_dialog.open_save(move |path| Action::SaveFile(path, active))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ui.button("Exit").clicked() {
|
||||
self.confirm_dialog.open("Exit","Exit Application\n(you will lose unsaved work)", Action::Quit)
|
||||
}
|
||||
});
|
||||
|
||||
ui.menu_button("Tools", |ui| {
|
||||
for PanelFactory(name, constructor) in &self.tools {
|
||||
if ui.button(name).clicked() {
|
||||
self.central_tiles.add_dyn_tab(constructor());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ui.menu_button("Help", |ui| {
|
||||
if ui.button("About").clicked() {
|
||||
self.central_tiles.add_tab(AboutScreen);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn configure_appearance(ctx: &egui::Context) {
|
||||
@@ -168,4 +347,3 @@ impl DsaUi {
|
||||
|
||||
ctx.set_fonts(fonts);
|
||||
}
|
||||
}
|
||||
|
||||
+5
-8
@@ -1,13 +1,10 @@
|
||||
use std::default::Default;
|
||||
use std::any::Any;
|
||||
use std::path::Path;
|
||||
use crate::ui::Action;
|
||||
use crate::ui::panels::TilePane;
|
||||
use eframe::epaint::Direction;
|
||||
use egui::{Ui, Widget, WidgetText};
|
||||
use egui_commonmark::{CommonMarkCache, CommonMarkViewer};
|
||||
use egui::{Ui, WidgetText};
|
||||
use egui_tiles::TileId;
|
||||
use emulator_core::config::{VERSION};
|
||||
use crate::ui::debugger::Action;
|
||||
use crate::ui::debugger::panels::TilePane;
|
||||
use emulator_core::config::VERSION;
|
||||
use std::any::Any;
|
||||
|
||||
pub struct AboutScreen;
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
use std::any::Any;
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
use std::path::Path;
|
||||
use std::sync::mpsc;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
|
||||
use egui::{ScrollArea, Ui, WidgetText};
|
||||
use egui_extras::{Size, StripBuilder};
|
||||
use egui_file::FileDialog;
|
||||
use egui_tiles::TileId;
|
||||
|
||||
use common::formats::binary_dse::DseExecutable;
|
||||
use disassembler::{disassemble, Disassembly};
|
||||
|
||||
use crate::ui::panels::TilePane;
|
||||
use crate::ui::{Action, DataSource};
|
||||
use crate::ui::panels::memory::MemoryViewer;
|
||||
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
enum ViewMode {
|
||||
Memory,
|
||||
Disassembly,
|
||||
}
|
||||
|
||||
enum DisassemblyState {
|
||||
Idle,
|
||||
Running(mpsc::Receiver<Disassembly>),
|
||||
Done(Disassembly),
|
||||
}
|
||||
|
||||
pub struct BinaryViewer {
|
||||
dialog: Option<FileDialog>,
|
||||
error: Option<String>,
|
||||
|
||||
program: Option<Arc<DseExecutable>>,
|
||||
memory_image: Option<Vec<u8>>,
|
||||
|
||||
view: ViewMode,
|
||||
memory_viewer: MemoryViewer,
|
||||
disassembly: DisassemblyState,
|
||||
}
|
||||
|
||||
impl BinaryViewer {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
dialog: None,
|
||||
error: None,
|
||||
program: None,
|
||||
memory_image: None,
|
||||
view: ViewMode::Memory,
|
||||
memory_viewer: MemoryViewer::new(),
|
||||
disassembly: DisassemblyState::Idle,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_open(path: &Path) -> Self {
|
||||
let mut viewer = Self::new();
|
||||
match File::open(path).map(|f| DseExecutable::read(&mut BufReader::new(f))) {
|
||||
Ok(Ok(bin)) => viewer.load_program(bin),
|
||||
Ok(Err(e)) => viewer.error = Some(format!("Failed to load {}: {e}", path.display())),
|
||||
Err(e) => viewer.error = Some(format!("Failed to load {}: {e}", path.display())),
|
||||
}
|
||||
viewer
|
||||
}
|
||||
|
||||
fn load_program(&mut self, bin: DseExecutable) {
|
||||
// TODO: replace with however DseExecutable actually exposes its raw memory image.
|
||||
// Assuming a method like this exists — swap for the real accessor.
|
||||
self.memory_image = Some(bin.to_memory_image());
|
||||
self.program = Some(Arc::new(bin));
|
||||
self.disassembly = DisassemblyState::Idle;
|
||||
self.view = ViewMode::Memory;
|
||||
self.error = None;
|
||||
}
|
||||
|
||||
fn open(&mut self) {
|
||||
let Ok(cwd) = std::env::current_dir() else {
|
||||
self.error = Some("Couldn't get CWD to open file!".to_string());
|
||||
return;
|
||||
};
|
||||
|
||||
if self.dialog.is_none() {
|
||||
let mut dialog = FileDialog::open_file();
|
||||
dialog.set_path(cwd);
|
||||
dialog.open();
|
||||
self.dialog = Some(dialog);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_file_dialog(&mut self, ui: &mut Ui) {
|
||||
let Some(dialog) = &mut self.dialog else { return };
|
||||
if !dialog.show(ui).selected() { return }
|
||||
|
||||
let Some(file) = dialog.path() else { return };
|
||||
let file = file.to_path_buf();
|
||||
self.dialog = None;
|
||||
|
||||
if !file.extension().is_some_and(|ext| ext == "dsb") {
|
||||
self.error = Some("Selected file is not a .dsb binary".into());
|
||||
return;
|
||||
}
|
||||
|
||||
let reader = match File::open(&file) {
|
||||
Ok(f) => f,
|
||||
Err(e) => { self.error = Some(e.to_string()); return; }
|
||||
};
|
||||
|
||||
match DseExecutable::read(&mut BufReader::new(reader)) {
|
||||
Ok(bin) => self.load_program(bin),
|
||||
Err(e) => self.error = Some(e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Kicks disassembly off on a background thread so a large binary
|
||||
/// doesn't stall the UI. `DseExecutable` is wrapped in `Arc` so the
|
||||
/// thread can borrow it without cloning the whole binary image.
|
||||
fn start_disassembly(&mut self) {
|
||||
let Some(program) = self.program.clone() else { return };
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
thread::spawn(move || {
|
||||
let result = disassemble(&program);
|
||||
let _ = tx.send(result);
|
||||
});
|
||||
|
||||
self.disassembly = DisassemblyState::Running(rx);
|
||||
self.view = ViewMode::Disassembly;
|
||||
}
|
||||
|
||||
fn poll_disassembly(&mut self) {
|
||||
if let DisassemblyState::Running(rx) = &self.disassembly {
|
||||
if let Ok(result) = rx.try_recv() {
|
||||
self.disassembly = DisassemblyState::Done(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn toolbar(&mut self, ui: &mut Ui) -> Option<Action> {
|
||||
let mut action = None;
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.spacing_mut().button_padding = egui::vec2(8.0, 4.0);
|
||||
ui.spacing_mut().item_spacing.x = 6.0;
|
||||
|
||||
if ui.button("Open").clicked() {
|
||||
self.open();
|
||||
}
|
||||
|
||||
let has_program = self.program.is_some();
|
||||
|
||||
if ui.add_enabled(has_program, egui::Button::new("Load")).clicked() {
|
||||
if let Some(data) = &self.memory_image {
|
||||
action = Some(Action::LoadBinary(DataSource::Data(data.clone())));
|
||||
}
|
||||
}
|
||||
|
||||
let disassembling = matches!(self.disassembly, DisassemblyState::Running(_));
|
||||
if ui
|
||||
.add_enabled(has_program && !disassembling, egui::Button::new("Disassemble"))
|
||||
.clicked()
|
||||
{
|
||||
self.start_disassembly();
|
||||
}
|
||||
|
||||
ui.separator();
|
||||
|
||||
ui.selectable_value(&mut self.view, ViewMode::Memory, "Memory");
|
||||
let has_disassembly = !matches!(self.disassembly, DisassemblyState::Idle);
|
||||
ui.add_enabled_ui(has_disassembly, |ui| {
|
||||
ui.selectable_value(&mut self.view, ViewMode::Disassembly, "Disassembly");
|
||||
});
|
||||
|
||||
if let Some(err) = &self.error {
|
||||
ui.separator();
|
||||
ui.colored_label(egui::Color32::from_rgb(220, 80, 80), err);
|
||||
}
|
||||
});
|
||||
|
||||
action
|
||||
}
|
||||
}
|
||||
|
||||
impl TilePane for BinaryViewer {
|
||||
fn title(&self) -> WidgetText {
|
||||
"Binary Viewer".into()
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option<Action> {
|
||||
self.handle_file_dialog(ui);
|
||||
self.poll_disassembly();
|
||||
|
||||
if matches!(self.disassembly, DisassemblyState::Running(_)) {
|
||||
ui.ctx().request_repaint(); // keep polling promptly while the background thread works
|
||||
}
|
||||
|
||||
let action = self.toolbar(ui);
|
||||
ui.separator();
|
||||
|
||||
match self.view {
|
||||
ViewMode::Memory => match &self.memory_image {
|
||||
Some(memory) => self.memory_viewer.ui(ui, memory, 0),
|
||||
None => {
|
||||
ui.centered_and_justified(|ui| {
|
||||
ui.weak("Open a binary to inspect its memory image.");
|
||||
});
|
||||
}
|
||||
},
|
||||
ViewMode::Disassembly => match &self.disassembly {
|
||||
DisassemblyState::Idle => {
|
||||
ui.centered_and_justified(|ui| {
|
||||
ui.weak("Click Disassemble to generate a disassembly.");
|
||||
});
|
||||
}
|
||||
DisassemblyState::Running(_) => {
|
||||
ui.centered_and_justified(|ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.spinner();
|
||||
ui.label("Disassembling…");
|
||||
});
|
||||
});
|
||||
}
|
||||
DisassemblyState::Done(output) => render_disassembly(ui, output),
|
||||
},
|
||||
}
|
||||
|
||||
action
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
fn render_disassembly(ui: &mut Ui, output: &Disassembly) {
|
||||
StripBuilder::new(ui)
|
||||
.size(Size::exact(350.0))
|
||||
.size(Size::remainder().at_most(500.0))
|
||||
.horizontal(|mut strip| {
|
||||
strip.cell(|ui| {
|
||||
ScrollArea::vertical().id_salt("symbols").show(ui, |ui| {
|
||||
egui::Grid::new("symbols_grid")
|
||||
.striped(true)
|
||||
.spacing([12.0, 2.0])
|
||||
.show(ui, |ui| {
|
||||
ui.strong("Addr");
|
||||
ui.strong("Name");
|
||||
ui.strong("Section");
|
||||
ui.strong("Bind");
|
||||
ui.end_row();
|
||||
|
||||
for symbol in &output.symbols {
|
||||
ui.monospace(format!("{:#06x}", symbol.address));
|
||||
ui.monospace(&symbol.name);
|
||||
ui.label(format!("{:?}", symbol.section));
|
||||
ui.label(format!("{:?}", symbol.binding));
|
||||
ui.end_row();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
strip.cell(|ui| {
|
||||
ScrollArea::vertical().id_salt("asm").show(ui, |ui| {
|
||||
egui::Grid::new("asm_grid")
|
||||
.striped(true)
|
||||
.spacing([12.0, 2.0])
|
||||
.show(ui, |ui| {
|
||||
for (addr, line) in output.text_lines.iter().enumerate() {
|
||||
ui.label(
|
||||
egui::RichText::new(format!("{:04X}", addr))
|
||||
.monospace()
|
||||
.weak(),
|
||||
);
|
||||
ui.monospace(line);
|
||||
ui.end_row();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
+19
-54
@@ -1,62 +1,41 @@
|
||||
use std::any::Any;
|
||||
use egui::{Color32, FontId, Ui, Vec2, WidgetText};
|
||||
use egui_tiles::TileId;
|
||||
use emulator_core::{
|
||||
memory::PhysAddr,
|
||||
memory::ram::{MemoryBank, Page}
|
||||
};
|
||||
use crate::ui::debugger::Action;
|
||||
use crate::ui::debugger::panels::TilePane;
|
||||
use super::Component;
|
||||
use emulator_core::{memory::ram::{MemoryBank, Page}, memory::PhysAddr, EmulatorHandle};
|
||||
use crate::ui::Action;
|
||||
use crate::ui::panels::TilePane;
|
||||
|
||||
pub struct Display {
|
||||
width: usize,
|
||||
height: usize,
|
||||
width: u32,
|
||||
height: u32,
|
||||
addr: PhysAddr,
|
||||
|
||||
mem: MemoryBank,
|
||||
emulator: EmulatorHandle,
|
||||
buffer: Vec<u8>,
|
||||
visible: bool,
|
||||
}
|
||||
|
||||
impl Display {
|
||||
pub fn new(width: u32, height: u32, addr: PhysAddr, mem: MemoryBank) -> Self {
|
||||
pub fn new(width: u32, height: u32, addr: PhysAddr, emulator: EmulatorHandle) -> Self {
|
||||
Self {
|
||||
width: width as usize,
|
||||
height: height as usize,
|
||||
width,
|
||||
height,
|
||||
addr,
|
||||
mem,
|
||||
emulator,
|
||||
buffer: Vec::new(),
|
||||
visible: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn visible(&mut self) -> &mut bool {
|
||||
&mut self.visible
|
||||
}
|
||||
|
||||
pub fn width(&self) -> usize {
|
||||
self.width
|
||||
self.width as usize
|
||||
}
|
||||
|
||||
pub fn height(&self) -> usize {
|
||||
self.height
|
||||
self.height as usize
|
||||
}
|
||||
|
||||
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 fn changed(&mut self) -> bool {
|
||||
let temp = self.internal_read();
|
||||
let temp = self.read();
|
||||
if temp != self.buffer {
|
||||
self.buffer = temp;
|
||||
return true;
|
||||
@@ -65,7 +44,7 @@ impl Display {
|
||||
}
|
||||
|
||||
pub fn read(&mut self) -> Vec<u8> {
|
||||
self.internal_read()
|
||||
self.emulator.mem_read(self.addr, self.width * self.height)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,25 +55,6 @@ impl TilePane for Display {
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option<Action> {
|
||||
Component::ui(self, ui);
|
||||
None
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
let display_w = self.width();
|
||||
let display_h = self.height();
|
||||
let data = self.read();
|
||||
@@ -143,5 +103,10 @@ impl Component for Display {
|
||||
Color32::WHITE,
|
||||
);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -4,8 +4,8 @@ use std::path::Path;
|
||||
use egui::{Ui, Widget, WidgetText};
|
||||
use egui_commonmark::{CommonMarkCache, CommonMarkViewer};
|
||||
use egui_tiles::TileId;
|
||||
use crate::ui::debugger::Action;
|
||||
use crate::ui::debugger::panels::TilePane;
|
||||
use crate::ui::Action;
|
||||
use crate::ui::panels::TilePane;
|
||||
|
||||
pub struct Documentation {
|
||||
buffer: Option<String>,
|
||||
+9
-14
@@ -1,22 +1,18 @@
|
||||
use crate::ui::Action;
|
||||
use crate::ui::panels::TilePane;
|
||||
use egui::{Key, Ui};
|
||||
use egui_code_editor::CodeEditor;
|
||||
use egui_tiles::TileId;
|
||||
use std::any::Any;
|
||||
use std::path::{Path, PathBuf};
|
||||
use egui::accesskit::Role::Code;
|
||||
use egui::{Key, Ui};
|
||||
use egui_code_editor::{CodeEditor, Syntax};
|
||||
use egui_file_dialog::FileDialog;
|
||||
use egui_tiles::TileId;
|
||||
use crate::ui::components::Component;
|
||||
use crate::ui::components::editor::{syntax, THEME};
|
||||
use crate::ui::debugger::Action;
|
||||
use crate::ui::debugger::panels::TilePane;
|
||||
use theme::THEME;
|
||||
mod syntax;
|
||||
mod theme;
|
||||
|
||||
pub struct Editor {
|
||||
buffer: String,
|
||||
filename: Option<PathBuf>,
|
||||
|
||||
editor: CodeEditor,
|
||||
dialog: FileDialog,
|
||||
visible: bool,
|
||||
}
|
||||
|
||||
impl Default for Editor {
|
||||
@@ -25,8 +21,6 @@ impl Default for Editor {
|
||||
buffer: String::new(),
|
||||
filename: None,
|
||||
editor: CodeEditor::default().with_fontsize(12.0).with_theme(THEME).with_numlines(true),
|
||||
dialog: FileDialog::new(),
|
||||
visible: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,3 +85,4 @@ impl TilePane for Editor {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
-1
@@ -1,7 +1,6 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use egui_code_editor::{Patch, Syntax};
|
||||
|
||||
pub fn dsa() -> Syntax {
|
||||
Syntax {
|
||||
word_start: BTreeSet::new(),
|
||||
@@ -0,0 +1,18 @@
|
||||
use egui_code_editor::ColorTheme;
|
||||
|
||||
pub const THEME: ColorTheme = ColorTheme {
|
||||
name: "Theme",
|
||||
dark: true,
|
||||
bg: "1b1b1b",
|
||||
cursor: "de5252", // fg4
|
||||
selection: "28323B", // bg2
|
||||
comments: "444444", // gray1
|
||||
functions: "7CCCC7", // green1
|
||||
keywords: "6C81E0", // red1
|
||||
literals: "A3ABFF", // fg1
|
||||
numerics: "8A46CF", // purple1
|
||||
punctuation: "99C9C9", // orange1
|
||||
strs: "618c84", // aqua1
|
||||
types: "B8B9D4", // yellow1
|
||||
special: "de5252", // blue1
|
||||
};
|
||||
+20
-54
@@ -1,35 +1,30 @@
|
||||
use std::any::Any;
|
||||
use egui::{Ui, Vec2, WidgetText};
|
||||
use egui_tiles::TileId;
|
||||
use emulator_core::{
|
||||
memory::PhysAddr,
|
||||
memory::ram::{MemoryBank, Page}
|
||||
};
|
||||
use crate::ui::debugger::Action;
|
||||
use crate::ui::debugger::panels::TilePane;
|
||||
use super::Component;
|
||||
use emulator_core::{memory::PhysAddr, EmulatorHandle};
|
||||
use crate::ui::Action;
|
||||
use crate::ui::panels::TilePane;
|
||||
|
||||
pub struct FrameBuffer {
|
||||
width: usize,
|
||||
height: usize,
|
||||
addr: PhysAddr,
|
||||
mem: MemoryBank,
|
||||
pub buffer: Vec<u32>, // RGBA pixels
|
||||
visible: bool,
|
||||
emulator: EmulatorHandle,
|
||||
pub buffer: Vec<u8>,
|
||||
|
||||
texture: Option<egui::TextureHandle>,
|
||||
}
|
||||
|
||||
impl FrameBuffer {
|
||||
pub fn new(width: u32, height: u32, addr: PhysAddr, mem: MemoryBank) -> Self {
|
||||
pub fn new(width: u32, height: u32, addr: PhysAddr, emulator: EmulatorHandle) -> Self {
|
||||
let width = width as usize;
|
||||
let height = height as usize;
|
||||
Self {
|
||||
width: width as usize,
|
||||
height: height as usize,
|
||||
width,
|
||||
height,
|
||||
addr,
|
||||
mem,
|
||||
buffer: vec![0u32; width as usize * height as usize],
|
||||
visible: true,
|
||||
|
||||
emulator,
|
||||
buffer: vec![0; width * height * 4],
|
||||
texture: None,
|
||||
}
|
||||
}
|
||||
@@ -40,25 +35,10 @@ impl FrameBuffer {
|
||||
pub fn height(&self) -> usize {
|
||||
self.height
|
||||
}
|
||||
pub fn visible(&mut self) -> &mut bool {
|
||||
&mut self.visible
|
||||
}
|
||||
|
||||
fn internal_read(&self) -> Vec<u32> {
|
||||
fn internal_read(&mut self) -> Vec<u8> {
|
||||
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()
|
||||
self.emulator.mem_read(self.addr, byte_size as u32)
|
||||
}
|
||||
|
||||
pub fn changed(&mut self) -> bool {
|
||||
@@ -70,7 +50,7 @@ impl FrameBuffer {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn read(&self) -> &[u32] {
|
||||
pub fn read(&self) -> &[u8] {
|
||||
&self.buffer
|
||||
}
|
||||
|
||||
@@ -87,27 +67,12 @@ impl TilePane for FrameBuffer {
|
||||
fn title(&self) -> WidgetText {
|
||||
"Framebuffer".into()
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option<Action> {
|
||||
Component::ui(self, ui);
|
||||
None
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option<Action> {
|
||||
let changed = self.changed();
|
||||
|
||||
// get or create the texture
|
||||
@@ -126,10 +91,10 @@ impl Component for FrameBuffer {
|
||||
if changed {
|
||||
let pixels: Vec<egui::Color32> = self
|
||||
.buffer
|
||||
.as_chunks::<4>().0
|
||||
.iter()
|
||||
.map(|&p| {
|
||||
let bytes = p.to_le_bytes();
|
||||
egui::Color32::from_rgba_premultiplied(bytes[0], bytes[1], bytes[2], bytes[3])
|
||||
.map(|&[red, green, blue, alpha]| {
|
||||
egui::Color32::from_rgba_premultiplied(red, green, blue, alpha)
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -153,5 +118,6 @@ impl Component for FrameBuffer {
|
||||
};
|
||||
|
||||
ui.image(egui::load::SizedTexture::new(texture.id(), size));
|
||||
None
|
||||
}
|
||||
}
|
||||
+12
-32
@@ -4,17 +4,16 @@ use egui::{Ui, WidgetText};
|
||||
use egui_extras::{Column, TableBuilder};
|
||||
use egui_tiles::{Tile, TileId};
|
||||
use common::prelude::Instruction;
|
||||
use emulator_core::EmulatorHandle;
|
||||
use emulator_core::memory::ram::MemoryBank;
|
||||
use crate::ui::debugger::Action;
|
||||
use crate::ui::debugger::panels::TilePane;
|
||||
use super::Component;
|
||||
use crate::ui::Action;
|
||||
use crate::ui::panels::TilePane;
|
||||
|
||||
pub struct MemoryInspector {
|
||||
memory: MemoryBank,
|
||||
emulator: EmulatorHandle,
|
||||
|
||||
view_size: u32,
|
||||
view_addr: u32,
|
||||
visible: bool,
|
||||
addr_input: String,
|
||||
|
||||
// settings
|
||||
@@ -23,14 +22,12 @@ pub struct MemoryInspector {
|
||||
|
||||
impl MemoryInspector {
|
||||
#[must_use]
|
||||
pub fn new(memory: MemoryBank) -> Self {
|
||||
pub fn new(emulator: EmulatorHandle) -> Self {
|
||||
Self {
|
||||
memory,
|
||||
emulator,
|
||||
view_size: 1024,
|
||||
view_addr: 0,
|
||||
visible: false,
|
||||
addr_input: String::from("0x00"),
|
||||
|
||||
memory_viewer: MemoryViewer::new()
|
||||
}
|
||||
}
|
||||
@@ -41,27 +38,8 @@ impl TilePane for MemoryInspector {
|
||||
fn title(&self) -> WidgetText {
|
||||
"Memory Inspector".into()
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option<Action> {
|
||||
Component::ui(self, ui);
|
||||
None
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for MemoryInspector {
|
||||
fn title(&self) -> &'static str {
|
||||
"Memory Inspector"
|
||||
}
|
||||
|
||||
fn visible(&mut self) -> &mut bool {
|
||||
&mut self.visible
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Address:");
|
||||
|
||||
@@ -96,12 +74,14 @@ impl Component for MemoryInspector {
|
||||
ui.label("(hex or decimal)");
|
||||
});
|
||||
|
||||
let mut window = vec![0u8; self.view_size as usize];
|
||||
for i in 0..self.view_size {
|
||||
window[i as usize] = self.memory.read_byte(self.view_addr + i as u32);
|
||||
let window = self.emulator.mem_read(self.view_addr, self.view_size);
|
||||
self.memory_viewer.ui(ui, &window, self.view_addr as usize);
|
||||
None
|
||||
}
|
||||
|
||||
self.memory_viewer.ui(ui, &window, self.view_addr as usize)
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
+8
-3
@@ -1,12 +1,17 @@
|
||||
pub mod documentation;
|
||||
pub mod editor;
|
||||
pub mod about;
|
||||
pub mod disassembler;
|
||||
pub mod display;
|
||||
pub mod framebuffer;
|
||||
pub mod memory;
|
||||
pub mod registers;
|
||||
pub mod serial;
|
||||
|
||||
use std::any::Any;
|
||||
use egui::{Id, Response, Stroke, StrokeKind, Ui, Widget};
|
||||
use egui_tiles::{Behavior, Container, SimplificationOptions, TabState, Tile, TileId, Tiles, Tree, UiResponse};
|
||||
use crate::ui::debugger::Action;
|
||||
use crate::ui::debugger::Action::RequestCloseTab;
|
||||
use crate::ui::Action;
|
||||
use crate::ui::Action::RequestCloseTab;
|
||||
|
||||
/// Anything that can live inside a tab in the central tiled area.
|
||||
/// `as_any_mut` lets callers reach back into a specific pane's concrete
|
||||
+27
-44
@@ -1,38 +1,22 @@
|
||||
use std::{f32, sync::Arc};
|
||||
use std::f32;
|
||||
use std::any::Any;
|
||||
use egui::{Ui, WidgetText};
|
||||
use egui_tiles::TileId;
|
||||
use common::prelude::Register;
|
||||
use emulator_core::processor::state::SharedState;
|
||||
use crate::ui::components::memory::MemoryInspector;
|
||||
use crate::ui::debugger::Action;
|
||||
use crate::ui::debugger::panels::TilePane;
|
||||
use super::Component;
|
||||
use emulator_core::EmulatorHandle;
|
||||
use crate::ui::Action;
|
||||
use crate::ui::panels::TilePane;
|
||||
|
||||
pub struct Registers {
|
||||
state: Arc<SharedState>,
|
||||
visible: bool,
|
||||
state: EmulatorHandle,
|
||||
}
|
||||
|
||||
impl TilePane for Registers {
|
||||
fn title(&self) -> WidgetText {
|
||||
"Registers".into()
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option<Action> {
|
||||
Component::ui(self, ui);
|
||||
None
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Registers {
|
||||
fn section(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
ui: &mut Ui,
|
||||
registers: Vec<(Register, u32)>,
|
||||
col_pairs: usize,
|
||||
name: &str,
|
||||
@@ -54,7 +38,7 @@ impl Registers {
|
||||
for chunk in registers.chunks(col_pairs) {
|
||||
for (reg, val) in chunk {
|
||||
ui.label(reg.to_string());
|
||||
ui.label(format!("0x{:08X} ({})", val, val));
|
||||
ui.label(format!("0x{:08X} ({:012})", val, val));
|
||||
}
|
||||
for _ in chunk.len()..col_pairs {
|
||||
ui.label("");
|
||||
@@ -68,27 +52,21 @@ impl Registers {
|
||||
}
|
||||
|
||||
impl Registers {
|
||||
pub fn new(state: Arc<SharedState>) -> Self {
|
||||
pub fn new(state: EmulatorHandle) -> Self {
|
||||
Self {
|
||||
state,
|
||||
visible: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Registers {
|
||||
fn title(&self) -> &str {
|
||||
"Registers"
|
||||
impl TilePane for Registers {
|
||||
fn title(&self) -> WidgetText {
|
||||
"Registers".into()
|
||||
}
|
||||
fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option<Action> {
|
||||
|
||||
fn visible(&mut self) -> &mut bool {
|
||||
&mut self.visible
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
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,
|
||||
@@ -101,7 +79,7 @@ impl Component for Registers {
|
||||
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]))
|
||||
.map(|i| (Register::from_u8(i).unwrap(), self.state.registers()[i as usize]))
|
||||
.collect();
|
||||
self.section(
|
||||
ui,
|
||||
@@ -113,18 +91,18 @@ impl Component for Registers {
|
||||
|
||||
// ── Stack ─────────────────────────────────────────────────
|
||||
let stack_registers = vec![
|
||||
(Register::Spr, state.registers[Register::Spr as usize]),
|
||||
(Register::Bpr, state.registers[Register::Bpr as usize]),
|
||||
(Register::Spr, self.state.get_register(Register::Spr)),
|
||||
(Register::Bpr, self.state.get_register(Register::Bpr)),
|
||||
];
|
||||
self.section(ui, 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]),
|
||||
(Register::Acc, self.state.get_register(Register::Acc)),
|
||||
(Register::Ret, self.state.get_register(Register::Ret)),
|
||||
(Register::Idr, self.state.get_register(Register::Idr)),
|
||||
(Register::Mmr, self.state.get_register(Register::Mmr)),
|
||||
];
|
||||
self.section(
|
||||
ui,
|
||||
@@ -136,11 +114,16 @@ impl Component for Registers {
|
||||
|
||||
// ── 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]),
|
||||
(Register::Pcx, self.state.get_register(Register::Pcx)),
|
||||
(Register::Sts, self.state.get_register(Register::Sts)),
|
||||
(Register::Cir, self.state.get_register(Register::Cir)),
|
||||
];
|
||||
self.section(ui, system_registers, col_pairs, "System Registers");
|
||||
});
|
||||
None
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
+25
-53
@@ -1,28 +1,18 @@
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::Ordering,
|
||||
mpsc::{Receiver, Sender},
|
||||
},
|
||||
};
|
||||
use std::any::Any;
|
||||
use egui::{Ui, WidgetText};
|
||||
use egui_tiles::TileId;
|
||||
use emulator_core::{
|
||||
memory::{
|
||||
use emulator_core::{memory::{
|
||||
PhysAddr,
|
||||
ram::MemoryBank
|
||||
},
|
||||
processor::{
|
||||
interrupts::Interrupt,
|
||||
state::SharedState
|
||||
}
|
||||
};
|
||||
use crate::ui::components::registers::Registers;
|
||||
use crate::ui::debugger::Action;
|
||||
use crate::ui::debugger::panels::TilePane;
|
||||
use super::Component;
|
||||
}, EmulatorHandle};
|
||||
use emulator_core::processor::Interrupt;
|
||||
use crate::ui::Action;
|
||||
use crate::ui::panels::TilePane;
|
||||
|
||||
/// ## SERIAL LAYOUT: (from the perspective of code inside the ui)
|
||||
/// - [base+0x00]: ser_out valid flag - set by kernel after writing a byte to serial
|
||||
@@ -34,29 +24,11 @@ pub struct Serial {
|
||||
pub write_tx: Sender<u8>,
|
||||
pub read_buff: Vec<u8>,
|
||||
pub write_buff: String,
|
||||
|
||||
pub visible: bool,
|
||||
}
|
||||
|
||||
impl TilePane for Serial {
|
||||
fn title(&self) -> WidgetText {
|
||||
"Serial".into()
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option<Action> {
|
||||
Component::ui(self, ui);
|
||||
None
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Serial {
|
||||
fn uart_io_thread(
|
||||
mem: MemoryBank,
|
||||
state: Arc<SharedState>,
|
||||
mut handle: EmulatorHandle,
|
||||
uart_base: PhysAddr,
|
||||
tx: Sender<u8>,
|
||||
rx: Receiver<u8>,
|
||||
@@ -68,42 +40,41 @@ impl Serial {
|
||||
write_buffer.push_back(byte);
|
||||
}
|
||||
|
||||
let ser_out_valid = mem.read_byte(uart_base + 0);
|
||||
let ser_out_valid = handle.read_byte(uart_base + 0);
|
||||
// Kernel => ui: valid flag in byte 0, data in byte 1
|
||||
if ser_out_valid == 0x01 {
|
||||
let ser_out = mem.read_byte(uart_base + 1);
|
||||
let ser_out = handle.read_byte(uart_base + 1);
|
||||
let _ = tx.send(ser_out);
|
||||
mem.write_byte(uart_base, 0x00); // clear the whole word
|
||||
handle.write_byte(uart_base, 0x00); // clear the whole word
|
||||
}
|
||||
|
||||
if !write_buffer.is_empty() {
|
||||
// Emulator => kernel: valid flag in byte 2, data in byte 3
|
||||
let ser_in_valid = mem.read_byte(uart_base + 2);
|
||||
let ser_in_valid = handle.read_byte(uart_base + 2);
|
||||
if ser_in_valid == 0x00 {
|
||||
if let Some(byte) = write_buffer.pop_front() {
|
||||
mem.write_byte(uart_base + 3, byte);
|
||||
mem.write_byte(uart_base + 2, 0x01);
|
||||
handle.write_byte(uart_base + 3, byte);
|
||||
handle.write_byte(uart_base + 2, 0x01);
|
||||
}
|
||||
}
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(2));
|
||||
|
||||
state.interrupt_queue.send(Interrupt::SerialIn).unwrap();
|
||||
handle.interrupt(Interrupt::SerialIn).unwrap();
|
||||
}
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(1));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(uart_base: PhysAddr, state: Arc<SharedState>, mem_handle: MemoryBank) -> Self {
|
||||
pub fn new(uart_base: PhysAddr, handle: EmulatorHandle) -> Self {
|
||||
let (read_tx, read_rx) = std::sync::mpsc::channel();
|
||||
let (write_tx, write_rx) = std::sync::mpsc::channel();
|
||||
let _ = std::thread::spawn(move || {
|
||||
Self::uart_io_thread(mem_handle, state, uart_base, read_tx, write_rx)
|
||||
Self::uart_io_thread(handle, uart_base, read_tx, write_rx)
|
||||
});
|
||||
|
||||
Self {
|
||||
visible: false,
|
||||
write_tx,
|
||||
read_rx,
|
||||
read_buff: Vec::new(),
|
||||
@@ -118,16 +89,11 @@ impl Serial {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Serial {
|
||||
fn title(&self) -> &str {
|
||||
"Serial"
|
||||
impl TilePane for Serial {
|
||||
fn title(&self) -> WidgetText {
|
||||
"Serial".into()
|
||||
}
|
||||
|
||||
fn visible(&mut self) -> &mut bool {
|
||||
&mut self.visible
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option<Action> {
|
||||
self.update();
|
||||
|
||||
// Text field for input and a send button
|
||||
@@ -145,5 +111,11 @@ impl Component for Serial {
|
||||
if ui.button("Clear").clicked() {
|
||||
self.read_buff.clear();
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -1,8 +1,7 @@
|
||||
use eframe::emath::Vec2;
|
||||
use egui::{Color32, Frame, Stroke};
|
||||
use egui::widget_style::{Classes, WidgetState};
|
||||
use emulator_core::processor::state::RunningState;
|
||||
|
||||
use emulator_core::processor::RunningState;
|
||||
|
||||
pub struct PaddedWidget {
|
||||
text: String,
|
||||
Reference in New Issue
Block a user