MIPS_primery_zadach / dandamudi05gtr guide risc processors programmers engineers
.pdf206 |
|
Guide to RISC Processors |
|
Program 11.5 A string reverse example |
|
1: |
# Reverses a string |
STRING_REVERSE.ASM |
2:#
3:# Objective: Reverses a given string.
4:# Input: Requests a string from the user.
5:# Output: Outputs the reversed string.
6:#
7:# $a0 - string pointer
8:#
9:################## Data segment ######################
10:.data
11:prompt:
12: |
.asciiz |
"Please enter a string: \n" |
13:out_msg:
14: |
.asciiz |
"The reversed string is: " |
15:in_string:
16: .space 31 17:
18:################## Code segment ######################
19:.text
20:.globl main
21:main:
22: |
la |
$a0,prompt |
# prompt user for input |
23:li $v0,4
24:syscall
26: |
la |
$a0,in_string |
# |
read input string |
27: |
li |
$a1,31 |
# |
buffer length in $a1 |
28:li $v0,8
29:syscall
31:la $a0,in_string
32:jal string_reverse
34: |
la |
$a0,out_msg |
# write output message |
35:li $v0,4
36:syscall
38: |
la |
$a0,in_string |
# output reversed string |
39:li $v0,4
40:syscall
42: |
li |
$v0,10 |
# exit |
43: |
syscall |
|
|
44: |
|
|
|
45:#------------------------------------------------------
Chapter 11 • Procedures and the Stack |
207 |
46:# STRING_REVERSE receives a pointer to a string in $a0
47:# and reverses the string
48:# $a0 - front pointer
49:# $t1 - back pointer
50:# $t2, $t3 - used as temporaries
51:#------------------------------------------------------
52:string_reverse:
53: |
move $t1,$a0 |
# init $t1 to front pointer |
54:loop1:
55:lbu $t2,($t1)
56: |
beq |
$t2,0xA,done1 |
# |
if |
linefeed |
57: |
beqz |
$t2,done1 |
# |
or |
NULL, we are done |
58:addu $t1,$t1,1
59: b loop1
60:done1:
61: |
sub $t1,$t1,1 |
# $t1 = back pointer |
62: |
|
|
63:reverse_loop:
64: |
bleu |
$t1,$a0,done |
# if back <= front, done |
65: |
lbu |
$t2,($a0) |
# |
66: |
lbu |
$t3,($t1) |
# exchange values |
67: |
sb |
$t2,($t1) |
# at front & back |
68: |
sb |
$t3,($a0) |
# |
69: |
addu |
$a0,$a0,1 |
# update front |
70: |
subu |
$t1,$t1,1 |
# and back |
71: |
b |
reverse_loop |
|
72:done:
73: jr $ra
The string reverse procedure uses the $a0 and $t1 registers for front and back pointers, respectively. The first while loop is implemented on lines 54–59. The second while loop condition is implemented on line 64. The two characters, pointed to by front and back, are exchanged on lines 65–68. The two pointers are updated on lines 69 and 70. Because this is a leaf procedure, we can leave the return address in the $ra register.
Passing Variable Number of Parameters
Procedures in C can be defined to accept a variable number of parameters. The input and output functions, scanf and printf, are the two common procedures that take a variable number of parameters. In this case, the called procedure does not know the number of parameters passed onto it. Usually, the first parameter in the parameter list specifies the number of parameters passed.
In assembly language procedures, a variable number of parameters can be easily han-
208 |
Guide to RISC Processors |
dled by the stack method of parameter passing. Only the stack size imposes a limit on the number of parameters that can be passed. The next example illustrates the use of the stack to pass a variable number of parameters in the MIPS assembly language programs.
Example 11.4 Passing a variable number of parameters via the stack.
The objective of this example is to show how easy it is to pass a variable number of parameters using the stack. The program, shown in Program 11.6, reads a number of integers and outputs their sum.
The procedure sum receives a variable number of integers via the stack. The parameter count is passed via $a0.
Program 11.6 Passing a variable number of parameters to a procedure
1: # Sum of variable number of integers VAR_PARA.ASM
2:#
3:# Objective: Finds sum of variable number of integers.
4: # |
Passes variable number of integers via stack. |
5:# Input: Requests integers from the user; input can be
6: # |
terminated by entering a zero. |
7:# Output: Outputs the sum of input numbers.
8:#
9:# $a0 - number of integers passed via the stack
10:# $v0 - sum is returned via this register
11:#
12:#################### Data segment ########################
13:.data
14:prompt:
15: |
.ascii |
"Please enter integers. \n" |
16: |
.asciiz |
"Entering zero terminates the input. \n" |
17:sum_msg:
18: |
.asciiz |
"The sum is: " |
19:newline:
20: |
.asciiz |
"\n" |
21: |
|
|
22:#################### Code segment ########################
23:.text
24:.globl main
25:main:
26: |
la |
$a0,prompt |
# prompt user for input |
27:li $v0,4
28:syscall
30:li $a0,0
31:read_more:
32: |
li |
$v0,5 |
# read a number |
Chapter 11 • Procedures and the Stack |
209 |
33:syscall
34:beqz $v0,exit_read
35: |
subu |
$sp,$sp,4 |
# |
reserve 4 |
bytes on stack |
36: |
sw |
$v0,($sp) |
# |
store the |
number on stack |
37:addu $a0,$a0,1
38: b read_more
39:exit_read:
40: |
jal |
sum |
# |
sum is returned in $v0 |
41: |
move |
$t0,$v0 |
|
|
42: |
|
|
|
|
43: |
la |
$a0,sum_msg |
# |
write output message |
44:li $v0,4
45:syscall
47: |
move $a0,$t0 |
# output sum |
48:li $v0,1
49:syscall
51: |
la |
$a0,newline |
# write newline |
52:li $v0,4
53:syscall
55: |
li |
$v0,10 |
# exit |
56: |
syscall |
|
|
57: |
|
|
|
58:#----------------------------------------------------
59:# SUM receives the number of integers passed in $a0
60:# and the actual numbers via the stack. It returns
61:# the sum in $v0.
62:#----------------------------------------------------
63:sum:
64: |
li |
$v0,0 |
# init sum = 0 |
65:sum_loop:
66:beqz $a0,done
67: |
lw |
$t0,($sp) |
# |
pop the top value |
68: |
addu |
$sp,$sp,4 |
# |
into $t0 |
69:addu $v0,$v0,$t0
70:subu $a0,$a0,1
71: b sum_loop
72:done:
73:jr $ra
The main program reads a sequence of integers from the input. Entering a zero terminates the input. Each number read from the input is pushed directly onto the stack (lines 35 and 36). Because $sp always points to the last item pushed onto the stack, we
210 |
Guide to RISC Processors |
can pass this value to the procedure. Thus, a simple procedure call (line 40) is sufficient to pass the parameter count and the actual values.
The procedure sum reads the numbers from the stack. As it reads, it decreases the stack size (i.e., $sp increases). The loop in the sum procedure terminates when $a0 is zero (line 66). When the loop terminates, the stack is also cleared of all the arguments.
Summary
We have introduced procedures and discussed how procedures are implemented in the MIPS assembly language. We can divide procedures into leaf and nonleaf categories. A leaf procedure does not call another procedure whereas a nonleaf procedure invokes another procedure. In the MIPS architecture, leaf procedures can be written using internal registers. As part of procedure invocation, the return address is stored in the $ra register. This value is used to transfer control back to the caller after executing the procedure. Nonleaf procedures, however, need to use the stack.
The stack is a last-in-first-out data structure that plays an important role in nonleaf procedure invocation and execution. The stack supports two operations: push and pop. Only the element at the top-of-stack is directly accessible through these operations. The $sp register points to the top of the stack. It is important to note that the stack grows downward (i.e., towards lower memory addresses). Because the MIPS architecture does not explicitly support stack operations, we need to manipulate the stack pointer ($sp) register to implement the stack push and pop operations.
When writing procedures in assembly language, parameter passing has to be explicitly handled. Parameter passing can be done via registers or the stack. Although the register method is efficient, the stack-based method is more general and flexible. Also, when the stack is used for parameter passing, passing a variable number of parameters is straightforward. We have demonstrated this by means of an example.
In this chapter we discussed direct procedure calls in which the target instruction address is directly specified. The MIPS architecture also supports indirect procedure calls. In these calls, the target is specified indirectly through a register. Even though we haven’t used this mechanism to invoke a procedure, we have used it to return from a procedure. We discuss indirect procedure calls in Chapter 14.
12
Addressing Modes
MIPS supports several ways to specify the location of the operands required by an instruction. These are called addressing modes. Most instructions expect their operands in the registers. However, load and store instructions are special in the sense that they interface with memory. These instructions require a memory address, which can be specified in several ways. This chapter gives details on the addressing modes we can use in writing MIPS assembly language programs.
Introduction
As discussed in Chapter 3, RISC processors use simple addressing modes. In contrast, CISC processors provide complex addressing modes. In this chapter, we look at the MIPS addressing modes in detail. SPIM simulates the MIPS R2000 processor, therefore our focus is on the assembly language of this processor.
As mentioned before, MIPS uses the load/store architecture. In this architecture, only the load and store instructions move data between memory and processor registers. All other instructions expect their operands in registers. Thus, they use the register addressing mode. The load and store instructions, however, need a memory address. A variety of addressing modes is available to specify the address of operands located in memory. The MIPS architecture supports the following addressing modes.
•Register addressing mode
•Immediate addressing mode
•Memory addressing mode
We look at these three addressing modes in the next three sections. Following this description, we give details on accessing and organizing arrays. We consider both onedimensional and multidimensional arrays. We give several examples to illustrate the use the addressing modes presented here. We conclude the chapter with a summary.
211
212 |
Guide to RISC Processors |
Addressing Modes
In this section, we briefly look at the three addressing modes. Later sections give examples that use these addressing modes.
Register Addressing Mode
This is the commonly used addressing mode. In this addressing mode, the operands are located in the registers. For example, in the instruction
add $t2,$t1,$t0
the two source operands are in $t1 and $t0. The result of the operation is also stored in a register ($t2 in this example). Most instructions, excluding the load and store, use this addressing mode. These instructions are encoded using the R-type format shown in Figure 4.2 on page 52.
Immediate Addressing Mode
In immediate addressing mode, the operand is stored in the instruction itself. This addressing mode is used for constants as shown in the following example.
addi $t0,$t0,4
This instruction is encoded using the I-type instruction format (see Figure 4.2). As you can see from this format, the immediate value is limited to 16 bits. As a consequence, the constant is limited to a signed 16-bit value. The advantage of this addressing mode is that the immediate operand is fetched along with the instruction; it does not require a separate memory access.
Memory Addressing Modes
As discussed in Chapter 4, the bare machine provides only a single memory addressing mode, disp(Rx), where displacement disp is a signed, 16-bit immediate value. The address is computed by adding disp to the contents of register Rx. The virtual machine supported by the assembler provides additional addressing modes for load and store instructions to help in assembly language programming. Table 12.1 shows the addressing modes supported by the virtual machine. Like the immediate addressing mode, the I-type instruction format is used to encode this addressing mode (see Figure 4.2).
Next we look at some examples of the memory addressing modes. The following instruction
lw $t0,($t1)
loads the 32-data at the address given by the $t1 register. In the following examples, we assume that the array is declared as
Chapter 12 • Addressing Modes |
213 |
||
|
Table 12.1 Addressing modes |
|
|
|
|
|
|
|
Format |
Address computed as |
|
|
|
|
|
|
(Rx) |
Contents of register Rx |
|
|
imm |
Immediate value imm |
|
|
imm(Rx) |
imm + contents of Rx |
|
|
symbol |
Address of symbol |
|
|
symbol± imm |
Address of symbol ±imm |
|
|
symbol± imm(Rx) |
Address of symbol ±(imm + contents of Rx) |
|
array:
.word 15,16,17,18,19,20
We use the load instruction to illustrate the various memory addressing modes. We can also specify the address by using array as in the following example.
lw $t1,array
This instruction loads the first word (i.e., value 15) into the $t1 register. We can specify an immediate constant as in the following example.
lw $t1,4($t0)
If $t0 contains the address of array, this instruction loads the second element (i.e., value 16) into the $t1 register. Of course, we can do the same thing using the following instruction.
lw $t1,array+4
In our next example, we look at the last addressing mode given in Table 12.1. In this addressing mode, the address is computed from three components: symbol±imm(Rx). Here is an example that uses these three components:
lw $t2,array+4($t0) sw $t2,array+0($t0)
This two-instruction sequence copies the second element into the first element, assuming that $t0 contains 0. We use this instruction sequence in an example later (see Example 12.1 on page 219).
Note that most load and store instructions operate only on aligned data. The MIPS, however, provides some instructions for manipulating unaligned data.
214 |
Guide to RISC Processors |
Processing Arrays
Arrays are useful in organizing a collection of related data items, such as test marks of a class, salaries of employees, and so on. We have used arrays of characters to represent strings. Such arrays are one-dimensional: only a single subscript is necessary to access a character in the array. Next we discuss one-dimensional arrays. High-level languages support multidimensional arrays, which are discussed towards the end of this chapter.
One-Dimensional Arrays
A one-dimensional array of test marks can be declared in C as
int |
test_marks [10]; |
In C, the subscript always starts at zero. Thus, the mark of the first student is given by test_marks[0] and that of the last student by test_marks[9].
Array declaration in high-level languages specifies the five attributes:
•Name of the array (test_marks),
•Number of the elements (10),
•Element size (4 bytes),
•Type of element (integer), and
•Index range (0 to 9).
From this information, the amount of storage space required for the array can be easily calculated. Storage space in bytes is given by
Storage space = number of elements * element size in bytes.
In our example, it is equal to 10 * 4 = 40 bytes. In assembly language, arrays are implemented by allocating the required amount of storage space. For example, we can declare the test_marks array as
test_marks: .space 40
An array name can be assigned to this storage space. But that is all the support you get in assembly language! It is up to you as a programmer to “properly” access the array taking into account the element size and the range of subscripts.
You need to know how the array is stored in memory in order to access its elements. For one-dimensional arrays, representation of the array in memory is rather direct: array elements are stored linearly in the same order, as shown in Figure 12.1. In the remainder of this section, we use the convention used for arrays in C (i.e., subscripts are assumed to begin with 0).
To access an element we need to know its displacement in bytes relative to the beginning of the array. Because we know the element size in bytes, it is rather straightforward to compute the displacement from the subscript:
Chapter 12 • Addressing Modes |
|
215 |
|
High memory |
test_marks[9] |
|
|
|
|
|
|
|
test_marks[8] |
|
|
|
test_marks[7] |
|
|
|
test_marks[6] |
|
|
|
test_marks[5] |
|
|
|
test_marks[4] |
|
|
|
test_marks[3] |
|
|
|
test_marks[2] |
|
|
|
test_marks[1] |
|
|
Low memory |
test_marks[0] |
|
test_marks |
|
|
Figure 12.1 One-dimensional array storage representation.
displacement = subscript * element size in bytes.
For example, to access the sixth student’s mark (i.e., subscript is 5), you have to use 5 * 4 = 20 as the displacement into the test_marks array. Later we present an example that computes the sum of a one-dimensional integer array.
Multidimensional Arrays
Programs often require arrays of more than one dimension. For example, we need a two-dimensional array of size 50 × 3 to store test marks of a class of 50 students taking three tests during a semester. In this section, we discuss how two-dimensional arrays are represented and manipulated in the assembly language. Our discussion can be generalized to higher-dimensional arrays.
For example, a 5 × 3 array to store test marks can be declared in C as
int class_marks[5][3]; /* 5 rows and 3 columns */
Storage representation of such arrays is not as direct as that for one-dimensional arrays. Because the memory is one-dimensional (i.e., linear array of bytes), we need to transform the two-dimensional structure to a one-dimensional structure. This transformation can be done in one of two common ways:
•Order the array elements row-by-row, starting with the first row,
•Order the array elements column-by-column, starting with the first column.
The first method, called the row-major ordering, is shown in Figure 12.2a. Row-major ordering is used in most high-level languages including C. The other method, called the