Skip to content

Commit 18f5c81

Browse files
committed
gate cache_write_enable_from_load with stall
1 parent 5796b0f commit 18f5c81

File tree

2 files changed

+116
-1
lines changed

2 files changed

+116
-1
lines changed

hw/rtl/cpu_and_mem/cpu/cache/cache_write_controller.sv

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,10 @@ module cache_write_controller #(
155155
end
156156

157157
logic cache_write_enable_from_load;
158-
assign cache_write_enable_from_load = cache_write_enable_from_load_registered;
158+
// Only fire the deferred load fill when the pipeline is advancing.
159+
// Otherwise the registered load enable can remain high across multi-cycle stalls
160+
// and repeatedly overwrite cache lines with stale load data.
161+
assign cache_write_enable_from_load = cache_write_enable_from_load_registered & ~i_stall;
159162

160163
// ===========================================================================
161164
// AMO Write Enable

verif/cocotb_tests/test_directed_multicycle.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,3 +504,115 @@ async def test_fld_faddd_load_use_hazard(dut: Any) -> None:
504504
await drain_pipeline(dut_if, state, use_fp_monitor=True)
505505

506506
cocotb.log.info("=== PASSED: FLD -> FADD.D load-use hazard ===")
507+
508+
509+
@cocotb.test()
510+
async def test_lh_bext_load_use_hazard(dut: Any) -> None:
511+
"""Test LH followed immediately by BEXT after an unrelated stall.
512+
513+
This reproduces a stale load-hit forwarding corner case:
514+
1. Warm cache with LH from address A (value has bit1=1)
515+
2. Trigger a multi-cycle DIV stall
516+
3. Execute LH from uncached address B (value bit1=0)
517+
4. Immediately consume with BEXT x3, x27, x15
518+
519+
Expected result is x3=0. Any stale forwarding from address A produces x3=1.
520+
"""
521+
cocotb.log.info("=== Test: LH -> BEXT integer load-use hazard ===")
522+
523+
dut_if, state, mem_model = await setup_test(dut, use_fp_monitor=False)
524+
525+
from encoders.op_tables import I_ALU, LOADS
526+
527+
enc_addi, _ = I_ALU["addi"]
528+
enc_lh, _ = LOADS["lh"]
529+
enc_bext, eval_bext = R_ALU["bext"]
530+
enc_div, eval_div = R_ALU["div"]
531+
532+
# Address A (cache warmup, bit1=1) and address B (expected miss, bit1=0)
533+
addr_a = 0x120
534+
addr_b = 0x220
535+
value_a = 0x00000002
536+
value_b = 0x00000000
537+
bit_index = 1
538+
539+
dut.data_memory_for_simulation.memory[addr_a >> 2].value = value_a
540+
dut.data_memory_for_simulation.memory[addr_b >> 2].value = value_b
541+
mem_model.write_word(addr_a, value_a)
542+
mem_model.write_word(addr_b, value_b)
543+
544+
cocotb.log.info(
545+
f"Memory init: A=0x{addr_a:08X}->0x{value_a:08X}, "
546+
f"B=0x{addr_b:08X}->0x{value_b:08X}"
547+
)
548+
549+
# Setup integer operands/registers.
550+
await execute_instruction(
551+
dut_if,
552+
state,
553+
enc_addi(10, 0, addr_a),
554+
10,
555+
addr_a,
556+
f"ADDI x10, x0, 0x{addr_a:X}",
557+
first_after_warmup=True,
558+
)
559+
await execute_instruction(
560+
dut_if,
561+
state,
562+
enc_addi(11, 0, addr_b),
563+
11,
564+
addr_b,
565+
f"ADDI x11, x0, 0x{addr_b:X}",
566+
)
567+
await execute_instruction(
568+
dut_if, state, enc_addi(15, 0, bit_index), 15, bit_index, "ADDI x15, x0, 1"
569+
)
570+
await execute_instruction(
571+
dut_if, state, enc_addi(7, 0, 100), 7, 100, "ADDI x7, x0, 100"
572+
)
573+
await execute_instruction(dut_if, state, enc_addi(8, 0, 3), 8, 3, "ADDI x8, x0, 3")
574+
575+
# Warm cache entry for address A (first load may miss and fill).
576+
await execute_instruction(
577+
dut_if, state, enc_lh(5, 10, 0), 5, 0x00000002, "LH x5, 0(x10)"
578+
)
579+
# Second load from same address should be served via cache-hit path.
580+
await execute_instruction(
581+
dut_if, state, enc_lh(6, 10, 0), 6, 0x00000002, "LH x6, 0(x10)"
582+
)
583+
584+
# Insert unrelated multi-cycle stall to stress stale forwarding state.
585+
div_expected = eval_div(100, 3)
586+
await execute_instruction(
587+
dut_if, state, enc_div(9, 7, 8), 9, div_expected, "DIV x9, x7, x8 (100/3)"
588+
)
589+
590+
# Critical pair: load from B then immediate dependent BEXT.
591+
await execute_instruction(
592+
dut_if, state, enc_lh(27, 11, 0), 27, 0x00000000, "LH x27, 0(x11)"
593+
)
594+
bext_expected = eval_bext(0x00000000, bit_index)
595+
await execute_instruction(
596+
dut_if,
597+
state,
598+
enc_bext(3, 27, 15),
599+
3,
600+
bext_expected,
601+
"BEXT x3, x27, x15",
602+
)
603+
604+
await drain_pipeline(dut_if, state, use_fp_monitor=False)
605+
606+
x27_hw = dut_if.read_register(27)
607+
x3_hw = dut_if.read_register(3)
608+
cocotb.log.info(f"Final x27 = 0x{x27_hw:08X} (expected 0x00000000)")
609+
cocotb.log.info(f"Final x3 = 0x{x3_hw:08X} (expected 0x{bext_expected:08X})")
610+
611+
assert (
612+
x27_hw == 0x00000000
613+
), f"x27 mismatch: got 0x{x27_hw:08X}, expected 0x00000000"
614+
assert (
615+
x3_hw == bext_expected
616+
), f"x3 mismatch: got 0x{x3_hw:08X}, expected 0x{bext_expected:08X}"
617+
618+
cocotb.log.info("=== PASSED: LH -> BEXT integer load-use hazard ===")

0 commit comments

Comments
 (0)