0
\$\begingroup\$

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?

\$\endgroup\$
9
  • 4
    \$\begingroup\$ There isn't a 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\$ Aug 23, 2023 at 22:34
  • 1
    \$\begingroup\$ @Connor I don't think it is best. There is no such thing. Context is everything. It just represented a unique watermark and is worth study. Particularly its coroutine call. \$\endgroup\$ Aug 24, 2023 at 7:54
  • 1
    \$\begingroup\$ @periblepsis I'm going to contradict you there: While historically relevant for someone who researches the genesis of the last 50 years of computers, it's really not like it's an architecture whose studies would bring you forward if you wanted to design a clean, simple and capable ISA or microarchitecture. It made some design decisions that were interesting back in the day, but that either just were changes from design choices made due to terrible hardware of the machines before PDP-11 (so, solving problems that don't exist today), or don't generally apply today (single move instead of \$\endgroup\$ Aug 24, 2023 at 9:01
  • 1
    \$\begingroup\$ load / store certainly is a strong design choice, but by far not all processor architectures follow that). \$\endgroup\$ Aug 24, 2023 at 9:04
  • 1
    \$\begingroup\$ @MarcusMüller You just don't see what I see, then. But if we sat around a table for an hour, I'd be able to share what I see and you'd be able to share your insights, too. At the end of it, I think we'd break knowing more about each others' views. Sadly, this isn't the place. \$\endgroup\$ Aug 24, 2023 at 10:47

2 Answers 2

2
\$\begingroup\$

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.

\$\endgroup\$
6
  • 1
    \$\begingroup\$ are you using flags to link from one instruction to another, which is very typical. mips is not typical for example a compare and jump is basically one instruction. most processors have flags that are the result of an alu operation including a compare (subtract but not save the result). and there is processor state register that holds those flags....so.....if your design has flags, then when an interrupt happens, from a system level, the interrupted code needs to flow as if it never knew what happened. so if you interrupt in a place where the foreground code is using those flags \$\endgroup\$
    – old_timer
    Aug 24, 2023 at 17:45
  • 1
    \$\begingroup\$ the interrupt needs to restore them. it falls under call/return in that the "call" is logic doing the interrupt and redirecting execution but you need not only a return address but you need to preserve state, when the interrupt is finished the flag bits and all the registers need to be they way they were when the interrupt happened. most of the time the software has to save registers on a stack and restore them and there is often a special return from interrupt that is different than return from function call and that special return does the extra steps like restore the flag register. \$\endgroup\$
    – old_timer
    Aug 24, 2023 at 17:49
  • 1
    \$\begingroup\$ but if you are mips like then you only need a return address and a normal-ish return. if you want to use a separate stack for interrupts then the design must allow for that stack pointer to be set up from a non-interrupt mode and/or like early arm the code can change itself from supervisor to interrupt and back and set the (banked) stack pointer. some mechanism to setup that separate stack, also that means that your return from interrupt needs to either have an if interrupt mode then else to know \$\endgroup\$
    – old_timer
    Aug 24, 2023 at 17:49
  • 1
    \$\begingroup\$ to switch stacks or have a separate return from interrupt instruction from the normal return from function call. \$\endgroup\$
    – old_timer
    Aug 24, 2023 at 17:50
  • 1
    \$\begingroup\$ let me simplify this. if you are a beginnner and have not done an interrupt yet, then do not worry about interrupts, make your processor not support interrupts it is already a task as it is to get something running. not always but a number of the big name RISC processors use/reserve a return address register. some the call instruction you specify the register (and can use any one of them) others the return address register is not an option it is hardcoded into the logic when the call is executed. usually you want 16 or more registers to start using registers and not stack for things. \$\endgroup\$
    – old_timer
    Aug 24, 2023 at 17:52
1
\$\begingroup\$

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.

\$\endgroup\$
9
  • 1
    \$\begingroup\$ "to some place" is an important detail. During my career I came across at least three different implementations... \$\endgroup\$ Aug 24, 2023 at 6:09
  • \$\begingroup\$ Okay, I have a call and return opcode that pass through my control unit. Where do the signals go after that? Should I try and take over my machine with a microsequencer? Or should I make the instruction one cycle long? I'm thinking of using a separate stack for the return addresses, what do you think? \$\endgroup\$
    – Connor
    Aug 24, 2023 at 7:59
  • 1
    \$\begingroup\$ @Connor this seems like a) a large set of completely new questions that should not be asked in a comment, but b) logically structured and reduced to one question that you should ask in a new question post. You're doing a lot of "beginner's mistakes" here in mixing terms you've read into categories they don't belong in. You're assuming something needs to be implemented in the one way you read about. "make instructions one cycle long" for example makes no sense here; where does it come from? The interruption aspect? That you can't counter with 1-per-cycle throughput instructions in practice, \$\endgroup\$ Aug 24, 2023 at 9:10
  • 1
    \$\begingroup\$ due to pipelining. So, hm. Ask a new question, and try to apply Occam's Razor to the assumptions you make about the solution while asking. This is all very wild, and honestly, you would be saving yourself (and us) a lot of effort if you just got a book that took you through processor design basics. You're playing that game, not us, and asking about a lot of "isolated assumptions" is tedious compared to understanding the F what you're doing. \$\endgroup\$ Aug 24, 2023 at 9:11
  • 1
    \$\begingroup\$ Your comment under @old_timer's answer is very symptomatic of that. You got an answer you don't understand, but you're already off asking a lot of new questions based on your self-observed lack of understanding. That obviously isn't a good deal for you nor for your answerers, because you don't learn anything if you only take facts stated as they are, and try to avoid understanding the background these facts come from. There isn't the one golden way – engineering needs you to understand what you're designing, not just apply the cookbook. Because if it was that, engineers wouldn't exist. \$\endgroup\$ Aug 24, 2023 at 9:14

Not the answer you're looking for? Browse other questions tagged or ask your own question.