mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
Merge remote-tracking branch 'origin/master' into landtmp/492
# Conflicts: # docs/TCP_COMMANDS.md
This commit is contained in:
@@ -431,9 +431,9 @@ The TCP server is the canonical instrumentation surface. Rule 3 in `CLAUDE.md` i
|
||||
|
||||
## Complete command index (generated)
|
||||
|
||||
**334 commands registered** — 321 on the native server (`runtime/src/debug_server.c`), 61 on the Beetle server (`runtime/src/beetle_debug_server.c`).
|
||||
**335 commands registered** — 322 on the native server (`runtime/src/debug_server.c`), 61 on the Beetle server (`runtime/src/beetle_debug_server.c`).
|
||||
|
||||
63 of 334 have prose above; **271 are index-only**. An index-only command still works — it just has no description here yet. Send it `{"cmd":"<name>"}` and read the reply, or find its `handle_*` function in the server source.
|
||||
63 of 335 have prose above; **272 are index-only**. An index-only command still works — it just has no description here yet. Send it `{"cmd":"<name>"}` and read the reply, or find its `handle_*` function in the server source.
|
||||
|
||||
Regenerate with `python tools/gen_tcp_commands.py`; `--check` fails if this block has drifted from the code.
|
||||
|
||||
@@ -506,6 +506,7 @@ Regenerate with `python tools/gen_tcp_commands.py`; `--check` fails if this bloc
|
||||
| `dirty_insn_log` | ✓ | | |
|
||||
| `dirty_ram_stats` | ✓ | | ✓ |
|
||||
| `dirty_ram_unsupported` | ✓ | | |
|
||||
| `disasm` | ✓ | | |
|
||||
| `disc_select` | ✓ | | |
|
||||
| `disp_ring` | ✓ | | |
|
||||
| `dispatch_check` | ✓ | | |
|
||||
@@ -775,3 +776,6 @@ Regenerate with `python tools/gen_tcp_commands.py`; `--check` fails if this bloc
|
||||
| `xprobe_watch` | ✓ | | |
|
||||
|
||||
<!-- END AUTOGENERATED COMMAND INDEX -->
|
||||
|
||||
`wtrace_dump pc_lo=<hex> pc_hi=<hex>` filters the recorded producer PC, masked to the physical address (KSEG0/KSEG1/KUSEG alias, like `addr_lo`/`addr_hi`), with inclusive lower and exclusive upper bounds, before limiting results. Omitted bounds match every entry; DMA entries use their recorded initiator, and a DMA whose initiator is unknown (0) never matches an explicit PC bound. Address/frame/order filters still apply.
|
||||
`disasm addr=<hex> count=<1..256>` inspects aligned guest instructions; default count is 16. Resolved targets use the guest PC. Unknown words are shown verbatim.
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# TCP guest instruction disassembly: source and validation
|
||||
|
||||
Reconstructed from [FractalEngineer/psxrecomp](https://github.com/FractalEngineer/psxrecomp), developed on
|
||||
`vr-dev` at `9976567eb8e95510ca7eeeeccd9af3a260885de5`.
|
||||
Exact source commits:
|
||||
|
||||
- [85c47e10](https://github.com/FractalEngineer/psxrecomp/commit/85c47e10a2fa6e112205e0e953a89ba120c4b139)
|
||||
- [d58db909](https://github.com/FractalEngineer/psxrecomp/commit/d58db9092a57cc692d0c9d936f5003fb4b3e0977)
|
||||
- [c2ed6e55](https://github.com/FractalEngineer/psxrecomp/commit/c2ed6e558f37212c36aedfdfa2c90863883f0aff)
|
||||
|
||||
Original author: Yves <calibratedbeats@gmail.com>. The feature commit retains
|
||||
that exact human author identity. The focused port consolidates the selected
|
||||
source hunks and corrections. Its commit also cites these sources and carries
|
||||
the original human contributor's `Co-authored-by: Yves <calibratedbeats@gmail.com>` trailer.
|
||||
|
||||
Adapted: The C-compatible bridge to the existing MIPS decoder, bounded TCP handler, build linkage and command docs. Formatting was corrected and covered during reconstruction.
|
||||
|
||||
Deliberately excluded: Guest instruction execution/codegen changes, new decoders, game-specific addresses and runtime printf debugging.
|
||||
|
||||
The feature was reconstructed on upstream master
|
||||
`db62eec15f81c88aef3d6dab51d4e566a7167d23`, retaining current upstream APIs,
|
||||
renderer/input policy and build integration. Validation after reconstruction:
|
||||
Disassembly operand/target/coprocessor/buffer regressions, debug-tool ON/OFF server compilation and regenerated TCP index.
|
||||
|
||||
The motivating consumer is [Medal of Honor, SLUS-00974 (NTSC-U)](https://github.com/FractalEngineer/Medal-Of-Honor-PSX-Recomp/tree/e323885958c2ae0b0df847bed6175f9e76524442).
|
||||
Game-specific integration remains in that repository. Consuming this change
|
||||
requires a deliberate framework pin update in the game repo and a rebuild.
|
||||
This is a host/runtime/tool change; it requires no BIOS or game C regeneration.
|
||||
|
||||
Historical alpha acceptance used the older framework checkpoint above. It is
|
||||
separate from current-master branch verification. Fresh game, renderer and
|
||||
headset checks, where applicable, are recorded in the PR validation notes;
|
||||
uncertain hardware/runtime behavior must not be inferred from a clean link.
|
||||
|
||||
## Current-master integration validation
|
||||
|
||||
Windows, GCC 13.2, Ninja, Release. Runtime test checks remain active with NDEBUG enabled.
|
||||
|
||||
Release disassembly tests passed for arithmetic, multiply/divide, shifts, branch/jump targets, immediates, COP0/COP2, unknown words and bounded buffers. Checks execute with NDEBUG enabled. Full runtime builds passed with debug tools enabled and disabled; the generated command index passes.
|
||||
|
||||
Integration: [Medal of Honor, SLUS-00974 NTSC-U](https://github.com/FractalEngineer/Medal-Of-Honor-PSX-Recomp/tree/e323885958c2ae0b0df847bed6175f9e76524442), built against the corrected framework stack with `PSXRECOMP_ROOT`. Bundled OpenBIOS LLE cold boot, menu navigation, mission gameplay, fresh savestate creation/loading and post-load input/frame advancement were checked with screenshots. An older alpha slot-5 savestate was refused; compatibility with historical states is not established. Memory-card round trips, attract-demo completion, other games and fresh headset visuals were not tested.
|
||||
|
||||
The registered recompiler suite reports 132 passes, four failures and three disabled tests. The four failures reproduce on unchanged upstream master in this Windows toolchain: `portable_settings_paths_test`, `optional_netplay_auth`, `compile_overlays_static_tail` and `aot_overlay_discovery`. The introduced BIOS-fingerprint and GL-guard failures are fixed.
|
||||
@@ -0,0 +1,41 @@
|
||||
# Write-trace producer-PC filters: source and validation
|
||||
|
||||
Reconstructed from [FractalEngineer/psxrecomp](https://github.com/FractalEngineer/psxrecomp), developed on
|
||||
`vr-dev` at `9976567eb8e95510ca7eeeeccd9af3a260885de5`.
|
||||
Exact source commits:
|
||||
|
||||
- [734977c9](https://github.com/FractalEngineer/psxrecomp/commit/734977c93e80789e9210a262daa35a88f41c40d4)
|
||||
|
||||
Original author: Yves <calibratedbeats@gmail.com>. The feature commit retains
|
||||
that exact human author identity. The focused port consolidates the selected
|
||||
source hunks and corrections. Its commit also cites these sources and carries
|
||||
the original human contributor's `Co-authored-by: Yves <calibratedbeats@gmail.com>` trailer.
|
||||
|
||||
Adapted: Only full-PC low/high query filters and response bounds; recording is unchanged.
|
||||
|
||||
Deliberately excluded: The same source commit's OpenXR hand snapshots and pose math; no game memory-map constants.
|
||||
|
||||
The feature was reconstructed on upstream master
|
||||
`db62eec15f81c88aef3d6dab51d4e566a7167d23`, retaining current upstream APIs,
|
||||
renderer/input policy and build integration. Validation after reconstruction:
|
||||
Six production-handler wrapped-ring regressions, debug-tool ON/OFF compilation and regenerated TCP index.
|
||||
|
||||
The motivating consumer is [Medal of Honor, SLUS-00974 (NTSC-U)](https://github.com/FractalEngineer/Medal-Of-Honor-PSX-Recomp/tree/e323885958c2ae0b0df847bed6175f9e76524442).
|
||||
Game-specific integration remains in that repository. Consuming this change
|
||||
requires a deliberate framework pin update in the game repo and a rebuild.
|
||||
This is a host/runtime/tool change; it requires no BIOS or game C regeneration.
|
||||
|
||||
Historical alpha acceptance used the older framework checkpoint above. It is
|
||||
separate from current-master branch verification. Fresh game, renderer and
|
||||
headset checks, where applicable, are recorded in the PR validation notes;
|
||||
uncertain hardware/runtime behavior must not be inferred from a clean link.
|
||||
|
||||
## Current-master integration validation
|
||||
|
||||
Windows, GCC 13.2, Ninja, Release. Runtime test checks remain active with NDEBUG enabled.
|
||||
|
||||
Six production-handler regressions passed against a wrapped trace ring, covering full PC segments, inclusive/exclusive bounds, filtering before count, combined filters, DMA metadata and response bounds. Full debug-enabled/disabled runtime builds and the generated command index pass.
|
||||
|
||||
Integration: [Medal of Honor, SLUS-00974 NTSC-U](https://github.com/FractalEngineer/Medal-Of-Honor-PSX-Recomp/tree/e323885958c2ae0b0df847bed6175f9e76524442), built against the corrected framework stack with `PSXRECOMP_ROOT`. Bundled OpenBIOS LLE cold boot, menu navigation, mission gameplay, fresh savestate creation/loading and post-load input/frame advancement were checked with screenshots. An older alpha slot-5 savestate was refused; compatibility with historical states is not established. Memory-card round trips, attract-demo completion, other games and fresh headset visuals were not tested.
|
||||
|
||||
The registered recompiler suite reports 132 passes, four failures and three disabled tests. The four failures reproduce on unchanged upstream master in this Windows toolchain: `portable_settings_paths_test`, `optional_netplay_auth`, `compile_overlays_static_tail` and `aot_overlay_discovery`. The introduced BIOS-fingerprint and GL-guard failures are fixed.
|
||||
@@ -23,6 +23,10 @@ psxrecomp_add_runtime_target(psx-runtime
|
||||
# (docs/ENHANCEMENTS.md G1.10). Titles get their <exe>_pgxp the same way.
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_executable(disasm_test tests/test_disasm.cpp src/disasm_shim.cpp
|
||||
../recompiler/src/mips_decoder.cpp)
|
||||
target_include_directories(disasm_test PRIVATE include ../recompiler/include)
|
||||
add_test(NAME disasm_test COMMAND disasm_test)
|
||||
# Netplay: which BIOS a match boots, and its wire helpers.
|
||||
add_executable(netplay_bios_settle_test
|
||||
tests/test_netplay_bios_settle.c
|
||||
@@ -121,6 +125,10 @@ if(BUILD_TESTING)
|
||||
set(PSX_GL_READBACK_COMPILER_BIN "${_gl_test_default_bin}" CACHE PATH "GCC/MinGW bin for GL fixture")
|
||||
find_package(Python3 QUIET COMPONENTS Interpreter)
|
||||
if(Python3_Interpreter_FOUND)
|
||||
add_test(NAME wtrace_dump_test COMMAND ${CMAKE_COMMAND} -E env
|
||||
"CC=${CMAKE_C_COMPILER}" "${Python3_EXECUTABLE}"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/test_wtrace_dump.py")
|
||||
set_tests_properties(wtrace_dump_test PROPERTIES SKIP_RETURN_CODE 77)
|
||||
add_test(NAME counted_mips_relocations_test COMMAND "${Python3_EXECUTABLE}"
|
||||
"${PSXRECOMP_ROOT}/tools/tests/test_counted_mips_relocations.py")
|
||||
add_test(NAME interp_mod_entry_guards_test COMMAND "${Python3_EXECUTABLE}"
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
#ifndef PSX_DISASM_H
|
||||
#define PSX_DISASM_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Format one MIPS R3000 instruction as text into out (NUL-terminated).
|
||||
* addr is used to resolve branch/jump targets. Returns the number of
|
||||
* characters written (excluding the NUL), or 0 if out/cap are unusable. */
|
||||
int psx_disasm_one(uint32_t word, uint32_t addr, char* out, int cap);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* PSX_DISASM_H */
|
||||
@@ -94,6 +94,10 @@ void psx_host_write_word(uint32_t addr, uint32_t val);
|
||||
void psx_host_write_half(uint32_t addr, uint16_t val);
|
||||
void psx_host_write_byte(uint32_t addr, uint8_t val);
|
||||
|
||||
/* Side-effect-free aligned word peek (RAM / scratchpad / BIOS ROM only), for
|
||||
* the debug path. Returns 0 (and leaves *out alone) for anything else. */
|
||||
int psx_peek_word(uint32_t addr, uint32_t *out);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -457,6 +457,13 @@ set(PSXRECOMP_RUNTIME_SOURCES
|
||||
# (sljit Tier-2 in-process JIT backend removed 2026-07-15 — was disabled by
|
||||
# default since 2026-06-25; gaps fall to the interpreter, gcc/tcc unaffected.)
|
||||
)
|
||||
# TCP `disasm` command only (debug_server.c); stripped with the rest of the TCP
|
||||
# server when PSX_DEBUG_TOOLS is OFF.
|
||||
if(PSX_DEBUG_TOOLS)
|
||||
list(APPEND PSXRECOMP_RUNTIME_SOURCES
|
||||
${PSXRECOMP_ROOT}/recompiler/src/mips_decoder.cpp
|
||||
${PSXRECOMP_ROOT}/runtime/src/disasm_shim.cpp)
|
||||
endif()
|
||||
|
||||
# Optional delay-sync netplay (recomp-net). Auto-discovers a sibling checkout
|
||||
# (…/recomp-net next to the game repo or next to psxrecomp). Override with
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
#include "overlay_backend.h"
|
||||
#include "cpu_state.h"
|
||||
#include "pgxp.h"
|
||||
#ifndef PSX_NO_DEBUG_TOOLS
|
||||
#include "psx_disasm.h"
|
||||
#endif
|
||||
#include "dma.h"
|
||||
#include "gpu.h"
|
||||
#include "gpu_render.h" /* gr_scale + gr_render_display_hires (screenshot_hires) */
|
||||
@@ -5185,6 +5188,60 @@ static void handle_get_registers(int id, const char *json)
|
||||
free(buf);
|
||||
}
|
||||
|
||||
/* disasm: disassemble guest instructions. {"cmd":"disasm","addr":"0x8001121C",
|
||||
* "count":32}. Reads live guest memory, so it works on EXE code and on
|
||||
* RAM-installed/overlay code alike. Uses the recompiler's MIPS decoder.
|
||||
* Reads go through psx_peek_word (main RAM / scratchpad / BIOS ROM only, no
|
||||
* device reads, no read hooks); a word outside those comes back as
|
||||
* {"word":null,"text":"<unreadable>"}. Compiled only with the debug tools,
|
||||
* like the sources it needs (runtime.cmake). */
|
||||
#ifndef PSX_NO_DEBUG_TOOLS
|
||||
static void handle_disasm(int id, const char *json)
|
||||
{
|
||||
char addr_str[32];
|
||||
if (!json_get_str(json, "addr", addr_str, sizeof(addr_str))) {
|
||||
send_err(id, "missing addr"); return;
|
||||
}
|
||||
const char *hex = addr_str;
|
||||
if (hex[0] == '0' && (hex[1] == 'x' || hex[1] == 'X')) hex += 2;
|
||||
char *hex_end = NULL;
|
||||
unsigned long long addr_val = strtoull(hex, &hex_end, 16);
|
||||
if (hex_end == hex || *hex_end != '\0' || addr_val > 0xFFFFFFFFull) {
|
||||
send_err(id, "bad addr"); return;
|
||||
}
|
||||
uint32_t addr = (uint32_t)addr_val & ~3u;
|
||||
int count = json_get_int(json, "count", 16);
|
||||
if (count < 1) count = 1;
|
||||
if (count > 256) count = 256;
|
||||
|
||||
size_t bufsz = 512u + (size_t)count * 160u;
|
||||
char *out = (char *)malloc(bufsz);
|
||||
if (!out) { send_err(id, "oom"); return; }
|
||||
int pos = snprintf(out, bufsz,
|
||||
"{\"id\":%d,\"ok\":true,\"addr\":\"0x%08X\",\"count\":%d,\"lines\":[",
|
||||
id, addr, count);
|
||||
char text[128];
|
||||
for (int i = 0; i < count; i++) {
|
||||
uint32_t a = addr + (uint32_t)i * 4u;
|
||||
uint32_t w = 0;
|
||||
if (!psx_peek_word(a, &w)) {
|
||||
pos += snprintf(out + pos, bufsz - (size_t)pos,
|
||||
"%s{\"addr\":\"0x%08X\",\"word\":null,\"text\":\"<unreadable>\"}",
|
||||
i ? "," : "", a);
|
||||
continue;
|
||||
}
|
||||
text[0] = '\0';
|
||||
(void)psx_disasm_one(w, a, text, (int)sizeof(text));
|
||||
pos += snprintf(out + pos, bufsz - (size_t)pos,
|
||||
"%s{\"addr\":\"0x%08X\",\"word\":\"0x%08X\",\"text\":\"%s\"}",
|
||||
i ? "," : "", a, w, text);
|
||||
}
|
||||
pos += snprintf(out + pos, bufsz - (size_t)pos, "]}");
|
||||
debug_server_send_line(out);
|
||||
free(out);
|
||||
}
|
||||
#endif /* !PSX_NO_DEBUG_TOOLS */
|
||||
|
||||
static void handle_read_ram(int id, const char *json)
|
||||
{
|
||||
char addr_str[32];
|
||||
@@ -10070,7 +10127,7 @@ static void wtrace_fill_entry(WriteTraceEntry *e, uint64_t seq,
|
||||
* instead of the stale last-CPU-store PC (which is meaningless mid-DMA). */
|
||||
if (g_dma_exec_depth > 0) {
|
||||
e->dma_ch = (int8_t)g_dma_cur_ch;
|
||||
if (g_dma_initiator_pc) e->pc = g_dma_initiator_pc;
|
||||
e->pc = g_dma_initiator_pc; /* 0 = unknown; never a stale CPU store PC */
|
||||
} else {
|
||||
e->dma_ch = -1;
|
||||
}
|
||||
@@ -11677,6 +11734,22 @@ static void handle_wtrace_dump(int id, const char *json)
|
||||
if (json_get_str(json, "addr_hi", hi_str, sizeof(hi_str)))
|
||||
filter_hi = hex_to_u32(hi_str) & 0x1FFFFFFFu;
|
||||
|
||||
/* Filter the recorded producer PC, segment-masked like the address filter
|
||||
* (KSEG0/KSEG1/KUSEG alias). DMA entries use the initiator PC stored by
|
||||
* wtrace_fill_entry; an unknown (0) initiator matches only when no PC
|
||||
* bound is given. Post-hoc only: recording, fingerprints and guest
|
||||
* execution are unchanged. pc_lo/pc_hi echo the raw request. */
|
||||
uint32_t pc_lo = 0, pc_hi = 0xFFFFFFFFu;
|
||||
int pc_filtered = 0;
|
||||
/* Default upper bound sits above every masked PC so no entry is excluded. */
|
||||
uint32_t pc_lo_m = 0, pc_hi_m = 0x20000000u;
|
||||
if (json_get_str(json, "pc_lo", lo_str, sizeof(lo_str))) {
|
||||
pc_lo = hex_to_u32(lo_str); pc_lo_m = pc_lo & 0x1FFFFFFFu; pc_filtered = 1;
|
||||
}
|
||||
if (json_get_str(json, "pc_hi", hi_str, sizeof(hi_str))) {
|
||||
pc_hi = hex_to_u32(hi_str); pc_hi_m = pc_hi & 0x1FFFFFFFu; pc_filtered = 1;
|
||||
}
|
||||
|
||||
/* Optional frame-window filter — the "query the ring for the window of
|
||||
* interest" primitive. Lets a caller reach entries in the MIDDLE of a deep,
|
||||
* high-traffic ring (which oldest-N / newest-N paging cannot). -1 = unbounded. */
|
||||
@@ -11700,8 +11773,9 @@ static void handle_wtrace_dump(int id, const char *json)
|
||||
size_t pos = 0;
|
||||
uint32_t emitted = 0;
|
||||
pos += snprintf(buf + pos, BUF_SZ - pos,
|
||||
"{\"id\":%d,\"ok\":true,\"total\":%llu,\"available\":%u,\"entries\":[",
|
||||
id, (unsigned long long)total, avail);
|
||||
"{\"id\":%d,\"ok\":true,\"total\":%llu,\"available\":%u,"
|
||||
"\"pc_lo\":\"0x%08X\",\"pc_hi\":\"0x%08X\",\"entries\":[",
|
||||
id, (unsigned long long)total, avail, pc_lo, pc_hi);
|
||||
for (uint32_t i = 0; i < avail && emitted < MAX_OUT && pos < BUF_SZ - 256; i++) {
|
||||
uint32_t idx;
|
||||
if (newest_first) {
|
||||
@@ -11712,6 +11786,10 @@ static void handle_wtrace_dump(int id, const char *json)
|
||||
}
|
||||
WriteTraceEntry *e = &s_wtrace[idx];
|
||||
if (e->addr < filter_lo || e->addr >= filter_hi) continue;
|
||||
if (pc_filtered) {
|
||||
uint32_t epc = e->pc & 0x1FFFFFFFu;
|
||||
if (e->pc == 0 || epc < pc_lo_m || epc >= pc_hi_m) continue;
|
||||
}
|
||||
if (frame_lo >= 0 && (int)e->frame < frame_lo) continue;
|
||||
if (frame_hi >= 0 && (int)e->frame > frame_hi) continue;
|
||||
pos += snprintf(buf + pos, BUF_SZ - pos,
|
||||
@@ -14786,6 +14864,9 @@ static const CmdEntry s_commands[] = {
|
||||
{ "c0_history", handle_c0_history },
|
||||
{ "capture_quads", handle_capture_quads },
|
||||
{ "get_quads", handle_get_quads },
|
||||
#ifndef PSX_NO_DEBUG_TOOLS
|
||||
{ "disasm", handle_disasm },
|
||||
#endif
|
||||
{ "gte_state", handle_gte_state },
|
||||
{ "gte_ring_dump", handle_gte_ring_dump },
|
||||
{ "gte_intpl_dump", handle_gte_intpl_dump },
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
#include "psx_disasm.h"
|
||||
#include "mips_decoder.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
/* C-linkage bridge so the C debug server can use the C++ MIPS decoder that the
|
||||
* recompiler already ships (recompiler/src/mips_decoder.cpp). */
|
||||
|
||||
/* GTE data registers (COP2 rd index) — psx-spx. */
|
||||
static const char* const kCop2Data[32] = {
|
||||
"VXY0", "VZ0", "VXY1", "VZ1", "VXY2", "VZ2", "RGB", "OTZ",
|
||||
"IR0", "IR1", "IR2", "IR3", "SXY0", "SXY1", "SXY2", "SXYP",
|
||||
"SZ0", "SZ1", "SZ2", "SZ3", "RGB0", "RGB1", "RGB2", "RES1",
|
||||
"MAC0", "MAC1", "MAC2", "MAC3", "IRGB", "ORGB", "LZCS", "LZCR"
|
||||
};
|
||||
|
||||
/* GTE control registers (COP2 rd index) — psx-spx. */
|
||||
static const char* const kCop2Ctrl[32] = {
|
||||
"RT11RT12", "RT13RT21", "RT22RT23", "RT31RT32", "RT33", "TRX", "TRY", "TRZ",
|
||||
"L11L12", "L13L21", "L22L23", "L31L32", "L33", "RBK", "GBK", "BBK",
|
||||
"LR1LR2", "LR3LG1", "LG2LG3", "LB1LB2", "LB3", "RFC", "GFC", "BFC",
|
||||
"OFX", "OFY", "H", "DQA", "DQB", "ZSF3", "ZSF4", "FLAG"
|
||||
};
|
||||
|
||||
static const char* gte_cmd_name(uint32_t raw) {
|
||||
switch (raw & 0x3Fu) {
|
||||
case 0x01: return "RTPS";
|
||||
case 0x06: return "NCLIP";
|
||||
case 0x0C: return "OP";
|
||||
case 0x10: return "DPCS";
|
||||
case 0x11: return "INTPL";
|
||||
case 0x12: return "MVMVA";
|
||||
case 0x13: return "NCDS";
|
||||
case 0x14: return "CDP";
|
||||
case 0x16: return "NCDT";
|
||||
case 0x1B: return "NCCS";
|
||||
case 0x1C: return "CC";
|
||||
case 0x1E: return "NCS";
|
||||
case 0x20: return "NCT";
|
||||
case 0x28: return "SQR";
|
||||
case 0x29: return "DCPL";
|
||||
case 0x2A: return "DPCT";
|
||||
case 0x2D: return "AVSZ3";
|
||||
case 0x2E: return "AVSZ4";
|
||||
case 0x30: return "RTPT";
|
||||
case 0x3D: return "GPF";
|
||||
case 0x3E: return "GPL";
|
||||
case 0x3F: return "NCCT";
|
||||
default: return "GTE";
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" int psx_disasm_one(uint32_t word, uint32_t addr, char* out, int cap) {
|
||||
if (!out || cap <= 0) return 0;
|
||||
using PSXRecomp::DecodedInstruction;
|
||||
using PSXRecomp::InstrFormat;
|
||||
using PSXRecomp::MipsDecoder;
|
||||
|
||||
DecodedInstruction d = MipsDecoder::decode(word, addr);
|
||||
const char* m = d.mnemonic ? d.mnemonic : "???";
|
||||
auto rn = [](uint8_t r) { return MipsDecoder::register_name(r); };
|
||||
uint8_t cop = d.rd & 0x1F;
|
||||
|
||||
int n;
|
||||
if (d.format == InstrFormat::UNKNOWN) {
|
||||
n = std::snprintf(out, (size_t)cap, ".word 0x%08X", word);
|
||||
} else if (word == 0u) {
|
||||
n = std::snprintf(out, (size_t)cap, "NOP");
|
||||
} else if (std::strcmp(m, "JR") == 0) {
|
||||
n = std::snprintf(out, (size_t)cap, "%-8s %s", m, rn(d.rs));
|
||||
} else if (std::strcmp(m, "JALR") == 0) {
|
||||
n = std::snprintf(out, (size_t)cap, "%-8s %s, %s", m, rn(d.rd), rn(d.rs));
|
||||
} else if (std::strcmp(m, "SLL") == 0 || std::strcmp(m, "SRL") == 0 ||
|
||||
std::strcmp(m, "SRA") == 0) {
|
||||
n = std::snprintf(out, (size_t)cap, "%-8s %s, %s, %d", m, rn(d.rd), rn(d.rt),
|
||||
d.shamt);
|
||||
} else if (std::strcmp(m, "MFHI") == 0 || std::strcmp(m, "MFLO") == 0) {
|
||||
n = std::snprintf(out, (size_t)cap, "%-8s %s", m, rn(d.rd));
|
||||
} else if (std::strcmp(m, "MTHI") == 0 || std::strcmp(m, "MTLO") == 0) {
|
||||
n = std::snprintf(out, (size_t)cap, "%-8s %s", m, rn(d.rs));
|
||||
} else if (std::strcmp(m, "SYSCALL") == 0 || std::strcmp(m, "BREAK") == 0) {
|
||||
n = std::snprintf(out, (size_t)cap, "%s", m);
|
||||
} else if (std::strcmp(m, "MULT") == 0 || std::strcmp(m, "MULTU") == 0 ||
|
||||
std::strcmp(m, "DIV") == 0 || std::strcmp(m, "DIVU") == 0) {
|
||||
n = std::snprintf(out, (size_t)cap, "%-8s %s, %s", m, rn(d.rs), rn(d.rt));
|
||||
} else if (std::strcmp(m, "SLLV") == 0 || std::strcmp(m, "SRLV") == 0 ||
|
||||
std::strcmp(m, "SRAV") == 0) {
|
||||
n = std::snprintf(out, (size_t)cap, "%-8s %s, %s, %s", m, rn(d.rd), rn(d.rt), rn(d.rs));
|
||||
} else if (d.format == InstrFormat::COP0) {
|
||||
if (std::strcmp(m, "MFC0") == 0 || std::strcmp(m, "MTC0") == 0)
|
||||
n = std::snprintf(out, (size_t)cap, "%-8s %s, $%u", m, rn(d.rt), (unsigned)cop);
|
||||
else if (std::strcmp(m, "RFE") == 0)
|
||||
n = std::snprintf(out, (size_t)cap, "%s", m);
|
||||
else
|
||||
n = std::snprintf(out, (size_t)cap, ".word 0x%08X", word);
|
||||
} else if (d.format == InstrFormat::COP2) {
|
||||
if (std::strcmp(m, "MFC2") == 0 || std::strcmp(m, "MTC2") == 0) {
|
||||
n = std::snprintf(out, (size_t)cap, "%-8s %s, $%s", m, rn(d.rt),
|
||||
kCop2Data[cop]);
|
||||
} else if (std::strcmp(m, "CFC2") == 0 || std::strcmp(m, "CTC2") == 0) {
|
||||
n = std::snprintf(out, (size_t)cap, "%-8s %s, $%s", m, rn(d.rt),
|
||||
kCop2Ctrl[cop]);
|
||||
} else if (d.rs & 0x10) { /* GTE command word */
|
||||
n = std::snprintf(out, (size_t)cap, "%-8s 0x%07X", gte_cmd_name(d.raw),
|
||||
d.raw & 0x1FFFFFFu);
|
||||
} else {
|
||||
n = std::snprintf(out, (size_t)cap, ".word 0x%08X", word);
|
||||
}
|
||||
} else if (d.format == InstrFormat::J) {
|
||||
n = std::snprintf(out, (size_t)cap, "%-8s 0x%08X", m, d.jump_target);
|
||||
} else if (d.is_branch && d.opcode != 0x04 && d.opcode != 0x05) {
|
||||
n = std::snprintf(out, (size_t)cap, "%-8s %s, 0x%08X", m, rn(d.rs), d.branch_target);
|
||||
} else if (d.is_branch) {
|
||||
n = std::snprintf(out, (size_t)cap, "%-8s %s, %s, 0x%08X", m, rn(d.rs),
|
||||
rn(d.rt), d.branch_target);
|
||||
} else if (d.opcode == 0x0F) { /* LUI */
|
||||
n = std::snprintf(out, (size_t)cap, "%-8s %s, 0x%04X", m, rn(d.rt),
|
||||
(unsigned)d.uimm16);
|
||||
} else if (d.is_load || d.is_store) {
|
||||
const char* reg = (d.opcode == 0x32 || d.opcode == 0x3A) ? kCop2Data[d.rt & 31] : rn(d.rt);
|
||||
const char* prefix = (d.opcode == 0x32 || d.opcode == 0x3A) ? "$" : "";
|
||||
n = std::snprintf(out, (size_t)cap, "%-8s %s%s, %d(%s)", m, prefix, reg,
|
||||
(int)d.imm16, rn(d.rs));
|
||||
} else if (d.format == InstrFormat::R || d.format == InstrFormat::SPECIAL) {
|
||||
n = std::snprintf(out, (size_t)cap, "%-8s %s, %s, %s", m, rn(d.rd), rn(d.rs),
|
||||
rn(d.rt));
|
||||
} else if (d.opcode >= 0x0C && d.opcode <= 0x0E) {
|
||||
n = std::snprintf(out, (size_t)cap, "%-8s %s, %s, 0x%04X", m, rn(d.rt), rn(d.rs), (unsigned)d.uimm16);
|
||||
} else {
|
||||
n = std::snprintf(out, (size_t)cap, "%-8s %s, %s, %d", m, rn(d.rt), rn(d.rs),
|
||||
(int)d.imm16);
|
||||
}
|
||||
if (n < 0) { out[0] = '\0'; return 0; }
|
||||
return (n < cap) ? n : (cap - 1);
|
||||
}
|
||||
@@ -164,6 +164,28 @@ static inline uint32_t psx_phys_addr_store(uint32_t addr) {
|
||||
/* Expose RAM pointer for oracle comparison (find_first_divergence). */
|
||||
uint8_t *memory_get_ram_ptr(void) { return ram; }
|
||||
uint8_t *memory_get_scratchpad_ptr(void) { return scratchpad; }
|
||||
|
||||
/* Side-effect-FREE aligned word peek for the debug/observability path ONLY:
|
||||
* main RAM (every KUSEG/KSEG0/KSEG1 mirror), scratchpad and BIOS ROM. Never
|
||||
* touches MMIO, the lockstep/DS read hooks, read watches, or unmapped_fatal.
|
||||
* Returns 1 and fills *out when addr is backed by one of those; 0 otherwise. */
|
||||
int psx_peek_word(uint32_t addr, uint32_t *out) {
|
||||
uint32_t phys, off;
|
||||
const uint8_t *p;
|
||||
if (addr >= 0xC0000000u || (addr & 3u)) return 0;
|
||||
phys = addr & 0x1FFFFFFFu;
|
||||
if (psx_ram_resolve(addr, 4u, &off)) {
|
||||
p = &ram[off];
|
||||
} else if (phys >= 0x1F800000u && phys <= 0x1F8003FCu) {
|
||||
p = &scratchpad[phys - 0x1F800000u];
|
||||
} else if (phys >= 0x1FC00000u && phys <= 0x1FC7FFFCu) {
|
||||
p = &bios_rom[phys - 0x1FC00000u];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
if (out) memcpy(out, p, sizeof(*out));
|
||||
return 1;
|
||||
}
|
||||
void memory_clear_low_boot_scratch(void) {
|
||||
memset(ram, 0, 0x10u);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
#include "psx_disasm.h"
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
static void require(bool condition, const char* expression, int line) {
|
||||
if (!condition) {
|
||||
std::fprintf(stderr, "FAIL line %d: %s\n", line, expression);
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
#define CHECK(expr) require((expr), #expr, __LINE__)
|
||||
static void check(unsigned word, unsigned pc, const char* expected) {
|
||||
char out[128];
|
||||
int n=psx_disasm_one(word, pc, out, sizeof(out));
|
||||
if (std::strcmp(out,expected)) {
|
||||
std::fprintf(stderr,"%08X: expected [%s], got [%s]\n",word,expected,out);
|
||||
CHECK(false);
|
||||
}
|
||||
CHECK(n == (int)std::strlen(expected));
|
||||
}
|
||||
int main() {
|
||||
check(0,0,"NOP");
|
||||
check(0x012A4020,0,"ADD $t0, $t1, $t2");
|
||||
check(0x012A0018,0,"MULT $t1, $t2");
|
||||
check(0x012A001B,0,"DIVU $t1, $t2");
|
||||
check(0x012A4004,0,"SLLV $t0, $t2, $t1");
|
||||
check(0x0521FFFF,0x80001000,"BGEZ $t1, 0x80001000");
|
||||
check(0x1920FFFE,0x80001000,"BLEZ $t1, 0x80000FFC");
|
||||
check(0x112AFFFE,0x80001000,"BEQ $t1, $t2, 0x80000FFC");
|
||||
check(0x08000000,0x8FFFFFFC,"J 0x90000000");
|
||||
check(0x3128FFFF,0,"ANDI $t0, $t1, 0xFFFF");
|
||||
check(0x2528FFFF,0,"ADDIU $t0, $t1, -1");
|
||||
check(0x8D28FFF8,0,"LW $t0, -8($t1)");
|
||||
check(0xC923FFF8,0,"LWC2 $VZ1, -8($t1)");
|
||||
check(0x40086000,0,"MFC0 $t0, $12");
|
||||
check(0x42000010,0,"RFE");
|
||||
check(0x48086800,0,"MFC2 $t0, $SXY1");
|
||||
check(0x4848D000,0,"CFC2 $t0, $H");
|
||||
check(0x4A080001,0,"RTPS 0x0080001");
|
||||
check(0x48200001,0,".word 0x48200001");
|
||||
check(0xFC000000,0,".word 0xFC000000");
|
||||
char tiny[4]={'x','x','x','x'};
|
||||
CHECK(psx_disasm_one(0,0,tiny,4)==3 && tiny[3]==0);
|
||||
CHECK(psx_disasm_one(0,0,tiny,1)==0 && tiny[0]==0);
|
||||
CHECK(psx_disasm_one(0,0,nullptr,4)==0);
|
||||
tiny[0]='x'; CHECK(psx_disasm_one(0,0,tiny,0)==0 && tiny[0]=='x');
|
||||
}
|
||||
@@ -35,6 +35,7 @@ extern void psx_write_word(uint32_t addr, uint32_t val);
|
||||
extern void psx_write_half(uint32_t addr, uint16_t val);
|
||||
extern void psx_write_byte(uint32_t addr, uint8_t val);
|
||||
extern uint8_t *memory_get_ram_ptr(void);
|
||||
extern uint8_t *memory_get_scratchpad_ptr(void);
|
||||
|
||||
/* Stub-side mutation counters (psx_ram_runtime_map_stubs.c). */
|
||||
extern int g_stub_device_restores;
|
||||
@@ -130,6 +131,39 @@ static void check_guest_access(int expanded) {
|
||||
check(psx_read_byte(0x80204005u) == 0x5Au, "byte reads back at its own address");
|
||||
}
|
||||
|
||||
/* ---- psx_peek_word: debug read that must never reach the bus ----------- */
|
||||
static void check_peek(int expanded) {
|
||||
uint32_t v = 0xDEADBEEFu;
|
||||
memset(memory_get_ram_ptr(), 0, PSX_MAIN_RAM_BACKING_BYTES);
|
||||
psx_write_word(0x80001000u, 0x11223344u);
|
||||
check(psx_peek_word(0x80001000u, &v) && v == 0x11223344u, "peek: KSEG0 RAM");
|
||||
check(psx_peek_word(0x00001000u, &v) && v == 0x11223344u, "peek: KUSEG RAM");
|
||||
check(psx_peek_word(0xA0001000u, &v) && v == 0x11223344u, "peek: KSEG1 RAM");
|
||||
v = 0;
|
||||
check(psx_peek_word(0x80201000u, &v) && ((v == 0x11223344u) == !expanded),
|
||||
"peek: 2nd mirror follows the live geometry");
|
||||
check(psx_peek_word(0x807FFFFCu, &v), "peek: top of the RAM window");
|
||||
memory_get_scratchpad_ptr()[0x10] = 0x78; memory_get_scratchpad_ptr()[0x13] = 0x12;
|
||||
check(psx_peek_word(0x1F800010u, &v) && (v & 0xFF) == 0x78 && (v >> 24) == 0x12,
|
||||
"peek: scratchpad");
|
||||
check(psx_peek_word(0x1F8003FCu, &v) && !psx_peek_word(0x1F800400u, &v),
|
||||
"peek: scratchpad end bound");
|
||||
check(psx_peek_word(0xBFC00000u, &v) && psx_peek_word(0x1FC7FFFCu, &v) &&
|
||||
!psx_peek_word(0x1FC80000u, &v), "peek: BIOS ROM bounds");
|
||||
/* Device / unmapped space is refused WITHOUT touching the bus (the stubbed
|
||||
* psx_fatal_halt traps if a real access reached unmapped_fatal). */
|
||||
v = 0xDEADBEEFu;
|
||||
check(!psx_peek_word(0x1F801044u, &v), "peek: JOY_RX refused");
|
||||
check(!psx_peek_word(0x1F801810u, &v), "peek: GPUREAD refused");
|
||||
check(!psx_peek_word(0x1F802000u, &v), "peek: expansion-2 MMIO refused");
|
||||
check(!psx_peek_word(0x1F000000u, &v), "peek: expansion-1 refused");
|
||||
check(!psx_peek_word(0x80800000u, &v), "peek: past the RAM window refused");
|
||||
check(!psx_peek_word(0xFFFE0130u, &v) && !psx_peek_word(0xC0000000u, &v),
|
||||
"peek: KSEG2 refused");
|
||||
check(!psx_peek_word(0x80001002u, &v), "peek: unaligned refused");
|
||||
check(v == 0xDEADBEEFu, "peek: refused reads leave *out untouched");
|
||||
}
|
||||
|
||||
/* ---- 3. a real DMA writing RAM from a guest-chosen MADR --------------- */
|
||||
#define DMA_BASE 0x1F801080u
|
||||
static void run_otc(uint32_t madr, uint32_t entries) {
|
||||
@@ -288,6 +322,7 @@ int main(void) {
|
||||
for (int expanded = 0; expanded < 2; ++expanded) {
|
||||
set_geometry(expanded);
|
||||
check_guest_access(expanded);
|
||||
check_peek(expanded);
|
||||
check_dma(expanded);
|
||||
}
|
||||
check_savestates();
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
"""Execute the production trace-query handler against a wrapped fixture ring.
|
||||
|
||||
Compile only the handler and its real JSON helpers, replacing the transport
|
||||
with stdout. This needs a C compiler, but no game image, SDL or live server.
|
||||
Exits 77 (ctest SKIP_RETURN_CODE) when no C compiler is available.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
SKIP_EXIT = 77
|
||||
|
||||
|
||||
def matching_brace(source, open_index):
|
||||
"""Index of the brace closing the one at open_index (skips strings/comments)."""
|
||||
depth, i, n = 0, open_index, len(source)
|
||||
while i < n:
|
||||
c = source[i]
|
||||
if source.startswith("/*", i):
|
||||
i = source.index("*/", i + 2) + 1
|
||||
elif source.startswith("//", i):
|
||||
i = source.index("\n", i)
|
||||
elif c in "\"'":
|
||||
i += 1
|
||||
while source[i] != c:
|
||||
i += 2 if source[i] == "\\" else 1
|
||||
elif c == "{":
|
||||
depth += 1
|
||||
elif c == "}":
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
return i
|
||||
i += 1
|
||||
raise AssertionError("unbalanced braces")
|
||||
|
||||
|
||||
def find_compiler():
|
||||
"""argv prefix for the C compiler named by CC (default cc), or None."""
|
||||
setting = os.environ.get("CC", "cc")
|
||||
if shutil.which(setting):
|
||||
return [setting]
|
||||
parts = shlex.split(setting, posix=os.name != "nt")
|
||||
return parts if parts and shutil.which(parts[0]) else None
|
||||
|
||||
|
||||
class WriteTraceQuery(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
compiler = find_compiler()
|
||||
if compiler is None:
|
||||
raise unittest.SkipTest("no C compiler (set CC)")
|
||||
cls.temp = tempfile.TemporaryDirectory()
|
||||
cls.addClassCleanup(cls.temp.cleanup)
|
||||
source = (Path(__file__).parents[1] / "src/debug_server.c").read_text(encoding="utf-8")
|
||||
|
||||
def function(name):
|
||||
match = re.search(r"static [^;{}]+\b" + name + r"\([^;{}]*\)\s*\{", source)
|
||||
if not match:
|
||||
raise AssertionError(f"production function missing: {name}")
|
||||
return source[match.start():matching_brace(source, match.end() - 1) + 1]
|
||||
|
||||
end = source.index("} WriteTraceEntry;") + len("} WriteTraceEntry;")
|
||||
start = source.rfind("typedef struct {", 0, end)
|
||||
harness = """#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#define WRITE_TRACE_CAP 8
|
||||
""" + source[start:end] + """
|
||||
static WriteTraceEntry ring[WRITE_TRACE_CAP], *s_wtrace=ring;
|
||||
static uint64_t s_wtrace_seq=10;
|
||||
static uint32_t s_wtrace_head=2;
|
||||
typedef struct { uint32_t pc; uint32_t gpr[32]; } CPUState;
|
||||
static CPUState *debug_cpu_ptr = NULL;
|
||||
static uint32_t s_frame_count = 0;
|
||||
uint32_t g_debug_current_func_addr = 0;
|
||||
uint32_t g_debug_last_store_pc = 0;
|
||||
int g_dma_exec_depth = 0, g_dma_cur_ch = -1;
|
||||
uint32_t g_dma_initiator_pc = 0;
|
||||
static void debug_server_send_line(const char *line) { puts(line); }
|
||||
static void send_err(int id, const char *error) { (void)id; (void)error; abort(); }
|
||||
"""
|
||||
for name in ("json_get_str", "json_get_int", "hex_to_u32", "wtrace_fill_entry",
|
||||
"handle_wtrace_dump"):
|
||||
harness += function(name) + "\n"
|
||||
# seq 7 is a DMA entry with an unknown (0) initiator PC.
|
||||
harness += """int main(int argc, char **argv) {
|
||||
if (argc > 4 && !strcmp(argv[1], "fill")) { /* fill <dma_depth> <initiator> <cpu_store_pc> */
|
||||
WriteTraceEntry e; memset(&e, 0, sizeof e);
|
||||
g_dma_exec_depth = atoi(argv[2]); g_dma_cur_ch = 2;
|
||||
g_dma_initiator_pc = (uint32_t)strtoul(argv[3], 0, 16);
|
||||
g_debug_last_store_pc = (uint32_t)strtoul(argv[4], 0, 16);
|
||||
wtrace_fill_entry(&e, 1, 0x1000, 0, 1, 4);
|
||||
printf("{\\"pc\\":%u,\\"dma_ch\\":%d}\\n", e.pc, e.dma_ch);
|
||||
return 0;
|
||||
}
|
||||
const uint32_t pcs[8]={0x80001000,0x80001004,0xA0001000,0x80001008,
|
||||
0x8000100C,0x00000000,0x80001004,0x00001004};
|
||||
for (int seq=2; seq<10; ++seq) {
|
||||
WriteTraceEntry *e=&ring[seq%WRITE_TRACE_CAP];
|
||||
e->seq=seq; e->pc=pcs[seq-2]; e->addr=0x1000+(seq-2)*4;
|
||||
e->frame=10+(seq-2)/2; e->width=4; e->dma_ch=seq==5?2:(seq==7?3:-1);
|
||||
}
|
||||
handle_wtrace_dump(7,argc>1?argv[1]:"{}");
|
||||
return 0;
|
||||
}
|
||||
"""
|
||||
path = Path(cls.temp.name)
|
||||
(path / "query.c").write_text(harness, encoding="utf-8", newline="\n")
|
||||
cls.exe = path / ("query.exe" if os.name == "nt" else "query")
|
||||
if Path(compiler[0]).stem.lower() in ("cl", "clang-cl"):
|
||||
out = ["/nologo", "/Fe:" + str(cls.exe), "/Fo:" + str(path) + os.sep]
|
||||
else:
|
||||
out = ["-o", str(cls.exe)]
|
||||
subprocess.run([*compiler, str(path / "query.c"), *out], check=True, cwd=path)
|
||||
|
||||
def query(self, **params):
|
||||
result = subprocess.check_output([str(self.exe), json.dumps(params)], text=True)
|
||||
reply = json.loads(result)
|
||||
self.assertEqual(reply["emitted"], len(reply["entries"]))
|
||||
return reply
|
||||
|
||||
def sequences(self, **params):
|
||||
return [e["seq"] for e in self.query(**params)["entries"]]
|
||||
|
||||
def fill(self, depth, initiator, store_pc):
|
||||
out = subprocess.check_output(
|
||||
[str(self.exe), "fill", str(depth), initiator, store_pc], text=True)
|
||||
return json.loads(out)
|
||||
|
||||
def test_fill_entry_producer_pc(self):
|
||||
self.assertEqual(self.fill(0, "0", "80001234"), {"pc": 0x80001234, "dma_ch": -1})
|
||||
self.assertEqual(self.fill(1, "80005000", "80001234"), {"pc": 0x80005000, "dma_ch": 2})
|
||||
# Unknown initiator: the DMA entry must not inherit the stale CPU store PC.
|
||||
self.assertEqual(self.fill(1, "0", "80001234"), {"pc": 0, "dma_ch": 2})
|
||||
|
||||
def test_default_wrapped_order(self):
|
||||
self.assertEqual(self.sequences(), list(range(2, 10)))
|
||||
|
||||
def test_pc_range_exclusive_upper(self):
|
||||
# seq 9 has KUSEG pc 0x1004, which aliases 0x80001004.
|
||||
self.assertEqual(self.sequences(pc_lo="80001004", pc_hi="8000100C"), [3, 5, 8, 9])
|
||||
|
||||
def test_segments_alias(self):
|
||||
self.assertEqual(self.sequences(pc_lo="A0001000", pc_hi="A0001004"), [2, 4])
|
||||
self.assertEqual(self.sequences(pc_lo="00001000", pc_hi="00001004"), [2, 4])
|
||||
self.assertEqual(self.sequences(pc_lo="80001004", pc_hi="80001008"), [3, 8, 9])
|
||||
|
||||
def test_unknown_dma_initiator_never_matches_pc_filter(self):
|
||||
self.assertIn(7, self.sequences())
|
||||
self.assertNotIn(7, self.sequences(pc_lo="0", pc_hi="FFFFFFFF"))
|
||||
self.assertEqual(self.sequences(pc_hi="80001000"), [])
|
||||
|
||||
def test_newest_and_count_after_filter(self):
|
||||
self.assertEqual(self.sequences(pc_lo="80001004", pc_hi="8000100C", newest=1, count=1), [9])
|
||||
self.assertEqual(self.sequences(pc_lo="80001004", pc_hi="8000100C", count=1), [3])
|
||||
|
||||
def test_combined_address_frame_filters(self):
|
||||
self.assertEqual(self.sequences(pc_lo="80001004", pc_hi="8000100C",
|
||||
addr_lo="80001008", addr_hi="80001018",
|
||||
frame_lo=11, frame_hi=12), [5])
|
||||
|
||||
def test_empty_ranges(self):
|
||||
self.assertEqual(self.sequences(pc_lo="80001004", pc_hi="80001004"), [])
|
||||
self.assertEqual(self.sequences(pc_hi="0"), [])
|
||||
|
||||
def test_dma_recorded_initiator_and_response_bounds(self):
|
||||
reply = self.query(pc_lo="80001008", pc_hi="8000100C")
|
||||
self.assertEqual(reply["pc_lo"], "0x80001008")
|
||||
self.assertEqual(reply["pc_hi"], "0x8000100C")
|
||||
self.assertEqual(reply["entries"][0]["dma_ch"], 2)
|
||||
self.assertEqual(reply["entries"][0]["seq"], 5)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if find_compiler() is None:
|
||||
print("SKIP: no C compiler (set CC)")
|
||||
sys.exit(SKIP_EXIT)
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user