Skip to content

BUG-010: test_rv32m_control_flow_regression.asm had a wrong MUL encoding and no halt sentinel

Field Value
Severity LOW
Component Testbench
File(s) tb/test_rv32m_control_flow_regression.asm
Status Fixed
Date 2026-07-23

Symptom

This file is not wired into the standard 15-test regression suite — it's only reachable via a manual ASM_TEST= override — so it appears to have never actually been run end to end. Running it (as part of validating BUG-008/BUG-009 against a second control-flow test) hung indefinitely. After adding the missing halt word (below), it ran but failed its own check: x19 actual=9 expected=20.

Root Cause

Two independent hand-encoding mistakes in the pipe-format hex, neither caught because nothing ever exercised them:

  1. No halt sentinel. The file ends with addi x0, x0, 0 | 00000013 | cpu | x19==20 and nothing after it. The core has no notion of "end of program" other than fetching the literal jal x0, 0 (HALT_WORD) bit pattern — without it, if_pc runs past the end of the loaded program and the testbench's wait_for_halt() blocks forever.
  2. Wrong mul encoding. mul s3, s1, s2 was hand-encoded as 0x012489B3, which has funct7=0x00. Real RV32M MUL requires funct7=0x01 (the R-type M-extension marker) — with funct7=0x00 this decodes as plain ADD instead. s1=5, s2=4: ADD gives s3=9 (5+4), matching the observed failure exactly; MUL should have given s3=20 (5×4). The file's own check annotation (x19==20) was written assuming the mul encoding was correct, so nothing flagged the mismatch — the author's expected value and the (wrong) hex were never cross-checked against each other by anything.

Fix

Rather than hand-patch two more hex values in a file that already had two hand-encoding bugs, the file was regenerated from real assembly source using the new assembler (scripts/riscv_asm.py, see toolchain.md):

    addi s1, x0, 5           # check: s1==5
    addi s2, x0, 4           # check: s2==4
    mul  s3, s1, s2          # check: s3==20

    j    path_a               # Path A: unconditional jump, skip the poison line
    addi s3, x0, 99          # skipped — if executed, s3 becomes 99 (fail)
path_a:

    bne  s1, s2, path_b       # s1(5) != s2(4) -> must branch
    addi s3, x0, 77          # skipped — if executed, s3 becomes 77 (fail)
path_b:

    addi x0, x0, 0           # check: s3==20
    halt

tb/test_rv32m_control_flow_regression.s is the new source; tb/test_rv32m_control_flow_regression.asm is regenerated from it (python3 scripts/riscv_asm.py tb/test_rv32m_control_flow_regression.s -o tb/test_rv32m_control_flow_regression.asm) rather than hand-edited. All 3 checks now pass, and this also re-validates the BUG-009 fix against a second control-flow shape (JAL immediately followed by a poison instruction, then BNE).

Notes

This file — plus the INT16 NPU block in cpu_peak_tests.asm (see BUG-011) — is the second and third instance found in this session of a hand-computed hex value silently disagreeing with the comment/mnemonic next to it. This class of bug is structural: nothing re-derives the hex from the mnemonic, so nothing can catch the two drifting apart. The assembler exists specifically to remove this failure mode going forward — see toolchain.md.