Intuition What this page is for
The parent note showed you the rules of RISC-V instruction encoding. This page hunts down every scenario those rules can produce — every sign, every field-split, every "gotcha" — and works each one to the last bit. Nothing is left to your imagination.
We will lean hard on two ideas the parent introduced:
a 12-bit immediate is a signed Two's Complement number (top bit = sign);
field splitting keeps rs1/rs2/rd in fixed places (fast decode).
If either feels shaky, keep the parent note RV32I/RV64I open beside this one.
Before we compute anything, let's map the territory . Instruction encoding has a small number of independent "axes" that can each flip. A worked example is only useful if, together with the others, it visits every cell .
Axis (what can vary)
Cell A
Cell B
Cell C
Immediate sign
positive imm
negative imm
zero / edge (-2048, +2047)
Format & field layout
I-type (contiguous imm)
S-type (imm split in two)
B/J-type (imm split and scaled ×2)
Fits in one instr?
yes (≤12 bits)
no → needs lui+addi pair
huge → 64-bit RV64I
Branch direction
forward (imm > 0)
backward (imm < 0)
not-taken (fall through)
Degenerate / x0
uses x0=0
nop (all-zero effect)
overflow wrap-around
RV32I vs RV64I
32-bit sign-extend
64-bit sign-extend
addw 32-bit wrap on 64-bit reg
Below, each example is tagged with the cells it covers. By example 9 every cell above has been visited at least once.
Intuition How to read the bit diagrams
Look at . This is the field map we will reuse. Each colored block is a run of bits with a fixed position in the 32-bit word. The two orange arrows show the scary part : in S-type and B-type the immediate is torn into pieces and scattered — but every other field stays exactly where it was. Keep this picture in mind; every example is just "fill in these boxes."
addi x5, x6, 100
Forecast: Guess the 32-bit hex before reading on. Is 100 going to sign-extend to something negative? (Hint: is bit 11 of 100 set?)
Step 1 — Pick the format & fixed fields.
addi is I-type : opcode = 0010011, funct3 = 000.
Why this step? Every derivation begins by locking the constant fields; they never depend on the operands, so getting them first means we can't misplace the movable ones.
Step 2 — Encode the register numbers.
rd = 5 = 00101, rs1 = 6 = 00110.
Why this step? Registers are plain 5-bit unsigned numbers (2 5 = 32 registers, exactly x0–x31). No sign games here.
Step 3 — Encode the immediate.
100 in binary is 0000 0110 0100. Bit 11 (the leftmost of the 12) is 0 , so this value is non-negative ; sign-extension will pad with zeros → stays + 100 .
Why this step? We must check bit 11 before trusting the number, because a 12-bit field is signed.
Step 4 — Pack the 32 bits. Layout is imm[11:0] | rs1 | funct3 | rd | opcode:
imm 000001100100 r s 1 00110 f u n c t 3 000 r d 00101 o p co d e 0010011
Grouping into 8-bit bytes gives 0x06430293.
Verify: Decode back — top 12 bits 0x064 = 100, positive, so x 5 = x 6 + 100 . Sign-extended value is exactly 100 . ✓
addi x5, x6, -1 and predict the stored value.
Forecast: What 12 bits represent − 1 ? What does that sign-extend to in 32 bits?
Step 1 — Represent − 1 in 12-bit two's complement.
− 1 = 1111 1111 1111 (all ones). See Two's Complement : all-ones is always − 1 regardless of width.
Why this step? The processor never "knows" you meant subtract — it just sign-extends the bits. So we must produce the bit pattern of − 1 , not the decimal.
Step 2 — Sign-extend to XLEN = 32.
Bit 11 is 1 → copy it leftward: the immediate becomes 0xFFFFFFFF.
Why this step? Sign-extension is why addi ..., -1 really subtracts one. The 12-bit -1 and the 32-bit -1 are the same number , just wider.
Step 3 — Result. x 5 = x 6 + ( − 1 ) = x 6 − 1 . RISC-V needs no subi opcode: a negative immediate is subtraction.
Verify: If x 6 = 10 , then x 5 = 9 . The immediate field 0xFFF sign-extended = − 1 ; 10 + ( − 1 ) = 9 . ✓
Worked example What are the largest and smallest immediates
addi accepts, and what happens at +2048?
Forecast: A 12-bit signed field — what's its range? Try to place 2048.
Step 1 — Compute the signed range of 12 bits.
A signed n -bit field spans [ − 2 n − 1 , 2 n − 1 − 1 ] . With n = 12 : [ − 2048 , + 2047 ] .
Why this step? One negative "slot" is spent on 0 , which is why the positive top (2047 ) is one less than the negative bottom's magnitude (2048 ).
Step 2 — Encode the extremes.
+2047 = 0111 1111 1111, -2048 = 1000 0000 0000.
Why this step? These are the "walls." +2047 has bit 11 = 0 (still positive); -2048 has bit 11 = 1 with all-zero magnitude — the most negative pattern.
Step 3 — What about addi x5, x6, 2048?
2048 needs bit 11 set, but a set bit 11 means negative . So 2048 does not fit . The assembler must instead emit lui+addi (see Example 5) or use a temp register.
Why this step? This is the exact frontier where "one instruction" stops working — the reason the two-instruction constant-load pattern exists.
Verify: 2 11 − 1 = 2047 ✓ and − 2 11 = − 2048 ✓; the bit pattern 1000 0000 0000 interpreted signed = − 2048 , confirming 2048 overflows. ✓
Look at : the store immediate is torn into a high piece imm[11:5] (7 bits, top of the word) and a low piece imm[4:0] (5 bits, where rd sits in other formats). The teal brackets show which bits go where.
sw x7, -4(x8) (store word x7 at address x8 - 4).
Forecast: The offset is -4. Which bits land in the high piece, which in the low piece?
Step 1 — Fixed fields. sw is S-type : opcode = 0100011, funct3 = 010.
Step 2 — Registers. In stores, rs1 = x8 (base address) = 01000, rs2 = x7 (value to store) = 00111.
Why this step? Note there is no rd — a store produces no register result. That freed 5-bit slot is exactly where the low immediate piece hides.
Step 3 — Build the full 12-bit immediate for − 4 .
− 4 = 1111 1111 1100 (12 bits, two's complement).
Why this step? We form the whole number first, then cut it — never the reverse, or we'd mis-slice.
Step 4 — Split it.
imm[11:5] = 1111111 (top 7 bits) → placed at bits [31:25].
imm[4:0] = 11100 (bottom 5 bits) → placed at bits [11:7].
Why this step? The split is deliberate: rs1,rs2,funct3 stay in their I/R-type homes so the decoder reads them without knowing the format yet.
Step 5 — Effective address. x 8 + sext ( − 4 ) = x 8 − 4 .
Verify: Reassemble 1111111 · 11100 = 1111 1111 1100 = − 4 ✓. If x 8 = 0 x 1000 , address = 0x0FFC. ✓
0xDEADBEEF into x10 (the classic li expansion).
Forecast: 0xBEEF's low 12 bits are 0xEEF. Is 0xEEF positive or negative as a signed 12-bit? That decides whether we must "round up" the upper part.
Step 1 — Split the constant into upper 20 + lower 12.
0xDEADBEEF = upper 0xDEADB (bits 31:12) and lower 0xEEF (bits 11:0).
Why this step? lui fills bits 31:12 (shifting a 20-bit immediate left by 12); addi adds a signed 12-bit low part. Together they cover 32 bits.
Step 2 — Check the sign of the low part.
0xEEF = 1110 1111 1111; bit 11 = 1 → negative : 0xEEF as signed 12-bit = − 273 .
Why this step? This is the subtlety. addi will sign-extend 0xEEF to 0xFFFFFEEF and add it — which subtracts 273 , corrupting the upper bits unless we compensate.
Step 3 — Round the upper part up by 1.
Because the low add is really "− 273 ", pre-add 1 to the upper immediate: 0xDEADB → 0xDEADC.
lui x 10 , 0xDEADC ⇒ x 10 = 0xDEADC000
addi x 10 , x 10 , − 273 ⇒ x 10 = 0xDEADC000 + ( 0xFFFFFEEF )
Why this step? Adding a sign-extended − 273 to 0xDEADC000 gives back the "missing" 0xBEEF low bits and undoes the +1 we added upstairs.
Verify: 0xDEADC000 + 0xFFFFFEEF = 0xDEADBEEF (mod 2 32 ) ✓ — the carry from the wrap cancels the extra 1.
0x12345678 into x11.
Forecast: Low 12 bits 0x678 — sign bit set or not? Do we round the upper part this time?
Step 1 — Split. Upper 0x12345, lower 0x678.
Step 2 — Sign-check the low part. 0x678 = 0110 0111 1000; bit 11 = 0 → positive (+ 1656 ).
Why this step? Positive low part means addi adds exactly what we want — no correction needed . Contrast Example 5.
Step 3 — Emit.
lui x 11 , 0x12345 ; addi x 11 , x 11 , 0x678
Verify: 0x12345000 + 0x678 = 0x12345678 ✓. No +1 on the upper part. ✓
See : two instructions on an address line. The bne sits at 0x104; the arrow curves backward to loop at 0x100, so the offset is negative and small.
bne x1, x0, loop where loop is 4 bytes before the branch.
Forecast: offset = target − pc = 0x100 − 0x104 = -4. B-type immediates are in units of 2 bytes — so what's the "scaled" number stored?
Step 1 — Compute the byte offset. target − p c = − 4 bytes.
Why this step? Branch targets are always relative to the branch's own address pc; PC-relative addressing (see Addressing Modes ) is why loops are position-independent.
Step 2 — Enforce the implicit bit 0 = 0.
The B-type immediate does not store bit 0 (instructions are ≥2-byte aligned). − 4 is even ✓, so the low bit being dropped loses nothing.
Why this step? Dropping the always-zero bit is what lets a 12-bit field reach ± 4 KiB instead of ± 2 KiB — free doubling of range.
Step 3 — Compare against x0. bne x1, x0, ... branches iff x 1 = 0 . No dedicated "branch-if-nonzero" opcode is needed; the zero register is the constant 0 .
Verify: From 0x104, offset − 4 lands on 0x100 = loop ✓, and − 4 is even so bit 0 = 0 is satisfied ✓.
Worked example Exam twist: "A
jal has a 20-bit immediate, so it reaches ± 2 19 bytes." True or false — and what's the real reach?
Forecast: Remember the store-the-bit-0 trick. Does J-type scale by 2 like B-type?
Step 1 — Recall J-type stores a scaled offset. Like B-type, bit 0 is implicit (0). So the 20 stored bits represent a half-count of bytes.
Why this step? The claim confuses "20 bits" with "20 bits of bytes ." The units are 2-byte steps .
Step 2 — Compute the true range.
Signed 20-bit half-steps: [ − 2 19 , 2 19 − 1 ] half-steps × 2 bytes = [ − 2 20 , + 2 20 − 2 ] bytes = ± 1 MiB .
Why this step? Multiplying the count by 2 doubles the byte reach — exactly the same mechanism as Example 7, one bit wider.
Step 3 — Verdict. False. The reach is ± 2 20 bytes (± 1 MiB), not ± 2 19 .
Verify: 2 20 = 1 , 048 , 576 bytes = 1 MiB ✓; the naive 2 19 is exactly half of that. ✓
Worked example Two edge cases at once:
(a) What does addi x0, x0, 0 do? (b) On RV64I , if x1 = 0xFFFFFFFF (largest 32-bit unsigned), what is addw x2, x1, x1?
Forecast (a): Where does the result go? Forecast (b): Does the 64-bit result matter, or only the low 32 bits (then sign-extended)?
Step 1 — Case (a): the canonical nop.
addi x0, x0, 0 computes 0 + 0 = 0 and writes it to x0 — but x0 discards all writes . Net effect: nothing changes . That is the official nop.
Why this step? This is the ultimate degenerate instruction: a real 32-bit encoding whose semantics are "do nothing," useful for pipeline stalls (see Pipelining ) and alignment padding.
Step 2 — Case (b): compute the 32-bit sum with wrap.
0xFFFFFFFF + 0xFFFFFFFF = 0x1FFFFFFFE. addw keeps only the low 32 bits → 0xFFFFFFFE.
Why this step? *w instructions exist precisely so 32-bit C arithmetic wraps correctly on a 64-bit machine — the top carry bit is thrown away.
Step 3 — Sign-extend the 32-bit result to 64 bits.
Low-32 result 0xFFFFFFFE has bit 31 = 1 → negative → sign-extend to 0xFFFFFFFFFFFFFFFE.
Why this step? Every *w result is defined to sign-extend, so a 32-bit value sits "naturally" in a 64-bit register.
Verify (a): result in x0 = 0 regardless — nop ✓.
Verify (b): low 32 bits of 0 x 1 F F F F F F F E = 0xFFFFFFFE; sign-extended = 0xFFFFFFFFFFFFFFFE ✓.
Recall Which cells are still uncovered after Example 9? (answer)
None — every row of the matrix (sign, format layout, fit, direction, degenerate, RV32I/RV64I) was hit. ✓
Recall Reveal
Why does 0xEEF force a +1 on the lui part but 0x678 does not? ::: 0xEEF has bit 11 set → signed-negative → the addi subtracts, so the upper part is pre-incremented to compensate. 0x678's bit 11 is clear → positive → no correction.
A 12-bit signed immediate's range? ::: [ − 2048 , + 2047 ] .
Real reach of a jal (J-type)? ::: ± 2 20 bytes = ± 1 MiB (offsets scale by 2).
What is addi x0, x0, 0? ::: The canonical nop — the write to x0 is discarded.
Mnemonic The three "sign traps"
S ign-extend the immediate · S plit but re-assemble whole first · S caled by 2 for branch/jump. If an encoding surprises you, one of these three S's is the culprit.
Related: RISC vs CISC · Assembler & Pseudo-instructions (the li and nop expansions live there).