Hardware, software and the stored program
Internal components, the three buses and their widths, I/O controllers, the stored program concept, and von Neumann against Harvard.
Do this lesson in the simulatorAt GCSE (F9.3) you met the von Neumann architecture: a processor, a memory that holds both the program and its data, and buses joining them. At A level you need the same picture drawn precisely: which components sit inside the computer, what each bus carries and in which direction, what exactly the stored program concept says, and when a designer would choose the Harvard architecture instead. This module then opens the processor itself, and it uses BugBot's microcontroller as the running example of a real embedded processor.
Hardware and software
Hardware is the physical components of a computer system. Software is the programs: sequences of instructions, together with the data they work on. Neither is any use alone. Hardware only does something when a program tells it what to do, and a program can only run on hardware.
The link between them is the instruction set. A processor can carry out a fixed set of simple operations, each with a binary code. Every piece of software, whatever language it was written in, is turned into (or interpreted by a program already made of) those binary instructions. Your Python program on BugBot runs inside the Python interpreter, which is itself machine code for the robot's processor. The robot's firmware, written in C, was compiled into the same machine code. So there are layers: your program, then the interpreter and operating system, then firmware and drivers, then the hardware that finally does the work.
The internal components
| Component | What it does |
|---|---|
| Processor | fetches instructions from main memory, decodes them and executes them |
| Main memory | holds the instructions and data of the programs that are running, each byte at a numbered address; the processor can read any address directly |
| Address bus | carries a memory address from the processor to memory or an I/O controller: one way only |
| Data bus | carries data or instructions between the processor, memory and I/O controllers: both ways |
| Control bus | carries control and timing signals, such as memory read, memory write, the clock, interrupt request, bus request, bus grant and reset: both ways, since some lines go into the processor and some out |
| I/O controller | an electronic circuit that connects a device (a keyboard, a disk, a motor) to the buses, translating between the processor's signals and the device's |
A bus is a set of parallel wires, and each wire carries one bit. So a bus's width is a number of bits, and it sets a limit:
- an address bus of
nwires can make 2n different addresses, so it limits how much memory the processor can address directly; - a data bus of
wwires carries onew-bit value at a time, usually the processor's word length.
for n in [8, 16, 20, 32]:
addresses = 2 ** n
print(f"{n:2}-bit address bus: {addresses:,} addresses")
print("32 bits of address, one byte at each:", 2 ** 32 // 1024 ** 3, "GiB")
That last line is why a processor with a 32-bit address bus is limited to 4 GiB of directly addressable memory.
Why I/O controllers?
The processor cannot drive a motor or read a camera directly. The devices work at different voltages and speeds, and each speaks its own protocol. An I/O controller sits between: the processor reads and writes it through the buses like memory, and the controller deals with the device. On BugBot, the processor does not switch motor current itself. It sends a byte to a motor driver chip on the Motion board, and that chip switches the motor.
The stored program concept
The stored program concept: machine code instructions are stored in main memory, alongside data, and are fetched and executed serially (one after another) by a processor that performs arithmetic and logical operations.
Three consequences follow, and exam questions often want them:
- To change the job, load a different program. Before stored programs, machines such as ENIAC were rewired for each problem.
- Instructions and data look the same. Memory holds binary patterns. A pattern is an instruction only because the program counter points at it. This lets one program handle another as data, which is how compilers, loaders and operating systems work.
- Instructions are executed one at a time, in order, unless an instruction changes the program counter (a branch).
memory = [0b00010101, 0b00100110, 0b11111111, 0b00000011, 0b00000100]
for address, value in enumerate(memory):
print(address, format(value, "08b"), "as a number:", value)
Nothing in that list says which bytes are instructions. The processor decides by where the program counter points, which is why a bug that jumps into data makes a program do nonsense.
Von Neumann and Harvard
| Von Neumann | Harvard | |
|---|---|---|
| Memory | one memory for instructions and data | separate memories for instructions and for data |
| Buses | one set of buses shared by both | a separate set of buses for each |
| Fetching | an instruction and a data item cannot use the bus at the same time | an instruction and data can be fetched at the same time |
| Word lengths and memory types | the same for both | can differ: instructions in flash or ROM, data in RAM |
| Typical use | general-purpose computers: PCs, laptops, servers | embedded systems and digital signal processors |
The weakness of von Neumann is that one shared bus limits how fast instructions and data move between memory and processor: the von Neumann bottleneck. Its strength is simplicity and flexibility: memory is not split, so any free space can hold program or data.
Harvard suits embedded systems because their program is fixed and lives in non-volatile memory, the data lives in a small RAM, and the separate paths give fast, predictable timing.
Contemporary processors mix the two. Main memory is shared, von Neumann style, but inside the processor the cache is split into an instruction cache and a data cache with separate paths, Harvard style. This is often called a modified Harvard architecture.
The robot's processor
BugBot's main processor is an ESP32-P4 microcontroller: processor cores, some RAM and many I/O interfaces on one chip, made for embedded systems. Its program (the firmware and the Python interpreter) is kept in flash memory that survives power off, and it works on data in RAM. When you run a program, your Python source is sent to the robot as data, and the interpreter, itself a stored program, reads it and acts on it: the stored program concept in action.
Task: bus widths
Work out what a bus of a given width can carry.
- For each address bus width in
[12, 16, 24, 32]bits, print how many different addresses it can make, as a whole number with no commas, in the formaddress bus 16 bits: 65536 addresses. - For each data bus width in
[8, 16, 32]bits, print the smallest and largest unsigned whole number it can carry, in the formdata bus 8 bits: 0 to 255.
Print the seven lines in that order. The robot does not move.
# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()
address_widths = [12, 16, 24, 32]
data_widths = [8, 16, 32]
Challenges
- A processor has a 20-bit address bus and each address holds one byte. How many MiB can it address?
- Give two reasons a washing machine controller would use Harvard architecture.
- Explain why the data bus must be bidirectional but the address bus need not be.