Part 1 ended with an ALU that computes, part 2 with registers that remember and state machines that step through a sequence. That is all a processor is made of. This part puts the pieces together into a small processor for a subset of RISC-V, a real and open instruction set. On the way you will turn bits into instructions, write your first programs in assembly and watch the processor run them, wire by wire.

From state machine to processor

A processor is a state machine too. Its state is the program counter (PC), which holds the address of the next instruction, plus the registers and the memory. What makes it more than a traffic light is where its next step comes from: the instructions are stored in memory as numbers, just like data. Patterson and Hennessy name these two principles, instructions represented as numbers and programs stored in memory like data, as the basis of the stored-program computer [1].

Which instructions a processor understands, how they are encoded and what they do is laid down in its instruction set architecture (ISA). Hennessy and Patterson describe the ISA as the boundary between software and hardware [2]: a program written for it runs on every processor that implements it, however that processor is built inside. This article uses RISC-V, whose specification is open and free to read [3]. Its base instruction set for 32 bits, RV32I, has 32 registers of 32 bits each, x0 to x31, and x0 always reads as 0 [3]. We take ten instructions from it: add, sub, and, or, addi, lw, sw, beq, bne and blt. Patterson and Hennessy build their simple processor for almost the same set, in the 64-bit variant with the loads and stores ld and sd [1].

An instruction is a number

Every RISC-V instruction is 32 bits long and divided into fields [3]. The opcode in the lowest seven bits says what kind of instruction it is. Other fields name registers, or hold an immediate: a constant that is part of the instruction itself. Our subset uses four formats: R-type for operations on two registers, I-type for operations with an immediate and for loads, S-type for stores and B-type for branches. The source registers rs1 and rs2 and the destination register rd sit in the same bits in every format, which keeps decoding simple [3].

Click through the bits of an instruction. Whenever the pattern is one the processor understands, the line below shows it as text. That line is assembly language.

Try it

An instruction is 32 bits

funct7
rs2x2
rs1x1
funct3
rdx3
opcodeR-type
add x3, x1, x20x002081B3

R-type: add takes two registers, x1 and x2, and writes the result into x3. The opcode says “register operation”; funct3 and funct7 say which one.

Challenges: 0 of 4

  • Turn add into sub with a single click. (Look at funct7.)
  • Make an add, addi or lw write its result into x10.
  • Make an addi that adds −1.
  • Make a branch that jumps three instructions back (−12 bytes).

Formats and encodings as in chapter 1 of the RISC-V specification; see the references.

Assembly: names for the bits

Nobody writes programs as rows of bits. Assembly language gives every instruction a short name and writes its fields as operands: add x3, x1, x2 means x3 = x1 + x2, with the destination first. An assembler translates each line into its 32 bits: one line, one instruction.

Besides the ten instructions, our assembler knows two pseudo-instructions from the specification: nop, which is addi x0, x0, 0 and changes nothing, and mv rd, rs, which is addi rd, rs, 0 and copies a register [3]. To load a constant, add it to x0: addi x5, x0, 10 puts 10 into x5. Immediates have 12 bits and range from −2048 to 2047, in two's complement as in part 1. A label such as loop: names the address of the next instruction; bne x5, x0, loop jumps there if x5 is not 0, and the assembler works out the distance in bytes. The branch adds it to its own address, within ±4 KiB [3].

Memory is addressed in bytes, so its words of four bytes are at the addresses 0, 4, 8 and so on. lw x8, 0(x5) loads the word at address x5 + 0, sw x8, 32(x0) stores x8 at address 32 [3]. The data memory here holds sixteen words; the first five are 2, 3, 5, 7 and 11.

That is all the assembly this article needs. Real programs also call functions, keep data on a stack and talk to the operating system, but for a processor that runs loops, ten instructions are enough. Step through the examples, then solve the challenges with your own code.

Try it

Your first program

Machine code

AddressMachine codeInstruction
000500293addi x5, x0, 5
400700313addi x6, x0, 7
8006283B3add x7, x5, x6

PC

0

Registers

x0
0
x1
0
x2
0
x3
0
x4
0
x5
0
x6
0
x7
0
x8
0
x9
0
x10
0
x11
0
x12
0
x13
0
x14
0
x15
0
x16
0
x17
0
x18
0
x19
0
x20
0
x21
0
x22
0
x23
0
x24
0
x25
0
x26
0
x27
0
x28
0
x29
0
x30
0
x31
0

Data memory (address: word)

0
2
4
3
8
5
12
7
16
11
20
0
24
0
28
0
32
0
36
0
40
0
44
0
48
0
52
0
56
0
60
0

This is all a programmer sees of the processor: the PC, the 32 registers and the memory. Step through a program and watch the PC: after each instruction it moves on by 4, unless a branch is taken.

Challenges: 0 of 3

  • Change “Sum 1 to 10” so that x6 ends up with 1 + 2 + … + 20 = 210.
  • There is no multiplication here: compute x7 = 6 × 7 with a loop.
  • Copy the first three words of the memory (2, 3, 5) to the addresses 32, 36 and 40.

The register file

Inside, the processor needs a home for its 32 registers: the register file. An instruction like add reads two registers and writes one, so the register file has two read ports and one write port [1]. Reading is combinational: for each read port, a multiplexer like the one in part 1 picks one of the registers. Writing goes through the clock: a decoder selects the register rd, and only if the control signal RegWrite is 1 does it take the new value at the clock edge, like the registers in part 2 [1].

Try it

The register file: read two, write one

DecoderRegWrite 1rd x3Write data 17x00x15x212x30x47x50x60x730123456701234567rs1 x1rs2 x2Read data 15Read data 212

Two multiplexers like the ones in part 1 pick the registers to read. They are combinational and answer at once. The decoder enables the one register to be written, and like every register in part 2 it changes only at the clock edge. x0 always reads 0; writing to it has no effect. Shown are x0 to x7, and x8 to x31 work the same way.

The datapath, piece by piece

Now the parts can be wired together into a datapath. Patterson and Hennessy build it in steps [1, sec. 4.3]. First the fetch: the PC addresses the instruction memory, and an adder computes PC + 4, the address of the next instruction. The PC with its adder is the counter from part 2. For R-type instructions, two registers go into the ALU and the result goes back into the register file. For loads and stores, the ALU adds a register and the sign-extended immediate to form the address. For branches, the ALU subtracts the two registers, and its Zero flag says whether they are equal; a second adder computes the branch target.

Wherever two sources can feed one input, a multiplexer chooses: the register or the immediate for the ALU (ALUSrc), the ALU result or the memory for the register file (MemtoReg), PC + 4 or the branch target for the PC. Instructions and data have separate memories, because a single-cycle processor reads an instruction and accesses data in the same clock cycle and can use each part only once per instruction [1, sec. 4.3]. For bne and blt we add a small box that checks the condition: Zero for beq, not Zero for bne, and for blt the signed comparison [3], which follows from the flags of part 1 as Negative ≠ Overflow.

Try it

The datapath, one kind of instruction at a time

[6–0][19–15][24–20][11–7][31–0][30, 14–12]4PCAddInstructionmemoryControlRegistersrs1rs2rdWrite dataRead data 1Read data 2Imm GenALU controlALUAddBranch?Data memoryAddressWrite dataRead data010101

Every instruction starts the same way: the PC sends its address to the instruction memory, which returns the instruction. At the same time an adder computes PC + 4, the address of the next instruction. The PC is the counter from part 2, counting in steps of 4.

Drawn after the single-cycle datapath in Patterson and Hennessy, sections 4.3 and 4.4, with our additions for addi, bne and blt; see the references.

Control: who sets the switches

Who sets all these multiplexers and enables? The control unit. It reads the opcode and sets seven signals. In a single-cycle processor it needs no memory at all: every instruction is done in one clock cycle, so the signals follow from the opcode alone, a truth table like the ones in part 1 [1, sec. 4.4]. The ALU is controlled in two levels. The control unit sends a two-bit ALUOp, meaning add, subtract or “look at funct3 and funct7”, and a small ALU control turns it into the control lines of the ALU from part 1 [1, sec. 4.4]. Processors that spread an instruction over several clock cycles need a state machine for their control, as promised in part 2.

Take the place of the control unit: set the signals for each instruction, then check your answer.

Try it

Be the control unit

add x3, x1, x2 adds two registers and writes the sum into a third.

  • RegWrite1: write a result into rd
  • ALUSrcsecond ALU input: 0 register rs2, 1 immediate
  • MemRead1: read the data memory
  • MemWrite1: write the data memory
  • MemtoRegvalue for rd: 0 ALU result, 1 memory
  • Branch1: may jump if the condition holds
  • ALUOp00 add, 01 subtract, 10 funct3 and funct7 decide
0 of 5 instructions solved

Running a program

Put together, this is a complete processor. In every clock cycle the datapath settles to the instruction at the PC; at the clock edge the PC, at most one register and at most one word of memory take their new values. Pick a program and step through it, or write your own. The values on the buses are those of the current clock cycle.

Try it

The whole processor

Machine code

AddressMachine codeInstruction
000A00293addi x5, x0, 10
400000313addi x6, x0, 0
800530333loop: add x6, x6, x5
12FFF28293addi x5, x5, -1
16FE029CE3bne x5, x0, loop

In the diagram: the instruction at address 0, during its clock cycle. The next clock edge stores its results.

[6–0][19–15][24–20][11–7][31–0][30, 14–12]4PCAddInstructionmemoryControlRegistersrs1rs2rdWrite dataRead data 1Read data 2Imm GenALU controlALUAddBranch?Data memoryAddressWrite dataRead data010101RegWrite 1ALUSrc 1ALUOp 00MemWrite 0MemRead 0MemtoReg 0Branch 0PCSrc 004addi x5, x0, 100x00A002930101010x5 ← 104

Registers

x0
0
x1
0
x2
0
x3
0
x4
0
x5
0
x6
0
x7
0
x8
0
x9
0
x10
0
x11
0
x12
0
x13
0
x14
0
x15
0
x16
0
x17
0
x18
0
x19
0
x20
0
x21
0
x22
0
x23
0
x24
0
x25
0
x26
0
x27
0
x28
0
x29
0
x30
0
x31
0

Data memory (address: word)

0
2
4
3
8
5
12
7
16
11
20
0
24
0
28
0
32
0
36
0
40
0
44
0
48
0
52
0
56
0
60
0

Everything the processor does is visible here. In every clock cycle the datapath settles to the instruction at the PC, and at the clock edge the PC, at most one register and at most one word of memory take their new values. One instruction per clock cycle: a single-cycle processor.

Drawn after the single-cycle datapath in Patterson and Hennessy, sections 4.3 and 4.4, with our additions for addi, bne and blt; see the references.

Why one clock cycle is too slow

There is a catch. Every instruction gets one clock cycle, so the clock must be slow enough for the slowest one. Patterson and Hennessy's example assumes 200 ps for a memory access and for the ALU, and 100 ps for reading or writing the register file. A load needs all five steps and takes 800 ps, a branch only 500 ps, yet every instruction is given the full 800 ps [1, sec. 4.6]. Move the slider: the slower the memory, the more time is spent waiting.

Try it

One clock cycle for everything

Time each instruction needs, in a clock cycle of 800 ps
  • memory access
  • register file and ALU
  • waiting for the clock edge
InstructionTime
lw800 ps
sw700 ps + 100 ps waiting
add, addi …600 ps + 200 ps waiting
beq, bne, blt500 ps + 300 ps waiting

This program executes 32 instructions. At 800 ps per clock cycle that takes 25.6 ns. If every instruction took only as long as it needs, it would be 18.2 ns, 29 % less.

Times as in the example in Patterson and Hennessy, section 4.6: 200 ps for a memory access and for the ALU, 100 ps for reading or writing the register file. addi needs the same steps as R-type. See the references.

The better idea comes from the assembly line. Split every instruction into its five steps, called IF, ID, EX, MEM and WB, and let five instructions work at the same time, each in a different step [1, sec. 4.6]. This is pipelining, and it is the topic of the next part.

Where to go from here

You have now seen every layer of a simple processor, from the switch in part 1 to a program that runs on it. Real processors add much more: multiplication, function calls with a stack, interrupts, caches and, above all, pipelines. Patterson and Hennessy's Computer Organization and Design follows this path in detail [1], Hennessy and Patterson's Computer Architecture: A Quantitative Approach shows how processors are made fast [2], and the full instruction set is in the RISC-V specification [3].

Back to part 1: from a switch to an ALU or part 2: memory and state machines.

References

  1. D. A. Patterson and J. L. Hennessy, Computer Organization and Design RISC-V Edition: The Hardware/Software Interface, 2nd ed. Morgan Kaufmann (Elsevier), 2020. ISBN 978-0-12-820331-6. Used here: chapter 2 “Instructions: Language of the Computer”; sections 4.3 “Building a Datapath”, 4.4 “A Simple Implementation Scheme” and 4.6 “An Overview of Pipelining”; appendix A “The Basics of Logic Design.” Publisher's page
  2. J. L. Hennessy and D. A. Patterson, Computer Architecture: A Quantitative Approach, 6th ed. Morgan Kaufmann (Elsevier), 2017. ISBN 978-0-12-811905-1. Publisher's page
  3. RISC-V International, The RISC-V Instruction Set Manual, Volume I: Unprivileged Architecture, version 20260120, 2026. Used here: chapter 1 “RV32I Base Integer Instruction Set, Version 2.1.” Online at RISC-V International