Introducing our Virtual Machine Project.
In this short series, we’re going to build our own virtual machine. When I say that we’re going to build a virtual machine, what I mean is not the kind of virtual machine you may be familiar with, such as a hosted virtual server for example, but instead the kind of virtual machine which is more like an emulator. In fact, the only reason I’m not calling it an emulator is that it doesn’t emulate any real-world hardware, but instead a brand new ‘imaginary’ CPU which we’re going to design as we go. Think along the lines of MSIL, LLVM, JVM, these are all professional virtual machines in the same vain, though the goal of this series is not to create quite as complete or professional a result, rather, it’s an educational exercise.
A simplified model of a computer.
Before we can build a virtual computer, we need some basic understanding of how a computer system works. For this reason, we’re first going to take a look at a simplified model of how a computer functions. We’ll then have a basis to begin writing a simulation of this model in our pascal code. Much of what we are about to learn is intentionally simplified for the sake of the intuition that it brings, but not precisely accurate with modern computers. I’ll be brushing over that detail, which you are welcome to investigate further for yourself, in order to keep things simple. Rest assured however, the fundamental principals that I hope to convey here, still apply to modern computers.
What we’ll start with then, is described in the following diagram.
All computers will have, at least, the three components labeled as ‘Clock’, ‘CPU’, and ‘Memory’ in the diagram.
The details may vary. For example, the clock is now often built into the CPU. There are various types of memory, and there are a wide range of CPU’s available. What matters however is that each of these components will exist in some form, and serve approximately the same purpose.
The memory component is one of the most important components in a computer system. It essentially stores a large list of numbers. The diagram stores six numbers, [01,05,03,02,02], which are indexed from zero through five on the left, however, this is the component of a computer that we’d typically refer to in terms of Gigabytes. For the sake of example, a memory which stores just one gigabyte of data, stores around 1.07B such numbers!
The importance of the memory component draws from the fact that everything that a computer does is essentially a computation on numbers in the memory. For example, if you press a key on the keyboard, that key is translated by the keyboards hardware into a number, and that number is placed via the circuitry into memory. The computer (specifically the CPU which we’ll discuss in a moment), is able to read that number from memory to know which key you pressed. It might then write numbers back into memory to be read by the graphics hardware, to display the character that you typed on the screen. Essentially, every piece of hardware connects to your computers CPU via memory.
** As a side note, that’s not entirely true, as I said, I’m simplifying here – but if you wish to understand further, we’ll briefly discuss ‘buses’ later, and you can continue to research from there if curious.
So what is this CPU component which I’ve mentioned already?
The CPU is the component of a computer which performs computation, or in other words, this component is the one which does the ‘work’ that you’ve tasked your computer with doing. It essentially follows a sequence of small instructions. It gets it’s list of instructions, again, from the memory. That is to say, when you ask the computer to run a program (application), that application is merely a list of instructions, albeit typically a very long list, which is loaded into memory so that the CPU can see it.
Every CPU is furnished with some small amounts of internal memory for storing special values called ‘registers’. Typically a CPU will have many registers. The number of registers and their purpose will vary among different CPU’s from different manufacturers, and even their names will vary, however, all CPU’s will have at least one register which behaves as a ‘program counter’ and at least one general purpose register which we’ll refer to as an ‘accumulator’ register for the time being.
In order to understand how these registers are used, we’re going to take a look at the operations of a CPU in single steps, known as ‘instruction cycles’.
Thus far, I’ve not mentioned the ‘Clock’ component of our model computer. The clock simply generates a repeating signal at regular intervals. Think of this clock as being something like the second hand on a wall-clock or wrist watch, except that rather than visually displaying time, it sends electrical pulses to the CPU. It’s also much faster than a second hand, in that it sends these pulses billions of times per second.
Each time the clock sends a pulse to the CPU, it triggers the CPU to perform some action. In our model, that action is to perform an ‘instruction cycle’. ** In real hardware, a single instruction cycle may take multiple clock pulses, but for the sake of our simplification we’re going to say that a complete instruction cycle will happen for each clock pulse.
If you’re feeling at all confused at this point, we’re about to walk through an instruction cycle step-by-step, and I hope this will clarify.
Step 1 – The clock sends a pulse to our CPU, telling it that the time has come to do something…
Step 2 – Our CPU needs an instruction.
Remember that I said CPU’s will have a register called a ‘program counter’? This is where the program counter comes in. The CPU presents the value which is currently contained in it’s program counter register onto the ‘Address Bus’. The address bus is simply a series of wires between the CPU and the memory, allowing our CPU to specify the location in memory that is is interested in. Currently, the program counter is set to zero, so our CPU is interested in the very first memory location (indexed as zero in the diagram). Our CPU wants to read an instruction from that first memory location, and so, as it sets the address bus to zero, it also sets the ‘Read-Write’ pin to zero. This is because a zero on the read-write pin tells the memory that the CPU wants to read a value from the memory, where-as, a one on the read-write pin would tell the memory that the CPU is going to store a value into memory….
Step 3 – The memory responds.
Now that the memory sees an address on the address bus (zero), and the ‘read’ request on the read-write pin (also zero), it looks up that zero location in memory, which in our diagram contains a value of one…
Our memory places this value of one onto the data bus.
The data bus is another series of wires connecting the CPU to the memory just like the address bus, but instead of representing a location in memory, the data bus is used to carry the data from memory locations to and from the CPU…
Step 4 – The CPU reacts.
Our CPU now sees a value of one on the data bus. At this time, the CPU is expecting an instruction and so it considers the value of one as an instruction. But what does the value mean? Well, all CPU’s are hard-wired to relate particular numbers to operations which they are able to perform. This is known as an instruction set. As we’re working with an imaginary CPU, we can decide for ourselves what each of these instructions means…
In the above diagram, I’ve added three instructions to our CPU’s instruction set. These are the ‘LOAD’ instruction represented by one, the ‘SAVE’ instruction represented by two, and the ‘ADD’ instruction represented by three. Our CPU just received a one from memory for it’s first instruction, and so it’s going to ‘LOAD’ something.
The ‘LOAD’ instruction is going to tell our CPU that it needs to read another value from the memory, and so the CPU will update the value on the address bus to the next memory location (one) and place that value on the address bus, again, it sets the read-write pin to perform a ‘read’ from memory….
Step 5 – The memory responds again…
Just as before, when reading the instruction from memory, the memory has been presented with a location (this time one), and a read request. The memory looks up location one, which contains the number five….
It then places that number five onto the data bus so that the CPU can see it.
Step 6 – Our CPU completes the load instruction.
At this point, our CPU has the value it wanted from memory, but it needs to do something with it. We’re going to say that our ‘LOAD’ instruction takes that value and places it into the ‘Accumulator’ register. The accumulator register, just like the program counter register, is just a small piece of memory inside the CPU it’s self, and so the CPU copies the five from the data bus into this internal ‘accumulator’ memory location.
Step 7 – The CPU re-configures for the next instruction.
At this point, our CPU has completed one instruction. The instruction did not do much, it simply transferred the value of five from location one in memory, and it took seven steps for me to describe the process! Still, the instruction cycle is not yet complete. The CPU is now going to wait for another instruction to perform, but first, it must update it’s program counter. If the clock were to pulse again right now, with the program counter stuck at zero, the CPU would simply repeat the load instruction with the same result. So the CPU progresses it’s program counter two places, one for the instruction that it read from memory location zero, and another for the value that it read from location one, and so the next instruction will come from location two…
At this point, our CPU has completed it’s instruction cycle, and will wait for the clock to pulse again, signaling that the next instruction should begin.
The second instruction…
Okay, great, we walked through an instruction cycle in seven steps, with diagrams, and we have a number five in the accumulator register of our CPU, but what good is that? Well, we have six values in memory, and we’re only up to the third (at position two), there are two more instructions contained within that data. So lets walk those instructions, albeit a little quicker this time.
The clock sends another pulse…
Our CPU now needs another instruction, and it’s program counter contains a two, so it places that two onto the address bus, and sets the read-write pin to read. The memory sees this, looks up the value in location two (in this case the value is three), and it places that value onto the data bus..
The CPU sees the number three come back on the data bus, this represents the ‘ADD’ instruction…
So our CPU is going to ‘ADD’ something, but what? Well, it’s going to request another value from the memory, and add that value to the one currently stored in the accumulator register. So the CPU updates the address bus to location three and keeps the read-write pin set to read. Seeing this request, the memory looks up the value stored at location three (in this case a two), and puts that two onto the data bus.
Seeing the number two come back on the data bus, the CPU adds this value to the one in it’s accumulator.
The CPU has now completed it’s second instruction, and so it updates it’s program counter again. It was at location two, and an instruction was read from that memory location, the instruction needed another number from memory, which it read from location three, and so the next instruction is stored at location four in memory. The program counter is updated to four, and the CPU waits again for the clock to signal….
At this point, our CPU has actually already done something useful, it’s added two numbers together. This is the very origin of computing, to perform calculations, and so we’ve accomplished something! However, to us humans the internal workings of the CPU are invisible. It’s all good and well that we have been able to add two numbers together, but we need to see the results of the calculation. So the CPU must put that result somewhere for us to see. Lets walk through the last instruction, it has one important difference from the instructions we’ve seen so far… So please, stick around for one more instruction cycle!
The final instruction
Our clock pulses once more, and our CPU has a value of four in the program counter. The CPU places that number four onto the address bus, sets the read-write pin to read, and the memory returns the value from location four on the data bus…
To our CPU, the number two is the ‘SAVE’ instruction.
Our ‘SAVE’ instruction is going to save the value from the accumulator to the very next location in memory. So our CPU sets the value on the address bus to the next location, five in this case, BUT, this is where the CPU does something slightly different. Instead of setting the read-write pin to read, it sets the pin to write. After all, the CPU wants to write something into a memory location this time instead of reading.
The CPU must also put the value seven, from the accumulator register, somewhere for the memory to see it. The CPU places the value seven onto the data-bus…
The memory now sees that the read-write pin is set to write, looking at the address bus it knows to write something to location five, and looking at the data bus it knows that the value seven should be written. The memory dutifully copies the seven from the data bus, to location five…
At this point, the circuitry of our imaginary computer system is reading the value seven from the last location in memory and displaying it to us. In something as simple as a calculator for example, it would be displaying seven on the L.C.D screen above it’s keys, or in a modern computer, more sophisticated processes take place which ultimately display this number seven on the display screen. Ultimately, we do not need to go into too much detail yet on how this result is being displayed, we simply imagine the hardware which is absent from my diagrams, connecting the memory to some output that we can read.
Our CPU has completed it’s first useful function! Phew – it took a lot of steps to do something so trivial as adding two numbers together. It took three instruction cycles, each broken down into smaller steps of reading from or writing to memory, and performing the calculation it’s self, but ultimately we got there in the end. The good news is, what just took you possibly several minutes to read, a real CPU would do in mere nano-seconds! As I said, that clock pulses several million or billion times per second, and so tedious as the workload may be, it happens very quickly.