Skip to content
Contents

Deciding: beq, bne and the branch target

Section 5.1 9 min read

  • branch: test, then maybe go elsewhere
  • taken and not taken
  • the branch offset: a distance in instructions
  • the branch target: PC + 4 + offset × 4

Checked against: MIPS32 Vol. II: BEQ, BNE carolyne_ref/dumps/hello_o0.dump (bfc001b0, bfc001ec) carolyne_ref/isa_mips/imm.py (imm_br) carolyne_ref/isa_mips/exec_unit_br.py

Before you start

  • §1.2: a branch's next instruction always runs (the nop)
  • §3.2: sign extension; §1.3: I-format

By the end, you can

  • Say what beq and bne test and what happens when the test holds
  • Compute a branch target from the branch address and the 16-bit field
  • Explain why the field is a distance and why it is multiplied by 4
  • Read b as beq $zero,$zero

Two registers, one test

A tests two registers:

instructionbranches whenotherwise
beq rs,rt,labelrs equals rtcontinues
bne rs,rt,labelrs differs from rtcontinues
b labelalways (it is beq $zero,$zero,label)—

When the test holds, the branch is and the PC moves to the label. When it does not, the branch is not taken and the program continues. One rule from §1.2 still stands: the instruction right after the branch runs first, taken or not. §5.4 explains why.

Three branches, watched

The runner has three branches: a taken beq, a taken bne, and a beq that is not taken. Each path prints a character, so the console shows which paths ran.

Three branches: taken, taken, not taken

What does a branch do when its test holds, and when it does not?

program (address · word · instruction)

PC →0xbfc000000x3c191000_start:lui $t9,0x1000
0xbfc000040x37390ff0ori $t9,$t9,0xff0
0xbfc000080x24080003li $t0,3
0xbfc0000c0x24090003li $t1,3
0xbfc000100x11090003beq $t0,$t1,0xbfc00020
0xbfc000140x00000000nop
0xbfc000180x240a004eli $t2,78
0xbfc0001c0xaf2a0000sw $t2,0($t9)
0xbfc000200x240a0059same:li $t2,89
0xbfc000240xaf2a0000sw $t2,0($t9)
0xbfc000280x24090004li $t1,4
0xbfc0002c0x15090003bne $t0,$t1,0xbfc0003c
0xbfc000300x00000000nop
0xbfc000340x240a003dli $t2,61
0xbfc000380xaf2a0000sw $t2,0($t9)
0xbfc0003c0x240a0021differ:li $t2,33
0xbfc000400xaf2a0000sw $t2,0($t9)
0xbfc000440x11090003beq $t0,$t1,0xbfc00054
0xbfc000480x00000000nop
0xbfc0004c0x240a002eli $t2,46
0xbfc000500xaf2a0000sw $t2,0($t9)
0xbfc000540xaf200008never:sw $zero,8($t9)

registers — read · written this tick

HI 0x00000000LO 0x00000000PC 0xbfc00000

console (what the doors printed)

▮
cycle 0/181 tick = 1 instruction (not a cycle)

tick 0 of 18

Nothing has run yet. The PC (program counter) points at the first instruction.

A taken branch (green chip) moves the PC arrow to the target after the orange delay slot has run, and the instructions in between are never executed. A not-taken branch (grey chip) runs its slot and then simply continues. The console shows which paths ran.

What to notice

At each branch a chip appears on the target row: green for taken, grey for not taken. After a taken branch the orange slot runs, then the PC arrow jumps and the skipped instructions never turn blue or green. After the not-taken beq, the arrow just moves down.

Where the target comes from

How does a 32-bit word name a 32-bit target? It does not. The 16-bit field is a : a distance in instructions. Here is the bnez that closes hello.c’s loop, from the dump:

bnez v0,bfc001b8 from hello.c at -O0 (0x1440FFF2 at 0xBFC001EC)

Where is the target address in this word?

3126
opcode6b
000101
5
2521
rs5b
00010
$v0 ($2)
2016
rt5b
00000
$zero ($0)
150
imm16b
1111111111110010
-14 (× 4 = -56 bytes)
binary00010100010000001111111111110010
hex0x1440fff2one hex digit = four bits; a digit that spans two fields has two colors
asmbne $v0,$zero,0xbfc001b8(objdump prints bnez $v0,0xbfc001b8)I-format
Hover or focus a field to see its bits, value and role.

What to notice

Nowhere. The field holds 0xFFF2, which is −14. rs is v0, rt is $zero (that is what makes bne print as bnez). The target 0xBFC001B8 that objdump printed was computed, from −14 and the branch’s own address.

The is (branch address + 4) + (offset × 4). Watch the arithmetic one step at a time:

From the field 0xFFF2 to the target 0xBFC001B8

Why +4, and why ×4?

0x1440fff2 at 0xbfc001ec: bne $v0,$zero,0xbfc001b8

  1. 1. the 16-bit offset field, bits 15–0 of the word0x0000fff2 = 65522
    00000000000000001111111111110010

    bne $v0,$zero,0xbfc001b8: the field holds 0xfff2. It is NOT an address; it counts instructions.

  2. 2. sign-extend it to 32 bits0xfffffff2 = -14
  3. 3. multiply by 4 (shift left by 2)0xffffffc8 = -56
  4. 4. PC + 4: the address of the delay slot0xbfc001f0
  5. 5. target = (PC + 4) + byte offset0xbfc001b8
cycle 0/41 tick = 1 arithmetic step
  • the field, and bits an extension or shift filled
  • the branch/jump target

What to notice

Sign extension makes 0xFFF2 into −14: fourteen instructions backward. Times 4 gives −56 bytes, because instructions are 4 bytes and the two low bits are never stored. The distance is measured from the delay slot at 0xBFC001F0, not from the branch. Result: 0xBFC001B8.

Before reading on, decide:

`b bfc001e4` at 0xBFC001B0 has the field 0x000C. Check the target with the formula.

Relative, not absolute

Because the field is a distance, the same word means a different target at a different address. Copy 0x1440FFF2 to another place and it still says “14 instructions back from my slot”. That is what lets the linker place a function anywhere without rewriting its branches. It is also why a branch reaches only ±128 KB: 32768 instructions each way, times 4 bytes. Anything further needs a jump (§5.3).

Go deeper: why the target is relative to PC + 4, and how your core computes it

In the 1985 pipeline, by the time a branch is decoded the fetch unit has already added 4 to the PC to fetch the next word. That sum, PC + 4, is sitting on a wire for free. Adding the shifted offset to it rather than to the PC saved nothing in logic but matched what the hardware already had, and the delay slot made it the natural reference point anyway: the slot’s address is where the branch “is” when it takes effect.

Carolyne’s branch unit does the same sum. Its station entry carries pc and npc, the next-PC the front end computed, and the body adds the sign-extended offset times 4 to npc. The immediate rule imm_br places the 16 field bits at bit 2 and sign-extends from bit 17, so the ×4 is a wiring choice, not an adder. A taken branch then redirects fetch to the sum; a not-taken one lets the front end’s own npc stand.

Can you now explain it?

A branch tests two registers and, if the test holds, moves the PC to a target. The word holds a signed distance in instructions from the delay slot; the target is branch + 4 + offset × 4. The next instruction runs either way.

If you remember only three things

  1. beq and bne: equal, or different. b is beq $zero,$zero. All of them run the slot first, then move the PC or not.
  2. The field is a distance, not an address. Sign-extend, times 4, add to branch + 4. The same word means a different target elsewhere, and that is by design.
  3. ±128 KB, then jump. 32768 instructions each way. Function calls and far targets use j and jal (§5.3).

Now try to explain this one yourself: suppose GCC had put one more instruction into hello.c’s loop body. What changes in the word 0x1440FFF2 at the loop’s end? Why does the branch still find the top?

Go deeper: a model answer, once you have tried

The bnez moves four bytes down, and the top of the loop stays where it was relative to the extra instruction, so the distance grows by one: the field becomes −15, 0xFFF1, and the word 0x1440FFF1. The target is recomputed from the new branch address and the new offset and lands on the same top. The assembler does this arithmetic for every branch when it turns a label into a field, which is why you never write offsets by hand.

Check your understanding

0/5 answered
  1. 1.A `beq` at 0xBFC00040 is NOT taken. After the branch and its delay slot, where does the program continue?

  2. 2.How far can a conditional branch reach?

  3. 3.What does `beq $zero,$zero,label` do?

  4. 4.Why is the branch offset multiplied by 4 instead of being stored in bytes?

  5. 5.The same branch word 0x1440FFF2 is copied to a different address. What happens to its target?


Next: §5.2 — comparing with zero, and how a compiler writes if and while.

Where this comes back

4 later sections build on this one.