471 lines
18 KiB
Rust
471 lines
18 KiB
Rust
#![feature(
|
|
likely_unlikely,
|
|
widening_mul,
|
|
sync_unsafe_cell,
|
|
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};
|
|
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);
|
|
}
|
|
#[cfg(feature = "debug")]
|
|
{
|
|
let mut input = std::io::stdin().lines();
|
|
loop {
|
|
{
|
|
println!(
|
|
"interrupts are {:?}, with mmio interupts flags {}",
|
|
simulation.interupts,
|
|
SHARED.external_enabled_interupts.load(Relaxed)
|
|
);
|
|
for i in 0..8 {
|
|
println!(
|
|
"r{i} 0x{:08x} r{} 0x{:08x}",
|
|
simulation.regs[i],
|
|
i + 8,
|
|
simulation.regs[i + 8]
|
|
);
|
|
}
|
|
println!(
|
|
"SP: {:08x} PC: {:08x}",
|
|
simulation.sp * 4,
|
|
simulation.pc * 4
|
|
);
|
|
println!("RAM near SP");
|
|
let min_pc = (simulation.pc).min(0x0100_0000 / 4 - 8);
|
|
let min_sp = (simulation.sp).min(0x0100_0000 / 4 - 8);
|
|
for i in 0..8 {
|
|
println!(
|
|
"{:8x}: 0x{:08x}",
|
|
(min_sp + i) * 4,
|
|
simulation.ram[min_sp + i],
|
|
);
|
|
}
|
|
println!("Ram near PC");
|
|
for i in 0..8 {
|
|
let idx = min_pc + i;
|
|
let istr = simulation.ram[idx];
|
|
if let Some(s) = simulation.book.get(&(idx as u32 * 4)) {
|
|
println!("{s}:")
|
|
};
|
|
println!(
|
|
"{:8x}: 0x{:08x} {}",
|
|
idx * 4,
|
|
istr,
|
|
cpu::instr_to_text(istr, idx as u32 * 4, &simulation.book)
|
|
)
|
|
}
|
|
}
|
|
while {
|
|
let next = input.next().unwrap().unwrap();
|
|
let next: Vec<_> = next.split_ascii_whitespace().collect();
|
|
if next.len() > 0 {
|
|
match next[0] {
|
|
"s" | "step" => {
|
|
let n: usize = {
|
|
if next.len() >= 2 {
|
|
parse_int::parse(next[1]).unwrap_or(1)
|
|
} else {
|
|
1
|
|
}
|
|
};
|
|
for _ in 0..n {
|
|
if simulation.error {
|
|
println!("cannot step, cpu killed");
|
|
break;
|
|
}
|
|
simulation.step(1);
|
|
}
|
|
false
|
|
}
|
|
"r" | "run" => {
|
|
while !simulation.error {
|
|
simulation.step(64);
|
|
}
|
|
false
|
|
}
|
|
"p" | "print" => {
|
|
if next.len() >= 2 {
|
|
match parse_int::parse::<u32>(next[1]) {
|
|
Ok(i) => {
|
|
let v = simulation.ram[i as usize / 4];
|
|
println!(
|
|
"0x{:8x} -- {}",
|
|
v,
|
|
cpu::instr_to_text(v, i, &simulation.book)
|
|
);
|
|
true
|
|
}
|
|
Err(e) => {
|
|
println!("{e}");
|
|
true
|
|
}
|
|
}
|
|
} else {
|
|
println!("{HELP_MSG}");
|
|
true
|
|
}
|
|
}
|
|
"c" | "context" => false,
|
|
"u" | "up" => {
|
|
while !simulation.error
|
|
&& simulation.ram[simulation.pc] != 0x8800_0000
|
|
{
|
|
simulation.step(1);
|
|
}
|
|
false
|
|
}
|
|
"t" | "to" => {
|
|
if next.len() >= 2 {
|
|
match parse_int::parse::<u32>(next[1]) {
|
|
Ok(v) => {
|
|
while !simulation.error
|
|
&& simulation.pc != (v as usize / 4)
|
|
{
|
|
simulation.step(1);
|
|
}
|
|
false
|
|
}
|
|
Err(e) => {
|
|
println!("{e}");
|
|
true
|
|
}
|
|
}
|
|
} else {
|
|
println!("{HELP_MSG}");
|
|
true
|
|
}
|
|
}
|
|
_ => {
|
|
println!("{HELP_MSG}");
|
|
true
|
|
}
|
|
}
|
|
} else {
|
|
println!("{HELP_MSG}");
|
|
true
|
|
}
|
|
} {}
|
|
}
|
|
}
|
|
});
|
|
|
|
#[allow(deprecated)]
|
|
let res = event_loop.run_app(&mut app);
|
|
match res {
|
|
Ok(_) => exit(0),
|
|
Err(e) => {
|
|
println!("{e}");
|
|
exit(1);
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
#[cfg(feature = "debug")]
|
|
const HELP_MSG: &str = "
|
|
step n - step trough n instructions (alias s)
|
|
run - run program until exit / error (alias r)
|
|
context - print context (alias c)
|
|
print n - print ram content at address n and next 8 (alias p)
|
|
up - run until the nex ret is reached (alias u)
|
|
to n - tun until PC = n (alias t)
|
|
";
|