commit 03f6a95572eeb67c6350d003becbe75d5a90cc25
Author: Edea Kramer <edea@lunarcry.my.domain>
Date: Wed, 13 Mar 2024 19:20:06 +0200
First commit
Diffstat:
12 files changed, 1521 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,2 @@
+misc/
+ultimecia-ha
diff --git a/TODO b/TODO
@@ -0,0 +1 @@
+1. Test switch cases if they need break or return or not;
diff --git a/bios/bios.ha b/bios/bios.ha
@@ -0,0 +1,42 @@
+use os;
+use io;
+use fmt;
+use util;
+
+export type BIOS = struct {
+ data: []u8,
+};
+
+export fn new() (io::error | BIOS) = {
+ const file = os::open("roms/scph1001.bin")!;
+ defer io::close(file)!;
+
+ let buffer = io::drain(file)!;
+
+ let b = BIOS {
+ data = buffer,
+ };
+
+ return b;
+};
+
+export fn load32(bios: BIOS, o: u32) u32 = {
+ let off = o;
+
+ //fmt::printfln("{:.02X}{:.02X} {:.02X}{:.02X}", bios.data[off], bios.data[off + 1], bios.data[2 + off], bios.data[3 +off])!;
+
+ let b0 = bios.data[off + 0]: u32;
+ let b1 = bios.data[off + 1]: u32;
+ let b2 = bios.data[off + 2]: u32;
+ let b3 = bios.data[off + 3]: u32;
+
+ let res = b0 | (b1 << 8) | (b2 << 16) | (b3 << 24);
+
+ return res;
+
+};
+
+export fn load8(bios: BIOS, o: u32) u8 = {
+ let off = o;
+ return bios.data[off: size];
+};
diff --git a/cpu/cpu.ha b/cpu/cpu.ha
@@ -0,0 +1,1048 @@
+// THE GOAT
+
+use fmt;
+use math::checked;
+use os;
+
+use interconnect;
+use cpu::instruction;
+
+let flag = false;
+
+export type CPU = struct{
+ // Program Counter
+ pc: u32,
+ next_pc: u32,
+ current_pc: u32,
+ // Status Register
+ sr: u32,
+ // hi register
+ hi: u32,
+ // lo register
+ lo: u32,
+ // Cop0 register 13: Cause register
+ cause: u32,
+ // Cop0 register 14: EPC
+ epc: u32,
+
+ // Set by the current instruction if a branch occured and the next
+ // instruction will be on the delay slot
+ branch: bool,
+ // If the current instruction executes on the delay slot
+ delay_slot: bool,
+ out_regs: [32]u32,
+ load: (u32, u32),
+ regs: [32]u32,
+ inter: interconnect::Interconnect,
+ next_instruction: instruction::instruction,
+};
+
+export type EXCEPTION = enum {
+ LOAD_ADDRESS_ERROR = 0x4,
+ STORE_ADDRESS_ERROR = 0x5,
+ SYSCALL = 0x8,
+ BREAK = 0x9,
+ ILLEGAL_INSTRUCTION = 0xa,
+ COPROCESSOR_ERROR = 0xb,
+ OVERFLOW = 0xc,
+};
+
+export fn new(inter: interconnect::Interconnect) CPU = {
+ let regs: [32]u32 = [0xdeadbeef...];
+ regs[0] = 0;
+ let pc: u32 = 0xbfc00000;
+ let cpu = CPU {
+ pc = pc,
+ next_pc = pc + 4,
+ current_pc = 0,
+ sr = 0,
+ hi = 0xdeadbeef,
+ lo = 0xdeadbeef,
+ cause = 0,
+ epc = 0,
+ branch = false,
+ delay_slot = false,
+ regs = regs,
+ out_regs = regs,
+ load = (0, 0),
+ inter = inter,
+ next_instruction = instruction::new(0x0),
+ };
+
+ return cpu;
+};
+
+fn set_reg(cpu: *CPU, reg: u32, val: u32) void = {
+ cpu.out_regs[reg] = val;
+ cpu.out_regs[0] = 0;
+};
+
+fn reg(cpu: *CPU, reg: u32) u32 = {
+ return cpu.out_regs[reg];
+};
+
+fn load32(cpu: *CPU, addr: u32) u32 = {
+ return interconnect::load32(cpu.inter, addr);
+};
+
+fn load16(cpu: *CPU, addr: u32) u16 = {
+ return interconnect::load16(cpu.inter, addr);
+};
+
+fn load8(cpu: *CPU, addr: u32) u8 = {
+ return interconnect::load8(cpu.inter, addr);
+};
+
+fn store32(cpu: *CPU, addr: u32, val: u32) void = {
+ interconnect::store32(cpu.inter, addr, val);
+ return;
+};
+
+fn store16(cpu: *CPU, addr: u32, val: u16) void = {
+ interconnect::store16(cpu.inter, addr, val);
+ return;
+};
+
+fn store8(cpu: *CPU, addr: u32, val: u8) void = {
+ interconnect::store8(cpu.inter, addr, val);
+ return;
+};
+
+fn op_lui(cpu: *CPU, instruction: instruction::instruction) void = {
+ let i = instruction;
+ let v = i.imm << 16;
+
+ set_reg(cpu, i.t, v);
+};
+
+fn op_ori(cpu: *CPU, instruction: instruction::instruction) void = {
+ let i = instruction.imm;
+ let t = instruction.t;
+ let s = instruction.s;
+
+ let v = reg(cpu, s) | i;
+
+ set_reg(cpu, t, v);
+};
+
+fn op_andi(cpu: *CPU, instruction: instruction::instruction) void = {
+ let i = instruction.imm;
+ let t = instruction.t;
+ let s = instruction.s;
+
+ let v = reg(cpu, s) & i;
+
+ set_reg(cpu, t, v);
+};
+
+fn op_or(cpu: *CPU, instruction: instruction::instruction) void = {
+ let d = instruction.d;
+ let t = instruction.t;
+ let s = instruction.s;
+
+ let v = reg(cpu, instruction.s) | reg(cpu, instruction.t);
+
+ set_reg(cpu, d, v);
+};
+
+fn op_xor(cpu: *CPU, instruction: instruction::instruction) void = {
+ let d = instruction.d;
+ let t = instruction.t;
+ let s = instruction.s;
+
+ let v = reg(cpu, instruction.s) ^ reg(cpu, instruction.t);
+
+ set_reg(cpu, d, v);
+};
+
+fn op_xori(cpu: *CPU, instruction: instruction::instruction) void = {
+ let i = instruction.imm;
+ let t = instruction.t;
+ let s = instruction.s;
+
+ let v = reg(cpu, instruction.s) ^ i;
+
+ set_reg(cpu, t, v);
+};
+
+fn op_nor(cpu: *CPU, instruction: instruction::instruction) void = {
+ let d = instruction.d;
+ let t = instruction.t;
+ let s = instruction.s;
+
+ let v = ~(reg(cpu, instruction.s) | reg(cpu, instruction.t));
+
+ set_reg(cpu, d, v);
+};
+
+fn op_and(cpu: *CPU, instruction: instruction::instruction) void = {
+ let d = instruction.d;
+ let t = instruction.t;
+ let s = instruction.s;
+
+ let v = reg(cpu, s) & reg(cpu, t);
+
+ set_reg(cpu, d, v);
+};
+
+fn op_sw(cpu: *CPU, instruction: instruction::instruction) void = {
+
+ if (cpu.sr & 0x10000 != 0) {
+ fmt::println("Ignoring store while cache is isolated")!;
+ return;
+ };
+
+ let i = instruction.imm_se;
+ let t = instruction.t;
+ let s = instruction.s;
+
+ let addr = reg(cpu, s) + i;
+ let v = reg(cpu, t);
+
+ if (addr % 4 == 0) {
+ store32(cpu, addr, v);
+
+ } else {
+ exception(cpu, EXCEPTION::STORE_ADDRESS_ERROR);
+ };
+
+};
+
+fn op_swl(cpu: *CPU, instruction: instruction::instruction) void = {
+ let i = instruction.imm_se;
+ let t = instruction.t;
+ let s = instruction.s;
+
+ let addr = reg(cpu, s) + i;
+ let v = reg(cpu, t);
+
+ let aligned_addr = addr & ~3;
+ let cur_mem = load32(cpu, aligned_addr);
+
+ let mem = switch(addr & 3) {
+ case 0 => yield (cur_mem & 0xffffff00) | (v >> 24);
+ case 1 => yield (cur_mem & 0xffff0000) | (v >> 16);
+ case 2 => yield (cur_mem & 0xff000000) | (v >> 8);
+ case 3 => yield (cur_mem & 0x00000000) | (v >> 0);
+ case => fmt::fatalf("UNREACHABLE!");
+ };
+
+ store32(cpu, addr, mem);
+
+};
+
+fn op_swr(cpu: *CPU, instruction: instruction::instruction) void = {
+ let i = instruction.imm_se;
+ let t = instruction.t;
+ let s = instruction.s;
+
+ let addr = reg(cpu, s) + i;
+ let v = reg(cpu, t);
+
+ let aligned_addr = addr & ~3;
+ let cur_mem = load32(cpu, aligned_addr);
+
+ let mem = switch(addr & 3) {
+ case 0 => yield (cur_mem & 0x00000000) | (v << 0);
+ case 1 => yield (cur_mem & 0x000000ff) | (v << 8);
+ case 2 => yield (cur_mem & 0x0000ffff) | (v << 16);
+ case 3 => yield (cur_mem & 0x00ffffff) | (v << 24);
+ case => fmt::fatalf("UNREACHABLE!");
+ };
+
+ store32(cpu, addr, mem);
+
+};
+
+fn op_sh(cpu: *CPU, instruction: instruction::instruction) void = {
+
+ if (cpu.sr & 0x10000 != 0) {
+ fmt::println("Ignoring store while cache is isolated")!;
+ return;
+ };
+
+ let i = instruction.imm_se;
+ let t = instruction.t;
+ let s = instruction.s;
+
+ let addr = reg(cpu, s) + i;
+ let v = reg(cpu, t);
+
+ if (addr % 2 == 0) {
+ store16(cpu, addr, v: u16);
+ } else {
+ exception(cpu, EXCEPTION::STORE_ADDRESS_ERROR);
+ };
+
+};
+
+fn op_sb(cpu: *CPU, instruction: instruction::instruction) void = {
+
+ if (cpu.sr & 0x10000 != 0) {
+ fmt::println("Ignoring store while cache is isolated")!;
+ return;
+ };
+
+ let i = instruction.imm_se;
+ let t = instruction.t;
+ let s = instruction.s;
+
+ let addr = reg(cpu, s) + i;
+ let v = reg(cpu, t);
+
+ store8(cpu, addr, v: u8);
+};
+
+fn op_sll(cpu: *CPU, instruction: instruction::instruction) void = {
+ let i = instruction.shift;
+ let t = instruction.t;
+ let d = instruction.d;
+
+ let v = reg(cpu, t) << i;
+
+ set_reg(cpu, d, v);
+};
+
+fn op_sllv(cpu: *CPU, instruction: instruction::instruction) void = {
+ let d = instruction.d;
+ let s = instruction.s;
+ let t = instruction.t;
+
+ let v = reg(cpu, t) << (reg(cpu, s) & 0x1f);
+
+ set_reg(cpu, d, v);
+};
+
+fn op_slt(cpu: *CPU, instruction: instruction::instruction) void = {
+ let d = instruction.d;
+ let s = instruction.s;
+ let t = instruction.t;
+
+ let s = reg(cpu, s): i32;
+ let t = reg(cpu, t): i32;
+
+ let v: u32 = if (s < t) 1 else 0;
+
+ set_reg(cpu, d, v: u32);
+};
+
+fn op_sltu(cpu: *CPU, instruction: instruction::instruction) void = {
+ let d = instruction.d;
+ let s = instruction.s;
+ let t = instruction.t;
+
+ let v: u32 = if (reg(cpu, s) < reg(cpu, t)) 1 else 0;
+
+ set_reg(cpu, d, v);
+};
+
+fn op_slti(cpu: *CPU, instruction: instruction::instruction) void = {
+ let i = instruction.imm_se: i32;
+ let s = instruction.s;
+ let t = instruction.t;
+
+ let s = reg(cpu, s): i32;
+
+ let v: u32 = if (s < i) 1 else 0;
+
+ set_reg(cpu, t, v: u32);
+};
+
+fn op_sltiu(cpu: *CPU, instruction: instruction::instruction) void = {
+ let i = instruction.imm_se;
+ let s = instruction.s;
+ let t = instruction.t;
+
+ let v: u32 = if (reg(cpu, s) < i) 1 else 0;
+
+ set_reg(cpu, t, v: u32);
+};
+
+fn op_addiu(cpu: *CPU, instruction: instruction::instruction) void = {
+ let i = instruction.imm_se;
+ let t = instruction.t;
+ let s = instruction.s;
+
+ let v = reg(cpu, s) + i;
+
+ set_reg(cpu, t, v);
+};
+
+fn op_addi(cpu: *CPU, instruction: instruction::instruction) void = {
+ let i = instruction.imm_se: i32;
+ let t = instruction.t;
+ let s = instruction.s;
+
+ let s = reg(cpu, s): i32;
+
+ let (v, overflowed) = math::checked::addi32(s, i);
+
+ if (overflowed) {
+ exception(cpu, EXCEPTION::OVERFLOW);
+ } else {
+ set_reg(cpu, t, v: u32);
+ };
+
+};
+
+fn op_addu(cpu: *CPU, instruction: instruction::instruction) void = {
+ let d = instruction.d;
+ let t = instruction.t;
+ let s = instruction.s;
+
+ let v = reg(cpu, s) + reg(cpu, t);
+
+ set_reg(cpu, d, v);
+};
+
+fn op_add(cpu: *CPU, instruction: instruction::instruction) void = {
+ let s = instruction.s;
+ let t = instruction.t;
+ let d = instruction.d;
+
+ let s = reg(cpu, s): i32;
+ let t = reg(cpu, t): i32;
+
+ let (v, overflowed) = math::checked::addi32(s, t);
+
+ if (overflowed) {
+ exception(cpu, EXCEPTION::OVERFLOW);
+ } else {
+ set_reg(cpu, d, v: u32);
+ };
+
+};
+
+fn op_subu(cpu: *CPU, instruction: instruction::instruction) void = {
+ let s = instruction.s;
+ let t = instruction.t;
+ let d = instruction.d;
+
+ let v = reg(cpu, s) - reg(cpu, t);
+
+ set_reg(cpu, d, v);
+};
+
+fn op_sub(cpu: *CPU, instruction: instruction::instruction) void = {
+ let s = instruction.s;
+ let t = instruction.t;
+ let d = instruction.d;
+
+ let s = reg(cpu, s): i32;
+ let t = reg(cpu, t): i32;
+
+ let (v, overflowed) = math::checked::subi32(s, t);
+
+ if (overflowed) {
+ exception(cpu, EXCEPTION::OVERFLOW);
+ } else {
+ set_reg(cpu, d, v: u32);
+ };
+
+};
+
+fn op_mtc0(cpu: *CPU, instruction: instruction::instruction) void = {
+ let cpu_r = instruction.t;
+ let cop_r = instruction.d;
+
+ let v = reg(cpu, cpu_r);
+
+ switch (cop_r) {
+ case 3,5,6,7,9,11 => if (v != 0) fmt::fatalf("Unhandled write to cop0 register");
+ case 12 => cpu.sr = v;
+ case 13 => if (v != 0) fmt::fatalf("Unhandled write to CAUSE register");
+ case => fmt::fatalf("Unhandled cop0 register {:.08X}", cop_r);
+ };
+};
+
+fn op_mfc0(cpu: *CPU, instruction: instruction::instruction) void = {
+ let cpu_r = instruction.t;
+ let cop_r = instruction.d;
+
+ let v: u32 = 0;
+
+ switch (cop_r) {
+ case 12 => v = cpu.sr;
+ case 13 => v = cpu.cause;
+ case 14 => v = cpu.epc;
+ case => fmt::fatalf("Unhandled read from cop0 register{:X}", cop_r);
+ };
+
+ cpu.load = (cpu_r, v);
+};
+
+fn op_rfe(cpu: *CPU, instruction: instruction::instruction) void = {
+ if (instruction.instr & 0x3f != 0b010000)
+ fmt::fatalf("Invalid cop0 instruction {:X}", instruction.instr);
+
+ let mode = cpu.sr & 0x3f;
+ cpu.sr &= ~0x3f;
+ cpu.sr |= mode >> 2;
+};
+
+fn op_cop0(cpu: *CPU, instruction: instruction::instruction) void = {
+
+ switch (instruction.cop_opcode) {
+ case 0b00000 => op_mfc0(cpu, instruction);
+ case 0b00100 => op_mtc0(cpu, instruction);
+ case 0b10000 => op_rfe(cpu, instruction);
+ case => fmt::fatalf("Unhandled cop0 instruction {:X} - cop op_code {:X}", instruction.instr, instruction.cop_opcode);
+ };
+};
+
+fn op_cop1(cpu: *CPU, instruction: instruction::instruction) void = {
+ exception(cpu, EXCEPTION::COPROCESSOR_ERROR);
+};
+
+fn op_cop2(cpu: *CPU, instruction: instruction::instruction) void = {
+ fmt::fatalf("Unhandled GTE instruction: {:.08X}", instruction.instr);
+};
+
+fn op_cop3(cpu: *CPU, instruction: instruction::instruction) void = {
+ exception(cpu, EXCEPTION::COPROCESSOR_ERROR);
+};
+
+fn op_lwc0(cpu: *CPU, instruction: instruction::instruction) void = {
+ exception(cpu, EXCEPTION::COPROCESSOR_ERROR);
+};
+
+fn op_lwc1(cpu: *CPU, instruction: instruction::instruction) void = {
+ exception(cpu, EXCEPTION::COPROCESSOR_ERROR);
+};
+
+fn op_lwc2(cpu: *CPU, instruction: instruction::instruction) void = {
+ fmt::fatalf("Unhandled GTE LWC: {:X}", instruction.instr);
+};
+
+fn op_lwc3(cpu: *CPU, instruction: instruction::instruction) void = {
+ exception(cpu, EXCEPTION::COPROCESSOR_ERROR);
+};
+
+fn op_swc0(cpu: *CPU, instruction: instruction::instruction) void = {
+ exception(cpu, EXCEPTION::COPROCESSOR_ERROR);
+};
+
+fn op_swc1(cpu: *CPU, instruction: instruction::instruction) void = {
+ exception(cpu, EXCEPTION::COPROCESSOR_ERROR);
+};
+
+fn op_swc2(cpu: *CPU, instruction: instruction::instruction) void = {
+ fmt::fatalf("Unhandled GTE SWC: {:X}", instruction.instr);
+};
+
+fn op_swc3(cpu: *CPU, instruction: instruction::instruction) void = {
+ exception(cpu, EXCEPTION::COPROCESSOR_ERROR);
+};
+
+fn op_lw(cpu: *CPU, instruction: instruction::instruction) void = {
+ let i = instruction.imm_se;
+ let t = instruction.t;
+ let s = instruction.s;
+
+ let addr = reg(cpu, s) + i;
+
+ if (addr % 4 == 0) {
+ let v = load32(cpu, addr);
+ cpu.load = (t, v);
+ } else {
+ exception(cpu, EXCEPTION::LOAD_ADDRESS_ERROR);
+ };
+
+};
+
+fn op_lwl(cpu: *CPU, instruction: instruction::instruction) void = {
+ let i = instruction.imm_se;
+ let t = instruction.t;
+ let s = instruction.s;
+
+ let addr = reg(cpu, s) + i;
+
+ let cur_v = cpu.out_regs[t];
+
+ let aligned_addr = addr & ~3;
+ let aligned_word = load32(cpu, aligned_addr);
+
+ let v = switch(addr & 3) {
+ case 0 => yield (cur_v & 0x00ffffff) | (aligned_word << 24);
+ case 1 => yield (cur_v & 0x0000ffff) | (aligned_word << 16);
+ case 2 => yield (cur_v & 0x000000ff) | (aligned_word << 8);
+ case 3 => yield (cur_v & 0x00000000) | (aligned_word << 0);
+ case => fmt::fatalf("UNREACHABLE!");
+ };
+
+ cpu.load = (t, v);
+
+};
+
+fn op_lwr(cpu: *CPU, instruction: instruction::instruction) void = {
+ let i = instruction.imm_se;
+ let t = instruction.t;
+ let s = instruction.s;
+
+ let addr = reg(cpu, s) + i;
+
+ let cur_v = cpu.out_regs[t];
+
+ let aligned_addr = addr & ~3;
+ let aligned_word = load32(cpu, aligned_addr);
+
+ let v = switch(addr & 3) {
+ case 0 => yield (cur_v & 0x00000000) | (aligned_word >> 0);
+ case 1 => yield (cur_v & 0xff000000) | (aligned_word >> 8);
+ case 2 => yield (cur_v & 0xffff0000) | (aligned_word >> 16);
+ case 3 => yield (cur_v & 0xffffff00) | (aligned_word >> 24);
+ case => fmt::fatalf("UNREACHABLE!");
+ };
+
+ cpu.load = (t, v);
+
+};
+
+fn op_lb(cpu: *CPU, instruction: instruction::instruction) void = {
+ let i = instruction.imm_se;
+ let t = instruction.t;
+ let s = instruction.s;
+
+ let addr = reg(cpu, s) + i;
+
+ let v = load8(cpu, addr): i8;
+
+ cpu.load = (t, v: u32);
+};
+
+fn op_lbu(cpu: *CPU, instruction: instruction::instruction) void = {
+ let i = instruction.imm_se;
+ let t = instruction.t;
+ let s = instruction.s;
+
+ let addr = reg(cpu, s) + i;
+
+ let v = load8(cpu, addr);
+
+ cpu.load = (t, v: u32);
+};
+
+fn op_lh(cpu: *CPU, instruction: instruction::instruction) void = {
+ let i = instruction.imm_se;
+ let t = instruction.t;
+ let s = instruction.s;
+
+ let addr = reg(cpu, s) + i;
+
+ let v = load16(cpu, addr): i16;
+ cpu.load = (t, v: u32);
+};
+
+
+fn op_lhu(cpu: *CPU, instruction: instruction::instruction) void = {
+ let i = instruction.imm_se;
+ let t = instruction.t;
+ let s = instruction.s;
+
+ let addr = reg(cpu, s) + i;
+
+ if (addr % 2 == 0) {
+ let v = load16(cpu, addr);
+ cpu.load = (t, v: u32);
+ } else {
+ exception(cpu, EXCEPTION::LOAD_ADDRESS_ERROR);
+ };
+};
+
+fn op_sra(cpu: *CPU, instruction: instruction::instruction) void = {
+ // DEBUG
+ // Casting shift value to i32 because hare doesn't support
+ // bitwise operations on signed values.
+ let i = instruction.shift: i32;
+ let t = instruction.t;
+ let d = instruction.d;
+
+ let t = reg(cpu, t): i32;
+
+ let v = t >> i;
+
+ cpu.load = (d, v: u32);
+};
+
+fn op_srav(cpu: *CPU, instruction: instruction::instruction) void = {
+ // DEBUG
+ // The same thing as sra.. but here we cast a value of u32
+ // to i32. that must have some implications. keep an eye on this
+ let d = instruction.d;
+ let s = instruction.s;
+ let t = instruction.t;
+
+ let t = reg(cpu, t): i32;
+ let s = reg(cpu, s): i32;
+
+ let v = t >> (s & 0x1f);
+
+ cpu.load = (d, v: u32);
+};
+
+fn op_srlv(cpu: *CPU, instruction: instruction::instruction) void = {
+ let d = instruction.d;
+ let s = instruction.s;
+ let t = instruction.t;
+
+ let t = reg(cpu, t);
+ let s = reg(cpu, s);
+
+ let v = t >> (s & 0x1f);
+
+ cpu.load = (d, v: u32);
+};
+
+fn op_srl(cpu: *CPU, instruction: instruction::instruction) void = {
+ let i = instruction.shift;
+ let t = instruction.t;
+ let d = instruction.d;
+
+ let v = reg(cpu, t) >> i;
+
+ cpu.load = (d, v: u32);
+};
+
+fn op_div(cpu: *CPU, instruction: instruction::instruction) void = {
+ let s = instruction.s;
+ let t = instruction.t;
+
+ let n = reg(cpu, s): i32;
+ let d = reg(cpu, t): i32;
+
+ if (d == 0) {
+ cpu.hi = n: u32;
+ if (n >= 0) {
+ cpu.lo = 0xffffffff;
+ } else {
+ cpu.lo = 1;
+ };
+ } else if (((n: u32) == 0x80000000) && d == -1) {
+ cpu.hi = 0;
+ cpu.lo = 0x80000000;
+ } else {
+ cpu.hi = (n % d): u32;
+ cpu.lo = (n / d): u32;
+ };
+
+};
+
+fn op_divu(cpu: *CPU, instruction: instruction::instruction) void = {
+ let s = instruction.s;
+ let t = instruction.t;
+
+ let n = reg(cpu, s);
+ let d = reg(cpu, t);
+
+ if (d == 0) {
+ cpu.hi = n;
+ cpu.lo = 0xffffffff;
+ } else {
+ cpu.hi = n % d;
+ cpu.lo = n / d;
+ };
+
+};
+
+fn op_multu(cpu: *CPU, instruction: instruction::instruction) void = {
+ let s = instruction.s;
+ let t = instruction.t;
+
+ let a = reg(cpu, s): u64;
+ let b = reg(cpu, t): u64;
+
+ let v = a * b;
+
+ cpu.hi = (v >> 32): u32;
+ cpu.lo = v: u32;
+
+};
+
+fn op_mult(cpu: *CPU, instruction: instruction::instruction) void = {
+ let s = instruction.s;
+ let t = instruction.t;
+
+ let a = (reg(cpu, s): i32): u64;
+ let b = (reg(cpu, t): i32): u64;
+
+ let v = (a * b): u64;
+
+ cpu.hi = (v >> 32): u32;
+ cpu.lo = v: u32;
+
+};
+
+fn op_mflo(cpu: *CPU, instruction: instruction::instruction) void = {
+ let d = instruction.d;
+ let lo = cpu.lo;
+ set_reg(cpu, d, lo);
+};
+
+fn op_mtlo(cpu: *CPU, instruction: instruction::instruction) void = {
+ let s = instruction.s;
+ cpu.lo = reg(cpu, s);
+};
+
+fn op_mfhi(cpu: *CPU, instruction: instruction::instruction) void = {
+ let d = instruction.d;
+ let hi = cpu.hi;
+ set_reg(cpu, d, hi);
+};
+
+fn op_mthi(cpu: *CPU, instruction: instruction::instruction) void = {
+ let s = instruction.s;
+ cpu.hi = reg(cpu, s);
+};
+
+
+// Jump - Branch Instructions
+
+fn op_j(cpu: *CPU, instruction: instruction::instruction) void = {
+ let i = instruction.imm_jump;
+ cpu.next_pc = (cpu.pc & 0xf0000000) | (i << 2);
+ cpu.branch = true;
+};
+
+fn op_jr(cpu: *CPU, instruction: instruction::instruction) void = {
+ let s = instruction.s;
+ cpu.next_pc = reg(cpu, s);
+ cpu.branch = true;
+};
+
+fn op_jal(cpu: *CPU, instruction: instruction::instruction) void = {
+ let ra = cpu.next_pc;
+ set_reg(cpu, 31, ra);
+ op_j(cpu, instruction);
+};
+
+fn op_jalr(cpu: *CPU, instruction: instruction::instruction) void = {
+ let d = instruction.d;
+ let s = instruction.s;
+
+ let ra = cpu.next_pc;
+
+ set_reg(cpu, d, ra);
+
+ cpu.next_pc = reg(cpu, s);
+
+ cpu.branch = true;
+};
+
+fn op_bne(cpu: *CPU, instruction: instruction::instruction) void = {
+ let i = instruction.imm_se;
+ let t = instruction.t;
+ let s = instruction.s;
+
+ if (reg(cpu, s) != reg(cpu, t))
+ branch(cpu, i);
+};
+
+fn op_beq(cpu: *CPU, instruction: instruction::instruction) void = {
+ let i = instruction.imm_se;
+ let t = instruction.t;
+ let s = instruction.s;
+
+ if (reg(cpu, s) == reg(cpu, t))
+ branch(cpu, i);
+};
+
+fn op_bgtz(cpu: *CPU, instruction: instruction::instruction) void = {
+ let i = instruction.imm_se;
+ let s = instruction.s;
+
+ let v = reg(cpu, s): i32;
+
+ if (v > 0) branch(cpu, i);
+};
+
+fn op_blez(cpu: *CPU, instruction: instruction::instruction) void = {
+ let i = instruction.imm_se;
+ let s = instruction.s;
+
+ let v = reg(cpu, s): i32;
+
+ if (v <= 0) branch(cpu, i);
+};
+
+fn op_bxx(cpu: *CPU, instruction: instruction::instruction) void = {
+ let i = instruction.imm_se;
+ let s = instruction.s;
+
+ //fmt::printfln("INSTR: {:.032b}", instruction.instr)!;
+ //fmt::printfln("SHIFTED: {:.032b} {}", instruction.instr >> 16, (instruction.instr >> 16) & 1)!;
+ //fmt::printfln("SHIFTED: {:.032b} {}", instruction.instr >> 20, (instruction.instr >> 20) & 1 != 0)!;
+
+ let is_bgez = (instruction.instr >> 16) & 1;
+ let is_link = (instruction.instr >> 20) & 1 != 0;
+
+ let v = reg(cpu, s): i32;
+
+ let test: u32 = if (v < 0) 1 else 0;
+
+ test ^= is_bgez;
+
+ if (test != 0) {
+ if (is_link) {
+ let ra = cpu.pc;
+ set_reg(cpu, 31, ra);
+ };
+ branch(cpu, i);
+ };
+};
+
+fn branch(cpu: *CPU, _off: u32) void = {
+ let off = _off << 2;
+ cpu.next_pc += off;
+ cpu.next_pc -= 4;
+ cpu.branch = true;
+};
+
+
+fn exception(cpu: *CPU, cause: EXCEPTION) void = {
+ // Exception handler address depends on the `BEV` bit;
+ let handler: u32 = if (cpu.sr & (1 << 22) != 0 ) 0xbfc00180 else 0x80000080;
+
+ // fmt::printfln("CPU SR: {:.032b}, mode: {:.032b} ", cpu.sr, cpu.sr & ~0x3f)!;
+ let mode = cpu.sr & 0x3f;
+ cpu.sr &= ~0x3f;
+ cpu.sr |= (mode << 2) & 0x3f;
+
+ cpu.cause = (cause: u32) << 2;
+ //fmt::printfln("{:.08b} - {:.08b}", 0b00000001, ~0b000000001i8 )!;
+ cpu.epc = cpu.current_pc;
+
+ if (cpu.delay_slot) {
+ // So.. When an exception occurs in a delay slot `EPC` points to
+ // the branch instruction AND bit 31 of `CAUSE` register is set
+ cpu.epc -= 4;
+ cpu.cause |= 1 << 31;
+ };
+
+ cpu.pc = handler;
+ cpu.next_pc = cpu.pc + 4;
+
+ return;
+};
+
+fn op_break(cpu: *CPU, instruction: instruction::instruction) void = {
+ exception(cpu, EXCEPTION::BREAK);
+};
+
+fn op_exception(cpu: *CPU, instruction: instruction::instruction) void = {
+ exception(cpu, EXCEPTION::SYSCALL);
+};
+
+fn op_illegal(cpu: *CPU, instruction: instruction::instruction) void = {
+ fmt::printfln("Illegal instruction {:X}!", instruction.instr)!;
+ exception(cpu, EXCEPTION::ILLEGAL_INSTRUCTION);
+};
+
+export fn run_next_instruction(cpu: *CPU) void = {
+ let pc = cpu.pc;
+
+ // Save the address for the current instruction in case of an exception
+ cpu.current_pc = cpu.pc;
+
+ if (cpu.current_pc % 4 != 0) {
+ exception(cpu, EXCEPTION::LOAD_ADDRESS_ERROR);
+ return;
+ };
+
+ let instruction = instruction::new(load32(cpu, pc));
+
+ cpu.delay_slot = cpu.branch;
+ cpu.branch = false;
+
+ cpu.pc = cpu.next_pc;
+
+ cpu.next_pc = cpu.next_pc + 4;
+
+ // Execute the pending load
+ let (reg, val) = cpu.load;
+
+ set_reg(cpu, reg, val);
+
+ cpu.load = (0, 0);
+
+ decode_and_execute(cpu, instruction);
+
+ cpu.regs = cpu.out_regs;
+};
+
+fn decode_and_execute(cpu: *CPU, instruction: instruction::instruction) void = {
+ //fmt::printfln("{:X} {:b} - {:b}", cpu.out_regs[3], instruction.func, instruction.sub)!;
+ switch (instruction.func) {
+ case 0b000000 =>
+ switch (instruction.sub) {
+ case 0b000000 => op_sll(cpu, instruction);
+ case 0b000100 => op_sllv(cpu, instruction);
+ case 0b100101 => op_or(cpu, instruction);
+ case 0b100110 => op_xor(cpu, instruction);
+ case 0b100111 => op_nor(cpu, instruction);
+ case 0b100100 => op_and(cpu, instruction);
+ case 0b101011 => op_sltu(cpu, instruction);
+ case 0b101010 => op_slt(cpu, instruction);
+ case 0b100001 => op_addu(cpu, instruction);
+ case 0b100000 => op_add(cpu, instruction);
+ case 0b001000 => op_jr(cpu, instruction);
+ case 0b001001 => op_jalr(cpu, instruction);
+ case 0b100011 => op_subu(cpu, instruction);
+ case 0b100010 => op_sub(cpu, instruction);
+ case 0b000011 => op_sra(cpu, instruction);
+ case 0b000111 => op_srav(cpu, instruction);
+ case 0b000110 => op_srlv(cpu, instruction);
+ case 0b000010 => op_srl(cpu, instruction);
+ case 0b011010 => op_div(cpu, instruction);
+ case 0b011011 => op_divu(cpu, instruction);
+ case 0b011000 => op_mult(cpu, instruction);
+ case 0b011001 => op_multu(cpu, instruction);
+ case 0b010010 => op_mflo(cpu, instruction);
+ case 0b010011 => op_mtlo(cpu, instruction);
+ case 0b010000 => op_mfhi(cpu, instruction);
+ case 0b010001 => op_mthi(cpu, instruction);
+ case 0b001101 => op_break(cpu, instruction);
+ case 0b001100 => op_exception(cpu, instruction);
+ case => fmt::fatalf("PC: {:.08X}, Unhandled instruction 0X{:.08X} - 0b{:.032b}", cpu.pc, instruction.instr, instruction.instr);
+ };
+ case 0b001111 => op_lui(cpu, instruction);
+ case 0b001101 => op_ori(cpu, instruction);
+ case 0b001110 => op_xori(cpu, instruction);
+ case 0b101011 => op_sw(cpu, instruction);
+ case 0b101010 => op_swl(cpu, instruction);
+ case 0b101110 => op_swr(cpu, instruction);
+ case 0b001001 => op_addiu(cpu, instruction);
+ case 0b001000 => op_addi(cpu, instruction);
+ case 0b000010 => op_j(cpu, instruction);
+ case 0b010000 => op_cop0(cpu, instruction);
+ case 0b010001 => op_cop1(cpu, instruction);
+ case 0b010010 => op_cop2(cpu, instruction);
+ case 0b010011 => op_cop3(cpu, instruction);
+ case 0b000101 => op_bne(cpu, instruction);
+ case 0b100011 => op_lw(cpu, instruction);
+ case 0b100010 => op_lwl(cpu, instruction);
+ case 0b100110 => op_lwr(cpu, instruction);
+ case 0b110000 => op_lwc0(cpu, instruction);
+ case 0b110001 => op_lwc1(cpu, instruction);
+ case 0b110010 => op_lwc2(cpu, instruction);
+ case 0b110011 => op_lwc3(cpu, instruction);
+ case 0b111000 => op_swc0(cpu, instruction);
+ case 0b111001 => op_swc1(cpu, instruction);
+ case 0b111010 => op_swc2(cpu, instruction);
+ case 0b111011 => op_swc3(cpu, instruction);
+ case 0b101001 => op_sh(cpu, instruction);
+ case 0b000011 => op_jal(cpu, instruction);
+ case 0b001100 => op_andi(cpu, instruction);
+ case 0b101000 => op_sb(cpu, instruction);
+ case 0b100000 => op_lb(cpu, instruction);
+ case 0b100100 => op_lbu(cpu, instruction);
+ case 0b100101 => op_lhu(cpu, instruction);
+ case 0b100001 => op_lh(cpu, instruction);
+ case 0b000100 => op_beq(cpu, instruction);
+ case 0b000111 => op_bgtz(cpu, instruction);
+ case 0b000110 => op_blez(cpu, instruction);
+ case 0b000001 => op_bxx(cpu, instruction);
+ case 0b001010 => op_slti(cpu, instruction);
+ case 0b001011 => op_sltiu(cpu, instruction);
+ case => op_illegal(cpu, instruction);//fmt::fatalf("CPU: {:X}, Unhandled instruction 0X{:.08X} - 0b{:.032b}", cpu.pc, instruction.instr, instruction.instr);
+ };
+};
diff --git a/cpu/instruction/instruction.ha b/cpu/instruction/instruction.ha
@@ -0,0 +1,33 @@
+use fmt;
+export type instruction = struct {
+ instr: u32,
+ func: u32,
+ t: u32,
+ s: u32,
+ d: u32,
+ imm: u32,
+ imm_se: u32,
+ sub: u32,
+ shift: u32,
+ imm_jump: u32,
+ cop_opcode: u32,
+};
+
+export fn new(ins: u32) instruction = {
+
+ let i = instruction {
+ instr = ins,
+ func = ins >> 26,
+ t = (ins >> 16) & 0x1f,
+ s = (ins >> 21) & 0x1f,
+ d = (ins >> 11) & 0x1f,
+ imm = ins & 0xffff,
+ imm_se = ((ins & 0xffff): i16): u32,
+ sub = ins & 0x3f,
+ shift = (ins >> 6) & 0x1f,
+ imm_jump = ins & 0x3ffffff,
+ cop_opcode = (ins >> 21) & 0x1f
+ };
+
+ return i;
+};
diff --git a/dma/dma.ha b/dma/dma.ha
diff --git a/interconnect/interconnect.ha b/interconnect/interconnect.ha
@@ -0,0 +1,235 @@
+use bios;
+use fmt;
+use util;
+use ram;
+
+export type Interconnect = struct {
+ bios: bios::BIOS,
+ ram: ram::ram,
+};
+
+export fn new(bios: bios::BIOS, ram: ram::ram) Interconnect = {
+ return Interconnect {
+ bios = bios,
+ ram = ram
+ };
+};
+
+export fn store32(inter: Interconnect, addr: u32, val: u32) void = {
+
+ if (addr % 4 != 0)
+ fmt::fatalf("Unaligned load32 address: {:.08X}", addr);
+
+ let abs_addr = util::mask_region(addr);
+
+ match(util::contains(util::MEMCONTROL_START: u32, util::MEMCONTROL_SIZE: u32, abs_addr)) {
+ case let off: u32 =>
+ switch (off) {
+ case 0 =>
+ if (val != 0x1f000000)
+ fmt::fatalf("Bad expansion 1 base address: {:.08X}", val);
+ case 4 =>
+ if (val != 0x1f802000)
+ fmt::fatalf("Bad expansion 2 base address: {:.08X}", val);
+ case =>
+ fmt::println("Unhandled write to MEMCONTROL Register")!;
+
+ };
+ return;
+ case void => yield;
+ };
+
+ match(util::contains(util::RAM_SIZE_START: u32, util::RAM_SIZE_SIZE: u32, abs_addr)) {
+ case let off: u32 => fmt::println("Unhandled write to RAM SIZE register")!; return;
+ case void => yield;
+ };
+
+ match(util::contains(util::CACHE_CONTROL_START: u32, util::CACHE_CONTROL_SIZE: u32, abs_addr)) {
+ case let off: u32 => fmt::println("Unhandled write to CACHE CONTROL register")!; return;
+ case void => yield;
+ };
+
+ match(util::contains(util::RAM_START: u32, util::RAM_SIZE: u32, abs_addr)) {
+ case let off: u32 => ram::store32(inter.ram, off, val); return;
+ case void => yield;
+ };
+
+ match(util::contains(util::IRQ_CONTROL_START: u32, util::IRQ_CONTROL_SIZE: u32, abs_addr)) {
+ case let off: u32 => fmt::printfln("IRQ control: {:.08X} <- {:X}", off, val)!; return;
+ case void => yield;
+ };
+
+ match(util::contains(util::DMA_START: u32, util::DMA_SIZE: u32, abs_addr)) {
+ case let off: u32 => fmt::printfln("DMA write: {:.08X} <- {:X}", abs_addr, val)!; return;
+ case void => yield;
+ };
+
+ match(util::contains(util::GPU_START: u32, util::GPU_SIZE: u32, abs_addr)) {
+ case let off: u32 => fmt::printfln("GPU write: {:.08X} <- {:X}", off, val)!; return;
+ case void => yield;
+ };
+
+ match(util::contains(util::TIMERS_START: u32, util::TIMERS_SIZE: u32, abs_addr)) {
+ case let off: u32 => fmt::printfln("Timer register write: {:.08X} <- {:X}", off, val)!; return;
+ case void => yield;
+ };
+
+ fmt::fatalf("Unhandled store32 into address {:.08X}", abs_addr);
+};
+
+export fn store16(inter: Interconnect, addr: u32, val: u16) void = {
+
+ if (addr % 2 != 0)
+ fmt::fatalf("Unaligned store16 address: {:.08X}", addr);
+
+ let abs_addr = util::mask_region(addr);
+
+ match(util::contains(util::SPU_START: u32, util::SPU_SIZE: u32, abs_addr)) {
+ case let off: u32 =>
+ if (util::DEBUG) fmt::printfln("Unhandled write to SPU register {:X}", off)!; return;
+ case void => yield;
+ };
+
+ match(util::contains(util::TIMERS_START: u32, util::TIMERS_SIZE: u32, abs_addr)) {
+ case let off: u32 =>
+ if (util::DEBUG) fmt::printfln("Unhandled write to Timer register {:X}", off)!; return;
+ case void => yield;
+ };
+
+ match(util::contains(util::RAM_START: u32, util::RAM_SIZE: u32, abs_addr)) {
+ case let off: u32 => ram::store16(inter.ram, off, val); return;
+ case void => yield;
+ };
+
+ match(util::contains(util::IRQ_CONTROL_START: u32, util::IRQ_CONTROL_SIZE: u32, abs_addr)) {
+ case let off: u32 =>
+ if (util::DEBUG) fmt::printfln("IRQ control write: {:X} <- {:.04X}", off, val)!;
+ return;
+ case void => yield;
+ };
+
+ fmt::fatalf("Unhandled store16 into address {:.08X}", abs_addr);
+};
+
+export fn store8(inter: Interconnect, addr: u32, val: u8) void = {
+
+ let abs_addr = util::mask_region(addr);
+
+ match(util::contains(util::EXPANSION_2_START: u32, util::EXPANSION_2_SIZE: u32, abs_addr)) {
+ case let off: u32 =>
+ fmt::printfln("Unhandled write to Expansion 2 register {:X}", off)!; return;
+ case void => yield;
+ };
+
+ match(util::contains(util::RAM_START: u32, util::RAM_SIZE: u32, abs_addr)) {
+ case let off: u32 =>
+ return ram::store8(inter.ram, off, val);
+ case void => yield;
+ };
+
+ fmt::fatalf("Unhandled store8 into address {:.08X}", addr);
+};
+
+export fn load32(inter: Interconnect, addr: u32) u32 = {
+ if (addr % 4 != 0)
+ fmt::fatalf("Unaligned store32 into address {:.08X}", addr);
+
+ let abs_addr = util::mask_region(addr);
+
+ match(util::contains(util::BIOS_START: u32, util::BIOS_SIZE: u32, abs_addr)) {
+ case let off: u32 =>
+ return bios::load32(inter.bios, off);
+ case void =>
+ yield;
+ };
+
+ match(util::contains(util::RAM_START: u32, util::RAM_SIZE: u32, abs_addr)) {
+ case let off: u32 =>
+ return ram::load32(inter.ram, off);
+ case void =>
+ yield;
+ };
+
+ match(util::contains(util::IRQ_CONTROL_START: u32, util::IRQ_CONTROL_SIZE: u32, abs_addr)) {
+ case let off: u32 =>
+ fmt::printfln("IRQ control read {:X}", off)!; return 0;
+ case void =>
+ yield;
+ };
+
+ match(util::contains(util::DMA_START: u32, util::DMA_SIZE: u32, abs_addr)) {
+ case let off: u32 =>
+ fmt::printfln("DMA read {:X}", abs_addr)!; return 0;
+ case void =>
+ yield;
+ };
+
+ match(util::contains(util::GPU_START: u32, util::GPU_SIZE: u32, abs_addr)) {
+ case let off: u32 =>
+ fmt::printfln("GPU read {:X}", off)!;
+ let bit: u32 = switch(off) {
+ case 4 => yield 0x10000000;
+ case => yield 0;
+ };
+
+ return bit;
+ case void =>
+ yield;
+ };
+
+ fmt::fatalf("Unhandled fetch32 at address {:.08X}", abs_addr);
+};
+
+export fn load16(inter: Interconnect, addr: u32) u16 = {
+ let abs_addr = util::mask_region(addr);
+
+ match(util::contains(util::SPU_START: u32, util::SPU_SIZE: u32, abs_addr)) {
+ case let off: u32 =>
+ if (util::DEBUG) fmt::printfln("Unhandled read from SPU register: {:.08X}", abs_addr)!; return 0;
+ case void =>
+ yield;
+ };
+
+ match(util::contains(util::RAM_START: u32, util::RAM_SIZE: u32, abs_addr)) {
+ case let off: u32 =>
+ return ram::load16(inter.ram, off);
+ case void =>
+ yield;
+ };
+
+ match(util::contains(util::IRQ_CONTROL_START: u32, util::IRQ_CONTROL_SIZE: u32, abs_addr)) {
+ case let off: u32 =>
+ fmt::printfln("IRQ control read: {:.08X}", off)!; return 0;
+ case void =>
+ yield;
+ };
+
+ fmt::fatalf("Unhandled fetch16 at address {:.08X}", abs_addr);
+};
+
+export fn load8(inter: Interconnect, addr: u32) u8 = {
+ let abs_addr = util::mask_region(addr);
+
+ match(util::contains(util::BIOS_START: u32, util::BIOS_SIZE: u32, abs_addr)) {
+ case let off: u32 =>
+ return bios::load8(inter.bios, off);
+ case void =>
+ yield;
+ };
+
+ match(util::contains(util::RAM_START: u32, util::RAM_SIZE: u32, abs_addr)) {
+ case let off: u32 =>
+ return ram::load8(inter.ram, off);
+ case void =>
+ yield;
+ };
+
+ match(util::contains(util::EXPANSION_1_START: u32, util::EXPANSION_1_SIZE: u32, abs_addr)) {
+ case let off: u32 =>
+ return 0xff;
+ case void =>
+ yield;
+ };
+
+ fmt::fatalf("Unhandled fetch8 at address {:.08X}", abs_addr);
+};
diff --git a/main.ha b/main.ha
@@ -0,0 +1,23 @@
+use fmt;
+use bios;
+use interconnect;
+use cpu;
+use io;
+use os;
+use ram;
+
+
+export fn main() void = {
+ static const DEBUG = true;
+
+ let b: bios::BIOS = bios::new()!;
+ defer free(b.data);
+ let r: ram::ram = ram::new();
+ defer free(r.data);
+ let inter: interconnect::Interconnect = interconnect::new(b, r);
+ let cpu: cpu::CPU = cpu::new(inter);
+ for (true) {
+ cpu::run_next_instruction(&cpu);
+ };
+
+};
diff --git a/ram/ram.ha b/ram/ram.ha
@@ -0,0 +1,74 @@
+use fmt;
+
+export type ram = struct {
+ data: []u8
+};
+
+export fn new() ram = {
+
+ let b:[2*1024*1024]u8 = [0xca...];
+
+ return ram {
+ data = alloc(b),
+ };
+
+};
+
+export fn load32(ram: ram, o: u32) u32 = {
+ let off = o;
+
+ let b0 = ram.data[off + 0]: u32;
+ let b1 = ram.data[off + 1]: u32;
+ let b2 = ram.data[off + 2]: u32;
+ let b3 = ram.data[off + 3]: u32;
+
+ let res = b0 | (b1 << 8) | (b2 << 16) | (b3 << 24);
+
+ return res;
+
+};
+
+export fn load16(ram: ram, o: u32) u16 = {
+ let off = o;
+
+ let b0 = ram.data[off + 0]: u16;
+ let b1 = ram.data[off + 1]: u16;
+
+ let res = b0 | (b1 << 8);
+
+ return res;
+
+};
+
+export fn load8(ram: ram, o: u32) u8 = {
+ return ram.data[o:size];
+};
+
+export fn store16(ram: ram, o: u32, val: u16) void = {
+ let off = o;
+
+ let b0 = val: u8;
+ let b1 = (val >> 8): u8;
+
+ ram.data[o + 0] = b0;
+ ram.data[o + 1] = b1;
+};
+
+export fn store32(ram: ram, o: u32, val: u32) void = {
+ let off = o;
+
+ let b0 = val: u8;
+ let b1 = (val >> 8): u8;
+ let b2 = (val >> 16): u8;
+ let b3 = (val >> 24): u8;
+
+ ram.data[o + 0] = b0;
+ ram.data[o + 1] = b1;
+ ram.data[o + 2] = b2;
+ ram.data[o + 3] = b3;
+
+};
+
+export fn store8(ram: ram, o: u32, val: u8) void = {
+ ram.data[o + 0] = val;
+};
diff --git a/roms/scph1001.bin b/roms/scph1001.bin
Binary files differ.
diff --git a/spu/spu.ha b/spu/spu.ha
@@ -0,0 +1 @@
+// THE SPU
diff --git a/util/util.ha b/util/util.ha
@@ -0,0 +1,62 @@
+use fmt;
+use os;
+
+export const DEBUG: bool = false;
+
+export const BIOS_START: u32 = 0x1FC00000;
+export def BIOS_SIZE: u64 = 512 * 1024;
+
+export const MEMCONTROL_START: u32 = 0x1F801000;
+export const MEMCONTROL_SIZE: u64 = 36;
+
+export const RAM_SIZE_START: u32 = 0x1F801060;
+export const RAM_SIZE_SIZE: u64 = 4;
+
+export const CACHE_CONTROL_START: u32 = 0xFFFE0130;
+export const CACHE_CONTROL_SIZE: u64 = 4;
+
+export const RAM_START: u32 = 0x00000000;
+export const RAM_SIZE: u64 = 2 * 1024 * 1024;
+
+export const SPU_START: u32 = 0x1F801C00;
+export const SPU_SIZE: u64 = 640;
+
+export const EXPANSION_1_START: u32 = 0x1F000000;
+export const EXPANSION_1_SIZE: u64 = 512*1024;
+
+export const EXPANSION_2_START: u32 = 0x1F802000;
+export const EXPANSION_2_SIZE: u64 = 66;
+
+export const IRQ_CONTROL_START: u32 = 0x1F801070;
+export const IRQ_CONTROL_SIZE: u64 = 8;
+
+export const TIMERS_START: u32 = 0x1F801100;
+export const TIMERS_SIZE: u64 = 0x30;
+
+export const DMA_START: u32 = 0x1F801080;
+export const DMA_SIZE: u64 = 0x80;
+
+export const GPU_START: u32 = 0x1F801810;
+export const GPU_SIZE: u64 = 8;
+
+export const REGION_MASK: [8]u32 = [
+ // KUSEG: 2MB
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ // KSEG0: 512MB
+ 0x7fffffff,
+ // KSEG1: 512MB
+ 0x1fffffff,
+ // KSEG2: 1MB
+ 0xffffffff, 0xffffffff
+];
+
+export fn mask_region(addr: u32) u32 = {
+ let idx = (addr >> 29): size;
+ return addr & REGION_MASK[idx];
+};
+
+
+export fn contains(start: u32, length: u32, addr: u32) (u32 | void) = {
+ if (addr >= start && addr < start + length)
+ return addr-start;
+};