Something went wrong. Try again.
A Playstation One (PS1) emulator fully written in Jam
Something went wrong. Try again.
jamstation bus.jam
14 kB · 377 lines
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378// SPDX-License-Identifier: MIT// Copyright (c) 2026 Raphael Amorim
// PSX memory bus.//// Routes word / halfword / byte reads and writes through the masked// physical address. Owns the RAM / BIOS / scratchpad / flat-IO / VRAM// buffers and the four device state buffers (gpu, dma, irq, timer).const { Gpu } = import("gpu");const { dmaRead32, dmaRead16, dmaRead8, dmaWrite32, dmaWrite16, dmaWrite8, createDma } = import("dma");const { irqRead32, irqRead16, irqRead8, irqWrite32, irqWrite16, irqWrite8, createIrq } = import("irq");const { Vec } = import("std/collections");const { Timer } = import("timer");const { createGte } = import("gte");const std = import("std");const { Cdrom } = import("cdrom");const { Disc, createDisc, discClose } = import("disc");const { Box } = import("std/box");const { padRead32, padRead16, padRead8, padWrite32, padWrite16, padWrite8, createPad } = import("pad");const { spuRead32, spuRead16, spuRead8, spuWrite32, spuWrite16, spuWrite8, createSpu, createSpuRam } = import("spu");const { Mdec, createMdecInput, createMdecOutput } = import("mdec");const { Sio1 } = import("sio1");const { Mcd, createMcdRam } = import("mcd");
pub const Bus = struct { ram: Vec(u8), bios: Vec(u8), spad: Vec(u8), io: Vec(u8), vram: Vec(u8), gpu: Gpu, dma: Vec(u32), irq: Vec(u32), timer: Timer, gte: Vec(u32), cdrom: Cdrom, disc: Box(Disc), pad: Vec(u8), spu: Vec(u8), spuram: Vec(u8), mdec: Mdec, mdecIn: Vec(u8), mdecOut: Vec(u8), sio1: Sio1, mcd: Mcd, mcdRam: Vec(u8), biosLoaded: u8, cfn drop(self: mut Self) { discClose(self.disc.ptrMut()); std.fmt.print("jamstation: disc closed\n"); }};
pub const BusRoute = struct { kind: u8, off: u32,};
const RAM_SIZE: u32 = 0x200000;const BIOS_SIZE: u32 = 0x80000;const SPAD_SIZE: u32 = 0x400;const IO_SIZE: u32 = 0x2000;const VRAM_SIZE: u32 = 0x100000;
pub fn regionMask(addr: u32) u32 { match ((addr >> 29) & 7) { 4 { return 0x7FFFFFFF; } 5 { return 0x1FFFFFFF; } _ { return 0xFFFFFFFF; } }}
pub fn bufRead32(buf: *mut[] u8, off: u32) u32 { return (buf[off] as u32) | ((buf[off + 1] as u32) << 8) | ((buf[off + 2] as u32) << 16) | ((buf[off + 3] as u32) << 24);}
pub fn bufRead16(buf: *mut[] u8, off: u32) u32 { return (buf[off] as u32) | ((buf[off + 1] as u32) << 8);}
pub fn bufRead8(buf: *mut[] u8, off: u32) u32 { return buf[off] as u32;}
pub fn bufWrite32(buf: *mut[] u8, off: u32, val: u32) { buf[off] = (val & 0xFF) as u8; buf[off + 1] = ((val >> 8) & 0xFF) as u8; buf[off + 2] = ((val >> 16) & 0xFF) as u8; buf[off + 3] = ((val >> 24) & 0xFF) as u8;}
pub fn bufWrite16(buf: *mut[] u8, off: u32, val: u32) { buf[off] = (val & 0xFF) as u8; buf[off + 1] = ((val >> 8) & 0xFF) as u8;}
pub fn bufWrite8(buf: *mut[] u8, off: u32, val: u32) { buf[off] = (val & 0xFF) as u8;}
// Per-region routing. A static IO-base/size table inlined// into an address comparator. `kind` values:// 0 = unmapped (reads return 0)// 1 = RAM// 2 = BIOS ROM// 3 = scratchpad// 4 = I/O register file// 5 = EXP1 (returns 0xFF on read with no cart loaded)// 6 = EXP2 (DTL debug-console region; only the ATC status byte matters)pub fn route(paddr: u32) BusRoute { if (paddr < 0x00800000) { const r = BusRoute { kind: 1, off: paddr & 0x001FFFFF }; return r; } if (paddr >= 0x1F000000 && paddr < 0x1F080000) { const r = BusRoute { kind: 5, off: paddr - 0x1F000000 }; return r; } if (paddr >= 0x1F800000 && paddr < 0x1F800400) { const r = BusRoute { kind: 3, off: paddr - 0x1F800000 }; return r; } if (paddr >= 0x1F801000 && paddr < 0x1F802000) { const r = BusRoute { kind: 4, off: paddr - 0x1F801000 }; return r; } if (paddr >= 0x1F802000 && paddr < 0x1FA00000) { const r = BusRoute { kind: 6, off: paddr - 0x1F802000 }; return r; } if (paddr >= 0x1FC00000 && paddr < 0x1FC80000) { const r = BusRoute { kind: 2, off: paddr - 0x1FC00000 }; return r; } if (paddr >= 0xFFFE0130 && paddr < 0xFFFE0134) { const r = BusRoute { kind: 4, off: 0x1F00 + (paddr - 0xFFFE0130) }; return r; } const r = BusRoute { kind: 0, off: 0 }; return r;}
pub fn createBus() Bus { // RAM/BIOS are zero-initialised. // // note!!! On Darwin uninitialised pages happen to read as zero (NOP), // on Linux glibc returns dirty memory and the CPU executes garbage. var ram = Vec(u8).filled(0, RAM_SIZE); var bios = Vec(u8).filled(0, BIOS_SIZE); var spad = Vec(u8).filled(0, SPAD_SIZE); var io = Vec(u8).filled(0, IO_SIZE); var vram = Vec(u8).filled(0, VRAM_SIZE);
// some default vals bufWrite32(io.ptr, 0x00, 0x1F000000); // EXP1_BASE bufWrite32(io.ptr, 0x04, 0x1F802000); // EXP2_BASE bufWrite32(io.ptr, 0x08, 0x0013243F); // EXP1_DELAY bufWrite32(io.ptr, 0x0C, 0x00003022); // EXP3_DELAY bufWrite32(io.ptr, 0x10, 0x0013243F); // BIOS_DELAY bufWrite32(io.ptr, 0x14, 0x200931E1); // SPU_DELAY bufWrite32(io.ptr, 0x18, 0x00020843); // CDROM_DELAY bufWrite32(io.ptr, 0x1C, 0x00070777); // EXP2_DELAY bufWrite32(io.ptr, 0x20, 0x00031125); // COM_DELAY bufWrite32(io.ptr, 0x60, 0x00000B88); // MC2 RAM_SIZE (2 MB mirror config)
var b: Bus = Bus { ram: ram, bios: bios, spad: spad, io: io, vram: vram, gpu: Gpu.init(), dma: createDma(), irq: createIrq(), timer: Timer.init(), gte: createGte(), cdrom: Cdrom.init(), disc: createDisc(), pad: createPad(), spu: createSpu(), spuram: createSpuRam(), mdec: Mdec.init(), mdecIn: createMdecInput(), mdecOut: createMdecOutput(), sio1: Sio1.init(), mcd: Mcd.init(), mcdRam: createMcdRam(), biosLoaded: 0, }; return b;}
pub fn ioRead32(bus: Bus, off: u32) u32 { match (off) { 0x040..=0x04F { return padRead32(bus.pad.ptr, bus.mcd, bus.mcdRam.ptr, off - 0x040); } 0x050..=0x05F { return bus.sio1.read32(off - 0x050); } 0x070..=0x07F { return irqRead32(bus.irq.ptr, off - 0x070); } 0x080..=0x0FF { return dmaRead32(bus.dma.ptr, off - 0x080); } 0x100..=0x12F { return bus.timer.read32(off - 0x100); } 0x800..=0x803 { return bus.cdrom.read32(off - 0x800); } 0x810..=0x817 { return bus.gpu.read32(bus.vram.ptr, off - 0x810); } 0x820..=0x827 { return bus.mdec.read32(bus.mdecOut.ptr, off - 0x820); } 0xC00..=0xFFF { return spuRead32(bus.spu.ptr, bus.spuram.ptr, off - 0xC00); } _ {} } if (off >= IO_SIZE) { return 0; } return bufRead32(bus.io.ptr, off);}
pub fn ioRead16(bus: Bus, off: u32) u32 { match (off) { 0x040..=0x04F { return padRead16(bus.pad.ptr, bus.mcd, bus.mcdRam.ptr, off - 0x040); } 0x050..=0x05F { return bus.sio1.read16(off - 0x050); } 0x070..=0x07F { return irqRead16(bus.irq.ptr, off - 0x070); } 0x080..=0x0FF { return dmaRead16(bus.dma.ptr, off - 0x080); } 0x100..=0x12F { return bus.timer.read16(off - 0x100); } 0x800..=0x803 { return bus.cdrom.read16(off - 0x800); } 0x810..=0x817 { return bus.gpu.read16(bus.vram.ptr, off - 0x810); } 0x820..=0x827 { return bus.mdec.read16(bus.mdecOut.ptr, off - 0x820); } 0xC00..=0xFFF { return spuRead16(bus.spu.ptr, bus.spuram.ptr, off - 0xC00); } _ {} } if (off >= IO_SIZE) { return 0; } return bufRead16(bus.io.ptr, off);}
pub fn ioRead8(bus: Bus, off: u32) u32 { match (off) { 0x040..=0x04F { return padRead8(bus.pad.ptr, bus.mcd, bus.mcdRam.ptr, off - 0x040); } 0x050..=0x05F { return bus.sio1.read8(off - 0x050); } 0x070..=0x07F { return irqRead8(bus.irq.ptr, off - 0x070); } 0x080..=0x0FF { return dmaRead8(bus.dma.ptr, off - 0x080); } 0x100..=0x12F { return bus.timer.read8(off - 0x100); } 0x800..=0x803 { return bus.cdrom.read8(off - 0x800); } 0x810..=0x817 { return bus.gpu.read8(bus.vram.ptr, off - 0x810); } 0x820..=0x827 { return bus.mdec.read8(bus.mdecOut.ptr, off - 0x820); } 0xC00..=0xFFF { return spuRead8(bus.spu.ptr, bus.spuram.ptr, off - 0xC00); } _ {} } if (off >= IO_SIZE) { return 0; } return bufRead8(bus.io.ptr, off);}
pub fn ioWrite32(bus: Bus, cop0: *mut[] u32, off: u32, val: u32) { match (off) { 0x040..=0x04F { padWrite32(bus.pad.ptr, bus.mcd, off - 0x040, val); return; } 0x050..=0x05F { bus.sio1.write32(off - 0x050, val); return; } 0x070..=0x07F { irqWrite32(bus.irq.ptr, cop0, off - 0x070, val); return; } 0x080..=0x0FF { dmaWrite32(bus.dma.ptr, bus, cop0, off - 0x080, val); return; } 0x100..=0x12F { bus.timer.write32(bus.irq.ptr, cop0, off - 0x100, val); return; } 0x800..=0x803 { bus.cdrom.write32(off - 0x800, val); return; } 0x810..=0x817 { bus.gpu.write32(bus.vram.ptr, off - 0x810, val); return; } 0x820..=0x827 { bus.mdec.write32(bus.mdecIn.ptr, bus.mdecOut.ptr, off - 0x820, val); return; } 0xC00..=0xFFF { spuWrite32(bus.spu.ptr, bus.spuram.ptr, off - 0xC00, val); return; } _ {} } if (off >= IO_SIZE) { return; } bufWrite32(bus.io.ptr, off, val);}
pub fn ioWrite16(bus: Bus, cop0: *mut[] u32, off: u32, val: u32) { match (off) { 0x040..=0x04F { padWrite16(bus.pad.ptr, bus.mcd, off - 0x040, val); return; } 0x050..=0x05F { bus.sio1.write16(off - 0x050, val); return; } 0x070..=0x07F { irqWrite16(bus.irq.ptr, cop0, off - 0x070, val); return; } 0x080..=0x0FF { dmaWrite16(bus.dma.ptr, bus, cop0, off - 0x080, val); return; } 0x100..=0x12F { bus.timer.write16(bus.irq.ptr, cop0, off - 0x100, val); return; } 0x800..=0x803 { bus.cdrom.write16(off - 0x800, val); return; } 0x810..=0x817 { bus.gpu.write16(bus.vram.ptr, off - 0x810, val); return; } 0x820..=0x827 { bus.mdec.write16(bus.mdecIn.ptr, bus.mdecOut.ptr, off - 0x820, val); return; } 0xC00..=0xFFF { spuWrite16(bus.spu.ptr, bus.spuram.ptr, off - 0xC00, val); return; } _ {} } if (off >= IO_SIZE) { return; } bufWrite16(bus.io.ptr, off, val);}
pub fn ioWrite8(bus: Bus, cop0: *mut[] u32, off: u32, val: u32) { match (off) { 0x040..=0x04F { padWrite8(bus.pad.ptr, bus.mcd, off - 0x040, val); return; } 0x050..=0x05F { bus.sio1.write8(off - 0x050, val); return; } 0x070..=0x07F { irqWrite8(bus.irq.ptr, cop0, off - 0x070, val); return; } 0x080..=0x0FF { dmaWrite8(bus.dma.ptr, bus, cop0, off - 0x080, val); return; } 0x100..=0x12F { bus.timer.write8(bus.irq.ptr, cop0, off - 0x100, val); return; } 0x800..=0x803 { bus.cdrom.write8(off - 0x800, val); return; } 0x810..=0x817 { bus.gpu.write8(bus.vram.ptr, off - 0x810, val); return; } 0x820..=0x827 { bus.mdec.write8(bus.mdecIn.ptr, bus.mdecOut.ptr, off - 0x820, val); return; } 0xC00..=0xFFF { spuWrite8(bus.spu.ptr, bus.spuram.ptr, off - 0xC00, val); return; } _ {} } if (off >= IO_SIZE) { return; } bufWrite8(bus.io.ptr, off, val);}
// `busRead/Write` is the public memory-access surface - `cop0` is only// needed when the access target is an IO region that may update the// COP0 cause register; a wrapper that hardcodes cop0=NULL would be// unsound, so we always thread it through.pub fn busRead32(bus: Bus, addr: u32) u32 { const paddr: u32 = addr & regionMask(addr); const r: BusRoute = route(paddr); match (r.kind) { 1 { return bufRead32(bus.ram.ptr, r.off & (RAM_SIZE - 1)); } 2 { return bufRead32(bus.bios.ptr, r.off & (BIOS_SIZE - 1)); } 3 { return bufRead32(bus.spad.ptr, r.off & (SPAD_SIZE - 1)); } 4 { return ioRead32(bus, r.off); } // kind 5: EXP1 (no cart loaded) - reads return 0xFF per byte. 5 { return 0xFFFFFFFF; } _ { return 0; } }}
pub fn busRead16(bus: Bus, addr: u32) u32 { const paddr: u32 = addr & regionMask(addr); const r: BusRoute = route(paddr); match (r.kind) { 1 { return bufRead16(bus.ram.ptr, r.off & (RAM_SIZE - 1)); } 2 { return bufRead16(bus.bios.ptr, r.off & (BIOS_SIZE - 1)); } 3 { return bufRead16(bus.spad.ptr, r.off & (SPAD_SIZE - 1)); } 4 { return ioRead16(bus, r.off); } 5 { return 0xFFFF; } _ { return 0; } }}
pub fn busRead8(bus: Bus, addr: u32) u32 { const paddr: u32 = addr & regionMask(addr); const r: BusRoute = route(paddr); match (r.kind) { 1 { return bufRead8(bus.ram.ptr, r.off & (RAM_SIZE - 1)); } 2 { return bufRead8(bus.bios.ptr, r.off & (BIOS_SIZE - 1)); } 3 { return bufRead8(bus.spad.ptr, r.off & (SPAD_SIZE - 1)); } 4 { return ioRead8(bus, r.off); } 5 { return 0xFF; } 6 { if (r.off == 0) { return 8; } return 0; } _ { return 0; } }}
pub fn busWrite32(bus: Bus, cop0: *mut[] u32, addr: u32, val: u32) { const paddr: u32 = addr & regionMask(addr); const r: BusRoute = route(paddr); match (r.kind) { 1 { bufWrite32(bus.ram.ptr, r.off & (RAM_SIZE - 1), val); } 3 { bufWrite32(bus.spad.ptr, r.off & (SPAD_SIZE - 1), val); } 4 { ioWrite32(bus, cop0, r.off, val); } _ {} }}
pub fn busWrite16(bus: Bus, cop0: *mut[] u32, addr: u32, val: u32) { const paddr: u32 = addr & regionMask(addr); const r: BusRoute = route(paddr); match (r.kind) { 1 { bufWrite16(bus.ram.ptr, r.off & (RAM_SIZE - 1), val); } 3 { bufWrite16(bus.spad.ptr, r.off & (SPAD_SIZE - 1), val); } 4 { ioWrite16(bus, cop0, r.off, val); } _ {} }}
pub fn busWrite8(bus: Bus, cop0: *mut[] u32, addr: u32, val: u32) { const paddr: u32 = addr & regionMask(addr); const r: BusRoute = route(paddr); match (r.kind) { 1 { bufWrite8(bus.ram.ptr, r.off & (RAM_SIZE - 1), val); } 3 { bufWrite8(bus.spad.ptr, r.off & (SPAD_SIZE - 1), val); } 4 { ioWrite8(bus, cop0, r.off, val); } _ {} }}