Add the ROM simulation helpers (sim_cpu_going_ack,
sim_cpu_exception) and tests that exercise the abstract command
interface and hart-window registers in qtest mode:
abstractauto – auto-execute on data/progbuf write
command-not-halted – command rejected when hart running
invalid-regno-exception – out-of-range register access
abstract-cmd-flow – SMP abstract read/write cycle
abstract-cmd-exception – exception during abstract command
hawindow – hart array window register
haltsum0-window – haltsum0 with 64-hart SMP
haltsum1-window – haltsum1 with 64-hart SMP
Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
---
tests/qtest/riscv-dm-test.c | 315 ++++++++++++++++++++++++++++++++++++
1 file changed, 315 insertions(+)
diff --git a/tests/qtest/riscv-dm-test.c b/tests/qtest/riscv-dm-test.c
index 3a0dd1cbd4..1b1ab02284 100644
--- a/tests/qtest/riscv-dm-test.c
+++ b/tests/qtest/riscv-dm-test.c
@@ -195,6 +195,24 @@ static void sim_cpu_resume_ack(QTestState *qts, uint32_t hartid)
rom_write32(qts, ROM_RESUME, hartid);
}
+/*
+ * Simulate the CPU executing the GOING acknowledgment.
+ * ROM code: detects GOING flag → writes 0 to GOING offset → jumps to cmd.
+ */
+static void sim_cpu_going_ack(QTestState *qts)
+{
+ rom_write32(qts, ROM_GOING, 0);
+}
+
+/*
+ * Simulate CPU hitting exception during abstract command.
+ * ROM exception handler writes 0 to EXCP offset.
+ */
+static void sim_cpu_exception(QTestState *qts)
+{
+ rom_write32(qts, ROM_EXCP, 0);
+}
+
/*
* Test: dmactive gate.
@@ -626,6 +644,293 @@ static void test_ackhavereset(void)
qtest_quit(qts);
}
+/*
+ * Test: Unsupported register numbers report CMDERR=EXCEPTION.
+ */
+static void test_invalid_regno_exception(void)
+{
+ QTestState *qts = qtest_init("-machine virt");
+ uint32_t cmd, acs, cmderr;
+
+ 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);
+
+ cmd = (0u << 24) | (1u << 17) | (2u << 20) | 0x1040u;
+ dm_write(qts, A_COMMAND, cmd);
+
+ acs = dm_read(qts, A_ABSTRACTCS);
+ cmderr = (acs & ABSTRACTCS_CMDERR_MASK) >> ABSTRACTCS_CMDERR_SHIFT;
+ g_assert_cmpuint(cmderr, ==, 3);
+
+ qtest_quit(qts);
+}
+
+/*
+ * Test: ABSTRACTAUTO read/write.
+ */
+static void test_abstractauto(void)
+{
+ QTestState *qts = qtest_init("-machine virt");
+ dm_set_active(qts);
+
+ /* Write autoexec pattern */
+ dm_write(qts, A_ABSTRACTAUTO, 0x00030003);
+ g_assert_cmpuint(dm_read(qts, A_ABSTRACTAUTO), ==, 0x00030003);
+
+ /* Clear */
+ dm_write(qts, A_ABSTRACTAUTO, 0);
+ g_assert_cmpuint(dm_read(qts, A_ABSTRACTAUTO), ==, 0);
+
+ qtest_quit(qts);
+}
+
+/*
+ * Test: COMMAND write when hart is not halted should set CMDERR.
+ */
+static void test_command_not_halted(void)
+{
+ QTestState *qts = qtest_init("-machine virt");
+ dm_set_active(qts);
+
+ /* Clear any existing CMDERR */
+ dm_write(qts, A_ABSTRACTCS, ABSTRACTCS_CMDERR_MASK);
+
+ /*
+ * Issue Access Register command (cmdtype=0, transfer=1, regno=0x1000)
+ * to a running hart. Should fail with HALTRESUME error (4).
+ */
+ uint32_t cmd = (0u << 24) | /* cmdtype = Access Register */
+ (1u << 17) | /* transfer = 1 */
+ (2u << 20) | /* aarsize = 32-bit */
+ 0x1000; /* regno = x0 */
+ dm_write(qts, A_COMMAND, cmd);
+
+ uint32_t acs = dm_read(qts, A_ABSTRACTCS);
+ uint32_t cmderr = (acs & ABSTRACTCS_CMDERR_MASK) >> ABSTRACTCS_CMDERR_SHIFT;
+ /* HALTRESUME error = 4 */
+ g_assert_cmpuint(cmderr, ==, 4);
+
+ /* Clear CMDERR */
+ dm_write(qts, A_ABSTRACTCS, ABSTRACTCS_CMDERR_MASK);
+ acs = dm_read(qts, A_ABSTRACTCS);
+ cmderr = (acs & ABSTRACTCS_CMDERR_MASK) >> ABSTRACTCS_CMDERR_SHIFT;
+ g_assert_cmpuint(cmderr, ==, 0);
+
+ qtest_quit(qts);
+}
+
+/*
+ * Test: Abstract command execution flow (Access Register).
+ *
+ * With hart halted, issue an Access Register command:
+ * 1. Hart must be halted first
+ * 2. Write COMMAND → DM sets BUSY=1, writes instructions to CMD space,
+ * sets FLAGS=GOING
+ * 3. Hart can keep reporting HALTED in the park loop before consuming GO
+ * 4. CPU ROM detects GOING → writes to GOING offset (ack) → jumps to cmd
+ * 5. CPU executes cmd → hits ebreak → re-enters ROM → writes HARTID
+ * 6. DM only completes after the selected hart returns to the park loop
+ */
+static void test_abstract_cmd_flow(void)
+{
+ QTestState *qts = qtest_init("-machine virt -smp 2");
+ dm_set_active(qts);
+
+ /* Halt both harts first. */
+ dm_write(qts, A_DMCONTROL, DMCONTROL_DMACTIVE | DMCONTROL_HALTREQ);
+ sim_cpu_halt_ack(qts, 0);
+ sim_cpu_halt_ack(qts, 1);
+
+ uint32_t status = dm_read(qts, A_DMSTATUS);
+ g_assert_cmpuint(status & DMSTATUS_ALLHALTED, ==, DMSTATUS_ALLHALTED);
+
+ /* Clear any latched CMDERR */
+ dm_write(qts, A_ABSTRACTCS, ABSTRACTCS_CMDERR_MASK);
+
+ /*
+ * Issue Access Register command:
+ * cmdtype=0 (Access Register), transfer=1, write=0 (read),
+ * aarsize=2 (32-bit), regno=0x1000 (x0)
+ */
+ uint32_t cmd = (0u << 24) | /* cmdtype = Access Register */
+ (1u << 17) | /* transfer = 1 */
+ (2u << 20) | /* aarsize = 32-bit */
+ 0x1000; /* regno = x0 */
+ dm_write(qts, A_COMMAND, cmd);
+
+ /* BUSY should be set */
+ uint32_t acs = dm_read(qts, A_ABSTRACTCS);
+ g_assert_cmpuint(acs & ABSTRACTCS_BUSY, ==, ABSTRACTCS_BUSY);
+
+ /*
+ * A different halted hart must not complete the command, and the selected
+ * hart must not complete it before GO has been consumed.
+ */
+ sim_cpu_halt_ack(qts, 1);
+ g_assert_cmpuint(dm_read(qts, A_ABSTRACTCS) & ABSTRACTCS_BUSY, ==,
+ ABSTRACTCS_BUSY);
+ sim_cpu_halt_ack(qts, 0);
+ g_assert_cmpuint(dm_read(qts, A_ABSTRACTCS) & ABSTRACTCS_BUSY, ==,
+ ABSTRACTCS_BUSY);
+
+ /* Simulate CPU: ROM detects GOING flag → writes GOING ack. */
+ sim_cpu_going_ack(qts);
+
+ /* Simulate CPU: execute cmd, hit ebreak, then re-enter ROM. */
+ sim_cpu_halt_ack(qts, 0);
+
+ /* BUSY should be cleared, no error */
+ acs = dm_read(qts, A_ABSTRACTCS);
+ g_assert_cmpuint(acs & ABSTRACTCS_BUSY, ==, 0);
+ uint32_t cmderr =
+ (acs & ABSTRACTCS_CMDERR_MASK) >> ABSTRACTCS_CMDERR_SHIFT;
+ g_assert_cmpuint(cmderr, ==, 0);
+
+ qtest_quit(qts);
+}
+
+/*
+ * Test: Abstract command exception path.
+ *
+ * When CPU hits an exception during abstract cmd execution:
+ * 1. ROM exception handler writes to EXCP offset
+ * 2. DM latches CMDERR=EXCEPTION(3) and stays busy until the hart parks again
+ * 3. CPU ebreak → re-enters ROM → writes HARTID (re-halt)
+ */
+static void test_abstract_cmd_exception(void)
+{
+ QTestState *qts = qtest_init("-machine virt");
+ dm_set_active(qts);
+
+ /* Halt hart 0 */
+ dm_write(qts, A_DMCONTROL, DMCONTROL_DMACTIVE | DMCONTROL_HALTREQ);
+ sim_cpu_halt_ack(qts, 0);
+
+ /* Clear CMDERR */
+ dm_write(qts, A_ABSTRACTCS, ABSTRACTCS_CMDERR_MASK);
+
+ /* Issue Access Register command */
+ uint32_t cmd = (0u << 24) | (1u << 17) | (2u << 20) | 0x1000;
+ dm_write(qts, A_COMMAND, cmd);
+
+ g_assert_cmpuint(dm_read(qts, A_ABSTRACTCS) & ABSTRACTCS_BUSY, ==,
+ ABSTRACTCS_BUSY);
+
+ /* CPU acknowledges going */
+ sim_cpu_going_ack(qts);
+
+ /* CPU hits exception during cmd execution → ROM exception handler */
+ sim_cpu_exception(qts);
+
+ /* BUSY stays set until the hart re-enters the park loop. */
+ uint32_t acs = dm_read(qts, A_ABSTRACTCS);
+ g_assert_cmpuint(acs & ABSTRACTCS_BUSY, ==, ABSTRACTCS_BUSY);
+ uint32_t cmderr =
+ (acs & ABSTRACTCS_CMDERR_MASK) >> ABSTRACTCS_CMDERR_SHIFT;
+ g_assert_cmpuint(cmderr, ==, 3); /* EXCEPTION */
+
+ /* CPU ebreak in exception handler → re-enters ROM → writes HARTID */
+ sim_cpu_halt_ack(qts, 0);
+
+ acs = dm_read(qts, A_ABSTRACTCS);
+ g_assert_cmpuint(acs & ABSTRACTCS_BUSY, ==, 0);
+
+ /* Hart should still be halted */
+ uint32_t st = dm_read(qts, A_DMSTATUS);
+ g_assert_cmpuint(st & DMSTATUS_ALLHALTED, ==, DMSTATUS_ALLHALTED);
+
+ /* Clear CMDERR for cleanup */
+ dm_write(qts, A_ABSTRACTCS, ABSTRACTCS_CMDERR_MASK);
+ acs = dm_read(qts, A_ABSTRACTCS);
+ cmderr = (acs & ABSTRACTCS_CMDERR_MASK) >> ABSTRACTCS_CMDERR_SHIFT;
+ g_assert_cmpuint(cmderr, ==, 0);
+
+ qtest_quit(qts);
+}
+
+/*
+ * Test: HAWINDOWSEL and HAWINDOW read/write.
+ */
+static void test_hawindow(void)
+{
+ QTestState *qts = qtest_init("-machine virt");
+ dm_set_active(qts);
+
+ /* Select window 0 */
+ dm_write(qts, A_HAWINDOWSEL, 0);
+ g_assert_cmpuint(dm_read(qts, A_HAWINDOWSEL), ==, 0);
+
+ /* Write a mask to HAWINDOW */
+ dm_write(qts, A_HAWINDOW, 0x5);
+ g_assert_cmpuint(dm_read(qts, A_HAWINDOW), ==, 0x5);
+
+ /* Clear */
+ dm_write(qts, A_HAWINDOW, 0);
+ g_assert_cmpuint(dm_read(qts, A_HAWINDOW), ==, 0);
+
+ qtest_quit(qts);
+}
+
+/*
+ * Test: HALTSUM0 window follows hartsel[19:5].
+ */
+static void test_haltsum0_window(void)
+{
+ QTestState *qts = qtest_init("-machine virt -smp 64");
+ dm_set_active(qts);
+
+ /* Mark hart 40 halted. */
+ sim_cpu_halt_ack(qts, 40);
+
+ /* Window base 0: hart 40 is out of range 0..31. */
+ dm_write(qts, A_DMCONTROL,
+ DMCONTROL_DMACTIVE | (0u << DMCONTROL_HARTSELLO_SHIFT));
+ g_assert_cmpuint(dm_read(qts, A_HALTSUM0), ==, 0);
+
+ /* Window base 32: hart 40 maps to bit 8. */
+ dm_write(qts, A_DMCONTROL,
+ DMCONTROL_DMACTIVE | (32u << DMCONTROL_HARTSELLO_SHIFT));
+ g_assert_cmpuint(dm_read(qts, A_HALTSUM0), ==, (1u << 8));
+
+ qtest_quit(qts);
+}
+
+/*
+ * Test: HALTSUM1 window follows hartsel[19:10].
+ */
+static void test_haltsum1_window(void)
+{
+ QTestState *qts = qtest_init("-machine virt -smp 64");
+
+ dm_set_active(qts);
+
+ sim_cpu_halt_ack(qts, 33);
+
+ g_assert_cmpuint(dm_read(qts, A_HALTSUM1), ==, (1u << 1));
+
+ dm_write(qts, A_DMCONTROL,
+ DMCONTROL_DMACTIVE | (1u << DMCONTROL_HARTSELHI_SHIFT));
+ g_assert_cmpuint(dm_read(qts, A_HALTSUM1), ==, 0);
+
+ qtest_quit(qts);
+}
+
+/* TCG-mode tests: real CPU execution. */
+
+/*
+ * Test: Halt CPU with TCG and verify DPC/DCSR.
+ *
+ * With real CPU execution:
+ * 1. Boot CPU, let it run briefly
+ * 2. Send HALTREQ → CPU enters debug mode
+ * 3. Read DCSR via abstract command → verify cause = HALTREQ (3)
+ * 4. Read DPC via abstract command → verify it's a valid address
+ * 5. Resume CPU, then re-halt to verify the cycle works
+ */
+
/*
* Test: Halt and resume cycle via ROM simulation.
*
@@ -691,7 +996,17 @@ int main(int argc, char *argv[])
qtest_add_func("/riscv-dm/optional-regs-absent", test_optional_regs_absent);
qtest_add_func("/riscv-dm/deactivate", test_deactivate);
qtest_add_func("/riscv-dm/ackhavereset", test_ackhavereset);
+ qtest_add_func("/riscv-dm/abstractauto", test_abstractauto);
+ qtest_add_func("/riscv-dm/command-not-halted", test_command_not_halted);
+ qtest_add_func("/riscv-dm/invalid-regno-exception",
+ test_invalid_regno_exception);
+ qtest_add_func("/riscv-dm/hawindow", test_hawindow);
+ qtest_add_func("/riscv-dm/haltsum0-window", test_haltsum0_window);
+ qtest_add_func("/riscv-dm/haltsum1-window", test_haltsum1_window);
qtest_add_func("/riscv-dm/halt-resume", test_halt_resume);
+ qtest_add_func("/riscv-dm/abstract-cmd-flow", test_abstract_cmd_flow);
+ qtest_add_func("/riscv-dm/abstract-cmd-exception",
+ test_abstract_cmd_exception);
return g_test_run();
}
--
2.53.0