IT WORKS HELL YEAH.

This commit is contained in:
2025-06-18 22:53:01 +01:00
parent 1210b19333
commit e281bc2d1d
26 changed files with 715 additions and 188 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ required-features = ["config"]
[dependencies]
common = { path = "../common" }
assembler = { path = "../assembler" }
dsa_editor = { git = "https://github.com/zxq5-dev/egui_code_editor", package = "egui_code_editor", rev = "5eb313e" }
dsa_editor = { path = "../dsa_editor" }
eframe = "0.31.1"
egui = "0.31.1"
rfd = "0.15.3"
-11
View File
@@ -36,8 +36,6 @@ pub fn run_emulator(
let mut instruction_count = 0;
loop {
println!("Looping");
let cmd = if running == Running::Running {
match cmd_rx.try_recv() {
Ok(cmd) => Some(cmd),
@@ -52,8 +50,6 @@ pub fn run_emulator(
};
if let Some(cmd) = cmd {
println!("Received command: {cmd:?}");
match cmd {
Command::Start => {
running = Running::Running;
@@ -69,18 +65,14 @@ pub fn run_emulator(
.expect("This is a valid path, WTF."),
));
}
println!("Emulator started");
}
Command::Stop => {
running = Running::Paused;
println!("Emulator stopped");
}
Command::Reset => {
running = Running::Paused;
processor.reset();
instruction_count = 0;
println!("Emulator rebooted");
}
Command::Step => {
running = Running::Paused;
@@ -98,15 +90,12 @@ pub fn run_emulator(
}
instruction_count += 1;
println!("Stepped one instruction");
}
Command::Read(new, _size) => {
addr = new;
println!("Memory view for address 0x{addr:08x}");
}
Command::Write(offset, data) => {
processor.memory.write_range(offset, data);
println!("Program loaded");
}
Command::Interrupt(_interrupt) => {
todo!("implement interrupts")
+1 -1
View File
@@ -78,7 +78,7 @@ impl MemoryUnit for MainStore {
bytes[1] = block.data[(offset + 1) as usize];
bytes[2] = block.data[(offset + 2) as usize];
bytes[3] = block.data[(offset + 3) as usize];
u32::from_le_bytes(bytes)
u32::from_be_bytes(bytes)
}
fn read_range(&mut self, addr: u32, size: u32) -> Vec<u8> {
+20 -6
View File
@@ -220,8 +220,8 @@ impl Executable for Instruction {
// address must be byte-aligned.
Self::StoreByte(a) => {
cpu.memory.write_byte(
cpu.get(a.r1) + u32::from(a.immediate),
cpu.get(a.r2) as u8,
cpu.get(a.r2) + u32::from(a.immediate),
cpu.get(a.r1) as u8,
);
}
@@ -231,16 +231,16 @@ impl Executable for Instruction {
// split the value into bytes and then write two bytes
let bytes = (cpu.get(a.r1) as u16).to_le_bytes();
cpu.memory
.write_byte(cpu.get(a.r1) + u32::from(a.immediate), bytes[0]);
.write_byte(cpu.get(a.r2) + u32::from(a.immediate), bytes[0]);
cpu.memory
.write_byte(cpu.get(a.r1) + u32::from(a.immediate) + 1, bytes[1]);
.write_byte(cpu.get(a.r2) + u32::from(a.immediate) + 1, bytes[1]);
}
// Stores a word from SrcReg in memory address (base + offset) The effective
// address must be 4-byte-aligned.
Self::StoreWord(a) => {
cpu.memory
.write_word(cpu.get(a.r1) + u32::from(a.immediate), cpu.get(a.r2));
.write_word(cpu.get(a.r2) + u32::from(a.immediate), cpu.get(a.r1));
}
// Loads a 16-bit literal value into reg, setting the bottom 16 bits of the
@@ -339,6 +339,14 @@ impl Executable for Instruction {
*cpu.reg(a.dr) = sub(cpu.get(a.sr1), cpu.get(a.sr2));
}
Self::AddImmediate(a) => {
*cpu.reg(a.r2) = add(cpu.get(a.r1), u32::from(a.immediate));
}
Self::SubImmediate(a) => {
*cpu.reg(a.r2) = sub(cpu.get(a.r1), u32::from(a.immediate));
}
// Performs bitwise AND on Src1 and Src2 storing the result in a.dr
Self::And(a) => *cpu.reg(a.dr) = and(cpu.get(a.sr1), cpu.get(a.sr2)),
@@ -384,7 +392,13 @@ impl Executable for Instruction {
cpu.halted = true;
}
_ => todo!(),
Self::Segment(_) => {}
Self::Data(_) => {}
_ => {
println!("unimplemented instruction: {}", self);
todo!()
}
}
}
}
+80
View File
@@ -0,0 +1,80 @@
use crate::emulator::{
system::model::State,
ui::interface::{Category, Component},
};
use eframe::egui;
use egui::{Color32, FontId, Margin, RichText, Vec2};
const VGA_WIDTH: usize = 80;
const VGA_HEIGHT: usize = 25;
pub struct Display {
visible: bool,
}
impl Display {
pub fn new() -> Self {
Self { visible: false }
}
}
impl Component for Display {
fn name(&self) -> &'static str {
"Display"
}
fn category(&self) -> Category {
Category::IO
}
fn visible(&mut self) -> &mut bool {
&mut self.visible
}
fn render(&mut self, state: &mut State, ui: &mut egui::Ui, ctx: &egui::Context) {
let display: Vec<u8> = state.display_view.clone();
let font_id = FontId::monospace(12.0);
let char_width = ui.fonts(|f| f.glyph_width(&font_id, 'W'));
let line_height = ui.fonts(|f| f.row_height(&font_id));
let display_size = Vec2::new(
char_width * VGA_WIDTH as f32,
line_height * VGA_HEIGHT as f32,
);
let (rect, _response) = ui.allocate_exact_size(display_size, egui::Sense::all());
// Fill background
// ui.painter().rect_filled(rect, 0.0, Color32::BLACK);
// Draw text
for y in 0..VGA_HEIGHT {
let mut row_text = String::with_capacity(VGA_WIDTH);
for x in 0..VGA_WIDTH {
let index = y * VGA_WIDTH + x;
if index < display.len() {
let byte = display[index];
let ch = if byte >= 32 && byte <= 126 {
byte as char
} else {
' '
};
row_text.push(ch);
} else {
row_text.push(' ');
}
}
let text_pos = rect.min + Vec2::new(0.0, y as f32 * line_height);
ui.painter().text(
text_pos,
egui::Align2::LEFT_TOP,
row_text,
font_id.clone(),
Color32::WHITE,
);
}
}
}
+84 -42
View File
@@ -12,8 +12,10 @@ use crate::emulator::{
};
pub struct Editor {
path: PathBuf,
path: Option<PathBuf>,
text: String,
buffer: String,
unsaved: bool,
output: Vec<u8>,
sender: Sender<Command>,
cursor_col: usize,
@@ -38,6 +40,10 @@ impl Component for Editor {
}
fn render(&mut self, state: &mut State, ui: &mut Ui, ctx: &Context) {
if self.buffer != self.text {
self.unsaved = true;
}
ui.vertical(|ui| {
// Top bar
@@ -62,7 +68,7 @@ impl Component for Editor {
},
);
ui.label(format!("Ln {}, Col {}", self.cursor_line, self.cursor_col));
self.render_bottom_bar(state, ui, ctx);
});
}
}
@@ -71,9 +77,11 @@ impl Editor {
#[must_use]
pub fn new(sender: Sender<Command>) -> Self {
Self {
path: PathBuf::new(),
path: None,
text: String::new(),
buffer: String::new(),
output: Vec::new(),
unsaved: true,
sender,
cursor_col: 1,
cursor_line: 1,
@@ -85,25 +93,31 @@ impl Editor {
}
fn filename(&self) -> &str {
self.path
.file_name()
.unwrap_or_else(|| OsStr::new("Unnamed!"))
.to_str()
.map_or_else(
|| unreachable!("File name should be valid UTF-8."),
|ext| ext,
)
if let Some(path) = &self.path {
return path
.file_name()
.unwrap_or_else(|| OsStr::new("Unnamed!"))
.to_str()
.map_or_else(
|| unreachable!("File name should be valid UTF-8."),
|ext| ext,
);
}
"Unnamed!"
}
fn extension(&self) -> &str {
self.path
.extension()
.unwrap_or_else(|| OsStr::new("Unknown!"))
.to_str()
.map_or_else(
|| unreachable!("File name should be valid UTF-8."),
|ext| ext,
)
if let Some(path) = &self.path {
return path
.extension()
.map_or_else(|| OsStr::new("Unknown!"), |ext| ext)
.to_str()
.map_or_else(
|| unreachable!("File name should be valid UTF-8."),
|ext| ext,
);
}
"Unknown!"
}
fn save(&mut self) {
@@ -113,6 +127,17 @@ impl Editor {
)
});
if let Some(path) = &self.path {
if let Err(why) = std::fs::write(path, &self.text) {
self.error = Some(format!("Failed to save file: {why}"));
return;
}
self.buffer = self.text.clone();
self.unsaved = false;
return;
}
if let Some(path) = FileDialog::new()
.add_filter("damn simple files", &["dsa", "dsb", "dsc", "dsd"])
.add_filter("all", &["*"])
@@ -122,7 +147,9 @@ impl Editor {
if let Err(why) = std::fs::write(&path, &self.text) {
self.error = Some(format!("Failed to save file: {why}"));
} else {
self.path = path;
self.path = Some(path);
self.buffer = self.text.clone();
self.unsaved = false;
}
}
}
@@ -141,8 +168,10 @@ impl Editor {
.pick_file()
{
if let Ok(contents) = std::fs::read_to_string(&path) {
self.path = path;
self.text = contents;
self.path = Some(path);
self.text = contents.clone();
self.buffer = contents;
self.unsaved = false;
}
}
}
@@ -151,7 +180,7 @@ impl Editor {
// Output area with synchronized scrolling
egui::ScrollArea::vertical()
.id_salt("output_scroll")
.max_width(350.0)
.max_width(400.0)
.show(ui, |ui| {
if self.output.is_empty() {
ui.label(
@@ -178,7 +207,7 @@ impl Editor {
bytes[i] = byte;
}
}
let value = u32::from_le_bytes(bytes);
let value = u32::from_be_bytes(bytes);
// Address column
ui.with_layout(
@@ -245,8 +274,9 @@ impl Editor {
.with_fontsize(12.0)
.with_rows(0)
.with_theme(ColorTheme::default())
.with_syntax(Syntax::dsa())
.with_numlines(true)
.desired_width(available_width - 450.0);
.desired_width(available_width - 500.0);
let mut editor = ed.clone();
@@ -257,17 +287,27 @@ impl Editor {
editor.show(ui, &mut self.text);
}
fn render_toolbar(&mut self, _state: &mut State, ui: &mut Ui, _ctx: &Context) {
fn render_bottom_bar(&mut self, _state: &mut State, ui: &mut Ui, _ctx: &Context) {
ui.horizontal(|ui| {
ui.label(format!("File type: {}", self.extension()));
ui.label(format!("Filename: {}", self.filename()));
// error display
ui.label(
egui::RichText::new(self.error.clone().unwrap_or_default())
.color(egui::Color32::RED),
);
// line and col
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
ui.label(format!("Ln {}, Col {}", self.cursor_line, self.cursor_col));
});
});
}
fn render_toolbar(&mut self, _state: &mut State, ui: &mut Ui, _ctx: &Context) {
ui.horizontal(|ui| {
ui.label(format!("File type: {}", self.extension()));
ui.label(format!("Filename: {}", self.filename()));
ui.label(format!("Unsaved: {}", self.unsaved));
// number of lines in the file
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
let line_count = self.text.lines().count();
@@ -291,20 +331,22 @@ impl Editor {
// builds the current file
if ui.button("Build").clicked() {
// TODO: uncomment this once assembler works!!!
// let instructions = assembler::assemble(&self.text);
// self.output = instructions
// .iter()
// .flat_map(|i| i.encode().to_le_bytes().to_vec())
// .collect();
if !self.unsaved {
if let Some(path) = &self.path {
let instructions = match assembler::assemble(path) {
Ok(instructions) => instructions,
Err(error) => {
self.error = Some(error.to_string());
return;
}
};
self.output = vec![
0x2e, 0xe0, 0x00, 0x00, 0x2e, 0xe1, 0x00, 0x01, 0x2e, 0xe2, 0x00,
0x06, 0x2e, 0xf6, 0x00, 0x00, 0x64, 0x01, 0x18, 0x00, 0x04, 0x37,
0x00, 0x00, 0x04, 0x77, 0x08, 0x00, 0x58, 0x57, 0x10, 0x00, 0x50,
0x56, 0xb8, 0x00, 0x42, 0xf7, 0x00, 0x04, 0x04, 0x37, 0x80, 0x00,
0x92, 0xf7, 0xb8, 0x00,
];
self.output = instructions
.iter()
.flat_map(|i| i.encode().to_be_bytes().to_vec())
.collect();
}
}
}
// Loads the generated binary into the assembler at the provided offset
+1 -2
View File
@@ -131,8 +131,7 @@ impl Component for MemoryInspector {
.fold(0u32, |acc, &byte| acc << 8 | u32::from(byte));
ui.monospace(format!("{combined}"));
ui.monospace(format!("{:?}", Instruction::decode(combined)));
ui.monospace("TODO! instruction");
ui.monospace(format!("{}", Instruction::decode(combined).unwrap_or(Instruction::Nop)));
ui.end_row();
}
+1
View File
@@ -1,4 +1,5 @@
pub mod control_unit;
pub mod display;
pub mod editor;
pub mod interface;
pub mod memory_inspector;
+6 -2
View File
@@ -18,8 +18,9 @@ use dsa_rs::emulator::{
processor::Processor,
},
ui::{
control_unit::ControlPanel, editor::Editor, interface::EmulatorUI,
memory_inspector::MemoryInspector, stack_inspector::StackInspector,
control_unit::ControlPanel, display::Display, editor::Editor,
interface::EmulatorUI, memory_inspector::MemoryInspector,
stack_inspector::StackInspector,
},
};
@@ -97,5 +98,8 @@ fn setup_ui(cmd_sender: Sender<Command>, state_reciever: Receiver<State>) -> Emu
let editor = Editor::new(cmd_sender.clone());
ui.add_component(Box::new(editor));
let display = Display::new();
ui.add_component(Box::new(display));
ui
}