In this assignment, you will write a C++ program to simulate a computer system memory. This is the first of a multi-part assignment concluding with a simple computing machine capable of executing real programs compiled with gcc. The purpose is to gain an understanding of a machine, its instruction set and how its features are used by realistic programs written in C/C++.
1 Problem Description
To simulate a computer system’s memory, create a class to represent a memory whose size is defined at run-time.
Your memory will include utility member functions to load it by reading a binary file, print its contents with a hex dump, a method to determine if a given address is legal, and methods that allow a caller to read or write 8, 16 and 32-bit values from (or to) any legal address.
Your program will accept parameters from the command line, read data from a file and print all of its non-error output to standard out (aka stdout) via cout and usage or file loading error messages to standard error (aka stderr) via cerr. (No other output may be printed to stderr. Note that check_address() warnings are not errors and must be written to stdout.)
2 Files You Must Write
You will write a C++ program suitable for execution on hopper.cs.niu.edu (or turing.cs.niu.edu.)
Your source files MUST be named exactly as shown below or they will fail to compile and you will receive zero points for this assignment.
Create a directory named a3 and place within it the source files defined below.
main.cpp Your main() and usage() function definitions will go here.
hex.h The declarations of your hex formatting functions will go here. hex.cpp The definitions of your hex formatting functions will go here. memory.h The definition of your memory class will go here.
memory.cpp The memory class member function definitions will go here.
2.1 main.cpp
You must provide a main() function that is implemented like this (plus appropriate documentation):
main() Function Structure
1 |
int |
main( int argc , char ** argv) |
||
2 |
{ |
|||
3 |
if ( argc != 3) |
|||
4 |
usage (); |
|||
5 |
||||
6 |
memory mem ( stoul( argv [1], 0 , 16)); |
|||
7 |
mem . dump (); |
|||
8 |
||||
9 |
if (! mem . load_file( argv [2])) |
|||
10 |
usage (); |
|||
11 |
mem . dump (); |
|||
12 |
||||
13 |
cout |
<< |
mem . get_size () << endl; |
|
14 |
cout |
<< |
hex32 ( mem . get8 (0)) << endl; |
|
15 |
cout |
<< |
hex32 ( mem . get16 (0)) << endl; |
|
16 |
cout |
<< |
hex32 ( mem . get32 (0)) << endl; |
|
17 |
cout |
<< |
hex0x32 ( mem . get8 (0)) << endl; |
|
18 |
cout |
<< |
hex0x32 ( mem . get16 (0)) << endl; |
|
19 |
cout |
<< |
hex0x32 ( mem . get32 (0)) << endl; |
|
20 |
cout |
<< |
hex8 ( mem . get8 (0)) << endl; |
|
21 |
cout |
<< |
hex8 ( mem . get16 (0)) << endl; |
|
22 |
cout |
<< |
hex8 ( mem . get32 (0)) << endl; |
|
23 |
||||
24 |
cout |
<< |
hex0x32 ( mem . get32 (0 x1000 )) << endl; |
|
25 |
||||
26 |
mem . set8 (0 x10 , 0 x12 ); |
|||
27 |
mem . set16 (0 x14 , 0 x1234 ); |
|||
28 |
mem . set32 (0 x18 , 0 x87654321 ); |
|||
29 |
mem . dump (); |
|||
30 |
||||
31 |
return 0; |
|||
32 |
} |
|||
Your usage() function must print an appropriate error message and terminate the program in the traditional manner:
https://en.wikipedia.org/wiki/Usage_message
h and hex.cpp
Your hex.cpp file will contain the implementation of the three functions.
std::string hex8(uint8_t i)
This function will return a std::string with exactly 2 hex digits representing the least significant 8 bits of the i argument.
std::string hex32(uint32_t i)
This function will return a std::string with 8 hex digits representing the 32 bits of the i
argument.
std::string hex0x32(uint32_t i)
This function will return a std::string beginning with 0x, followed by the 8 hex digits representing the 32 bits of the i argument. It must be implemented by creating a string by concatenating a 0x to the output of your hex32() function like this:
return std::string("0x")+hex32(i);
In other words, your program will NEVER format a printable 32-bit hex number anywhere in your application other than in your hex32() function.
This is one way to format an 8-bit integer into an 2-character hex string with leading zeros:
std::string hex8(uint8_t i)
{
std::ostringstream os;
os << std::hex << std::setfill(’0’) << std::setw(2) << static_cast(i); return os.str();
}
Note that the static_cast is necessary to prevent the insertion operator << from treating the 8-bit integer as a character and printing it incorrectly. The printing of other integer sizes does not have this problem.
memory(uint32_t siz);
Save the siz argument in the size member variable. Then allocate siz bytes for the mem
array and initialize every byte to 0xa5.
You may implement the following rounding logic to make the job of formatting and aligning your last line of output in your dump() method easier:
siz = (siz+15)&0xfffffff0; // round the length up mod-16
~memory();
In the destructor, free the memory that was allocated in the constructor to represent the simulated memory.
bool check_address(uint32_t i) const;
Return true if the given address is in your simulated memory. If the given address is not in your simulated memory then print a warning message like this:
WARNING: Address out of range: 0x00001000
and return false.
Obviously, formatting this warning message will involve using your hex0x32() function.
uint32_t get_size() const;
Return the (possibly rounded up) siz value.
uint8_t get8(uint32_t addr) const;
Check to see if the given addr is in your mem by calling check_address(). If addr is in the valid range, return the value of the byte from your simulated memory at that address. If addr is not in the valid range then return zero to the caller.
uint16_t get16(uint32_t addr) const;
This function must call your get8() function twice to get two bytes and then combine them in little-endian order to create a 16-bit return value. Because you are using your get8() function, the job of validating the addresses of the two bytes will be taken care of there. Do not redundantly check the validity in this function.
https://en.wikipedia.org/wiki/Endianness
uint32_t get32(uint32_t addr) const;
This function must call get16() function twice and combine the results in little-endian order similar to the implementation of get16().
void set8(uint32_t addr, uint8_t val);
This function will call check_address() to verify the the addr argument is valid. If addr is valid then set the byte in the simulated memory at that address to the given val. If addr is not valid then discard the data and return to the caller.
void set16(uint32_t addr, uint16_t val);
This function will call set8() twice to store the given val in little-endian order into the simulated memory starting at the address given in the addr argument.
void set32(uint32_t addr, uint32_t val);
This function will call set16() twice to store the given val in little-endian order into the simulated memory starting at the address given in the addr argument.
void dump() const;
Dump the entire contents of your simulated memory in hex with ASCII on the right exactly, space-for-space in the format shown in the output section below.
In order to format the ASCII part of the dump lines, use logic like this to determine if you are to show an ASCII character or a dot for those bytes that do not have a valid printable value:
uint8_t ch = get8(i);
ascii[i 16] = isprint(ch) ? ch : ’.’;
In this code fragment, the i variable is the address counter in the loop that is formatting and printing the memory bytes one at-a-time, ascii is a 17-byte character array that is used to collect the 16 print characters (plus the null-terminator) to display at the end of the current line of hex bytes, and isprint() is a standard C library function you can read about in the on line manual or google it.
bool load_file(const string &fname);
Open the file named fname in binary mode and read its contents into your simulated memory. You may open a file in binary mode like this:
ifstream infile(fname, ios::in|ios::binary);
If the file can not be opened, then print a suitable message to stderr including the name of the file and return false:
Can’t open file ’testdata’ for reading.
You must make certain that the file can fit into your memory! One simple way to do that is to read the file one byte at-a-time and check the byte address before you write to it by calling check_address(). If the address is valid, keep going. If the address is not valid, then print a suitable to stderr message like this, close the file and return false:
Program too big.
If the file loads OK then close the file and return true.
uint8_t *mem;
An array of bytes representing the simulated memory. Allocate it with the given size in your constructor and de-allocate it in the destructor.
uint32_t size;
Use this to retain the number of bytes that you allocated for mem. It will be needed to implement check_address() and determine how many bytes to display in dump().
3 Input
Your program will accept two arguments on the command line as shown in the main() code snipit above.
The first argument is a hex number representing the amount of memory to simulate. The second argument is the name of a file to load into the simulated memory.
4 Output
Your program’s output will be a dump of the simulated memory after it has been constructed then again after it has been loaded. Then and some lines of output from the test calls to the setX(), getX() and hexX() functions and another dump to know that the setX() functions are working properly.
For example if your program is executed like this with a file that contains the word “hello” on a line by itself
./memory 30 testdata
then the output will look like this:
Sample Run
1
2
3
4
5
6
7 |
00000020: a5 a5 a5 a5 a5 a5 a5 |
a5 a5 a5 a5 |
a5 |
a5 |
a5 |
a5 |
a5 |
*................* |
||||||||||
8 |
48 |
|||||||||||||||||
9 |
00000068 |
|||||||||||||||||
10 |
00006568 |
|||||||||||||||||
11 |
6 c6c6568 |
|||||||||||||||||
12 |
0 x00000068 |
|||||||||||||||||
13 |
0 x00006568 |
|||||||||||||||||
14 |
0 x6c6c6568 |
|||||||||||||||||
15 |
68 |
|||||||||||||||||
16 |
68 |
|||||||||||||||||
17 |
68 |
|||||||||||||||||
18 |
WARNING : Address out of range: |
0 x00001000 |
||||||||||||||||
19 |
WARNING : Address out of range: |
0 x00001001 |
||||||||||||||||
20 |
WARNING : Address out of range: |
0 x00001002 |
||||||||||||||||
21 |
WARNING : Address out of range: |
0 x00001003 |
||||||||||||||||
22 |
0 x00000000 |
|||||||||||||||||
23 |
||||||||||||||||||
24 |
00000000: |
68 |
65 |
6 c |
6 c |
6 f |
0 a |
a5 |
a5 |
a5 |
a5 |
a5 |
a5 |
a5 |
a5 |
a5 |
a5 |
* hello.................... * |
25 |
00000010: |
12 |
a5 |
a5 |
a5 |
34 |
12 |
a5 |
a5 |
21 |
43 |
65 |
87 |
a5 |
a5 |
a5 |
a5 |
*....4...! Ce............ * |
26 |
00000020: |
a5 |
a5 |
a5 |
a5 |
a5 |
a5 |
a5 |
a5 |
a5 |
a5 |
a5 |
a5 |
a5 |
a5 |
a5 |
a5 |
*................* |
If you create a test file on Windows with the word “hello” in it, then the values in bytes 5 and 6 may be different than those shown above.
DescriptionIn this final assignment, the students will demonstrate their ability to apply two ma
Path finding involves finding a path from A to B. Typically we want the path to have certain properties,such as being the shortest or to avoid going t
Develop a program to emulate a purchase transaction at a retail store. Thisprogram will have two classes, a LineItem class and a Transaction class. Th
1 Project 1 Introduction - the SeaPort Project series For this set of projects for the course, we wish to simulate some of the aspects of a number of
1 Project 2 Introduction - the SeaPort Project series For this set of projects for the course, we wish to simulate some of the aspects of a number of