lhu
lhu
rd, off(rs1)
rd = mem[rs1 + off]
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

lhu loads a halfword (16 bits, or 2 bytes) from memory and treats it as unsigned. It is the companion to lh, differing in just one thing: how it fills the upper half of the destination register. Both read 16 bits using the same lhu rd, offset(rs1) addressing; the lh page covers the halfword idea and the addressing form.

A register is 32 bits, so a 16-bit load must fill the top 16. lh copies the sign bit across them, keeping negative numbers negative. lhu instead fills them with 0s — called zero-extension — which means the result is always non-negative, somewhere from 0 to 65535. The u in the name marks this unsigned reading.

Which one to use is a statement about what the data means. For values that can be negative, use lh; for values that are never negative, use lhu. The bit pattern 0xFFFF makes the difference vivid: lhu reads it as 65535, while lh reads the very same bits as -1.

Most 16-bit data in practice is unsigned — character codes in certain text encodings, port numbers and checksums in network data, 16-bit pixel formats, length fields in file formats. For all of these, sign-extending would wreck any value whose top bit happened to be set, so lhu is the right tool. Halfwords sit 2 bytes apart, so loops over them step the address by 2.