Subroutines need somewhere to remember where to return to, and that is the call stack. Today you add the stack and stack pointer to the machine with push and pop, before any opcode uses them.
Add a call stack of 16-bit addresses with push and pop.
A subroutine call has to remember where it came from so it can go back, and CHIP-8 keeps those return addresses on a small call stack - classically sixteen entries deep, which caps how deeply subroutines can nest. It is a last-in-first-out structure: the most recent call returns first. A stack pointer (sp) tracks how many addresses are stored; pushing writes at the pointer and bumps it up, popping steps it down and reads the slot.
Building the stack as its own lesson, before any opcode touches it, follows the machine’s grain: 2NNN (call) will push and 00EE (return) will pop, and both are trivial once push and pop already work. Keep the two operations symmetric - a value pushed then popped comes straight back, and two pushes come back in reverse order - and the call and return opcodes in the next two lessons are almost nothing but a push and a pop.
type VM struct {// ... existing fields ...stack [16]uint16sp byte // number of entries in use; also the index of the next slot}func (v *VM) push(addr uint16) { v.stack[v.sp] = addr; v.sp++ }func (v *VM) pop() uint16 { v.sp--; return v.stack[v.sp] }