Decisions & loops
By default the CPU executes your instructions in the order they appear, from top to bottom. On its own, that order can never make a choice or repeat work. A branch is how a program leaves that straight path: it tests a condition and, when the condition holds, directs the CPU to continue at a different place instead of the next line. Every decision and every loop comes from this one mechanism.
There is no if or for. You construct them from labels (named positions) and branches (jump to a position when a condition holds). A label is a name followed by :.
To understand how a branch executes, you need to know about the Program Counter (PC). The PC is a special register, hidden from your code, that holds the memory address of the instruction being executed. A branch instruction writes a new value into the PC, so the CPU fetches its next instruction from your target label rather than from the following line of code.
The branch family
Every branch compares two registers and jumps to the label only when the test holds. When the test fails, execution continues on the next line. Six tests form the core:
beq a, b, label # branch if a == bbne a, b, label # branch if a != bblt 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)
and test equality and ignore sign. and form the ordering pair, less-than and greater-or-equal, and compare the registers as signed numbers. and perform the same comparisons but treat the values as unsigned, which suits addresses or counts that are never negative.
What do signed and unsigned mean? A register holds 32 bits, and the same bits can be read in two ways. Read as unsigned, they form a plain whole number from 0 up to about 4 billion. Read as signed, the topmost bit counts as a minus sign, so half the bit patterns represent negative numbers. The bits themselves do not change. Only how you read them changes. That is why there are two sets of comparisons: use the signed ones (, ) for ordinary numbers that may be negative, and the unsigned ones (, ) for values that are never negative, such as memory addresses.
There is no direct "branch if greater" or "branch if less-or-equal". You obtain those by swapping the two registers, and the assembler provides and as pseudo-branches that perform the swap for you, so becomes . Two more shortcuts: and test one register against zero, and jumps with no condition at all.
li t0, 0 # sum = 0li t1, 1 # i = 1li t2, 6 # limit = 6 (loop while i < 6)loop:bge t1, t2, done # if i >= 6, leave the loopadd t0, t0, t1 # sum += iaddi t1, t1, 1 # i++j loop # go check againdone:mv a0, t0 # print the sum (should be 15)li a7, 1ecallli a7, 10ecall
Trace it. t0 holds the running sum (starts at 0), t1 holds the counter i (starts at 1), and t2 holds the limit 6. Each pass through loop: leaves the loop once i reaches 6. Otherwise adds i to the sum, increases i by one, and jumps back to test again. The loop adds 1, 2, 3, 4, and 5, and done prints the total, 15.
bge compares t1 with t2 to decide if the loop continues.
t1 becomes 6, transfers control to done rather than letting the body run again. That test is what separates a pass that repeats from the pass that stops.t1 increases and the program counter (pc) jumps back to loop. Every time pc returns to loop, the branch condition was false, so the loop runs again.