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 <[email protected]> --- 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
