Conditional Jumps
Part of Programming Fundamentals
Conditional jumps let a program choose between paths based on computed results, making it possible to write software that responds to data rather than blindly executing a fixed sequence.
Why This Matters
A program that executes the same sequence of instructions every time, regardless of input, is not a program in any useful sense β it is a fixed procedure. The ability to make decisions based on data is what separates a computer from a calculator. Conditional jumps are the hardware mechanism that implements every IF statement, every loop, every comparison in software.
When rebuilding computation from the hardware up, understanding conditional jumps means understanding how a CPU physically implements logic. The comparison is performed, flags are set, and the jump either changes the program counter or does not. Everything higher-level β IF-THEN-ELSE, WHILE loops, FOR loops, CASE statements β compiles down to this mechanism.
Understanding this layer gives you precise control over program flow and the ability to optimize critical code by thinking directly about what the hardware does.
The Flag Register
Every CPU has a flags register (also called status register or condition codes register) β a collection of bits that record the outcome of the most recent arithmetic or comparison operation.
The essential flags:
Zero flag (Z): Set when the result of the last operation was zero. Cleared otherwise.
Carry flag (C): Set when the last addition produced a carry out of the most significant bit, or when the last subtraction required a borrow. Indicates unsigned overflow.
Sign flag (N, also called S or N): Set when the result was negative (high bit was 1). Useful for signed arithmetic.
Overflow flag (V): Set when the last operation overflowed in the signed sense β positive plus positive produced a negative result, or vice versa.
These flags are set automatically by arithmetic and logical operations. Most CPUs also have a compare instruction (CMP, CP) that performs subtraction and sets the flags based on the result but discards the subtracted value β its only purpose is to set up flags for a subsequent conditional jump.
Conditional Jump Instructions
A conditional jump instruction checks one or more flags and either jumps to the target address (if the condition is true) or falls through to the next instruction (if false).
On the Z80 processor, a typical set of conditional jumps:
JP Z, address ; jump if Zero flag is set (result was zero)
JP NZ, address ; jump if Zero flag is clear (result was non-zero)
JP C, address ; jump if Carry flag is set
JP NC, address ; jump if Carry flag is clear
JP M, address ; jump if Sign flag is set (result was negative)
JP P, address ; jump if Sign flag is clear (result was positive or zero)
JP PE, address ; jump if Overflow/Parity flag is set
JP PO, address ; jump if Overflow/Parity flag is clear
The 6502 processor uses branch instructions with relative addressing, which are shorter than absolute jumps and more efficient for tight loops:
BEQ label ; branch if equal (Zero set)
BNE label ; branch if not equal (Zero clear)
BCS label ; branch if carry set
BCC label ; branch if carry clear
BMI label ; branch if minus (Sign set)
BPL label ; branch if plus (Sign clear)
BVS label ; branch if overflow set
BVC label ; branch if overflow clear
Implementing Comparisons
To compare two values and branch based on the result, use CMP (subtract without storing result) followed by a conditional jump:
; Compare A with value 10, jump to TOOLARGE if A > 10
LD A, (VALUE)
CP 10 ; sets flags based on A - 10
JP P, TOOLARGE ; jump if result positive (A > 10)
JP Z, TOOLARGE ; also jump if equal (A = 10, so > becomes >=)
; ... fall through here if A < 10
For comparing two variables:
; if (A < B) jump to LESS
LD A, (VARA)
LD B, (VARB)
CP B ; A - B
JP M, LESS ; result negative means A < B
Be careful with signed versus unsigned comparisons. Signed overflow (V flag) must be considered when comparing values that might be negative. The condition βsigned A < signed Bβ is: Sign flag differs from Overflow flag. Many CPUs provide combined conditions for this:
JP LT, address ; signed less than (some instruction sets)
JP GE, address ; signed greater than or equal
Where these are not available, you implement them from the primitive flags.
Building IF-THEN-ELSE in Assembly
High-level IF-THEN-ELSE compiles to conditional jumps. The general pattern:
; if (condition) { then_body } else { else_body }
; evaluate condition, set flags
CMP value
JP NOT_CONDITION, else_label ; jump over then body if condition false
then_label:
; then body
JP done_label ; jump over else body
else_label:
; else body
done_label:
; continue
For a specific example, implementing IF X > 5 THEN PRINT "BIG" ELSE PRINT "SMALL":
LD A, (X)
CP 5
JP Z, SMALL ; X = 5, not > 5
JP M, SMALL ; X < 5, not > 5
BIG:
; print "BIG"
JP DONE
SMALL:
; print "SMALL"
DONE:
Building Loops in Assembly
Loops combine conditional jumps with backward jumps (jumps to earlier addresses).
While loop (while (condition) { body }):
LOOP_TOP:
; test condition
LD A, (COUNTER)
CP 0
JP Z, LOOP_EXIT ; exit when counter reaches zero
; loop body
DJNZ LOOP_TOP ; or: JP LOOP_TOP (unconditional back jump)
LOOP_EXIT:
For loop (for i = 0 to N-1): Most 8-bit processors have a special loop instruction. The Z80βs DJNZ (decrement B and jump if not zero) is optimized specifically for this pattern: load the count into B, put the loop body before DJNZ, and the loop runs exactly B times.
LD B, 10 ; loop 10 times
LOOP:
; loop body goes here
DJNZ LOOP ; decrement B; if B != 0, jump to LOOP
The 6502 has no dedicated loop instruction, but the DEX/BNE (decrement X register, branch if not zero) pair achieves the same effect.
Nested Conditions
Complex conditions combine multiple comparisons. AND conditions require both to be true β use two sequential conditional jumps where either one can exit:
; if (A > 0 AND A < 100) then ...
LD A, (VALUE)
CP 0
JP M, FAIL ; fail if negative
JP Z, FAIL ; fail if zero
CP 100
JP P, FAIL ; fail if >= 100
; both conditions met, fall through
; ...
FAIL:
OR conditions require either to be true β use jumps that succeed on either branch:
; if (A = 0 OR A = 255) then ...
LD A, (VALUE)
CP 0
JP Z, SUCCESS
CP 255
JP Z, SUCCESS
; neither condition met
JP CONTINUE
SUCCESS:
; ...
CONTINUE:
Performance Considerations
Conditional jumps have performance implications beyond just correctness. Modern CPUs predict whether branches will be taken, and mispredictions are expensive. For embedded and early microprocessors without branch prediction, each conditional jump costs the same whether taken or not β but the arrangement of the common case matters for cache behavior and code clarity.
Put the most likely branch as the fall-through path (no jump taken), leaving the less likely case as the jump target. This is both a performance and readability convention.
For tight inner loops, minimize the number of conditional jumps in the loop body. Moving invariant comparisons (those whose result does not change across iterations) outside the loop is a fundamental optimization.
Practical Notes for Rebuilders
Study your specific CPUβs flag behavior carefully. Different processors set flags differently for the same operation. Some CPUs do not update flags on load instructions; others do. Some set the carry flag on subtraction in the opposite sense from addition. Read the data sheet.
Test your conditional logic with boundary values: values at, just above, and just below the tested threshold. Off-by-one errors in conditions are extremely common and easy to miss without systematic testing.
Use symbolic labels in assembly β not hardcoded addresses β for all jump targets. As you edit and move code, labels automatically track the correct address.