Introduction to Operation System (430)

profileshahanil436
assg01.zip

assg01/assg01.md

--- title: 'Assignment 1 : Hypothetical Machine Simulator' author: 'CSci 430: Introduction to Operating Systems' date: 'Fall 2020' --- # Overview In this assignment you will be building an implementation of the hypothetical machine simulator like the one discussed in chapter 1 of our textbook and that you worked on for the first written assignment. The goal is to become better familiar with some fundamental hardware concepts that we rely on when building operating system components in this class. Another goal is to familiarize you with the structure of the assignments you need to complete for this class. **Questions** - What is the purpose of a standard fetch-execute cycle in a computing system? - How does a computing system operate at the hardware level to translate and execute instructions? - How can test driven development help you to create and debug your code? **Objectives** - Familiarize ourselves with test driven development and developing software to pass unit tests. - Become familiar with the class assignment structure of unit tests and system tests. - Refresh our understanding of basics of how computing systems operate at a hardware level, by studying in more detail the Hypothetical Machine from our Stallings textbook, and implementing a working simulation of this hypothetical computing system. # Introduction In this assignment you will be implementing a simulation of the hypothetical machine architecture description given in our Stalling textbook chapter 01. The hypothetical machine described is simple, and is meant to illustrate the basics of a CPU hardware fetch/execute cycle for performing computation, and a basic machine instruction set with some examples processor-memory, data processing, and control type instructions. We will simplify the hypothetical machine architecture in some regards, but expand on it a bit in others for this assignment. You will be implementing the following list of opcodes for this simulation: \pagebreak | opcode | mnemonic | description | |--------|-------------|---------------------------------------| | 0 | NOOP / HALT | Indicates system halt state | | 1 | LOAD | Load AC from memory | | 2 | STORE | Store AC to memory | | 3 | JMP | Perform unconditional jump to address | | 4 | SUB | Subtract memory reference from AC | | 5 | ADD | Add memory reference to AC | I have given you a large portion of the simulation structure for this first assignment, as the primary goal of the assignment is to become familiar with using system development tools, like make and the compiler and the unit test frameworks. For all assignments for this class, I will always give you a `Makefile` and a set of starting template files. The files given should build and run successfully, though they will be incomplete, and will not pass all (or any) of the defined unit and system tests you will be given. Your task for the assignments will always be to add code so that you can pass the unit and system tests to create a final working system, using the defined development system and Unix build tools. All assignments will have 2 targets and files that define executables that are built by the build system. For assg01 the files are named: - `assg01-tests.cpp` - `assg01-sim.cpp` If you examine the Makefile for this and all future assignment, you will always have the following targets defined: - all: builds all executables, including the test executable to perform unit tests and the sim executable to perform the system test / simulations. - tests: Will invoke the test executable to perform all unit tests, and the sim executable to perform system tests. Notice that this target depends on unit-tests and system-tests, which in turn depend on the sim and test executables being first up to date and built. - clean: delete all build products and revert to a clean project build state. You should start by checking that your development system builds cleanly and that you can run the tests. You will be using the following steps often while working on the assignments to make a clean build and check your tests (you can run the make and make tests target from VS Code as well): ``` $ make clean rm -f test sim *.o *.gch *~ rm -f -r output html latex $ make g++ -Wall -Werror -pedantic -g -I../../include -c assg01-tests.cpp -o assg01-tests.o g++ -Wall -Werror -pedantic -g -I../../include -c HypotheticalMachineSimulator.cpp -o HypotheticalMachineSimulator.o g++ -Wall -Werror -pedantic -g assg01-tests.o HypotheticalMachineSimulator.o -L../../libs -lSimulatorException -o test g++ -Wall -Werror -pedantic -g -I../../include -c assg01-sim.cpp -o assg01-sim.o g++ -Wall -Werror -pedantic -g assg01-sim.o HypotheticalMachineSimulator.o -L../../libs -lSimulatorException -o sim $ make tests ./test -s ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ test is a Catch v2.7.2 host application. Run with -? for options ------------------------------------------------------------------------------- <initializeMemory()> HypotheticalMachineController test memory initialization ------------------------------------------------------------------------------- assg01-tests.cpp:29 ............................................................................... assg01-tests.cpp:34: FAILED: CHECK( sim.getMemoryBaseAddress() == 300 ) with expansion: 0 == 300 (0x12c) ... skipped output of teests ... =============================================================================== test cases: 11 | 0 passed | 11 failed assertions: 170 | 35 passed | 135 failed ``` I skipped the output from running the unit tests. As you can see at the end all of the test cases, and most of the unit test assertions are failing initially. But if you look before that, the code is successfully compiling, and the test and sim executable targets are being built. You will not have to modify the assg01-tests.cpp nor the `assg01-sim.cpp` files that I give you for this assignments. The assg01-tests.cpp contains unit tests for the assignment. The `assg01-sim.cpp` file will build a command line executable to perform system tests using your simulator. You should always start by writing code to pass the unit tests, and only after you have the unit tests working should you move on and try and get the whole system simulation working. # Unit Test Tasks You should take a look at the test cases and assertions defined in the `assg01-tests.cpp` file to get started. I will try and always give you the unit tests in the order that it would be best to work on. Thus you should always start by looking at the first unit test in the first test case, and writing the code to get this test to pass. Then proceed to work on the next unit test and so on. I have given you files named `HypotheticalMachineSimulator.hpp` and `HypotheticalMachineSimulator.cpp` for this first assignment. The `.hpp` file is a header file, it contains the declaration of the `HypotheticalMachineSimulator` class, as well as some supporting classes. You will not need to make any changes to this header file for this assignment. The `.cpp` file is where the implementations of the simulation class member functions will be created. All of your work for this assignment will be done in the `HypotheticalMachineSimulator.cpp` file, where you will finish the code to implement several member functions of the simulator class. For this assignment, to get all of the functions of the simulator working, you need to perform the following tasks in this order. I give an outline of what should be done here to write each member function of the simulator. There are additional hints in the template files given as comments that you should look at as well for additional tasks you will need to perform that are not described here. 1. Implement the **initializeMemory()** function. You can pass these unit tests by simply initializing the member variables with the parameters given to this function. However, you also need to dynamically allocate an array of integers in this function that will serve as the memory storage for the simulation. You should also initialize the allocated memory so that all locations initially contain a value of 0. If you are a bit rusty on dynamic memory allocation, basically you need to do the following. There is already a member variable named `memory` in this class. Memory is a type `int*` (a pointer to an integer) defined for our `HypotheticalMachineSimulator` class. If you know how much memory you need to allocate, you can simply use the `new` keyword to allocate a block / array of memory, doing something like the following ```c++ memory = new int[memorySize]; ``` There are some additional tasks as well for this first function. You should check that the memory to be initialized makes sense in terms of it size for this simulation. 2. Implement the **translateAddress()** function and get the unit tests to work for this test case. The `translateAddress()` function takes a virtual address in the simulation memory address space and translates it to a real address. So for example, if the address space defined for the simulation has a base address of 300 and a bounding (last) address of 1000, then if you ask to translate address 355, this should be translated to the real address 55. The address / index of 55 can then be used to index into the `memory[]` array to read or write values to the simulated memory. There is one additional thing that should be done in this function. If the requested address is beyond the bounds of our simulation address space, you should throw an exception. For example, if the base address of memory is 300, and the bounds address is 1000, then any address of 299 or lower should be rejected and an exception thrown. Also for our simulation, any address exactly equal to the upper bound of 1000 or bigger is an illegal reference, and should also generate an exception. 3. Implement the **peekAddress()** and **pokeAddress()** functions and pass the unit tests for those functions. These functions are tested by using poke to write a value somewhere in memory, then we peek the same address and see if we get the value we wrote to read back out again. Both of these functions should reuse the `translateAddress()` function form the previous step. In both cases, you first start by translating the given address to a real address. Then for poke you need to save the indicated value into the correct location of your `memory[]` array. And likewise for peek, you need to read out a value from your `memory[]` array and return it. 4. Implement the **fetch()** method for the fetch phase of a fetch/execute cycle. If you are following along in the unit test file, you will see there are unit tests before the `fetch()` unit tests to test the `loadProgram()` function. You have already been given all of `loadProgram()`, but you should read over this function and see if you understand how it works. Your implementation of fetch should be a simple single line of code if you reuse your `peekAddress()` funciton. Basically, given the current value of the PC, you want to use `peekAddress()` to read the value pointed to by your PC and store this into the IR instruction register. 5. Implement the **execute()** method for the execute phase of a fetch/execute cycle. The execute phase has a lot more it needs to do than the fetch. You need to do the following tasks in the execute phase: - Test that the value in the instruction register is valid - Translate the opcode and address from the current value in the instruction register. - Increment the PC by 1 in preparation for the next fetch phase. - Finally actually execute the indicated instruction. You will do this by calling one of the functions `executeLoad()`, `executeStore()`, `executeJump()`, `executeSub()` or `executeAdd()` To translate the opcode and address you need to perform integer division and use the modulus operator `%`. Basically the instruction register should have a 4 digit decimal value such as 1940 in the format `XYYY`. The first decimal digit, the 1000's digit, is the opcode or instruction, a 1 in this case for a LOAD instruction. The last 3 decimal digits represent a reference address, memory address 940 in this case. The translation phase should end up with a 1 opcode in the irOpcode member variable, and 940 in the irAddress member variable. You should use something like a switch statement as the final part of your `execute()` function to simply call one of the 5 member functions that will handle performing the actual instruction execution. 6. Implement the **executeLoad()**, **executeStore()**, **executeJump()**, **executeSub()** and **executeAdd()** functions. Each of these has individual unit tests for them, so you should implement each one individually. All of these should be relatively simple 1 or 2 lines of code function if you reuse some of the previously implemented function. For example for the `executeLoad()` function, you should simply be able to use `peekAddress()` to get the value referenced by the irAddress member variable, then store this value into the accumulator. 7. Finally put it all together and test a full simulation using the **runSimulation()** method. The final unit tests load programs and call the **runSimulation()** method to see if they halt when expected and end up with the expected final calculations in memory and in the AC. Your `runSimulation()` For this assignment you have been given the code for the `runSimulation()` method, but the code is commented out because it relies on you correctly implementing the above functions first to work correctly. Uncomment the code in the `runSimulation()` method and the final unit tests should now be passing for you. # System Tests: Putting it all Together Once all of the unit tests are passing, you can begin working on the system tests. For this first assignment you do not have to do anything to get the simulation working, it has been implemented for you. But in future assignments you may be asked to implement part of the full simulation as well. So you should try out the simulator and understand how it works. The sim executable that is built uses the `HypothetheticalMachineSimulation` class you finished implementing to load and execute a program in the simulated machine. The sim targets for the assignments for this class will be typical command line programs that will expect 1 or more command line parameters to run. In this first assignment the sim program needs 2 command line arguments: the maximum number of cycles to simulate and the name of a hypothetical machine simulation file to load and attempt to run. You can ask the sim executable for help from the command line to see what command line parameters it is expecting: ``` $ ./sim -h Usage: sim maxCycles prog.sim Run hypothetical machine simulation on the given system state/simulation file maxCycles The maximum number of machine cycles (fetch/execute cycles) to perform file.sim A simulation definition file containing starting state of machine and program / memory contents. ``` If the sim target has been built successfully, you can run a system test simulation manually by invoking the sim program with the correct arguments: ``` $ ./sim 100 simfiles/prog-01.sim ``` This will load and try and simulate the program from the file `simfiles/prog-01.sim`. The first parameter specifies the maximum number of simulated machine cycles to perform, so if the program is an infinite loop it will stop in this case after performing 100 cycles. If you are passing all of the unit tests, your simulation should be able to hopefully pass all of the system tests. You can run all of the system tests using the system-tests target from the command line ``` $ make system-tests ./run-system-tests System test prog-01: PASSED System test prog-02: PASSED System test prog-03: PASSED System test prog-04: PASSED System test prog-05: PASSED System test prog-06: PASSED System test prog-07: PASSED System test prog-08: PASSED System test prog-09: PASSED System test prog-10: PASSED =============================================================================== All system tests passed (10 tests passed of 10 system tests) ``` The system tests work by running the simulation on a program and comparing the actual output seen with the correct expected output. Any difference in output will cause the system test to fail for that given input program test. # Assignment Submission In order to document your work and have a definitive version you would like to grade, a MyLeoOnline submission folder has been created named Assignment 01 for this assignment. There is a target in your `Makefile for` these assignments named **submit**. When your code is at a point that you think it is ready to submit, run the submit target: ``` $ make submit $ make submit tar cvfz assg01.tar.gz HypotheticalMachineSimulator.hpp HypotheticalMachineSimulator.cpp HypotheticalMachineSimulator.hpp HypotheticalMachineSimulator.cpp ``` The result of this target is a tared and gziped (compressed) archive, named assg01.tar.gz in your directory. You should upload this file archive to the submission folder to complete this assignment. # Requirements and Grading Rubrics ## Program Execution, Output and Functional Requirements 1. Your program must compile, run and produce some sort of output to be graded. 0 if not satisfied. 2. 10 pts each (70 pts) for completing each of the 7 listed steps in this assignment to write the functions needed to create the Hypothetical Machine. 3. 20 pts if all given unit tests are passed by your code. 4. 10 pts if all system tests pass and your hypothetical machine produces correct output for the given system tests. ## Program Style and Documentation This section is supplemental for the first assignment. If you uses the VS Code editor as described for this class, part of the configuration is to automatically run the `uncrustify` code beautifier on your code files everytime you save the file. You can run this tool manually from the command line as follows: ``` $ make beautify uncrustify -c ../../config/.uncrustify.cfg --replace --no-backup *.hpp *.cpp Parsing: HypotheticalMachineSimulator.hpp as language CPP Parsing: HypotheticalMachineSimulator.cpp as language CPP Parsing: assg01-sim.cpp as language CPP Parsing: assg01-tests.cpp as language CPP ``` Class style guidelines have been defined for this class. The `uncrustify.cfg` file defines a particular code style, like indentation, where to place opening and closing braces, whitespace around operators, etc. By running the beautifier on your files it reformats your code to conform to the defined class style guidelines. The beautifier may not be able to fix all style issues, so I might give comments to you about style issues to fix after looking at your code. But you should pay attention to the formatting of the code style defined by this configuration file. Another required element for class style is that code must be properly documented. Most importantly, all functions and class member functions must have function documentation proceeding the function. These have been given to you for the first assignment, but you may need to provide these for future assignment. For example, the code documentation block for the first function you write for this assignment looks like this: ```c++ /** * @brief initialize memory * * Initialize the contents of memory. Allocate array larget enough to * hold memory contents for the program. Record base and bounds * address for memory address translation. This memory function * dynamically allocates enough memory to hold the addresses for the * indicated begin and end memory ranges. * * @param memoryBaseAddress The int value for the base or beginning * address of the simulated memory address space for this * simulation. * @param memoryBoundsAddress The int value for the bounding address, * e.g. the maximum or upper valid address of the simulated memory * address space for this simulation. * * @exception Throws SimulatorException if * address space is invalid. Currently we support only 4 digit * opcodes XYYY, where the 3 digit YYY specifies a reference * address. Thus we can only address memory from 000 - 999 * given the limits of the expected opcode format. */ ``` This is an example of a `doxygen` formatted code documentation comment. The two `**` starting the block comment are required for `doxygen` to recognize this as a documentation comment. The `@brief`, `@param`, `@exception` etc. tags are used by `doxygen` to build reference documentation from your code. You can build the documentation using the `make docs` build target, though it does require you to have `doxygen` tools installed on your system to work. ``` $ make docs doxygen ../../config/Doxyfile 2>&1 | grep warning | grep -v "\file statement" | grep -v "\pagebreak" | sort -t: -k2 -n | sed -e "s|/home/dash/repos/csci430-os-sims/assg/assg01/||g" ``` The result of this is two new subdirectories in your current directory named `html` and `latex`. You can use a regular browser to browse the html based documentation in the `html` directory. You will need `latex` tools installed to build the `pdf` reference manual in the `latex` directory. You can use the `make docs` to see if you are missing any required function documentation or tags in your documentation. For example, if you remove one of the `@param` tags from the above function documentation, and run the docs, you would see ``` $ make docs doxygen ../../config/Doxyfile 2>&1 | grep warning | grep -v "\file statement" | grep -v "\pagebreak" | sort -t: -k2 -n | sed -e "s|/home/dash/repos/csci430-os-sims/assg/assg01/||g" HypotheticalMachineSimulator.hpp:88: warning: The following parameter of HypotheticalMachineSimulator::initializeMemory(int memoryBaseAddress, int memoryBoundsAddress) is not documented: parameter 'memoryBoundsAddress' ``` The documentation generator expects that there is a description, and that all input parameters and return values are documented for all functions, among other things. You can run the documentation generation to see if you are missing any required documentation in you project files.

assg01/assg01.pdf

Assignment 1 : Hypothetical Machine Simulator

CSci 430: Introduction to Operating Systems

Fall 2020

Overview In this assignment you will be building an implementation of the hypothetical machine simulator like the one discussed in chapter 1 of our textbook and that you worked on for the first written assignment. The goal is to become better familiar with some fundamental hardware concepts that we rely on when building operating system components in this class. Another goal is to familiarize you with the structure of the assignments you need to complete for this class.

Questions

• What is the purpose of a standard fetch-execute cycle in a computing system? • How does a computing system operate at the hardware level to translate and execute instructions? • How can test driven development help you to create and debug your code?

Objectives

• Familiarize ourselves with test driven development and developing software to pass unit tests. • Become familiar with the class assignment structure of unit tests and system tests. • Refresh our understanding of basics of how computing systems operate at a hardware level, by studying in more

detail the Hypothetical Machine from our Stallings textbook, and implementing a working simulation of this hypothetical computing system.

Introduction In this assignment you will be implementing a simulation of the hypothetical machine architecture description given in our Stalling textbook chapter 01. The hypothetical machine described is simple, and is meant to illustrate the basics of a CPU hardware fetch/execute cycle for performing computation, and a basic machine instruction set with some examples processor-memory, data processing, and control type instructions. We will simplify the hypothetical machine architecture in some regards, but expand on it a bit in others for this assignment. You will be implementing the following list of opcodes for this simulation:

1

opcode mnemonic description 0 NOOP / HALT Indicates system halt state 1 LOAD Load AC from memory 2 STORE Store AC to memory 3 JMP Perform unconditional jump to address 4 SUB Subtract memory reference from AC 5 ADD Add memory reference to AC

I have given you a large portion of the simulation structure for this first assignment, as the primary goal of the assignment is to become familiar with using system development tools, like make and the compiler and the unit test frameworks. For all assignments for this class, I will always give you a Makefile and a set of starting template files. The files given should build and run successfully, though they will be incomplete, and will not pass all (or any) of the defined unit and system tests you will be given. Your task for the assignments will always be to add code so that you can pass the unit and system tests to create a final working system, using the defined development system and Unix build tools.

All assignments will have 2 targets and files that define executables that are built by the build system. For assg01 the files are named:

• assg01-tests.cpp • assg01-sim.cpp

If you examine the Makefile for this and all future assignment, you will always have the following targets defined:

• all: builds all executables, including the test executable to perform unit tests and the sim executable to perform the system test / simulations.

• tests: Will invoke the test executable to perform all unit tests, and the sim executable to perform system tests. Notice that this target depends on unit-tests and system-tests, which in turn depend on the sim and test executables being first up to date and built.

• clean: delete all build products and revert to a clean project build state.

You should start by checking that your development system builds cleanly and that you can run the tests. You will be using the following steps often while working on the assignments to make a clean build and check your tests (you can run the make and make tests target from VS Code as well):

$ make clean rm -f test sim *.o *.gch *~ rm -f -r output html latex

$ make g++ -Wall -Werror -pedantic -g -I../../include -c assg01-tests.cpp -o assg01-tests.o g++ -Wall -Werror -pedantic -g -I../../include -c HypotheticalMachineSimulator.cpp -o HypotheticalMachineSimulator.o g++ -Wall -Werror -pedantic -g assg01-tests.o HypotheticalMachineSimulator.o -L../../libs -lSimulatorException -o test g++ -Wall -Werror -pedantic -g -I../../include -c assg01-sim.cpp -o assg01-sim.o g++ -Wall -Werror -pedantic -g assg01-sim.o HypotheticalMachineSimulator.o -L../../libs -lSimulatorException -o sim

$ make tests ./test -s

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ test is a Catch v2.7.2 host application. Run with -? for options

------------------------------------------------------------------------------- <initializeMemory()> HypotheticalMachineController test memory initialization -------------------------------------------------------------------------------

2

assg01-tests.cpp:29 ...............................................................................

assg01-tests.cpp:34: FAILED: CHECK( sim.getMemoryBaseAddress() == 300 )

with expansion: 0 == 300 (0x12c)

... skipped output of teests ...

=============================================================================== test cases: 11 | 0 passed | 11 failed assertions: 170 | 35 passed | 135 failed

I skipped the output from running the unit tests. As you can see at the end all of the test cases, and most of the unit test assertions are failing initially. But if you look before that, the code is successfully compiling, and the test and sim executable targets are being built.

You will not have to modify the assg01-tests.cpp nor the assg01-sim.cpp files that I give you for this assignments. The assg01-tests.cpp contains unit tests for the assignment. The assg01-sim.cpp file will build a command line executable to perform system tests using your simulator. You should always start by writing code to pass the unit tests, and only after you have the unit tests working should you move on and try and get the whole system simulation working.

Unit Test Tasks You should take a look at the test cases and assertions defined in the assg01-tests.cpp file to get started. I will try and always give you the unit tests in the order that it would be best to work on. Thus you should always start by looking at the first unit test in the first test case, and writing the code to get this test to pass. Then proceed to work on the next unit test and so on.

I have given you files named HypotheticalMachineSimulator.hpp and HypotheticalMachineSimulator.cpp for this first assignment. The .hpp file is a header file, it contains the declaration of the HypotheticalMachineSimulator class, as well as some supporting classes. You will not need to make any changes to this header file for this assignment. The .cpp file is where the implementations of the simulation class member functions will be created. All of your work for this assignment will be done in the HypotheticalMachineSimulator.cpp file, where you will finish the code to implement several member functions of the simulator class.

For this assignment, to get all of the functions of the simulator working, you need to perform the following tasks in this order. I give an outline of what should be done here to write each member function of the simulator. There are additional hints in the template files given as comments that you should look at as well for additional tasks you will need to perform that are not described here.

1. Implement the initializeMemory() function. You can pass these unit tests by simply initializing the member variables with the parameters given to this function. However, you also need to dynamically allocate an array of integers in this function that will serve as the memory storage for the simulation. You should also initialize the allocated memory so that all locations initially contain a value of 0. If you are a bit rusty on dynamic memory allocation, basically you need to do the following. There is already a member variable named memory in this class. Memory is a type int* (a pointer to an integer) defined for our HypotheticalMachineSimulator class. If you know how much memory you need to allocate, you can simply use the new keyword to allocate a block / array of memory, doing something like the following memory = new int[memorySize];

There are some additional tasks as well for this first function. You should check that the memory to be initialized makes sense in terms of it size for this simulation.

2. Implement the translateAddress() function and get the unit tests to work for this test case. The translateAddress() function takes a virtual address in the simulation memory address space and translates it

3

to a real address. So for example, if the address space defined for the simulation has a base address of 300 and a bounding (last) address of 1000, then if you ask to translate address 355, this should be translated to the real address 55. The address / index of 55 can then be used to index into the memory[] array to read or write values to the simulated memory. There is one additional thing that should be done in this function. If the requested address is beyond the bounds of our simulation address space, you should throw an exception. For example, if the base address of memory is 300, and the bounds address is 1000, then any address of 299 or lower should be rejected and an exception thrown. Also for our simulation, any address exactly equal to the upper bound of 1000 or bigger is an illegal reference, and should also generate an exception.

3. Implement the peekAddress() and pokeAddress() functions and pass the unit tests for those functions. These functions are tested by using poke to write a value somewhere in memory, then we peek the same address and see if we get the value we wrote to read back out again. Both of these functions should reuse the translateAddress() function form the previous step. In both cases, you first start by translating the given address to a real address. Then for poke you need to save the indicated value into the correct location of your memory[] array. And likewise for peek, you need to read out a value from your memory[] array and return it.

4. Implement the fetch() method for the fetch phase of a fetch/execute cycle. If you are following along in the unit test file, you will see there are unit tests before the fetch() unit tests to test the loadProgram() function. You have already been given all of loadProgram(), but you should read over this function and see if you understand how it works. Your implementation of fetch should be a simple single line of code if you reuse your peekAddress() funciton. Basically, given the current value of the PC, you want to use peekAddress() to read the value pointed to by your PC and store this into the IR instruction register.

5. Implement the execute() method for the execute phase of a fetch/execute cycle. The execute phase has a lot more it needs to do than the fetch. You need to do the following tasks in the execute phase:

• Test that the value in the instruction register is valid • Translate the opcode and address from the current value in the instruction register. • Increment the PC by 1 in preparation for the next fetch phase. • Finally actually execute the indicated instruction. You will do this by calling one of the functions

executeLoad(), executeStore(), executeJump(), executeSub() or executeAdd()

To translate the opcode and address you need to perform integer division and use the modulus operator %. Basically the instruction register should have a 4 digit decimal value such as 1940 in the format XYYY. The first decimal digit, the 1000’s digit, is the opcode or instruction, a 1 in this case for a LOAD instruction. The last 3 decimal digits represent a reference address, memory address 940 in this case. The translation phase should end up with a 1 opcode in the irOpcode member variable, and 940 in the irAddress member variable. You should use something like a switch statement as the final part of your execute() function to simply call one of the 5 member functions that will handle performing the actual instruction execution.

6. Implement the executeLoad(), executeStore(), executeJump(), executeSub() and executeAdd() func- tions. Each of these has individual unit tests for them, so you should implement each one individually. All of these should be relatively simple 1 or 2 lines of code function if you reuse some of the previously implemented function. For example for the executeLoad() function, you should simply be able to use peekAddress() to get the value referenced by the irAddress member variable, then store this value into the accumulator.

7. Finally put it all together and test a full simulation using the runSimulation() method. The final unit tests load programs and call the runSimulation() method to see if they halt when expected and end up with the expected final calculations in memory and in the AC. Your runSimulation() For this assignment you have been given the code for the runSimulation() method, but the code is commented out because it relies on you correctly implementing the above functions first to work correctly. Uncomment the code in the runSimulation() method and the final unit tests should now be passing for you.

System Tests: Putting it all Together Once all of the unit tests are passing, you can begin working on the system tests. For this first assignment you do not have to do anything to get the simulation working, it has been implemented for you. But in future assignments you may be asked to implement part of the full simulation as well. So you should try out the simulator and understand how it works.

4

The sim executable that is built uses the HypothetheticalMachineSimulation class you finished implementing to load and execute a program in the simulated machine. The sim targets for the assignments for this class will be typical command line programs that will expect 1 or more command line parameters to run. In this first assignment the sim program needs 2 command line arguments: the maximum number of cycles to simulate and the name of a hypothetical machine simulation file to load and attempt to run. You can ask the sim executable for help from the command line to see what command line parameters it is expecting:

$ ./sim -h Usage: sim maxCycles prog.sim Run hypothetical machine simulation on the given system state/simulation file

maxCycles The maximum number of machine cycles (fetch/execute cycles) to perform

file.sim A simulation definition file containing starting state of machine and program / memory contents.

If the sim target has been built successfully, you can run a system test simulation manually by invoking the sim program with the correct arguments:

$ ./sim 100 simfiles/prog-01.sim

This will load and try and simulate the program from the file simfiles/prog-01.sim. The first parameter specifies the maximum number of simulated machine cycles to perform, so if the program is an infinite loop it will stop in this case after performing 100 cycles.

If you are passing all of the unit tests, your simulation should be able to hopefully pass all of the system tests. You can run all of the system tests using the system-tests target from the command line

$ make system-tests ./run-system-tests System test prog-01: PASSED System test prog-02: PASSED System test prog-03: PASSED System test prog-04: PASSED System test prog-05: PASSED System test prog-06: PASSED System test prog-07: PASSED System test prog-08: PASSED System test prog-09: PASSED System test prog-10: PASSED =============================================================================== All system tests passed (10 tests passed of 10 system tests)

The system tests work by running the simulation on a program and comparing the actual output seen with the correct expected output. Any difference in output will cause the system test to fail for that given input program test.

Assignment Submission In order to document your work and have a definitive version you would like to grade, a MyLeoOnline submission folder has been created named Assignment 01 for this assignment. There is a target in your Makefile for these assignments named submit. When your code is at a point that you think it is ready to submit, run the submit target:

$ make submit $ make submit tar cvfz assg01.tar.gz HypotheticalMachineSimulator.hpp HypotheticalMachineSimulator.cpp HypotheticalMachineSimulator.hpp HypotheticalMachineSimulator.cpp

The result of this target is a tared and gziped (compressed) archive, named assg01.tar.gz in your directory. You should upload this file archive to the submission folder to complete this assignment.

5

Requirements and Grading Rubrics Program Execution, Output and Functional Requirements

1. Your program must compile, run and produce some sort of output to be graded. 0 if not satisfied. 2. 10 pts each (70 pts) for completing each of the 7 listed steps in this assignment to write the functions needed to

create the Hypothetical Machine. 3. 20 pts if all given unit tests are passed by your code. 4. 10 pts if all system tests pass and your hypothetical machine produces correct output for the given system tests.

Program Style and Documentation This section is supplemental for the first assignment. If you uses the VS Code editor as described for this class, part of the configuration is to automatically run the uncrustify code beautifier on your code files everytime you save the file. You can run this tool manually from the command line as follows:

$ make beautify uncrustify -c ../../config/.uncrustify.cfg --replace --no-backup *.hpp *.cpp Parsing: HypotheticalMachineSimulator.hpp as language CPP Parsing: HypotheticalMachineSimulator.cpp as language CPP Parsing: assg01-sim.cpp as language CPP Parsing: assg01-tests.cpp as language CPP

Class style guidelines have been defined for this class. The uncrustify.cfg file defines a particular code style, like indentation, where to place opening and closing braces, whitespace around operators, etc. By running the beautifier on your files it reformats your code to conform to the defined class style guidelines. The beautifier may not be able to fix all style issues, so I might give comments to you about style issues to fix after looking at your code. But you should pay attention to the formatting of the code style defined by this configuration file.

Another required element for class style is that code must be properly documented. Most importantly, all functions and class member functions must have function documentation proceeding the function. These have been given to you for the first assignment, but you may need to provide these for future assignment. For example, the code documentation block for the first function you write for this assignment looks like this: /** * @brief initialize memory * * Initialize the contents of memory. Allocate array larget enough to * hold memory contents for the program. Record base and bounds * address for memory address translation. This memory function * dynamically allocates enough memory to hold the addresses for the * indicated begin and end memory ranges. * * @param memoryBaseAddress The int value for the base or beginning * address of the simulated memory address space for this * simulation. * @param memoryBoundsAddress The int value for the bounding address, * e.g. the maximum or upper valid address of the simulated memory * address space for this simulation. * * @exception Throws SimulatorException if * address space is invalid. Currently we support only 4 digit * opcodes XYYY, where the 3 digit YYY specifies a reference * address. Thus we can only address memory from 000 - 999 * given the limits of the expected opcode format. */

This is an example of a doxygen formatted code documentation comment. The two ** starting the block comment are required for doxygen to recognize this as a documentation comment. The @brief, @param, @exception etc. tags

6

are used by doxygen to build reference documentation from your code. You can build the documentation using the make docs build target, though it does require you to have doxygen tools installed on your system to work.

$ make docs doxygen ../../config/Doxyfile 2>&1

| grep warning | grep -v "\file statement" | grep -v "\pagebreak" | sort -t: -k2 -n | sed -e "s|/home/dash/repos/csci430-os-sims/assg/assg01/||g"

The result of this is two new subdirectories in your current directory named html and latex. You can use a regular browser to browse the html based documentation in the html directory. You will need latex tools installed to build the pdf reference manual in the latex directory.

You can use the make docs to see if you are missing any required function documentation or tags in your documentation. For example, if you remove one of the @param tags from the above function documentation, and run the docs, you would see

$ make docs doxygen ../../config/Doxyfile 2>&1

| grep warning | grep -v "\file statement" | grep -v "\pagebreak" | sort -t: -k2 -n | sed -e "s|/home/dash/repos/csci430-os-sims/assg/assg01/||g"

HypotheticalMachineSimulator.hpp:88: warning: The following parameter of HypotheticalMachineSimulator::initializeMemory(int memoryBaseAddress,

int memoryBoundsAddress) is not documented: parameter 'memoryBoundsAddress'

The documentation generator expects that there is a description, and that all input parameters and return values are documented for all functions, among other things. You can run the documentation generation to see if you are missing any required documentation in you project files.

7

  • Overview
  • Introduction
  • Unit Test Tasks
  • System Tests: Putting it all Together
  • Assignment Submission
  • Requirements and Grading Rubrics
    • Program Execution, Output and Functional Requirements
    • Program Style and Documentation

assg01/assg01-sim.cpp

assg01/assg01-sim.cpp

/**  @file  assg01-sim.cpp
 *  @brief  System Test Simulator
 *
 *  @author  Derek Harter
 *  @note    cwid: 123456
 *  @date    Fall 2020
 *  @note    ide:  VS Code Editor/IDE ; GNU gcc tools
 *
 * Command line invocation of Hypothetical Machine Simulator,
 * used to perform system tests.  Given a file that specifies the
 * initial state of the memory and registers of the hypothetical
 * machine, we create a simulator object, load the program/state,
 * then execute fetch/execute cycles until either the machine
 * halts or we reach the maximum number of cycles specified.
 */
#include   "HypotheticalMachineSimulator.hpp"
#include   "SimulatorException.hpp"
#include   < iostream >
#include   < string >
using   namespace  std ;


/**
 *  @brief  usage
 *
 * Usage information for invoking simulator with command line
 * arguments.  Print usage information and exit with non success
 * status to indicate error.
 */
void  usage ()
{
  cout  <<   "Usage: sim maxCycles prog.sim"   <<  endl
        <<   "Run hypothetical machine simulation on the given system state/simulation file"
        <<  endl  <<  endl
        <<   "maxCycles          The maximum number of machine cycles (fetch/execute"   <<  endl
        <<   "                     cycles) to perform"   <<  endl
        <<   "file.sim           A simulation definition file containing starting"   <<  endl
        <<   "                     state of machine and program / memory contents."   <<  endl ;
  exit ( 1 );
}


/**
 *  @brief  main entry point
 *
 * Entry point of the hypothetical machine simulator system test
 * command line program.  This program is so small we do most all of
 * our work here.  We parse command line arguments, and if successful
 * we instantiate a HypotheticalMachineSimulator, load the
 * indicated program, and run the simulation.
 *
 *  @param  argc The command line argument count.  This program requires
 *   3 command line arguments to run.
 *  @param  argv[] The command line argument values.  argv[1] should be
 *   the number of fetch/execute cycles to perform (maximum), and
 *   argv[2] should be the simulation file to be loaded and executed.
 *
 *  @return  0 is returned if simulation finishes successfully with no errors
 *   or exceptions.  A non-zero value is returned when an exception occurs
 *   or whenever simulation terminates abnormally.
 */
int  main ( int  argc ,   char **  argv )
{
   // parse command line arguments
   // if we do not get required command line arguments, print usage
   // and exit immediately.
   if   ( argc  !=   3 )
   {
    usage ();
   }
   int  maxCycles  =  atoi ( argv [ 1 ]);
  string simFileName  =  string ( argv [ 2 ]);
   bool  verbose  =   true ;

   // load the indicated simulation file and execute the program
   HypotheticalMachineSimulator  sim ;

   try
   {
    sim . loadProgram ( simFileName );
    sim . runSimulation ( maxCycles ,  verbose );
   }
   catch   ( const   SimulatorException &  e )
   {
    cerr  <<   "Simulation run resulted in runtime error occurring:"   <<  endl ;
    cerr  <<  e . what ()   <<  endl ;
    exit ( 1 );
   }

   // exit with 0 status code to indicate successful invocation of simulator
   return   0 ;
}

assg01/assg01-tests.cpp

assg01/assg01-tests.cpp

/**  @file  assg01-tests.cpp
 *  @brief  Unit tests for Hypothetical Machine simulation
 *
 *  @author  Derek Harter
 *  @note    cwid: 123456
 *  @date    Fall 2020
 *  @note    ide:  VS Code Editor/IDE ; GNU gcc tools
 *
 * Unit tests for assignment 1, the hypothetical machine simulator.
 * You should start with the first set of tests in the first TEST_CASE,
 * and get the tests to pass in order from the first one in this file
 * to the last one.  If all unit tests pass, you will most likely have
 * a working system simulator for this assignment.
 */
#define  CATCH_CONFIG_MAIN  // This tells catch to provide a main(), only do this 1 time
#include   "catch.hpp"
#include   "HypotheticalMachineSimulator.hpp"
#include   "SimulatorException.hpp"
#include   < string >
using   namespace  std ;


/// simulator instance used for all of the tests
HypotheticalMachineSimulator  sim ;

/**
 *  @brief  test memory initialization
 */
TEST_CASE ( "<initializeMemory()> HypotheticalMachineController test memory initialization" ,
           "[member]" )
{
   // make sure memory set correctly, especially memory size determined correctly
  sim . initializeMemory ( 300 ,   1000 );
  CHECK ( sim . getMemoryBaseAddress ()   ==   300 );
  CHECK ( sim . getMemoryBoundsAddress ()   ==   1000 );
  CHECK ( sim . getMemorySize ()   ==   700 );

   // reset should be working to reset state of simulator
  sim . reset ();
  CHECK ( sim . getMemoryBaseAddress ()   ==   0 );
  CHECK ( sim . getMemoryBoundsAddress ()   ==   0 );
  CHECK ( sim . getMemorySize ()   ==   0 );

   // another random test
  sim . initializeMemory ( 42 ,   917 );
  CHECK ( sim . getMemoryBaseAddress ()   ==   42 );
  CHECK ( sim . getMemoryBoundsAddress ()   ==   917 );
  CHECK ( sim . getMemorySize ()   ==   ( 917   -   42 )   );

   // we use XYYY addresses, where X is an opcode and YYY is a reference
   // address, thus memory can't exceed 1000, only valid memory is 0 - 999
  sim . reset ();
  CHECK_THROWS_AS ( sim . initializeMemory ( 99 ,   1001 ),   SimulatorException );

}

/**
 *  @brief  test memory address translation
 */
TEST_CASE ( "<translateAddress()> HypotheticalMachineController test memory address translation" ,
           "[member()]" )
{
   // a typical memory address space to test first
  sim . initializeMemory ( 300 ,   1000 );
  CHECK ( sim . getMemoryBaseAddress ()   ==   300 );
  CHECK ( sim . getMemoryBoundsAddress ()   ==   1000 );
  CHECK ( sim . getMemorySize ()   ==   700 );

   // test some translations of addresses in this address space
  CHECK ( sim . translateAddress ( 300 )   ==   0 );
  CHECK ( sim . translateAddress ( 476 )   ==   176 );
  CHECK ( sim . translateAddress ( 999 )   ==   699 );     // last legal address in ths address space

   // check bounds testing, we should get exception if try and reference an
   // illegal address
  CHECK_THROWS_AS ( sim . translateAddress ( 299 ),   SimulatorException );
  CHECK_THROWS_AS ( sim . translateAddress ( 1000 ),   SimulatorException );

   // a second more difficult memory address space
  sim . initializeMemory ( 187 ,   432 );
  CHECK ( sim . getMemoryBaseAddress ()   ==   187 );
  CHECK ( sim . getMemoryBoundsAddress ()   ==   432 );
  CHECK ( sim . getMemorySize ()   ==   ( 432   -   187 )   );

   // test some translations of addresses in this address space
  CHECK ( sim . translateAddress ( 187 )   ==   0 );
  CHECK ( sim . translateAddress ( 217 )   ==   30 );
  CHECK ( sim . translateAddress ( 432   -   1 )   ==   ( 432   -   187   -
                                           1 )   );    // last legal address in ths address space

   // check bounds testing, we should get exception if try and reference an
   // illegal address
  CHECK_THROWS_AS ( sim . translateAddress ( 186 ),   SimulatorException );
  CHECK_THROWS_AS ( sim . translateAddress ( 432 ),   SimulatorException );
}


/**
 *  @brief  test memory access functions
 */
TEST_CASE ( "<peek() and poke()> HypotheticalMachineController test memory peeks and pokes" ,
           "[member]" )
{
   // a typical memory address space to test first
  sim . initializeMemory ( 300 ,   1000 );
  CHECK ( sim . getMemoryBaseAddress ()   ==   300 );
  CHECK ( sim . getMemoryBoundsAddress ()   ==   1000 );
  CHECK ( sim . getMemorySize ()   ==   700 );

   // poke some random locations then peek and make sure
   // we get same value back
  sim . pokeAddress ( 300 ,   42 );
  CHECK ( sim . peekAddress ( 300 )   ==   42 );

  sim . pokeAddress ( 999 ,   1867 );
  CHECK ( sim . peekAddress ( 999 )   ==   1867 );

  sim . pokeAddress ( 456 ,   789 );
  CHECK ( sim . peekAddress ( 456 )   ==   789 );

  sim . pokeAddress ( 789 ,   456 );
  CHECK ( sim . peekAddress ( 789 )   ==   456 );

   // should still be illegal to peek and poke to addresses beyond
   // our simulation memory address space
  CHECK_THROWS_AS ( sim . pokeAddress ( 299 ,   42 ),   SimulatorException );
  CHECK_THROWS_AS ( sim . peekAddress ( 299 ),   SimulatorException );
  CHECK_THROWS_AS ( sim . pokeAddress ( 1000 ,   42 ),   SimulatorException );
  CHECK_THROWS_AS ( sim . peekAddress ( 1000 ),   SimulatorException );
}


/**
 *  @brief  test program loading function
 */
TEST_CASE ( "<loadProgram()> HypotheticalMachineController test program load" ,
           "[member]" )
{
   // should be throwing exception when file name is not correct
  string progFile  =   "simfiles/badfile.sim" ;
  CHECK_THROWS_AS ( sim . loadProgram ( progFile ),   SimulatorException );

   // load actual program and see we get expected values into
   // the hypothetical machine
  progFile  =   "simfiles/prog-01.sim" ;
  sim . loadProgram ( progFile );
  CHECK ( sim . getPC ()   ==   300 );
  CHECK ( sim . getAC ()   ==   0 );
  CHECK ( sim . getIR ()   ==   0 );
  CHECK ( sim . getMemoryBaseAddress ()   ==   300 );
  CHECK ( sim . getMemoryBoundsAddress ()   ==   1000 );
  CHECK ( sim . getMemorySize ()   ==   700 );

   // test memory was loaded as expected
  CHECK ( sim . peekAddress ( 300 )   ==   1940 );
  CHECK ( sim . peekAddress ( 301 )   ==   5941 );
  CHECK ( sim . peekAddress ( 302 )   ==   2941 );
  CHECK ( sim . peekAddress ( 303 )   ==   0 );
  CHECK ( sim . peekAddress ( 939 )   ==   0 );
  CHECK ( sim . peekAddress ( 940 )   ==   3 );
  CHECK ( sim . peekAddress ( 941 )   ==   2 );
  CHECK ( sim . peekAddress ( 942 )   ==   0 );

   // load another program for more tests
  progFile  =   "simfiles/prog-02.sim" ;
  sim . loadProgram ( progFile );
  CHECK ( sim . getPC ()   ==   50 );
  CHECK ( sim . getAC ()   ==   0 );
  CHECK ( sim . getIR ()   ==   0 );
  CHECK ( sim . getMemoryBaseAddress ()   ==   50 );
  CHECK ( sim . getMemoryBoundsAddress ()   ==   150 );
  CHECK ( sim . getMemorySize ()   ==   100 );

   // test memory was loaded as expected
  CHECK ( sim . peekAddress ( 50 )   ==   1141 );
  CHECK ( sim . peekAddress ( 51 )   ==   5140 );
  CHECK ( sim . peekAddress ( 52 )   ==   3051 );
  CHECK ( sim . peekAddress ( 53 )   ==   4142 );
  CHECK ( sim . peekAddress ( 54 )   ==   0 );
  CHECK ( sim . peekAddress ( 140 )   ==   2 );
  CHECK ( sim . peekAddress ( 141 )   ==   4 );
  CHECK ( sim . peekAddress ( 142 )   ==   8 );
  CHECK ( sim . peekAddress ( 143 )   ==   0 );
}


/**
 *  @brief  test fetch phase
 */
TEST_CASE ( "<fetch()> HypotheticalMachineController test fetch phase" ,
           "[member]" )
{
   // load a program and check results of performing fetch cycles
  string progFile  =   "simfiles/prog-01.sim" ;
  sim . loadProgram ( progFile );

   // initially in prog-01 PC is 300, should fetch instruction 1940
   // and then we fetch until the 0/noop/halt
  sim . fetch ();
  CHECK ( sim . getIR ()   ==   1940 );
  CHECK ( sim . getPC ()   ==   300 );
  sim . incrementPC ();

  sim . fetch ();
  CHECK ( sim . getIR ()   ==   5941 );
  CHECK ( sim . getPC ()   ==   301 );
  sim . incrementPC ();

  sim . fetch ();
  CHECK ( sim . getIR ()   ==   2941 );
  CHECK ( sim . getPC ()   ==   302 );
  sim . incrementPC ();

  sim . fetch ();
  CHECK ( sim . getIR ()   ==   0 );
  CHECK ( sim . getPC ()   ==   303 );
  sim . incrementPC ();

   // load a second program and step through it
  progFile  =   "simfiles/prog-02.sim" ;
  sim . loadProgram ( progFile );

   // initially in prog-01 PC is 300, should fetch instruction 1940
   // and then we fetch until the 0/noop/halt
  sim . fetch ();
  CHECK ( sim . getIR ()   ==   1141 );
  CHECK ( sim . getPC ()   ==   50 );
  sim . incrementPC ();

  sim . fetch ();
  CHECK ( sim . getIR ()   ==   5140 );
  CHECK ( sim . getPC ()   ==   51 );
  sim . incrementPC ();

  sim . fetch ();
  CHECK ( sim . getIR ()   ==   3051 );
  CHECK ( sim . getPC ()   ==   52 );
  sim . incrementPC ();

  sim . fetch ();
  CHECK ( sim . getIR ()   ==   4142 );
  CHECK ( sim . getPC ()   ==   53 );
  sim . incrementPC ();

  sim . fetch ();
  CHECK ( sim . getIR ()   ==   0 );
  CHECK ( sim . getPC ()   ==   54 );
  sim . incrementPC ();
}


/**
 *  @brief  test execute phase
 */
TEST_CASE ( "<execute()> HypotheticalMachineController test ir translation in execute()" ,
           "[method]" )
{
   // the execute() part of cycle starts out by translating
   // the fetched ir into opcode and address parts, test these are translated
   // correctly
  string progFile  =   "simfiles/prog-01.sim" ;
  sim . loadProgram ( progFile );
  sim . fetch ();
  CHECK ( sim . getIR ()   ==   1940 );
  sim . execute ();
  CHECK ( sim . getIROpcode ()   ==   1 );
  CHECK ( sim . getIRAddress ()   ==   940 );

  sim . fetch ();
  CHECK ( sim . getIR ()   ==   5941 );
  sim . execute ();
  CHECK ( sim . getIROpcode ()   ==   5 );
  CHECK ( sim . getIRAddress ()   ==   941 );

  sim . fetch ();
  CHECK ( sim . getIR ()   ==   2941 );
  sim . execute ();
  CHECK ( sim . getIROpcode ()   ==   2 );
  CHECK ( sim . getIRAddress ()   ==   941 );


   // test that a nonsensical ir value is detected
  sim . pokeAddress ( 303 ,   10000 );   // only values of 0000 - 9999 make sense in this sim
  sim . fetch ();
  CHECK ( sim . getIR ()   ==   10000 );
  CHECK_THROWS_AS ( sim . execute (),   SimulatorException );

   // check a second set of translations to ensure working
  progFile  =   "simfiles/prog-02.sim" ;
  sim . loadProgram ( progFile );
  sim . fetch ();
  CHECK ( sim . getIR ()   ==   1141 );
  sim . execute ();
  CHECK ( sim . getIROpcode ()   ==   1 );
  CHECK ( sim . getIRAddress ()   ==   141 );

  sim . fetch ();
  CHECK ( sim . getIR ()   ==   5140 );
  sim . execute ();
  CHECK ( sim . getIROpcode ()   ==   5 );
  CHECK ( sim . getIRAddress ()   ==   140 );

  sim . fetch ();
  CHECK ( sim . getIR ()   ==   3051 );
  sim . execute ();
  CHECK ( sim . getIROpcode ()   ==   3 );
  CHECK ( sim . getIRAddress ()   ==   51 );
}


/**
 *  @brief  test load instruction
 */
TEST_CASE ( "<executeLoad()> HypotheticalMachineController test load instruction" ,
           "[member]" )
{
   // prog-03 tests load instructions
  string progFile  =   "simfiles/prog-03.sim" ;
  sim . loadProgram ( progFile );

   // fetch and execute the 3 load instructions and test the result is
   // as we expect
  sim . fetch ();
  sim . execute ();
  CHECK ( sim . getIROpcode ()   ==   1 );
  CHECK ( sim . getIRAddress ()   ==   150 );
  CHECK ( sim . getAC ()   ==   42 );

  sim . fetch ();
  sim . execute ();
  CHECK ( sim . getIROpcode ()   ==   1 );
  CHECK ( sim . getIRAddress ()   ==   175 );
  CHECK ( sim . getAC ()   ==   - 5 );

  sim . fetch ();
  sim . execute ();
  CHECK ( sim . getIROpcode ()   ==   1 );
  CHECK ( sim . getIRAddress ()   ==   190 );
  CHECK ( sim . getAC ()   ==   123 );


   // load from out of bounds reference should fail
  sim . fetch ();
  CHECK_THROWS_AS ( sim . execute (),   SimulatorException );
}


/**
 *  @brief  test store instruction
 */
TEST_CASE ( "<executeStore()> HypotheticalMachineController test store instruction" ,
           "[member]" )
{
   // prog-04 tests store instructions
  string progFile  =   "simfiles/prog-04.sim" ;
  sim . loadProgram ( progFile );

   // fetch and execute the 3 store instructions and test the result is
   // as we expect
  sim . fetch ();
  sim . execute ();
  CHECK ( sim . getIROpcode ()   ==   2 );
  CHECK ( sim . getIRAddress ()   ==   250 );
  CHECK ( sim . getAC ()   ==   32 );
  CHECK ( sim . peekAddress ( 250 )   ==   32 );

  sim . fetch ();
  sim . execute ();
  CHECK ( sim . getIROpcode ()   ==   2 );
  CHECK ( sim . getIRAddress ()   ==   242 );
  CHECK ( sim . getAC ()   ==   32 );
  CHECK ( sim . peekAddress ( 242 )   ==   32 );

  sim . fetch ();
  sim . execute ();
  CHECK ( sim . getIROpcode ()   ==   2 );
  CHECK ( sim . getIRAddress ()   ==   287 );
  CHECK ( sim . getAC ()   ==   32 );
  CHECK ( sim . peekAddress ( 287 )   ==   32 );

   // next instruction loads from out of bounds reference and should fail
  sim . fetch ();
  CHECK_THROWS_AS ( sim . execute (),   SimulatorException );
}


/**
 *  @brief  test add and subtract instructions
 */
TEST_CASE ( "<executeAdd() and executeSubtract()> HypotheticalMachineController test add and subtract instructions" ,
           "[member]" )
{
   // prog-05 tests addition and subtraction
  string progFile  =   "simfiles/prog-05.sim" ;
  sim . loadProgram ( progFile );

   // subtract SUB=4
   // additon  ADD=5
   // fetch and execute the 8 instructions in this test program and determine
   // if the results are calculated as we expect
  sim . fetch ();
  sim . execute ();
  CHECK ( sim . getIROpcode ()   ==   4 );
  CHECK ( sim . getIRAddress ()   ==   701 );
  CHECK ( sim . getAC ()   ==   41 );

  sim . fetch ();
  sim . execute ();
  CHECK ( sim . getIROpcode ()   ==   5 );
  CHECK ( sim . getIRAddress ()   ==   701 );
  CHECK ( sim . getAC ()   ==   42 );

  sim . fetch ();
  sim . execute ();
  CHECK ( sim . getIROpcode ()   ==   4 );
  CHECK ( sim . getIRAddress ()   ==   702 );
  CHECK ( sim . getAC ()   ==   43 );

  sim . fetch ();
  sim . execute ();
  CHECK ( sim . getIROpcode ()   ==   5 );
  CHECK ( sim . getIRAddress ()   ==   702 );
  CHECK ( sim . getAC ()   ==   42 );

  sim . fetch ();
  sim . execute ();
  CHECK ( sim . getIROpcode ()   ==   4 );
  CHECK ( sim . getIRAddress ()   ==   703 );
  CHECK ( sim . getAC ()   ==   35 );

  sim . fetch ();
  sim . execute ();
  CHECK ( sim . getIROpcode ()   ==   5 );
  CHECK ( sim . getIRAddress ()   ==   704 );
  CHECK ( sim . getAC ()   ==   30 );

  sim . fetch ();
  sim . execute ();
  CHECK ( sim . getIROpcode ()   ==   4 );
  CHECK ( sim . getIRAddress ()   ==   705 );
  CHECK ( sim . getAC ()   ==   15 );

  sim . fetch ();
  sim . execute ();
  CHECK ( sim . getIROpcode ()   ==   5 );
  CHECK ( sim . getIRAddress ()   ==   705 );
  CHECK ( sim . getAC ()   ==   30 );
}


/**
 *  @brief  test jump instruction
 */
TEST_CASE ( "<executeJump()> HypotheticalMachineController test jump instructions" ,
           "[member]" )
{
   // prog-06 tests jump instructions
  string progFile  =   "simfiles/prog-06.sim" ;
  sim . loadProgram ( progFile );

   // JMP=6
  sim . fetch ();
  sim . execute ();
  CHECK ( sim . getIROpcode ()   ==   3 );
  CHECK ( sim . getIRAddress ()   ==   750 );
  CHECK ( sim . getPC ()   ==   750 );

  sim . fetch ();
  sim . execute ();
  CHECK ( sim . getIROpcode ()   ==   3 );
  CHECK ( sim . getIRAddress ()   ==   725 );
  CHECK ( sim . getPC ()   ==   725 );

  sim . fetch ();
  sim . execute ();
  CHECK ( sim . getIROpcode ()   ==   3 );
  CHECK ( sim . getIRAddress ()   ==   790 );
  CHECK ( sim . getPC ()   ==   790 );

  sim . fetch ();
  sim . execute ();
  CHECK ( sim . getIROpcode ()   ==   0 );
  CHECK ( sim . getIRAddress ()   ==   0 );
}


/**
 *  @brief  test full simulation
 */
TEST_CASE ( "<fetch/execute cycles> HypotheticalMachineController test execution simulation of whole programs" ,
           "[simulation]" )
{
   // test program 01 execution
  string progFile  =   "simfiles/prog-01.sim" ;
  sim . loadProgram ( progFile );

   int  cycle  =  sim . runSimulation ();
  CHECK ( cycle  ==   4 );
  CHECK ( sim . getPC ()   ==   303 );
  CHECK ( sim . getAC ()   ==   5 );
  CHECK ( sim . peekAddress ( 941 )   ==   5 );

   // test program 02 execution
   // has an infinite loop so will run for the max cycles
  progFile  =   "simfiles/prog-02.sim" ;
  sim . loadProgram ( progFile );

  cycle  =  sim . runSimulation ();
  CHECK ( cycle  ==   100 );
  CHECK ( sim . getPC ()   ==   52 );
  CHECK ( sim . getAC ()   ==   104 );

}

assg01/assg01-tests.o

assg01/HypotheticalMachineSimulator.cpp

assg01/HypotheticalMachineSimulator.cpp

/**  @file  HypotheticalMachineSimulator.cpp
 *  @brief  HypotheticalMachineSimulator implementations
 *
 *  @author  Student Name
 *  @note    cwid: 111 11 111
 *  @date    Fall 2019
 *  @note    ide:  g++ 8.2.0 / GNU Make 4.2.1
 *
 * Implementation file for our Hypothetical machine class and
 * supporting functions.
 */
#include   "HypotheticalMachineSimulator.hpp"
#include   "SimulatorException.hpp"
#include   < cstdlib >
#include   < iostream >
#include   < iomanip >
#include   < fstream >


/**
 *  @brief  OpcodeMnemonic overload output stream
 *
 * Overload the output stream operator to display the
 * mnemonic opcodes to an output stream in a more human
 * friendly fashion.
 *
 *  @param  out The output stream we should send the representation
 *   of the indicated list to.
 *  @param  opcode A enumerated type of type OPCODE_MNEMONICS
 *   current state of.
 *
 *  @returns  ostream& Returns a reference to the originaly provided
 *   output stream, but after we  have inserted the opcode
 *   representation onto the stream.
 */
ostream  &   operator << ( ostream &  out ,   const   OpcodeMnemonic &  opcode )
{
   switch   ( opcode )
   {
   case  NOOP_HALT :
    out  <<   int ( NOOP_HALT )   <<   " (NOOP_HALT)" ;
     break ;
   case  LOAD :
    out  <<   int ( LOAD )   <<   " (LOAD)" ;
     break ;
   case  STORE :
    out  <<   int ( STORE )   <<   " (STORE)" ;
     break ;
   case  JMP :
    out  <<   int ( JMP )   <<   " (JMP)" ;
     break ;
   case  SUB :
    out  <<   int ( SUB )   <<   " (SUB)" ;
     break ;
   case  ADD :
    out  <<   int ( ADD )   <<   " (ADD)" ;
     break ;
   default :
    out  <<   int ( opcode )   <<   " (Error: unknown/invalid opcode)" ;
   }

   return  out ;
}


/**
 *  @brief  HypotheticalMachineSimulator default constructor
 *
 * Default constructor for the Hypothetical Machine Simulator.
 * This constructor will initialize the hypothetical machine
 * state so that all registers and memory are initialized to
 * empty or NOOP/HALT states.
 */
HypotheticalMachineSimulator :: HypotheticalMachineSimulator ()
{
  memory  =  NULL ;    // no memory defined until we load program;
   this -> reset ();
}


/**
 *  @brief  HypotheticalMachineSimulator destructor
 *
 * Destructor for the hypothetical machine simulator.
 * We dynamically allocate the memory array to hold
 * the values of our simulated memory, which must be
 * released.
 */
HypotheticalMachineSimulator ::~ HypotheticalMachineSimulator ()
{
   this -> reset ();
}


/**
 *  @brief  reset machine
 *
 * Reset the hypothetical machine back to clean state.
 * The actual work of deallocation and initialization
 * is done here so we can call on destruction or reload
 * of a program.
 */
void   HypotheticalMachineSimulator :: reset ()
{
   // if we currently have any memory, deallocate it.
   if   ( memory )
   {
     delete []  memory ;
   }

   // initialize all values to empty/beginning states
  memory  =  NULL ;
  memoryBaseAddress  =  memoryBoundsAddress  =  memorySize  =   0 ;
  pc  =  ac  =  ir  =   0 ;
  irOpcode  =  NOOP_HALT ;
  irAddress  =   0 ;
  memoryAddressList . clear ();
}


/**
 *  @brief  load program
 *
 * Load program from simulation file into memory and registers.
 * We load the description of the initial register contents,
 * and the contents of program instructions and data in memory
 * from a basic text file.  File must be in the expected
 * format or else this function will give an exception
 * and give up.
 *
 *  @exception  Will throw SimulatorException
 *   if file not found, or if file contents are badly formatted
 *   or missing.
 *  @param  programFile A string parameter specifying the
 *   file name we are to load into the hypothetical machine
 *   simulator.  Can be a relative or absolute path name, but
 *   of course the file must exist and be of the correct
 *   input format for this function to work.
 */
void   HypotheticalMachineSimulator :: loadProgram ( string programFile )
{
  ifstream programStream ;

   // open the file as a stream for reading,
   // error check that file loaded successfully
  programStream . open ( programFile . c_str ());
   if   ( ! programStream . is_open ())
   {
     //cerr << "Error: could not open program file: "
     //   << programFile << endl;
    string msg  =   "Error: could not open program file: "   +  programFile ;
     throw   SimulatorException ( msg );
   }

   // parse the file, we expect file in an exact format
  string key ;
   int   value ;

   // registers come first
  programStream  >>  key  >>   value ;
   if   ( key  !=   "PC" )
   {
     throw   SimulatorException ( "Error: expecting PC from program file" );
   }
   this -> pc  =   value ;

  programStream  >>  key  >>   value ;
   if   ( key  !=   "AC" )
   {
     throw   SimulatorException ( "Error: expecting AC from program file" );
   }
   this -> ac  =   value ;

   // Now we load memory, starting with specification of memory bounds
   int  memoryBaseAddress ;
   int  memoryBoundsAddress ;
  programStream  >>  key  >>  memoryBaseAddress  >>  memoryBoundsAddress ;
   if   ( key  !=   "MEM" )
   {
     throw   SimulatorException ( "Error: expecting MEM from program file" );
   }
  initializeMemory ( memoryBaseAddress ,  memoryBoundsAddress );

   // Finally load memory contents.  Remaining lines of file from
   // current line to last line are key/value pairs of
   //    addr value
   // to give the address and contents of memory.
   int  addr ;
   while   ( programStream  >>  addr  >>   value )
   {
    pokeAddress ( addr ,   value );
     // keep track of memory addresses for display purposes
    memoryAddressList . push_back ( addr );
   }
   // sort the list of memory addresses
  memoryAddressList . sort ();
}


/**
 *  @brief  initialize memory
 *
 * Initialize the contents of memory.  Allocate array larget enough to
 * hold memory contents for the program.  Record base and bounds
 * address for memory address translation.  This memory function
 * dynamically allocates enough memory to hold the addresses for the
 * indicated begin and end memory ranges.
 *
 *  @param  memoryBaseAddress The int value for the base or beginning
 *   address of the simulated memory address space for this
 *   simulation.
 *  @param  memoryBoundsAddress The int value for the bounding address,
 *   e.g. the maximum or upper valid address of the simulated memory
 *   address space for this simulation.
 *
 *  @exception  Throws SimulatorException if
 *   address space is invalid.  Currently we support only 4 digit
 *   opcodes XYYY, where the 3 digit YYY specifies a reference
 *   address.  Thus we can only address memory from 000 - 999
 *   given the limits of the expected opcode format.
 */
void   HypotheticalMachineSimulator :: initializeMemory ( int  memoryBaseAddress ,   int  memoryBoundsAddress )
{
   // initializeMemory steps
   // 1. set the base, bounds and size member variables
   // 2.0 if memory is already allocated, free it up first.  This is because we
   //   can reuse a simulation many times, thus we should free past old memory
   //   before allocating a new block of memory
   // 2.1 allocate a new array into the memory member variable of the correct
   //    size to hold requested memory e.g. memory = new int[memorySize];
   // 3. you should ensure that all of of memory is initialized to 0
}


/**
 *  @brief  memory address translation
 *
 * Given a memory address, translate the address to the real array index/memory
 * reference.  In addition we check the final bounds and throw an exception
 * for an illegal memory access attempt.
 *
 *  @param  simAddress The virtual or simulated address that we should translate to
 *   the real address/index.
 *
 *  @exception  Throws SimulatorException if the simAddress
 *   referenced is illegal, e.g. if it is below or above the memory bounds.
 *  @returns  int Returns the calculated real address translation of the simulated
 *   address.
 */
int   HypotheticalMachineSimulator :: translateAddress ( int  simAddress )   const
{
   // need to do the actual translation and return the calculated real address
   // here.  You need to make a calculation using the memoryBaseAddress
   // here to translate into the real address.
   //
   // also, this method should throw an exception if the address is out
   // of bounds.  You should check that the address is less than the base address
   // or greater than the bounds address, and throw an exception if the reference
   // is invalid.  You can trhow an exception like this:
   // throw SimulatorException("translateAddress: Memory bounds access error");
   return   0 ;
}


/**
 *  @brief  poke memory
 *
 * Set (poke) the value of a given address in our memory.
 * The address given is in our simulator address space, so
 * remember it needs to be translated to the real address
 * before poke occurs.
 *
 *  @param  simAddress The virtual simulation address we are to write
 *   the value into.
 *  @param  value The value to be written into the indicated virtual
 *   address.
 */
void   HypotheticalMachineSimulator :: pokeAddress ( int  simAddress ,   int   value )
{
   // 1. you should reuse your translate address function you did in
   // previous step here.  Given the simulation address, translate it
   // to a real address / index into the memory[] array
   // 2. when you have the correct real address / index, you can then simply
   //    "poke" the required value into that address
}


/**
 *  @brief  peek memory
 *
 * Retrive (peek) the value of a given address in our simulator
 * memory.  The address given is in our simulator address space,
 * so remember it needs to be translated to the real address before
 * peek occurs
 *
 *  @param  simAddress The virtual address we should access and read
 *   a value from.
 *
 *  @returns  int The value that is currently in the indicated virtual
 *   memory address we are reading from and returning.
 */
int   HypotheticalMachineSimulator :: peekAddress ( int  simAddress )   const
{
   // 1. again need to start by reusing translateAddress to translate the
   //    simulation memory address into the correct index of our memory[]
   //    array.
   // 2. then when you have the real address / index, you can read that out
   //    of memory[] and return the result
   return   0 ;   // need to fix this and return the actual value you peek from memory
}


/**
 *  @brief  fetch phase
 *
 * Perform the fetch phase of a fetch/execute cycle. Update the
 * machine with results of the fetch.  We load the IR with indicated
 * instruction, which is basically all that occurs during the fetch
 * phase of a cycle.
 */
void   HypotheticalMachineSimulator :: fetch ()
{
   // load the instruction register, using PC as index of
   // instruction to fetch.  You should reuse your peekAddress
   // to get the contents of memory here pointed to by the pc, and then
   // store that instruction into the instruciton register.
}


/**
 *  @brief  execute phase
 *
 * Perform the execute phase of a fetch/execute cycle.  Execute the
 * current instruction residing in the instruction register.  We first
 * translate the instruction into an opcode/reference pair.  Then we
 * delegete work using a lookup table based on the indicated opcode to
 * a function to perform the actual work of the operation.  The
 * PC is also incremented during the execute phase in preparation
 * for the next fetch.
 */
void   HypotheticalMachineSimulator :: execute ()
{
   // You need to do the following tasks for execute
   // 1. if ir < 0 or ir > 9999 thrown an exception as the instruction makes no sense
   // 2. translate the opcode and address.  The ir should have the fetched instruction we
   //    are executing, and you just check that the ir makes sense.  The first decimal digit
   // 2.1 the ir has an int value with 4 decimal (base 10) digits, like XYYY.  The first
   //     decimal digit (the thousands place) represents the instruction.  You can use
   //     integer division to determine opcode represented by the instruction.  You should
   //     extract the opcode and store it into the irOpcode member variable.  You may need to
   //     perform a static_cast<OpcodeMnemonic> on the integer value 0-9 that you determine
   //     is the opcode before assigning it into the irOpcode field.
   // 2.2 Then the remaining 3 digits contain the reference address.  You can use modulus
   //     operator (%) to extract the 3 decimal digits on the end of the instruction and save
   //     them to the irAddress member field.
   // 3. once the instruction is translated into opcode and address, you should increment
   //    the pc (call the incrementPC() function).  Only increment the PC if the opcode is not
   //    a NOOP_HALT instruction.  The NOOP_HALT should cause the program to stop immediately,
   //    including that the pc stops incrementing for the fetch/execute cycles.
   // 4. Finally we need to actually execute the specific instruction indicated by the
   //    irOpcode.  You should create a switch statement using the irOpcode, and call
   //    executeLoad, executeStore, executeJmp, executeSub, executeAdd member functions
   //    given the indicated opcode.  Then we will need to write those 5 functions to
   //    actually perform the simulated machine instructions.
}


/**
 *  @brief  execute load
 *
 * Execute a load instruction.
 *  @pre  current irOpcode is a LOAD when called.
 */
void   HypotheticalMachineSimulator :: executeLoad ()
{
   // load is pretty simple, need to copy the value from memory pointed to by
   // irAddress into the accumulator.  You should use the peekAddress() function
   // here to actually access the value you are loading into the ac
}


/**
 *  @brief  execute store
 *
 * Execute a store instruction.
 *  @pre  current irOpcode is a STORE when called
 */
void   HypotheticalMachineSimulator :: executeStore ()
{
   // store is the reverse of load.  You will need to use pokeAddress() here and the
   // irAddress and ac to copy the value in the ac back to memory pointed to by the
   // irAddress
}


/**
 *  @brief  execute add
 *
 * Execute an add instruction.
 * @[re current irOpcode is an ADD when called
 */
void   HypotheticalMachineSimulator :: executeAdd ()
{
   // add arithemetic instruction is an example of a data processing instruction
   // that performs some calculation.  You need to use peekAddress to get the data
   // value that is referenced by the irAddress, and add that to the accumulator.
   // The result of the addition should be stored back into the accumulator.
}


/**
 *  @brief  execute sub
 *
 * Execute a subtract instruction.
 *  @pre  current irOpcode is a SUB when called
 */
void   HypotheticalMachineSimulator :: executeSub ()
{
   // sub is our other data processing instruction.  Should be pretty much identical
   // to your add, but you want to subtract the referenced data in memory from the ac
   // and store back to ac
}


/**
 *  @brief  execute jmp
 *
 * Execute a jump instruction.
 *  @pre  current irOpcode is a JMP when called
 */
void   HypotheticalMachineSimulator :: executeJmp ()
{
   // The jump instruction works by changing the program counter.  This operation
   // should be the simplest one yet, all you have to do is change the pc to the
   // value asked for in the irAddress reference.
}


/**
 *  @brief  run simulation
 *
 * Run simulation of hypothetical machine.  We run cycles until we hit
 * a NOOP_HALT instruction, or until we reach the specified maximum
 * number of cycles.  This function returns the number of cycles that
 * were executed.  If verbose is specified, the state of the system
 * registers and memory are displayed after each fetch/execute phase.
 *
 *  @param  maxCycles When running the simulation, we will onlyc
 *   simulate this maximum number of fetch/execute cycles,
 *   to avoid infinite loops.  Default maxCycles is 100 if
 *   not specified.
 *  @param  verbose If true we will display the full system
 *   state to standard output after each fetch and each
 *   execute cycle. Defaults to false if not specified.
 *
 *  @returns  int Returns the number of actual fetch/execute
 *   cycles that were performed.  This cycles shoule be
 *   cycles <= maxCycles given when this function is called.
 */
int   HypotheticalMachineSimulator :: runSimulation ( int  maxCycles ,   bool  verbose )
{
   int  cycle  =   0 ;

   // Finally for step 7 I have given you an implementation of this
   // function.  It uses your fetch() and execute() functions (which in)
   // turn use the others you implemented) to run a full simulation of
   // our hypothetical machine.  Uncomment the while loop and then all of
   // your unit tests should be passing if the previous functions were fully
   // implemented correctly and as asked for.  If unit tests are not passing
   // after uncommenting the code below, you shold go back to the first failing
   // unit test and figure out and fix that issue, and proceed fixing issues 1
   // at a time.
   /*
     bool done = false;

     while (!done)
     {
     // perform fetch stage
     fetch();
     irOpcode = NOOP_HALT;
     irAddress = 0;
     if (verbose)
     {
      cout << "==================== cycle: " << cycle + 1 << endl;
      cout << "-------------------- fetch" << endl;
      cout << *this;
     }

     // perform execute stage
     execute();
     if (verbose)
     {
      cout << "-------------------- execute" << endl;
      cout << *this;
     }

     // increment cycle counter for next cycle
     cycle++;

     // we are done if we exceed the maximum number of
     // cycles to simulate
     if (cycle >= maxCycles)
     {
      done = true;
     }

     // or we are done when we hit a NOOP_HALT
     // instruction
     if (irOpcode == NOOP_HALT)
     {
      done = true;
     }
     }
   */

   return  cycle ;
}


/**
 *  @brief  memory base address accessor
 *
 *  @returns  int Returns the current virtual memory base address.
 */
int   HypotheticalMachineSimulator :: getMemoryBaseAddress ()   const
{
   return  memoryBaseAddress ;
}


/**
 *  @brief  memory bounds address accessor
 *
 *  @returns  int Returns the current virtual upper bounds address.
 */
int   HypotheticalMachineSimulator :: getMemoryBoundsAddress ()   const
{
   return  memoryBoundsAddress ;
}


/**
 *  @brief  memory size accessor
 *
 *  @returns  int Returns the current total memory size for this
 *   simulation.
 */
int   HypotheticalMachineSimulator :: getMemorySize ()   const
{
   return  memorySize ;
}


/**
 *  @brief  program counter accessor
 *
 *  @returns  int Returns the current PC (program counter)
 *   register of the simulation.
 */
int   HypotheticalMachineSimulator :: getPC ()   const
{
   return  pc ;
}


/**
 *  @brief  program counter increment
 *
 * Mutator to increment pc to next location in memory.  a convenience
 * method in case we ever need to do something more complicated in
 * addition to pc increment at end of execute stage.
 */
void   HypotheticalMachineSimulator :: incrementPC ()
{
  pc ++ ;
}


/**
 *  @brief  accumulator accessor
 *
 *  @returns  int Returns the current value of the simulator
 *   AC (accumulator) register.
 */
int   HypotheticalMachineSimulator :: getAC ()   const
{
   return  ac ;
}


/**
 *  @brief  instruction register accessor
 *
 *  @returns  int Returns the current value of the simulator
 *   IR (instruction regiser).
 */
int   HypotheticalMachineSimulator :: getIR ()   const
{
   return  ir ;
}


/**
 *  @brief  instruction register opcode accessor
 *
 *  @returns  int Returns the translated opcode from the
 *   fetched instruction fetched into the instruction
 *   register.
 */
int   HypotheticalMachineSimulator :: getIROpcode ()   const
{
   return  irOpcode ;
}


/**
 *  @brief  instruction register address accessor
 *
 *  @returns  int Returns the referenced address of the current
 *   loaded instruction/opcode in the instruction register.
 */
int   HypotheticalMachineSimulator :: getIRAddress ()   const
{
   return  irAddress ;
}


/**
 * @brief  overload output stream operator for simulation
 *
 * Overload the output stream operator so that we can display current
 * state of Hypothetical Machine simulation to a stream.
 *
 *  @param  out The output stream we should send the representation
 *   of the current machine state to.
 *  @param  sim The HypotheticalMachineSimulator object to display
 *   current state of.
 *
 *  @returns  ostream& Returns a reference to the originaly provided
 *   output stream, but after we  have inserted current hypothetical
 *   machine state onto the stream.
 */
ostream &   operator << ( ostream &  out ,   const   HypotheticalMachineSimulator &  sim )
{
  out  <<   "CPU Registers"   <<  endl
       <<   "-------------"   <<  endl
       <<   "PC: "   <<  sim . pc  <<  endl
       <<   "AC: "   <<  sim . ac  <<  endl
       <<   "IR: "   <<  sim . ir  <<  endl ;
   if   ( sim . irOpcode  !=  NOOP_HALT )
   {
    out  <<   "    opcode : "   <<  sim . irOpcode  <<  endl ;
   }
   if   ( sim . irAddress  !=   0 )
   {
    out  <<   "    address: "   <<  sim . irAddress  <<  endl ;
   }
  out  <<  endl ;

  out  <<   "Memory"   <<  endl
       <<   "------"   <<  endl ;
   for   ( auto addr  :  sim . memoryAddressList )
   {
    cout  <<  setw ( 3 )   <<  left  <<  addr  <<   ": "
          <<  sim . peekAddress ( addr )   <<  endl ;
   }

  cout  <<  endl ;

   return  out ;
}

assg01/HypotheticalMachineSimulator.hpp

/** @file HypotheticalMachineSimulator.hpp * @brief HypotheticalMachineSimulator API/Includes * * @author Jane Programmer * @note cwid: 123456 * @date Fall 2020 * @note ide: VS Code Editor/IDE ; GNU gcc tools * * Header include file for our Hypothetical machine class. * Definition of simulator API goes in this file. Implementation * of member methods is found in corresponding .cpp file. */ #ifndef HYPOTHETICAL_MACHINE_SIMULATOR_HPP #define HYPOTHETICAL_MACHINE_SIMULATOR_HPP #include "SimulatorException.hpp" #include <fstream> #include <iostream> #include <iomanip> #include <iostream> #include <list> #include <string> using namespace std; /** @enum OpcodeMnemonic * @brief opcode mnemonic codes * * opcode mnemonic codes for the hypothetical machine simulator * and overloaded output stream operator to display opcodes * to a stream in more human friendly forms. */ enum OpcodeMnemonic { NOOP_HALT = 0, LOAD = 1, STORE = 2, JMP = 3, SUB = 4, ADD = 5 }; ostream& operator<<(ostream& out, const OpcodeMnemonic& opcode); /** @class HypotheticalMachineSimulator * @brief Hypothetical Machine Simulation class * * Perform simulation of the Stallings 7th ed. hypothetical machine. * This simulator can read in initial state of a hypothetical machine, * the registers (PC program counter, AC accumulator) and initial * memory contents, which may include code and data. Functions are * provided to simulate fetch/execute cycles for the hypothetical * machine, and support functions to implement the different * supported operation opcodes of the machine (like memory peek/poke). */ class HypotheticalMachineSimulator { private: /// @brief An array of memory contents for our hypothetical machine int* memory; /// @brief Base address of simulated memory int memoryBaseAddress; /// @brief Bounding address (end) of simulated memory int memoryBoundsAddress; /// @brief Total size of simulated memory in machine int memorySize; /// @brief Program Counter register int pc; /// @brief Accumulator register int ac; /// @brief Instruction register int ir; /// @brief Translated opcode of current instruction OpcodeMnemonic irOpcode; /// @brief Translated reference address of current instruction int irAddress; /// @brief List of memory locations when displaying system state list<int> memoryAddressList; public: // constructors and destructors HypotheticalMachineSimulator(); ~HypotheticalMachineSimulator(); void reset(); // program loading and initializtion void loadProgram(string simulationFile); void initializeMemory(int memoryBaseAddress, int memoryBoundsAddress); // program execution int translateAddress(int virtAddr) const; void pokeAddress(int simAddr, int value); int peekAddress(int simAddr) const; void fetch(); void execute(); void executeLoad(); void executeStore(); void executeAdd(); void executeSub(); void executeJmp(); int runSimulation(int maxCycles = 100, bool verbose = false); // accessor methods, mostly for testing int getMemoryBaseAddress() const; int getMemoryBoundsAddress() const; int getMemorySize() const; int getPC() const; void incrementPC(); int getAC() const; int getIR() const; int getIROpcode() const; int getIRAddress() const; // overloaded operators friend ostream& operator<<(ostream& out, const HypotheticalMachineSimulator& sim); }; #endif // HYPOTHETICAL_MACHINE_SIMULATOR_HPP

assg01/HypotheticalMachineSimulator.o

assg01/Makefile

# source files in this project (for beautification) PROJECT_NAME=assg01 sources = $(PROJECT_NAME)-tests.cpp \ $(PROJECT_NAME)-sim.cpp \ HypotheticalMachineSimulator.hpp \ HypotheticalMachineSimulator.cpp # template files, list all files that define template classes # or functions and should not be compiled separately (template # is included where used) template-files = # object file targets used for both testing and simulation assg-objects = HypotheticalMachineSimulator.o # common targets and variables used for all assignments/projects include ../../include/Makefile.inc

assg01/run-system-tests

#!/bin/bash # # Run all system tests. We run the tests and capture their standard output (plus any error stream messages) # to an output file. A system test passes if the difference of the output matches a reference/correct example # output file. No differences means the system test passes, but differences indicate problems and the # system test fails. # list of system test simulations to run tests="prog-01 prog-02 prog-03 prog-04 prog-05 prog-06 prog-07 prog-08 prog-09 prog-10" NUM_TESTS=10 # directories for input and output files simdir="simfiles" outdir="output" # constants for colored terminal output (https://misc.flogisoft.com/bash/tip_colors_and_formatting) GREEN="\e[1m\e[92m" # this is actually bold light green RED="\e[1m\e[91m" # bold light red NORMAL="\e[0m" # create temporary directory for output, remove any old output first rm -rf ${outdir} mkdir -p ${outdir} # run all of the system tests declare -i passed=0 for test in $tests do simfile=${simdir}/${test}.sim resfile=${simdir}/${test}.res outfile=${outdir}/${test}.out ./sim 100 ${simfile} > ${outfile} 2>&1 # diff returns 0 if files are identical, which means system test passed diff --report-identical-files --brief --ignore-all-space --ignore-blank-lines --ignore-tab-expansion --ignore-case ${outfile} ${resfile} > /dev/null if [ $? -eq 0 ] then echo -e "System test ${test}: ${GREEN}PASSED${NORMAL}" passed=$(( passed + 1 )) else echo -e "System test ${test}: ${RED}FAILED${NORMAL}" fi done if [ ${passed} -eq ${NUM_TESTS} ] then echo -e "${GREEN}===============================================================================${NORMAL}" echo -e "${GREEN}All system tests passed ${NORMAL} (${passed} tests passed of ${NUM_TESTS} system tests)" else echo -e "${RED}===============================================================================${NORMAL}" echo -e "${RED}System test failures detected${NORMAL} (${passed} tests passed of ${NUM_TESTS} system tests)" fi

assg01/simfiles/prog-01.res

assg01/simfiles/prog-01.sim

PC 300 AC 0 MEM 300 1000 300 1940 301 5941 302 2941 940 0003 941 0002

assg01/simfiles/prog-02.res

assg01/simfiles/prog-02.sim

PC 050 AC 0 MEM 50 150 050 1141 051 5140 052 3051 053 4142 140 0002 141 0004 142 0008

assg01/simfiles/prog-03.res

assg01/simfiles/prog-03.sim

PC 100 AC 0 MEM 100 200 100 1150 101 1175 102 1190 103 1200 150 42 175 -5 190 123

assg01/simfiles/prog-04.res

assg01/simfiles/prog-04.sim

PC 200 AC 32 MEM 200 300 200 2250 201 2242 202 2287 203 2199 242 1 250 1 287 1

assg01/simfiles/prog-05.res

assg01/simfiles/prog-05.sim

PC 600 AC 42 MEM 600 800 600 4701 601 5701 602 4702 603 5702 604 4703 605 5704 606 4705 607 5705 701 1 702 -1 703 7 704 -5 705 15

assg01/simfiles/prog-06.res

assg01/simfiles/prog-06.sim

PC 722 AC 1 MEM 722 822 722 3750 725 3790 750 3725 790 0

assg01/simfiles/prog-07.res

assg01/simfiles/prog-07.sim

PC 300 AC 2 MEM 300 1000 300 4940 301 5941 302 2941 940 0001 941 0002

assg01/simfiles/prog-08.res

assg01/simfiles/prog-08.sim

PC 300 AC 0 MEM 300 1000 300 1941 301 5940 302 3301 940 0002 941 0004

assg01/simfiles/prog-09.res

assg01/simfiles/prog-09.sim

PC 300 AC 0 MEM 300 1000 300 3301 301 4941 302 1940 940 0004 941 0005

assg01/simfiles/prog-10.res

assg01/simfiles/prog-10.sim

PC 300 AC 0003 MEM 300 1000 300 5940 301 4941 302 3301 940 0004 941 0005

assg01/simfiles/prog-ps01.res

assg01/simfiles/prog-ps01.sim

PC 300 AC 0000 MEM 300 1000 300 1941 301 5940 302 2941 940 0003 941 0002

assg01/simfiles/prog-ps02.res

assg01/simfiles/prog-ps02.sim

PC 300 AC -1 MEM 300 1000 300 5940 301 3300 302 2941 940 0002 941 0004

assg01/simfiles/prog-ps03.res

assg01/simfiles/prog-ps03.sim

PC 300 AC 0 MEM 300 1000 300 1940 301 4941 302 2940 940 0003 941 0009

assg01/simfiles/prog-ps04.res

assg01/simfiles/prog-ps04.sim

PC 300 AC 0 MEM 300 1000 300 1941 301 4940 302 3301 940 0001 941 0001