rewrote assembler backend to support custom executable format (DSE) and also refactored (moving crates around)

This commit is contained in:
2026-07-16 00:34:13 +01:00
parent 7a84073bc4
commit a4d42bdad6
60 changed files with 1486 additions and 1669 deletions
+15
View File
@@ -0,0 +1,15 @@
[package]
name = "emulator_core"
edition.workspace = true
version.workspace = true
authors.workspace = true
[lib]
name = "emulator_core"
path = "src/lib.rs"
[dependencies]
arc-swap = "1.9.2"
common = { version = "0.1.0", path = "../../common" }
serde = { version = "1.0.228", features = ["derive"] }
ron = "0.12.2"
@@ -0,0 +1,10 @@
/* @[crate::config::IoMap] */
IoMap(
items: [
IoMapping(
device: "Display",
base: 0x0002_0000,
size: 0x0000_07D0, // 2000 bytes (80x25)
),
]
)
@@ -0,0 +1,42 @@
/* @[crate::config::MemoryMap] */
MemoryMap(
table_addr: 0x1000,
regions: [
// 0000-1000 = reserved
// Region(
// base: 0x0,
// size: 0x1000,
// region_type: Reserved
// ),
// // 1000-4000 = bootloader
// Region(
// base: 0x1000,
// size: 0x3000,
// region_type: Bootloader
// ),
// 5000-0FFFFFFF = MMIO
Region(
base: 0x20000,
size: 0x0FFF_FFFF,
region_type: MMIO
),
// 10000000-BFFFFFFF = userspace
Region(
base: 0x1000_0000,
size: 0xBFFF_FFFF,
region_type: Usable
),
// C0000000-C0FFFFFF = kernel page tables
Region(
base: 0xC000_0000,
size: 0x100_0000,
region_type: KernelTables
),
// C1000000-FFFFFFFF = kernel
Region(
base: 0xC100_0000,
size: 0x3EFF_FFFF,
region_type: Kernel
)
]
)
@@ -0,0 +1,2 @@
pub const DEFAULT_MEMORY_MAP: &'static str = include_str!("./dsa.mmap.ron");
pub const DEFAULT_IO_MAP: &'static str = include_str!("./dsa.iomap.ron");
@@ -0,0 +1,97 @@
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;
mod default;
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
#[repr(u32)]
#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
pub enum RegionType {
Reserved,
Bootloader,
Kernel,
KernelTables,
Usable,
MMIO,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
pub struct Region {
pub base: PhysAddr,
pub size: u32,
pub region_type: RegionType,
// flags for the ui
#[serde(default)]
pub identity_map_on_boot: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MemoryMap {
pub table_addr: PhysAddr,
pub regions: Vec<Region>,
}
#[derive(Serialize, Deserialize)]
pub struct IoMapping {
pub device: String,
pub base: u32,
pub size: u32,
}
#[derive(Serialize, Deserialize)]
pub struct IoMap { items: Vec<IoMapping> }
impl Default for IoMap {
fn default() -> Self {
ron::from_str(DEFAULT_IO_MAP).unwrap()
}
}
impl Default for MemoryMap {
fn default() -> Self {
ron::from_str(DEFAULT_MEMORY_MAP).unwrap()
}
}
impl MemoryMap {
pub const DEFAULT_MEMORY_MAP_ADDR: PhysAddr = 0x1000;
pub fn new() -> Self {
MemoryMap {
table_addr: Self::DEFAULT_MEMORY_MAP_ADDR,
regions: Vec::new(),
}
}
pub fn apply(&self, cpu: &mut Emulator) {
for reg in &self.regions {
if reg.identity_map_on_boot {
Self::identity_map(cpu.mmu_mut(), reg);
}
}
let mem = cpu.memory_mut();
let mut offset = self.table_addr;
for region in &self.regions {
println!("Wrote entry: {region:?} to page table");
mem.write_word(offset, region.base);
mem.write_word(offset + 4, region.size);
mem.write_word(offset + 8, region.region_type as u32);
offset += 12;
}
}
pub fn identity_map(mmu: &mut MMU, region: &Region) {
let first_page = region.base >> 12;
let page_count = region.size >> 12;
for page in (first_page..first_page + page_count).map(|p| p << 12) {
mmu.create_mapping(page, page);
}
}
}
+5
View File
@@ -0,0 +1,5 @@
#![feature(likely_unlikely)]
pub mod processor;
pub mod memory;
pub mod config;
@@ -0,0 +1,109 @@
use crate::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::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::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,54 @@
#[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),
SerialIn = 32,
// 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::SerialIn => 32,
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,589 @@
use std::{
hint::unlikely,
ops::AddAssign,
sync::{
Arc,
atomic::Ordering,
mpsc::{self, TryRecvError},
},
thread,
time::{Duration, Instant},
};
use common::{
isa::instructions::{Instruction, Opcode},
isa::register::Register
};
use crate::{
memory::{
mmu::MMU,
ram::MemoryBank
},
processor::{
interrupts::Interrupt,
state::SharedState
}
};
use crate::config::{MemoryMap, RegionType};
use crate::memory::ram::Page;
pub struct Emulator {
internal_state: ProcessorSnapshot,
shared_state: Arc<SharedState>,
// Interrupts
interrupts: mpsc::Receiver<Interrupt>,
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::<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
__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 let x = self.shared_state.goto.load(Ordering::Relaxed) && x != -1 {
*self.mut_reg(Register::Pcx) = x as u32;
self.shared_state.goto.store(-1, Ordering::Relaxed);
}
if self.shared_state.reseting.load(Ordering::Relaxed) {
self.internal_state.registers = [0; Register::COUNT as usize];
self.internal_state.clock = 0;
// sit in a wait loop while resetting.
while self.shared_state.reseting.load(Ordering::Relaxed) {
thread::sleep(Duration::from_millis(100));
}
self.internal_state.halted = false;
}
}
#[cold]
pub fn wait_for_instructions(&mut self) {
{
// record time for previous execution.
self.internal_state.time = self.time.elapsed();
self.update();
self.time = Instant::now();
}
// 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();
}
loop {
// Check for interrupts.
if let Ok(int) = self.interrupts.recv_timeout(Duration::from_millis(100)) {
self.internal_state.halted = false;
self.interrupt(int);
break;
}
// if the cpu didn't halt on it's own then it's ready to run again.
if !self.internal_state.halted {
return;
}
// UI is resetting the ui
self.check_update();
}
}
pub fn run(&mut self) -> Result<(), String> {
// wait for UI to initialise ui.
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(int) => self.interrupt(int),
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) {
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) {
// 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) => {
let addr = self.reg(word.src1_uc()) + word.imm16() as u32;
let hi = self.mem_read_byte(addr) as u32;
let lo = self.mem_read_byte(addr + 1) as u32;
*self.mut_reg(word.dest_uc()) = (hi << 8) | lo;
// *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) => {
self.pop(Register::Ret);
*self.mut_reg(Register::Pcx) = self.reg(Register::Ret);
println!(
"Returning from interrupt to address {:04X}",
self.reg(Register::Pcx)
);
}
None => {}
}
}
}
#[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 || 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);
}
}
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,31 @@
use std::sync::{Arc, atomic::AtomicBool, mpsc};
use std::sync::atomic::AtomicIsize;
use arc_swap::ArcSwap;
use crate::processor::processor::ProcessorSnapshot;
use super::interrupts::Interrupt;
pub struct SharedState {
pub proc: ArcSwap<ProcessorSnapshot>,
pub goto: AtomicIsize,
pub reseting: AtomicBool,
pub paused: AtomicBool,
pub update_req: AtomicBool,
pub interrupt_queue: mpsc::Sender<Interrupt>,
}
impl SharedState {
pub fn new(sender: mpsc::Sender<Interrupt>) -> Self {
Self {
reseting: AtomicBool::new(false),
proc: ArcSwap::new(Arc::new(ProcessorSnapshot::default())),
goto: AtomicIsize::new(-1),
paused: AtomicBool::new(true),
interrupt_queue: sender,
update_req: AtomicBool::new(false),
}
}
}