---
title: 'Instructions and the Program Counter'
source: 'https://academia.sh/en/courses/how-computers-work/instructions-and-the-program-counter'
course: 'How Computers Work'
language: en
updated: '2026-08-17T18:08:13+00:00'
license: 'CC BY-SA 4.0'
---

# Instructions and the Program Counter

The encoding of instructions as bit patterns, the fetch-decode-execute cycle, and how program flow arises.

The previous lesson established that data resides in memory as addressed bytes.
Instructions reside in the same memory, in the same form: an instruction is nothing but
a bit pattern encoded according to a defined rule. The distinction between program and
data is not in the hardware but in the interpretation applied to those bytes.

This lesson asks: how does the processor turn a bit pattern in memory into an action,
and how does it determine the next action?

## The Instruction Set

Every processor family has a defined **instruction set**: a contract specifying which
operations exist, which bit pattern encodes each one, and which operands each takes.
The instruction set is the interface between hardware and software; two different
processors that implement the same instruction set can run the same program.

The typical instruction classes are few:

- **Data movement** — loading from memory into a register, storing from a register to
  memory.
- **Arithmetic and logic** — addition, subtraction, comparison, bit operations.
- **Control flow** — branching, subroutine call and return.
- **System** — interrupts, changing privilege level, operating system calls.

Every construct in a programming language ultimately reduces to instructions from these
classes.

## How an Instruction Is Encoded

An instruction encoding is a pattern divided into bit fields. One field, the **opcode**,
says which operation to perform; the others specify the operands.

To make the structure concrete, consider a 32-bit encoding defined purely for teaching
purposes. This encoding does not belong to any real processor; field layout differs
across architectures.

| Bit range | Field | Width |
|---|---|---|
| 31-24 | opcode | 8 bits |
| 23-16 | destination register | 8 bits |
| 15-8 | first source | 8 bits |
| 7-0 | second source | 8 bits |

If the course's shared example is decoded under this rule:

$$
\texttt{0x41424344} \rightarrow \underbrace{\texttt{0x41}}_{\text{op}}\ \underbrace{\texttt{0x42}}_{\text{dest}}\ \underbrace{\texttt{0x43}}_{\text{src 1}}\ \underbrace{\texttt{0x44}}_{\text{src 2}}
$$

If opcode `0x41` is assumed to mean "add", the instruction says: add the contents of
registers 67 and 68, write the result to register 66.

The same thirty-two bits have now been interpreted four times over the course of this
topic: as an integer, a floating-point number, text, and now an instruction. Which
interpretation applies to the pattern is decided by where the processor reads those
bytes from — as an instruction, or as data.

Fixed-length encodings (every instruction the same width) simplify the decode stage.
Variable-length encodings instead shrink program size by fitting frequently used
instructions into fewer bytes. Both approaches are common, and the choice reflects the
architecture's design priorities.

## Machine Language and Assembly

Writing bit patterns by hand is error-prone. **Assembly** is a one-to-one
representation that gives each instruction a readable name:

```
    load    r1, [1000]     ; load the value at address 1000 into r1
    load    r2, [1004]
    add     r3, r1, r2     ; r3 = r1 + r2
    store   [1008], r3
```

The relationship between assembly and machine language is nearly one-to-one: each line
translates to one instruction. A line in a high-level language, by contrast, can
correspond to dozens of instructions; that translation is performed by the compiler and
is covered in this topic's final lesson.

## The Fetch-Decode-Execute Cycle

From the moment it powers on until it powers off, the processor repeats a single cycle:

1. **Fetch.** The instruction is read from the address the program counter points to.
2. **Decode.** The bit fields are parsed; the opcode and operands are determined.
3. **Execute.** The operation is carried out by the arithmetic and logic unit or the
   memory unit.
4. **Write back.** The produced value is written to the destination register or to
   memory.
5. The program counter advances to the next instruction, and the cycle starts over.

The **program counter** is a special register that holds the address of the next
instruction. For ordinary instructions it increases by the instruction width at every
step; this is why a program flows "top to bottom".

This cycle is the definition of everything the processor does. The difference between
an operating system, a browser, or a game lies not in the cycle but in the sequence of
instructions it reads.

## Overlapping the Steps

The steps of the cycle use different hardware units: fetching occupies the memory
interface, decoding the control unit, executing the arithmetic unit. Waiting for each
step in sequence leaves most of these units idle at any given moment.

A **pipeline** closes this gap: while one instruction executes, the next is decoded,
and a third is fetched. The time to complete a single instruction does not change; the
number of instructions completed per unit of time increases.

The cost of a pipeline is branching. In a conditional branch, the address of the next
instruction is unknown until the condition is computed; which instructions to feed into
the pipeline is uncertain. The processor makes a prediction, and if the prediction is
wrong, it discards the instructions already in the pipeline. Branches that are hard to
predict therefore carry a measurable cost.

This repeats an observation from the first topic: instruction count is not the sole
determinant of duration.

## Changing the Flow

The fact that the program counter can be written to directly is the source of every
control structure.

An **unconditional branch** writes a new address to the program counter; flow continues
from there. A **conditional branch** first examines the result of a comparison.
Comparison instructions write their result to flag bits — whether the result is zero,
negative, or overflowed. A conditional branch reads these flags and changes the program
counter only if the condition holds.

The correspondence with high-level constructs is built this way:

| Construct | Instruction equivalent |
|---|---|
| `if` | Compare; branch to the end of the block if the condition fails |
| `while` | A branch testing the condition + a branch back to the top at the end of the body |
| Function call | Save the return address, branch to the target; on return, branch to the saved address |

Where the return address is saved during a function call is the function of the call
stack; stack layout is covered in this topic's final lesson.

The following program simulates the fetch-decode-execute cycle on the teaching
encoding:

```python
OP_NAMES = {0x41: "add", 0x42: "subtract", 0xFF: "halt"}


def decode(instruction: int) -> tuple[str, int, int, int]:
    """Splits a 32-bit instruction into its fields."""
    op   = (instruction >> 24) & 0xFF
    dest = (instruction >> 16) & 0xFF
    src1 = (instruction >> 8) & 0xFF
    src2 = instruction & 0xFF
    return OP_NAMES.get(op, "unknown"), dest, src1, src2


def run(memory: list[int], registers: dict[int, int]) -> None:
    pc = 0                                   # program counter
    while pc < len(memory):
        op, dest, s1, s2 = decode(memory[pc])
        if op == "halt":
            break
        if op == "add":
            registers[dest] = registers.get(s1, 0) + registers.get(s2, 0)
        elif op == "subtract":
            registers[dest] = registers.get(s1, 0) - registers.get(s2, 0)
        pc += 1                              # advance to the next instruction


print(decode(0x41424344))    # ('add', 66, 67, 68)

memory = [0x41424344, 0x42024344, 0xFF000000]
registers = {67: 10, 68: 4}
run(memory, registers)
print(registers[66], registers[2])  # 14 6
```

The `pc += 1` line is the program counter's ordinary advance. If a branch instruction
were added, all it would do is write a different value to this variable.

## Summary

- Instructions reside in memory just like data; the distinction lies in the
  interpretation applied to the bytes.
- The instruction set is the contract between hardware and software, and it contains a
  small number of instruction classes.
- An instruction is a bit pattern divided into opcode and operand fields; field layout
  is architecture-specific.
- The processor continuously repeats the fetch-decode-execute cycle; the difference
  between programs lies not in the cycle but in the sequence of instructions read.
- The program counter holds the address of the next instruction; conditional and
  unconditional branches produce every control structure by changing this register.

## Next Step

Every turn of the cycle requires at least one memory access, and as the previous lesson
showed, main memory is hundreds of times slower than the processor. The next lesson
covers the cache layer that closes this gap, and the access pattern a program must
follow for the cache to work efficiently.
