What is TINY?
TINY is a set of programs that run on a host machine and simulate a simple but operational computer. The design is intended to allow one to experiment with the concepts of assembly language programming without being overwhelmed with syntax, binary arithmetic, and a complex architecture. The TINY computer is a ‘base-10’ machine that contains 1,000 bytes of memory and 4 registers. There are 20 machine instructions that provide the necessary structure for most demonstration programs. The main focus while studying TINY should be to understand the basic architectural concepts that are common to all computers of the ‘von Neumann’ class. Although large mainframes are more complex and enjoy a much richer instruction set, the basic concepts of machine architecture are much the same as those used by TINY.
Figure 1 is a schematic representation of TINY's architecture. The smallest addressable unit of storage in the primary memory device is called a byte. The size of each byte in TINY’s primary storage is sufficient to store 5 decimal digits and a plus (+) or minus (-) sign. (Although TINY’s bytes are not the same size as the bytes in other computers, it is true that for most computers a byte is the smallest addressable unit of storage.) This means that each addressable byte of memory can contain a number between -99,999 and +99,999. You should also note that the addresses of these bytes consist of three decimal digits and range from 000 to 999.
The Data Bus is the communication link between memory and the CPU. One byte can be transmitted via the Data Bus at a time. Notice that this bus is bi-directional. The CPU can both read the contents of a memory location and store a new value at a particular location. However, the two operations cannot be performed simultaneously.
The Address Bus is used to specify which of the 1,000 memory bytes should be referenced. Thus, if it is desired to store the number +12345 at address 056, the address 056 is first placed on the Address Bus and then the number +12345 is placed on the Data Bus. The Address Bus may be thought of as a pointer to a particular memory cell.
The TINY CPU is about as simple as a CPU can be; and, yet; it still allows one to write complete programs. The CPU consists of 4 registers:
1. Program Counter (PC) - contains address of next instruction (3 decimal digits)
2. Instruction Register (IR) - contains current instruction (5 decimal digits)
3. Accumulator (ACC) - general purpose register used by all arithmetic and logic instructions (5 digits plus sign)
4. Stack Pointer (SP) - contains the address of the top of a stack used by subroutine calls (3 decimal digits)
The CPU also contains connections to the Input and Output devices. Input is received from the keyboard, and output is sent to the terminal screen. These connections to the outside world are linked to the ACC register. That is, all data coming in from the keyboard is placed in the ACC register; and all data going out to the terminal screen must first be placed in the ACC register. TINY has no other input or output devices.
MEMORY
Address Contents
000
001 C P U
002 PC ACC
<---- INPUT
003 ----> OUTPUT
Address Bus
004 <---------
<========>
005 Data Bus IR SP
006 CU ALU
007
•
•
• ACC = Accumulator
IR = Instruction Register
PC = Program Counter
899 SP = Stack Pointer
900 R
O
M
999
Figure 1: TINY Machine Architecture
TINY Instruction Cycle
When a TINY program is loaded for execution, the instructions are loaded into memory and the PC is set to 000. The CPU then initiates an instruction cycle. The TINY instruction cycle consists of 3 steps that are repeated in an endless loop.
1. Fetch next instruction as indicated by the PC and load it into the IR. ( IR f c(PC) )
2. Increment the PC by 1. ( PC f PC + 1 )
3. Decode and Execute the instruction. (See Appendix A)
There are several conclusions we can draw from this instruction cycle.
First, the CPU makes the assumption that the PC is pointing to an instruction. If, due to some mistake, the PC is not pointing to an instruction, the CPU will blindly load the data and attempt to decode and execute the “next instruction”. If by chance the data is equivalent to some valid instruction, the CPU will carry out the bogus instruction, which may lead to unpredictable results. As far as the CPU is concerned, there is no inherent difference between an instruction and data. It is up to the programmer to insure that the Program Counter references only valid instructions.
A second conclusion that can be gleaned from considering the instruction cycle is that, if not altered by some instruction, the CPU will attempt to execute an instruction from each byte of memory in sequence from address 000 to 999. Thus, the CPU assumes that the instructions are arranged in a sequence. If the programmer needs to program a loop or branch, a special instruction will be needed.
A third conclusion from the TINY instruction cycle is that all TINY programs must begin at address 000. This is one of the limitations of the TINY architecture. No provision has been made to allow you to load a program at an arbitrary address and then set the PC accordingly. All TINY programs must be loaded with the first instruction stored at address 000.
Finally, unless a STOP instruction is used, the CPU will execute forever (or at least until an invalid instruction is fetched or the PC points to an invalid address).
A TINY machine instruction consists of 5 decimal digits that are grouped into two parts - the operation code (also known as the opcode) and the operand. The first 2 digits of an instruction are used to represent the opcode, while the last 3 digits are used for the operand. The opcode is a number that defines the instruction that should be carried out. For instance the opcode for a LOAD instruction (moves data from memory to the ACC register) is the number 01. The operand supplies a value that will be needed by the instruction. For example, if a programmer wanted to load a number from memory address 567, the instruction needed is 01567.
The opcodes for all of the TINY instructions are listed in Appendix A. Using Appendix A, what is accomplished by the following program segment?
01567
06567
04567
00000
The letter codes for each opcode are called mnemonics. A mnemonic is a word or pseudo-word used as a memory aid. It is easier for programmers to remember that MUL is the multiply instruction than it is to remember to use opcode number 08. It is the use of mnemonics that is the major difference between machine language and assembly language. Assembly language uses mnemonic instructions that have to be translated by a program called an assembler into the actual machine code that used by the CPU. Such an assembler program is available for TINY. Let's consider some of the instructions individually.
As we learned while looking at the instruction cycle, the STOP instruction is very important. Without it, the CPU will continue to fetch and execute instructions even if the values fetched are actually data. The STOP instruction ignores the value stored in the operand portion of the machine instruction. Thus, not only is 00000 decoded as the STOP instruction, but also so is any other value that has an opcode equal to 00 (such as 00123).
Since many data items have a value less than or equal to 00999, if the CPU does fetch a data byte by mistake it may be interpreted as a STOP statement and the program will terminate (i.e. return control to the computer’s operating system). Even so, a programmer should ensure that their programs always contain an explicit STOP instruction. To avoid confusion, the STOP instruction is usually written with an operand equal to 000; that is, TINY programmers should use 00000 as the STOP instruction.
The LD (load) and ST (store) instructions copy data between a designated memory location and the ACC register. The Load instruction copies data from a memory location to the ACC; while the Store instruction copies data from the ACC back to a memory location. Both of these instructions use the operand to specify the address of the desired memory byte. For example, the instruction 01234, would copy the value stored at address 234 to the ACC register. The instruction 04321 would copy the value in the ACC register to the memory location at the address 321.
An aside: When TINY copies data from one location to another, it adheres to a generally accepted rule that most computers follow when copying data. The copy procedure is an example of “non-destructive read - destructive write” operation. In other words, the original data is left undisturbed at the source location; however, any data that was stored at the destination location is replaced by the data that is copied from the source.
The LDA (load address) instruction is not the same as the LD instruction. Instead of loading the contents of memory at the specified address into the ACC, it loads the address itself. Thus, this instruction doesn't need to access memory at all. The operand is simply copied from the IR to the ACC register. Since the operand contains only 3 digits, two zeros are added to the operand before it is written to the ACC register. For example the instruction, 03567 will load the value 00567 into the ACC register. This instruction will be used in connection with two of the ROM routines to be explained later.
The arithmetic instructions - ADD, SUB, MUL, and DIV - all use the ACC register as well as a value stored at a memory location. The first value to be added (or subtracted, or multiplied, or divided) should be loaded into the accumulator first. The second value is then specified in the arithmetic instruction itself. For example, the instruction 06123 specifies that the value stored at address 123 should be added to the value that is already in the ACC register. The TINY architecture only supports integer values; floating-point values cannot be represented. Consequently, when dividing, the result must be an integer. Any fractional result will be truncated (e.g. 5 divided by 2 is 2). The following program segment subtracts the value located at address 576 from the value at address 575 and stores the result at address 577:
01575
07576
04577
This program segment is analogous to the C++ statement: z = x – y, where x is a variable name that represents the address 575, y represents address 576, and z represents the address 577.
The IN and OUT instructions allow the CPU to communicate with the outside world. The IN instruction will cause the CPU to pause and wait for one character to be received from the keyboard. When that character is received, its ASCII code is stored in the ACC and the CPU proceeds with the next instruction. The OUT instruction sends the value that is in the ACC to the output device (terminal screen). The terminal screen interprets all values as an ASCII code. Thus, if the ACC register contains the value 00065 when the OUT instruction is executed, the terminal screen will display the letter ‘A’. (An ASCII chart may be found in Appendix B.)
Exercise #1: On paper write a TINY machine language program that writes the text “Hello World” on the terminal screen. You may assume that the ASCII codes for the letters “Hello World” are stored in the memory addresses 100 through 110. That is, the memory location at address 100 contains the value 00072 (ASCII code for ‘H’); address 101 contains 00101 (‘e’), and so on. The space found between the words “Hello” and “World” is represented by the ASCII code 00032, which is stored at address 105. Remember that the first instruction in your TINY program must be stored at address 000.
The branch instructions are used to alter the contents of the PC register. Referring to the instruction cycle, you will note that the PC is incremented BEFORE each instruction is executed; thus, if an instruction alters the PC, the normal sequence of instructions is altered. The B (unconditional branch) instruction simply copies the operand to the PC; thus the next instruction to be fetched will come from this new address. The other branch instructions - BP, BN, and BZ - will first test the value in the ACC. If the test returns a true condition, the operand of the branch instruction is copied to the PC. If the test returns a false condition, the PC is not altered, and execution continues with the next instruction. For example the instruction 13100 will test the value in the ACC. If the ACC contains a value greater than 0, then the operand, 100, is copied to the PC register; otherwise, this instruction takes no action.
Because the IN and OUT instructions are so tedious to use for normal input and output, four callable subroutines have been made available in READ ONLY MEMORY (ROM). These subroutines are called PRINTN, PRINTC, INPUTN, & INPUTC. To use these routines you must use the JSB (Jump to subroutine) instruction. The address for each subroutine is listed in Appendix A; for instance, the INPUTN routine begins at address 950. Using the JSB opcode (16), the following program segment accepts a number from the keyboard, calculates its square, and prints the result on the screen:
16950 ß Jump to subroutine named INPUTN to read in a number
04100 ß Save the number received at address 100
01100 ß Re-load the ACC with the number stored at address 100
08100 ß Multiply the number stored in the ACC by the number stored at address 100
16900 ß Jump to subroutine named PRINTN to display the answer
You should now be ready to write some of your own programs on paper using TINY code.
Exercise #2: On paper write a TINY machine program that will read 3 integers from the keyboard and then display their product on the screen.
Exercise #3: On paper write a TINY machine program that will print the positive integers beginning with 1 onto the screen one per line. You may assume that address 100 contains the value 00001, address 101 contains the value 00013, and address 102 contains the value 00010. The program should loop indefinitely (or least until the integers exceed the maximum value allowed by TINY.)
Let's take a look at the TINY machine in action. The VAX DCL command: $ RUNTINY (RUNT for short) invokes the simulated machine. After we have written some TINY assembler programs, we will use the command:
$
RUNTINY program_name
and specify the filename of the program; however, for now let's use the simulator without a pre-written program. The keyword MEMORY may be used in place of a filename. This option allows TINY to start-up with nothing but random contents in its memory, and allows you to examine the architecture of the machine.
Follow along on the computer while you read this section.
Step 1: While at the dollar sign prompt (‘$’), type the command: $ RUNTINY MEMORY
The keyword MEMORY invokes the DEBUG option available with TINY. The DEBUG option causes the CPU to pause after an instruction has been fetched from memory and before the PC register is incremented by one. The value within each of the four registers is displayed; in addition, if it can be decoded, the mnemonic representation of the contents of the IR register is also displayed. The CPU will wait until you press a key to initiate some action. Pressing the 'H' key will display a help message:
Step 2: Press the H key.
The commands available while in DEBUG mode are:
<RETURN> Proceed with execution of current instruction
B Breakpoint Set or Clear
D Deposit new value into Memory
E Examine contents of Memory
G GO - continue without 'Single-Stepping'
H Print this help message
J Jump to new address (and fetch new instruction)
L Labels - Display symbol table
Q Quit (Stop execution)
S Display stack
W Write memory contents to disk
As an example, if we wanted to load the program listed in the previous section of this guide, we would use the D(eposit) command to deposit the correct values into memory and then use the J(ump) command to jump to the start address (00000).
Step 3: Enter the program from the previous section. The following dialog illustrates the procedure:
1. Press the D key
2. Specify 000 as the desired start address.
3. Enter the value 16950.
4. Enter the value 04100. (Notice that TINY anticipates another deposit).
5. Enter the value 01100.
6. Enter the value 08100.
7. Enter the value 16900.
8. Enter the value 00000.
9. Press RETURN to exit the DEPOSIT mode.
10. Press the J key and specify 000 as the desired address.
You may now step through the program one step at a time by pressing the <RETURN> key, or you could use the G(o) command to run the program to its completion.
The E(xamine) command can be used to examine a range of memory locations. When you press the E key, you will be asked to specify an address. If you specify one address, TINY will display the contents of that one location; however, if you specify a range of addresses (using the format: xxx:yyy), TINY will display a memory ‘dump’ that includes the range specified. You will notice that a dump display includes 8 values on a line with the address of the first location printed in the left-most column. On the right side of display will appear the characters that correspond to the 8 values if those values are valid ASCII codes. You should find the E(xamine) command helpful when attempting to correct stubborn errors.
The B(reakpoint) command is used in conjunction with the Go command to allow you to quickly skip over sections of a program in order to examine a different section of code. For instance, if we had a subroutine located at address 250 that was not executing properly, we would use the B command to set a breakpoint at address 250 and then press the G key to let the program proceed as normal. When execution reached address 250, TINY will halt execution and return to DEBUG mode to allow you to ‘single-step’ through the subroutine and examine values in the registers and memory. The B command can also be used to clear a breakpoint when it is no longer needed.
Exercise #4: Using the MEMORY option of RUNTINY, enter in the programs you wrote for exercise 2 and 3 and test it to see if they run correctly. Use the D option to enter the program instructions beginning at address 000. Don't forget that the second program will require you to deposit data values into addresses 100, 101, and 102 as well. When each of the programs works correctly, use the W option to save the program in a file.
TINY Assembler
Writing machine code can be quite laborious, even when the machine is as simple as TINY. There are two major problems with programming in machine code. First, working with numeric op-codes is difficult. It is easy to forget the numeric values associated with each op-code. The second problem has to deal with the actual memory addresses when one wants to temporarily store a value or branch to the top of a loop. It would be much easier to program a computer if the programmer could use the mnemonic representations for the op-codes as well as variable names and labels for the memory locations that need to be accessed. Assembly language provides these two facilities.
Assembly language instructions are written using the following format:
LABEL: OPCODE OPERAND ;COMMENT
Each of the four fields of an instruction is optional (depending upon the intent of the instruction). In fact, a blank line is interpreted as an assembly language instruction where all four fields are missing. Although we usually include spaces or tabs to separate the 4 fields of an assembly language instruction, the only spacing required is that found between the OPCODE and the OPERAND.
The label field is used to give a particular address in memory a name. These names are also called symbols. For instance, the first instruction in a loop may contain the label ‘WHILE’; and this name, rather than the numerical address, is referenced by the branch instruction that returns control to the beginning of the loop (e.g. “B WHILE”). Data variables are another example of the use of labels. The address of a variable may be assigned a symbol so that the programmer can refer to the symbol rather than the numeric address. Each label in a program must be unique. It is also important that the label field be terminated with a colon. (Note: This means that a label cannot itself contain a colon.)
The opcode field is where you will place the mnemonic that represents the instruction desired. One or more spaces and/or tabs must separate the opcode from the operand. If the opcode field is missing, the operand field must be missing as well. If there is no opcode present, the line will not generate any machine code. If a label is present on a line with no opcode, the label refers to the address of the next opcode in the program.
The operand field’s presence is determined by the opcode used. Some opcodes require an operand (e.g. LD, ADD, B, etc.) while others do not (e.g. STOP, RSB, PUSH, etc.). The operand must be a symbol that matches some label within the program.
The comment field is available so that you may provide internal documentation. It is recommended that you document each group of instructions that correspond to one high-level command or pseudo-code instruction. You should also preface your programs with comments that provide the following information:
;****************************************
;*Program Name: SAMPLE *
;*Author: John Doe *
;*Date Due:
;*Assignment: Lab 0 *
;* *
;*Description: This program doesn't *
;* do very much. *
;****************************************
Finally, a section of code that is logically separate from other sections of code needs to be set off by comments. For instance, subroutines should be prefaced with documentation that describes the routine.
To write a program, use any editor to create a file with an extension of .TNY, insert the assembly language instructions, and exit. The VAX DCL command
$ TINY filename
will assemble the program. The machine code will be stored in a file with the extension .TNC. A listing will also be created (the file extension is .LIS). You should review the listing as it displays the machine code created as well as the source text and error messages. A symbol table is also created.
Let's create a sample TINY program:
Step 1. Using $ EDT, create a file named SAMPLE.TNY that contains the instructions listed in figure 2 below.
Step 2. Assemble the program using the command: $ TINY SAMPLE
Step 3. Examine the listing, SAMPLE.LIS, as well as the machine code program, SAMPLE.TNC.
Exercise #5: Write a TINY assembly language program that will prompt for and read 3 integers from the keyboard. The program should then display the average of the 3 integers on the screen with an appropriate message.
(See sample run below.)
Sample Run:
SCORE?
75
SCORE?
87
SCORE?
92
AVERAGE
= 84
;******************************************************
;*Program
Name: SAMPLE *
;*Author: John Doe *
;*Date
Due: Sept. 2002 *
;*Assignment: Lab 0 *
;*
*
;*Description: This program accepts an integer and *
;* displays its square + 1. *
;******************************************************
main: ; void main () {
JSB INPUTN ; cin >> i;
ST i
LD i ; cout << (i * i + 1);
MUL i
ADD #1
JSB PRINTN
STOP ; }
;
CONSTANTS
#1: DB 1
;
VARIABLES
i: DS 1 ; int i;
Sample TINY
Program
Figure 2
TINY CONSTANTS
There are two TINY assembler pseudo operations (pseudo-ops for short) that allow the programmer to store constant data values in memory at “compile-time” (before execution).
The first pseudo-op is "DC", which stands for "Define Character". DC is used to enter a string of ASCII text into memory. For example the command: STR1: DC 'SCORE? '
assigns the ASCII code for each character in the string “SCORE? ” into memory at consecutive addresses beginning at the address labeled STR1. Assuming that STR1 is a label for address 100, then the previous command would cause the following values to be stored in TINY’s memory:
Address 100 101 102 103 104 105 106
Contents 00083 00067 00079 00082 00069 00063 00032
The second pseudo-operation that is used to enter constant integer values is "DB". Define Byte places a numeric value at the current address. For example: #1: DB 1
places the value 00001 in memory at the address specified by the label “#1”.
The ROM routine, PRINTC, requires a string that is zero-terminated. For example, if it is desired that the string “SCORE? ” be displayed, then the following pseudo-ops are needed:
STR1: DC 'SCORE?
'
DB 0
Given the two declarations above, the instructions needed to display the string would then be:
LDA STR1 ;
cout << "SCORE? ";
JSB PRINTC
In order to allocate space for a variable, the assembler pseudo-operation, "DS" should be used. Define Storage allocates memory but does not assign an initial value. For example, the command:
i: DS 1 is equivalent to the C++ command: int i;
However, C++ variable declarations are sometimes used to give a variable an initial value. An example would be the C++ statement: int i = 0; The equivalent declarations in TINY would use the DB pseudo-op instead:
i: DB 0.
In order to ease the transition from high-level languages to assembly language, let’s look at some examples of TINY assembly code that implement some high-level program constructs. In the following templates, "_n" is used to indicate that a number is to be used to ensure that a symbol is unique, "b?" is used to represent any of the conditional branch instructions (ie. bz, bn, or bp)
IF_n:
<instructions to evaluate
operands to be tested>
b? THEN_n
b
ENDIF_n
THEN_n:
<instructions to perform>
ENDIF_n:
For example, suppose we wanted to write the code equivalent to the following pseudo-code:
int x, max;
...
if (x > max) {
max = x;
}
We would write the TINY code as follows:
IF_1: ;
if (x > max) {
ld x
sub max
bp THEN_1
b END_IF_1
THEN_1:
ld x ;
max = x;
st
max
ENDIF_1: ;
}
...
; VARIABLES
x: ds 1 ; int x, max;
max: ds 1
The IF-THEN-ELSE construct
IF_n:
<instructions to evaluate
operands to be tested>
b? THEN_n
b ELSE_n
THEN_n:
<instructions to perform>
b ENDIF_n ;WARNING - This instruction is a must!
ELSE_n:
<alternate instructions to
perform>
ENDIF_n:
As an example of the use of the IF-THEN-ELSE construct, we'll implement the algorithm that computes abs(x), the absolute value function.
Pseudo-Code:
int x, abs;
...
if
(x >= 0) {
abs = x;
}
else {
abs = -x;
}
TINY:
IF_2: ;
if (x >= 0) {
ld x
bp THEN_2
bz THEN_2
b ELSE_2
THEN_2:
ld x ;
abs = x;
st abs
b ENDIF_2 ; }
ELSE_2: ;
else {
ld #0 ;
abs = -x;
sub x
st abs
ENDIF_2: ;
}
...
; CONSTANTS
#0: db 0
; VARIABLES
x: ds 1 ;
int x, abs;
abs: ds 1
The WHILE construct
WHILE_n:
<instructions to evaluate
operands to be tested>
b?
WHILE_BEGIN_n
b
WHILE_END_n
WHILE_BEGIN_n:
<instructions for body of
loop>
b
WHILE_n
WHILE_END_n:
As an example, suppose we need a routine that repeatedly reads 2 integers from the keyboard and, while they are not both zero, it calculates their sum.
cin >> a;
cin >> b;
while ( !(a==0 && b==0) ) {
cout << a+b << endl;
cin >> a;
cin >> b;
}
TINY Implementation:
jsb inputn ; cin >> a;
st a
jsb inputn ; cin >> b;
st b
WHILE_4: ;
while ( !(a==0 && b==0) )
ld a
bz AND_4
b WHILE_BEGIN_4
AND_4:
ld b
bz WHILE_END_4
b WHILE_BEGIN_4
WHILE_BEGIN_4: ; {
ld a ;
cout << a+b << endl;
add b
jsb printn
lda endl
jsb printc
jsb inputn ;
cin >> a;
st a
jsb inputn ;
cin >> b;
st b
b WHILE_4 ; }
WHILE_END_4:
...
;CONSTANTS
endl: db 13
db 10
db 0
; VARIABLES
a: ds 1 ;
int a;
b: ds 1 ; int b;
The DO-WHILE construct
DO_n:
<instructions for body of
loop>
WHILE_n: <instructions to evaluate
operands to be tested>
b? DO_n
END_DO_WHILE_n:
An example of the use of the DO-WHILE construct is summing a series of terms like: 1 + 2 + 3 + 4 + 5 + ... + N until the sum exceeds 100.
Pseudo-Code:
int sum, n;
...
sum = 0;
n
= 0;
do {
n++ ;
sum = sum + n;
} while (sum <= 100);
TINY:
ld #0 ; sum = 0;
st sum
ld #0 ; n = 0;
st n
DO_5: ;
DO {
ld n ;
n++ ;
add #1
st n
ld sum ;
sum = sum + n;
add n
st sum
WHILE_5:
ld sum ; } While (sum <= 100);
sub #100
bn DO_5
bz DO_5
END_DO_WHILE_5:
;CONSTANTS
#0: db 0
#1: db 1
#100: db 100
; VARIABLES
sum: ds 1 ;
int sum, n;
n: ds 1
The FOR construct
FOR_n:
ld Initial_Value
st i ; i is the counting variable
FOR_TEST_n:
ld i ; Test i to
see if we are done...
sub Target_Value
bp END_FOR_n
<instructions for body of
loop>
ld i ; Increment i
for next iteration.
add #1
st i
b FOR_TEST_n
END_FOR_n:
As an example, suppose that you needed to input and sum 200 integers:
int sum, i, j;
...
sum = 0;
for (i = 1; i <= 200; i++) {
cin >> j;
sum = sum + j;
}
TINY:
ld #0 ; sum = 0;
st sum
FOR_06: ;
for (i = 1; i <= 200; i++) {
ld #1
st i
FOR_TEST_06:
ld i
sub #200
bp END_FOR_06
jsb inputn ;
cin >> j;
st j
ld sum ;
sum = sum + j;
add j
st sum
ld i ; {
add #1
st i
b FOR_TEST_06
END_FOR_06:
. . . .
; CONSTANTS
#0:
db 0
#1:
db 1
#200:
db 200
; VARIABLES
sum: ds 1 ; int sum, i, j;
i: ds 1
j: ds 1