Overview
In this assignment we will be using what you have learned about the Linux command line, Make build system, g++
compiler and unit testing to build an example small command line application to simulate a hypothetical machine
architecture. 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
Practice using Unix command line development tools.
Familiarize ourselves with test driven development and developing software to pass unit tests.
See more examples of compiling and building software using standard gnu and make build tools.
Become familiar with the class assignment structure of unit tests and system tests.
Practice using Unix tools for writing code documentation and code to conform to style guidelines.
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 we will be implementing a simulator, using Unix development tools, of the hypothetical machine architecture description given in our Stalling textbook chapter 01. The hypothetical machine describe is simple, and is meant to illustrate the basics of a 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:
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 the Unix development tools, like make and the compiler and 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.2 the files are named:
assg01.2-tests.cpp
assg01.2-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.
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:
Unit Test Tasks
You should take a look at the test cases and assertions defined in the assg01.2-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 hints.
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. 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];
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 3 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() funciton, 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() needs a while loop that calls fetch() followed by calling execute(). The loop should keep iterating until either you come to a NOOP_HALT instruction, or until you iterate maxCycles, which by default is 100 cycles for this function. You can ignore the
verbose parameter for now, it defaults to false meaning that not output is generated by the function by default.
If you need any help related to Operation System then please contact at Codersarts or comments below of this post.
Comments