initial commit
This commit is contained in:
2394
Cargo.lock
generated
Normal file
2394
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
9
Cargo.toml
Normal file
9
Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "sim"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
pixels = "0.15.0"
|
||||||
|
winit = "0.30.13"
|
||||||
|
winit_input_helper = "0.17.0"
|
||||||
BIN
sim_rs.tar.xz
Normal file
BIN
sim_rs.tar.xz
Normal file
Binary file not shown.
330
src/cpu.rs
Normal file
330
src/cpu.rs
Normal file
@@ -0,0 +1,330 @@
|
|||||||
|
use std::{
|
||||||
|
hint::{likely, unlikely},
|
||||||
|
io::Read,
|
||||||
|
process::exit,
|
||||||
|
sync::{Mutex, atomic::AtomicU32},
|
||||||
|
thread::sleep,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
use pixels::Pixels;
|
||||||
|
|
||||||
|
enum Op2 {
|
||||||
|
Direct(u32),
|
||||||
|
Register(u8),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Reg(u8);
|
||||||
|
enum Cond {
|
||||||
|
Ifeq,
|
||||||
|
Ifne,
|
||||||
|
Iflt,
|
||||||
|
Ifge,
|
||||||
|
Ifgt,
|
||||||
|
Ifle,
|
||||||
|
Ifult,
|
||||||
|
Ifuge,
|
||||||
|
Ifugt,
|
||||||
|
Ifule,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for Cond {
|
||||||
|
fn from(value: u8) -> Self {
|
||||||
|
match value {
|
||||||
|
0b0000 => Cond::Ifeq,
|
||||||
|
0b0001 => Cond::Ifne,
|
||||||
|
0b1000 => Cond::Iflt,
|
||||||
|
0b1001 => Cond::Ifge,
|
||||||
|
0b1010 => Cond::Ifgt,
|
||||||
|
0b1011 => Cond::Ifle,
|
||||||
|
0b1100 => Cond::Ifult,
|
||||||
|
0b1101 => Cond::Ifuge,
|
||||||
|
0b1110 => Cond::Ifugt,
|
||||||
|
0b1111 => Cond::Ifule,
|
||||||
|
_ => {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cond {
|
||||||
|
fn eval(self, a: u32, b: u32) -> bool {
|
||||||
|
match self {
|
||||||
|
Cond::Ifeq => a == b,
|
||||||
|
Cond::Ifne => a != b,
|
||||||
|
Cond::Iflt => (a as i32) < (b as i32),
|
||||||
|
Cond::Ifge => (a as i32) >= (b as i32),
|
||||||
|
Cond::Ifgt => (a as i32) > (b as i32),
|
||||||
|
Cond::Ifle => (a as i32) <= (b as i32),
|
||||||
|
Cond::Ifult => a < b,
|
||||||
|
Cond::Ifuge => a >= b,
|
||||||
|
Cond::Ifugt => a > b,
|
||||||
|
Cond::Ifule => a <= b,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Instruction {
|
||||||
|
Copy(Reg, Op2),
|
||||||
|
Add(Reg, Reg, Op2),
|
||||||
|
Sub(Reg, Reg, Op2),
|
||||||
|
Or(Reg, Reg, Op2),
|
||||||
|
And(Reg, Reg, Op2),
|
||||||
|
Xor(Reg, Reg, Op2),
|
||||||
|
Lsl(Reg, Reg, Op2),
|
||||||
|
Lsr(Reg, Reg, Op2),
|
||||||
|
Asr(Reg, Reg, Op2),
|
||||||
|
Store(Reg, Op2, Reg),
|
||||||
|
Load(Reg, Reg, Op2),
|
||||||
|
Push(Op2),
|
||||||
|
Pop(Reg),
|
||||||
|
Skip(u8, Cond, Reg, Op2),
|
||||||
|
Jump(u32), //address / 4
|
||||||
|
Call(u32), //address / 4
|
||||||
|
Ret(),
|
||||||
|
GetStack(Reg),
|
||||||
|
SetStack(Op2),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u32> for Instruction {
|
||||||
|
fn from(value: u32) -> Self {
|
||||||
|
match value >> 30 {
|
||||||
|
0b00 => {
|
||||||
|
let t = value & (1 << 29); // 3rd bit set
|
||||||
|
let value = value - t;
|
||||||
|
let t = t != 0;
|
||||||
|
if t {
|
||||||
|
Self::Call(value)
|
||||||
|
} else {
|
||||||
|
Self::Jump(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt => {
|
||||||
|
let imediate = (value & (1 << 28)) != 0;
|
||||||
|
let s = (value & 1 << 27) != 0;
|
||||||
|
const MASK: u32 = 0b1111;
|
||||||
|
let opcode = (value >> 24) & MASK;
|
||||||
|
let rd = Reg(((value >> 20) & MASK) as u8);
|
||||||
|
let rx = Reg(((value >> 16) & MASK) as u8);
|
||||||
|
let op2 = {
|
||||||
|
if imediate {
|
||||||
|
let value = if s {
|
||||||
|
value | 0xFFFF0000
|
||||||
|
} else {
|
||||||
|
value & 0x0000FFFF
|
||||||
|
};
|
||||||
|
Op2::Direct(value)
|
||||||
|
} else {
|
||||||
|
Op2::Register((value >> 12 & MASK) as u8)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match (fmt, opcode) {
|
||||||
|
(1, 0b0000) => Self::Copy(rd, op2),
|
||||||
|
(1, 0b0001) => Self::Add(rd, rx, op2),
|
||||||
|
(1, 0b0010) => Self::Sub(rd, rx, op2),
|
||||||
|
(1, 0b0011) => Self::Or(rd, rx, op2),
|
||||||
|
(1, 0b0100) => Self::And(rd, rx, op2),
|
||||||
|
(1, 0b0101) => Self::Xor(rd, rx, op2),
|
||||||
|
(1, 0b0110) => Self::Lsl(rd, rx, op2),
|
||||||
|
(1, 0b0111) => Self::Lsr(rd, rx, op2),
|
||||||
|
(1, 0b1000) => Self::Asr(rd, rx, op2),
|
||||||
|
(2, 0b0000) => Self::Store(rx, op2, rd),
|
||||||
|
(2, 0b0001) => Self::Load(rd, rx, op2),
|
||||||
|
(2, 0b0010) => Self::Push(op2),
|
||||||
|
(2, 0b0011) => Self::Pop(rd),
|
||||||
|
(2, 0b1000) => Self::Ret(),
|
||||||
|
(2, 0b1101) => Self::GetStack(rd),
|
||||||
|
(2, 0b1110) => Self::SetStack(op2),
|
||||||
|
(3, skip) => Self::Skip(rd.0, (skip as u8).into(), rx, op2),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Computer<'a, 'b> {
|
||||||
|
ram: Box<[u32; 0x01000000 / 4]>,
|
||||||
|
regs: [u32; 16],
|
||||||
|
pc: usize,
|
||||||
|
sp: usize,
|
||||||
|
screen: &'b Mutex<Pixels<'a>>,
|
||||||
|
key: &'b AtomicU32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn iot() -> ! {
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> Computer<'a, 'b> {
|
||||||
|
pub fn new(filename: String, screen: &'b Mutex<Pixels<'a>>, key: &'b AtomicU32) -> Self {
|
||||||
|
let mut new = Self {
|
||||||
|
ram: Box::new([0; 0x01000000 / 4]),
|
||||||
|
regs: [0; 16],
|
||||||
|
pc: 0,
|
||||||
|
sp: 0,
|
||||||
|
screen,
|
||||||
|
key,
|
||||||
|
};
|
||||||
|
let mut buf = String::new();
|
||||||
|
std::fs::File::open(filename)
|
||||||
|
.unwrap()
|
||||||
|
.read_to_string(&mut buf)
|
||||||
|
.unwrap();
|
||||||
|
for (i, line) in buf.lines().enumerate() {
|
||||||
|
match u32::from_str_radix(line, 16) {
|
||||||
|
Ok(val) => new.ram[i] = val,
|
||||||
|
Err(_) => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
new
|
||||||
|
}
|
||||||
|
pub fn step(&mut self) {
|
||||||
|
let next_opcode = self.ram[self.pc];
|
||||||
|
match Instruction::from(next_opcode) {
|
||||||
|
Instruction::Copy(reg, op2) => {
|
||||||
|
self.rg_wr(reg, self.resolve(op2));
|
||||||
|
self.pc += 1;
|
||||||
|
}
|
||||||
|
Instruction::Add(reg, reg1, op2) => {
|
||||||
|
self.rg_wr(reg, self.rg_r(reg1) + self.resolve(op2));
|
||||||
|
self.pc += 1;
|
||||||
|
}
|
||||||
|
Instruction::Sub(reg, reg1, op2) => {
|
||||||
|
self.rg_wr(reg, self.rg_r(reg1) - self.resolve(op2));
|
||||||
|
self.pc += 1;
|
||||||
|
}
|
||||||
|
Instruction::Or(reg, reg1, op2) => {
|
||||||
|
self.rg_wr(reg, self.rg_r(reg1) | self.resolve(op2));
|
||||||
|
self.pc += 1;
|
||||||
|
}
|
||||||
|
Instruction::And(reg, reg1, op2) => {
|
||||||
|
self.rg_wr(reg, self.rg_r(reg1) & self.resolve(op2));
|
||||||
|
self.pc += 1;
|
||||||
|
}
|
||||||
|
Instruction::Xor(reg, reg1, op2) => {
|
||||||
|
self.rg_wr(reg, self.rg_r(reg1) ^ self.resolve(op2));
|
||||||
|
self.pc += 1;
|
||||||
|
}
|
||||||
|
Instruction::Lsl(reg, reg1, op2) => {
|
||||||
|
self.rg_wr(reg, self.rg_r(reg1) << self.resolve(op2));
|
||||||
|
self.pc += 1;
|
||||||
|
}
|
||||||
|
Instruction::Lsr(reg, reg1, op2) => {
|
||||||
|
self.rg_wr(reg, self.rg_r(reg1) >> self.resolve(op2));
|
||||||
|
self.pc += 1;
|
||||||
|
}
|
||||||
|
Instruction::Asr(reg, reg1, op2) => {
|
||||||
|
self.rg_wr(reg, (self.rg_r(reg1) as i32 >> self.resolve(op2)) as u32);
|
||||||
|
self.pc += 1;
|
||||||
|
}
|
||||||
|
Instruction::Store(reg, op2, reg1) => {
|
||||||
|
let addr = (self.rg_r(reg) + self.resolve(op2)) as usize;
|
||||||
|
if !addr.is_multiple_of(4) {
|
||||||
|
iot();
|
||||||
|
}
|
||||||
|
if addr <= 0x00ffffff {
|
||||||
|
self.ram[addr / 4] = self.rg_r(reg1);
|
||||||
|
} else if addr <= 0x00ffffff + 480 * 640 * 4 {
|
||||||
|
let addr_screen = addr - 0x01000000;
|
||||||
|
let ubgr = self.rg_r(reg1);
|
||||||
|
let rgba = [ubgr as u8, (ubgr >> 8) as u8, (ubgr >> 16) as u8, 0xff];
|
||||||
|
let mut pixels = self.screen.lock().unwrap();
|
||||||
|
let screen = pixels.frame_mut();
|
||||||
|
for i in 0..4 {
|
||||||
|
screen[addr_screen + i] = rgba[i];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
iot();
|
||||||
|
}
|
||||||
|
self.pc += 1;
|
||||||
|
}
|
||||||
|
Instruction::Load(reg, reg1, op2) => {
|
||||||
|
let addr = (self.rg_r(reg1) + self.resolve(op2)) as usize;
|
||||||
|
if !addr.is_multiple_of(4) {
|
||||||
|
iot();
|
||||||
|
}
|
||||||
|
self.rg_wr(
|
||||||
|
reg,
|
||||||
|
if addr <= 0x00ffffff {
|
||||||
|
self.ram[addr / 4]
|
||||||
|
} else if addr <= 0x00ffffff + 480 * 640 * 4 {
|
||||||
|
let pixels = self.screen.lock().unwrap();
|
||||||
|
let buffer = pixels.frame();
|
||||||
|
let mut res = 0;
|
||||||
|
let addr_screen = addr - 0x01000000;
|
||||||
|
for i in 0..3 {
|
||||||
|
res += (buffer[addr_screen + i] as u32) << (i as u32 * 8)
|
||||||
|
}
|
||||||
|
res
|
||||||
|
} else if addr == 0x01200000 {
|
||||||
|
self.key.load(std::sync::atomic::Ordering::Relaxed)
|
||||||
|
} else {
|
||||||
|
iot();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
self.pc += 1;
|
||||||
|
}
|
||||||
|
Instruction::Push(op2) => {
|
||||||
|
self.sp -= 1;
|
||||||
|
self.ram[self.sp] = self.resolve(op2);
|
||||||
|
self.pc += 1;
|
||||||
|
}
|
||||||
|
Instruction::Pop(reg) => {
|
||||||
|
self.rg_wr(reg, self.ram[self.sp]);
|
||||||
|
self.sp += 1;
|
||||||
|
self.pc += 1;
|
||||||
|
}
|
||||||
|
Instruction::Skip(d, cond, reg, op2) => {
|
||||||
|
self.pc += 1;
|
||||||
|
if cond.eval(self.rg_r(reg), self.resolve(op2)) {
|
||||||
|
self.pc += d as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Instruction::Jump(mut addr) => {
|
||||||
|
if addr & (1 << 28) != 0 {
|
||||||
|
addr += 7 << 29;
|
||||||
|
} else if unlikely(addr == 0) {
|
||||||
|
sleep(Duration::from_hours(1));
|
||||||
|
}
|
||||||
|
self.pc = (addr + self.pc as u32) as usize;
|
||||||
|
}
|
||||||
|
Instruction::Call(addr) => {
|
||||||
|
self.sp -= 1;
|
||||||
|
self.ram[self.sp] = ((self.pc << 2) + 4) as u32;
|
||||||
|
self.pc += addr as usize;
|
||||||
|
self.pc &= 0x3FFFFFFF; //wrapping on 30 bit pc
|
||||||
|
}
|
||||||
|
Instruction::Ret() => {
|
||||||
|
self.pc = (self.ram[self.sp] >> 2) as usize;
|
||||||
|
self.sp += 1;
|
||||||
|
}
|
||||||
|
Instruction::GetStack(reg) => {
|
||||||
|
self.rg_wr(reg, (self.sp << 2) as u32);
|
||||||
|
self.pc += 1;
|
||||||
|
}
|
||||||
|
Instruction::SetStack(op2) => {
|
||||||
|
let v = self.resolve(op2);
|
||||||
|
if likely(v.is_multiple_of(4)) {
|
||||||
|
self.sp = (v >> 2) as usize;
|
||||||
|
} else {
|
||||||
|
self.sp = usize::MAX //Yes, that means that clever program using sp to store information wont work on my emulator. Deal with it
|
||||||
|
}
|
||||||
|
self.pc += 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
fn resolve(&self, op2: Op2) -> u32 {
|
||||||
|
match op2 {
|
||||||
|
Op2::Direct(v) => v,
|
||||||
|
Op2::Register(r) => self.regs[r as usize],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn rg_wr(&mut self, reg: Reg, v: u32) {
|
||||||
|
self.regs[reg.0 as usize] = v
|
||||||
|
}
|
||||||
|
fn rg_r(&self, reg: Reg) -> u32 {
|
||||||
|
self.regs[reg.0 as usize]
|
||||||
|
}
|
||||||
|
}
|
||||||
139
src/main.rs
Normal file
139
src/main.rs
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
#![feature(likely_unlikely)]
|
||||||
|
#![deny(clippy::all)]
|
||||||
|
|
||||||
|
use std::env::args;
|
||||||
|
use std::process::exit;
|
||||||
|
use std::sync::atomic::AtomicU32;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::thread::{scope, sleep};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use pixels::{Error, Pixels, SurfaceTexture};
|
||||||
|
use winit::dpi::LogicalSize;
|
||||||
|
use winit::event::{Event, WindowEvent};
|
||||||
|
use winit::event_loop::EventLoop;
|
||||||
|
use winit::keyboard::KeyCode;
|
||||||
|
use winit::platform::scancode::PhysicalKeyExtScancode;
|
||||||
|
use winit::window::Window;
|
||||||
|
use winit_input_helper::WinitInputHelper;
|
||||||
|
|
||||||
|
use crate::cpu::Computer;
|
||||||
|
|
||||||
|
const WIDTH: u32 = 640;
|
||||||
|
const HEIGHT: u32 = 480;
|
||||||
|
|
||||||
|
mod cpu;
|
||||||
|
|
||||||
|
fn main() -> Result<(), Error> {
|
||||||
|
let event_loop = EventLoop::new().unwrap();
|
||||||
|
let mut input = WinitInputHelper::new();
|
||||||
|
let window = {
|
||||||
|
let size = LogicalSize::new((WIDTH * 3) as f64, (HEIGHT * 3) as f64);
|
||||||
|
#[allow(deprecated)]
|
||||||
|
Arc::new(
|
||||||
|
event_loop
|
||||||
|
.create_window(
|
||||||
|
Window::default_attributes()
|
||||||
|
.with_title("bisare screen")
|
||||||
|
.with_inner_size(size)
|
||||||
|
.with_min_inner_size(size),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let pixels = Mutex::new({
|
||||||
|
let window_size = window.inner_size();
|
||||||
|
let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, &window);
|
||||||
|
Pixels::new(WIDTH, HEIGHT, surface_texture)?
|
||||||
|
});
|
||||||
|
|
||||||
|
let keyboard = AtomicU32::new(0);
|
||||||
|
let program = args()
|
||||||
|
.nth(1)
|
||||||
|
.expect("you must supply the exec name as the first argument");
|
||||||
|
let kbref = &keyboard;
|
||||||
|
let pixelref = &pixels;
|
||||||
|
scope(|sc| {
|
||||||
|
sc.spawn(|| {
|
||||||
|
let mut simulation = Computer::new(program, pixelref, kbref);
|
||||||
|
loop {
|
||||||
|
simulation.step();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
#[allow(deprecated)]
|
||||||
|
let res = event_loop.run(|event, elwt| {
|
||||||
|
match event {
|
||||||
|
Event::Resumed => {}
|
||||||
|
Event::NewEvents(_) => input.step(),
|
||||||
|
Event::AboutToWait => input.end_step(),
|
||||||
|
Event::DeviceEvent { event, .. } => {
|
||||||
|
input.process_device_event(&event);
|
||||||
|
}
|
||||||
|
Event::WindowEvent { event, .. } => {
|
||||||
|
// Draw the current frame
|
||||||
|
if event == WindowEvent::RedrawRequested {
|
||||||
|
if let Err(_) = pixels.lock().unwrap().render() {
|
||||||
|
elwt.exit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sleep(Duration::from_millis(30));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let WindowEvent::KeyboardInput {
|
||||||
|
device_id: _,
|
||||||
|
ref event,
|
||||||
|
is_synthetic: _,
|
||||||
|
} = event
|
||||||
|
{
|
||||||
|
match event.state {
|
||||||
|
winit::event::ElementState::Pressed => {
|
||||||
|
if let Some(val) = event.physical_key.to_scancode() {
|
||||||
|
println!("key with code {val} was pressed");
|
||||||
|
kbref.store(val, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
winit::event::ElementState::Released => {
|
||||||
|
println!("key released");
|
||||||
|
kbref.store(0, std::sync::atomic::Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle input events
|
||||||
|
if input.process_window_event(&event) {
|
||||||
|
// Close events
|
||||||
|
if input.close_requested() {
|
||||||
|
elwt.exit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize the window
|
||||||
|
if let Some(size) = input.window_resized() {
|
||||||
|
if let Err(_) = pixels
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.resize_surface(size.width, size.height)
|
||||||
|
{
|
||||||
|
elwt.exit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update internal state and request a redraw
|
||||||
|
window.request_redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
match res {
|
||||||
|
Ok(_) => exit(0),
|
||||||
|
Err(e) => {
|
||||||
|
println!("{e}");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user