Subroutines

Loading...

Code

#
Lbl
Op
ATD

General Memory

What does this mean?

Help

Instruction to be executed next is highlighted in blue.

Target address of instruction to be executed next is highlighted in pink.

Mouse over words (the binary numbers) to see how they could be interpreted.

Other Registers:

Instruction Location Counter

Instruction Register

Storage Register

Accumulator

Multiplier-Quotient Register:

Index Register A

Index Register B

Index Register C

In the last example, we wrote a program that computes 2 to the power of x.  So what if we wanted to reuse this code in another program that required us to compute powers of 2?  We could copy the code into each part of the program that required it, but this would be tedious and cumbersome— especially working in a time before text editors!  This is where the concept of subroutines comes in: a piece of code that can be "called" when necessary to perform some smaller task within a larger program. In C we can do something as simple as y = func(x); but in assembly, this is a bit more complicated.  

In the code, you can see at the top the main body of code that seeks to compute 23 + 24.  Below that is a modified version of the looping example labeled with POW, and below that some space allocated for constants.  We use the accumulator to pass arguments and return, placing our desired exponent into the accumulator and then retrieving the product from it afterwards.  You might think, why don't we just jump to POW?  But the problem is that then, after running the subroutine, how we would get back to where we left off.

The most important parts of doing this properly are the TSX operations in the main body and the TRA operation in the subroutine. TSX is a special, nonindexable operation, which stores the negative of the current instruction location counter into the index register designated by the tag before transferring control to the target address. Why is this useful? Because if any instruction with an indexable operation and a tag is performed, the target address of the operation is modified by the index register.  Specifically, we subtract the value of the index register from the target address.  This is how we can call the subroutine wherever we want: first we run TSX POW, 4 to store the negative of the current instruction location counter (-C(ILC))into index register 4, and then jump to where POW is.  Then, after the subroutine is done, we run TRA 1,4, which subtracts the value of index register 4 from 1 as the address to transfer to. But the value of index register 4 was previously set to -C(ILC), so then the address becomes 1-(-C(ILC)) = C(ILC) + 1— the instruction that comes right after TSX POW, 4 where we left off.

One last detail: You'll notice that in the main body, we run SXD SAVE, 4 at the beginning and LXD SAVE, 4 at the end.  This is because we use index register 4 for calling the subroutine, so if we wanted to preserve index register 4's value for some other purpose later in the program, we need to store it to a register and then load it later.  S(tore)XD and L(oad)XD allow us to do exactly that. On the other hand, in the subroutine, we save the value of index register 1 before running through our loop which would modify it, and then reload it before returning to the main program.  In actual practice, the user of a subroutine would not necessarily be the person who wrote it.  Large libraries of subroutines to compute functions like sin and cosine existed on magnetic tapes to be used in other programs.  Thus, because the user of a subroutine would not necessarily know what registers would be modified by the subroutine, it is the responsibility of the subroutine's author to store and restore any modified registers so that the user can simply assume that these registers will not be changed.  A similar process is used to call subroutines in the modern RISC-V calling convention.