This is a follow-on from this question:
I'd like to implement call
and return
instructions in a Microarchitecture I'm creating, but I'm not sure of the best way to go about it.
How are these instructions usually implemented?
This is a follow-on from this question:
I'd like to implement call
and return
instructions in a Microarchitecture I'm creating, but I'm not sure of the best way to go about it.
How are these instructions usually implemented?
TEMLIB's answer may be complete but am going to answer in a simlar way but perhaps different, depends on the reader.
A "call" means to jump/branch somewhere with a solution for returning to the instruction after the call.
A return means to jump/branch to the instruction after the call based on the design solution for call/return.
You pretty much have two choices, either you use registers or you use the stack. You can try to CISC/RISC this if you want but those are your basic choices. You also need to think about interrupts which are essentially a call and return as well but from a system engineering perspective need to save state. So again, registers or stack.
Stack solutions are pretty straight forward the return address is pushed on the stack with the call and popped off and used on the return. If it is an interrupt then you will likely want to push the return address and the psr if you have flags/state, and if you have execution modes then interrupts tend to be higher than the low level user/application level if not its own execution mode (possibly its own stack, etc). Which means you need to save the information as to what mode was interrupted as well as any flags (carry, zero, etc).
A register based approach tends to need to use a general purpose register as the callee needs to save that register to the stack IF they become a caller and nest to another function. The call instruction or instructions (pc relative, register, absolute, etc) would save the return address in a known register, to some extent a reserved register, and then the return is simply a register based branch/jump and may not even need a return instruction if there is already a register based branch/jump. Interrupts with this scheme are tricky as you would need a place to copy/keep the state and then the return from interrupt is not just a branch as that state needs to be restored on the way out. You can look at ARM for example on ways to do this. If you for example have an interrupt execution mode and only one interrupt can be in that mode at a time then only one copy of the processor state register needs to be kept and within the interrupt it is business as usual, the special return would branch to the interrupted address (which you also need a solution for where that is be it banked registers or yet another special register) and would restore the state, if another interrupt is pending then that would repeat the process. The cortex-m solution is very interesting in that not only is it a stack based solution for an architecture that uses a register based call/return. But it goes so far as to make the hardware conform to the proposed calling convention (tools folks are free to choose or invent whatever calling convention they like the processor vendors is no more than a suggestion in reality (granted folks will tend to conform)). So the compiler does not have to craft a special interrupt handler function wrapped with code to save state and a return using a special instruction. Instead the compiler can generate a normal function, the normally volatile registers are preserved for you as well as the processor state as well as the interrupted address as well as a way to know from a normal return/branch that this was in fact an interrupt and then the logic restores state from the stack. Generally the compiler/programmer has to do all the work if they want to call a compiled function to save state of all the registers, then call a compiled function and on return from that function restore state and use the special return from interrupt instruction.
The real question is what is your design thus far? Are you prescribing register based or stack based for function call parameters, and based on that should probably be stack based or register based for the return address.
Call/return are trivial, it is the interrupt that is the one to think about.
It is not explicitly required to have call and return instruction so long as you can solve the same thing functionally for example in some architectures you can do math with the pc to generate the return address in a register, then can do a simple jump to an address in whatever addressing mode, the two of those combined with a register based branch/jump instruction allows for you to create a register based call/return without a special call instruction. not efficient, but again are you going for a very small number of instructions/operations or not aiming for that?
register based is the easiest of course, no memory cycles other than the register file(s) which is part of every instruction anyway. A stack based solution is a lot more steps and has the time penalty of the memory operation. (are you RISC like or CISC like thus far?)
A comment above is the true answer. There is no one right answer for this, in general register or stack are your choices, pick one. But IMO the call/return is trivial and should have only been minutes to implement and move on with your processor. Interrupts are the problem, call/return are trivial and should be obvious based on your overall design thus far.
CALL is "copy the current address to some place then jump somewhere else" while RETURN is "jump to the saved address".
Often, that copy is done to a stack, stored in internal registers or in main memory, to allow nested calls. For functions that don't call sub-functions, an optimised method allows to store the return address in a register, which can be visible to the programmer (Link Register), or hidden in high performance implementations of some old architectures (e.g. x86)
CALL / RETURN is closely related with interruptions. Like CALL/RETURN, interruptions need to save the current address and have a way to return back to the program. It is also important that the CALL works even if the interruption occurs at exactly the same time the CALL instruction is executed. The "save address" and "jump somewhere else" operations must be tied together and un-interruptible.
usual
. A CISC call is usually handled differently (because the architecture approach is different) than a jump/link instruction on RISC (which has an equivalent in, of all places, the PDP-11's register-only call instruction.) For RISC, because of the typical pipeline, a call just sets the PC and stuffs the next PC address after the call into a register. CISC does more than that. For background purposes, I'd recommend that you carefully read the PDP-11 call instruction and see how it works. It supports coroutines and nothing since like it has been since implemented. Unique. \$\endgroup\$best
. There is no such thing. Context is everything. It just represented a unique watermark and is worth study. Particularly its coroutine call. \$\endgroup\$