Decisions & loops
By default the CPU runs your instructions in order, one after another, from top to bottom. On its own that can never make a choice or repeat work. A branch is how a program escapes that straight line: it checks a condition and, if the condition is true, tells the CPU to continue somewhere else instead of at the next line. Every decision and every loop is built from this one idea.
There is no if or for. You build them from labels (named positions) and branches (jump there when a condition holds). A label is just a name followed by :.
To understand how this actually executes, you must understand the Program Counter (PC). The PC is a special, hidden register that holds the memory address of the current instruction. A branch instruction actively rewrites the value of the PC, forcing the CPU to fetch its next instruction from your target label instead of simply moving to the next line of code.
The branch family
Every branch compares two registers and jumps to the label only if the test holds; otherwise it falls through to the next line. There are six core tests:
beq a, b, label # branch if a == b bne a, b, label # branch if a != b blt a, b, label # branch if a < b (signed) bge a, b, label # branch if a >= b (signed) bltu a, b, label # branch if a < b (unsigned) bgeu a, b, label # branch if a >= b (unsigned)
beq and bne test equality and ignore sign. blt and bge are the ordering pair, less-than and greater-or-equal, comparing the registers as signed numbers. bltu and bgeu make the same comparison but treat the values as unsigned, which is what you want for addresses or counts that are never negative.
What do signed and unsigned actually mean? A register is just 32 bits, and the same bits can be read two ways. Read as unsigned, they are a plain whole number from 0 up to about 4 billion. Read as signed, the topmost bit is treated as a minus sign, so half the patterns count as negative numbers. The bits never change; only the interpretation does. That's why there are two sets of comparisons: use the signed ones (blt, bge) for ordinary numbers that might be negative, and the unsigned ones (bltu, bgeu) for values that are never negative, like memory addresses.
Notice there's no direct "branch if greater" or "branch if less-or-equal". You get those by swapping the two registers, and the assembler offers bgt and ble as pseudo-branches that do the swap for you, so bgt a, b becomes blt b, a. Two more shortcuts: beqz and bnez test a single register against zero, and j label jumps with no condition at all.
li t0, 0 # sum = 0 li t1, 1 # i = 1 li t2, 6 # limit = 6 (loop while i < 6) loop: bge t1, t2, done # if i >= 6, leave the loop add t0, t0, t1 # sum += i addi t1, t1, 1 # i++ j loop # go check again done: mv a0, t0 # print the sum (should be 15) li a7, 1 ecall li a7, 10 ecall
Trace it. t0 is the running sum (starts 0), t1 is the counter i (starts 1), t2 is the limit 6. Each pass through loop: bge t1, t2, done leaves once i reaches 6; otherwise add t0, t0, t1 adds i to the sum, addi t1, t1, 1 bumps i by one, and j loop jumps back to test again. So it adds 1, 2, 3, 4, 5 and done prints the total, 15.
t1 climb and the program counter (pc) jump back to loop. Each time pc resets to loop, the branch condition evaluated to false and the loop continues.