Capstone — Building and Comparing Two Small Programs
8-Bit CPUs — 6502/6510 & Z80
Chapter 12 · Capstone — Building and Comparing Two Small Programs
One task, one final time, on both chips: find the largest value in a 5-byte array. Unlike cpu8bit1-8's own summing routine, this one uses a real subroutine call — the first time either chip's real hardware stack (cpu8bit1-3, cpu8bit1-7) actually gets exercised by a working program in this course, rather than just described.
The 6502 Version
LDX #1 ; start comparing from index 1 LDA ARRAY ; A = ARRAY[0] STA MAX ; MAX = ARRAY[0] (the starting candidate) LOOP CPX #5 BEQ DONE ; NEW — branch if equal (complements BNE from cpu8bit1-8) LDA ARRAY,X ; A = the next candidate — zero-page,X (cpu8bit1-4) JSR UPDATE_MAX ; NEW — call the subroutine (real stack use, cpu8bit1-3) INX JMP LOOP DONE JMP DONE ; no HALT on the 6502 (cpu8bit1-8) ; --- Subroutine: UPDATE_MAX --- ; Input: A = candidate. Updates MAX in place if the candidate is bigger. UPDATE_MAX CMP MAX ; NEW — compare A against MAX (like SBC, but nothing stored) BCC SKIP ; NEW — branch if Carry Clear (A < MAX) STA MAX ; A >= MAX — update it SKIP RTS ; NEW — return, pulling the address JSR pushed ARRAY .BYTE 10,50,30,80,20 MAX .BYTE 0
New here: BEQ (branch if equal — the counterpart to cpu8bit1-8's own BNE), CMP (compares A against a value the same way ADC/SBC would, without storing a result), BCC (branch if Carry is clear), and — for the first time in this course — JSR/RTS, a real subroutine call and return, automatically using the page-1 stack cpu8bit1-3 only described until now.
The Z80 Version
LD HL, ARRAY LD A, (HL) ; A = ARRAY[0] LD (MAX), A ; MAX = ARRAY[0] INC HL LD B, 4 ; 4 remaining comparisons LOOP LD A, (HL) ; A = the next candidate — register-indirect (cpu8bit1-7) CALL UPDATE_MAX ; NEW — call the subroutine (real stack use, cpu8bit1-7) INC HL DJNZ LOOP ; cpu8bit1-8 DONE JR DONE ; --- Subroutine: UPDATE_MAX --- ; Input: A = candidate. Updates MAX in place if the candidate is bigger. UPDATE_MAX LD C, A ; save the candidate in C — NOT B, which DJNZ's own loop still needs LD A, (MAX) CP C ; NEW — compare A (MAX) against C (candidate) JR NC, SKIP ; NEW — jump if No Carry (MAX >= candidate) LD A, C LD (MAX), A SKIP RET ; NEW — return, pulling the address CALL pushed ARRAY DEFB 10,50,30,80,20 MAX DEFB 0
New here: CP (compares A against a value, like CMP on the 6502), JR NC (jump if no carry), and — for the first time on this side too — CALL/RET, the Z80's own subroutine call, using its fully flexible 16-bit stack from cpu8bit1-7.
DJNZ counter, and overwriting it inside the subroutine would silently corrupt the loop the moment the subroutine returned. This is exactly the register-preservation discipline assembly1-7 first introduced for R7, showing up here as an equally real concern for an entirely different register on an entirely different chip.
The Real Payoff: Two Opposite Flag Conventions
Both subroutines do the same comparison — yet the 6502's CMP/BCC and the Z80's CP/JR NC read the Carry flag in opposite directions:
| Chip | After CMP/CP | Carry means | Instruction used to detect "smaller" |
|---|---|---|---|
| 6502 | CMP MAX (A − MAX) | SET if A ≥ MAX (no borrow needed) | BCC — Carry Clear means A < MAX |
| Z80 | CP C (A − C) | SET if A < C (a borrow was needed) | JR NC — No Carry means A ≥ C |
On the 6502, Carry set means "no borrow was needed" — the first value was large enough. On the Z80, Carry set means the exact opposite — "a borrow was needed," meaning the first value was too small. Anyone porting comparison logic between the two chips has to consciously flip this reading, or the ported code will silently do the wrong thing while looking completely correct on the page. It's a small, precise, entirely real example of exactly the kind of "same-looking operation, genuinely different behavior" this whole course has been surfacing since cpu8bit1-1.
Chapter Attribution
| Capstone piece | Concept | From |
|---|---|---|
| Both chips' overall approach | Cost-driven minimalism vs. compatibility-driven richness | cpu8bit1-2, cpu8bit1-5 |
| ARRAY,X / (HL) with INC | Zero-page,X and register-indirect addressing | cpu8bit1-4, cpu8bit1-7 |
| JSR/RTS and CALL/RET | Real subroutine calls, finally exercising the hardware stack | cpu8bit1-3, cpu8bit1-7 |
| CPX/BEQ, DJNZ | Loop management — three 6502 instructions vs. one Z80 instruction | cpu8bit1-8 |
| CMP/BCC vs. CP/JR NC | Genuinely opposite Carry-flag conventions between the two chips | cpu8bit1-3, cpu8bit1-6 (the status/flag registers each chip introduced) |
| The deliberate C-not-B register choice | Register preservation across a subroutine call | assembly1-7, echoed for the Z80's own registers here |
cpu8bit1-11 already named the still-unwritten x86-64 course as the place this same tension continues, at a scale forty more years of accretion actually produces.
Hands-On Exercises
Trace the 6502's UPDATE_MAX subroutine for a call where A (the candidate) = 30 and MAX currently holds 50. Using this chapter's own CMP/BCC semantics, determine whether MAX gets updated, and state the value of MAX afterward.
📄 View solutionTrace the Z80's UPDATE_MAX subroutine for a call where the candidate is 80 and MAX currently holds 50. Using this chapter's own CP/JR NC semantics (note: the OPPOSITE Carry convention from the 6502), determine whether MAX gets updated, and state the value of MAX afterward.
📄 View solutionPick three rows from this chapter's own chapter-attribution table and explain, in one or two sentences each, exactly which piece of this capstone's code draws on that chapter's material and why it was needed here.
📄 View solutionChapter 12 Quick Reference — Course Recap
- cpu8bit1-1 — the 1975–76 boom, a bridge from assembly1, RISC/CISC previewed
- cpu8bit1-2 to -4 — the 6502: cost-driven minimalism, the full register/stack picture, zero page as "extra registers"
- cpu8bit1-5 to -7 — the Z80: compatibility-driven richness, main/shadow/index registers, addressing and a fully flexible stack
- cpu8bit1-8 — one routine, both chips: DJNZ vs. INX/CPX/BNE, and why cycle counts alone can mislead
- cpu8bit1-9 — interrupts: 3 fixed vectors vs. 3 selectable modes, EXX's real payoff
- cpu8bit1-10 — real machines, real markets, both chips still active today
- cpu8bit1-11 — RISC vs. CISC formally defined, the real 6502→ARM lineage, an honest divergence named
- cpu8bit1-12 — a real subroutine call on both chips, surfacing a genuinely opposite flag convention
- Next stop: the still-unwritten x86-64 course, picking up the Z80's own richness story after four more decades