ELEC 242 - Subroutines Subroutines
ELEC 242 - Subroutines Subroutines
ELEC 242 - Subroutines Subroutines
Subroutines
Subroutines are procedures written separate from the main program. Whenever the main
program must perform a function that is defined by a subroutine, it calls the subroutine into
operation. In order to do this, control must be passed from the main program to the starting
point of the subroutine. Execution continues with the subroutine and upon completion control is
returned back to the main program at the instruction that follows the one that called the
subroutine. Notice that the difference between the operation of a subroutine call and a jump is
that a call to a subroutine not only produces a jump to an appropriate address in program
storage memory, but it also has a mechanism for saving information such as IP and CS, which
is needed to return back to the main program. We should begin by defining some standard
programming language terms.
These terms are generic and are language dependent.
Subroutine – a section of code that is that is typically designed to be called more than
once from different points in a program.
Function – a subroutine that returns a result
Procedure – a subroutine that does not return a result
In assembly language, a Function is a Procedure that returns a result, and the terms Procedure
and Subroutine are interchangeable. It is common practice today to refer to all assembly
language subroutines as procedures since the term is more closely associated with current high
level programming languages.
So just what is a subroutine? It is a transfer of control statement that is invoked with the CALL
instruction. The other transfer of control statement is the JUMP. We learned about the different
types of jump when we studied the loop. A CALL is similar to a JUMP except:
The address of the next instruction after the CALL instruction is saved.
The IP or CS:IP is reloaded with the address of the subroutine
The code for the subroutine is executed
When the subroutine is completed, a RETurn is executed which reloads the IP or CS:IP
with the address of the instruction immediately after the CALL
The program resumes from there.
So the difference between a JUMP and a CALL is that the JUMP does not preserve the address
(IP or CS:IP) of the next instruction after the call, and, therefore, or program cannot return to
the next instruction after the JUMP.
There are two types of procedures: Near and Far.
NEAR Procedures
When the subroutine is located in the same segment, usually the same code segment, as the
CALL statement, this is a near for the near procedure. In the following examples, Procname is
any label we wish to use for the name of the procedure. The assembly language statement for
the near procedure is:
call Procname
In addition, the subroutine code would be:
Procname proc
; procedure code here
ret
Procname endp
Before branching to the near subroutine, the call instruction causes the IP to be PUSHed on the
Stack. The offset address of the subroutine in the current code segment is loaded in the IP and
program execution continues from there.
When the subroutine is finished, the RETurn instruction causes the contents of the stack to be
POPed into the IP. Program execution continues from the memory location immediately after
the call instruction. In place of RET, we could have used the near return instruction RETN.
However, the assembler will use the correct return since it knows this is a near procedure.
FAR Procedures
When the subroutine and the CALL statement are located in different segments, this is a near
for the far procedure. The assembly language statement for the far procedure is:
CALL far ptr Procname
; procedure code here
ret
Procname endp
In addition, the subroutine code would be:
Procname proc far
; procedure code here
ret
Procname endp
It is the operand that initiates either ran inter segment or an intrasegment call. The operands
Near-proc, Memptr16, and Regptr16 all specify intrasegment calls to a subroutine. In all three
cases, execution of the instruction causes the contents of IP to be saved on the stack. Then the
stack pointer (SP) is decremented by 2. The saved value of IP is the address of the instruction
that follows the CALL instruction. After saving the return address, a new 16-bit value, which
corresponds to the storage location of the first instruction in the subroutine, is loaded into IP.
The three types of intrasegment operands represent different ways of specifying this new value
of IP. In a Near-proc operand, the displacement of the first instruction of the subroutine from
the current value of IP is supplied directly by the instruction. An example is
CALL NEAR PROC Here the label NEAR determines the 16-bit displacement and is coded as
an immediate operand following the opcode for the call instruction. Call is actually a relative
addressing mode instruction; that is, the offset address is calculated relative to the address of
the call instruction itself. With 16 bits, the displacement is limited to 32K bytes.
The Memptr16 and Regptr16 operands provide indirect subroutine addressing by specifying a
memory location or an internal register, respectively, as the source of a new value for IP. The
value specified in this way is not a displacement. It is the actual offset that is to be loaded into
IP. An example of the Regptr 16 operand is
CALL BX
When this instruction is executed, the contents of BX are loaded into IP and execution
continues with the subroutine starting at a physical address derived from CS and IP.
By using one of the various addressing modes of the 8086, an internal register can be used as a
pointer to an operand that resides in memory. This represents a Memptr16 type of operand. In
this case, the value of the physical address of the off- set is obtained from the current contents
of the data segment register OS and the address of addresses held in the specified registers. For
instance, the instruction CALL [BX] has its subroutine offset address at the memory location
whose physical address is derived from the contents of OS and BX. The value stored at this
memory location is loaded into IP. Again the current contents of CS and the new value in IP
point to the first instruction of the subroutine.
Notice that in both intrasegment call examples the subroutine was located with- in the same
code segment as the call instruction. The other type of CALL instruction, the intersegment call,
permits the subroutine to reside in another code segment. It corresponds to the Far-proc and
Memptr32 operands. These operands specify both a new offset address for IP and a new
segment address for CS. In both cases, execution of the call instruction causes the contents of
the CS and IP registers to be saved on the stack and then new values are loaded into IP and CS.
The saved values of CS and IP permit return to the main program from a different code
segment.
Far-proc represents a 32-bit immediate operand that is stored in the four bytes that follow the
opcode of the call instruction in program memory. These two words are loaded directly from
code segment memory into IP and CS with execution of the CALL instruction. An example is
the instruction
Every subroutine must end by executing an instruction that returns control to the main program.
This is the return (RET) instruction. Its execution causes the value of IP or both the values of IP
and CS that were saved on the stack to be returned back to their corresponding registers. In
general, an intrasegment return results from an intrasegment call and an intersegment return
results from an intersegment call.
There is an additional option with the return instruction. It is that a two-byte code following the
return instruction can be included. This code gets added to the stack pointer after restoring the
return address into IP or IP and CS for Far-proc calls. The purpose of this stack pointer
displacement is to provide a simple means by which the parameters that were saved on the
stack before the call to the subroutine was initiated can be discarded.
After the context switch to a subroutine, we find that it is usually necessary to save the contents
of certain registers or some other main program parameters. These values are saved by pushing
them onto the stack. Typically, these data correspond to registers and memory locations that are
used by the subroutine. In this way, their original contents are kept intact in the stack segment
of memory during the execution of the subroutine. Before a return to the main program takes
place, the saved registers and main program parameters are restored. This is done by popping
the saved values from the stack back into their original locations.
Before branching to the far subroutine, the call instruction causes both the CS and the IP to be
PUSHed on the Stack. The segment address and the offset address of the subroutine in the a
different segment are loaded in the CS and IP and program execution continues from there.
When the subroutine is finished, the RETurn instruction causes the CS and IP to be POPed
from of the stack. Program execution continues from the memory location immediately after
the call instruction. Since the subroutine was invoked as a far procedure, we actually execute a
RETF even though we may use the RET.
Nested Procedures
A nested procedure is a procedure called from within a procedure. There is nothing significant
here, and all earlier comments apply. The setup may be similar to the following:
call ProcName
In addition, the subroutine code would be:
ProcName proc
; procedure code here
call Time_Delay
; perhaps more code here
ret
ProcName endp
Time_Delay proc
; procedure code here
ret
Time_Delay endp
Note: Some texts and programmers consider the main part of the program to be a procedure.
You may feel free to do so. Just make sure your subroutines are after main. If you do, then the
code segment of your program would look like this:
.code
.startup
main proc
;program goes here
mov ah,04ch
int 21h
main endp
sub1 proc
; subroutine code
sub1 endp
sub2 proc
; subroutine code
sub2 endp
.exit
end
Preserving Registers
The instruction that is used to save parameters on the stack is the push (PUSH) instruction and
that used to retrieve them back is the pop (POP) instruction. The standard PUSH and POP
instructions can be written with a general-purpose register, a segment register except CS, or a
storage location in memory as their operand.
Registers that will be used during the subroutine that contain data that is necessary on the return
should be preserved. While there are many ways to do this, the easiest method is to PUSH them
on the stack. A few things that we must remember are:
We must make sure to create a Stack, and make it large enough, for any program that
has a CALL
POP all registers that contain data or addresses that we need after the RETurn
We must POP each register that we PUSH
We must POP in the reverse order of the PUSH
If we PUSH after the CALL, we must POP before the RETurn (inside the subroutine) or
if we PUSH before the CALL, we must after the RETurn (in the calling module or
subroutine) The former is preferred.
PUSH Examples
PUSH AX – the contents of a 16-bit register
PUSH EBX - the contents of a 32-bit register
PUSHA (286 and higher) – Preserves all usable registers of 80286
PUSHAD (386 and higher) – Preserves all usable registers of 80386
Note: POPA and POPAD restore the registers in the reverse order of the PUSH, so you do not
have to worry about scrambling register contents. While this is the easiest way, it takes longer
than specific PUSHes, and requires a large Stack if we PUSH many times before POPping.
Execution of a PUSH instruction causes the data corresponding to the operand to be pushed
onto the top of the stack. For instance, if the instruction is PUSH AX its execution results in the
following:
SP <- SP - 1 ;SP is decremented
SS:SP <= AH ;AH is PUSHed on the Stack
SP <- SP - 1 ;SP is decremented
SS:SP <= AL ;AL is PUSHed on the Stack
This shows that the two bytes of AX are saved in the stack part of memory and the stack
pointer is decremented by 2 such that it points to the new top of the stack. On the other hand, if
the instruction is POP AX, its execution results in the following:
AL <- SS:SP ;AL is POPped from the Stack
SP <- SP + 1 ;SP is incremented
AH <= SS:SP ;AH is POPped from the Stack
SP <- SP + 1 ;SP is incremented
character to the right for each additional character. When we clear the screen in MS/DOS, the
screen is cleared and cursor is homed to the top left corner of the screen. As characters are sent
to the CRT, the cursor moves from left to right and top to bottom. When the display is filled, the
screen scrolls up.
Clear Screen Using Screen Scroll Up
Function 6: Screen Scroll Up
With appropriate
Entry Parameters:
values, this function
clears the Register AH: 06H screen just
like the Register AL: number of rows to scroll, 0 for all DOS
command Register BH: see explanation below CLS.
Register CH: Top Row number
Register CL: Top Column Number
Register DH: End Row Number
Register DL: End Column Number
Exit Parameter:
Display is scrolled
Uses INT 10H
Requirements: If the 7 registers contain data that must be used after the interrupt (before
initialization) they should be pushed on the Stack, or saved in storage.
Register AH <= 06H the Video BIOS Function
Register AL <= Number of rows to scroll (0 for all)
Register BH <= Controls foreground and background
Register CH <= Row number at top of the region
Register CL <= Column number at top left of the region
Register DH <= Row number at bottom of the region
Register DL <= Column number at bottom right of the region
We can put his routine anywhere. However, it seems like something we may wish to do from
various locations in a program, so we will discuss how to do this as a procedure
Clear Screen Algorithm
AH <= 6 ; The Video BIOS function for scroll up
AH <= 7 ; The Video BIOS function for scroll down (use one or the other)
AL <= Number of lines to scroll, 0 = all
CX <= Start row column info (0 for top left)
We may enter the Row in CH and the column in CL separately
DX <= End row column data. The data in Hex is
For the bottom, the 25th row is 19H and placed in DH
For the right, the 80th column is 50H and placed in DL
So DX <= 1950h
BH <= foreground/background information
1EH for a dark blue screen with bright yellow text
As a procedure, we might wish to define the Video BIOS data as words and bytes, and just load
the registers as needed:
.data
vbios dw 600h
row0 dw 0
row26 dw 1950h
color db 1Eh
At the end of the main code area, we would write the clear screen procedure
CLR PROC
AX <= vbios
CX <= row0
DX <= row26
BH <= color
INT 10h
RET
CLR endp
Exit Parameter:
Cursor positioned on display
This interrupt is used to position the cursor at a desired row and column. When used with BIOS
INT 21H Function 9, the output string begins at the current cursor location and continues from
there normally.
Requirements: If the registers contain data that must be used after the interrupt (before
initialization) they should be pushed on the Stack, or saved in storage. The pseudo code to
initialize this function is:
Register AX <= 02H
Register DH <= Row data from 0 to 24
Register DL <= Column data from 0 to 79
Register BH <= Video Page (always 0 in this course)
Here is a typical setup modifying the previous example. Assume we wish to start on Row 7
Column 20. We could load DX (061F) or load DH (06) and DL (1F) Keep in mind, the
computer counts from 0 so you must subtract 1. Here is a simple example.
.data
Position1 dw 61Fh
There may be other declarations. In the code section, there may be other statements. This
routine might be coded directly where needed or as a procedure. More than likely, this will be
written and a procedure, so that is how it will be demonstrated.
We will first look at the basic procedure where we will always place the cursor in the same
position.
SetCur proc
DX <= Position1 ;the row and column data
AH <= 2 ;The BIOS function
BH <= 0 ;video page no. 0
INT 10h
RET
SetCur endp
Subroutine Examples
We wish to display multiple messages or prompts on the console device to the user so that
he/she will enter a reply via the console device. The message addresses are passed into the
subroutine. The subroutine displays the message and waits for a reply. When it receives a reply,
it returns the reply in a register.
Subroutine GetIt
On Entry
DX has the address of the message
We display the message using BIOS function 9
On Exit
AH has the Character
We receive input using BIOS Function 1
A subroutine is used since multiple prompts and replies will occur
; Procedure Example
;
; written by ANDREW H. ANDERSEN, JR.
;
;
.model small
.stack 1000h
.data
conin equ 1
prnt equ 9
.code
.startup
mov ax,@data
mov ds,ax
init:
call Clr
fini:
mov ah,04ch
int 21h
Clr proc
;Clear Screen with 25 line scroll.
ret
Clr endp
SetCur proc
;On Entry DX must have row column position
;On Exit The Cursor is positioned
ret
SetCur endp
ShowIt proc
; On Entry DX must have address of message
; On Exit, AL has a Y or N
mov ah,prnt
int 21h
mov ah,conin
int 21h
ret
ShowIt endp
.exit
end