initial commit

This commit is contained in:
ipc 2023-08-07 23:14:18 +02:00
commit a1fa4a16e2
20 changed files with 3539 additions and 0 deletions

2296
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

15
Cargo.toml Normal file
View File

@ -0,0 +1,15 @@
[package]
name = "ive"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1.0.72"
byteorder = "1.4.3"
egui = "0.22.0"
egui-wgpu = "0.22.0"
egui-winit = "0.22.0"
env_logger = "0.10.0"
pixels = "0.13.0"
winit = "0.28.6"
winit_input_helper = "0.14.1"

19
LICENSE Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2023 ipc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

5
README.md Normal file
View File

@ -0,0 +1,5 @@
# ive
risc-v emulator with display
![ive](img/ive.png)

9
give/.cargo/config.toml Normal file
View File

@ -0,0 +1,9 @@
[build]
target = "riscv32i-unknown-none-elf"
[profile.dev]
panic = "abort"
[profile.release]
opt-level = 1
panic = "abort"

6
give/Cargo.toml Normal file
View File

@ -0,0 +1,6 @@
[package]
name = "give"
version = "0.1.0"
edition = "2021"
[dependencies]

4
give/README.md Normal file
View File

@ -0,0 +1,4 @@
# give
give is a library for interfacing with the ive display. To use give
you must have the riscv32i-unknown-none-elf toolchain installed.

View File

@ -0,0 +1,9 @@
[build]
target = "riscv32i-unknown-none-elf"
[profile.dev]
panic = "abort"
[profile.release]
opt-level = 1
panic = "abort"

14
give/examples/grid/Cargo.lock generated Normal file
View File

@ -0,0 +1,14 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "give"
version = "0.1.0"
[[package]]
name = "grid"
version = "0.1.0"
dependencies = [
"give",
]

View File

@ -0,0 +1,7 @@
[package]
name = "grid"
version = "0.1.0"
edition = "2021"
[dependencies.give]
path = "../../"

Binary file not shown.

View File

@ -0,0 +1,10 @@
ENTRY(_start);
SECTIONS
{
. = 0x80000000;
.text : { *(.text .text.*) }
. = 0x0;
.data : { *(.data) }
.bss : { *(.bss) }
}

View File

@ -0,0 +1,50 @@
#![no_std]
#![no_main]
fn draw_grid(grid: &[u8], data: &[u8], w: u32, h: u32, x: u32, y: u32) {
for (i, c) in grid.iter().enumerate() {
let cx = (x + (i as u32 % w)) * 16;
let cy = (y + (i as u32 / h)) * 16;
match c {
0 => give::draw_rect_color(0x000000ff, cx, cy),
1 => give::draw_rect(data.as_ptr(), cx, cy),
_ => unreachable!(),
}
}
}
#[no_mangle]
pub extern "C" fn _start() -> ! {
let tile = include_bytes!("../data/tile");
#[rustfmt::skip]
let formation = [
0, 1, 0,
1, 0, 1,
0, 1, 0,
1, 1, 1,
1, 0, 1,
1, 1, 1,
1, 0, 1,
0, 1, 0,
1, 0, 1,
];
let mut n = 0;
let mut timer = 0;
loop {
draw_grid(&formation[n * 9..(n * 9) + 9], tile, 3, 3, 400, 300);
timer += 1;
if timer == 16 {
n += 1;
timer = 0;
}
if n == 3 {
n = 0;
}
give::redraw();
}
}

42
give/src/lib.rs Normal file
View File

@ -0,0 +1,42 @@
#![no_std]
use core::panic::PanicInfo;
use core::ptr;
const CTRL_DRAW_ADDR: *mut u32 = 0x60000000 as *mut u32;
const DRAW_ADDR: *mut u32 = 0x60000001 as *mut u32;
pub const TILE_SIZE: usize = 16;
#[panic_handler]
pub fn panic(_panic: &PanicInfo<'_>) -> ! {
loop {}
}
extern "C" fn ctrl_draw_write(val: u32) {
unsafe {
ptr::write_volatile(CTRL_DRAW_ADDR, val);
}
}
extern "C" fn draw_write(val: u32) {
unsafe {
ptr::write_volatile(DRAW_ADDR, val);
}
}
pub extern "C" fn redraw() {
ctrl_draw_write(1);
}
pub extern "C" fn draw_rect_color(color: u32, x: u32, y: u32) {
draw_write(0);
draw_write((x << 16) | y);
draw_write(color);
}
pub extern "C" fn draw_rect(data: *const u8, x: u32, y: u32) {
draw_write(1);
draw_write((x << 16) | y);
draw_write(data as u32);
}

BIN
img/ive.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

417
src/cpu.rs Normal file
View File

@ -0,0 +1,417 @@
use crate::gpu::{Gpu, Primitive};
use crate::{elf, Memory, Store};
use byteorder::{ByteOrder, LittleEndian};
pub const RAM_BASE: u32 = 0x80000000;
const RAM_SIZE: u32 = RAM_BASE + 0x100000;
const GPU_BASE: u32 = 0x60000000;
const GPU_SIZE: u32 = GPU_BASE + 0x1;
#[derive(Debug)]
enum Format {
R,
I,
IL,
S,
B,
J,
JI,
U,
UI,
E,
}
enum Opcode {
Add,
Sub,
Xor,
Or,
And,
Sll,
Srl,
Sra,
Slt,
Sltu,
Addi,
Xori,
Ori,
Andi,
Slli,
Srli,
Srai,
Slti,
Sltiu,
Lb,
Lh,
Lw,
Lbu,
Lhu,
Sb,
Sh,
Sw,
Beq,
Bne,
Blt,
Bge,
Bltu,
Bgeu,
Jal,
Jalr,
Lui,
Auipc,
Ecall,
Ebreak,
}
struct Ram {
mem: Vec<u8>,
}
struct Bus {
ram: Box<dyn Memory>,
gpu: Gpu,
}
pub struct Cpu {
pub reg: [u32; 31],
pub pc: u32,
bus: Bus,
}
struct Instruction {
op: Format,
rd: u8,
f3: u8,
f7: u8,
rs1: u8,
rs2: u8,
imm: u32,
simm: u32,
uimm: u32,
bimm: u32,
jimm: u32,
}
impl Ram {
fn new() -> Self {
let mut mem = Vec::new();
mem.resize((RAM_SIZE - RAM_BASE) as usize, 0);
Self { mem }
}
}
impl Memory for Ram {
fn read(&self, addr: u32) -> u32 {
let addr = addr as usize;
// should check for unaligned reads
LittleEndian::read_u32(&self.mem[addr..addr + 4])
}
fn write(&mut self, addr: u32, val: Store) {
let addr = addr as usize;
match val {
Store::Byte(b) => self.mem[addr] = b,
Store::Half(h) => LittleEndian::write_u16(&mut self.mem[addr..addr + 2], h),
Store::Word(w) => LittleEndian::write_u32(&mut self.mem[addr..addr + 4], w),
}
}
}
impl Format {
fn new(ins: u32) -> Self {
match ins & 0x7f {
0x33 => Self::R,
0x13 => Self::I,
0x03 => Self::IL,
0x23 => Self::S,
0x63 => Self::B,
0x6f => Self::J,
0x67 => Self::JI,
0x37 => Self::U,
0x17 => Self::UI,
0x73 => Self::E,
_ => panic!(),
}
}
}
macro_rules! op {
($op: ident) => {
Instruction {
op: Format::$op,
..
}
};
}
macro_rules! f3f7 {
($op: ident, $f3: expr, $f7: expr) => {
Instruction {
op: Format::$op,
f3: $f3,
f7: $f7,
..
}
};
}
macro_rules! f3 {
($op: ident, $f3: expr) => {
Instruction {
op: Format::$op,
f3: $f3,
..
}
};
}
impl Opcode {
fn new(ins: &Instruction) -> Self {
match ins {
f3f7!(R, 0x00, 0x00) => Self::Add,
f3f7!(R, 0x00, 0x20) => Self::Sub,
f3f7!(R, 0x04, 0x00) => Self::Xor,
f3f7!(R, 0x06, 0x00) => Self::Or,
f3f7!(R, 0x07, 0x00) => Self::And,
f3f7!(R, 0x01, 0x00) => Self::Sll,
f3f7!(R, 0x05, 0x00) => Self::Srl,
f3f7!(R, 0x05, 0x20) => Self::Sra,
f3f7!(R, 0x02, 0x00) => Self::Slt,
f3f7!(R, 0x03, 0x00) => Self::Sltu,
f3!(I, 0x00) => Self::Addi,
f3!(I, 0x04) => Self::Xori,
f3!(I, 0x06) => Self::Ori,
f3!(I, 0x07) => Self::Andi,
f3!(I, 0x01) => Self::Slli,
f3f7!(I, 0x05, 0x00) => Self::Srli,
f3f7!(I, 0x05, 0x20) => Self::Srai,
f3!(I, 0x02) => Self::Slti,
f3!(I, 0x03) => Self::Sltiu,
f3!(IL, 0x00) => Self::Lb,
f3!(IL, 0x01) => Self::Lh,
f3!(IL, 0x02) => Self::Lw,
f3!(IL, 0x04) => Self::Lbu,
f3!(IL, 0x05) => Self::Lhu,
f3!(S, 0x00) => Self::Sb,
f3!(S, 0x01) => Self::Sh,
f3!(S, 0x02) => Self::Sw,
f3!(B, 0x00) => Self::Beq,
f3!(B, 0x01) => Self::Bne,
f3!(B, 0x04) => Self::Blt,
f3!(B, 0x05) => Self::Bge,
f3!(B, 0x06) => Self::Bltu,
f3!(B, 0x07) => Self::Bgeu,
op!(J) => Self::Jal,
f3!(JI, 0x0) => Self::Jalr,
op!(U) => Self::Lui,
op!(UI) => Self::Auipc,
op!(E) => match ins.imm {
0x0 => Self::Ecall,
_ => Self::Ebreak,
},
_ => panic!("bad instruction"),
}
}
}
impl Instruction {
fn new(ins: u32) -> Self {
Self {
op: Format::new(ins),
rd: ((ins >> 7) & 0x1f) as u8,
f3: ((ins >> 12) & 0x7) as u8,
f7: ((ins >> 25) & 0x7f) as u8,
rs1: ((ins >> 15) & 0x1f) as u8,
rs2: ((ins >> 20) & 0x1f) as u8,
imm: ((ins & 0xfff00000) as i64 as i32 >> 20) as u32,
simm: (((ins & 0xfe000000) as i64 as i32 >> 20) as u32 | ((ins >> 7) & 0x1f)),
uimm: ins >> 12,
bimm: ((ins & 0x80000000) as i64 as i32 >> 19) as u32
| ((ins & 0x80) << 4)
| ((ins >> 20) & 0x7e0)
| ((ins >> 7) & 0x1e),
jimm: ((ins & 0x80000000) as i64 as i32 >> 11) as u32
| (ins & 0xff000)
| ((ins >> 9) & 0x800)
| ((ins >> 20) & 0x7fe),
}
}
fn exec_r(&self, cpu: &mut Cpu, op: fn(u32, u32) -> u32) {
let rs1 = cpu.reg_read(self.rs1);
let rs2 = cpu.reg_read(self.rs2);
cpu.reg_write(self.rd, op(rs1, rs2));
}
fn exec_i(&self, cpu: &mut Cpu, op: fn(u32, u32) -> u32) {
let rs1 = cpu.reg_read(self.rs1);
cpu.reg_write(self.rd, op(rs1, self.imm));
}
fn exec_il(&self, cpu: &mut Cpu, op: fn(u32) -> u32) {
let rs1 = cpu.reg_read(self.rs1);
let load = cpu.read(rs1.wrapping_add_signed(self.imm as i32));
cpu.reg_write(self.rd, op(load));
}
fn exec_s(&self, cpu: &mut Cpu, op: fn(u32) -> Store) {
let rs1 = cpu.reg_read(self.rs1);
let rs2 = cpu.reg_read(self.rs2);
cpu.write(rs1.wrapping_add_signed(self.simm as i32), op(rs2))
}
fn exec_b(&self, cpu: &mut Cpu, op: fn(u32, u32) -> bool) {
let rs1 = cpu.reg_read(self.rs1);
let rs2 = cpu.reg_read(self.rs2);
if op(rs1, rs2) {
cpu.pc = cpu.pc.wrapping_add_signed(self.bimm as i32) - 4;
}
}
fn execute(&self, cpu: &mut Cpu) {
match Opcode::new(self) {
Opcode::Add => self.exec_r(cpu, |r1, r2| r1.wrapping_add_signed(r2 as i32)),
Opcode::Sub => self.exec_r(cpu, |r1, r2| r1.wrapping_sub(r2)),
Opcode::Xor => self.exec_r(cpu, |r1, r2| r1 ^ r2),
Opcode::Or => self.exec_r(cpu, |r1, r2| r1 | r2),
Opcode::And => self.exec_r(cpu, |r1, r2| r1 & r2),
Opcode::Sll => self.exec_r(cpu, |r1, r2| r1.wrapping_shl(r2)),
Opcode::Srl => self.exec_r(cpu, |r1, r2| r1.wrapping_shr(r2)),
Opcode::Sra => self.exec_r(cpu, |r1, r2| r1.wrapping_shr(r2)),
Opcode::Slt => self.exec_r(cpu, |r1, r2| (r1 < (r2 as i32) as u32) as u32),
Opcode::Sltu => self.exec_r(cpu, |r1, r2| (r1 < r2) as u32),
Opcode::Addi => self.exec_i(cpu, |r1, imm| r1.wrapping_add_signed(imm as i32)),
Opcode::Xori => self.exec_i(cpu, |r1, imm| r1 ^ imm),
Opcode::Ori => self.exec_i(cpu, |r1, imm| r1 | imm),
Opcode::Andi => self.exec_i(cpu, |r1, imm| r1 & imm),
Opcode::Slli => self.exec_i(cpu, |r1, imm| r1.wrapping_shl(imm & 0x1f)),
Opcode::Srli => self.exec_i(cpu, |r1, imm| r1.wrapping_shr(imm & 0x1f)),
Opcode::Srai => self.exec_i(cpu, |r1, imm| r1.wrapping_shr(imm & 0x1f) as i32 as u32),
Opcode::Slti => self.exec_i(cpu, |r1, imm| (r1 < (imm as i32) as u32) as u32),
Opcode::Sltiu => self.exec_i(cpu, |r1, imm| (r1 < imm) as u32),
Opcode::Lb => self.exec_il(cpu, |imm| imm as u8 as u32),
Opcode::Lh => self.exec_il(cpu, |imm| imm as u16 as u32),
Opcode::Lw => self.exec_il(cpu, |imm| imm),
Opcode::Lbu => self.exec_il(cpu, |imm| imm as u8 as u32),
Opcode::Lhu => self.exec_il(cpu, |imm| imm as u16 as u32),
Opcode::Sb => self.exec_s(cpu, |r1| Store::Byte(r1 as u8)),
Opcode::Sh => self.exec_s(cpu, |r1| Store::Half(r1 as u16)),
Opcode::Sw => self.exec_s(cpu, Store::Word),
Opcode::Beq => self.exec_b(cpu, |r1, r2| (r1 as i32) == (r2 as i32)),
Opcode::Bne => self.exec_b(cpu, |r1, r2| (r1 as i32) != (r2 as i32)),
Opcode::Blt => self.exec_b(cpu, |r1, r2| (r1 as i32) < (r2 as i32)),
Opcode::Bge => self.exec_b(cpu, |r1, r2| (r1 as i32) >= (r2 as i32)),
Opcode::Bltu => self.exec_b(cpu, |r1, r2| r1 < r2),
Opcode::Bgeu => self.exec_b(cpu, |r1, r2| r1 >= r2),
Opcode::Jal => {
cpu.reg_write(self.rd, cpu.pc);
cpu.pc = cpu.pc.wrapping_add_signed(self.jimm as i32) - 4;
}
Opcode::Jalr => {
let pc = cpu.pc;
let rs1 = cpu.reg_read(self.rs1);
cpu.pc = rs1.wrapping_add_signed(self.imm as i32);
cpu.reg_write(self.rd, pc);
}
Opcode::Lui => cpu.reg_write(self.rd, self.uimm << 12),
Opcode::Auipc => cpu.reg_write(self.rd, cpu.pc.wrapping_add(self.uimm << 12) - 4),
Opcode::Ecall | Opcode::Ebreak => (),
}
}
}
impl Memory for Bus {
fn read(&self, addr: u32) -> u32 {
match addr {
RAM_BASE..=RAM_SIZE => self.ram.read(addr - RAM_BASE),
GPU_BASE..=GPU_SIZE => self.gpu.read(addr - GPU_BASE),
_ => panic!(),
}
}
fn write(&mut self, addr: u32, val: Store) {
match addr {
RAM_BASE..=RAM_SIZE => self.ram.write(addr - RAM_BASE, val),
GPU_BASE..=GPU_SIZE => self.gpu.write(addr - GPU_BASE, val),
_ => panic!(),
}
}
}
impl Cpu {
pub fn new(mem: Vec<u8>) -> Result<Self, elf::Error> {
let mut reg: [u32; 31] = [0; 31];
let mut ram = Ram::new();
// set sp to point to top of ram
reg[2] = RAM_SIZE;
let pc = elf::load(mem, &mut ram.mem)?;
Ok(Self {
reg,
pc,
bus: Bus {
ram: Box::new(ram),
gpu: Gpu::new(),
},
})
}
pub fn read(&self, addr: u32) -> u32 {
self.bus.read(addr)
}
fn write(&mut self, addr: u32, val: Store) {
self.bus.write(addr, val);
}
fn reg_read(&mut self, reg: u8) -> u32 {
let reg = reg as usize;
if reg > self.reg.len() {
panic!("invalid register read");
}
self.reg[reg]
}
fn reg_write(&mut self, reg: u8, val: u32) {
let reg = reg as usize;
if reg > self.reg.len() {
panic!("invalid register write");
}
self.reg[reg] = val;
self.reg[0] = 0
}
pub fn gpu_queue(&self) -> &Vec<Primitive> {
&self.bus.gpu.queue
}
pub fn gpu_clear(&mut self) {
self.bus.gpu.update = false;
self.bus.gpu.queue.clear();
}
pub fn update(&mut self) -> bool {
self.bus.gpu.update
}
pub fn step(&mut self) -> bool {
let inst = Instruction::new(self.read(self.pc));
self.pc += 4;
inst.execute(self);
if self.pc == 0 {
return false;
}
true
}
}

116
src/elf.rs Normal file
View File

@ -0,0 +1,116 @@
use crate::cpu::RAM_BASE;
use byteorder::{LittleEndian, ReadBytesExt};
use std::io::Cursor;
use std::io::Read;
const MAGIC: [u8; 4] = [0x7f, 0x45, 0x4c, 0x46];
const LOAD: u32 = 1;
#[derive(Debug)]
pub enum Error {
Eof,
BadMagic,
BadType,
BadMachine,
}
struct Phdr {
ptype: u32,
offset: u32,
vaddr: u32,
memsz: u32,
}
fn read_u16<R: Read>(buf: &mut R) -> u16 {
buf.read_u16::<LittleEndian>().unwrap()
}
fn read_u32<R: Read>(buf: &mut R) -> u32 {
buf.read_u32::<LittleEndian>().unwrap()
}
impl Phdr {
fn new<R: Read>(buf: &mut R) -> Self {
let ptype = read_u32(buf);
let offset = read_u32(buf);
let vaddr = read_u32(buf);
for _ in 0..2 {
read_u32(buf);
}
let memsz = read_u32(buf);
for _ in 0..2 {
read_u32(buf);
}
Self {
ptype,
offset,
vaddr,
memsz,
}
}
}
pub fn load(elf: Vec<u8>, mem: &mut [u8]) -> Result<u32, Error> {
let mut rdr = Cursor::new(elf);
let mut ident: [u8; 16] = [0; 16];
if rdr.read_exact(&mut ident).is_err() {
return Err(Error::Eof);
}
if ident[..MAGIC.len()] != MAGIC {
return Err(Error::BadMagic);
}
let etype = read_u16(&mut rdr);
if etype != 0x2 {
return Err(Error::BadType);
}
let machine = read_u16(&mut rdr);
if machine != 0xf3 {
return Err(Error::BadMachine);
}
// version
read_u32(&mut rdr);
let entry = read_u32(&mut rdr);
let phoff = read_u32(&mut rdr);
// shoff
read_u32(&mut rdr);
// flags
read_u32(&mut rdr);
// ehsize
read_u16(&mut rdr);
// phentsize
read_u16(&mut rdr);
let phnum = read_u16(&mut rdr);
rdr.set_position(phoff as u64);
for _ in 0..phnum {
let phdr = Phdr::new(&mut rdr);
if phdr.ptype != LOAD {
continue;
}
let vaddr = (phdr.vaddr - RAM_BASE) as usize;
let memsz = phdr.memsz as usize;
let pos = rdr.position();
rdr.set_position(phdr.offset as u64);
if rdr.read_exact(&mut mem[vaddr..vaddr + memsz]).is_err() {
return Err(Error::Eof);
}
rdr.set_position(pos);
}
Ok(entry)
}

137
src/gpu.rs Normal file
View File

@ -0,0 +1,137 @@
use crate::{Memory, Store};
enum Command {
Control,
Draw,
}
#[derive(PartialEq)]
enum DrawCommand {
Rect,
TexturedRect,
Idle,
}
pub enum Primitive {
Rect(u32, u32),
TexturedRect(u32, u32),
}
struct Packet {
cmd: DrawCommand,
len: u32,
count: u32,
args: [u32; 2],
}
pub struct Gpu {
pub update: bool,
packet: Packet,
pub queue: Vec<Primitive>,
}
impl Command {
fn new(addr: u32) -> Self {
match addr {
0x0 => Self::Control,
0x1 => Self::Draw,
_ => panic!(),
}
}
}
impl DrawCommand {
fn new(cmd: u32) -> Self {
match cmd {
0x0 => Self::Rect,
0x1 => Self::TexturedRect,
_ => panic!(),
}
}
}
impl Primitive {
fn new(packet: &Packet) -> Self {
let args = packet.args;
match packet.cmd {
DrawCommand::Rect => Self::Rect(args[0], args[1]),
DrawCommand::TexturedRect => Self::TexturedRect(args[0], args[1]),
_ => unreachable!(),
}
}
}
impl Packet {
pub fn new(cmd: DrawCommand) -> Self {
let len = match cmd {
DrawCommand::Rect | DrawCommand::TexturedRect => 2,
DrawCommand::Idle => 0,
};
Self {
cmd,
len,
count: 0,
args: [0; 2],
}
}
pub fn idle(&self) -> bool {
self.cmd == DrawCommand::Idle
}
fn reset(&mut self) {
self.cmd = DrawCommand::Idle;
self.len = 0;
}
fn transfer(&self) -> bool {
self.len > 0 && self.count == self.len
}
fn read(&mut self, queue: &mut Vec<Primitive>, arg: u32) {
self.args[self.count as usize] = arg;
self.count += 1;
if self.transfer() {
queue.push(Primitive::new(self));
self.reset();
}
}
}
impl Gpu {
pub fn new() -> Self {
Self {
update: false,
packet: Packet::new(DrawCommand::Idle),
queue: vec![],
}
}
}
impl Memory for Gpu {
fn read(&self, addr: u32) -> u32 {
panic!("attempted to read at {:#08x?}", addr);
}
fn write(&mut self, addr: u32, val: Store) {
if let Store::Word(w) = val {
match Command::new(addr) {
Command::Control => {
self.update = true;
}
Command::Draw => {
if self.packet.idle() {
self.packet = Packet::new(DrawCommand::new(w));
} else {
self.packet.read(&mut self.queue, w);
}
}
}
} else {
panic!("attempted to store {:#?}", val);
}
}
}

372
src/lib.rs Normal file
View File

@ -0,0 +1,372 @@
use anyhow::{bail, Result};
use egui::{ClippedPrimitive, Context, TexturesDelta};
use egui_wgpu::renderer::{Renderer, ScreenDescriptor};
use pixels::{wgpu, Pixels, PixelsContext, SurfaceTexture};
use std::fs;
use std::io::Read;
use winit::dpi::LogicalSize;
use winit::event::{Event, VirtualKeyCode};
use winit::event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget};
use winit::window::{Window, WindowBuilder};
use winit_input_helper::WinitInputHelper;
mod cpu;
mod elf;
mod gpu;
use crate::cpu::Cpu;
use crate::gpu::Primitive;
const FB_WIDTH: u32 = 800;
const FB_HEIGHT: u32 = 600;
const WINDOW_WIDTH: u32 = 1024;
const WINDOW_HEIGHT: u32 = 768;
const TILE_SIZE: u32 = 16;
#[derive(Debug)]
enum Store {
Byte(u8),
Half(u16),
Word(u32),
}
enum Order {
Lsb,
Msb,
}
trait Memory {
fn read(&self, addr: u32) -> u32;
fn write(&mut self, addr: u32, val: Store);
}
struct Gui {
window_open: bool,
gpu_log: Vec<String>,
}
struct State {
egui_ctx: Context,
egui_state: egui_winit::State,
screen_descriptor: ScreenDescriptor,
renderer: Renderer,
paint_jobs: Vec<ClippedPrimitive>,
textures: TexturesDelta,
gui: Gui,
cpu: Cpu,
}
impl Gui {
fn new() -> Self {
Self {
window_open: true,
gpu_log: Vec::new(),
}
}
fn ui(&mut self, ctx: &Context, cpu: &Cpu) {
egui::Window::new("regs")
.open(&mut self.window_open)
.show(ctx, |ui| {
ui.horizontal(|ui| {
ui.monospace("pc:");
ui.monospace(format!("{:#08x}", cpu.pc));
});
for (i, reg) in cpu.reg.iter().enumerate() {
ui.horizontal(|ui| {
ui.monospace(format!("r{}:", i));
ui.monospace(format!("{:#08x}", reg));
});
}
});
egui::Window::new("gpu")
.open(&mut self.window_open)
.constrain(true)
.show(ctx, |ui| {
let mut log = self.gpu_log.join("\n");
egui::ScrollArea::vertical()
.stick_to_bottom(true)
.show(ui, |ui| {
ui.add(egui::TextEdit::multiline(&mut log).code_editor())
});
});
}
}
impl State {
fn new<T>(
event_loop: &EventLoopWindowTarget<T>,
width: u32,
height: u32,
scale_factor: f32,
pixels: &Pixels,
cpu: Cpu,
) -> Self {
let max_texture_size = pixels.device().limits().max_texture_dimension_2d as usize;
let egui_ctx = Context::default();
let mut egui_state = egui_winit::State::new(event_loop);
egui_state.set_max_texture_side(max_texture_size);
egui_state.set_pixels_per_point(scale_factor);
let screen_descriptor = ScreenDescriptor {
size_in_pixels: [width, height],
pixels_per_point: scale_factor,
};
let renderer = Renderer::new(pixels.device(), pixels.render_texture_format(), None, 1);
let textures = TexturesDelta::default();
let gui = Gui::new();
Self {
egui_ctx,
egui_state,
screen_descriptor,
renderer,
paint_jobs: Vec::new(),
textures,
gui,
cpu,
}
}
pub fn handle_event(&mut self, event: &winit::event::WindowEvent) {
let _ = self.egui_state.on_event(&self.egui_ctx, event);
}
pub fn resize(&mut self, width: u32, height: u32) {
if width > 0 && height > 0 {
self.screen_descriptor.size_in_pixels = [width, height];
}
}
pub fn scale_factor(&mut self, scale_factor: f64) {
self.screen_descriptor.pixels_per_point = scale_factor as f32;
}
pub fn prepare(&mut self, window: &Window) {
let raw_input = self.egui_state.take_egui_input(window);
let output = self.egui_ctx.run(raw_input, |egui_ctx| {
self.gui.ui(egui_ctx, &self.cpu);
});
self.textures.append(output.textures_delta);
self.egui_state
.handle_platform_output(window, &self.egui_ctx, output.platform_output);
self.paint_jobs = self.egui_ctx.tessellate(output.shapes);
}
pub fn render(
&mut self,
encoder: &mut wgpu::CommandEncoder,
render_target: &wgpu::TextureView,
context: &PixelsContext,
) {
for (id, image_delta) in &self.textures.set {
self.renderer
.update_texture(&context.device, &context.queue, *id, image_delta);
}
self.renderer.update_buffers(
&context.device,
&context.queue,
encoder,
&self.paint_jobs,
&self.screen_descriptor,
);
{
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("egui"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: render_target,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: true,
},
})],
depth_stencil_attachment: None,
});
self.renderer
.render(&mut rpass, &self.paint_jobs, &self.screen_descriptor);
}
let textures = std::mem::take(&mut self.textures);
for id in &textures.free {
self.renderer.free_texture(id);
}
}
fn draw_rect(frame: &mut [u8], order: Order, offset: u32, color: u32) {
let offset = offset as usize * 4;
let (r, g, b, a) = match order {
Order::Lsb => (
(color >> 24) as u8,
(color >> 16) as u8,
(color >> 8) as u8,
color as u8,
),
Order::Msb => (
color as u8,
(color >> 8) as u8,
(color >> 16) as u8,
(color >> 24) as u8,
),
};
if a == 0xff {
frame[offset..offset + 4].copy_from_slice(&[r, g, b, a]);
}
}
fn draw(&mut self, frame: &mut [u8]) {
if !self.update() {
return;
}
let log: Vec<String> = self
.gpu_queue()
.iter()
.map(|cmd| match cmd {
Primitive::Rect(offset, color) => {
let (x, y) = ((offset >> 16), (offset & 0xff));
for i in 0..TILE_SIZE * TILE_SIZE {
let x = x + (i % TILE_SIZE);
let y = (y + (i / TILE_SIZE)) * FB_WIDTH;
Self::draw_rect(frame, Order::Lsb, x + y, *color);
}
format!("Rect: {:#08x} {:#08x}", offset, color)
}
Primitive::TexturedRect(offset, addr) => {
let (x, y) = ((offset >> 16), (offset & 0xff));
for i in 0..TILE_SIZE * TILE_SIZE {
let data = self.read(addr + (i * 4));
let x = x + (i % TILE_SIZE);
let y = (y + (i / TILE_SIZE)) * FB_WIDTH;
Self::draw_rect(frame, Order::Msb, x + y, data);
}
format!("TexturedRect: {:#08x} {:#08x}", offset, addr)
}
})
.collect();
self.gui.gpu_log.extend(log);
self.gpu_clear();
}
fn read(&self, offset: u32) -> u32 {
self.cpu.read(offset)
}
fn gpu_queue(&self) -> &Vec<Primitive> {
self.cpu.gpu_queue()
}
fn gpu_clear(&mut self) {
self.cpu.gpu_clear();
}
fn update(&mut self) -> bool {
self.cpu.update()
}
fn step(&mut self) {
self.cpu.step();
}
}
pub fn run(path: String) -> Result<()> {
env_logger::init();
let mut file = fs::File::open(path)?;
let mut data = Vec::new();
file.read_to_end(&mut data)?;
let cpu = match Cpu::new(data) {
Ok(cpu) => cpu,
Err(e) => match e {
elf::Error::Eof => bail!("eof"),
elf::Error::BadMagic => bail!("bad magic"),
elf::Error::BadType => bail!("invalid executable"),
elf::Error::BadMachine => bail!("foreign elf architecture"),
},
};
let event_loop = EventLoop::new();
let mut input = WinitInputHelper::new();
let window = {
let size = LogicalSize::new(WINDOW_WIDTH as f64, WINDOW_HEIGHT as f64);
WindowBuilder::new()
.with_title("ive")
.with_inner_size(size)
.with_min_inner_size(size)
.with_resizable(false)
.build(&event_loop)
.unwrap()
};
let (mut pixels, mut state) = {
let window_size = window.inner_size();
let scale_factor = 1.0;
let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, &window);
let pixels = Pixels::new(FB_WIDTH, FB_HEIGHT, surface_texture).unwrap();
let state = State::new(
&event_loop,
window_size.width,
window_size.height,
scale_factor,
&pixels,
cpu,
);
(pixels, state)
};
event_loop.run(move |event, _, control_flow| {
if input.update(&event) {
if input.key_pressed(VirtualKeyCode::Escape) || input.close_requested() {
*control_flow = ControlFlow::Exit;
return;
}
if let Some(scale_factor) = input.scale_factor() {
state.scale_factor(scale_factor);
}
if let Some(size) = input.window_resized() {
if pixels.resize_surface(size.width, size.height).is_err() {
*control_flow = ControlFlow::Exit;
return;
}
state.resize(size.width, size.height);
}
window.request_redraw();
}
match event {
Event::WindowEvent { event, .. } => {
state.handle_event(&event);
}
Event::RedrawRequested(_) => {
state.draw(pixels.frame_mut());
state.prepare(&window);
let _ = pixels.render_with(|encoder, target, ctx| {
ctx.scaling_renderer.render(encoder, target);
state.render(encoder, target, ctx);
Ok(())
});
}
_ => (),
}
state.step();
if state.update() {
window.request_redraw();
}
});
}

11
src/main.rs Normal file
View File

@ -0,0 +1,11 @@
use std::env;
use std::process;
fn main() -> anyhow::Result<()> {
if let Some(path) = env::args().nth(1) {
ive::run(path)
} else {
eprintln!("usage: {} <file>", env::args().next().unwrap());
process::exit(1);
}
}