extracted emulator backend into independent crate
This commit is contained in:
@@ -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(
|
||||
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;
|
||||
|
||||
@@ -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;
|
||||
+27
-12
@@ -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);
|
||||
+1
-1
@@ -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()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
+3
-3
@@ -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();
|
||||
+2
-3
@@ -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 {
|
||||
+4
-5
@@ -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);
|
||||
}
|
||||
|
||||
+2
-3
@@ -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 {
|
||||
+2
-2
@@ -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
-2
@@ -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;
|
||||
|
||||
+8
-9
@@ -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
|
||||
@@ -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