Seyed Masoud Hosseini · Overview · Study log · Ideas · Transcript · RSS feed
Embedded Systems, 6502 breadboard computer · Lecture 5 of 29 · 24:24
Part 5: What is a stack and how does it work?
Study guide
What this lecture covers
The previous video's "Hello world" program worked but was long and repetitive. This lecture first cleans it up by moving the repeated LCD-instruction and print-character code into subroutines called with JSR and exited with RTS, dramatically shrinking the assembled machine code. It's a practical lesson in how assembly code size directly maps to the compiled program.
After reprogramming the EEPROM, the refactored program fails to print anything. The bulk of the lecture is a single-step debugging session with the Arduino bus monitor that traces exactly what JSR and RTS do internally, leading to the discovery that the 6502's call stack lives at a fixed memory range the breadboard computer doesn't actually have any RAM for yet.
Key ideas
- Subroutines (
JSR/RTS):JSRjumps to a subroutine and saves a return address;RTSreturns to the instruction after the original call, letting repeated code be written once and reused. - The 6502 stack: a fixed 256-byte region from
$0100to$01FFused to store return addresses (and optionally other temporary data) via push and pull operations. - Stack pointer: an 8-bit CPU register that always points somewhere within page
$01xx; it decrements when data is pushed and increments when data is pulled. - Stack pointer initialization: the stack pointer starts at an arbitrary value on power-up, so programs should explicitly set it (commonly to
$FF) usingLDX #$FFfollowed byTXS, since there is no direct "load stack pointer" instruction. - Push/pull for register preservation: values can be temporarily saved on the stack (for example the accumulator) with a push, then restored with a pull, as long as pushes and pulls are correctly paired.
- Stack overflow: pushing more data than the 256-byte stack can hold causes the stack pointer to wrap around and overwrite earlier data, corrupting saved return addresses.
- Missing RAM as root cause: the program fails because nothing in the circuit responds to addresses in the
$0100-$01FFrange, so writes there go nowhere and later reads return meaningless data.
Walkthrough
Factoring repeated code into subroutines (0:00)
The lecture moves the block of code that sends an instruction to the LCD into a subroutine labeled lcd_instruction, replacing each repeated copy in the main program with a single JSR lcd_instruction line ending in RTS. The same refactor is applied to the code that prints a character, replacing dozens of duplicated lines with JSR print_character calls.
Comparing assembled code size (3:01)
Reassembling the refactored program produces a much smaller binary than the original "Hello world" version, illustrating that assembly source maps directly to machine code size, at the minor cost of extra clock cycles spent jumping to and returning from subroutines.
The program stops working after reprogramming (4:02)
After writing the smaller program to the EEPROM and resetting the computer, the display fails to print anything, prompting the lecture to reconnect the Arduino bus monitor from earlier videos to trace address, data, read/write, and clock signals cycle by cycle.
Tracing a JSR call and its hidden writes (7:05)
Single-stepping through a JSR to the LCD-instruction subroutine reveals two unexpected write cycles to addresses $0124 and $0123 before the jump completes. These turn out to be the processor saving the return address ($800E) so it can find its way back after the subroutine finishes.
Discovering the broken RTS (9:07)
When the subroutine reaches RTS, the processor tries to read back the saved return address from $0123/$0124 but gets the wrong bytes, then jumps to a nonsensical address instead of returning correctly. The lecture identifies that nothing in the circuit is actually connected to those addresses, so both the earlier write and this read are effectively going nowhere.
Explaining the 6502's fixed stack region (15:16)
Referencing the datasheet, the lecture explains that addresses $0100 through $01FF are reserved as the stack, tracked by an 8-bit stack pointer that decrements on each push. It walks through how nested subroutine calls use the stack, how pushing and pulling the accumulator can preserve its value across a call, and why unbalanced pushes and pulls, or too many nested calls, can cause a stack overflow that corrupts saved return addresses.
Initializing the stack pointer (21:19)
Because the stack pointer starts at an arbitrary value on power-up, the lecture shows the standard fix: load the X register with $FF and transfer it to the stack pointer with TXS, ensuring the stack starts at the top of its 256-byte range.
Identifying the real fix needed (23:21)
The lecture concludes that subroutine calls cannot work correctly until the computer has actual RAM covering the $0100-$01FF stack region, and previews adding a RAM chip in the next video.
Before you watch
- Watch parts 1 through 4 first; this lecture assumes the VIA wiring, LCD instruction set, and Arduino bus-monitoring setup from those videos.
- Comfort reading hexadecimal addresses and following single-stepped bus traces is important for the debugging section.
Check your understanding
- What does
JSRdo internally on the 6502, and why does it need somewhere to store a return address? - Why did the refactored program stop working even though the subroutine logic itself was correct?
- What is a stack overflow on the 6502, and how could unbalanced pushes and pulls cause one?
- Why does the lecture initialize the stack pointer to
$FFusingLDX/TXSinstead of a direct load instruction? - What hardware change does the lecture say is needed before subroutine calls can work reliably?
From the YouTube description
Schematics, datasheets, kits, and more at https://eater.net/6502
Part 1: https://www.youtube.com/watch?v=LnzuMJLZRdU
Part 2: https://www.youtube.com/watch?v=yl8vPW5hydQ
Part 3: https://www.youtube.com/watch?v=oO8_2JJV0B4
Part 4: https://www.youtube.com/watch?v=FY3zTUaykVo
Part 5: This video!
Part 6: https://www.youtube.com/watch?v=i_wrxBdXTgM
Part 7: https://www.youtube.com/watch?v=omI0MrTWiMU
Support these videos on Patreon: https://www.patreon.com/beneater or https://eater.net/support for other ways to support.
------------------
Social media:
Website: https://www.eater.net
Twitter: https://x.com/beneater
Patreon: https://patreon.com/beneater
Reddit: https://www.reddit.com/r/beneater
Special thanks to these supporters for making this video possible:
Adam Lininger, Adrien Friggeri, Alex Catchpole, Andrew R. Whalley, Anthony Cuccia, Armin Brauns, Austin Grant, BakerStaunch, Beau-James Erion, Ben Dyson, Ben Kamen, Ben Williams, Bradley Pirtle, Bryan Brickman, Carlos Ambrozak, Christopher Blackmon, Clayton Parker Coleman, Daniel Tang, Dave Walter, David Boardman, David H. Friedman, David House, David Turner, Dean Winger, Debilu Krastas, Dmitry Guyvoronsky, Dušan Dželebdžić, Dzevad Trumic, Eric Brummer, Eric Busalacchi, Eric Dynowski, Eric Twilegar, Erik Broeders, Eugene Bulkin, Foaly, fxshlein, HaykH, Ian Tait, Ingo Eble, Ivan Sorokin, Jason DeStefano, JavaXP, Jay Binks, Jayne Gabriele, Jeremy A., Jeremy Wise, Jimmy Campbell, Joel Jakobsson, Joel Messerli, Joel Miller, Joern Heidenreich, Jon Dugan, Jordan Scales, Joshua King, Justin Duch, Kefen, Kent Collins, Lambda GPU Workstations, Lucas Nestor, Maksym Zavershynskyi, Manne Moquist, Marcus Classon, Mats Fredriksson, Matt Alexander, Matthäus Pawelczyk, Michael, Michael Burke, Michael Garland, Michael Tedder, Miguel Ríos, Nathan Wachholz, Nicholas Counts, Nicholas Moresco, Örn Arnarson, Paul Pluzhnikov, Paul Randal, Philip Hofstetter, Randy True, Ric King, Richard Wells, Rob Bruno, Robert Blackshaw, Robert Butler, Ross, Sachin Chitale, Sam Rose, Samuel E Joseph, Scott, Sergey Ten, Sever Banesiu, SonOfSofaman, Stefan Nesinger, Stefanus Du Toit, Stephen Riley, Stephen Smithstone, Steve Jones, Steve Gorman, Steven Pequeno, Thomas Ballinger, Tom Burns, Vladimir Kanazir, Warren Miller, xisente, Ziggy L
← Part 4: Connecting an LCD to our computer · Part 6: RAM and bus timing →
