extracted emulator backend into independent crate

This commit is contained in:
2026-07-14 22:13:11 +01:00
parent b9d0f4fdd4
commit 7a84073bc4
38 changed files with 296 additions and 419 deletions
+158
View File
@@ -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(),
}
}
}
+1 -1
View File
@@ -56,7 +56,7 @@ impl std::fmt::Display for Register {
impl 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;
/// # Safety
+15
View File
@@ -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,3 +1,4 @@
/* @[crate::config::MemoryMap] */
MemoryMap(
table_addr: 0x1000,
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 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::{
Emulator,
backend::memory::{PhysAddr, mmu::MMU},
// io::DeviceId,
};
mod default;
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
@@ -25,7 +25,7 @@ pub struct Region {
pub size: u32,
pub region_type: RegionType,
// flags for the emulator
// flags for the ui
#[serde(default)]
pub identity_map_on_boot: bool,
}
@@ -38,11 +38,26 @@ pub struct MemoryMap {
#[derive(Serialize, Deserialize)]
pub struct IoMapping {
// pub device: DeviceId,
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;
+5
View File
@@ -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 {
buffer: TLB,
@@ -28,7 +28,7 @@ pub use bulkalloc::BulkAllocStore;
// #[cfg(feature = "mainstore-prealloc")]
// pub use prealloc::PreAllocStore;
use crate::backend::memory::PhysAddr;
use crate::memory::PhysAddr;
#[repr(transparent)]
#[derive(Clone)]
@@ -1,4 +1,4 @@
use crate::backend::memory::{PhysAddr, VirtAddr};
use crate::memory::{PhysAddr, VirtAddr};
const TLB_SIZE: usize = 256;
const TLB_MASK: u32 = (TLB_SIZE - 1) as u32;
@@ -10,13 +10,22 @@ use std::{
time::{Duration, Instant},
};
use crate::{
MemoryBank, Page, SharedState,
backend::{memory::mmu::MMU, processor::interrupts::Interrupt},
config::{MemoryMap, RegionType},
use common::{
isa::instructions::{Instruction, Opcode},
isa::register::Register
};
use common::isa::instructions::{Instruction, Opcode};
use common::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,
@@ -197,13 +206,13 @@ impl Emulator {
return;
}
// UI is resetting the emulator
// UI is resetting the ui
self.check_update();
}
}
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) {
self.wait_for_instructions();
}
@@ -324,9 +333,15 @@ impl Emulator {
)
}
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,
)
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(
@@ -515,7 +530,7 @@ impl Emulator {
#[inline]
fn mem_write_byte(&mut self, addr: u32, val: u8) {
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 {
eprintln!("writing char {} to uart + {}", val as char, addr - 0x40000);
@@ -2,7 +2,7 @@ use std::sync::{Arc, atomic::AtomicBool, mpsc};
use arc_swap::ArcSwap;
use crate::backend::processor::processor::ProcessorSnapshot;
use crate::processor::processor::ProcessorSnapshot;
use super::interrupts::Interrupt;
@@ -22,15 +22,16 @@ fxhash = "0.2.1"
ron = "0.12.0"
serde = { version = "1.0.228", features = ["derive"] }
common = { path = "../common" }
common = { path = "../../common" }
egui = "0.33.3"
eframe = "0.33.3"
egui_tiles = "0.14.1"
egui_code_editor = "0.2.21"
egui_file = "0.25.0"
dirs = "6.0.0"
assembler = { path = "../assembler" }
assembler = { path = "../../assembler" }
egui_extras = "0.33.3"
emu_core = { version = "0.3.0", path = "../backend" }
[features]
default = ["mainstore-prealloc"]
@@ -2,7 +2,7 @@ use std::{fs, path::PathBuf};
use clap::Parser;
use crate::config::{IoMapping, MemoryMap};
use emu_core::config::{IoMap, IoMapping, MemoryMap};
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
@@ -36,7 +36,7 @@ impl DsaArgs {
.and_then(|map| ron::from_str(&map).ok())
})
.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()))
}
pub fn get_io_map(&self) -> Vec<IoMapping> {
pub fn get_io_map(&self) -> IoMap {
self.io_map
.as_ref()
.and_then(|m| {
@@ -55,7 +55,7 @@ impl DsaArgs {
.and_then(|map| ron::from_str(&map).ok())
})
.unwrap_or_else(|| {
ron::from_str(include_str!("./config/default/dsa.iomap.ron")).unwrap()
IoMap::default()
})
}
}
+7
View File
@@ -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 common::prelude::Instruction;
use dsa::{Emulator, Page, args::DsaArgs, frontend::run_app};
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;
@@ -1,9 +1,9 @@
use std::sync::{Arc, atomic::Ordering};
use super::Component;
use crate::{MemoryBank, SharedState};
use common::prelude::Register;
use emu_core::memory::ram::MemoryBank;
use emu_core::processor::state::SharedState;
pub struct Controller {
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() {
self.state.reseting.store(true, Ordering::Relaxed);
self.mem.clear();
@@ -1,7 +1,6 @@
use egui::{Color32, FontId, Vec2};
use crate::{MemoryBank, Page, backend::memory::PhysAddr};
use emu_core::memory::PhysAddr;
use emu_core::memory::ram::{MemoryBank, Page};
use super::Component;
pub struct Display {
@@ -15,9 +15,8 @@ use egui::{Align, Context, Key, Layout, Ui};
use egui_code_editor::{CodeEditor, ColorTheme, Completer, Syntax};
use egui_extras::{Column, TableBuilder};
use egui_file::FileDialog;
use crate::{MemoryBank, Page, SharedState};
use emu_core::memory::ram::{MemoryBank, Page};
use emu_core::processor::state::SharedState;
use super::Component;
pub struct Editor {
@@ -86,7 +85,7 @@ impl Component for Editor {
Layout::left_to_right(Align::Min),
|ui| {
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);
});
}
@@ -327,7 +326,7 @@ impl Editor {
fn render_editor(&mut self, ui: &mut Ui, _ctx: &Context) {
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);
}
@@ -1,7 +1,6 @@
use egui::Vec2;
use crate::{MemoryBank, Page, backend::memory::PhysAddr};
use emu_core::memory::PhysAddr;
use emu_core::memory::ram::{MemoryBank, Page};
use super::Component;
pub struct FrameBuffer {
@@ -1,8 +1,8 @@
use std::num::ParseIntError;
use common::prelude::Instruction;
use crate::{MemoryBank, frontend::components::Component};
use emu_core::memory::ram::MemoryBank;
use super::Component;
pub struct MemoryInspector {
memory: MemoryBank,
@@ -1,8 +1,7 @@
use std::{f32, sync::Arc};
use common::prelude::Register;
use crate::SharedState;
use emu_core::processor::state::SharedState;
use super::Component;
@@ -6,17 +6,16 @@ use std::{
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 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+0x01]: ser_out byte - data payload for emulator to read
/// - [base+0x02]: ser_in valid flag - set by emulator after writing a byte to serial
/// - [base+0x01]: ser_out byte - data payload for ui to read
/// - [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
pub struct Serial {
pub read_rx: Receiver<u8>,
@@ -43,7 +42,7 @@ impl Serial {
}
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 {
let ser_out = mem.read_byte(uart_base + 1);
let _ = tx.send(ser_out);
@@ -1,16 +1,16 @@
use eframe::NativeOptions;
use eframe::egui;
use std::mem;
use std::sync::Arc;
use std::sync::atomic::Ordering;
mod components;
use components::{
Component, controller::Controller, display::Display, editor::Editor, framebuffer::FrameBuffer,
memory::MemoryInspector, registers::Registers, serial::Serial,
controller::Controller, display::Display, editor::Editor, framebuffer::FrameBuffer, memory::MemoryInspector,
registers::Registers, serial::Serial, Component,
};
use crate::{MemoryBank, SharedState, config::VERSION};
use emu_core::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<()> {
eframe::run_native(
@@ -22,7 +22,7 @@ pub fn run_app(state: Arc<SharedState>, mem_handle: MemoryBank) -> eframe::Resul
impl eframe::App for DsaUi {
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);
// title panel with dsa title
-2
View File
@@ -1,2 +0,0 @@
pub mod memory;
pub mod processor;
-332
View File
@@ -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)
),
]
-11
View File
@@ -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};