Skip to content

Commit b075bf5

Browse files
committed
Add MUL/DIV corner case and partial flush tests to int_muldiv_shim
Add 8 new tests covering review feedback gaps: - REMU basic (unsigned remainder path) - DIV/DIVU by zero (quotient = 0xFFFFFFFF per RISC-V spec) - REM by zero (remainder = dividend per RISC-V spec) - DIV signed overflow (-2^31 / -1 = -2^31) - REM signed overflow (-2^31 % -1 = 0) - Partial flush suppresses younger in-flight MUL - Partial flush keeps older in-flight MUL
1 parent 713ea72 commit b075bf5

File tree

1 file changed

+246
-3
lines changed

1 file changed

+246
-3
lines changed

verif/cocotb_tests/tomasulo/fu_shims/test_int_muldiv_shim.py

Lines changed: 246 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@
1414

1515
"""Unit tests for the int_muldiv_shim module.
1616
17-
Tests MUL, MULH, MULHSU, MULHU, DIV, DIVU, REM operations, busy
18-
signalling, and flush behavior. MUL has ~4-cycle latency, DIV has
19-
~17-cycle latency, so tests poll for completion.
17+
Tests MUL, MULH, MULHSU, MULHU, DIV, DIVU, REM, REMU operations,
18+
divide-by-zero, signed overflow, busy signalling, and full/partial
19+
flush behavior. MUL has ~4-cycle latency, DIV has ~17-cycle latency,
20+
so tests poll for completion.
2021
"""
2122

2223
from typing import Any
@@ -452,3 +453,245 @@ async def test_flush_clears_div(dut: Any) -> None:
452453
await FallingEdge(iface.clock)
453454
result = iface.read_div_fu_complete()
454455
assert result["valid"] is False, "DIV result should be suppressed after flush"
456+
457+
458+
# ============================================================================
459+
# Test 13: REMU basic (unsigned remainder)
460+
# ============================================================================
461+
@cocotb.test()
462+
async def test_remu_basic(dut: Any) -> None:
463+
"""REMU: 43 % 7 = 1 (unsigned remainder)."""
464+
iface = await setup(dut)
465+
466+
rob_tag = 12
467+
iface.drive_issue(
468+
valid=True,
469+
rob_tag=rob_tag,
470+
op=_op("REMU"),
471+
src1_value=43,
472+
src2_value=7,
473+
)
474+
await RisingEdge(iface.clock)
475+
iface.clear_issue()
476+
477+
result = await wait_for_div_complete(iface)
478+
assert (
479+
result["tag"] == rob_tag
480+
), f"tag mismatch: got {result['tag']}, expected {rob_tag}"
481+
assert result["value"] == 1, f"Expected 1, got {result['value']}"
482+
483+
484+
# ============================================================================
485+
# Test 14: DIV by zero -> quotient = 0xFFFFFFFF
486+
# ============================================================================
487+
@cocotb.test()
488+
async def test_div_by_zero(dut: Any) -> None:
489+
"""DIV: x / 0 = -1 (0xFFFFFFFF) per RISC-V spec."""
490+
iface = await setup(dut)
491+
492+
rob_tag = 13
493+
iface.drive_issue(
494+
valid=True,
495+
rob_tag=rob_tag,
496+
op=_op("DIV"),
497+
src1_value=42,
498+
src2_value=0,
499+
)
500+
await RisingEdge(iface.clock)
501+
iface.clear_issue()
502+
503+
result = await wait_for_div_complete(iface)
504+
assert (
505+
result["tag"] == rob_tag
506+
), f"tag mismatch: got {result['tag']}, expected {rob_tag}"
507+
assert (
508+
result["value"] == MASK32
509+
), f"DIV by zero should return 0xFFFFFFFF, got 0x{result['value']:08X}"
510+
511+
512+
# ============================================================================
513+
# Test 15: DIVU by zero -> quotient = 0xFFFFFFFF
514+
# ============================================================================
515+
@cocotb.test()
516+
async def test_divu_by_zero(dut: Any) -> None:
517+
"""DIVU: x / 0 = 0xFFFFFFFF per RISC-V spec."""
518+
iface = await setup(dut)
519+
520+
rob_tag = 14
521+
iface.drive_issue(
522+
valid=True,
523+
rob_tag=rob_tag,
524+
op=_op("DIVU"),
525+
src1_value=100,
526+
src2_value=0,
527+
)
528+
await RisingEdge(iface.clock)
529+
iface.clear_issue()
530+
531+
result = await wait_for_div_complete(iface)
532+
assert (
533+
result["tag"] == rob_tag
534+
), f"tag mismatch: got {result['tag']}, expected {rob_tag}"
535+
assert (
536+
result["value"] == MASK32
537+
), f"DIVU by zero should return 0xFFFFFFFF, got 0x{result['value']:08X}"
538+
539+
540+
# ============================================================================
541+
# Test 16: REM by zero -> remainder = dividend
542+
# ============================================================================
543+
@cocotb.test()
544+
async def test_rem_by_zero(dut: Any) -> None:
545+
"""REM: x % 0 = x per RISC-V spec."""
546+
iface = await setup(dut)
547+
548+
rob_tag = 15
549+
dividend = 123
550+
iface.drive_issue(
551+
valid=True,
552+
rob_tag=rob_tag,
553+
op=_op("REM"),
554+
src1_value=dividend,
555+
src2_value=0,
556+
)
557+
await RisingEdge(iface.clock)
558+
iface.clear_issue()
559+
560+
result = await wait_for_div_complete(iface)
561+
assert (
562+
result["tag"] == rob_tag
563+
), f"tag mismatch: got {result['tag']}, expected {rob_tag}"
564+
assert (
565+
result["value"] == dividend
566+
), f"REM by zero should return dividend ({dividend}), got {result['value']}"
567+
568+
569+
# ============================================================================
570+
# Test 17: DIV signed overflow (-2^31 / -1 = -2^31)
571+
# ============================================================================
572+
@cocotb.test()
573+
async def test_div_signed_overflow(dut: Any) -> None:
574+
"""DIV: 0x80000000 / 0xFFFFFFFF = 0x80000000 (signed overflow)."""
575+
iface = await setup(dut)
576+
577+
rob_tag = 16
578+
min_int = 0x8000_0000 # -2^31 in signed 32-bit
579+
neg_one = 0xFFFF_FFFF # -1 in signed 32-bit
580+
581+
iface.drive_issue(
582+
valid=True,
583+
rob_tag=rob_tag,
584+
op=_op("DIV"),
585+
src1_value=min_int,
586+
src2_value=neg_one,
587+
)
588+
await RisingEdge(iface.clock)
589+
iface.clear_issue()
590+
591+
result = await wait_for_div_complete(iface)
592+
assert (
593+
result["tag"] == rob_tag
594+
), f"tag mismatch: got {result['tag']}, expected {rob_tag}"
595+
assert (
596+
result["value"] == min_int
597+
), f"DIV overflow should return 0x80000000, got 0x{result['value']:08X}"
598+
599+
600+
# ============================================================================
601+
# Test 18: REM signed overflow (-2^31 % -1 = 0)
602+
# ============================================================================
603+
@cocotb.test()
604+
async def test_rem_signed_overflow(dut: Any) -> None:
605+
"""REM: 0x80000000 % 0xFFFFFFFF = 0 (signed overflow)."""
606+
iface = await setup(dut)
607+
608+
rob_tag = 17
609+
min_int = 0x8000_0000
610+
neg_one = 0xFFFF_FFFF
611+
612+
iface.drive_issue(
613+
valid=True,
614+
rob_tag=rob_tag,
615+
op=_op("REM"),
616+
src1_value=min_int,
617+
src2_value=neg_one,
618+
)
619+
await RisingEdge(iface.clock)
620+
iface.clear_issue()
621+
622+
result = await wait_for_div_complete(iface)
623+
assert (
624+
result["tag"] == rob_tag
625+
), f"tag mismatch: got {result['tag']}, expected {rob_tag}"
626+
assert (
627+
result["value"] == 0
628+
), f"REM overflow should return 0, got 0x{result['value']:08X}"
629+
630+
631+
# ============================================================================
632+
# Test 19: Partial flush suppresses younger in-flight MUL
633+
# ============================================================================
634+
@cocotb.test()
635+
async def test_partial_flush_suppresses_younger(dut: Any) -> None:
636+
"""Partial flush with flush_tag younger than in-flight op suppresses result."""
637+
iface = await setup(dut)
638+
639+
# Issue MUL with rob_tag=10
640+
iface.drive_issue(
641+
valid=True,
642+
rob_tag=10,
643+
op=_op("MUL"),
644+
src1_value=7,
645+
src2_value=6,
646+
)
647+
await RisingEdge(iface.clock)
648+
iface.clear_issue()
649+
650+
# Partial flush: flush_tag=5, head=0 -> tag 10 is younger than 5, gets flushed
651+
iface.drive_partial_flush(flush_tag=5, head_tag=0)
652+
await RisingEdge(iface.clock)
653+
iface.clear_partial_flush()
654+
await FallingEdge(iface.clock)
655+
656+
# Wait for multiplier to finish; result should be suppressed
657+
for _ in range(MAX_LATENCY):
658+
await RisingEdge(iface.clock)
659+
await FallingEdge(iface.clock)
660+
result = iface.read_mul_fu_complete()
661+
assert (
662+
result["valid"] is False
663+
), "MUL result should be suppressed after partial flush of younger tag"
664+
665+
666+
# ============================================================================
667+
# Test 20: Partial flush keeps older in-flight MUL
668+
# ============================================================================
669+
@cocotb.test()
670+
async def test_partial_flush_keeps_older(dut: Any) -> None:
671+
"""Partial flush with flush_tag older than in-flight op keeps result."""
672+
iface = await setup(dut)
673+
674+
# Issue MUL with rob_tag=3
675+
rob_tag = 3
676+
iface.drive_issue(
677+
valid=True,
678+
rob_tag=rob_tag,
679+
op=_op("MUL"),
680+
src1_value=7,
681+
src2_value=6,
682+
)
683+
await RisingEdge(iface.clock)
684+
iface.clear_issue()
685+
686+
# Partial flush: flush_tag=10, head=0 -> tag 3 is older than 10, not flushed
687+
iface.drive_partial_flush(flush_tag=10, head_tag=0)
688+
await RisingEdge(iface.clock)
689+
iface.clear_partial_flush()
690+
691+
# Result should still appear
692+
result = await wait_for_mul_complete(iface)
693+
assert result["valid"], "MUL result should NOT be suppressed (tag is older)"
694+
assert (
695+
result["tag"] == rob_tag
696+
), f"tag mismatch: got {result['tag']}, expected {rob_tag}"
697+
assert result["value"] == 42, f"Expected 42, got {result['value']}"

0 commit comments

Comments
 (0)