Add TCG-mode helper functions that boot a real CPU and interact with the Debug Module through clock stepping, then add the first batch of TCG tests:
tcg/halt-dpc – halt via HALTREQ, verify DPC/DCSR tcg/resethaltreq-priority – resethaltreq takes effect on reset tcg/gpr-rw – read/write GPRs via abstract cmds tcg/fpr-rw – read/write FPRs via abstract cmds rv32-gpr64-notsup – 64-bit GPR access rejected on RV32 tcg/command-ignored-on-cmderr – sticky cmderr blocks new cmds Signed-off-by: Chao Liu <[email protected]> --- tests/qtest/riscv-dm-test.c | 355 ++++++++++++++++++++++++++++++++++++ 1 file changed, 355 insertions(+) diff --git a/tests/qtest/riscv-dm-test.c b/tests/qtest/riscv-dm-test.c index 1b1ab02284..2e7c29ed7c 100644 --- a/tests/qtest/riscv-dm-test.c +++ b/tests/qtest/riscv-dm-test.c @@ -213,6 +213,139 @@ static void sim_cpu_exception(QTestState *qts) rom_write32(qts, ROM_EXCP, 0); } +/* TCG-mode helpers: real CPU execution with clock stepping. */ + +static QTestState *tcg_dm_init(void) +{ + const char *arch = qtest_get_arch(); + const char *cpu_type = strstr(arch, "64") ? "rv64" : "rv32"; + g_autofree char *args = g_strdup_printf( + "-machine virt -accel tcg -smp 1 -cpu %s,sdext=true", cpu_type); + QTestState *qts = qtest_init(args); + + /* Check VM status */ + QDict *resp = qtest_qmp(qts, "{'execute': 'query-status'}"); + const QDict *ret = qdict_get_qdict(resp, "return"); + bool running = qdict_get_bool(ret, "running"); + const char *vm_status = qdict_get_str(ret, "status"); + g_test_message("VM status: %s running=%d", vm_status, running); + qobject_unref(resp); + + if (!running) { + resp = qtest_qmp(qts, "{'execute': 'cont'}"); + g_test_message("Sent cont"); + qobject_unref(resp); + } + + /* Let the CPU boot via MTTCG thread */ + g_usleep(TCG_BOOT_US); + + dm_set_active(qts); + return qts; +} + +static bool tcg_wait_for_halt(QTestState *qts) +{ + for (int t = 0; t < TCG_POLL_TIMEOUT_US; t += TCG_POLL_STEP_US) { + g_usleep(TCG_POLL_STEP_US); + uint32_t st = dm_read(qts, A_DMSTATUS); + if (t < TCG_POLL_STEP_US * 5) { + g_test_message("halt poll: DMSTATUS=0x%08x", st); + } + if (st & DMSTATUS_ALLHALTED) { + return true; + } + } + return false; +} + +static bool tcg_wait_for_cmd_done(QTestState *qts) +{ + for (int t = 0; t < TCG_POLL_TIMEOUT_US; t += TCG_POLL_STEP_US) { + g_usleep(TCG_POLL_STEP_US); + if (!(dm_read(qts, A_ABSTRACTCS) & ABSTRACTCS_BUSY)) { + return true; + } + } + return false; +} + +static bool tcg_halt_hart(QTestState *qts) +{ + dm_write(qts, A_DMCONTROL, DMCONTROL_DMACTIVE | DMCONTROL_HALTREQ); + return tcg_wait_for_halt(qts); +} + +static bool tcg_resume_hart(QTestState *qts) +{ + dm_write(qts, A_DMCONTROL, DMCONTROL_DMACTIVE | DMCONTROL_RESUMEREQ); + for (int t = 0; t < TCG_POLL_TIMEOUT_US; t += TCG_POLL_STEP_US) { + g_usleep(TCG_POLL_STEP_US); + if (dm_read(qts, A_DMSTATUS) & DMSTATUS_ALLRESUMEACK) { + return true; + } + } + return false; +} + + +/* + * Read a register via Access Register abstract command. + * Returns true on success, false on error or timeout. + */ +static bool tcg_abstract_read_reg(QTestState *qts, uint32_t regno, + uint32_t *val) +{ + dm_write(qts, A_ABSTRACTCS, ABSTRACTCS_CMDERR_MASK); + + uint32_t cmd = (0u << 24) | /* cmdtype = Access Register */ + (1u << 17) | /* transfer = 1 */ + (2u << 20) | /* aarsize = 32-bit */ + (regno & 0xFFFF); + dm_write(qts, A_COMMAND, cmd); + + if (!tcg_wait_for_cmd_done(qts)) { + return false; + } + + uint32_t acs = dm_read(qts, A_ABSTRACTCS); + if (acs & ABSTRACTCS_CMDERR_MASK) { + return false; + } + + *val = dm_read(qts, A_DATA0); + return true; +} + +/* + * Write a register via Access Register abstract command. + * Returns true on success, false on error or timeout. + */ +static bool tcg_abstract_write_reg(QTestState *qts, uint32_t regno, + uint32_t val) +{ + dm_write(qts, A_ABSTRACTCS, ABSTRACTCS_CMDERR_MASK); + dm_write(qts, A_DATA0, val); + + uint32_t cmd = (0u << 24) | /* cmdtype = Access Register */ + (1u << 17) | /* transfer = 1 */ + (1u << 16) | /* write = 1 */ + (2u << 20) | /* aarsize = 32-bit */ + (regno & 0xFFFF); + dm_write(qts, A_COMMAND, cmd); + + if (!tcg_wait_for_cmd_done(qts)) { + return false; + } + + uint32_t acs = dm_read(qts, A_ABSTRACTCS); + return !(acs & ABSTRACTCS_CMDERR_MASK); +} + +/* + * Write a 64-bit register via Access Register abstract command (aarsize=3). + * DATA1 holds high 32 bits, DATA0 holds low 32 bits. + */ /* * Test: dmactive gate. @@ -930,6 +1063,218 @@ static void test_haltsum1_window(void) * 4. Read DPC via abstract command → verify it's a valid address * 5. Resume CPU, then re-halt to verify the cycle works */ +static void test_tcg_halt_dpc(void) +{ + QTestState *qts = tcg_dm_init(); + + /* Halt the CPU */ + g_assert_true(tcg_halt_hart(qts)); + + uint32_t status = dm_read(qts, A_DMSTATUS); + g_assert_cmpuint(status & DMSTATUS_ALLHALTED, ==, DMSTATUS_ALLHALTED); + g_assert_cmpuint(status & DMSTATUS_ANYRUNNING, ==, 0); + + /* Read DCSR: verify cause = HALTREQ (3) */ + uint32_t dcsr; + g_assert_true(tcg_abstract_read_reg(qts, REGNO_DCSR, &dcsr)); + uint32_t cause = (dcsr & DCSR_CAUSE_MASK) >> DCSR_CAUSE_SHIFT; + g_assert_cmpuint(cause, ==, DCSR_CAUSE_HALTREQ); + + /* Read DPC: implementation dependent, but it must stay aligned. */ + uint32_t dpc; + g_assert_true(tcg_abstract_read_reg(qts, REGNO_DPC, &dpc)); + g_assert_cmpuint(dpc & 1u, ==, 0); + + /* Resume the CPU */ + g_assert_true(tcg_resume_hart(qts)); + status = dm_read(qts, A_DMSTATUS); + g_assert_cmpuint(status & DMSTATUS_ALLRESUMEACK, ==, + DMSTATUS_ALLRESUMEACK); + + /* Let it run a bit, then re-halt */ + g_usleep(TCG_BOOT_US); + + g_assert_true(tcg_halt_hart(qts)); + + /* DPC after re-halt is also execution dependent; only assert alignment */ + uint32_t dpc2; + g_assert_true(tcg_abstract_read_reg(qts, REGNO_DPC, &dpc2)); + g_assert_cmpuint(dpc2 & 1u, ==, 0); + + /* DCSR cause should be HALTREQ again */ + g_assert_true(tcg_abstract_read_reg(qts, REGNO_DCSR, &dcsr)); + cause = (dcsr & DCSR_CAUSE_MASK) >> DCSR_CAUSE_SHIFT; + g_assert_cmpuint(cause, ==, DCSR_CAUSE_HALTREQ); + + qtest_quit(qts); +} + +/* + * Test: reset-haltreq and haltreq asserted together. + * + * Priority per Debug Spec v1.0 Table 4.2 requires reset-haltreq (cause=5) + * to win over haltreq (cause=3). + */ +static void test_tcg_resethaltreq_priority(void) +{ + QTestState *qts = tcg_dm_init(); + uint32_t status, dcsr, cause; + + dm_write(qts, A_DMCONTROL, DMCONTROL_DMACTIVE | DMCONTROL_SETRESETHALTREQ); + + dm_write(qts, A_DMCONTROL, DMCONTROL_DMACTIVE | + DMCONTROL_HARTRESET | DMCONTROL_HALTREQ); + dm_write(qts, A_DMCONTROL, DMCONTROL_DMACTIVE | DMCONTROL_HALTREQ); + + g_assert_true(tcg_wait_for_halt(qts)); + status = dm_read(qts, A_DMSTATUS); + g_assert_cmpuint(status & DMSTATUS_ALLHALTED, ==, DMSTATUS_ALLHALTED); + + g_assert_true(tcg_abstract_read_reg(qts, REGNO_DCSR, &dcsr)); + cause = (dcsr & DCSR_CAUSE_MASK) >> DCSR_CAUSE_SHIFT; + g_assert_cmpuint(cause, ==, DCSR_CAUSE_RESET); + + dm_write(qts, A_DMCONTROL, DMCONTROL_DMACTIVE | + DMCONTROL_CLRRESETHALTREQ | DMCONTROL_HALTREQ); + dm_write(qts, A_DMCONTROL, DMCONTROL_DMACTIVE); + + qtest_quit(qts); +} + +/* + * Test: Read/Write all 32 GPR registers via abstract commands (TCG). + * + * For each GPR x0..x31: + * - x0: hardwired to 0, read should return 0 + * - x1..x31: write a test pattern, read back and verify + */ +static void test_tcg_gpr_rw(void) +{ + QTestState *qts = tcg_dm_init(); + + g_assert_true(tcg_halt_hart(qts)); + + /* x0 is hardwired to 0 */ + uint32_t val; + g_assert_true(tcg_abstract_read_reg(qts, REGNO_GPR(0), &val)); + g_assert_cmpuint(val, ==, 0); + + /* x1..x31: write test patterns and read back */ + for (int i = 1; i < 32; i++) { + uint32_t pattern = 0xA5000000u | (uint32_t)i; + + g_assert_true(tcg_abstract_write_reg(qts, REGNO_GPR(i), pattern)); + g_assert_true(tcg_abstract_read_reg(qts, REGNO_GPR(i), &val)); + g_assert_cmpuint(val, ==, pattern); + } + + /* Verify x0 is still 0 after all writes */ + g_assert_true(tcg_abstract_read_reg(qts, REGNO_GPR(0), &val)); + g_assert_cmpuint(val, ==, 0); + + qtest_quit(qts); +} + +/* + * Test: Read/Write all 32 FPR registers via abstract commands (TCG). + * + * For each FPR f0..f31: + * - Write a 32-bit test pattern (single-precision view) + * - Read back and verify + */ +static void test_tcg_fpr_rw(void) +{ + QTestState *qts = tcg_dm_init(); + + g_assert_true(tcg_halt_hart(qts)); + + uint32_t val; + + for (int i = 0; i < 32; i++) { + uint32_t pattern = 0x3F800000u + (uint32_t)i; /* 1.0f + i */ + + g_assert_true(tcg_abstract_write_reg(qts, REGNO_FPR(i), pattern)); + g_assert_true(tcg_abstract_read_reg(qts, REGNO_FPR(i), &val)); + g_assert_cmpuint(val, ==, pattern); + } + + qtest_quit(qts); +} + +/* + * Test: RV32 rejects 64-bit GPR abstract accesses at command decode time. + */ +static void test_rv32_gpr64_notsup(void) +{ + QTestState *qts; + uint32_t acs; + const char *arch = qtest_get_arch(); + + if (!strstr(arch, "32")) { + g_test_skip("RV32-only"); + return; + } + + qts = qtest_init("-machine virt"); + + dm_set_active(qts); + dm_write(qts, A_DMCONTROL, DMCONTROL_DMACTIVE | DMCONTROL_HALTREQ); + sim_cpu_halt_ack(qts, 0); + dm_write(qts, A_ABSTRACTCS, ABSTRACTCS_CMDERR_MASK); + + dm_write(qts, A_COMMAND, + (0u << 24) | (1u << 17) | (3u << 20) | REGNO_GPR(1)); + + acs = dm_read(qts, A_ABSTRACTCS); + g_assert_cmpuint(acs & ABSTRACTCS_BUSY, ==, 0); + g_assert_cmpuint((acs & ABSTRACTCS_CMDERR_MASK) >> + ABSTRACTCS_CMDERR_SHIFT, ==, 2); + + qtest_quit(qts); +} + +/* + * Test: COMMAND writes are ignored while CMDERR is non-zero. + */ +static void test_tcg_command_ignored_on_cmderr(void) +{ + QTestState *qts = tcg_dm_init(); + + g_assert_true(tcg_halt_hart(qts)); + + /* Baseline last command: read x0, result must be 0. */ + dm_write(qts, A_ABSTRACTCS, ABSTRACTCS_CMDERR_MASK); + dm_write(qts, A_COMMAND, + (0u << 24) | (1u << 17) | (2u << 20) | REGNO_GPR(0)); + g_assert_true(tcg_wait_for_cmd_done(qts)); + g_assert_cmpuint(dm_read(qts, A_DATA0), ==, 0); + + /* Latch cmderr with an unsupported command. */ + dm_write(qts, A_COMMAND, 0xFF000000); + uint32_t acs = dm_read(qts, A_ABSTRACTCS); + g_assert_cmpuint((acs & ABSTRACTCS_CMDERR_MASK) >> + ABSTRACTCS_CMDERR_SHIFT, ==, 2); + + /* + * This command must be ignored because cmderr != 0. + * If it incorrectly overwrites last_cmd, later autoexec will read DCSR. + */ + dm_write(qts, A_COMMAND, + (0u << 24) | (1u << 17) | (2u << 20) | REGNO_DCSR); + + dm_write(qts, A_ABSTRACTCS, ABSTRACTCS_CMDERR_MASK); + dm_write(qts, A_ABSTRACTAUTO, 0x1); /* autoexec on data0 */ + dm_write(qts, A_DATA0, 0xDEADBEEF); /* triggers autoexec of last_cmd */ + + g_assert_true(tcg_wait_for_cmd_done(qts)); + acs = dm_read(qts, A_ABSTRACTCS); + g_assert_cmpuint((acs & ABSTRACTCS_CMDERR_MASK) >> + ABSTRACTCS_CMDERR_SHIFT, ==, 2); + g_assert_cmpuint(dm_read(qts, A_DATA0), ==, 0xDEADBEEF); + + qtest_quit(qts); +} + /* * Test: Halt and resume cycle via ROM simulation. @@ -1008,5 +1353,15 @@ int main(int argc, char *argv[]) qtest_add_func("/riscv-dm/abstract-cmd-exception", test_abstract_cmd_exception); + /* TCG-mode tests: real CPU execution */ + qtest_add_func("/riscv-dm/tcg/halt-dpc", test_tcg_halt_dpc); + qtest_add_func("/riscv-dm/tcg/resethaltreq-priority", + test_tcg_resethaltreq_priority); + qtest_add_func("/riscv-dm/tcg/gpr-rw", test_tcg_gpr_rw); + qtest_add_func("/riscv-dm/tcg/fpr-rw", test_tcg_fpr_rw); + qtest_add_func("/riscv-dm/rv32-gpr64-notsup", test_rv32_gpr64_notsup); + qtest_add_func("/riscv-dm/tcg/command-ignored-on-cmderr", + test_tcg_command_ignored_on_cmderr); + return g_test_run(); } -- 2.53.0
