Control Flow — Branching & Conditionals
Assembly Fundamentals
Chapter 6 · Control Flow — Branching & Conditionals
assembly1-4 introduced the N/Z/P condition codes with a promise: "a later instruction can make a decision based on the result of an earlier one." This chapter delivers on that promise. The BR instruction family reads N/Z/P directly, and it's the only tool LC-3 gives you for everything a high-level language expresses with if, else, and loops.
The BR Instruction — Reading Condition Codes
BR branches — changes the PC instead of just letting it advance normally — but only if the current condition codes match a set of bits baked into the instruction itself. Its encoding reuses the exact PC-relative shape assembly1-5 already showed for LD:
The three bits right after the opcode aren't a destination register the way LD's were — they're three independent flags. At runtime, the CPU compares those three bits against the current N/Z/P condition codes; if any of the bits set to 1 in the instruction match a condition code that's currently set to 1, the branch is taken and the PC jumps to PC + SEXT(offset9), exactly like LD's own address computation. If none match, execution just falls through to the next instruction as normal.
| Mnemonic | n / z / p bits | Branches when... |
|---|---|---|
| BRn | 1 / 0 / 0 | the last result was negative |
| BRz | 0 / 1 / 0 | the last result was exactly zero |
| BRp | 0 / 0 / 1 | the last result was positive |
| BRnz | 1 / 1 / 0 | the last result was ≤ 0 |
| BRzp | 0 / 1 / 1 | the last result was ≥ 0 |
| BRnp | 1 / 0 / 1 | the last result was nonzero |
| BR / BRnzp | 1 / 1 / 1 | always — since exactly one of N/Z/P is always set (assembly1-4) |
| (no mnemonic) | 0 / 0 / 0 | never — a technically valid but useless instruction, effectively a no-op |
assembly1-4), a BR with all three bits set is guaranteed to branch every single time, regardless of what the condition codes actually are. LC-3 has no separate "unconditional jump to a label" instruction — BRnzp is that instruction, just expressed through the same mechanism as every conditional branch.
Implementing if/else
Translate if (R1 == 0) { R2 = 100; } else { R2 = -100; } into LC-3. Note that 100 and -100 don't fit in assembly1-5's own 5-bit immediate range, so both constants have to live in memory and be loaded, exactly as that chapter's warn-box described:
ADD R1, R1, #0 ; adds zero — doesn't change R1, but resets N/Z/P to reflect it BRz ISZERO LD R2, NEG100 BRnzp DONE ISZERO LD R2, POS100 DONE ; execution continues here either way ... NEG100 .FILL #-100 POS100 .FILL #100
That leading ADD R1, R1, #0 is deliberate, not filler — it's exactly the "re-establish the condition codes right before branching" move assembly1-4's own warn-box called for. If R1's value came from several instructions earlier, and any register-writing instruction ran since, the condition codes might no longer reflect R1 at all without this step.
Implementing Loops
Loops are just conditional branches that point backward instead of forward. Here's a loop that sums the numbers from 1 up to whatever's stored at N:
AND R2, R2, #0 ; R2 = sum = 0 LD R1, N ; R1 = counter = N LOOP ADD R2, R2, R1 ; sum += counter ADD R1, R1, #-1 ; counter-- — this ALSO sets condition codes for the new R1 BRp LOOP ; loop while counter is still > 0 ST R2, SUM ... N .FILL #5 SUM .FILL #0
Notice there's no separate "test the counter" instruction here at all, unlike the if/else example above — the decrement ADD R1, R1, #-1 already writes R1 and sets condition codes as a side effect, and BRp immediately follows it with nothing in between. That's the condition-code discipline from assembly1-4 applied correctly: reuse a result's condition codes immediately, or re-establish them deliberately if anything comes between the computation and the branch.
assembly1-3 already flagged that a 9-bit PC-relative offset can only reach roughly ±256 words. That limit applies to BR exactly as much as it applies to LD — a branch target more than ~256 words away from the BR instruction itself simply cannot be expressed. LC-3 programs that need to jump further rely on a different instruction entirely, JMP, which jumps to whatever address is sitting in a register rather than computing one from an offset — the same mechanism assembly1-7 uses to implement RET.
Hands-On Exercises
Using this chapter's own BR bitfield diagram and mnemonic table, explain why a BR instruction with all three n/z/p bits cleared to 0 is technically valid but never actually useful in a real program.
📄 View solutionIn this chapter's if/else example, explain specifically what could go wrong if the leading ADD R1, R1, #0 were removed, tying your answer back to assembly1-4's own warn-box about condition codes reflecting only the last register write.
Modify this chapter's own summing loop so it instead counts how many iterations run before the counter reaches zero (i.e. computes the original value of N, but by counting rather than reading it back). Write the full modified loop.
📄 View solutionChapter 6 Quick Reference
- BR — opcode 0000, three condition bits (n/z/p) + a 9-bit PC-relative offset, reusing LD's own PC-relative encoding shape
- Branches if ANY bit set to 1 in the instruction matches a currently-set condition code
- BRnzp — always branches, since exactly one of N/Z/P is always set; LC-3's stand-in for an unconditional jump
- if/else — set/re-establish condition codes, then branch around the block you want to skip
- Loops — a backward-pointing conditional branch; reuse condition codes set by the loop's own decrement when possible
- Always branch immediately after the instruction whose result you're checking, or deliberately re-set flags first (assembly1-4)
- BR shares LD's ±256-word PC-relative reach limit; longer jumps need JMP (previewed for assembly1-7)