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 them. I’ll also discuss how the ALU is controlled.
Like I said in part 1, I decided to go with the Harvard-architecture, which means that the 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:
On the top-left corner of this block diagram, there are two 8-Bit numbers, labeled A and B, which are stored in buffers. The values for the next calculation will be loaded from the register-file.
The result (R) of a calculation can be stored in the register-file. However, let’s think about the instruction memory first.
This is the place where our micro-programs are going to be stored. Unfortunately, you can’t just create such programs in a high-level language without a compiler. You’d either have to hard-wire the program 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.
One micro instruction
One thing to note: A lot of these features are not yet implemented in our CPU. However, I think it’s a good idea to discuss the features we want to support before actually designing the hardware.
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 them. It’s also not bad to simply pass a value through 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:
Our instruction-length is now: 2-Bit
Next, we’ll 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 each of them. 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 sometimes, the result should not be saved in a register.
Instruction length: 15-Bit
There is the option to shift the result. The programmer can choose from the following alternatives: 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
Another important aspect in programming 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? The ALU has a line that gets pulled HIGH when the last was zero. This can be used to check whether two numbers are equal. Two additional important flags are the negative and adder-overflow flags.
Therefore, we have the following options:
(00) Don’t jump
(01) Jump, if the last result was negative
(10) Jump, if the last result was zero
(11) Jump, if an overflow occurred
So we’ll need two additional bits to cover these options:
Instruction length: 19-Bit
Then, we’ll obviously need to tell our CPU where to jump to. I’ll simply define, that our program-storage will have 256 entries. Therefore 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 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 bit is the read/write flag and 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 data word 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 can hold 8KiB of application code.
Back to the data storage
In the last paragraph, you learned everything you need to know to construct the data storage! You 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, we have the 4-Bit address in our micro-instructions and one decoder per value (A, B, R). So our ALU (with the updated outputs and functions), the shifter and the register-file look 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 omit them.
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.