Meet Nandy

Taking a short break from creating circuits to jot down some preliminary thoughts on the overall architecture for Nandy. While I could probably be a bit more ambitious for now I think the following would be a reasonable goal:

My first computer was an 8-bit processor with 128K of RAM; so I’ll feel right at home working with something this primitive 🙂

Instruction Set

Since the instruction set is kind of the heart and soul of the CPU let’s talk about that in more detail. I spent some time reading through the technical manuals of several classic CPUs (like the Zilog Z80 and Motorola 6809) to get a feel for their capabilities. Ultimately the MIPS series of CPUs caught my interest due to its relative simplicity compared to the others.

Not that MIPS is a simple processor…not at all! All that instruction pipelining stuff, wow. A project for another time maybe 🙂

Registers & Memory

This choice will help simplify the CPU design in some aspects (e.g. shear number of gates required) and make it more complicated in other ways (e.g. instruction encoding, numeric limitations, etc.). I’ll talk about those issues when I run into them 🙂

Operations

I’m new at this. I’m building a CPU from NAND gates. I don’t really want to design and build a 50+ instruction set CPU…at least not yet. So how few operations can I get away with and still have something that can be called a “computer”?

Well, one of the primary things computers do is math. Let’s start there:

I’m going to avoid multiplication and division (beyond bit shifting). Those can calculations get rather complicated to do with logic gates and can actually be performed using repeated addition or subtraction in software.

I’m also going to limit Nandy to integer math, no floating-point values at this point. If I’m feeling ambitious I might look into either hardware or software implementation of fixed-point arithmetic; which would give some ability to work with non-integer (“real”) values.

In addition to mathematical operations, I’ll also need some ability to “branch” in order to perform if/then/else type logic. I’m thinking I’ll at least need:

branch if zero

branch if negative

branch always

That should cover most “comparison” type expressions (e.g. if A < B then …) and “goto” branching for sub-routine calls and such.

Since there are only a handful of registers in a CPU for storing data but a “huge” amount of RAM nearby I’ll need some instructions for accessing memory (RAM). As I briefly mentioned above I was inspired by the RISC / load-store architecture which means most of my instructions will operate only on registers and only a few of them will actually write to and read from memory:

read 16-bit word from RAM and store in a register

write a register value into a 16-bit word in RAM

Finally, I’ll also need some way to load “immediate” values into registers; e.g. “A = 3”:

load a register with a 16-bit constant

This last one will be tricky. Nandy’s registers will be 16-bits wide and so will it’s instructions. So how do I load a 16-bit register with a 16-bit value when the instruction is only 16-bits wide? Note, somewhere in that 16-bit instruction I have to encode “load this immediate value” (along with the other instructions too) so at least a couple bits need to be used for that…leaving fewer than 16 bits for that 16-bit value…

Food for thought; problem for another day 🙂

Well, in total that puts me at around 15 instructions. Not too bad!

Addressing Modes

So what are addressing modes? Basically these are various methods the CPU can use to access data; which is typically placed in some location (address) in the computer’s RAM.

Nandy is a bit unique in that two of the three addressing modes I’m considering don’t actually address RAM, but instead refer to the CPUs internal registers or constant values. The third mode though is the primary (well, only actually) means to access data in RAM.

Register-Direct

As I mentioned above, most of the Nandy instructions will operate only on registers; some CPUs refer to this as the register-direct addressing mode. Basically the idea is that I load registers with values of interest (either with immediate values or from RAM) then perform a calculation. The result of that calculation is stored into another register. So basically, this mode moves data between registers only.

Immediate

I mentioned this one above too. Basically immediate addressing lets you load a constant value into a register. For example I might load A with 3, then B with 4 using immediate addressing. The A and B registers can then be added together and stored into a third register C (i.e. register-direct addressing).

Base-Offset

This is the big one. This form of addressing consists of two parts: a base value (in one register) and an offset from that base (in another register or as an immediate value). Essentially you derive the final address by:

Address = Base + Offset

Once you have that address you can use it to access data in RAM or branch to that location (e.g. if/then expression) which is also somewhere in RAM.

Well, one of the neat features of this approach is that the “offset” can be a positive or negative value. This means that I can refer to data that’s either “before” (negative) or “after” (positive) the base value. This sort of thing really becomes useful when performing branching (e.g. jump back a few instructions if register A is zero).

The other nice thing is that the “offset” is kind of optional; setting it to 0 means that only the “base” is used to compute the final address. So really, you get register-indirect built-in and even kind of get absolute/direct addressing too!

That’s all for now. Maybe next time I’ll have a few more circuits to share.