Master the fundamental concepts of cpu design through this focused micro-challenge.
You have read the whole brief, and the concepts above stay free on every task. Writing and running the code needs a plan.
Three hints are available for this task, revealed one at a time inside the code workspace so you can struggle productively before seeing them.
Every task includes starter code, theory, and hidden tests so you can implement and verify locally in the browser.
How it worksCALL saves the return address (current PC) on the stack and jumps to a subroutine. RET pops that address back into PC, resuming the caller. This is how main invokes printf without losing its place.
cLoading…
On entry, callees often save frame pointer and allocate locals:
BP, adjusts SP for localsFor example, if PC=0x100 when CALL executes at 0x50, the stack holds 0x51 (address of the next instruction) and PC jumps to the function entry.
For this exercise, you will extend your stack with CALL and RET opcodes. You will need nested calls working before the pipeline tasks, because mispredicted returns are a classic branch-predictor failure mode in real silicon.
Keep the relevant datasheet, ISA manual, or architecture textbook chapter open while you implement. When your output disagrees with the reference trace on the same program, the bug is usually a mis-decoded opcode, a stale register read, or a flag bit left unchanged after arithmetic.
For this exercise, you will use those habits while implementing the requirement in the starter code. Microarchitectural product names change across CPU generations, but the control ideas (fetch, bypass, cache lines, vector lanes) stay stable enough to debug from first principles.
Add subroutines to the toy CPU. CALL label pushes the return address (the index of the instruction after the CALL) onto the hardware stack and jumps. RET pops that address back into PC. Return addresses share the stack with PUSH/POP data, which is what makes nested and recursive calls work. It is also how a missing POP or HALT ends up corrupting control flow.
Registers R0: R3 (8-bit, start at 0), flags EQ/GT. The stack has 16 bytes at addresses 0-15 and is full-descending, with SP starting at 16.
| Instruction | Effect |
|---|---|
MOV Rd, imm, ADD Rd, Rs, SUB Rd, Rs | as before (mod 256) |
CMP Ra, Rb | EQ = Ra == Rb, GT = Ra > Rb (unsigned) |
JMP / JEQ / JGT label | jump always / if EQ / if GT |
PUSH Rs / POP Rd | mem[--SP] = Rs / Rd = mem[SP++] |
CALL label | mem[--SP] = PC + 1; PC = label |
RET | PC = mem[SP++] |
OUT Rd | print Rd in decimal |
HALT | stop |
A push when SP == 0 is a stack overflow, and a pop when SP == 16 is an underflow. Both stop the machine. Labels are name: at the start of a line. Programs are otherwise well-formed.
Besides the OUT lines, trace every call and return:
cLoading…
pc values are 0-based instruction indices (labels and blank lines don't count). The run ends with one of:
cLoading…
Input:
cLoading…
Output:
cLoading…
CALL and RET use the same stack memory and SP as PUSH/POP.N counts executed instructions, including HALT.Hidden tests cover a recursive subroutine that saves a register with PUSH/POP around its recursive call, and a main program that forgets HALT. There it falls through into a subroutine body and executes RET with an empty stack.