extracted emulator backend into independent crate
This commit is contained in:
Generated
+4
-2
@@ -5,9 +5,11 @@
|
|||||||
<sourceFolder url="file://$MODULE_DIR$/dsa/assembler_old/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/dsa/assembler_old/src" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/dsa/common/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/dsa/common/src" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/dsa/compiler/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/dsa/compiler/src" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/dsa/emulator/benches" isTestSource="true" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/dsa/emulator/src" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/dsa/linker/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/dsa/linker/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/dsa/assembler/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/dsa/emulator/backend/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/dsa/emulator/frontend/benches" isTestSource="true" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/dsa/emulator/frontend/src" isTestSource="false" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = ["dsa/common", "dsa/assembler", "dsa/emulator", "dsa/linker", "dsa/compiler"]
|
members = ["dsa/common", "dsa/assembler", "dsa/emulator/frontend", "dsa/linker", "dsa/compiler", "dsa/emulator/backend"]
|
||||||
resolver = "3"
|
resolver = "3"
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
|
|||||||
@@ -0,0 +1,158 @@
|
|||||||
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
|
||||||
|
use std::fs::File;
|
||||||
|
use binrw::{binrw, BinRead, binwrite, NullString, BinReaderExt, BinWriterExt, BinWrite};
|
||||||
|
|
||||||
|
use common::prelude::Instruction;
|
||||||
|
use crate::assembler::Symbol;
|
||||||
|
|
||||||
|
pub fn disassemble(in_path: PathBuf, out_path: PathBuf) -> Result<(), String> {
|
||||||
|
|
||||||
|
let Ok(content) = fs::read(in_path) else {
|
||||||
|
return Err(String::new())
|
||||||
|
};
|
||||||
|
|
||||||
|
for (line_num, chunk) in content.chunks(4).enumerate() {
|
||||||
|
let address = line_num * 4;
|
||||||
|
|
||||||
|
let value = u32::from_le_bytes(*chunk.as_array().unwrap());
|
||||||
|
println!("{:#?}", Instruction(value));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[brw(magic = b"DSAX", little)]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct DseHeader {
|
||||||
|
version: u16,
|
||||||
|
flags: u16,
|
||||||
|
entry_point: u32,
|
||||||
|
|
||||||
|
symbol_table_offset: u32,
|
||||||
|
symbol_table_size: u32,
|
||||||
|
|
||||||
|
string_table_offset: u32,
|
||||||
|
string_table_size: u32,
|
||||||
|
|
||||||
|
data_offset: u32,
|
||||||
|
data_size: u32,
|
||||||
|
|
||||||
|
text_offset: u32,
|
||||||
|
text_size: u32
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[brw(little)]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct SymbolEntry {
|
||||||
|
name_offset: u32,
|
||||||
|
value: u32,
|
||||||
|
section: u8,
|
||||||
|
binding: u8,
|
||||||
|
_pad: u16
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct DseExecutable {
|
||||||
|
header: DseHeader,
|
||||||
|
symbols: Vec<SymbolEntry>,
|
||||||
|
string_table: Vec<u8>,
|
||||||
|
data: Vec<u8>,
|
||||||
|
text: Vec<u32>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DseExecutable {
|
||||||
|
fn load(path: PathBuf) -> binrw::BinResult<Self> {
|
||||||
|
let mut file = File::open(path).map_err(|e| binrw::Error::Io(e))?;
|
||||||
|
Self::read(&mut file)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read<R: Read + Seek>(reader: &mut R) -> binrw::BinResult<Self> {
|
||||||
|
let header = DseHeader::read(reader)?;
|
||||||
|
|
||||||
|
reader.seek(SeekFrom::Start(header.symbol_table_offset as u64))?;
|
||||||
|
let symbols: Vec<SymbolEntry> = (0..header.symbol_table_size)
|
||||||
|
.map(|_| SymbolEntry::read(reader))
|
||||||
|
.collect::<binrw::BinResult<_>>()?;
|
||||||
|
|
||||||
|
reader.seek(SeekFrom::Start(header.string_table_offset as u64))?;
|
||||||
|
let mut string_table = vec![0u8; header.string_table_size as usize];
|
||||||
|
reader.read_exact(&mut string_table).map_err(|e| binrw::Error::Io(e))?;
|
||||||
|
|
||||||
|
reader.seek(SeekFrom::Start(header.data_offset as u64))?;
|
||||||
|
let mut data = vec![0u8; header.data_size as usize];
|
||||||
|
reader.read_exact(&mut data).map_err(|e| binrw::Error::Io(e))?;
|
||||||
|
|
||||||
|
reader.seek(SeekFrom::Start(header.text_offset as u64))?;
|
||||||
|
let text: Vec<u32> = (0..header.text_size)
|
||||||
|
.map(|_| reader.read_le::<u32>())
|
||||||
|
.collect::<binrw::BinResult<_>>()?;
|
||||||
|
|
||||||
|
Ok(DseExecutable { header, symbols, string_table, data, text })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save(&self, path: PathBuf) -> binrw::BinResult<()> {
|
||||||
|
let mut file = File::create(path)
|
||||||
|
.map_err(|e| binrw::Error::Io(e))?;
|
||||||
|
self.write(&mut file)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write<W: Write + Seek>(&self, writer: &mut W) -> binrw::BinResult<()> {
|
||||||
|
self.header.write(writer)?;
|
||||||
|
|
||||||
|
for symbol in &self.symbols {
|
||||||
|
symbol.write(writer)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.write_all(&self.string_table)?;
|
||||||
|
writer.write_all(&self.data)?;
|
||||||
|
|
||||||
|
for word in &self.text {
|
||||||
|
writer.write_le(word)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(
|
||||||
|
symbols: &[SymbolEntry],
|
||||||
|
string_table: &[u8],
|
||||||
|
data: &[u8],
|
||||||
|
text: &[u32],
|
||||||
|
entry_point: u32,
|
||||||
|
) -> DseExecutable {
|
||||||
|
const HEADER_SIZE: u32 = 4 + 2 + 2 + 4 + 4*2 + 4*2 + 4*2 + 4*2; // adjust to actual packed size
|
||||||
|
|
||||||
|
let symbol_table_offset = HEADER_SIZE;
|
||||||
|
let symbol_table_size = symbols.len() as u32 * 8; // 4+4+1+1+2 padded = 8 bytes each
|
||||||
|
let string_table_offset = symbol_table_offset + symbol_table_size;
|
||||||
|
let data_offset = string_table_offset + string_table.len() as u32;
|
||||||
|
let text_offset = data_offset + data.len() as u32;
|
||||||
|
|
||||||
|
let header = DseHeader {
|
||||||
|
version: 1,
|
||||||
|
flags: 0,
|
||||||
|
entry_point,
|
||||||
|
symbol_table_offset,
|
||||||
|
symbol_table_size: symbols.len() as u32,
|
||||||
|
string_table_offset,
|
||||||
|
string_table_size: string_table.len() as u32,
|
||||||
|
data_offset,
|
||||||
|
data_size: data.len() as u32,
|
||||||
|
text_offset,
|
||||||
|
text_size: text.len() as u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
DseExecutable {
|
||||||
|
header,
|
||||||
|
symbols: symbols.to_vec(),
|
||||||
|
string_table: string_table.to_vec(),
|
||||||
|
data: data.to_vec(),
|
||||||
|
text: text.to_vec(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ impl std::fmt::Display for Register {
|
|||||||
|
|
||||||
impl Register {
|
impl Register {
|
||||||
/// Total number of registers, not counting Null as it's not a real register.
|
/// Total number of registers, not counting Null as it's not a real register.
|
||||||
/// reading or writing to Null is undefined behaviour.
|
/// reading or writing to Null is undefined behaviour!!
|
||||||
pub const COUNT: u8 = Self::Null as u8;
|
pub const COUNT: u8 = Self::Null as u8;
|
||||||
|
|
||||||
/// # Safety
|
/// # Safety
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "emu_core"
|
||||||
|
edition.workspace = true
|
||||||
|
version.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "emu_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)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
+1
@@ -1,3 +1,4 @@
|
|||||||
|
/* @[crate::config::MemoryMap] */
|
||||||
MemoryMap(
|
MemoryMap(
|
||||||
table_addr: 0x1000,
|
table_addr: 0x1000,
|
||||||
regions: [
|
regions: [
|
||||||
@@ -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");
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
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::{
|
mod default;
|
||||||
Emulator,
|
|
||||||
backend::memory::{PhysAddr, mmu::MMU},
|
|
||||||
// io::DeviceId,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ pub struct Region {
|
|||||||
pub size: u32,
|
pub size: u32,
|
||||||
pub region_type: RegionType,
|
pub region_type: RegionType,
|
||||||
|
|
||||||
// flags for the emulator
|
// flags for the ui
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub identity_map_on_boot: bool,
|
pub identity_map_on_boot: bool,
|
||||||
}
|
}
|
||||||
@@ -38,11 +38,26 @@ pub struct MemoryMap {
|
|||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct IoMapping {
|
pub struct IoMapping {
|
||||||
// pub device: DeviceId,
|
pub device: String,
|
||||||
pub base: u32,
|
pub base: u32,
|
||||||
pub size: 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 {
|
impl MemoryMap {
|
||||||
pub const DEFAULT_MEMORY_MAP_ADDR: PhysAddr = 0x1000;
|
pub const DEFAULT_MEMORY_MAP_ADDR: PhysAddr = 0x1000;
|
||||||
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#![feature(likely_unlikely)]
|
||||||
|
|
||||||
|
pub mod processor;
|
||||||
|
pub mod memory;
|
||||||
|
pub mod config;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::backend::memory::{FaultInfo, PhysAddr, VirtAddr, tlb::TLB};
|
use crate::memory::{FaultInfo, PhysAddr, VirtAddr, tlb::TLB};
|
||||||
|
|
||||||
pub struct MMU {
|
pub struct MMU {
|
||||||
buffer: TLB,
|
buffer: TLB,
|
||||||
@@ -28,7 +28,7 @@ pub use bulkalloc::BulkAllocStore;
|
|||||||
// #[cfg(feature = "mainstore-prealloc")]
|
// #[cfg(feature = "mainstore-prealloc")]
|
||||||
// pub use prealloc::PreAllocStore;
|
// pub use prealloc::PreAllocStore;
|
||||||
|
|
||||||
use crate::backend::memory::PhysAddr;
|
use crate::memory::PhysAddr;
|
||||||
|
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::backend::memory::{PhysAddr, VirtAddr};
|
use crate::memory::{PhysAddr, VirtAddr};
|
||||||
|
|
||||||
const TLB_SIZE: usize = 256;
|
const TLB_SIZE: usize = 256;
|
||||||
const TLB_MASK: u32 = (TLB_SIZE - 1) as u32;
|
const TLB_MASK: u32 = (TLB_SIZE - 1) as u32;
|
||||||
+27
-12
@@ -10,13 +10,22 @@ use std::{
|
|||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use common::{
|
||||||
MemoryBank, Page, SharedState,
|
isa::instructions::{Instruction, Opcode},
|
||||||
backend::{memory::mmu::MMU, processor::interrupts::Interrupt},
|
isa::register::Register
|
||||||
config::{MemoryMap, RegionType},
|
|
||||||
};
|
};
|
||||||
use common::isa::instructions::{Instruction, Opcode};
|
use crate::{
|
||||||
use common::isa::register::Register;
|
memory::{
|
||||||
|
mmu::MMU,
|
||||||
|
ram::MemoryBank
|
||||||
|
},
|
||||||
|
processor::{
|
||||||
|
interrupts::Interrupt,
|
||||||
|
state::SharedState
|
||||||
|
}
|
||||||
|
};
|
||||||
|
use crate::config::{MemoryMap, RegionType};
|
||||||
|
use crate::memory::ram::Page;
|
||||||
|
|
||||||
pub struct Emulator {
|
pub struct Emulator {
|
||||||
internal_state: ProcessorSnapshot,
|
internal_state: ProcessorSnapshot,
|
||||||
@@ -197,13 +206,13 @@ impl Emulator {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// UI is resetting the emulator
|
// UI is resetting the ui
|
||||||
self.check_update();
|
self.check_update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&mut self) -> Result<(), String> {
|
pub fn run(&mut self) -> Result<(), String> {
|
||||||
// wait for UI to initialise emulator.
|
// wait for UI to initialise ui.
|
||||||
if self.shared_state.paused.load(Ordering::Relaxed) {
|
if self.shared_state.paused.load(Ordering::Relaxed) {
|
||||||
self.wait_for_instructions();
|
self.wait_for_instructions();
|
||||||
}
|
}
|
||||||
@@ -324,9 +333,15 @@ impl Emulator {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
Some(Opcode::Ldh) => {
|
Some(Opcode::Ldh) => {
|
||||||
*self.mut_reg(word.dest_uc()) = u32::from(
|
let addr = self.reg(word.src1_uc()) + word.imm16() as u32;
|
||||||
self.mem_read_word(self.reg(word.src1_uc()) + word.imm16() as u32) >> 16,
|
|
||||||
)
|
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) => {
|
Some(Opcode::Ldw) => {
|
||||||
*self.mut_reg(word.dest_uc()) = u32::from(
|
*self.mut_reg(word.dest_uc()) = u32::from(
|
||||||
@@ -515,7 +530,7 @@ impl Emulator {
|
|||||||
#[inline]
|
#[inline]
|
||||||
fn mem_write_byte(&mut self, addr: u32, val: u8) {
|
fn mem_write_byte(&mut self, addr: u32, val: u8) {
|
||||||
if addr == 0x40000 || addr == 0x40001 || addr == 0x40002 || addr == 0x40003 {
|
if addr == 0x40000 || addr == 0x40001 || addr == 0x40002 || addr == 0x40003 {
|
||||||
eprintln!("emulator wrote 0x{:08x} to uart + {}", val, addr - 0x40000);
|
eprintln!("ui wrote 0x{:08x} to uart + {}", val, addr - 0x40000);
|
||||||
|
|
||||||
if addr == 0x40001 || addr == 0x40003 {
|
if addr == 0x40001 || addr == 0x40003 {
|
||||||
eprintln!("writing char {} to uart + {}", val as char, addr - 0x40000);
|
eprintln!("writing char {} to uart + {}", val as char, addr - 0x40000);
|
||||||
+1
-1
@@ -2,7 +2,7 @@ use std::sync::{Arc, atomic::AtomicBool, mpsc};
|
|||||||
|
|
||||||
use arc_swap::ArcSwap;
|
use arc_swap::ArcSwap;
|
||||||
|
|
||||||
use crate::backend::processor::processor::ProcessorSnapshot;
|
use crate::processor::processor::ProcessorSnapshot;
|
||||||
|
|
||||||
use super::interrupts::Interrupt;
|
use super::interrupts::Interrupt;
|
||||||
|
|
||||||
@@ -22,15 +22,16 @@ fxhash = "0.2.1"
|
|||||||
ron = "0.12.0"
|
ron = "0.12.0"
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
|
|
||||||
common = { path = "../common" }
|
common = { path = "../../common" }
|
||||||
egui = "0.33.3"
|
egui = "0.33.3"
|
||||||
eframe = "0.33.3"
|
eframe = "0.33.3"
|
||||||
egui_tiles = "0.14.1"
|
egui_tiles = "0.14.1"
|
||||||
egui_code_editor = "0.2.21"
|
egui_code_editor = "0.2.21"
|
||||||
egui_file = "0.25.0"
|
egui_file = "0.25.0"
|
||||||
dirs = "6.0.0"
|
dirs = "6.0.0"
|
||||||
assembler = { path = "../assembler" }
|
assembler = { path = "../../assembler" }
|
||||||
egui_extras = "0.33.3"
|
egui_extras = "0.33.3"
|
||||||
|
emu_core = { version = "0.3.0", path = "../backend" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["mainstore-prealloc"]
|
default = ["mainstore-prealloc"]
|
||||||
@@ -2,7 +2,7 @@ use std::{fs, path::PathBuf};
|
|||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
use crate::config::{IoMapping, MemoryMap};
|
use emu_core::config::{IoMap, IoMapping, MemoryMap};
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(version, about, long_about = None)]
|
#[command(version, about, long_about = None)]
|
||||||
@@ -36,7 +36,7 @@ impl DsaArgs {
|
|||||||
.and_then(|map| ron::from_str(&map).ok())
|
.and_then(|map| ron::from_str(&map).ok())
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
ron::from_str(include_str!("./config/default/dsa.mmap.ron")).unwrap()
|
MemoryMap::default()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ impl DsaArgs {
|
|||||||
.and_then(|path| Some(fs::read(path).unwrap()))
|
.and_then(|path| Some(fs::read(path).unwrap()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_io_map(&self) -> Vec<IoMapping> {
|
pub fn get_io_map(&self) -> IoMap {
|
||||||
self.io_map
|
self.io_map
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|m| {
|
.and_then(|m| {
|
||||||
@@ -55,7 +55,7 @@ impl DsaArgs {
|
|||||||
.and_then(|map| ron::from_str(&map).ok())
|
.and_then(|map| ron::from_str(&map).ok())
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
ron::from_str(include_str!("./config/default/dsa.iomap.ron")).unwrap()
|
IoMap::default()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#![feature(likely_unlikely)]
|
||||||
|
#![feature(inherent_associated_types)]
|
||||||
|
#![feature(str_as_str)]
|
||||||
|
|
||||||
|
pub mod ui;
|
||||||
|
pub mod args;
|
||||||
|
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
use common::prelude::Instruction;
|
use common::prelude::Instruction;
|
||||||
use dsa::{Emulator, Page, args::DsaArgs, frontend::run_app};
|
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
use dsa::args::DsaArgs;
|
||||||
|
use dsa::ui::run_app;
|
||||||
|
use emu_core::memory::ram::Page;
|
||||||
|
use emu_core::processor::processor::Emulator;
|
||||||
|
|
||||||
const STACK_SIZE: usize = 1024 * 1024 * 16;
|
const STACK_SIZE: usize = 1024 * 1024 * 16;
|
||||||
|
|
||||||
+3
-3
@@ -1,9 +1,9 @@
|
|||||||
use std::sync::{Arc, atomic::Ordering};
|
use std::sync::{Arc, atomic::Ordering};
|
||||||
|
|
||||||
use super::Component;
|
use super::Component;
|
||||||
use crate::{MemoryBank, SharedState};
|
|
||||||
|
|
||||||
use common::prelude::Register;
|
use common::prelude::Register;
|
||||||
|
use emu_core::memory::ram::MemoryBank;
|
||||||
|
use emu_core::processor::state::SharedState;
|
||||||
|
|
||||||
pub struct Controller {
|
pub struct Controller {
|
||||||
state: Arc<SharedState>,
|
state: Arc<SharedState>,
|
||||||
@@ -57,7 +57,7 @@ impl Component for Controller {
|
|||||||
// });
|
// });
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// Resets the emulator and all attached devices
|
// Resets the ui and all attached devices
|
||||||
if ui.button("Reset All").clicked() {
|
if ui.button("Reset All").clicked() {
|
||||||
self.state.reseting.store(true, Ordering::Relaxed);
|
self.state.reseting.store(true, Ordering::Relaxed);
|
||||||
self.mem.clear();
|
self.mem.clear();
|
||||||
+2
-3
@@ -1,7 +1,6 @@
|
|||||||
use egui::{Color32, FontId, Vec2};
|
use egui::{Color32, FontId, Vec2};
|
||||||
|
use emu_core::memory::PhysAddr;
|
||||||
use crate::{MemoryBank, Page, backend::memory::PhysAddr};
|
use emu_core::memory::ram::{MemoryBank, Page};
|
||||||
|
|
||||||
use super::Component;
|
use super::Component;
|
||||||
|
|
||||||
pub struct Display {
|
pub struct Display {
|
||||||
+4
-5
@@ -15,9 +15,8 @@ use egui::{Align, Context, Key, Layout, Ui};
|
|||||||
use egui_code_editor::{CodeEditor, ColorTheme, Completer, Syntax};
|
use egui_code_editor::{CodeEditor, ColorTheme, Completer, Syntax};
|
||||||
use egui_extras::{Column, TableBuilder};
|
use egui_extras::{Column, TableBuilder};
|
||||||
use egui_file::FileDialog;
|
use egui_file::FileDialog;
|
||||||
|
use emu_core::memory::ram::{MemoryBank, Page};
|
||||||
use crate::{MemoryBank, Page, SharedState};
|
use emu_core::processor::state::SharedState;
|
||||||
|
|
||||||
use super::Component;
|
use super::Component;
|
||||||
|
|
||||||
pub struct Editor {
|
pub struct Editor {
|
||||||
@@ -86,7 +85,7 @@ impl Component for Editor {
|
|||||||
Layout::left_to_right(Align::Min),
|
Layout::left_to_right(Align::Min),
|
||||||
|ui| {
|
|ui| {
|
||||||
if self.show_output {
|
if self.show_output {
|
||||||
egui::SidePanel::right("Editor Output").show_inside(ui, |ui| {
|
egui::SidePanel::right("Editor Output").resizable(false).show_inside(ui, |ui| {
|
||||||
self.output.ui(ui, ctx);
|
self.output.ui(ui, ctx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -327,7 +326,7 @@ impl Editor {
|
|||||||
|
|
||||||
fn render_editor(&mut self, ui: &mut Ui, _ctx: &Context) {
|
fn render_editor(&mut self, ui: &mut Ui, _ctx: &Context) {
|
||||||
let available_width = ui.available_width();
|
let available_width = ui.available_width();
|
||||||
let mut editor = self.editor.clone().desired_width(available_width - 500.0);
|
let mut editor = self.editor.clone().desired_width(available_width);
|
||||||
editor.show_with_completer(ui, &mut self.text, &mut self.completer);
|
editor.show_with_completer(ui, &mut self.text, &mut self.completer);
|
||||||
}
|
}
|
||||||
|
|
||||||
+2
-3
@@ -1,7 +1,6 @@
|
|||||||
use egui::Vec2;
|
use egui::Vec2;
|
||||||
|
use emu_core::memory::PhysAddr;
|
||||||
use crate::{MemoryBank, Page, backend::memory::PhysAddr};
|
use emu_core::memory::ram::{MemoryBank, Page};
|
||||||
|
|
||||||
use super::Component;
|
use super::Component;
|
||||||
|
|
||||||
pub struct FrameBuffer {
|
pub struct FrameBuffer {
|
||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
use std::num::ParseIntError;
|
use std::num::ParseIntError;
|
||||||
|
|
||||||
use common::prelude::Instruction;
|
use common::prelude::Instruction;
|
||||||
|
use emu_core::memory::ram::MemoryBank;
|
||||||
use crate::{MemoryBank, frontend::components::Component};
|
use super::Component;
|
||||||
|
|
||||||
pub struct MemoryInspector {
|
pub struct MemoryInspector {
|
||||||
memory: MemoryBank,
|
memory: MemoryBank,
|
||||||
+1
-2
@@ -1,8 +1,7 @@
|
|||||||
use std::{f32, sync::Arc};
|
use std::{f32, sync::Arc};
|
||||||
|
|
||||||
use common::prelude::Register;
|
use common::prelude::Register;
|
||||||
|
use emu_core::processor::state::SharedState;
|
||||||
use crate::SharedState;
|
|
||||||
|
|
||||||
use super::Component;
|
use super::Component;
|
||||||
|
|
||||||
+8
-9
@@ -6,17 +6,16 @@ use std::{
|
|||||||
mpsc::{Receiver, Sender},
|
mpsc::{Receiver, Sender},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use emu_core::memory::PhysAddr;
|
||||||
|
use emu_core::memory::ram::MemoryBank;
|
||||||
|
use emu_core::processor::interrupts::Interrupt;
|
||||||
|
use emu_core::processor::state::SharedState;
|
||||||
use super::Component;
|
use super::Component;
|
||||||
use crate::{
|
|
||||||
MemoryBank, SharedState,
|
|
||||||
backend::{memory::PhysAddr, processor::interrupts::Interrupt},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// ## SERIAL LAYOUT: (from the perspective of code inside the emulator)
|
/// ## 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
|
/// - [base+0x00]: ser_out valid flag - set by kernel after writing a byte to serial
|
||||||
/// - [base+0x01]: ser_out byte - data payload for emulator to read
|
/// - [base+0x01]: ser_out byte - data payload for ui to read
|
||||||
/// - [base+0x02]: ser_in valid flag - set by emulator after writing a byte to serial
|
/// - [base+0x02]: ser_in valid flag - set by ui after writing a byte to serial
|
||||||
/// - [base+0x03]: ser_in byte - data payload for kernel to read
|
/// - [base+0x03]: ser_in byte - data payload for kernel to read
|
||||||
pub struct Serial {
|
pub struct Serial {
|
||||||
pub read_rx: Receiver<u8>,
|
pub read_rx: Receiver<u8>,
|
||||||
@@ -43,7 +42,7 @@ impl Serial {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let ser_out_valid = mem.read_byte(uart_base + 0);
|
let ser_out_valid = mem.read_byte(uart_base + 0);
|
||||||
// Kernel => emulator: valid flag in byte 0, data in byte 1
|
// Kernel => ui: valid flag in byte 0, data in byte 1
|
||||||
if ser_out_valid == 0x01 {
|
if ser_out_valid == 0x01 {
|
||||||
let ser_out = mem.read_byte(uart_base + 1);
|
let ser_out = mem.read_byte(uart_base + 1);
|
||||||
let _ = tx.send(ser_out);
|
let _ = tx.send(ser_out);
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
use eframe::NativeOptions;
|
use eframe::NativeOptions;
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
use std::mem;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
mod components;
|
mod components;
|
||||||
use components::{
|
use components::{
|
||||||
Component, controller::Controller, display::Display, editor::Editor, framebuffer::FrameBuffer,
|
controller::Controller, display::Display, editor::Editor, framebuffer::FrameBuffer, memory::MemoryInspector,
|
||||||
memory::MemoryInspector, registers::Registers, serial::Serial,
|
registers::Registers, serial::Serial, Component,
|
||||||
};
|
};
|
||||||
|
use emu_core::config::VERSION;
|
||||||
use crate::{MemoryBank, SharedState, config::VERSION};
|
use emu_core::memory::ram::MemoryBank;
|
||||||
|
use emu_core::processor::state::SharedState;
|
||||||
|
|
||||||
pub fn run_app(state: Arc<SharedState>, mem_handle: MemoryBank) -> eframe::Result<()> {
|
pub fn run_app(state: Arc<SharedState>, mem_handle: MemoryBank) -> eframe::Result<()> {
|
||||||
eframe::run_native(
|
eframe::run_native(
|
||||||
@@ -22,7 +22,7 @@ pub fn run_app(state: Arc<SharedState>, mem_handle: MemoryBank) -> eframe::Resul
|
|||||||
|
|
||||||
impl eframe::App for DsaUi {
|
impl eframe::App for DsaUi {
|
||||||
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||||
// request an update from emulator every cycle
|
// request an update from ui every cycle
|
||||||
self.state.update_req.store(true, Ordering::Relaxed);
|
self.state.update_req.store(true, Ordering::Relaxed);
|
||||||
|
|
||||||
// title panel with dsa title
|
// title panel with dsa title
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
pub mod memory;
|
|
||||||
pub mod processor;
|
|
||||||
@@ -1,332 +0,0 @@
|
|||||||
// use common::isa::instructions::Instruction;
|
|
||||||
// use dsa::{Emulator, MemoryBank, Page};
|
|
||||||
// /// DSA Emulator Benchmark
|
|
||||||
// ///
|
|
||||||
// /// Place this in `dsa/src/bin/bench.rs` and run with:
|
|
||||||
// /// cargo run --bin bench --release
|
|
||||||
// ///
|
|
||||||
// /// Three workloads are tested:
|
|
||||||
// /// 1. ALU-heavy — tight arithmetic loop, no memory, no branches
|
|
||||||
// /// 2. Memory-heavy — repeated load/store to a working set of addresses
|
|
||||||
// /// 3. Branch-heavy — the bf.dsa dispatch pattern (ieq + jnz chains)
|
|
||||||
// ///
|
|
||||||
// /// Each workload is a hand-assembled flat binary loaded directly into
|
|
||||||
// /// the emulator, bypassing all the args/display/IO plumbing.
|
|
||||||
// use std::{
|
|
||||||
// thread,
|
|
||||||
// time::{Duration, Instant},
|
|
||||||
// u16,
|
|
||||||
// };
|
|
||||||
|
|
||||||
// // ---------------------------------------------------------------------------
|
|
||||||
// // Minimal no-op memory map + IO so the emulator boots without a config file.
|
|
||||||
// // ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// fn make_emulator() -> Emulator {
|
|
||||||
// use dsa::{
|
|
||||||
// config::{MemoryMap, Region, RegionType},
|
|
||||||
// io::display::Display,
|
|
||||||
// };
|
|
||||||
|
|
||||||
// let mmap = MemoryMap {
|
|
||||||
// table_addr: 0x40000,
|
|
||||||
// regions: vec![
|
|
||||||
// Region {
|
|
||||||
// base: 0x0000_0000,
|
|
||||||
// size: 0x0010_0000, // 1 MiB RAM
|
|
||||||
// region_type: RegionType::Usable,
|
|
||||||
// identity_map_on_boot: true,
|
|
||||||
// },
|
|
||||||
// Region {
|
|
||||||
// base: 0x0020_0000,
|
|
||||||
// size: 0x0002_0000, // 128 KiB MMIO (display)
|
|
||||||
// region_type: RegionType::MMIO,
|
|
||||||
// identity_map_on_boot: false,
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
// };
|
|
||||||
|
|
||||||
// // let iomap = vec![dsa::config::IoMapping {
|
|
||||||
// // device: dsa::io::DeviceId::Display,
|
|
||||||
// // base: 0x0020_0000,
|
|
||||||
// // size: 0x0000_07D0,
|
|
||||||
// // }];
|
|
||||||
|
|
||||||
// // let (display, _handle) = Display::new(80, 25);
|
|
||||||
// // Emulator::new(MemoryBank::new())
|
|
||||||
// // .with_device(display)
|
|
||||||
// // .apply_memory_map(mmap)
|
|
||||||
// // .apply_io_map(iomap)
|
|
||||||
// // .unwrap()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // ---------------------------------------------------------------------------
|
|
||||||
// // Instruction encoding helpers (little-endian words)
|
|
||||||
// // ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// /// Encode an I-type instruction: [op:6][src:5][dst:5][imm:16]
|
|
||||||
// fn enc_i(op: u8, src: u8, dst: u8, imm: u16) -> u32 {
|
|
||||||
// ((op as u32 & 0x3F) << 26)
|
|
||||||
// | ((src as u32 & 0x1F) << 21)
|
|
||||||
// | ((dst as u32 & 0x1F) << 16)
|
|
||||||
// | imm as u32
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// Encode an R-type instruction: [op:6][r1:5][r2:5][r3:5][r4:5][u6:6]
|
|
||||||
// fn enc_r(op: u8, r1: u8, r2: u8, r3: u8, r4: u8, u6: u8) -> u32 {
|
|
||||||
// ((op as u32 & 0x3F) << 26)
|
|
||||||
// | ((r1 as u32 & 0x1F) << 21)
|
|
||||||
// | ((r2 as u32 & 0x1F) << 16)
|
|
||||||
// | ((r3 as u32 & 0x1F) << 11)
|
|
||||||
// | ((r4 as u32 & 0x1F) << 6)
|
|
||||||
// | (u6 as u32 & 0x3F)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Register indices — must match your Register enum discriminants
|
|
||||||
// const RG0: u8 = 0;
|
|
||||||
// const RG1: u8 = 1;
|
|
||||||
// const RG2: u8 = 2;
|
|
||||||
// const RG3: u8 = 3;
|
|
||||||
// const RG4: u8 = 4;
|
|
||||||
// const RG5: u8 = 5;
|
|
||||||
// const ZERO: u8 = 16;
|
|
||||||
|
|
||||||
// // Opcodes — must match your Opcode enum discriminants.
|
|
||||||
// // Fill these in from your ISA.md / encode.rs.
|
|
||||||
// const OP_NOP: u8 = 0x0D; // adjust to match your actual opcode values
|
|
||||||
// const OP_HLT: u8 = 0x2B;
|
|
||||||
// const OP_ADD: u8 = 0x22;
|
|
||||||
// const OP_ADDI: u8 = 0x23;
|
|
||||||
// const OP_SUBI: u8 = 0x24;
|
|
||||||
// const OP_LDW: u8 = 0x07;
|
|
||||||
// const OP_STW: u8 = 0x0A;
|
|
||||||
// const OP_LLI: u8 = 0x0B;
|
|
||||||
// const OP_LUI: u8 = 0x0C;
|
|
||||||
// const OP_IEQ: u8 = 0x0E;
|
|
||||||
// const OP_JNZ: u8 = 0x15;
|
|
||||||
// const OP_JMP: u8 = 0x12;
|
|
||||||
|
|
||||||
// // ---------------------------------------------------------------------------
|
|
||||||
// // Workload 1: ALU-heavy
|
|
||||||
// //
|
|
||||||
// // Counts down from 10_000_000 using subi, jumps back to top.
|
|
||||||
// // Pure register arithmetic, no memory accesses after init.
|
|
||||||
// // Instruction mix: lli, subi, ieq, jnz, hlt
|
|
||||||
// //
|
|
||||||
// // Layout (each word at address = index * 4):
|
|
||||||
// // 0: lli rg0, 0 (upper half of count) [lui follows]
|
|
||||||
// // 4: lui rg0, <high>
|
|
||||||
// // 8: loop: subi rg0, 1, rg0
|
|
||||||
// // 12: ieq rg0, zero, rg1
|
|
||||||
// // 16: jnz rg1, zero, 8 [jump to loop]
|
|
||||||
// // 20: hlt
|
|
||||||
// // ---------------------------------------------------------------------------
|
|
||||||
// fn alu_workload() -> Vec<u8> {
|
|
||||||
// let count: u32 = 10_000_000;
|
|
||||||
// let lo = (count & 0xFFFF) as u16;
|
|
||||||
// let hi = ((count >> 16) & 0xFFFF) as u16;
|
|
||||||
|
|
||||||
// let words: &[u32] = &[
|
|
||||||
// enc_i(OP_LLI, 0, RG0, lo), // 0x00: lli rg0, lo(count)
|
|
||||||
// enc_i(OP_LUI, 0, RG0, hi), // 0x04: lui rg0, hi(count)
|
|
||||||
// // loop @ 0x08:
|
|
||||||
// enc_i(OP_SUBI, RG0, RG0, 1), // 0x08: subi rg0, rg0, 1
|
|
||||||
// enc_r(OP_IEQ, RG0, ZERO, RG1, 0, 0), // 0x0C: ieq rg0, zero, rg1
|
|
||||||
// enc_i(OP_JNZ, RG1, ZERO, 0x0008), // 0x10: jnz rg1, zero, 0x08 <- BUG: jnz jumps when rg1!=0
|
|
||||||
// // rg1=1 when rg0==0, so we want jez here to keep looping
|
|
||||||
// // fix: use jez (jump when rg1==0, i.e. rg0!=0)
|
|
||||||
// enc_i(OP_HLT, 0, 0, 0), // 0x14: hlt
|
|
||||||
// ];
|
|
||||||
// // Note: replace OP_JNZ above with your JEZ opcode so the loop continues
|
|
||||||
// // while rg0 != 0. JNZ exits when rg1 != 0, i.e. when rg0 == 0 (done).
|
|
||||||
// // The ieq sets rg1=1 only when rg0==0, so jnz rg1 exits the loop correctly.
|
|
||||||
// // Actually this IS correct: loop while ieq returns 0 (rg0 != 0),
|
|
||||||
// // exit when ieq returns 1 (rg0 == 0). jnz rg1 = jump when rg1 != 0 = jump when done.
|
|
||||||
// // We want to jump BACK while not done, so we need jez:
|
|
||||||
// // jez rg1, zero, 0x08 = jump back when rg1==0 (rg0 != 0, not done yet)
|
|
||||||
// // Swap OP_JNZ for your JEZ opcode.
|
|
||||||
// words_to_bytes(words)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // ---------------------------------------------------------------------------
|
|
||||||
// // Workload 2: Memory-heavy
|
|
||||||
// //
|
|
||||||
// // Repeatedly writes and reads a 64-word working set.
|
|
||||||
// // Tests page table lookup performance under repeated memory access.
|
|
||||||
// //
|
|
||||||
// // rg0 = base address (0x1000, well within RAM, page-aligned)
|
|
||||||
// // rg1 = loop counter (1000 outer iterations)
|
|
||||||
// // rg2 = inner counter (64 words per pass)
|
|
||||||
// // rg3 = scratch for store value
|
|
||||||
// // ---------------------------------------------------------------------------
|
|
||||||
// fn memory_workload() -> Vec<u8> {
|
|
||||||
// let base: u32 = 0x1000;
|
|
||||||
// let outer: u16 = 1000;
|
|
||||||
// let inner: u16 = 64;
|
|
||||||
|
|
||||||
// let words: &[u32] = &[
|
|
||||||
// // init
|
|
||||||
// enc_i(OP_LLI, 0, RG0, (base & 0xFFFF) as u16), // rg0 = base
|
|
||||||
// enc_i(OP_LLI, 0, RG1, outer), // rg1 = outer count
|
|
||||||
// // outer_loop @ 0x08:
|
|
||||||
// enc_i(OP_LLI, 0, RG2, inner), // rg2 = inner count
|
|
||||||
// enc_i(OP_LLI, 0, RG3, 0xABCD), // rg3 = value to write
|
|
||||||
// // inner_loop @ 0x10:
|
|
||||||
// enc_i(OP_STW, RG3, RG0, 0), // stw rg3, rg0, 0
|
|
||||||
// enc_i(OP_LDW, RG0, RG3, 0), // ldw rg0, rg3, 0 (read back)
|
|
||||||
// enc_i(OP_ADDI, RG0, RG0, 4), // rg0 += 4
|
|
||||||
// enc_i(OP_SUBI, RG2, RG2, 1), // rg2 -= 1
|
|
||||||
// enc_r(OP_IEQ, RG2, ZERO, RG4, 0, 0), // ieq rg2, zero, rg4
|
|
||||||
// enc_i(OP_JNZ, RG4, ZERO, 0x0010), // jez rg4 -> inner_loop (swap opcode)
|
|
||||||
// // restore base, dec outer
|
|
||||||
// enc_i(OP_LLI, 0, RG0, (base & 0xFFFF) as u16),
|
|
||||||
// enc_i(OP_SUBI, RG1, RG1, 1),
|
|
||||||
// enc_r(OP_IEQ, RG1, ZERO, RG4, 0, 0),
|
|
||||||
// enc_i(OP_JNZ, RG4, ZERO, 0x0008), // jez rg4 -> outer_loop (swap opcode)
|
|
||||||
// enc_i(OP_HLT, 0, 0, 0),
|
|
||||||
// ];
|
|
||||||
// words_to_bytes(words)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // ---------------------------------------------------------------------------
|
|
||||||
// // Workload 3: Branch-heavy
|
|
||||||
// //
|
|
||||||
// // Mimics the bf.dsa dispatch pattern: compare a value against 8 constants,
|
|
||||||
// // branch on each. Worst case for branch predictor.
|
|
||||||
// // rg0 = current "instruction" (cycles through 8 values)
|
|
||||||
// // rg8-rgf = the 8 bf opcode constants
|
|
||||||
// // ---------------------------------------------------------------------------
|
|
||||||
// fn branch_workload() -> Vec<u8> {
|
|
||||||
// // 8 bf opcodes: + - > < . , [ ]
|
|
||||||
// let opcodes: [u16; 8] = [43, 45, 62, 60, 46, 44, 91, 93];
|
|
||||||
// // We'll cycle rg0 through these values and do the full dispatch chain.
|
|
||||||
// // To keep it self-contained: load opcodes, set outer loop counter,
|
|
||||||
// // inner loop cycles rg0 through all 8 values doing ieq+jnz for each.
|
|
||||||
// let outer: u16 = u16::MAX;
|
|
||||||
|
|
||||||
// let mut w: Vec<u32> = Vec::new();
|
|
||||||
|
|
||||||
// // Load 8 opcode constants into rg8-rgf (regs 8-15)
|
|
||||||
// for (i, &op) in opcodes.iter().enumerate() {
|
|
||||||
// w.push(enc_i(OP_LLI, 0, 8 + i as u8, op));
|
|
||||||
// }
|
|
||||||
// // rg1 = outer counter
|
|
||||||
// w.push(enc_i(OP_LLI, 0, RG1, outer));
|
|
||||||
|
|
||||||
// // outer_loop:
|
|
||||||
// let outer_loop_addr = (w.len() * 4) as u16;
|
|
||||||
|
|
||||||
// // rg5 = inner counter (8 opcodes)
|
|
||||||
// w.push(enc_i(OP_LLI, 0, RG5, 8));
|
|
||||||
// // rg2 = pointer into opcode table (we'll use addi to step rg0 through values)
|
|
||||||
// // For simplicity: just set rg0 to each opcode in sequence via lli each iteration
|
|
||||||
// // This is branch-heavy, not arithmetic-heavy, so the lli overhead is fine.
|
|
||||||
// for (i, &op) in opcodes.iter().enumerate() {
|
|
||||||
// let next_offset = ((w.len() + 4) * 4) as u16; // addr after the jnz
|
|
||||||
// w.push(enc_i(OP_LLI, 0, RG0, op)); // rg0 = opcode
|
|
||||||
// w.push(enc_r(OP_IEQ, RG0, 8 + i as u8, RG4, 0, 0)); // ieq rg0, rgX, rg4
|
|
||||||
// w.push(enc_i(OP_JNZ, RG4, ZERO, next_offset)); // jnz rg4 (jez to skip)
|
|
||||||
// w.push(enc_i(OP_NOP, 0, 0, 0)); // "handler" nop
|
|
||||||
// }
|
|
||||||
// // dec outer, loop
|
|
||||||
// let after_dispatch = (w.len() * 4) as u16;
|
|
||||||
// w.push(enc_i(OP_SUBI, RG1, RG1, 1));
|
|
||||||
// w.push(enc_r(OP_IEQ, RG1, ZERO, RG4, 0, 0));
|
|
||||||
// w.push(enc_i(OP_JNZ, RG4, ZERO, outer_loop_addr)); // jez -> outer_loop
|
|
||||||
// w.push(enc_i(OP_HLT, 0, 0, 0));
|
|
||||||
|
|
||||||
// words_to_bytes(&w)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn words_to_bytes(words: &[u32]) -> Vec<u8> {
|
|
||||||
// words.iter().flat_map(|w| w.to_le_bytes()).collect()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // ---------------------------------------------------------------------------
|
|
||||||
// // Runner
|
|
||||||
// // ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// struct BenchResult {
|
|
||||||
// name: &'static str,
|
|
||||||
// instructions: u64,
|
|
||||||
// elapsed: Duration,
|
|
||||||
// mips: f64,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn run_bench(name: &'static str, binary: Vec<u8>) -> BenchResult {
|
|
||||||
// let mut emu = make_emulator();
|
|
||||||
// let handle = emu.state_handle();
|
|
||||||
|
|
||||||
// let pages: Vec<Page> = Page::paginate(&binary).collect();
|
|
||||||
// for (i, page) in pages.iter().enumerate() {
|
|
||||||
// emu.memory_mut().write_page(i as u32 * 4096, page);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// let start = Instant::now();
|
|
||||||
// // run() blocks until HLT
|
|
||||||
// emu.run().expect("emulator run failed");
|
|
||||||
// let elapsed = start.elapsed();
|
|
||||||
|
|
||||||
// // read clock from emulator — we need to expose it.
|
|
||||||
// // For now, estimate from elapsed + target MIPS.
|
|
||||||
// // TODO: expose emulator.clock() -> u64 and use that directly.
|
|
||||||
// let instructions = handle.proc.load().clock as u64;
|
|
||||||
// let mips = instructions as f64 / elapsed.as_micros() as f64;
|
|
||||||
|
|
||||||
// BenchResult {
|
|
||||||
// name,
|
|
||||||
// instructions,
|
|
||||||
// elapsed,
|
|
||||||
// mips,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn main() {
|
|
||||||
// println!("DSA Emulator Benchmark");
|
|
||||||
// println!("======================");
|
|
||||||
// println!();
|
|
||||||
|
|
||||||
// // NOTE: you need to expose `pub fn clock(&self) -> usize` on Emulator
|
|
||||||
// // and ensure `run()` returns normally on HLT without blocking in idle_wait.
|
|
||||||
// // The idle_wait loop currently spins forever after HLT — for benchmarking,
|
|
||||||
// // you want run() to return when the program halts. Add a flag or check:
|
|
||||||
// // if !self.internal_state.running { break 'emu; }
|
|
||||||
// // after the HLT handler sets running=false.
|
|
||||||
|
|
||||||
// let workloads: &[(&'static str, fn() -> Vec<u8>)] = &[
|
|
||||||
// ("ALU-heavy (10M countdown)", alu_workload),
|
|
||||||
// ("Memory-heavy (64K rw ops)", memory_workload),
|
|
||||||
// ("Branch-heavy (bf dispatch)", branch_workload),
|
|
||||||
// ];
|
|
||||||
|
|
||||||
// let mut results = Vec::new();
|
|
||||||
// for &(name, build) in workloads {
|
|
||||||
// print!("Running {}... ", name);
|
|
||||||
// let binary = build();
|
|
||||||
// let result = run_bench(name, binary);
|
|
||||||
// println!("done ({:.1}ms)", result.elapsed.as_secs_f64() * 1000.0);
|
|
||||||
// results.push(result);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// println!();
|
|
||||||
// println!(
|
|
||||||
// "{:<35} {:>12} {:>12} {:>10}",
|
|
||||||
// "Workload", "Instructions", "Time", "MIPS"
|
|
||||||
// );
|
|
||||||
// println!("{}", "-".repeat(72));
|
|
||||||
// for r in &results {
|
|
||||||
// println!(
|
|
||||||
// "{:<35} {:>12} {:>10.1}ms {:>10.1}",
|
|
||||||
// r.name,
|
|
||||||
// r.instructions,
|
|
||||||
// r.elapsed.as_secs_f64() * 1000.0,
|
|
||||||
// r.mips,
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// let avg_mips: f64 = results.iter().map(|r| r.mips).sum::<f64>() / results.len() as f64;
|
|
||||||
// println!("{}", "-".repeat(72));
|
|
||||||
// println!("{:<35} {:>35.1} MIPS avg", "Overall", avg_mips);
|
|
||||||
// }
|
|
||||||
|
|
||||||
fn main() {}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
[
|
|
||||||
IoMapping(
|
|
||||||
device: Display,
|
|
||||||
base: 0x0002_0000,
|
|
||||||
size: 0x0000_07D0, // 2000 bytes (80x25)
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
#![feature(likely_unlikely)]
|
|
||||||
#![feature(inherent_associated_types)]
|
|
||||||
#![feature(str_as_str)]
|
|
||||||
|
|
||||||
pub mod args;
|
|
||||||
pub mod backend;
|
|
||||||
pub mod config;
|
|
||||||
pub mod frontend;
|
|
||||||
|
|
||||||
pub use backend::memory::ram::*;
|
|
||||||
pub use backend::{processor::processor::Emulator, processor::state::SharedState};
|
|
||||||
Reference in New Issue
Block a user