Files
bisare_sim_rs/simu/src/main.rs
2026-03-23 19:19:38 +01:00

530 lines
19 KiB
Rust

#![feature(likely_unlikely, widening_mul, int_lowest_highest_one)]
#![deny(clippy::all)]
use std::env::args;
use std::hint::unlikely;
use std::process::exit;
use std::sync::{
Arc,
atomic::Ordering::{Acquire, Relaxed, Release},
};
use std::thread::scope;
use std::time::{Duration, Instant};
use pixels::wgpu::{BlendState, Color};
use pixels::{Error, Pixels, PixelsBuilder, SurfaceTexture};
use winit::application::ApplicationHandler;
use winit::dpi::LogicalSize;
use winit::event::WindowEvent;
use winit::event_loop::EventLoop;
use winit::platform::scancode::PhysicalKeyExtScancode;
use winit::window::Window;
use crate::cpu::{Computer, MMIOInterupt, instr_to_text};
mod wait;
use wait::WaitOnAtomic;
mod cpu;
use cpu::SHARED;
fn wait_int() {
let mut v = (&SHARED.external_interupts).load(Acquire);
while unlikely(v != 0) {
#[cfg(feature = "debug")]
println!("wating for interupt clear {v}");
SHARED.external_interupts.wait(v);
v = (&SHARED.external_interupts).load(Acquire);
}
}
const WIDTH: u32 = 640;
const HEIGHT: u32 = 480;
struct App<'a> {
w: Option<Arc<Window>>,
pixels: Option<Pixels<'a>>,
}
impl<'a> ApplicationHandler for App<'a> {
fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
let window = {
let size = LogicalSize::new(WIDTH as f64, HEIGHT as f64);
Arc::new(
event_loop
.create_window(
Window::default_attributes()
.with_title("bisare screen")
.with_min_inner_size(size)
.with_maximized(true),
)
.unwrap(),
)
};
self.w = Some(window.clone());
let size = window.inner_size();
let surface_texture = SurfaceTexture::new(size.width, size.height, window);
let pix = PixelsBuilder::new(WIDTH, HEIGHT, surface_texture)
.clear_color(Color::BLACK)
.blend_state(BlendState::REPLACE)
.build();
self.pixels = Some(pix.unwrap());
}
fn window_event(
&mut self,
elwt: &winit::event_loop::ActiveEventLoop,
_: winit::window::WindowId,
event: WindowEvent,
) {
// Draw the current frame
match event {
WindowEvent::KeyboardInput {
event: key_event, ..
} => {
let enabled = (&SHARED.external_enabled_interupts).load(Relaxed)
& Into::<u32>::into(MMIOInterupt::Keyboard)
!= 0;
if enabled {
wait_int();
}
#[cfg(feature = "debug")]
print!("Keyboard event: ");
#[cfg(feature = "rich_keyboard")]
{
use winit::platform::modifier_supplement::KeyEventExtModifierSupplement;
let kb0 = key_event.text_with_all_modifiers().map_or(u32::MAX, |s| {
s.as_bytes()
.into_iter()
.fold(0, |a, e| a << 8 | (*e as u32))
});
SHARED.keyboard[0].store(kb0, Relaxed);
let kb1 = key_event
.key_without_modifiers()
.to_text()
.map_or(u32::MAX, |s| {
s.as_bytes()
.into_iter()
.fold(0, |a, e| a << 8 | (*e as u32))
});
SHARED.keyboard[1].store(kb1, Relaxed);
let kb2 =
key_event.state.is_pressed() as u32 | ((key_event.repeat as u32) << 1);
SHARED.keyboard[2].store(kb2, Relaxed);
#[cfg(feature = "debug")]
print!("{kb0} {kb1} {kb2}")
}
let kb3 = key_event.physical_key.to_scancode().unwrap_or(0);
#[cfg(feature = "debug")]
println!(" {kb3}");
SHARED.keyboard[3].store(kb3, Relaxed);
if enabled {
(&SHARED.external_interupts).store(MMIOInterupt::Keyboard.into(), Release);
#[cfg(feature = "debug")]
println!("wake due to keyboard event");
SHARED.external_interupts.signal();
}
}
WindowEvent::CursorMoved { position, .. } => {
let enabled = (&SHARED.external_enabled_interupts).load(Relaxed)
& Into::<u32>::into(MMIOInterupt::MouseMove)
!= 0;
if enabled {
wait_int();
}
match self
.pixels
.as_ref()
.unwrap()
.window_pos_to_pixel((position.x as f32, position.y as f32))
{
Ok((x, y)) => {
(&cpu::SHARED.mouse[1]).store(x as u32, Relaxed);
(&cpu::SHARED.mouse[2]).store(y as u32, Relaxed);
}
Err(_) => {
(&SHARED.mouse[1]).store(u32::MAX, Relaxed);
(&SHARED.mouse[2]).store(u32::MAX, Relaxed);
}
}
if enabled {
(&SHARED.external_interupts).store(MMIOInterupt::MouseMove.into(), Release);
#[cfg(feature = "debug")]
println!("wake due mouse move");
SHARED.external_interupts.signal();
}
}
// WindowEvent::MouseWheel {
// delta,
// ..
// } => {}
WindowEvent::MouseInput { state, button, .. } => {
let enabled = (&SHARED.external_enabled_interupts).load(Relaxed)
& Into::<u32>::into(MMIOInterupt::MouseClick)
!= 0;
if enabled {
wait_int();
}
let but = 1
<< match button {
winit::event::MouseButton::Left => 0,
winit::event::MouseButton::Right => 1,
winit::event::MouseButton::Middle => 2,
winit::event::MouseButton::Back => 3,
winit::event::MouseButton::Forward => 4,
winit::event::MouseButton::Other(i) => i & 31,
};
match state {
winit::event::ElementState::Pressed => {
(&cpu::SHARED.mouse[0]).fetch_or(but, std::sync::atomic::Ordering::Relaxed)
}
winit::event::ElementState::Released => (&cpu::SHARED.mouse[0])
.fetch_and(!but, std::sync::atomic::Ordering::Relaxed),
};
if enabled {
(&SHARED.external_interupts).store(MMIOInterupt::MouseClick.into(), Release);
#[cfg(feature = "debug")]
println!("wake mouse click");
SHARED.external_interupts.signal();
}
}
WindowEvent::ScaleFactorChanged { .. } => {
self.w.as_ref().unwrap().request_redraw();
}
//handling redraws and other graphical events
WindowEvent::RedrawRequested => {
let enabled = (&SHARED.external_enabled_interupts).load(Relaxed)
& Into::<u32>::into(MMIOInterupt::VSync)
!= 0;
if enabled {
(&SHARED.external_interupts).store(MMIOInterupt::VSync.into(), Relaxed);
SHARED.external_interupts.signal();
wait_int();
}
let pix = self.pixels.as_mut().unwrap();
let screen = pix.frame_mut();
for (addr, ubgr) in cpu::SHARED.screen_buf.iter().enumerate() {
let raw = ubgr.load(std::sync::atomic::Ordering::Relaxed);
#[cfg(not(feature = "rgba"))]
let rgba: [u8; 4] = raw.to_le_bytes();
#[cfg(feature = "rgba")]
let rgba = raw.to_be_bytes();
for i in 0..4 {
screen[addr * 4 + i] = rgba[i];
}
}
if let Err(_) = pix.render() {
elwt.exit();
return;
}
}
WindowEvent::Resized(size) => {
if self
.pixels
.as_mut()
.unwrap()
.resize_surface(size.width, size.height)
.is_err()
{
println!("Error while resising pixels, exiting!");
elwt.exit();
return;
}
self.w.as_ref().unwrap().request_redraw();
}
WindowEvent::CloseRequested => {
elwt.exit();
return;
}
WindowEvent::Destroyed => {
println!("Windows destroyed, exiting!");
elwt.exit();
return;
}
//do nothing for the other events
_ => {}
}
}
fn new_events(
&mut self,
event_loop: &winit::event_loop::ActiveEventLoop,
cause: winit::event::StartCause,
) {
match cause {
winit::event::StartCause::ResumeTimeReached {
requested_resume, ..
} => {
let mut next = requested_resume + Duration::from_secs_f64(1. / 60.);
let now = Instant::now();
if next < now {
next = now + Duration::from_secs_f64(1. / 30.);
}
event_loop.set_control_flow(winit::event_loop::ControlFlow::WaitUntil(next));
if let Some(w) = self.w.as_ref() {
w.request_redraw();
}
}
winit::event::StartCause::WaitCancelled { .. } => {}
winit::event::StartCause::Poll => {
let next = Instant::now() + Duration::from_secs_f64(1. / 60.);
event_loop.set_control_flow(winit::event_loop::ControlFlow::WaitUntil(next));
}
winit::event::StartCause::Init => {
let next = Instant::now() + Duration::from_secs_f64(1. / 60.);
event_loop.set_control_flow(winit::event_loop::ControlFlow::WaitUntil(next));
}
}
}
}
fn main() -> Result<(), Error> {
let event_loop = EventLoop::new().unwrap();
// let mut input = WinitInputHelper::new();
let mut app = App {
w: None,
pixels: None,
};
let program = args()
.nth(1)
.expect("you must supply the exec name as the first argument");
scope(|sc| {
sc.spawn(|| {
let mut simulation = Computer::new(program);
#[cfg(not(feature = "debug"))]
loop {
simulation.step(64);
}
//ugly debug code, I should improve that using a real TUI crate
#[cfg(feature = "debug")]
{
debug_loop(&mut simulation);
}
});
#[allow(deprecated)]
let res = event_loop.run_app(&mut app);
match res {
Ok(_) => exit(0),
Err(e) => {
println!("{e}");
exit(1);
}
}
})
}
#[cfg(feature = "debug")]
fn debug_loop(com: &mut Computer) {
struct Wrap(DefaultCompleter, DefaultCompleter);
use clap::Parser;
use clap_repl::ClapEditor;
use clap_repl::reedline::{
Completer, DefaultCompleter, DefaultPrompt, DefaultPromptSegment, FileBackedHistory,
};
use crate::cpu::instr_to_text;
#[derive(Debug, Parser)]
#[command(name = "")] // This name will show up in clap's error messages, so it is important to set it to "".
enum Commands {
/// Step by single instrcution
#[command(alias = "s")]
Step {
///number of instruction to step (default one)
num: Option<usize>,
},
/// Run until program halt, or specified instruction is reached
#[command(alias = "r")]
Run {
/// Can be either a label or a address (support hex format)
desigantor: Option<String>,
},
/// run until the current function return.
#[command(alias = "u")]
Up,
/// Print memory at address. Support hexa format
#[command(alias = "p")]
Print { address: String },
/// Print the address associated with a label
#[command(alias = "l")]
Label { label: String },
/// Print context
#[command(alias = "c")]
Context,
}
let prompt = DefaultPrompt {
left_prompt: DefaultPromptSegment::Basic(">>".to_owned()),
right_prompt: DefaultPromptSegment::Empty,
};
let commands_comp = DefaultCompleter::new_with_wordlen(
["step", "run", "up", "print", "label", "help", "context"]
.map(|s| s.to_string())
.to_vec(),
0,
);
let mut labels_comp =
DefaultCompleter::with_inclusions("_0123456789".chars().collect::<Vec<_>>().as_slice())
.set_min_word_len(0);
labels_comp.insert(com.book.0.values().cloned().collect());
let editor = ClapEditor::<Commands>::builder()
.with_prompt(Box::new(prompt))
.with_editor_hook(|reed| {
reed.with_history(Box::new(
FileBackedHistory::with_file(1000, "debug_cmd.hist".into()).unwrap(),
))
.with_completer(Box::new(Wrap(commands_comp, labels_comp)))
.with_quick_completions(true)
.with_partial_completions(true)
})
.build();
debug_context(com);
editor.repl(|command| match command {
Commands::Step { num } => {
let steps = num.unwrap_or(1);
com.debug_step(steps);
debug_context(com);
}
Commands::Run { desigantor } => match desigantor {
Some(s) => match parse_int::parse::<usize>(s.as_str()) {
Ok(addr) => {
while com.pc != (addr / 4) {
com.debug_step(1);
}
debug_context(com);
}
Err(_) => match com.book.1.get(s.as_str()).cloned() {
Some(addr) => {
while com.pc != (addr as usize / 4) {
com.debug_step(1);
}
debug_context(com);
}
None => {
println!("Error, {s} cannot be interpreted as addr nor label")
}
},
},
None => {
while !com.error {
com.debug_step(64);
}
debug_context(com);
}
},
Commands::Up => {
let curr_sp = com.sp;
while (com.sp > curr_sp)
|| ((com.ram[com.pc] != (0b10001000 << 24))
&& (com.ram[com.pc] != (0b10101000 << 24)))
{
com.debug_step(1);
}
debug_context(com);
}
Commands::Print { address } => match parse_int::parse::<usize>(address.as_str()) {
Ok(addr) => match com.ram.get(addr / 4) {
Some(i) => {
println!(
"RAM at {addr:8x}: {:8x} {}",
i,
instr_to_text(*i, u32::MAX, &com.book.0)
)
}
None => println!("Cannot index RAM at address {addr:8x}"),
},
Err(_) => match com.book.1.get(address.as_str()).cloned() {
Some(addr) => println!(
"RAM at {addr:8x}: {:8x} {}",
com.ram[addr as usize / 4],
instr_to_text(com.ram[addr as usize / 4], addr, &com.book.0)
),
None => {
println!("Error, {address} cannot be interpreted as addr nor label")
}
},
},
Commands::Label { label } => match com.book.1.get(label.as_str()) {
Some(addr) => println!("label is at addr {addr}"),
None => println!("error: label not found"),
},
Commands::Context => debug_context(com),
});
exit(0);
impl Completer for Wrap {
fn complete(&mut self, line: &str, pos: usize) -> Vec<clap_repl::reedline::Suggestion> {
let trimmed = line.trim_start();
let line_parts = trimmed.splitn(2, ' ').collect::<Vec<_>>();
if line_parts.len() <= 1 {
self.0.complete(line, pos)
} else {
match line_parts[0] {
"r" | "run" | "p" | "print" | "l" | "label" => {
let trimmed_2 = line_parts[1].trim_start();
let offset = line.len() - trimmed_2.len();
let mut sub = self.1.complete(trimmed_2, pos - offset);
for sug in sub.iter_mut() {
use clap_repl::reedline::Span;
sug.span = Span {
start: sug.span.start + offset,
end: sug.span.end + offset,
}
}
sub
}
_ => Vec::new(),
}
}
}
}
}
fn debug_context(com: &Computer) {
println!("Interupt state: {:?}", com.interupts);
for i in 0..8 {
println!(
"r{i} = {:8x} r{:<2} = {:8x}",
com.regs[i],
i + 8,
com.regs[i + 8]
);
}
println!("SP={:08x} PC={:08x}", com.sp, com.pc);
println!("RAM at SP | Ram at PC:");
let mut pc_lines = Vec::new();
for i in 0..16 {
match com.book.0.get(&((com.pc + i) as u32 * 4)) {
Some(label) => pc_lines.push(format!(" {label}:")),
None => {}
};
pc_lines.push(format!(
"{:08x} {}",
com.ram[com.pc + i],
instr_to_text(com.ram[com.pc + i], (com.pc + i) as u32 * 4, &com.book.0)
));
}
for (i, pc_l) in pc_lines.iter().enumerate() {
if com.sp + i < com.ram.len() {
println!("{:08x} | {pc_l}", com.ram[com.sp + i])
} else {
println!(" -- | {pc_l}")
}
}
}