530 lines
19 KiB
Rust
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}")
|
|
}
|
|
}
|
|
}
|