Processors

So far, we have used the raw materials of switches to build circuits that can do arithmetic and store state. Now it’s time to go the rest of the way and build a computer.

Data Path

Let’s build something gradually more computer-like, step by step. (This evolution is necessarily somewhat visual, so please follow along with the diagrams posted elsewhere.) I think it’s interesting to ask yourself a philosophical question: what is a “computer”? It’s clearly a subjective definitional question, so you can decide for yourself.

  1. Start with an adder. You feed in two operands, and you get a result (the sum). Is this a computer? My personal opinion is no; it seems too fixed-function and too ephemeral.
  2. Let’s add two registers for the operands, and feed the adder’s output into both registers. Registers have an enable input that decides whether they take a new input. Now, let’s think about what the “user interface” is to this circuit: instead of two input numbers, it’s the two enable bits. By setting these bits, the user can decide to put the sum of the current register values back into either register (or both at once). This feels slightly more computer-like to me, because we are sequencing a few summations by setting those bits. But it is uncomfortably unrealistic that we have to input data by “reaching in” to the registers to set values.
  3. Next, we’ll add an immediate input to the user interface: a number that we can transfer directly into a register. We will need multiplexers in front of each register to decide whether to take the new value from the sum or the immediate input. The select bits for these multiplexers become part of the user interface too. This seems more “computery” to me, because we can do a whole sequence of sums just be interacting via the external interface.

Instruction

At the final step, we have created an interesting situation where the external interface constitutes an “instruction” for the circuit. Let’s say our numbers (registers and immediate) are all 4 bits. The interfaces has 8 bits total:

  • An enable bit for each of the two registers, which decides whether we’re putting a new value into each.
  • A select bit for each register, which decides whether that new value comes from the immediate input or the sum (the adder’s output). We’ll assume a convention where 1 means the immediate and 0 means the sum.
  • Four immediate bits.

These are all just bits, so we can string them together into an 8-bit number. Let’s lay them out like this:

esesiiii
AABB

What I mean by this is: the most significant bit is the register A’s enable, the next is register A’s select, then there are B’s analogous bits, and then the last 4 bits are the immediate value. So let’s think about this instruction:

11001000

This instruction disables register B, it enables register A to take its value from the immediate. The immediate value is \(1000_2 = 8_{10}\). To summarize, this instruction says “set register A to 8.”

This is interesting! We have created a situation where a plain ol’ number—just a string of bits—means something to our circuit. To emphasize, you could also write this instruction as the hex number c8. And this number means “set register A to 8.”

Machine Code

In a weird way, this view means we’ve defined a programming language. A really bad, primitive programming language. So a program like this:

c8
33
80
20

This program would execute these steps:

  1. Set A to 8.
  2. Set B to 3.
  3. Set A to the sum of A and B.
  4. Set B to the sum of A and B.

We’re kind of programming! In an extremely inconvenient way!

This bit-level “programming language” exists in every processor in existence. It is called machine code, and it is how all software on the computer works. Every program you’ve ever run, and every program you’ve ever written in every language, eventually translates down to machine code for your processor.

Instruction Set Architectures

A machine code language is called an instruction set architecture (ISA). We have, in this lecture, invented a little ISA. Let’s call this the femtoprocessor (FemtoProc) ISA, just to have a name, because this is for an extraordinarily tiny processor.

Some popular ISAs for “real” computers include:

  • [RISC-V][], which we are using in this course.
  • ARM, which your phone almost certainly uses and your laptop might use.
  • Intel’s x86, which your laptop might use.

Each of these ISAs defines a “meaning” for strings of bits. Then, processors interpret those bits to decide which actions to take.

Assembly Language

Writing out the raw bits (or the hex numbers) can be really annoying. To make machine-code programs easier to write, ISAs typically come with a text format with a roughly 1-1 correspondence to machine code. For example, we can define an assembly language for FemtoProc consisting of two kinds of instructions:

  • wrimm {A,B}, N: Set the indicated register to the immediate value N.
  • add {A,B}: Add the current values in registers A and B together, and put the sum into the indicated register.

Using these mnemonics, here’s our program above again in assembly language:

wrimm A, 8
wrimm B, 3
add A
add B

This already looks a little more like a programming language. Maybe you can imagine that it would be pretty easy to write a translator that takes this text format and turns it into actual machine-code bits in the FemtoProc ISA.

A tool that does this translation, from assembly language to machine code, is called an assembler.

Finishing the FemtoProc

Our FemtoProc data path is pretty cool, but we still have to tell it to run one instruction at a time. Real processors can run through an entire list of instructions autonomously.

To make this work, we need to add a few things:

  • An instruction memory, which holds the bits for a series of instructions.
  • A program counter (PC), which is a little register that holds the address of the current instruction.
  • An incrementor, which is just an adder wired up to a constant value like 1 or 4, that increments the PC value on every cycle.

With this setup, the PC counts upward, cycle by cycle, and pulls a new instruction out of the instruction memory each time. This is the magic we need to get the “automatic sequencing” we wanted.

From FemtoProc to a Full Processor

Personally, I think this complete FemtoProc counts as a computer: the smallest, simplest viable computer, but a computer nonetheless.

“Real” processors, however, have a few additional elements that FemtoProc lacks:

  • A register file, i.e., more than 2 registers. For example RISC-V defines 32 registers. Real ISAs pick these registers with binary numbers, not one enable bit per register.
  • An arithmetic logic unit (ALU), which supports more than just adding: it can choose between adding, multiplying, shifting, etc.
  • Some external data memory, which tracks a lot of data. The processor can load and store data from this memory.