pseudogoto raret returns from a function — it sends control back to whoever called it. It is the closing bracket that every call expects. When a function was called, the caller left a return address in the ra register (see call and jal); ret simply jumps to that address, picking up right after the original call. It is a pseudo-instruction for jumping to ra.
The whole correctness of ret rests on one condition: ra must still hold the right address when it runs. For a function that calls nothing else, that is automatic — nothing touched ra between entry and exit. But the moment a function makes a call of its own, that call overwrites ra with a new return address, erasing the one the function needs. This is why a function that calls others must save its ra on entry and restore it just before ret. Forget the restore and the function returns to the wrong place — a classic crash where the program jumps into nonsense or loops forever.
By convention, a function leaves its result in the a0 register before returning, and the caller looks for it there.
The example is the simplest complete function: square multiplies its argument by itself and then ret hands the result back. Because it calls nothing else, it needs no saving or restoring — just compute and return. Stepping through it in the simulator and watching control jump back to the caller is the clearest way to see calling and returning work.
jalr zero, ra, 0
A pseudo-instruction: the assembler turns it into the real instruction(s) above.
square: mul a0, a0, a0 ret # return to the caller