segment_testrom: read T2 after the load delay slot and mask the delta to 16 bits

The Timer 2 subtraction sat in the second lw's load delay slot, so it read
the stale register, and the difference was not masked to T2's 16 bits. The
Beetle disc-boot run of the probe returned 0xFFFFFFF3-style values for
every delta. Add a nop after the load and an andi 0xFFFF.

The layout shifts by six words: probe_run moves from 0x000100C0 to
0x000100D8. The ledger ids are unchanged; fetch-tag-segment now counts 27
tags and alias-fetch-coherence reads 10 vs Beetle 5 for the moved leaf.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Shokunin
2026-09-29 12:53:02 -07:00
parent 3afa4a0b41
commit ba18f992a5
+13 -6
View File
@@ -15,11 +15,11 @@ The program exercises each place the segment reaches:
R+0x00 $ra seen by `leaf` after `jal` (link, KUSEG)
R+0x04 $ra seen by `leaf` after `bgezal` (link, KUSEG)
R+0x08 $ra seen by `seeded` after `jalr` (link, KUSEG)
R+0x10 T2 cycles around `probe_run` at its KUSEG home (cached fetch)
R+0x10 T2 ticks around `probe_run` at its KUSEG home (cached fetch)
R+0x14 `getpc` link inside that run (0x0001xxxx)
R+0x18 T2 cycles around `probe_run` via its KSEG0 alias (cached fetch)
R+0x18 T2 ticks around `probe_run` via its KSEG0 alias (cached fetch)
R+0x1C `getpc` link inside that run (0x8001xxxx)
R+0x20 T2 cycles around `probe_run` via its KSEG1 alias (uncached, +4/fetch)
R+0x20 T2 ticks around `probe_run` via its KSEG1 alias (uncached, +4/fetch)
R+0x24 `getpc` link inside that run (0xA001xxxx)
`seeded` is entered through a register. The test seeds it in the link
@@ -28,8 +28,11 @@ its `getpc` call returns, which is enough to show per-instruction uncached
fetch charging. The results block R (0x00011000) lies outside the loaded
image, so the stores never touch code pages. Timer 2 is put in mode 0
(system clock, free-running) first. The T2 deltas include the call overhead,
which is the same for all three runs. The program ends in a `j .` spin, where
an oracle run can read R.
which is the same for all three runs. Each delta is (after - before) & 0xFFFF,
because T2 is a 16-bit counter that wraps. The subtraction waits one `nop`
after the second `lw`: it would otherwise sit in the load delay slot and read
the stale register. The program ends in a `j .` spin, where an oracle run can
read R.
The MIPS is hand-encoded, so the output needs no toolchain and is
reproducible byte for byte. recompiler/tests/test_segment_aware_codegen.py
@@ -74,6 +77,7 @@ def or_(rd, rs, rt): return R(0x25, rs, rt, rd)
def jr(rs): return R(0x08, rs)
def jalr(rs, rd="ra"): return R(0x09, rs, 0, rd)
def addiu(rt, rs, imm): return I(0x09, rs, rt, imm)
def andi(rt, rs, imm): return I(0x0C, rs, rt, imm)
def ori(rt, rs, imm): return I(0x0D, rs, rt, imm)
def lui(rt, imm): return I(0x0F, 0, rt, imm)
def lw(rt, off, base): return I(0x23, base, rt, off)
@@ -147,8 +151,11 @@ def _assemble():
a.emit(jalr("t0"), nop(), sw("v0", 0x08, "s0"))
for n, segment in enumerate((KUSEG, KSEG0, KSEG1)):
a.la("t0", "probe_run", segment)
# T2 before and after the call. The nop keeps subu out of the second
# lw's load delay slot; T2 is 16 bits, so mask the difference.
a.emit(lw("t1", 0x1120, "s1"), jalr("t0"), nop(),
lw("t2", 0x1120, "s1"), subu("t2", "t2", "t1"),
lw("t2", 0x1120, "s1"), nop(), subu("t2", "t2", "t1"),
andi("t2", "t2", 0xFFFF),
sw("t2", 0x10 + 8 * n, "s0"), sw("v1", 0x14 + 8 * n, "s0"))
a.label("spin")
a.j("spin"); a.emit(nop())