added serial-out support to emulator + serial lib & command line mode for dsa emulator

This commit is contained in:
2026-02-23 19:51:05 +00:00
parent 7ab1ac8842
commit 1d38aca523
22 changed files with 1906 additions and 24 deletions
+55
View File
@@ -0,0 +1,55 @@
use std::path::Path;
use std::thread::sleep;
use std::time::Duration;
use std::{env, fs};
use crate::emulator::system::model::{Command, Running};
use crate::emulator::{config::Config, system::model::State};
pub fn run_cli() -> Result<(), Box<dyn std::error::Error>> {
// Initialize channels and read in configuration.
let (cmd_sender, cmd_receiver) = std::sync::mpsc::channel();
let (state_sender, state_reciever) = std::sync::mpsc::channel();
// not needed for now.
// let config = Config::load(Path::new(".dsa.emulator.toml"))?;
crate::setup_emulator(cmd_receiver, state_sender, None);
// run CLI.
let mut state = State::new(cmd_sender, state_reciever);
let mut bin_path: Option<String> = None;
for (i, arg) in env::args().enumerate().skip(1) {
// check for args --bin and <bin_path>
if arg == "--bin" {
bin_path = Some(env::args().nth(i + 1).expect("Binary path not provided"));
}
}
let binary =
fs::read(bin_path.expect("unreachable")).expect("unable to read binary file");
state.send(Command::Write(0, binary));
println!("{:?}", state.running);
sleep(Duration::from_secs(1));
state.update().unwrap();
state.cmd_sender.send(Command::Start).unwrap();
loop {
sleep(Duration::from_millis(20));
state.send(Command::DisplayRequest);
state.send(Command::RunningRequest);
state.update().expect("update failed");
for ch in state.serial_buff.drain(..) {
print!("{}", ch as char);
}
if state.running == Running::Halted {
break;
}
}
Ok(())
}
+1
View File
@@ -1,3 +1,4 @@
pub mod cli;
#[cfg(feature = "config")]
pub mod config;
pub mod misc;
@@ -0,0 +1,2 @@
pub const DISPLAY_ADDRESS: u32 = 0x20000;
pub const SERIAL_ADDRESS: u32 = DISPLAY_ADDRESS + 2000;
+3
View File
@@ -135,6 +135,9 @@ pub fn run_emulator(
Vec::new()
}),
));
let _ = state_tx.send(StateUpdate::Serial(
processor.serial_buff.drain(..).collect(),
));
}
Command::StackRequest if update => {
let _ = state_tx.send(StateUpdate::StackView(
+1
View File
@@ -1,4 +1,5 @@
pub mod cache;
pub mod constants;
pub mod emulator;
pub mod memory;
pub mod model;
+7
View File
@@ -28,6 +28,7 @@ pub enum Command {
WriteBlock(Address, Box<[u8; 256]>),
// request emulator state.
SerialRequest,
MemRequest(Address, u32),
DisplayRequest,
StackRequest,
@@ -79,6 +80,7 @@ pub struct State {
pub error_log: Vec<String>,
pub instruction_history: Vec<(u32, u32)>,
pub serial_buff: Vec<u8>,
}
impl State {
@@ -95,6 +97,7 @@ impl State {
display_view: vec![],
error_log: vec![],
instruction_history: vec![],
serial_buff: vec![],
}
}
@@ -121,6 +124,9 @@ impl State {
StateUpdate::InstructionHistory(history) => {
self.instruction_history.extend(history);
}
StateUpdate::Serial(buffer) => {
self.serial_buff.extend(buffer);
}
}
if self.error_log.len() > 256 {
@@ -148,6 +154,7 @@ impl State {
pub enum StateUpdate {
Registers(RegFile),
Serial(Vec<u8>),
Running(Running),
Instructions(usize),
StackView(Vec<u8>),
+30 -14
View File
@@ -5,6 +5,7 @@ use std::{
use crate::emulator::system::{
cache::Cache,
constants::{DISPLAY_ADDRESS, SERIAL_ADDRESS},
memory::MemoryUnit,
model::{IODevice, ProcessorError, RegFile},
};
@@ -19,6 +20,8 @@ pub struct Processor {
pub void: u32,
pub cache: Cache,
pub serial_buff: Vec<u8>,
}
impl Processor {
@@ -31,6 +34,8 @@ impl Processor {
io_devices,
void: 0,
cache: Cache::new(),
serial_buff: Vec::with_capacity(32768),
}
}
@@ -97,7 +102,7 @@ impl Processor {
}
pub fn display(&mut self) -> Result<Vec<u8>, ProcessorError> {
Ok(self.memory.read_range(0x20000, 2000))
Ok(self.memory.read_range(DISPLAY_ADDRESS, 2000))
}
pub fn cmp(&mut self, a: u32, b: u32) {
@@ -267,30 +272,41 @@ impl Executable for Instruction {
// Stores a byte from SrcReg in memory address (base + offset) The effective
// address must be byte-aligned.
Self::StoreByte(a) => {
cpu.memory.write_byte(
cpu.get(a.r2)? + u32::from(a.immediate),
cpu.get(a.r1)? as u8,
);
let addr = cpu.get(a.r2)? + u32::from(a.immediate);
let val = cpu.get(a.r1)? as u8;
if addr == SERIAL_ADDRESS {
cpu.serial_buff.push(val);
}
cpu.memory.write_byte(addr, val);
}
// Stores a half-word from SrcReg in memory address (base + offset) The
// effective address must be 2-byte-aligned.
Self::StoreHalfword(a) => {
// split the value into bytes and then write two bytes
let addr = cpu.get(a.r2)? + u32::from(a.immediate);
let bytes = (cpu.get(a.r1)? as u16).to_le_bytes();
cpu.memory
.write_byte(cpu.get(a.r2)? + u32::from(a.immediate), bytes[0]);
cpu.memory
.write_byte(cpu.get(a.r2)? + u32::from(a.immediate) + 1, bytes[1]);
if addr == SERIAL_ADDRESS {
cpu.serial_buff.extend(bytes);
}
// split the value into bytes and then write two bytes
cpu.memory.write_byte(addr, bytes[0]);
cpu.memory.write_byte(addr + 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.r2)? + u32::from(a.immediate),
cpu.get(a.r1)?,
)?;
let addr = cpu.get(a.r2)? + u32::from(a.immediate);
let val = cpu.get(a.r1)?;
let bytes = val.to_le_bytes();
if addr == SERIAL_ADDRESS {
cpu.serial_buff.extend(bytes);
}
cpu.memory.write_word(addr, val)?;
}
// Loads a 16-bit literal value into reg, setting the bottom 16 bits of the
+1 -1
View File
@@ -448,7 +448,7 @@ impl Editor {
}
let mut assembler = Assembler::new(&dsa_path);
compiler.start();
assembler.start();
// Or block until done
self.output = match assembler.output() {
+6
View File
@@ -1,9 +1,15 @@
use std::env;
use std::path::Path;
use std::sync::Arc;
use dsa_rs::emulator::{config::Config, misc::rpc::get_rpc_client_or_none};
fn main() -> Result<(), Box<dyn std::error::Error>> {
if env::args().any(|arg| arg == "--cli") {
dsa_rs::emulator::cli::run_cli()?;
std::process::exit(0);
}
// Initialize channels and read in configuration.
let (cmd_sender, cmd_receiver) = std::sync::mpsc::channel();
let (state_sender, state_reciever) = std::sync::mpsc::channel();