mirror of
https://github.com/simias/rustation.git
synced 2025-04-02 10:52:36 -04:00
Most of the codebase implements Encodable/Decodable now, with the exception of the BIOS, CD image and gamepads. The BIOS checksum is stored however, in order for the frontend to know which file to load.
239 lines
6.6 KiB
Rust
239 lines
6.6 KiB
Rust
//! XXX: All of this is very much *not* overflow tolerant. I'm just
|
|
//! hoping that a u64 will work for the time being but with the fixed
|
|
//! point representations shifting things around it's probably going
|
|
//! to be a problem sooner or later.
|
|
|
|
use std::{fmt};
|
|
|
|
/// List of all peripherals requiring a TimeSheet. The value of the
|
|
/// enum is used as the index in the timesheet table
|
|
#[derive(Clone, Copy, Debug, RustcDecodable, RustcEncodable)]
|
|
pub enum Peripheral {
|
|
/// Graphics Processing Unit
|
|
Gpu,
|
|
/// Timers
|
|
Timer0,
|
|
Timer1,
|
|
Timer2,
|
|
/// Gamepad/Memory Card controller
|
|
PadMemCard,
|
|
/// CD-ROM controller
|
|
CdRom,
|
|
}
|
|
|
|
|
|
/// Struct keeping track of the various peripheral's emulation advancement.
|
|
#[derive(RustcDecodable, RustcEncodable)]
|
|
pub struct TimeKeeper {
|
|
/// Counter keeping track of the current date. Unit is a period of
|
|
/// the CPU clock at 33.8685MHz (~29.5ns)
|
|
now: Cycles,
|
|
/// Next time a peripheral needs an update
|
|
next_sync: Cycles,
|
|
/// Time sheets for keeping track of the various peripherals
|
|
timesheets: [TimeSheet; 6],
|
|
}
|
|
|
|
impl TimeKeeper {
|
|
pub fn new() -> TimeKeeper {
|
|
TimeKeeper {
|
|
now: 0,
|
|
// Force a sync at the start to initialize evrything
|
|
next_sync: 0,
|
|
timesheets: [TimeSheet::new(); 6],
|
|
}
|
|
}
|
|
|
|
pub fn now(&self) -> Cycles {
|
|
self.now
|
|
}
|
|
|
|
pub fn tick(&mut self, cycles: Cycles) {
|
|
self.now += cycles;
|
|
}
|
|
|
|
/// Synchronize the timesheet for the given peripheral and return
|
|
/// the elapsed time synce the last sync.
|
|
pub fn sync(&mut self, who: Peripheral) -> Cycles {
|
|
self.timesheets[who as usize].sync(self.now)
|
|
}
|
|
|
|
pub fn set_next_sync_delta(&mut self, who: Peripheral, delta: Cycles) {
|
|
let date = self.now + delta;
|
|
|
|
self.timesheets[who as usize].set_next_sync(date);
|
|
|
|
if date < self.next_sync {
|
|
self.next_sync = date;
|
|
}
|
|
}
|
|
|
|
/// Set next sync *only* if it's sooner than what's already
|
|
/// configured.
|
|
pub fn maybe_set_next_sync(&mut self,
|
|
who: Peripheral,
|
|
date: Cycles) {
|
|
let timesheet = &mut self.timesheets[who as usize];
|
|
|
|
let next_sync = timesheet.next_sync();
|
|
|
|
if next_sync > date {
|
|
timesheet.set_next_sync(date);
|
|
}
|
|
}
|
|
|
|
/// Set next sync delta *only* if it's sooner than what's already
|
|
/// configured.
|
|
pub fn maybe_set_next_sync_delta(&mut self,
|
|
who: Peripheral,
|
|
delta: Cycles) {
|
|
let date = self.now + delta;
|
|
|
|
self.maybe_set_next_sync(who, date);
|
|
}
|
|
|
|
/// Called by a peripheral when there's no asynchronous event
|
|
/// scheduled.
|
|
pub fn no_sync_needed(&mut self, who: Peripheral) {
|
|
// Instead of disabling the sync completely we can just use a
|
|
// distant date. Peripheral's syncs should be idempotent
|
|
// anyway.
|
|
self.timesheets[who as usize].set_next_sync(Cycles::max_value());
|
|
}
|
|
|
|
pub fn sync_pending(&self) -> bool{
|
|
self.next_sync <= self.now
|
|
}
|
|
|
|
pub fn needs_sync(&self, who: Peripheral) -> bool {
|
|
self.timesheets[who as usize].needs_sync(self.now)
|
|
}
|
|
|
|
pub fn update_sync_pending(&mut self) {
|
|
self.next_sync =
|
|
self.timesheets.iter().map(|t| t.next_sync).min().unwrap();
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for TimeKeeper {
|
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
|
let now = self.now;
|
|
let cpu_freq = ::cpu::CPU_FREQ_HZ as Cycles;
|
|
|
|
let seconds = now / cpu_freq;
|
|
let rem = now % cpu_freq;
|
|
|
|
write!(fmt, "{}s+{:08}", seconds, rem)
|
|
}
|
|
}
|
|
|
|
|
|
#[derive(Clone, Copy, Debug, RustcDecodable, RustcEncodable)]
|
|
/// Struct used to keep track of individual peripherals
|
|
struct TimeSheet {
|
|
/// Date of the last synchronization
|
|
last_sync: Cycles,
|
|
/// Date of the next "forced" sync
|
|
next_sync: Cycles,
|
|
}
|
|
|
|
impl TimeSheet {
|
|
|
|
fn new() -> TimeSheet {
|
|
TimeSheet {
|
|
last_sync: 0,
|
|
// We force a synchronization at startup to initialize
|
|
// everything
|
|
next_sync: 0,
|
|
}
|
|
}
|
|
|
|
/// Forward the time sheet to the current date and return the
|
|
/// elapsed time since the last sync.
|
|
fn sync(&mut self, now: Cycles) -> Cycles {
|
|
let delta = now - self.last_sync;
|
|
|
|
self.last_sync = now;
|
|
|
|
delta
|
|
}
|
|
|
|
fn next_sync(&self) -> Cycles {
|
|
self.next_sync
|
|
}
|
|
|
|
fn set_next_sync(&mut self, when: Cycles) {
|
|
self.next_sync = when;
|
|
}
|
|
|
|
fn needs_sync(&self, now: Cycles) -> bool {
|
|
self.next_sync <= now
|
|
}
|
|
}
|
|
|
|
/// 64bit timestamps will wrap in roughly 17271 years with a CPU clock
|
|
/// at 33.8685MHz so it should be plenty enough.
|
|
pub type Cycles = u64;
|
|
|
|
/// Fixed point representation of a cycle counter used to store
|
|
/// non-integer cycle counts. Required because the CPU and GPU clocks
|
|
/// have a non-integer ratio.
|
|
#[derive(Clone, Copy, Debug, RustcDecodable, RustcEncodable)]
|
|
pub struct FracCycles(Cycles);
|
|
|
|
impl FracCycles {
|
|
pub fn from_fp(val: Cycles) -> FracCycles {
|
|
FracCycles(val)
|
|
}
|
|
|
|
pub fn from_f32(val: f32) -> FracCycles {
|
|
let precision = (1u32 << FracCycles::frac_bits()) as f32;
|
|
|
|
FracCycles((val * precision) as Cycles)
|
|
}
|
|
|
|
pub fn from_cycles(val: Cycles) -> FracCycles {
|
|
FracCycles(val << FracCycles::frac_bits())
|
|
}
|
|
|
|
/// Return the raw fixed point value
|
|
pub fn get_fp(self) -> Cycles {
|
|
self.0
|
|
}
|
|
|
|
/// Return the number of fractional bits in the fixed point
|
|
/// representation
|
|
pub fn frac_bits() -> Cycles {
|
|
16
|
|
}
|
|
|
|
pub fn add(self, val: FracCycles) -> FracCycles {
|
|
FracCycles(self.get_fp() + val.get_fp())
|
|
}
|
|
|
|
pub fn multiply(self, mul: FracCycles) -> FracCycles {
|
|
let v = self.get_fp() * mul.get_fp();
|
|
|
|
// The shift amount is doubled during the multiplication so we
|
|
// have to shift it back to its normal position.
|
|
FracCycles(v >> FracCycles::frac_bits())
|
|
}
|
|
|
|
pub fn divide(self, denominator: FracCycles) -> FracCycles {
|
|
// In order not to lose precision we must shift the numerator
|
|
// once more *before* the division. Otherwise the division of
|
|
// the two shifted value would only give us the integer part
|
|
// of the result.
|
|
let numerator = self.get_fp() << FracCycles::frac_bits();
|
|
|
|
FracCycles(numerator / denominator.get_fp())
|
|
}
|
|
|
|
pub fn ceil(self) -> Cycles {
|
|
let shift = FracCycles::frac_bits();
|
|
|
|
let align = (1 << shift) - 1;
|
|
|
|
(self.0 + align) >> shift
|
|
}
|
|
}
|