initial commit

This commit is contained in:
2026-03-03 10:26:19 +00:00
commit ebb9489410
15 changed files with 633 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
/target
+143
View File
@@ -0,0 +1,143 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "arc-swap"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9f3647c145568cec02c42054e07bdf9a5a698e15b466fb2341bfc393cd24aa5"
dependencies = [
"rustversion",
]
[[package]]
name = "bitflags"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
dependencies = [
"serde_core",
]
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "dsa-emu"
version = "0.1.0"
dependencies = [
"arc-swap",
"fxhash",
"ron",
"serde",
]
[[package]]
name = "fxhash"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
dependencies = [
"byteorder",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
dependencies = [
"proc-macro2",
]
[[package]]
name = "ron"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd490c5b18261893f14449cbd28cb9c0b637aebf161cd77900bfdedaff21ec32"
dependencies = [
"bitflags",
"once_cell",
"serde",
"serde_derive",
"typeid",
"unicode-ident",
]
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "syn"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "typeid"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+10
View File
@@ -0,0 +1,10 @@
[package]
name = "dsa-emu"
version = "0.1.0"
edition = "2024"
[dependencies]
arc-swap = "1.8.2"
fxhash = "0.2.1"
ron = "0.12.0"
serde = { version = "1.0.228", features = ["derive"] }
+7
View File
@@ -0,0 +1,7 @@
LoadConfig(
// defined as the bootloader in dsa.mmap.ron
start_address: 0x1000,
// maximum allowed bootloader size (as to not overlap MMIO)
max_size: 0x4000,
)
+41
View File
@@ -0,0 +1,41 @@
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: 0x5000,
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
)
]
)
+32
View File
@@ -0,0 +1,32 @@
use arc_swap::ArcSwap;
use std::{
sync::{
Arc,
atomic::{AtomicBool, AtomicU8},
},
thread,
time::Duration,
};
use crate::processor::{processor::Emulator, state::SharedState};
mod memory;
mod processor;
fn main() {
let mut emulator = Emulator::new();
let state = emulator.state_handle();
let runner = thread::spawn(move || emulator.run());
let observer = thread::spawn(|| observe(state));
runner.join().unwrap();
observer.join().unwrap();
}
fn observe(state: Arc<SharedState>) {
loop {
thread::sleep(std::time::Duration::from_millis(100));
let state = state.proc.load();
println!("GP Registers: {:?}", state.gp_registers);
}
}
View File
+60
View File
@@ -0,0 +1,60 @@
use fxhash::FxHashMap;
use crate::memory::PhysAddr;
type Page = [u8; 4096];
pub struct MainStore {
pages: FxHashMap<PhysAddr, Page>,
}
impl MainStore {
pub fn new() -> Self {
Self {
pages: FxHashMap::default(),
}
}
#[inline(always)]
const fn segment_addr(addr: PhysAddr) -> (PhysAddr, usize) {
(addr & !(0xFFF), (addr & 0xFFF) as usize)
}
#[inline]
pub fn read_byte(&self, addr: PhysAddr) -> u8 {
let (page, offset) = Self::segment_addr(addr);
self.pages.get(&page).map(|p| p[offset]).unwrap_or(0)
}
#[inline]
pub fn read_word(&self, addr: PhysAddr) -> u32 {
let (page, offset) = Self::segment_addr(addr);
let page = self.pages.get(&page).unwrap_or(&[0; 4096]);
u32::from_be_bytes(page[offset..=offset + 3].try_into().unwrap())
}
#[inline]
pub fn read_page(&self, addr: PhysAddr) -> &[u8] {
self.pages.get(&addr).unwrap_or(&[0; 4096])
}
#[inline]
pub fn write_byte(&mut self, addr: PhysAddr, value: u8) {
let (page, offset) = Self::segment_addr(addr);
self.pages.entry(page).or_insert_with(|| [0; 4096])[offset] = value;
}
#[inline]
pub fn write_word(&mut self, addr: PhysAddr, value: u32) {
let (page, offset) = Self::segment_addr(addr);
let page = self.pages.entry(page).or_insert_with(|| [0; 4096]);
page[offset..=offset + 3].copy_from_slice(&value.to_be_bytes());
}
#[inline]
pub fn write_page(&mut self, addr: PhysAddr, value: &[u8; 4096]) {
let (page, offset) = Self::segment_addr(addr);
let page = self.pages.entry(page).or_insert_with(|| [0; 4096]);
page[offset..offset + value.len()].copy_from_slice(value);
}
}
+109
View File
@@ -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,
}
}
}
+65
View File
@@ -0,0 +1,65 @@
use serde::{Deserialize, Serialize};
use crate::memory::mmu::MMU;
mod cache;
pub mod mainstore;
pub mod mmu;
mod tlb;
pub type VirtAddr = u32;
pub type PhysAddr = u32;
pub enum FaultInfo {
PageFault,
}
#[repr(u32)]
#[derive(Serialize, Deserialize, Clone, Copy)]
enum RegionType {
Reserved,
Bootloader,
Kernel,
KernelTables,
Usable,
MMIO,
}
#[derive(Serialize, Deserialize, Clone, Copy)]
pub struct Region {
base: PhysAddr,
size: usize,
region_type: RegionType,
}
#[derive(Serialize, Deserialize)]
pub struct MemoryMap {
table_addr: PhysAddr,
pub regions: Vec<Region>,
}
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 identity_map(mmu: &mut MMU, region: &Region) {
let first_page = region.base >> 12;
let page_count = (region.size >> 12) as u32;
for page in (first_page..first_page + page_count).map(|p| p << 12) {
mmu.create_mapping(page, page);
}
}
}
impl Default for MemoryMap {
fn default() -> Self {
ron::from_str(include_str!("../../config/dsa.mmap.ron")).unwrap()
}
}
+54
View File
@@ -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,
};
}
}
+4
View File
@@ -0,0 +1,4 @@
pub enum Interrupt {
PageFault,
ProtectionFault,
}
+3
View File
@@ -0,0 +1,3 @@
mod interrupts;
pub mod processor;
pub mod state;
+67
View File
@@ -0,0 +1,67 @@
use std::{sync::Arc, thread, time::Duration};
use crate::{
memory::{FaultInfo, MemoryMap, mainstore::MainStore, mmu::MMU},
processor::{
interrupts::Interrupt,
state::{ProcessorSnapshot, SharedState},
},
};
pub struct Emulator {
internal_state: ProcessorSnapshot,
shared_state: Arc<SharedState>,
mmu: MMU,
mainstore: MainStore,
}
impl Emulator {
pub fn new() -> Self {
Self {
internal_state: ProcessorSnapshot::default(),
shared_state: Arc::new(SharedState::new()),
mmu: MMU::new(),
mainstore: MainStore::new(),
}
}
pub fn state_handle(&self) -> Arc<SharedState> {
self.shared_state.clone()
}
pub fn run(&mut self) {
let mut clock = 0;
let regions = MemoryMap::default();
self.mmu.paging_enabled(true);
MemoryMap::identity_map(&mut self.mmu, regions.regions.get(0).unwrap());
loop {
thread::sleep(Duration::from_micros(1000));
match self.mmu.lookup(clock) {
Ok(addr) => {
let x = self.mainstore.read_word(addr);
println!("{}", x);
self.mainstore.write_word(addr, x + 1);
}
Err(FaultInfo::PageFault) => self.interrupt(Interrupt::PageFault),
}
self.internal_state.gp_registers[0] += 10;
if clock % 1000 == 0 {
self.shared_state
.proc
.store(Arc::new(self.internal_state.clone()));
}
clock += 1;
}
}
fn interrupt(&mut self, int: Interrupt) {
todo!()
}
}
+37
View File
@@ -0,0 +1,37 @@
use std::sync::{
Arc,
atomic::{AtomicBool, AtomicU8},
};
use arc_swap::ArcSwap;
pub struct SharedState {
pub proc: ArcSwap<ProcessorSnapshot>,
pub running: AtomicBool,
pub interrupt: AtomicU8,
}
impl SharedState {
pub fn new() -> Self {
Self {
proc: ArcSwap::new(Arc::new(ProcessorSnapshot::default())),
running: AtomicBool::new(true),
interrupt: AtomicU8::new(0),
}
}
}
#[derive(Debug, Clone)]
pub struct ProcessorSnapshot {
pub gp_registers: [u32; 16],
}
impl Default for ProcessorSnapshot {
fn default() -> Self {
Self {
gp_registers: [0; 16],
}
}
}