|
|
c m s c 311
s u m m e r 2 0 0 2 |
Due Date Sunday, July 7, 11:59 PM (just before midnight)
Posted: June 27, 2002
Additions
Normally, both the data and the text is stored in memory. However, for this project, you can store the assembly lanugage portion in whatever data structure you want. The data portion will be stored in a simulated memory (basically, an array of characters or bytes).
In Project #3 (the next project), you will begin to store the text section in a binary format, as well.
Please note that *all* programming projects in this course (including this one) are to be done independently or with the assistance of the instructional staff of this course only, unless otherwise specified IN PRINT by official webpages.
Please review the policies outlined on the class syllabus concerning the use of class computer accounts and concerning the University's Code of Academic Integrity. The instructors of this course will review the programs submitted by students for potential violations of the Code of Academic Integrity and if it is believed that a violation has occurred it will be referred to the Office of Judicial Programs and the Student Honor Council.
Hardcoding is considered a violation of academic integrity
which takes the same arguments as the command line:
This will now read in the contents of a file and load the data portion into memory (as well, as load the program into some data structure).
You should now have some error-checking for the file.
If the file does not exist, print:
File [<file name>] does not exist. Please try again!
where <file name> is the name of the file
entered using the -f command. This error message
should also be printed if read in from the command line,
and the file doesn't exist.
which quits out of the program
In addition to these commands, you should handle the following commands.
This will reinitialize memory and registers. Initialization is explained in the next section.
This reloads the data portion of the current program (but doesn't run the program).
If the program successfully reloads, print:
Data segment of [<filename>] successfully reloaded.
If no program has been loaded, the print the following.
Data segment fails to reload. Please load a program.
This runs the current program until completion. Note: this does not reload the data portion. It merely runs the program. If you are currently stepping through a program, it completes the rest of the program. If you are not stepping through the program, then it starts at the beginning.
This takes an integer argument (call it N and
assume N is greater than or equal to 1). This runs N steps of the
program. You should print out the following, before executing each
step.
Running instruction: [<instruction>] which is instruction <num>
where <instruction> is the instruction being run (without
comments), and <num> is the instruction you are on (this is the
value of the program counter, i.e., the index of the instruction).
The instruction begins indexing at 0.
If this function produces output, then print the output.
If the program completes before N steps, just stop. A program completes if there is a system call to halt the program.
This resets the program counter back to the first instruction. If the user calls the following three commands in order: reload, reset pc, and run, they can rerun the last loaded program.
This takes either an integer (from 0 to 31), or a range (written as start-end, as argument, where start<=end and both are base 10 between 0 and 31, inclusive). If it's a range, it prints the contents of the register in base 10 (signed) and in hexadecimal (unsigned).
For example, if the user typed:
view registers 10-12
Then, the output the contents of registers 10, 11, and 12. You can assume that the registers ranges are valid (the first register number is always less than or equal to the first, and that both register numbers are between 0 and 31, inclusive).
The output format looks like:
Reg[ 10 ] = -1 0xFFFFFFFF
Reg[ 11 ] = 12 0x0000000C
Reg[ 12 ] = 7 0x00000007
This prints the signed base 10 value, followed by the unsigned
hexadecimal value. For extra credit, if the value is less than 128,
then print out the character using single quotes. This should
be printed as in last project (and printed after the hexadecimal
value above).
This takes either an integer (written in unsigned base 10, or in hex with a leading 0x), which represents an address, or a range. The range has the same format as the registers. You may assume that if a range occurs, both values are in unsigned base 10, or in unsigned hex (with a leading 0x).
Take the address and divide by 4 (using integer division) and multiply by 4. Essentially, this will produce a word-aligned address. Thus, if the user wants to see address 10-23, you will actually show the contents of addresses 8-20 (which is hex 0008 up to hex 0014). However, you will display word quanties.
For example, if the input is
view memory 10-23
Then, the output is:
0008: 00 00 00 ff (255)
000C: 00 00 00 01 (1)
0010: FF FF FF FF (-1)
0014: FF FF FF FE (-2)
In other words, you will print the address (in hex), plus a colon,
plus 4 bytes, as 2 hex digits, separated by a space. If addr
is the address, then the 4 bytes should be the contents of address,
addr, addr + 1, addr + 2, and addr + 3.
Then, after printing the 4 bytes, print the value of the word in memory as a signed int. This should be printed based on the endian-ness of the simulated memory (which is big-endian).
This takes the same arguments as view memory. However, this time, you print the addresses as specified, and list out the characters.
For example, if you are asked to print address 10-15, you will
print all 6 characters, 4 characters to a line.
000A: 'h' 'o' 'w' 'd'
000E: 'y' '\0'
Notice that, unlike view memory, you print out at any
start address.
Follow the same rules as in P1. Print out characters in single quotes. Exceptions will be '\0' (printing the null character), and printing '\n', and print space using just underscore, _.
This makes it easier to see if characters are stored correctly in memory.
===> (1) You entered: [quit]
followed by the appropriate output for that command. Commands
should start numbering at 0.
*** INVALID COMMAND [<command>]Note: invalid commands may have bogus arguments too. <command> should include the command and the arguments too.
The Memory object should store int values in big-endian order. (OIT stores in little-endian).
Create in an instance of RegisterFile. This will represent the 32 registers, which will just be an array of 32 signed int values.
If a program has been loaded (from command line or through a load), you should set the program counter to the first instruction. Instructions should be saved in some sort of structure, say, a vector. You may wish to create a Program object, which stores a vector of Instruction objects and a list of Label and their corresponding instruction/address.
The program should be ready to run, with the program counter (which is just an index). You may wish to make the program counter part of the Program object.
As a suggestion, you may want to have a step() method, and a run() for Program.
Also, Program should contain pointers or references to Memory and RegisterFile, so it can see and modify them. In particular, the constructor should take them as arguments and save them as data members.
Here's what you need to do:
Furthermore:
Here's a way to handle it using C/C++, assuming you use
a character array to represent memory.
int val = -10 ; // want to store this in memory
// Woops, HTML inteprets < and > as tags, so this
// didnt show up properly...
char * ptr = reinterpret_cast<char *>( & val ) ;
// now store at address 1003
mem[ 1003 ] = *ptr ;
ptr++ ;
// now store at address 1002
mem[ 1002 ] = *ptr ;
ptr++ ;
// now store at address 1001
mem[ 1001 ] = *ptr ;
ptr++ ;
// now store at address 1001
mem[ 1000 ] = *ptr ;
Here's what's happening. You are putting a character pointer
to the address of an int value. An int value should be 4 bytes
(it is on the OIT class cluster). The reason you store the
first byte in 1003 instead of 1000 is because the OIT machines
are little endian, so we are converting it to big endian.
Thus, ptr initially points to the least significant byte,
and now it's store in the most significant byte.
In Java, the technique is a little more complicated, but not that bad. In Java, you should be able to do two operations. First, you can cast an int to a byte. This SHOULD truncate the upper bits, leaving only the lower bits.
Then, you can use the bitshift operator >> to shift by 8 bits to the right, and again, cast to a byte. Do this two more times, and you have all 4 bytes. Again, since you are getting the bytes starting at the least significant end, you will need to save this to memory starting at address + 3 and working down to address where address stores the first byte (the most significant byte).
At this point, you can run the program.
| Command | Explanation |
| add $rd, $rs, $rt | Reg[ $rd ] = Reg[ $rs ] + Reg[ $rt ] |
| addi $rd, $rs, immed | $rd = $rs + immed where immed is 16 bit 2-complement, sign-extended |
| sub $rd, $rs, $rt | Reg[ $rd ] = Reg[ $rs ] - Reg[ $rt ] |
| and $rd, $rs, $rt | Reg[ $rd ] = Reg[ $rs ] & Reg[ $rt ] (bitwise and) |
| andi $rd, $rs, immed | Reg[ $rd ] = Reg[ $rs ] & immed where immed is 16 bit 2-complement, sign extended |
| or $rd, $rs, $rt | Reg[ $rd ] = Reg[ $rs ] | Reg[ $rt ] (bitwise or) |
| ori $rd, $rs, immed | $rd = $rs | immed where immed is 16 bit 2-complement, zero extended |
| xor $rd, $rs, $rt | $rd = $rs ^ $rt (bitwise xor) |
| xori $rd, $rs, immed | $rd = $rs & immed where immed is 16 bit 2-complement, zero extended |
| andi $rd, $rs, immed | $rd = $rs & immed where immed is 16 bit 2-complement, zero extended |
| beq $rd, $rs, LABEL | Jump to LABEL if Reg[ $rd ] == Reg[ $rs ], otherwise next instruction |
| bne $rd, $rs, LABEL | Jump to LABEL if Reg[ $rd ] != Reg[ $rs ], otherwise next instruction |
| slt $rd, $rs, $rt | If Reg[ $rs ] < Reg[ $rt ], then Reg[ $rd ] = 1 else Reg [ $rd ] = 0. |
| slti $rd, $rs, immed | (CORRECTION: slt was replaced with slti on 5/13) If Reg[ $rs ] < immed then Reg[ $rd ] = 1 else Reg$[ rd ] = 0. |
| jr $rd | Jump to absolute address stored in $rd |
| j LABEL | Jump to LABEL |
| jal LABEL | Jump to LABEL, storing address after this instruction in $r31 |
| lw $rt, offset($rs) | Compute address by Reg[ s ] + offset (sign extended offset) and load word to Reg[ t ]. |
| lbu $rt, offset($rs) | Compute address by Reg[ s ] + offset (sign extended offset) and load byte to Reg[ t ] (upper 24 bits are 0). |
| sw $rt, offset($rs) | Compute address by Reg[ s ] + offset (sign extended offset) and store word from Reg[ t ] to that address. |
| sb $rt, offset($rs) | Compute address by Reg[ s ] + offset (sign extended offset) and Stores least significant byte of Reg[ t ] to that address. |
| lui $rd, immed | Load upper 16 bits of $rd with immed and lower 16 bits with 0. |
| bgtz $rs, LABEL | If Reg[ s ] > 0, jump to LABEL, otherwise next instruction. |
| bgez $rs, LABEL | If Reg[ s ] >= 0, jump to LABEL, otherwise next instruction. |
| bltz $rs, LABEL | If Reg[ s ] < 0, jump to LABEL, otherwise next instruction. |
| blez $rs, LABEL | If Reg[ s ] <= 0, jump to LABEL, otherwise next instruction. |
A hardware interrupt usually occurs from an external device such as a mouse, keyboard, printer, etc. One mechanism for handling, say, mouse movements is called polling. This is where the CPU repeatedly checks to see if the mouse is doing anything. This can be wasteful use of CPU resources if the mouse isn't doing anything.
The other approach is to use hardware interrupts. This is an external signal (for example, a 0 to 1 transition) to the CPU, indicating an interrupt has occurred. Typically, the CPU completes the current instruction it is working on, then handles the interrupt.
The interrupt typically saves registers on the stack (although this isn't absolutely necessary), determines what interrupt has occurred (each interrupt has a special number), then runs the appropriate interrupt handling routine.
Once the interrupt handling routine is done, the program that was interrupted is restarted. The interrupt handling routine needs to be "programmed" ahead of time and can be altered to use routines that the user has developed (if the user is "trusted").
A hardware interrupt is similar to a function call, but is less predicatable.
An interrupt can also be used like a function call. Instead of the interrupt occurring due to a device signalling the CPU, an assembly language program uses special instructions to tell the operating system to run a special routine.
This mechanism can also be used like a function call. In the old days, IBM had something called BIOS which were a series of routines stored in, say, ROM. You invoked the routines using software interrupts.
The software interrupt is much closer to a function call. It uses a similar technique to invoke the interrupt handling routine, and then the routine runs under "supervisor" or "kernel" mode, which is a privileged mode of operation.
You need to support the following software interrupts.
Print without any spaces or newlines except where indicated (i.e., in strings or for the debug).
Print the contents of register N as an signed int. Assume that it's a valid register number.
Print the contents of register N as an unsigned hex (e.g. 0xFFFF). Assume that it's a valid register number. Prints left to right.
Print the contents of register N as a character (using only the bottom 8 bits). This time, print out the character. So, if the register contains "\n", actually print out the newline character.
Print the contents at memory address addr as an signed int. Assume that it's word-aligned memory. (Can print error message if not). addr can be base 10 or hex.
Print the contents at memory address addr as unsigned hex. Assume that it's word-aligned memory. (Can print error message if not). addr can be base 10 or hex.
Print the contents at memory address addr as a char. It does not have to be word-aligned. This time, print out the character. So, if the register contains "\n", actually print out the newline character.
Print the contents at memory address addr as a string. This should print character by character, until you reach a null character (which should not be printed), at which point you stop.
Print the register contents from low to high inclusive. Prints same output as view registers.
Read a signed integer into register N. Use scanf or cin to read in the value.
Read a character into register N. Use scanf or cin to read in the value.
Read a character into memory address addr. Use scanf or cin to read in the value. This will skip white space, and read in non-whitespace word (similar to %s or cin into a string variable).
|
See the class syllabus for policies concerning email Last Modified: Sun Jun 30 23:08:48 EDT 2002 |
|
|
|
|
|