Skip to content
Educora
University25 min53 / 59

Computer architecture

Learn the von Neumann architecture, the CPU's instruction cycle and registers, the cache hierarchy, binary arithmetic in two's complement and the basics of IEEE 754 floating point.

Check yourself
In this lesson you will learn
  • Explain the parts of a von Neumann computer and the fetch–decode–execute cycle
  • Compute CPU time and average memory access time
  • Write integers in two's complement and recognise overflow
  • Encode a number in IEEE 754 and explain rounding errors

The 3 GHz processor in your phone ticks 3 billion times a second, yet if you type 0.1 + 0.2 == 0.3 in Python the answer is False. Both facts follow from how a computer is built: the processor fetches and executes instructions one by one, and it stores numbers in a finite number of bits. In this lesson we study the parts of the hardware that affect programmers the most.

The von Neumann architecture and the instruction cycle

Definition
Von Neumann architecture

Program and data are kept in the same memory; the processor (arithmetic logic unit, control unit and registers) fetches instructions from memory one after another and executes them; everything is connected by a bus. To change the program you change the memory contents, not the wiring.

RegisterRole
PC — program counteraddress of the next instruction
IR — instruction registerthe instruction being executed
MAR / MDRmemory address and the data read from or written to it
ACC and general registersintermediate results of calculations
Flagszero, sign, carry and overflow bits
  1. 1
    Fetch

    The address in PC goes to MAR, the instruction is read from memory into IR, and PC moves to the next instruction.

  2. 2
    Decode

    The control unit works out the operation code and the operands.

  3. 3
    Execute

    The ALU computes, or data is read from or written to memory; a jump instruction changes PC. Then the cycle repeats.

Python
memory = [('LOAD', 5), ('ADD', 6), ('STORE', 7), ('PRINT', 7), ('HALT', 0), 20, 22, 0]
pc, acc, executed = 0, 0, 0
while True:
    op, addr = memory[pc]
    pc += 1
    executed += 1
    if op == 'LOAD':
        acc = memory[addr]
    elif op == 'ADD':
        acc += memory[addr]
    elif op == 'STORE':
        memory[addr] = acc
    elif op == 'PRINT':
        print('memory[7] =', memory[addr])
    elif op == 'HALT':
        break
print('instructions executed:', executed, '| PC =', pc, '| ACC =', acc)
▸ Expected output
memory[7] = 42
instructions executed: 5 | PC = 5 | ACC = 42
A toy accumulator CPU: cells 0–4 hold instructions and cells 5–7 hold data — in the same array, as von Neumann intended. The loop is: fetch (memory[pc]), increment PC, decode (if op == …), execute.
t = N · CPI / ft = N · CPI / f
where:
  • tCPU time of the program, s
  • Nnumber of executed instructions
  • CPIaverage clock cycles per instruction
  • fclock frequency, Hz

The “iron law” of processor performance: to go faster you need fewer instructions (a better algorithm and compiler), a lower CPI (pipelining, caches) or a higher clock rate.

Example 1: CPU time

A program executes 2 · 10⁹ instructions with CPI = 1.5 at 3 GHz. What is its CPU time? What if the compiler cuts the instruction count by 20% but CPI rises to 1.6?

Show solution
t = 2 · 10⁹ · 1.5 / (3 · 10⁹) = 3 · 10⁹ / 3 · 10⁹ = 1 s.
New version: N = 1.6 · 10⁹, t = 1.6 · 10⁹ · 1.6 / (3 · 10⁹) = 2.56 / 3 ≈ 0.853 s.
Even though each instruction takes more cycles, the total time fell by ≈ 15% — never judge by one metric alone.

The memory hierarchy and caches

The processor is roughly a hundred times faster than main memory (RAM) — the von Neumann bottleneck. The fix is a memory hierarchy: small, fast cache levels close to the processor and large, slow memories further away. Caches rely on locality: data used recently is likely to be used again soon (temporal locality), and so are neighbouring addresses (spatial locality). That is why memory is fetched in 64-byte cache lines.

LevelTypical sizeApproximate latency
Registersa few hundred bytes≈ 1 cycle
L1 cache32–64 KB per core≈ 1 ns
L2 cache0.25–2 MB per core≈ 3–5 ns
L3 cacheshared, tens of MB≈ 10–20 ns
Main memory (RAM)gigabytes≈ 60–100 ns
SSDhundreds of GB – TB≈ 20–100 µs
Orders of magnitude for a modern desktop processor (exact values depend on the model). Each step down is roughly 3–10 times slower but larger.
AMAT = thit + m · tmiss
where:
  • AMATaverage memory access time
  • thitaccess time on a cache hit
  • mmiss rate
  • tmissmiss penalty (going to the next level)
Example 2: a two-level cache

L1: 1 ns, miss rate 5%. L2: 4 ns, misses 20% of the requests that reach it. RAM: 100 ns. Find the AMAT. What would it be without L2?

Show solution
L1's miss penalty is L2's own AMAT: 4 + 0.2 · 100 = 24 ns.
AMAT = 1 + 0.05 · 24 = 1 + 1.2 = 2.2 ns.
Without L2: AMAT = 1 + 0.05 · 100 = 6 ns — about 2.7 times slower.
Takeaway: cache-friendly code (walking an array in order) is much faster than scattered accesses (a linked list).

Integers: two's complement

In n-bit two's complement the top bit has a negative weight: −2ⁿ⁻¹. So negative numbers start with 1, and no separate “subtractor” is needed: the ALU computes a − b as a + (−b). Addition itself is built from logic gates: in a half adder the sum bit is a XOR b and the carry is a AND b.

−x = (NOT x) + 1; n bit: −2ⁿ⁻¹ … 2ⁿ⁻¹ − 1
where:
  • NOT xall bits of x inverted
  • nnumber of bits (8 bits: −128 … 127)
Example 3: −45 and overflow

a) Write −45 in 8-bit two's complement. b) What does 100 + 50 give in 8-bit signed arithmetic?

Show solution
a) 45 = 00101101 → invert: 11010010 → +1: 11010011.
Check: −128 + 64 + 16 + 2 + 1 = −45 ✓ (read as unsigned it is 211 = 256 − 45).
b) 100 = 01100100, 50 = 00110010, sum = 10010110.
The top bit became 1 → −128 + 16 + 4 + 2 = −106. Two positive numbers gave a negative sum — that is overflow: 150 > 127. The result wraps around: 150 − 256 = −106.
Interactive
Loading simulation…
Set 11010011: unsigned it is 211, in two's complement 211 − 256 = −45. Then try 10000000 (−128) and 01111111 (127) — the limits of 8 bits.
Interactive
Loading simulation…
The sum bit of a half adder: XOR gives 1 only when the inputs differ (1 + 1 = 10₂ — sum 0, carry 1).

Floating point: IEEE 754

x = (−1)ˢ · 1.m₂ · 2^(e − 127)
where:
  • ssign bit (1 bit)
  • ebiased exponent (8 bits, bias 127)
  • mfraction of the mantissa (23 bits; the leading 1 is implicit)

The 32-bit (single) format. The 64-bit double: 1 + 11 + 52 bits, bias 1023, about 15–16 decimal digits of precision. Python's float is a double.

Example 4: encoding −6.25

Write −6.25 in IEEE 754 single precision (bits and hex code).

Show solution
Sign: negative → s = 1.
6.25 = 6 + 0.25 = 110₂ + 0.01₂ = 110.01₂ = 1.1001₂ · 2².
Exponent: e = 2 + 127 = 129 = 10000001₂.
Mantissa (without the leading 1): 1001 followed by 19 zeros.
Bits: 1 | 10000001 | 10010000000000000000000
Groups of 4: 1100 0000 1100 1000 0000 … → C0C80000₁₆.
Python
import struct, math

def to_bits(x, bits=8):
    return format(x % 2 ** bits, '0' + str(bits) + 'b')

def from_bits(s):
    v = int(s, 2)
    return v - 2 ** len(s) if s[0] == '1' else v

print(to_bits(45), to_bits(-45), from_bits('11010011'))
print('100 + 50 in 8 bits:', from_bits(to_bits(100 + 50)))

raw = struct.pack('>f', -6.25).hex()
print('-6.25 as float32:', raw, format(int(raw, 16), '032b'))
print(0.1 + 0.2, 0.1 + 0.2 == 0.3, math.isclose(0.1 + 0.2, 0.3))
print(struct.unpack('>f', struct.pack('>f', 0.1))[0])
▸ Expected output
00101101 11010011 -45
100 + 50 in 8 bits: -106
-6.25 as float32: c0c80000 11000000110010000000000000000000
0.30000000000000004 False True
0.10000000149011612
x % 2 ** bits wraps the number onto the n-bit “circle” — that is two's complement itself. struct shows the real 32 bits: the same C0C80000 we got by hand. 0.1 is an infinite binary fraction (0.000110011…₂), so in float32 it becomes 0.10000000149…, and in double 0.1 + 0.2 = 0.30000000000000004.

Key points

  • In a von Neumann computer program and data share memory; the CPU repeats fetch–decode–execute.
  • CPU time is t = N · CPI / f; performance depends on all three factors.
  • Caches rely on locality; AMAT = thit + m · tmiss.
  • Two's complement: −x = NOT x + 1, n bits cover −2ⁿ⁻¹ … 2ⁿ⁻¹ − 1; leaving the range is overflow.
  • IEEE 754: sign, biased exponent, mantissa; numbers like 0.1 are not exact, so never compare floats with ==.

Check yourself

10 questions. Every correct answer earns XP.

1 / 10
What is −20 in 8-bit two's complement?