diff --git a/hdl/hazard3_core.v b/hdl/hazard3_core.v index f7b8790..32b2d8d 100644 --- a/hdl/hazard3_core.v +++ b/hdl/hazard3_core.v @@ -894,10 +894,13 @@ always @ (posedge clk or negedge rst_n) begin `endif if (d_memop_is_amo) begin mw_local_exclusive_reserved <= 1'b0; - end else if (xm_memop == MEMOP_SC_W) begin + end else if (xm_memop == MEMOP_SC_W && bus_dph_ready_d) begin mw_local_exclusive_reserved <= 1'b0; - end else if (xm_memop == MEMOP_LR_W) begin - mw_local_exclusive_reserved <= bus_dph_exokay_d; + end else if (xm_memop == MEMOP_LR_W && bus_dph_ready_d) begin + // In theory, the bus should never report HEXOKAY when HRESP is asserted. + // Still might happen (e.g. if HEXOKAY is tied high), so mask HEXOKAY with + // HREADY to be sure a failed lr.w clears the monitor. + mw_local_exclusive_reserved <= bus_dph_exokay_d && !bus_dph_err_d; end end end diff --git a/test/sim/sw_testcases/load_store_fault.c b/test/sim/sw_testcases/load_store_fault.c new file mode 100644 index 0000000..607028c --- /dev/null +++ b/test/sim/sw_testcases/load_store_fault.c @@ -0,0 +1,40 @@ +#include "tb_cxxrtl_io.h" +#include "hazard3_csr.h" + +// Check load/stores which generate a bus fault generate an exception, and +// report the correct mcause and mepc. + +int main() { + // Word-aligned address which generates an access fault. Constrained to a + // particular register, because the instructions appear in the test log to + // confirm mepc value. + register void *bad_addr asm ("a5") = (void*)0xcdef1234u; + + asm volatile ( + "sw zero, (%0)\n" + "sh zero, (%0)\n" + "sb zero, (%0)\n" + "lw zero, (%0)\n" + "lh zero, (%0)\n" + "lhu zero, (%0)\n" + "lb zero, (%0)\n" + "lbu zero, (%0)\n" + : : "r" (bad_addr) + ); + tb_puts("Done.\n"); + return 0; +} + +void __attribute__((interrupt)) handle_exception() { + tb_printf("-> exception, mcause = %u\n", read_csr(mcause)); + write_csr(mcause, 0); + uint32_t mepc = read_csr(mepc); + if (*(uint16_t*)mepc & 0x3 == 0x3) { + tb_printf("exception instr: %04x%04x\n", *(uint16_t*)(mepc + 2), *(uint16_t*)mepc); + write_csr(mepc, mepc + 4); + } + else { + tb_printf("exception instr: %04x\n", *(uint16_t*)(mepc + 2), *(uint16_t*)mepc); + write_csr(mepc, mepc + 2); + } +} diff --git a/test/sim/sw_testcases/load_store_fault.expected_output b/test/sim/sw_testcases/load_store_fault.expected_output new file mode 100644 index 0000000..e468546 --- /dev/null +++ b/test/sim/sw_testcases/load_store_fault.expected_output @@ -0,0 +1,17 @@ +-> exception, mcause = 7 +exception instr: 0007a023 +-> exception, mcause = 7 +exception instr: 00079023 +-> exception, mcause = 7 +exception instr: 00078023 +-> exception, mcause = 5 +exception instr: 0007a003 +-> exception, mcause = 5 +exception instr: 00079003 +-> exception, mcause = 5 +exception instr: 0007d003 +-> exception, mcause = 5 +exception instr: 00078003 +-> exception, mcause = 5 +exception instr: 0007c003 +Done. diff --git a/test/sim/sw_testcases/lr_sc_fault.c b/test/sim/sw_testcases/lr_sc_fault.c new file mode 100644 index 0000000..976f61c --- /dev/null +++ b/test/sim/sw_testcases/lr_sc_fault.c @@ -0,0 +1,62 @@ +#include "tb_cxxrtl_io.h" +#include "hazard3_csr.h" + +// Check lr/sc which generate a bus fault generate an exception, and +// report the correct mcause and mepc. + +// Calling convention abuse to get stable register allocation without cursed register keyword +uint32_t __attribute__((naked)) do_lr_sc(uint32_t initial_sc, uint32_t *dst, const uint32_t *src) { + asm volatile ( + // a5 used as a dumpster + "lr.w a5, (a2)\n" + // a0 not written if sc.w suppressed -> return value == initial_sc + "sc.w a0, a0, (a1)\n" + "ret" + ); +} + +int main() { + uint32_t scratch_word; + uint32_t sc_result; + uint32_t *bad_addr = (uint32_t*)0xcdef1234u; + uint32_t *good_addr = &scratch_word; + + // Only the lr.w should except, because failed lr.w clears the local monitor, + // suppressing the sc.w. + tb_puts("Failed load, suppressed store\n"); + sc_result = do_lr_sc(123, bad_addr, bad_addr); + // Failing sc.w must write 0 to the success register. + tb_printf("sc.w result: %u\n", sc_result); + + // This time the sc.w should fault, after the successful lr.w. + tb_puts("Good load, failed store\n"); + sc_result = do_lr_sc(123, bad_addr, good_addr); + // Excepted sc.w must not write the success register. + tb_printf("sc.w result: %u\n", sc_result); + + tb_puts("Repeated failed store\n"); + // Repeat just the sc.w. This should now be suppressed, because the prior + // faulting sc.w should clear the monitor bit. + asm volatile ( + "sc.w %0, zero, (%1)\n" + : "+r" (sc_result) : "r" (bad_addr) + ); + // Failing sc.w must write 0 to result register. + tb_printf("sc.w result: %u\n", sc_result); + + return 0; +} + +void __attribute__((interrupt)) handle_exception() { + tb_printf("-> exception, mcause = %u\n", read_csr(mcause)); + write_csr(mcause, 0); + uint32_t mepc = read_csr(mepc); + if (*(uint16_t*)mepc & 0x3 == 0x3) { + tb_printf("exception instr: %04x%04x\n", *(uint16_t*)(mepc + 2), *(uint16_t*)mepc); + write_csr(mepc, mepc + 4); + } + else { + tb_printf("exception instr: %04x\n", *(uint16_t*)(mepc + 2), *(uint16_t*)mepc); + write_csr(mepc, mepc + 2); + } +} diff --git a/test/sim/sw_testcases/lr_sc_fault.expected_output b/test/sim/sw_testcases/lr_sc_fault.expected_output new file mode 100644 index 0000000..c4d5dfd --- /dev/null +++ b/test/sim/sw_testcases/lr_sc_fault.expected_output @@ -0,0 +1,10 @@ +Failed load, suppressed store +-> exception, mcause = 5 +exception instr: 100627af +sc.w result: 0 +Good load, failed store +-> exception, mcause = 7 +exception instr: 18a5a52f +sc.w result: 123 +Repeated failed store +sc.w result: 0