In the first part of this series I discussed the ALU of our CPU. In this part we’ll take a look at the data and program storage and how to address the data. I’ll also discuss how the ALU is controlled.
Like I said in part 1, I decided to go with the harvard-archictecture, which means that our program and data storage are physically and logically different parts of the CPU. So let’s start with the data storage. First of all, we’ll need some registers to quickly store data from calculations that will be used in future calculations.
But where do we put these registers? Let’s take a look at our ALU from part 1:
On the top-left corner of this block diagram, there are two 8-Bit numbers, labelled A and B. These are buffers. The values for the next calculation will be loaded from the register-file into these buffers.
The result R can be saved in the register-file after the calculations were made. I don’t even want to go into very much details right now. First let’s just think about the instruction memory.
This is the place where our micro-programs are going to be stored. Of course you can’t just simply create such programs in a high-level language. Without a compiler you’d have to either hard-wire the program onto the CPU or you’d have to flash a ROM with binary numbers, where each address of the ROM is one line of code. I’ll go with the second method. For this purpose, let’s think about one micro-instruction (a.k.a one line of code).
One micro instruction
One thing to note: A lot of these features are not yet implemented in our CPU, but I think it’s a good idea to discuss the features, we want to support, before actually designing the hardware, so you’ll need to use your imagination on a lot of things for now, but I promise you, everything will be covered by the end of this series!
Instruction length: 0-Bit
First of all, we need to tell the ALU what to do next. As our ALU supports 3 operations for sure (AND, ADD, NOT), we’ll at least need two bits to be able to address all of these operations. It’s also not bad to simply push through a value without changing it. This function can come in handy when you simply want to shift a data-word without using it in any arithmetic or logic function. So our ALU-OP table looks like this:
As you can see here, I forgot to add the NOT and the push through operations in the ALU from step 1. That’s why you should think about the features first, before starting with the design! Let’s just pretend I didn’t and these functions are here.
Our instruction-length is now: 2-Bit
Next we’ll absolutely have to tell the ALU which registers to use for the next calculations and also where to put the result. As we have a register-file with 16 entries, we’ll need 4 bits to address one entry. So we’ll need 3*4 = 12 additional bits in our instruction.
Instruction length: 14-Bit
We’ll also need one bit to enable the write-back to the register file, because maybe we don’t even want to save it back.
Instruction length: 15-Bit
There is the option to shift the result. The programmer can choose from the following options: don’t shift, (logical) shift left and (logical) shift right. This CPU will not have an option for arithmetic shifting built into the hardware. So the application programmer has 3 options, therefore we’ll need to add 2-Bits:
(00) Don’t shift
(01) Shift left
(10) Shift right
So our instruction length changes accordingly:
Instruction length: 17-Bit
What’s another important thing when programming? I guess one of the most important things is the possibility to jump to a specific position in the code, for example when an if-condition evaluates to false. Do you remember the additional flags that our ALU can output? In part 1 I passed through the last carry-out from the carry-ripple adder as a flag, so it can be used to check, whether an overflow occurred during the last calculation, or not. I think another two important things are a flag that gets pulled high when the last result was 0 and another one that gets high when the last calculation’s result was negative.
Ok, so now we have the following options:
(00) Don’t jump
(01) Jump, if the last result was negative
(10) Jump, if the last result was all zeroes
(11) Jump, if the last calculation overflowed
So we’ll need another 2-Bits to cover these options:
Instruction length: 19-Bit
Then we’ll obviously need to tell our CPU where to jump. I’ll now simply define, that our program-storage will have 256 entries. That means, that we’ll need to use another 8-Bits to be able to address the entire instruction-memory space.
Instruction length: 27-Bit
Okay, so now we should have everything covered, right? Well almost. There is one last thing: It’s nice and dandy to save all these values in our register-file, but that’s not enough. We’ll need to have some kind of storage to hold the values, when they are not immediately needed. We’ll need RAM!
Okay, so that makes up for additional 5-Bits:
The MEM-ADDR register will hold the address of the external memory to write to and the EN-MEM-ADDR enables this register.
This register will hold the data we want to write to the external memory and the flag will enable it.
(3, 4) RW/MEM-SELECT
The first flag is the read/write flag. The second one is the memory-select flag, which I’ll discuss in more detail in a later part of this series.
This flag will tell the ALU to use data from the MEM-BUFF register instead of the register-file. This is useful when you loaded a value from the RAM and you want to use this value instead of one, that’s saved in the register-file.
Instruction length: 32-Bit
And that’s our final instruction length. So that means that our instruction memory is a 256×32 ROM, which means it can hold 8KiB of application code.
Back to the data storage
So in the last paragraph you actually learned everything that you need to know, to construct the data storage! You now know, that we have 16 registers for storing data between calculations and the possibility to store another 2.048 Bit in an external RAM. We can also store programs that have 256 instructions in our instruction-ROM. The last thing we need is something that tells the register-file which values are needed.
For this case we have the 4-Bit address in our micro-instructions and one decoder per value (A, B, R) will be used. So our ALU (with the updated outputs and functions), the shifter and the register-file looks like this:
Please view the link. The result is way too large, to show it in a screenshot!
Also note, that I only built one of the 16 registers, the other 15 look exactly the same, but they would take up so much space, that I decided to leave them away. I guess you should get the idea.
Also a lot of other signals are yet to be connected, however I tried to label everything, so you know where they will go.
In the next part …
… I’ll build everything that’s necessary to write micro-programs and to control the ALU.