This page is a workout . The parent note told you the rules . Here we hit those rules with every awkward case a machine can throw at you: negative offsets, backward branches, zero-register tricks, jumps that reach and jumps that cannot , and an exam-style "decode this hex" twist. If you can survive every cell of the matrix below, no instruction encoding can surprise you.
Intuition Read this first
An instruction is just a 32-bit number. Encoding = turning human assembly into that number. Decoding = turning the number back into meaning. Every example below does one direction or the other, carefully, one field at a time. We never write a bit we cannot justify.
Definition Two pieces of notation we use throughout — read this before the examples
n ( b ) means "the number n , packed into a field that is b bits wide." So 3 4 ( 6 ) says "write 34 using exactly 6 bits" = 100010. It is a bookkeeping label reminding you how many switches that value occupies; it is not a subscript in the ordinary maths sense.
Bit slice x [ 31 : 28 ] means "the bits of x from position 31 down to position 28" — a chunk cut out of a bigger number. Position 0 is the rightmost (lowest) bit. So x [ 31 : 28 ] is the top 4 bits.
Concatenation A ∥ B means "glue the bit patterns A and B side by side" to make one longer pattern — like writing two strings of digits next to each other. E.g. 0001 ∥ 0010 = 00010010.
Each row is a class of situation this topic can produce. The last column names the worked example(s) that hit that cell. Every cell is covered.
#
Case class
What makes it tricky
Covered by
A
R-type, positive registers
field reorder (rd sits in the middle)
Ex 1
B
R-type, shift with shamt
rs unused ⇒ must be 0
Ex 2
C
I-type, positive immediate
offset sits in low 16 bits
Ex 3
D
I-type, negative immediate
two's-complement sign extension
Ex 4
E
Branch, forward (positive offset)
(PC+4)+imm×4
Ex 5
F
Branch, backward (negative offset)
signed word offset, loop back
Ex 6
G
J-type, target reachable (same region)
top 4 bits of PC+4 == top 4 bits of target
Ex 7
H
J-type, target unreachable (degenerate)
top 4 bits of PC+4 ≠ top 4 bits of target ⇒ j cannot reach
Ex 8
I
Zero / degenerate register ($zero)
register number 0 as a real encoding
Ex 9
J
Exam twist: decode raw hex
reverse direction, identify format
Ex 10
Prerequisite vault topics we lean on: MIPS Register File and Conventions , Sign Extension and Immediates , PC-relative vs Absolute Addressing , Control Unit and Decoding .
Figure s01 (above): the three MIPS layouts stacked. Top row R = opcode(6), rs(5), rt(5), rd(5), shamt(5), funct(6). Middle row I = opcode(6), rs(5), rt(5), immediate(16). Bottom row J = opcode(6), address(26). Bit 31 is on the left, bit 0 on the right; the leftmost box (opcode) is the "master key" the decoder reads first.
Keep this picture open. Every example just fills the coloured boxes with numbers, then converts to binary/hex. The register numbers we use (from MIPS Register File and Conventions ):
Recall Register numbers used on this page
$zero=0, $t0=8, $t1=9, $t2=10, $s0=16, $s1=17, $s2=18, $s3=19.
Worked example Ex 1 — Encode
sub $t0, $s1, $s2
Forecast: guess where $t0 lands in the 32 bits. First register in the text — is it first in the bits? (It is not .)
Step 1 — Identify the slots. sub rd, rs, rt ⇒ rd=$t0=8, rs=$s1=17, rt=$s2=18.
Why this step? The assembly syntax order is destination-first, but the bit order is rs, rt, rd (sources first). Mapping names to slots before touching bits stops this classic mix-up.
Step 2 — Fill the constant fields. opcode=0 (all R-type), shamt=0 (not a shift), funct=34 for sub.
Why this step? opcode 0 means "look at funct." For sub, funct is 100010 = 34.
Step 3 — Lay out the six fields.
0 ( 6 ) ∣ 1 7 ( 5 ) ∣ 1 8 ( 5 ) ∣ 8 ( 5 ) ∣ 0 ( 5 ) ∣ 3 4 ( 6 )
Binary: 000000 10001 10010 01000 00000 100010
Why this step? Concatenating the fields MSB→LSB in the order opcode, rs, rt, rd, shamt, funct builds the final 32-bit word.
Step 4 — Convert to hex. Regroup the same 32 bits into nibbles of 4 (ignoring the field boundaries): 0000 0010 0011 0010 0100 0000 0010 0010 = 0x02324022.
Why this step? Hex is just binary in groups of 4 bits — each nibble is one hex digit. The field boundaries (6/5/5/…) do not line up with nibble boundaries, so we re-slice the exact same 32 bits into groups of 4 before reading off the hex digits.
Verify: Count widths 6 + 5 + 5 + 5 + 5 + 6 = 32 . ✓ Read rd back out (bits [ 15 : 11 ] ) → 01000 = 8 = $t0. ✓
Worked example Ex 2 — Encode
sll $t0, $s1, 4 (shift left logical by 4)
Forecast: a shift has only one source register and a small count. Which field holds the count 4, and what happens to the now-unused rs?
Step 1 — Map the operands. sll rd, rt, shamt ⇒ rd=$t0=8, rt=$s1=17, shamt=4. rs is not used ⇒ rs=0.
Why this step? Shifts read the value to shift from rt, put the count in shamt, and have no second register — so rs must be zeroed, not left blank. A blank field is still 5 physical bits; the decoder will read whatever is there.
Step 2 — Constants. opcode=0, funct=0 for sll.
Why this step? sll is R-type (opcode 0); its funct code is 000000 = 0.
Step 3 — Lay out.
0 ( 6 ) ∣ 0 ( 5 ) ∣ 1 7 ( 5 ) ∣ 8 ( 5 ) ∣ 4 ( 5 ) ∣ 0 ( 6 )
Binary: 000000 00000 10001 01000 00100 000000
Why this step? Notice shamt finally does something (=4). In every non-shift R-type it was forced to 0; here it carries the answer.
Step 4 — Hex. Re-slice the 32 bits into nibbles: 0000 0000 0001 0001 0100 0001 0000 0000 = 0x00114100.
Why this step? Again the 6/5/5/5/5/6 field cuts do not match the 4-bit hex cuts, so we regroup by 4 and translate each nibble to one hex digit.
Verify: shamt field (bits [ 10 : 6 ] ) = 00100 = 4. ✓ rs field = 00000 = 0 as required. ✓
Worked example Ex 3 — Encode
addi $t0, $s3, 100
Forecast: the immediate 100 fits easily in 16 bits. Which register field becomes the destination now that R-type's rd is gone?
Step 1 — Map operands. addi rt, rs, imm ⇒ rt=$t0=8 (destination ), rs=$s3=19 (source), imm=100.
Why this step? I-type dropped rd, so rt inherits the destination role. This is the opposite of rt's source role in stores — always re-derive it.
Step 2 — opcode. opcode for addi = 8.
Why this step? I-type has a non-zero opcode that names the operation directly (no funct field exists).
Step 3 — Immediate in 16 bits. 100 = 0000 0000 0110 0100.
Why this step? 100 is positive and < 2 15 , so no sign issues — just pad with leading zeros to 16 bits.
Step 4 — Lay out and convert.
8 ( 6 ) ∣ 1 9 ( 5 ) ∣ 8 ( 5 ) ∣ 10 0 ( 16 )
Binary: 001000 10011 01000 0000000001100100
Hex: re-slice into nibbles 0010 0010 0110 1000 0000 0000 0110 0100 = 0x22680064.
Why this step? The hex conversion regroups the 32 bits by 4; the low 16 immediate bits neatly become the last four hex digits 0064, which is a fast sanity check that the immediate landed in the right place.
Verify: 6 + 5 + 5 + 16 = 32 ✓. Low 16 bits 0x0064 = 100. ✓
Worked example Ex 4 — Encode
addi $t0, $s3, -8
Forecast: how do you fit a negative number into an unsigned-looking 16-bit box? Guess the top bit.
Step 1 — Two's complement of − 8 in 16 bits. − 8 = 2 16 − 8 = 65528 = 1111 1111 1111 1000.
Why this step? Immediates for addi/lw/sw are signed (see Sign Extension and Immediates ). Negative values are stored in two's complement, so the top (sign) bit is 1.
Step 2 — Same slots as Ex 3. rt=$t0=8, rs=$s3=19, opcode=8.
Why this step? Sign of the immediate does not change where fields go — only the bit pattern of the immediate.
Step 3 — Lay out.
8 ( 6 ) ∣ 1 9 ( 5 ) ∣ 8 ( 5 ) ∣ FFF8 ( 16 )
Binary: 001000 10011 01000 1111111111111000
Hex: 0010 0010 0110 1000 1111 1111 1111 1000 = 0x2268FFF8.
Why this step? Regrouping the 32 bits into nibbles gives the hex; the negative immediate shows up as the high-valued last four digits FFF8, the visual signature of a small negative number.
Step 4 — What the datapath does at runtime. The 16-bit 0xFFF8 is sign-extended to 32 bits 0xFFFFFFF8 before adding. That 32-bit value equals − 8 , so $s3 + (-8) is computed correctly.
Why this step? Without sign extension the ALU would see + 65528 and give the wrong result. This is exactly why the immediate must be signed .
Verify: 0 xFFF8 = 65528 ; as a signed 16-bit value 65528 − 65536 = − 8 . ✓ Sign-extended 0 xFFFFFFF8 = 4294967288 ; signed 32-bit = − 8 . ✓
Worked example Ex 5 — Encode
beq $t0, $t1, L where L is 5 instructions ahead
Forecast: guess the stored immediate. Is it 5, or is it the byte distance?
Step 1 — Map the register fields. beq rs, rt, offset ⇒ rs=$t0=8, rt=$t1=9. opcode for beq = 4.
Why this step? beq is I-type, so before worrying about the offset we fill the same register boxes as any I-type: opcode, rs, rt.
Step 2 — What "ahead" is measured from. The offset is counted from the instruction after the branch, i.e. from address P C + 4 .
Why this step? By the time the branch executes, the PC has already advanced by 4 (the fetch of the next instruction). MIPS defines the offset relative to that P C + 4 (see PC-relative vs Absolute Addressing ).
Step 3 — Stored immediate = word distance. L sits 5 instructions past P C + 4 's target, so imm = 5 (in words ), stored in the 16-bit field as 0x0005.
Why this step? Offsets are stored in words, not bytes, to stretch the 16-bit reach.
Step 4 — Lay out the I-type word.
4 ( 6 ) ∣ 8 ( 5 ) ∣ 9 ( 5 ) ∣ 5 ( 16 )
Binary: 000100 01000 01001 0000000000000101
Hex: 0001 0001 0000 1001 0000 0000 0000 0101 = 0x11090005.
Why this step? Same "fill the coloured boxes, then re-slice into nibbles" workflow as every I-type — the offset just happens to feed the branch adder instead of the ALU.
Step 5 — Hardware reconstructs the byte target.
target = ( P C + 4 ) + imm × 4 = ( P C + 4 ) + 20 = P C + 24
Why the ×4? Instructions are 4 bytes and word-aligned, so the hardware shifts the stored word count left by 2.
Figure s02 (above): the seven consecutive instruction addresses P, P+4, …, P+24 running top to bottom. P holds the beq (magenta), P+24 holds the target L (orange). The magenta arrow starts at PC+4 (= P+4, violet) and spans five word-steps down to L, labelled imm = 5 words = 20 bytes, showing the target is measured from PC+4, not from the branch itself .
Verify: Encoding = 0 x 11090005 (opcode 4, rs 8, rt 9, imm 5). If P C = 0 x 00400000 , target = 0 x 00400000 + 24 = 0 x 00400018 . ✓
Worked example Ex 6 — Encode
bne $t0, $zero, TOP where TOP is 3 instructions before the branch
Forecast: a loop jumps backwards . So the stored immediate must be… positive or negative?
Step 1 — Map the register fields. bne rs, rt, offset ⇒ rs=$t0=8, rt=$zero=0. opcode for bne = 5.
Why this step? Even a loop-back branch is a plain I-type; we fill opcode/rs/rt first, exactly as before. $zero in rt gives the honest field 00000.
Step 2 — Count words from P C + 4 . Let the branch be at address P . Then P C + 4 = P + 4 . TOP is 3 instructions before the branch, i.e. at P − 12 .
Why this step? Always measure from P + 4 , even when going backwards — the rule never changes.
Step 3 — Solve for the word offset.
target = ( P + 4 ) + imm × 4 = P − 12 ⇒ imm × 4 = − 16 ⇒ imm = − 4
Why this step? The offset is a signed word count; a backward branch gives a negative immediate.
Step 4 — Encode − 4 in 16 bits. − 4 = 1111 1111 1111 1100 = 0xFFFC.
Why this step? Two's complement, sign bit 1 — same mechanism as Ex 4, but here it lives in the branch immediate.
Step 5 — Lay out the I-type word and convert.
5 ( 6 ) ∣ 8 ( 5 ) ∣ 0 ( 5 ) ∣ FFFC ( 16 )
Binary: 000101 01000 00000 1111111111111100
Hex: re-slice into nibbles 0001 0101 0000 0000 1111 1111 1111 1100 = 0x1500FFFC.
Why this step? Completing the 32-bit word and regrouping by 4 keeps this example on the same "fill the boxes, then read the hex" track as Ex 5 — only the immediate changed sign.
Figure s03 (above): a loop of five addresses P-12 (=TOP, orange) down to P+4 (violet), with the bne at P (magenta). The magenta arrow curves backwards from PC+4 up to TOP, labelled jump back, imm × 4 = -16, illustrating that a backward jump is simply a negative signed immediate.
Verify: Encoding = 0 x 1500 F F F C (opcode 5, rs 8, rt 0, imm 0xFFFC). With P = 0 x 00400020 : target = ( P + 4 ) + ( − 4 ) × 4 = 0 x 00400024 − 16 = 0 x 00400014 = P − 12 . ✓ 0 xFFFC signed = − 4 . ✓
Worked example Ex 7 — Encode
j 0x00400024, with the jump instruction itself at 0x00400010
Forecast: which bits of the address do we actually store? Guess how many we throw away — and whether the top bits of the jump and the target agree.
Step 1 — Check the region first. Jump at P = 0 x 00400010 ⇒ P C + 4 = 0 x 00400014 , whose top 4 bits ([ 31 : 28 ] ) are 0000. Target 0x00400024 top 4 bits are also 0000. They match, so j can reach.
Why this step? j only supplies the low 28 bits of the target; it inherits the top 4 bits from P C + 4 . If those top 4 bits already agree with the target, the jump is reachable — always test this before encoding.
Step 2 — Write the target in binary.
0 x 00400024 = 0000 0000 0100 0000 0000 0000 0010 0100.
Step 3 — Drop the low 2 bits and take bits [ 27 : 2 ] . The low 2 bits are 00 (word-aligned), so store the address shifted right by 2. Right-shift by 2: 0 x 00400024 ≫ 2 = 0 x 00100009 ; the low 26 bits are the field = 0 x 100009 .
Why this step? The always-zero low 2 bits and the PC-supplied top 4 bits are never stored, so only bits [ 27 : 2 ] (26 bits) go into the address field.
Step 4 — Assemble the 32-bit word and convert to hex. opcode j = 2, so opcode bits are 000010 (6 bits) and the field is 0x100009 in 26 bits.
2 ( 6 ) ∣ 0 x 10000 9 ( 26 )
Full 32-bit binary (opcode 000010 then the 26-bit field 00 0001 0000 0000 0000 0000 1001):
00001000 00010000 00000000 00001001
Re-slice into nibbles: 0000 1000 0001 0000 0000 0000 0000 1001 = 0x08100009.
Why this step? Concatenating opcode (top 6) with the 26-bit field gives the 32-bit word; regrouping those exact bits into nibbles of 4 yields the hex. Note the opcode 000010 and the field's leading 00… merge into the first hex digit 0 and the 8 — this is why 2 ( 6 ) ∥0 x 10000 9 ( 26 ) becomes 0x08100009 and not 0x02….
Step 5 — Reconstruct to confirm.
target = ( P C + 4 ) [ 31 : 28 ] ∥ field ∥00
With ( P C + 4 ) [ 31 : 28 ] = 0000: target = 0000 ∥ 0x100009 ∥ 00 = 0 x 00400024 . ✓ We land exactly where asked because the top 4 bits matched (Step 1) .
Verify: 0 x 00400024 ≫ 2 = 0 x 100009 ; encoding ( 2 ≪ 26 ) ∣ 0 x 100009 = 0 x 08100009 ; reconstruct ( 0 x 00400014 & 0 xF0000000 ) ∣ ( 0 x 100009 ≪ 2 ) = 0 x 00400024 . ✓
j at 0x0FFFFFF8 wants to reach target 0x10000000. Can it?
Forecast: the two addresses differ by only 8 bytes — surely a jump can hop 8 bytes? Watch what happens to the top 4 bits when PC+4 sits just below a region boundary.
Step 1 — Compute PC+4 and compare top 4 bits. Jump at P = 0 x 0 F F F F F F 8 ⇒ P C + 4 = 0 x 0 F F F F F F C . Top 4 bits of P C + 4 ([ 31 : 28 ] ) are 0000. Top 4 bits of the target 0x10000000 are 0001. They differ.
Why this step? j forces the target's top 4 bits to equal ( P C + 4 ) [ 31 : 28 ] . Here that is 0000, but the target needs 0001. There is no address-field value that can override those top 4 bits.
Step 2 — Show that no field can produce the target. The 26-bit field can only supply bits [ 27 : 2 ] . Even the largest field, glued with the forced top nibble 0000 and low 00, gives a maximum reachable address of
max = [ 31 : 28 ] 0000 ∥ 26 ones 111 … 1 ∥ 2 00 = 0 x 0 F F F F F F C .
Since the target 0x10000000 > 0 x 0 F F F F F F C , it is outside the reachable window . j cannot encode it.
Why this step? This is the honest degenerate case the matrix (row H) promised: when ( P C + 4 ) [ 31 : 28 ] = target [ 31 : 28 ] , the 256 MB region the jump lives in does not contain the target, so j is impossible — no clever field value fixes it.
Step 3 — What to do instead. Load the full 32-bit target into a register and use jr (jump register, an R-type that reads a complete 32-bit register value — no top-bit stealing).
la $ t 0 , 0 x 10000000 ; jr $ t 0
Why this step? jr takes the entire 32-bit address from a register, so it can reach anywhere — that is exactly why it exists.
Step 4 — Contrast with a case that DOES cross safely. If instead the jump were at 0x0FFFFFFC (so P C + 4 = 0 x 10000000 ), then ( P C + 4 ) [ 31 : 28 ] = 0001, which now matches the target 0x10000000. The field would be ( 0 x 10000000 ≫ 2 ) & ( 2 26 − 1 ) . Here 2 26 − 1 = 0x03FFFFFF (26 ones); AND-ing keeps only the low 26 bits: 0 x 04000000 & 0 x 03 F F F F F F = 0 x 00000000 . Reconstruct: 0001 ∥ 0x0000000 ∥ 00 = 0 x 10000000 . ✓ Reachable — because PC+4 had already crossed into the target's region.
Why this step? It isolates the true deciding factor: reachability depends only on whether P C + 4 and the target share top 4 bits, not on how "close" the byte addresses look.
Verify: Unreachable case: with P C + 4 = 0 x 0 F F F F F F C the max reachable address = ( 0 x 0 F F F F F F C & 0 x F 0000000 ) ∣ (( 2 26 − 1 ) ≪ 2 ) = 0 x 0 F F F F F F C < 0 x 10000000 , so target unreachable. ✓ Crossing case: P C + 4 = 0 x 10000000 , field = ( 0 x 10000000 ≫ 2 ) & 0 x 03 F F F F F F = 0 , reconstruct = ( 0 x 10000000 & 0 x F 0000000 ) ∣ ( 0 ≪ 2 ) = 0 x 10000000 . ✓
Worked example Ex 9 — Encode
add $t0, $s1, $zero (a copy, using $zero)
Forecast: $zero is register number 0. Does its field become blank, or an honest 00000?
Step 1 — Map operands. rd=$t0=8, rs=$s1=17, rt=$zero=0 .
Why this step? $zero is a real register whose contents are hardwired to 0 (see MIPS Register File and Conventions ). Its number is 0, so its 5-bit field is 00000 — a legitimate encoding, not an empty slot.
Step 2 — Constants. opcode=0, shamt=0, funct=32 (add).
Step 3 — Lay out and convert.
0 ( 6 ) ∣ 1 7 ( 5 ) ∣ 0 ( 5 ) ∣ 8 ( 5 ) ∣ 0 ( 5 ) ∣ 3 2 ( 6 )
Binary: 000000 10001 00000 01000 00000 100000.
Hex: re-slice into nibbles 0000 0010 0010 0000 0100 0000 0010 0000 = 0x02204020.
Why this step? Regrouping the field-boundary binary into 4-bit nibbles gives the hex. Because add $s1 + 0 = $s1, this instruction copies $s1 into $t0 — the effect of the move pseudo-instruction. $zero being addressable makes this trick possible.
Verify: rt field (bits [ 20 : 16 ] ) = 00000 = 0 = $zero. ✓ funct = 100000 (6 bits) = 32. ✓
Worked example Ex 10 — Decode the 32-bit word
0x8D0A0004. What instruction is it?
Forecast: you get bits , not assembly. Your first move must be to read the opcode — it decides everything else.
Step 1 — Expand to binary.
0x8D0A0004 = 1000 1101 0000 1010 0000 0000 0000 0100.
Step 2 — Peel off the opcode (top 6 bits).
100011 = 35. opcode 35 = lw ⇒ I-type (see Control Unit and Decoding ).
Why this step? The opcode is the master key. Non-zero opcode ⇒ not R-type; 35 specifically names lw, so we now expect the I-type layout: rs(5), rt(5), imm(16).
Step 3 — Slice the I-type fields. After the 6 opcode bits:
rs = next 5 = 01000 = 8 = $t0
rt = next 5 = 01010 = 10 = $t2
imm = last 16 = 0000 0000 0000 0100 = 4
Step 4 — Reassemble the assembly. lw rt, imm(rs) ⇒ lw $t2, 4($t0) .
Why this step? For lw, rs is the base address, rt is the destination, imm is the byte offset — put them back in lw $rt, offset($rs) order.
Verify: Re-encode lw $t2, 4($t0): opcode 35, rs=8, rt=10, imm=4 → 100011 01000 01010 0000000000000100 = 0x8D0A0004. ✓ Round-trips exactly.
Recall One-line summary of each cell
Positive R-type reorders to rs,rt,rd ::: Ex 1: 0x02324022
Shift uses shamt, rs=0 ::: Ex 2: 0x00114100
Positive immediate pads with zeros ::: Ex 3: 0x22680064
Negative immediate uses two's complement + sign extension ::: Ex 4: 0x2268FFF8
Forward branch stores positive word count ::: Ex 5: 0x11090005, target PC+24
Backward branch stores negative word count ::: Ex 6: 0x1500FFFC, imm 0xFFFC = −4
Reachable jump (same region) stores bits [ 27 : 2 ] ::: Ex 7: 0x08100009
Unreachable jump (top 4 bits differ) → use jr ::: Ex 8
$zero encodes as an honest 00000 ::: Ex 9: 0x02204020
Decode by reading opcode first ::: Ex 10: lw $t2, 4($t0)
Mnemonic The universal decode ritual
O pcode first · then F ields by format · then S ign-extend if immediate · then ×4 if it's a branch/jump offset.
"Only Fools Skip ×4."