Merge remote-tracking branch 'refs/remotes/origin/main'
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
use crate::backend::memory::{FaultInfo, PhysAddr, VirtAddr, tlb::TLB};
|
||||
|
||||
pub struct MMU {
|
||||
buffer: TLB,
|
||||
enable_paging: bool,
|
||||
|
||||
// Memory Management Register (MMR) Tells MMU where to find page tables
|
||||
mmr_value: u32,
|
||||
|
||||
page_table: Box<[Option<PageTable>; 1024]>,
|
||||
}
|
||||
|
||||
impl MMU {
|
||||
pub fn new() -> Self {
|
||||
MMU {
|
||||
buffer: TLB::new(),
|
||||
enable_paging: false,
|
||||
mmr_value: 0,
|
||||
page_table: Box::new([const { None }; 1024]),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn paging_enabled(&mut self, enable: bool) {
|
||||
self.enable_paging = enable;
|
||||
}
|
||||
|
||||
pub fn tlb_insert(&mut self, virtual_address: VirtAddr, physical_address: PhysAddr) {
|
||||
self.buffer.insert(virtual_address, physical_address);
|
||||
}
|
||||
|
||||
pub fn create_mapping(&mut self, virtual_address: VirtAddr, physical_address: PhysAddr) {
|
||||
let table = (virtual_address >> 22) as usize;
|
||||
let page = ((virtual_address >> 12) & 0x3FF) as usize;
|
||||
|
||||
self.page_table[table].get_or_insert_default().entries[page] =
|
||||
Some(PageTableEntry::new(physical_address, true, true, false));
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn lookup(&mut self, virtual_address: VirtAddr) -> Result<PhysAddr, FaultInfo> {
|
||||
if !self.enable_paging {
|
||||
return Ok(virtual_address);
|
||||
}
|
||||
|
||||
if let Some(addr) = self.buffer.lookup(virtual_address) {
|
||||
return Ok(addr);
|
||||
}
|
||||
|
||||
self.walk(virtual_address)
|
||||
}
|
||||
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
fn walk(&mut self, virtual_address: VirtAddr) -> Result<PhysAddr, FaultInfo> {
|
||||
let phys_addr = self.walk_page_table(virtual_address)?;
|
||||
self.buffer.insert(virtual_address, phys_addr);
|
||||
Ok(phys_addr)
|
||||
}
|
||||
|
||||
fn walk_page_table(&mut self, virtual_address: VirtAddr) -> Result<PhysAddr, FaultInfo> {
|
||||
let table = (virtual_address >> 22) as usize;
|
||||
let page = ((virtual_address >> 12) & 0x3FF) as usize;
|
||||
|
||||
if let Some(table) = &self.page_table[table] {
|
||||
return table.entries[page]
|
||||
.map(|entry| entry.physical_addr)
|
||||
.ok_or(FaultInfo::PageFault);
|
||||
} else {
|
||||
Err(FaultInfo::PageFault)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct PageDir {
|
||||
entries: Box<[PageTable; 1024]>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct PageTable {
|
||||
entries: Box<[Option<PageTableEntry>; 1024]>,
|
||||
}
|
||||
|
||||
impl Default for PageTable {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
entries: Box::new([None; 1024]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct PageTableEntry {
|
||||
physical_addr: PhysAddr,
|
||||
writable: bool,
|
||||
present: bool,
|
||||
user: bool,
|
||||
}
|
||||
|
||||
impl PageTableEntry {
|
||||
fn new(physical_addr: PhysAddr, writable: bool, present: bool, user: bool) -> Self {
|
||||
Self {
|
||||
physical_addr,
|
||||
writable,
|
||||
present,
|
||||
user,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
mod cache;
|
||||
pub mod mmu;
|
||||
pub mod ram;
|
||||
mod tlb;
|
||||
|
||||
pub type VirtAddr = u32;
|
||||
pub type PhysAddr = u32;
|
||||
|
||||
pub enum FaultInfo {
|
||||
PageFault,
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
use std::{
|
||||
alloc::{Layout, alloc_zeroed, dealloc},
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
#[cfg(feature = "mainstore-arraymap")]
|
||||
pub mod arraymap;
|
||||
#[cfg(feature = "mainstore-arraymap")]
|
||||
pub use arraymap::ArrayStore;
|
||||
|
||||
#[cfg(feature = "mainstore-hashmap")]
|
||||
pub mod hashmap;
|
||||
#[cfg(feature = "mainstore-hashmap")]
|
||||
pub use hashmap::HashStore;
|
||||
|
||||
#[cfg(feature = "mainstore-stackarray")]
|
||||
pub mod stackarray;
|
||||
#[cfg(feature = "mainstore-stackarray")]
|
||||
pub use stackarray::StackArrayStore;
|
||||
|
||||
#[cfg(feature = "mainstore-bulkalloc")]
|
||||
pub mod bulkalloc;
|
||||
#[cfg(feature = "mainstore-bulkalloc")]
|
||||
pub use bulkalloc::BulkAllocStore;
|
||||
|
||||
// #[cfg(feature = "mainstore-prealloc")]
|
||||
// pub mod prealloc;
|
||||
// #[cfg(feature = "mainstore-prealloc")]
|
||||
// pub use prealloc::PreAllocStore;
|
||||
|
||||
use crate::backend::memory::PhysAddr;
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone)]
|
||||
pub struct Page([u8; 4096]);
|
||||
|
||||
impl Page {
|
||||
pub const SIZE: usize = 4096;
|
||||
pub const COUNT: usize = u32::MAX as usize / Self::SIZE + 1;
|
||||
|
||||
pub const MASK: u32 = 0xFFF;
|
||||
pub const ZERO: Self = Page::zeroed();
|
||||
|
||||
pub const fn zeroed() -> Self {
|
||||
Self([0; 4096])
|
||||
}
|
||||
|
||||
pub const fn from(data: [u8; 4096]) -> Self {
|
||||
Self(data)
|
||||
}
|
||||
|
||||
pub fn read_word(&self, offset: usize) -> u32 {
|
||||
u32::from_le_bytes(self.0[offset..offset + 4].try_into().unwrap())
|
||||
}
|
||||
|
||||
pub fn write_word(&mut self, offset: usize, value: u32) {
|
||||
self.0[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
|
||||
}
|
||||
|
||||
pub fn paginate(data: &[u8]) -> impl Iterator<Item = Page> + '_ {
|
||||
data.chunks(4096).map(|chunk| {
|
||||
let mut arr = [0u8; 4096];
|
||||
arr[..chunk.len()].copy_from_slice(chunk);
|
||||
Page(arr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Page {
|
||||
type Target = [u8];
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Page {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<u8>> for Page {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(data: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
let arr: [u8; 4096] = data
|
||||
.try_into()
|
||||
.map_err(|v: Vec<u8>| format!("Expected 4096 bytes, got {}", v.len()))?;
|
||||
Ok(Page(arr))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
const fn idx(addr: PhysAddr) -> (usize, usize) {
|
||||
((addr >> 12) as usize, (addr & 0xFFF) as usize)
|
||||
}
|
||||
|
||||
pub struct MemoryBank {
|
||||
heap: *mut u8,
|
||||
}
|
||||
|
||||
unsafe impl Send for MemoryBank {}
|
||||
unsafe impl Sync for MemoryBank {}
|
||||
impl MemoryBank {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
heap: unsafe {
|
||||
// allocate the full 32 bit address range.
|
||||
alloc_zeroed(Layout::from_size_align(u32::MAX as usize, 4096).unwrap())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn dealloc(&self) {
|
||||
unsafe {
|
||||
dealloc(
|
||||
self.heap,
|
||||
Layout::from_size_align(u32::MAX as usize, 4096).unwrap(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
unsafe {
|
||||
std::ptr::write_bytes(self.heap, 0, u32::MAX as usize);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn read_word(&self, addr: PhysAddr) -> u32 {
|
||||
unsafe { (self.heap.add(addr as usize) as *const u32).read_volatile() }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn read_byte(&self, addr: PhysAddr) -> u8 {
|
||||
unsafe { self.heap.add(addr as usize).read_volatile() }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn read_page(&self, addr: PhysAddr) -> &Page {
|
||||
debug_assert_eq!(addr % 4096, 0);
|
||||
unsafe { (self.heap.add(addr as usize) as *const Page).as_ref_unchecked() }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn write_byte(&self, addr: PhysAddr, value: u8) {
|
||||
unsafe { self.heap.add(addr as usize).write_volatile(value) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn write_word(&self, addr: PhysAddr, value: u32) {
|
||||
unsafe { (self.heap.add(addr as usize) as *mut u32).write_volatile(value) };
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn write_page(&self, addr: PhysAddr, value: &Page) {
|
||||
unsafe {
|
||||
(self.heap.add(addr as usize) as *mut Page).copy_from(value, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for MemoryBank {
|
||||
fn clone(&self) -> Self {
|
||||
Self { heap: self.heap }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
use crate::backend::memory::{PhysAddr, VirtAddr};
|
||||
|
||||
const TLB_SIZE: usize = 256;
|
||||
const TLB_MASK: u32 = (TLB_SIZE - 1) as u32;
|
||||
|
||||
pub struct TLB {
|
||||
entries: [TlbEntry; TLB_SIZE],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct TlbEntry {
|
||||
virtual_address: VirtAddr,
|
||||
physical_address: PhysAddr,
|
||||
valid: bool,
|
||||
}
|
||||
|
||||
impl TLB {
|
||||
pub fn new() -> Self {
|
||||
TLB {
|
||||
entries: [TlbEntry {
|
||||
virtual_address: 0,
|
||||
physical_address: 0,
|
||||
valid: false,
|
||||
}; TLB_SIZE],
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn index(page: u32) -> usize {
|
||||
let hash = page ^ (page >> 12);
|
||||
(hash & TLB_MASK) as usize
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn lookup(&self, virtual_address: VirtAddr) -> Option<PhysAddr> {
|
||||
let page = virtual_address >> 12; // for 4KB pages
|
||||
let entry = self.entries[Self::index(page)];
|
||||
if entry.valid && entry.virtual_address == virtual_address {
|
||||
Some(entry.physical_address)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn insert(&mut self, virt: VirtAddr, phys: PhysAddr) {
|
||||
let idx = Self::index(virt) as usize;
|
||||
self.entries[idx] = TlbEntry {
|
||||
virtual_address: virt,
|
||||
physical_address: phys,
|
||||
valid: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod memory;
|
||||
pub mod processor;
|
||||
@@ -0,0 +1,52 @@
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum Interrupt {
|
||||
// CPU exceptions 0-31
|
||||
InvalidInterrupt = 0,
|
||||
PageFault = 1,
|
||||
ProtectionFault = 2,
|
||||
InvalidOpcode = 3,
|
||||
DivideByZero = 4,
|
||||
StackOverflow = 5,
|
||||
WriteToReadOnly = 6,
|
||||
ReadFromWriteOnly = 7,
|
||||
UnmappedIo = 8,
|
||||
// 8-31 reserved for future CPU exceptions
|
||||
|
||||
// IRQ's (32-63)
|
||||
Hardware(u8),
|
||||
|
||||
// Syscalls (64-255)
|
||||
// OS defined, program uses INT 64-127
|
||||
Software(u8) = 128,
|
||||
}
|
||||
|
||||
impl Interrupt {
|
||||
pub fn code(&self) -> u8 {
|
||||
match self {
|
||||
Interrupt::InvalidInterrupt => 0,
|
||||
Interrupt::PageFault => 1,
|
||||
Interrupt::ProtectionFault => 2,
|
||||
Interrupt::InvalidOpcode => 3,
|
||||
Interrupt::DivideByZero => 4,
|
||||
Interrupt::StackOverflow => 5,
|
||||
Interrupt::WriteToReadOnly => 6,
|
||||
Interrupt::ReadFromWriteOnly => 7,
|
||||
Interrupt::UnmappedIo => 8,
|
||||
Interrupt::Hardware(x) => {
|
||||
if *x < 32 || *x > 63 {
|
||||
0
|
||||
} else {
|
||||
*x
|
||||
}
|
||||
}
|
||||
Interrupt::Software(x) => {
|
||||
if *x < 64 {
|
||||
0
|
||||
} else {
|
||||
*x
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod interrupts;
|
||||
pub mod processor;
|
||||
pub mod state;
|
||||
@@ -0,0 +1,561 @@
|
||||
use std::{
|
||||
hint::unlikely,
|
||||
ops::AddAssign,
|
||||
sync::{
|
||||
atomic::Ordering,
|
||||
mpsc::{self, TryRecvError},
|
||||
Arc,
|
||||
},
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use common::isa::instructions::{Instruction, Opcode};
|
||||
use common::isa::register::Register;
|
||||
use crate::{
|
||||
<<<<<<< HEAD:dsa/emulator/src/processor/processor.rs
|
||||
config::{IoMapping, MemoryMap, RegionType},
|
||||
io::{IoDevice, MappedDevice},
|
||||
memory::{mmu::MMU, ram::RandomAccessMemory},
|
||||
processor::{interrupts::Interrupt, state::SharedState},
|
||||
Page,
|
||||
=======
|
||||
MemoryBank, Page, SharedState,
|
||||
backend::{memory::mmu::MMU, processor::interrupts::Interrupt},
|
||||
config::{IoMapping, MemoryMap, RegionType},
|
||||
>>>>>>> refs/remotes/origin/main:dsa/emulator/src/backend/processor/processor.rs
|
||||
};
|
||||
|
||||
pub struct Emulator {
|
||||
internal_state: ProcessorSnapshot,
|
||||
shared_state: Arc<SharedState>,
|
||||
|
||||
// Interrupts
|
||||
interrupts: mpsc::Receiver<u8>,
|
||||
pending_fault: Option<Interrupt>,
|
||||
|
||||
// memory
|
||||
mmu: MMU,
|
||||
mainstore: MemoryBank,
|
||||
|
||||
// IO
|
||||
mmio_region: Option<(u32, u32)>, // start end end of segment
|
||||
|
||||
// optionals - not necessarily set on boot.
|
||||
memory_map: Option<MemoryMap>,
|
||||
|
||||
// Config params
|
||||
__mmap_configured: bool,
|
||||
|
||||
time: Instant,
|
||||
}
|
||||
|
||||
unsafe impl Send for Emulator {}
|
||||
impl Emulator {
|
||||
pub fn new() -> Self {
|
||||
let (sender, receiver) = mpsc::channel::<u8>();
|
||||
|
||||
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
|
||||
__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 update(&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.halted = false;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cold]
|
||||
fn shutdown(&mut self) {
|
||||
self.internal_state.halted = true;
|
||||
}
|
||||
|
||||
pub fn halt(&mut self) {
|
||||
self.internal_state.halted = true;
|
||||
|
||||
// wait until we're un-halted.
|
||||
while self.internal_state.halted {
|
||||
self.wait_for_instructions();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_update(&mut self) {
|
||||
// UI requested a state update.
|
||||
if self.shared_state.update_req.load(Ordering::Relaxed) {
|
||||
self.update();
|
||||
}
|
||||
|
||||
if self.shared_state.reseting.load(Ordering::Relaxed) {
|
||||
self.internal_state.registers = [0; Register::COUNT as usize];
|
||||
self.internal_state.clock = 0;
|
||||
|
||||
// sit in a wait loop while resetting.
|
||||
while self.shared_state.reseting.load(Ordering::Relaxed) {
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cold]
|
||||
pub fn wait_for_instructions(&mut self) {
|
||||
{
|
||||
// record time for previous execution.
|
||||
self.internal_state.time = self.time.elapsed();
|
||||
self.update();
|
||||
self.time = Instant::now();
|
||||
}
|
||||
|
||||
// if paused from the UI thread, just wait.
|
||||
while self.shared_state.paused.load(Ordering::Relaxed) {
|
||||
thread::sleep(Duration::from_millis(10));
|
||||
// UI is making changes / requesting updates.
|
||||
self.check_update();
|
||||
}
|
||||
|
||||
// if the cpu didn't halt on it's own then it's ready to run again.
|
||||
if !self.internal_state.halted {
|
||||
return;
|
||||
}
|
||||
|
||||
loop {
|
||||
// Check for interrupts.
|
||||
if let Ok(code) = self.interrupts.recv_timeout(Duration::from_millis(100)) {
|
||||
self.internal_state.halted = false;
|
||||
self.interrupt(Interrupt::Software(code));
|
||||
break;
|
||||
}
|
||||
|
||||
// UI is resetting the emulator
|
||||
self.check_update();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> Result<(), String> {
|
||||
// wait for UI to initialise emulator.
|
||||
if self.shared_state.paused.load(Ordering::Relaxed) {
|
||||
self.wait_for_instructions();
|
||||
}
|
||||
|
||||
self.boot()?;
|
||||
self.time = Instant::now();
|
||||
|
||||
'emu: loop {
|
||||
// so that it always returns zero. this is more performance friendly than checking
|
||||
// for a zero register each access as it's branchless
|
||||
*self.mut_reg(Register::Zero) = 0;
|
||||
|
||||
// IMPORTANT
|
||||
// do not change anything about this loop. it's fully optimised afaik.
|
||||
// Check for commands or hardware Interrupts every 32k cycles
|
||||
if unlikely(self.internal_state.clock & 0x7FFF == 0) {
|
||||
// Update UI thread (roughly every 512k cycles targeting 60 UPS)
|
||||
if unlikely(self.internal_state.clock & 0x7FFFF == 0) {
|
||||
if self.shared_state.update_req.load(Ordering::Relaxed) {
|
||||
self.update();
|
||||
}
|
||||
}
|
||||
|
||||
if unlikely(self.shared_state.paused.load(Ordering::Relaxed)) {
|
||||
self.wait_for_instructions();
|
||||
}
|
||||
|
||||
match self.interrupts.try_recv() {
|
||||
Ok(code) => self.interrupt(Interrupt::Software(code)),
|
||||
Err(TryRecvError::Disconnected) => break 'emu,
|
||||
Err(TryRecvError::Empty) => {}
|
||||
}
|
||||
}
|
||||
|
||||
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.shutdown();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn interrupt(&mut self, int: Interrupt) {
|
||||
let idt = self.reg(Register::Idr);
|
||||
|
||||
*self.mut_reg(Register::Spr) -= 4;
|
||||
let spr = self.reg(Register::Spr);
|
||||
let pcx = self.reg(Register::Pcx);
|
||||
self.mem_write_word(spr, pcx);
|
||||
|
||||
*self.mut_reg(Register::Pcx) = self.mem_read_word(idt + int.code() as u32 * 4)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn execute(&mut self, word: Instruction) {
|
||||
// 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 {
|
||||
match Opcode::from_u8(word.opcode()) {
|
||||
// Nothing
|
||||
Some(Opcode::Nop) => {}
|
||||
|
||||
// move
|
||||
Some(Opcode::Mov) => {
|
||||
*self.mut_reg(word.dest_uc()) = self.reg(word.src1_uc());
|
||||
}
|
||||
Some(Opcode::CMov) => {
|
||||
if self.reg(word.misc_uc()) != 0 {
|
||||
*self.mut_reg(word.dest_uc()) = self.reg(word.src1_uc());
|
||||
}
|
||||
}
|
||||
|
||||
// load
|
||||
Some(Opcode::Ldbs) => todo!(),
|
||||
Some(Opcode::Ldhs) => todo!(),
|
||||
Some(Opcode::Ldb) => {
|
||||
*self.mut_reg(word.dest_uc()) = u32::from(
|
||||
self.mem_read_byte(self.reg(word.src1_uc()) + word.imm16() as u32),
|
||||
)
|
||||
}
|
||||
Some(Opcode::Ldh) => {
|
||||
*self.mut_reg(word.dest_uc()) = u32::from(
|
||||
self.mem_read_word(self.reg(word.src1_uc()) + word.imm16() as u32) >> 16,
|
||||
)
|
||||
}
|
||||
Some(Opcode::Ldw) => {
|
||||
*self.mut_reg(word.dest_uc()) = u32::from(
|
||||
self.mem_read_word(self.reg(word.src1_uc()) + word.imm16() as u32),
|
||||
)
|
||||
}
|
||||
|
||||
// store
|
||||
Some(Opcode::Stb) => {
|
||||
self.mem_write_byte(
|
||||
self.reg(word.dest_uc()) + word.imm16() as u32,
|
||||
self.reg(word.src1_uc()) as u8,
|
||||
);
|
||||
}
|
||||
Some(Opcode::Sth) => {
|
||||
self.mem_write_byte(
|
||||
self.reg(word.dest_uc()) + word.imm16() as u32,
|
||||
(self.reg(word.src1_uc()) as u16 >> 8) as u8,
|
||||
);
|
||||
self.mem_write_byte(
|
||||
self.reg(word.dest_uc()) + word.imm16() as u32 + 1,
|
||||
self.reg(word.src1_uc()) as u8,
|
||||
);
|
||||
}
|
||||
Some(Opcode::Stw) => {
|
||||
self.mem_write_word(
|
||||
self.reg(word.dest_uc()) + word.imm16() as u32,
|
||||
self.reg(word.src1_uc()),
|
||||
);
|
||||
}
|
||||
|
||||
// load immediate
|
||||
Some(Opcode::Lli) => {
|
||||
*self.mut_reg(word.dest_uc()) = word.imm16() as u32;
|
||||
}
|
||||
Some(Opcode::Lui) => {
|
||||
*self.mut_reg(word.dest_uc()) =
|
||||
(word.imm16() as u32) << 16 | self.reg(word.dest_uc());
|
||||
}
|
||||
|
||||
// Comparison
|
||||
Some(Opcode::Ieq) => {
|
||||
*self.mut_reg(word.dest_uc()) =
|
||||
(self.reg(word.src1_uc()) == self.reg(word.src2_uc())) as u32;
|
||||
}
|
||||
Some(Opcode::Ine) => {
|
||||
*self.mut_reg(word.dest_uc()) =
|
||||
(self.reg(word.src1_uc()) != self.reg(word.src2_uc())) as u32;
|
||||
}
|
||||
Some(Opcode::Ilt) => {
|
||||
*self.mut_reg(word.dest_uc()) =
|
||||
(self.reg(word.src1_uc()) < self.reg(word.src2_uc())) as u32;
|
||||
}
|
||||
Some(Opcode::Ile) => {
|
||||
*self.mut_reg(word.dest_uc()) =
|
||||
(self.reg(word.src1_uc()) <= self.reg(word.src2_uc())) as u32;
|
||||
}
|
||||
Some(Opcode::Igt) => {
|
||||
*self.mut_reg(word.dest_uc()) =
|
||||
(self.reg(word.src1_uc()) > self.reg(word.src2_uc())) as u32;
|
||||
}
|
||||
Some(Opcode::Ige) => {
|
||||
*self.mut_reg(word.dest_uc()) =
|
||||
(self.reg(word.src1_uc()) >= self.reg(word.src2_uc())) as u32;
|
||||
}
|
||||
|
||||
// Jump
|
||||
Some(Opcode::Jmp) => {
|
||||
*self.mut_reg(Register::Pcx) = self.reg(word.dest_uc()) + word.imm16() as u32
|
||||
}
|
||||
|
||||
Some(Opcode::Jez) => {
|
||||
if self.reg(word.src1_uc()) == 0 {
|
||||
*self.mut_reg(Register::Pcx) =
|
||||
self.reg(word.dest_uc()) + word.imm16() as u32
|
||||
}
|
||||
}
|
||||
Some(Opcode::Jnz) => {
|
||||
if self.reg(word.src1_uc()) != 0 {
|
||||
*self.mut_reg(Register::Pcx) =
|
||||
self.reg(word.dest_uc()) + word.imm16() as u32
|
||||
}
|
||||
}
|
||||
Some(Opcode::Jnc) => todo!(),
|
||||
Some(Opcode::Jic) => todo!(),
|
||||
|
||||
// Bitwise
|
||||
Some(Opcode::And) => {
|
||||
*self.mut_reg(word.dest_uc()) =
|
||||
self.reg(word.src1_uc()) & self.reg(word.src2_uc());
|
||||
}
|
||||
Some(Opcode::Nand) => {
|
||||
*self.mut_reg(word.dest_uc()) =
|
||||
!(self.reg(word.src1_uc()) & self.reg(word.src2_uc()));
|
||||
}
|
||||
Some(Opcode::Or) => {
|
||||
*self.mut_reg(word.dest_uc()) =
|
||||
self.reg(word.src1_uc()) | self.reg(word.src2_uc());
|
||||
}
|
||||
Some(Opcode::Nor) => {
|
||||
*self.mut_reg(word.dest_uc()) =
|
||||
!(self.reg(word.src1_uc()) | self.reg(word.src2_uc()));
|
||||
}
|
||||
Some(Opcode::Xor) => {
|
||||
*self.mut_reg(word.dest_uc()) =
|
||||
self.reg(word.src1_uc()) ^ self.reg(word.src2_uc());
|
||||
}
|
||||
Some(Opcode::Xnor) => {
|
||||
*self.mut_reg(word.dest_uc()) =
|
||||
!(self.reg(word.src1_uc()) ^ self.reg(word.src2_uc()));
|
||||
}
|
||||
Some(Opcode::Not) => {
|
||||
*self.mut_reg(word.dest_uc()) = !self.reg(word.src1_uc());
|
||||
}
|
||||
|
||||
// Arithmetic
|
||||
Some(Opcode::Add) => {
|
||||
*self.mut_reg(word.dest_uc()) =
|
||||
self.reg(word.src1_uc()) + self.reg(word.src2_uc());
|
||||
}
|
||||
|
||||
Some(Opcode::Sub) => {
|
||||
*self.mut_reg(word.dest_uc()) =
|
||||
self.reg(word.src1_uc()) - self.reg(word.src2_uc());
|
||||
}
|
||||
|
||||
Some(Opcode::Shl) => {
|
||||
*self.mut_reg(word.dest_uc()) = self.reg(word.src1_uc())
|
||||
<< (self.reg(word.src2_uc()) + word.shamt() as u32);
|
||||
}
|
||||
|
||||
Some(Opcode::Shr) => {
|
||||
*self.mut_reg(word.dest_uc()) = self.reg(word.src1_uc())
|
||||
>> (self.reg(word.src2_uc()) + word.shamt() as u32);
|
||||
}
|
||||
|
||||
Some(Opcode::Addi) => {
|
||||
*self.mut_reg(word.dest_uc()) = self.reg(word.src1_uc()) + word.imm16() as u32;
|
||||
}
|
||||
|
||||
Some(Opcode::Subi) => {
|
||||
*self.mut_reg(word.dest_uc()) = self.reg(word.src1_uc()) - word.imm16() as u32;
|
||||
}
|
||||
|
||||
// Utility
|
||||
Some(Opcode::Push) => {
|
||||
self.push(word.src1_uc());
|
||||
}
|
||||
Some(Opcode::Pop) => {
|
||||
self.pop(word.dest_uc());
|
||||
}
|
||||
|
||||
// Function
|
||||
Some(Opcode::Call) => {
|
||||
self.push(Register::Pcx);
|
||||
*self.mut_reg(Register::Pcx) = self.reg(word.dest_uc()) + word.imm16() as u32
|
||||
}
|
||||
Some(Opcode::Ret) => {
|
||||
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.halt(),
|
||||
Some(Opcode::IRet) => todo!(),
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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 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 {
|
||||
eprintln!("emulator wrote 0x{:08x} to uart", val);
|
||||
}
|
||||
self.mainstore.write_byte(addr, val);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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) {
|
||||
self.mainstore.write_word(addr, val);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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) {
|
||||
self.mainstore.write_page(addr, val);
|
||||
}
|
||||
|
||||
fn fault(&mut self, interrupt: Interrupt) {
|
||||
self.pending_fault = Some(interrupt);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProcessorSnapshot {
|
||||
pub halted: bool,
|
||||
pub clock: usize,
|
||||
pub time: Duration,
|
||||
pub registers: [u32; Register::COUNT as usize],
|
||||
}
|
||||
|
||||
impl Default for ProcessorSnapshot {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
halted: false,
|
||||
clock: 0,
|
||||
time: Duration::ZERO,
|
||||
registers: [0; Register::COUNT as usize],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
use std::sync::{Arc, atomic::AtomicBool, mpsc};
|
||||
|
||||
use arc_swap::ArcSwap;
|
||||
|
||||
use crate::backend::processor::processor::ProcessorSnapshot;
|
||||
|
||||
pub struct SharedState {
|
||||
pub proc: ArcSwap<ProcessorSnapshot>,
|
||||
|
||||
pub reseting: AtomicBool,
|
||||
pub paused: AtomicBool,
|
||||
pub update_req: AtomicBool,
|
||||
|
||||
interrupt_queue: mpsc::Sender<u8>,
|
||||
}
|
||||
|
||||
impl SharedState {
|
||||
pub fn new(sender: mpsc::Sender<u8>) -> Self {
|
||||
Self {
|
||||
reseting: AtomicBool::new(false),
|
||||
proc: ArcSwap::new(Arc::new(ProcessorSnapshot::default())),
|
||||
|
||||
paused: AtomicBool::new(true),
|
||||
interrupt_queue: sender,
|
||||
update_req: AtomicBool::new(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user