MIPS_primery_zadach / dandamudi05gtr guide risc processors programmers engineers
.pdf176 |
|
Guide to RISC Processors |
15: |
.asciiz |
"Please enter a number (<11 digits): " |
16:out_msg:
17: |
.asciiz |
"\nThe sum of individual digits is: " |
18:newline:
19: .asciiz "\n"
20:number:
21: |
.space |
11 |
# space for input string |
22: |
|
|
|
23:#################### Code segment #######################
24:.text
25:.globl main
26:main:
27: |
la |
$a0,number_prompt # prompt user for input |
28:li $v0,4
29:syscall
31: |
la |
$a0,number |
# |
read |
the input number |
32: |
li |
$a1,11 |
# |
as a |
string |
33:li $v0,8
34:syscall
36: |
la |
$a0,out_msg |
# write output message |
37:li $v0,4
38:syscall
40: |
la |
$t0,number |
# |
pointer to number |
41: |
li |
$t2,0 |
# |
init sum to zero |
42:loop:
43:lb $t1,($t0)
44: |
beq |
$t1,0xA,exit_loop |
# if linefeed or |
|
45: |
beqz |
$t1,exit_loop |
# NULL, we are done |
|
46: |
and |
$t1,$t1,0x0F |
# strip off upper 4 |
bits |
47: |
addu |
$t2,$t2,$t1 |
# add to running total |
|
48: |
addu |
$t0,$t0,1 |
# increment pointer |
|
49: |
b |
loop |
|
|
50: |
exit_loop: |
|
|
|
51: |
move |
$a0,$t2 |
# output sum |
|
52:li $v0,1
53:syscall
55: |
la |
$a0,newline |
# output newline |
56:li $v0,4
57:syscall
59: |
li |
$v0,10 |
# exit |
60:syscall
Chapter 10 • Assembly Language Overview |
177 |
The loop body to compute the sum follows the pseudocode given before. The input digit, which is in character form, must be converted to its numeric equivalent. Because ASCII assigns a special set of contiguous values to the digit characters, this conversion is straightforward. All we have to do is to mask off the upper half of the byte. This conversion is done by
and $t1,$t1,0xF
on line 46. Alternatively, we could also subtract the character code for 0 as
sub $t1,$t1,’0’
instead of masking the upper half-byte. This numeric value is added to the sum (maintained in $t2) on line 47. The string pointer in $t0 is incremented to point to the next input digit (line 48). When the loop is exited, we simply output the sum in the $t2 register.
Example 10.2 Conversion of lowercase letters to uppercase.
This program demonstrates how character manipulation can be used to convert lowercase letters to uppercase. The program receives a character string from the user and converts all lowercase letters to uppercase and displays the string. Characters other than the lowercase letters are not changed in any way. The pseudocode of Program 10.3 is shown below:
main()
prompt user for input read input string write output sum message index := 0
char := string[index] while (char =NULL)
if ((char ≥ ’a’) AND (char ≤ ’z’)) then
char := char + ’A’ − ’a’ end if
index := index + 1
char := string[index] end while
output string exit
end main
We can see from Program 10.3 that the compound if condition requires two conditional branch instructions. The conditional branch instruction blt on line 41 tests if the character is less than a. If so, it is not a lowercase character and conversion is not done.
178 |
Guide to RISC Processors |
Similarly, the other condition, character > z, is tested by the bgt instruction on line 42. If both these tests are false, the character is a lowercase letter. In this case, we use
addu $t1,$t1,-32
to convert it to an uppercase letter (line 43). For example, if the character is a, the character is represented by 0x61 (or 97 in decimal). By subtracting −32 (or 0x20), we get 0x41 or 65 in decimal. As you know, this value represents A in ASCII. Of course, we can do this case conversion by using the subtract instruction as shown here:
sub $t1,$t1,32
We can also use the logical and instruction to force the 5th bit to zero. This can be easily done by
and $t1,$t1,0xDF
The original or converted character is written back into the string using the sb instruction on line 46. After the loop is terminated, we display the converted string (lines 50–52) and exit.
Program 10.3 Conversion of lowercase letters to uppercase
1: # Uppercase conversion of characters |
TOUPPER.ASM |
2:#
3:# Objective: To convert lowercase letters to
4: # |
corresponding uppercase letters. |
5:# Input: Requests a character string from the user.
6:# Output: Prints the input string in uppercase.
7:#
8:# $t0 - points to character string
9:# $t1 - used for character conversion
10:#
11:################### Data segment #####################
12:.data
13:name_prompt:
14: |
.asciiz |
"Please type your name: \n" |
15:out_msg:
16: |
.asciiz |
"Your name in capitals is: " |
17:in_name:
18: |
.space |
31 |
# space for input string |
19: |
|
|
|
20:################### Code segment #####################
21:.text
22:.globl main
23:main:
24: |
la |
$a0,name_prompt # prompt user for input |
Chapter 10 • Assembly Language Overview |
179 |
25:li $v0,4
26:syscall
28: |
la |
$a0,in_name |
# read the input string |
29:li $a1,31
30:li $v0,8
31:syscall
32: |
|
|
|
33: |
la |
$a0,out_msg |
# write output message |
34:li $v0,4
35:syscall
37:la $t0,in_name
38:loop:
39:lb $t1,($t0)
40: |
beqz $t1,exit_loop |
# if NULL, we are done |
41:blt $t1,’a’,no_change
42:bgt $t1,’z’,no_change
43: |
addu $t1,$t1,-32 |
# |
convert |
to uppercase |
44: |
|
# |
’A’-’a’ |
= -32 |
45:no_change:
46:sb $t1,($t0)
47: |
addu |
$t0,$t0,1 |
# increment pointer |
48: |
b |
loop |
|
49:exit_loop:
50: |
la |
$a0,in_name |
# output converted string |
51:li $v0,4
52:syscall
54: |
li |
$v0,10 |
# exit |
55:syscall
Example 10.3 Division of two integers using subtraction.
In this example, we use subtraction to implement the division operation. Note that the MIPS instruction set supports multiplication and division instructions, even though we have not discussed them in this chapter. These instructions are described in detail in Chapter 13. For now we show how the division operation can be implemented using repeated subtraction.
main()
prompt user for input
read the two input numbers (dividend and divisor) quotient := 0
remainder := number
while (remainder < divisor)
180 |
Guide to RISC Processors |
quotient := quotient + 1 remainder := remainder − divisor
end while output quotient output remainder exit
end main
The program, shown in Program 10.4, follows the pseudocode in a straightforward manner. The user is prompted for the dividend (numerator) and divisor (denominator) on lines 23–25. The two input numbers are read on lines 27–28 and 31–32. The divisor is stored in $t1 and the dividend (also the remainder) is stored in the $t0 register. The quotient, which is maintained in $t2, is initialized to zero on line 35.
Program 10.4 Division by repeated subtraction
1: # Division using subtraction DIVIDE.ASM
2:#
3:# Objective: Finds quotient and remainder of a division
4: # |
using the subtract instruction. |
5:# Input: Requests two numbers from the user.
6:# Output: Outputs the quotient and remainder.
7:#
8:################### Data segment ###################
9:.data
10:prompt:
11: |
.asciiz |
"Please enter dividend and divisor: " |
12:quo_msg:
13: |
.asciiz |
"The quotient is: " |
14:rem_msg:
15: |
.asciiz |
"The remainder is: " |
16:newline:
17: |
.asciiz |
"\n" |
18: |
|
|
19:################### Code segment ###################
20:.text
21:.globl main
22:main:
23: |
la |
$a0,prompt |
# prompt user for input |
24:li $v0,4
25:syscall
27: |
li |
$v0,5 |
# read dividend into $t0 |
28:syscall
29:move $t0,$v0
Chapter 10 • |
Assembly Language Overview |
181 |
||
30: |
|
|
|
|
31: |
li |
$v0,5 |
# read divisor into $t1 |
|
32:syscall
33:move $t1,$v0
35: |
li |
$t2,0 |
# quotient (in $t2) = 0 |
36: |
loop: |
|
|
37: |
bltu |
$t0,$t1,done |
# if rem < divisor, done |
38: |
add |
$t2,$t2,1 |
# increment quotient |
39: |
sub |
$t0,$t0,$t1 |
# compute remainder |
40: |
b |
loop |
|
41: |
|
|
|
42: |
done: |
|
|
43: |
la |
$a0,quo_msg |
# write quotient message |
44:li $v0,4
45:syscall
47: |
move $a0,$t2 |
# output quotient |
48:li $v0,1
49:syscall
51: la $a0,newline # write newline
52:li $v0,4
53:syscall
55: |
la |
$a0,rem_msg |
# write remainder message |
56:li $v0,4
57:syscall
59: |
move $a0,$t0 |
# output remainder |
60:li $v0,1
61:syscall
63: la $a0,newline # write newline
64:li $v0,4
65:syscall
67: |
li |
$v0,10 |
# exit |
68:syscall
The division loop consists of the lines 36–40. The while loop termination condition (remainder < divisor) is tested on line 37. If the condition is not met, the quotient is incremented (line 38) and the remainder is updated by subtracting the divisor (line 39).
After the loop is terminated, we first display the quotient, which is in $t2, using the print_int system call. Then after displaying the remainder, the program terminates.
182 |
Guide to RISC Processors |
Summary
In this chapter, we presented the basics of the MIPS assembly language programming. We discussed three types of assembly language statements:
1.Executable statements that instruct the processor as to what to do;
2.Pseudoinstructions that are supported by the assembler;
3.Assembler directives that facilitate the assembly process.
Assembler directives to allocate storage space for data variables and to define numeric and string constants were discussed in detail. The assembler extends the processor instruction set by providing several pseudoinstructions. It translates these pseudoinstructions to one or more processor instructions. We have given an example to show how the assembler does this translation. We give more examples in later chapters.
We have given an overview of the MIPS instruction set. Although we discussed in detail the load and store instructions, there was only a brief review of the remaining instructions of the MIPS instruction set. A detailed discussion of these instructions is provided in later chapters.
11
Procedures and the Stack
We have given an overview of the MIPS assembly language in the last chapter. This chapter discusses how procedures are written in the MIPS assembly language. Procedure is an important programming construct that facilitates modular programming. Procedures can be divided into leaf and nonleaf procedures. A leaf procedure does not call other procedures. On the other hand, a nonleaf procedure calls other procedures. In the MIPS architecture, we can implement simple leaf procedures without using the stack. Nonleaf procedures always need to use the stack, at least to store the return address of the calling procedure.
In this chapter, we discuss how both leaf and nonleaf procedures are implemented in the assembly language. Our initial discussion deals with simple leaf procedures that can be implemented using the registers. Then we look at how the stack is implemented in the MIPS architecture. The details about the stack implementation are useful in understanding how we can handle nonleaf procedures in the assembly language.
Parameter passing is an important aspect of procedure invocation. As you know, we can use either the call-by-value or call-by-reference mechanism for parameter passing. Here we give details on how we can implement these two types of parameter-passing mechanisms in the assembly language programs.
Introduction
A procedure is a logically self-contained unit of code designed to perform a specific task. Procedures are sometimes called subprograms and play an important role in modular program development. In high-level languages, there are two types of subprograms: procedures and functions. Each function receives a list of arguments and performs a computation based on the arguments passed onto it and returns a single value.
Procedures also receive a list of arguments just as the functions do. However, procedures, after performing their computation, may return zero or more results back to the calling procedure. In C language, both these subprogram types are combined into a single
183
184 |
Guide to RISC Processors |
function construct. For example, in the following C function
int sum (int x, int y, int z)
{
return (x+y+z);
}
the parameters x, y, and z are called formal parameters and the function body is defined based on these parameters. When this function is called (or invoked) by a statement like
total = sum(number1,number2,number3);
the actual parameters—number1, number2, and number3—are used in the computation of the function sum.
There are two types of parameter-passing mechanisms: call-by-value and call-by- reference. In the call-by-value mechanism, the called function (sum in our example) is provided only the values of the parameters for its use. Thus, in this mechanism, the values of these actual parameters are not changed in the called function; these values can only be used as in a mathematical function. In our example, the sum function is invoked by using the call-by-value mechanism, as we simply pass the values of number1, number2, and number3 to the called function sum. Thus, if sum modifies x, y, or z, these changes are not reflected in the calling function.
To illustrate this point, let’s assume that we want to exchange two values a and b passed on to the function swap. Suppose we define our swap function as follows.
/* Incorrect swap procedure */ int swap (int a, int b)
{
int temp;
temp = a; a = b;
b = temp; return;
}
If we call swap as
swap(value1, value2);
it will not exchange the two values value1 and value2 because we are using the call- by-value mechanism. We can remedy this problem by using the call-by-reference mechanism.
In the call-by-reference mechanism, the called function actually receives the addresses (i.e., pointers) of the parameters from the calling function. The function can change the contents of these parameters—and these changes are seen by the calling function—by directly manipulating the actual parameter storage space. We can rewrite the swap function as
Chapter 11 • Procedures and the Stack |
185 |
/* Correct swap procedure */ void swap (int *a, int *b)
{
int temp;
temp = *a; *a = *b; *b = temp;
}
This procedure works fine as it passes the addresses of the two parameters from the calling function. Such a function can be invoked as
swap (&data1, &data2);
Often both types of parameter-passing mechanisms are used in the same function. As an example, consider finding the roots of the quadratic equation
ax2 + bx + c = 0 .
The two roots are defined as |
√ |
|
|
|
|
|
|
|
|
|
|
|
b |
2 |
− 4ac |
|
|
root1 = |
−b + |
|
, |
||
|
|
||||
2a |
|
||||
|
|
|
|||
|
√ |
|
|
|
|
|
b |
2 |
− 4ac |
|
|
root2 = |
−b − |
|
. |
||
|
|
||||
2a |
|
||||
|
|
|
The roots are real if b2 ≥ 4ac, and imaginary otherwise.
Suppose that we want to write a function that receives a, b, and c and returns the values of the two roots (if real) and indicates whether the roots are real or imaginary.
int roots (double a, double b, double c, double *root1, double *root2)
{
int root_type = 1;
if (4*a*c <= b*b){ /* roots are real */ *root1 = (−b + sqrt(b*b − 4*a*c))/(2*a); *root2 = (−b − sqrt(b*b − 4*a*c))/(2*a);
}
else /* roots are imaginary */ root_type = 0;
return (root_type);
}
The function receives parameters a, b, and c via the call-by-value mechanism, and root1 and root2 parameters are passed using the call-by-reference mechanism. A typical invocation of roots is