Something went wrong. Try again.
A Playstation One (PS1) emulator fully written in Jam
Something went wrong. Try again.
jamstation timer.jam
11 kB · 314 lines
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315// SPDX-License-Identifier: MIT// Copyright (c) 2026 Raphael Amorim
const { irqRaise, IC_TIMER0, IC_TIMER1, IC_TIMER2 } = import("irq");pub const TimerChannel = struct { counter: f32, target: u32, syncEnable: u32, syncMode: u32, irqTarget: u32, irqMax: u32, irqRepeat: u32, irqToggle: u32, irq: u32, targetReached: u32, maxReached: u32, resetTarget: u32, irqFired: u32, paused: u32, blankOnce: u32, clkSource: u32,
pub fn init() Self { return Self { counter: 0.0, target: 0, syncEnable: 0, syncMode: 0, irqTarget: 0, irqMax: 0, irqRepeat: 0, irqToggle: 0, irq: 0, targetReached: 0, maxReached: 0, resetTarget: 0, irqFired: 0, paused: 0, blankOnce: 0, clkSource: 0, }; }};
pub const Timer = struct { hblank: u32, vblank: u32, channels: [3]TimerChannel, // Timer 0 dot-clock ticks per sysclk cycle = (11/7)/hdiv, refreshed from // the GPU display mode each frame (setDotclock). // Default = 320-wide (hdiv 8). dotDiv: f32,
pub fn init() Self { return Self { hblank: 0, vblank: 0, channels: [ TimerChannel.init(), TimerChannel.init(), TimerChannel.init(), ], dotDiv: 11.0 / 7.0 / 8.0, }; }
pub fn setDotclock(self: mut Self, dm: u32) { if ((dm & 0x40) != 0) { self.dotDiv = 11.0 / 7.0 / 7.0; return; } const hr: u32 = dm & 3; var div: f32 = 10.0; if (hr == 1) { div = 8.0; } else if (hr == 2) { div = 5.0; } else if (hr == 3) { div = 4.0; } self.dotDiv = 11.0 / 7.0 / div; }
pub fn getMode(self: mut Self, idx: u32) u32 { var v: u32 = 0; v = v | ((self.channels[idx].syncEnable & 1) << 0); v = v | ((self.channels[idx].syncMode & 3) << 1); v = v | ((self.channels[idx].resetTarget & 1) << 3); v = v | ((self.channels[idx].irqTarget & 1) << 4); v = v | ((self.channels[idx].irqMax & 1) << 5); v = v | ((self.channels[idx].irqRepeat & 1) << 6); v = v | ((self.channels[idx].irqToggle & 1) << 7); v = v | ((self.channels[idx].clkSource & 3) << 8); v = v | ((self.channels[idx].irq & 1) << 10); v = v | ((self.channels[idx].targetReached & 1) << 11); v = v | ((self.channels[idx].maxReached & 1) << 12); self.channels[idx].targetReached = 0; self.channels[idx].maxReached = 0; return v; }
pub fn setMode(self: mut Self, idx: u32, val: u32) { self.channels[idx].syncEnable = (val >> 0) & 1; self.channels[idx].syncMode = (val >> 1) & 3; self.channels[idx].resetTarget = (val >> 3) & 1; self.channels[idx].irqTarget = (val >> 4) & 1; self.channels[idx].irqMax = (val >> 5) & 1; self.channels[idx].irqRepeat = (val >> 6) & 1; self.channels[idx].irqToggle = (val >> 7) & 1; self.channels[idx].clkSource = (val >> 8) & 3; self.channels[idx].targetReached = 0; self.channels[idx].maxReached = 0; self.channels[idx].irq = 1; self.channels[idx].irqFired = 0; self.channels[idx].counter = 0.0; self.channels[idx].blankOnce = 0; self.channels[idx].paused = 0;
const syncMode: u32 = self.channels[idx].syncMode; const syncEn: u32 = self.channels[idx].syncEnable; if (idx == 0) { if (syncMode != 1 && syncMode != 2 && syncEn != 0) { var p: u32 = self.hblank; if (syncMode == 3) { p = 1; } self.channels[idx].paused = p; } } if (idx == 1) { if (syncMode != 1 && syncMode != 2 && syncEn != 0) { var p: u32 = self.vblank; if (syncMode == 3) { p = 1; } self.channels[idx].paused = p; } } if (idx == 2) { if (syncEn != 0) { if (syncMode == 0 || syncMode == 3) { self.channels[idx].paused = 1; } } } }
pub fn handleIrq(self: mut Self, idx: u32, ic: *mut[] u32, cop0: *mut[] u32) { const counter: f32 = self.channels[idx].counter; const target: u32 = self.channels[idx].target; var fireIrq: u32 = 0;
if (counter > (target as f32)) { self.channels[idx].targetReached = 1; if (self.channels[idx].resetTarget != 0) { self.channels[idx].counter = 0.0; } if (self.channels[idx].irqTarget != 0) { fireIrq = 1; } } if (counter > 65535.0) { self.channels[idx].counter = 0.0; self.channels[idx].maxReached = 1; if (self.channels[idx].irqMax != 0) { fireIrq = 1; } } if (fireIrq == 0) { return; }
if (self.channels[idx].irqToggle == 0) { self.channels[idx].irq = 0; } else { self.channels[idx].irq = self.channels[idx].irq ^ 1; }
const trigger: bool = self.channels[idx].irq == 0;
if (self.channels[idx].irqRepeat == 0) { if (trigger && self.channels[idx].irqFired == 0) { self.channels[idx].irqFired = 1; } else { return; } }
self.channels[idx].irq = 1;
if (trigger) { var line: u32 = IC_TIMER0; match (idx) { 1 { line = IC_TIMER1; } 2 { line = IC_TIMER2; } _ {} } irqRaise(ic, cop0, line); } }
// +0 counter (16-bit) // +4 mode (16-bit, read also clears targetReached / maxReached) // +8 target (16-bit) pub fn read32(self: mut Self, off: u32) u32 { const idx: u32 = off >> 4; const reg: u32 = off & 0xF; if (idx >= 3) { return 0; } match (reg) { 0 { return self.channels[idx].counter as u32; } 4 { return self.getMode(idx); } 8 { return self.channels[idx].target; } _ { return 0; } } }
pub fn read16(self: mut Self, off: u32) u32 { return self.read32(off) & 0xFFFF; } pub fn read8(self: mut Self, off: u32) u32 { return self.read32(off) & 0xFF; }
pub fn write32(self: mut Self, ic: *mut[] u32, cop0: *mut[] u32, off: u32, val: u32) { const idx: u32 = off >> 4; const reg: u32 = off & 0xF; if (idx >= 3) { return; } match (reg) { 0 { self.channels[idx].counter = (val & 0xFFFF) as f32; } 4 { self.setMode(idx, val); } 8 { self.channels[idx].target = val & 0xFFFF; } _ {} } self.handleIrq(idx, ic, cop0); }
pub fn write16(self: mut Self, ic: *mut[] u32, cop0: *mut[] u32, off: u32, val: u32) { self.write32(ic, cop0, off, val & 0xFFFF); }
pub fn write8(self: mut Self, ic: *mut[] u32, cop0: *mut[] u32, off: u32, val: u32) { self.write32(ic, cop0, off, val & 0xFF); }
pub fn update(self: mut Self, ic: *mut[] u32, cop0: *mut[] u32) { self.updateCyc(ic, cop0, 2); }
pub fn updateCyc(self: mut Self, ic: *mut[] u32, cop0: *mut[] u32, cyc: u32) { self.updateOne(0, 2, ic, cop0); self.updateOne(1, 2, ic, cop0); self.updateOne(2, 2, ic, cop0); }
pub fn updateOne(self: mut Self, idx: u32, cyc: u32, ic: *mut[] u32, cop0: *mut[] u32) { if (self.channels[idx].paused != 0) { return; } // Timer 1 sourced from hblank is incremented in hblankBegin. if (idx == 1 && (self.channels[idx].clkSource & 1) != 0) { return; } const cf: f32 = cyc as f32; if (idx == 2 && self.channels[idx].clkSource >= 2) { self.channels[idx].counter = self.channels[idx].counter + cf / 8.0; } else if (idx == 0 && (self.channels[idx].clkSource & 1) != 0) { self.channels[idx].counter = self.channels[idx].counter + cf * self.dotDiv; } else { self.channels[idx].counter = self.channels[idx].counter + cf; } self.handleIrq(idx, ic, cop0); }
pub fn hblankBegin(self: mut Self, ic: *mut[] u32, cop0: *mut[] u32) { self.hblank = 1;
if ((self.channels[1].clkSource & 1) != 0 && self.channels[1].paused == 0) { self.channels[1].counter = self.channels[1].counter + 1.0; self.handleIrq(1, ic, cop0); }
if (self.channels[0].syncEnable == 0) { return; } match (self.channels[0].syncMode) { 0 { self.channels[0].paused = 1; } 1 { self.channels[0].counter = 0.0; } 2 { self.channels[0].counter = 0.0; self.channels[0].paused = 0; } 3 { if (self.channels[0].blankOnce == 0) { self.channels[0].blankOnce = 1; self.channels[0].syncEnable = 0; self.channels[0].paused = 0; } } _ {} } }
pub fn hblankEnd(self: mut Self) { self.hblank = 0; if (self.channels[0].syncEnable == 0) { return; } match (self.channels[0].syncMode) { 0 { self.channels[0].paused = 0; } 2 { self.channels[0].paused = 1; } _ {} } }
pub fn vblankBegin(self: mut Self, ic: *mut[] u32, cop0: *mut[] u32) { self.vblank = 1;
if (self.channels[1].syncEnable == 0) { return; } match (self.channels[1].syncMode) { 0 { self.channels[1].paused = 1; } 1 { self.channels[1].counter = 0.0; } 2 { self.channels[1].counter = 0.0; self.channels[1].paused = 0; } 3 { if (self.channels[1].blankOnce == 0) { self.channels[1].blankOnce = 1; self.channels[1].syncEnable = 0; self.channels[1].paused = 0; } } _ {} } }
pub fn vblankEnd(self: mut Self) { self.vblank = 0; if (self.channels[1].syncEnable == 0) { return; } match (self.channels[1].syncMode) { 0 { self.channels[1].paused = 0; } 2 { self.channels[1].paused = 1; } _ {} } }};