Skip to content

Fix RAS pop-then-push writing the new return address to the wrong stack slot#31

Open
tejassinghbhati wants to merge 3 commits into
bsc-loca:mainfrom
tejassinghbhati:fix/ras-pop-then-push
Open

Fix RAS pop-then-push writing the new return address to the wrong stack slot#31
tejassinghbhati wants to merge 3 commits into
bsc-loca:mainfrom
tejassinghbhati:fix/ras-pop-then-push

Conversation

@tejassinghbhati

@tejassinghbhati tejassinghbhati commented Jul 17, 2026

Copy link
Copy Markdown

Summary

  • fix the return address stack so the pop-then-push case (JALR with rd = link, rs1 = link, rd != rs1, e.g. jalr ra, t0) replaces the top-of-stack entry instead of writing to the dead slot above it
  • add a self-checking unit testbench for return_address_stack.sv (there was none) covering LIFO ordering, pop-then-push top-entry replacement, chained pop-then-push, and full-depth pointer wrap-around, wired into scripts/questa_ci.sh

Why

head_pointer points to the next free slot; the top of the stack is read from
output_pointer = head_pointer - 1. The simultaneous push_i && pop_i case wrote
address_stack[head_pointer], a slot return_address_o never reads and the next
plain push overwrites. The stack was therefore left unchanged and the new return
address was dropped, so the following ret in that call chain always mispredicted.
See #30 for the full walk-through.

Fix

end else if (push_i && pop_i) begin
    address_stack[output_pointer] <= pc_execution_i;

head_pointer correctly stays unchanged, since pop-then-push leaves the stack
depth the same. Performance-only; no architectural behavior change.

Validation

Ran the new testbench with Verilator 5.032 (--binary --timing):

  • fixed RTL: all 25 checks pass (TEST PASSED)

  • original RTL (negative control): the pop-then-push regression checks fail
    exactly as the issue predicts, while plain LIFO and wrap-around still pass:

    FAIL: test 2 pop returns C (not stale B): return_address_o = 0x0000000080002000, expected 0x0000000080003000
    FAIL: test 3 second pop-then-push pops B: return_address_o = 0x0000000080004000, expected 0x0000000080005000
    FAIL: test 3 pop returns C: return_address_o = 0x0000000080004000, expected 0x0000000080006000
    TEST FAILED, 3 errors
    

The testbench follows the structure of the neighboring tb_branch_predictor
(same runtest.sh QuestaSim flow, colors.vh, wave.do) so it also runs under
make questa via scripts/questa_ci.sh.

Fixes #30

For JALR with rd=link, rs1=link, rd!=rs1 the decoder asserts push and pop
simultaneously (pop-then-push per the RISC-V RAS hint table). The RAS wrote
the new return address at head_pointer, the empty slot above the top of the
stack, leaving the stack unchanged and dropping the new return address. The
following return then always mispredicted.

Write the replacement into the top entry (output_pointer = head_pointer - 1)
instead. The head pointer correctly stays unchanged, as pop-then-push leaves
the stack depth the same.

Fixes bsc-loca#30
Covers basic LIFO ordering, the pop-then-push top-entry replacement
(regression for the bug fixed in the previous commit), chained
pop-then-push sequences, and full-depth pointer wrap-around. Follows the
structure of the neighboring tb_branch_predictor testbench and is wired
into scripts/questa_ci.sh.

Verified with Verilator 5.032 (--binary --timing): all 25 checks pass on
the fixed RTL; against the previous RTL the pop-then-push checks fail
(stale top-of-stack value returned), confirming the regression isolates
the defect described in bsc-loca#30.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RAS: pop-then-push JALR (rd=link, rs1=link, rd≠rs1) writes the new return address to the wrong stack slot

1 participant