Initial commit
I should've made a git repo for this much sooner, oops :p
This commit is contained in:
commit
9b21b663f4
1881
Cargo.lock
generated
Normal file
1881
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
14
Cargo.toml
Normal file
14
Cargo.toml
Normal file
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "fox32"
|
||||
version = "0.1.0"
|
||||
authors = ["ry"]
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
pixels = "0.9.0"
|
||||
rodio = "0.14.0"
|
||||
winit = "0.26"
|
||||
winit_input_helper = "0.11"
|
46
encoding.md
Normal file
46
encoding.md
Normal file
|
@ -0,0 +1,46 @@
|
|||
# Encoding
|
||||
```
|
||||
size instr . cond dest src <src> <dest>
|
||||
xx xxxxxx 0 xxx xx xx <8,16,32 bits> <8,16,32 bits>
|
||||
```
|
||||
|
||||
|
||||
# Size table
|
||||
If instruction doesn't allow variable sizes or size was not specified, set size bits to 0b11
|
||||
| | |
|
||||
| :--: | -------------- |
|
||||
| 0b00 | byte (8 bits) |
|
||||
| 0b01 | half (16 bits) |
|
||||
| 0b10 | word (32 bits) |
|
||||
|
||||
# Instruction table
|
||||
| 0x | -0 | -1 | -2 | -3 | -4 | -5 | -6 | -7 | -8 | -9 | -A | -B | -C | -D | -E | -F |
|
||||
| :-: | ---- | ------------- | ------------- | ------------- | ------------- | ------------- | ------------- | -------------- | ---- | ----- | -------------- | --- | --- | --- | --- | --- |
|
||||
| 0- | NOP | ADD[.8,16,32] | MUL[.8,16,32] | AND[.8,16,32] | SLA[.8,16,32] | SRA[.8,16,32] | BSE[.8,16,32] | CMP[.8,16,32] | JMP | RJMP | PUSH[.8,16,32] | IN | ISE | | | |
|
||||
| 1- | HALT | INC[.8,16,32] | POW[.8,16,32] | OR[.8,16,32] | | SRL[.8,16,32] | BCL[.8,16,32] | MOV[.8,16,32] | CALL | RCALL | POP[.8,16,32] | OUT | ICL | | | |
|
||||
| 2- | BRK | SUB[.8,16,32] | DIV[.8,16,32] | XOR[.8,16,32] | ROL[.8,16,32] | ROR[.8,16,32] | BTS[.8,16,32] | MOVZ[.8,16,32] | LOOP | RLOOP | RET | | | | | |
|
||||
| 3- | | DEC[.8,16,32] | REM[.8,16,32] | NOT[.8,16,32] | | | | | | RTA | RETI | | | | | |
|
||||
|
||||
# Condition table
|
||||
| | |
|
||||
| :---: | -------- |
|
||||
| 0b000 | (always) |
|
||||
| 0b001 | IFZ |
|
||||
| 0b010 | IFNZ |
|
||||
| 0b011 | IFC |
|
||||
| 0b100 | IFNC |
|
||||
|
||||
# Destination table
|
||||
| | |
|
||||
| :--: | ------------------- |
|
||||
| 0b00 | register |
|
||||
| 0b01 | register (pointer) |
|
||||
| 0b10 | immediate (pointer) |
|
||||
|
||||
# Source table
|
||||
| | |
|
||||
| :--: | ------------------- |
|
||||
| 0b00 | register |
|
||||
| 0b01 | register (pointer) |
|
||||
| 0b10 | immediate |
|
||||
| 0b11 | immediate (pointer) |
|
2953
src/cpu.rs
Normal file
2953
src/cpu.rs
Normal file
File diff suppressed because it is too large
Load Diff
421
src/main.rs
Normal file
421
src/main.rs
Normal file
|
@ -0,0 +1,421 @@
|
|||
// main.rs
|
||||
|
||||
pub mod cpu;
|
||||
pub mod mouse;
|
||||
use cpu::{Bus, CPU, Memory, Interrupt, IO};
|
||||
use mouse::Mouse;
|
||||
|
||||
use std::convert::TryInto;
|
||||
use std::env;
|
||||
use std::fs::read;
|
||||
//use std::process::exit;
|
||||
use std::sync::{Arc, mpsc, Mutex};
|
||||
use std::io::{stdout, Write};
|
||||
use std::thread;
|
||||
|
||||
use log::error;
|
||||
use pixels::{Pixels, SurfaceTexture};
|
||||
use winit::dpi::LogicalSize;
|
||||
use winit::event::{Event, VirtualKeyCode};
|
||||
use winit::event_loop::{ControlFlow, EventLoop};
|
||||
use winit::window::WindowBuilder;
|
||||
use winit_input_helper::WinitInputHelper;
|
||||
|
||||
use rodio::{OutputStream, buffer::SamplesBuffer, Sink};
|
||||
|
||||
const WIDTH: usize = 640;
|
||||
const HEIGHT: usize = 480;
|
||||
|
||||
pub struct Display {
|
||||
background: Vec<u8>,
|
||||
overlays: Arc<Mutex<Vec<Overlay>>>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
pub struct Overlay {
|
||||
enabled: bool,
|
||||
width: usize,
|
||||
height: usize,
|
||||
x: usize,
|
||||
y: usize,
|
||||
framebuffer_pointer: u32,
|
||||
}
|
||||
|
||||
impl IO for Bus {
|
||||
fn read_io(&mut self, port: u32) -> u32 {
|
||||
match port {
|
||||
0x02000000..=0x0200031F => { // overlay port
|
||||
let overlay_lock = self.memory.overlays.lock().unwrap();
|
||||
let overlay_number = (port & 0x000000FF) as usize;
|
||||
let setting = (port & 0x0000FF00) >> 8;
|
||||
|
||||
match setting {
|
||||
0x00 => {
|
||||
// we're reading the position of this overlay
|
||||
let x = overlay_lock[overlay_number].x as u32;
|
||||
let y = overlay_lock[overlay_number].y as u32;
|
||||
(y << 16) | x
|
||||
}
|
||||
0x01 => {
|
||||
// we're reading the size of this overlay
|
||||
let width = overlay_lock[overlay_number].width as u32;
|
||||
let height = overlay_lock[overlay_number].height as u32;
|
||||
(height << 16) | width
|
||||
}
|
||||
0x02 => {
|
||||
// we're reading the framebuffer pointer of this overlay
|
||||
overlay_lock[overlay_number].framebuffer_pointer + 0x02000000
|
||||
}
|
||||
0x03 => {
|
||||
// we're reading the enable status of this overlay
|
||||
overlay_lock[overlay_number].enabled as u32
|
||||
}
|
||||
_ => panic!("invalid overlay control port"),
|
||||
}
|
||||
}
|
||||
0x02000400..=0x02000401 => { // mouse port
|
||||
let setting = (port & 0x000000FF) as u8;
|
||||
match setting {
|
||||
0x00 => {
|
||||
// we're reading the button states
|
||||
let mut byte: u8 = 0x00;
|
||||
let mut mouse_lock = self.mouse.lock().expect("failed to lock the mouse mutex");
|
||||
if mouse_lock.click {
|
||||
byte |= 0b01;
|
||||
mouse_lock.click = false;
|
||||
}
|
||||
if mouse_lock.held {
|
||||
byte |= 0b10;
|
||||
} else {
|
||||
byte &= !0b10;
|
||||
}
|
||||
byte as u32
|
||||
}
|
||||
0x01 => {
|
||||
// we're reading the position
|
||||
let mouse_lock = self.mouse.lock().expect("failed to lock the mouse mutex");
|
||||
let x = mouse_lock.x as u32;
|
||||
let y = mouse_lock.y as u32;
|
||||
(y << 16) | x
|
||||
}
|
||||
_ => panic!("invalid mouse control port"),
|
||||
}
|
||||
}
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
fn write_io(&mut self, port: u32, word: u32) {
|
||||
match port {
|
||||
0x00000000 => { // terminal output port
|
||||
print!("{}", word as u8 as char);
|
||||
stdout().flush().expect("could not flush stdout");
|
||||
}
|
||||
0x02000000..=0x0200031F => { // overlay port
|
||||
let mut overlay_lock = self.memory.overlays.lock().unwrap();
|
||||
let overlay_number = (port & 0x000000FF) as usize;
|
||||
let setting = (port & 0x0000FF00) >> 8;
|
||||
|
||||
match setting {
|
||||
0x00 => {
|
||||
// we're setting the position of this overlay
|
||||
let x = (word & 0x0000FFFF) as usize;
|
||||
let y = ((word & 0xFFFF0000) >> 16) as usize;
|
||||
overlay_lock[overlay_number].x = x;
|
||||
overlay_lock[overlay_number].y = y;
|
||||
}
|
||||
0x01 => {
|
||||
// we're setting the size of this overlay
|
||||
let width = (word & 0x0000FFFF) as usize;
|
||||
let height = ((word & 0xFFFF0000) >> 16) as usize;
|
||||
overlay_lock[overlay_number].width = width;
|
||||
overlay_lock[overlay_number].height = height;
|
||||
}
|
||||
0x02 => {
|
||||
// we're setting the framebuffer pointer of this overlay
|
||||
if word < 0x02000000 {
|
||||
panic!("overlay framebuffer must be within shared memory");
|
||||
}
|
||||
overlay_lock[overlay_number].framebuffer_pointer = word - 0x02000000;
|
||||
}
|
||||
0x03 => {
|
||||
// we're setting the enable status of this overlay
|
||||
overlay_lock[overlay_number].enabled = word != 0;
|
||||
}
|
||||
_ => panic!("invalid overlay control port"),
|
||||
}
|
||||
}
|
||||
0x02000320 => { // audio port
|
||||
if word < 0x02000000 {
|
||||
panic!("audio buffer must be within shared memory");
|
||||
}
|
||||
let address = word as usize - 0x02000000;
|
||||
let shared_memory_lock = self.memory.shared_memory.lock().unwrap();
|
||||
|
||||
let length = u32::from_le_bytes(shared_memory_lock[address..address+4].try_into().unwrap()) as usize;
|
||||
let start_address = address + 4;
|
||||
let end_address = start_address + length;
|
||||
|
||||
let audio_data: Vec<i16> = shared_memory_lock[start_address..end_address].to_vec().chunks_exact(2).map(|x| ((x[1] as i16) << 8) | x[0] as i16).collect();
|
||||
let builder = thread::Builder::new().name("audio".to_string());
|
||||
builder.spawn({
|
||||
move || {
|
||||
let buffer = SamplesBuffer::new(1, 44100, audio_data);
|
||||
let (_stream, stream_handle) = OutputStream::try_default().unwrap();
|
||||
let sink = Sink::try_new(&stream_handle).unwrap();
|
||||
sink.append(buffer);
|
||||
sink.sleep_until_end();
|
||||
}
|
||||
}).unwrap();
|
||||
}
|
||||
0x02000400..=0x02000401 => { // mouse port
|
||||
let setting = (port & 0x000000FF) as u8;
|
||||
match setting {
|
||||
0x00 => {
|
||||
// we're setting the button states
|
||||
let mut mouse_lock = self.mouse.lock().expect("failed to lock the mouse mutex");
|
||||
mouse_lock.click = word & (1 << 0) != 0;
|
||||
mouse_lock.held = word & (1 << 1) != 0;
|
||||
}
|
||||
0x01 => {
|
||||
// we're setting the position
|
||||
let mut mouse_lock = self.mouse.lock().expect("failed to lock the mouse mutex");
|
||||
let x = (word & 0x0000FFFF) as u16;
|
||||
let y = ((word & 0xFFFF0000) >> 16) as u16;
|
||||
mouse_lock.x = x;
|
||||
mouse_lock.y = y;
|
||||
}
|
||||
_ => panic!("invalid mouse control port"),
|
||||
}
|
||||
}
|
||||
_ => return,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
/*if args.len() != 2 {
|
||||
println!("fox32\nUsage: {} <binary>", &args[0]);
|
||||
exit(1);
|
||||
}*/
|
||||
|
||||
let mut display = Display::new();
|
||||
let mouse = Arc::new(Mutex::new(Mouse::new()));
|
||||
|
||||
// 32 MiB of shared memory
|
||||
// add some "bonus bytes" at the end of memory to prevent dumb errors - (lua was here)
|
||||
// see the ImmediatePtr match arm in read_source() in cpu.rs for more info
|
||||
// basically all immediate pointers read 32 bits, even if the opcode size is smaller
|
||||
// so attempting to read something like the last byte of shared memory (0x03FFFFFF) would previously panic
|
||||
let shared_memory = Arc::new(Mutex::new(vec![0u8; 0x0200000F]));
|
||||
|
||||
let mut cpu = {
|
||||
let cpu_shared_memory = Arc::clone(&shared_memory);
|
||||
let cpu_overlays = Arc::clone(&display.overlays);
|
||||
//let cpu_read_only_memory = include_bytes!("../rom.bin").to_vec();
|
||||
let cpu_read_only_memory = read("rom.bin").unwrap();
|
||||
// 32 MiB of CPU-only memory
|
||||
let memory = Memory::new(0x02000000, cpu_shared_memory, cpu_overlays, cpu_read_only_memory);
|
||||
|
||||
let cpu_mouse = Arc::clone(&mouse);
|
||||
|
||||
let bus = Bus { memory, mouse: cpu_mouse };
|
||||
CPU::new(bus)
|
||||
};
|
||||
|
||||
if args.len() >= 2 {
|
||||
let input_file_name = &args[1];
|
||||
let opcodes = read(input_file_name).unwrap();
|
||||
//println!("{:02X?}", opcodes);
|
||||
let mut address = 0u32;
|
||||
for byte in opcodes.iter() {
|
||||
cpu.bus.memory.write_8(address, *byte);
|
||||
address += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let event_loop = EventLoop::new();
|
||||
let mut input = WinitInputHelper::new();
|
||||
let window = {
|
||||
let size = LogicalSize::new(WIDTH as f64, HEIGHT as f64);
|
||||
WindowBuilder::new()
|
||||
.with_title("fox32")
|
||||
.with_inner_size(size)
|
||||
.with_min_inner_size(size)
|
||||
.build(&event_loop)
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
window.set_cursor_visible(false);
|
||||
|
||||
let mut pixels = {
|
||||
let window_size = window.inner_size();
|
||||
let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, &window);
|
||||
Pixels::new(WIDTH as u32, HEIGHT as u32, surface_texture).unwrap()
|
||||
};
|
||||
|
||||
let (interrupt_sender, interrupt_receiver) = mpsc::channel::<Interrupt>();
|
||||
|
||||
let builder = thread::Builder::new().name("cpu".to_string());
|
||||
builder.spawn({
|
||||
move || {
|
||||
loop {
|
||||
while !cpu.halted {
|
||||
if let Ok(interrupt) = interrupt_receiver.try_recv() {
|
||||
cpu.interrupt(interrupt);
|
||||
}
|
||||
cpu.execute_memory_instruction();
|
||||
}
|
||||
if !cpu.interrupts_enabled {
|
||||
// the cpu was halted and interrupts are disabled
|
||||
// at this point, the cpu is dead and cannot resume, break out of the loop
|
||||
break;
|
||||
}
|
||||
if let Ok(interrupt) = interrupt_receiver.recv() {
|
||||
cpu.halted = false;
|
||||
cpu.interrupt(interrupt);
|
||||
} else {
|
||||
// sender is closed, break
|
||||
break;
|
||||
}
|
||||
}
|
||||
println!("CPU halted");
|
||||
}
|
||||
}).unwrap();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Poll;
|
||||
|
||||
// draw the current frame
|
||||
if let Event::MainEventsCleared = event {
|
||||
// update internal state and request a redraw
|
||||
|
||||
let mut shared_memory_lock = shared_memory.lock().expect("failed to lock the shared memory mutex");
|
||||
//shared_memory_lock[0x01FFFFFF] = shared_memory_lock[0x01FFFFFF].overflowing_add(1).0; // increment vsync counter
|
||||
let _ = interrupt_sender.send(Interrupt::Request(0xFF)); // vsync interrupt
|
||||
display.update(&mut *shared_memory_lock);
|
||||
drop(shared_memory_lock);
|
||||
window.request_redraw();
|
||||
|
||||
display.draw(pixels.get_frame());
|
||||
if pixels
|
||||
.render()
|
||||
.map_err(|e| error!("pixels.render() failed: {}", e))
|
||||
.is_err()
|
||||
{
|
||||
*control_flow = ControlFlow::Exit;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// handle input events
|
||||
if input.update(&event) {
|
||||
// close events
|
||||
if input.key_pressed(VirtualKeyCode::Escape) || input.quit() {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
return;
|
||||
}
|
||||
|
||||
// resize the window
|
||||
if let Some(size) = input.window_resized() {
|
||||
pixels.resize_surface(size.width, size.height);
|
||||
}
|
||||
|
||||
let mouse_pixel = input.mouse().map(|(mx, my)| {
|
||||
let (x, y) = pixels.window_pos_to_pixel((mx, my)).unwrap_or_else(|pos| pixels.clamp_pixel_pos(pos));
|
||||
(x as u16, y as u16)
|
||||
}).unwrap_or_default();
|
||||
|
||||
// TODO: allow right click
|
||||
let mut mouse_lock = mouse.lock().expect("failed to lock the mouse mutex");
|
||||
mouse_lock.x = mouse_pixel.0;
|
||||
mouse_lock.y = mouse_pixel.1;
|
||||
mouse_lock.held = input.mouse_held(0);
|
||||
if input.mouse_pressed(0) {
|
||||
println!("Mouse click at {:?}", mouse_pixel);
|
||||
mouse_lock.click = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
impl Display {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
background: vec![0; (HEIGHT*WIDTH*4) as usize],
|
||||
overlays: Arc::new(Mutex::new(vec![Overlay { enabled: false, width: 16, height: 16, x: 0, y: 0, framebuffer_pointer: 0 }; 32])),
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, shared_memory: &mut [u8]) {
|
||||
let overlay_lock = self.overlays.lock().unwrap();
|
||||
|
||||
for i in 0..(HEIGHT*WIDTH*4) as usize {
|
||||
self.background[i] = shared_memory[i];
|
||||
}
|
||||
|
||||
for index in 0..=31 {
|
||||
if overlay_lock[index].enabled {
|
||||
blit_overlay(&mut self.background, &overlay_lock[index], shared_memory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(&self, frame: &mut [u8]) {
|
||||
for (i, pixel) in frame.chunks_exact_mut(4).enumerate() {
|
||||
//let x = (i % WIDTH as usize) as i16;
|
||||
//let y = (i / WIDTH as usize) as i16;
|
||||
|
||||
let i = i * 4;
|
||||
|
||||
let slice = &self.background[i..i+4];
|
||||
pixel.copy_from_slice(slice);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// modified from https://github.com/parasyte/pixels/blob/main/examples/invaders/simple-invaders/src/sprites.rs
|
||||
fn blit_overlay(framebuffer: &mut [u8], overlay: &Overlay, shared_memory: &mut [u8]) {
|
||||
//assert!(overlay.x + overlay.width <= WIDTH);
|
||||
//assert!(overlay.y + overlay.height <= HEIGHT);
|
||||
|
||||
let mut width = overlay.width * 4;
|
||||
let mut height = overlay.height;
|
||||
|
||||
// FIXME: this is a hack, and it only allows overlays to go off-screen on the bottom and right sides
|
||||
// it also completely fucks up the image on the right side :p
|
||||
if overlay.x + overlay.width > WIDTH {
|
||||
let difference = (overlay.x + overlay.width) - WIDTH;
|
||||
width = width - (difference * 4);
|
||||
//println!("width: {}, difference: {}", width, difference);
|
||||
}
|
||||
if overlay.y + overlay.height > HEIGHT {
|
||||
let difference = (overlay.y + overlay.height) - HEIGHT;
|
||||
height = height - difference;
|
||||
//println!("height: {}, difference: {}", height, difference);
|
||||
}
|
||||
|
||||
let overlay_framebuffer = &shared_memory[overlay.framebuffer_pointer as usize..(overlay.framebuffer_pointer+((width as u32)*(height as u32))) as usize];
|
||||
|
||||
let mut overlay_framebuffer_index = 0;
|
||||
for y in 0..height {
|
||||
let index = overlay.x * 4 + overlay.y * WIDTH * 4 + y * WIDTH * 4;
|
||||
// merge overlay onto screen
|
||||
// this is such a dumb hack
|
||||
let mut zipped = framebuffer[index..index + width].iter_mut().zip(&overlay_framebuffer[overlay_framebuffer_index..overlay_framebuffer_index + width]);
|
||||
while let Some((left, &right)) = zipped.next() {
|
||||
let (left_0, &right_0) = (left, &right);
|
||||
let (left_1, &right_1) = zipped.next().unwrap();
|
||||
let (left_2, &right_2) = zipped.next().unwrap();
|
||||
let (left_3, &right_3) = zipped.next().unwrap();
|
||||
// ensure that the alpha byte is greater than zero, meaning that this pixel shouldn't be transparent
|
||||
if right_3 > 0 {
|
||||
*left_0 = right_0;
|
||||
*left_1 = right_1;
|
||||
*left_2 = right_2;
|
||||
*left_3 = right_3;
|
||||
}
|
||||
}
|
||||
overlay_framebuffer_index += width;
|
||||
}
|
||||
}
|
14
src/mouse.rs
Normal file
14
src/mouse.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
// mouse.rs
|
||||
|
||||
pub struct Mouse {
|
||||
pub x: u16,
|
||||
pub y: u16,
|
||||
pub click: bool,
|
||||
pub held: bool,
|
||||
}
|
||||
|
||||
impl Mouse {
|
||||
pub fn new() -> Self {
|
||||
Mouse { x: 0, y: 0, click: false, held: false }
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user