mirror of
https://github.com/MCKero6423/uv-k5-v3-emulator.git
synced 2026-10-02 03:15:36 +00:00
UVK5_BK4819_PROBE reassembles the sixteen bits a read clocks out and logs them against the register's own value: 1566 of 1566 agree on the working model. That agreement is not proof -- removing the skip_falling fix, which is exactly the historical left-shift regression, leaves the reassembled word unchanged, so this observation point is not the one the guest samples at. tools/test_bk4819_readback.sh therefore stays the guard. tools/test_bk4819_readback.py is the working draft of a portable replacement (no source patch, no rebuild, no ARM gdb) and is deliberately NOT registered in run_tests.sh, so a proven guard is not swapped for an unproven one. Both the file and the two READMEs say so.
113 lines
4.6 KiB
Python
113 lines
4.6 KiB
Python
#!/usr/bin/env python3
|
|
"""A register read must deliver the value the register holds.
|
|
|
|
DRAFT -- not yet a guard, and deliberately not registered in tools/run_tests.sh.
|
|
|
|
It passes on the working model (238 reads, every one agreeing), but it does *not* fail when
|
|
the historical fix is removed: with skip_falling left unset the reassembled word is unchanged,
|
|
so this observation point is not the one the guest samples at. Until that is understood,
|
|
tools/test_bk4819_readback.sh remains the guard for this bug and this file is the working
|
|
draft of its portable replacement.
|
|
|
|
This is the guard for a bug that was invisible for a long time: reads arrived shifted
|
|
one place left, so seeding REG_0C with 0x1248 delivered 0x2490 to the firmware. Writes
|
|
were always fine and the registers the firmware polls hardest were legitimately zero,
|
|
and reading zero and getting zero looks like success.
|
|
|
|
It used to be tools/test_bk4819_readback.sh, which seeded a register by *patching
|
|
qemu/py32f071.c*, rebuilt QEMU, and read the value out of the running guest with
|
|
gdb-multiarch. Three dependencies -- a source tree, a compiler and an ARM gdb -- for a
|
|
guard whose whole job is to protect whoever is editing the model right now. On a machine
|
|
without an ARM gdb it skipped, which is to say the guard did not exist there.
|
|
|
|
It now watches the bits the model presents to the guest (UVK5_BK4819_PROBE) and asserts
|
|
that the sixteen bits a read delivers are the register's own value. No rebuild, no gdb,
|
|
no source patch: plain Python, so it runs wherever the emulator does.
|
|
"""
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import time
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
sys.path.insert(0, HERE)
|
|
|
|
import uvk5_testenv # noqa: E402
|
|
|
|
# The firmware reads REG_0C about 1000 times in the first seconds, REG_31 and REG_48
|
|
# hundreds of times, so a sample is guaranteed without seeding anything.
|
|
MIN_SAMPLES = 200
|
|
|
|
|
|
def main():
|
|
qemu = uvk5_testenv.qemu()
|
|
firmware = uvk5_testenv.firmware()
|
|
missing = uvk5_testenv.missing([
|
|
(qemu, "QEMU", "set QEMU=/path/to/qemu-system-arm, or put it on PATH"),
|
|
(firmware, "firmware", "run tools/fetch_firmware.py, or set ELF=..."),
|
|
])
|
|
if missing:
|
|
return uvk5_testenv.skip(missing)
|
|
|
|
workdir = tempfile.mkdtemp(prefix="uvk5-readback-")
|
|
probe = os.path.join(workdir, "reads.log")
|
|
env = dict(os.environ)
|
|
env["UVK5_BK4819_PROBE"] = probe
|
|
env.setdefault("UVK5_FLASH_IMAGE", os.path.join(HERE, "..", "assets", "flash.img"))
|
|
env["PATH"] = "F:/msys64/mingw64/bin;" + env.get("PATH", "")
|
|
|
|
# No QMP needed: the probe writes to a file and the firmware asks the chip on its own.
|
|
proc = subprocess.Popen([str(qemu), "-M", "uv-k5-v3", "-nographic", "-monitor", "none",
|
|
"-serial", "null", "-kernel", str(firmware)],
|
|
env=env, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
try:
|
|
deadline = time.time() + 25
|
|
while time.time() < deadline:
|
|
time.sleep(1.0)
|
|
if os.path.exists(probe) and len(open(probe, encoding="utf-8").read().splitlines()) >= MIN_SAMPLES:
|
|
break
|
|
finally:
|
|
proc.terminate()
|
|
try:
|
|
proc.wait(timeout=10)
|
|
except Exception:
|
|
proc.kill()
|
|
|
|
reads = []
|
|
if os.path.exists(probe):
|
|
with open(probe, encoding="utf-8") as fh:
|
|
reads = [line.split() for line in fh if line.startswith("READ ")]
|
|
if len(reads) < MIN_SAMPLES:
|
|
print("FAIL only %d reads captured (wanted %d): did the firmware boot?"
|
|
% (len(reads), MIN_SAMPLES))
|
|
return 1
|
|
|
|
bad = []
|
|
for fields in reads:
|
|
values = dict(field.split("=", 1) for field in fields[1:] if "=" in field)
|
|
sent, reg = values.get("sent"), values.get("reg")
|
|
if sent is not None and reg is not None and sent != reg:
|
|
bad.append((values.get("cmd"), sent, reg))
|
|
|
|
print(" %d register reads, %d delivered something other than the register's value"
|
|
% (len(reads), len(bad)))
|
|
if not bad:
|
|
print("")
|
|
print("register reads are bit-aligned")
|
|
return 0
|
|
|
|
print("")
|
|
for cmd, sent, reg in bad[:5]:
|
|
direction = ""
|
|
if sent == "%04x" % ((int(reg, 16) << 1) & 0xffff):
|
|
direction = " (shifted one place left; the command byte's trailing falling edge is eating bit 15)"
|
|
elif sent == "%04x" % (int(reg, 16) >> 1):
|
|
direction = " (shifted one place right; a data bit is being presented twice)"
|
|
print("FAIL REG_%s: register holds %s, guest received %s%s" % (cmd, reg, sent, direction))
|
|
return 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|