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.

A deliberate register choice, not an accident
The subroutine saves the candidate in C, not B — because B is the outer loop's own 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:

ChipAfter CMP/CPCarry meansInstruction used to detect "smaller"
6502CMP MAX (A − MAX)SET if A ≥ MAX (no borrow needed)BCC — Carry Clear means A < MAX
Z80CP 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 pieceConceptFrom
Both chips' overall approachCost-driven minimalism vs. compatibility-driven richnesscpu8bit1-2, cpu8bit1-5
ARRAY,X / (HL) with INCZero-page,X and register-indirect addressingcpu8bit1-4, cpu8bit1-7
JSR/RTS and CALL/RETReal subroutine calls, finally exercising the hardware stackcpu8bit1-3, cpu8bit1-7
CPX/BEQ, DJNZLoop management — three 6502 instructions vs. one Z80 instructioncpu8bit1-8
CMP/BCC vs. CP/JR NCGenuinely opposite Carry-flag conventions between the two chipscpu8bit1-3, cpu8bit1-6 (the status/flag registers each chip introduced)
The deliberate C-not-B register choiceRegister preservation across a subroutine callassembly1-7, echoed for the Z80's own registers here
Honest scope note
This capstone deliberately stays within what this course actually taught. Left out, on purpose: any real hardware or emulator setup walkthrough (this course has been entirely about reading and reasoning through code on the page); a full instruction-set reference for either chip (only the instructions actually needed for these examples were introduced); interrupt-driven demonstrations (cpu8bit1-9 covered the mechanism, but no worked interrupt-driven program was built); and sound or graphics chip programming, which would require covering hardware entirely specific to individual machines rather than the CPUs themselves. None of these are gaps in what was taught — they're deliberate boundaries of a course about the two chips themselves, not the machines built around them.
The course this closes, and the one it opens
This closes the 8-Bit CPUs course — twelve chapters tracing two real, historically significant chips from their founding constraints (cost vs. compatibility) through registers, addressing, interrupts, real markets, and finally a formal RISC-vs-CISC framing with a real, documented lineage into ARM. It also stays open: 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

Exercise 1

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 solution
Exercise 2

Trace 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 solution
Exercise 3

Pick 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 solution

Chapter 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