bltu
bltu
rs1, rs2, label
if (rs1 < rs2) goto label
R
31–25funct7
24–20rs2
19–15rs1
14–12funct3
11–7rd
6–0opcode
I
31–20imm[11:0]
19–15rs1
14–12funct3
11–7rd
6–0opcode
S
31–25imm[11:5]
24–20rs2
19–15rs1
14–12funct3
11–7imm[4:0]
6–0opcode
B
31–25imm[12,10:5]
24–20rs2
19–15rs1
14–12funct3
11–7imm[4:1,11]
6–0opcode
U
31–12imm[31:12]
11–7rd
6–0opcode
J
31–12imm[20,10:1,11,19:12]
11–7rd
6–0opcode

bltu is blt with one change: it compares the two values as unsigned. The name is branch if less than, unsigned. It jumps to the label when rs1 is below rs2 with both read as never-negative numbers (0 up to about 4 billion, no negatives). The blt page covers the branch itself; this page is about why the unsigned reading is a separate instruction.

The same 32 bits can mean two different numbers depending on whether the top bit is read as a negative sign or as part of the value. blt reads them as signed, bltu as unsigned, and the two genuinely disagree: a register whose bits are all 1s is -1 to blt (very small) but the largest possible value to bltu. Pick the wrong one and the comparison silently gives the wrong answer.

Use bltu for anything that cannot be negative: memory addresses, sizes, counts, positions. Comparing addresses with the signed blt is a classic hidden bug, because a large address has its top bit set and signed comparison mistakes it for negative.

There is a neat trick this enables. To check that an index is in range — at least 0 and below the size n — a single bltu i, n, ok does both at once. If i were negative, its unsigned reading would be enormous and fail the test automatically, so one unsigned comparison catches both the too-big and the negative cases.