Level 56: Call and Return
Task
Goal: program a function call: jal (J-format) jumps to a label and writes the next-instruction address (PC + 4) into rd; jalr jumps through a register — the target (rs1 + imm) with bit 0 cleared, the low address bit is always 0. Build sum_to_n: a call with the argument in x5, the function sums 5+4+3+2+1 into x6 and returns via jalr x0, 0(ra). The .end halt sits at the return point — in the middle of the program; the parser assembles the code below it too.
The first program that can call parts of itself: a function with an argument, a body, and a return. The schematic still does not change — execution runs on the reference core, as on levels 54 and 55. The new instructions are jal (the J-format) and jalr:
jal rd, labeljumps to the label and simultaneously writes the next-instruction address (PC + 4) into rd — the return address.jal ra, labelis a call;rais the standard name of x1, the return-address register.jalr rd, imm(rs1)jumps through a register: the target = rs1 + imm, with bit 0 of the sum cleared in hardware, and only then is the multiple-of-4 check applied.jalr x0, 0(ra)is the return from a function.
The canonical sum_to_n program — a function that sums the numbers from 1 to n (the argument in x5, the result in x6):
addi x5, x0, 5 # 0: the argument
jal ra, sum_to_n # 4: the call (ra = 8)
.end # 8: the halt at the return point — the function is below
sum_to_n:
addi x6, x0, 0 # 12
again: beq x5, x0, back # 16
add x6, x6, x5 # 20
addi x5, x5, -1 # 24
beq x0, x0, again # 28
back: jalr x0, 0(ra) # 32
Note the .end: here it sits in the middle of the program. The halt is the place where jalr returns; the function is written below it, and the parser calmly assembles the code after the directive. The assembler emits the jal x0, 0 word at address 8, and the labels and instructions after it get their addresses as usual.
Level tasks:
- Pack the
jalword in "Word from fields" (the J-format appeared on level 56) and take apart the offset split: imm[20]=w31, imm[10:1]=w30:21, imm[11]=w20, imm[19:12]=w19:12 — the offset base, as with beq, is the address of the instruction itself. - Assemble sum_to_n in the Assembler tab. The pseudo-instructions
j label(→jal x0, label) andret(→jalr x0, 0(ra)) are also available from level 56. - Before the first "Check" answer the question about ra: jal sits at address 4 — count PC + 4 yourself.
- Experiment in the Execution block with jalr targets:
jalr x0, 1(x1)with x1 = 0x10 — the jump lands at 0x10 (bit 0 cleared);jalr x0, 2(x1)— the target 0x12 is not a multiple of 4, the errorERR_INSTR_ADDRESS_MISALIGNED.
Solution for self-check
First count ra and the cycles on paper. The solution below is a set of checkpoints.
What you should get: sum_to_n returns correctly: ra = 8 (the .end address), x6 = 15, x5 = 0; the predicted 25 cycles (3 + 6 + 5 × 3 + 1) match the trace, and jalr at PC 32 brings the PC back to address 8.
Step 1 — the return address. jal at address 4 writes 4 + 4 = 8 into ra: after the return, execution continues at address 8, where .end sits. So at the end of the run ra = 8, PC = 8, and the status is halted. The same number is asked by the predict before the first check.
Step 2 — count cycles. The entry is 3 instructions (addi, jal; the .end halt adds no cycle). The function: the beq guard executes 6 times (5 times not taken, the sixth is taken and leads to back), the body is 5 iterations × (add + addi + the jump-back beq), and the final jalr. Total: 3 + 6 + 5 × 3 + 1 = 25 cycles. The result: x6 = 1+2+3+4+5 = 15, x5 = 0.
Step 3 — take jalr apart. The target = (rs1 + imm) & ~1: bit 0 is always cleared, so jalr x0, 1(x1) with x1 = 0x10 jumps to 0x10 (the sum 0x11 became 0x10). But an odd bit 1 is not fixed: jalr x0, 2(x1) gives the target 0x12 — the core stops with ERR_INSTR_ADDRESS_MISALIGNED. A write to x0 does not block the jump: rd = x0 only skips saving the return address.
Step 4 — compare with the trace. Find in the trace: jal at PC 4 writes x1 = 8 and sends the PC to 12; five returns of beq to again; jalr at PC 32 returns the PC to 8; the .end halt record without a cycle count. Later in the course comes the stack: the function will get its own saved registers, and ra will start living in memory.
Check yourself
Why does jalr clear bit 0 of the target?
So that an odd rs1 + imm sum cannot create a misaligned address: bit 0 is cleared in hardware before the multiple-of-4 check. Bit 1 is not fixed — jalr x0, 2(x1) with x1 = 0x10 targets 0x12 and gives ERR_INSTR_ADDRESS_MISALIGNED.