Skip to content

Jumps, the stack and interrupts

An instruction set ended with jumps and branches. This page adds the two kinds of jump that come back: the subroutine, which the program calls on purpose, and the interrupt, which something outside the program causes. Both depend on the stack. And the little computer gets some devices, so programs have something to do.

Hex numbers are written with a $, as in 6502 assembly.

A subroutine is a piece of program that other parts can use: multiply two numbers, show a number on a display. JSR (jump to subroutine) jumps to it, and RTS (return from subroutine) comes back to just after the JSR. For that, JSR has to put the return address somewhere, and if a subroutine can call another subroutine, there have to be as many places as there are calls in progress. The last call made is the first to return, so the right structure is a stack, like a stack of plates: put things on top (push), take them off the top (pull).

The 6502’s stack is page 1 of memory, $0100 to $01FF, and the stack pointer, S, holds where the next byte goes. It starts at the top and grows down: a push writes at $0100 + S and takes 1 from S; a pull adds 1 and reads. JSR pushes the return address (two bytes), RTS pulls it. Programs can push and pull A too, with PHA (push A) and PLA (pull A), and the flags, with PHP (push processor status, the flags) and PLP (pull them), to save them for later. TXS (transfer X to the stack pointer) sets S, so a program starts with LDX #$FF and TXS to put the stack at the top of page 1.

A subroutine that calls another: one doubles A with ASL (arithmetic shift left), and the other calls it twice. Step through and watch the stack on the right: each JSR pushes two bytes (the address of its own last byte, which RTS adds 1 to) and S moves down; each RTS pulls them off again. When the program stops, A × 4 = 12 is at $0000, and S is back at $FF.

The little computer has a few devices in its I/O space, which the program reads and writes like memory (as on Memory and buses):

AddressNameWhat it does
$8000LEDSeight LEDs, one per bit
$8001BUTTONbit 0: the button is held now; bit 7: it’s been pressed (write to clear)
$8002BTNCTLbit 0: interrupt when it’s pressed
$8004–$8007DIGITSfour 7-segment digits, one bit per segment (a is bit 0)
$8008, $8009T1LO, T1HItimer 1’s period, in clock cycles
$800AT1CTLbit 0: run; bit 1: interrupt each time it runs out
$800BT1STATbit 7: it’s run out (write to clear)
$800C, $800DTONELO, TONEHIthe tone’s period, in clock cycles: 1,000,000 ÷ period Hz
$800ETONECTLbit 0: sound on
$800FDUTYhow much of each period the output is high, out of 256

The simplest way to use a device is to keep asking it: read the button, see if it’s held, do something, read it again. That’s polling.

A light runs along the LEDs. Click Run, then hold the button (press and hold on it with the mouse or a finger): the light goes the other way. The program checks the button once each time round, between waits.

Polling works, but the program has to stop and look, again and again. A program busy with something else can miss a press, and one that’s waiting wastes all its time asking.

An interrupt lets a device break in. The device pulls the 6502’s IRQ line (interrupt request); at the end of the instruction it’s on, the 6502 does what a JSR would: it pushes the PC, and the flags too, then jumps to the address stored at $FFFE–$FFFF, the IRQ vector, where the interrupt routine is. That routine deals with the device, tells it it’s been seen (or it would interrupt again straight away), and ends with RTI (return from interrupt), which pulls the flags and the PC, and the interrupted program carries on as if nothing had happened.

Two details make that safe. The routine must leave the registers as it found them, since the program was in the middle of using them. The flags are taken care of: the 6502 pushed them on the way in and RTI pulls them, so the routine can change them freely. A, X and Y aren’t saved, so the routine pushes any it uses, and pulls them back before RTI. And the I flag can hold interrupts off: SEI (set interrupt disable) sets it, so no interrupts, and CLI (clear interrupt disable) clears it. The 6502 starts with I set, so a program clears it once it’s ready.

The main program does nothing at all: a JMP to itself. Click Run, then press the button: each press interrupts it, and the interrupt routine adds 1 to the LEDs, with INC (increment), which adds 1 to a byte in memory. Stop and Step after a press to watch the 6502 push the PC and flags (on the stack, right), run the routine, and RTI back.

The 6502 has a second line, NMI (non-maskable interrupt), with its own vector at $FFFA, which the I flag can’t hold off, for things that mustn’t wait, such as a power failure. And the BRK (break) instruction causes an interrupt from software, through the same vector as IRQ; a bit in the pushed flags (B) tells the routine which it was.

A timer counts clock cycles, and interrupts every so many. It gives a program a regular tick to keep time by, without the program having to count anything itself.

A stopwatch. Timer 1 interrupts every 10,000 cycles, a hundredth of a second; the interrupt routine counts the hundredths and seconds and shows them on the digits. Meanwhile the main program runs a light chaser, with no idea it’s being interrupted 100 times a second. The routine counts in decimal (the 6502’s decimal mode, switched on with SED, set decimal, and off with CLD, clear decimal), so turning each count into digits is easy.

The second timer, the tone generator, doesn’t interrupt; it drives a speaker. Its output switches high and low once per period, a square wave, and the period sets the pitch: 1,000,000 ÷ period Hz, so a period of 2,273 cycles is about 440 Hz, the A above middle C.

This next figure makes sound once you run it. It’s quiet, but the switch under the devices turns it off (the same switch as the relay clicks on the first page).

A scale: the program writes each note’s period to the tone generator, turns it on, and waits a quarter of a second on timer 1, polling it this time. The DUTY register sets how much of each period the output is high: 128 gives a square wave, other values a thinner pulse with a different sound. Try adding LDA #32 and STA $800F at the start.

A pulse whose width the program controls is called pulse-width modulation (PWM). Run fast enough and filtered, it’s a way to make in-between voltages from an output that can only be high or low, which is how plenty of chips dim LEDs and drive motors.

  • JSR pushes the return address on the stack and jumps; RTS pulls it and returns. The 6502’s stack is page 1, growing down from $01FF, with S pointing at the next free byte.
  • Devices are addresses in the I/O space. Polling keeps asking them.
  • An interrupt lets a device break in: the 6502 pushes the PC and flags, jumps through the IRQ vector at $FFFE, and RTI puts everything back. The I flag holds interrupts off; NMI can’t be held off.
  • A timer interrupts regularly, for keeping time; a tone generator makes a square wave (or, with its duty, a pulse) for sound.
  • Rodnay Zaks, Programming the 6502, 1978: the chapters on subroutines, the stack and input/output.
  • Garth Wilson’s “6502 interrupts” primer, at 6502.org: everything about IRQ, NMI and BRK, in detail.