aboutsummaryrefslogtreecommitdiffstats
path: root/psp_out/timing_validate.py
blob: 66ad82e9e570ff2bc8b2879e678c24e21f91ad41 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
"""Validate the cmd 0x0d accept/reject timing separation.

Interleaved A/B so clock drift and USB jitter hit both arms equally.
"""
import ctypes, time, statistics
from tinygrad import Device
from tinygrad.runtime.autogen.am import am

d = Device["AMD"]; adev = d.iface.dev_impl; psp = adev.psp
SZ = ctypes.sizeof(am.struct_psp_gfx_cmd_resp); C = 28

def timed(cid, words):
    adev.vram.view(psp.cmd_paddr, SZ)[:] = b"\x00" * SZ
    c = am.struct_psp_gfx_cmd_resp(cmd_id=cid)
    buf = bytearray(memoryview(c).cast('B'))
    for i, v in words.items():
        buf[C+i*4:C+4+i*4] = int(v & 0xFFFFFFFF).to_bytes(4, 'little')
    cmd = am.struct_psp_gfx_cmd_resp.from_buffer_copy(bytes(buf))
    t0 = time.perf_counter()
    try: psp._ring_submit(cmd)
    except Exception: pass
    return (time.perf_counter() - t0) * 1e3

N = 120
A, B = [], []
for _ in range(6): timed(0x7e, {})
for i in range(N):
    A.append(timed(0x0d, {0: 3, 1: 0, 2: 1}))   # ACCEPT
    B.append(timed(0x0d, {0: 0xF}))             # REJECT

def stats(x): return statistics.median(x), statistics.mean(x), statistics.pstdev(x)
ma, aa, sa = stats(A); mb, ab, sb = stats(B)
print(f"  ACCEPT (idx=3):  median={ma:.3f}  mean={aa:.3f}  sd={sa:.3f}   n={N}")
print(f"  REJECT (idx=0xF): median={mb:.3f}  mean={ab:.3f}  sd={sb:.3f}   n={N}")
print(f"  median separation: {ma-mb:+.3f} ms")

# Mann-Whitney U via rank sum (no scipy)
comb = sorted([(v,0) for v in A] + [(v,1) for v in B])
ranks = {}
i = 0
rsum = 0.0
while i < len(comb):
    j = i
    while j+1 < len(comb) and comb[j+1][0] == comb[i][0]: j += 1
    r = (i+j)/2.0 + 1
    for k in range(i, j+1):
        if comb[k][1] == 0: rsum += r
    i = j+1
n1 = n2 = N
U = rsum - n1*(n1+1)/2
mu = n1*n2/2
sd = (n1*n2*(n1+n2+1)/12) ** 0.5
z = (U-mu)/sd
print(f"  Mann-Whitney U={U:.0f}  z={z:+.2f}  (|z|>3 => clearly separable)")

# how well does a single sample classify?
thr = (ma+mb)/2
acc = (sum(1 for v in A if v > thr) + sum(1 for v in B if v <= thr)) / (2*N)
print(f"  single-sample classification accuracy at threshold {thr:.3f} ms: {100*acc:.1f}%")
print("\nsOS alive:", psp.is_sos_alive())