edea

A ps1 emulator in harelang
Log | Files | Refs

commit 03f6a95572eeb67c6350d003becbe75d5a90cc25
Author: Edea Kramer <edea@lunarcry.my.domain>
Date:   Wed, 13 Mar 2024 19:20:06 +0200

First commit

Diffstat:
A.gitignore | 2++
ATODO | 1+
Abios/bios.ha | 42++++++++++++++++++++++++++++++++++++++++++
Acpu/cpu.ha | 1048+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acpu/instruction/instruction.ha | 33+++++++++++++++++++++++++++++++++
Adma/dma.ha | 0
Ainterconnect/interconnect.ha | 235+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amain.ha | 23+++++++++++++++++++++++
Aram/ram.ha | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aroms/scph1001.bin | 0
Aspu/spu.ha | 1+
Autil/util.ha | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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; +};