Skip to content

Commit 466daa3

Browse files
committed
working on ip7 calibration again
rework ip7 calibration again, this time bin 1000 and 100 hz deltas and adjust step based on that
1 parent bc46731 commit 466daa3

1 file changed

Lines changed: 93 additions & 31 deletions

File tree

src/mips_core.rs

Lines changed: 93 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,16 @@ pub struct MipsCore {
8787
/// Key = `(delta >> 16) / 100 * 100`, value = number of occurrences.
8888
#[cfg(feature = "developer_ip7")]
8989
pub compare_delta_stats: std::collections::HashMap<u32, u32>,
90+
/// Learned slow-tick CP0 delta in hardware counts (16.16 fixed-point, >> 16 = integer counts).
91+
/// Initialised to 0 (unknown). First delta seen is assumed to be the 100 Hz (slow) tick.
92+
compare_delta_slow: u64,
93+
/// Learned fast-tick CP0 delta in hardware counts.
94+
/// Initialised to 0 (unknown). Set once we see a delta ~10x smaller than delta_slow.
95+
compare_delta_fast: u64,
96+
/// The raw 16.16 fixed-point delta programmed in the *previous* Compare write.
97+
/// Used for calibration: dt_ns/dc measure the old interval, so count_step must be
98+
/// computed against the old delta, not the new one. Zero = no previous write yet.
99+
compare_delta_prev: u64,
90100
pub cp0_status: u32, // 12: Status Register
91101
pub cp0_cause: u32, // 13: Cause Register
92102
pub cp0_epc: u64, // 14: Exception Program Counter
@@ -232,6 +242,9 @@ impl MipsCore {
232242
compare_last_instant: std::time::Instant::now(),
233243
#[cfg(feature = "developer_ip7")]
234244
compare_delta_stats: std::collections::HashMap::new(),
245+
compare_delta_slow: 0,
246+
compare_delta_fast: 0,
247+
compare_delta_prev: 0,
235248
cp0_status: 0,
236249
cp0_cause: 0,
237250
cp0_epc: 0,
@@ -442,6 +455,54 @@ impl MipsCore {
442455
}
443456
}
444457

458+
/// Bin a CP0 Compare delta into a target tick period in nanoseconds
459+
/// (1_000_000 for 1 kHz, 10_000_000 for 100 Hz).
460+
///
461+
/// `delta` may be in any consistent unit (e.g. raw 16.16 fixed-point) — only the
462+
/// ratios between slow and fast buckets matter. Maintains two learned buckets:
463+
/// `compare_delta_slow` (100 Hz, seeded on first call) and `compare_delta_fast`
464+
/// (~10x smaller, 1 kHz). All comparisons use ±5% fuzzy equality.
465+
/// Returns `Some(target_ns)` or `None` for a zero/degenerate delta.
466+
fn bin_compare_delta(&mut self, d: u64) -> Option<u64> {
467+
if d == 0 {
468+
return None;
469+
}
470+
// ±5% fuzzy equality.
471+
let fuzzy_eq = |a: u64, b: u64| -> bool {
472+
let threshold = a.max(b) * 5 / 100;
473+
a.abs_diff(b) <= threshold
474+
};
475+
476+
if self.compare_delta_slow == 0 {
477+
// First delta ever — seed slow bucket, assume 100 Hz.
478+
self.compare_delta_slow = d;
479+
return Some(10_000_000);
480+
}
481+
482+
if fuzzy_eq(d, self.compare_delta_slow) {
483+
return Some(10_000_000);
484+
}
485+
486+
if self.compare_delta_fast != 0 && fuzzy_eq(d, self.compare_delta_fast) {
487+
return Some(1_000_000);
488+
}
489+
490+
// Check if d ≈ slow/10 (i.e. ~10x smaller → fast tick).
491+
if fuzzy_eq(d * 10, self.compare_delta_slow) {
492+
self.compare_delta_fast = d;
493+
return Some(1_000_000);
494+
}
495+
496+
if d > self.compare_delta_slow {
497+
// Larger than slow — one-shot or low-freq timer; update slow bucket.
498+
self.compare_delta_slow = d;
499+
Some(10_000_000)
500+
} else {
501+
// Unrecognised intermediate — fall back to slow.
502+
Some(10_000_000)
503+
}
504+
}
505+
445506
/// Write CP0 register by index.
446507
/// When reg 12 (Status) is written, invokes `status_changed_cb` with (old, new).
447508
pub fn write_cp0(&mut self, reg: u32, value: u64) {
@@ -485,44 +546,45 @@ impl MipsCore {
485546
if self.compare_last_cycles != 0 {
486547
let dc = cycles_now.wrapping_sub(self.compare_last_cycles);
487548
let dt_ns = now.duration_since(self.compare_last_instant).as_nanos() as u64;
488-
let delta = self.cp0_compare.wrapping_sub(self.cp0_count);
489-
// Record delta in frequency map (rounded to nearest 100 hw-counts).
549+
// New delta being programmed (what the *next* interval will fire at),
550+
// stored as raw 16.16 fixed-point for use in the next calibration.
551+
let new_delta = self.cp0_compare.wrapping_sub(self.cp0_count);
490552
#[cfg(feature = "developer_ip7")]
491553
{
492-
let bucket = ((delta >> 16) as u32 / 100) * 100;
554+
let bucket = ((new_delta >> 16) as u32 / 100) * 100;
493555
*self.compare_delta_stats.entry(bucket).or_insert(0) += 1;
494556
}
495-
// Snap the intended tick period to 1ms or 10ms based on estimated target:
496-
// target_ns = delta * 2 * dt_ns / dc
497-
// then use that as the denominator multiplier instead of a fixed 1_000_000.
498-
const TARGET_1MS: u64 = 1_000_000;
499-
const TARGET_10MS: u64 = 10_000_000;
500-
if dc > 0 {
501-
let target_ns = delta.saturating_mul(dt_ns) / dc * 2;
502-
// If estimated target is under 3ms, treat as 1kHz tick, else 100Hz.
503-
let snapped_ns = if (target_ns >> 16) < 3_000_000 {
504-
TARGET_1MS
505-
} else {
506-
TARGET_10MS
507-
};
508-
let denom = dc.saturating_mul(snapped_ns);
509-
self.count_step = (delta.saturating_mul(dt_ns) / denom)
510-
.clamp(1 << 12, 10 << 15);
511-
#[cfg(feature = "developer_ip7")]
512-
{
513-
let total_samples: u32 = self.compare_delta_stats.values().sum();
514-
if total_samples <= 5 {
515-
eprintln!("compare calib: delta={} dt_ns={} dc={} target_ns={} ({}ms) snapped={}ms count_step={}",
516-
delta >> 16, dt_ns, dc,
517-
target_ns >> 16, (target_ns >> 16) / 1_000_000,
518-
snapped_ns / 1_000_000,
519-
self.count_step);
557+
// dt_ns/dc measure the interval since the last Compare write, which
558+
// ran under compare_delta_prev. Calibrate against that, not new_delta,
559+
// so a tick-rate switch doesn't mix old timing with the new delta.
560+
// If there was no previous delta (first write after reset), skip.
561+
let prev_delta = self.compare_delta_prev;
562+
if prev_delta != 0 {
563+
if let Some(snapped_ns) = self.bin_compare_delta(prev_delta) {
564+
if dc > 0 {
565+
let denom = dc.saturating_mul(snapped_ns);
566+
self.count_step = (prev_delta.saturating_mul(dt_ns) / denom)
567+
.clamp(1 << 12, 10 << 15);
568+
#[cfg(feature = "developer_ip7")]
569+
{
570+
let total_samples: u32 = self.compare_delta_stats.values().sum();
571+
if total_samples <= 10 {
572+
eprintln!("compare calib: prev_d={} new_d={} dt_ns={} dc={} \
573+
snapped={}ms slow={} fast={} count_step={}",
574+
prev_delta >> 16, new_delta >> 16, dt_ns, dc,
575+
snapped_ns / 1_000_000,
576+
self.compare_delta_slow >> 16,
577+
self.compare_delta_fast >> 16,
578+
self.count_step);
579+
}
580+
}
581+
} else {
582+
self.count_step = 1 << 15;
520583
}
584+
self.count_step_atomic.store(self.count_step, Ordering::Relaxed);
521585
}
522-
} else {
523-
self.count_step = 1 << 15;
524586
}
525-
self.count_step_atomic.store(self.count_step, Ordering::Relaxed);
587+
self.compare_delta_prev = new_delta;
526588
}
527589
// First write: keep default count_step (1<<15), just record state.
528590
self.compare_last_cycles = cycles_now;

0 commit comments

Comments
 (0)